Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
543dfdeac8 | ||
|
|
b1b1cd3263 | ||
|
|
55563dac1c | ||
|
|
ec350645a4 | ||
|
|
e4aa6e1bdf | ||
|
|
f7f06010ae | ||
|
|
73dc4536b0 | ||
|
|
2b298ab9fc | ||
|
|
3a36c2e6ae | ||
|
|
6448de834c | ||
|
|
f4d145c3a0 | ||
|
|
0bacbaeff7 | ||
|
|
79d8dd0020 | ||
|
|
9bce9e4fde | ||
|
|
232a545d19 | ||
|
|
c1deb99b70 | ||
|
|
1625bdb454 | ||
|
|
b982822fdc | ||
|
|
50820dfc9c | ||
|
|
ee0fb39822 | ||
|
|
9760baff9c | ||
|
|
f7cd14d0f6 | ||
|
|
b29603f1b7 | ||
|
|
afb04a19f9 | ||
|
|
50e9ae6ca7 | ||
|
|
05a85a8e9c | ||
|
|
2af50f7b78 | ||
|
|
15dd4d3c07 | ||
|
|
058d55b68c | ||
|
|
22ebd5f2d6 | ||
|
|
8744abc39f | ||
|
|
1574b9380b | ||
|
|
66441fb2cb | ||
|
|
2466307b74 | ||
|
|
6a5b25045c | ||
|
|
742a922b90 | ||
|
|
2e2d534257 | ||
|
|
271700692a | ||
|
|
6bc699672b | ||
|
|
c07b0d0912 | ||
|
|
61cac4eebc | ||
|
|
b723da6574 | ||
|
|
0159de8a21 | ||
|
|
c961a73c42 | ||
|
|
2bafb1704b | ||
|
|
cf84ed3881 | ||
|
|
95de5bad78 | ||
|
|
7cdb3b09d9 | ||
|
|
b3cf44631c | ||
|
|
e3d96c349f | ||
|
|
f0e42f0654 | ||
|
|
bf1da40976 | ||
|
|
9994dba0f7 | ||
|
|
294885be1a | ||
|
|
82cdc51ca8 | ||
|
|
e855a85c85 | ||
|
|
b9c49c93b8 | ||
|
|
dc393ed14a | ||
|
|
cbae2aecfc | ||
|
|
66e0c53593 | ||
|
|
d7bf2aa783 | ||
|
|
90d0e76eb6 | ||
|
|
6f3c635f6f | ||
|
|
2b12cb4a7e | ||
|
|
1a9fb9caca | ||
|
|
2da92475ba | ||
|
|
36d1dcd9cc | ||
|
|
eb62c3bd27 | ||
|
|
05cd7f170f | ||
|
|
0d4655d1db | ||
|
|
e42e3e55ca | ||
|
|
2a6039da98 | ||
|
|
33035e9978 | ||
|
|
5f682023a6 | ||
|
|
fe2e4a1010 | ||
|
|
50e479332c | ||
|
|
49c3770d87 | ||
|
|
ae24cad4b8 | ||
|
|
a8eab99e44 | ||
|
|
b6d97c3f1d | ||
|
|
6daca1b630 | ||
|
|
9a3695c949 | ||
|
|
9499b9f9be | ||
|
|
5fdc6b55ee | ||
|
|
3f2b44f983 | ||
|
|
18a8272fe5 | ||
|
|
070f6a00a8 | ||
|
|
12dabedf0d | ||
|
|
24da600a72 | ||
|
|
84561408d1 | ||
|
|
9065839328 | ||
|
|
3f30bf156c | ||
|
|
b659ecfa7e | ||
|
|
c240a49f04 | ||
|
|
e89b37190e | ||
|
|
ff61a74a8a | ||
|
|
39db5a1efd | ||
|
|
0802014779 | ||
|
|
bfc30274ff | ||
|
|
bec7a0becb | ||
|
|
fe8fc1be25 | ||
|
|
bcb7cff5f2 | ||
|
|
8023c29c3f | ||
|
|
147fafb067 | ||
|
|
674f4a2cb3 | ||
|
|
b326fe12d9 | ||
|
|
263f0d1c81 | ||
|
|
47ae5b4758 | ||
|
|
25c54dad84 | ||
|
|
48b8efe9c6 | ||
|
|
5d34fcdccd | ||
|
|
e63546bda6 | ||
|
|
04c8ca9718 | ||
|
|
08d69e3975 | ||
|
|
bfc165c46d | ||
|
|
9764f3f384 | ||
|
|
b1915b1ad6 | ||
|
|
5991eb04fa | ||
|
|
b3e07652cc | ||
|
|
cad7551caf | ||
|
|
8ac132fee1 | ||
|
|
ec94c93b24 | ||
|
|
96ed1ff950 | ||
|
|
9d4f15c1c9 | ||
|
|
6bcb7adeb4 | ||
|
|
d311e10bea | ||
|
|
adf7834529 | ||
|
|
c837d8e1d3 | ||
|
|
ce61c60af5 | ||
|
|
dc0dd43406 | ||
|
|
6c2744d373 | ||
|
|
f671e1204c | ||
|
|
c0af2622af | ||
|
|
6b511cc844 | ||
|
|
4ade0f98aa | ||
|
|
67a672e6ed | ||
|
|
54e8ea13f4 | ||
|
|
3110be1ca5 | ||
|
|
ddc6b48a49 | ||
|
|
5a2e838def | ||
|
|
ebc6d76034 | ||
|
|
42fc70a0eb | ||
|
|
87fee37711 | ||
|
|
4870351d2f | ||
|
|
aba387c526 | ||
|
|
36d8e1ceee | ||
|
|
68c9a28d06 | ||
|
|
30c9af3631 | ||
|
|
f42055be57 | ||
|
|
17a5c37d06 | ||
|
|
8c45982cc6 | ||
|
|
6697100f95 | ||
|
|
94812b4242 | ||
|
|
4fae777e89 | ||
|
|
7ea4b02460 | ||
|
|
11423af777 | ||
|
|
23c67ac47c | ||
|
|
3ae00072ae | ||
|
|
b4b9ecccb4 | ||
|
|
53b68887b6 | ||
|
|
ea8e555f53 | ||
|
|
407427f627 | ||
|
|
2b2d9dc2de | ||
|
|
b4ad1ff5bc | ||
|
|
ab538670d3 | ||
|
|
691a9b8b2b | ||
|
|
ee3fcdb96a | ||
|
|
ba88ee7c92 | ||
|
|
9cd5a47bbe | ||
|
|
be3ab7d13d | ||
|
|
cdbd480f4d | ||
|
|
0538e46023 | ||
|
|
ebf84037cc | ||
|
|
1b3cdbc7fb | ||
|
|
1d13d189c1 | ||
|
|
7fcdecde95 | ||
|
|
bc852a6fba | ||
|
|
28ff6b3532 | ||
|
|
b0e1c09579 | ||
|
|
d3a5cd22a6 | ||
|
|
96a1628033 | ||
|
|
d6de0979fb | ||
|
|
19301b9005 | ||
|
|
02c76da51a | ||
|
|
435597286f | ||
|
|
8e3b5e38e4 | ||
|
|
9fcab6e881 | ||
|
|
afd5ebb563 | ||
|
|
e169c0f49b | ||
|
|
8c97056b0e | ||
|
|
4be1501f57 | ||
|
|
59402569ed | ||
|
|
0aff2a3139 | ||
|
|
9c4271891a | ||
|
|
7c240199f3 | ||
|
|
1e9ab79cad | ||
|
|
d37b7b3702 | ||
|
|
ac2c79a7ce | ||
|
|
84cc7d5eb0 | ||
|
|
27467bdb67 | ||
|
|
1ee1a3f687 | ||
|
|
05c6f9f822 | ||
|
|
952052869f | ||
|
|
df8b216357 | ||
|
|
79faa1f706 | ||
|
|
31cec42b23 | ||
|
|
05beafd5bb | ||
|
|
cba7c5bd80 | ||
|
|
9b22b6d883 | ||
|
|
2a6940014a | ||
|
|
2a1941112e | ||
|
|
5e2ea082d1 | ||
|
|
ba33806cc3 | ||
|
|
62eeb97eb8 | ||
|
|
0a24252fca | ||
|
|
243f4e6b0a | ||
|
|
3bc277f525 | ||
|
|
f0bfdfaf19 | ||
|
|
2979430137 | ||
|
|
1c4b759c4e | ||
|
|
f67022267f | ||
|
|
febee82e9e | ||
|
|
66d8cdc56e | ||
|
|
8d59c007c8 | ||
|
|
3a0b4988fc | ||
|
|
b7bbe13c91 | ||
|
|
4cbe174c13 | ||
|
|
bbe63e9a93 | ||
|
|
6f0f0d5136 | ||
|
|
e055c2ccf2 | ||
|
|
08d61e4b25 | ||
|
|
9c9d8e855e | ||
|
|
bb7b990055 | ||
|
|
0aee0805a9 | ||
|
|
9afc1043c9 | ||
|
|
1622eb63b4 | ||
|
|
d769f8f35e | ||
|
|
9cd56804ea | ||
|
|
6d17b42beb | ||
|
|
ac546881e8 | ||
|
|
77aca31ffe | ||
|
|
9395bb105d | ||
|
|
47feb1b6a0 | ||
|
|
f98afc1d83 | ||
|
|
6b407a339b | ||
|
|
c91d2d9c2b | ||
|
|
f82fbdcaed | ||
|
|
c6817c80b0 | ||
|
|
6fc468bddb | ||
|
|
5b6fc9b415 | ||
|
|
5c45e4aed4 | ||
|
|
5fa617a8aa | ||
|
|
387dd72d54 | ||
|
|
91bbfa52ba | ||
|
|
b80fe27705 | ||
|
|
dbcb3717d6 | ||
|
|
7836493d23 | ||
|
|
d27c7b0e1f | ||
|
|
26bc9d9d61 | ||
|
|
684cf05d75 | ||
|
|
417c719c78 | ||
|
|
dc63e25995 | ||
|
|
74e9f76aaa | ||
|
|
40d383faae | ||
|
|
eaac3bbd76 | ||
|
|
ae12432294 | ||
|
|
d0a6792bdf | ||
|
|
a5b67f3084 | ||
|
|
6e48e5dfcb | ||
|
|
83c93beaa6 | ||
|
|
ddd4afbdba | ||
|
|
a2cc2623a8 | ||
|
|
d4da5be4ee | ||
|
|
b03e2173a5 | ||
|
|
f547ef82e2 | ||
|
|
254065d3c4 | ||
|
|
29b38ab9e0 | ||
|
|
0e627c9b09 | ||
|
|
f8b019c7c8 | ||
|
|
c4419268b8 | ||
|
|
ade26dedd8 | ||
|
|
574e92fc57 | ||
|
|
8c6e31bda0 | ||
|
|
70d7a28a36 | ||
|
|
78def5da6e | ||
|
|
ffe2cc74b8 | ||
|
|
99b1d9b677 | ||
|
|
869378d1ca | ||
|
|
f306ca5a49 | ||
|
|
b39c762557 | ||
|
|
98e15914eb | ||
|
|
8c47368fff | ||
|
|
4176480f39 | ||
|
|
f81a15b7cb | ||
|
|
b3ba12593d | ||
|
|
51aef55c83 | ||
|
|
ec41e0fe1e | ||
|
|
2d36a76b26 | ||
|
|
9f5e7c4a41 | ||
|
|
e98f3eae10 | ||
|
|
5b5075b2c7 | ||
|
|
238baa14fa | ||
|
|
620532049c | ||
|
|
67566f155b | ||
|
|
d976937486 | ||
|
|
3f4410ff68 | ||
|
|
4c26585ef6 | ||
|
|
6fa7d64814 | ||
|
|
67eeedfb1d | ||
|
|
d0a823c8b0 | ||
|
|
df9d535769 | ||
|
|
219ded1b91 | ||
|
|
a0eef07958 | ||
|
|
c7af8bc2cc | ||
|
|
349873ab63 | ||
|
|
f2cf4a393f | ||
|
|
2c1c504fc5 | ||
|
|
625a27fac2 | ||
|
|
5164073bf0 | ||
|
|
66dd76e42b | ||
|
|
bb05011dca | ||
|
|
c296e62543 | ||
|
|
8847f8ed68 | ||
|
|
b536a2ac7c | ||
|
|
a33cbbe6b1 | ||
|
|
afb7efd11b | ||
|
|
613298c4ab | ||
|
|
d39401e2d3 | ||
|
|
a5602a71fa | ||
|
|
0bd45a090a | ||
|
|
68f570c0a5 | ||
|
|
f0456b6377 | ||
|
|
dfeeeacb8c | ||
|
|
72d683a86a | ||
|
|
ee337bbfb8 | ||
|
|
d4729a3795 | ||
|
|
352a2d8851 | ||
|
|
c7da787265 | ||
|
|
159ba856a6 | ||
|
|
6d3624147e | ||
|
|
f1e647f04b | ||
|
|
90d72c6a5e | ||
|
|
5ce7ee2e3b | ||
|
|
c1bcf5dab2 | ||
|
|
11ab91c0fb | ||
|
|
6c84492618 | ||
|
|
91dc65b7ee | ||
|
|
2ec9c24b3e | ||
|
|
eab8b46879 | ||
|
|
67e0367987 | ||
|
|
cd43f618e0 | ||
|
|
3687b0f104 | ||
|
|
b1d245651d | ||
|
|
020a37785a | ||
|
|
14b3dff8e7 | ||
|
|
1567af2f12 | ||
|
|
35f599d184 | ||
|
|
c361e0a8a6 | ||
|
|
38c60776f6 | ||
|
|
9bedb0faaf | ||
|
|
965915bffa | ||
|
|
a711bef57f | ||
|
|
a736b55499 | ||
|
|
8720c56291 | ||
|
|
2c5650ca9d | ||
|
|
f3ae3b0df5 | ||
|
|
37766232bf | ||
|
|
5e8717918c | ||
|
|
d624ceb72b | ||
|
|
fcb5076e42 | ||
|
|
067bdde570 | ||
|
|
fcad4a1343 | ||
|
|
cf0dd31714 | ||
|
|
e3e75e5720 | ||
|
|
5369f2449c | ||
|
|
a7dae16877 | ||
|
|
eb46fe30a2 | ||
|
|
aa1b678b54 | ||
|
|
e53f75f2b9 | ||
|
|
538b830c3e | ||
|
|
cb32338cae | ||
|
|
cf9284f057 | ||
|
|
3ac57066ce | ||
|
|
db19cdea42 | ||
|
|
2d7965b7e6 | ||
|
|
4a2d1d6555 | ||
|
|
1b93904a27 | ||
|
|
303789fc93 | ||
|
|
3c6417896e | ||
|
|
23a4fc65fe | ||
|
|
575e0b9494 | ||
|
|
e49998593e | ||
|
|
33c6eb3e37 | ||
|
|
405c14f7e1 | ||
|
|
b26304fa59 | ||
|
|
1c834e8938 | ||
|
|
8e51528dc1 | ||
|
|
898e01ccb5 | ||
|
|
24725090f5 | ||
|
|
cef74d0186 | ||
|
|
4e2a1230f3 | ||
|
|
a8959f3987 | ||
|
|
bce9116cff | ||
|
|
b78c3bc36b | ||
|
|
94be8a1b8a | ||
|
|
c19ea55ed7 | ||
|
|
34c2c50f42 | ||
|
|
6f219ad1da | ||
|
|
2487d741b6 | ||
|
|
f62c26b9b6 | ||
|
|
1d7c56d423 | ||
|
|
827507603a | ||
|
|
0977602647 | ||
|
|
ee86a5c1e0 | ||
|
|
c4ffe1b8b3 | ||
|
|
eaebcf77f3 | ||
|
|
fa56d91c08 | ||
|
|
04623a6edf | ||
|
|
a3afaeec8e | ||
|
|
5d7a1cf7a0 | ||
|
|
4e29f274c5 | ||
|
|
05a321dc8c | ||
|
|
7818551e6a | ||
|
|
5dfa6ae618 | ||
|
|
9b6f104ffe | ||
|
|
80118f4561 | ||
|
|
1fbb6871a0 | ||
|
|
474b05e5c4 | ||
|
|
7d1e6b545d | ||
|
|
01e31cf3c3 | ||
|
|
640cc06d63 | ||
|
|
b2f97f5406 | ||
|
|
d510356f20 | ||
|
|
cb6753742b | ||
|
|
73253206f2 | ||
|
|
90f9162e3f | ||
|
|
c74f203c0d | ||
|
|
afa7eee4ad | ||
|
|
228b20eb7c | ||
|
|
60c4ede916 | ||
|
|
85e2845fd8 | ||
|
|
c1bedd5dce | ||
|
|
e424f02812 | ||
|
|
7dd9da12a7 | ||
|
|
67109b291d | ||
|
|
bee0e28e5b | ||
|
|
9ee79a16bb | ||
|
|
bbc0fe0a63 | ||
|
|
96077905fb | ||
|
|
00431ebecb | ||
|
|
203ddd0c37 | ||
|
|
ab91165946 | ||
|
|
6a7609d707 | ||
|
|
ec6f95d970 | ||
|
|
0e54cad8ee | ||
|
|
c093e378d8 | ||
|
|
a829da1775 | ||
|
|
7d83083525 | ||
|
|
a1f104cd30 | ||
|
|
895852f1d2 | ||
|
|
9a0e8f38de | ||
|
|
ffb2258bb1 | ||
|
|
d95a7d1a58 | ||
|
|
3a8d0ccbc2 | ||
|
|
4ed4f52121 | ||
|
|
d416b700e3 | ||
|
|
d3055a9d2a | ||
|
|
cb66331e87 | ||
|
|
5263610aac | ||
|
|
5d53abc5ff | ||
|
|
89c4822fa0 | ||
|
|
1f4037b93c | ||
|
|
660f8607a6 | ||
|
|
52cd9b359c | ||
|
|
127a634ba2 | ||
|
|
09869ec135 | ||
|
|
4e52765755 | ||
|
|
2b3ae52fa5 | ||
|
|
63fdbfb8cf | ||
|
|
5c00882bff | ||
|
|
043edd4990 | ||
|
|
fd3d8b3b98 | ||
|
|
e0354005ea | ||
|
|
da34ccd54e | ||
|
|
4460770981 | ||
|
|
5782dae432 | ||
|
|
494659ab1c | ||
|
|
eae9177b18 | ||
|
|
08afaa7982 | ||
|
|
d7179b404b | ||
|
|
be71239b7c | ||
|
|
d330d77c33 | ||
|
|
a8bdd19a03 | ||
|
|
720d1e2a71 | ||
|
|
f685f5d2a5 | ||
|
|
0a84b90373 | ||
|
|
f98cda71c0 | ||
|
|
b28c95d31a | ||
|
|
abec6af3ac | ||
|
|
e802d2960b | ||
|
|
7d26bfbfad | ||
|
|
4e251b10b9 | ||
|
|
f8380e9dcd | ||
|
|
c2243e2c28 | ||
|
|
ffcde49975 | ||
|
|
f4144a63f8 | ||
|
|
2655a318c2 | ||
|
|
e553b6780b | ||
|
|
398e0fd3fb | ||
|
|
de3c0f639a | ||
|
|
1096cd00b9 | ||
|
|
260be7c8ea | ||
|
|
bed554cc50 | ||
|
|
f60fd6a5d4 | ||
|
|
a6c19950a0 | ||
|
|
e1ff60a57d | ||
|
|
5d324c0a6f | ||
|
|
4b17932bc8 | ||
|
|
fe3d1b1cfe | ||
|
|
42c5b63ad7 | ||
|
|
e65eee49c4 | ||
|
|
03e1e8e203 | ||
|
|
a135e14348 | ||
|
|
dbc8c7c607 | ||
|
|
404e8290b8 | ||
|
|
eba273a72a | ||
|
|
75a1f2edbf | ||
|
|
1bf4a95643 | ||
|
|
4358386906 | ||
|
|
f4bc50c20f | ||
|
|
985ce8e031 | ||
|
|
2b72bc08d7 | ||
|
|
fa348e5027 | ||
|
|
cfa2aad8b4 | ||
|
|
8c145c42f3 | ||
|
|
2b6b48688e | ||
|
|
9b0617f889 | ||
|
|
fb5c963f17 | ||
|
|
ff679125e6 | ||
|
|
91d7fa1f9c | ||
|
|
8310d62499 | ||
|
|
c6a50d960e | ||
|
|
bee44b382b | ||
|
|
ac28ed12b1 | ||
|
|
c4d039da37 | ||
|
|
f125d19854 | ||
|
|
1ce2e24f5d | ||
|
|
5cf7f22b70 | ||
|
|
7af9f8a976 | ||
|
|
fe5bfa0fe0 | ||
|
|
97bf67d8db |
+3
-5
@@ -1,5 +1,7 @@
|
||||
/.eunit/*
|
||||
/.eunit
|
||||
/.release
|
||||
/docs
|
||||
/ebin
|
||||
/lib/*/ebin/*
|
||||
/lib/*/tmp
|
||||
/lib/*/test/tmp
|
||||
@@ -7,11 +9,7 @@
|
||||
/lib/elixir/src/*_lexer.erl
|
||||
/lib/elixir/src/*_parser.erl
|
||||
/lib/elixir/test/ebin
|
||||
/deps/*
|
||||
/ebin
|
||||
/rel/elixir
|
||||
erl_crash.dump
|
||||
.dialyzer_plt
|
||||
.dialyzer.base_plt
|
||||
.*.swp
|
||||
docs
|
||||
|
||||
+7
-2
@@ -1,9 +1,14 @@
|
||||
language: erlang
|
||||
script: "make compile && make test"
|
||||
script: "make compile && rm -rf .git && make test"
|
||||
notifications:
|
||||
irc: "irc.freenode.org#elixir-lang"
|
||||
recipients:
|
||||
- jose.valim@plataformatec.com.br
|
||||
- yrashk@gmail.com
|
||||
- eric.meadows.jonsson@gmail.com
|
||||
otp_release:
|
||||
- 17.0-rc1
|
||||
- R16B03-1
|
||||
- R16B03
|
||||
- R16B02
|
||||
- R16B01
|
||||
- R16B
|
||||
|
||||
+140
-3
@@ -1,3 +1,140 @@
|
||||
# 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
|
||||
* [Kernel] Warnings now are explicitly tagged with "warning:" in messages
|
||||
* [Kernel] Explicit functions inlined by the compiler, including operators. This means that `Kernel.+/2` will now expand to `:erlang.+/2` and so on
|
||||
* [Mix] Do not fail if a Mix dependency relies on an outdated Elixir version
|
||||
* [Process] Add `Process.send/2` and `Process.send_after/3`
|
||||
* [Version] Add `Version.compare/2`
|
||||
|
||||
* Bug fixes
|
||||
* [Atom] Inspect `:...` and `:foo@bar` without quoting
|
||||
* [Keyword] The list `[1, 2, three: :four]` now correctly expands to `[1, 2, {:three, :four}]`
|
||||
* [Kernel] Ensure undefined `@attributes` shows proper stacktrace in warnings
|
||||
* [Kernel] Guarantee nullary funs/macros are allowed in guards
|
||||
* [Process] Ensure monitoring functions are inlined by the compiler
|
||||
|
||||
* Deprecations
|
||||
* [IEx] The helper `m/0` has been deprecated. The goal is to group all runtime statistic related helpers into a single module
|
||||
* [Kernel] `binary_to_term/1`, `binary_to_term/2`, `term_to_binary/1` and `term_to_binary/2` are deprecated in favor of their counterparts in the `:erlang` module
|
||||
* [Kernel] `//` for default arguments is deprecated in favor of `\\`. This is a soft deprecation, no warnings will be emitted for it in this release
|
||||
* [Kernel] Deprecated `@behavior` in favor of `@behaviour`
|
||||
* [Record] `to_keywords`, `getter` and `list getter` functionalities in `defrecordp` are deprecated
|
||||
* [Record] `Record.import/2` is deprecated
|
||||
|
||||
* Backwards incompatible changes
|
||||
* [Dict] Implementations of `equal?/2` and `merge/2` in `HashDict` and `ListDict` are no longer polymorphic. To get polymorphism, use the functions in `Dict` instead
|
||||
* [File] `File.cp/3` and `File.cp_r/3` no longer carry Unix semantics where the function behaves differently if the destination is an existing previous directory or not. It now always copies source to destination, doing it recursively in the latter
|
||||
* [IEx] IEx now loads the `.iex.exs` file instead of `.iex`
|
||||
* [Kernel] Remove `**` from the list of allowed operators
|
||||
* [Kernel] Limit sigils delimiters to one of the following: `<>`, `{}`, `[]`, `()`, `||`, `//`, `"` and `'`
|
||||
* [Range] `Range` is no longer a record, instead use `first .. last` if you need pattern matching
|
||||
* [Set] Implementations of `difference/2`, `disjoint?/2`, `equal?/2`, `intersection/2`, `subset?/2` and `union/2` in `HashSet` are no longer polymorphic. To get polymorphism, use the functions in `Set` instead
|
||||
|
||||
# v0.12.2 (2014-01-15)
|
||||
|
||||
* Enhancements
|
||||
* [EEx] Allow `EEx.AssignsEngine` to accept any Dict
|
||||
* [Enum] Add `Enum.flat_map_reduce/3`
|
||||
* [ExUnit] Support `@moduletag` in ExUnit cases
|
||||
* [Kernel] Improve stacktraces to be relative to the compilation path and include the related application
|
||||
* [Stream] Add `Stream.transform/3`
|
||||
|
||||
* Bug fixes
|
||||
* [ExUnit] `:include` in ExUnit only has effect if a test was previously excluded with `:exclude`
|
||||
* [ExUnit] Only run `setup_all` and `teardown_all` if there are tests in the case
|
||||
* [Kernel] Ensure bitstring modifier arguments are expanded
|
||||
* [Kernel] Ensure compiler does not block on missing modules
|
||||
* [Kernel] Ensure `<>/2` works only with binaries
|
||||
* [Kernel] Fix usage of string literals inside `<<>>` when `utf8`/`utf16`/`utf32` is used as specifier
|
||||
* [Mix] Ensure mix properly copies _build dependencies on Windows
|
||||
|
||||
* Deprecations
|
||||
* [Enum] Deprecate `Enum.first/1` in favor of `Enum.at/2` and `List.first/1`
|
||||
* [Kernel] Deprecate continuable heredocs. In previous versions, Elixir would continue parsing on the same line the heredoc started, this behaviour has been deprecated
|
||||
* [Kernel] `is_alive/0` is deprecated in favor of `Node.alive?`
|
||||
* [Kernel] `Kernel.inspect/2` with `Inspect.Opts[]` is deprecated in favor of `Inspect.Algebra.to_doc/2`
|
||||
* [Kernel] `Kernel.inspect/2` with `:raw` option is deprecated, use `:records` option instead
|
||||
* [Kernel] Deprecate `<-/2` in favor of `send/2`
|
||||
|
||||
* Backwards incompatible changes
|
||||
* [String] Change `String.next_grapheme/1` and `String.next_codepoint/1` to return `nil` on string end
|
||||
|
||||
# v0.12.1 (2014-01-04)
|
||||
|
||||
* Enhancements
|
||||
* [ExUnit] Support `:include` and `:exclude` configuration options to filter which tests should run based on their tags. Those options are also supported via `mix test` as `--include` and `--exclude`
|
||||
* [ExUnit] Allow doctests to match against `#MyModule<>`
|
||||
|
||||
* Bug fixes
|
||||
* [CLI] Abort when a pattern given to elixirc does not match any file
|
||||
* [Float] Fix `Float.parse/1` to handle numbers of the form "-0.x"
|
||||
* [IEx] Improve error message for `IEx.Helpers.r` when module does not exist
|
||||
* [Mix] Ensure `deps.get` updates origin if lock origin and dep origin do not match
|
||||
* [Mix] Use relative symlinks in _build
|
||||
* [Typespec] Fix conversion of unary ops from typespec format to ast
|
||||
* [Typespec] Fix handling of `tuple()` and `{}`
|
||||
|
||||
* Deprecations
|
||||
* [Kernel] Do not leak clause heads. Previously, a variable defined in a case/receive head clauses would leak to the outer scope. This behaviour is deprecated and will be removed in the next release.
|
||||
* [Kernel] Deprecate `__FILE__` in favor of `__DIR__` or `__ENV__.file`
|
||||
|
||||
* Backwards incompatible changes
|
||||
* [GenFSM] GenServer now stops on unknown event/sync_event requests
|
||||
* [GenServer] GenServer now stops on unknown call/cast requests
|
||||
* [Kernel] Change how `->` is represented in AST. Now each clause is represented by its own AST node which makes composition easier. See commit 51aef55 for more information.
|
||||
|
||||
# v0.12.0 (2013-12-15)
|
||||
|
||||
* Enhancements
|
||||
* [Exception] Allow `exception/1` to be overridden and promote it as the main mechanism to customize exceptions
|
||||
* [File] Add `File.stream_to!/3`
|
||||
* [Float] Add `Float.floor/1`, `Float.ceil/1` and `Float.round/3`
|
||||
* [Kernel] Add `List.delete_at/2` and `List.updated_at/3`
|
||||
* [Kernel] Add `Enum.reverse/2`
|
||||
* [Kernel] Implement `defmodule/2`, `@/1`, `def/2` and friends in Elixir itself. `case/2`, `try/2` and `receive/1` have been made special forms. `var!/1`, `var!/2` and `alias!/1` have also been implemented in Elixir and demoted from special forms
|
||||
* [Record] Support dynamic fields in `defrecordp`
|
||||
* [Stream] Add `Stream.resource/3`
|
||||
* [Stream] Add `Stream.zip/2`, `Stream.filter_map/3`, `Stream.each/2`, `Stream.take_every/2`, `Stream.chunk/2`, `Stream.chunk/3`, `Stream.chunk/4`, `Stream.chunk_by/2`, `Stream.scan/2`, `Stream.scan/3`, `Stream.uniq/2`, `Stream.after/2` and `Stream.run/1`
|
||||
* [Stream] Support `Stream.take/2` and `Stream.drop/2` with negative counts
|
||||
|
||||
* Bug fixes
|
||||
* [HashDict] Ensure a `HashDict` stored in an attribute can be accessed via the attribute
|
||||
* [Enum] Fix bug in `Enum.chunk/4` where you'd get an extra element when the enumerable was a multiple of the counter and a pad was given
|
||||
* [IEx] Ensure `c/2` helper works with full paths
|
||||
* [Kernel] `quote location: :keep` now only affects definitions in order to keep the proper trace in definition exceptions
|
||||
* [Mix] Also symlink `include` directories in _build dependencies
|
||||
* [Version] Fix `Version.match?/2` with `~>` and versions with alphanumeric build info (like `-dev`)
|
||||
|
||||
* Deprecations
|
||||
* [Enum] `Enumerable.count/1` and `Enumerable.member?/2` should now return tagged tuples. Please see `Enumerable` docs for more info
|
||||
* [Enum] Deprecate `Enum.chunks/2`, `Enum.chunks/4` and `Enum.chunks_by/2` in favor of `Enum.chunk/2`, `Enum.chunk/4` and `Enum.chunk_by/2`
|
||||
* [File] `File.binstream!/3` is deprecated. Simply use `File.stream!/3` which is able to figure out if `stream` or `binstream` operations should be used
|
||||
* [Macro] `Macro.extract_args/1` is deprecated in favor of `Macro.decompose_call/1`
|
||||
|
||||
* Backwards incompatible changes
|
||||
* [Enum] Behaviour of `Enum.drop/2` and `Enum.take/2` has been switched when given negative counts
|
||||
* [Enum] Behaviour of `Enum.zip/2` has been changed to stop as soon as the first enumerable finishes
|
||||
* [Enum] `Enumerable.reduce/3` protocol has changed to support suspension. Please see `Enumerable` docs for more info
|
||||
* [Mix] Require `:escript_main_module` to be set before generating escripts
|
||||
* [Range] `Range.Iterator` protocol has changed in order to work with the new `Enumerable.reduce/3`. Please see `Range.Iterator` docs for more info
|
||||
* [Stream] The `Stream.Lazy` structure has changed to accumulate functions and accumulators as we go (its inspected representation has also changed)
|
||||
* [Typespec] `when` clauses were moved to the outer part of the spec and should be in the keywords format. So `add(a, b) when is_subtype(a, integer) and is_subtype(b, integer) :: integer` should now be written as `add(a, b) :: integer when a: integer, b: integer`
|
||||
|
||||
# v0.11.2 (2013-11-14)
|
||||
|
||||
* Enhancements
|
||||
@@ -112,7 +249,7 @@
|
||||
* Enhancements
|
||||
* [CLI] Add `--verbose` to elixirc, which now is non-verbose by default
|
||||
* [Dict] Add `Dict.Behaviour` as a convenience to create your own dictionaries
|
||||
* [Enum] Add `Enum.split/2`, `Enum.reduce/2`, `Enum.flat_map/2`, `Enum.chunks/2`, `Enum.chunks/4`, `Enum.chunks_by/2`, `Enum.concat/1` and `Enum.concat/2`
|
||||
* [Enum] Add `Enum.split/2`, `Enum.reduce/2`, `Enum.flat_map/2`, `Enum.chunk/2`, `Enum.chunk/4`, `Enum.chunk_by/2`, `Enum.concat/1` and `Enum.concat/2`
|
||||
* [Enum] Support negative indices in `Enum.at/fetch/fetch!`
|
||||
* [ExUnit] Show failures on CLIFormatter as soon as they pop up
|
||||
* [IEx] Allow for strings in `h` helper
|
||||
@@ -298,7 +435,7 @@
|
||||
* [Bitwise] Precedence of operators used by the Bitwise module were changed, check `elixir_parser.yrl` for more information
|
||||
* [File] `rm_rf` and `cp_r` now returns a tuple with three elements on failures
|
||||
* [Kernel] The quoted representation for `->` clauses changed from a tuple with two elements to a tuple with three elements to support metadata
|
||||
* [Kernel] Sigils now dispatch to `sigil_$` instead of `__$__` where `$` is the sigil caracter
|
||||
* [Kernel] Sigils now dispatch to `sigil_$` instead of `__$__` where `$` is the sigil character
|
||||
* [Macro] `Macro.expand/2` now expands until final form. Although this is backwards incompatible, it is very likely you do not need to change your code, since expansion until its final form is recommended, particularly if you are expecting an atom out of it
|
||||
* [Mix] No longer support beam files on `mix local`
|
||||
|
||||
@@ -628,7 +765,7 @@
|
||||
* Backwards incompatible changes
|
||||
* [Kernel] Compile files now follow `Elixir-ModuleName` convention to solve issues with Erlang embedded mode. This removes the `__MAIN__` pseudo-variable as modules are now located inside `Elixir` namespace
|
||||
* [Kernel] `__using__` callback triggered by `use` now receives just one argument. Caller information can be accessed via macros using `__CALLER__`
|
||||
* [Kernel] Comprehensions syntax changed to be more compatible with Erlang behavior
|
||||
* [Kernel] Comprehensions syntax changed to be more compatible with Erlang behaviour
|
||||
* [Kernel] loop and recur are removed in favor of recursion with named functions
|
||||
* [Module] Removed data functions in favor of unifying the attributes API
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
REBAR := "$(shell echo `pwd`/rebar)"
|
||||
REBAR := rebar
|
||||
ELIXIRC := bin/elixirc --verbose --ignore-module-conflict $(ELIXIRC_OPTS)
|
||||
ERLC := erlc -I lib/elixir/include
|
||||
ERL := erl -I lib/elixir/include -noshell -pa lib/elixir/ebin
|
||||
@@ -20,6 +20,8 @@ define APP_TEMPLATE
|
||||
$(1): lib/$(1)/ebin/Elixir.$(2).beam lib/$(1)/ebin/$(1).app
|
||||
|
||||
lib/$(1)/ebin/$(1).app: lib/$(1)/mix.exs
|
||||
$(Q) mkdir -p lib/$(1)/_build/shared/lib/$(1)
|
||||
$(Q) cp -R lib/$(1)/ebin lib/$(1)/_build/shared/lib/$(1)/
|
||||
$(Q) cd lib/$(1) && ../../bin/elixir -e "Mix.Sup.start_link()" -r mix.exs -e "Mix.Task.run('compile.app')"
|
||||
$(Q) cp lib/$(1)/_build/shared/lib/$(1)/ebin/$(1).app lib/$(1)/ebin/$(1).app
|
||||
$(Q) rm -rf lib/$(1)/_build
|
||||
@@ -27,7 +29,7 @@ lib/$(1)/ebin/$(1).app: lib/$(1)/mix.exs
|
||||
lib/$(1)/ebin/Elixir.$(2).beam: $(wildcard lib/$(1)/lib/*.ex) $(wildcard lib/$(1)/lib/*/*.ex) $(wildcard lib/$(1)/lib/*/*/*.ex)
|
||||
@ echo "==> $(1) (compile)"
|
||||
@ rm -rf lib/$(1)/ebin
|
||||
$(Q) $$(ELIXIRC) "lib/$(1)/lib/**/*.ex" -o lib/$(1)/ebin
|
||||
$(Q) cd lib/$(1) && ../../$$(ELIXIRC) "lib/**/*.ex" -o ebin
|
||||
|
||||
test_$(1): $(1)
|
||||
@ echo "==> $(1) (exunit)"
|
||||
@@ -50,7 +52,7 @@ lib/elixir/src/elixir.app.src: src/elixir.app.src
|
||||
$(Q) cat src/elixir.app.src >>lib/elixir/src/elixir.app.src
|
||||
|
||||
erlang:
|
||||
$(Q) cd lib/elixir && $(REBAR) compile
|
||||
$(Q) cd lib/elixir && ../../$(REBAR) compile
|
||||
|
||||
# Since Mix depends on EEx and EEx depends on
|
||||
# Mix, we first compile EEx without the .app
|
||||
@@ -63,17 +65,18 @@ $(KERNEL): lib/elixir/lib/*.ex lib/elixir/lib/*/*.ex
|
||||
echo "==> bootstrap (compile)"; \
|
||||
$(ERL) -s elixir_compiler core -s erlang halt; \
|
||||
fi
|
||||
@ echo "==> kernel (compile)";
|
||||
$(Q) $(ELIXIRC) "lib/elixir/lib/**/*.ex" -o lib/elixir/ebin;
|
||||
@ echo "==> elixir (compile)";
|
||||
$(Q) cd lib/elixir && ../../$(ELIXIRC) "lib/kernel.ex" -o ebin;
|
||||
$(Q) cd lib/elixir && ../../$(ELIXIRC) "lib/**/*.ex" -o ebin;
|
||||
$(Q) $(MAKE) unicode
|
||||
$(Q) rm -rf lib/elixir/ebin/elixir.app
|
||||
$(Q) cd lib/elixir && $(REBAR) compile
|
||||
$(Q) cd lib/elixir && ../../$(REBAR) compile
|
||||
|
||||
unicode: $(UNICODE)
|
||||
$(UNICODE): lib/elixir/unicode/unicode.ex lib/elixir/unicode/UnicodeData.txt lib/elixir/unicode/GraphemeBreakProperty.txt
|
||||
$(UNICODE): lib/elixir/unicode/*
|
||||
@ echo "==> unicode (compile)";
|
||||
@ echo "This step can take up to a minute to compile in order to embed the Unicode database"
|
||||
$(Q) $(ELIXIRC) lib/elixir/unicode/unicode.ex -o lib/elixir/ebin;
|
||||
$(Q) cd lib/elixir && ../../$(ELIXIRC) unicode/unicode.ex -o ebin;
|
||||
|
||||
$(eval $(call APP_TEMPLATE,ex_unit,ExUnit))
|
||||
$(eval $(call APP_TEMPLATE,eex,EEx))
|
||||
@@ -94,9 +97,10 @@ install: compile
|
||||
done
|
||||
|
||||
clean:
|
||||
cd lib/elixir && $(REBAR) clean
|
||||
cd lib/elixir && ../../$(REBAR) clean
|
||||
rm -rf ebin
|
||||
rm -rf lib/*/ebin
|
||||
rm -rf lib/elixir/test/ebin
|
||||
rm -rf lib/*/tmp
|
||||
rm -rf lib/mix/test/fixtures/git_repo
|
||||
rm -rf lib/mix/test/fixtures/deps_on_git_repo
|
||||
@@ -114,7 +118,7 @@ docs: compile ../ex_doc/bin/ex_doc
|
||||
mkdir -p ebin
|
||||
rm -rf docs
|
||||
cp -R -f lib/*/ebin/*.beam ./ebin
|
||||
bin/elixir ../ex_doc/bin/ex_doc "Elixir" "$(VERSION)" -m Kernel -u "https://github.com/elixir-lang/elixir" --source-ref "$(call SOURCE_REF)"
|
||||
bin/elixir ../ex_doc/bin/ex_doc "Elixir" "$(VERSION)" "./ebin" -m Kernel -u "https://github.com/elixir-lang/elixir" --source-ref "$(call SOURCE_REF)"
|
||||
rm -rf ebin
|
||||
|
||||
../ex_doc/bin/ex_doc:
|
||||
@@ -126,9 +130,9 @@ release_zip: compile
|
||||
zip -9 -r v$(VERSION).zip bin CHANGELOG.md LEGAL lib/*/ebin LICENSE README.md rel VERSION
|
||||
|
||||
release_docs: docs
|
||||
cd ../elixir-lang.github.com
|
||||
rm -rf ../elixir-lang.github.com/docs/master
|
||||
mv docs ../elixir-lang.github.com/docs/master
|
||||
cd ../docs
|
||||
rm -rf ../docs/master
|
||||
mv docs ../docs/master
|
||||
|
||||
release_erl: compile
|
||||
$(Q) rm -rf rel/elixir
|
||||
@@ -152,7 +156,7 @@ test_doc_test: compile
|
||||
$(Q) cd lib/elixir && ../../bin/elixir -r "test/doc_test.exs";
|
||||
|
||||
test_kernel: compile
|
||||
@ echo "==> kernel (exunit)"
|
||||
@ echo "==> elixir (exunit)"
|
||||
$(Q) cd lib/elixir && ../../bin/elixir -r "test/elixir/test_helper.exs" -pr "test/elixir/**/*_test.exs";
|
||||
|
||||
.dialyzer.base_plt:
|
||||
|
||||
+1
-1
@@ -14,7 +14,7 @@ This document simply outlines the release process:
|
||||
|
||||
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
|
||||
7) Release new zip with `make release_zip`, push new zip to GitHub Releases
|
||||
|
||||
8) Push package to expm with `expm publish package.exs`
|
||||
|
||||
|
||||
+1
-1
@@ -5,7 +5,7 @@ if [ $# -eq 0 ] || [ "$1" = "--help" ] || [ "$1" = "-h" ]; then
|
||||
-v Prints version and exit
|
||||
-e \"command\" Evaluates the given command (*)
|
||||
-r \"file\" Requires the given files/patterns (*)
|
||||
-S \"script\" Finds and executes the given script (*)
|
||||
-S \"script\" Finds and executes the given script
|
||||
-pr \"file\" Requires the given files/patterns in parallel (*)
|
||||
-pa \"path\" Prepends the given path to Erlang code path (*)
|
||||
-pz \"path\" Appends the given path to Erlang code path (*)
|
||||
|
||||
+1
-1
@@ -12,7 +12,7 @@ echo.
|
||||
echo -v Prints version and exit
|
||||
echo -e command Evaluates the given command (*)
|
||||
echo -r file Requires the given files/patterns (*)
|
||||
echo -S script Finds and executes the given script (*)
|
||||
echo -S script Finds and executes the given script
|
||||
echo -pr file Requires the given files/patterns in parallel (*)
|
||||
echo -pa path Prepends the given path to Erlang code path (*)
|
||||
echo -pz path Appends the given path to Erlang code path (*)
|
||||
|
||||
@@ -5,7 +5,7 @@ if [ $# -gt 0 ] && ([ "$1" = "--help" ] || [ "$1" = "-h" ]); then
|
||||
-v Prints version
|
||||
-e \"command\" Evaluates the given command (*)
|
||||
-r \"file\" Requires the given files/patterns (*)
|
||||
-S \"script\" Finds and executes the given script (*)
|
||||
-S \"script\" Finds and executes the given script
|
||||
-pr \"file\" Requires the given files/patterns in parallel (*)
|
||||
-pa \"path\" Prepends the given path to Erlang code path (*)
|
||||
-pz \"path\" Appends the given path to Erlang code path (*)
|
||||
@@ -17,7 +17,7 @@ if [ $# -gt 0 ] && ([ "$1" = "--help" ] || [ "$1" = "-h" ]); then
|
||||
--hidden Makes a hidden node
|
||||
--detached Starts the Erlang VM detached from console
|
||||
--remsh \"name\" Connects to a node using a remote shell
|
||||
--dot-iex \"path\" Overrides default .iex file and uses path instead;
|
||||
--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
|
||||
|
||||
+20
-7
@@ -1,7 +1,7 @@
|
||||
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:
|
||||
|
||||
@@ -27,6 +27,19 @@ defmodule EEx do
|
||||
above and is available to you if you want to provide your own
|
||||
ways of handling the compiled template.
|
||||
|
||||
## Options
|
||||
|
||||
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
|
||||
|
||||
EEx has the concept of engines which allows you to modify or
|
||||
@@ -92,7 +105,7 @@ defmodule EEx do
|
||||
Sample.sample(1, 2) #=> "3"
|
||||
|
||||
"""
|
||||
defmacro function_from_string(kind, name, source, args // [], options // []) do
|
||||
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
|
||||
@@ -128,7 +141,7 @@ defmodule EEx do
|
||||
Sample.sample(1, 2) #=> "3"
|
||||
|
||||
"""
|
||||
defmacro function_from_file(kind, name, file, args // [], options // []) 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
|
||||
@@ -146,7 +159,7 @@ defmodule EEx do
|
||||
Get a string `source` and generate a quoted expression
|
||||
that can be evaluated by Elixir or compiled to a function.
|
||||
"""
|
||||
def compile_string(source, options // []) do
|
||||
def compile_string(source, options \\ []) do
|
||||
EEx.Compiler.compile(source, options)
|
||||
end
|
||||
|
||||
@@ -154,7 +167,7 @@ defmodule EEx do
|
||||
Get a `filename` and generate a quoted expression
|
||||
that can be evaluated by Elixir or compiled to a function.
|
||||
"""
|
||||
def compile_file(filename, options // []) do
|
||||
def compile_file(filename, options \\ []) do
|
||||
options = Keyword.merge options, [file: filename, line: 1]
|
||||
compile_string(File.read!(filename), options)
|
||||
end
|
||||
@@ -168,7 +181,7 @@ defmodule EEx do
|
||||
#=> "foo baz"
|
||||
|
||||
"""
|
||||
def eval_string(source, bindings // [], options // []) do
|
||||
def eval_string(source, bindings \\ [], options \\ []) do
|
||||
compiled = compile_string(source, options)
|
||||
do_eval(compiled, bindings, options)
|
||||
end
|
||||
@@ -186,7 +199,7 @@ defmodule EEx do
|
||||
#=> "foo baz"
|
||||
|
||||
"""
|
||||
def eval_file(filename, bindings // [], options // []) do
|
||||
def eval_file(filename, bindings \\ [], options \\ []) do
|
||||
options = Keyword.put options, :file, filename
|
||||
compiled = compile_file(filename, options)
|
||||
do_eval(compiled, bindings, options)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
defrecord EEx.State, engine: EEx.SmartEngine, dict: [], file: 'nofile', line: 1, start_line: 1
|
||||
defrecord EEx.State, engine: EEx.SmartEngine, dict: [], file: "nofile", line: 1, start_line: 1
|
||||
|
||||
defmodule EEx.Compiler do
|
||||
@moduledoc false
|
||||
@@ -23,7 +23,7 @@ defmodule EEx.Compiler do
|
||||
end
|
||||
|
||||
defp generate_buffer([{ :expr, line, mark, chars }|t], buffer, scope, state) do
|
||||
expr = maybe_block :elixir_translator.forms!(chars, line, state.file, [])
|
||||
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
|
||||
@@ -41,7 +41,7 @@ defmodule EEx.Compiler do
|
||||
|
||||
defp generate_buffer([{ :end_expr, line, _, chars }|t], buffer, [current|_], state) do
|
||||
{ wrapped, state } = wrap_expr(current, line, buffer, chars, state)
|
||||
tuples = maybe_block :elixir_translator.forms!(wrapped, state.start_line, state.file, [])
|
||||
tuples = Code.string_to_quoted!(wrapped, [line: state.start_line, file: state.file])
|
||||
buffer = insert_quotes(tuples, state.dict)
|
||||
{ buffer, t }
|
||||
end
|
||||
@@ -63,7 +63,7 @@ defmodule EEx.Compiler do
|
||||
defp wrap_expr(current, line, buffer, chars, state) do
|
||||
new_lines = List.duplicate(?\n, line - state.line)
|
||||
|
||||
if state.dict == [] and is_empty?(buffer) do
|
||||
if state.dict == [] and empty?(buffer) do
|
||||
{ current ++ new_lines ++ chars, state }
|
||||
else
|
||||
key = length(state.dict)
|
||||
@@ -74,24 +74,18 @@ defmodule EEx.Compiler do
|
||||
|
||||
# Check if the syntax node represents an empty string
|
||||
|
||||
defp is_empty?(bin) when is_binary(bin) do
|
||||
defp empty?(bin) when is_binary(bin) do
|
||||
bc(<<c>> inbits bin, not c in [?\s, ?\t, ?\r, ?\n], do: <<c>>) == ""
|
||||
end
|
||||
|
||||
defp is_empty?({ :<>, _, [left, right] }) do
|
||||
is_empty?(left) and is_empty?(right)
|
||||
defp empty?({ :<>, _, [left, right] }) do
|
||||
empty?(left) and empty?(right)
|
||||
end
|
||||
|
||||
defp is_empty?(_) do
|
||||
defp empty?(_) do
|
||||
false
|
||||
end
|
||||
|
||||
# Block wrapping
|
||||
|
||||
defp maybe_block([]), do: nil
|
||||
defp maybe_block([h]), do: h
|
||||
defp maybe_block(other), do: { :__block__, [], other }
|
||||
|
||||
# Changes placeholder to real expression
|
||||
|
||||
defp insert_quotes({ :__EEX__, _, [key] }, dict) do
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
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:
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ defmodule EEx.TransformerEngine do
|
||||
@moduledoc """
|
||||
An abstract engine that is meant to be used and
|
||||
built upon in other modules. This engine implements
|
||||
the `EEx.Engine` behavior and provides a `transform`
|
||||
the `EEx.Engine` behaviour and provides a `transform`
|
||||
overridable directive that allows a developer to
|
||||
customize the expression returned by the engine.
|
||||
|
||||
@@ -13,7 +13,7 @@ defmodule EEx.TransformerEngine do
|
||||
@doc false
|
||||
defmacro __using__(_) do
|
||||
quote do
|
||||
@behavior EEx.Engine
|
||||
@behaviour EEx.Engine
|
||||
|
||||
def handle_text(buffer, text) do
|
||||
EEx.Engine.handle_text(buffer, text)
|
||||
@@ -88,7 +88,7 @@ defmodule EEx.AssignsEngine do
|
||||
defmacro __using__(_) do
|
||||
quote unquote: false do
|
||||
defp transform({ :@, line, [{ name, _, atom }] }) when is_atom(name) and is_atom(atom) do
|
||||
quote do: Keyword.get(var!(assigns), unquote(name))
|
||||
quote do: Dict.get(var!(assigns), unquote(name))
|
||||
end
|
||||
|
||||
defp transform(arg) do
|
||||
|
||||
+2
-2
@@ -2,6 +2,6 @@ defmodule EEx.Mixfile do
|
||||
use Mix.Project
|
||||
|
||||
def project do
|
||||
[app: :eex, version: System.version]
|
||||
[app: :eex, version: System.version, build_per_environment: false]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -7,16 +7,21 @@ defmodule EEx.SmartEngineTest do
|
||||
assert_eval "foo bar", "foo bar"
|
||||
end
|
||||
|
||||
test "evaluates with assigns" do
|
||||
test "evaluates with assigns as a ListDict" 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
|
||||
end
|
||||
|
||||
test "evaluates with loops" do
|
||||
assert_eval "1\n2\n3\n", "<%= lc x inlist [1, 2, 3] do %><%= x %>\n<% end %>"
|
||||
end
|
||||
|
||||
defp assert_eval(expected, actual, binding // []) do
|
||||
result = EEx.eval_string(actual, binding, file: __FILE__)
|
||||
defp assert_eval(expected, actual, binding \\ []) do
|
||||
result = EEx.eval_string(actual, binding, file: __ENV__.file)
|
||||
assert result == expected
|
||||
end
|
||||
end
|
||||
|
||||
@@ -321,7 +321,7 @@ foo
|
||||
{ EExText.Compiled,
|
||||
:before_compile,
|
||||
0,
|
||||
[file: to_char_list(__FILE__), line: 7]
|
||||
[file: to_char_list(Path.relative_to_cwd(__ENV__.file)), line: 7]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -330,7 +330,7 @@ foo
|
||||
{ EExText.Compiled,
|
||||
:after_compile,
|
||||
0,
|
||||
[file: to_char_list(__FILE__), line: 18]
|
||||
[file: to_char_list(Path.relative_to_cwd(__ENV__.file)), line: 18]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -345,7 +345,7 @@ foo
|
||||
end
|
||||
|
||||
defp assert_eval(expected, actual) do
|
||||
result = EEx.eval_string(actual, [], file: __FILE__, engine: EEx.Engine)
|
||||
result = EEx.eval_string(actual, [], file: __ENV__.file, engine: EEx.Engine)
|
||||
assert result == expected
|
||||
end
|
||||
end
|
||||
|
||||
@@ -9,8 +9,8 @@
|
||||
-define(line(Opts), elixir_utils:get_line(Opts)).
|
||||
|
||||
-record(elixir_scope, {
|
||||
context=nil, %% can be assign, guards or nil
|
||||
extra=nil, %% extra information about the context, like fn_match for fns
|
||||
context=nil, %% can be match, guards or nil
|
||||
extra=nil, %% extra information about the context, like fn_match and do_match
|
||||
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
|
||||
@@ -18,26 +18,34 @@
|
||||
function=nil, %% the current function
|
||||
vars=[], %% a dict of defined variables and their alias
|
||||
backup_vars=nil, %% a copy of vars to be used on ^var
|
||||
temp_vars=nil, %% a set of all variables defined in a particular assign
|
||||
clause_vars=nil, %% a dict of all variables defined in a particular clause
|
||||
match_vars=nil, %% a set of all variables defined in a particular match
|
||||
export_vars=nil, %% a dict of all variables defined in a particular clause
|
||||
extra_guards=nil, %% extra guards from args expansion
|
||||
counter=[], %% a counter for the variables defined
|
||||
local=nil, %% the scope to evaluate local functions against
|
||||
context_modules=[], %% modules defined in the current context
|
||||
counter=[], %% a dict counting the variables defined
|
||||
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
|
||||
macro_counter=0, %% macros expansions counter
|
||||
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
|
||||
aliases, %% an orddict with aliases by new -> old names
|
||||
file, %% the current scope filename
|
||||
requires, %% a set with modules required
|
||||
macro_macros=[], %% a list with macros imported from module inside a macro
|
||||
macros, %% a list with macros imported from module
|
||||
macro_functions=[], %% a list with functions imported from module inside a macro
|
||||
functions %% a list with functions imported from module
|
||||
local=nil %% the module to delegate local functions to
|
||||
}).
|
||||
|
||||
-record(elixir_quote, {
|
||||
line=nil,
|
||||
line=false,
|
||||
keep=false,
|
||||
context=nil,
|
||||
vars_hygiene=true,
|
||||
aliases_hygiene=true,
|
||||
@@ -54,9 +62,6 @@
|
||||
existing_atoms_only=false
|
||||
}).
|
||||
|
||||
%% Introspection
|
||||
-define(defs(Kind), Kind == def; Kind == defp; Kind == defmacro; Kind == defmacrop).
|
||||
|
||||
%% Used in tokenization and interpolation
|
||||
|
||||
%% Numbers
|
||||
@@ -75,7 +80,8 @@
|
||||
-define(is_atom(S), ?is_identifier(S) orelse (S == $@)).
|
||||
|
||||
-define(is_identifier(S), ?is_digit(S) orelse ?is_upcase(S) orelse ?is_downcase(S) orelse (S == $_)).
|
||||
-define(is_terminator(S), (S == $?) orelse (S == $!) orelse (S == $:)).
|
||||
-define(is_sigil(S), (S == $/) orelse (S == $<) orelse (S == $") orelse (S == $') orelse
|
||||
(S == $[) orelse (S == $() orelse (S == ${) orelse (S == $|)).
|
||||
|
||||
%% Quotes
|
||||
-define(is_quote(S), S == $" orelse S == $').
|
||||
|
||||
@@ -75,7 +75,7 @@ defmodule Application.Behaviour do
|
||||
# Application.Behaviour.start(:my_app)
|
||||
#
|
||||
@doc false
|
||||
def start(app, type // :temporary) do
|
||||
def start(app, type \\ :temporary) do
|
||||
case :application.start(app, type) do
|
||||
{ :error, { :not_started, dep } } ->
|
||||
case start(dep) do
|
||||
@@ -92,7 +92,7 @@ defmodule Application.Behaviour do
|
||||
@doc false
|
||||
defmacro __using__(_) do
|
||||
quote location: :keep do
|
||||
@behavior :application
|
||||
@behaviour :application
|
||||
|
||||
@doc false
|
||||
def stop(_state) do
|
||||
|
||||
+92
-21
@@ -36,7 +36,7 @@ defmodule Behaviour do
|
||||
`@callback` attributes. `defcallback` is a simple
|
||||
mechanism that defines the `@callback` attribute
|
||||
according to the given type specification. `defcallback` allows
|
||||
documentsion to be created for the callback and defines
|
||||
documentation to be created for the callback and defines
|
||||
a custom function signature.
|
||||
|
||||
The callbacks and their documentation can be retrieved
|
||||
@@ -46,44 +46,60 @@ defmodule Behaviour do
|
||||
@doc """
|
||||
Define a function callback according to the given type specification.
|
||||
"""
|
||||
defmacro defcallback({ :::, _, [fun, return] }) do
|
||||
do_defcallback(fun, return, __CALLER__)
|
||||
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__)
|
||||
do_defcallback(fun, quote(do: term), [], __CALLER__)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Define a macro callback according to the given type specification.
|
||||
"""
|
||||
defmacro defmacrocallback({ :::, _, [fun, return] }) do
|
||||
do_defmacrocallback(fun, return, __CALLER__)
|
||||
defmacro defmacrocallback({ :::, _, [fun, return_and_guard] }) do
|
||||
{ return, guard } = split_return_and_guard(return_and_guard)
|
||||
do_defmacrocallback(fun, return, guard, __CALLER__)
|
||||
end
|
||||
|
||||
defmacro defmacrocallback(fun) do
|
||||
do_defmacrocallback(fun, quote(do: Macro.t), __CALLER__)
|
||||
do_defmacrocallback(fun, quote(do: Macro.t), [], __CALLER__)
|
||||
end
|
||||
|
||||
defp do_defcallback(fun, spec, caller) do
|
||||
case Macro.extract_args(fun) do
|
||||
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 do_defcallback(fun, return, guards, caller) do
|
||||
case Macro.decompose_call(fun) do
|
||||
{ name, args } ->
|
||||
do_callback(:def, name, args, name, length(args), spec, caller)
|
||||
:error ->
|
||||
do_callback(:def, name, args, name, length(args), args, return, guards, caller)
|
||||
_ ->
|
||||
raise ArgumentError, message: "invalid syntax in defcallback #{Macro.to_string(fun)}"
|
||||
end
|
||||
end
|
||||
|
||||
defp do_defmacrocallback(fun, spec, caller) do
|
||||
case Macro.extract_args(fun) do
|
||||
defp do_defmacrocallback(fun, return, guards, caller) do
|
||||
case Macro.decompose_call(fun) do
|
||||
{ name, args } ->
|
||||
do_callback(:defmacro, :"MACRO-#{name}", [quote(do: env :: Macro.Env.t)|args], name, length(args), spec, caller)
|
||||
:error ->
|
||||
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)}"
|
||||
end
|
||||
end
|
||||
|
||||
defp do_callback(kind, name, args, docs_name, docs_arity, return, caller) do
|
||||
defp do_callback(kind, name, args, docs_name, docs_arity, docs_args, return, guards, caller) do
|
||||
Enum.each args, fn
|
||||
{ :::, _, [left, right] } ->
|
||||
ensure_not_default(left)
|
||||
@@ -95,24 +111,79 @@ defmodule Behaviour do
|
||||
end
|
||||
|
||||
quote do
|
||||
@callback unquote(name)(unquote_splicing(args)) :: unquote(return)
|
||||
Behaviour.store_docs __MODULE__, unquote(caller.line), unquote(kind), unquote(docs_name), unquote(docs_arity)
|
||||
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))
|
||||
end
|
||||
end
|
||||
|
||||
defp ensure_not_default({ ://, _, [_, _] }) do
|
||||
raise ArgumentError, message: "default arguments // not supported in defcallback"
|
||||
raise ArgumentError, message: "default arguments // not supported in defcallback/defmacrocallback"
|
||||
end
|
||||
|
||||
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, arity) do
|
||||
def store_docs(module, line, kind, name, args, arity) do
|
||||
doc = Module.get_attribute module, :doc
|
||||
Module.delete_attribute module, :doc
|
||||
Module.put_attribute module, :behaviour_docs, { { name, arity }, line, kind, 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 }
|
||||
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
|
||||
|
||||
+12
-12
@@ -46,83 +46,83 @@ defmodule Bitwise do
|
||||
Bitwise not.
|
||||
"""
|
||||
defmacro bnot(expr) do
|
||||
quote do: __op__(:bnot, unquote(expr))
|
||||
quote do: :erlang.bnot(unquote(expr))
|
||||
end
|
||||
|
||||
@doc """
|
||||
Bitwise not as operator.
|
||||
"""
|
||||
defmacro ~~~expr do
|
||||
quote do: __op__(:bnot, unquote(expr))
|
||||
quote do: :erlang.bnot(unquote(expr))
|
||||
end
|
||||
|
||||
@doc """
|
||||
Bitwise and.
|
||||
"""
|
||||
defmacro band(left, right) do
|
||||
quote do: __op__(:band, unquote(left), unquote(right))
|
||||
quote do: :erlang.band(unquote(left), unquote(right))
|
||||
end
|
||||
|
||||
@doc """
|
||||
Bitwise and as operator.
|
||||
"""
|
||||
defmacro left &&& right do
|
||||
quote do: __op__(:band, unquote(left), unquote(right))
|
||||
quote do: :erlang.band(unquote(left), unquote(right))
|
||||
end
|
||||
|
||||
@doc """
|
||||
Bitwise or.
|
||||
"""
|
||||
defmacro bor(left, right) do
|
||||
quote do: __op__(:bor, unquote(left), unquote(right))
|
||||
quote do: :erlang.bor(unquote(left), unquote(right))
|
||||
end
|
||||
|
||||
@doc """
|
||||
Bitwise or as operator.
|
||||
"""
|
||||
defmacro left ||| right do
|
||||
quote do: __op__(:bor, unquote(left), unquote(right))
|
||||
quote do: :erlang.bor(unquote(left), unquote(right))
|
||||
end
|
||||
|
||||
@doc """
|
||||
Bitwise xor.
|
||||
"""
|
||||
defmacro bxor(left, right) do
|
||||
quote do: __op__(:bxor, unquote(left), unquote(right))
|
||||
quote do: :erlang.bxor(unquote(left), unquote(right))
|
||||
end
|
||||
|
||||
@doc """
|
||||
Bitwise xor as operator.
|
||||
"""
|
||||
defmacro left ^^^ right do
|
||||
quote do: __op__(:bxor, unquote(left), unquote(right))
|
||||
quote do: :erlang.bxor(unquote(left), unquote(right))
|
||||
end
|
||||
|
||||
@doc """
|
||||
Arithmetic bitshift left.
|
||||
"""
|
||||
defmacro bsl(left, right) do
|
||||
quote do: __op__(:bsl, unquote(left), unquote(right))
|
||||
quote do: :erlang.bsl(unquote(left), unquote(right))
|
||||
end
|
||||
|
||||
@doc """
|
||||
Arithmetic bitshift left as operator.
|
||||
"""
|
||||
defmacro left <<< right do
|
||||
quote do: __op__(:bsl, unquote(left), unquote(right))
|
||||
quote do: :erlang.bsl(unquote(left), unquote(right))
|
||||
end
|
||||
|
||||
@doc """
|
||||
Arithmetic bitshift right.
|
||||
"""
|
||||
defmacro bsr(left, right) do
|
||||
quote do: __op__(:bsr, unquote(left), unquote(right))
|
||||
quote do: :erlang.bsr(unquote(left), unquote(right))
|
||||
end
|
||||
|
||||
@doc """
|
||||
Arithmetic bitshift right as operator.
|
||||
"""
|
||||
defmacro left >>> right do
|
||||
quote do: __op__(:bsr, unquote(left), unquote(right))
|
||||
quote do: :erlang.bsr(unquote(left), unquote(right))
|
||||
end
|
||||
end
|
||||
|
||||
+20
-26
@@ -1,7 +1,8 @@
|
||||
defmodule Code do
|
||||
defexception LoadError, file: nil do
|
||||
def message(exception) do
|
||||
"could not load #{exception.file}"
|
||||
defexception LoadError, [:file, :message] do
|
||||
def exception(opts) do
|
||||
file = opts[:file]
|
||||
LoadError[message: "could not load #{file}", file: file]
|
||||
end
|
||||
end
|
||||
|
||||
@@ -9,8 +10,7 @@ defmodule Code do
|
||||
Utilities for managing code compilation, code evaluation and code loading.
|
||||
|
||||
This module complements [Erlang's code module](http://www.erlang.org/doc/man/code.html)
|
||||
to add behavior which is specific to Elixir.
|
||||
|
||||
to add behaviour which is specific to Elixir.
|
||||
"""
|
||||
|
||||
@doc """
|
||||
@@ -115,7 +115,7 @@ defmodule Code do
|
||||
{3, [a: 1, b: 2]}
|
||||
|
||||
"""
|
||||
def eval_string(string, binding // [], opts // [])
|
||||
def eval_string(string, binding \\ [], opts \\ [])
|
||||
|
||||
def eval_string(string, binding, Macro.Env[] = env) do
|
||||
do_eval_string(string, binding, env.to_keywords)
|
||||
@@ -127,7 +127,7 @@ defmodule Code do
|
||||
end
|
||||
|
||||
defp do_eval_string(string, binding, opts) when is_list(binding) do
|
||||
{ value, binding, _scope } = :elixir.eval to_char_list(string), binding, opts
|
||||
{ value, binding, _env, _scope } = :elixir.eval to_char_list(string), binding, opts
|
||||
{ value, binding }
|
||||
end
|
||||
|
||||
@@ -150,7 +150,7 @@ defmodule Code do
|
||||
{3, [a: 1, b: 2]}
|
||||
|
||||
"""
|
||||
def eval_quoted(quoted, binding // [], opts // [])
|
||||
def eval_quoted(quoted, binding \\ [], opts \\ [])
|
||||
|
||||
def eval_quoted(quoted, binding, Macro.Env[] = env) do
|
||||
do_eval_quoted(quoted, binding, env.to_keywords)
|
||||
@@ -162,8 +162,7 @@ defmodule Code do
|
||||
end
|
||||
|
||||
defp do_eval_quoted(quoted, binding, opts) when is_list(binding) do
|
||||
{ value, binding, _scope } =
|
||||
:elixir.eval_quoted [quoted], binding, opts
|
||||
{ value, binding, _env, _scope } = :elixir.eval_quoted quoted, binding, opts
|
||||
{ value, binding }
|
||||
end
|
||||
|
||||
@@ -226,13 +225,13 @@ defmodule Code do
|
||||
`Macro.to_string/2`, which converts a quoted form to a string/binary
|
||||
representation.
|
||||
"""
|
||||
def string_to_quoted(string, opts // []) when is_list(opts) do
|
||||
def string_to_quoted(string, opts \\ []) when is_list(opts) do
|
||||
file = Keyword.get opts, :file, "nofile"
|
||||
line = Keyword.get opts, :line, 1
|
||||
res = :elixir_translator.forms(to_char_list(string), line, file, opts)
|
||||
res = :elixir.string_to_quoted(to_char_list(string), line, file, opts)
|
||||
|
||||
case res do
|
||||
{ :ok, forms } -> { :ok, unpack_quote(line, forms) }
|
||||
{ :ok, forms } -> { :ok, forms }
|
||||
_ -> res
|
||||
end
|
||||
end
|
||||
@@ -247,17 +246,12 @@ defmodule Code do
|
||||
|
||||
Check `string_to_quoted/2` for options information.
|
||||
"""
|
||||
def string_to_quoted!(string, opts // []) when is_list(opts) do
|
||||
def string_to_quoted!(string, opts \\ []) when is_list(opts) do
|
||||
file = Keyword.get opts, :file, "nofile"
|
||||
line = Keyword.get opts, :line, 1
|
||||
res = :elixir_translator.forms!(to_char_list(string), line, file, opts)
|
||||
unpack_quote(line, res)
|
||||
:elixir.string_to_quoted!(to_char_list(string), line, file, opts)
|
||||
end
|
||||
|
||||
defp unpack_quote(_line, []), do: nil
|
||||
defp unpack_quote(_line, [forms]) when not is_list(forms), do: forms
|
||||
defp unpack_quote(line, forms), do: { :__block__, [line: line], forms }
|
||||
|
||||
@doc """
|
||||
Load the given file.
|
||||
|
||||
@@ -272,7 +266,7 @@ defmodule Code do
|
||||
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.
|
||||
"""
|
||||
def load_file(file, relative_to // nil) when is_binary(file) do
|
||||
def load_file(file, relative_to \\ nil) when is_binary(file) do
|
||||
file = find_file(file, relative_to)
|
||||
:elixir_code_server.call { :acquire, file }
|
||||
loaded = :elixir_compiler.file file
|
||||
@@ -295,7 +289,7 @@ defmodule Code do
|
||||
|
||||
Check `load_file/2` if you want a file to be loaded concurrently.
|
||||
"""
|
||||
def require_file(file, relative_to // nil) when is_binary(file) do
|
||||
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
|
||||
@@ -352,8 +346,8 @@ defmodule Code do
|
||||
|
||||
For compiling many files at once, check `Kernel.ParallelCompiler.files/2`.
|
||||
"""
|
||||
def compile_string(string, file // "nofile") when is_binary(file) do
|
||||
:elixir_compiler.string String.to_char_list!(string), file
|
||||
def compile_string(string, file \\ "nofile") when is_binary(file) do
|
||||
:elixir_compiler.string to_char_list(string), file
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -363,8 +357,8 @@ defmodule Code do
|
||||
the first element is the module name and the second one is its
|
||||
binary.
|
||||
"""
|
||||
def compile_quoted(quoted, file // "nofile") when is_binary(file) do
|
||||
:elixir_compiler.quoted [quoted], file
|
||||
def compile_quoted(quoted, file \\ "nofile") when is_binary(file) do
|
||||
:elixir_compiler.quoted quoted, file
|
||||
end
|
||||
|
||||
@doc """
|
||||
|
||||
+57
-59
@@ -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
|
||||
@@ -34,21 +34,20 @@ defmodule Dict do
|
||||
## Match
|
||||
|
||||
Dictionaries are required to implement all operations
|
||||
using the match (`===`) operator. Any deviation from
|
||||
this behaviour should be avoided and explicitly documented.
|
||||
using the match (`===`) operator.
|
||||
"""
|
||||
|
||||
use Behaviour
|
||||
|
||||
@type key :: any
|
||||
@type value :: any
|
||||
@type keys :: [ key ]
|
||||
@type t :: tuple | list
|
||||
|
||||
defcallback new :: t
|
||||
defcallback new(Keyword.t) :: t
|
||||
defcallback new(Enum.t) :: t
|
||||
defcallback new(Enum.t, (any -> { key, value })) :: t
|
||||
defcallback delete(t, key) :: t
|
||||
defcallback drop(t, keys) :: t
|
||||
defcallback drop(t, Enum.t) :: t
|
||||
defcallback empty(t) :: t
|
||||
defcallback equal?(t, t) :: boolean
|
||||
defcallback get(t, key) :: value
|
||||
@@ -56,21 +55,21 @@ defmodule Dict do
|
||||
defcallback fetch(t, key) :: { :ok, value } | :error
|
||||
defcallback fetch!(t, key) :: value | no_return
|
||||
defcallback has_key?(t, key) :: boolean
|
||||
defcallback keys(t) :: list(key)
|
||||
defcallback keys(t) :: [key]
|
||||
defcallback merge(t, t) :: t
|
||||
defcallback merge(t, t, (key, value, value -> value)) :: t
|
||||
defcallback pop(t, key) :: {value, t}
|
||||
defcallback pop(t, key, value) :: {value, t}
|
||||
defcallback put(t, key, value) :: t
|
||||
defcallback put_new(t, key, value) :: t
|
||||
defcallback reduce(t, Enumerable.acc, Enumerable.reducer) :: Enumerable.result
|
||||
defcallback size(t) :: non_neg_integer()
|
||||
defcallback split(t, keys) :: {t, t}
|
||||
defcallback take(t, keys) :: t
|
||||
defcallback split(t, Enum.t) :: {t, t}
|
||||
defcallback take(t, Enum.t) :: t
|
||||
defcallback to_list(t) :: list()
|
||||
defcallback update(t, key, value, (value -> value)) :: t
|
||||
defcallback update!(t, key, (value -> value)) :: t | no_return
|
||||
defcallback values(t) :: list(value)
|
||||
defcallback reduce(t, any, ({key, value}, any -> any)) :: any
|
||||
|
||||
defmacrop target(dict) do
|
||||
quote do
|
||||
@@ -102,7 +101,7 @@ defmodule Dict do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns a list of all values in `dict`
|
||||
Returns a list of all values in `dict`.
|
||||
The values are not guaranteed to be in any order.
|
||||
|
||||
## Examples
|
||||
@@ -164,7 +163,7 @@ defmodule Dict do
|
||||
3
|
||||
"""
|
||||
@spec get(t, key, value) :: value
|
||||
def get(dict, key, default // nil) do
|
||||
def get(dict, key, default \\ nil) do
|
||||
target(dict).get(dict, key, default)
|
||||
end
|
||||
|
||||
@@ -259,12 +258,15 @@ defmodule Dict do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Merges the given `enum` into `dict`. If one of the `enum` keys
|
||||
already exists in `dict`, the `dict` value is replaced by the `enum`
|
||||
value.
|
||||
Merges the dict `b` into dict `a`.
|
||||
|
||||
The `enum` must yield tuples with two elements on enumeration,
|
||||
where the first element represents the key and the second the value.
|
||||
If one of the dict `b` entries already exists in the `dict`,
|
||||
the functions in entries in `b` have higher precedence unless a
|
||||
function is given to resolve conflicts.
|
||||
|
||||
Notice this function is polymorphic as it merges dicts of any
|
||||
type. Each dict implementation also provides a `merge` function,
|
||||
but they can only merge dicts of the same type.
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -274,22 +276,6 @@ defmodule Dict do
|
||||
...> [a: Dict.get(d, :a), b: Dict.get(d, :b), d: Dict.get(d, :d)]
|
||||
[a: 3, b: 2, d: 4]
|
||||
|
||||
"""
|
||||
@spec merge(t, t) :: t
|
||||
def merge(dict, enum) do
|
||||
merge(dict, enum, fn(_k, _v1, v2) -> v2 end)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Merges the given `enum` into `dict`. If one of the `enum` entries
|
||||
already exists in `dict`, the given function is invoked to resolve
|
||||
the conflict.
|
||||
|
||||
The `enum` must yield tuples with two elements on enumeration,
|
||||
where the first element represents the key and the second the value.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> d1 = dict_impl.new([a: 1, b: 2])
|
||||
...> d2 = dict_impl.new([a: 3, d: 4])
|
||||
...> d = Dict.merge(d1, d2, fn(_k, v1, v2) ->
|
||||
@@ -300,8 +286,17 @@ defmodule Dict do
|
||||
|
||||
"""
|
||||
@spec merge(t, t, (key, value, value -> value)) :: t
|
||||
def merge(dict, enum, fun) do
|
||||
target(dict).merge(dict, enum, fun)
|
||||
def merge(dict1, dict2, fun \\ fn(_k, _v1, v2) -> v2 end) do
|
||||
target1 = target(dict1)
|
||||
target2 = target(dict2)
|
||||
|
||||
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) }
|
||||
end) |> elem(1)
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -327,7 +322,7 @@ defmodule Dict do
|
||||
|
||||
"""
|
||||
@spec pop(t, key, value) :: {value, t}
|
||||
def pop(dict, key, default // nil) do
|
||||
def pop(dict, key, default \\ nil) do
|
||||
target(dict).pop(dict, key, default)
|
||||
end
|
||||
|
||||
@@ -348,11 +343,6 @@ defmodule Dict do
|
||||
target(dict).update!(dict, key, fun)
|
||||
end
|
||||
|
||||
@doc false
|
||||
def update(dict, key, fun) do
|
||||
target(dict).update(dict, key, fun)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Update a value in `dict` by calling `fun` on the value to get a new value. If
|
||||
`key` is not present in `dict` then `initial` will be stored as the first
|
||||
@@ -382,7 +372,7 @@ defmodule Dict do
|
||||
|
||||
iex> d = dict_impl.new([a: 1, b: 2, c: 3, d: 4])
|
||||
...> { d1, d2 } = Dict.split(d, [:a, :c, :e])
|
||||
...> { Dict.to_list(d1), Dict.to_list(d2) }
|
||||
...> { 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([])
|
||||
@@ -392,11 +382,11 @@ defmodule Dict do
|
||||
|
||||
iex> d = dict_impl.new([a: 1, b: 2])
|
||||
...> { d1, d2 } = Dict.split(d, [:a, :b, :c])
|
||||
...> { Dict.to_list(d1), Dict.to_list(d2) }
|
||||
...> { Dict.to_list(d1) |> Enum.sort, Dict.to_list(d2) }
|
||||
{ [a: 1, b: 2], [] }
|
||||
|
||||
"""
|
||||
@spec split(t, keys) :: {t, t}
|
||||
@spec split(t, [key]) :: {t, t}
|
||||
def split(dict, keys) do
|
||||
target(dict).split(dict, keys)
|
||||
end
|
||||
@@ -414,18 +404,18 @@ defmodule Dict do
|
||||
|
||||
iex> d = dict_impl.new([a: 1, b: 2])
|
||||
...> d = Dict.drop(d, [:c, :d])
|
||||
...> Dict.to_list(d)
|
||||
...> Dict.to_list(d) |> Enum.sort
|
||||
[a: 1, b: 2]
|
||||
|
||||
"""
|
||||
@spec drop(t, keys) :: t
|
||||
@spec drop(t, [key]) :: t
|
||||
def drop(dict, keys) do
|
||||
target(dict).drop(dict, keys)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns a new dict where only the keys in `keys` from `dict` are
|
||||
included.
|
||||
Returns a new dict where only the keys in `keys` from `dict` are included.
|
||||
|
||||
Any non-member keys are ignored.
|
||||
|
||||
## Examples
|
||||
@@ -441,7 +431,7 @@ defmodule Dict do
|
||||
[]
|
||||
|
||||
"""
|
||||
@spec take(t, keys) :: t
|
||||
@spec take(t, [key]) :: t
|
||||
def take(dict, keys) do
|
||||
target(dict).take(dict, keys)
|
||||
end
|
||||
@@ -463,13 +453,16 @@ defmodule Dict do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Check if two dicts are equal using `===`. If the dicts are
|
||||
of different types, they are first converted to lists.
|
||||
Check if two dicts are equal using `===`.
|
||||
|
||||
Notice this function is polymorphic as it compares dicts of any
|
||||
type. Each dict implementation also provides an `equal?` function,
|
||||
but they can only compare dicts of the same type.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> a = dict_impl.new(a: 2, b: 3, f: 5, c: 123)
|
||||
...> b = ListDict.new(a: 2, b: 3, f: 5, c: 123)
|
||||
...> b = [a: 2, b: 3, f: 5, c: 123]
|
||||
...> Dict.equal?(a, b)
|
||||
true
|
||||
|
||||
@@ -480,16 +473,21 @@ defmodule Dict do
|
||||
|
||||
"""
|
||||
@spec equal?(t, t) :: boolean
|
||||
def equal?(a, b) do
|
||||
a_target = target(a)
|
||||
b_target = target(b)
|
||||
def equal?(dict1, dict2) do
|
||||
target1 = target(dict1)
|
||||
target2 = target(dict2)
|
||||
|
||||
cond do
|
||||
a_target == b_target ->
|
||||
a_target.equal?(a, b)
|
||||
target1 == target2 ->
|
||||
target1.equal?(dict1, dict2)
|
||||
|
||||
a_target.size(a) == b_target.size(b) ->
|
||||
ListDict.equal?(a_target.to_list(a), b_target.to_list(b))
|
||||
target1.size(dict1) == target2.size(dict2) ->
|
||||
target2.reduce(dict2, { :cont, true }, fn({ k, v }, _acc) ->
|
||||
case target1.fetch(dict1, k) do
|
||||
{ :ok, ^v } -> { :cont, true }
|
||||
_ -> { :halt, false }
|
||||
end
|
||||
end) |> elem(1)
|
||||
|
||||
true ->
|
||||
false
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
defmodule Dict.Behaviour do
|
||||
@moduledoc """
|
||||
Default implementations for some required functions in the `Dict` module.
|
||||
|
||||
This module makes it easier to create your own `Dict` compliant
|
||||
module, by providing default implementations for some required functions.
|
||||
|
||||
Usage:
|
||||
## Example
|
||||
|
||||
defmodule MyDict do
|
||||
use Dict.Behaviour
|
||||
@@ -13,20 +15,42 @@ defmodule Dict.Behaviour do
|
||||
# override default implementations if needed
|
||||
end
|
||||
|
||||
The client module must contain following functions: `size/1`, `fetch/2`,
|
||||
`put/3`, `update/4`, `delete/2` and `reduce/3`. All of them are part of
|
||||
the Dict behaviour, so no extra functions are actually required.
|
||||
The client module must contain the following functions:
|
||||
|
||||
* `delete/2`
|
||||
* `fetch/2`
|
||||
* `put/3`
|
||||
* `reduce/3`
|
||||
* `size/1`
|
||||
* `update/4`
|
||||
|
||||
All of which are part of the `Dict` behaviour, so no extra functions are
|
||||
actually required.
|
||||
|
||||
Based on these functions, `Dict.Behaviour` generates default implementations
|
||||
for other functions such as `drop`, `take`, etc. All of the functions are
|
||||
defined as overridable, so you can provide your own implementation if
|
||||
needed.
|
||||
for the following functions:
|
||||
|
||||
If you implement `new/0` and `new/1` functions, you can also test your custom
|
||||
module via `Dict` doctests:
|
||||
* `drop/2`
|
||||
* `equal?/2`
|
||||
* `fetch!/2`
|
||||
* `get/2`
|
||||
* `get/3`
|
||||
* `has_key?/2`
|
||||
* `keys/1`
|
||||
* `merge/2`
|
||||
* `merge/3`
|
||||
* `put_new/3`
|
||||
* `take/2`
|
||||
* `to_list/1`
|
||||
* `values/1`
|
||||
|
||||
All of these functions are defined as overridable, so you can provide your own
|
||||
implementation if needed.
|
||||
|
||||
Note you can also test your custom module via `Dict`'s doctests:
|
||||
|
||||
defmodule MyDict do
|
||||
def new(keywords // []) do
|
||||
def new(keywords \\ []) do
|
||||
...
|
||||
end
|
||||
end
|
||||
@@ -42,84 +66,82 @@ defmodule Dict.Behaviour do
|
||||
quote do
|
||||
@behaviour Dict
|
||||
|
||||
def get(dict, key, default // nil) do
|
||||
def get(dict, key, default \\ nil) do
|
||||
case fetch(dict, key) do
|
||||
{ :ok, value } -> value
|
||||
:error -> default
|
||||
end
|
||||
end
|
||||
defoverridable get: 2, get: 3
|
||||
|
||||
def fetch!(dict, key) do
|
||||
case fetch(dict, key) do
|
||||
{ :ok, value } -> value
|
||||
:error -> raise(KeyError, key: key)
|
||||
:error -> raise KeyError, key: key
|
||||
end
|
||||
end
|
||||
defoverridable fetch!: 2
|
||||
|
||||
def has_key?(dict, key) do
|
||||
match? { :ok, _ }, fetch(dict, key)
|
||||
end
|
||||
defoverridable has_key?: 2
|
||||
|
||||
def put_new(dict, key, value) do
|
||||
update(dict, key, value, fn(v) -> v end)
|
||||
end
|
||||
defoverridable put_new: 3
|
||||
|
||||
def drop(dict, []), do: dict
|
||||
|
||||
def drop(dict, [key|keys]) do
|
||||
drop(delete(dict, key), keys)
|
||||
def drop(dict, keys) do
|
||||
Enum.reduce keys, dict, &delete(&2, &1)
|
||||
end
|
||||
defoverridable drop: 2
|
||||
|
||||
def take(dict, keys) do
|
||||
take(dict, keys, new)
|
||||
end
|
||||
defoverridable take: 2
|
||||
|
||||
defp take(_dict, [], acc), do: acc
|
||||
defp take(dict, [key|keys], acc) do
|
||||
case fetch(dict, key) do
|
||||
{ :ok, value } -> take(dict, keys, put(acc, key, value))
|
||||
:error -> take(dict, keys, acc)
|
||||
Enum.reduce keys, new, fn key, acc ->
|
||||
case fetch(dict, key) do
|
||||
{ :ok, value } -> put(acc, key, value)
|
||||
:error -> acc
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def to_list(dict), do: reduce(dict, [], &[&1|&2]) |> Enum.reverse
|
||||
defoverridable to_list: 1
|
||||
def to_list(dict) do
|
||||
reduce(dict, { :cont, [] }, fn
|
||||
kv, acc -> { :cont, [kv|acc] }
|
||||
end) |> elem(1) |> :lists.reverse
|
||||
end
|
||||
|
||||
def keys(dict), do: reduce(dict, [], fn({k, _}, acc) -> [k | acc] end) |> Enum.reverse
|
||||
defoverridable keys: 1
|
||||
def keys(dict) do
|
||||
reduce(dict, { :cont, [] }, fn
|
||||
{k, _}, acc -> { :cont, [k|acc] }
|
||||
end) |> elem(1) |> :lists.reverse
|
||||
end
|
||||
|
||||
def values(dict), do: reduce(dict, [], fn({_, v}, acc) -> [v | acc] end) |> Enum.reverse
|
||||
defoverridable values: 1
|
||||
def values(dict) do
|
||||
reduce(dict, { :cont, [] }, fn
|
||||
{_, v}, acc -> { :cont, [v|acc] }
|
||||
end) |> elem(1) |> :lists.reverse
|
||||
end
|
||||
|
||||
def equal?(dict1, dict2) do
|
||||
import Kernel, except: [size: 1]
|
||||
case size(dict1) == size(dict2) do
|
||||
false -> false
|
||||
true ->
|
||||
try do
|
||||
reduce(dict1, nil, fn({ k, v }, _acc) ->
|
||||
unless fetch(dict2, k) == { :ok, v }, do: throw(:error)
|
||||
end)
|
||||
true
|
||||
catch
|
||||
:error -> false
|
||||
end
|
||||
true ->
|
||||
reduce(dict1, { :cont, true }, fn({ k, v }, _acc) ->
|
||||
case fetch(dict2, k) do
|
||||
{ :ok, ^v } -> { :cont, true }
|
||||
_ -> { :halt, false }
|
||||
end
|
||||
end) |> elem(1)
|
||||
end
|
||||
end
|
||||
defoverridable equal?: 2
|
||||
|
||||
def merge(dict, enumerable, callback // fn(_k, _v1, v2) -> v2 end) do
|
||||
Enum.reduce(enumerable, dict, fn({key, value}, acc) ->
|
||||
update(acc, key, value, fn(v1) -> callback.(key, v1, value) end)
|
||||
end)
|
||||
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)) }
|
||||
end) |> elem(1)
|
||||
end
|
||||
defoverridable merge: 2, merge: 3
|
||||
|
||||
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
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
+946
-810
File diff suppressed because it is too large
Load Diff
+118
-79
@@ -1,3 +1,7 @@
|
||||
# Some exceptions implement `message/1` instead of `exception/1` mostly
|
||||
# for bootstrap reasons. It is recommended for applications to implement
|
||||
# `exception/1` instead of `message/1` as described in `defexception/3` docs.
|
||||
|
||||
defexception RuntimeError, message: "runtime error"
|
||||
defexception ArgumentError, message: "argument error"
|
||||
defexception ArithmeticError, message: "bad argument in arithmetic expression"
|
||||
@@ -5,19 +9,22 @@ defexception SystemLimitError, message: "a system limit has been reached"
|
||||
|
||||
defexception SyntaxError, [file: nil, line: nil, description: "syntax error"] do
|
||||
def message(exception) do
|
||||
"#{Exception.format_file_line(exception.file, exception.line)}#{exception.description}"
|
||||
Exception.format_file_line(Path.relative_to_cwd(exception.file), exception.line) <>
|
||||
" " <> exception.description
|
||||
end
|
||||
end
|
||||
|
||||
defexception TokenMissingError, [file: nil, line: nil, description: "expression is incomplete"] do
|
||||
def message(exception) do
|
||||
"#{Exception.format_file_line(exception.file, exception.line)}#{exception.description}"
|
||||
Exception.format_file_line(Path.relative_to_cwd(exception.file), exception.line) <>
|
||||
" " <> exception.description
|
||||
end
|
||||
end
|
||||
|
||||
defexception CompileError, [file: nil, line: nil, description: "compile error"] do
|
||||
def message(exception) do
|
||||
"#{Exception.format_file_line(exception.file, exception.line)}#{exception.description}"
|
||||
Exception.format_file_line(Path.relative_to_cwd(exception.file), exception.line) <>
|
||||
" " <> exception.description
|
||||
end
|
||||
end
|
||||
|
||||
@@ -47,8 +54,16 @@ end
|
||||
|
||||
defexception BadArityError, [function: nil, args: nil] do
|
||||
def message(exception) do
|
||||
"bad arity error: #{inspect(exception.function)} called with #{inspect(exception.args)}"
|
||||
fun = exception.function
|
||||
args = exception.args
|
||||
insp = Enum.map_join(args, ", ", &inspect/1)
|
||||
{ :arity, arity } = :erlang.fun_info(fun, :arity)
|
||||
"#{inspect(fun)} with arity #{arity} called with #{count(length(args), insp)}"
|
||||
end
|
||||
|
||||
defp count(0, _insp), do: "no arguments"
|
||||
defp count(1, insp), do: "1 argument (#{insp})"
|
||||
defp count(x, insp), do: "#{x} arguments (#{insp})"
|
||||
end
|
||||
|
||||
defexception UndefinedFunctionError, [module: nil, function: nil, arity: nil] do
|
||||
@@ -100,25 +115,65 @@ defexception Enum.OutOfBoundsError, message: "out of bounds error"
|
||||
|
||||
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))
|
||||
"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))
|
||||
location = if on = on(exception), do: ". #{on}", else: ""
|
||||
"could not #{action exception} from #{source exception} to " <>
|
||||
"#{destination exception}#{location}: #{formatted}"
|
||||
end
|
||||
end
|
||||
|
||||
defmodule Exception do
|
||||
@moduledoc """
|
||||
Several convenience functions to work with and pretty print
|
||||
Convenience functions to work with and pretty print
|
||||
exceptions and stacktraces.
|
||||
|
||||
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.
|
||||
|
||||
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.
|
||||
"""
|
||||
|
||||
@type stacktrace :: [stacktrace_entry]
|
||||
|
||||
@type stacktrace_entry ::
|
||||
{ module, function, arity_or_args, location } |
|
||||
{ function, arity_or_args, location }
|
||||
|
||||
@typep arity_or_args :: non_neg_integer | list
|
||||
@typep location :: Keyword.t
|
||||
|
||||
@doc """
|
||||
Normalizes an exception, converting Erlang exceptions
|
||||
to Elixir exceptions. It takes the kind spilled by
|
||||
`catch` as an argument as a convenience for converting only
|
||||
`:errors`, ignorning the others.
|
||||
to Elixir exceptions.
|
||||
|
||||
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.
|
||||
to Elixir exceptions.
|
||||
|
||||
Useful when interfacing Erlang code with Elixir code.
|
||||
"""
|
||||
def normalize(exception) when is_exception(exception) do
|
||||
exception
|
||||
@@ -175,74 +230,57 @@ defmodule Exception do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Receives a tuple representing a stacktrace entry and formats it.
|
||||
|
||||
The current working directory may be given as an argument,
|
||||
otherwise one is automatically retrieved.
|
||||
Receives an stacktrace entry and formats it into a string.
|
||||
"""
|
||||
def format_stacktrace_entry(entry, cwd // nil)
|
||||
@spec format_stacktrace_entry(stacktrace_entry) :: String.t
|
||||
def format_stacktrace_entry(entry)
|
||||
|
||||
# From Macro.Env.stacktrace
|
||||
def format_stacktrace_entry({ module, :__MODULE__, 0, file_line }, cwd) do
|
||||
"#{format_location(file_line, cwd)}#{inspect module} (module)"
|
||||
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__, 2, file_line }, cwd) do
|
||||
"#{format_location(file_line, cwd)}(module)"
|
||||
def format_stacktrace_entry({ _module, :__MODULE__, 1, location }) do
|
||||
format_location(location) <> "(module)"
|
||||
end
|
||||
|
||||
# From :elixir_compiler_*
|
||||
def format_stacktrace_entry({ _module, :__FILE__, 2, file_line }, cwd) do
|
||||
"#{format_location(file_line, cwd)}(file)"
|
||||
def format_stacktrace_entry({ _module, :__FILE__, 1, location }) do
|
||||
format_location(location) <> "(file)"
|
||||
end
|
||||
|
||||
def format_stacktrace_entry({module, fun, arity, file_line}, cwd) do
|
||||
"#{format_location(file_line, cwd)}#{format_mfa(module, fun, arity)}"
|
||||
def format_stacktrace_entry({module, fun, arity, location}) do
|
||||
format_application(module) <> format_location(location) <> format_mfa(module, fun, arity)
|
||||
end
|
||||
|
||||
def format_stacktrace_entry({fun, arity, file_line}, cwd) do
|
||||
"#{format_location(file_line, cwd)}#{format_fa(fun, arity)}"
|
||||
def format_stacktrace_entry({fun, arity, location}) do
|
||||
format_location(location) <> format_fa(fun, arity)
|
||||
end
|
||||
|
||||
defp format_application(module) do
|
||||
case :application.get_application(module) do
|
||||
{ :ok, app } -> "(" <> atom_to_binary(app) <> ") "
|
||||
:undefined -> ""
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Formats the stacktrace.
|
||||
|
||||
A stacktrace must be given as an argument. If not, this function
|
||||
calculates the current stacktrace and formats it. As a consequence,
|
||||
the value of `System.stacktrace` is changed.
|
||||
calculates a new stacktrace based on the caller and formats it. As
|
||||
a consequence, the value of `System.stacktrace` is changed.
|
||||
"""
|
||||
def format_stacktrace(trace // nil) do
|
||||
def format_stacktrace(trace \\ nil) do
|
||||
trace = trace || try do
|
||||
throw(:stacktrace)
|
||||
catch
|
||||
:stacktrace -> Enum.drop(:erlang.get_stacktrace, 1)
|
||||
end
|
||||
|
||||
cwd = System.cwd
|
||||
|
||||
case trace do
|
||||
[] -> "\n"
|
||||
s -> " " <> Enum.map_join(s, "\n ", &format_stacktrace_entry(&1, cwd)) <> "\n"
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Formats the caller, i.e. the first entry in the stacktrace.
|
||||
Notice that due to tail call optimization, the stacktrace
|
||||
may not report the direct caller of the function.
|
||||
"""
|
||||
def format_caller(trace // nil) do
|
||||
trace = trace || try do
|
||||
throw(:stacktrace)
|
||||
catch
|
||||
:stacktrace -> Enum.drop(:erlang.get_stacktrace, 1)
|
||||
end
|
||||
|
||||
if entry = Enum.at(trace, 1) do
|
||||
format_stacktrace_entry(entry)
|
||||
else
|
||||
"nofile:0: "
|
||||
s -> " " <> Enum.map_join(s, "\n ", &format_stacktrace_entry(&1)) <> "\n"
|
||||
end
|
||||
end
|
||||
|
||||
@@ -257,12 +295,7 @@ defmodule Exception do
|
||||
|
||||
"""
|
||||
def format_fa(fun, arity) do
|
||||
if is_list(arity) do
|
||||
inspected = lc x inlist arity, do: inspect(x)
|
||||
"#{inspect fun}(#{Enum.join(inspected, ", ")})"
|
||||
else
|
||||
"#{inspect fun}/#{arity}"
|
||||
end
|
||||
"#{inspect fun}#{format_arity(arity)}"
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -279,60 +312,66 @@ defmodule Exception do
|
||||
iex> Exception.format_mfa nil, :bar, []
|
||||
"nil.bar()"
|
||||
|
||||
Anonymous functions are reported as -func/arity-anonfn-count-,
|
||||
where func is the name of the enclosing function. Convert to
|
||||
"anonymous fn in func/arity"
|
||||
"""
|
||||
def format_mfa(module, fun, arity) do
|
||||
def format_mfa(module, fun, arity) when is_atom(fun) do
|
||||
fun =
|
||||
case inspect(fun) do
|
||||
<< ?:, erl :: binary >> -> erl
|
||||
elixir -> elixir
|
||||
":" <> fun -> fun
|
||||
fun -> fun
|
||||
end
|
||||
|
||||
if is_list(arity) do
|
||||
inspected = lc x inlist arity, do: inspect(x)
|
||||
"#{format_module module}#{fun}(#{Enum.join(inspected, ", ")})"
|
||||
else
|
||||
"#{format_module module}#{fun}/#{arity}"
|
||||
case match?("\"-" <> _, fun) and String.split(fun, "-") do
|
||||
[ "\"", outer_fun, "fun", _count, "\"" ] ->
|
||||
"anonymous fn#{format_arity(arity)} in #{inspect module}.#{outer_fun}"
|
||||
_ ->
|
||||
"#{inspect module}.#{fun}#{format_arity(arity)}"
|
||||
end
|
||||
end
|
||||
|
||||
defp format_module(mod), do: "#{inspect mod}."
|
||||
defp format_arity(arity) when is_list(arity) do
|
||||
inspected = lc x inlist arity, do: inspect(x)
|
||||
"(#{Enum.join(inspected, ", ")})"
|
||||
end
|
||||
|
||||
defp format_arity(arity), do: "/#{arity}"
|
||||
|
||||
@doc """
|
||||
Formats the given file and line as shown in stacktraces.
|
||||
If any of the values are nil, they are omitted.
|
||||
|
||||
The current working directory may be given as an argument,
|
||||
otherwise one is automatically retrieved.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Exception.format_file_line("foo", 1)
|
||||
"foo:1: "
|
||||
"foo:1:"
|
||||
|
||||
iex> Exception.format_file_line("foo", nil)
|
||||
"foo: "
|
||||
"foo:"
|
||||
|
||||
iex> Exception.format_file_line(nil, nil)
|
||||
""
|
||||
|
||||
"""
|
||||
def format_file_line(file, line, cwd // nil) do
|
||||
if file do
|
||||
file = to_string(file)
|
||||
file = if cwd, do: Path.relative_to(file, cwd), else: Path.relative_to_cwd(file)
|
||||
def format_file_line(file, line) do
|
||||
format_file_line(file, line, "")
|
||||
end
|
||||
|
||||
defp format_file_line(file, line, suffix) do
|
||||
if file do
|
||||
if line && line != 0 do
|
||||
"#{file}:#{line}: "
|
||||
"#{file}:#{line}:#{suffix}"
|
||||
else
|
||||
"#{file}: "
|
||||
"#{file}:#{suffix}"
|
||||
end
|
||||
else
|
||||
""
|
||||
end
|
||||
end
|
||||
|
||||
defp format_location(opts, cwd) do
|
||||
format_file_line Keyword.get(opts, :file), Keyword.get(opts, :line), cwd
|
||||
defp format_location(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
|
||||
|
||||
+105
-105
@@ -31,22 +31,6 @@ defrecord File.Stat, Record.extract(:file_info, from_lib: "kernel/include/file.h
|
||||
"""
|
||||
end
|
||||
|
||||
defexception File.Error, [reason: nil, action: "", path: nil] do
|
||||
def message(exception) do
|
||||
formatted = iolist_to_binary(:file.format_error(reason exception))
|
||||
"could not #{action exception} #{path exception}: #{formatted}"
|
||||
end
|
||||
end
|
||||
|
||||
defexception File.CopyError, [reason: nil, action: "", source: nil, destination: nil, on: nil] do
|
||||
def message(exception) do
|
||||
formatted = iolist_to_binary(:file.format_error(reason exception))
|
||||
location = if on = on(exception), do: ". #{on}", else: ""
|
||||
"could not #{action exception} from #{source exception} to " <>
|
||||
"#{destination exception}#{location}: #{formatted}"
|
||||
end
|
||||
end
|
||||
|
||||
defmodule File do
|
||||
@moduledoc """
|
||||
This module contains functions to manipulate files.
|
||||
@@ -63,9 +47,9 @@ defmodule File do
|
||||
in the `IO` module. By default, a file is opened in binary mode
|
||||
which requires the functions `IO.binread/2` and `IO.binwrite/2`
|
||||
to interact with the file. A developer may pass `:utf8` as an
|
||||
option when opening the file and then all other functions
|
||||
from `IO` are available, since they work directly with Unicode
|
||||
data.
|
||||
option when opening the file, then the slower `IO.read/2` and
|
||||
`IO.write/2` functions must be used as they are responsible for
|
||||
doing the proper conversions and data guarantees.
|
||||
|
||||
Most of the functions in this module return `:ok` or
|
||||
`{ :ok, result }` in case of success, `{ :error, reason }`
|
||||
@@ -101,7 +85,7 @@ defmodule File do
|
||||
|
||||
## Examples
|
||||
|
||||
File.regular? __FILE__ #=> true
|
||||
File.regular? __ENV__.file #=> true
|
||||
|
||||
"""
|
||||
def regular?(path) do
|
||||
@@ -236,7 +220,7 @@ defmodule File do
|
||||
Default is `:local`.
|
||||
|
||||
"""
|
||||
def stat(path, opts // []) do
|
||||
def stat(path, opts \\ []) do
|
||||
case F.read_file_info(path, opts) do
|
||||
{:ok, fileinfo} ->
|
||||
{:ok, set_elem(fileinfo, 0, File.Stat) }
|
||||
@@ -249,7 +233,7 @@ defmodule File do
|
||||
Same as `stat/2` but returns the `File.Stat` directly and
|
||||
throws `File.Error` if an error is returned.
|
||||
"""
|
||||
def stat!(path, opts // []) do
|
||||
def stat!(path, opts \\ []) do
|
||||
case stat(path, opts) do
|
||||
{:ok, info} -> info
|
||||
{:error, reason} ->
|
||||
@@ -261,7 +245,7 @@ defmodule File do
|
||||
Writes the given `File.Stat` back to the filesystem at the given
|
||||
path. Returns `:ok` or `{ :error, reason }`.
|
||||
"""
|
||||
def write_stat(path, File.Stat[] = stat, opts // []) do
|
||||
def write_stat(path, File.Stat[] = stat, opts \\ []) do
|
||||
F.write_file_info(path, set_elem(stat, 0, :file_info), opts)
|
||||
end
|
||||
|
||||
@@ -269,7 +253,7 @@ defmodule File do
|
||||
Same as `write_stat/3` but raises an exception if it fails.
|
||||
Returns `:ok` otherwise.
|
||||
"""
|
||||
def write_stat!(path, File.Stat[] = stat, opts // []) do
|
||||
def write_stat!(path, File.Stat[] = stat, opts \\ []) do
|
||||
case write_stat(path, stat, opts) do
|
||||
:ok -> :ok
|
||||
{ :error, reason } ->
|
||||
@@ -281,7 +265,7 @@ defmodule File do
|
||||
Updates modification time (mtime) and access time (atime) of
|
||||
the given file. File is created if it doesn’t exist.
|
||||
"""
|
||||
def touch(path, time // :calendar.local_time) do
|
||||
def touch(path, time \\ :calendar.local_time) do
|
||||
case F.change_time(path, time) do
|
||||
{ :error, :enoent } ->
|
||||
write(path, "")
|
||||
@@ -295,7 +279,7 @@ defmodule File do
|
||||
Same as `touch/2` but raises an exception if it fails.
|
||||
Returns `:ok` otherwise.
|
||||
"""
|
||||
def touch!(path, time // :calendar.local_time) do
|
||||
def touch!(path, time \\ :calendar.local_time) do
|
||||
case touch(path, time) do
|
||||
:ok -> :ok
|
||||
{ :error, reason } ->
|
||||
@@ -325,7 +309,7 @@ defmodule File do
|
||||
Typical error reasons are the same as in `open/2`,
|
||||
`read/1` and `write/3`.
|
||||
"""
|
||||
def copy(source, destination, bytes_count // :infinity) do
|
||||
def copy(source, destination, bytes_count \\ :infinity) do
|
||||
F.copy(source, destination, bytes_count)
|
||||
end
|
||||
|
||||
@@ -333,7 +317,7 @@ defmodule File do
|
||||
The same as `copy/3` but raises an `File.CopyError` if it fails.
|
||||
Returns the `bytes_copied` otherwise.
|
||||
"""
|
||||
def copy!(source, destination, bytes_count // :infinity) do
|
||||
def copy!(source, destination, bytes_count \\ :infinity) do
|
||||
case copy(source, destination, bytes_count) do
|
||||
{ :ok, bytes_count } -> bytes_count
|
||||
{ :error, reason } ->
|
||||
@@ -345,11 +329,6 @@ defmodule File do
|
||||
@doc """
|
||||
Copies the contents in `source` to `destination` preserving its mode.
|
||||
|
||||
Similar to the command `cp` in Unix systems, this function
|
||||
behaves differently depending if `destination` is a directory
|
||||
or not. In particular, if `destination` is a directory, it
|
||||
copies the contents of `source` to `destination/source`.
|
||||
|
||||
If a file already exists in the destination, it invokes a
|
||||
callback which should return `true` if the existing file
|
||||
should be overwritten, `false` otherwise. It defaults to return `true`.
|
||||
@@ -360,17 +339,14 @@ defmodule File do
|
||||
If you want to copy contents from an io device to another device
|
||||
or do a straight copy from a source to a destination without
|
||||
preserving modes, check `copy/3` instead.
|
||||
"""
|
||||
def cp(source, destination, callback // fn(_, _) -> true end) do
|
||||
output =
|
||||
if dir?(destination) do
|
||||
mkdir(destination)
|
||||
FN.join(destination, FN.basename(source))
|
||||
else
|
||||
destination
|
||||
end
|
||||
|
||||
case do_cp_file(source, output, callback, []) do
|
||||
Note: The command `cp` in Unix systems behaves differently depending
|
||||
if `destination` is an existing directory or not. We have chosen to
|
||||
explicitly disallow this behaviour. If destination is a directory, an
|
||||
error will be returned.
|
||||
"""
|
||||
def cp(source, destination, callback \\ fn(_, _) -> true end) do
|
||||
case do_cp_file(source, destination, callback, []) do
|
||||
{ :error, reason, _ } -> { :error, reason }
|
||||
_ -> :ok
|
||||
end
|
||||
@@ -380,7 +356,7 @@ defmodule File do
|
||||
The same as `cp/3`, but raises `File.CopyError` if it fails.
|
||||
Returns the list of copied files otherwise.
|
||||
"""
|
||||
def cp!(source, destination, callback // fn(_, _) -> true end) do
|
||||
def cp!(source, destination, callback \\ fn(_, _) -> true end) do
|
||||
case cp(source, destination, callback) do
|
||||
:ok -> :ok
|
||||
{ :error, reason } ->
|
||||
@@ -390,16 +366,12 @@ defmodule File do
|
||||
end
|
||||
end
|
||||
|
||||
@doc %S"""
|
||||
@doc ~S"""
|
||||
Copies the contents in source to destination.
|
||||
Similar to the command `cp -r` in Unix systems,
|
||||
this function behaves differently depending
|
||||
if `source` and `destination` are a file or a directory.
|
||||
|
||||
If both are files, it simply copies `source` to
|
||||
`destination`. However, if `destination` is a directory,
|
||||
it copies the contents of `source` to `destination/source`
|
||||
recursively.
|
||||
If the source is a file, it copies `source` to
|
||||
`destination`. If the source is a directory, it copies
|
||||
the contents inside source into the destination.
|
||||
|
||||
If a file already exists in the destination,
|
||||
it invokes a callback which should return
|
||||
@@ -419,33 +391,26 @@ defmodule File do
|
||||
success with all files and directories copied in no
|
||||
specific order, `{ :error, reason }` otherwise.
|
||||
|
||||
Note: The command `cp` in Unix systems behaves differently
|
||||
depending if `destination` is an existing directory or not.
|
||||
We have chosen to explicitly disallow this behaviour.
|
||||
|
||||
## Examples
|
||||
|
||||
# Copies "a.txt" to "tmp/a.txt"
|
||||
File.cp_r "a.txt", "tmp"
|
||||
|
||||
# Copies all files in "samples" to "tmp/samples"
|
||||
File.cp_r "samples", "tmp"
|
||||
# Copies "a.txt" to "tmp"
|
||||
File.cp_r "a.txt", "tmp.txt"
|
||||
|
||||
# Copies all files in "samples" to "tmp"
|
||||
File.cp_r "samples/.", "tmp"
|
||||
File.cp_r "samples", "tmp"
|
||||
|
||||
# Same as before, but asks the user how to proceed in case of conflicts
|
||||
File.cp_r "samples/.", "tmp", fn(source, destination) ->
|
||||
File.cp_r "samples", "tmp", fn(source, destination) ->
|
||||
IO.gets("Overwriting #{destination} by #{source}. Type y to confirm.") == "y"
|
||||
end
|
||||
|
||||
"""
|
||||
def cp_r(source, destination, callback // fn(_, _) -> true end) when is_function(callback) do
|
||||
output =
|
||||
if dir?(destination) || dir?(source) do
|
||||
mkdir(destination)
|
||||
FN.join(destination, FN.basename(source))
|
||||
else
|
||||
destination
|
||||
end
|
||||
|
||||
case do_cp_r(source, output, callback, []) do
|
||||
def cp_r(source, destination, callback \\ fn(_, _) -> true end) when is_function(callback) do
|
||||
case do_cp_r(source, destination, callback, []) do
|
||||
{ :error, _, _ } = error -> error
|
||||
res -> { :ok, res }
|
||||
end
|
||||
@@ -455,7 +420,7 @@ defmodule File do
|
||||
The same as `cp_r/3`, but raises `File.CopyError` if it fails.
|
||||
Returns the list of copied files otherwise.
|
||||
"""
|
||||
def cp_r!(source, destination, callback // fn(_, _) -> true end) do
|
||||
def cp_r!(source, destination, callback \\ fn(_, _) -> true end) do
|
||||
case cp_r(source, destination, callback) do
|
||||
{ :ok, files } -> files
|
||||
{ :error, reason, file } ->
|
||||
@@ -560,15 +525,17 @@ defmodule File do
|
||||
* :enospc - There is a no space left on the device.
|
||||
* :eacces - Missing permission for writing the file or searching one of the parent directories.
|
||||
* :eisdir - The named file is a directory.
|
||||
|
||||
Check `File.open/2` for existing modes.
|
||||
"""
|
||||
def write(path, content, modes // []) do
|
||||
def write(path, content, modes \\ []) do
|
||||
F.write_file(path, content, modes)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Same as `write/3` but raises an exception if it fails, returns `:ok` otherwise.
|
||||
"""
|
||||
def write!(path, content, modes // []) do
|
||||
def write!(path, content, modes \\ []) do
|
||||
case F.write_file(path, content, modes) do
|
||||
:ok -> :ok
|
||||
{ :error, reason } ->
|
||||
@@ -663,7 +630,7 @@ defmodule File do
|
||||
do_rm_rf(path, { :ok, [] })
|
||||
end
|
||||
|
||||
defp do_rm_rf(path, { :ok, acc } = entry) do
|
||||
defp do_rm_rf(path, { :ok, _ } = entry) do
|
||||
case safe_list_dir(path) do
|
||||
{ :ok, files } when is_list(files) ->
|
||||
res =
|
||||
@@ -781,7 +748,7 @@ defmodule File do
|
||||
|
||||
If a function is given two modes (instead of a list), it dispatches to `open/3`.
|
||||
|
||||
Check http://www.erlang.org/doc/man/file.html#open-2 for more information about
|
||||
Check http:\\www.erlang.org/doc/man/file.html#open-2 for more information about
|
||||
other options like `read_ahead` and `delayed_write`.
|
||||
|
||||
This function returns:
|
||||
@@ -802,7 +769,7 @@ defmodule File do
|
||||
File.close(file)
|
||||
|
||||
"""
|
||||
def open(path, modes // [])
|
||||
def open(path, modes \\ [])
|
||||
|
||||
def open(path, modes) when is_list(modes) do
|
||||
F.open(path, open_defaults(modes, true))
|
||||
@@ -849,7 +816,7 @@ defmodule File do
|
||||
Same as `open/2` but raises an error if file could not be opened.
|
||||
Returns the `io_device` otherwise.
|
||||
"""
|
||||
def open!(path, modes // []) do
|
||||
def open!(path, modes \\ []) do
|
||||
case open(path, modes) do
|
||||
{ :ok, device } -> device
|
||||
{ :error, reason } ->
|
||||
@@ -936,7 +903,7 @@ defmodule File do
|
||||
It returns `{ :ok, [files] }` in case of success,
|
||||
`{ :error, reason }` otherwise.
|
||||
"""
|
||||
def ls(path // ".") do
|
||||
def ls(path \\ ".") do
|
||||
case F.list_dir(path) do
|
||||
{ :ok, file_list } -> { :ok, Enum.map(file_list, &to_string(&1)) }
|
||||
{ :error, _ } = error -> error
|
||||
@@ -947,7 +914,7 @@ defmodule File do
|
||||
The same as `ls/1` but raises `File.Error`
|
||||
in case of an error.
|
||||
"""
|
||||
def ls!(dir // ".") do
|
||||
def ls!(dir \\ ".") do
|
||||
case ls(dir) do
|
||||
{ :ok, value } -> value
|
||||
{ :error, reason } ->
|
||||
@@ -968,39 +935,72 @@ defmodule File do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Opens the given `file` with the given `mode` and returns
|
||||
a stream for each `:line` (default) or for a given number
|
||||
Opens the file at the given `path` with the given `modes` and
|
||||
returns a stream for each `:line` (default) or for a given number
|
||||
of bytes given by `line_or_bytes`.
|
||||
|
||||
The returned stream will fail for the same reasons as `File.open!`.
|
||||
Note that the file is opened only and every time streaming begins.
|
||||
The returned stream will fail for the same reasons as
|
||||
`File.open!/2`. Note that the file is opened only and every time
|
||||
streaming begins.
|
||||
|
||||
Note that stream by default uses `IO.binread/2` unless
|
||||
the file is opened with an encoding, then the slower `IO.read/2`
|
||||
is used to do the proper data conversion and guarantees.
|
||||
"""
|
||||
def stream!(file, mode // [], line_or_bytes // :line) do
|
||||
fn(acc, fun) ->
|
||||
device = open!(file, mode)
|
||||
try do
|
||||
IO.stream(device, line_or_bytes, acc, fun)
|
||||
after
|
||||
F.close(device)
|
||||
def stream!(path, modes \\ [], line_or_bytes \\ :line) do
|
||||
modes = open_defaults(modes, true)
|
||||
bin = nil? List.keyfind(modes, :encoding, 0)
|
||||
|
||||
start_fun =
|
||||
fn ->
|
||||
case F.open(path, modes) do
|
||||
{ :ok, device } -> device
|
||||
{ :error, reason } ->
|
||||
raise File.Error, reason: reason, action: "stream", path: to_string(path)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
next_fun =
|
||||
case bin do
|
||||
true -> &IO.do_binstream(&1, line_or_bytes)
|
||||
false -> &IO.do_stream(&1, line_or_bytes)
|
||||
end
|
||||
|
||||
Stream.resource(start_fun, next_fun, &F.close/1)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Opens the given `file` with the given `mode` and returns
|
||||
a binstream for each `:line` (default) or for a given number
|
||||
of bytes given by `line_or_bytes`.
|
||||
Receives a stream and returns a new stream that will open the file
|
||||
at the given `path` for write with the extra `modes` and write
|
||||
each value to the file.
|
||||
|
||||
The returned stream will fail for the same reasons as `File.open!`.
|
||||
Note that the file is opened only and every time streaming begins.
|
||||
The returned stream will fail for the same reasons as
|
||||
`File.open!/2`. Note that the file is opened only and every time
|
||||
streaming begins.
|
||||
|
||||
Note that stream by default uses `IO.binwrite/2` unless
|
||||
the file is opened with an encoding, then the slower `IO.write/2`
|
||||
is used to do the proper data conversion and guarantees.
|
||||
"""
|
||||
def binstream!(file, mode // [], line_or_bytes // :line) do
|
||||
fn(fun, acc) ->
|
||||
device = open!(file, mode)
|
||||
try do
|
||||
IO.binstream(device, line_or_bytes, fun, acc)
|
||||
after
|
||||
F.close(device)
|
||||
def stream_to!(stream, path, modes \\ []) do
|
||||
modes = open_defaults([:write|List.delete(modes, :write)], true)
|
||||
bin = nil? List.keyfind(modes, :encoding, 0)
|
||||
|
||||
fn acc, f ->
|
||||
case F.open(path, modes) do
|
||||
{ :ok, device } ->
|
||||
each =
|
||||
case bin do
|
||||
true -> &IO.binwrite(device, &1)
|
||||
false -> &IO.write(device, &1)
|
||||
end
|
||||
|
||||
stream
|
||||
|> Stream.each(each)
|
||||
|> Stream.after(fn -> F.close(device) end)
|
||||
|> Enumerable.Stream.Lazy.reduce(acc, f)
|
||||
{ :error, reason } ->
|
||||
raise File.Error, reason: reason, action: "stream_to", path: to_string(path)
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -1046,7 +1046,7 @@ defmodule File do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Changes the owner given by the user id `gid`
|
||||
Changes the owner given by the user id `uid`
|
||||
for a given `file`. Returns `:ok` on success,
|
||||
or `{:error, reason}` on failure.
|
||||
"""
|
||||
@@ -1057,8 +1057,8 @@ defmodule File do
|
||||
@doc """
|
||||
Same as `chown/2`, but raises an exception in case of failure. Otherwise `:ok`.
|
||||
"""
|
||||
def chown!(file, gid) do
|
||||
case chown(file, gid) do
|
||||
def chown!(file, uid) do
|
||||
case chown(file, uid) do
|
||||
:ok -> :ok
|
||||
{ :error, reason } ->
|
||||
raise File.Error, reason: reason, action: "change owner for", path: to_string(file)
|
||||
|
||||
+92
-13
@@ -4,8 +4,8 @@ defmodule Float do
|
||||
"""
|
||||
|
||||
@doc """
|
||||
Parses a binary into a float.
|
||||
|
||||
Parses a binary into a float.
|
||||
|
||||
If successful, returns a tuple of the form `{ float, remainder_of_binary }`.
|
||||
Otherwise `:error`.
|
||||
|
||||
@@ -22,28 +22,40 @@ defmodule Float do
|
||||
|
||||
"""
|
||||
@spec parse(binary) :: { float, binary } | :error
|
||||
def parse(binary) when is_binary(binary) do
|
||||
def parse("-" <> binary) do
|
||||
case parse_unsign(binary) do
|
||||
:error -> :error
|
||||
{ number, remainder } -> { -number, remainder }
|
||||
end
|
||||
end
|
||||
|
||||
def parse(binary) do
|
||||
parse_unsign(binary)
|
||||
end
|
||||
|
||||
defp parse_unsign("-" <> _), do: :error
|
||||
defp parse_unsign(binary) when is_binary(binary) do
|
||||
case Integer.parse binary do
|
||||
:error -> :error
|
||||
{ integer_part, after_integer } -> parse after_integer, integer_part
|
||||
{ integer_part, after_integer } -> parse_unsign after_integer, integer_part
|
||||
end
|
||||
end
|
||||
|
||||
# Dot followed by digit is required afterwards or we are done
|
||||
defp parse(<< ?., char, rest :: binary >>, int) when char in ?0..?9 do
|
||||
parse(rest, char - ?0, 1, int)
|
||||
defp parse_unsign(<< ?., char, rest :: binary >>, int) when char in ?0..?9 do
|
||||
parse_unsign(rest, char - ?0, 1, int)
|
||||
end
|
||||
|
||||
defp parse(rest, int) do
|
||||
defp parse_unsign(rest, int) do
|
||||
{ :erlang.float(int), rest }
|
||||
end
|
||||
|
||||
# Handle decimal points
|
||||
defp parse(<< char, rest :: binary >>, float, decimal, int) when char in ?0..?9 do
|
||||
parse rest, 10 * float + (char - ?0), decimal + 1, int
|
||||
defp parse_unsign(<< char, rest :: binary >>, float, decimal, int) when char in ?0..?9 do
|
||||
parse_unsign rest, 10 * float + (char - ?0), decimal + 1, int
|
||||
end
|
||||
|
||||
defp parse(<< ?e, after_e :: binary >>, float, decimal, int) do
|
||||
defp parse_unsign(<< ?e, after_e :: binary >>, float, decimal, int) do
|
||||
case Integer.parse after_e do
|
||||
:error ->
|
||||
# Note we rebuild the binary here instead of breaking it apart at
|
||||
@@ -56,11 +68,11 @@ defmodule Float do
|
||||
end
|
||||
end
|
||||
|
||||
defp parse(bitstring, float, decimal, int) do
|
||||
defp parse_unsign(bitstring, float, decimal, int) do
|
||||
{ floatify(int, float, decimal), bitstring }
|
||||
end
|
||||
|
||||
defp floatify(int, float, decimal, exponential // 0) do
|
||||
defp floatify(int, float, decimal, exponential \\ 0) do
|
||||
multiplier = if int < 0, do: -1.0, else: 1.0
|
||||
|
||||
# Try to ensure the minimum amount of rounding errors
|
||||
@@ -70,7 +82,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
|
||||
@@ -80,4 +92,71 @@ defmodule Float do
|
||||
result
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Round 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
|
||||
|
||||
"""
|
||||
@spec floor(float | integer) :: integer
|
||||
def floor(num) when is_integer(num), do: num
|
||||
def floor(num) when is_float(num) do
|
||||
truncated = :erlang.trunc(num)
|
||||
case :erlang.abs(num - truncated) do
|
||||
x when x > 0 and num < 0 -> truncated - 1
|
||||
_ -> truncated
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Round 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
|
||||
|
||||
"""
|
||||
@spec ceil(float | integer) :: integer
|
||||
def ceil(num) when is_integer(num), do: num
|
||||
def ceil(num) when is_float(num) do
|
||||
truncated = :erlang.trunc(num)
|
||||
case :erlang.abs(num - truncated) do
|
||||
x when x > 0 and num > 0 -> truncated + 1
|
||||
_ -> truncated
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Rounds a floating point value to an arbitrary number of fractional digits
|
||||
(between 0 and 15).
|
||||
|
||||
## Examples
|
||||
|
||||
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
|
||||
|
||||
@@ -25,7 +25,6 @@ defmodule GenEvent.Behaviour do
|
||||
def handle_call(:notifications, notifications) do
|
||||
{:ok, Enum.reverse(notifications), []}
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
{ :ok, pid } = :gen_event.start_link
|
||||
|
||||
@@ -21,78 +21,67 @@ defmodule GenFSM.Behaviour do
|
||||
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
|
||||
# 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
|
||||
#
|
||||
# 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
|
||||
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()
|
||||
|
||||
@@ -138,15 +127,15 @@ defmodule GenFSM.Behaviour do
|
||||
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_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
|
||||
* `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
|
||||
* `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
|
||||
* `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_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
|
||||
@@ -154,9 +143,9 @@ defmodule GenFSM.Behaviour do
|
||||
|
||||
For each state you need to define either or both of these:
|
||||
|
||||
* `state_name(event, state_data)` - invoked to handle
|
||||
* `state_name(event, state_data)` - invoked to handle
|
||||
`send_event` messages;
|
||||
* `state_name(event, from, state_data)`- invoked to handle
|
||||
* `state_name(event, from, state_data)`- invoked to handle
|
||||
`sync_send_event` messages;
|
||||
|
||||
If you send asynchronous events you only need to implement the
|
||||
@@ -177,16 +166,16 @@ defmodule GenFSM.Behaviour do
|
||||
@doc false
|
||||
defmacro __using__(_) do
|
||||
quote location: :keep do
|
||||
@behavior :gen_fsm
|
||||
@behaviour :gen_fsm
|
||||
|
||||
@doc false
|
||||
def handle_event(_event, state_name, state_data) do
|
||||
{ :next_state, state_name, state_data }
|
||||
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
|
||||
{ :reply, :ok, state_name, state_data }
|
||||
def handle_sync_event(event, from, state_name, state_data) do
|
||||
{ :stop, {:bad_sync_event, state_name, event}, state_data }
|
||||
end
|
||||
|
||||
@doc false
|
||||
|
||||
@@ -86,7 +86,7 @@ defmodule GenServer.Behaviour do
|
||||
@doc false
|
||||
defmacro __using__(_) do
|
||||
quote location: :keep do
|
||||
@behavior :gen_server
|
||||
@behaviour :gen_server
|
||||
|
||||
@doc false
|
||||
def init(args) do
|
||||
@@ -94,8 +94,8 @@ defmodule GenServer.Behaviour do
|
||||
end
|
||||
|
||||
@doc false
|
||||
def handle_call(_request, _from, state) do
|
||||
{ :noreply, state }
|
||||
def handle_call(request, _from, state) do
|
||||
{ :stop, { :bad_call, request }, state }
|
||||
end
|
||||
|
||||
@doc false
|
||||
@@ -104,8 +104,8 @@ defmodule GenServer.Behaviour do
|
||||
end
|
||||
|
||||
@doc false
|
||||
def handle_cast(_msg, state) do
|
||||
{ :noreply, state }
|
||||
def handle_cast(msg, state) do
|
||||
{ :stop, { :bad_cast, msg }, state }
|
||||
end
|
||||
|
||||
@doc false
|
||||
|
||||
+159
-349
@@ -2,46 +2,14 @@ defmodule HashDict do
|
||||
@moduledoc """
|
||||
A key-value store.
|
||||
|
||||
The `HashDict` is meant to work well with both small and
|
||||
large set of keys and it is an implementation of the `Dict`
|
||||
behaviour. For more information about the functions and
|
||||
their APIs, please consult the `Dict` module.
|
||||
The `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.
|
||||
"""
|
||||
|
||||
use Dict.Behaviour
|
||||
|
||||
# A dictionary (key-value) implementation based on dynamic hashing.
|
||||
#
|
||||
# This implementation is based on hash tries. We first start with
|
||||
# a set of 8 buckets and expand when the density is about 5 entries
|
||||
# per bucket. We use bit shifting to make rehashing faster on
|
||||
# expansion.
|
||||
#
|
||||
# Compared to dict, it provides many enhancements:
|
||||
#
|
||||
# 1. HashDict buckets are ordered sets, this gives us faster access
|
||||
# and modification times
|
||||
#
|
||||
# 2. It uses phash2 to calculate the hash (instead of phash)
|
||||
#
|
||||
# 3. The dictionary first starts with a single bucket, instead of
|
||||
# a set of 8 buckets. This allow us to skip hashing altogher
|
||||
# for small dictionaries, providing faster operations and
|
||||
# reducing memory consumption
|
||||
#
|
||||
# 4. Once we reach 8 elements, the dictionary is promoted to a
|
||||
# set of buckets
|
||||
|
||||
# The ordered record contains a single bucket
|
||||
@ordered_threshold 8
|
||||
|
||||
defrecordp :ordered, HashDict,
|
||||
size: 0,
|
||||
bucket: []
|
||||
|
||||
# The bucketed record contains a series of buckets.
|
||||
@expand_load 5
|
||||
@contract_load 2
|
||||
@node_bitmap 0b111
|
||||
@node_shift 3
|
||||
@node_size 8
|
||||
@@ -49,23 +17,18 @@ defmodule HashDict do
|
||||
|
||||
defrecordp :trie, HashDict,
|
||||
size: 0,
|
||||
depth: 0,
|
||||
expand_on: @node_size * @expand_load,
|
||||
contract_on: @contract_load,
|
||||
root: @node_template
|
||||
|
||||
import Bitwise
|
||||
|
||||
# Let's inline common instructions
|
||||
# Inline common instructions
|
||||
@compile :inline_list_funcs
|
||||
@compile { :inline, bucket_hash: 1, bucket_index: 1, bucket_nth_index: 2, bucket_next: 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
|
||||
ordered()
|
||||
trie()
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -77,9 +40,9 @@ defmodule HashDict do
|
||||
#=> #HashDict<[a: 2, b: 1]>
|
||||
|
||||
"""
|
||||
@spec new(list({key :: term, value :: term})) :: Dict.t
|
||||
def new(pairs) do
|
||||
Enum.reduce pairs, ordered(), fn { k, v }, dict ->
|
||||
@spec new(Enum.t) :: Dict.t
|
||||
def new(enum) do
|
||||
Enum.reduce enum, trie(), fn { k, v }, dict ->
|
||||
put(dict, k, v)
|
||||
end
|
||||
end
|
||||
@@ -94,383 +57,230 @@ defmodule HashDict do
|
||||
#=> #HashDict<[{"a","a"},{"b","b"}]>
|
||||
|
||||
"""
|
||||
@spec new(list, (term -> {key :: term, value ::term})) :: Dict.t
|
||||
def new(list, transform) when is_function(transform) do
|
||||
Enum.reduce list, new(), fn i, dict ->
|
||||
@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
|
||||
end
|
||||
|
||||
def put(dict, key, value) do
|
||||
{ dict, _ } = dict_put(dict, key, { :put, value })
|
||||
dict
|
||||
def empty(trie()) do
|
||||
trie()
|
||||
end
|
||||
|
||||
def update!(dict, key, fun) when is_function(fun, 1) do
|
||||
case dict_put(dict, key, { :update, nil, fun }) do
|
||||
{ dict, 0 } ->
|
||||
dict
|
||||
{ _dict, 1 } ->
|
||||
raise KeyError, key: key
|
||||
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(dict, key, initial, fun) when is_function(fun, 1) do
|
||||
{ dict, _ } = dict_put(dict, key, { :update, initial, fun })
|
||||
dict
|
||||
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 fetch(ordered(bucket: bucket), key) do
|
||||
bucket_get(bucket, key)
|
||||
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, depth: depth), key) do
|
||||
bucket_get(node_bucket(root, depth, bucket_hash(key)), key)
|
||||
end
|
||||
|
||||
def pop(dict, key, default // nil) do
|
||||
case dict_delete(dict, key) do
|
||||
{ dict, _, 0 } -> { default, dict }
|
||||
{ dict, value, _ } -> { value, dict }
|
||||
end
|
||||
def fetch(trie(root: root), key) do
|
||||
do_fetch(root, key, key_hash(key))
|
||||
end
|
||||
|
||||
def delete(dict, key) do
|
||||
{ dict, _, _ } = dict_delete(dict, key)
|
||||
dict
|
||||
end
|
||||
|
||||
def size(dict) do
|
||||
elem(dict, 1)
|
||||
end
|
||||
|
||||
def empty(_) do
|
||||
ordered()
|
||||
end
|
||||
|
||||
def merge(dict, enum, callback // fn(_k, _v1, v2) -> v2 end)
|
||||
|
||||
def merge(dict1, dict2, callback) when is_record(dict1, HashDict) and is_record(dict2, HashDict) and elem(dict1, 1) < elem(dict2, 1) do
|
||||
dict_fold dict1, dict2, fn [k|v1], acc ->
|
||||
update(acc, k, v1, &callback.(k, v1, &1))
|
||||
case dict_delete(dict, key) do
|
||||
{ dict, _value } -> dict
|
||||
:error -> dict
|
||||
end
|
||||
end
|
||||
|
||||
def merge(dict1, dict2, callback) when is_record(dict1, HashDict) and is_record(dict2, HashDict) do
|
||||
dict_fold dict2, dict1, fn [k|v2], acc ->
|
||||
update(acc, k, v2, &callback.(k, &1, v2))
|
||||
def pop(dict, key, default \\ nil) do
|
||||
case dict_delete(dict, key) do
|
||||
{ dict, value } -> { value, dict }
|
||||
:error -> { default, dict }
|
||||
end
|
||||
end
|
||||
|
||||
def merge(dict, enumerable, callback) when is_record(dict, HashDict) do
|
||||
super(dict, enumerable, callback)
|
||||
def size(trie(size: size)) do
|
||||
size
|
||||
end
|
||||
|
||||
def reduce(trie(root: root), acc, fun) do
|
||||
do_reduce(root, acc, fun, @node_size, fn
|
||||
{:suspend, acc} -> {:suspended, acc, &{ :done, elem(&1, 1) }}
|
||||
{:halt, acc} -> {:halted, acc}
|
||||
{:cont, acc} -> {:done, acc}
|
||||
end)
|
||||
end
|
||||
|
||||
def split(dict, keys) do
|
||||
split(keys, new, dict)
|
||||
end
|
||||
|
||||
defp split([], including, excluding) do
|
||||
{ including, excluding }
|
||||
end
|
||||
|
||||
defp split([key|keys], including, excluding) do
|
||||
case dict_delete(excluding, key) do
|
||||
{ excluding, _, 0 } -> split(keys, including, excluding)
|
||||
{ excluding, value, _ } -> split(keys, put(including, key, value), excluding)
|
||||
Enum.reduce keys, { new, dict }, fn key, { inc, exc } = acc ->
|
||||
case dict_delete(exc, key) do
|
||||
{ exc, value } -> { put(inc, key, value), exc }
|
||||
:error -> acc
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@doc false
|
||||
def reduce(ordered(bucket: bucket), acc, fun) do
|
||||
bucket_reduce(bucket, acc, fun)
|
||||
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)) }
|
||||
end) |> elem(1)
|
||||
end
|
||||
|
||||
def reduce(trie(root: root, depth: depth), acc, fun) do
|
||||
node_reduce(root, depth, acc, fun, @node_size)
|
||||
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)) }
|
||||
end) |> elem(1)
|
||||
end
|
||||
|
||||
## Dict-wide functions
|
||||
## General helpers
|
||||
|
||||
defp dict_fold(ordered(bucket: bucket), acc, fun) do
|
||||
bucket_fold(bucket, acc, fun)
|
||||
end
|
||||
|
||||
defp dict_fold(trie(root: root, depth: depth), acc, fun) do
|
||||
node_fold(root, depth, acc, fun, @node_size)
|
||||
end
|
||||
|
||||
defp dict_put(ordered(size: @ordered_threshold, bucket: bucket), key, value) do
|
||||
root = node_relocate(bucket, 0)
|
||||
dict_put(trie(size: @ordered_threshold, root: root), key, value)
|
||||
end
|
||||
|
||||
defp dict_put(ordered(size: size, bucket: bucket) = dict, key, value) do
|
||||
{ new, count } = bucket_put(bucket, key, value)
|
||||
{ ordered(dict, size: size + count, bucket: new), count }
|
||||
end
|
||||
|
||||
defp dict_put(trie(root: root, depth: depth, size: size, expand_on: size, contract_on: contract_on) = dict, key, value) do
|
||||
root = node_expand(root, depth, depth + 1)
|
||||
dict = trie(dict, root: root, depth: depth + 1,
|
||||
expand_on: size * @node_size, contract_on: contract_on * @node_size)
|
||||
dict_put(dict, key, value)
|
||||
end
|
||||
|
||||
defp dict_put(trie(root: root, size: size, depth: depth) = dict, key, value) do
|
||||
pos = bucket_hash(key)
|
||||
{ root, count } = node_put(root, depth, pos, key, value)
|
||||
{ trie(dict, size: size + count, root: root), count }
|
||||
end
|
||||
|
||||
defp dict_delete(ordered(bucket: bucket, size: size) = dict, key) do
|
||||
case bucket_delete(bucket, key) do
|
||||
{ _, value, 0 } ->
|
||||
{ dict, value, 0 }
|
||||
{ new_bucket, value, -1 } ->
|
||||
{ ordered(dict, size: size - 1, bucket: new_bucket), value, -1 }
|
||||
defp dict_delete(trie(root: root, size: size), key) do
|
||||
case do_delete(root, key, key_hash(key)) do
|
||||
{ root, value } -> { trie(root: root, size: size - 1), value }
|
||||
:error -> :error
|
||||
end
|
||||
end
|
||||
|
||||
defp dict_delete(trie(root: root, size: size, depth: depth) = dict, key) do
|
||||
pos = bucket_hash(key)
|
||||
case node_delete(root, depth, pos, key) do
|
||||
{ _, value, 0 } ->
|
||||
{ dict, value, 0 }
|
||||
{ root, value, -1 } ->
|
||||
{ if depth > 0 and trie(dict, :contract_on) == size do
|
||||
root = node_contract(root, depth)
|
||||
trie(dict,
|
||||
root: root,
|
||||
size: size - 1,
|
||||
depth: depth - 1,
|
||||
contract_on: div(size, @node_size),
|
||||
expand_on: div(trie(dict, :expand_on), @node_size))
|
||||
else
|
||||
trie(dict, size: size - 1, root: root)
|
||||
end, value, -1 }
|
||||
## Dict manipulation
|
||||
|
||||
defp do_fetch(node, key, hash) do
|
||||
index = key_mask(hash)
|
||||
case elem(node, index) do
|
||||
[^key|v] -> {:ok, v}
|
||||
{^key, v, _} -> {:ok, v}
|
||||
{_, _, n} -> do_fetch(n, key, key_shift(hash))
|
||||
_ -> :error
|
||||
end
|
||||
end
|
||||
|
||||
## Bucket helpers
|
||||
|
||||
# Get value from the bucket
|
||||
defp bucket_get([[k|_]|_bucket], key) when k > key do
|
||||
:error
|
||||
defp do_put(node, key, value, hash) do
|
||||
index = key_mask(hash)
|
||||
case elem(node, index) do
|
||||
[] ->
|
||||
{set_elem(node, index, [key|value]), 1}
|
||||
[^key|_] ->
|
||||
{set_elem(node, index, [key|value]), 0}
|
||||
[k|v] ->
|
||||
n = set_elem(@node_template, key_mask(key_shift(hash)), [key|value])
|
||||
{set_elem(node, index, {k, v, n}), 1}
|
||||
{^key, _, n} ->
|
||||
{set_elem(node, index, {key, value, n}), 0}
|
||||
{k, v, n} ->
|
||||
{n, counter} = do_put(n, key, value, key_shift(hash))
|
||||
{set_elem(node, index, {k, v, n}), counter}
|
||||
end
|
||||
end
|
||||
|
||||
defp bucket_get([[key|value]|_bucket], key) do
|
||||
{ :ok, value }
|
||||
defp do_update(node, key, initial, fun, hash) do
|
||||
index = key_mask(hash)
|
||||
case elem(node, index) do
|
||||
[] ->
|
||||
{set_elem(node, index, [key|initial.()]), 1}
|
||||
[^key|value] ->
|
||||
{set_elem(node, index, [key|fun.(value)]), 0}
|
||||
[k|v] ->
|
||||
n = set_elem(@node_template, key_mask(key_shift(hash)), [key|initial.()])
|
||||
{set_elem(node, index, {k, v, n}), 1}
|
||||
{^key, value, n} ->
|
||||
{set_elem(node, index, {key, fun.(value), n}), 0}
|
||||
{k, v, n} ->
|
||||
{n, counter} = do_update(n, key, initial, fun, key_shift(hash))
|
||||
{set_elem(node, index, {k, v, n}), counter}
|
||||
end
|
||||
end
|
||||
|
||||
defp bucket_get([_e|bucket], key) do
|
||||
bucket_get(bucket, key)
|
||||
defp do_delete(node, key, hash) do
|
||||
index = key_mask(hash)
|
||||
case elem(node, index) do
|
||||
[] ->
|
||||
:error
|
||||
[^key|value] ->
|
||||
{set_elem(node, index, []), value}
|
||||
[_|_] ->
|
||||
:error
|
||||
{^key, value, n} ->
|
||||
{set_elem(node, index, do_compact_node(n)), value}
|
||||
{k, v, n} ->
|
||||
case do_delete(n, key, key_shift(hash)) do
|
||||
{@node_template, value} ->
|
||||
{set_elem(node, index, [k|v]), value}
|
||||
{n, value} ->
|
||||
{set_elem(node, index, {k, v, n}), value}
|
||||
:error ->
|
||||
:error
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defp bucket_get([], _key) do
|
||||
:error
|
||||
Enum.each 0..(@node_size - 1), fn index ->
|
||||
defp do_compact_node(node) when elem(node, unquote(index)) != [] do
|
||||
case elem(node, unquote(index)) do
|
||||
[k|v] ->
|
||||
case set_elem(node, unquote(index), []) do
|
||||
@node_template -> [k|v]
|
||||
n -> {k, v, n}
|
||||
end
|
||||
{k, v, n} ->
|
||||
{k, v, set_elem(node, unquote(index), do_compact_node(n))}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Puts a value in the bucket
|
||||
defp bucket_put([[k|_]|_]=bucket, key, { :put, value }) when k > key do
|
||||
{ [[key|value]|bucket], 1 }
|
||||
## Dict reduce
|
||||
|
||||
defp do_reduce_each(_node, {:halt, acc}, _fun, _next) do
|
||||
{:halted, acc}
|
||||
end
|
||||
|
||||
defp bucket_put([[k|_]|_]=bucket, key, { :update, initial, _fun }) when k > key do
|
||||
{ [[key|initial]|bucket], 1 }
|
||||
defp do_reduce_each(node, {:suspend, acc}, fun, next) do
|
||||
{:suspended, acc, &do_reduce_each(node, &1, fun, next)}
|
||||
end
|
||||
|
||||
defp bucket_put([[key|_]|bucket], key, { :put, value }) do
|
||||
{ [[key|value]|bucket], 0 }
|
||||
defp do_reduce_each([], acc, _fun, next) do
|
||||
next.(acc)
|
||||
end
|
||||
|
||||
defp bucket_put([[key|value]|bucket], key, { :update, _initial, fun }) do
|
||||
{ [[key|fun.(value)]|bucket], 0 }
|
||||
defp do_reduce_each([k|v], {:cont, acc}, fun, next) do
|
||||
next.(fun.({k,v}, acc))
|
||||
end
|
||||
|
||||
defp bucket_put([e|bucket], key, value) do
|
||||
{ rest, count } = bucket_put(bucket, key, value)
|
||||
{ [e|rest], count }
|
||||
defp do_reduce_each({k, v, n}, {:cont, acc}, fun, next) do
|
||||
do_reduce(n, fun.({k, v}, acc), fun, @node_size, next)
|
||||
end
|
||||
|
||||
defp bucket_put([], key, { :put, value }) do
|
||||
{ [[key|value]], 1 }
|
||||
defp do_reduce(node, acc, fun, count, next) when count > 0 do
|
||||
do_reduce_each(:erlang.element(count, node), acc, fun, &do_reduce(node, &1, fun, count - 1, next))
|
||||
end
|
||||
|
||||
defp bucket_put([], key, { :update, initial, _fun }) do
|
||||
{ [[key|initial]], 1 }
|
||||
defp do_reduce(_node, acc, _fun, 0, next) do
|
||||
next.(acc)
|
||||
end
|
||||
|
||||
# Puts a value in the bucket without returning
|
||||
# the operation value
|
||||
defp bucket_put!([[k|_]|_]=bucket, key, value) when k > key, do: [[key|value]|bucket]
|
||||
defp bucket_put!([[key|_]|bucket], key, value), do: [[key|value]|bucket]
|
||||
defp bucket_put!([e|bucket], key, value), do: [e|bucket_put!(bucket, key, value)]
|
||||
defp bucket_put!([], key, value), do: [[key|value]]
|
||||
## Key operations
|
||||
|
||||
# Deletes a key from the bucket
|
||||
defp bucket_delete([[k|_]|_]=bucket, key) when k > key do
|
||||
{ bucket, nil, 0 }
|
||||
end
|
||||
import Bitwise
|
||||
|
||||
defp bucket_delete([[key|value]|bucket], key) do
|
||||
{ bucket, value, -1 }
|
||||
end
|
||||
|
||||
defp bucket_delete([e|bucket], key) do
|
||||
{ rest, value, count } = bucket_delete(bucket, key)
|
||||
{ [e|rest], value, count }
|
||||
end
|
||||
|
||||
defp bucket_delete([], _key) do
|
||||
{ [], nil, 0 }
|
||||
end
|
||||
|
||||
# Reduces the bucket
|
||||
defp bucket_reduce([[k|v]|t], acc, fun) do
|
||||
bucket_reduce(t, fun.({ k, v }, acc), fun)
|
||||
end
|
||||
|
||||
defp bucket_reduce([], acc, _fun) do
|
||||
acc
|
||||
end
|
||||
|
||||
defp bucket_fold(bucket, acc, fun) do
|
||||
:lists.foldl(fun, acc, bucket)
|
||||
end
|
||||
|
||||
defp bucket_hash(key) do
|
||||
defp key_hash(key) do
|
||||
:erlang.phash2(key)
|
||||
end
|
||||
|
||||
defp bucket_index(hash) do
|
||||
defp key_mask(hash) do
|
||||
hash &&& @node_bitmap
|
||||
end
|
||||
|
||||
defp bucket_nth_index(hash, n) do
|
||||
(hash >>> (@node_shift * n)) &&& @node_bitmap
|
||||
end
|
||||
|
||||
defp bucket_next(hash) do
|
||||
defp key_shift(hash) do
|
||||
hash >>> @node_shift
|
||||
end
|
||||
|
||||
## Node helpers
|
||||
|
||||
# Gets a bucket from the node
|
||||
defp node_bucket(node, 0, hash) do
|
||||
elem(node, bucket_index(hash))
|
||||
end
|
||||
|
||||
defp node_bucket(node, depth, hash) do
|
||||
child = elem(node, bucket_index(hash))
|
||||
node_bucket(child, depth - 1, bucket_next(hash))
|
||||
end
|
||||
|
||||
# Puts a key-value into a node
|
||||
defp node_put(node, 0, hash, key, value) do
|
||||
pos = bucket_index(hash)
|
||||
{ new, count } = bucket_put(elem(node, pos), key, value)
|
||||
{ set_elem(node, pos, new), count }
|
||||
end
|
||||
|
||||
defp node_put(node, depth, hash, key, value) do
|
||||
pos = bucket_index(hash)
|
||||
{ new, count } = node_put(elem(node, pos), depth - 1, bucket_next(hash), key, value)
|
||||
{ set_elem(node, pos, new), count }
|
||||
end
|
||||
|
||||
# Deletes a key from the bucket
|
||||
defp node_delete(node, 0, hash, key) do
|
||||
pos = bucket_index(hash)
|
||||
case bucket_delete(elem(node, pos), key) do
|
||||
{ _, value, 0 } -> { node, value, 0 }
|
||||
{ new, value, -1 } -> { set_elem(node, pos, new), value, -1 }
|
||||
end
|
||||
end
|
||||
|
||||
defp node_delete(node, depth, hash, key) do
|
||||
pos = bucket_index(hash)
|
||||
case node_delete(elem(node, pos), depth - 1, bucket_next(hash), key) do
|
||||
{ _, value, 0 } -> { node, value, 0 }
|
||||
{ new, value, -1 } -> { set_elem(node, pos, new), value, -1 }
|
||||
end
|
||||
end
|
||||
|
||||
# Reduces a node recursively
|
||||
defp node_reduce(bucket, -1, acc, fun, _) do
|
||||
bucket_reduce(bucket, acc, fun)
|
||||
end
|
||||
|
||||
defp node_reduce(node, depth, acc, fun, count) when count >= 1 do
|
||||
acc = node_reduce(:erlang.element(count, node), depth - 1, acc, fun, @node_size)
|
||||
node_reduce(node, depth, acc, fun, count - 1)
|
||||
end
|
||||
|
||||
defp node_reduce(_node, _, acc, _fun, 0) do
|
||||
acc
|
||||
end
|
||||
|
||||
# Folds a node recursively
|
||||
defp node_fold(bucket, -1, acc, fun, _) do
|
||||
bucket_fold(bucket, acc, fun)
|
||||
end
|
||||
|
||||
defp node_fold(node, depth, acc, fun, count) when count >= 1 do
|
||||
acc = node_fold(:erlang.element(count, node), depth - 1, acc, fun, @node_size)
|
||||
node_fold(node, depth, acc, fun, count - 1)
|
||||
end
|
||||
|
||||
defp node_fold(_node, _, acc, _fun, 0) do
|
||||
acc
|
||||
end
|
||||
|
||||
# Node resizing
|
||||
defp node_expand({ b1, b2, b3, b4, b5, b6, b7, b8 }, 0, n) do
|
||||
{ node_relocate(b1, n), node_relocate(b2, n), node_relocate(b3, n),
|
||||
node_relocate(b4, n), node_relocate(b5, n), node_relocate(b6, n),
|
||||
node_relocate(b7, n), node_relocate(b8, n) }
|
||||
end
|
||||
|
||||
defp node_expand({ b1, b2, b3, b4, b5, b6, b7, b8 }, depth, n) do
|
||||
depth = depth - 1
|
||||
{ node_expand(b1, depth, n), node_expand(b2, depth, n), node_expand(b3, depth, n),
|
||||
node_expand(b4, depth, n), node_expand(b5, depth, n), node_expand(b6, depth, n),
|
||||
node_expand(b7, depth, n), node_expand(b8, depth, n) }
|
||||
end
|
||||
|
||||
defp node_contract({ b1, b2, b3, b4, b5, b6, b7, b8 }, depth) when depth > 0 do
|
||||
depth = depth - 1
|
||||
{ node_contract(b1, depth), node_contract(b2, depth), node_contract(b3, depth),
|
||||
node_contract(b4, depth), node_contract(b5, depth), node_contract(b6, depth),
|
||||
node_contract(b7, depth), node_contract(b8, depth) }
|
||||
end
|
||||
|
||||
defp node_contract({ b1, b2, b3, b4, b5, b6, b7, b8 }, 0) do
|
||||
b1 |> each_contract(b2) |> each_contract(b3) |> each_contract(b4)
|
||||
|> each_contract(b5) |> each_contract(b6) |> each_contract(b7)
|
||||
|> each_contract(b8)
|
||||
end
|
||||
|
||||
defp each_contract([[k|_v]=e|acc], [[key|_value]|_]=bucket) when k < key, do: [e|each_contract(acc, bucket)]
|
||||
defp each_contract(acc, [e|bucket]), do: [e|each_contract(acc, bucket)]
|
||||
defp each_contract([], bucket), do: bucket
|
||||
defp each_contract(acc, []), do: acc
|
||||
|
||||
defp node_relocate(node // @node_template, bucket, n) do
|
||||
:lists.foldl fn [key|value], acc ->
|
||||
pos = key |> bucket_hash() |> bucket_nth_index(n)
|
||||
set_elem(acc, pos, bucket_put!(elem(acc, pos), key, value))
|
||||
end, node, bucket
|
||||
end
|
||||
end
|
||||
|
||||
defimpl Enumerable, for: HashDict do
|
||||
def reduce(dict, acc, fun), do: HashDict.reduce(dict, acc, fun)
|
||||
def member?(dict, { k, v }), do: match?({ :ok, ^v }, HashDict.fetch(dict, k))
|
||||
def member?(_dict, _), do: false
|
||||
def count(dict), do: HashDict.size(dict)
|
||||
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
|
||||
|
||||
+178
-380
@@ -2,49 +2,33 @@ defmodule HashSet do
|
||||
@moduledoc """
|
||||
A set store.
|
||||
|
||||
The `HashSet` is meant to work well with both small and
|
||||
large sets. It is an implementation of the `Set` behaviour.
|
||||
For more information about the functions and their APIs,
|
||||
please consult the `Set` module.
|
||||
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
|
||||
functions and their APIs, please consult the `Set` module.
|
||||
"""
|
||||
|
||||
@behaviour Set
|
||||
|
||||
# The ordered record contains a single bucket.
|
||||
@ordered_threshold 8
|
||||
|
||||
defrecordp :ordered, HashSet,
|
||||
size: 0,
|
||||
bucket: []
|
||||
|
||||
# The bucketed record contains a series of buckets.
|
||||
@expand_load 5
|
||||
@contract_load 2
|
||||
@node_bitmap 0b1111
|
||||
@node_shift 4
|
||||
@node_size 16
|
||||
@node_bitmap 0b111
|
||||
@node_shift 3
|
||||
@node_size 8
|
||||
@node_template :erlang.make_tuple(@node_size, [])
|
||||
|
||||
@expand_default (@node_size * @expand_load)
|
||||
@contract_default @contract_load
|
||||
|
||||
defrecordp :trie, HashSet,
|
||||
size: 0,
|
||||
depth: 0,
|
||||
expand_on: @expand_default,
|
||||
contract_on: @contract_default,
|
||||
root: @node_template
|
||||
|
||||
import Bitwise
|
||||
|
||||
# Inline common instructions
|
||||
@compile :inline_list_funcs
|
||||
@compile { :inline, bucket_hash: 1, bucket_index: 1, bucket_nth_index: 2, bucket_next: 1 }
|
||||
@compile { :inline, key_hash: 1, key_mask: 1, key_shift: 1 }
|
||||
|
||||
@doc """
|
||||
Creates a new empty set.
|
||||
"""
|
||||
def new() do
|
||||
ordered()
|
||||
@spec new :: Set.t
|
||||
def new do
|
||||
trie()
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -52,434 +36,248 @@ defmodule HashSet do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> HashSet.new [1, 1, 2, 3, 3] |> HashSet.to_list
|
||||
[1,2,3]
|
||||
HashSet.new [1, 2]
|
||||
#=> #HashSet<[1, 2]>
|
||||
|
||||
"""
|
||||
def new(members) do
|
||||
Enum.reduce members, ordered(), fn member, set ->
|
||||
put(set, member)
|
||||
@spec new(Enum.t) :: Set.t
|
||||
def new(enum) do
|
||||
Enum.reduce enum, trie(), fn i, set ->
|
||||
put(set, i)
|
||||
end
|
||||
end
|
||||
|
||||
def union(set1, set2) when is_record(set1, HashSet) and is_record(set2, HashSet) and elem(set1, 1) <= elem(set2, 1) do
|
||||
set_fold set1, set2, fn v1, acc ->
|
||||
put(acc, v1)
|
||||
@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(set1, set2) when is_record(set1, HashSet) and is_record(set2, HashSet) do
|
||||
set_fold set2, set1, fn v, acc ->
|
||||
put(acc, v)
|
||||
def union(trie(size: size1) = set1, trie(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
|
||||
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 ->
|
||||
if member?(set2, v), do: put(acc, v), else: acc
|
||||
end
|
||||
end
|
||||
|
||||
def intersection(set1, set2) when is_record(set1, HashSet) and is_record(set2, HashSet) and elem(set1, 1) <= elem(set2, 1) do
|
||||
set_filter set1, fn e -> set_member?(set2, e) end
|
||||
end
|
||||
|
||||
def intersection(set1, set2) when is_record(set1, HashSet) and is_record(set2, HashSet) do
|
||||
set_filter set2, fn e -> set_member?(set1, e) end
|
||||
end
|
||||
|
||||
def difference(set1, set2) when is_record(set1, HashSet) and is_record(set2, HashSet) do
|
||||
set_filter set1, fn m -> not set_member?(set2, m) end
|
||||
end
|
||||
|
||||
def member?(set, member) when is_record(set, HashSet) do
|
||||
set_member?(set, member)
|
||||
end
|
||||
|
||||
def empty(_) do
|
||||
ordered()
|
||||
end
|
||||
|
||||
def size(set) do
|
||||
elem(set, 1)
|
||||
end
|
||||
|
||||
def to_list(ordered(bucket: bucket)) do
|
||||
bucket
|
||||
def difference(trie() = set1, trie() = set2) do
|
||||
set_fold set2, set1, fn v, acc -> delete(acc, v) end
|
||||
end
|
||||
|
||||
def to_list(set) do
|
||||
set_fold(set, [], &[&1|&2]) |> :lists.reverse
|
||||
end
|
||||
|
||||
def put(set, member) do
|
||||
{ set, _ } = set_put(set, member)
|
||||
set
|
||||
end
|
||||
|
||||
def delete(set, member) do
|
||||
{ set, _ } = set_delete(set, member)
|
||||
set
|
||||
end
|
||||
|
||||
def equal?(set1, set2) do
|
||||
size = elem(set1, 1)
|
||||
case elem(set2, 1) do
|
||||
^size ->
|
||||
set_equal?(set1, set2)
|
||||
_ ->
|
||||
false
|
||||
def equal?(trie(size: size1) = set1, trie(size: size2) = set2) do
|
||||
case size1 do
|
||||
^size2 -> subset?(set1, set2)
|
||||
_ -> false
|
||||
end
|
||||
end
|
||||
|
||||
def subset?(set1, set2) do
|
||||
set_equal?(set1, set2)
|
||||
def subset?(trie() = set1, trie() = set2) do
|
||||
reduce(set1, { :cont, true }, fn member, acc ->
|
||||
case member?(set2, member) do
|
||||
true -> { :cont, acc }
|
||||
_ -> { :halt, false }
|
||||
end
|
||||
end) |> elem(1)
|
||||
end
|
||||
|
||||
def disjoint?(set1, set2) do
|
||||
set_disjoint?(set1, set2)
|
||||
def disjoint?(trie() = set1, trie() = set2) do
|
||||
reduce(set2, { :cont, true }, fn member, acc ->
|
||||
case member?(set1, member) do
|
||||
false -> { :cont, acc }
|
||||
_ -> { :halt, false }
|
||||
end
|
||||
end) |> elem(1)
|
||||
end
|
||||
|
||||
@doc false
|
||||
def reduce(ordered(bucket: bucket), acc, fun) do
|
||||
:lists.foldl(fun, acc, bucket)
|
||||
def empty(trie()) do
|
||||
trie()
|
||||
end
|
||||
|
||||
def reduce(trie() = set, acc, fun) do
|
||||
set_fold(set, acc, fun)
|
||||
def member?(trie(root: root), term) do
|
||||
do_member?(root, term, key_hash(term))
|
||||
end
|
||||
|
||||
## HashSet-wide functions
|
||||
|
||||
defp set_filter(ordered(bucket: bucket, size: size), fun) do
|
||||
{ new, removed_count } = bucket_filter(bucket, fun, [], 0)
|
||||
ordered(bucket: new, size: size - removed_count)
|
||||
def put(trie(root: root, size: size), term) do
|
||||
{root, counter} = do_put(root, term, key_hash(term))
|
||||
trie(root: root, size: size + counter)
|
||||
end
|
||||
|
||||
defp set_filter(trie(root: root, depth: depth, size: size) = set, fun) do
|
||||
{ new, removed_count } = node_filter(root, depth, fun, @node_size)
|
||||
|
||||
if depth > 0 and trie(set, :contract_on) >= (size - removed_count) do
|
||||
contract_trie(trie(root: new,
|
||||
size: size - removed_count,
|
||||
depth: depth,
|
||||
contract_on: trie(set, :contract_on),
|
||||
expand_on: trie(set, :expand_on)))
|
||||
else
|
||||
trie(size: size - removed_count, root: new, depth: depth)
|
||||
def delete(trie(root: root, size: size) = set, term) do
|
||||
case do_delete(root, term, key_hash(term)) do
|
||||
{:ok, root} -> trie(root: root, size: size - 1)
|
||||
:error -> set
|
||||
end
|
||||
end
|
||||
|
||||
defp set_put(ordered(size: @ordered_threshold, bucket: bucket), member) do
|
||||
root = node_relocate(bucket, 0)
|
||||
set_put(trie(size: @ordered_threshold, root: root), member)
|
||||
def reduce(trie(root: root), acc, fun) do
|
||||
do_reduce(root, acc, fun, @node_size, fn
|
||||
{:suspend, acc} -> {:suspended, acc, &{ :done, elem(&1, 1) }}
|
||||
{:halt, acc} -> {:halted, acc}
|
||||
{:cont, acc} -> {:done, acc}
|
||||
end)
|
||||
end
|
||||
|
||||
defp set_put(ordered(size: size, bucket: bucket) = set, member) do
|
||||
{ new, count } = bucket_put(bucket, member)
|
||||
{ ordered(set, size: size + count, bucket: new), count }
|
||||
def size(trie(size: size)) do
|
||||
size
|
||||
end
|
||||
|
||||
defp set_put(trie(root: root, depth: depth, size: size, expand_on: size, contract_on: contract_on) = set, member) do
|
||||
root = node_expand(root, depth, depth + 1)
|
||||
set = trie(set, root: root, depth: depth + 1,
|
||||
expand_on: size * @node_size, contract_on: contract_on * @node_size)
|
||||
set_put(set, member)
|
||||
## Set helpers
|
||||
|
||||
defp set_fold(trie(root: root), acc, fun) do
|
||||
do_fold(root, acc, fun, @node_size)
|
||||
end
|
||||
|
||||
defp set_put(trie(root: root, size: size, depth: depth) = set, member) do
|
||||
pos = bucket_hash(member)
|
||||
{ root, count } = node_put(root, depth, pos, member)
|
||||
{ trie(set, size: size + count, root: root), count }
|
||||
end
|
||||
## Set manipulation
|
||||
|
||||
defp set_member?(ordered(bucket: bucket), member) do
|
||||
:lists.member(member, bucket)
|
||||
end
|
||||
|
||||
defp set_member?(trie(root: root, depth: depth), member) do
|
||||
:lists.member(member, node_bucket(root, depth, bucket_hash(member)))
|
||||
end
|
||||
|
||||
defp set_delete(ordered(bucket: bucket, size: size) = set, member) do
|
||||
case bucket_delete(bucket, member) do
|
||||
{ _, 0 } ->
|
||||
{ set, 0 }
|
||||
{ new_bucket, -1 } ->
|
||||
{ ordered(set, size: size - 1, bucket: new_bucket), -1 }
|
||||
defp do_member?(node, term, hash) do
|
||||
index = key_mask(hash)
|
||||
case elem(node, index) do
|
||||
[] -> false
|
||||
[^term|_] -> true
|
||||
[_] -> false
|
||||
[_|n] -> do_member?(n, term, key_shift(hash))
|
||||
end
|
||||
end
|
||||
|
||||
defp set_delete(trie(root: root, size: size, depth: depth) = set, member) do
|
||||
pos = bucket_hash(member)
|
||||
case node_delete(root, depth, pos, member) do
|
||||
{ _, 0 } ->
|
||||
{ set, 0 }
|
||||
{ root, -1 } ->
|
||||
{ if depth > 0 and trie(set, :contract_on) == size do
|
||||
root = node_contract(root, depth)
|
||||
trie(set,
|
||||
root: root,
|
||||
size: size - 1,
|
||||
depth: depth - 1,
|
||||
contract_on: div(size, @node_size),
|
||||
expand_on: div(trie(set, :expand_on), @node_size))
|
||||
else
|
||||
trie(set, size: size - 1, root: root)
|
||||
end, -1 }
|
||||
defp do_put(node, term, hash) do
|
||||
index = key_mask(hash)
|
||||
case elem(node, index) do
|
||||
[] ->
|
||||
{set_elem(node, index, [term]), 1}
|
||||
[^term|_] ->
|
||||
{node, 0}
|
||||
[t] ->
|
||||
n = set_elem(@node_template, key_mask(key_shift(hash)), [term])
|
||||
{set_elem(node, index, [t|n]), 1}
|
||||
[t|n] ->
|
||||
{n, counter} = do_put(n, term, key_shift(hash))
|
||||
{set_elem(node, index, [t|n]), counter}
|
||||
end
|
||||
end
|
||||
|
||||
defp set_equal?(set1, set2) do
|
||||
try do
|
||||
reduce(set1, true, fn member, acc ->
|
||||
case member?(set2, member) do
|
||||
true -> acc
|
||||
_ -> throw(:error)
|
||||
defp do_delete(node, term, hash) do
|
||||
index = key_mask(hash)
|
||||
case elem(node, index) do
|
||||
[] ->
|
||||
:error
|
||||
[^term] ->
|
||||
{:ok, set_elem(node, index, [])}
|
||||
[_] ->
|
||||
:error
|
||||
[^term|n] ->
|
||||
{:ok, set_elem(node, index, do_compact_node(n))}
|
||||
[t|n] ->
|
||||
case do_delete(n, term, key_shift(hash)) do
|
||||
{:ok, @node_template} ->
|
||||
{:ok, set_elem(node, index, [t])}
|
||||
{:ok, n} ->
|
||||
{:ok, set_elem(node, index, [t|n])}
|
||||
:error ->
|
||||
:error
|
||||
end
|
||||
end)
|
||||
catch
|
||||
:error -> false
|
||||
end
|
||||
end
|
||||
|
||||
defp set_disjoint?(set1, set2) do
|
||||
try do
|
||||
reduce(set1, true, fn member, acc ->
|
||||
case member?(set2, member) do
|
||||
false -> acc
|
||||
_ -> throw(:error)
|
||||
end
|
||||
end)
|
||||
catch
|
||||
:error -> false
|
||||
Enum.each 0..(@node_size - 1), fn index ->
|
||||
defp do_compact_node(node) when elem(node, unquote(index)) != [] do
|
||||
case elem(node, unquote(index)) do
|
||||
[t] ->
|
||||
case set_elem(node, unquote(index), []) do
|
||||
@node_template -> [t]
|
||||
n -> [t|n]
|
||||
end
|
||||
[t|n] ->
|
||||
[t|set_elem(node, unquote(index), do_compact_node(n))]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defp set_fold(ordered(bucket: bucket), acc, fun) do
|
||||
bucket_fold(bucket, acc, fun)
|
||||
## Set fold
|
||||
|
||||
defp do_fold_each([], acc, _fun), do: acc
|
||||
defp do_fold_each([t], acc, fun), do: fun.(t, acc)
|
||||
defp do_fold_each([t|n], acc, fun), do: do_fold(n, fun.(t, acc), fun, @node_size)
|
||||
|
||||
defp do_fold(node, acc, fun, count) when count > 0 do
|
||||
acc = do_fold_each(:erlang.element(count, node), acc, fun)
|
||||
do_fold(node, acc, fun, count - 1)
|
||||
end
|
||||
|
||||
defp set_fold(trie(root: root, depth: depth), acc, fun) do
|
||||
node_fold(root, depth, acc, fun, @node_size)
|
||||
end
|
||||
|
||||
## Bucket helpers
|
||||
|
||||
defp bucket_filter([e|bucket], fun, acc, count) do
|
||||
case fun.(e) do
|
||||
true -> bucket_filter(bucket, fun, [e|acc], count)
|
||||
false -> bucket_filter(bucket, fun, acc, count + 1)
|
||||
end
|
||||
end
|
||||
|
||||
defp bucket_filter([], _fun, acc, count) do
|
||||
{ :lists.reverse(acc), count }
|
||||
end
|
||||
|
||||
defp bucket_put([m|_]=bucket, member) when m > member do
|
||||
{ [member|bucket], 1 }
|
||||
end
|
||||
|
||||
defp bucket_put([member|bucket], member) do
|
||||
{ [member|bucket], 0 }
|
||||
end
|
||||
|
||||
defp bucket_put([e|bucket], member) do
|
||||
{ rest, count } = bucket_put(bucket, member)
|
||||
{ [e|rest], count }
|
||||
end
|
||||
|
||||
defp bucket_put([], member) do
|
||||
{ [member], 1 }
|
||||
end
|
||||
|
||||
defp bucket_put!([m|_]=bucket, member) when m > member, do: [member|bucket]
|
||||
defp bucket_put!([member|bucket], member), do: [member|bucket]
|
||||
defp bucket_put!([e|bucket], member), do: [e|bucket_put!(bucket, member)]
|
||||
defp bucket_put!([], member), do: [member]
|
||||
|
||||
# Deletes a key from the bucket
|
||||
defp bucket_delete([m,_|_]=bucket, member) when m > member do
|
||||
{ bucket, 0 }
|
||||
end
|
||||
|
||||
defp bucket_delete([member|bucket], member) do
|
||||
{ bucket, -1 }
|
||||
end
|
||||
|
||||
defp bucket_delete([e|bucket], member) do
|
||||
{ rest, count } = bucket_delete(bucket, member)
|
||||
{ [e|rest], count }
|
||||
end
|
||||
|
||||
defp bucket_delete([], _member) do
|
||||
{ [], 0 }
|
||||
end
|
||||
|
||||
defp bucket_fold(bucket, acc, fun) do
|
||||
:lists.foldl(fun, acc, bucket)
|
||||
end
|
||||
|
||||
defp bucket_hash(key) do
|
||||
:erlang.phash2(key)
|
||||
end
|
||||
|
||||
defp bucket_nth_index(hash, n) do
|
||||
(hash >>> (@node_shift * n)) &&& @node_bitmap
|
||||
end
|
||||
|
||||
defp bucket_index(hash) do
|
||||
hash &&& @node_bitmap
|
||||
end
|
||||
|
||||
defp bucket_next(hash) do
|
||||
hash >>> @node_shift
|
||||
end
|
||||
|
||||
# Trie resizing
|
||||
|
||||
defp contract_trie(trie(depth: 0) = set) do
|
||||
set
|
||||
end
|
||||
|
||||
defp contract_trie(trie(root: root, depth: depth, size: size, contract_on: contract_on, expand_on: expand_on) = set) when size <= contract_on do
|
||||
new_contract_on = div(contract_on, @node_size)
|
||||
new_expand_on = div(expand_on, @node_size)
|
||||
|
||||
if new_contract_on == 0, do: new_contract_on = @contract_default
|
||||
if new_expand_on == 0, do: new_expand_on = @expand_default
|
||||
|
||||
contract_trie(trie(set, root: node_contract(root, depth),
|
||||
size: size,
|
||||
depth: depth - 1,
|
||||
contract_on: new_contract_on,
|
||||
expand_on: new_expand_on))
|
||||
end
|
||||
|
||||
defp contract_trie(set) do
|
||||
set
|
||||
end
|
||||
|
||||
# Node helpers
|
||||
|
||||
# Gets a bucket from the node
|
||||
defp node_bucket(node, 0, hash) do
|
||||
elem(node, bucket_index(hash))
|
||||
end
|
||||
|
||||
defp node_bucket(node, depth, hash) do
|
||||
child = elem(node, bucket_index(hash))
|
||||
node_bucket(child, depth - 1, bucket_next(hash))
|
||||
end
|
||||
|
||||
defp node_put(node, 0, hash, member) do
|
||||
pos = bucket_index(hash)
|
||||
{ new, count } = bucket_put(elem(node, pos), member)
|
||||
{ set_elem(node, pos, new), count }
|
||||
end
|
||||
|
||||
defp node_put(node, depth, hash, member) do
|
||||
pos = bucket_index(hash)
|
||||
{ new, count } = node_put(elem(node, pos), depth - 1, bucket_next(hash), member)
|
||||
{ set_elem(node, pos, new), count }
|
||||
end
|
||||
|
||||
# Deletes a key from the bucket
|
||||
defp node_delete(node, 0, hash, member) do
|
||||
pos = bucket_index(hash)
|
||||
case bucket_delete(elem(node, pos), member) do
|
||||
{ _, 0 } -> { node, 0 }
|
||||
{ new, -1 } -> { set_elem(node, pos, new), -1 }
|
||||
end
|
||||
end
|
||||
|
||||
defp node_delete(node, depth, hash, member) do
|
||||
pos = bucket_index(hash)
|
||||
case node_delete(elem(node, pos), depth - 1, bucket_next(hash), member) do
|
||||
{ _, 0 } -> { node, 0 }
|
||||
{ new, -1 } -> { set_elem(node, pos, new), -1 }
|
||||
end
|
||||
end
|
||||
|
||||
defp node_fold(bucket, -1, acc, fun, _) do
|
||||
bucket_fold(bucket, acc, fun)
|
||||
end
|
||||
|
||||
defp node_fold(node, depth, acc, fun, count) when count >= 1 do
|
||||
acc = node_fold(:erlang.element(count, node), depth - 1, acc, fun, @node_size)
|
||||
node_fold(node, depth, acc, fun, count - 1)
|
||||
end
|
||||
|
||||
defp node_fold(_node, _, acc, _fun, 0) do
|
||||
defp do_fold(_node, acc, _fun, 0) do
|
||||
acc
|
||||
end
|
||||
|
||||
defp node_filter(bucket, -1, fun, _) do
|
||||
bucket_filter(bucket, fun, [], 0)
|
||||
## Set reduce
|
||||
|
||||
defp do_reduce_each(_node, {:halt, acc}, _fun, _next) do
|
||||
{:halted, acc}
|
||||
end
|
||||
|
||||
defp node_filter(node, depth, fun, count) when count >= 1 do
|
||||
case node_filter(:erlang.element(count, node), depth - 1, fun, @node_size) do
|
||||
{ _, 0 } ->
|
||||
node_filter(node, depth, fun, count - 1)
|
||||
{ new_element, count1 } ->
|
||||
{ new_node, count2 } = node_filter(:erlang.setelement(count, node, new_element), depth, fun, count - 1)
|
||||
{ new_node, count1 + count2 }
|
||||
end
|
||||
defp do_reduce_each(node, {:suspend, acc}, fun, next) do
|
||||
{:suspended, acc, &do_reduce_each(node, &1, fun, next)}
|
||||
end
|
||||
|
||||
defp node_filter(node, _, _fun, 0) do
|
||||
{ node, 0 }
|
||||
defp do_reduce_each([], acc, _fun, next) do
|
||||
next.(acc)
|
||||
end
|
||||
|
||||
defp node_relocate(node // @node_template, bucket, n) do
|
||||
:lists.foldl fn member, acc ->
|
||||
pos = member |> bucket_hash() |> bucket_nth_index(n)
|
||||
set_elem(acc, pos, bucket_put!(elem(acc, pos), member))
|
||||
end, node, bucket
|
||||
defp do_reduce_each([t], {:cont, acc}, fun, next) do
|
||||
next.(fun.(t, acc))
|
||||
end
|
||||
|
||||
# Node resizing
|
||||
defp node_expand({ b1, b2, b3, b4, b5, b6, b7, b8, b9, b10, b11, b12, b13, b14, b15, b16 }, 0, n) do
|
||||
{ node_relocate(b1, n), node_relocate(b2, n), node_relocate(b3, n),
|
||||
node_relocate(b4, n), node_relocate(b5, n), node_relocate(b6, n),
|
||||
node_relocate(b7, n), node_relocate(b8, n), node_relocate(b9, n),
|
||||
node_relocate(b10, n), node_relocate(b11, n), node_relocate(b12, n),
|
||||
node_relocate(b13, n), node_relocate(b14, n), node_relocate(b15, n),
|
||||
node_relocate(b16, n) }
|
||||
defp do_reduce_each([t|n], {:cont, acc}, fun, next) do
|
||||
do_reduce(n, fun.(t, acc), fun, @node_size, next)
|
||||
end
|
||||
|
||||
defp node_expand({ b1, b2, b3, b4, b5, b6, b7, b8, b9, b10, b11, b12, b13, b14, b15, b16 }, depth, n) do
|
||||
depth = depth - 1
|
||||
{ node_expand(b1, depth, n), node_expand(b2, depth, n), node_expand(b3, depth, n),
|
||||
node_expand(b4, depth, n), node_expand(b5, depth, n), node_expand(b6, depth, n),
|
||||
node_expand(b7, depth, n), node_expand(b8, depth, n), node_expand(b9, depth, n),
|
||||
node_expand(b10, depth, n), node_expand(b11, depth, n), node_expand(b12, depth, n),
|
||||
node_expand(b13, depth, n), node_expand(b14, depth, n), node_expand(b15, depth, n),
|
||||
node_expand(b16, depth, n) }
|
||||
defp do_reduce(node, acc, fun, count, next) when count > 0 do
|
||||
do_reduce_each(:erlang.element(count, node), acc, fun, &do_reduce(node, &1, fun, count - 1, next))
|
||||
end
|
||||
|
||||
defp node_contract({ b1, b2, b3, b4, b5, b6, b7, b8, b9, b10, b11, b12, b13, b14, b15, b16 }, depth) when depth > 0 do
|
||||
depth = depth - 1
|
||||
{ node_contract(b1, depth), node_contract(b2, depth), node_contract(b3, depth),
|
||||
node_contract(b4, depth), node_contract(b5, depth), node_contract(b6, depth),
|
||||
node_contract(b7, depth), node_contract(b8, depth), node_contract(b9, depth),
|
||||
node_contract(b10, depth), node_contract(b11, depth), node_contract(b12, depth),
|
||||
node_contract(b13, depth), node_contract(b14, depth), node_contract(b15, depth),
|
||||
node_contract(b16, depth) }
|
||||
defp do_reduce(_node, acc, _fun, 0, next) do
|
||||
next.(acc)
|
||||
end
|
||||
|
||||
defp node_contract({ b1, b2, b3, b4, b5, b6, b7, b8, b9, b10, b11, b12, b13, b14, b15, b16 }, 0) do
|
||||
b1 |> each_contract(b2) |> each_contract(b3) |> each_contract(b4)
|
||||
|> each_contract(b5) |> each_contract(b6) |> each_contract(b7)
|
||||
|> each_contract(b8) |> each_contract(b9) |> each_contract(b10)
|
||||
|> each_contract(b11) |> each_contract(b12) |> each_contract(b13)
|
||||
|> each_contract(b14) |> each_contract(b15) |> each_contract(b16)
|
||||
## Key operations
|
||||
|
||||
import Bitwise
|
||||
|
||||
defp key_hash(key) do
|
||||
:erlang.phash2(key)
|
||||
end
|
||||
|
||||
defp each_contract([m1|acc], [m2|_]=bucket) when m1 < m2, do: [m1|each_contract(acc, bucket)]
|
||||
defp each_contract(acc, [m|bucket]), do: [m|each_contract(acc, bucket)]
|
||||
defp each_contract([], bucket), do: bucket
|
||||
defp each_contract(acc, []), do: acc
|
||||
defp key_mask(hash) do
|
||||
hash &&& @node_bitmap
|
||||
end
|
||||
|
||||
defp key_shift(hash) do
|
||||
hash >>> @node_shift
|
||||
end
|
||||
end
|
||||
|
||||
defimpl Enumerable, for: HashSet do
|
||||
def reduce(set, acc, fun), do: HashSet.reduce(set, acc, fun)
|
||||
def member?(set, v), do: HashSet.member?(set, v)
|
||||
def count(set), do: HashSet.size(set)
|
||||
def member?(set, v), do: { :ok, HashSet.member?(set, v) }
|
||||
def count(set), do: { :ok, HashSet.size(set) }
|
||||
end
|
||||
|
||||
+95
-52
@@ -1,7 +1,13 @@
|
||||
import Kernel, except: [inspect: 1]
|
||||
import Inspect.Algebra
|
||||
|
||||
defrecord Inspect.Opts, raw: false, limit: 50, pretty: false, width: 80
|
||||
defrecord Inspect.Opts,
|
||||
records: true,
|
||||
binaries: :infer,
|
||||
char_lists: :infer,
|
||||
limit: 50,
|
||||
pretty: false,
|
||||
width: 80
|
||||
|
||||
defprotocol Inspect do
|
||||
@moduledoc """
|
||||
@@ -13,11 +19,9 @@ defprotocol Inspect do
|
||||
followed by the inspecting options, represented by the record
|
||||
`Inspect.Opts`.
|
||||
|
||||
Inspection is done using the functions available in
|
||||
`Inspect.Algebra` and by calling `Kernel.inspect/2` recursively
|
||||
passing the `Inspect.Opts` as an argument. When `Kernel.inspect/2`
|
||||
receives an `Inspect.Opts` record as the second argument, it returns
|
||||
the underlying algebra document instead of the formatted string.
|
||||
Inspection is done using the functions available in `Inspect.Algebra`.
|
||||
|
||||
## Examples
|
||||
|
||||
Many times, inspecting a structure can be implemented in function
|
||||
of existing entities. For example, here is `HashSet`'s `inspect`
|
||||
@@ -27,7 +31,7 @@ defprotocol Inspect do
|
||||
import Inspect.Algebra
|
||||
|
||||
def inspect(dict, opts) do
|
||||
concat ["#HashSet<", Kernel.inspect(HashSet.to_list(dict), opts), ">"]
|
||||
concat ["#HashSet<", to_doc(HashSet.to_list(dict), opts), ">"]
|
||||
end
|
||||
end
|
||||
|
||||
@@ -35,14 +39,24 @@ defprotocol Inspect do
|
||||
concatenates algebra documents together. In the example above,
|
||||
it is concatenating the string `"HashSet<"` (all strings are
|
||||
valid algebra documents that keep their formatting when pretty
|
||||
printed), the document returned by `Kernel.inspect/2` and the
|
||||
printed), the document returned by `Inspect.Algebra.to_doc/2` and the
|
||||
other string `">"`.
|
||||
|
||||
Since regular strings are valid entities in an algebra document,
|
||||
an implementation of inspect may simply return a string,
|
||||
although that will devoid it of any pretty-printing.
|
||||
"""
|
||||
|
||||
## Error handling
|
||||
|
||||
In case there is an error while your structure is being inspected,
|
||||
Elixir will automatically fall back to tuple inspection for records.
|
||||
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:
|
||||
|
||||
Inspect.HashSet.inspect(HashSet.new, Inspect.Opts.new)
|
||||
|
||||
"""
|
||||
def inspect(thing, opts)
|
||||
end
|
||||
|
||||
@@ -86,7 +100,7 @@ defimpl Inspect, for: Atom do
|
||||
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
|
||||
@@ -113,16 +127,21 @@ defimpl Inspect, for: Atom do
|
||||
# Detect if atom
|
||||
|
||||
defp valid_atom_identifier?(<<h, t :: binary>>) when h in ?a..?z or h in ?A..?Z or h == ?_ do
|
||||
case valid_identifier?(t) do
|
||||
<<>> -> true
|
||||
<<??>> -> true
|
||||
<<?!>> -> true
|
||||
_ -> false
|
||||
end
|
||||
valid_atom_piece?(t)
|
||||
end
|
||||
|
||||
defp valid_atom_identifier?(_), do: false
|
||||
|
||||
defp valid_atom_piece?(t) do
|
||||
case valid_identifier?(t) do
|
||||
<<>> -> true
|
||||
<<??>> -> true
|
||||
<<?!>> -> true
|
||||
<<?@, t::binary>> -> valid_atom_piece?(t)
|
||||
_ -> false
|
||||
end
|
||||
end
|
||||
|
||||
defp valid_identifier?(<<h, t :: binary>>)
|
||||
when h in ?a..?z
|
||||
when h in ?A..?Z
|
||||
@@ -135,7 +154,7 @@ defimpl Inspect, for: Atom do
|
||||
end
|
||||
|
||||
defimpl Inspect, for: BitString do
|
||||
@doc %S"""
|
||||
@doc ~S"""
|
||||
Represents a string as itself escaping all necessary
|
||||
characters. Binaries that contain non-printable characters
|
||||
are printed using the bitstring syntax.
|
||||
@@ -150,8 +169,8 @@ defimpl Inspect, for: BitString do
|
||||
"<<0, 1, 2>>"
|
||||
|
||||
"""
|
||||
def inspect(thing, opts) when is_binary(thing) do
|
||||
if String.printable?(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
|
||||
inspect_bitstring(thing, opts)
|
||||
@@ -205,11 +224,29 @@ defimpl Inspect, for: BitString do
|
||||
defp escape(<<?\v, t :: binary>>, char, binary) do
|
||||
escape(t, char, << binary :: binary, ?\\, ?v >>)
|
||||
end
|
||||
defp escape(<<h :: utf8, t :: binary>>, char, binary) do
|
||||
head = << h :: utf8 >>
|
||||
if String.printable?(head) do
|
||||
escape(t, char, append(head, binary))
|
||||
else
|
||||
<< byte :: size(8), h :: binary >> = head
|
||||
t = << h :: binary, t :: binary >>
|
||||
escape(t, char, << binary :: binary, octify(byte) :: binary >>)
|
||||
end
|
||||
end
|
||||
defp escape(<<h, t :: binary>>, char, binary) do
|
||||
escape(t, char, << binary :: binary, h >>)
|
||||
escape(t, char, << binary :: binary, octify(h) :: binary >>)
|
||||
end
|
||||
defp escape(<<>>, _char, binary), do: binary
|
||||
|
||||
defp octify(byte) do
|
||||
<< hi :: size(2), mi :: size(3), lo :: size(3) >> = << byte >>
|
||||
<< ?\\, ?0 + hi, ?0 + mi, ?0 + lo >>
|
||||
end
|
||||
|
||||
defp append(<<h, t :: binary>>, binary), do: append(t, << binary :: binary, h >>)
|
||||
defp append(<<>>, binary), do: binary
|
||||
|
||||
## Bitstrings
|
||||
|
||||
defp inspect_bitstring(bitstring, Inspect.Opts[] = opts) do
|
||||
@@ -243,7 +280,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
|
||||
@@ -262,21 +299,21 @@ defimpl Inspect, for: List do
|
||||
|
||||
def inspect([], _opts), do: "[]"
|
||||
|
||||
def inspect(thing, Inspect.Opts[] = opts) do
|
||||
def inspect(thing, Inspect.Opts[char_lists: lists] = opts) do
|
||||
cond do
|
||||
:io_lib.printable_list(thing) ->
|
||||
lists == :as_char_lists or (lists == :infer and :io_lib.printable_list(thing)) ->
|
||||
<< ?', Inspect.BitString.escape(String.from_char_list!(thing), ?') :: binary, ?' >>
|
||||
keyword?(thing) && not opts.raw ->
|
||||
keyword?(thing) ->
|
||||
surround_many("[", thing, "]", opts.limit, &keyword(&1, opts))
|
||||
true ->
|
||||
surround_many("[", thing, "]", opts.limit, &Kernel.inspect(&1, opts))
|
||||
surround_many("[", thing, "]", opts.limit, &to_doc(&1, opts))
|
||||
end
|
||||
end
|
||||
|
||||
defp keyword({key, value}, opts) do
|
||||
concat(
|
||||
key_to_binary(key) <> ": ",
|
||||
Kernel.inspect(value, opts)
|
||||
to_doc(value, opts)
|
||||
)
|
||||
end
|
||||
|
||||
@@ -314,24 +351,24 @@ defimpl Inspect, for: Tuple do
|
||||
|
||||
def inspect({}, _opts), do: "{}"
|
||||
|
||||
def inspect(tuple, opts) do
|
||||
unless opts.raw do
|
||||
def inspect(tuple, Inspect.Opts[] = opts) do
|
||||
if opts.records do
|
||||
record_inspect(tuple, opts)
|
||||
end || surround_many("{", tuple_to_list(tuple), "}", opts.limit, &Kernel.inspect(&1, opts))
|
||||
else
|
||||
surround_many("{", tuple_to_list(tuple), "}", opts.limit, &to_doc(&1, opts))
|
||||
end
|
||||
end
|
||||
|
||||
## Helpers
|
||||
|
||||
defp record_inspect(record, opts) do
|
||||
defp record_inspect(record, Inspect.Opts[] = opts) do
|
||||
[name|tail] = tuple_to_list(record)
|
||||
|
||||
if is_atom(name) && (fields = record_fields(name)) && (length(fields) == size(record) - 1) do
|
||||
if Enum.first(tail) == :__exception__ do
|
||||
surround_record(name, tl(fields), tl(tail), opts)
|
||||
else
|
||||
surround_record(name, fields, tail, opts)
|
||||
end
|
||||
end || surround_many("{", [name|tail], "}", opts.limit, &Kernel.inspect(&1, opts))
|
||||
surround_record(name, fields, tail, opts)
|
||||
else
|
||||
surround_many("{", [name|tail], "}", opts.limit, &to_doc(&1, opts))
|
||||
end
|
||||
end
|
||||
|
||||
defp record_fields(name) do
|
||||
@@ -346,20 +383,26 @@ defimpl Inspect, for: Tuple do
|
||||
end
|
||||
end
|
||||
|
||||
defp surround_record(name, fields, tail, opts) do
|
||||
fields = lc { field, _ } inlist fields, do: field
|
||||
|
||||
defp surround_record(name, fields, tail, Inspect.Opts[] = opts) do
|
||||
concat(
|
||||
Inspect.Atom.inspect(name, opts),
|
||||
surround_many("[", Enum.zip(fields, tail), "]", opts.limit, &keyword(&1, opts))
|
||||
surround_many("[", zip_fields(fields, tail), "]", opts.limit, &keyword(&1, opts))
|
||||
)
|
||||
end
|
||||
|
||||
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)]
|
||||
end
|
||||
end
|
||||
|
||||
defp zip_fields([], []) do
|
||||
[]
|
||||
end
|
||||
|
||||
defp keyword({ k, v }, opts) do
|
||||
concat(
|
||||
atom_to_binary(k) <> ": ",
|
||||
Kernel.inspect(v, opts)
|
||||
)
|
||||
concat(k <> ": ", to_doc(v, opts))
|
||||
end
|
||||
end
|
||||
|
||||
@@ -398,21 +441,21 @@ defimpl Inspect, for: Float do
|
||||
end
|
||||
|
||||
defimpl Inspect, for: Regex do
|
||||
@doc %S"""
|
||||
Represents the Regex using the `%r""` syntax.
|
||||
@doc ~S"""
|
||||
Represents the Regex using the `~r""` syntax.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> inspect(%r/foo/m)
|
||||
"%r\"foo\"m"
|
||||
iex> inspect(~r/foo/m)
|
||||
"~r\"foo\"m"
|
||||
|
||||
"""
|
||||
def inspect(regex, opts) when size(regex) == 5 do
|
||||
concat ["%r", Kernel.inspect(Regex.source(regex), opts), Regex.opts(regex)]
|
||||
concat ["~r", to_doc(Regex.source(regex), opts), Regex.opts(regex)]
|
||||
end
|
||||
|
||||
def inspect(other, opts) do
|
||||
Kernel.inspect(other, opts.raw(true))
|
||||
def inspect(other, Inspect.Opts[] = opts) do
|
||||
to_doc(other, opts.records(false))
|
||||
end
|
||||
end
|
||||
|
||||
@@ -438,7 +481,7 @@ defimpl Inspect, for: Function do
|
||||
end
|
||||
|
||||
defp default_inspect(mod, fun_info) do
|
||||
"#Function<#{uniq(fun_info)} 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(name) do
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
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].
|
||||
|
||||
@@ -71,12 +71,9 @@ defmodule Inspect.Algebra do
|
||||
@nesting 1
|
||||
@break " "
|
||||
|
||||
defp repeat(_, 0), do: ""
|
||||
defp repeat(s, i), do: :lists.duplicate(i, s)
|
||||
|
||||
# Functional interface to `doc` records
|
||||
|
||||
@type t :: :doc_nil | doc_cons_t | doc_nest_t | doc_break_t | doc_group_t | binary
|
||||
@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
|
||||
@@ -96,14 +93,38 @@ defmodule Inspect.Algebra do
|
||||
|
||||
defp do_is_doc(doc) do
|
||||
quote do
|
||||
unquote(doc) |> is_binary or
|
||||
unquote(doc) |> is_integer or
|
||||
unquote(doc) == :doc_nil or
|
||||
(unquote(doc) |> is_tuple and
|
||||
is_binary(unquote(doc)) or
|
||||
unquote(doc) in [:doc_nil, :doc_line] or
|
||||
(is_tuple(unquote(doc)) and
|
||||
elem(unquote(doc), 0) in [:doc_cons, :doc_nest, :doc_break, :doc_group])
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Converts an Elixir structure to an algebra document
|
||||
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)
|
||||
end
|
||||
else
|
||||
Inspect.inspect(arg, opts)
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns `:doc_nil` which is a document entity used to represent
|
||||
nothingness. Takes no arguments.
|
||||
@@ -161,7 +182,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.
|
||||
@@ -201,7 +222,7 @@ defmodule Inspect.Algebra do
|
||||
@spec glue(t, binary, t) :: doc_cons_t
|
||||
def glue(x, g, y) when is_binary(g), do: concat(x, concat(break(g), y))
|
||||
|
||||
@doc %S"""
|
||||
@doc ~S"""
|
||||
Returns a group containing the specified document.
|
||||
|
||||
## Examples
|
||||
@@ -246,7 +267,7 @@ defmodule Inspect.Algebra do
|
||||
@spec space(t, t) :: doc_cons_t
|
||||
def space(x, y), do: concat(x, concat(" ", y))
|
||||
|
||||
@doc %S"""
|
||||
@doc ~S"""
|
||||
Inserts a mandatory linebreak between two document entities.
|
||||
|
||||
## Examples
|
||||
@@ -257,7 +278,7 @@ defmodule Inspect.Algebra do
|
||||
|
||||
"""
|
||||
@spec line(t, t) :: doc_cons_t
|
||||
def line(x, y), do: concat(x, concat(@newline, y))
|
||||
def line(x, y), do: concat(x, concat(:doc_line, y))
|
||||
|
||||
@doc """
|
||||
Folds a list of document entities into a document entity
|
||||
@@ -280,7 +301,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.
|
||||
@@ -296,10 +317,10 @@ defmodule Inspect.Algebra do
|
||||
"""
|
||||
@spec surround(binary, t, binary) :: t
|
||||
def surround(left, doc, right) do
|
||||
group concat [left, nest(doc, @nesting), right]
|
||||
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.
|
||||
@@ -314,32 +335,38 @@ defmodule Inspect.Algebra do
|
||||
iex> Inspect.Algebra.pretty(doc, 20)
|
||||
"[1, 2, 3, ...]"
|
||||
|
||||
iex> doc = Inspect.Algebra.surround_many("[", Enum.to_list(1..5), "]", 3, &integer_to_binary(&1), "!")
|
||||
iex> Inspect.Algebra.pretty(doc, 20)
|
||||
"[1! 2! 3! ...]"
|
||||
|
||||
"""
|
||||
@spec surround_many(binary, [any], binary, integer | :infinity, (term -> t)) :: t
|
||||
def surround_many(left, [], right, _, _fun) do
|
||||
@spec surround_many(binary, [any], binary, integer | :infinity, (term -> t), binary) :: t
|
||||
def surround_many(left, docs, right, limit, fun, separator \\ @surround_separator)
|
||||
|
||||
def surround_many(left, [], right, _, _fun, _) do
|
||||
concat(left, right)
|
||||
end
|
||||
|
||||
def surround_many(left, docs, right, limit, fun) do
|
||||
surround(left, surround_many(docs, limit, fun), right)
|
||||
def surround_many(left, docs, right, limit, fun, sep) do
|
||||
surround(left, surround_many(docs, limit, fun, sep), right)
|
||||
end
|
||||
|
||||
defp surround_many(_, 0, _fun) do
|
||||
defp surround_many(_, 0, _fun, _sep) do
|
||||
"..."
|
||||
end
|
||||
|
||||
defp surround_many([h], _limit, fun) do
|
||||
defp surround_many([h], _limit, fun, _sep) do
|
||||
fun.(h)
|
||||
end
|
||||
|
||||
defp surround_many([h|t], limit, fun) when is_list(t) do
|
||||
defp surround_many([h|t], limit, fun, sep) when is_list(t) do
|
||||
glue(
|
||||
concat(fun.(h), @surround_separator),
|
||||
surround_many(t, decrement(limit), fun)
|
||||
concat(fun.(h), sep),
|
||||
surround_many(t, decrement(limit), fun, sep)
|
||||
)
|
||||
end
|
||||
|
||||
defp surround_many([h|t], _limit, fun) do
|
||||
defp surround_many([h|t], _limit, fun, _sep) do
|
||||
glue(
|
||||
concat(fun.(h), @tail_separator),
|
||||
fun.(t)
|
||||
@@ -367,58 +394,48 @@ defmodule Inspect.Algebra do
|
||||
|
||||
# Rendering and internal helpers
|
||||
|
||||
# Records representing __simple__ documents, already on a fixed layout
|
||||
# Those are generalized by `sdoc` type.
|
||||
@type sdoc :: :s_nil | s_text_t | s_line_t
|
||||
defrecordp :s_text, str: "" :: binary, sdoc: :s_nil :: sdoc
|
||||
defrecordp :s_line, indent: 1 :: non_neg_integer, sdoc: :s_nil :: sdoc
|
||||
|
||||
# Record representing the document mode to be rendered: flat or broken
|
||||
@typep mode :: :flat | :break
|
||||
|
||||
@doc false
|
||||
@spec fits?(integer, [{ integer, mode, t }]) :: boolean
|
||||
def fits?(:infinity, _), do: true # no pretty printing
|
||||
def fits?(w, _) when w < 0, do: false
|
||||
def fits?(_, []), do: true
|
||||
def fits?(_, [{_, _, :doc_line} | _]), do: true
|
||||
def fits?(w, [{_, _, :doc_nil} | t]), do: fits?(w, t)
|
||||
def fits?(w, [{i, m, doc_cons(left: x, right: y)} | t]), do: fits?(w, [{i, m, x} | [{i, m, y} | t]])
|
||||
def fits?(w, [{i, m, doc_nest(indent: j, doc: x)} | t]), do: fits?(w, [{i + j, m, x} | t])
|
||||
def fits?(w, [{i, _, doc_group(doc: x)} | t]), do: fits?(w, [{i, :flat, x} | t])
|
||||
def fits?(w, [{_, _, s} | t]) when is_binary(s), do: fits?((w - byte_size s), t)
|
||||
def fits?(w, [{_, :flat, doc_break(str: s)} | t]), do: fits?((w - byte_size s), t)
|
||||
def fits?(_, [{_, :break, doc_break(str: _)} | _]), do: true
|
||||
def fits?(w, [{i, _, doc_group(doc: x)} | t]), do: fits?(w, [{i, :flat, x} | t])
|
||||
|
||||
@doc false
|
||||
@spec format(integer | :infinity, integer, [{ integer, mode, t }]) :: atom | tuple
|
||||
def format(_, _, []), do: :s_nil
|
||||
@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)
|
||||
def format(w, k, [{i, m, doc_cons(left: x, right: y)} | t]), do: format(w, k, [{i, m, x} | [{i, m, y} | t]])
|
||||
def format(w, k, [{i, m, doc_nest(indent: j, doc: x)} | t]), do: format(w, k, [{i + j, m, x} | t])
|
||||
def format(w, k, [{_, _, s} | t]) when is_binary(s), do: s_text(str: s, sdoc: format(w, (k + byte_size s), t))
|
||||
def format(w, k, [{i, m, doc_group(doc: x)} | t]), do: format(w, k, [{i, m, x} | t])
|
||||
def format(w, k, [{_, :flat, doc_break(str: s)} | t]), do: s_text(str: s, sdoc: format(w, (k + byte_size s), t))
|
||||
def format(w, k, [{_, _, s} | t]) when is_binary(s), do: [s | format(w, (k + byte_size s), t)]
|
||||
def format(w, k, [{_, :flat, doc_break(str: s)} | t]), do: [s | format(w, (k + byte_size s), t)]
|
||||
def format(w, k, [{i, :break, doc_break(str: s)} | t]) do
|
||||
k = k + byte_size(s)
|
||||
|
||||
if w == :infinity or fits?(w - k, t) do
|
||||
s_text(str: s, sdoc: format(w, k, t))
|
||||
[s | format(w, k, t)]
|
||||
else
|
||||
s_line(indent: i, sdoc: format(w, i, t))
|
||||
[indent(i) | format(w, i, t)]
|
||||
end
|
||||
end
|
||||
|
||||
@doc false
|
||||
@spec render(sdoc) :: binary
|
||||
def render(sdoc) do
|
||||
iolist_to_binary do_render sdoc
|
||||
end
|
||||
defp indent(0), do: @newline
|
||||
defp indent(i), do: @newline <> :binary.copy(" ", i)
|
||||
|
||||
@spec do_render(sdoc) :: [binary]
|
||||
defp do_render(:s_nil), do: [""]
|
||||
defp do_render(s_text(str: s, sdoc: d)), do: [s | do_render(d)]
|
||||
defp do_render(s_line(indent: i, sdoc: d)) do
|
||||
prefix = repeat " ", i
|
||||
[@newline | [prefix | do_render d]]
|
||||
@doc false
|
||||
@spec render([binary]) :: binary
|
||||
def render(sdoc) do
|
||||
iolist_to_binary sdoc
|
||||
end
|
||||
end
|
||||
|
||||
+21
-20
@@ -1,7 +1,8 @@
|
||||
defexception IO.StreamError, reason: nil do
|
||||
def message(exception) do
|
||||
formatted = iolist_to_binary(:file.format_error(reason exception))
|
||||
"error during streaming: #{formatted}"
|
||||
defexception IO.StreamError, [:reason, :message] do
|
||||
def exception(opts) do
|
||||
reason = opts[:reason]
|
||||
formatted = iolist_to_binary(:file.format_error(reason))
|
||||
IO.StreamError[message: "error during streaming: #{formatted}", reason: reason]
|
||||
end
|
||||
end
|
||||
|
||||
@@ -48,7 +49,7 @@ defmodule IO do
|
||||
NFS file system.
|
||||
"""
|
||||
@spec read(device, :line | non_neg_integer) :: chardata | nodata
|
||||
def read(device // group_leader, chars_or_line)
|
||||
def read(device \\ group_leader, chars_or_line)
|
||||
|
||||
def read(device, :line) do
|
||||
:io.get_line(map_dev(device), '')
|
||||
@@ -71,7 +72,7 @@ defmodule IO do
|
||||
NFS file system.
|
||||
"""
|
||||
@spec binread(device, :line | non_neg_integer) :: iodata | nodata
|
||||
def binread(device // group_leader, chars_or_line)
|
||||
def binread(device \\ group_leader, chars_or_line)
|
||||
|
||||
def binread(device, :line) do
|
||||
case :file.read_line(map_dev(device)) do
|
||||
@@ -105,7 +106,7 @@ defmodule IO do
|
||||
|
||||
"""
|
||||
@spec write(device, chardata) :: :ok
|
||||
def write(device // group_leader(), item) do
|
||||
def write(device \\ group_leader(), item) do
|
||||
:io.put_chars map_dev(device), to_chardata(item)
|
||||
end
|
||||
|
||||
@@ -116,7 +117,7 @@ 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
|
||||
def binwrite(device \\ group_leader(), item) when is_iolist(item) do
|
||||
:file.write map_dev(device), item
|
||||
end
|
||||
|
||||
@@ -126,7 +127,7 @@ defmodule IO do
|
||||
to be a chardata.
|
||||
"""
|
||||
@spec puts(device, chardata) :: :ok
|
||||
def puts(device // group_leader(), item) do
|
||||
def puts(device \\ group_leader(), item) do
|
||||
erl_dev = map_dev(device)
|
||||
:io.put_chars erl_dev, [to_chardata(item), ?\n]
|
||||
end
|
||||
@@ -145,7 +146,7 @@ defmodule IO do
|
||||
|
||||
"""
|
||||
@spec inspect(term, Keyword.t) :: term
|
||||
def inspect(item, opts // []) do
|
||||
def inspect(item, opts \\ []) do
|
||||
inspect group_leader(), item, opts
|
||||
end
|
||||
|
||||
@@ -184,7 +185,7 @@ defmodule IO do
|
||||
"""
|
||||
@spec getn(chardata, pos_integer) :: chardata | nodata
|
||||
@spec getn(device, chardata) :: chardata | nodata
|
||||
def getn(prompt, count // 1)
|
||||
def getn(prompt, count \\ 1)
|
||||
|
||||
def getn(prompt, count) when is_integer(count) do
|
||||
getn(group_leader, prompt, count)
|
||||
@@ -218,7 +219,7 @@ defmodule IO do
|
||||
NFS file system.
|
||||
"""
|
||||
@spec gets(device, chardata) :: chardata | nodata
|
||||
def gets(device // group_leader(), prompt) do
|
||||
def gets(device \\ group_leader(), prompt) do
|
||||
:io.get_line(map_dev(device), to_chardata(prompt))
|
||||
end
|
||||
|
||||
@@ -243,7 +244,7 @@ defmodule IO do
|
||||
"""
|
||||
@spec stream(device, :line | pos_integer) :: Enumerable.t
|
||||
def stream(device, line_or_codepoints) do
|
||||
fn(acc, f) -> stream(map_dev(device), line_or_codepoints, acc, f) end
|
||||
Stream.unfold(map_dev(device), &do_stream(&1, line_or_codepoints))
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -256,30 +257,30 @@ defmodule IO do
|
||||
"""
|
||||
@spec binstream(device, :line | pos_integer) :: Enumerable.t
|
||||
def binstream(device, line_or_bytes) do
|
||||
fn(acc, f) -> binstream(map_dev(device), line_or_bytes, acc, f) end
|
||||
Stream.unfold(map_dev(device), &do_binstream(&1, line_or_bytes))
|
||||
end
|
||||
|
||||
@doc false
|
||||
def stream(device, what, acc, fun) do
|
||||
def do_stream(device, what) do
|
||||
case read(device, what) do
|
||||
:eof ->
|
||||
acc
|
||||
nil
|
||||
{ :error, reason } ->
|
||||
raise IO.StreamError, reason: reason
|
||||
data ->
|
||||
stream(device, what, fun.(data, acc), fun)
|
||||
{ data, device }
|
||||
end
|
||||
end
|
||||
|
||||
@doc false
|
||||
def binstream(device, what, acc, fun) do
|
||||
def do_binstream(device, what) do
|
||||
case binread(device, what) do
|
||||
:eof ->
|
||||
acc
|
||||
nil
|
||||
{ :error, reason } ->
|
||||
raise IO.StreamError, reason: reason
|
||||
data ->
|
||||
binstream(device, what, fun.(data, acc), fun)
|
||||
{ data, device }
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
defmodule IO.ANSI.Sequence do
|
||||
@moduledoc false
|
||||
|
||||
defmacro defsequence(name, code // "", terminator // "m") do
|
||||
defmacro defsequence(name, code \\ "", terminator \\ "m") do
|
||||
quote bind_quoted: [name: name, code: code, terminator: terminator] do
|
||||
def unquote(name)() do
|
||||
"\e[#{unquote(code)}#{unquote(terminator)}"
|
||||
@@ -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.
|
||||
"""
|
||||
@@ -32,7 +32,7 @@ defmodule IO.ANSI do
|
||||
"""
|
||||
@spec terminal? :: boolean
|
||||
@spec terminal?(:io.device) :: boolean
|
||||
def terminal?(device // :erlang.group_leader) do
|
||||
def terminal?(device \\ :erlang.group_leader) do
|
||||
match?({:ok, _}, :io.columns(device))
|
||||
end
|
||||
|
||||
@@ -134,11 +134,11 @@ defmodule IO.ANSI do
|
||||
end
|
||||
|
||||
defp escape_sequence(other) do
|
||||
[spec|_] = String.split(other, %r/(,|\})/)
|
||||
[spec|_] = String.split(other, ~r/(,|\})/)
|
||||
raise ArgumentError, message: "invalid ANSI sequence specification: #{spec}"
|
||||
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
|
||||
@@ -159,7 +159,7 @@ defmodule IO.ANSI do
|
||||
|
||||
"""
|
||||
@spec escape(String.t, emit :: boolean) :: String.t
|
||||
def escape(string, emit // terminal?) do
|
||||
def escape(string, emit \\ terminal?) do
|
||||
{rendered, emitted} = do_escape(string, false, emit, false, [])
|
||||
if emitted and emit do
|
||||
rendered <> reset
|
||||
@@ -168,7 +168,7 @@ defmodule IO.ANSI do
|
||||
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
|
||||
@@ -188,7 +188,7 @@ defmodule IO.ANSI do
|
||||
|
||||
"""
|
||||
@spec escape_fragment(String.t, emit :: boolean) :: String.t
|
||||
def escape_fragment(string, emit // terminal?) do
|
||||
def escape_fragment(string, emit \\ terminal?) do
|
||||
{rendered, _emitted} = do_escape(string, false, emit, false, [])
|
||||
rendered
|
||||
end
|
||||
|
||||
@@ -0,0 +1,377 @@
|
||||
defmodule IO.ANSI.Docs do
|
||||
@moduledoc false
|
||||
|
||||
@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).
|
||||
|
||||
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:
|
||||
|
||||
* `:enabled` - toggles coloring on and off (true)
|
||||
* `:doc_code` - code blocks (cyan, bright)
|
||||
* `:doc_inline_code` - inline code (cyan)
|
||||
* `:doc_headings` - h1 and h2 headings (yellow, bright)
|
||||
* `:doc_title` - top level heading (reverse, yellow, bright)
|
||||
* `:doc_bold` - bold text (bright)
|
||||
* `:doc_underline` - underlined text (underline)
|
||||
|
||||
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`
|
||||
"""
|
||||
def print(doc, colors \\ @default_colors) do
|
||||
doc
|
||||
|> String.split(["\r\n","\n"], trim: false)
|
||||
|> Enum.map(&String.rstrip/1)
|
||||
|> process("", colors)
|
||||
end
|
||||
|
||||
defp process([], _indent, _colors), do: nil
|
||||
|
||||
defp process(["# " <> heading | rest], _indent, colors) do
|
||||
write_h1(String.strip(heading), colors)
|
||||
process(rest, "", colors)
|
||||
end
|
||||
|
||||
defp process(["## " <> heading | rest], _indent, colors) do
|
||||
write_h2(String.strip(heading), colors)
|
||||
process(rest, "", colors)
|
||||
end
|
||||
|
||||
defp process(["### " <> heading | rest], indent, colors) do
|
||||
write_h3(String.strip(heading), indent, colors)
|
||||
process(rest, indent, colors)
|
||||
end
|
||||
|
||||
defp process(["" | rest], indent, colors) do
|
||||
process(rest, indent, colors)
|
||||
end
|
||||
|
||||
defp process([" " <> line | rest], indent, colors) do
|
||||
process_code(rest, [line], indent, colors)
|
||||
end
|
||||
|
||||
defp process([line | rest], indent, colors) 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_text(rest, [line], indent, false, colors)
|
||||
end
|
||||
end
|
||||
|
||||
defp strip_spaces(" " <> line, acc) do
|
||||
strip_spaces(line, acc + 1)
|
||||
end
|
||||
|
||||
defp strip_spaces(rest, acc) do
|
||||
{ rest, acc }
|
||||
end
|
||||
|
||||
## Headings
|
||||
|
||||
defp write_h1(heading, colors) do
|
||||
write_h2(String.upcase(heading), colors)
|
||||
end
|
||||
|
||||
defp write_h2(heading, colors) do
|
||||
write(:doc_headings, heading, colors)
|
||||
end
|
||||
|
||||
defp write_h3(heading, indent, colors) do
|
||||
IO.write(indent)
|
||||
write(:doc_headings, heading, colors)
|
||||
end
|
||||
|
||||
## Lists
|
||||
|
||||
defp process_list(line, rest, count, indent, colors) do
|
||||
IO.write indent <> "• "
|
||||
{ contents, rest, done } = process_list_next(rest, count, false, [])
|
||||
process_text(contents, [line], indent <> " ", true, colors)
|
||||
if done, do: IO.puts(IO.ANSI.reset)
|
||||
process(rest, indent, colors)
|
||||
end
|
||||
|
||||
# Process the thing after a list item entry. It can be either:
|
||||
#
|
||||
# * Continuation of the list
|
||||
# * A nested list
|
||||
# * The end of the list
|
||||
#
|
||||
defp process_list_next([" " <> _ = line | rest], count, _done, acc) do
|
||||
case list_next(line, count) do
|
||||
: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 }
|
||||
end
|
||||
|
||||
defp process_list_next(["" | rest], count, _done, acc) do
|
||||
process_list_next(rest, count, true, [""|acc])
|
||||
end
|
||||
|
||||
defp process_list_next(rest, _count, done, acc) do
|
||||
{ Enum.reverse(acc), rest, done }
|
||||
end
|
||||
|
||||
defp list_next(<<bullet, ?\s, _ :: binary>>, 0) when bullet in @bullets, do: :done
|
||||
defp list_next(line, 0), do: chop(line, 2)
|
||||
defp list_next(" " <> line, acc), do: list_next(line, acc - 1)
|
||||
defp list_next(line, _acc), do: line
|
||||
|
||||
defp chop(" " <> line, acc) when acc > 0, do: chop(line, acc - 1)
|
||||
defp chop(line, _acc), do: line
|
||||
|
||||
## 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)
|
||||
end
|
||||
|
||||
defp process_text([], para, indent, from_list, colors) do
|
||||
write_text(Enum.reverse(para), indent, from_list, colors)
|
||||
end
|
||||
|
||||
defp process_text([line | rest], para, indent, true, colors) 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)
|
||||
_ ->
|
||||
process_text(rest, [line | para], indent, true, colors)
|
||||
end
|
||||
end
|
||||
|
||||
defp process_text([line | rest], para, indent, from_list, colors) do
|
||||
process_text(rest, [line | para], indent, from_list, colors)
|
||||
end
|
||||
|
||||
defp write_text(lines, indent, from_list, colors) do
|
||||
lines
|
||||
|> Enum.join(" ")
|
||||
|> handle_links
|
||||
|> handle_inline(nil, [], [], colors)
|
||||
|> String.split(~r{\s})
|
||||
|> write_with_wrap(column_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)
|
||||
end
|
||||
|
||||
# Blank line between code blocks
|
||||
defp process_code([ "", " " <> line | rest ], code, indent, colors) do
|
||||
process_code(rest, [line, "" | code], indent, colors)
|
||||
end
|
||||
|
||||
defp process_code([ " " <> line | rest ], code, indent, colors) do
|
||||
process_code(rest, [line|code], indent, colors)
|
||||
end
|
||||
|
||||
defp process_code(rest, code, indent, colors) do
|
||||
write_code(code, indent, colors)
|
||||
process(rest, indent, colors)
|
||||
end
|
||||
|
||||
defp write_code(code, indent, colors) do
|
||||
write(:doc_code, "#{indent}┃ #{Enum.join(Enum.reverse(code), "\n#{indent}┃ ")}", colors)
|
||||
end
|
||||
|
||||
## Helpers
|
||||
|
||||
defp write(style, string, colors) do
|
||||
IO.puts color(style, colors) <> string <> IO.ANSI.reset
|
||||
IO.puts IO.ANSI.reset
|
||||
end
|
||||
|
||||
defp write_with_wrap([], _available, _indent, _first) do
|
||||
:ok
|
||||
end
|
||||
|
||||
defp write_with_wrap(words, available, indent, first) do
|
||||
{ words, rest } = take_words(words, available, [])
|
||||
IO.puts (if first, do: "", else: indent) <> Enum.join(words, " ")
|
||||
write_with_wrap(rest, available, indent, false)
|
||||
end
|
||||
|
||||
defp take_words([word|words], available, acc) do
|
||||
available = available - length_without_escape(word, 0)
|
||||
|
||||
cond do
|
||||
# It fits, take one for space and continue decreasing
|
||||
available > 0 ->
|
||||
take_words(words, available - 1, [word|acc])
|
||||
|
||||
# No space but we got no words
|
||||
acc == [] ->
|
||||
{ [word], words }
|
||||
|
||||
# Otherwise
|
||||
true ->
|
||||
{ Enum.reverse(acc), [word|words] }
|
||||
end
|
||||
end
|
||||
|
||||
defp take_words([], _available, acc) do
|
||||
{ Enum.reverse(acc), [] }
|
||||
end
|
||||
|
||||
defp length_without_escape(<< ?\e, ?[, _, _, ?m, rest :: binary >>, count) do
|
||||
length_without_escape(rest, count)
|
||||
end
|
||||
|
||||
defp length_without_escape(<< ?\e, ?[, _, ?m, rest :: binary >>, count) do
|
||||
length_without_escape(rest, count)
|
||||
end
|
||||
|
||||
defp length_without_escape(rest, count) do
|
||||
case String.next_grapheme(rest) do
|
||||
{ _, rest } -> length_without_escape(rest, count + 1)
|
||||
nil -> count
|
||||
end
|
||||
end
|
||||
|
||||
defp handle_links(text) do
|
||||
text
|
||||
|> remove_square_brackets_in_link
|
||||
|> escape_underlines_in_link
|
||||
end
|
||||
|
||||
defp escape_underlines_in_link(text) do
|
||||
case Regex.match?(~r{.*(https?\S*)}, text) do
|
||||
true ->
|
||||
Regex.replace(~r{_}, text, "\\\\_")
|
||||
_ ->
|
||||
text
|
||||
end
|
||||
end
|
||||
|
||||
defp remove_square_brackets_in_link(text) do
|
||||
Regex.replace(~r{\[(.*?)\]\((.*?)\)}, text, "\\1 (\\2)")
|
||||
end
|
||||
|
||||
# Single inline quotes.
|
||||
@single [?`, ?_, ?*]
|
||||
|
||||
# ` does not require space in between
|
||||
@spaced [?_, ?*]
|
||||
|
||||
# Clauses for handling spaces
|
||||
defp handle_inline(<<?*, ?*, ?\s, rest :: binary>>, nil, buffer, acc, colors) do
|
||||
handle_inline(rest, nil, [?\s, ?*, ?*|buffer], acc, colors)
|
||||
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)
|
||||
end
|
||||
|
||||
defp handle_inline(<<?\s, ?*, ?*, rest :: binary>>, limit, buffer, acc, colors) do
|
||||
handle_inline(rest, limit, [?*, ?*, ?\s|buffer], acc, colors)
|
||||
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)
|
||||
end
|
||||
|
||||
# Clauses for handling escape
|
||||
defp handle_inline(<<?\\, ?\\, rest :: binary>>, limit, buffer, acc, colors) do
|
||||
handle_inline(rest, limit, [?\\|buffer], acc, colors)
|
||||
end
|
||||
|
||||
defp handle_inline(<<?\\, ?*, ?*, rest :: binary>>, limit, buffer, acc, colors) do
|
||||
handle_inline(rest, limit, [?*, ?*|buffer], acc, colors)
|
||||
end
|
||||
|
||||
# A escape is not valid inside `
|
||||
defp handle_inline(<<?\\, mark, rest :: binary>>, limit, buffer, acc, colors)
|
||||
when mark in [?_, ?*, ?`] and not(mark == limit and mark == ?`) do
|
||||
handle_inline(rest, limit, [mark|buffer], acc, colors)
|
||||
end
|
||||
|
||||
# Inline start
|
||||
defp handle_inline(<<?*, ?*, rest :: binary>>, nil, buffer, acc, colors) when rest != "" do
|
||||
handle_inline(rest, ?d, ["**"], [Enum.reverse(buffer)|acc], colors)
|
||||
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)
|
||||
end
|
||||
|
||||
# Inline end
|
||||
defp handle_inline(<<?*, ?*, rest :: binary>>, ?d, buffer, acc, colors) do
|
||||
handle_inline(rest, nil, [], [inline_buffer(buffer, colors)|acc], colors)
|
||||
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)
|
||||
end
|
||||
|
||||
defp handle_inline(<<char, rest :: binary>>, mark, buffer, acc, colors) do
|
||||
handle_inline(rest, mark, [char|buffer], acc, colors)
|
||||
end
|
||||
|
||||
defp handle_inline(<<>>, _mark, buffer, acc, _colors) do
|
||||
iolist_to_binary Enum.reverse([Enum.reverse(buffer)|acc])
|
||||
end
|
||||
|
||||
defp inline_buffer(buffer, colors) do
|
||||
[h|t] = Enum.reverse([IO.ANSI.reset|buffer])
|
||||
[color_for(h, colors)|t]
|
||||
end
|
||||
|
||||
defp color_for("`", colors), do: color(:doc_inline_code, colors)
|
||||
defp color_for("_", colors), do: color(:doc_underline, colors)
|
||||
defp color_for("*", colors), do: color(:doc_bold, colors)
|
||||
defp color_for("**", colors), do: color(:doc_bold, colors)
|
||||
|
||||
defp color(style, colors) do
|
||||
color = colors[style]
|
||||
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
|
||||
+2142
-1947
File diff suppressed because it is too large
Load Diff
@@ -34,7 +34,7 @@ defmodule Kernel.CLI do
|
||||
This function is used by Elixir's CLI and also
|
||||
by escripts generated by Elixir.
|
||||
"""
|
||||
def run(fun, halt // true) do
|
||||
def run(fun, halt \\ true) do
|
||||
try do
|
||||
fun.()
|
||||
if halt do
|
||||
@@ -221,7 +221,7 @@ defmodule Kernel.CLI do
|
||||
defp process_compiler(["--warnings-as-errors"|t], config) do
|
||||
process_compiler t, config.update_compiler_options(&[{:warnings_as_errors, true}|&1])
|
||||
end
|
||||
|
||||
|
||||
defp process_compiler(["--verbose"|t], config) do
|
||||
process_compiler t, config.verbose_compile(true)
|
||||
end
|
||||
@@ -339,18 +339,42 @@ defmodule Kernel.CLI do
|
||||
defp process_command({:compile, patterns}, config) do
|
||||
:filelib.ensure_dir(:filename.join(config.output, "."))
|
||||
|
||||
files = Enum.map patterns, &Path.wildcard(&1)
|
||||
files = Enum.uniq(Enum.concat(files))
|
||||
files = Enum.filter files, &:filelib.is_regular(&1)
|
||||
case match_regular_files(patterns) do
|
||||
{ :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, ",")}" }
|
||||
end
|
||||
end
|
||||
|
||||
if files != [] do
|
||||
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)
|
||||
defp match_regular_files(patterns) do
|
||||
matched_files = Enum.map patterns, fn(pattern) ->
|
||||
case Path.wildcard(pattern) do
|
||||
[] -> { :missing, pattern }
|
||||
files -> { :ok, files }
|
||||
end
|
||||
end
|
||||
|
||||
files = Enum.filter_map matched_files,
|
||||
fn(match) -> elem(match, 0) == :ok end,
|
||||
&elem(&1, 1)
|
||||
|
||||
missing_patterns = Enum.filter_map matched_files,
|
||||
fn(match) -> elem(match, 0) == :missing end,
|
||||
&elem(&1, 1)
|
||||
|
||||
if missing_patterns == [] do
|
||||
files = Enum.uniq(Enum.concat(files))
|
||||
files = Enum.filter files, &:filelib.is_regular(&1)
|
||||
{ :ok, files }
|
||||
else
|
||||
{ :error, "--compile : No files matched patterns #{Enum.join(patterns, ",")}" }
|
||||
{ :missing, Enum.uniq(missing_patterns) }
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -18,9 +18,19 @@ defmodule Kernel.ErrorHandler do
|
||||
{ :module, _ } -> []
|
||||
{ :error, _ } ->
|
||||
parent = :erlang.get(:elixir_compiler_pid)
|
||||
parent <- { :waiting, self(), module }
|
||||
ref = :erlang.make_ref
|
||||
send parent, { :waiting, self(), ref, module }
|
||||
:erlang.garbage_collect(self)
|
||||
receive do
|
||||
{ :release, ^parent } -> :ok
|
||||
{ ^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)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
# any of the `GenServer.Behaviour` conveniences.
|
||||
defmodule Kernel.LexicalTracker do
|
||||
@timeout 30_000
|
||||
@behavior :gen_server
|
||||
@behaviour :gen_server
|
||||
|
||||
@import 2
|
||||
@alias 3
|
||||
@@ -104,8 +104,8 @@ defmodule Kernel.LexicalTracker do
|
||||
{ :reply, d, d }
|
||||
end
|
||||
|
||||
def handle_call(_request, _from, d) do
|
||||
{ :noreply, d }
|
||||
def handle_call(request, _from, d) do
|
||||
{ :stop, { :bad_call, request }, d }
|
||||
end
|
||||
|
||||
def handle_cast({ :remote_dispatch, module }, d) do
|
||||
@@ -137,8 +137,8 @@ defmodule Kernel.LexicalTracker do
|
||||
{ :stop, :normal, d }
|
||||
end
|
||||
|
||||
def handle_cast(_msg, d) do
|
||||
{ :noreply, d }
|
||||
def handle_cast(msg, d) do
|
||||
{ :stop, { :bad_cast, msg }, d }
|
||||
end
|
||||
|
||||
def handle_info(_msg, d) do
|
||||
|
||||
@@ -24,7 +24,7 @@ defmodule Kernel.ParallelCompiler do
|
||||
The compiler doesn't care about the return values of the callbacks.
|
||||
Returns the modules generated by each compiled file.
|
||||
"""
|
||||
def files(files, callbacks // [])
|
||||
def files(files, callbacks \\ [])
|
||||
|
||||
def files(files, callbacks) when is_list(callbacks) do
|
||||
spawn_compilers(files, nil, callbacks)
|
||||
@@ -34,7 +34,7 @@ defmodule Kernel.ParallelCompiler do
|
||||
Compiles the given files to the given path.
|
||||
Read `files/2` for more information.
|
||||
"""
|
||||
def files_to_path(files, path, callbacks // [])
|
||||
def files_to_path(files, path, callbacks \\ [])
|
||||
|
||||
def files_to_path(files, path, callbacks) when is_binary(path) and is_list(callbacks) do
|
||||
spawn_compilers(files, path, callbacks)
|
||||
@@ -62,29 +62,42 @@ defmodule Kernel.ParallelCompiler do
|
||||
wait_for_messages(files, 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)
|
||||
spawn_compilers(t, original, output, callbacks, waiting, queued, schedulers, result)
|
||||
end
|
||||
|
||||
# Spawn a compiler for each file in the list until we reach the limit
|
||||
defp spawn_compilers([h|t], original, output, callbacks, waiting, queued, schedulers, result) do
|
||||
parent = self()
|
||||
|
||||
child = spawn_link fn ->
|
||||
:erlang.put(:elixir_compiler_pid, parent)
|
||||
:erlang.put(:elixir_ensure_compiled, true)
|
||||
:erlang.process_flag(:error_handler, Kernel.ErrorHandler)
|
||||
{ pid, ref } =
|
||||
:erlang.spawn_monitor fn ->
|
||||
# Notify Code.ensure_compiled/2 that we should
|
||||
# attempt to compile the module by doing a dispatch.
|
||||
:erlang.put(:elixir_ensure_compiled, true)
|
||||
|
||||
try do
|
||||
if output do
|
||||
:elixir_compiler.file_to_path(h, output)
|
||||
else
|
||||
:elixir_compiler.file(h)
|
||||
end
|
||||
parent <- { :compiled, self(), h }
|
||||
catch
|
||||
kind, reason ->
|
||||
parent <- { :failure, self(), kind, reason, System.stacktrace }
|
||||
# Set the elixir_compiler_pid used by our custom Kernel.ErrorHandler.
|
||||
:erlang.put(:elixir_compiler_pid, parent)
|
||||
:erlang.process_flag(:error_handler, Kernel.ErrorHandler)
|
||||
|
||||
exit(try do
|
||||
if output do
|
||||
:elixir_compiler.file_to_path(h, output)
|
||||
else
|
||||
:elixir_compiler.file(h)
|
||||
end
|
||||
{ :compiled, h }
|
||||
catch
|
||||
kind, reason ->
|
||||
{ :failure, kind, reason, System.stacktrace }
|
||||
end)
|
||||
end
|
||||
end
|
||||
|
||||
spawn_compilers(t, original, output, callbacks, waiting, [{child, 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
|
||||
@@ -92,7 +105,10 @@ defmodule Kernel.ParallelCompiler do
|
||||
|
||||
# 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 <- { :release, self() } end
|
||||
Enum.each queued, fn { child, _, _ } ->
|
||||
{ ^child, ref, _ } = List.keyfind(waiting, child, 0)
|
||||
send child, { ref, :release }
|
||||
end
|
||||
wait_for_messages([], original, output, callbacks, waiting, queued, schedulers, result)
|
||||
end
|
||||
|
||||
@@ -104,38 +120,61 @@ defmodule Kernel.ParallelCompiler do
|
||||
# Wait for messages from child processes
|
||||
defp wait_for_messages(files, original, output, callbacks, waiting, queued, schedulers, result) do
|
||||
receive do
|
||||
{ :compiled, child, file } ->
|
||||
if callback = Keyword.get(callbacks, :each_file) do
|
||||
callback.(file)
|
||||
end
|
||||
new_queued = List.keydelete(queued, child, 0)
|
||||
|
||||
# Sometimes we may have spurious entries in the waiting
|
||||
# list because someone invoked try/rescue UndefinedFunctionError
|
||||
new_waiting = List.keydelete(waiting, child, 0)
|
||||
spawn_compilers(files, original, output, callbacks, new_waiting, new_queued, schedulers, result)
|
||||
{ :module_available, child, ref, file, module, binary } ->
|
||||
if callback = Keyword.get(callbacks, :each_module) do
|
||||
callback.(file, module, binary)
|
||||
end
|
||||
|
||||
# Release the module loader which is waiting for an ack
|
||||
child <- { ref, :ack }
|
||||
send child, { ref, :ack }
|
||||
|
||||
new_waiting = release_waiting_processes(module, waiting)
|
||||
new_result = [module|result]
|
||||
wait_for_messages(files, original, output, callbacks, new_waiting, queued, schedulers, new_result)
|
||||
{ :waiting, child, on } ->
|
||||
new_waiting = :orddict.store(child, on, waiting)
|
||||
spawn_compilers(files, original, output, callbacks, new_waiting, queued, schedulers, result)
|
||||
{ :failure, child, kind, reason, stacktrace } ->
|
||||
available = lc { pid, _, waiting_module } inlist waiting,
|
||||
waiting_module == module,
|
||||
do: pid
|
||||
|
||||
spawn_compilers(available ++ files, original, output, callbacks,
|
||||
waiting, queued, schedulers, [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 }
|
||||
else
|
||||
waiting = [{ child, ref, on }|waiting]
|
||||
end
|
||||
spawn_compilers(files, 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_queued = List.keydelete(queued, down_pid, 0)
|
||||
spawn_compilers(files, 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)
|
||||
end
|
||||
end
|
||||
|
||||
defp handle_failure(ref, kind, reason, stacktrace, files, waiting, 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, file } ->
|
||||
Enum.each Enum.reverse(queued), fn { pid, _ref, file } ->
|
||||
case List.keyfind(waiting, pid, 0) do
|
||||
{ _, mod } -> IO.puts "* #{file} is missing module #{inspect mod}"
|
||||
{ ^pid, _, mod } -> IO.puts "* #{file} is missing module #{inspect mod}"
|
||||
_ -> :ok
|
||||
end
|
||||
end
|
||||
@@ -143,29 +182,18 @@ defmodule Kernel.ParallelCompiler do
|
||||
IO.puts "\nThe first failure is shown below..."
|
||||
end
|
||||
|
||||
{^child, file} = List.keyfind(queued, child, 0)
|
||||
IO.puts "== Compilation error on file #{file} =="
|
||||
:erlang.raise(kind, reason, stacktrace)
|
||||
_ ->
|
||||
:ok
|
||||
end
|
||||
end
|
||||
|
||||
defp many_missing?(child, files, waiting, queued) do
|
||||
waiting_length = length(waiting)
|
||||
|
||||
match?({ ^child, _ }, List.keyfind(waiting, child, 0)) and
|
||||
match?({ ^child, _, _ }, List.keyfind(waiting, child, 0)) and
|
||||
waiting_length > 1 and files == [] and
|
||||
waiting_length == length(queued)
|
||||
end
|
||||
|
||||
# Release waiting processes that are waiting for the given module
|
||||
defp release_waiting_processes(module, waiting) do
|
||||
Enum.filter waiting, fn { child, waiting_module } ->
|
||||
if waiting_module == module do
|
||||
child <- { :release, self() }
|
||||
false
|
||||
else
|
||||
true
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -13,7 +13,7 @@ defmodule Kernel.ParallelRequire do
|
||||
|
||||
Returns the modules generated by each required file.
|
||||
"""
|
||||
def files(files, callback // default_callback) do
|
||||
def files(files, callback \\ default_callback) do
|
||||
schedulers = max(:erlang.system_info(:schedulers_online), 2)
|
||||
spawn_requires(files, [], callback, schedulers, [])
|
||||
end
|
||||
@@ -35,7 +35,7 @@ defmodule Kernel.ParallelRequire do
|
||||
ensure_compiled = :erlang.get(:elixir_ensure_compiled)
|
||||
{ :error_handler, handler } = :erlang.process_info(parent, :error_handler)
|
||||
|
||||
child = spawn_link fn ->
|
||||
{ pid, ref } = :erlang.spawn_monitor fn ->
|
||||
if compiler_pid != :undefined do
|
||||
:erlang.put(:elixir_compiler_pid, compiler_pid)
|
||||
end
|
||||
@@ -46,25 +46,35 @@ defmodule Kernel.ParallelRequire do
|
||||
|
||||
:erlang.process_flag(:error_handler, handler)
|
||||
|
||||
try do
|
||||
exit(try do
|
||||
new = Code.require_file(h) || []
|
||||
parent <- { :required, self, Enum.map(new, &elem(&1, 0)), h }
|
||||
{ :required, Enum.map(new, &elem(&1, 0)), h }
|
||||
catch
|
||||
kind, reason ->
|
||||
parent <- { :failure, self, kind, reason, System.stacktrace }
|
||||
end
|
||||
{ :failure, kind, reason, System.stacktrace }
|
||||
end)
|
||||
end
|
||||
|
||||
spawn_requires(t, [child|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
|
||||
{ :required, child, mods, file } ->
|
||||
callback.(file)
|
||||
spawn_requires(files, List.delete(waiting, child), callback, schedulers, mods ++ result)
|
||||
{ :failure, _child, kind, reason, stacktrace } ->
|
||||
:erlang.raise(kind, reason, stacktrace)
|
||||
{ :DOWN, ref, :process, pid, status } ->
|
||||
tuple = { pid, ref }
|
||||
if tuple in waiting do
|
||||
case status do
|
||||
{ :required, mods, file } ->
|
||||
callback.(file)
|
||||
result = mods ++ result
|
||||
waiting = List.delete(waiting, tuple)
|
||||
{ :failure, kind, reason, stacktrace } ->
|
||||
:erlang.raise(kind, reason, stacktrace)
|
||||
other ->
|
||||
:erlang.raise(:exit, other, [])
|
||||
end
|
||||
end
|
||||
spawn_requires(files, waiting, callback, schedulers, result)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -72,7 +72,7 @@ defmodule Kernel.RecordRewriter do
|
||||
case record_fields(module, record) do
|
||||
{ fields, optimizable } ->
|
||||
opt_call =
|
||||
if :lists.member({ function, length(args) + 1 }, optimizable) do
|
||||
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
|
||||
@@ -111,7 +111,7 @@ defmodule Kernel.RecordRewriter do
|
||||
|
||||
defp optimize_record_other_call(line, record, res, function, left, args) do
|
||||
call = { :call, line,
|
||||
{ :remote, line, { :atom, line, record }, { :atom, 0, function } },
|
||||
{ :remote, line, { :atom, 0, record }, { :atom, 0, function } },
|
||||
args ++ [left]
|
||||
}
|
||||
{ call, res }
|
||||
@@ -128,7 +128,7 @@ defmodule Kernel.RecordRewriter do
|
||||
{ { :call, call_line, remote, [integer, tuple, value] }, dict, res }
|
||||
end
|
||||
|
||||
defp optimize_expr({ :call, call_line, { :remote, line, left, right }, args } = call, module, dict) do
|
||||
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)
|
||||
|
||||
@@ -1,22 +1,22 @@
|
||||
defmodule Kernel.SpecialForms do
|
||||
@moduledoc """
|
||||
In this module we define Elixir special forms. Special forms
|
||||
cannot be overriden by the developer and are the basic
|
||||
cannot be overridden by the developer and are the basic
|
||||
building blocks of Elixir code.
|
||||
|
||||
Some of those forms are lexical (like `alias`, `import`, etc).
|
||||
The macros `{}`, `[]` and `<<>>` are also special forms used
|
||||
to define data structures, respectively tuples, lists and binaries.
|
||||
Some of those forms are lexical (like `alias`, `case`, etc).
|
||||
The macros `{}` and `<<>>` are also special forms used to define
|
||||
tuple and binary data structures respectively.
|
||||
|
||||
This module also documents Elixir's pseudo variables (`__MODULE__`,
|
||||
`__FILE__`, `__ENV__`, `__DIR__` and `__CALLER__`). Pseudo variables
|
||||
return information about Elixir's compilation environment and can only
|
||||
This module also documents Elixir's pseudo variables (`__ENV__`,
|
||||
`__MODULE__`, `__DIR__` and `__CALLER__`). Pseudo variables return
|
||||
information about Elixir's compilation environment and can only
|
||||
be read, never assigned to.
|
||||
|
||||
Finally, it also documents 3 special forms (`__block__`,
|
||||
`__scope__` and `__aliases__`), which are not intended to be
|
||||
called directly by the developer but they appear in quoted
|
||||
contents since they are essential in Elixir's constructs.
|
||||
Finally, it also documents 2 special forms, `__block__` and
|
||||
`__aliases__`, which are not intended to be called directly by the
|
||||
developer but they appear in quoted contents since they are essential
|
||||
in Elixir's constructs.
|
||||
"""
|
||||
|
||||
@doc """
|
||||
@@ -34,7 +34,7 @@ defmodule Kernel.SpecialForms do
|
||||
{ :{}, [], [1,2,3] }
|
||||
|
||||
"""
|
||||
defmacro :{}.(args)
|
||||
defmacro unquote(:{})(args)
|
||||
|
||||
@doc """
|
||||
Defines a new bitstring.
|
||||
@@ -171,7 +171,7 @@ defmodule Kernel.SpecialForms do
|
||||
For floats, unit * size must result in 32 or 64, corresponding
|
||||
to binary32 and binary64, respectively.
|
||||
"""
|
||||
defmacro :<<>>.(args)
|
||||
defmacro unquote(:<<>>)(args)
|
||||
|
||||
@doc """
|
||||
Defines a remote call or an alias.
|
||||
@@ -227,7 +227,7 @@ defmodule Kernel.SpecialForms do
|
||||
In case the right-side is also dynamic, `.`'s behaviour can be reproduced
|
||||
at runtime via `apply/3` and `Module.concat/2`:
|
||||
|
||||
iex> apply(Kernel, :+, [1,2])
|
||||
iex> apply(:erlang, :+, [1,2])
|
||||
3
|
||||
iex> Module.concat(Kernel, Sample)
|
||||
Kernel.Sample
|
||||
@@ -284,7 +284,7 @@ defmodule Kernel.SpecialForms do
|
||||
[{:__aliases__, [alias: false], [:String]}, Sample]}
|
||||
|
||||
"""
|
||||
defmacro (:.).(left, right)
|
||||
defmacro unquote(:.)(left, right)
|
||||
|
||||
@doc """
|
||||
`alias` is used to setup aliases, often useful with modules names.
|
||||
@@ -331,7 +331,7 @@ defmodule Kernel.SpecialForms do
|
||||
Elixir won't emit any warnings though, since the alias
|
||||
was not explicitly defined.
|
||||
|
||||
Both warning behaviors could be changed by explicitily
|
||||
Both warning behaviours could be changed by explicitily
|
||||
setting the `:warn` option to true or false.
|
||||
"""
|
||||
defmacro alias(module, opts)
|
||||
@@ -443,7 +443,7 @@ defmodule Kernel.SpecialForms do
|
||||
Elixir won't emit any warnings though, since the import
|
||||
was not explicitly defined.
|
||||
|
||||
Both warning behaviors could be changed by explicitily
|
||||
Both warning behaviours could be changed by explicitily
|
||||
setting the `:warn` option to true or false.
|
||||
|
||||
## Ambiguous function/macro names
|
||||
@@ -471,26 +471,52 @@ defmodule Kernel.SpecialForms do
|
||||
"""
|
||||
defmacro __MODULE__
|
||||
|
||||
@doc """
|
||||
Returns the current file name as a binary.
|
||||
|
||||
Although the file can be accessed in the `__ENV__`, this macro
|
||||
is a convenient shortcut.
|
||||
"""
|
||||
defmacro __FILE__
|
||||
|
||||
@doc """
|
||||
Returns the current directory as a binary.
|
||||
|
||||
Although the directory can be accessed as `Path.dirname(__ENV__.file)`,
|
||||
this macro is a convenient shortcut.
|
||||
"""
|
||||
defmacro __DIR__
|
||||
|
||||
@doc %S"""
|
||||
@doc """
|
||||
Accesses an already bound variable in match clauses.
|
||||
|
||||
## Examples
|
||||
|
||||
Elixir allows variables to be rebound via static single assignment:
|
||||
|
||||
iex> x = 1
|
||||
iex> x = 2
|
||||
iex> x
|
||||
2
|
||||
|
||||
However, in some situations, it is useful to match against an existing
|
||||
value, instead of rebinding. This can be done with the `^` special form:
|
||||
|
||||
iex> x = 1
|
||||
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
|
||||
1
|
||||
|
||||
"""
|
||||
defmacro ^(var)
|
||||
|
||||
@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
|
||||
|
||||
@@ -532,14 +558,14 @@ defmodule Kernel.SpecialForms do
|
||||
2.0 #=> Floats
|
||||
[1, 2] #=> Lists
|
||||
"strings" #=> Strings
|
||||
{key, value} #=> Tuple with two elements
|
||||
{key, value} #=> Tuples with two elements
|
||||
|
||||
## Quote and macros
|
||||
|
||||
`quote` is commonly used with macros for code generation. As an exercise,
|
||||
let's define a macro that multiplies a number by itself (squared). Note
|
||||
there is no reason to define such as a macro (and it would actually be
|
||||
seen as a bad practice), but it is simple enough that allows us to focus
|
||||
seen as a bad practice), but it is simple enough that it allows us to focus
|
||||
on the important aspects of quotes and macros:
|
||||
|
||||
defmodule Math do
|
||||
@@ -557,8 +583,9 @@ defmodule Kernel.SpecialForms do
|
||||
|
||||
At first, there is nothing in this example that actually reveals it is a
|
||||
macro. But what is happening is that, at compilation time, `squared(5)`
|
||||
becomes `5 * 5`. We can see this behaviour in practice though
|
||||
because our macro actually has a bug:
|
||||
becomes `5 * 5`. The argument `5` is duplicated in the produced code, we
|
||||
can see this behaviour in practice though because our macro actually has
|
||||
a bug:
|
||||
|
||||
import Math
|
||||
my_number = fn ->
|
||||
@@ -613,11 +640,11 @@ defmodule Kernel.SpecialForms do
|
||||
end
|
||||
end
|
||||
|
||||
`:bind_quoted` will translate to the same example as above. `:bind_quoted`
|
||||
can be used in many cases and is seen as good practice, not only because
|
||||
it helps us from running into common mistakes but also because it allows
|
||||
us to leverage other tools exposed by macros, as unquote fragments discussed
|
||||
in some sections below.
|
||||
`:bind_quoted` will translate to the same code as the example above.
|
||||
`:bind_quoted` can be used in many cases and is seen as good practice,
|
||||
not only because it helps us from running into common mistakes but also
|
||||
because it allows us to leverage other tools exposed by macros, such as
|
||||
unquote fragments discussed in some sections below.
|
||||
|
||||
Before we finish this brief introduction, you will notice that, even though
|
||||
we defined a variable `x` inside our quote:
|
||||
@@ -637,7 +664,7 @@ defmodule Kernel.SpecialForms do
|
||||
because Elixir macros are hygienic, a topic we will discuss at length
|
||||
in the next sections as well.
|
||||
|
||||
### Hygiene in variables
|
||||
## Hygiene in variables
|
||||
|
||||
Consider the following example:
|
||||
|
||||
@@ -655,8 +682,8 @@ defmodule Kernel.SpecialForms do
|
||||
|
||||
In the example above, `a` returns 10 even if the macro
|
||||
is apparently setting it to 1 because variables defined
|
||||
in the macro does not affect the context the macro is executed.
|
||||
If you want to set or get a variable in the user context, you
|
||||
in the macro does not affect the context the macro is executed in.
|
||||
If you want to set or get a variable in the caller's context, you
|
||||
can do it with the help of the `var!` macro:
|
||||
|
||||
defmodule NoHygiene do
|
||||
@@ -671,7 +698,7 @@ defmodule Kernel.SpecialForms do
|
||||
NoHygiene.interference
|
||||
a #=> 1
|
||||
|
||||
Note that you cannot even access variables defined by the same
|
||||
Note that you cannot even access variables defined in the same
|
||||
module unless you explicitly give it a context:
|
||||
|
||||
defmodule Hygiene do
|
||||
@@ -717,7 +744,7 @@ defmodule Kernel.SpecialForms do
|
||||
|
||||
quote hygiene: [vars: false], do: x
|
||||
|
||||
### Hygiene in aliases
|
||||
## Hygiene in aliases
|
||||
|
||||
Aliases inside quote are hygienic by default.
|
||||
Consider the following example:
|
||||
@@ -737,10 +764,23 @@ defmodule Kernel.SpecialForms do
|
||||
in the context the macro is expanded, the code above works
|
||||
because `D` still expands to `HashDict`.
|
||||
|
||||
In some particular cases you may want to access an alias
|
||||
or a module defined in the caller. In such scenarios, you
|
||||
can access it by disabling hygiene with `hygiene: [aliases: false]`
|
||||
or by using the `alias!` macro inside the quote:
|
||||
Similarly, even if we defined an alias with the same name
|
||||
before invoking a macro, it won't affect the macro's result:
|
||||
|
||||
defmodule Hygiene do
|
||||
alias HashDict, as: D
|
||||
|
||||
defmacro no_interference do
|
||||
quote do: D.new
|
||||
end
|
||||
end
|
||||
|
||||
require Hygiene
|
||||
alias SomethingElse, as: D
|
||||
Hygiene.no_interference #=> #HashDict<[]>
|
||||
|
||||
In some cases, you want to access an alias or a module defined
|
||||
in the caller. For such, you can use the `alias!` macro:
|
||||
|
||||
defmodule Hygiene do
|
||||
# This will expand to Elixir.Nested.hello
|
||||
@@ -789,7 +829,16 @@ defmodule Kernel.SpecialForms do
|
||||
Hygiene.return_size #=> 5
|
||||
|
||||
Notice how `return_size` returns 5 even though the `size/1`
|
||||
function is not imported.
|
||||
function is not imported. In fact, even if `return_size` imported
|
||||
a function from another module, it wouldn't affect the function
|
||||
result:
|
||||
|
||||
def return_size do
|
||||
import Dict, only: [size: 1]
|
||||
get_size
|
||||
end
|
||||
|
||||
Calling this new `return_size` will still return 5 as result.
|
||||
|
||||
Elixir is smart enough to delay the resolution to the latest
|
||||
moment possible. So, if you call `size("hello")` inside quote,
|
||||
@@ -842,12 +891,8 @@ defmodule Kernel.SpecialForms do
|
||||
When using `location: :keep` and invalid arguments are given to
|
||||
`Sample.add/2`, the stacktrace information will point to the file
|
||||
and line inside the quote. Without `location: :keep`, the error is
|
||||
reported to where `defadd` was invoked.
|
||||
|
||||
Note that `location: :keep` evaluates the code as if it was defined
|
||||
inside the `Adder` file, in particular, the macro `__FILE__` and
|
||||
exceptions happening inside the quote will always point to the file
|
||||
where `Added` was defined.
|
||||
reported to where `defadd` was invoked. Note `location: :keep` affects
|
||||
only definitions inside the quote.
|
||||
|
||||
## Binding and unquote fragments
|
||||
|
||||
@@ -860,12 +905,12 @@ defmodule Kernel.SpecialForms do
|
||||
def unquote(k)(), do: unquote(v)
|
||||
end
|
||||
|
||||
In the example above, we have generated the function `foo/0` and
|
||||
In the example above, we have generated the functions `foo/0` and
|
||||
`bar/0` dynamically. Now, imagine that, we want to convert this
|
||||
functionality into a macro:
|
||||
|
||||
defmacro defkv(kv) do
|
||||
Enum.each kv, fn { k, v } ->
|
||||
Enum.map kv, fn { k, v } ->
|
||||
quote do
|
||||
def unquote(k)(), do: unquote(v)
|
||||
end
|
||||
@@ -882,13 +927,13 @@ defmodule Kernel.SpecialForms do
|
||||
defkv kv
|
||||
|
||||
This is because the macro is expecting its arguments to be a
|
||||
key-value at **compilation** time. Since in the example above
|
||||
keyword list at **compilation** time. Since in the example above
|
||||
we are passing the representation of the variable `kv`, our
|
||||
code fails.
|
||||
|
||||
This is actually a common pitfall when developing macros. In
|
||||
practice, we want to avoid doing work at compilation time as
|
||||
much as we can. That said, let's attempt to improve our macro:
|
||||
much as possible. That said, let's attempt to improve our macro:
|
||||
|
||||
defmacro defkv(kv) do
|
||||
quote do
|
||||
@@ -900,12 +945,12 @@ defmodule Kernel.SpecialForms do
|
||||
|
||||
If you try to run our new macro, you will notice it won't
|
||||
even compile, complaining that the variables `k` and `v`
|
||||
do not exist. This is because of the ambiguity: `unquote(k)`
|
||||
does not exist. This is because of the ambiguity: `unquote(k)`
|
||||
can either be an unquote fragment, as previously, or a regular
|
||||
unquote as in `unquote(kv)`.
|
||||
|
||||
One solution for this problem is to disable unquoting in the
|
||||
macro, however, doing that would make it impossible to inject
|
||||
One solution to this problem is to disable unquoting in the
|
||||
macro, however, doing that would make it impossible to inject the
|
||||
`kv` representation into the tree. That's when the `:bind_quoted`
|
||||
option comes to the rescue (again!). By using `:bind_quoted`, we
|
||||
can automatically disable unquoting while still injecting the
|
||||
@@ -924,27 +969,6 @@ defmodule Kernel.SpecialForms do
|
||||
"""
|
||||
defmacro quote(opts, block)
|
||||
|
||||
@doc """
|
||||
When used inside quoting, marks that the variable should
|
||||
not be hygienized. The argument can be either a variable
|
||||
unquoted or an atom representing the variable name.
|
||||
Check `quote/2` for more information.
|
||||
"""
|
||||
defmacro var!(var)
|
||||
|
||||
@doc """
|
||||
Defines a variable in the given context.
|
||||
Check `quote/2` for more information.
|
||||
"""
|
||||
defmacro var!(var, context)
|
||||
|
||||
@doc """
|
||||
When used inside quoting, marks that the alias should not
|
||||
be hygienezed. This means the alias will be expanded when
|
||||
the macro is expanded.
|
||||
"""
|
||||
defmacro alias!(alias)
|
||||
|
||||
@doc """
|
||||
Unquotes the given expression from inside a macro.
|
||||
|
||||
@@ -968,8 +992,7 @@ defmodule Kernel.SpecialForms do
|
||||
#=> { :sum, [], [1, 13, 3] }
|
||||
|
||||
"""
|
||||
name = :unquote
|
||||
defmacro unquote(name)(expr)
|
||||
defmacro unquote(:unquote)(expr)
|
||||
|
||||
@doc """
|
||||
Unquotes the given list expanding its arguments. Similar
|
||||
@@ -982,8 +1005,7 @@ defmodule Kernel.SpecialForms do
|
||||
#=> { :sum, [], [1, 2, 3, 4, 5] }
|
||||
|
||||
"""
|
||||
name = :unquote_splicing
|
||||
defmacro unquote(name)(expr)
|
||||
defmacro unquote(:unquote_splicing)(expr)
|
||||
|
||||
@doc """
|
||||
List comprehensions allow you to quickly build a list from another list:
|
||||
@@ -1067,8 +1089,7 @@ defmodule Kernel.SpecialForms do
|
||||
3
|
||||
|
||||
"""
|
||||
name = :fn
|
||||
defmacro unquote(name)(clauses)
|
||||
defmacro unquote(:fn)(clauses)
|
||||
|
||||
@doc """
|
||||
Internal special form for block expressions.
|
||||
@@ -1158,21 +1179,7 @@ defmodule Kernel.SpecialForms do
|
||||
&(foo(&1, &2); &3 + &4)
|
||||
|
||||
"""
|
||||
name = :&
|
||||
defmacro unquote(name)(expr)
|
||||
|
||||
@doc """
|
||||
Internal special form for modifying scope information.
|
||||
|
||||
Used when `quote` is invoked with `location: :keep` to execute
|
||||
a given block as if it belonged to another file.
|
||||
|
||||
quote location: :keep, do: 1
|
||||
#=> { :__scope__, [line: 1], [[file: "iex"],[do: 1]] }
|
||||
|
||||
Check `quote/2` for more information.
|
||||
"""
|
||||
defmacro __scope__(opts, args)
|
||||
defmacro unquote(:&)(expr)
|
||||
|
||||
@doc """
|
||||
Internal special form to hold aliases information.
|
||||
@@ -1218,4 +1225,292 @@ defmodule Kernel.SpecialForms do
|
||||
See `Kernel.defoverridable` for more information and documentation.
|
||||
"""
|
||||
defmacro super(args)
|
||||
|
||||
@doc """
|
||||
Matches the given expression against the given clauses.
|
||||
|
||||
## Examples
|
||||
|
||||
case thing do
|
||||
{ :selector, i, value } when is_integer(i) ->
|
||||
value
|
||||
value ->
|
||||
value
|
||||
end
|
||||
|
||||
In the example above, we match `thing` against each clause "head"
|
||||
and execute the clause "body" corresponding to the first clause
|
||||
that matches. If no clause matches, an error is raised.
|
||||
|
||||
## Variables handling
|
||||
|
||||
Notice that variables bound in a clause "head" do not leak to the
|
||||
outer context:
|
||||
|
||||
case data do
|
||||
{ :ok, value } -> value
|
||||
:error -> nil
|
||||
end
|
||||
|
||||
value #=> unbound variable value
|
||||
|
||||
However, variables explicitly bound in the clause "body" are
|
||||
accessible from the outer context:
|
||||
|
||||
value = 7
|
||||
|
||||
case lucky? do
|
||||
false -> value = 13
|
||||
true -> true
|
||||
end
|
||||
|
||||
value #=> 7 or 13
|
||||
|
||||
In the example above, value is going to be `7` or `13` depending on
|
||||
the value of `lucky?`. In case `value` has no previous value before
|
||||
case, clauses that do not explicitly bind a value have the variable
|
||||
bound to nil.
|
||||
"""
|
||||
defmacro case(condition, blocks)
|
||||
|
||||
@doc """
|
||||
Evaluate the given expressions and handle any error, exit
|
||||
or throw that may have happened.
|
||||
|
||||
## Examples
|
||||
|
||||
try do
|
||||
do_something_that_may_fail(some_arg)
|
||||
rescue
|
||||
ArgumentError ->
|
||||
IO.puts "Invalid argument given"
|
||||
catch
|
||||
value ->
|
||||
IO.puts "caught \#{value}"
|
||||
else
|
||||
value ->
|
||||
IO.puts "Success! The result was \#{value}"
|
||||
after
|
||||
IO.puts "This is printed regardless if it failed or succeed"
|
||||
end
|
||||
|
||||
The rescue clause is used to handle exceptions, while the catch
|
||||
clause can be used to catch thrown values. The else clause can
|
||||
be used to control flow based on the result of the expression.
|
||||
Catch, rescue and else clauses work based on pattern matching.
|
||||
|
||||
Note that calls inside `try` are not tail recursive since the VM
|
||||
needs to keep the stacktrace in case an exception happens.
|
||||
|
||||
## Rescue clauses
|
||||
|
||||
Besides relying on pattern matching, rescue clauses provides some
|
||||
conveniences around exceptions that allows one to rescue an
|
||||
exception by its name. All the following formats are valid rescue
|
||||
expressions:
|
||||
|
||||
try do
|
||||
UndefinedModule.undefined_function
|
||||
rescue
|
||||
UndefinedFunctionError -> nil
|
||||
end
|
||||
|
||||
try do
|
||||
UndefinedModule.undefined_function
|
||||
rescue
|
||||
[UndefinedFunctionError] -> nil
|
||||
end
|
||||
|
||||
# rescue and bind to x
|
||||
try do
|
||||
UndefinedModule.undefined_function
|
||||
rescue
|
||||
x in [UndefinedFunctionError] -> nil
|
||||
end
|
||||
|
||||
# rescue all and bind to x
|
||||
try do
|
||||
UndefinedModule.undefined_function
|
||||
rescue
|
||||
x -> nil
|
||||
end
|
||||
|
||||
## Catching exits and Erlang errors
|
||||
|
||||
The catch clause works exactly the same as in erlang. Therefore,
|
||||
one can also handle exits/errors coming from Erlang as below:
|
||||
|
||||
try do
|
||||
exit(1)
|
||||
catch
|
||||
:exit, 1 -> IO.puts "Exited with 1"
|
||||
end
|
||||
|
||||
try do
|
||||
error(:sample)
|
||||
catch
|
||||
:error, :sample ->
|
||||
IO.puts "sample error"
|
||||
end
|
||||
|
||||
Although the second form should be avoided in favor of raise/rescue
|
||||
control mechanisms.
|
||||
|
||||
## Else clauses
|
||||
|
||||
Else clauses allow the result of the expression to be pattern
|
||||
matched on:
|
||||
|
||||
x = 2
|
||||
try do
|
||||
1 / x
|
||||
rescue
|
||||
ArithmeticError ->
|
||||
:infinity
|
||||
else
|
||||
y when y < 1 and y > -1 ->
|
||||
:small
|
||||
_ ->
|
||||
:large
|
||||
end
|
||||
|
||||
If an else clause is not present the result of the expression will
|
||||
be return, if no exceptions are raised:
|
||||
|
||||
x = 1
|
||||
^x =
|
||||
try do
|
||||
1 / x
|
||||
rescue
|
||||
ArithmeticError ->
|
||||
:infinity
|
||||
end
|
||||
|
||||
However when an else clause is present but the result of the expression
|
||||
does not match any of the patterns an exception will be raised. This
|
||||
exception will not be caught by a catch or rescue in the same try:
|
||||
|
||||
x = 1
|
||||
try do
|
||||
try do
|
||||
1 / x
|
||||
rescue
|
||||
# The TryClauseError can not be rescued here:
|
||||
TryClauseError ->
|
||||
:error_a
|
||||
else
|
||||
0 ->
|
||||
:small
|
||||
end
|
||||
rescue
|
||||
# The TryClauseError is rescued here:
|
||||
TryClauseError ->
|
||||
:error_b
|
||||
end
|
||||
|
||||
Similarly an exception inside an else clause is not caught or rescued
|
||||
inside the same try:
|
||||
|
||||
try do
|
||||
try do
|
||||
nil
|
||||
catch
|
||||
# The exit(1) call below can not be caught here:
|
||||
:exit, _ ->
|
||||
:exit_a
|
||||
else
|
||||
_ ->
|
||||
exit(1)
|
||||
end
|
||||
catch
|
||||
# The exit is caught here:
|
||||
:exit, _ ->
|
||||
:exit_b
|
||||
end
|
||||
|
||||
This means the VM nolonger 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.
|
||||
|
||||
## Variable handling
|
||||
|
||||
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:
|
||||
|
||||
try do
|
||||
x = 1
|
||||
do_something_that_may_fail(same_arg)
|
||||
:ok
|
||||
catch
|
||||
_, _ -> :failed
|
||||
end
|
||||
|
||||
x #=> unbound variable `x`
|
||||
|
||||
In the example above, `x` cannot be accessed since it was defined
|
||||
inside the `try` clause. A common practice to address this issue
|
||||
is to return the variables defined inside `try`:
|
||||
|
||||
x =
|
||||
try do
|
||||
x = 1
|
||||
do_something_that_may_fail(same_arg)
|
||||
x
|
||||
catch
|
||||
_, _ -> :failed
|
||||
end
|
||||
|
||||
"""
|
||||
defmacro try(args)
|
||||
|
||||
@doc """
|
||||
Checks if there is a message matching the given clauses
|
||||
in the current process mailbox.
|
||||
|
||||
In case there is no such message, the current process hangs
|
||||
until a message arrives or waits until a given timeout value.
|
||||
|
||||
## Examples
|
||||
|
||||
receive do
|
||||
{ :selector, i, value } when is_integer(i) ->
|
||||
value
|
||||
value when is_atom(value) ->
|
||||
value
|
||||
_ ->
|
||||
IO.puts :stderr, "Unexpected message received"
|
||||
end
|
||||
|
||||
An optional after clause can be given in case the message was not
|
||||
received after the specified period of time:
|
||||
|
||||
receive do
|
||||
{ :selector, i, value } when is_integer(i) ->
|
||||
value
|
||||
value when is_atom(value) ->
|
||||
value
|
||||
_ ->
|
||||
IO.puts :stderr, "Unexpected message received"
|
||||
after
|
||||
5000 ->
|
||||
IO.puts :stderr, "No message in 5 seconds"
|
||||
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
|
||||
|
||||
* `:infinity` - The process should wait indefinitely for a matching
|
||||
message, this is the same as not using a timeout.
|
||||
|
||||
* 0 - if there is no matching message in the mailbox, the timeout
|
||||
will occur immediately.
|
||||
|
||||
## Variables handling
|
||||
|
||||
The `receive` special form handles variables exactly as the `case`
|
||||
special macro. For more information, check the docs for `case/2`.
|
||||
"""
|
||||
defmacro receive(args)
|
||||
end
|
||||
|
||||
+318
-152
@@ -2,56 +2,95 @@ defmodule Kernel.Typespec do
|
||||
@moduledoc """
|
||||
Provides macros and functions for working with typespecs.
|
||||
|
||||
The attributes `@type`, `@opaque`, `@typep`, `@spec` and
|
||||
`@callback` available in modules are handled by the equivalent
|
||||
macros defined by this module.
|
||||
Elixir comes with a notation for declaring types and specifications. Elixir is
|
||||
dynamically typed, as such typespecs are never used by the compiler to
|
||||
optimize or modify code. Still, using typespecs is useful as documentation and
|
||||
tools such as [Dialyzer](http://www.erlang.org/doc/man/dialyzer.html) can
|
||||
analyze the code with typespecs to find bugs.
|
||||
|
||||
## Defining a type
|
||||
The attributes `@type`, `@opaque`, `@typep`, `@spec` and `@callback` available
|
||||
in modules are handled by the equivalent macros defined by this module. See
|
||||
sub-sections "Defining a type" and "Defining a specification" below.
|
||||
|
||||
@type type_name :: type
|
||||
@typep type_name :: type
|
||||
@opaque type_name :: type
|
||||
## Types and their syntax
|
||||
|
||||
For more details, see documentation for `deftype`, `deftypep` and `defopaque`
|
||||
below.
|
||||
The type syntax provided by Elixir is fairly similar to the one in
|
||||
[Erlang](http://www.erlang.org/doc/reference_manual/typespec.html).
|
||||
|
||||
## Defining a specification
|
||||
Most of the built-in types provided in Erlang (for example, `pid()`) are
|
||||
expressed the same way: `pid()` or simply `pid`. Parametrized types are also
|
||||
supported (`list(integer)`) and so are remote types (`Enum.t`).
|
||||
|
||||
@spec function_name(type, type) :: type
|
||||
@callback function_name(type, type) :: type
|
||||
Integers and atom literals are allowed as types (ex. `1`, `:atom` or
|
||||
`false`). All other types are built of unions of predefined types. Certain
|
||||
shorthands are allowed, such as `[...]`, `<<>>` and `{...}`.
|
||||
|
||||
For more details, see documentation for `defspec` and `defcallback` below.
|
||||
### Predefined types
|
||||
|
||||
## Types
|
||||
Type :: any # the top type, the set of all terms
|
||||
| none # the bottom type, contains no terms
|
||||
| pid
|
||||
| port
|
||||
| reference
|
||||
| Atom
|
||||
| Bitstring
|
||||
| float
|
||||
| Fun
|
||||
| Integer
|
||||
| List
|
||||
| Tuple
|
||||
| Union
|
||||
| UserDefined # Described in section "Defining a type"
|
||||
|
||||
The type syntax provided by Elixir is fairly similar to the one
|
||||
in Erlang.
|
||||
Atom :: atom
|
||||
| ElixirAtom # `:foo`, `:bar`, ...
|
||||
|
||||
Most of the built-in types provided in Erlang (for example, `pid()`)
|
||||
are expressed the same way: `pid()` or simply `pid`. Parametrized types
|
||||
are also supported (`list(integer())`) and so are remote types (`Enum.t`).
|
||||
Bitstring :: <<>>
|
||||
| << _ :: M >> # M is a positive integer
|
||||
| << _ :: _ * N >> # N is a positive integer
|
||||
| << _ :: M, _ :: _ * N >>
|
||||
|
||||
Certain data type shortcuts (`[...]`, `<<>>` and `{...}`) are supported as
|
||||
well.
|
||||
Fun :: (... -> any) # any function
|
||||
| (... -> Type) # any arity, returning Type
|
||||
| (() -> Type))
|
||||
| (TList -> Type)
|
||||
|
||||
Main differences lie in how bit strings and functions are defined:
|
||||
Integer :: integer
|
||||
| ElixirInteger # ..., -1, 0, 1, ... 42 ...
|
||||
| ElixirInteger..ElixirInteger # an integer range
|
||||
|
||||
### Bit Strings
|
||||
List :: list(Type) # proper list ([]-terminated)
|
||||
| improper_list(Type1, Type2) # Type1=contents, Type2=termination
|
||||
| maybe_improper_list(Type1, Type2) # Type1 and Type2 as above
|
||||
| nonempty_list(Type) # proper non-empty list
|
||||
| [] # empty list
|
||||
| [Type] # shorthand for list(Type)
|
||||
| [Type, ...] # shorthand for nonempty_list(Type)
|
||||
|
||||
Tuple :: tuple # a tuple of any size
|
||||
| {} # empty tuple
|
||||
| { TList }
|
||||
|
||||
TList :: Type
|
||||
| Type, TList
|
||||
|
||||
Union :: Type1 | Type2
|
||||
|
||||
### Bit strings
|
||||
|
||||
Bit string with a base size of 3:
|
||||
|
||||
<<_ :: 3>>
|
||||
<< _ :: 3 >>
|
||||
|
||||
Bit string with a unit size of 8:
|
||||
|
||||
<<_ :: _ * 8>>
|
||||
<< _ :: _ * 8 >>
|
||||
|
||||
### Anonymous functions
|
||||
|
||||
Any anonymous function:
|
||||
|
||||
((...) -> any)
|
||||
or
|
||||
(... -> any)
|
||||
|
||||
Anonymous function with arity of zero:
|
||||
@@ -61,27 +100,98 @@ defmodule Kernel.Typespec do
|
||||
Anonymous function with some arity:
|
||||
|
||||
((type, type) -> type)
|
||||
or
|
||||
(type, type -> type)
|
||||
|
||||
## Built-in types
|
||||
|
||||
Built-in type | Defined as
|
||||
:-------------------- | :---------
|
||||
`term` | `any`
|
||||
`binary` | `<< _ :: _ * 8 >>`
|
||||
`bitstring` | `<< _ :: _ * 1 >>`
|
||||
`boolean` | `false` | `true`
|
||||
`byte` | `0..255`
|
||||
`char` | `0..0xffff`
|
||||
`number` | `integer` | `float`
|
||||
`list` | `[any]`
|
||||
`maybe_improper_list` | `maybe_improper_list(any, any)`
|
||||
`nonempty_list` | `nonempty_list(any)`
|
||||
`iodata` | `iolist` | `binary`
|
||||
`iolist` | `maybe_improper_list(byte` | `binary` | `iolist, binary` | `[])`
|
||||
`module` | `atom`
|
||||
`mfa` | `{ atom, atom, arity }`
|
||||
`arity` | `0..255`
|
||||
`node` | `atom`
|
||||
`timeout` | `:infinity` | `non_neg_integer`
|
||||
`no_return` | `none`
|
||||
`fun` | `(... -> any)`
|
||||
|
||||
Some built-in types cannot be expressed with valid syntax according to the
|
||||
language defined above.
|
||||
|
||||
Built-in type | Can be interpreted as
|
||||
:---------------- | :--------------------
|
||||
`non_neg_integer` | `0..`
|
||||
`pos_integer` | `1..`
|
||||
`neg_integer` | `..-1`
|
||||
|
||||
Types defined in other modules are referred to as "remote types", they are
|
||||
referenced as `Module.type_name` (ex. `Enum.t` or `String.t`).
|
||||
|
||||
## Defining a type
|
||||
|
||||
@type type_name :: type
|
||||
@typep type_name :: type
|
||||
@opaque type_name :: type
|
||||
|
||||
A type defined with `@typep` is private. An opaque type, defined with
|
||||
`@opaque` is a type where the internal structure of the type will not be
|
||||
visible, but the type is still public.
|
||||
|
||||
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 }]
|
||||
|
||||
Types can also be defined for records, see `defrecord/3`.
|
||||
|
||||
## Defining a specification
|
||||
|
||||
@spec function_name(type1, type2) :: return_type
|
||||
@callback function_name(type1, type2) :: return_type
|
||||
|
||||
Callbacks are used to define the callbacks functions of behaviours (see
|
||||
`Behaviour`).
|
||||
|
||||
Guards can be used to restrict type variables given as arguments to the
|
||||
function.
|
||||
|
||||
@spec function(arg) :: [arg] when arg: atom
|
||||
|
||||
Type variables with no restriction can also be defined.
|
||||
|
||||
@spec function(arg) :: [arg] when arg: var
|
||||
|
||||
Specifications can be overloaded just like ordinary functions.
|
||||
|
||||
@spec function(integer) :: atom
|
||||
@spec function(atom) :: integer
|
||||
|
||||
## Notes
|
||||
|
||||
Elixir discourages the use of type `string()` as it might be confused
|
||||
with binaries which are referred to as "strings" in Elixir (as opposed to
|
||||
character lists). In order to use the type that is called `string()` in Erlang,
|
||||
one has to use the `char_list()` type which is a synonym for `string()`. If you
|
||||
use `string()`, you'll get a warning from the compiler.
|
||||
Elixir discourages the use of type `string` as it might be confused with
|
||||
binaries which are referred to as "strings" in Elixir (as opposed to character
|
||||
lists). In order to use the type that is called `string` in Erlang, one has to
|
||||
use the `char_list` type which is a synonym for `string`. If you use `string`,
|
||||
you'll get a warning from the compiler.
|
||||
|
||||
If you want to refer to the "string" type (the one operated by functions in the
|
||||
String module), use `String.t()` type instead.
|
||||
|
||||
See http://www.erlang.org/doc/reference_manual/typespec.html
|
||||
for more information.
|
||||
If you want to refer to the "string" type (the one operated on by functions in
|
||||
the `String` module), use `String.t` type instead.
|
||||
"""
|
||||
|
||||
@doc """
|
||||
Defines a type.
|
||||
This macro is the one responsible for handling the attribute `@type`.
|
||||
This macro is responsible for handling the attribute `@type`.
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -90,13 +200,13 @@ defmodule Kernel.Typespec do
|
||||
"""
|
||||
defmacro deftype(type) do
|
||||
quote do
|
||||
Kernel.Typespec.deftype(:type, unquote(Macro.escape type), __ENV__)
|
||||
Kernel.Typespec.deftype(:type, unquote(Macro.escape(type, unquote: true)), __ENV__)
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Defines an opaque type.
|
||||
This macro is the one responsible for handling the attribute `@opaque`.
|
||||
This macro is responsible for handling the attribute `@opaque`.
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -105,13 +215,13 @@ defmodule Kernel.Typespec do
|
||||
"""
|
||||
defmacro defopaque(type) do
|
||||
quote do
|
||||
Kernel.Typespec.deftype(:opaque, unquote(Macro.escape type), __ENV__)
|
||||
Kernel.Typespec.deftype(:opaque, unquote(Macro.escape(type, unquote: true)), __ENV__)
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Defines a private type.
|
||||
This macro is the one responsible for handling the attribute `@typep`.
|
||||
This macro is responsible for handling the attribute `@typep`.
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -120,13 +230,13 @@ defmodule Kernel.Typespec do
|
||||
"""
|
||||
defmacro deftypep(type) do
|
||||
quote do
|
||||
Kernel.Typespec.deftype(:typep, unquote(Macro.escape type), __ENV__)
|
||||
Kernel.Typespec.deftype(:typep, unquote(Macro.escape(type, unquote: true)), __ENV__)
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Defines a spec.
|
||||
This macro is the one responsible for handling the attribute `@spec`.
|
||||
This macro is responsible for handling the attribute `@spec`.
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -135,13 +245,13 @@ defmodule Kernel.Typespec do
|
||||
"""
|
||||
defmacro defspec(spec) do
|
||||
quote do
|
||||
Kernel.Typespec.defspec(:spec, unquote(Macro.escape spec), __ENV__)
|
||||
Kernel.Typespec.defspec(:spec, unquote(Macro.escape(spec, unquote: true)), __ENV__)
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Defines a callback.
|
||||
This macro is the one responsible for handling the attribute `@callback`.
|
||||
This macro is responsible for handling the attribute `@callback`.
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -150,7 +260,7 @@ defmodule Kernel.Typespec do
|
||||
"""
|
||||
defmacro defcallback(spec) do
|
||||
quote do
|
||||
Kernel.Typespec.defspec(:callback, unquote(Macro.escape spec), __ENV__)
|
||||
Kernel.Typespec.defspec(:callback, unquote(Macro.escape(spec, unquote: true)), __ENV__)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -188,7 +298,7 @@ defmodule Kernel.Typespec do
|
||||
if export do
|
||||
Module.add_doc(module, caller.line, kind, { name, arity }, doc)
|
||||
else
|
||||
:elixir_errors.warn "#{caller.file}:#{caller.line}: type #{name}/#{arity} is private, " <>
|
||||
:elixir_errors.warn caller.line, caller.file, "type #{name}/#{arity} is private, " <>
|
||||
"@typedoc's are always discarded for private types\n"
|
||||
end
|
||||
end
|
||||
@@ -217,8 +327,8 @@ defmodule Kernel.Typespec do
|
||||
"""
|
||||
def defines_type?(module, name, arity) do
|
||||
finder = &match?({ ^name, _, vars } when length(vars) == arity, &1)
|
||||
:lists.any(finder, Module.get_attribute(module, :type)) or
|
||||
:lists.any(finder, Module.get_attribute(module, :opaque))
|
||||
Enum.any?(Module.get_attribute(module, :type), finder) or
|
||||
Enum.any?(Module.get_attribute(module, :opaque), finder)
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -227,7 +337,7 @@ defmodule Kernel.Typespec do
|
||||
"""
|
||||
def defines_spec?(module, name, arity) do
|
||||
tuple = { name, arity }
|
||||
:lists.any(&match?(^tuple, &1), Module.get_attribute(module, :spec))
|
||||
Enum.any?(Module.get_attribute(module, :spec), &match?(^tuple, &1))
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -236,15 +346,28 @@ defmodule Kernel.Typespec do
|
||||
"""
|
||||
def defines_callback?(module, name, arity) do
|
||||
tuple = { name, arity }
|
||||
:lists.any(&match?(^tuple, &1), Module.get_attribute(module, :callback))
|
||||
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
|
||||
args = lc arg inlist args, do: typespec_to_ast(arg)
|
||||
{ :::, [line: line], [{ name, [line: line], args }, typespec_to_ast(result)] }
|
||||
meta = [line: line]
|
||||
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 } })
|
||||
|
||||
result = if vars == [] do
|
||||
typespec_to_ast(result)
|
||||
else
|
||||
{ :when, meta, [typespec_to_ast(result), vars] }
|
||||
end
|
||||
|
||||
{ :::, meta, [body, result] }
|
||||
end
|
||||
|
||||
def spec_to_ast(name, { :type, line, :fun, [] }) do
|
||||
@@ -252,15 +375,25 @@ defmodule Kernel.Typespec do
|
||||
end
|
||||
|
||||
def spec_to_ast(name, { :type, line, :bounded_fun, [{ :type, _, :fun, [{ :type, _, :product, args }, result] }, constraints] }) do
|
||||
[h|t] =
|
||||
lc {:type, line, :constraint, [{:atom, _, :is_subtype}, [var, type]]} inlist constraints do
|
||||
{ :is_subtype, [line: line], [typespec_to_ast(var), typespec_to_ast(type)] }
|
||||
guards =
|
||||
lc {:type, _, :constraint, [{:atom, _, :is_subtype}, [{ :var, _, var }, type]]} inlist constraints do
|
||||
{ var, typespec_to_ast(type) }
|
||||
end
|
||||
|
||||
args = lc arg inlist args, do: typespec_to_ast(arg)
|
||||
guards = Enum.reduce t, h, fn(x, acc) -> { :and, line, [acc, x] } end
|
||||
meta = [line: line]
|
||||
|
||||
{ :::, [line: line], [{ :when, [line: line], [{ name, [line: line], args }, guards] }, typespec_to_ast(result)] }
|
||||
vars = args ++ [result]
|
||||
|> 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)
|
||||
|
||||
{ :::, meta, [
|
||||
{ name, [line: line], args },
|
||||
{ :when, meta, [typespec_to_ast(result), guards ++ vars] }
|
||||
] }
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -281,10 +414,10 @@ defmodule Kernel.Typespec do
|
||||
@doc """
|
||||
Returns all type docs available from the module's beam code.
|
||||
|
||||
It is returned as a list of tuples where the first element is the pair of type
|
||||
The result is returned as a list of tuples where the first element is the pair of type
|
||||
name and arity and the second element is the documentation.
|
||||
|
||||
The module has to have a corresponding beam file on the disk which can be
|
||||
The module must have a corresponding beam file which can be
|
||||
located by the runtime system.
|
||||
"""
|
||||
@spec beam_typedocs(module | binary) :: [tuple] | nil
|
||||
@@ -301,10 +434,10 @@ defmodule Kernel.Typespec do
|
||||
@doc """
|
||||
Returns all types available from the module's beam code.
|
||||
|
||||
It is returned as a list of tuples where the first
|
||||
The result is returned as a list of tuples where the first
|
||||
element is the type (`:typep`, `:type` and `:opaque`).
|
||||
|
||||
The module has to have a corresponding beam file on the disk which can be
|
||||
The module must have a corresponding beam file which can be
|
||||
located by the runtime system.
|
||||
"""
|
||||
@spec beam_types(module | binary) :: [tuple] | nil
|
||||
@@ -317,7 +450,7 @@ defmodule Kernel.Typespec do
|
||||
lc { :attribute, _, kind, { name, _, args } = type } inlist abstract_code, kind in [:opaque, :type] do
|
||||
cond do
|
||||
kind == :opaque -> { :opaque, type }
|
||||
:lists.member({ name, length(args) }, exported_types) -> { :type, type }
|
||||
{ name, length(args) } in exported_types -> { :type, type }
|
||||
true -> { :typep, type }
|
||||
end
|
||||
end
|
||||
@@ -329,10 +462,10 @@ defmodule Kernel.Typespec do
|
||||
@doc """
|
||||
Returns all specs available from the module's beam code.
|
||||
|
||||
It is returned as a list of tuples where the first
|
||||
The result is returned as a list of tuples where the first
|
||||
element is spec name and arity and the second is the spec.
|
||||
|
||||
The module has to have a corresponding beam file on the disk which can be
|
||||
The module must have a corresponding beam file which can be
|
||||
located by the runtime system.
|
||||
"""
|
||||
@spec beam_specs(module | binary) :: [tuple] | nil
|
||||
@@ -343,10 +476,10 @@ defmodule Kernel.Typespec do
|
||||
@doc """
|
||||
Returns all callbacks available from the module's beam code.
|
||||
|
||||
It is returned as a list of tuples where the first
|
||||
The result is returned as a list of tuples where the first
|
||||
element is spec name and arity and the second is the spec.
|
||||
|
||||
The module has to have a corresponding beam file on the disk
|
||||
The module must have a corresponding beam file
|
||||
which can be located by the runtime system.
|
||||
"""
|
||||
@spec beam_callbacks(module | binary) :: [tuple] | nil
|
||||
@@ -386,21 +519,7 @@ defmodule Kernel.Typespec do
|
||||
## Macro callbacks
|
||||
|
||||
@doc false
|
||||
def deftype(kind, { :::, _, [type, definition] }, caller) do
|
||||
do_deftype(kind, type, definition, caller)
|
||||
end
|
||||
|
||||
def deftype(kind, {name, _meta, args} = type, caller)
|
||||
when is_atom(name) and not is_list(args) do
|
||||
do_deftype(kind, type, { :term, [line: caller.line], nil }, caller)
|
||||
end
|
||||
|
||||
def deftype(_kind, other, caller) do
|
||||
type_spec = Macro.to_string(other)
|
||||
compile_error caller, "invalid type specification #{type_spec}"
|
||||
end
|
||||
|
||||
defp do_deftype(kind, { name, _, args }, definition, caller) do
|
||||
def deftype(kind, { :::, _, [{ name, _, args }, definition] }, caller) when is_atom(name) and name != ::: do
|
||||
args =
|
||||
if is_atom(args) do
|
||||
[]
|
||||
@@ -417,20 +536,29 @@ defmodule Kernel.Typespec do
|
||||
define_type(caller, kind, type)
|
||||
end
|
||||
|
||||
@doc false
|
||||
def defspec(type, {:::, _, [{ :when, _, [{ name, meta, args }, constraints_guard] }, return] }, caller) do
|
||||
if is_atom(args), do: args = []
|
||||
constraints = guard_to_constraints(constraints_guard, caller)
|
||||
spec = { :type, line(meta), :fun, fn_args(meta, args, return, Keyword.keys(constraints), caller) }
|
||||
spec = { :type, line(meta), :bounded_fun, [spec, Keyword.values(constraints)] }
|
||||
code = { { name, Kernel.length(args) }, spec }
|
||||
Module.compile_typespec(caller.module, type, code)
|
||||
code
|
||||
def deftype(_kind, other, caller) do
|
||||
type_spec = Macro.to_string(other)
|
||||
compile_error caller, "invalid type specification: #{type_spec}"
|
||||
end
|
||||
|
||||
def defspec(type, {:::, _, [{ name, meta, args }, return]}, caller) do
|
||||
@doc false
|
||||
def defspec(type, { :::, meta, [{ name, _, args }, return_and_guard] }, caller) when is_atom(name) and name != ::: do
|
||||
if is_atom(args), do: args = []
|
||||
spec = { :type, line(meta), :fun, fn_args(meta, args, return, [], caller) }
|
||||
{ return, guard } = split_return_and_guard(return_and_guard)
|
||||
|
||||
unless Keyword.keyword?(guard) do
|
||||
guard = Macro.to_string(guard)
|
||||
compile_error caller, "expected keywords as guard in function type specification, got: #{guard}"
|
||||
end
|
||||
|
||||
vars = Keyword.keys(guard)
|
||||
constraints = guard_to_constraints(guard, vars, meta, caller)
|
||||
|
||||
spec = { :type, line(meta), :fun, fn_args(meta, args, return, vars, caller) }
|
||||
if constraints != [] do
|
||||
spec = { :type, line(meta), :bounded_fun, [spec, constraints] }
|
||||
end
|
||||
|
||||
code = { { name, Kernel.length(args) }, spec }
|
||||
Module.compile_typespec(caller.module, type, code)
|
||||
code
|
||||
@@ -438,23 +566,67 @@ defmodule Kernel.Typespec do
|
||||
|
||||
def defspec(_type, other, caller) do
|
||||
spec = Macro.to_string(other)
|
||||
compile_error caller, "invalid function type specification #{spec}"
|
||||
compile_error caller, "invalid function type specification: #{spec}"
|
||||
end
|
||||
|
||||
defp guard_to_constraints({ :is_subtype, meta, [{ name, _, _ }, type] }, caller) do
|
||||
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)
|
||||
contraints = [{ :atom, line, :is_subtype }, [{:var, line, name}, typespec(type, [], caller)]]
|
||||
[{ name, { :type, line, :constraint, contraints } }]
|
||||
end
|
||||
|
||||
defp guard_to_constraints({ :and, _, [left, right] }, caller) do
|
||||
guard_to_constraints(left, caller) ++ guard_to_constraints(right, caller)
|
||||
Enum.reduce(guard, [], fn
|
||||
{ _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 }
|
||||
[type|acc]
|
||||
end) |> Enum.reverse
|
||||
end
|
||||
|
||||
## To AST conversion
|
||||
|
||||
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
|
||||
Enum.flat_map(args, &collect_vars/1)
|
||||
end
|
||||
|
||||
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
|
||||
collect_vars(type)
|
||||
end
|
||||
|
||||
defp collect_vars({ :paren_type, _line, [type] }) do
|
||||
collect_vars(type)
|
||||
end
|
||||
|
||||
defp collect_vars({ :var, _line, var }) do
|
||||
[erl_to_ex_var(var)]
|
||||
end
|
||||
|
||||
defp collect_vars(_) do
|
||||
[]
|
||||
end
|
||||
|
||||
defp typespec_to_ast({ :type, line, :tuple, :any }) do
|
||||
typespec_to_ast({:type, line, :tuple, []})
|
||||
{ :tuple, [line: line], [] }
|
||||
end
|
||||
|
||||
defp typespec_to_ast({ :type, line, :tuple, args }) do
|
||||
@@ -462,8 +634,8 @@ defmodule Kernel.Typespec do
|
||||
{ :{}, [line: line], args }
|
||||
end
|
||||
|
||||
defp typespec_to_ast({ :type, _line, :list, [arg] }) do
|
||||
case unpack_typespec_kw(arg, []) do
|
||||
defp typespec_to_ast({ :type, _line, :list, [{ :type, _, :union, unions } = arg] }) do
|
||||
case unpack_typespec_kw(unions, []) do
|
||||
{ :ok, ast } -> ast
|
||||
:error -> [typespec_to_ast(arg)]
|
||||
end
|
||||
@@ -487,17 +659,16 @@ defmodule Kernel.Typespec do
|
||||
|
||||
defp typespec_to_ast({ :type, line, :union, args }) do
|
||||
args = lc arg inlist args, do: typespec_to_ast(arg)
|
||||
Enum.reduce tl(args), hd(args),
|
||||
fn(arg, expr) -> { :|, [line: line], [expr, arg] } end
|
||||
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, [line: line], typespec_to_ast(result)}] }
|
||||
[{ :->, [line: line], [args, typespec_to_ast(result)] }]
|
||||
end
|
||||
|
||||
defp typespec_to_ast({ :type, line, :fun, [args, result] }) do
|
||||
{ :->, [line: line], [{[typespec_to_ast(args)], [line: line], typespec_to_ast(result)}] }
|
||||
[{ :->, [line: line], [[typespec_to_ast(args)], typespec_to_ast(result)] }]
|
||||
end
|
||||
|
||||
defp typespec_to_ast({ :type, line, :fun, [] }) do
|
||||
@@ -514,14 +685,11 @@ defmodule Kernel.Typespec do
|
||||
end
|
||||
|
||||
defp typespec_to_ast({ :var, line, var }) do
|
||||
var =
|
||||
case atom_to_binary(var) do
|
||||
<<"_", c :: [binary, size(1)], rest :: binary>> ->
|
||||
binary_to_atom("_#{String.downcase(c)}#{rest}")
|
||||
<<c :: [binary, size(1)], rest :: binary>> ->
|
||||
binary_to_atom("#{String.downcase(c)}#{rest}")
|
||||
end
|
||||
{ var, line, nil }
|
||||
{ erl_to_ex_var(var), line, nil }
|
||||
end
|
||||
|
||||
defp typespec_to_ast({ :op, line, op, arg }) do
|
||||
{ op, [line: line], [typespec_to_ast(arg)] }
|
||||
end
|
||||
|
||||
# Special shortcut(s)
|
||||
@@ -544,16 +712,16 @@ defmodule Kernel.Typespec do
|
||||
end
|
||||
|
||||
defp typespec_to_ast({ :typed_record_field,
|
||||
{ :record_field, line, { :atom, line1, name }},
|
||||
{ :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
|
||||
|
||||
@@ -563,6 +731,15 @@ defmodule Kernel.Typespec do
|
||||
|
||||
defp typespec_to_ast(other), do: other
|
||||
|
||||
defp erl_to_ex_var(var) do
|
||||
case atom_to_binary(var) do
|
||||
<<"_", c :: [binary, size(1)], rest :: binary>> ->
|
||||
binary_to_atom("_#{String.downcase(c)}#{rest}")
|
||||
<<c :: [binary, size(1)], rest :: binary>> ->
|
||||
binary_to_atom("#{String.downcase(c)}#{rest}")
|
||||
end
|
||||
end
|
||||
|
||||
## From AST conversion
|
||||
|
||||
defp line(meta) do
|
||||
@@ -574,7 +751,7 @@ defmodule Kernel.Typespec do
|
||||
|
||||
# Handle unions
|
||||
defp typespec({ :|, meta, [_, _] } = exprs, vars, caller) do
|
||||
exprs = Enum.reverse(collect_union(exprs))
|
||||
exprs = collect_union(exprs)
|
||||
union = lc e inlist exprs, do: typespec(e, vars, caller)
|
||||
{ :type, line(meta), :union, union }
|
||||
end
|
||||
@@ -593,7 +770,7 @@ defmodule Kernel.Typespec do
|
||||
end
|
||||
|
||||
# Handle ranges
|
||||
defp typespec({:"..", meta, args}, vars, caller) do
|
||||
defp typespec({:.., meta, args}, vars, caller) do
|
||||
typespec({:range, meta, args}, vars, caller)
|
||||
end
|
||||
|
||||
@@ -608,17 +785,13 @@ defmodule Kernel.Typespec do
|
||||
end
|
||||
|
||||
# Handle funs
|
||||
defp typespec({:->, meta, [{[{:fun, _, arguments}], cmeta, return}]}, vars, caller) when is_list(arguments) do
|
||||
typespec({:->, meta, [{arguments, cmeta, return}]}, vars, caller)
|
||||
end
|
||||
|
||||
defp typespec({:->, meta, [{arguments, _, return}]}, vars, caller) when is_list(arguments) do
|
||||
defp typespec([{:->, meta, [arguments, return]}], vars, caller) when is_list(arguments) do
|
||||
args = fn_args(meta, arguments, return, vars, caller)
|
||||
{ :type, line(meta), :fun, args }
|
||||
end
|
||||
|
||||
# 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] }
|
||||
@@ -646,11 +819,7 @@ defmodule Kernel.Typespec do
|
||||
end
|
||||
|
||||
# Handle tuples
|
||||
defp typespec({:tuple, meta, atom}, vars, caller) when is_atom(atom) do
|
||||
typespec({:{}, meta, []}, vars, caller)
|
||||
end
|
||||
|
||||
defp typespec({:{}, meta, []}, _, _) do
|
||||
defp typespec({:tuple, meta, args}, _vars, _caller) when args == [] or is_atom(args) do
|
||||
{ :type, line(meta), :tuple, :any }
|
||||
end
|
||||
|
||||
@@ -659,6 +828,10 @@ defmodule Kernel.Typespec do
|
||||
{ :type, line(meta), :tuple, args }
|
||||
end
|
||||
|
||||
defp typespec({ left, right }, vars, caller) do
|
||||
typespec({ :{}, [], [left, right] }, vars, caller)
|
||||
end
|
||||
|
||||
# Handle blocks
|
||||
defp typespec({:__block__, _meta, [arg]}, vars, caller) do
|
||||
typespec(arg, vars, caller)
|
||||
@@ -666,7 +839,7 @@ defmodule Kernel.Typespec do
|
||||
|
||||
# Handle variables or local calls
|
||||
defp typespec({name, meta, atom}, vars, caller) when is_atom(atom) do
|
||||
if :lists.member(name, vars) do
|
||||
if name in vars do
|
||||
{ :var, line(meta), name }
|
||||
else
|
||||
typespec({name, meta, []}, vars, caller)
|
||||
@@ -675,7 +848,7 @@ defmodule Kernel.Typespec do
|
||||
|
||||
# Handle local calls
|
||||
defp typespec({:string, meta, arguments}, vars, caller) do
|
||||
IO.write "warning: string() type use is discouraged. For character lists, use " <>
|
||||
: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 }
|
||||
@@ -715,18 +888,14 @@ defmodule Kernel.Typespec do
|
||||
typespec({ :nonempty_list, [], [spec] }, vars, caller)
|
||||
end
|
||||
|
||||
defp typespec([h|t] = l, vars, caller) do
|
||||
union = Enum.reduce(t, validate_kw(h, l, caller), fn(x, acc) ->
|
||||
{ :|, [], [acc, validate_kw(x, l, caller)] }
|
||||
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] }
|
||||
end)
|
||||
typespec({ :list, [], [union] }, vars, caller)
|
||||
end
|
||||
|
||||
defp typespec(t, vars, caller) when is_tuple(t) do
|
||||
args = lc e inlist tuple_to_list(t), do: typespec(e, vars, caller)
|
||||
{ :type, 0, :tuple, args }
|
||||
end
|
||||
|
||||
## Helpers
|
||||
|
||||
defp compile_error(caller, desc) do
|
||||
@@ -738,12 +907,12 @@ defmodule Kernel.Typespec do
|
||||
{ :remote_type, line(meta), [ remote, name, arguments ] }
|
||||
end
|
||||
|
||||
defp collect_union({ :|, _, [a, b] }), do: [b|collect_union(a)]
|
||||
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(_, original, caller) do
|
||||
compile_error(caller, "unexpected list #{Macro.to_string original} in typespec")
|
||||
compile_error(caller, "unexpected list in typespec: #{Macro.to_string original}")
|
||||
end
|
||||
|
||||
defp fn_args(meta, args, return, vars, caller) do
|
||||
@@ -766,15 +935,12 @@ defmodule Kernel.Typespec do
|
||||
{:var, line(meta), name}
|
||||
end
|
||||
|
||||
defp unpack_typespec_kw({ :type, _, :union, [
|
||||
next,
|
||||
{ :type, _, :tuple, [{ :atom, _, atom }, type] }
|
||||
] }, acc) do
|
||||
unpack_typespec_kw(next, [{atom, typespec_to_ast(type)}|acc])
|
||||
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({ :type, _, :tuple, [{ :atom, _, atom }, type] }, acc) do
|
||||
{ :ok, [{atom, typespec_to_ast(type)}|acc] }
|
||||
defp unpack_typespec_kw([], acc) do
|
||||
{ :ok, :lists.reverse(acc) }
|
||||
end
|
||||
|
||||
defp unpack_typespec_kw(_, _acc) do
|
||||
|
||||
+33
-10
@@ -25,7 +25,9 @@ defmodule Keyword do
|
||||
|
||||
@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`
|
||||
@@ -114,7 +116,7 @@ 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_atom(key) do
|
||||
case :lists.keyfind(key, 1, keywords) do
|
||||
{ ^key, value } -> value
|
||||
false -> default
|
||||
@@ -209,6 +211,27 @@ defmodule Keyword do
|
||||
lc { _, value } inlist keywords, do: value
|
||||
end
|
||||
|
||||
@doc """
|
||||
Deletes the entry in the keyword list for a `key` with `value`.
|
||||
If no `key` with `value` exists, returns the keyword list unchanged.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Keyword.delete([a: 1, b: 2], :a, 1)
|
||||
[b: 2]
|
||||
|
||||
iex> Keyword.delete([a: 1, b: 2, a: 3], :a, 3)
|
||||
[a: 1, b: 2]
|
||||
|
||||
iex> Keyword.delete([b: 2], :a, 5)
|
||||
[b: 2]
|
||||
|
||||
"""
|
||||
@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
|
||||
end
|
||||
|
||||
@doc """
|
||||
Deletes all entries in the keyword list for a specific `key`.
|
||||
If the `key` does not exist, returns the keyword list unchanged.
|
||||
@@ -254,7 +277,7 @@ defmodule Keyword do
|
||||
Puts the given `value` under `key`.
|
||||
|
||||
If a previous value is already stored, all entries are
|
||||
removed and the value is overriden.
|
||||
removed and the value is overridden.
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -301,7 +324,7 @@ defmodule Keyword do
|
||||
|
||||
"""
|
||||
@spec equal?(t, t) :: boolean
|
||||
def equal?(left, right) do
|
||||
def equal?(left, right) when is_list(left) and is_list(right) do
|
||||
:lists.sort(left) == :lists.sort(right)
|
||||
end
|
||||
|
||||
@@ -316,7 +339,7 @@ defmodule Keyword do
|
||||
|
||||
"""
|
||||
@spec merge(t, t) :: t
|
||||
def merge(d1, d2) do
|
||||
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)
|
||||
end
|
||||
|
||||
@@ -333,7 +356,7 @@ defmodule Keyword do
|
||||
|
||||
"""
|
||||
@spec merge(t, t, (key, value, value -> value)) :: t
|
||||
def merge(d1, d2, fun) do
|
||||
def merge(d1, d2, fun) when is_list(d1) and is_list(d2) do
|
||||
do_merge(d2, d1, fun)
|
||||
end
|
||||
|
||||
@@ -433,7 +456,7 @@ defmodule Keyword do
|
||||
acc = { [], [] }
|
||||
|
||||
{ take, drop } = Enum.reduce dict, acc, fn({ k, v }, { take, drop }) ->
|
||||
case :lists.member(k, keys) do
|
||||
case k in keys do
|
||||
true -> { [{k, v}|take], drop }
|
||||
false -> { take, [{k, v}|drop] }
|
||||
end
|
||||
@@ -458,7 +481,7 @@ defmodule Keyword do
|
||||
|
||||
"""
|
||||
def take(dict, keys) do
|
||||
lc { k, _ } = tuple inlist dict, :lists.member(k, keys), do: tuple
|
||||
lc { k, _ } = tuple inlist dict, k in keys, do: tuple
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -477,7 +500,7 @@ defmodule Keyword do
|
||||
|
||||
"""
|
||||
def drop(dict, keys) do
|
||||
lc { k, _ } = tuple inlist dict, not :lists.member(k, keys), do: tuple
|
||||
lc { k, _ } = tuple inlist dict, not k in keys, do: tuple
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -505,7 +528,7 @@ defmodule Keyword do
|
||||
{1,[]}
|
||||
|
||||
"""
|
||||
def pop(dict, key, default // nil) do
|
||||
def pop(dict, key, default \\ nil) do
|
||||
{ get(dict, key, default), delete(dict, key) }
|
||||
end
|
||||
|
||||
@@ -534,7 +557,7 @@ defmodule Keyword do
|
||||
{1,[a: 2]}
|
||||
|
||||
"""
|
||||
def pop_first(dict, key, default // nil) do
|
||||
def pop_first(dict, key, default \\ nil) do
|
||||
{ get(dict, key, default), delete_first(dict, key) }
|
||||
end
|
||||
end
|
||||
|
||||
+149
-19
@@ -4,6 +4,12 @@ defmodule List do
|
||||
and cannot be part of the Enum protocol. In general,
|
||||
favor using the Enum API instead of List.
|
||||
|
||||
Some functions in this module expect an index. Index
|
||||
access for list is linear. Negative indexes are also
|
||||
supported but they imply the list will be iterated twice,
|
||||
one to calculate the proper index and another to the
|
||||
operation.
|
||||
|
||||
A decision was taken to delegate most functions to
|
||||
Erlang's standard library but follow Elixir's convention
|
||||
of receiving the target (in this case, a list) as the
|
||||
@@ -13,9 +19,9 @@ defmodule List do
|
||||
@compile :inline_list_funcs
|
||||
|
||||
@doc """
|
||||
Deletes the given item from the list. Returns a list without the item.
|
||||
If the item occurs more than once in the list, just the first occurrence
|
||||
is removed.
|
||||
Deletes the given item from the list. Returns a list without
|
||||
the item. If the item occurs more than once in the list, just
|
||||
the first occurrence is removed.
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -26,6 +32,7 @@ defmodule List do
|
||||
[1, 2, 3]
|
||||
|
||||
"""
|
||||
@spec delete(list, any) :: list
|
||||
def delete(list, item) do
|
||||
:lists.delete(item, list)
|
||||
end
|
||||
@@ -41,6 +48,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
|
||||
:lists.duplicate(n, elem)
|
||||
end
|
||||
@@ -54,12 +62,13 @@ defmodule List do
|
||||
[1,2,3]
|
||||
|
||||
"""
|
||||
@spec flatten(deep_list) :: list when deep_list: [any | deep_list]
|
||||
def flatten(list) do
|
||||
:lists.flatten(list)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Flattens the given `list` of nested lists.
|
||||
Flattens the given `list` of nested lists.
|
||||
The list `tail` will be added at the end of
|
||||
the flattened list.
|
||||
|
||||
@@ -69,6 +78,7 @@ defmodule List do
|
||||
[1,2,3,4,5]
|
||||
|
||||
"""
|
||||
@spec flatten(deep_list, [elem]) :: [elem] when elem: var, deep_list: [elem | deep_list]
|
||||
def flatten(list, tail) do
|
||||
:lists.flatten(list, tail)
|
||||
end
|
||||
@@ -86,6 +96,7 @@ defmodule List do
|
||||
2
|
||||
|
||||
"""
|
||||
@spec foldl([elem], acc, (elem, acc -> acc)) :: acc when elem: var, acc: var
|
||||
def foldl(list, acc, function) when is_list(list) and is_function(function) do
|
||||
:lists.foldl(function, acc, list)
|
||||
end
|
||||
@@ -100,10 +111,28 @@ defmodule List do
|
||||
-2
|
||||
|
||||
"""
|
||||
@spec foldr([elem], acc, (elem, acc -> acc)) :: acc when elem: var, acc: var
|
||||
def foldr(list, acc, function) when is_list(list) and is_function(function) do
|
||||
:lists.foldr(function, acc, list)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns the first element in `list` or `nil` if `list` is empty.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> List.first([])
|
||||
nil
|
||||
iex> List.first([1])
|
||||
1
|
||||
iex> List.first([1, 2, 3])
|
||||
1
|
||||
|
||||
"""
|
||||
@spec first([elem]) :: nil | elem when elem: var
|
||||
def first([]), do: nil
|
||||
def first([h|_]), do: h
|
||||
|
||||
@doc """
|
||||
Returns the last element in `list` or `nil` if `list` is empty.
|
||||
|
||||
@@ -117,11 +146,10 @@ defmodule List do
|
||||
3
|
||||
|
||||
"""
|
||||
def last([]), do: nil
|
||||
|
||||
def last(list) do
|
||||
:lists.last(list)
|
||||
end
|
||||
@spec last([elem]) :: nil | elem when elem: var
|
||||
def last([]), do: nil
|
||||
def last([h]), do: h
|
||||
def last([_|t]), do: last(t)
|
||||
|
||||
@doc """
|
||||
Receives a list of tuples and returns the first tuple
|
||||
@@ -140,7 +168,8 @@ defmodule List do
|
||||
nil
|
||||
|
||||
"""
|
||||
def keyfind(list, key, position, default // nil) do
|
||||
@spec keyfind([tuple], any, non_neg_integer, any) :: any
|
||||
def keyfind(list, key, position, default \\ nil) do
|
||||
:lists.keyfind(key, position + 1, list) || default
|
||||
end
|
||||
|
||||
@@ -161,6 +190,7 @@ defmodule List do
|
||||
false
|
||||
|
||||
"""
|
||||
@spec keymember?([tuple], any, non_neg_integer) :: any
|
||||
def keymember?(list, key, position) do
|
||||
:lists.keymember(key, position + 1, list)
|
||||
end
|
||||
@@ -175,6 +205,7 @@ defmodule List do
|
||||
[a: 3, b: 2]
|
||||
|
||||
"""
|
||||
@spec keyreplace([tuple], any, non_neg_integer, tuple) :: [tuple]
|
||||
def keyreplace(list, key, position, new_tuple) do
|
||||
:lists.keyreplace(key, position + 1, list, new_tuple)
|
||||
end
|
||||
@@ -192,6 +223,7 @@ defmodule List do
|
||||
[a: 5, b: 3, c: 1]
|
||||
|
||||
"""
|
||||
@spec keysort([tuple], non_neg_integer) :: [tuple]
|
||||
def keysort(list, position) do
|
||||
:lists.keysort(position + 1, list)
|
||||
end
|
||||
@@ -210,6 +242,7 @@ defmodule List do
|
||||
[a: 1, b: 2, c: 3]
|
||||
|
||||
"""
|
||||
@spec keystore([tuple], any, non_neg_integer, tuple) :: [tuple]
|
||||
def keystore(list, key, position, new_tuple) do
|
||||
:lists.keystore(key, position + 1, list, new_tuple)
|
||||
end
|
||||
@@ -222,15 +255,16 @@ defmodule List do
|
||||
## Examples
|
||||
|
||||
iex> List.keydelete([a: 1, b: 2], :a, 0)
|
||||
[{ :b, 2 }]
|
||||
[b: 2]
|
||||
|
||||
iex> List.keydelete([a: 1, b: 2], 2, 1)
|
||||
[{ :a, 1 }]
|
||||
[a: 1]
|
||||
|
||||
iex> List.keydelete([a: 1, b: 2], :c, 0)
|
||||
[{ :a, 1 }, { :b, 2 }]
|
||||
[a: 1, b: 2]
|
||||
|
||||
"""
|
||||
@spec keydelete([tuple], any, non_neg_integer) :: [tuple]
|
||||
def keydelete(list, key, position) do
|
||||
:lists.keydelete(key, position + 1, list)
|
||||
end
|
||||
@@ -252,6 +286,7 @@ defmodule List do
|
||||
[]
|
||||
|
||||
"""
|
||||
@spec wrap(list | any) :: list
|
||||
def wrap(list) when is_list(list) do
|
||||
list
|
||||
end
|
||||
@@ -276,6 +311,7 @@ defmodule List do
|
||||
[{1, 3, 5}]
|
||||
|
||||
"""
|
||||
@spec zip([list]) :: [tuple]
|
||||
def zip([]), do: []
|
||||
def zip(list_of_lists) when is_list(list_of_lists) do
|
||||
do_zip(list_of_lists, [])
|
||||
@@ -294,14 +330,15 @@ defmodule List do
|
||||
[[1, 2, 3], [:a, :b, :c]]
|
||||
|
||||
"""
|
||||
@spec unzip([tuple]) :: [list]
|
||||
def unzip(list) when is_list(list) do
|
||||
:lists.map &tuple_to_list/1, zip(list)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns a list with `value` inserted at the specified `index`. Note that `index`
|
||||
is capped at the list length. Negative indices indicate an offset from the
|
||||
end of the list.
|
||||
Returns a list with `value` inserted at the specified `index`.
|
||||
Note that `index` is capped at the list length. Negative indices
|
||||
indicate an offset from the end of the list.
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -318,6 +355,7 @@ defmodule List do
|
||||
[0, 1, 2, 3]
|
||||
|
||||
"""
|
||||
@spec insert_at(list, integer, any) :: list
|
||||
def insert_at(list, index, value) do
|
||||
if index < 0 do
|
||||
do_insert_at(list, length(list) + index + 1, value)
|
||||
@@ -327,9 +365,9 @@ defmodule List do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns a list with a replaced value at the specified `index`. Negative indices
|
||||
indicate an offset from the end of the list. If `index` is out of bounds, the
|
||||
original `list` is returned.
|
||||
Returns a list with a replaced value at the specified `index`.
|
||||
Negative indices indicate an offset from the end of the list.
|
||||
If `index` is out of bounds, the original `list` is returned.
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -346,6 +384,7 @@ defmodule List do
|
||||
[1, 2, 3]
|
||||
|
||||
"""
|
||||
@spec replace_at(list, integer, any) :: list
|
||||
def replace_at(list, index, value) do
|
||||
if index < 0 do
|
||||
do_replace_at(list, length(list) + index, value)
|
||||
@@ -354,6 +393,61 @@ defmodule List do
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns a list with an updated value at the specified `index`.
|
||||
Negative indices indicate an offset from the end of the list.
|
||||
If `index` is out of bounds, the original `list` is returned.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> List.update_at([1, 2, 3], 0, &(&1 + 10))
|
||||
[11, 2, 3]
|
||||
|
||||
iex> List.update_at([1, 2, 3], 10, &(&1 + 10))
|
||||
[1, 2, 3]
|
||||
|
||||
iex> List.update_at([1, 2, 3], -1, &(&1 + 10))
|
||||
[1, 2, 13]
|
||||
|
||||
iex> List.update_at([1, 2, 3], -10, &(&1 + 10))
|
||||
[1, 2, 3]
|
||||
|
||||
"""
|
||||
@spec update_at([elem], integer, (elem -> any)) :: list when elem: var
|
||||
def update_at(list, index, fun) do
|
||||
if index < 0 do
|
||||
do_update_at(list, length(list) + index, fun)
|
||||
else
|
||||
do_update_at(list, index, fun)
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Produces a new list by removing the value at the specified `index`.
|
||||
Negative indices indicate an offset from the end of the list.
|
||||
If `index` is out of bounds, the original `list` is returned.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> List.delete_at([1, 2, 3], 0)
|
||||
[2, 3]
|
||||
|
||||
iex List.delete_at([1, 2, 3], 10)
|
||||
[1, 2, 3]
|
||||
|
||||
iex> List.delete_at([1, 2, 3], -1)
|
||||
[1, 2]
|
||||
|
||||
"""
|
||||
@spec delete_at(list, integer) :: list
|
||||
def delete_at(list, index) do
|
||||
if index < 0 do
|
||||
do_delete_at(list, length(list) + index)
|
||||
else
|
||||
do_delete_at(list, index)
|
||||
end
|
||||
end
|
||||
|
||||
## Helpers
|
||||
|
||||
# replace_at
|
||||
@@ -388,6 +482,42 @@ defmodule List do
|
||||
[ h | do_insert_at(t, index - 1, value) ]
|
||||
end
|
||||
|
||||
# update_at
|
||||
|
||||
defp do_update_at([value|list], 0, fun) do
|
||||
[ fun.(value) | list ]
|
||||
end
|
||||
|
||||
defp do_update_at(list, index, _fun) when index < 0 do
|
||||
list
|
||||
end
|
||||
|
||||
defp do_update_at([h|t], index, fun) do
|
||||
[ h | do_update_at(t, index - 1, fun) ]
|
||||
end
|
||||
|
||||
defp do_update_at([], _index, _fun) do
|
||||
[]
|
||||
end
|
||||
|
||||
# delete_at
|
||||
|
||||
defp do_delete_at([], _index) do
|
||||
[]
|
||||
end
|
||||
|
||||
defp do_delete_at([_|t], 0) do
|
||||
t
|
||||
end
|
||||
|
||||
defp do_delete_at(list, index) when index < 0 do
|
||||
list
|
||||
end
|
||||
|
||||
defp do_delete_at([h|t], index) do
|
||||
[h | do_delete_at(t, index-1)]
|
||||
end
|
||||
|
||||
# zip
|
||||
|
||||
defp do_zip(list, acc) do
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -47,7 +47,7 @@ defmodule ListDict do
|
||||
def has_key?([{ _, _ }|t], key), do: has_key?(t, key)
|
||||
def has_key?([], _key), do: false
|
||||
|
||||
def get(dict, key, default // nil)
|
||||
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
|
||||
@@ -64,7 +64,7 @@ defmodule ListDict do
|
||||
end
|
||||
end
|
||||
|
||||
def pop(dict, key, default // nil) do
|
||||
def pop(dict, key, default \\ nil) do
|
||||
{ get(dict, key, default), delete(dict, key) }
|
||||
end
|
||||
|
||||
@@ -84,7 +84,7 @@ defmodule ListDict do
|
||||
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
|
||||
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
|
||||
@@ -94,7 +94,7 @@ defmodule ListDict do
|
||||
acc = { [], [] }
|
||||
|
||||
{take, drop} = Enum.reduce dict, acc, fn({ k, v }, { take, drop }) ->
|
||||
if :lists.member(k, keys) do
|
||||
if k in keys do
|
||||
{ [{k, v}|take], drop }
|
||||
else
|
||||
{ take, [{k, v}|drop] }
|
||||
@@ -105,11 +105,11 @@ defmodule ListDict do
|
||||
end
|
||||
|
||||
def take(dict, keys) do
|
||||
lc { k, _ } = tuple inlist dict, :lists.member(k, keys), do: tuple
|
||||
lc { k, _ } = tuple inlist dict, k in keys, do: tuple
|
||||
end
|
||||
|
||||
def drop(dict, keys) do
|
||||
lc { k, _ } = tuple inlist dict, not :lists.member(k, keys), do: tuple
|
||||
lc { k, _ } = tuple inlist dict, not k in keys, do: tuple
|
||||
end
|
||||
|
||||
def update!([{key, value}|dict], key, fun) do
|
||||
@@ -142,5 +142,10 @@ defmodule ListDict 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
|
||||
|
||||
+259
-193
@@ -2,55 +2,55 @@ import Kernel, except: [to_string: 1]
|
||||
|
||||
defmodule Macro do
|
||||
@moduledoc """
|
||||
This module provides conveniences for working with macros.
|
||||
Conveniences for working with macros.
|
||||
"""
|
||||
|
||||
@typedoc "Abstract Syntax Tree (AST) node"
|
||||
@type t :: { t, t } | { t, Keyword.t, t } | atom | number | binary | list
|
||||
@typedoc "Abstract Syntax Tree (AST)"
|
||||
@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] }
|
||||
|
||||
@binary_ops [:===, :!==,
|
||||
:==, :!=, :<=, :>=,
|
||||
:&&, :||, :<>, :++, :--, ://, :\\, :::, :<-, :.., :|>, :=~,
|
||||
:<, :>, :->,
|
||||
:+, :-, :*, :/, :=, :|, :.,
|
||||
:and, :or, :xor, :when, :in, :inlist, :inbits,
|
||||
:<<<, :>>>, :|||, :&&&, :^^^, :~~~]
|
||||
|
||||
@doc false
|
||||
defmacro binary_ops do
|
||||
[ :===, :!==,
|
||||
:==, :!=, :<=, :>=,
|
||||
:&&, :||, :<>, :++, :--, :**, ://, :::, :<-, :.., :|>, :=~,
|
||||
:<, :>, :->,
|
||||
:+, :-, :*, :/, :=, :|, :.,
|
||||
:and, :or, :xor, :when, :in, :inlist, :inbits,
|
||||
:<<<, :>>>, :|||, :&&&, :^^^, :~~~ ]
|
||||
end
|
||||
defmacro binary_ops, do: @binary_ops
|
||||
|
||||
@unary_ops [:!, :@, :^, :not, :+, :-, :~~~, :&]
|
||||
|
||||
@doc false
|
||||
defmacro unary_ops do
|
||||
[:!, :@, :^, :not, :+, :-, :~~~, :&]
|
||||
end
|
||||
defmacro unary_ops, do: @unary_ops
|
||||
|
||||
@spec binary_op_props(atom) :: { :left | :right, precedence :: integer }
|
||||
defp binary_op_props(o) do
|
||||
case o do
|
||||
::: -> {:right, 30}
|
||||
:when -> {:right, 40}
|
||||
o when o in [:inlist, :inbits] -> {:left, 50}
|
||||
:// -> {:right, 60}
|
||||
:| -> {:left, 70}
|
||||
o when o in [:<-, :inlist, :inbits, ://, :\\, :::] -> {:left, 40}
|
||||
:| -> {:right, 50}
|
||||
:when -> {:right, 70}
|
||||
:= -> {:right, 80}
|
||||
o when o in [:||, :|||, :or, :xor] -> {:left, 130}
|
||||
o when o in [:&&, :&&&, :and] -> {:left, 140}
|
||||
o when o in [:==, :!=, :<, :<=, :>=, :>, :=~, :===, :!==] -> {:left, 150}
|
||||
o when o in [:<-, :|>, :<<<, :>>>] -> {:right, 160}
|
||||
o when o in [:|>, :<<<, :>>>] -> {:right, 160}
|
||||
:in -> {:left, 170}
|
||||
:.. -> {:left, 200}
|
||||
o when o in [:++, :--, :.., :<>] -> {:right, 200}
|
||||
o when o in [:+, :-] -> {:left, 210}
|
||||
o when o in [:*, :/] -> {:left, 220}
|
||||
o when o in [:<>] -> {:right, 230}
|
||||
o when o in [:++, :--, :**] -> {:right, 240}
|
||||
:^^^ -> {:left, 250}
|
||||
:. -> {:left, 310}
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Breaks a pipeline expression into a list. Raises if
|
||||
the pipeline is ill-formed.
|
||||
Breaks a pipeline expression into a list.
|
||||
|
||||
Raises if the pipeline is ill-formed.
|
||||
"""
|
||||
@spec unpipe(Macro.t) :: [Macro.t]
|
||||
def unpipe({ :|> , _, [left, right] }) do
|
||||
@@ -62,11 +62,15 @@ defmodule Macro do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Pipes the given `expr` in to the `call_expr` as the
|
||||
Pipes `expr` into the `call_expr` as the
|
||||
argument in the given `position`.
|
||||
"""
|
||||
@spec pipe(Macro.t, Macro.t, integer) :: Macro.t | no_return
|
||||
def pipe(expr, call_args, integer // 0)
|
||||
def pipe(expr, call_args, integer \\ 0)
|
||||
|
||||
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) }
|
||||
@@ -82,33 +86,83 @@ defmodule Macro do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Receives an expression representing a possible definition
|
||||
and extracts its arguments. It returns a tuple with the
|
||||
function name and the arguments list or `:error` if not
|
||||
a valid call syntax.
|
||||
Recurs the quoted expression applying the given function to
|
||||
each metadata node.
|
||||
|
||||
This is useful for macros that want to provide the same
|
||||
argument syntax available in def/defp/defmacro and friends.
|
||||
This is often useful to remove information like lines and
|
||||
hygienic counters from the expression for either storage or
|
||||
comparison.
|
||||
|
||||
## Examples
|
||||
|
||||
extract_args(quote do: foo) == { :foo, [] }
|
||||
extract_args(quote do: foo()) == { :foo, [] }
|
||||
extract_args(quote do: foo(1, 2, 3)) == { :foo, [1, 2, 3] }
|
||||
extract_args(quote do: 1.(1, 2, 3)) == :error
|
||||
iex> quoted = quote line: 10, do: sample()
|
||||
{:sample, [line: 10], []}
|
||||
iex> Macro.update_meta(quoted, &Keyword.delete(&1, :line))
|
||||
{:sample, [], []}
|
||||
|
||||
"""
|
||||
@spec extract_args(Macro.t) :: { atom, [Macro.t] } | :error
|
||||
def extract_args(expr) do
|
||||
:elixir_clauses.extract_args(expr)
|
||||
@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) }
|
||||
end
|
||||
|
||||
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)
|
||||
end
|
||||
|
||||
def update_meta(other, _fun) do
|
||||
other
|
||||
end
|
||||
|
||||
@doc """
|
||||
Decomposes a local or remote call into its remote part (when provided),
|
||||
function name and argument list.
|
||||
|
||||
Returns `:error` when an invalid call syntax is provided.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Macro.decompose_call(quote do: foo)
|
||||
{ :foo, [] }
|
||||
|
||||
iex> Macro.decompose_call(quote do: foo())
|
||||
{ :foo, [] }
|
||||
|
||||
iex> Macro.decompose_call(quote do: 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] }
|
||||
|
||||
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 }
|
||||
|
||||
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(_),
|
||||
do: :error
|
||||
|
||||
@doc """
|
||||
Recursively escapes a value so it can be inserted
|
||||
into a syntax tree.
|
||||
|
||||
One may pass `unquote: true` to `escape/2`
|
||||
which leaves unquote statements unescaped, effectively
|
||||
which leaves `unquote` statements unescaped, effectively
|
||||
unquoting the contents on escape.
|
||||
|
||||
## Examples
|
||||
@@ -125,22 +179,23 @@ defmodule Macro do
|
||||
"""
|
||||
@spec escape(term) :: Macro.t
|
||||
@spec escape(term, Keyword.t) :: Macro.t
|
||||
def escape(expr, opts // []) do
|
||||
def escape(expr, opts \\ []) do
|
||||
elem(:elixir_quote.escape(expr, Keyword.get(opts, :unquote, false)), 0)
|
||||
end
|
||||
|
||||
@doc %S"""
|
||||
Unescape the given chars. This is the unescaping behavior
|
||||
used by default in Elixir single- and double-quoted strings.
|
||||
Check `unescape_string/2` for information on how to customize
|
||||
the escaping map.
|
||||
@doc ~S"""
|
||||
Unescape the given chars.
|
||||
|
||||
This is the unescaping behaviour used by default in Elixir
|
||||
single- and double-quoted strings. Check `unescape_string/2`
|
||||
for information on how to customize the escaping map.
|
||||
|
||||
In this setup, Elixir will escape the following: `\a`, `\b`,
|
||||
`\d`, `\e`, `\f`, `\n`, `\r`, `\s`, `\t` and `\v`. Octals are
|
||||
also escaped according to the latin1 set they represent.
|
||||
|
||||
This function is commonly used on sigil implementations
|
||||
(like `%r`, `%b` and others) which receive a raw, unescaped
|
||||
(like `~r`, `~s` and others) which receive a raw, unescaped
|
||||
string.
|
||||
|
||||
## Examples
|
||||
@@ -149,22 +204,23 @@ defmodule Macro do
|
||||
"example\n"
|
||||
|
||||
In the example above, we pass a string with `\n` escaped
|
||||
and we return a version with it unescaped.
|
||||
and return a version with it unescaped.
|
||||
"""
|
||||
@spec unescape_string(String.t) :: String.t
|
||||
def unescape_string(chars) 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
|
||||
as Elixir single- and double-quoted strings.
|
||||
|
||||
## Map
|
||||
|
||||
The map must be a function. The function receives an integer
|
||||
representing the number of the characters it wants to unescape.
|
||||
representing the codepoint of the character it wants to unescape.
|
||||
Here is the default mapping function implemented by Elixir:
|
||||
|
||||
def unescape_map(?a), do: ?\a
|
||||
@@ -185,16 +241,16 @@ defmodule Macro do
|
||||
## Octals
|
||||
|
||||
Octals will by default be escaped unless the map function
|
||||
returns false for ?0.
|
||||
returns `false` for `?0`.
|
||||
|
||||
## Hex
|
||||
|
||||
Hexadecimals will by default be escaped unless the map function
|
||||
returns false for ?x.
|
||||
returns `false` for `?x`.
|
||||
|
||||
## Examples
|
||||
|
||||
Using the unescape_map defined above is easy:
|
||||
Using the `unescape_map` function defined above is easy:
|
||||
|
||||
Macro.unescape_string "example\\n", &unescape_map(&1)
|
||||
|
||||
@@ -206,12 +262,13 @@ defmodule Macro do
|
||||
|
||||
@doc """
|
||||
Unescape the given tokens according to the default map.
|
||||
|
||||
Check `unescape_string/1` and `unescape_string/2` for more
|
||||
information about unescaping.
|
||||
|
||||
Only tokens that are binaries are unescaped, all others are
|
||||
ignored. This function is useful when implementing your own
|
||||
sigils. Check the implementation of `Kernel.sigil_b`
|
||||
sigils. Check the implementation of `Kernel.sigil_s/2`
|
||||
for examples.
|
||||
"""
|
||||
@spec unescape_tokens([Macro.t]) :: [Macro.t]
|
||||
@@ -221,6 +278,7 @@ defmodule Macro do
|
||||
|
||||
@doc """
|
||||
Unescape the given tokens according to the given map.
|
||||
|
||||
Check `unescape_tokens/1` and `unescape_string/2` for more information.
|
||||
"""
|
||||
@spec unescape_tokens([Macro.t], (non_neg_integer -> non_neg_integer | false)) :: [Macro.t]
|
||||
@@ -239,7 +297,7 @@ defmodule Macro do
|
||||
"""
|
||||
@spec to_string(Macro.t) :: String.t
|
||||
@spec to_string(Macro.t, (Macro.t, String.t -> String.t)) :: String.t
|
||||
def to_string(tree, fun // fn(_ast, string) -> string end)
|
||||
def to_string(tree, fun \\ fn(_ast, string) -> string end)
|
||||
|
||||
# Variables
|
||||
def to_string({ var, _, atom } = ast, fun) when is_atom(atom) do
|
||||
@@ -271,31 +329,43 @@ defmodule Macro do
|
||||
|
||||
# Tuple containers
|
||||
def to_string({ :{}, _, args } = ast, fun) do
|
||||
fun.(ast, "{" <> Enum.map_join(args, ", ", &to_string(&1, fun)) <> "}")
|
||||
tuple = "{" <> Enum.map_join(args, ", ", &to_string(&1, fun)) <> "}"
|
||||
fun.(ast, tuple)
|
||||
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
|
||||
if right != [] and Keyword.keyword?(right) do
|
||||
right = kw_list_to_string(right, fun)
|
||||
else
|
||||
right = fun.(ast, op_to_string(right, fun, :when, :right))
|
||||
end
|
||||
|
||||
fun.(ast, op_to_string(left, fun, :when, :left) <> " when " <> right)
|
||||
end
|
||||
|
||||
# Binary ops
|
||||
def to_string({ op, _, [left, right] } = ast, fun) when op in 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
|
||||
|
||||
@@ -310,7 +380,7 @@ defmodule Macro do
|
||||
fun.(ast, "not " <> to_string(arg, fun))
|
||||
end
|
||||
|
||||
def to_string({ op, _, [arg] } = ast, fun) when op in 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
|
||||
|
||||
@@ -322,7 +392,7 @@ defmodule Macro do
|
||||
# All other calls
|
||||
def to_string({ target, _, args } = ast, fun) when is_list(args) do
|
||||
{ list, last } = :elixir_utils.split_last(args)
|
||||
fun.(ast, case is_kw_blocks?(last) do
|
||||
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)
|
||||
end)
|
||||
@@ -335,25 +405,30 @@ defmodule Macro do
|
||||
|
||||
# Lists
|
||||
def to_string(list, fun) when is_list(list) do
|
||||
if Keyword.keyword?(list) do
|
||||
fun.(list, "[" <> kw_list_to_string(list, fun) <> "]")
|
||||
else
|
||||
fun.(list, "[" <> Enum.map_join(list, ", ", &to_string(&1, fun)) <> "]")
|
||||
end
|
||||
fun.(list, cond do
|
||||
list == [] ->
|
||||
"[]"
|
||||
:io_lib.printable_list(list) ->
|
||||
"'" <> Inspect.BitString.escape(String.from_char_list!(list), ?') <> "'"
|
||||
Keyword.keyword?(list) ->
|
||||
"[" <> kw_list_to_string(list, fun) <> "]"
|
||||
true ->
|
||||
"[" <> Enum.map_join(list, ", ", &to_string(&1, fun)) <> "]"
|
||||
end)
|
||||
end
|
||||
|
||||
# All other structures
|
||||
def to_string(other, fun), do: fun.(other, inspect(other, raw: true))
|
||||
def to_string(other, fun), do: fun.(other, inspect(other, records: false))
|
||||
|
||||
# Block keywords
|
||||
defmacrop kw_keywords, do: [:do, :catch, :rescue, :after, :else]
|
||||
@kw_keywords [:do, :catch, :rescue, :after, :else]
|
||||
|
||||
defp is_kw_blocks?([_|_] = kw) do
|
||||
Enum.all?(kw, &match?({x, _} when x in kw_keywords, &1))
|
||||
defp kw_blocks?([_|_] = kw) do
|
||||
Enum.all?(kw, &match?({x, _} when x in unquote(@kw_keywords), &1))
|
||||
end
|
||||
defp is_kw_blocks?(_), do: false
|
||||
defp kw_blocks?(_), do: false
|
||||
|
||||
defp module_to_string(atom, _fun) when is_atom(atom), do: inspect(atom, raw: true)
|
||||
defp module_to_string(atom, _fun) when is_atom(atom), do: inspect(atom, records: false)
|
||||
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)
|
||||
@@ -362,23 +437,25 @@ defmodule Macro do
|
||||
defp call_to_string(other, fun), do: to_string(other, fun)
|
||||
|
||||
defp call_to_string_with_args(target, args, fun) do
|
||||
{ list, last } = :elixir_utils.split_last(args)
|
||||
target = call_to_string(target, fun)
|
||||
args = args_to_string(args, fun)
|
||||
target <> "(" <> args <> ")"
|
||||
end
|
||||
|
||||
case last != [] and Keyword.keyword?(last) do
|
||||
true ->
|
||||
args = Enum.map_join(list, ", ", &to_string(&1, fun))
|
||||
if list != [], do: args = args <> ", "
|
||||
args = args <> kw_list_to_string(last, fun)
|
||||
target <> "(" <> args <> ")"
|
||||
false ->
|
||||
args = Enum.map_join(args, ", ", &to_string(&1, fun))
|
||||
target <> "(" <> args <> ")"
|
||||
defp args_to_string(args, fun) do
|
||||
{ list, last } = :elixir_utils.split_last(args)
|
||||
|
||||
if last != [] and Keyword.keyword?(last) do
|
||||
args = Enum.map_join(list, ", ", &to_string(&1, fun))
|
||||
if list != [], do: args = args <> ", "
|
||||
args <> kw_list_to_string(last, fun)
|
||||
else
|
||||
Enum.map_join(args, ", ", &to_string(&1, fun))
|
||||
end
|
||||
end
|
||||
|
||||
defp kw_blocks_to_string(kw, fun) do
|
||||
Enum.reduce(kw_keywords, " ", fn(x, acc) ->
|
||||
Enum.reduce(@kw_keywords, " ", fn(x, acc) ->
|
||||
case Keyword.has_key?(kw, x) do
|
||||
true -> acc <> kw_block_to_string(x, Keyword.get(kw, x), fun)
|
||||
false -> acc
|
||||
@@ -391,8 +468,8 @@ defmodule Macro do
|
||||
atom_to_binary(key) <> "\n " <> block <> "\n"
|
||||
end
|
||||
|
||||
defp block_to_string({ :->, _, exprs }, fun) do
|
||||
Enum.map_join(exprs, "\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)
|
||||
@@ -414,7 +491,7 @@ defmodule Macro do
|
||||
"(" <> to_string(expr, fun) <> ")"
|
||||
end
|
||||
|
||||
defp op_to_string({ op, _, [_, _] } = expr, fun, parent_op, side) when op in binary_ops do
|
||||
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
|
||||
@@ -432,8 +509,8 @@ 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 }) ->
|
||||
defp arrow_to_string(pairs, fun, paren \\ false) do
|
||||
Enum.map_join(pairs, "; ", fn({ :->, _, [left, right] }) ->
|
||||
left = comma_join_or_empty_paren(left, fun, paren)
|
||||
left <> "-> " <> to_string(right, fun)
|
||||
end)
|
||||
@@ -456,18 +533,19 @@ defmodule Macro do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Receives a AST node and expands it once. The following contents are expanded:
|
||||
Receives an AST node and expands it once.
|
||||
|
||||
The following contents are expanded:
|
||||
|
||||
* Macros (local or remote);
|
||||
* Aliases are expanded (if possible) and return atoms;
|
||||
* All pseudo-variables (`__FILE__`, `__MODULE__`, etc);
|
||||
* Pseudo-variables (`__ENV__`, `__MODULE__` and `__DIR__`);
|
||||
* Module attributes reader (`@foo`);
|
||||
|
||||
In case the expression cannot be expanded, it returns the expression
|
||||
If the expression cannot be expanded, it returns the expression
|
||||
itself. Notice that `expand_once/2` performs the expansion just
|
||||
once and it is not recursive. Check `expand/2` for expansion
|
||||
until the node no longer represents a macro and `expand_all/2`
|
||||
for recursive expansion.
|
||||
until the node can no longer be expanded.
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -512,7 +590,7 @@ defmodule Macro do
|
||||
end
|
||||
|
||||
The final module name will be `MyHelpers.Module` and not
|
||||
`My.Module`. With `Macro.expand`, such aliases are taken
|
||||
`My.Module`. With `Macro.expand/2`, such aliases are taken
|
||||
into consideration. Local and remote macros are also
|
||||
expanded. We could rewrite our macro above to use this
|
||||
function as:
|
||||
@@ -531,153 +609,141 @@ defmodule Macro do
|
||||
|
||||
"""
|
||||
def expand_once(ast, env) do
|
||||
elem(expand_once(ast, env, nil), 0)
|
||||
elem(do_expand_once(ast, env), 0)
|
||||
end
|
||||
|
||||
defp expand_once({ :__aliases__, _, _ } = original, env, cache) 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, cache }
|
||||
{ receiver, true }
|
||||
aliases ->
|
||||
aliases = lc alias inlist aliases, do: elem(expand_once(alias, env, cache), 0)
|
||||
aliases = lc alias inlist 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, cache }
|
||||
false -> { original, false, cache }
|
||||
{ receiver, true }
|
||||
false ->
|
||||
{ original, false }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Expand @ calls
|
||||
defp expand_once({ :@, _, [{ name, _, args }] } = original, env, cache) 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, cache }
|
||||
false -> { original, false, cache }
|
||||
true -> { Module.get_attribute(module, name), true }
|
||||
false -> { original, false }
|
||||
end
|
||||
end
|
||||
|
||||
# Expand pseudo-variables
|
||||
defp expand_once({ :__MODULE__, _, atom }, env, cache) when is_atom(atom),
|
||||
do: { env.module, true, cache }
|
||||
defp expand_once({ :__FILE__, _, atom }, env, cache) when is_atom(atom),
|
||||
do: { env.file, true, cache }
|
||||
defp expand_once({ :__DIR__, _, atom }, env, cache) when is_atom(atom),
|
||||
do: { :filename.dirname(env.file), true, cache }
|
||||
defp expand_once({ :__ENV__, _, atom }, env, cache) when is_atom(atom),
|
||||
do: { env, true, cache }
|
||||
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
|
||||
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 }
|
||||
end
|
||||
end
|
||||
|
||||
# Expand possible macro import invocation
|
||||
defp expand_once({ atom, line, args } = original, env, cache) when is_atom(atom) do
|
||||
args = case is_atom(args) do
|
||||
true -> []
|
||||
false -> args
|
||||
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 }
|
||||
else
|
||||
case do_expand_once({ atom, meta, [] }, env) do
|
||||
{ _, true } = exp -> exp
|
||||
{ _, false } -> { original, false }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
case not is_partial?(args) do
|
||||
false -> { original, false, cache }
|
||||
true ->
|
||||
module = env.module
|
||||
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)
|
||||
|
||||
extra = if function_exported?(module, :__info__, 1) do
|
||||
[{ module, module.__info__(:macros) }]
|
||||
else
|
||||
[]
|
||||
end
|
||||
if :elixir_import.special_form(atom, arity) do
|
||||
{ original, false }
|
||||
else
|
||||
module = env.module
|
||||
extra = if function_exported?(module, :__info__, 1) do
|
||||
[{ module, module.__info__(:macros) }]
|
||||
else
|
||||
[]
|
||||
end
|
||||
|
||||
cache = to_erl_env(env, cache)
|
||||
expand = :elixir_dispatch.expand_import(line, { atom, length(args) }, args,
|
||||
env.module, extra, cache)
|
||||
expand = :elixir_dispatch.expand_import(meta, { atom, length(args) }, args,
|
||||
:elixir_env.ex_to_env(env), extra)
|
||||
|
||||
case expand do
|
||||
{ :ok, _, expanded } -> { expanded, true, cache }
|
||||
{ :error, _ } -> { original, false, cache }
|
||||
end
|
||||
case expand do
|
||||
{ :ok, receiver, quoted } ->
|
||||
next = :elixir_counter.next
|
||||
{ :elixir_quote.linify_with_context_counter(0, { receiver, next }, quoted), true }
|
||||
{ :ok, _receiver } ->
|
||||
{ original, false }
|
||||
:error ->
|
||||
{ original, false }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Expand possible macro require invocation
|
||||
defp expand_once({ { :., _, [left, right] }, line, args } = original, env, cache) when is_atom(right) do
|
||||
{ receiver, _, _ } = expand_once(left, env, cache)
|
||||
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) and not is_partial?(args) do
|
||||
false -> { original, false, cache }
|
||||
case is_atom(receiver) do
|
||||
false -> { original, false }
|
||||
true ->
|
||||
cache = to_erl_env(env, cache)
|
||||
expand = :elixir_dispatch.expand_require(line, receiver, { right, length(args) },
|
||||
args, env.module, cache)
|
||||
expand = :elixir_dispatch.expand_require(meta, receiver, { right, length(args) },
|
||||
args, :elixir_env.ex_to_env(env))
|
||||
|
||||
case expand do
|
||||
{ :ok, _receiver, expanded } -> { expanded, true, cache }
|
||||
{ :error, _ } -> { original, false, cache }
|
||||
{ :ok, receiver, quoted } ->
|
||||
next = :elixir_counter.next
|
||||
{ :elixir_quote.linify_with_context_counter(0, { receiver, next }, quoted), true }
|
||||
:error ->
|
||||
{ original, false }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Anything else is just returned
|
||||
defp expand_once(other, _env, cache), do: { other, false, cache }
|
||||
|
||||
defp to_erl_env(env, nil), do: :elixir_scope.to_erl_env(env)
|
||||
defp to_erl_env(_env, cache), do: cache
|
||||
|
||||
defp is_partial?(args) do
|
||||
:lists.any(&match?({ :&, _, [_] }, &1), args)
|
||||
end
|
||||
defp do_expand_once(other, _env), do: { other, false }
|
||||
|
||||
@doc """
|
||||
Receives a AST node and expands it until it no longer represents
|
||||
a macro. Check `expand_once/2` for more information on how
|
||||
expansion works and `expand_all/2` for recursive expansion.
|
||||
Receives an AST node and expands it until it no longer represents
|
||||
a macro.
|
||||
|
||||
Check `expand_once/2` for more information on how
|
||||
expansion works.
|
||||
"""
|
||||
def expand(tree, env) do
|
||||
elem(expand(tree, env, nil), 0)
|
||||
expand_until({ tree, true }, env)
|
||||
end
|
||||
|
||||
@doc false # Used internally by Elixir
|
||||
def expand(tree, env, cache) do
|
||||
expand_until({ tree, true, cache }, env)
|
||||
defp expand_until({ tree, true }, env) do
|
||||
expand_until(do_expand_once(tree, env), env)
|
||||
end
|
||||
|
||||
defp expand_until({ tree, true, cache }, env) do
|
||||
expand_until(expand_once(tree, env, cache), env)
|
||||
end
|
||||
|
||||
defp expand_until({ tree, false, cache }, _env) do
|
||||
{ tree, cache }
|
||||
end
|
||||
|
||||
@doc false # Used internally by Elixir
|
||||
def expand_all(tree, env, cache) do
|
||||
expand_all_until(expand(tree, env, cache), env)
|
||||
end
|
||||
|
||||
defp expand_all_until({ { left, meta, right }, cache }, env) do
|
||||
{ left, cache } = expand_all(left, env, cache)
|
||||
{ right, cache } = expand_all(right, env, cache)
|
||||
{ { left, meta, right }, cache }
|
||||
end
|
||||
|
||||
defp expand_all_until({ { left, right }, cache }, env) do
|
||||
{ left, cache } = expand_all(left, env, cache)
|
||||
{ right, cache } = expand_all(right, env, cache)
|
||||
{ { left, right }, cache }
|
||||
end
|
||||
|
||||
defp expand_all_until({ list, cache }, env) when is_list(list) do
|
||||
:lists.mapfoldl(&expand_all(&1, env, &2), cache, list)
|
||||
end
|
||||
|
||||
defp expand_all_until({ other, cache }, _env) do
|
||||
{ other, cache }
|
||||
defp expand_until({ tree, false }, _env) do
|
||||
tree
|
||||
end
|
||||
|
||||
@doc """
|
||||
Recurs the quoted expression checking if all sub-terms are
|
||||
safe (i.e. they represent data structures and don't actually
|
||||
evaluate code) and returns `:ok` unless a given term is unsafe,
|
||||
Recursively traverses the quoted expression checking if all sub-terms are
|
||||
safe.
|
||||
|
||||
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 }`.
|
||||
"""
|
||||
def safe_term(terms) do
|
||||
|
||||
+25
-13
@@ -12,37 +12,45 @@ defmodule Macro.Env do
|
||||
* `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
|
||||
* `aliases` - a list of two item tuples, where the first
|
||||
item is the aliased name and the second the actual name
|
||||
* `context` - the context of the environment. It can be nil
|
||||
(default context), inside a guard or inside an assign
|
||||
* `aliases` - a list of two item tuples, where the first
|
||||
item is the aliased name and the second the actual name
|
||||
* `requires` - the list of required modules
|
||||
* `functions` - a list of functions imported from each module
|
||||
* `macros` - a list of macros imported from each module
|
||||
* `context_modules` - a list of modules defined in the current context
|
||||
* `macro_aliases` - a list of aliases defined inside the current macro
|
||||
* `vars` - a list keeping all defined varaibles as { var, context }
|
||||
* `context_modules` - a list of modules defined in the current 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 file :: binary
|
||||
@type line :: non_neg_integer
|
||||
@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 context_modules :: [module]
|
||||
@type vars :: [{ atom, atom }]
|
||||
@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, :aliases, :context, :requires, :functions,
|
||||
:macros, :context_modules, :macro_aliases, :vars, :lexical_tracker]
|
||||
fields = [:module, :file, :line, :function, :context, :requires, :aliases, :functions,
|
||||
:macros, :macro_aliases, :context_modules, :vars, :export_vars, :lexical_tracker,
|
||||
:local]
|
||||
|
||||
types = quote do: [module: module, file: file, line: line,
|
||||
function: name_arity, aliases: aliases, requires: requires,
|
||||
functions: functions, macros: macros, context_modules: context_modules,
|
||||
macro_aliases: aliases, vars: vars, lexical_tracker: lexical_tracker]
|
||||
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__)
|
||||
@@ -73,12 +81,16 @@ defmodule Macro.Env do
|
||||
def stacktrace(record) do
|
||||
cond do
|
||||
nil?(record.module) ->
|
||||
[{ :elixir_compiler, :__FILE__, 2, location(record) }]
|
||||
[{ :elixir_compiler, :__FILE__, 1, relative_location(record) }]
|
||||
nil?(record.function) ->
|
||||
[{ module(record), :__MODULE__, 0, location(record) }]
|
||||
[{ module(record), :__MODULE__, 0, relative_location(record) }]
|
||||
true ->
|
||||
{ name, arity } = record.function
|
||||
[{ module(record), name, arity, location(record) }]
|
||||
[{ module(record), name, arity, relative_location(record) }]
|
||||
end
|
||||
end
|
||||
|
||||
defp relative_location(record) do
|
||||
[file: Path.relative_to_cwd(file(record)), line: line(record)]
|
||||
end
|
||||
end
|
||||
|
||||
+80
-57
@@ -5,7 +5,7 @@ defmodule Module do
|
||||
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.
|
||||
@@ -69,7 +69,7 @@ defmodule Module do
|
||||
@before_compile A
|
||||
end
|
||||
|
||||
* `@behaviour` (notice the british spelling)
|
||||
* `@behaviour` (notice the British spelling)
|
||||
|
||||
Specify an OTP or user-defined behaviour.
|
||||
|
||||
@@ -183,7 +183,7 @@ defmodule Module do
|
||||
When just a module is provided, the function is assumed to be
|
||||
`__on_definition__/6`.
|
||||
|
||||
Note that you can\'t provide the current module to `@on_definition`
|
||||
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
|
||||
@@ -257,7 +257,7 @@ defmodule Module do
|
||||
* `@typep` - defines a private type to be used in `@spec`
|
||||
* `@opaque` - defines an opaque type to be used in `@spec`
|
||||
* `@spec` - provides a specification for a function
|
||||
* `@callback` - provides a specification for the behavior callback
|
||||
* `@callback` - provides a specification for the behaviour callback
|
||||
|
||||
In addition to the built-in attributes outlined above, custom attributes may
|
||||
also be added. A custom attribute is any valid identifier prefixed with an
|
||||
@@ -298,7 +298,7 @@ defmodule Module do
|
||||
|
||||
In addition to the above, you may also pass to `__info__/1` any atom supported
|
||||
by Erlang's `module_info` function which also gets defined for each compiled
|
||||
module. See http://erlang.org/doc/reference_manual/modules.html#id74571 for
|
||||
module. See http://erlang.org/doc/reference_manual/modules.html#id69430 for
|
||||
more information.
|
||||
"""
|
||||
def __info__(kind)
|
||||
@@ -340,7 +340,7 @@ defmodule Module do
|
||||
Foo.sum(1, 2) #=> 3
|
||||
|
||||
"""
|
||||
def eval_quoted(module, quoted, binding // [], opts // [])
|
||||
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))
|
||||
@@ -352,13 +352,16 @@ defmodule Module do
|
||||
|
||||
def eval_quoted(module, quoted, binding, opts) do
|
||||
assert_not_compiled!(:eval_quoted, module)
|
||||
:elixir_module.eval_quoted(module, quoted, binding, opts)
|
||||
:elixir_def.reset_last(module)
|
||||
{ value, binding, _env, _scope } =
|
||||
:elixir.eval_quoted quoted, binding, Keyword.put(opts, :module, module)
|
||||
{ value, binding }
|
||||
end
|
||||
|
||||
@doc """
|
||||
Creates a module with the given name and given by
|
||||
Creates a module with the given name and defined by
|
||||
the given quoted expressions. The line where the module
|
||||
is defined and its file can be given as options.
|
||||
is defined and its file can be passed as options.
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -371,7 +374,7 @@ defmodule Module do
|
||||
|
||||
Hello.world #=> true
|
||||
|
||||
## Differences with `defmodule`
|
||||
## Differences from `defmodule`
|
||||
|
||||
`Module.create` works similarly to `defmodule` and
|
||||
return the same results. While one could also use
|
||||
@@ -384,20 +387,18 @@ defmodule Module do
|
||||
when defining the module, while `defmodule` automatically
|
||||
shares the same environment.
|
||||
"""
|
||||
def create(module, quoted, opts // [])
|
||||
def create(module, quoted, opts \\ [])
|
||||
|
||||
def create(module, quoted, env) when is_env(env) do
|
||||
create(module, quoted, env.to_keywords)
|
||||
end
|
||||
|
||||
def create(module, quoted, opts) when is_atom(module) do
|
||||
line = Keyword.get(opts, :line, 1)
|
||||
:elixir_module.compile(line, module, quoted, [], :elixir.scope_for_eval(opts))
|
||||
:elixir_module.compile(module, quoted, [], :elixir.env_for_eval(opts))
|
||||
end
|
||||
|
||||
@doc """
|
||||
Concatenates the list of aliases and returns a new alias.
|
||||
It handles char lists, binaries and atoms.
|
||||
Concatenates a list of aliases and returns a new alias.
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -405,17 +406,15 @@ defmodule Module do
|
||||
Foo.Bar
|
||||
iex> Module.concat([Foo, "Bar"])
|
||||
Foo.Bar
|
||||
iex> Module.concat([Foo, 'Bar'])
|
||||
Foo.Bar
|
||||
|
||||
"""
|
||||
@spec concat([binary | atom]) :: atom
|
||||
def concat(list) when is_list(list) do
|
||||
:elixir_aliases.concat(list)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Concatenates the two given aliases and returns a new alias.
|
||||
It handles char lists, binaries and atoms.
|
||||
Concatenates two aliases and returns a new alias.
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -423,18 +422,17 @@ defmodule Module do
|
||||
Foo.Bar
|
||||
iex> Module.concat(Foo, "Bar")
|
||||
Foo.Bar
|
||||
iex> Module.concat(Foo, 'Bar')
|
||||
Foo.Bar
|
||||
|
||||
"""
|
||||
@spec concat(binary | atom, binary | atom) :: atom
|
||||
def concat(left, right) do
|
||||
:elixir_aliases.concat([left, right])
|
||||
end
|
||||
|
||||
@doc """
|
||||
Concatenates the list aliases and returns a new alias only
|
||||
Concatenates a list of aliases and returns a new alias only
|
||||
if the alias was already referenced. If the alias was not
|
||||
referenced yet, fails with ArgumentError.
|
||||
referenced yet, fails with `ArgumentError`.
|
||||
It handles char lists, binaries and atoms.
|
||||
|
||||
## Examples
|
||||
@@ -446,14 +444,15 @@ defmodule Module do
|
||||
List.Chars
|
||||
|
||||
"""
|
||||
@spec safe_concat([binary | atom]) :: atom | no_return
|
||||
def safe_concat(list) when is_list(list) do
|
||||
:elixir_aliases.safe_concat(list)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Concatenates the two aliases and returns a new alias only
|
||||
Concatenates two aliases and returns a new alias only
|
||||
if the alias was already referenced. If the alias was not
|
||||
referenced yet, fails with ArgumentError.
|
||||
referenced yet, fails with `ArgumentError`.
|
||||
It handles char lists, binaries and atoms.
|
||||
|
||||
## Examples
|
||||
@@ -465,6 +464,7 @@ defmodule Module do
|
||||
List.Chars
|
||||
|
||||
"""
|
||||
@spec safe_concat(binary | atom, binary | atom) :: atom | no_return
|
||||
def safe_concat(left, right) do
|
||||
:elixir_aliases.safe_concat([left, right])
|
||||
end
|
||||
@@ -485,7 +485,7 @@ defmodule Module do
|
||||
@doc """
|
||||
Attaches documentation to a given function or type. It expects
|
||||
the module the function/type belongs to, the line (a non negative
|
||||
integer), the kind (def or defmacro), a tuple representing
|
||||
integer), the kind (`def` or `defmacro`), a tuple representing
|
||||
the function and its arity, the function signature (the signature
|
||||
should be omitted for types) and the documentation, which should
|
||||
be either a binary or a boolean.
|
||||
@@ -498,7 +498,7 @@ defmodule Module do
|
||||
end
|
||||
|
||||
"""
|
||||
def add_doc(module, line, kind, tuple, signature // [], doc)
|
||||
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
|
||||
@@ -531,8 +531,8 @@ defmodule Module do
|
||||
|
||||
# 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({ :\\, defline, [left, right ] }, line, i) do
|
||||
{ :\\, defline, [simplify_signature(left, line, i), right] }
|
||||
end
|
||||
|
||||
defp simplify_signature({ var, line, atom }, _, _i) when is_atom(atom) do
|
||||
@@ -564,11 +564,11 @@ 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
|
||||
|
||||
@@ -584,7 +584,7 @@ defmodule Module do
|
||||
|
||||
@doc """
|
||||
Checks if the module defines the given function or macro.
|
||||
Use `defines?/3` to assert for an specific type.
|
||||
Use `defines?/3` to assert for a specific type.
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -602,8 +602,8 @@ defmodule Module do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Checks if the module defines a function or macro with the
|
||||
given `kind`. `kind` can be either `:def`, `:defp`,
|
||||
Checks if the module defines a function or macro of the
|
||||
given `kind`. `kind` can be any of `:def`, `:defp`,
|
||||
`:defmacro` or `:defmacrop`.
|
||||
|
||||
## Examples
|
||||
@@ -625,13 +625,13 @@ defmodule Module do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Return all functions defined in the given module.
|
||||
Return all functions defined in `module`.
|
||||
|
||||
## Examples
|
||||
|
||||
defmodule Example do
|
||||
def version, do: 1
|
||||
Module.definitions_in __MODULE__ #=> [{:version,1}]
|
||||
Module.definitions_in __MODULE__ #=> [{:version,0}]
|
||||
end
|
||||
|
||||
"""
|
||||
@@ -642,14 +642,14 @@ defmodule Module do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns all functions defined in te given module according
|
||||
Returns all functions defined in `module`, according
|
||||
to its kind.
|
||||
|
||||
## Examples
|
||||
|
||||
defmodule Example do
|
||||
def version, do: 1
|
||||
Module.definitions_in __MODULE__, :def #=> [{:version,1}]
|
||||
Module.definitions_in __MODULE__, :def #=> [{:version,0}]
|
||||
Module.definitions_in __MODULE__, :defp #=> []
|
||||
end
|
||||
|
||||
@@ -661,7 +661,7 @@ defmodule Module do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Makes the given functions in the given module overridable.
|
||||
Makes the given functions in `module` overridable.
|
||||
An overridable function is lazily defined, allowing a
|
||||
developer to customize it. See `Kernel.defoverridable` for
|
||||
more information and documentation.
|
||||
@@ -676,7 +676,12 @@ defmodule Module do
|
||||
raise "Cannot make function #{name}/#{arity} overridable because it was not defined"
|
||||
clause ->
|
||||
:elixir_def.delete_definition(module, tuple)
|
||||
neighbours = Module.DispatchTracker.yank(module, tuple)
|
||||
|
||||
neighbours = if loaded?(Module.LocalsTracker) do
|
||||
Module.LocalsTracker.yank(module, tuple)
|
||||
else
|
||||
[]
|
||||
end
|
||||
|
||||
old = get_attribute(module, :__overridable)
|
||||
merged = :orddict.update(tuple, fn({ count, _, _, _ }) ->
|
||||
@@ -689,10 +694,10 @@ defmodule Module do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns true if the given tuple in module is marked as overridable.
|
||||
Returns `true` if `tuple` in `module` is marked as overridable.
|
||||
"""
|
||||
def overridable?(module, tuple) do
|
||||
!! List.keyfind(get_attribute(module, :__overridable), tuple, 0)
|
||||
!!List.keyfind(get_attribute(module, :__overridable), tuple, 0)
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -740,9 +745,11 @@ defmodule Module do
|
||||
|
||||
Module.get_attribute(__MODULE__, :foo, true)
|
||||
|
||||
Notice the third argument is used to indicate if a warning
|
||||
should be emitted when the attribute was not previously defined.
|
||||
This is true for `@foo` attributes but false for direct calls.
|
||||
Notice the third argument may be given to indicate a stacktrace
|
||||
to be emitted when the attribute was not previously defined.
|
||||
The default value for `warn` is nil for direct calls but the `@foo`
|
||||
macro sets it to the proper stacktrace automatically, warning
|
||||
every time `@foo` is used but not set previously.
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -756,7 +763,9 @@ defmodule Module do
|
||||
end
|
||||
|
||||
"""
|
||||
def get_attribute(module, key, warn // false) when is_atom(key) do
|
||||
@spec get_attribute(module, atom, warn :: nil | [tuple]) :: term
|
||||
def get_attribute(module, key, warn \\ nil) when
|
||||
is_atom(key) and (is_list(warn) or nil?(warn)) do
|
||||
assert_not_compiled!(:get_attribute, module)
|
||||
table = data_table_for(module)
|
||||
|
||||
@@ -765,18 +774,30 @@ defmodule Module do
|
||||
[] ->
|
||||
acc = :ets.lookup_element(table, :__acc_attributes, 2)
|
||||
|
||||
if :lists.member(key, acc) do
|
||||
[]
|
||||
else
|
||||
warn && :elixir_errors.warn "#{Exception.format_caller} undefined module attribute @#{key}, " <>
|
||||
"please remove access to @#{key} or explicitly set it to nil before access\n"
|
||||
nil
|
||||
cond do
|
||||
:lists.member(key, acc) ->
|
||||
[]
|
||||
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 ->
|
||||
nil
|
||||
end
|
||||
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 matches the given key.
|
||||
Deletes all attributes that match the given key.
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -808,7 +829,7 @@ defmodule Module do
|
||||
* `:persist` - The attribute will be persisted in the Erlang
|
||||
Abstract Format. Useful when interfacing with Erlang libraries.
|
||||
|
||||
By default, both options are false.
|
||||
By default, both options are `false`.
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -865,7 +886,7 @@ defmodule Module do
|
||||
:ok ->
|
||||
:ok
|
||||
{ :error, :private_doc } ->
|
||||
:elixir_errors.warn "#{env.file}:#{line} function #{name}/#{arity} is private, @doc's are always discarded for private functions\n"
|
||||
:elixir_errors.warn line, env.file, "function #{name}/#{arity} is private, @doc's are always discarded for private functions\n"
|
||||
end
|
||||
|
||||
delete_attribute(module, :doc)
|
||||
@@ -893,7 +914,7 @@ defmodule Module do
|
||||
{ atom, 0 }
|
||||
end
|
||||
|
||||
defp normalize_attribute(kind, atom) when kind in [:behavior, :behaviour] and is_atom(atom) do
|
||||
defp normalize_attribute(:behaviour, atom) when is_atom(atom) do
|
||||
Code.ensure_compiled(atom)
|
||||
atom
|
||||
end
|
||||
@@ -933,4 +954,6 @@ defmodule Module do
|
||||
raise ArgumentError,
|
||||
message: "could not call #{fun} on module #{inspect module} because it was already compiled"
|
||||
end
|
||||
|
||||
defp loaded?(module), do: is_tuple :code.is_loaded(module)
|
||||
end
|
||||
|
||||
+63
-97
@@ -11,36 +11,31 @@
|
||||
#
|
||||
# We also have can the following vertices:
|
||||
#
|
||||
# * `Module` - a module that was invoked via an import or remotely
|
||||
# * `Module` - a module that was invoked via an import
|
||||
# * `{ name, arity }` - a local function/arity pair
|
||||
# * `{ :import, name, arity }` - an invoked function/arity import
|
||||
# * `{ :remote, name, arity }` - an remotely invoked function/arity
|
||||
#
|
||||
# Each of those vertices can associate to other vertices
|
||||
# as described below:
|
||||
#
|
||||
# * `Module`
|
||||
# * in neighbours: `{ :import, name, arity }` and `{ :remote, name arity }`
|
||||
# * in neighbours: `{ :import, name, arity }`
|
||||
#
|
||||
# * `{ name, arity }`
|
||||
# * in neighbours: `:local`, `{ name, arity }`
|
||||
# * out neighbours: `{ :import, name, arity }` and `{ :remote, name arity }`
|
||||
# * out neighbours: `{ :import, name, arity }`
|
||||
#
|
||||
# * `{ :import, name, arity }`
|
||||
# * in neighbours: `{ name, arity }`
|
||||
# * out neighbours: `Module`
|
||||
#
|
||||
# * `{ :remote, name, arity }`
|
||||
# * in neighbours: `{ name, arity }`
|
||||
# * out neighbours: `Module`
|
||||
#
|
||||
# Note that since this is required for bootstrap, we can't use
|
||||
# any of the `GenServer.Behaviour` conveniences.
|
||||
defmodule Module.DispatchTracker do
|
||||
defmodule Module.LocalsTracker do
|
||||
@moduledoc false
|
||||
|
||||
@timeout 30_000
|
||||
@behavior :gen_server
|
||||
@timeout 30_000
|
||||
@behaviour :gen_server
|
||||
|
||||
@type ref :: pid | module
|
||||
@type name :: atom
|
||||
@@ -48,44 +43,9 @@ defmodule Module.DispatchTracker do
|
||||
|
||||
@type local :: { name, arity }
|
||||
@type import :: { :import, name, arity }
|
||||
@type remote :: { :remote, name, arity }
|
||||
|
||||
# Public API
|
||||
|
||||
@doc """
|
||||
Receives a dispatch or a module and returns all dispatches
|
||||
that calls it.
|
||||
|
||||
In case the argument is a module, the response will be
|
||||
made by import and remote dispatches.
|
||||
|
||||
In case the argument is another dispatch, the response
|
||||
will be made by local dispatches.
|
||||
|
||||
This function is not recursive, so if A dispatches to
|
||||
B which dispatches to C, A does not appear in the result,
|
||||
only B.
|
||||
"""
|
||||
@spec dispatches_to(ref, module) :: [import | remote]
|
||||
@spec dispatches_to(ref, local | import | remote) :: [local]
|
||||
def dispatches_to(ref, dispatch) do
|
||||
d = :gen_server.call(to_pid(ref), :digraph, @timeout)
|
||||
:digraph.in_neighbours(d, dispatch) |> only_tuples
|
||||
end
|
||||
|
||||
@doc """
|
||||
Receives a local and returns all dispatches from that local.
|
||||
|
||||
This function is not recursive, so if A dispatches to
|
||||
B which dispatches to C, C does not appear in the result,
|
||||
only B.
|
||||
"""
|
||||
@spec dispatches_from(ref, local) :: [local | import | remote]
|
||||
def dispatches_from(ref, { name, arity }) do
|
||||
d = :gen_server.call(to_pid(ref), :digraph, @timeout)
|
||||
:digraph.out_neighbours(d, { name, arity }) |> only_tuples
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns all imported modules that had the given
|
||||
`{ name, arity }` invoked.
|
||||
@@ -96,16 +56,6 @@ defmodule Module.DispatchTracker do
|
||||
:digraph.out_neighbours(d, { :import, name, arity })
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns all modules that had the given `{ name, arity }`
|
||||
invoked remotely.
|
||||
"""
|
||||
@spec remotes_with_dispatch(ref, name_arity) :: [module]
|
||||
def remotes_with_dispatch(ref, { name, arity }) do
|
||||
d = :gen_server.call(to_pid(ref), :digraph, @timeout)
|
||||
:digraph.out_neighbours(d, { :remote, name, arity })
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns all locals that are reachable.
|
||||
|
||||
@@ -130,14 +80,10 @@ defmodule Module.DispatchTracker 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, :__dispatch_tracker)
|
||||
[{ _, val }] = :ets.lookup(table, :__locals_tracker)
|
||||
val
|
||||
end
|
||||
|
||||
defp only_tuples(list) do
|
||||
lc x inlist list, is_tuple(x), do: x
|
||||
end
|
||||
|
||||
# Internal API
|
||||
|
||||
# Starts the tracker and returns its pid.
|
||||
@@ -173,16 +119,10 @@ defmodule Module.DispatchTracker do
|
||||
:gen_server.cast(pid, { :add_local, from, to })
|
||||
end
|
||||
|
||||
# Adds a remote dispatch to the given target.
|
||||
@doc false
|
||||
def add_remote(pid, function, module, target) when is_atom(module) and is_tuple(target) do
|
||||
:gen_server.cast(pid, { :add_external, :remote, function, module, target })
|
||||
end
|
||||
|
||||
# Adds a import dispatch to the given target.
|
||||
@doc false
|
||||
def add_import(pid, function, module, target) when is_atom(module) and is_tuple(target) do
|
||||
:gen_server.cast(pid, { :add_external, :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.
|
||||
@@ -205,6 +145,7 @@ defmodule Module.DispatchTracker do
|
||||
d = :gen_server.call(pid, :digraph, @timeout)
|
||||
|
||||
lc { name, arity } inlist all_defined,
|
||||
:digraph.in_neighbours(d, { :import, name, arity }) != [],
|
||||
n = :digraph.out_neighbours(d, { :import, name, arity }),
|
||||
n != [] do
|
||||
{ n, name, arity }
|
||||
@@ -246,6 +187,16 @@ defmodule Module.DispatchTracker do
|
||||
end
|
||||
end
|
||||
|
||||
@doc false
|
||||
def cache_env(pid, env) do
|
||||
: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)
|
||||
end
|
||||
|
||||
# Stops the gen server
|
||||
@doc false
|
||||
def stop(pid) do
|
||||
@@ -257,77 +208,92 @@ defmodule Module.DispatchTracker do
|
||||
def init([]) do
|
||||
d = :digraph.new([:protected])
|
||||
:digraph.add_vertex(d, :local)
|
||||
{ :ok, d }
|
||||
{ :ok, { d, [] } }
|
||||
end
|
||||
|
||||
def handle_call({ :yank, local }, _from, d) do
|
||||
def handle_call({ :cache_env, env }, _from, { d, cache }) do
|
||||
case cache do
|
||||
[{i,^env}|_] ->
|
||||
{ :reply, i, { d, cache } }
|
||||
t ->
|
||||
i = length(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 }
|
||||
end
|
||||
|
||||
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 }, d }
|
||||
{ :reply, { in_vertices, out_vertices }, state }
|
||||
end
|
||||
|
||||
def handle_call(:digraph, _from, d) do
|
||||
{ :reply, d, d }
|
||||
def handle_call(:digraph, _from, { d, _ } = state) do
|
||||
{ :reply, d, state }
|
||||
end
|
||||
|
||||
def handle_call(_request, _from, d) do
|
||||
{ :noreply, d }
|
||||
def handle_call(request, _from, state) do
|
||||
{ :stop, { :bad_call, request }, state }
|
||||
end
|
||||
|
||||
def handle_info(_msg, d) do
|
||||
{ :noreply, d }
|
||||
def handle_info(_msg, state) do
|
||||
{ :noreply, state }
|
||||
end
|
||||
|
||||
def handle_cast({ :add_local, from, to }, d) do
|
||||
def handle_cast({ :add_local, from, to }, { d, _ } = state) do
|
||||
handle_add_local(d, from, to)
|
||||
{ :noreply, d }
|
||||
{ :noreply, state }
|
||||
end
|
||||
|
||||
def handle_cast({ :add_external, kind, function, module, { name, arity } }, d) do
|
||||
handle_import_or_remote(d, kind, function, module, name, arity)
|
||||
{ :noreply, d }
|
||||
def handle_cast({ :add_import, function, module, { name, arity } }, { d, _ } = state) do
|
||||
handle_import(d, function, module, name, arity)
|
||||
{ :noreply, state }
|
||||
end
|
||||
|
||||
def handle_cast({ :add_definition, kind, tuple }, d) do
|
||||
def handle_cast({ :add_definition, kind, tuple }, { d, _ } = state) do
|
||||
handle_add_definition(d, kind, tuple)
|
||||
{ :noreply, d }
|
||||
{ :noreply, state }
|
||||
end
|
||||
|
||||
def handle_cast({ :add_defaults, kind, { name, arity }, defaults }, d) do
|
||||
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 })
|
||||
end
|
||||
{ :noreply, d }
|
||||
{ :noreply, state }
|
||||
end
|
||||
|
||||
def handle_cast({ :reattach, tuple, { in_neigh, out_neigh } }, d) do
|
||||
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, d }
|
||||
{ :noreply, state }
|
||||
end
|
||||
|
||||
def handle_cast(:stop, d) do
|
||||
{ :stop, :normal, d }
|
||||
def handle_cast(:stop, state) do
|
||||
{ :stop, :normal, state }
|
||||
end
|
||||
|
||||
def handle_cast(_msg, d) do
|
||||
{ :noreply, d }
|
||||
def handle_cast(msg, state) do
|
||||
{ :stop, { :bad_cast, msg }, state }
|
||||
end
|
||||
|
||||
def terminate(_reason, _d) do
|
||||
def terminate(_reason, _state) do
|
||||
:ok
|
||||
end
|
||||
|
||||
def code_change(_old, d, _extra) do
|
||||
{ :ok, d }
|
||||
def code_change(_old, state, _extra) do
|
||||
{ :ok, state }
|
||||
end
|
||||
|
||||
defp handle_import_or_remote(d, kind, function, module, name, arity) do
|
||||
defp handle_import(d, function, module, name, arity) do
|
||||
:digraph.add_vertex(d, module)
|
||||
|
||||
tuple = { kind, name, arity }
|
||||
tuple = { :import, name, arity }
|
||||
:digraph.add_vertex(d, tuple)
|
||||
replace_edge!(d, tuple, module)
|
||||
|
||||
+19
-2
@@ -1,6 +1,11 @@
|
||||
defmodule Node do
|
||||
@moduledoc """
|
||||
Functions related to Erlang nodes.
|
||||
Functions related to VM nodes.
|
||||
|
||||
Some of the functions in this module are inlined by the compiler,
|
||||
similar to functions in the `Kernel` module and they are explicitly
|
||||
marked in their docs as "inlined by the compiler". For more information
|
||||
about inlined functions, check out the `Kernel` module.
|
||||
"""
|
||||
|
||||
@type t :: atom
|
||||
@@ -97,6 +102,8 @@ defmodule Node do
|
||||
|
||||
Check http://www.erlang.org/doc/man/erlang.html#spawn-2 for
|
||||
the list of available options.
|
||||
|
||||
Inlined by the compiler.
|
||||
"""
|
||||
@spec spawn(t, (() -> any)) :: pid
|
||||
def spawn(node, fun) do
|
||||
@@ -109,6 +116,8 @@ defmodule Node do
|
||||
|
||||
Check http://www.erlang.org/doc/man/erlang.html#spawn_opt-3 for
|
||||
the list of available options.
|
||||
|
||||
Inlined by the compiler.
|
||||
"""
|
||||
@spec spawn(t, (() -> any), Process.spawn_opts) :: pid | {pid, reference}
|
||||
def spawn(node, fun, opts) do
|
||||
@@ -122,6 +131,8 @@ defmodule Node do
|
||||
|
||||
Check http://www.erlang.org/doc/man/erlang.html#spawn-4 for
|
||||
the list of available options.
|
||||
|
||||
Inlined by the compiler.
|
||||
"""
|
||||
@spec spawn(t, module, atom, [any]) :: pid
|
||||
def spawn(node, module, fun, args) do
|
||||
@@ -135,6 +146,8 @@ defmodule Node do
|
||||
|
||||
Check http://www.erlang.org/doc/man/erlang.html#spawn_opt-5 for
|
||||
the list of available options.
|
||||
|
||||
Inlined by the compiler.
|
||||
"""
|
||||
@spec spawn(t, module, atom, [any], Process.spawn_opts) :: pid | {pid, reference}
|
||||
def spawn(node, module, fun, args, opts) do
|
||||
@@ -147,6 +160,8 @@ defmodule Node do
|
||||
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.
|
||||
"""
|
||||
@spec spawn_link(t, (() -> any)) :: pid
|
||||
def spawn_link(node, fun) do
|
||||
@@ -159,6 +174,8 @@ defmodule Node do
|
||||
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.
|
||||
"""
|
||||
@spec spawn_link(t, module, atom, [any]) :: pid
|
||||
def spawn_link(node, module, fun, args) do
|
||||
@@ -172,7 +189,7 @@ defmodule Node do
|
||||
|
||||
This function will raise `FunctionClauseError` if the given `node` is not alive.
|
||||
"""
|
||||
def set_cookie(node // Node.self, cookie) when is_atom(cookie) do
|
||||
def set_cookie(node \\ Node.self, cookie) when is_atom(cookie) do
|
||||
:erlang.set_cookie(node, cookie)
|
||||
end
|
||||
|
||||
|
||||
@@ -33,7 +33,7 @@ defmodule OptionParser do
|
||||
|
||||
Extra information about switches can be given as arguments, too.
|
||||
This is useful when a switch must behave as a boolean
|
||||
or if duplicated switches should be kept, overriden or accumulated.
|
||||
or if duplicated switches should be kept, overridden or accumulated.
|
||||
|
||||
The following types are supported:
|
||||
|
||||
@@ -79,7 +79,7 @@ defmodule OptionParser do
|
||||
{ [op: false], ["path/to/file"], [] }
|
||||
|
||||
"""
|
||||
def parse(argv, opts // []) when is_list(argv) and is_list(opts) do
|
||||
def parse(argv, opts \\ []) when is_list(argv) and is_list(opts) do
|
||||
parse(argv, opts, true)
|
||||
end
|
||||
|
||||
@@ -97,7 +97,7 @@ defmodule OptionParser do
|
||||
iex> OptionParser.parse_head(["--verbose", "--source", "lib", "test/enum_test.exs", "--unlock"])
|
||||
{ [verbose: true, source: "lib"], ["test/enum_test.exs", "--unlock"], [] }
|
||||
"""
|
||||
def parse_head(argv, opts // []) when is_list(argv) and is_list(opts) do
|
||||
def parse_head(argv, opts \\ []) when is_list(argv) and is_list(opts) do
|
||||
parse(argv, opts, false)
|
||||
end
|
||||
|
||||
|
||||
+2
-11
@@ -408,8 +408,8 @@ 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(maybe_remove_dirsep(result, os_type))
|
||||
defp do_join(<<>>, <<>>, result, _os_type), do:
|
||||
iolist_to_binary(:lists.reverse(result))
|
||||
defp do_join(<<>>, relativename, [?:|rest], :win32), do:
|
||||
do_join(relativename, <<>>, [?:|rest], :win32)
|
||||
defp do_join(<<>>, relativename, [?/|result], os_type), do:
|
||||
@@ -419,15 +419,6 @@ defmodule Path do
|
||||
defp do_join(<<char, rest :: binary>>, relativename, result, os_type) when is_integer(char), do:
|
||||
do_join(rest, relativename, [char|result], os_type)
|
||||
|
||||
defp maybe_remove_dirsep([?/, ?:, letter], :win32), do:
|
||||
[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.
|
||||
|
||||
@@ -20,7 +20,7 @@ defmodule Port do
|
||||
@doc """
|
||||
See http://www.erlang.org/doc/man/erlang.html#port_command-2.
|
||||
"""
|
||||
def command(port, data, options // []) do
|
||||
def command(port, data, options \\ []) do
|
||||
:erlang.port_command(port, data, options)
|
||||
end
|
||||
|
||||
|
||||
+77
-18
@@ -1,11 +1,11 @@
|
||||
defmodule Process do
|
||||
@moduledoc """
|
||||
This module provides convenience functions around processes and
|
||||
the process dictionary. In Erlang, most of these functions are
|
||||
auto-imported, but in Elixir they are grouped in a module for
|
||||
convenience. Notice that these functions, different from Erlang's,
|
||||
always return nil instead of undefined. You can use their Erlang
|
||||
version if you want the undefined value.
|
||||
Conveniences for working with processes and the process dictionary.
|
||||
|
||||
Some of the functions in this module are inlined by the compiler,
|
||||
similar to functions in the `Kernel` module and they are explicitly
|
||||
marked in their docs as "inlined by the compiler". For more information
|
||||
about inlined functions, check out the `Kernel` module.
|
||||
"""
|
||||
|
||||
@doc """
|
||||
@@ -32,7 +32,7 @@ defmodule Process do
|
||||
"""
|
||||
@spec get(term) :: term
|
||||
@spec get(term, default :: term) :: term
|
||||
def get(key, default // nil) do
|
||||
def get(key, default \\ nil) do
|
||||
case :erlang.get(key) do
|
||||
:undefined ->
|
||||
default
|
||||
@@ -76,21 +76,23 @@ defmodule Process do
|
||||
@doc """
|
||||
Sends an exit signal with the given reason to the pid.
|
||||
|
||||
The following behavior applies if reason is any term except `:normal` or `:kill`:
|
||||
The following behaviour applies if reason is any term except `:normal` or `:kill`:
|
||||
|
||||
1) If pid is not trapping exits, pid will exit with the given reason;
|
||||
|
||||
2) If pid is trapping exits, the exit signal is transformed into a message
|
||||
{'EXIT', from, reason} and delivered to the message queue of pid;
|
||||
{:EXIT, from, reason} and delivered to the message queue of pid;
|
||||
|
||||
3) If reason is the atom `:normal`, pid will not exit. If it is trapping exits,
|
||||
the exit signal is transformed into a message {'EXIT', from, :normal} and
|
||||
the exit signal is transformed into a message {:EXIT, from, :normal} and
|
||||
delivered to its message queue;
|
||||
|
||||
4) If reason is the atom `:kill`, that is if `exit(pid, :kill)` is called, an
|
||||
untrappable exit signal is sent to pid which will unconditionally exit with
|
||||
exit reason `:killed`.
|
||||
|
||||
Inlined by the compiler.
|
||||
|
||||
## Examples
|
||||
|
||||
Process.exit(pid, :kill)
|
||||
@@ -110,12 +112,37 @@ defmodule Process do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns the pid of a new process started by the application of `fun`.
|
||||
It behaves exactly the same as `Kernel.spawn/1`.
|
||||
Sends a message to the given process.
|
||||
|
||||
It does the same as `Kernel.send/2`.
|
||||
"""
|
||||
@spec spawn((() -> any)) :: pid
|
||||
def spawn(fun) do
|
||||
:erlang.spawn(fun)
|
||||
@spec send(dest, msg) :: msg when
|
||||
dest: pid | port | atom | { atom, node },
|
||||
msg: any
|
||||
def send(dest, msg) do
|
||||
:erlang.send(dest, msg)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Sends `msg` to `dest` after `time` millisecons.
|
||||
|
||||
If `dest` is a pid, it has to be a pid of a local process, dead or alive.
|
||||
If `dest` is an atom, it is supposed to be the name of a registered process
|
||||
which is looked up at the time of delivery. No error is given if the name does
|
||||
not refer to a process.
|
||||
|
||||
This function returns a timer reference, which can be read or canceled with
|
||||
`:erlang.read_timer/1`, `:erlang.start_timer/3` and `:erlang.cancel_timer/1`.
|
||||
Note `time` cannot be greater than `4294967295`.
|
||||
|
||||
Finally, the timer will be automatically canceled if the given `dest` is a pid
|
||||
which is not alive or when the given pid exits. Note that timers will not be
|
||||
automatically canceled when `dest` is an atom (as the atom resolution is done
|
||||
on delivery).
|
||||
"""
|
||||
@spec send_after(pid | atom, term, non_neg_integer) :: reference
|
||||
def send_after(dest, msg, time) do
|
||||
:erlang.send_after(time, dest, msg)
|
||||
end
|
||||
|
||||
@type spawn_opt :: :link | :monitor | {:priority, :low | :normal | :high} |
|
||||
@@ -124,11 +151,24 @@ defmodule Process do
|
||||
{:min_bin_vheap_size, non_neg_integer}
|
||||
@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`.
|
||||
|
||||
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`.
|
||||
|
||||
It also accepts extra options, for the list of available options
|
||||
check http://www.erlang.org/doc/man/erlang.html#spawn_opt-2
|
||||
|
||||
Inlined by the compiler.
|
||||
"""
|
||||
@spec spawn((() -> any), spawn_opts) :: pid | {pid, reference}
|
||||
def spawn(fun, opts) do
|
||||
@@ -141,6 +181,8 @@ defmodule Process do
|
||||
scheduler queue and be run some time later.
|
||||
|
||||
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
|
||||
@@ -155,6 +197,7 @@ defmodule Process do
|
||||
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}
|
||||
def spawn(mod, fun, args, opts) do
|
||||
@@ -165,6 +208,8 @@ defmodule Process do
|
||||
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.
|
||||
|
||||
Inlined by the compiler.
|
||||
"""
|
||||
@spec spawn_link((() -> any)) :: pid
|
||||
def spawn_link(fun) do
|
||||
@@ -175,6 +220,8 @@ defmodule Process do
|
||||
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
|
||||
def spawn_link(mod, fun, args) do
|
||||
@@ -184,6 +231,8 @@ defmodule Process do
|
||||
@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}
|
||||
def spawn_monitor(fun) do
|
||||
@@ -194,6 +243,8 @@ defmodule Process do
|
||||
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}
|
||||
def spawn_monitor(mod, fun, args) do
|
||||
@@ -205,6 +256,8 @@ defmodule Process do
|
||||
It returns the monitor reference.
|
||||
|
||||
See http://www.erlang.org/doc/man/erlang.html#monitor-2 for more info.
|
||||
|
||||
Inlined by the compiler.
|
||||
"""
|
||||
@spec monitor(pid | {reg_name :: atom, node :: atom} | reg_name :: atom) :: reference
|
||||
def monitor(item) do
|
||||
@@ -217,10 +270,12 @@ defmodule Process do
|
||||
If the monitoring is already turned off, nothing happens.
|
||||
|
||||
See http://www.erlang.org/doc/man/erlang.html#demonitor-2 for more info.
|
||||
|
||||
Inlined by the compiler.
|
||||
"""
|
||||
@spec demonitor(reference) :: true
|
||||
@spec demonitor(reference, options :: [:flush | :info]) :: boolean
|
||||
def demonitor(monitor_ref, options // []) do
|
||||
def demonitor(monitor_ref, options \\ []) do
|
||||
:erlang.demonitor(monitor_ref, options)
|
||||
end
|
||||
|
||||
@@ -244,6 +299,8 @@ defmodule Process do
|
||||
(or port) `pid`, if there is not such a link already.
|
||||
|
||||
See http://www.erlang.org/doc/man/erlang.html#link-1 for more info.
|
||||
|
||||
Inlined by the compiler.
|
||||
"""
|
||||
@spec link(pid | port) :: true
|
||||
def link(pid) do
|
||||
@@ -256,6 +313,8 @@ defmodule Process do
|
||||
fail, even if there is no link or `id` does not exist
|
||||
|
||||
See http://www.erlang.org/doc/man/erlang.html#unlink-1 for more info.
|
||||
|
||||
Inlined by the compiler.
|
||||
"""
|
||||
@spec unlink(pid | port) :: true
|
||||
def unlink(pid) do
|
||||
@@ -264,8 +323,8 @@ defmodule Process do
|
||||
|
||||
@doc """
|
||||
Associates the name with a pid or a port identifier. name, which must
|
||||
be an atom, can be used instead of the pid / port identifier in the
|
||||
send operator (name <- message).
|
||||
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.
|
||||
"""
|
||||
|
||||
+17
-15
@@ -22,10 +22,6 @@ defmodule Protocol do
|
||||
@functions []
|
||||
@fallback_to_any false
|
||||
|
||||
# Deprecated
|
||||
@only nil
|
||||
@except nil
|
||||
|
||||
# Invoke the user given block
|
||||
unquote(block)
|
||||
|
||||
@@ -71,7 +67,8 @@ defmodule Protocol do
|
||||
# Inline both helpers
|
||||
@compile { :inline, any_impl_for: 0, rec_impl_for: 1 }
|
||||
|
||||
unless Kernel.Typespec.defines_type?(__MODULE__, :t, 0) do
|
||||
if :code.ensure_loaded(Kernel.Typespec) == { :module, Kernel.Typespec } and
|
||||
not Kernel.Typespec.defines_type?(__MODULE__, :t, 0) do
|
||||
@type t :: term
|
||||
end
|
||||
|
||||
@@ -207,7 +204,7 @@ defmodule Protocol do
|
||||
# Defines an implementation with fallback to the given module.
|
||||
defp impl_with_fallback(mod, guard, current, arg) do
|
||||
quote do
|
||||
{ unquote(guard)(unquote(arg)),
|
||||
{ :erlang.unquote(guard)(unquote(arg)),
|
||||
unquote(with_fallback(Module.concat(current, mod))) }
|
||||
end
|
||||
end
|
||||
@@ -261,7 +258,7 @@ defmodule Protocol.DSL do
|
||||
|
||||
# Convert the spec to callback if possible,
|
||||
# otherwise generate a dummy callback
|
||||
Protocol.DSL.callback_from_spec(__MODULE__, name, arity) ||
|
||||
Protocol.DSL.__spec__?(__MODULE__, name, arity) ||
|
||||
@callback unquote(name)(unquote_splicing(type_args)) :: term
|
||||
end
|
||||
end
|
||||
@@ -271,15 +268,20 @@ defmodule Protocol.DSL do
|
||||
end
|
||||
|
||||
@doc false
|
||||
def callback_from_spec(module, name, arity) do
|
||||
tuple = { name, arity }
|
||||
specs = Module.get_attribute(module, :spec)
|
||||
def __spec__?(module, name, arity) do
|
||||
case :code.ensure_loaded(Kernel.Typespec) do
|
||||
{ :module, Kernel.Typespec } ->
|
||||
tuple = { name, arity }
|
||||
specs = Module.get_attribute(module, :spec)
|
||||
|
||||
found = lc { k, v } inlist specs, k == tuple do
|
||||
Kernel.Typespec.define_callback(module, tuple, v)
|
||||
true
|
||||
found = lc { k, v } inlist specs, k == tuple do
|
||||
Kernel.Typespec.define_callback(module, tuple, v)
|
||||
true
|
||||
end
|
||||
|
||||
found != []
|
||||
{ :error, _ } ->
|
||||
true
|
||||
end
|
||||
|
||||
found != []
|
||||
end
|
||||
end
|
||||
|
||||
+69
-36
@@ -1,14 +1,38 @@
|
||||
defrecord Range, [:first, :last] do
|
||||
defmodule Range do
|
||||
@moduledoc """
|
||||
Defines a Range.
|
||||
"""
|
||||
|
||||
@type t :: { Range, any, any }
|
||||
@type t(first, last) :: { Range, first, last }
|
||||
|
||||
@doc """
|
||||
Creates a new range.
|
||||
"""
|
||||
def new(first, last) do
|
||||
{ Range, first, last }
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns the first item of the range.
|
||||
"""
|
||||
def first({ Range, first, _ }) do
|
||||
first
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns the last item of the range.
|
||||
"""
|
||||
def last({ Range, _, last }) do
|
||||
last
|
||||
end
|
||||
end
|
||||
|
||||
defprotocol Range.Iterator do
|
||||
@doc """
|
||||
Reduces the range based on the type of the first argument.
|
||||
Returns the function that calculates the next item.
|
||||
"""
|
||||
def reduce(first, range, acc, fun)
|
||||
def next(first, range)
|
||||
|
||||
@doc """
|
||||
Count how many items are in the range.
|
||||
@@ -17,56 +41,65 @@ defprotocol Range.Iterator do
|
||||
end
|
||||
|
||||
defimpl Enumerable, for: Range do
|
||||
def reduce(first .. _ = range, acc, fun) do
|
||||
Range.Iterator.reduce(first, range, acc, fun)
|
||||
def reduce(first .. last = range, acc, fun) 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 }
|
||||
end
|
||||
|
||||
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
|
||||
reduce(next.(x), y, fun.(x, acc), fun, next, true)
|
||||
end
|
||||
|
||||
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 }
|
||||
end
|
||||
|
||||
def member?(first .. last, value) do
|
||||
first <= value and value <= last
|
||||
if first <= last do
|
||||
{ :ok, first <= value and value <= last }
|
||||
else
|
||||
{ :ok, last <= value and value <= first }
|
||||
end
|
||||
end
|
||||
|
||||
def count(first .. _ = range) do
|
||||
Range.Iterator.count(first, range)
|
||||
{ :ok, Range.Iterator.count(first, range) }
|
||||
end
|
||||
end
|
||||
|
||||
defimpl Range.Iterator, for: Integer do
|
||||
def reduce(first, Range[last: last], acc, fun) when is_integer(last) do
|
||||
reducer = if last >= first do
|
||||
fn(acc, fun) -> do_reducer_up(first, last, acc, fun) end
|
||||
def next(first, _ .. last) when is_integer(last) do
|
||||
if last >= first do
|
||||
&(&1 + 1)
|
||||
else
|
||||
fn(acc, fun) -> do_reducer_down(first, last, acc, fun) end
|
||||
&(&1 - 1)
|
||||
end
|
||||
Enumerable.Function.reduce(reducer, acc, fun)
|
||||
end
|
||||
|
||||
defp do_reducer_up(counter, last, acc, _fun) when counter > last do
|
||||
acc
|
||||
end
|
||||
|
||||
defp do_reducer_up(counter, last, acc, fun) do
|
||||
do_reducer_up(counter + 1, last, fun.(counter, acc), fun)
|
||||
end
|
||||
|
||||
defp do_reducer_down(counter, last, acc, _fun) when counter < last do
|
||||
acc
|
||||
end
|
||||
|
||||
defp do_reducer_down(counter, last, acc, fun) do
|
||||
do_reducer_down(counter - 1, last, fun.(counter, acc), fun)
|
||||
end
|
||||
|
||||
def count(first, Range[last: last]) when is_integer(last) and last >= first do
|
||||
last - first + 1
|
||||
end
|
||||
|
||||
def count(first, Range[last: last]) when is_integer(last) do
|
||||
first - last + 1
|
||||
def count(first, _ .. last) when is_integer(last) do
|
||||
if last >= first do
|
||||
last - first + 1
|
||||
else
|
||||
first - last + 1
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defimpl Inspect, for: Range do
|
||||
def inspect(Range[first: first, last: last], opts) do
|
||||
Inspect.Algebra.concat [Kernel.inspect(first, opts), "..", Kernel.inspect(last, opts)]
|
||||
import Inspect.Algebra
|
||||
|
||||
def inspect(first .. last, opts) do
|
||||
concat [to_doc(first, opts), "..", to_doc(last, opts)]
|
||||
end
|
||||
end
|
||||
|
||||
+76
-172
@@ -1,5 +1,5 @@
|
||||
defmodule Record do
|
||||
@moduledoc %S"""
|
||||
@moduledoc ~S"""
|
||||
Functions for working with Records.
|
||||
|
||||
A record is a tagged tuple which contains one or more elements
|
||||
@@ -84,41 +84,9 @@ defmodule Record do
|
||||
|
||||
defrecord User, name: "José", age: 25
|
||||
|
||||
Notice that, unlike `defrecordp`, `defrecord` expects an
|
||||
alias as the first argument. This is because `defrecord` is going
|
||||
to create a module named `User` with all the relevant metadata.
|
||||
This module can then be imported and we can manipulate the user
|
||||
as with `defrecordp`:
|
||||
|
||||
Record.import User, as: :user
|
||||
user() #=> User[name: "José", age: 25]
|
||||
user(age: 26) #=> User[name: "José", age: 26]
|
||||
|
||||
Notice that now since the record definition is accessible, Elixir
|
||||
shows the record nicely formatted, no longer as a simple tuple. We
|
||||
can get the raw formatting by passing `raw: true` to `inspect`:
|
||||
|
||||
inspect user(), raw: true
|
||||
{ User, "José", 25 }
|
||||
|
||||
Since working with external records is common, Elixir allows
|
||||
developers to skip importing the record altogether in favor
|
||||
of a `Module[args]` syntax:
|
||||
|
||||
# Skipping a field uses its default value
|
||||
User[] #=> User[name: "José", age: 25]
|
||||
User[age: 26] #=> User[name: "José", age: 26]
|
||||
|
||||
The macro name is replaced by the module name and the parentheses
|
||||
are replaced by brackets. When this syntax is used, there is no
|
||||
need to import the record.
|
||||
|
||||
Before we sum up the differences between `defrecord` and
|
||||
`defrecordp`, there is one last functionality introduced by
|
||||
`defrecord` that we need to discuss.
|
||||
|
||||
## Runtime access
|
||||
|
||||
All the functionality discussed above happens at compilation time.
|
||||
This means that both `user(age: 26)` and `User[age: 26]` are expanded
|
||||
into a tuple at compile time.
|
||||
@@ -150,7 +118,7 @@ defmodule Record do
|
||||
whenever you need to dynamically set or update fields.
|
||||
|
||||
The standard library contains excellent examples of both use cases,
|
||||
with [`HashDict`](HashDict.html) being implemented with `defrecordp`
|
||||
with [`HashDict`](HashDict.html) being implemented with `defrecordp`
|
||||
and [`Range`](Range.html) with `defrecord`.
|
||||
|
||||
You can learn more about records in the `Kernel.defrecord/3` docs. Now
|
||||
@@ -188,16 +156,16 @@ defmodule Record do
|
||||
@type t :: tuple
|
||||
|
||||
@doc """
|
||||
Extract record information from an Erlang file.
|
||||
|
||||
Extract record information from an Erlang file.
|
||||
|
||||
Returns the fields as a list of tuples.
|
||||
|
||||
## Examples
|
||||
|
||||
> Record.extract(:file_info, from_lib: "kernel/include/file.hrl")
|
||||
[size: :undefined, type: :undefined, access: :undefined, atime: :undefined,
|
||||
mtime: :undefined, ctime: :undefined, mode: :undefined, links: :undefined,
|
||||
major_device: :undefined, minor_device: :undefined, inode: :undefined,
|
||||
Record.extract(:file_info, from_lib: "kernel/include/file.hrl")
|
||||
#=> [size: :undefined, type: :undefined, access: :undefined, atime: :undefined,
|
||||
mtime: :undefined, ctime: :undefined, mode: :undefined, links: :undefined,
|
||||
major_device: :undefined, minor_device: :undefined, inode: :undefined,
|
||||
uid: :undefined, gid: :undefined]
|
||||
|
||||
defrecord FileInfo, Record.extract(:file_info, from_lib: "kernel/include/file.hrl")
|
||||
@@ -207,36 +175,6 @@ defmodule Record do
|
||||
Record.Extractor.retrieve(name, opts)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Import a public record definition as a set of private macros.
|
||||
|
||||
Macros defined as in `Kernel.defrecordp/3`. This is useful when one
|
||||
desires to manipulate a record via a set of macros instead
|
||||
of the regular access syntax.
|
||||
|
||||
## Example
|
||||
|
||||
defmodule Test do
|
||||
Record.import File.Stat, as: :file_stat
|
||||
|
||||
def size(file_stat(size: size)), do: size
|
||||
end
|
||||
|
||||
"""
|
||||
defmacro import(module, as: name) do
|
||||
quote do
|
||||
module = unquote(module)
|
||||
|
||||
fields = if module == __MODULE__ do
|
||||
@record_fields
|
||||
else
|
||||
module.__record__(:fields)
|
||||
end
|
||||
|
||||
Record.defmacros(unquote(name), fields, __ENV__, module)
|
||||
end
|
||||
end
|
||||
|
||||
@doc false
|
||||
def defrecord(name, fields, opts) do
|
||||
block = Keyword.get(opts, :do, nil)
|
||||
@@ -268,30 +206,52 @@ defmodule Record do
|
||||
defp record_check!(_), do: :ok
|
||||
|
||||
@doc false
|
||||
def defrecordp(name, tag, fields) when is_atom(name) and is_atom(tag) and is_list(fields) do
|
||||
{ fields, types, def_type } = recordp_split(fields, [], [], false)
|
||||
type = binary_to_atom(atom_to_binary(name) <> "_t")
|
||||
tag = tag || name
|
||||
def defrecordp(name, tag, fields) do
|
||||
case recordp_split(fields, [], [], false) do
|
||||
{ :ok, fields, types, def_type } ->
|
||||
types = Macro.escape(types)
|
||||
|
||||
quote do
|
||||
Record.defmacros(unquote(name), unquote(fields), __ENV__, unquote(tag))
|
||||
# bind_quoted isn't available when bootstrapping record
|
||||
quoted = quote [unquote: false] do
|
||||
Record.defmacros(name, fields, __ENV__, tag)
|
||||
|
||||
if unquote(def_type) do
|
||||
@typep unquote(type)() :: { unquote(tag), unquote_splicing(types) }
|
||||
end
|
||||
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.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
|
||||
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
|
||||
recordp_split(t, [other|defaults], [quote(do: term)|types], def_type)
|
||||
end
|
||||
|
||||
defp recordp_split([], defaults, types, def_type) do
|
||||
{ :lists.reverse(defaults), :lists.reverse(types), def_type }
|
||||
{ :ok, :lists.reverse(defaults), :lists.reverse(types), def_type }
|
||||
end
|
||||
|
||||
defp recordp_split(_, _, _, _) do
|
||||
:error
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -347,16 +307,20 @@ defmodule Record do
|
||||
accessor_specs(values, 1, [])
|
||||
]
|
||||
|
||||
if env == Macro.Env do
|
||||
Module.eval_quoted(env, contents, [], [])
|
||||
else
|
||||
Module.eval_quoted(env.module, contents, [], env.location)
|
||||
# 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, [], env.location)
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Define macros for manipulating records.
|
||||
|
||||
Define macros for manipulating records.
|
||||
|
||||
This is called
|
||||
directly by `Kernel.defrecordp/3`. It expects the macro name, the
|
||||
record values and the environment.
|
||||
@@ -368,7 +332,9 @@ defmodule Record do
|
||||
end
|
||||
|
||||
"""
|
||||
def defmacros(name, values, env, tag // nil) do
|
||||
def defmacros(name, values, env, tag \\ nil)
|
||||
when is_atom(name) and is_list(values) and is_atom(tag) do
|
||||
|
||||
escaped = lc value inlist values do
|
||||
{ key, value } = convert_value(value)
|
||||
{ key, Macro.escape(value) }
|
||||
@@ -381,10 +347,6 @@ defmodule Record do
|
||||
Record.access(unquote(tag), unquote(escaped), [], __CALLER__)
|
||||
end
|
||||
|
||||
defmacrop unquote(name)(record) when is_tuple(record) do
|
||||
Record.to_keywords(unquote(tag), unquote(escaped), record)
|
||||
end
|
||||
|
||||
defmacrop unquote(name)(args) do
|
||||
Record.access(unquote(tag), unquote(escaped), args, __CALLER__)
|
||||
end
|
||||
@@ -408,7 +370,7 @@ defmodule Record do
|
||||
end
|
||||
|
||||
# Store fields that can be optimized and that cannot be
|
||||
# optimized as they are overriden
|
||||
# optimized as they are overridden
|
||||
@doc false
|
||||
def __on_definition__(env, kind, name, args, _guards, _body) do
|
||||
tuple = { name, length(args) }
|
||||
@@ -425,14 +387,24 @@ defmodule Record do
|
||||
Module.put_attribute(module, :record_optimizable, functions)
|
||||
end
|
||||
|
||||
@doc false
|
||||
# Implements the access macro used by records.
|
||||
# It returns the index of the given field.
|
||||
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
|
||||
|
||||
# Implements the access macro used by records.
|
||||
# It returns a quoted expression that defines
|
||||
# a record or a match in case the record is
|
||||
# inside a match.
|
||||
@doc false
|
||||
def access(atom, fields, keyword, caller) do
|
||||
unless is_keyword(keyword) do
|
||||
raise ArgumentError, message: "expected contents inside brackets to be a keyword list, got: #{inspect keyword}"
|
||||
raise ArgumentError, message: "expected contents inside brackets to be a keyword list or an atom, got: #{inspect keyword}"
|
||||
end
|
||||
|
||||
in_match = caller.in_match?
|
||||
@@ -459,31 +431,10 @@ defmodule Record do
|
||||
|
||||
case remaining do
|
||||
[] ->
|
||||
quote do: { unquote_splicing([atom|match]) }
|
||||
{ :{}, [], [atom|match] }
|
||||
_ ->
|
||||
keys = lc { key, _ } inlist remaining, do: key
|
||||
raise ArgumentError, message: "record #{inspect atom} does not have the keys: #{inspect keys}"
|
||||
end
|
||||
end
|
||||
|
||||
# Implements to_keywords macro defined by defmacros.
|
||||
# It returns a quoted expression that represents
|
||||
# converting record to keywords list.
|
||||
@doc false
|
||||
def to_keywords(_atom, fields, record) do
|
||||
{ var, extra } = cache_var(record)
|
||||
|
||||
keywords = Enum.map fields,
|
||||
fn { key, _default } ->
|
||||
index = find_index(fields, key, 0)
|
||||
quote do
|
||||
{ unquote(key), :erlang.element(unquote(index + 2), unquote(var)) }
|
||||
end
|
||||
end
|
||||
|
||||
quote do
|
||||
unquote_splicing(extra)
|
||||
unquote(keywords)
|
||||
raise ArgumentError, message: "record #{inspect atom} does not have the key: #{inspect hd(keys)}"
|
||||
end
|
||||
end
|
||||
|
||||
@@ -495,8 +446,6 @@ defmodule Record do
|
||||
get(atom, fields, record, args)
|
||||
is_keyword(args) ->
|
||||
update(atom, fields, record, args, caller)
|
||||
is_list(args) ->
|
||||
to_list(atom, fields, record, args)
|
||||
true ->
|
||||
raise ArgumentError, message: "expected arguments to be a compile time atom, list or keywords"
|
||||
end
|
||||
@@ -540,42 +489,8 @@ defmodule Record do
|
||||
end
|
||||
end
|
||||
|
||||
# Implements to_list macro defined by defmacros.
|
||||
# It returns a quoted expression that represents
|
||||
# extracting given fields from record.
|
||||
@doc false
|
||||
defp to_list(atom, fields, record, keys) do
|
||||
unless is_list(fields) do
|
||||
raise ArgumentError, message: "expected arguments to be a compile time list"
|
||||
end
|
||||
|
||||
{ var, extra } = cache_var(record)
|
||||
|
||||
list = Enum.map keys,
|
||||
fn(key) ->
|
||||
index = find_index(fields, key, 0)
|
||||
if index do
|
||||
quote do: :erlang.element(unquote(index + 2), unquote(var))
|
||||
else
|
||||
raise ArgumentError, message: "record #{inspect atom} does not have the key: #{inspect key}"
|
||||
end
|
||||
end
|
||||
|
||||
quote do
|
||||
unquote_splicing(extra)
|
||||
unquote(list)
|
||||
end
|
||||
end
|
||||
|
||||
defp cache_var({ var, _, kind } = tuple) when is_atom(var) and is_atom(kind) do
|
||||
{ tuple, [] }
|
||||
end
|
||||
|
||||
defp cache_var(other) do
|
||||
quote do
|
||||
{ x, [x = unquote(other)] }
|
||||
end
|
||||
end
|
||||
|
||||
## Function generation
|
||||
|
||||
@@ -685,7 +600,7 @@ defmodule Record do
|
||||
quote do
|
||||
@doc false
|
||||
def to_keywords(record) do
|
||||
unquote(:orddict.from_list(sorted))
|
||||
unquote(sorted)
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -780,11 +695,11 @@ defmodule Record do
|
||||
end
|
||||
|
||||
defp updater_lookup(k, key, values) do
|
||||
v = find_index(values, key, 1)
|
||||
index = find_index(values, key, 0)
|
||||
|
||||
quote do
|
||||
case :lists.keyfind(unquote(k), 1, keywords) do
|
||||
false -> elem(record, unquote(v))
|
||||
false -> :erlang.element(unquote(index + 2), record)
|
||||
{_, value} -> value
|
||||
end
|
||||
end
|
||||
@@ -807,8 +722,7 @@ defmodule Record do
|
||||
|
||||
defp core_specs(values) do
|
||||
types = lc { _, _, spec } inlist values, do: spec
|
||||
options = if values == [], do: [], else: [options_specs(values)]
|
||||
values_specs = if values == [], do: [], else: values_specs(values)
|
||||
options = lc { k, _, v } inlist values, do: { k, v }
|
||||
|
||||
quote do
|
||||
unless Kernel.Typespec.defines_type?(__MODULE__, :t, 0) do
|
||||
@@ -816,7 +730,7 @@ defmodule Record do
|
||||
end
|
||||
|
||||
unless Kernel.Typespec.defines_type?(__MODULE__, :options, 0) do
|
||||
@type options :: unquote(options) | [{String.t, unquote(values_specs)}]
|
||||
@type options :: unquote(options) | [{String.t, term}]
|
||||
end
|
||||
|
||||
@spec new :: t
|
||||
@@ -829,17 +743,6 @@ defmodule Record do
|
||||
end
|
||||
end
|
||||
|
||||
defp options_specs([{ k, _, v }|t]) do
|
||||
:lists.foldl fn { k, _, v }, acc ->
|
||||
{ :|, [], [{ k, v }, acc] }
|
||||
end, { k, v }, t
|
||||
end
|
||||
defp values_specs([{ _, _, v }|t]) do
|
||||
:lists.foldl fn { _, _, v }, acc ->
|
||||
{ :|, [], [v, acc] }
|
||||
end, v, t
|
||||
end
|
||||
|
||||
defp accessor_specs([{ :__exception__, _, _ }|t], 1, acc) do
|
||||
accessor_specs(t, 2, acc)
|
||||
end
|
||||
@@ -890,6 +793,7 @@ defmodule Record 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
|
||||
|
||||
@@ -6,10 +6,11 @@ defmodule Record.Extractor do
|
||||
def retrieve(name, from: file) when is_binary(file) do
|
||||
file = String.to_char_list!(file)
|
||||
|
||||
case :code.where_is_file(file) do
|
||||
:non_existing -> realfile = file
|
||||
realfile -> :ok
|
||||
end
|
||||
realfile =
|
||||
case :code.where_is_file(file) do
|
||||
:non_existing -> file
|
||||
realfile -> realfile
|
||||
end
|
||||
|
||||
retrieve_record(name, realfile)
|
||||
end
|
||||
|
||||
+48
-47
@@ -1,17 +1,17 @@
|
||||
defmodule Regex do
|
||||
@moduledoc %S"""
|
||||
@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).
|
||||
|
||||
Regular expressions in Elixir can be created using `Regex.compile!`
|
||||
or using the special form with `%r`:
|
||||
Regular expressions in Elixir can be created using `Regex.compile!/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
|
||||
~r/foo/i
|
||||
|
||||
The `re` module provides several options, the ones available in Elixir, followed by
|
||||
their shortcut in parenthesis, are:
|
||||
@@ -37,9 +37,6 @@ 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).
|
||||
"""
|
||||
|
||||
defrecordp :regex, Regex, [:re_pattern, :source, :options, :groups]
|
||||
@@ -51,7 +48,7 @@ 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,
|
||||
@@ -60,13 +57,13 @@ defmodule Regex do
|
||||
## 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 }
|
||||
def compile(source, options // "")
|
||||
def compile(source, options \\ "")
|
||||
|
||||
def compile(source, options) when is_binary(options) do
|
||||
case translate_options(options) do
|
||||
@@ -100,7 +97,7 @@ defmodule Regex do
|
||||
Compiles the regular expression according to the given options.
|
||||
Fails with `Regex.CompileError` if the regex cannot be compiled.
|
||||
"""
|
||||
def compile!(source, options // "") 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}"
|
||||
@@ -112,13 +109,14 @@ 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
|
||||
_ = return_for(string)
|
||||
:re.run(string, compiled, [{ :capture, :none }]) == :match
|
||||
end
|
||||
|
||||
@@ -131,15 +129,15 @@ defmodule Regex do
|
||||
|
||||
## 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, string, options \\ [])
|
||||
|
||||
def run(regex(re_pattern: compiled, groups: groups), string, options) do
|
||||
return = Keyword.get(options, :return, return_for(string))
|
||||
@@ -163,15 +161,15 @@ defmodule Regex do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Regex.named_captures(%r/c(?<foo>d)/g, "abcd")
|
||||
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")
|
||||
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/a(?<foo>b)c(?<bar>d)/g, "efgh")
|
||||
nil
|
||||
|
||||
"""
|
||||
def named_captures(regex(groups: groups) = regex, string, options // []) do
|
||||
def named_captures(regex(groups: groups) = regex, string, options \\ []) do
|
||||
options = Keyword.put_new(options, :capture, :groups)
|
||||
results = run(regex, string, options)
|
||||
if results, do: Enum.zip(groups, results)
|
||||
@@ -189,7 +187,7 @@ defmodule Regex do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Regex.source(%r(foo))
|
||||
iex> Regex.source(~r(foo))
|
||||
"foo"
|
||||
|
||||
"""
|
||||
@@ -202,7 +200,7 @@ defmodule Regex do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Regex.opts(%r(foo)m)
|
||||
iex> Regex.opts(~r(foo)m)
|
||||
"m"
|
||||
|
||||
"""
|
||||
@@ -215,7 +213,7 @@ defmodule Regex do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Regex.groups(%r/(?<foo>bar)/g)
|
||||
iex> Regex.groups(~r/(?<foo>bar)/g)
|
||||
[:foo]
|
||||
|
||||
"""
|
||||
@@ -237,15 +235,15 @@ defmodule Regex do
|
||||
|
||||
## 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, string, options \\ [])
|
||||
|
||||
def scan(regex(re_pattern: compiled, groups: groups), string, options) do
|
||||
return = Keyword.get(options, :return, return_for(string))
|
||||
@@ -269,19 +267,19 @@ defmodule Regex do
|
||||
If no number of parts is given, it defaults to `:infinity`.
|
||||
|
||||
## 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, string, options \\ [])
|
||||
|
||||
def split(regex(re_pattern: compiled), string, options) do
|
||||
parts =
|
||||
@@ -302,7 +300,7 @@ defmodule Regex do
|
||||
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.
|
||||
|
||||
@@ -313,29 +311,29 @@ defmodule Regex do
|
||||
|
||||
## 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", "[&]")
|
||||
iex> Regex.replace(~r/b/, "abc", "[&]")
|
||||
"a[b]c"
|
||||
iex> Regex.replace(%r/b/, "abc", "[\\&]")
|
||||
iex> Regex.replace(~r/b/, "abc", "[\\&]")
|
||||
"a[&]c"
|
||||
iex> Regex.replace(%r/(b)/, "abc", "[\\1]")
|
||||
iex> Regex.replace(~r/(b)/, "abc", "[\\1]")
|
||||
"a[b]c"
|
||||
|
||||
"""
|
||||
def replace(regex(re_pattern: compiled), string, replacement, options // []) do
|
||||
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)
|
||||
end
|
||||
|
||||
{ :ok, pattern } = :re.compile(%S"[.^$*+?()[{\\\|\s#]", [:unicode])
|
||||
{ :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
|
||||
@@ -346,7 +344,7 @@ defmodule Regex do
|
||||
"\\\\what\\ if"
|
||||
|
||||
"""
|
||||
@spec escape(String.t | char_list) :: String.t | char_list
|
||||
@spec escape(String.t) :: String.t
|
||||
def escape(string) do
|
||||
:re.replace(string, @escape_pattern, "\\\\&", [:global, { :return, return_for(string) }])
|
||||
end
|
||||
@@ -366,7 +364,10 @@ 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 return_for(element) when is_list(element) do
|
||||
IO.write :stderr, "Passing char lists to Regex is deprecated, please use binaries instead\n#{Exception.format_stacktrace}"
|
||||
:list
|
||||
end
|
||||
|
||||
defp translate_options(<<?u, t :: binary>>) do
|
||||
IO.write "The /u flag for regular expressions is no longer needed\n#{Exception.format_stacktrace}"
|
||||
@@ -382,7 +383,7 @@ defmodule Regex do
|
||||
defp translate_options(<<>>), do: []
|
||||
defp translate_options(rest), do: { :error, rest }
|
||||
|
||||
{ :ok, pattern } = :re.compile(%S"\(\?<(?<G>[^>]*)>")
|
||||
{ :ok, pattern } = :re.compile(~S"\(\?<(?<G>[^>]*)>")
|
||||
@groups_pattern pattern
|
||||
|
||||
defp parse_groups(source) do
|
||||
|
||||
+112
-17
@@ -1,5 +1,5 @@
|
||||
defmodule Set do
|
||||
@moduledoc %S"""
|
||||
@moduledoc ~S"""
|
||||
This module specifies the Set API expected to be
|
||||
implemented by different representations.
|
||||
|
||||
@@ -38,6 +38,9 @@ defmodule Set do
|
||||
@type values :: [ value ]
|
||||
@type t :: tuple
|
||||
|
||||
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
|
||||
@@ -46,6 +49,7 @@ defmodule Set do
|
||||
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()
|
||||
@@ -67,11 +71,11 @@ defmodule Set do
|
||||
## Examples
|
||||
|
||||
iex> s = HashSet.new([1, 2, 3])
|
||||
...> Set.delete(s, 4) |> HashSet.to_list
|
||||
...> Set.delete(s, 4) |> Enum.sort
|
||||
[1, 2, 3]
|
||||
|
||||
iex> s = HashSet.new([1, 2, 3])
|
||||
...> Set.delete(s, 2) |> HashSet.to_list
|
||||
...> Set.delete(s, 2) |> Enum.sort
|
||||
[1, 3]
|
||||
"""
|
||||
@spec delete(t, value) :: t
|
||||
@@ -82,20 +86,37 @@ defmodule Set do
|
||||
@doc """
|
||||
Returns a set that is `set1` without the members of `set2`.
|
||||
|
||||
Notice this function is polymorphic as it calculates the difference
|
||||
for of any type. Each set implementation also provides a `difference`
|
||||
function, but they can only work with sets of the same type.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Set.difference(HashSet.new([1,2]), HashSet.new([2,3,4])) |> HashSet.to_list
|
||||
iex> Set.difference(HashSet.new([1,2]), HashSet.new([2,3,4])) |> Enum.sort
|
||||
[1]
|
||||
|
||||
"""
|
||||
@spec difference(t, t) :: t
|
||||
def difference(set1, set2) do
|
||||
target(set1).difference(set1, set2)
|
||||
target1 = target(set1)
|
||||
target2 = target(set2)
|
||||
|
||||
if target1 == target2 do
|
||||
target1.difference(set1, set2)
|
||||
else
|
||||
target2.reduce(set2, { :cont, set1 }, fn v, acc ->
|
||||
{ :cont, target1.delete(acc, v) }
|
||||
end) |> elem(1)
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Checks if `set1` and `set2` have no members in common.
|
||||
|
||||
Notice this function is polymorphic as it checks for disjoint sets of
|
||||
any type. Each set implementation also provides a `disjoint?` function,
|
||||
but they can only work with sets of the same type.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Set.disjoint?(HashSet.new([1, 2]), HashSet.new([3, 4]))
|
||||
@@ -106,7 +127,19 @@ defmodule Set do
|
||||
"""
|
||||
@spec disjoint?(t, t) :: boolean
|
||||
def disjoint?(set1, set2) do
|
||||
target(set1).disjoint?(set1, set2)
|
||||
target1 = target(set1)
|
||||
target2 = target(set2)
|
||||
|
||||
if target1 == target2 do
|
||||
target1.disjoint?(set1, set2)
|
||||
else
|
||||
target2.reduce(set2, { :cont, true }, fn member, acc ->
|
||||
case target1.member?(set1, member) do
|
||||
false -> { :cont, acc }
|
||||
_ -> { :halt, false }
|
||||
end
|
||||
end) |> elem(1)
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -118,7 +151,11 @@ defmodule Set do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Checks if `set1` and `set2` are equal.
|
||||
Check if two sets are equal using `===`.
|
||||
|
||||
Notice this function is polymorphic as it compares sets of
|
||||
any type. Each set implementation also provides an `equal?`
|
||||
function, but they can only work with sets of the same type.
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -131,24 +168,49 @@ defmodule Set do
|
||||
"""
|
||||
@spec equal?(t, t) :: boolean
|
||||
def equal?(set1, set2) do
|
||||
target(set1).equal?(set1, set2)
|
||||
target1 = target(set1)
|
||||
target2 = target(set2)
|
||||
|
||||
cond do
|
||||
target1 == target2 ->
|
||||
target1.equal?(set1, set2)
|
||||
|
||||
target1.size(set1) == target2.size(set2) ->
|
||||
do_subset?(target1, target2, set1, set2)
|
||||
|
||||
true ->
|
||||
false
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns a set containing only members in common between `set1` and `set2`.
|
||||
|
||||
Notice this function is polymorphic as it calculates the intersection of
|
||||
any type. Each set implementation also provides a `intersection` function,
|
||||
but they can only work with sets of the same type.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Set.intersection(HashSet.new([1,2]), HashSet.new([2,3,4])) |> HashSet.to_list
|
||||
iex> Set.intersection(HashSet.new([1,2]), HashSet.new([2,3,4])) |> Enum.sort
|
||||
[2]
|
||||
|
||||
iex> Set.intersection(HashSet.new([1,2]), HashSet.new([3,4])) |> HashSet.to_list
|
||||
iex> Set.intersection(HashSet.new([1,2]), HashSet.new([3,4])) |> Enum.sort
|
||||
[]
|
||||
|
||||
"""
|
||||
@spec intersection(t, t) :: t
|
||||
def intersection(set1, set2) do
|
||||
target(set1).intersection(set1, set2)
|
||||
target1 = target(set1)
|
||||
target2 = target(set2)
|
||||
|
||||
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) }
|
||||
end) |> elem(1)
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -173,10 +235,10 @@ defmodule Set do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Set.put(HashSet.new([1, 2, 3]), 3) |> Set.to_list
|
||||
iex> Set.put(HashSet.new([1, 2, 3]), 3) |> Enum.sort
|
||||
[1, 2, 3]
|
||||
|
||||
iex> Set.put(HashSet.new([1, 2, 3]), 4) |> Set.to_list
|
||||
iex> Set.put(HashSet.new([1, 2, 3]), 4) |> Enum.sort
|
||||
[1, 2, 3, 4]
|
||||
|
||||
"""
|
||||
@@ -202,6 +264,10 @@ defmodule Set do
|
||||
@doc """
|
||||
Checks if `set1`'s members are all contained in `set2`.
|
||||
|
||||
Notice this function is polymorphic as it checks the subset for
|
||||
any type. Each set implementation also provides a `subset?` function,
|
||||
but they can only work with sets of the same type.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Set.subset?(HashSet.new([1, 2]), HashSet.new([1, 2, 3]))
|
||||
@@ -211,7 +277,14 @@ defmodule Set do
|
||||
"""
|
||||
@spec subset?(t, t) :: boolean
|
||||
def subset?(set1, set2) do
|
||||
target(set1).subset?(set1, set2)
|
||||
target1 = target(set1)
|
||||
target2 = target(set2)
|
||||
|
||||
if target1 == target2 do
|
||||
target1.subset?(set1, set2)
|
||||
else
|
||||
do_subset?(target1, target2, set1, set2)
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -219,7 +292,7 @@ defmodule Set do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> HashSet.to_list(HashSet.new([1, 2, 3]))
|
||||
iex> HashSet.to_list(HashSet.new([1, 2, 3])) |> Enum.sort
|
||||
[1,2,3]
|
||||
|
||||
"""
|
||||
@@ -231,15 +304,37 @@ defmodule Set do
|
||||
@doc """
|
||||
Returns a set containing all members of `set1` and `set2`.
|
||||
|
||||
Notice this function is polymorphic as it calculates the union of
|
||||
any type. Each set implementation also provides a `union` function,
|
||||
but they can only work with sets of the same type.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Set.union(HashSet.new([1,2]), HashSet.new([2,3,4])) |> HashSet.to_list
|
||||
iex> Set.union(HashSet.new([1,2]), HashSet.new([2,3,4])) |> Enum.sort
|
||||
[1,2,3,4]
|
||||
|
||||
"""
|
||||
@spec union(t, t) :: t
|
||||
def union(set1, set2) do
|
||||
target(set1).union(set1, set2)
|
||||
target1 = target(set1)
|
||||
target2 = target(set2)
|
||||
|
||||
if target1 == target2 do
|
||||
target1.union(set1, set2)
|
||||
else
|
||||
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 ->
|
||||
case target2.member?(set2, member) do
|
||||
true -> { :cont, acc }
|
||||
_ -> { :halt, false }
|
||||
end
|
||||
end) |> elem(1)
|
||||
end
|
||||
|
||||
defp unsupported_set(set) do
|
||||
|
||||
+851
-286
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,188 @@
|
||||
defmodule Stream.Reducers do
|
||||
# Collection of reducers shared by Enum and Stream.
|
||||
@moduledoc false
|
||||
|
||||
defmacro chunk(n, step, limit, f \\ nil) do
|
||||
quote do
|
||||
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 }
|
||||
else
|
||||
{ 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) }
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defmacro chunk_by(callback, f \\ nil) do
|
||||
quote do
|
||||
fn
|
||||
entry, acc(h, { buffer, value }, t) ->
|
||||
new_value = unquote(callback).(entry)
|
||||
if new_value == value do
|
||||
{ :cont, acc(h, { [entry|buffer], value }, t) }
|
||||
else
|
||||
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) }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defmacro drop(f \\ nil) do
|
||||
quote do
|
||||
fn
|
||||
_entry, acc(h, n, t) when n > 0 ->
|
||||
{ :cont, acc(h, n-1, t) }
|
||||
entry, acc(h, n, t) ->
|
||||
cont_with_acc(unquote(f), entry, h, n, t)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defmacro drop_while(callback, f \\ nil) do
|
||||
quote do
|
||||
fn entry, acc(h, bool, t) = orig ->
|
||||
if bool and unquote(callback).(entry) do
|
||||
{ :cont, orig }
|
||||
else
|
||||
cont_with_acc(unquote(f), entry, h, false, t)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defmacro filter(callback, f \\ nil) do
|
||||
quote do
|
||||
fn(entry, acc) ->
|
||||
if unquote(callback).(entry) do
|
||||
cont(unquote(f), entry, acc)
|
||||
else
|
||||
{ :cont, acc }
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defmacro filter_map(filter, mapper, f \\ nil) do
|
||||
quote do
|
||||
fn(entry, acc) ->
|
||||
if unquote(filter).(entry) do
|
||||
cont(unquote(f), unquote(mapper).(entry), acc)
|
||||
else
|
||||
{ :cont, acc }
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defmacro map(callback, f \\ nil) do
|
||||
quote do
|
||||
fn(entry, acc) ->
|
||||
cont(unquote(f), unquote(callback).(entry), acc)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defmacro reject(callback, f \\ nil) do
|
||||
quote do
|
||||
fn(entry, acc) ->
|
||||
unless unquote(callback).(entry) do
|
||||
cont(unquote(f), entry, acc)
|
||||
else
|
||||
{ :cont, acc }
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defmacro scan_2(callback, f \\ nil) 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) ->
|
||||
value = unquote(callback).(entry, acc)
|
||||
cont_with_acc(unquote(f), value, h, { :ok, value }, t)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defmacro scan_3(callback, f \\ nil) do
|
||||
quote do
|
||||
fn(entry, acc(h, acc, t)) ->
|
||||
value = unquote(callback).(entry, acc)
|
||||
cont_with_acc(unquote(f), value, h, value, t)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defmacro take(f \\ nil) do
|
||||
quote do
|
||||
fn(entry, acc(h, n, t) = orig) ->
|
||||
if n >= 1 do
|
||||
cont_with_acc(unquote(f), entry, h, n-1, t)
|
||||
else
|
||||
{ :halt, orig }
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defmacro take_every(nth, f \\ nil) do
|
||||
quote do
|
||||
fn
|
||||
entry, acc(h, n, t) when n === :first
|
||||
when n === unquote(nth) ->
|
||||
cont_with_acc(unquote(f), entry, h, 1, t)
|
||||
entry, acc(h, n, t) ->
|
||||
{ :cont, acc(h, n+1, t) }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defmacro take_while(callback, f \\ nil) do
|
||||
quote do
|
||||
fn(entry, acc) ->
|
||||
if unquote(callback).(entry) do
|
||||
cont(unquote(f), entry, acc)
|
||||
else
|
||||
{ :halt, acc }
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defmacro uniq(callback, f \\ nil) do
|
||||
quote do
|
||||
fn(entry, acc(h, prev, t) = acc) ->
|
||||
value = unquote(callback).(entry)
|
||||
if :lists.member(value, prev) do
|
||||
{ :cont, acc }
|
||||
else
|
||||
cont_with_acc(unquote(f), entry, h, [value|prev], t)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
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)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
+57
-37
@@ -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
|
||||
@@ -149,7 +149,7 @@ defmodule String do
|
||||
def printable?(<<?\a, t :: binary>>), do: printable?(t)
|
||||
|
||||
def printable?(<<>>), do: true
|
||||
def printable?(_), do: false
|
||||
def printable?(b) when is_binary(b), do: false
|
||||
|
||||
@doc """
|
||||
Divides a string into substrings at each Unicode whitespace
|
||||
@@ -168,7 +168,7 @@ 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.
|
||||
@@ -197,16 +197,16 @@ 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{,}, global: false)
|
||||
["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", ""]
|
||||
@@ -218,11 +218,11 @@ defmodule String do
|
||||
"""
|
||||
@spec split(t, t | [t] | Regex.t) :: [t]
|
||||
@spec split(t, t | [t] | Regex.t, Keyword.t) :: [t]
|
||||
def split(binary, pattern, options // [])
|
||||
def split(binary, pattern, options \\ [])
|
||||
|
||||
def split("", _pattern, _options), do: [""]
|
||||
|
||||
def split(binary, "", options), do: split(binary, %r"", options)
|
||||
def split(binary, "", options), do: split(binary, ~r"", options)
|
||||
|
||||
def split(binary, pattern, options) when is_regex(pattern) do
|
||||
Regex.split(pattern, binary, options)
|
||||
@@ -413,7 +413,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
|
||||
@@ -438,7 +438,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.
|
||||
@@ -482,7 +482,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`.
|
||||
@@ -497,9 +497,9 @@ defmodule String do
|
||||
"a-b,c"
|
||||
|
||||
The pattern can also be a regex. In those cases, one can give `\N`
|
||||
in the `replacement` string to access a specific catpure in the regex:
|
||||
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 `&`,
|
||||
@@ -519,7 +519,7 @@ 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 \\ [])
|
||||
|
||||
def replace(subject, pattern, replacement, options) when is_regex(pattern) do
|
||||
Regex.replace(pattern, subject, replacement, global: options[:global])
|
||||
@@ -562,7 +562,7 @@ defmodule String do
|
||||
do_reverse(next_grapheme(rest), [grapheme|acc])
|
||||
end
|
||||
|
||||
defp do_reverse(:no_grapheme, acc), do: iolist_to_binary(acc)
|
||||
defp do_reverse(nil, acc), do: iolist_to_binary(acc)
|
||||
|
||||
@doc """
|
||||
Returns a binary `subject` duplicated `n` times.
|
||||
@@ -602,7 +602,7 @@ defmodule String do
|
||||
Returns the next codepoint in a String.
|
||||
|
||||
The result is a tuple with the codepoint and the
|
||||
remaining of the string or `:no_codepoint` in case
|
||||
remaining of the string or `nil` in case
|
||||
the string reached its end.
|
||||
|
||||
As with other functions in the String module, this
|
||||
@@ -617,10 +617,10 @@ defmodule String do
|
||||
|
||||
"""
|
||||
@compile { :inline, next_codepoint: 1 }
|
||||
@spec next_codepoint(t) :: {codepoint, t} | :no_codepoint
|
||||
@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
|
||||
@@ -651,7 +651,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
|
||||
@@ -693,7 +693,7 @@ defmodule String do
|
||||
Returns the next grapheme in a String.
|
||||
|
||||
The result is a tuple with the grapheme and the
|
||||
remaining of the string or `:no_grapheme` in case
|
||||
remaining of the string or `nil` in case
|
||||
the String reached its end.
|
||||
|
||||
## Examples
|
||||
@@ -703,7 +703,7 @@ defmodule String do
|
||||
|
||||
"""
|
||||
@compile { :inline, next_grapheme: 1 }
|
||||
@spec next_grapheme(t) :: { grapheme, t } | :no_grapheme
|
||||
@spec next_grapheme(t) :: { grapheme, t } | nil
|
||||
defdelegate next_grapheme(string), to: String.Graphemes
|
||||
|
||||
@doc """
|
||||
@@ -722,7 +722,7 @@ defmodule String do
|
||||
def first(string) do
|
||||
case next_grapheme(string) do
|
||||
{ char, _ } -> char
|
||||
:no_grapheme -> nil
|
||||
nil -> nil
|
||||
end
|
||||
end
|
||||
|
||||
@@ -747,7 +747,7 @@ defmodule String do
|
||||
do_last(next_grapheme(rest), char)
|
||||
end
|
||||
|
||||
defp do_last(:no_grapheme, last_char), do: last_char
|
||||
defp do_last(nil, last_char), do: last_char
|
||||
|
||||
@doc """
|
||||
Returns the number of unicode graphemes in an utf8 string.
|
||||
@@ -769,7 +769,7 @@ defmodule String do
|
||||
1 + do_length(next_grapheme(rest))
|
||||
end
|
||||
|
||||
defp do_length(:no_grapheme), do: 0
|
||||
defp do_length(nil), do: 0
|
||||
|
||||
@doc """
|
||||
Returns the grapheme in the `position` of the given utf8 `string`.
|
||||
@@ -811,7 +811,7 @@ defmodule String do
|
||||
char
|
||||
end
|
||||
|
||||
defp do_at(:no_grapheme, _, _), do: nil
|
||||
defp do_at(nil, _, _), do: nil
|
||||
|
||||
@doc """
|
||||
Returns a substring starting at the offset given by the first, and
|
||||
@@ -934,11 +934,11 @@ defmodule String do
|
||||
acc <> char
|
||||
end
|
||||
|
||||
defp do_slice(:no_grapheme, start_pos, _, current_pos, acc) when start_pos == current_pos do
|
||||
defp do_slice(nil, start_pos, _, current_pos, acc) when start_pos == current_pos do
|
||||
acc
|
||||
end
|
||||
|
||||
defp do_slice(:no_grapheme, _, _, _, acc) do
|
||||
defp do_slice(nil, _, _, _, acc) do
|
||||
case acc do
|
||||
"" -> nil
|
||||
_ -> acc
|
||||
@@ -974,7 +974,7 @@ defmodule String do
|
||||
end
|
||||
|
||||
defp do_starts_with(string, prefix) when is_binary(prefix) do
|
||||
match?({0, _}, :binary.match(string, prefix))
|
||||
Kernel.match?({0, _}, :binary.match(string, prefix))
|
||||
end
|
||||
|
||||
defp do_starts_with(_, _) do
|
||||
@@ -1021,7 +1021,24 @@ defmodule String do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns `true` if `string` contains match, otherwise `false`.
|
||||
Check if `string` matches the given regular expression.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> String.match?("foo", ~r/foo/)
|
||||
true
|
||||
iex> String.match?("bar", ~r/foo/)
|
||||
false
|
||||
|
||||
"""
|
||||
@spec match?(t, Regex.t) :: boolean
|
||||
def match?(string, regex) do
|
||||
Regex.match?(regex, string)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Check if `string` contains any of the given `contents`.
|
||||
|
||||
`matches` can be either a single string or a list of strings.
|
||||
|
||||
## Examples
|
||||
@@ -1036,12 +1053,12 @@ defmodule String do
|
||||
"""
|
||||
@spec contains?(t, t | [t]) :: boolean
|
||||
|
||||
def contains?(string, matches) when is_list(matches) do
|
||||
Enum.any?(matches, &do_contains(string, &1))
|
||||
def contains?(string, contents) when is_list(contents) do
|
||||
Enum.any?(contents, &do_contains(string, &1))
|
||||
end
|
||||
|
||||
def contains?(string, match) do
|
||||
do_contains(string, match)
|
||||
def contains?(string, content) do
|
||||
do_contains(string, content)
|
||||
end
|
||||
|
||||
defp do_contains(string, "") when is_binary(string) do
|
||||
@@ -1056,9 +1073,12 @@ defmodule String do
|
||||
raise ArgumentError
|
||||
end
|
||||
|
||||
defexception UnicodeConversionError, encoded: nil, rest: nil, kind: nil do
|
||||
def message(exception) do
|
||||
"#{exception.kind} #{detail(exception.rest)}"
|
||||
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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -152,7 +152,7 @@ defmodule Supervisor.Behaviour do
|
||||
|
||||
#{@child_doc}
|
||||
"""
|
||||
def worker(module, args, options // []) do
|
||||
def worker(module, args, options \\ []) do
|
||||
child(:worker, module, args, options)
|
||||
end
|
||||
|
||||
@@ -166,7 +166,7 @@ defmodule Supervisor.Behaviour do
|
||||
|
||||
#{@child_doc}
|
||||
"""
|
||||
def supervisor(module, args, options // []) do
|
||||
def supervisor(module, args, options \\ []) do
|
||||
options = Keyword.update(options, :shutdown, :infinity, fn(x) -> x end)
|
||||
child(:supervisor, module, args, options)
|
||||
end
|
||||
|
||||
+15
-20
@@ -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
|
||||
@@ -28,8 +19,8 @@ defmodule System do
|
||||
|
||||
# Read and strip the version from the `VERSION` file.
|
||||
defmacrop get_version do
|
||||
case read_stripped("VERSION") do
|
||||
"" -> raise CompileError, message: "could not read the version number from VERSION"
|
||||
case read_stripped(:filename.join(__DIR__, "../../../VERSION")) do
|
||||
"" -> raise RuntimeError, message: "could not read the version number from VERSION"
|
||||
data -> data
|
||||
end
|
||||
end
|
||||
@@ -38,7 +29,7 @@ defmodule System do
|
||||
# the most recent tag. If that is not available, tries to read the commit hash
|
||||
# from .git/HEAD. If that fails, returns an empty string.
|
||||
defmacrop get_describe do
|
||||
dirpath = ".git"
|
||||
dirpath = :filename.join(__DIR__, "../../../.git")
|
||||
case :file.read_file_info(dirpath) do
|
||||
{ :ok, _ } ->
|
||||
if :os.find_executable('git') do
|
||||
@@ -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 """
|
||||
@@ -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
|
||||
@@ -376,7 +371,7 @@ defmodule System do
|
||||
"""
|
||||
@spec halt() :: no_return
|
||||
@spec halt(non_neg_integer | binary | :abort) :: no_return
|
||||
def halt(status // 0)
|
||||
def halt(status \\ 0)
|
||||
|
||||
def halt(status) when is_integer(status) or status == :abort do
|
||||
:erlang.halt(status)
|
||||
|
||||
+97
-43
@@ -31,8 +31,18 @@ defmodule URI do
|
||||
|
||||
@doc """
|
||||
Returns the default port for a given scheme.
|
||||
|
||||
If the scheme is unknown to URI, returns `nil`.
|
||||
Any scheme may be registered via `default_port/2`.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> URI.default_port("ftp")
|
||||
21
|
||||
|
||||
iex> URI.default_port("ponzi")
|
||||
nil
|
||||
|
||||
"""
|
||||
def default_port(scheme) when is_binary(scheme) do
|
||||
{ :ok, dict } = :application.get_env(:elixir, :uri)
|
||||
@@ -48,44 +58,71 @@ defmodule URI do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Encodes an enumerable into a query string.
|
||||
|
||||
Takes an enumerable (containing a sequence of two-item tuples)
|
||||
and returns a string of the form "k=v&k2=v2..." where keys and values are
|
||||
URL encoded as per `encode/1`. Keys and values can be any term
|
||||
that implements the `String.Chars` protocol (i.e. can be converted
|
||||
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).
|
||||
|
||||
## Examples
|
||||
|
||||
iex> hd = HashDict.new([{"foo", 1}, {"bar", "2"}])
|
||||
iex> URI.encode_query(hd)
|
||||
"bar=2&foo=1"
|
||||
|
||||
"""
|
||||
def encode_query(l), do: Enum.map_join(l, "&", &pair/1)
|
||||
|
||||
@doc """
|
||||
Given a query string of the form "key1=value1&key=value2...", produces an
|
||||
orddict with one entry for each key-value pair. Each key and value will be a
|
||||
binary. It also does percent-unescaping of both keys and values.
|
||||
Decodes a query string into a `HashDict`.
|
||||
|
||||
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
|
||||
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"]
|
||||
|
||||
"""
|
||||
def decode_query(q, dict // HashDict.new) when is_binary(q) do
|
||||
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
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns an iterator function over the query string that decodes
|
||||
the query string in steps.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> URI.query_decoder("foo=1&bar=2") |> Enum.map &(&1)
|
||||
[{"foo", "1"}, {"bar", "2"}]
|
||||
|
||||
"""
|
||||
def query_decoder(q) when is_binary(q) do
|
||||
fn(acc, fun) ->
|
||||
do_decoder(q, acc, fun)
|
||||
end
|
||||
Stream.unfold(q, &do_decoder/1)
|
||||
end
|
||||
|
||||
defp do_decoder("", acc, _fun) do
|
||||
acc
|
||||
defp do_decoder("") do
|
||||
nil
|
||||
end
|
||||
|
||||
defp do_decoder(q, acc, fun) do
|
||||
next =
|
||||
defp do_decoder(q) do
|
||||
{ first, next } =
|
||||
case :binary.split(q, "&") do
|
||||
[first, rest] -> rest
|
||||
[first] -> ""
|
||||
[first, rest] -> { first, rest }
|
||||
[first] -> { first, "" }
|
||||
end
|
||||
|
||||
current =
|
||||
@@ -94,7 +131,7 @@ defmodule URI do
|
||||
[ key ] -> { decode(key), nil }
|
||||
end
|
||||
|
||||
do_decoder(next, fun.(current, acc), fun)
|
||||
{ current, next }
|
||||
end
|
||||
|
||||
defp pair({k, v}) do
|
||||
@@ -102,7 +139,13 @@ defmodule URI do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Percent (URL) encodes a URI.
|
||||
Percent-escape a URI.
|
||||
|
||||
## Example
|
||||
|
||||
iex> URI.encode("http://elixir-lang.com/getting_started/2.html")
|
||||
"http%3A%2F%2Felixir-lang.com%2Fgetting_started%2F2.html"
|
||||
|
||||
"""
|
||||
def encode(s), do: bc(<<c>> inbits s, do: <<percent(c) :: binary>>)
|
||||
|
||||
@@ -124,21 +167,34 @@ defmodule URI do
|
||||
defp hex(n), do: <<n + ?A - 10>>
|
||||
|
||||
@doc """
|
||||
Unpercent (URL) decodes a URI.
|
||||
Percent-unescape a URI.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> URI.decode("http%3A%2F%2Felixir-lang.com")
|
||||
"http://elixir-lang.com"
|
||||
|
||||
"""
|
||||
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
|
||||
@@ -146,24 +202,22 @@ defmodule URI do
|
||||
@doc """
|
||||
Parses a URI into components.
|
||||
|
||||
URIs have portions that are handled specially for the
|
||||
particular scheme of the URI. For example, http and https
|
||||
have different default ports. Sometimes the parsing
|
||||
of portions themselves are different. This parser
|
||||
is extensible via behavior modules. If you have a
|
||||
module named `URI.MYSCHEME` with a function called
|
||||
`parse` that takes a single argument, the generically
|
||||
parsed URI, that function will be called when this
|
||||
parse function is passed a URI of that scheme. This
|
||||
allows you to build on top of what the URI library
|
||||
currently offers. You also need to define `default_port`
|
||||
which takes no arguments and returns the default port
|
||||
for that particular scheme. Take a look at `URI.HTTPS` for an
|
||||
example of one of these extension modules.
|
||||
URIs have portions that are handled specially for the particular
|
||||
scheme of the URI. For example, http and https have different
|
||||
default ports. Such values can be accessed and registered via
|
||||
`URI.default_port/1` and `URI.default_port/2`.
|
||||
|
||||
## 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]
|
||||
|
||||
"""
|
||||
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
|
||||
@@ -193,7 +247,7 @@ defmodule URI do
|
||||
# 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)
|
||||
|
||||
+191
-214
@@ -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.Schema`
|
||||
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
|
||||
|
||||
@@ -31,7 +26,7 @@ defmodule Version do
|
||||
|
||||
Requirements allow you to specify which versions of a given
|
||||
dependency you are willing to work against. It supports common
|
||||
operators like `>=`, `<=`, `>`, `==` and friends that
|
||||
operators like `>=`, `<=`, `>`, `==` and friends that
|
||||
work as one would expect:
|
||||
|
||||
# Only version 2.0.0
|
||||
@@ -54,7 +49,6 @@ 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,
|
||||
@@ -62,95 +56,147 @@ defmodule Version do
|
||||
|
||||
import Kernel, except: [match?: 2]
|
||||
|
||||
defrecord Schema, major: 0, minor: 0, patch: 0, pre: nil, build: nil, source: nil
|
||||
defrecord Requirement, source: nil, matchspec: nil
|
||||
defrecord Schema, [:major, :minor, :patch, :pre, :build, :source]
|
||||
defrecord Requirement, [:source, :matchspec]
|
||||
|
||||
defexception InvalidRequirement, reason: :invalid_requirement do
|
||||
def message(InvalidRequirement[reason: reason]) when is_binary(reason) do
|
||||
{ first, rest } = String.next_grapheme(reason)
|
||||
String.downcase(first) <> rest
|
||||
end
|
||||
|
||||
def message(InvalidRequirement[]) do
|
||||
"invalid version specification"
|
||||
end
|
||||
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
|
||||
def match?(vsn, req) when is_binary(req) do
|
||||
case parse_requirement(req) do
|
||||
{ :ok, req } ->
|
||||
match?(version, req)
|
||||
{ :error, reason } ->
|
||||
raise InvalidRequirement, reason: reason
|
||||
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)
|
||||
@spec compare(String.t | Schema.t, String.t | Schema.t) :: :gt | :eq | :lt
|
||||
def compare(vsn1, vsn2) do
|
||||
do_compare(to_matchable(vsn1), to_matchable(vsn2))
|
||||
end
|
||||
|
||||
def valid?(Version.Schema[major: nil]), do: false
|
||||
def valid?(Version.Schema[]), do: true
|
||||
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
|
||||
pre1 == [] and pre2 != [] -> :gt
|
||||
pre1 != [] and pre2 == [] -> :lt
|
||||
pre1 > pre2 -> :gt
|
||||
pre1 < pre2 -> :lt
|
||||
true -> :eq
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Parse a version string into a `Version.Schema`.
|
||||
|
||||
## Examples
|
||||
|
||||
> Version.parse("2.0.1-alpha1")
|
||||
iex> Version.parse("2.0.1-alpha1") |> elem(1)
|
||||
#Version.Schema<2.0.1-alpha1>
|
||||
|
||||
iex> Version.parse("2.0-alpha1")
|
||||
:error
|
||||
|
||||
"""
|
||||
@spec parse(String.t) :: Schema.t
|
||||
@spec parse(String.t) :: { :ok, Schema.t } | :error
|
||||
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]
|
||||
{ :ok, { major, minor, patch, pre } } ->
|
||||
vsn = Version.Schema[major: major, minor: minor, patch: patch,
|
||||
pre: pre, source: string, build: get_build(string)]
|
||||
{ :ok, vsn }
|
||||
:error ->
|
||||
:error
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Parse a version requirement string into a `Version.Schema`.
|
||||
|
||||
## 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(Schema[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
|
||||
case Regex.run(~r/\+([^\s]+)$/, string) do
|
||||
nil ->
|
||||
nil
|
||||
|
||||
@@ -159,69 +205,6 @@ defmodule Version do
|
||||
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
|
||||
|
||||
> 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
|
||||
|
||||
defmodule Parser.DSL do
|
||||
@moduledoc false
|
||||
|
||||
@@ -289,78 +272,75 @@ 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) || []
|
||||
|
||||
{ :ok, { major, minor, patch, pre } }
|
||||
else
|
||||
{ :error, :invalid_version }
|
||||
end
|
||||
end
|
||||
|
||||
@doc false
|
||||
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)
|
||||
if nil?(minor) or (nil?(patch) and not approximate?) do
|
||||
:error
|
||||
else
|
||||
piece
|
||||
major = binary_to_integer(major)
|
||||
minor = binary_to_integer(minor)
|
||||
patch = patch && binary_to_integer(patch)
|
||||
|
||||
case parse_pre(pre) do
|
||||
{ :ok, pre } ->
|
||||
{ :ok, { major, minor, patch, pre } }
|
||||
:error ->
|
||||
:error
|
||||
end
|
||||
end
|
||||
else
|
||||
:error
|
||||
end
|
||||
end
|
||||
|
||||
@spec valid_requirement?(list) :: boolean
|
||||
def valid_requirement?([]) do
|
||||
false
|
||||
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
|
||||
|
||||
def valid_requirement?([a | next]) do
|
||||
valid_requirement?(a, next)
|
||||
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)
|
||||
|
||||
# it must finish with a version
|
||||
defp valid_requirement?(a, []) when is_binary(a) 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)
|
||||
@@ -376,73 +356,57 @@ 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, [] }
|
||||
|
||||
[_, major, _] ->
|
||||
{ binary_to_integer(major) + 1, 0, 0, [] }
|
||||
|
||||
[_, major, minor, _] ->
|
||||
{ binary_to_integer(major), binary_to_integer(minor) + 1, 0, [] }
|
||||
|
||||
[_, major, minor, _, _] ->
|
||||
{ binary_to_integer(major), binary_to_integer(minor) + 1, 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)
|
||||
|
||||
version = parse_condition(version)
|
||||
{ :'==', :'$_', { :const, version } }
|
||||
end
|
||||
|
||||
defp to_condition([:'!=', version | _]) do
|
||||
version = Version.to_matchable(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' }},
|
||||
@@ -458,7 +422,7 @@ defmodule Version do
|
||||
end
|
||||
|
||||
defp to_condition([:'>=', version | _]) do
|
||||
matchable = Version.to_matchable(version)
|
||||
matchable = parse_condition(version)
|
||||
|
||||
{ :orelse, { :andalso, { :not, { :is_binary, :'$1' } },
|
||||
{ :'==', :'$_', { :const, matchable } } },
|
||||
@@ -466,7 +430,7 @@ defmodule Version do
|
||||
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' }},
|
||||
@@ -482,7 +446,7 @@ defmodule Version do
|
||||
end
|
||||
|
||||
defp to_condition([:'<=', version | _]) do
|
||||
matchable = Version.to_matchable(version)
|
||||
matchable = parse_condition(version)
|
||||
|
||||
{ :orelse, { :andalso, { :not, { :is_binary, :'$1' } },
|
||||
{ :'==', :'$_', { :const, matchable } } },
|
||||
@@ -500,6 +464,19 @@ defmodule Version do
|
||||
defp to_condition(current, [:'||', operator, version | rest]) do
|
||||
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
|
||||
|
||||
@@ -516,13 +493,13 @@ defimpl Inspect, for: Version.Schema do
|
||||
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
|
||||
|
||||
@@ -4,6 +4,7 @@ defmodule Elixir.Mixfile do
|
||||
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" ]
|
||||
|
||||
@@ -24,4 +24,4 @@
|
||||
{verbose, false}
|
||||
]}.
|
||||
|
||||
{require_otp_vsn,"(R16B|R17A).*"}.
|
||||
{require_otp_vsn,"(R16B|17).*"}.
|
||||
|
||||
+88
-52
@@ -1,9 +1,11 @@
|
||||
%% Main entry point for Elixir functions. All of those functions are
|
||||
%% private to the Elixir compiler and reserved to be used by Elixir only.
|
||||
-module(elixir).
|
||||
-behaviour(application).
|
||||
-export([main/1, start_cli/0,
|
||||
scope_for_eval/1, scope_for_eval/2,
|
||||
eval/2, eval/3, eval/4, eval_forms/3,
|
||||
eval_quoted/2, eval_quoted/3, eval_quoted/4]).
|
||||
string_to_quoted/4, 'string_to_quoted!'/4,
|
||||
env_for_eval/1, env_for_eval/2, quoted_to_erl/2, quoted_to_erl/3,
|
||||
eval/2, eval/3, eval_forms/3, eval_forms/4, eval_quoted/3]).
|
||||
-include("elixir.hrl").
|
||||
|
||||
%% Top level types
|
||||
@@ -11,7 +13,7 @@
|
||||
-type char_list() :: string().
|
||||
-type as_boolean(T) :: T.
|
||||
|
||||
% OTP APPLICATION API
|
||||
%% OTP Application API
|
||||
|
||||
-export([start/2, stop/1, config_change/3]).
|
||||
|
||||
@@ -22,7 +24,7 @@ start(_Type, _Args) ->
|
||||
io:setopts(standard_io, [binary,{encoding,unicode}]),
|
||||
io:setopts(standard_error, [binary,{encoding,unicode}])
|
||||
end),
|
||||
elixir_sup:start_link([]).
|
||||
elixir_sup:start_link().
|
||||
|
||||
stop(_S) ->
|
||||
ok.
|
||||
@@ -44,95 +46,129 @@ start_cli() ->
|
||||
|
||||
%% EVAL HOOKS
|
||||
|
||||
scope_for_eval(Opts) ->
|
||||
scope_for_eval(#elixir_scope{
|
||||
file = <<"nofile">>,
|
||||
env_for_eval(Opts) ->
|
||||
env_for_eval(#elixir_env{
|
||||
local = nil,
|
||||
aliases = [],
|
||||
requires = elixir_dispatch:default_requires(),
|
||||
functions = elixir_dispatch:default_functions(),
|
||||
macros = elixir_dispatch:default_macros()
|
||||
}, Opts).
|
||||
|
||||
scope_for_eval(Scope, 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
|
||||
end,
|
||||
|
||||
File = case lists:keyfind(file, 1, Opts) of
|
||||
{ file, RawFile } when is_binary(RawFile) -> RawFile;
|
||||
false -> Scope#elixir_scope.file
|
||||
false -> Env#elixir_env.file
|
||||
end,
|
||||
|
||||
Local = case lists:keyfind(delegate_locals_to, 1, Opts) of
|
||||
{ delegate_locals_to, LocalOpt } -> LocalOpt;
|
||||
false -> Scope#elixir_scope.local
|
||||
false -> Env#elixir_env.local
|
||||
end,
|
||||
|
||||
Aliases = case lists:keyfind(aliases, 1, Opts) of
|
||||
{ aliases, AliasesOpt } -> AliasesOpt;
|
||||
false -> Scope#elixir_scope.aliases
|
||||
false -> Env#elixir_env.aliases
|
||||
end,
|
||||
|
||||
Requires = case lists:keyfind(requires, 1, Opts) of
|
||||
{ requires, List } -> ordsets:from_list(List);
|
||||
false -> Scope#elixir_scope.requires
|
||||
false -> Env#elixir_env.requires
|
||||
end,
|
||||
|
||||
Functions = case lists:keyfind(functions, 1, Opts) of
|
||||
{ functions, FunctionsOpt } -> FunctionsOpt;
|
||||
false -> Scope#elixir_scope.functions
|
||||
false -> Env#elixir_env.functions
|
||||
end,
|
||||
|
||||
Macros = case lists:keyfind(macros, 1, Opts) of
|
||||
{ macros, MacrosOpt } -> MacrosOpt;
|
||||
false -> Scope#elixir_scope.macros
|
||||
false -> Env#elixir_env.macros
|
||||
end,
|
||||
|
||||
Scope#elixir_scope{
|
||||
file=File, local=Local,
|
||||
Module = case lists:keyfind(module, 1, Opts) of
|
||||
{ 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 }.
|
||||
requires=Requires, aliases=Aliases, line=Line
|
||||
}.
|
||||
|
||||
%% String evaluation
|
||||
|
||||
eval(String, Binding) -> eval(String, Binding, []).
|
||||
eval(String, Binding) ->
|
||||
eval(String, Binding, []).
|
||||
|
||||
eval(String, Binding, Opts) ->
|
||||
case lists:keyfind(line, 1, Opts) of
|
||||
false -> Line = 1;
|
||||
{ line, Line } -> []
|
||||
end,
|
||||
eval(String, Binding, Line, scope_for_eval(Opts)).
|
||||
|
||||
eval(String, Binding, Line, #elixir_scope{file=File} = S) when
|
||||
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
|
||||
is_list(String), is_list(Binding), is_integer(Line), is_binary(File) ->
|
||||
Forms = elixir_translator:'forms!'(String, Line, File, []),
|
||||
eval_forms(Forms, Binding, S).
|
||||
Forms = 'string_to_quoted!'(String, Line, File, []),
|
||||
eval_forms(Forms, Binding, E).
|
||||
|
||||
%% Quoted evaluation
|
||||
|
||||
eval_quoted(Tree, Binding) -> eval_quoted(Tree, Binding, []).
|
||||
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_forms(elixir_quote:linify(Line, Tree), Binding, E).
|
||||
|
||||
eval_quoted(Tree, Binding, Opts) ->
|
||||
case lists:keyfind(line, 1, Opts) of
|
||||
{ line, Line } -> [];
|
||||
false -> Line = 1
|
||||
end,
|
||||
eval_quoted(Tree, Binding, Line, scope_for_eval(Opts)).
|
||||
|
||||
eval_quoted(Tree, Binding, Line, #elixir_scope{} = S) when is_integer(Line) ->
|
||||
eval_forms(elixir_quote:linify(Line, Tree), Binding, S).
|
||||
|
||||
%% Handle forms evaluation internally, it is an
|
||||
%% internal API not meant for external usage.
|
||||
%% 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, scope_for_eval(Opts));
|
||||
eval_forms(Tree, Binding, env_for_eval(Opts)).
|
||||
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),
|
||||
|
||||
eval_forms(Tree, Binding, Scope) ->
|
||||
{ ParsedBinding, ParsedScope } = elixir_scope:load_binding(Binding, Scope, nil),
|
||||
{ Exprs, NewScope } = elixir_translator:translate(Tree, ParsedScope),
|
||||
case Exprs of
|
||||
[] ->
|
||||
{ nil, Binding, NewScope };
|
||||
case Erl of
|
||||
{ atom, _, Atom } ->
|
||||
{ Atom, Binding, NewEnv, NewScope };
|
||||
_ ->
|
||||
{ value, Value, NewBinding } = erl_eval:exprs(Exprs, ParsedBinding),
|
||||
{ Value, elixir_scope:dump_binding(NewBinding, NewScope), NewScope }
|
||||
{ value, Value, NewBinding } = erl_eval:expr(Erl, ParsedBinding),
|
||||
{ Value, elixir_scope:dump_binding(NewBinding, NewScope), NewEnv, NewScope }
|
||||
end.
|
||||
|
||||
%% 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 }.
|
||||
|
||||
%% 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 } ->
|
||||
try elixir_parser:parse(Tokens) of
|
||||
{ ok, Forms } -> { ok, Forms };
|
||||
{ error, { Line, _, [Error, Token] } } -> { error, { Line, Error, Token } }
|
||||
catch
|
||||
{ error, { Line, _, [Error, Token] } } -> { error, { Line, Error, Token } }
|
||||
end;
|
||||
{ error, Reason, _Rest, _SoFar } -> { error, Reason }
|
||||
end.
|
||||
|
||||
'string_to_quoted!'(String, StartLine, File, Opts) ->
|
||||
case string_to_quoted(String, StartLine, File, Opts) of
|
||||
{ ok, Forms } ->
|
||||
Forms;
|
||||
{ error, { Line, Error, Token } } ->
|
||||
elixir_errors:parse_error(Line, File, Error, Token)
|
||||
end.
|
||||
|
||||
@@ -1,98 +1,90 @@
|
||||
-module(elixir_aliases).
|
||||
-export([nesting_alias/2, last/1, concat/1, safe_concat/1,
|
||||
format_error/1, ensure_loaded/3, ensure_loaded/4, expand/4, store/5]).
|
||||
-export([inspect/1, last/1, concat/1, safe_concat/1, format_error/1,
|
||||
ensure_loaded/3, expand/4, store/7]).
|
||||
-include("elixir.hrl").
|
||||
|
||||
%% Store an alias in the given scope
|
||||
store(_Meta, New, New, _TKV, S) -> S;
|
||||
store(Meta, New, Old, TKV, S) ->
|
||||
record_warn(Meta, New, TKV, S),
|
||||
|
||||
SA = S#elixir_scope{
|
||||
aliases=orddict:store(New, Old, S#elixir_scope.aliases)
|
||||
},
|
||||
|
||||
case lists:keymember(context, 1, Meta) of
|
||||
true ->
|
||||
SA#elixir_scope{
|
||||
macro_aliases=orddict:store(New, Old, S#elixir_scope.macro_aliases)
|
||||
};
|
||||
false ->
|
||||
SA
|
||||
inspect(Atom) when is_atom(Atom) ->
|
||||
case elixir_compiler:get_opt(internal) of
|
||||
true -> atom_to_binary(Atom, utf8);
|
||||
false -> 'Elixir.Inspect.Atom':inspect(Atom)
|
||||
end.
|
||||
|
||||
record_warn(Meta, Ref, Opts, S) ->
|
||||
%% Store an alias in the given scope
|
||||
store(_Meta, New, New, _TKV, Aliases, MacroAliases, _Lexical) ->
|
||||
{ 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) ->
|
||||
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 } });
|
||||
_ ->
|
||||
Aliases
|
||||
end.
|
||||
|
||||
record_warn(Meta, Ref, Opts, Lexical) ->
|
||||
Warn =
|
||||
case lists:keyfind(warn, 1, Opts) of
|
||||
{ warn, false } -> false;
|
||||
{ warn, true } -> true;
|
||||
false -> not lists:keymember(context, 1, Meta)
|
||||
end,
|
||||
elixir_lexical:record_alias(Ref, ?line(Meta), Warn, S#elixir_scope.lexical_tracker).
|
||||
elixir_lexical:record_alias(Ref, ?line(Meta), Warn, 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) ->
|
||||
concat(List);
|
||||
|
||||
expand({ '__aliases__', Meta, _ } = Alias, Aliases, MacroAliases, LexicalTracker) ->
|
||||
case lists:keyfind(alias, 1, Meta) of
|
||||
{ alias, false } ->
|
||||
expand(Alias, MacroAliases, LexicalTracker);
|
||||
{ alias, Atom } when is_atom(Atom) ->
|
||||
case expand(Alias, MacroAliases, LexicalTracker) of
|
||||
OtherAtom when is_atom(OtherAtom) -> OtherAtom;
|
||||
OtherAliases when is_list(OtherAliases) -> Atom
|
||||
end;
|
||||
Atom;
|
||||
false ->
|
||||
expand(Alias, Aliases, LexicalTracker)
|
||||
end.
|
||||
|
||||
expand({ '__aliases__', _Meta, [H] }, Aliases, LexicalTracker) when H /= 'Elixir' ->
|
||||
case expand_one(H, Aliases, LexicalTracker) of
|
||||
false -> [H];
|
||||
Atom -> Atom
|
||||
end;
|
||||
|
||||
expand({ '__aliases__', _Meta, [H|T] }, Aliases, LexicalTracker) when is_atom(H) ->
|
||||
case H of
|
||||
'Elixir' ->
|
||||
concat(T);
|
||||
_ ->
|
||||
case expand_one(H, Aliases, LexicalTracker) of
|
||||
false -> [H|T];
|
||||
Atom -> concat([Atom|T])
|
||||
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;
|
||||
_ -> nil
|
||||
end,
|
||||
case lookup(Lookup, Aliases, Counter) of
|
||||
Lookup -> [H|T];
|
||||
Atom ->
|
||||
elixir_lexical:record_alias(Lookup, LexicalTracker),
|
||||
case T of
|
||||
[] -> Atom;
|
||||
_ -> concat([Atom|T])
|
||||
end
|
||||
end;
|
||||
|
||||
expand({ '__aliases__', _Meta, List }, _Aliases, _LexicalTracker) ->
|
||||
List.
|
||||
|
||||
expand_one(H, Aliases, LexicalTracker) ->
|
||||
Lookup = list_to_atom("Elixir." ++ atom_to_list(H)),
|
||||
case lookup(Lookup, Aliases) of
|
||||
Lookup -> false;
|
||||
Else ->
|
||||
elixir_lexical:record_alias(Lookup, LexicalTracker),
|
||||
Else
|
||||
end.
|
||||
|
||||
%% Ensure a module is loaded before its usage.
|
||||
|
||||
ensure_loaded(Line, Ref, S) ->
|
||||
ensure_loaded(Line, S#elixir_scope.file, Ref, S#elixir_scope.context_modules).
|
||||
|
||||
ensure_loaded(_Line, _File, 'Elixir.Kernel', _FileModules) ->
|
||||
ok;
|
||||
|
||||
ensure_loaded(Line, File, Ref, FileModules) ->
|
||||
ensure_loaded(_Meta, 'Elixir.Kernel', _E) -> ok;
|
||||
ensure_loaded(Meta, Ref, E) ->
|
||||
try
|
||||
Ref:module_info(compile)
|
||||
catch
|
||||
error:undef ->
|
||||
Kind = case lists:member(Ref, FileModules) of
|
||||
Kind = case lists:member(Ref, E#elixir_env.context_modules) of
|
||||
true -> scheduled_module;
|
||||
false -> unloaded_module
|
||||
end,
|
||||
elixir_errors:form_error(Line, File, ?MODULE, { Kind, Ref })
|
||||
elixir_errors:form_error(Meta, E#elixir_env.file, ?MODULE, { Kind, Ref })
|
||||
end.
|
||||
|
||||
%% Receives an atom and returns the last bit as an alias.
|
||||
@@ -105,74 +97,48 @@ last([$.|_], Acc) -> Acc;
|
||||
last([H|T], Acc) -> last(T, [H|Acc]);
|
||||
last([], Acc) -> Acc.
|
||||
|
||||
%% Gets two modules names and return an alias
|
||||
%% which can be passed down to the alias directive
|
||||
%% and it will create a proper shortcut representing
|
||||
%% the given nesting.
|
||||
%%
|
||||
%% Examples:
|
||||
%%
|
||||
%% nesting_alias('Elixir.Foo.Bar', 'Elixir.Foo.Bar.Baz.Bat')
|
||||
%% { 'Elixir.Baz', 'Elixir.Foo.Bar.Baz' }
|
||||
%%
|
||||
%% When passed to alias, the example above will generate an
|
||||
%% alias like:
|
||||
%%
|
||||
%% 'Elixir.Baz' => 'Elixir.Foo.Bar.Baz'
|
||||
%%
|
||||
nesting_alias(nil, _Full) -> false;
|
||||
|
||||
nesting_alias(Prefix, Full) ->
|
||||
PrefixList = list_nesting(Prefix),
|
||||
FullList = list_nesting(Full),
|
||||
(PrefixList /= []) andalso do_nesting(PrefixList, FullList, []).
|
||||
|
||||
do_nesting([X|PreTail], [X|Tail], Acc) ->
|
||||
do_nesting(PreTail, Tail, [X|Acc]);
|
||||
do_nesting([], [H|_], Acc) ->
|
||||
{ list_to_atom("Elixir." ++ H), concat(lists:reverse([H|Acc])) };
|
||||
do_nesting(_, _, _Acc) ->
|
||||
false.
|
||||
|
||||
list_nesting(Atom) ->
|
||||
case string:tokens(atom_to_list(Atom), ".") of
|
||||
["Elixir"|T] -> T;
|
||||
_ -> []
|
||||
end.
|
||||
|
||||
%% Receives a list of atoms, binaries or lists
|
||||
%% representing modules and concatenates them.
|
||||
|
||||
concat(Args) -> list_to_atom(raw_concat(Args)).
|
||||
safe_concat(Args) -> list_to_existing_atom(raw_concat(Args)).
|
||||
concat(Args) -> binary_to_atom(do_concat(Args), utf8).
|
||||
safe_concat(Args) -> binary_to_existing_atom(do_concat(Args), utf8).
|
||||
|
||||
raw_concat(['Elixir'|Args]) -> do_concat(Args);
|
||||
raw_concat([nil|Args]) -> do_concat(Args);
|
||||
raw_concat(Args) -> do_concat(Args).
|
||||
do_concat([H|T]) when is_atom(H), H /= nil ->
|
||||
do_concat([atom_to_binary(H, utf8)|T]);
|
||||
do_concat([<<"Elixir.", _/binary>>=H|T]) ->
|
||||
do_concat(T, H);
|
||||
do_concat([<<"Elixir">>=H|T]) ->
|
||||
do_concat(T, H);
|
||||
do_concat(T) ->
|
||||
do_concat(T, <<"Elixir">>).
|
||||
|
||||
do_concat(Args) ->
|
||||
Aliases = [to_partial(Arg) || Arg <- Args],
|
||||
"Elixir" ++ lists:concat(Aliases).
|
||||
do_concat([nil|T], Acc) ->
|
||||
do_concat(T, Acc);
|
||||
do_concat([H|T], Acc) when is_atom(H) ->
|
||||
do_concat(T, <<Acc/binary, $., (to_partial(atom_to_binary(H, utf8)))/binary>>);
|
||||
do_concat([H|T], Acc) when is_binary(H) ->
|
||||
do_concat(T, <<Acc/binary, $., (to_partial(H))/binary>>);
|
||||
do_concat([], Acc) ->
|
||||
Acc.
|
||||
|
||||
to_partial(Arg) when is_binary(Arg) -> to_partial(binary_to_list(Arg));
|
||||
to_partial(Arg) when is_atom(Arg) -> to_partial(atom_to_list(Arg));
|
||||
to_partial("Elixir." ++ Arg) -> [$.|Arg];
|
||||
to_partial([$.|_] = Arg) -> Arg;
|
||||
to_partial(Arg) when is_list(Arg) -> [$.|Arg].
|
||||
to_partial(<<"Elixir.", Arg/binary>>) -> Arg;
|
||||
to_partial(<<".", Arg/binary>>) -> Arg;
|
||||
to_partial(Arg) when is_binary(Arg) -> Arg.
|
||||
|
||||
%% Lookup an alias in the current scope.
|
||||
|
||||
lookup(Else, Dict) ->
|
||||
case orddict:find(Else, Dict) of
|
||||
{ ok, Value } when Value /= Else -> lookup(Value, Dict);
|
||||
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
|
||||
end.
|
||||
|
||||
%% Errors
|
||||
|
||||
format_error({unloaded_module, Module}) ->
|
||||
io_lib:format("module ~ts is not loaded and could not be found", [elixir_errors:inspect(Module)]);
|
||||
io_lib:format("module ~ts is not loaded and could not be found", [elixir_aliases:inspect(Module)]);
|
||||
|
||||
format_error({scheduled_module, Module}) ->
|
||||
io_lib:format("module ~ts is not loaded but was defined. This happens because you are trying to use a module in the same context it is defined. Try defining the module outside the context that requires it.",
|
||||
[elixir_errors:inspect(Module)]).
|
||||
[inspect(Module)]).
|
||||
@@ -0,0 +1,202 @@
|
||||
%% Handle translation of Elixir literals to Erlang AST.
|
||||
-module(elixir_bitstring).
|
||||
-export([translate/3, expand/3]).
|
||||
-include("elixir.hrl").
|
||||
|
||||
%% Expansion
|
||||
|
||||
expand(Meta, Args, E) ->
|
||||
case E#elixir_env.context of
|
||||
match ->
|
||||
{ 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) }
|
||||
end.
|
||||
|
||||
expand_bitstr(_Fun, [], Acc, E) ->
|
||||
{ lists:reverse(Acc), E };
|
||||
expand_bitstr(Fun, [{'::',Meta,[Left,Right]}|T], Acc, 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
|
||||
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),
|
||||
expand_bitstr(Fun, T, [Expr|Acc], ES).
|
||||
|
||||
%% Expand bit info
|
||||
|
||||
expand_bit_info(Meta, Info, E) when is_list(Info) ->
|
||||
expand_bit_info(Meta, Info, default, [], E);
|
||||
|
||||
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) ->
|
||||
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);
|
||||
size ->
|
||||
case Size of
|
||||
default -> ok;
|
||||
_ -> elixir_errors:compile_error(Meta, E#elixir_env.file, "duplicated size definition in bitstring")
|
||||
end,
|
||||
{ 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)
|
||||
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, [Expr|_], _Size, _Types, E) ->
|
||||
elixir_errors:compile_error(Meta, E#elixir_env.file,
|
||||
"unknown bitstring specifier ~ts", ['Elixir.Kernel':inspect(Expr)]);
|
||||
|
||||
expand_bit_info(_Meta, [], Size, Types, _) ->
|
||||
case Size of
|
||||
default -> lists:reverse(Types);
|
||||
_ -> [Size|lists:reverse(Types)]
|
||||
end.
|
||||
|
||||
expand_bit_type_or_size(binary, []) -> type;
|
||||
expand_bit_type_or_size(integer, []) -> type;
|
||||
expand_bit_type_or_size(float, []) -> type;
|
||||
expand_bit_type_or_size(bitstring, []) -> type;
|
||||
expand_bit_type_or_size(bytes, []) -> type;
|
||||
expand_bit_type_or_size(bits, []) -> type;
|
||||
expand_bit_type_or_size(utf8, []) -> type;
|
||||
expand_bit_type_or_size(utf16, []) -> type;
|
||||
expand_bit_type_or_size(utf32, []) -> type;
|
||||
expand_bit_type_or_size(signed, []) -> type;
|
||||
expand_bit_type_or_size(unsigned, []) -> type;
|
||||
expand_bit_type_or_size(big, []) -> type;
|
||||
expand_bit_type_or_size(little, []) -> type;
|
||||
expand_bit_type_or_size(native, []) -> type;
|
||||
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
|
||||
Expr ->
|
||||
elixir_errors:compile_error(ExprMeta, E#elixir_env.file,
|
||||
"unknown bitstring specifier ~ts", ['Elixir.Macro':to_string(Expr)]);
|
||||
Other ->
|
||||
List = case is_list(Other) of true -> Other; false -> [Other] end,
|
||||
expand_bit_info(Meta, List ++ T, Size, Types, E)
|
||||
end.
|
||||
|
||||
%% Translation
|
||||
|
||||
translate(Meta, Args, S) ->
|
||||
case S#elixir_scope.context of
|
||||
match ->
|
||||
build_bitstr(fun elixir_translator:translate/2, Args, Meta, S);
|
||||
_ ->
|
||||
build_bitstr(fun(X, Acc) -> elixir_translator:translate_arg(X, Acc, S) end, Args, Meta, S)
|
||||
end.
|
||||
|
||||
build_bitstr(Fun, Exprs, Meta, S) ->
|
||||
{ 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 };
|
||||
|
||||
build_bitstr_each(Fun, [{'::',_,[H,V]}|T], Meta, S, Acc) ->
|
||||
{ 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) ->
|
||||
build_bitstr_each(Fun, T, Meta, S, Acc, H, default, default).
|
||||
|
||||
build_bitstr_each(Fun, T, Meta, S, Acc, H, default, Types) when is_binary(H) ->
|
||||
Element =
|
||||
case types_allow_splice(Types, []) of
|
||||
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 };
|
||||
false ->
|
||||
case types_require_conversion(Types) of
|
||||
true ->
|
||||
{ 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")
|
||||
end
|
||||
end,
|
||||
|
||||
build_bitstr_each(Fun, T, Meta, S, [Element|Acc]);
|
||||
|
||||
build_bitstr_each(_Fun, _T, Meta, S, _Acc, H, _Size, _Types) when is_binary(H) ->
|
||||
elixir_errors:compile_error(Meta, S#elixir_scope.file, "size is not supported for literal string in <<>>");
|
||||
|
||||
build_bitstr_each(_Fun, _T, Meta, S, _Acc, H, _Size, _Types) when is_list(H); is_atom(H) ->
|
||||
elixir_errors:compile_error(Meta, S#elixir_scope.file, "invalid literal ~ts in <<>>",
|
||||
['Elixir.Macro':to_string(H)]);
|
||||
|
||||
build_bitstr_each(Fun, T, Meta, S, Acc, H, Size, Types) ->
|
||||
{ Expr, NS } = Fun(H, S),
|
||||
|
||||
case Expr of
|
||||
{ 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])
|
||||
end;
|
||||
_ ->
|
||||
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);
|
||||
types_require_conversion([UTF|T]) when UTF == utf8; UTF == utf16; UTF == utf32 -> types_require_conversion(T);
|
||||
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([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.
|
||||
|
||||
%% Extra bitstring specifiers
|
||||
|
||||
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) };
|
||||
_ ->
|
||||
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) }.
|
||||
|
||||
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) ->
|
||||
[Other|extract_bit_type(Meta, T, S)];
|
||||
extract_bit_type(_Meta, [], _S) ->
|
||||
[].
|
||||
@@ -0,0 +1,51 @@
|
||||
%% An Erlang module that behaves like an Elixir module
|
||||
%% used for bootstraping.
|
||||
-module(elixir_bootstrap).
|
||||
-export(['MACRO-def'/2, 'MACRO-def'/3, 'MACRO-defp'/3, 'MACRO-defmodule'/3,
|
||||
'MACRO-defmacro'/2, 'MACRO-defmacro'/3, 'MACRO-defmacrop'/3,
|
||||
'MACRO-@'/2, '__info__'/1]).
|
||||
-define(kernel, 'Elixir.Kernel').
|
||||
|
||||
%% Mock out @ to be a no-op unless Kernel is defined.
|
||||
'MACRO-@'(Caller, Tree) ->
|
||||
unless_loaded('MACRO-@', [Caller, Tree], fun() -> nil end).
|
||||
|
||||
'MACRO-def'(Caller, Call) -> 'MACRO-def'(Caller, Call, nil).
|
||||
'MACRO-def'(Caller, Call, Expr) -> define(Caller, def, Call, Expr).
|
||||
'MACRO-defp'(Caller, Call, Expr) -> define(Caller, defp, Call, Expr).
|
||||
|
||||
'MACRO-defmacro'(Caller, Call) -> 'MACRO-defmacro'(Caller, Call, nil).
|
||||
'MACRO-defmacro'(Caller, Call, Expr) -> define(Caller, defmacro, Call, Expr).
|
||||
'MACRO-defmacrop'(Caller, Call, Expr) -> define(Caller, defmacrop, Call, Expr).
|
||||
|
||||
'MACRO-defmodule'(_Caller, Alias, [{do,Block}]) ->
|
||||
{ Escaped, _ } = elixir_quote:escape(Block, false),
|
||||
Args = [Alias, Escaped, [], env()],
|
||||
{ { '.', [], [elixir_module, compile] }, [], Args }.
|
||||
|
||||
'__info__'(functions) ->
|
||||
[];
|
||||
'__info__'(macros) ->
|
||||
[{ '@', 1 },
|
||||
{def,1},
|
||||
{def,2},
|
||||
{defmacro,1},
|
||||
{defmacro,2},
|
||||
{defmacrop,2},
|
||||
{defmodule,2},
|
||||
{defp,2}].
|
||||
|
||||
define({Line,E}, Kind, Call, Expr) ->
|
||||
{ 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 }.
|
||||
|
||||
unless_loaded(Fun, Args, Callback) ->
|
||||
case code:is_loaded(?kernel) of
|
||||
{ _, _} -> apply(?kernel, Fun, Args);
|
||||
false -> Callback()
|
||||
end.
|
||||
|
||||
env() ->
|
||||
{ '__ENV__', [], nil }.
|
||||
@@ -1,134 +1,109 @@
|
||||
%% Handle code related to args, guard and -> matching for case,
|
||||
%% fn, receive and friends. try is handled in elixir_try.
|
||||
-module(elixir_clauses).
|
||||
-export([
|
||||
assigns/3, assigns_block/5, assigns_block/6, extract_splat_guards/1,
|
||||
get_pairs/4, get_pairs/5, match/3, extract_args/1, extract_guards/1]).
|
||||
-export([match/3, clause/6, clauses/3, get_pairs/2, get_pairs/3,
|
||||
extract_splat_guards/1, extract_guards/1]).
|
||||
-include("elixir.hrl").
|
||||
|
||||
%% Get pairs from a clause.
|
||||
|
||||
get_pairs(Meta, Key, Clauses, S) ->
|
||||
get_pairs(Meta, Key, Clauses, S, false).
|
||||
|
||||
get_pairs(Meta, Key, Clauses, S, AllowNil) ->
|
||||
get_pairs(Key, Clauses) ->
|
||||
get_pairs(Key, Clauses, false).
|
||||
get_pairs(Key, Clauses, AllowNil) ->
|
||||
case lists:keyfind(Key, 1, Clauses) of
|
||||
{ Key, { '->', _, Pairs } } ->
|
||||
[{ Key, PMeta, Left, Right } || { Left, PMeta, Right } <- Pairs];
|
||||
{ Key, Pairs } when is_list(Pairs) ->
|
||||
[{ Key, Meta, Left, Right } || { '->', Meta, [Left, Right] } <- Pairs];
|
||||
{ Key, nil } when AllowNil ->
|
||||
[];
|
||||
{ Key, _ } ->
|
||||
elixir_errors:syntax_error(Meta, S#elixir_scope.file, "expected pairs with -> for key ~ts", [Key]);
|
||||
_ ->
|
||||
false ->
|
||||
[]
|
||||
end.
|
||||
|
||||
% Function for translating assigns.
|
||||
%% Translate matches
|
||||
|
||||
assigns(Fun, Args, #elixir_scope{context=Context, temp_vars=TempVars,
|
||||
match(Fun, Args, #elixir_scope{context=Context, match_vars=MatchVars,
|
||||
backup_vars=BackupVars, vars=Vars} = S) when Context /= match ->
|
||||
{ Result, NewS } = assigns(Fun, Args, S#elixir_scope{context=match,
|
||||
temp_vars=ordsets:new(), backup_vars=Vars}),
|
||||
{ Result, NewS } = match(Fun, Args, S#elixir_scope{context=match,
|
||||
match_vars=ordsets:new(), backup_vars=Vars}),
|
||||
{ Result, NewS#elixir_scope{context=Context,
|
||||
temp_vars=TempVars, backup_vars=BackupVars} };
|
||||
match_vars=MatchVars, backup_vars=BackupVars} };
|
||||
match(Fun, Args, S) -> Fun(Args, S).
|
||||
|
||||
assigns(Fun, Args, S) -> Fun(Args, S).
|
||||
%% Translate clauses with args, guards and expressions
|
||||
|
||||
%% Function for translating a block that is preceeded by an
|
||||
%% assignment and optional guards. This is used by def* and fn.
|
||||
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}),
|
||||
|
||||
assigns_block(Line, Fun, BareArgs, Exprs, S) ->
|
||||
{ Args, Guards } = extract_guards(BareArgs),
|
||||
assigns_block(Line, Fun, Args, Exprs, Guards, S).
|
||||
SG = SA#elixir_scope{context=guard, extra_guards=nil},
|
||||
Extra = SA#elixir_scope.extra_guards,
|
||||
|
||||
assigns_block(Line, Fun, Args, Exprs, Guards, S) when is_integer(Line) ->
|
||||
{ TArgs, SA } = assigns(Fun, Args, S#elixir_scope{extra_guards=[]}),
|
||||
{ TExprs, SE } = elixir_translator:translate(Exprs, SA#elixir_scope{extra_guards=nil}),
|
||||
|
||||
FArgs = listify(TArgs),
|
||||
SG = SA#elixir_scope{context=guard, extra_guards=nil},
|
||||
Extra = SA#elixir_scope.extra_guards,
|
||||
|
||||
FGuards = case Guards of
|
||||
TGuards = case Guards of
|
||||
[] -> case Extra of [] -> []; _ -> [Extra] end;
|
||||
_ -> [translate_guard(Line, Guard, Extra, SG) || Guard <- Guards]
|
||||
end,
|
||||
|
||||
% Uncompact expressions from the block.
|
||||
case TExprs of
|
||||
[{ block, _, FExprs }] -> [];
|
||||
_ -> FExprs = TExprs
|
||||
end,
|
||||
|
||||
{ { clause, Line, FArgs, FGuards, FExprs }, 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_each(elixir_quote:linify(Line, Guard), S))|Extra].
|
||||
[element(1, elixir_translator:translate(elixir_quote:linify(Line, Guard), S))|Extra].
|
||||
|
||||
extract_guards({ 'when', _, [Left, Right] }) -> { Left, extract_or_clauses(Right, []) };
|
||||
extract_guards({ 'when', _, [Left, Right] }) -> { Left, extract_or_guards(Right) };
|
||||
extract_guards(Else) -> { Else, [] }.
|
||||
|
||||
extract_or_clauses({ 'when', _, [Left, Right] }, Acc) -> extract_or_clauses(Right, [Left|Acc]);
|
||||
extract_or_clauses(Term, Acc) -> [Term|Acc].
|
||||
|
||||
% Extract name and args from the given expression.
|
||||
|
||||
extract_args({ { '.', _, [Name] }, _, Args }) when is_atom(Name), is_list(Args) -> { Name, Args };
|
||||
extract_args({ Name, _, Args }) when is_atom(Name), is_atom(Args) -> { Name, [] };
|
||||
extract_args({ Name, _, Args }) when is_atom(Name), is_list(Args) -> { Name, Args };
|
||||
extract_args(_) -> error.
|
||||
extract_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_clauses(Right, []) };
|
||||
{ Left, extract_or_guards(Right) };
|
||||
extract_splat_guards(Else) ->
|
||||
{ Else, [] }.
|
||||
|
||||
% Function for translating macros with match style like case and receive.
|
||||
|
||||
match(Meta, Clauses, #elixir_scope{clause_vars=C1} = S) ->
|
||||
{ TC, TS } = do_match(Meta, Clauses, S#elixir_scope{clause_vars=orddict:new()}),
|
||||
C2 = TS#elixir_scope.clause_vars,
|
||||
{ TC, TS#elixir_scope{clause_vars=elixir_scope:merge_clause_vars(C1, C2)} }.
|
||||
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)} }.
|
||||
|
||||
do_match(_Meta, [], S) ->
|
||||
do_clauses(_Meta, [], S) ->
|
||||
{ [], S };
|
||||
|
||||
do_match(_Meta, [DecoupledClause], S) ->
|
||||
{ TDecoupledClause, TS } = each_clause(DecoupledClause, S),
|
||||
{ [TDecoupledClause], TS };
|
||||
% do_clauses(_Meta, [DecoupledClause], S) ->
|
||||
% { TDecoupledClause, TS } = each_clause(DecoupledClause, S),
|
||||
% { [TDecoupledClause], TS };
|
||||
|
||||
do_match(Meta, DecoupledClauses, S) ->
|
||||
do_clauses(Meta, DecoupledClauses, S) ->
|
||||
% Transform tree just passing the variables counter forward
|
||||
% and storing variables defined inside each clause.
|
||||
Transformer = fun(X, {Acc, CV}) ->
|
||||
{ TX, TAcc } = each_clause(X, Acc),
|
||||
{ TX, { merge_clauses_scope(S, TAcc), [TAcc#elixir_scope.clause_vars|CV] } }
|
||||
Transformer = fun(X, {SAcc, VAcc}) ->
|
||||
{ TX, TS } = each_clause(X, SAcc),
|
||||
{ TX,
|
||||
{ elixir_scope:mergec(S, TS), [TS#elixir_scope.export_vars|VAcc] } }
|
||||
end,
|
||||
|
||||
{ TClauses, { TS, ReverseCV } } = lists:mapfoldl(Transformer, {S, []}, DecoupledClauses),
|
||||
{ TClauses, { TS, ReverseCV } } =
|
||||
lists:mapfoldl(Transformer, {S, []}, DecoupledClauses),
|
||||
|
||||
% Now get all the variables defined inside each clause
|
||||
CV = lists:reverse(ReverseCV),
|
||||
AllVars = lists:foldl(fun(KV, Acc) ->
|
||||
elixir_scope:merge_clause_vars(Acc, KV)
|
||||
end, orddict:new(), CV),
|
||||
AllVars = lists:foldl(fun elixir_scope:merge_vars/2, [], CV),
|
||||
|
||||
% Create a new scope that contains a list of all variables
|
||||
% defined inside all the clauses. It returns this new scope and
|
||||
% a list of tuples where the first element is the variable name,
|
||||
% the second one is the new pointer to the variable and the third
|
||||
% is the old pointer.
|
||||
{ FinalVars, FS } = lists:mapfoldl(fun({ Key, Ref }, Acc) ->
|
||||
normalize_vars(Key, Ref, Acc)
|
||||
{ FinalVars, FS } = lists:mapfoldl(fun({ Key, Val }, Acc) ->
|
||||
normalize_vars(Key, Val, Acc)
|
||||
end, TS, AllVars),
|
||||
|
||||
% Expand all clauses by adding a match operation at the end
|
||||
% that assigns variables missing in one clause to the others.
|
||||
% that defines variables missing in one clause to the others.
|
||||
expand_clauses(?line(Meta), TClauses, CV, FinalVars, [], FS).
|
||||
|
||||
expand_clauses(Line, [Clause|T], [ClauseVars|V], FinalVars, Acc, S) ->
|
||||
@@ -149,7 +124,8 @@ expand_clauses(Line, [Clause|T], [ClauseVars|V], FinalVars, Acc, S) ->
|
||||
{ match, _, { var, _, UserVarName } = UserVar, _ } when UserVarName /= '_' ->
|
||||
{ [UserVar,MatchExpr,Final|RawClauseExprs], S };
|
||||
_ ->
|
||||
{ StorageVar, SS } = elixir_scope:build_erl_var(Line, S),
|
||||
{ VarName, _, SS } = elixir_scope:build_var('_', S),
|
||||
StorageVar = { var, Line, VarName },
|
||||
StorageExpr = { match, Line, StorageVar, Final },
|
||||
{ [StorageVar,MatchExpr,StorageExpr|RawClauseExprs], SS }
|
||||
end;
|
||||
@@ -167,21 +143,17 @@ expand_clauses(_Line, [], [], _FinalVars, Acc, S) ->
|
||||
% Handle each key/value clause pair and translate them accordingly.
|
||||
|
||||
each_clause({ do, Meta, [Condition], Expr }, S) ->
|
||||
assigns_block(?line(Meta), fun elixir_translator:translate_each/2, 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 }, S) ->
|
||||
assigns_block(?line(Meta), fun elixir_translator:translate_each/2, Condition, [Expr], S);
|
||||
{ Arg, Guards } = extract_guards(Condition),
|
||||
clause(?line(Meta), fun elixir_translator:translate_many/2, [Arg], Expr, Guards, S);
|
||||
|
||||
each_clause({ 'after', Meta, [Condition], Expr }, S) ->
|
||||
{ TCondition, SC } = elixir_translator:translate_each(Condition, S),
|
||||
{ TBody, SB } = elixir_translator:translate([Expr], SC),
|
||||
{ { clause, ?line(Meta), [TCondition], [], TBody }, SB };
|
||||
|
||||
each_clause({ Key, Meta, [_|_], _ }, S) when Key == do; Key == 'after' ->
|
||||
elixir_errors:syntax_error(Meta, S#elixir_scope.file, "too many arguments given for ~ts", [Key]);
|
||||
|
||||
each_clause({ Key, Meta, _, _ }, S) ->
|
||||
elixir_errors:syntax_error(Meta, S#elixir_scope.file, "invalid key ~ts", [Key]).
|
||||
{ TCondition, SC } = elixir_translator:translate(Condition, S),
|
||||
{ TExpr, SB } = elixir_translator:translate(Expr, 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
|
||||
@@ -189,55 +161,51 @@ each_clause({ Key, Meta, _, _ }, S) ->
|
||||
|
||||
has_match_tuple({'receive', _, _, _, _}) ->
|
||||
true;
|
||||
|
||||
has_match_tuple({'receive', _, _}) ->
|
||||
true;
|
||||
|
||||
has_match_tuple({'case', _, _, _}) ->
|
||||
true;
|
||||
|
||||
has_match_tuple({match, _, _, _}) ->
|
||||
true;
|
||||
|
||||
has_match_tuple({'fun', _, { clauses, _ }}) ->
|
||||
false;
|
||||
|
||||
has_match_tuple(H) when is_tuple(H) ->
|
||||
has_match_tuple(tuple_to_list(H));
|
||||
|
||||
has_match_tuple(H) when is_list(H) ->
|
||||
lists:any(fun has_match_tuple/1, H);
|
||||
|
||||
has_match_tuple(_) -> false.
|
||||
|
||||
% Normalize the given var in between clauses
|
||||
% by picking one value as reference and retriving
|
||||
% its previous value.
|
||||
|
||||
normalize_vars(Key, Value, #elixir_scope{vars=Vars,clause_vars=ClauseVars} = S) ->
|
||||
FS = S#elixir_scope{
|
||||
normalize_vars(Key, Value, #elixir_scope{vars=Vars,export_vars=ClauseVars} = S) ->
|
||||
VS = S#elixir_scope{
|
||||
vars=orddict:store(Key, Value, Vars),
|
||||
clause_vars=orddict:store(Key, Value, ClauseVars)
|
||||
export_vars=orddict:store(Key, Value, ClauseVars)
|
||||
},
|
||||
|
||||
Expr = case orddict:find(Key, Vars) of
|
||||
{ ok, OldValue } -> { var, 0, OldValue };
|
||||
{ ok, { PreValue, _ } } -> { var, 0, PreValue };
|
||||
error -> { atom, 0, nil }
|
||||
end,
|
||||
|
||||
{ { Key, Value, Expr }, FS }.
|
||||
{ { Key, Value, Expr }, VS }.
|
||||
|
||||
% Generate match vars by checking if they were updated
|
||||
% or not and assigning the previous value.
|
||||
|
||||
generate_match_vars([{ Key, NewValue, OldValue }|T], ClauseVars, Left, Right) ->
|
||||
generate_match_vars([{ Key, Value, Expr }|T], ClauseVars, Left, Right) ->
|
||||
case orddict:find(Key, ClauseVars) of
|
||||
{ ok, NewValue } ->
|
||||
{ ok, Value } ->
|
||||
generate_match_vars(T, ClauseVars, Left, Right);
|
||||
{ ok, ClauseValue } ->
|
||||
generate_match_vars(T, ClauseVars, [{ var, 0, NewValue }|Left], [{ var, 0, ClauseValue }|Right]);
|
||||
{ ok, Clause } ->
|
||||
generate_match_vars(T, ClauseVars,
|
||||
[{ var, 0, element(1, Value) }|Left],
|
||||
[{ var, 0, element(1, Clause) }|Right]);
|
||||
error ->
|
||||
generate_match_vars(T, ClauseVars, [{ var, 0, NewValue }|Left], [OldValue|Right])
|
||||
generate_match_vars(T, ClauseVars,
|
||||
[{ var, 0, element(1, Value) }|Left], [Expr|Right])
|
||||
end;
|
||||
|
||||
generate_match_vars([], _ClauseVars, Left, Right) ->
|
||||
@@ -249,15 +217,5 @@ generate_match(Line, [Left], [Right]) ->
|
||||
generate_match(Line, LeftVars, RightVars) ->
|
||||
{ match, Line, { tuple, Line, LeftVars }, { tuple, Line, RightVars } }.
|
||||
|
||||
listify(Expr) when not is_list(Expr) -> [Expr];
|
||||
listify(Expr) -> Expr.
|
||||
|
||||
%% We don't use umergec because imports, aliases and
|
||||
%% what not are not passed from one clause to the other.
|
||||
merge_clauses_scope(S1, S2) ->
|
||||
S1#elixir_scope{
|
||||
counter=S2#elixir_scope.counter,
|
||||
extra_guards=S2#elixir_scope.extra_guards,
|
||||
super=S1#elixir_scope.super orelse S2#elixir_scope.super,
|
||||
caller=S1#elixir_scope.caller orelse S2#elixir_scope.caller
|
||||
}.
|
||||
unblock({ 'block', _, Exprs }) -> Exprs;
|
||||
unblock(Exprs) -> [Exprs].
|
||||
|
||||
@@ -2,7 +2,9 @@
|
||||
-export([call/1, cast/1]).
|
||||
-export([start_link/0, init/1, handle_call/3, handle_cast/2,
|
||||
handle_info/2, terminate/2, code_change/3]).
|
||||
-behavior(gen_server).
|
||||
-behaviour(gen_server).
|
||||
|
||||
-define(timeout, 30000).
|
||||
-record(elixir_code_server, {
|
||||
compilation_status=[],
|
||||
argv=[],
|
||||
@@ -14,21 +16,19 @@
|
||||
}).
|
||||
|
||||
call(Args) ->
|
||||
gen_server:call(?MODULE, Args, get_timeout()).
|
||||
gen_server:call(?MODULE, Args, ?timeout).
|
||||
|
||||
cast(Args) ->
|
||||
gen_server:cast(?MODULE, Args).
|
||||
|
||||
get_timeout() ->
|
||||
30000.
|
||||
|
||||
%% Callbacks
|
||||
|
||||
start_link() ->
|
||||
{ ok, _ } = gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).
|
||||
gen_server:start_link({local, ?MODULE}, ?MODULE, ok, []).
|
||||
|
||||
init(_Args) ->
|
||||
code:ensure_loaded('Elixir.Module.DispatchTracker'),
|
||||
init(ok) ->
|
||||
code:ensure_loaded('Elixir.Macro.Env'),
|
||||
code:ensure_loaded('Elixir.Module.LocalsTracker'),
|
||||
code:ensure_loaded('Elixir.Kernel.LexicalTracker'),
|
||||
{ ok, #elixir_code_server{} }.
|
||||
|
||||
@@ -83,8 +83,8 @@ handle_call(erl_compiler_options, _From, Config) ->
|
||||
{ reply, Opts, Config }
|
||||
end;
|
||||
|
||||
handle_call(_Request, _From, Config) ->
|
||||
{ reply, undef, Config }.
|
||||
handle_call(Request, _From, 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]} };
|
||||
@@ -131,8 +131,8 @@ handle_cast({ unload_files, Files }, Config) ->
|
||||
handle_cast({ return_module_name, H }, #elixir_code_server{pool={T,Counter}} = Config) ->
|
||||
{ noreply, Config#elixir_code_server{pool={[H|T],Counter}} };
|
||||
|
||||
handle_cast(_Request, Config) ->
|
||||
{ noreply, Config }.
|
||||
handle_cast(Request, Config) ->
|
||||
{ stop, { badcast, Request }, Config }.
|
||||
|
||||
handle_info(_Request, Config) ->
|
||||
{ noreply, Config }.
|
||||
@@ -143,7 +143,8 @@ terminate(_Reason, _Config) ->
|
||||
code_change(_Old, Config, _Extra) ->
|
||||
{ ok, Config }.
|
||||
|
||||
module_tuple(I) -> { list_to_atom("elixir_compiler_" ++ integer_to_list(I)), I }.
|
||||
module_tuple(I) ->
|
||||
{ list_to_atom("elixir_compiler_" ++ integer_to_list(I)), I }.
|
||||
|
||||
erl_compiler_options() ->
|
||||
Key = "ERL_COMPILER_OPTIONS",
|
||||
|
||||
@@ -1,27 +1,21 @@
|
||||
-module(elixir_compiler).
|
||||
-export([get_opts/0, get_opt/1, get_opt/2, string/2, quoted/2, file/1, file_to_path/2]).
|
||||
-export([core/0, module/4, eval_forms/4, format_error/1]).
|
||||
-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]).
|
||||
-include("elixir.hrl").
|
||||
|
||||
%% Public API
|
||||
|
||||
%% Get compilation options.
|
||||
|
||||
get_opt(Key) -> get_opt(Key, get_opts()).
|
||||
|
||||
get_opt(Key, Dict) ->
|
||||
get_opt(Key) ->
|
||||
Dict = elixir_code_server:call(compiler_options),
|
||||
case lists:keyfind(Key, 1, Dict) of
|
||||
false -> false;
|
||||
{ Key, Value } -> Value
|
||||
end.
|
||||
|
||||
get_opts() ->
|
||||
elixir_code_server:call(compiler_options).
|
||||
|
||||
%% Compilation entry points.
|
||||
|
||||
string(Contents, File) when is_list(Contents), is_binary(File) ->
|
||||
Forms = elixir_translator:'forms!'(Contents, 1, File, []),
|
||||
Forms = elixir:'string_to_quoted!'(Contents, 1, File, []),
|
||||
quoted(Forms, File).
|
||||
|
||||
quoted(Forms, File) when is_binary(File) ->
|
||||
@@ -31,8 +25,8 @@ quoted(Forms, File) when is_binary(File) ->
|
||||
put(elixir_compiled, []),
|
||||
elixir_lexical:run(File, fun
|
||||
(Pid) ->
|
||||
Scope = elixir:scope_for_eval([{file,File}]),
|
||||
eval_forms(Forms, 1, [], Scope#elixir_scope{lexical_tracker=Pid})
|
||||
Env = elixir:env_for_eval([{line,1},{file,File}]),
|
||||
eval_forms(Forms, [], Env#elixir_env{lexical_tracker=Pid})
|
||||
end),
|
||||
lists:reverse(get(elixir_compiled))
|
||||
after
|
||||
@@ -51,51 +45,65 @@ file_to_path(File, Path) when is_binary(File), is_binary(Path) ->
|
||||
|
||||
%% Evaluation
|
||||
|
||||
eval_forms(Forms, Line, Vars, S) ->
|
||||
{ Module, I } = retrieve_module_name(),
|
||||
{ Exprs, FS } = elixir_translator:translate(Forms, S),
|
||||
eval_forms(Forms, Vars, E) ->
|
||||
case (E#elixir_env.module == nil) andalso allows_fast_compilation(Forms) of
|
||||
true -> eval_compilation(Forms, Vars, E);
|
||||
false -> code_loading_compilation(Forms, Vars, E)
|
||||
end.
|
||||
|
||||
Fun = eval_fun(S#elixir_scope.module),
|
||||
Form = eval_mod(Fun, Exprs, Line, S#elixir_scope.file, Module, Vars),
|
||||
Args = [X || { _, _, _, X } <- Vars],
|
||||
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 }.
|
||||
|
||||
code_loading_compilation(Forms, Vars, #elixir_env{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),
|
||||
|
||||
{ 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]),
|
||||
|
||||
%% Pass { native, false } to speed up bootstrap
|
||||
%% process when native is set to true
|
||||
{ module(Form, S#elixir_scope.file, [{native,false}], true, fun(_, Binary) ->
|
||||
Res = Module:Fun(S#elixir_scope.module, Args),
|
||||
code:delete(Module),
|
||||
|
||||
module(Form, E#elixir_env.file, [{native,false}], true, fun(_, Binary) ->
|
||||
%% If we have labeled locals, anonymous functions
|
||||
%% were created and therefore we cannot ditch the
|
||||
%% module
|
||||
case beam_lib:chunks(Binary, [labeled_locals]) of
|
||||
{ ok, { _, [{ labeled_locals, []}] } } ->
|
||||
code:purge(Module),
|
||||
return_module_name(I);
|
||||
_ ->
|
||||
ok
|
||||
end,
|
||||
Purgeable =
|
||||
case beam_lib:chunks(Binary, [labeled_locals]) of
|
||||
{ ok, { _, [{ labeled_locals, []}] } } -> true;
|
||||
_ -> false
|
||||
end,
|
||||
dispatch_loaded(Module, Fun, Args, Purgeable, I, EE)
|
||||
end).
|
||||
|
||||
Res
|
||||
end), FS }.
|
||||
dispatch_loaded(Module, Fun, Args, Purgeable, I, E) ->
|
||||
Res = Module:Fun(Args),
|
||||
code:delete(Module),
|
||||
if Purgeable ->
|
||||
code:purge(Module),
|
||||
return_module_name(I);
|
||||
true ->
|
||||
ok
|
||||
end,
|
||||
{ Res, E }.
|
||||
|
||||
eval_fun(nil) -> '__FILE__';
|
||||
eval_fun(_) -> '__MODULE__'.
|
||||
code_fun(nil) -> '__FILE__';
|
||||
code_fun(_) -> '__MODULE__'.
|
||||
|
||||
eval_mod(Fun, Exprs, Line, File, Module, Vars) when is_binary(File), is_integer(Line) ->
|
||||
Cons = lists:foldr(fun({ _, _, Var, _ }, Acc) ->
|
||||
{ cons, Line, { var, Line, Var }, Acc }
|
||||
end, { nil, Line }, Vars),
|
||||
|
||||
Args = [{ var, Line, '_@MODULE'}, Cons],
|
||||
code_mod(Fun, Expr, Line, File, Module, Vars) when is_binary(File), is_integer(Line) ->
|
||||
Tuple = { tuple, Line, [{ var, Line, K } || { _, _, K, _ } <- Vars] },
|
||||
Relative = elixir_utils:relative_to_cwd(File),
|
||||
|
||||
[
|
||||
{ attribute, Line, file, { elixir_utils:characters_to_list(File), 1 } },
|
||||
{ attribute, Line, file, { elixir_utils:characters_to_list(Relative), 1 } },
|
||||
{ attribute, Line, module, Module },
|
||||
{ attribute, Line, export, [{ Fun, 2 }, { '__RELATIVE__', 0 }] },
|
||||
{ function, Line, Fun, length(Args), [
|
||||
{ clause, Line, Args, [], Exprs }
|
||||
{ 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)] }
|
||||
@@ -108,6 +116,11 @@ retrieve_module_name() ->
|
||||
return_module_name(I) ->
|
||||
elixir_code_server:cast({ return_module_name, I }).
|
||||
|
||||
allows_fast_compilation({ '__block__', _, Exprs }) ->
|
||||
lists:all(fun allows_fast_compilation/1, Exprs);
|
||||
allows_fast_compilation({defmodule,_,_}) -> true;
|
||||
allows_fast_compilation(_) -> false.
|
||||
|
||||
%% INTERNAL API
|
||||
|
||||
%% Compile the module by forms based on the scope information
|
||||
@@ -173,7 +186,7 @@ core_file(File) ->
|
||||
catch
|
||||
Kind:Reason ->
|
||||
io:format("~p: ~p~nstacktrace: ~p~n", [Kind, Reason, erlang:get_stacktrace()]),
|
||||
exit(1)
|
||||
erlang:halt(1)
|
||||
end.
|
||||
|
||||
core_main() ->
|
||||
@@ -182,13 +195,14 @@ core_main() ->
|
||||
<<"lib/elixir/lib/keyword.ex">>,
|
||||
<<"lib/elixir/lib/module.ex">>,
|
||||
<<"lib/elixir/lib/list.ex">>,
|
||||
<<"lib/elixir/lib/kernel/typespec.ex">>,
|
||||
<<"lib/elixir/lib/record.ex">>,
|
||||
<<"lib/elixir/lib/macro.ex">>,
|
||||
<<"lib/elixir/lib/macro/env.ex">>,
|
||||
<<"lib/elixir/lib/exception.ex">>,
|
||||
<<"lib/elixir/lib/code.ex">>,
|
||||
<<"lib/elixir/lib/module/locals_tracker.ex">>,
|
||||
<<"lib/elixir/lib/protocol.ex">>,
|
||||
<<"lib/elixir/lib/stream/reducers.ex">>,
|
||||
<<"lib/elixir/lib/enum.ex">>,
|
||||
<<"lib/elixir/lib/inspect/algebra.ex">>,
|
||||
<<"lib/elixir/lib/inspect.ex">>,
|
||||
@@ -199,12 +213,12 @@ core_main() ->
|
||||
<<"lib/elixir/lib/io.ex">>,
|
||||
<<"lib/elixir/lib/path.ex">>,
|
||||
<<"lib/elixir/lib/system.ex">>,
|
||||
<<"lib/elixir/lib/kernel/typespec.ex">>,
|
||||
<<"lib/elixir/lib/kernel/cli.ex">>,
|
||||
<<"lib/elixir/lib/kernel/error_handler.ex">>,
|
||||
<<"lib/elixir/lib/kernel/parallel_compiler.ex">>,
|
||||
<<"lib/elixir/lib/kernel/record_rewriter.ex">>,
|
||||
<<"lib/elixir/lib/kernel/lexical_tracker.ex">>,
|
||||
<<"lib/elixir/lib/module/dispatch_tracker.ex">>
|
||||
<<"lib/elixir/lib/kernel/lexical_tracker.ex">>
|
||||
].
|
||||
|
||||
binary_to_path({ModuleName, Binary}, CompilePath) ->
|
||||
@@ -216,17 +230,19 @@ binary_to_path({ModuleName, Binary}, CompilePath) ->
|
||||
|
||||
format_error({ skip_native, Module }) ->
|
||||
io_lib:format("skipping native compilation for ~ts because it contains on_load attribute",
|
||||
[elixir_errors:inspect(Module)]).
|
||||
[elixir_aliases:inspect(Module)]).
|
||||
|
||||
format_errors([]) ->
|
||||
exit({ nocompile, "compilation failed but no error was raised" });
|
||||
|
||||
format_errors(Errors) ->
|
||||
lists:foreach(fun ({File, Each}) ->
|
||||
lists:foreach(fun (Error) -> elixir_errors:handle_file_error(File, Error) end, Each)
|
||||
BinFile = elixir_utils:characters_to_binary(File),
|
||||
lists:foreach(fun (Error) -> elixir_errors:handle_file_error(BinFile, Error) end, Each)
|
||||
end, Errors).
|
||||
|
||||
format_warnings(Bootstrap, Warnings) ->
|
||||
lists:foreach(fun ({File, Each}) ->
|
||||
lists:foreach(fun (Warning) -> elixir_errors:handle_file_warning(Bootstrap, File, Warning) end, Each)
|
||||
BinFile = elixir_utils:characters_to_binary(File),
|
||||
lists:foreach(fun (Warning) -> elixir_errors:handle_file_warning(Bootstrap, BinFile, Warning) end, Each)
|
||||
end, Warnings).
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
-module(elixir_counter).
|
||||
-export([start_link/0, init/1, handle_call/3, handle_cast/2,
|
||||
handle_info/2, terminate/2, code_change/3, next/0]).
|
||||
-behaviour(gen_server).
|
||||
|
||||
-define(timeout, 1000). %% 1 second
|
||||
-define(limit, 4294967296). %% 2^32
|
||||
|
||||
next() ->
|
||||
gen_server:call(?MODULE, next, ?timeout).
|
||||
|
||||
start_link() ->
|
||||
gen_server:start_link({local, ?MODULE}, ?MODULE, 0, []).
|
||||
|
||||
init(Counter) ->
|
||||
{ ok, Counter }.
|
||||
|
||||
handle_call(next, _From, Counter) ->
|
||||
{ reply, Counter, bump(Counter) };
|
||||
handle_call(Request, _From, Counter) ->
|
||||
{ stop, { badcall, Request }, Counter }.
|
||||
|
||||
handle_cast(Request, Counter) ->
|
||||
{ stop, { badcast, Request }, Counter }.
|
||||
|
||||
handle_info(_Request, Counter) ->
|
||||
{ noreply, Counter }.
|
||||
|
||||
terminate(_Reason, _Counter) ->
|
||||
ok.
|
||||
|
||||
code_change(_Old, Counter, _Extra) ->
|
||||
{ ok, Counter }.
|
||||
|
||||
bump(Counter) when Counter < ?limit ->
|
||||
Counter + 1;
|
||||
bump(_Counter) ->
|
||||
0.
|
||||
+106
-127
@@ -1,17 +1,9 @@
|
||||
% Holds the logic responsible for functions definition (def(p) and defmacro(p)).
|
||||
-module(elixir_def).
|
||||
-export([table/1,
|
||||
clauses_table/1,
|
||||
setup/1,
|
||||
cleanup/1,
|
||||
reset_last/1,
|
||||
lookup_definition/2,
|
||||
delete_definition/2,
|
||||
wrap_definition/6,
|
||||
store_definition/7,
|
||||
store_each/8,
|
||||
unwrap_stored_definitions/2,
|
||||
format_error/1]).
|
||||
-export([table/1, clauses_table/1, setup/1,
|
||||
cleanup/1, reset_last/1, lookup_definition/2,
|
||||
delete_definition/2, store_definition/6, unwrap_definitions/1,
|
||||
store_each/8, format_error/1]).
|
||||
-include("elixir.hrl").
|
||||
|
||||
-define(attr, '__def_table').
|
||||
@@ -53,76 +45,59 @@ delete_definition(Module, Tuple) ->
|
||||
ets:delete(table(Module), Tuple),
|
||||
ets:delete(clauses_table(Module), Tuple).
|
||||
|
||||
%% Wraps the function into a call to store_definition once the function
|
||||
%% definition is read. The function is compiled into a meta tree to ensure
|
||||
%% we will receive the full function.
|
||||
%%
|
||||
%% We need to wrap functions instead of eagerly defining them to ensure
|
||||
%% functions inside branches won't propagate, for example:
|
||||
%%
|
||||
%% if false do
|
||||
%% def bar, do: 1
|
||||
%% else
|
||||
%% def bar, do: 2
|
||||
%% end
|
||||
%%
|
||||
%% If we just analyzed the compiled structure (i.e. the function availables
|
||||
%% before evaluating the function body), we would see both definitions.
|
||||
|
||||
wrap_definition(Kind, Meta, Call, Expr, CheckClauses, S) ->
|
||||
Line = ?line(Meta),
|
||||
?wrap_call(Line, ?MODULE, store_definition, [
|
||||
{atom, Line, Kind},
|
||||
{integer, Line, Line},
|
||||
{atom, Line, CheckClauses},
|
||||
{var, Line, '_@MODULE'},
|
||||
Call,
|
||||
Expr,
|
||||
elixir_scope:serialize(S)
|
||||
]).
|
||||
|
||||
% Invoked by the wrap definition with the function abstract tree.
|
||||
% Each function is then added to the function table.
|
||||
|
||||
store_definition(Kind, Line, CheckClauses, Module, Call, Body, RawS) ->
|
||||
S = elixir_scope:deserialize(RawS),
|
||||
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),
|
||||
|
||||
{ Name, Args } = case elixir_clauses:extract_args(NameAndArgs) of
|
||||
error ->
|
||||
Format = [Kind, 'Elixir.Macro':to_string(NameAndArgs)],
|
||||
elixir_errors:syntax_error(Line, S#elixir_scope.file, "invalid syntax in ~ts ~ts", Format);
|
||||
Tuple ->
|
||||
Tuple
|
||||
{ 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 })
|
||||
end,
|
||||
|
||||
assert_no_aliases_name(Line, Name, Args, S),
|
||||
store_definition(Kind, Line, CheckClauses, Module, Name, Args, Guards, Body, S).
|
||||
%% Now that we have verified the call format,
|
||||
%% extract meta information like file and context.
|
||||
{ _, Meta, _ } = Call,
|
||||
DoCheckClauses = (not lists:keymember(context, 1, Meta)) andalso (CheckClauses),
|
||||
|
||||
store_definition(Kind, Line, _CheckClauses, nil, _Name, _Args, _Guards, _Body, #elixir_scope{file=File}) ->
|
||||
elixir_errors:form_error(Line, File, ?MODULE, { no_module, Kind });
|
||||
%% 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 }
|
||||
end,
|
||||
|
||||
store_definition(Kind, Line, CheckClauses, Module, Name, Args, Guards, Body, #elixir_scope{} = DS) ->
|
||||
LinifyArgs = elixir_quote:linify(Line, Key, Args),
|
||||
LinifyGuards = elixir_quote:linify(Line, Key, Guards),
|
||||
LinifyBody = elixir_quote:linify(Line, Key, Body),
|
||||
|
||||
assert_no_aliases_name(Line, Name, Args, E),
|
||||
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) ->
|
||||
Arity = length(Args),
|
||||
Tuple = { Name, Arity },
|
||||
S = DS#elixir_scope{module=Module, function=Tuple},
|
||||
elixir_tracker:record_definition(Tuple, Kind, Module),
|
||||
E = ER#elixir_env{function=Tuple},
|
||||
elixir_locals:record_definition(Tuple, Kind, Module),
|
||||
|
||||
CO = elixir_compiler:get_opts(),
|
||||
Location = retrieve_file(Line, Module, S, CO),
|
||||
run_on_definition_callbacks(Kind, Line, Module, Name, Args, Guards, Body, S, CO),
|
||||
|
||||
{ Function, Defaults, TS } = translate_definition(Kind, Line, Name, Args, Guards, Body, S),
|
||||
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),
|
||||
|
||||
DefaultsLength = length(Defaults),
|
||||
elixir_tracker:record_defaults(Tuple, Kind, Module, DefaultsLength),
|
||||
elixir_locals:record_defaults(Tuple, Kind, Module, DefaultsLength),
|
||||
|
||||
File = TS#elixir_scope.file,
|
||||
Table = table(Module),
|
||||
File = E#elixir_env.file,
|
||||
Table = table(Module),
|
||||
CTable = clauses_table(Module),
|
||||
|
||||
compile_super(Module, TS),
|
||||
check_previous_defaults(Table, Line, Name, Arity, Kind, DefaultsLength, S),
|
||||
compile_super(Module, Super, E),
|
||||
check_previous_defaults(Table, Line, Name, Arity, Kind, DefaultsLength, E),
|
||||
|
||||
store_each(CheckClauses, Kind, File, Location,
|
||||
Table, CTable, DefaultsLength, Function),
|
||||
@@ -133,85 +108,85 @@ store_definition(Kind, Line, CheckClauses, Module, Name, Args, Guards, Body, #el
|
||||
|
||||
%% @on_definition
|
||||
|
||||
run_on_definition_callbacks(Kind, Line, Module, Name, Args, Guards, Expr, S, CO) ->
|
||||
case elixir_compiler:get_opt(internal, CO) of
|
||||
run_on_definition_callbacks(Kind, Line, Module, Name, Args, Guards, Expr, E) ->
|
||||
case elixir_compiler:get_opt(internal) of
|
||||
true ->
|
||||
ok;
|
||||
_ ->
|
||||
Env = elixir_scope:to_ex_env({ Line, S }),
|
||||
Env = elixir_env:env_to_ex({ Line, E }),
|
||||
Callbacks = 'Elixir.Module':get_attribute(Module, on_definition),
|
||||
[Mod:Fun(Env, Kind, Name, Args, Guards, Expr) || { Mod, Fun } <- Callbacks]
|
||||
end.
|
||||
|
||||
%% Retrieve @file or fallback to default
|
||||
%% Retrieve location from meta file or @file, otherwise nil
|
||||
|
||||
retrieve_file(Line, Module, S, CO) ->
|
||||
case elixir_compiler:get_opt(internal, CO) of
|
||||
true -> { elixir_utils:characters_to_list(S#elixir_scope.file), Line };
|
||||
_ ->
|
||||
case 'Elixir.Module':get_attribute(Module, file) of
|
||||
nil -> { elixir_utils:characters_to_list(S#elixir_scope.file), Line };
|
||||
Else ->
|
||||
'Elixir.Module':delete_attribute(Module, file),
|
||||
{ Else, 0 }
|
||||
end
|
||||
retrieve_location(Line, File, Module) ->
|
||||
case not(elixir_compiler:get_opt(internal)) andalso
|
||||
'Elixir.Module':get_attribute(Module, file) of
|
||||
X when X == nil; X == false ->
|
||||
if
|
||||
is_binary(File) ->
|
||||
{ elixir_utils:characters_to_list(elixir_utils:relative_to_cwd(File)), Line };
|
||||
true ->
|
||||
nil
|
||||
end;
|
||||
X ->
|
||||
'Elixir.Module':delete_attribute(Module, file),
|
||||
{ X, 0 }
|
||||
end.
|
||||
|
||||
%% Compile super
|
||||
|
||||
compile_super(Module, #elixir_scope{function=Function, super=true}) ->
|
||||
compile_super(Module, true, #elixir_env{function=Function}) ->
|
||||
elixir_def_overridable:store(Module, Function, true);
|
||||
|
||||
compile_super(_Module, _S) -> ok.
|
||||
compile_super(_Module, _, _E) -> ok.
|
||||
|
||||
%% Translate the given call and expression given
|
||||
%% and then store it in memory.
|
||||
|
||||
translate_definition(Kind, Line, Name, RawArgs, RawGuards, RawBody, S) when is_integer(Line) ->
|
||||
Args = elixir_quote:linify(Line, RawArgs),
|
||||
Guards = elixir_quote:linify(Line, RawGuards),
|
||||
Body = elixir_quote:linify(Line, RawBody),
|
||||
Arity = length(Args),
|
||||
translate_definition(Kind, Line, Name, Args, Guards, Body, E) when is_integer(Line) ->
|
||||
Arity = length(Args),
|
||||
|
||||
%% Macros receive a special argument on invocation. Notice it does
|
||||
%% not affect the arity of the stored function, but the clause
|
||||
%% already contains it.
|
||||
ExtendedArgs = case is_macro(Kind) of
|
||||
AllArgs = case is_macro(Kind) of
|
||||
true -> [{ '_@CALLER', [{line,Line}], nil }|Args];
|
||||
false -> Args
|
||||
end,
|
||||
|
||||
{ Unpacked, Defaults } = elixir_def_defaults:unpack(Kind, Name, ExtendedArgs, S),
|
||||
{ Clauses, TS } = translate_clause(Line, Kind, Unpacked, Guards, Body, S),
|
||||
{ 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),
|
||||
|
||||
Function = { function, Line, Name, Arity, Clauses },
|
||||
{ Function, Defaults, TS }.
|
||||
{ Function, Defaults, Super }.
|
||||
|
||||
translate_clause(_Line, _Kind, _Unpacked, [], nil, S) ->
|
||||
{ [], S };
|
||||
|
||||
translate_clause(Line, Kind, _Unpacked, _Guards, nil, #elixir_scope{file=File}) ->
|
||||
elixir_errors:syntax_error(Line, File, "missing do keyword in ~ts", [Kind]);
|
||||
|
||||
translate_clause(Line, Kind, Unpacked, Guards, Body, S) ->
|
||||
Expr = expr_from_body(Line, Body),
|
||||
|
||||
{ TClause, TS } = elixir_clauses:assigns_block(Line,
|
||||
fun elixir_translator:translate/2, Unpacked, [Expr], Guards, S),
|
||||
translate_clause(nil, _Line, _Kind, _Args, [], _Body, _S) ->
|
||||
{ [], false };
|
||||
translate_clause(nil, Line, Kind, _Args, _Guards, _Body, #elixir_scope{file=File}) ->
|
||||
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),
|
||||
|
||||
%% 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_scope, to_ex_env, [{ var, Line, '_@CALLER' }])
|
||||
?wrap_call(Line, elixir_env, env_to_ex, [{ var, Line, '_@CALLER' }])
|
||||
},
|
||||
setelement(5, TClause, [FBody|element(5, TClause)]);
|
||||
false -> TClause
|
||||
end,
|
||||
|
||||
{ [FClause], TS }.
|
||||
{ [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] }.
|
||||
|
||||
@@ -222,20 +197,20 @@ is_macro(_) -> false.
|
||||
% Unwrap the functions stored in the functions table.
|
||||
% It returns a list of all functions to be exported, plus the macros,
|
||||
% and the body of all functions.
|
||||
unwrap_stored_definitions(File, Module) ->
|
||||
unwrap_definitions(Module) ->
|
||||
Table = table(Module),
|
||||
CTable = clauses_table(Module),
|
||||
ets:delete(Table, last),
|
||||
unwrap_stored_definition(ets:tab2list(Table), CTable, File, [], [], [], [], [], [], []).
|
||||
unwrap_definition(ets:tab2list(Table), CTable, [], [], [], [], [], [], []).
|
||||
|
||||
unwrap_stored_definition([Fun|T], CTable, File, All, Exports, Private, Def, Defmacro, Functions, Tail) ->
|
||||
unwrap_definition([Fun|T], CTable, All, Exports, Private, Def, Defmacro, Functions, Tail) ->
|
||||
Tuple = element(1, Fun),
|
||||
Clauses = [Clause || { _, Clause } <- ets:lookup(CTable, Tuple)],
|
||||
|
||||
{ NewFun, NewExports, NewPrivate, NewDef, NewDefmacro } =
|
||||
case Clauses of
|
||||
[] -> { false, Exports, Private, Def, Defmacro };
|
||||
_ -> unwrap_stored_definition(element(2, Fun), Tuple, Fun, Exports, Private, Def, Defmacro)
|
||||
_ -> unwrap_definition(element(2, Fun), Tuple, Fun, Exports, Private, Def, Defmacro)
|
||||
end,
|
||||
|
||||
{ NewFunctions, NewTail } = case NewFun of
|
||||
@@ -244,39 +219,35 @@ unwrap_stored_definition([Fun|T], CTable, File, All, Exports, Private, Def, Defm
|
||||
{ Functions, Tail };
|
||||
_ ->
|
||||
NewAll = [Tuple|All],
|
||||
function_for_stored_definition(NewFun, Clauses, File, Functions, Tail)
|
||||
function_for_stored_definition(NewFun, Clauses, Functions, Tail)
|
||||
end,
|
||||
|
||||
unwrap_stored_definition(T, CTable, File, NewAll, NewExports, NewPrivate,
|
||||
unwrap_definition(T, CTable, NewAll, NewExports, NewPrivate,
|
||||
NewDef, NewDefmacro, NewFunctions, NewTail);
|
||||
|
||||
unwrap_stored_definition([], _CTable, _File, All, Exports, Private, Def, Defmacro, Functions, Tail) ->
|
||||
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) }.
|
||||
|
||||
unwrap_stored_definition(def, Tuple, Fun, Exports, Private, Def, Defmacro) ->
|
||||
unwrap_definition(def, Tuple, Fun, Exports, Private, Def, Defmacro) ->
|
||||
{ Fun, [Tuple|Exports], Private, [Tuple|Def], Defmacro };
|
||||
|
||||
unwrap_stored_definition(defmacro, { Name, Arity } = Tuple, Fun, Exports, Private, Def, Defmacro) ->
|
||||
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] };
|
||||
|
||||
unwrap_stored_definition(defp, Tuple, Fun, Exports, Private, Def, 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 };
|
||||
|
||||
unwrap_stored_definition(defmacrop, Tuple, Fun, Exports, 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 }.
|
||||
|
||||
%% Helpers
|
||||
|
||||
function_for_stored_definition({{Name,Arity}, _, Line, _, _, {File, _}, _}, Clauses, File, Functions, Tail) ->
|
||||
function_for_stored_definition({{Name,Arity}, _, Line, _, _, nil, _}, Clauses, Functions, Tail) ->
|
||||
{ [{ function, Line, Name, Arity, Clauses }|Functions], Tail };
|
||||
|
||||
function_for_stored_definition({{Name,Arity}, _, Line, _, _, Location, _}, Clauses, _File, Functions, Tail) ->
|
||||
function_for_stored_definition({{Name,Arity}, _, Line, _, _, Location, _}, Clauses, Functions, Tail) ->
|
||||
{ Functions, [
|
||||
{ function, Line, Name, Arity, Clauses },
|
||||
{ attribute, Line, file, Location } | Tail
|
||||
@@ -336,10 +307,10 @@ check_valid_defaults(Line, File, Name, Arity, Kind, _, 0) ->
|
||||
check_valid_defaults(Line, File, Name, Arity, Kind, _, _) ->
|
||||
elixir_errors:form_error(Line, File, ?MODULE, { clauses_with_defaults, { Kind, Name, Arity } }).
|
||||
|
||||
check_previous_defaults(Table, Line, Name, Arity, Kind, Defaults, S) ->
|
||||
check_previous_defaults(Table, Line, Name, Arity, Kind, Defaults, E) ->
|
||||
Matches = ets:match(Table, { { Name, '$2' }, '$1', '_', '_', '_', '_', '$3' }),
|
||||
[ begin
|
||||
elixir_errors:form_error(Line, S#elixir_scope.file, ?MODULE,
|
||||
elixir_errors:form_error(Line, E#elixir_env.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)].
|
||||
|
||||
@@ -347,9 +318,8 @@ defaults_conflict(A, D, Arity, Defaults) ->
|
||||
((Arity >= (A - D)) andalso (Arity < A)) orelse
|
||||
((A >= (Arity - Defaults)) andalso (A < Arity)).
|
||||
|
||||
assert_no_aliases_name(Line, '__aliases__', [Atom], #elixir_scope{file=File}) when is_atom(Atom) ->
|
||||
Message = "function names should start with lowercase characters or underscore, invalid name ~ts",
|
||||
elixir_errors:syntax_error(Line, File, Message, [Atom]);
|
||||
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(_Meta, _Aliases, _Args, _S) ->
|
||||
ok.
|
||||
@@ -379,4 +349,13 @@ format_error({ungrouped_clause,{Kind,Name,Arity,OrigLine,OrigFile}}) ->
|
||||
[Kind, Kind, Name, Arity, OrigFile, OrigLine]);
|
||||
|
||||
format_error({changed_kind,{Name,Arity,Previous,Current}}) ->
|
||||
io_lib:format("~ts ~ts/~B already defined as ~ts", [Current, Name, Arity, Previous]).
|
||||
io_lib:format("~ts ~ts/~B already defined as ~ts", [Current, Name, Arity, Previous]);
|
||||
|
||||
format_error({no_alias, Atom}) ->
|
||||
io_lib:format("function names should start with lowercase characters or underscore, invalid name ~ts", [Atom]);
|
||||
|
||||
format_error({invalid_def, Kind, NameAndArgs}) ->
|
||||
io_lib:format("invalid syntax in ~ts ~ts", [Kind, 'Elixir.Macro':to_string(NameAndArgs)]);
|
||||
|
||||
format_error({missing_do, Kind}) ->
|
||||
io_lib:format("missing do keyword in ~ts", [Kind]).
|
||||
|
||||
@@ -1,8 +1,24 @@
|
||||
% Handle default clauses for function definitions.
|
||||
-module(elixir_def_defaults).
|
||||
-export([unpack/4]).
|
||||
-export([expand/2, unpack/4]).
|
||||
-include("elixir.hrl").
|
||||
|
||||
expand(Args, E) ->
|
||||
lists:mapfoldl(fun
|
||||
({ '//', Meta, [Left, Right] }, Acc) ->
|
||||
elixir_errors:deprecation(Meta, E#elixir_env.file, "using // for default arguments is deprecated, "
|
||||
"please use \\\\ instead"),
|
||||
{ ELeft, EL } = elixir_exp:expand(Left, Acc),
|
||||
{ ERight, _ } = elixir_exp:expand(Right, Acc#elixir_env{context=nil}),
|
||||
{ { '\\\\', Meta, [ELeft, ERight] }, EL };
|
||||
({ '\\\\', Meta, [Left, Right] }, Acc) ->
|
||||
{ ELeft, EL } = elixir_exp:expand(Left, Acc),
|
||||
{ ERight, _ } = elixir_exp:expand(Right, Acc#elixir_env{context=nil}),
|
||||
{ { '\\\\', Meta, [ELeft, ERight] }, EL };
|
||||
(Left, Acc) ->
|
||||
elixir_exp:expand(Left, Acc)
|
||||
end, E, Args).
|
||||
|
||||
unpack(Kind, Name, Args, S) ->
|
||||
unpack_each(Kind, Name, Args, [], [], S).
|
||||
|
||||
@@ -11,16 +27,16 @@ unpack(Kind, Name, Args, S) ->
|
||||
%% Unpack default from given args.
|
||||
%% Returns the given arguments without their default
|
||||
%% clauses and a list of clauses for the default calls.
|
||||
unpack_each(Kind, Name, [{'//', Line, [Expr, _]}|T] = List, Acc, Clauses, S) ->
|
||||
unpack_each(Kind, Name, [{'\\\\', Line, [Expr, _]}|T] = List, Acc, Clauses, S) ->
|
||||
Base = build_match(Acc, Line, []),
|
||||
{ Args, Invoke } = extract_defaults(List, Line, length(Base), [], []),
|
||||
|
||||
{ DefArgs, SA } = elixir_clauses:assigns(fun elixir_translator:translate/2, Base ++ Args, S),
|
||||
{ InvokeArgs, _ } = elixir_translator:translate_args(Base ++ Invoke, SA),
|
||||
{ DefArgs, SA } = elixir_clauses:match(fun elixir_translator:translate_many/2, Base ++ Args, S),
|
||||
{ DefInvoke, _ } = elixir_translator:translate_args(Base ++ Invoke, SA),
|
||||
|
||||
Call = { call, Line,
|
||||
{ atom, Line, name_for_kind(Kind, Name) },
|
||||
InvokeArgs
|
||||
DefInvoke
|
||||
},
|
||||
|
||||
Clause = { clause, Line, DefArgs, [], [Call] },
|
||||
@@ -34,7 +50,7 @@ unpack_each(_Kind, _Name, [], Acc, Clauses, _S) ->
|
||||
|
||||
% Extract default values from args following the current default clause.
|
||||
|
||||
extract_defaults([{'//', _, [_Expr, Default]}|T], Line, Counter, NewArgs, NewInvoke) ->
|
||||
extract_defaults([{'\\\\', _, [_Expr, Default]}|T], Line, Counter, NewArgs, NewInvoke) ->
|
||||
extract_defaults(T, Line, Counter, NewArgs, [Default|NewInvoke]);
|
||||
|
||||
extract_defaults([_|T], Line, Counter, NewArgs, NewInvoke) ->
|
||||
|
||||
@@ -1,62 +0,0 @@
|
||||
%% Module responsible for local invocation of macros and functions.
|
||||
-module(elixir_def_local).
|
||||
-export([macro_for/3, function_for/3]).
|
||||
-include("elixir.hrl").
|
||||
|
||||
macro_for(_Tuple, _All, #elixir_scope{module=nil}) -> false;
|
||||
|
||||
macro_for(Tuple, All, #elixir_scope{module=Module}) ->
|
||||
try elixir_def:lookup_definition(Module, Tuple) of
|
||||
{ { Tuple, Kind, Line, _, _, _, _ }, Clauses } when Kind == defmacro, Clauses /= [];
|
||||
All, Kind == defmacrop, Clauses /= [] ->
|
||||
fun() -> get_function(Line, Module, Clauses) end;
|
||||
_ ->
|
||||
false
|
||||
catch
|
||||
error:badarg -> false
|
||||
end.
|
||||
|
||||
function_for(Module, Name, Arity) ->
|
||||
Tuple = { Name, Arity },
|
||||
case elixir_def:lookup_definition(Module, Tuple) of
|
||||
{ { Tuple, _, Line, _, _, _, _ }, Clauses } when Clauses /= [] ->
|
||||
%% There is no need to record such calls
|
||||
%% since they come from funtions that were
|
||||
%% already analyzed at compilation time.
|
||||
get_function(Line, Module, Clauses);
|
||||
_ ->
|
||||
[_|T] = erlang:get_stacktrace(),
|
||||
erlang:raise(error, undef, [{Module,Name,Arity,[]}|T])
|
||||
end.
|
||||
|
||||
get_function(Line, Module, Clauses) ->
|
||||
RewrittenClauses = [rewrite_clause(Clause, Module) || Clause <- Clauses],
|
||||
Fun = { 'fun', Line, {clauses, RewrittenClauses } },
|
||||
{ value, Result, _Binding } = erl_eval:exprs([Fun], []),
|
||||
Result.
|
||||
|
||||
rewrite_clause({ call, Line, { atom, Line, RawName }, Args }, Module) ->
|
||||
Remote = { remote, Line,
|
||||
{ atom, Line, ?MODULE },
|
||||
{ atom, Line, function_for }
|
||||
},
|
||||
|
||||
%% If we have a macro, its arity in the table is
|
||||
%% actually one less than in the function call
|
||||
{ Name, Arity } = case atom_to_list(RawName) of
|
||||
"MACRO-" ++ Rest -> { list_to_atom(Rest), length(Args) - 1 };
|
||||
_ -> { RawName, length(Args) }
|
||||
end,
|
||||
|
||||
FunCall = { call, Line, Remote, [
|
||||
{ atom, Line, Module }, { atom, Line, Name }, { integer, Line, Arity }
|
||||
] },
|
||||
{ call, Line, FunCall, Args };
|
||||
|
||||
rewrite_clause(Tuple, Module) when is_tuple(Tuple) ->
|
||||
list_to_tuple(rewrite_clause(tuple_to_list(Tuple), Module));
|
||||
|
||||
rewrite_clause(List, Module) when is_list(List) ->
|
||||
[rewrite_clause(Item, Module) || Item <- List];
|
||||
|
||||
rewrite_clause(Else, _) -> Else.
|
||||
@@ -1,6 +1,6 @@
|
||||
% Holds the logic responsible for defining overridable functions and handling super.
|
||||
-module(elixir_def_overridable).
|
||||
-export([store_pending/1, is_defined/2, ensure_defined/4,
|
||||
-export([store_pending/1, ensure_defined/4,
|
||||
name/2, store/3, format_error/1]).
|
||||
-include("elixir.hrl").
|
||||
|
||||
@@ -12,17 +12,11 @@ overridable(Module, Value) ->
|
||||
|
||||
%% Check if an overridable function is defined.
|
||||
|
||||
is_defined(Module, Tuple) ->
|
||||
ensure_defined(Meta, Module, Tuple, S) ->
|
||||
Overridable = overridable(Module),
|
||||
case orddict:find(Tuple, Overridable) of
|
||||
{ ok, { _, _, _, _ } } -> true;
|
||||
_ -> false
|
||||
end.
|
||||
|
||||
ensure_defined(Meta, Module, Tuple, S) ->
|
||||
case is_defined(Module, Tuple) of
|
||||
true -> ok;
|
||||
_ -> elixir_errors:form_error(Meta, S#elixir_scope.file, ?MODULE, { no_super, Module, Tuple })
|
||||
{ ok, { _, _, _, _ } } -> ok;
|
||||
_ -> elixir_errors:form_error(Meta, S#elixir_scope.file, ?MODULE, { no_super, Module, Tuple })
|
||||
end.
|
||||
|
||||
%% Gets the name based on the function and stored overridables
|
||||
@@ -49,7 +43,12 @@ store(Module, Function, GenerateName) ->
|
||||
false -> { Kind, Name }
|
||||
end,
|
||||
|
||||
'Elixir.Module.DispatchTracker':reattach(Module, Kind, { Name, Arity }, Neighbours),
|
||||
case code:is_loaded('Elixir.Module.LocalsTracker') of
|
||||
{ _, _ } ->
|
||||
'Elixir.Module.LocalsTracker':reattach(Module, Kind, { Name, Arity }, Neighbours);
|
||||
_ ->
|
||||
ok
|
||||
end,
|
||||
|
||||
Def = { function, Line, FinalName, Arity, Clauses },
|
||||
elixir_def:store_each(false, FinalKind, File, Location,
|
||||
@@ -68,7 +67,7 @@ format_error({ no_super, Module, { Name, Arity } }) ->
|
||||
Bins = [format_fa(X) || { X, { _, _, _, _ } } <- overridable(Module)],
|
||||
Joined = 'Elixir.Enum':join(Bins, <<", ">>),
|
||||
io_lib:format("no super defined for ~ts/~B in module ~ts. Overridable functions available are: ~ts",
|
||||
[Name, Arity, elixir_errors:inspect(Module), Joined]).
|
||||
[Name, Arity, elixir_aliases:inspect(Module), Joined]).
|
||||
|
||||
format_fa({ Name, Arity }) ->
|
||||
A = atom_to_binary(Name, utf8),
|
||||
|
||||
+316
-378
@@ -2,34 +2,37 @@
|
||||
%% This module access the information stored on the scope
|
||||
%% by elixir_import and therefore assumes it is normalized (ordsets)
|
||||
-module(elixir_dispatch).
|
||||
-export([default_macros/0, default_functions/0, default_requires/0,
|
||||
dispatch_require/6, dispatch_import/5,
|
||||
-export([dispatch_import/5, dispatch_require/6,
|
||||
require_function/5, import_function/4,
|
||||
expand_import/6, expand_require/6, find_import/4,
|
||||
format_error/1, in_erlang_functions/0, in_erlang_macros/0]).
|
||||
expand_import/5, expand_require/5,
|
||||
default_functions/0, default_macros/0, default_requires/0,
|
||||
find_import/4, format_error/1]).
|
||||
-include("elixir.hrl").
|
||||
|
||||
-import(ordsets, [is_element/2]).
|
||||
-define(builtin, 'Elixir.Kernel').
|
||||
|
||||
%% Module macros
|
||||
-define(kernel, 'Elixir.Kernel').
|
||||
-define(node, 'Elixir.Node').
|
||||
-define(process, 'Elixir.Process').
|
||||
|
||||
default_functions() ->
|
||||
[ { ?builtin, elixir_imported_functions() } ].
|
||||
[ { ?kernel, elixir_imported_functions() } ].
|
||||
default_macros() ->
|
||||
[ { ?builtin, elixir_imported_macros() } ].
|
||||
[ { ?kernel, elixir_imported_macros() } ].
|
||||
default_requires() ->
|
||||
[ 'Elixir.Integer', 'Elixir.Kernel', 'Elixir.Kernel.Typespec', 'Elixir.Record' ].
|
||||
[ 'Elixir.Kernel', 'Elixir.Kernel.Typespec' ].
|
||||
|
||||
find_import(Meta, Name, Arity, S) ->
|
||||
find_import(Meta, Name, Arity, E) ->
|
||||
Tuple = { Name, Arity },
|
||||
|
||||
case find_dispatch(Meta, Tuple, S) of
|
||||
case find_dispatch(Meta, Tuple, [], E) of
|
||||
{ function, Receiver } ->
|
||||
elixir_lexical:record_import(Receiver, S#elixir_scope.lexical_tracker),
|
||||
elixir_tracker:record_import(Tuple, Receiver, S#elixir_scope.module, S#elixir_scope.function),
|
||||
elixir_lexical:record_import(Receiver, E#elixir_env.lexical_tracker),
|
||||
elixir_locals:record_import(Tuple, Receiver, E#elixir_env.module, E#elixir_env.function),
|
||||
Receiver;
|
||||
{ macro, Receiver } ->
|
||||
elixir_lexical:record_import(Receiver, S#elixir_scope.lexical_tracker),
|
||||
elixir_tracker:record_import(Tuple, Receiver, S#elixir_scope.module, S#elixir_scope.function),
|
||||
elixir_lexical:record_import(Receiver, E#elixir_env.lexical_tracker),
|
||||
elixir_locals:record_import(Tuple, Receiver, E#elixir_env.module, E#elixir_env.function),
|
||||
Receiver;
|
||||
_ ->
|
||||
false
|
||||
@@ -37,239 +40,205 @@ find_import(Meta, Name, Arity, S) ->
|
||||
|
||||
%% Function retrieval
|
||||
|
||||
import_function(Meta, Name, Arity, S) ->
|
||||
import_function(Meta, Name, Arity, E) ->
|
||||
Tuple = { Name, Arity },
|
||||
case find_dispatch(Meta, Tuple, S) of
|
||||
case find_dispatch(Meta, Tuple, [], E) of
|
||||
{ function, Receiver } ->
|
||||
elixir_lexical:record_import(Receiver, S#elixir_scope.lexical_tracker),
|
||||
elixir_tracker:record_import(Tuple, Receiver, S#elixir_scope.module, S#elixir_scope.function),
|
||||
remote_function(Meta, Receiver, Name, Arity, S);
|
||||
elixir_lexical:record_import(Receiver, E#elixir_env.lexical_tracker),
|
||||
elixir_locals:record_import(Tuple, Receiver, E#elixir_env.module, E#elixir_env.function),
|
||||
remote_function(Receiver, Name, Arity, E);
|
||||
{ macro, _Receiver } ->
|
||||
false;
|
||||
{ import, Receiver } ->
|
||||
require_function(Meta, Receiver, Name, Arity, S);
|
||||
require_function(Meta, Receiver, Name, Arity, E);
|
||||
false ->
|
||||
case elixir_import:special_form(Name, Arity) of
|
||||
true -> false;
|
||||
false ->
|
||||
elixir_tracker:record_local(Tuple, S#elixir_scope.module, S#elixir_scope.function),
|
||||
{ { 'fun', ?line(Meta), { function, Name, Arity } }, S }
|
||||
elixir_locals:record_local(Tuple, E#elixir_env.module, E#elixir_env.function),
|
||||
{ local, Name, Arity }
|
||||
end
|
||||
end.
|
||||
|
||||
require_function(Meta, Receiver, Name, Arity, S) ->
|
||||
Tuple = { Name, Arity },
|
||||
|
||||
case is_element(Tuple, get_optional_macros(Receiver)) of
|
||||
require_function(_Meta, Receiver, Name, Arity, E) ->
|
||||
case is_element({ Name, Arity }, get_optional_macros(Receiver)) of
|
||||
true -> false;
|
||||
false -> remote_function(Receiver, Name, Arity, E)
|
||||
end.
|
||||
|
||||
remote_function(Receiver, Name, Arity, E) ->
|
||||
elixir_lexical:record_remote(Receiver, E#elixir_env.lexical_tracker),
|
||||
case inline(Receiver, Name, Arity) of
|
||||
{ AR, AN } -> { remote, AR, AN, Arity };
|
||||
false -> { remote, Receiver, Name, Arity }
|
||||
end.
|
||||
|
||||
%% Dispatches
|
||||
|
||||
dispatch_import(Meta, Name, Args, E, Callback) ->
|
||||
Arity = length(Args),
|
||||
case expand_import(Meta, { Name, Arity }, Args, E, []) of
|
||||
{ ok, Receiver, Quoted } ->
|
||||
expand_quoted(Meta, Receiver, Name, Arity, Quoted, E);
|
||||
{ ok, Receiver } ->
|
||||
elixir_exp:expand({ { '.', [], [Receiver, Name] }, Meta, Args }, E);
|
||||
error ->
|
||||
Callback()
|
||||
end.
|
||||
|
||||
dispatch_require(Meta, Receiver, Name, Args, E, Callback) when is_atom(Receiver) ->
|
||||
Arity = length(Args),
|
||||
|
||||
case rewrite(Receiver, Name, Args, Arity) of
|
||||
{ ok, AR, AN, AA } ->
|
||||
Callback(AR, AN, AA);
|
||||
false ->
|
||||
elixir_lexical:record_remote(Receiver, S#elixir_scope.lexical_tracker),
|
||||
elixir_tracker:record_remote(Tuple, Receiver, S#elixir_scope.module, S#elixir_scope.function),
|
||||
remote_function(Meta, Receiver, Name, Arity, S)
|
||||
end.
|
||||
|
||||
%% Function dispatch
|
||||
|
||||
dispatch_import(Meta, Name, Args, S, Callback) ->
|
||||
Module = S#elixir_scope.module,
|
||||
Arity = length(Args),
|
||||
Tuple = { Name, Arity },
|
||||
|
||||
case find_dispatch(Meta, Tuple, S) of
|
||||
{ function, Receiver } ->
|
||||
elixir_lexical:record_import(Receiver, S#elixir_scope.lexical_tracker),
|
||||
elixir_tracker:record_import(Tuple, Receiver, Module, S#elixir_scope.function),
|
||||
Endpoint = case (Receiver == ?builtin) andalso is_element(Tuple, in_erlang_functions()) of
|
||||
true -> erlang;
|
||||
false -> Receiver
|
||||
end,
|
||||
elixir_translator:translate_each({ { '.', Meta, [Endpoint, Name] }, Meta, Args }, S);
|
||||
{ import, Receiver } ->
|
||||
elixir_lexical:record_remote(Receiver, S#elixir_scope.lexical_tracker),
|
||||
elixir_tracker:record_remote(Tuple, Receiver, S#elixir_scope.module, S#elixir_scope.function),
|
||||
elixir_translator:translate_each({ { '.', Meta, [Receiver, Name] }, [{require,false}|Meta], Args }, S);
|
||||
Result ->
|
||||
case do_expand_import(Meta, Tuple, Args, Module, S, Result) of
|
||||
{ error, noexpansion } ->
|
||||
Callback();
|
||||
{ error, internal } ->
|
||||
elixir_lexical:record_import(?builtin, S#elixir_scope.lexical_tracker),
|
||||
elixir_tracker:record_import(Tuple, ?builtin, Module, S#elixir_scope.function),
|
||||
elixir_macros:translate({ Name, Meta, Args }, S);
|
||||
{ ok, Receiver, Tree } ->
|
||||
translate_expansion(Meta, Receiver, Tree, S)
|
||||
end
|
||||
end.
|
||||
|
||||
dispatch_require(Meta, Receiver, Name, Args, S, Callback) ->
|
||||
Module = S#elixir_scope.module,
|
||||
Arity = length(Args),
|
||||
Tuple = { Name, Arity },
|
||||
|
||||
case (Receiver == ?builtin) andalso is_element(Tuple, in_erlang_functions()) of
|
||||
true ->
|
||||
elixir_lexical:record_remote(Receiver, S#elixir_scope.lexical_tracker),
|
||||
elixir_tracker:record_remote(Tuple, Receiver, S#elixir_scope.module, S#elixir_scope.function),
|
||||
{ TArgs, SA } = elixir_translator:translate_args(Args, S),
|
||||
{ ?wrap_call(?line(Meta), erlang, Name, TArgs), SA };
|
||||
false ->
|
||||
case expand_require(Meta, Receiver, Tuple, Args, Module, S) of
|
||||
{ error, noexpansion } ->
|
||||
Callback();
|
||||
{ error, internal } ->
|
||||
elixir_lexical:record_remote(?builtin, S#elixir_scope.lexical_tracker),
|
||||
elixir_tracker:record_remote(Tuple, ?builtin, S#elixir_scope.module, S#elixir_scope.function),
|
||||
elixir_macros:translate({ Name, Meta, Args }, S);
|
||||
{ ok, Receiver, Tree } ->
|
||||
translate_expansion(Meta, Receiver, Tree, S)
|
||||
end
|
||||
end.
|
||||
|
||||
%% Macros expansion
|
||||
|
||||
expand_import(Meta, Tuple, Args, Module, Extra, S) ->
|
||||
Result = find_dispatch(Meta, Tuple, Extra, S),
|
||||
do_expand_import(Meta, Tuple, Args, Module, S, Result).
|
||||
|
||||
do_expand_import(Meta, { Name, Arity } = Tuple, Args, Module, S, Result) ->
|
||||
Function = S#elixir_scope.function,
|
||||
|
||||
Fun = (Function /= nil) andalso (Function /= Tuple) andalso
|
||||
elixir_def_local:macro_for(Tuple, true, S),
|
||||
|
||||
case Fun of
|
||||
false ->
|
||||
do_expand_import_no_local(Meta, Tuple, Args, Module, S, Result);
|
||||
_ ->
|
||||
case Result of
|
||||
{ Kind, Receiver } when Kind == import; Receiver == Module ->
|
||||
do_expand_import_no_local(Meta, Tuple, Args, Module, S, Result);
|
||||
{ _, Receiver } ->
|
||||
Error = { macro_conflict, { Receiver, Name, Arity } },
|
||||
elixir_errors:form_error(Meta, S#elixir_scope.file, ?MODULE, Error);
|
||||
false ->
|
||||
elixir_tracker:record_local(Tuple, Module, S#elixir_scope.function),
|
||||
{ ok, Module, expand_macro_fun(Meta, Fun(), Module, Name, Args, Module, S) }
|
||||
end
|
||||
end.
|
||||
|
||||
do_expand_import_no_local(Meta, { Name, Arity } = Tuple, Args, Module, S, Result) ->
|
||||
case Result of
|
||||
{ macro, ?builtin } ->
|
||||
case is_element(Tuple, in_erlang_macros()) of
|
||||
true -> { error, internal };
|
||||
false ->
|
||||
elixir_lexical:record_import(?builtin, S#elixir_scope.lexical_tracker),
|
||||
elixir_tracker:record_import(Tuple, ?builtin, Module, S#elixir_scope.function),
|
||||
{ ok, ?builtin, expand_macro_named(Meta, ?builtin, Name, Arity, Args, Module, S) }
|
||||
end;
|
||||
{ macro, Receiver } ->
|
||||
elixir_lexical:record_import(Receiver, S#elixir_scope.lexical_tracker),
|
||||
elixir_tracker:record_import(Tuple, Receiver, Module, S#elixir_scope.function),
|
||||
{ ok, Receiver, expand_macro_named(Meta, Receiver, Name, Arity, Args, Module, S) };
|
||||
{ import, Receiver } ->
|
||||
expand_require(Meta, Receiver, Tuple, Args, Module, S);
|
||||
_ ->
|
||||
{ error, noexpansion }
|
||||
end.
|
||||
|
||||
expand_require(Meta, ?builtin, { Name, Arity } = Tuple, Args, Module, S) ->
|
||||
case is_element(Tuple, in_erlang_macros()) of
|
||||
true -> { error, internal };
|
||||
false ->
|
||||
case is_element(Tuple, in_elixir_macros()) of
|
||||
true ->
|
||||
elixir_lexical:record_remote(?builtin, S#elixir_scope.lexical_tracker),
|
||||
elixir_tracker:record_remote(Tuple, ?builtin, S#elixir_scope.module, S#elixir_scope.function),
|
||||
{ ok, ?builtin, expand_macro_named(Meta, ?builtin, Name, Arity, Args, Module, S) };
|
||||
false ->
|
||||
{ error, noexpansion }
|
||||
case expand_require(Meta, Receiver, { Name, Arity }, Args, E) of
|
||||
{ ok, Receiver, Quoted } -> expand_quoted(Meta, Receiver, Name, Arity, Quoted, E);
|
||||
error -> Callback(Receiver, Name, Args)
|
||||
end
|
||||
end;
|
||||
|
||||
expand_require(Meta, Receiver, { Name, Arity } = Tuple, Args, Module, S) ->
|
||||
Function = S#elixir_scope.function,
|
||||
dispatch_require(_Meta, Receiver, Name, Args, _E, Callback) ->
|
||||
Callback(Receiver, Name, Args).
|
||||
|
||||
%% Macros expansion
|
||||
|
||||
expand_import(Meta, { Name, Arity } = Tuple, Args, E, Extra) ->
|
||||
Module = E#elixir_env.module,
|
||||
Dispatch = find_dispatch(Meta, Tuple, Extra, E),
|
||||
Function = E#elixir_env.function,
|
||||
Local = (Function /= nil) andalso (Function /= Tuple) andalso
|
||||
elixir_locals:macro_for(Module, Name, Arity),
|
||||
|
||||
case Dispatch of
|
||||
%% In case it is an import, we dispatch the import.
|
||||
{ Kind, _ } when Kind == import ->
|
||||
do_expand_import(Meta, Tuple, Args, Module, E, Dispatch);
|
||||
|
||||
%% There is a local and an import. This is a conflict unless
|
||||
%% the receiver is the same as module (happens on bootstrap).
|
||||
{ _, Receiver } when Local /= false, Receiver /= Module ->
|
||||
Error = { macro_conflict, { Receiver, Name, Arity } },
|
||||
elixir_errors:form_error(Meta, E#elixir_env.file, ?MODULE, Error);
|
||||
|
||||
%% There is no local. Dispatch the import.
|
||||
_ when Local == false ->
|
||||
do_expand_import(Meta, Tuple, Args, Module, E, Dispatch);
|
||||
|
||||
%% Dispatch to the local.
|
||||
_ ->
|
||||
elixir_locals:record_local(Tuple, Module, Function),
|
||||
{ ok, Module, expand_macro_fun(Meta, Local(), Module, Name, Args, E) }
|
||||
end.
|
||||
|
||||
do_expand_import(Meta, { Name, Arity } = Tuple, Args, Module, E, Result) ->
|
||||
case Result of
|
||||
{ function, Receiver } ->
|
||||
elixir_lexical:record_import(Receiver, E#elixir_env.lexical_tracker),
|
||||
elixir_locals:record_import(Tuple, Receiver, Module, E#elixir_env.function),
|
||||
case rewrite(Receiver, Name, Args, Arity) of
|
||||
{ ok, AR, AN, AA } ->
|
||||
{ ok, AR, { { '.', [], [AR, AN] }, [], AA } };
|
||||
false ->
|
||||
{ ok, Receiver }
|
||||
end;
|
||||
{ macro, Receiver } ->
|
||||
elixir_lexical:record_import(Receiver, E#elixir_env.lexical_tracker),
|
||||
elixir_locals:record_import(Tuple, Receiver, Module, E#elixir_env.function),
|
||||
{ ok, Receiver, expand_macro_named(Meta, Receiver, Name, Arity, Args, E) };
|
||||
{ import, Receiver } ->
|
||||
case expand_require([{require,false}|Meta], Receiver, Tuple, Args, E) of
|
||||
{ ok, _, _ } = Response -> Response;
|
||||
error -> { ok, Receiver }
|
||||
end;
|
||||
false when Module == ?kernel ->
|
||||
case rewrite(Module, Name, Args, Arity) of
|
||||
{ ok, AR, AN, AA } ->
|
||||
{ ok, AR, { { '.', [], [AR, AN] }, [], AA } };
|
||||
false ->
|
||||
error
|
||||
end;
|
||||
false ->
|
||||
error
|
||||
end.
|
||||
|
||||
expand_require(Meta, Receiver, { Name, Arity } = Tuple, Args, E) ->
|
||||
Module = E#elixir_env.module,
|
||||
|
||||
case is_element(Tuple, get_optional_macros(Receiver)) of
|
||||
true ->
|
||||
elixir_lexical:record_remote(Receiver, S#elixir_scope.lexical_tracker),
|
||||
elixir_tracker:record_remote(Tuple, Receiver, Module, Function),
|
||||
{ ok, Receiver, expand_macro_named(Meta, Receiver, Name, Arity, Args, Module, S) };
|
||||
false -> { error, noexpansion }
|
||||
true ->
|
||||
Requires = E#elixir_env.requires,
|
||||
case (Receiver == Module) orelse is_element(Receiver, Requires) orelse skip_require(Meta) of
|
||||
true ->
|
||||
elixir_lexical:record_remote(Receiver, E#elixir_env.lexical_tracker),
|
||||
{ ok, Receiver, expand_macro_named(Meta, Receiver, Name, Arity, Args, E) };
|
||||
false ->
|
||||
Info = { unrequired_module, { Receiver, Name, length(Args), Requires } },
|
||||
elixir_errors:form_error(Meta, E#elixir_env.file, ?MODULE, Info)
|
||||
end;
|
||||
false ->
|
||||
error
|
||||
end.
|
||||
|
||||
%% Expansion helpers
|
||||
|
||||
expand_macro_fun(Meta, Fun, Receiver, Name, Args, Module, S) ->
|
||||
Requires = S#elixir_scope.requires,
|
||||
case (Receiver == Module) or is_element(Receiver, Requires) or skip_require(Meta) of
|
||||
true -> ok;
|
||||
false ->
|
||||
Tuple = { unrequired_module, { Receiver, Name, length(Args), Requires } },
|
||||
elixir_errors:form_error(Meta, S#elixir_scope.file, ?MODULE, Tuple)
|
||||
end,
|
||||
|
||||
expand_macro_fun(Meta, Fun, Receiver, Name, Args, E) ->
|
||||
Line = ?line(Meta),
|
||||
SArg = {Line,S},
|
||||
EArg = { Line, E },
|
||||
|
||||
try
|
||||
apply(Fun, [SArg|Args])
|
||||
apply(Fun, [EArg|Args])
|
||||
catch
|
||||
Kind:Reason ->
|
||||
Info = { Receiver, Name, length(Args), location(Line, S) },
|
||||
erlang:raise(Kind, Reason, prune_stacktrace(Info, erlang:get_stacktrace(), SArg))
|
||||
Info = [{ Receiver, Name, length(Args), location(Line, E) }, mfa(Line, E)],
|
||||
erlang:raise(Kind, Reason, prune_stacktrace(Info, erlang:get_stacktrace(), EArg))
|
||||
end.
|
||||
|
||||
expand_macro_named(Meta, Receiver, Name, Arity, Args, Module, S) ->
|
||||
expand_macro_named(Meta, Receiver, Name, Arity, Args, E) ->
|
||||
ProperName = ?elixir_macro(Name),
|
||||
ProperArity = Arity + 1,
|
||||
Fun = fun Receiver:ProperName/ProperArity,
|
||||
expand_macro_fun(Meta, Fun, Receiver, Name, Args, Module, S).
|
||||
expand_macro_fun(Meta, Fun, Receiver, Name, Args, E).
|
||||
|
||||
translate_expansion(Meta, Receiver, Tree, S) ->
|
||||
expand_quoted(Meta, Receiver, Name, Arity, Quoted, E) ->
|
||||
Line = ?line(Meta),
|
||||
New = S#elixir_scope.macro_counter + 1,
|
||||
Next = elixir_counter:next(),
|
||||
|
||||
try
|
||||
elixir_translator:translate_each(
|
||||
elixir_quote:linify(Line, { Receiver, New }, Tree),
|
||||
S#elixir_scope{macro_counter=New}
|
||||
)
|
||||
elixir_exp:expand(
|
||||
elixir_quote:linify_with_context_counter(Line, { Receiver, Next }, Quoted),
|
||||
E)
|
||||
catch
|
||||
Kind:Reason ->
|
||||
erlang:raise(Kind, Reason, prune_stacktrace(mfa(Line, S), erlang:get_stacktrace(), nil))
|
||||
Info = [{ Receiver, Name, Arity, location(Line, E) }, mfa(Line, E)],
|
||||
erlang:raise(Kind, Reason, prune_stacktrace(Info, erlang:get_stacktrace(), nil))
|
||||
end.
|
||||
|
||||
mfa(Line, #elixir_scope{module=nil} = S) ->
|
||||
{ elixir_compiler, '__FILE__', 2, location(Line, S) };
|
||||
mfa(Line, #elixir_env{module=nil} = E) ->
|
||||
{ elixir_compiler, '__FILE__', 2, location(Line, E) };
|
||||
mfa(Line, #elixir_env{module=Module, function=nil} = E) ->
|
||||
{ Module, '__MODULE__', 0, location(Line, E) };
|
||||
mfa(Line, #elixir_env{module=Module, function={ Name, Arity }} = E) ->
|
||||
{ Module, Name, Arity, location(Line, E) }.
|
||||
|
||||
mfa(Line, #elixir_scope{module=Module,function=nil} = S) ->
|
||||
{ Module, '__MODULE__', 0, location(Line, S) };
|
||||
|
||||
mfa(Line, #elixir_scope{module=Module,function={ Name, Arity }} = S) ->
|
||||
{ Module, Name, Arity, location(Line, S) }.
|
||||
|
||||
location(Line, S) ->
|
||||
[{ file, S#elixir_scope.file }, { line, Line }].
|
||||
location(Line, E) ->
|
||||
[{ file, elixir_utils:characters_to_list(E#elixir_env.file) }, { line, Line }].
|
||||
|
||||
%% Helpers
|
||||
|
||||
skip_require(Meta) ->
|
||||
lists:keyfind(require, 1, Meta) == { require, false }.
|
||||
|
||||
find_dispatch(Meta, Tuple, S) ->
|
||||
find_dispatch(Meta, Tuple, [], S).
|
||||
|
||||
find_dispatch(Meta, Tuple, Extra, S) ->
|
||||
case is_import(Meta, Tuple, S) of
|
||||
find_dispatch(Meta, Tuple, Extra, E) ->
|
||||
case is_import(Meta) of
|
||||
{ import, _ } = Import ->
|
||||
Import;
|
||||
false ->
|
||||
Functions = S#elixir_scope.functions,
|
||||
Macros = Extra ++ S#elixir_scope.macros,
|
||||
File = S#elixir_scope.file,
|
||||
FunMatch = find_dispatch(Tuple, Functions),
|
||||
MacMatch = find_dispatch(Tuple, Macros),
|
||||
Funs = E#elixir_env.functions,
|
||||
Macs = Extra ++ E#elixir_env.macros,
|
||||
FunMatch = find_dispatch(Tuple, Funs),
|
||||
MacMatch = find_dispatch(Tuple, Macs),
|
||||
|
||||
case { FunMatch, MacMatch } of
|
||||
{ [], [Receiver] } -> { macro, Receiver };
|
||||
@@ -279,21 +248,18 @@ find_dispatch(Meta, Tuple, Extra, S) ->
|
||||
{ Name, Arity } = Tuple,
|
||||
[First, Second|_] = FunMatch ++ MacMatch,
|
||||
Error = { ambiguous_call, { First, Second, Name, Arity } },
|
||||
elixir_errors:form_error(Meta, File, ?MODULE, Error)
|
||||
elixir_errors:form_error(Meta, E#elixir_env.file, ?MODULE, Error)
|
||||
end
|
||||
end.
|
||||
|
||||
find_dispatch(Tuple, List) ->
|
||||
[Receiver || { Receiver, Set } <- List, is_element(Tuple, Set)].
|
||||
|
||||
is_import(Meta, Tuple, S) ->
|
||||
is_import(Meta) ->
|
||||
case lists:keyfind(import, 1, Meta) of
|
||||
{ import, _ } = Import ->
|
||||
case lists:keyfind(context, 1, Meta) of
|
||||
{ context, Context } ->
|
||||
not_an_import(Tuple, Context, S#elixir_scope.macro_functions)
|
||||
andalso not_an_import(Tuple, Context, S#elixir_scope.macro_macros)
|
||||
andalso Import;
|
||||
{ context, _ } -> Import;
|
||||
false ->
|
||||
false
|
||||
end;
|
||||
@@ -301,59 +267,30 @@ is_import(Meta, Tuple, S) ->
|
||||
false
|
||||
end.
|
||||
|
||||
not_an_import(Tuple, Context, Dict) ->
|
||||
case orddict:find(Context, Dict) of
|
||||
{ ok, Pairs } ->
|
||||
not lists:any(fun({ _, List }) -> lists:member(Tuple, List) end, Pairs);
|
||||
error ->
|
||||
true
|
||||
end.
|
||||
|
||||
%% We've reached the invoked macro, skip it with the rest
|
||||
prune_stacktrace(Info, [{ _, _, [S|_], _ }|_], S) ->
|
||||
[Info];
|
||||
|
||||
prune_stacktrace(Info, [{ _, _, [E|_], _ }|_], E) ->
|
||||
Info;
|
||||
%% We've reached the elixir_dispatch internals, skip it with the rest
|
||||
prune_stacktrace(Info, [{ elixir_dispatch, _, _, _ }|_], _) ->
|
||||
[Info];
|
||||
|
||||
prune_stacktrace(Info, [H|T], S) ->
|
||||
[H|prune_stacktrace(Info, T, S)];
|
||||
|
||||
prune_stacktrace(Info, [{ Mod, _, _, _ }|_], _) when Mod == elixir_dispatch; Mod == elixir_exp ->
|
||||
Info;
|
||||
prune_stacktrace(Info, [H|T], E) ->
|
||||
[H|prune_stacktrace(Info, T, E)];
|
||||
prune_stacktrace(Info, [], _) ->
|
||||
[Info].
|
||||
|
||||
remote_function(Meta, Receiver, Name, Arity, S) ->
|
||||
Line = ?line(Meta),
|
||||
|
||||
Final =
|
||||
case (Receiver == ?builtin) andalso is_element({ Name, Arity }, in_erlang_functions()) of
|
||||
true -> erlang;
|
||||
false -> Receiver
|
||||
end,
|
||||
|
||||
{ { 'fun', Line, { function,
|
||||
{ atom, Line, Final },
|
||||
{ atom, Line, Name },
|
||||
{ integer, Line, Arity}
|
||||
} }, S }.
|
||||
|
||||
Info.
|
||||
|
||||
%% ERROR HANDLING
|
||||
|
||||
format_error({ unrequired_module, { Receiver, Name, Arity, _Required }}) ->
|
||||
Module = elixir_errors:inspect(Receiver),
|
||||
Module = elixir_aliases:inspect(Receiver),
|
||||
io_lib:format("you must require ~ts before invoking the macro ~ts.~ts/~B",
|
||||
[Module, Module, Name, Arity]);
|
||||
|
||||
format_error({ macro_conflict, { Receiver, Name, Arity } }) ->
|
||||
io_lib:format("call to local macro ~ts/~B conflicts with imported ~ts.~ts/~B, "
|
||||
"please rename the local macro or remove the conflicting import",
|
||||
[Name, Arity, elixir_errors:inspect(Receiver), Name, Arity]);
|
||||
|
||||
[Name, Arity, elixir_aliases:inspect(Receiver), Name, Arity]);
|
||||
format_error({ ambiguous_call, { Mod1, Mod2, Name, Arity }}) ->
|
||||
io_lib:format("function ~ts/~B imported from both ~ts and ~ts, call is ambiguous",
|
||||
[Name, Arity, elixir_errors:inspect(Mod1), elixir_errors:inspect(Mod2)]).
|
||||
[Name, Arity, elixir_aliases:inspect(Mod1), elixir_aliases:inspect(Mod2)]).
|
||||
|
||||
%% INTROSPECTION
|
||||
|
||||
@@ -373,144 +310,145 @@ get_optional_macros(Receiver) ->
|
||||
|
||||
elixir_imported_functions() ->
|
||||
try
|
||||
?builtin:'__info__'(functions)
|
||||
catch
|
||||
error:undef -> in_erlang_functions()
|
||||
end.
|
||||
|
||||
elixir_imported_macros() ->
|
||||
try
|
||||
?builtin:'__info__'(macros)
|
||||
catch
|
||||
error:undef -> in_erlang_macros()
|
||||
end.
|
||||
|
||||
%% Macros imported from Kernel module. Sorted on compilation.
|
||||
|
||||
in_elixir_macros() ->
|
||||
try
|
||||
?builtin:'__info__'(macros)
|
||||
?kernel:'__info__'(functions)
|
||||
catch
|
||||
error:undef -> []
|
||||
end.
|
||||
|
||||
%% Functions imported from Erlang module. MUST BE SORTED.
|
||||
in_erlang_functions() ->
|
||||
[
|
||||
{ abs, 1 },
|
||||
{ apply, 2 },
|
||||
{ apply, 3 },
|
||||
{ atom_to_list, 1 },
|
||||
{ binary_part, 3 },
|
||||
{ binary_to_float, 1 },
|
||||
{ binary_to_float, 2 },
|
||||
{ binary_to_integer, 1 },
|
||||
{ binary_to_integer, 2 },
|
||||
{ binary_to_term, 1 },
|
||||
{ binary_to_term, 2 },
|
||||
{ bit_size, 1 },
|
||||
{ bitstring_to_list, 1 },
|
||||
{ byte_size, 1 },
|
||||
% { date, 0 },
|
||||
{ exit, 1 },
|
||||
% { float, 1 },
|
||||
{ float_to_binary, 1 },
|
||||
{ float_to_list, 1 },
|
||||
{ hd, 1 },
|
||||
{ integer_to_binary, 1 },
|
||||
{ integer_to_binary, 2 },
|
||||
{ integer_to_list, 1 },
|
||||
{ integer_to_list, 2 },
|
||||
{ iolist_size, 1 },
|
||||
{ iolist_to_binary, 1 },
|
||||
{ is_alive, 0 },
|
||||
{ is_atom, 1 },
|
||||
{ is_binary, 1 },
|
||||
{ is_bitstring, 1 },
|
||||
{ is_boolean, 1 },
|
||||
{ is_float, 1 },
|
||||
{ is_function, 1 },
|
||||
{ is_function, 2 },
|
||||
{ is_integer, 1 },
|
||||
{ is_list, 1 },
|
||||
{ is_number, 1 },
|
||||
{ is_pid, 1 },
|
||||
{ is_port, 1 },
|
||||
{ is_reference, 1 },
|
||||
{ is_tuple, 1 },
|
||||
{ length, 1 },
|
||||
{ list_to_atom, 1 },
|
||||
{ list_to_bitstring, 1 },
|
||||
{ list_to_existing_atom, 1 },
|
||||
{ list_to_float, 1 },
|
||||
{ list_to_integer, 1 },
|
||||
{ list_to_integer, 2 },
|
||||
{ list_to_tuple, 1 },
|
||||
{ make_ref, 0 },
|
||||
{ max, 2 },
|
||||
{ min, 2 },
|
||||
{ node, 0 },
|
||||
{ node, 1 },
|
||||
% { now, 0 },
|
||||
{ round, 1 },
|
||||
{ self, 0 },
|
||||
{ size, 1 },
|
||||
{ spawn, 1 },
|
||||
{ spawn, 3 },
|
||||
{ spawn_link, 1 },
|
||||
{ spawn_link, 3 },
|
||||
% { split_binary, 2 },
|
||||
{ term_to_binary, 1 },
|
||||
{ term_to_binary, 2 },
|
||||
{ throw, 1 },
|
||||
% { time, 0 },
|
||||
{ tl, 1 },
|
||||
{ trunc, 1 },
|
||||
{ tuple_size, 1 },
|
||||
{ tuple_to_list, 1 }
|
||||
].
|
||||
elixir_imported_macros() ->
|
||||
try
|
||||
?kernel:'__info__'(macros)
|
||||
catch
|
||||
error:undef -> []
|
||||
end.
|
||||
|
||||
%% Macros implemented in Erlang. MUST BE SORTED.
|
||||
in_erlang_macros() ->
|
||||
[
|
||||
{'!',1},
|
||||
{'!=',2},
|
||||
{'!==',2},
|
||||
{'*',2},
|
||||
{'+',1},
|
||||
{'+',2},
|
||||
{'++',2},
|
||||
{'-',1},
|
||||
{'-',2},
|
||||
{'--',2},
|
||||
{'/',2},
|
||||
{'<',2},
|
||||
{'<-',2},
|
||||
{'<=',2},
|
||||
{'==',2},
|
||||
{'===',2},
|
||||
{'>',2},
|
||||
{'>=',2},
|
||||
{'@',1},
|
||||
{'and',2},
|
||||
{'case',2},
|
||||
{def,1},
|
||||
{def,2},
|
||||
{def,4},
|
||||
{defmacro,1},
|
||||
{defmacro,2},
|
||||
{defmacro,4},
|
||||
{defmacrop,1},
|
||||
{defmacrop,2},
|
||||
{defmacrop,4},
|
||||
{defmodule,2},
|
||||
{defp,1},
|
||||
{defp,2},
|
||||
{defp,4},
|
||||
{in,2},
|
||||
{'not',1},
|
||||
{'or',2},
|
||||
{'receive',1},
|
||||
{'try',1},
|
||||
{'xor',2}
|
||||
].
|
||||
rewrite(?kernel, atom_to_binary, [Arg], 1) ->
|
||||
{ ok, erlang, atom_to_binary, [Arg, utf8] };
|
||||
rewrite(?kernel, binary_to_atom, [Arg], 1) ->
|
||||
{ ok, erlang, binary_to_atom, [Arg, utf8] };
|
||||
rewrite(?kernel, binary_to_existing_atom, [Arg], 1) ->
|
||||
{ ok, erlang, binary_to_existing_atom, [Arg, utf8] };
|
||||
rewrite(?kernel, elem, [Tuple, Index], 2) ->
|
||||
{ ok, erlang, element, [increment(Index), Tuple] };
|
||||
rewrite(?kernel, set_elem, [Tuple, Index, Value], 2) ->
|
||||
{ ok, erlang, setelement, [increment(Index), Tuple, Value] };
|
||||
rewrite(?process, monitor, [Arg], 1) ->
|
||||
{ ok, erlang, monitor, [process, Arg] };
|
||||
rewrite(Receiver, Name, Args, Arity) ->
|
||||
case inline(Receiver, Name, Arity) of
|
||||
{ AR, AN } -> { ok, AR, AN, Args };
|
||||
false -> false
|
||||
end.
|
||||
|
||||
increment(Number) when is_number(Number) ->
|
||||
Number + 1;
|
||||
increment(Other) ->
|
||||
{ { '.', [], [erlang, '+'] }, [], [Other, 1] }.
|
||||
|
||||
inline(?kernel, '+', 2) -> { erlang, '+' };
|
||||
inline(?kernel, '-', 2) -> { erlang, '-' };
|
||||
inline(?kernel, '+', 1) -> { erlang, '+' };
|
||||
inline(?kernel, '-', 1) -> { erlang, '-' };
|
||||
inline(?kernel, '*', 2) -> { erlang, '*' };
|
||||
inline(?kernel, '/', 2) -> { erlang, '/' };
|
||||
inline(?kernel, '++', 2) -> { erlang, '++' };
|
||||
inline(?kernel, '--', 2) -> { erlang, '--' };
|
||||
inline(?kernel, 'xor', 2) -> { erlang, 'xor' };
|
||||
inline(?kernel, 'not', 1) -> { erlang, 'not' };
|
||||
inline(?kernel, '<', 2) -> { erlang, '<' };
|
||||
inline(?kernel, '>', 2) -> { erlang, '>' };
|
||||
inline(?kernel, '<=', 2) -> { erlang, '=<' };
|
||||
inline(?kernel, '>=', 2) -> { erlang, '>=' };
|
||||
inline(?kernel, '==', 2) -> { erlang, '==' };
|
||||
inline(?kernel, '!=', 2) -> { erlang, '/=' };
|
||||
inline(?kernel, '===', 2) -> { erlang, '=:=' };
|
||||
inline(?kernel, '!==', 2) -> { erlang, '=/=' };
|
||||
inline(?kernel, abs, 1) -> { erlang, abs };
|
||||
inline(?kernel, apply, 2) -> { erlang, apply };
|
||||
inline(?kernel, apply, 3) -> { erlang, apply };
|
||||
inline(?kernel, atom_to_list, 1) -> { erlang, atom_to_list };
|
||||
inline(?kernel, binary_part, 3) -> { erlang, binary_part };
|
||||
inline(?kernel, binary_to_float, 1) -> { erlang, binary_to_float };
|
||||
inline(?kernel, binary_to_float, 2) -> { erlang, binary_to_float };
|
||||
inline(?kernel, binary_to_integer, 1) -> { erlang, binary_to_integer };
|
||||
inline(?kernel, binary_to_integer, 2) -> { erlang, binary_to_integer };
|
||||
inline(?kernel, bit_size, 1) -> { erlang, bit_size };
|
||||
inline(?kernel, bitstring_to_list, 1) -> { erlang, bitstring_to_list };
|
||||
inline(?kernel, byte_size, 1) -> { erlang, byte_size };
|
||||
inline(?kernel, 'div', 2) -> { erlang, 'div' };
|
||||
inline(?kernel, exit, 1) -> { erlang, exit };
|
||||
inline(?kernel, float_to_binary, 1) -> { erlang, float_to_binary };
|
||||
inline(?kernel, float_to_list, 1) -> { erlang, float_to_list };
|
||||
inline(?kernel, hd, 1) -> { erlang, hd };
|
||||
inline(?kernel, integer_to_binary, 1) -> { erlang, integer_to_binary };
|
||||
inline(?kernel, integer_to_binary, 2) -> { erlang, integer_to_binary };
|
||||
inline(?kernel, integer_to_list, 1) -> { erlang, integer_to_list };
|
||||
inline(?kernel, integer_to_list, 2) -> { erlang, integer_to_list };
|
||||
inline(?kernel, iolist_size, 1) -> { erlang, iolist_size };
|
||||
inline(?kernel, iolist_to_binary, 1) -> { erlang, iolist_to_binary };
|
||||
inline(?kernel, is_atom, 1) -> { erlang, is_atom };
|
||||
inline(?kernel, is_binary, 1) -> { erlang, is_binary };
|
||||
inline(?kernel, is_bitstring, 1) -> { erlang, is_bitstring };
|
||||
inline(?kernel, is_boolean, 1) -> { erlang, is_boolean };
|
||||
inline(?kernel, is_float, 1) -> { erlang, is_float };
|
||||
inline(?kernel, is_function, 1) -> { erlang, is_function };
|
||||
inline(?kernel, is_function, 2) -> { erlang, is_function };
|
||||
inline(?kernel, is_integer, 1) -> { erlang, is_integer };
|
||||
inline(?kernel, is_list, 1) -> { erlang, is_list };
|
||||
inline(?kernel, is_number, 1) -> { erlang, is_number };
|
||||
inline(?kernel, is_pid, 1) -> { erlang, is_pid };
|
||||
inline(?kernel, is_port, 1) -> { erlang, is_port };
|
||||
inline(?kernel, is_reference, 1) -> { erlang, is_reference };
|
||||
inline(?kernel, is_tuple, 1) -> { erlang, is_tuple };
|
||||
inline(?kernel, length, 1) -> { erlang, length };
|
||||
inline(?kernel, list_to_atom, 1) -> { erlang, list_to_atom };
|
||||
inline(?kernel, list_to_bitstring, 1) -> { erlang, list_to_bitstring };
|
||||
inline(?kernel, list_to_existing_atom, 1) -> { erlang, list_to_existing_atom };
|
||||
inline(?kernel, list_to_float, 1) -> { erlang, list_to_float };
|
||||
inline(?kernel, list_to_integer, 1) -> { erlang, list_to_integer };
|
||||
inline(?kernel, list_to_integer, 2) -> { erlang, list_to_integer };
|
||||
inline(?kernel, list_to_tuple, 1) -> { erlang, list_to_tuple };
|
||||
inline(?kernel, make_ref, 0) -> { erlang, make_ref };
|
||||
inline(?kernel, max, 2) -> { erlang, max };
|
||||
inline(?kernel, min, 2) -> { erlang, min };
|
||||
inline(?kernel, node, 0) -> { erlang, node };
|
||||
inline(?kernel, node, 1) -> { erlang, node };
|
||||
inline(?kernel, 'rem', 2) -> { erlang, 'rem' };
|
||||
inline(?kernel, round, 1) -> { erlang, round };
|
||||
inline(?kernel, self, 0) -> { erlang, self };
|
||||
inline(?kernel, send, 2) -> { erlang, send };
|
||||
inline(?kernel, size, 1) -> { erlang, size };
|
||||
inline(?kernel, spawn, 1) -> { erlang, spawn };
|
||||
inline(?kernel, spawn, 3) -> { erlang, spawn };
|
||||
inline(?kernel, spawn_link, 1) -> { erlang, spawn_link };
|
||||
inline(?kernel, spawn_link, 3) -> { erlang, spawn_link };
|
||||
inline(?kernel, throw, 1) -> { erlang, throw };
|
||||
inline(?kernel, tl, 1) -> { erlang, tl };
|
||||
inline(?kernel, trunc, 1) -> { erlang, trunc };
|
||||
inline(?kernel, tuple_size, 1) -> { erlang, tuple_size };
|
||||
inline(?kernel, tuple_to_list, 1) -> { erlang, tuple_to_list };
|
||||
|
||||
inline(?process, exit, 2) -> { erlang, exit };
|
||||
inline(?process, spawn, 1) -> { erlang, spawn };
|
||||
inline(?process, spawn, 2) -> { erlang, spawn_opt };
|
||||
inline(?process, spawn, 3) -> { erlang, spawn };
|
||||
inline(?process, spawn, 4) -> { erlang, spawn_opt };
|
||||
inline(?process, spawn_link, 1) -> { erlang, spawn_link };
|
||||
inline(?process, spawn_link, 3) -> { erlang, spawn_link };
|
||||
inline(?process, spawn_monitor, 1) -> { erlang, spawn_monitor };
|
||||
inline(?process, spawn_monitor, 3) -> { erlang, spawn_monitor };
|
||||
inline(?process, demonitor, 1) -> { erlang, demonitor };
|
||||
inline(?process, demonitor, 2) -> { erlang, demonitor };
|
||||
inline(?process, link, 1) -> { erlang, link };
|
||||
inline(?process, unlink, 1) -> { erlang, unlink };
|
||||
|
||||
inline(?node, spawn, 2) -> { erlang, spawn };
|
||||
inline(?node, spawn, 3) -> { erlang, spawn_opt };
|
||||
inline(?node, spawn, 4) -> { erlang, spawn };
|
||||
inline(?node, spawn, 5) -> { erlang, spawn_opt };
|
||||
inline(?node, spawn_link, 2) -> { erlang, spawn_link };
|
||||
inline(?node, spawn_link, 4) -> { erlang, spawn_link };
|
||||
inline(?node, spawn_monitor, 2) -> { erlang, spawn_monitor };
|
||||
inline(?node, spawn_monitor, 4) -> { erlang, spawn_monitor };
|
||||
|
||||
inline(_, _, _) -> false.
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
-module(elixir_env).
|
||||
-include("elixir.hrl").
|
||||
-export([ex_to_env/1, env_to_scope/1, env_to_scope_with_vars/2, env_to_ex/1]).
|
||||
-export([mergea/2, mergev/2, merge_vars/2, merge_opt_vars/2]).
|
||||
|
||||
%% Conversion in between #elixir_env, #elixir_scope and Macro.Env
|
||||
|
||||
env_to_ex({ Line, #elixir_env{} = Env }) ->
|
||||
erlang:setelement(1, Env#elixir_env{line=Line}, 'Elixir.Macro.Env').
|
||||
|
||||
ex_to_env(Env) when element(1, Env) == 'Elixir.Macro.Env' ->
|
||||
erlang:setelement(1, Env, elixir_env).
|
||||
|
||||
env_to_scope(#elixir_env{module=Module,file=File,function=Function,context=Context}) ->
|
||||
#elixir_scope{module=Module,file=File,function=Function,context=Context}.
|
||||
|
||||
env_to_scope_with_vars(#elixir_env{} = Env, Vars) ->
|
||||
(env_to_scope(Env))#elixir_scope{
|
||||
vars=orddict:from_list(Vars),
|
||||
counter=[{'_',length(Vars)}]
|
||||
}.
|
||||
|
||||
%% SCOPE MERGING
|
||||
|
||||
%% Receives two scopes and return a new scope based on the second
|
||||
%% with their variables merged.
|
||||
mergev(E1, E2) when is_list(E1) ->
|
||||
E2#elixir_env{
|
||||
vars=merge_vars(E1, E2#elixir_env.vars),
|
||||
export_vars=merge_opt_vars(E1, E2#elixir_env.export_vars)
|
||||
};
|
||||
mergev(E1, E2) ->
|
||||
E2#elixir_env{
|
||||
vars=merge_vars(E1#elixir_env.vars, E2#elixir_env.vars),
|
||||
export_vars=merge_opt_vars(E1#elixir_env.export_vars, E2#elixir_env.export_vars)
|
||||
}.
|
||||
|
||||
%% Receives two scopes and return the later scope
|
||||
%% keeping the variables from the first (counters,
|
||||
%% imports and everything else are passed forward).
|
||||
|
||||
mergea(E1, E2) ->
|
||||
E2#elixir_env{vars=E1#elixir_env.vars}.
|
||||
|
||||
merge_vars(V1, V2) -> ordsets:union(V1, V2).
|
||||
|
||||
merge_opt_vars(_V1, nil) -> nil;
|
||||
merge_opt_vars(nil, _V2) -> nil;
|
||||
merge_opt_vars(V1, V2) -> ordsets:union(V1, V2).
|
||||
@@ -1,57 +1,43 @@
|
||||
% A bunch of helpers to help to deal with errors in Elixir source code.
|
||||
% This is not exposed in the Elixir language.
|
||||
-module(elixir_errors).
|
||||
-export([syntax_error/3, syntax_error/4,
|
||||
compile_error/3, compile_error/4, inspect/1,
|
||||
form_error/4, parse_error/4, assert_module_scope/3,
|
||||
assert_no_function_scope/3, assert_function_scope/3,
|
||||
assert_no_match_scope/3, assert_no_guard_scope/3,
|
||||
assert_no_match_or_guard_scope/3, warn/1,
|
||||
-export([compile_error/3, compile_error/4,
|
||||
form_error/4, parse_error/4, warn/2, warn/3,
|
||||
handle_file_warning/2, handle_file_warning/3, handle_file_error/2,
|
||||
deprecation/3, deprecation/4]).
|
||||
-include("elixir.hrl").
|
||||
|
||||
warn(Warning) ->
|
||||
CompilerPid = get(elixir_compiler_pid),
|
||||
|
||||
if
|
||||
CompilerPid =/= undefined ->
|
||||
elixir_code_server:cast({ register_warning, CompilerPid });
|
||||
true -> false
|
||||
end,
|
||||
|
||||
io:put_chars(standard_error, Warning).
|
||||
|
||||
%% Handle inspecting for exceptions modules
|
||||
warn(Caller, Warning) ->
|
||||
warn([Caller, "warning: ", Warning]).
|
||||
|
||||
inspect(Atom) when is_atom(Atom) ->
|
||||
'Elixir.Inspect.Atom':inspect(Atom).
|
||||
warn(Line, File, Warning) ->
|
||||
warn(file_format(Line, File, "warning: " ++ Warning)).
|
||||
|
||||
%% Raised during macros translation.
|
||||
%% Raised during expansion/translation/compilation.
|
||||
|
||||
-spec syntax_error(non_neg_integer() | list(), file:filename_all(), binary() | string()) -> no_return().
|
||||
-spec syntax_error(non_neg_integer() | list(), file:filename_all(), binary() | string(), list()) -> no_return().
|
||||
-spec form_error(list(), binary(), module(), any()) -> no_return().
|
||||
|
||||
syntax_error(Meta, File, Message) when is_list(Message) ->
|
||||
syntax_error(Meta, File, iolist_to_binary(Message));
|
||||
form_error(Meta, File, Module, Desc) ->
|
||||
compile_error(Meta, File, format_error(Module, Desc)).
|
||||
|
||||
syntax_error(Meta, File, Message) when is_binary(Message) ->
|
||||
raise(Meta, File, 'Elixir.SyntaxError', Message).
|
||||
|
||||
syntax_error(Meta, File, Format, Args) ->
|
||||
Message = io_lib:format(Format, Args),
|
||||
raise(Meta, File, 'Elixir.SyntaxError', elixir_utils:characters_to_binary(Message)).
|
||||
-spec compile_error(list(), binary(), iolist()) -> no_return().
|
||||
-spec compile_error(list(), binary(), iolist(), list()) -> no_return().
|
||||
|
||||
compile_error(Meta, File, Message) when is_list(Message) ->
|
||||
compile_error(Meta, File, iolist_to_binary(Message));
|
||||
|
||||
compile_error(Meta, File, Message) when is_binary(Message) ->
|
||||
raise(Meta, File, 'Elixir.CompileError', Message).
|
||||
|
||||
compile_error(Meta, File, Format, Args) ->
|
||||
Message = io_lib:format(Format, Args),
|
||||
raise(Meta, File, 'Elixir.CompileError', elixir_utils:characters_to_binary(Message)).
|
||||
|
||||
compile_error(Meta, File, Format, Args) when is_list(Format) ->
|
||||
compile_error(Meta, File, io_lib:format(Format, Args)).
|
||||
|
||||
%% Raised on tokenizing/parsing
|
||||
|
||||
-spec parse_error(non_neg_integer() | list(), file:filename_all(), iolist() | atom(), [] | iolist()) -> no_return().
|
||||
@@ -80,14 +66,6 @@ parse_error(Meta, File, Error, Token) ->
|
||||
Message = <<BinError / binary, BinToken / binary >>,
|
||||
raise(Meta, File, 'Elixir.SyntaxError', Message).
|
||||
|
||||
%% Raised during compilation
|
||||
|
||||
-spec form_error(non_neg_integer() | list(), file:filename_all(), module(), any()) -> no_return().
|
||||
|
||||
form_error(Meta, File, Module, Desc) ->
|
||||
Message = iolist_to_binary(format_error(Module, Desc)),
|
||||
raise(Meta, File, 'Elixir.CompileError', Message).
|
||||
|
||||
%% Shows a deprecation message
|
||||
|
||||
deprecation(Meta, File, Message) -> deprecation(Meta, File, Message, []).
|
||||
@@ -121,7 +99,7 @@ handle_file_warning(_, File, { Line, sys_core_fold, { no_effect, { erlang, F, A
|
||||
end
|
||||
end,
|
||||
Message = io_lib:format(Fmt, Args),
|
||||
warn(file_format(Line, File, Message));
|
||||
warn(Line, File, Message);
|
||||
|
||||
%% Rewrite undefined behaviour to check for protocols
|
||||
handle_file_warning(_, File, {Line,erl_lint,{undefined_behaviour_func,{Fun,Arity},Module}}) ->
|
||||
@@ -133,15 +111,15 @@ handle_file_warning(_, File, {Line,erl_lint,{undefined_behaviour_func,{Fun,Arity
|
||||
|
||||
Kind = protocol_or_behaviour(Module),
|
||||
Raw = "undefined ~ts ~ts ~ts/~B (for ~ts ~ts)",
|
||||
Message = io_lib:format(Raw, [Kind, DefKind, Def, DefArity, Kind, inspect(Module)]),
|
||||
warn(file_format(Line, File, Message));
|
||||
Message = io_lib:format(Raw, [Kind, DefKind, Def, DefArity, Kind, elixir_aliases:inspect(Module)]),
|
||||
warn(Line, File, Message);
|
||||
|
||||
handle_file_warning(_, File, {Line,erl_lint,{undefined_behaviour,Module}}) ->
|
||||
case elixir_compiler:get_opt(internal) of
|
||||
true -> [];
|
||||
false ->
|
||||
Message = io_lib:format("behaviour ~ts undefined", [inspect(Module)]),
|
||||
warn(file_format(Line, File, Message))
|
||||
Message = io_lib:format("behaviour ~ts undefined", [elixir_aliases:inspect(Module)]),
|
||||
warn(Line, File, Message)
|
||||
end;
|
||||
|
||||
%% Ignore unused vars at "weird" lines (<= 0)
|
||||
@@ -155,12 +133,12 @@ handle_file_warning(_, _File, {_Line,erl_lint,{shadowed_var,_Var,_Where}}) ->
|
||||
%% Properly format other unused vars
|
||||
handle_file_warning(_, File, {Line,erl_lint,{unused_var,Var}}) ->
|
||||
Message = format_error(erl_lint, { unused_var, format_var(Var) }),
|
||||
warn(file_format(Line, File, Message));
|
||||
warn(Line, File, Message);
|
||||
|
||||
%% Default behavior
|
||||
%% Default behaviour
|
||||
handle_file_warning(_, File, {Line,Module,Desc}) ->
|
||||
Message = format_error(Module, Desc),
|
||||
warn(file_format(Line, File, Message)).
|
||||
warn(Line, File, Message).
|
||||
|
||||
handle_file_warning(File, Desc) ->
|
||||
handle_file_warning(false, File, Desc).
|
||||
@@ -177,38 +155,12 @@ handle_file_error(File, {Line,erl_lint,{unsafe_var,Var,{In,_Where}}}) ->
|
||||
raise(Line, File, 'Elixir.CompileError', iolist_to_binary(Message));
|
||||
|
||||
handle_file_error(File, {Line,erl_lint,{spec_fun_undefined,{M,F,A}}}) ->
|
||||
Message = io_lib:format("spec for undefined function ~ts.~ts/~B", [inspect(M), F, A]),
|
||||
Message = io_lib:format("spec for undefined function ~ts.~ts/~B", [elixir_aliases:inspect(M), F, A]),
|
||||
raise(Line, File, 'Elixir.CompileError', iolist_to_binary(Message));
|
||||
|
||||
handle_file_error(File, {Line,Module,Desc}) ->
|
||||
form_error(Line, File, Module, Desc).
|
||||
|
||||
%% Assertions
|
||||
|
||||
assert_no_function_scope(_Meta, _Kind, #elixir_scope{function=nil}) -> [];
|
||||
assert_no_function_scope(Meta, Kind, S) ->
|
||||
syntax_error(Meta, S#elixir_scope.file, "cannot invoke ~ts inside a function", [Kind]).
|
||||
|
||||
assert_module_scope(Meta, Kind, #elixir_scope{module=nil,file=File}) ->
|
||||
syntax_error(Meta, File, "cannot invoke ~ts outside module", [Kind]);
|
||||
assert_module_scope(_Meta, _Kind, #elixir_scope{module=Module}) -> Module.
|
||||
|
||||
assert_function_scope(Meta, Kind, #elixir_scope{function=nil,file=File}) ->
|
||||
syntax_error(Meta, File, "cannot invoke ~ts outside function", [Kind]);
|
||||
assert_function_scope(_Meta, _Kind, #elixir_scope{function=Function}) -> Function.
|
||||
|
||||
assert_no_match_or_guard_scope(Meta, Kind, S) ->
|
||||
assert_no_match_scope(Meta, Kind, S),
|
||||
assert_no_guard_scope(Meta, Kind, S).
|
||||
|
||||
assert_no_match_scope(Meta, _Kind, #elixir_scope{context=match} = S) ->
|
||||
compile_error(Meta, S#elixir_scope.file, "invalid pattern in match clause", []);
|
||||
assert_no_match_scope(_Meta, _Kind, _S) -> [].
|
||||
|
||||
assert_no_guard_scope(Meta, _Kind, #elixir_scope{context=guard} = S) ->
|
||||
compile_error(Meta, S#elixir_scope.file, "invalid pattern in guard", []);
|
||||
assert_no_guard_scope(_Meta, _Kind, _S) -> [].
|
||||
|
||||
%% Helpers
|
||||
|
||||
raise(Meta, File, Kind, Message) when is_list(Meta) ->
|
||||
@@ -217,7 +169,7 @@ raise(Meta, File, Kind, Message) when is_list(Meta) ->
|
||||
raise(none, File, Kind, Message) ->
|
||||
raise(0, File, Kind, Message);
|
||||
|
||||
raise(Line, File, Kind, Message) when is_integer(Line) ->
|
||||
raise(Line, File, Kind, Message) when is_integer(Line), is_binary(File) ->
|
||||
%% Populate the stacktrace so we can raise it
|
||||
try
|
||||
throw(ok)
|
||||
@@ -225,13 +177,13 @@ raise(Line, File, Kind, Message) when is_integer(Line) ->
|
||||
ok -> ok
|
||||
end,
|
||||
Stacktrace = erlang:get_stacktrace(),
|
||||
Exception = Kind:new([{description, Message}, {file, iolist_to_binary(File)}, {line, Line}]),
|
||||
Exception = Kind:new([{description, Message}, {file, File}, {line, Line}]),
|
||||
erlang:raise(error, Exception, tl(Stacktrace)).
|
||||
|
||||
file_format(0, File, Message) ->
|
||||
file_format(0, File, Message) when is_binary(File) ->
|
||||
io_lib:format("~ts: ~ts~n", [elixir_utils:relative_to_cwd(File), Message]);
|
||||
|
||||
file_format(Line, File, Message) ->
|
||||
file_format(Line, File, Message) when is_binary(File) ->
|
||||
io_lib:format("~ts:~w: ~ts~n", [elixir_utils:relative_to_cwd(File), Line, Message]).
|
||||
|
||||
format_var(Var) ->
|
||||
|
||||
@@ -0,0 +1,556 @@
|
||||
-module(elixir_exp).
|
||||
-export([expand/2, expand_many/2, expand_args/2, expand_arg/2]).
|
||||
-import(elixir_errors, [compile_error/3, compile_error/4]).
|
||||
-include("elixir.hrl").
|
||||
|
||||
%% =
|
||||
|
||||
expand({ '=', Meta, [Left, Right] }, E) ->
|
||||
assert_no_guard_scope(Meta, '=', E),
|
||||
{ ERight, ER } = expand(Right, E),
|
||||
{ ELeft, EL } = elixir_exp_clauses:match(fun expand/2, Left, E),
|
||||
{ { '=', Meta, [ELeft, ERight] }, elixir_env:mergev(EL, ER) };
|
||||
|
||||
%% Literal operators
|
||||
|
||||
expand({ '{}', Meta, Args }, E) ->
|
||||
{ EArgs, EA } = expand_args(Args, E),
|
||||
{ { '{}', Meta, EArgs }, EA };
|
||||
|
||||
expand({ '<<>>', Meta, Args }, E) ->
|
||||
elixir_bitstring:expand(Meta, Args, E);
|
||||
|
||||
%% Other operators
|
||||
|
||||
expand({ '__op__', Meta, [_, _] = Args }, E) ->
|
||||
{ EArgs, EA } = expand_args(Args, E),
|
||||
{ { '__op__', Meta, EArgs }, EA };
|
||||
|
||||
expand({ '__op__', Meta, [_, _, _] = Args }, E) ->
|
||||
{ EArgs, EA } = expand_args(Args, E),
|
||||
{ { '__op__', Meta, EArgs }, EA };
|
||||
|
||||
expand({ '->', Meta, _Args }, E) ->
|
||||
compile_error(Meta, E#elixir_env.file, "unhandled operator ->");
|
||||
|
||||
%% __block__
|
||||
|
||||
expand({ '__block__', _Meta, [] }, E) ->
|
||||
{ nil, E };
|
||||
expand({ '__block__', _Meta, [Arg] }, E) ->
|
||||
expand(Arg, E);
|
||||
expand({ '__block__', Meta, Args }, E) when is_list(Args) ->
|
||||
{ EArgs, EA } = expand_many(Args, E),
|
||||
{ { '__block__', Meta, EArgs }, EA };
|
||||
|
||||
%% __aliases__
|
||||
|
||||
expand({ '__aliases__', _, _ } = Alias, E) ->
|
||||
case elixir_aliases:expand(Alias, E#elixir_env.aliases,
|
||||
E#elixir_env.macro_aliases, E#elixir_env.lexical_tracker) of
|
||||
Receiver when is_atom(Receiver) ->
|
||||
elixir_lexical:record_remote(Receiver, E#elixir_env.lexical_tracker),
|
||||
{ Receiver, E };
|
||||
Aliases ->
|
||||
{ EAliases, EA } = expand_args(Aliases, E),
|
||||
|
||||
case lists:all(fun is_atom/1, EAliases) of
|
||||
true ->
|
||||
Receiver = elixir_aliases:concat(EAliases),
|
||||
elixir_lexical:record_remote(Receiver, E#elixir_env.lexical_tracker),
|
||||
{ Receiver, EA };
|
||||
false ->
|
||||
{ { { '.', [], [elixir_aliases, concat] }, [], [EAliases] }, EA }
|
||||
end
|
||||
end;
|
||||
|
||||
%% alias
|
||||
|
||||
expand({ alias, Meta, [Ref] }, E) ->
|
||||
expand({ alias, Meta, [Ref,[]] }, E);
|
||||
expand({ alias, Meta, [Ref, KV] }, E) ->
|
||||
assert_no_match_or_guard_scope(Meta, alias, E),
|
||||
{ ERef, ER } = expand(Ref, E),
|
||||
{ EKV, ET } = expand_opts(Meta, alias, [as, warn], no_alias_opts(KV), ER),
|
||||
|
||||
if
|
||||
is_atom(ERef) ->
|
||||
{ { alias, Meta, [ERef, EKV] },
|
||||
expand_alias(Meta, true, ERef, EKV, ET) };
|
||||
true ->
|
||||
compile_error(Meta, E#elixir_env.file,
|
||||
"invalid arguments for alias, expected a compile time atom or alias as argument")
|
||||
end;
|
||||
|
||||
expand({ require, Meta, [Ref] }, E) ->
|
||||
expand({ require, Meta, [Ref, []] }, E);
|
||||
expand({ require, Meta, [Ref, KV] }, E) ->
|
||||
assert_no_match_or_guard_scope(Meta, require, E),
|
||||
|
||||
{ ERef, ER } = expand(Ref, E),
|
||||
{ EKV, ET } = expand_opts(Meta, require, [as, warn], no_alias_opts(KV), ER),
|
||||
|
||||
if
|
||||
is_atom(ERef) ->
|
||||
elixir_aliases:ensure_loaded(Meta, ERef, ET),
|
||||
{ { require, Meta, [ERef, EKV] },
|
||||
expand_require(Meta, ERef, EKV, ET) };
|
||||
true ->
|
||||
compile_error(Meta, E#elixir_env.file,
|
||||
"invalid arguments for require, expected a compile time atom or alias as argument")
|
||||
end;
|
||||
|
||||
expand({ import, Meta, [Left] }, E) ->
|
||||
expand({ import, Meta, [Left, []]}, E);
|
||||
|
||||
expand({ import, Meta, [Ref, KV] }, E) ->
|
||||
assert_no_match_or_guard_scope(Meta, import, E),
|
||||
{ ERef, ER } = expand(Ref, E),
|
||||
{ EKV, ET } = expand_opts(Meta, import, [only, except, warn], KV, ER),
|
||||
|
||||
if
|
||||
is_atom(ERef) ->
|
||||
elixir_aliases:ensure_loaded(Meta, ERef, ET),
|
||||
{ Functions, Macros } = elixir_import:import(Meta, ERef, EKV, ET),
|
||||
{ { import, Meta, [ERef, EKV] },
|
||||
expand_require(Meta, ERef, EKV, ET#elixir_env{functions=Functions, macros=Macros}) };
|
||||
true ->
|
||||
compile_error(Meta, E#elixir_env.file, "invalid name for import, expected a compile time atom or alias")
|
||||
end;
|
||||
|
||||
%% Pseudo vars
|
||||
|
||||
expand({ '__MODULE__', _, Atom }, E) when is_atom(Atom) ->
|
||||
{ E#elixir_env.module, E };
|
||||
expand({ '__DIR__', _, Atom }, E) when is_atom(Atom) ->
|
||||
{ filename:dirname(E#elixir_env.file), E };
|
||||
expand({ '__CALLER__', _, Atom } = Caller, E) when is_atom(Atom) ->
|
||||
{ Caller, E };
|
||||
expand({ '__ENV__', Meta, Atom }, E) when is_atom(Atom) ->
|
||||
Env = elixir_env:env_to_ex({ ?line(Meta), E }),
|
||||
{ { '{}', [], tuple_to_list(Env) }, E };
|
||||
expand({ { '.', DotMeta, [{ '__ENV__', Meta, Atom }, Field] }, CallMeta, [] }, E) when is_atom(Atom), is_atom(Field) ->
|
||||
Env = elixir_env:env_to_ex({ ?line(Meta), E }),
|
||||
case erlang:function_exported('Elixir.Macro.Env', Field, 1) of
|
||||
true -> { Env:Field(), E };
|
||||
false -> { { { '.', DotMeta, [{ '{}', [], tuple_to_list(Env) }, Field] }, CallMeta, [] }, E }
|
||||
end;
|
||||
|
||||
%% Quote
|
||||
|
||||
expand({ Unquote, Meta, [_] }, E) when Unquote == unquote; Unquote == unquote_splicing ->
|
||||
compile_error(Meta, E#elixir_env.file, "~p called outside quote", [Unquote]);
|
||||
|
||||
expand({ quote, Meta, [Opts] }, E) when is_list(Opts) ->
|
||||
case lists:keyfind(do, 1, Opts) of
|
||||
{ do, Do } ->
|
||||
expand({ quote, Meta, [lists:keydelete(do, 1, Opts), [{do,Do}]] }, E);
|
||||
false ->
|
||||
compile_error(Meta, E#elixir_env.file, "missing do keyword in quote")
|
||||
end;
|
||||
|
||||
expand({ quote, Meta, [_] }, E) ->
|
||||
compile_error(Meta, E#elixir_env.file, "invalid arguments for quote");
|
||||
|
||||
expand({ quote, Meta, [KV, Do] }, E) when is_list(Do) ->
|
||||
Exprs =
|
||||
case lists:keyfind(do, 1, Do) of
|
||||
{ do, Expr } -> Expr;
|
||||
false -> compile_error(Meta, E#elixir_scope.file, "missing do keyword in quote")
|
||||
end,
|
||||
|
||||
ValidOpts = [hygiene, context, var_context, location, line, unquote, bind_quoted],
|
||||
{ EKV, ET } = expand_opts(Meta, quote, ValidOpts, KV, E),
|
||||
|
||||
Hygiene = case lists:keyfind(hygiene, 1, EKV) of
|
||||
{ hygiene, List } when is_list(List) ->
|
||||
List;
|
||||
false ->
|
||||
[]
|
||||
end,
|
||||
|
||||
Context = case lists:keyfind(context, 1, EKV) of
|
||||
{ context, Atom } when is_atom(Atom) ->
|
||||
Atom;
|
||||
{ context, _ } ->
|
||||
compile_error(Meta, E#elixir_env.file, "invalid :context for quote, expected a compile time atom or an alias");
|
||||
false ->
|
||||
case E#elixir_env.module of
|
||||
nil -> 'Elixir';
|
||||
Mod -> Mod
|
||||
end
|
||||
end,
|
||||
|
||||
Vars = lists:keyfind(vars, 1, Hygiene) /= { vars, false },
|
||||
Aliases = lists:keyfind(aliases, 1, Hygiene) /= { aliases, false },
|
||||
Imports = lists:keyfind(imports, 1, Hygiene) /= { imports, false },
|
||||
|
||||
Keep = lists:keyfind(location, 1, EKV) == { location, keep },
|
||||
Line = proplists:get_value(line, EKV, false),
|
||||
|
||||
{ Binding, DefaultUnquote } = case lists:keyfind(bind_quoted, 1, EKV) of
|
||||
{ bind_quoted, BQ } -> { BQ, false };
|
||||
false -> { nil, true }
|
||||
end,
|
||||
|
||||
Unquote = case lists:keyfind(unquote, 1, EKV) of
|
||||
{ unquote, Bool } when is_boolean(Bool) -> Bool;
|
||||
false -> DefaultUnquote
|
||||
end,
|
||||
|
||||
Q = #elixir_quote{vars_hygiene=Vars, line=Line, keep=Keep, unquote=Unquote,
|
||||
aliases_hygiene=Aliases, imports_hygiene=Imports, context=Context},
|
||||
|
||||
{ Quoted, _Q } = elixir_quote:quote(Exprs, Binding, Q, ET),
|
||||
expand(Quoted, ET);
|
||||
|
||||
expand({ quote, Meta, [_, _] }, E) ->
|
||||
compile_error(Meta, E#elixir_env.file, "invalid arguments for quote");
|
||||
|
||||
%% Functions
|
||||
|
||||
expand({ '&', _, [Arg] } = Original, E) when is_integer(Arg) ->
|
||||
{ Original, E };
|
||||
expand({ '&', Meta, [Arg] }, E) ->
|
||||
assert_no_match_or_guard_scope(Meta, '&', E),
|
||||
case elixir_fn:capture(Meta, Arg, E) of
|
||||
{ local, Fun, Arity } ->
|
||||
{ { '&', Meta, [{ '/', [], [{ Fun, [], nil }, Arity] }] }, E };
|
||||
{ expanded, Expr, EE } ->
|
||||
expand(Expr, EE)
|
||||
end;
|
||||
|
||||
expand({ fn, Meta, Pairs }, E) ->
|
||||
assert_no_match_or_guard_scope(Meta, fn, E),
|
||||
elixir_fn:expand(Meta, Pairs, E);
|
||||
|
||||
%% Case/Receive/Try
|
||||
|
||||
expand({'case', Meta, [Expr, KV]}, E) ->
|
||||
assert_no_match_or_guard_scope(Meta, 'case', E),
|
||||
{ EExpr, EE } = expand(Expr, E),
|
||||
{ EClauses, EC } = elixir_exp_clauses:'case'(Meta, KV, EE),
|
||||
{ { 'case', Meta, [EExpr, EClauses] }, EC };
|
||||
|
||||
expand({'receive', Meta, [KV]}, E) ->
|
||||
assert_no_match_or_guard_scope(Meta, 'receive', E),
|
||||
{ EClauses, EC } = elixir_exp_clauses:'receive'(Meta, KV, E),
|
||||
{ { 'receive', Meta, [EClauses] }, EC };
|
||||
|
||||
expand({'try', Meta, [KV]}, E) ->
|
||||
assert_no_match_or_guard_scope(Meta, 'try', E),
|
||||
{ EClauses, EC } = elixir_exp_clauses:'try'(Meta, KV, E),
|
||||
{ { 'try', Meta, [EClauses] }, EC };
|
||||
|
||||
%% Comprehensions
|
||||
|
||||
expand({ Kind, Meta, Args }, E) when is_list(Args), (Kind == lc) orelse (Kind == bc) ->
|
||||
expand_comprehension(Meta, Kind, Args, E);
|
||||
|
||||
%% Super
|
||||
|
||||
expand({ super, Meta, Args }, E) when is_list(Args) ->
|
||||
assert_no_match_or_guard_scope(Meta, super, E),
|
||||
{ EArgs, EA } = expand_args(Args, E),
|
||||
{ { super, Meta, EArgs }, EA };
|
||||
|
||||
%% Vars
|
||||
|
||||
expand({ '^', Meta, [Arg] }, #elixir_env{context=match} = E) ->
|
||||
case expand(Arg, E) of
|
||||
{ { Name, _, Kind } = EArg, EA } when is_atom(Name), is_atom(Kind) ->
|
||||
{ { '^', Meta, [EArg] }, EA };
|
||||
_ ->
|
||||
Msg = "invalid argument for unary operator ^, expected an existing variable, got: ^~ts",
|
||||
compile_error(Meta, E#elixir_env.file, Msg, ['Elixir.Macro':to_string(Arg)])
|
||||
end;
|
||||
expand({ '^', Meta, [Arg] }, E) ->
|
||||
compile_error(Meta, E#elixir_env.file,
|
||||
"cannot use ^~ts outside of match clauses", ['Elixir.Macro':to_string(Arg)]);
|
||||
|
||||
expand({ '_', _, Kind } = Var, E) when is_atom(Kind) ->
|
||||
{ Var, E };
|
||||
expand({ Name, Meta, Kind } = Var, #elixir_env{context=match, export_vars=Export} = E) when is_atom(Name), is_atom(Kind) ->
|
||||
Pair = { Name, var_kind(Meta, Kind) },
|
||||
NewVars = ordsets:add_element(Pair, E#elixir_env.vars),
|
||||
NewExport = case (Export /= nil) andalso (lists:keyfind(export, 1, Meta) /= { export, false }) of
|
||||
true -> ordsets:add_element(Pair, Export);
|
||||
false -> Export
|
||||
end,
|
||||
{ Var, E#elixir_env{vars=NewVars, export_vars=NewExport} };
|
||||
expand({ Name, Meta, Kind } = Var, #elixir_env{vars=Vars} = E) when is_atom(Name), is_atom(Kind) ->
|
||||
case lists:member({ Name, var_kind(Meta, Kind) }, Vars) of
|
||||
true ->
|
||||
{ Var, E };
|
||||
false ->
|
||||
VarMeta = lists:keyfind(var, 1, Meta),
|
||||
if
|
||||
VarMeta == { var, true } ->
|
||||
Extra = case Kind of
|
||||
nil -> "";
|
||||
_ -> io_lib:format(" (context ~ts)", [elixir_aliases:inspect(Kind)])
|
||||
end,
|
||||
|
||||
compile_error(Meta, E#elixir_env.file, "expected var ~ts~ts to expand to an existing "
|
||||
"variable or be a part of a match", [Name, Extra]);
|
||||
true ->
|
||||
expand({ Name, Meta, [] }, E)
|
||||
end
|
||||
end;
|
||||
|
||||
%% Local calls
|
||||
|
||||
expand({ Atom, Meta, Args }, E) when is_atom(Atom), is_list(Meta), is_list(Args) ->
|
||||
assert_no_ambiguous_op(Atom, Meta, Args, E),
|
||||
|
||||
elixir_dispatch:dispatch_import(Meta, Atom, Args, E, fun() ->
|
||||
expand_local(Meta, Atom, Args, E)
|
||||
end);
|
||||
|
||||
%% Remote calls
|
||||
|
||||
expand({ { '.', DotMeta, [Left, Right] }, Meta, Args }, E)
|
||||
when (is_tuple(Left) orelse is_atom(Left)), is_atom(Right), is_list(Meta), is_list(Args) ->
|
||||
{ ELeft, EL } = expand(Left, E),
|
||||
|
||||
elixir_dispatch:dispatch_require(Meta, ELeft, Right, Args, EL, fun(AR, AF, AA) ->
|
||||
expand_remote(AR, DotMeta, AF, Meta, AA, E, EL)
|
||||
end);
|
||||
|
||||
%% Anonymous calls
|
||||
|
||||
expand({ { '.', DotMeta, [Expr] }, Meta, Args }, E) when is_list(Args) ->
|
||||
{ EExpr, EE } = expand(Expr, E),
|
||||
if
|
||||
is_atom(EExpr) ->
|
||||
compile_error(Meta, E#elixir_env.file, "invalid function call :~ts.()", [EExpr]);
|
||||
true ->
|
||||
{ EArgs, EA } = expand_args(Args, elixir_env:mergea(E, EE)),
|
||||
{ { { '.', DotMeta, [EExpr] }, Meta, EArgs }, elixir_env:mergev(EE, EA) }
|
||||
end;
|
||||
|
||||
%% Invalid calls
|
||||
|
||||
expand({ _, Meta, Args } = Invalid, E) when is_list(Meta) and is_list(Args) ->
|
||||
compile_error(Meta, E#elixir_env.file, "invalid call ~ts",
|
||||
['Elixir.Macro':to_string(Invalid)]);
|
||||
|
||||
expand({ _, _, _ } = Tuple, E) ->
|
||||
compile_error([{line,0}], E#elixir_env.file, "invalid quoted expression: ~ts",
|
||||
['Elixir.Kernel':inspect(Tuple, [{records,false}])]);
|
||||
|
||||
%% Literals
|
||||
|
||||
expand({ Left, Right }, E) ->
|
||||
{ [ELeft, ERight], EE } = expand_args([Left, Right], E),
|
||||
{ { ELeft, ERight }, EE };
|
||||
|
||||
expand(List, #elixir_env{context=match} = E) when is_list(List) ->
|
||||
expand_list(List, fun expand/2, E, []);
|
||||
|
||||
expand(List, E) when is_list(List) ->
|
||||
{ EArgs, { EC, EV } } = expand_list(List, fun expand_arg/2, {E, E}, []),
|
||||
{ EArgs, elixir_env:mergea(EV, EC) };
|
||||
|
||||
expand(Function, E) when is_function(Function) ->
|
||||
case (erlang:fun_info(Function, type) == { type, external }) andalso
|
||||
(erlang:fun_info(Function, env) == { env, [] }) of
|
||||
true ->
|
||||
{ Function, E };
|
||||
false ->
|
||||
compile_error([{line,0}], E#elixir_env.file,
|
||||
"invalid quoted expression: ~ts", ['Elixir.Kernel':inspect(Function)])
|
||||
end;
|
||||
|
||||
expand(Other, E) when is_number(Other); is_atom(Other); is_binary(Other); is_pid(Other) ->
|
||||
{ Other, E };
|
||||
|
||||
expand(Other, E) ->
|
||||
compile_error([{line,0}], E#elixir_env.file,
|
||||
"invalid quoted expression: ~ts", ['Elixir.Kernel':inspect(Other)]).
|
||||
|
||||
%% Helpers
|
||||
|
||||
expand_list([{ '|', Meta, [_, _] = Args }], Fun, Acc, List) ->
|
||||
{ EArgs, EAcc } = lists:mapfoldl(Fun, Acc, Args),
|
||||
expand_list([], Fun, EAcc, [{ '|', Meta, EArgs }|List]);
|
||||
expand_list([H|T], Fun, Acc, List) ->
|
||||
{ EArg, EAcc } = Fun(H, Acc),
|
||||
expand_list(T, Fun, EAcc, [EArg|List]);
|
||||
expand_list([], _Fun, Acc, List) ->
|
||||
{ lists:reverse(List), Acc }.
|
||||
|
||||
expand_many(Args, E) ->
|
||||
lists:mapfoldl(fun expand/2, E, Args).
|
||||
|
||||
%% Variables in arguments are not propagated from one
|
||||
%% argument to the other. For instance:
|
||||
%%
|
||||
%% x = 1
|
||||
%% foo(x = x + 2, x)
|
||||
%% x
|
||||
%%
|
||||
%% Should be the same as:
|
||||
%%
|
||||
%% foo(3, 1)
|
||||
%% 3
|
||||
%%
|
||||
%% However, lexical information is.
|
||||
expand_arg(Arg, Acc) when is_number(Arg); is_atom(Arg); is_binary(Arg); is_pid(Arg) ->
|
||||
{ Arg, Acc };
|
||||
expand_arg(Arg, { Acc1, Acc2 }) ->
|
||||
{ EArg, EAcc } = expand(Arg, Acc1),
|
||||
{ EArg, { elixir_env:mergea(Acc1, EAcc), elixir_env:mergev(Acc2, EAcc) } }.
|
||||
|
||||
expand_args(Args, #elixir_env{context=match} = E) ->
|
||||
lists:mapfoldl(fun expand/2, E, Args);
|
||||
expand_args(Args, E) ->
|
||||
{ EArgs, { EC, EV } } = lists:mapfoldl(fun expand_arg/2, {E, E}, Args),
|
||||
{ EArgs, elixir_env:mergea(EV, EC) }.
|
||||
|
||||
%% Match/var helpers
|
||||
|
||||
var_kind(Meta, Kind) ->
|
||||
case lists:keyfind(counter, 1, Meta) of
|
||||
{ counter, Counter } -> Counter;
|
||||
false -> Kind
|
||||
end.
|
||||
|
||||
%% Locals
|
||||
|
||||
assert_no_ambiguous_op(Name, Meta, [Arg], E) ->
|
||||
case lists:keyfind(ambiguous_op, 1, Meta) of
|
||||
{ ambiguous_op, Kind } ->
|
||||
case lists:member({ Name, Kind }, E#elixir_env.vars) of
|
||||
true ->
|
||||
compile_error(Meta, E#elixir_env.file, "\"~ts ~ts\" looks like a function call but "
|
||||
"there is a variable named \"~ts\", please use explicit parenthesis or even spaces",
|
||||
[Name, 'Elixir.Macro':to_string(Arg), Name]);
|
||||
false ->
|
||||
ok
|
||||
end;
|
||||
_ ->
|
||||
ok
|
||||
end;
|
||||
assert_no_ambiguous_op(_Atom, _Meta, _Args, _E) ->
|
||||
ok.
|
||||
|
||||
expand_local(Meta, Name, Args, #elixir_env{local=nil, function=nil} = E) ->
|
||||
{ EArgs, EA } = expand_args(Args, E),
|
||||
{ { Name, Meta, EArgs }, EA };
|
||||
expand_local(Meta, Name, Args, #elixir_env{local=nil, module=Module, function=Function} = E) ->
|
||||
elixir_locals:record_local({ Name, length(Args) }, Module, Function),
|
||||
{ EArgs, EA } = expand_args(Args, E),
|
||||
{ { Name, Meta, EArgs }, EA };
|
||||
expand_local(Meta, Name, Args, E) ->
|
||||
expand({ { '.', Meta, [E#elixir_env.local, Name] }, Meta, Args }, E).
|
||||
|
||||
%% Remote
|
||||
|
||||
expand_remote(Receiver, DotMeta, Right, Meta, Args, E, EL) ->
|
||||
if
|
||||
is_atom(Receiver) -> elixir_lexical:record_remote(Receiver, E#elixir_env.lexical_tracker);
|
||||
true -> ok
|
||||
end,
|
||||
{ EArgs, EA } = expand_args(Args, E),
|
||||
{ { { '.', DotMeta, [Receiver, Right] }, Meta, EArgs }, elixir_env:mergev(EL, EA) }.
|
||||
|
||||
%% Lexical helpers
|
||||
|
||||
expand_opts(Meta, Kind, Allowed, Opts, E) ->
|
||||
{ EOpts, EE } = expand(Opts, E),
|
||||
validate_opts(Meta, Kind, Allowed, EOpts, EE),
|
||||
{ EOpts, EE }.
|
||||
|
||||
validate_opts(Meta, Kind, Allowed, Opts, E) when is_list(Opts) ->
|
||||
[begin
|
||||
compile_error(Meta, E#elixir_env.file,
|
||||
"unsupported option ~ts given to ~s", ['Elixir.Kernel':inspect(Key), Kind])
|
||||
end || { Key, _ } <- Opts, not lists:member(Key, Allowed)];
|
||||
|
||||
validate_opts(Meta, Kind, _Allowed, _Opts, S) ->
|
||||
compile_error(Meta, S#elixir_scope.file, "invalid options for ~s, expected a keyword list", [Kind]).
|
||||
|
||||
no_alias_opts(KV) when is_list(KV) ->
|
||||
case lists:keyfind(as, 1, KV) of
|
||||
{ as, As } -> lists:keystore(as, 1, KV, { as, no_alias_expansion(As) });
|
||||
false -> KV
|
||||
end;
|
||||
no_alias_opts(KV) -> KV.
|
||||
|
||||
no_alias_expansion({ '__aliases__', Meta, [H|T] }) when (H /= 'Elixir') and is_atom(H) ->
|
||||
{ '__aliases__', Meta, ['Elixir',H|T] };
|
||||
no_alias_expansion(Other) ->
|
||||
Other.
|
||||
|
||||
expand_require(Meta, Ref, KV, E) ->
|
||||
RE = E#elixir_env{requires=ordsets:add_element(Ref, E#elixir_env.requires)},
|
||||
expand_alias(Meta, false, Ref, KV, RE).
|
||||
|
||||
expand_alias(Meta, IncludeByDefault, Ref, KV, #elixir_env{context_modules=Context} = E) ->
|
||||
New = expand_as(lists:keyfind(as, 1, KV), Meta, IncludeByDefault, Ref, E),
|
||||
|
||||
%% Add the alias to context_modules if defined is true.
|
||||
%% This is used by defmodule in order to store the defined
|
||||
%% module in context modules.
|
||||
NewContext =
|
||||
case lists:keyfind(defined, 1, Meta) of
|
||||
{ defined, Mod } when is_atom(Mod) -> [Mod|Context];
|
||||
false -> Context
|
||||
end,
|
||||
|
||||
{ Aliases, MacroAliases } = elixir_aliases:store(Meta, New, Ref, KV, E#elixir_env.aliases,
|
||||
E#elixir_env.macro_aliases, E#elixir_env.lexical_tracker),
|
||||
|
||||
E#elixir_env{aliases=Aliases, macro_aliases=MacroAliases, context_modules=NewContext}.
|
||||
|
||||
expand_as({ as, true }, _Meta, _IncludeByDefault, Ref, _E) ->
|
||||
elixir_aliases:last(Ref);
|
||||
expand_as({ as, false }, _Meta, _IncludeByDefault, Ref, _E) ->
|
||||
Ref;
|
||||
expand_as({ as, Atom }, Meta, _IncludeByDefault, _Ref, E) when is_atom(Atom) ->
|
||||
case length(string:tokens(atom_to_list(Atom), ".")) of
|
||||
1 -> compile_error(Meta, E#elixir_env.file,
|
||||
"invalid value for keyword :as, expected an alias, got atom: ~ts", [elixir_aliases:inspect(Atom)]);
|
||||
2 -> Atom;
|
||||
_ -> compile_error(Meta, E#elixir_env.file,
|
||||
"invalid value for keyword :as, expected an alias, got nested alias: ~ts", [elixir_aliases:inspect(Atom)])
|
||||
end;
|
||||
expand_as(false, _Meta, IncludeByDefault, Ref, _E) ->
|
||||
if IncludeByDefault -> elixir_aliases:last(Ref);
|
||||
true -> Ref
|
||||
end;
|
||||
expand_as({ as, Other }, Meta, _IncludeByDefault, _Ref, E) ->
|
||||
compile_error(Meta, E#elixir_env.file,
|
||||
"invalid value for keyword :as, expected an alias, got: ~ts", ['Elixir.Macro':to_string(Other)]).
|
||||
|
||||
%% Comprehensions
|
||||
|
||||
expand_comprehension(Meta, Kind, Args, E) ->
|
||||
case elixir_utils:split_last(Args) of
|
||||
{ Cases, [{do,Expr}] } ->
|
||||
{ ECases, EC } = lists:mapfoldl(fun expand_comprehension_clause/2, E, Cases),
|
||||
{ EExpr, _ } = expand(Expr, EC),
|
||||
{ { Kind, Meta, ECases ++ [[{do,EExpr}]] }, E };
|
||||
_ ->
|
||||
compile_error(Meta, E#elixir_env.file, "missing do keyword in comprehension ~ts", [Kind])
|
||||
end.
|
||||
|
||||
expand_comprehension_clause({Gen, Meta, [Left, Right]}, E) when Gen == inbits; Gen == inlist ->
|
||||
{ ERight, ER } = expand(Right, E),
|
||||
{ ELeft, EL } = elixir_exp_clauses:match(fun expand/2, Left, E),
|
||||
{ { Gen, Meta, [ELeft, ERight] }, elixir_env:mergev(EL, ER) };
|
||||
expand_comprehension_clause(X, E) ->
|
||||
expand(X, E).
|
||||
|
||||
%% Assertions
|
||||
|
||||
assert_no_match_or_guard_scope(Meta, Kind, E) ->
|
||||
assert_no_match_scope(Meta, Kind, E),
|
||||
assert_no_guard_scope(Meta, Kind, E).
|
||||
assert_no_match_scope(Meta, _Kind, #elixir_env{context=match,file=File}) ->
|
||||
compile_error(Meta, File, "invalid expression in match");
|
||||
assert_no_match_scope(_Meta, _Kind, _E) -> [].
|
||||
assert_no_guard_scope(Meta, _Kind, #elixir_env{context=guard,file=File}) ->
|
||||
compile_error(Meta, File, "invalid expression in guard");
|
||||
assert_no_guard_scope(_Meta, _Kind, _E) -> [].
|
||||
@@ -0,0 +1,200 @@
|
||||
%% Handle code related to args, guard and -> matching for case,
|
||||
%% fn, receive and friends. try is handled in elixir_try.
|
||||
-module(elixir_exp_clauses).
|
||||
-export([match/3, clause/5, 'case'/3, 'receive'/3, 'try'/3, def/5]).
|
||||
-import(elixir_errors, [compile_error/3, compile_error/4]).
|
||||
-include("elixir.hrl").
|
||||
|
||||
match(Fun, Expr, #elixir_env{context=Context} = E) ->
|
||||
{ EExpr, EE } = Fun(Expr, E#elixir_env{context=match}),
|
||||
{ EExpr, EE#elixir_env{context=Context} }.
|
||||
|
||||
def(Fun, Args, Guards, Body, E) ->
|
||||
{ EArgs, EA } = match(Fun, Args, E),
|
||||
{ EGuards, EG } = guard(Guards, EA#elixir_env{context=guard}),
|
||||
{ EBody, EB } = elixir_exp:expand(Body, EG#elixir_env{context=E#elixir_env.context}),
|
||||
{ EArgs, EGuards, EBody, EB }.
|
||||
|
||||
clause(Meta, Kind, Fun, { '->', ClauseMeta, [_, _] } = Clause, E) when is_function(Fun, 3) ->
|
||||
clause(Meta, Kind, fun(X, Acc) -> Fun(ClauseMeta, X, Acc) end, Clause, E);
|
||||
clause(_Meta, _Kind, Fun, { '->', Meta, [Left, Right] }, E) ->
|
||||
{ ELeft, EL } = head(Fun, Left, E),
|
||||
{ ERight, ER } = elixir_exp:expand(Right, EL),
|
||||
{ { '->', Meta, [ELeft, ERight] }, ER };
|
||||
clause(Meta, Kind, _Fun, _, E) ->
|
||||
compile_error(Meta, E#elixir_env.file, "expected -> clauses in ~ts", [Kind]).
|
||||
|
||||
head(Fun, [{ 'when', Meta, [_,_|_] = All }], E) ->
|
||||
{ Args, Guard } = elixir_utils:split_last(All),
|
||||
{ EArgs, EA } = match(Fun, Args, E),
|
||||
{ EGuard, EG } = guard(Guard, EA#elixir_env{context=guard}),
|
||||
{ [{ 'when', Meta, EArgs ++ [EGuard] }], EG#elixir_env{context=E#elixir_env.context} };
|
||||
head(Fun, Args, E) ->
|
||||
match(Fun, Args, E).
|
||||
|
||||
guard({ 'when', Meta, [Left, Right] }, E) ->
|
||||
{ ELeft, EL } = guard(Left, E),
|
||||
{ ERight, ER } = guard(Right, EL),
|
||||
{ { 'when', Meta, [ELeft, ERight] }, ER };
|
||||
guard(Other, E) ->
|
||||
elixir_exp:expand(Other, E).
|
||||
|
||||
%% Case
|
||||
|
||||
'case'(Meta, [], E) ->
|
||||
compile_error(Meta, E#elixir_env.file, "missing do keyword in case");
|
||||
'case'(Meta, KV, E) when not is_list(KV) ->
|
||||
compile_error(Meta, E#elixir_env.file, "invalid arguments for case");
|
||||
'case'(Meta, KV, E) ->
|
||||
EE = E#elixir_env{export_vars=[]},
|
||||
{ EClauses, EVars } = lists:mapfoldl(fun(X, Acc) -> do_case(Meta, X, Acc, EE) end, [], KV),
|
||||
{ EClauses, elixir_env:mergev(EVars, E) }.
|
||||
|
||||
do_case(Meta, { 'do', _ } = Do, Acc, E) ->
|
||||
expand_with_export(Meta, 'case', expand_arg(Meta, 'case', 'do'), Do, Acc, E);
|
||||
do_case(Meta, { Key, _ }, _Acc, E) ->
|
||||
compile_error(Meta, E#elixir_env.file, "unexpected keyword ~ts in case", [Key]).
|
||||
|
||||
%% Receive
|
||||
|
||||
'receive'(Meta, [], E) ->
|
||||
compile_error(Meta, E#elixir_env.file, "missing do or after keyword in receive");
|
||||
'receive'(Meta, KV, E) when not is_list(KV) ->
|
||||
compile_error(Meta, E#elixir_env.file, "invalid arguments for receive");
|
||||
'receive'(Meta, KV, E) ->
|
||||
EE = E#elixir_env{export_vars=[]},
|
||||
{ EClauses, EVars } = lists:mapfoldl(fun(X, Acc) -> do_receive(Meta, X, Acc, EE) end, [], KV),
|
||||
{ EClauses, elixir_env:mergev(EVars, E) }.
|
||||
|
||||
do_receive(_Meta, { 'do', nil } = Do, Acc, _E) ->
|
||||
{ Do, Acc };
|
||||
do_receive(Meta, { 'do', _ } = Do, Acc, E) ->
|
||||
expand_with_export(Meta, 'receive', expand_arg(Meta, 'receive', 'do'), Do, Acc, E);
|
||||
do_receive(_Meta, { 'after', [{ '->', Meta, [[Left], Right] }] }, Acc, E) ->
|
||||
{ ELeft, EL } = elixir_exp:expand(Left, E),
|
||||
{ ERight, ER } = elixir_exp:expand(Right, EL),
|
||||
EClause = { 'after', [{ '->', Meta, [[ELeft], ERight] }] },
|
||||
{ EClause, elixir_env:merge_vars(Acc, ER#elixir_env.export_vars) };
|
||||
do_receive(Meta, { 'after', _ }, _Acc, E) ->
|
||||
compile_error(Meta, E#elixir_env.file, "expected a single -> clause for after in receive");
|
||||
do_receive(Meta, { Key, _ }, _Acc, E) ->
|
||||
compile_error(Meta, E#elixir_env.file, "unexpected keyword ~ts in receive", [Key]).
|
||||
|
||||
%% Try
|
||||
|
||||
'try'(Meta, [], E) ->
|
||||
compile_error(Meta, E#elixir_env.file, "missing do keywords in try");
|
||||
'try'(Meta, KV, E) when not is_list(KV) ->
|
||||
elixir_errors:compile_error(Meta, E#elixir_env.file, "invalid arguments for try");
|
||||
'try'(Meta, KV, E) ->
|
||||
{ lists:map(fun(X) -> do_try(Meta, X, E) end, KV), E }.
|
||||
|
||||
do_try(_Meta, { 'do', Expr }, E) ->
|
||||
{ EExpr, _ } = elixir_exp:expand(Expr, E),
|
||||
{ 'do', EExpr };
|
||||
do_try(_Meta, { 'after', Expr }, E) ->
|
||||
{ EExpr, _ } = elixir_exp:expand(Expr, E),
|
||||
{ 'after', EExpr };
|
||||
do_try(Meta, { 'else', _ } = Else, E) ->
|
||||
expand_without_export(Meta, 'try', expand_arg(Meta, 'try', 'else'), Else, E);
|
||||
do_try(Meta, { 'catch', _ } = Catch, E) ->
|
||||
expand_without_export(Meta, 'try', fun elixir_exp:expand_many/2, Catch, E);
|
||||
do_try(Meta, { 'rescue', _ } = Rescue, E) ->
|
||||
expand_without_export(Meta, 'try', fun expand_rescue/3, Rescue, E);
|
||||
do_try(Meta, { Key, _ }, E) ->
|
||||
compile_error(Meta, E#elixir_env.file, "unexpected keyword ~ts in try", [Key]).
|
||||
|
||||
expand_rescue(Meta, [Arg], E) ->
|
||||
case expand_rescue(Arg, E) of
|
||||
{ EArg, EA } ->
|
||||
{ [EArg], EA };
|
||||
false ->
|
||||
compile_error(Meta, E#elixir_env.file, "invalid rescue clause. The clause should "
|
||||
"match on an alias, a variable or be in the `var in [alias]` format")
|
||||
end;
|
||||
expand_rescue(Meta, _, E) ->
|
||||
compile_error(Meta, E#elixir_env.file, "expected one arg for rescue clauses (->) in try").
|
||||
|
||||
%% rescue var => var in _
|
||||
expand_rescue({ Name, _, Atom } = Var, E) when is_atom(Name), is_atom(Atom) ->
|
||||
expand_rescue({ in, [], [Var, { '_', [], E#elixir_env.module }] }, E);
|
||||
|
||||
%% rescue var in [Exprs]
|
||||
expand_rescue({ in, Meta, [Left, Right] }, E) ->
|
||||
{ ERight, ER } = elixir_exp:expand(Right, E#elixir_env{context=nil}),
|
||||
{ ELeft, EL } = elixir_exp:expand(Left, ER#elixir_env{context=match}),
|
||||
|
||||
case ELeft of
|
||||
{ Name, _, Atom } when is_atom(Name), is_atom(Atom) ->
|
||||
case normalize_rescue(ERight) of
|
||||
false -> false;
|
||||
Other -> { { in, Meta, [ELeft, Other] }, EL }
|
||||
end;
|
||||
_ ->
|
||||
false
|
||||
end;
|
||||
|
||||
%% rescue Error => _ in [Error]
|
||||
expand_rescue(Arg, E) ->
|
||||
expand_rescue({ in, [], [{ '_', [], E#elixir_env.module }, Arg] }, E).
|
||||
|
||||
normalize_rescue({ '_', _, Atom } = N) when is_atom(Atom) -> N;
|
||||
normalize_rescue(Atom) when is_atom(Atom) -> [Atom];
|
||||
normalize_rescue(Other) ->
|
||||
is_list(Other)
|
||||
andalso lists:all(fun is_var_or_atom/1, Other)
|
||||
andalso Other.
|
||||
|
||||
is_var_or_atom({ Name, _, Atom }) when is_atom(Name), is_atom(Atom) -> true;
|
||||
is_var_or_atom(Atom) when is_atom(Atom) -> true;
|
||||
is_var_or_atom(_) -> false.
|
||||
|
||||
%% Expansion helpers
|
||||
|
||||
export_vars({ Left, Meta, Right }) when is_atom(Left), is_list(Meta), is_atom(Right) ->
|
||||
{ Left, [{export,false}|Meta], Right };
|
||||
export_vars({ Left, Meta, Right }) ->
|
||||
{ export_vars(Left), Meta, export_vars(Right) };
|
||||
export_vars({ Left, Right }) ->
|
||||
{ export_vars(Left), export_vars(Right) };
|
||||
export_vars(List) when is_list(List) ->
|
||||
[export_vars(X) || X <- List];
|
||||
export_vars(Other) ->
|
||||
Other.
|
||||
|
||||
%% Returns a function that expands arguments
|
||||
%% considering we have at maximum one entry.
|
||||
expand_arg(Meta, Kind, Key) ->
|
||||
fun
|
||||
([Arg], E) ->
|
||||
{ EArg, EA } = elixir_exp:expand(Arg, E),
|
||||
{ [EArg], EA };
|
||||
(_, E) ->
|
||||
compile_error(Meta, E#elixir_env.file, "expected one arg for ~ts clauses (->) in ~ts", [Key, Kind])
|
||||
end.
|
||||
|
||||
%% Expands all -> pairs in a given key keeping the overall vars.
|
||||
expand_with_export(Meta, Kind, Fun, { Key, Clauses }, Acc, E) when is_list(Clauses) ->
|
||||
EFun =
|
||||
case lists:keyfind(export_all, 1, Meta) of
|
||||
{ export_all, true } -> Fun;
|
||||
_ -> fun(ExportArgs, ExportE) -> Fun(export_vars(ExportArgs), ExportE) end
|
||||
end,
|
||||
Transformer = fun(Clause, Vars) ->
|
||||
{ EClause, EC } = clause(Meta, Kind, EFun, Clause, E),
|
||||
{ EClause, elixir_env:merge_vars(Vars, EC#elixir_env.export_vars) }
|
||||
end,
|
||||
{ EClauses, EVars } = lists:mapfoldl(Transformer, Acc, Clauses),
|
||||
{ { Key, EClauses }, EVars };
|
||||
expand_with_export(Meta, Kind, _Fun, { Key, _ }, _Acc, E) ->
|
||||
compile_error(Meta, E#elixir_env.file, "expected -> clauses for ~ts in ~ts", [Key, Kind]).
|
||||
|
||||
%% Expands all -> pairs in a given key but do not keep the overall vars.
|
||||
expand_without_export(Meta, Kind, Fun, { Key, Clauses }, E) when is_list(Clauses) ->
|
||||
Transformer = fun(Clause) ->
|
||||
{ EClause, _ } = clause(Meta, Kind, Fun, Clause, E),
|
||||
EClause
|
||||
end,
|
||||
{ Key, lists:map(Transformer, Clauses) };
|
||||
expand_without_export(Meta, Kind, _Fun, { Key, _ }, E) ->
|
||||
compile_error(Meta, E#elixir_env.file, "expected -> clauses for ~ts in ~ts", [Key, Kind]).
|
||||
@@ -1,13 +1,13 @@
|
||||
-module(elixir_fn).
|
||||
-export([fn/3, capture/3]).
|
||||
-import(elixir_scope, [umergec/2]).
|
||||
-import(elixir_errors, [syntax_error/3, syntax_error/4, compile_error/4]).
|
||||
-export([translate/3, capture/3, expand/3]).
|
||||
-import(elixir_errors, [compile_error/3, compile_error/4]).
|
||||
-include("elixir.hrl").
|
||||
|
||||
fn(Meta, Clauses, S) ->
|
||||
Transformer = fun({ ArgsWithGuards, CMeta, Expr }, Acc) ->
|
||||
translate(Meta, Clauses, S) ->
|
||||
Transformer = fun({ '->', CMeta, [ArgsWithGuards, Expr] }, Acc) ->
|
||||
{ Args, Guards } = elixir_clauses:extract_splat_guards(ArgsWithGuards),
|
||||
elixir_clauses:assigns_block(?line(CMeta), fun translate_fn_match/2, Args, [Expr], Guards, umergec(S, Acc))
|
||||
{ TClause, TS } = elixir_clauses:clause(?line(CMeta), fun translate_fn_match/2, Args, Expr, Guards, Acc),
|
||||
{ TClause, elixir_scope:mergef(S, TS) }
|
||||
end,
|
||||
|
||||
{ TClauses, NS } = lists:mapfoldl(Transformer, S, Clauses),
|
||||
@@ -15,21 +15,32 @@ fn(Meta, Clauses, S) ->
|
||||
|
||||
case length(lists:usort(Arities)) of
|
||||
1 ->
|
||||
{ { 'fun', ?line(Meta), { clauses, TClauses } }, umergec(S, NS) };
|
||||
{ { 'fun', ?line(Meta), { clauses, TClauses } }, NS };
|
||||
_ ->
|
||||
syntax_error(Meta, S#elixir_scope.file,
|
||||
"cannot mix clauses with different arities in function definition")
|
||||
compile_error(Meta, S#elixir_scope.file,
|
||||
"cannot mix clauses with different arities in function definition")
|
||||
end.
|
||||
|
||||
translate_fn_match(Arg, S) ->
|
||||
{ TArg, TS } = elixir_translator:translate(Arg, S#elixir_scope{extra=fn_match}),
|
||||
{ TArg, TS } = elixir_translator:translate_many(Arg, S#elixir_scope{extra=fn_match}),
|
||||
{ TArg, TS#elixir_scope{extra=S#elixir_scope.extra} }.
|
||||
|
||||
capture(Meta, { '/', _, [{ { '.', _, [_, F] } = Dot, RequireMeta , [] }, A] }, S) when is_atom(F), is_integer(A) ->
|
||||
Args = [{ '&', [], [X] } || X <- lists:seq(1, A)],
|
||||
capture_require(Meta, { Dot, RequireMeta, Args }, S, true);
|
||||
%% Expansion
|
||||
|
||||
capture(Meta, { '/', _, [{ F, _, C }, A] }, S) when is_atom(F), is_integer(A), is_atom(C) ->
|
||||
expand(Meta, Clauses, E) when is_list(Clauses) ->
|
||||
Transformer = fun(Clause) ->
|
||||
{ EClause, _ } = elixir_exp_clauses:clause(Meta, fn, fun elixir_exp:expand_many/2, Clause, E),
|
||||
EClause
|
||||
end,
|
||||
{ { fn, Meta, lists:map(Transformer, Clauses) }, E }.
|
||||
|
||||
%% Capture
|
||||
|
||||
capture(Meta, { '/', _, [{ { '.', _, [_, F] } = Dot, RequireMeta , [] }, A] }, E) when is_atom(F), is_integer(A) ->
|
||||
Args = [{ '&', [], [X] } || X <- lists:seq(1, A)],
|
||||
capture_require(Meta, { Dot, RequireMeta, Args }, E, true);
|
||||
|
||||
capture(Meta, { '/', _, [{ F, _, C }, A] }, E) when is_atom(F), is_integer(A), is_atom(C) ->
|
||||
ImportMeta =
|
||||
case lists:keyfind(import_fa, 1, Meta) of
|
||||
{ import_fa, { Receiver, Context } } ->
|
||||
@@ -40,115 +51,102 @@ capture(Meta, { '/', _, [{ F, _, C }, A] }, S) when is_atom(F), is_integer(A), i
|
||||
false -> Meta
|
||||
end,
|
||||
Args = [{ '&', [], [X] } || X <- lists:seq(1, A)],
|
||||
capture_import(Meta, { F, ImportMeta, Args }, S, true);
|
||||
capture_import(Meta, { F, ImportMeta, Args }, E, true);
|
||||
|
||||
capture(Meta, { { '.', _, [_, Fun] }, _, Args } = Expr, S) when is_atom(Fun), is_list(Args) ->
|
||||
capture_require(Meta, Expr, S, is_sequential_and_not_empty(Args));
|
||||
capture(Meta, { { '.', _, [_, Fun] }, _, Args } = Expr, E) when is_atom(Fun), is_list(Args) ->
|
||||
capture_require(Meta, Expr, E, is_sequential_and_not_empty(Args));
|
||||
|
||||
capture(Meta, { { '.', _, [_] }, _, Args } = Expr, S) when is_list(Args) ->
|
||||
do_capture(Meta, Expr, S, false);
|
||||
capture(Meta, { { '.', _, [_] }, _, Args } = Expr, E) when is_list(Args) ->
|
||||
do_capture(Meta, Expr, E, false);
|
||||
|
||||
capture(Meta, { '__block__', _, [Expr] }, S) ->
|
||||
capture(Meta, Expr, S);
|
||||
capture(Meta, { '__block__', _, [Expr] }, E) ->
|
||||
capture(Meta, Expr, E);
|
||||
|
||||
capture(Meta, { '__block__', _, _ } = Expr, S) ->
|
||||
capture(Meta, { '__block__', _, _ } = Expr, E) ->
|
||||
Message = "invalid args for &, block expressions are not allowed, got: ~ts",
|
||||
syntax_error(Meta, S#elixir_scope.file, Message, ['Elixir.Macro':to_string(Expr)]);
|
||||
compile_error(Meta, E#elixir_env.file, Message, ['Elixir.Macro':to_string(Expr)]);
|
||||
|
||||
capture(Meta, { Atom, _, Args } = Expr, S) when is_atom(Atom), is_list(Args) ->
|
||||
capture_import(Meta, Expr, S, is_sequential_and_not_empty(Args));
|
||||
capture(Meta, { Atom, _, Args } = Expr, E) when is_atom(Atom), is_list(Args) ->
|
||||
capture_import(Meta, Expr, E, is_sequential_and_not_empty(Args));
|
||||
|
||||
capture(Meta, { Left, Right }, S) ->
|
||||
capture(Meta, { '{}', Meta, [Left, Right] }, S);
|
||||
capture(Meta, { Left, Right }, E) ->
|
||||
capture(Meta, { '{}', Meta, [Left, Right] }, E);
|
||||
|
||||
capture(Meta, List, S) when is_list(List) ->
|
||||
do_capture(Meta, List, S, is_sequential_and_not_empty(List));
|
||||
capture(Meta, List, E) when is_list(List) ->
|
||||
do_capture(Meta, List, E, is_sequential_and_not_empty(List));
|
||||
|
||||
capture(Meta, Arg, S) when is_integer(Arg) ->
|
||||
compile_error(Meta, S#elixir_scope.file, "unhandled &~B outside of a capture", [Arg]);
|
||||
capture(Meta, Arg, E) ->
|
||||
invalid_capture(Meta, Arg, E).
|
||||
|
||||
capture(Meta, Arg, S) ->
|
||||
invalid_capture(Meta, Arg, S).
|
||||
capture_import(Meta, { Atom, ImportMeta, Args } = Expr, E, Sequential) ->
|
||||
Res = Sequential andalso
|
||||
elixir_dispatch:import_function(ImportMeta, Atom, length(Args), E),
|
||||
handle_capture(Res, Meta, Expr, E, Sequential).
|
||||
|
||||
%% Helpers
|
||||
capture_require(Meta, { { '.', _, [Left, Right] }, RequireMeta, Args } = Expr, E, Sequential) ->
|
||||
{ Mod, EE } = elixir_exp:expand(Left, E),
|
||||
Res = Sequential andalso is_atom(Mod) andalso
|
||||
elixir_dispatch:require_function(RequireMeta, Mod, Right, length(Args), EE),
|
||||
handle_capture(Res, Meta, Expr, EE, Sequential).
|
||||
|
||||
capture_import(Meta, { Atom, ImportMeta, Args } = Expr, S, Sequential) ->
|
||||
case Sequential andalso
|
||||
elixir_dispatch:import_function(ImportMeta, Atom, length(Args), S) of
|
||||
false -> do_capture(Meta, Expr, S, Sequential);
|
||||
Else -> Else
|
||||
handle_capture({ local, Fun, Arity }, _Meta, _Expr, _E, _Sequential) ->
|
||||
{ local, Fun, Arity };
|
||||
handle_capture({ remote, Receiver, Fun, Arity }, Meta, _Expr, E, _Sequential) ->
|
||||
Tree = { { '.', [], [erlang, make_fun] }, Meta, [Receiver, Fun, Arity] },
|
||||
{ expanded, Tree, E };
|
||||
handle_capture(false, Meta, Expr, E, Sequential) ->
|
||||
do_capture(Meta, Expr, E, Sequential).
|
||||
|
||||
do_capture(Meta, Expr, E, Sequential) ->
|
||||
case do_escape(Expr, elixir_counter:next(), E, []) of
|
||||
{ _, [] } when not Sequential ->
|
||||
invalid_capture(Meta, Expr, E);
|
||||
{ EExpr, EDict } ->
|
||||
EVars = validate(Meta, EDict, 1, E),
|
||||
Fn = { fn, Meta, [{ '->', Meta, [EVars, EExpr]}]},
|
||||
{ expanded, Fn, E }
|
||||
end.
|
||||
|
||||
capture_require(Meta, { { '.', _, [Left, Right] }, RequireMeta, Args } = Expr, S, Sequential) ->
|
||||
{ Mod, SE } = 'Elixir.Macro':expand_all(Left, elixir_scope:to_ex_env({ ?line(Meta), S }), S),
|
||||
|
||||
case Sequential andalso is_atom(Mod) andalso
|
||||
elixir_dispatch:require_function(RequireMeta, Mod, Right, length(Args), SE) of
|
||||
false -> do_capture(Meta, Expr, S, Sequential);
|
||||
Else -> Else
|
||||
end.
|
||||
|
||||
do_capture(Meta, Expr, S, Sequential) ->
|
||||
case do_escape(Expr, S, []) of
|
||||
{ _, _, [] } when not Sequential ->
|
||||
invalid_capture(Meta, Expr, S);
|
||||
{ TExpr, TS, TDict } ->
|
||||
TVars = validate(Meta, TDict, 1, S),
|
||||
fn(Meta, [{ TVars, Meta, TExpr }], TS)
|
||||
end.
|
||||
|
||||
invalid_capture(Meta, Arg, S) ->
|
||||
invalid_capture(Meta, Arg, E) ->
|
||||
Message = "invalid args for &, expected an expression in the format of &Mod.fun/arity, "
|
||||
"&local/arity or a capture containing at least one argument as &1, got: ~ts",
|
||||
syntax_error(Meta, S#elixir_scope.file, Message, ['Elixir.Macro':to_string(Arg)]).
|
||||
compile_error(Meta, E#elixir_env.file, Message, ['Elixir.Macro':to_string(Arg)]).
|
||||
|
||||
validate(Meta, [{ Pos, Var }|T], Pos, S) ->
|
||||
[Var|validate(Meta, T, Pos + 1, S)];
|
||||
validate(Meta, [{ Pos, Var }|T], Pos, E) ->
|
||||
[Var|validate(Meta, T, Pos + 1, E)];
|
||||
|
||||
validate(Meta, [{ Pos, _ }|_], Expected, S) ->
|
||||
compile_error(Meta, S#elixir_scope.file, "capture &~B cannot be defined without &~B", [Pos, Expected]);
|
||||
validate(Meta, [{ Pos, _ }|_], Expected, E) ->
|
||||
compile_error(Meta, E#elixir_env.file, "capture &~B cannot be defined without &~B", [Pos, Expected]);
|
||||
|
||||
validate(_Meta, [], _Pos, _S) ->
|
||||
validate(_Meta, [], _Pos, _E) ->
|
||||
[].
|
||||
|
||||
do_escape({ '&', Meta, [Pos] }, S, Dict) when is_integer(Pos), Pos > 0 ->
|
||||
case orddict:find(Pos, Dict) of
|
||||
{ ok, Var } ->
|
||||
{ Var, S, Dict };
|
||||
error ->
|
||||
{ Var, SC } = elixir_scope:build_ex_var(?line(Meta), S),
|
||||
{ Var, SC, orddict:store(Pos, Var, Dict) }
|
||||
end;
|
||||
do_escape({ '&', _, [Pos] }, Counter, _E, Dict) when is_integer(Pos), Pos > 0 ->
|
||||
Var = { list_to_atom([$x, $@+Pos]), [{ counter, Counter }], elixir_fn },
|
||||
{ Var, orddict:store(Pos, Var, Dict) };
|
||||
|
||||
do_escape({ '&', Meta, [Pos] }, S, _Dict) when is_integer(Pos) ->
|
||||
compile_error(Meta, S#elixir_scope.file, "capture &~B is not allowed", [Pos]);
|
||||
do_escape({ '&', Meta, [Pos] }, _Counter, E, _Dict) when is_integer(Pos) ->
|
||||
compile_error(Meta, E#elixir_env.file, "capture &~B is not allowed", [Pos]);
|
||||
|
||||
do_escape({ '&', Meta, _ } = Arg, S, _Dict) ->
|
||||
do_escape({ '&', Meta, _ } = Arg, _Counter, E, _Dict) ->
|
||||
Message = "nested captures via & are not allowed: ~ts",
|
||||
compile_error(Meta, S#elixir_scope.file, Message, ['Elixir.Macro':to_string(Arg)]);
|
||||
compile_error(Meta, E#elixir_env.file, Message, ['Elixir.Macro':to_string(Arg)]);
|
||||
|
||||
do_escape({ Left, Meta, Right }, S0, Dict0) ->
|
||||
{ TLeft, S1, Dict1 } = do_escape(Left, S0, Dict0),
|
||||
{ TRight, S2, Dict2 } = do_escape(Right, S1, Dict1),
|
||||
{ { TLeft, Meta, TRight }, S2, Dict2 };
|
||||
do_escape({ Left, Meta, Right }, Counter, E, Dict0) ->
|
||||
{ TLeft, Dict1 } = do_escape(Left, Counter, E, Dict0),
|
||||
{ TRight, Dict2 } = do_escape(Right, Counter, E, Dict1),
|
||||
{ { TLeft, Meta, TRight }, Dict2 };
|
||||
|
||||
do_escape({ Left, Right }, S0, Dict0) ->
|
||||
{ TLeft, S1, Dict1 } = do_escape(Left, S0, Dict0),
|
||||
{ TRight, S2, Dict2 } = do_escape(Right, S1, Dict1),
|
||||
{ { TLeft, TRight }, S2, Dict2 };
|
||||
do_escape({ Left, Right }, Counter, E, Dict0) ->
|
||||
{ TLeft, Dict1 } = do_escape(Left, Counter, E, Dict0),
|
||||
{ TRight, Dict2 } = do_escape(Right, Counter, E, Dict1),
|
||||
{ { TLeft, TRight }, Dict2 };
|
||||
|
||||
do_escape(List, S, Dict) when is_list(List) ->
|
||||
do_escape_list(List, S, Dict, []);
|
||||
do_escape(List, Counter, E, Dict) when is_list(List) ->
|
||||
lists:mapfoldl(fun(X, Acc) -> do_escape(X, Counter, E, Acc) end, Dict, List);
|
||||
|
||||
do_escape(Other, S, Dict) ->
|
||||
{ Other, S, Dict }.
|
||||
|
||||
do_escape_list([H|T], S, Dict, Acc) ->
|
||||
{ TH, TS, TDict } = do_escape(H, S, Dict),
|
||||
do_escape_list(T, TS, TDict, [TH|Acc]);
|
||||
|
||||
do_escape_list([], S, Dict, Acc) ->
|
||||
{ lists:reverse(Acc), S, Dict }.
|
||||
do_escape(Other, _Counter, _E, Dict) ->
|
||||
{ Other, Dict }.
|
||||
|
||||
is_sequential_and_not_empty([]) -> false;
|
||||
is_sequential_and_not_empty(List) -> is_sequential(List, 1).
|
||||
|
||||
@@ -7,55 +7,55 @@
|
||||
|
||||
%% IMPORT
|
||||
|
||||
import(Meta, Ref, Opts, S) ->
|
||||
SI =
|
||||
import(Meta, Ref, Opts, E) ->
|
||||
Res =
|
||||
case keyfind(only, Opts) of
|
||||
{ only, functions } ->
|
||||
import_functions(Meta, Ref, Opts, S);
|
||||
{ import_functions(Meta, Ref, Opts, E),
|
||||
E#elixir_env.macros };
|
||||
{ only, macros } ->
|
||||
import_macros(true, Meta, Ref, Opts, S);
|
||||
{ E#elixir_env.functions,
|
||||
import_macros(true, Meta, Ref, Opts, E) };
|
||||
{ only, List } when is_list(List) ->
|
||||
import_macros(false, Meta, Ref, Opts, import_functions(Meta, Ref, Opts, S));
|
||||
{ import_functions(Meta, Ref, Opts, E),
|
||||
import_macros(false, Meta, Ref, Opts, E) };
|
||||
false ->
|
||||
import_macros(false, Meta, Ref, Opts, import_functions(Meta, Ref, Opts, S))
|
||||
{ import_functions(Meta, Ref, Opts, E),
|
||||
import_macros(false, Meta, Ref, Opts, E) }
|
||||
end,
|
||||
record_warn(Meta, Ref, Opts, SI),
|
||||
SI.
|
||||
|
||||
import_functions(Meta, Ref, Opts, S) ->
|
||||
{ Functions, Temp } = calculate(Meta, Ref, Opts, S#elixir_scope.functions,
|
||||
S#elixir_scope.macro_functions, fun() -> get_functions(Ref) end, S),
|
||||
S#elixir_scope{functions=Functions, macro_functions=Temp}.
|
||||
record_warn(Meta, Ref, Opts, E),
|
||||
Res.
|
||||
|
||||
import_macros(Force, Meta, Ref, Opts, S) ->
|
||||
Existing = fun() ->
|
||||
import_functions(Meta, Ref, Opts, E) ->
|
||||
calculate(Meta, Ref, Opts, E#elixir_env.functions, E, fun() -> get_functions(Ref) end).
|
||||
|
||||
import_macros(Force, Meta, Ref, Opts, E) ->
|
||||
calculate(Meta, Ref, Opts, E#elixir_env.macros, E, fun() ->
|
||||
case Force of
|
||||
true -> get_macros(Meta, Ref, S);
|
||||
true -> get_macros(Meta, Ref, E);
|
||||
false -> get_optional_macros(Ref)
|
||||
end
|
||||
end,
|
||||
{ Macros, Temp } = calculate(Meta, Ref, Opts, S#elixir_scope.macros,
|
||||
S#elixir_scope.macro_macros, Existing, S),
|
||||
S#elixir_scope{macros=Macros, macro_macros=Temp}.
|
||||
end).
|
||||
|
||||
record_warn(Meta, Ref, Opts, S) ->
|
||||
record_warn(Meta, Ref, Opts, E) ->
|
||||
Warn =
|
||||
case keyfind(warn, Opts) of
|
||||
{ warn, false } -> false;
|
||||
{ warn, true } -> true;
|
||||
false -> not lists:keymember(context, 1, Meta)
|
||||
end,
|
||||
elixir_lexical:record_import(Ref, ?line(Meta), Warn, S#elixir_scope.lexical_tracker).
|
||||
elixir_lexical:record_import(Ref, ?line(Meta), Warn, E#elixir_env.lexical_tracker).
|
||||
|
||||
%% Calculates the imports based on only and except
|
||||
|
||||
calculate(Meta, Key, Opts, Old, Temp, Existing, S) ->
|
||||
calculate(Meta, Key, Opts, Old, E, Existing) ->
|
||||
New = case keyfind(only, Opts) of
|
||||
{ only, Only } when is_list(Only) ->
|
||||
case Only -- get_exports(Key) of
|
||||
[{Name,Arity}|_] ->
|
||||
Tuple = { invalid_import, { Key, Name, Arity } },
|
||||
elixir_errors:form_error(Meta, S#elixir_scope.file, ?MODULE, Tuple);
|
||||
elixir_errors:form_error(Meta, E#elixir_env.file, ?MODULE, Tuple);
|
||||
_ ->
|
||||
intersection(Only, Existing())
|
||||
end;
|
||||
@@ -76,25 +76,10 @@ calculate(Meta, Key, Opts, Old, Temp, Existing, S) ->
|
||||
Final = remove_internals(Set),
|
||||
|
||||
case Final of
|
||||
[] ->
|
||||
{ keydelete(Key, Old),
|
||||
if_quoted(Meta, Temp, fun(Value) -> keydelete(Key, Value) end) };
|
||||
[] -> keydelete(Key, Old);
|
||||
_ ->
|
||||
ensure_no_special_form_conflict(Meta, S#elixir_scope.file, Key, Final),
|
||||
{ [{ Key, Final }|keydelete(Key, Old)],
|
||||
if_quoted(Meta, Temp, fun(Value) -> [{ Key, Final }|keydelete(Key, Value)] end) }
|
||||
end.
|
||||
|
||||
if_quoted(Meta, Temp, Callback) ->
|
||||
case lists:keyfind(context, 1, Meta) of
|
||||
{ context, Context } ->
|
||||
Current = case orddict:find(Context, Temp) of
|
||||
{ ok, Value } -> Value;
|
||||
error -> []
|
||||
end,
|
||||
orddict:store(Context, Callback(Current), Temp);
|
||||
_ ->
|
||||
Temp
|
||||
ensure_no_special_form_conflict(Meta, E#elixir_env.file, Key, Final),
|
||||
[{ Key, Final }|keydelete(Key, Old)]
|
||||
end.
|
||||
|
||||
%% Retrieve functions and macros from modules
|
||||
@@ -113,13 +98,13 @@ get_functions(Module) ->
|
||||
error:undef -> Module:module_info(exports)
|
||||
end.
|
||||
|
||||
get_macros(Meta, Module, S) ->
|
||||
get_macros(Meta, Module, E) ->
|
||||
try
|
||||
Module:'__info__'(macros)
|
||||
catch
|
||||
error:undef ->
|
||||
Tuple = { no_macros, Module },
|
||||
elixir_errors:form_error(Meta, S#elixir_scope.file, ?MODULE, Tuple)
|
||||
elixir_errors:form_error(Meta, E#elixir_env.file, ?MODULE, Tuple)
|
||||
end.
|
||||
|
||||
get_optional_macros(Module) ->
|
||||
@@ -150,14 +135,14 @@ ensure_no_special_form_conflict(_Meta, _File, _Key, []) -> ok.
|
||||
|
||||
format_error({invalid_import,{Receiver, Name, Arity}}) ->
|
||||
io_lib:format("cannot import ~ts.~ts/~B because it doesn't exist",
|
||||
[elixir_errors:inspect(Receiver), Name, Arity]);
|
||||
[elixir_aliases:inspect(Receiver), Name, Arity]);
|
||||
|
||||
format_error({special_form_conflict,{Receiver, Name, Arity}}) ->
|
||||
io_lib:format("cannot import ~ts.~ts/~B because it conflicts with Elixir special forms",
|
||||
[elixir_errors:inspect(Receiver), Name, Arity]);
|
||||
[elixir_aliases:inspect(Receiver), Name, Arity]);
|
||||
|
||||
format_error({ no_macros, Module }) ->
|
||||
io_lib:format("could not load macros from module ~ts", [elixir_errors:inspect(Module)]).
|
||||
io_lib:format("could not load macros from module ~ts", [elixir_aliases:inspect(Module)]).
|
||||
|
||||
%% LIST HELPERS
|
||||
|
||||
@@ -196,7 +181,6 @@ special_form('^',1) -> true;
|
||||
special_form('=',2) -> true;
|
||||
special_form('__op__',2) -> true;
|
||||
special_form('__op__',3) -> true;
|
||||
special_form('__scope__',2) -> true;
|
||||
special_form('__block__',_) -> true;
|
||||
special_form('->',_) -> true;
|
||||
special_form('<<>>',_) -> true;
|
||||
@@ -210,7 +194,6 @@ special_form('import',2) -> true;
|
||||
special_form('__ENV__',0) -> true;
|
||||
special_form('__CALLER__',0) -> true;
|
||||
special_form('__MODULE__',0) -> true;
|
||||
special_form('__FILE__',0) -> true;
|
||||
special_form('__DIR__',0) -> true;
|
||||
special_form('__aliases__',_) -> true;
|
||||
special_form('quote',1) -> true;
|
||||
@@ -219,10 +202,9 @@ special_form('unquote',1) -> true;
|
||||
special_form('unquote_splicing',1) -> true;
|
||||
special_form('fn',_) -> true;
|
||||
special_form('super',_) -> true;
|
||||
special_form('super?',0) -> true;
|
||||
special_form('bc',_) -> true;
|
||||
special_form('lc',_) -> true;
|
||||
special_form('var!',1) -> true;
|
||||
special_form('var!',2) -> true;
|
||||
special_form('alias!',1) -> true;
|
||||
special_form('case',2) -> true;
|
||||
special_form('try',2) -> true;
|
||||
special_form('receive',1) -> true;
|
||||
special_form(_, _) -> false.
|
||||
|
||||
@@ -138,7 +138,13 @@ unescape_chars(<<>>, _Map, _Octals, _Hex, Acc) -> Acc.
|
||||
|
||||
append_escaped(Rest, Map, List, Octal, Hex, Acc, Base) ->
|
||||
Codepoint = list_to_integer(List, Base),
|
||||
unescape_chars(Rest, Map, Octal, Hex, <<Acc/binary, Codepoint/utf8>>).
|
||||
try <<Acc/binary, Codepoint/utf8>> of
|
||||
Binary -> unescape_chars(Rest, Map, Octal, Hex, Binary)
|
||||
catch
|
||||
error:badarg ->
|
||||
Msg = <<"invalid or reserved unicode codepoint ", (integer_to_binary(Codepoint))/binary>>,
|
||||
error('Elixir.ArgumentError':exception([{message,Msg}]))
|
||||
end.
|
||||
|
||||
% Unescape Helpers
|
||||
|
||||
|
||||
@@ -13,7 +13,6 @@ run(File, Callback) ->
|
||||
case code:is_loaded(?tracker) of
|
||||
{ file, _ } ->
|
||||
Pid = ?tracker:start_link(),
|
||||
|
||||
try
|
||||
Callback(Pid)
|
||||
after
|
||||
@@ -75,6 +74,6 @@ warn_unused_aliases(File, Pid) ->
|
||||
end || { M, L } <- ?tracker:collect_unused_aliases(Pid)].
|
||||
|
||||
format_error({unused_alias, Module}) ->
|
||||
io_lib:format("unused alias ~ts", [elixir_errors:inspect(Module)]);
|
||||
io_lib:format("unused alias ~ts", [elixir_aliases:inspect(Module)]);
|
||||
format_error({unused_import, Module}) ->
|
||||
io_lib:format("unused import ~ts", [elixir_errors:inspect(Module)]).
|
||||
io_lib:format("unused import ~ts", [elixir_aliases:inspect(Module)]).
|
||||
|
||||
@@ -1,199 +0,0 @@
|
||||
%% Handle translation of Elixir literals to Erlang AST.
|
||||
-module(elixir_literal).
|
||||
-export([translate/2]).
|
||||
-import(elixir_translator, [translate_each/2, translate_args/2]).
|
||||
-import(elixir_scope, [umergec/2]).
|
||||
-include("elixir.hrl").
|
||||
|
||||
translate({ '<<>>', Meta, Args }, S) when is_list(Args) ->
|
||||
case S#elixir_scope.context of
|
||||
match ->
|
||||
build_bitstr(fun elixir_translator:translate_each/2, Args, Meta, S);
|
||||
_ ->
|
||||
{ TArgs, { SC, SV } } = build_bitstr(fun elixir_translator:translate_arg/2, Args, Meta, { S, S }),
|
||||
{ TArgs, umergec(SV, SC) }
|
||||
end;
|
||||
|
||||
translate({ '{}', Meta, Args }, S) when is_list(Args) ->
|
||||
{ TArgs, SE } = translate_args(Args, S),
|
||||
{ { tuple, ?line(Meta), TArgs }, SE };
|
||||
|
||||
translate({ Left, Right }, S) ->
|
||||
translate({ '{}', [], [Left, Right]}, S);
|
||||
|
||||
translate([], S) ->
|
||||
{ { nil, 0 }, S };
|
||||
|
||||
translate([_|_] = Args, S) ->
|
||||
[RTail|RArgs] = lists:reverse(Args),
|
||||
|
||||
case RTail of
|
||||
{ '|', _, [Left,Right] } ->
|
||||
RExprs = [Left|RArgs],
|
||||
TailFun = fun(ST) -> translate_each(Right, ST) end;
|
||||
_ ->
|
||||
RExprs = [RTail|RArgs],
|
||||
TailFun = fun(ST) -> { { nil, 0 }, ST } end
|
||||
end,
|
||||
|
||||
{ Exprs, SE } = translate_args(lists:reverse(RExprs), S),
|
||||
{ Tail, ST } = TailFun(SE),
|
||||
{ elixir_utils:list_to_cons(0, Exprs, Tail), ST };
|
||||
|
||||
translate(Tuple, S) when is_tuple(Tuple) ->
|
||||
elixir_errors:compile_error(0, S#elixir_scope.file,
|
||||
"tuples in quoted expressions must have 2 or 3 items, invalid quoted expression: ~ts",
|
||||
['Elixir.Kernel':inspect(Tuple)]);
|
||||
|
||||
translate(Other, S) ->
|
||||
{ elixir_utils:elixir_to_erl(0, Other, S), S }.
|
||||
|
||||
%% Helpers
|
||||
|
||||
% Build a bitstring taking into accounts the following types:
|
||||
%
|
||||
% * If a bitstring or a list is given, we just append its items
|
||||
% * If '::' is given, extract the bitstring information
|
||||
% * All the other types are simply translated and handled with Erlang's default
|
||||
%
|
||||
build_bitstr(Fun, Exprs, Meta, S) ->
|
||||
{ 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 };
|
||||
|
||||
build_bitstr_each(Fun, [{'::',_,[H,V]}|T], Meta, S, Acc) ->
|
||||
%% Variables defined outside the binary can be accounted
|
||||
%% on subparts, however we can't assign new variables.
|
||||
case S of
|
||||
{ ES, _ } -> []; %% translate_arg, no assigns
|
||||
_ -> ES = S#elixir_scope{context=nil} %% translate_each, revert assigns
|
||||
end,
|
||||
|
||||
{ Size, Types } = extract_bit_values(Meta, V, ES),
|
||||
build_bitstr_each(Fun, T, Meta, S, Acc, H, Size, Types);
|
||||
|
||||
build_bitstr_each(Fun, [H|T], Meta, S, Acc) ->
|
||||
build_bitstr_each(Fun, T, Meta, S, Acc, H, default, default).
|
||||
|
||||
build_bitstr_each(Fun, T, Meta, S, Acc, H, Size, Types) ->
|
||||
{ Expr, NS } = Fun(H, S),
|
||||
|
||||
AllowString = types_allow_string(Types),
|
||||
AllowSplice = types_allow_splice(Types),
|
||||
AllowAny = (AllowString orelse AllowSplice) andalso (Size == default),
|
||||
|
||||
case AllowAny andalso Expr of
|
||||
{ bin, _, [{ bin_element, 0, { string, 0, String }, default, default }] } when AllowString ->
|
||||
build_bitstr_each(Fun, T, Meta, NS, [{ bin_element, ?line(Meta), { string, 0, String }, Size, Types }|Acc]);
|
||||
{ bin, _, Elements } when AllowSplice ->
|
||||
build_bitstr_each(Fun, T, Meta, NS, lists:reverse(Elements) ++ Acc);
|
||||
{ cons, _, _, _ } = Cons ->
|
||||
build_bitstr_each(Fun, T, Meta, NS, rehash_cons(Cons, Size, Types, []) ++ Acc);
|
||||
{ nil, _ } ->
|
||||
build_bitstr_each(Fun, T, Meta, NS, Acc);
|
||||
_ ->
|
||||
build_bitstr_each(Fun, T, Meta, NS, [{ bin_element, ?line(Meta), Expr, Size, Types }|Acc])
|
||||
end.
|
||||
|
||||
rehash_cons({ nil, _ }, _Size, _Types, Acc) -> Acc;
|
||||
rehash_cons({ cons, Line, Left, Right }, Size, Types, Acc) ->
|
||||
rehash_cons(Right, Size, Types, [{ bin_element, Line, Left, Size, Types }|Acc]).
|
||||
|
||||
types_allow_string([End|T]) when End == little; End == big -> types_allow_string(T);
|
||||
types_allow_string([UTF|T]) when UTF == utf8; UTF == utf16; UTF == utf32 -> types_allow_string(T);
|
||||
types_allow_string([]) -> true;
|
||||
types_allow_string(_) -> false.
|
||||
|
||||
types_allow_splice(default) -> true;
|
||||
types_allow_splice([bytes]) -> true;
|
||||
types_allow_splice([binary]) -> true;
|
||||
types_allow_splice([bits]) -> true;
|
||||
types_allow_splice([bitstring]) -> true;
|
||||
types_allow_splice(_) -> false.
|
||||
|
||||
%% Extra bitstring specifiers
|
||||
|
||||
extract_bit_values(Meta, V, S) when is_list(V) ->
|
||||
extract_bit_values(Meta, V, default, [], S);
|
||||
|
||||
extract_bit_values(Meta, V, S) ->
|
||||
extract_bit_values(Meta, [V], S).
|
||||
|
||||
extract_bit_values(Meta, [{ Value, _Meta, Atom }|T] = All, Size, Types, S)
|
||||
when is_atom(Value) andalso (is_atom(Atom) orelse Atom == []) ->
|
||||
case extract_bit_type(Value, Types) of
|
||||
{ error, unknown } ->
|
||||
handle_unknown_specifier(Meta, All, Size, Types, S);
|
||||
NewTypes when is_list(NewTypes) ->
|
||||
extract_bit_values(Meta, T, Size, NewTypes, S)
|
||||
end;
|
||||
|
||||
extract_bit_values(Meta, [{ Value, CallMeta, [_] = Args }|T] = All, Size, Types, S) when is_atom(Value) ->
|
||||
{ TArgs, _ } = elixir_translator:translate_args(Args, S),
|
||||
case extract_bit_type_or_size(Value, TArgs, Size, Types) of
|
||||
{ error, unknown } ->
|
||||
handle_unknown_specifier(Meta, All, Size, Types, S);
|
||||
{ error, Msg } ->
|
||||
elixir_errors:syntax_error(CallMeta, S#elixir_scope.file, Msg);
|
||||
{ NewSize, NewTypes } ->
|
||||
extract_bit_values(Meta, T, NewSize, NewTypes, S)
|
||||
end;
|
||||
|
||||
extract_bit_values(Meta, [Size|T], default, Types, S) when is_integer(Size) andalso Size >= 0 ->
|
||||
extract_bit_values(Meta, T, {integer, Meta, Size}, Types, S);
|
||||
|
||||
extract_bit_values(Meta, [{ '|', _, [Left, Right] }], Size, Types, S) ->
|
||||
{ Expanded, ES } = 'Elixir.Macro':expand_all(Right, elixir_scope:to_ex_env({ ?line(Meta), S }), S),
|
||||
extract_bit_values(Meta, [Left|join_expansion(Expanded,[])], Size, Types, ES);
|
||||
|
||||
extract_bit_values(Meta, [_|_] = All, Size, Types, S) ->
|
||||
handle_unknown_specifier(Meta, All, Size, Types, S);
|
||||
|
||||
extract_bit_values(_Meta, [], Size, [], _S) ->
|
||||
{ Size, default };
|
||||
|
||||
extract_bit_values(_Meta, [], Size, Types, _S) ->
|
||||
{ Size, lists:reverse(Types) }.
|
||||
|
||||
extract_bit_type(Value, Types) when
|
||||
Value == binary; Value == integer; Value == float; Value == bitstring;
|
||||
Value == bytes; Value == bits;
|
||||
Value == utf8; Value == utf16; Value == utf32;
|
||||
Value == signed; Value == unsigned;
|
||||
Value == big; Value == little; Value == native ->
|
||||
[Value|Types];
|
||||
|
||||
extract_bit_type(_Value, _Types) ->
|
||||
{ error, unknown }.
|
||||
|
||||
extract_bit_type_or_size(size, [{ Kind, _, _ } = Arg], default, Types) when Kind == var; Kind == integer ->
|
||||
{ Arg, Types };
|
||||
|
||||
extract_bit_type_or_size(size, _Args, default, _Types) ->
|
||||
{ error, "size in bitstring expects an integer or a variable as argument" };
|
||||
|
||||
extract_bit_type_or_size(size, _Args, _Other, _Types) ->
|
||||
{ error, "duplicated size definition for bitstring" };
|
||||
|
||||
extract_bit_type_or_size(unit, [{ integer, _, Arg }], Size, Types) ->
|
||||
{ Size, [{ unit, Arg }|Types] };
|
||||
|
||||
extract_bit_type_or_size(unit, _Args, _Other, _Types) ->
|
||||
{ error, "unit in bitstring expects an integer as argument" };
|
||||
|
||||
extract_bit_type_or_size(_Value, _Args, _Other, _Types) ->
|
||||
{ error, unknown }.
|
||||
|
||||
handle_unknown_specifier(Meta, [H|T], Size, Types, S) ->
|
||||
case 'Elixir.Macro':expand_all(H, elixir_scope:to_ex_env({ ?line(Meta), S }), S) of
|
||||
{ H, ES } ->
|
||||
elixir_errors:syntax_error(Meta, ES#elixir_scope.file, "unknown bitstring specifier ~ts", ['Elixir.Macro':to_string(H)]);
|
||||
{ E, ES } ->
|
||||
extract_bit_values(Meta, join_expansion(E,T), Size, Types, ES)
|
||||
end.
|
||||
|
||||
join_expansion({ '__block__', _, [Expanded] }, Tail) -> join_expansion(Expanded, Tail);
|
||||
join_expansion(Expanded, Tail) when is_list(Expanded) -> Expanded ++ Tail;
|
||||
join_expansion(Expanded, Tail) -> [Expanded|Tail].
|
||||
@@ -1,19 +1,76 @@
|
||||
%% Module responsible for tracking invocations of module calls.
|
||||
-module(elixir_tracker).
|
||||
-module(elixir_locals).
|
||||
-export([
|
||||
setup/1, cleanup/1,
|
||||
record_local/2, record_local/3,
|
||||
record_import/4, record_remote/4,
|
||||
setup/1, cleanup/1, cache_env/1, get_cached_env/1,
|
||||
record_local/2, record_local/3, record_import/4,
|
||||
record_definition/3, record_defaults/4,
|
||||
ensure_no_function_conflict/4, warn_unused_local/3,
|
||||
format_error/1
|
||||
ensure_no_function_conflict/4, warn_unused_local/3, format_error/1
|
||||
]).
|
||||
-export([macro_for/3, function_for/3]).
|
||||
|
||||
-include("elixir.hrl").
|
||||
-define(attr, '__locals_tracker').
|
||||
-define(tracker, 'Elixir.Module.LocalsTracker').
|
||||
|
||||
-define(attr, '__dispatch_tracker').
|
||||
-define(tracker, 'Elixir.Module.DispatchTracker').
|
||||
macro_for(nil, _Name, _Arity) -> false;
|
||||
macro_for(Module, Name, Arity) ->
|
||||
Tuple = { Name, Arity },
|
||||
try elixir_def:lookup_definition(Module, Tuple) of
|
||||
{ { Tuple, Kind, Line, _, _, _, _ }, [_|_] = Clauses }
|
||||
when Kind == defmacro; Kind == defmacrop ->
|
||||
fun() -> get_function(Line, Module, Clauses) end;
|
||||
_ ->
|
||||
false
|
||||
catch
|
||||
error:badarg -> false
|
||||
end.
|
||||
|
||||
%% RECORD
|
||||
function_for(Module, Name, Arity) ->
|
||||
Tuple = { Name, Arity },
|
||||
case elixir_def:lookup_definition(Module, Tuple) of
|
||||
{ { Tuple, _, Line, _, _, _, _ }, Clauses } when Clauses /= [] ->
|
||||
%% There is no need to record such calls
|
||||
%% since they come from funtions that were
|
||||
%% already analyzed at compilation time.
|
||||
get_function(Line, Module, Clauses);
|
||||
_ ->
|
||||
[_|T] = erlang:get_stacktrace(),
|
||||
erlang:raise(error, undef, [{Module,Name,Arity,[]}|T])
|
||||
end.
|
||||
|
||||
get_function(Line, Module, Clauses) ->
|
||||
RewrittenClauses = [rewrite_clause(Clause, Module) || Clause <- Clauses],
|
||||
Fun = { 'fun', Line, {clauses, RewrittenClauses } },
|
||||
{ value, Result, _Binding } = erl_eval:exprs([Fun], []),
|
||||
Result.
|
||||
|
||||
rewrite_clause({ call, Line, { atom, Line, RawName }, Args }, Module) ->
|
||||
Remote = { remote, Line,
|
||||
{ atom, Line, ?MODULE },
|
||||
{ atom, Line, function_for }
|
||||
},
|
||||
|
||||
%% If we have a macro, its arity in the table is
|
||||
%% actually one less than in the function call
|
||||
{ Name, Arity } = case atom_to_list(RawName) of
|
||||
"MACRO-" ++ Rest -> { list_to_atom(Rest), length(Args) - 1 };
|
||||
_ -> { RawName, length(Args) }
|
||||
end,
|
||||
|
||||
FunCall = { call, Line, Remote, [
|
||||
{ atom, Line, Module }, { atom, Line, Name }, { integer, Line, Arity }
|
||||
] },
|
||||
{ call, Line, FunCall, Args };
|
||||
|
||||
rewrite_clause(Tuple, Module) when is_tuple(Tuple) ->
|
||||
list_to_tuple(rewrite_clause(tuple_to_list(Tuple), Module));
|
||||
|
||||
rewrite_clause(List, Module) when is_list(List) ->
|
||||
[rewrite_clause(Item, Module) || Item <- List];
|
||||
|
||||
rewrite_clause(Else, _) -> Else.
|
||||
|
||||
%% TRACKING
|
||||
|
||||
setup(Module) ->
|
||||
case code:is_loaded(?tracker) of
|
||||
@@ -45,14 +102,6 @@ record_import(Tuple, Receiver, Module, Function) ->
|
||||
true
|
||||
end).
|
||||
|
||||
record_remote(_Tuple, Receiver, Module, _Function)
|
||||
when Module == nil; Module == Receiver -> false;
|
||||
record_remote(Tuple, Receiver, Module, Function) ->
|
||||
if_tracker(Module, fun(Pid) ->
|
||||
?tracker:add_remote(Pid, Function, Receiver, Tuple),
|
||||
true
|
||||
end).
|
||||
|
||||
record_definition(Tuple, Kind, Module) ->
|
||||
if_tracker(Module, fun(Pid) ->
|
||||
?tracker:add_definition(Pid, Kind, Tuple),
|
||||
@@ -67,8 +116,6 @@ record_defaults(Tuple, Kind, Module, Defaults) ->
|
||||
true
|
||||
end).
|
||||
|
||||
%% HELPERS
|
||||
|
||||
if_tracker(Module, Callback) ->
|
||||
try ets:lookup_element(Module, ?attr, 2) of
|
||||
Pid -> Callback(Pid)
|
||||
@@ -76,6 +123,24 @@ if_tracker(Module, Callback) ->
|
||||
error:badarg -> false
|
||||
end.
|
||||
|
||||
%% CACHING
|
||||
|
||||
cache_env(#elixir_env{module=Module} = RE) ->
|
||||
E = RE#elixir_env{line=nil,vars=[]},
|
||||
try ets:lookup_element(Module, ?attr, 2) of
|
||||
Pid ->
|
||||
{ Pid, ?tracker:cache_env(Pid, E) }
|
||||
catch
|
||||
error:badarg ->
|
||||
{ Escaped, _ } = elixir_quote:escape(E, false),
|
||||
Escaped
|
||||
end;
|
||||
cache_env(ExEnv) ->
|
||||
cache_env(elixir_env:ex_to_env(ExEnv)).
|
||||
|
||||
get_cached_env({Pid,Ref}) -> ?tracker:get_cached_env(Pid, Ref);
|
||||
get_cached_env(Env) -> Env.
|
||||
|
||||
%% ERROR HANDLING
|
||||
|
||||
ensure_no_function_conflict(Meta, File, Module, AllDefined) ->
|
||||
@@ -101,7 +166,7 @@ warn_unused_local(File, Module, Private) ->
|
||||
|
||||
format_error({function_conflict,{Receivers, Name, Arity}}) ->
|
||||
io_lib:format("imported ~ts.~ts/~B conflicts with local function",
|
||||
[elixir_errors:inspect(hd(Receivers)), Name, Arity]);
|
||||
[elixir_aliases:inspect(hd(Receivers)), Name, Arity]);
|
||||
|
||||
format_error({unused_args,{Name, Arity}}) ->
|
||||
io_lib:format("default arguments in ~ts/~B are never used", [Name, Arity]);
|
||||
@@ -1,313 +0,0 @@
|
||||
%% Those macros behave like they belong to Kernel,
|
||||
%% but do not since they need to be implemented in Erlang.
|
||||
-module(elixir_macros).
|
||||
-export([translate/2]).
|
||||
-import(elixir_translator, [translate_each/2]).
|
||||
-import(elixir_scope, [umergec/2, umergea/2]).
|
||||
-import(elixir_errors, [compile_error/3, syntax_error/3, syntax_error/4,
|
||||
assert_no_function_scope/3, assert_module_scope/3, assert_no_match_or_guard_scope/3]).
|
||||
|
||||
-include("elixir.hrl").
|
||||
-define(opt_in_types(Kind), Kind == atom orelse Kind == integer orelse Kind == float).
|
||||
|
||||
%% Operators
|
||||
|
||||
translate({ '+', _Meta, [Expr] }, S) when is_number(Expr) ->
|
||||
translate_each(Expr, S);
|
||||
|
||||
translate({ '-', _Meta, [Expr] }, S) when is_number(Expr) ->
|
||||
translate_each(-1 * Expr, S);
|
||||
|
||||
translate({ Op, Meta, Exprs }, S) when is_list(Exprs),
|
||||
Op == '<-' orelse Op == '--' ->
|
||||
assert_no_match_or_guard_scope(Meta, Op, S),
|
||||
translate_each({ '__op__', Meta, [Op|Exprs] }, S);
|
||||
|
||||
translate({ Op, Meta, Exprs }, S) when is_list(Exprs),
|
||||
Op == '+' orelse Op == '-' orelse Op == '*' orelse Op == '/' orelse
|
||||
Op == '++' orelse Op == 'not' orelse Op == 'and' orelse Op == 'or' orelse
|
||||
Op == 'xor' orelse Op == '<' orelse Op == '>' orelse Op == '<=' orelse
|
||||
Op == '>=' orelse Op == '==' orelse Op == '!=' orelse Op == '===' orelse
|
||||
Op == '!==' ->
|
||||
translate_each({ '__op__', Meta, [Op|Exprs] }, S);
|
||||
|
||||
translate({ '!', Meta, [{ '!', _, [Expr] }] }, S) ->
|
||||
{ TExpr, SE } = translate_each(Expr, S),
|
||||
elixir_utils:convert_to_boolean(?line(Meta), TExpr, true, S#elixir_scope.context == guard, SE);
|
||||
|
||||
translate({ '!', Meta, [Expr] }, S) ->
|
||||
{ TExpr, SE } = translate_each(Expr, S),
|
||||
elixir_utils:convert_to_boolean(?line(Meta), TExpr, false, S#elixir_scope.context == guard, SE);
|
||||
|
||||
translate({ in, Meta, [Left, Right] }, #elixir_scope{extra_guards=nil} = S) ->
|
||||
translate_in(Meta, Left, Right, S);
|
||||
|
||||
%% @
|
||||
|
||||
translate({'@', Meta, [{ Name, _, [Arg] }]}, S) when Name == typep; Name == type; Name == spec; Name == callback; Name == opaque ->
|
||||
case elixir_compiler:get_opt(internal) of
|
||||
true -> { { nil, ?line(Meta) }, S };
|
||||
false ->
|
||||
Call = { { '.', Meta, ['Elixir.Kernel.Typespec', spec_to_macro(Name)] }, Meta, [Arg] },
|
||||
translate_each(Call, S)
|
||||
end;
|
||||
|
||||
translate({'@', Meta, [{ Name, _, Args }]}, S) ->
|
||||
assert_module_scope(Meta, '@', S),
|
||||
|
||||
case is_reserved_data(Name) andalso elixir_compiler:get_opt(internal) of
|
||||
true ->
|
||||
{ { nil, ?line(Meta) }, S };
|
||||
_ ->
|
||||
case Args of
|
||||
[Arg] ->
|
||||
case S#elixir_scope.function of
|
||||
nil ->
|
||||
translate_each({
|
||||
{ '.', Meta, ['Elixir.Module', put_attribute] },
|
||||
Meta,
|
||||
[ { '__MODULE__', Meta, false }, Name, Arg ]
|
||||
}, S);
|
||||
_ ->
|
||||
syntax_error(Meta, S#elixir_scope.file,
|
||||
"cannot dynamically set attribute @~ts inside a function", [Name])
|
||||
end;
|
||||
_ when is_atom(Args) or (Args == []) ->
|
||||
case S#elixir_scope.function of
|
||||
nil ->
|
||||
translate_each({
|
||||
{ '.', Meta, ['Elixir.Module', get_attribute] },
|
||||
Meta,
|
||||
[ { '__MODULE__', Meta, false }, Name, true ]
|
||||
}, S);
|
||||
_ ->
|
||||
Contents = 'Elixir.Module':get_attribute(S#elixir_scope.module, Name),
|
||||
{ elixir_utils:elixir_to_erl(?line(Meta), Contents, S), S }
|
||||
end;
|
||||
_ ->
|
||||
syntax_error(Meta, S#elixir_scope.file, "expected 0 or 1 argument for @~ts, got: ~p", [Name, length(Args)])
|
||||
end
|
||||
end;
|
||||
|
||||
%% Case
|
||||
|
||||
translate({'case', Meta, [Expr, KV]}, S) when is_list(KV) ->
|
||||
assert_no_match_or_guard_scope(Meta, 'case', S),
|
||||
Clauses = elixir_clauses:get_pairs(Meta, do, KV, S),
|
||||
{ TExpr, NS } = translate_each(Expr, S),
|
||||
|
||||
RClauses = case elixir_utils:returns_boolean(TExpr) of
|
||||
true -> rewrite_case_clauses(Clauses);
|
||||
false -> Clauses
|
||||
end,
|
||||
|
||||
{ TClauses, TS } = elixir_clauses:match(Meta, RClauses, NS),
|
||||
{ { 'case', ?line(Meta), TExpr, TClauses }, TS };
|
||||
|
||||
%% Try
|
||||
|
||||
translate({'try', Meta, [Clauses]}, S) when is_list(Clauses) ->
|
||||
assert_no_match_or_guard_scope(Meta, 'try', S),
|
||||
|
||||
Do = proplists:get_value('do', Clauses, nil),
|
||||
{ TDo, SB } = elixir_translator:translate_each(Do, S#elixir_scope{noname=true}),
|
||||
|
||||
Catch = [Tuple || { X, _ } = Tuple <- Clauses, X == 'rescue' orelse X == 'catch'],
|
||||
{ TCatch, SC } = elixir_try:clauses(Meta, Catch, umergea(S, SB)),
|
||||
|
||||
After = proplists:get_value('after', Clauses, nil),
|
||||
{ TAfter, SA } = elixir_translator:translate_each(After, umergea(S, SC)),
|
||||
|
||||
Else = elixir_clauses:get_pairs(Meta, else, Clauses, S),
|
||||
{ TElse, SE } = elixir_clauses:match(Meta, Else, umergea(S, SA)),
|
||||
|
||||
SF = (umergec(S, SE))#elixir_scope{noname=S#elixir_scope.noname},
|
||||
{ { 'try', ?line(Meta), pack(TDo), TElse, TCatch, pack(TAfter) }, SF };
|
||||
|
||||
%% Receive
|
||||
|
||||
translate({'receive', Meta, [KV] }, S) when is_list(KV) ->
|
||||
assert_no_match_or_guard_scope(Meta, 'receive', S),
|
||||
Do = elixir_clauses:get_pairs(Meta, do, KV, S, true),
|
||||
|
||||
case lists:keyfind('after', 1, KV) of
|
||||
false ->
|
||||
{ TClauses, SC } = elixir_clauses:match(Meta, Do, S),
|
||||
{ { 'receive', ?line(Meta), TClauses }, SC };
|
||||
_ ->
|
||||
After = elixir_clauses:get_pairs(Meta, 'after', KV, S),
|
||||
{ TClauses, SC } = elixir_clauses:match(Meta, Do ++ After, S),
|
||||
{ FClauses, TAfter } = elixir_utils:split_last(TClauses),
|
||||
{ _, _, [FExpr], _, FAfter } = TAfter,
|
||||
{ { 'receive', ?line(Meta), FClauses, FExpr, FAfter }, SC }
|
||||
end;
|
||||
|
||||
%% Definitions
|
||||
|
||||
translate({defmodule, Meta, [Ref, KV]}, S) when is_list(KV) ->
|
||||
{ TRef, _ } = translate_each(Ref, S),
|
||||
|
||||
Block = case lists:keyfind(do, 1, KV) of
|
||||
{ do, DoValue } -> DoValue;
|
||||
false -> syntax_error(Meta, S#elixir_scope.file, "missing do keyword in defmodule")
|
||||
end,
|
||||
|
||||
{ FRef, FS } = case TRef of
|
||||
{ atom, _, Module } ->
|
||||
FullModule = expand_module(Ref, Module, S),
|
||||
|
||||
RS = case elixir_aliases:nesting_alias(S#elixir_scope.module, FullModule) of
|
||||
{ New, Old } -> elixir_aliases:store(Meta, New, Old, [{warn,false}], S);
|
||||
false -> S
|
||||
end,
|
||||
|
||||
{
|
||||
{ atom, Meta, FullModule },
|
||||
RS#elixir_scope{context_modules=[FullModule|S#elixir_scope.context_modules]}
|
||||
};
|
||||
_ ->
|
||||
{ TRef, S }
|
||||
end,
|
||||
|
||||
MS = FS#elixir_scope{local=nil},
|
||||
{ elixir_module:translate(Meta, FRef, Block, MS), FS };
|
||||
|
||||
translate({Kind, Meta, [Call]}, S) when ?defs(Kind) ->
|
||||
translate({Kind, Meta, [Call, nil]}, S);
|
||||
|
||||
translate({Kind, Meta, [Call, Expr]}, S) when ?defs(Kind) ->
|
||||
assert_module_scope(Meta, Kind, S),
|
||||
assert_no_function_scope(Meta, Kind, S),
|
||||
{ TCall, QC, SC } = elixir_quote:erl_escape(Call, true, S),
|
||||
{ TExpr, QE, SE } = elixir_quote:erl_escape(Expr, true, SC),
|
||||
CheckClauses = (not lists:keymember(context, 1, Meta)) andalso
|
||||
(not QC#elixir_quote.unquoted) andalso (not QE#elixir_quote.unquoted),
|
||||
{ elixir_def:wrap_definition(Kind, Meta, TCall, TExpr, CheckClauses, SE), SE };
|
||||
|
||||
translate({ Name, Meta, Args }, S) ->
|
||||
syntax_error(Meta, S#elixir_scope.file,
|
||||
"invalid arguments for macro ~ts/~B", [Name, length(Args)]).
|
||||
|
||||
%% Helpers
|
||||
|
||||
translate_in(Meta, Left, Right, S) ->
|
||||
Line = ?line(Meta),
|
||||
|
||||
{ TLeft, SL } = translate_each(Left, S),
|
||||
{ TRight, SR } = translate_each(Right, SL),
|
||||
|
||||
Cache = (S#elixir_scope.context == nil),
|
||||
|
||||
{ Var, SV } = case Cache of
|
||||
true -> elixir_scope:build_erl_var(Line, SR);
|
||||
false -> { TLeft, SR }
|
||||
end,
|
||||
|
||||
{ TCache, TExpr } = case TRight of
|
||||
{ nil, _ } ->
|
||||
Expr = { atom, Line, false },
|
||||
{ Cache, Expr };
|
||||
{ cons, _, _, _ } ->
|
||||
[H|T] = elixir_utils:cons_to_list(TRight),
|
||||
Expr = lists:foldr(fun(X, Acc) ->
|
||||
{ op, Line, 'orelse', { op, Line, '=:=', Var, X }, Acc }
|
||||
end, { op, Line, '=:=', Var, H }, T),
|
||||
{ Cache, Expr };
|
||||
{ string, _, [H|T] } ->
|
||||
Expr = lists:foldl(fun(X, Acc) ->
|
||||
{ op, Line, 'orelse', { op, Line, '=:=', Var, { integer, Line, X } }, Acc }
|
||||
end, { op, Line, '=:=', Var, { integer, Line, H } }, T),
|
||||
{ Cache, Expr };
|
||||
{ tuple, _, [{ atom, _, 'Elixir.Range' }, Start, End] } ->
|
||||
Expr = case { Start, End } of
|
||||
{ { K1, _, StartInt }, { K2, _, EndInt } } when ?opt_in_types(K1), ?opt_in_types(K2), StartInt =< EndInt ->
|
||||
increasing_compare(Line, Var, Start, End);
|
||||
{ { K1, _, _ }, { K2, _, _ } } when ?opt_in_types(K1), ?opt_in_types(K2) ->
|
||||
decreasing_compare(Line, Var, Start, End);
|
||||
_ ->
|
||||
{ op, Line, 'orelse',
|
||||
{ op, Line, 'andalso',
|
||||
{ op, Line, '=<', Start, End},
|
||||
increasing_compare(Line, Var, Start, End) },
|
||||
{ op, Line, 'andalso',
|
||||
{ op, Line, '<', End, Start},
|
||||
decreasing_compare(Line, Var, Start, End) } }
|
||||
end,
|
||||
{ Cache, Expr };
|
||||
_ ->
|
||||
case Cache of
|
||||
true ->
|
||||
{ false, ?wrap_call(Line, 'Elixir.Enum', 'member?', [TRight, TLeft]) };
|
||||
false ->
|
||||
compile_error(Meta, S#elixir_scope.file, "invalid args for operator in, it expects a "
|
||||
"compile time list or range on the right side when used in guard expressions")
|
||||
end
|
||||
end,
|
||||
|
||||
case TCache of
|
||||
true -> { { block, Line, [ { match, Line, Var, TLeft }, TExpr ] }, SV };
|
||||
false -> { TExpr, SV }
|
||||
end.
|
||||
|
||||
increasing_compare(Line, Var, Start, End) ->
|
||||
{ op, Line, 'andalso',
|
||||
{ op, Line, '>=', Var, Start },
|
||||
{ op, Line, '=<', Var, End } }.
|
||||
|
||||
decreasing_compare(Line, Var, Start, End) ->
|
||||
{ op, Line, 'andalso',
|
||||
{ op, Line, '=<', Var, Start },
|
||||
{ op, Line, '>=', Var, End } }.
|
||||
|
||||
%% TODO: Once we have elixir_exp, we can move this
|
||||
%% clause to Elixir code and out of case.
|
||||
rewrite_case_clauses([
|
||||
{do,Meta1,[{'when',_,[{V,M,C},{in,_,[{V,M,C},[false,nil]]}]}],False},
|
||||
{do,Meta2,[{'_',_,UC}],True}] = Clauses)
|
||||
when is_atom(V), is_list(M), is_atom(C), is_atom(UC) ->
|
||||
case lists:keyfind('cond', 1, M) of
|
||||
{ 'cond', true } ->
|
||||
[{do,Meta1,[false],False},{do,Meta2,[true],True}];
|
||||
_ ->
|
||||
Clauses
|
||||
end;
|
||||
|
||||
rewrite_case_clauses(Clauses) ->
|
||||
Clauses.
|
||||
|
||||
%% defmodule :foo
|
||||
expand_module(Raw, _Module, _S) when is_atom(Raw) ->
|
||||
Raw;
|
||||
|
||||
%% defmodule Hello
|
||||
expand_module({ '__aliases__', _, [H] }, _Module, S) ->
|
||||
elixir_aliases:concat([S#elixir_scope.module, H]);
|
||||
|
||||
%% defmodule Hello.World
|
||||
expand_module({ '__aliases__', _, _ } = Alias, Module, S) ->
|
||||
case elixir_aliases:expand(Alias, S#elixir_scope.aliases, S#elixir_scope.macro_aliases,
|
||||
S#elixir_scope.lexical_tracker) of
|
||||
Atom when is_atom(Atom) ->
|
||||
Module;
|
||||
Aliases when is_list(Aliases) ->
|
||||
elixir_aliases:concat([S#elixir_scope.module, Module])
|
||||
end;
|
||||
|
||||
%% defmodule Elixir.Hello.World
|
||||
expand_module(_Raw, Module, S) ->
|
||||
elixir_aliases:concat([S#elixir_scope.module, Module]).
|
||||
|
||||
is_reserved_data(moduledoc) -> true;
|
||||
is_reserved_data(typedoc) -> true;
|
||||
is_reserved_data(doc) -> true;
|
||||
is_reserved_data(_) -> false.
|
||||
|
||||
spec_to_macro(type) -> deftype;
|
||||
spec_to_macro(typep) -> deftypep;
|
||||
spec_to_macro(opaque) -> defopaque;
|
||||
spec_to_macro(spec) -> defspec;
|
||||
spec_to_macro(callback) -> defcallback.
|
||||
|
||||
% Pack a list of expressions from a block.
|
||||
pack({ 'block', _, Exprs }) -> Exprs;
|
||||
pack(Expr) -> [Expr].
|
||||
@@ -1,6 +1,6 @@
|
||||
-module(elixir_module).
|
||||
-export([translate/4, compile/5, data_table/1, docs_table/1,
|
||||
eval_quoted/4, format_error/1, eval_callbacks/5]).
|
||||
-export([compile/4, data_table/1, docs_table/1,
|
||||
format_error/1, eval_callbacks/5]).
|
||||
-include("elixir.hrl").
|
||||
|
||||
-define(acc_attr, '__acc_attributes').
|
||||
@@ -9,25 +9,6 @@
|
||||
-define(persisted_attr, '__persisted_attributes').
|
||||
-define(overridable_attr, '__overridable').
|
||||
|
||||
eval_quoted(Module, Quoted, RawBinding, Opts) ->
|
||||
Binding = orddict:store('_@MODULE', Module, RawBinding),
|
||||
Scope = scope_for_eval(Module, Opts),
|
||||
|
||||
elixir_def:reset_last(Module),
|
||||
|
||||
case lists:keyfind(line, 1, Opts) of
|
||||
{ line, Line } -> Line;
|
||||
false -> Line = 1
|
||||
end,
|
||||
|
||||
{ Value, FinalBinding, _Scope } = elixir:eval_quoted([Quoted], Binding, Line, Scope),
|
||||
{ Value, FinalBinding }.
|
||||
|
||||
scope_for_eval(Module, #elixir_scope{} = S) ->
|
||||
S#elixir_scope{module=Module};
|
||||
scope_for_eval(Module, Opts) ->
|
||||
scope_for_eval(Module, elixir:scope_for_eval(Opts)).
|
||||
|
||||
%% TABLE METHODS
|
||||
|
||||
data_table(Module) ->
|
||||
@@ -36,82 +17,73 @@ data_table(Module) ->
|
||||
docs_table(Module) ->
|
||||
ets:lookup_element(Module, ?docs_attr, 2).
|
||||
|
||||
%% TRANSFORMATION FUNCTIONS
|
||||
%% Compilation hook
|
||||
|
||||
%% Transformation of args and scope into a compiled erlang call.
|
||||
%% The abstract form for extra arguments may be given and they
|
||||
%% will be passed to the invoked function.
|
||||
|
||||
translate(Meta, Ref, Block, S) ->
|
||||
Line = ?line(Meta),
|
||||
LexS = case S#elixir_scope.function of
|
||||
nil -> S;
|
||||
_ -> S#elixir_scope{lexical_tracker=nil}
|
||||
compile(Module, Block, Vars, #elixir_env{line=Line} = Env) when is_atom(Module) ->
|
||||
%% In case we are generating a module from inside a function,
|
||||
%% we get rid of the lexical tracker information as, at this
|
||||
%% point, the lexical tracker process is long gone.
|
||||
LexEnv = case Env#elixir_env.function of
|
||||
nil -> Env#elixir_env{module=Module, local=nil};
|
||||
_ -> Env#elixir_env{lexical_tracker=nil, function=nil, module=Module, local=nil}
|
||||
end,
|
||||
|
||||
MetaBlock = elixir_utils:elixir_to_erl(Line, Block, LexS),
|
||||
{ MetaS, Vars } = elixir_scope:serialize_with_vars(Line, LexS),
|
||||
case LexEnv#elixir_env.lexical_tracker of
|
||||
nil ->
|
||||
elixir_lexical:run(LexEnv#elixir_env.file, fun(Pid) ->
|
||||
do_compile(Line, Module, Block, Vars, LexEnv#elixir_env{lexical_tracker=Pid})
|
||||
end);
|
||||
_ ->
|
||||
do_compile(Line, Module, Block, Vars, LexEnv)
|
||||
end;
|
||||
|
||||
Args = [{integer, Line, Line}, Ref, MetaBlock, Vars, MetaS],
|
||||
?wrap_call(Line, ?MODULE, compile, Args).
|
||||
compile(Module, _Block, _Vars, #elixir_env{line=Line,file=File}) ->
|
||||
elixir_errors:form_error(Line, File, ?MODULE, { invalid_module, Module });
|
||||
|
||||
%% The compilation hook.
|
||||
compile(Module, Block, Vars, ExEnv) ->
|
||||
compile(Module, Block, Vars, elixir_env:ex_to_env(ExEnv)).
|
||||
|
||||
compile(Line, Module, Block, Vars, #elixir_scope{context_modules=FileModules} = RawS) when is_atom(Module) ->
|
||||
C = elixir_compiler:get_opts(),
|
||||
S = case lists:member(Module, FileModules) of
|
||||
true -> RawS;
|
||||
false -> RawS#elixir_scope{context_modules=[Module|FileModules]}
|
||||
end,
|
||||
|
||||
File = S#elixir_scope.file,
|
||||
FileList = elixir_utils:characters_to_list(File),
|
||||
|
||||
check_module_availability(Line, File, Module, C),
|
||||
build(Line, File, Module, S#elixir_scope.lexical_tracker),
|
||||
do_compile(Line, Module, Block, Vars, E) ->
|
||||
File = E#elixir_env.file,
|
||||
check_module_availability(Line, File, Module),
|
||||
build(Line, File, Module, E#elixir_env.lexical_tracker),
|
||||
|
||||
try
|
||||
Result = eval_form(Line, Module, Block, Vars, S),
|
||||
{ Base, Export, Private, Def, Defmacro, Functions } = elixir_def:unwrap_stored_definitions(FileList, Module),
|
||||
{ Result, NE } = eval_form(Line, Module, Block, Vars, E),
|
||||
{ Base, Export, Private, Def, Defmacro, Functions } = elixir_def:unwrap_definitions(Module),
|
||||
|
||||
{ All, Forms0 } = functions_form(Line, File, Module, Base, Export, Def, Defmacro, Functions, C),
|
||||
Forms1 = specs_form(Module, Private, Defmacro, Forms0, C),
|
||||
{ All, Forms0 } = functions_form(Line, File, Module, Base, Export, Def, Defmacro, Functions),
|
||||
Forms1 = specs_form(Module, Private, Defmacro, Forms0),
|
||||
Forms2 = attributes_form(Line, File, Module, Forms1),
|
||||
Forms3 = typedocs_form(Module, Forms2),
|
||||
|
||||
case ets:lookup(data_table(Module), 'on_load') of
|
||||
[] -> ok;
|
||||
[{on_load,OnLoad}] ->
|
||||
[elixir_tracker:record_local(Tuple, Module) || Tuple <- OnLoad]
|
||||
[elixir_locals:record_local(Tuple, Module) || Tuple <- OnLoad]
|
||||
end,
|
||||
|
||||
AllFunctions = Def ++ [T || { T, defp, _, _, _ } <- Private],
|
||||
elixir_tracker:ensure_no_function_conflict(Line, File, Module, AllFunctions),
|
||||
elixir_tracker:warn_unused_local(File, Module, Private),
|
||||
elixir_locals:ensure_no_function_conflict(Line, File, Module, AllFunctions),
|
||||
elixir_locals:warn_unused_local(File, Module, Private),
|
||||
warn_invalid_clauses(Line, File, Module, All),
|
||||
warn_unused_docs(Line, File, Module),
|
||||
|
||||
Location = { elixir_utils:relative_to_cwd(elixir_utils:characters_to_list(File)), Line },
|
||||
|
||||
Final = [
|
||||
{ attribute, Line, file, { FileList, Line } },
|
||||
{ attribute, Line, file, Location },
|
||||
{ attribute, Line, module, Module } | Forms3
|
||||
],
|
||||
|
||||
Binary = load_form(Line, Final, compile_opts(Module), S),
|
||||
Binary = load_form(Line, Final, compile_opts(Module), NE),
|
||||
{ module, Module, Binary, Result }
|
||||
after
|
||||
elixir_tracker:cleanup(Module),
|
||||
elixir_locals:cleanup(Module),
|
||||
elixir_def:cleanup(Module),
|
||||
ets:delete(docs_table(Module)),
|
||||
ets:delete(data_table(Module))
|
||||
end;
|
||||
|
||||
compile(Line, Other, _Block, _Vars, #elixir_scope{file=File}) ->
|
||||
elixir_errors:form_error(Line, File, ?MODULE, { invalid_module, Other });
|
||||
|
||||
compile(Line, Module, Block, Vars, RawS) ->
|
||||
Dict = [{ { Name, Kind }, Value } || { Name, Kind, Value, _ } <- Vars],
|
||||
S = elixir_scope:deserialize_with_vars(RawS, Dict),
|
||||
compile(Line, Module, Block, Vars, S).
|
||||
end.
|
||||
|
||||
%% Hook that builds both attribute and functions and set up common hooks.
|
||||
|
||||
@@ -133,7 +105,7 @@ build(Line, File, Module, Lexical) ->
|
||||
_ -> ets:insert(DataTable, { on_definition, [] })
|
||||
end,
|
||||
|
||||
Attributes = [behavior, behaviour, on_load, spec, type, export_type, opaque, callback, compile],
|
||||
Attributes = [behaviour, on_load, spec, type, export_type, opaque, callback, compile],
|
||||
ets:insert(DataTable, { ?acc_attr, [before_compile, after_compile, on_definition|Attributes] }),
|
||||
ets:insert(DataTable, { ?persisted_attr, [vsn|Attributes] }),
|
||||
ets:insert(DataTable, { ?docs_attr, ets:new(DataTable, [ordered_set, public]) }),
|
||||
@@ -142,28 +114,26 @@ build(Line, File, Module, Lexical) ->
|
||||
|
||||
%% Setup other modules
|
||||
elixir_def:setup(Module),
|
||||
elixir_tracker:setup(Module).
|
||||
elixir_locals:setup(Module).
|
||||
|
||||
%% Receives the module representation and evaluates it.
|
||||
|
||||
eval_form(Line, Module, Block, Vars, RawS) ->
|
||||
S = scope_for_eval(Module, RawS),
|
||||
{ Value, NewS } = elixir_compiler:eval_forms([Block], Line, Vars, S),
|
||||
eval_form(Line, Module, Block, Vars, E) ->
|
||||
{ Value, EE } = elixir_compiler:eval_forms(Block, Vars, E),
|
||||
elixir_def_overridable:store_pending(Module),
|
||||
Env = elixir_scope:to_ex_env({ Line, S }),
|
||||
eval_callbacks(Line, Module, before_compile, [Env], NewS),
|
||||
EC = eval_callbacks(Line, Module, before_compile, [elixir_env:env_to_ex({ Line, EE })], EE),
|
||||
elixir_def_overridable:store_pending(Module),
|
||||
Value.
|
||||
{ Value, EC }.
|
||||
|
||||
%% Return the form with exports and function declarations.
|
||||
|
||||
functions_form(Line, File, Module, BaseAll, BaseExport, Def, Defmacro, RawFunctions, C) ->
|
||||
BaseFunctions = case elixir_compiler:get_opt(internal, C) of
|
||||
functions_form(Line, File, Module, BaseAll, BaseExport, Def, Defmacro, RawFunctions) ->
|
||||
BaseFunctions = case elixir_compiler:get_opt(internal) of
|
||||
true -> RawFunctions;
|
||||
false -> record_rewrite_functions(Module, RawFunctions)
|
||||
end,
|
||||
|
||||
Info = add_info_function(Line, File, Module, BaseExport, Def, Defmacro, C),
|
||||
Info = add_info_function(Line, File, Module, BaseExport, Def, Defmacro),
|
||||
|
||||
All = [{ '__info__', 1 }|BaseAll],
|
||||
Export = [{ '__info__', 1 }|BaseExport],
|
||||
@@ -220,11 +190,10 @@ typedocs_form(Module, Current) ->
|
||||
|
||||
%% Specs
|
||||
|
||||
specs_form(Module, Private, Defmacro, Forms, C) ->
|
||||
specs_form(Module, Private, Defmacro, Forms) ->
|
||||
Defmacrop = [Tuple || { Tuple, defmacrop, _, _, _ } <- Private],
|
||||
case elixir_compiler:get_opt(internal, C) of
|
||||
true -> Forms;
|
||||
_ ->
|
||||
case code:ensure_loaded('Elixir.Kernel.Typespec') of
|
||||
{ module, 'Elixir.Kernel.Typespec' } ->
|
||||
Callbacks = 'Elixir.Module':get_attribute(Module, callback),
|
||||
Specs = [translate_spec(Spec, Defmacro, Defmacrop) ||
|
||||
Spec <- 'Elixir.Module':get_attribute(Module, spec)],
|
||||
@@ -233,7 +202,9 @@ specs_form(Module, Private, Defmacro, Forms, C) ->
|
||||
'Elixir.Module':delete_attribute(Module, callback),
|
||||
|
||||
Temp = specs_attributes(spec, Forms, Specs),
|
||||
specs_attributes(callback, Temp, Callbacks)
|
||||
specs_attributes(callback, Temp, Callbacks);
|
||||
{ error, _ } ->
|
||||
Forms
|
||||
end.
|
||||
|
||||
specs_attributes(Type, Forms, Specs) ->
|
||||
@@ -273,11 +244,10 @@ compile_opts(Module) ->
|
||||
[] -> []
|
||||
end.
|
||||
|
||||
load_form(Line, Forms, Opts, #elixir_scope{file=File} = S) ->
|
||||
load_form(Line, Forms, Opts, #elixir_env{file=File} = E) ->
|
||||
elixir_compiler:module(Forms, File, Opts, fun(Module, Binary) ->
|
||||
EvalS = scope_for_eval(Module, S),
|
||||
Env = elixir_scope:to_ex_env({ Line, EvalS }),
|
||||
eval_callbacks(Line, Module, after_compile, [Env, Binary], EvalS),
|
||||
Env = elixir_env:env_to_ex({ Line, E }),
|
||||
eval_callbacks(Line, Module, after_compile, [Env, Binary], E),
|
||||
|
||||
case get(elixir_compiled) of
|
||||
Current when is_list(Current) ->
|
||||
@@ -297,8 +267,8 @@ load_form(Line, Forms, Opts, #elixir_scope{file=File} = S) ->
|
||||
Binary
|
||||
end).
|
||||
|
||||
check_module_availability(Line, File, Module, Compiler) ->
|
||||
case elixir_compiler:get_opt(ignore_module_conflict, Compiler) of
|
||||
check_module_availability(Line, File, Module) ->
|
||||
case elixir_compiler:get_opt(ignore_module_conflict) of
|
||||
false ->
|
||||
case code:ensure_loaded(Module) of
|
||||
{ module, _ } ->
|
||||
@@ -310,7 +280,6 @@ check_module_availability(Line, File, Module, Compiler) ->
|
||||
[]
|
||||
end.
|
||||
|
||||
warn_invalid_clauses(_Line, _File, 'Elixir.Kernel', _All) -> ok;
|
||||
warn_invalid_clauses(_Line, _File, 'Elixir.Kernel.SpecialForms', _All) -> ok;
|
||||
warn_invalid_clauses(_Line, File, Module, All) ->
|
||||
ets:foldl(fun
|
||||
@@ -337,16 +306,16 @@ warn_unused_docs(Line, File, Module) ->
|
||||
|
||||
% EXTRA FUNCTIONS
|
||||
|
||||
add_info_function(Line, File, Module, Export, Def, Defmacro, C) ->
|
||||
add_info_function(Line, File, Module, Export, Def, Defmacro) ->
|
||||
Pair = { '__info__', 1 },
|
||||
case lists:member(Pair, Export) of
|
||||
true ->
|
||||
elixir_errors:form_error(Line, File, ?MODULE, {internal_function_overridden, Pair});
|
||||
false ->
|
||||
Docs = elixir_compiler:get_opt(docs, C),
|
||||
Docs = elixir_compiler:get_opt(docs),
|
||||
{ function, 0, '__info__', 1, [
|
||||
functions_clause(Def),
|
||||
macros_clause(Module, Def, Defmacro),
|
||||
macros_clause(Defmacro),
|
||||
docs_clause(Module, Docs),
|
||||
moduledoc_clause(Line, Module, Docs),
|
||||
module_clause(Module),
|
||||
@@ -357,14 +326,8 @@ add_info_function(Line, File, Module, Export, Def, Defmacro, C) ->
|
||||
functions_clause(Def) ->
|
||||
{ clause, 0, [{ atom, 0, functions }], [], [elixir_utils:elixir_to_erl(Def)] }.
|
||||
|
||||
macros_clause(Module, Def, Defmacro) ->
|
||||
All = handle_builtin_macros(Module, Def, Defmacro),
|
||||
{ clause, 0, [{ atom, 0, macros }], [], [elixir_utils:elixir_to_erl(All)] }.
|
||||
|
||||
handle_builtin_macros('Elixir.Kernel', Def, Defmacro) ->
|
||||
ordsets:subtract(ordsets:union(Defmacro,
|
||||
elixir_dispatch:in_erlang_macros()), Def);
|
||||
handle_builtin_macros(_, _Def, Defmacro) -> Defmacro.
|
||||
macros_clause(Defmacro) ->
|
||||
{ clause, 0, [{ atom, 0, macros }], [], [elixir_utils:elixir_to_erl(Defmacro)] }.
|
||||
|
||||
module_clause(Module) ->
|
||||
{ clause, 0, [{ atom, 0, module }], [], [{ atom, 0, Module }] }.
|
||||
@@ -392,41 +355,41 @@ else_clause() ->
|
||||
|
||||
% HELPERS
|
||||
|
||||
eval_callbacks(Line, Module, Name, Args, RawS) ->
|
||||
{ Binding, S } = elixir_scope:load_binding([], RawS, Module),
|
||||
eval_callbacks(Line, Module, Name, Args, E) ->
|
||||
Callbacks = lists:reverse(ets:lookup_element(data_table(Module), Name, 2)),
|
||||
Meta = [{line,Line},{require,false}],
|
||||
|
||||
lists:foreach(fun({M,F}) ->
|
||||
{ Tree, _ } = elixir_dispatch:dispatch_require(Meta, M, F, Args, S, fun() ->
|
||||
apply(M, F, Args),
|
||||
{ { atom, 0, nil }, S }
|
||||
lists:foldl(fun({M,F}, Acc) ->
|
||||
{ Expr, ET } = elixir_dispatch:dispatch_require(Meta, M, F, Args, Acc, fun(AM, AF, AA) ->
|
||||
apply(AM, AF, AA),
|
||||
{ nil, Acc }
|
||||
end),
|
||||
|
||||
case Tree of
|
||||
{ atom, _, Atom } ->
|
||||
Atom;
|
||||
_ ->
|
||||
if
|
||||
is_atom(Expr) ->
|
||||
ET;
|
||||
true ->
|
||||
try
|
||||
erl_eval:exprs([Tree], Binding)
|
||||
{ _Value, _Binding, EE, _S } = elixir:eval_forms(Expr, [], ET),
|
||||
EE
|
||||
catch
|
||||
Kind:Reason ->
|
||||
Info = { M, F, length(Args), [{ file, elixir_utils:characters_to_list(S#elixir_scope.file) }, { line, Line }] },
|
||||
Info = { M, F, length(Args), location(Line, E) },
|
||||
erlang:raise(Kind, Reason, prune_stacktrace(Info, erlang:get_stacktrace()))
|
||||
end
|
||||
end
|
||||
end, Callbacks).
|
||||
end, E, Callbacks).
|
||||
|
||||
%% We've reached the elixir_module or erl_eval internals, skip it with the rest
|
||||
prune_stacktrace(Info, [{ erl_eval, _, _, _ }|_]) ->
|
||||
location(Line, E) ->
|
||||
[{ file, elixir_utils:characters_to_list(E#elixir_env.file) }, { line, Line }].
|
||||
|
||||
%% We've reached the elixir_module or eval internals, skip it with the rest
|
||||
prune_stacktrace(Info, [{ elixir, eval_forms, _, _ }|_]) ->
|
||||
[Info];
|
||||
|
||||
prune_stacktrace(Info, [{ elixir_module, _, _, _ }|_]) ->
|
||||
[Info];
|
||||
|
||||
prune_stacktrace(Info, [H|T]) ->
|
||||
[H|prune_stacktrace(Info, T)];
|
||||
|
||||
prune_stacktrace(Info, []) ->
|
||||
[Info].
|
||||
|
||||
@@ -434,22 +397,16 @@ prune_stacktrace(Info, []) ->
|
||||
|
||||
format_error({ invalid_clause, { Name, Arity } }) ->
|
||||
io_lib:format("empty clause provided for nonexistent function or macro ~ts/~B", [Name, Arity]);
|
||||
|
||||
format_error({ unused_doc, typedoc }) ->
|
||||
"@typedoc provided but no type follows it";
|
||||
|
||||
format_error({ unused_doc, doc }) ->
|
||||
"@doc provided but no definition follows it";
|
||||
|
||||
format_error({ internal_function_overridden, { Name, Arity } }) ->
|
||||
io_lib:format("function ~ts/~B is internal and should not be overridden", [Name, Arity]);
|
||||
|
||||
format_error({ invalid_module, Module}) ->
|
||||
io_lib:format("invalid module name: ~p", [Module]);
|
||||
|
||||
format_error({ module_defined, Module }) ->
|
||||
io_lib:format("redefining module ~ts", [elixir_errors:inspect(Module)]);
|
||||
|
||||
io_lib:format("redefining module ~ts", [elixir_aliases:inspect(Module)]);
|
||||
format_error({ module_in_definition, Module }) ->
|
||||
io_lib:format("cannot define module ~ts because it is currently being defined",
|
||||
[elixir_errors:inspect(Module)]).
|
||||
[elixir_aliases:inspect(Module)]).
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user