Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
83591b9d76 | ||
|
|
b0bc02ef70 | ||
|
|
a7ffebc915 | ||
|
|
40653e433b | ||
|
|
c0b007af9a | ||
|
|
2fee17e709 | ||
|
|
ebb5500086 | ||
|
|
9754f056dd | ||
|
|
e19dbf04ed | ||
|
|
c654159a2b | ||
|
|
6715c528df | ||
|
|
1c7a37a2b7 | ||
|
|
84a0985a39 | ||
|
|
73b9ac7449 | ||
|
|
8e659930c0 | ||
|
|
095a91a31f | ||
|
|
7393973d96 | ||
|
|
c3c8c3c71d | ||
|
|
73eeec2eec | ||
|
|
c0bb6d73ae | ||
|
|
82da86afcb | ||
|
|
c1a7c039fe | ||
|
|
5b754c8630 | ||
|
|
d195429470 | ||
|
|
6cc9a4b9f4 | ||
|
|
08aa22616a | ||
|
|
e4ddfc2118 | ||
|
|
4955eb9deb | ||
|
|
befa36ce1e | ||
|
|
5e8f9284d2 | ||
|
|
c0a6dc4b47 | ||
|
|
99da29b430 | ||
|
|
3c3c989ef7 | ||
|
|
977ec32476 | ||
|
|
27b4ebe34e | ||
|
|
b0f9d7e9bc | ||
|
|
f9afc520ca | ||
|
|
a6589c62a4 | ||
|
|
77155c55a6 | ||
|
|
776b07f2f0 | ||
|
|
1f00e01679 | ||
|
|
a7fa43338e | ||
|
|
394ccb3f42 | ||
|
|
e7c0a2a3a7 | ||
|
|
f808da8ffb | ||
|
|
c0c8f7f72b | ||
|
|
290403f7d7 | ||
|
|
967ca97455 | ||
|
|
ba00a1ffc6 | ||
|
|
a209dc72e3 | ||
|
|
b6ea6b63ed | ||
|
|
df4cee50d5 | ||
|
|
318eaebf3f | ||
|
|
0a182b4590 | ||
|
|
88ac1f6a7b | ||
|
|
621db32ffb | ||
|
|
5b2f44d1d7 | ||
|
|
ad3cf4e451 | ||
|
|
209c88ab0b | ||
|
|
3c3cf143e7 | ||
|
|
45454ebaf0 | ||
|
|
e321550127 | ||
|
|
2dc1e41aca | ||
|
|
74c82f8590 | ||
|
|
63cfe89096 | ||
|
|
5e5bf1ddb1 | ||
|
|
262a47d6ee | ||
|
|
f8dfc485ea | ||
|
|
52ab64d60b | ||
|
|
3de7f42155 | ||
|
|
3cf4c21ad1 | ||
|
|
802b0d11a6 | ||
|
|
67443d3691 | ||
|
|
85371e4e45 | ||
|
|
2da5326549 | ||
|
|
a83547723e | ||
|
|
879651cba9 | ||
|
|
cc3634ed37 | ||
|
|
5a5ab18a57 | ||
|
|
8fca8a965b | ||
|
|
0beb65298d | ||
|
|
218552af3f | ||
|
|
a2c6630793 | ||
|
|
8c3c0a9149 | ||
|
|
b1181320d3 | ||
|
|
9742c1c17d | ||
|
|
eb4dda0b1e | ||
|
|
bde91c325c | ||
|
|
7c2f25fc85 | ||
|
|
d82f315fa7 | ||
|
|
fc505f8a82 | ||
|
|
241cd1747d | ||
|
|
dd6fb4acc2 | ||
|
|
a056f0ac40 | ||
|
|
b830485541 | ||
|
|
15d347fb96 | ||
|
|
c11d50e451 | ||
|
|
64e6037986 | ||
|
|
8a9927ea1b | ||
|
|
889de92187 | ||
|
|
033fb6a5e9 | ||
|
|
7ad7847596 | ||
|
|
9dcfc53f60 | ||
|
|
26f9ce121a | ||
|
|
0c557d2766 | ||
|
|
e13a516715 | ||
|
|
accf7dac82 | ||
|
|
b3a65eaca1 | ||
|
|
3e29d9fa2e | ||
|
|
0edf1882ff | ||
|
|
35d019559d | ||
|
|
7048236988 | ||
|
|
b335b1663d | ||
|
|
f69ecfe8aa | ||
|
|
10a456823c | ||
|
|
a26d4cf0cd | ||
|
|
3f11eef086 | ||
|
|
ad5885ba2e | ||
|
|
a42725910f | ||
|
|
a6f8e178c0 | ||
|
|
7c2a661a5f | ||
|
|
15af0356a2 | ||
|
|
ebe522ec50 | ||
|
|
6d491325d5 | ||
|
|
f697ff4160 | ||
|
|
4ae58d3bf4 | ||
|
|
650648a687 | ||
|
|
b561c93db2 | ||
|
|
15c1a9891c | ||
|
|
10697628f9 | ||
|
|
89da6e032d | ||
|
|
e969a330d5 | ||
|
|
0f4598fca0 | ||
|
|
cb5bb2e548 | ||
|
|
acf912c2c8 | ||
|
|
6b82967dd6 | ||
|
|
789d01ae2a | ||
|
|
531434959f | ||
|
|
d9baf3c39e | ||
|
|
91be13d128 | ||
|
|
62527cae9c | ||
|
|
466c2b2e1b | ||
|
|
2ba6238dd1 | ||
|
|
48cef0c65b | ||
|
|
3d208e219d | ||
|
|
f8fd11bef0 | ||
|
|
9703c9968b | ||
|
|
82f5868257 | ||
|
|
3ab74433c0 | ||
|
|
31dec377f0 | ||
|
|
a6db102d05 | ||
|
|
628d1b630e | ||
|
|
fbd7c34211 | ||
|
|
25f5546d6f | ||
|
|
288925c2fa | ||
|
|
4a0b1aa384 | ||
|
|
ee1b87089d | ||
|
|
074db53fbb | ||
|
|
60693e48e5 | ||
|
|
e49a00d3cb | ||
|
|
1fe756a2db | ||
|
|
1da36c5b04 | ||
|
|
7a2aea160c | ||
|
|
2837ce381b | ||
|
|
3ae417cbb2 | ||
|
|
12c5cf0515 | ||
|
|
26eafd1a4c | ||
|
|
cdd8e5862d | ||
|
|
eee1993cdd | ||
|
|
46e898e413 | ||
|
|
82e1c9337c | ||
|
|
3bac4ddec6 | ||
|
|
0703be3863 | ||
|
|
d087686f3d | ||
|
|
595d343e05 | ||
|
|
dce2bee57e | ||
|
|
d512f545f4 | ||
|
|
1bdefbb3d1 | ||
|
|
0e4bef3ac8 | ||
|
|
c5121feac5 | ||
|
|
08f592610e | ||
|
|
664d1ec0d3 | ||
|
|
8ca28496e4 | ||
|
|
c5cd8c3fcd | ||
|
|
cf98f9b975 | ||
|
|
26e6fdbeb2 | ||
|
|
35edf833b6 | ||
|
|
1a343b01d6 | ||
|
|
bc76627feb | ||
|
|
0e0d93076c | ||
|
|
c04dcc1882 | ||
|
|
5822cd153f | ||
|
|
65c624ef55 | ||
|
|
6c56bb8682 | ||
|
|
b666c696b4 | ||
|
|
1f8c8f5292 | ||
|
|
03323b9bfa | ||
|
|
8195662b98 | ||
|
|
d6c0533395 | ||
|
|
474d4f6532 | ||
|
|
e548807605 | ||
|
|
25f82dcb46 | ||
|
|
596670ab9c | ||
|
|
c20ee8f7e9 | ||
|
|
3e13f77d1b | ||
|
|
76be461964 | ||
|
|
31431fa795 | ||
|
|
3bf1b08af3 | ||
|
|
f1baed6434 | ||
|
|
e9aa9b213b | ||
|
|
93b3c6fc3d | ||
|
|
17eb2c77c8 | ||
|
|
1b043c36bb | ||
|
|
cda2986a63 | ||
|
|
9bc67bbfdf | ||
|
|
83c964ae46 | ||
|
|
1b2d8bb915 | ||
|
|
34f0f93e8f | ||
|
|
e6cc35fbbd | ||
|
|
6e9b8fb454 | ||
|
|
83173698da | ||
|
|
72b7b8ebe2 | ||
|
|
358e49378c | ||
|
|
e8bd90a1c8 | ||
|
|
096711b517 | ||
|
|
d599e76366 | ||
|
|
1c16ebe2ff | ||
|
|
eb813f71d6 | ||
|
|
ce683fc10e | ||
|
|
7dd8fa4c8d | ||
|
|
fd7e038927 | ||
|
|
f3a0a52c10 | ||
|
|
b7018f77f3 | ||
|
|
01230d68c7 | ||
|
|
e1bca3c65f | ||
|
|
f726b6bbdf | ||
|
|
00e9098d19 | ||
|
|
7b7c691196 | ||
|
|
94d714bb2f | ||
|
|
1be0a5329b | ||
|
|
73fdf7442b | ||
|
|
5598eb68d0 | ||
|
|
99a03ffcf9 | ||
|
|
07e8a7318c | ||
|
|
b7dac7e24a | ||
|
|
9b01bae463 | ||
|
|
ca4997e7f4 | ||
|
|
4c1ee4e261 | ||
|
|
cacb7d5997 | ||
|
|
4b502985d4 | ||
|
|
48ecb7d6f0 | ||
|
|
023866b2b0 | ||
|
|
72c3432042 | ||
|
|
d7facd11df | ||
|
|
76b8b9f3a9 | ||
|
|
20f26909c8 | ||
|
|
276eee072d | ||
|
|
37621d4a07 | ||
|
|
a768da2980 | ||
|
|
8e5f14dc74 | ||
|
|
c832d3a78e | ||
|
|
c052558cbf | ||
|
|
a0d225f891 | ||
|
|
45862f8472 | ||
|
|
dbe2851cdb | ||
|
|
33d6780bb1 | ||
|
|
8924fa4aee | ||
|
|
e4812ec185 | ||
|
|
ea32d951ab | ||
|
|
598f0e0141 | ||
|
|
d484499917 | ||
|
|
00a6753497 | ||
|
|
edb5016112 | ||
|
|
672dc8c595 | ||
|
|
f203560832 | ||
|
|
e3d086abb5 | ||
|
|
74ea72246e | ||
|
|
447a57750f | ||
|
|
e1c3df3bef | ||
|
|
4c5cb1bc7a | ||
|
|
81d36cd921 | ||
|
|
88e4b5833f | ||
|
|
d5323e3311 | ||
|
|
1e73ea0527 | ||
|
|
dc3ca821fb | ||
|
|
c9fe36c28b | ||
|
|
7ba5733496 | ||
|
|
e035dda2db | ||
|
|
8e8ebe73b9 | ||
|
|
30e56696c9 | ||
|
|
c817e6a078 | ||
|
|
c8a04ac0ad | ||
|
|
e66fe507bb | ||
|
|
6ea09bb2ef | ||
|
|
7a1065809a | ||
|
|
8f194b7d19 | ||
|
|
6a4fa93b00 | ||
|
|
cad30a73fe | ||
|
|
0ba2162113 | ||
|
|
42841a021e | ||
|
|
690687353f | ||
|
|
25862cb7a3 | ||
|
|
227e7690a3 | ||
|
|
a6f256fbe8 | ||
|
|
3ebca00872 | ||
|
|
7e11cea780 | ||
|
|
ad7f1c6a3b | ||
|
|
a8b7663f37 | ||
|
|
3b6afdd7d4 | ||
|
|
943fabb548 | ||
|
|
53f5fe71bb | ||
|
|
c406ce4aac | ||
|
|
4b4a7e8d4a | ||
|
|
7d2214678e | ||
|
|
81972106d9 | ||
|
|
6a95d0517f | ||
|
|
8abed86b1a | ||
|
|
5a882161ac | ||
|
|
ffb1d9be4f | ||
|
|
d5e2b41c37 | ||
|
|
c049c1388a | ||
|
|
eb93b9f7a9 | ||
|
|
867d1eaf9b | ||
|
|
32d4739dad | ||
|
|
650cfc0448 | ||
|
|
d4d824022f | ||
|
|
c6284557e7 | ||
|
|
a8a2f86a23 | ||
|
|
fc219e7dca | ||
|
|
96a103d222 | ||
|
|
f525f5b8cb | ||
|
|
5c30a687fa | ||
|
|
9ffe8b0e97 | ||
|
|
12c8564a26 | ||
|
|
903aa60fec | ||
|
|
969bc968ee | ||
|
|
e941d78daf | ||
|
|
8d3475825b | ||
|
|
68c890e179 | ||
|
|
6f73d7dbb9 | ||
|
|
c2ac967123 | ||
|
|
dd573c9806 | ||
|
|
58ee415e63 | ||
|
|
561397da7a | ||
|
|
3d2438c625 | ||
|
|
a08710bfda | ||
|
|
151695b43c | ||
|
|
2d8259b432 | ||
|
|
ae03e10db5 | ||
|
|
208fbaf5ef | ||
|
|
820ca0a57e | ||
|
|
cf309ede14 | ||
|
|
da0a95695d | ||
|
|
b2541a1b93 | ||
|
|
e8fd78b85d | ||
|
|
1a578f6574 | ||
|
|
5df40f7c42 | ||
|
|
001c923f43 | ||
|
|
5e24dbaddb | ||
|
|
3f8e3d4d38 | ||
|
|
391fee3ea2 | ||
|
|
6cee8572e0 | ||
|
|
f2d9e802cd | ||
|
|
5247961185 | ||
|
|
53a2618cba | ||
|
|
d360addc65 | ||
|
|
08e7da305c | ||
|
|
afab1f8d73 | ||
|
|
749266b649 | ||
|
|
c4dcaf540d | ||
|
|
561d6ea0d1 | ||
|
|
1574f61a8b | ||
|
|
7dc941227f | ||
|
|
ca026039a3 | ||
|
|
4b06a2d316 | ||
|
|
00299fe7cb | ||
|
|
9e6b650efd | ||
|
|
c56d0760f6 | ||
|
|
aedc78536f | ||
|
|
0c17fa0ab2 | ||
|
|
752a09c988 | ||
|
|
d5a8821ec1 | ||
|
|
aa7400aaad | ||
|
|
d849fc5dca | ||
|
|
3d73d1a359 | ||
|
|
18bcef3ef4 | ||
|
|
115fd99bfb | ||
|
|
879499b409 | ||
|
|
e265771c6e | ||
|
|
f9f728cb66 | ||
|
|
e321caee24 | ||
|
|
5dd3a23752 | ||
|
|
c572cb80fc | ||
|
|
b761f1a605 | ||
|
|
c1f907b412 | ||
|
|
c45d0f4f2f | ||
|
|
406f0dcffe | ||
|
|
9dbc216c11 | ||
|
|
22b455fad4 | ||
|
|
9807b506eb | ||
|
|
624423686d | ||
|
|
e5cba5e4b5 | ||
|
|
1f15bf72e7 | ||
|
|
e6bc5a273c | ||
|
|
8fa6c4f96f | ||
|
|
b7e8f97de1 | ||
|
|
5fc68fe71b | ||
|
|
808260cd1b | ||
|
|
d9152c08d8 | ||
|
|
4048e542d9 | ||
|
|
b08273700f | ||
|
|
0d5512931e | ||
|
|
7fc37de058 | ||
|
|
c4dac6fd7a | ||
|
|
d3122ddcc5 | ||
|
|
74cfcda902 | ||
|
|
6fc6dd4352 | ||
|
|
7014986f65 | ||
|
|
031f4e942f | ||
|
|
9904bdce9d | ||
|
|
754418493f | ||
|
|
27000ea1b4 | ||
|
|
620ee4298c | ||
|
|
1d09da7e53 | ||
|
|
5115256853 | ||
|
|
0d715343de | ||
|
|
8170ff7b2c | ||
|
|
f442fc0d77 | ||
|
|
441547d8e4 | ||
|
|
5e123e6519 | ||
|
|
5b45ee5c8d | ||
|
|
939942bdb3 | ||
|
|
e64be43090 | ||
|
|
da898e1ac9 | ||
|
|
8dba46a0ac | ||
|
|
633eb871ee | ||
|
|
91c0fa71fa | ||
|
|
87da4e91bb | ||
|
|
7768828d9c | ||
|
|
8ece000489 | ||
|
|
1297fa5953 | ||
|
|
c7a2442bc7 | ||
|
|
f88ee1c23a | ||
|
|
c608f8279a | ||
|
|
d99a5c5e9e | ||
|
|
5999caed62 | ||
|
|
163485b5ba | ||
|
|
64a22fc15d | ||
|
|
07cd772962 | ||
|
|
160a8255ef | ||
|
|
cca430522d | ||
|
|
db417110e7 | ||
|
|
67896b56c5 | ||
|
|
d21d603617 | ||
|
|
fdd108fc43 | ||
|
|
19ff7a03f9 | ||
|
|
7f5744bde7 | ||
|
|
b87797f788 | ||
|
|
48b19ec580 | ||
|
|
f2e200f832 | ||
|
|
a541f53dd1 | ||
|
|
6ce77b1b49 | ||
|
|
5222c7737f | ||
|
|
b0cf65b5cd | ||
|
|
f7c8ed0dd6 | ||
|
|
7cdc25538e | ||
|
|
b82cd8cbd0 | ||
|
|
9365556ea4 | ||
|
|
de376663f0 | ||
|
|
f24ec671c2 | ||
|
|
c0f7a268c7 | ||
|
|
7b4e0915b1 | ||
|
|
2ea7de7361 | ||
|
|
82cc435fd7 | ||
|
|
8eab556f55 | ||
|
|
5a37594853 | ||
|
|
82f3fa00d0 | ||
|
|
c97dc3bd15 | ||
|
|
d6e99e2f5a | ||
|
|
46b6f82473 | ||
|
|
c80e43ca42 | ||
|
|
80e41f8b4b | ||
|
|
9e16cf36ed | ||
|
|
1d8e39a2df | ||
|
|
21db352039 | ||
|
|
45f033d15a | ||
|
|
8eba2fcba0 | ||
|
|
e476c06798 | ||
|
|
0b6b7ea0e4 | ||
|
|
54097e11d7 | ||
|
|
a41528d297 | ||
|
|
e62ef92a4b | ||
|
|
7be271876e | ||
|
|
7c639f9252 | ||
|
|
f03e707e8e | ||
|
|
38e63451d6 | ||
|
|
5e1366c946 | ||
|
|
0470ebf5af | ||
|
|
b71642045d | ||
|
|
582e814422 | ||
|
|
d15ad4da80 | ||
|
|
bc0089ff42 | ||
|
|
bff8a6ae75 | ||
|
|
6d4ad4ade6 | ||
|
|
057691722e | ||
|
|
0e2752f7c2 | ||
|
|
76b2255d7b | ||
|
|
93d5128db3 | ||
|
|
6d98020923 | ||
|
|
77110f970b | ||
|
|
cf6ce9a3fd | ||
|
|
be0dea7c0a | ||
|
|
b0b444bbd3 | ||
|
|
3e45188e88 | ||
|
|
51171cb8e1 | ||
|
|
0060190580 | ||
|
|
1da4844103 | ||
|
|
7f2862762a | ||
|
|
3e2af17a83 | ||
|
|
1f7a8065fa |
@@ -0,0 +1 @@
|
||||
lib/elixir/test/elixir/fixtures/*.txt text eol=lf
|
||||
+83
-1
@@ -1,3 +1,85 @@
|
||||
# v0.10.0 (2013-07-15)
|
||||
|
||||
* enhancements
|
||||
* [ExUnit] Support `trace: true` option which gives detailed reporting on test runs
|
||||
* [HashDict] Optimize `HashDict` to store pairs in a cons cell reducing storage per key by half
|
||||
* [Kernel] Add pretty printing support for inspect
|
||||
* [Kernel] Add document algebra library used as the foundation for pretty printing
|
||||
* [Kernel] Add `defrecordp/3` that enables specifying the first element of the tuple
|
||||
* [Kernel] Add the `Set` API and a hash based implementation via `HashSet`
|
||||
* [Kernel] Add `Stream` as composable, lazy-enumerables
|
||||
* [Mix] `mix archive` now includes the version of the generated archive
|
||||
* [Mix] Mix now requires explicit dependency overriding to be given with `override: true`
|
||||
* [Mix] Projects can now define an `:elixir` key to outline supported Elixir versions
|
||||
* [Typespec] Improve error messages to contain file, line and the typespec itself
|
||||
|
||||
* bug fix
|
||||
* [CLI] Elixir can now run on Unix directories with `:` in its path
|
||||
* [Kernel] `match?/2` does not leak variables to outer scope
|
||||
* [Kernel] Keep `head|tail` format when splicing at the tail
|
||||
* [Kernel] Ensure variables defined in the module body are not passed to callbacks
|
||||
* [Mix] On dependencies conflict, show from where each source is coming from
|
||||
* [Mix] Empty projects no longer leave empty ebin files on `mix compile`
|
||||
* [Module] Calling `Module.register_attribute/3` no longer automatically changes it to persisted or accumulated
|
||||
|
||||
* deprecations
|
||||
* [Enum] Receiving the index of iteration in `Enum.map/2` and `Enum.each/2` is deprecated in favor of `Stream.with_index/1`
|
||||
* [File] `File.iterator/1` and `File.biniterator/1` are deprecated in favor of `IO.stream/1` and `IO.binstream/1`
|
||||
* [File] `File.iterator!/2` and `File.biniterator!/2` are deprecated in favor of `File.stream!/2` and `File.binstream!/2`
|
||||
* [Kernel] Deprecate recently added `quote binding: ...` in favor of the clearer `quote bind_quoted: ...`
|
||||
* [Kernel] Deprecate `Kernel.float/1` in favor of a explicit conversion
|
||||
* [Mix] Deprecate `mix run EXPR` in favor of `mix run -e EXPR`
|
||||
* [Record] `Record.__index__/2` deprecated in favor of `Record.__record__(:index, key)`
|
||||
|
||||
* backwards incompatible changes
|
||||
* [Kernel] The `Binary.Inspect` protocol has been renamed to `Inspect`
|
||||
* [Kernel] Tighten up the grammar rules regarding parentheses omission, previously the examples below would compile but now they raise an error message:
|
||||
|
||||
do_something 1, is_list [], 3
|
||||
[1, is_atom :foo, 3]
|
||||
|
||||
* [Module] Calling `Module.register_attribute/3` no longer automatically changes it to persisted or accumulated
|
||||
* [Record] First element of a record via `defrecordp` is now the `defrecordp` name and no longer the current atom
|
||||
* [URI] Remove custom URI parsers in favor of `URI.default_port/2`
|
||||
|
||||
# v0.9.3 (2013-06-23)
|
||||
|
||||
* enhancements
|
||||
* [File] Add `File.chgrp`, `File.chmod` and `File.chown`
|
||||
* [Kernel] Add `--warnings-as-errors` to Elixir's compiler options
|
||||
* [Kernel] Print warnings to stderr
|
||||
* [Kernel] Warn on undefined module attributes
|
||||
* [Kernel] Emit warning for `x in []` in guards
|
||||
* [Kernel] Add `binding/0` and `binding/1` for retrieving bindings
|
||||
* [Kernel] `quote` now allows a binding as an option
|
||||
* [Macro] Add `Macro.expand_once/2` and `Macro.expand_all/2`
|
||||
* [Mix] Implement `Mix.Version` for basic versioning semantics
|
||||
* [Mix] Support creation and installation of archives (.ez files)
|
||||
* [Mix] `github: ...` shortcut now uses the faster `git` schema instead of `https`
|
||||
* [Record] Allow types to be given to `defrecordp`
|
||||
|
||||
* bug fix
|
||||
* [Kernel] The elixir executable on Windows now supports the same options as the UNIX one
|
||||
* [Kernel] Improve error messages on default clauses clash
|
||||
* [Kernel] `__MODULE__.Foo` now returns `Foo` when outside of a Module
|
||||
* [Kernel] Improve error messages when default clauses from different definitions collide
|
||||
* [Kernel] `^x` variables should always refer to the value before the expression
|
||||
* [Kernel] Allow `(x, y) when z` in function clauses and try expressions
|
||||
* [Mix] Mix now properly evaluates rebar scripts
|
||||
|
||||
* deprecations
|
||||
* [Code] `Code.string_to_ast/1` has been deprecated in favor of `Code.string_to_quoted/1`
|
||||
* [Macro] `Macro.to_binary/1` has been deprecated in favor of `Macro.to_string/1`
|
||||
* [Typespec] Deprecate `(fun(...) -> ...)` in favor of `(... -> ...)`
|
||||
|
||||
* backwards incompatible changes
|
||||
* [Bitwise] Precedence of operators used by the Bitwise module were changed, check `elixir_parser.yrl` for more information
|
||||
* [File] `rm_rf` and `cp_r` now returns a tuple with three elements on failures
|
||||
* [Kernel] The quoted representation for `->` clauses changed from a tuple with two elements to a tuple with three elements to support metadata
|
||||
* [Kernel] Sigils now dispatch to `sigil_$` instead of `__$__` where `$` is the sigil caracter
|
||||
* [Macro] `Macro.expand/2` now expands until final form. Although this is backwards incompatible, it is very likely you do not need to change your code, since expansion until its final form is recommended, particularly if you are expecting an atom out of it
|
||||
* [Mix] No longer support beam files on `mix local`
|
||||
|
||||
# v0.9.2 (2013-06-13)
|
||||
|
||||
* enhancements
|
||||
@@ -11,7 +93,7 @@
|
||||
* bug fix
|
||||
* [HashDict] Ensure HashDict uses exact match throughout its implementation
|
||||
* [IEx] Do not interpret ANSI codes in IEx results
|
||||
* [IEx] Ensure --cookie is set before accessing remote shell
|
||||
* [IEx] Ensure `--cookie` is set before accessing remote shell
|
||||
* [Kernel] Do not ignore nil when dispatching protocols to avoid infinite loops
|
||||
* [Mix] Fix usage of shell expressions in `Mix.Shell.cmd`
|
||||
* [Mix] Start the application by default on escripts
|
||||
|
||||
+15
-5
@@ -95,8 +95,8 @@ Good pull requests - patches, improvements, new features - are a fantastic
|
||||
help. They should remain focused in scope and avoid containing unrelated
|
||||
commits.
|
||||
|
||||
**IMPORTANT**: By submitting a patch, you agree that your work will be made
|
||||
public forever and will be licensed under the license used by the project.
|
||||
**IMPORTANT**: By submitting a patch, you agree that your work will be
|
||||
licensed under the license used by the project.
|
||||
|
||||
If you have any significant pull request in mind (e.g. implementing features,
|
||||
refactoring code, porting to a different language), **please ask first**
|
||||
@@ -144,16 +144,26 @@ in order to craft an excellent pull request:
|
||||
[interactive rebase](https://help.github.com/articles/interactive-rebase)
|
||||
feature to tidy up your commits before making them public.
|
||||
|
||||
5. Push your topic branch up to your fork:
|
||||
5. Make sure all the tests are still passing.
|
||||
|
||||
```bash
|
||||
make test
|
||||
```
|
||||
|
||||
This command will compile the code in your branch and use that
|
||||
version of Elixir to run the tests. This is needed to ensure your changes can
|
||||
pass all the tests.
|
||||
|
||||
6. Push your topic branch up to your fork:
|
||||
|
||||
```bash
|
||||
git push origin <topic-branch-name>
|
||||
```
|
||||
|
||||
6. [Open a Pull Request](https://help.github.com/articles/using-pull-requests/)
|
||||
7. [Open a Pull Request](https://help.github.com/articles/using-pull-requests/)
|
||||
with a clear title and description.
|
||||
|
||||
7. If you haven't updated your pull request for a while, you should consider
|
||||
8. If you haven't updated your pull request for a while, you should consider
|
||||
rebasing on master and resolving any conflicts.
|
||||
|
||||
**IMPORTANT**: _Never ever_ merge upstream `master` into your branches. You
|
||||
|
||||
@@ -1,9 +1,16 @@
|
||||
REBAR := $(shell echo `pwd`/rebar)
|
||||
REBAR := "$(shell echo `pwd`/rebar)"
|
||||
ELIXIRC := bin/elixirc --ignore-module-conflict $(ELIXIRC_OPTS)
|
||||
ERLC := erlc -I lib/elixir/include
|
||||
ERL := erl -I lib/elixir/include -noshell -pa lib/elixir/ebin
|
||||
VERSION := $(strip $(shell cat VERSION))
|
||||
INSTALL_PATH := /usr/local
|
||||
Q := @
|
||||
PREFIX := /usr/local
|
||||
LIBDIR := lib
|
||||
INSTALL = install
|
||||
INSTALL_DIR = $(INSTALL) -m755 -d
|
||||
INSTALL_DATA = $(INSTALL) -m644
|
||||
INSTALL_PROGRAM = $(INSTALL) -m755
|
||||
|
||||
|
||||
.PHONY: install compile erlang elixir dialyze test clean docs release_docs release_zip release_erl
|
||||
.NOTPARALLEL: compile
|
||||
@@ -14,15 +21,15 @@ define APP_TEMPLATE
|
||||
$(1): lib/$(1)/ebin/Elixir.$(2).beam lib/$(1)/ebin/$(1).app
|
||||
|
||||
lib/$(1)/ebin/$(1).app:
|
||||
@ cd lib/$(1) && ../../bin/elixir -e "Mix.Server.start_link(:dev)" -r mix.exs -e "Mix.Task.run('compile.app')"
|
||||
$(Q) cd lib/$(1) && ../../bin/elixir -e "Mix.Server.start_link(:dev)" -r mix.exs -e "Mix.Task.run('compile.app')"
|
||||
|
||||
lib/$(1)/ebin/Elixir.$(2).beam: $(wildcard lib/$(1)/lib/*.ex) $(wildcard lib/$(1)/lib/*/*.ex) $(wildcard lib/$(1)/lib/*/*/*.ex)
|
||||
@ echo "==> $(1) (compile)"
|
||||
@ $$(ELIXIRC) "lib/$(1)/lib/**/*.ex" -o lib/$(1)/ebin
|
||||
$(Q) $$(ELIXIRC) "lib/$(1)/lib/**/*.ex" -o lib/$(1)/ebin
|
||||
|
||||
test_$(1): $(1)
|
||||
@ echo "==> $(1) (exunit)"
|
||||
@ cd lib/$(1) && ../../bin/elixir -r "test/test_helper.exs" -pr "test/**/*_test.exs";
|
||||
$(Q) cd lib/$(1) && ../../bin/elixir -r "test/test_helper.exs" -pr "test/**/*_test.exs";
|
||||
endef
|
||||
|
||||
#==> Compilation tasks
|
||||
@@ -35,11 +42,11 @@ default: compile
|
||||
compile: lib/elixir/src/elixir.app.src erlang elixir
|
||||
|
||||
lib/elixir/src/elixir.app.src: src/elixir.app.src
|
||||
@ rm -rf lib/elixir/src/elixir.app.src
|
||||
@ cp src/elixir.app.src lib/elixir/src/elixir.app.src
|
||||
$(Q) rm -rf lib/elixir/src/elixir.app.src
|
||||
$(Q) cp src/elixir.app.src lib/elixir/src/elixir.app.src
|
||||
|
||||
erlang:
|
||||
@ cd lib/elixir && $(REBAR) compile
|
||||
$(Q) cd lib/elixir && $(REBAR) compile
|
||||
|
||||
# Since Mix depends on EEx and EEx depends on
|
||||
# Mix, we first compile EEx without the .app
|
||||
@@ -48,21 +55,21 @@ elixir: kernel lib/eex/ebin/Elixir.EEx.beam mix ex_unit eex iex
|
||||
|
||||
kernel: $(KERNEL) VERSION
|
||||
$(KERNEL): lib/elixir/lib/*.ex lib/elixir/lib/*/*.ex
|
||||
@ if [ ! -f $(KERNEL) ]; then \
|
||||
$(Q) if [ ! -f $(KERNEL) ]; then \
|
||||
echo "==> bootstrap (compile)"; \
|
||||
$(ERL) -s elixir_compiler core -s erlang halt; \
|
||||
fi
|
||||
@ echo "==> kernel (compile)";
|
||||
@ $(ELIXIRC) "lib/elixir/lib/**/*.ex" -o lib/elixir/ebin;
|
||||
@ $(MAKE) unicode
|
||||
@ rm -rf lib/elixir/ebin/elixir.app
|
||||
@ cd lib/elixir && $(REBAR) compile
|
||||
$(Q) $(ELIXIRC) "lib/elixir/lib/**/*.ex" -o lib/elixir/ebin;
|
||||
$(Q) $(MAKE) unicode
|
||||
$(Q) rm -rf lib/elixir/ebin/elixir.app
|
||||
$(Q) cd lib/elixir && $(REBAR) compile
|
||||
|
||||
unicode: $(UNICODE)
|
||||
$(UNICODE): lib/elixir/priv/unicode.ex lib/elixir/priv/UnicodeData.txt lib/elixir/priv/NamedSequences.txt
|
||||
@ echo "==> unicode (compile)";
|
||||
@ echo "This step can take up to a minute to compile in order to embed the Unicode database"
|
||||
@ $(ELIXIRC) lib/elixir/priv/unicode.ex -o lib/elixir/ebin;
|
||||
$(Q) $(ELIXIRC) lib/elixir/priv/unicode.ex -o lib/elixir/ebin;
|
||||
|
||||
$(eval $(call APP_TEMPLATE,ex_unit,ExUnit))
|
||||
$(eval $(call APP_TEMPLATE,eex,EEx))
|
||||
@@ -72,16 +79,18 @@ $(eval $(call APP_TEMPLATE,iex,IEx))
|
||||
install: compile
|
||||
@ echo "==> elixir (install)"
|
||||
for dir in lib/*; do \
|
||||
install -m755 -d $(INSTALL_PATH)/lib/elixir/$$dir/ebin; \
|
||||
install -m644 $$dir/ebin/* $(INSTALL_PATH)/lib/elixir/$$dir/ebin; \
|
||||
$(INSTALL_DIR) "$(DESTDIR)$(PREFIX)/$(LIBDIR)/elixir/$$dir/ebin"; \
|
||||
$(INSTALL_DATA) $$dir/ebin/* "$(DESTDIR)$(PREFIX)/$(LIBDIR)/elixir/$$dir/ebin"; \
|
||||
done
|
||||
$(INSTALL_DIR) "$(DESTDIR)$(PREFIX)/$(LIBDIR)/elixir/bin"
|
||||
$(INSTALL_PROGRAM) $(filter-out %.bat, $(wildcard bin/*)) "$(DESTDIR)$(PREFIX)/$(LIBDIR)/elixir/bin"
|
||||
$(INSTALL_DIR) "$(DESTDIR)$(PREFIX)/bin"
|
||||
for file in "$(DESTDIR)$(PREFIX)"/$(LIBDIR)/elixir/bin/* ; do \
|
||||
ln -sf "../$(LIBDIR)/elixir/bin/$${file##*/}" "$(DESTDIR)$(PREFIX)/bin/" ; \
|
||||
done
|
||||
install -m755 -d $(INSTALL_PATH)/lib/elixir/bin
|
||||
install -m755 $(filter-out %.bat, $(wildcard bin/*)) $(INSTALL_PATH)/lib/elixir/bin
|
||||
install -m755 -d $(INSTALL_PATH)/bin
|
||||
ln -sf $(INSTALL_PATH)/lib/elixir/bin/* $(INSTALL_PATH)/bin
|
||||
|
||||
clean:
|
||||
@ cd lib/elixir && $(REBAR) clean
|
||||
$(Q) cd lib/elixir && $(REBAR) clean
|
||||
rm -rf ebin
|
||||
rm -rf lib/*/ebin
|
||||
rm -rf lib/*/test/tmp
|
||||
@@ -92,6 +101,9 @@ clean:
|
||||
rm -rf lib/elixir/src/*_parser.erl
|
||||
rm -rf lib/elixir/test/ebin
|
||||
|
||||
clean_exbeam:
|
||||
$(Q) rm -f lib/*/ebin/Elixir.*.beam
|
||||
|
||||
#==> Release tasks
|
||||
|
||||
SOURCE_REF = $(shell head="$$(git rev-parse HEAD)" tag="$$(git tag --points-at $$head | tail -1)" ; echo "$${tag:-$$head}\c")
|
||||
@@ -117,8 +129,8 @@ release_docs: docs
|
||||
mv docs ../elixir-lang.github.com/docs/master
|
||||
|
||||
release_erl: compile
|
||||
@ rm -rf rel/elixir
|
||||
@ cd rel && ../rebar generate
|
||||
$(Q) rm -rf rel/elixir
|
||||
$(Q) cd rel && ../rebar generate
|
||||
|
||||
#==> Tests tasks
|
||||
|
||||
@@ -126,29 +138,29 @@ test: test_erlang test_elixir
|
||||
|
||||
test_erlang: compile
|
||||
@ echo "==> elixir (eunit)"
|
||||
@ mkdir -p lib/elixir/test/ebin
|
||||
@ $(ERLC) -pa lib/elixir/ebin -o lib/elixir/test/ebin lib/elixir/test/erlang/*.erl
|
||||
@ $(ERL) -pa lib/elixir/test/ebin -s test_helper test -s erlang halt;
|
||||
$(Q) mkdir -p lib/elixir/test/ebin
|
||||
$(Q) $(ERLC) -pa lib/elixir/ebin -o lib/elixir/test/ebin lib/elixir/test/erlang/*.erl
|
||||
$(Q) $(ERL) -pa lib/elixir/test/ebin -s test_helper test -s erlang halt;
|
||||
@ echo
|
||||
|
||||
test_elixir: test_kernel test_ex_unit test_doc_test test_mix test_eex test_iex
|
||||
|
||||
test_doc_test: compile
|
||||
@ echo "==> doctest (exunit)"
|
||||
@ cd lib/elixir && ../../bin/elixir -r "test/doc_test.exs";
|
||||
$(Q) cd lib/elixir && ../../bin/elixir -r "test/doc_test.exs";
|
||||
|
||||
test_kernel: compile
|
||||
@ echo "==> kernel (exunit)"
|
||||
@ cd lib/elixir && ../../bin/elixir -r "test/elixir/test_helper.exs" -pr "test/elixir/**/*_test.exs";
|
||||
$(Q) cd lib/elixir && ../../bin/elixir -r "test/elixir/test_helper.exs" -pr "test/elixir/**/*_test.exs";
|
||||
|
||||
.dialyzer.base_plt:
|
||||
@ echo "==> Adding Erlang/OTP basic applications to a new base PLT"
|
||||
@ dialyzer --output_plt .dialyzer.base_plt --build_plt --apps erts kernel stdlib compiler syntax_tools inets crypto ssl
|
||||
$(Q) dialyzer --output_plt .dialyzer.base_plt --build_plt --apps erts kernel stdlib compiler syntax_tools inets crypto ssl
|
||||
|
||||
dialyze: .dialyzer.base_plt
|
||||
@ rm -f .dialyzer_plt
|
||||
@ cp .dialyzer.base_plt .dialyzer_plt
|
||||
$(Q) rm -f .dialyzer_plt
|
||||
$(Q) cp .dialyzer.base_plt .dialyzer_plt
|
||||
@ echo "==> Adding Elixir to PLT..."
|
||||
@ dialyzer --plt .dialyzer_plt --add_to_plt -r lib/elixir/ebin lib/ex_unit/ebin lib/mix/ebin lib/iex/ebin lib/eex/ebin
|
||||
$(Q) dialyzer --plt .dialyzer_plt --add_to_plt -r lib/elixir/ebin lib/ex_unit/ebin lib/mix/ebin lib/iex/ebin lib/eex/ebin
|
||||
@ echo "==> Dialyzing Elixir..."
|
||||
@ dialyzer --plt .dialyzer_plt -r lib/elixir/ebin lib/ex_unit/ebin lib/mix/ebin lib/iex/ebin lib/eex/ebin
|
||||
$(Q) dialyzer --plt .dialyzer_plt -r lib/elixir/ebin lib/ex_unit/ebin lib/mix/ebin lib/iex/ebin lib/eex/ebin
|
||||
|
||||
@@ -26,7 +26,7 @@ Building the documentation requires [ex_doc](https://github.com/elixir-lang/ex_d
|
||||
|
||||
# After cloning and compiling Elixir
|
||||
$ git clone git://github.com/elixir-lang/ex_doc.git
|
||||
$ cd ../ex_doc && ../elixir/bin/mix compile
|
||||
$ cd ex_doc && ../elixir/bin/mix compile
|
||||
$ cd ../elixir && make docs
|
||||
|
||||
## Contributing
|
||||
@@ -52,4 +52,4 @@ We appreciate any contribution to Elixir, so check out our [CONTRIBUTING.md](CON
|
||||
|
||||
Elixir source code is released under Apache 2 License with some parts under Erlang's license (EPL).
|
||||
|
||||
Check LEGAL and LICENSE files for more information.
|
||||
Check LEGAL and LICENSE files for more information.
|
||||
|
||||
+6
-4
@@ -2,7 +2,7 @@
|
||||
|
||||
This document simply outlines the release process:
|
||||
|
||||
1) Remove `.dev` extension from VERSION
|
||||
1) Remove `-dev` extension from VERSION
|
||||
|
||||
2) Run `make clean test` to ensure all tests pass from scratch and the CI is green
|
||||
|
||||
@@ -12,13 +12,15 @@ This document simply outlines the release process:
|
||||
|
||||
5) Create tag from master branch
|
||||
|
||||
6) Release new docs with `make release_docs`, update elixir-lang.org
|
||||
6) Release new docs with `make release_docs`, move docs to `docs/stable`
|
||||
|
||||
7) Release new zip with `make release_zip`, push new zip to Elixir's elixir-lang.org/packages.html
|
||||
7) Release new zip with `make release_zip`, push new zip to Github Releases
|
||||
|
||||
8) Push package to expm with `expm publish package.exs`
|
||||
|
||||
9) After release, bump versions and add `.dev` back
|
||||
9) After release, bump versions and add `-dev` back
|
||||
|
||||
10) `make release_docs` once again and push `elixir-lang.github.com`
|
||||
|
||||
## Places where version is mentioned
|
||||
|
||||
|
||||
+4
-4
@@ -2,7 +2,7 @@
|
||||
if [ $# -eq 0 ] || [ $1 = "--help" ] || [ $1 = "-h" ]; then
|
||||
echo "Usage: `basename $0` [options] [.exs file] [data]
|
||||
|
||||
-v Prints version
|
||||
-v Prints version and exit
|
||||
-e \"command\" Evaluates the given command (*)
|
||||
-r \"file\" Requires the given files/patterns (*)
|
||||
-S \"script\" Finds and executes the given script (*)
|
||||
@@ -43,7 +43,7 @@ while [ $I -le $# ]; do
|
||||
case "$PEEK" in
|
||||
-v|--compile|--no-halt|+iex|+compile)
|
||||
;;
|
||||
-e|-r|-pr|-pa|-pz|--remsh|-S)
|
||||
-e|-r|-pr|-pa|-pz|--remsh|--app)
|
||||
S=2
|
||||
;;
|
||||
--detached|--hidden)
|
||||
@@ -79,7 +79,7 @@ if [ "$ELIXIR_NO_CLI" != "1" ]; then ERL="$ERL -s elixir start_cli"; fi
|
||||
|
||||
if [ -f "$SCRIPT_PATH/../releases/RELEASES" ] && [ -f "$SCRIPT_PATH/erl" ]
|
||||
then
|
||||
exec "$SCRIPT_PATH"/erl -env ERL_LIBS $ERL_LIBS:"$SCRIPT_PATH/../lib" -boot elixir -noshell $ELIXIR_ERL_OPTS $ERL -extra "$@"
|
||||
exec "$SCRIPT_PATH"/erl -pa "$SCRIPT_PATH"/../lib/*/ebin -noshell $ELIXIR_ERL_OPTS $ERL -boot start -extra "$@"
|
||||
else
|
||||
exec erl -env ERL_LIBS $ERL_LIBS:"$SCRIPT_PATH/../lib" -noshell $ELIXIR_ERL_OPTS $ERL -extra "$@"
|
||||
exec erl -pa "$SCRIPT_PATH"/../lib/*/ebin -noshell $ELIXIR_ERL_OPTS $ERL -extra "$@"
|
||||
fi
|
||||
|
||||
+106
-12
@@ -1,25 +1,119 @@
|
||||
@echo off
|
||||
SETLOCAL enabledelayedexpansion
|
||||
set argc=0
|
||||
for %%x in (%*) do set /A argc+=1
|
||||
if %argc% == 0 (
|
||||
if %argc%== 0 (
|
||||
goto documentation
|
||||
) else (
|
||||
goto run
|
||||
goto parseopts
|
||||
)
|
||||
:documentation
|
||||
echo Usage: %~nx0 [options] [.exs file] [data]
|
||||
echo.
|
||||
echo -v Prints version and exit
|
||||
echo -e command Evaluates the given command (*)
|
||||
echo -r command Requires the given file/pattern (*)
|
||||
echo -pr command Requires the given file/pattern in parallel (*)
|
||||
echo -pa path Prepend the given path to Erlang code path (*)
|
||||
echo -pz path Append the given path to Erlang code path (*)
|
||||
echo --app app Start the given app and its dependencies (*)
|
||||
echo --no-halt Do not halt the Erlang VM after execution
|
||||
echo -v Prints version and exit
|
||||
echo -e command Evaluates the given command (*)
|
||||
echo -r file Requires the given files/patterns (*)
|
||||
echo -S script Finds and executes the given script (*)
|
||||
echo -pr file Requires the given files/patterns in parallel (*)
|
||||
echo -pa path Prepends the given path to Erlang code path (*)
|
||||
echo -pz path Appends the given path to Erlang code path (*)
|
||||
echo --app app Start the given app and its dependencies (*)
|
||||
echo --erl switches Switches to be passed down to erlang (*)
|
||||
echo --name name Makes and assigns a name to the distributed node
|
||||
echo --sname name Makes and assigns a short name to the distributed node
|
||||
echo --cookie cookie Sets a cookie for this distributed node
|
||||
echo --hidden Makes a hidden node
|
||||
echo --detached Starts the Erlang VM detached from console
|
||||
echo --no-halt Does not halt the Erlang VM after execution
|
||||
echo.
|
||||
echo ** Options marked with (*) can be given more than once
|
||||
echo ** Options given after the .exs file or -- are passed down to the executed code
|
||||
echo ** Options can be passed to the erlang runtime using ELIXIR_ERL_OPTS
|
||||
echo ** Options can be passed to the erlang runtime using ELIXIR_ERL_OPTS or --erl
|
||||
goto :EOF
|
||||
|
||||
:parseopts
|
||||
|
||||
rem Parameters for Erlang
|
||||
set parsErlang=
|
||||
|
||||
rem Make sure we keep a copy of all parameters
|
||||
set allPars=%*
|
||||
|
||||
rem Get the original path name from the batch file
|
||||
set originPath=%~dp0
|
||||
|
||||
rem Optional parameters before the "-extra" parameter
|
||||
set beforeExtra=
|
||||
|
||||
rem Recursive loop called for each parameter that parses the cmd line parameters
|
||||
:startloop
|
||||
set par="%1"
|
||||
shift
|
||||
if "%par%"=="" (
|
||||
rem if no parameters defined
|
||||
goto :run
|
||||
)
|
||||
rem ******* ERLANG PARAMETERS **********************
|
||||
for /f "usebackq" %%m in (`echo %par%^|findstr \--detached`) do (
|
||||
set parsErlang=%parsErlang% -detached
|
||||
goto:startloop
|
||||
)
|
||||
for /f "usebackq" %%m in (`echo %par%^|findstr \--hidden`) do (
|
||||
set parsErlang=%parsErlang% -hidden
|
||||
goto:startloop
|
||||
)
|
||||
for /f "usebackq" %%m in (`echo %par%^|findstr \--cookie`) do (
|
||||
set parsErlang=%parsErlang% -setcookie %1
|
||||
shift
|
||||
goto:startloop
|
||||
)
|
||||
for /f "usebackq" %%m in (`echo %par%^|findstr \--sname`) do (
|
||||
set parsErlang=%parsErlang% -sname %1
|
||||
shift
|
||||
goto:startloop
|
||||
)
|
||||
for /f "usebackq" %%m in (`echo %par%^|findstr \--name`) do (
|
||||
set parsErlang=%parsErlang% -name %1
|
||||
shift
|
||||
goto:startloop
|
||||
)
|
||||
for /f "usebackq" %%m in (`echo %par%^|findstr \--erl`) do (
|
||||
set beforeExtra=%beforeExtra% %~1
|
||||
shift
|
||||
goto:startloop
|
||||
)
|
||||
rem ******* elixir parameters **********************
|
||||
for /f "usebackq" %%m in (`echo %par%^|findstr \--v`) do (
|
||||
goto:startloop
|
||||
)
|
||||
for /f "usebackq" %%m in (`echo %par%^|findstr \--compile`) do (
|
||||
goto:startloop
|
||||
)
|
||||
for /f "usebackq" %%m in (`echo %par%^|findstr \--no-halt`) do (
|
||||
goto:startloop
|
||||
)
|
||||
for /f "usebackq" %%m in (`echo %par%^|findstr \+iex`) do (
|
||||
goto:startloop
|
||||
)
|
||||
for /f "usebackq" %%m in (`echo %par%^|findstr \+compile`) do (
|
||||
goto:startloop
|
||||
)
|
||||
for /f "usebackq" %%m in (`echo %par%^|findstr \-[er]`) do (
|
||||
shift
|
||||
goto:startloop
|
||||
)
|
||||
for /f "usebackq" %%m in (`echo %par%^|findstr \-p[raz]`) do (
|
||||
shift
|
||||
goto:startloop
|
||||
)
|
||||
for /f "usebackq" %%m in (`echo %par%^|findstr \--app`) do (
|
||||
shift
|
||||
goto:startloop
|
||||
)
|
||||
for /f "usebackq" %%m in (`echo %par%^|findstr \--remsh`) do (
|
||||
shift
|
||||
goto:startloop
|
||||
)
|
||||
rem ******* assume all pre-params are parsed ********************
|
||||
:run
|
||||
erl -env ERL_LIBS %ERL_LIBS%;"%~dp0\..\lib" -noshell %ELIXIR_ERL_OPTS% -s elixir start_cli -extra %*
|
||||
erl -env ERL_LIBS %ERL_LIBS%;"%originPath%\..\lib" -noshell %ELIXIR_ERL_OPTS% %parsErlang% -s elixir start_cli %beforeExtra% -extra %*
|
||||
|
||||
@@ -6,6 +6,7 @@ if [ $# -eq 0 ]; then
|
||||
--no-docs Do not attach documentation to compiled modules
|
||||
--no-debug-info Do not attach debug info to compiled modules
|
||||
--ignore-module-conflict
|
||||
--warnings-as-errors Treat warnings as errors and return non-zero exit code
|
||||
|
||||
** Options given after -- are passed down to the executed code
|
||||
** Options can be passed to the erlang runtime using ELIXIR_ERL_OPTS" >&2
|
||||
|
||||
@@ -13,6 +13,7 @@ echo -o The directory to output compiled files
|
||||
echo --no-docs Do not attach documentation to compiled modules
|
||||
echo --no-debug-info Do not attach debug info to compiled modules
|
||||
echo --ignore-module-conflict
|
||||
echo --warnings-as-errors Treat warnings as errors and return non-zero exit code
|
||||
echo.
|
||||
echo ** Options marked with (*) can be given more than once
|
||||
echo ** Options given after -- are passed down to the executed code
|
||||
|
||||
@@ -13,6 +13,9 @@ if [ $# -gt 0 ] && ([ $1 = "--help" ] || [ $1 = "-h" ]); then
|
||||
--erl \"switches\" Switches to be passed down to erlang (*)
|
||||
--name \"name\" Makes and assigns a name to the distributed node
|
||||
--sname \"name\" Makes and assigns a short name to the distributed node
|
||||
--cookie \"cookie\" Sets a cookie for this distributed node
|
||||
--hidden Makes a hidden node
|
||||
--detached Starts the Erlang VM detached from console
|
||||
--remsh \"name\" Connects to a node using a remote shell
|
||||
--dot-iex \"path\" Overrides default .iex file and uses path instead;
|
||||
path can be empty, then no file will be loaded
|
||||
|
||||
@@ -75,7 +75,7 @@ defmodule EEx.Compiler do
|
||||
# Check if the syntax node represents an empty string
|
||||
|
||||
defp is_empty?(bin) when is_binary(bin) do
|
||||
bc(<<c>> inbits bin, not c in [?\s,?\t,?\r,?\n], do: <<c>>) == ""
|
||||
bc(<<c>> inbits bin, not c in [?\s, ?\t, ?\r, ?\n], do: <<c>>) == ""
|
||||
end
|
||||
|
||||
defp is_empty?({ :<>, _, [left, right] }) do
|
||||
|
||||
@@ -73,7 +73,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: Keyword.get(var!(assigns), unquote(name))
|
||||
end
|
||||
|
||||
defp transform(arg) do
|
||||
|
||||
@@ -20,8 +20,8 @@ defmodule EEx.Tokenizer do
|
||||
end
|
||||
|
||||
defp tokenize('<%%' ++ t, current_line, line, buffer, acc) do
|
||||
{ buffer, new_line, rest } = tokenize_expr t, line, [?%,?<|buffer]
|
||||
tokenize rest, current_line, new_line, [?>,?%|buffer], acc
|
||||
{ buffer, new_line, rest } = tokenize_expr t, line, [?%, ?<|buffer]
|
||||
tokenize rest, current_line, new_line, [?>, ?%|buffer], acc
|
||||
end
|
||||
|
||||
defp tokenize('<%#' ++ t, current_line, line, buffer, acc) do
|
||||
@@ -63,7 +63,7 @@ defmodule EEx.Tokenizer do
|
||||
|
||||
# Tokenize an expression until we find %>
|
||||
|
||||
defp tokenize_expr([?%,?>|t], line, buffer) do
|
||||
defp tokenize_expr([?%, ?>|t], line, buffer) do
|
||||
{ buffer, line, t }
|
||||
end
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ defmodule EEx.SmartEngineTest do
|
||||
end
|
||||
|
||||
test "evaluates with loops" do
|
||||
assert_eval "1\n2\n3\n", "<%= lc x inlist [1,2,3] do %><%= x %>\n<% end %>"
|
||||
assert_eval "1\n2\n3\n", "<%= lc x inlist [1, 2, 3] do %><%= x %>\n<% end %>"
|
||||
end
|
||||
|
||||
defp assert_eval(expected, actual, binding // []) do
|
||||
|
||||
@@ -21,7 +21,7 @@ defmodule EEx.TokenizerTest do
|
||||
end
|
||||
|
||||
test "strings with more than one line" do
|
||||
assert T.tokenize('foo\n<%= bar %>', 1) == [ { :text, 1, "foo\n" },{ :expr, 2, '=', ' bar ' } ]
|
||||
assert T.tokenize('foo\n<%= bar %>', 1) == [ { :text, 1, "foo\n" }, { :expr, 2, '=', ' bar ' } ]
|
||||
end
|
||||
|
||||
test "strings with more than one line and expression with more than one line" do
|
||||
|
||||
@@ -101,7 +101,7 @@ defmodule EExTest do
|
||||
end
|
||||
|
||||
test "evaluates with require code" do
|
||||
assert_eval "foo 1,2,3", "foo <% require Enum, as: E %><%= E.join [1,2,3], \",\" %>"
|
||||
assert_eval "foo 1,2,3", "foo <% require Enum, as: E %><%= E.join [1, 2, 3], \",\" %>"
|
||||
end
|
||||
|
||||
test "evaluates with end of token" do
|
||||
@@ -242,7 +242,7 @@ Number 3
|
||||
"""
|
||||
|
||||
string = """
|
||||
<%= Enum.map [1,2,3], fn x -> %>
|
||||
<%= Enum.map [1, 2, 3], fn x -> %>
|
||||
Number <%= x %>
|
||||
<% end %>
|
||||
"""
|
||||
|
||||
@@ -1 +1 @@
|
||||
ExUnit.start []
|
||||
ExUnit.start [trace: "--trace" in System.argv]
|
||||
@@ -13,11 +13,12 @@
|
||||
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
|
||||
name_args=false, %% when true, it means arguments should be named
|
||||
module=nil, %% the current module
|
||||
function=nil, %% the current function
|
||||
vars=[], %% a dict of defined variables and their alias
|
||||
temp_vars=[], %% a dict of all variables defined in a particular assign
|
||||
list_vars=nil, %% a list of vars passed down to Macro.Env
|
||||
backup_vars=nil, %% a copy of vars to be used on ^var
|
||||
temp_vars=nil, %% a set of all variables defined in a particular assign
|
||||
clause_vars=nil, %% a dict of all variables defined in a particular clause
|
||||
extra_guards=nil, %% extra guards from args expansion
|
||||
counter=[], %% a counter for the variables defined
|
||||
@@ -41,7 +42,7 @@
|
||||
imports_hygiene=true,
|
||||
unquote=true,
|
||||
unquoted=false,
|
||||
mark=true
|
||||
escape=false
|
||||
}).
|
||||
|
||||
%% Introspection
|
||||
@@ -61,7 +62,7 @@
|
||||
-define(is_downcase(S), S >= $a andalso S =< $z).
|
||||
|
||||
%% Atoms
|
||||
-define(is_new_atom(S), ?is_quote(S) orelse ?is_upcase(S) orelse ?is_downcase(S) orelse (S == $_)).
|
||||
-define(is_atom_start(S), ?is_quote(S) orelse ?is_upcase(S) orelse ?is_downcase(S) orelse (S == $_)).
|
||||
-define(is_atom(S), ?is_identifier(S) orelse (S == $@)).
|
||||
|
||||
-define(is_identifier(S), ?is_digit(S) orelse ?is_upcase(S) orelse ?is_downcase(S) orelse (S == $_)).
|
||||
|
||||
@@ -57,7 +57,7 @@ defmodule Behaviour do
|
||||
case Macro.extract_args(fun) do
|
||||
{ name, args } -> :ok
|
||||
:error ->
|
||||
raise ArgumentError, message: "invalid syntax in defcallback #{Macro.to_binary(fun)}"
|
||||
raise ArgumentError, message: "invalid syntax in defcallback #{Macro.to_string(fun)}"
|
||||
end
|
||||
|
||||
arity = length(args)
|
||||
@@ -88,13 +88,13 @@ defmodule Behaviour do
|
||||
def store_docs(module, line, name, arity) do
|
||||
doc = Module.get_attribute module, :doc
|
||||
Module.delete_attribute module, :doc
|
||||
Module.put_attribute module, :__behaviour_docs, { { name, arity }, line, doc }
|
||||
Module.put_attribute module, :__behaviour_docs__, { { name, arity }, line, doc }
|
||||
end
|
||||
|
||||
@doc false
|
||||
defmacro __using__(_) do
|
||||
quote do
|
||||
Module.register_attribute(__MODULE__, :__behaviour_docs, accumulate: true)
|
||||
Module.register_attribute(__MODULE__, :__behaviour_docs__, accumulate: true)
|
||||
@before_compile unquote(__MODULE__)
|
||||
import unquote(__MODULE__)
|
||||
end
|
||||
@@ -109,8 +109,8 @@ defmodule Behaviour do
|
||||
end
|
||||
|
||||
def __behaviour__(:docs) do
|
||||
@__behaviour_docs
|
||||
@__behaviour_docs__
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -74,19 +74,11 @@ defimpl Binary.Chars, for: Number do
|
||||
@doc """
|
||||
Simply converts the number (integer or a float) to a binary.
|
||||
"""
|
||||
|
||||
@digits 20
|
||||
@limit :math.pow(10, @digits)
|
||||
|
||||
def to_binary(thing) when is_integer(thing) do
|
||||
integer_to_binary(thing)
|
||||
end
|
||||
|
||||
def to_binary(thing) when thing > @limit do
|
||||
float_to_binary(thing, scientific: @digits)
|
||||
end
|
||||
|
||||
def to_binary(thing) do
|
||||
float_to_binary(thing, compact: true, decimals: @digits)
|
||||
list_to_binary(:io_lib_format.fwrite_g(thing))
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,462 +0,0 @@
|
||||
import Kernel, except: [inspect: 1]
|
||||
|
||||
defprotocol Binary.Inspect do
|
||||
@moduledoc """
|
||||
The `Binary.Inspect` protocol is responsible for
|
||||
converting any structure to a binary for textual
|
||||
representation. All basic data structures
|
||||
(tuple, list, function, pid, etc) implement the
|
||||
inspect protocol. Other structures are advised to
|
||||
implement the protocol in order to provide pretty
|
||||
printing.
|
||||
"""
|
||||
|
||||
def inspect(thing, opts)
|
||||
end
|
||||
|
||||
defmodule Binary.Inspect.Utils do
|
||||
@moduledoc false
|
||||
|
||||
## container_join
|
||||
|
||||
def container_join(tuple, first, last, opts) when is_tuple(tuple) do
|
||||
container_join(tuple_to_list(tuple), first, last, opts)
|
||||
end
|
||||
|
||||
def container_join(list, first, last, opts) do
|
||||
first <> do_container_join(list, opts, Keyword.get(opts, :limit, :infinity)) <> last
|
||||
end
|
||||
|
||||
defp do_container_join(_, _opts, 0) do
|
||||
"..."
|
||||
end
|
||||
|
||||
defp do_container_join([h], opts, _counter) do
|
||||
Kernel.inspect(h, opts)
|
||||
end
|
||||
|
||||
defp do_container_join([h|t], opts, counter) when is_list(t) do
|
||||
Kernel.inspect(h, opts) <> "," <> do_container_join(t, opts, decrement(counter))
|
||||
end
|
||||
|
||||
defp do_container_join([h|t], opts, _counter) do
|
||||
Kernel.inspect(h, opts) <> "|" <> Kernel.inspect(t, opts)
|
||||
end
|
||||
|
||||
defp do_container_join([], _opts, _counter) do
|
||||
""
|
||||
end
|
||||
|
||||
defp decrement(:infinity), do: :infinity
|
||||
defp decrement(counter), do: counter - 1
|
||||
|
||||
## escape
|
||||
|
||||
def escape(other, char) do
|
||||
b = do_escape(other, char, <<>>)
|
||||
<< char, b :: binary, char >>
|
||||
end
|
||||
|
||||
@compile {:inline, do_escape: 3}
|
||||
defp do_escape(<<>>, _char, binary), do: binary
|
||||
defp do_escape(<< char, t :: binary >>, char, binary) do
|
||||
do_escape(t, char, << binary :: binary, ?\\, char >>)
|
||||
end
|
||||
defp do_escape(<<?#, ?{, t :: binary>>, char, binary) do
|
||||
do_escape(t, char, << binary :: binary, ?\\, ?#, ?{ >>)
|
||||
end
|
||||
defp do_escape(<<?\a, t :: binary>>, char, binary) do
|
||||
do_escape(t, char, << binary :: binary, ?\\, ?a >>)
|
||||
end
|
||||
defp do_escape(<<?\b, t :: binary>>, char, binary) do
|
||||
do_escape(t, char, << binary :: binary, ?\\, ?b >>)
|
||||
end
|
||||
defp do_escape(<<?\d, t :: binary>>, char, binary) do
|
||||
do_escape(t, char, << binary :: binary, ?\\, ?d >>)
|
||||
end
|
||||
defp do_escape(<<?\e, t :: binary>>, char, binary) do
|
||||
do_escape(t, char, << binary :: binary, ?\\, ?e >>)
|
||||
end
|
||||
defp do_escape(<<?\f, t :: binary>>, char, binary) do
|
||||
do_escape(t, char, << binary :: binary, ?\\, ?f >>)
|
||||
end
|
||||
defp do_escape(<<?\n, t :: binary>>, char, binary) do
|
||||
do_escape(t, char, << binary :: binary, ?\\, ?n >>)
|
||||
end
|
||||
defp do_escape(<<?\r, t :: binary>>, char, binary) do
|
||||
do_escape(t, char, << binary :: binary, ?\\, ?r >>)
|
||||
end
|
||||
defp do_escape(<<?\\, t :: binary>>, char, binary) do
|
||||
do_escape(t, char, << binary :: binary, ?\\, ?\\ >>)
|
||||
end
|
||||
defp do_escape(<<?\t, t :: binary>>, char, binary) do
|
||||
do_escape(t, char, << binary :: binary, ?\\, ?t >>)
|
||||
end
|
||||
defp do_escape(<<?\v, t :: binary>>, char, binary) do
|
||||
do_escape(t, char, << binary :: binary, ?\\, ?v >>)
|
||||
end
|
||||
defp do_escape(<<h, t :: binary>>, char, binary) do
|
||||
do_escape(t, char, << binary :: binary, h >>)
|
||||
end
|
||||
end
|
||||
|
||||
defimpl Binary.Inspect, for: Atom do
|
||||
require Macro
|
||||
import Binary.Inspect.Utils
|
||||
|
||||
@moduledoc """
|
||||
Represents the atom as an Elixir term. The atoms false, true
|
||||
and nil are simply quoted. Modules are properly represented
|
||||
as modules using the dot notation.
|
||||
|
||||
Notice that in Elixir, all operators can be represented using
|
||||
literal atoms (`:+`, `:-`, etc).
|
||||
|
||||
## Examples
|
||||
|
||||
iex> inspect(:foo)
|
||||
":foo"
|
||||
iex> inspect(nil)
|
||||
"nil"
|
||||
iex> inspect(Foo.Bar)
|
||||
"Foo.Bar"
|
||||
|
||||
"""
|
||||
|
||||
def inspect(false, _), do: "false"
|
||||
def inspect(true, _), do: "true"
|
||||
def inspect(nil, _), do: "nil"
|
||||
def inspect(:"", _), do: ":\"\""
|
||||
def inspect(Elixir, _), do: "Elixir"
|
||||
|
||||
def inspect(atom, _) do
|
||||
binary = atom_to_binary(atom)
|
||||
|
||||
cond do
|
||||
valid_atom_identifier?(binary) ->
|
||||
":" <> binary
|
||||
valid_ref_identifier?(binary) ->
|
||||
Module.to_binary(atom)
|
||||
atom in Macro.binary_ops or atom in Macro.unary_ops ->
|
||||
":" <> binary
|
||||
true ->
|
||||
":" <> escape(binary, ?")
|
||||
end
|
||||
end
|
||||
|
||||
# Detect if atom is an atom alias (Elixir.Foo.Bar.Baz)
|
||||
|
||||
defp valid_ref_identifier?("Elixir" <> rest) do
|
||||
valid_ref_piece?(rest)
|
||||
end
|
||||
|
||||
defp valid_ref_identifier?(_), do: false
|
||||
|
||||
defp valid_ref_piece?(<<?., h, t :: binary>>) when h in ?A..?Z do
|
||||
valid_ref_piece? valid_identifier?(t)
|
||||
end
|
||||
|
||||
defp valid_ref_piece?(<<>>), do: true
|
||||
defp valid_ref_piece?(_), do: false
|
||||
|
||||
# Detect if atom
|
||||
|
||||
defp valid_atom_identifier?(<<h, t :: binary>>) when h in ?a..?z or h in ?A..?Z or h == ?_ do
|
||||
case valid_identifier?(t) do
|
||||
<<>> -> true
|
||||
<<??>> -> true
|
||||
<<?!>> -> true
|
||||
_ -> false
|
||||
end
|
||||
end
|
||||
|
||||
defp valid_atom_identifier?(_), do: false
|
||||
|
||||
defp valid_identifier?(<<h, t :: binary>>)
|
||||
when h in ?a..?z
|
||||
when h in ?A..?Z
|
||||
when h in ?0..?9
|
||||
when h == ?_ do
|
||||
valid_identifier? t
|
||||
end
|
||||
|
||||
defp valid_identifier?(other), do: other
|
||||
end
|
||||
|
||||
defimpl Binary.Inspect, for: BitString do
|
||||
import Binary.Inspect.Utils
|
||||
|
||||
@moduledoc %B"""
|
||||
Represents the string as itself escaping
|
||||
all necessary characters.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> inspect("bar")
|
||||
"\"bar\""
|
||||
iex> inspect("f\"oo")
|
||||
"\"f\\\"oo\""
|
||||
|
||||
"""
|
||||
|
||||
def inspect(thing, opts) when is_binary(thing) do
|
||||
if String.printable?(thing) do
|
||||
escape(thing, ?")
|
||||
else
|
||||
as_bitstring(thing, opts)
|
||||
end
|
||||
end
|
||||
|
||||
def inspect(thing, opts) do
|
||||
as_bitstring(thing, opts)
|
||||
end
|
||||
|
||||
## Bitstrings
|
||||
|
||||
defp as_bitstring(bitstring, opts) do
|
||||
"<<" <> each_bit(bitstring, Keyword.get(opts, :limit, :infinity)) <> ">>"
|
||||
end
|
||||
|
||||
defp each_bit(_, 0) do
|
||||
"..."
|
||||
end
|
||||
|
||||
defp each_bit(<<h, t :: bitstring>>, counter) when t != <<>> do
|
||||
integer_to_binary(h) <> "," <> each_bit(t, decrement(counter))
|
||||
end
|
||||
|
||||
defp each_bit(<<h :: size(8)>>, _counter) do
|
||||
integer_to_binary(h)
|
||||
end
|
||||
|
||||
defp each_bit(<<>>, _counter) do
|
||||
<<>>
|
||||
end
|
||||
|
||||
defp each_bit(bitstring, _counter) do
|
||||
size = bit_size(bitstring)
|
||||
<<h :: size(size)>> = bitstring
|
||||
integer_to_binary(h) <> "::size(" <> integer_to_binary(size) <> ")"
|
||||
end
|
||||
|
||||
defp decrement(:infinity), do: :infinity
|
||||
defp decrement(counter), do: counter - 1
|
||||
end
|
||||
|
||||
defimpl Binary.Inspect, for: List do
|
||||
import Binary.Inspect.Utils
|
||||
|
||||
@moduledoc %B"""
|
||||
Represents a list checking if it can be printed or not.
|
||||
If so, a single-quoted representation is returned,
|
||||
otherwise the brackets syntax is used.
|
||||
|
||||
Inspecting a list is conservative as it does not try
|
||||
to guess how the list is encoded. That said, `'josé'`
|
||||
will likely be inspected as `[106,111,115,195,169]`
|
||||
because we can't know if it is encoded in utf-8
|
||||
or iso-5569-1, which is common in Erlang libraries.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> inspect('bar')
|
||||
"'bar'"
|
||||
iex> inspect([0|'bar'])
|
||||
"[0,98,97,114]"
|
||||
iex> inspect([:foo,:bar])
|
||||
"[:foo,:bar]"
|
||||
|
||||
"""
|
||||
|
||||
def inspect([], _), do: "[]"
|
||||
|
||||
def inspect(thing, opts) do
|
||||
cond do
|
||||
:io_lib.printable_list(thing) ->
|
||||
escape(:unicode.characters_to_binary(thing), ?')
|
||||
keyword?(thing) ->
|
||||
"[" <> join_keywords(thing, opts) <> "]"
|
||||
true ->
|
||||
container_join(thing, "[", "]", opts)
|
||||
end
|
||||
end
|
||||
|
||||
defp join_keywords(thing, opts) do
|
||||
Enum.join(lc {key, value} inlist thing do
|
||||
key_to_binary(key, opts) <> ": " <> Kernel.inspect(value, opts)
|
||||
end, ", ")
|
||||
end
|
||||
|
||||
defp key_to_binary(key, opts) do
|
||||
case Binary.Inspect.Atom.inspect(key, opts) do
|
||||
":" <> right -> right
|
||||
other -> other
|
||||
end
|
||||
end
|
||||
|
||||
defp keyword?([{ key, _value } | rest]) when is_atom(key) do
|
||||
case atom_to_list(key) do
|
||||
'Elixir.' ++ _ -> false
|
||||
_ -> keyword?(rest)
|
||||
end
|
||||
end
|
||||
|
||||
defp keyword?([]), do: true
|
||||
defp keyword?(_other), do: false
|
||||
end
|
||||
|
||||
defimpl Binary.Inspect, for: Tuple do
|
||||
import Binary.Inspect.Utils
|
||||
|
||||
@moduledoc """
|
||||
Inspect tuples. If the tuple represents a record,
|
||||
it shows it nicely formatted using the access syntax.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> inspect({1,2,3})
|
||||
"{1,2,3}"
|
||||
iex> inspect(ArgumentError.new)
|
||||
"ArgumentError[message: \\\"argument error\\\"]"
|
||||
|
||||
"""
|
||||
|
||||
def inspect({}, _), do: "{}"
|
||||
|
||||
def inspect(tuple, opts) do
|
||||
unless opts[:raw] do
|
||||
record_inspect(tuple, opts)
|
||||
end || container_join(tuple, "{", "}", opts)
|
||||
end
|
||||
|
||||
## Helpers
|
||||
|
||||
defp record_inspect(record, opts) do
|
||||
list = tuple_to_list(record)
|
||||
[name|tail] = list
|
||||
|
||||
if (fields = record_fields(name)) && (length(fields) == size(record) - 1) do
|
||||
if Enum.first(tail) == :__exception__ do
|
||||
record_join(name, tl(fields), tl(tail), opts)
|
||||
else
|
||||
record_join(name, fields, tail, opts)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defp record_fields(name) do
|
||||
try do
|
||||
name.__record__(:fields)
|
||||
rescue
|
||||
_ -> nil
|
||||
end
|
||||
end
|
||||
|
||||
defp record_join(name, fields, tail, opts) do
|
||||
fields = lc { field, _ } inlist fields, do: field
|
||||
Binary.Inspect.Atom.inspect(name, opts) <> "[" <>
|
||||
record_join(fields, tail, opts) <> "]"
|
||||
end
|
||||
|
||||
defp record_join([f], [v], opts) do
|
||||
atom_to_binary(f, :utf8) <> ": " <> Kernel.inspect(v, opts)
|
||||
end
|
||||
|
||||
defp record_join([fh|ft], [vh|vt], opts) do
|
||||
atom_to_binary(fh, :utf8) <> ": " <>
|
||||
Kernel.inspect(vh, opts) <> ", " <>
|
||||
record_join(ft, vt, opts)
|
||||
end
|
||||
|
||||
defp record_join([], [], _opts) do
|
||||
""
|
||||
end
|
||||
end
|
||||
|
||||
defimpl Binary.Inspect, for: Number do
|
||||
@moduledoc """
|
||||
Represents the number as a binary.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> inspect(1)
|
||||
"1"
|
||||
|
||||
"""
|
||||
|
||||
@digits 20
|
||||
@limit :math.pow(10, @digits)
|
||||
|
||||
def inspect(thing, _) when is_integer(thing) do
|
||||
integer_to_binary(thing)
|
||||
end
|
||||
|
||||
def inspect(thing, _) when thing > @limit do
|
||||
float_to_binary(thing, scientific: @digits)
|
||||
end
|
||||
|
||||
def inspect(thing, _) do
|
||||
float_to_binary(thing, compact: true, decimals: @digits)
|
||||
end
|
||||
end
|
||||
|
||||
defimpl Binary.Inspect, for: Regex do
|
||||
@moduledoc %B"""
|
||||
Represents the Regex using the `%r""` syntax.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> inspect(%r/foo/m)
|
||||
"%r\"foo\"m"
|
||||
|
||||
"""
|
||||
|
||||
def inspect(regex, _opts) when size(regex) == 5 do
|
||||
"%r" <> Kernel.inspect(Regex.source(regex), []) <> Regex.opts(regex)
|
||||
end
|
||||
|
||||
def inspect(other, opts) do
|
||||
Kernel.inspect other, Keyword.put(opts, :raw, true)
|
||||
end
|
||||
end
|
||||
|
||||
defimpl Binary.Inspect, for: Function do
|
||||
@moduledoc """
|
||||
Inspect functions, when possible, in a literal form.
|
||||
"""
|
||||
|
||||
def inspect(function, _opts) do
|
||||
fun_info = :erlang.fun_info(function)
|
||||
if fun_info[:type] == :external and fun_info[:env] == [] do
|
||||
"function(#{Kernel.inspect(fun_info[:module])}.#{fun_info[:name]}/#{fun_info[:arity]})"
|
||||
else
|
||||
'#Fun' ++ rest = :erlang.fun_to_list(function)
|
||||
"#Function" <> list_to_binary(rest)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defimpl Binary.Inspect, for: PID do
|
||||
@moduledoc "Inspect PIDs"
|
||||
|
||||
def inspect(pid, _) do
|
||||
"#PID" <> list_to_binary pid_to_list(pid)
|
||||
end
|
||||
end
|
||||
|
||||
defimpl Binary.Inspect, for: Port do
|
||||
@moduledoc "Inspect ports"
|
||||
|
||||
def inspect(port, _) do
|
||||
list_to_binary :erlang.port_to_list(port)
|
||||
end
|
||||
end
|
||||
|
||||
defimpl Binary.Inspect, for: Reference do
|
||||
@moduledoc "Inspect references"
|
||||
|
||||
def inspect(ref, _) do
|
||||
'#Ref' ++ rest = :erlang.ref_to_list(ref)
|
||||
"#Reference" <> list_to_binary(rest)
|
||||
end
|
||||
end
|
||||
+12
-12
@@ -46,83 +46,83 @@ defmodule Bitwise do
|
||||
Bitwise not.
|
||||
"""
|
||||
defmacro bnot(expr) do
|
||||
quote do: __op__ :bnot, unquote(expr)
|
||||
quote do: __op__(:bnot, unquote(expr))
|
||||
end
|
||||
|
||||
@doc """
|
||||
Bitwise not as operator.
|
||||
"""
|
||||
defmacro ~~~expr do
|
||||
quote do: __op__ :bnot, unquote(expr)
|
||||
quote do: __op__(:bnot, unquote(expr))
|
||||
end
|
||||
|
||||
@doc """
|
||||
Bitwise and.
|
||||
"""
|
||||
defmacro band(left, right) do
|
||||
quote do: __op__ :band, unquote(left), unquote(right)
|
||||
quote do: __op__(:band, unquote(left), unquote(right))
|
||||
end
|
||||
|
||||
@doc """
|
||||
Bitwise and as operator.
|
||||
"""
|
||||
defmacro left &&& right do
|
||||
quote do: __op__ :band, unquote(left), unquote(right)
|
||||
quote do: __op__(:band, unquote(left), unquote(right))
|
||||
end
|
||||
|
||||
@doc """
|
||||
Bitwise or.
|
||||
"""
|
||||
defmacro bor(left, right) do
|
||||
quote do: __op__ :bor, unquote(left), unquote(right)
|
||||
quote do: __op__(:bor, unquote(left), unquote(right))
|
||||
end
|
||||
|
||||
@doc """
|
||||
Bitwise or as operator.
|
||||
"""
|
||||
defmacro left ||| right do
|
||||
quote do: __op__ :bor, unquote(left), unquote(right)
|
||||
quote do: __op__(:bor, unquote(left), unquote(right))
|
||||
end
|
||||
|
||||
@doc """
|
||||
Bitwise xor.
|
||||
"""
|
||||
defmacro bxor(left, right) do
|
||||
quote do: __op__ :bxor, unquote(left), unquote(right)
|
||||
quote do: __op__(:bxor, unquote(left), unquote(right))
|
||||
end
|
||||
|
||||
@doc """
|
||||
Bitwise xor as operator.
|
||||
"""
|
||||
defmacro left ^^^ right do
|
||||
quote do: __op__ :bxor, unquote(left), unquote(right)
|
||||
quote do: __op__(:bxor, unquote(left), unquote(right))
|
||||
end
|
||||
|
||||
@doc """
|
||||
Arithmetic bitshift left.
|
||||
"""
|
||||
defmacro bsl(left, right) do
|
||||
quote do: __op__ :bsl, unquote(left), unquote(right)
|
||||
quote do: __op__(:bsl, unquote(left), unquote(right))
|
||||
end
|
||||
|
||||
@doc """
|
||||
Arithmetic bitshift left as operator.
|
||||
"""
|
||||
defmacro left <<< right do
|
||||
quote do: __op__ :bsl, unquote(left), unquote(right)
|
||||
quote do: __op__(:bsl, unquote(left), unquote(right))
|
||||
end
|
||||
|
||||
@doc """
|
||||
Arithmetic bitshift right.
|
||||
"""
|
||||
defmacro bsr(left, right) do
|
||||
quote do: __op__ :bsr, unquote(left), unquote(right)
|
||||
quote do: __op__(:bsr, unquote(left), unquote(right))
|
||||
end
|
||||
|
||||
@doc """
|
||||
Arithmetic bitshift right as operator.
|
||||
"""
|
||||
defmacro left >>> right do
|
||||
quote do: __op__ :bsr, unquote(left), unquote(right)
|
||||
quote do: __op__(:bsr, unquote(left), unquote(right))
|
||||
end
|
||||
end
|
||||
|
||||
+113
-50
@@ -6,13 +6,11 @@ defmodule Code do
|
||||
end
|
||||
|
||||
@moduledoc """
|
||||
The Code module is responsible to manage code compilation,
|
||||
The Code module is responsible for managing code compilation,
|
||||
code evaluation and code loading.
|
||||
|
||||
It complements (Erlang's code module)[1] to add behavior
|
||||
which is specific to Elixir.
|
||||
|
||||
[1]: (www.erlang.org/doc/man/code.html)
|
||||
It complements [Erlang's code module](http://www.erlang.org/doc/man/code.html)
|
||||
to add behavior which is specific to Elixir.
|
||||
|
||||
"""
|
||||
|
||||
@@ -26,8 +24,8 @@ defmodule Code do
|
||||
@doc """
|
||||
Removes the given files from the loaded files list.
|
||||
The modules defined in the file are not removed,
|
||||
calling this function only removes it from the list,
|
||||
allowing it to be required again.
|
||||
calling this function only removes them from the list,
|
||||
allowing them to be required again.
|
||||
"""
|
||||
def unload_files(files) do
|
||||
:elixir_code_server.cast { :unload_files, files }
|
||||
@@ -35,7 +33,7 @@ defmodule Code do
|
||||
|
||||
@doc """
|
||||
Appends a path to Erlang VM code path.
|
||||
The path is expanded with `Path.expand` before added.
|
||||
The path is expanded with `Path.expand` before being appended.
|
||||
"""
|
||||
def append_path(path) do
|
||||
:code.add_pathz(Path.expand to_char_list(path))
|
||||
@@ -43,7 +41,7 @@ defmodule Code do
|
||||
|
||||
@doc """
|
||||
Prepends a path to Erlang VM code path.
|
||||
The path is expanded with `Path.expand` before added.
|
||||
The path is expanded with `Path.expand` before being prepended.
|
||||
"""
|
||||
def prepend_path(path) do
|
||||
:code.add_patha(Path.expand to_char_list(path))
|
||||
@@ -51,7 +49,7 @@ defmodule Code do
|
||||
|
||||
@doc """
|
||||
Deletes a path from Erlang VM code path.
|
||||
The path is expanded with `Path.expand` before deleted.
|
||||
The path is expanded with `Path.expand` before being deleted.
|
||||
"""
|
||||
def delete_path(path) do
|
||||
:code.del_path(Path.expand to_char_list(path))
|
||||
@@ -59,27 +57,29 @@ defmodule Code do
|
||||
|
||||
@doc """
|
||||
Evaluates the contents given by string. The second argument is the
|
||||
binding (which should be a keyword) followed by a keyword list of
|
||||
binding (which should be a keyword), followed by a keyword list of
|
||||
environment options. Those options can be:
|
||||
|
||||
* `:file` - the file to be considered in the evaluation
|
||||
* `:line` - the line the script starts
|
||||
* `:line` - the line on which the script starts
|
||||
* `:delegate_locals_to` - delegate local calls to the given module,
|
||||
the default is to not delegate
|
||||
|
||||
Besides, the following scope values can be configured:
|
||||
Additionally, the following scope values can be configured:
|
||||
|
||||
* `:aliases` - a list of tuples with the alias and its target
|
||||
* `:requires` - a list of modules required
|
||||
* `:functions` - a list of tuples where the first element is a module
|
||||
and the second a list of imported function names and arity
|
||||
and the second a list of imported function names and arity. The list
|
||||
of function names and arity must be sorted;
|
||||
* `:macros` - a list of tuples where the first element is a module
|
||||
and the second a list of imported macro names and arity
|
||||
and the second a list of imported macro names and arity. The list
|
||||
of function names and arity must be sorted;
|
||||
|
||||
Notice that setting any ov the values above overrides Elixir default
|
||||
Notice that setting any of the values above overrides Elixir default
|
||||
values. For example, setting `:requires` to `[]`, will no longer
|
||||
automatically required the `Kernel` module, in the same way setting
|
||||
`:macros` will no longer auto-import `Kernel` macros as `if`, `case`,
|
||||
automatically require the `Kernel` module; in the same way setting
|
||||
`:macros` will no longer auto-import `Kernel` macros like `if`, `case`,
|
||||
etc.
|
||||
|
||||
## Examples
|
||||
@@ -87,7 +87,7 @@ defmodule Code do
|
||||
iex> Code.eval_string("a + b", [a: 1, b: 2], file: __ENV__.file, line: __ENV__.line)
|
||||
{ 3, [ {:a, 1}, {:b, 2} ] }
|
||||
|
||||
For convenience, you can my pass `__ENV__` as argument and
|
||||
For convenience, you can pass `__ENV__` as an argument and
|
||||
all imports, requires and aliases will be automatically carried
|
||||
over:
|
||||
|
||||
@@ -98,10 +98,15 @@ defmodule Code do
|
||||
def eval_string(string, binding // [], opts // [])
|
||||
|
||||
def eval_string(string, binding, Macro.Env[] = env) do
|
||||
eval_string(string, binding, env.to_keywords)
|
||||
do_eval_string(string, binding, env.to_keywords)
|
||||
end
|
||||
|
||||
def eval_string(string, binding, opts) do
|
||||
def eval_string(string, binding, opts) when is_list(opts) do
|
||||
validate_eval_opts(opts)
|
||||
do_eval_string(string, binding, opts)
|
||||
end
|
||||
|
||||
defp do_eval_string(string, binding, opts) when is_list(binding) do
|
||||
{ value, binding, _scope } =
|
||||
:elixir.eval :unicode.characters_to_list(string), binding, opts
|
||||
{ value, binding }
|
||||
@@ -119,7 +124,7 @@ defmodule Code do
|
||||
...> Code.eval_quoted(contents, [a: 1, b: 2], file: __ENV__.file, line: __ENV__.line)
|
||||
{ 3, [ {:a, 1}, {:b, 2} ] }
|
||||
|
||||
For convenience, you can my pass `__ENV__` as argument and
|
||||
For convenience, you can pass `__ENV__` as an argument and
|
||||
all options will be automatically extracted from the environment:
|
||||
|
||||
iex> contents = quote(hygiene: [vars: false], do: a + b)
|
||||
@@ -130,17 +135,65 @@ defmodule Code do
|
||||
def eval_quoted(quoted, binding // [], opts // [])
|
||||
|
||||
def eval_quoted(quoted, binding, Macro.Env[] = env) do
|
||||
eval_quoted(quoted, binding, env.to_keywords)
|
||||
do_eval_quoted(quoted, binding, env.to_keywords)
|
||||
end
|
||||
|
||||
def eval_quoted(quoted, binding, opts) do
|
||||
def eval_quoted(quoted, binding, opts) when is_list(opts) do
|
||||
validate_eval_opts(opts)
|
||||
do_eval_quoted(quoted, binding, opts)
|
||||
end
|
||||
|
||||
defp do_eval_quoted(quoted, binding, opts) when is_list(binding) do
|
||||
{ value, binding, _scope } =
|
||||
:elixir.eval_quoted [quoted], binding, opts
|
||||
{ value, binding }
|
||||
end
|
||||
|
||||
defp validate_eval_opts(opts) do
|
||||
if f = opts[:functions], do: validate_imports(:functions, f)
|
||||
if m = opts[:macros], do: validate_imports(:macros, m)
|
||||
if a = opts[:aliases], do: validate_aliases(:aliases, a)
|
||||
if r = opts[:requires], do: validate_requires(:requires, r)
|
||||
end
|
||||
|
||||
defp validate_requires(kind, requires) do
|
||||
valid = is_list(requires) and Enum.all?(requires, is_atom(&1))
|
||||
|
||||
unless valid do
|
||||
raise ArgumentError, "expected :#{kind} option given to eval in the format: [module]"
|
||||
end
|
||||
end
|
||||
|
||||
defp validate_aliases(kind, aliases) do
|
||||
valid = is_list(aliases) and Enum.all?(aliases, fn { k, v } ->
|
||||
is_atom(k) and is_atom(v)
|
||||
end)
|
||||
|
||||
unless valid do
|
||||
raise ArgumentError, "expected :#{kind} option given to eval in the format: [{ module, module }]"
|
||||
end
|
||||
end
|
||||
|
||||
defp validate_imports(kind, imports) do
|
||||
valid = is_list(imports) and Enum.all?(imports, fn { k, v } ->
|
||||
is_atom(k) and is_list(v) and Enum.all?(v, fn { name, arity } ->
|
||||
is_atom(name) and is_integer(arity)
|
||||
end)
|
||||
end)
|
||||
|
||||
unless valid do
|
||||
raise ArgumentError, "expected :#{kind} option given to eval in the format: [{ module, [{ name, arity }] }]"
|
||||
end
|
||||
end
|
||||
|
||||
@doc false
|
||||
def string_to_ast(string, opts // []) do
|
||||
IO.write "[WARNING] Code.string_to_ast is deprecated, please use Code.string_to_quoted instead\n#{Exception.format_stacktrace}"
|
||||
string_to_quoted(string, opts)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Converts the given string to AST. It returns `{ :ok, ast }`
|
||||
Converts the given string to its quoted form. Returns `{ :ok, quoted_form }`
|
||||
if it succeeds, `{ :error, { line, error, token } }` otherwise.
|
||||
|
||||
## Options
|
||||
@@ -153,41 +206,47 @@ defmodule Code do
|
||||
* `:existing_atoms_only` - When true, raises an error
|
||||
when non-existing atoms are found by the tokenizer.
|
||||
|
||||
## Macro.to_binary/1
|
||||
## Macro.to_string/1
|
||||
|
||||
The opposite of converting a string to its AST is
|
||||
`Macro.to_binary`, which converts a AST to a binary
|
||||
The opposite of converting a string to its quoted form is
|
||||
`Macro.to_string`, which converts a quoted form to a string/binary
|
||||
representation.
|
||||
"""
|
||||
def string_to_ast(string, opts // []) do
|
||||
def string_to_quoted(string, opts // []) do
|
||||
file = Keyword.get opts, :file, "nofile"
|
||||
line = Keyword.get opts, :line, 1
|
||||
res = :elixir_translator.forms(:unicode.characters_to_list(string), line, file, opts)
|
||||
|
||||
case res do
|
||||
{ :ok, ast } -> { :ok, unpack_ast(line, ast) }
|
||||
{ :ok, forms } -> { :ok, unpack_quote(line, forms) }
|
||||
_ -> res
|
||||
end
|
||||
end
|
||||
|
||||
@doc false
|
||||
def string_to_ast!(string, opts // []) do
|
||||
IO.write "[WARNING] Code.string_to_ast! is deprecated, please use Code.string_to_quoted! instead\n#{Exception.format_stacktrace}"
|
||||
string_to_quoted!(string, opts)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Converts the given string to AST. It returns the ast if it succeeds,
|
||||
Converts the given string to its quoted form. It returns the ast if it succeeds,
|
||||
raises an exception otherwise. The exception is a TokenMissingError
|
||||
in case a token is missing (usually because the expression is incomplete),
|
||||
SyntaxError otherwise.
|
||||
|
||||
Check `Code.string_to_ast/2` for options information.
|
||||
Check `Code.string_to_quoted/2` for options information.
|
||||
"""
|
||||
def string_to_ast!(string, opts // []) do
|
||||
def string_to_quoted!(string, opts // []) do
|
||||
file = Keyword.get opts, :file, "nofile"
|
||||
line = Keyword.get opts, :line, 1
|
||||
res = :elixir_translator.forms!(:unicode.characters_to_list(string), line, file, opts)
|
||||
unpack_ast(line, res)
|
||||
unpack_quote(line, res)
|
||||
end
|
||||
|
||||
defp unpack_ast(_line, []), do: nil
|
||||
defp unpack_ast(_line, [forms]) when not is_list(forms), do: forms
|
||||
defp unpack_ast(line, forms), do: { :__block__, [line: line], forms }
|
||||
defp unpack_quote(_line, []), do: nil
|
||||
defp unpack_quote(_line, [forms]) when not is_list(forms), do: forms
|
||||
defp unpack_quote(line, forms), do: { :__block__, [line: line], forms }
|
||||
|
||||
@doc """
|
||||
Loads the given `file`. Accepts `relative_to` as an argument to tell where
|
||||
@@ -197,7 +256,7 @@ defmodule Code do
|
||||
|
||||
Notice that if `load_file` is invoked by different processes
|
||||
concurrently, the target file will be invoked concurrently
|
||||
in many times. I.e. if `load_file` is called N times with
|
||||
many times. I.e. if `load_file` is called N times with
|
||||
a given file, the given file will be loaded N times. Check
|
||||
`require_file` if you don't want a file to be loaded concurrently.
|
||||
"""
|
||||
@@ -239,25 +298,29 @@ defmodule Code do
|
||||
|
||||
@doc """
|
||||
Loads the compilation options from the code server.
|
||||
Check compiler_options/1 for more information.
|
||||
Check `compiler_options/1` for more information.
|
||||
"""
|
||||
def compiler_options do
|
||||
:elixir_code_server.call :compiler_options
|
||||
end
|
||||
|
||||
@doc """
|
||||
Sets compilation options. Those options are global
|
||||
Sets compilation options. These options are global
|
||||
since they are stored by Elixir's Code Server.
|
||||
|
||||
Available options are:
|
||||
|
||||
* docs - when true, retain documentation in the compiled module.
|
||||
True by default;
|
||||
* debug_info - when true, retain debug information in the compiled module.
|
||||
This allows a developer to reconstruct the original source
|
||||
code, for such reasons, false by default;
|
||||
* ignore_module_conflict - when true, override modules that were already defined
|
||||
without raising errors, false by default;
|
||||
* `:docs` - when true, retain documentation in the compiled module,
|
||||
true by default;
|
||||
|
||||
* `:debug_info` - when true, retain debug information in the compiled module.
|
||||
This allows a developer to reconstruct the original source
|
||||
code, for such reasons, false by default;
|
||||
|
||||
* `:ignore_module_conflict` - when true, override modules that were already defined
|
||||
without raising errors, false by default;
|
||||
|
||||
* `:warnings_as_errors` - cause compilation to fail when warnings are spewed;
|
||||
|
||||
"""
|
||||
def compiler_options(opts) do
|
||||
@@ -289,7 +352,7 @@ defmodule Code do
|
||||
loaded, it works as no-op. If the module was not loaded yet,
|
||||
it tries to load it.
|
||||
|
||||
If it succeeds loading the module anyhow, it returns
|
||||
If it succeeds loading the module, it returns
|
||||
`{ :module, module }`. If not, returns `{ :error, reason }` with
|
||||
the error reason.
|
||||
|
||||
@@ -297,7 +360,7 @@ defmodule Code do
|
||||
|
||||
Erlang has two modes to load code: interactive and embedded.
|
||||
|
||||
By default, the Erlang VM runs on interactive mode, where modules
|
||||
By default, the Erlang VM runs in interactive mode, where modules
|
||||
are loaded as needed. In embedded mode the opposite happens, as all
|
||||
modules need to be loaded upfront or explicitly.
|
||||
|
||||
@@ -340,7 +403,7 @@ defmodule Code do
|
||||
loaded yet, it checks if it needs to be compiled first and just
|
||||
then tries to load it.
|
||||
|
||||
If it succeeds loading the module anyhow, it returns
|
||||
If it succeeds loading the module, it returns
|
||||
`{ :module, module }`. If not, returns `{ :error, reason }` with
|
||||
the error reason.
|
||||
|
||||
|
||||
@@ -73,6 +73,8 @@ defmodule Dict do
|
||||
elem(unquote(dict), 0)
|
||||
is_list(unquote(dict)) ->
|
||||
ListDict
|
||||
true ->
|
||||
unsupported_dict(unquote(dict))
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -387,7 +389,7 @@ defmodule Dict do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns a new dict where the the given `keys` a removed from `dict`.
|
||||
Returns a new dict where the given `keys` are removed from `dict`.
|
||||
Any non-member keys are ignored.
|
||||
|
||||
## Examples
|
||||
@@ -480,4 +482,8 @@ defmodule Dict do
|
||||
def to_list(dict) do
|
||||
target(dict).to_list(dict)
|
||||
end
|
||||
|
||||
defp unsupported_dict(dict) do
|
||||
raise ArgumentError, message: "unsupported dict: #{inspect dict}"
|
||||
end
|
||||
end
|
||||
|
||||
+135
-75
@@ -8,7 +8,7 @@ defprotocol Enumerable do
|
||||
|
||||
For example, in the expression
|
||||
|
||||
Enum.map([1,2,3], &1 * 2)
|
||||
Enum.map([1, 2, 3], &1 * 2)
|
||||
|
||||
`Enum.map` invokes `Enumerable.reduce` to perform the reducing operation
|
||||
that builds a mapped list by calling the mapping function `&1 * 2` on every
|
||||
@@ -58,13 +58,15 @@ defmodule Enum do
|
||||
flavours. If a given collection implements the mentioned protocol (like
|
||||
list, for instance), you can do:
|
||||
|
||||
Enum.map([1,2,3], fn(x) -> x * 2 end)
|
||||
Enum.map([1, 2, 3], fn(x) -> x * 2 end)
|
||||
|
||||
Depending on the type of the collection, the user-provided function will
|
||||
accept a certain type of argument. For dicts, the argument is always a
|
||||
`{ key, value }` tuple.
|
||||
"""
|
||||
|
||||
@compile :inline_list_funcs
|
||||
|
||||
@type t :: Enumerable.t
|
||||
@type element :: any
|
||||
@type index :: non_neg_integer
|
||||
@@ -91,7 +93,7 @@ defmodule Enum do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Enum.count([1,2,3])
|
||||
iex> Enum.count([1, 2, 3])
|
||||
3
|
||||
|
||||
"""
|
||||
@@ -102,6 +104,11 @@ defmodule Enum do
|
||||
|
||||
@doc """
|
||||
Counts for how many items the function returns true.
|
||||
|
||||
## Examples
|
||||
iex> Enum.count([1, 2, 3, 4, 5], fn(x) -> rem(x, 2) == 0 end)
|
||||
2
|
||||
|
||||
"""
|
||||
@spec count(t, (element -> as_boolean(term))) :: non_neg_integer
|
||||
def count(collection, fun) do
|
||||
@@ -117,18 +124,18 @@ defmodule Enum do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Enum.all?([2,4,6], fn(x) -> rem(x, 2) == 0 end)
|
||||
iex> Enum.all?([2, 4, 6], fn(x) -> rem(x, 2) == 0 end)
|
||||
true
|
||||
|
||||
iex> Enum.all?([2,3,4], fn(x) -> rem(x, 2) == 0 end)
|
||||
iex> Enum.all?([2, 3, 4], fn(x) -> rem(x, 2) == 0 end)
|
||||
false
|
||||
|
||||
If no function is given, it defaults to checking if
|
||||
all items in the collection evaluate to true.
|
||||
|
||||
iex> Enum.all?([1,2,3])
|
||||
iex> Enum.all?([1, 2, 3])
|
||||
true
|
||||
iex> Enum.all?([1,nil,3])
|
||||
iex> Enum.all?([1, nil, 3])
|
||||
false
|
||||
|
||||
"""
|
||||
@@ -155,18 +162,18 @@ defmodule Enum do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Enum.any?([2,4,6], fn(x) -> rem(x, 2) == 1 end)
|
||||
iex> Enum.any?([2, 4, 6], fn(x) -> rem(x, 2) == 1 end)
|
||||
false
|
||||
|
||||
iex> Enum.any?([2,3,4], fn(x) -> rem(x, 2) == 1 end)
|
||||
iex> Enum.any?([2, 3, 4], fn(x) -> rem(x, 2) == 1 end)
|
||||
true
|
||||
|
||||
If no function is given, it defaults to checking if
|
||||
at least one item in the collection evaluates to true.
|
||||
|
||||
iex> Enum.any?([false,false,false])
|
||||
iex> Enum.any?([false, false, false])
|
||||
false
|
||||
iex> Enum.any?([false,true,false])
|
||||
iex> Enum.any?([false, true, false])
|
||||
true
|
||||
|
||||
"""
|
||||
@@ -194,16 +201,16 @@ defmodule Enum do
|
||||
|
||||
Expects an ordered collection.
|
||||
|
||||
## Examples
|
||||
## Examples
|
||||
|
||||
iex> Enum.at([2,4,6], 0)
|
||||
2
|
||||
iex> Enum.at([2,4,6], 2)
|
||||
6
|
||||
iex> Enum.at([2,4,6], 4)
|
||||
nil
|
||||
iex> Enum.at([2,4,6], 4, :none)
|
||||
:none
|
||||
iex> Enum.at([2, 4, 6], 0)
|
||||
2
|
||||
iex> Enum.at([2, 4, 6], 2)
|
||||
6
|
||||
iex> Enum.at([2, 4, 6], 4)
|
||||
nil
|
||||
iex> Enum.at([2, 4, 6], 4, :none)
|
||||
:none
|
||||
|
||||
"""
|
||||
@spec at(t, index) :: element | nil
|
||||
@@ -221,11 +228,11 @@ defmodule Enum do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Enum.drop([1,2,3], 2)
|
||||
iex> Enum.drop([1, 2, 3], 2)
|
||||
[3]
|
||||
iex> Enum.drop([1,2,3], 10)
|
||||
iex> Enum.drop([1, 2, 3], 10)
|
||||
[]
|
||||
iex> Enum.drop([1,2,3], 0)
|
||||
iex> Enum.drop([1, 2, 3], 0)
|
||||
[1,2,3]
|
||||
|
||||
"""
|
||||
@@ -253,7 +260,7 @@ defmodule Enum do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Enum.drop_while([1,2,3,4,5], fn(x) -> x < 3 end)
|
||||
iex> Enum.drop_while([1, 2, 3, 4, 5], fn(x) -> x < 3 end)
|
||||
[3,4,5]
|
||||
"""
|
||||
@spec drop_while(t, (element -> as_boolean(term))) :: list
|
||||
@@ -270,19 +277,24 @@ defmodule Enum do
|
||||
|
||||
@doc """
|
||||
Invokes the given `fun` for each item in the `collection`.
|
||||
Returns the `collection` itself. `fun` can take two parameters,
|
||||
in which case the second parameter will be the iteration index.
|
||||
Returns `:ok`.
|
||||
|
||||
## Examples
|
||||
|
||||
Enum.each(['some', 'example'], fn(x) -> IO.puts x end)
|
||||
Enum.each(["some", "example"], fn(x) -> IO.puts x end)
|
||||
"some"
|
||||
"example"
|
||||
#=> :ok
|
||||
|
||||
"""
|
||||
@spec each(t, (element -> any) | (element, index -> any)) :: :ok
|
||||
def each(collection, fun) when is_list(collection) do
|
||||
cond do
|
||||
is_function(fun, 1) -> :lists.foreach(fun, collection)
|
||||
is_function(fun, 2) -> :lists.foldl(fn(h, idx) -> fun.(h, idx); idx + 1 end, 0, collection)
|
||||
is_function(fun, 2) ->
|
||||
IO.write "[WARNING] Passing a funtion of arity 2 to Enum.each/2 is deprecated, " <>
|
||||
"please use Stream.with_index/1 instead\n#{Exception.format_stacktrace}"
|
||||
:lists.foldl(fn(h, idx) -> fun.(h, idx); idx + 1 end, 0, collection)
|
||||
end
|
||||
|
||||
:ok
|
||||
@@ -297,6 +309,9 @@ defmodule Enum do
|
||||
end)
|
||||
|
||||
is_function(fun, 2) ->
|
||||
IO.write "[WARNING] Passing a funtion of arity 2 to Enum.each/2 is deprecated, " <>
|
||||
"please use Stream.with_index/1 instead\n#{Exception.format_stacktrace}"
|
||||
|
||||
Enumerable.reduce(collection, 0, fn(entry, idx) ->
|
||||
fun.(entry, idx)
|
||||
idx + 1
|
||||
@@ -313,7 +328,7 @@ defmodule Enum do
|
||||
|
||||
iex> Enum.empty?([])
|
||||
true
|
||||
iex> Enum.empty?([1,2,3])
|
||||
iex> Enum.empty?([1, 2, 3])
|
||||
false
|
||||
|
||||
"""
|
||||
@@ -334,14 +349,14 @@ defmodule Enum do
|
||||
|
||||
Expects an ordered collection.
|
||||
|
||||
## Examples
|
||||
## Examples
|
||||
|
||||
iex> Enum.fetch([2,4,6], 0)
|
||||
{ :ok, 2 }
|
||||
iex> Enum.fetch([2,4,6], 2)
|
||||
{ :ok, 6 }
|
||||
iex> Enum.fetch([2,4,6], 4)
|
||||
:error
|
||||
iex> Enum.fetch([2, 4, 6], 0)
|
||||
{ :ok, 2 }
|
||||
iex> Enum.fetch([2, 4, 6], 2)
|
||||
{ :ok, 6 }
|
||||
iex> Enum.fetch([2, 4, 6], 4)
|
||||
:error
|
||||
|
||||
"""
|
||||
@spec fetch(t, index) :: { :ok, element } | :error
|
||||
@@ -370,13 +385,13 @@ defmodule Enum do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Enum.fetch!([2,4,6], 0)
|
||||
iex> Enum.fetch!([2, 4, 6], 0)
|
||||
2
|
||||
|
||||
iex> Enum.fetch!([2,4,6], 2)
|
||||
iex> Enum.fetch!([2, 4, 6], 2)
|
||||
6
|
||||
|
||||
iex> Enum.fetch!([2,4,6], 4)
|
||||
iex> Enum.fetch!([2, 4, 6], 4)
|
||||
** (Enum.OutOfBoundsError) out of bounds error
|
||||
|
||||
"""
|
||||
@@ -435,13 +450,13 @@ defmodule Enum do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Enum.find([2,4,6], fn(x) -> rem(x, 2) == 1 end)
|
||||
iex> Enum.find([2, 4, 6], fn(x) -> rem(x, 2) == 1 end)
|
||||
nil
|
||||
|
||||
iex> Enum.find([2,4,6], 0, fn(x) -> rem(x, 2) == 1 end)
|
||||
iex> Enum.find([2, 4, 6], 0, fn(x) -> rem(x, 2) == 1 end)
|
||||
0
|
||||
|
||||
iex> Enum.find([2,3,4], fn(x) -> rem(x, 2) == 1 end)
|
||||
iex> Enum.find([2, 3, 4], fn(x) -> rem(x, 2) == 1 end)
|
||||
3
|
||||
|
||||
"""
|
||||
@@ -468,10 +483,10 @@ defmodule Enum do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Enum.find_value([2,4,6], fn(x) -> rem(x, 2) == 1 end)
|
||||
iex> Enum.find_value([2, 4, 6], fn(x) -> rem(x, 2) == 1 end)
|
||||
nil
|
||||
|
||||
iex> Enum.find_value([2,3,4], fn(x) -> rem(x, 2) == 1 end)
|
||||
iex> Enum.find_value([2, 3, 4], fn(x) -> rem(x, 2) == 1 end)
|
||||
true
|
||||
|
||||
"""
|
||||
@@ -501,10 +516,10 @@ defmodule Enum do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Enum.find_index([2,4,6], fn(x) -> rem(x, 2) == 1 end)
|
||||
iex> Enum.find_index([2, 4, 6], fn(x) -> rem(x, 2) == 1 end)
|
||||
nil
|
||||
|
||||
iex> Enum.find_index([2,3,4], fn(x) -> rem(x, 2) == 1 end)
|
||||
iex> Enum.find_index([2, 3, 4], fn(x) -> rem(x, 2) == 1 end)
|
||||
1
|
||||
|
||||
"""
|
||||
@@ -529,7 +544,7 @@ defmodule Enum do
|
||||
|
||||
iex> Enum.first([])
|
||||
nil
|
||||
iex> Enum.first([1,2,3])
|
||||
iex> Enum.first([1, 2, 3])
|
||||
1
|
||||
|
||||
"""
|
||||
@@ -557,11 +572,11 @@ defmodule Enum do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Enum.join([1,2,3])
|
||||
iex> Enum.join([1, 2, 3])
|
||||
"123"
|
||||
iex> Enum.join([1,2,3], " = ")
|
||||
iex> Enum.join([1, 2, 3], " = ")
|
||||
"1 = 2 = 3"
|
||||
iex> Enum.join([1,2,3], ' = ')
|
||||
iex> Enum.join([1, 2, 3], ' = ')
|
||||
'1 = 2 = 3'
|
||||
|
||||
"""
|
||||
@@ -583,8 +598,6 @@ defmodule Enum do
|
||||
@doc """
|
||||
Returns a new collection, where each item is the result
|
||||
of invoking `fun` on each corresponding item of `collection`.
|
||||
`fun` can take two parameters, in which case the second parameter
|
||||
will be the iteration index.
|
||||
|
||||
For dicts, the function accepts a key-value tuple.
|
||||
|
||||
@@ -603,6 +616,8 @@ defmodule Enum do
|
||||
is_function(fun, 1) ->
|
||||
lc item inlist collection, do: fun.(item)
|
||||
is_function(fun, 2) ->
|
||||
IO.write "[WARNING] Passing a funtion of arity 2 to Enum.map/2 is deprecated, " <>
|
||||
"please use Stream.with_index/1 instead\n#{Exception.format_stacktrace}"
|
||||
mapper = fn(h, idx) -> { fun.(h, idx), idx + 1 } end
|
||||
{ list, _ } = :lists.mapfoldl(mapper, 0, collection)
|
||||
list
|
||||
@@ -627,11 +642,11 @@ defmodule Enum do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Enum.map_join([1,2,3], &1 * 2)
|
||||
iex> Enum.map_join([1, 2, 3], &1 * 2)
|
||||
"246"
|
||||
iex> Enum.map_join([1,2,3], " = ", &1 * 2)
|
||||
iex> Enum.map_join([1, 2, 3], " = ", &1 * 2)
|
||||
"2 = 4 = 6"
|
||||
iex> Enum.map_join([1,2,3], ' = ', &1 * 2)
|
||||
iex> Enum.map_join([1, 2, 3], ' = ', &1 * 2)
|
||||
'2 = 4 = 6'
|
||||
|
||||
"""
|
||||
@@ -714,6 +729,26 @@ defmodule Enum do
|
||||
Enumerable.reduce(collection, acc, fun)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns elements of collection for which `fun` returns false.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Enum.reject([1, 2, 3], fn(x) -> rem(x, 2) == 0 end)
|
||||
[1, 3]
|
||||
|
||||
"""
|
||||
@spec reject(t, (element -> as_boolean(term))) :: list
|
||||
def reject(collection, fun) when is_list(collection) do
|
||||
lc item inlist collection, !fun.(item), do: item
|
||||
end
|
||||
|
||||
def reject(collection, fun) do
|
||||
Enumerable.reduce(collection, [], fn(entry, acc) ->
|
||||
unless fun.(entry), do: [entry|acc], else: acc
|
||||
end) |> :lists.reverse
|
||||
end
|
||||
|
||||
@doc """
|
||||
Reverses the collection.
|
||||
|
||||
@@ -739,7 +774,7 @@ defmodule Enum do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Enum.sort([3,2,1])
|
||||
iex> Enum.sort([3, 2, 1])
|
||||
[1,2,3]
|
||||
|
||||
"""
|
||||
@@ -759,7 +794,7 @@ defmodule Enum do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Enum.sort([1,2,3], &1 > &2)
|
||||
iex> Enum.sort([1, 2, 3], &1 > &2)
|
||||
[3,2,1]
|
||||
|
||||
"""
|
||||
@@ -785,15 +820,15 @@ defmodule Enum do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Enum.split([1,2,3], 2)
|
||||
iex> Enum.split([1, 2, 3], 2)
|
||||
{ [1,2], [3] }
|
||||
iex> Enum.split([1,2,3], 10)
|
||||
iex> Enum.split([1, 2, 3], 10)
|
||||
{ [1,2,3], [] }
|
||||
iex> Enum.split([1,2,3], 0)
|
||||
iex> Enum.split([1, 2, 3], 0)
|
||||
{ [], [1,2,3] }
|
||||
iex> Enum.split([1,2,3], -1)
|
||||
iex> Enum.split([1, 2, 3], -1)
|
||||
{ [1,2], [3] }
|
||||
iex> Enum.split([1,2,3], -5)
|
||||
iex> Enum.split([1, 2, 3], -5)
|
||||
{ [], [1,2,3] }
|
||||
|
||||
"""
|
||||
@@ -825,7 +860,7 @@ defmodule Enum do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Enum.split_while([1,2,3,4], fn(x) -> x < 3 end)
|
||||
iex> Enum.split_while([1, 2, 3, 4], fn(x) -> x < 3 end)
|
||||
{ [1, 2], [3, 4] }
|
||||
"""
|
||||
@spec split_while(t, (element -> as_boolean(term))) :: {list, list}
|
||||
@@ -850,11 +885,11 @@ defmodule Enum do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Enum.take([1,2,3], 2)
|
||||
iex> Enum.take([1, 2, 3], 2)
|
||||
[1,2]
|
||||
iex> Enum.take([1,2,3], 10)
|
||||
iex> Enum.take([1, 2, 3], 10)
|
||||
[1,2,3]
|
||||
iex> Enum.take([1,2,3], 0)
|
||||
iex> Enum.take([1, 2, 3], 0)
|
||||
[]
|
||||
|
||||
"""
|
||||
@@ -870,10 +905,10 @@ defmodule Enum do
|
||||
|
||||
def take(collection, count) when count > 0 do
|
||||
{ list, _ } = Enumerable.reduce(collection, { [], count }, fn(entry, { list, count }) ->
|
||||
if count > 0 do
|
||||
if count > 1 do
|
||||
{ [entry|list], count - 1 }
|
||||
else
|
||||
throw({ :enum_take, list })
|
||||
throw({ :enum_take, [entry|list] })
|
||||
end
|
||||
end)
|
||||
:lists.reverse(list)
|
||||
@@ -892,7 +927,7 @@ defmodule Enum do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Enum.take_while([1,2,3], fn(x) -> x < 3 end)
|
||||
iex> Enum.take_while([1, 2, 3], fn(x) -> x < 3 end)
|
||||
[1, 2]
|
||||
|
||||
"""
|
||||
@@ -938,10 +973,10 @@ defmodule Enum do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Enum.uniq([1,2,3,2,1])
|
||||
iex> Enum.uniq([1, 2, 3, 2, 1])
|
||||
[1, 2, 3]
|
||||
|
||||
iex> Enum.uniq([{1,:x}, {2,:y}, {1,:z}], fn {x,_} -> x end)
|
||||
iex> Enum.uniq([{1, :x}, {2, :y}, {1, :z}], fn {x, _} -> x end)
|
||||
[{1,:x}, {2,:y}]
|
||||
|
||||
"""
|
||||
@@ -971,6 +1006,15 @@ defmodule Enum do
|
||||
of tuples. The number of elements in the resulting list is
|
||||
dictated by the first enum. In case the second list is shorter,
|
||||
values are filled with nil.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Enum.zip([1, 2, 3], [:a, :b, :c])
|
||||
[{1,:a},{2,:b},{3,:c}]
|
||||
|
||||
iex> Enum.zip([1,2,3,4,5], [:a, :b, :c])
|
||||
[{1,:a},{2,:b},{3,:c},{4,nil},{5,nil}]
|
||||
|
||||
"""
|
||||
@spec zip(t, t) :: [{any, any}]
|
||||
def zip(coll1, coll2) when is_list(coll1) and is_list(coll2) do
|
||||
@@ -996,7 +1040,7 @@ defmodule Enum do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Enum.max([1,2,3])
|
||||
iex> Enum.max([1, 2, 3])
|
||||
3
|
||||
|
||||
"""
|
||||
@@ -1025,6 +1069,22 @@ defmodule Enum do
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns the collection with each element wrapped in a tuple
|
||||
along side its index.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Enum.with_index [1,2,3]
|
||||
[{1,0},{2,1},{3,2}]
|
||||
|
||||
"""
|
||||
@spec with_index(t) :: list({ element, non_neg_integer })
|
||||
def with_index(collection) do
|
||||
map_reduce(collection, 0, fn x, acc ->
|
||||
{ { x, acc }, acc + 1 }
|
||||
end) |> elem(0)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns the maximum value.
|
||||
@@ -1066,12 +1126,12 @@ defmodule Enum do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns the manimum value.
|
||||
Returns the minimum value.
|
||||
Raises empty error in case the collection is empty.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Enum.min([1,2,3])
|
||||
iex> Enum.min([1, 2, 3])
|
||||
1
|
||||
|
||||
"""
|
||||
@@ -1106,7 +1166,7 @@ defmodule Enum do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns the manimum value.
|
||||
Returns the minimum value.
|
||||
Raises empty error in case the collection is empty.
|
||||
|
||||
## Examples
|
||||
|
||||
+16
-10
@@ -192,9 +192,7 @@ defmodule Exception do
|
||||
calculates the current stacktrace and formats it. As consequence,
|
||||
the value of `System.stacktrace` is changed.
|
||||
"""
|
||||
def format_stacktrace(trace // nil)
|
||||
|
||||
def format_stacktrace(trace) do
|
||||
def format_stacktrace(trace // nil) do
|
||||
trace = trace || try do
|
||||
throw(:stacktrace)
|
||||
catch
|
||||
@@ -208,14 +206,22 @@ defmodule Exception do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Prints the current stacktrace to standard output.
|
||||
|
||||
A stacktrace must be given as argument. If not, this function
|
||||
calculates the current stacktrace and formats it. As consequence,
|
||||
the value of `System.stacktrace` is changed.
|
||||
Formats the caller, i.e. the first entry in the stacktrace.
|
||||
Notice that doing to tail call optimization, the stacktrace
|
||||
may not report the direct caller of the function.
|
||||
"""
|
||||
def print_stacktrace(trace // nil) do
|
||||
IO.write format_stacktrace(trace)
|
||||
def format_caller(trace // nil) do
|
||||
trace = trace || try do
|
||||
throw(:stacktrace)
|
||||
catch
|
||||
:stacktrace -> Enum.drop(:erlang.get_stacktrace, 1)
|
||||
end
|
||||
|
||||
if entry = Enum.at(trace, 1) do
|
||||
format_stacktrace_entry(entry)
|
||||
else
|
||||
"nofile:0: "
|
||||
end
|
||||
end
|
||||
|
||||
## Helpers
|
||||
|
||||
+200
-168
@@ -38,10 +38,12 @@ defexception File.Error, [reason: nil, action: "", path: nil] do
|
||||
end
|
||||
end
|
||||
|
||||
defexception File.CopyError, [reason: nil, action: "", source: nil, destination: nil] do
|
||||
defexception File.CopyError, [reason: nil, action: "", source: nil, destination: nil, on: nil] do
|
||||
def message(exception) do
|
||||
formatted = list_to_binary(:file.format_error(reason exception))
|
||||
"could not #{action exception} from #{source exception} to #{destination exception}: #{formatted}"
|
||||
location = if on = on(exception), do: ". #{on}", else: ""
|
||||
"could not #{action exception} from #{source exception} to " <>
|
||||
"#{destination exception}#{location}: #{formatted}"
|
||||
end
|
||||
end
|
||||
|
||||
@@ -54,28 +56,30 @@ end
|
||||
|
||||
defmodule File do
|
||||
@moduledoc """
|
||||
This module contains function to manipulate files.
|
||||
Many of the functions that interact with the filesystem
|
||||
have their naming based on its UNIX variants. For
|
||||
example, deleting a file is done with `File.rm`.
|
||||
Getting its stats with `File.stat`.
|
||||
This module contains functions to manipulate files.
|
||||
|
||||
In order to write and read files, one must use the
|
||||
functions in the IO module. By default, a file is
|
||||
opened in binary mode which requires the functions
|
||||
`IO.binread`, `IO.binwrite` and `IO.binreadline` to
|
||||
interact with the file. A developer may pass `:utf8`
|
||||
as an option when opening the file and then all other
|
||||
functions from IO are available, since they work directly
|
||||
with Unicode data.
|
||||
Some of those functions are low-level, allowing the user
|
||||
to interact with the file or IO devices, like `File.open/2`,
|
||||
`File.copy/3` and others. This module also provides higher
|
||||
level functions that works with filenames and have their naming
|
||||
based on UNIX variants. For example, one can copy a file
|
||||
via `File.cp/3` and remove files and directories recursively
|
||||
via `File.rm_rf/2`
|
||||
|
||||
Most of the functions in this module return `:ok`
|
||||
or `{ :ok, result }` in case of success, `{ :error, reason }`
|
||||
otherwise. Those function are also followed by
|
||||
a variant that ends with `!` which returns the
|
||||
result (without the `{ :ok, result }` tuple) in
|
||||
case of success or raises an exception in case it
|
||||
fails. For example:
|
||||
In order to write and read files, one must use the functions
|
||||
in the IO module. By default, a file is opened in binary mode
|
||||
which requires the functions `IO.binread` and `IO.binwrite`
|
||||
to interact with the file. A developer may pass `:utf8` as an
|
||||
option when opening the file and then all other functions
|
||||
from IO are available, since they work directly with Unicode
|
||||
data.
|
||||
|
||||
Most of the functions in this module return `:ok` or
|
||||
`{ :ok, result }` in case of success, `{ :error, reason }`
|
||||
otherwise. Those function are also followed by a variant
|
||||
that ends with `!` which returns the result (without the
|
||||
`{ :ok, result }` tuple) in case of success or raises an
|
||||
exception in case it fails. For example:
|
||||
|
||||
File.read("hello.txt")
|
||||
#=> { :ok, "World" }
|
||||
@@ -89,16 +93,10 @@ defmodule File do
|
||||
File.read!("invalid.txt")
|
||||
#=> raises File.Error
|
||||
|
||||
In general, a developer should use the former in case
|
||||
he wants to react in the fie does not exist. The latter
|
||||
should be used when the developer expects his software
|
||||
to fail in case the file cannot be read (i.e. it is
|
||||
literally an exception).
|
||||
|
||||
Finally, the functions in this module accept either
|
||||
a char lists or a binary. When manipulating paths, a char
|
||||
list is returned if one is given as argument. However,
|
||||
when reading files, binaries are always returned.
|
||||
In general, a developer should use the former in case he wants
|
||||
to react if the file does not exist. The latter should be used
|
||||
when the developer expects his software to fail in case the
|
||||
file cannot be read (i.e. it is literally an exception).
|
||||
"""
|
||||
|
||||
alias :file, as: F
|
||||
@@ -125,7 +123,7 @@ defmodule File do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns true if the given argument exists.
|
||||
Returns true if the given path exists.
|
||||
It can be regular file, directory, socket,
|
||||
symbolic link, named pipe or device file.
|
||||
|
||||
@@ -233,7 +231,7 @@ defmodule File do
|
||||
|
||||
@doc """
|
||||
Returns information about the `path`. If it exists, it
|
||||
returns a `{ :ok, info }` tuple, where info is as a
|
||||
returns a `{ :ok, info }` tuple, where info is a
|
||||
`File.Info` record. Retuns `{ :error, reason }` with
|
||||
the same reasons as `File.read` if a failure occurs.
|
||||
|
||||
@@ -310,17 +308,24 @@ defmodule File do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Copies the contents of `source` to `destination`. Both
|
||||
parameters can be a filename or an io device opened with `File.open`.
|
||||
`bytes_count` specifies the number of bytes to count, the default
|
||||
being `:infinity`.
|
||||
Copies the contents of `source` to `destination`.
|
||||
|
||||
If file `destination` already exists, it is overriden
|
||||
Both parameters can be a filename or an io device opened
|
||||
with `File.open/2`. `bytes_count` specifies the number of
|
||||
bytes to copy, the default being `:infinity`.
|
||||
|
||||
If file `destination` already exists, it is overwritten
|
||||
by the contents in `source`.
|
||||
|
||||
Returns `{ :ok, bytes_copied }` if successful,
|
||||
`{ :error, reason }` otherwise.
|
||||
|
||||
Compared to the `File.cp/3`, this function is more low-level,
|
||||
allowing a copy from device to device limited by a number of
|
||||
bytes. On the other hand, `File.cp/3` performs more extensive
|
||||
checks on both source and destination and it also preserves
|
||||
the file mode after copy.
|
||||
|
||||
Typical error reasons are the same as in `open/2`,
|
||||
`read/1` and `write/2`.
|
||||
"""
|
||||
@@ -329,7 +334,7 @@ defmodule File do
|
||||
end
|
||||
|
||||
@doc """
|
||||
The same as `copy/3` but raises an File.CopyError if it fails.
|
||||
The same as `copy/3` but raises an `File.CopyError` if it fails.
|
||||
Returns the `bytes_copied` otherwise.
|
||||
"""
|
||||
def copy!(source, destination, bytes_count // :infinity) do
|
||||
@@ -342,40 +347,36 @@ defmodule File do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Copies the contents in `source` to `destination`.
|
||||
Similar to the command `cp -r` in Unix systems,
|
||||
this function behaves differently depending
|
||||
if `source` and `destination` are a file or a directory.
|
||||
Copies the contents in `source` to `destination` preserving its mode.
|
||||
|
||||
If both are files, it simply copies `source` to
|
||||
`destination`. However, if `destination` is a directory,
|
||||
it copies the contents of `source` to `destination/source`
|
||||
recursively.
|
||||
Similar to the command `cp` in Unix systems, this function
|
||||
behaves differently depending if `destination` is a directory
|
||||
or not. In particular, if `destination` is a directory, it
|
||||
copies the contents of `source` to `destination/source`.
|
||||
|
||||
If a file already exists in the destination,
|
||||
it invokes a callback which should return
|
||||
true if the existing file should be overriden,
|
||||
false otherwise. It defaults to return true.
|
||||
If a file already exists in the destination, it invokes a
|
||||
callback which should return true if the existing file
|
||||
should be overwritten, false otherwise. It defaults to return true.
|
||||
|
||||
It returns `:ok` in case of success, returns
|
||||
`{ :error, reason }` otherwise.
|
||||
|
||||
If you want to copy contents from an io device to another device
|
||||
or do a straight copy from a source to a destination without
|
||||
preserving modes, check `File.copy/3` instead.
|
||||
"""
|
||||
def cp(source, destination, callback // fn(_, _) -> true end) do
|
||||
if dir?(source) do
|
||||
{ :error, :eisdir }
|
||||
else
|
||||
output =
|
||||
if dir?(destination) do
|
||||
mkdir(destination)
|
||||
FN.join(destination, FN.basename(source))
|
||||
else
|
||||
destination
|
||||
end
|
||||
|
||||
case do_cp_file(source, output, callback, []) do
|
||||
{ :error, _ } = error -> error
|
||||
_ -> :ok
|
||||
output =
|
||||
if dir?(destination) do
|
||||
mkdir(destination)
|
||||
FN.join(destination, FN.basename(source))
|
||||
else
|
||||
destination
|
||||
end
|
||||
|
||||
case do_cp_file(source, output, callback, []) do
|
||||
{ :error, reason, _ } -> { :error, reason }
|
||||
_ -> :ok
|
||||
end
|
||||
end
|
||||
|
||||
@@ -388,7 +389,8 @@ defmodule File do
|
||||
:ok -> :ok
|
||||
{ :error, reason } ->
|
||||
raise File.CopyError, reason: reason, action: "copy recursively",
|
||||
source: :unicode.characters_to_binary(source), destination: :unicode.characters_to_binary(destination)
|
||||
source: :unicode.characters_to_binary(source),
|
||||
destination: :unicode.characters_to_binary(destination)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -405,7 +407,7 @@ defmodule File do
|
||||
|
||||
If a file already exists in the destination,
|
||||
it invokes a callback which should return
|
||||
true if the existing file should be overriden,
|
||||
true if the existing file should be overwritten,
|
||||
false otherwise. It defaults to return true.
|
||||
|
||||
If a directory already exists in the destination
|
||||
@@ -434,7 +436,7 @@ defmodule File do
|
||||
|
||||
# Same as before, but asks the user how to proceed in case of conflicts
|
||||
File.cp_r "samples/.", "tmp", fn(source, destination) ->
|
||||
IO.gets("Overriding #{destination} by #{source}. Type y to confirm.") == "y"
|
||||
IO.gets("Overwriting #{destination} by #{source}. Type y to confirm.") == "y"
|
||||
end
|
||||
|
||||
"""
|
||||
@@ -448,7 +450,7 @@ defmodule File do
|
||||
end
|
||||
|
||||
case do_cp_r(source, output, callback, []) do
|
||||
{ :error, _ } = error -> error
|
||||
{ :error, _, _ } = error -> error
|
||||
res -> { :ok, res }
|
||||
end
|
||||
end
|
||||
@@ -460,19 +462,26 @@ defmodule File do
|
||||
def cp_r!(source, destination, callback // fn(_, _) -> true end) do
|
||||
case cp_r(source, destination, callback) do
|
||||
{ :ok, files } -> files
|
||||
{ :error, reason } ->
|
||||
{ :error, reason, file } ->
|
||||
raise File.CopyError, reason: reason, action: "copy recursively",
|
||||
source: :unicode.characters_to_binary(source), destination: :unicode.characters_to_binary(destination)
|
||||
source: :unicode.characters_to_binary(source),
|
||||
destination: :unicode.characters_to_binary(destination),
|
||||
on: file
|
||||
end
|
||||
end
|
||||
|
||||
# src may be a file or a directory, dest is definitely
|
||||
# a directory. Returns nil unless an error is found.
|
||||
defp do_cp_r(src, dest, callback, acc) when is_list(acc) do
|
||||
case F.read_link(src) do
|
||||
{ :ok, link } ->
|
||||
do_cp_link(link, src, dest, callback, acc)
|
||||
_ ->
|
||||
case :elixir.file_type(src) do
|
||||
{ :ok, :regular } ->
|
||||
do_cp_file(src, dest, callback, acc)
|
||||
{ :ok, :symlink } ->
|
||||
case F.read_link(src) do
|
||||
{ :ok, link } -> do_cp_link(link, src, dest, callback, acc)
|
||||
{ :error, reason } -> { :error, reason, :unicode.characters_to_binary(src) }
|
||||
end
|
||||
{ :ok, :directory } ->
|
||||
case F.list_dir(src) do
|
||||
{ :ok, files } ->
|
||||
case mkdir(dest) do
|
||||
@@ -480,12 +489,12 @@ defmodule File do
|
||||
Enum.reduce(files, [dest|acc], fn(x, acc) ->
|
||||
do_cp_r(FN.join(src, x), FN.join(dest, x), callback, acc)
|
||||
end)
|
||||
reason -> reason
|
||||
{ :error, reason } -> { :error, reason, :unicode.characters_to_binary(dest) }
|
||||
end
|
||||
{ :error, :enotdir } ->
|
||||
do_cp_file(src, dest, callback, acc)
|
||||
reason -> reason
|
||||
{ :error, reason } -> { :error, reason, :unicode.characters_to_binary(src) }
|
||||
end
|
||||
{ :ok, _ } -> { :error, :eio, src }
|
||||
{ :error, reason } -> { :error, reason, :unicode.characters_to_binary(src) }
|
||||
end
|
||||
end
|
||||
|
||||
@@ -514,12 +523,12 @@ defmodule File do
|
||||
{ :ok, _ } ->
|
||||
copy_file_mode!(src, dest)
|
||||
[dest|acc]
|
||||
reason -> reason
|
||||
{ :error, reason } -> { :error, reason, :unicode.characters_to_binary(src) }
|
||||
end
|
||||
else
|
||||
acc
|
||||
end
|
||||
reason -> reason
|
||||
{ :error, reason } -> { :error, reason, :unicode.characters_to_binary(src) }
|
||||
end
|
||||
end
|
||||
|
||||
@@ -533,12 +542,12 @@ defmodule File do
|
||||
rm(dest)
|
||||
case F.make_symlink(link, dest) do
|
||||
:ok -> [dest|acc]
|
||||
reason -> reason
|
||||
{ :error, reason } -> { :error, reason, :unicode.characters_to_binary(src) }
|
||||
end
|
||||
else
|
||||
acc
|
||||
end
|
||||
reason -> reason
|
||||
{ :error, reason } -> { :error, reason, :unicode.characters_to_binary(src) }
|
||||
end
|
||||
end
|
||||
|
||||
@@ -638,7 +647,7 @@ defmodule File do
|
||||
|
||||
@doc """
|
||||
Remove files and directories recursively at the given `path`.
|
||||
Symlinks are not followed but simply removed, non existing
|
||||
Symlinks are not followed but simply removed, non-existing
|
||||
files are simply ignored (i.e. doesn't make this function fail).
|
||||
|
||||
Returns `{ :ok, files_and_directories }` with all files and
|
||||
@@ -671,18 +680,18 @@ defmodule File do
|
||||
case rmdir(path) do
|
||||
:ok -> { :ok, [path|acc] }
|
||||
{ :error, :enoent } -> res
|
||||
reason -> reason
|
||||
reason -> { :error, reason, :unicode.characters_to_binary(path) }
|
||||
end
|
||||
reason -> reason
|
||||
reason -> { :error, reason, :unicode.characters_to_binary(path) }
|
||||
end
|
||||
{ :error, :enotdir } ->
|
||||
{ :error, reason } when reason in [:enotdir, :eio] ->
|
||||
case rm(path) do
|
||||
:ok -> { :ok, [path|acc] }
|
||||
{ :error, :enoent } -> entry
|
||||
reason -> reason
|
||||
{ :error, reason } when reason in [:enoent, :enotdir] -> entry
|
||||
reason -> { :error, reason, :unicode.characters_to_binary(path) }
|
||||
end
|
||||
{ :error, :enoent } -> entry
|
||||
reason -> reason
|
||||
{ :error, reason } -> { :error, reason, :unicode.characters_to_binary(path) }
|
||||
end
|
||||
end
|
||||
|
||||
@@ -704,8 +713,10 @@ defmodule File do
|
||||
def rm_rf!(path) do
|
||||
case rm_rf(path) do
|
||||
{ :ok, files } -> files
|
||||
{ :error, reason } ->
|
||||
raise File.Error, reason: reason, action: "remove files and directories recursively from", path: :unicode.characters_to_binary(path)
|
||||
{ :error, reason, _ } ->
|
||||
raise File.Error, reason: reason,
|
||||
action: "remove files and directories recursively from",
|
||||
path: :unicode.characters_to_binary(path)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -714,11 +725,10 @@ defmodule File do
|
||||
|
||||
In order to write and read files, one must use the functions
|
||||
in the IO module. By default, a file is opened in binary mode
|
||||
which requires the functions `IO.binread`, `IO.binwrite` and
|
||||
`IO.binreadline` to interact with the file. A developer may pass
|
||||
`:utf8` as an option when opening the file and then all other
|
||||
functions from IO are available, since they work directly with
|
||||
Unicode data.
|
||||
which requires the functions `IO.binread` and `IO.binwrite`
|
||||
to interact with the file. A developer may pass `:utf8` as an
|
||||
option when opening the file and then all other functions from
|
||||
`IO` are available, since they work directly with Unicode data.
|
||||
|
||||
The allowed modes:
|
||||
|
||||
@@ -753,19 +763,19 @@ defmodule File do
|
||||
|
||||
This function returns:
|
||||
|
||||
* { :ok, io_device } - The file has been opened in the requested mode.
|
||||
* `{ :ok, io_device }` - The file has been opened in the requested mode.
|
||||
`io_device` is actually the pid of the process which handles the file.
|
||||
This process is linked to the process which originally opened the file.
|
||||
If any process to which the io_device is linked terminates, the file will
|
||||
be closed and the process itself will be terminated. An io_device returned
|
||||
If any process to which the `io_device` is linked terminates, the file will
|
||||
be closed and the process itself will be terminated. An `io_device` returned
|
||||
from this call can be used as an argument to the `IO` module functions.
|
||||
|
||||
* { :error, reason } - The file could not be opened.
|
||||
* `{ :error, reason }` - The file could not be opened.
|
||||
|
||||
## Examples
|
||||
|
||||
{ :ok, file } = File.open("foo.tar.gz", [:read, :compressed])
|
||||
IO.readline(file)
|
||||
IO.read(file, :line)
|
||||
File.close(file)
|
||||
|
||||
"""
|
||||
@@ -796,7 +806,7 @@ defmodule File do
|
||||
## Examples
|
||||
|
||||
File.open("file.txt", [:read, :write], fn(file) ->
|
||||
IO.readline(file)
|
||||
IO.read(file, :line)
|
||||
end)
|
||||
|
||||
"""
|
||||
@@ -905,7 +915,7 @@ defmodule File do
|
||||
"""
|
||||
def ls(path // ".") do
|
||||
case F.list_dir(path) do
|
||||
{ :ok, file_list } -> { :ok, Enum.map file_list, :unicode.characters_to_binary(&1) }
|
||||
{ :ok, file_list } -> { :ok, Enum.map(file_list, :unicode.characters_to_binary(&1)) }
|
||||
{ :error, _ } = error -> error
|
||||
end
|
||||
end
|
||||
@@ -934,80 +944,124 @@ defmodule File do
|
||||
F.close(io_device)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Converts the file device into an iterator that can be
|
||||
passed into `Enum`. The device is iterated line
|
||||
by line, at the end of iteration the file is closed.
|
||||
|
||||
This reads the file as utf-8. CHeck out `File.biniterator`
|
||||
to handle the file as a raw binary.
|
||||
|
||||
## Examples
|
||||
|
||||
An example that lazily iterates a file replacing all double
|
||||
quotes per single quotes and write each line to a target file
|
||||
is shown below:
|
||||
|
||||
{ :ok, device } = File.open("README.md")
|
||||
source = File.iterator(device)
|
||||
File.open "NEWREADME.md", [:write], fn(target) ->
|
||||
Enum.each source, fn(line) ->
|
||||
IO.write target, Regex.replace(%r/"/, line, "'")
|
||||
end
|
||||
end
|
||||
|
||||
"""
|
||||
@doc false
|
||||
def iterator(device) do
|
||||
fn(fun, acc) ->
|
||||
do_iterator(device, fun, acc)
|
||||
end
|
||||
IO.write "[WARNING] File.iterator/1 is deprecated, please use IO.stream/1 instead\n#{Exception.format_stacktrace}"
|
||||
IO.stream(device)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Opens the given `file` with the given `mode` and
|
||||
returns its iterator. The returned iterator will
|
||||
returns its stream. The returned stream will
|
||||
fail for the same reasons as `File.open!`. Note
|
||||
that the file is opened when the iteration begins.
|
||||
"""
|
||||
def iterator!(file, mode // []) do
|
||||
fn(fun, acc) ->
|
||||
def stream!(file, mode // []) do
|
||||
fn(acc, fun) ->
|
||||
device = open!(file, mode)
|
||||
try do
|
||||
do_iterator(device, fun, acc)
|
||||
IO.stream(device, acc, fun)
|
||||
after
|
||||
F.close(device)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Converts the file device into an iterator that can
|
||||
be passed into `Enum` to iterate line by line as a
|
||||
binary. Check `iterator/1` for more information.
|
||||
"""
|
||||
@doc false
|
||||
def iterator!(device, mode // []) do
|
||||
IO.write "[WARNING] File.iterator!/2 is deprecated, please use File.stream!/2 instead\n#{Exception.format_stacktrace}"
|
||||
stream!(device, mode)
|
||||
end
|
||||
|
||||
@doc false
|
||||
def biniterator(device) do
|
||||
fn(fun, acc) ->
|
||||
do_biniterator(device, fun, acc)
|
||||
end
|
||||
IO.write "[WARNING] File.biniterator/1 is deprecated, please use IO.binstream/1 instead\n#{Exception.format_stacktrace}"
|
||||
IO.binstream(device)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Opens the given `file` with the given `mode` and
|
||||
returns its biniterator. The returned iterator will
|
||||
returns its binstream. The returned stream will
|
||||
fail for the same reasons as `File.open!`. Note
|
||||
that the file is opened when the iteration begins.
|
||||
"""
|
||||
def biniterator!(file, mode // []) do
|
||||
def binstream!(file, mode // []) do
|
||||
fn(fun, acc) ->
|
||||
device = open!(file, mode)
|
||||
try do
|
||||
do_biniterator(device, fun, acc)
|
||||
IO.binstream(device, fun, acc)
|
||||
after
|
||||
F.close(device)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@doc false
|
||||
def biniterator!(device, mode // []) do
|
||||
IO.write "[WARNING] File.binstream!/2 is deprecated, please use File.binstream!/2 instead\n#{Exception.format_stacktrace}"
|
||||
binstream!(device, mode)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Changes the unix file `mode` for a given `file`.
|
||||
Returns `:ok` on success, or `{:error, reason}`
|
||||
on failure.
|
||||
"""
|
||||
def chmod(file, mode) do
|
||||
F.change_mode(file, mode)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Same as `chmod/2`, but raises an exception in case of failure. Otherwise `:ok`.
|
||||
"""
|
||||
def chmod!(file, mode) do
|
||||
case chmod(file, mode) do
|
||||
:ok -> :ok
|
||||
{ :error, reason } ->
|
||||
raise File.Error, reason: reason, action: "change mode for", path: :unicode.characters_to_binary(file)
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Changes the user group given by the group id `gid`
|
||||
for a given `file`. Returns `:ok` on success, or
|
||||
`{:error, reason}` on failure.
|
||||
"""
|
||||
def chgrp(file, gid) do
|
||||
F.change_group(file, gid)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Same as `chgrp/2`, but raises an exception in case of failure. Otherwise `:ok`.
|
||||
"""
|
||||
def chgrp!(file, gid) do
|
||||
case chgrp(file, gid) do
|
||||
:ok -> :ok
|
||||
{ :error, reason } ->
|
||||
raise File.Error, reason: reason, action: "change group for", path: :unicode.characters_to_binary(file)
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Changes the owner given by the user id `gid`
|
||||
for a given `file`. Returns `:ok` on success,
|
||||
or `{:error, reason}` on failure.
|
||||
"""
|
||||
def chown(file, uid) do
|
||||
F.change_owner(file, uid)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Same as `chown/2`, but raises an exception in case of failure. Otherwise `:ok`.
|
||||
"""
|
||||
def chown!(file, gid) do
|
||||
case chown(file, gid) do
|
||||
:ok -> :ok
|
||||
{ :error, reason } ->
|
||||
raise File.Error, reason: reason, action: "change owner for", path: :unicode.characters_to_binary(file)
|
||||
end
|
||||
end
|
||||
|
||||
## Helpers
|
||||
|
||||
defp open_defaults([:charlist|t], _add_binary) do
|
||||
@@ -1024,26 +1078,4 @@ defmodule File do
|
||||
|
||||
defp open_defaults([], true), do: [:binary]
|
||||
defp open_defaults([], false), do: []
|
||||
|
||||
defp do_iterator(device, acc, fun) do
|
||||
case :io.get_line(device, '') do
|
||||
:eof ->
|
||||
acc
|
||||
{ :error, reason } ->
|
||||
raise File.IteratorError, reason: reason
|
||||
data ->
|
||||
do_iterator(device, fun.(data, acc), fun)
|
||||
end
|
||||
end
|
||||
|
||||
defp do_biniterator(device, acc, fun) do
|
||||
case F.read_line(device) do
|
||||
:eof ->
|
||||
acc
|
||||
{ :error, reason } ->
|
||||
raise File.IteratorError, reason: reason
|
||||
{ :ok, data } ->
|
||||
do_iterator(device, fun.(data, acc), fun)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
defmodule GenEvent.Behaviour do
|
||||
@moduledoc """
|
||||
This module is a convenience to define GenEvent callbacks in Elixir.
|
||||
This module is a convenience for defining GenEvent callbacks in Elixir.
|
||||
|
||||
GenEvent is an OTP behaviour that encapsulates event handling functionality.
|
||||
|
||||
## Example
|
||||
|
||||
Bellow follows an example of a GenEvent that stores notifications
|
||||
Below is an example of a GenEvent that stores notifications
|
||||
until they are fetched:
|
||||
|
||||
defmodule MyEventHandler do
|
||||
@@ -29,11 +29,16 @@ defmodule GenEvent.Behaviour do
|
||||
end
|
||||
|
||||
{ :ok, pid } = :gen_event.start_link
|
||||
#=> {:ok,#PID<0.42.0>}
|
||||
|
||||
:gen_event.add_handler(pid, MyEventHandler, [])
|
||||
#=> :ok
|
||||
|
||||
:gen_event.notify(pid, {:notification, 1})
|
||||
#=> :ok
|
||||
|
||||
:gen_event.notify(pid, {:notification, 2})
|
||||
#=> :ok
|
||||
|
||||
:gen_event.call(pid, MyEventHandler, :notifications)
|
||||
#=> [1, 2]
|
||||
@@ -44,12 +49,12 @@ defmodule GenEvent.Behaviour do
|
||||
Notice we never call the server callbacks directly, they are called
|
||||
by OTP whenever we interact with the server.
|
||||
|
||||
Starting and sending messages to gen_event is done
|
||||
Starting and sending messages to the GenEvent is done
|
||||
via Erlang's `:gen_event` module. For more information,
|
||||
please refer to the following:
|
||||
|
||||
http://www.erlang.org/doc/man/gen_event.html
|
||||
http://learnyousomeerlang.com/event-handlers
|
||||
* http://www.erlang.org/doc/man/gen_event.html
|
||||
* http://learnyousomeerlang.com/event-handlers
|
||||
"""
|
||||
|
||||
@doc false
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
defmodule GenServer.Behaviour do
|
||||
@moduledoc """
|
||||
This module is a convenience to define GenServer callbacks in Elixir.
|
||||
This module is a convenience for defining GenServer callbacks in Elixir.
|
||||
|
||||
A server is responsible to react to messages received from client
|
||||
and a GenServer is an OTP behaviour that encapsulates common server
|
||||
A server is responsible for reacting to messages received from a client.
|
||||
A GenServer is an OTP behaviour that encapsulates common server
|
||||
functionalities.
|
||||
|
||||
## Example
|
||||
|
||||
Bellow follows an example of a GenServer that push and pop items
|
||||
into a stack:
|
||||
Below is an example of a GenServer that pushes and pops items
|
||||
onto a stack:
|
||||
|
||||
defmodule MyServer do
|
||||
use GenServer.Behaviour
|
||||
@@ -20,17 +20,17 @@ defmodule GenServer.Behaviour do
|
||||
{ :reply, h, t }
|
||||
end
|
||||
|
||||
def handle_call(_request, _from, _config) do
|
||||
def handle_call(request, from, config) do
|
||||
# Call the default implementation from GenServer.Behaviour
|
||||
super
|
||||
super(request, from, config)
|
||||
end
|
||||
|
||||
def handle_cast({ :push, item }, config) do
|
||||
{ :noreply, [item|config] }
|
||||
end
|
||||
|
||||
def handle_cast(_request, _config) do
|
||||
super
|
||||
def handle_cast(request, config) do
|
||||
super(request, config)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -47,8 +47,8 @@ defmodule GenServer.Behaviour do
|
||||
|
||||
Notice we never call the server callbacks directly, they are called
|
||||
by OTP whenever we interact with the server. **cast** messages are
|
||||
asynchronous while **call** ones are synchronous. In the case of
|
||||
GenServer's, there are 8 different values a callback such as
|
||||
asynchronous while **call** ones are synchronous. For a
|
||||
GenServer, there are 8 different values a callback such as
|
||||
`handle_call` or `handle_cast` can return:
|
||||
|
||||
{ :reply, reply, new_state }
|
||||
@@ -62,7 +62,7 @@ defmodule GenServer.Behaviour do
|
||||
|
||||
There are 6 callbacks required to be implemented in a GenServer. The
|
||||
`GenServer.Behaviour` module defines all of them automatically, but
|
||||
allows us to customize the ones we need. The list of callbacks are:
|
||||
allows us to customize the ones we need. The required callbacks are:
|
||||
|
||||
* `init(args)` - invoked when the server is started;
|
||||
* `handle_call(msg, from, state)` - invoked to handle call messages;
|
||||
@@ -74,13 +74,13 @@ defmodule GenServer.Behaviour do
|
||||
* `code_change(old_vsn, state, extra)` - called when the application
|
||||
code is being upgraded live (hot code swap);
|
||||
|
||||
Starting and sending messages to the gen_server is done
|
||||
Starting and sending messages to the GenServer is done
|
||||
via Erlang's `:gen_server` module. For more information,
|
||||
please refer to the following:
|
||||
|
||||
http://www.erlang.org/doc/man/gen_server.html
|
||||
http://www.erlang.org/doc/design_principles/gen_server_concepts.html
|
||||
http://learnyousomeerlang.com/clients-and-servers
|
||||
* http://www.erlang.org/doc/man/gen_server.html
|
||||
* http://www.erlang.org/doc/design_principles/gen_server_concepts.html
|
||||
* http://learnyousomeerlang.com/clients-and-servers
|
||||
"""
|
||||
|
||||
@doc false
|
||||
@@ -122,4 +122,4 @@ defmodule GenServer.Behaviour do
|
||||
handle_cast: 2, terminate: 2, code_change: 3]
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
+88
-146
@@ -35,7 +35,7 @@ defmodule HashDict do
|
||||
# The ordered record contains a single bucket
|
||||
@ordered_threshold 8
|
||||
|
||||
defrecordp :ordered,
|
||||
defrecordp :ordered, HashDict,
|
||||
size: 0,
|
||||
bucket: []
|
||||
|
||||
@@ -47,7 +47,7 @@ defmodule HashDict do
|
||||
@node_size 8
|
||||
@node_template :erlang.make_tuple(@node_size, [])
|
||||
|
||||
defrecordp :trie,
|
||||
defrecordp :trie, HashDict,
|
||||
size: 0,
|
||||
depth: 0,
|
||||
expand_on: @node_size * @expand_load,
|
||||
@@ -57,6 +57,7 @@ defmodule HashDict do
|
||||
import Bitwise
|
||||
|
||||
# Let's inline common instructions
|
||||
@compile :inline_list_funcs
|
||||
@compile { :inline, bucket_hash: 1, bucket_index: 1, bucket_nth_index: 2, bucket_next: 1 }
|
||||
|
||||
@doc """
|
||||
@@ -72,7 +73,7 @@ defmodule HashDict do
|
||||
|
||||
## Examples
|
||||
|
||||
HashDict.new [{:b,1},{:a,2}]
|
||||
HashDict.new [{:b, 1}, {:a, 2}]
|
||||
#=> #HashDict<[a: 2, b: 1]>
|
||||
|
||||
"""
|
||||
@@ -101,27 +102,15 @@ defmodule HashDict do
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Puts the given key and value in the dict.
|
||||
"""
|
||||
def put(dict, key, value) do
|
||||
{ dict, _ } = dict_put(dict, key, { :put, value })
|
||||
dict
|
||||
end
|
||||
|
||||
@doc """
|
||||
Puts the given value under key in the dictionary
|
||||
only if one does not exist yet.
|
||||
"""
|
||||
def put_new(dict, key, value) do
|
||||
update(dict, key, value, fn(v) -> v end)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Updates the key in the dictionary according
|
||||
to the given function. Raises if the key does
|
||||
not exist in the dictionary.
|
||||
"""
|
||||
def update(dict, key, fun) when is_function(fun, 1) do
|
||||
case dict_put(dict, key, { :update, nil, fun }) do
|
||||
{ dict, 0 } ->
|
||||
@@ -131,55 +120,37 @@ defmodule HashDict do
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Updates the key in the dictionary according
|
||||
to the given function. Adds initial value if
|
||||
the key does not exist in the dicionary.
|
||||
"""
|
||||
def update(dict, key, initial, fun) when is_function(fun, 1) do
|
||||
{ dict, _ } = dict_put(dict, key, { :update, initial, fun })
|
||||
dict
|
||||
end
|
||||
|
||||
@doc """
|
||||
Gets the value under key from the dict.
|
||||
"""
|
||||
def get(dict, key, default // nil) do
|
||||
case dict_get(dict, key) do
|
||||
{ ^key, value } -> value
|
||||
false -> default
|
||||
case fetch(dict, key) do
|
||||
{ :ok, value } -> value
|
||||
:error -> default
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Fetches the value under key from the dict
|
||||
and return it in a tagged tuple.
|
||||
"""
|
||||
def fetch(dict, key) do
|
||||
case dict_get(dict, key) do
|
||||
{ ^key, value } -> { :ok, value }
|
||||
false -> :error
|
||||
end
|
||||
def fetch(ordered(bucket: bucket), key) do
|
||||
bucket_get(bucket, key)
|
||||
end
|
||||
|
||||
def fetch(trie(root: root, depth: depth), key) do
|
||||
bucket_get(node_bucket(root, depth, bucket_hash(key)), key)
|
||||
end
|
||||
|
||||
def fetch!(dict, key) when is_tuple(dict) do
|
||||
case dict_get(dict, key) do
|
||||
{ ^key, value } -> value
|
||||
false -> raise(KeyError, key: key)
|
||||
case fetch(dict, key) do
|
||||
{ :ok, value } -> value
|
||||
:error -> raise(KeyError, key: key)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@doc """
|
||||
Checks if the dict has the given key.
|
||||
"""
|
||||
def has_key?(dict, key) do
|
||||
match? { ^key, _ }, dict_get(dict, key)
|
||||
match? { :ok, _ }, fetch(dict, key)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns the value under key from the dict as well as the dict without key.
|
||||
"""
|
||||
def pop(dict, key, default // nil) do
|
||||
case dict_delete(dict, key) do
|
||||
{ dict, _, 0 } -> { default, dict }
|
||||
@@ -187,24 +158,15 @@ defmodule HashDict do
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Deletes a value from the dict.
|
||||
"""
|
||||
def delete(dict, key) do
|
||||
{ dict, _, _ } = dict_delete(dict, key)
|
||||
dict
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns the dict size.
|
||||
"""
|
||||
def size(dict) do
|
||||
elem(dict, 1)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns an empty dict.
|
||||
"""
|
||||
def empty(_) do
|
||||
ordered()
|
||||
end
|
||||
@@ -213,50 +175,38 @@ defmodule HashDict do
|
||||
size = elem(dict1, 1)
|
||||
case elem(dict2, 1) do
|
||||
^size ->
|
||||
dict_equal?(dict1, dict1)
|
||||
dict_equal?(dict1, dict2)
|
||||
_ ->
|
||||
false
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Converts the dict to a list.
|
||||
"""
|
||||
def to_list(ordered(bucket: bucket)) do
|
||||
bucket
|
||||
lc [k|v] inlist bucket, do: { k, v }
|
||||
end
|
||||
|
||||
def to_list(dict) do
|
||||
dict_fold(dict, [], [&1|&2]) |> :lists.reverse
|
||||
reduce(dict, [], [&1|&2]) |> :lists.reverse
|
||||
end
|
||||
|
||||
@doc """
|
||||
Get all keys in the dict.
|
||||
"""
|
||||
def keys(dict) do
|
||||
dict_fold(dict, [], fn { k, _ }, acc -> [k|acc] end)
|
||||
dict_fold(dict, [], fn [k|_], acc -> [k|acc] end)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Get all values in the dict.
|
||||
"""
|
||||
def values(dict) do
|
||||
dict_fold(dict, [], fn { _, v }, acc -> [v|acc] end)
|
||||
dict_fold(dict, [], fn [_|v], acc -> [v|acc] end)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Merges two dictionaries.
|
||||
"""
|
||||
def merge(dict, enum, callback // fn(_k, _v1, v2) -> v2 end)
|
||||
|
||||
def merge(dict1, dict2, callback) when is_record(dict1, HashDict) and is_record(dict2, HashDict) and elem(dict1, 1) < elem(dict2, 1) do
|
||||
dict_fold dict1, dict2, fn { k, v1 }, acc ->
|
||||
dict_fold dict1, dict2, fn [k|v1], acc ->
|
||||
update(acc, k, v1, callback.(k, v1, &1))
|
||||
end
|
||||
end
|
||||
|
||||
def merge(dict1, dict2, callback) when is_record(dict1, HashDict) and is_record(dict2, HashDict) do
|
||||
dict_fold dict2, dict1, fn { k, v2 }, acc ->
|
||||
dict_fold dict2, dict1, fn [k|v2], acc ->
|
||||
update(acc, k, v2, callback.(k, &1, v2))
|
||||
end
|
||||
end
|
||||
@@ -267,12 +217,6 @@ defmodule HashDict do
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Splits a dict into two dicts,
|
||||
one containing entries with key in the keys list,
|
||||
and another containing entries with key not in keys.
|
||||
Returns a 2-tuple of the new dicts.
|
||||
"""
|
||||
def split(dict, keys) do
|
||||
split(keys, new, dict)
|
||||
end
|
||||
@@ -288,10 +232,6 @@ defmodule HashDict do
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns a new dict with only the entries
|
||||
which key is in keys.
|
||||
"""
|
||||
def take(dict, keys) do
|
||||
take(dict, keys, new)
|
||||
end
|
||||
@@ -304,34 +244,23 @@ defmodule HashDict do
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns a new dict with only the entries
|
||||
which key is not in keys
|
||||
"""
|
||||
def drop(dict, []), do: dict
|
||||
|
||||
def drop(dict, [key|keys]) do
|
||||
drop(delete(dict, key), keys)
|
||||
end
|
||||
|
||||
@doc false
|
||||
def reduce(ordered(bucket: bucket), acc, fun) do
|
||||
:lists.foldl(fun, acc, bucket)
|
||||
bucket_reduce(bucket, acc, fun)
|
||||
end
|
||||
|
||||
def reduce(trie() = dict, acc, fun) do
|
||||
dict_fold(dict, acc, fun)
|
||||
def reduce(trie(root: root, depth: depth), acc, fun) do
|
||||
node_reduce(root, depth, acc, fun, @node_size)
|
||||
end
|
||||
|
||||
## Dict-wide functions
|
||||
|
||||
defp dict_get(ordered(bucket: bucket), key) do
|
||||
bucket_get(bucket, key)
|
||||
end
|
||||
|
||||
defp dict_get(trie(root: root, depth: depth), key) do
|
||||
bucket_get(node_bucket(root, depth, bucket_hash(key)), key)
|
||||
end
|
||||
|
||||
defp dict_fold(ordered(bucket: bucket), acc, fun) do
|
||||
bucket_fold(bucket, acc, fun)
|
||||
end
|
||||
@@ -379,7 +308,7 @@ defmodule HashDict do
|
||||
{ dict, value, 0 }
|
||||
{ root, value, -1 } ->
|
||||
{ if depth > 0 and trie(dict, :contract_on) == size do
|
||||
root = node_contract(root, depth, depth - 1)
|
||||
root = node_contract(root, depth)
|
||||
trie(dict,
|
||||
root: root,
|
||||
size: size - 1,
|
||||
@@ -394,9 +323,9 @@ defmodule HashDict do
|
||||
|
||||
defp dict_equal?(dict1, dict2) do
|
||||
try do
|
||||
reduce(dict1, true, fn({ key, value }, acc) ->
|
||||
dict_fold(dict1, true, fn [key|value], acc ->
|
||||
case fetch(dict2, key) do
|
||||
{ _ok, ^value } ->
|
||||
{ :ok, ^value } ->
|
||||
acc
|
||||
_ ->
|
||||
throw(:error)
|
||||
@@ -410,12 +339,12 @@ defmodule HashDict do
|
||||
## Bucket helpers
|
||||
|
||||
# Get value from the bucket
|
||||
defp bucket_get([{k,_}|_bucket], key) when k > key do
|
||||
false
|
||||
defp bucket_get([[k|_]|_bucket], key) when k > key do
|
||||
:error
|
||||
end
|
||||
|
||||
defp bucket_get([{key,_}=e|_bucket], key) do
|
||||
e
|
||||
defp bucket_get([[key|value]|_bucket], key) do
|
||||
{ :ok, value }
|
||||
end
|
||||
|
||||
defp bucket_get([_e|bucket], key) do
|
||||
@@ -423,24 +352,24 @@ defmodule HashDict do
|
||||
end
|
||||
|
||||
defp bucket_get([], _key) do
|
||||
false
|
||||
:error
|
||||
end
|
||||
|
||||
# Puts a value in the bucket
|
||||
defp bucket_put([{k,_}|_]=bucket, key, { :put, value }) when k > key do
|
||||
{ [{key, value}|bucket], 1 }
|
||||
defp bucket_put([[k|_]|_]=bucket, key, { :put, value }) when k > key do
|
||||
{ [[key|value]|bucket], 1 }
|
||||
end
|
||||
|
||||
defp bucket_put([{k,_}|_]=bucket, key, { :update, initial, _fun }) when k > key do
|
||||
{ [{key, initial}|bucket], 1 }
|
||||
defp bucket_put([[k|_]|_]=bucket, key, { :update, initial, _fun }) when k > key do
|
||||
{ [[key|initial]|bucket], 1 }
|
||||
end
|
||||
|
||||
defp bucket_put([{key,_}|bucket], key, { :put, value }) do
|
||||
{ [{key,value}|bucket], 0 }
|
||||
defp bucket_put([[key|_]|bucket], key, { :put, value }) do
|
||||
{ [[key|value]|bucket], 0 }
|
||||
end
|
||||
|
||||
defp bucket_put([{key,value}|bucket], key, { :update, _initial, fun }) do
|
||||
{ [{key, fun.(value)}|bucket], 0 }
|
||||
defp bucket_put([[key|value]|bucket], key, { :update, _initial, fun }) do
|
||||
{ [[key|fun.(value)]|bucket], 0 }
|
||||
end
|
||||
|
||||
defp bucket_put([e|bucket], key, value) do
|
||||
@@ -449,26 +378,26 @@ defmodule HashDict do
|
||||
end
|
||||
|
||||
defp bucket_put([], key, { :put, value }) do
|
||||
{ [{key,value}], 1 }
|
||||
{ [[key|value]], 1 }
|
||||
end
|
||||
|
||||
defp bucket_put([], key, { :update, initial, _fun }) do
|
||||
{ [{key,initial}], 1 }
|
||||
{ [[key|initial]], 1 }
|
||||
end
|
||||
|
||||
# Puts a value in the bucket without returning
|
||||
# the operation value
|
||||
defp bucket_put!([{k,_}|_]=bucket, key, value) when k > key, do: [{key,value}|bucket]
|
||||
defp bucket_put!([{key,_}|bucket], key, value), do: [{key,value}|bucket]
|
||||
defp bucket_put!([{_,_}=e|bucket], key, value), do: [e|bucket_put!(bucket, key, value)]
|
||||
defp bucket_put!([], key, value), do: [{key,value}]
|
||||
defp bucket_put!([[k|_]|_]=bucket, key, value) when k > key, do: [[key|value]|bucket]
|
||||
defp bucket_put!([[key|_]|bucket], key, value), do: [[key|value]|bucket]
|
||||
defp bucket_put!([e|bucket], key, value), do: [e|bucket_put!(bucket, key, value)]
|
||||
defp bucket_put!([], key, value), do: [[key|value]]
|
||||
|
||||
# Deletes a key from the bucket
|
||||
defp bucket_delete([{k,_}|_]=bucket, key) when k > key do
|
||||
defp bucket_delete([[k|_]|_]=bucket, key) when k > key do
|
||||
{ bucket, nil, 0 }
|
||||
end
|
||||
|
||||
defp bucket_delete([{key,value}|bucket], key) do
|
||||
defp bucket_delete([[key|value]|bucket], key) do
|
||||
{ bucket, value, -1 }
|
||||
end
|
||||
|
||||
@@ -481,7 +410,15 @@ defmodule HashDict do
|
||||
{ [], nil, 0 }
|
||||
end
|
||||
|
||||
# Folds the bucket
|
||||
# Reduces the bucket
|
||||
defp bucket_reduce([[k|v]|t], acc, fun) do
|
||||
bucket_reduce(t, fun.({ k, v }, acc), fun)
|
||||
end
|
||||
|
||||
defp bucket_reduce([], acc, _fun) do
|
||||
acc
|
||||
end
|
||||
|
||||
defp bucket_fold(bucket, acc, fun) do
|
||||
:lists.foldl(fun, acc, bucket)
|
||||
end
|
||||
@@ -544,6 +481,20 @@ defmodule HashDict do
|
||||
end
|
||||
end
|
||||
|
||||
# Reduces a node recursively
|
||||
defp node_reduce(bucket, -1, acc, fun, _) do
|
||||
bucket_reduce(bucket, acc, fun)
|
||||
end
|
||||
|
||||
defp node_reduce(node, depth, acc, fun, count) when count >= 1 do
|
||||
acc = node_reduce(:erlang.element(count, node), depth - 1, acc, fun, @node_size)
|
||||
node_reduce(node, depth, acc, fun, count - 1)
|
||||
end
|
||||
|
||||
defp node_reduce(_node, _, acc, _fun, 0) do
|
||||
acc
|
||||
end
|
||||
|
||||
# Folds a node recursively
|
||||
defp node_fold(bucket, -1, acc, fun, _) do
|
||||
bucket_fold(bucket, acc, fun)
|
||||
@@ -572,27 +523,26 @@ defmodule HashDict do
|
||||
node_expand(b7, depth, n), node_expand(b8, depth, n) }
|
||||
end
|
||||
|
||||
defp node_contract({ b1, b2, b3, b4, b5, b6, b7, b8 }, depth, n) when depth > 1 do
|
||||
defp node_contract({ b1, b2, b3, b4, b5, b6, b7, b8 }, depth) when depth > 0 do
|
||||
depth = depth - 1
|
||||
{ node_contract(b1, depth, n), node_contract(b2, depth, n), node_contract(b3, depth, n),
|
||||
node_contract(b4, depth, n), node_contract(b5, depth, n), node_contract(b6, depth, n),
|
||||
node_contract(b7, depth, n), node_contract(b8, depth, n) }
|
||||
{ node_contract(b1, depth), node_contract(b2, depth), node_contract(b3, depth),
|
||||
node_contract(b4, depth), node_contract(b5, depth), node_contract(b6, depth),
|
||||
node_contract(b7, depth), node_contract(b8, depth) }
|
||||
end
|
||||
|
||||
defp node_contract({ b1, b2, b3, b4, b5, b6, b7, b8 }, 1, n) do
|
||||
@node_template |> each_contract(b1, n) |> each_contract(b2, n) |> each_contract(b3, n)
|
||||
|> each_contract(b4, n) |> each_contract(b5, n) |> each_contract(b6, n)
|
||||
|> each_contract(b7, n) |> each_contract(b8, n)
|
||||
defp node_contract({ b1, b2, b3, b4, b5, b6, b7, b8 }, 0) do
|
||||
b1 |> each_contract(b2) |> each_contract(b3) |> each_contract(b4)
|
||||
|> each_contract(b5) |> each_contract(b6) |> each_contract(b7)
|
||||
|> each_contract(b8)
|
||||
end
|
||||
|
||||
defp each_contract(acc, { b1, b2, b3, b4, b5, b6, b7, b8 }, n) do
|
||||
acc |> node_relocate(b1, n) |> node_relocate(b2, n) |> node_relocate(b3, n)
|
||||
|> node_relocate(b4, n) |> node_relocate(b5, n) |> node_relocate(b6, n)
|
||||
|> node_relocate(b7, n) |> node_relocate(b8, n)
|
||||
end
|
||||
defp each_contract([[k|_v]=e|acc], [[key|_value]|_]=bucket) when k < key, do: [e|each_contract(acc, bucket)]
|
||||
defp each_contract(acc, [e|bucket]), do: [e|each_contract(acc, bucket)]
|
||||
defp each_contract([], bucket), do: bucket
|
||||
defp each_contract(acc, []), do: acc
|
||||
|
||||
defp node_relocate(node // @node_template, bucket, n) do
|
||||
:lists.foldl fn { key, value }, acc ->
|
||||
:lists.foldl fn [key|value], acc ->
|
||||
pos = key |> bucket_hash() |> bucket_nth_index(n)
|
||||
set_elem(acc, pos, bucket_put!(elem(acc, pos), key, value))
|
||||
end, node, bucket
|
||||
@@ -609,11 +559,3 @@ end
|
||||
defimpl Access, for: HashDict do
|
||||
def access(dict, key), do: HashDict.get(dict, key, nil)
|
||||
end
|
||||
|
||||
defimpl Binary.Inspect, for: HashDict do
|
||||
import Kernel, except: [inspect: 2]
|
||||
|
||||
def inspect(dict, opts) do
|
||||
"#HashDict<" <> Kernel.inspect(HashDict.to_list(dict), opts) <> ">"
|
||||
end
|
||||
end
|
||||
|
||||
@@ -0,0 +1,492 @@
|
||||
defmodule HashSet do
|
||||
@moduledoc """
|
||||
A set store.
|
||||
|
||||
The `HashSet` is meant to work well with both small and
|
||||
large sets. It is an implementation of the `Set` behaviour.
|
||||
For more information about the functions and their APIs,
|
||||
please consult the `Set` module.
|
||||
"""
|
||||
|
||||
@behaviour Set
|
||||
|
||||
# The ordered record contains a single bucket.
|
||||
@ordered_threshold 8
|
||||
|
||||
defrecordp :ordered, HashSet,
|
||||
size: 0,
|
||||
bucket: []
|
||||
|
||||
# The bucketed record contains a series of buckets.
|
||||
@expand_load 5
|
||||
@contract_load 2
|
||||
@node_bitmap 0b1111
|
||||
@node_shift 4
|
||||
@node_size 16
|
||||
@node_template :erlang.make_tuple(@node_size, [])
|
||||
|
||||
@expand_default (@node_size * @expand_load)
|
||||
@contract_default @contract_load
|
||||
|
||||
defrecordp :trie, HashSet,
|
||||
size: 0,
|
||||
depth: 0,
|
||||
expand_on: @expand_default,
|
||||
contract_on: @contract_default,
|
||||
root: @node_template
|
||||
|
||||
import Bitwise
|
||||
|
||||
@compile :inline_list_funcs
|
||||
@compile { :inline, bucket_hash: 1, bucket_index: 1, bucket_nth_index: 2, bucket_next: 1 }
|
||||
|
||||
@doc """
|
||||
Creates a new empty set.
|
||||
"""
|
||||
def new() do
|
||||
ordered()
|
||||
end
|
||||
|
||||
@doc """
|
||||
Creates a new set from the given enumerable.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> HashSet.new [1, 1, 2, 3, 3] |> HashSet.to_list
|
||||
[1,2,3]
|
||||
|
||||
"""
|
||||
def new(members) do
|
||||
Enum.reduce members, ordered(), fn member, set ->
|
||||
put(set, member)
|
||||
end
|
||||
end
|
||||
|
||||
def union(set1, set2) when is_record(set1, HashSet) and is_record(set2, HashSet) and elem(set1, 1) <= elem(set2, 1) do
|
||||
set_fold set1, set2, fn v1, acc ->
|
||||
put(acc, v1)
|
||||
end
|
||||
end
|
||||
|
||||
def union(set1, set2) when is_record(set1, HashSet) and is_record(set2, HashSet) do
|
||||
set_fold set2, set1, fn v, acc ->
|
||||
put(acc, v)
|
||||
end
|
||||
end
|
||||
|
||||
def intersection(set1, set2) when is_record(set1, HashSet) and is_record(set2, HashSet) and elem(set1, 1) <= elem(set2, 1) do
|
||||
set_filter set1, fn e -> set_member?(set2, e) end
|
||||
end
|
||||
|
||||
def intersection(set1, set2) when is_record(set1, HashSet) and is_record(set2, HashSet) do
|
||||
set_filter set2, fn e -> set_member?(set1, e) end
|
||||
end
|
||||
|
||||
def difference(set1, set2) when is_record(set1, HashSet) and is_record(set2, HashSet) do
|
||||
set_filter set1, fn m -> not set_member?(set2, m) end
|
||||
end
|
||||
|
||||
def member?(set, member) when is_record(set, HashSet) do
|
||||
set_member?(set, member)
|
||||
end
|
||||
|
||||
def empty(_) do
|
||||
ordered()
|
||||
end
|
||||
|
||||
def size(set) do
|
||||
elem(set, 1)
|
||||
end
|
||||
|
||||
def to_list(ordered(bucket: bucket)) do
|
||||
bucket
|
||||
end
|
||||
|
||||
def to_list(set) do
|
||||
set_fold(set, [], [&1|&2]) |> :lists.reverse
|
||||
end
|
||||
|
||||
def put(set, member) do
|
||||
{ set, _ } = set_put(set, member)
|
||||
set
|
||||
end
|
||||
|
||||
def delete(set, member) do
|
||||
{ set, _ } = set_delete(set, member)
|
||||
set
|
||||
end
|
||||
|
||||
def equal?(set1, set2) do
|
||||
size = elem(set1, 1)
|
||||
case elem(set2, 1) do
|
||||
^size ->
|
||||
set_equal?(set1, set2)
|
||||
_ ->
|
||||
false
|
||||
end
|
||||
end
|
||||
|
||||
def subset?(set1, set2) do
|
||||
set_equal?(set1, set2)
|
||||
end
|
||||
|
||||
def disjoint?(set1, set2) do
|
||||
set_disjoint?(set1, set2)
|
||||
end
|
||||
|
||||
@doc false
|
||||
def reduce(ordered(bucket: bucket), acc, fun) do
|
||||
:lists.foldl(fun, acc, bucket)
|
||||
end
|
||||
|
||||
def reduce(trie() = set, acc, fun) do
|
||||
set_fold(set, acc, fun)
|
||||
end
|
||||
|
||||
## HashSet-wide functions
|
||||
|
||||
defp set_filter(ordered(bucket: bucket, size: size), fun) do
|
||||
{ new, removed_count } = bucket_filter(bucket, fun, [], 0)
|
||||
ordered(bucket: new, size: size - removed_count)
|
||||
end
|
||||
|
||||
defp set_filter(trie(root: root, depth: depth, size: size) = set, fun) do
|
||||
{ new, removed_count } = node_filter(root, depth, fun, @node_size)
|
||||
|
||||
if depth > 0 and trie(set, :contract_on) >= (size - removed_count) do
|
||||
contract_trie(trie(root: new,
|
||||
size: size - removed_count,
|
||||
depth: depth,
|
||||
contract_on: trie(set, :contract_on),
|
||||
expand_on: trie(set, :expand_on)))
|
||||
else
|
||||
trie(size: size - removed_count, root: new, depth: depth)
|
||||
end
|
||||
end
|
||||
|
||||
defp set_put(ordered(size: @ordered_threshold, bucket: bucket), member) do
|
||||
root = node_relocate(bucket, 0)
|
||||
set_put(trie(size: @ordered_threshold, root: root), member)
|
||||
end
|
||||
|
||||
defp set_put(ordered(size: size, bucket: bucket) = set, member) do
|
||||
{ new, count } = bucket_put(bucket, member)
|
||||
{ ordered(set, size: size + count, bucket: new), count }
|
||||
end
|
||||
|
||||
defp set_put(trie(root: root, depth: depth, size: size, expand_on: size, contract_on: contract_on) = set, member) do
|
||||
root = node_expand(root, depth, depth + 1)
|
||||
set = trie(set, root: root, depth: depth + 1,
|
||||
expand_on: size * @node_size, contract_on: contract_on * @node_size)
|
||||
set_put(set, member)
|
||||
end
|
||||
|
||||
defp set_put(trie(root: root, size: size, depth: depth) = set, member) do
|
||||
pos = bucket_hash(member)
|
||||
{ root, count } = node_put(root, depth, pos, member)
|
||||
{ trie(set, size: size + count, root: root), count }
|
||||
end
|
||||
|
||||
defp set_member?(ordered(bucket: bucket), member) do
|
||||
:lists.member(member, bucket)
|
||||
end
|
||||
|
||||
defp set_member?(trie(root: root, depth: depth), member) do
|
||||
:lists.member(member, node_bucket(root, depth, bucket_hash(member)))
|
||||
end
|
||||
|
||||
defp set_delete(ordered(bucket: bucket, size: size) = set, member) do
|
||||
case bucket_delete(bucket, member) do
|
||||
{ _, 0 } ->
|
||||
{ set, 0 }
|
||||
{ new_bucket, -1 } ->
|
||||
{ ordered(set, size: size - 1, bucket: new_bucket), -1 }
|
||||
end
|
||||
end
|
||||
|
||||
defp set_delete(trie(root: root, size: size, depth: depth) = set, member) do
|
||||
pos = bucket_hash(member)
|
||||
case node_delete(root, depth, pos, member) do
|
||||
{ _, 0 } ->
|
||||
{ set, 0 }
|
||||
{ root, -1 } ->
|
||||
{ if depth > 0 and trie(set, :contract_on) == size do
|
||||
root = node_contract(root, depth)
|
||||
trie(set,
|
||||
root: root,
|
||||
size: size - 1,
|
||||
depth: depth - 1,
|
||||
contract_on: div(size, @node_size),
|
||||
expand_on: div(trie(set, :expand_on), @node_size))
|
||||
else
|
||||
trie(set, size: size - 1, root: root)
|
||||
end, -1 }
|
||||
end
|
||||
end
|
||||
|
||||
defp set_equal?(set1, set2) do
|
||||
try do
|
||||
reduce(set1, true, fn member, acc ->
|
||||
case member?(set2, member) do
|
||||
true -> acc
|
||||
_ -> throw(:error)
|
||||
end
|
||||
end)
|
||||
catch
|
||||
:error -> false
|
||||
end
|
||||
end
|
||||
|
||||
defp set_disjoint?(set1, set2) do
|
||||
try do
|
||||
reduce(set1, true, fn member, acc ->
|
||||
case member?(set2, member) do
|
||||
false -> acc
|
||||
_ -> throw(:error)
|
||||
end
|
||||
end)
|
||||
catch
|
||||
:error -> false
|
||||
end
|
||||
end
|
||||
|
||||
defp set_fold(ordered(bucket: bucket), acc, fun) do
|
||||
bucket_fold(bucket, acc, fun)
|
||||
end
|
||||
|
||||
defp set_fold(trie(root: root, depth: depth), acc, fun) do
|
||||
node_fold(root, depth, acc, fun, @node_size)
|
||||
end
|
||||
|
||||
## Bucket helpers
|
||||
|
||||
defp bucket_filter([e|bucket], fun, acc, count) do
|
||||
case fun.(e) do
|
||||
true -> bucket_filter(bucket, fun, [e|acc], count)
|
||||
false -> bucket_filter(bucket, fun, acc, count + 1)
|
||||
end
|
||||
end
|
||||
|
||||
defp bucket_filter([], _fun, acc, count) do
|
||||
{ :lists.reverse(acc), count }
|
||||
end
|
||||
|
||||
defp bucket_put([m|_]=bucket, member) when m > member do
|
||||
{ [member|bucket], 1 }
|
||||
end
|
||||
|
||||
defp bucket_put([member|bucket], member) do
|
||||
{ [member|bucket], 0 }
|
||||
end
|
||||
|
||||
defp bucket_put([e|bucket], member) do
|
||||
{ rest, count } = bucket_put(bucket, member)
|
||||
{ [e|rest], count }
|
||||
end
|
||||
|
||||
defp bucket_put([], member) do
|
||||
{ [member], 1 }
|
||||
end
|
||||
|
||||
defp bucket_put!([m|_]=bucket, member) when m > member, do: [member|bucket]
|
||||
defp bucket_put!([member|bucket], member), do: [member|bucket]
|
||||
defp bucket_put!([e|bucket], member), do: [e|bucket_put!(bucket, member)]
|
||||
defp bucket_put!([], member), do: [member]
|
||||
|
||||
# Deletes a key from the bucket
|
||||
defp bucket_delete([m,_|_]=bucket, member) when m > member do
|
||||
{ bucket, 0 }
|
||||
end
|
||||
|
||||
defp bucket_delete([member|bucket], member) do
|
||||
{ bucket, -1 }
|
||||
end
|
||||
|
||||
defp bucket_delete([e|bucket], member) do
|
||||
{ rest, count } = bucket_delete(bucket, member)
|
||||
{ [e|rest], count }
|
||||
end
|
||||
|
||||
defp bucket_delete([], _member) do
|
||||
{ [], 0 }
|
||||
end
|
||||
|
||||
defp bucket_fold(bucket, acc, fun) do
|
||||
:lists.foldl(fun, acc, bucket)
|
||||
end
|
||||
|
||||
defp bucket_hash(key) do
|
||||
:erlang.phash2(key)
|
||||
end
|
||||
|
||||
defp bucket_nth_index(hash, n) do
|
||||
(hash >>> (@node_shift * n)) &&& @node_bitmap
|
||||
end
|
||||
|
||||
defp bucket_index(hash) do
|
||||
hash &&& @node_bitmap
|
||||
end
|
||||
|
||||
defp bucket_next(hash) do
|
||||
hash >>> @node_shift
|
||||
end
|
||||
|
||||
# Trie resizing
|
||||
|
||||
defp contract_trie(trie(depth: 0) = set) do
|
||||
set
|
||||
end
|
||||
|
||||
defp contract_trie(trie(root: root, depth: depth, size: size, contract_on: contract_on, expand_on: expand_on) = set) when size <= contract_on do
|
||||
new_contract_on = div(contract_on, @node_size)
|
||||
new_expand_on = div(expand_on, @node_size)
|
||||
|
||||
if new_contract_on == 0, do: new_contract_on = @contract_default
|
||||
if new_expand_on == 0, do: new_expand_on = @expand_default
|
||||
|
||||
contract_trie(trie(set, root: node_contract(root, depth),
|
||||
size: size,
|
||||
depth: depth - 1,
|
||||
contract_on: new_contract_on,
|
||||
expand_on: new_expand_on))
|
||||
end
|
||||
|
||||
defp contract_trie(set) do
|
||||
set
|
||||
end
|
||||
|
||||
# Node helpers
|
||||
|
||||
# Gets a bucket from the node
|
||||
defp node_bucket(node, 0, hash) do
|
||||
elem(node, bucket_index(hash))
|
||||
end
|
||||
|
||||
defp node_bucket(node, depth, hash) do
|
||||
child = elem(node, bucket_index(hash))
|
||||
node_bucket(child, depth - 1, bucket_next(hash))
|
||||
end
|
||||
|
||||
defp node_put(node, 0, hash, member) do
|
||||
pos = bucket_index(hash)
|
||||
{ new, count } = bucket_put(elem(node, pos), member)
|
||||
{ set_elem(node, pos, new), count }
|
||||
end
|
||||
|
||||
defp node_put(node, depth, hash, member) do
|
||||
pos = bucket_index(hash)
|
||||
{ new, count } = node_put(elem(node, pos), depth - 1, bucket_next(hash), member)
|
||||
{ set_elem(node, pos, new), count }
|
||||
end
|
||||
|
||||
# Deletes a key from the bucket
|
||||
defp node_delete(node, 0, hash, member) do
|
||||
pos = bucket_index(hash)
|
||||
case bucket_delete(elem(node, pos), member) do
|
||||
{ _, 0 } -> { node, 0 }
|
||||
{ new, -1 } -> { set_elem(node, pos, new), -1 }
|
||||
end
|
||||
end
|
||||
|
||||
defp node_delete(node, depth, hash, member) do
|
||||
pos = bucket_index(hash)
|
||||
case node_delete(elem(node, pos), depth - 1, bucket_next(hash), member) do
|
||||
{ _, 0 } -> { node, 0 }
|
||||
{ new, -1 } -> { set_elem(node, pos, new), -1 }
|
||||
end
|
||||
end
|
||||
|
||||
defp node_fold(bucket, -1, acc, fun, _) do
|
||||
bucket_fold(bucket, acc, fun)
|
||||
end
|
||||
|
||||
defp node_fold(node, depth, acc, fun, count) when count >= 1 do
|
||||
acc = node_fold(:erlang.element(count, node), depth - 1, acc, fun, @node_size)
|
||||
node_fold(node, depth, acc, fun, count - 1)
|
||||
end
|
||||
|
||||
defp node_fold(_node, _, acc, _fun, 0) do
|
||||
acc
|
||||
end
|
||||
|
||||
defp node_filter(bucket, -1, fun, _) do
|
||||
bucket_filter(bucket, fun, [], 0)
|
||||
end
|
||||
|
||||
defp node_filter(node, depth, fun, count) when count >= 1 do
|
||||
case node_filter(:erlang.element(count, node), depth - 1, fun, @node_size) do
|
||||
{ _, 0 } ->
|
||||
node_filter(node, depth, fun, count - 1)
|
||||
{ new_element, count1 } ->
|
||||
{ new_node, count2 } = node_filter(:erlang.setelement(count, node, new_element), depth, fun, count - 1)
|
||||
{ new_node, count1 + count2 }
|
||||
end
|
||||
end
|
||||
|
||||
defp node_filter(node, _, _fun, 0) do
|
||||
{ node, 0 }
|
||||
end
|
||||
|
||||
defp node_relocate(node // @node_template, bucket, n) do
|
||||
:lists.foldl fn member, acc ->
|
||||
pos = member |> bucket_hash() |> bucket_nth_index(n)
|
||||
set_elem(acc, pos, bucket_put!(elem(acc, pos), member))
|
||||
end, node, bucket
|
||||
end
|
||||
|
||||
# Node resizing
|
||||
defp node_expand({ b1, b2, b3, b4, b5, b6, b7, b8, b9, b10, b11, b12, b13, b14, b15, b16 }, 0, n) do
|
||||
{ node_relocate(b1, n), node_relocate(b2, n), node_relocate(b3, n),
|
||||
node_relocate(b4, n), node_relocate(b5, n), node_relocate(b6, n),
|
||||
node_relocate(b7, n), node_relocate(b8, n), node_relocate(b9, n),
|
||||
node_relocate(b10, n), node_relocate(b11, n), node_relocate(b12, n),
|
||||
node_relocate(b13, n), node_relocate(b14, n), node_relocate(b15, n),
|
||||
node_relocate(b16, n) }
|
||||
end
|
||||
|
||||
defp node_expand({ b1, b2, b3, b4, b5, b6, b7, b8, b9, b10, b11, b12, b13, b14, b15, b16 }, depth, n) do
|
||||
depth = depth - 1
|
||||
{ node_expand(b1, depth, n), node_expand(b2, depth, n), node_expand(b3, depth, n),
|
||||
node_expand(b4, depth, n), node_expand(b5, depth, n), node_expand(b6, depth, n),
|
||||
node_expand(b7, depth, n), node_expand(b8, depth, n), node_expand(b9, depth, n),
|
||||
node_expand(b10, depth, n), node_expand(b11, depth, n), node_expand(b12, depth, n),
|
||||
node_expand(b13, depth, n), node_expand(b14, depth, n), node_expand(b15, depth, n),
|
||||
node_expand(b16, depth, n) }
|
||||
end
|
||||
|
||||
defp node_contract({ b1, b2, b3, b4, b5, b6, b7, b8, b9, b10, b11, b12, b13, b14, b15, b16 }, depth) when depth > 0 do
|
||||
depth = depth - 1
|
||||
{ node_contract(b1, depth), node_contract(b2, depth), node_contract(b3, depth),
|
||||
node_contract(b4, depth), node_contract(b5, depth), node_contract(b6, depth),
|
||||
node_contract(b7, depth), node_contract(b8, depth), node_contract(b9, depth),
|
||||
node_contract(b10, depth), node_contract(b11, depth), node_contract(b12, depth),
|
||||
node_contract(b13, depth), node_contract(b14, depth), node_contract(b15, depth),
|
||||
node_contract(b16, depth) }
|
||||
end
|
||||
|
||||
defp node_contract({ b1, b2, b3, b4, b5, b6, b7, b8, b9, b10, b11, b12, b13, b14, b15, b16 }, 0) do
|
||||
b1 |> each_contract(b2) |> each_contract(b3) |> each_contract(b4)
|
||||
|> each_contract(b5) |> each_contract(b6) |> each_contract(b7)
|
||||
|> each_contract(b8) |> each_contract(b9) |> each_contract(b10)
|
||||
|> each_contract(b11) |> each_contract(b12) |> each_contract(b13)
|
||||
|> each_contract(b14) |> each_contract(b15) |> each_contract(b16)
|
||||
end
|
||||
|
||||
defp each_contract([m1|acc], [m2|_]=bucket) when m1 < m2, do: [m1|each_contract(acc, bucket)]
|
||||
defp each_contract(acc, [m|bucket]), do: [m|each_contract(acc, bucket)]
|
||||
defp each_contract([], bucket), do: bucket
|
||||
defp each_contract(acc, []), do: acc
|
||||
|
||||
@doc false
|
||||
def inspect_depth(trie(depth: d)), do: d
|
||||
@doc false
|
||||
def inspect_contract(trie(contract_on: c)), do: c
|
||||
@doc false
|
||||
def inspect_expand(trie(expand_on: e)), do: e
|
||||
end
|
||||
|
||||
defimpl Enumerable, for: HashSet do
|
||||
def reduce(set, acc, fun), do: HashSet.reduce(set, acc, fun)
|
||||
def member?(set, v), do: HashSet.member?(set, v)
|
||||
def count(set), do: HashSet.size(set)
|
||||
end
|
||||
@@ -0,0 +1,400 @@
|
||||
import Kernel, except: [inspect: 1]
|
||||
import Inspect.Algebra
|
||||
|
||||
defrecord Inspect.Opts, raw: false, limit: :infinity, pretty: false, width: 80
|
||||
|
||||
defprotocol Inspect do
|
||||
@moduledoc """
|
||||
The `Inspect` protocol is responsible for converting any Elixir
|
||||
data structure into an algebra document. This document is then
|
||||
formatted, either in pretty printing format or a regular one.
|
||||
|
||||
The `inspect/2` function receives the entity to be inspected
|
||||
followed by the inspecting options, represented by the record
|
||||
`Inspect.Opts`.
|
||||
|
||||
Inspection is done using the functions available in
|
||||
`Inspect.Algebra` and by calling `Kernel.inspect/2` recursively
|
||||
passing the `Inspect.Opts` as an argument. When `Kernel.inspect/2`
|
||||
receives an `Inspect.Opts` record as the second argument, it returns
|
||||
the underlying algebra document instead of the formatted string.
|
||||
|
||||
Many times, inspecting a structure can be implemented using functions
|
||||
of the existing entities. For example, here is `HashSet`'s `inspect`
|
||||
implementation:
|
||||
|
||||
defimpl Inspect, for: HashSet do
|
||||
import Inspect.Algebra
|
||||
|
||||
def inspect(dict, opts) do
|
||||
concat ["#HashSet<", Kernel.inspect(HashSet.to_list(dict), opts), ">"]
|
||||
end
|
||||
end
|
||||
|
||||
The `concat` function comes from `Inspect.Algebra` and it
|
||||
concatenates algebra documents together. In the example above,
|
||||
it is concatenating the string `"HashSet<"` (all strings are
|
||||
valid algebra documents that keep their formatting when pretty
|
||||
printed), the document returned by `Kernel.inspect/2` and the
|
||||
other string `">"`.
|
||||
|
||||
Since regular strings are valid entities in an algebra document,
|
||||
an implementation of inspect may simply return a binary,
|
||||
although that will devoid it of any pretty-printing.
|
||||
"""
|
||||
|
||||
def inspect(thing, opts)
|
||||
end
|
||||
|
||||
defimpl Inspect, for: Atom do
|
||||
require Macro
|
||||
|
||||
@doc """
|
||||
Represents the atom as an Elixir term. The atoms `false`, `true`
|
||||
and `nil` are simply quoted. Modules are properly represented
|
||||
as modules using the dot notation.
|
||||
|
||||
Notice that in Elixir, all operators can be represented using
|
||||
literal atoms (`:+`, `:-`, etc).
|
||||
|
||||
## Examples
|
||||
|
||||
iex> inspect(:foo)
|
||||
":foo"
|
||||
iex> inspect(nil)
|
||||
"nil"
|
||||
iex> inspect(Foo.Bar)
|
||||
"Foo.Bar"
|
||||
|
||||
"""
|
||||
def inspect(atom, _opts) do
|
||||
inspect(atom)
|
||||
end
|
||||
|
||||
def inspect(false), do: "false"
|
||||
def inspect(true), do: "true"
|
||||
def inspect(nil), do: "nil"
|
||||
def inspect(:""), do: ":\"\""
|
||||
def inspect(Elixir), do: "Elixir"
|
||||
|
||||
def inspect(atom) do
|
||||
binary = atom_to_binary(atom)
|
||||
|
||||
cond do
|
||||
valid_atom_identifier?(binary) ->
|
||||
":" <> binary
|
||||
valid_ref_identifier?(binary) ->
|
||||
"Elixir." <> rest = binary
|
||||
rest
|
||||
atom in Macro.binary_ops or atom in Macro.unary_ops ->
|
||||
":" <> binary
|
||||
true ->
|
||||
<< ?:, ?", String.escape(binary, ?") :: binary, ?" >>
|
||||
end
|
||||
end
|
||||
|
||||
# Detect if atom is an atom alias (Elixir.Foo.Bar.Baz)
|
||||
|
||||
defp valid_ref_identifier?("Elixir" <> rest) do
|
||||
valid_ref_piece?(rest)
|
||||
end
|
||||
|
||||
defp valid_ref_identifier?(_), do: false
|
||||
|
||||
defp valid_ref_piece?(<<?., h, t :: binary>>) when h in ?A..?Z do
|
||||
valid_ref_piece? valid_identifier?(t)
|
||||
end
|
||||
|
||||
defp valid_ref_piece?(<<>>), do: true
|
||||
defp valid_ref_piece?(_), do: false
|
||||
|
||||
# Detect if atom
|
||||
|
||||
defp valid_atom_identifier?(<<h, t :: binary>>) when h in ?a..?z or h in ?A..?Z or h == ?_ do
|
||||
case valid_identifier?(t) do
|
||||
<<>> -> true
|
||||
<<??>> -> true
|
||||
<<?!>> -> true
|
||||
_ -> false
|
||||
end
|
||||
end
|
||||
|
||||
defp valid_atom_identifier?(_), do: false
|
||||
|
||||
defp valid_identifier?(<<h, t :: binary>>)
|
||||
when h in ?a..?z
|
||||
when h in ?A..?Z
|
||||
when h in ?0..?9
|
||||
when h == ?_ do
|
||||
valid_identifier? t
|
||||
end
|
||||
|
||||
defp valid_identifier?(other), do: other
|
||||
end
|
||||
|
||||
defimpl Inspect, for: BitString do
|
||||
@doc %B"""
|
||||
Represents a string as itself escaping all necessary
|
||||
characters. Bitstrings and strings that contain non-
|
||||
printable characters are printed using the bitstring
|
||||
syntax.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> inspect("bar")
|
||||
"\"bar\""
|
||||
iex> inspect("f\"oo")
|
||||
"\"f\\\"oo\""
|
||||
|
||||
"""
|
||||
def inspect(thing, opts) when is_binary(thing) do
|
||||
if String.printable?(thing) do
|
||||
<< ?", String.escape(thing, ?") :: binary, ?" >>
|
||||
else
|
||||
as_bitstring(thing, opts)
|
||||
end
|
||||
end
|
||||
|
||||
def inspect(thing, opts) do
|
||||
as_bitstring(thing, opts)
|
||||
end
|
||||
|
||||
## Bitstrings
|
||||
|
||||
defp as_bitstring(bitstring, Inspect.Opts[] = opts) do
|
||||
"<<" <> each_bit(bitstring, opts.limit) <> ">>"
|
||||
end
|
||||
|
||||
defp each_bit(_, 0) do
|
||||
"..."
|
||||
end
|
||||
|
||||
defp each_bit(<<h, t :: bitstring>>, counter) when t != <<>> do
|
||||
integer_to_binary(h) <> ", " <> each_bit(t, decrement(counter))
|
||||
end
|
||||
|
||||
defp each_bit(<<h :: size(8)>>, _counter) do
|
||||
integer_to_binary(h)
|
||||
end
|
||||
|
||||
defp each_bit(<<>>, _counter) do
|
||||
<<>>
|
||||
end
|
||||
|
||||
defp each_bit(bitstring, _counter) do
|
||||
size = bit_size(bitstring)
|
||||
<<h :: size(size)>> = bitstring
|
||||
integer_to_binary(h) <> "::size(" <> integer_to_binary(size) <> ")"
|
||||
end
|
||||
|
||||
defp decrement(:infinity), do: :infinity
|
||||
defp decrement(counter), do: counter - 1
|
||||
end
|
||||
|
||||
defimpl Inspect, for: List do
|
||||
@doc %B"""
|
||||
Represents a list, checking if it can be printed or not.
|
||||
If so, a single-quoted representation is returned,
|
||||
otherwise the brackets syntax is used. Keywords are
|
||||
printed in keywords syntax.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> inspect('bar')
|
||||
"'bar'"
|
||||
iex> inspect([0|'bar'])
|
||||
"[0, 98, 97, 114]"
|
||||
iex> inspect([:foo,:bar])
|
||||
"[:foo, :bar]"
|
||||
|
||||
"""
|
||||
|
||||
def inspect([], _opts), do: "[]"
|
||||
|
||||
def inspect(thing, Inspect.Opts[] = opts) do
|
||||
cond do
|
||||
:io_lib.printable_list(thing) ->
|
||||
<< ?', String.escape(:unicode.characters_to_binary(thing), ?') :: binary, ?' >>
|
||||
keyword?(thing) && not opts.raw ->
|
||||
surround_many("[", thing, "]", opts.limit, keyword(&1, opts))
|
||||
true ->
|
||||
surround_many("[", thing, "]", opts.limit, Kernel.inspect(&1, opts))
|
||||
end
|
||||
end
|
||||
|
||||
defp keyword({key, value}, opts) do
|
||||
concat(
|
||||
key_to_binary(key) <> ": ",
|
||||
Kernel.inspect(value, opts)
|
||||
)
|
||||
end
|
||||
|
||||
defp key_to_binary(key) do
|
||||
case Inspect.Atom.inspect(key) do
|
||||
":" <> right -> right
|
||||
other -> other
|
||||
end
|
||||
end
|
||||
|
||||
defp keyword?([{ key, _value } | rest]) when is_atom(key) do
|
||||
case atom_to_list(key) do
|
||||
'Elixir.' ++ _ -> false
|
||||
_ -> keyword?(rest)
|
||||
end
|
||||
end
|
||||
|
||||
defp keyword?([]), do: true
|
||||
defp keyword?(_other), do: false
|
||||
end
|
||||
|
||||
defimpl Inspect, for: Tuple do
|
||||
@doc """
|
||||
Represents tuples. If the tuple represents a record,
|
||||
it shows it nicely formatted using the access syntax.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> inspect({1, 2, 3})
|
||||
"{1, 2, 3}"
|
||||
iex> inspect(ArgumentError.new)
|
||||
"ArgumentError[message: \\\"argument error\\\"]"
|
||||
|
||||
"""
|
||||
|
||||
def inspect({}, _opts), do: "{}"
|
||||
|
||||
def inspect(tuple, opts) do
|
||||
unless opts.raw do
|
||||
record_inspect(tuple, opts)
|
||||
end || surround_many("{", tuple_to_list(tuple), "}", opts.limit, Kernel.inspect(&1, opts))
|
||||
end
|
||||
|
||||
## Helpers
|
||||
|
||||
defp record_inspect(record, opts) do
|
||||
[name|tail] = tuple_to_list(record)
|
||||
|
||||
if is_atom(name) && (fields = record_fields(name)) && (length(fields) == size(record) - 1) do
|
||||
if Enum.first(tail) == :__exception__ do
|
||||
surround_record(name, tl(fields), tl(tail), opts)
|
||||
else
|
||||
surround_record(name, fields, tail, opts)
|
||||
end
|
||||
end || surround_many("{", [name|tail], "}", opts.limit, Kernel.inspect(&1, opts))
|
||||
end
|
||||
|
||||
defp record_fields(name) do
|
||||
case atom_to_binary(name) do
|
||||
"Elixir." <> _ ->
|
||||
try do
|
||||
name.__record__(:fields)
|
||||
rescue
|
||||
_ -> nil
|
||||
end
|
||||
_ -> nil
|
||||
end
|
||||
end
|
||||
|
||||
defp surround_record(name, fields, tail, opts) do
|
||||
fields = lc { field, _ } inlist fields, do: field
|
||||
|
||||
concat(
|
||||
Inspect.Atom.inspect(name, opts),
|
||||
surround_many("[", Enum.zip(fields, tail), "]", opts.limit, keyword(&1, opts))
|
||||
)
|
||||
end
|
||||
|
||||
defp keyword({ k, v }, opts) do
|
||||
concat(
|
||||
atom_to_binary(k, :utf8) <> ": ",
|
||||
Kernel.inspect(v, opts)
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
defimpl Inspect, for: Number do
|
||||
@doc """
|
||||
Represents the number as a string.
|
||||
|
||||
Floats are represented using the shortened, correctly rounded string
|
||||
that converts to float when read back with `binary_to_float/1`. This
|
||||
is done via the Erlang implementation of _Printing Floating-Point
|
||||
Numbers Quickly and Accurately_ in Proceedings of the SIGPLAN '96
|
||||
Conference on Programming Language Design and Implementation.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> inspect(1)
|
||||
"1"
|
||||
|
||||
"""
|
||||
def inspect(thing, _opts) when is_integer(thing) do
|
||||
integer_to_binary(thing)
|
||||
end
|
||||
|
||||
def inspect(thing, _opts) do
|
||||
list_to_binary(:io_lib_format.fwrite_g(thing))
|
||||
end
|
||||
end
|
||||
|
||||
defimpl Inspect, for: Regex do
|
||||
@doc %B"""
|
||||
Represents the Regex using the `%r""` syntax.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> inspect(%r/foo/m)
|
||||
"%r\"foo\"m"
|
||||
|
||||
"""
|
||||
def inspect(regex, opts) when size(regex) == 5 do
|
||||
concat ["%r", Kernel.inspect(Regex.source(regex), opts), Regex.opts(regex)]
|
||||
end
|
||||
|
||||
def inspect(other, opts) do
|
||||
Kernel.inspect(other, opts.raw(true))
|
||||
end
|
||||
end
|
||||
|
||||
defimpl Inspect, for: Function do
|
||||
def inspect(function, _opts) do
|
||||
fun_info = :erlang.fun_info(function)
|
||||
if fun_info[:type] == :external and fun_info[:env] == [] do
|
||||
"function(#{Inspect.Atom.inspect(fun_info[:module])}.#{fun_info[:name]}/#{fun_info[:arity]})"
|
||||
else
|
||||
'#Fun' ++ rest = :erlang.fun_to_list(function)
|
||||
"#Function" <> list_to_binary(rest)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defimpl Inspect, for: PID do
|
||||
def inspect(pid, _opts) do
|
||||
"#PID" <> list_to_binary(pid_to_list(pid))
|
||||
end
|
||||
end
|
||||
|
||||
defimpl Inspect, for: Port do
|
||||
def inspect(port, _opts) do
|
||||
list_to_binary :erlang.port_to_list(port)
|
||||
end
|
||||
end
|
||||
|
||||
defimpl Inspect, for: Reference do
|
||||
def inspect(ref, _opts) do
|
||||
'#Ref' ++ rest = :erlang.ref_to_list(ref)
|
||||
"#Reference" <> list_to_binary(rest)
|
||||
end
|
||||
end
|
||||
|
||||
defimpl Inspect, for: HashDict do
|
||||
def inspect(dict, opts) do
|
||||
concat ["#HashDict<", Inspect.List.inspect(HashDict.to_list(dict), opts), ">"]
|
||||
end
|
||||
end
|
||||
|
||||
defimpl Inspect, for: HashSet do
|
||||
def inspect(set, opts) do
|
||||
concat ["#HashSet<", Inspect.List.inspect(HashSet.to_list(set), opts), ">"]
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,390 @@
|
||||
defmodule Inspect.Algebra do
|
||||
@moduledoc %B"""
|
||||
A set of functions for creating and manipulating algebra
|
||||
documents, as described in ["Strictly Pretty" (2000) by Christian Lindig][0].
|
||||
|
||||
An algebra document is represented by an `Inspect.Algebra` node
|
||||
or a regular string.
|
||||
|
||||
iex> Inspect.Algebra.empty
|
||||
:doc_nil
|
||||
iex> "foo"
|
||||
"foo"
|
||||
|
||||
With the functions in this module, we can concatenate different
|
||||
elements together and render them:
|
||||
|
||||
iex> doc = Inspect.Algebra.concat(Inspect.Algebra.empty, "foo")
|
||||
iex> Inspect.Algebra.pretty(doc, 80)
|
||||
"foo"
|
||||
|
||||
The functions `nest/2`, `space/2` and `line/2` help you put the
|
||||
document together into a rigid structure. However, the document
|
||||
algebra gets interesting when using functions like `break/2`, which
|
||||
converts the given string into a line break depending on how much space
|
||||
there is to print. Let's glue two docs together with a break and then
|
||||
render it:
|
||||
|
||||
iex> doc = Inspect.Algebra.glue("a", " ", "b")
|
||||
iex> Inspect.Algebra.pretty(doc, 80)
|
||||
"a b"
|
||||
|
||||
Notice the break was represented as is, because we haven't reached
|
||||
a line limit. Once we do, it is replaced by a newline:
|
||||
|
||||
iex> doc = Inspect.Algebra.glue(String.duplicate("a", 20), " ", "b")
|
||||
iex> Inspect.Algebra.pretty(doc, 10)
|
||||
"aaaaaaaaaaaaaaaaaaaa\nb"
|
||||
|
||||
Finally, this module also contains Elixir related functions, a bit
|
||||
tied to Elixir formatting, namely `surround/3` and `surround_many/5`.
|
||||
|
||||
## Implementation details
|
||||
|
||||
The original Haskell implementation of the algorithm by [Wadler][1]
|
||||
relies on lazy evaluation to unfold document groups on two alternatives:
|
||||
`:flat` (breaks as spaces) and `:break` (breaks as newlines).
|
||||
Implementing the same logic in a strict language such as Elixir leads
|
||||
to an exponential growth of possible documents, unless document groups
|
||||
are encoded explictly as `:flat` or `:break`. Those groups are then reduced
|
||||
to a simple document, where the layout is already decided, per [Lindig][0].
|
||||
|
||||
This implementation slightly changes the semantic of Lindig's algorithm
|
||||
to allow elements that belong to the same group to be printed together
|
||||
in the same line, even if they do not fit the line fully. This was achieved
|
||||
by changing `:break` to mean a possible break and `:flat` to force a flat
|
||||
structure. Then deciding if a break works as a newline is just a matter
|
||||
of checking if we have enough space until the next break that is not
|
||||
inside a group (which is still flat).
|
||||
|
||||
Custom pretty printers can be implemented using the documents returned
|
||||
by this module and by providing their own rendering functions.
|
||||
|
||||
[0]: http://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.34.2200
|
||||
[1]: http://homepages.inf.ed.ac.uk/wadler/papers/prettier/prettier.pdf
|
||||
|
||||
"""
|
||||
|
||||
@surround_separator ","
|
||||
@tail_separator " |"
|
||||
@newline "\n"
|
||||
@nesting 1
|
||||
@break " "
|
||||
|
||||
defp repeat(_, 0), do: ""
|
||||
defp repeat(s, i), do: :lists.duplicate(i, s)
|
||||
|
||||
# Functional interface to `doc` records
|
||||
|
||||
@type doc :: :doc_nil | :doc_cons_t | :doc_nest_t | :doc_break_t | :doc_group_t | binary
|
||||
defrecordp :doc_cons, [left: :doc_nil, right: :doc_nil]
|
||||
defrecordp :doc_nest, [indent: 1, doc: :doc_nil]
|
||||
defrecordp :doc_break, [str: " "]
|
||||
defrecordp :doc_group, [doc: :doc_nil]
|
||||
|
||||
@doc """
|
||||
Returns `:doc_nil` which is a document entity used to represent
|
||||
nothingness. Takes no arguments.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Inspect.Algebra.empty
|
||||
:doc_nil
|
||||
|
||||
"""
|
||||
@spec empty() :: :doc_nil
|
||||
def empty, do: :doc_nil
|
||||
|
||||
@doc """
|
||||
Concatenates two document entities. Takes two arguments:
|
||||
left doc and right doc. Returns a DocCons doc
|
||||
|
||||
## Examples
|
||||
|
||||
iex> doc = Inspect.Algebra.concat "Tasteless", "Artosis"
|
||||
iex> Inspect.Algebra.pretty(doc, 80)
|
||||
"TastelessArtosis"
|
||||
|
||||
"""
|
||||
@spec concat(doc, doc) :: :doc_cons_t
|
||||
def concat(x, y), do: doc_cons(left: x, right: y)
|
||||
|
||||
@doc """
|
||||
Concatenates a list of documents.
|
||||
"""
|
||||
@spec concat([doc]) :: :doc_cons_t
|
||||
def concat(docs), do: folddoc(docs, concat(&1, &2))
|
||||
|
||||
@doc """
|
||||
Nests document entity `x` positions deep. Nesting will be
|
||||
appended to the line breaks.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> doc = Inspect.Algebra.nest(Inspect.Algebra.concat(Inspect.Algebra.break, "6"), 5)
|
||||
iex> Inspect.Algebra.pretty(doc, 80)
|
||||
" 6"
|
||||
|
||||
"""
|
||||
@spec nest(doc, non_neg_integer) :: :doc_nest_t
|
||||
def nest(x, 0), do: x
|
||||
def nest(x, i) when is_integer(i), do: doc_nest(indent: i, doc: x)
|
||||
|
||||
@doc %B"""
|
||||
Document entity representing a break. This break can
|
||||
be rendered as a linebreak or as spaces, depending on the
|
||||
`mode` of the chosen layout or the provided separator.
|
||||
|
||||
## Examples
|
||||
|
||||
Let's glue two docs together with a break and then render it:
|
||||
|
||||
iex> doc = Inspect.Algebra.glue("a", " ", "b")
|
||||
iex> Inspect.Algebra.pretty(doc, 80)
|
||||
"a b"
|
||||
|
||||
Notice the break was represented as is, because we haven't reached
|
||||
a line limit. Once we do, it is replaced by a newline:
|
||||
|
||||
iex> doc = Inspect.Algebra.glue(String.duplicate("a", 20), " ", "b")
|
||||
iex> Inspect.Algebra.pretty(doc, 10)
|
||||
"aaaaaaaaaaaaaaaaaaaa\nb"
|
||||
|
||||
"""
|
||||
@spec break(binary) :: :doc_break_t
|
||||
def break(s) when is_binary(s), do: doc_break(str: s)
|
||||
|
||||
@spec break() :: :doc_break_t
|
||||
def break(), do: doc_break(str: @break)
|
||||
|
||||
@doc """
|
||||
Inserts a break between two docs. See `break/1` for more info.
|
||||
"""
|
||||
@spec glue(doc, doc) :: :doc_cons_t
|
||||
def glue(x, y), do: concat(x, concat(break, y))
|
||||
|
||||
@doc """
|
||||
Inserts a break, passed as the second argument, between two docs,
|
||||
the first and the third arguments.
|
||||
"""
|
||||
@spec glue(doc, binary, doc) :: :doc_cons_t
|
||||
def glue(x, g, y) when is_binary(g), do: concat(x, concat(break(g), y))
|
||||
|
||||
@doc %B"""
|
||||
Returns a group containing the specified document.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> doc = Inspect.Algebra.group(
|
||||
...> Inspect.Algebra.concat(
|
||||
...> Inspect.Algebra.group(
|
||||
...> Inspect.Algebra.concat(
|
||||
...> "Hello,",
|
||||
...> Inspect.Algebra.concat(
|
||||
...> Inspect.Algebra.break,
|
||||
...> "A"
|
||||
...> )
|
||||
...> )
|
||||
...> ),
|
||||
...> Inspect.Algebra.concat(
|
||||
...> Inspect.Algebra.break,
|
||||
...> "B"
|
||||
...> )
|
||||
...> ))
|
||||
iex> Inspect.Algebra.pretty(doc, 80)
|
||||
"Hello, A B"
|
||||
iex> Inspect.Algebra.pretty(doc, 6)
|
||||
"Hello,\nA B"
|
||||
|
||||
"""
|
||||
@spec group(doc) :: :doc_group_t
|
||||
def group(d), do: doc_group(doc: d)
|
||||
|
||||
@doc """
|
||||
Inserts a mandatory single space between two document entities.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> doc = Inspect.Algebra.space "Hughes", "Wadler"
|
||||
iex> Inspect.Algebra.pretty(doc, 80)
|
||||
"Hughes Wadler"
|
||||
|
||||
"""
|
||||
@spec space(doc, doc) :: :doc_cons_t
|
||||
def space(x, y), do: concat(x, concat(" ", y))
|
||||
|
||||
@doc %B"""
|
||||
Inserts a mandatory linebreak between two document entities.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> doc = Inspect.Algebra.line "Hughes", "Wadler"
|
||||
iex> Inspect.Algebra.pretty(doc, 80)
|
||||
"Hughes\nWadler"
|
||||
|
||||
"""
|
||||
@spec line(doc, doc) :: :doc_cons_t
|
||||
def line(x, y), do: concat(x, concat(@newline, y))
|
||||
|
||||
@doc """
|
||||
Folds a list of document entities into a document entity
|
||||
using a function that is passed as the first argument.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> doc = ["A", "B"]
|
||||
iex> doc = Inspect.Algebra.folddoc(doc, fn(x,y) ->
|
||||
...> Inspect.Algebra.concat [x, "!", y]
|
||||
...> end)
|
||||
iex> Inspect.Algebra.pretty(doc, 80)
|
||||
"A!B"
|
||||
|
||||
"""
|
||||
@spec folddoc([any], ((doc, [doc]) -> doc)) :: doc
|
||||
def folddoc([], _), do: empty
|
||||
def folddoc([doc], _), do: doc
|
||||
def folddoc([d|ds], f), do: f.(d, folddoc(ds, f))
|
||||
|
||||
# Elixir conveniences
|
||||
|
||||
@doc %B"""
|
||||
Surrounds a document with characters.
|
||||
|
||||
Puts the document between left and right enclosing and nesting it.
|
||||
The document is marked as a group, to show the maximum as possible
|
||||
concisely together.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> doc = Inspect.Algebra.surround "[", Inspect.Algebra.glue("a", "b"), "]"
|
||||
iex> Inspect.Algebra.pretty(doc, 3)
|
||||
"[a\n b]"
|
||||
|
||||
"""
|
||||
@spec surround(binary, doc, binary) :: doc
|
||||
def surround(left, doc, right) do
|
||||
group concat [left, nest(doc, @nesting), right]
|
||||
end
|
||||
|
||||
@doc %B"""
|
||||
Maps and glues a collection of items together using the given separator
|
||||
and surrounds them. A limit can be passed which, once reached, stops
|
||||
gluing and outputs "..." instead.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> doc = Inspect.Algebra.surround_many("[", Enum.to_list(1..5), "]", :infinity, integer_to_binary(&1))
|
||||
iex> Inspect.Algebra.pretty(doc, 5)
|
||||
"[1,\n 2,\n 3,\n 4,\n 5]"
|
||||
|
||||
iex> doc = Inspect.Algebra.surround_many("[", Enum.to_list(1..5), "]", 3, integer_to_binary(&1))
|
||||
iex> Inspect.Algebra.pretty(doc, 20)
|
||||
"[1, 2, 3, ...]"
|
||||
|
||||
"""
|
||||
@spec surround_many(binary, [any], binary, integer | :infinity, (term -> doc)) :: doc
|
||||
def surround_many(left, [], right, _, _fun) do
|
||||
concat(left, right)
|
||||
end
|
||||
|
||||
def surround_many(left, docs, right, limit, fun) do
|
||||
surround(left, surround_many(docs, limit, fun), right)
|
||||
end
|
||||
|
||||
defp surround_many(_, 0, _fun) do
|
||||
"..."
|
||||
end
|
||||
|
||||
defp surround_many([h], _limit, fun) do
|
||||
fun.(h)
|
||||
end
|
||||
|
||||
defp surround_many([h|t], limit, fun) when is_list(t) do
|
||||
glue(
|
||||
concat(fun.(h), @surround_separator),
|
||||
surround_many(t, decrement(limit), fun)
|
||||
)
|
||||
end
|
||||
|
||||
defp surround_many([h|t], _limit, fun) do
|
||||
glue(
|
||||
concat(fun.(h), @tail_separator),
|
||||
fun.(t)
|
||||
)
|
||||
end
|
||||
|
||||
defp decrement(:infinity), do: :infinity
|
||||
defp decrement(counter), do: counter - 1
|
||||
|
||||
@doc """
|
||||
The pretty printing function.
|
||||
|
||||
Takes the maximum width and a document to print as its arguments
|
||||
and returns the string representation of the best layout for the
|
||||
document to fit in the given width.
|
||||
"""
|
||||
@spec pretty(doc, non_neg_integer) :: binary
|
||||
def pretty(d, w) do
|
||||
sdoc = format w, 0, [{0, default_mode(w), doc_group(doc: d)}]
|
||||
render(sdoc)
|
||||
end
|
||||
|
||||
defp default_mode(:infinity), do: :flat
|
||||
defp default_mode(_), do: :break
|
||||
|
||||
# Rendering and internal helpers
|
||||
|
||||
# Records representing __simple__ documents, already on a fixed layout
|
||||
# Those are generalized by `sdoc` type.
|
||||
@type sdoc :: :s_nil | :s_text_t | :s_line_t
|
||||
defrecordp :s_text, [str: "", sdoc: :s_nil]
|
||||
defrecordp :s_line, [indent: 1, sdoc: :s_nil] # newline + spaces
|
||||
|
||||
# Record representing the document mode to be rendered: __flat__ or __broken__
|
||||
@type mode :: :flat | :break
|
||||
|
||||
@doc false
|
||||
@spec fits?(integer, [{ integer, mode, doc }]) :: boolean
|
||||
def fits?(:infinity, _), do: true # no pretty printing
|
||||
def fits?(w, _) when w < 0, do: false
|
||||
def fits?(_, []), do: true
|
||||
def fits?(w, [{_, _, :doc_nil} | t]), do: fits?(w, t)
|
||||
def fits?(w, [{i, m, doc_cons(left: x, right: y)} | t]), do: fits?(w, [{i, m, x} | [{i, m, y} | t]])
|
||||
def fits?(w, [{i, m, doc_nest(indent: j, doc: x)} | t]), do: fits?(w, [{i + j, m, x} | t])
|
||||
def fits?(w, [{_, _, s} | t]) when is_binary(s), do: fits?((w - byte_size s), t)
|
||||
def fits?(w, [{_, :flat, doc_break(str: s)} | t]), do: fits?((w - byte_size s), t)
|
||||
def fits?(_, [{_, :break, doc_break(str: _)} | _]), do: true
|
||||
def fits?(w, [{i, _, doc_group(doc: x)} | t]), do: fits?(w, [{i, :flat, x} | t])
|
||||
|
||||
@doc false
|
||||
@spec format(integer, integer, [{ integer, mode, doc }]) :: atom | tuple
|
||||
def format(_, _, []), do: :s_nil
|
||||
def format(w, k, [{_, _, :doc_nil} | t]), do: format(w, k, t)
|
||||
def format(w, k, [{i, m, doc_cons(left: x, right: y)} | t]), do: format(w, k, [{i, m, x} | [{i, m, y} | t]])
|
||||
def format(w, k, [{i, m, doc_nest(indent: j, doc: x)} | t]), do: format(w, k, [{i + j, m, x} | t])
|
||||
def format(w, k, [{_, _, s} | t]) when is_binary(s), do: s_text(str: s, sdoc: format(w, (k + byte_size s), t))
|
||||
def format(w, k, [{i, m, doc_group(doc: x)} | t]), do: format(w, k, [{i, m, x} | t])
|
||||
def format(w, k, [{_, :flat, doc_break(str: s)} | t]), do: s_text(str: s, sdoc: format(w, (k + byte_size s), t))
|
||||
def format(w, k, [{i, :break, doc_break(str: s)} | t]) do
|
||||
k = k + byte_size(s)
|
||||
if fits?(w - k, t) do
|
||||
s_text(str: s, sdoc: format(w, k, t))
|
||||
else
|
||||
s_line(indent: i, sdoc: format(w, i, t))
|
||||
end
|
||||
end
|
||||
|
||||
@doc false
|
||||
@spec render(sdoc) :: binary
|
||||
def render(sdoc) do
|
||||
iolist_to_binary do_render sdoc
|
||||
end
|
||||
|
||||
@spec do_render(sdoc) :: [binary]
|
||||
defp do_render(:s_nil), do: [""]
|
||||
defp do_render(s_text(str: s, sdoc: d)), do: [s | do_render(d)]
|
||||
defp do_render(s_line(indent: i, sdoc: d)) do
|
||||
prefix = repeat " ", i
|
||||
[@newline | [prefix | do_render d]]
|
||||
end
|
||||
end
|
||||
+99
-27
@@ -26,7 +26,8 @@ defmodule IO do
|
||||
import :erlang, only: [group_leader: 0]
|
||||
|
||||
@doc """
|
||||
Reads `count` bytes from the IO device. It returns:
|
||||
Reads `count` characters from the IO device or until
|
||||
the end of the line if `:line` is given. It returns:
|
||||
|
||||
* `data` - The input characters.
|
||||
|
||||
@@ -36,48 +37,53 @@ defmodule IO do
|
||||
for instance {:error, :estale} if reading from an
|
||||
NFS file system.
|
||||
"""
|
||||
def read(device // group_leader(), count) do
|
||||
def read(device // group_leader, chars_or_line)
|
||||
|
||||
def read(device, :line) do
|
||||
:io.get_line(map_dev(device), "")
|
||||
end
|
||||
|
||||
def read(device, count) when count >= 0 do
|
||||
:io.get_chars(map_dev(device), "", count)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Reads `count` bytes from the IO device as binary,
|
||||
no unicode conversion happens.
|
||||
Reads `count` bytes from the IO device or until
|
||||
the end of the line if `:line` is given. It returns:
|
||||
|
||||
Check `read/2` for more information.
|
||||
* `data` - The input characters.
|
||||
|
||||
* :eof - End of file was encountered.
|
||||
|
||||
* {:error, reason} - Other (rare) error condition,
|
||||
for instance {:error, :estale} if reading from an
|
||||
NFS file system.
|
||||
"""
|
||||
def binread(device // group_leader(), count) do
|
||||
def binread(device // group_leader, chars_or_line)
|
||||
|
||||
def binread(device, :line) do
|
||||
case :file.read_line(map_dev(device)) do
|
||||
{ :ok, data } -> data
|
||||
other -> other
|
||||
end
|
||||
end
|
||||
|
||||
def binread(device, count) when count >= 0 do
|
||||
case :file.read(map_dev(device), count) do
|
||||
{ :ok, data } -> data
|
||||
other -> other
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Reads a line from the IO device. It returns:
|
||||
|
||||
* `data` - The input characters.
|
||||
|
||||
* :eof - End of file was encountered.
|
||||
|
||||
* {:error, reason} - Other (rare) error condition,
|
||||
for instance {:error, :estale} if reading from an
|
||||
NFS file system.
|
||||
|
||||
This function does the same as `gets/2`,
|
||||
except the prompt is not required as argument.
|
||||
"""
|
||||
@doc false
|
||||
def readline(device // group_leader()) do
|
||||
IO.write "[WARNING] IO.readline(device) is deprecated, please use IO.read(device, :line) instead\n#{Exception.format_stacktrace}"
|
||||
:io.get_line(map_dev(device), "")
|
||||
end
|
||||
|
||||
@doc """
|
||||
Reads a line from the IO device as binary,
|
||||
no unicode conversion happens.
|
||||
|
||||
Check `readline/1` for more information.
|
||||
"""
|
||||
@doc false
|
||||
def binreadline(device // group_leader()) do
|
||||
IO.write "[WARNING] IO.binreadline(device) is deprecated, please use IO.binread(device, :line) instead\n#{Exception.format_stacktrace}"
|
||||
case :file.read_line(map_dev(device)) do
|
||||
{ :ok, data } -> data
|
||||
other -> other
|
||||
@@ -129,6 +135,10 @@ defmodule IO do
|
||||
Inspects and writes the given argument to the device
|
||||
followed by a new line. A set of options can be given.
|
||||
|
||||
It sets by default pretty printing to true and the
|
||||
width to be the width of the device, with a minimum
|
||||
of 80 characters.
|
||||
|
||||
## Examples
|
||||
|
||||
IO.inspect Process.list
|
||||
@@ -141,7 +151,16 @@ defmodule IO do
|
||||
@doc """
|
||||
Inspects the item with options using the given device.
|
||||
"""
|
||||
def inspect(device, item, opts) do
|
||||
def inspect(device, item, opts) when is_list(opts) do
|
||||
opts = Keyword.put_new(opts, :pretty, true)
|
||||
|
||||
unless Keyword.get(opts, :width) do
|
||||
opts = case :io.columns(device) do
|
||||
{ :ok, width } -> Keyword.put(opts, :width, width)
|
||||
{ :error, _ } -> opts
|
||||
end
|
||||
end
|
||||
|
||||
puts device, Kernel.inspect(item, opts)
|
||||
item
|
||||
end
|
||||
@@ -196,6 +215,59 @@ defmodule IO do
|
||||
:io.get_line(map_dev(device), to_iodata(prompt))
|
||||
end
|
||||
|
||||
@doc """
|
||||
Converts the io device into a Stream. The device is
|
||||
iterated line by line.
|
||||
|
||||
This reads the io as utf-8. Check out
|
||||
`IO.binstream/1` to handle the IO as a raw binary.
|
||||
|
||||
## Examples
|
||||
|
||||
Here is an example on how we mimic an echo server
|
||||
from the command line:
|
||||
|
||||
Enum.each IO.stream(:stdio), IO.write(&1)
|
||||
|
||||
"""
|
||||
def stream(device) do
|
||||
stream(map_dev(device), &1, &2)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Converts the io device into a Stream. The device is
|
||||
iterated line by line.
|
||||
|
||||
This reads the io as a raw binary.
|
||||
"""
|
||||
def binstream(device) do
|
||||
binstream(map_dev(device), &1, &2)
|
||||
end
|
||||
|
||||
@doc false
|
||||
def stream(device, acc, fun) do
|
||||
case read(device, :line) do
|
||||
:eof ->
|
||||
acc
|
||||
{ :error, reason } ->
|
||||
raise File.IteratorError, reason: reason
|
||||
data ->
|
||||
stream(device, fun.(data, acc), fun)
|
||||
end
|
||||
end
|
||||
|
||||
@doc false
|
||||
def binstream(device, acc, fun) do
|
||||
case binread(device, :line) do
|
||||
:eof ->
|
||||
acc
|
||||
{ :error, reason } ->
|
||||
raise File.IteratorError, reason: reason
|
||||
data ->
|
||||
binstream(device, fun.(data, acc), fun)
|
||||
end
|
||||
end
|
||||
|
||||
# Map the Elixir names for standard io and error to Erlang names
|
||||
defp map_dev(:stdio), do: :standard_io
|
||||
defp map_dev(:stderr), do: :standard_error
|
||||
|
||||
+11
-15
@@ -2,18 +2,14 @@ defmodule IO.ANSI.Sequence do
|
||||
@moduledoc false
|
||||
|
||||
defmacro defsequence(name, code) do
|
||||
quote do
|
||||
name = unquote(name)
|
||||
code = unquote(code)
|
||||
quote bind_quoted: [name: name, code: code] do
|
||||
def unquote(name)() do
|
||||
"\e[#{unquote(code)}m"
|
||||
end
|
||||
|
||||
def name, [], [], do:
|
||||
quote do: "\e[#{unquote(code)}m"
|
||||
|
||||
args =
|
||||
quote do: [<< unquote(atom_to_binary(name)), rest :: binary >> ]
|
||||
|
||||
defp :escape_sequence, args, [], do:
|
||||
quote do: { "\e[#{unquote(code)}m", rest }
|
||||
defp escape_sequence(<< unquote(atom_to_binary(name)), rest :: binary >>) do
|
||||
{ "\e[#{unquote(code)}m", rest }
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -76,7 +72,7 @@ defmodule IO.ANSI do
|
||||
@doc "Sets primary (default) font"
|
||||
defsequence :primary_font, 10
|
||||
|
||||
lc font_n inlist [1,2,3,4,5,6,7,8,9] do
|
||||
lc font_n inlist [1, 2, 3, 4, 5, 6, 7, 8, 9] do
|
||||
@doc "Sets alternative font #{font_n}"
|
||||
defsequence :"font_#{font_n}", font_n + 10
|
||||
end
|
||||
@@ -151,7 +147,7 @@ defmodule IO.ANSI do
|
||||
|
||||
## Example
|
||||
|
||||
iex> IO.ANSI.escape("Hello %{red,bright,green}yes")
|
||||
iex> IO.ANSI.escape("Hello %{red,bright,green}yes", true)
|
||||
"Hello \e[31m\e[1m\e[32myes\e[0m"
|
||||
"""
|
||||
@spec escape(String.t, emit :: boolean) :: String.t
|
||||
@@ -177,9 +173,9 @@ defmodule IO.ANSI do
|
||||
|
||||
## Example
|
||||
|
||||
iex> IO.ANSI.escape_fragment("Hello %{red,bright,green}yes")
|
||||
iex> IO.ANSI.escape_fragment("Hello %{red,bright,green}yes", true)
|
||||
"Hello \e[31m\e[1m\e[32myes"
|
||||
iex> IO.ANSI.escape_fragment("%{reset}bye")
|
||||
iex> IO.ANSI.escape_fragment("%{reset}bye", true)
|
||||
"\e[0mbye"
|
||||
|
||||
"""
|
||||
|
||||
+644
-320
File diff suppressed because it is too large
Load Diff
@@ -107,37 +107,37 @@ defmodule Kernel.CLI do
|
||||
System.halt 0
|
||||
end
|
||||
|
||||
defp process_shared(["-pa",h|t], config) do
|
||||
defp process_shared(["-pa", h|t], config) do
|
||||
Enum.each Path.wildcard(Path.expand(h)), Code.prepend_path(&1)
|
||||
process_shared t, config
|
||||
end
|
||||
|
||||
defp process_shared(["-pz",h|t], config) do
|
||||
defp process_shared(["-pz", h|t], config) do
|
||||
Enum.each Path.wildcard(Path.expand(h)), Code.append_path(&1)
|
||||
process_shared t, config
|
||||
end
|
||||
|
||||
defp process_shared(["--app",h|t], config) do
|
||||
process_shared t, config.update_commands [{:app,h}|&1]
|
||||
defp process_shared(["--app", h|t], config) do
|
||||
process_shared t, config.update_commands [{:app, h}|&1]
|
||||
end
|
||||
|
||||
defp process_shared(["--no-halt"|t], config) do
|
||||
process_shared t, config.halt(false)
|
||||
end
|
||||
|
||||
defp process_shared(["-e",h|t], config) do
|
||||
process_shared t, config.update_commands [{:eval,h}|&1]
|
||||
defp process_shared(["-e", h|t], config) do
|
||||
process_shared t, config.update_commands [{:eval, h}|&1]
|
||||
end
|
||||
|
||||
defp process_shared(["-r",h|t], config) do
|
||||
process_shared t, config.update_commands [{:require,h}|&1]
|
||||
defp process_shared(["-r", h|t], config) do
|
||||
process_shared t, config.update_commands [{:require, h}|&1]
|
||||
end
|
||||
|
||||
defp process_shared(["-pr",h|t], config) do
|
||||
process_shared t, config.update_commands [{:parallel_require,h}|&1]
|
||||
defp process_shared(["-pr", h|t], config) do
|
||||
process_shared t, config.update_commands [{:parallel_require, h}|&1]
|
||||
end
|
||||
|
||||
defp process_shared([erl,_|t], config) when erl in ["--erl", "--sname", "--name", "--cookie"] do
|
||||
defp process_shared([erl, _|t], config) when erl in ["--erl", "--sname", "--name", "--cookie"] do
|
||||
process_shared t, config
|
||||
end
|
||||
|
||||
@@ -159,8 +159,8 @@ defmodule Kernel.CLI do
|
||||
process_iex t, config
|
||||
end
|
||||
|
||||
defp process_argv(["-S",h|t], config) do
|
||||
{ config.update_commands([{:script,h}|&1]), t }
|
||||
defp process_argv(["-S", h|t], config) do
|
||||
{ config.update_commands([{:script, h}|&1]), t }
|
||||
end
|
||||
|
||||
defp process_argv([h|t] = list, config) do
|
||||
@@ -168,7 +168,7 @@ defmodule Kernel.CLI do
|
||||
"-" <> _ ->
|
||||
shared_option? list, config, process_argv(&1, &2)
|
||||
_ ->
|
||||
{ config.update_commands([{:file,h}|&1]), t }
|
||||
{ config.update_commands([{:file, h}|&1]), t }
|
||||
end
|
||||
end
|
||||
|
||||
@@ -182,20 +182,24 @@ defmodule Kernel.CLI do
|
||||
{ config, t }
|
||||
end
|
||||
|
||||
defp process_compiler(["-o",h|t], config) do
|
||||
defp process_compiler(["-o", h|t], config) do
|
||||
process_compiler t, config.output(h)
|
||||
end
|
||||
|
||||
defp process_compiler(["--no-docs"|t], config) do
|
||||
process_compiler t, config.update_compiler_options([{:docs,false}|&1])
|
||||
process_compiler t, config.update_compiler_options([{:docs, false}|&1])
|
||||
end
|
||||
|
||||
defp process_compiler(["--no-debug-info"|t], config) do
|
||||
process_compiler t, config.update_compiler_options([{:debug_info,false}|&1])
|
||||
process_compiler t, config.update_compiler_options([{:debug_info, false}|&1])
|
||||
end
|
||||
|
||||
defp process_compiler(["--ignore-module-conflict"|t], config) do
|
||||
process_compiler t, config.update_compiler_options([{:ignore_module_conflict,true}|&1])
|
||||
process_compiler t, config.update_compiler_options([{:ignore_module_conflict, true}|&1])
|
||||
end
|
||||
|
||||
defp process_compiler(["--warnings-as-errors"|t], config) do
|
||||
process_compiler t, config.update_compiler_options([{:warnings_as_errors, true}|&1])
|
||||
end
|
||||
|
||||
defp process_compiler([h|t] = list, config) do
|
||||
@@ -209,7 +213,7 @@ defmodule Kernel.CLI do
|
||||
end
|
||||
|
||||
defp process_compiler([], config) do
|
||||
{ config.update_commands([{:compile,config.compile}|&1]), [] }
|
||||
{ config.update_commands([{:compile, config.compile}|&1]), [] }
|
||||
end
|
||||
|
||||
# Process iex options
|
||||
@@ -220,16 +224,16 @@ defmodule Kernel.CLI do
|
||||
|
||||
# This clause is here so that Kernel.CLI does not error out with "unknown
|
||||
# option"
|
||||
defp process_iex(["--dot-iex",_|t], config) do
|
||||
defp process_iex(["--dot-iex", _|t], config) do
|
||||
process_iex t, config
|
||||
end
|
||||
|
||||
defp process_iex([opt,_|t], config) when opt in ["--remsh"] do
|
||||
defp process_iex([opt, _|t], config) when opt in ["--remsh"] do
|
||||
process_iex t, config
|
||||
end
|
||||
|
||||
defp process_iex(["-S",h|t], config) do
|
||||
{ config.update_commands([{:script,h}|&1]), t }
|
||||
defp process_iex(["-S", h|t], config) do
|
||||
{ config.update_commands([{:script, h}|&1]), t }
|
||||
end
|
||||
|
||||
defp process_iex([h|t] = list, config) do
|
||||
@@ -237,7 +241,7 @@ defmodule Kernel.CLI do
|
||||
"-" <> _ ->
|
||||
shared_option? list, config, process_iex(&1, &2)
|
||||
_ ->
|
||||
{ config.update_commands([{:file,h}|&1]), t }
|
||||
{ config.update_commands([{:file, h}|&1]), t }
|
||||
end
|
||||
end
|
||||
|
||||
@@ -247,7 +251,7 @@ defmodule Kernel.CLI do
|
||||
|
||||
# Process commands
|
||||
|
||||
defp process_command({:cookie,h}, _config) do
|
||||
defp process_command({:cookie, h}, _config) do
|
||||
if Node.alive? do
|
||||
Node.set_cookie(binary_to_atom(h))
|
||||
:ok
|
||||
@@ -325,7 +329,6 @@ defmodule Kernel.CLI do
|
||||
Code.compiler_options(config.compiler_options)
|
||||
Kernel.ParallelCompiler.files_to_path(files, config.output,
|
||||
fn file -> IO.puts "Compiled #{file}" end)
|
||||
:ok
|
||||
else
|
||||
{ :error, "--compile : No files matched patterns #{Enum.join(patterns, ",")}" }
|
||||
end
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
defexception Kernel.CompilationError, message: "compilation failed"
|
||||
|
||||
defmodule Kernel.ParallelCompiler do
|
||||
alias :orddict, as: OrdDict
|
||||
|
||||
@@ -15,6 +17,10 @@ defmodule Kernel.ParallelCompiler do
|
||||
the current file stops being compiled until the dependency is
|
||||
resolved.
|
||||
|
||||
If there is any error during compilation or if warnings_as_errors
|
||||
is set to true and there is a warning, this function will fail
|
||||
with an exception.
|
||||
|
||||
A callback that is invoked every time a file is compiled
|
||||
with its name can be optionally given as argument.
|
||||
"""
|
||||
@@ -32,8 +38,14 @@ defmodule Kernel.ParallelCompiler do
|
||||
|
||||
defp spawn_compilers(files, path, callback) do
|
||||
Code.ensure_loaded(Kernel.ErrorHandler)
|
||||
:elixir_code_server.cast(:reset_warnings)
|
||||
schedulers = max(:erlang.system_info(:schedulers_online), 2)
|
||||
spawn_compilers(files, path, callback, [], [], schedulers, [])
|
||||
|
||||
result = spawn_compilers(files, path, callback, [], [], schedulers, [])
|
||||
case :elixir_code_server.call(:compilation_status) do
|
||||
:ok -> result
|
||||
:error -> raise Kernel.CompilationError, [], []
|
||||
end
|
||||
end
|
||||
|
||||
# We already have 4 currently running, don't spawn new ones
|
||||
@@ -64,7 +76,7 @@ defmodule Kernel.ParallelCompiler do
|
||||
end
|
||||
end
|
||||
|
||||
spawn_compilers(t, output, callback, waiting, [{child,h}|queued], schedulers, result)
|
||||
spawn_compilers(t, output, callback, waiting, [{child, h}|queued], schedulers, result)
|
||||
end
|
||||
|
||||
# No more files, nothing waiting, queue is empty, we are done
|
||||
@@ -138,4 +150,4 @@ defmodule Kernel.ParallelCompiler do
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -119,7 +119,16 @@ defmodule Kernel.RecordRewriter do
|
||||
|
||||
## Expr
|
||||
|
||||
defp optimize_expr({ :call, call_line, { :remote, line, left, right }, args }, module, dict) do
|
||||
defp optimize_expr({ :call, call_line,
|
||||
{ :remote, _, { :atom, _, :erlang }, { :atom, _, :setelement } } = remote,
|
||||
[{ :integer, _, pos } = integer, tuple, value] }, module, dict) when pos > 1 do
|
||||
|
||||
{ tuple, dict, res } = optimize_expr(tuple, module, dict)
|
||||
{ value, dict, _ } = optimize_expr(value, module, dict)
|
||||
{ { :call, call_line, remote, [integer, tuple, value] }, dict, res }
|
||||
end
|
||||
|
||||
defp optimize_expr({ :call, call_line, { :remote, line, left, right }, args } = call, module, dict) do
|
||||
{ left, dict, res } = optimize_expr(left, module, dict)
|
||||
{ right, dict, _ } = optimize_expr(right, module, dict)
|
||||
{ args, dict } = optimize_args(args, module, dict)
|
||||
|
||||
@@ -247,7 +247,7 @@ defmodule Kernel.SpecialForms do
|
||||
for example:
|
||||
|
||||
iex> import List
|
||||
...> flatten([1,[2],3])
|
||||
...> flatten([1, [2], 3])
|
||||
[1,2,3]
|
||||
|
||||
## Selector
|
||||
@@ -387,7 +387,9 @@ defmodule Kernel.SpecialForms do
|
||||
* `:location` - When set to `:keep`, keeps the current line and file on quotes.
|
||||
Read the Stacktrace information section below for more information;
|
||||
* `:hygiene` - Allows a developer to disable hygiene selectively;
|
||||
* `:context` - Sets the context resolution happens at;
|
||||
* `:context` - Sets the resolution context;
|
||||
* `:binding` - Passes a binding to the macro. Whenever a binding is given,
|
||||
unquote is automatically disabled;
|
||||
|
||||
## Macro literals
|
||||
|
||||
@@ -397,7 +399,7 @@ defmodule Kernel.SpecialForms do
|
||||
:sum #=> Atoms
|
||||
1 #=> Integers
|
||||
2.0 #=> Floats
|
||||
[1,2] #=> Lists
|
||||
[1, 2] #=> Lists
|
||||
"binaries" #=> Binaries
|
||||
{key, value} #=> Tuple with two elements
|
||||
|
||||
@@ -629,6 +631,79 @@ defmodule Kernel.SpecialForms do
|
||||
code as if it was defined inside `GenServer.Behaviour` file, in
|
||||
particular, the macro `__FILE__` and exceptions happening inside
|
||||
the quote will always point to `GenServer.Behaviour` file.
|
||||
|
||||
## Binding and unquote fragments
|
||||
|
||||
Elixir quote/unquote mechanisms provides a functionality called
|
||||
unquote fragments. Unquote fragments provide an easy to generate
|
||||
functions on the fly. Consider this example:
|
||||
|
||||
kv = [foo: 1, bar: 2]
|
||||
Enum.each kv, fn { k, v } ->
|
||||
def unquote(k)(), do: unquote(v)
|
||||
end
|
||||
|
||||
In the example above, we have generated the function `foo/0` and
|
||||
`bar/0` dynamically. Now, imagine that, we want to convert this
|
||||
functionality into a macro:
|
||||
|
||||
defmacro defkv(kv) do
|
||||
Enum.each kv, fn { k, v } ->
|
||||
quote do
|
||||
def unquote(k)(), do: unquote(v)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
We can invoke this macro as:
|
||||
|
||||
defkv [foo: 1, bar: 2]
|
||||
|
||||
However, we can't invoke it as follows:
|
||||
|
||||
kv = [foo: 1, bar: 2]
|
||||
defkv kv
|
||||
|
||||
This is because the macro is expecting its arguments to be a
|
||||
key-value at **compilation** time. Since in the example above
|
||||
we are passing the representation of the variable `kv`, our
|
||||
code fails.
|
||||
|
||||
This is actually a common pitfall when developing macros. In
|
||||
practive, we want to avoid doing work at compilation time as
|
||||
much as we can. That said, let's attempt to improve our macro:
|
||||
|
||||
defmacro defkv(kv) do
|
||||
quote do
|
||||
Enum.each unquote(kv), fn { k, v } ->
|
||||
def unquote(k)(), do: unquote(v)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
If you try to run our new macro, you will notice it won't
|
||||
even compile, complaining that the variables `k` and `v`
|
||||
do not exist. This is because of the ambiguity: `unquote(k)`
|
||||
can either be an unquote fragment, as previously, or a regular
|
||||
unquote as in `unquote(kv)`.
|
||||
|
||||
One solution for this problem is to disable unquoting in the
|
||||
macro, however, doing that would make it impossible to inject
|
||||
`kv` representation into the tree. That's when the `:bind_quoted`
|
||||
option comes to the rescue. By using `:bind_quoted`, we can
|
||||
automatically disable unquoting while still injecting the
|
||||
desired variables into the tree:
|
||||
|
||||
defmacro defkv(kv) do
|
||||
quote bind_quoted: [kv: kv] do
|
||||
Enum.each kv, fn { k, v } ->
|
||||
def unquote(k)(), do: unquote(v)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
In fact, the `:binding` option is recommended every time one
|
||||
desires to inject a value into the quote.
|
||||
"""
|
||||
defmacro quote(opts, block)
|
||||
|
||||
@@ -643,6 +718,10 @@ defmodule Kernel.SpecialForms do
|
||||
|
||||
@doc """
|
||||
Defines a variable in the given context.
|
||||
|
||||
If the context is false, it is not stored in any particular
|
||||
context and the variable is not shared in between clauses.
|
||||
|
||||
Check `quote/2` for more information.
|
||||
"""
|
||||
defmacro var!(var, context)
|
||||
@@ -686,7 +765,7 @@ defmodule Kernel.SpecialForms do
|
||||
|
||||
## Examples
|
||||
|
||||
values = [2,3,4]
|
||||
values = [2, 3, 4]
|
||||
quote do: sum(1, unquote_splicing(values), 5)
|
||||
#=> { :sum, [], [1, 2, 3, 4, 5] }
|
||||
|
||||
@@ -697,7 +776,7 @@ defmodule Kernel.SpecialForms do
|
||||
@doc """
|
||||
List comprehensions allow you to quickly build a list from another list:
|
||||
|
||||
iex> lc n inlist [1,2,3,4], do: n * 2
|
||||
iex> lc n inlist [1, 2, 3, 4], do: n * 2
|
||||
[2,4,6,8]
|
||||
|
||||
A comprehension accepts many generators and also filters. Generators
|
||||
@@ -705,35 +784,48 @@ defmodule Kernel.SpecialForms do
|
||||
to loop lists and bitstrings:
|
||||
|
||||
# A list generator:
|
||||
iex> lc n inlist [1,2,3,4], do: n * 2
|
||||
iex> lc n inlist [1, 2, 3, 4], do: n * 2
|
||||
[2,4,6,8]
|
||||
|
||||
# A bit string generator:
|
||||
iex> lc <<n>> inbits <<1,2,3,4>>, do: n * 2
|
||||
iex> lc <<n>> inbits <<1, 2, 3, 4>>, do: n * 2
|
||||
[2,4,6,8]
|
||||
|
||||
# A generator from a variable:
|
||||
iex> list = [1,2,3,4]
|
||||
iex> list = [1, 2, 3, 4]
|
||||
...> lc n inlist list, do: n * 2
|
||||
[2,4,6,8]
|
||||
|
||||
# A comprehension with two generators
|
||||
iex> lc x inlist [1,2], y inlist [2,3], do: x*y
|
||||
iex> lc x inlist [1, 2], y inlist [2, 3], do: x*y
|
||||
[2,3,4,6]
|
||||
|
||||
Filters can also be given:
|
||||
|
||||
# A comprehension with a generator and a filter
|
||||
iex> lc n inlist [1,2,3,4,5,6], rem(n, 2) == 0, do: n
|
||||
iex> lc n inlist [1, 2, 3, 4, 5, 6], rem(n, 2) == 0, do: n
|
||||
[2,4,6]
|
||||
|
||||
Bit string generators are quite useful when you need to
|
||||
organize bit string streams:
|
||||
|
||||
iex> pixels = <<213,45,132,64,76,32,76,0,0,234,32,15>>
|
||||
iex> lc <<r::8,g::8,b::8>> inbits pixels, do: {r,g,b}
|
||||
iex> pixels = <<213, 45, 132, 64, 76, 32, 76, 0, 0, 234, 32, 15>>
|
||||
iex> lc <<r::8, g::8, b::8>> inbits pixels, do: {r, g, b}
|
||||
[{213,45,132},{64,76,32},{76,0,0},{234,32,15}]
|
||||
|
||||
Note: Differently from Erlang, Elixir comprehensions filters
|
||||
never behave as guards when it comes to errors. Errors in
|
||||
list comprehensions will always be raised. Consider this
|
||||
Erlang example:
|
||||
|
||||
erl> [I || I <- [1,2,3], hd(I)].
|
||||
[]
|
||||
|
||||
In Elixir, it will raise:
|
||||
|
||||
iex> lc i inlist [1,2,3], hd(i), do: i
|
||||
** (ArgumentError) argument error
|
||||
|
||||
"""
|
||||
defmacro lc(args)
|
||||
|
||||
@@ -755,7 +847,7 @@ defmodule Kernel.SpecialForms do
|
||||
and should not be invoked directly:
|
||||
|
||||
iex> quote do: (1; 2; 3)
|
||||
{ :__block__, [], [1,2,3] }
|
||||
{ :__block__, [], [1, 2, 3] }
|
||||
|
||||
"""
|
||||
defmacro __block__(args)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
defmodule Kernel.Typespec do
|
||||
@moduledoc """
|
||||
Holds macros and functions for working with typespecs.
|
||||
Provides macros and functions for working with typespecs.
|
||||
|
||||
The attributes `@type`, `@opaque`, `@typep`, `@spec` and
|
||||
`@callback` available in modules are handled by the equivalent
|
||||
@@ -8,20 +8,19 @@ defmodule Kernel.Typespec do
|
||||
|
||||
## Defining a type
|
||||
|
||||
@type type_name :: type
|
||||
@typep type_name :: type
|
||||
@opaque type_name :: type
|
||||
@type type_name :: type
|
||||
@typep type_name :: type
|
||||
@opaque type_name :: type
|
||||
|
||||
For more details, see documentation for deftype, deftypep and defopaque in
|
||||
Kernel.Typespec
|
||||
For more details, see documentation for `deftype`, `deftypep` and `defopaque`
|
||||
below.
|
||||
|
||||
## Defining a specification
|
||||
|
||||
@spec function_name(type, type) :: type
|
||||
@callback function_name(type, type) :: type
|
||||
@spec function_name(type, type) :: type
|
||||
@callback function_name(type, type) :: type
|
||||
|
||||
For more details, see documentation for defspec and defcallback in
|
||||
Kernel.Typespec
|
||||
For more details, see documentation for `defspec` and `defcallback` below.
|
||||
|
||||
## Types
|
||||
|
||||
@@ -30,9 +29,10 @@ defmodule Kernel.Typespec do
|
||||
|
||||
Most of the built-in types provided in Erlang (for example, `pid()`)
|
||||
are expressed the same way: `pid()` or simply `pid`. Parametrized types
|
||||
are also supported: `list(integer())` and so are remote types: `Enum.t`.
|
||||
are also supported (`list(integer())`) and so are remote types (`Enum.t`).
|
||||
|
||||
Certain data type shortcuts ([...], <<>> and {...}) are supported as well.
|
||||
Certain data type shortcuts (`[...]`, `<<>>` and `{...}`) are supported as
|
||||
well.
|
||||
|
||||
Main differences lie in how bit strings and functions are defined:
|
||||
|
||||
@@ -50,22 +50,16 @@ defmodule Kernel.Typespec do
|
||||
|
||||
Any anonymous function:
|
||||
|
||||
(fun(...) -> any)
|
||||
or
|
||||
((...) -> any)
|
||||
or
|
||||
(... -> any)
|
||||
|
||||
Anonymous function with arity of zero:
|
||||
|
||||
(fun() -> type)
|
||||
or
|
||||
(() -> type)
|
||||
|
||||
Anonymous function with some arity:
|
||||
|
||||
(fun(type, type) -> type)
|
||||
or
|
||||
((type, type) -> type)
|
||||
or
|
||||
(type, type -> type)
|
||||
@@ -87,7 +81,7 @@ defmodule Kernel.Typespec do
|
||||
|
||||
@doc """
|
||||
Defines a type.
|
||||
This macro is the one responsible to handle the attribute @type.
|
||||
This macro is the one responsible for handling the attribute `@type`.
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -102,7 +96,7 @@ defmodule Kernel.Typespec do
|
||||
|
||||
@doc """
|
||||
Defines an opaque type.
|
||||
This macro is the one responsible to handle the attribute @opaque.
|
||||
This macro is the one responsible for handling the attribute `@opaque`.
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -117,7 +111,7 @@ defmodule Kernel.Typespec do
|
||||
|
||||
@doc """
|
||||
Defines a private type.
|
||||
This macro is the one responsible to handle the attribute @typep.
|
||||
This macro is the one responsible for handling the attribute `@typep`.
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -132,7 +126,7 @@ defmodule Kernel.Typespec do
|
||||
|
||||
@doc """
|
||||
Defines a spec.
|
||||
This macro is the one responsible to handle the attribute @spec.
|
||||
This macro is the one responsible for handling the attribute `@spec`.
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -147,7 +141,7 @@ defmodule Kernel.Typespec do
|
||||
|
||||
@doc """
|
||||
Defines a callback.
|
||||
This macro is the one responsible to handle the attribute @callback.
|
||||
This macro is the one responsible for handling the attribute `@callback`.
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -175,7 +169,7 @@ defmodule Kernel.Typespec do
|
||||
|
||||
Module.compile_typespec module, kind, type
|
||||
if export, do:
|
||||
Module.compile_typespec module, :export_type, [{ name, length(vars) }]
|
||||
Module.compile_typespec(module, :export_type, [{ name, length(vars) }])
|
||||
type
|
||||
end
|
||||
|
||||
@@ -194,7 +188,7 @@ defmodule Kernel.Typespec do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns true if the current module defines a given type
|
||||
Returns `true` if the current module defines a given type
|
||||
(private, opaque or not). This function is only available
|
||||
for modules being compiled.
|
||||
"""
|
||||
@@ -205,7 +199,7 @@ defmodule Kernel.Typespec do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns true if the current module defines a given spec.
|
||||
Returns `true` if the current module defines a given spec.
|
||||
This function is only available for modules being compiled.
|
||||
"""
|
||||
def defines_spec?(module, name, arity) do
|
||||
@@ -214,7 +208,7 @@ defmodule Kernel.Typespec do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns true if the current module defines a callback.
|
||||
Returns `true` if the current module defines a callback.
|
||||
This function is only available for modules being compiled.
|
||||
"""
|
||||
def defines_callback?(module, name, arity) do
|
||||
@@ -225,7 +219,7 @@ defmodule Kernel.Typespec do
|
||||
@doc """
|
||||
Converts a spec clause back to Elixir AST.
|
||||
"""
|
||||
def spec_to_ast(name, { :type, line, :fun, [{:type, _, :product, args},result] }) do
|
||||
def spec_to_ast(name, { :type, line, :fun, [{:type, _, :product, args}, result] }) do
|
||||
args = lc arg inlist args, do: typespec_to_ast(arg)
|
||||
{ :::, [line: line], [{ name, [line: line], args }, typespec_to_ast(result)] }
|
||||
end
|
||||
@@ -262,13 +256,13 @@ defmodule Kernel.Typespec do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns all types available from the beam.
|
||||
Returns all types available from the module's beam code.
|
||||
|
||||
It is returned as a list of tuples where the first
|
||||
element is the type (`:typep`, `:type` and `:opaque`).
|
||||
|
||||
The module has to have a corresponding beam file
|
||||
on the file system.
|
||||
The module has to have a corresponding beam file on the disk which can be
|
||||
located by the runtime system.
|
||||
"""
|
||||
def beam_types(module) do
|
||||
case abstract_code(module) do
|
||||
@@ -289,26 +283,26 @@ defmodule Kernel.Typespec do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns all specs available from the beam.
|
||||
Returns all specs available from the module's beam code.
|
||||
|
||||
It is returned as a list of tuples where the first
|
||||
element is spec name and arity and the second is the spec.
|
||||
|
||||
The module has to have a corresponding beam file
|
||||
on the file system.
|
||||
The module has to have a corresponding beam file on the disk which can be
|
||||
located by the runtime system.
|
||||
"""
|
||||
def beam_specs(module) do
|
||||
from_abstract_code(module, :spec)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns all callbacks available from the beam.
|
||||
Returns all callbacks available from the module's beam code.
|
||||
|
||||
It is returned as a list of tuples where the first
|
||||
element is spec name and arity and the second is the spec.
|
||||
|
||||
The module has to have a corresponding beam file
|
||||
on the file system.
|
||||
The module has to have a corresponding beam file on the disk which can be
|
||||
located by the runtime system.
|
||||
"""
|
||||
def beam_callbacks(module) do
|
||||
from_abstract_code(module, :callback)
|
||||
@@ -355,9 +349,9 @@ defmodule Kernel.Typespec do
|
||||
do_deftype(kind, type, { :term, [line: caller.line], nil }, caller)
|
||||
end
|
||||
|
||||
def deftype(_kind, other, _caller) do
|
||||
type_spec = Macro.to_binary(other)
|
||||
raise ArgumentError, message: "invalid type specification #{type_spec}"
|
||||
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
|
||||
@@ -396,9 +390,9 @@ defmodule Kernel.Typespec do
|
||||
code
|
||||
end
|
||||
|
||||
def defspec(_type, other, _caller) do
|
||||
spec = Macro.to_binary(other)
|
||||
raise ArgumentError, message: "invalid function type specification #{spec}"
|
||||
def defspec(_type, other, caller) do
|
||||
spec = Macro.to_string(other)
|
||||
compile_error caller, "invalid function type specification #{spec}"
|
||||
end
|
||||
|
||||
defp guard_to_constraints({ :is_subtype, meta, [{ name, _, _ }, type] }, caller) do
|
||||
@@ -451,17 +445,17 @@ defmodule Kernel.Typespec do
|
||||
fn(arg, expr) -> { :|, [line: line], [expr, arg] } end
|
||||
end
|
||||
|
||||
defp typespec_to_ast({ :type, line, :fun, [{:type, _, :product, args},result] }) do
|
||||
defp typespec_to_ast({ :type, line, :fun, [{:type, _, :product, args}, result] }) do
|
||||
args = lc arg inlist args, do: typespec_to_ast(arg)
|
||||
{ :"->", [line: line], [{args, typespec_to_ast(result)}] }
|
||||
{ :->, [line: line], [{args, [line: line], typespec_to_ast(result)}] }
|
||||
end
|
||||
|
||||
defp typespec_to_ast({ :type, line, :fun, [args, result] }) do
|
||||
{ :"->", [line: line], [{[typespec_to_ast(args)], typespec_to_ast(result)}] }
|
||||
{ :->, [line: line], [{[typespec_to_ast(args)], [line: line], typespec_to_ast(result)}] }
|
||||
end
|
||||
|
||||
defp typespec_to_ast({ :type, line, :fun, [] }) do
|
||||
typespec_to_ast({ :type, line, :fun, [{:type, line, :any}, {:type,line,:any, []} ] })
|
||||
typespec_to_ast({ :type, line, :fun, [{:type, line, :any}, {:type, line, :any, []} ] })
|
||||
end
|
||||
|
||||
defp typespec_to_ast({ :type, line, :range, [left, right] }) do
|
||||
@@ -475,12 +469,12 @@ defmodule Kernel.Typespec do
|
||||
|
||||
defp typespec_to_ast({ :var, line, var }) do
|
||||
var =
|
||||
case atom_to_binary(var) do
|
||||
<<"_", c :: [binary, size(1)], rest :: binary>> ->
|
||||
binary_to_atom("_#{String.downcase(c)}#{rest}")
|
||||
<<c :: [binary, size(1)], rest :: binary>> ->
|
||||
binary_to_atom("#{String.downcase(c)}#{rest}")
|
||||
end
|
||||
case atom_to_binary(var) do
|
||||
<<"_", c :: [binary, size(1)], rest :: binary>> ->
|
||||
binary_to_atom("_#{String.downcase(c)}#{rest}")
|
||||
<<c :: [binary, size(1)], rest :: binary>> ->
|
||||
binary_to_atom("#{String.downcase(c)}#{rest}")
|
||||
end
|
||||
{ var, line, nil }
|
||||
end
|
||||
|
||||
@@ -533,14 +527,14 @@ defmodule Kernel.Typespec do
|
||||
end
|
||||
|
||||
# Handle unions
|
||||
defp typespec({ :|, meta, [_,_] } = exprs, vars, caller) do
|
||||
defp typespec({ :|, meta, [_, _] } = exprs, vars, caller) do
|
||||
exprs = Enum.reverse(collect_union(exprs))
|
||||
union = lc e inlist exprs, do: typespec(e, vars, caller)
|
||||
{ :type, line(meta), :union, union }
|
||||
end
|
||||
|
||||
# Handle binaries
|
||||
defp typespec({:<<>>, meta, []}, _,_) do
|
||||
defp typespec({:<<>>, meta, []}, _, _) do
|
||||
{:type, line(meta), :binary, [{:integer, line(meta), 0}, {:integer, line(meta), 0}]}
|
||||
end
|
||||
|
||||
@@ -568,11 +562,11 @@ defmodule Kernel.Typespec do
|
||||
end
|
||||
|
||||
# Handle funs
|
||||
defp typespec({:->, meta, [{[{:fun, _, arguments}], return}]}, vars, caller) when is_list(arguments) do
|
||||
typespec({:->, meta, [{arguments, return}]}, vars, caller)
|
||||
defp typespec({:->, meta, [{[{:fun, _, arguments}], cmeta, return}]}, vars, caller) when is_list(arguments) do
|
||||
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
|
||||
@@ -597,9 +591,11 @@ defmodule Kernel.Typespec do
|
||||
end
|
||||
|
||||
# Handle remote calls
|
||||
defp typespec({{:., meta, [remote, name]}, _, args}, vars, caller) do
|
||||
defp typespec({{:., meta, [remote, name]}, _, args} = orig, vars, caller) do
|
||||
remote = Macro.expand remote, caller
|
||||
unless is_atom(remote), do: raise ArgumentError, message: "invalid remote in typespec"
|
||||
unless is_atom(remote) do
|
||||
compile_error(caller, "invalid remote in typespec: #{Macro.to_string(orig)}")
|
||||
end
|
||||
remote_type({typespec(remote, vars, caller), meta, typespec(name, vars, caller), args}, vars, caller)
|
||||
end
|
||||
|
||||
@@ -674,8 +670,8 @@ defmodule Kernel.Typespec do
|
||||
end
|
||||
|
||||
defp typespec([h|t] = l, vars, caller) do
|
||||
union = Enum.reduce(t, validate_kw(h, l), fn(x, acc) ->
|
||||
{ :|, [], [acc, validate_kw(x, l)] }
|
||||
union = Enum.reduce(t, validate_kw(h, l, caller), fn(x, acc) ->
|
||||
{ :|, [], [acc, validate_kw(x, l, caller)] }
|
||||
end)
|
||||
typespec({ :list, [], [union] }, vars, caller)
|
||||
end
|
||||
@@ -687,6 +683,10 @@ defmodule Kernel.Typespec do
|
||||
|
||||
## Helpers
|
||||
|
||||
defp compile_error(caller, desc) do
|
||||
raise CompileError, file: caller.file, line: caller.line, description: desc
|
||||
end
|
||||
|
||||
defp remote_type({remote, meta, name, arguments}, vars, caller) do
|
||||
arguments = lc arg inlist arguments, do: typespec(arg, vars, caller)
|
||||
{ :remote_type, line(meta), [ remote, name, arguments ] }
|
||||
@@ -695,14 +695,14 @@ defmodule Kernel.Typespec do
|
||||
defp collect_union({ :|, _, [a, b] }), do: [b|collect_union(a)]
|
||||
defp collect_union(v), do: [v]
|
||||
|
||||
defp validate_kw({ key, _ } = t, _) when is_atom(key), do: t
|
||||
defp validate_kw(_, original) do
|
||||
raise ArgumentError, message: "unexpected list #{inspect original} in typespec"
|
||||
defp validate_kw({ key, _ } = t, _, _caller) when is_atom(key), do: t
|
||||
defp validate_kw(_, original, caller) do
|
||||
compile_error(caller, "unexpected list #{Macro.to_string original} in typespec")
|
||||
end
|
||||
|
||||
defp fn_args(meta, args, return, vars, caller) do
|
||||
case [fn_args(meta, args, vars, caller), typespec(return, vars, caller)] do
|
||||
[{:type,_,:any},{:type,_,:any,[]}] -> []
|
||||
[{:type, _, :any}, {:type, _, :any, []}] -> []
|
||||
x -> x
|
||||
end
|
||||
end
|
||||
@@ -724,11 +724,11 @@ defmodule Kernel.Typespec do
|
||||
next,
|
||||
{ :type, _, :tuple, [{ :atom, _, atom }, type] }
|
||||
] }, acc) do
|
||||
unpack_typespec_kw(next, [{atom,typespec_to_ast(type)}|acc])
|
||||
unpack_typespec_kw(next, [{atom, typespec_to_ast(type)}|acc])
|
||||
end
|
||||
|
||||
defp unpack_typespec_kw({ :type, _, :tuple, [{ :atom, _, atom }, type] }, acc) do
|
||||
{ :ok, [{atom,typespec_to_ast(type)}|acc] }
|
||||
{ :ok, [{atom, typespec_to_ast(type)}|acc] }
|
||||
end
|
||||
|
||||
defp unpack_typespec_kw(_, _acc) do
|
||||
|
||||
+39
-23
@@ -18,8 +18,8 @@ defmodule Keyword do
|
||||
@type t :: [{key, value}]
|
||||
|
||||
@doc """
|
||||
Creates a Keyword from enum. Differently from `Keyword.new`
|
||||
which behaves as a dict, `Keyword.from_enum` do not remove
|
||||
Creates a Keyword from an enum. Unlike `Keyword.new`
|
||||
which behaves as a dict, `Keyword.from_enum` does not remove
|
||||
duplicated entries.
|
||||
"""
|
||||
@spec from_enum(Enum.t) :: t
|
||||
@@ -51,12 +51,12 @@ defmodule Keyword do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Creates a Keyword from an enumerable. Similarly to dicts,
|
||||
Creates a Keyword from an enumerable. Similar to dicts,
|
||||
duplicated entries are removed, the latest one prevails.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Keyword.new([{:b,1},{:a,2}])
|
||||
iex> Keyword.new([{:b, 1}, {:a, 2}])
|
||||
[a: 2, b: 1]
|
||||
|
||||
"""
|
||||
@@ -74,7 +74,7 @@ defmodule Keyword do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Keyword.new([:a, :b], fn (x) -> {x,x} end) |> Enum.sort
|
||||
iex> Keyword.new([:a, :b], fn (x) -> {x, x} end) |> Enum.sort
|
||||
[a: :a, b: :b]
|
||||
|
||||
"""
|
||||
@@ -87,20 +87,21 @@ defmodule Keyword do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Gets the value for specific key.
|
||||
Gets the value for a specific `key`.
|
||||
|
||||
If key not exist return default value (nil if no default value)
|
||||
exists.
|
||||
If `key` does not exist, return default value (`nil` if no default value).
|
||||
|
||||
If duplicated entries exist, the first one is returned.
|
||||
Use get_values/2 to retrieve all entries.
|
||||
Use `get_values/2` to retrieve all entries.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Keyword.get([a: 1], :a)
|
||||
1
|
||||
|
||||
iex> Keyword.get([a: 1], :b)
|
||||
nil
|
||||
|
||||
iex> Keyword.get([a: 1], :b, 3)
|
||||
3
|
||||
|
||||
@@ -115,8 +116,8 @@ defmodule Keyword do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Fetchs the value for specific key and return it in a tuple.
|
||||
If the key does not exist, returns `:error`.
|
||||
Fetches the value for a specific `key` and returns it in a tuple.
|
||||
If the `key` does not exist, returns `:error`.
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -136,8 +137,8 @@ defmodule Keyword do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Fetches the value for specific key. If key does not exist,
|
||||
an error is raised.
|
||||
Fetches the value for specific `key`. If `key` does not exist,
|
||||
a `KeyError` is raised.
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -157,7 +158,7 @@ defmodule Keyword do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Gets all values for a specific key.
|
||||
Gets all values for a specific `key`.
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -179,6 +180,9 @@ defmodule Keyword do
|
||||
iex> Keyword.keys([a: 1, b: 2])
|
||||
[:a,:b]
|
||||
|
||||
iex> Keyword.keys([a: 1, b: 2, a: 3])
|
||||
[:a,:b,:a]
|
||||
|
||||
"""
|
||||
@spec keys(t) :: [key]
|
||||
def keys(keywords) do
|
||||
@@ -186,7 +190,7 @@ defmodule Keyword do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns all values.
|
||||
Returns all values from the keyword list.
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -200,8 +204,8 @@ defmodule Keyword do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Deletes all entries in the keyword list for a specific key.
|
||||
If the key does not exist, returns the keyword list unchanged.
|
||||
Deletes all entries in the keyword list for a specific `key`.
|
||||
If the `key` does not exist, returns the keyword list unchanged.
|
||||
Use `delete_first` to delete just the first entry in case of
|
||||
duplicated keys.
|
||||
|
||||
@@ -209,6 +213,10 @@ defmodule Keyword do
|
||||
|
||||
iex> Keyword.delete([a: 1, b: 2], :a)
|
||||
[b: 2]
|
||||
|
||||
iex> Keyword.delete([a: 1, b: 2, a: 3], :a)
|
||||
[b: 2]
|
||||
|
||||
iex> Keyword.delete([b: 2], :a)
|
||||
[b: 2]
|
||||
|
||||
@@ -219,13 +227,14 @@ defmodule Keyword do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Deletes the first entry in the keyword list for a specific key.
|
||||
If the key does not exist, returns the keyword list unchanged.
|
||||
Deletes the first entry in the keyword list for a specific `key`.
|
||||
If the `key` does not exist, returns the keyword list unchanged.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Keyword.delete_first([a: 1, b: 2, a: 3], :a)
|
||||
[b: 2, a: 3]
|
||||
|
||||
iex> Keyword.delete_first([b: 2], :a)
|
||||
[b: 2]
|
||||
|
||||
@@ -246,6 +255,9 @@ defmodule Keyword do
|
||||
iex> Keyword.put([a: 1, b: 2], :a, 3)
|
||||
[a: 3, b: 2]
|
||||
|
||||
iex> Keyword.put([a: 1, b: 2, a: 4], :a, 3)
|
||||
[a: 3, b: 2]
|
||||
|
||||
"""
|
||||
@spec put(t, key, value) :: t
|
||||
def put(keywords, key, value) when is_atom(key) do
|
||||
@@ -257,6 +269,8 @@ defmodule Keyword do
|
||||
already exists.
|
||||
|
||||
## Examples
|
||||
iex> Keyword.put_new([a: 1], :b, 2)
|
||||
[b: 2, a: 1]
|
||||
|
||||
iex> Keyword.put_new([a: 1, b: 2], :a, 3)
|
||||
[a: 1, b: 2]
|
||||
@@ -266,7 +280,7 @@ defmodule Keyword do
|
||||
def put_new(keywords, key, value) when is_atom(key) do
|
||||
case :lists.keyfind(key, 1, keywords) do
|
||||
{ ^key, _ } -> keywords
|
||||
false -> [{key,value}|keywords]
|
||||
false -> [{key, value}|keywords]
|
||||
end
|
||||
end
|
||||
|
||||
@@ -326,12 +340,13 @@ defmodule Keyword do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns whether a given key exists in the given keywords.
|
||||
Returns whether a given `key` exists in the given `keywords`.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Keyword.has_key?([a: 1], :a)
|
||||
true
|
||||
|
||||
iex> Keyword.has_key?([a: 1], :b)
|
||||
false
|
||||
|
||||
@@ -342,7 +357,7 @@ defmodule Keyword do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Updates the key with the given function. If the key does
|
||||
Updates the `key` with the given function. If the `key` does
|
||||
not exist, raises `KeyError`.
|
||||
|
||||
## Examples
|
||||
@@ -368,13 +383,14 @@ defmodule Keyword do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Updates the key with the given function. If the key does
|
||||
Updates the `key` with the given function. If the `key` does
|
||||
not exist, inserts the given `initial` value.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Keyword.update([a: 1], :a, 13, &1 * 2)
|
||||
[a: 2]
|
||||
|
||||
iex> Keyword.update([a: 1], :b, 11, &1 * 2)
|
||||
[a: 1, b: 11]
|
||||
|
||||
|
||||
+12
-10
@@ -10,12 +10,14 @@ defmodule List do
|
||||
first argument.
|
||||
"""
|
||||
|
||||
@compile :inline_list_funcs
|
||||
|
||||
@doc """
|
||||
Given a list of lists, concatenates the sublists into a single list.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> List.concat([[1,[2],3], [4], [5,6]])
|
||||
iex> List.concat([[1, [2], 3], [4], [5, 6]])
|
||||
[1,[2],3,4,5,6]
|
||||
|
||||
"""
|
||||
@@ -32,7 +34,7 @@ defmodule List do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> List.concat([1,2,3], [4,5,6])
|
||||
iex> List.concat([1, 2, 3], [4, 5, 6])
|
||||
[1,2,3,4,5,6]
|
||||
|
||||
"""
|
||||
@@ -47,7 +49,7 @@ defmodule List do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> List.delete([1,2,3], 1)
|
||||
iex> List.delete([1, 2, 3], 1)
|
||||
[2,3]
|
||||
|
||||
"""
|
||||
@@ -63,7 +65,7 @@ defmodule List do
|
||||
iex> List.duplicate("hello", 3)
|
||||
["hello","hello","hello"]
|
||||
|
||||
iex> List.duplicate([1,2], 2)
|
||||
iex> List.duplicate([1, 2], 2)
|
||||
[[1,2],[1,2]]
|
||||
"""
|
||||
def duplicate(elem, n) do
|
||||
@@ -77,10 +79,10 @@ defmodule List do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> List.flatten([1,[[2],3]])
|
||||
iex> List.flatten([1, [[2], 3]])
|
||||
[1,2,3]
|
||||
|
||||
iex> List.flatten([1,[[2],3]], [4,5])
|
||||
iex> List.flatten([1, [[2], 3]], [4, 5])
|
||||
[1,2,3,4,5]
|
||||
|
||||
"""
|
||||
@@ -98,10 +100,10 @@ defmodule List do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> List.foldl([5,5], 10, fn (x, acc) -> x + acc end)
|
||||
iex> List.foldl([5, 5], 10, fn (x, acc) -> x + acc end)
|
||||
20
|
||||
|
||||
iex> List.foldl([1,2,3,4], 0, fn (x, acc) -> x - acc end)
|
||||
iex> List.foldl([1, 2, 3, 4], 0, fn (x, acc) -> x - acc end)
|
||||
2
|
||||
|
||||
"""
|
||||
@@ -115,7 +117,7 @@ defmodule List do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> List.foldr([1,2,3,4], 0, fn (x, acc) -> x - acc end)
|
||||
iex> List.foldr([1, 2, 3, 4], 0, fn (x, acc) -> x - acc end)
|
||||
-2
|
||||
|
||||
"""
|
||||
@@ -241,7 +243,7 @@ defmodule List do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> List.wrap([1,2,3])
|
||||
iex> List.wrap([1, 2, 3])
|
||||
[1,2,3]
|
||||
|
||||
"""
|
||||
|
||||
@@ -15,7 +15,7 @@ defmodule ListDict do
|
||||
Creates a new `ListDict` from the given pairs.
|
||||
"""
|
||||
def new(pairs) do
|
||||
Enum.map pairs, fn({ k, v }) -> { k, v } end
|
||||
Enum.to_list pairs
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -26,38 +26,22 @@ defmodule ListDict do
|
||||
Enum.map list, transform
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns all keys in the dict.
|
||||
"""
|
||||
def keys(dict) do
|
||||
lc { key, _ } inlist dict, do: key
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns all values in the dict.
|
||||
"""
|
||||
def values(dict) do
|
||||
lc { _, value } inlist dict, do: value
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns the dict size.
|
||||
"""
|
||||
def size(dict) do
|
||||
length(dict)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns true if the dict has the given key.
|
||||
"""
|
||||
def has_key?(dict, key) do
|
||||
:lists.keymember(key, 1, dict)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns the value under key from the given
|
||||
dict or default if no key is set.
|
||||
"""
|
||||
def get(dict, key, default // nil) do
|
||||
case :lists.keyfind(key, 1, dict) do
|
||||
{ ^key, value } -> value
|
||||
@@ -65,10 +49,6 @@ defmodule ListDict do
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns the value under key from the given
|
||||
dict in a tagged tuple, otherwise `:error`.
|
||||
"""
|
||||
def fetch(dict, key) do
|
||||
case :lists.keyfind(key, 1, dict) do
|
||||
{ ^key, value } -> { :ok, value }
|
||||
@@ -76,10 +56,6 @@ defmodule ListDict do
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns the value under the given key
|
||||
raises `KeyError` if the key does not exist.
|
||||
"""
|
||||
def fetch!(dict, key) do
|
||||
case :lists.keyfind(key, 1, dict) do
|
||||
{ ^key, value } -> value
|
||||
@@ -87,42 +63,25 @@ defmodule ListDict do
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns the value under the given key
|
||||
from the dict as well as the dict without that key.
|
||||
"""
|
||||
def pop(dict, key, default // nil) do
|
||||
{ get(dict, key, default), delete(dict, key) }
|
||||
end
|
||||
|
||||
@doc """
|
||||
Puts the given key-value pair in the dict.
|
||||
"""
|
||||
def put(dict, key, val) do
|
||||
[{key, val}|delete(dict, key)]
|
||||
end
|
||||
|
||||
@doc """
|
||||
Puts the given key-value pair in the dict
|
||||
if no entry exists yet.
|
||||
"""
|
||||
def put_new(dict, key, val) do
|
||||
case :lists.keyfind(key, 1, dict) do
|
||||
{ ^key, _ } -> dict
|
||||
false -> [{key,val}|dict]
|
||||
false -> [{key, val}|dict]
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Deletes the entry under the given key from the dict.
|
||||
"""
|
||||
def delete(dict, key) do
|
||||
lc { k, _ } = tuple inlist dict, key != k, do: tuple
|
||||
end
|
||||
|
||||
@doc """
|
||||
Merges the given Enumerable into the dict.
|
||||
"""
|
||||
def merge(dict, enum, callback // fn(_k, _v1, v2) -> v2 end)
|
||||
|
||||
def merge(dict1, dict2, fun) do
|
||||
@@ -131,42 +90,25 @@ defmodule ListDict do
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Splits a dict into two dicts,
|
||||
one containing entries with key in the keys list,
|
||||
and another containing entries with key not in keys.
|
||||
Returns a 2-tuple of the new dicts.
|
||||
"""
|
||||
def split(dict, keys) do
|
||||
acc = { new(), new() }
|
||||
Enum.reduce dict, acc, fn({ k, v }, { take, drop }) ->
|
||||
if :lists.member(k, keys) do
|
||||
{ [{k,v}|take], drop }
|
||||
{ [{k, v}|take], drop }
|
||||
else
|
||||
{ take, [{k,v}|drop] }
|
||||
{ take, [{k, v}|drop] }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns a new dict with only the entries
|
||||
which key is in keys
|
||||
"""
|
||||
def take(dict, keys) do
|
||||
lc { k, _ } = tuple inlist dict, :lists.member(k, keys), do: tuple
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns a new dict with only the entries
|
||||
which key is not in keys
|
||||
"""
|
||||
def drop(dict, keys) do
|
||||
lc { k, _ } = tuple inlist dict, not :lists.member(k, keys), do: tuple
|
||||
end
|
||||
|
||||
@doc """
|
||||
Updates the key in the dict according to the given function.
|
||||
"""
|
||||
def update([{key, value}|dict], key, fun) do
|
||||
[{key, fun.(value)}|delete(dict, key)]
|
||||
end
|
||||
@@ -179,10 +121,6 @@ defmodule ListDict do
|
||||
raise(KeyError, key: key)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Updates the key in the dict according to the given function
|
||||
or uses the given initial value if no entry exists.
|
||||
"""
|
||||
def update([{key, value}|dict], key, _initial, fun) do
|
||||
[{key, fun.(value)}|delete(dict, key)]
|
||||
end
|
||||
@@ -195,20 +133,11 @@ defmodule ListDict do
|
||||
[{key, initial}]
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns an empty `ListDict`.
|
||||
"""
|
||||
def empty(_dict), do: []
|
||||
|
||||
@doc """
|
||||
Check if the ListDict is equal to another ListDict.
|
||||
"""
|
||||
def equal?(dict, other) do
|
||||
:lists.keysort(1, dict) == :lists.keysort(1, other)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Converts the dict to a list.
|
||||
"""
|
||||
def to_list(dict), do: dict
|
||||
end
|
||||
|
||||
+213
-126
@@ -1,10 +1,12 @@
|
||||
import Kernel, except: [to_binary: 1]
|
||||
import Kernel, except: [to_string: 1]
|
||||
|
||||
defmodule Macro do
|
||||
@moduledoc """
|
||||
This module provides conveniences for working with macros.
|
||||
"""
|
||||
|
||||
@type t :: { t, t } | { t, Keyword.t, t } | atom | number | binary | list
|
||||
|
||||
@doc """
|
||||
Returns a list of binary operators. This is available
|
||||
as a macro so it can be used in guard clauses.
|
||||
@@ -14,10 +16,10 @@ defmodule Macro do
|
||||
:===, :!==,
|
||||
:==, :!=, :<=, :>=,
|
||||
:&&, :||, :<>, :++, :--, :**, ://, :::, :<-, :.., :|>, :=~,
|
||||
:<, :>,
|
||||
:<, :>, :->,
|
||||
:+, :-, :*, :/, :=, :|, :.,
|
||||
:and, :or, :xor, :when, :in, :inlist, :inbits,
|
||||
:<<<, :>>>, :|||, :&&&, :^^^, :~~~
|
||||
:<<<, :>>>, :|||, :&&&, :^^^
|
||||
]
|
||||
end
|
||||
|
||||
@@ -26,7 +28,7 @@ defmodule Macro do
|
||||
as a macro so it can be used in guard clauses.
|
||||
"""
|
||||
defmacro unary_ops do
|
||||
[:!, :@, :^, :not, :+, :-]
|
||||
[:!, :@, :^, :not, :+, :-, :~~~, :&]
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -42,8 +44,8 @@ defmodule Macro do
|
||||
|
||||
extract_args(quote do: foo) == { :foo, [] }
|
||||
extract_args(quote do: foo()) == { :foo, [] }
|
||||
extract_args(quote do: foo(1,2,3)) == { :foo, [1,2,3] }
|
||||
extract_args(quote do: 1.(1,2,3)) == :error
|
||||
extract_args(quote do: foo(1, 2, 3)) == { :foo, [1, 2, 3] }
|
||||
extract_args(quote do: 1.(1, 2, 3)) == :error
|
||||
|
||||
"""
|
||||
def extract_args(expr) do
|
||||
@@ -54,6 +56,10 @@ defmodule Macro do
|
||||
Recursively escapes a value so it can be inserted
|
||||
into a syntax tree.
|
||||
|
||||
One may pass `unquote: true` to `Macro.escape/2`
|
||||
which leaves unquote statements unescaped, effectively
|
||||
unquoting the contents on escape.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Macro.escape(:foo)
|
||||
@@ -62,14 +68,12 @@ defmodule Macro do
|
||||
iex> Macro.escape({ :a, :b, :c })
|
||||
{ :{}, [], [:a, :b, :c] }
|
||||
|
||||
"""
|
||||
def escape(expr) do
|
||||
:elixir_quote.escape(expr, false) |> elem(0)
|
||||
end
|
||||
iex> Macro.escape({ :unquote, [], [1] }, unquote: true)
|
||||
1
|
||||
|
||||
@doc false
|
||||
def escape_quoted(expr) do
|
||||
:elixir_quote.escape(expr, true) |> elem(0)
|
||||
"""
|
||||
def escape(expr, opts // []) do
|
||||
:elixir_quote.escape(expr, Keyword.get(opts, :unquote, false)) |> elem(0)
|
||||
end
|
||||
|
||||
@doc %B"""
|
||||
@@ -151,7 +155,7 @@ defmodule Macro do
|
||||
|
||||
Only tokens that are binaries are unescaped, all others are
|
||||
ignored. This function is useful when implementing your own
|
||||
sigils. Check the implementation of `Kernel.__b__`
|
||||
sigils. Check the implementation of `Kernel.sigil_b`
|
||||
for examples.
|
||||
"""
|
||||
def unescape_tokens(tokens) do
|
||||
@@ -166,112 +170,124 @@ defmodule Macro do
|
||||
:elixir_interpolation.unescape_tokens(tokens, map)
|
||||
end
|
||||
|
||||
@doc false
|
||||
def to_binary(tree) do
|
||||
IO.write "[WARNING] Macro.to_binary is deprecated, please use Macro.to_string instead\n#{Exception.format_stacktrace}"
|
||||
to_string(tree)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Converts the given expression to a binary.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Macro.to_binary(quote do: foo.bar(1, 2, 3))
|
||||
iex> Macro.to_string(quote do: foo.bar(1, 2, 3))
|
||||
"foo.bar(1, 2, 3)"
|
||||
|
||||
"""
|
||||
def to_binary(tree)
|
||||
def to_string(tree)
|
||||
|
||||
# Variables
|
||||
def to_binary({ var, _, atom }) when is_atom(atom) do
|
||||
def to_string({ var, _, atom }) when is_atom(atom) do
|
||||
atom_to_binary(var, :utf8)
|
||||
end
|
||||
|
||||
# Aliases
|
||||
def to_binary({ :__aliases__, _, refs }) do
|
||||
Enum.map_join(refs, ".", call_to_binary(&1))
|
||||
def to_string({ :__aliases__, _, refs }) do
|
||||
Enum.map_join(refs, ".", call_to_string(&1))
|
||||
end
|
||||
|
||||
# Blocks
|
||||
def to_binary({ :__block__, _, [expr] }) do
|
||||
to_binary(expr)
|
||||
def to_string({ :__block__, _, [expr] }) do
|
||||
to_string(expr)
|
||||
end
|
||||
|
||||
def to_binary({ :__block__, _, _ } = expr) do
|
||||
block = adjust_new_lines block_to_binary(expr), "\n "
|
||||
def to_string({ :__block__, _, _ } = expr) do
|
||||
block = adjust_new_lines block_to_string(expr), "\n "
|
||||
"(\n " <> block <> "\n)"
|
||||
end
|
||||
|
||||
# Bits containers
|
||||
def to_binary({ :<<>>, _, args }) do
|
||||
"<<" <> Enum.map_join(args, ", ", to_binary(&1)) <> ">>"
|
||||
def to_string({ :<<>>, _, args }) do
|
||||
"<<" <> Enum.map_join(args, ", ", to_string(&1)) <> ">>"
|
||||
end
|
||||
|
||||
# Tuple containers
|
||||
def to_binary({ :{}, _, args }) do
|
||||
"{" <> Enum.map_join(args, ", ", to_binary(&1)) <> "}"
|
||||
def to_string({ :{}, _, args }) do
|
||||
"{" <> Enum.map_join(args, ", ", to_string(&1)) <> "}"
|
||||
end
|
||||
|
||||
# List containers
|
||||
def to_binary({ :[], _, args }) do
|
||||
"[" <> Enum.map_join(args, ", ", to_binary(&1)) <> "]"
|
||||
def to_string({ :[], _, args }) do
|
||||
"[" <> Enum.map_join(args, ", ", to_string(&1)) <> "]"
|
||||
end
|
||||
|
||||
# Fn keyword
|
||||
def to_binary({ :fn, _, [[do: { :->, _, [{_,tuple}] } = arrow]] })
|
||||
def to_string({ :fn, _, [[do: { :->, _, [{_, _, tuple}] } = arrow]] })
|
||||
when not is_tuple(tuple) or elem(tuple, 0) != :__block__ do
|
||||
"fn " <> arrow_to_binary(arrow) <> " end"
|
||||
"fn " <> arrow_to_string(arrow) <> " end"
|
||||
end
|
||||
|
||||
def to_binary({ :fn, _, [[do: { :->, _, [_] } = block]] }) do
|
||||
"fn " <> block_to_binary(block) <> "\nend"
|
||||
def to_string({ :fn, _, [[do: { :->, _, [_] } = block]] }) do
|
||||
"fn " <> block_to_string(block) <> "\nend"
|
||||
end
|
||||
|
||||
def to_binary({ :fn, _, [[do: block]] }) do
|
||||
block = adjust_new_lines block_to_binary(block), "\n "
|
||||
def to_string({ :fn, _, [[do: block]] }) do
|
||||
block = adjust_new_lines block_to_string(block), "\n "
|
||||
"fn\n " <> block <> "\nend"
|
||||
end
|
||||
|
||||
# Partial call
|
||||
def to_binary({ :&, _, [num] }) do
|
||||
def to_string({ :&, _, [num] }) do
|
||||
"&#{num}"
|
||||
end
|
||||
|
||||
# left -> right
|
||||
def to_binary({ :->, _, _ } = arrow) do
|
||||
"(" <> arrow_to_binary(arrow, true) <> ")"
|
||||
def to_string({ :->, _, _ } = arrow) do
|
||||
"(" <> arrow_to_string(arrow, true) <> ")"
|
||||
end
|
||||
|
||||
# Binary ops
|
||||
def to_binary({ op, _, [left, right] }) when op in binary_ops do
|
||||
op_to_binary(left) <> " #{op} " <> op_to_binary(right)
|
||||
def to_string({ op, _, [left, right] }) when op in binary_ops do
|
||||
op_to_string(left) <> " #{op} " <> op_to_string(right)
|
||||
end
|
||||
|
||||
# Splat when
|
||||
def to_string({ :when, _, args }) do
|
||||
{ left, right } = :elixir_tree_helpers.split_last(args)
|
||||
"(" <> Enum.map_join(left, ", ", to_string(&1)) <> ") when " <> to_string(right)
|
||||
end
|
||||
|
||||
# Unary ops
|
||||
def to_binary({ :not, _, [arg] }) do
|
||||
"not " <> to_binary(arg)
|
||||
def to_string({ :not, _, [arg] }) do
|
||||
"not " <> to_string(arg)
|
||||
end
|
||||
|
||||
def to_binary({ op, _, [arg] }) when op in unary_ops do
|
||||
atom_to_binary(op, :utf8) <> to_binary(arg)
|
||||
def to_string({ op, _, [arg] }) when op in unary_ops do
|
||||
atom_to_binary(op, :utf8) <> to_string(arg)
|
||||
end
|
||||
|
||||
# All other calls
|
||||
def to_binary({ target, _, args }) when is_list(args) do
|
||||
def to_string({ target, _, args }) when is_list(args) do
|
||||
{ list, last } = :elixir_tree_helpers.split_last(args)
|
||||
case is_kw_blocks?(last) do
|
||||
true -> call_to_binary_with_args(target, list) <> kw_blocks_to_binary(last)
|
||||
false -> call_to_binary_with_args(target, args)
|
||||
true -> call_to_string_with_args(target, list) <> kw_blocks_to_string(last)
|
||||
false -> call_to_string_with_args(target, args)
|
||||
end
|
||||
end
|
||||
|
||||
# Two-item tuples
|
||||
def to_binary({ left, right }) do
|
||||
to_binary({ :{}, [], [left, right] })
|
||||
def to_string({ left, right }) do
|
||||
to_string({ :{}, [], [left, right] })
|
||||
end
|
||||
|
||||
# Lists
|
||||
def to_binary(list) when is_list(list) do
|
||||
to_binary({ :[], [], list })
|
||||
def to_string(list) when is_list(list) do
|
||||
to_string({ :[], [], list })
|
||||
end
|
||||
|
||||
# All other structures
|
||||
def to_binary(other), do: inspect(other, raw: true)
|
||||
def to_string(other), do: inspect(other, raw: true)
|
||||
|
||||
# Block keywords
|
||||
defmacrop kw_keywords, do: [:do, :catch, :rescue, :after, :else]
|
||||
@@ -281,56 +297,56 @@ defmodule Macro do
|
||||
end
|
||||
defp is_kw_blocks?(_), do: false
|
||||
|
||||
defp module_to_binary(atom) when is_atom(atom), do: inspect(atom, raw: true)
|
||||
defp module_to_binary(other), do: call_to_binary(other)
|
||||
defp module_to_string(atom) when is_atom(atom), do: inspect(atom, raw: true)
|
||||
defp module_to_string(other), do: call_to_string(other)
|
||||
|
||||
defp call_to_binary(atom) when is_atom(atom), do: atom_to_binary(atom, :utf8)
|
||||
defp call_to_binary({ :., _, [arg] }), do: module_to_binary(arg) <> "."
|
||||
defp call_to_binary({ :., _, [left, right] }), do: module_to_binary(left) <> "." <> call_to_binary(right)
|
||||
defp call_to_binary(other), do: to_binary(other)
|
||||
defp call_to_string(atom) when is_atom(atom), do: atom_to_binary(atom, :utf8)
|
||||
defp call_to_string({ :., _, [arg] }), do: module_to_string(arg) <> "."
|
||||
defp call_to_string({ :., _, [left, right] }), do: module_to_string(left) <> "." <> call_to_string(right)
|
||||
defp call_to_string(other), do: to_string(other)
|
||||
|
||||
defp call_to_binary_with_args(target, args) do
|
||||
args = Enum.map_join(args, ", ", to_binary(&1))
|
||||
call_to_binary(target) <> "(" <> args <> ")"
|
||||
defp call_to_string_with_args(target, args) do
|
||||
args = Enum.map_join(args, ", ", to_string(&1))
|
||||
call_to_string(target) <> "(" <> args <> ")"
|
||||
end
|
||||
|
||||
defp kw_blocks_to_binary(kw) do
|
||||
defp kw_blocks_to_string(kw) do
|
||||
Enum.reduce(kw_keywords, " ", fn(x, acc) ->
|
||||
case Keyword.has_key?(kw, x) do
|
||||
true -> acc <> kw_block_to_binary(x, Keyword.get(kw, x))
|
||||
true -> acc <> kw_block_to_string(x, Keyword.get(kw, x))
|
||||
false -> acc
|
||||
end
|
||||
end) <> "end"
|
||||
end
|
||||
|
||||
defp kw_block_to_binary(key, value) do
|
||||
block = adjust_new_lines block_to_binary(value), "\n "
|
||||
defp kw_block_to_string(key, value) do
|
||||
block = adjust_new_lines block_to_string(value), "\n "
|
||||
atom_to_binary(key, :utf8) <> "\n " <> block <> "\n"
|
||||
end
|
||||
|
||||
defp block_to_binary({ :->, _, exprs }) do
|
||||
Enum.map_join(exprs, "\n", fn({ left, right }) ->
|
||||
defp block_to_string({ :->, _, exprs }) do
|
||||
Enum.map_join(exprs, "\n", fn({ left, _, right }) ->
|
||||
left = comma_join_or_empty_paren(left, false)
|
||||
left <> "->\n " <> adjust_new_lines block_to_binary(right), "\n "
|
||||
left <> "->\n " <> adjust_new_lines block_to_string(right), "\n "
|
||||
end)
|
||||
end
|
||||
|
||||
defp block_to_binary({ :__block__, _, exprs }) do
|
||||
Enum.map_join(exprs, "\n", to_binary(&1))
|
||||
defp block_to_string({ :__block__, _, exprs }) do
|
||||
Enum.map_join(exprs, "\n", to_string(&1))
|
||||
end
|
||||
|
||||
defp block_to_binary(other), do: to_binary(other)
|
||||
defp block_to_string(other), do: to_string(other)
|
||||
|
||||
defp op_to_binary({ op, _, [_, _] } = expr) when op in binary_ops do
|
||||
"(" <> to_binary(expr) <> ")"
|
||||
defp op_to_string({ op, _, [_, _] } = expr) when op in binary_ops do
|
||||
"(" <> to_string(expr) <> ")"
|
||||
end
|
||||
|
||||
defp op_to_binary(expr), do: to_binary(expr)
|
||||
defp op_to_string(expr), do: to_string(expr)
|
||||
|
||||
defp arrow_to_binary({ :->, _, pairs }, paren // false) do
|
||||
Enum.map_join(pairs, "; ", fn({ left, right }) ->
|
||||
defp arrow_to_string({ :->, _, pairs }, paren // false) do
|
||||
Enum.map_join(pairs, "; ", fn({ left, _, right }) ->
|
||||
left = comma_join_or_empty_paren(left, paren)
|
||||
left <> "-> " <> to_binary(right)
|
||||
left <> "-> " <> to_string(right)
|
||||
end)
|
||||
end
|
||||
|
||||
@@ -338,7 +354,7 @@ defmodule Macro do
|
||||
defp comma_join_or_empty_paren([], false), do: ""
|
||||
|
||||
defp comma_join_or_empty_paren(left, _) do
|
||||
Enum.map_join(left, ", ", to_binary(&1)) <> " "
|
||||
Enum.map_join(left, ", ", to_string(&1)) <> " "
|
||||
end
|
||||
|
||||
defp adjust_new_lines(block, replacement) do
|
||||
@@ -351,32 +367,18 @@ defmodule Macro do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Receives an expression representation and expands it. The following
|
||||
contents are expanded:
|
||||
Receives a AST node and expands it once. The following contents are expanded:
|
||||
|
||||
* Macros (local or remote);
|
||||
* Aliases are expanded (if possible) and return atoms;
|
||||
* All pseudo-variables (__FILE__, __MODULE__, etc);
|
||||
* Module attributes reader (@foo);
|
||||
|
||||
In case the expression cannot be expanded, it returns the expression itself.
|
||||
|
||||
Notice that `Macro.expand` is not recursive and it does not
|
||||
expand child expressions. In this example
|
||||
|
||||
Macro.expand(quote(do: !some_macro), __ENV__)
|
||||
|
||||
`!some_macro` will expand to something like:
|
||||
|
||||
case some_macro do
|
||||
false -> true
|
||||
nil -> true
|
||||
_ -> false
|
||||
end
|
||||
|
||||
Notice that the `!` operator is a macro that expands to a case.
|
||||
Even though `some_macro` is also a macro, it is not expanded
|
||||
because it is a child expression given to `!` as argument.
|
||||
In case the expression cannot be expanded, it returns the expression
|
||||
itself. Notice that `Macro.expand_once/2` performs the expansion just
|
||||
once and it is not recursive. Check `Macro.expand/2` for expansion
|
||||
until the node no longer represents a macro and `Macro.expand_all/2`
|
||||
for recursive expansion.
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -439,46 +441,50 @@ defmodule Macro do
|
||||
end
|
||||
|
||||
"""
|
||||
def expand(aliases, env) do
|
||||
expand(aliases, env, nil)
|
||||
def expand_once(aliases, env) do
|
||||
expand_once(aliases, env, nil) |> elem(0)
|
||||
end
|
||||
|
||||
defp expand({ :__aliases__, _, _ } = original, env, cache) do
|
||||
defp expand_once({ :__aliases__, _, _ } = original, env, cache) do
|
||||
case :elixir_aliases.expand(original, env.aliases, env.macro_aliases) do
|
||||
atom when is_atom(atom) -> atom
|
||||
atom when is_atom(atom) -> { atom, true, cache }
|
||||
aliases ->
|
||||
aliases = lc alias inlist aliases, do: expand(alias, env, cache)
|
||||
aliases = lc alias inlist aliases, do: (expand_once(alias, env, cache) |> elem(0))
|
||||
|
||||
case :lists.all(is_atom(&1), aliases) do
|
||||
true -> :elixir_aliases.concat(aliases)
|
||||
false -> original
|
||||
true -> { :elixir_aliases.concat(aliases), true, cache }
|
||||
false -> { original, false, cache }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Expand @ calls
|
||||
defp expand({ :@, _, [{ name, _, args }] } = original, env, _cache) when is_atom(args) or args == [] do
|
||||
defp expand_once({ :@, _, [{ name, _, args }] } = original, env, cache) when is_atom(args) or args == [] do
|
||||
case (module = env.module) && Module.open?(module) do
|
||||
true -> Module.get_attribute(module, name)
|
||||
false -> original
|
||||
true -> { Module.get_attribute(module, name), true, cache }
|
||||
false -> { original, false, cache }
|
||||
end
|
||||
end
|
||||
|
||||
# Expand pseudo-variables
|
||||
defp expand({ :__MODULE__, _, atom }, env, _cache) when is_atom(atom), do: env.module
|
||||
defp expand({ :__FILE__, _, atom }, env, _cache) when is_atom(atom), do: env.file
|
||||
defp expand({ :__DIR__, _, atom }, env, _cache) when is_atom(atom), do: :filename.dirname(env.file)
|
||||
defp expand({ :__ENV__, _, atom }, env, _cache) when is_atom(atom), do: env
|
||||
defp expand_once({ :__MODULE__, _, atom }, env, cache) when is_atom(atom),
|
||||
do: { env.module, true, cache }
|
||||
defp expand_once({ :__FILE__, _, atom }, env, cache) when is_atom(atom),
|
||||
do: { env.file, true, cache }
|
||||
defp expand_once({ :__DIR__, _, atom }, env, cache) when is_atom(atom),
|
||||
do: { :filename.dirname(env.file), true, cache }
|
||||
defp expand_once({ :__ENV__, _, atom }, env, cache) when is_atom(atom),
|
||||
do: { env, true, cache }
|
||||
|
||||
# Expand possible macro import invocation
|
||||
defp expand({ atom, line, args } = original, env, cache) when is_atom(atom) do
|
||||
defp expand_once({ atom, line, args } = original, env, cache) when is_atom(atom) do
|
||||
args = case is_atom(args) do
|
||||
true -> []
|
||||
false -> args
|
||||
end
|
||||
|
||||
case not is_partial?(args) do
|
||||
false -> original
|
||||
false -> { original, false, cache }
|
||||
true ->
|
||||
module = env.module
|
||||
|
||||
@@ -488,43 +494,124 @@ defmodule Macro do
|
||||
[]
|
||||
end
|
||||
|
||||
cache = to_erl_env(env, cache)
|
||||
expand = :elixir_dispatch.expand_import(line, { atom, length(args) }, args,
|
||||
env.module, extra, to_erl_env(env, cache))
|
||||
env.module, extra, cache)
|
||||
|
||||
case expand do
|
||||
{ :ok, _, expanded } -> expanded
|
||||
{ :error, _ } -> original
|
||||
{ :ok, _, expanded } -> { expanded, true, cache }
|
||||
{ :error, _ } -> { original, false, cache }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Expand possible macro require invocation
|
||||
defp expand({ { :., _, [left, right] }, line, args } = original, env, cache) when is_atom(right) do
|
||||
receiver = expand(left, env)
|
||||
defp expand_once({ { :., _, [left, right] }, line, args } = original, env, cache) when is_atom(right) do
|
||||
{ receiver, _, _ } = expand_once(left, env, cache)
|
||||
|
||||
case is_atom(receiver) and not is_partial?(args) do
|
||||
false -> original
|
||||
false -> { original, false, cache }
|
||||
true ->
|
||||
cache = to_erl_env(env, cache)
|
||||
expand = :elixir_dispatch.expand_require(line, receiver, { right, length(args) },
|
||||
args, env.module, to_erl_env(env, cache))
|
||||
args, env.module, cache)
|
||||
|
||||
case expand do
|
||||
{ :ok, _receiver, expanded } -> expanded
|
||||
{ :error, _ } -> original
|
||||
{ :ok, _receiver, expanded } -> { expanded, true, cache }
|
||||
{ :error, _ } -> { original, false, cache }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Anything else is just returned
|
||||
defp expand(other, _env, _cache), do: other
|
||||
defp expand_once(other, _env, cache), do: { other, false, cache }
|
||||
|
||||
defp to_erl_env(env, nil), do: :elixir_scope.to_erl_env(env)
|
||||
defp to_erl_env(_env, cache), do: cache
|
||||
|
||||
## Helpers
|
||||
|
||||
defp is_partial?(args) do
|
||||
:lists.any(match?({ :&, _, [_] }, &1), args)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Receives a AST node and expands it until it no longer represents
|
||||
a macro. Check `Macro.expand_once/2` for more information on how
|
||||
expansion works and `Macro.expand_all/2` for recursive expansion.
|
||||
"""
|
||||
def expand(tree, env) do
|
||||
expand(tree, env, nil) |> elem(0)
|
||||
end
|
||||
|
||||
@doc false # Used internally by Elixir
|
||||
def expand(tree, env, cache) do
|
||||
expand_until({ tree, true, cache }, env)
|
||||
end
|
||||
|
||||
defp expand_until({ tree, true, cache }, env) do
|
||||
expand_until(expand_once(tree, env, cache), env)
|
||||
end
|
||||
|
||||
defp expand_until({ tree, false, cache }, _env) do
|
||||
{ tree, cache }
|
||||
end
|
||||
|
||||
@doc %B"""
|
||||
Receives a AST node and expands it until it no longer represents
|
||||
a macro. Then it expands all of its children recursively.
|
||||
|
||||
The documentation for `Macro.expand_once/2` goes into more detail
|
||||
on how expansion works. Keep in mind that `Macro.expand_all/2` is
|
||||
naive as it doesn't mutate the environment. Take this example:
|
||||
|
||||
quoted = quote do: __MODULE__
|
||||
Macro.to_string Macro.expand_all(quoted, __ENV__)
|
||||
#=> "nil"
|
||||
|
||||
The example above, works as expected. Outside of a module,
|
||||
`__MODULE__` returns nil. Now consider this variation:
|
||||
|
||||
quoted = quote do
|
||||
defmodule Foo, do: __MODULE__
|
||||
end
|
||||
Macro.to_string Macro.expand_all(quoted, __ENV__)
|
||||
#=> "defmodule(Foo) do\n nil\nend"
|
||||
|
||||
One would expect `__MODULE__` to be expanded to `Foo`
|
||||
but that is not the case since all the expansion is done
|
||||
based on the environment `__ENV__`. This behaviour is on
|
||||
purpose, as the proper expansion would require passing through
|
||||
the whole Elixir compiler by actually executing the code, which
|
||||
is beyond the scope of this function.
|
||||
"""
|
||||
def expand_all(tree, env) do
|
||||
expand_all(tree, env, nil) |> elem(0)
|
||||
end
|
||||
|
||||
@doc false # Used internally by Elixir
|
||||
def expand_all(tree, env, cache) do
|
||||
expand_all_until(expand(tree, env, cache), env)
|
||||
end
|
||||
|
||||
defp expand_all_until({ { left, meta, right }, cache }, env) do
|
||||
{ left, cache } = expand_all(left, env, cache)
|
||||
{ right, cache } = expand_all(right, env, cache)
|
||||
{ { left, meta, right }, cache }
|
||||
end
|
||||
|
||||
defp expand_all_until({ { left, right }, cache }, env) do
|
||||
{ left, cache } = expand_all(left, env, cache)
|
||||
{ right, cache } = expand_all(right, env, cache)
|
||||
{ { left, right }, cache }
|
||||
end
|
||||
|
||||
defp expand_all_until({ list, cache }, env) when is_list(list) do
|
||||
:lists.mapfoldl(expand_all(&1, env, &2), cache, list)
|
||||
end
|
||||
|
||||
defp expand_all_until({ other, cache }, _env) do
|
||||
{ other, cache }
|
||||
end
|
||||
|
||||
@doc """
|
||||
Recurs the quoted expression checking if all sub terms are
|
||||
safe (i.e. they represented data structured and don't actually
|
||||
|
||||
@@ -21,6 +21,7 @@ defmodule Macro.Env do
|
||||
* `macros` - a list of macros imported from each module
|
||||
* `context_modules` - a list of modules defined in the current context
|
||||
* `macro_aliases` - a list of aliases defined inside the current macro
|
||||
* `vars` - a list keeping all defined varaibles as { var, context }
|
||||
"""
|
||||
|
||||
@type name_arity :: { atom, non_neg_integer }
|
||||
@@ -32,14 +33,15 @@ defmodule Macro.Env do
|
||||
@type functions :: [{ module, [name_arity] }]
|
||||
@type macros :: [{ module, [name_arity] }]
|
||||
@type context_modules :: [module]
|
||||
@type vars :: [{ atom, atom }]
|
||||
|
||||
fields = [:module, :file, :line, :function, :aliases, :context, :requires,
|
||||
:functions, :macros, :context_modules, :macro_aliases]
|
||||
:functions, :macros, :context_modules, :macro_aliases, :vars]
|
||||
|
||||
types = quote do: [module: module, file: file, line: line,
|
||||
function: name_arity, aliases: aliases, requires: requires,
|
||||
functions: functions, macros: macros, context_modules: context_modules,
|
||||
macro_aliases: aliases]
|
||||
macro_aliases: aliases, vars: vars]
|
||||
|
||||
Record.deffunctions(fields, __MODULE__)
|
||||
Record.deftypes(fields, types, __MODULE__)
|
||||
|
||||
+52
-46
@@ -1,6 +1,4 @@
|
||||
defmodule Module do
|
||||
require :ets, as: ETS
|
||||
|
||||
defmacrop is_env(env) do
|
||||
quote do
|
||||
is_tuple(unquote(env)) and size(unquote(env)) > 1 and elem(unquote(env), 0) == Macro.Env
|
||||
@@ -192,7 +190,7 @@ defmodule Module do
|
||||
IO.puts "and guards"
|
||||
IO.inspect guards
|
||||
IO.puts "and body"
|
||||
IO.puts Macro.to_binary(body)
|
||||
IO.puts Macro.to_string(body)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -304,7 +302,7 @@ defmodule Module do
|
||||
"""
|
||||
def open?(module) do
|
||||
table = data_table_for(module)
|
||||
table == ETS.info(table, :name)
|
||||
table == :ets.info(table, :name)
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -492,12 +490,12 @@ defmodule Module do
|
||||
{ simplify_signature(x, line, acc), acc + 1 }
|
||||
end
|
||||
|
||||
case ETS.lookup(table, tuple) do
|
||||
case :ets.lookup(table, tuple) do
|
||||
[] ->
|
||||
ETS.insert(table, { tuple, line, kind, signature, doc })
|
||||
:ets.insert(table, { tuple, line, kind, signature, doc })
|
||||
:ok
|
||||
[{ tuple, line, _old_kind, old_sign, old_doc }] ->
|
||||
ETS.insert(table, {
|
||||
:ets.insert(table, {
|
||||
tuple,
|
||||
line,
|
||||
kind,
|
||||
@@ -577,7 +575,7 @@ defmodule Module do
|
||||
def defines?(module, tuple) when is_tuple(tuple) do
|
||||
assert_not_compiled!(:defines?, module)
|
||||
table = function_table_for(module)
|
||||
ETS.lookup(table, tuple) != []
|
||||
:ets.lookup(table, tuple) != []
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -597,7 +595,7 @@ defmodule Module do
|
||||
def defines?(module, tuple, kind) do
|
||||
assert_not_compiled!(:defines?, module)
|
||||
table = function_table_for(module)
|
||||
case ETS.lookup(table, tuple) do
|
||||
case :ets.lookup(table, tuple) do
|
||||
[{ _, ^kind, _, _, _, _, _ }] -> true
|
||||
_ -> false
|
||||
end
|
||||
@@ -617,7 +615,7 @@ defmodule Module do
|
||||
def definitions_in(module) do
|
||||
assert_not_compiled!(:definitions_in, module)
|
||||
table = function_table_for(module)
|
||||
lc { tuple, _, _, _, _, _, _ } inlist ETS.tab2list(table), do: tuple
|
||||
lc { tuple, _, _, _, _, _, _ } inlist :ets.tab2list(table), do: tuple
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -636,7 +634,7 @@ defmodule Module do
|
||||
def definitions_in(module, kind) do
|
||||
assert_not_compiled!(:definitions_in, module)
|
||||
table = function_table_for(module)
|
||||
lc { tuple, stored_kind, _, _, _, _, _ } inlist ETS.tab2list(table), stored_kind == kind, do: tuple
|
||||
lc { tuple, stored_kind, _, _, _, _, _ } inlist :ets.tab2list(table), stored_kind == kind, do: tuple
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -690,26 +688,39 @@ defmodule Module do
|
||||
assert_not_compiled!(:put_attribute, module)
|
||||
table = data_table_for(module)
|
||||
value = normalize_attribute(key, value)
|
||||
acc = ETS.lookup_element(table, :__acc_attributes, 2)
|
||||
acc = :ets.lookup_element(table, :__acc_attributes, 2)
|
||||
|
||||
new =
|
||||
if :lists.member(key, acc) do
|
||||
case ETS.lookup(table, key) do
|
||||
[{^key,old}] -> [value|old]
|
||||
case :ets.lookup(table, key) do
|
||||
[{^key, old}] -> [value|old]
|
||||
[] -> [value]
|
||||
end
|
||||
else
|
||||
value
|
||||
end
|
||||
|
||||
ETS.insert(table, { key, new })
|
||||
:ets.insert(table, { key, new })
|
||||
end
|
||||
|
||||
@doc """
|
||||
Gets the given attribute from a module. If the attribute
|
||||
was marked as accumulate with `Module.register_attribute`,
|
||||
was marked with `accumulate` with `Module.register_attribute`,
|
||||
a list is always returned.
|
||||
|
||||
The `@` macro compiles to a call to this function. For example,
|
||||
the following code:
|
||||
|
||||
@foo
|
||||
|
||||
Expands to:
|
||||
|
||||
Module.get_attribute(__MODULE__, :foo, true)
|
||||
|
||||
Notice the third argument is used to indicate if a warning
|
||||
should be emitted when the attribute was not previously defined.
|
||||
This is true for `@foo` attributes but false for direct calls.
|
||||
|
||||
## Examples
|
||||
|
||||
defmodule Foo do
|
||||
@@ -722,15 +733,22 @@ defmodule Module do
|
||||
end
|
||||
|
||||
"""
|
||||
def get_attribute(module, key) when is_atom(key) do
|
||||
def get_attribute(module, key, warn // false) when is_atom(key) do
|
||||
assert_not_compiled!(:get_attribute, module)
|
||||
table = data_table_for(module)
|
||||
|
||||
case ETS.lookup(table, key) do
|
||||
[{^key,old}] -> old
|
||||
case :ets.lookup(table, key) do
|
||||
[{^key, val}] -> val
|
||||
[] ->
|
||||
acc = ETS.lookup_element(table, :__acc_attributes, 2)
|
||||
if :lists.member(key, acc), do: [], else: nil
|
||||
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
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -748,7 +766,7 @@ defmodule Module do
|
||||
def delete_attribute(module, key) when is_atom(key) do
|
||||
assert_not_compiled!(:delete_attribute, module)
|
||||
table = data_table_for(module)
|
||||
ETS.delete(table, key)
|
||||
:ets.delete(table, key)
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -767,9 +785,7 @@ defmodule Module do
|
||||
* `:persist` - The attribute will be persisted in the Erlang
|
||||
Abstract Format. Useful when interfacing with Erlang libraries.
|
||||
|
||||
By default, both options are true. Which means that registering
|
||||
an attribute without passing any options will revert the attribute
|
||||
behavior to exactly the same expected in :
|
||||
By default, both options are false.
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -784,18 +800,18 @@ defmodule Module do
|
||||
end
|
||||
|
||||
"""
|
||||
def register_attribute(module, new, opts // []) do
|
||||
def register_attribute(module, new, opts) when is_atom(new) do
|
||||
assert_not_compiled!(:register_attribute, module)
|
||||
table = data_table_for(module)
|
||||
|
||||
if Keyword.get(opts, :persist, true) do
|
||||
old = ETS.lookup_element(table, :__persisted_attributes, 2)
|
||||
ETS.insert(table, { :__persisted_attributes, [new|old] })
|
||||
if Keyword.get(opts, :persist) do
|
||||
old = :ets.lookup_element(table, :__persisted_attributes, 2)
|
||||
:ets.insert(table, { :__persisted_attributes, [new|old] })
|
||||
end
|
||||
|
||||
if Keyword.get(opts, :accumulate, true) do
|
||||
old = ETS.lookup_element(table, :__acc_attributes, 2)
|
||||
ETS.insert(table, { :__acc_attributes, [new|old] })
|
||||
if Keyword.get(opts, :accumulate) do
|
||||
old = :ets.lookup_element(table, :__acc_attributes, 2)
|
||||
:ets.insert(table, { :__acc_attributes, [new|old] })
|
||||
end
|
||||
end
|
||||
|
||||
@@ -812,16 +828,6 @@ defmodule Module do
|
||||
tl(String.split(Binary.Chars.to_binary(module), "."))
|
||||
end
|
||||
|
||||
@doc """
|
||||
Convert a module name to binary without the Elixir prefix.
|
||||
"""
|
||||
def to_binary(Elixir), do: "Elixir"
|
||||
|
||||
def to_binary(module) do
|
||||
"Elixir." <> rest = Binary.Chars.to_binary(module)
|
||||
rest
|
||||
end
|
||||
|
||||
@doc false
|
||||
# Used internally to compile documentation. This function
|
||||
# is private and must be used only internally.
|
||||
@@ -836,7 +842,7 @@ defmodule Module do
|
||||
:ok ->
|
||||
:ok
|
||||
{ :error, :private_doc } ->
|
||||
IO.puts "#{env.file}:#{line} function #{name}/#{arity} is private, @doc's are always discarded for private functions"
|
||||
:elixir_errors.warn "#{env.file}:#{line} function #{name}/#{arity} is private, @doc's are always discarded for private functions\n"
|
||||
end
|
||||
|
||||
delete_attribute(module, :doc)
|
||||
@@ -850,12 +856,12 @@ defmodule Module do
|
||||
table = data_table_for(module)
|
||||
|
||||
new =
|
||||
case ETS.lookup(table, key) do
|
||||
[{^key,old}] -> [value|old]
|
||||
case :ets.lookup(table, key) do
|
||||
[{^key, old}] -> [value|old]
|
||||
[] -> [value]
|
||||
end
|
||||
|
||||
ETS.insert(table, { key, new })
|
||||
:ets.insert(table, { key, new })
|
||||
end
|
||||
|
||||
## Helpers
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
defmodule OptionParser do
|
||||
@moduledoc """
|
||||
This module contains functions to parse command line arguments.
|
||||
"""
|
||||
|
||||
@doc """
|
||||
Parses the argv and returns one tuple with parsed options
|
||||
and the arguments.
|
||||
Parses `argv` and returns a tuple with parsed options
|
||||
and arguments.
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -16,26 +20,28 @@ defmodule OptionParser do
|
||||
|
||||
## Aliases
|
||||
|
||||
A set of aliases can be given as second argument:
|
||||
A set of aliases can be given as the second argument:
|
||||
|
||||
iex> OptionParser.parse(["-d"], aliases: [d: :debug])
|
||||
{ [debug: true], [] }
|
||||
|
||||
## Switches
|
||||
|
||||
Extra information about switches can be given as argument too.
|
||||
This is useful in order to say a switch must behave as a boolean
|
||||
Extra information about switches can be given as arguments, too.
|
||||
This is useful when a switch must behave as a boolean
|
||||
or if duplicated switches should be kept, overriden or accumulated.
|
||||
|
||||
The following types are supported:
|
||||
|
||||
* `:boolean` - Mark the given switch as boolean. Boolean switches
|
||||
never consumes the following value unless it is
|
||||
true or false;
|
||||
* `:boolean` - Marks the given switch as a boolean. Boolean switches
|
||||
never consume the following value unless it is
|
||||
`true` or `false`;
|
||||
* `:integer` - Parses the switch as an integer;
|
||||
* `:float` - Parses the switch as a float;
|
||||
|
||||
The following extra options are supported:
|
||||
|
||||
* `:keep` - Keep duplicated items in the list instead of overriding;
|
||||
* `:keep` - Keeps duplicated items in the list instead of overriding;
|
||||
|
||||
Examples:
|
||||
|
||||
@@ -59,16 +65,18 @@ defmodule OptionParser do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Similar to parse but only parses the head of the argv.
|
||||
I.e. as soon as it finds a non switch, it stops parsing.
|
||||
Similar to `parse/2` but only parses the head of `argv`;
|
||||
as soon as it finds a non-switch, it stops parsing.
|
||||
|
||||
Check `parse/2` for more info.
|
||||
See `parse/2` for more information.
|
||||
|
||||
## Example
|
||||
|
||||
iex> OptionParser.parse_head(["--source", "lib", "test/enum_test.exs", "--verbose"])
|
||||
{ [source: "lib"], ["test/enum_test.exs", "--verbose"] }
|
||||
|
||||
iex> OptionParser.parse_head(["--verbose", "--source", "lib", "test/enum_test.exs", "--unlock"])
|
||||
{[verbose: true, source: "lib"], ["test/enum_test.exs", "--unlock"]}
|
||||
"""
|
||||
def parse_head(argv, opts // []) when is_list(argv) and is_list(opts) do
|
||||
parse(argv, opts, false)
|
||||
@@ -127,10 +135,21 @@ defmodule OptionParser do
|
||||
end
|
||||
|
||||
defp store_option(dict, option, value, kind) do
|
||||
if is_switch_a? :keep, kind do
|
||||
[{ option, value }|dict]
|
||||
else
|
||||
[{ option, value }|Keyword.delete(dict, option)]
|
||||
case kind do
|
||||
:keep ->
|
||||
[{ option, value }|dict]
|
||||
:integer ->
|
||||
case String.to_integer(value) do
|
||||
{ value, "" } -> [{ option, value }|Keyword.delete(dict, option)]
|
||||
_ -> dict
|
||||
end
|
||||
:float ->
|
||||
case String.to_float(value) do
|
||||
{ value, "" } -> [{ option, value }|Keyword.delete(dict, option)]
|
||||
_ -> dict
|
||||
end
|
||||
_ ->
|
||||
[{ option, value }|Keyword.delete(dict, option)]
|
||||
end
|
||||
end
|
||||
|
||||
@@ -160,7 +179,7 @@ defmodule OptionParser do
|
||||
end
|
||||
|
||||
defp to_underscore(option) do
|
||||
bc <<c>> inbits option, do: << if c == ?-, do: ?_, else: c >>
|
||||
bc <<c>> inbits option, do: << if(c == ?-, do: ?_, else: c) >>
|
||||
end
|
||||
|
||||
defp is_no?("no-" <> _), do: true
|
||||
|
||||
+11
-11
@@ -4,7 +4,7 @@ defmodule Path do
|
||||
retrieving file system paths.
|
||||
|
||||
The functions in this module may receive a char list or
|
||||
a binary as argument and will return a value of the same
|
||||
a binary as an argument and will return a value of the same
|
||||
type.
|
||||
|
||||
The majority of the functions in this module do not
|
||||
@@ -18,7 +18,7 @@ defmodule Path do
|
||||
@type r :: char_list | binary
|
||||
|
||||
@doc """
|
||||
Converts the given path to an absolute one. Differently from
|
||||
Converts the given path to an absolute one. Unlike
|
||||
`Path.expand/1`, no attempt is made to resolve `..`, `.` or `~`.
|
||||
|
||||
## Unix examples
|
||||
@@ -45,7 +45,7 @@ defmodule Path do
|
||||
Builds a path from `relative_to` to `path`. If `path` is already
|
||||
an absolute path, `relative_to` is ignored. See also `Path.relative/2`.
|
||||
|
||||
Differently from `Path.expand/2`, no attempt is made to
|
||||
Unlike `Path.expand/2`, no attempt is made to
|
||||
resolve `..`, `.` or `~`.
|
||||
|
||||
## Examples
|
||||
@@ -67,7 +67,7 @@ defmodule Path do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Path.expand("/foo/bar/../bar")
|
||||
Path.expand("/foo/bar/../bar")
|
||||
"/foo/bar"
|
||||
|
||||
"""
|
||||
@@ -91,9 +91,9 @@ defmodule Path do
|
||||
Path.expand("foo/bar/../bar", "baz")
|
||||
#=> "/quux/baz/foo/bar"
|
||||
|
||||
iex> Path.expand("foo/bar/../bar", "/baz")
|
||||
Path.expand("foo/bar/../bar", "/baz")
|
||||
"/baz/foo/bar"
|
||||
iex> Path.expand("/foo/bar/../bar", "/baz")
|
||||
Path.expand("/foo/bar/../bar", "/baz")
|
||||
"/foo/bar"
|
||||
|
||||
"""
|
||||
@@ -390,11 +390,11 @@ defmodule Path do
|
||||
|
||||
defp do_join(<<uc_letter, ?:, rest :: binary>>, relativename, [], :win32) when uc_letter in ?A..?Z, do:
|
||||
do_join(rest, relativename, [?:, uc_letter+?a-?A], :win32)
|
||||
defp do_join(<<?\\,rest :: binary>>, relativename, result, :win32), do:
|
||||
do_join(<<?/,rest :: binary>>, relativename, result, :win32)
|
||||
defp do_join(<<?/,rest :: binary>>, relativename, [?., ?/|result], os_type), do:
|
||||
defp do_join(<<?\\, rest :: binary>>, relativename, result, :win32), do:
|
||||
do_join(<<?/, rest :: binary>>, relativename, result, :win32)
|
||||
defp do_join(<<?/, rest :: binary>>, relativename, [?., ?/|result], os_type), do:
|
||||
do_join(rest, relativename, [?/|result], os_type)
|
||||
defp do_join(<<?/,rest :: binary>>, relativename, [?/|result], os_type), do:
|
||||
defp do_join(<<?/, rest :: binary>>, relativename, [?/|result], os_type), do:
|
||||
do_join(rest, relativename, [?/|result], os_type)
|
||||
defp do_join(<<>>, <<>>, result, os_type), do:
|
||||
list_to_binary(maybe_remove_dirsep(result, os_type))
|
||||
@@ -404,7 +404,7 @@ defmodule Path do
|
||||
do_join(relativename, <<>>, [?/|result], os_type)
|
||||
defp do_join(<<>>, relativename, result, os_type), do:
|
||||
do_join(relativename, <<>>, [?/|result], os_type)
|
||||
defp do_join(<<char,rest :: binary>>, relativename, result, os_type) when is_integer(char), do:
|
||||
defp do_join(<<char, rest :: binary>>, relativename, result, os_type) when is_integer(char), do:
|
||||
do_join(rest, relativename, [char|result], os_type)
|
||||
|
||||
defp maybe_remove_dirsep([?/, ?:, letter], :win32), do:
|
||||
|
||||
@@ -76,9 +76,9 @@ defmodule Process do
|
||||
@doc """
|
||||
Sends an exit signal with the given reason to the pid.
|
||||
|
||||
The following behavior apply if reason is any term except `:normal` or `:kill`:
|
||||
The following behavior applies if reason is any term except `:normal` or `:kill`:
|
||||
|
||||
1) If pid is not trapping exits, pid itself will exist with the given reason;
|
||||
1) If pid is not trapping exits, pid will exit with the given reason;
|
||||
|
||||
2) If pid is trapping exits, the exit signal is transformed into a message
|
||||
{'EXIT', from, reason} and delivered to the message queue of pid;
|
||||
@@ -101,6 +101,14 @@ defmodule Process do
|
||||
:erlang.exit(pid, reason)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns the pid (process identifier) of the calling process.
|
||||
"""
|
||||
@spec self() :: pid
|
||||
def self() do
|
||||
:erlang.self()
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns the pid of a new process started by the application of `fun`.
|
||||
It behaves exactly the same as `Kernel.spawn/1`.
|
||||
|
||||
+21
-17
@@ -34,6 +34,8 @@ defmodule Protocol do
|
||||
|
||||
# Set up a clear slate to store defined functions
|
||||
@functions []
|
||||
@only nil
|
||||
@except nil
|
||||
|
||||
# Invoke the user given block
|
||||
unquote(block)
|
||||
@@ -81,14 +83,14 @@ defmodule Protocol do
|
||||
def assert_protocol(module) do
|
||||
case Code.ensure_compiled(module) do
|
||||
{ :module, ^module } -> nil
|
||||
_ -> raise ArgumentError, message: "#{module} is not loaded"
|
||||
_ -> raise ArgumentError, message: "#{inspect module} is not loaded"
|
||||
end
|
||||
|
||||
try do
|
||||
module.__protocol__(:name)
|
||||
rescue
|
||||
UndefinedFunctionError ->
|
||||
raise ArgumentError, message: "#{module} is not a protocol"
|
||||
raise ArgumentError, message: "#{inspect module} is not a protocol"
|
||||
end
|
||||
end
|
||||
|
||||
@@ -236,13 +238,13 @@ defmodule Protocol do
|
||||
|
||||
# Returns a quoted expression that allows to check
|
||||
# if the first item in the tuple is a built-in or not.
|
||||
defp is_builtin?([{h,_,_}]) do
|
||||
defp is_builtin?([{h, _, _}]) do
|
||||
quote do
|
||||
first == unquote(h)
|
||||
end
|
||||
end
|
||||
|
||||
defp is_builtin?([{h,_,_}|t]) do
|
||||
defp is_builtin?([{h, _, _}|t]) do
|
||||
quote do
|
||||
first == unquote(h) or unquote(is_builtin?(t))
|
||||
end
|
||||
@@ -349,36 +351,38 @@ defmodule Protocol.DSL do
|
||||
end
|
||||
|
||||
defp default_clause(name, args) do
|
||||
quote do: { [other], apply(other, unquote(name), [unquote_splicing(args)]) }
|
||||
{ [quote do: other],
|
||||
[],
|
||||
quote(do: apply(other, unquote(name), [unquote_splicing(args)])) }
|
||||
end
|
||||
|
||||
defp nil_clause() do
|
||||
quote do
|
||||
{ [nil], raise(Protocol.UndefinedError, protocol: __MODULE__, structure: xA) }
|
||||
end
|
||||
{ [nil],
|
||||
[],
|
||||
quote(do: raise(Protocol.UndefinedError, protocol: __MODULE__, structure: xA)) }
|
||||
end
|
||||
|
||||
defp record_clause(name, args, nil) do
|
||||
quote do
|
||||
{ [__MODULE__.Record],
|
||||
Module.concat(__MODULE__, :erlang.element(1, xA)).unquote(name)(unquote_splicing(args)) }
|
||||
end
|
||||
{ [quote(do: __MODULE__.Record)],
|
||||
[],
|
||||
quote(do: Module.concat(__MODULE__, :erlang.element(1, xA)).unquote(name)(unquote_splicing(args))) }
|
||||
end
|
||||
|
||||
defp record_clause(name, args, fallback) do
|
||||
arity = length(args)
|
||||
|
||||
quote do
|
||||
{ [__MODULE__.Record],
|
||||
(target = Module.concat(__MODULE__, :erlang.element(1, xA))
|
||||
{ [quote(do: __MODULE__.Record)],
|
||||
[],
|
||||
quote do
|
||||
target = Module.concat(__MODULE__, :erlang.element(1, xA))
|
||||
try do
|
||||
target.unquote(name)(unquote_splicing(args))
|
||||
catch
|
||||
:error, :undef, [[{ ^target, name, args, _ }|_]|_] when
|
||||
name == unquote(name) and length(args) == unquote(arity) ->
|
||||
apply unquote(fallback), name, [unquote_splicing(args)]
|
||||
end) }
|
||||
end
|
||||
end
|
||||
end }
|
||||
end
|
||||
|
||||
defmacro def(expression) do
|
||||
|
||||
@@ -5,6 +5,9 @@ defrecord Range, [:first, :last] do
|
||||
end
|
||||
|
||||
defprotocol Range.Iterator do
|
||||
@doc """
|
||||
Reduces the range based on the type of the first argument.
|
||||
"""
|
||||
def reduce(first, range, acc, fun)
|
||||
|
||||
@doc """
|
||||
@@ -61,10 +64,8 @@ defimpl Range.Iterator, for: Number do
|
||||
end
|
||||
end
|
||||
|
||||
defimpl Binary.Inspect, for: Range do
|
||||
import Kernel, except: [inspect: 2]
|
||||
|
||||
defimpl Inspect, for: Range do
|
||||
def inspect(Range[first: first, last: last], opts) do
|
||||
Kernel.inspect(first, opts) <> ".." <> Kernel.inspect(last, opts)
|
||||
Inspect.Algebra.concat [Kernel.inspect(first, opts), "..", Kernel.inspect(last, opts)]
|
||||
end
|
||||
end
|
||||
|
||||
+320
-224
@@ -1,155 +1,181 @@
|
||||
defmodule Record do
|
||||
@moduledoc %B"""
|
||||
Functions to define Elixir records
|
||||
Convenience functions for working with Records.
|
||||
|
||||
A record is a tagged tuple which contains one or more elements and the first
|
||||
element is a module. One creates a record by calling `defrecord` or
|
||||
`defrecordp` which are documented in `Kernel`.
|
||||
A record is a tagged tuple which contains one or more elements
|
||||
where the first element is an atom. We can manually create a record
|
||||
by simply defining such a tuple:
|
||||
|
||||
## Examples
|
||||
iex> record = { User, "josé", 25 }
|
||||
iex> is_record(record, User)
|
||||
true
|
||||
|
||||
defrecord FileInfo, atime: nil, accesses: 0
|
||||
However, manually constructing tuples can be quite error prone.
|
||||
In case we need to add a new field to our User, it would require
|
||||
us to carefully change all places where the tuple is used.
|
||||
Furthermore, as more and more items are added to the tuple, they
|
||||
lose semantic value.
|
||||
|
||||
The line above will define a module named `FileInfo` which
|
||||
contains a function named `new` that returns a new record
|
||||
and other functions to read and set the values in the
|
||||
record:
|
||||
This module solves these problems by allowing us to name each
|
||||
element and encapsulate the generation and manipulation of
|
||||
such tuples. Much of the functionality provided by this module happens
|
||||
at compilation time, meaning they don't add any runtime overhead while
|
||||
considerably improving the quality of our code.
|
||||
|
||||
file_info = FileInfo.new(atime: now())
|
||||
file_info.atime #=> Returns the value of atime
|
||||
file_info.atime(now()) #=> Updates the value of atime
|
||||
For these reasons, Records are frequently used in Elixir and
|
||||
are also very useful when combined with Protocols. This module
|
||||
provides different mechanisms for working with records and we
|
||||
are going to explore them in the following sections.
|
||||
|
||||
# Update multiple attributes at once:
|
||||
file_info.update(atime: now(), accesses: 1)
|
||||
## defrecordp
|
||||
|
||||
# Obtain the keywords representation of a record:
|
||||
file_info.to_keywords #=> [accesses: 1, atime: {1370,7171,911705}]
|
||||
The simplest way of working with records is via `defrecordp`:
|
||||
|
||||
|
||||
A record is simply a tuple where the first element is the record
|
||||
module name. We can get the record raw representation as follow:
|
||||
|
||||
inspect FileInfo.new, raw: true
|
||||
#=> { FileInfo, nil, nil }
|
||||
|
||||
Besides defining readers and writers for each attribute, Elixir also
|
||||
defines an `update_#{attribute}` function to update the value. Such
|
||||
functions expect a function as argument that receives the current
|
||||
value and must return the new one. For example, every time the file
|
||||
is accessed, the accesses counter can be incremented with:
|
||||
|
||||
file_info.update_accesses(fn(old) -> old + 1 end)
|
||||
|
||||
Which can be also written as:
|
||||
|
||||
file_info.update_accesses(&1 + 1)
|
||||
|
||||
## Access syntax
|
||||
|
||||
Records in Elixir can be expanded at compilation time to provide
|
||||
pattern matching and faster operations. For example, the clause
|
||||
below will only match if a `FileInfo` is given and the number of
|
||||
accesses is zero:
|
||||
|
||||
def enforce_no_access(FileInfo[accesses: 0]), do: :ok
|
||||
|
||||
The clause above will expand to:
|
||||
|
||||
def enforce_no_access({ FileInfo, _, 0 }), do: :ok
|
||||
|
||||
The downside of using such syntax is that, every time the record
|
||||
changes, your code now needs to be recompiled (which is usually
|
||||
not a concern since Elixir build tools by default recompiles the
|
||||
whole project whenever there is a change).
|
||||
|
||||
Finally, keep in mind that Elixir triggers some optimizations whenever
|
||||
the access syntax is used. For example:
|
||||
|
||||
def no_access?(FileInfo[] = file_info) do
|
||||
file_info.accesses == 0
|
||||
defmodule User do
|
||||
defrecordp :user, name: "José", age: 25
|
||||
end
|
||||
|
||||
Is translated to:
|
||||
In the example above, `defrecordp` is going to generate a set of
|
||||
macros named `user` that allows us to create, update and match
|
||||
on a record. Our record is going to have two fields, a `name` with
|
||||
default value of "José" and `age` of 25.
|
||||
|
||||
def no_access?({ FileInfo, _, _ } = file_info) do
|
||||
elem(file_info, 1) == 0
|
||||
Let's see some examples:
|
||||
|
||||
# To create records
|
||||
user() #=> { :user, "José", 25 }
|
||||
user(age: 26) #=> { :user, "José", 26 }
|
||||
|
||||
By using the `user` macro, we no longer need to explicitly create
|
||||
a tuple with all elements. It also allows us to create and modify
|
||||
values by name:
|
||||
|
||||
# Create a new record
|
||||
sample_user = user()
|
||||
|
||||
# And now change its age to 26
|
||||
user(sample_user, age: 26)
|
||||
|
||||
Since `user` is a macro, all the work happens at compilation time.
|
||||
This means all operations, like changing the `age` above, work
|
||||
as a simple tuple operation at runtime:
|
||||
|
||||
# This update operation...
|
||||
user(sample_user, age: 26)
|
||||
|
||||
# Literally translates to this one:
|
||||
set_elem(sample_user, 2, 26)
|
||||
|
||||
For this reason, the following operation is not allowed as all
|
||||
values need to be explicit:
|
||||
|
||||
new_values = [age: 26]
|
||||
user(sample_user, new_values)
|
||||
|
||||
As the name says, `defrecordp` is useful when you don't want to
|
||||
expose the record definition. The `user` macro used above, for
|
||||
example, is only available inside the `User` module and nowhere else.
|
||||
You can find more information in `Kernel.defrecordp/2` docs.
|
||||
|
||||
In general, though, records are used as part of a module's public
|
||||
interface. For such use cases, Elixir provides record modules.
|
||||
|
||||
## defrecord
|
||||
|
||||
By using `defrecord`, a developer can make a Record definition
|
||||
available everywhere within Elixir code. Let's see an example:
|
||||
|
||||
defrecord User, name: "José", age: 25
|
||||
|
||||
Notice that, unlike `defrecordp`, `defrecord` expects an
|
||||
alias as the first argument. This is because `defrecord` is going
|
||||
to create a module named `User` with all the relevant metadata.
|
||||
This module can then be imported and we can manipulate the user
|
||||
as with `defrecordp`:
|
||||
|
||||
Record.import User, as: :user
|
||||
user() #=> User[name: "José", age: 25]
|
||||
user(age: 26) #=> User[name: "José", age: 26]
|
||||
|
||||
Notice that now, since the record definition is accessible, Elixir
|
||||
shows the record nicely formatted, no longer as a simple tuple. We
|
||||
can get the raw formatting by passing `raw: true` to `inspect`:
|
||||
|
||||
inspect user(), raw: true
|
||||
{ User, "José", 25 }
|
||||
|
||||
Since working with external records is common, Elixir allows
|
||||
developers to skip importing the record altogether in favor
|
||||
of a `Module[args]` syntax:
|
||||
|
||||
# Skipping a field uses its default value
|
||||
User[] #=> User[name: "José", age: 25]
|
||||
User[age: 26] #=> User[name: "José", age: 26]
|
||||
|
||||
The macro name is replaced by the module name and the parenthesis
|
||||
are replaced by brackets. When the shortcut syntax is used, there
|
||||
is no need to import the record.
|
||||
|
||||
Before we sum up the differences between `defrecord` and
|
||||
`defrecordp`, there is one last functionality introduced by
|
||||
`defrecord` that we need to discuss.
|
||||
|
||||
## Runtime access
|
||||
|
||||
All the functionality discussed above happens at compilation time.
|
||||
This means that both `user(user, age: 26)` and `User[user, age: 26]`
|
||||
will be transformed into a simple tuple operation at compile time.
|
||||
|
||||
However, there are some situations where we want to set or update
|
||||
fields dynamically. `defrecord` (and `defrecord` only) supports
|
||||
it out of the box:
|
||||
|
||||
defrecord User, name: "José", age: 25
|
||||
|
||||
opts = [name: "Hello"]
|
||||
user = User.new(opts)
|
||||
#=> User[name: "Hello", age: 25]
|
||||
user.update(age: 26)
|
||||
#=> User[name: "Hello", age: 26]
|
||||
|
||||
All the calls above happen at runtime. It gives Elixir
|
||||
records flexibility at the cost of performance since
|
||||
there is more work happening at runtime.
|
||||
|
||||
To sum up, `defrecordp` should be used when you don't want
|
||||
to expose the record information while `defrecord` should be used
|
||||
whenever you want to share a record within your code or with other
|
||||
libraries or whenever you need to dynamically set or update fields.
|
||||
|
||||
You can learn more about records in the `Kernel.defrecord/2` docs. Now
|
||||
let's discuss the usefulness of combining records with protocols.
|
||||
|
||||
## Protocols
|
||||
|
||||
Developers can extend existing protocols by creating their own
|
||||
records and implementing the desired protocols. For instance,
|
||||
imagine that you have created a new representation for storing
|
||||
date and time, represented by the year, the week of the year
|
||||
and the week day:
|
||||
|
||||
defrecord WeekDate, year: nil, week: nil, week_day: nil
|
||||
|
||||
Now we want this date to be represented as a string and this
|
||||
can be done by implementing the `Binary.Chars` protocol for
|
||||
our record:
|
||||
|
||||
defimpl Binary.Chars, for: WeekDate do
|
||||
def to_binary(WeekDate[year: year, week: week, week_day: day]) do
|
||||
"#{year}-W#{week}-#{day}"
|
||||
end
|
||||
end
|
||||
|
||||
Which provides faster get and set times for record operations.
|
||||
Now we can explicitly convert our WeekDate:
|
||||
|
||||
## Runtime introspection
|
||||
to_binary WeekDate[year: 2013, week: 26, week_day: 4]
|
||||
"2013-W26-4"
|
||||
|
||||
At runtime, developers can use `__record__` to get information
|
||||
about the given record:
|
||||
|
||||
FileInfo.__record__(:name)
|
||||
#=> FileInfo
|
||||
|
||||
FileInfo.__record__(:fields)
|
||||
#=> [atime: nil, accesses: 0]
|
||||
|
||||
In order to quickly access the index of a field, one can use
|
||||
the `__index__` function:
|
||||
|
||||
FileInfo.__index__(:atime)
|
||||
#=> 0
|
||||
|
||||
FileInfo.__index__(:unknown)
|
||||
#=> nil
|
||||
|
||||
## Compile-time introspection
|
||||
|
||||
At the compile time, one can access following information about the record
|
||||
from within the record module:
|
||||
|
||||
* `@record_fields` — a keyword list of record fields with defaults
|
||||
* `@record_types` — a keyword list of record fields with types
|
||||
|
||||
defrecord Foo, bar: nil do
|
||||
record_type bar: nil | integer
|
||||
IO.inspect @record_fields
|
||||
IO.inspect @record_types
|
||||
end
|
||||
|
||||
prints out
|
||||
|
||||
[bar: nil]
|
||||
[bar: {:|,[line: ...],[nil,{:integer,[line: ...],nil}]}]
|
||||
|
||||
where the last line is a quoted representation of
|
||||
|
||||
[bar: nil | integer]
|
||||
|
||||
## Documentation
|
||||
|
||||
By default records are not documented and have `@moduledoc` set to false.
|
||||
|
||||
## Types
|
||||
|
||||
Every record defines a type named `t` that can be accessed in typespecs.
|
||||
For example, assuming the `Config` record defined above, it could be used
|
||||
in typespecs as follow:
|
||||
|
||||
@spec handle_config(Config.t) :: boolean()
|
||||
|
||||
Inside the record definition, a developer can define his own types too:
|
||||
|
||||
defrecord Config, counter: 0, failures: [] do
|
||||
@type kind :: term
|
||||
record_type counter: integer, failures: [kind]
|
||||
end
|
||||
|
||||
When defining a type, all the fields not mentioned in the type are
|
||||
assumed to have type `term`.
|
||||
|
||||
## Importing records
|
||||
|
||||
It is also possible to import a public record (a record, defined using
|
||||
`defrecord`) as a set of private macros (as if it was defined using `defrecordp`):
|
||||
|
||||
Record.import Config, as: :config
|
||||
|
||||
See `Record.import/2` and `defrecordp/2` documentation for more information
|
||||
A protocol can be implemented for any record defined via `defrecord`.
|
||||
"""
|
||||
|
||||
@doc """
|
||||
@@ -166,68 +192,89 @@ defmodule Record do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Main entry point for records definition. It defines a module
|
||||
with the given `name` and the fields specified in `values`.
|
||||
This is invoked directly by `Kernel.defrecord`, so check it
|
||||
for more information and documentation.
|
||||
Imports a public record definition as a set of private macros,
|
||||
as defined by `Kernel.defrecordp/2`. This is useful when one
|
||||
desires to manipulate a record via a set of macros instead
|
||||
of the regular access syntax.
|
||||
|
||||
## Example
|
||||
|
||||
defmodule Test do
|
||||
Record.import File.Stat, as: :file_stat
|
||||
|
||||
def size(file_stat(size: size)), do: size
|
||||
end
|
||||
|
||||
"""
|
||||
def defrecord(name, values, opts) do
|
||||
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)
|
||||
record_check!(fields)
|
||||
|
||||
quote do
|
||||
unquoted_values = unquote(values)
|
||||
unquoted_fields = unquote(fields)
|
||||
|
||||
defmodule unquote(name) do
|
||||
@moduledoc false
|
||||
import Elixir.Record.DSL
|
||||
|
||||
@record_fields []
|
||||
@record_types []
|
||||
|
||||
# Reassign values to inner scope to
|
||||
# avoid conflicts in nested records
|
||||
values = unquoted_values
|
||||
|
||||
Elixir.Record.deffunctions(values, __ENV__)
|
||||
Elixir.Record.deffunctions(unquoted_fields, __ENV__)
|
||||
value = unquote(block)
|
||||
Elixir.Record.deftypes(values, @record_types, __ENV__)
|
||||
Elixir.Record.deftypes(@record_fields, @record_types, __ENV__)
|
||||
value
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Import public record definition as a set of private macros (as defined by defrecordp/2)
|
||||
defp record_check!([{ field, { :::, _, [_, _] }}|_]) when is_atom(field) do
|
||||
raise ArgumentError, message: "typespecs are not supported inlined with defrecord, " <>
|
||||
"please use record_type instead"
|
||||
end
|
||||
|
||||
## Usage
|
||||
defp record_check!([_|t]), do: record_check!(t)
|
||||
defp record_check!(_), do: :ok
|
||||
|
||||
Record.import Record.Module, as: macro_name
|
||||
@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
|
||||
|
||||
## Example
|
||||
|
||||
defmodule Test do
|
||||
Record.import File.Stat, as: :file_stat
|
||||
|
||||
def size(file_stat(size: size)), do: size
|
||||
end
|
||||
|
||||
"""
|
||||
defmacro import(module, as: name) do
|
||||
quote do
|
||||
Record.defmacros(unquote(name), unquote(module).__record__(:fields), __ENV__, unquote(module))
|
||||
Record.defmacros(unquote(name), unquote(fields), __ENV__, unquote(tag))
|
||||
|
||||
if unquote(def_type) do
|
||||
@typep unquote(type)() :: { unquote(tag), unquote_splicing(types) }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Main entry point for private records definition. It defines
|
||||
a set of macros with the given `name` and the fields specified
|
||||
in `values`. This is invoked directly by `Kernel.defrecordp`,
|
||||
so check it for more information and documentation.
|
||||
"""
|
||||
def defrecordp(name, fields) do
|
||||
quote do
|
||||
Record.defmacros(unquote(name), unquote(fields), __ENV__)
|
||||
end
|
||||
defp recordp_split([{ field, { :::, _, [default, type] }}|t], defaults, types, _) do
|
||||
recordp_split t, [{ field, default }|defaults], [type|types], true
|
||||
end
|
||||
|
||||
defp recordp_split([other|t], defaults, types, def_type) do
|
||||
recordp_split t, [other|defaults], [quote(do: term)|types], def_type
|
||||
end
|
||||
|
||||
defp recordp_split([], defaults, types, def_type) do
|
||||
{ :lists.reverse(defaults), :lists.reverse(types), def_type }
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -308,25 +355,23 @@ defmodule Record do
|
||||
{ key, Macro.escape(value) }
|
||||
end
|
||||
|
||||
tag = tag || name
|
||||
|
||||
contents = quote do
|
||||
defmacrop unquote(name)() do
|
||||
Record.access(unquote(tag) || __MODULE__, unquote(escaped), [], __CALLER__)
|
||||
Record.access(unquote(tag), unquote(escaped), [], __CALLER__)
|
||||
end
|
||||
|
||||
defmacrop unquote(name)(record) when is_tuple(record) do
|
||||
Record.to_keywords(unquote(tag) || __MODULE__, unquote(escaped), record)
|
||||
Record.to_keywords(unquote(tag), unquote(escaped), record)
|
||||
end
|
||||
|
||||
defmacrop unquote(name)(args) do
|
||||
Record.access(unquote(tag) || __MODULE__, unquote(escaped), args, __CALLER__)
|
||||
end
|
||||
|
||||
defmacrop unquote(name)(record, key) when is_atom(key) do
|
||||
Record.get(unquote(tag) || __MODULE__, unquote(escaped), record, key)
|
||||
Record.access(unquote(tag), unquote(escaped), args, __CALLER__)
|
||||
end
|
||||
|
||||
defmacrop unquote(name)(record, args) do
|
||||
Record.dispatch(unquote(tag) || __MODULE__, unquote(escaped), record, args, __CALLER__)
|
||||
Record.dispatch(unquote(tag), unquote(escaped), record, args, __CALLER__)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -368,7 +413,7 @@ defmodule Record do
|
||||
@doc false
|
||||
def access(atom, fields, keyword, caller) do
|
||||
unless is_keyword(keyword) do
|
||||
raise "expected contents inside brackets to be a Keyword"
|
||||
raise ArgumentError, message: "expected contents inside brackets to be a Keyword"
|
||||
end
|
||||
|
||||
in_match = caller.in_match?
|
||||
@@ -398,31 +443,56 @@ defmodule Record do
|
||||
quote do: { unquote_splicing([atom|match]) }
|
||||
_ ->
|
||||
keys = lc { key, _ } inlist remaining, do: key
|
||||
raise "record #{inspect atom} does not have the keys: #{inspect keys}"
|
||||
raise ArgumentError, message: "record #{inspect atom} does not have the keys: #{inspect keys}"
|
||||
end
|
||||
end
|
||||
|
||||
# Dispatch the call to either update or to_list depending on the args given.
|
||||
# Implements to_keywords macro defined by defmacros.
|
||||
# It returns a quoted expression that represents
|
||||
# converting record to keywords list.
|
||||
@doc false
|
||||
def to_keywords(_atom, fields, record) do
|
||||
{ var, extra } = cache_var(record)
|
||||
|
||||
keywords = Enum.map fields,
|
||||
fn { key, _default } ->
|
||||
index = find_index(fields, key, 0)
|
||||
quote do
|
||||
{ unquote(key), :erlang.element(unquote(index + 2), unquote(var)) }
|
||||
end
|
||||
end
|
||||
|
||||
quote do
|
||||
unquote_splicing(extra)
|
||||
unquote(keywords)
|
||||
end
|
||||
end
|
||||
|
||||
# Dispatch the call to either get, update or to_list depending on the args given.
|
||||
@doc false
|
||||
def dispatch(atom, fields, record, args, caller) do
|
||||
if is_keyword(args) do
|
||||
update(atom, fields, record, args, caller)
|
||||
else
|
||||
to_list(atom, fields, record, args)
|
||||
cond do
|
||||
is_atom(args) ->
|
||||
get(atom, fields, record, args)
|
||||
is_keyword(args) ->
|
||||
update(atom, fields, record, args, caller)
|
||||
is_list(args) ->
|
||||
to_list(atom, fields, record, args)
|
||||
true ->
|
||||
raise ArgumentError, message: "expected arguments to be a compile time atom, list or keywords"
|
||||
end
|
||||
end
|
||||
|
||||
# Implements the update macro defined by defmacros.
|
||||
# It returns a quoted expression that represents
|
||||
# the access given by the keywords.
|
||||
@doc false
|
||||
defp update(atom, fields, var, keyword, caller) do
|
||||
unless is_keyword(keyword) do
|
||||
raise "expected contents inside brackets to be a Keyword"
|
||||
raise ArgumentError, message: "expected arguments to be compile time keywords"
|
||||
end
|
||||
|
||||
if caller.in_match? do
|
||||
raise "cannot invoke update style macro inside match context"
|
||||
raise ArgumentError, message: "cannot invoke update style macro inside match context"
|
||||
end
|
||||
|
||||
Enum.reduce keyword, var, fn({ key, value }, acc) ->
|
||||
@@ -432,7 +502,7 @@ defmodule Record do
|
||||
:erlang.setelement(unquote(index + 2), unquote(acc), unquote(value))
|
||||
end
|
||||
else
|
||||
raise "record #{inspect atom} does not have the key: #{inspect key}"
|
||||
raise ArgumentError, message: "record #{inspect atom} does not have the key: #{inspect key}"
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -440,52 +510,58 @@ defmodule Record do
|
||||
# Implements the get macro defined by defmacros.
|
||||
# It returns a quoted expression that represents
|
||||
# getting the value of a given field.
|
||||
@doc false
|
||||
def get(atom, fields, var, key) do
|
||||
defp get(atom, fields, var, key) do
|
||||
index = find_index(fields, key, 0)
|
||||
if index do
|
||||
quote do
|
||||
:erlang.element(unquote(index + 2), unquote(var))
|
||||
end
|
||||
else
|
||||
raise "record #{inspect atom} does not have the key: #{inspect key}"
|
||||
raise ArgumentError, message: "record #{inspect atom} does not have the key: #{inspect key}"
|
||||
end
|
||||
end
|
||||
|
||||
# Implements to_keywords macro defined by defmacros.
|
||||
# It returns a quoted expression that represents
|
||||
# converting record to keywords list.
|
||||
@doc false
|
||||
def to_keywords(_atom, fields, record) do
|
||||
Enum.map fields,
|
||||
fn { key, _default } ->
|
||||
index = find_index(fields, key, 0)
|
||||
quote do
|
||||
{ unquote(key), :erlang.element(unquote(index + 2), unquote(record)) }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Implements to_list macro defined by defmacros.
|
||||
# It returns a quoted expression that represents
|
||||
# extracting given fields from record.
|
||||
@doc false
|
||||
defp to_list(atom, fields, record, keys) do
|
||||
Enum.map keys,
|
||||
unless is_list(fields) do
|
||||
raise ArgumentError, message: "expected arguments to be a compile time list"
|
||||
end
|
||||
|
||||
{ var, extra } = cache_var(record)
|
||||
|
||||
list = Enum.map keys,
|
||||
fn(key) ->
|
||||
index = find_index(fields, key, 0)
|
||||
if index do
|
||||
quote do: :erlang.element(unquote(index + 2), unquote(record))
|
||||
quote do: :erlang.element(unquote(index + 2), unquote(var))
|
||||
else
|
||||
raise "record #{inspect atom} does not have the key: #{inspect key}"
|
||||
raise ArgumentError, message: "record #{inspect atom} does not have the key: #{inspect key}"
|
||||
end
|
||||
end
|
||||
|
||||
quote do
|
||||
unquote_splicing(extra)
|
||||
unquote(list)
|
||||
end
|
||||
end
|
||||
|
||||
defp cache_var({ var, _, kind } = tuple) when is_atom(var) and is_atom(kind) do
|
||||
{ tuple, [] }
|
||||
end
|
||||
|
||||
defp cache_var(other) do
|
||||
quote do
|
||||
{ x, [x = unquote(other)] }
|
||||
end
|
||||
end
|
||||
|
||||
## Function generation
|
||||
|
||||
# Define __record__/1 and __record__/2 as reflection functions
|
||||
# that returns the record names and fields.
|
||||
# Define __record__/1, __record__/2, __record__/3 as reflection
|
||||
# functions that return the record names and fields.
|
||||
#
|
||||
# Note that fields are *not* keywords. They are in the same
|
||||
# order as given as parameter and reflects the order of the
|
||||
@@ -497,15 +573,31 @@ defmodule Record do
|
||||
#
|
||||
# FileInfo.__record__(:name) #=> FileInfo
|
||||
# FileInfo.__record__(:fields) #=> [atime: nil, mtime: nil]
|
||||
# FileInfo.__record__(:index, :atime) #=> 1
|
||||
# FileInfo.__record__(:index, :mtime) #=> 2
|
||||
#
|
||||
defp reflection(values) do
|
||||
quoted = lc { k, _ } inlist values do
|
||||
index = find_index(values, k, 0)
|
||||
quote do
|
||||
def __record__(:index, unquote(k)), do: unquote(index + 1)
|
||||
end
|
||||
end
|
||||
|
||||
quote do
|
||||
@doc false
|
||||
def __record__(kind, _), do: __record__(kind)
|
||||
unquote(quoted)
|
||||
|
||||
@doc false
|
||||
def __record__(:name), do: __MODULE__
|
||||
def __record__(:fields), do: unquote(values)
|
||||
def __record__(:index, _), do: nil
|
||||
@doc false
|
||||
def __record__(:index, arg, _), do: __record__(:index, arg)
|
||||
|
||||
@doc false
|
||||
def __record__(kind, _), do: __record__(kind)
|
||||
|
||||
@doc false
|
||||
def __record__(:name), do: __MODULE__
|
||||
def __record__(:fields), do: unquote(values)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -570,7 +662,11 @@ defmodule Record do
|
||||
index = find_index(values, k, 0)
|
||||
quote do
|
||||
@doc false
|
||||
def __index__(unquote(k)), do: unquote(index + 1)
|
||||
def __index__(unquote(k)) do
|
||||
IO.write "[WARNING] #{__MODULE__}.__index__/1 is deprecated, " <>
|
||||
"please use #{__MODULE__}.__record__(:index, key) instead\n#{Exception.format_stacktrace}"
|
||||
unquote(index + 1)
|
||||
end
|
||||
end
|
||||
end
|
||||
quote do
|
||||
@@ -726,8 +822,8 @@ defmodule Record do
|
||||
@spec to_keywords(t) :: options
|
||||
@spec update(options, t) :: t
|
||||
@spec __record__(:name) :: atom
|
||||
@spec __record__(:fields) :: [{atom,any}]
|
||||
@spec __index__(atom) :: non_neg_integer | nil
|
||||
@spec __record__(:fields) :: [{atom, any}]
|
||||
@spec __record__(:index, atom) :: non_neg_integer | nil
|
||||
end
|
||||
end
|
||||
|
||||
@@ -766,15 +862,15 @@ defmodule Record do
|
||||
defp convert_value(atom) when is_atom(atom), do: { atom, nil }
|
||||
|
||||
defp convert_value({ atom, other }) when is_atom(atom) and is_function(other), do:
|
||||
raise ArgumentError, message: "record field default value #{inspect atom} cannot be a function"
|
||||
raise(ArgumentError, message: "record field default value #{inspect atom} cannot be a function")
|
||||
|
||||
defp convert_value({ atom, other }) when is_atom(atom) and (is_reference(other) or is_pid(other) or is_port(other)), do:
|
||||
raise ArgumentError, message: "record field default value #{inspect atom} cannot be a reference, pid or port"
|
||||
raise(ArgumentError, message: "record field default value #{inspect atom} cannot be a reference, pid or port")
|
||||
|
||||
defp convert_value({ atom, _ } = tuple) when is_atom(atom), do: tuple
|
||||
|
||||
defp convert_value({ field, _ }), do:
|
||||
raise ArgumentError, message: "record field name has to be an atom, got #{inspect field}"
|
||||
raise(ArgumentError, message: "record field name has to be an atom, got #{inspect field}")
|
||||
|
||||
defp find_index([{ k, _ }|_], k, i), do: i
|
||||
defp find_index([{ _, _ }|t], k, i), do: find_index(t, k, i + 1)
|
||||
|
||||
@@ -29,17 +29,18 @@ defmodule Record.Extractor do
|
||||
|
||||
# Retrieve the record with the given name from the given file
|
||||
defp retrieve_record(name, file) do
|
||||
records = retrieve_from_file(file)
|
||||
form = read_file(file)
|
||||
records = retrieve_records(form)
|
||||
if record = List.keyfind(records, name, 0) do
|
||||
parse_record(record)
|
||||
parse_record(record, form)
|
||||
else
|
||||
raise ArgumentError, "no record #{name} found at #{to_binary(file)}"
|
||||
end
|
||||
end
|
||||
|
||||
# Parse the given file and retrieve all existent records.
|
||||
defp retrieve_from_file(file) do
|
||||
lc { :attribute, _, :record, record } inlist read_file(file), do: record
|
||||
defp retrieve_records(form) do
|
||||
lc { :attribute, _, :record, record } inlist form, do: record
|
||||
end
|
||||
|
||||
# Read a file and return its abstract syntax form that also
|
||||
@@ -57,12 +58,11 @@ defmodule Record.Extractor do
|
||||
# Parse a tuple with name and fields and returns a
|
||||
# list of tuples where the first element is the field
|
||||
# and the second is its default value.
|
||||
defp parse_record({ _name, fields }) do
|
||||
defp parse_record({ _name, fields }, form) do
|
||||
cons = List.foldr fields, { nil, 0 }, fn f, acc ->
|
||||
{ :cons, 0, parse_field(f), acc }
|
||||
end
|
||||
{ :value, list, _ } = :erl_eval.expr(cons, [])
|
||||
list
|
||||
eval_record(cons, form)
|
||||
end
|
||||
|
||||
defp parse_field({ :typed_record_field, record_field, _type }) do
|
||||
@@ -76,4 +76,16 @@ defmodule Record.Extractor do
|
||||
defp parse_field({ :record_field, _, key, value }) do
|
||||
{ :tuple, 0, [key, value] }
|
||||
end
|
||||
|
||||
defp eval_record(cons, form) do
|
||||
form = form ++
|
||||
[ { :function, 0, :hello, 0, [
|
||||
{ :clause, 0, [], [], [ cons ] } ] } ]
|
||||
|
||||
{ :function, 0, :hello, 0, [
|
||||
{ :clause, 0, [], [], [ record_ast ] } ] } = :erl_expand_records.module(form, []) |> List.last
|
||||
|
||||
{ :value, record, _ } = :erl_eval.expr(record_ast, [])
|
||||
record
|
||||
end
|
||||
end
|
||||
+44
-45
@@ -2,7 +2,7 @@ defmodule Regex do
|
||||
@moduledoc %B"""
|
||||
Regular expressions for Elixir built on top of the re module
|
||||
in the Erlang Standard Library. More information can be found
|
||||
on re documentation: http://www.erlang.org/doc/man/re.html
|
||||
in the re documentation: http://www.erlang.org/doc/man/re.html
|
||||
|
||||
Regular expressions in Elixir can be created using Regex.compile!
|
||||
or using the special form with `%r`:
|
||||
@@ -10,32 +10,32 @@ defmodule Regex do
|
||||
# A simple regular expressions that matches foo anywhere in the string
|
||||
%r/foo/
|
||||
|
||||
# A regular expression with case insensitive options and handle unicode chars
|
||||
# A regular expression with case insensitive options and handling for unicode chars
|
||||
%r/foo/iu
|
||||
|
||||
The re module provides several options, the one available in Elixir, followed by
|
||||
The re module provides several options, the ones available in Elixir, followed by
|
||||
their shortcut in parenthesis, are:
|
||||
|
||||
* unicode (u) - enable unicode specific patterns like \p
|
||||
* caseless (i) - add case insensitivity
|
||||
* dotall (s) - causes dot to match newlines and also set newline to anycrlf.
|
||||
The new line setting can be overwritten by setting `(*CR)` or `(*LF)` or
|
||||
* `unicode` (u) - enable unicode specific patterns like \p
|
||||
* `caseless` (i) - add case insensitivity
|
||||
* `dotall` (s) - causes dot to match newlines and also set newline to anycrlf.
|
||||
The new line setting can be overridden by setting `(*CR)` or `(*LF)` or
|
||||
`(*CRLF)` or `(*ANY)` according to re documentation
|
||||
* multiline (m) - causes `^` and `$` to mark the beginning and end of each line.
|
||||
You need to use `\A` and `\z` to match the end or beginning of the string
|
||||
* extended (x) - whitespace characters are ignored except when escaped and
|
||||
* `multiline` (m) - causes `^` and `$` to mark the beginning and end of each line.
|
||||
Use `\A` and `\z` to match the end or beginning of the string
|
||||
* `extended` (x) - whitespace characters are ignored except when escaped and
|
||||
allow `#` to delimit comments
|
||||
* firstline (f) - forces the unanchored pattern to match before or at the first
|
||||
* `firstline` (f) - forces the unanchored pattern to match before or at the first
|
||||
newline, though the matched text may continue over the newline
|
||||
* ungreedy (r) - invert the "greediness" of the regexp
|
||||
* groups (g) - compile with info about groups available
|
||||
* `ungreedy` (r) - invert the "greediness" of the regexp
|
||||
* `groups` (g) - compile with info about groups available
|
||||
|
||||
The options not available are:
|
||||
|
||||
* anchored - not available, use `^` or `\A` instead
|
||||
* dollar_endonly - not available, use `\z` instead
|
||||
* no_auto_capture - not available, use `?:` instead
|
||||
* newline - not available, use `(*CR)` or `(*LF)` or `(*CRLF)` or `(*ANYCRLF)`
|
||||
* `anchored` - not available, use `^` or `\A` instead
|
||||
* `dollar_endonly` - not available, use `\z` instead
|
||||
* `no_auto_capture` - not available, use `?:` instead
|
||||
* `newline` - not available, use `(*CR)` or `(*LF)` or `(*CRLF)` or `(*ANYCRLF)`
|
||||
or `(*ANY)` at the beginning of the regexp according to the re documentation
|
||||
|
||||
Most of the functions in this module accept either a binary or a char list
|
||||
@@ -43,7 +43,7 @@ defmodule Regex do
|
||||
a binary, a char list will return a char list).
|
||||
"""
|
||||
|
||||
defrecordp :regex, [:re_pattern, :source, :options, :groups]
|
||||
defrecordp :regex, Regex, [:re_pattern, :source, :options, :groups]
|
||||
@type t :: { Regex, term, term, term, term }
|
||||
|
||||
defexception CompileError, message: "regex could not be compiled"
|
||||
@@ -79,20 +79,8 @@ defmodule Regex do
|
||||
end
|
||||
end
|
||||
|
||||
@doc false
|
||||
def index(regex(re_pattern: compiled), string) do
|
||||
IO.puts "Regex.index(re, string) is deprecated. Please use " <>
|
||||
"Regex.run(re, string, return: :index) instead."
|
||||
Exception.print_stacktrace
|
||||
|
||||
case :re.run(string, compiled, [{ :capture, :first, :index }]) do
|
||||
:nomatch -> nil
|
||||
{ :match, [{index,_}] } -> index
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns a boolean if there was a match or not.
|
||||
Returns a boolean indicating whether there was a match or not.
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -108,8 +96,8 @@ defmodule Regex do
|
||||
|
||||
@doc """
|
||||
Runs the regular expression against the given string.
|
||||
It returns a list with all matches, nil if no match ocurred, or []
|
||||
if it matched, /g was specified, but nothing was captured.
|
||||
It returns a list with all matches, `nil` if no match occurred, or `[]`
|
||||
if it matched, `/g` was specified, but nothing was captured.
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -140,14 +128,17 @@ defmodule Regex do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns the given captures as a list of tuples.
|
||||
Returns the given captures as a keyword list or `nil` if no captures are found.
|
||||
Requires the regex to be compiled with the groups option.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Regex.captures(%r/c(?<foo>d)/g, "abcd")
|
||||
[foo: "d"]
|
||||
|
||||
iex> Regex.captures(%r/a(?<foo>b)c(?<bar>d)/g, "abcd")
|
||||
[bar: "d", foo: "b"]
|
||||
iex> Regex.captures(%r/a(?<foo>b)c(?<bar>d)/g, "efgh")
|
||||
nil
|
||||
"""
|
||||
def captures(regex(groups: groups) = regex, string, options // []) do
|
||||
unless captures = Keyword.get(options, :capture) do
|
||||
@@ -159,7 +150,7 @@ defmodule Regex do
|
||||
options = Keyword.put(options, :capture, captures)
|
||||
end
|
||||
results = run(regex, string, options)
|
||||
if results, do: Enum.zip captures, results
|
||||
if results, do: Enum.zip(captures, results)
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -170,7 +161,7 @@ defmodule Regex do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns the regex source as binary.
|
||||
Returns the regex source as a binary.
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -196,11 +187,11 @@ defmodule Regex do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns list of named groups in regex.
|
||||
Returns a list of named groups in the regex.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Regex.groups(%r/(?<foo>foo)/g)
|
||||
iex> Regex.groups(%r/(?<foo>bar)/g)
|
||||
[:foo]
|
||||
|
||||
"""
|
||||
@@ -236,8 +227,16 @@ defmodule Regex do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Split the given target in the number of parts specified.
|
||||
If no ammount of parts is given, it defaults to :infinity.
|
||||
Split the given target into the number of parts specified.
|
||||
If no number of parts is given, it defaults to `:infinity`.
|
||||
|
||||
## Examples
|
||||
iex> Regex.split(%r/-/, "a-b-c")
|
||||
["a","b","c"]
|
||||
iex> Regex.split(%r/-/, "a-b-c", [parts: 2])
|
||||
["a","b-c"]
|
||||
iex> Regex.split(%r/-/, "abc")
|
||||
["abc"]
|
||||
"""
|
||||
|
||||
def split(regex, string, options // [])
|
||||
@@ -256,11 +255,11 @@ defmodule Regex do
|
||||
end
|
||||
|
||||
@doc %B"""
|
||||
Receives a regex, a binary and a replacement and returns a new
|
||||
Receives a regex, a binary and a replacement, returns a new
|
||||
binary where the all matches are replaced by replacement.
|
||||
|
||||
Inside the replacement, you can either give "&" to access the
|
||||
whole regular expression or \N, where N is in integer to access
|
||||
Inside the replacement, you can either give `&` to access the
|
||||
whole regular expression or `\N`, where `N` is in integer to access
|
||||
a specific matching parens. You can also set global to false
|
||||
if you want to replace just the first occurrence.
|
||||
|
||||
@@ -326,7 +325,7 @@ defmodule Regex do
|
||||
defp translate_options(<<?x, t :: binary>>), do: [:extended|translate_options(t)]
|
||||
defp translate_options(<<?f, t :: binary>>), do: [:firstline|translate_options(t)]
|
||||
defp translate_options(<<?r, t :: binary>>), do: [:ungreedy|translate_options(t)]
|
||||
defp translate_options(<<?s, t :: binary>>), do: [:dotall,{:newline,:anycrlf}|translate_options(t)]
|
||||
defp translate_options(<<?s, t :: binary>>), do: [:dotall, {:newline, :anycrlf}|translate_options(t)]
|
||||
defp translate_options(<<?m, t :: binary>>), do: [:multiline|translate_options(t)]
|
||||
defp translate_options(<<?g, t :: binary>>), do: [:groups|translate_options(t)]
|
||||
defp translate_options(<<>>), do: []
|
||||
|
||||
@@ -0,0 +1,233 @@
|
||||
defmodule Set do
|
||||
@moduledoc %B"""
|
||||
This module specifies the Set API expected to be
|
||||
implemented by different representations.
|
||||
|
||||
It also provides functions that redirect to the
|
||||
underlying Set, allowing a developer to work with
|
||||
different Set implementations using one API.
|
||||
|
||||
To create a new set, use the `new` functions defined
|
||||
by each set type:
|
||||
|
||||
HashSet.new #=> creates an empty HashSet
|
||||
|
||||
For simplicity's sake, in the examples below every time
|
||||
`new` is used, it implies one of the module-specific
|
||||
calls like above.
|
||||
"""
|
||||
|
||||
use Behaviour
|
||||
|
||||
@type value :: any
|
||||
@type values :: [ value ]
|
||||
@type t :: tuple
|
||||
|
||||
defcallback delete(t, value) :: t
|
||||
defcallback difference(t, t) :: t
|
||||
defcallback disjoint?(t, t) :: boolean
|
||||
defcallback empty(t) :: t
|
||||
defcallback equal?(t, t) :: boolean
|
||||
defcallback intersection(t, t) :: t
|
||||
defcallback member?(t, value) :: boolean
|
||||
defcallback put(t, value) :: t
|
||||
defcallback size(t) :: non_neg_integer
|
||||
defcallback subset?(t, t) :: boolean
|
||||
defcallback to_list(t) :: list()
|
||||
defcallback union(t, t) :: t
|
||||
|
||||
defmacrop target(set) do
|
||||
quote do
|
||||
if is_tuple(unquote(set)) do
|
||||
elem(unquote(set), 0)
|
||||
else
|
||||
unsupported_set(unquote(set))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Deletes `value` from `set`.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> s = HashSet.new([1, 2, 3])
|
||||
...> Set.delete(s, 4) |> HashSet.to_list
|
||||
[1, 2, 3]
|
||||
|
||||
iex> s = HashSet.new([1, 2, 3])
|
||||
...> Set.delete(s, 2) |> HashSet.to_list
|
||||
[1, 3]
|
||||
"""
|
||||
@spec delete(t, value) :: t
|
||||
def delete(set, value) do
|
||||
target(set).delete(set, value)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns a set that is `set1` without the members of `set2`.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Set.difference(HashSet.new([1,2]), HashSet.new([2,3,4])) |> HashSet.to_list
|
||||
[1]
|
||||
|
||||
"""
|
||||
@spec difference(t, t) :: t
|
||||
def difference(set1, set2) do
|
||||
target(set1).difference(set1, set2)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Checks if `set1` and `set2` have no members in common.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Set.disjoint?(HashSet.new([1, 2]), HashSet.new([3, 4]))
|
||||
true
|
||||
iex> Set.disjoint?(HashSet.new([1, 2]), HashSet.new([2, 3]))
|
||||
false
|
||||
|
||||
"""
|
||||
@spec disjoint?(t, t) :: boolean
|
||||
def disjoint?(set1, set2) do
|
||||
target(set1).disjoint?(set1, set2)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns an empty set of the same type as `set`.
|
||||
"""
|
||||
@spec empty(t) :: t
|
||||
def empty(set) do
|
||||
target(set).empty(set)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Checks if `set1` and `set2` are equal.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Set.equal?(HashSet.new([1, 2]), HashSet.new([2, 1, 1]))
|
||||
true
|
||||
|
||||
iex> Set.equal?(HashSet.new([1, 2]), HashSet.new([3, 4]))
|
||||
false
|
||||
|
||||
"""
|
||||
@spec equal?(t, t) :: boolean
|
||||
def equal?(set1, set2) do
|
||||
target(set1).equal?(set1, set2)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns a set containing only members in common between `set1` and `set2`.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Set.intersection(HashSet.new([1,2]), HashSet.new([2,3,4])) |> HashSet.to_list
|
||||
[2]
|
||||
|
||||
iex> Set.intersection(HashSet.new([1,2]), HashSet.new([3,4])) |> HashSet.to_list
|
||||
[]
|
||||
|
||||
"""
|
||||
@spec intersection(t, t) :: t
|
||||
def intersection(set1, set2) do
|
||||
target(set1).intersection(set1, set2)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Checks if `set` contains `value`.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Set.member?(HashSet.new([1, 2, 3]), 2)
|
||||
true
|
||||
|
||||
iex> Set.member?(HashSet.new([1, 2, 3]), 4)
|
||||
false
|
||||
|
||||
"""
|
||||
@spec member?(t, value) :: boolean
|
||||
def member?(set, value) do
|
||||
target(set).member?(set, value)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Inserts `value` into `set` if it does not already contain it.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Set.put(HashSet.new([1, 2, 3]), 3) |> Set.to_list
|
||||
[1, 2, 3]
|
||||
|
||||
iex> Set.put(HashSet.new([1, 2, 3]), 4) |> Set.to_list
|
||||
[1, 2, 3, 4]
|
||||
|
||||
"""
|
||||
@spec put(t, value) :: t
|
||||
def put(set, value) do
|
||||
target(set).put(set, value)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns the number of elements in `set`.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Set.size(HashSet.new([1, 2, 3]))
|
||||
3
|
||||
|
||||
"""
|
||||
@spec size(t) :: non_neg_integer
|
||||
def size(set) do
|
||||
target(set).size(set)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Checks if `set1`'s members are all contained in `set2`.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Set.subset?(HashSet.new([1, 2]), HashSet.new([1, 2, 3]))
|
||||
true
|
||||
iex> Set.subset?(HashSet.new([1, 2, 3]), HashSet.new([1, 2]))
|
||||
false
|
||||
"""
|
||||
@spec subset?(t, t) :: boolean
|
||||
def subset?(set1, set2) do
|
||||
target(set1).subset?(set1, set2)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Converts `set` to a list.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> HashSet.to_list(HashSet.new([1, 2, 3]))
|
||||
[1,2,3]
|
||||
|
||||
"""
|
||||
@spec to_list(t) :: list
|
||||
def to_list(set) do
|
||||
target(set).to_list(set)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns a set containing all members of `set1` and `set2`.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Set.union(HashSet.new([1,2]), HashSet.new([2,3,4])) |> HashSet.to_list
|
||||
[1,2,3,4]
|
||||
|
||||
"""
|
||||
@spec union(t, t) :: t
|
||||
def union(set1, set2) do
|
||||
target(set1).union(set1, set2)
|
||||
end
|
||||
|
||||
defp unsupported_set(set) do
|
||||
raise ArgumentError, message: "unsupported set: #{inspect set}"
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,387 @@
|
||||
defmodule Stream do
|
||||
@moduledoc """
|
||||
Module for creating and composing streams.
|
||||
|
||||
Streams are composable, lazy enumerables. Any enumerable that generates
|
||||
items one by one during enumeration is called a stream. For example,
|
||||
Elixir's `Range` is a stream:
|
||||
|
||||
iex> range = 1..5
|
||||
1..5
|
||||
iex> Enum.map range, &1 * 2
|
||||
[2,4,6,8,10]
|
||||
|
||||
In the example above, as we mapped over the range, the elements being
|
||||
enumerated were created one by one, during enumeration. The `Stream`
|
||||
module allows us to map the range, without triggering its enumeration:
|
||||
|
||||
iex> range = 1..3
|
||||
iex> stream = Stream.map(range, &1 * 2)
|
||||
iex> Enum.map(stream, &1 + 1)
|
||||
[3,5,7]
|
||||
|
||||
Notice we started with a range and then we created a stream that is
|
||||
meant to multiply each item in the range by 2. At this point, no
|
||||
computation was done yet. Just when `Enum.map/2` is called we
|
||||
enumerate over each item in the range, multiplying it by 2 and adding 1.
|
||||
We say the functions in `Stream` are *lazy* and the functions in `Enum`
|
||||
are *eager*.
|
||||
|
||||
Due to their laziness, streams are useful when working with large
|
||||
(or even infinite) collections. When chaining many operations with `Enum`,
|
||||
intermediate lists are created, while `Stream` creates a recipe of
|
||||
computations that are executed at a later moment. Let's see another
|
||||
example:
|
||||
|
||||
1..3 |>
|
||||
Enum.map(IO.inspect(&1)) |>
|
||||
Enum.map(&1 * 2) |>
|
||||
Enum.map(IO.inspect(&1))
|
||||
1
|
||||
2
|
||||
3
|
||||
2
|
||||
4
|
||||
6
|
||||
#=> [2,4,6]
|
||||
|
||||
Notice that we first printed each item in the list, then multiplied each
|
||||
element by 2 and finally printed each new value. In this example, the list
|
||||
was iterated three times. Let's see an example with streams:
|
||||
|
||||
stream = 1..3 |>
|
||||
Stream.map(IO.inspect(&1)) |>
|
||||
Stream.map(&1 * 2) |>
|
||||
Stream.map(IO.inspect(&1))
|
||||
Enum.to_list(stream)
|
||||
1
|
||||
2
|
||||
2
|
||||
4
|
||||
3
|
||||
6
|
||||
#=> [2,4,6]
|
||||
|
||||
Although the end result is the same, the order in which the items were
|
||||
printed changed! With streams, we print the first item and then print
|
||||
its double. In this example, the list was iterated just once!
|
||||
|
||||
That's what we meant when we first said that streams are composable,
|
||||
lazy enumerables. Notice we could call `Stream.map/2` multiple times,
|
||||
effectively composing the streams and they are lazy. The computations
|
||||
are performed only when you call a function from the `Enum` module.
|
||||
|
||||
## Creating Streams
|
||||
|
||||
There are many functions in Elixir's standard library that return
|
||||
streams, some examples are:
|
||||
|
||||
* `IO.stream/1` - Streams input lines, one by one;
|
||||
* `URI.query_decoder/1` - Decodes a query string, pair by pair;
|
||||
|
||||
This module also allows us to create streams from any enumerable:
|
||||
|
||||
iex> stream = Stream.map([1,2,3], &1 * 2)
|
||||
iex> Enum.map(stream, &1 + 1)
|
||||
[3,5,7]
|
||||
|
||||
By simply passing a list (which is an enumerable) as the first argument
|
||||
to `Stream.map/2`, we have automatically created a stream that will
|
||||
multiply the items in the list by 2 on enumeration.
|
||||
|
||||
This module also provides other functions for creating streams, such as
|
||||
`Stream.cycle/1`.
|
||||
"""
|
||||
|
||||
defrecord Lazy, [:enumerable, :fun, :acc]
|
||||
|
||||
defimpl Enumerable, for: Lazy do
|
||||
def reduce(Lazy[] = lazy, acc, fun) do
|
||||
do_reduce(lazy, acc, fun)
|
||||
end
|
||||
|
||||
def count(Lazy[] = lazy) do
|
||||
do_reduce(lazy, 0, fn _, acc -> acc + 1 end)
|
||||
end
|
||||
|
||||
def member?(Lazy[] = lazy, value) do
|
||||
do_reduce(lazy, false, fn(entry, _) ->
|
||||
if entry === value, do: throw({ :stream_lazy, true }), else: false
|
||||
end)
|
||||
end
|
||||
|
||||
defp do_reduce(Lazy[enumerable: enumerable, fun: f1, acc: nil], acc, fun) do
|
||||
do_reduce(enumerable, acc, f1.(fun))
|
||||
end
|
||||
|
||||
defp do_reduce(Lazy[enumerable: enumerable, fun: f1, acc: side], acc, fun) do
|
||||
do_reduce(enumerable, { acc, side }, f1.(fun)) |> elem(0)
|
||||
end
|
||||
|
||||
defp do_reduce(enumerable, acc, fun) do
|
||||
Enumerable.reduce(enumerable, acc, fun)
|
||||
catch
|
||||
{ :stream_lazy, acc } -> acc
|
||||
end
|
||||
end
|
||||
|
||||
@type t :: Lazy.t | (acc, (element, acc -> acc) -> acc)
|
||||
@type acc :: any
|
||||
@type element :: any
|
||||
@type index :: non_neg_integer
|
||||
@type default :: any
|
||||
|
||||
@doc """
|
||||
Creates a stream that cycles through the given enumerable,
|
||||
infinitely.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> stream = Stream.cycle([1,2,3])
|
||||
iex> Enum.take(stream, 5)
|
||||
[1,2,3,1,2]
|
||||
|
||||
"""
|
||||
@spec cycle(Enumerable.t) :: t
|
||||
def cycle(enumerable) do
|
||||
do_cycle(enumerable, &1, &2)
|
||||
end
|
||||
|
||||
defp do_cycle(enumerable, acc, fun) do
|
||||
acc = Enumerable.reduce(enumerable, acc, fun)
|
||||
do_cycle(enumerable, acc, fun)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Lazily drops the next `n` items from the enumerable.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> stream = Stream.drop(1..10, 5)
|
||||
iex> Enum.to_list(stream)
|
||||
[6,7,8,9,10]
|
||||
|
||||
"""
|
||||
@spec drop(Enumerable.t, non_neg_integer) :: t
|
||||
def drop(enumerable, n) when n >= 0 do
|
||||
Lazy[enumerable: enumerable,
|
||||
fun: fn(f1) ->
|
||||
fn
|
||||
_entry, { acc, n } when n > 0 ->
|
||||
{ acc, n - 1 }
|
||||
entry, { acc, n } ->
|
||||
{ f1.(entry, acc), n }
|
||||
end
|
||||
end,
|
||||
acc: n]
|
||||
end
|
||||
|
||||
@doc """
|
||||
Lazily drops elements of the enumerable while the given
|
||||
function returns true.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> stream = Stream.drop_while(1..10, &1 <= 5)
|
||||
iex> Enum.to_list(stream)
|
||||
[6,7,8,9,10]
|
||||
|
||||
"""
|
||||
@spec drop_while(Enumerable.t, (element -> as_boolean(term))) :: t
|
||||
def drop_while(enumerable, f) do
|
||||
Lazy[enumerable: enumerable,
|
||||
fun: fn(f1) ->
|
||||
fn
|
||||
entry, { acc, true } ->
|
||||
if f.(entry), do: { acc, true }, else: { f1.(entry, acc), false }
|
||||
entry, { acc, false } ->
|
||||
{ f1.(entry, acc), false }
|
||||
end
|
||||
end,
|
||||
acc: true]
|
||||
end
|
||||
|
||||
@doc """
|
||||
Creates a stream that will filter elements according to
|
||||
the given function on enumeration.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> stream = Stream.filter([1, 2, 3], fn(x) -> rem(x, 2) == 0 end)
|
||||
iex> Enum.to_list(stream)
|
||||
[2]
|
||||
|
||||
"""
|
||||
@spec filter(Enumerable.t, (element -> as_boolean(term))) :: t
|
||||
def filter(enumerable, f) do
|
||||
Lazy[enumerable: enumerable,
|
||||
fun: fn(f1) ->
|
||||
fn(entry, acc) ->
|
||||
if f.(entry), do: f1.(entry, acc), else: acc
|
||||
end
|
||||
end]
|
||||
end
|
||||
|
||||
@doc """
|
||||
Emit a sequence of values, starting with `start_value`. Successive
|
||||
values are generated by calling `next_fun` on the previous value.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Stream.iterate(0, &1+1) |> Enum.take(5)
|
||||
[0,1,2,3,4]
|
||||
|
||||
"""
|
||||
|
||||
@spec iterate(element, (element -> element)) :: t
|
||||
def iterate(start_value, next_fun) do
|
||||
fn acc, fun ->
|
||||
do_iterate(start_value, next_fun, fun.(start_value, acc), fun)
|
||||
end
|
||||
end
|
||||
|
||||
defp do_iterate(value, next_fun, acc, fun) do
|
||||
next = next_fun.(value)
|
||||
do_iterate(next, next_fun, fun.(next, acc), fun)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Creates a stream that will apply the given function on
|
||||
enumeration.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> stream = Stream.map([1, 2, 3], fn(x) -> x * 2 end)
|
||||
iex> Enum.to_list(stream)
|
||||
[2,4,6]
|
||||
|
||||
"""
|
||||
@spec map(Enumerable.t, (element -> any)) :: t
|
||||
def map(enumerable, f) do
|
||||
Lazy[enumerable: enumerable,
|
||||
fun: fn(f1) ->
|
||||
fn(entry, acc) ->
|
||||
f1.(f.(entry), acc)
|
||||
end
|
||||
end]
|
||||
end
|
||||
|
||||
@doc """
|
||||
Creates a stream that will reject elements according to
|
||||
the given function on enumeration.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> stream = Stream.reject([1, 2, 3], fn(x) -> rem(x, 2) == 0 end)
|
||||
iex> Enum.to_list(stream)
|
||||
[1,3]
|
||||
|
||||
"""
|
||||
@spec reject(Enumerable.t, (element -> as_boolean(term))) :: t
|
||||
def reject(enumerable, f) do
|
||||
Lazy[enumerable: enumerable,
|
||||
fun: fn(f1) ->
|
||||
fn(entry, acc) ->
|
||||
unless f.(entry), do: f1.(entry, acc), else: acc
|
||||
end
|
||||
end]
|
||||
end
|
||||
|
||||
|
||||
@doc """
|
||||
Returns a stream generated by calling `generator_fun` repeatedly.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Stream.repeatedly(function(:random.uniform/0)) |> Enum.take(3)
|
||||
[0.4435846174457203, 0.7230402056221108, 0.94581636451987]
|
||||
|
||||
"""
|
||||
@spec repeatedly((() -> element)) :: t
|
||||
def repeatedly(generator_fun)
|
||||
when is_function(generator_fun, 0) do
|
||||
do_repeatedly(generator_fun, &1, &2)
|
||||
end
|
||||
|
||||
defp do_repeatedly(generator_fun, acc, fun) do
|
||||
do_repeatedly(generator_fun, fun.(generator_fun.(), acc), fun)
|
||||
end
|
||||
|
||||
|
||||
@doc """
|
||||
Lazily takes the next `n` items from the enumerable and stops
|
||||
enumeration.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> stream = Stream.take(1..100, 5)
|
||||
iex> Enum.to_list(stream)
|
||||
[1,2,3,4,5]
|
||||
|
||||
iex> stream = Stream.cycle([1, 2, 3]) |> Stream.take(5)
|
||||
iex> Enum.to_list(stream)
|
||||
[1,2,3,1,2]
|
||||
|
||||
"""
|
||||
@spec take(Enumerable.t, non_neg_integer) :: t
|
||||
def take(enumerable, n) when n >= 0 do
|
||||
Lazy[enumerable: enumerable,
|
||||
fun: fn(f1) ->
|
||||
fn
|
||||
(entry, { acc, n }) when n > 0 ->
|
||||
{ f1.(entry, acc), n-1 }
|
||||
(_entry, acc) ->
|
||||
throw { :stream_lazy, acc }
|
||||
end
|
||||
end,
|
||||
acc: n]
|
||||
end
|
||||
|
||||
@doc """
|
||||
Lazily takes elements of the enumerable while the given
|
||||
function returns true.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> stream = Stream.take_while(1..100, &1 <= 5)
|
||||
iex> Enum.to_list(stream)
|
||||
[1,2,3,4,5]
|
||||
|
||||
"""
|
||||
@spec take_while(Enumerable.t, (element -> as_boolean(term))) :: t
|
||||
def take_while(enumerable, f) do
|
||||
Lazy[enumerable: enumerable,
|
||||
fun: fn(f1) ->
|
||||
fn
|
||||
entry, { acc, true } ->
|
||||
if f.(entry), do: { f1.(entry, acc), true }, else: { acc, false }
|
||||
_entry, acc ->
|
||||
acc
|
||||
end
|
||||
end,
|
||||
acc: true]
|
||||
end
|
||||
|
||||
@doc """
|
||||
Creates a stream where each item in the enumerable will
|
||||
be accompanied by its index.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> stream = Stream.with_index([1, 2, 3])
|
||||
iex> Enum.to_list(stream)
|
||||
[{1,0},{2,1},{3,2}]
|
||||
|
||||
"""
|
||||
@spec with_index(Enumerable.t) :: t
|
||||
def with_index(enumerable) do
|
||||
Lazy[enumerable: enumerable,
|
||||
fun: fn(f1) ->
|
||||
fn(entry, { acc, counter }) ->
|
||||
acc = f1.({ entry, counter }, acc)
|
||||
{ acc, counter + 1}
|
||||
end
|
||||
end,
|
||||
acc: 0]
|
||||
end
|
||||
end
|
||||
@@ -138,6 +138,67 @@ defmodule String do
|
||||
def printable?(<<>>), do: true
|
||||
def printable?(_), do: false
|
||||
|
||||
@doc %B"""
|
||||
Escape the given string. It expects one extra paremeter
|
||||
representing the string surrounds which should also be escaped.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> String.escape("abc", ?")
|
||||
"abc"
|
||||
|
||||
iex> String.escape("a\nb", ?")
|
||||
"a\\nb"
|
||||
|
||||
"""
|
||||
def escape(other, char) do
|
||||
escape(other, char, <<>>)
|
||||
end
|
||||
|
||||
@compile { :inline, escape: 3 }
|
||||
|
||||
defp escape(<<>>, _char, binary), do: binary
|
||||
|
||||
defp escape(<< char, t :: binary >>, char, binary) do
|
||||
escape(t, char, << binary :: binary, ?\\, char >>)
|
||||
end
|
||||
defp escape(<<?#, ?{, t :: binary>>, char, binary) do
|
||||
escape(t, char, << binary :: binary, ?\\, ?#, ?{ >>)
|
||||
end
|
||||
defp escape(<<?\a, t :: binary>>, char, binary) do
|
||||
escape(t, char, << binary :: binary, ?\\, ?a >>)
|
||||
end
|
||||
defp escape(<<?\b, t :: binary>>, char, binary) do
|
||||
escape(t, char, << binary :: binary, ?\\, ?b >>)
|
||||
end
|
||||
defp escape(<<?\d, t :: binary>>, char, binary) do
|
||||
escape(t, char, << binary :: binary, ?\\, ?d >>)
|
||||
end
|
||||
defp escape(<<?\e, t :: binary>>, char, binary) do
|
||||
escape(t, char, << binary :: binary, ?\\, ?e >>)
|
||||
end
|
||||
defp escape(<<?\f, t :: binary>>, char, binary) do
|
||||
escape(t, char, << binary :: binary, ?\\, ?f >>)
|
||||
end
|
||||
defp escape(<<?\n, t :: binary>>, char, binary) do
|
||||
escape(t, char, << binary :: binary, ?\\, ?n >>)
|
||||
end
|
||||
defp escape(<<?\r, t :: binary>>, char, binary) do
|
||||
escape(t, char, << binary :: binary, ?\\, ?r >>)
|
||||
end
|
||||
defp escape(<<?\\, t :: binary>>, char, binary) do
|
||||
escape(t, char, << binary :: binary, ?\\, ?\\ >>)
|
||||
end
|
||||
defp escape(<<?\t, t :: binary>>, char, binary) do
|
||||
escape(t, char, << binary :: binary, ?\\, ?t >>)
|
||||
end
|
||||
defp escape(<<?\v, t :: binary>>, char, binary) do
|
||||
escape(t, char, << binary :: binary, ?\\, ?v >>)
|
||||
end
|
||||
defp escape(<<h, t :: binary>>, char, binary) do
|
||||
escape(t, char, << binary :: binary, h >>)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Splits a string on sub strings at each Unicode whitespace
|
||||
occurrence with leading and trailing whitespace ignored.
|
||||
@@ -146,7 +207,7 @@ defmodule String do
|
||||
|
||||
iex> String.split("foo bar")
|
||||
["foo", "bar"]
|
||||
iex> String.split("foo" <> <<194,133>> <> "bar")
|
||||
iex> String.split("foo" <> <<194, 133>> <> "bar")
|
||||
["foo", "bar"]
|
||||
iex> String.split(" foo bar ")
|
||||
["foo", "bar"]
|
||||
@@ -185,6 +246,8 @@ defmodule String do
|
||||
@spec split(t, t | [t] | Regex.t, Keyword.t) :: [t]
|
||||
def split(binary, pattern, options // [])
|
||||
|
||||
def split("", _pattern, _options), do: [""]
|
||||
|
||||
def split(binary, pattern, options) when is_regex(pattern) do
|
||||
Regex.split(pattern, binary, global: options[:global])
|
||||
end
|
||||
@@ -383,7 +446,7 @@ defmodule String do
|
||||
"a,[b],c"
|
||||
iex> String.replace("a,b,c", ",", "[]", insert_replaced: 2)
|
||||
"a[],b[],c"
|
||||
iex> String.replace("a,b,c", ",", "[]", insert_replaced: [1,1])
|
||||
iex> String.replace("a,b,c", ",", "[]", insert_replaced: [1, 1])
|
||||
"a[,,]b[,,]c"
|
||||
|
||||
"""
|
||||
@@ -399,7 +462,7 @@ defmodule String do
|
||||
opts = if options[:global] != false, do: [:global], else: []
|
||||
|
||||
if insert = options[:insert_replaced] do
|
||||
opts = [{:insert_replaced,insert}|opts]
|
||||
opts = [{:insert_replaced, insert}|opts]
|
||||
end
|
||||
|
||||
opts
|
||||
@@ -410,6 +473,8 @@ defmodule String do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> String.duplicate("abc", 0)
|
||||
""
|
||||
iex> String.duplicate("abc", 1)
|
||||
"abc"
|
||||
iex> String.duplicate("abc", 2)
|
||||
@@ -417,7 +482,7 @@ defmodule String do
|
||||
|
||||
"""
|
||||
@spec duplicate(t, pos_integer) :: t
|
||||
def duplicate(subject, n) when is_integer(n) and n > 0 do
|
||||
def duplicate(subject, n) when is_integer(n) and n >= 0 do
|
||||
:binary.copy(subject, n)
|
||||
end
|
||||
|
||||
@@ -773,8 +838,8 @@ defmodule String do
|
||||
{34.0,""}
|
||||
iex> String.to_float("34.25")
|
||||
{34.25,""}
|
||||
iex> String.to_float("56.5xyz")
|
||||
{56.5,"xyz"}
|
||||
iex> String.to_float("56.5xyz")
|
||||
{56.5,"xyz"}
|
||||
iex> String.to_float("pi")
|
||||
:error
|
||||
|
||||
@@ -789,7 +854,7 @@ defmodule String do
|
||||
{int_result, int_remainder} = :string.to_integer(charlist)
|
||||
case int_result do
|
||||
:error -> :error
|
||||
_ -> {float(int_result), list_to_binary(int_remainder)}
|
||||
_ -> {:erlang.float(int_result), list_to_binary(int_remainder)}
|
||||
end
|
||||
_ -> {result, list_to_binary(remainder)}
|
||||
end
|
||||
@@ -824,7 +889,7 @@ defmodule String do
|
||||
end
|
||||
|
||||
defp do_starts_with(string, prefix) when is_binary(prefix) do
|
||||
match?({0,_}, :binary.match(string, prefix))
|
||||
match?({0, _}, :binary.match(string, prefix))
|
||||
end
|
||||
|
||||
defp do_starts_with(_, _) do
|
||||
|
||||
@@ -162,8 +162,8 @@ defmodule System do
|
||||
defp write_tmp_dir(dir) do
|
||||
case :file.read_file_info(dir) do
|
||||
{:ok, info} ->
|
||||
type_index = File.Stat.__index__ :type
|
||||
access_index = File.Stat.__index__ :access
|
||||
type_index = File.Stat.__record__(:index, :type)
|
||||
access_index = File.Stat.__record__(:index, :access)
|
||||
case { elem(info, type_index), elem(info, access_index) } do
|
||||
{ :directory, access } when access in [:read_write, :write] ->
|
||||
dir
|
||||
|
||||
+61
-60
@@ -1,17 +1,52 @@
|
||||
defmodule URI do
|
||||
@on_load :preload_parsers
|
||||
|
||||
defrecord Info, [scheme: nil, path: nil, query: nil,
|
||||
fragment: nil, authority: nil,
|
||||
userinfo: nil, host: nil, port: nil,
|
||||
specifics: nil]
|
||||
|
||||
import Bitwise
|
||||
|
||||
@moduledoc """
|
||||
Utilities for working with and creating URIs.
|
||||
"""
|
||||
|
||||
defrecord Info, [scheme: nil, path: nil, query: nil,
|
||||
fragment: nil, authority: nil,
|
||||
userinfo: nil, host: nil, port: nil]
|
||||
|
||||
import Bitwise
|
||||
|
||||
@ports [
|
||||
{ "ftp", 21 },
|
||||
{ "http", 80 },
|
||||
{ "https", 443 },
|
||||
{ "ldap", 389 },
|
||||
{ "sftp", 22 },
|
||||
{ "tftp", 69 },
|
||||
]
|
||||
|
||||
Enum.each @ports, fn { scheme, port } ->
|
||||
def normalize_scheme(unquote(scheme)), do: unquote(scheme)
|
||||
def default_port(unquote(scheme)), do: unquote(port)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Normalizes the scheme according to the spec by downcasing it.
|
||||
"""
|
||||
def normalize_scheme(nil), do: nil
|
||||
def normalize_scheme(scheme), do: String.downcase(scheme)
|
||||
|
||||
@doc """
|
||||
Returns the default port for a given scheme.
|
||||
If the scheme is unknown to URI, returns nil.
|
||||
Any scheme may be registered via `URI.default_port/2`.
|
||||
"""
|
||||
def default_port(scheme) when is_binary(scheme) do
|
||||
{ :ok, dict } = :application.get_env(:elixir, :uri)
|
||||
Dict.get(dict, scheme)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Registers a scheme with a default port into Elixir.
|
||||
"""
|
||||
def default_port(scheme, port) when is_binary(scheme) and port > 0 do
|
||||
{ :ok, dict } = :application.get_env(:elixir, :uri)
|
||||
:application.set_env(:elixir, :uri, Dict.put(dict, scheme, port))
|
||||
end
|
||||
|
||||
@doc """
|
||||
Takes an enumerable (containing a sequence of two-item tuples)
|
||||
and returns a string of k=v&k2=v2... where keys and values are
|
||||
@@ -69,7 +104,7 @@ defmodule URI do
|
||||
@doc """
|
||||
Percent (URL) encodes a URI.
|
||||
"""
|
||||
def encode(s), do: bc <<c>> inbits s, do: <<percent(c) :: binary>>
|
||||
def encode(s), do: bc(<<c>> inbits s, do: <<percent(c) :: binary>>)
|
||||
|
||||
defp percent(32), do: <<?+>>
|
||||
defp percent(?-), do: <<?->>
|
||||
@@ -141,41 +176,17 @@ defmodule URI do
|
||||
if port, do: authority = authority <> ":" <> integer_to_binary(port)
|
||||
end
|
||||
|
||||
info = URI.Info[
|
||||
scheme: scheme && String.downcase(scheme), path: path, query: query,
|
||||
scheme = normalize_scheme(scheme)
|
||||
|
||||
if nil?(port) and not nil?(scheme) do
|
||||
port = default_port(scheme)
|
||||
end
|
||||
|
||||
URI.Info[
|
||||
scheme: scheme, path: path, query: query,
|
||||
fragment: fragment, authority: authority,
|
||||
userinfo: userinfo, host: host, port: port
|
||||
]
|
||||
|
||||
scheme_specific(scheme, info)
|
||||
end
|
||||
|
||||
@doc false
|
||||
def scheme_module(scheme) do
|
||||
if scheme do
|
||||
module =
|
||||
try do
|
||||
Module.safe_concat(URI, String.upcase(scheme))
|
||||
rescue
|
||||
ArgumentError -> nil
|
||||
end
|
||||
|
||||
if module && Code.ensure_loaded?(module) do
|
||||
module
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defp scheme_specific(scheme, info) do
|
||||
if module = scheme_module(scheme) do
|
||||
module.parse(default_port(info, module))
|
||||
else
|
||||
info
|
||||
end
|
||||
end
|
||||
|
||||
defp default_port(info, module) do
|
||||
if info.port, do: info, else: info.port(module.default_port)
|
||||
end
|
||||
|
||||
# Split an authority into its userinfo, host and port parts.
|
||||
@@ -184,38 +195,28 @@ defmodule URI do
|
||||
components = Regex.run %r/(^(.*)@)?([^:]*)(:(\d*))?/, s
|
||||
destructure [_, _, userinfo, host, _, port], nillify(components)
|
||||
port = if port, do: binary_to_integer(port)
|
||||
{ userinfo, host && String.downcase(host), port }
|
||||
{ userinfo, host, port }
|
||||
end
|
||||
|
||||
# Regex.run returns empty strings sometimes. We want
|
||||
# to replace those with nil for consistency.
|
||||
defp nillify(l) do
|
||||
lc s inlist l do
|
||||
if size(s) > 0 do
|
||||
s
|
||||
else
|
||||
nil
|
||||
end
|
||||
if size(s) > 0, do: s, else: nil
|
||||
end
|
||||
end
|
||||
|
||||
# Reference parsers so the parse/1 doesn't fail
|
||||
# on safe_concat.
|
||||
defp preload_parsers do
|
||||
parsers = [URI.FTP, URI.HTTP, URI.HTTPS, URI.LDAP, URI.SFTP, URI.TFTP]
|
||||
Enum.each parsers, Code.ensure_loaded(&1)
|
||||
:ok
|
||||
end
|
||||
end
|
||||
|
||||
defimpl Binary.Chars, for: URI.Info do
|
||||
def to_binary(uri) do
|
||||
result = ""
|
||||
def to_binary(URI.Info[] = uri) do
|
||||
scheme = uri.scheme
|
||||
|
||||
if module = URI.scheme_module(uri.scheme) do
|
||||
if module.default_port == uri.port, do: uri = uri.port(nil)
|
||||
if scheme && (port = URI.default_port(scheme)) do
|
||||
if uri.port == port, do: uri = uri.port(nil)
|
||||
end
|
||||
|
||||
result = ""
|
||||
|
||||
if uri.scheme, do: result = result <> uri.scheme <> "://"
|
||||
if uri.userinfo, do: result = result <> uri.userinfo <> "@"
|
||||
if uri.host, do: result = result <> uri.host
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
defmodule URI.FTP do
|
||||
@behavior URI.Parser
|
||||
def default_port(), do: 21
|
||||
def parse(info), do: info
|
||||
end
|
||||
@@ -1,5 +0,0 @@
|
||||
defmodule URI.HTTP do
|
||||
@behavior URI.Parser
|
||||
def default_port(), do: 80
|
||||
def parse(info), do: info
|
||||
end
|
||||
@@ -1,5 +0,0 @@
|
||||
defmodule URI.HTTPS do
|
||||
@behavior URI.Parser
|
||||
def default_port(), do: 443
|
||||
def parse(info), do: info
|
||||
end
|
||||
@@ -1,7 +0,0 @@
|
||||
defmodule URI.LDAP do
|
||||
@behavior URI.Parser
|
||||
def default_port(), do: 389
|
||||
|
||||
# TODO: LDAP specific parsing.
|
||||
def parse(info), do: info
|
||||
end
|
||||
@@ -1,18 +0,0 @@
|
||||
defmodule URI.Parser do
|
||||
@moduledoc """
|
||||
Defines the behavior for each URI.Parser.
|
||||
Check URI.HTTP for a possible implementation.
|
||||
"""
|
||||
|
||||
use Behaviour
|
||||
|
||||
@doc """
|
||||
Responsible for parsing extra URL information.
|
||||
"""
|
||||
defcallback parse(uri_info :: URI.Info.t) :: URI.Info.t
|
||||
|
||||
@doc """
|
||||
Responsible for returning the default port.
|
||||
"""
|
||||
defcallback default_port() :: integer
|
||||
end
|
||||
@@ -1,5 +0,0 @@
|
||||
defmodule URI.SFTP do
|
||||
@behavior URI.Parser
|
||||
def default_port(), do: 22
|
||||
def parse(info), do: info
|
||||
end
|
||||
@@ -1,5 +0,0 @@
|
||||
defmodule URI.TFTP do
|
||||
@behavior URI.Parser
|
||||
def default_port(), do: 69
|
||||
def parse(info), do: info
|
||||
end
|
||||
@@ -18,7 +18,7 @@ defmodule String.Unicode do
|
||||
|
||||
data_path = Path.join(__DIR__, "UnicodeData.txt")
|
||||
|
||||
{ codes, whitespace } = Enum.reduce File.iterator!(data_path), { [], [] }, fn(line, { cacc, wacc }) ->
|
||||
{ codes, whitespace } = Enum.reduce File.stream!(data_path), { [], [] }, fn(line, { cacc, wacc }) ->
|
||||
[ codepoint, _name, _category,
|
||||
_class, bidi, _decomposition,
|
||||
_numeric_1, _numeric_2, _numeric_3,
|
||||
@@ -39,7 +39,7 @@ defmodule String.Unicode do
|
||||
|
||||
special_path = Path.join(__DIR__, "SpecialCasing.txt")
|
||||
|
||||
codes = Enum.reduce File.iterator!(special_path), codes, fn(line, acc) ->
|
||||
codes = Enum.reduce File.stream!(special_path), codes, fn(line, acc) ->
|
||||
[ codepoint, lower, title, upper, _comment ] = :binary.split(line, "; ", [:global])
|
||||
key = to_binary.(codepoint)
|
||||
:lists.keystore(key, 1, acc, { key, to_binary.(upper), to_binary.(lower), to_binary.(title) })
|
||||
@@ -47,7 +47,7 @@ defmodule String.Unicode do
|
||||
|
||||
seqs_path = Path.join(__DIR__, "NamedSequences.txt")
|
||||
|
||||
seqs = Enum.map File.iterator!(seqs_path), fn(line) ->
|
||||
seqs = Enum.map File.stream!(seqs_path), fn(line) ->
|
||||
[ _name, codepoints ] = :binary.split(line, ";", [:global])
|
||||
codepoints = :binary.split(codepoints, " ", [:global])
|
||||
codepoints = Enum.map codepoints, Regex.replace(%r/\s+/, &1, "")
|
||||
@@ -137,7 +137,7 @@ defmodule String.Unicode do
|
||||
|
||||
# Split
|
||||
|
||||
def split(""), do: ""
|
||||
def split(""), do: [""]
|
||||
|
||||
def split(string) when is_binary(string) do
|
||||
:lists.reverse do_split(string, "", [])
|
||||
|
||||
@@ -24,4 +24,4 @@
|
||||
{verbose, false}
|
||||
]}.
|
||||
|
||||
{require_otp_vsn,"(R16).*"}.
|
||||
{require_otp_vsn,"(R16B).*"}.
|
||||
|
||||
+13
-34
@@ -2,10 +2,11 @@
|
||||
-behaviour(application).
|
||||
-export([main/1, start_cli/0,
|
||||
scope_for_eval/1, scope_for_eval/2,
|
||||
eval/2, eval/3, eval/4,
|
||||
eval/2, eval/3, eval/4, file_type/1,
|
||||
eval_quoted/2, eval_quoted/3, eval_quoted/4,
|
||||
eval_forms/3, translate_forms/3]).
|
||||
-include("elixir.hrl").
|
||||
-include_lib("kernel/include/file.hrl").
|
||||
|
||||
%% Top level types
|
||||
-export_type([char_list/0, as_boolean/1]).
|
||||
@@ -43,6 +44,14 @@ start_cli() ->
|
||||
application:start(?MODULE),
|
||||
'Elixir.Kernel.CLI':main(init:get_plain_arguments()).
|
||||
|
||||
%% Helpers
|
||||
|
||||
file_type(File) ->
|
||||
case file:read_file_info(File) of
|
||||
{ ok, #file_info{type=Type} } -> { ok, Type };
|
||||
{ error, _ } = Error -> Error
|
||||
end.
|
||||
|
||||
%% EVAL HOOKS
|
||||
|
||||
scope_for_eval(Opts) ->
|
||||
@@ -128,48 +137,18 @@ translate_forms(Tree, Binding, Opts) when is_list(Opts) ->
|
||||
translate_forms(Tree, Binding, scope_for_eval(Opts));
|
||||
|
||||
translate_forms(Tree, Binding, #elixir_scope{} = Scope) ->
|
||||
elixir_translator:translate(Tree, Scope#elixir_scope{
|
||||
vars=binding_dict(Binding),
|
||||
temp_vars=[],
|
||||
clause_vars=nil,
|
||||
counter=[]
|
||||
}).
|
||||
elixir_translator:translate(Tree, elixir_scope:vars_from_binding(Scope, Binding)).
|
||||
|
||||
eval_forms(Tree, Binding, Scope) ->
|
||||
{ ParseTree, NewScope } = translate_forms(Tree, Binding, Scope),
|
||||
case ParseTree of
|
||||
[] -> { nil, Binding, NewScope };
|
||||
_ ->
|
||||
{value, Value, NewBinding} = erl_eval:exprs(ParseTree, normalize_binding(Binding)),
|
||||
{Value, final_binding(NewBinding, NewScope#elixir_scope.vars), NewScope }
|
||||
{value, Value, NewBinding} = erl_eval:exprs(ParseTree, elixir_scope:binding_for_eval(Binding, nil)),
|
||||
{Value, elixir_scope:binding_from_vars(NewScope, NewBinding), NewScope }
|
||||
end.
|
||||
|
||||
%% INTERNAL HELPERS
|
||||
|
||||
to_binary(Bin) when is_binary(Bin) -> Bin;
|
||||
to_binary(List) when is_list(List) -> list_to_binary(List).
|
||||
|
||||
binding_dict(List) -> binding_dict(List, orddict:new()).
|
||||
binding_dict([{{H,Kind},_}|T], Dict) -> binding_dict(T, orddict:store({ H, Kind }, H, Dict));
|
||||
binding_dict([{H,_}|T], Dict) -> binding_dict(T, orddict:store({ H, nil }, H, Dict));
|
||||
binding_dict([], Dict) -> Dict.
|
||||
|
||||
final_binding(Binding, Vars) -> final_binding(Binding, [], Binding, Vars).
|
||||
final_binding([{Var,_}|T], Acc, Binding, Vars) ->
|
||||
case lists:member($@, atom_to_list(Var)) of
|
||||
true ->
|
||||
final_binding(T, Acc, Binding, Vars);
|
||||
false ->
|
||||
RealName = orddict:fetch({ Var, nil }, Vars),
|
||||
RealValue = proplists:get_value(RealName, Binding, nil),
|
||||
final_binding(T, [{Var, RealValue}|Acc], Binding, Vars)
|
||||
end;
|
||||
|
||||
final_binding([], Acc, _Binding, _Vars) -> lists:reverse(Acc).
|
||||
|
||||
normalize_binding(Binding) ->
|
||||
Keyword = orddict:from_list(Binding),
|
||||
case orddict:find('_@MODULE', Keyword) of
|
||||
{ ok, _ } -> Keyword;
|
||||
_ -> orddict:store('_@MODULE', nil, Keyword)
|
||||
end.
|
||||
|
||||
@@ -134,6 +134,7 @@ concat(Args) -> list_to_atom(raw_concat(Args)).
|
||||
safe_concat(Args) -> list_to_existing_atom(raw_concat(Args)).
|
||||
|
||||
raw_concat(['Elixir'|Args]) -> do_concat(Args);
|
||||
raw_concat([nil|Args]) -> do_concat(Args);
|
||||
raw_concat(Args) -> do_concat(Args).
|
||||
|
||||
do_concat(Args) ->
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
%% fn, receive and friends. try is handled in elixir_try.
|
||||
-module(elixir_clauses).
|
||||
-export([
|
||||
assigns/3, assigns_block/5, assigns_block/6, extract_last_guards/1,
|
||||
assigns/3, assigns_block/5, assigns_block/6, extract_splat_guards/1,
|
||||
get_pairs/4, get_pairs/5, match/3, extract_args/1, extract_guards/1]).
|
||||
-include("elixir.hrl").
|
||||
|
||||
@@ -14,7 +14,7 @@ get_pairs(Meta, Key, Clauses, S) ->
|
||||
get_pairs(Meta, Key, Clauses, S, AllowNil) ->
|
||||
case lists:keyfind(Key, 1, Clauses) of
|
||||
{ Key, { '->', _, Pairs } } ->
|
||||
[{ Key, Left, Right } || { Left, Right } <- Pairs];
|
||||
[{ Key, PMeta, Left, Right } || { Left, PMeta, Right } <- Pairs];
|
||||
{ Key, nil } when AllowNil ->
|
||||
[];
|
||||
{ Key, _ } ->
|
||||
@@ -25,9 +25,12 @@ get_pairs(Meta, Key, Clauses, S, AllowNil) ->
|
||||
|
||||
% Function for translating assigns.
|
||||
|
||||
assigns(Fun, Args, #elixir_scope{context=Context} = S) when Context /= match ->
|
||||
{ Result, NewS } = assigns(Fun, Args, S#elixir_scope{context=match, temp_vars=orddict:new()}),
|
||||
{ Result, NewS#elixir_scope{context=Context} };
|
||||
assigns(Fun, Args, #elixir_scope{context=Context, temp_vars=TempVars,
|
||||
backup_vars=BackupVars, vars=Vars} = S) when Context /= match ->
|
||||
{ Result, NewS } = assigns(Fun, Args, S#elixir_scope{context=match,
|
||||
temp_vars=ordsets:new(), backup_vars=Vars}),
|
||||
{ Result, NewS#elixir_scope{context=Context,
|
||||
temp_vars=TempVars, backup_vars=BackupVars} };
|
||||
|
||||
assigns(Fun, Args, S) -> Fun(Args, S).
|
||||
|
||||
@@ -77,13 +80,13 @@ extract_args({ Name, _, Args }) when is_atom(Name), is_atom(Args) -> { Name, []
|
||||
extract_args({ Name, _, Args }) when is_atom(Name), is_list(Args) -> { Name, Args };
|
||||
extract_args(_) -> error.
|
||||
|
||||
% Extract guards when it is in the last element of the args
|
||||
% Extract guards when multiple left side args are allowed.
|
||||
|
||||
extract_last_guards([]) -> { [], [] };
|
||||
extract_last_guards(Args) ->
|
||||
{ Left, Right } = elixir_tree_helpers:split_last(Args),
|
||||
{ Bare, Guards } = extract_guards(Right),
|
||||
{ Left ++ [Bare], Guards }.
|
||||
extract_splat_guards([{ 'when', _, [_,_|_] = Args }]) ->
|
||||
{ Left, Right } = elixir_tree_helpers:split_last(Args),
|
||||
{ Left, extract_or_clauses(Right, []) };
|
||||
extract_splat_guards(Else) ->
|
||||
{ Else, [] }.
|
||||
|
||||
% Function for translating macros with match style like case and receive.
|
||||
|
||||
@@ -95,15 +98,15 @@ match(Meta, Clauses, #elixir_scope{clause_vars=C1} = S) ->
|
||||
do_match(_Meta, [], S) ->
|
||||
{ [], S };
|
||||
|
||||
do_match(Meta, [DecoupledClause], S) ->
|
||||
{ TDecoupledClause, TS } = each_clause(Meta, DecoupledClause, S),
|
||||
do_match(_Meta, [DecoupledClause], S) ->
|
||||
{ TDecoupledClause, TS } = each_clause(DecoupledClause, S),
|
||||
{ [TDecoupledClause], TS };
|
||||
|
||||
do_match(Meta, DecoupledClauses, S) ->
|
||||
% Transform tree just passing the variables counter forward
|
||||
% and storing variables defined inside each clause.
|
||||
Transformer = fun(X, {Acc, CV}) ->
|
||||
{ TX, TAcc } = each_clause(Meta, X, Acc),
|
||||
{ TX, TAcc } = each_clause(X, Acc),
|
||||
{ TX, { merge_clauses_scope(S, TAcc), [TAcc#elixir_scope.clause_vars|CV] } }
|
||||
end,
|
||||
|
||||
@@ -163,21 +166,21 @@ expand_clauses(_Line, [], [], _FinalVars, Acc, S) ->
|
||||
|
||||
% Handle each key/value clause pair and translate them accordingly.
|
||||
|
||||
each_clause(Meta, { do, [Condition], Expr }, S) ->
|
||||
each_clause({ do, Meta, [Condition], Expr }, S) ->
|
||||
assigns_block(?line(Meta), fun elixir_translator:translate_each/2, Condition, [Expr], S);
|
||||
|
||||
each_clause(Meta, { else, [Condition], Expr }, S) ->
|
||||
each_clause({ else, Meta, [Condition], Expr }, S) ->
|
||||
assigns_block(?line(Meta), fun elixir_translator:translate_each/2, Condition, [Expr], S);
|
||||
|
||||
each_clause(Meta, { 'after', [Condition], Expr }, S) ->
|
||||
each_clause({ 'after', Meta, [Condition], Expr }, S) ->
|
||||
{ TCondition, SC } = elixir_translator:translate_each(Condition, S),
|
||||
{ TBody, SB } = elixir_translator:translate([Expr], SC),
|
||||
{ { clause, ?line(Meta), [TCondition], [], TBody }, SB };
|
||||
|
||||
each_clause(Meta, { Key, [_|_], _ }, S) when Key == do; Key == 'after' ->
|
||||
each_clause({ Key, Meta, [_|_], _ }, S) when Key == do; Key == 'after' ->
|
||||
elixir_errors:syntax_error(Meta, S#elixir_scope.file, "too many arguments given for ~ts", [Key]);
|
||||
|
||||
each_clause(Meta, { Key, _, _ }, S) ->
|
||||
each_clause({ Key, Meta, _, _ }, S) ->
|
||||
elixir_errors:syntax_error(Meta, S#elixir_scope.file, "invalid key ~ts", [Key]).
|
||||
|
||||
% Check if the given expression is a match tuple.
|
||||
@@ -253,6 +256,5 @@ merge_clauses_scope(S1, S2) ->
|
||||
counter=S2#elixir_scope.counter,
|
||||
extra_guards=S2#elixir_scope.extra_guards,
|
||||
super=S1#elixir_scope.super orelse S2#elixir_scope.super,
|
||||
caller=S1#elixir_scope.caller orelse S2#elixir_scope.caller,
|
||||
name_args=S1#elixir_scope.name_args orelse S2#elixir_scope.name_args
|
||||
caller=S1#elixir_scope.caller orelse S2#elixir_scope.caller
|
||||
}.
|
||||
@@ -5,11 +5,12 @@
|
||||
-behavior(gen_server).
|
||||
-record(elixir_code_server, {
|
||||
argv=[],
|
||||
compilation_status=ok,
|
||||
loaded=[],
|
||||
at_exit=[],
|
||||
pool=[],
|
||||
counter=0,
|
||||
compiler_options=[{docs,true},{debug_info,true}],
|
||||
compiler_options=[{docs,true},{debug_info,true},{warnings_as_errors,false}],
|
||||
waiting=[]
|
||||
}).
|
||||
|
||||
@@ -58,6 +59,9 @@ handle_call(argv, _From, Config) ->
|
||||
handle_call(compiler_options, _From, Config) ->
|
||||
{ reply, Config#elixir_code_server.compiler_options, Config };
|
||||
|
||||
handle_call(compilation_status, _From, Config) ->
|
||||
{ reply, Config#elixir_code_server.compilation_status, Config };
|
||||
|
||||
handle_call(retrieve_module_name, _From, Config) ->
|
||||
case Config#elixir_code_server.pool of
|
||||
[H|T] ->
|
||||
@@ -80,6 +84,15 @@ handle_cast({ compiler_options, Options }, Config) ->
|
||||
Final = orddict:merge(fun(_,_,V) -> V end, Config#elixir_code_server.compiler_options, Options),
|
||||
{ noreply, Config#elixir_code_server{compiler_options=Final} };
|
||||
|
||||
handle_cast(register_warning, Config) ->
|
||||
case orddict:find(warnings_as_errors, Config#elixir_code_server.compiler_options) of
|
||||
{ ok, true } -> { noreply, Config#elixir_code_server{compilation_status=error} };
|
||||
_ -> { noreply, Config }
|
||||
end;
|
||||
|
||||
handle_cast(reset_warnings, Config) ->
|
||||
{ noreply, Config#elixir_code_server{compilation_status=ok} };
|
||||
|
||||
handle_cast({ loaded, Path }, Config) ->
|
||||
Current = Config#elixir_code_server.loaded,
|
||||
case orddict:find(Path, Current) of
|
||||
|
||||
@@ -84,12 +84,12 @@ eval_forms(Forms, Line, Vars, S) ->
|
||||
%% Compile the module by forms based on the scope information
|
||||
%% executes the callback in case of success. This automatically
|
||||
%% handles errors and warnings. Used by this module and elixir_module.
|
||||
module(Forms, S, Callback) ->
|
||||
module(Forms, File, Callback) ->
|
||||
Options = case get_opt(debug_info) of
|
||||
true -> [debug_info];
|
||||
_ -> []
|
||||
end,
|
||||
module(Forms, S#elixir_scope.file, Options, false, Callback).
|
||||
module(Forms, File, Options, false, Callback).
|
||||
|
||||
module(Forms, File, RawOptions, Bootstrap, Callback) when
|
||||
is_binary(File), is_list(Forms), is_list(RawOptions), is_boolean(Bootstrap), is_function(Callback) ->
|
||||
@@ -208,9 +208,9 @@ core_main() ->
|
||||
[
|
||||
"lib/elixir/lib/kernel.ex",
|
||||
"lib/elixir/lib/keyword.ex",
|
||||
"lib/elixir/lib/module.ex",
|
||||
"lib/elixir/lib/list.ex",
|
||||
"lib/elixir/lib/kernel/typespec.ex",
|
||||
"lib/elixir/lib/module.ex",
|
||||
"lib/elixir/lib/record.ex",
|
||||
"lib/elixir/lib/macro.ex",
|
||||
"lib/elixir/lib/macro/env.ex",
|
||||
@@ -218,7 +218,8 @@ core_main() ->
|
||||
"lib/elixir/lib/code.ex",
|
||||
"lib/elixir/lib/protocol.ex",
|
||||
"lib/elixir/lib/enum.ex",
|
||||
"lib/elixir/lib/binary/inspect.ex",
|
||||
"lib/elixir/lib/inspect/algebra.ex",
|
||||
"lib/elixir/lib/inspect.ex",
|
||||
"lib/elixir/lib/binary/chars.ex",
|
||||
"lib/elixir/lib/io.ex",
|
||||
"lib/elixir/lib/path.ex",
|
||||
|
||||
@@ -100,7 +100,7 @@ store_definition(Kind, Line, CheckClauses, Module, Call, Body, RawS) ->
|
||||
|
||||
{ Name, Args } = case elixir_clauses:extract_args(NameAndArgs) of
|
||||
error ->
|
||||
Format = [Kind, 'Elixir.Macro':to_binary(NameAndArgs)],
|
||||
Format = [Kind, 'Elixir.Macro':to_string(NameAndArgs)],
|
||||
elixir_errors:syntax_error(Line, S#elixir_scope.file, "invalid syntax in ~ts ~ts", Format);
|
||||
Tuple ->
|
||||
Tuple
|
||||
@@ -132,9 +132,10 @@ store_definition(Kind, Line, CheckClauses, Module, Name, Args, Guards, Body, #el
|
||||
CTable = clauses_table(Module),
|
||||
|
||||
compile_super(Module, TS),
|
||||
check_previous_defaults(Table, Line, Name, Arity, Kind, DefaultsLength, S),
|
||||
|
||||
store_each(CheckClauses, Kind, File, Location,
|
||||
Table, CTable, DefaultsLength, Function),
|
||||
|
||||
[store_each(false, Kind, File, Location, Table, CTable, 0,
|
||||
default_function_for(Kind, Name, Default)) || Default <- Defaults],
|
||||
|
||||
@@ -203,7 +204,7 @@ translate_clause(_Line, _Kind, _Unpacked, [], nil, S) ->
|
||||
{ [], S };
|
||||
|
||||
translate_clause(Line, Kind, _Unpacked, _Guards, nil, #elixir_scope{file=File}) ->
|
||||
elixir_errors:syntax_error(Line, File, "missing keyword do in ~ts", [Kind]);
|
||||
elixir_errors:syntax_error(Line, File, "missing do keyword in ~ts", [Kind]);
|
||||
|
||||
translate_clause(Line, Kind, Unpacked, Guards, Body, S) ->
|
||||
Expr = expr_from_body(Line, Body),
|
||||
@@ -211,14 +212,6 @@ translate_clause(Line, Kind, Unpacked, Guards, Body, S) ->
|
||||
{ TClause, TS } = elixir_clauses:assigns_block(Line,
|
||||
fun elixir_translator:translate/2, Unpacked, [Expr], Guards, S),
|
||||
|
||||
%% Add names to args
|
||||
NClause = case TS#elixir_scope.name_args of
|
||||
true ->
|
||||
NArgs = elixir_def_overridable:assign_args(Line, element(3, TClause), TS),
|
||||
setelement(3, TClause, NArgs);
|
||||
false -> TClause
|
||||
end,
|
||||
|
||||
%% Set __CALLER__ if used
|
||||
FClause = case is_macro(Kind) andalso TS#elixir_scope.caller of
|
||||
true ->
|
||||
@@ -226,8 +219,8 @@ translate_clause(Line, Kind, Unpacked, Guards, Body, S) ->
|
||||
{ 'var', Line, '__CALLER__' },
|
||||
?wrap_call(Line, elixir_scope, to_ex_env, [{ var, Line, '_@CALLER' }])
|
||||
},
|
||||
setelement(5, NClause, [FBody|element(5, NClause)]);
|
||||
false -> NClause
|
||||
setelement(5, TClause, [FBody|element(5, TClause)]);
|
||||
false -> TClause
|
||||
end,
|
||||
|
||||
{ [FClause], TS }.
|
||||
@@ -317,8 +310,8 @@ store_each(Check, Kind, File, Location, Table, CTable, Defaults, {function, Line
|
||||
FinalLocation = StoredLocation,
|
||||
FinalDefaults = max(Defaults, StoredDefaults),
|
||||
check_valid_kind(Line, File, Name, Arity, Kind, StoredKind),
|
||||
check_valid_defaults(Line, File, Name, Arity, Kind, Defaults, StoredDefaults),
|
||||
(Check and StoredCheck) andalso check_valid_clause(Line, File, Name, Arity, Kind, Table);
|
||||
(Check and StoredCheck) andalso check_valid_clause(Line, File, Name, Arity, Kind, Table),
|
||||
check_valid_defaults(Line, File, Name, Arity, Kind, Defaults, StoredDefaults);
|
||||
[] ->
|
||||
FinalLine = Line,
|
||||
FinalLocation = Location,
|
||||
@@ -348,7 +341,18 @@ check_valid_defaults(_Line, _File, _Name, _Arity, _Kind, 0, _) -> [];
|
||||
check_valid_defaults(Line, File, Name, Arity, Kind, _, 0) ->
|
||||
elixir_errors:handle_file_warning(File, { Line, ?MODULE, { out_of_order_defaults, { Kind, Name, Arity } } });
|
||||
check_valid_defaults(Line, File, Name, Arity, Kind, _, _) ->
|
||||
elixir_errors:handle_file_warning(File, { Line, ?MODULE, { clauses_with_defaults, { Kind, Name, Arity } } }).
|
||||
elixir_errors:form_error(Line, File, ?MODULE, { clauses_with_defaults, { Kind, Name, Arity } }).
|
||||
|
||||
check_previous_defaults(Table, Line, Name, Arity, Kind, Defaults, S) ->
|
||||
Matches = ets:match(Table, { { Name, '$2' }, '$1', '_', '_', '_', '_', '$3' }),
|
||||
[ begin
|
||||
elixir_errors:form_error(Line, S#elixir_scope.file, ?MODULE,
|
||||
{ defs_with_defaults, Name, { Kind, Arity }, { K, A } })
|
||||
end || [K, A, D] <- Matches, A /= Arity, D /= 0, defaults_conflict(A, D, Arity, Defaults)].
|
||||
|
||||
defaults_conflict(A, D, Arity, Defaults) ->
|
||||
((Arity >= (A - D)) andalso (Arity < A)) orelse
|
||||
((A >= (Arity - Defaults)) andalso (A < Arity)).
|
||||
|
||||
assert_no_aliases_name(Line, '__aliases__', [Atom], #elixir_scope{file=File}) when is_atom(Atom) ->
|
||||
Message = "function names should start with lowercase characters or underscore, invalid name ~ts",
|
||||
@@ -362,8 +366,17 @@ assert_no_aliases_name(_Meta, _Aliases, _Args, _S) ->
|
||||
format_error({no_module,{Kind,Name,Arity}}) ->
|
||||
io_lib:format("cannot define function outside module, invalid scope for ~ts ~ts/~B", [Kind, Name, Arity]);
|
||||
|
||||
format_error({defs_with_defaults, Name, { Kind, Arity }, { K, A } }) when Arity > A ->
|
||||
io_lib:format("~ts ~ts/~B defaults conflicts with ~ts ~ts/~B",
|
||||
[Kind, Name, Arity, K, Name, A]);
|
||||
|
||||
format_error({defs_with_defaults, Name, { Kind, Arity }, { K, A } }) when Arity < A ->
|
||||
io_lib:format("~ts ~ts/~B conflicts with defaults from ~ts ~ts/~B",
|
||||
[Kind, Name, Arity, K, Name, A]);
|
||||
|
||||
format_error({clauses_with_defaults,{Kind,Name,Arity}}) ->
|
||||
io_lib:format("~ts ~ts/~B has default values and multiple clauses, use a separate clause for declaring defaults", [Kind, Name, Arity]);
|
||||
io_lib:format("~ts ~ts/~B has default values and multiple clauses, "
|
||||
"use a separate clause for declaring defaults", [Kind, Name, Arity]);
|
||||
|
||||
format_error({out_of_order_defaults,{Kind,Name,Arity}}) ->
|
||||
io_lib:format("clause with defaults should be the first clause in ~ts ~ts/~B", [Kind, Name, Arity]);
|
||||
@@ -372,4 +385,4 @@ format_error({override_function,{Kind,Name,Arity}}) ->
|
||||
io_lib:format("trying to override previously defined ~ts ~ts/~B", [Kind, Name, Arity]);
|
||||
|
||||
format_error({changed_kind,{Name,Arity,Previous,Current}}) ->
|
||||
io_lib:format("~ts ~ts/~B already defined as ~ts", [Current, Name, Arity, Previous]).
|
||||
io_lib:format("~ts ~ts/~B already defined as ~ts", [Current, Name, Arity, Previous]).
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
% Holds the logic responsible for defining overridable functions and handling super.
|
||||
-module(elixir_def_overridable).
|
||||
-export([store_pending/1, is_defined/2, ensure_defined/4,
|
||||
assign_args/3, retrieve_args/3, name/2, store/3, format_error/1]).
|
||||
assign_args/3, name/2, store/3, format_error/1]).
|
||||
-include("elixir.hrl").
|
||||
|
||||
overridable(Module) ->
|
||||
@@ -25,15 +25,6 @@ ensure_defined(Meta, Module, Tuple, S) ->
|
||||
_ -> elixir_errors:form_error(Meta, S#elixir_scope.file, ?MODULE, { no_super, Module, Tuple })
|
||||
end.
|
||||
|
||||
%% Retrieve args defined for the given arity.
|
||||
|
||||
retrieve_args(Meta, Arity, S) ->
|
||||
Line = ?line(Meta),
|
||||
{
|
||||
[ { var, Line, super_arg(X) } || X <- lists:seq(1, Arity) ],
|
||||
S#elixir_scope{name_args=true}
|
||||
}.
|
||||
|
||||
%% Assign pseudo variables to the given vars.
|
||||
|
||||
assign_args(Line, Args, S) when is_integer(Line) ->
|
||||
|
||||
@@ -13,9 +13,9 @@
|
||||
-define(builtin, 'Elixir.Kernel').
|
||||
|
||||
default_functions() ->
|
||||
[ { ?builtin, ordsets:union(in_elixir_functions(), in_erlang_functions()) } ].
|
||||
[ { ?builtin, elixir_imported_functions() } ].
|
||||
default_macros() ->
|
||||
[ { ?builtin, ordsets:union(in_elixir_macros(), in_erlang_macros()) } ].
|
||||
[ { ?builtin, elixir_imported_macros() } ].
|
||||
default_requires() ->
|
||||
[ ?builtin, 'Elixir.Kernel.Typespec', 'Elixir.Record' ].
|
||||
|
||||
@@ -23,9 +23,14 @@ find_import(Meta, Name, Arity, S) ->
|
||||
Tuple = { Name, Arity },
|
||||
|
||||
case find_dispatch(Meta, Tuple, S) of
|
||||
{ function, Receiver } -> Receiver;
|
||||
{ macro, Receiver } -> Receiver;
|
||||
_ -> false
|
||||
{ function, Receiver } ->
|
||||
elixir_tracker:record_import(Tuple, Receiver, S#elixir_scope.module, S#elixir_scope.function),
|
||||
Receiver;
|
||||
{ macro, Receiver } ->
|
||||
elixir_tracker:record_import(Tuple, Receiver, S#elixir_scope.module, S#elixir_scope.function),
|
||||
Receiver;
|
||||
_ ->
|
||||
false
|
||||
end.
|
||||
|
||||
%% Function retrieval
|
||||
@@ -184,7 +189,7 @@ expand_macro_fun(Meta, Fun, Receiver, Name, Args, Module, S) ->
|
||||
apply(Fun, [SArg|Args])
|
||||
catch
|
||||
Kind:Reason ->
|
||||
Info = { Receiver, Name, length(Args), [{ file, S#elixir_scope.file }, { line, Line }] },
|
||||
Info = { Receiver, Name, length(Args), location(Line, S) },
|
||||
erlang:raise(Kind, Reason, munge_stacktrace(Info, erlang:get_stacktrace(), SArg))
|
||||
end.
|
||||
|
||||
@@ -195,10 +200,29 @@ expand_macro_named(Meta, Receiver, Name, Arity, Args, Module, S) ->
|
||||
expand_macro_fun(Meta, Fun, Receiver, Name, Args, Module, S).
|
||||
|
||||
translate_expansion(Meta, Tree, S) ->
|
||||
elixir_translator:translate_each(
|
||||
elixir_quote:linify(?line(Meta), Tree),
|
||||
S
|
||||
).
|
||||
Line = ?line(Meta),
|
||||
|
||||
try
|
||||
elixir_translator:translate_each(
|
||||
elixir_quote:linify(Line, Tree),
|
||||
S
|
||||
)
|
||||
catch
|
||||
Kind:Reason ->
|
||||
erlang:raise(Kind, Reason, [mfa(Line, S)|erlang:get_stacktrace()])
|
||||
end.
|
||||
|
||||
mfa(Line, #elixir_scope{module=nil} = S) ->
|
||||
{ elixir_compiler, '__FILE__', 2, location(Line, S) };
|
||||
|
||||
mfa(Line, #elixir_scope{module=Module,function=nil} = S) ->
|
||||
{ Module, '__MODULE__', 0, location(Line, S) };
|
||||
|
||||
mfa(Line, #elixir_scope{module=Module,function={ Name, Arity }} = S) ->
|
||||
{ Module, Name, Arity, location(Line, S) }.
|
||||
|
||||
location(Line, S) ->
|
||||
[{ file, S#elixir_scope.file }, { line, Line }].
|
||||
|
||||
%% Helpers
|
||||
|
||||
@@ -312,13 +336,18 @@ get_optional_macros(Receiver) ->
|
||||
{ error, _ } -> []
|
||||
end.
|
||||
|
||||
%% Functions imported from Kernel module. Sorted on compilation.
|
||||
|
||||
in_elixir_functions() ->
|
||||
elixir_imported_functions() ->
|
||||
try
|
||||
?builtin:'__info__'(functions)
|
||||
catch
|
||||
error:undef -> []
|
||||
error:undef -> in_erlang_functions()
|
||||
end.
|
||||
|
||||
elixir_imported_macros() ->
|
||||
try
|
||||
?builtin:'__info__'(macros)
|
||||
catch
|
||||
error:undef -> in_erlang_macros()
|
||||
end.
|
||||
|
||||
%% Macros imported from Kernel module. Sorted on compilation.
|
||||
@@ -352,7 +381,7 @@ in_erlang_functions() ->
|
||||
{ byte_size, 1 },
|
||||
% { date, 0 },
|
||||
{ exit, 1 },
|
||||
{ float, 1 },
|
||||
% { float, 1 },
|
||||
{ float_to_binary, 1 },
|
||||
{ float_to_list, 1 },
|
||||
{ hd, 1 },
|
||||
|
||||
@@ -1,19 +1,24 @@
|
||||
% A bunch of helpers to help to deal with errors in Elixir source code.
|
||||
% This is not exposed in the Elixir language.
|
||||
-module(elixir_errors).
|
||||
-export([syntax_error/3, syntax_error/4, inspect/1,
|
||||
-export([syntax_error/3, syntax_error/4,
|
||||
compile_error/3, compile_error/4, inspect/1,
|
||||
form_error/4, parse_error/4, assert_module_scope/3,
|
||||
assert_no_function_scope/3, assert_function_scope/3,
|
||||
assert_no_match_scope/3, assert_no_guard_scope/3,
|
||||
assert_no_match_or_guard_scope/3,
|
||||
assert_no_match_or_guard_scope/3, warn/1,
|
||||
handle_file_warning/2, handle_file_warning/3, handle_file_error/2,
|
||||
deprecation/3, deprecation/4, file_format/3]).
|
||||
-include("elixir.hrl").
|
||||
|
||||
warn(Warning) ->
|
||||
elixir_code_server:cast(register_warning),
|
||||
io:put_chars(standard_error, Warning).
|
||||
|
||||
%% Handle inspecting for exceptions
|
||||
|
||||
inspect(Atom) when is_atom(Atom) ->
|
||||
'Elixir.Binary.Inspect.Atom':inspect(Atom, []).
|
||||
'Elixir.Inspect.Atom':inspect(Atom).
|
||||
|
||||
%% Raised during macros translation.
|
||||
|
||||
@@ -30,6 +35,16 @@ syntax_error(Meta, File, Format, Args) ->
|
||||
Message = io_lib:format(Format, Args),
|
||||
raise(Meta, File, 'Elixir.SyntaxError', unicode:characters_to_binary(Message)).
|
||||
|
||||
compile_error(Meta, File, Message) when is_list(Message) ->
|
||||
compile_error(Meta, File, iolist_to_binary(Message));
|
||||
|
||||
compile_error(Meta, File, Message) when is_binary(Message) ->
|
||||
raise(Meta, File, 'Elixir.CompileError', Message).
|
||||
|
||||
compile_error(Meta, File, Format, Args) ->
|
||||
Message = io_lib:format(Format, Args),
|
||||
raise(Meta, File, 'Elixir.CompileError', unicode:characters_to_binary(Message)).
|
||||
|
||||
%% Raised on tokenizing/parsing
|
||||
|
||||
-spec parse_error(non_neg_integer() | list(), file:filename(), iolist() | atom(), [] | iolist()) -> no_return().
|
||||
@@ -93,14 +108,14 @@ handle_file_warning(_, File, {Line,erl_lint,{undefined_behaviour_func,{Fun,Arity
|
||||
Kind = protocol_or_behaviour(Module),
|
||||
Raw = "undefined ~ts function ~ts/~B (for ~ts ~ts)",
|
||||
Message = io_lib:format(Raw, [Kind, Fun, Arity, Kind, inspect(Module)]),
|
||||
io:format(file_format(Line, File, Message));
|
||||
warn(file_format(Line, File, Message));
|
||||
|
||||
handle_file_warning(_, File, {Line,erl_lint,{undefined_behaviour,Module}}) ->
|
||||
case elixir_compiler:get_opt(internal) of
|
||||
true -> [];
|
||||
false ->
|
||||
Message = io_lib:format("behaviour ~ts undefined", [inspect(Module)]),
|
||||
io:format(file_format(Line, File, Message))
|
||||
warn(file_format(Line, File, Message))
|
||||
end;
|
||||
|
||||
handle_file_warning(_, _File, {Line,erl_lint,{unused_var,_Var}}) when Line =< 0 ->
|
||||
@@ -108,16 +123,16 @@ handle_file_warning(_, _File, {Line,erl_lint,{unused_var,_Var}}) when Line =< 0
|
||||
|
||||
handle_file_warning(_, File, {Line,erl_lint,{unused_var,Var}}) ->
|
||||
Message = format_error(erl_lint, { unused_var, format_var(Var) }),
|
||||
io:format(file_format(Line, File, Message));
|
||||
warn(file_format(Line, File, Message));
|
||||
|
||||
handle_file_warning(_, File, {Line,erl_lint,{shadowed_var,Var,Where}}) ->
|
||||
Message = format_error(erl_lint, { shadowed_var, format_var(Var), Where }),
|
||||
io:format(file_format(Line, File, Message));
|
||||
warn(file_format(Line, File, Message));
|
||||
|
||||
%% Default behavior
|
||||
handle_file_warning(_, File, {Line,Module,Desc}) ->
|
||||
Message = format_error(Module, Desc),
|
||||
io:format(file_format(Line, File, Message)).
|
||||
warn(file_format(Line, File, Message)).
|
||||
|
||||
handle_file_warning(File, Desc) ->
|
||||
handle_file_warning(false, File, Desc).
|
||||
|
||||
@@ -196,18 +196,15 @@ intersection([], _All) -> [].
|
||||
|
||||
%% Internal funs that are never imported etc.
|
||||
|
||||
remove_underscored(default, List) -> remove_underscored(List);
|
||||
remove_underscored(_, List) -> List.
|
||||
remove_underscored(default, List) ->
|
||||
lists:filter(fun({ Name, _ }) ->
|
||||
case atom_to_list(Name) of
|
||||
"_" ++ _ -> false;
|
||||
_ -> true
|
||||
end
|
||||
end, List);
|
||||
|
||||
remove_underscored([{ Name, _ } = H|T]) when Name < a ->
|
||||
case atom_to_list(Name) of
|
||||
[$_, $_, _, $_, $_] -> [H|remove_underscored(T)];
|
||||
"_" ++ _ -> remove_underscored(T);
|
||||
_ -> [H|remove_underscored(T)]
|
||||
end;
|
||||
|
||||
remove_underscored(T) ->
|
||||
T.
|
||||
remove_underscored(_, List) -> List.
|
||||
|
||||
remove_internals(Set) ->
|
||||
ordsets:del_element({ module_info, 1 },
|
||||
@@ -221,10 +218,9 @@ special_form() ->
|
||||
{'=',2},
|
||||
{'__op__',2},
|
||||
{'__op__',3},
|
||||
{'__ambiguousop__','*'},
|
||||
{'__scope__',2},
|
||||
{'__block__','*'},
|
||||
{'->','2'},
|
||||
{'->','*'},
|
||||
{'<<>>','*'},
|
||||
{'{}','*'},
|
||||
{'[]','*'},
|
||||
|
||||
@@ -65,7 +65,11 @@ translate(Atom, S) when is_atom(Atom) ->
|
||||
{ { atom, 0, Atom }, S };
|
||||
|
||||
translate(Bitstring, S) when is_bitstring(Bitstring) ->
|
||||
{ elixir_tree_helpers:elixir_to_erl(Bitstring), S }.
|
||||
{ elixir_tree_helpers:elixir_to_erl(Bitstring), S };
|
||||
|
||||
translate(Other, S) ->
|
||||
elixir_errors:compile_error(0, S#elixir_scope.file, "invalid quoted expression: ~ts",
|
||||
['Elixir.Kernel':inspect(Other)]).
|
||||
|
||||
%% Helpers
|
||||
|
||||
@@ -156,8 +160,8 @@ extract_bit_values(Meta, [Size|T], default, Types, S) when is_integer(Size) anda
|
||||
extract_bit_values(Meta, T, {integer, Meta, Size}, Types, S);
|
||||
|
||||
extract_bit_values(Meta, [{ '|', _, [Left, Right] }], Size, Types, S) ->
|
||||
Expanded = 'Elixir.Macro':expand(Right, elixir_scope:to_ex_env({ ?line(Meta), S })),
|
||||
extract_bit_values(Meta, [Left|join_expansion(Expanded,[])], Size, Types, S);
|
||||
{ Expanded, ES } = 'Elixir.Macro':expand_all(Right, elixir_scope:to_ex_env({ ?line(Meta), S }), S),
|
||||
extract_bit_values(Meta, [Left|join_expansion(Expanded,[])], Size, Types, ES);
|
||||
|
||||
extract_bit_values(Meta, [_|_] = All, Size, Types, S) ->
|
||||
handle_unknown_specifier(Meta, All, Size, Types, S);
|
||||
@@ -198,11 +202,11 @@ extract_bit_type_or_size(_Value, _Args, _Other, _Types) ->
|
||||
{ error, unknown }.
|
||||
|
||||
handle_unknown_specifier(Meta, [H|T], Size, Types, S) ->
|
||||
case 'Elixir.Macro':expand(H, elixir_scope:to_ex_env({ ?line(Meta), S })) of
|
||||
H ->
|
||||
elixir_errors:syntax_error(Meta, S#elixir_scope.file, "unknown bitstring specifier ~ts", ['Elixir.Macro':to_binary(H)]);
|
||||
E ->
|
||||
extract_bit_values(Meta, join_expansion(E,T), Size, Types, S)
|
||||
case 'Elixir.Macro':expand_all(H, elixir_scope:to_ex_env({ ?line(Meta), S }), S) of
|
||||
{ H, ES } ->
|
||||
elixir_errors:syntax_error(Meta, ES#elixir_scope.file, "unknown bitstring specifier ~ts", ['Elixir.Macro':to_string(H)]);
|
||||
{ E, ES } ->
|
||||
extract_bit_values(Meta, join_expansion(E,T), Size, Types, ES)
|
||||
end.
|
||||
|
||||
join_expansion({ '__block__', _, [Expanded] }, Tail) -> join_expansion(Expanded, Tail);
|
||||
|
||||
@@ -3,9 +3,11 @@
|
||||
-module(elixir_macros).
|
||||
-export([translate/2]).
|
||||
-import(elixir_translator, [translate_each/2, translate_args/2, translate_apply/7]).
|
||||
-import(elixir_scope, [umergec/2]).
|
||||
-import(elixir_errors, [syntax_error/3, syntax_error/4,
|
||||
assert_no_function_scope/3, assert_module_scope/3, assert_no_match_or_guard_scope/3]).
|
||||
-import(elixir_scope, [umergec/2, umergea/2]).
|
||||
-import(elixir_errors, [compile_error/3, compile_error/4,
|
||||
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).
|
||||
@@ -70,13 +72,15 @@ translate({ function, MetaFA, [{ '/', _, [{F, Meta, C}, A]}] }, S) when is_atom(
|
||||
end,
|
||||
|
||||
case elixir_dispatch:import_function(WrappedMeta, F, A, S) of
|
||||
false -> syntax_error(WrappedMeta, S#elixir_scope.file, "expected ~ts/~B to be a function, but it is a macro", [F, A]);
|
||||
false -> compile_error(WrappedMeta, S#elixir_scope.file,
|
||||
"expected ~ts/~B to be a function, but it is a macro", [F, A]);
|
||||
Else -> Else
|
||||
end;
|
||||
|
||||
translate({ function, Meta, [_] }, S) ->
|
||||
translate({ function, Meta, [Arg] }, S) ->
|
||||
assert_no_match_or_guard_scope(Meta, 'function', S),
|
||||
syntax_error(Meta, S#elixir_scope.file, "invalid args for function");
|
||||
syntax_error(Meta, S#elixir_scope.file, "invalid args for function/1: ~ts",
|
||||
['Elixir.Macro':to_string(Arg)]);
|
||||
|
||||
translate({ function, Meta, [_,_,_] = Args }, S) when is_list(Args) ->
|
||||
assert_no_match_or_guard_scope(Meta, 'function', S),
|
||||
@@ -119,7 +123,7 @@ translate({'@', Meta, [{ Name, _, Args }]}, S) ->
|
||||
translate_each({
|
||||
{ '.', Meta, ['Elixir.Module', get_attribute] },
|
||||
Meta,
|
||||
[ { '__MODULE__', Meta, false }, Name ]
|
||||
[ { '__MODULE__', Meta, false }, Name, true ]
|
||||
}, S);
|
||||
_ ->
|
||||
Contents = 'Elixir.Module':get_attribute(S#elixir_scope.module, Name),
|
||||
@@ -132,7 +136,7 @@ translate({'@', Meta, [{ Name, _, Args }]}, S) ->
|
||||
|
||||
%% Case
|
||||
|
||||
translate({'case', Meta, [Expr, KV]}, S) ->
|
||||
translate({'case', Meta, [Expr, KV]}, S) when is_list(KV) ->
|
||||
assert_no_match_or_guard_scope(Meta, 'case', S),
|
||||
Clauses = elixir_clauses:get_pairs(Meta, do, KV, S),
|
||||
{ TExpr, NS } = translate_each(Expr, S),
|
||||
@@ -147,27 +151,27 @@ translate({'case', Meta, [Expr, KV]}, S) ->
|
||||
|
||||
%% Try
|
||||
|
||||
translate({'try', Meta, [Clauses]}, S) ->
|
||||
translate({'try', Meta, [Clauses]}, S) when is_list(Clauses) ->
|
||||
assert_no_match_or_guard_scope(Meta, 'try', S),
|
||||
|
||||
Do = proplists:get_value('do', Clauses, nil),
|
||||
{ TDo, SB } = elixir_translator:translate_each(Do, S#elixir_scope{noname=true}),
|
||||
|
||||
Catch = [Tuple || { X, _ } = Tuple <- Clauses, X == 'rescue' orelse X == 'catch'],
|
||||
{ TCatch, SC } = elixir_try:clauses(Meta, Catch, umergec(S, SB)),
|
||||
{ TCatch, SC } = elixir_try:clauses(Meta, Catch, umergea(S, SB)),
|
||||
|
||||
After = proplists:get_value('after', Clauses, nil),
|
||||
{ TAfter, SA } = elixir_translator:translate_each(After, umergec(S, SC)),
|
||||
{ TAfter, SA } = elixir_translator:translate_each(After, umergea(S, SC)),
|
||||
|
||||
Else = elixir_clauses:get_pairs(Meta, else, Clauses, S),
|
||||
{ TElse, SE } = elixir_clauses:match(Meta, Else, umergec(S, SA)),
|
||||
{ TElse, SE } = elixir_clauses:match(Meta, Else, umergea(S, SA)),
|
||||
|
||||
SF = (umergec(S, SE))#elixir_scope{noname=S#elixir_scope.noname},
|
||||
{ { 'try', ?line(Meta), pack(TDo), TElse, TCatch, pack(TAfter) }, SF };
|
||||
|
||||
%% Receive
|
||||
|
||||
translate({'receive', Meta, [KV] }, S) ->
|
||||
translate({'receive', Meta, [KV] }, S) when is_list(KV) ->
|
||||
assert_no_match_or_guard_scope(Meta, 'receive', S),
|
||||
Do = elixir_clauses:get_pairs(Meta, do, KV, S, true),
|
||||
|
||||
@@ -185,12 +189,12 @@ translate({'receive', Meta, [KV] }, S) ->
|
||||
|
||||
%% Definitions
|
||||
|
||||
translate({defmodule, Meta, [Ref, KV]}, S) ->
|
||||
translate({defmodule, Meta, [Ref, KV]}, S) when is_list(KV) ->
|
||||
{ TRef, _ } = translate_each(Ref, S),
|
||||
|
||||
Block = case lists:keyfind(do, 1, KV) of
|
||||
{ do, DoValue } -> DoValue;
|
||||
false -> syntax_error(Meta, S#elixir_scope.file, "expected do: argument in defmodule")
|
||||
false -> syntax_error(Meta, S#elixir_scope.file, "missing do keyword in defmodule")
|
||||
end,
|
||||
|
||||
{ FRef, FS } = case TRef of
|
||||
@@ -243,7 +247,11 @@ translate({ apply, Meta, [Left, Right, Args] }, S) when is_list(Args) ->
|
||||
|
||||
translate({ apply, Meta, Args }, S) ->
|
||||
{ TArgs, NS } = translate_args(Args, S),
|
||||
{ ?wrap_call(?line(Meta), erlang, apply, TArgs), NS }.
|
||||
{ ?wrap_call(?line(Meta), erlang, apply, TArgs), NS };
|
||||
|
||||
translate({ Name, Meta, Args }, S) ->
|
||||
syntax_error(Meta, S#elixir_scope.file,
|
||||
"invalid arguments for macro ~ts/~B", [Name, length(Args)]).
|
||||
|
||||
%% Helpers
|
||||
|
||||
@@ -267,6 +275,9 @@ translate_in(Meta, Left, Right, S) ->
|
||||
end,
|
||||
|
||||
{ InGuard, TExpr } = case TRight of
|
||||
{ nil, _ } ->
|
||||
Expr = { atom, Line, false },
|
||||
{ Cache, Expr };
|
||||
{ cons, _, _, _ } ->
|
||||
[H|T] = elixir_tree_helpers:cons_to_list(TRight),
|
||||
Expr = lists:foldr(fun(X, Acc) ->
|
||||
@@ -299,7 +310,8 @@ translate_in(Meta, Left, Right, S) ->
|
||||
true ->
|
||||
{ false, ?wrap_call(Line, 'Elixir.Enum', 'member?', [TRight, TLeft]) };
|
||||
false ->
|
||||
syntax_error(Meta, S#elixir_scope.file, "invalid args for operator in, it expects an explicit array or an explicit range on the right side when used in guard expressions")
|
||||
compile_error(Meta, S#elixir_scope.file, "invalid args for operator in, it expects a "
|
||||
"compile time list or range on the right side when used in guard expressions")
|
||||
end
|
||||
end,
|
||||
|
||||
@@ -318,15 +330,15 @@ decreasing_compare(Line, Var, Start, End) ->
|
||||
{ op, Line, '=<', Var, Start },
|
||||
{ op, Line, '>=', Var, End } }.
|
||||
|
||||
rewrite_case_clauses([{do,[{in,_,[{'_',_,_},[false,nil]]}],False},{do,[{'_',_,_}],True}]) ->
|
||||
[{do,[false],False},{do,[true],True}];
|
||||
rewrite_case_clauses([{do,Meta1,[{in,_,[{'_',_,_},[false,nil]]}],False},{do,Meta2,[{'_',_,_}],True}]) ->
|
||||
[{do,Meta1,[false],False},{do,Meta2,[true],True}];
|
||||
|
||||
rewrite_case_clauses(Clauses) ->
|
||||
Clauses.
|
||||
|
||||
%% defmodule :foo
|
||||
expand_module(Raw, Module, #elixir_scope{module=Nesting}) when is_atom(Raw); Nesting == nil ->
|
||||
Module;
|
||||
expand_module(Raw, _Module, _S) when is_atom(Raw) ->
|
||||
Raw;
|
||||
|
||||
%% defmodule Hello
|
||||
expand_module({ '__aliases__', _, [H] }, _Module, S) ->
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
-define(persisted_attr, '__persisted_attributes').
|
||||
|
||||
eval_quoted(Module, Quoted, RawBinding, Opts) ->
|
||||
Binding = binding_for_eval(Module, RawBinding),
|
||||
Binding = elixir_scope:binding_for_eval(RawBinding, Module),
|
||||
Scope = scope_for_eval(Module, Opts),
|
||||
|
||||
elixir_def:reset_last(Module),
|
||||
@@ -27,8 +27,6 @@ scope_for_eval(Module, #elixir_scope{} = S) ->
|
||||
scope_for_eval(Module, Opts) ->
|
||||
scope_for_eval(Module, elixir:scope_for_eval(Opts)).
|
||||
|
||||
binding_for_eval(Module, Binding) -> [{'_@MODULE',Module}|Binding].
|
||||
|
||||
%% TABLE METHODS
|
||||
|
||||
data_table(Module) ->
|
||||
@@ -247,7 +245,7 @@ spec_for_macro(Else) -> Else.
|
||||
%% Loads the form into the code server.
|
||||
|
||||
load_form(Line, Forms, S) ->
|
||||
elixir_compiler:module(Forms, S, fun(Module, Binary) ->
|
||||
elixir_compiler:module(Forms, S#elixir_scope.file, fun(Module, Binary) ->
|
||||
EvalS = scope_for_eval(Module, S),
|
||||
Env = elixir_scope:to_ex_env({ Line, EvalS }),
|
||||
eval_callbacks(Line, Module, after_compile, [Env, Binary], EvalS),
|
||||
@@ -332,8 +330,9 @@ else_clause() ->
|
||||
|
||||
% HELPERS
|
||||
|
||||
eval_callbacks(Line, Module, Name, Args, S) ->
|
||||
Binding = binding_for_eval(Module, []),
|
||||
eval_callbacks(Line, Module, Name, Args, RawS) ->
|
||||
Binding = elixir_scope:binding_for_eval([], Module),
|
||||
S = elixir_scope:vars_from_binding(RawS, Binding),
|
||||
Callbacks = lists:reverse(ets:lookup_element(data_table(Module), Name, 2)),
|
||||
Meta = [{line,Line},{require,false}],
|
||||
|
||||
|
||||
+294
-276
@@ -1,21 +1,24 @@
|
||||
Nonterminals
|
||||
grammar expr_list
|
||||
expr paren_expr block_expr fn_expr bracket_expr call_expr bracket_at_expr max_expr
|
||||
base_expr matched_expr matched_op_expr unmatched_expr op_expr
|
||||
add_op mult_op unary_op two_op regex_op right_op bin_concat_op
|
||||
match_op send_op default_op when_op pipe_op in_op inc_op range_op
|
||||
andand_op oror_op and_op or_op comp_expr_op colon_colon_op three_op at_op
|
||||
expr container_expr block_expr no_parens_expr identifier_expr max_expr
|
||||
bracket_expr bracket_at_expr base_expr matched_expr unmatched_expr
|
||||
op_expr matched_op_expr no_parens_op_expr
|
||||
comp_op_eol at_op_eol unary_op_eol and_op_eol or_op_eol tail_op_eol
|
||||
add_op_eol mult_op_eol exp_op_eol two_op_eol type_op_eol stab_op_eol
|
||||
arrow_op_eol range_op_eol than_op_eol default_op_eol match_op_eol
|
||||
when_op_eol in_op_eol inc_op_eol
|
||||
open_paren close_paren empty_paren
|
||||
open_bracket close_bracket
|
||||
open_curly close_curly
|
||||
open_bit close_bit
|
||||
base_comma_expr comma_expr optional_comma_expr matched_comma_expr
|
||||
call_args call_args_parens parens_call
|
||||
call_args_no_parens call_args_no_parens_strict call_args_parens_not_one
|
||||
stab stab_eol stab_op stab_expr stab_maybe_expr
|
||||
kw_eol kw_expr kw_comma kw_base
|
||||
matched_kw_expr matched_kw_comma matched_kw_base
|
||||
dot_op dot_ref dot_identifier dot_op_identifier dot_do_identifier
|
||||
container_comma_expr_base container_comma_expr container_arg container_args
|
||||
call_args_parens_comma_expr call_args_parens parens_call
|
||||
call_args_no_parens_one call_args_no_parens_expr call_args_no_parens_comma_expr
|
||||
call_args_no_parens_all call_args_no_parens_many call_args_no_parens_many_strict
|
||||
stab stab_eol stab_expr stab_maybe_expr stab_parens_many
|
||||
kw_eol kw_expr kw_comma kw
|
||||
call_args_no_parens_kw_expr call_args_no_parens_kw_comma call_args_no_parens_kw
|
||||
dot_op dot_alias dot_identifier dot_op_identifier dot_do_identifier
|
||||
dot_paren_identifier dot_punctuated_identifier dot_bracket_identifier
|
||||
var list bracket_access bit_string tuple
|
||||
do_block fn_eol do_eol end_eol block_eol block_item block_list
|
||||
@@ -26,46 +29,43 @@ Terminals
|
||||
bracket_identifier paren_identifier do_identifier block_identifier
|
||||
fn 'end' aliases
|
||||
number signed_number atom bin_string list_string sigil
|
||||
dot_call_op comp_op op_identifier
|
||||
'not' 'and' 'or' 'xor' 'when' 'in' 'inlist' 'inbits' 'do'
|
||||
'true' 'false' 'nil'
|
||||
'=' '+' '-' '*' '/' '++' '--' '**' '//'
|
||||
'(' ')' '[' ']' '{' '}' '<<' '>>' '::'
|
||||
eol ',' '&' '|' '.' '^' '@' '<-' '<>' '->' '|>' '=~'
|
||||
'&&' '||' '!' '...' '..'
|
||||
'<<<' '>>>' '&&&' '|||' '^^^' '~~~'
|
||||
dot_call_op op_identifier
|
||||
comp_op at_op unary_op and_op or_op arrow_op match_op
|
||||
range_op in_op inc_op when_op than_op default_op tail_op
|
||||
dual_op add_op mult_op exp_op two_op type_op stab_op
|
||||
'true' 'false' 'nil' 'do' eol ',' '.' '&'
|
||||
'(' ')' '[' ']' '{' '}' '<<' '>>'
|
||||
.
|
||||
|
||||
Rootsymbol grammar.
|
||||
|
||||
%% There are two shift/reduce conflicts coming from call_args_parens.
|
||||
Expect 2.
|
||||
|
||||
Left 5 do.
|
||||
Right 10 stab_op.
|
||||
Left 20 ','. % Solve nested call_args conflicts
|
||||
Right 30 colon_colon_op.
|
||||
Right 40 when_op.
|
||||
Right 50 default_op.
|
||||
Left 60 pipe_op.
|
||||
Left 70 inc_op.
|
||||
Right 80 match_op.
|
||||
Right 110 send_op.
|
||||
Left 120 oror_op.
|
||||
Left 130 andand_op.
|
||||
Left 140 or_op.
|
||||
Left 150 and_op.
|
||||
Left 160 comp_expr_op.
|
||||
Left 170 in_op.
|
||||
Right 180 regex_op.
|
||||
Right 190 right_op.
|
||||
Left 200 range_op.
|
||||
Left 210 three_op.
|
||||
Left 220 add_op.
|
||||
Left 230 mult_op.
|
||||
Right 240 bin_concat_op.
|
||||
Right 250 two_op.
|
||||
Nonassoc 300 unary_op.
|
||||
Right 10 stab_op_eol. %% ->
|
||||
Left 20 ','.
|
||||
Right 30 type_op_eol. %% ::
|
||||
Right 40 when_op_eol. %% when
|
||||
Left 50 inc_op_eol. %% inlist, inbits
|
||||
Right 60 default_op_eol. %% //
|
||||
Left 70 tail_op_eol. %% |
|
||||
Right 80 match_op_eol. %% =
|
||||
Left 130 or_op_eol. %% ||, |||, or, xor
|
||||
Left 140 and_op_eol. %% &&, &&&, and
|
||||
Left 150 comp_op_eol. %% <, >, <=, >=, ==, !=, =~, ===, !==
|
||||
Right 160 arrow_op_eol. %% < (op), (op) > (e.g <-, |>, <<<, >>>)
|
||||
Left 170 in_op_eol. %% in
|
||||
Left 200 range_op_eol. %% ..
|
||||
Left 210 add_op_eol. %% + (op), - (op)
|
||||
Left 220 mult_op_eol. %% * (op), / (op)
|
||||
Right 230 than_op_eol. %% < (op) > (e.g <>)
|
||||
Right 240 two_op_eol. %% ++, --, **
|
||||
Left 250 exp_op_eol. %% ^ (op) (e.g ^^^)
|
||||
Nonassoc 300 unary_op_eol. %% +, -, !, ^, not, ~~~
|
||||
Left 310 dot_call_op.
|
||||
Left 310 dot_op.
|
||||
Nonassoc 320 at_op.
|
||||
Left 310 dot_op. %% .
|
||||
Nonassoc 320 at_op_eol. %% @ (op)
|
||||
Nonassoc 330 var.
|
||||
|
||||
%%% MAIN FLOW OF EXPRESSIONS
|
||||
@@ -79,116 +79,132 @@ grammar -> '$empty' : [nil].
|
||||
|
||||
% Note expressions are on reverse order
|
||||
expr_list -> expr : ['$1'].
|
||||
expr_list -> empty_paren : [nil].
|
||||
expr_list -> expr_list eol expr : ['$3'|'$1'].
|
||||
|
||||
paren_expr -> empty_paren : nil.
|
||||
paren_expr -> expr : '$1'.
|
||||
|
||||
expr -> empty_paren : nil.
|
||||
expr -> matched_expr : '$1'.
|
||||
expr -> no_parens_expr : '$1'.
|
||||
expr -> unmatched_expr : '$1'.
|
||||
|
||||
matched_expr -> matched_expr matched_op_expr : build_op(element(1, '$2'), '$1', element(2, '$2')).
|
||||
matched_expr -> unary_op matched_expr : build_unary_op('$1', '$2').
|
||||
matched_expr -> at_op matched_expr : build_unary_op('$1', '$2').
|
||||
matched_expr -> matched_expr no_parens_op_expr : build_op(element(1, '$2'), '$1', element(2, '$2')).
|
||||
matched_expr -> unary_op_eol matched_expr : build_unary_op('$1', '$2').
|
||||
matched_expr -> unary_op_eol no_parens_expr : build_unary_op('$1', '$2').
|
||||
matched_expr -> at_op_eol matched_expr : build_unary_op('$1', '$2').
|
||||
matched_expr -> at_op_eol no_parens_expr : build_unary_op('$1', '$2').
|
||||
matched_expr -> bracket_at_expr : '$1'.
|
||||
matched_expr -> fn_expr : '$1'.
|
||||
matched_expr -> identifier_expr : '$1'.
|
||||
|
||||
no_parens_expr -> dot_punctuated_identifier call_args_no_parens_many_strict : build_identifier('$1', '$2').
|
||||
no_parens_expr -> dot_op_identifier call_args_no_parens_many_strict : build_identifier('$1', '$2').
|
||||
no_parens_expr -> dot_identifier call_args_no_parens_many_strict : build_identifier('$1', '$2').
|
||||
|
||||
unmatched_expr -> empty_paren op_expr : build_op(element(1, '$2'), nil, element(2, '$2')).
|
||||
unmatched_expr -> matched_expr op_expr : build_op(element(1, '$2'), '$1', element(2, '$2')).
|
||||
unmatched_expr -> unmatched_expr op_expr : build_op(element(1, '$2'), '$1', element(2, '$2')).
|
||||
unmatched_expr -> unary_op expr : build_unary_op('$1', '$2').
|
||||
unmatched_expr -> at_op expr : build_unary_op('$1', '$2').
|
||||
unmatched_expr -> unary_op_eol expr : build_unary_op('$1', '$2').
|
||||
unmatched_expr -> at_op_eol expr : build_unary_op('$1', '$2').
|
||||
unmatched_expr -> block_expr : '$1'.
|
||||
|
||||
op_expr -> match_op expr : { '$1', '$2' }.
|
||||
op_expr -> add_op expr : { '$1', '$2' }.
|
||||
op_expr -> mult_op expr : { '$1', '$2' }.
|
||||
op_expr -> two_op expr : { '$1', '$2' }.
|
||||
op_expr -> regex_op expr : { '$1', '$2' }.
|
||||
op_expr -> right_op expr : { '$1', '$2' }.
|
||||
op_expr -> andand_op expr : { '$1', '$2' }.
|
||||
op_expr -> three_op expr : { '$1', '$2' }.
|
||||
op_expr -> oror_op expr : { '$1', '$2' }.
|
||||
op_expr -> and_op expr : { '$1', '$2' }.
|
||||
op_expr -> or_op expr : { '$1', '$2' }.
|
||||
op_expr -> pipe_op expr : { '$1', '$2' }.
|
||||
op_expr -> bin_concat_op expr : { '$1', '$2' }.
|
||||
op_expr -> in_op expr : { '$1', '$2' }.
|
||||
op_expr -> inc_op expr : { '$1', '$2' }.
|
||||
op_expr -> when_op expr : { '$1', '$2' }.
|
||||
op_expr -> send_op expr : { '$1', '$2' }.
|
||||
op_expr -> range_op expr : { '$1', '$2' }.
|
||||
op_expr -> default_op expr : { '$1', '$2' }.
|
||||
op_expr -> colon_colon_op expr : { '$1', '$2' }.
|
||||
op_expr -> comp_expr_op expr : { '$1', '$2' }.
|
||||
|
||||
matched_op_expr -> match_op matched_expr : { '$1', '$2' }.
|
||||
matched_op_expr -> add_op matched_expr : { '$1', '$2' }.
|
||||
matched_op_expr -> mult_op matched_expr : { '$1', '$2' }.
|
||||
matched_op_expr -> two_op matched_expr : { '$1', '$2' }.
|
||||
matched_op_expr -> regex_op matched_expr : { '$1', '$2' }.
|
||||
matched_op_expr -> right_op matched_expr : { '$1', '$2' }.
|
||||
matched_op_expr -> andand_op matched_expr : { '$1', '$2' }.
|
||||
matched_op_expr -> three_op matched_expr : { '$1', '$2' }.
|
||||
matched_op_expr -> oror_op matched_expr : { '$1', '$2' }.
|
||||
matched_op_expr -> and_op matched_expr : { '$1', '$2' }.
|
||||
matched_op_expr -> or_op matched_expr : { '$1', '$2' }.
|
||||
matched_op_expr -> pipe_op matched_expr : { '$1', '$2' }.
|
||||
matched_op_expr -> bin_concat_op matched_expr : { '$1', '$2' }.
|
||||
matched_op_expr -> in_op matched_expr : { '$1', '$2' }.
|
||||
matched_op_expr -> inc_op matched_expr : { '$1', '$2' }.
|
||||
matched_op_expr -> when_op matched_expr : { '$1', '$2' }.
|
||||
matched_op_expr -> send_op matched_expr : { '$1', '$2' }.
|
||||
matched_op_expr -> range_op matched_expr : { '$1', '$2' }.
|
||||
matched_op_expr -> default_op matched_expr : { '$1', '$2' }.
|
||||
matched_op_expr -> colon_colon_op matched_expr : { '$1', '$2' }.
|
||||
matched_op_expr -> comp_expr_op matched_expr : { '$1', '$2' }.
|
||||
|
||||
block_expr -> parens_call call_args_parens do_block : build_identifier('$1', '$2' ++ '$3').
|
||||
block_expr -> parens_call call_args_parens call_args_parens do_block : build_nested_parens('$1', '$2', '$3' ++ '$4').
|
||||
block_expr -> dot_punctuated_identifier call_args_no_parens_strict do_block : build_identifier('$1', '$2' ++ '$3').
|
||||
block_expr -> dot_do_identifier do_block : build_identifier('$1', '$2').
|
||||
block_expr -> dot_identifier call_args_no_parens_strict do_block : build_identifier('$1', '$2' ++ '$3').
|
||||
block_expr -> dot_punctuated_identifier call_args_no_parens_all do_block : build_identifier('$1', '$2' ++ '$3').
|
||||
block_expr -> dot_identifier call_args_no_parens_all do_block : build_identifier('$1', '$2' ++ '$3').
|
||||
|
||||
fn_expr -> fn_eol stab end_eol : build_fn('$1', build_stab(lists:reverse('$2'))).
|
||||
fn_expr -> call_expr : '$1'.
|
||||
op_expr -> match_op_eol expr : { '$1', '$2' }.
|
||||
op_expr -> add_op_eol expr : { '$1', '$2' }.
|
||||
op_expr -> mult_op_eol expr : { '$1', '$2' }.
|
||||
op_expr -> exp_op_eol expr : { '$1', '$2' }.
|
||||
op_expr -> two_op_eol expr : { '$1', '$2' }.
|
||||
op_expr -> and_op_eol expr : { '$1', '$2' }.
|
||||
op_expr -> or_op_eol expr : { '$1', '$2' }.
|
||||
op_expr -> tail_op_eol expr : { '$1', '$2' }.
|
||||
op_expr -> than_op_eol expr : { '$1', '$2' }.
|
||||
op_expr -> in_op_eol expr : { '$1', '$2' }.
|
||||
op_expr -> inc_op_eol expr : { '$1', '$2' }.
|
||||
op_expr -> when_op_eol expr : { '$1', '$2' }.
|
||||
op_expr -> range_op_eol expr : { '$1', '$2' }.
|
||||
op_expr -> default_op_eol expr : { '$1', '$2' }.
|
||||
op_expr -> type_op_eol expr : { '$1', '$2' }.
|
||||
op_expr -> comp_op_eol expr : { '$1', '$2' }.
|
||||
op_expr -> arrow_op_eol expr : { '$1', '$2' }.
|
||||
|
||||
call_expr -> dot_punctuated_identifier call_args_no_parens_strict : build_identifier('$1', '$2').
|
||||
call_expr -> dot_op_identifier call_args_no_parens_strict : build_identifier('$1', '$2').
|
||||
call_expr -> dot_identifier call_args_no_parens_strict : build_identifier('$1', '$2').
|
||||
call_expr -> dot_punctuated_identifier : build_identifier('$1', []).
|
||||
call_expr -> dot_do_identifier : build_identifier('$1', nil).
|
||||
call_expr -> var : build_identifier('$1', nil).
|
||||
call_expr -> max_expr : '$1'.
|
||||
no_parens_op_expr -> match_op_eol no_parens_expr : { '$1', '$2' }.
|
||||
no_parens_op_expr -> add_op_eol no_parens_expr : { '$1', '$2' }.
|
||||
no_parens_op_expr -> mult_op_eol no_parens_expr : { '$1', '$2' }.
|
||||
no_parens_op_expr -> exp_op_eol no_parens_expr : { '$1', '$2' }.
|
||||
no_parens_op_expr -> two_op_eol no_parens_expr : { '$1', '$2' }.
|
||||
no_parens_op_expr -> and_op_eol no_parens_expr : { '$1', '$2' }.
|
||||
no_parens_op_expr -> or_op_eol no_parens_expr : { '$1', '$2' }.
|
||||
no_parens_op_expr -> tail_op_eol no_parens_expr : { '$1', '$2' }.
|
||||
no_parens_op_expr -> than_op_eol no_parens_expr : { '$1', '$2' }.
|
||||
no_parens_op_expr -> in_op_eol no_parens_expr : { '$1', '$2' }.
|
||||
no_parens_op_expr -> inc_op_eol no_parens_expr : { '$1', '$2' }.
|
||||
no_parens_op_expr -> when_op_eol no_parens_expr : { '$1', '$2' }.
|
||||
no_parens_op_expr -> range_op_eol no_parens_expr : { '$1', '$2' }.
|
||||
no_parens_op_expr -> default_op_eol no_parens_expr : { '$1', '$2' }.
|
||||
no_parens_op_expr -> type_op_eol no_parens_expr : { '$1', '$2' }.
|
||||
no_parens_op_expr -> comp_op_eol no_parens_expr : { '$1', '$2' }.
|
||||
no_parens_op_expr -> arrow_op_eol no_parens_expr : { '$1', '$2' }.
|
||||
|
||||
matched_op_expr -> match_op_eol matched_expr : { '$1', '$2' }.
|
||||
matched_op_expr -> add_op_eol matched_expr : { '$1', '$2' }.
|
||||
matched_op_expr -> mult_op_eol matched_expr : { '$1', '$2' }.
|
||||
matched_op_expr -> exp_op_eol matched_expr : { '$1', '$2' }.
|
||||
matched_op_expr -> two_op_eol matched_expr : { '$1', '$2' }.
|
||||
matched_op_expr -> and_op_eol matched_expr : { '$1', '$2' }.
|
||||
matched_op_expr -> or_op_eol matched_expr : { '$1', '$2' }.
|
||||
matched_op_expr -> tail_op_eol matched_expr : { '$1', '$2' }.
|
||||
matched_op_expr -> than_op_eol matched_expr : { '$1', '$2' }.
|
||||
matched_op_expr -> in_op_eol matched_expr : { '$1', '$2' }.
|
||||
matched_op_expr -> inc_op_eol matched_expr : { '$1', '$2' }.
|
||||
matched_op_expr -> when_op_eol matched_expr : { '$1', '$2' }.
|
||||
matched_op_expr -> range_op_eol matched_expr : { '$1', '$2' }.
|
||||
matched_op_expr -> default_op_eol matched_expr : { '$1', '$2' }.
|
||||
matched_op_expr -> type_op_eol matched_expr : { '$1', '$2' }.
|
||||
matched_op_expr -> comp_op_eol matched_expr : { '$1', '$2' }.
|
||||
matched_op_expr -> arrow_op_eol matched_expr : { '$1', '$2' }.
|
||||
|
||||
identifier_expr -> dot_punctuated_identifier call_args_no_parens_one : build_identifier('$1', '$2').
|
||||
identifier_expr -> dot_op_identifier call_args_no_parens_one : build_identifier('$1', '$2').
|
||||
identifier_expr -> dot_identifier call_args_no_parens_one : build_identifier('$1', '$2').
|
||||
identifier_expr -> dot_punctuated_identifier : build_identifier('$1', []).
|
||||
identifier_expr -> dot_do_identifier : build_identifier('$1', nil).
|
||||
identifier_expr -> var : build_identifier('$1', nil).
|
||||
identifier_expr -> max_expr : '$1'.
|
||||
|
||||
max_expr -> fn_eol stab end_eol : build_fn('$1', build_stab(lists:reverse('$2'))).
|
||||
max_expr -> open_paren stab close_paren : build_stab(lists:reverse('$2')).
|
||||
max_expr -> bracket_expr : '$1'.
|
||||
max_expr -> parens_call call_args_parens : build_identifier('$1', '$2').
|
||||
max_expr -> parens_call call_args_parens call_args_parens : build_nested_parens('$1', '$2', '$3').
|
||||
max_expr -> dot_ref : '$1'.
|
||||
max_expr -> dot_alias : '$1'.
|
||||
max_expr -> base_expr : '$1'.
|
||||
max_expr -> open_paren stab close_paren : build_stab(lists:reverse('$2')).
|
||||
|
||||
bracket_expr -> dot_bracket_identifier bracket_access : build_access(build_identifier('$1', nil), '$2').
|
||||
bracket_expr -> max_expr bracket_access : build_access('$1', '$2').
|
||||
|
||||
bracket_at_expr -> at_op dot_bracket_identifier bracket_access : build_access(build_unary_op('$1', build_identifier('$2', nil)), '$3').
|
||||
bracket_at_expr -> at_op max_expr bracket_access : build_access(build_unary_op('$1', '$2'), '$3').
|
||||
bracket_at_expr -> bracket_at_expr bracket_access : build_access('$1', '$2').
|
||||
bracket_at_expr -> at_op_eol dot_bracket_identifier bracket_access :
|
||||
build_access(build_unary_op('$1', build_identifier('$2', nil)), '$3').
|
||||
bracket_at_expr -> at_op_eol max_expr bracket_access :
|
||||
build_access(build_unary_op('$1', '$2'), '$3').
|
||||
bracket_at_expr -> bracket_at_expr bracket_access :
|
||||
build_access('$1', '$2').
|
||||
|
||||
base_expr -> number : ?exprs('$1').
|
||||
base_expr -> signed_number : { element(4, '$1'), [{line,?line('$1')}], ?exprs('$1') }.
|
||||
base_expr -> atom : build_atom('$1').
|
||||
base_expr -> list : '$1'.
|
||||
base_expr -> tuple : '$1'.
|
||||
base_expr -> 'true' : ?op('$1').
|
||||
base_expr -> 'false' : ?op('$1').
|
||||
base_expr -> 'nil' : ?op('$1').
|
||||
base_expr -> 'true' : ?id('$1').
|
||||
base_expr -> 'false' : ?id('$1').
|
||||
base_expr -> 'nil' : ?id('$1').
|
||||
base_expr -> aliases : { '__aliases__', [{line,?line('$1')}], ?exprs('$1') }.
|
||||
base_expr -> bin_string : build_bin_string('$1').
|
||||
base_expr -> list_string : build_list_string('$1').
|
||||
base_expr -> bit_string : '$1'.
|
||||
base_expr -> '&' : { '&', [{line,?line('$1')}], ?exprs('$1') }.
|
||||
base_expr -> '...' : { ?op('$1'), [{line,?line('$1')}], [] }.
|
||||
base_expr -> sigil : build_sigil('$1').
|
||||
|
||||
%% Blocks
|
||||
@@ -217,9 +233,13 @@ stab_eol -> stab : '$1'.
|
||||
stab_eol -> stab eol : '$1'.
|
||||
|
||||
stab_expr -> expr : '$1'.
|
||||
stab_expr -> stab_op stab_maybe_expr : build_op('$1', [], '$2').
|
||||
stab_expr -> call_args_no_parens stab_op stab_maybe_expr : build_op('$2', unwrap_splice('$1'), '$3').
|
||||
stab_expr -> call_args_parens_not_one stab_op stab_maybe_expr : build_op('$2', unwrap_splice('$1'), '$3').
|
||||
stab_expr -> stab_op_eol stab_maybe_expr : build_op('$1', [], '$2').
|
||||
stab_expr -> call_args_no_parens_all stab_op_eol stab_maybe_expr :
|
||||
build_op('$2', unwrap_when(unwrap_splice('$1')), '$3').
|
||||
stab_expr -> stab_parens_many stab_op_eol stab_maybe_expr :
|
||||
build_op('$2', unwrap_splice('$1'), '$3').
|
||||
stab_expr -> stab_parens_many when_op expr stab_op_eol stab_maybe_expr :
|
||||
build_op('$4', [{ 'when', [{line,?line('$2')}], unwrap_splice('$1') ++ ['$3'] }], '$5').
|
||||
|
||||
stab_maybe_expr -> 'expr' : '$1'.
|
||||
stab_maybe_expr -> '$empty' : nil.
|
||||
@@ -239,7 +259,7 @@ open_paren -> '(' eol : '$1'.
|
||||
close_paren -> ')' : '$1'.
|
||||
close_paren -> eol ')' : '$2'.
|
||||
|
||||
empty_paren -> open_paren ')' : nil.
|
||||
empty_paren -> open_paren ')' : '$1'.
|
||||
|
||||
open_bracket -> '[' : '$1'.
|
||||
open_bracket -> '[' eol : '$1'.
|
||||
@@ -258,107 +278,69 @@ close_curly -> eol '}' : '$2'.
|
||||
|
||||
% Operators
|
||||
|
||||
add_op -> '+' : '$1'.
|
||||
add_op -> '-' : '$1'.
|
||||
add_op -> '+' eol : '$1'.
|
||||
add_op -> '-' eol : '$1'.
|
||||
add_op_eol -> add_op : '$1'.
|
||||
add_op_eol -> add_op eol : '$1'.
|
||||
add_op_eol -> dual_op : '$1'.
|
||||
add_op_eol -> dual_op eol : '$1'.
|
||||
|
||||
mult_op -> '*' : '$1'.
|
||||
mult_op -> '/' : '$1'.
|
||||
mult_op -> '*' eol : '$1'.
|
||||
mult_op -> '/' eol : '$1'.
|
||||
mult_op_eol -> mult_op : '$1'.
|
||||
mult_op_eol -> mult_op eol : '$1'.
|
||||
|
||||
two_op -> '++' : '$1'.
|
||||
two_op -> '--' : '$1'.
|
||||
two_op -> '++' eol : '$1'.
|
||||
two_op -> '--' eol : '$1'.
|
||||
two_op -> '**' : '$1'.
|
||||
two_op -> '**' eol : '$1'.
|
||||
exp_op_eol -> exp_op : '$1'.
|
||||
exp_op_eol -> exp_op eol : '$1'.
|
||||
|
||||
regex_op -> '=~' : '$1'.
|
||||
regex_op -> '=~' eol : '$1'.
|
||||
two_op_eol -> two_op : '$1'.
|
||||
two_op_eol -> two_op eol : '$1'.
|
||||
|
||||
right_op -> '|>' : '$1'.
|
||||
right_op -> '|>' eol : '$1'.
|
||||
default_op_eol -> default_op : '$1'.
|
||||
default_op_eol -> default_op eol : '$1'.
|
||||
|
||||
three_op -> '&&&' : '$1'.
|
||||
three_op -> '&&&' eol : '$1'.
|
||||
three_op -> '|||' : '$1'.
|
||||
three_op -> '|||' eol : '$1'.
|
||||
three_op -> '<<<' : '$1'.
|
||||
three_op -> '<<<' eol : '$1'.
|
||||
three_op -> '>>>' : '$1'.
|
||||
three_op -> '>>>' eol : '$1'.
|
||||
three_op -> '^^^' : '$1'.
|
||||
three_op -> '^^^' eol : '$1'.
|
||||
type_op_eol -> type_op : '$1'.
|
||||
type_op_eol -> type_op eol : '$1'.
|
||||
|
||||
default_op -> '//' : '$1'.
|
||||
default_op -> '//' eol : '$1'.
|
||||
unary_op_eol -> unary_op : '$1'.
|
||||
unary_op_eol -> unary_op eol : '$1'.
|
||||
unary_op_eol -> dual_op : '$1'.
|
||||
unary_op_eol -> dual_op eol : '$1'.
|
||||
|
||||
colon_colon_op -> '::' : '$1'.
|
||||
colon_colon_op -> '::' eol : '$1'.
|
||||
match_op_eol -> match_op : '$1'.
|
||||
match_op_eol -> match_op eol : '$1'.
|
||||
|
||||
unary_op -> '+' : '$1'.
|
||||
unary_op -> '+' eol : '$1'.
|
||||
unary_op -> '-' : '$1'.
|
||||
unary_op -> '-' eol : '$1'.
|
||||
unary_op -> '!' : '$1'.
|
||||
unary_op -> '!' eol : '$1'.
|
||||
unary_op -> '^' : '$1'.
|
||||
unary_op -> '^' eol : '$1'.
|
||||
unary_op -> 'not' : '$1'.
|
||||
unary_op -> 'not' eol : '$1'.
|
||||
unary_op -> '~~~' : '$1'.
|
||||
unary_op -> '~~~' eol : '$1'.
|
||||
and_op_eol -> and_op : '$1'.
|
||||
and_op_eol -> and_op eol : '$1'.
|
||||
|
||||
at_op -> '@' : '$1'.
|
||||
at_op -> '@' eol : '$1'.
|
||||
or_op_eol -> or_op : '$1'.
|
||||
or_op_eol -> or_op eol : '$1'.
|
||||
|
||||
match_op -> '=' : '$1'.
|
||||
match_op -> '=' eol : '$1'.
|
||||
tail_op_eol -> tail_op : '$1'.
|
||||
tail_op_eol -> tail_op eol : '$1'.
|
||||
|
||||
andand_op -> '&&' : '$1'.
|
||||
andand_op -> '&&' eol : '$1'.
|
||||
than_op_eol -> than_op : '$1'.
|
||||
than_op_eol -> than_op eol : '$1'.
|
||||
|
||||
oror_op -> '||' : '$1'.
|
||||
oror_op -> '||' eol : '$1'.
|
||||
in_op_eol -> in_op : '$1'.
|
||||
in_op_eol -> in_op eol : '$1'.
|
||||
|
||||
and_op -> 'and' : '$1'.
|
||||
and_op -> 'and' eol : '$1'.
|
||||
inc_op_eol -> inc_op : '$1'.
|
||||
inc_op_eol -> inc_op eol : '$1'.
|
||||
|
||||
or_op -> 'or' : '$1'.
|
||||
or_op -> 'or' eol : '$1'.
|
||||
or_op -> 'xor' : '$1'.
|
||||
or_op -> 'xor' eol : '$1'.
|
||||
when_op_eol -> when_op : '$1'.
|
||||
when_op_eol -> when_op eol : '$1'.
|
||||
|
||||
pipe_op -> '|' : '$1'.
|
||||
pipe_op -> '|' eol : '$1'.
|
||||
stab_op_eol -> stab_op : '$1'.
|
||||
stab_op_eol -> stab_op eol : '$1'.
|
||||
|
||||
bin_concat_op -> '<>' : '$1'.
|
||||
bin_concat_op -> '<>' eol : '$1'.
|
||||
range_op_eol -> range_op : '$1'.
|
||||
range_op_eol -> range_op eol : '$1'.
|
||||
|
||||
in_op -> 'in' : '$1'.
|
||||
in_op -> 'in' eol : '$1'.
|
||||
at_op_eol -> at_op : '$1'.
|
||||
at_op_eol -> at_op eol : '$1'.
|
||||
|
||||
inc_op -> 'inlist' : '$1'.
|
||||
inc_op -> 'inlist' eol : '$1'.
|
||||
inc_op -> 'inbits' : '$1'.
|
||||
inc_op -> 'inbits' eol : '$1'.
|
||||
comp_op_eol -> comp_op : '$1'.
|
||||
comp_op_eol -> comp_op eol : '$1'.
|
||||
|
||||
when_op -> 'when' : '$1'.
|
||||
when_op -> 'when' eol : '$1'.
|
||||
|
||||
stab_op -> '->' : '$1'.
|
||||
stab_op -> '->' eol : '$1'.
|
||||
|
||||
send_op -> '<-' : '$1'.
|
||||
send_op -> '<-' eol : '$1'.
|
||||
|
||||
range_op -> '..' : '$1'.
|
||||
range_op -> '..' eol : '$1'.
|
||||
|
||||
comp_expr_op -> comp_op : '$1'.
|
||||
comp_expr_op -> comp_op eol : '$1'.
|
||||
arrow_op_eol -> arrow_op : '$1'.
|
||||
arrow_op_eol -> arrow_op eol : '$1'.
|
||||
|
||||
% Dot operator
|
||||
|
||||
@@ -368,7 +350,7 @@ dot_op -> '.' eol : '$1'.
|
||||
dot_identifier -> identifier : '$1'.
|
||||
dot_identifier -> matched_expr dot_op identifier : build_dot('$2', '$1', '$3').
|
||||
|
||||
dot_ref -> matched_expr dot_op aliases : build_dot_ref('$2', '$1', '$3').
|
||||
dot_alias -> matched_expr dot_op aliases : build_dot_alias('$2', '$1', '$3').
|
||||
|
||||
dot_op_identifier -> op_identifier : '$1'.
|
||||
dot_op_identifier -> matched_expr dot_op op_identifier : build_dot('$2', '$1', '$3').
|
||||
@@ -388,85 +370,106 @@ dot_punctuated_identifier -> matched_expr dot_op punctuated_identifier : build_d
|
||||
parens_call -> dot_paren_identifier : '$1'.
|
||||
parens_call -> matched_expr dot_call_op : { '.', [{line,?line('$2')}], ['$1'] }. % Fun/local calls
|
||||
|
||||
% Function calls
|
||||
% Function calls with no parentheses
|
||||
|
||||
matched_comma_expr -> matched_expr : ['$1'].
|
||||
matched_comma_expr -> matched_comma_expr ',' matched_expr : ['$3'|'$1'].
|
||||
call_args_no_parens_expr -> matched_expr : '$1'.
|
||||
call_args_no_parens_expr -> empty_paren : nil.
|
||||
call_args_no_parens_expr -> no_parens_expr : throw_no_parens_many_strict('$1').
|
||||
|
||||
call_args_no_parens_strict -> call_args_no_parens : '$1'.
|
||||
call_args_no_parens_strict -> open_paren ')' : throw_no_parens_strict('$1').
|
||||
call_args_no_parens_strict -> open_paren matched_kw_base close_paren : throw_no_parens_strict('$1').
|
||||
call_args_no_parens_strict -> open_paren matched_expr ',' call_args_no_parens close_paren : throw_no_parens_strict('$1').
|
||||
call_args_no_parens_comma_expr -> matched_expr ',' call_args_no_parens_expr : ['$3', '$1'].
|
||||
call_args_no_parens_comma_expr -> call_args_no_parens_comma_expr ',' call_args_no_parens_expr : ['$3'|'$1'].
|
||||
|
||||
call_args_no_parens -> matched_comma_expr : lists:reverse('$1').
|
||||
call_args_no_parens -> matched_kw_base : ['$1'].
|
||||
call_args_no_parens -> matched_comma_expr ',' matched_kw_base : lists:reverse(['$3'|'$1']).
|
||||
call_args_no_parens_all -> call_args_no_parens_one : '$1'.
|
||||
call_args_no_parens_all -> call_args_no_parens_many : '$1'.
|
||||
|
||||
call_args_parens_not_one -> open_paren ')' : [].
|
||||
call_args_parens_not_one -> open_paren matched_kw_base close_paren : ['$2'].
|
||||
call_args_parens_not_one -> open_paren matched_expr ',' call_args_no_parens close_paren : ['$2'|'$4'].
|
||||
call_args_no_parens_one -> call_args_no_parens_kw : ['$1'].
|
||||
call_args_no_parens_one -> matched_expr : ['$1'].
|
||||
call_args_no_parens_one -> no_parens_expr : ['$1'].
|
||||
|
||||
base_comma_expr -> paren_expr ',' : ['$1'].
|
||||
base_comma_expr -> base_comma_expr paren_expr ',' : ['$2'|'$1'].
|
||||
call_args_no_parens_many -> matched_expr ',' call_args_no_parens_kw : ['$1', '$3'].
|
||||
call_args_no_parens_many -> call_args_no_parens_comma_expr : lists:reverse('$1').
|
||||
call_args_no_parens_many -> call_args_no_parens_comma_expr ',' call_args_no_parens_kw : lists:reverse(['$3'|'$1']).
|
||||
|
||||
comma_expr -> paren_expr : ['$1'].
|
||||
comma_expr -> kw_base : ['$1'].
|
||||
comma_expr -> base_comma_expr : '$1'.
|
||||
comma_expr -> base_comma_expr paren_expr : ['$2'|'$1'].
|
||||
comma_expr -> base_comma_expr kw_base : ['$2'|'$1'].
|
||||
call_args_no_parens_many_strict -> call_args_no_parens_many : '$1'.
|
||||
call_args_no_parens_many_strict -> empty_paren : throw_no_parens_strict('$1').
|
||||
call_args_no_parens_many_strict -> open_paren call_args_no_parens_kw close_paren : throw_no_parens_strict('$1').
|
||||
call_args_no_parens_many_strict -> open_paren call_args_no_parens_many close_paren : throw_no_parens_strict('$1').
|
||||
|
||||
optional_comma_expr -> paren_expr : '$1'.
|
||||
optional_comma_expr -> paren_expr ',' : '$1'.
|
||||
stab_parens_many -> empty_paren : [].
|
||||
stab_parens_many -> open_paren call_args_no_parens_kw close_paren : ['$2'].
|
||||
stab_parens_many -> open_paren call_args_no_parens_many close_paren : '$2'.
|
||||
|
||||
call_args -> comma_expr : lists:reverse('$1').
|
||||
% Containers and function calls with parentheses
|
||||
|
||||
call_args_parens -> open_paren ')' : [].
|
||||
call_args_parens -> open_paren call_args close_paren : '$2'.
|
||||
container_expr -> empty_paren : nil.
|
||||
container_expr -> matched_expr : '$1'.
|
||||
container_expr -> unmatched_expr : '$1'.
|
||||
container_expr -> no_parens_expr : throw_no_parens_many_strict('$1').
|
||||
|
||||
container_comma_expr_base -> container_expr ',' : ['$1'].
|
||||
container_comma_expr_base -> container_comma_expr_base container_expr ',' : ['$2'|'$1'].
|
||||
|
||||
container_comma_expr -> container_expr : ['$1'].
|
||||
container_comma_expr -> kw : ['$1'].
|
||||
container_comma_expr -> container_comma_expr_base : '$1'.
|
||||
container_comma_expr -> container_comma_expr_base container_expr : ['$2'|'$1'].
|
||||
container_comma_expr -> container_comma_expr_base kw : ['$2'|'$1'].
|
||||
|
||||
container_arg -> container_expr : '$1'.
|
||||
container_arg -> container_expr ',' : '$1'.
|
||||
container_args -> container_comma_expr : lists:reverse('$1').
|
||||
|
||||
call_args_parens_comma_expr -> container_expr : ['$1'].
|
||||
call_args_parens_comma_expr -> call_args_parens_comma_expr ',' container_expr : ['$3'|'$1'].
|
||||
|
||||
call_args_parens -> empty_paren : [].
|
||||
call_args_parens -> open_paren no_parens_expr close_paren : ['$2'].
|
||||
call_args_parens -> open_paren kw close_paren : ['$2'].
|
||||
call_args_parens -> open_paren call_args_parens_comma_expr close_paren : lists:reverse('$2').
|
||||
call_args_parens -> open_paren call_args_parens_comma_expr ',' kw close_paren : lists:reverse(['$4'|'$2']).
|
||||
|
||||
% KV
|
||||
|
||||
kw_eol -> kw_identifier : '$1'.
|
||||
kw_eol -> kw_identifier eol : '$1'.
|
||||
kw_expr -> kw_eol paren_expr : { ?exprs('$1'),'$2' }.
|
||||
|
||||
kw_expr -> kw_eol container_expr : { ?exprs('$1'),'$2' }.
|
||||
kw_comma -> kw_expr ',' : ['$1'].
|
||||
kw_comma -> kw_comma kw_expr ',' : ['$2'|'$1'].
|
||||
kw -> kw_expr : ['$1'].
|
||||
kw -> kw_comma : lists:reverse('$1').
|
||||
kw -> kw_comma kw_expr : lists:reverse(['$2'|'$1']).
|
||||
|
||||
kw_base -> kw_expr : ['$1'].
|
||||
kw_base -> kw_comma : lists:reverse('$1').
|
||||
kw_base -> kw_comma kw_expr : lists:reverse(['$2'|'$1']).
|
||||
|
||||
matched_kw_expr -> kw_eol matched_expr : {?exprs('$1'),'$2'}.
|
||||
matched_kw_expr -> kw_eol empty_paren : {?exprs('$1'),nil}.
|
||||
matched_kw_comma -> matched_kw_expr : ['$1'].
|
||||
matched_kw_comma -> matched_kw_expr ',' matched_kw_comma : ['$1'|'$3'].
|
||||
matched_kw_base -> matched_kw_comma : '$1'.
|
||||
call_args_no_parens_kw_expr -> kw_eol call_args_no_parens_expr : {?exprs('$1'),'$2'}.
|
||||
call_args_no_parens_kw_comma -> call_args_no_parens_kw_expr : ['$1'].
|
||||
call_args_no_parens_kw_comma -> call_args_no_parens_kw_expr ',' call_args_no_parens_kw_comma : ['$1'|'$3'].
|
||||
call_args_no_parens_kw -> call_args_no_parens_kw_comma : '$1'.
|
||||
|
||||
% Lists
|
||||
|
||||
bracket_access -> open_bracket ']' : { [], ?line('$1') }.
|
||||
bracket_access -> open_bracket optional_comma_expr close_bracket : { '$2', ?line('$1') }.
|
||||
bracket_access -> open_bracket kw_base close_bracket : { '$2', ?line('$1') }.
|
||||
bracket_access -> open_bracket container_arg close_bracket : { '$2', ?line('$1') }.
|
||||
bracket_access -> open_bracket kw close_bracket : { '$2', ?line('$1') }.
|
||||
|
||||
list -> open_bracket ']' : [].
|
||||
list -> open_bracket kw_base close_bracket : '$2'.
|
||||
list -> open_bracket optional_comma_expr close_bracket : ['$2'].
|
||||
list -> open_bracket paren_expr ',' call_args close_bracket : ['$2'|'$4'].
|
||||
list -> open_bracket kw close_bracket : '$2'.
|
||||
list -> open_bracket container_arg close_bracket : ['$2'].
|
||||
list -> open_bracket container_expr ',' container_args close_bracket : ['$2'|'$4'].
|
||||
|
||||
% Tuple
|
||||
|
||||
tuple -> open_curly '}' : build_tuple('$1', []).
|
||||
tuple -> open_curly optional_comma_expr close_curly : build_tuple('$1', ['$2']).
|
||||
tuple -> open_curly paren_expr ',' call_args close_curly : build_tuple('$1', ['$2'|'$4']).
|
||||
tuple -> open_curly container_arg close_curly : build_tuple('$1', ['$2']).
|
||||
tuple -> open_curly container_expr ',' container_args close_curly : build_tuple('$1', ['$2'|'$4']).
|
||||
|
||||
% Bitstrings
|
||||
|
||||
bit_string -> open_bit '>>' : { '<<>>', [{line,?line('$1')}], [] }.
|
||||
bit_string -> open_bit call_args close_bit : { '<<>>', [{line,?line('$1')}], '$2' }.
|
||||
bit_string -> open_bit container_args close_bit : { '<<>>', [{line,?line('$1')}], '$2' }.
|
||||
|
||||
Erlang code.
|
||||
|
||||
-define(op(Node), element(1, Node)).
|
||||
-define(id(Node), element(1, Node)).
|
||||
-define(line(Node), element(2, Node)).
|
||||
-define(exprs(Node), element(3, Node)).
|
||||
|
||||
@@ -479,17 +482,14 @@ Erlang code.
|
||||
|
||||
%% Operators
|
||||
|
||||
build_op({ _, _, _ } = Op, Left, Right) ->
|
||||
{ ?exprs(Op), [{line,?line(Op)}], [Left, Right] };
|
||||
|
||||
build_op({ BOp, Line }, { UOp, _, [Left] }, Right) when ?rearrange_bop(BOp), ?rearrange_uop(UOp) ->
|
||||
build_op({ _Kind, Line, BOp }, { UOp, _, [Left] }, Right) when ?rearrange_bop(BOp), ?rearrange_uop(UOp) ->
|
||||
{ UOp, [{line,Line}], [{ BOp, [{line,Line}], [Left, Right] }] };
|
||||
|
||||
build_op(Op, Left, Right) ->
|
||||
{ ?op(Op), [{line,?line(Op)}], [Left, Right] }.
|
||||
build_op({ _Kind, Line, Op }, Left, Right) ->
|
||||
{ Op, [{line,Line}], [Left, Right] }.
|
||||
|
||||
build_unary_op(Op, Expr) ->
|
||||
{ ?op(Op), [{line,?line(Op)}], [Expr] }.
|
||||
build_unary_op({ _Kind, Line, Op }, Expr) ->
|
||||
{ Op, [{line,Line}], [Expr] }.
|
||||
|
||||
build_tuple(_Marker, [Left, Right]) ->
|
||||
{ Left, Right };
|
||||
@@ -503,15 +503,15 @@ build_block([nil]) -> { '__block__', [], [n
|
||||
build_block([{Op,_,[_]}]=Exprs) when ?rearrange_uop(Op) -> { '__block__', [], Exprs };
|
||||
build_block([{unquote_splicing,_,Args}]=Exprs) when
|
||||
length(Args) =< 2 -> { '__block__', [], Exprs };
|
||||
build_block([Expr]) when not is_list(Expr) -> Expr;
|
||||
build_block([Expr]) -> Expr;
|
||||
build_block(Exprs) -> { '__block__', [], Exprs }.
|
||||
|
||||
%% Dots
|
||||
|
||||
build_dot_ref(Dot, { '__aliases__', _, Left }, { 'aliases', _, Right }) ->
|
||||
build_dot_alias(Dot, { '__aliases__', _, Left }, { 'aliases', _, Right }) ->
|
||||
{ '__aliases__', [{line,?line(Dot)}], Left ++ Right };
|
||||
|
||||
build_dot_ref(Dot, Other, { 'aliases', _, Right }) ->
|
||||
build_dot_alias(Dot, Other, { 'aliases', _, Right }) ->
|
||||
{ '__aliases__', [{line,?line(Dot)}], [Other|Right] }.
|
||||
|
||||
build_dot(Dot, Left, Right) ->
|
||||
@@ -533,8 +533,8 @@ build_identifier({ '.', Meta, _ } = Dot, Args) ->
|
||||
build_identifier({ Keyword, Line }, Args) when Keyword == fn ->
|
||||
{ fn, [{line,Line}], Args };
|
||||
|
||||
build_identifier({ op_identifier, Line, Identifier }, Args) ->
|
||||
{ '__ambiguousop__', [{line,Line}], [{ Identifier, [{line,Line}], nil }|Args] };
|
||||
build_identifier({ op_identifier, Line, Identifier }, [Arg]) ->
|
||||
{ Identifier, [{ambiguous_op,nil},{line,Line}], [Arg] };
|
||||
|
||||
build_identifier({ _, Line, Identifier }, Args) ->
|
||||
{ Identifier, [{line,Line}], Args }.
|
||||
@@ -555,13 +555,13 @@ build_fn(Op, Stab) ->
|
||||
|
||||
build_access(Expr, Access) ->
|
||||
Meta = [{line,?line(Access)}],
|
||||
{ { '.', Meta, ['Elixir.Kernel', access] }, Meta, [ Expr, ?op(Access) ] }.
|
||||
{ { '.', Meta, ['Elixir.Kernel', access] }, Meta, [ Expr, ?id(Access) ] }.
|
||||
|
||||
%% Interpolation aware
|
||||
|
||||
build_sigil({ sigil, Line, Sigil, Parts, Modifiers }) ->
|
||||
Meta = [{line,Line}],
|
||||
{ list_to_atom([$_,$_,Sigil,$_,$_]), Meta, [ { '<<>>', Meta, Parts }, Modifiers ] }.
|
||||
{ list_to_atom("sigil_" ++ [Sigil]), Meta, [ { '<<>>', Meta, Parts }, Modifiers ] }.
|
||||
|
||||
build_bin_string({ bin_string, _Line, [H] }) when is_binary(H) -> H;
|
||||
build_bin_string({ bin_string, Line, Args }) -> { '<<>>', [{line,Line}], Args }.
|
||||
@@ -579,25 +579,25 @@ build_atom({ atom, Line, Args }) ->
|
||||
%% Keywords
|
||||
|
||||
build_stab([{ '->', Meta, [Left, Right] }|T]) ->
|
||||
{ '->', Meta, build_stab(T, Left, [Right], []) };
|
||||
{ '->', Meta, build_stab(Meta, T, Left, [Right], []) };
|
||||
|
||||
build_stab(Else) ->
|
||||
build_block(Else).
|
||||
|
||||
build_stab([{ '->', _, [Left, Right] }|T], Marker, Temp, Acc) ->
|
||||
H = { Marker, build_block(lists:reverse(Temp)) },
|
||||
build_stab(T, Left, [Right], [H|Acc]);
|
||||
build_stab(Old, [{ '->', New, [Left, Right] }|T], Marker, Temp, Acc) ->
|
||||
H = { Marker, Old, build_block(lists:reverse(Temp)) },
|
||||
build_stab(New, T, Left, [Right], [H|Acc]);
|
||||
|
||||
build_stab([H|T], Marker, Temp, Acc) ->
|
||||
build_stab(T, Marker, [H|Temp], Acc);
|
||||
build_stab(Meta, [H|T], Marker, Temp, Acc) ->
|
||||
build_stab(Meta, T, Marker, [H|Temp], Acc);
|
||||
|
||||
build_stab([], Marker, Temp, Acc) ->
|
||||
H = { Marker, build_block(lists:reverse(Temp)) },
|
||||
build_stab(Meta, [], Marker, Temp, Acc) ->
|
||||
H = { Marker, Meta, build_block(lists:reverse(Temp)) },
|
||||
lists:reverse([H|Acc]).
|
||||
|
||||
%% Every time the parser sees a (unquote_splicing())
|
||||
%% it assumes that a block is being spliced, wrapping
|
||||
%% the splicing in a __block__. But in the stab cause,
|
||||
%% the splicing in a __block__. But in the stab clause,
|
||||
%% we can have (unquote_splicing(1,2,3)) -> :ok, in such
|
||||
%% case, we don't actually want the block, since it is
|
||||
%% an arg style call. unwrap_splice unwraps the splice
|
||||
@@ -607,12 +607,30 @@ unwrap_splice([{ '__block__', [], [{ unquote_splicing, _, _ }] = Splice }]) ->
|
||||
|
||||
unwrap_splice(Other) -> Other.
|
||||
|
||||
unwrap_when(Args) ->
|
||||
case elixir_tree_helpers:split_last(Args) of
|
||||
{ Start, { 'when', Meta, [_, _] = End } } ->
|
||||
[{ 'when', Meta, Start ++ End }];
|
||||
{ _, _ } ->
|
||||
Args
|
||||
end.
|
||||
|
||||
%% Errors
|
||||
|
||||
throw(Line, Error, Token) ->
|
||||
throw({ error, { Line, ?MODULE, [Error, Token] }}).
|
||||
|
||||
throw_no_parens_strict(Token) ->
|
||||
throw(?line(Token), "invalid comma inside parenthesis. If you are making a "
|
||||
throw(?line(Token), "unexpected parenthesis. If you are making a "
|
||||
"function call, do not insert spaces in between the function name and the "
|
||||
"opening parentheses. Syntax error before: ", ")").
|
||||
"opening parentheses. Syntax error before: ", "'('").
|
||||
|
||||
throw_no_parens_many_strict(Token) ->
|
||||
Line =
|
||||
case lists:keyfind(line, 1, element(2, Token)) of
|
||||
{ line, L } -> L;
|
||||
false -> 0
|
||||
end,
|
||||
|
||||
throw(Line, "unexpected comma. Parentheses are required to solve ambiguity "
|
||||
"in nested calls. Syntax error before: ", "','").
|
||||
|
||||
@@ -11,7 +11,7 @@ handle({ _, Meta, Args } = Original, S, Opt) when is_list(Args), S#elixir_scope.
|
||||
{ Call, Def, SC } when Def /= [] ->
|
||||
Final = validate(Meta, Def, SC),
|
||||
Block = setelement(3, Original, Call),
|
||||
elixir_translator:translate_fn(Meta, [{ Final, Block }], SC);
|
||||
elixir_translator:translate_fn(Meta, [{ Final, Meta, Block }], SC);
|
||||
_ -> error
|
||||
end;
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
%% Implements Elixir quote.
|
||||
-module(elixir_quote).
|
||||
-export([escape/2, erl_escape/3, erl_quote/3, linify/2, unquote/5]).
|
||||
-export([escape/2, erl_escape/3, erl_quote/4, linify/2, unquote/6, join/5]).
|
||||
-include("elixir.hrl").
|
||||
|
||||
%% Apply the line from site call on quoted contents.
|
||||
@@ -9,7 +9,7 @@ linify(Line, Exprs) when is_integer(Line) ->
|
||||
|
||||
do_linify(Line, { Left, Meta, Right }) ->
|
||||
NewMeta = case ?line(Meta) of
|
||||
0 -> lists:keystore(line, 1, Meta, { line, Line });
|
||||
0 -> keystore(line, Meta, Line);
|
||||
_ -> Meta
|
||||
end,
|
||||
{ do_linify(Line, Left), NewMeta, do_linify(Line, Right) };
|
||||
@@ -24,10 +24,10 @@ do_linify(_, Else) -> Else.
|
||||
|
||||
%% Some expressions cannot be unquoted at compilation time.
|
||||
%% This function is responsible for doing runtime unquoting.
|
||||
unquote(_File, Meta, Left, { '__aliases__', _, Args }, nil) ->
|
||||
unquote(_File, _Line, Meta, Left, { '__aliases__', _, Args }, nil) ->
|
||||
{ '__aliases__', Meta, [Left|Args] };
|
||||
|
||||
unquote(_File, Meta, Left, Right, nil) when is_atom(Right) ->
|
||||
unquote(_File, _Line, Meta, Left, Right, nil) when is_atom(Right) ->
|
||||
case atom_to_list(Right) of
|
||||
"Elixir." ++ _ ->
|
||||
{ '__aliases__', Meta, [Left, Right] };
|
||||
@@ -35,22 +35,43 @@ unquote(_File, Meta, Left, Right, nil) when is_atom(Right) ->
|
||||
{ { '.', Meta, [Left, Right] }, Meta, [] }
|
||||
end;
|
||||
|
||||
unquote(_File, Meta, Left, Right, Args) when is_atom(Right) ->
|
||||
unquote(_File, _Line, Meta, Left, { Right, _, Context }, nil) when is_atom(Right), is_atom(Context) ->
|
||||
{ { '.', Meta, [Left, Right] }, Meta, [] };
|
||||
|
||||
unquote(_File, _Line, Meta, Left, { Right, _, Args }, nil) when is_atom(Right) ->
|
||||
{ { '.', Meta, [Left, Right] }, Meta, Args };
|
||||
|
||||
unquote(File, Meta, _Left, _Right, _Args) ->
|
||||
elixir_errors:syntax_error(Meta, File, "expected unquote after dot to return an atom or an alias").
|
||||
unquote(File, Line, _Meta, _Left, Right, nil) ->
|
||||
elixir_errors:syntax_error(Line, File, "expected unquote after dot to return an atom, "
|
||||
"an alias or a quoted call, got: ~ts", ['Elixir.Macro':to_string(Right)]);
|
||||
|
||||
unquote(_File, _Line, Meta, Left, Right, Args) when is_atom(Right) ->
|
||||
{ { '.', Meta, [Left, Right] }, Meta, Args };
|
||||
|
||||
unquote(_File, _Line, Meta, Left, { Right, _, Context }, Args) when is_atom(Right), is_atom(Context) ->
|
||||
{ { '.', Meta, [Left, Right] }, Meta, Args };
|
||||
|
||||
unquote(File, Line, _Meta, _Left, Right, _Args) ->
|
||||
elixir_errors:syntax_error(Line, File, "expected unquote after dot with args to return an atom "
|
||||
"or a quoted call, got: ~ts", ['Elixir.Macro':to_string(Right)]).
|
||||
|
||||
join(_File, _Line, Left, Right, Rest) when is_list(Left), is_list(Right), is_list(Rest) ->
|
||||
Rest ++ Left ++ Right;
|
||||
|
||||
join(_File, _Line, Left, Right, Rest) ->
|
||||
[H|T] = lists:reverse(Rest ++ Left),
|
||||
lists:reverse([{ '|', [], [H, Right] }|T]).
|
||||
|
||||
%% Escapes the given expression. It is similar to quote, but
|
||||
%% lines are kept and hygiene mechanisms are disabled.
|
||||
escape(Expr, Unquote) ->
|
||||
quote(Expr, #elixir_quote{
|
||||
quote(Expr, nil, #elixir_quote{
|
||||
line=keep,
|
||||
vars_hygiene=false,
|
||||
aliases_hygiene=false,
|
||||
imports_hygiene=false,
|
||||
unquote=Unquote,
|
||||
mark=false
|
||||
escape=true
|
||||
}, nil).
|
||||
|
||||
erl_escape(Expr, Unquote, S) ->
|
||||
@@ -60,11 +81,24 @@ erl_escape(Expr, Unquote, S) ->
|
||||
|
||||
%% Quotes an expression and return its quoted Elixir AST.
|
||||
|
||||
quote(Expr, Q, S) ->
|
||||
do_quote(Expr, Q, S).
|
||||
quote(Expr, nil, Q, S) ->
|
||||
do_quote(Expr, Q, S);
|
||||
|
||||
erl_quote(Expr, Q, S) ->
|
||||
{ QExpr, TQ } = quote(Expr, Q, S),
|
||||
quote(Expr, Binding, Q, S) ->
|
||||
Context = Q#elixir_quote.context,
|
||||
|
||||
Vars = [ { '{}', [],
|
||||
[ '=', [], [
|
||||
{ '{}', [], [K, [], Context] },
|
||||
V
|
||||
] ]
|
||||
} || { K, V } <- Binding],
|
||||
|
||||
{ TExprs, TQ } = do_quote(Expr, Q, S),
|
||||
{ { '{}',[], ['__block__',[], Vars ++ [TExprs] ] }, TQ }.
|
||||
|
||||
erl_quote(Expr, Binding, Q, S) ->
|
||||
{ QExpr, TQ } = quote(Expr, Binding, Q, S),
|
||||
{ TExpr, TS } = elixir_translator:translate_each(QExpr, S),
|
||||
{ TExpr, TQ, TS }.
|
||||
|
||||
@@ -79,12 +113,12 @@ do_quote({ unquote, _Meta, [Expr] }, #elixir_quote{unquote=true} = Q, _) ->
|
||||
|
||||
%% Context mark
|
||||
|
||||
do_quote({ Def, Meta, Args }, #elixir_quote{mark=true} = Q, S) when ?defs(Def); Def == defmodule; Def == alias; Def == import ->
|
||||
NewMeta = lists:keystore(context, 1, Meta, { context, Q#elixir_quote.context }),
|
||||
do_quote({ Def, Meta, Args }, #elixir_quote{escape=false} = Q, S) when ?defs(Def); Def == defmodule; Def == alias; Def == import ->
|
||||
NewMeta = keystore(context, Meta, Q#elixir_quote.context),
|
||||
do_quote_tuple({ Def, NewMeta, Args }, Q, S);
|
||||
|
||||
do_quote({ { '.', _, [_, Def] } = Target, Meta, Args }, #elixir_quote{mark=true} = Q, S) when ?defs(Def); Def == defmodule; Def == alias; Def == import ->
|
||||
NewMeta = lists:keystore(context, 1, Meta, { context, Q#elixir_quote.context }),
|
||||
do_quote({ { '.', _, [_, Def] } = Target, Meta, Args }, #elixir_quote{escape=false} = Q, S) when ?defs(Def); Def == defmodule; Def == alias; Def == import ->
|
||||
NewMeta = keystore(context, Meta, Q#elixir_quote.context),
|
||||
do_quote_tuple({ Target, NewMeta, Args }, Q, S);
|
||||
|
||||
%% Aliases
|
||||
@@ -98,7 +132,7 @@ do_quote({ '__aliases__', Meta, [H|T] } = Alias, #elixir_quote{aliases_hygiene=t
|
||||
Atom when is_atom(Atom) -> Atom;
|
||||
Aliases when is_list(Aliases) -> false
|
||||
end,
|
||||
AliasMeta = lists:keystore(alias, 1, Meta, { alias, Annotation }),
|
||||
AliasMeta = keystore(alias, Meta, Annotation),
|
||||
do_quote_tuple({ '__aliases__', AliasMeta, [H|T] }, Q, S);
|
||||
|
||||
%% Vars
|
||||
@@ -124,23 +158,29 @@ do_quote({ { '.', _, [_, function] } = Target, Meta, [{ '/', _, [{F, _, C}, A]}]
|
||||
#elixir_quote{imports_hygiene=true} = Q, S) when is_atom(F), is_integer(A), is_atom(C) ->
|
||||
do_quote_fa(Target, Meta, Args, F, A, Q, S);
|
||||
|
||||
do_quote({ Name, Meta, ArgsOrAtom } = Tuple, #elixir_quote{imports_hygiene=true} = Q, S) when is_atom(Name) ->
|
||||
do_quote({ Name, Meta, ArgsOrAtom }, #elixir_quote{imports_hygiene=true} = Q, S) when is_atom(Name) ->
|
||||
Arity = case is_atom(ArgsOrAtom) of
|
||||
true -> 0;
|
||||
false -> length(ArgsOrAtom)
|
||||
end,
|
||||
|
||||
case (lists:keyfind(import, 1, Meta) == false) andalso
|
||||
case (keyfind(import, Meta) == false) andalso
|
||||
elixir_dispatch:find_import(Meta, Name, Arity, S) of
|
||||
false -> do_quote_tuple(Tuple, Q, S);
|
||||
false ->
|
||||
AmbMeta =
|
||||
case (Arity == 1) andalso keyfind(ambiguous_op, Meta) of
|
||||
{ ambiguous_op, nil } -> keystore(ambiguous_op, Meta, Q#elixir_quote.context);
|
||||
_ -> Meta
|
||||
end,
|
||||
do_quote_tuple({ Name, AmbMeta, ArgsOrAtom }, Q, S);
|
||||
Receiver ->
|
||||
ImportMeta = lists:keystore(import, 1,
|
||||
lists:keystore(context, 1, Meta, { context, Q#elixir_quote.context }),
|
||||
{ import, Receiver }),
|
||||
ImportMeta = keystore(import,
|
||||
keystore(context, Meta, Q#elixir_quote.context),
|
||||
Receiver),
|
||||
do_quote_tuple({ Name, ImportMeta, ArgsOrAtom }, Q, S)
|
||||
end;
|
||||
|
||||
do_quote({ _, _, _ } = Tuple, Q, S) ->
|
||||
do_quote({ _, _, _ } = Tuple, #elixir_quote{escape=false} = Q, S) ->
|
||||
do_quote_tuple(Tuple, Q, S);
|
||||
|
||||
%% Literals
|
||||
@@ -155,7 +195,7 @@ do_quote({ Left, Right }, Q, S) ->
|
||||
{ TRight, RQ } = do_quote(Right, LQ, S),
|
||||
{ { TLeft, TRight }, RQ };
|
||||
|
||||
do_quote(Tuple, Q, S) when is_tuple(Tuple) ->
|
||||
do_quote(Tuple, #elixir_quote{escape=true} = Q, S) when is_tuple(Tuple) ->
|
||||
{ TT, TQ } = do_quote(tuple_to_list(Tuple), Q, S),
|
||||
{ { '{}', [], TT }, TQ };
|
||||
|
||||
@@ -168,16 +208,16 @@ do_quote(Other, Q, _) ->
|
||||
%% Quote helpers
|
||||
|
||||
do_quote_call(Left, Meta, Expr, Args, Q, S) ->
|
||||
All = [meta(Meta, Q), Left, { unquote, Meta, [Expr] }, Args],
|
||||
All = [?line(Meta), meta(Meta, Q), Left, { unquote, Meta, [Expr] }, Args],
|
||||
{ TAll, TQ } = lists:mapfoldl(fun(X, Acc) -> do_quote(X, Acc, S) end, Q, All),
|
||||
{ { { '.', Meta, [elixir_quote, unquote] }, Meta, [{ '__FILE__', [], nil }|TAll] }, TQ }.
|
||||
|
||||
do_quote_fa(Target, Meta, Args, F, A, Q, S) ->
|
||||
NewMeta =
|
||||
case (lists:keyfind(import_fa, 1, Meta) == false) andalso
|
||||
case (keyfind(import_fa, Meta) == false) andalso
|
||||
elixir_dispatch:find_import(Meta, F, A, S) of
|
||||
false -> Meta;
|
||||
Receiver -> lists:keystore(import_fa, 1, Meta, { import_fa, { Receiver, Q#elixir_quote.context } })
|
||||
Receiver -> keystore(import_fa, Meta, { Receiver, Q#elixir_quote.context })
|
||||
end,
|
||||
do_quote_tuple({ Target, NewMeta, Args }, Q, S).
|
||||
|
||||
@@ -189,15 +229,29 @@ do_quote_tuple({ Left, Meta, Right }, Q, S) ->
|
||||
meta(Meta, #elixir_quote{line=keep}) ->
|
||||
Meta;
|
||||
meta(Meta, #elixir_quote{line=nil}) ->
|
||||
lists:keydelete(line, 1, Meta);
|
||||
keydelete(line, Meta);
|
||||
meta(Meta, #elixir_quote{line=Line}) ->
|
||||
lists:keystore(line, 1, Meta, { line, Line }).
|
||||
keystore(line, Meta, Line).
|
||||
|
||||
keyfind(Key, Meta) ->
|
||||
lists:keyfind(Key, 1, Meta).
|
||||
keydelete(Key, Meta) ->
|
||||
lists:keydelete(Key, 1, Meta).
|
||||
keystore(Key, Meta, Value) ->
|
||||
lists:keystore(Key, 1, Meta, { Key, Value }).
|
||||
|
||||
%% Quote splicing
|
||||
|
||||
do_splice([{ '|', _, [{ unquote_splicing, _, [Left] }, Right] }|T], #elixir_quote{unquote=true} = Q, S) ->
|
||||
{ TT, TQ } = do_splice(T, Q, S, [], []),
|
||||
{ do_splice_join(do_splice_join(TT, Left), Right), TQ#elixir_quote{unquoted=true} };
|
||||
do_splice([{ '|', Meta, [{ unquote_splicing, _, [Left] }, Right] }|T], #elixir_quote{unquote=true} = Q, S) ->
|
||||
%% Process the remaining entries on the list.
|
||||
%% For [1, 2, 3, unquote_splicing(arg)|tail], this will quote
|
||||
%% 1, 2 and 3, which could even be unquotes.
|
||||
{ TT, QT } = do_splice(T, Q, S, [], []),
|
||||
{ TR, QR } = do_quote(Right, QT, S),
|
||||
|
||||
%% Do the joining at runtime when we are aware of the values.
|
||||
Args = [{ '__FILE__', [], nil }, ?line(Meta), Left, TR, TT],
|
||||
{ { { '.', Meta, [elixir_quote, join] }, Meta, Args }, QR#elixir_quote{unquoted=true} };
|
||||
|
||||
do_splice(List, Q, S) ->
|
||||
do_splice(List, Q, S, [], []).
|
||||
|
||||
@@ -5,13 +5,15 @@
|
||||
serialize/1, deserialize/1,
|
||||
serialize_with_vars/2, deserialize_with_vars/2,
|
||||
to_erl_env/1, to_ex_env/1,
|
||||
umergev/2, umergec/2, merge_clause_vars/2
|
||||
vars_from_binding/2, binding_for_eval/2, binding_from_vars/2,
|
||||
umergev/2, umergec/2, umergea/2, merge_clause_vars/2
|
||||
]).
|
||||
-include("elixir.hrl").
|
||||
|
||||
translate_var(Meta, Name, Kind, S, Callback) ->
|
||||
Line = ?line(Meta),
|
||||
Vars = S#elixir_scope.vars,
|
||||
Tuple = { Name, Kind },
|
||||
|
||||
case Name of
|
||||
'_' ->
|
||||
@@ -20,9 +22,9 @@ translate_var(Meta, Name, Kind, S, Callback) ->
|
||||
case S#elixir_scope.context of
|
||||
match ->
|
||||
TempVars = S#elixir_scope.temp_vars,
|
||||
case { orddict:is_key({ Name, Kind }, Vars), orddict:find(Name, TempVars) } of
|
||||
{ true, { ok, Kind } } ->
|
||||
{ { var, Line, orddict:fetch({ Name, Kind }, Vars) }, S };
|
||||
case { orddict:is_key(Tuple, Vars), is_list(TempVars) andalso ordsets:is_element(Tuple, TempVars) } of
|
||||
{ true, true } ->
|
||||
{ { var, Line, orddict:fetch(Tuple, Vars) }, S };
|
||||
{ Else, _ } ->
|
||||
{ NewVar, NS } = if
|
||||
Kind /= nil -> build_erl_var(Line, S);
|
||||
@@ -33,11 +35,14 @@ translate_var(Meta, Name, Kind, S, Callback) ->
|
||||
RealName = element(3, NewVar),
|
||||
ClauseVars = S#elixir_scope.clause_vars,
|
||||
{ NewVar, NS#elixir_scope{
|
||||
vars=orddict:store({ Name, Kind }, RealName, Vars),
|
||||
temp_vars=orddict:store(Name, Kind, TempVars),
|
||||
vars=orddict:store(Tuple, RealName, Vars),
|
||||
temp_vars=if
|
||||
TempVars == nil -> TempVars;
|
||||
true -> ordsets:add_element(Tuple, TempVars)
|
||||
end,
|
||||
clause_vars=if
|
||||
ClauseVars == nil -> nil;
|
||||
true -> orddict:store({ Name, Kind }, RealName, ClauseVars)
|
||||
ClauseVars == nil; Kind == false -> ClauseVars;
|
||||
true -> orddict:store(Tuple, RealName, ClauseVars)
|
||||
end
|
||||
} }
|
||||
end;
|
||||
@@ -76,18 +81,24 @@ build_ex_var(Line, Key, Name, S) when is_integer(Line) ->
|
||||
% Handle Macro.Env conversion
|
||||
|
||||
to_erl_env({ 'Elixir.Macro.Env', Module, File, _Line, Function, Aliases, Context,
|
||||
Requires, Functions, Macros, ContextModules, MacroAliases }) ->
|
||||
Requires, Functions, Macros, ContextModules, MacroAliases, Vars }) ->
|
||||
#elixir_scope{module=Module,file=File,
|
||||
function=Function,aliases=Aliases,context=Context,
|
||||
requires=Requires,macros=Macros,functions=Functions,
|
||||
context_modules=ContextModules,macro_aliases=MacroAliases}.
|
||||
context_modules=ContextModules,macro_aliases=MacroAliases,
|
||||
list_vars=Vars}.
|
||||
|
||||
to_ex_env({ Line, #elixir_scope{module=Module,file=File,
|
||||
function=Function,aliases=Aliases,context=Context,
|
||||
requires=Requires,macros=Macros,functions=Functions,
|
||||
context_modules=ContextModules,macro_aliases=MacroAliases} }) when is_integer(Line) ->
|
||||
context_modules=ContextModules,macro_aliases=MacroAliases,
|
||||
vars=Vars,list_vars=ListVars} }) when is_integer(Line) ->
|
||||
{ 'Elixir.Macro.Env', Module, File, Line, Function, Aliases,
|
||||
Context, Requires, Functions, Macros, ContextModules, MacroAliases }.
|
||||
Context, Requires, Functions, Macros, ContextModules, MacroAliases,
|
||||
list_vars(ListVars, Vars) }.
|
||||
|
||||
list_vars(nil, Vars) -> [K || { K, _ } <- Vars];
|
||||
list_vars(Other, _) -> Other.
|
||||
|
||||
% Provides a tuple with only the scope information we want to serialize.
|
||||
|
||||
@@ -130,8 +141,8 @@ deserialize_with_vars({ File, Functions, Requires, Macros,
|
||||
counter=[{'',length(Vars)}]
|
||||
}.
|
||||
|
||||
% Receives two scopes and return a new scope based on the second
|
||||
% with their variables merged.
|
||||
%% Receives two scopes and return a new scope based on the second
|
||||
%% with their variables merged.
|
||||
|
||||
umergev(S1, S2) ->
|
||||
V1 = S1#elixir_scope.vars,
|
||||
@@ -143,11 +154,22 @@ umergev(S1, S2) ->
|
||||
clause_vars=merge_clause_vars(C1, C2)
|
||||
}.
|
||||
|
||||
% Receives two scopes and return the later scope
|
||||
% keeping the variables from the first (counters,
|
||||
% imports and everything else are passed forward).
|
||||
%% Receives two scopes and return the first scope with
|
||||
%% counters and flags from the later.
|
||||
|
||||
umergec(S1, S2) ->
|
||||
S1#elixir_scope{
|
||||
counter=S2#elixir_scope.counter,
|
||||
extra_guards=S2#elixir_scope.extra_guards,
|
||||
super=S2#elixir_scope.super,
|
||||
caller=S2#elixir_scope.caller
|
||||
}.
|
||||
|
||||
%% Receives two scopes and return the later scope
|
||||
%% keeping the variables from the first (counters,
|
||||
%% imports and everything else are passed forward).
|
||||
|
||||
umergea(S1, S2) ->
|
||||
S2#elixir_scope{
|
||||
vars=S1#elixir_scope.vars,
|
||||
temp_vars=S1#elixir_scope.temp_vars,
|
||||
@@ -179,3 +201,40 @@ var_merger(_Var, K1, K2) ->
|
||||
var_number([$@|T], _Acc) -> var_number(T, []);
|
||||
var_number([H|T], Acc) -> var_number(T, [H|Acc]);
|
||||
var_number([], Acc) -> list_to_integer(lists:reverse(Acc)).
|
||||
|
||||
%% Setup the vars in scope from binding
|
||||
|
||||
vars_from_binding(Scope, Binding) ->
|
||||
Scope#elixir_scope{
|
||||
vars=binding_dict(Binding),
|
||||
temp_vars=[],
|
||||
clause_vars=nil,
|
||||
counter=[]
|
||||
}.
|
||||
|
||||
binding_dict(List) -> binding_dict(List, orddict:new()).
|
||||
binding_dict([{{H,Kind},_}|T], Dict) -> binding_dict(T, orddict:store({ H, Kind }, H, Dict));
|
||||
binding_dict([{H,_}|T], Dict) -> binding_dict(T, orddict:store({ H, nil }, H, Dict));
|
||||
binding_dict([], Dict) -> Dict.
|
||||
|
||||
binding_for_eval(Binding, Module) ->
|
||||
Keyword = orddict:from_list(Binding),
|
||||
case orddict:find('_@MODULE', Keyword) of
|
||||
{ ok, _ } -> Keyword;
|
||||
_ -> orddict:store('_@MODULE', Module, Keyword)
|
||||
end.
|
||||
|
||||
binding_from_vars(#elixir_scope{vars=Vars}, Binding) ->
|
||||
binding_from_vars(Binding, [], Binding, Vars).
|
||||
|
||||
binding_from_vars([{Var,_}|T], Acc, Binding, Vars) ->
|
||||
case lists:member($@, atom_to_list(Var)) of
|
||||
true ->
|
||||
binding_from_vars(T, Acc, Binding, Vars);
|
||||
false ->
|
||||
RealName = orddict:fetch({ Var, nil }, Vars),
|
||||
RealValue = proplists:get_value(RealName, Binding, nil),
|
||||
binding_from_vars(T, [{Var, RealValue}|Acc], Binding, Vars)
|
||||
end;
|
||||
|
||||
binding_from_vars([], Acc, _Binding, _Vars) -> lists:reverse(Acc).
|
||||
|
||||
+198
-120
@@ -10,73 +10,93 @@
|
||||
existing_atoms_only=false
|
||||
}).
|
||||
|
||||
-define(container2(T1, T2),
|
||||
-define(container(T1, T2),
|
||||
T1 == ${, T2 == $};
|
||||
T1 == $[, T2 == $]
|
||||
).
|
||||
|
||||
-define(comp3(T1, T2, T3),
|
||||
T1 == $=, T2 == $=, T3 == $=;
|
||||
T1 == $!, T2 == $=, T3 == $=
|
||||
).
|
||||
%% New ops table
|
||||
|
||||
-define(op3(T1, T2, T3),
|
||||
T1 == $<, T2 == $<, T3 == $<;
|
||||
T1 == $>, T2 == $>, T3 == $>;
|
||||
T1 == $^, T2 == $^, T3 == $^;
|
||||
T1 == $~, T2 == $~, T3 == $~;
|
||||
T1 == $&, T2 == $&, T3 == $&;
|
||||
T1 == $|, T2 == $|, T3 == $|
|
||||
).
|
||||
|
||||
-define(comp2(T1, T2),
|
||||
T1 == $=, T2 == $=;
|
||||
T1 == $!, T2 == $=;
|
||||
T1 == $<, T2 == $=;
|
||||
T1 == $>, T2 == $=
|
||||
).
|
||||
|
||||
-define(op2(T1, T2),
|
||||
T1 == $&, T2 == $&;
|
||||
T1 == $|, T2 == $|;
|
||||
T1 == $<, T2 == $>;
|
||||
T1 == $+, T2 == $+;
|
||||
T1 == $-, T2 == $-;
|
||||
T1 == $*, T2 == $*;
|
||||
T1 == $/, T2 == $/;
|
||||
T1 == $:, T2 == $:;
|
||||
T1 == $<, T2 == $-;
|
||||
T1 == $-, T2 == $>;
|
||||
T1 == $., T2 == $.;
|
||||
T1 == $|, T2 == $>;
|
||||
T1 == $/, T2 == $>;
|
||||
T1 == $=, T2 == $~
|
||||
).
|
||||
|
||||
-define(comp1(T),
|
||||
T == $<;
|
||||
T == $>
|
||||
).
|
||||
|
||||
-define(op1(T),
|
||||
T == $+;
|
||||
T == $-;
|
||||
T == $*;
|
||||
T == $/;
|
||||
T == $=;
|
||||
T == $|;
|
||||
T == $!;
|
||||
T == $^;
|
||||
T == $@
|
||||
).
|
||||
-define(at_op(T),
|
||||
T == $@).
|
||||
|
||||
-define(unary_op(T),
|
||||
T == '+';
|
||||
T == '-';
|
||||
T == '@';
|
||||
T == '!';
|
||||
T == '^'
|
||||
).
|
||||
% T == $&;
|
||||
T == $!;
|
||||
T == $^).
|
||||
|
||||
-define(unary_op3(T1, T2, T3),
|
||||
T1 == $~, T2 == $~, T3 == $~).
|
||||
|
||||
-define(exp_op3(T1, T2, T3),
|
||||
T1 == $^, T2 == $^, T3 == $^).
|
||||
|
||||
-define(two_op(T1, T2),
|
||||
T1 == $+, T2 == $+;
|
||||
T1 == $-, T2 == $-;
|
||||
T1 == $*, T2 == $*).
|
||||
|
||||
-define(than_op(T1, T2),
|
||||
T1 == $<, T2 == $>).
|
||||
|
||||
-define(mult_op(T),
|
||||
T == $* orelse T == $/).
|
||||
|
||||
-define(dual_op(T),
|
||||
T == $+ orelse T == $-).
|
||||
|
||||
-define(range_op(T1, T2),
|
||||
T1 == $., T2 == $.).
|
||||
|
||||
-define(arrow_op3(T1, T2, T3),
|
||||
T1 == $<, T2 == $<, T3 == $<;
|
||||
T1 == $>, T2 == $>, T3 == $>).
|
||||
|
||||
-define(arrow_op(T1, T2),
|
||||
T1 == $<, T2 == $-;
|
||||
T1 == $|, T2 == $>).
|
||||
|
||||
-define(comp_op(T),
|
||||
T == $<;
|
||||
T == $>).
|
||||
|
||||
-define(comp_op2(T1, T2),
|
||||
T1 == $=, T2 == $=;
|
||||
T1 == $=, T2 == $~;
|
||||
T1 == $!, T2 == $=;
|
||||
T1 == $<, T2 == $=;
|
||||
T1 == $>, T2 == $=).
|
||||
|
||||
-define(comp_op3(T1, T2, T3),
|
||||
T1 == $=, T2 == $=, T3 == $=;
|
||||
T1 == $!, T2 == $=, T3 == $=).
|
||||
|
||||
-define(and_op(T1, T2),
|
||||
T1 == $&, T2 == $&).
|
||||
|
||||
-define(or_op(T1, T2),
|
||||
T1 == $|, T2 == $|).
|
||||
|
||||
-define(and_op3(T1, T2, T3),
|
||||
T1 == $&, T2 == $&, T3 == $&).
|
||||
|
||||
-define(or_op3(T1, T2, T3),
|
||||
T1 == $|, T2 == $|, T3 == $|).
|
||||
|
||||
-define(match_op(T),
|
||||
T == $=).
|
||||
|
||||
-define(tail_op(T),
|
||||
T == $|).
|
||||
|
||||
-define(default_op(T1, T2),
|
||||
T1 == $/, T2 == $/).
|
||||
|
||||
-define(stab_op(T1, T2),
|
||||
T1 == $-, T2 == $>).
|
||||
|
||||
-define(type_op(T1, T2),
|
||||
T1 == $:, T2 == $:).
|
||||
|
||||
tokenize(String, Line, Opts) ->
|
||||
File = case lists:keyfind(file, 1, Opts) of
|
||||
@@ -205,13 +225,10 @@ tokenize([$?,Char|T], Line, Scope, Tokens) ->
|
||||
tokenize(T, Line, Scope, [{ number, Line, Char }|Tokens]);
|
||||
|
||||
% Dot identifier/operators
|
||||
|
||||
tokenize("..." ++ Rest, Line, Scope, Tokens) ->
|
||||
Token = check_call_identifier(identifier, Line, '...', Rest),
|
||||
tokenize(Rest, Line, Scope, [Token|Tokens]);
|
||||
|
||||
% ## Containers
|
||||
|
||||
tokenize([$.,T|Tail], Line, Scope, Tokens) when ?is_space(T) ->
|
||||
case [T|Tail] of
|
||||
[$\r,$\n|Rest] -> tokenize([$.|Rest], Line + 1, Scope, Tokens);
|
||||
@@ -219,22 +236,30 @@ tokenize([$.,T|Tail], Line, Scope, Tokens) when ?is_space(T) ->
|
||||
[_|Rest] -> tokenize([$.|Rest], Line, Scope, Tokens)
|
||||
end;
|
||||
|
||||
% ## Containers
|
||||
tokenize(".<<>>" ++ Rest, Line, Scope, Tokens) ->
|
||||
handle_call_identifier(Rest, Line, '<<>>', Scope, Tokens);
|
||||
|
||||
tokenize([$.,T1,T2|Rest], Line, Scope, Tokens) when ?container2(T1, T2) ->
|
||||
tokenize([$.,T1,T2|Rest], Line, Scope, Tokens) when ?container(T1, T2) ->
|
||||
handle_call_identifier(Rest, Line, list_to_atom([T1, T2]), Scope, Tokens);
|
||||
|
||||
% ## Three Token Operators
|
||||
tokenize([$.,T1,T2,T3|Rest], Line, Scope, Tokens) when ?comp3(T1, T2, T3); ?op3(T1, T2, T3) ->
|
||||
tokenize([$.,T1,T2,T3|Rest], Line, Scope, Tokens) when
|
||||
?unary_op3(T1, T2, T3); ?comp_op3(T1, T2, T3); ?and_op3(T1, T2, T3); ?or_op3(T1, T2, T3);
|
||||
?arrow_op3(T1, T2, T3); ?exp_op3(T1, T2, T3) ->
|
||||
handle_call_identifier(Rest, Line, list_to_atom([T1, T2, T3]), Scope, Tokens);
|
||||
|
||||
% ## Two Token Operators
|
||||
tokenize([$.,T1,T2|Rest], Line, Scope, Tokens) when ?comp2(T1, T2); ?op2(T1, T2) ->
|
||||
tokenize([$.,T1,T2|Rest], Line, Scope, Tokens) when
|
||||
?comp_op2(T1, T2); ?and_op(T1, T2); ?or_op(T1, T2); ?arrow_op(T1, T2);
|
||||
?range_op(T1, T2); ?than_op(T1, T2); ?default_op(T1, T2); ?two_op(T1, T2);
|
||||
?stab_op(T1, T2); ?type_op(T1, T2) ->
|
||||
handle_call_identifier(Rest, Line, list_to_atom([T1, T2]), Scope, Tokens);
|
||||
|
||||
% ## Single Token Operators
|
||||
tokenize([$.,T|Rest], Line, Scope, Tokens) when ?comp1(T); ?op1(T); T == $& ->
|
||||
tokenize([$.,T|Rest], Line, Scope, Tokens) when
|
||||
?at_op(T); ?unary_op(T); ?dual_op(T); ?mult_op(T); ?comp_op(T);
|
||||
?match_op(T); ?tail_op(T); T == $& ->
|
||||
handle_call_identifier(Rest, Line, list_to_atom([T]), Scope, Tokens);
|
||||
|
||||
% Dot call
|
||||
@@ -282,7 +307,7 @@ tokenize([$:,H|T], Line, #scope{file=File} = Scope, Tokens) when ?is_quote(H) ->
|
||||
interpolation_error(Error, " (for atom starting at line ~B)", [Line])
|
||||
end;
|
||||
|
||||
tokenize([$:,T|String], Line, Scope, Tokens) when ?is_new_atom(T) ->
|
||||
tokenize([$:,T|String], Line, Scope, Tokens) when ?is_atom_start(T) ->
|
||||
{ Rest, Atom } = tokenize_atom([T|String], [], Scope),
|
||||
tokenize(Rest, Line, Scope, [{ atom, Line, Atom }|Tokens]);
|
||||
|
||||
@@ -295,19 +320,26 @@ tokenize(":..." ++ Rest, Line, Scope, Tokens) ->
|
||||
tokenize(":<<>>" ++ Rest, Line, Scope, Tokens) ->
|
||||
tokenize(Rest, Line, Scope, [{ atom, Line, '<<>>' }|Tokens]);
|
||||
|
||||
tokenize([$:,T1,T2|Rest], Line, Scope, Tokens) when ?container2(T1, T2) ->
|
||||
tokenize([$:,T1,T2|Rest], Line, Scope, Tokens) when ?container(T1, T2) ->
|
||||
tokenize(Rest, Line, Scope, [{ atom, Line, list_to_atom([T1,T2]) }|Tokens]);
|
||||
|
||||
% ## Three Token Operators
|
||||
tokenize([$:,T1,T2,T3|Rest], Line, Scope, Tokens) when ?comp3(T1, T2, T3); ?op3(T1, T2, T3) ->
|
||||
tokenize([$:,T1,T2,T3|Rest], Line, Scope, Tokens) when
|
||||
?unary_op3(T1, T2, T3); ?comp_op3(T1, T2, T3); ?and_op3(T1, T2, T3); ?or_op3(T1, T2, T3);
|
||||
?arrow_op3(T1, T2, T3); ?exp_op3(T1, T2, T3) ->
|
||||
tokenize(Rest, Line, Scope, [{ atom, Line, list_to_atom([T1,T2,T3]) }|Tokens]);
|
||||
|
||||
% ## Two Token Operators
|
||||
tokenize([$:,T1,T2|Rest], Line, Scope, Tokens) when ?comp2(T1, T2); ?op2(T1, T2) ->
|
||||
tokenize([$:,T1,T2|Rest], Line, Scope, Tokens) when
|
||||
?comp_op2(T1, T2); ?and_op(T1, T2); ?or_op(T1, T2); ?arrow_op(T1, T2);
|
||||
?range_op(T1, T2); ?than_op(T1, T2); ?default_op(T1, T2); ?two_op(T1, T2);
|
||||
?stab_op(T1, T2); ?type_op(T1, T2) ->
|
||||
tokenize(Rest, Line, Scope, [{ atom, Line, list_to_atom([T1,T2]) }|Tokens]);
|
||||
|
||||
% ## Single Token Operators
|
||||
tokenize([$:,T|Rest], Line, Scope, Tokens) when ?comp1(T); ?op1(T); T == $&; T == $. ->
|
||||
tokenize([$:,T|Rest], Line, Scope, Tokens) when
|
||||
?at_op(T); ?unary_op(T); ?dual_op(T); ?mult_op(T); ?comp_op(T);
|
||||
?match_op(T); ?tail_op(T); T == $&; T == $. ->
|
||||
tokenize(Rest, Line, Scope, [{ atom, Line, list_to_atom([T]) }|Tokens]);
|
||||
|
||||
% End of line
|
||||
@@ -336,13 +368,24 @@ tokenize("\r\n" ++ Rest, Line, Scope, Tokens) ->
|
||||
tokenize([$&,H|Rest], Line, Scope, Tokens) when ?is_digit(H) ->
|
||||
tokenize(Rest, Line, Scope, [{ '&', Line, [list_to_integer([H])] }|Tokens]);
|
||||
|
||||
% ## Comparison three token operators
|
||||
tokenize([T1,T2,T3|Rest], Line, Scope, Tokens) when ?comp3(T1, T2, T3) ->
|
||||
handle_comp_op(Rest, Line, list_to_atom([T1,T2,T3]), Scope, Tokens);
|
||||
|
||||
% ## Three token operators
|
||||
tokenize([T1,T2,T3|Rest], Line, Scope, Tokens) when ?op3(T1, T2, T3) ->
|
||||
handle_op(Rest, Line, list_to_atom([T1,T2,T3]), Scope, Tokens);
|
||||
tokenize([T1,T2,T3|Rest], Line, Scope, Tokens) when ?unary_op3(T1, T2, T3) ->
|
||||
handle_nonl_op(Rest, Line, unary_op, list_to_atom([T1,T2,T3]), Scope, Tokens);
|
||||
|
||||
tokenize([T1,T2,T3|Rest], Line, Scope, Tokens) when ?comp_op3(T1, T2, T3) ->
|
||||
handle_op(Rest, Line, comp_op, list_to_atom([T1,T2,T3]), Scope, Tokens);
|
||||
|
||||
tokenize([T1,T2,T3|Rest], Line, Scope, Tokens) when ?and_op3(T1, T2, T3) ->
|
||||
handle_op(Rest, Line, and_op, list_to_atom([T1,T2,T3]), Scope, Tokens);
|
||||
|
||||
tokenize([T1,T2,T3|Rest], Line, Scope, Tokens) when ?or_op3(T1, T2, T3) ->
|
||||
handle_op(Rest, Line, or_op, list_to_atom([T1,T2,T3]), Scope, Tokens);
|
||||
|
||||
tokenize([T1,T2,T3|Rest], Line, Scope, Tokens) when ?arrow_op3(T1, T2, T3) ->
|
||||
handle_op(Rest, Line, arrow_op, list_to_atom([T1,T2,T3]), Scope, Tokens);
|
||||
|
||||
tokenize([T1,T2,T3|Rest], Line, Scope, Tokens) when ?exp_op3(T1, T2, T3) ->
|
||||
handle_op(Rest, Line, exp_op, list_to_atom([T1,T2,T3]), Scope, Tokens);
|
||||
|
||||
% ## Containers + punctuation tokens
|
||||
tokenize([T,T|Rest], Line, Scope, Tokens) when T == $<; T == $> ->
|
||||
@@ -354,21 +397,58 @@ tokenize([T|Rest], Line, Scope, Tokens) when T == $(;
|
||||
Token = { list_to_atom([T]), Line },
|
||||
handle_terminator(Rest, Line, Scope, Token, Tokens);
|
||||
|
||||
% ## Comparison two token operators
|
||||
tokenize([T1,T2|Rest], Line, Scope, Tokens) when ?comp2(T1, T2) ->
|
||||
handle_comp_op(Rest, Line, list_to_atom([T1, T2]), Scope, Tokens);
|
||||
|
||||
% ## Two Token Operators
|
||||
tokenize([T1,T2|Rest], Line, Scope, Tokens) when ?op2(T1, T2) ->
|
||||
handle_op(Rest, Line, list_to_atom([T1, T2]), Scope, Tokens);
|
||||
tokenize([T1,T2|Rest], Line, Scope, Tokens) when ?two_op(T1, T2) ->
|
||||
handle_op(Rest, Line, two_op, list_to_atom([T1, T2]), Scope, Tokens);
|
||||
|
||||
% ## Comparison single token operators
|
||||
tokenize([T|Rest], Line, Scope, Tokens) when ?comp1(T) ->
|
||||
handle_comp_op(Rest, Line, list_to_atom([T]), Scope, Tokens);
|
||||
tokenize([T1,T2|Rest], Line, Scope, Tokens) when ?than_op(T1, T2) ->
|
||||
handle_op(Rest, Line, than_op, list_to_atom([T1, T2]), Scope, Tokens);
|
||||
|
||||
tokenize([T1,T2|Rest], Line, Scope, Tokens) when ?range_op(T1, T2) ->
|
||||
handle_op(Rest, Line, range_op, list_to_atom([T1, T2]), Scope, Tokens);
|
||||
|
||||
tokenize([T1,T2|Rest], Line, Scope, Tokens) when ?arrow_op(T1, T2) ->
|
||||
handle_op(Rest, Line, arrow_op, list_to_atom([T1, T2]), Scope, Tokens);
|
||||
|
||||
tokenize([T1,T2|Rest], Line, Scope, Tokens) when ?comp_op2(T1, T2) ->
|
||||
handle_op(Rest, Line, comp_op, list_to_atom([T1, T2]), Scope, Tokens);
|
||||
|
||||
tokenize([T1,T2|Rest], Line, Scope, Tokens) when ?and_op(T1, T2) ->
|
||||
handle_op(Rest, Line, and_op, list_to_atom([T1, T2]), Scope, Tokens);
|
||||
|
||||
tokenize([T1,T2|Rest], Line, Scope, Tokens) when ?or_op(T1, T2) ->
|
||||
handle_op(Rest, Line, or_op, list_to_atom([T1, T2]), Scope, Tokens);
|
||||
|
||||
tokenize([T1,T2|Rest], Line, Scope, Tokens) when ?default_op(T1, T2) ->
|
||||
handle_op(Rest, Line, default_op, list_to_atom([T1, T2]), Scope, Tokens);
|
||||
|
||||
tokenize([T1,T2|Rest], Line, Scope, Tokens) when ?stab_op(T1, T2) ->
|
||||
handle_op(Rest, Line, stab_op, list_to_atom([T1, T2]), Scope, Tokens);
|
||||
|
||||
tokenize([T1,T2|Rest], Line, Scope, Tokens) when ?type_op(T1, T2) ->
|
||||
handle_op(Rest, Line, type_op, list_to_atom([T1, T2]), Scope, Tokens);
|
||||
|
||||
% ## Single Token Operators
|
||||
tokenize([T|Rest], Line, Scope, Tokens) when ?op1(T) ->
|
||||
handle_op(Rest, Line, list_to_atom([T]), Scope, Tokens);
|
||||
tokenize([T|Rest], Line, Scope, Tokens) when ?at_op(T) ->
|
||||
handle_nonl_op(Rest, Line, at_op, list_to_atom([T]), Scope, Tokens);
|
||||
|
||||
tokenize([T|Rest], Line, Scope, Tokens) when ?unary_op(T) ->
|
||||
handle_nonl_op(Rest, Line, unary_op, list_to_atom([T]), Scope, Tokens);
|
||||
|
||||
tokenize([T|Rest], Line, Scope, Tokens) when ?comp_op(T) ->
|
||||
handle_op(Rest, Line, comp_op, list_to_atom([T]), Scope, Tokens);
|
||||
|
||||
tokenize([T|Rest], Line, Scope, Tokens) when ?dual_op(T) ->
|
||||
handle_nonl_op(Rest, Line, dual_op, list_to_atom([T]), Scope, Tokens);
|
||||
|
||||
tokenize([T|Rest], Line, Scope, Tokens) when ?mult_op(T) ->
|
||||
handle_op(Rest, Line, mult_op, list_to_atom([T]), Scope, Tokens);
|
||||
|
||||
tokenize([T|Rest], Line, Scope, Tokens) when ?match_op(T) ->
|
||||
handle_op(Rest, Line, match_op, list_to_atom([T]), Scope, Tokens);
|
||||
|
||||
tokenize([T|Rest], Line, Scope, Tokens) when ?tail_op(T) ->
|
||||
handle_op(Rest, Line, tail_op, list_to_atom([T]), Scope, Tokens);
|
||||
|
||||
tokenize([$.|Rest], Line, Scope, Tokens) ->
|
||||
tokenize(Rest, Line, Scope, add_token_with_nl({ '.', Line }, Tokens));
|
||||
@@ -410,13 +490,13 @@ tokenize([H|_] = String, Line, Scope, Tokens) when ?is_downcase(H); H == $_ ->
|
||||
% Ambiguous unary/binary operators tokens
|
||||
|
||||
tokenize([Space, Sign, NotMarker|T], Line, Scope, [{ Identifier, _, _ } = H|Tokens]) when
|
||||
Sign == $+ orelse Sign == $-,
|
||||
?is_horizontal_space(Space),
|
||||
not(?is_space(NotMarker)),
|
||||
NotMarker /= $(, NotMarker /= $+, NotMarker /= $-, NotMarker /= $>,
|
||||
Identifier == identifier orelse Identifier == punctuated_identifier ->
|
||||
?dual_op(Sign),
|
||||
?is_horizontal_space(Space),
|
||||
not(?is_space(NotMarker)),
|
||||
NotMarker /= $(, NotMarker /= $+, NotMarker /= $-, NotMarker /= $>,
|
||||
Identifier == identifier orelse Identifier == punctuated_identifier ->
|
||||
Rest = [NotMarker|T],
|
||||
tokenize(Rest, Line, Scope, [{ list_to_atom([Sign]), Line }, setelement(1, H, op_identifier)|Tokens]);
|
||||
tokenize(Rest, Line, Scope, [{ dual_op, Line, list_to_atom([Sign]) }, setelement(1, H, op_identifier)|Tokens]);
|
||||
|
||||
% Spaces
|
||||
|
||||
@@ -456,26 +536,23 @@ handle_strings(T, Line, H, #scope{file=File} = Scope, Tokens) ->
|
||||
{ error, { Line, "invalid interpolation in key ", [$"|T] } }
|
||||
end;
|
||||
{ NewLine, Parts, Rest } ->
|
||||
Token = { string_type(H),Line,unescape_tokens(Parts) },
|
||||
Token = { string_type(H), Line, unescape_tokens(Parts) },
|
||||
tokenize(Rest, NewLine, Scope, [Token|Tokens]);
|
||||
Error ->
|
||||
interpolation_error(Error, " (for string starting at line ~B)", [Line])
|
||||
end.
|
||||
|
||||
handle_comp_op([$:|Rest], Line, Op, Scope, Tokens) when ?is_space(hd(Rest)) ->
|
||||
handle_nonl_op([$:|Rest], Line, _Kind, Op, Scope, Tokens) when ?is_space(hd(Rest)) ->
|
||||
tokenize(Rest, Line, Scope, [{ kw_identifier, Line, Op }|Tokens]);
|
||||
|
||||
handle_comp_op(Rest, Line, Op, Scope, Tokens) ->
|
||||
tokenize(Rest, Line, Scope, add_token_with_nl({ comp_op, Line, Op }, Tokens)).
|
||||
handle_nonl_op(Rest, Line, Kind, Op, Scope, Tokens) ->
|
||||
tokenize(Rest, Line, Scope, [{ Kind, Line, Op }|Tokens]).
|
||||
|
||||
handle_op([$:|Rest], Line, Op, Scope, Tokens) when ?is_space(hd(Rest)) ->
|
||||
handle_op([$:|Rest], Line, _Kind, Op, Scope, Tokens) when ?is_space(hd(Rest)) ->
|
||||
tokenize(Rest, Line, Scope, [{ kw_identifier, Line, Op }|Tokens]);
|
||||
|
||||
handle_op(Rest, Line, Op, Scope, Tokens) when ?unary_op(Op) ->
|
||||
tokenize(Rest, Line, Scope, [{ Op, Line }|Tokens]);
|
||||
|
||||
handle_op(Rest, Line, Op, Scope, Tokens) ->
|
||||
tokenize(Rest, Line, Scope, add_token_with_nl({ Op, Line }, Tokens)).
|
||||
handle_op(Rest, Line, Kind, Op, Scope, Tokens) ->
|
||||
tokenize(Rest, Line, Scope, add_token_with_nl({ Kind, Line, Op }, Tokens)).
|
||||
|
||||
handle_call_identifier(Rest, Line, Op, Scope, Tokens) ->
|
||||
Token = check_call_identifier(identifier, Line, Op, Rest),
|
||||
@@ -703,7 +780,7 @@ tokenize_any_identifier(String, Line, Acc, Scope) ->
|
||||
tokenize_kw_or_call_identifier(_Kind, Line, Atom, [$:,H|T]) when ?is_space(H) ->
|
||||
{ [H|T], { kw_identifier, Line, Atom } };
|
||||
|
||||
tokenize_kw_or_call_identifier(_Kind, Line, Atom, [$:,H|_]) when ?is_new_atom(H) ->
|
||||
tokenize_kw_or_call_identifier(_Kind, Line, Atom, [$:,H|_]) when ?is_atom_start(H) ->
|
||||
{ error, { Line, "keyword argument must be followed by space after: ", atom_to_list(Atom) ++ [$:] } };
|
||||
|
||||
tokenize_kw_or_call_identifier(Kind, Line, Atom, Rest) ->
|
||||
@@ -813,15 +890,16 @@ check_keyword(Line, Identifier, Atom, Tokens) when
|
||||
Identifier == identifier; Identifier == do_identifier;
|
||||
Identifier == bracket_identifier; Identifier == paren_identifier ->
|
||||
case keyword(Atom) of
|
||||
true -> { ok, [{ Atom, Line }|Tokens] };
|
||||
op -> { ok, add_token_with_nl({ Atom, Line }, Tokens) };
|
||||
block -> { ok, [{ block_identifier, Line, Atom }|Tokens] };
|
||||
do ->
|
||||
do ->
|
||||
case do_keyword_valid(Tokens) of
|
||||
true -> { ok, [{ Atom, Line }|Tokens] };
|
||||
false -> { error, "do" }
|
||||
end;
|
||||
false -> nomatch
|
||||
false -> nomatch;
|
||||
true -> { ok, [{ Atom, Line }|Tokens] };
|
||||
block -> { ok, [{ block_identifier, Line, Atom }|Tokens] };
|
||||
unary_op -> { ok, [{ unary_op, Line, Atom }|Tokens] };
|
||||
Kind -> { ok, add_token_with_nl({ Kind, Line, Atom }, Tokens) }
|
||||
end;
|
||||
|
||||
check_keyword(_, _, _, _) -> nomatch.
|
||||
@@ -836,19 +914,19 @@ keyword('end') -> true;
|
||||
keyword('true') -> true;
|
||||
keyword('false') -> true;
|
||||
keyword('nil') -> true;
|
||||
keyword('not') -> true;
|
||||
|
||||
% Special handling for do
|
||||
keyword('do') -> do;
|
||||
|
||||
% Bin operator keywords
|
||||
keyword('and') -> op;
|
||||
keyword('or') -> op;
|
||||
keyword('xor') -> op;
|
||||
keyword('when') -> op;
|
||||
keyword('in') -> op;
|
||||
keyword('inlist') -> op;
|
||||
keyword('inbits') -> op;
|
||||
% Operators keywords
|
||||
keyword('not') -> unary_op;
|
||||
keyword('and') -> and_op;
|
||||
keyword('or') -> or_op;
|
||||
keyword('xor') -> or_op;
|
||||
keyword('when') -> when_op;
|
||||
keyword('in') -> in_op;
|
||||
keyword('inlist') -> inc_op;
|
||||
keyword('inbits') -> inc_op;
|
||||
|
||||
% Block keywords
|
||||
keyword('after') -> block;
|
||||
|
||||
@@ -24,7 +24,7 @@ setup(Module) ->
|
||||
end.
|
||||
|
||||
cleanup(Module) ->
|
||||
if_tracker(Module, fun(Pid) -> ?tracker:stop(Pid) end).
|
||||
if_tracker(Module, fun(Pid) -> unlink(Pid), ?tracker:stop(Pid) end).
|
||||
|
||||
record_local(Tuple, Module) when is_atom(Module) ->
|
||||
if_tracker(Module, fun(Pid) ->
|
||||
@@ -122,7 +122,7 @@ format_error({import_conflict,{Receivers, Name, Arity}}) ->
|
||||
io_lib:format("imported ~ts.~ts/~B conflicts with local function",
|
||||
[elixir_errors:inspect(hd(Receivers)), Name, Arity]);
|
||||
|
||||
format_error({ unused_import, Module }) ->
|
||||
format_error({unused_import, Module}) ->
|
||||
io_lib:format("unused import ~ts", [elixir_errors:inspect(Module)]);
|
||||
|
||||
format_error({unused_args,{Name, Arity}}) ->
|
||||
|
||||
@@ -4,10 +4,11 @@
|
||||
-export([forms/4, 'forms!'/4]).
|
||||
-export([translate/2, translate_each/2, translate_arg/2,
|
||||
translate_args/2, translate_apply/7, translate_fn/3]).
|
||||
-import(elixir_scope, [umergev/2, umergec/2]).
|
||||
-import(elixir_errors, [syntax_error/3, syntax_error/4, parse_error/4,
|
||||
assert_function_scope/3, assert_module_scope/3, assert_no_guard_scope/3,
|
||||
assert_no_match_or_guard_scope/3]).
|
||||
-import(elixir_scope, [umergev/2, umergec/2, umergea/2]).
|
||||
-import(elixir_errors, [syntax_error/3, syntax_error/4,
|
||||
compile_error/3, compile_error/4,
|
||||
assert_function_scope/3, assert_module_scope/3,
|
||||
assert_no_guard_scope/3, assert_no_match_or_guard_scope/3]).
|
||||
-include("elixir.hrl").
|
||||
|
||||
forms(String, StartLine, File, Opts) ->
|
||||
@@ -26,8 +27,10 @@ forms(String, StartLine, File, Opts) ->
|
||||
|
||||
'forms!'(String, StartLine, File, Opts) ->
|
||||
case forms(String, StartLine, File, Opts) of
|
||||
{ ok, Forms } -> Forms;
|
||||
{ error, { Line, Error, Token } } -> parse_error(Line, File, Error, Token)
|
||||
{ ok, Forms } ->
|
||||
Forms;
|
||||
{ error, { Line, Error, Token } } ->
|
||||
elixir_errors:parse_error(Line, File, Error, Token)
|
||||
end.
|
||||
|
||||
translate(Forms, S) ->
|
||||
@@ -62,9 +65,6 @@ translate_each({ '__block__', Meta, Args }, S) when is_list(Args) ->
|
||||
{ TArgs, NS } = translate(Args, S),
|
||||
{ { block, ?line(Meta), TArgs }, NS };
|
||||
|
||||
translate_each({ '__scope__', _Meta, [[{erl,true}],[{do,Expr}]] }, S) ->
|
||||
{ Expr, S };
|
||||
|
||||
translate_each({ '__scope__', _Meta, [[{file,File}],[{do,Expr}]] }, S) ->
|
||||
Old = S#elixir_scope.file,
|
||||
{ TExpr, TS } = translate_each(Expr, S#elixir_scope{file=File}),
|
||||
@@ -80,18 +80,6 @@ translate_each({ '__op__', Meta, [Op, Left, Right] }, S) when is_atom(Op) ->
|
||||
{ [TLeft, TRight], NS } = translate_args([Left, Right], S),
|
||||
{ { op, ?line(Meta), convert_op(Op), TLeft, TRight }, NS };
|
||||
|
||||
translate_each({ '__ambiguousop__', Meta, [Var, H|T] }, S) ->
|
||||
{ Name, _, Kind } = Var,
|
||||
|
||||
case orddict:find({ Name, Kind }, S#elixir_scope.vars) of
|
||||
error -> translate_each({ Name, Meta, [H|T] }, S);
|
||||
_ ->
|
||||
case T of
|
||||
[] -> translate_each(rellocate_ambiguous_op(H, Var), S);
|
||||
_ -> syntax_error(Meta, S#elixir_scope.file, "Many arguments given to ~s, but ~s is a variable. Use even spaces to solve ambiguity.", [Name, Name])
|
||||
end
|
||||
end;
|
||||
|
||||
%% Lexical
|
||||
|
||||
translate_each({ alias, Meta, [Ref] }, S) ->
|
||||
@@ -106,7 +94,7 @@ translate_each({ alias, Meta, [Ref, KV] }, S) ->
|
||||
{ atom, _, Old } ->
|
||||
translate_alias(Meta, true, Old, TKV, ST);
|
||||
_ ->
|
||||
syntax_error(Meta, S#elixir_scope.file, "invalid args for alias, expected an atom or alias as argument")
|
||||
compile_error(Meta, S#elixir_scope.file, "invalid args for alias, expected a compile time atom or alias as argument")
|
||||
end;
|
||||
|
||||
translate_each({ require, Meta, [Ref] }, S) ->
|
||||
@@ -123,7 +111,7 @@ translate_each({ require, Meta, [Ref, KV] }, S) ->
|
||||
elixir_aliases:ensure_loaded(Meta, Old, ST),
|
||||
translate_require(Meta, Old, TKV, ST);
|
||||
_ ->
|
||||
syntax_error(Meta, S#elixir_scope.file, "invalid args for require, expected an atom or alias as argument")
|
||||
compile_error(Meta, S#elixir_scope.file, "invalid args for require, expected a compile time atom or alias as argument")
|
||||
end;
|
||||
|
||||
translate_each({ import, Meta, [Left] }, S) ->
|
||||
@@ -149,7 +137,7 @@ translate_each({ import, Meta, [Left, Right, KV] }, S) ->
|
||||
|
||||
Selector = case TSelector of
|
||||
{ atom, _, SelectorAtom } -> SelectorAtom;
|
||||
_ -> syntax_error(Meta, S#elixir_scope.file, "invalid selector for import, expected an atom")
|
||||
_ -> compile_error(Meta, S#elixir_scope.file, "invalid selector for import, expected a compile time atom")
|
||||
end,
|
||||
|
||||
case TRef of
|
||||
@@ -158,7 +146,7 @@ translate_each({ import, Meta, [Left, Right, KV] }, S) ->
|
||||
SF = elixir_import:import(Meta, Old, TKV, Selector, ST),
|
||||
translate_require(Meta, Old, TKV, SF);
|
||||
_ ->
|
||||
syntax_error(Meta, S#elixir_scope.file, "invalid name for import, expected an atom or alias")
|
||||
compile_error(Meta, S#elixir_scope.file, "invalid name for import, expected a compile time atom or alias")
|
||||
end;
|
||||
|
||||
%% Pseudo variables
|
||||
@@ -200,14 +188,14 @@ translate_each({ '__aliases__', Meta, _ } = Alias, S) ->
|
||||
%% Quoting
|
||||
|
||||
translate_each({ Unquote, Meta, [_|_] }, S) when Unquote == unquote; Unquote == unquote_splicing ->
|
||||
syntax_error(Meta, S#elixir_scope.file, "~p called outside quote", [Unquote]);
|
||||
compile_error(Meta, S#elixir_scope.file, "~p called outside quote", [Unquote]);
|
||||
|
||||
translate_each({ quote, Meta, [Opts] }, S) when is_list(Opts) ->
|
||||
case lists:keyfind(do, 1, Opts) of
|
||||
{ do, Do } ->
|
||||
translate_each({ quote, Meta, [lists:keydelete(do, 1, Opts), [{do,Do}]] }, S);
|
||||
false ->
|
||||
syntax_error(Meta, S#elixir_scope.file, "invalid args for quote, do is missing")
|
||||
syntax_error(Meta, S#elixir_scope.file, "missing do keyword in quote")
|
||||
end;
|
||||
|
||||
translate_each({ quote, Meta, [_] }, S) ->
|
||||
@@ -217,10 +205,10 @@ translate_each({ quote, Meta, [KV, Do] }, S) when is_list(Do) ->
|
||||
Exprs =
|
||||
case lists:keyfind(do, 1, Do) of
|
||||
{ do, E } -> E;
|
||||
false -> syntax_error(Meta, S#elixir_scope.file, "invalid args for quote")
|
||||
false -> syntax_error(Meta, S#elixir_scope.file, "missing do keyword in quote")
|
||||
end,
|
||||
|
||||
ValidOpts = [hygiene, context, var_context, location, line, file, unquote],
|
||||
ValidOpts = [hygiene, context, var_context, location, line, file, unquote, binding, bind_quoted],
|
||||
{ TKV, ST } = translate_opts(Meta, quote, ValidOpts, KV, S),
|
||||
|
||||
Hygiene = case lists:keyfind(hygiene, 1, TKV) of
|
||||
@@ -234,7 +222,7 @@ translate_each({ quote, Meta, [KV, Do] }, S) when is_list(Do) ->
|
||||
{ context, Atom } when is_atom(Atom) ->
|
||||
Atom;
|
||||
{ context, _ } ->
|
||||
syntax_error(Meta, S#elixir_scope.file, "invalid :context for quote, expected an atom or an alias");
|
||||
compile_error(Meta, S#elixir_scope.file, "invalid :context for quote, expected a compile time atom or an alias");
|
||||
false ->
|
||||
case S#elixir_scope.module of
|
||||
nil -> 'Elixir';
|
||||
@@ -251,39 +239,43 @@ translate_each({ quote, Meta, [KV, Do] }, S) when is_list(Do) ->
|
||||
false -> { nil, nil }
|
||||
end,
|
||||
|
||||
Line = case lists:keyfind(line, 1, TKV) of
|
||||
{ line, LineValue } -> LineValue;
|
||||
false -> DefaultLine
|
||||
end,
|
||||
Line = proplists:get_value(line, TKV, DefaultLine),
|
||||
File = proplists:get_value(file, TKV, DefaultFile),
|
||||
|
||||
File = case lists:keyfind(file, 1, TKV) of
|
||||
{ file, FileValue } -> FileValue;
|
||||
false -> DefaultFile
|
||||
end,
|
||||
|
||||
TFile = case File of
|
||||
Scope = case File of
|
||||
keep -> S#elixir_scope.file;
|
||||
_ -> File
|
||||
end,
|
||||
|
||||
WExprs = if
|
||||
is_binary(TFile) ->
|
||||
{ '__scope__', Meta, [[{file,TFile}],[{do,Exprs}]] };
|
||||
QExprs = if
|
||||
is_binary(Scope) ->
|
||||
{ '__scope__', Meta, [[{file,Scope}],[{do,Exprs}]] };
|
||||
File == nil ->
|
||||
Exprs;
|
||||
true ->
|
||||
syntax_error(Meta, S#elixir_scope.file, "invalid args for quote, expected :file to be a binary")
|
||||
compile_error(Meta, S#elixir_scope.file, "invalid :file for quote, expected a compile time binary")
|
||||
end,
|
||||
|
||||
{ Binding, DefaultUnquote } = case lists:keyfind(binding, 1, TKV) of
|
||||
{ binding, B } when is_list(B) ->
|
||||
elixir_errors:deprecation(Meta, S#elixir_scope.file, ":binding in quote is deprecated in favor of :bind_quoted"),
|
||||
{ B, false };
|
||||
false ->
|
||||
case lists:keyfind(bind_quoted, 1, TKV) of
|
||||
{ bind_quoted, BQ } -> { BQ, false };
|
||||
false -> { nil, true }
|
||||
end
|
||||
end,
|
||||
|
||||
Unquote = case lists:keyfind(unquote, 1, TKV) of
|
||||
{ unquote, false } -> false;
|
||||
_ -> true
|
||||
{ unquote, Bool } when is_boolean(Bool) -> Bool;
|
||||
false -> DefaultUnquote
|
||||
end,
|
||||
|
||||
Q = #elixir_quote{vars_hygiene=Vars, line=Line, unquote=Unquote,
|
||||
aliases_hygiene=Aliases, imports_hygiene=Imports, context=Context},
|
||||
|
||||
{ TExprs, _TQ, TS } = elixir_quote:erl_quote(WExprs, Q, ST),
|
||||
{ TExprs, _TQ, TS } = elixir_quote:erl_quote(QExprs, Binding, Q, ST),
|
||||
{ TExprs, TS };
|
||||
|
||||
translate_each({ quote, Meta, [_, _] }, S) ->
|
||||
@@ -301,7 +293,8 @@ translate_each({ 'var!', Meta, [{Name, _, Atom}, Kind] }, S) when is_atom(Name),
|
||||
translate_each({ 'var!', Meta, [Name, Kind] }, S) when is_atom(Name) ->
|
||||
Expanded = expand_quote_context(Meta, Kind, "invalid second argument for var!", S),
|
||||
elixir_scope:translate_var(Meta, Name, Expanded, S, fun() ->
|
||||
syntax_error(Meta, S#elixir_scope.file, "expected var!(~ts) to expand to an existing variable or be a part of a match", [Name])
|
||||
compile_error(Meta, S#elixir_scope.file, "expected var!(~ts) to expand to an existing "
|
||||
"variable or be a part of a match", [Name])
|
||||
end);
|
||||
|
||||
translate_each({ 'var!', Meta, [_, _] }, S) ->
|
||||
@@ -320,7 +313,7 @@ translate_each({ Kind, Meta, Args }, S) when is_list(Args), (Kind == lc) orelse
|
||||
|
||||
%% Super (exceptionally supports partial application)
|
||||
|
||||
translate_each({ super, Meta, Args } = Original, S) ->
|
||||
translate_each({ super, Meta, Args } = Original, S) when is_list(Args) ->
|
||||
case elixir_partials:handle(Original, S) of
|
||||
error ->
|
||||
assert_no_match_or_guard_scope(Meta, super, S),
|
||||
@@ -331,15 +324,11 @@ translate_each({ super, Meta, Args } = Original, S) ->
|
||||
{ _, Arity } = Function,
|
||||
|
||||
{ TArgs, TS } = if
|
||||
is_atom(Args) andalso Arity == 0 ->
|
||||
elixir_def_overridable:retrieve_args(Meta, Arity, S);
|
||||
is_atom(Args) ->
|
||||
elixir_errors:deprecation(Meta, S#elixir_scope.file, "omitting arguments of super is deprecated, please call super with arguments instead"),
|
||||
elixir_def_overridable:retrieve_args(Meta, Arity, S);
|
||||
not is_atom(Args) andalso length(Args) == Arity ->
|
||||
length(Args) == Arity ->
|
||||
translate_args(Args, S);
|
||||
true ->
|
||||
syntax_error(Meta, S#elixir_scope.file, "super must be called with the same number of arguments as the current function")
|
||||
syntax_error(Meta, S#elixir_scope.file, "super must be called with the same number of "
|
||||
"arguments as the current function")
|
||||
end,
|
||||
|
||||
Super = elixir_def_overridable:name(Module, Function),
|
||||
@@ -356,20 +345,20 @@ translate_each({ 'super?', Meta, [] }, S) ->
|
||||
%% Variables
|
||||
|
||||
translate_each({ '^', Meta, [ { Name, _, Kind } ] }, #elixir_scope{context=match} = S) when is_atom(Name), is_atom(Kind) ->
|
||||
case orddict:find({ Name, Kind }, S#elixir_scope.vars) of
|
||||
case orddict:find({ Name, Kind }, S#elixir_scope.backup_vars) of
|
||||
{ ok, Value } ->
|
||||
{ { var, Meta, Value }, S };
|
||||
error ->
|
||||
syntax_error(Meta, S#elixir_scope.file, "unbound variable ^~ts", [Name])
|
||||
compile_error(Meta, S#elixir_scope.file, "unbound variable ^~ts", [Name])
|
||||
end;
|
||||
|
||||
translate_each({ '^', Meta, [ { Name, _, Kind } ] }, S) when is_atom(Name), is_atom(Kind) ->
|
||||
syntax_error(Meta, S#elixir_scope.file,
|
||||
compile_error(Meta, S#elixir_scope.file,
|
||||
"cannot use ^~ts outside of match clauses", [Name]);
|
||||
|
||||
translate_each({ '^', Meta, [ Expr ] }, S) ->
|
||||
syntax_error(Meta, S#elixir_scope.file,
|
||||
"the unary operator ^ can only be used with variables, invalid expression ^~ts", ['Elixir.Macro':to_binary(Expr)]);
|
||||
"the unary operator ^ can only be used with variables, invalid expression ^~ts", ['Elixir.Macro':to_string(Expr)]);
|
||||
|
||||
translate_each({ Name, Meta, Kind }, S) when is_atom(Name), is_atom(Kind) ->
|
||||
elixir_scope:translate_var(Meta, Name, Kind, S, fun() ->
|
||||
@@ -378,7 +367,12 @@ translate_each({ Name, Meta, Kind }, S) when is_atom(Name), is_atom(Kind) ->
|
||||
|
||||
%% Local calls
|
||||
|
||||
translate_each({ '->', Meta, _Args }, S) ->
|
||||
syntax_error(Meta, S#elixir_scope.file, "unhandled operator ->");
|
||||
|
||||
translate_each({ Atom, Meta, Args } = Original, S) when is_atom(Atom) ->
|
||||
assert_no_ambiguous_op(Atom, Meta, Args, S),
|
||||
|
||||
case elixir_partials:is_sequential(Args) andalso
|
||||
elixir_dispatch:import_function(Meta, Atom, length(Args), S) of
|
||||
false ->
|
||||
@@ -387,13 +381,16 @@ translate_each({ Atom, Meta, Args } = Original, S) when is_atom(Atom) ->
|
||||
Callback = fun() ->
|
||||
case S#elixir_scope.context of
|
||||
match ->
|
||||
syntax_error(Meta, S#elixir_scope.file, "cannot invoke function ~ts/~B inside match", [Atom, length(Args)]);
|
||||
compile_error(Meta, S#elixir_scope.file,
|
||||
"cannot invoke function ~ts/~B inside match", [Atom, length(Args)]);
|
||||
guard ->
|
||||
Arity = length(Args),
|
||||
File = S#elixir_scope.file,
|
||||
case Arity of
|
||||
0 -> syntax_error(Meta, File, "unknown variable ~ts or cannot invoke function ~ts/~B inside guard", [Atom, Atom, Arity]);
|
||||
_ -> syntax_error(Meta, File, "cannot invoke local ~ts/~B inside guard", [Atom, Arity])
|
||||
0 -> compile_error(Meta, File, "unknown variable ~ts or cannot invoke "
|
||||
"function ~ts/~B inside guard", [Atom, Atom, Arity]);
|
||||
_ -> compile_error(Meta, File, "cannot invoke local ~ts/~B inside guard",
|
||||
[Atom, Arity])
|
||||
end;
|
||||
_ ->
|
||||
translate_local(Meta, Atom, Args, S)
|
||||
@@ -426,7 +423,7 @@ translate_each({ { '.', _, [Left, Right] }, Meta, Args } = Original, S) when is_
|
||||
elixir_dispatch:dispatch_require(Meta, Receiver, Right, Args, umergev(SL, SR), fun() ->
|
||||
case S#elixir_scope.context of
|
||||
Context when Receiver /= erlang, (Context == match) orelse (Context == guard) ->
|
||||
syntax_error(Meta, S#elixir_scope.file, "cannot invoke remote function ~ts.~ts/~B inside ~ts",
|
||||
compile_error(Meta, S#elixir_scope.file, "cannot invoke remote function ~ts.~ts/~B inside ~ts",
|
||||
[elixir_errors:inspect(Receiver), Right, length(Args), Context]);
|
||||
_ ->
|
||||
Callback()
|
||||
@@ -435,7 +432,7 @@ translate_each({ { '.', _, [Left, Right] }, Meta, Args } = Original, S) when is_
|
||||
_ ->
|
||||
case S#elixir_scope.context of
|
||||
Context when Context == match; Context == guard ->
|
||||
syntax_error(Meta, S#elixir_scope.file, "cannot invoke remote function ~ts/~B inside ~ts",
|
||||
compile_error(Meta, S#elixir_scope.file, "cannot invoke remote function ~ts/~B inside ~ts",
|
||||
[Right, length(Args), Context]);
|
||||
_ ->
|
||||
Callback()
|
||||
@@ -466,7 +463,7 @@ translate_each({ { '.', _, [Expr] }, Meta, Args } = Original, S) ->
|
||||
|
||||
translate_each({ Invalid, Meta, _Args }, S) ->
|
||||
syntax_error(Meta, S#elixir_scope.file, "unexpected parenthesis after ~ts",
|
||||
['Elixir.Macro':to_binary(Invalid)]);
|
||||
['Elixir.Macro':to_string(Invalid)]);
|
||||
|
||||
%% Literals
|
||||
|
||||
@@ -478,18 +475,30 @@ translate_each(Literal, S) ->
|
||||
%% Opts
|
||||
|
||||
translate_opts(Meta, Kind, Allowed, Opts, S) ->
|
||||
{ TOpts, TS } = translate_each(Opts, S),
|
||||
FOpts = elixir_tree_helpers:erl_to_elixir(TOpts),
|
||||
validate_opts(Meta, Kind, Allowed, FOpts, TS),
|
||||
{ FOpts, TS }.
|
||||
{ Expanded, TS } = case literal_opts(Opts) orelse skip_expansion(S#elixir_scope.module) of
|
||||
true -> { Opts, S };
|
||||
false -> 'Elixir.Macro':expand_all(Opts, elixir_scope:to_ex_env({ ?line(Meta), S }), S)
|
||||
end,
|
||||
validate_opts(Meta, Kind, Allowed, Expanded, TS),
|
||||
{ Expanded, TS }.
|
||||
|
||||
skip_expansion('Elixir.Kernel') -> true;
|
||||
skip_expansion('Elixir.Kernel.Typespec') -> true;
|
||||
skip_expansion(_) -> false.
|
||||
|
||||
literal_opts({ X, Y }) -> literal_opts(X) andalso literal_opts(Y);
|
||||
literal_opts(Opts) when is_list(Opts) -> lists:all(fun literal_opts/1, Opts);
|
||||
literal_opts(Opts) when is_atom(Opts); is_number(Opts); is_bitstring(Opts) -> true;
|
||||
literal_opts(_) -> false.
|
||||
|
||||
validate_opts(Meta, Kind, Allowed, Opts, S) when is_list(Opts) ->
|
||||
[begin
|
||||
syntax_error(Meta, S#elixir_scope.file, "unsupported option ~s given to ~s", [Key, Kind])
|
||||
compile_error(Meta, S#elixir_scope.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) ->
|
||||
syntax_error(Meta, S#elixir_scope.file, "invalid options for ~s, expected a keyword list", [Kind]).
|
||||
compile_error(Meta, S#elixir_scope.file, "invalid options for ~s, expected a keyword list", [Kind]).
|
||||
|
||||
%% Quote
|
||||
|
||||
@@ -499,7 +508,7 @@ expand_quote_context(Meta, Alias, Msg, S) ->
|
||||
{ { atom, _, Atom }, _ } ->
|
||||
Atom;
|
||||
_ ->
|
||||
syntax_error(Meta, S#elixir_scope.file, "~ts, expected a compile time available alias or an atom", [Msg])
|
||||
compile_error(Meta, S#elixir_scope.file, "~ts, expected a compile time available alias or an atom", [Msg])
|
||||
end.
|
||||
|
||||
%% Require
|
||||
@@ -526,14 +535,14 @@ translate_alias(Meta, IncludeByDefault, Old, TKV, S) ->
|
||||
true -> Old
|
||||
end;
|
||||
_ ->
|
||||
syntax_error(Meta, S#elixir_scope.file,
|
||||
"invalid args for alias, expected an atom or alias in option :as")
|
||||
compile_error(Meta, S#elixir_scope.file,
|
||||
"invalid :as for alias, expected a compile time atom or alias")
|
||||
end,
|
||||
|
||||
case (New == Old) orelse (length(string:tokens(atom_to_list(New), ".")) == 2) of
|
||||
true -> ok;
|
||||
false -> syntax_error(Meta, S#elixir_scope.file,
|
||||
"invalid args for alias, cannot create nested alias ~s", [elixir_errors:inspect(New)])
|
||||
false -> compile_error(Meta, S#elixir_scope.file,
|
||||
"invalid :as for alias, nested alias ~s not allowed", [elixir_errors:inspect(New)])
|
||||
end,
|
||||
|
||||
{ { atom, ?line(Meta), nil }, elixir_aliases:store(Meta, New, Old, S) }.
|
||||
@@ -552,14 +561,15 @@ no_alias_expansion({ '__aliases__', Meta, [H|T] }) when (H /= 'Elixir') and is_a
|
||||
no_alias_expansion(Other) ->
|
||||
Other.
|
||||
|
||||
is_atom_tuple({ atom, _, _ }) -> true;
|
||||
is_atom_tuple(_) -> false.
|
||||
|
||||
%% Function
|
||||
|
||||
translate_fn(Meta, Clauses, S) ->
|
||||
Line = ?line(Meta),
|
||||
|
||||
Transformer = fun({ ArgsWithGuards, Expr }, Acc) ->
|
||||
{ Args, Guards } = elixir_clauses:extract_last_guards(ArgsWithGuards),
|
||||
elixir_clauses:assigns_block(Line, fun elixir_translator:translate/2, Args, [Expr], Guards, umergec(S, Acc))
|
||||
Transformer = fun({ ArgsWithGuards, CMeta, Expr }, Acc) ->
|
||||
{ Args, Guards } = elixir_clauses:extract_splat_guards(ArgsWithGuards),
|
||||
elixir_clauses:assigns_block(?line(CMeta), fun elixir_translator:translate/2, Args, [Expr], Guards, umergec(S, Acc))
|
||||
end,
|
||||
|
||||
{ TClauses, NS } = lists:mapfoldl(Transformer, S, Clauses),
|
||||
@@ -567,13 +577,16 @@ translate_fn(Meta, Clauses, S) ->
|
||||
|
||||
case length(lists:usort(Arities)) of
|
||||
1 ->
|
||||
{ { 'fun', Line, { clauses, TClauses } }, umergec(S, NS) };
|
||||
{ { 'fun', ?line(Meta), { clauses, TClauses } }, umergec(S, NS) };
|
||||
_ ->
|
||||
syntax_error(Meta, S#elixir_scope.file, "cannot mix clauses with different arities in function definition")
|
||||
end.
|
||||
|
||||
%% Locals
|
||||
|
||||
translate_local(Meta, Name, Args, #elixir_scope{local=nil,function=nil} = S) ->
|
||||
compile_error(Meta, S#elixir_scope.file, "function ~ts/~B undefined", [Name, length(Args)]);
|
||||
|
||||
translate_local(Meta, Name, Args, #elixir_scope{local=nil,module=Module,function=Function} = S) ->
|
||||
elixir_tracker:record_local({ Name, length(Args) }, Module, Function),
|
||||
Line = ?line(Meta),
|
||||
@@ -602,14 +615,14 @@ translate_local(Meta, Name, Args, S) ->
|
||||
|
||||
translate_arg(Arg, { Acc, S }) ->
|
||||
{ TArg, TAcc } = translate_each(Arg, Acc),
|
||||
{ TArg, { umergec(Acc, TAcc), umergev(S, TAcc) } }.
|
||||
{ TArg, { umergea(Acc, TAcc), umergev(S, TAcc) } }.
|
||||
|
||||
translate_args(Args, #elixir_scope{context=match} = S) ->
|
||||
translate(Args, S);
|
||||
|
||||
translate_args(Args, S) ->
|
||||
{ TArgs, { SC, SV } } = lists:mapfoldl(fun translate_arg/2, {S, S}, Args),
|
||||
{ TArgs, umergec(SV, SC) }.
|
||||
{ TArgs, umergea(SV, SC) }.
|
||||
|
||||
%% Translate apply
|
||||
%% Used by both apply and external function invocation macros.
|
||||
@@ -650,14 +663,20 @@ convert_op('<=') -> '=<';
|
||||
convert_op('<-') -> '!';
|
||||
convert_op(Else) -> Else.
|
||||
|
||||
is_atom_tuple({ atom, _, _ }) -> true;
|
||||
is_atom_tuple(_) -> false.
|
||||
assert_no_ambiguous_op(Name, Meta, [Arg], S) ->
|
||||
case lists:keyfind(ambiguous_op, 1, Meta) of
|
||||
{ ambiguous_op, Kind } ->
|
||||
case orddict:find({ Name, Kind }, S#elixir_scope.vars) of
|
||||
error -> ok;
|
||||
_ ->
|
||||
syntax_error(Meta, S#elixir_scope.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])
|
||||
end;
|
||||
_ -> ok
|
||||
end;
|
||||
|
||||
rellocate_ambiguous_op({ Op, Meta, [Expr] }, Var) when Op == '+'; Op == '-' ->
|
||||
{ Op, Meta, [Var, Expr] };
|
||||
|
||||
rellocate_ambiguous_op({ Call, Meta, [H|T] }, Var) ->
|
||||
{ Call, Meta, [rellocate_ambiguous_op(H, Var)|T] }.
|
||||
assert_no_ambiguous_op(_Atom, _Meta, _Args, _S) -> ok.
|
||||
|
||||
%% Comprehensions
|
||||
|
||||
@@ -668,7 +687,7 @@ translate_comprehension(Meta, Kind, Args, S) ->
|
||||
{ TExpr, SE } = translate_comprehension_do(Meta, Kind, Expr, SC),
|
||||
{ { Kind, ?line(Meta), TExpr, TCases }, umergec(S, SE) };
|
||||
_ ->
|
||||
syntax_error(Meta, S#elixir_scope.file, "keyword argument :do missing for comprehension ~s", [Kind])
|
||||
syntax_error(Meta, S#elixir_scope.file, "missing do keyword in comprehension ~s", [Kind])
|
||||
end.
|
||||
|
||||
translate_comprehension_do(_Meta, bc, { '<<>>', _, _ } = Expr, S) ->
|
||||
@@ -694,5 +713,7 @@ translate_comprehension_clause(_Meta, {inlist, Meta, [Left, Right]}, S) ->
|
||||
{ { generate, ?line(Meta), TLeft, TRight }, SL };
|
||||
|
||||
translate_comprehension_clause(Meta, X, S) ->
|
||||
Line = ?line(Meta),
|
||||
{ TX, TS } = translate_each(X, S),
|
||||
elixir_tree_helpers:convert_to_boolean(?line(Meta), TX, true, false, TS).
|
||||
{ BX, BS } = elixir_tree_helpers:convert_to_boolean(Line, TX, true, false, TS),
|
||||
{ { match, Line, { var, Line, '_' }, BX }, BS }.
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
%% to generate erlang abstract format for basic structures
|
||||
%% as lists, condition clauses, etc.
|
||||
-module(elixir_tree_helpers).
|
||||
-export([elixir_to_erl/1, erl_to_elixir/1, split_last/1,
|
||||
-export([elixir_to_erl/1, split_last/1,
|
||||
cons_to_list/1, list_to_cons/2, list_to_cons/3,
|
||||
convert_to_boolean/5, returns_boolean/1, get_line/1]).
|
||||
-include("elixir.hrl").
|
||||
@@ -34,8 +34,11 @@ list_to_cons(Line, List, Tail) ->
|
||||
elixir_to_erl(Tree) when is_tuple(Tree) ->
|
||||
{ tuple, 0, [elixir_to_erl(X) || X <- tuple_to_list(Tree)] };
|
||||
|
||||
elixir_to_erl([]) ->
|
||||
{ nil, 0 };
|
||||
|
||||
elixir_to_erl(Tree) when is_list(Tree) ->
|
||||
elixir_to_erl_cons(lists:reverse(Tree), { nil, 0 });
|
||||
elixir_to_erl_cons_1(Tree, []);
|
||||
|
||||
elixir_to_erl(Tree) when is_atom(Tree) ->
|
||||
{ atom, 0, Tree };
|
||||
@@ -49,26 +52,13 @@ elixir_to_erl(Tree) when is_float(Tree) ->
|
||||
elixir_to_erl(Tree) when is_binary(Tree) ->
|
||||
{ bin, 0, [{ bin_element, 0, { string, 0, binary_to_list(Tree) }, default, default }] }.
|
||||
|
||||
elixir_to_erl_cons([H|T], Acc) ->
|
||||
elixir_to_erl_cons(T, { cons, 0, elixir_to_erl(H), Acc });
|
||||
elixir_to_erl_cons_1([H|T], Acc) -> elixir_to_erl_cons_1(T, [H|Acc]);
|
||||
elixir_to_erl_cons_1(Other, Acc) -> elixir_to_erl_cons_2(Acc, elixir_to_erl(Other)).
|
||||
|
||||
elixir_to_erl_cons([], Acc) -> Acc.
|
||||
|
||||
erl_to_elixir({ tuple, _, Args }) ->
|
||||
list_to_tuple([erl_to_elixir(X) || X <- Args]);
|
||||
|
||||
erl_to_elixir({ Kind, _, Value }) when Kind == atom; Kind == integer; Kind == float ->
|
||||
Value;
|
||||
|
||||
erl_to_elixir({ bin, _, [{ bin_element, 0, { string, 0, String }, default, default }] }) ->
|
||||
list_to_binary(String);
|
||||
|
||||
erl_to_elixir({ nil, _ }) -> [];
|
||||
|
||||
erl_to_elixir({ cons, _, Left, Right }) -> [erl_to_elixir(Left)|erl_to_elixir(Right)];
|
||||
|
||||
erl_to_elixir(Other) ->
|
||||
{ '__scope__', [], [[{erl,true}],[{do,Other}]] }.
|
||||
elixir_to_erl_cons_2([H|T], Acc) ->
|
||||
elixir_to_erl_cons_2(T, { cons, 0, elixir_to_erl(H), Acc });
|
||||
elixir_to_erl_cons_2([], Acc) ->
|
||||
Acc.
|
||||
|
||||
%% Others
|
||||
|
||||
@@ -94,7 +84,7 @@ returns_boolean({ call, _, { remote, _, { atom, _, erlang }, { atom, _, Fun } },
|
||||
returns_boolean({ call, _, { remote, _, { atom, _, erlang }, { atom, _, Fun } }, [_,_,_] }) when
|
||||
Fun == function_exported -> true;
|
||||
|
||||
returns_boolean({ atom, _, Bool }) when Bool == true; Bool == false -> true;
|
||||
returns_boolean({ atom, _, Bool }) when is_boolean(Bool) -> true;
|
||||
|
||||
returns_boolean({ 'case', _, _, Clauses }) ->
|
||||
lists:all(fun
|
||||
|
||||
@@ -6,11 +6,11 @@
|
||||
clauses(Meta, Clauses, S) ->
|
||||
Catch = elixir_clauses:get_pairs(Meta, 'catch', Clauses, S),
|
||||
Rescue = elixir_clauses:get_pairs(Meta, rescue, Clauses, S),
|
||||
Transformer = fun(X, Acc) -> each_clause(Meta, X, umergec(S, Acc)) end,
|
||||
Transformer = fun(X, Acc) -> each_clause(X, umergec(S, Acc)) end,
|
||||
lists:mapfoldl(Transformer, S, Rescue ++ Catch).
|
||||
|
||||
each_clause(Meta, { 'catch', Raw, Expr }, S) ->
|
||||
{ Args, Guards } = elixir_clauses:extract_last_guards(Raw),
|
||||
each_clause({ 'catch', Meta, Raw, Expr }, S) ->
|
||||
{ Args, Guards } = elixir_clauses:extract_splat_guards(Raw),
|
||||
|
||||
Final = case Args of
|
||||
[X] -> [throw, X, { '_', Meta, nil }];
|
||||
@@ -23,19 +23,19 @@ each_clause(Meta, { 'catch', Raw, Expr }, S) ->
|
||||
Condition = { '{}', Meta, Final },
|
||||
elixir_clauses:assigns_block(?line(Meta), fun elixir_translator:translate_each/2, Condition, [Expr], Guards, S);
|
||||
|
||||
each_clause(Meta, { rescue, [Condition|T], Expr }, S) ->
|
||||
each_clause({ rescue, Meta, [Condition], Expr }, S) ->
|
||||
case normalize_rescue(Meta, Condition, S) of
|
||||
{ Left, Right } ->
|
||||
case Left of
|
||||
{ '_', _, _ } ->
|
||||
{ ClauseVar, CS } = elixir_scope:build_ex_var(?line(Meta), S),
|
||||
{ Clause, _ } = rescue_guards(Meta, ClauseVar, Right, S),
|
||||
each_clause(Meta, { 'catch', [error, Clause|T], Expr }, CS);
|
||||
each_clause({ 'catch', Meta, Clause, Expr }, CS);
|
||||
_ ->
|
||||
{ Clause, Safe } = rescue_guards(Meta, Left, Right, S),
|
||||
case Safe of
|
||||
true ->
|
||||
each_clause(Meta, { 'catch', [error, Clause|T], Expr }, S);
|
||||
each_clause({ 'catch', Meta, Clause, Expr }, S);
|
||||
false ->
|
||||
{ ClauseVar, CS } = elixir_scope:build_ex_var(?line(Meta), S),
|
||||
{ FinalClause, _ } = rescue_guards(Meta, ClauseVar, Right, S),
|
||||
@@ -44,17 +44,17 @@ each_clause(Meta, { rescue, [Condition|T], Expr }, S) ->
|
||||
{ { '.', Meta, ['Elixir.Exception', normalize] }, Meta, [ClauseVar] }
|
||||
] },
|
||||
FinalExpr = prepend_to_block(Meta, Match, Expr),
|
||||
each_clause(Meta, { 'catch', [error, FinalClause|T], FinalExpr }, CS)
|
||||
each_clause({ 'catch', Meta, FinalClause, FinalExpr }, CS)
|
||||
end
|
||||
end;
|
||||
_ ->
|
||||
each_clause(Meta, { 'catch', [error, Condition|T], Expr }, S)
|
||||
each_clause({ 'catch', Meta, [error, Condition], Expr }, S)
|
||||
end;
|
||||
|
||||
each_clause(Meta, {rescue,_,_}, S) ->
|
||||
each_clause({rescue,Meta,_,_}, S) ->
|
||||
elixir_errors:syntax_error(Meta, S#elixir_scope.file, "too many arguments given for rescue");
|
||||
|
||||
each_clause(Meta, {Key,_,_}, S) ->
|
||||
each_clause({Key,Meta,_,_}, S) ->
|
||||
elixir_errors:syntax_error(Meta, S#elixir_scope.file, "invalid key ~ts in try", [Key]).
|
||||
|
||||
%% Helpers
|
||||
@@ -79,11 +79,11 @@ normalize_rescue(_, { in, Meta, [Left, Right] }, S) ->
|
||||
_ when is_list(Right) ->
|
||||
is_valid_rescue_list(Right, S) andalso { Left, Right };
|
||||
_ ->
|
||||
Expanded = 'Elixir.Macro':expand(Right, elixir_scope:to_ex_env({ ?line(Meta), S })),
|
||||
case is_valid_rescue_list(Expanded, S) of
|
||||
{ Expanded, ES } = 'Elixir.Macro':expand_all(Right, elixir_scope:to_ex_env({ ?line(Meta), S }), S),
|
||||
case is_valid_rescue_list(Expanded, ES) of
|
||||
true -> { Left, Expanded };
|
||||
false ->
|
||||
elixir_errors:syntax_error(Meta, S#elixir_scope.file, "invalid use of operator \"in\" in rescue inside try")
|
||||
elixir_errors:syntax_error(Meta, ES#elixir_scope.file, "invalid use of operator \"in\" in rescue inside try")
|
||||
end
|
||||
end;
|
||||
|
||||
@@ -92,12 +92,12 @@ normalize_rescue(Meta, Condition, S) ->
|
||||
{ { atom, _, Atom }, _ } ->
|
||||
normalize_rescue(Meta, { in, Meta, [{ '_', Meta, nil }, [Atom]] }, S);
|
||||
_ ->
|
||||
elixir_errors:syntax_error(Meta, S#elixir_scope.file, "invalid rescue clause. The clause should match on an alias, "
|
||||
"a variable or be in the `var in [alias]` format")
|
||||
elixir_errors:syntax_error(Meta, S#elixir_scope.file, "invalid rescue clause. The clause should "
|
||||
"match on an alias, a variable or be in the `var in [alias]` format")
|
||||
end.
|
||||
|
||||
%% Convert rescue clauses into guards.
|
||||
rescue_guards(_, Var, nil, _) -> { Var, false };
|
||||
rescue_guards(_, Var, nil, _) -> { [error, Var], false };
|
||||
|
||||
rescue_guards(Meta, Var, Guards, S) ->
|
||||
{ RawElixir, RawErlang } = rescue_each_var(Meta, Var, Guards),
|
||||
@@ -114,7 +114,7 @@ rescue_guards(Meta, Var, Guards, S) ->
|
||||
[join(Meta, 'and', [IsTuple, IsException, OrElse])|Erlang]
|
||||
end,
|
||||
{
|
||||
{ 'when', Meta, [Var, reverse_join(Meta, 'when', Final)] },
|
||||
[{ 'when', Meta, [error, Var, reverse_join(Meta, 'when', Final)] }],
|
||||
Safe
|
||||
}.
|
||||
|
||||
@@ -238,8 +238,8 @@ erlang_rescue_guard_for(Meta, Var, 'Elixir.ErlangError') ->
|
||||
%% Helpers
|
||||
|
||||
format_error({ rescue_no_match, Var, Alias }) ->
|
||||
VarBinary = 'Elixir.Macro':to_binary(Var),
|
||||
AliasBinary = 'Elixir.Macro':to_binary(Alias),
|
||||
VarBinary = 'Elixir.Macro':to_string(Var),
|
||||
AliasBinary = 'Elixir.Macro':to_string(Alias),
|
||||
Message = "rescue clause (~ts = ~ts) can never match, maybe you meant to write: ~ts in [~ts] ?",
|
||||
io_lib:format(Message, [AliasBinary, VarBinary, VarBinary, AliasBinary]).
|
||||
|
||||
@@ -269,4 +269,4 @@ prepend_to_block(_Meta, Expr, { '__block__', Meta, Args }) ->
|
||||
{ '__block__', Meta, [Expr|Args] };
|
||||
|
||||
prepend_to_block(Meta, Expr, Args) ->
|
||||
{ '__block__', Meta, [Expr, Args] }.
|
||||
{ '__block__', Meta, [Expr, Args] }.
|
||||
|
||||
@@ -2,27 +2,31 @@ ExUnit.start []
|
||||
|
||||
defmodule KernelTest do
|
||||
use ExUnit.Case, async: true
|
||||
doctest Kernel
|
||||
doctest Kernel.SpecialForms
|
||||
|
||||
doctest Access.List
|
||||
doctest Binary.Chars.List
|
||||
doctest Binary.Inspect.Atom
|
||||
doctest Binary.Inspect.BitString
|
||||
doctest Binary.Inspect.List
|
||||
doctest Binary.Inspect.Tuple
|
||||
doctest Binary.Inspect.Number
|
||||
doctest Binary.Inspect.Regex
|
||||
doctest Inspect.Atom
|
||||
doctest Inspect.BitString
|
||||
doctest Inspect.List
|
||||
doctest Inspect.Number
|
||||
doctest Inspect.Regex
|
||||
doctest Inspect.Tuple
|
||||
doctest Bitwise
|
||||
doctest Code
|
||||
doctest Dict
|
||||
doctest Enum
|
||||
doctest IO.ANSI
|
||||
doctest Regex
|
||||
doctest String
|
||||
doctest Keyword
|
||||
doctest Kernel
|
||||
doctest Kernel.SpecialForms
|
||||
doctest List
|
||||
doctest Macro
|
||||
doctest Module
|
||||
doctest OptionParser
|
||||
doctest Path
|
||||
doctest Module
|
||||
doctest Macro
|
||||
doctest List
|
||||
doctest Keyword
|
||||
doctest Regex
|
||||
doctest Set
|
||||
doctest Stream
|
||||
doctest String
|
||||
doctest Inspect.Algebra
|
||||
end
|
||||
|
||||
@@ -3,73 +3,36 @@ Code.require_file "test_helper.exs", __DIR__
|
||||
defmodule AccessTest do
|
||||
use ExUnit.Case, async: true
|
||||
|
||||
# Test nil at compilation time does not fail
|
||||
# and that @config[:foo] has proper precedence.
|
||||
@config nil
|
||||
nil = @config[:foo]
|
||||
|
||||
@config [foo: :bar]
|
||||
:bar = @config[:foo]
|
||||
|
||||
@mod :lists
|
||||
[1, 2, 3] = @mod.flatten([1, [2], 3])
|
||||
|
||||
@mod -13
|
||||
-13 = @mod
|
||||
|
||||
test :nil do
|
||||
assert nil[:foo] == nil
|
||||
end
|
||||
|
||||
test :list do
|
||||
assert [foo: :bar][:foo] == :bar
|
||||
assert [foo: [bar: :baz]][:foo][:bar] == :baz
|
||||
assert [foo: [bar: :baz]][:fuu][:bar] == nil
|
||||
end
|
||||
|
||||
test :nil do
|
||||
assert nil[:foo] == nil
|
||||
end
|
||||
|
||||
# Test nil at compilation time does not fail
|
||||
# and that @config[:foo] has proper precedence.
|
||||
nil = @config[:foo]
|
||||
|
||||
@config [foo: :bar]
|
||||
:bar = @config[:foo]
|
||||
|
||||
@mod :lists
|
||||
[1,2,3] = @mod.flatten([1,[2],3])
|
||||
|
||||
test :atom do
|
||||
exception = assert_raise RuntimeError, fn ->
|
||||
foo = :foo
|
||||
foo[:atom]
|
||||
end
|
||||
assert exception.message == "The access protocol can only be invoked for atoms at compilation time, tried to invoke it for :foo"
|
||||
assert exception.message == "The access protocol can only be invoked for atoms " <>
|
||||
"at compilation time, tried to invoke it for :foo"
|
||||
end
|
||||
end
|
||||
|
||||
defmodule Access.RecordTest do
|
||||
use ExUnit.Case, async: true
|
||||
|
||||
defrecord Config, integer: 0
|
||||
|
||||
test :keywords do
|
||||
assert Config[] == { Config, 0 }
|
||||
assert Config[integer: 1] == { Config, 1 }
|
||||
end
|
||||
|
||||
test :in_match_with_variable do
|
||||
assert get_var(Config.new) == 0
|
||||
assert get_var(Config.new(integer: 1)) == 1
|
||||
end
|
||||
|
||||
test :in_match_with_record_match do
|
||||
assert is_config(Config.new) == true
|
||||
assert is_config({ Access.AtomTest, 1 }) == false
|
||||
assert is_config({ Config, 1, 2 }) == false
|
||||
end
|
||||
|
||||
test :in_match_with_field_match do
|
||||
assert is_zero(Config.new) == true
|
||||
assert is_zero(Config.new(integer: 1)) == false
|
||||
end
|
||||
|
||||
test :match do
|
||||
assert Config[integer: 1] = Config.new(integer: 1)
|
||||
refute Config[integer: 1] = Config.new(integer: 0)
|
||||
end
|
||||
|
||||
defp get_var(Config[integer: integer]) do
|
||||
integer
|
||||
end
|
||||
|
||||
defp is_zero(Config[integer: 0]), do: true
|
||||
defp is_zero(Config[integer: _]), do: false
|
||||
|
||||
defp is_config(Config[]), do: true
|
||||
defp is_config(_), do: false
|
||||
end
|
||||
|
||||
@@ -16,8 +16,8 @@ defmodule BehaviourTest do
|
||||
test :docs do
|
||||
docs = Enum.sort(Sample.__behaviour__(:docs))
|
||||
assert docs == [
|
||||
{{:bar,2},13,"Bar"},
|
||||
{{:foo,2},10,"Foo"}
|
||||
{{:bar, 2}, 13, "Bar"},
|
||||
{{:foo, 2}, 10, "Foo"}
|
||||
]
|
||||
end
|
||||
|
||||
|
||||
@@ -52,7 +52,7 @@ defmodule Binary.Chars.NumberTest do
|
||||
|
||||
test :float do
|
||||
assert to_binary(1.0) == "1.0"
|
||||
assert to_binary(1.0e10) == "10000000000.0"
|
||||
assert to_binary(1.0e10) == "1.0e10"
|
||||
end
|
||||
end
|
||||
|
||||
@@ -60,7 +60,7 @@ defmodule Binary.Chars.ListTest do
|
||||
use ExUnit.Case, async: true
|
||||
|
||||
test :basic do
|
||||
assert to_binary([ 1, "b", 3 ]) == <<1,98,3>>
|
||||
assert to_binary([ 1, "b", 3 ]) == <<1, 98, 3>>
|
||||
end
|
||||
|
||||
test :printable do
|
||||
@@ -76,46 +76,46 @@ defmodule Binary.Chars.ErrorsTest do
|
||||
use ExUnit.Case, async: true
|
||||
|
||||
test :list do
|
||||
assert to_binary([0,1, 2, 3,255]) == <<0,1,2,3,255>>
|
||||
assert to_binary([0,[1, "hello"], 2, [["bye"]]]) == <<0,1,104,101,108,108,111,2,98,121,101>>
|
||||
assert to_binary([0, 1, 2, 3, 255]) == <<0, 1, 2, 3, 255>>
|
||||
assert to_binary([0, [1, "hello"], 2, [["bye"]]]) == <<0, 1, 104, 101, 108, 108, 111, 2, 98, 121, 101>>
|
||||
|
||||
assert_raise Protocol.UndefinedError, "protocol Binary.Chars.List not implemented for [256]. Only iolists are supported", fn ->
|
||||
to_binary([256])
|
||||
end
|
||||
assert_raise Protocol.UndefinedError, "protocol Binary.Chars.List not implemented for [1001,10001,100001]. Only iolists are supported", fn ->
|
||||
assert_raise Protocol.UndefinedError, "protocol Binary.Chars.List not implemented for [1001, 10001, 100001]. Only iolists are supported", fn ->
|
||||
to_binary([1001, 10001, 100001])
|
||||
end
|
||||
assert_raise Protocol.UndefinedError, "protocol Binary.Chars.List not implemented for [:atom,13,\"hello\"]. Only iolists are supported", fn ->
|
||||
assert_raise Protocol.UndefinedError, "protocol Binary.Chars.List not implemented for [:atom, 13, \"hello\"]. Only iolists are supported", fn ->
|
||||
to_binary([:atom, 13, "hello"])
|
||||
end
|
||||
end
|
||||
|
||||
test :tuple do
|
||||
assert_raise Protocol.UndefinedError, "protocol Binary.Chars not implemented for {1,2,3}", fn ->
|
||||
to_binary({1,2,3})
|
||||
assert_raise Protocol.UndefinedError, "protocol Binary.Chars not implemented for {1, 2, 3}", fn ->
|
||||
to_binary({1, 2, 3})
|
||||
end
|
||||
end
|
||||
|
||||
test :nested_in_tuple do
|
||||
assert_raise Protocol.UndefinedError, "protocol Binary.Chars not implemented for {1,[2],:atom}", fn ->
|
||||
assert_raise Protocol.UndefinedError, "protocol Binary.Chars not implemented for {1, [2], :atom}", fn ->
|
||||
to_binary({1, [2], :atom})
|
||||
end
|
||||
end
|
||||
|
||||
test :nested_tuple do
|
||||
assert_raise Protocol.UndefinedError, "protocol Binary.Chars.List not implemented for [1,{[2],:atom}]. Only iolists are supported", fn ->
|
||||
assert_raise Protocol.UndefinedError, "protocol Binary.Chars.List not implemented for [1, {[2], :atom}]. Only iolists are supported", fn ->
|
||||
to_binary([1, {[2], :atom}])
|
||||
end
|
||||
end
|
||||
|
||||
test :nested_pid do
|
||||
assert_raise Protocol.UndefinedError, %r"^protocol Binary\.Chars\.List not implemented for \[#PID<.+?>,:atom\]\. Only iolists are supported$", fn ->
|
||||
assert_raise Protocol.UndefinedError, %r"^protocol Binary\.Chars\.List not implemented for \[#PID<.+?>, :atom\]\. Only iolists are supported$", fn ->
|
||||
to_binary([self(), :atom])
|
||||
end
|
||||
end
|
||||
|
||||
test :nested_fun do
|
||||
assert_raise Protocol.UndefinedError, %r"^protocol Binary\.Chars\.List not implemented for \[:atom,#Function<.+?>\]\. Only iolists are supported$", fn ->
|
||||
assert_raise Protocol.UndefinedError, %r"^protocol Binary\.Chars\.List not implemented for \[:atom, #Function<.+?>\]\. Only iolists are supported$", fn ->
|
||||
to_binary([:atom, fn -> end])
|
||||
end
|
||||
end
|
||||
|
||||
@@ -53,7 +53,7 @@ bar
|
||||
assert "\123" == "S"
|
||||
assert "\377" == "ÿ"
|
||||
assert "\128" == "\n8"
|
||||
assert "\18" == <<1,?8>>
|
||||
assert "\18" == <<1, ?8>>
|
||||
end
|
||||
|
||||
test :hex do
|
||||
@@ -62,10 +62,10 @@ bar
|
||||
assert "\xFF" == "ÿ"
|
||||
assert "\x{A}"== "\n"
|
||||
assert "\x{E9}"== "é"
|
||||
assert "\x{10F}" == <<196,143>>
|
||||
assert "\x{10FF}" == <<225,131,191>>
|
||||
assert "\x{10FFF}" == <<240,144,191,191>>
|
||||
assert "\x{10FFFF}" == <<244,143,191,191>>
|
||||
assert "\x{10F}" == <<196, 143>>
|
||||
assert "\x{10FF}" == <<225, 131, 191>>
|
||||
assert "\x{10FFF}" == <<240, 144, 191, 191>>
|
||||
assert "\x{10FFFF}" == <<244, 143, 191, 191>>
|
||||
end
|
||||
|
||||
test :match do
|
||||
@@ -85,17 +85,17 @@ bar
|
||||
end
|
||||
|
||||
test :bitsyntax_with_utf do
|
||||
assert <<106,111,115,101>> == << "jose" :: utf8 >>
|
||||
assert <<106,111,115,101>> == << 'jose' :: utf8 >>
|
||||
assert <<106, 111, 115, 101>> == << "jose" :: utf8 >>
|
||||
assert <<106, 111, 115, 101>> == << 'jose' :: utf8 >>
|
||||
|
||||
assert <<0,106,0,111,0,115,0,101>> == << "jose" :: utf16 >>
|
||||
assert <<0,106,0,111,0,115,0,101>> == << 'jose' :: utf16 >>
|
||||
assert <<0, 106, 0, 111, 0, 115, 0, 101>> == << "jose" :: utf16 >>
|
||||
assert <<0, 106, 0, 111, 0, 115, 0, 101>> == << 'jose' :: utf16 >>
|
||||
|
||||
assert <<106,0,111,0,115,0,101,0>> == << "jose" :: [utf16, little] >>
|
||||
assert <<106,0,111,0,115,0,101,0>> == << 'jose' :: [utf16, little] >>
|
||||
assert <<106, 0, 111, 0, 115, 0, 101, 0>> == << "jose" :: [utf16, little] >>
|
||||
assert <<106, 0, 111, 0, 115, 0, 101, 0>> == << 'jose' :: [utf16, little] >>
|
||||
|
||||
assert <<0,0,0,106,0,0,0,111,0,0,0,115,0,0,0,101>> == << "jose" :: utf32 >>
|
||||
assert <<0,0,0,106,0,0,0,111,0,0,0,115,0,0,0,101>> == << 'jose' :: utf32 >>
|
||||
assert <<0, 0, 0, 106, 0, 0, 0, 111, 0, 0, 0, 115, 0, 0, 0, 101>> == << "jose" :: utf32 >>
|
||||
assert <<0, 0, 0, 106, 0, 0, 0, 111, 0, 0, 0, 115, 0, 0, 0, 101>> == << 'jose' :: utf32 >>
|
||||
end
|
||||
|
||||
@binary "new "
|
||||
@@ -148,4 +148,4 @@ bar
|
||||
defp is_match?(_, _) do
|
||||
false
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -38,11 +38,11 @@ defmodule Bitwise.OperatorsTest do
|
||||
end
|
||||
|
||||
test :band do
|
||||
assert 1 &&& 1 == 1
|
||||
assert (1 &&& 1) == 1
|
||||
end
|
||||
|
||||
test :bor do
|
||||
assert 0 ||| 1 == 1
|
||||
assert (0 ||| 1) == 1
|
||||
end
|
||||
|
||||
test :bxor do
|
||||
@@ -50,10 +50,10 @@ defmodule Bitwise.OperatorsTest do
|
||||
end
|
||||
|
||||
test :bsl do
|
||||
assert 1 <<< 1 == 2
|
||||
assert (1 <<< 1) == 2
|
||||
end
|
||||
|
||||
test :bsr do
|
||||
assert 1 >>> 1 == 0
|
||||
assert (1 >>> 1) == 0
|
||||
end
|
||||
end
|
||||
|
||||
@@ -28,6 +28,14 @@ defmodule CodeTest do
|
||||
assert Code.eval_string("one", [], delegate_locals_to: __MODULE__) == { 1, [] }
|
||||
end
|
||||
|
||||
test :eval_options do
|
||||
assert Code.eval_string("is_atom(:foo) and is_record(1..2, Range) and K.is_list([])", [],
|
||||
functions: [{ Kernel, [is_atom: 1] }],
|
||||
macros: [{ Kernel, [..: 2, and: 2, is_record: 2]}],
|
||||
aliases: [{K, Kernel}],
|
||||
requires: [Kernel]) == { true, [] }
|
||||
end
|
||||
|
||||
test :eval_with_requires do
|
||||
assert Code.eval_string("Kernel.if true, do: :ok", [], requires: [Z, Kernel]) == { :ok, [] }
|
||||
end
|
||||
@@ -39,7 +47,7 @@ defmodule CodeTest do
|
||||
|
||||
test :eval_quoted_with_env do
|
||||
alias :lists, as: MyList
|
||||
assert Code.eval_quoted(quote(do: MyList.flatten [[1,2,3]]), [], __ENV__) == { [1,2,3],[] }
|
||||
assert Code.eval_quoted(quote(do: MyList.flatten [[1, 2, 3]]), [], __ENV__) == { [1, 2, 3],[] }
|
||||
end
|
||||
|
||||
test :require do
|
||||
@@ -65,25 +73,25 @@ defmodule CodeTest do
|
||||
assert :filename.absname(__FILE__) == __FILE__
|
||||
end
|
||||
|
||||
test :string_to_ast do
|
||||
assert Code.string_to_ast("1 + 2") == { :ok, { :+, [line: 1], [1, 2] } }
|
||||
assert { :error, _ } = Code.string_to_ast("a.1")
|
||||
test :string_to_quoted do
|
||||
assert Code.string_to_quoted("1 + 2") == { :ok, { :+, [line: 1], [1, 2] } }
|
||||
assert { :error, _ } = Code.string_to_quoted("a.1")
|
||||
end
|
||||
|
||||
test :string_to_ast_existing_atoms_only do
|
||||
assert :badarg = catch_error(Code.string_to_ast(":thereisnosuchatom", existing_atoms_only: true))
|
||||
assert :badarg = catch_error(Code.string_to_ast!(":thereisnosuchatom", existing_atoms_only: true))
|
||||
test :string_to_quoted_existing_atoms_only do
|
||||
assert :badarg = catch_error(Code.string_to_quoted(":thereisnosuchatom", existing_atoms_only: true))
|
||||
assert :badarg = catch_error(Code.string_to_quoted!(":thereisnosuchatom", existing_atoms_only: true))
|
||||
end
|
||||
|
||||
test :string_to_ast! do
|
||||
assert Code.string_to_ast!("1 + 2") == { :+, [line: 1], [1, 2] }
|
||||
test :string_to_quoted! do
|
||||
assert Code.string_to_quoted!("1 + 2") == { :+, [line: 1], [1, 2] }
|
||||
|
||||
assert_raise SyntaxError, fn ->
|
||||
Code.string_to_ast!("a.1")
|
||||
Code.string_to_quoted!("a.1")
|
||||
end
|
||||
|
||||
assert_raise TokenMissingError, fn ->
|
||||
Code.string_to_ast!("1 +")
|
||||
Code.string_to_quoted!("1 +")
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user