Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
44b0412042 | ||
|
|
d984a82587 | ||
|
|
e59d50a891 | ||
|
|
7bf568d0b3 | ||
|
|
f95cafe5ca | ||
|
|
f0e1443f21 | ||
|
|
cf619585a6 | ||
|
|
c639b01146 | ||
|
|
b4415f2a14 | ||
|
|
538b51c20a | ||
|
|
c8a0158791 | ||
|
|
b088d4b90e | ||
|
|
02a415facd | ||
|
|
5cee19067d | ||
|
|
3496681236 | ||
|
|
a7645a2c8b | ||
|
|
d9423627f3 | ||
|
|
5a8520fb0f | ||
|
|
4458ee1232 | ||
|
|
a6b1cca91f | ||
|
|
e2535731c8 | ||
|
|
393ff69b1e | ||
|
|
033747ad63 | ||
|
|
730d043631 | ||
|
|
c819bdbf74 | ||
|
|
64f8541ee6 | ||
|
|
d1f4e2d49b | ||
|
|
5a13aecbae | ||
|
|
61904ee24c | ||
|
|
5c7fdcc244 | ||
|
|
f772877039 | ||
|
|
e571218917 | ||
|
|
aa01dd1fc1 | ||
|
|
30971c9cb3 | ||
|
|
abfc69a6e2 | ||
|
|
ac1fc484e4 | ||
|
|
889bfebe4e | ||
|
|
e9899a5462 | ||
|
|
ec92bb118d | ||
|
|
fd9ca435b7 | ||
|
|
3ec0d20621 | ||
|
|
16f3b928a1 | ||
|
|
fb20b6ddf6 | ||
|
|
d77a4d6c3f | ||
|
|
d7900239c2 | ||
|
|
babd98be3f | ||
|
|
9a85b1556b | ||
|
|
d1da200af5 | ||
|
|
e260b92f89 | ||
|
|
e09e5c3f41 | ||
|
|
a76a281309 | ||
|
|
be42d00067 | ||
|
|
f21a998f81 | ||
|
|
89e0018d58 | ||
|
|
21b691cd4e | ||
|
|
a8a91c2282 | ||
|
|
5275646966 | ||
|
|
e60cd6a484 | ||
|
|
552006bea3 | ||
|
|
2077b584ad | ||
|
|
29f05fda35 | ||
|
|
cbb7c545e0 | ||
|
|
c909267077 | ||
|
|
a9bbabfabc | ||
|
|
c4d666a4ba | ||
|
|
8e824d3da1 | ||
|
|
41caa0d635 | ||
|
|
3a0108edf8 | ||
|
|
7123fb9d09 | ||
|
|
6d8e118192 | ||
|
|
846703b31c | ||
|
|
46905b39a7 | ||
|
|
0f43aca5bc | ||
|
|
587c83cbff | ||
|
|
e8194b1764 | ||
|
|
feb9cb645e | ||
|
|
29f9a4fd67 | ||
|
|
22f3a50eba | ||
|
|
470e5424bb | ||
|
|
8f272c4cc8 | ||
|
|
46c23b1547 | ||
|
|
4d607ace08 | ||
|
|
78adb5b200 | ||
|
|
311efb66a9 | ||
|
|
272be2c088 | ||
|
|
911acc3173 | ||
|
|
182cbfe541 | ||
|
|
c43bc6a06d | ||
|
|
38aa5c57e5 | ||
|
|
4f93dae442 | ||
|
|
41c74e9682 | ||
|
|
e0f5dd506a | ||
|
|
f90eeaa024 | ||
|
|
9451e69996 | ||
|
|
924350868a | ||
|
|
ee91be519a | ||
|
|
90951be944 | ||
|
|
65cf31d32d | ||
|
|
e14c9ea6ce | ||
|
|
8635fcc869 | ||
|
|
65809a5644 | ||
|
|
d53a315069 | ||
|
|
8d2199c3f3 | ||
|
|
54e69c023a | ||
|
|
55503239a4 | ||
|
|
19d8446ac0 | ||
|
|
5a94906f1d | ||
|
|
915ba1bb29 | ||
|
|
d35d03a8ee | ||
|
|
b959049c77 | ||
|
|
24c4bc3009 | ||
|
|
000a174353 | ||
|
|
b126e3b089 | ||
|
|
319957f5df | ||
|
|
cb2eb7d376 | ||
|
|
1857267872 | ||
|
|
91faec5f65 | ||
|
|
0f0a91faef | ||
|
|
baec57637e | ||
|
|
0c8c6f03e0 | ||
|
|
7bdbefb277 | ||
|
|
ec0e7d8056 | ||
|
|
c2e912bdfd | ||
|
|
14e1d410f1 | ||
|
|
15925573e8 | ||
|
|
f32ede4e3f | ||
|
|
1dc9193e69 | ||
|
|
dacb30ad05 | ||
|
|
362df17316 | ||
|
|
d54d0f6a6b | ||
|
|
72d2f38cb5 | ||
|
|
b3cc527126 | ||
|
|
6b45855f0e | ||
|
|
40835556da | ||
|
|
dc9a47d259 | ||
|
|
2d1cf2ebc1 | ||
|
|
9a56e5ef5a | ||
|
|
ab148551ba | ||
|
|
4d7c75f2ca | ||
|
|
e7f3db9fd4 | ||
|
|
4682511923 | ||
|
|
e942c07f34 | ||
|
|
dc403e4311 | ||
|
|
b3c6bc2bc5 | ||
|
|
c377483715 | ||
|
|
3ded858534 | ||
|
|
aa09b1e2d2 | ||
|
|
8cb4c719f9 | ||
|
|
ffa2abd8c6 | ||
|
|
14722ba102 | ||
|
|
7f4437ece5 | ||
|
|
afc7644fe0 | ||
|
|
7de880495b | ||
|
|
29addf9e93 | ||
|
|
6e9f9f254f | ||
|
|
db25e24eb8 | ||
|
|
8a184a6b3d | ||
|
|
fcd6213c4c | ||
|
|
5409b7e707 | ||
|
|
815772811f | ||
|
|
9cff61493e | ||
|
|
65a3434bd4 | ||
|
|
4ff9f9aa29 | ||
|
|
3d982fd1a9 | ||
|
|
396b1e68a8 | ||
|
|
9c549b3465 | ||
|
|
04194a189d | ||
|
|
35e04d0ea5 | ||
|
|
1038018e84 | ||
|
|
2b3e53670e | ||
|
|
dff93a44c2 | ||
|
|
f76c8fec7b | ||
|
|
8b19b0b3f4 | ||
|
|
77fad29a1b | ||
|
|
5bab9cae49 | ||
|
|
05507d1f3a | ||
|
|
e369900b06 | ||
|
|
a629cfce86 | ||
|
|
1a93d35d9c | ||
|
|
06cc1c183f | ||
|
|
cb5806bbf3 | ||
|
|
db70fd5a5f | ||
|
|
513ed0bbae | ||
|
|
3bbae0a368 | ||
|
|
2134db3a0d | ||
|
|
80f35eab7d | ||
|
|
ab8a2d195f | ||
|
|
a7096b5061 | ||
|
|
8552886a8a | ||
|
|
789a657490 | ||
|
|
2ee44eba29 | ||
|
|
e4a9fae0fd | ||
|
|
0030b282c1 | ||
|
|
9c90955863 | ||
|
|
61e626bdb8 | ||
|
|
3c40de4c0c | ||
|
|
86a8a6ee88 | ||
|
|
4d5696e074 | ||
|
|
892f211550 | ||
|
|
2caa16ac22 | ||
|
|
32c5694551 | ||
|
|
1aeb7ae7d0 | ||
|
|
fe8f3464b6 | ||
|
|
8941d2821d | ||
|
|
7ba3c3cfb7 | ||
|
|
5cc2724b40 | ||
|
|
40dff7ebe8 | ||
|
|
5158a19fbd | ||
|
|
fd936bb326 | ||
|
|
3e54d33481 | ||
|
|
0fe1e686b3 | ||
|
|
9e80f3b82b | ||
|
|
01bda975c3 | ||
|
|
05a8ce56a3 | ||
|
|
d498a89267 | ||
|
|
46db0d4304 | ||
|
|
f2ec77886a | ||
|
|
4e8ae3abdb | ||
|
|
dc7ffae763 | ||
|
|
b7897b224d | ||
|
|
c80a65b657 | ||
|
|
f02cd8437b | ||
|
|
7d87e84803 | ||
|
|
b4a0b4cc19 | ||
|
|
1940f490b2 | ||
|
|
69098968be | ||
|
|
85a04fe919 | ||
|
|
54db13ae56 | ||
|
|
f60d3668ed | ||
|
|
3471898364 | ||
|
|
bf7d172f11 | ||
|
|
28457563ce | ||
|
|
280a5ceedc | ||
|
|
a696cf41dc | ||
|
|
f3f0ec09b7 | ||
|
|
e040688bd9 | ||
|
|
9d84e1c007 | ||
|
|
2e6a83dff9 | ||
|
|
d866a26ac1 | ||
|
|
3453c30f4c | ||
|
|
50a7b0e1bc | ||
|
|
7ce790035d | ||
|
|
606cd2ff69 | ||
|
|
9c9d2e1ea0 | ||
|
|
6e7baa42ac | ||
|
|
633f92edbe | ||
|
|
b23f2fc360 | ||
|
|
5b31ec421d | ||
|
|
2d38f20b43 | ||
|
|
9f6f92b0f2 | ||
|
|
4a183b462c | ||
|
|
4d53d8c710 | ||
|
|
a09a0c2a1a | ||
|
|
6a42ab0797 | ||
|
|
183ed3ffdd | ||
|
|
8a8779fcbc | ||
|
|
bcb8cd763e | ||
|
|
97c0809845 | ||
|
|
e33a9557aa | ||
|
|
43706a4623 | ||
|
|
b816c98877 | ||
|
|
23f0ff81c4 | ||
|
|
6aada7fe3c | ||
|
|
e24cef0126 | ||
|
|
b161070345 | ||
|
|
0b4f9ad486 | ||
|
|
d484ca8ae2 | ||
|
|
d77c1a34f4 | ||
|
|
f6b0f29f5d | ||
|
|
436f563ad3 | ||
|
|
81b80d5b9c | ||
|
|
425cea5a84 | ||
|
|
b4c0826467 | ||
|
|
d94491841a | ||
|
|
bb1093ef2d | ||
|
|
863e22a7e2 | ||
|
|
aa682a4704 | ||
|
|
01d5f99437 | ||
|
|
34c2da2c65 | ||
|
|
a3d5be91f2 | ||
|
|
bbb77e0102 | ||
|
|
19164d2e55 | ||
|
|
a950d4fda1 | ||
|
|
33ac879521 | ||
|
|
6ef4c80724 | ||
|
|
3843ee6ce5 | ||
|
|
3ab1db6740 | ||
|
|
1f2dc0f716 | ||
|
|
83fa2fbbbf | ||
|
|
cc969f8d01 | ||
|
|
3de70b909a | ||
|
|
7782227182 | ||
|
|
58cd9e5c11 | ||
|
|
862ab01f94 | ||
|
|
2c630ff33c | ||
|
|
007571e979 | ||
|
|
53552d434d | ||
|
|
eb32937858 | ||
|
|
6720cb6830 | ||
|
|
16c0262122 | ||
|
|
2606f47138 | ||
|
|
9337d50775 | ||
|
|
069415595a | ||
|
|
4e88d82871 | ||
|
|
5e8d948e39 | ||
|
|
893700f65e | ||
|
|
c3f0929a77 | ||
|
|
3c48099718 | ||
|
|
419e2a1d2a | ||
|
|
39c84f4324 | ||
|
|
8448513c8b | ||
|
|
bc65fc0276 | ||
|
|
b1f0d3ed99 | ||
|
|
211d9428b4 | ||
|
|
8a3f2cfc2f | ||
|
|
fff7a85769 | ||
|
|
8652ee5838 | ||
|
|
beb1bbeb3d | ||
|
|
377c64d1b0 | ||
|
|
cc9783a437 | ||
|
|
e566d7cda0 | ||
|
|
5c43548b0f | ||
|
|
82518c3376 | ||
|
|
8ab8663370 | ||
|
|
d44d71cce1 | ||
|
|
4d66ac72e1 | ||
|
|
fb49aa1e99 | ||
|
|
a6b9d2ed72 | ||
|
|
df1366c1d6 | ||
|
|
0bcc02d816 | ||
|
|
1c9dff1fee | ||
|
|
ed4ee3fe5c | ||
|
|
cc8f6a4ead | ||
|
|
82f800e1c5 | ||
|
|
18aeb78547 | ||
|
|
83ccde4db6 | ||
|
|
59900260a1 | ||
|
|
6bfcf2f33c | ||
|
|
45bd25057f | ||
|
|
3f6e6101d8 | ||
|
|
eb17182ed4 | ||
|
|
5b08851a8d | ||
|
|
bc0adc2f4f | ||
|
|
67f2be2eb9 | ||
|
|
fe5d8e6483 | ||
|
|
4cc28c2e8c | ||
|
|
78453597c2 | ||
|
|
927594bb14 | ||
|
|
fab45f58c6 | ||
|
|
a9375903ae | ||
|
|
44d4b5a7bf | ||
|
|
ed582c3977 | ||
|
|
9d4231c92c | ||
|
|
ecdb3e9aa9 | ||
|
|
fb2686fe37 | ||
|
|
55e885f5c5 | ||
|
|
f92d8393da | ||
|
|
cb0c3d8a2a | ||
|
|
d9ece7edb0 | ||
|
|
24a256686a | ||
|
|
ce9b381f67 | ||
|
|
a0d42b47b6 | ||
|
|
e3e5b9b85b | ||
|
|
5f4180a898 | ||
|
|
184cb36e00 | ||
|
|
48a0211156 | ||
|
|
9dc7172ec5 | ||
|
|
00db100bd4 | ||
|
|
1804db48c8 | ||
|
|
b92368104e | ||
|
|
6f0c0269d2 | ||
|
|
9b431a8831 | ||
|
|
d53509efd2 | ||
|
|
ea0226b3da | ||
|
|
4346a41811 | ||
|
|
b342739dba | ||
|
|
b570850faf | ||
|
|
5882cf5fb6 | ||
|
|
4dde1b076b | ||
|
|
142ebc3ecd | ||
|
|
339c57f1c6 | ||
|
|
76260b057f | ||
|
|
40a384aef6 | ||
|
|
e7fcaff839 | ||
|
|
a4b01b2f97 | ||
|
|
ee561bd473 | ||
|
|
6696133abb | ||
|
|
42b0c24f6a | ||
|
|
b8d02e94b2 | ||
|
|
d9a8bbb65e | ||
|
|
c0bc64fb19 | ||
|
|
0445647d63 | ||
|
|
da03542309 | ||
|
|
2e24e228d1 | ||
|
|
f83eb30975 | ||
|
|
80d81ed47a | ||
|
|
dc9803de6d | ||
|
|
4e5c851c44 | ||
|
|
b76d39e9d4 | ||
|
|
be34a75124 | ||
|
|
78c16c0e89 | ||
|
|
860ca5b5cb | ||
|
|
ce8733fc6f | ||
|
|
24109337fe | ||
|
|
90c2959ed7 | ||
|
|
70e3be3ccf | ||
|
|
eeb796107e | ||
|
|
166bbb17f9 | ||
|
|
4ed84f86cd | ||
|
|
540c763c85 | ||
|
|
3d4877ffda | ||
|
|
84262257ef | ||
|
|
68dd952b45 | ||
|
|
a068b5a4b0 | ||
|
|
c6da7b56ba | ||
|
|
dbe918c911 | ||
|
|
4977ec8c05 | ||
|
|
bf8f5c4bb8 | ||
|
|
3bb8e17e9e | ||
|
|
6b755112ce | ||
|
|
9650344c76 | ||
|
|
ca6788113e | ||
|
|
095e4a7d2e | ||
|
|
23475f9590 | ||
|
|
e69a35a85b | ||
|
|
a6392a8220 | ||
|
|
21399ebfa6 | ||
|
|
deb0191a21 | ||
|
|
489b1e04c7 | ||
|
|
a80f9d9ae1 | ||
|
|
22eb707aab | ||
|
|
c22e8e85dc | ||
|
|
98b1acfe28 | ||
|
|
cd8f52aa92 | ||
|
|
efc6963e79 | ||
|
|
dcf3692722 | ||
|
|
30294998e5 | ||
|
|
48863afca4 | ||
|
|
8622ed53e2 | ||
|
|
5e34d0a7f1 | ||
|
|
5d775f2c24 | ||
|
|
9acf4b2df9 | ||
|
|
45ee18578b | ||
|
|
fafdf40687 | ||
|
|
22206f87bf | ||
|
|
2e7d938707 | ||
|
|
57ccb29bc4 | ||
|
|
79bb046ad6 | ||
|
|
24c17c350c | ||
|
|
2187db5415 | ||
|
|
84cdbe3562 | ||
|
|
95ed007b1c | ||
|
|
55bd1d4e5a | ||
|
|
e6e1af7d84 | ||
|
|
1019917f1f | ||
|
|
9f0a30de43 | ||
|
|
ef67a9527a | ||
|
|
ff04acd3a6 | ||
|
|
a79f7c291f | ||
|
|
10ea9bdd40 | ||
|
|
ba62dc9522 | ||
|
|
d9bb48f691 | ||
|
|
580107d29c | ||
|
|
bb8f3aaa8d | ||
|
|
f4b436cbc3 |
@@ -4,7 +4,6 @@
|
||||
/ebin
|
||||
/lib/*/ebin/*
|
||||
/lib/*/tmp
|
||||
/lib/*/test/tmp
|
||||
/lib/elixir/src/elixir.app.src
|
||||
/lib/elixir/src/*_lexer.erl
|
||||
/lib/elixir/src/*_parser.erl
|
||||
|
||||
+186
-31
@@ -1,4 +1,160 @@
|
||||
# v0.13.2 (2014-05-11)
|
||||
# Changelog
|
||||
|
||||
## v0.14.2
|
||||
|
||||
* Enhancements
|
||||
* [Enum] Improve performance of `Enum.join/2` and `Enum.map_join/3` by using iolists
|
||||
* [Kernel] Ensure compatibility with Erlang 17.1
|
||||
* [Kernel] Support `@external_resource` attribute to external dependencies to a module
|
||||
* [Mix] Allow built Git dependencies to run on a system without Git by passing `--no-deps-check`
|
||||
* [Mix] Add `MIX_ARCHIVES` env variable (it is recommended for Elixir build tools to swap this environment)
|
||||
* [Task] Set `:proc_lib` initial call on task to aid debugging
|
||||
* [Typespec] Delay typespec compilation to after expansion
|
||||
* [URI] Allow `parse/1` now accepts `%URI{}` as argument and return the uri itself
|
||||
|
||||
* Bug fixes
|
||||
* [CLI] Support paths inside archives in `-pa` and `-pz` options
|
||||
* [IEx] Remove dely when printing data from the an application start callback
|
||||
* [IEx] Ensure we show a consistent error when we cannot evaluate `.iex.exs`
|
||||
* [Kernel] Ensure derived protocols are defined with a file
|
||||
* [Kernel] Change precedence of `&` to not special case `/`
|
||||
* [Kernel] Ensure we can only use variables and `\\` as arguments of bodyless clause
|
||||
|
||||
* Soft deprecations (no warnings emitted)
|
||||
* [EEx] Using `EEx.TransformerEngine` and `EEx.AssignsEngine` are deprecated in favor of function composition with `Macro.prewalk/1` (see `EEx.SmartEngine` for an example)
|
||||
* [Mix] `Mix.Generator.from_file/1` is deprecated in favor of passing `from_file: file` option to `embed_text/2` and `embed_template/2` (note though that `from_file/1` expects a path relative to the current file while the `from_file: file` expects one relative to the current working directory)
|
||||
|
||||
* Deprecations
|
||||
* [Kernel] `size/1` is deprecated in favor of `byte_size/1` and `tuple_size/1` (this change was soft deprecated two releases ago)
|
||||
|
||||
* Backwards incompatible changes
|
||||
* [CLI] Remove support for the `--gen-debug` option as its usage is not documented by OTP
|
||||
* [Kernel] Sigils no longer balance start and end tokens, e.g. the sigil `~s(f(o)o)` is no longer valid as it finishes in the first closing `)`
|
||||
* [Kernel] Variables set in `cond` clause heads are no longer available outside of that particular `cond` clause (this is the behaviour also found in `case`, `receive` and friends)
|
||||
* [System] `build_info/0` now returns a map
|
||||
|
||||
## v0.14.1 (2014-06-18)
|
||||
|
||||
* Enhancements
|
||||
* [Base] Decoding and encoding functions now accept the `:case` as an option
|
||||
* [ExUnit] The test process now exits with `:shutdown` reason
|
||||
* [GenEvent] `GenEvent.stream/2` now accepts `:sync` and `:async` modes
|
||||
* [Node] Add `Node.start/3` and `Node.stop/0`
|
||||
* [String] Updated Unicode database to 7.0
|
||||
* [Task] Log when tasks crash
|
||||
|
||||
* Bug fixes
|
||||
* [Enum] `Enum.slice/2` and `Enum.slice/3` always returns a list (and never nil)
|
||||
* [Kernel] Disambiguate (w)erl to (w)erl.exe
|
||||
* [Mix] Ensure umbrella project is recompiled when a dependency inside an umbrella child changes
|
||||
* [OptionParser] Do not allow underscores in option names
|
||||
* [Path] Fix path expansion of `"/.."`
|
||||
* [Path] Do not match files starting with `.` in `Path.wildcard/2` by default
|
||||
* [Process] `Process.info(pid, :registered_name)` returns `{:registered_name, nil}` if there is no registered name
|
||||
* [String] `String.slice/2` and `String.slice/3` always returns a list (and never nil)
|
||||
* [URI] `encode/1` does not escape reserved/unreserved characters by default nor encodes whitespace as `+` (check `URI.encode_www_form/1` and `URI.decode_www_form/1` for previous behaviour)
|
||||
|
||||
* Deprecations
|
||||
* [Mix] `:escript_*` options were moved into a single `:escript` group
|
||||
|
||||
* Backwards incompatible changes
|
||||
* [GenEvent] `GenEvent.stream/2` defaults to `:sync` mode
|
||||
* [Kernel] Remove `get_in/1`
|
||||
|
||||
## v0.14.0 (2014-06-08)
|
||||
|
||||
* Enhancements
|
||||
* [ExUnit] Add `on_exit/1` callbacks that are guaranteed to run once the test process exits and always in another process
|
||||
* [Kernel] Store documentation in the abstract code to avoid loading them when the module is loaded
|
||||
* [Kernel] Add `get_in/2`, `put_in/3`, `update_in/3` and `get_and_update_in/3` to handle nested data structure operations
|
||||
* [Kernel] Add `get_in/1`, `put_in/2`, `update_in/2` and `get_and_update_in/2` to handle nested data structure operations via paths
|
||||
* [Mix] Add `Mix.Config` to ease definition of configuration files
|
||||
* [Mix] Add `mix loadconfig` task that can be called multiple times to load external configs
|
||||
* [Mix] Support `--config` option on `mix run`
|
||||
* [Mix] Support `HTTP_PROXY` and `HTTPS_PROXY` on Mix url commands
|
||||
* [Mix] Support `--names` options in `mix help` which emit only names (useful for autocompletion)
|
||||
* [Protocol] Add `Protocol.consolidate/2`, `Protocol.consolidated?/1` and a `mix compile.protocols` task for protocol consolidation
|
||||
* [Protocol] Add `Protocol.derive/3` for runtime deriving of a struct
|
||||
* [String] Add `String.chunk/2`
|
||||
* [Struct] Add support for `@derive` before `defstruct/2` definitions
|
||||
|
||||
* Bug fixes
|
||||
* [File] `File.rm` now consistently deletes read-only across operating systems
|
||||
* [Kernel] Ensure Mix `_build` structure works on Windows when copying projects
|
||||
* [Kernel] Ensure `1.0E10` (with uppercase E) is also valid syntax
|
||||
* [Mix] Fix `mix do` task for Windows' powershell users
|
||||
* [Path] Fix `Path.absname("/")` and `Path.expand("/")` to return the absolute path `"/"`.
|
||||
|
||||
* Soft deprecations (no warnings emitted)
|
||||
* [Kernel] `size/1` is deprecated, please use `byte_size/1` or `tuple_size/1` instead
|
||||
* [ExUnit] `teardown/2` and `teardown_all/2` are deprecated in favor of `on_exit/1` callbacks
|
||||
|
||||
* Deprecations
|
||||
* [Access] `Access.access/2` is deprecated in favor of `Access.get/2`
|
||||
* [Dict] `Dict.Behaviour` is deprecated in favor of `Dict`
|
||||
* [Kernel] `Application.Behaviour`, `GenEvent.Behaviour`, `GenServer.Behaviour` and `Supervisor.Behaviour` are deprecated in favor of `Application`, `GenEvent`, `GenServer` and `Supervisor`
|
||||
* [Kernel] `defexception/3` is deprecated in favor of `defexception/1`
|
||||
* [Kernel] `raise/3` is deprecated in favor of `reraise/2`
|
||||
* [Kernel] `set_elem/3` is deprecated in favor of `put_elem/3`
|
||||
* [Kernel] Passing an atom `var!/1` is deprecated, variables can be built dynamically with `Macro.var/2`
|
||||
* [Mix] Exceptions that define a `:mix_error` field to be compatible with Mix are no longer supported. Instead please provide a `:mix` field and use `Mix.raise/1` and `Mix.raise/2`
|
||||
|
||||
* Backwards incompatible changes
|
||||
* [Access] `Kernel.access/2` no longer exists and the `Access` protocol now requires `get/2` (instead of `access/2`) and `get_and_update/3` to be implemented
|
||||
* [Kernel] Retrieving docs as `module.__info__(:docs)` is no longer supported, please use `Code.get_docs/2` instead
|
||||
* [Kernel] `Code.compiler_options/1` no longer accepts custom options, only the ones specified by Elixir (use mix config instead)
|
||||
* [Mix] `mix new` no longer generates a supevision tree by default, please pass `--sup` instead
|
||||
* [Task] Tasks are automatically linked to callers and a failure in the task will crash the caller directly
|
||||
|
||||
## v0.13.3 (2014-05-24)
|
||||
|
||||
* Enhancements
|
||||
* [OptionParser] Add `:strict` option that only parses known switches
|
||||
* [OptionParser] Add `next/2` useful for manual parsing of options
|
||||
* [Macro] Add `Macro.prewalk/2/3` and `Macro.postwalk/2/3`
|
||||
* [Kernel] `GenEvent`, `GenServer`, `Supervisor`, `Agent` and `Task` modules added
|
||||
* [Kernel] Make deprecations compiler warnings to avoid the same deprecation being printed multiple times
|
||||
|
||||
* Bug fixes
|
||||
* [Enum] Fix `Enum.join/2` and `Enum.map_join/3` for empty binaries at the beginning of the collection
|
||||
* [ExUnit] Ensure the formatter doesn't error when printing :EXITs
|
||||
* [Kernel] Rename `ELIXIR_ERL_OPTS` to `ELIXIR_ERL_OPTIONS` for consistency with `ERL_COMPILER_OPTIONS`
|
||||
* [OptionParser] Parse `-` as a plain argument
|
||||
* [OptionParser] `--` is always removed from argument list on `parse/2` and when it is the leading entry on `parse_head/2`
|
||||
* [Regex] Properly escape regex (previously regex controls were double escaped)
|
||||
|
||||
* Soft deprecations (no warnings emitted)
|
||||
* [Dict] `Dict.Behaviour` is deprecated in favor of `Dict`
|
||||
* [Kernel] `Application.Behaviour`, `GenEvent.Behaviour`, `GenServer.Behaviour` and `Supervisor.Behaviour` are deprecated in favor of `Application`, `GenEvent`, `GenServer` and `Supervisor`
|
||||
* [Kernel] `defexception/3` is deprecated in favor of `defexception/1`
|
||||
* [Kernel] `raise/3` is deprecated in favor of `reraise/2`
|
||||
* [Kernel] `set_elem/3` is deprecated in favor of `put_elem/3`
|
||||
|
||||
* Soft deprecations for conversions (no warnings emitted)
|
||||
* [Kernel] `atom_to_binary/1` and `atom_to_list/1` are deprecated in favor of `Atom.to_string/1` and `Atom.to_char_list/1`
|
||||
* [Kernel] `bitstring_to_list/1` and `list_to_bitstring/1` are deprecated in favor of the `:erlang` ones
|
||||
* [Kernel] `binary_to_atom/1`, `binary_to_existing_atom/1`, `binary_to_float/1`, `binary_to_integer/1` and `binary_to_integer/2` are deprecated in favor of conversion functions in `String`
|
||||
* [Kernel] `float_to_binary/*` and `float_to_list/*` are deprecated in favor of `Float.to_string/*` and `Float.to_char_list/*`
|
||||
* [Kernel] `integer_to_binary/*` and `integer_to_list/*` are deprecated in favor of `Integer.to_string/*` and `Integer.to_char_list/*`
|
||||
* [Kernel] `iodata_to_binary/1` and `iodata_length/1` are deprecated `IO.iodata_to_binary/1` and `IO.iodata_length/1`
|
||||
* [Kernel] `list_to_atom/1`, `list_to_existing_atom/1`, `list_to_float/1`, `list_to_integer/1`, `list_to_integer/2` and `list_to_tuple/1` are deprecated in favor of conversion functions in `List`
|
||||
* [Kernel] `tuple_to_list/1` is deprecated in favor of `Tuple.to_list/1`
|
||||
* [List] `List.from_char_data/1` and `List.from_char_data!/1` deprecated in favor of `String.to_char_list/1`
|
||||
* [String] `String.from_char_data/1` and `String.from_char_data!/1` deprecated in favor of `List.to_string/1`
|
||||
|
||||
* Deprecations
|
||||
* [Kernel] `is_exception/1`, `is_record/1` and `is_record/2` are deprecated in favor of `Exception.exception?1`, `Record.record?/1` and `Record.record?/2`
|
||||
* [Kernel] `defrecord/3` is deprecated in favor of structs
|
||||
* [Kernel] `:hygiene` in `quote` is deprecated
|
||||
* [Mix] `Mix.project/0` is deprecated in favor of `Mix.Project.config/0`
|
||||
* [Process] `Process.spawn/1`, `Process.spawn/3`, `Process.spawn_link/1`, `Process.spawn_link/3`, `Process.spawn_monitor/1`, `Process.spawn_monitor/3`, `Process.send/2` and `Process.self/0` are deprecated in favor of the ones in `Kernel`
|
||||
|
||||
* Backwards incompatible changes
|
||||
* [Exception] Exceptions now generate structs instead of records
|
||||
* [OptionParser] Errors on parsing returns the switch and value as binaries (unparsed)
|
||||
* [String] `String.to_char_list/1` (previously deprecated) no longer returns a tuple but the char list only and raises in case of failure
|
||||
|
||||
## v0.13.2 (2014-05-11)
|
||||
|
||||
* Enhancements
|
||||
* [Application] Add an Application module with common functions to work with OTP applications
|
||||
@@ -7,7 +163,7 @@
|
||||
* [Mix] `mix deps.clean` now works accross environments
|
||||
* [Mix] Support line numbers in `mix test`, e.g. test/some/file_test.exs:12
|
||||
* [Mix] Use `@file` attributes to detect dependencies in between `.ex` and external files. This means changing an `.eex` file will no longer recompile the whole project only the files that depend directly on it
|
||||
* [Mix] Support application configurations in `config/config.exs` which can be customized by specifying your own `:config_path`
|
||||
* [Mix] Support application configurations in `config/config.exs`
|
||||
* [Mix] Support user-wide configuration with `~/.mix/config.exs`
|
||||
* [Mix] `mix help` now uses ANSI formatting to print guides
|
||||
* [Regex] Support functions in `Regex.replace/4`
|
||||
@@ -30,7 +186,6 @@
|
||||
* [System] Convert remaining functions in System to rely on char data
|
||||
|
||||
* Soft deprecations (no warnings emitted)
|
||||
* [Application] `use Application.Behaviour` is deprecated in favor of `use Application`
|
||||
* [Exception] `exception.message` is deprecated in favor `Exception.message/1` for retrieving exception messages
|
||||
* [Kernel] `is_exception/1`, `is_record/1` and `is_record/2` are deprecated in favor of `Exception.exception?1`, `Record.record?/1` and `Record.record?/2`
|
||||
* [Mix] `Mix.project/0` is deprecated in favor of `Mix.Project.config/0`
|
||||
@@ -51,7 +206,7 @@
|
||||
* [URI] The `URI.Info` record has now become the `URI` struct
|
||||
* [Version] The `Version.Schema` record has now become the `Version` struct
|
||||
|
||||
# v0.13.1 (2014-04-27)
|
||||
## v0.13.1 (2014-04-27)
|
||||
|
||||
* Enhancements
|
||||
* [Mix] Support `MIX_EXS` as configuration for running the current mix.exs file
|
||||
@@ -77,7 +232,7 @@
|
||||
* [Path] Atoms are no longer supported in Path functions
|
||||
* [Regex] Regexes are no longer unicode by default. Instead, they must be explicitly marked with the `u` option
|
||||
|
||||
# v0.13.0 (2014-04-20)
|
||||
## v0.13.0 (2014-04-20)
|
||||
|
||||
* Enhancements
|
||||
* [Base] Add `Base` module which does conversions to bases 16, 32, hex32, 64 and url64
|
||||
@@ -128,7 +283,7 @@
|
||||
* Backwards incompatible changes
|
||||
* [ExUnit] Formatters are now required to be a GenEvent and `ExUnit.run/2` returns a map with results
|
||||
|
||||
# v0.12.5 (2014-03-09)
|
||||
## v0.12.5 (2014-03-09)
|
||||
|
||||
* Bug fixes
|
||||
* [Kernel] Ensure `try` does not generate an after clause. Generating an after clause forbade clauses in the `else` part from being tail recursive. This should improve performance and memory consumption of `Stream` functions
|
||||
@@ -146,7 +301,7 @@
|
||||
* Backwards incompatible changes
|
||||
* [Mix] Remove `MIX_GIT_FORCE_HTTPS` as Git itself already provides mechanisms for doing so
|
||||
|
||||
# v0.12.4 (2014-02-12)
|
||||
## v0.12.4 (2014-02-12)
|
||||
|
||||
* Enhancements
|
||||
* [Mix] `mix deps.get` and `mix deps.update` no longer compile dependencies afterwards. Instead, they mark the dependencies which are going to be automatically compiled next time `deps.check` is invoked (which is done automatically by most mix tasks). This means users should have a better workflow when migrating in between environments
|
||||
@@ -160,7 +315,7 @@
|
||||
* [ExUnit] `CaptureIO` returns an empty string instead of nil when there is no capture
|
||||
* [Version] The `Version` module now only works with SemVer. The functions `Version.parse/1` and `Version.parse_requirement/1` now return `{:ok,res} | :error` for the cases you want to handle non SemVer cases manually. All other functions will trigger errors on non semantics versions
|
||||
|
||||
# v0.12.3 (2014-02-02)
|
||||
## v0.12.3 (2014-02-02)
|
||||
|
||||
* Enhancements
|
||||
* [Kernel] Warnings now are explicitly tagged with "warning:" in messages
|
||||
@@ -193,7 +348,7 @@
|
||||
* [Range] `Range` is no longer a record, instead use `first .. last` if you need pattern matching
|
||||
* [Set] Implementations of `difference/2`, `disjoint?/2`, `equal?/2`, `intersection/2`, `subset?/2` and `union/2` in `HashSet` are no longer polymorphic. To get polymorphism, use the functions in `Set` instead
|
||||
|
||||
# v0.12.2 (2014-01-15)
|
||||
## v0.12.2 (2014-01-15)
|
||||
|
||||
* Enhancements
|
||||
* [EEx] Allow `EEx.AssignsEngine` to accept any Dict
|
||||
@@ -222,7 +377,7 @@
|
||||
* Backwards incompatible changes
|
||||
* [String] Change `String.next_grapheme/1` and `String.next_codepoint/1` to return `nil` on string end
|
||||
|
||||
# v0.12.1 (2014-01-04)
|
||||
## v0.12.1 (2014-01-04)
|
||||
|
||||
* Enhancements
|
||||
* [ExUnit] Support `:include` and `:exclude` configuration options to filter which tests should run based on their tags. Those options are also supported via `mix test` as `--include` and `--exclude`
|
||||
@@ -246,7 +401,7 @@
|
||||
* [GenServer] GenServer now stops on unknown call/cast requests
|
||||
* [Kernel] Change how `->` is represented in AST. Now each clause is represented by its own AST node which makes composition easier. See commit 51aef55 for more information.
|
||||
|
||||
# v0.12.0 (2013-12-15)
|
||||
## v0.12.0 (2013-12-15)
|
||||
|
||||
* Enhancements
|
||||
* [Exception] Allow `exception/1` to be overridden and promote it as the main mechanism to customize exceptions
|
||||
@@ -283,7 +438,7 @@
|
||||
* [Stream] The `Stream.Lazy` structure has changed to accumulate functions and accumulators as we go (its inspected representation has also changed)
|
||||
* [Typespec] `when` clauses were moved to the outer part of the spec and should be in the keywords format. So `add(a, b) when is_subtype(a, integer) and is_subtype(b, integer) :: integer` should now be written as `add(a, b) :: integer when a: integer, b: integer`
|
||||
|
||||
# v0.11.2 (2013-11-14)
|
||||
## v0.11.2 (2013-11-14)
|
||||
|
||||
* Enhancements
|
||||
* [Mix] Add `mix iex` that redirects users to the proper `iex -S mix` command
|
||||
@@ -292,7 +447,7 @@
|
||||
* Backwards incompatible changes
|
||||
* [Mix] Mix now compiles files to `_build`. Projects should update just fine, however documentation and books may want to update to the latest information
|
||||
|
||||
# v0.11.1 (2013-11-07)
|
||||
## v0.11.1 (2013-11-07)
|
||||
|
||||
* Enhancements
|
||||
* [Mix] Improve dependency convergence by explicitly checking each requirement instead of expecting all requirements to be equal
|
||||
@@ -310,7 +465,7 @@
|
||||
* Backwards incompatible changes
|
||||
* [Mix] Setting `:load_paths` in your project configuration is deprecated
|
||||
|
||||
# v0.11.0 (2013-11-02)
|
||||
## v0.11.0 (2013-11-02)
|
||||
|
||||
* Enhancements
|
||||
* [Code] Eval now returns variables from other contexts
|
||||
@@ -357,7 +512,7 @@
|
||||
* [Process] `Process.group_leader/2` args have been reversed so the "subject" comes first
|
||||
* [Protocol] Protocol no longer dispatches to `Number`, but to `Integer` and `Float`
|
||||
|
||||
# v0.10.3 (2013-10-02)
|
||||
## v0.10.3 (2013-10-02)
|
||||
|
||||
* Enhancements
|
||||
* [Enum] Add `Enum.take_every/2`
|
||||
@@ -392,7 +547,7 @@
|
||||
* [Mix] `File.IteratorError` was renamed to `IO.StreamError`
|
||||
* [Mix] `mix new` now defaults to the `--sup` option, use `--bare` to get the previous behaviour
|
||||
|
||||
# v0.10.2 (2013-09-03)
|
||||
## v0.10.2 (2013-09-03)
|
||||
|
||||
* Enhancements
|
||||
* [CLI] Add `--verbose` to elixirc, which now is non-verbose by default
|
||||
@@ -455,7 +610,7 @@
|
||||
* [Kernel] The previous ambiguous import syntax `import :functions, Foo` was removed in favor of `import Foo, only: :functions`
|
||||
* [OptionParser] `parse` and `parse_head` now returns a tuple with three elements instead of two
|
||||
|
||||
# v0.10.1 (2013-08-03)
|
||||
## v0.10.1 (2013-08-03)
|
||||
|
||||
* Enhancements
|
||||
* [Behaviour] Add support for `defmacrocallback/1`
|
||||
@@ -505,7 +660,7 @@
|
||||
* [Regex] `Regex.scan/3` now always returns a list of lists, normalizing the result, instead of list with mixed lists and binaries
|
||||
* [System] `System.halt/2` was removed since the current Erlang implementation of such function is bugged
|
||||
|
||||
# v0.10.0 (2013-07-15)
|
||||
## v0.10.0 (2013-07-15)
|
||||
|
||||
* Enhancements
|
||||
* [ExUnit] Support `trace: true` option which gives detailed reporting on test runs
|
||||
@@ -549,7 +704,7 @@
|
||||
* [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)
|
||||
## v0.9.3 (2013-06-23)
|
||||
|
||||
* Enhancements
|
||||
* [File] Add `File.chgrp`, `File.chmod` and `File.chown`
|
||||
@@ -587,7 +742,7 @@
|
||||
* [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)
|
||||
## v0.9.2 (2013-06-13)
|
||||
|
||||
* Enhancements
|
||||
* [ExUnit] `capture_io` now captures prompt by default
|
||||
@@ -612,7 +767,7 @@
|
||||
* Backwards incompatible changes
|
||||
* [Kernel] The `=~` operator now returns true or false instead of an index
|
||||
|
||||
# v0.9.1 (2013-05-30)
|
||||
## v0.9.1 (2013-05-30)
|
||||
|
||||
* Enhancements
|
||||
* [IEx] Limit the number of entries kept in history and allow it to be configured
|
||||
@@ -628,7 +783,7 @@
|
||||
* [Record] Fix a bug where nested records cannot be defined
|
||||
* [Record] Fix a bug where a record named Record cannot be defined
|
||||
|
||||
# v0.9.0 (2013-05-23)
|
||||
## v0.9.0 (2013-05-23)
|
||||
|
||||
* Enhancements
|
||||
* [ExUnit] `ExUnit.CaptureIO` now accepts an input to be used during capture
|
||||
@@ -641,7 +796,7 @@
|
||||
* [Kernel] Erlang R15 is no longer supported
|
||||
* [Kernel] Elixir modules are now represented as `Elixir.ModuleName` (using `.` instead of `-` as separator)
|
||||
|
||||
# v0.8.3 (2013-05-22)
|
||||
## v0.8.3 (2013-05-22)
|
||||
|
||||
* Enhancements
|
||||
* [CLI] Flags `-p` and `-pr` fails if pattern match no files
|
||||
@@ -693,7 +848,7 @@
|
||||
* [Kernel] Precedence of `|>` has changed to lower to support constructs like `1..5 |> Enum.to_list`
|
||||
* [Mix] `mix escriptize` now receives arguments as binaries
|
||||
|
||||
# v0.8.2 (2013-04-20)
|
||||
## v0.8.2 (2013-04-20)
|
||||
|
||||
* Enhancements
|
||||
* [ExUnit] Use ANSI escape codes in CLI output
|
||||
@@ -733,7 +888,7 @@
|
||||
unquote(Macro.escape(x))
|
||||
end
|
||||
|
||||
# v0.8.1 (2013-02-17)
|
||||
## v0.8.1 (2013-02-17)
|
||||
|
||||
* Enhancements
|
||||
* [ExUnit] Tests can now receive metadata set on setup/teardown callbacks
|
||||
@@ -760,7 +915,7 @@
|
||||
* [Kernel] Old `:local.(args)` syntax is deprecated
|
||||
* [Process] `Process.self` is deprecated in favor `Kernel.self`
|
||||
|
||||
# v0.8.0 (2013-01-28)
|
||||
## v0.8.0 (2013-01-28)
|
||||
|
||||
* Enhancements
|
||||
* [Binary] Support `<< "string" :: utf8 >>` as in Erlang
|
||||
@@ -802,7 +957,7 @@
|
||||
* [Mix] `mix iex` is no longer functional, please use `iex -S mix`
|
||||
* [OptionParser] `:flags` option was deprecated in favor of `:switches` to support many types
|
||||
|
||||
# v0.7.2 (2012-12-04)
|
||||
## v0.7.2 (2012-12-04)
|
||||
|
||||
* Enhancements
|
||||
* [CLI] `--debug-info` is now true by default
|
||||
@@ -828,7 +983,7 @@
|
||||
* [Record] Default-based generated functions are deprecated
|
||||
* [Typespec] Enhancements and deprecations to the `@spec/@callback` and the fun type syntax
|
||||
|
||||
# v0.7.1 (2012-11-18)
|
||||
## v0.7.1 (2012-11-18)
|
||||
|
||||
* Enhancements
|
||||
* [IEx] Only show documented functions and also show docs for default generated functions
|
||||
@@ -859,7 +1014,7 @@
|
||||
* [Enum] `Enum.times` is deprecated in favor of using ranges
|
||||
* [System] `halt` moved to `System` module
|
||||
|
||||
# v0.7.0 (2012-10-20)
|
||||
## v0.7.0 (2012-10-20)
|
||||
|
||||
* Enhancements
|
||||
* [Behaviour] Add Behaviour with a simple callback DSL to define callbacks
|
||||
@@ -908,7 +1063,7 @@
|
||||
* [Kernel] `Erlang.` syntax is deprecated in favor of simply using atoms
|
||||
* [Module] `Module.read_attribute` and `Module.add_attribute` deprecated in favor of `Module.get_attribute` and `Module.put_attribute` which mimics Dict API
|
||||
|
||||
# v0.6.0 (2012-08-01)
|
||||
## v0.6.0 (2012-08-01)
|
||||
|
||||
* Backwards incompatible changes
|
||||
* [Kernel] Compile files now follow `Elixir-ModuleName` convention to solve issues with Erlang embedded mode. This removes the `__MAIN__` pseudo-variable as modules are now located inside `Elixir` namespace
|
||||
@@ -958,6 +1113,6 @@
|
||||
* [Regex] Back references are now properly supported
|
||||
* [System] Add `System.find_executable`
|
||||
|
||||
# v0.5.0 (2012-05-24)
|
||||
## v0.5.0 (2012-05-24)
|
||||
|
||||
* First official release
|
||||
|
||||
+21
-1
@@ -71,7 +71,7 @@ Please provide as much detail and context as possible.
|
||||
|
||||
## Contributing
|
||||
|
||||
We incentivate everyone to contribute to Elixir and help us tackle
|
||||
We incentivize everyone to contribute to Elixir and help us tackle
|
||||
existing issues! To do so, there are a few things you need to know
|
||||
about the code. First, Elixir code is divided in applications inside
|
||||
the `lib` folder:
|
||||
@@ -107,6 +107,26 @@ check [the official build status on Travis-CI](https://travis-ci.org/elixir-lang
|
||||
With tests running and passing, you are ready to contribute to Elixir and
|
||||
send your pull requests.
|
||||
|
||||
### Building on Windows
|
||||
|
||||
There are a few extra steps you'll need to take for contributing from Windows.
|
||||
Basically, once you have Erlang 17, Git, and MSYS from MinGW on your system,
|
||||
you're all set. Specifically, here's what you need to do to get up and running:
|
||||
|
||||
1. Install [Git](http://www.git-scm.com/download/win),
|
||||
[Erlang](http://www.erlang.org/download.html), and the
|
||||
[MinGW Installation Manager](http://sourceforge.net/projects/mingw/files/latest/download?source=files).
|
||||
2. Use the MinGW Installation Manager to install the msys-bash, msys-make, and
|
||||
msys-grep packages.
|
||||
3. Add `;C:\Program Files (x86)\Git\bin;C:\Program Files\erl6.0\bin;C:\Program Files\erl6.0\erts-6.0\bin;C:\MinGW\msys\1.0\bin`
|
||||
to your "Path" environment variable . (This is under Control Panel > System
|
||||
and Security > System > Advanced system settings > Environment Variables >
|
||||
System variables)
|
||||
|
||||
You can now work in the Command Prompt similar to how you would on other OS'es,
|
||||
except for some things (like creating symlinks) you'll need to run the Command
|
||||
Prompt as an Administrator.
|
||||
|
||||
## Contributing Documentation
|
||||
|
||||
Code documentation (`@doc`, `@moduledoc`, `@typedoc`) has a special convention:
|
||||
|
||||
@@ -32,7 +32,7 @@ $(1): lib/$(1)/ebin/Elixir.$(2).beam lib/$(1)/ebin/$(1).app
|
||||
lib/$(1)/ebin/$(1).app: lib/$(1)/mix.exs
|
||||
$(Q) mkdir -p lib/$(1)/_build/shared/lib/$(1)
|
||||
$(Q) cp -R lib/$(1)/ebin lib/$(1)/_build/shared/lib/$(1)/
|
||||
$(Q) cd lib/$(1) && ../../bin/elixir -e "Mix.Sup.start_link()" -r mix.exs -e "Mix.Task.run('compile.app')"
|
||||
$(Q) cd lib/$(1) && ../../bin/elixir -e "Mix.start(:permanent, [])" -r mix.exs -e "Mix.Task.run('compile.app')"
|
||||
$(Q) cp lib/$(1)/_build/shared/lib/$(1)/ebin/$(1).app lib/$(1)/ebin/$(1).app
|
||||
$(Q) rm -rf lib/$(1)/_build
|
||||
|
||||
@@ -67,10 +67,10 @@ erlang:
|
||||
|
||||
# Since Mix depends on EEx and EEx depends on
|
||||
# Mix, we first compile EEx without the .app
|
||||
# file, then mix and then compile eex fully
|
||||
elixir: kernel lib/eex/ebin/Elixir.EEx.beam mix ex_unit eex iex
|
||||
# file, then mix and then compile EEx fully
|
||||
elixir: stdlib lib/eex/ebin/Elixir.EEx.beam mix ex_unit eex iex
|
||||
|
||||
kernel: $(KERNEL) VERSION
|
||||
stdlib: $(KERNEL) VERSION
|
||||
$(KERNEL): lib/elixir/lib/*.ex lib/elixir/lib/*/*.ex
|
||||
$(Q) if [ ! -f $(KERNEL) ]; then \
|
||||
echo "==> bootstrap (compile)"; \
|
||||
@@ -101,7 +101,7 @@ install: compile
|
||||
$(INSTALL_DATA) $$dir/ebin/* "$(DESTDIR)$(PREFIX)/$(LIBDIR)/elixir/$$dir/ebin"; \
|
||||
done
|
||||
$(Q) $(INSTALL_DIR) "$(DESTDIR)$(PREFIX)/$(LIBDIR)/elixir/bin"
|
||||
$(Q) $(INSTALL_PROGRAM) $(filter-out %.bat, $(wildcard bin/*)) "$(DESTDIR)$(PREFIX)/$(LIBDIR)/elixir/bin"
|
||||
$(Q) $(INSTALL_PROGRAM) $(filter-out %.ps1, $(filter-out %.bat, $(wildcard bin/*))) "$(DESTDIR)$(PREFIX)/$(LIBDIR)/elixir/bin"
|
||||
$(Q) $(INSTALL_DIR) "$(DESTDIR)$(PREFIX)/bin"
|
||||
$(Q) for file in "$(DESTDIR)$(PREFIX)"/$(LIBDIR)/elixir/bin/* ; do \
|
||||
ln -sf "../$(LIBDIR)/elixir/bin/$${file##*/}" "$(DESTDIR)$(PREFIX)/bin/" ; \
|
||||
@@ -124,13 +124,15 @@ clean_exbeam:
|
||||
#==> Release tasks
|
||||
|
||||
SOURCE_REF = $(shell head="$$(git rev-parse HEAD)" tag="$$(git tag --points-at $$head | tail -1)" ; echo "$${tag:-$$head}\c")
|
||||
DOCS = bin/elixir ../ex_doc/bin/ex_doc "$(1)" "$(VERSION)" "lib/$(2)/ebin" -m "$(3)" -u "https://github.com/elixir-lang/elixir" --source-ref "$(call SOURCE_REF)" -o docs/$(2) -p http://elixir-lang.org/docs.html
|
||||
|
||||
docs: compile ../ex_doc/bin/ex_doc
|
||||
mkdir -p ebin
|
||||
rm -rf docs
|
||||
cp -R -f lib/*/ebin/*.beam ./ebin
|
||||
bin/elixir ../ex_doc/bin/ex_doc "Elixir" "$(VERSION)" "./ebin" -m Kernel -u "https://github.com/elixir-lang/elixir" --source-ref "$(call SOURCE_REF)"
|
||||
rm -rf ebin
|
||||
$(Q) rm -rf docs
|
||||
$(call DOCS,Elixir,elixir,Kernel)
|
||||
$(call DOCS,EEx,eex,EEx)
|
||||
$(call DOCS,Mix,mix,Mix)
|
||||
$(call DOCS,IEx,iex,IEx)
|
||||
$(call DOCS,ExUnit,ex_unit,ExUnit)
|
||||
|
||||
../ex_doc/bin/ex_doc:
|
||||
@ echo "ex_doc is not found in ../ex_doc as expected. See README for more information."
|
||||
@@ -149,21 +151,28 @@ release_docs: docs
|
||||
|
||||
test: test_erlang test_elixir
|
||||
|
||||
test_erlang: compile
|
||||
TEST_ERL = lib/elixir/test/erlang
|
||||
TEST_EBIN = lib/elixir/test/ebin
|
||||
TEST_ERLS = $(addprefix $(TEST_EBIN)/, $(addsuffix .beam, $(basename $(notdir $(wildcard $(TEST_ERL)/*.erl)))))
|
||||
|
||||
test_erlang: compile $(TEST_ERLS)
|
||||
@ echo "==> elixir (eunit)"
|
||||
$(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;
|
||||
$(Q) $(ERL) -pa $(TEST_EBIN) -s test_helper test;
|
||||
@ echo ""
|
||||
|
||||
test_elixir: test_kernel test_ex_unit test_doc_test test_mix test_eex test_iex
|
||||
$(TEST_EBIN)/%.beam: $(TEST_ERL)/%.erl
|
||||
$(Q) mkdir -p $(TEST_EBIN)
|
||||
$(Q) $(ERLC) -o $(TEST_EBIN) $<
|
||||
|
||||
test_elixir: test_stdlib test_ex_unit test_doc_test test_mix test_eex test_iex
|
||||
|
||||
test_doc_test: compile
|
||||
@ echo "==> doctest (exunit)"
|
||||
$(Q) cd lib/elixir && ../../bin/elixir -r "test/doc_test.exs";
|
||||
|
||||
test_kernel: compile
|
||||
test_stdlib: compile
|
||||
@ echo "==> elixir (exunit)"
|
||||
$(Q) exec epmd & exit
|
||||
$(Q) cd lib/elixir && ../../bin/elixir -r "test/elixir/test_helper.exs" -pr "test/elixir/**/*_test.exs";
|
||||
|
||||
.dialyzer.base_plt:
|
||||
|
||||
@@ -37,7 +37,7 @@ We appreciate any contribution to Elixir, so check out our [CONTRIBUTING.md](CON
|
||||
|
||||
## Important links
|
||||
|
||||
* #elixir-lang on freenode IRC
|
||||
* \#elixir-lang on freenode IRC
|
||||
* [Website][1]
|
||||
* [Issue tracker][2]
|
||||
* [elixir-talk Mailing list (questions)][3]
|
||||
@@ -54,4 +54,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](LEGAL) and [LICENSE](LICENSE) files for more information.
|
||||
|
||||
+13
-11
@@ -1,26 +1,28 @@
|
||||
## Release process
|
||||
# Release process
|
||||
|
||||
This document simply outlines the release process:
|
||||
|
||||
1) Remove `-dev` extension from VERSION
|
||||
1. Remove `-dev` extension from VERSION
|
||||
|
||||
2) Ensure CHANGELOG is updated and timestamp
|
||||
2. Ensure CHANGELOG is updated and timestamp
|
||||
|
||||
3) Commit changes above with title "Release vVERSION" and generate new tag
|
||||
3. Commit changes above with title "Release vVERSION" and generate new tag
|
||||
|
||||
4) Run `make clean test` to ensure all tests pass from scratch and the CI is green
|
||||
4. Run `make clean test` to ensure all tests pass from scratch and the CI is green
|
||||
|
||||
5) Push master and tags
|
||||
5. Push master and tags
|
||||
|
||||
6) Release new docs with `make release_docs`, move docs to `docs/stable`
|
||||
6. Release new docs with `make release_docs`, move docs to `docs/stable`
|
||||
|
||||
7) Release new zip with `make release_zip`, push new zip to GitHub Releases
|
||||
7. Release new zip with `make release_zip`, push new zip to GitHub Releases
|
||||
|
||||
8) Merge master into stable branch and push it
|
||||
8. Merge master into stable branch and push it
|
||||
|
||||
9) After release, bump versions, add `-dev` back and commit
|
||||
9. After release, bump versions, add `-dev` back and commit
|
||||
|
||||
10) `make release_docs` once again and push it to `elixir-lang.github.com`
|
||||
10. `make release_docs` once again and push it to `elixir-lang.org`
|
||||
|
||||
11. Also update `release` file in `elixir-lang.org`
|
||||
|
||||
## Places where version is mentioned
|
||||
|
||||
|
||||
+3
-7
@@ -17,11 +17,10 @@ if [ $# -eq 0 ] || [ "$1" = "--help" ] || [ "$1" = "-h" ]; then
|
||||
--hidden Makes a hidden node
|
||||
--detached Starts the Erlang VM detached from console
|
||||
--no-halt Does not halt the Erlang VM after execution
|
||||
--gen-debug Turns on default debugging for all GenServers
|
||||
|
||||
** Options marked with (*) can be given more than once
|
||||
** Options given after the .exs file or -- are passed down to the executed code
|
||||
** Options can be passed to the erlang runtime using ELIXIR_ERL_OPTS or --erl" >&2
|
||||
** Options can be passed to the erlang runtime using ELIXIR_ERL_OPTIONS or --erl" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
@@ -72,9 +71,6 @@ while [ $I -le $# ]; do
|
||||
eval "VAL=\${$I}"
|
||||
ERL="$ERL "$VAL""
|
||||
;;
|
||||
--gen-debug)
|
||||
ERL="$ERL -generic_debug"
|
||||
;;
|
||||
*)
|
||||
break
|
||||
;;
|
||||
@@ -84,7 +80,7 @@ done
|
||||
|
||||
SELF=$(readlink_f "$0")
|
||||
SCRIPT_PATH=$(dirname "$SELF")
|
||||
if [ "$MODE" != "iex" ]; then ERL="$ERL -s elixir start_cli"; fi
|
||||
if [ "$MODE" != "iex" ]; then ERL="$ERL -noshell -s elixir start_cli"; fi
|
||||
|
||||
if [ -z "$ERL_PATH" ]; then
|
||||
if [ -f "$SCRIPT_PATH/../releases/RELEASES" ] && [ -f "$SCRIPT_PATH/erl" ]; then
|
||||
@@ -94,4 +90,4 @@ if [ -z "$ERL_PATH" ]; then
|
||||
fi
|
||||
fi
|
||||
|
||||
exec "$ERL_PATH" -pa "$SCRIPT_PATH"/../lib/*/ebin -noshell $ELIXIR_ERL_OPTS $ERL -extra "$@"
|
||||
exec "$ERL_PATH" -pa "$SCRIPT_PATH"/../lib/*/ebin $ELIXIR_ERL_OPTIONS $ERL -extra "$@"
|
||||
|
||||
+7
-13
@@ -1,12 +1,8 @@
|
||||
@echo off
|
||||
set argc=0
|
||||
for %%A in (%*) do (
|
||||
if "%%A"=="--help" goto documentation
|
||||
if "%%A"=="-h" goto documentation
|
||||
if "%%A"=="/h" goto documentation
|
||||
set /A argc+=1
|
||||
)
|
||||
if %argc%==0 goto documentation
|
||||
if "%1"=="" goto documentation
|
||||
if "%1"=="--help" goto documentation
|
||||
if "%1"=="-h" goto documentation
|
||||
if "%1"=="/h" goto documentation
|
||||
goto parseopts
|
||||
|
||||
:documentation
|
||||
@@ -27,11 +23,10 @@ echo --cookie cookie Sets a cookie for this distributed node
|
||||
echo --hidden Makes a hidden node
|
||||
echo --detached Starts the Erlang VM detached from console
|
||||
echo --no-halt Does not halt the Erlang VM after execution
|
||||
echo --gen-debug Turns on default debugging for all GenServers
|
||||
echo.
|
||||
echo ** Options marked with (*) can be given more than once
|
||||
echo ** Options given after the .exs file or -- are passed down to the executed code
|
||||
echo ** Options can be passed to the erlang runtime using ELIXIR_ERL_OPTS or --erl
|
||||
echo ** Options can be passed to the erlang runtime using ELIXIR_ERL_OPTIONS or --erl
|
||||
goto :EOF
|
||||
|
||||
:parseopts
|
||||
@@ -72,7 +67,6 @@ IF NOT "%par%"=="%par:--cookie=%" (Set parsErlang=%parsErlang% -setcookie %1 &
|
||||
IF NOT "%par%"=="%par:--sname=%" (Set parsErlang=%parsErlang% -sname %1 && shift)
|
||||
IF NOT "%par%"=="%par:--name=%" (Set parsErlang=%parsErlang% -name %1 && shift)
|
||||
IF NOT "%par%"=="%par:--erl=%" (Set beforeExtra=%beforeExtra% %~1 && shift)
|
||||
IF NOT "%par%"=="%par:--gen-debug=%" (Set parsErlang=%parsErlang% -generic_debug)
|
||||
rem ******* elixir parameters **********************
|
||||
rem Note: we don't have to do anything with options that don't take an argument
|
||||
IF NOT "%par%"=="%par:-e=%" (shift)
|
||||
@@ -95,7 +89,7 @@ for /d %%d in ("%originPath%..\lib\*.") do (
|
||||
SETLOCAL disabledelayedexpansion
|
||||
:run
|
||||
IF %useWerl% EQU 1 (
|
||||
werl %ext_libs% -noshell %ELIXIR_ERL_OPTS% %parsErlang% -s elixir start_cli %beforeExtra% -extra %*
|
||||
werl.exe %ext_libs% %ELIXIR_ERL_OPTIONS% %parsErlang% -s elixir start_cli %beforeExtra% -extra %*
|
||||
) ELSE (
|
||||
erl %ext_libs% -noshell %ELIXIR_ERL_OPTS% %parsErlang% -s elixir start_cli %beforeExtra% -extra %*
|
||||
erl.exe %ext_libs% -noshell %ELIXIR_ERL_OPTIONS% %parsErlang% -s elixir start_cli %beforeExtra% -extra %*
|
||||
)
|
||||
|
||||
+2
-1
@@ -10,7 +10,8 @@ if [ $# -eq 0 ] || [ "$1" = "--help" ] || [ "$1" = "-h" ]; then
|
||||
--verbose Print informational messages.
|
||||
|
||||
** Options given after -- are passed down to the executed code
|
||||
** Options can be passed to the erlang runtime using ELIXIR_ERL_OPTS" >&2
|
||||
** Options can be passed to the erlang runtime using ELIXIR_ERL_OPTIONS
|
||||
** Options can be passed to the erlang compiler using ERL_COMPILER_OPTIONS" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
|
||||
+2
-2
@@ -19,8 +19,8 @@ echo --ignore-module-conflict
|
||||
echo --warnings-as-errors Treat warnings as errors and return non-zero exit code
|
||||
echo --verbose Print informational messages.
|
||||
echo.
|
||||
echo ** Options marked with (*) can be given more than once
|
||||
echo ** Options given after -- are passed down to the executed code
|
||||
echo ** Options can be passed to the erlang runtime using ELIXIR_ERL_OPTS" >&2
|
||||
echo ** Options can be passed to the erlang runtime using ELIXIR_ERL_OPTIONS
|
||||
echo ** Options can be passed to the erlang compiler using ERL_COMPILER_OPTIONS >&2
|
||||
:run
|
||||
call "%~dp0\elixir.bat" +elixirc %*
|
||||
|
||||
@@ -16,14 +16,13 @@ if [ $# -gt 0 ] && ([ "$1" = "--help" ] || [ "$1" = "-h" ]); then
|
||||
--cookie \"cookie\" Sets a cookie for this distributed node
|
||||
--hidden Makes a hidden node
|
||||
--detached Starts the Erlang VM detached from console
|
||||
--gen-debug Turns on default debugging for all GenServers
|
||||
--remsh \"name\" Connects to a node using a remote shell
|
||||
--dot-iex \"path\" Overrides default .iex.exs file and uses path instead;
|
||||
path can be empty, then no file will be loaded
|
||||
|
||||
** Options marked with (*) can be given more than once
|
||||
** Options given after the .exs file or -- are passed down to the executed code
|
||||
** Options can be passed to the VM using ELIXIR_ERL_OPTS or --erl" >&2
|
||||
** Options can be passed to the VM using ELIXIR_ERL_OPTIONS or --erl" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
# Initialize with path to mix.bat relative to caller's working directory
|
||||
$toCmd = '' + (Resolve-Path -relative (Split-Path $MyInvocation.MyCommand.Path)) + '\mix.bat'
|
||||
|
||||
foreach ($arg in $args)
|
||||
{
|
||||
$toCmd += ' '
|
||||
|
||||
if ($arg -is [array])
|
||||
{
|
||||
# Commas created the array so we need to reintroduce those commas
|
||||
for ($i = 0; $i -lt $arg.length; $i++)
|
||||
{
|
||||
$toCmd += $arg[$i]
|
||||
if ($i -ne ($arg.length - 1))
|
||||
{
|
||||
$toCmd += ', '
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
$toCmd += $arg
|
||||
}
|
||||
}
|
||||
|
||||
# Corrected arguments are ready to pass to batch file
|
||||
cmd /c $toCmd
|
||||
+24
-20
@@ -1,4 +1,6 @@
|
||||
defexception EEx.SyntaxError, message: nil
|
||||
defmodule EEx.SyntaxError do
|
||||
defexception [:message]
|
||||
end
|
||||
|
||||
defmodule EEx do
|
||||
@moduledoc ~S"""
|
||||
@@ -12,32 +14,31 @@ defmodule EEx do
|
||||
|
||||
This module provides 3 main APIs for you to use:
|
||||
|
||||
1) Evaluate a string (`eval_string`) or a file (`eval_file`)
|
||||
directly. This is the simplest API to use but also the
|
||||
slowest, since the code is evaluated and not compiled before;
|
||||
1. Evaluate a string (`eval_string`) or a file (`eval_file`)
|
||||
directly. This is the simplest API to use but also the
|
||||
slowest, since the code is evaluated and not compiled before.
|
||||
|
||||
2) Define a function from a string (`function_from_string`)
|
||||
or a file (`function_from_file`). This allows you to embed
|
||||
the template as a function inside a module which will then
|
||||
be compiled. This is the preferred API if you have access
|
||||
to the template at compilation time;
|
||||
2. Define a function from a string (`function_from_string`)
|
||||
or a file (`function_from_file`). This allows you to embed
|
||||
the template as a function inside a module which will then
|
||||
be compiled. This is the preferred API if you have access
|
||||
to the template at compilation time.
|
||||
|
||||
3) Compile a string (`compile_string`) or a file (`compile_file`)
|
||||
into Elixir syntax tree. This is the API used by both functions
|
||||
above and is available to you if you want to provide your own
|
||||
ways of handling the compiled template.
|
||||
3. Compile a string (`compile_string`) or a file (`compile_file`)
|
||||
into Elixir syntax tree. This is the API used by both functions
|
||||
above and is available to you if you want to provide your own
|
||||
ways of handling the compiled template.
|
||||
|
||||
## Options
|
||||
|
||||
All functions in this module accepts EEx-related options.
|
||||
They are:
|
||||
|
||||
* `:line` - the line to be used as the template start.
|
||||
Defaults to 1;
|
||||
* `:file` - the file to be used in the template.
|
||||
Defaults to the given file the template is read from
|
||||
or to "nofile" when compiling from a string;
|
||||
* `:engine` - the EEx engine to be used for compilation.
|
||||
* `:line` - the line to be used as the template start. Defaults to 1.
|
||||
* `:file` - the file to be used in the template. Defaults to the given
|
||||
file the template is read from or to "nofile" when compiling
|
||||
from a string.
|
||||
* `:engine` - the EEx engine to be used for compilation.
|
||||
|
||||
## Engine
|
||||
|
||||
@@ -81,7 +82,7 @@ defmodule EEx do
|
||||
iex> EEx.eval_string "<%= @foo %>", assigns: [foo: 1]
|
||||
"1"
|
||||
|
||||
In other words, <%= @foo %> is simply translated to:
|
||||
In other words, `<%= @foo %>` is simply translated to:
|
||||
|
||||
<%= Dict.get assigns, :foo %>
|
||||
|
||||
@@ -91,6 +92,7 @@ defmodule EEx do
|
||||
|
||||
@doc """
|
||||
Generates a function definition from the string.
|
||||
|
||||
The kind (`:def` or `:defp`) must be given, the
|
||||
function name, its arguments and the compilation options.
|
||||
|
||||
@@ -119,6 +121,7 @@ defmodule EEx do
|
||||
|
||||
@doc """
|
||||
Generates a function definition from the file contents.
|
||||
|
||||
The kind (`:def` or `:defp`) must be given, the
|
||||
function name, its arguments and the compilation options.
|
||||
|
||||
@@ -146,6 +149,7 @@ defmodule EEx do
|
||||
args = Enum.map args, fn arg -> {arg, [line: 1], nil} end
|
||||
compiled = EEx.compile_file(file, info)
|
||||
|
||||
@external_resource file
|
||||
@file file
|
||||
case kind do
|
||||
:def -> def(unquote(name)(unquote_splicing(args)), do: unquote(compiled))
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
defmodule EEx.Compiler do
|
||||
@moduledoc false
|
||||
|
||||
# when changing this setting, don't forget to update the docs for EEx
|
||||
# When changing this setting, don't forget to update the docs for EEx
|
||||
@default_engine EEx.SmartEngine
|
||||
|
||||
@doc """
|
||||
@@ -21,7 +21,7 @@ defmodule EEx.Compiler do
|
||||
# Generates the buffers by handling each expression from the tokenizer
|
||||
|
||||
defp generate_buffer([{:text, chars}|t], buffer, scope, state) do
|
||||
buffer = state.engine.handle_text(buffer, String.from_char_data!(chars))
|
||||
buffer = state.engine.handle_text(buffer, IO.chardata_to_string(chars))
|
||||
generate_buffer(t, buffer, scope, state)
|
||||
end
|
||||
|
||||
@@ -68,7 +68,7 @@ defmodule EEx.Compiler do
|
||||
defp wrap_expr(current, line, buffer, chars, state) do
|
||||
new_lines = List.duplicate(?\n, line - state.line)
|
||||
key = length(state.quoted)
|
||||
placeholder = '__EEX__(' ++ integer_to_list(key) ++ ');'
|
||||
placeholder = '__EEX__(' ++ Integer.to_char_list(key) ++ ');'
|
||||
{current ++ placeholder ++ new_lines ++ chars,
|
||||
%{state | quoted: [{key, buffer}|state.quoted]}}
|
||||
end
|
||||
|
||||
+57
-15
@@ -1,27 +1,28 @@
|
||||
defmodule EEx.Engine do
|
||||
@moduledoc ~S"""
|
||||
This is the basic EEx engine that ships with Elixir.
|
||||
Basic EEx engine that ships with Elixir.
|
||||
|
||||
An engine needs to implement three functions:
|
||||
|
||||
* `handle_body(quoted)` - receives the final built quoted
|
||||
expression, should do final post-processing and return a
|
||||
quoted expression;
|
||||
* `handle_body(quoted)` - receives the final built quoted
|
||||
expression, should do final post-processing and return a
|
||||
quoted expression.
|
||||
|
||||
* `handle_text(buffer, text)` - it receives the buffer,
|
||||
the text and must return a new quoted expression;
|
||||
* `handle_text(buffer, text)` - it receives the buffer,
|
||||
the text and must return a new quoted expression.
|
||||
|
||||
* `handle_expr(buffer, marker, expr)` - it receives the buffer,
|
||||
the marker, the expr and must return a new quoted expression;
|
||||
* `handle_expr(buffer, marker, expr)` - it receives the buffer,
|
||||
the marker, the expr and must return a new quoted expression.
|
||||
|
||||
The marker is what follows exactly after `<%`. For example,
|
||||
`<% foo %>` has an empty marker, but `<%= foo %>` has `"="`
|
||||
as marker. The allowed markers so far are:
|
||||
The marker is what follows exactly after `<%`. For example,
|
||||
`<% foo %>` has an empty marker, but `<%= foo %>` has `"="`
|
||||
as marker. The allowed markers so far are: `""` and `"="`.
|
||||
|
||||
* `""`
|
||||
* `"="`
|
||||
Read `handle_expr/3` below for more information about the markers
|
||||
implemented by default by this engine.
|
||||
|
||||
Read `handle_expr/3` below for more information about the markers
|
||||
implemented by default by this engine.
|
||||
`EEx.Engine` can be used directly if one desires to use the
|
||||
default implementations for the functions above.
|
||||
"""
|
||||
|
||||
use Behaviour
|
||||
@@ -30,6 +31,47 @@ defmodule EEx.Engine do
|
||||
defcallback handle_text(Macro.t, binary) :: Macro.t
|
||||
defcallback handle_expr(Macro.t, binary, Macro.t) :: Macro.t
|
||||
|
||||
@doc false
|
||||
defmacro __using__(_) do
|
||||
quote do
|
||||
@behaviour EEx.Engine
|
||||
|
||||
def handle_body(body) do
|
||||
EEx.Engine.handle_body(body)
|
||||
end
|
||||
|
||||
def handle_text(buffer, text) do
|
||||
EEx.Engine.handle_text(buffer, text)
|
||||
end
|
||||
|
||||
def handle_expr(buffer, mark, expr) do
|
||||
EEx.Engine.handle_expr(buffer, mark, expr)
|
||||
end
|
||||
|
||||
defoverridable [handle_body: 1, handle_expr: 3, handle_text: 2]
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Handles assigns in quoted expressions.
|
||||
|
||||
This can be added to any custom engine by invoking
|
||||
`handle_assign/3` with `Macro.prewalk/1`:
|
||||
|
||||
def handle_expr(buffer, token, expr) do
|
||||
expr = Macro.prewalk(expr, &EEx.Engine.handle_assign/1)
|
||||
EEx.Engine.handle_expr(buffer, token, expr)
|
||||
end
|
||||
|
||||
"""
|
||||
def handle_assign({:@, line, [{name, _, atom}]}) when is_atom(name) and is_atom(atom) do
|
||||
quote line: line, do: Dict.get(var!(assigns), unquote(name))
|
||||
end
|
||||
|
||||
def handle_assign(arg) do
|
||||
arg
|
||||
end
|
||||
|
||||
@doc """
|
||||
The default implementation implementation simply returns the
|
||||
given expression.
|
||||
|
||||
@@ -1,14 +1,5 @@
|
||||
defmodule EEx.TransformerEngine do
|
||||
@moduledoc """
|
||||
An abstract engine that is meant to be used and
|
||||
built upon in other modules. This engine implements
|
||||
the `EEx.Engine` behaviour and provides a `transform`
|
||||
overridable directive that allows a developer to
|
||||
customize the expression returned by the engine.
|
||||
|
||||
Check `EEx.AssignsEngine` and `EEx.SmartEngine` for
|
||||
examples of using this module.
|
||||
"""
|
||||
@moduledoc false
|
||||
|
||||
@doc false
|
||||
defmacro __using__(_) do
|
||||
@@ -49,20 +40,33 @@ defmodule EEx.TransformerEngine do
|
||||
end
|
||||
|
||||
defmodule EEx.AssignsEngine do
|
||||
@moduledoc """
|
||||
An abstract engine that, when used with the
|
||||
`TransformerEngine`, allows a developer to access
|
||||
assigns using `@` as syntax.
|
||||
@moduledoc false
|
||||
|
||||
This engine is included by default on the SmartEngine.
|
||||
@doc false
|
||||
defmacro __using__(_) do
|
||||
quote unquote: false do
|
||||
defp transform({:@, line, [{name, _, atom}]}) when is_atom(name) and is_atom(atom) do
|
||||
quote do: Dict.get(var!(assigns), unquote(name))
|
||||
end
|
||||
|
||||
defp transform(arg) do
|
||||
super(arg)
|
||||
end
|
||||
|
||||
defoverridable [transform: 1]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defmodule EEx.SmartEngine do
|
||||
@moduledoc """
|
||||
The default engine used by EEx.
|
||||
|
||||
It includes assigns (like `@foo`) and possibly other
|
||||
conveniences in the future.
|
||||
|
||||
## Examples
|
||||
|
||||
defmodule MyEngine do
|
||||
use EEx.TransformerEngine
|
||||
use EEx.AssignsEngine
|
||||
end
|
||||
|
||||
iex> EEx.eval_string("<%= @foo %>", assigns: [foo: 1])
|
||||
"1"
|
||||
|
||||
@@ -88,29 +92,10 @@ defmodule EEx.AssignsEngine do
|
||||
|
||||
"""
|
||||
|
||||
@doc false
|
||||
defmacro __using__(_) do
|
||||
quote unquote: false do
|
||||
defp transform({:@, line, [{name, _, atom}]}) when is_atom(name) and is_atom(atom) do
|
||||
quote do: Dict.get(var!(assigns), unquote(name))
|
||||
end
|
||||
use EEx.Engine
|
||||
|
||||
defp transform(arg) do
|
||||
super(arg)
|
||||
end
|
||||
|
||||
defoverridable [transform: 1]
|
||||
end
|
||||
def handle_expr(buffer, mark, expr) do
|
||||
expr = Macro.prewalk(expr, &EEx.Engine.handle_assign/1)
|
||||
super(buffer, mark, expr)
|
||||
end
|
||||
end
|
||||
|
||||
defmodule EEx.SmartEngine do
|
||||
use EEx.TransformerEngine
|
||||
use EEx.AssignsEngine
|
||||
|
||||
@moduledoc """
|
||||
An engine meant for end-user usage that includes
|
||||
`EEx.AssignsEngine` and other conveniences. Read
|
||||
`EEx.AssignsEngine` for examples.
|
||||
"""
|
||||
end
|
||||
|
||||
@@ -5,15 +5,15 @@ defmodule EEx.Tokenizer do
|
||||
Tokenizes the given char list or binary.
|
||||
It returns 4 different types of tokens as result:
|
||||
|
||||
* {:text, contents}
|
||||
* {:expr, line, marker, contents}
|
||||
* {:start_expr, line, marker, contents}
|
||||
* {:middle_expr, line, marker, contents}
|
||||
* {:end_expr, line, marker, contents}
|
||||
* `{:text, contents}`
|
||||
* `{:expr, line, marker, contents}`
|
||||
* `{:start_expr, line, marker, contents}`
|
||||
* `{:middle_expr, line, marker, contents}`
|
||||
* `{:end_expr, line, marker, contents}`
|
||||
|
||||
"""
|
||||
def tokenize(bin, line) when is_binary(bin) do
|
||||
tokenize(List.from_char_data!(bin), line)
|
||||
tokenize(String.to_char_list(bin), line)
|
||||
end
|
||||
|
||||
def tokenize(list, line) do
|
||||
|
||||
+24
-14
@@ -2,16 +2,20 @@ Code.require_file "test_helper.exs", __DIR__
|
||||
|
||||
require EEx
|
||||
|
||||
defmodule EExText.Compiled do
|
||||
defmodule EExTest.Compiled do
|
||||
def before_compile do
|
||||
fill_in_stacktrace
|
||||
{__ENV__.line, hd(tl(System.stacktrace))}
|
||||
end
|
||||
{:erlang, 1, 2}.tuple_to_list
|
||||
|
||||
EEx.function_from_string :def, :string_sample, "<%= a + b %>", [:a, :b]
|
||||
|
||||
filename = Path.join(__DIR__, "fixtures/eex_template_with_bindings.eex")
|
||||
EEx.function_from_file :defp, :private_file_sample, filename, [:bar]
|
||||
|
||||
filename = Path.join(__DIR__, "fixtures/eex_template_with_bindings.eex")
|
||||
EEx.function_from_file :def, :public_file_sample, filename, [:bar]
|
||||
|
||||
def file_sample(arg), do: private_file_sample(arg)
|
||||
|
||||
def after_compile do
|
||||
@@ -319,39 +323,45 @@ foo
|
||||
end
|
||||
end
|
||||
|
||||
test "sets external resource attribute" do
|
||||
assert EExTest.Compiled.__info__(:attributes)[:external_resource] ==
|
||||
[Path.join(__DIR__, "fixtures/eex_template_with_bindings.eex")]
|
||||
end
|
||||
|
||||
test "defined from string" do
|
||||
assert EExText.Compiled.string_sample(1, 2) == "3"
|
||||
assert EExTest.Compiled.string_sample(1, 2) == "3"
|
||||
end
|
||||
|
||||
test "defined from file" do
|
||||
assert EExText.Compiled.file_sample(1) == "foo 1\n"
|
||||
assert EExTest.Compiled.file_sample(1) == "foo 1\n"
|
||||
assert EExTest.Compiled.public_file_sample(1) == "foo 1\n"
|
||||
end
|
||||
|
||||
test "defined from file do not affect backtrace" do
|
||||
assert EExText.Compiled.before_compile ==
|
||||
assert EExTest.Compiled.before_compile ==
|
||||
{8,
|
||||
{EExText.Compiled,
|
||||
{EExTest.Compiled,
|
||||
:before_compile,
|
||||
0,
|
||||
[file: to_char_list(Path.relative_to_cwd(__ENV__.file)), line: 7]
|
||||
}
|
||||
}
|
||||
|
||||
assert EExText.Compiled.after_compile ==
|
||||
{19,
|
||||
{EExText.Compiled,
|
||||
assert EExTest.Compiled.after_compile ==
|
||||
{23,
|
||||
{EExTest.Compiled,
|
||||
:after_compile,
|
||||
0,
|
||||
[file: to_char_list(Path.relative_to_cwd(__ENV__.file)), line: 18]
|
||||
[file: to_char_list(Path.relative_to_cwd(__ENV__.file)), line: 22]
|
||||
}
|
||||
}
|
||||
|
||||
assert EExText.Compiled.unknown ==
|
||||
{25,
|
||||
{EExText.Compiled,
|
||||
assert EExTest.Compiled.unknown ==
|
||||
{29,
|
||||
{EExTest.Compiled,
|
||||
:unknown,
|
||||
0,
|
||||
[file: 'unknown', line: 24]
|
||||
[file: 'unknown', line: 28]
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
+102
-20
@@ -1,13 +1,14 @@
|
||||
import Kernel, except: [access: 2]
|
||||
|
||||
defprotocol Access do
|
||||
@moduledoc """
|
||||
The Access protocol is the underlying protocol invoked
|
||||
when the brackets syntax is used. For instance, `foo[bar]`
|
||||
is translated to `access foo, bar` which, by default,
|
||||
invokes the `Access.access` protocol.
|
||||
The Access protocol is used by `foo[bar]` and also
|
||||
empowers the nested update functions in Kernel.
|
||||
|
||||
This protocol is implemented by default for Lists, Maps
|
||||
For instance, `foo[bar]` translates `Access.get(foo, bar)`.
|
||||
`Kernel.get_in/2`, `Kernel.put_in/3`, `Kernel.update_in/3` and
|
||||
`Kernel.get_and_update_in/3` are also all powered by the Access
|
||||
protocol.
|
||||
|
||||
This protocol is implemented by default for keywords, maps
|
||||
and dictionary like types:
|
||||
|
||||
iex> keywords = [a: 1, b: 2]
|
||||
@@ -22,46 +23,127 @@ defprotocol Access do
|
||||
iex> star_ratings[1.5]
|
||||
"★☆"
|
||||
|
||||
The key access must be implemented using the `===` operator.
|
||||
This protocol is limited and is implemented only for the
|
||||
following built-in types: keywords, records and functions.
|
||||
The key comparison must be implemented using the `===` operator.
|
||||
"""
|
||||
|
||||
@doc """
|
||||
Receives the element being accessed and the access item.
|
||||
Accesses the given key in the container.
|
||||
"""
|
||||
def access(container, key)
|
||||
@spec get(t, term) :: t
|
||||
def get(container, key)
|
||||
|
||||
@doc """
|
||||
Gets a value and updates the given `key` in one pass.
|
||||
|
||||
The function must receive the value for the given `key`
|
||||
(or `nil` if the key doesn't exist in `container`) and
|
||||
the function must return a tuple containing the `get`
|
||||
value and the new value to be stored in the `container`.
|
||||
"""
|
||||
@spec get_and_update(t, term, (term -> {get, term})) :: {get, t} when get: var
|
||||
def get_and_update(container, key, fun)
|
||||
end
|
||||
|
||||
defimpl Access, for: List do
|
||||
def access(dict, key) when is_atom(key) do
|
||||
def get(dict, key) when is_atom(key) do
|
||||
case :lists.keyfind(key, 1, dict) do
|
||||
{^key, value} -> value
|
||||
false -> nil
|
||||
end
|
||||
end
|
||||
|
||||
def access(_dict, key) do
|
||||
raise ArgumentError, message: "the access protocol for lists expect the key to be an atom, got: #{inspect key}"
|
||||
def get(_dict, key) do
|
||||
raise ArgumentError,
|
||||
"the access protocol for lists expect the key to be an atom, got: #{inspect key}"
|
||||
end
|
||||
|
||||
def get_and_update(dict, key, fun) when is_atom(key) do
|
||||
get_and_update(dict, [], key, fun)
|
||||
end
|
||||
|
||||
defp get_and_update([{key, value}|t], acc, key, fun) do
|
||||
{get, update} = fun.(value)
|
||||
{get, :lists.reverse(acc, [{key, update}|t])}
|
||||
end
|
||||
|
||||
defp get_and_update([h|t], acc, key, fun) do
|
||||
get_and_update(t, [h|acc], key, fun)
|
||||
end
|
||||
|
||||
defp get_and_update([], acc, key, fun) do
|
||||
{get, update} = fun.(nil)
|
||||
{get, [{key, update}|:lists.reverse(acc)]}
|
||||
end
|
||||
end
|
||||
|
||||
defimpl Access, for: Map do
|
||||
def access(map, key) do
|
||||
def get(map, key) do
|
||||
case :maps.find(key, map) do
|
||||
{:ok, value} -> value
|
||||
:error -> nil
|
||||
end
|
||||
end
|
||||
|
||||
def get_and_update(map, key, fun) do
|
||||
value =
|
||||
case :maps.find(key, map) do
|
||||
{:ok, value} -> value
|
||||
:error -> nil
|
||||
end
|
||||
|
||||
{get, update} = fun.(value)
|
||||
{get, :maps.put(key, update, map)}
|
||||
end
|
||||
|
||||
def get!(%{} = map, key) do
|
||||
case :maps.find(key, map) do
|
||||
{:ok, value} -> value
|
||||
:error -> raise KeyError, key: key, term: map
|
||||
end
|
||||
end
|
||||
|
||||
def get!(other, key) do
|
||||
raise ArgumentError,
|
||||
"could not get key #{inspect key}. Expected map/struct, got: #{inspect other}"
|
||||
end
|
||||
|
||||
def get_and_update!(%{} = map, key, fun) do
|
||||
case :maps.find(key, map) do
|
||||
{:ok, value} ->
|
||||
{get, update} = fun.(value)
|
||||
{get, :maps.put(key, update, map)}
|
||||
:error ->
|
||||
raise KeyError, key: key, term: map
|
||||
end
|
||||
end
|
||||
|
||||
def get_and_update!(other, key, _fun) do
|
||||
raise ArgumentError,
|
||||
"could not update key #{inspect key}. Expected map/struct, got: #{inspect other}"
|
||||
end
|
||||
end
|
||||
|
||||
defimpl Access, for: Atom do
|
||||
def access(nil, _) do
|
||||
def get(nil, _) do
|
||||
nil
|
||||
end
|
||||
|
||||
def access(atom, _) do
|
||||
raise "The access protocol can only be invoked for atoms at " <>
|
||||
"compilation time, tried to invoke it for #{inspect atom}"
|
||||
def get(atom, _) do
|
||||
undefined(atom)
|
||||
end
|
||||
|
||||
def get_and_update(nil, _, fun) do
|
||||
fun.(nil)
|
||||
end
|
||||
|
||||
def get_and_update(atom, _key, _fun) do
|
||||
undefined(atom)
|
||||
end
|
||||
|
||||
defp undefined(atom) do
|
||||
raise Protocol.UndefinedError,
|
||||
protocol: @protocol,
|
||||
value: atom,
|
||||
description: "only the nil atom is supported"
|
||||
end
|
||||
end
|
||||
|
||||
@@ -0,0 +1,228 @@
|
||||
defmodule Agent do
|
||||
@moduledoc """
|
||||
Agents are a simple abstraction around state.
|
||||
|
||||
Often in Elixir there is a need to share or store state that
|
||||
must be accessed from different processes or by the same process
|
||||
at different points in time.
|
||||
|
||||
The Agent module provides a basic server implementation that
|
||||
allows state to be retrieved and updated via a simple API.
|
||||
|
||||
## Examples
|
||||
|
||||
For example, in the Mix tool that ships with Elixir, we need
|
||||
to keep a set of all tasks executed by a given project. Since
|
||||
this set is shared, we can implement it with an Agent:
|
||||
|
||||
defmodule Mix.TasksServer do
|
||||
def start_link do
|
||||
Agent.start_link(fn -> HashSet.new end, name: __MODULE__)
|
||||
end
|
||||
|
||||
@doc "Checks if the task has already executed"
|
||||
def executed?(task, project) do
|
||||
item = {task, project}
|
||||
Agent.get(__MODULE__, fn set ->
|
||||
item in set
|
||||
end)
|
||||
end
|
||||
|
||||
@doc "Marks a task as executed"
|
||||
def put_task(task, project) do
|
||||
item = {task, project}
|
||||
Agent.update(__MODULE__, &Set.put(&1, item))
|
||||
end
|
||||
end
|
||||
|
||||
Note that agents still provide a segregation between the
|
||||
client and server APIs, as seen in GenServers. In particular,
|
||||
all code inside the function passed to the agent is executed
|
||||
by the agent. This distinction is important because you may
|
||||
want to avoid expensive operations inside the agent, as it will
|
||||
effectively block the agent until the request is fulfilled.
|
||||
|
||||
Consider these two examples:
|
||||
|
||||
# Compute in the agent/server
|
||||
def get_something(agent) do
|
||||
Agent.get(agent, fn state -> do_something_expensive(state) end)
|
||||
end
|
||||
|
||||
# Compute in the agent/client
|
||||
def get_something(agent) do
|
||||
Agent.get(agent, &(&1)) |> do_something_expensive()
|
||||
end
|
||||
|
||||
The first one blocks the agent while the second one copies
|
||||
all the state to the client and executes the operation in the client.
|
||||
The trade-off here is exactly if the data is small enough to be
|
||||
sent to the client cheaply or large enough to require processing on
|
||||
the server (or at least some initial processing).
|
||||
|
||||
## Name Registration
|
||||
|
||||
An Agent is bound to the same name registration rules as GenServers.
|
||||
Read more about it in the `GenServer` docs.
|
||||
|
||||
## A word on distributed agents
|
||||
|
||||
It is important to consider the limitations of distributed agents. Agents
|
||||
work by sending anonymous functions between the caller and the agent.
|
||||
In a distributed setup with multiple nodes, agents only work if the caller
|
||||
(client) and the agent have the same version of a given module.
|
||||
|
||||
This setup may exhibit issues when doing "rolling upgrades". By rolling
|
||||
upgrades we mean the following situation: you wish to deploy a new version of
|
||||
your software by *shutting down* some of your nodes and replacing them with
|
||||
nodes running a new version of the software. In this setup, part of your
|
||||
environment will have one version of a given module and the other part
|
||||
another version (the newer one) of the same module; this may cause agents to
|
||||
crash. That said, if you plan to run in distributed environments, agents
|
||||
should likely be avoided.
|
||||
|
||||
Note, however, that agents work fine if you want to perform hot code
|
||||
swapping, as it keeps both the old and new versions of a given module.
|
||||
We detail how to do hot code swapping with agents in the next section.
|
||||
|
||||
## Hot code swapping
|
||||
|
||||
An agent can have its code hot swapped live by simply passing a module,
|
||||
function and args tuple to the update instruction. For example, imagine
|
||||
you have an agent named `:sample` and you want to convert its inner state
|
||||
from some dict structure to a map. It can be done with the following
|
||||
instruction:
|
||||
|
||||
{:update, :sample, {:advanced, {Enum, :into, [%{}]}}}
|
||||
|
||||
The agent's state will be added to the given list as the first argument.
|
||||
"""
|
||||
|
||||
@typedoc "Return values of `start*` functions"
|
||||
@type on_start :: {:ok, pid} | {:error, {:already_started, pid} | term}
|
||||
|
||||
@typedoc "The agent name"
|
||||
@type name :: atom | {:global, term} | {:via, module, term}
|
||||
|
||||
@typedoc "The agent reference"
|
||||
@type agent :: pid | {atom, node} | name
|
||||
|
||||
@typedoc "The agent state"
|
||||
@type state :: term
|
||||
|
||||
@doc """
|
||||
Starts an agent linked to the current process.
|
||||
|
||||
This is often used to start the agent as part of a supervision tree.
|
||||
|
||||
Once the agent is spawned, the given function is invoked and its return
|
||||
value is used as the agent state. Note that `start_link` does not return
|
||||
until the given function has returned.
|
||||
|
||||
## Options
|
||||
|
||||
The `:name` option is used for registration as described in the module
|
||||
documentation.
|
||||
|
||||
If the `:timeout` option is present, the agent is allowed to spend at most
|
||||
the given number of milliseconds on initialization or it will be terminated
|
||||
and the start function will return `{:error, :timeout}`.
|
||||
|
||||
If the `:debug` option is present, the corresponding function in the
|
||||
[`:sys` module](http://www.erlang.org/doc/man/sys.html) will be invoked.
|
||||
|
||||
If the `:spawn_opt` option is present, its value will be passed as options
|
||||
to the underlying process as in `Process.spawn/4`.
|
||||
|
||||
## Return values
|
||||
|
||||
If the server is successfully created and initialized, the function returns
|
||||
`{:ok, pid}`, where `pid` is the pid of the server. If there already exists
|
||||
an agent with the specified name, the function returns
|
||||
`{:error, {:already_started, pid}}` with the pid of that process.
|
||||
|
||||
If the given function callback fails with `reason`, the function returns
|
||||
`{:error, reason}`.
|
||||
"""
|
||||
@spec start_link((() -> term), GenServer.options) :: on_start
|
||||
def start_link(fun, options \\ []) when is_function(fun, 0) do
|
||||
GenServer.start_link(Agent.Server, fun, options)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Starts an agent process without links (outside of a supervision tree).
|
||||
|
||||
See `start_link/2` for more information.
|
||||
"""
|
||||
@spec start((() -> term), GenServer.options) :: on_start
|
||||
def start(fun, options \\ []) when is_function(fun, 0) do
|
||||
GenServer.start(Agent.Server, fun, options)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Gets the agent value and executes the given function.
|
||||
|
||||
The function `fun` is sent to the `agent` which invokes the function
|
||||
passing the agent state. The result of the function invocation is
|
||||
returned.
|
||||
|
||||
A timeout can also be specified (it has a default value of 5000).
|
||||
"""
|
||||
@spec get(agent, (state -> a), timeout) :: a when a: var
|
||||
def get(agent, fun, timeout \\ 5000) when is_function(fun, 1) do
|
||||
GenServer.call(agent, {:get, fun}, timeout)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Gets and updates the agent state in one operation.
|
||||
|
||||
The function `fun` is sent to the `agent` which invokes the function
|
||||
passing the agent state. The function must return a tuple with two
|
||||
elements, the first being the value to return (i.e. the `get` value)
|
||||
and the second one is the new state.
|
||||
|
||||
A timeout can also be specified (it has a default value of 5000).
|
||||
"""
|
||||
@spec get_and_update(agent, (state -> {a, state}), timeout) :: a when a: var
|
||||
def get_and_update(agent, fun, timeout \\ 5000) when is_function(fun, 1) do
|
||||
GenServer.call(agent, {:get_and_update, fun}, timeout)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Updates the agent state.
|
||||
|
||||
The function `fun` is sent to the `agent` which invokes the function
|
||||
passing the agent state. The function must return the new state.
|
||||
|
||||
A timeout can also be specified (it has a default value of 5000).
|
||||
This function always returns `:ok`.
|
||||
"""
|
||||
@spec update(agent, (state -> state)) :: :ok
|
||||
def update(agent, fun, timeout \\ 5000) when is_function(fun, 1) do
|
||||
GenServer.call(agent, {:update, fun}, timeout)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Performs a cast (fire and forget) operation on the agent state.
|
||||
|
||||
The function `fun` is sent to the `agent` which invokes the function
|
||||
passing the agent state. The function must return the new state.
|
||||
|
||||
Note that `cast` returns `:ok` immediately, regardless of whether the
|
||||
destination node or agent exists.
|
||||
"""
|
||||
@spec cast(agent, (state -> state)) :: :ok
|
||||
def cast(agent, fun) when is_function(fun, 1) do
|
||||
GenServer.cast(agent, fun)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Stops the agent.
|
||||
|
||||
Returns `:ok` if the agent is stopped within the given `timeout`.
|
||||
"""
|
||||
@spec stop(agent, timeout) :: :ok
|
||||
def stop(agent, timeout \\ 5000) do
|
||||
GenServer.call(agent, :stop, timeout)
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,53 @@
|
||||
defmodule Agent.Server do
|
||||
@moduledoc false
|
||||
|
||||
use GenServer
|
||||
|
||||
def init(fun) do
|
||||
{:ok, fun.()}
|
||||
end
|
||||
|
||||
def handle_call({:get, fun}, _from, state) do
|
||||
{:reply, fun.(state), state}
|
||||
end
|
||||
|
||||
def handle_call({:get_and_update, fun}, _from, state) do
|
||||
{reply, state} = fun.(state)
|
||||
{:reply, reply, state}
|
||||
end
|
||||
|
||||
def handle_call({:update, fun}, _from, state) do
|
||||
{:reply, :ok, fun.(state)}
|
||||
end
|
||||
|
||||
def handle_call(:stop, _from, state) do
|
||||
{:stop, :normal, :ok, state}
|
||||
end
|
||||
|
||||
def handle_call(msg, from, state) do
|
||||
super(msg, from, state)
|
||||
end
|
||||
|
||||
def handle_cast(fun, state) when is_function(fun, 1) do
|
||||
{:noreply, fun.(state)}
|
||||
end
|
||||
|
||||
def handle_cast(msg, state) do
|
||||
super(msg, state)
|
||||
end
|
||||
|
||||
def code_change(_old, state, { m, f, a }) do
|
||||
{:ok, apply(m, f, [state|a])}
|
||||
end
|
||||
|
||||
def terminate(_reason, _state) do
|
||||
# There is a race condition if the agent is
|
||||
# restarted too fast and it is registered.
|
||||
try do
|
||||
self |> :erlang.process_info(:registered_name) |> elem(1) |> Process.unregister
|
||||
rescue
|
||||
_ -> :ok
|
||||
end
|
||||
:ok
|
||||
end
|
||||
end
|
||||
@@ -13,7 +13,7 @@ defmodule Application do
|
||||
|
||||
In Elixir, Mix is responsible for compiling your source code and
|
||||
generating your application `.app` file. Furthermore, Mix is also
|
||||
responsible for configuring, starting and stoping your application
|
||||
responsible for configuring, starting and stopping your application
|
||||
and its dependencies. For this reason, this documentation will focus
|
||||
on the remaining aspects of your application: the application environment,
|
||||
and the application callback module.
|
||||
@@ -48,7 +48,7 @@ defmodule Application do
|
||||
developers to configure the environment of their dependencies.
|
||||
|
||||
Keep in mind that each application is responsible for its environment.
|
||||
Do not use the fucntions in this module for directly access or modify
|
||||
Do not use the functions in this module for directly access or modify
|
||||
the environment of other application (as it may lead to inconsistent
|
||||
data in the application environment).
|
||||
|
||||
@@ -83,8 +83,8 @@ defmodule Application do
|
||||
setup where applications takeover and failovers are configured. This particular
|
||||
aspect of applications can be read with more detail in the OTP documentation:
|
||||
|
||||
* http://www.erlang.org/doc/man/application.html
|
||||
* http://www.erlang.org/doc/design_principles/applications.html
|
||||
* http://www.erlang.org/doc/man/application.html
|
||||
* http://www.erlang.org/doc/design_principles/applications.html
|
||||
|
||||
A developer may also implement the `stop/1` callback (automatically defined
|
||||
by `use Application`) which does any application cleanup. It receives the
|
||||
@@ -152,9 +152,8 @@ defmodule Application do
|
||||
|
||||
## Options
|
||||
|
||||
* `:timeout` - the timeout for the change (defaults to 5000ms);
|
||||
|
||||
* `:persistent` - persists the given value on application load and reloads;
|
||||
* `:timeout` - the timeout for the change (defaults to 5000ms)
|
||||
* `:persistent` - persists the given value on application load and reloads
|
||||
|
||||
If `put_env/4` is called before the application is loaded, the application
|
||||
environment values specified in the `.app` file will override the ones
|
||||
@@ -223,13 +222,16 @@ defmodule Application do
|
||||
|
||||
The `type` argument specifies the type of the application:
|
||||
|
||||
* `:permanent` - if `app` terminates, all other applications and the entire
|
||||
node are also terminated;
|
||||
* `:transient` - if `app` terminates with `:normal` reason, it is reported
|
||||
but no other applications are terminated. If a transient application terminates
|
||||
abnormally, all other applications and the entire node are also terminated;
|
||||
* `:temporary` - if `app` termiantes, it is reported but no other applications
|
||||
are terminated (the default);
|
||||
* `:permanent` - if `app` terminates, all other applications and the entire
|
||||
node are also terminated.
|
||||
|
||||
* `:transient` - if `app` terminates with `:normal` reason, it is reported
|
||||
but no other applications are terminated. If a transient application
|
||||
terminates abnormally, all other applications and the entire node are
|
||||
also terminated.
|
||||
|
||||
* `:temporary` - if `app` terminates, it is reported but no other
|
||||
applications are terminated (the default).
|
||||
|
||||
Note that it is always possible to stop an application explicitly by calling
|
||||
`stop/1`. Regardless of the type of the application, no other applications will
|
||||
@@ -246,7 +248,7 @@ defmodule Application do
|
||||
@doc """
|
||||
Stops the given `app`.
|
||||
|
||||
When stopped, the application is still loaded.
|
||||
When stopped, the application is still loaded.
|
||||
"""
|
||||
@spec stop(app) :: :ok | {:error, term}
|
||||
def stop(app) do
|
||||
@@ -305,8 +307,8 @@ defmodule Application do
|
||||
@spec app_dir(app) :: String.t
|
||||
def app_dir(app) when is_atom(app) do
|
||||
case :code.lib_dir(app) do
|
||||
lib when is_list(lib) -> String.from_char_data!(lib)
|
||||
{:error, :bad_name} -> raise ArgumentError, message: "unknown application: #{inspect app}"
|
||||
lib when is_list(lib) -> IO.chardata_to_string(lib)
|
||||
{:error, :bad_name} -> raise ArgumentError, "unknown application: #{inspect app}"
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -1,17 +0,0 @@
|
||||
defmodule Application.Behaviour do
|
||||
@moduledoc false
|
||||
|
||||
defmacro __using__(_) do
|
||||
# IO.write :stderr, "use Application.Behaviour is deprecated, please use Application instead\n#{Exception.format_stacktrace}"
|
||||
quote location: :keep do
|
||||
@behaviour :application
|
||||
|
||||
@doc false
|
||||
def stop(_state) do
|
||||
:ok
|
||||
end
|
||||
|
||||
defoverridable [stop: 1]
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,25 @@
|
||||
defmodule Atom do
|
||||
@doc """
|
||||
Convenience functions for working with atoms.
|
||||
"""
|
||||
|
||||
@doc """
|
||||
Converts an atom to string.
|
||||
|
||||
Inlined by the compiler.
|
||||
"""
|
||||
@spec to_string(atom) :: String.t
|
||||
def to_string(atom) do
|
||||
:erlang.atom_to_binary(atom, :utf8)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Converts an atom to a char list.
|
||||
|
||||
Inlined by the compiler.
|
||||
"""
|
||||
@spec to_char_list(atom) :: char_list
|
||||
def to_char_list(atom) do
|
||||
:erlang.atom_to_list(atom)
|
||||
end
|
||||
end
|
||||
+230
-102
@@ -5,8 +5,91 @@ defmodule Base do
|
||||
This module provides data encoding and decoding functions
|
||||
according to [RFC 4648](http://tools.ietf.org/html/rfc4648).
|
||||
|
||||
This document defines the commonly used base 64, base 32, and base
|
||||
16 encoding schemes.
|
||||
This document defines the commonly used base 16, base 32, and base
|
||||
64 encoding schemes.
|
||||
|
||||
## Base 16 alphabet
|
||||
|
||||
| Value | Encoding | Value | Encoding | Value | Encoding | Value | Encoding |
|
||||
|------:|---------:|------:|---------:|------:|---------:|------:|---------:|
|
||||
| 0| 0| 4| 4| 8| 8| 12| C|
|
||||
| 1| 1| 5| 5| 9| 9| 13| D|
|
||||
| 2| 2| 6| 6| 10| A| 14| E|
|
||||
| 3| 3| 7| 7| 11| B| 15| F|
|
||||
|
||||
## Base 32 alphabet
|
||||
|
||||
| Value | Encoding | Value | Encoding | Value | Encoding | Value | Encoding |
|
||||
|------:|---------:|------:|---------:|------:|---------:|------:|---------:|
|
||||
| 0| A| 9| J| 18| S| 27| 3|
|
||||
| 1| B| 10| K| 19| T| 28| 4|
|
||||
| 2| C| 11| L| 20| U| 29| 5|
|
||||
| 3| D| 12| M| 21| V| 30| 6|
|
||||
| 4| E| 13| N| 22| W| 31| 7|
|
||||
| 5| F| 14| O| 23| X| | |
|
||||
| 6| G| 15| P| 24| Y| (pad)| =|
|
||||
| 7| H| 16| Q| 25| Z| | |
|
||||
| 8| I| 17| R| 26| 2| | |
|
||||
|
||||
|
||||
## Base 32 (extended hex) alphabet
|
||||
|
||||
| Value | Encoding | Value | Encoding | Value | Encoding | Value | Encoding |
|
||||
|------:|---------:|------:|---------:|------:|---------:|------:|---------:|
|
||||
| 0| 0| 9| 9| 18| I| 27| R|
|
||||
| 1| 1| 10| A| 19| J| 28| S|
|
||||
| 2| 2| 11| B| 20| K| 29| T|
|
||||
| 3| 3| 12| C| 21| L| 30| U|
|
||||
| 4| 4| 13| D| 22| M| 31| V|
|
||||
| 5| 5| 14| E| 23| N| | |
|
||||
| 6| 6| 15| F| 24| O| (pad)| =|
|
||||
| 7| 7| 16| G| 25| P| | |
|
||||
| 8| 8| 17| H| 26| Q| | |
|
||||
|
||||
## Base 64 alphabet
|
||||
|
||||
| Value | Encoding | Value | Encoding | Value | Encoding | Value | Encoding |
|
||||
|------:|---------:|------:|---------:|------:|---------:|------:|---------:|
|
||||
| 0| A| 17| R| 34| i| 51| z|
|
||||
| 1| B| 18| S| 35| j| 52| 0|
|
||||
| 2| C| 19| T| 36| k| 53| 1|
|
||||
| 3| D| 20| U| 37| l| 54| 2|
|
||||
| 4| E| 21| V| 38| m| 55| 3|
|
||||
| 5| F| 22| W| 39| n| 56| 4|
|
||||
| 6| G| 23| X| 40| o| 57| 5|
|
||||
| 7| H| 24| Y| 41| p| 58| 6|
|
||||
| 8| I| 25| Z| 42| q| 59| 7|
|
||||
| 9| J| 26| a| 43| r| 60| 8|
|
||||
| 10| K| 27| b| 44| s| 61| 9|
|
||||
| 11| L| 28| c| 45| t| 62| +|
|
||||
| 12| M| 29| d| 46| u| 63| /|
|
||||
| 13| N| 30| e| 47| v| | |
|
||||
| 14| O| 31| f| 48| w| (pad)| =|
|
||||
| 15| P| 32| g| 49| x| | |
|
||||
| 16| Q| 33| h| 50| y| | |
|
||||
|
||||
## Base 64 (URL and filename safe) alphabet
|
||||
|
||||
| Value | Encoding | Value | Encoding | Value | Encoding | Value | Encoding |
|
||||
|------:|---------:|------:|---------:|------:|---------:|------:|---------:|
|
||||
| 0| A| 17| R| 34| i| 51| z|
|
||||
| 1| B| 18| S| 35| j| 52| 0|
|
||||
| 2| C| 19| T| 36| k| 53| 1|
|
||||
| 3| D| 20| U| 37| l| 54| 2|
|
||||
| 4| E| 21| V| 38| m| 55| 3|
|
||||
| 5| F| 22| W| 39| n| 56| 4|
|
||||
| 6| G| 23| X| 40| o| 57| 5|
|
||||
| 7| H| 24| Y| 41| p| 58| 6|
|
||||
| 8| I| 25| Z| 42| q| 59| 7|
|
||||
| 9| J| 26| a| 43| r| 60| 8|
|
||||
| 10| K| 27| b| 44| s| 61| 9|
|
||||
| 11| L| 28| c| 45| t| 62| -|
|
||||
| 12| M| 29| d| 46| u| 63| _|
|
||||
| 13| N| 30| e| 47| v| | |
|
||||
| 14| O| 31| f| 48| w| (pad)| =|
|
||||
| 15| P| 32| g| 49| x| | |
|
||||
| 16| Q| 33| h| 50| y| | |
|
||||
|
||||
"""
|
||||
|
||||
b16_alphabet = Enum.with_index '0123456789ABCDEF'
|
||||
@@ -25,45 +108,86 @@ defmodule Base do
|
||||
defp unquote(dec)(unquote(encoding)), do: unquote(value)
|
||||
end
|
||||
defp unquote(dec)(c) do
|
||||
raise ArgumentError, message: "non-alphabet digit found: #{<<c>>}"
|
||||
raise ArgumentError, "non-alphabet digit found: #{<<c>>}"
|
||||
end
|
||||
end
|
||||
|
||||
defp encode_case(:upper, func),
|
||||
do: func
|
||||
defp encode_case(:lower, func),
|
||||
do: &to_lower(func.(&1))
|
||||
|
||||
defp decode_case(:upper, func),
|
||||
do: func
|
||||
defp decode_case(:lower, func),
|
||||
do: &func.(from_lower(&1))
|
||||
defp decode_case(:mixed, func),
|
||||
do: &func.(from_mixed(&1))
|
||||
|
||||
defp to_lower(char) when char in ?A..?Z,
|
||||
do: char + (?a - ?A)
|
||||
defp to_lower(char),
|
||||
do: char
|
||||
|
||||
defp from_lower(char) when char in ?a..?z,
|
||||
do: char - (?a - ?A)
|
||||
defp from_lower(char) when not char in ?A..?Z,
|
||||
do: char
|
||||
defp from_lower(char),
|
||||
do: raise(ArgumentError, "non-alphabet digit found: #{<<char>>}")
|
||||
|
||||
defp from_mixed(char) when char in ?a..?z,
|
||||
do: char - (?a - ?A)
|
||||
defp from_mixed(char),
|
||||
do: char
|
||||
|
||||
@doc """
|
||||
Encodes a binary string into a base 16 encoded string.
|
||||
|
||||
Accepts an atom `:upper` (default) for encoding to upper case characters or
|
||||
`:lower` for lower case characters.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Base.encode16("foobar")
|
||||
"666F6F626172"
|
||||
|
||||
iex> Base.encode16("foobar", case: :lower)
|
||||
"666f6f626172"
|
||||
|
||||
"""
|
||||
@spec encode16(binary) :: binary
|
||||
def encode16(data) when is_binary(data) do
|
||||
do_encode16(data, &enc16/1)
|
||||
@spec encode16(binary, Keyword.t) :: binary
|
||||
def encode16(data, opts \\ []) when is_binary(data) do
|
||||
case = Keyword.get(opts, :case, :upper)
|
||||
do_encode16(data, encode_case(case, &enc16/1))
|
||||
end
|
||||
|
||||
|
||||
@doc """
|
||||
Decodes a base 16 encoded string into a binary string.
|
||||
|
||||
The following alphabet is used both for encoding and decoding:
|
||||
|
||||
| Value | Encoding | Value | Encoding | Value | Encoding | Value | Encoding |
|
||||
|------:|---------:|------:|---------:|------:|---------:|------:|---------:|
|
||||
| 0| 0| 4| 4| 8| 8| 12| C|
|
||||
| 1| 1| 5| 5| 9| 9| 13| D|
|
||||
| 2| 2| 6| 6| 10| A| 14| E|
|
||||
| 3| 3| 7| 7| 11| B| 15| F|
|
||||
Accepts an atom `:upper` (default) for decoding from upper case characters or
|
||||
`:lower` for lower case characters. `:mixed` can be given for mixed case
|
||||
characters.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Base.decode16("666F6F626172")
|
||||
{:ok, "foobar"}
|
||||
|
||||
iex> Base.decode16("666f6f626172", case: :lower)
|
||||
{:ok, "foobar"}
|
||||
|
||||
iex> Base.decode16("666f6F626172", case: :mixed)
|
||||
{:ok, "foobar"}
|
||||
|
||||
"""
|
||||
@spec decode16(binary) :: {:ok, binary} | :error
|
||||
def decode16(string) when is_binary(string) do
|
||||
{:ok, decode16!(string)}
|
||||
@spec decode16(binary, Keyword.t) :: {:ok, binary} | :error
|
||||
def decode16(string, opts \\ []) when is_binary(string) do
|
||||
case = Keyword.get(opts, :case, :upper)
|
||||
{:ok, do_decode16(string, decode_case(case, &dec16/1))}
|
||||
rescue
|
||||
ArgumentError -> :error
|
||||
end
|
||||
@@ -71,6 +195,10 @@ defmodule Base do
|
||||
@doc """
|
||||
Decodes a base 16 encoded string into a binary string.
|
||||
|
||||
Accepts an atom `:upper` (default) for decoding from upper case characters or
|
||||
`:lower` for lower case characters. `:mixed` can be given for mixed case
|
||||
characters.
|
||||
|
||||
An `ArgumentError` exception is raised if the padding is incorrect or
|
||||
a non-alphabet character is present in the string.
|
||||
|
||||
@@ -79,10 +207,18 @@ defmodule Base do
|
||||
iex> Base.decode16!("666F6F626172")
|
||||
"foobar"
|
||||
|
||||
iex> Base.decode16!("666f6f626172", case: :lower)
|
||||
"foobar"
|
||||
|
||||
iex> Base.decode16!("666f6F626172", case: :mixed)
|
||||
"foobar"
|
||||
|
||||
"""
|
||||
@spec decode16!(binary) :: binary
|
||||
def decode16!(string) when is_binary(string) do
|
||||
do_decode16(string, &dec16/1)
|
||||
@spec decode16!(binary, Keyword.t) :: binary
|
||||
def decode16!(string, opts \\ []) when is_binary(string) do
|
||||
case = Keyword.get(opts, :case, :upper)
|
||||
do_decode16(string, decode_case(case, &dec16/1))
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -102,28 +238,6 @@ defmodule Base do
|
||||
@doc """
|
||||
Decodes a base 64 encoded string into a binary string.
|
||||
|
||||
The following alphabet is used both for encoding and decoding:
|
||||
|
||||
| Value | Encoding | Value | Encoding | Value | Encoding | Value | Encoding |
|
||||
|------:|---------:|------:|---------:|------:|---------:|------:|---------:|
|
||||
| 0| A| 17| R| 34| i| 51| z|
|
||||
| 1| B| 18| S| 35| j| 52| 0|
|
||||
| 2| C| 19| T| 36| k| 53| 1|
|
||||
| 3| D| 20| U| 37| l| 54| 2|
|
||||
| 4| E| 21| V| 38| m| 55| 3|
|
||||
| 5| F| 22| W| 39| n| 56| 4|
|
||||
| 6| G| 23| X| 40| o| 57| 5|
|
||||
| 7| H| 24| Y| 41| p| 58| 6|
|
||||
| 8| I| 25| Z| 42| q| 59| 7|
|
||||
| 9| J| 26| a| 43| r| 60| 8|
|
||||
| 10| K| 27| b| 44| s| 61| 9|
|
||||
| 11| L| 28| c| 45| t| 62| +|
|
||||
| 12| M| 29| d| 46| u| 63| /|
|
||||
| 13| N| 30| e| 47| v| | |
|
||||
| 14| O| 31| f| 48| w| (pad)| =|
|
||||
| 15| P| 32| g| 49| x| | |
|
||||
| 16| Q| 33| h| 50| y| | |
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Base.decode64("Zm9vYmFy")
|
||||
@@ -175,28 +289,6 @@ defmodule Base do
|
||||
Decodes a base 64 encoded string with URL and filename safe alphabet
|
||||
into a binary string.
|
||||
|
||||
The following alphabet is used both for encoding and decoding:
|
||||
|
||||
| Value | Encoding | Value | Encoding | Value | Encoding | Value | Encoding |
|
||||
|------:|---------:|------:|---------:|------:|---------:|------:|---------:|
|
||||
| 0| A| 17| R| 34| i| 51| z|
|
||||
| 1| B| 18| S| 35| j| 52| 0|
|
||||
| 2| C| 19| T| 36| k| 53| 1|
|
||||
| 3| D| 20| U| 37| l| 54| 2|
|
||||
| 4| E| 21| V| 38| m| 55| 3|
|
||||
| 5| F| 22| W| 39| n| 56| 4|
|
||||
| 6| G| 23| X| 40| o| 57| 5|
|
||||
| 7| H| 24| Y| 41| p| 58| 6|
|
||||
| 8| I| 25| Z| 42| q| 59| 7|
|
||||
| 9| J| 26| a| 43| r| 60| 8|
|
||||
| 10| K| 27| b| 44| s| 61| 9|
|
||||
| 11| L| 28| c| 45| t| 62| -|
|
||||
| 12| M| 29| d| 46| u| 63| _|
|
||||
| 13| N| 30| e| 47| v| | |
|
||||
| 14| O| 31| f| 48| w| (pad)| =|
|
||||
| 15| P| 32| g| 49| x| | |
|
||||
| 16| Q| 33| h| 50| y| | |
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Base.url_decode64("_3_-_A==")
|
||||
@@ -231,43 +323,49 @@ defmodule Base do
|
||||
@doc """
|
||||
Encodes a binary string into a base 32 encoded string.
|
||||
|
||||
Accepts an atom `:upper` (default) for encoding to upper case characters or
|
||||
`:lower` for lower case characters.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Base.encode32("foobar")
|
||||
"MZXW6YTBOI======"
|
||||
|
||||
iex> Base.encode32("foobar", case: :lower)
|
||||
"mzxw6ytboi======"
|
||||
|
||||
"""
|
||||
@spec encode32(binary) :: binary
|
||||
def encode32(data) when is_binary(data) do
|
||||
do_encode32(data, &enc32/1)
|
||||
@spec encode32(binary, Keyword.t) :: binary
|
||||
def encode32(data, opts \\ []) when is_binary(data) do
|
||||
case = Keyword.get(opts, :case, :upper)
|
||||
do_encode32(data, encode_case(case, &enc32/1))
|
||||
end
|
||||
|
||||
@doc """
|
||||
Decodes a base 32 encoded string into a binary string.
|
||||
|
||||
The following alphabet is used both for encoding and decoding:
|
||||
|
||||
| Value | Encoding | Value | Encoding | Value | Encoding | Value | Encoding |
|
||||
|------:|---------:|------:|---------:|------:|---------:|------:|---------:|
|
||||
| 0| A| 9| J| 18| S| 27| 3|
|
||||
| 1| B| 10| K| 19| T| 28| 4|
|
||||
| 2| C| 11| L| 20| U| 29| 5|
|
||||
| 3| D| 12| M| 21| V| 30| 6|
|
||||
| 4| E| 13| N| 22| W| 31| 7|
|
||||
| 5| F| 14| O| 23| X| | |
|
||||
| 6| G| 15| P| 24| Y| (pad)| =|
|
||||
| 7| H| 16| Q| 25| Z| | |
|
||||
| 8| I| 17| R| 26| 2| | |
|
||||
Accepts an atom `:upper` (default) for decoding from upper case characters or
|
||||
`:lower` for lower case characters. `:mixed` can be given for mixed case
|
||||
characters.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Base.decode32("MZXW6YTBOI======")
|
||||
{:ok, "foobar"}
|
||||
|
||||
iex> Base.decode32("mzxw6ytboi======", case: :lower)
|
||||
{:ok, "foobar"}
|
||||
|
||||
iex> Base.decode32("mzXW6ytBOi======", case: :mixed)
|
||||
{:ok, "foobar"}
|
||||
|
||||
"""
|
||||
@spec decode32(binary) :: {:ok, binary} | :error
|
||||
def decode32(string) do
|
||||
{:ok, do_decode32(string, &dec32/1)}
|
||||
@spec decode32(binary, Keyword.t) :: {:ok, binary} | :error
|
||||
def decode32(string, opts \\ []) do
|
||||
case = Keyword.get(opts, :case, :upper)
|
||||
{:ok, do_decode32(string, decode_case(case, &dec32/1))}
|
||||
rescue
|
||||
ArgumentError -> :error
|
||||
end
|
||||
@@ -275,6 +373,10 @@ defmodule Base do
|
||||
@doc """
|
||||
Decodes a base 32 encoded string into a binary string.
|
||||
|
||||
Accepts an atom `:upper` (default) for decoding from upper case characters or
|
||||
`:lower` for lower case characters. `:mixed` can be given for mixed case
|
||||
characters.
|
||||
|
||||
An `ArgumentError` exception is raised if the padding is incorrect or
|
||||
a non-alphabet character is present in the string.
|
||||
|
||||
@@ -283,54 +385,68 @@ defmodule Base do
|
||||
iex> Base.decode32!("MZXW6YTBOI======")
|
||||
"foobar"
|
||||
|
||||
iex> Base.decode32!("mzxw6ytboi======", case: :lower)
|
||||
"foobar"
|
||||
|
||||
iex> Base.decode32!("mzXW6ytBOi======", case: :mixed)
|
||||
"foobar"
|
||||
|
||||
"""
|
||||
@spec decode32!(binary) :: binary
|
||||
def decode32!(string) do
|
||||
do_decode32(string, &dec32/1)
|
||||
@spec decode32!(binary, Keyword.t) :: binary
|
||||
def decode32!(string, opts \\ []) do
|
||||
case = Keyword.get(opts, :case, :upper)
|
||||
do_decode32(string, decode_case(case, &dec32/1))
|
||||
end
|
||||
|
||||
@doc """
|
||||
Encodes a binary string into a base 32 encoded string with an
|
||||
extended hexadecimal alphabet.
|
||||
|
||||
Accepts an atom `:upper` (default) for encoding to upper case characters or
|
||||
`:lower` for lower case characters.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Base.hex_encode32("foobar")
|
||||
"CPNMUOJ1E8======"
|
||||
|
||||
iex> Base.hex_encode32("foobar", case: :lower)
|
||||
"cpnmuoj1e8======"
|
||||
|
||||
"""
|
||||
@spec hex_encode32(binary) :: binary
|
||||
def hex_encode32(data) when is_binary(data) do
|
||||
do_encode32(data, &enc32hex/1)
|
||||
@spec hex_encode32(binary, Keyword.t) :: binary
|
||||
def hex_encode32(data, opts \\ []) when is_binary(data) do
|
||||
case = Keyword.get(opts, :case, :upper)
|
||||
do_encode32(data, encode_case(case, &enc32hex/1))
|
||||
end
|
||||
|
||||
@doc """
|
||||
Decodes a base 32 encoded string with extended hexadecimal alphabet
|
||||
into a binary string.
|
||||
|
||||
The following alphabet is used both for encoding and decoding:
|
||||
|
||||
| Value | Encoding | Value | Encoding | Value | Encoding | Value | Encoding |
|
||||
|------:|---------:|------:|---------:|------:|---------:|------:|---------:|
|
||||
| 0| 0| 9| 9| 18| I| 27| R|
|
||||
| 1| 1| 10| A| 19| J| 28| S|
|
||||
| 2| 2| 11| B| 20| K| 29| T|
|
||||
| 3| 3| 12| C| 21| L| 30| U|
|
||||
| 4| 4| 13| D| 22| M| 31| V|
|
||||
| 5| 5| 14| E| 23| N| | |
|
||||
| 6| 6| 15| F| 24| O| (pad)| =|
|
||||
| 7| 7| 16| G| 25| P| | |
|
||||
| 8| 8| 17| H| 26| Q| | |
|
||||
Accepts an atom `:upper` (default) for decoding from upper case characters or
|
||||
`:lower` for lower case characters. `:mixed` can be given for mixed case
|
||||
characters.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Base.hex_decode32("CPNMUOJ1E8======")
|
||||
{:ok, "foobar"}
|
||||
|
||||
iex> Base.hex_decode32("cpnmuoj1e8======", case: :lower)
|
||||
{:ok, "foobar"}
|
||||
|
||||
iex> Base.hex_decode32("cpnMuOJ1E8======", case: :mixed)
|
||||
{:ok, "foobar"}
|
||||
|
||||
"""
|
||||
@spec hex_decode32(binary) :: {:ok, binary} | :error
|
||||
def hex_decode32(string) when is_binary(string) do
|
||||
{:ok, do_decode32(string, &dec32hex/1)}
|
||||
@spec hex_decode32(binary, Keyword.t) :: {:ok, binary} | :error
|
||||
def hex_decode32(string, opts \\ []) when is_binary(string) do
|
||||
case = Keyword.get(opts, :case, :upper)
|
||||
{:ok, do_decode32(string, decode_case(case, &dec32hex/1))}
|
||||
rescue
|
||||
ArgumentError -> :error
|
||||
end
|
||||
@@ -339,6 +455,10 @@ defmodule Base do
|
||||
Decodes a base 32 encoded string with extended hexadecimal alphabet
|
||||
into a binary string.
|
||||
|
||||
Accepts an atom `:upper` (default) for decoding from upper case characters or
|
||||
`:lower` for lower case characters. `:mixed` can be given for mixed case
|
||||
characters.
|
||||
|
||||
An `ArgumentError` exception is raised if the padding is incorrect or
|
||||
a non-alphabet character is present in the string.
|
||||
|
||||
@@ -347,10 +467,18 @@ defmodule Base do
|
||||
iex> Base.hex_decode32!("CPNMUOJ1E8======")
|
||||
"foobar"
|
||||
|
||||
iex> Base.hex_decode32!("cpnmuoj1e8======", case: :lower)
|
||||
"foobar"
|
||||
|
||||
iex> Base.hex_decode32!("cpnMuOJ1E8======", case: :mixed)
|
||||
"foobar"
|
||||
|
||||
"""
|
||||
@spec hex_decode32!(binary) :: binary
|
||||
def hex_decode32!(string) when is_binary(string) do
|
||||
do_decode32(string, &dec32hex/1)
|
||||
@spec hex_decode32!(binary, Keyword.t) :: binary
|
||||
def hex_decode32!(string, opts \\ []) when is_binary(string) do
|
||||
case = Keyword.get(opts, :case, :upper)
|
||||
do_decode32(string, decode_case(case, &dec32hex/1))
|
||||
end
|
||||
|
||||
defp do_encode16(<<>>, _), do: <<>>
|
||||
@@ -365,7 +493,7 @@ defmodule Base do
|
||||
end
|
||||
end
|
||||
defp do_decode16(_, _) do
|
||||
raise ArgumentError, message: "odd-length string"
|
||||
raise ArgumentError, "odd-length string"
|
||||
end
|
||||
|
||||
defp do_encode64(<<>>, _), do: <<>>
|
||||
@@ -400,7 +528,7 @@ defmodule Base do
|
||||
end
|
||||
end
|
||||
defp do_decode64(_, _) do
|
||||
raise ArgumentError, message: "incorrect padding"
|
||||
raise ArgumentError, "incorrect padding"
|
||||
end
|
||||
|
||||
defp do_encode32(<<>>, _), do: <<>>
|
||||
@@ -458,7 +586,7 @@ defmodule Base do
|
||||
end
|
||||
end
|
||||
defp do_decode32(_, _) do
|
||||
raise ArgumentError, message: "incorrect padding"
|
||||
raise ArgumentError, "incorrect padding"
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
@@ -78,7 +78,7 @@ defmodule Behaviour do
|
||||
{name, args} ->
|
||||
do_callback(:def, name, args, name, length(args), args, return, guards, caller)
|
||||
_ ->
|
||||
raise ArgumentError, message: "invalid syntax in defcallback #{Macro.to_string(spec)}"
|
||||
raise ArgumentError, "invalid syntax in defcallback #{Macro.to_string(spec)}"
|
||||
end
|
||||
end
|
||||
|
||||
@@ -88,7 +88,7 @@ defmodule Behaviour do
|
||||
do_callback(:defmacro, :"MACRO-#{name}", [quote(do: env :: Macro.Env.t)|args],
|
||||
name, length(args), args, return, guards, caller)
|
||||
_ ->
|
||||
raise ArgumentError, message: "invalid syntax in defmacrocallback #{Macro.to_string(spec)}"
|
||||
raise ArgumentError, "invalid syntax in defmacrocallback #{Macro.to_string(spec)}"
|
||||
end
|
||||
end
|
||||
|
||||
@@ -111,7 +111,7 @@ defmodule Behaviour do
|
||||
end
|
||||
|
||||
defp ensure_not_default({:\\, _, [_, _]}) do
|
||||
raise ArgumentError, message: "default arguments \\\\ not supported in defcallback/defmacrocallback"
|
||||
raise ArgumentError, "default arguments \\\\ not supported in defcallback/defmacrocallback"
|
||||
end
|
||||
|
||||
defp ensure_not_default(_), do: :ok
|
||||
|
||||
@@ -24,8 +24,8 @@ defmodule Bitwise do
|
||||
Allow a developer to use this module in their programs with
|
||||
the following options:
|
||||
|
||||
* `:only_operators` - Include only operators;
|
||||
* `:skip_operators` - Skip operators;
|
||||
* `:only_operators` - include only operators
|
||||
* `:skip_operators` - skip operators
|
||||
|
||||
"""
|
||||
defmacro __using__(options) do
|
||||
|
||||
+104
-39
@@ -1,11 +1,4 @@
|
||||
defmodule Code do
|
||||
defexception LoadError, [:file, :message] do
|
||||
def exception(opts) do
|
||||
file = opts[:file]
|
||||
LoadError[message: "could not load #{file}", file: file]
|
||||
end
|
||||
end
|
||||
|
||||
@moduledoc """
|
||||
Utilities for managing code compilation, code evaluation and code loading.
|
||||
|
||||
@@ -66,21 +59,24 @@ defmodule Code do
|
||||
|
||||
Those options can be:
|
||||
|
||||
* `:file` - the file to be considered in the evaluation
|
||||
* `:line` - the line on which the script starts
|
||||
* `:delegate_locals_to` - delegate local calls to the given module,
|
||||
the default is to not delegate
|
||||
* `:file` - the file to be considered in the evaluation
|
||||
* `:line` - the line on which the script starts
|
||||
* `:delegate_locals_to` - delegate local calls to the given module,
|
||||
the default is to not delegate
|
||||
|
||||
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. 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. The list
|
||||
of function names and arity must be sorted
|
||||
* `: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; 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; the list
|
||||
of function names and arity must be sorted
|
||||
|
||||
Notice that setting any of the values above overrides Elixir's default
|
||||
values. For example, setting `:requires` to `[]`, will no longer
|
||||
@@ -135,14 +131,14 @@ defmodule Code do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> contents = quote(hygiene: [vars: false], do: a + b)
|
||||
iex> contents = quote(do: var!(a) + var!(b))
|
||||
iex> Code.eval_quoted(contents, [a: 1, b: 2], file: __ENV__.file, line: __ENV__.line)
|
||||
{3, [a: 1, b: 2]}
|
||||
|
||||
For convenience, you can pass `__ENV__` as the `opts` argument and
|
||||
all options will be automatically extracted from the current environment:
|
||||
|
||||
iex> contents = quote(hygiene: [vars: false], do: a + b)
|
||||
iex> contents = quote(do: var!(a) + var!(b))
|
||||
iex> Code.eval_quoted(contents, [a: 1, b: 2], __ENV__)
|
||||
{3, [a: 1, b: 2]}
|
||||
|
||||
@@ -171,7 +167,7 @@ defmodule Code do
|
||||
valid = is_list(requires) and Enum.all?(requires, &is_atom(&1))
|
||||
|
||||
unless valid do
|
||||
raise ArgumentError, message: "expected :#{kind} option given to eval in the format: [module]"
|
||||
raise ArgumentError, "expected :#{kind} option given to eval in the format: [module]"
|
||||
end
|
||||
end
|
||||
|
||||
@@ -181,7 +177,7 @@ defmodule Code do
|
||||
end)
|
||||
|
||||
unless valid do
|
||||
raise ArgumentError, message: "expected :#{kind} option given to eval in the format: [{module, module}]"
|
||||
raise ArgumentError, "expected :#{kind} option given to eval in the format: [{module, module}]"
|
||||
end
|
||||
end
|
||||
|
||||
@@ -193,7 +189,7 @@ defmodule Code do
|
||||
end)
|
||||
|
||||
unless valid do
|
||||
raise ArgumentError, message: "expected :#{kind} option given to eval in the format: [{module, [{name, arity}]}]"
|
||||
raise ArgumentError, "expected :#{kind} option given to eval in the format: [{module, [{name, arity}]}]"
|
||||
end
|
||||
end
|
||||
|
||||
@@ -205,13 +201,13 @@ defmodule Code do
|
||||
|
||||
## Options
|
||||
|
||||
* `:file` - The filename to be used in stacktraces
|
||||
and the file reported in the `__ENV__` variable.
|
||||
* `:file` - the filename to be used in stacktraces
|
||||
and the file reported in the `__ENV__` variable
|
||||
|
||||
* `:line` - The line reported in the `__ENV__` variable.
|
||||
* `:line` - the line reported in the `__ENV__` variable
|
||||
|
||||
* `:existing_atoms_only` - When `true`, raises an error
|
||||
when non-existing atoms are found by the tokenizer.
|
||||
* `:existing_atoms_only` - when `true`, raises an error
|
||||
when non-existing atoms are found by the tokenizer
|
||||
|
||||
## Macro.to_string/2
|
||||
|
||||
@@ -252,7 +248,7 @@ defmodule Code do
|
||||
"""
|
||||
def eval_file(file, relative_to \\ nil) do
|
||||
file = find_file(file, relative_to)
|
||||
eval_string File.read!(file), [], []
|
||||
eval_string File.read!(file), [], [file: file, line: 1]
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -315,6 +311,15 @@ defmodule Code do
|
||||
:elixir_code_server.call :compiler_options
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns a list with the available compiler options.
|
||||
|
||||
See `Code.compiler_options/1` for more info.
|
||||
"""
|
||||
def available_compiler_options do
|
||||
[:docs, :debug_info, :ignore_module_conflict, :warnings_as_errors]
|
||||
end
|
||||
|
||||
@doc """
|
||||
Sets compilation options.
|
||||
|
||||
@@ -322,20 +327,26 @@ defmodule Code do
|
||||
|
||||
Available options are:
|
||||
|
||||
* `:docs` - when `true`, retain documentation in the compiled module,
|
||||
`true` 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, `false` by default;
|
||||
* `:debug_info` - when `true`, retain debug information in the compiled
|
||||
module; this allows a developer to reconstruct the original source
|
||||
code, `false` by default
|
||||
|
||||
* `:ignore_module_conflict` - when `true`, override modules that were already defined
|
||||
without raising errors, `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 generated;
|
||||
* `:warnings_as_errors` - cause compilation to fail when warnings are
|
||||
generated
|
||||
|
||||
"""
|
||||
def compiler_options(opts) do
|
||||
{opts, bad} = Keyword.split(opts, available_compiler_options)
|
||||
if bad != [] do
|
||||
bad = bad |> Keyword.keys |> Enum.join(", ")
|
||||
raise ArgumentError, message: "unknown compiler options: #{bad}"
|
||||
end
|
||||
:elixir_code_server.cast {:compiler_options, opts}
|
||||
end
|
||||
|
||||
@@ -455,6 +466,60 @@ defmodule Code do
|
||||
match?({:module, ^module}, ensure_compiled(module))
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns the docs for the given module.
|
||||
|
||||
When given a module name, it finds its BEAM code and reads the docs from it.
|
||||
|
||||
When given a path to a .beam file, it will load the docs directly from that
|
||||
file.
|
||||
|
||||
The return value depends on the `kind` value:
|
||||
|
||||
* `:docs` - list of all docstrings attached to functions and macros
|
||||
using the `@doc` attribute
|
||||
|
||||
* `:moduledoc` - tuple `{<line>, <doc>}` where `line` is the line on
|
||||
which module definition starts and `doc` is the string
|
||||
attached to the module using the `@moduledoc` attribute
|
||||
|
||||
* `:all` - a keyword list with both `:docs` and `:moduledoc`
|
||||
|
||||
"""
|
||||
def get_docs(module, kind) when is_atom(module) do
|
||||
case :code.get_object_code(module) do
|
||||
{_module, bin, _beam_path} ->
|
||||
do_get_docs(bin, kind)
|
||||
|
||||
:error -> nil
|
||||
end
|
||||
end
|
||||
|
||||
def get_docs(binpath, kind) when is_binary(binpath) do
|
||||
do_get_docs(String.to_char_list(binpath), kind)
|
||||
end
|
||||
|
||||
@docs_chunk 'ExDc'
|
||||
|
||||
defp do_get_docs(bin_or_path, kind) do
|
||||
case :beam_lib.chunks(bin_or_path, [@docs_chunk]) do
|
||||
{:ok, {_module, [{@docs_chunk, bin}]}} ->
|
||||
lookup_docs(:erlang.binary_to_term(bin), kind)
|
||||
|
||||
{:error, :beam_lib, {:missing_chunk, _, @docs_chunk}} -> nil
|
||||
end
|
||||
end
|
||||
|
||||
defp lookup_docs({:elixir_docs_v1, docs}, kind),
|
||||
do: do_lookup_docs(docs, kind)
|
||||
|
||||
# unsupported chunk version
|
||||
defp lookup_docs(_, _), do: nil
|
||||
|
||||
defp do_lookup_docs(docs, :all), do: docs
|
||||
defp do_lookup_docs(docs, kind) when kind in [:docs, :moduledoc],
|
||||
do: Keyword.get(docs, kind)
|
||||
|
||||
## Helpers
|
||||
|
||||
# Finds the file given the relative_to path.
|
||||
@@ -470,7 +535,7 @@ defmodule Code do
|
||||
if File.regular?(file) do
|
||||
file
|
||||
else
|
||||
raise LoadError, file: file
|
||||
raise Code.LoadError, file: file
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
+205
-4
@@ -31,6 +31,70 @@ defmodule Dict do
|
||||
|
||||
Dictionaries are required to implement all operations
|
||||
using the match (`===`) operator.
|
||||
|
||||
## Default implementation
|
||||
|
||||
Default implementations for some functions in the `Dict` module
|
||||
are provided via `use Dict`.
|
||||
|
||||
For example:
|
||||
|
||||
defmodule MyDict do
|
||||
use Dict
|
||||
|
||||
# implement required functions (see below)
|
||||
# override default implementations if optimization
|
||||
# is needed
|
||||
end
|
||||
|
||||
The client module must contain the following functions:
|
||||
|
||||
* `delete/2`
|
||||
* `fetch/2`
|
||||
* `put/3`
|
||||
* `reduce/3`
|
||||
* `size/1`
|
||||
|
||||
All functions, except `reduce/3`, are required by the Dict behaviour.
|
||||
`reduce/3` must be implemtented as per the Enumerable protocol.
|
||||
|
||||
Based on these functions, `Dict` generates default implementations
|
||||
for the following functions:
|
||||
|
||||
* `drop/2`
|
||||
* `equal?/2`
|
||||
* `fetch!/2`
|
||||
* `get/2`
|
||||
* `get/3`
|
||||
* `has_key?/2`
|
||||
* `keys/1`
|
||||
* `merge/2`
|
||||
* `merge/3`
|
||||
* `pop/2`
|
||||
* `pop/3`
|
||||
* `put_new/3`
|
||||
* `split/2`
|
||||
* `take/2`
|
||||
* `to_list/1`
|
||||
* `update/4`
|
||||
* `update!/3`
|
||||
* `values/1`
|
||||
|
||||
All of these functions are defined as overridable, so you can provide
|
||||
your own implementation if needed.
|
||||
|
||||
Note you can also test your custom module via `Dict`'s doctests:
|
||||
|
||||
defmodule MyDict do
|
||||
# ...
|
||||
end
|
||||
|
||||
defmodule MyTests do
|
||||
use ExUnit.Case
|
||||
doctest Dict
|
||||
defp dict_impl, do: MyDict
|
||||
end
|
||||
|
||||
"""
|
||||
|
||||
use Behaviour
|
||||
@@ -63,6 +127,146 @@ defmodule Dict do
|
||||
defcallback update!(t, key, (value -> value)) :: t | no_return
|
||||
defcallback values(t) :: list(value)
|
||||
|
||||
defmacro __using__(_) do
|
||||
# Use this import to guarantee proper code expansion
|
||||
import Kernel, except: [size: 1]
|
||||
|
||||
quote do
|
||||
@behaviour Dict
|
||||
|
||||
def get(dict, key, default \\ nil) do
|
||||
case fetch(dict, key) do
|
||||
{:ok, value} -> value
|
||||
:error -> default
|
||||
end
|
||||
end
|
||||
|
||||
def fetch!(dict, key) do
|
||||
case fetch(dict, key) do
|
||||
{:ok, value} -> value
|
||||
:error -> raise KeyError, key: key, term: dict
|
||||
end
|
||||
end
|
||||
|
||||
def has_key?(dict, key) do
|
||||
match? {:ok, _}, fetch(dict, key)
|
||||
end
|
||||
|
||||
def put_new(dict, key, value) do
|
||||
case has_key?(dict, key) do
|
||||
true -> dict
|
||||
false -> put(dict, key, value)
|
||||
end
|
||||
end
|
||||
|
||||
def drop(dict, keys) do
|
||||
Enum.reduce(keys, dict, &delete(&2, &1))
|
||||
end
|
||||
|
||||
def take(dict, keys) do
|
||||
Enum.reduce(keys, new, fn key, acc ->
|
||||
case fetch(dict, key) do
|
||||
{:ok, value} -> put(acc, key, value)
|
||||
:error -> acc
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
def to_list(dict) do
|
||||
reduce(dict, {:cont, []}, fn
|
||||
kv, acc -> {:cont, [kv|acc]}
|
||||
end) |> elem(1) |> :lists.reverse
|
||||
end
|
||||
|
||||
def keys(dict) do
|
||||
reduce(dict, {:cont, []}, fn
|
||||
{k, _}, acc -> {:cont, [k|acc]}
|
||||
end) |> elem(1) |> :lists.reverse
|
||||
end
|
||||
|
||||
def values(dict) do
|
||||
reduce(dict, {:cont, []}, fn
|
||||
{_, v}, acc -> {:cont, [v|acc]}
|
||||
end) |> elem(1) |> :lists.reverse
|
||||
end
|
||||
|
||||
def equal?(dict1, dict2) do
|
||||
# Use this import to avoid conflicts in the user code
|
||||
import Kernel, except: [size: 1]
|
||||
|
||||
case size(dict1) == size(dict2) do
|
||||
false -> false
|
||||
true ->
|
||||
reduce(dict1, {:cont, true}, fn({k, v}, _acc) ->
|
||||
case fetch(dict2, k) do
|
||||
{:ok, ^v} -> {:cont, true}
|
||||
_ -> {:halt, false}
|
||||
end
|
||||
end) |> elem(1)
|
||||
end
|
||||
end
|
||||
|
||||
def merge(dict1, dict2, fun \\ fn(_k, _v1, v2) -> v2 end) do
|
||||
# Use this import to avoid conflicts in the user code
|
||||
import Kernel, except: [size: 1]
|
||||
|
||||
if size(dict1) < size(dict2) do
|
||||
reduce(dict1, {:cont, dict2}, fn {k, v1}, acc ->
|
||||
{:cont, update(acc, k, v1, &fun.(k, v1, &1))}
|
||||
end)
|
||||
else
|
||||
reduce(dict2, {:cont, dict1}, fn {k, v2}, acc ->
|
||||
{:cont, update(acc, k, v2, &fun.(k, &1, v2))}
|
||||
end)
|
||||
end |> elem(1)
|
||||
end
|
||||
|
||||
def update(dict, key, initial, fun) do
|
||||
case fetch(dict, key) do
|
||||
{:ok, value} ->
|
||||
put(dict, key, fun.(value))
|
||||
:error ->
|
||||
put(dict, key, initial)
|
||||
end
|
||||
end
|
||||
|
||||
def update!(dict, key, fun) do
|
||||
case fetch(dict, key) do
|
||||
{:ok, value} ->
|
||||
put(dict, key, fun.(value))
|
||||
:error ->
|
||||
raise KeyError, key: key, term: dict
|
||||
end
|
||||
end
|
||||
|
||||
def pop(dict, key, default \\ nil) do
|
||||
case fetch(dict, key) do
|
||||
{:ok, value} ->
|
||||
{value, delete(dict, key)}
|
||||
:error ->
|
||||
{default, dict}
|
||||
end
|
||||
end
|
||||
|
||||
def split(dict, keys) do
|
||||
Enum.reduce(keys, {new, dict}, fn key, {inc, exc} = acc ->
|
||||
case fetch(exc, key) do
|
||||
{:ok, value} ->
|
||||
{put(inc, key, value), delete(exc, key)}
|
||||
:error ->
|
||||
acc
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
defoverridable merge: 2, merge: 3, equal?: 2, to_list: 1, keys: 1,
|
||||
values: 1, take: 2, drop: 2, get: 2, get: 3, fetch!: 2,
|
||||
has_key?: 2, put_new: 3, pop: 2, pop: 3, split: 2,
|
||||
update: 4, update!: 3
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
defmacrop target(dict) do
|
||||
quote do
|
||||
case unquote(dict) do
|
||||
@@ -70,9 +274,6 @@ defmodule Dict do
|
||||
x
|
||||
%{} ->
|
||||
Map
|
||||
x when is_tuple(x) ->
|
||||
IO.write :stderr, "Using records with the Dict module is deprecated, please use structs instead\n#{Exception.format_stacktrace}"
|
||||
elem(x, 0)
|
||||
x when is_list(x) ->
|
||||
Keyword
|
||||
x ->
|
||||
@@ -487,6 +688,6 @@ defmodule Dict do
|
||||
end
|
||||
|
||||
defp unsupported_dict(dict) do
|
||||
raise ArgumentError, message: "unsupported dict: #{inspect dict}"
|
||||
raise ArgumentError, "unsupported dict: #{inspect dict}"
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,199 +0,0 @@
|
||||
defmodule Dict.Behaviour do
|
||||
@moduledoc ~S"""
|
||||
Default implementations for some required functions in the `Dict` module.
|
||||
|
||||
This module makes it easier to create your own `Dict` compliant
|
||||
module, by providing default implementations for some required functions.
|
||||
|
||||
## Example
|
||||
|
||||
defmodule MyDict do
|
||||
use Dict.Behaviour
|
||||
|
||||
# implement required functions (see below)
|
||||
|
||||
# override default implementations if optimization is needed
|
||||
end
|
||||
|
||||
The client module must contain the following functions:
|
||||
|
||||
* `delete/2`
|
||||
* `fetch/2`
|
||||
* `put/3`
|
||||
* `reduce/3`
|
||||
* `size/1`
|
||||
|
||||
All functions, except `reduce/3`, are required by the Dict behaviour.
|
||||
`reduce/3` must be implemtented as per the Enumerable protocol.
|
||||
|
||||
Based on these functions, `Dict.Behaviour` generates default implementations
|
||||
for the following functions:
|
||||
|
||||
* `drop/2`
|
||||
* `equal?/2`
|
||||
* `fetch!/2`
|
||||
* `get/2`
|
||||
* `get/3`
|
||||
* `has_key?/2`
|
||||
* `keys/1`
|
||||
* `merge/2`
|
||||
* `merge/3`
|
||||
* `pop/2`
|
||||
* `pop/3`
|
||||
* `put_new/3`
|
||||
* `split/2`
|
||||
* `take/2`
|
||||
* `to_list/1`
|
||||
* `values/1`
|
||||
* `update/4`
|
||||
* `update!/3`
|
||||
|
||||
All of these functions are defined as overridable, so you can provide your own
|
||||
implementation if needed.
|
||||
|
||||
Note you can also test your custom module via `Dict`'s doctests:
|
||||
|
||||
defmodule MyDict do
|
||||
def new(keywords \\ []) do
|
||||
...
|
||||
end
|
||||
end
|
||||
|
||||
defmodule MyTests do
|
||||
use ExUnit.Case
|
||||
doctest Dict
|
||||
defp dict_impl, do: MyDict
|
||||
end
|
||||
|
||||
"""
|
||||
defmacro __using__(_) do
|
||||
# Use this import to guarantee proper code expansion
|
||||
import Kernel, except: [size: 1]
|
||||
|
||||
quote do
|
||||
@behaviour Dict
|
||||
|
||||
def get(dict, key, default \\ nil) do
|
||||
case fetch(dict, key) do
|
||||
{:ok, value} -> value
|
||||
:error -> default
|
||||
end
|
||||
end
|
||||
|
||||
def fetch!(dict, key) do
|
||||
case fetch(dict, key) do
|
||||
{:ok, value} -> value
|
||||
:error -> raise KeyError, key: key, term: dict
|
||||
end
|
||||
end
|
||||
|
||||
def has_key?(dict, key) do
|
||||
match? {:ok, _}, fetch(dict, key)
|
||||
end
|
||||
|
||||
def put_new(dict, key, value) do
|
||||
update(dict, key, value, fn(v) -> v end)
|
||||
end
|
||||
|
||||
def drop(dict, keys) do
|
||||
Enum.reduce keys, dict, &delete(&2, &1)
|
||||
end
|
||||
|
||||
def take(dict, keys) do
|
||||
Enum.reduce keys, new, fn key, acc ->
|
||||
case fetch(dict, key) do
|
||||
{:ok, value} -> put(acc, key, value)
|
||||
:error -> acc
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def to_list(dict) do
|
||||
reduce(dict, {:cont, []}, fn
|
||||
kv, acc -> {:cont, [kv|acc]}
|
||||
end) |> elem(1) |> :lists.reverse
|
||||
end
|
||||
|
||||
def keys(dict) do
|
||||
reduce(dict, {:cont, []}, fn
|
||||
{k, _}, acc -> {:cont, [k|acc]}
|
||||
end) |> elem(1) |> :lists.reverse
|
||||
end
|
||||
|
||||
def values(dict) do
|
||||
reduce(dict, {:cont, []}, fn
|
||||
{_, v}, acc -> {:cont, [v|acc]}
|
||||
end) |> elem(1) |> :lists.reverse
|
||||
end
|
||||
|
||||
def equal?(dict1, dict2) do
|
||||
# Use this import to avoid conflicts in the user code
|
||||
import Kernel, except: [size: 1]
|
||||
|
||||
case size(dict1) == size(dict2) do
|
||||
false -> false
|
||||
true ->
|
||||
reduce(dict1, {:cont, true}, fn({k, v}, _acc) ->
|
||||
case fetch(dict2, k) do
|
||||
{:ok, ^v} -> {:cont, true}
|
||||
_ -> {:halt, false}
|
||||
end
|
||||
end) |> elem(1)
|
||||
end
|
||||
end
|
||||
|
||||
def merge(dict1, dict2, fun \\ fn(_k, _v1, v2) -> v2 end) do
|
||||
reduce(dict1, {:cont, dict2}, fn {k, v1}, acc ->
|
||||
{:cont, update(acc, k, v1, &fun.(k, v1, &1))}
|
||||
end) |> elem(1)
|
||||
end
|
||||
|
||||
def update(dict, key, initial, fun) do
|
||||
case fetch(dict, key) do
|
||||
{ :ok, value } ->
|
||||
put(dict, key, fun.(value))
|
||||
|
||||
:error ->
|
||||
put(dict, key, initial)
|
||||
end
|
||||
end
|
||||
|
||||
def update!(dict, key, fun) do
|
||||
case fetch(dict, key) do
|
||||
{ :ok, value } ->
|
||||
put(dict, key, fun.(value))
|
||||
|
||||
:error ->
|
||||
raise KeyError, key: key, term: dict
|
||||
end
|
||||
end
|
||||
|
||||
def pop(dict, key, default \\ nil) do
|
||||
case fetch(dict, key) do
|
||||
{ :ok, value } ->
|
||||
{ value, delete(dict, key) }
|
||||
|
||||
:error ->
|
||||
{ default, dict }
|
||||
end
|
||||
end
|
||||
|
||||
def split(dict, keys) do
|
||||
Enum.reduce keys, { new, dict }, fn key, { inc, exc } = acc ->
|
||||
case fetch(exc, key) do
|
||||
{ :ok, value } ->
|
||||
{ put(inc, key, value), delete(exc, key) }
|
||||
|
||||
:error ->
|
||||
acc
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defoverridable merge: 2, merge: 3, equal?: 2, to_list: 1, keys: 1,
|
||||
values: 1, take: 2, drop: 2, get: 2, get: 3, fetch!: 2,
|
||||
has_key?: 2, put_new: 3, pop: 2, pop: 3, update: 4, update!: 3,
|
||||
split: 2
|
||||
end
|
||||
end
|
||||
end
|
||||
+62
-32
@@ -39,9 +39,9 @@ defprotocol Enumerable do
|
||||
|
||||
It must be a tagged tuple with one of the following "tags":
|
||||
|
||||
* `:cont` - the enumeration should continue
|
||||
* `:halt` - the enumeration should halt immediately
|
||||
* `:suspend` - the enumeration should be suspended immediately
|
||||
* `:cont` - the enumeration should continue
|
||||
* `:halt` - the enumeration should halt immediately
|
||||
* `:suspend` - the enumeration should be suspended immediately
|
||||
|
||||
Depending on the accumulator value, the result returned by
|
||||
`Enumerable.reduce/3` will change. Please check the `result`
|
||||
@@ -936,10 +936,15 @@ defmodule Enum do
|
||||
def join(collection, joiner \\ "")
|
||||
|
||||
def join(collection, joiner) when is_binary(joiner) do
|
||||
reduce(collection, "", fn
|
||||
entry, "" -> to_string(entry)
|
||||
entry, acc -> acc <> joiner <> to_string(entry)
|
||||
reduced = reduce(collection, :first, fn
|
||||
entry, :first -> enum_to_string(entry)
|
||||
entry, acc -> [acc, joiner|enum_to_string(entry)]
|
||||
end)
|
||||
if reduced == :first do
|
||||
""
|
||||
else
|
||||
IO.iodata_to_binary reduced
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -990,10 +995,16 @@ defmodule Enum do
|
||||
def map_join(collection, joiner \\ "", mapper)
|
||||
|
||||
def map_join(collection, joiner, mapper) when is_binary(joiner) do
|
||||
reduce(collection, "", fn
|
||||
entry, "" -> to_string(mapper, entry)
|
||||
entry, acc -> acc <> joiner <> to_string(mapper, entry)
|
||||
reduced = reduce(collection, :first, fn
|
||||
entry, :first -> enum_to_string(mapper.(entry))
|
||||
entry, acc -> [acc, joiner|enum_to_string(mapper.(entry))]
|
||||
end)
|
||||
|
||||
if reduced == :first do
|
||||
""
|
||||
else
|
||||
IO.iodata_to_binary reduced
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -1406,18 +1417,33 @@ defmodule Enum do
|
||||
@doc """
|
||||
Returns a subset list of the given collection. Drops elements
|
||||
until element position `start`, then takes `count` elements.
|
||||
|
||||
If the count is greater than collection length, it returns as
|
||||
much as possible. If zero, then it returns `[]`.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Enum.slice(1..100, 5, 10)
|
||||
[6, 7, 8, 9, 10, 11, 12, 13, 14, 15]
|
||||
|
||||
iex> Enum.slice(1..10, 5, 100)
|
||||
[6, 7, 8, 9, 10]
|
||||
|
||||
iex> Enum.slice(1..10, 5, 0)
|
||||
[]
|
||||
|
||||
"""
|
||||
@spec slice(t, integer, non_neg_integer) :: list
|
||||
|
||||
def slice(_coll, _start, 0), do: []
|
||||
|
||||
def slice(coll, start, count) when start < 0 do
|
||||
{list, new_start} = enumerate_and_count(coll, start)
|
||||
if new_start >= 0, do: slice(list, new_start, count)
|
||||
if new_start >= 0 do
|
||||
slice(list, new_start, count)
|
||||
else
|
||||
[]
|
||||
end
|
||||
end
|
||||
|
||||
def slice(coll, start, count) when is_list(coll) and start >= 0 and count > 0 do
|
||||
@@ -1425,7 +1451,7 @@ defmodule Enum do
|
||||
end
|
||||
|
||||
def slice(coll, start, count) when start >= 0 and count > 0 do
|
||||
{start, _, list} = Enumerable.reduce(coll, {:cont, {start, count, []}}, fn
|
||||
{_, _, list} = Enumerable.reduce(coll, {:cont, {start, count, []}}, fn
|
||||
_entry, {start, count, _list} when start > 0 ->
|
||||
{:cont, {start-1, count, []}}
|
||||
entry, {start, count, list} when count > 1 ->
|
||||
@@ -1434,15 +1460,7 @@ defmodule Enum do
|
||||
{:halt, {start, count, [entry|list]}}
|
||||
end) |> elem(1)
|
||||
|
||||
if start <= 0, do: :lists.reverse(list)
|
||||
end
|
||||
|
||||
def slice(coll, start, 0) do
|
||||
res =
|
||||
Enumerable.reduce(coll, {:cont, start}, fn _, start ->
|
||||
if start > 0, do: {:cont, start-1}, else: {:halt, []}
|
||||
end) |> elem(1)
|
||||
if is_list(res), do: res
|
||||
:lists.reverse(list)
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -1457,17 +1475,31 @@ defmodule Enum do
|
||||
The first position (after adding count to negative positions) must be smaller
|
||||
or equal to the last position.
|
||||
|
||||
If the start of the range is not a valid offset for the given
|
||||
collection or if the range is in reverse order, returns `[]`.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Enum.slice(1..100, 5..10)
|
||||
[6, 7, 8, 9, 10, 11]
|
||||
|
||||
iex> Enum.slice(1..10, 5..20)
|
||||
[6, 7, 8, 9, 10]
|
||||
|
||||
iex> Enum.slice(1..10, 11..20)
|
||||
[]
|
||||
|
||||
iex> Enum.slice(1..10, 6..5)
|
||||
[]
|
||||
|
||||
"""
|
||||
@spec slice(t, Range.t) :: list
|
||||
def slice(coll, first..last) when first >= 0 and last >= 0 do
|
||||
# Simple case, which works on infinite collections
|
||||
if last - first >= 0 do
|
||||
slice(coll, first, last - first + 1)
|
||||
else
|
||||
[]
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1478,6 +1510,8 @@ defmodule Enum do
|
||||
length = corr_last - corr_first + 1
|
||||
if corr_first >= 0 and length > 0 do
|
||||
slice(list, corr_first, length)
|
||||
else
|
||||
[]
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1805,7 +1839,7 @@ defmodule Enum do
|
||||
|
||||
## Helpers
|
||||
|
||||
@compile {:inline, to_string: 2}
|
||||
@compile {:inline, enum_to_string: 1}
|
||||
|
||||
defp enumerate_and_count(collection, count) when is_list(collection) do
|
||||
{collection, length(collection) - abs(count)}
|
||||
@@ -1815,12 +1849,8 @@ defmodule Enum do
|
||||
map_reduce(collection, -abs(count), fn(x, acc) -> {x, acc + 1} end)
|
||||
end
|
||||
|
||||
defp to_string(mapper, entry) do
|
||||
case mapper.(entry) do
|
||||
x when is_binary(x) -> x
|
||||
o -> String.Chars.to_string(o)
|
||||
end
|
||||
end
|
||||
defp enum_to_string(entry) when is_binary(entry), do: entry
|
||||
defp enum_to_string(entry), do: String.Chars.to_string(entry)
|
||||
|
||||
## Implementations
|
||||
|
||||
@@ -2131,12 +2161,12 @@ defmodule Enum do
|
||||
|
||||
## slice
|
||||
|
||||
defp do_slice([], start, _count) do
|
||||
if start == 0, do: []
|
||||
defp do_slice([], _start, _count) do
|
||||
[]
|
||||
end
|
||||
|
||||
defp do_slice(list, start, 0) do
|
||||
if start < length(list), do: []
|
||||
defp do_slice(_list, _start, 0) do
|
||||
[]
|
||||
end
|
||||
|
||||
defp do_slice([h|t], 0, count) do
|
||||
@@ -2184,7 +2214,7 @@ defimpl Enumerable, for: Map do
|
||||
end
|
||||
|
||||
defimpl Enumerable, for: Function do
|
||||
def reduce(function, acc, fun),
|
||||
def reduce(function, acc, fun) when is_function(function, 2),
|
||||
do: function.(acc, fun)
|
||||
def member?(_function, _value),
|
||||
do: {:error, __MODULE__}
|
||||
|
||||
+368
-244
@@ -1,180 +1,26 @@
|
||||
# Some exceptions implement `message/1` instead of `exception/1` mostly
|
||||
# for bootstrap reasons. It is recommended for applications to implement
|
||||
# `exception/1` instead of `message/1` as described in `defexception/3` docs.
|
||||
|
||||
defexception RuntimeError, message: "runtime error"
|
||||
defexception ArgumentError, message: "argument error"
|
||||
defexception ArithmeticError, message: "bad argument in arithmetic expression"
|
||||
defexception SystemLimitError, message: "a system limit has been reached"
|
||||
|
||||
defexception SyntaxError, [file: nil, line: nil, description: "syntax error"] do
|
||||
def message(exception) do
|
||||
Exception.format_file_line(Path.relative_to_cwd(exception.file), exception.line) <>
|
||||
" " <> exception.description
|
||||
end
|
||||
end
|
||||
|
||||
defexception TokenMissingError, [file: nil, line: nil, description: "expression is incomplete"] do
|
||||
def message(exception) do
|
||||
Exception.format_file_line(Path.relative_to_cwd(exception.file), exception.line) <>
|
||||
" " <> exception.description
|
||||
end
|
||||
end
|
||||
|
||||
defexception CompileError, [file: nil, line: nil, description: "compile error"] do
|
||||
def message(exception) do
|
||||
Exception.format_file_line(Path.relative_to_cwd(exception.file), exception.line) <>
|
||||
" " <> exception.description
|
||||
end
|
||||
end
|
||||
|
||||
defexception BadFunctionError, [term: nil] do
|
||||
def message(exception) do
|
||||
"expected a function, got: #{inspect(exception.term)}"
|
||||
end
|
||||
end
|
||||
|
||||
defexception BadStructError, [struct: nil, term: nil] do
|
||||
def message(exception) do
|
||||
"expected a struct named #{inspect(exception.struct)}, got: #{inspect(exception.term)}"
|
||||
end
|
||||
end
|
||||
|
||||
defexception MatchError, [term: nil] do
|
||||
def message(exception) do
|
||||
"no match of right hand side value: #{inspect(exception.term)}"
|
||||
end
|
||||
end
|
||||
|
||||
defexception CaseClauseError, [term: nil] do
|
||||
def message(exception) do
|
||||
"no case clause matching: #{inspect(exception.term)}"
|
||||
end
|
||||
end
|
||||
|
||||
defexception TryClauseError, [term: nil] do
|
||||
def message(exception) do
|
||||
"no try clause matching: #{inspect(exception.term)}"
|
||||
end
|
||||
end
|
||||
|
||||
defexception BadArityError, [function: nil, args: nil] do
|
||||
def message(exception) do
|
||||
fun = exception.function
|
||||
args = exception.args
|
||||
insp = Enum.map_join(args, ", ", &inspect/1)
|
||||
{:arity, arity} = :erlang.fun_info(fun, :arity)
|
||||
"#{inspect(fun)} with arity #{arity} called with #{count(length(args), insp)}"
|
||||
end
|
||||
|
||||
defp count(0, _insp), do: "no arguments"
|
||||
defp count(1, insp), do: "1 argument (#{insp})"
|
||||
defp count(x, insp), do: "#{x} arguments (#{insp})"
|
||||
end
|
||||
|
||||
defexception UndefinedFunctionError, [module: nil, function: nil, arity: nil] do
|
||||
def message(exception) do
|
||||
if exception.function do
|
||||
formatted = Exception.format_mfa exception.module, exception.function, exception.arity
|
||||
"undefined function: #{formatted}"
|
||||
else
|
||||
"undefined function"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defexception FunctionClauseError, [module: nil, function: nil, arity: nil] do
|
||||
def message(exception) do
|
||||
if exception.function do
|
||||
formatted = Exception.format_mfa exception.module, exception.function, exception.arity
|
||||
"no function clause matching in #{formatted}"
|
||||
else
|
||||
"no function clause matches"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defexception Protocol.UndefinedError, [protocol: nil, value: nil, description: nil] do
|
||||
def message(exception) do
|
||||
msg = "protocol #{inspect exception.protocol} not implemented for #{inspect exception.value}"
|
||||
if exception.description do
|
||||
msg <> ", " <> exception.description
|
||||
else
|
||||
msg
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defexception ErlangError, [original: nil] do
|
||||
def message(exception) do
|
||||
"erlang error: #{inspect(exception.original)}"
|
||||
end
|
||||
end
|
||||
|
||||
defexception KeyError, key: nil, term: nil do
|
||||
def message(exception) do
|
||||
"key #{inspect exception.key} not found in: #{inspect exception.term}"
|
||||
end
|
||||
end
|
||||
|
||||
defexception UnicodeConversionError, [:encoded, :message] do
|
||||
def exception(opts) do
|
||||
UnicodeConversionError[
|
||||
encoded: opts[:encoded],
|
||||
message: "#{opts[:kind]} #{detail(opts[:rest])}"
|
||||
]
|
||||
end
|
||||
|
||||
defp detail(rest) when is_binary(rest) do
|
||||
"encoding starting at #{inspect rest}"
|
||||
end
|
||||
|
||||
defp detail([h|_]) do
|
||||
"code point #{h}"
|
||||
end
|
||||
end
|
||||
|
||||
defexception Enum.OutOfBoundsError, message: "out of bounds error"
|
||||
|
||||
defexception Enum.EmptyError, message: "empty error"
|
||||
|
||||
defexception File.Error, [reason: nil, action: "", path: nil] do
|
||||
def message(exception) do
|
||||
formatted = iodata_to_binary(:file.format_error(reason exception))
|
||||
"could not #{action exception} #{path exception}: #{formatted}"
|
||||
end
|
||||
end
|
||||
|
||||
defexception File.CopyError, [reason: nil, action: "", source: nil, destination: nil, on: nil] do
|
||||
def message(exception) do
|
||||
formatted = iodata_to_binary(:file.format_error(reason exception))
|
||||
location = if on = on(exception), do: ". #{on}", else: ""
|
||||
"could not #{action exception} from #{source exception} to " <>
|
||||
"#{destination exception}#{location}: #{formatted}"
|
||||
end
|
||||
end
|
||||
|
||||
defmodule Exception do
|
||||
@moduledoc """
|
||||
Functions to work with and pretty print exceptions.
|
||||
Functions to format throw/catch/exit and exceptions.
|
||||
|
||||
Note that stacktraces in Elixir are updated on throw,
|
||||
errors and exits. For example, at any given moment,
|
||||
`System.stacktrace` will return the stacktrace for the
|
||||
last throw/error/exit that ocurred in the current process.
|
||||
|
||||
Finally note developers should not rely on the particular
|
||||
format of the `format` functions provided by this module.
|
||||
They may be changed in future releases in order to better
|
||||
suit Elixir's tool chain. In other words, by using the
|
||||
functions in this module it is guarantee you will format
|
||||
exceptions as in the current Elixir version being used.
|
||||
Do not rely on the particular format returned by the `format`
|
||||
functions in this module. They may be changed in future releases
|
||||
in order to better suit Elixir's tool chain. In other words,
|
||||
by using the functions in this module it is guarantee you will
|
||||
format exceptions as in the current Elixir version being used.
|
||||
"""
|
||||
|
||||
@type t :: tuple
|
||||
@typedoc "The exception type (as generated by defexception)"
|
||||
@type t :: %{__struct__: module, __exception__: true}
|
||||
|
||||
@typedoc "The kind handled by formatting functions"
|
||||
@type kind :: :error | :exit | :throw | {:EXIT, pid}
|
||||
|
||||
@type stacktrace :: [stacktrace_entry]
|
||||
@type process ::
|
||||
pid | atom | {atom, node} | {:global, term} | {:via, module, term}
|
||||
@type stacktrace_entry ::
|
||||
{module, function, arity_or_args, location} |
|
||||
{function, arity_or_args, location}
|
||||
@@ -182,24 +28,25 @@ defmodule Exception do
|
||||
@typep arity_or_args :: non_neg_integer | list
|
||||
@typep location :: Keyword.t
|
||||
|
||||
@callback exception(term) :: t
|
||||
@callback message(t) :: String.t
|
||||
|
||||
@doc """
|
||||
Returns true if the given argument is an exception.
|
||||
"""
|
||||
def exception?(exception) do
|
||||
is_tuple(exception) and tuple_size(exception) > 1 and
|
||||
:erlang.element(2, exception) == :__exception__
|
||||
end
|
||||
def exception?(%{__struct__: struct, __exception__: true}) when is_atom(struct), do: true
|
||||
def exception?(_), do: false
|
||||
|
||||
@doc """
|
||||
Gets the message for an exception.
|
||||
"""
|
||||
def message(exception) do
|
||||
def message(%{__struct__: module, __exception__: true} = exception) when is_atom(module) do
|
||||
try do
|
||||
exception.message
|
||||
module.message(exception)
|
||||
rescue
|
||||
e ->
|
||||
raise ArgumentError, message:
|
||||
"Got #{inspect e.__record__(:name)} with message " <>
|
||||
raise ArgumentError,
|
||||
"Got #{inspect e.__struct__} with message " <>
|
||||
"\"#{message(e)}\" while retrieving message for #{inspect(exception)}"
|
||||
end
|
||||
end
|
||||
@@ -220,16 +67,17 @@ defmodule Exception do
|
||||
an empty stacktrace, `[]`, must be used.
|
||||
"""
|
||||
@spec normalize(:error, any, stacktrace) :: t
|
||||
@spec normalize(:exit | :throw, any, stacktrace) :: any
|
||||
# Generating a stacktrace is expensive, default to nil to only fetch it when
|
||||
# needed.
|
||||
@spec normalize(kind, payload, stacktrace) :: payload when payload: var
|
||||
|
||||
# Generating a stacktrace is expensive, default to nil
|
||||
# to only fetch it when needed.
|
||||
def normalize(kind, payload, stacktrace \\ nil)
|
||||
|
||||
def normalize(:error, exception, stacktrace) do
|
||||
if exception?(exception) do
|
||||
exception
|
||||
else
|
||||
normalize_error(exception, stacktrace)
|
||||
ErlangError.normalize(exception, stacktrace)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -237,60 +85,6 @@ defmodule Exception do
|
||||
payload
|
||||
end
|
||||
|
||||
defp normalize_error(:badarg, _stacktrace) do
|
||||
ArgumentError[]
|
||||
end
|
||||
|
||||
defp normalize_error(:badarith, _stacktrace) do
|
||||
ArithmeticError[]
|
||||
end
|
||||
|
||||
defp normalize_error(:system_limit, _stacktrace) do
|
||||
SystemLimitError[]
|
||||
end
|
||||
|
||||
defp normalize_error({:badarity, {fun, args}}, _stacktrace) do
|
||||
BadArityError[function: fun, args: args]
|
||||
end
|
||||
|
||||
defp normalize_error({:badfun, term}, _stacktrace) do
|
||||
BadFunctionError[term: term]
|
||||
end
|
||||
|
||||
defp normalize_error({:badstruct, struct, term}, _stacktrace) do
|
||||
BadStructError[struct: struct, term: term]
|
||||
end
|
||||
|
||||
defp normalize_error({:badmatch, term}, _stacktrace) do
|
||||
MatchError[term: term]
|
||||
end
|
||||
|
||||
defp normalize_error({:case_clause, term}, _stacktrace) do
|
||||
CaseClauseError[term: term]
|
||||
end
|
||||
|
||||
defp normalize_error({:try_clause, term}, _stacktrace) do
|
||||
TryClauseError[term: term]
|
||||
end
|
||||
|
||||
defp normalize_error(:undef, stacktrace) do
|
||||
{mod, fun, arity} = from_stacktrace(stacktrace || :erlang.get_stacktrace)
|
||||
UndefinedFunctionError[module: mod, function: fun, arity: arity]
|
||||
end
|
||||
|
||||
defp normalize_error(:function_clause, stacktrace) do
|
||||
{mod, fun, arity} = from_stacktrace(stacktrace || :erlang.get_stacktrace)
|
||||
FunctionClauseError[module: mod, function: fun, arity: arity]
|
||||
end
|
||||
|
||||
defp normalize_error({:badarg, payload}, _stacktrace) do
|
||||
ArgumentError[message: "argument error: #{inspect(payload)}"]
|
||||
end
|
||||
|
||||
defp normalize_error(other, _stacktrace) do
|
||||
ErlangError[original: other]
|
||||
end
|
||||
|
||||
@doc """
|
||||
Normalizes and formats any throw, error and exit.
|
||||
|
||||
@@ -304,12 +98,12 @@ defmodule Exception do
|
||||
not return the stacktrace corresponding to the exception
|
||||
an empty stacktrace, `[]`, must be used.
|
||||
"""
|
||||
@spec format_banner(:error | :exit | :throw, any, stacktrace) :: String.t
|
||||
@spec format_banner(kind, any, stacktrace | nil) :: String.t
|
||||
def format_banner(kind, exception, stacktrace \\ nil)
|
||||
|
||||
def format_banner(:error, exception, stacktrace) do
|
||||
exception = normalize(:error, exception, stacktrace)
|
||||
"** (" <> inspect(exception.__record__(:name)) <> ") " <> message(exception)
|
||||
"** (" <> inspect(exception.__struct__) <> ") " <> message(exception)
|
||||
end
|
||||
|
||||
def format_banner(:throw, reason, _stacktrace) do
|
||||
@@ -320,18 +114,34 @@ defmodule Exception do
|
||||
"** (exit) " <> format_exit(reason, <<"\n ">>)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Normalizes and formats an exception and stacktrace.
|
||||
def format_banner({:EXIT, pid}, reason, _stacktrace) do
|
||||
"** (EXIT from #{inspect pid}) " <> format_exit(reason, <<"\n ">>)
|
||||
end
|
||||
|
||||
Behaves the same as `format_banner/3` but also includes
|
||||
the formatted stacktrace.
|
||||
@doc """
|
||||
Normalizes and formats throw/errors/exits and stacktrace.
|
||||
|
||||
It relies on `format_banner/3` and `format_stacktrace/1`
|
||||
to generate the final format.
|
||||
|
||||
Note that `{:EXIT, pid}` do not generate a stacktrace though
|
||||
(as they are retrieved as messages without stacktraces).
|
||||
"""
|
||||
@spec format(:error | :exit | :throw, any, stacktrace) :: String.t
|
||||
def format(kind, exception, stacktrace \\ :erlang.get_stacktrace()) do
|
||||
message = format_banner(kind, exception, stacktrace)
|
||||
|
||||
@spec format(kind, any, stacktrace | nil) :: String.t
|
||||
|
||||
def format(kind, payload, stacktrace \\ nil)
|
||||
|
||||
def format({:EXIT, _} = kind, any, _) do
|
||||
format_banner(kind, any)
|
||||
end
|
||||
|
||||
def format(kind, payload, stacktrace) do
|
||||
stacktrace = stacktrace || System.stacktrace
|
||||
message = format_banner(kind, payload, stacktrace)
|
||||
case stacktrace do
|
||||
[] -> message
|
||||
_ -> message <> "\n" <> format_stacktrace(stacktrace)
|
||||
_ -> message <> "\n" <> format_stacktrace(stacktrace)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -547,7 +357,7 @@ defmodule Exception do
|
||||
|
||||
defp format_application(module) do
|
||||
case :application.get_application(module) do
|
||||
{:ok, app} -> "(" <> atom_to_binary(app) <> ") "
|
||||
{:ok, app} -> "(" <> Atom.to_string(app) <> ") "
|
||||
:undefined -> ""
|
||||
end
|
||||
end
|
||||
@@ -624,7 +434,7 @@ defmodule Exception do
|
||||
end
|
||||
|
||||
defp format_arity(arity) when is_integer(arity) do
|
||||
"/" <> integer_to_binary(arity)
|
||||
"/" <> Integer.to_string(arity)
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -662,6 +472,320 @@ defmodule Exception do
|
||||
defp format_location(opts) when is_list(opts) do
|
||||
format_file_line Keyword.get(opts, :file), Keyword.get(opts, :line), " "
|
||||
end
|
||||
end
|
||||
|
||||
# Some exceptions implement `message/1` instead of `exception/1` mostly
|
||||
# for bootstrap reasons. It is recommended for applications to implement
|
||||
# `exception/1` instead of `message/1` as described in `defexception/1`
|
||||
# docs.
|
||||
|
||||
defmodule RuntimeError do
|
||||
defexception message: "runtime error"
|
||||
|
||||
def exception(msg) when is_binary(msg) do
|
||||
%RuntimeError{message: msg}
|
||||
end
|
||||
|
||||
def exception(arg) do
|
||||
super(arg)
|
||||
end
|
||||
end
|
||||
|
||||
defmodule ArgumentError do
|
||||
defexception message: "argument error"
|
||||
|
||||
def exception(msg) when is_binary(msg) do
|
||||
%ArgumentError{message: msg}
|
||||
end
|
||||
|
||||
def exception(arg) do
|
||||
super(arg)
|
||||
end
|
||||
end
|
||||
|
||||
defmodule ArithmeticError do
|
||||
defexception []
|
||||
|
||||
def message(_) do
|
||||
"bad argument in arithmetic expression"
|
||||
end
|
||||
end
|
||||
|
||||
defmodule SystemLimitError do
|
||||
defexception []
|
||||
|
||||
def message(_) do
|
||||
"a system limit has been reached"
|
||||
end
|
||||
end
|
||||
|
||||
defmodule SyntaxError do
|
||||
defexception [file: nil, line: nil, description: "syntax error"]
|
||||
|
||||
def message(exception) do
|
||||
Exception.format_file_line(Path.relative_to_cwd(exception.file), exception.line) <>
|
||||
" " <> exception.description
|
||||
end
|
||||
end
|
||||
|
||||
defmodule TokenMissingError do
|
||||
defexception [file: nil, line: nil, description: "expression is incomplete"]
|
||||
|
||||
def message(exception) do
|
||||
Exception.format_file_line(Path.relative_to_cwd(exception.file), exception.line) <>
|
||||
" " <> exception.description
|
||||
end
|
||||
end
|
||||
|
||||
defmodule CompileError do
|
||||
defexception [file: nil, line: nil, description: "compile error"]
|
||||
|
||||
def message(exception) do
|
||||
Exception.format_file_line(Path.relative_to_cwd(exception.file), exception.line) <>
|
||||
" " <> exception.description
|
||||
end
|
||||
end
|
||||
|
||||
defmodule BadFunctionError do
|
||||
defexception [term: nil]
|
||||
|
||||
def message(exception) do
|
||||
"expected a function, got: #{inspect(exception.term)}"
|
||||
end
|
||||
end
|
||||
|
||||
defmodule BadStructError do
|
||||
defexception [struct: nil, term: nil]
|
||||
|
||||
def message(exception) do
|
||||
"expected a struct named #{inspect(exception.struct)}, got: #{inspect(exception.term)}"
|
||||
end
|
||||
end
|
||||
|
||||
defmodule MatchError do
|
||||
defexception [term: nil]
|
||||
|
||||
def message(exception) do
|
||||
"no match of right hand side value: #{inspect(exception.term)}"
|
||||
end
|
||||
end
|
||||
|
||||
defmodule CaseClauseError do
|
||||
defexception [term: nil]
|
||||
|
||||
def message(exception) do
|
||||
"no case clause matching: #{inspect(exception.term)}"
|
||||
end
|
||||
end
|
||||
|
||||
defmodule CondClauseError do
|
||||
defexception []
|
||||
|
||||
def message(_exception) do
|
||||
"no cond clause evaluated to a true value"
|
||||
end
|
||||
end
|
||||
|
||||
defmodule TryClauseError do
|
||||
defexception [term: nil]
|
||||
|
||||
def message(exception) do
|
||||
"no try clause matching: #{inspect(exception.term)}"
|
||||
end
|
||||
end
|
||||
|
||||
defmodule BadArityError do
|
||||
defexception [function: nil, args: nil]
|
||||
|
||||
def message(exception) do
|
||||
fun = exception.function
|
||||
args = exception.args
|
||||
insp = Enum.map_join(args, ", ", &inspect/1)
|
||||
{:arity, arity} = :erlang.fun_info(fun, :arity)
|
||||
"#{inspect(fun)} with arity #{arity} called with #{count(length(args), insp)}"
|
||||
end
|
||||
|
||||
defp count(0, _insp), do: "no arguments"
|
||||
defp count(1, insp), do: "1 argument (#{insp})"
|
||||
defp count(x, insp), do: "#{x} arguments (#{insp})"
|
||||
end
|
||||
|
||||
defmodule UndefinedFunctionError do
|
||||
defexception [module: nil, function: nil, arity: nil]
|
||||
|
||||
def message(exception) do
|
||||
if exception.function do
|
||||
formatted = Exception.format_mfa exception.module, exception.function, exception.arity
|
||||
"undefined function: #{formatted}"
|
||||
else
|
||||
"undefined function"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defmodule FunctionClauseError do
|
||||
defexception [module: nil, function: nil, arity: nil]
|
||||
|
||||
def message(exception) do
|
||||
if exception.function do
|
||||
formatted = Exception.format_mfa exception.module, exception.function, exception.arity
|
||||
"no function clause matching in #{formatted}"
|
||||
else
|
||||
"no function clause matches"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defmodule Code.LoadError do
|
||||
defexception [:file, :message]
|
||||
|
||||
def exception(opts) do
|
||||
file = Keyword.fetch!(opts, :file)
|
||||
%Code.LoadError{message: "could not load #{file}", file: file}
|
||||
end
|
||||
end
|
||||
|
||||
defmodule Protocol.UndefinedError do
|
||||
defexception [protocol: nil, value: nil, description: nil]
|
||||
|
||||
def message(exception) do
|
||||
msg = "protocol #{inspect exception.protocol} not implemented for #{inspect exception.value}"
|
||||
if exception.description do
|
||||
msg <> ", " <> exception.description
|
||||
else
|
||||
msg
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defmodule KeyError do
|
||||
defexception key: nil, term: nil
|
||||
|
||||
def message(exception) do
|
||||
"key #{inspect exception.key} not found in: #{inspect exception.term}"
|
||||
end
|
||||
end
|
||||
|
||||
defmodule UnicodeConversionError do
|
||||
defexception [:encoded, :message]
|
||||
|
||||
def exception(opts) do
|
||||
%UnicodeConversionError{
|
||||
encoded: Keyword.fetch!(opts, :encoded),
|
||||
message: "#{Keyword.fetch!(opts, :kind)} #{detail Keyword.fetch!(opts, :rest)}"
|
||||
}
|
||||
end
|
||||
|
||||
defp detail(rest) when is_binary(rest) do
|
||||
"encoding starting at #{inspect rest}"
|
||||
end
|
||||
|
||||
defp detail([h|_]) do
|
||||
"code point #{h}"
|
||||
end
|
||||
end
|
||||
|
||||
defmodule Enum.OutOfBoundsError do
|
||||
defexception []
|
||||
|
||||
def message(_) do
|
||||
"out of bounds error"
|
||||
end
|
||||
end
|
||||
|
||||
defmodule Enum.EmptyError do
|
||||
defexception []
|
||||
|
||||
def message(_) do
|
||||
"empty error"
|
||||
end
|
||||
end
|
||||
|
||||
defmodule File.Error do
|
||||
defexception [reason: nil, action: "", path: nil]
|
||||
|
||||
def message(exception) do
|
||||
formatted = IO.iodata_to_binary(:file.format_error(exception.reason))
|
||||
"could not #{exception.action} #{exception.path}: #{formatted}"
|
||||
end
|
||||
end
|
||||
|
||||
defmodule File.CopyError do
|
||||
defexception [reason: nil, action: "", source: nil, destination: nil, on: nil]
|
||||
|
||||
def message(exception) do
|
||||
formatted = IO.iodata_to_binary(:file.format_error(exception.reason))
|
||||
location = if on = exception.on, do: ". #{on}", else: ""
|
||||
"could not #{exception.action} from #{exception.source} to " <>
|
||||
"#{exception.destination}#{location}: #{formatted}"
|
||||
end
|
||||
end
|
||||
|
||||
defmodule ErlangError do
|
||||
defexception [original: nil]
|
||||
|
||||
def message(exception) do
|
||||
"erlang error: #{inspect(exception.original)}"
|
||||
end
|
||||
|
||||
@doc false
|
||||
def normalize(:badarg, _stacktrace) do
|
||||
%ArgumentError{}
|
||||
end
|
||||
|
||||
def normalize(:badarith, _stacktrace) do
|
||||
%ArithmeticError{}
|
||||
end
|
||||
|
||||
def normalize(:system_limit, _stacktrace) do
|
||||
%SystemLimitError{}
|
||||
end
|
||||
|
||||
def normalize(:cond_clause, _stacktrace) do
|
||||
%CondClauseError{}
|
||||
end
|
||||
|
||||
def normalize({:badarity, {fun, args}}, _stacktrace) do
|
||||
%BadArityError{function: fun, args: args}
|
||||
end
|
||||
|
||||
def normalize({:badfun, term}, _stacktrace) do
|
||||
%BadFunctionError{term: term}
|
||||
end
|
||||
|
||||
def normalize({:badstruct, struct, term}, _stacktrace) do
|
||||
%BadStructError{struct: struct, term: term}
|
||||
end
|
||||
|
||||
def normalize({:badmatch, term}, _stacktrace) do
|
||||
%MatchError{term: term}
|
||||
end
|
||||
|
||||
def normalize({:case_clause, term}, _stacktrace) do
|
||||
%CaseClauseError{term: term}
|
||||
end
|
||||
|
||||
def normalize({:try_clause, term}, _stacktrace) do
|
||||
%TryClauseError{term: term}
|
||||
end
|
||||
|
||||
def normalize(:undef, stacktrace) do
|
||||
{mod, fun, arity} = from_stacktrace(stacktrace || :erlang.get_stacktrace)
|
||||
%UndefinedFunctionError{module: mod, function: fun, arity: arity}
|
||||
end
|
||||
|
||||
def normalize(:function_clause, stacktrace) do
|
||||
{mod, fun, arity} = from_stacktrace(stacktrace || :erlang.get_stacktrace)
|
||||
%FunctionClauseError{module: mod, function: fun, arity: arity}
|
||||
end
|
||||
|
||||
def normalize({:badarg, payload}, _stacktrace) do
|
||||
%ArgumentError{message: "argument error: #{inspect(payload)}"}
|
||||
end
|
||||
|
||||
def normalize(other, _stacktrace) do
|
||||
%ErlangError{original: other}
|
||||
end
|
||||
|
||||
defp from_stacktrace([{module, function, args, _}|_]) when is_list(args) do
|
||||
{module, function, length(args)}
|
||||
|
||||
+178
-272
@@ -1,144 +1,3 @@
|
||||
require Record
|
||||
|
||||
defmodule File.Stat do
|
||||
@moduledoc """
|
||||
A struct responsible to hold file information.
|
||||
|
||||
In Erlang, this struct is represented by a `:file_info` record.
|
||||
Therefore this module also provides functions for converting
|
||||
in between the Erlang record and the Elixir struct.
|
||||
|
||||
Its fields are:
|
||||
|
||||
* `size` - Size of file in bytes.
|
||||
* `type` - `:device`, `:directory`, `:regular`, `:other`. The type of the file.
|
||||
* `access` - `:read`, `:write`, `:read_write`, `:none`. The current system access to
|
||||
the file.
|
||||
* `atime` - The last time the file was read.
|
||||
* `mtime` - The last time the file was written.
|
||||
* `ctime` - The interpretation of this time field depends on the operating
|
||||
system. On Unix, it is the last time the file or the inode was
|
||||
changed. In Windows, it is the create time.
|
||||
* `mode` - The file permissions.
|
||||
* `links` - The number of links to this file. This is always 1 for file
|
||||
systems which have no concept of links.
|
||||
* `major_device` - Identifies the file system where the file is located.
|
||||
In windows, the number indicates a drive as follows:
|
||||
0 means A:, 1 means B:, and so on.
|
||||
* `minor_device` - Only valid for character devices on Unix. In all other
|
||||
cases, this field is zero.
|
||||
* `inode` - Gives the inode number. On non-Unix file systems, this field
|
||||
will be zero.
|
||||
* `uid` - Indicates the owner of the file.
|
||||
* `gid` - Gives the group that the owner of the file belongs to. Will be
|
||||
zero for non-Unix file systems.
|
||||
|
||||
The time type returned in `atime`, `mtime`, and `ctime` is dependent on the
|
||||
time type set in options. `{:time, type}` where type can be `:local`,
|
||||
`:universal`, or `:posix`. Default is `:local`.
|
||||
"""
|
||||
|
||||
record = Record.extract(:file_info, from_lib: "kernel/include/file.hrl")
|
||||
keys = :lists.map(&elem(&1, 0), record)
|
||||
vals = :lists.map(&{&1, [], nil}, keys)
|
||||
pairs = :lists.zip(keys, vals)
|
||||
|
||||
defstruct keys
|
||||
|
||||
@doc """
|
||||
Converts a `File.Stat` struct to a `:file_info` record.
|
||||
"""
|
||||
def to_record(%File.Stat{unquote_splicing(pairs)}) do
|
||||
{:file_info, unquote_splicing(vals)}
|
||||
end
|
||||
|
||||
@doc """
|
||||
Converts a `:file_info` record into a `File.Stat`.
|
||||
"""
|
||||
def from_record({:file_info, unquote_splicing(vals)}) do
|
||||
%File.Stat{unquote_splicing(pairs)}
|
||||
end
|
||||
end
|
||||
|
||||
defmodule File.Stream do
|
||||
@moduledoc """
|
||||
Defines a `File.Stream` struct returned by `File.stream!/2`.
|
||||
|
||||
The following fields are public:
|
||||
|
||||
* `path` - the file path
|
||||
* `modes` - the file modes
|
||||
* `raw` - a boolean indicating if bin functions should be used
|
||||
* `line_or_bytes` - if reading should read lines or a given amount of bytes
|
||||
|
||||
"""
|
||||
|
||||
defstruct path: nil, modes: [], line_or_bytes: :line, raw: true
|
||||
|
||||
defimpl Collectable do
|
||||
def empty(stream) do
|
||||
stream
|
||||
end
|
||||
|
||||
def into(%{path: path, modes: modes, raw: raw} = stream) do
|
||||
modes = for mode <- modes, not mode in [:read], do: mode
|
||||
|
||||
case :file.open(path, [:write|modes]) do
|
||||
{:ok, device} ->
|
||||
{:ok, into(device, stream, raw)}
|
||||
{:error, reason} ->
|
||||
raise File.Error, reason: reason, action: "stream", path: path
|
||||
end
|
||||
end
|
||||
|
||||
defp into(device, stream, raw) do
|
||||
fn
|
||||
:ok, {:cont, x} ->
|
||||
case raw do
|
||||
true -> IO.binwrite(device, x)
|
||||
false -> IO.write(device, x)
|
||||
end
|
||||
:ok, :done ->
|
||||
:file.close(device)
|
||||
stream
|
||||
:ok, :halt ->
|
||||
:file.close(device)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defimpl Enumerable do
|
||||
def reduce(%{path: path, modes: modes, line_or_bytes: line_or_bytes, raw: raw}, acc, fun) do
|
||||
modes = for mode <- modes, not mode in [:write, :append], do: mode
|
||||
|
||||
start_fun =
|
||||
fn ->
|
||||
case :file.open(path, modes) do
|
||||
{:ok, device} -> device
|
||||
{:error, reason} ->
|
||||
raise File.Error, reason: reason, action: "stream", path: path
|
||||
end
|
||||
end
|
||||
|
||||
next_fun =
|
||||
case raw do
|
||||
true -> &IO.each_binstream(&1, line_or_bytes)
|
||||
false -> &IO.each_stream(&1, line_or_bytes)
|
||||
end
|
||||
|
||||
Stream.resource(start_fun, next_fun, &:file.close/1).(acc, fun)
|
||||
end
|
||||
|
||||
def count(_stream) do
|
||||
{:error, __MODULE__}
|
||||
end
|
||||
|
||||
def member?(_stream, _term) do
|
||||
{:error, __MODULE__}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defmodule File do
|
||||
@moduledoc ~S"""
|
||||
This module contains functions to manipulate files.
|
||||
@@ -211,8 +70,7 @@ defmodule File do
|
||||
about such options and other performance considerations.
|
||||
"""
|
||||
|
||||
alias :file, as: F
|
||||
alias :filelib, as: FL
|
||||
alias :file, as: F
|
||||
|
||||
@type posix :: :file.posix()
|
||||
@type io_device :: :file.io_device()
|
||||
@@ -228,7 +86,7 @@ defmodule File do
|
||||
"""
|
||||
@spec regular?(Path.t) :: boolean
|
||||
def regular?(path) do
|
||||
FL.is_regular(String.from_char_data!(path))
|
||||
:elixir_utils.read_file_type(IO.chardata_to_string(path)) == {:ok, :regular}
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -236,7 +94,7 @@ defmodule File do
|
||||
"""
|
||||
@spec dir?(Path.t) :: boolean
|
||||
def dir?(path) do
|
||||
FL.is_dir(String.from_char_data!(path))
|
||||
:elixir_utils.read_file_type(IO.chardata_to_string(path)) == {:ok, :directory}
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -258,7 +116,7 @@ defmodule File do
|
||||
"""
|
||||
@spec exists?(Path.t) :: boolean
|
||||
def exists?(path) do
|
||||
match?({:ok, _}, F.read_file_info(String.from_char_data!(path)))
|
||||
match?({:ok, _}, F.read_file_info(IO.chardata_to_string(path)))
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -267,16 +125,17 @@ defmodule File do
|
||||
|
||||
Typical error reasons are:
|
||||
|
||||
* :eacces - Missing search or write permissions for the parent directories of `path`.
|
||||
* :eexist - There is already a file or directory named `path`.
|
||||
* :enoent - A component of `path` does not exist.
|
||||
* :enospc - There is a no space left on the device.
|
||||
* :enotdir - A component of `path` is not a directory
|
||||
On some platforms, `:enoent` is returned instead.
|
||||
* `:eacces` - missing search or write permissions for the parent
|
||||
directories of `path`
|
||||
* `:eexist` - there is already a file or directory named `path`
|
||||
* `:enoent` - a component of `path` does not exist
|
||||
* `:enospc` - there is a no space left on the device
|
||||
* `:enotdir` - a component of `path` is not a directory;
|
||||
on some platforms, `:enoent` is returned instead
|
||||
"""
|
||||
@spec mkdir(Path.t) :: :ok | {:error, posix}
|
||||
def mkdir(path) do
|
||||
F.make_dir(String.from_char_data!(path))
|
||||
F.make_dir(IO.chardata_to_string(path))
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -284,7 +143,7 @@ defmodule File do
|
||||
"""
|
||||
@spec mkdir!(Path.t) :: :ok | no_return
|
||||
def mkdir!(path) do
|
||||
path = String.from_char_data!(path)
|
||||
path = IO.chardata_to_string(path)
|
||||
case mkdir(path) do
|
||||
:ok -> :ok
|
||||
{:error, reason} ->
|
||||
@@ -298,13 +157,38 @@ defmodule File do
|
||||
|
||||
Typical error reasons are:
|
||||
|
||||
* :eacces - Missing search or write permissions for the parent directories of `path`.
|
||||
* :enospc - There is a no space left on the device.
|
||||
* :enotdir - A component of `path` is not a directory.
|
||||
* `:eacces` - missing search or write permissions for the parent
|
||||
directories of `path`
|
||||
* `:enospc` - there is a no space left on the device
|
||||
* `:enotdir` - a component of `path` is not a directory
|
||||
"""
|
||||
@spec mkdir_p(Path.t) :: :ok | {:error, posix}
|
||||
def mkdir_p(path) do
|
||||
FL.ensure_dir(Path.join(path, "."))
|
||||
do_mkdir_p(IO.chardata_to_string(path))
|
||||
end
|
||||
|
||||
defp do_mkdir_p("/") do
|
||||
:ok
|
||||
end
|
||||
|
||||
defp do_mkdir_p(path) do
|
||||
if dir?(path) do
|
||||
:ok
|
||||
else
|
||||
parent = Path.dirname(path)
|
||||
if parent == path do
|
||||
# Protect against infinite loop
|
||||
{:error, :einval}
|
||||
else
|
||||
_ = do_mkdir_p(parent)
|
||||
case :file.make_dir(path) do
|
||||
{:error, :eexist} = error ->
|
||||
if dir?(path), do: :ok, else: error
|
||||
other ->
|
||||
other
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -312,7 +196,7 @@ defmodule File do
|
||||
"""
|
||||
@spec mkdir_p!(Path.t) :: :ok | no_return
|
||||
def mkdir_p!(path) do
|
||||
path = String.from_char_data!(path)
|
||||
path = IO.chardata_to_string(path)
|
||||
case mkdir_p(path) do
|
||||
:ok -> :ok
|
||||
{:error, reason} ->
|
||||
@@ -326,19 +210,19 @@ defmodule File do
|
||||
|
||||
Typical error reasons:
|
||||
|
||||
* :enoent - The file does not exist.
|
||||
* :eacces - Missing permission for reading the file,
|
||||
or for searching one of the parent directories.
|
||||
* :eisdir - The named file is a directory.
|
||||
* :enotdir - A component of the file name is not a directory.
|
||||
On some platforms, `:enoent` is returned instead.
|
||||
* :enomem - There is not enough memory for the contents of the file.
|
||||
* `:enoent` - the file does not exist
|
||||
* `:eacces` - missing permission for reading the file,
|
||||
or for searching one of the parent directories
|
||||
* `:eisdir` - the named file is a directory
|
||||
* `:enotdir` - a component of the file name is not a directory;
|
||||
on some platforms, `:enoent` is returned instead
|
||||
* `:enomem` - there is not enough memory for the contents of the file
|
||||
|
||||
You can use `:file.format_error/1` to get a descriptive string of the error.
|
||||
"""
|
||||
@spec read(Path.t) :: {:ok, binary} | {:error, posix}
|
||||
def read(path) do
|
||||
F.read_file(String.from_char_data!(path))
|
||||
F.read_file(IO.chardata_to_string(path))
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -347,7 +231,7 @@ defmodule File do
|
||||
"""
|
||||
@spec read!(Path.t) :: binary | no_return
|
||||
def read!(path) do
|
||||
path = String.from_char_data!(path)
|
||||
path = IO.chardata_to_string(path)
|
||||
case read(path) do
|
||||
{:ok, binary} ->
|
||||
binary
|
||||
@@ -359,20 +243,19 @@ defmodule File do
|
||||
@doc """
|
||||
Returns information about the `path`. If it exists, it
|
||||
returns a `{:ok, info}` tuple, where info is a
|
||||
`File.Stat` record. Returns `{:error, reason}` with
|
||||
`File.Stat` struct. Returns `{:error, reason}` with
|
||||
the same reasons as `read/1` if a failure occurs.
|
||||
|
||||
## Options
|
||||
|
||||
The accepted options are:
|
||||
|
||||
* `:time` if the time should be `:local`, `:universal` or `:posix`.
|
||||
Default is `:local`.
|
||||
* `:time` - `:local | :universal | :posix`; default: `:local`
|
||||
|
||||
"""
|
||||
@spec stat(Path.t, stat_options) :: {:ok, File.Stat.t} | {:error, posix}
|
||||
def stat(path, opts \\ []) do
|
||||
case F.read_file_info(String.from_char_data!(path), opts) do
|
||||
case F.read_file_info(IO.chardata_to_string(path), opts) do
|
||||
{:ok, fileinfo} ->
|
||||
{:ok, File.Stat.from_record(fileinfo)}
|
||||
error ->
|
||||
@@ -386,7 +269,7 @@ defmodule File do
|
||||
"""
|
||||
@spec stat!(Path.t, stat_options) :: File.Stat.t | no_return
|
||||
def stat!(path, opts \\ []) do
|
||||
path = String.from_char_data!(path)
|
||||
path = IO.chardata_to_string(path)
|
||||
case stat(path, opts) do
|
||||
{:ok, info} -> info
|
||||
{:error, reason} ->
|
||||
@@ -400,7 +283,7 @@ defmodule File do
|
||||
"""
|
||||
@spec write_stat(Path.t, File.Stat.t, stat_options) :: :ok | {:error, posix}
|
||||
def write_stat(path, stat, opts \\ []) do
|
||||
F.write_file_info(String.from_char_data!(path), File.Stat.to_record(stat), opts)
|
||||
F.write_file_info(IO.chardata_to_string(path), File.Stat.to_record(stat), opts)
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -409,7 +292,7 @@ defmodule File do
|
||||
"""
|
||||
@spec write_stat!(Path.t, File.Stat.t, stat_options) :: :ok | no_return
|
||||
def write_stat!(path, stat, opts \\ []) do
|
||||
path = String.from_char_data!(path)
|
||||
path = IO.chardata_to_string(path)
|
||||
case write_stat(path, stat, opts) do
|
||||
:ok -> :ok
|
||||
{:error, reason} ->
|
||||
@@ -423,7 +306,7 @@ defmodule File do
|
||||
"""
|
||||
@spec touch(Path.t, :calendar.datetime) :: :ok | {:error, posix}
|
||||
def touch(path, time \\ :calendar.local_time) do
|
||||
path = String.from_char_data!(path)
|
||||
path = IO.chardata_to_string(path)
|
||||
case F.change_time(path, time) do
|
||||
{:error, :enoent} ->
|
||||
write(path, "")
|
||||
@@ -439,7 +322,7 @@ defmodule File do
|
||||
"""
|
||||
@spec touch!(Path.t, :calendar.datetime) :: :ok | no_return
|
||||
def touch!(path, time \\ :calendar.local_time) do
|
||||
path = String.from_char_data!(path)
|
||||
path = IO.chardata_to_string(path)
|
||||
case touch(path, time) do
|
||||
:ok -> :ok
|
||||
{:error, reason} ->
|
||||
@@ -482,7 +365,7 @@ defmodule File do
|
||||
"""
|
||||
@spec copy(Path.t, Path.t, pos_integer | :infinity) :: {:ok, non_neg_integer} | {:error, posix}
|
||||
def copy(source, destination, bytes_count \\ :infinity) do
|
||||
F.copy(String.from_char_data!(source), String.from_char_data!(destination), bytes_count)
|
||||
F.copy(IO.chardata_to_string(source), IO.chardata_to_string(destination), bytes_count)
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -491,8 +374,8 @@ defmodule File do
|
||||
"""
|
||||
@spec copy!(Path.t, Path.t, pos_integer | :infinity) :: non_neg_integer | no_return
|
||||
def copy!(source, destination, bytes_count \\ :infinity) do
|
||||
source = String.from_char_data!(source)
|
||||
destination = String.from_char_data!(destination)
|
||||
source = IO.chardata_to_string(source)
|
||||
destination = IO.chardata_to_string(destination)
|
||||
case copy(source, destination, bytes_count) do
|
||||
{:ok, bytes_count} -> bytes_count
|
||||
{:error, reason} ->
|
||||
@@ -522,8 +405,8 @@ defmodule File do
|
||||
"""
|
||||
@spec cp(Path.t, Path.t, (Path.t, Path.t -> boolean)) :: :ok | {:error, posix}
|
||||
def cp(source, destination, callback \\ fn(_, _) -> true end) do
|
||||
source = String.from_char_data!(source)
|
||||
destination = String.from_char_data!(destination)
|
||||
source = IO.chardata_to_string(source)
|
||||
destination = IO.chardata_to_string(destination)
|
||||
|
||||
case do_cp_file(source, destination, callback, []) do
|
||||
{:error, reason, _} -> {:error, reason}
|
||||
@@ -537,8 +420,8 @@ defmodule File do
|
||||
"""
|
||||
@spec cp!(Path.t, Path.t, (Path.t, Path.t -> boolean)) :: :ok | no_return
|
||||
def cp!(source, destination, callback \\ fn(_, _) -> true end) do
|
||||
source = String.from_char_data!(source)
|
||||
destination = String.from_char_data!(destination)
|
||||
source = IO.chardata_to_string(source)
|
||||
destination = IO.chardata_to_string(destination)
|
||||
|
||||
case cp(source, destination, callback) do
|
||||
:ok -> :ok
|
||||
@@ -593,8 +476,8 @@ defmodule File do
|
||||
"""
|
||||
@spec cp_r(Path.t, Path.t, (Path.t, Path.t -> boolean)) :: {:ok, [binary]} | {:error, posix, binary}
|
||||
def cp_r(source, destination, callback \\ fn(_, _) -> true end) when is_function(callback) do
|
||||
source = String.from_char_data!(source)
|
||||
destination = String.from_char_data!(destination)
|
||||
source = IO.chardata_to_string(source)
|
||||
destination = IO.chardata_to_string(destination)
|
||||
|
||||
case do_cp_r(source, destination, callback, []) do
|
||||
{:error, _, _} = error -> error
|
||||
@@ -608,8 +491,8 @@ defmodule File do
|
||||
"""
|
||||
@spec cp_r!(Path.t, Path.t, (Path.t, Path.t -> boolean)) :: [binary] | no_return
|
||||
def cp_r!(source, destination, callback \\ fn(_, _) -> true end) do
|
||||
source = String.from_char_data!(source)
|
||||
destination = String.from_char_data!(destination)
|
||||
source = IO.chardata_to_string(source)
|
||||
destination = IO.chardata_to_string(destination)
|
||||
|
||||
case cp_r(source, destination, callback) do
|
||||
{:ok, files} -> files
|
||||
@@ -622,7 +505,7 @@ defmodule File do
|
||||
# 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 :elixir_utils.file_type(src) do
|
||||
case :elixir_utils.read_link_type(src) do
|
||||
{:ok, :regular} ->
|
||||
do_cp_file(src, dest, callback, acc)
|
||||
{:ok, :symlink} ->
|
||||
@@ -713,19 +596,19 @@ defmodule File do
|
||||
|
||||
Typical error reasons are:
|
||||
|
||||
* :enoent - A component of the file name does not exist.
|
||||
* :enotdir - A component of the file name is not a directory.
|
||||
On some platforms, enoent is returned instead.
|
||||
* :enospc - There is a no space left on the device.
|
||||
* :eacces - Missing permission for writing the file or searching one of the parent directories.
|
||||
* :eisdir - The named file is a directory.
|
||||
* `:enoent` - a component of the file name does not exist
|
||||
* `:enotdir` - a component of the file name is not a directory;
|
||||
on some platforms, enoent is returned instead
|
||||
* `:enospc` - there is a no space left on the device
|
||||
* `:eacces` - missing permission for writing the file or searching one of
|
||||
the parent directories
|
||||
* `:eisdir` - the named file is a directory
|
||||
|
||||
The writing is automatically done in `:raw` mode. Check
|
||||
`File.open/2` for other available options.
|
||||
Check `File.open/2` for other available options.
|
||||
"""
|
||||
@spec write(Path.t, iodata, list) :: :ok | {:error, posix}
|
||||
def write(path, content, modes \\ []) do
|
||||
F.write_file(String.from_char_data!(path), content, [:raw|modes])
|
||||
F.write_file(IO.chardata_to_string(path), content, modes)
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -733,7 +616,7 @@ defmodule File do
|
||||
"""
|
||||
@spec write!(Path.t, iodata, list) :: :ok | no_return
|
||||
def write!(path, content, modes \\ []) do
|
||||
path = String.from_char_data!(path)
|
||||
path = IO.chardata_to_string(path)
|
||||
case F.write_file(path, content, modes) do
|
||||
:ok -> :ok
|
||||
{:error, reason} ->
|
||||
@@ -743,16 +626,18 @@ defmodule File do
|
||||
|
||||
@doc """
|
||||
Tries to delete the file `path`.
|
||||
|
||||
Returns `:ok` if successful, or `{:error, reason}` if an error occurs.
|
||||
Note the file is deleted even if in read-only mode.
|
||||
|
||||
Typical error reasons are:
|
||||
|
||||
* :enoent - The file does not exist.
|
||||
* :eacces - Missing permission for the file or one of its parents.
|
||||
* :eperm - The file is a directory and user is not super-user.
|
||||
* :enotdir - A component of the file name is not a directory.
|
||||
On some platforms, enoent is returned instead.
|
||||
* :einval - Filename had an improper type, such as tuple.
|
||||
* `:enoent` - the file does not exist
|
||||
* `:eacces` - missing permission for the file or one of its parents
|
||||
* `:eperm` - the file is a directory and user is not super-user
|
||||
* `:enotdir` - a component of the file name is not a directory;
|
||||
on some platforms, enoent is returned instead
|
||||
* `:einval` - filename had an improper type, such as tuple
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -765,7 +650,27 @@ defmodule File do
|
||||
"""
|
||||
@spec rm(Path.t) :: :ok | {:error, posix}
|
||||
def rm(path) do
|
||||
F.delete(String.from_char_data!(path))
|
||||
path = IO.chardata_to_string(path)
|
||||
case F.delete(path) do
|
||||
:ok ->
|
||||
:ok
|
||||
{:error, :eacces} = e ->
|
||||
change_mode_windows(path) || e
|
||||
{:error, _} = e ->
|
||||
e
|
||||
end
|
||||
end
|
||||
|
||||
defp change_mode_windows(path) do
|
||||
if match? {:win32, _}, :os.type do
|
||||
case F.read_file_info(IO.chardata_to_string(path)) do
|
||||
{:ok, file_info} when elem(file_info, 3) in [:read, :none] ->
|
||||
File.chmod(path, (elem(file_info, 7) + 0200))
|
||||
F.delete(path)
|
||||
_ ->
|
||||
nil
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -773,7 +678,7 @@ defmodule File do
|
||||
"""
|
||||
@spec rm!(Path.t) :: :ok | no_return
|
||||
def rm!(path) do
|
||||
path = String.from_char_data!(path)
|
||||
path = IO.chardata_to_string(path)
|
||||
case rm(path) do
|
||||
:ok -> :ok
|
||||
{:error, reason} ->
|
||||
@@ -796,7 +701,7 @@ defmodule File do
|
||||
"""
|
||||
@spec rmdir(Path.t) :: :ok | {:error, posix}
|
||||
def rmdir(path) do
|
||||
F.del_dir(String.from_char_data!(path))
|
||||
F.del_dir(IO.chardata_to_string(path))
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -804,7 +709,7 @@ defmodule File do
|
||||
"""
|
||||
@spec rmdir!(Path.t) :: :ok | {:error, posix}
|
||||
def rmdir!(path) do
|
||||
path = String.from_char_data!(path)
|
||||
path = IO.chardata_to_string(path)
|
||||
case rmdir(path) do
|
||||
:ok -> :ok
|
||||
{:error, reason} ->
|
||||
@@ -832,7 +737,7 @@ defmodule File do
|
||||
"""
|
||||
@spec rm_rf(Path.t) :: {:ok, [binary]} | {:error, posix, binary}
|
||||
def rm_rf(path) do
|
||||
do_rm_rf(String.from_char_data!(path), {:ok, []})
|
||||
do_rm_rf(IO.chardata_to_string(path), {:ok, []})
|
||||
end
|
||||
|
||||
defp do_rm_rf(path, {:ok, _} = entry) do
|
||||
@@ -886,9 +791,9 @@ defmodule File do
|
||||
end
|
||||
|
||||
defp safe_list_dir(path) do
|
||||
case :elixir_utils.file_type(path) do
|
||||
case :elixir_utils.read_link_type(path) do
|
||||
{:ok, :symlink} ->
|
||||
case :elixir_utils.file_type(path, :read_file_info) do
|
||||
case :elixir_utils.read_file_type(path) do
|
||||
{:ok, :directory} -> {:ok, :directory}
|
||||
_ -> {:ok, :regular}
|
||||
end
|
||||
@@ -907,7 +812,7 @@ defmodule File do
|
||||
"""
|
||||
@spec rm_rf!(Path.t) :: [binary] | no_return
|
||||
def rm_rf!(path) do
|
||||
path = String.from_char_data!(path)
|
||||
path = IO.chardata_to_string(path)
|
||||
case rm_rf(path) do
|
||||
{:ok, files} -> files
|
||||
{:error, reason, _} ->
|
||||
@@ -928,43 +833,55 @@ defmodule File do
|
||||
|
||||
The allowed modes:
|
||||
|
||||
* `:read` - The file, which must exist, is opened for reading.
|
||||
* `:read` - the file, which must exist, is opened for reading.
|
||||
|
||||
* `:write` - The file is opened for writing. It is created if it does not exist.
|
||||
If the file exists, and if write is not combined with read, the file will be truncated.
|
||||
* `:write` - the file is opened for writing. It is created if it does not
|
||||
exist.
|
||||
|
||||
* `:append` - The file will be opened for writing, and it will be created if it does not exist.
|
||||
Every write operation to a file opened with append will take place at the end of the file.
|
||||
If the file does exists, and if write is not combined with read, the file
|
||||
will be truncated.
|
||||
|
||||
* `:exclusive` - The file, when opened for writing, is created if it does not exist.
|
||||
If the file exists, open will return `{:error, :eexist}`.
|
||||
* `:append` - the file will be opened for writing, and it will be created
|
||||
if it does not exist. Every write operation to a file opened with append
|
||||
will take place at the end of the file.
|
||||
|
||||
* `:char_list` - When this term is given, read operations on the file will return char lists rather than binaries;
|
||||
* `:exclusive` - the file, when opened for writing, is created if it does
|
||||
not exist. If the file exists, open will return `{:error, :eexist}`.
|
||||
|
||||
* `:compressed` - Makes it possible to read or write gzip compressed files.
|
||||
The compressed option must be combined with either read or write, but not both.
|
||||
Note that the file size obtained with `stat/1` will most probably not
|
||||
match the number of bytes that can be read from a compressed file.
|
||||
* `:char_list` - when this term is given, read operations on the file will
|
||||
return char lists rather than binaries.
|
||||
|
||||
* `:utf8` - This option denotes how data is actually stored in the disk file and
|
||||
makes the file perform automatic translation of characters to and from utf-8.
|
||||
If data is sent to a file in a format that cannot be converted to the utf-8
|
||||
or if data is read by a function that returns data in a format that cannot cope
|
||||
with the character range of the data, an error occurs and the file will be closed.
|
||||
* `:compressed` - makes it possible to read or write gzip compressed files.
|
||||
|
||||
The compressed option must be combined with either read or write, but not
|
||||
both. Note that the file size obtained with `stat/1` will most probably
|
||||
not match the number of bytes that can be read from a compressed file.
|
||||
|
||||
* `:utf8` - this option denotes how data is actually stored in the disk
|
||||
file and makes the file perform automatic translation of characters to
|
||||
and from utf-8.
|
||||
|
||||
If data is sent to a file in a format that cannot be converted to the
|
||||
utf-8 or if data is read by a function that returns data in a format that
|
||||
cannot cope with the character range of the data, an error occurs and the
|
||||
file will be closed.
|
||||
|
||||
Check http://www.erlang.org/doc/man/file.html#open-2 for more information about
|
||||
other options like `:read_ahead` and `:delayed_write`.
|
||||
|
||||
This function returns:
|
||||
|
||||
* `{: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
|
||||
from this call can be used as an argument to the `IO` module functions.
|
||||
* `{:ok, io_device}` - the file has been opened in the requested mode.
|
||||
|
||||
* `{:error, reason}` - The file could not be opened.
|
||||
`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 from this call can be used as an argument to the
|
||||
`IO` module functions.
|
||||
|
||||
* `{:error, reason}` - the file could not be opened.
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -977,7 +894,7 @@ defmodule File do
|
||||
def open(path, modes \\ [])
|
||||
|
||||
def open(path, modes) when is_list(modes) do
|
||||
F.open(String.from_char_data!(path), open_defaults(modes, true))
|
||||
F.open(IO.chardata_to_string(path), open_defaults(modes, true))
|
||||
end
|
||||
|
||||
def open(path, function) when is_function(function) do
|
||||
@@ -989,14 +906,15 @@ defmodule File do
|
||||
|
||||
The file is opened, given to the function as argument and
|
||||
automatically closed after the function returns, regardless
|
||||
if there was an error or not.
|
||||
if there was an error when executing the function.
|
||||
|
||||
It returns `{:ok, function_result}` in case of success,
|
||||
`{:error, reason}` otherwise.
|
||||
|
||||
Do not use this function with `:delayed_write` option
|
||||
since automatically closing the file may fail
|
||||
(as writes are delayed).
|
||||
This function expects the file to be closed with success,
|
||||
which is usually the case unless the `:delayed_write` option
|
||||
is given. For this reason, we do not recommend passing
|
||||
`:delayed_write` to this function.
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -1020,11 +938,12 @@ defmodule File do
|
||||
|
||||
@doc """
|
||||
Same as `open/2` but raises an error if file could not be opened.
|
||||
|
||||
Returns the `io_device` otherwise.
|
||||
"""
|
||||
@spec open!(Path.t, list) :: io_device | no_return
|
||||
def open!(path, modes \\ []) do
|
||||
path = String.from_char_data!(path)
|
||||
path = IO.chardata_to_string(path)
|
||||
case open(path, modes) do
|
||||
{:ok, device} -> device
|
||||
{:error, reason} ->
|
||||
@@ -1034,11 +953,12 @@ defmodule File do
|
||||
|
||||
@doc """
|
||||
Same as `open/3` but raises an error if file could not be opened.
|
||||
|
||||
Returns the function result otherwise.
|
||||
"""
|
||||
@spec open!(Path.t, list, (io_device -> res)) :: res | no_return when res: var
|
||||
def open!(path, modes, function) do
|
||||
path = String.from_char_data!(path)
|
||||
path = IO.chardata_to_string(path)
|
||||
case open(path, modes, function) do
|
||||
{:ok, device} -> device
|
||||
{:error, reason} ->
|
||||
@@ -1057,7 +977,7 @@ defmodule File do
|
||||
@spec cwd() :: {:ok, binary} | {:error, posix}
|
||||
def cwd() do
|
||||
case F.get_cwd do
|
||||
{:ok, base} -> {:ok, String.from_char_data!(base)}
|
||||
{:ok, base} -> {:ok, IO.chardata_to_string(base)}
|
||||
{:error, _} = error -> error
|
||||
end
|
||||
end
|
||||
@@ -1068,7 +988,7 @@ defmodule File do
|
||||
@spec cwd!() :: binary | no_return
|
||||
def cwd!() do
|
||||
case F.get_cwd do
|
||||
{:ok, cwd} -> String.from_char_data!(cwd)
|
||||
{:ok, cwd} -> IO.chardata_to_string(cwd)
|
||||
{:error, reason} ->
|
||||
raise File.Error, reason: reason, action: "get current working directory"
|
||||
end
|
||||
@@ -1081,7 +1001,7 @@ defmodule File do
|
||||
"""
|
||||
@spec cd(Path.t) :: :ok | {:error, posix}
|
||||
def cd(path) do
|
||||
F.set_cwd(String.from_char_data!(path))
|
||||
F.set_cwd(IO.chardata_to_string(path))
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -1089,7 +1009,7 @@ defmodule File do
|
||||
"""
|
||||
@spec cd!(Path.t) :: :ok | no_return
|
||||
def cd!(path) do
|
||||
path = String.from_char_data!(path)
|
||||
path = IO.chardata_to_string(path)
|
||||
case F.set_cwd(path) do
|
||||
:ok -> :ok
|
||||
{:error, reason} ->
|
||||
@@ -1124,8 +1044,8 @@ defmodule File do
|
||||
"""
|
||||
@spec ls(Path.t) :: {:ok, [binary]} | {:error, posix}
|
||||
def ls(path \\ ".") do
|
||||
case F.list_dir(String.from_char_data!(path)) do
|
||||
{:ok, file_list} -> {:ok, Enum.map(file_list, &String.from_char_data!/1)}
|
||||
case F.list_dir(IO.chardata_to_string(path)) do
|
||||
{:ok, file_list} -> {:ok, Enum.map(file_list, &IO.chardata_to_string/1)}
|
||||
{:error, _} = error -> error
|
||||
end
|
||||
end
|
||||
@@ -1136,7 +1056,7 @@ defmodule File do
|
||||
"""
|
||||
@spec ls!(Path.t) :: [binary] | no_return
|
||||
def ls!(path \\ ".") do
|
||||
path = String.from_char_data!(path)
|
||||
path = IO.chardata_to_string(path)
|
||||
case ls(path) do
|
||||
{:ok, value} -> value
|
||||
{:error, reason} ->
|
||||
@@ -1184,21 +1104,7 @@ defmodule File do
|
||||
"""
|
||||
def stream!(path, modes \\ [], line_or_bytes \\ :line) do
|
||||
modes = open_defaults(modes, true)
|
||||
raw = :lists.keyfind(:encoding, 1, modes) == false
|
||||
|
||||
modes =
|
||||
if raw do
|
||||
if :lists.keyfind(:read_ahead, 1, modes) do
|
||||
[:raw|modes]
|
||||
else
|
||||
[:raw, :read_ahead|modes]
|
||||
end
|
||||
else
|
||||
modes
|
||||
end
|
||||
|
||||
%File.Stream{path: String.from_char_data!(path), modes: modes,
|
||||
raw: raw, line_or_bytes: line_or_bytes}
|
||||
File.Stream.__build__(IO.chardata_to_string(path), modes, line_or_bytes)
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -1208,7 +1114,7 @@ defmodule File do
|
||||
"""
|
||||
@spec chmod(Path.t, integer) :: :ok | {:error, posix}
|
||||
def chmod(path, mode) do
|
||||
F.change_mode(String.from_char_data!(path), mode)
|
||||
F.change_mode(IO.chardata_to_string(path), mode)
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -1216,7 +1122,7 @@ defmodule File do
|
||||
"""
|
||||
@spec chmod!(Path.t, integer) :: :ok | no_return
|
||||
def chmod!(path, mode) do
|
||||
path = String.from_char_data!(path)
|
||||
path = IO.chardata_to_string(path)
|
||||
case chmod(path, mode) do
|
||||
:ok -> :ok
|
||||
{:error, reason} ->
|
||||
@@ -1231,7 +1137,7 @@ defmodule File do
|
||||
"""
|
||||
@spec chgrp(Path.t, integer) :: :ok | {:error, posix}
|
||||
def chgrp(path, gid) do
|
||||
F.change_group(String.from_char_data!(path), gid)
|
||||
F.change_group(IO.chardata_to_string(path), gid)
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -1239,7 +1145,7 @@ defmodule File do
|
||||
"""
|
||||
@spec chgrp!(Path.t, integer) :: :ok | no_return
|
||||
def chgrp!(path, gid) do
|
||||
path = String.from_char_data!(path)
|
||||
path = IO.chardata_to_string(path)
|
||||
case chgrp(path, gid) do
|
||||
:ok -> :ok
|
||||
{:error, reason} ->
|
||||
@@ -1254,7 +1160,7 @@ defmodule File do
|
||||
"""
|
||||
@spec chown(Path.t, integer) :: :ok | {:error, posix}
|
||||
def chown(path, uid) do
|
||||
F.change_owner(String.from_char_data!(path), uid)
|
||||
F.change_owner(IO.chardata_to_string(path), uid)
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -1262,7 +1168,7 @@ defmodule File do
|
||||
"""
|
||||
@spec chown!(Path.t, integer) :: :ok | no_return
|
||||
def chown!(path, uid) do
|
||||
path = String.from_char_data!(path)
|
||||
path = IO.chardata_to_string(path)
|
||||
case chown(path, uid) do
|
||||
:ok -> :ok
|
||||
{:error, reason} ->
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
require Record
|
||||
|
||||
defmodule File.Stat do
|
||||
@moduledoc """
|
||||
A struct responsible to hold file information.
|
||||
|
||||
In Erlang, this struct is represented by a `:file_info` record.
|
||||
Therefore this module also provides functions for converting
|
||||
in between the Erlang record and the Elixir struct.
|
||||
|
||||
Its fields are:
|
||||
|
||||
* `size` - size of file in bytes.
|
||||
|
||||
* `type` - `:device | :directory | :regular | :other`; the type of the
|
||||
file.
|
||||
|
||||
* `access` - `:read | :write | :read_write | :none`; the current system
|
||||
access to the file.
|
||||
|
||||
* `atime` - the last time the file was read.
|
||||
|
||||
* `mtime` - the last time the file was written.
|
||||
|
||||
* `ctime` - the interpretation of this time field depends on the operating
|
||||
system. On Unix, it is the last time the file or the inode was changed.
|
||||
In Windows, it is the time of creation.
|
||||
|
||||
* `mode` - the file permissions.
|
||||
|
||||
* `links` - the number of links to this file. This is always 1 for file
|
||||
systems which have no concept of links.
|
||||
|
||||
* `major_device` - identifies the file system where the file is located.
|
||||
In windows, the number indicates a drive as follows: 0 means A:, 1 means
|
||||
B:, and so on.
|
||||
|
||||
* `minor_device` - only valid for character devices on Unix. In all other
|
||||
cases, this field is zero.
|
||||
|
||||
* `inode` - gives the inode number. On non-Unix file systems, this field
|
||||
will be zero.
|
||||
|
||||
* `uid` - indicates the owner of the file.
|
||||
|
||||
* `gid` - gives the group that the owner of the file belongs to. Will be
|
||||
zero for non-Unix file systems.
|
||||
|
||||
The time type returned in `atime`, `mtime`, and `ctime` is dependent on the
|
||||
time type set in options. `{:time, type}` where type can be `:local`,
|
||||
`:universal`, or `:posix`. Default is `:local`.
|
||||
"""
|
||||
|
||||
record = Record.extract(:file_info, from_lib: "kernel/include/file.hrl")
|
||||
keys = :lists.map(&elem(&1, 0), record)
|
||||
vals = :lists.map(&{&1, [], nil}, keys)
|
||||
pairs = :lists.zip(keys, vals)
|
||||
|
||||
defstruct keys
|
||||
|
||||
@doc """
|
||||
Converts a `File.Stat` struct to a `:file_info` record.
|
||||
"""
|
||||
def to_record(%File.Stat{unquote_splicing(pairs)}) do
|
||||
{:file_info, unquote_splicing(vals)}
|
||||
end
|
||||
|
||||
@doc """
|
||||
Converts a `:file_info` record into a `File.Stat`.
|
||||
"""
|
||||
def from_record({:file_info, unquote_splicing(vals)}) do
|
||||
%File.Stat{unquote_splicing(pairs)}
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,96 @@
|
||||
defmodule File.Stream do
|
||||
@moduledoc """
|
||||
Defines a `File.Stream` struct returned by `File.stream!/3`.
|
||||
|
||||
The following fields are public:
|
||||
|
||||
* `path` - the file path
|
||||
* `modes` - the file modes
|
||||
* `raw` - a boolean indicating if bin functions should be used
|
||||
* `line_or_bytes` - if reading should read lines or a given amount of bytes
|
||||
|
||||
"""
|
||||
|
||||
defstruct path: nil, modes: [], line_or_bytes: :line, raw: true
|
||||
|
||||
@doc false
|
||||
def __build__(path, modes, line_or_bytes) do
|
||||
raw = :lists.keyfind(:encoding, 1, modes) == false
|
||||
|
||||
modes =
|
||||
if raw do
|
||||
if :lists.keyfind(:read_ahead, 1, modes) == {:read_ahead, false} do
|
||||
[:raw|modes]
|
||||
else
|
||||
[:raw, :read_ahead|modes]
|
||||
end
|
||||
else
|
||||
modes
|
||||
end
|
||||
|
||||
%File.Stream{path: path, modes: modes, raw: raw, line_or_bytes: line_or_bytes}
|
||||
end
|
||||
|
||||
defimpl Collectable do
|
||||
def empty(stream) do
|
||||
stream
|
||||
end
|
||||
|
||||
def into(%{path: path, modes: modes, raw: raw} = stream) do
|
||||
modes = for mode <- modes, not mode in [:read], do: mode
|
||||
|
||||
case :file.open(path, [:write|modes]) do
|
||||
{:ok, device} ->
|
||||
{:ok, into(device, stream, raw)}
|
||||
{:error, reason} ->
|
||||
raise File.Error, reason: reason, action: "stream", path: path
|
||||
end
|
||||
end
|
||||
|
||||
defp into(device, stream, raw) do
|
||||
fn
|
||||
:ok, {:cont, x} ->
|
||||
case raw do
|
||||
true -> IO.binwrite(device, x)
|
||||
false -> IO.write(device, x)
|
||||
end
|
||||
:ok, :done ->
|
||||
:file.close(device)
|
||||
stream
|
||||
:ok, :halt ->
|
||||
:file.close(device)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defimpl Enumerable do
|
||||
def reduce(%{path: path, modes: modes, line_or_bytes: line_or_bytes, raw: raw}, acc, fun) do
|
||||
modes = for mode <- modes, not mode in [:write, :append], do: mode
|
||||
|
||||
start_fun =
|
||||
fn ->
|
||||
case :file.open(path, modes) do
|
||||
{:ok, device} -> device
|
||||
{:error, reason} ->
|
||||
raise File.Error, reason: reason, action: "stream", path: path
|
||||
end
|
||||
end
|
||||
|
||||
next_fun =
|
||||
case raw do
|
||||
true -> &IO.each_binstream(&1, line_or_bytes)
|
||||
false -> &IO.each_stream(&1, line_or_bytes)
|
||||
end
|
||||
|
||||
Stream.resource(start_fun, next_fun, &:file.close/1).(acc, fun)
|
||||
end
|
||||
|
||||
def count(_stream) do
|
||||
{:error, __MODULE__}
|
||||
end
|
||||
|
||||
def member?(_stream, _term) do
|
||||
{:error, __MODULE__}
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -169,4 +169,86 @@ defmodule Float do
|
||||
def round(number, precision) when is_float(number) and is_integer(precision) and precision in 0..15 do
|
||||
Kernel.round(number * :math.pow(10, precision)) / :math.pow(10, precision)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns a char list which corresponds to the text representation of the given float.
|
||||
|
||||
Inlined by the compiler.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Float.to_char_list(7.0)
|
||||
'7.00000000000000000000e+00'
|
||||
|
||||
"""
|
||||
@spec to_char_list(float) :: char_list
|
||||
def to_char_list(number) do
|
||||
:erlang.float_to_list(number)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns a list which corresponds to the text representation
|
||||
of `float`.
|
||||
|
||||
## Options
|
||||
|
||||
* `:decimals` — number of decimal points to show
|
||||
* `:scientific` — number of decimal points to show, in scientific format
|
||||
* `:compact` — when true, use the most compact representation (ignored
|
||||
with the `scientific` option)
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Float.to_char_list 7.1, [decimals: 2, compact: true]
|
||||
'7.1'
|
||||
|
||||
"""
|
||||
@spec to_char_list(float, list) :: char_list
|
||||
def to_char_list(float, options) do
|
||||
:erlang.float_to_list(float, expand_compact(options))
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns a binary which corresponds to the text representation
|
||||
of `some_float`.
|
||||
|
||||
Inlined by the compiler.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Float.to_string(7.0)
|
||||
"7.00000000000000000000e+00"
|
||||
|
||||
"""
|
||||
@spec to_string(float) :: String.t
|
||||
def to_string(some_float) do
|
||||
:erlang.float_to_binary(some_float)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns a binary which corresponds to the text representation
|
||||
of `float`.
|
||||
|
||||
## Options
|
||||
|
||||
* `:decimals` — number of decimal points to show
|
||||
* `:scientific` — number of decimal points to show, in scientific format
|
||||
* `:compact` — when true, use the most compact representation (ignored
|
||||
with the `scientific` option)
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Float.to_string 7.1, [decimals: 2, compact: true]
|
||||
"7.1"
|
||||
|
||||
"""
|
||||
@spec to_string(float, list) :: String.t
|
||||
def to_string(float, options) do
|
||||
:erlang.float_to_binary(float, expand_compact(options))
|
||||
end
|
||||
|
||||
defp expand_compact([{:compact, false}|t]), do: expand_compact(t)
|
||||
defp expand_compact([{:compact, true}|t]), do: [:compact|expand_compact(t)]
|
||||
defp expand_compact([h|t]), do: [h|expand_compact(t)]
|
||||
defp expand_compact([]), do: []
|
||||
end
|
||||
|
||||
@@ -0,0 +1,664 @@
|
||||
defmodule GenEvent do
|
||||
@moduledoc """
|
||||
A behaviour module for implementing event handling functionality.
|
||||
|
||||
The event handling model consists of a generic event manager
|
||||
process with an arbitrary number of event handlers which are
|
||||
added and deleted dynamically.
|
||||
|
||||
An event manager implemented using this module will have a standard
|
||||
set of interface functions and include functionality for tracing and
|
||||
error reporting. It will also fit into an supervision tree.
|
||||
|
||||
## Example
|
||||
|
||||
There are many use cases for event handlers. For example, a logging
|
||||
system can be built using event handlers where which log message is
|
||||
an event and different event handlers can be plugged to handle the
|
||||
log messages. One handler may print error messages on the terminal,
|
||||
another can write it to a file, while a third one can keep the
|
||||
messages in memory (like a buffer) until they are read.
|
||||
|
||||
As an example, let's have a GenEvent that accumulates messages until
|
||||
they are collected by an explicit call.
|
||||
|
||||
defmodule LoggerHandler do
|
||||
use GenEvent
|
||||
|
||||
# Callbacks
|
||||
|
||||
def handle_event({:log, x}, messages) do
|
||||
{:ok, [x|messages]}
|
||||
end
|
||||
|
||||
def handle_call(:messages, messages) do
|
||||
{:ok, Enum.reverse(messages), []}
|
||||
end
|
||||
end
|
||||
|
||||
{:ok, pid} = GenEvent.start_link()
|
||||
|
||||
GenEvent.add_handler(pid, LoggerHandler, [])
|
||||
#=> :ok
|
||||
|
||||
GenEvent.notify(pid, {:log, 1})
|
||||
#=> :ok
|
||||
|
||||
GenEvent.notify(pid, {:log, 2})
|
||||
#=> :ok
|
||||
|
||||
GenEvent.call(pid, LoggerHandler, :messages)
|
||||
#=> [1, 2]
|
||||
|
||||
GenEvent.call(pid, LoggerHandler, :messages)
|
||||
#=> []
|
||||
|
||||
We start a new event manager by calling `GenEvent.start_link/0`.
|
||||
Notifications can be sent to the event manager which will then
|
||||
invoke `handle_event/0` for each registered handler.
|
||||
|
||||
We can add new handlers with `add_handler/4`. Calls can also
|
||||
be made to specific handlers by using `call/3`.
|
||||
|
||||
## Callbacks
|
||||
|
||||
There are 6 callbacks required to be implemented in a `GenEvent`. By
|
||||
adding `use GenEvent` to your module, Elixir will automatically define
|
||||
all 6 callbacks for you, leaving it up to you to implement the ones
|
||||
you want to customize. The callbacks are:
|
||||
|
||||
* `init(args)` - invoked when the event handler is added.
|
||||
|
||||
It must return:
|
||||
|
||||
- `{:ok, state}`
|
||||
- `{:ok, state, :hibernate}`
|
||||
- `{:error, reason}`
|
||||
|
||||
* `handle_event(msg, state)` - invoked whenever an event is sent via
|
||||
`notify/2` or `sync_notify/2`.
|
||||
|
||||
It must return:
|
||||
|
||||
- `{:ok, new_state}`
|
||||
- `{:ok, new_state, :hibernate}`
|
||||
- `{:swap_handler, args1, new_state, handler2, args2}`
|
||||
- `:remove_handler`
|
||||
|
||||
* `handle_call(msg, state)` - invoked when a `call/3` is done to a specific
|
||||
handler.
|
||||
|
||||
It must return:
|
||||
|
||||
- `{:ok, reply, new_state}`
|
||||
- `{:ok, reply, new_state, :hibernate}`
|
||||
- `{:swap_handler, reply, args1, new_state, handler2, args2}`
|
||||
- `{:remove_handler, reply}`
|
||||
|
||||
* `handle_info(msg, state)` - invoked to handle all other messages which
|
||||
are received by the process. Must return the same values as
|
||||
`handle_event/2`.
|
||||
|
||||
It must return:
|
||||
|
||||
- `{:noreply, state}`
|
||||
- `{:noreply, state, timeout}`
|
||||
- `{:stop, reason, state}`
|
||||
|
||||
* `terminate(reason, state)` - called when the event handler is removed or
|
||||
the event manager is terminating. It can return any term.
|
||||
|
||||
* `code_change(old_vsn, state, extra)` - called when the application
|
||||
code is being upgraded live (hot code swapping).
|
||||
|
||||
It must return:
|
||||
|
||||
- `{:ok, new_state}`
|
||||
|
||||
## Name Registration
|
||||
|
||||
A GenEvent is bound to the same name registration rules as a `GenServer`.
|
||||
Read more about it in the `GenServer` docs.
|
||||
|
||||
## Streaming
|
||||
|
||||
`GenEvent`s can be streamed from and streamed with the help of `stream/2`.
|
||||
Here are some examples:
|
||||
|
||||
stream = GenEvent.stream(pid)
|
||||
|
||||
# Take the next 10 events
|
||||
Enum.take(stream, 10)
|
||||
|
||||
# Print all remaining events
|
||||
for event <- stream do
|
||||
IO.inspect event
|
||||
end
|
||||
|
||||
A stream may also be given an id, which allows all streams with the given
|
||||
id to be cancelled at any moment via `cancel_streams/1`.
|
||||
|
||||
## Learn more
|
||||
|
||||
If you wish to find out more about gen events, Elixir getting started
|
||||
guides provide a tutorial-like introduction. The documentation and links
|
||||
in Erlang can also provide extra insight.
|
||||
|
||||
* http://elixir-lang.org/getting_started/mix/1.html
|
||||
* http://www.erlang.org/doc/man/gen_event.html
|
||||
* http://learnyousomeerlang.com/event-handlers
|
||||
"""
|
||||
|
||||
@typedoc "Return values of `start*` functions"
|
||||
@type on_start :: {:ok, pid} | {:error, {:already_started, pid}}
|
||||
|
||||
@typedoc "The GenEvent manager name"
|
||||
@type name :: atom | {:global, term} | {:via, module, term}
|
||||
|
||||
@typedoc "Options used by the `start*` functions"
|
||||
@type options :: [name: name]
|
||||
|
||||
@typedoc "The event manager reference"
|
||||
@type manager :: pid | name | {atom, node}
|
||||
|
||||
@typedoc "Supported values for new handlers"
|
||||
@type handler :: module | {module, term}
|
||||
|
||||
@doc """
|
||||
Defines a `GenEvent` stream.
|
||||
|
||||
This is a struct returned by `stream/2`. The struct is public and
|
||||
contains the following fields:
|
||||
|
||||
* `:manager` - the manager reference given to `GenEvent.stream/2`
|
||||
* `:id` - the event stream id for cancellation
|
||||
* `:timeout` - the timeout in between events, defaults to `:infinity`
|
||||
* `:duration` - the duration of the subscription, defaults to `:infinity`
|
||||
* `:mode` - if the subscription mode is sync or async, defaults to `:sync`
|
||||
"""
|
||||
defstruct manager: nil, id: nil, timeout: :infinity, duration: :infinity, mode: :sync
|
||||
|
||||
@doc false
|
||||
defmacro __using__(_) do
|
||||
quote location: :keep do
|
||||
@behaviour :gen_event
|
||||
|
||||
@doc false
|
||||
def init(args) do
|
||||
{:ok, args}
|
||||
end
|
||||
|
||||
@doc false
|
||||
def handle_event(_event, state) do
|
||||
{:ok, state}
|
||||
end
|
||||
|
||||
@doc false
|
||||
def handle_call(_request, state) do
|
||||
{:ok, {:error, :bad_call}, state}
|
||||
end
|
||||
|
||||
@doc false
|
||||
def handle_info(_msg, state) do
|
||||
{:ok, state}
|
||||
end
|
||||
|
||||
@doc false
|
||||
def terminate(reason, state) do
|
||||
:ok
|
||||
end
|
||||
|
||||
@doc false
|
||||
def code_change(_old, state, _extra) do
|
||||
{:ok, state}
|
||||
end
|
||||
|
||||
defoverridable [init: 1, handle_event: 2, handle_call: 2,
|
||||
handle_info: 2, terminate: 2, code_change: 3]
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Starts an event manager linked to the current process.
|
||||
|
||||
This is often used to start the `GenEvent` as part of a supervision tree.
|
||||
|
||||
It accepts the `:name` option which is described under the `Name Registration`
|
||||
section in the `GenServer` module docs.
|
||||
|
||||
If the event manager is successfully created and initialized, the function
|
||||
returns `{:ok, pid}`, where pid is the pid of the server. If there already
|
||||
exists a process with the specified server name, the function returns
|
||||
`{:error, {:already_started, pid}}` with the pid of that process.
|
||||
|
||||
Note that a `GenEvent` started with `start_link/1` is linked to the
|
||||
parent process and will exit not only on crashes but also if the parent
|
||||
process exits with `:normal` reason.
|
||||
"""
|
||||
@spec start_link(options) :: on_start
|
||||
def start_link(options \\ []) when is_list(options) do
|
||||
do_start(:link, options)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Starts an event manager process without links (outside of a supervision tree).
|
||||
|
||||
See `start_link/1` for more information.
|
||||
"""
|
||||
@spec start(options) :: on_start
|
||||
def start(options \\ []) when is_list(options) do
|
||||
do_start(:nolink, options)
|
||||
end
|
||||
|
||||
defp do_start(mode, options) do
|
||||
case Keyword.get(options, :name) do
|
||||
nil ->
|
||||
:gen.start(:gen_event, mode, :"no callback module", [], [])
|
||||
atom when is_atom(atom) ->
|
||||
:gen.start(:gen_event, mode, {:local, atom}, :"no callback module", [], [])
|
||||
other when is_tuple(other) ->
|
||||
:gen.start(:gen_event, mode, other, :"no callback module", [], [])
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns a stream that consumes and notifies events to the `manager`.
|
||||
|
||||
The stream is a `GenEvent` struct that implements the `Enumerable`
|
||||
protocol. The supported options are:
|
||||
|
||||
* `:id` - an id to identify all live stream instances; when an `:id` is
|
||||
given, existing streams can be called with via `cancel_streams`.
|
||||
|
||||
* `:timeout` (Enumerable) - raises if no event arrives in X milliseconds.
|
||||
|
||||
* `:duration` (Enumerable) - only consume events during the X milliseconds
|
||||
from the streaming start.
|
||||
|
||||
* `:mode` - the mode to consume events, can be `:sync` (default) or
|
||||
`:async`. On sync, the event manager waits for the event to be consumed
|
||||
before moving on to the next event handler.
|
||||
|
||||
"""
|
||||
def stream(manager, options \\ []) do
|
||||
%GenEvent{manager: manager,
|
||||
id: Keyword.get(options, :id),
|
||||
timeout: Keyword.get(options, :timeout, :infinity),
|
||||
duration: Keyword.get(options, :duration, :infinity),
|
||||
mode: Keyword.get(options, :mode, :sync)}
|
||||
end
|
||||
|
||||
@doc """
|
||||
Adds a new event handler to the event `manager`.
|
||||
|
||||
The event manager will call the `init/1` callback with `args` to
|
||||
initiate the event handler and its internal state.
|
||||
|
||||
If `init/1` returns a correct value indicating successful completion,
|
||||
the event manager adds the event handler and this function returns
|
||||
`:ok`. If the callback fails with `reason` or returns `{:error, reason}`,
|
||||
the event handler is ignored and this function returns `{:EXIT, reason}`
|
||||
or `{:error, reason}`, respectively.
|
||||
|
||||
## Linked handlers
|
||||
|
||||
When adding a handler, a `:link` option with value `true` can be given.
|
||||
This means the event handler and the calling process are now linked.
|
||||
|
||||
If the calling process later terminates with `reason`, the event manager
|
||||
will delete the event handler by calling the `terminate/2` callback with
|
||||
`{:stop, reason}` as argument. If the event handler later is deleted,
|
||||
the event manager sends a message `{:gen_event_EXIT, handler, reason}`
|
||||
to the calling process. Reason is one of the following:
|
||||
|
||||
* `:normal` - if the event handler has been removed due to a call to
|
||||
`remove_handler/3`, or `:remove_handler` has been returned by a callback
|
||||
function
|
||||
|
||||
* `:shutdown` - if the event handler has been removed because the event
|
||||
manager is terminating
|
||||
|
||||
* `{:swapped, new_handler, pid}` - if the process pid has replaced the
|
||||
event handler by another
|
||||
|
||||
* a term - if the event handler is removed due to an error. Which term
|
||||
depends on the error
|
||||
|
||||
"""
|
||||
@spec add_handler(manager, handler, term, [link: boolean]) :: :ok | {:EXIT, term} | {:error, term}
|
||||
def add_handler(manager, handler, args, options \\ []) do
|
||||
case Keyword.get(options, :link, false) do
|
||||
true -> :gen_event.add_sup_handler(manager, handler, args)
|
||||
false -> :gen_event.add_handler(manager, handler, args)
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Sends an event notification to the event `manager`.
|
||||
|
||||
The event manager will call `handle_event/2` for each installed event handler.
|
||||
|
||||
`notify` is asynchronous and will return immediately after the notification is
|
||||
sent. `notify` will not fail even if the specified event manager does not exist,
|
||||
unless it is specified as `name` (atom).
|
||||
"""
|
||||
@spec notify(manager, term) :: :ok
|
||||
defdelegate notify(manager, event), to: :gen_event
|
||||
|
||||
@doc """
|
||||
Sends a sync event notification to the event `manager`.
|
||||
|
||||
In other words, this function only returns `:ok` after the event manager
|
||||
invokes the `handle_event/2` on each installed event handler.
|
||||
|
||||
See `notify/2` for more info.
|
||||
"""
|
||||
@spec sync_notify(manager, term) :: :ok
|
||||
defdelegate sync_notify(manager, event), to: :gen_event
|
||||
|
||||
@doc """
|
||||
Makes a synchronous call to the event `handler` installed in `manager`.
|
||||
|
||||
The given `request` is sent and the caller waits until a reply arrives or
|
||||
a timeout occurs. The event manager will call `handle_call/2` to handle
|
||||
the request.
|
||||
|
||||
The return value `reply` is defined in the return value of `handle_call/2`.
|
||||
If the specified event handler is not installed, the function returns
|
||||
`{:error, :bad_module}`.
|
||||
"""
|
||||
@spec call(manager, handler, term, timeout) :: term | {:error, term}
|
||||
def call(manager, handler, request, timeout \\ 5000) do
|
||||
:gen_event.call(manager, handler, request, timeout)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Cancels all streams currently running with the given `:id`.
|
||||
|
||||
In order for a stream to be cancelled, an `:id` must be passed
|
||||
when the stream is created via `stream/2`. Passing a stream without
|
||||
an id leads to an argument error.
|
||||
"""
|
||||
@spec cancel_streams(t) :: :ok
|
||||
def cancel_streams(%GenEvent{id: nil}) do
|
||||
raise ArgumentError, "cannot cancel streams without an id"
|
||||
end
|
||||
|
||||
def cancel_streams(%GenEvent{manager: manager, id: id}) do
|
||||
handlers = :gen_event.which_handlers(manager)
|
||||
|
||||
for {Enumerable.GenEvent, {handler_id, _}} = ref <- handlers,
|
||||
handler_id === id do
|
||||
:gen_event.delete_handler(manager, ref, :remove_handler)
|
||||
end
|
||||
|
||||
:ok
|
||||
end
|
||||
|
||||
@doc """
|
||||
Removes an event handler from the event `manager`.
|
||||
|
||||
The event manager will call `terminate/2` to terminate the event handler
|
||||
and return the callback value. If the specified event handler is not
|
||||
installed, the function returns `{:error, :module_not_found}`.
|
||||
"""
|
||||
@spec remove_handler(manager, handler, term) :: term | {:error, term}
|
||||
def remove_handler(manager, handler, args) do
|
||||
:gen_event.delete_handler(manager, handler, args)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Replaces an old event handler with a new one in the event `manager`.
|
||||
|
||||
First, the old event handler is deleted by calling `terminate/2` with
|
||||
the given `args1` and collects the return value. Then the new event handler
|
||||
is added and initiated by calling `init({args2, term}), where term is the
|
||||
return value of calling `terminate/2` in the old handler. This makes it
|
||||
possible to transfer information from one handler to another.
|
||||
|
||||
The new handler will be added even if the specified old event handler
|
||||
is not installed in which case `term = :error` or if the handler fails to
|
||||
terminate with a given reason.
|
||||
|
||||
If there was a linked connection between handler1 and a process pid, there
|
||||
will be a link connection between handler2 and pid instead. A new link in
|
||||
between the caller process and the new handler can also be set with by
|
||||
giving `link: true` as option. See `add_handler/4` for more information.
|
||||
|
||||
If `init/1` in the second handler returns a correct value, this function
|
||||
returns `:ok`.
|
||||
"""
|
||||
@spec swap_handler(manager, handler, term, handler, term, [link: boolean]) :: :ok | {:error, term}
|
||||
def swap_handler(manager, handler1, args1, handler2, args2, options \\ []) do
|
||||
case Keyword.get(options, :link, false) do
|
||||
true -> :gen_event.swap_sup_handler(manager, {handler1, args1}, {handler2, args2})
|
||||
false -> :gen_event.swap_handler(manager, {handler1, args1}, {handler2, args2})
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns a list of all event handlers installed in the `manager`.
|
||||
"""
|
||||
@spec which_handlers(manager) :: [handler]
|
||||
defdelegate which_handlers(manager), to: :gen_event
|
||||
|
||||
@doc """
|
||||
Terminates the event `manager`.
|
||||
|
||||
Before terminating, the event manager will call `terminate(:stop, ...)`
|
||||
for each installed event handler.
|
||||
"""
|
||||
@spec stop(manager) :: :ok
|
||||
defdelegate stop(manager), to: :gen_event
|
||||
end
|
||||
|
||||
defimpl Enumerable, for: GenEvent do
|
||||
use GenEvent
|
||||
|
||||
@doc false
|
||||
def init({_mode, mon_pid, _pid, ref} = state) do
|
||||
# Tell the mon_pid we are good to go, and send self() so that this handler
|
||||
# can be removed later without using the managers name.
|
||||
send(mon_pid, {:UP, ref, self()})
|
||||
{:ok, state}
|
||||
end
|
||||
|
||||
@doc false
|
||||
def handle_event(event, {:sync, mon_pid, pid, ref} = state) do
|
||||
sync = Process.monitor(mon_pid)
|
||||
send pid, {ref, sync, event}
|
||||
receive do
|
||||
{^sync, :done} ->
|
||||
Process.demonitor(sync, [:flush])
|
||||
:remove_handler
|
||||
{^sync, :next} ->
|
||||
Process.demonitor(sync, [:flush])
|
||||
{:ok, state}
|
||||
{:DOWN, ^sync, _, _, _} ->
|
||||
{:ok, state}
|
||||
end
|
||||
end
|
||||
|
||||
def handle_event(event, {:async, _mon_pid, pid, ref} = state) do
|
||||
send pid, {ref, nil, event}
|
||||
{:ok, state}
|
||||
end
|
||||
|
||||
def reduce(stream, acc, fun) do
|
||||
start_fun = fn() -> start(stream) end
|
||||
next_fun = &next(stream, &1)
|
||||
stop_fun = &stop(stream, &1)
|
||||
Stream.resource(start_fun, next_fun, stop_fun).(acc, wrap_reducer(fun))
|
||||
end
|
||||
|
||||
def count(_stream) do
|
||||
{:error, __MODULE__}
|
||||
end
|
||||
|
||||
def member?(_stream, _item) do
|
||||
{:error, __MODULE__}
|
||||
end
|
||||
|
||||
defp wrap_reducer(fun) do
|
||||
fn
|
||||
{nil, _manager, event}, acc ->
|
||||
fun.(event, acc)
|
||||
{ref, manager, event}, acc ->
|
||||
try do
|
||||
fun.(event, acc)
|
||||
after
|
||||
send manager, {ref, :next}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defp start(%{manager: manager, id: id, duration: duration, mode: mode} = stream) do
|
||||
{mon_pid, mon_ref} = add_handler(mode, manager, id, duration)
|
||||
send mon_pid, {:UP, mon_ref, self()}
|
||||
|
||||
receive do
|
||||
# The subscription process gave us a go.
|
||||
{:UP, ^mon_ref, manager_pid} ->
|
||||
{mon_ref, mon_pid, manager_pid}
|
||||
# The subscription process died due to an abnormal reason.
|
||||
{:DOWN, ^mon_ref, _, _, reason} ->
|
||||
exit({reason, {__MODULE__, :start, [stream]}})
|
||||
end
|
||||
end
|
||||
|
||||
defp next(%{timeout: timeout} = stream, {mon_ref, mon_pid, manager_pid} = acc) do
|
||||
# If :DOWN is received must resend it to self so that stop/2 can receive it
|
||||
# and know that the handler has been removed.
|
||||
receive do
|
||||
{:DOWN, ^mon_ref, _, _, :normal} ->
|
||||
send(self(), {:DOWN, mon_ref, :process, mon_pid, :normal})
|
||||
nil
|
||||
{:DOWN, ^mon_ref, _, _, reason} ->
|
||||
send(self(), {:DOWN, mon_ref, :process, mon_pid, :normal})
|
||||
exit({reason, {__MODULE__, :next, [stream, acc]}})
|
||||
{^mon_ref, sync_ref, event} ->
|
||||
{{sync_ref, manager_pid, event}, acc}
|
||||
after
|
||||
timeout ->
|
||||
exit({:timeout, {__MODULE__, :next, [stream, acc]}})
|
||||
end
|
||||
end
|
||||
|
||||
defp stop(%{mode: mode} = stream, {mon_ref, mon_pid, manager_pid} = acc) do
|
||||
case remove_handler(mon_ref, mon_pid, manager_pid) do
|
||||
:ok when mode == :async ->
|
||||
flush_events(mon_ref)
|
||||
:ok ->
|
||||
:ok
|
||||
{:error, reason} ->
|
||||
exit({reason, {__MODULE__, :stop, [stream, acc]}})
|
||||
end
|
||||
end
|
||||
|
||||
defp add_handler(mode, manager, id, duration) do
|
||||
parent = self()
|
||||
|
||||
# The subscription is managed by another process, that dies if
|
||||
# the handler dies, and is killed when there is a need to remove
|
||||
# the subscription.
|
||||
spawn_monitor(fn ->
|
||||
# It is possible that the handler could be removed, and then the GenEvent
|
||||
# could exit before this process has exited normally. Because the removal
|
||||
# does not cause an unlinking this process would exit with the same
|
||||
# reason. Trapping exits ensures that no errors is raised in this case.
|
||||
Process.flag(:trap_exit, true)
|
||||
parent_ref = Process.monitor(parent)
|
||||
|
||||
# Receive the notification from the parent, unless it died.
|
||||
mon_ref = receive do
|
||||
{:UP, ref, ^parent} -> ref
|
||||
{:DOWN, ^parent_ref, _, _, _} -> exit(:normal)
|
||||
end
|
||||
|
||||
cancel = cancel_ref(id, mon_ref)
|
||||
:ok = :gen_event.add_sup_handler(manager, {__MODULE__, cancel},
|
||||
{mode, self(), parent, mon_ref})
|
||||
|
||||
receive do
|
||||
# This message is already in the mailbox if we got this far.
|
||||
{:UP, ^mon_ref, manager_pid} ->
|
||||
send(parent, {:UP, mon_ref, manager_pid})
|
||||
receive do
|
||||
# The stream has finished, remove the handler.
|
||||
{:DONE, ^mon_ref} ->
|
||||
exit_handler(manager_pid, parent_ref, cancel)
|
||||
|
||||
# If the parent died, we can exit normally.
|
||||
{:DOWN, ^parent_ref, _, _, _} ->
|
||||
exit(:normal)
|
||||
|
||||
# reason should be normal unless the handler is swapped.
|
||||
{:gen_event_EXIT, {__MODULE__, ^cancel}, reason} ->
|
||||
exit(reason)
|
||||
|
||||
# Exit if the manager dies, so the streamer is notified.
|
||||
{:EXIT, ^manager_pid, :noconnection} ->
|
||||
exit({:nodedown, node(manager_pid)})
|
||||
|
||||
{:EXIT, ^manager_pid, reason} ->
|
||||
exit(reason)
|
||||
after
|
||||
# Our time is over, notify the parent.
|
||||
duration -> exit(:normal)
|
||||
end
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
defp cancel_ref(nil, mon_ref), do: mon_ref
|
||||
defp cancel_ref(id, mon_ref), do: {id, mon_ref}
|
||||
|
||||
defp exit_handler(manager_pid, parent_ref, cancel) do
|
||||
# Send exit signal so manager removes handler.
|
||||
Process.exit(manager_pid, :shutdown)
|
||||
receive do
|
||||
# If the parent died, we can exit normally.
|
||||
{:DOWN, ^parent_ref, _, _, _} ->
|
||||
exit(:normal)
|
||||
|
||||
# Probably the reason is :shutdown, which occurs when the manager receives
|
||||
# an exit signal from a handler supervising process. However whatever the
|
||||
# reason the handler has been removed so it is ok.
|
||||
{:gen_event_EXIT, {__MODULE__, ^cancel}, _} ->
|
||||
exit(:normal)
|
||||
|
||||
# The connection broke, perhaps the handler might try to forward events
|
||||
# before it removes the handler, so must exit abnormally.
|
||||
{:EXIT, ^manager_pid, :noconnection} ->
|
||||
exit({:nodedown, node(manager_pid)})
|
||||
|
||||
# The manager has exited but don't exit abnormally as the handler has died
|
||||
# with the manager and all expected events have been handled. This is ok.
|
||||
{:EXIT, ^manager_pid, _} ->
|
||||
exit(:normal)
|
||||
end
|
||||
end
|
||||
|
||||
defp remove_handler(mon_ref, mon_pid, manager_pid) do
|
||||
send(mon_pid, {:DONE, mon_ref})
|
||||
receive do
|
||||
{^mon_ref, sync, _} when sync != nil ->
|
||||
send(manager_pid, {sync, :done})
|
||||
Process.demonitor(mon_ref, [:flush])
|
||||
:ok
|
||||
{:DOWN, ^mon_ref, _, _, :normal} ->
|
||||
:ok
|
||||
{:DOWN, ^mon_ref, _, _, reason} ->
|
||||
{:error, reason}
|
||||
end
|
||||
end
|
||||
|
||||
defp flush_events(mon_ref) do
|
||||
receive do
|
||||
{^mon_ref, _, _} ->
|
||||
flush_events(mon_ref)
|
||||
after
|
||||
0 -> :ok
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -1,100 +0,0 @@
|
||||
defmodule GenEvent.Behaviour do
|
||||
@moduledoc """
|
||||
This module is a convenience for defining GenEvent callbacks in Elixir.
|
||||
|
||||
GenEvent is an OTP behaviour that encapsulates event handling functionality.
|
||||
|
||||
## Example
|
||||
|
||||
Below is an example of a GenEvent that stores notifications
|
||||
until they are fetched:
|
||||
|
||||
defmodule MyEventHandler do
|
||||
use GenEvent.Behaviour
|
||||
|
||||
# Callbacks
|
||||
|
||||
def init(_) do
|
||||
{:ok, []}
|
||||
end
|
||||
|
||||
def handle_event({:notification, x}, notifications) do
|
||||
{:ok, [x|notifications]}
|
||||
end
|
||||
|
||||
def handle_call(:notifications, notifications) do
|
||||
{:ok, Enum.reverse(notifications), []}
|
||||
end
|
||||
end
|
||||
|
||||
{:ok, pid} = :gen_event.start_link
|
||||
#=> {: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]
|
||||
|
||||
:gen_event.call(pid, MyEventHandler, :notifications)
|
||||
#=> []
|
||||
|
||||
Notice we never call the server callbacks directly, they are called
|
||||
by OTP whenever we interact with the server.
|
||||
|
||||
Starting and sending messages to 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
|
||||
"""
|
||||
|
||||
@doc false
|
||||
defmacro __using__(_) do
|
||||
quote location: :keep do
|
||||
@behaviour :gen_event
|
||||
|
||||
@doc false
|
||||
def init(args) do
|
||||
{:ok, args}
|
||||
end
|
||||
|
||||
@doc false
|
||||
def handle_event(_event, state) do
|
||||
{:ok, state}
|
||||
end
|
||||
|
||||
@doc false
|
||||
def handle_call(_request, state) do
|
||||
{:ok, :ok, state}
|
||||
end
|
||||
|
||||
@doc false
|
||||
def handle_info(_msg, state) do
|
||||
{:ok, state}
|
||||
end
|
||||
|
||||
@doc false
|
||||
def terminate(reason, state) do
|
||||
:ok
|
||||
end
|
||||
|
||||
@doc false
|
||||
def code_change(_old, state, _extra) do
|
||||
{:ok, state}
|
||||
end
|
||||
|
||||
defoverridable [init: 1,
|
||||
handle_event: 2,
|
||||
handle_call: 2, handle_info: 2,
|
||||
terminate: 2, code_change: 3]
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,430 @@
|
||||
defmodule GenServer do
|
||||
@moduledoc """
|
||||
A behaviour module for implementing the server of a client-server relation.
|
||||
|
||||
A GenServer is a process as any other Elixir process and it can be used
|
||||
to keep state, execute code asynchronously and so on. The advantage of using
|
||||
a generic server process (GenServer) implemented using this module is that it
|
||||
will have a standard set of interface functions and include functionality for
|
||||
tracing and error reporting. It will also fit into a supervision tree.
|
||||
|
||||
## Example
|
||||
|
||||
The GenServer behaviour abstracts the common client-server interaction.
|
||||
Developer are only required to implement the callbacks and functionality they are
|
||||
interested in.
|
||||
|
||||
Let's start with a code example and then explore the available callbacks.
|
||||
Imagine we want a GenServer that works like a stack, allowing us to push
|
||||
and pop items:
|
||||
|
||||
defmodule Stack do
|
||||
use GenServer
|
||||
|
||||
# Callbacks
|
||||
|
||||
def handle_call(:pop, _from, [h|t]) do
|
||||
{:reply, h, t}
|
||||
end
|
||||
|
||||
def handle_cast({:push, item}, state) do
|
||||
{:noreply, [item|state]}
|
||||
end
|
||||
end
|
||||
|
||||
# Start the server
|
||||
{:ok, pid} = GenServer.start_link(Stack, [:hello])
|
||||
|
||||
# This is the client
|
||||
GenServer.call(pid, :pop)
|
||||
#=> :hello
|
||||
|
||||
GenServer.cast(pid, {:push, :world})
|
||||
#=> :ok
|
||||
|
||||
GenServer.call(pid, :pop)
|
||||
#=> :world
|
||||
|
||||
We start our `Stack` by calling `start_link/3`, passing the module
|
||||
with the server implementation and its initial argument (a list
|
||||
representing the stack containing the item `:hello`). We can primarily
|
||||
interact with the server by sending two types of messages. **call**
|
||||
messages expect a reply from the server (and are therefore synchronous)
|
||||
while **cast** messages do not.
|
||||
|
||||
Every time you do a `GenServer.call/3`, the client will send a message
|
||||
that must be handled by the `handle_call/3` callback in the GenServer.
|
||||
A `cast/2` message must be handled by `handle_cast/2`.
|
||||
|
||||
## Callbacks
|
||||
|
||||
There are 6 callbacks required to be implemented in a `GenServer`. By
|
||||
adding `use GenServer` to your module, Elixir will automatically define
|
||||
all 6 callbacks for you, leaving it up to you to implement the ones
|
||||
you want to customize. The callbacks are:
|
||||
|
||||
* `init(args)` - invoked when the server is started.
|
||||
|
||||
It must return:
|
||||
|
||||
- `{:ok, state}`
|
||||
- `{:ok, state, timeout}`
|
||||
- `:ignore`
|
||||
- `{:stop, reason}`
|
||||
|
||||
* `handle_call(msg, {from, ref}, state)` and `handle_cast(msg, state)` -
|
||||
invoked to handle call (sync) and cast (async) messages.
|
||||
|
||||
It must return:
|
||||
|
||||
- `{:reply, reply, new_state}`
|
||||
- `{:reply, reply, new_state, timeout}`
|
||||
- `{:reply, reply, new_state, :hibernate}`
|
||||
- `{:noreply, new_state}`
|
||||
- `{:noreply, new_state, timeout}`
|
||||
- `{:noreply, new_state, :hibernate}`
|
||||
- `{:stop, reason, new_state}`
|
||||
- `{:stop, reason, reply, new_state}`
|
||||
|
||||
* `handle_info(msg, state)` - invoked to handle all other messages which
|
||||
are received by the process.
|
||||
|
||||
It must return:
|
||||
|
||||
- `{:noreply, state}`
|
||||
- `{:noreply, state, timeout}`
|
||||
- `{:stop, reason, state}`
|
||||
|
||||
* `terminate(reason, state)` - called when the server is about to
|
||||
terminate, useful for cleaning up. It must return `:ok`.
|
||||
|
||||
* `code_change(old_vsn, state, extra)` - called when the application
|
||||
code is being upgraded live (hot code swapping).
|
||||
|
||||
It must return:
|
||||
|
||||
- `{:ok, new_state}`
|
||||
- `{:error, reason}`
|
||||
|
||||
## Name Registration
|
||||
|
||||
Both `start_link/3` and `start/3` support the `GenServer` to register
|
||||
a name on start via the `:name` option. Registered names are also
|
||||
automatically cleaned up on termination. The supported values are:
|
||||
|
||||
* an atom - the GenServer is registered locally with the given name
|
||||
using `Process.register/2`.
|
||||
|
||||
* `{:global, term}`- the GenServer is registered globally with the given
|
||||
term using the functions in the `:global` module.
|
||||
|
||||
* `{:via, module, term}` - the GenServer is registered with the given
|
||||
mechanism and name. The `:via` option expects a module name to control
|
||||
the registration mechanism alongside a name which can be any term.
|
||||
|
||||
For example, we could start and register our Stack server locally as follows:
|
||||
|
||||
# Start the server and register it locally with name MyStack
|
||||
{:ok, _} = GenServer.start_link(Stack, [:hello], name: MyStack)
|
||||
|
||||
# Now messages can be sent directly to MyStack
|
||||
GenServer.call(MyStack, :pop) #=> :hello
|
||||
|
||||
Once the server is started, the remaining functions in this module (`call/3`,
|
||||
`cast/2`, and friends) will also accept an atom, or any `:global` or `:via`
|
||||
tuples. In general, the following formats are supported:
|
||||
|
||||
* a `pid`
|
||||
* an `atom` if the server is locally registered
|
||||
* `{atom, node}` if the server is locally registered at another node
|
||||
* `{:global, term}` if the server is globally registered
|
||||
* `{:via, module, name}` if the server is registered through an alternative
|
||||
registry
|
||||
|
||||
## Client / Server APIs
|
||||
|
||||
Although in the example above we have used `GenServer.start_link/3` and
|
||||
friends to directly start and communicate with the server, most of the
|
||||
time we don't call the `GenServer` functions directly. Instead, we wrap
|
||||
the calls in new functions representing the public API of the server.
|
||||
|
||||
Here is a better implementation of our Stack module:
|
||||
|
||||
defmodule Stack do
|
||||
use GenServer
|
||||
|
||||
# Client
|
||||
|
||||
def start_link(default) do
|
||||
GenServer.start_link(__MODULE__, default)
|
||||
end
|
||||
|
||||
def push(pid, item) do
|
||||
GenServer.cast(pid, {:push, item})
|
||||
end
|
||||
|
||||
def pop(pid) do
|
||||
GenServer.call(pid, :pop)
|
||||
end
|
||||
|
||||
# Server (callbacks)
|
||||
|
||||
def handle_call(:pop, _from, [h|t]) do
|
||||
{:reply, h, t}
|
||||
end
|
||||
|
||||
def handle_call(request, from, state) do
|
||||
# Call the default implementation from GenServer
|
||||
super(request, from, state)
|
||||
end
|
||||
|
||||
def handle_cast({:push, item}, state) do
|
||||
{:noreply, [item|state]}
|
||||
end
|
||||
|
||||
def handle_cast(request, state) do
|
||||
super(request, state)
|
||||
end
|
||||
end
|
||||
|
||||
In practice, it is common to have both server and client functions in
|
||||
the same module. If the server and/or client implementations are growing
|
||||
complex, you may want to have them in different modules.
|
||||
|
||||
## Learn more
|
||||
|
||||
If you wish to find out more about gen servers, Elixir getting started
|
||||
guides provide a tutorial-like introduction. The documentation and links
|
||||
in Erlang can also provide extra insight.
|
||||
|
||||
* http://elixir-lang.org/getting_started/mix/1.html
|
||||
* 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
|
||||
"""
|
||||
|
||||
@typedoc "Return values of `start*` functions"
|
||||
@type on_start :: {:ok, pid} | :ignore | {:error, {:already_started, pid} | term}
|
||||
|
||||
@typedoc "The GenServer name"
|
||||
@type name :: atom | {:global, term} | {:via, module, term}
|
||||
|
||||
@typedoc "Options used by the `start*` functions"
|
||||
@type options :: [debug: debug,
|
||||
name: name,
|
||||
timeout: timeout,
|
||||
spawn_opt: Process.spawn_opt]
|
||||
|
||||
@typedoc "debug options supported by the `start*` functions"
|
||||
@type debug :: [:trace | :log | :statistics | {:log_to_file, Path.t}]
|
||||
|
||||
@typedoc "The server reference"
|
||||
@type server :: pid | name | {atom, node}
|
||||
|
||||
@doc false
|
||||
defmacro __using__(_) do
|
||||
quote location: :keep do
|
||||
@behaviour :gen_server
|
||||
|
||||
@doc false
|
||||
def init(args) do
|
||||
{:ok, args}
|
||||
end
|
||||
|
||||
@doc false
|
||||
def handle_call(msg, _from, state) do
|
||||
{:stop, {:bad_call, msg}, state}
|
||||
end
|
||||
|
||||
@doc false
|
||||
def handle_info(_msg, state) do
|
||||
{:noreply, state}
|
||||
end
|
||||
|
||||
@doc false
|
||||
def handle_cast(msg, state) do
|
||||
{:stop, {:bad_cast, msg}, state}
|
||||
end
|
||||
|
||||
@doc false
|
||||
def terminate(_reason, _state) do
|
||||
:ok
|
||||
end
|
||||
|
||||
@doc false
|
||||
def code_change(_old, state, _extra) do
|
||||
{:ok, state}
|
||||
end
|
||||
|
||||
defoverridable [init: 1, handle_call: 3, handle_info: 2,
|
||||
handle_cast: 2, terminate: 2, code_change: 3]
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Starts a `GenServer` process linked to the current process.
|
||||
|
||||
This is often used to start the `GenServer` as part of a supervision tree.
|
||||
|
||||
Once the server is started, it calls the `init/1` function in the given `module`
|
||||
passing the given `args` to initialize it. To ensure a synchronized start-up
|
||||
procedure, this function does not return until `init/1` has returned.
|
||||
|
||||
Note that a `GenServer` started with `start_link/3` is linked to the
|
||||
parent process and will exit in case of crashes. The GenServer will also
|
||||
exit due to the `:normal` reasons in case it is configured to trap exits
|
||||
in the `init/1` callback.
|
||||
|
||||
## Options
|
||||
|
||||
The `:name` option is used for name registration as described in the module
|
||||
documentation. If the option `:timeout` option is present, the server is
|
||||
allowed to spend the given milliseconds initializing or it will be
|
||||
terminated and the start function will return `{:error, :timeout}`.
|
||||
|
||||
If the `:debug` option is present, the corresponding function in the
|
||||
[`:sys` module](http://www.erlang.org/doc/man/sys.html) will be invoked.
|
||||
|
||||
If the `:spawn_opt` option is present, its value will be passed as options
|
||||
to the underlying process as in `Process.spawn/4`.
|
||||
|
||||
## Return values
|
||||
|
||||
If the server is successfully created and initialized, the function returns
|
||||
`{:ok, pid}`, where pid is the pid of the server. If there already exists a
|
||||
process with the specified server name, the function returns
|
||||
`{:error, {:already_started, pid}}` with the pid of that process.
|
||||
|
||||
If the `init/1` callback fails with `reason`, the function returns
|
||||
`{:error, reason}`. Otherwise, if it returns `{:stop, reason}`
|
||||
or `:ignore`, the process is terminated and the function returns
|
||||
`{:error, reason}` or `:ignore`, respectively.
|
||||
"""
|
||||
@spec start_link(module, any, options) :: on_start
|
||||
def start_link(module, args, options \\ []) when is_atom(module) and is_list(options) do
|
||||
do_start(:link, module, args, options)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Starts a `GenServer` process without links (outside of a supervision tree).
|
||||
|
||||
See `start_link/3` for more information.
|
||||
"""
|
||||
@spec start(module, any, options) :: on_start
|
||||
def start(module, args, options \\ []) when is_atom(module) and is_list(options) do
|
||||
do_start(:nolink, module, args, options)
|
||||
end
|
||||
|
||||
defp do_start(link, module, args, options) do
|
||||
case Keyword.pop(options, :name) do
|
||||
{nil, opts} ->
|
||||
:gen.start(:gen_server, link, module, args, opts)
|
||||
{atom, opts} when is_atom(atom) ->
|
||||
:gen.start(:gen_server, link, {:local, atom}, module, args, opts)
|
||||
{other, opts} when is_tuple(other) ->
|
||||
:gen.start(:gen_server, link, other, module, args, opts)
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Makes a synchronous call to the `server` and waits for its reply.
|
||||
|
||||
The client sends the given `request` to the server and waits until a reply
|
||||
arrives or a timeout occurs. `handle_call/3` will be called on the server
|
||||
to handle the request.
|
||||
|
||||
The server can be any of the values described in the `Name Registration`
|
||||
section of the module documentation.
|
||||
|
||||
## Timeouts
|
||||
|
||||
The `timeout` is an integer greater than zero which specifies how many
|
||||
milliseconds to wait for a reply, or the atom `:infinity` to wait
|
||||
indefinitely. The default value is 5000. If no reply is received within
|
||||
the specified time, the function call fails. If the caller catches the
|
||||
failure and continues running, and the server is just late with the reply,
|
||||
it may arrive at any time later into the caller's message queue. The caller
|
||||
must in this case be prepared for this and discard any such garbage messages
|
||||
that are two element tuples with a reference as the first element.
|
||||
"""
|
||||
@spec call(server, term, timeout) :: term
|
||||
def call(server, request, timeout \\ 5000) do
|
||||
:gen_server.call(server, request, timeout)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Sends an asynchronous request to the `server`.
|
||||
|
||||
This function returns `:ok` immediately, regardless of whether the
|
||||
destination node or server does exists. `handle_cast/2` will be called on the
|
||||
server to handle the request.
|
||||
"""
|
||||
@spec cast(server, term) :: :ok
|
||||
defdelegate cast(server, request), to: :gen_server
|
||||
|
||||
@doc """
|
||||
Casts all servers locally registered as `name` at the specified nodes.
|
||||
|
||||
The function returns immediately and ignores nodes that do not exist, or where the
|
||||
server name does not exist.
|
||||
|
||||
See `multi_call/4` for more information.
|
||||
"""
|
||||
@spec abcast([node], name :: atom, term) :: :abcast
|
||||
def abcast(nodes \\ nodes(), name, request) do
|
||||
:gen_server.abcast(nodes, name, request)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Calls all servers locally registered as `name` at the specified `nodes`.
|
||||
|
||||
The `request` is first sent to every node and then we wait for the
|
||||
replies. This function returns a tuple containing the node and its reply
|
||||
as first element and all bad nodes as second element. The bad nodes is a
|
||||
list of nodes that either did not exist, or where a server with the given
|
||||
`name` did not exist or did not reply.
|
||||
|
||||
Nodes is a list of node names to which the request is sent. The default
|
||||
value is the list of all known nodes.
|
||||
|
||||
To avoid that late answers (after the timeout) pollute the caller's message
|
||||
queue, a middleman process is used to do the actual calls. Late answers will
|
||||
then be discarded when they arrive to a terminated process.
|
||||
"""
|
||||
@spec multi_call([node], name :: atom, term, timeout) ::
|
||||
{replies :: [{node, term}], bad_nodes :: [node]}
|
||||
def multi_call(nodes \\ nodes(), name, request, timeout \\ :infinity) do
|
||||
:gen_server.multi_call(nodes, name, request, timeout)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Replies to a client.
|
||||
|
||||
This function can be used by a server to explicitly send a reply to a
|
||||
client that called `call/3` or `multi_call/4`. When the reply cannot be
|
||||
defined in the return value of `handle_call/3`.
|
||||
|
||||
The `client` must be the `from` argument (the second argument) received
|
||||
in `handle_call/3` callbacks. Reply is an arbitrary term which will be
|
||||
given back to the client as the return value of the call.
|
||||
|
||||
This function always returns `:ok`.
|
||||
"""
|
||||
@spec reply({pid, reference}, term) :: :ok
|
||||
def reply(client, reply)
|
||||
|
||||
def reply({to, tag}, reply) do
|
||||
try do
|
||||
send(to, {tag, reply})
|
||||
:ok
|
||||
catch
|
||||
_, _ -> :ok
|
||||
end
|
||||
end
|
||||
|
||||
@compile {:inline, [nodes: 0]}
|
||||
|
||||
defp nodes do
|
||||
[node()|:erlang.nodes()]
|
||||
end
|
||||
end
|
||||
@@ -1,125 +0,0 @@
|
||||
defmodule GenServer.Behaviour do
|
||||
@moduledoc """
|
||||
This module is a convenience for defining GenServer callbacks in Elixir.
|
||||
|
||||
A server is responsible for reacting to messages received from a client.
|
||||
A GenServer is an OTP behaviour that encapsulates common server
|
||||
functionalities.
|
||||
|
||||
## Example
|
||||
|
||||
Below is an example of a GenServer that pushes and pops items
|
||||
onto a stack:
|
||||
|
||||
defmodule MyServer do
|
||||
use GenServer.Behaviour
|
||||
|
||||
# Callbacks
|
||||
|
||||
def handle_call(:pop, _from, [h|t]) do
|
||||
{:reply, h, t}
|
||||
end
|
||||
|
||||
def handle_call(request, from, config) do
|
||||
# Call the default implementation from GenServer.Behaviour
|
||||
super(request, from, config)
|
||||
end
|
||||
|
||||
def handle_cast({:push, item}, config) do
|
||||
{:noreply, [item|config]}
|
||||
end
|
||||
|
||||
def handle_cast(request, config) do
|
||||
super(request, config)
|
||||
end
|
||||
end
|
||||
|
||||
{:ok, pid} = :gen_server.start_link(MyServer, [:hello], [])
|
||||
|
||||
:gen_server.call(pid, :pop)
|
||||
#=> :hello
|
||||
|
||||
:gen_server.cast(pid, {:push, :world})
|
||||
#=> :ok
|
||||
|
||||
:gen_server.call(pid, :pop)
|
||||
#=> :world
|
||||
|
||||
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. For a
|
||||
GenServer, there are 8 different values a callback such as
|
||||
`handle_call` or `handle_cast` can return:
|
||||
|
||||
{:reply, reply, new_state}
|
||||
{:reply, reply, new_state, timeout}
|
||||
{:reply, reply, new_state, :hibernate}
|
||||
{:noreply, new_state}
|
||||
{:noreply, new_state, timeout}
|
||||
{:noreply, new_state, :hibernate}
|
||||
{:stop, reason, new_state}
|
||||
{:stop, reason, reply, new_state}
|
||||
|
||||
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 required callbacks are:
|
||||
|
||||
* `init(args)` - invoked when the server is started;
|
||||
* `handle_call(msg, from, state)` - invoked to handle call messages;
|
||||
* `handle_cast(msg, state)` - invoked to handle cast messages;
|
||||
* `handle_info(msg, state)` - handle all other messages which are
|
||||
normally received by processes;
|
||||
* `terminate(reason, state)` - called when the server is about to
|
||||
terminate, useful for cleaning up;
|
||||
* `code_change(old_vsn, state, extra)` - called when the application
|
||||
code is being upgraded live (hot code swap);
|
||||
|
||||
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
|
||||
"""
|
||||
|
||||
@doc false
|
||||
defmacro __using__(_) do
|
||||
quote location: :keep do
|
||||
@behaviour :gen_server
|
||||
|
||||
@doc false
|
||||
def init(args) do
|
||||
{:ok, args}
|
||||
end
|
||||
|
||||
@doc false
|
||||
def handle_call(request, _from, state) do
|
||||
{:stop, {:bad_call, request}, state}
|
||||
end
|
||||
|
||||
@doc false
|
||||
def handle_info(_msg, state) do
|
||||
{:noreply, state}
|
||||
end
|
||||
|
||||
@doc false
|
||||
def handle_cast(msg, state) do
|
||||
{:stop, {:bad_cast, msg}, state}
|
||||
end
|
||||
|
||||
@doc false
|
||||
def terminate(_reason, _state) do
|
||||
:ok
|
||||
end
|
||||
|
||||
@doc false
|
||||
def code_change(_old, state, _extra) do
|
||||
{:ok, state}
|
||||
end
|
||||
|
||||
defoverridable [init: 1, handle_call: 3, handle_info: 2,
|
||||
handle_cast: 2, terminate: 2, code_change: 3]
|
||||
end
|
||||
end
|
||||
end
|
||||
+42
-46
@@ -14,7 +14,7 @@ defmodule HashDict do
|
||||
functions and their APIs, please consult the `Dict` module.
|
||||
"""
|
||||
|
||||
use Dict.Behaviour
|
||||
use Dict
|
||||
|
||||
@node_bitmap 0b111
|
||||
@node_shift 3
|
||||
@@ -22,6 +22,7 @@ defmodule HashDict do
|
||||
@node_template :erlang.make_tuple(@node_size, [])
|
||||
|
||||
@opaque t :: map
|
||||
@doc false
|
||||
defstruct size: 0, root: @node_template
|
||||
|
||||
# Inline common instructions
|
||||
@@ -43,7 +44,7 @@ defmodule HashDict do
|
||||
|
||||
def update!(%HashDict{root: root, size: size} = dict, key, fun) when is_function(fun, 1) do
|
||||
{root, counter} = do_update(root, key, fn -> raise KeyError, key: key, term: dict end,
|
||||
fun, key_hash(key))
|
||||
fun, key_hash(key))
|
||||
%HashDict{root: root, size: size + counter}
|
||||
end
|
||||
|
||||
@@ -59,14 +60,14 @@ defmodule HashDict do
|
||||
def delete(dict, key) do
|
||||
case dict_delete(dict, key) do
|
||||
{dict, _value} -> dict
|
||||
:error -> dict
|
||||
:error -> dict
|
||||
end
|
||||
end
|
||||
|
||||
def pop(dict, key, default \\ nil) do
|
||||
case dict_delete(dict, key) do
|
||||
{dict, value} -> {value, dict}
|
||||
:error -> {default, dict}
|
||||
:error -> {default, dict}
|
||||
end
|
||||
end
|
||||
|
||||
@@ -83,33 +84,13 @@ defmodule HashDict do
|
||||
end)
|
||||
end
|
||||
|
||||
def split(dict, keys) do
|
||||
Enum.reduce keys, {new, dict}, fn key, {inc, exc} = acc ->
|
||||
case dict_delete(exc, key) do
|
||||
{exc, value} -> {put(inc, key, value), exc}
|
||||
:error -> acc
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def merge(%HashDict{size: size1} = dict1, %HashDict{size: size2} = dict2, callback) when size1 < size2 do
|
||||
reduce(dict1, {:cont, dict2}, fn {k, v1}, acc ->
|
||||
{:cont, update(acc, k, v1, &callback.(k, v1, &1))}
|
||||
end) |> elem(1)
|
||||
end
|
||||
|
||||
def merge(%HashDict{} = dict1, %HashDict{} = dict2, callback) do
|
||||
reduce(dict2, {:cont, dict1}, fn {k, v2}, acc ->
|
||||
{:cont, update(acc, k, v2, &callback.(k, &1, v2))}
|
||||
end) |> elem(1)
|
||||
end
|
||||
|
||||
## General helpers
|
||||
|
||||
defp dict_delete(%HashDict{root: root, size: size}, key) do
|
||||
@doc false
|
||||
def dict_delete(%HashDict{root: root, size: size}, key) do
|
||||
case do_delete(root, key, key_hash(key)) do
|
||||
{root, value} -> {%HashDict{root: root, size: size - 1}, value}
|
||||
:error -> :error
|
||||
:error -> :error
|
||||
end
|
||||
end
|
||||
|
||||
@@ -129,17 +110,17 @@ defmodule HashDict do
|
||||
index = key_mask(hash)
|
||||
case elem(node, index) do
|
||||
[] ->
|
||||
{set_elem(node, index, [key|value]), 1}
|
||||
{put_elem(node, index, [key|value]), 1}
|
||||
[^key|_] ->
|
||||
{set_elem(node, index, [key|value]), 0}
|
||||
{put_elem(node, index, [key|value]), 0}
|
||||
[k|v] ->
|
||||
n = set_elem(@node_template, key_mask(key_shift(hash)), [key|value])
|
||||
{set_elem(node, index, {k, v, n}), 1}
|
||||
n = put_elem(@node_template, key_mask(key_shift(hash)), [key|value])
|
||||
{put_elem(node, index, {k, v, n}), 1}
|
||||
{^key, _, n} ->
|
||||
{set_elem(node, index, {key, value, n}), 0}
|
||||
{put_elem(node, index, {key, value, n}), 0}
|
||||
{k, v, n} ->
|
||||
{n, counter} = do_put(n, key, value, key_shift(hash))
|
||||
{set_elem(node, index, {k, v, n}), counter}
|
||||
{put_elem(node, index, {k, v, n}), counter}
|
||||
end
|
||||
end
|
||||
|
||||
@@ -147,17 +128,17 @@ defmodule HashDict do
|
||||
index = key_mask(hash)
|
||||
case elem(node, index) do
|
||||
[] ->
|
||||
{set_elem(node, index, [key|initial.()]), 1}
|
||||
{put_elem(node, index, [key|initial.()]), 1}
|
||||
[^key|value] ->
|
||||
{set_elem(node, index, [key|fun.(value)]), 0}
|
||||
{put_elem(node, index, [key|fun.(value)]), 0}
|
||||
[k|v] ->
|
||||
n = set_elem(@node_template, key_mask(key_shift(hash)), [key|initial.()])
|
||||
{set_elem(node, index, {k, v, n}), 1}
|
||||
n = put_elem(@node_template, key_mask(key_shift(hash)), [key|initial.()])
|
||||
{put_elem(node, index, {k, v, n}), 1}
|
||||
{^key, value, n} ->
|
||||
{set_elem(node, index, {key, fun.(value), n}), 0}
|
||||
{put_elem(node, index, {key, fun.(value), n}), 0}
|
||||
{k, v, n} ->
|
||||
{n, counter} = do_update(n, key, initial, fun, key_shift(hash))
|
||||
{set_elem(node, index, {k, v, n}), counter}
|
||||
{put_elem(node, index, {k, v, n}), counter}
|
||||
end
|
||||
end
|
||||
|
||||
@@ -167,17 +148,17 @@ defmodule HashDict do
|
||||
[] ->
|
||||
:error
|
||||
[^key|value] ->
|
||||
{set_elem(node, index, []), value}
|
||||
{put_elem(node, index, []), value}
|
||||
[_|_] ->
|
||||
:error
|
||||
{^key, value, n} ->
|
||||
{set_elem(node, index, do_compact_node(n)), value}
|
||||
{put_elem(node, index, do_compact_node(n)), value}
|
||||
{k, v, n} ->
|
||||
case do_delete(n, key, key_shift(hash)) do
|
||||
{@node_template, value} ->
|
||||
{set_elem(node, index, [k|v]), value}
|
||||
{put_elem(node, index, [k|v]), value}
|
||||
{n, value} ->
|
||||
{set_elem(node, index, {k, v, n}), value}
|
||||
{put_elem(node, index, {k, v, n}), value}
|
||||
:error ->
|
||||
:error
|
||||
end
|
||||
@@ -188,12 +169,12 @@ defmodule HashDict do
|
||||
defp do_compact_node(node) when elem(node, unquote(index)) != [] do
|
||||
case elem(node, unquote(index)) do
|
||||
[k|v] ->
|
||||
case set_elem(node, unquote(index), []) do
|
||||
case put_elem(node, unquote(index), []) do
|
||||
@node_template -> [k|v]
|
||||
n -> {k, v, n}
|
||||
end
|
||||
{k, v, n} ->
|
||||
{k, v, set_elem(node, unquote(index), do_compact_node(n))}
|
||||
{k, v, put_elem(node, unquote(index), do_compact_node(n))}
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -253,7 +234,14 @@ defimpl Enumerable, for: HashDict do
|
||||
end
|
||||
|
||||
defimpl Access, for: HashDict do
|
||||
def access(dict, key), do: HashDict.get(dict, key, nil)
|
||||
def get(dict, key) do
|
||||
HashDict.get(dict, key, nil)
|
||||
end
|
||||
|
||||
def get_and_update(dict, key, fun) do
|
||||
{get, update} = fun.(HashDict.get(dict, key, nil))
|
||||
{get, HashDict.put(dict, key, update)}
|
||||
end
|
||||
end
|
||||
|
||||
defimpl Collectable, for: HashDict do
|
||||
@@ -269,3 +257,11 @@ defimpl Collectable, for: HashDict do
|
||||
end}
|
||||
end
|
||||
end
|
||||
|
||||
defimpl Inspect, for: HashDict do
|
||||
import Inspect.Algebra
|
||||
|
||||
def inspect(dict, opts) do
|
||||
concat ["#HashDict<", Inspect.List.inspect(HashDict.to_list(dict), opts), ">"]
|
||||
end
|
||||
end
|
||||
|
||||
+19
-10
@@ -22,6 +22,7 @@ defmodule HashSet do
|
||||
@node_template :erlang.make_tuple(@node_size, [])
|
||||
|
||||
@opaque t :: map
|
||||
@doc false
|
||||
defstruct size: 0, root: @node_template
|
||||
|
||||
# Inline common instructions
|
||||
@@ -134,15 +135,15 @@ defmodule HashSet do
|
||||
index = key_mask(hash)
|
||||
case elem(node, index) do
|
||||
[] ->
|
||||
{set_elem(node, index, [term]), 1}
|
||||
{put_elem(node, index, [term]), 1}
|
||||
[^term|_] ->
|
||||
{node, 0}
|
||||
[t] ->
|
||||
n = set_elem(@node_template, key_mask(key_shift(hash)), [term])
|
||||
{set_elem(node, index, [t|n]), 1}
|
||||
n = put_elem(@node_template, key_mask(key_shift(hash)), [term])
|
||||
{put_elem(node, index, [t|n]), 1}
|
||||
[t|n] ->
|
||||
{n, counter} = do_put(n, term, key_shift(hash))
|
||||
{set_elem(node, index, [t|n]), counter}
|
||||
{put_elem(node, index, [t|n]), counter}
|
||||
end
|
||||
end
|
||||
|
||||
@@ -152,17 +153,17 @@ defmodule HashSet do
|
||||
[] ->
|
||||
:error
|
||||
[^term] ->
|
||||
{:ok, set_elem(node, index, [])}
|
||||
{:ok, put_elem(node, index, [])}
|
||||
[_] ->
|
||||
:error
|
||||
[^term|n] ->
|
||||
{:ok, set_elem(node, index, do_compact_node(n))}
|
||||
{:ok, put_elem(node, index, do_compact_node(n))}
|
||||
[t|n] ->
|
||||
case do_delete(n, term, key_shift(hash)) do
|
||||
{:ok, @node_template} ->
|
||||
{:ok, set_elem(node, index, [t])}
|
||||
{:ok, put_elem(node, index, [t])}
|
||||
{:ok, n} ->
|
||||
{:ok, set_elem(node, index, [t|n])}
|
||||
{:ok, put_elem(node, index, [t|n])}
|
||||
:error ->
|
||||
:error
|
||||
end
|
||||
@@ -173,12 +174,12 @@ defmodule HashSet do
|
||||
defp do_compact_node(node) when elem(node, unquote(index)) != [] do
|
||||
case elem(node, unquote(index)) do
|
||||
[t] ->
|
||||
case set_elem(node, unquote(index), []) do
|
||||
case put_elem(node, unquote(index), []) do
|
||||
@node_template -> [t]
|
||||
n -> [t|n]
|
||||
end
|
||||
[t|n] ->
|
||||
[t|set_elem(node, unquote(index), do_compact_node(n))]
|
||||
[t|put_elem(node, unquote(index), do_compact_node(n))]
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -264,3 +265,11 @@ defimpl Collectable, for: HashSet do
|
||||
end}
|
||||
end
|
||||
end
|
||||
|
||||
defimpl Inspect, for: HashSet do
|
||||
import Inspect.Algebra
|
||||
|
||||
def inspect(set, opts) do
|
||||
concat ["#HashSet<", Inspect.List.inspect(HashSet.to_list(set), opts), ">"]
|
||||
end
|
||||
end
|
||||
|
||||
+93
-87
@@ -8,7 +8,7 @@ defprotocol Inspect do
|
||||
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
|
||||
followed by the inspecting options, represented by the struct
|
||||
`Inspect.Opts`.
|
||||
|
||||
Inspection is done using the functions available in `Inspect.Algebra`.
|
||||
@@ -70,7 +70,7 @@ defimpl Inspect, for: Atom do
|
||||
def inspect(:""), do: ":\"\""
|
||||
|
||||
def inspect(atom) do
|
||||
binary = atom_to_binary(atom)
|
||||
binary = Atom.to_string(atom)
|
||||
|
||||
cond do
|
||||
valid_ref_identifier?(binary) ->
|
||||
@@ -142,7 +142,7 @@ end
|
||||
defimpl Inspect, for: BitString do
|
||||
def inspect(thing, %Inspect.Opts{binaries: bins} = opts) when is_binary(thing) do
|
||||
if bins == :as_strings or (bins == :infer and String.printable?(thing)) do
|
||||
<< ?", escape(thing, ?") :: binary, ?" >>
|
||||
<<?", escape(thing, ?") :: binary, ?">>
|
||||
else
|
||||
inspect_bitstring(thing, opts)
|
||||
end
|
||||
@@ -202,19 +202,40 @@ defimpl Inspect, for: BitString do
|
||||
else
|
||||
<< byte :: size(8), h :: binary >> = head
|
||||
t = << h :: binary, t :: binary >>
|
||||
escape(t, char, << binary :: binary, octify(byte) :: binary >>)
|
||||
escape(t, char, << binary :: binary, escape_char(byte) :: binary >>)
|
||||
end
|
||||
end
|
||||
defp escape(<<h, t :: binary>>, char, binary) do
|
||||
escape(t, char, << binary :: binary, octify(h) :: binary >>)
|
||||
escape(t, char, << binary :: binary, escape_char(h) :: binary >>)
|
||||
end
|
||||
defp escape(<<>>, _char, binary), do: binary
|
||||
|
||||
@doc false
|
||||
# Also used by Regex
|
||||
def escape_char(char) when char in ?\000..?\377,
|
||||
do: octify(char)
|
||||
|
||||
def escape_char(char), do: hexify(char)
|
||||
|
||||
defp octify(byte) do
|
||||
<< hi :: size(2), mi :: size(3), lo :: size(3) >> = << byte >>
|
||||
<< ?\\, ?0 + hi, ?0 + mi, ?0 + lo >>
|
||||
end
|
||||
|
||||
defp hexify(char) when char < 0x10000 do
|
||||
<<a::4, b::4, c::4, d::4>> = <<char::size(16)>>
|
||||
<<?\\, ?x, ?{, to_hex(a), to_hex(b), to_hex(c), to_hex(d), ?}>>
|
||||
end
|
||||
|
||||
defp hexify(char) when char < 0x1000000 do
|
||||
<<a::4, b::4, c::4, d::4, e::4, f::4>> = <<char::size(24)>>
|
||||
<<?\\, ?x, ?{, to_hex(a), to_hex(b), to_hex(c),
|
||||
to_hex(d), to_hex(e), to_hex(f), ?}>>
|
||||
end
|
||||
|
||||
defp to_hex(c) when c in 0..9, do: ?0+c
|
||||
defp to_hex(c) when c in 10..15, do: ?a+c-10
|
||||
|
||||
defp append(<<h, t :: binary>>, binary), do: append(t, << binary :: binary, h >>)
|
||||
defp append(<<>>, binary), do: binary
|
||||
|
||||
@@ -229,11 +250,11 @@ defimpl Inspect, for: BitString do
|
||||
end
|
||||
|
||||
defp each_bit(<<h, t :: bitstring>>, counter, acc) when t != <<>> do
|
||||
each_bit(t, decrement(counter), acc <> integer_to_binary(h) <> ", ")
|
||||
each_bit(t, decrement(counter), acc <> Integer.to_string(h) <> ", ")
|
||||
end
|
||||
|
||||
defp each_bit(<<h :: size(8)>>, _counter, acc) do
|
||||
acc <> integer_to_binary(h)
|
||||
acc <> Integer.to_string(h)
|
||||
end
|
||||
|
||||
defp each_bit(<<>>, _counter, acc) do
|
||||
@@ -243,7 +264,7 @@ defimpl Inspect, for: BitString do
|
||||
defp each_bit(bitstring, _counter, acc) do
|
||||
size = bit_size(bitstring)
|
||||
<<h :: size(size)>> = bitstring
|
||||
acc <> integer_to_binary(h) <> "::size(" <> integer_to_binary(size) <> ")"
|
||||
acc <> Integer.to_string(h) <> "::size(" <> Integer.to_string(size) <> ")"
|
||||
end
|
||||
|
||||
defp decrement(:infinity), do: :infinity
|
||||
@@ -275,7 +296,7 @@ defimpl Inspect, for: List do
|
||||
def inspect(thing, %Inspect.Opts{char_lists: lists} = opts) do
|
||||
cond do
|
||||
lists == :as_char_lists or (lists == :infer and printable?(thing)) ->
|
||||
<< ?', Inspect.BitString.escape(String.from_char_data!(thing), ?') :: binary, ?' >>
|
||||
<< ?', Inspect.BitString.escape(IO.chardata_to_string(thing), ?') :: binary, ?' >>
|
||||
keyword?(thing) ->
|
||||
surround_many("[", thing, "]", opts.limit, &keyword(&1, opts))
|
||||
true ->
|
||||
@@ -291,7 +312,7 @@ defimpl Inspect, for: List do
|
||||
end
|
||||
|
||||
def keyword?([{key, _value} | rest]) when is_atom(key) do
|
||||
case atom_to_list(key) do
|
||||
case Atom.to_char_list(key) do
|
||||
'Elixir.' ++ _ -> false
|
||||
_ -> keyword?(rest)
|
||||
end
|
||||
@@ -326,63 +347,13 @@ defimpl Inspect, for: Tuple do
|
||||
def inspect({}, _opts), do: "{}"
|
||||
|
||||
def inspect(tuple, opts) do
|
||||
if opts.records do
|
||||
record_inspect(tuple, opts)
|
||||
else
|
||||
surround_many("{", tuple_to_list(tuple), "}", opts.limit, &to_doc(&1, opts))
|
||||
end
|
||||
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
|
||||
surround_record(name, fields, tail, opts)
|
||||
else
|
||||
surround_many("{", [name|tail], "}", opts.limit, &to_doc(&1, opts))
|
||||
end
|
||||
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
|
||||
concat(
|
||||
Inspect.Atom.inspect(name, opts),
|
||||
surround_many("[", zip_fields(fields, tail), "]", opts.limit, &keyword(&1, opts))
|
||||
)
|
||||
end
|
||||
|
||||
defp zip_fields([{key, _}|tk], [value|tv]) do
|
||||
case atom_to_binary(key) do
|
||||
"_" <> _ -> zip_fields(tk, tv)
|
||||
key -> [{key, value}|zip_fields(tk, tv)]
|
||||
end
|
||||
end
|
||||
|
||||
defp zip_fields([], []) do
|
||||
[]
|
||||
end
|
||||
|
||||
defp keyword({k, v}, opts) do
|
||||
concat(k <> ": ", to_doc(v, opts))
|
||||
surround_many("{", Tuple.to_list(tuple), "}", opts.limit, &to_doc(&1, opts))
|
||||
end
|
||||
end
|
||||
|
||||
defimpl Inspect, for: Map do
|
||||
def inspect(map, opts) do
|
||||
inspect(map, "", opts)
|
||||
nest inspect(map, "", opts), 1
|
||||
end
|
||||
|
||||
def inspect(map, name, opts) do
|
||||
@@ -408,20 +379,67 @@ end
|
||||
|
||||
defimpl Inspect, for: Integer do
|
||||
def inspect(thing, _opts) do
|
||||
integer_to_binary(thing)
|
||||
Integer.to_string(thing)
|
||||
end
|
||||
end
|
||||
|
||||
defimpl Inspect, for: Float do
|
||||
def inspect(thing, _opts) do
|
||||
iodata_to_binary(:io_lib_format.fwrite_g(thing))
|
||||
IO.iodata_to_binary(:io_lib_format.fwrite_g(thing))
|
||||
end
|
||||
end
|
||||
|
||||
defimpl Inspect, for: Regex do
|
||||
def inspect(regex, opts) do
|
||||
concat ["~r", to_doc(regex.source, opts), regex.opts]
|
||||
def inspect(regex, _opts) do
|
||||
delim = ?/
|
||||
concat ["~r",
|
||||
<<delim, escape(regex.source, delim)::binary, delim>>,
|
||||
regex.opts]
|
||||
end
|
||||
|
||||
defp escape(bin, term),
|
||||
do: escape(bin, <<>>, term)
|
||||
|
||||
defp escape(<<?\\, term>> <> rest, buf, term),
|
||||
do: escape(rest, buf <> <<?\\, term>>, term)
|
||||
|
||||
defp escape(<<term>> <> rest, buf, term),
|
||||
do: escape(rest, buf <> <<?\\, term>>, term)
|
||||
|
||||
# the list of characters is from `String.printable?` impl
|
||||
# minus characters treated specially by regex: \s, \d, \b, \e
|
||||
|
||||
defp escape(<<?\n>> <> rest, buf, term),
|
||||
do: escape(rest, <<buf::binary, ?\\, ?n>>, term)
|
||||
|
||||
defp escape(<<?\r>> <> rest, buf, term),
|
||||
do: escape(rest, <<buf::binary, ?\\, ?r>>, term)
|
||||
|
||||
defp escape(<<?\t>> <> rest, buf, term),
|
||||
do: escape(rest, <<buf::binary, ?\\, ?t>>, term)
|
||||
|
||||
defp escape(<<?\v>> <> rest, buf, term),
|
||||
do: escape(rest, <<buf::binary, ?\\, ?v>>, term)
|
||||
|
||||
defp escape(<<?\f>> <> rest, buf, term),
|
||||
do: escape(rest, <<buf::binary, ?\\, ?f>>, term)
|
||||
|
||||
defp escape(<<?\a>> <> rest, buf, term),
|
||||
do: escape(rest, <<buf::binary, ?\\, ?a>>, term)
|
||||
|
||||
defp escape(<<c::utf8>> <> rest, buf, term) do
|
||||
charstr = <<c::utf8>>
|
||||
if String.printable?(charstr) and not c in [?\d, ?\b, ?\e] do
|
||||
escape(rest, buf <> charstr, term)
|
||||
else
|
||||
escape(rest, buf <> Inspect.BitString.escape_char(c), term)
|
||||
end
|
||||
end
|
||||
|
||||
defp escape(<<c>> <> rest, buf, term),
|
||||
do: escape(rest, <<buf::binary, Inspect.BitString.escape_char(c)>>, term)
|
||||
|
||||
defp escape(<<>>, buf, _), do: buf
|
||||
end
|
||||
|
||||
defimpl Inspect, for: Function do
|
||||
@@ -432,7 +450,7 @@ defimpl Inspect, for: Function do
|
||||
if fun_info[:type] == :external and fun_info[:env] == [] do
|
||||
"&#{Inspect.Atom.inspect(mod)}.#{fun_info[:name]}/#{fun_info[:arity]}"
|
||||
else
|
||||
case atom_to_list(mod) do
|
||||
case Atom.to_char_list(mod) do
|
||||
'elixir_compiler_' ++ _ ->
|
||||
if function_exported?(mod, :__RELATIVE__, 0) do
|
||||
"#Function<#{uniq(fun_info)} in file:#{mod.__RELATIVE__}>"
|
||||
@@ -455,7 +473,7 @@ defimpl Inspect, for: Function do
|
||||
end
|
||||
|
||||
defp extract_name(name) do
|
||||
name = atom_to_binary(name)
|
||||
name = Atom.to_string(name)
|
||||
case :binary.split(name, "-", [:global]) do
|
||||
["", name | _] -> "." <> name
|
||||
_ -> "." <> name
|
||||
@@ -463,27 +481,27 @@ defimpl Inspect, for: Function do
|
||||
end
|
||||
|
||||
defp uniq(fun_info) do
|
||||
integer_to_binary(fun_info[:new_index]) <> "." <>
|
||||
integer_to_binary(fun_info[:uniq])
|
||||
Integer.to_string(fun_info[:new_index]) <> "." <>
|
||||
Integer.to_string(fun_info[:uniq])
|
||||
end
|
||||
end
|
||||
|
||||
defimpl Inspect, for: PID do
|
||||
def inspect(pid, _opts) do
|
||||
"#PID" <> iodata_to_binary(:erlang.pid_to_list(pid))
|
||||
"#PID" <> IO.iodata_to_binary(:erlang.pid_to_list(pid))
|
||||
end
|
||||
end
|
||||
|
||||
defimpl Inspect, for: Port do
|
||||
def inspect(port, _opts) do
|
||||
iodata_to_binary :erlang.port_to_list(port)
|
||||
IO.iodata_to_binary :erlang.port_to_list(port)
|
||||
end
|
||||
end
|
||||
|
||||
defimpl Inspect, for: Reference do
|
||||
def inspect(ref, _opts) do
|
||||
'#Ref' ++ rest = :erlang.ref_to_list(ref)
|
||||
"#Reference" <> iodata_to_binary(rest)
|
||||
"#Reference" <> IO.iodata_to_binary(rest)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -496,23 +514,11 @@ defimpl Inspect, for: Any do
|
||||
else
|
||||
dunder ->
|
||||
if :maps.keys(dunder) == :maps.keys(map) do
|
||||
Inspect.Map.inspect(:maps.remove(:__struct__, map),
|
||||
Inspect.Atom.inspect(struct, opts), opts)
|
||||
pruned = :maps.remove(:__exception__, :maps.remove(:__struct__, map))
|
||||
Inspect.Map.inspect(pruned, Inspect.Atom.inspect(struct, opts), opts)
|
||||
else
|
||||
Inspect.Map.inspect(map, opts)
|
||||
end
|
||||
end
|
||||
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
|
||||
|
||||
@@ -4,27 +4,32 @@ defmodule Inspect.Opts do
|
||||
|
||||
The following fields are available:
|
||||
|
||||
* `:structs` - when false, structs are not formatted by the inspect protocol,
|
||||
they are instead printed as maps, defaults to true;
|
||||
* `:structs` - when false, structs are not formatted by the inspect
|
||||
protocol, they are instead printed as maps, defaults to true.
|
||||
|
||||
* `:binaries` - when `:as_strings` all binaries will be printed as strings,
|
||||
non-printable bytes will be escaped; when `:as_binaries` all
|
||||
binaries will be printed in bit syntax; when the default
|
||||
`:infer`, the binary will be printed as a string if it is
|
||||
printable, otherwise in bit syntax;
|
||||
* `:binaries` - when `:as_strings` all binaries will be printed as strings,
|
||||
non-printable bytes will be escaped.
|
||||
|
||||
* `:char_lists` - when `:as_char_lists` all lists will be printed as char lists,
|
||||
non-printable elements will be escaped; when `:as_lists` all
|
||||
lists will be printed as lists; when the default `:infer`, the
|
||||
list will be printed as a char list if it is printable,
|
||||
otherwise as list;
|
||||
When `:as_binaries` all binaries will be printed in bit syntax.
|
||||
|
||||
* `:limit` - limits the number of items that are printed for tuples, bitstrings,
|
||||
and lists, does not apply to strings nor char lists, defaults to 50;
|
||||
When the default `:infer`, the binary will be printed as a string if it
|
||||
is printable, otherwise in bit syntax.
|
||||
|
||||
* `:pretty` - if set to true enables pretty printing, defaults to false;
|
||||
* `:char_lists` - when `:as_char_lists` all lists will be printed as char
|
||||
lists, non-printable elements will be escaped.
|
||||
|
||||
* `:width` - defaults to the 80 characters;
|
||||
When `:as_lists` all lists will be printed as lists.
|
||||
|
||||
When the default `:infer`, the list will be printed as a char list if it
|
||||
is printable, otherwise as list.
|
||||
|
||||
* `:limit` - limits the number of items that are printed for tuples,
|
||||
bitstrings, and lists, does not apply to strings nor char lists, defaults
|
||||
to 50.
|
||||
|
||||
* `:pretty` - if set to true enables pretty printing, defaults to false.
|
||||
|
||||
* `:width` - defaults to the 80 characters.
|
||||
"""
|
||||
|
||||
defstruct structs: true :: boolean,
|
||||
@@ -32,8 +37,7 @@ defmodule Inspect.Opts do
|
||||
char_lists: :infer :: :infer | :as_lists | :as_char_lists,
|
||||
limit: 50 :: pos_integer,
|
||||
width: 80 :: pos_integer | :infinity,
|
||||
pretty: false :: boolean,
|
||||
records: true :: boolean
|
||||
pretty: false :: boolean
|
||||
end
|
||||
|
||||
defmodule Inspect.Algebra do
|
||||
@@ -114,19 +118,25 @@ defmodule Inspect.Algebra do
|
||||
|
||||
@type t :: :doc_nil | :doc_line | doc_cons | doc_nest | doc_break | doc_group | binary
|
||||
|
||||
require Record
|
||||
|
||||
@typep doc_cons :: {:doc_cons, t, t}
|
||||
Record.defrecordp :doc_cons, left: :doc_nil, right: :doc_nil
|
||||
defmacrop doc_cons(left, right) do
|
||||
quote do: {:doc_cons, unquote(left), unquote(right)}
|
||||
end
|
||||
|
||||
@typep doc_nest :: {:doc_nest, non_neg_integer, t}
|
||||
Record.defrecordp :doc_nest, indent: 1, doc: :doc_nil
|
||||
@typep doc_nest :: {:doc_nest, t, non_neg_integer}
|
||||
defmacrop doc_nest(doc, indent) do
|
||||
quote do: {:doc_nest, unquote(doc), unquote(indent) }
|
||||
end
|
||||
|
||||
@typep doc_break :: {:doc_break, binary}
|
||||
Record.defrecordp :doc_break, str: " "
|
||||
defmacrop doc_break(break) do
|
||||
quote do: {:doc_break, unquote(break)}
|
||||
end
|
||||
|
||||
@typep doc_group :: {:doc_group, t}
|
||||
Record.defrecordp :doc_group, doc: :doc_nil
|
||||
defmacrop doc_group(group) do
|
||||
quote do: {:doc_group, unquote(group)}
|
||||
end
|
||||
|
||||
defmacrop is_doc(doc) do
|
||||
if Macro.Env.in_guard?(__CALLER__) do
|
||||
@@ -154,23 +164,6 @@ defmodule Inspect.Algebra do
|
||||
according to the inspect protocol.
|
||||
"""
|
||||
@spec to_doc(any, Inspect.Opts.t) :: t
|
||||
def to_doc(arg, %Inspect.Opts{} = opts) when is_record(arg) do
|
||||
# IO.write :stderr, "inspect records is deprecated (just remove this code when protocols+records are removed)\n#{Exception.format_stacktrace}"
|
||||
if opts.records do
|
||||
try do
|
||||
Inspect.inspect(arg, opts)
|
||||
rescue
|
||||
e ->
|
||||
res = Inspect.Tuple.inspect(arg, opts)
|
||||
raise ArgumentError, message:
|
||||
"Got #{inspect e.__record__(:name)} with message " <>
|
||||
"#{Exception.message(e)} while inspecting #{pretty(res, opts.width)}"
|
||||
end
|
||||
else
|
||||
Inspect.Tuple.inspect(arg, opts)
|
||||
end
|
||||
end
|
||||
|
||||
def to_doc(%{__struct__: struct} = map, %Inspect.Opts{} = opts) when is_atom(struct) do
|
||||
if opts.structs do
|
||||
try do
|
||||
@@ -178,8 +171,8 @@ defmodule Inspect.Algebra do
|
||||
rescue
|
||||
e ->
|
||||
res = Inspect.Map.inspect(map, opts)
|
||||
raise ArgumentError, message:
|
||||
"Got #{inspect e.__record__(:name)} with message " <>
|
||||
raise ArgumentError,
|
||||
"Got #{inspect e.__struct__} with message " <>
|
||||
"\"#{Exception.message(e)}\" while inspecting #{pretty(res, opts.width)}"
|
||||
end
|
||||
else
|
||||
@@ -192,8 +185,7 @@ defmodule Inspect.Algebra do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns `:doc_nil` which is a document entity used to represent
|
||||
nothingness. Takes no arguments.
|
||||
Returns a document entity used to represent nothingness.
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -205,8 +197,7 @@ defmodule Inspect.Algebra do
|
||||
def empty, do: :doc_nil
|
||||
|
||||
@doc """
|
||||
Concatenates two document entities. Takes two arguments:
|
||||
left doc and right doc. Returns a DocCons doc
|
||||
Concatenates two document entities.
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -217,7 +208,7 @@ defmodule Inspect.Algebra do
|
||||
"""
|
||||
@spec concat(t, t) :: doc_cons
|
||||
def concat(x, y) when is_doc(x) and is_doc(y) do
|
||||
doc_cons(left: x, right: y)
|
||||
doc_cons(x, y)
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -228,15 +219,16 @@ defmodule Inspect.Algebra do
|
||||
folddoc(docs, &concat(&1, &2))
|
||||
end
|
||||
|
||||
@doc """
|
||||
Nests document entity `x` positions deep. Nesting will be
|
||||
appended to the line breaks.
|
||||
@doc ~S"""
|
||||
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"
|
||||
iex> doc = Inspect.Algebra.nest(Inspect.Algebra.glue("hello", "world"), 5)
|
||||
iex> Inspect.Algebra.pretty(doc, 5)
|
||||
"hello\n world"
|
||||
|
||||
"""
|
||||
@spec nest(t, non_neg_integer) :: doc_nest
|
||||
@@ -245,13 +237,15 @@ defmodule Inspect.Algebra do
|
||||
end
|
||||
|
||||
def nest(x, i) when is_doc(x) and is_integer(i) do
|
||||
doc_nest(indent: i, doc: x)
|
||||
doc_nest(x, i)
|
||||
end
|
||||
|
||||
@doc ~S"""
|
||||
Document entity representing a break. This break can
|
||||
be rendered as a linebreak or as spaces, depending on the
|
||||
`mode` of the chosen layout or the provided separator.
|
||||
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
|
||||
|
||||
@@ -270,10 +264,10 @@ defmodule Inspect.Algebra do
|
||||
|
||||
"""
|
||||
@spec break(binary) :: doc_break
|
||||
def break(s) when is_binary(s), do: doc_break(str: s)
|
||||
def break(s) when is_binary(s), do: doc_break(s)
|
||||
|
||||
@spec break() :: doc_break
|
||||
def break(), do: doc_break(str: @break)
|
||||
def break(), do: doc_break(@break)
|
||||
|
||||
@doc """
|
||||
Inserts a break between two docs. See `break/1` for more info.
|
||||
@@ -317,7 +311,7 @@ defmodule Inspect.Algebra do
|
||||
"""
|
||||
@spec group(t) :: doc_group
|
||||
def group(d) when is_doc(d) do
|
||||
doc_group(doc: d)
|
||||
doc_group(d)
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -393,15 +387,15 @@ defmodule Inspect.Algebra do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> doc = Inspect.Algebra.surround_many("[", Enum.to_list(1..5), "]", :infinity, &integer_to_binary(&1))
|
||||
iex> doc = Inspect.Algebra.surround_many("[", Enum.to_list(1..5), "]", :infinity, &Integer.to_string(&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> doc = Inspect.Algebra.surround_many("[", Enum.to_list(1..5), "]", 3, &Integer.to_string(&1))
|
||||
iex> Inspect.Algebra.pretty(doc, 20)
|
||||
"[1, 2, 3, ...]"
|
||||
|
||||
iex> doc = Inspect.Algebra.surround_many("[", Enum.to_list(1..5), "]", 3, &integer_to_binary(&1), "!")
|
||||
iex> doc = Inspect.Algebra.surround_many("[", Enum.to_list(1..5), "]", 3, &Integer.to_string(&1), "!")
|
||||
iex> Inspect.Algebra.pretty(doc, 20)
|
||||
"[1! 2! 3! ...]"
|
||||
|
||||
@@ -451,7 +445,7 @@ defmodule Inspect.Algebra do
|
||||
"""
|
||||
@spec pretty(t, non_neg_integer | :infinity) :: binary
|
||||
def pretty(d, w) do
|
||||
sdoc = format w, 0, [{0, default_mode(w), doc_group(doc: d)}]
|
||||
sdoc = format w, 0, [{0, default_mode(w), doc_group(d)}]
|
||||
render(sdoc)
|
||||
end
|
||||
|
||||
@@ -465,28 +459,28 @@ defmodule Inspect.Algebra do
|
||||
|
||||
@doc false
|
||||
@spec fits?(integer, [{integer, mode, t}]) :: boolean
|
||||
def fits?(w, _) when w < 0, do: false
|
||||
def fits?(_, []), do: true
|
||||
def fits?(_, [{_, _, :doc_line} | _]), do: true
|
||||
def fits?(w, [{_, _, :doc_nil} | t]), do: fits?(w, t)
|
||||
def fits?(w, [{i, m, doc_cons(left: x, right: y)} | t]), do: fits?(w, [{i, m, x} | [{i, m, y} | t]])
|
||||
def fits?(w, [{i, m, doc_nest(indent: j, doc: x)} | t]), do: fits?(w, [{i + j, m, x} | t])
|
||||
def fits?(w, [{i, _, doc_group(doc: x)} | t]), do: fits?(w, [{i, :flat, x} | t])
|
||||
def fits?(w, [{_, _, s} | t]) when is_binary(s), do: fits?((w - byte_size s), t)
|
||||
def fits?(w, [{_, :flat, doc_break(str: s)} | t]), do: fits?((w - byte_size s), t)
|
||||
def fits?(_, [{_, :break, doc_break(str: _)} | _]), do: true
|
||||
def fits?(w, _) when w < 0, do: false
|
||||
def fits?(_, []), do: true
|
||||
def fits?(_, [{_, _, :doc_line} | _]), do: true
|
||||
def fits?(w, [{_, _, :doc_nil} | t]), do: fits?(w, t)
|
||||
def fits?(w, [{i, m, doc_cons(x, y)} | t]), do: fits?(w, [{i, m, x} | [{i, m, y} | t]])
|
||||
def fits?(w, [{i, m, doc_nest(x, j)} | t]), do: fits?(w, [{i + j, m, x} | t])
|
||||
def fits?(w, [{i, _, doc_group(x)} | t]), do: fits?(w, [{i, :flat, x} | t])
|
||||
def fits?(w, [{_, _, s} | t]) when is_binary(s), do: fits?((w - byte_size s), t)
|
||||
def fits?(w, [{_, :flat, doc_break(s)} | t]), do: fits?((w - byte_size s), t)
|
||||
def fits?(_, [{_, :break, doc_break(_)} | _]), do: true
|
||||
|
||||
@doc false
|
||||
@spec format(integer | :infinity, integer, [{integer, mode, t}]) :: [binary]
|
||||
def format(_, _, []), do: []
|
||||
def format(w, _, [{i, _, :doc_line} | t]), do: [indent(i) | format(w, i, t)]
|
||||
def format(w, k, [{_, _, :doc_nil} | t]), do: format(w, k, t)
|
||||
def format(w, k, [{i, m, doc_cons(left: x, right: y)} | t]), do: format(w, k, [{i, m, x} | [{i, m, y} | t]])
|
||||
def format(w, k, [{i, m, doc_nest(indent: j, doc: x)} | t]), do: format(w, k, [{i + j, m, x} | t])
|
||||
def format(w, k, [{i, m, doc_group(doc: x)} | t]), do: format(w, k, [{i, m, x} | t])
|
||||
def format(w, k, [{_, _, s} | t]) when is_binary(s), do: [s | format(w, (k + byte_size s), t)]
|
||||
def format(w, k, [{_, :flat, doc_break(str: s)} | t]), do: [s | format(w, (k + byte_size s), t)]
|
||||
def format(w, k, [{i, :break, doc_break(str: s)} | t]) do
|
||||
def format(_, _, []), do: []
|
||||
def format(w, _, [{i, _, :doc_line} | t]), do: [indent(i) | format(w, i, t)]
|
||||
def format(w, k, [{_, _, :doc_nil} | t]), do: format(w, k, t)
|
||||
def format(w, k, [{i, m, doc_cons(x, y)} | t]), do: format(w, k, [{i, m, x} | [{i, m, y} | t]])
|
||||
def format(w, k, [{i, m, doc_nest(x, j)} | t]), do: format(w, k, [{i + j, m, x} | t])
|
||||
def format(w, k, [{i, m, doc_group(x)} | t]), do: format(w, k, [{i, m, x} | t])
|
||||
def format(w, k, [{_, _, s} | t]) when is_binary(s), do: [s | format(w, (k + byte_size s), t)]
|
||||
def format(w, k, [{_, :flat, doc_break(s)} | t]), do: [s | format(w, (k + byte_size s), t)]
|
||||
def format(w, k, [{i, :break, doc_break(s)} | t]) do
|
||||
k = k + byte_size(s)
|
||||
|
||||
if w == :infinity or fits?(w - k, t) do
|
||||
@@ -502,6 +496,6 @@ defmodule Inspect.Algebra do
|
||||
@doc false
|
||||
@spec render([binary]) :: binary
|
||||
def render(sdoc) do
|
||||
iodata_to_binary sdoc
|
||||
IO.iodata_to_binary sdoc
|
||||
end
|
||||
end
|
||||
|
||||
@@ -69,4 +69,71 @@ defmodule Integer do
|
||||
defp do_parse(bitstring, acc) do
|
||||
{acc, bitstring}
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns a binary which corresponds to the text representation
|
||||
of `some_integer`.
|
||||
|
||||
Inlined by the compiler.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Integer.to_string(123)
|
||||
"123"
|
||||
|
||||
"""
|
||||
@spec to_string(integer) :: String.t
|
||||
def to_string(some_integer) do
|
||||
:erlang.integer_to_binary(some_integer)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns a binary which corresponds to the text representation
|
||||
of `some_integer` in base `base`.
|
||||
|
||||
Inlined by the compiler.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Integer.to_string(100, 16)
|
||||
"64"
|
||||
|
||||
"""
|
||||
@spec to_string(integer, pos_integer) :: String.t
|
||||
def to_string(some_integer, base) do
|
||||
:erlang.integer_to_binary(some_integer, base)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns a char list which corresponds to the text representation of the given integer.
|
||||
|
||||
Inlined by the compiler.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Integer.to_char_list(7)
|
||||
'7'
|
||||
|
||||
"""
|
||||
@spec to_char_list(integer) :: list
|
||||
def to_char_list(number) do
|
||||
:erlang.integer_to_list(number)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns a char list which corresponds to the text representation of the
|
||||
given integer in the given case.
|
||||
|
||||
Inlined by the compiler.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Integer.to_char_list(1023, 16)
|
||||
'3FF'
|
||||
|
||||
"""
|
||||
@spec to_char_list(integer, pos_integer) :: list
|
||||
def to_char_list(number, base) do
|
||||
:erlang.integer_to_list(number, base)
|
||||
end
|
||||
end
|
||||
|
||||
+124
-105
@@ -1,66 +1,3 @@
|
||||
defexception IO.StreamError, [:reason, :message] do
|
||||
def exception(opts) do
|
||||
reason = opts[:reason]
|
||||
formatted = iodata_to_binary(:file.format_error(reason))
|
||||
IO.StreamError[message: "error during streaming: #{formatted}", reason: reason]
|
||||
end
|
||||
end
|
||||
|
||||
defmodule IO.Stream do
|
||||
@moduledoc """
|
||||
Defines a `IO.Stream` struct returned by `IO.stream/2` and `IO.binstream/2`.
|
||||
|
||||
The following fields are public:
|
||||
|
||||
* `device` - the IO device
|
||||
* `raw` - a boolean indicating if bin functions should be used
|
||||
* `line_or_bytes` - if reading should read lines or a given amount of bytes
|
||||
|
||||
"""
|
||||
|
||||
defstruct device: nil, raw: true, line_or_bytes: :line
|
||||
|
||||
defimpl Collectable do
|
||||
def empty(stream) do
|
||||
stream
|
||||
end
|
||||
|
||||
def into(%{device: device, raw: raw} = stream) do
|
||||
{:ok, into(stream, device, raw)}
|
||||
end
|
||||
|
||||
defp into(stream, device, raw) do
|
||||
fn
|
||||
:ok, {:cont, x} ->
|
||||
case raw do
|
||||
true -> IO.binwrite(device, x)
|
||||
false -> IO.write(device, x)
|
||||
end
|
||||
:ok, _ -> stream
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defimpl Enumerable do
|
||||
def reduce(%{device: device, raw: raw, line_or_bytes: line_or_bytes}, acc, fun) do
|
||||
next_fun =
|
||||
case raw do
|
||||
true -> &IO.each_binstream(&1, line_or_bytes)
|
||||
false -> &IO.each_stream(&1, line_or_bytes)
|
||||
end
|
||||
Stream.unfold(device, next_fun).(acc, fun)
|
||||
end
|
||||
|
||||
def count(_stream) do
|
||||
{:error, __MODULE__}
|
||||
end
|
||||
|
||||
def member?(_stream, _term) do
|
||||
{:error, __MODULE__}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defmodule IO do
|
||||
@moduledoc """
|
||||
Functions handling IO.
|
||||
@@ -84,17 +21,18 @@ defmodule IO do
|
||||
the atom must be the name of a registered process. However,
|
||||
there are three exceptions for this rule:
|
||||
|
||||
* `:standard_io` - when the `:standard_io` atom is given,
|
||||
it is treated as a shortcut for `Process.group_leader`
|
||||
* `:standard_io` - when the `:standard_io` atom is given,
|
||||
it is treated as a shortcut for `Process.group_leader`
|
||||
|
||||
* `:stdio` - is a shortcut for `:standard_io`
|
||||
* `:stdio` - is a shortcut for `:standard_io`
|
||||
|
||||
* `:stderr` - is a shortcut for `:standard_error`
|
||||
* `:stderr` - is a shortcut for `:standard_error`
|
||||
|
||||
"""
|
||||
|
||||
@type device :: atom | pid
|
||||
@type nodata :: {:error, term} | :eof
|
||||
@type chardata() :: :unicode.chardata()
|
||||
|
||||
import :erlang, only: [group_leader: 0]
|
||||
|
||||
@@ -108,15 +46,15 @@ defmodule IO do
|
||||
Reads `count` characters from the IO device or until
|
||||
the end of the line if `:line` is given. It returns:
|
||||
|
||||
* `data` - The input characters.
|
||||
* `data` - the input characters
|
||||
|
||||
* `:eof` - End of file was encountered.
|
||||
* `:eof` - end of file was encountered
|
||||
|
||||
* `{:error, reason}` - Other (rare) error condition,
|
||||
for instance `{:error, :estale}` if reading from an
|
||||
NFS file system.
|
||||
* `{:error, reason}` - other (rare) error condition;
|
||||
for instance, `{:error, :estale}` if reading from an
|
||||
NFS volume
|
||||
"""
|
||||
@spec read(device, :line | non_neg_integer) :: char_data | nodata
|
||||
@spec read(device, :line | non_neg_integer) :: chardata | nodata
|
||||
def read(device \\ group_leader, chars_or_line)
|
||||
|
||||
def read(device, :line) do
|
||||
@@ -131,13 +69,13 @@ defmodule IO do
|
||||
Reads `count` bytes from the IO device or until
|
||||
the end of the line if `:line` is given. It returns:
|
||||
|
||||
* `data` - The input characters.
|
||||
* `data` - the input characters
|
||||
|
||||
* `:eof` - End of file was encountered.
|
||||
* `:eof` - end of file was encountered
|
||||
|
||||
* `{:error, reason}` - Other (rare) error condition,
|
||||
for instance `{:error, :estale}` if reading from an
|
||||
NFS file system.
|
||||
* `{:error, reason}` - other (rare) error condition;
|
||||
for instance, `{:error, :estale}` if reading from an
|
||||
NFS volume
|
||||
"""
|
||||
@spec binread(device, :line | non_neg_integer) :: iodata | nodata
|
||||
def binread(device \\ group_leader, chars_or_line)
|
||||
@@ -171,9 +109,9 @@ defmodule IO do
|
||||
#=> "error"
|
||||
|
||||
"""
|
||||
@spec write(device, char_data | String.Chars.t) :: :ok
|
||||
@spec write(device, chardata | String.Chars.t) :: :ok
|
||||
def write(device \\ group_leader(), item) do
|
||||
:io.put_chars map_dev(device), to_char_data(item)
|
||||
:io.put_chars map_dev(device), to_chardata(item)
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -190,12 +128,12 @@ defmodule IO do
|
||||
@doc """
|
||||
Writes the argument to the device, similar to `write/2`,
|
||||
but adds a newline at the end. The argument is expected
|
||||
to be a char_data.
|
||||
to be a chardata.
|
||||
"""
|
||||
@spec puts(device, char_data | String.Chars.t) :: :ok
|
||||
@spec puts(device, chardata | String.Chars.t) :: :ok
|
||||
def puts(device \\ group_leader(), item) do
|
||||
erl_dev = map_dev(device)
|
||||
:io.put_chars erl_dev, [to_char_data(item), ?\n]
|
||||
:io.put_chars erl_dev, [to_chardata(item), ?\n]
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -236,16 +174,16 @@ defmodule IO do
|
||||
Otherwise, `count` is the number of raw bytes to be retrieved.
|
||||
It returns:
|
||||
|
||||
* `data` - The input characters.
|
||||
* `data` - the input characters
|
||||
|
||||
* `:eof` - End of file was encountered.
|
||||
* `:eof` - end of file was encountered
|
||||
|
||||
* `{:error, reason}` - Other (rare) error condition,
|
||||
for instance `{:error, :estale}` if reading from an
|
||||
NFS file system.
|
||||
* `{:error, reason}` - other (rare) error condition;
|
||||
for instance, `{:error, :estale}` if reading from an
|
||||
NFS volume
|
||||
"""
|
||||
@spec getn(char_data | String.Chars.t, pos_integer) :: char_data | nodata
|
||||
@spec getn(device, char_data | String.Chars.t) :: char_data | nodata
|
||||
@spec getn(chardata | String.Chars.t, pos_integer) :: chardata | nodata
|
||||
@spec getn(device, chardata | String.Chars.t) :: chardata | nodata
|
||||
def getn(prompt, count \\ 1)
|
||||
|
||||
def getn(prompt, count) when is_integer(count) do
|
||||
@@ -262,26 +200,26 @@ defmodule IO do
|
||||
the number of unicode codepoints to be retrieved.
|
||||
Otherwise, `count` is the number of raw bytes to be retrieved.
|
||||
"""
|
||||
@spec getn(device, char_data | String.Chars.t, pos_integer) :: char_data | nodata
|
||||
@spec getn(device, chardata | String.Chars.t, pos_integer) :: chardata | nodata
|
||||
def getn(device, prompt, count) do
|
||||
:io.get_chars(map_dev(device), to_char_data(prompt), count)
|
||||
:io.get_chars(map_dev(device), to_chardata(prompt), count)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Reads a line from the IO device. It returns:
|
||||
|
||||
* `data` - The characters in the line terminated
|
||||
by a LF (or end of file).
|
||||
* `data` - the characters in the line terminated
|
||||
by a LF (or end of file)
|
||||
|
||||
* `:eof` - End of file was encountered.
|
||||
* `:eof` - end of file was encountered
|
||||
|
||||
* `{:error, reason}` - Other (rare) error condition,
|
||||
for instance `{:error, :estale}` if reading from an
|
||||
NFS file system.
|
||||
* `{:error, reason}` - other (rare) error condition;
|
||||
for instance, `{:error, :estale}` if reading from an
|
||||
NFS volume
|
||||
"""
|
||||
@spec gets(device, char_data | String.Chars.t) :: char_data | nodata
|
||||
@spec gets(device, chardata | String.Chars.t) :: chardata | nodata
|
||||
def gets(device \\ group_leader(), prompt) do
|
||||
:io.get_line(map_dev(device), to_char_data(prompt))
|
||||
:io.get_line(map_dev(device), to_chardata(prompt))
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -310,7 +248,7 @@ defmodule IO do
|
||||
"""
|
||||
@spec stream(device, :line | pos_integer) :: Enumerable.t
|
||||
def stream(device, line_or_codepoints) do
|
||||
%IO.Stream{device: map_dev(device), raw: false, line_or_bytes: line_or_codepoints}
|
||||
IO.Stream.__build__(map_dev(device), false, line_or_codepoints)
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -328,7 +266,88 @@ defmodule IO do
|
||||
"""
|
||||
@spec binstream(device, :line | pos_integer) :: Enumerable.t
|
||||
def binstream(device, line_or_bytes) do
|
||||
%IO.Stream{device: map_dev(device), raw: true, line_or_bytes: line_or_bytes}
|
||||
IO.Stream.__build__(map_dev(device), true, line_or_bytes)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Converts chardata (a list of integers representing codepoints,
|
||||
lists and strings) into a string.
|
||||
|
||||
In case the conversion fails, it raises a `UnicodeConversionError`.
|
||||
If a string is given, returns the string itself.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> IO.chardata_to_string([0x00E6, 0x00DF])
|
||||
"æß"
|
||||
|
||||
iex> IO.chardata_to_string([0x0061, "bc"])
|
||||
"abc"
|
||||
|
||||
"""
|
||||
@spec chardata_to_string(chardata) :: String.t | no_return
|
||||
def chardata_to_string(string) when is_binary(string) do
|
||||
string
|
||||
end
|
||||
|
||||
def chardata_to_string(list) when is_list(list) do
|
||||
case :unicode.characters_to_binary(list) do
|
||||
result when is_binary(result) ->
|
||||
result
|
||||
|
||||
{:error, encoded, rest} ->
|
||||
raise UnicodeConversionError, encoded: encoded, rest: rest, kind: :invalid
|
||||
|
||||
{:incomplete, encoded, rest} ->
|
||||
raise UnicodeConversionError, encoded: encoded, rest: rest, kind: :incomplete
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Converts iodata (a list of integers representing bytes, lists
|
||||
and binaries) into a binary.
|
||||
|
||||
Notice that this function treats lists of integers as raw bytes
|
||||
and does not perform any kind of encoding conversion. If you want
|
||||
to convert from a char list to a string (UTF-8 encoded), please
|
||||
use `chardata_to_string/1` instead.
|
||||
|
||||
If this function receives a binary, the same binary is returned.
|
||||
|
||||
Inlined by the compiler.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> bin1 = <<1, 2, 3>>
|
||||
iex> bin2 = <<4, 5>>
|
||||
iex> bin3 = <<6>>
|
||||
iex> IO.iodata_to_binary([bin1, 1, [2, 3, bin2], 4|bin3])
|
||||
<<1,2,3,1,2,3,4,5,4,6>>
|
||||
|
||||
iex> bin = <<1, 2, 3>>
|
||||
iex> IO.iodata_to_binary(bin)
|
||||
<<1,2,3>>
|
||||
|
||||
"""
|
||||
@spec iodata_to_binary(iodata) :: binary
|
||||
def iodata_to_binary(item) do
|
||||
:erlang.iolist_to_binary(item)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns the size of an iodata.
|
||||
|
||||
Inlined by the compiler.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> IO.iodata_length([1, 2|<<3, 4>>])
|
||||
4
|
||||
|
||||
"""
|
||||
@spec iodata_length(iodata) :: non_neg_integer
|
||||
def iodata_length(item) do
|
||||
:erlang.iolist_size(item)
|
||||
end
|
||||
|
||||
@doc false
|
||||
@@ -355,13 +374,13 @@ defmodule IO do
|
||||
end
|
||||
end
|
||||
|
||||
@compile {:inline, map_dev: 1, to_char_data: 1}
|
||||
@compile {:inline, map_dev: 1, to_chardata: 1}
|
||||
|
||||
# Map the Elixir names for standard io and error to Erlang names
|
||||
defp map_dev(:stdio), do: :standard_io
|
||||
defp map_dev(:stderr), do: :standard_error
|
||||
defp map_dev(other) when is_atom(other) or is_pid(other) or is_tuple(other), do: other
|
||||
|
||||
defp to_char_data(list) when is_list(list), do: list
|
||||
defp to_char_data(other), do: to_string(other)
|
||||
defp to_chardata(list) when is_list(list), do: list
|
||||
defp to_chardata(other), do: to_string(other)
|
||||
end
|
||||
|
||||
@@ -7,7 +7,7 @@ defmodule IO.ANSI.Sequence do
|
||||
"\e[#{unquote(code)}#{unquote(terminator)}"
|
||||
end
|
||||
|
||||
defp escape_sequence(unquote(atom_to_list(name))) do
|
||||
defp escape_sequence(unquote(Atom.to_char_list(name))) do
|
||||
unquote(name)()
|
||||
end
|
||||
end
|
||||
@@ -131,7 +131,7 @@ defmodule IO.ANSI do
|
||||
defsequence :clear, "2", "J"
|
||||
|
||||
defp escape_sequence(other) do
|
||||
raise ArgumentError, message: "invalid ANSI sequence specification: #{other}"
|
||||
raise ArgumentError, "invalid ANSI sequence specification: #{other}"
|
||||
end
|
||||
|
||||
@doc ~S"""
|
||||
@@ -155,7 +155,7 @@ defmodule IO.ANSI do
|
||||
|
||||
"""
|
||||
@spec escape(String.t, emit :: boolean) :: String.t
|
||||
def escape(string, emit \\ terminal?) do
|
||||
def escape(string, emit \\ terminal?) when is_binary(string) and is_boolean(emit) do
|
||||
{rendered, emitted} = do_escape(string, emit, false, nil, [])
|
||||
if emitted do
|
||||
rendered <> reset
|
||||
@@ -185,7 +185,7 @@ defmodule IO.ANSI do
|
||||
|
||||
"""
|
||||
@spec escape_fragment(String.t, emit :: boolean) :: String.t
|
||||
def escape_fragment(string, emit \\ terminal?) do
|
||||
def escape_fragment(string, emit \\ terminal?) when is_binary(string) and is_boolean(emit) do
|
||||
{escaped, _emitted} = do_escape(string, emit, false, nil, [])
|
||||
escaped
|
||||
end
|
||||
@@ -210,8 +210,8 @@ defmodule IO.ANSI do
|
||||
end
|
||||
|
||||
defp do_escape(<<>>, _emit, _emitted, buffer, _acc) when is_list(buffer) do
|
||||
buffer = iodata_to_binary Enum.reverse(buffer)
|
||||
raise ArgumentError, message: "missing } for escape fragment #{buffer}"
|
||||
buffer = IO.iodata_to_binary Enum.reverse(buffer)
|
||||
raise ArgumentError, "missing } for escape fragment #{buffer}"
|
||||
end
|
||||
|
||||
defp do_escape(<<?%, ?{, t :: binary>>, emit, emitted, nil, acc) do
|
||||
@@ -223,6 +223,6 @@ defmodule IO.ANSI do
|
||||
end
|
||||
|
||||
defp do_escape(<<>>, _emit, emitted, nil, acc) do
|
||||
{iodata_to_binary(Enum.reverse(acc)), emitted}
|
||||
{IO.iodata_to_binary(Enum.reverse(acc)), emitted}
|
||||
end
|
||||
end
|
||||
|
||||
@@ -8,14 +8,14 @@ defmodule IO.ANSI.Docs do
|
||||
|
||||
The supported values are:
|
||||
|
||||
* `:enabled` - toggles coloring on and off (true)
|
||||
* `:doc_code` - code blocks (cyan, bright)
|
||||
* `:doc_inline_code` - inline code (cyan)
|
||||
* `:doc_headings` - h1 and h2 headings (yellow, bright)
|
||||
* `:doc_title` - top level heading (reverse, yellow, bright)
|
||||
* `:doc_bold` - bold text (bright)
|
||||
* `:doc_underline` - underlined text (underline)
|
||||
* `:width` - the width to format the text (80)
|
||||
* `:enabled` - toggles coloring on and off (true)
|
||||
* `:doc_code` - code blocks (cyan, bright)
|
||||
* `:doc_inline_code` - inline code (cyan)
|
||||
* `:doc_headings` - h1 and h2 headings (yellow, bright)
|
||||
* `:doc_title` - top level heading (reverse, yellow, bright)
|
||||
* `:doc_bold` - bold text (bright)
|
||||
* `:doc_underline` - underlined text (underline)
|
||||
* `:width` - the width to format the text (80)
|
||||
|
||||
Values for the color settings are strings with
|
||||
comma-separated ANSI values.
|
||||
@@ -192,7 +192,7 @@ defmodule IO.ANSI.Docs do
|
||||
|> handle_links
|
||||
|> handle_inline(nil, [], [], options)
|
||||
|> String.split(~r{\s})
|
||||
|> write_with_wrap(options[:width] - size(indent), indent, from_list)
|
||||
|> write_with_wrap(options[:width] - byte_size(indent), indent, from_list)
|
||||
|
||||
unless from_list, do: IO.puts(IO.ANSI.reset)
|
||||
end
|
||||
@@ -355,7 +355,7 @@ defmodule IO.ANSI.Docs do
|
||||
end
|
||||
|
||||
defp handle_inline(<<>>, _mark, buffer, acc, _options) do
|
||||
iodata_to_binary Enum.reverse([Enum.reverse(buffer)|acc])
|
||||
IO.iodata_to_binary Enum.reverse([Enum.reverse(buffer)|acc])
|
||||
end
|
||||
|
||||
defp inline_buffer(buffer, options) do
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
defmodule IO.StreamError do
|
||||
defexception [:reason, :message]
|
||||
|
||||
def exception(opts) do
|
||||
reason = opts[:reason]
|
||||
formatted = IO.iodata_to_binary(:file.format_error(reason))
|
||||
%IO.StreamError{message: "error during streaming: #{formatted}", reason: reason}
|
||||
end
|
||||
end
|
||||
|
||||
defmodule IO.Stream do
|
||||
@moduledoc """
|
||||
Defines a `IO.Stream` struct returned by `IO.stream/2` and `IO.binstream/2`.
|
||||
|
||||
The following fields are public:
|
||||
|
||||
* `device` - the IO device
|
||||
* `raw` - a boolean indicating if bin functions should be used
|
||||
* `line_or_bytes` - if reading should read lines or a given amount of bytes
|
||||
|
||||
"""
|
||||
|
||||
defstruct device: nil, raw: true, line_or_bytes: :line
|
||||
|
||||
@doc false
|
||||
def __build__(device, raw, line_or_bytes) do
|
||||
%IO.Stream{device: device, raw: raw, line_or_bytes: line_or_bytes}
|
||||
end
|
||||
|
||||
defimpl Collectable do
|
||||
def empty(stream) do
|
||||
stream
|
||||
end
|
||||
|
||||
def into(%{device: device, raw: raw} = stream) do
|
||||
{:ok, into(stream, device, raw)}
|
||||
end
|
||||
|
||||
defp into(stream, device, raw) do
|
||||
fn
|
||||
:ok, {:cont, x} ->
|
||||
case raw do
|
||||
true -> IO.binwrite(device, x)
|
||||
false -> IO.write(device, x)
|
||||
end
|
||||
:ok, _ -> stream
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defimpl Enumerable do
|
||||
def reduce(%{device: device, raw: raw, line_or_bytes: line_or_bytes}, acc, fun) do
|
||||
next_fun =
|
||||
case raw do
|
||||
true -> &IO.each_binstream(&1, line_or_bytes)
|
||||
false -> &IO.each_stream(&1, line_or_bytes)
|
||||
end
|
||||
Stream.unfold(device, next_fun).(acc, fun)
|
||||
end
|
||||
|
||||
def count(_stream) do
|
||||
{:error, __MODULE__}
|
||||
end
|
||||
|
||||
def member?(_stream, _term) do
|
||||
{:error, __MODULE__}
|
||||
end
|
||||
end
|
||||
end
|
||||
+594
-992
File diff suppressed because it is too large
Load Diff
+111
-85
@@ -9,15 +9,13 @@ defmodule Kernel.CLI do
|
||||
This is the API invoked by Elixir boot process.
|
||||
"""
|
||||
def main(argv) do
|
||||
argv = for arg <- argv, do: String.from_char_data!(arg)
|
||||
argv = for arg <- argv, do: IO.chardata_to_string(arg)
|
||||
|
||||
{config, argv} = process_argv(argv, @blank_config)
|
||||
{config, argv} = parse_argv(argv)
|
||||
System.argv(argv)
|
||||
|
||||
run fn ->
|
||||
command_results = Enum.map(Enum.reverse(config.commands), &process_command(&1, config))
|
||||
command_errors = for {:error, msg} <- command_results, do: msg
|
||||
errors = Enum.reverse(config.errors) ++ command_errors
|
||||
errors = process_commands(config)
|
||||
|
||||
if errors != [] do
|
||||
Enum.each(errors, &IO.puts(:stderr, &1))
|
||||
@@ -55,6 +53,23 @@ defmodule Kernel.CLI do
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Parses ARGV returning the CLI config and trailing args.
|
||||
"""
|
||||
def parse_argv(argv) do
|
||||
parse_argv(argv, @blank_config)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Process commands according to the parsed config from `parse_argv/1`.
|
||||
Returns all errors.
|
||||
"""
|
||||
def process_commands(config) do
|
||||
results = Enum.map(Enum.reverse(config.commands), &process_command(&1, config))
|
||||
errors = for {:error, msg} <- results, do: msg
|
||||
Enum.reverse(config.errors, errors)
|
||||
end
|
||||
|
||||
## Helpers
|
||||
|
||||
defp at_exit(status) do
|
||||
@@ -75,7 +90,7 @@ defmodule Kernel.CLI do
|
||||
end
|
||||
|
||||
defp shared_option?(list, config, callback) do
|
||||
case process_shared(list, config) do
|
||||
case parse_shared(list, config) do
|
||||
{[h|hs], _} when h == hd(list) ->
|
||||
new_config = %{config | errors: ["#{h} : Unknown option" | config.errors]}
|
||||
callback.(hs, new_config)
|
||||
@@ -106,160 +121,173 @@ defmodule Kernel.CLI do
|
||||
[]
|
||||
end
|
||||
|
||||
# Process shared options
|
||||
# Parse shared options
|
||||
|
||||
defp process_shared([opt|_t], _config) when opt in ["-v", "--version"] do
|
||||
defp parse_shared([opt|_t], _config) when opt in ["-v", "--version"] do
|
||||
IO.puts "Elixir #{System.version}"
|
||||
System.halt 0
|
||||
end
|
||||
|
||||
defp process_shared(["-pa", h|t], config) do
|
||||
Enum.each Path.wildcard(Path.expand(h)), &Code.prepend_path(&1)
|
||||
process_shared t, config
|
||||
defp parse_shared(["-pa", h|t], config) do
|
||||
add_code_path(h, &Code.prepend_path/1)
|
||||
parse_shared t, config
|
||||
end
|
||||
|
||||
defp process_shared(["-pz", h|t], config) do
|
||||
Enum.each Path.wildcard(Path.expand(h)), &Code.append_path(&1)
|
||||
process_shared t, config
|
||||
defp parse_shared(["-pz", h|t], config) do
|
||||
add_code_path(h, &Code.append_path/1)
|
||||
parse_shared t, config
|
||||
end
|
||||
|
||||
defp process_shared(["--app", h|t], config) do
|
||||
process_shared t, %{config | commands: &[{:app, h}|&1]}
|
||||
defp parse_shared(["--app", h|t], config) do
|
||||
parse_shared t, %{config | commands: [{:app, h} | config.commands]}
|
||||
end
|
||||
|
||||
defp process_shared(["--no-halt"|t], config) do
|
||||
process_shared t, %{config | halt: false}
|
||||
defp parse_shared(["--no-halt"|t], config) do
|
||||
parse_shared t, %{config | halt: false}
|
||||
end
|
||||
|
||||
defp process_shared(["-e", h|t], config) do
|
||||
process_shared t, %{config | commands: [{:eval, h} | config.commands]}
|
||||
defp parse_shared(["-e", h|t], config) do
|
||||
parse_shared t, %{config | commands: [{:eval, h} | config.commands]}
|
||||
end
|
||||
|
||||
defp process_shared(["-r", h|t], config) do
|
||||
process_shared t, %{config | commands: [{:require, h} | config.commands]}
|
||||
defp parse_shared(["-r", h|t], config) do
|
||||
parse_shared t, %{config | commands: [{:require, h} | config.commands]}
|
||||
end
|
||||
|
||||
defp process_shared(["-pr", h|t], config) do
|
||||
process_shared t, %{config | commands: [{:parallel_require, h} | config.commands]}
|
||||
defp parse_shared(["-pr", h|t], config) do
|
||||
parse_shared t, %{config | commands: [{:parallel_require, h} | config.commands]}
|
||||
end
|
||||
|
||||
defp process_shared([erl, _|t], config) when erl in ["--erl", "--sname", "--name", "--cookie"] do
|
||||
process_shared t, config
|
||||
defp parse_shared([erl, _|t], config) when erl in ["--erl", "--sname", "--name", "--cookie"] do
|
||||
parse_shared t, config
|
||||
end
|
||||
|
||||
defp process_shared([erl|t], config) when erl in ["--detached", "--hidden", "--gen-debug"] do
|
||||
process_shared t, config
|
||||
defp parse_shared([erl|t], config) when erl in ["--detached", "--hidden"] do
|
||||
parse_shared t, config
|
||||
end
|
||||
|
||||
defp process_shared(list, config) do
|
||||
defp parse_shared(list, config) do
|
||||
{list, config}
|
||||
end
|
||||
|
||||
|
||||
defp add_code_path(path, fun) do
|
||||
path = Path.expand(path)
|
||||
case Path.wildcard(path) do
|
||||
[] -> fun.(path)
|
||||
list -> Enum.each(list, fun)
|
||||
end
|
||||
end
|
||||
|
||||
# Process init options
|
||||
|
||||
defp process_argv(["--"|t], config) do
|
||||
defp parse_argv(["--"|t], config) do
|
||||
{config, t}
|
||||
end
|
||||
|
||||
defp process_argv(["+elixirc"|t], config) do
|
||||
process_compiler t, config
|
||||
defp parse_argv(["+elixirc"|t], config) do
|
||||
parse_compiler t, config
|
||||
end
|
||||
|
||||
defp process_argv(["+iex"|t], config) do
|
||||
process_iex t, config
|
||||
defp parse_argv(["+iex"|t], config) do
|
||||
parse_iex t, config
|
||||
end
|
||||
|
||||
defp process_argv(["-S", h|t], config) do
|
||||
defp parse_argv(["-S", h|t], config) do
|
||||
{%{config | commands: [{:script, h} | config.commands]}, t}
|
||||
end
|
||||
|
||||
defp process_argv([h|t] = list, config) do
|
||||
defp parse_argv([h|t] = list, config) do
|
||||
case h do
|
||||
"-" <> _ ->
|
||||
shared_option? list, config, &process_argv(&1, &2)
|
||||
shared_option? list, config, &parse_argv(&1, &2)
|
||||
_ ->
|
||||
{%{config | commands: [{:file, h} | config.commands]}, t}
|
||||
if Keyword.has_key?(config.commands, :eval) do
|
||||
{config, list}
|
||||
else
|
||||
{%{config | commands: [{:file, h} | config.commands]}, t}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defp process_argv([], config) do
|
||||
defp parse_argv([], config) do
|
||||
{config, []}
|
||||
end
|
||||
|
||||
# Process compiler options
|
||||
# Parse compiler options
|
||||
|
||||
defp process_compiler(["--"|t], config) do
|
||||
defp parse_compiler(["--"|t], config) do
|
||||
{config, t}
|
||||
end
|
||||
|
||||
defp process_compiler(["-o", h|t], config) do
|
||||
process_compiler t, %{config | output: h}
|
||||
defp parse_compiler(["-o", h|t], config) do
|
||||
parse_compiler t, %{config | output: h}
|
||||
end
|
||||
|
||||
defp process_compiler(["--no-docs"|t], config) do
|
||||
process_compiler t, %{config | compiler_options: [{:docs, false} | config.compiler_options]}
|
||||
defp parse_compiler(["--no-docs"|t], config) do
|
||||
parse_compiler t, %{config | compiler_options: [{:docs, false} | config.compiler_options]}
|
||||
end
|
||||
|
||||
defp process_compiler(["--no-debug-info"|t], config) do
|
||||
process_compiler t, %{config | compiler_options: [{:debug_info, false} | config.compiler_options]}
|
||||
defp parse_compiler(["--no-debug-info"|t], config) do
|
||||
parse_compiler t, %{config | compiler_options: [{:debug_info, false} | config.compiler_options]}
|
||||
end
|
||||
|
||||
defp process_compiler(["--ignore-module-conflict"|t], config) do
|
||||
process_compiler t, %{config | compiler_options: [{:ignore_module_conflict, true} | config.compiler_options]}
|
||||
defp parse_compiler(["--ignore-module-conflict"|t], config) do
|
||||
parse_compiler t, %{config | compiler_options: [{:ignore_module_conflict, true} | config.compiler_options]}
|
||||
end
|
||||
|
||||
defp process_compiler(["--warnings-as-errors"|t], config) do
|
||||
process_compiler t, %{config | compiler_options: [{:warnings_as_errors, true} | config.compiler_options]}
|
||||
defp parse_compiler(["--warnings-as-errors"|t], config) do
|
||||
parse_compiler t, %{config | compiler_options: [{:warnings_as_errors, true} | config.compiler_options]}
|
||||
end
|
||||
|
||||
defp process_compiler(["--verbose"|t], config) do
|
||||
process_compiler t, %{config | verbose_compile: true}
|
||||
defp parse_compiler(["--verbose"|t], config) do
|
||||
parse_compiler t, %{config | verbose_compile: true}
|
||||
end
|
||||
|
||||
defp process_compiler([h|t] = list, config) do
|
||||
defp parse_compiler([h|t] = list, config) do
|
||||
case h do
|
||||
"-" <> _ ->
|
||||
shared_option? list, config, &process_compiler(&1, &2)
|
||||
shared_option? list, config, &parse_compiler(&1, &2)
|
||||
_ ->
|
||||
pattern = if :filelib.is_dir(h), do: "#{h}/**/*.ex", else: h
|
||||
process_compiler t, %{config | compile: [pattern | config.compile]}
|
||||
pattern = if File.dir?(h), do: "#{h}/**/*.ex", else: h
|
||||
parse_compiler t, %{config | compile: [pattern | config.compile]}
|
||||
end
|
||||
end
|
||||
|
||||
defp process_compiler([], config) do
|
||||
defp parse_compiler([], config) do
|
||||
{%{config | commands: [{:compile, config.compile}|config.commands]}, []}
|
||||
end
|
||||
|
||||
# Process iex options
|
||||
# Parse iex options
|
||||
|
||||
defp process_iex(["--"|t], config) do
|
||||
defp parse_iex(["--"|t], config) do
|
||||
{config, t}
|
||||
end
|
||||
|
||||
# This clause is here so that Kernel.CLI does not error out with "unknown
|
||||
# option"
|
||||
defp process_iex(["--dot-iex", _|t], config) do
|
||||
process_iex t, config
|
||||
# This clause is here so that Kernel.CLI does not
|
||||
# error out with "unknown option"
|
||||
defp parse_iex(["--dot-iex", _|t], config) do
|
||||
parse_iex t, config
|
||||
end
|
||||
|
||||
defp process_iex([opt, _|t], config) when opt in ["--remsh"] do
|
||||
process_iex t, config
|
||||
defp parse_iex([opt, _|t], config) when opt in ["--remsh"] do
|
||||
parse_iex t, config
|
||||
end
|
||||
|
||||
defp process_iex(["-S", h|t], config) do
|
||||
defp parse_iex(["-S", h|t], config) do
|
||||
{%{config | commands: [{:script, h} | config.commands]}, t}
|
||||
end
|
||||
|
||||
defp process_iex([h|t] = list, config) do
|
||||
defp parse_iex([h|t] = list, config) do
|
||||
case h do
|
||||
"-" <> _ ->
|
||||
shared_option? list, config, &process_iex(&1, &2)
|
||||
shared_option? list, config, &parse_iex(&1, &2)
|
||||
_ ->
|
||||
{%{config | commands: [{:file, h} | config.commands]}, t}
|
||||
end
|
||||
end
|
||||
|
||||
defp process_iex([], config) do
|
||||
defp parse_iex([], config) do
|
||||
{config, []}
|
||||
end
|
||||
|
||||
@@ -267,7 +295,7 @@ defmodule Kernel.CLI do
|
||||
|
||||
defp process_command({:cookie, h}, _config) do
|
||||
if Node.alive? do
|
||||
wrapper fn -> Node.set_cookie(binary_to_atom(h)) end
|
||||
wrapper fn -> Node.set_cookie(String.to_atom(h)) end
|
||||
else
|
||||
{:error, "--cookie : Cannot set cookie if the node is not alive (set --name or --sname)"}
|
||||
end
|
||||
@@ -278,7 +306,7 @@ defmodule Kernel.CLI do
|
||||
end
|
||||
|
||||
defp process_command({:app, app}, _config) when is_binary(app) do
|
||||
case Application.ensure_all_started(binary_to_atom(app)) do
|
||||
case Application.ensure_all_started(String.to_atom(app)) do
|
||||
{:error, {app, reason}} ->
|
||||
{:error, "--app : Could not start application #{app}: " <>
|
||||
Application.format_error(reason)}
|
||||
@@ -296,7 +324,7 @@ defmodule Kernel.CLI do
|
||||
end
|
||||
|
||||
defp process_command({:file, file}, _config) when is_binary(file) do
|
||||
if :filelib.is_regular(file) do
|
||||
if File.regular?(file) do
|
||||
wrapper fn -> Code.require_file(file) end
|
||||
else
|
||||
{:error, "No file named #{file}"}
|
||||
@@ -304,9 +332,7 @@ defmodule Kernel.CLI do
|
||||
end
|
||||
|
||||
defp process_command({:require, pattern}, _config) when is_binary(pattern) do
|
||||
files = Path.wildcard(pattern)
|
||||
files = Enum.uniq(files)
|
||||
files = Enum.filter files, &:filelib.is_regular(&1)
|
||||
files = filter_patterns(pattern)
|
||||
|
||||
if files != [] do
|
||||
wrapper fn -> Enum.map files, &Code.require_file(&1) end
|
||||
@@ -316,9 +342,7 @@ defmodule Kernel.CLI do
|
||||
end
|
||||
|
||||
defp process_command({:parallel_require, pattern}, _config) when is_binary(pattern) do
|
||||
files = Path.wildcard(pattern)
|
||||
files = Enum.uniq(files)
|
||||
files = Enum.filter files, &:filelib.is_regular(&1)
|
||||
files = filter_patterns(pattern)
|
||||
|
||||
if files != [] do
|
||||
wrapper fn -> Kernel.ParallelRequire.files(files) end
|
||||
@@ -330,7 +354,7 @@ defmodule Kernel.CLI do
|
||||
defp process_command({:compile, patterns}, config) do
|
||||
:filelib.ensure_dir(:filename.join(config.output, "."))
|
||||
|
||||
case match_regular_files(patterns) do
|
||||
case filter_multiple_patterns(patterns) do
|
||||
{:ok, []} ->
|
||||
{:error, "No files matched provided patterns"}
|
||||
{:ok, files} ->
|
||||
@@ -344,9 +368,13 @@ defmodule Kernel.CLI do
|
||||
end
|
||||
end
|
||||
|
||||
defp match_regular_files(patterns) do
|
||||
defp filter_patterns(pattern) do
|
||||
Enum.filter(Enum.uniq(Path.wildcard(pattern)), &File.regular?(&1))
|
||||
end
|
||||
|
||||
defp filter_multiple_patterns(patterns) do
|
||||
matched_files = Enum.map patterns, fn(pattern) ->
|
||||
case Path.wildcard(pattern) do
|
||||
case filter_patterns(pattern) do
|
||||
[] -> {:missing, pattern}
|
||||
files -> {:ok, files}
|
||||
end
|
||||
@@ -361,9 +389,7 @@ defmodule Kernel.CLI do
|
||||
&elem(&1, 1)
|
||||
|
||||
if missing_patterns == [] do
|
||||
files = Enum.uniq(Enum.concat(files))
|
||||
files = Enum.filter files, &:filelib.is_regular(&1)
|
||||
{:ok, files}
|
||||
{:ok, Enum.uniq(Enum.concat(files))}
|
||||
else
|
||||
{:missing, Enum.uniq(missing_patterns)}
|
||||
end
|
||||
|
||||
@@ -7,7 +7,8 @@
|
||||
# Note that since this is required for bootstrap, we can't use
|
||||
# any of the `GenServer.Behaviour` conveniences.
|
||||
defmodule Kernel.LexicalTracker do
|
||||
@timeout 30_000
|
||||
@moduledoc false
|
||||
@timeout 30_000
|
||||
@behaviour :gen_server
|
||||
|
||||
@import 2
|
||||
|
||||
@@ -17,9 +17,11 @@ defmodule Kernel.ParallelCompiler do
|
||||
|
||||
This function receives a set of callbacks as options:
|
||||
|
||||
* `:each_file` - for each file compiled, invokes the callback passing the file
|
||||
* `:each_module` - for each module compiled, invokes the callback
|
||||
passing the file, module and the module bytecode
|
||||
* `:each_file` - for each file compiled, invokes the callback passing the
|
||||
file
|
||||
|
||||
* `:each_module` - for each module compiled, invokes the callback passing
|
||||
the file, module and the module bytecode
|
||||
|
||||
The compiler doesn't care about the return values of the callbacks.
|
||||
Returns the modules generated by each compiled file.
|
||||
|
||||
@@ -29,7 +29,7 @@ defmodule Kernel.SpecialForms do
|
||||
Conveniences for manipulating tuples can be found in the
|
||||
`Tuple` module. Some functions for working with tuples are
|
||||
also available in `Kernel`, namely `Kernel.elem/2`,
|
||||
`Kernel.set_elem/3` and `Kernel.tuple_size/1`.
|
||||
`Kernel.put_elem/3` and `Kernel.tuple_size/1`.
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -118,18 +118,25 @@ defmodule Kernel.SpecialForms do
|
||||
end
|
||||
end
|
||||
|
||||
Now a struct can be created as follow:
|
||||
In practice though, structs are usually defined with the
|
||||
`Kernel.defstruct/1` macro:
|
||||
|
||||
defmodule User do
|
||||
defstruct name: "josé", age: 27
|
||||
end
|
||||
|
||||
Now a struct can be created as follows:
|
||||
|
||||
%User{}
|
||||
|
||||
Underneath, a struct is just a map with a `__struct__` field
|
||||
pointing to the User module:
|
||||
Underneath a struct is just a map with a `__struct__` field
|
||||
pointing to the `User` module:
|
||||
|
||||
%User{} == %{__struct__: User, name: "josé", age: 27}
|
||||
|
||||
A struct also validates the given keys are part of the defined
|
||||
A struct also validates that the given keys are part of the defined
|
||||
struct. The example below will fail because there is no key
|
||||
`:full_name` in the user struct:
|
||||
`:full_name` in the `User` struct:
|
||||
|
||||
%User{full_name: "José Valim"}
|
||||
|
||||
@@ -148,8 +155,10 @@ defmodule Kernel.SpecialForms do
|
||||
compilation time and it will guarantee at runtime the given
|
||||
argument is a struct, failing with `BadStructError` otherwise.
|
||||
|
||||
Check `Kernel.defprotocol/2` for more information on how structs
|
||||
can be used with protocols for polymorphic dispatch. Also,
|
||||
Although structs are maps, by default structs do not implement
|
||||
any of the protocols implemented for maps. Check
|
||||
`Kernel.defprotocol/2` for more information on how structs
|
||||
can be used with protocols for polymorphic dispatch. Also
|
||||
see `Kernel.struct/2` for examples on how to create and update
|
||||
structs dynamically.
|
||||
"""
|
||||
@@ -580,7 +589,7 @@ defmodule Kernel.SpecialForms do
|
||||
defmacro import(module, opts)
|
||||
|
||||
@doc """
|
||||
Returns the current environment information as a `Macro.Env[]` record.
|
||||
Returns the current environment information as a `Macro.Env` struct.
|
||||
|
||||
In the environment you can access the current filename,
|
||||
line numbers, set up aliases, the current function and others.
|
||||
@@ -603,6 +612,14 @@ defmodule Kernel.SpecialForms do
|
||||
"""
|
||||
defmacro __DIR__
|
||||
|
||||
@doc """
|
||||
Returns the current calling environment as a `Macro.Env` struct.
|
||||
|
||||
In the environment you can access the filename, line numbers,
|
||||
set up aliases, the function and others.
|
||||
"""
|
||||
defmacro __CALLER__
|
||||
|
||||
@doc """
|
||||
Accesses an already bound variable in match clauses.
|
||||
|
||||
@@ -653,24 +670,28 @@ defmodule Kernel.SpecialForms do
|
||||
The tuple above represents a function call to `sum` passing 1, 2 and
|
||||
3 as arguments. The tuple elements are:
|
||||
|
||||
* The first element of the tuple is always an atom or
|
||||
another tuple in the same representation;
|
||||
* The second element of the tuple represents metadata;
|
||||
* The third element of the tuple are the arguments for the
|
||||
function call. The third argument may be an atom, which is
|
||||
usually a variable (or a local call);
|
||||
* The first element of the tuple is always an atom or
|
||||
another tuple in the same representation.
|
||||
|
||||
* The second element of the tuple represents metadata.
|
||||
|
||||
* The third element of the tuple are the arguments for the
|
||||
function call. The third argument may be an atom, which is
|
||||
usually a variable (or a local call).
|
||||
|
||||
## Options
|
||||
|
||||
* `:unquote` - When false, disables unquoting. Useful when you have a quote
|
||||
inside another quote and want to control what quote is
|
||||
able to unquote;
|
||||
* `:location` - When set to `:keep`, keeps the current line and file from quote.
|
||||
Read the Stacktrace information section below for more information;
|
||||
* `:hygiene` - Allows a developer to disable hygiene selectively;
|
||||
* `:context` - Sets the resolution context;
|
||||
* `:bind_quoted` - Passes a binding to the macro. Whenever a binding is given,
|
||||
`unquote` is automatically disabled;
|
||||
* `:unquote` - when false, disables unquoting. Useful when you have a quote
|
||||
inside another quote and want to control what quote is able to unquote.
|
||||
|
||||
* `:location` - when set to `:keep`, keeps the current line and file from
|
||||
quote. Read the Stacktrace information section below for more
|
||||
information.
|
||||
|
||||
* `:context` - sets the resolution context.
|
||||
|
||||
* `:bind_quoted` - passes a binding to the macro. Whenever a binding is
|
||||
given, `unquote` is automatically disabled.
|
||||
|
||||
## Quote literals
|
||||
|
||||
@@ -864,10 +885,6 @@ defmodule Kernel.SpecialForms do
|
||||
ContextHygiene.read
|
||||
#=> 1
|
||||
|
||||
Hygiene for variables can be disabled overall as:
|
||||
|
||||
quote hygiene: [vars: false], do: x
|
||||
|
||||
## Hygiene in aliases
|
||||
|
||||
Aliases inside quote are hygienic by default.
|
||||
@@ -987,9 +1004,6 @@ defmodule Kernel.SpecialForms do
|
||||
|
||||
Lazy.return_size #=> 2
|
||||
|
||||
As in aliases, import expansion can be explicitly disabled
|
||||
via the `hygiene: [imports: false]` option.
|
||||
|
||||
## Stacktrace information
|
||||
|
||||
When defining functions via macros, developers have the option of
|
||||
@@ -1055,9 +1069,9 @@ defmodule Kernel.SpecialForms do
|
||||
we are passing the representation of the variable `kv`, our
|
||||
code fails.
|
||||
|
||||
This is actually a common pitfall when developing macros. In
|
||||
practice, we want to avoid doing work at compilation time as
|
||||
much as possible. That said, let's attempt to improve our macro:
|
||||
This is actually a common pitfall when developing macros. We are
|
||||
assuming a particular shape in the macro. We can work around it
|
||||
by unquoting the variable inside the quoted expression:
|
||||
|
||||
defmacro defkv(kv) do
|
||||
quote do
|
||||
@@ -1313,21 +1327,21 @@ defmodule Kernel.SpecialForms do
|
||||
|
||||
On the other hand, aliases holds some properties:
|
||||
|
||||
1) The head element of aliases can be any term;
|
||||
1. The head element of aliases can be any term.
|
||||
|
||||
2) The tail elements of aliases are guaranteed to always be atoms;
|
||||
2. The tail elements of aliases are guaranteed to always be atoms.
|
||||
|
||||
3) When the head element of aliases is the atom `:Elixir`, no expansion happen;
|
||||
3. When the head element of aliases is the atom `:Elixir`, no expansion happen.
|
||||
|
||||
4) When the head element of aliases is not an atom, it is expanded at runtime:
|
||||
4. When the head element of aliases is not an atom, it is expanded at runtime:
|
||||
|
||||
quote do: some_var.Foo
|
||||
{:__aliases__, [], [{:some_var, [], Elixir}, :Foo]}
|
||||
quote do: some_var.Foo
|
||||
{:__aliases__, [], [{:some_var, [], Elixir}, :Foo]}
|
||||
|
||||
Since `some_var` is not available at compilation time, the compiler
|
||||
expands such expression to:
|
||||
Since `some_var` is not available at compilation time, the compiler
|
||||
expands such expression to:
|
||||
|
||||
Module.concat [some_var, Foo]
|
||||
Module.concat [some_var, Foo]
|
||||
|
||||
"""
|
||||
defmacro __aliases__(args)
|
||||
@@ -1383,7 +1397,27 @@ defmodule Kernel.SpecialForms do
|
||||
case, clauses that do not explicitly bind a value have the variable
|
||||
bound to nil.
|
||||
"""
|
||||
defmacro case(condition, blocks)
|
||||
defmacro case(condition, clauses)
|
||||
|
||||
@doc """
|
||||
Evaluates the expression corresponding to the first clause that
|
||||
evaluates to truth value.
|
||||
|
||||
Raises an error if all conditions evaluate to to nil or false.
|
||||
|
||||
## Examples
|
||||
|
||||
cond do
|
||||
1 + 1 == 1 ->
|
||||
"This will never match"
|
||||
2 * 2 != 4 ->
|
||||
"Nor this"
|
||||
true ->
|
||||
"This will"
|
||||
end
|
||||
|
||||
"""
|
||||
defmacro cond(clauses)
|
||||
|
||||
@doc ~S"""
|
||||
Evaluate the given expressions and handle any error, exit
|
||||
@@ -1642,11 +1676,11 @@ defmodule Kernel.SpecialForms do
|
||||
The `after` clause can be specified even if there are no match clauses.
|
||||
There are two special cases for the timeout value given to `after`
|
||||
|
||||
* `:infinity` - The process should wait indefinitely for a matching
|
||||
message, this is the same as not using a timeout.
|
||||
* `:infinity` - the process should wait indefinitely for a matching
|
||||
message, this is the same as not using a timeout
|
||||
|
||||
* 0 - if there is no matching message in the mailbox, the timeout
|
||||
will occur immediately.
|
||||
* 0 - if there is no matching message in the mailbox, the timeout
|
||||
will occur immediately
|
||||
|
||||
## Variables handling
|
||||
|
||||
|
||||
+118
-129
@@ -104,36 +104,36 @@ defmodule Kernel.Typespec do
|
||||
|
||||
## Built-in types
|
||||
|
||||
Built-in type | Defined as
|
||||
:-------------------- | :---------
|
||||
`term` | `any`
|
||||
`binary` | `<< _ :: _ * 8 >>`
|
||||
`bitstring` | `<< _ :: _ * 1 >>`
|
||||
`boolean` | `false` | `true`
|
||||
`byte` | `0..255`
|
||||
`char` | `0..0xffff`
|
||||
`number` | `integer` | `float`
|
||||
`list` | `[any]`
|
||||
`maybe_improper_list` | `maybe_improper_list(any, any)`
|
||||
`nonempty_list` | `nonempty_list(any)`
|
||||
`iodata` | `iolist` | `binary`
|
||||
`iolist` | `maybe_improper_list(byte` | `binary` | `iolist, binary` | `[])`
|
||||
`module` | `atom`
|
||||
`mfa` | `{atom, atom, arity}`
|
||||
`arity` | `0..255`
|
||||
`node` | `atom`
|
||||
`timeout` | `:infinity` | `non_neg_integer`
|
||||
`no_return` | `none`
|
||||
`fun` | `(... -> any)`
|
||||
Built-in type | Defined as
|
||||
:-------------------- | :---------
|
||||
`term` | `any`
|
||||
`binary` | `<< _ :: _ * 8 >>`
|
||||
`bitstring` | `<< _ :: _ * 1 >>`
|
||||
`boolean` | `false` | `true`
|
||||
`byte` | `0..255`
|
||||
`char` | `0..0xffff`
|
||||
`number` | `integer` | `float`
|
||||
`list` | `[any]`
|
||||
`maybe_improper_list` | `maybe_improper_list(any, any)`
|
||||
`nonempty_list` | `nonempty_list(any)`
|
||||
`iodata` | `iolist` | `binary`
|
||||
`iolist` | `maybe_improper_list(byte` | `binary` | `iolist, binary` | `[])`
|
||||
`module` | `atom`
|
||||
`mfa` | `{atom, atom, arity}`
|
||||
`arity` | `0..255`
|
||||
`node` | `atom`
|
||||
`timeout` | `:infinity` | `non_neg_integer`
|
||||
`no_return` | `none`
|
||||
`fun` | `(... -> any)`
|
||||
|
||||
Some built-in types cannot be expressed with valid syntax according to the
|
||||
language defined above.
|
||||
|
||||
Built-in type | Can be interpreted as
|
||||
:---------------- | :--------------------
|
||||
`non_neg_integer` | `0..`
|
||||
`pos_integer` | `1..`
|
||||
`neg_integer` | `..-1`
|
||||
Built-in type | Can be interpreted as
|
||||
:---------------- | :--------------------
|
||||
`non_neg_integer` | `0..`
|
||||
`pos_integer` | `1..`
|
||||
`neg_integer` | `..-1`
|
||||
|
||||
Types defined in other modules are referred to as "remote types", they are
|
||||
referenced as `Module.type_name` (ex. `Enum.t` or `String.t`).
|
||||
@@ -153,8 +153,6 @@ defmodule Kernel.Typespec do
|
||||
|
||||
@type dict(key, value) :: [{key, value}]
|
||||
|
||||
Types can also be defined for records, see `defrecord/3`.
|
||||
|
||||
## Defining a specification
|
||||
|
||||
@spec function_name(type1, type2) :: return_type
|
||||
@@ -264,60 +262,18 @@ defmodule Kernel.Typespec do
|
||||
end
|
||||
end
|
||||
|
||||
## Helpers
|
||||
|
||||
@doc """
|
||||
Defines a `type`, `typep` or `opaque` by receiving Erlang's typespec.
|
||||
Defines a `type`, `typep` or `opaque` by receiving a typespec expression.
|
||||
"""
|
||||
def define_type(caller, kind, {name, _, vars} = type) when kind in [:type, :typep, :opaque] do
|
||||
{kind, export} =
|
||||
case kind do
|
||||
:type -> {:type, true}
|
||||
:typep -> {:type, false}
|
||||
:opaque -> {:opaque, true}
|
||||
end
|
||||
|
||||
module = caller.module
|
||||
arity = length(vars)
|
||||
|
||||
Module.compile_typespec module, kind, type
|
||||
|
||||
if export do
|
||||
Module.compile_typespec(module, :export_type, [{name, arity}])
|
||||
end
|
||||
|
||||
define_doc(caller, kind, name, arity, export)
|
||||
type
|
||||
end
|
||||
|
||||
defp define_doc(caller, kind, name, arity, export) do
|
||||
module = caller.module
|
||||
doc = Module.get_attribute(module, :typedoc)
|
||||
|
||||
if doc do
|
||||
if export do
|
||||
Module.add_doc(module, caller.line, kind, {name, arity}, doc)
|
||||
else
|
||||
:elixir_errors.warn caller.line, caller.file, "type #{name}/#{arity} is private, " <>
|
||||
"@typedoc's are always discarded for private types\n"
|
||||
end
|
||||
end
|
||||
|
||||
Module.delete_attribute(module, :typedoc)
|
||||
def define_type(kind, expr, doc \\ nil, env) do
|
||||
Module.store_typespec(env.module, kind, {kind, expr, doc, env})
|
||||
end
|
||||
|
||||
@doc """
|
||||
Defines a `spec` by receiving Erlang's typespec.
|
||||
Defines a `spec` by receiving a typespec expression.
|
||||
"""
|
||||
def define_spec(module, tuple, definition) do
|
||||
Module.compile_typespec module, :spec, {tuple, definition}
|
||||
end
|
||||
|
||||
@doc """
|
||||
Defines a `callback` by receiving Erlang's typespec.
|
||||
"""
|
||||
def define_callback(module, tuple, definition) do
|
||||
Module.compile_typespec module, :callback, {tuple, definition}
|
||||
def define_spec(kind, expr, env) do
|
||||
Module.store_typespec(env.module, kind, {kind, expr, env})
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -326,9 +282,12 @@ defmodule Kernel.Typespec do
|
||||
for modules being compiled.
|
||||
"""
|
||||
def defines_type?(module, name, arity) do
|
||||
finder = &match?({^name, _, vars} when length(vars) == arity, &1)
|
||||
Enum.any?(Module.get_attribute(module, :type), finder) or
|
||||
Enum.any?(Module.get_attribute(module, :opaque), finder)
|
||||
finder = fn {_kind, expr, _doc, _caller} ->
|
||||
type_to_signature(expr) == {name, arity}
|
||||
end
|
||||
|
||||
:lists.any(finder, Module.get_attribute(module, :type)) or
|
||||
:lists.any(finder, Module.get_attribute(module, :opaque))
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -336,8 +295,10 @@ defmodule Kernel.Typespec do
|
||||
This function is only available for modules being compiled.
|
||||
"""
|
||||
def defines_spec?(module, name, arity) do
|
||||
tuple = {name, arity}
|
||||
Enum.any?(Module.get_attribute(module, :spec), &match?(^tuple, &1))
|
||||
finder = fn {_kind, expr, _caller} ->
|
||||
spec_to_signature(expr) == {name, arity}
|
||||
end
|
||||
:lists.any(finder, Module.get_attribute(module, :spec))
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -345,8 +306,10 @@ defmodule Kernel.Typespec do
|
||||
This function is only available for modules being compiled.
|
||||
"""
|
||||
def defines_callback?(module, name, arity) do
|
||||
tuple = {name, arity}
|
||||
Enum.any?(Module.get_attribute(module, :callback), &match?(^tuple, &1))
|
||||
finder = fn {_kind, expr, _caller} ->
|
||||
spec_to_signature(expr) == {name, arity}
|
||||
end
|
||||
:lists.any(finder, Module.get_attribute(module, :callback))
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -425,7 +388,7 @@ defmodule Kernel.Typespec do
|
||||
case abstract_code(module) do
|
||||
{:ok, abstract_code} ->
|
||||
type_docs = for {:attribute, _, :typedoc, tup} <- abstract_code, do: tup
|
||||
List.flatten(type_docs)
|
||||
:lists.flatten(type_docs)
|
||||
_ ->
|
||||
nil
|
||||
end
|
||||
@@ -445,7 +408,7 @@ defmodule Kernel.Typespec do
|
||||
case abstract_code(module) do
|
||||
{:ok, abstract_code} ->
|
||||
exported_types = for {:attribute, _, :export_type, types} <- abstract_code, do: types
|
||||
exported_types = List.flatten(exported_types)
|
||||
exported_types = :lists.flatten(exported_types)
|
||||
|
||||
for {:attribute, _, kind, {name, _, args} = type} <- abstract_code, kind in [:opaque, :type] do
|
||||
cond do
|
||||
@@ -516,10 +479,40 @@ defmodule Kernel.Typespec do
|
||||
binary
|
||||
end
|
||||
|
||||
## Helpers
|
||||
|
||||
@doc false
|
||||
def spec_to_signature({:when, _, [spec, _]}),
|
||||
do: type_to_signature(spec)
|
||||
def spec_to_signature(other),
|
||||
do: type_to_signature(other)
|
||||
|
||||
@doc false
|
||||
def type_to_signature({:::, _, [{name, _, nil}, _]}),
|
||||
do: {name, 0}
|
||||
def type_to_signature({:::, _, [{name, _, args}, _]}),
|
||||
do: {name, length(args)}
|
||||
|
||||
## Macro callbacks
|
||||
|
||||
@doc false
|
||||
def deftype(kind, {:::, _, [{name, _, args}, definition]}, caller) when is_atom(name) and name != ::: do
|
||||
def defspec(kind, expr, caller) do
|
||||
Module.store_typespec(caller.module, kind, {kind, expr, caller})
|
||||
end
|
||||
|
||||
@doc false
|
||||
def deftype(kind, expr, caller) do
|
||||
module = caller.module
|
||||
doc = Module.get_attribute(module, :typedoc)
|
||||
|
||||
Module.delete_attribute(module, :typedoc)
|
||||
Module.store_typespec(module, kind, {kind, expr, doc, caller})
|
||||
end
|
||||
|
||||
## Translation from Elixir AST to typespec AST
|
||||
|
||||
@doc false
|
||||
def translate_type(kind, {:::, _, [{name, _, args}, definition]}, doc, caller) when is_atom(name) and name != ::: do
|
||||
args =
|
||||
if is_atom(args) do
|
||||
[]
|
||||
@@ -527,31 +520,43 @@ defmodule Kernel.Typespec do
|
||||
for(arg <- args, do: variable(arg))
|
||||
end
|
||||
|
||||
vars = for {:var, _, var} <- args, do: var
|
||||
spec = typespec(definition, vars, caller)
|
||||
vars = for {:var, _, var} <- args, do: var
|
||||
spec = typespec(definition, vars, caller)
|
||||
|
||||
vars = for {:var, _, _} = var <- args, do: var
|
||||
type = {name, spec, vars}
|
||||
vars = for {:var, _, _} = var <- args, do: var
|
||||
type = {name, spec, vars}
|
||||
arity = length(vars)
|
||||
|
||||
define_type(caller, kind, type)
|
||||
{kind, export} =
|
||||
case kind do
|
||||
:type -> {:type, true}
|
||||
:typep -> {:type, false}
|
||||
:opaque -> {:opaque, true}
|
||||
end
|
||||
|
||||
if not export and doc do
|
||||
:elixir_errors.warn(caller.line, caller.file, "type #{name}/#{arity} is private, " <>
|
||||
"@typedoc's are always discarded for private types\n")
|
||||
end
|
||||
|
||||
{{kind, {name, arity}, type}, caller.line, export, doc}
|
||||
end
|
||||
|
||||
def deftype(_kind, other, caller) do
|
||||
def translate_type(_kind, other, _doc, caller) do
|
||||
type_spec = Macro.to_string(other)
|
||||
compile_error caller, "invalid type specification: #{type_spec}"
|
||||
end
|
||||
|
||||
@doc false
|
||||
|
||||
def defspec(type, {:when, _meta, [spec, guard]}, caller) do
|
||||
defspec(type, spec, guard, caller)
|
||||
def translate_spec(kind, {:when, _meta, [spec, guard]}, caller) do
|
||||
translate_spec(kind, spec, guard, caller)
|
||||
end
|
||||
|
||||
def defspec(type, spec, caller) do
|
||||
defspec(type, spec, [], caller)
|
||||
def translate_spec(kind, spec, caller) do
|
||||
translate_spec(kind, spec, [], caller)
|
||||
end
|
||||
|
||||
defp defspec(type, {:::, meta, [{name, _, args}, return]}, guard, caller) when is_atom(name) and name != ::: do
|
||||
defp translate_spec(kind, {:::, meta, [{name, _, args}, return]}, guard, caller) when is_atom(name) and name != ::: do
|
||||
if is_atom(args), do: args = []
|
||||
|
||||
unless Keyword.keyword?(guard) do
|
||||
@@ -567,12 +572,11 @@ defmodule Kernel.Typespec do
|
||||
spec = {:type, line(meta), :bounded_fun, [spec, constraints]}
|
||||
end
|
||||
|
||||
code = {{name, Kernel.length(args)}, spec}
|
||||
Module.compile_typespec(caller.module, type, code)
|
||||
code
|
||||
arity = length(args)
|
||||
{{kind, {name, arity}, spec}, caller.line}
|
||||
end
|
||||
|
||||
defp defspec(_type, spec, _guard, caller) do
|
||||
defp translate_spec(_kind, spec, _guard, caller) do
|
||||
spec = Macro.to_string(spec)
|
||||
compile_error caller, "invalid function type specification: #{spec}"
|
||||
end
|
||||
@@ -580,14 +584,14 @@ defmodule Kernel.Typespec do
|
||||
defp guard_to_constraints(guard, vars, meta, caller) do
|
||||
line = line(meta)
|
||||
|
||||
Enum.reduce(guard, [], fn
|
||||
:lists.foldl(fn
|
||||
{_name, {:var, _, context}}, acc when is_atom(context) ->
|
||||
acc
|
||||
{name, type}, acc ->
|
||||
constraint = [{:atom, line, :is_subtype}, [{:var, line, name}, typespec(type, vars, caller)]]
|
||||
type = {:type, line, :constraint, constraint}
|
||||
[type|acc]
|
||||
end) |> Enum.reverse
|
||||
end, [], guard) |> :lists.reverse
|
||||
end
|
||||
|
||||
## To AST conversion
|
||||
@@ -707,10 +711,6 @@ defmodule Kernel.Typespec do
|
||||
typespec_to_ast({:type, line, :char_list, []})
|
||||
end
|
||||
|
||||
defp typespec_to_ast({:remote_type, line, [{:atom, _, :elixir}, {:atom, _, :char_data}, []]}) do
|
||||
typespec_to_ast({:type, line, :char_data, []})
|
||||
end
|
||||
|
||||
defp typespec_to_ast({:remote_type, line, [{:atom, _, :elixir}, {:atom, _, :as_boolean}, [arg]]}) do
|
||||
typespec_to_ast({:type, line, :as_boolean, [arg]})
|
||||
end
|
||||
@@ -746,15 +746,15 @@ defmodule Kernel.Typespec do
|
||||
defp typespec_to_ast(other), do: other
|
||||
|
||||
defp erl_to_ex_var(var) do
|
||||
case atom_to_binary(var) do
|
||||
case Atom.to_string(var) do
|
||||
<<"_", c :: [binary, size(1)], rest :: binary>> ->
|
||||
binary_to_atom("_#{String.downcase(c)}#{rest}")
|
||||
String.to_atom("_#{String.downcase(c)}#{rest}")
|
||||
<<c :: [binary, size(1)], rest :: binary>> ->
|
||||
binary_to_atom("#{String.downcase(c)}#{rest}")
|
||||
String.to_atom("#{String.downcase(c)}#{rest}")
|
||||
end
|
||||
end
|
||||
|
||||
## From AST conversion
|
||||
## To typespec conversion
|
||||
|
||||
defp line(meta) do
|
||||
case :lists.keyfind(:line, 1, meta) do
|
||||
@@ -785,9 +785,9 @@ defmodule Kernel.Typespec do
|
||||
|
||||
## Handle maps and structs
|
||||
defp typespec({:%{}, meta, fields}, vars, caller) do
|
||||
fields = Enum.map(fields, fn {k, v} ->
|
||||
fields = :lists.map(fn {k, v} ->
|
||||
{:type, line(meta), :map_field_assoc, typespec(k, vars, caller), typespec(v, vars, caller)}
|
||||
end)
|
||||
end, fields)
|
||||
{:type, line(meta), :map, fields}
|
||||
end
|
||||
|
||||
@@ -828,13 +828,6 @@ defmodule Kernel.Typespec do
|
||||
{:op, line(meta), op, {:integer, line(meta), integer}}
|
||||
end
|
||||
|
||||
# Handle access macro
|
||||
defp typespec({{:., meta, [Kernel, :access]}, meta1, [target, args]}, vars, caller) do
|
||||
access = {{:., meta, [Kernel, :access]}, meta1,
|
||||
[target, args ++ [_: {:any, [], []}]]}
|
||||
typespec(Macro.expand(access, caller), vars, caller)
|
||||
end
|
||||
|
||||
# Handle remote calls
|
||||
defp typespec({{:., meta, [remote, name]}, _, args} = orig, vars, caller) do
|
||||
remote = Macro.expand remote, caller
|
||||
@@ -865,7 +858,7 @@ defmodule Kernel.Typespec do
|
||||
|
||||
# Handle variables or local calls
|
||||
defp typespec({name, meta, atom}, vars, caller) when is_atom(atom) do
|
||||
if name in vars do
|
||||
if :lists.member(name, vars) do
|
||||
{:var, line(meta), name}
|
||||
else
|
||||
typespec({name, meta, []}, vars, caller)
|
||||
@@ -884,10 +877,6 @@ defmodule Kernel.Typespec do
|
||||
typespec((quote do: :elixir.char_list()), vars, caller)
|
||||
end
|
||||
|
||||
defp typespec({:char_data, _meta, []}, vars, caller) do
|
||||
typespec((quote do: :elixir.char_data()), vars, caller)
|
||||
end
|
||||
|
||||
defp typespec({:as_boolean, _meta, [arg]}, vars, caller) do
|
||||
typespec((quote do: :elixir.as_boolean(unquote(arg))), vars, caller)
|
||||
end
|
||||
@@ -919,10 +908,10 @@ defmodule Kernel.Typespec do
|
||||
end
|
||||
|
||||
defp typespec(list, vars, caller) do
|
||||
[h|t] = Enum.reverse(list)
|
||||
union = Enum.reduce(t, validate_kw(h, list, caller), fn(x, acc) ->
|
||||
[h|t] = :lists.reverse(list)
|
||||
union = :lists.foldl(fn(x, acc) ->
|
||||
{:|, [], [validate_kw(x, list, caller), acc]}
|
||||
end)
|
||||
end, validate_kw(h, list, caller), t)
|
||||
typespec({:list, [], [union]}, vars, caller)
|
||||
end
|
||||
|
||||
|
||||
+41
-19
@@ -6,13 +6,14 @@ defmodule Keyword do
|
||||
|
||||
A keyword may have duplicated keys so it is not strictly
|
||||
a dictionary. However most of the functions in this module
|
||||
behaves exactly as a dictionary and mimic the API defined
|
||||
behave exactly as a dictionary and mimic the API defined
|
||||
by the `Dict` behaviour.
|
||||
|
||||
For example, `Keyword.get` will get the first entry matching
|
||||
the given key, regardless if duplicated entries exist.
|
||||
Similarly, `Keyword.put` and `Keyword.delete` ensure all
|
||||
duplicated entries for a given key are removed when invoked.
|
||||
|
||||
A handful of functions exist to handle duplicated keys, in
|
||||
particular, `from_enum` allows creating a new keywords without
|
||||
removing duplicated keys, `get_values` returns all values for
|
||||
@@ -23,6 +24,7 @@ defmodule Keyword do
|
||||
in `Enum` and `List` can also be applied.
|
||||
"""
|
||||
|
||||
@compile :inline_list_funcs
|
||||
@behaviour Dict
|
||||
|
||||
@type key :: atom
|
||||
@@ -173,7 +175,12 @@ defmodule Keyword do
|
||||
"""
|
||||
@spec get_values(t, key) :: [value]
|
||||
def get_values(keywords, key) when is_list(keywords) and is_atom(key) do
|
||||
for {k, v} <- keywords, key == k, do: v
|
||||
fun = fn
|
||||
{k, v} when k === key -> {true, v}
|
||||
{_, _} -> false
|
||||
end
|
||||
|
||||
:lists.filtermap(fun, keywords)
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -191,7 +198,7 @@ defmodule Keyword do
|
||||
"""
|
||||
@spec keys(t) :: [key]
|
||||
def keys(keywords) when is_list(keywords) do
|
||||
for {key, _} <- keywords, do: key
|
||||
:lists.map(fn {k, _} -> k end, keywords)
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -205,11 +212,11 @@ defmodule Keyword do
|
||||
"""
|
||||
@spec values(t) :: [value]
|
||||
def values(keywords) when is_list(keywords) do
|
||||
for {_, value} <- keywords, do: value
|
||||
:lists.map(fn {_, v} -> v end, keywords)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Deletes the entry in the keyword list for a `key` with `value`.
|
||||
Deletes the entries in the keyword list for a `key` with `value`.
|
||||
If no `key` with `value` exists, returns the keyword list unchanged.
|
||||
|
||||
## Examples
|
||||
@@ -226,11 +233,11 @@ defmodule Keyword do
|
||||
"""
|
||||
@spec delete(t, key, value) :: t
|
||||
def delete(keywords, key, value) when is_list(keywords) and is_atom(key) do
|
||||
for {k, v} = tuple <- keywords, key != k or value != v, do: tuple
|
||||
:lists.filter(fn {k, v} -> k != key or v != value end, keywords)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Deletes all entries in the keyword list for a specific `key`.
|
||||
Deletes the 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.
|
||||
@@ -249,7 +256,7 @@ defmodule Keyword do
|
||||
"""
|
||||
@spec delete(t, key) :: t
|
||||
def delete(keywords, key) when is_list(keywords) and is_atom(key) do
|
||||
for {k, _} = tuple <- keywords, key != k, do: tuple
|
||||
:lists.filter(fn {k, _} -> k != key end, keywords)
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -338,7 +345,8 @@ defmodule Keyword do
|
||||
"""
|
||||
@spec merge(t, t) :: t
|
||||
def merge(d1, d2) when is_list(d1) and is_list(d2) do
|
||||
d2 ++ for({k, _} = tuple <- d1, not has_key?(d2, k), do: tuple)
|
||||
fun = fn {k, _v} -> not has_key?(d2, k) end
|
||||
d2 ++ :lists.filter(fun, d1)
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -387,6 +395,9 @@ defmodule Keyword do
|
||||
Updates the `key` with the given function. If the `key` does
|
||||
not exist, raises `KeyError`.
|
||||
|
||||
If there are duplicated entries, they are all removed and only the first one
|
||||
is updated.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Keyword.update!([a: 1], :a, &(&1 * 2))
|
||||
@@ -417,6 +428,9 @@ defmodule Keyword do
|
||||
Updates the `key` with the given function. If the `key` does
|
||||
not exist, inserts the given `initial` value.
|
||||
|
||||
If there are duplicated entries, they are all removed and only the first one
|
||||
is updated.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Keyword.update([a: 1], :a, 13, &(&1 * 2))
|
||||
@@ -440,8 +454,13 @@ defmodule Keyword do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Splits the given keywords in two given the given keys.
|
||||
Duplicated keys are preserved in the split keyword list.
|
||||
Takes all entries corresponding to the given keys and extracts them into a
|
||||
separate keyword list. Returns a tuple with the new list and the old list
|
||||
with removed keys.
|
||||
|
||||
Keys for which there are no entires in the keyword list are ignored.
|
||||
|
||||
Entries with duplicated keys end up in the same keyword list.
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -455,20 +474,22 @@ defmodule Keyword do
|
||||
|
||||
"""
|
||||
def split(keywords, keys) when is_list(keywords) do
|
||||
acc = {[], []}
|
||||
|
||||
{take, drop} = Enum.reduce keywords, acc, fn({k, v}, {take, drop}) ->
|
||||
fun = fn {k, v}, {take, drop} ->
|
||||
case k in keys do
|
||||
true -> {[{k, v}|take], drop}
|
||||
false -> {take, [{k, v}|drop]}
|
||||
end
|
||||
end
|
||||
|
||||
{Enum.reverse(take), Enum.reverse(drop)}
|
||||
acc = {[], []}
|
||||
{take, drop} = :lists.foldl(fun, acc, keywords)
|
||||
{:lists.reverse(take), :lists.reverse(drop)}
|
||||
end
|
||||
|
||||
@doc """
|
||||
Takes the given keys from the dict.
|
||||
Takes all entries corresponding to the given keys and returns them in a new
|
||||
keyword list.
|
||||
|
||||
Duplicated keys are preserved in the new keyword list.
|
||||
|
||||
## Examples
|
||||
@@ -483,11 +504,12 @@ defmodule Keyword do
|
||||
|
||||
"""
|
||||
def take(keywords, keys) when is_list(keywords) do
|
||||
for {k, _} = tuple <- keywords, k in keys, do: tuple
|
||||
:lists.filter(fn {k, _} -> k in keys end, keywords)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Drops the given keys from the dict.
|
||||
|
||||
Duplicated keys are preserved in the new keyword list.
|
||||
|
||||
## Examples
|
||||
@@ -496,13 +518,13 @@ defmodule Keyword do
|
||||
iex> Keyword.drop(d, [:b, :d])
|
||||
[a: 1, c: 3]
|
||||
|
||||
iex> d = [a: 1, b: 2, c: 3, d: 4, a: 5]
|
||||
iex> d = [a: 1, b: 2, b: 3, c: 3, d: 4, a: 5]
|
||||
iex> Keyword.drop(d, [:b, :d])
|
||||
[a: 1, c: 3, a: 5]
|
||||
|
||||
"""
|
||||
def drop(keywords, keys) when is_list(keywords) do
|
||||
for {k, _} = tuple <- keywords, not k in keys, do: tuple
|
||||
:lists.filter(fn {k, _} -> not k in keys end, keywords)
|
||||
end
|
||||
|
||||
@doc """
|
||||
|
||||
+103
-37
@@ -337,7 +337,7 @@ defmodule List do
|
||||
"""
|
||||
@spec unzip([tuple]) :: [list]
|
||||
def unzip(list) when is_list(list) do
|
||||
:lists.map &tuple_to_list/1, zip(list)
|
||||
:lists.map &Tuple.to_list/1, zip(list)
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -454,56 +454,122 @@ defmodule List do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Converts char data into a char list.
|
||||
Converts a char list to an atom.
|
||||
|
||||
A char data is a string or a list of integers and strings and it
|
||||
is converted into a list with each codepoint represented as its
|
||||
respective integer value.
|
||||
Currently Elixir does not support conversions from char lists
|
||||
which contains Unicode codepoints greater than 0xFF.
|
||||
|
||||
Inlined by the compiler.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> List.from_char_data("æß")
|
||||
{:ok, 'æß'}
|
||||
iex> List.from_char_data([?a, "bc"])
|
||||
{:ok, 'abc'}
|
||||
iex> List.to_atom('elixir')
|
||||
:elixir
|
||||
|
||||
"""
|
||||
@spec from_char_data(char_data) :: {:ok, char_list} | {:error, list, binary} | {:incomplete, list, binary}
|
||||
def from_char_data(char_data) do
|
||||
case :unicode.characters_to_list(char_data) do
|
||||
result when is_list(result) ->
|
||||
{:ok, result}
|
||||
|
||||
{:error, _, _} = error ->
|
||||
error
|
||||
|
||||
{:incomplete, _, _} = incomplete ->
|
||||
incomplete
|
||||
end
|
||||
@spec to_atom(char_list) :: atom
|
||||
def to_atom(char_list) do
|
||||
:erlang.list_to_atom(char_list)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Converts char data into a char list.
|
||||
Converts a char list to an existing atom.
|
||||
|
||||
A char data is a string or a list of integers and strings and it
|
||||
is converted into a list with each codepoint represented as its
|
||||
respective integer value.
|
||||
Currently Elixir does not support conversions from char lists
|
||||
which contains Unicode codepoints greater than 0xFF.
|
||||
|
||||
In case the conversion fails or is incomplete, it raises a
|
||||
`UnicodeConversionError`.
|
||||
Inlined by the compiler.
|
||||
"""
|
||||
@spec to_existing_atom(char_list) :: atom
|
||||
def to_existing_atom(char_list) do
|
||||
:erlang.list_to_existing_atom(char_list)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns the float whose text representation is `char_list`.
|
||||
|
||||
Inlined by the compiler.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> List.from_char_data!("æß")
|
||||
'æß'
|
||||
iex> List.from_char_data!("abc")
|
||||
'abc'
|
||||
iex> List.to_float('2.2017764e+0')
|
||||
2.2017764
|
||||
|
||||
"""
|
||||
@spec from_char_data!(char_data) :: char_list | no_return
|
||||
def from_char_data!(char_data) do
|
||||
case :unicode.characters_to_list(char_data) do
|
||||
result when is_list(result) ->
|
||||
@spec to_float(char_list) :: float
|
||||
def to_float(char_list) do
|
||||
:erlang.list_to_float(char_list)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns an integer whose text representation is `char_list`.
|
||||
|
||||
Inlined by the compiler.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> List.to_integer('123')
|
||||
123
|
||||
|
||||
"""
|
||||
@spec to_integer(char_list) :: integer
|
||||
def to_integer(char_list) do
|
||||
:erlang.list_to_integer(char_list)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns an integer whose text representation is `char_list` in base `base`.
|
||||
|
||||
Inlined by the compiler.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> List.to_integer('3FF', 16)
|
||||
1023
|
||||
|
||||
"""
|
||||
@spec to_integer(char_list, non_neg_integer) :: integer
|
||||
def to_integer(char_list, base) do
|
||||
:erlang.list_to_integer(char_list, base)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Converts a list to a tuple.
|
||||
|
||||
Inlined by the compiler.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> List.to_tuple([:share, [:elixir, 163]])
|
||||
{:share, [:elixir, 163]}
|
||||
|
||||
"""
|
||||
@spec to_tuple(list) :: tuple
|
||||
def to_tuple(list) do
|
||||
:erlang.list_to_tuple(list)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Converts a list of integers representing codepoints, lists or
|
||||
strings into a string.
|
||||
|
||||
Notice that this function expect a list of integer representing
|
||||
UTF-8 codepoints. If you have a list of bytes, you must instead use
|
||||
[the `:binary` module](http://erlang.org/doc/man/binary.html).
|
||||
|
||||
## Examples
|
||||
|
||||
iex> List.to_string([0x00E6, 0x00DF])
|
||||
"æß"
|
||||
|
||||
iex> List.to_string([0x0061, "bc"])
|
||||
"abc"
|
||||
|
||||
"""
|
||||
@spec to_string(:unicode.char_list) :: String.t
|
||||
def to_string(list) when is_list(list) do
|
||||
case :unicode.characters_to_binary(list) do
|
||||
result when is_binary(result) ->
|
||||
result
|
||||
|
||||
{:error, encoded, rest} ->
|
||||
@@ -592,7 +658,7 @@ defmodule List do
|
||||
|
||||
case heads do
|
||||
nil -> :lists.reverse acc
|
||||
_ -> do_zip mlist, [list_to_tuple(:lists.reverse(heads))|acc]
|
||||
_ -> do_zip mlist, [:erlang.list_to_tuple(:lists.reverse(heads))|acc]
|
||||
end
|
||||
end
|
||||
|
||||
@@ -608,6 +674,6 @@ defmodule List do
|
||||
{nil, nil}
|
||||
end
|
||||
|
||||
defp to_list(tuple) when is_tuple(tuple), do: tuple_to_list(tuple)
|
||||
defp to_list(tuple) when is_tuple(tuple), do: Tuple.to_list(tuple)
|
||||
defp to_list(list) when is_list(list), do: list
|
||||
end
|
||||
|
||||
@@ -13,7 +13,7 @@ defprotocol List.Chars do
|
||||
end
|
||||
|
||||
defimpl List.Chars, for: Atom do
|
||||
def to_char_list(atom), do: atom_to_list(atom)
|
||||
def to_char_list(atom), do: Atom.to_char_list(atom)
|
||||
end
|
||||
|
||||
defimpl List.Chars, for: BitString do
|
||||
@@ -21,7 +21,7 @@ defimpl List.Chars, for: BitString do
|
||||
Returns the given binary converted to a char list.
|
||||
"""
|
||||
def to_char_list(thing) when is_binary(thing) do
|
||||
List.from_char_data!(thing)
|
||||
String.to_char_list(thing)
|
||||
end
|
||||
|
||||
def to_char_list(thing) do
|
||||
@@ -38,7 +38,7 @@ end
|
||||
|
||||
defimpl List.Chars, for: Integer do
|
||||
def to_char_list(thing) do
|
||||
integer_to_list(thing)
|
||||
Integer.to_char_list(thing)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -47,10 +47,10 @@ defimpl List.Chars, for: Float do
|
||||
@limit :math.pow(10, @digits)
|
||||
|
||||
def to_char_list(thing) when thing > @limit do
|
||||
float_to_list(thing, scientific: @digits)
|
||||
Float.to_char_list(thing, scientific: @digits)
|
||||
end
|
||||
|
||||
def to_char_list(thing) do
|
||||
float_to_list(thing, compact: true, decimals: @digits)
|
||||
Float.to_char_list(thing, compact: true, decimals: @digits)
|
||||
end
|
||||
end
|
||||
|
||||
+155
-65
@@ -16,7 +16,7 @@ defmodule Macro do
|
||||
:&&, :||, :<>, :++, :--, :\\, :::, :<-, :.., :|>, :=~,
|
||||
:<, :>, :->,
|
||||
:+, :-, :*, :/, :=, :|, :.,
|
||||
:and, :or, :xor, :when, :in, :inlist, :inbits,
|
||||
:and, :or, :xor, :when, :in,
|
||||
:<<<, :>>>, :|||, :&&&, :^^^, :~~~]
|
||||
|
||||
@doc false
|
||||
@@ -30,7 +30,7 @@ defmodule Macro do
|
||||
@spec binary_op_props(atom) :: {:left | :right, precedence :: integer}
|
||||
defp binary_op_props(o) do
|
||||
case o do
|
||||
o when o in [:<-, :inlist, :inbits, :\\, :::] -> {:left, 40}
|
||||
o when o in [:<-, :\\, :::] -> {:left, 40}
|
||||
:| -> {:right, 50}
|
||||
:when -> {:right, 70}
|
||||
:= -> {:right, 80}
|
||||
@@ -68,7 +68,7 @@ defmodule Macro do
|
||||
def pipe(expr, call_args, position)
|
||||
|
||||
def pipe(expr, {:&, _, _} = call_args, _integer) do
|
||||
raise ArgumentError, message: "cannot pipe #{to_string expr} into #{to_string call_args}"
|
||||
raise ArgumentError, "cannot pipe #{to_string expr} into #{to_string call_args}"
|
||||
end
|
||||
|
||||
def pipe(expr, {call, line, atom}, integer) when is_atom(atom) do
|
||||
@@ -80,17 +80,15 @@ defmodule Macro do
|
||||
end
|
||||
|
||||
def pipe(expr, call_args, _integer) do
|
||||
raise ArgumentError,
|
||||
message: "cannot pipe #{to_string expr} into #{to_string call_args}"
|
||||
raise ArgumentError, "cannot pipe #{to_string expr} into #{to_string call_args}"
|
||||
end
|
||||
|
||||
@doc """
|
||||
Recurs the quoted expression applying the given function to
|
||||
each metadata node.
|
||||
Applies the given function to the node metadata if it contains one.
|
||||
|
||||
This is often useful to remove information like lines and
|
||||
hygienic counters from the expression for either storage or
|
||||
comparison.
|
||||
This is often useful when used with `Macro.prewalk/1` to remove
|
||||
information like lines and hygienic counters from the expression
|
||||
for either storage or comparison.
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -104,21 +102,135 @@ defmodule Macro do
|
||||
def update_meta(quoted, fun)
|
||||
|
||||
def update_meta({left, meta, right}, fun) when is_list(meta) do
|
||||
{update_meta(left, fun), fun.(meta), update_meta(right, fun)}
|
||||
end
|
||||
|
||||
def update_meta({left, right}, fun) do
|
||||
{update_meta(left, fun), update_meta(right, fun)}
|
||||
end
|
||||
|
||||
def update_meta(list, fun) when is_list(list) do
|
||||
for x <- list, do: update_meta(x, fun)
|
||||
{left, fun.(meta), right}
|
||||
end
|
||||
|
||||
def update_meta(other, _fun) do
|
||||
other
|
||||
end
|
||||
|
||||
@doc """
|
||||
Genrates a AST node representing the variable given
|
||||
by the atoms `var` and `context`.
|
||||
|
||||
## Examples
|
||||
|
||||
In order to build a variable, a context is expected.
|
||||
Most of the times, in order to preserve hygiene, the
|
||||
context must be `__MODULE__`:
|
||||
|
||||
iex> Macro.var(:foo, __MODULE__)
|
||||
{:foo, [], __MODULE__}
|
||||
|
||||
However, if there is a need to access the user variable,
|
||||
nil can be given:
|
||||
|
||||
iex> Macro.var(:foo, nil)
|
||||
{:foo, [], nil}
|
||||
|
||||
"""
|
||||
@spec var(var, context) :: {var, [], context} when var: atom, context: atom
|
||||
def var(var, context) when is_atom(var) and is_atom(context) do
|
||||
{var, [], context}
|
||||
end
|
||||
|
||||
@doc """
|
||||
Performs a depth-first, pre-order traversal of quoted expressions.
|
||||
"""
|
||||
@spec prewalk(t, (t -> t)) :: t
|
||||
def prewalk(ast, fun) when is_function(fun, 1) do
|
||||
elem(prewalk(ast, nil, fn x, nil -> {fun.(x), nil} end), 0)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Performs a depth-first, pre-order traversal of quoted expressions
|
||||
using an accumulator.
|
||||
"""
|
||||
@spec prewalk(t, any, (t, any -> {t, any})) :: {t, any}
|
||||
def prewalk(ast, acc, fun) when is_function(fun, 2) do
|
||||
{ast, acc} = fun.(ast, acc)
|
||||
do_prewalk(ast, acc, fun)
|
||||
end
|
||||
|
||||
defp do_prewalk({form, meta, args}, acc, fun) do
|
||||
unless is_atom(form) do
|
||||
{form, acc} = fun.(form, acc)
|
||||
{form, acc} = do_prewalk(form, acc, fun)
|
||||
end
|
||||
|
||||
unless is_atom(args) do
|
||||
{args, acc} = Enum.map_reduce(args, acc, fn x, acc ->
|
||||
{x, acc} = fun.(x, acc)
|
||||
do_prewalk(x, acc, fun)
|
||||
end)
|
||||
end
|
||||
|
||||
{{form, meta, args}, acc}
|
||||
end
|
||||
|
||||
defp do_prewalk({left, right}, acc, fun) do
|
||||
{left, acc} = fun.(left, acc)
|
||||
{left, acc} = do_prewalk(left, acc, fun)
|
||||
{right, acc} = fun.(right, acc)
|
||||
{right, acc} = do_prewalk(right, acc, fun)
|
||||
{{left, right}, acc}
|
||||
end
|
||||
|
||||
defp do_prewalk(list, acc, fun) when is_list(list) do
|
||||
Enum.map_reduce(list, acc, fn x, acc ->
|
||||
{x, acc} = fun.(x, acc)
|
||||
do_prewalk(x, acc, fun)
|
||||
end)
|
||||
end
|
||||
|
||||
defp do_prewalk(x, acc, _fun) do
|
||||
{x, acc}
|
||||
end
|
||||
|
||||
@doc """
|
||||
Performs a depth-first, post-order traversal of quoted expressions.
|
||||
"""
|
||||
@spec postwalk(t, (t -> t)) :: t
|
||||
def postwalk(ast, fun) when is_function(fun, 1) do
|
||||
elem(postwalk(ast, nil, fn x, nil -> {fun.(x), nil} end), 0)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Performs a depth-first, post-order traversal of quoted expressions
|
||||
using an accumulator.
|
||||
"""
|
||||
@spec postwalk(t, any, (t, any -> {t, any})) :: {t, any}
|
||||
def postwalk(ast, acc, fun) when is_function(fun, 2) do
|
||||
do_postwalk(ast, acc, fun)
|
||||
end
|
||||
|
||||
defp do_postwalk({form, meta, args}, acc, fun) do
|
||||
unless is_atom(form) do
|
||||
{form, acc} = do_postwalk(form, acc, fun)
|
||||
end
|
||||
|
||||
unless is_atom(args) do
|
||||
{args, acc} = Enum.map_reduce(args, acc, &do_postwalk(&1, &2, fun))
|
||||
end
|
||||
|
||||
fun.({form, meta, args}, acc)
|
||||
end
|
||||
|
||||
defp do_postwalk({left, right}, acc, fun) do
|
||||
{left, acc} = do_postwalk(left, acc, fun)
|
||||
{right, acc} = do_postwalk(right, acc, fun)
|
||||
fun.({left, right}, acc)
|
||||
end
|
||||
|
||||
defp do_postwalk(list, acc, fun) when is_list(list) do
|
||||
{list, acc} = Enum.map_reduce(list, acc, &do_postwalk(&1, &2, fun))
|
||||
fun.(list, acc)
|
||||
end
|
||||
|
||||
defp do_postwalk(x, acc, fun) do
|
||||
fun.(x, acc)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Decomposes a local or remote call into its remote part (when provided),
|
||||
function name and argument list.
|
||||
@@ -300,7 +412,7 @@ defmodule Macro do
|
||||
|
||||
# Variables
|
||||
def to_string({var, _, atom} = ast, fun) when is_atom(atom) do
|
||||
fun.(ast, atom_to_binary(var))
|
||||
fun.(ast, Atom.to_string(var))
|
||||
end
|
||||
|
||||
# Aliases
|
||||
@@ -389,7 +501,7 @@ defmodule Macro do
|
||||
# Unary ops
|
||||
def to_string({unary, _, [{binary, _, [_, _]} = arg]} = ast, fun)
|
||||
when unary in unquote(@unary_ops) and binary in unquote(@binary_ops) do
|
||||
fun.(ast, atom_to_binary(unary) <> "(" <> to_string(arg, fun) <> ")")
|
||||
fun.(ast, Atom.to_string(unary) <> "(" <> to_string(arg, fun) <> ")")
|
||||
end
|
||||
|
||||
def to_string({:not, _, [arg]} = ast, fun) do
|
||||
@@ -397,12 +509,12 @@ defmodule Macro do
|
||||
end
|
||||
|
||||
def to_string({op, _, [arg]} = ast, fun) when op in unquote(@unary_ops) do
|
||||
fun.(ast, atom_to_binary(op) <> to_string(arg, fun))
|
||||
fun.(ast, Atom.to_string(op) <> to_string(arg, fun))
|
||||
end
|
||||
|
||||
# Access
|
||||
def to_string({{:., _, [Kernel, :access]}, _, [left, right]} = ast, fun) do
|
||||
fun.(ast, to_string(left, fun) <> to_string(right, fun))
|
||||
def to_string({{:., _, [Access, :get]}, _, [left, right]} = ast, fun) do
|
||||
fun.(ast, to_string(left, fun) <> to_string([right], fun))
|
||||
end
|
||||
|
||||
# All other calls
|
||||
@@ -425,7 +537,7 @@ defmodule Macro do
|
||||
list == [] ->
|
||||
"[]"
|
||||
:io_lib.printable_list(list) ->
|
||||
"'" <> Inspect.BitString.escape(String.from_char_data!(list), ?') <> "'"
|
||||
"'" <> Inspect.BitString.escape(IO.chardata_to_string(list), ?') <> "'"
|
||||
Keyword.keyword?(list) ->
|
||||
"[" <> kw_list_to_string(list, fun) <> "]"
|
||||
true ->
|
||||
@@ -434,7 +546,7 @@ defmodule Macro do
|
||||
end
|
||||
|
||||
# All other structures
|
||||
def to_string(other, fun), do: fun.(other, inspect(other, records: false))
|
||||
def to_string(other, fun), do: fun.(other, inspect(other, []))
|
||||
|
||||
# Block keywords
|
||||
@kw_keywords [:do, :catch, :rescue, :after, :else]
|
||||
@@ -444,12 +556,12 @@ defmodule Macro do
|
||||
end
|
||||
defp kw_blocks?(_), do: false
|
||||
|
||||
defp module_to_string(atom, _fun) when is_atom(atom), do: inspect(atom, records: false)
|
||||
defp module_to_string(atom, _fun) when is_atom(atom), do: inspect(atom, [])
|
||||
defp module_to_string(other, fun), do: call_to_string(other, fun)
|
||||
|
||||
defp call_to_string(atom, _fun) when is_atom(atom), do: atom_to_binary(atom)
|
||||
defp call_to_string({:., _, [arg]}, fun), do: module_to_string(arg, fun) <> "."
|
||||
defp call_to_string({:., _, [left, right]}, fun), do: module_to_string(left, fun) <> "." <> call_to_string(right, fun)
|
||||
defp call_to_string(atom, _fun) when is_atom(atom), do: Atom.to_string(atom)
|
||||
defp call_to_string({:., _, [arg]}, fun), do: module_to_string(arg, fun) <> "."
|
||||
defp call_to_string({:., _, [left, right]}, fun), do: module_to_string(left, fun) <> "." <> call_to_string(right, fun)
|
||||
defp call_to_string(other, fun), do: to_string(other, fun)
|
||||
|
||||
defp call_to_string_with_args(target, args, fun) do
|
||||
@@ -481,7 +593,7 @@ defmodule Macro do
|
||||
|
||||
defp kw_block_to_string(key, value, fun) do
|
||||
block = adjust_new_lines block_to_string(value, fun), "\n "
|
||||
atom_to_binary(key) <> "\n " <> block <> "\n"
|
||||
Atom.to_string(key) <> "\n " <> block <> "\n"
|
||||
end
|
||||
|
||||
defp block_to_string([{:->, _, _}|_] = block, fun) do
|
||||
@@ -510,7 +622,11 @@ defmodule Macro do
|
||||
|
||||
defp kw_list_to_string(list, fun) do
|
||||
Enum.map_join(list, ", ", fn {key, value} ->
|
||||
atom_to_binary(key) <> ": " <> to_string(value, fun)
|
||||
atom_name = case Inspect.Atom.inspect(key) do
|
||||
":" <> rest -> rest
|
||||
other -> other
|
||||
end
|
||||
atom_name <> ": " <> to_string(value, fun)
|
||||
end)
|
||||
end
|
||||
|
||||
@@ -570,10 +686,10 @@ defmodule Macro do
|
||||
|
||||
The following contents are expanded:
|
||||
|
||||
* Macros (local or remote);
|
||||
* Aliases are expanded (if possible) and return atoms;
|
||||
* Pseudo-variables (`__ENV__`, `__MODULE__` and `__DIR__`);
|
||||
* Module attributes reader (`@foo`);
|
||||
* Macros (local or remote)
|
||||
* Aliases are expanded (if possible) and return atoms
|
||||
* Pseudo-variables (`__ENV__`, `__MODULE__` and `__DIR__`)
|
||||
* Module attributes reader (`@foo`)
|
||||
|
||||
If the expression cannot be expanded, it returns the expression
|
||||
itself. Notice that `expand_once/2` performs the expansion just
|
||||
@@ -590,7 +706,7 @@ defmodule Macro do
|
||||
Consider the implementation below:
|
||||
|
||||
defmacro defmodule_with_length(name, do: block) do
|
||||
length = length(atom_to_list(name))
|
||||
length = length(Atom.to_char_list(name))
|
||||
|
||||
quote do
|
||||
defmodule unquote(name) do
|
||||
@@ -630,7 +746,7 @@ defmodule Macro do
|
||||
|
||||
defmacro defmodule_with_length(name, do: block) do
|
||||
expanded = Macro.expand(name, __CALLER__)
|
||||
length = length(atom_to_list(expanded))
|
||||
length = length(Atom.to_char_list(expanded))
|
||||
|
||||
quote do
|
||||
defmodule unquote(name) do
|
||||
@@ -722,7 +838,7 @@ defmodule Macro do
|
||||
{:ok, receiver, quoted} ->
|
||||
next = :elixir_counter.next
|
||||
{:elixir_quote.linify_with_context_counter(0, {receiver, next}, quoted), true}
|
||||
{:ok, _receiver} ->
|
||||
{:ok, _receiver, _name, _args} ->
|
||||
{original, false}
|
||||
:error ->
|
||||
{original, false}
|
||||
@@ -770,30 +886,4 @@ defmodule Macro do
|
||||
defp expand_until({tree, false}, _env) do
|
||||
tree
|
||||
end
|
||||
|
||||
@doc """
|
||||
Recursively traverses the quoted expression checking if all sub-terms are
|
||||
safe.
|
||||
|
||||
Terms are considered safe if they represent data structures and don't actually
|
||||
evaluate code. Returns `:ok` unless a given term is unsafe,
|
||||
which is returned as `{:unsafe, term}`.
|
||||
"""
|
||||
def safe_term(terms) do
|
||||
IO.write :stderr, "Macro.safe_term/1 is deprecated\n#{Exception.format_stacktrace}"
|
||||
do_safe_term(terms) || :ok
|
||||
end
|
||||
|
||||
defp do_safe_term({local, _, terms}) when local in [:{}, :%{}, :__aliases__] do
|
||||
do_safe_term(terms)
|
||||
end
|
||||
|
||||
defp do_safe_term({unary, _, [term]}) when unary in [:+, :-] do
|
||||
do_safe_term(term)
|
||||
end
|
||||
|
||||
defp do_safe_term({left, right}), do: do_safe_term(left) || do_safe_term(right)
|
||||
defp do_safe_term(terms) when is_list(terms), do: Enum.find_value(terms, &do_safe_term(&1))
|
||||
defp do_safe_term(terms) when is_tuple(terms), do: {:unsafe, terms}
|
||||
defp do_safe_term(_), do: nil
|
||||
end
|
||||
|
||||
+21
-19
@@ -6,25 +6,27 @@ defmodule Macro.Env do
|
||||
`__ENV__`. Inside macros, the caller environment can be
|
||||
accessed as `__CALLER__`. It contains the following fields:
|
||||
|
||||
* `module` - the current module name.
|
||||
* `file` - the current file name as a binary
|
||||
* `line` - the current line as an integer
|
||||
* `function` - a tuple as `{atom, integer`}, where the first
|
||||
element is the function name and the seconds its arity. Returns
|
||||
`nil` if not inside a function
|
||||
* `context` - the context of the environment. It can be nil
|
||||
(default context), inside a guard or inside an assign
|
||||
* `aliases` - a list of two item tuples, where the first
|
||||
item is the aliased name and the second the actual name
|
||||
* `requires` - the list of required modules
|
||||
* `functions` - a list of functions imported from each module
|
||||
* `macros` - a list of macros imported from each module
|
||||
* `macro_aliases` - a list of aliases defined inside the current macro
|
||||
* `context_modules` - a list of modules defined in the current context
|
||||
* `vars` - a list keeping all defined variables as {var, context}
|
||||
* `export_vars` - a list keeping all variables to be exported in a construct (may be nil)
|
||||
* `lexical_tracker` - PID to the lexical tracker which is responsible to keep user info
|
||||
* `local` - the module to expand local functions to
|
||||
* `module` - the current module name
|
||||
* `file` - the current file name as a binary
|
||||
* `line` - the current line as an integer
|
||||
* `function` - a tuple as `{atom, integer`}, where the first
|
||||
element is the function name and the seconds its arity; returns
|
||||
`nil` if not inside a function
|
||||
* `context` - the context of the environment; it can be `nil`
|
||||
(default context), inside a guard or inside an assign
|
||||
* `aliases` - a list of two item tuples, where the first
|
||||
item is the aliased name and the second the actual name
|
||||
* `requires` - the list of required modules
|
||||
* `functions` - a list of functions imported from each module
|
||||
* `macros` - a list of macros imported from each module
|
||||
* `macro_aliases` - a list of aliases defined inside the current macro
|
||||
* `context_modules` - a list of modules defined in the current context
|
||||
* `vars` - a list keeping all defined variables as `{var, context}`
|
||||
* `export_vars` - a list keeping all variables to be exported in a
|
||||
construct (may be `nil`)
|
||||
* `lexical_tracker` - PID of the lexical tracker which is responsible to
|
||||
keep user info
|
||||
* `local` - the module to expand local functions to
|
||||
"""
|
||||
|
||||
@type name_arity :: {atom, non_neg_integer}
|
||||
|
||||
+4
-44
@@ -11,10 +11,12 @@ defmodule Map do
|
||||
their APIs, please consult the `Dict` module.
|
||||
"""
|
||||
|
||||
use Dict.Behaviour
|
||||
use Dict
|
||||
|
||||
defdelegate [keys(map), values(map), size(map), merge(map1, map2), to_list(map)], to: :maps
|
||||
|
||||
@compile {:inline, fetch: 2, put: 3, delete: 2, has_key?: 2}
|
||||
|
||||
@doc """
|
||||
Returns a new empty map.
|
||||
"""
|
||||
@@ -24,21 +26,10 @@ defmodule Map do
|
||||
|
||||
def fetch(map, key), do: :maps.find(key, map)
|
||||
|
||||
def pop(map, key, default \\ nil) do
|
||||
{get(map, key, default), delete(map, key)}
|
||||
end
|
||||
|
||||
def put(map, key, val) do
|
||||
:maps.put(key, val, map)
|
||||
end
|
||||
|
||||
def put_new(map, key, val) do
|
||||
case has_key?(map, key) do
|
||||
true -> map
|
||||
false -> :maps.put(key, val, map)
|
||||
end
|
||||
end
|
||||
|
||||
def delete(map, key), do: :maps.remove(key, map)
|
||||
|
||||
def merge(map1, map2, callback) do
|
||||
@@ -47,36 +38,5 @@ defmodule Map do
|
||||
end, map1, map2
|
||||
end
|
||||
|
||||
def split(map, keys) do
|
||||
acc = {%{}, %{}}
|
||||
|
||||
:maps.fold fn k, v, {take, drop} ->
|
||||
if k in keys do
|
||||
{put(take, k, v), drop}
|
||||
else
|
||||
{take, put(drop, k, v)}
|
||||
end
|
||||
end, acc, map
|
||||
end
|
||||
|
||||
def update!(map, key, fun) do
|
||||
case :maps.find(key, map) do
|
||||
:error ->
|
||||
raise(KeyError, key: key, term: map)
|
||||
{:ok, val} ->
|
||||
:maps.put(key, fun.(val), map)
|
||||
end
|
||||
end
|
||||
|
||||
def update(map, key, initial, fun) do
|
||||
case :maps.find(key, map) do
|
||||
:error ->
|
||||
:maps.put(key, initial, map)
|
||||
{:ok, val} ->
|
||||
:maps.put(key, fun.(val), map)
|
||||
end
|
||||
end
|
||||
|
||||
def equal?(map, map), do: true
|
||||
def equal?(_, _), do: false
|
||||
def equal?(%{} = map1, %{} = map2), do: map1 === map2
|
||||
end
|
||||
|
||||
+53
-44
@@ -14,7 +14,7 @@ defmodule Module do
|
||||
Each module can be decorated with one or more attributes. The following ones
|
||||
are currently defined by Elixir:
|
||||
|
||||
* `@after_compile`
|
||||
* `@after_compile`
|
||||
|
||||
A hook that will be invoked right after the current module is compiled.
|
||||
|
||||
@@ -33,7 +33,7 @@ defmodule Module do
|
||||
end
|
||||
end
|
||||
|
||||
* `@before_compile`
|
||||
* `@before_compile`
|
||||
|
||||
A hook that will be invoked before the module is compiled.
|
||||
|
||||
@@ -63,7 +63,7 @@ defmodule Module do
|
||||
@before_compile A
|
||||
end
|
||||
|
||||
* `@behaviour` (notice the British spelling)
|
||||
* `@behaviour` (notice the British spelling)
|
||||
|
||||
Specify an OTP or user-defined behaviour.
|
||||
|
||||
@@ -75,7 +75,7 @@ defmodule Module do
|
||||
# ...
|
||||
end
|
||||
|
||||
* `@compile`
|
||||
* `@compile`
|
||||
|
||||
Define options for module compilation that are passed to the Erlang
|
||||
compiler.
|
||||
@@ -95,7 +95,7 @@ defmodule Module do
|
||||
end
|
||||
end
|
||||
|
||||
* `@doc`
|
||||
* `@doc`
|
||||
|
||||
Provide documentation for the function or macro that follows the
|
||||
attribute.
|
||||
@@ -122,7 +122,7 @@ defmodule Module do
|
||||
end
|
||||
end
|
||||
|
||||
* `@file`
|
||||
* `@file`
|
||||
|
||||
Change the filename used in stacktraces for the function or macro that
|
||||
follows the attribute.
|
||||
@@ -139,7 +139,7 @@ defmodule Module do
|
||||
end
|
||||
end
|
||||
|
||||
* `@moduledoc`
|
||||
* `@moduledoc`
|
||||
|
||||
Provide documentation for the current module.
|
||||
|
||||
@@ -156,7 +156,7 @@ defmodule Module do
|
||||
end
|
||||
|
||||
|
||||
* `@on_definition`
|
||||
* `@on_definition`
|
||||
|
||||
A hook that will be invoked when each function or macro in the current
|
||||
module is defined. Useful when annotating functions.
|
||||
@@ -211,7 +211,7 @@ defmodule Module do
|
||||
end
|
||||
end
|
||||
|
||||
* `@on_load`
|
||||
* `@on_load`
|
||||
|
||||
A hook that will be invoked whenever the module is loaded.
|
||||
|
||||
@@ -237,7 +237,7 @@ defmodule Module do
|
||||
end
|
||||
end
|
||||
|
||||
* `@vsn`
|
||||
* `@vsn`
|
||||
|
||||
Specify the module version. Accepts any valid Elixir value.
|
||||
|
||||
@@ -247,14 +247,25 @@ defmodule Module do
|
||||
@vsn "1.0"
|
||||
end
|
||||
|
||||
* `@external_resource`
|
||||
|
||||
Specify an external resource to the current module.
|
||||
|
||||
Many times a module embeds information from an external file. This
|
||||
attribute allows the module to annotate which external resources
|
||||
have been used.
|
||||
|
||||
Tools like Mix may use this information to ensure the module is
|
||||
recompiled in case any of the external resources change.
|
||||
|
||||
The following attributes are part of typespecs and are also reserved by
|
||||
Elixir (see `Kernel.Typespec` for more information about typespecs):
|
||||
|
||||
* `@type` - defines a type to be used in `@spec`
|
||||
* `@typep` - defines a private type to be used in `@spec`
|
||||
* `@opaque` - defines an opaque type to be used in `@spec`
|
||||
* `@spec` - provides a specification for a function
|
||||
* `@callback` - provides a specification for the behaviour callback
|
||||
* `@type` - defines a type to be used in `@spec`
|
||||
* `@typep` - defines a private type to be used in `@spec`
|
||||
* `@opaque` - defines an opaque type to be used in `@spec`
|
||||
* `@spec` - provides a specification for a function
|
||||
* `@callback` - provides a specification for the behaviour callback
|
||||
|
||||
In addition to the built-in attributes outlined above, custom attributes may
|
||||
also be added. A custom attribute is any valid identifier prefixed with an
|
||||
@@ -280,18 +291,11 @@ defmodule Module do
|
||||
Each module gets an `__info__/1` function when it's compiled. The function
|
||||
takes one of the following atoms:
|
||||
|
||||
* `:functions` - keyword list of public functions along with their arities
|
||||
* `:functions` - keyword list of public functions along with their arities
|
||||
|
||||
* `:macros` - keyword list of public macros along with their arities
|
||||
* `:macros` - keyword list of public macros along with their arities
|
||||
|
||||
* `:docs` - list of all docstrings attached to functions and macros
|
||||
using the `@doc` attribute
|
||||
|
||||
* `:moduledoc` - tuple `{<line>, <doc>}` where `line` is the line on
|
||||
which module definition starts and `doc` is the string
|
||||
attached to the module using the `@moduledoc` attribute
|
||||
|
||||
* `:module` - module name (`Module == Module.__info__(:module)`)
|
||||
* `:module` - module name (`Module == Module.__info__(:module)`)
|
||||
|
||||
In addition to the above, you may also pass to `__info__/1` any atom supported
|
||||
by Erlang's `module_info` function which also gets defined for each compiled
|
||||
@@ -356,8 +360,10 @@ defmodule Module do
|
||||
|
||||
@doc """
|
||||
Creates a module with the given name and defined by
|
||||
the given quoted expressions. The line where the module
|
||||
is defined and its file can be passed as options.
|
||||
the given quoted expressions.
|
||||
|
||||
The line where the module is defined and its file **must**
|
||||
be passed as options.
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -383,13 +389,16 @@ defmodule Module do
|
||||
when defining the module, while `defmodule` automatically
|
||||
shares the same environment.
|
||||
"""
|
||||
def create(module, quoted, opts \\ [])
|
||||
def create(module, quoted, opts)
|
||||
|
||||
def create(module, quoted, %Macro.Env{} = env) do
|
||||
create(module, quoted, Map.to_list(env))
|
||||
end
|
||||
|
||||
def create(module, quoted, opts) when is_atom(module) do
|
||||
def create(module, quoted, opts) when is_atom(module) and is_list(opts) do
|
||||
unless Keyword.has_key?(opts, :file) do
|
||||
raise ArgumentError, "expected :file to be given as option"
|
||||
end
|
||||
:elixir_module.compile(module, quoted, [], :elixir.env_for_eval(opts))
|
||||
end
|
||||
|
||||
@@ -507,9 +516,9 @@ defmodule Module do
|
||||
assert_not_compiled!(:add_doc, module)
|
||||
table = docs_table_for(module)
|
||||
|
||||
{signature, _} = Enum.map_reduce signature, 1, fn(x, acc) ->
|
||||
{signature, _} = :lists.mapfoldl fn(x, acc) ->
|
||||
{simplify_signature(x, acc), acc + 1}
|
||||
end
|
||||
end, 1, signature
|
||||
|
||||
case :ets.lookup(table, tuple) do
|
||||
[] ->
|
||||
@@ -534,8 +543,8 @@ defmodule Module do
|
||||
end
|
||||
|
||||
defp simplify_signature({:%, _, [left, _]}, _i) when is_atom(left) do
|
||||
last = List.last(String.split(atom_to_binary(left), "."))
|
||||
atom = binary_to_atom(downcase(last))
|
||||
last = List.last(String.split(Atom.to_string(left), "."))
|
||||
atom = String.to_atom(downcase(last))
|
||||
{atom, [], nil}
|
||||
end
|
||||
|
||||
@@ -544,8 +553,8 @@ defmodule Module do
|
||||
end
|
||||
|
||||
defp simplify_signature({var, _, atom}, _i) when is_atom(atom) do
|
||||
case atom_to_binary(var) do
|
||||
"_" <> rest -> {binary_to_atom(rest), [], Elixir}
|
||||
case Atom.to_string(var) do
|
||||
"_" <> rest -> {String.to_atom(rest), [], Elixir}
|
||||
_ -> {var, [], nil}
|
||||
end
|
||||
end
|
||||
@@ -804,7 +813,7 @@ defmodule Module do
|
||||
end
|
||||
|
||||
defp warn_info([entry|_]) do
|
||||
opts = elem(entry, size(entry) - 1)
|
||||
opts = elem(entry, tuple_size(entry) - 1)
|
||||
Exception.format_file_line(Keyword.get(opts, :file), Keyword.get(opts, :line)) <> " "
|
||||
end
|
||||
|
||||
@@ -838,12 +847,12 @@ defmodule Module do
|
||||
|
||||
When registering an attribute, two options can be given:
|
||||
|
||||
* `:accumulate` - Several calls to the same attribute will
|
||||
accumulate instead of override the previous one. New attributes
|
||||
are always added to the top of the accumulated list.
|
||||
* `:accumulate` - several calls to the same attribute will
|
||||
accumulate instead of override the previous one. New attributes
|
||||
are always added to the top of the accumulated list.
|
||||
|
||||
* `:persist` - The attribute will be persisted in the Erlang
|
||||
Abstract Format. Useful when interfacing with Erlang libraries.
|
||||
* `:persist` - the attribute will be persisted in the Erlang
|
||||
Abstract Format. Useful when interfacing with Erlang libraries.
|
||||
|
||||
By default, both options are `false`.
|
||||
|
||||
@@ -911,7 +920,7 @@ defmodule Module do
|
||||
@doc false
|
||||
# Used internally to compile types. This function
|
||||
# is private and must be used only internally.
|
||||
def compile_typespec(module, key, value) when is_atom(key) do
|
||||
def store_typespec(module, key, value) when is_atom(key) do
|
||||
assert_not_compiled!(:put_attribute, module)
|
||||
table = data_table_for(module)
|
||||
|
||||
@@ -945,7 +954,7 @@ defmodule Module do
|
||||
end
|
||||
|
||||
defp normalize_attribute(key, _value) when key in [:type, :typep, :export_type, :opaque, :callback] do
|
||||
raise ArgumentError, message: "attributes type, typep, export_type, opaque and callback " <>
|
||||
raise ArgumentError, "attributes type, typep, export_type, opaque and callback " <>
|
||||
"must be set via Kernel.Typespec"
|
||||
end
|
||||
|
||||
@@ -968,7 +977,7 @@ defmodule Module do
|
||||
defp assert_not_compiled!(fun, module) do
|
||||
open?(module) ||
|
||||
raise ArgumentError,
|
||||
message: "could not call #{fun} on module #{inspect module} because it was already compiled"
|
||||
"could not call #{fun} on module #{inspect module} because it was already compiled"
|
||||
end
|
||||
|
||||
defp loaded?(module), do: is_tuple :code.is_loaded(module)
|
||||
|
||||
+27
-2
@@ -10,6 +10,31 @@ defmodule Node do
|
||||
|
||||
@type t :: node
|
||||
|
||||
@doc """
|
||||
Turns a non-distributed node into a distributed node.
|
||||
|
||||
This functionality starts the `:net_kernel` and other
|
||||
related processes.
|
||||
"""
|
||||
@spec start(node, :longnames | :shortnames, non_neg_integer) ::
|
||||
{:ok, pid} | {:error, term}
|
||||
def start(name, type \\ :longnames, tick_time \\ 15000) do
|
||||
:net_kernel.start([name, type, tick_time])
|
||||
end
|
||||
|
||||
@doc """
|
||||
Turns a distributed node into a non-distributed node.
|
||||
|
||||
For other nodes in the network, this is the same as the node going down.
|
||||
Only possible when the node was started with `Node.start/3`, otherwise
|
||||
returns `{:error, :not_allowed}`. Returns `{:error, :not_found}` if the
|
||||
local node is not alive.
|
||||
"""
|
||||
@spec stop() :: :ok | {:error, term}
|
||||
def stop() do
|
||||
:net_kernel.stop()
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns the current node.
|
||||
|
||||
@@ -49,8 +74,8 @@ defmodule Node do
|
||||
|
||||
See http://www.erlang.org/doc/man/erlang.html#nodes-1 for more info.
|
||||
"""
|
||||
@typep list_arg :: :visible | :hidden | :connected | :this | :known
|
||||
@spec list(list_arg | [list_arg]) :: [t]
|
||||
@typep state :: :visible | :hidden | :connected | :this | :known
|
||||
@spec list(state | [state]) :: [t]
|
||||
def list(args) do
|
||||
:erlang.nodes(args)
|
||||
end
|
||||
|
||||
+262
-99
@@ -3,6 +3,11 @@ defmodule OptionParser do
|
||||
This module contains functions to parse command line arguments.
|
||||
"""
|
||||
|
||||
@type argv :: [String.t]
|
||||
@type parsed :: Keyword.t
|
||||
@type errors :: [{String.t, String.t | nil}]
|
||||
@type options :: [switches: Keyword.t, strict: Keyword.t, aliases: Keyword.t]
|
||||
|
||||
@doc """
|
||||
Parses `argv` into a keywords list.
|
||||
|
||||
@@ -27,41 +32,62 @@ defmodule OptionParser do
|
||||
|
||||
Note Elixir also converts the switches to underscore atoms, as
|
||||
`--source-path` becomes `:source_path`, to better suit Elixir
|
||||
conventions.
|
||||
conventions. This means that option names on the command line cannot contain
|
||||
underscores; such options will be reported as `:undefined` (in strict mode)
|
||||
or `:invalid` (in basic mode).
|
||||
|
||||
## Switches
|
||||
|
||||
Many times though, it is better to explicitly list the available
|
||||
switches and their formats. The following types are supported:
|
||||
switches and their formats. The switches can be specified via two
|
||||
different options:
|
||||
|
||||
* `:boolean` - Marks the given switch as a boolean. Boolean switches
|
||||
never consume the following value unless it is
|
||||
`true` or `false`;
|
||||
* `:integer` - Parses the switch as an integer;
|
||||
* `:float` - Parses the switch as a float;
|
||||
* `:string` - Returns the switch as a string;
|
||||
* `:strict` - the switches are strict. Any switch that does not
|
||||
exist in the switch list is treated as an error.
|
||||
|
||||
If a switch can't be parsed, the option is returned in the invalid
|
||||
options list (third element of the returned tuple).
|
||||
* `:switches` - configure some switches. Switches that does not
|
||||
exist in the switch list are still attempted to be parsed.
|
||||
|
||||
Note only `:strict` or `:switches` may be given at once.
|
||||
|
||||
For each switch, the following types are supported:
|
||||
|
||||
* `:boolean` - marks the given switch as a boolean. Boolean switches
|
||||
never consume the following value unless it is `true` or
|
||||
`false`.
|
||||
* `:integer` - parses the switch as an integer.
|
||||
* `:float` - parses the switch as a float.
|
||||
* `:string` - returns the switch as a string.
|
||||
|
||||
If a switch can't be parsed or is not specfied in the strict case,
|
||||
the option is returned in the invalid options list (third element
|
||||
of the returned tuple).
|
||||
|
||||
The following extra "types" are supported:
|
||||
|
||||
* `:keep` - Keeps duplicated items in the list instead of overriding;
|
||||
* `:keep` - keeps duplicated items in the list instead of overriding
|
||||
|
||||
Examples:
|
||||
|
||||
iex> OptionParser.parse(["--unlock", "path/to/file"], switches: [unlock: :boolean])
|
||||
iex> OptionParser.parse(["--unlock", "path/to/file"], strict: [unlock: :boolean])
|
||||
{[unlock: true], ["path/to/file"], []}
|
||||
|
||||
iex> OptionParser.parse(["--unlock", "--limit", "0", "path/to/file"],
|
||||
...> switches: [unlock: :boolean, limit: :integer])
|
||||
...> strict: [unlock: :boolean, limit: :integer])
|
||||
{[unlock: true, limit: 0], ["path/to/file"], []}
|
||||
|
||||
iex> OptionParser.parse(["--limit", "3"], switches: [limit: :integer])
|
||||
iex> OptionParser.parse(["--limit", "3"], strict: [limit: :integer])
|
||||
{[limit: 3], [], []}
|
||||
|
||||
iex> OptionParser.parse(["--limit", "yyz"], switches: [limit: :integer])
|
||||
{[], [], [limit: "yyz"]}
|
||||
iex> OptionParser.parse(["--limit", "xyz"], strict: [limit: :integer])
|
||||
{[], [], [{"--limit", "xyz"}]}
|
||||
|
||||
iex> OptionParser.parse(["--unknown", "xyz"], strict: [])
|
||||
{[], ["xyz"], [{"--unknown", nil}]}
|
||||
|
||||
iex> OptionParser.parse(["--limit", "3", "--unknown", "xyz"],
|
||||
...> switches: [limit: :integer])
|
||||
{[limit: 3, unknown: "xyz"], [], []}
|
||||
|
||||
## Negation switches
|
||||
|
||||
@@ -85,8 +111,10 @@ defmodule OptionParser do
|
||||
{[debug: true], [], []}
|
||||
|
||||
"""
|
||||
@spec parse(argv, options) :: {parsed, argv, errors}
|
||||
def parse(argv, opts \\ []) when is_list(argv) and is_list(opts) do
|
||||
parse(argv, opts, true)
|
||||
config = compile_config(opts, true)
|
||||
do_parse(argv, config, [], [], [])
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -104,71 +132,158 @@ defmodule OptionParser do
|
||||
{[verbose: true, source: "lib"], ["test/enum_test.exs", "--unlock"], []}
|
||||
|
||||
"""
|
||||
@spec parse_head(argv, options) :: {parsed, argv, errors}
|
||||
def parse_head(argv, opts \\ []) when is_list(argv) and is_list(opts) do
|
||||
parse(argv, opts, false)
|
||||
config = compile_config(opts, false)
|
||||
do_parse(argv, config, [], [], [])
|
||||
end
|
||||
|
||||
defp do_parse([], _config, opts, args, invalid) do
|
||||
{Enum.reverse(opts), Enum.reverse(args), Enum.reverse(invalid)}
|
||||
end
|
||||
|
||||
defp do_parse(argv, {aliases, switches, strict, all}=config, opts, args, invalid) do
|
||||
case next(argv, aliases, switches, strict) do
|
||||
{:ok, option, value, rest} ->
|
||||
# the option exist and it was successfully parsed
|
||||
kinds = List.wrap Keyword.get(switches, option)
|
||||
new_opts = do_store_option(opts, option, value, kinds)
|
||||
do_parse(rest, config, new_opts, args, invalid)
|
||||
|
||||
{:invalid, option, value, rest} ->
|
||||
# the option exist but it has wrong value
|
||||
do_parse(rest, config, opts, args, [{option, value}|invalid])
|
||||
|
||||
{:undefined, option, _value, rest} ->
|
||||
# the option does not exist (for strict cases)
|
||||
do_parse(rest, config, opts, args, [{option, nil}|invalid])
|
||||
|
||||
{:error, ["--"|rest]} ->
|
||||
{Enum.reverse(opts), Enum.reverse(args, rest), Enum.reverse(invalid)}
|
||||
|
||||
{:error, [arg|rest]=remaining_args} ->
|
||||
# there is no option
|
||||
if all do
|
||||
do_parse(rest, config, opts, [arg|args], invalid)
|
||||
else
|
||||
{Enum.reverse(opts), Enum.reverse(args, remaining_args), Enum.reverse(invalid)}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Low-level function that parses one option.
|
||||
|
||||
It accepts the same options as `parse/2` and `parse_head/2`
|
||||
as both functions are built on top of next. This function
|
||||
may return:
|
||||
|
||||
* `{:ok, key, value, rest}` - the option `key` with `value` was
|
||||
successfully parsed
|
||||
|
||||
* `{:invalid, key, value, rest}` - the option `key` is invalid with `value`
|
||||
(returned when the switch type does not match the one given via the
|
||||
command line)
|
||||
|
||||
* `{:undefined, key, value, rest}` - the option `key` is undefined
|
||||
(returned on strict cases and the switch is unknown)
|
||||
|
||||
* `{:error, rest}` - there are no switches at the top of the given argv
|
||||
"""
|
||||
|
||||
@spec next(argv, options) ::
|
||||
{:ok, key :: atom, value :: term, argv} |
|
||||
{:invalid, key :: atom, value :: term, argv} |
|
||||
{:undefined, key :: atom, value :: term, argv} |
|
||||
{:error, argv}
|
||||
|
||||
def next(argv, opts \\ []) when is_list(argv) and is_list(opts) do
|
||||
{aliases, switches, strict, _} = compile_config(opts, true)
|
||||
next(argv, aliases, switches, strict)
|
||||
end
|
||||
|
||||
defp next([], _aliases, _switches, _strict) do
|
||||
{:error, []}
|
||||
end
|
||||
|
||||
defp next(["--"|_]=argv, _aliases, _switches, _strict) do
|
||||
{:error, argv}
|
||||
end
|
||||
|
||||
defp next(["-"|_]=argv, _aliases, _switches, _strict) do
|
||||
{:error, argv}
|
||||
end
|
||||
|
||||
defp next(["- " <> _|_]=argv, _aliases, _switches, _strict) do
|
||||
{:error, argv}
|
||||
end
|
||||
|
||||
defp next(["-" <> option|rest], aliases, switches, strict) do
|
||||
{option, value} = split_option(option)
|
||||
opt_name_bin = "-" <> option
|
||||
tagged = tag_option(option, value, switches, aliases)
|
||||
|
||||
if strict and not option_defined?(tagged, switches) do
|
||||
{:undefined, opt_name_bin, value, rest}
|
||||
else
|
||||
{opt_name, kinds, value} = normalize_option(tagged, value, switches)
|
||||
{value, kinds, rest} = normalize_value(value, kinds, rest, strict)
|
||||
case validate_option(value, kinds) do
|
||||
{:ok, new_value} -> {:ok, opt_name, new_value, rest}
|
||||
:invalid -> {:invalid, opt_name_bin, value, rest}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defp next(argv, _aliases, _switches, _strict) do
|
||||
{:error, argv}
|
||||
end
|
||||
|
||||
## Helpers
|
||||
|
||||
defp parse(argv, opts, bool) do
|
||||
aliases = opts[:aliases] || []
|
||||
switches = opts[:switches] || []
|
||||
parse(argv, aliases, switches, bool)
|
||||
defp compile_config(opts, all) do
|
||||
aliases = opts[:aliases] || []
|
||||
|
||||
{switches, strict} = cond do
|
||||
s = opts[:switches] ->
|
||||
{s, false}
|
||||
s = opts[:strict] ->
|
||||
{s, true}
|
||||
true ->
|
||||
{[], false}
|
||||
end
|
||||
|
||||
{aliases, switches, strict, all}
|
||||
end
|
||||
|
||||
defp parse(argv, aliases, switches, all) do
|
||||
parse(argv, aliases, switches, [], [], [], all)
|
||||
end
|
||||
defp validate_option(value, kinds) do
|
||||
{is_invalid, value} = cond do
|
||||
:invalid in kinds ->
|
||||
{true, value}
|
||||
:boolean in kinds ->
|
||||
case value do
|
||||
t when t in [true, "true"] -> {nil, true}
|
||||
f when f in [false, "false"] -> {nil, false}
|
||||
_ -> {true, value}
|
||||
end
|
||||
:integer in kinds ->
|
||||
case Integer.parse(value) do
|
||||
{value, ""} -> {nil, value}
|
||||
_ -> {true, value}
|
||||
end
|
||||
:float in kinds ->
|
||||
case Float.parse(value) do
|
||||
{value, ""} -> {nil, value}
|
||||
_ -> {true, value}
|
||||
end
|
||||
true ->
|
||||
{nil, value}
|
||||
end
|
||||
|
||||
defp parse(["--"|_] = value, _aliases, _switches, dict, args, invalid, _all) do
|
||||
{Enum.reverse(dict), Enum.reverse(args, value), Enum.reverse(invalid)}
|
||||
end
|
||||
|
||||
defp parse(["-" <> option|t], aliases, switches, dict, args, invalid, all) do
|
||||
{option, value} = split_option(option)
|
||||
{option, kinds, value} = normalize_option(option, value, switches, aliases)
|
||||
{value, kinds, t} = normalize_value(value, kinds, t)
|
||||
{dict, invalid} = store_option(dict, invalid, option, value, kinds)
|
||||
parse(t, aliases, switches, dict, args, invalid, all)
|
||||
end
|
||||
|
||||
defp parse([h|t], aliases, switches, dict, args, invalid, true) do
|
||||
parse(t, aliases, switches, dict, [h|args], invalid, true)
|
||||
end
|
||||
|
||||
defp parse([], _, _switches, dict, args, invalid, true) do
|
||||
{Enum.reverse(dict), Enum.reverse(args), Enum.reverse(invalid)}
|
||||
end
|
||||
|
||||
defp parse(value, _, _switches, dict, _args, invalid, false) do
|
||||
{Enum.reverse(dict), value, Enum.reverse(invalid)}
|
||||
end
|
||||
|
||||
defp store_option(dict, invalid, option, value, kinds) do
|
||||
{invalid_option, value} =
|
||||
cond do
|
||||
:invalid in kinds ->
|
||||
{option, value}
|
||||
:boolean in kinds ->
|
||||
{nil, value in [true, "true"]}
|
||||
:integer in kinds ->
|
||||
case Integer.parse(value) do
|
||||
{value, ""} -> {nil, value}
|
||||
_ -> {option, value}
|
||||
end
|
||||
:float in kinds ->
|
||||
case Float.parse(value) do
|
||||
{value, ""} -> {nil, value}
|
||||
_ -> {option, value}
|
||||
end
|
||||
true ->
|
||||
{nil, value}
|
||||
end
|
||||
|
||||
if invalid_option do
|
||||
{dict, [{option, value}|invalid]}
|
||||
if is_invalid do
|
||||
:invalid
|
||||
else
|
||||
{do_store_option(dict, option, value, kinds), invalid}
|
||||
{:ok, value}
|
||||
end
|
||||
end
|
||||
|
||||
@@ -181,20 +296,36 @@ defmodule OptionParser do
|
||||
end
|
||||
end
|
||||
|
||||
defp normalize_option(<<?-, option :: binary>>, value, switches, _aliases) do
|
||||
normalize_option(get_negated(option, switches), value, switches)
|
||||
defp tag_option(<<?-, option :: binary>>, value, switches, _aliases) do
|
||||
get_negated(option, value, switches)
|
||||
end
|
||||
|
||||
defp normalize_option(option, value, switches, aliases) do
|
||||
option = get_option(option)
|
||||
if alias = aliases[option] do
|
||||
normalize_option({:default, alias}, value, switches)
|
||||
defp tag_option(option, _value, _switches, aliases) when is_binary(option) do
|
||||
opt = get_option(option)
|
||||
if alias = aliases[opt] do
|
||||
{:default, alias}
|
||||
else
|
||||
{option, [:invalid], value}
|
||||
:unknown
|
||||
end
|
||||
end
|
||||
|
||||
defp normalize_option({:negated, option}, _value, switches) do
|
||||
defp option_defined?(:unknown, _switches) do
|
||||
false
|
||||
end
|
||||
|
||||
defp option_defined?({:negated, option}, switches) do
|
||||
Keyword.has_key?(switches, option)
|
||||
end
|
||||
|
||||
defp option_defined?({:default, option}, switches) do
|
||||
Keyword.has_key?(switches, option)
|
||||
end
|
||||
|
||||
defp normalize_option(:unknown, value, _switches) do
|
||||
{nil, [:invalid], value}
|
||||
end
|
||||
|
||||
defp normalize_option({:negated, option}, nil, switches) do
|
||||
kinds = List.wrap(switches[option])
|
||||
|
||||
cond do
|
||||
@@ -203,15 +334,20 @@ defmodule OptionParser do
|
||||
kinds == [] ->
|
||||
{option, kinds, true}
|
||||
true ->
|
||||
{option, [:invalid], false}
|
||||
{reverse_negated(option), [:invalid], nil}
|
||||
end
|
||||
end
|
||||
|
||||
defp normalize_option({:negated, option}, value, _switches) do
|
||||
{option, [:invalid], value}
|
||||
end
|
||||
|
||||
defp normalize_option({:default, option}, value, switches) do
|
||||
{option, List.wrap(switches[option]), value}
|
||||
end
|
||||
|
||||
defp normalize_value(nil, kinds, t) do
|
||||
defp normalize_value(nil, kinds, t, strict) do
|
||||
nil_or_true = if strict, do: nil, else: true
|
||||
cond do
|
||||
:boolean in kinds ->
|
||||
{true, kinds, t}
|
||||
@@ -219,19 +355,21 @@ defmodule OptionParser do
|
||||
[h|t] = t
|
||||
{h, kinds, t}
|
||||
kinds == [] ->
|
||||
{true, kinds, t}
|
||||
{nil_or_true, kinds, t}
|
||||
true ->
|
||||
{true, [:invalid], t}
|
||||
{nil, [:invalid], t}
|
||||
end
|
||||
end
|
||||
|
||||
defp normalize_value(value, kinds, t) do
|
||||
defp normalize_value(value, kinds, t, _) do
|
||||
{value, kinds, t}
|
||||
end
|
||||
|
||||
defp value_in_tail?(["-" <> _|_]), do: false
|
||||
defp value_in_tail?([]), do: false
|
||||
defp value_in_tail?(_), do: true
|
||||
defp value_in_tail?(["-"|_]), do: true
|
||||
defp value_in_tail?(["- " <> _|_]), do: true
|
||||
defp value_in_tail?(["-" <> _|_]), do: false
|
||||
defp value_in_tail?([]), do: false
|
||||
defp value_in_tail?(_), do: true
|
||||
|
||||
defp split_option(option) do
|
||||
case :binary.split(option, "=") do
|
||||
@@ -240,21 +378,46 @@ defmodule OptionParser do
|
||||
end
|
||||
end
|
||||
|
||||
defp to_underscore(option) do
|
||||
for <<c <- option>>, into: "", do: << if(c == ?-, do: ?_, else: c) >>
|
||||
end
|
||||
defp to_underscore(option), do: to_underscore(option, <<>>)
|
||||
|
||||
defp to_underscore("_" <> _rest, _acc), do: nil
|
||||
|
||||
defp to_underscore("-" <> rest, acc),
|
||||
do: to_underscore(rest, acc <> "_")
|
||||
|
||||
defp to_underscore(<<c>> <> rest, acc),
|
||||
do: to_underscore(rest, <<acc::binary, c>>)
|
||||
|
||||
defp to_underscore(<<>>, acc), do: acc
|
||||
|
||||
defp get_option(option) do
|
||||
option |> to_underscore |> binary_to_atom
|
||||
if str = to_underscore(option) do
|
||||
String.to_atom(str)
|
||||
end
|
||||
end
|
||||
|
||||
defp get_negated("no-" <> rest = option, switches) do
|
||||
negated = get_option(rest)
|
||||
option = if Keyword.has_key?(switches, negated), do: negated, else: get_option(option)
|
||||
{:negated, option}
|
||||
defp reverse_negated(negated) do
|
||||
String.to_atom("no_" <> Atom.to_string(negated))
|
||||
end
|
||||
|
||||
defp get_negated(rest, _switches) do
|
||||
{:default, get_option(rest)}
|
||||
defp get_negated("no-" <> rest = option, value, switches) do
|
||||
if negated = get_option(rest) do
|
||||
option = if Keyword.has_key?(switches, negated) and value == nil do
|
||||
negated
|
||||
else
|
||||
get_option(option)
|
||||
end
|
||||
{:negated, option}
|
||||
else
|
||||
:unknown
|
||||
end
|
||||
end
|
||||
|
||||
defp get_negated(rest, _value, _switches) do
|
||||
if option = get_option(rest) do
|
||||
{:default, option}
|
||||
else
|
||||
:unknown
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
+93
-34
@@ -13,7 +13,7 @@ defmodule Path do
|
||||
"""
|
||||
|
||||
alias :filename, as: FN
|
||||
@type t :: char_data
|
||||
@type t :: :unicode.chardata()
|
||||
|
||||
@doc """
|
||||
Converts the given path to an absolute one. Unlike
|
||||
@@ -58,17 +58,20 @@ defmodule Path do
|
||||
"""
|
||||
@spec absname(t, t) :: binary
|
||||
def absname(path, relative_to) do
|
||||
path = String.from_char_data!(path)
|
||||
path = IO.chardata_to_string(path)
|
||||
case type(path) do
|
||||
:relative -> join(relative_to, path)
|
||||
:absolute ->
|
||||
if :binary.last(path) == ?/ do
|
||||
binary_part(path, 0, byte_size(path) - 1)
|
||||
else
|
||||
path
|
||||
cond do
|
||||
path == "/" ->
|
||||
path
|
||||
:binary.last(path) == ?/ ->
|
||||
binary_part(path, 0, byte_size(path) - 1)
|
||||
true ->
|
||||
path
|
||||
end
|
||||
:volumerelative ->
|
||||
relative_to = String.from_char_data!(relative_to)
|
||||
relative_to = IO.chardata_to_string(relative_to)
|
||||
absname_vr(split(path), split(relative_to), relative_to)
|
||||
end
|
||||
end
|
||||
@@ -85,7 +88,7 @@ defmodule Path do
|
||||
defp absname_vr([<<x, ?:>>|name], _, _relative) do
|
||||
cwd =
|
||||
case :file.get_cwd([x, ?:]) do
|
||||
{:ok, dir} -> String.from_char_data!(dir)
|
||||
{:ok, dir} -> IO.chardata_to_string(dir)
|
||||
{:error, _} -> <<x, ?:, ?/>>
|
||||
end
|
||||
absname(join(name), cwd)
|
||||
@@ -138,6 +141,7 @@ defmodule Path do
|
||||
|
||||
## Unix examples
|
||||
|
||||
Path.type("/") #=> :absolute
|
||||
Path.type("/usr/local/bin") #=> :absolute
|
||||
Path.type("usr/local/bin") #=> :relative
|
||||
Path.type("../usr/local/bin") #=> :relative
|
||||
@@ -181,7 +185,7 @@ defmodule Path do
|
||||
case :os.type() do
|
||||
{:win32, _} -> win32_pathtype(name)
|
||||
_ -> unix_pathtype(name)
|
||||
end |> elem(1) |> String.from_char_data!
|
||||
end |> elem(1) |> IO.chardata_to_string
|
||||
end
|
||||
|
||||
defp unix_pathtype(<<?/, relative :: binary>>), do:
|
||||
@@ -245,7 +249,7 @@ defmodule Path do
|
||||
"""
|
||||
@spec relative_to(t, t) :: binary
|
||||
def relative_to(path, from) do
|
||||
path = String.from_char_data!(path)
|
||||
path = IO.chardata_to_string(path)
|
||||
relative_to(split(path), split(from), path)
|
||||
end
|
||||
|
||||
@@ -269,7 +273,7 @@ defmodule Path do
|
||||
@spec relative_to_cwd(t) :: binary
|
||||
def relative_to_cwd(path) do
|
||||
case :file.get_cwd do
|
||||
{:ok, base} -> relative_to(path, String.from_char_data!(base))
|
||||
{:ok, base} -> relative_to(path, IO.chardata_to_string(base))
|
||||
_ -> path
|
||||
end
|
||||
end
|
||||
@@ -292,7 +296,7 @@ defmodule Path do
|
||||
"""
|
||||
@spec basename(t) :: binary
|
||||
def basename(path) do
|
||||
FN.basename(String.from_char_data!(path))
|
||||
FN.basename(IO.chardata_to_string(path))
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -314,7 +318,7 @@ defmodule Path do
|
||||
"""
|
||||
@spec basename(t, t) :: binary
|
||||
def basename(path, extension) do
|
||||
FN.basename(String.from_char_data!(path), String.from_char_data!(extension))
|
||||
FN.basename(IO.chardata_to_string(path), IO.chardata_to_string(extension))
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -330,7 +334,7 @@ defmodule Path do
|
||||
"""
|
||||
@spec dirname(t) :: binary
|
||||
def dirname(path) do
|
||||
FN.dirname(String.from_char_data!(path))
|
||||
FN.dirname(IO.chardata_to_string(path))
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -347,7 +351,7 @@ defmodule Path do
|
||||
"""
|
||||
@spec extname(t) :: binary
|
||||
def extname(path) do
|
||||
FN.extension(String.from_char_data!(path))
|
||||
FN.extension(IO.chardata_to_string(path))
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -364,7 +368,7 @@ defmodule Path do
|
||||
"""
|
||||
@spec rootname(t) :: binary
|
||||
def rootname(path) do
|
||||
FN.rootname(String.from_char_data!(path))
|
||||
FN.rootname(IO.chardata_to_string(path))
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -382,7 +386,7 @@ defmodule Path do
|
||||
"""
|
||||
@spec rootname(t, t) :: binary
|
||||
def rootname(path, extension) do
|
||||
FN.rootname(String.from_char_data!(path), String.from_char_data!(extension))
|
||||
FN.rootname(IO.chardata_to_string(path), IO.chardata_to_string(extension))
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -407,7 +411,7 @@ defmodule Path do
|
||||
def join([name1, name2|rest]), do:
|
||||
join([join(name1, name2)|rest])
|
||||
def join([name]), do:
|
||||
do_join(String.from_char_data!(name), <<>>, [], major_os_type())
|
||||
do_join(IO.chardata_to_string(name), <<>>, [], major_os_type())
|
||||
|
||||
@doc """
|
||||
Joins two paths.
|
||||
@@ -420,7 +424,7 @@ defmodule Path do
|
||||
"""
|
||||
@spec join(t, t) :: binary
|
||||
def join(left, right),
|
||||
do: do_join(String.from_char_data!(left), relative(right), [], major_os_type())
|
||||
do: do_join(IO.chardata_to_string(left), relative(right), [], major_os_type())
|
||||
|
||||
defp major_os_type do
|
||||
:os.type |> elem(0)
|
||||
@@ -435,7 +439,7 @@ defmodule Path 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:
|
||||
iodata_to_binary(maybe_remove_dirsep(result, os_type))
|
||||
IO.iodata_to_binary(maybe_remove_dirsep(result, os_type))
|
||||
defp do_join(<<>>, relativename, [?:|rest], :win32), do:
|
||||
do_join(relativename, <<>>, [?:|rest], :win32)
|
||||
defp do_join(<<>>, relativename, [?/|result], os_type), do:
|
||||
@@ -476,7 +480,33 @@ defmodule Path do
|
||||
def split(""), do: []
|
||||
|
||||
def split(path) do
|
||||
FN.split(String.from_char_data!(path))
|
||||
FN.split(IO.chardata_to_string(path))
|
||||
end
|
||||
|
||||
defmodule Wildcard do
|
||||
@moduledoc false
|
||||
|
||||
def read_file_info(file) do
|
||||
call({:read_link_info, file})
|
||||
end
|
||||
|
||||
def list_dir(dir) do
|
||||
case call({:list_dir, dir}) do
|
||||
{:ok, files} ->
|
||||
{:ok, for(file <- files, hd(file) != ?., do: file)}
|
||||
other ->
|
||||
other
|
||||
end
|
||||
end
|
||||
|
||||
@compile {:inline, call: 1}
|
||||
|
||||
defp call(tuple) do
|
||||
x = :erlang.dt_spread_tag(true)
|
||||
y = :gen_server.call(:file_server_2, tuple)
|
||||
:erlang.dt_restore_tag(x)
|
||||
y
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -486,19 +516,26 @@ defmodule Path do
|
||||
"wildcard characters" are interpreted in a special way. The
|
||||
following characters are special:
|
||||
|
||||
* `?` - Matches one character.
|
||||
* `*` - Matches any number of characters up to the end of
|
||||
the filename, the next dot, or the next slash.
|
||||
* `**` - Two adjacent <c>*</c>'s used as a single pattern will
|
||||
match all files and zero or more directories and subdirectories.
|
||||
* `[char1,char2,...]` - Matches any of the characters listed. Two characters
|
||||
separated by a hyphen will match a range of characters.
|
||||
* `{item1,item2,...}` - Matches one of the alternatives.
|
||||
* `?` - matches one character
|
||||
|
||||
* `*` - matches any number of characters up to the end of the filename, the
|
||||
next dot, or the next slash
|
||||
|
||||
* `**` - two adjacent `*`'s used as a single pattern will match all
|
||||
files and zero or more directories and subdirectories
|
||||
|
||||
* `[char1,char2,...]` - matches any of the characters listed; two
|
||||
characters separated by a hyphen will match a range of characters
|
||||
|
||||
* `{item1,item2,...}` - matches one of the alternatives
|
||||
|
||||
Other characters represent themselves. Only paths that have
|
||||
exactly the same character in the same position will match. Note
|
||||
that matching is case-sensitive; i.e. "a" will not match "A".
|
||||
|
||||
By default, the patterns `*` and `?` do not match files starting
|
||||
with a dot `.` unless `match_dot: true` is given.
|
||||
|
||||
## Examples
|
||||
|
||||
Imagine you have a directory called `projects` with three Elixir projects
|
||||
@@ -513,17 +550,31 @@ defmodule Path do
|
||||
|
||||
"""
|
||||
@spec wildcard(t) :: [binary]
|
||||
def wildcard(glob) do
|
||||
def wildcard(glob, opts \\ []) do
|
||||
mod = if Keyword.get(opts, :match_dot), do: :file, else: Path.Wildcard
|
||||
glob
|
||||
|> List.from_char_data!
|
||||
|> :filelib.wildcard
|
||||
|> Enum.map(&String.from_char_data!/1)
|
||||
|> chardata_to_list()
|
||||
|> :filelib.wildcard(mod)
|
||||
|> Enum.map(&IO.chardata_to_string/1)
|
||||
end
|
||||
|
||||
# Normalize the given path by expanding "..", "." and "~".
|
||||
|
||||
defp chardata_to_list(chardata) do
|
||||
case :unicode.characters_to_list(chardata) do
|
||||
result when is_list(result) ->
|
||||
result
|
||||
|
||||
{:error, encoded, rest} ->
|
||||
raise UnicodeConversionError, encoded: encoded, rest: rest, kind: :invalid
|
||||
|
||||
{:incomplete, encoded, rest} ->
|
||||
raise UnicodeConversionError, encoded: encoded, rest: rest, kind: :incomplete
|
||||
end
|
||||
end
|
||||
|
||||
defp expand_home(type) do
|
||||
case String.from_char_data!(type) do
|
||||
case IO.chardata_to_string(type) do
|
||||
"~" <> rest -> System.user_home! <> rest
|
||||
rest -> rest
|
||||
end
|
||||
@@ -531,6 +582,14 @@ defmodule Path do
|
||||
|
||||
defp normalize(path), do: normalize(split(path), [])
|
||||
|
||||
defp normalize([".."|t], ["/"|_] = acc) do
|
||||
normalize t, acc
|
||||
end
|
||||
|
||||
defp normalize([".."|t], [<<letter, ?:, ?/>>|_] = acc) when letter in ?a..?z do
|
||||
normalize t, acc
|
||||
end
|
||||
|
||||
defp normalize([".."|t], [_|acc]) do
|
||||
normalize t, acc
|
||||
end
|
||||
|
||||
+32
-73
@@ -2,10 +2,16 @@ defmodule Process do
|
||||
@moduledoc """
|
||||
Conveniences for working with processes and the process dictionary.
|
||||
|
||||
Some of the functions in this module are inlined by the compiler,
|
||||
similar to functions in the `Kernel` module and they are explicitly
|
||||
marked in their docs as "inlined by the compiler". For more information
|
||||
about inlined functions, check out the `Kernel` module.
|
||||
Besides the functions available in this module, the `Kernel` module
|
||||
exposes and auto-imports some basic functionality related to processes
|
||||
available through the functions:
|
||||
|
||||
* `Kernel.spawn/1` and `Kernel.spawn/3`
|
||||
* `Kernel.spawn_link/1` and `Kernel.spawn_link/3`
|
||||
* `Kernel.spawn_monitor/1` and `Kernel.spawn_monitor/3`
|
||||
* `Kernel.self/0`
|
||||
* `Kernel.send/2`
|
||||
|
||||
"""
|
||||
|
||||
@doc """
|
||||
@@ -57,15 +63,6 @@ defmodule Process do
|
||||
nillify :erlang.put(key, value)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Deletes all items in the dictionary.
|
||||
"""
|
||||
@spec delete :: [{term, term}]
|
||||
def delete() do
|
||||
IO.write :stderr, "Process.delete/0 is deprecated\n#{Exception.format_stacktrace}"
|
||||
:erlang.erase()
|
||||
end
|
||||
|
||||
@doc """
|
||||
Deletes the given key from the dictionary.
|
||||
"""
|
||||
@@ -79,18 +76,18 @@ defmodule Process do
|
||||
|
||||
The following behaviour applies if reason is any term except `:normal` or `:kill`:
|
||||
|
||||
1) If pid is not trapping exits, pid will exit with the given reason;
|
||||
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;
|
||||
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.
|
||||
|
||||
3) If reason is the atom `:normal`, pid will not exit. If it is trapping exits,
|
||||
the exit signal is transformed into a message {:EXIT, from, :normal} and
|
||||
delivered to its message queue;
|
||||
3. If reason is the atom `:normal`, pid will not exit. If it is trapping
|
||||
exits, the exit signal is transformed into a message {:EXIT, from,
|
||||
:normal} and delivered to its message queue.
|
||||
|
||||
4) If reason is the atom `:kill`, that is if `exit(pid, :kill)` is called, an
|
||||
untrappable exit signal is sent to pid which will unconditionally exit with
|
||||
exit reason `:killed`.
|
||||
4. If reason is the atom `:kill`, that is if `exit(pid, :kill)` is called,
|
||||
an untrappable exit signal is sent to pid which will unconditionally
|
||||
exit with exit reason `:killed`.
|
||||
|
||||
Inlined by the compiler.
|
||||
|
||||
@@ -197,54 +194,6 @@ defmodule Process do
|
||||
:erlang.spawn_opt(mod, fun, args, opts)
|
||||
end
|
||||
|
||||
@doc false
|
||||
def spawn(fun) do
|
||||
# IO.write :stderr, "Process.spawn/1 is deprecated, use the one in Kernel instead\n#{Exception.format_stacktrace}"
|
||||
:erlang.spawn(fun)
|
||||
end
|
||||
|
||||
@doc false
|
||||
def spawn(mod, fun, args) do
|
||||
# IO.write :stderr, "Process.spawn/3 is deprecated, use the one in Kernel instead\n#{Exception.format_stacktrace}"
|
||||
:erlang.spawn(mod, fun, args)
|
||||
end
|
||||
|
||||
@doc false
|
||||
def spawn_link(fun) do
|
||||
# IO.write :stderr, "Process.spawn_link/1 is deprecated, use the one in Kernel instead\n#{Exception.format_stacktrace}"
|
||||
:erlang.spawn_link(fun)
|
||||
end
|
||||
|
||||
@doc false
|
||||
def spawn_link(mod, fun, args) do
|
||||
# IO.write :stderr, "Process.spawn_link/3 is deprecated, use the one in Kernel instead\n#{Exception.format_stacktrace}"
|
||||
:erlang.spawn_link(mod, fun, args)
|
||||
end
|
||||
|
||||
@doc false
|
||||
def spawn_monitor(fun) do
|
||||
# IO.write :stderr, "Process.spawn_monitor/1 is deprecated, use the one in Kernel instead\n#{Exception.format_stacktrace}"
|
||||
:erlang.spawn_monitor(fun)
|
||||
end
|
||||
|
||||
@doc false
|
||||
def spawn_monitor(mod, fun, args) do
|
||||
# IO.write :stderr, "Process.spawn_monitor/3 is deprecated, use the one in Kernel instead\n#{Exception.format_stacktrace}"
|
||||
:erlang.spawn_monitor(mod, fun, args)
|
||||
end
|
||||
|
||||
@doc false
|
||||
def self() do
|
||||
# IO.write :stderr, "Process.self/0 is deprecated, use the one in Kernel instead\n#{Exception.format_stacktrace}"
|
||||
:erlang.self()
|
||||
end
|
||||
|
||||
@doc false
|
||||
def send(dest, msg) do
|
||||
# IO.write :stderr, "Process.send/2 is deprecated, use the one in Kernel instead\n#{Exception.format_stacktrace}"
|
||||
:erlang.send(dest, msg)
|
||||
end
|
||||
|
||||
@doc """
|
||||
The calling process starts monitoring the item given.
|
||||
It returns the monitor reference.
|
||||
@@ -325,7 +274,7 @@ defmodule Process do
|
||||
already registered (check with `registered?/1`).
|
||||
"""
|
||||
@spec register(pid | port, atom) :: true
|
||||
def register(pid, name) do
|
||||
def register(pid, name) when not name in [nil, false, true] do
|
||||
:erlang.register(name, pid)
|
||||
end
|
||||
|
||||
@@ -419,8 +368,18 @@ defmodule Process do
|
||||
|
||||
See http://www.erlang.org/doc/man/erlang.html#process_info-2 for more info.
|
||||
"""
|
||||
@spec info(pid, atom) :: {atom, term}
|
||||
def info(pid, spec) do
|
||||
@spec info(pid, atom) :: {atom, term} | nil
|
||||
def info(pid, spec)
|
||||
|
||||
def info(pid, :registered_name) do
|
||||
case :erlang.process_info(pid, :registered_name) do
|
||||
:undefined -> nil
|
||||
[] -> {:registered_name, []}
|
||||
other -> other
|
||||
end
|
||||
end
|
||||
|
||||
def info(pid, spec) when is_atom(spec) do
|
||||
nillify :erlang.process_info(pid, spec)
|
||||
end
|
||||
|
||||
|
||||
+570
-231
@@ -1,226 +1,19 @@
|
||||
defmodule Protocol do
|
||||
@moduledoc false
|
||||
@moduledoc """
|
||||
Functions for working with protocols.
|
||||
"""
|
||||
|
||||
# Callback for defprotocol.
|
||||
@doc false
|
||||
def defprotocol(name, [do: block]) do
|
||||
quote do
|
||||
defmodule unquote(name) do
|
||||
# We don't allow function definition inside protocols
|
||||
import Kernel, except: [
|
||||
defmacrop: 1, defmacrop: 2, defmacro: 1, defmacro: 2,
|
||||
defp: 1, defp: 2, def: 1, def: 2
|
||||
]
|
||||
@doc """
|
||||
Defines a new protocol function.
|
||||
|
||||
# Import the new dsl that holds the new def
|
||||
import Protocol.DSL, only: :macros
|
||||
Protocols do not allow functions to be defined directly, instead, the
|
||||
regular `Kernel.def/*` macros are replaced by this macro which
|
||||
defines the protocol functions with the appropriate callbacks.
|
||||
"""
|
||||
defmacro def(signature)
|
||||
|
||||
# Compile with debug info for consolidation
|
||||
@compile :debug_info
|
||||
|
||||
# Set up a clear slate to store defined functions
|
||||
@functions []
|
||||
@fallback_to_any false
|
||||
|
||||
# Invoke the user given block
|
||||
unquote(block)
|
||||
|
||||
# Finalize expansion
|
||||
unquote(after_defprotocol)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defp after_defprotocol do
|
||||
quote unquote: false do
|
||||
# == Deprecated records handling ==
|
||||
{arg, impl} = Protocol.rec_impl_for(__MODULE__)
|
||||
Kernel.def impl_for(unquote(arg)) when Kernel.is_record(unquote(arg)), do: unquote(impl)
|
||||
# == Deprecated records handling ==
|
||||
|
||||
@spec impl_for(term) :: module | nil
|
||||
Kernel.def impl_for(data)
|
||||
|
||||
# Define the implementation for structs.
|
||||
#
|
||||
# It simply delegates to struct_impl_for which is then
|
||||
# optimized during protocol consolidation.
|
||||
Kernel.def impl_for(%{__struct__: struct}) when :erlang.is_atom(struct) do
|
||||
struct_impl_for(struct)
|
||||
end
|
||||
|
||||
# Define the implementation for builtins.
|
||||
for {guard, mod} <- Protocol.builtin do
|
||||
target = Module.concat(__MODULE__, mod)
|
||||
|
||||
Kernel.def impl_for(data) when :erlang.unquote(guard)(data) do
|
||||
case impl_for?(unquote(target)) do
|
||||
true -> unquote(target).__impl__(:name)
|
||||
false -> any_impl_for
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@spec impl_for!(term) :: module | no_return
|
||||
Kernel.def impl_for!(data) do
|
||||
impl_for(data) || raise(Protocol.UndefinedError, protocol: __MODULE__, value: data)
|
||||
end
|
||||
|
||||
# Internal handler for Any
|
||||
if @fallback_to_any do
|
||||
Kernel.defp any_impl_for do
|
||||
case impl_for?(__MODULE__.Any) do
|
||||
true -> __MODULE__.Any.__impl__(:name)
|
||||
false -> nil
|
||||
end
|
||||
end
|
||||
else
|
||||
Kernel.defp any_impl_for, do: nil
|
||||
end
|
||||
|
||||
# Internal handler for Structs
|
||||
Kernel.defp struct_impl_for(struct) do
|
||||
target = Module.concat(__MODULE__, struct)
|
||||
case impl_for?(target) do
|
||||
true -> target.__impl__(:name)
|
||||
false -> any_impl_for
|
||||
end
|
||||
end
|
||||
|
||||
# Check if compilation is available internally
|
||||
Kernel.defp impl_for?(target) do
|
||||
Code.ensure_compiled?(target) and
|
||||
function_exported?(target, :__impl__, 1)
|
||||
end
|
||||
|
||||
# Inline any and struct implementations
|
||||
@compile {:inline, any_impl_for: 0, struct_impl_for: 1, impl_for?: 1}
|
||||
|
||||
if :code.ensure_loaded(Kernel.Typespec) == {:module, Kernel.Typespec} and
|
||||
not Kernel.Typespec.defines_type?(__MODULE__, :t, 0) do
|
||||
@type t :: term
|
||||
end
|
||||
|
||||
# Store information as an attribute so it
|
||||
# can be read without loading the module.
|
||||
Module.register_attribute(__MODULE__, :protocol, persist: true)
|
||||
@protocol [fallback_to_any: !!@fallback_to_any, consolidated: false]
|
||||
|
||||
@doc false
|
||||
Kernel.def __protocol__(:name), do: __MODULE__
|
||||
Kernel.def __protocol__(:functions), do: unquote(:lists.sort(@functions))
|
||||
end
|
||||
end
|
||||
|
||||
# Callback for defimpl.
|
||||
@doc false
|
||||
def defimpl(protocol, opts) do
|
||||
do_defimpl(protocol, :lists.keysort(1, opts))
|
||||
end
|
||||
|
||||
defp do_defimpl(protocol, [do: block, for: for]) when is_list(for) do
|
||||
for f <- for, do: do_defimpl(protocol, [do: block, for: f])
|
||||
end
|
||||
|
||||
defp do_defimpl(protocol, [do: block, for: for]) do
|
||||
quote do
|
||||
protocol = unquote(protocol)
|
||||
for = unquote(for)
|
||||
name = Module.concat(protocol, for)
|
||||
|
||||
Protocol.assert_protocol(protocol)
|
||||
|
||||
defmodule name do
|
||||
@behaviour unquote(protocol)
|
||||
@protocol unquote(protocol)
|
||||
@for unquote(for)
|
||||
|
||||
unquote(block)
|
||||
|
||||
Module.register_attribute(__MODULE__, :impl, persist: true)
|
||||
@impl [protocol: @protocol, for: @for]
|
||||
|
||||
@doc false
|
||||
def __impl__(:name), do: __MODULE__
|
||||
def __impl__(:protocol), do: @protocol
|
||||
def __impl__(:for), do: @for
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Check if the given module is a protocol. Raises an error
|
||||
# if not loaded or not a protocol.
|
||||
@doc false
|
||||
def assert_protocol(module) do
|
||||
case Code.ensure_compiled(module) do
|
||||
{:module, ^module} -> nil
|
||||
_ -> raise ArgumentError, message: "#{inspect module} is not loaded"
|
||||
end
|
||||
|
||||
try do
|
||||
module.__protocol__(:name)
|
||||
rescue
|
||||
UndefinedFunctionError ->
|
||||
raise ArgumentError, message: "#{inspect module} is not a protocol"
|
||||
end
|
||||
end
|
||||
|
||||
# Builtin types.
|
||||
@doc false
|
||||
def builtin do
|
||||
[is_tuple: Tuple,
|
||||
is_atom: Atom,
|
||||
is_list: List,
|
||||
is_map: Map,
|
||||
is_bitstring: BitString,
|
||||
is_integer: Integer,
|
||||
is_float: Float,
|
||||
is_function: Function,
|
||||
is_pid: PID,
|
||||
is_port: Port,
|
||||
is_reference: Reference]
|
||||
end
|
||||
|
||||
# Implements the function that detects the protocol and
|
||||
# returns the module to dispatch to.
|
||||
@doc false
|
||||
def rec_impl_for(current) do
|
||||
all = [Any] ++ for {_guard, mod} <- builtin, do: mod
|
||||
arg = quote do: arg
|
||||
target = Module.concat(current, Tuple)
|
||||
|
||||
fallback = quote do
|
||||
case impl_for?(unquote(target)) do
|
||||
true -> unquote(target).__impl__(:name)
|
||||
false -> any_impl_for
|
||||
end
|
||||
end
|
||||
|
||||
impl_for = quote do
|
||||
atom = :erlang.element(1, unquote(arg))
|
||||
|
||||
case not(atom in unquote(all)) and match?('Elixir.' ++ _, atom_to_list(atom)) do
|
||||
true ->
|
||||
target = Module.concat(unquote(current), atom)
|
||||
case impl_for?(target) do
|
||||
true -> target.__impl__(:name)
|
||||
false -> unquote(fallback)
|
||||
end
|
||||
false ->
|
||||
unquote(fallback)
|
||||
end
|
||||
end
|
||||
|
||||
{arg, impl_for}
|
||||
end
|
||||
end
|
||||
|
||||
defmodule Protocol.DSL do
|
||||
@moduledoc false
|
||||
|
||||
@doc false
|
||||
defmacro def({_, _, args}) when args == [] or is_atom(args) do
|
||||
raise ArgumentError, message: "protocol functions expect at least one argument"
|
||||
raise ArgumentError, "protocol functions expect at least one argument"
|
||||
end
|
||||
|
||||
defmacro def({name, _, args}) when is_atom(name) and is_list(args) do
|
||||
@@ -230,7 +23,7 @@ defmodule Protocol.DSL do
|
||||
type_args = [quote(do: t) | type_args]
|
||||
|
||||
call_args = for i <- :lists.seq(2, arity),
|
||||
do: {binary_to_atom(<<?x, i + 64>>), [], __MODULE__}
|
||||
do: {String.to_atom(<<?x, i + 64>>), [], __MODULE__}
|
||||
call_args = [quote(do: t) | call_args]
|
||||
|
||||
quote do
|
||||
@@ -250,30 +43,576 @@ defmodule Protocol.DSL do
|
||||
|
||||
# Convert the spec to callback if possible,
|
||||
# otherwise generate a dummy callback
|
||||
Protocol.DSL.__spec__?(__MODULE__, name, arity) ||
|
||||
Protocol.__spec__?(__MODULE__, name, arity) ||
|
||||
@callback unquote(name)(unquote_splicing(type_args)) :: term
|
||||
end
|
||||
end
|
||||
|
||||
defmacro def(_) do
|
||||
raise ArgumentError, message: "invalid args for def inside defprotocol"
|
||||
raise ArgumentError, "invalid args for def inside defprotocol"
|
||||
end
|
||||
|
||||
@doc """
|
||||
Checks if the given module is loaded and is protocol.
|
||||
|
||||
Returns `:ok` if so, otherwise raises ArgumentError.
|
||||
"""
|
||||
@spec assert_protocol!(module) :: :ok | no_return
|
||||
def assert_protocol!(module) do
|
||||
assert_protocol!(module, "")
|
||||
end
|
||||
|
||||
defp assert_protocol!(module, extra) do
|
||||
case Code.ensure_compiled(module) do
|
||||
{:module, ^module} -> :ok
|
||||
_ -> raise ArgumentError, "#{inspect module} is not available" <> extra
|
||||
end
|
||||
|
||||
try do
|
||||
module.__protocol__(:name)
|
||||
rescue
|
||||
UndefinedFunctionError ->
|
||||
raise ArgumentError, "#{inspect module} is not a protocol" <> extra
|
||||
end
|
||||
|
||||
:ok
|
||||
end
|
||||
|
||||
@doc """
|
||||
Checks if the given module is loaded and is an implementation
|
||||
of the given protocol.
|
||||
|
||||
Returns `:ok` if so, otherwise raises ArgumentError.
|
||||
"""
|
||||
@spec assert_impl!(module, module) :: :ok | no_return
|
||||
def assert_impl!(protocol, impl) do
|
||||
assert_impl!(protocol, impl, "")
|
||||
end
|
||||
|
||||
defp assert_impl!(protocol, impl, extra) do
|
||||
case Code.ensure_compiled(impl) do
|
||||
{:module, ^impl} -> :ok
|
||||
_ -> raise ArgumentError,
|
||||
"#{inspect impl} is not available" <> extra
|
||||
end
|
||||
|
||||
try do
|
||||
impl.__impl__(:protocol)
|
||||
rescue
|
||||
UndefinedFunctionError ->
|
||||
raise ArgumentError,
|
||||
"#{inspect impl} is not an implementation of a protocol" <> extra
|
||||
else
|
||||
^protocol ->
|
||||
:ok
|
||||
other ->
|
||||
raise ArgumentError,
|
||||
"expected #{inspect impl} to be an implementation of #{inspect protocol}, got: #{inspect other}" <> extra
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Derive the `protocol` for `module` with the given options.
|
||||
"""
|
||||
defmacro derive(protocol, module, options \\ []) do
|
||||
quote do
|
||||
module = unquote(module)
|
||||
Protocol.__derive__([{unquote(protocol), unquote(options)}], module, __ENV__)
|
||||
end
|
||||
end
|
||||
|
||||
## Consolidation
|
||||
|
||||
@doc """
|
||||
Extract all protocols from the given paths.
|
||||
|
||||
The paths can be either a char list or a string. Internally
|
||||
they are worked on as char lists, so passing them as lists
|
||||
avoid extra conversion.
|
||||
|
||||
Does not load any of the protocols.
|
||||
|
||||
## Examples
|
||||
|
||||
# Get Elixir's ebin and retrieve all protocols
|
||||
iex> path = :code.lib_dir(:elixir, :ebin)
|
||||
iex> mods = Protocol.extract_protocols([path])
|
||||
iex> Enumerable in mods
|
||||
true
|
||||
|
||||
"""
|
||||
@spec extract_protocols([char_list | String.t]) :: [atom]
|
||||
def extract_protocols(paths) do
|
||||
extract_matching_by_attribute paths, 'Elixir.',
|
||||
fn module, attributes ->
|
||||
case attributes[:protocol] do
|
||||
[fallback_to_any: _, consolidated: _] -> module
|
||||
_ -> nil
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Extract all types implemented for the given protocol from
|
||||
the given paths.
|
||||
|
||||
The paths can be either a char list or a string. Internally
|
||||
they are worked on as char lists, so passing them as lists
|
||||
avoid extra conversion.
|
||||
|
||||
Does not load any of the implementations.
|
||||
|
||||
## Examples
|
||||
|
||||
# Get Elixir's ebin and retrieve all protocols
|
||||
iex> path = :code.lib_dir(:elixir, :ebin)
|
||||
iex> mods = Protocol.extract_impls(Enumerable, [path])
|
||||
iex> List in mods
|
||||
true
|
||||
|
||||
"""
|
||||
@spec extract_impls(module, [char_list | String.t]) :: [atom]
|
||||
def extract_impls(protocol, paths) when is_atom(protocol) do
|
||||
prefix = Atom.to_char_list(protocol) ++ '.'
|
||||
extract_matching_by_attribute paths, prefix, fn
|
||||
_mod, attributes ->
|
||||
case attributes[:impl] do
|
||||
[protocol: ^protocol, for: for] -> for
|
||||
_ -> nil
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defp extract_matching_by_attribute(paths, prefix, callback) do
|
||||
for path <- paths,
|
||||
file <- list_dir(path),
|
||||
mod = extract_from_file(path, file, prefix, callback),
|
||||
do: mod
|
||||
end
|
||||
|
||||
defp list_dir(path) when is_list(path) do
|
||||
case :file.list_dir(path) do
|
||||
{:ok, files} -> files
|
||||
_ -> []
|
||||
end
|
||||
end
|
||||
|
||||
defp list_dir(path), do: list_dir(to_char_list(path))
|
||||
|
||||
defp extract_from_file(path, file, prefix, callback) do
|
||||
if :lists.prefix(prefix, file) and :filename.extension(file) == '.beam' do
|
||||
extract_from_beam(:filename.join(path, file), callback)
|
||||
end
|
||||
end
|
||||
|
||||
defp extract_from_beam(file, callback) do
|
||||
case :beam_lib.chunks(file, [:attributes]) do
|
||||
{:ok, {module, [attributes: attributes]}} ->
|
||||
callback.(module, attributes)
|
||||
_ ->
|
||||
nil
|
||||
end
|
||||
end
|
||||
|
||||
defmacrop if_ok(expr, call) do
|
||||
quote do
|
||||
case unquote(expr) do
|
||||
{:ok, var} -> unquote(Macro.pipe(quote(do: var), call, 0))
|
||||
other -> other
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns true if the protocol was consolidated.
|
||||
"""
|
||||
@spec consolidated?(module) :: boolean
|
||||
def consolidated?(protocol) do
|
||||
protocol.__info__(:attributes)[:protocol][:consolidated]
|
||||
end
|
||||
|
||||
@doc """
|
||||
Receives a protocol and a list of implementations and
|
||||
consolidates the given protocol.
|
||||
|
||||
Consolidation happens by changing the protocol `impl_for`
|
||||
in the abstract format to have fast lookup rules. Usually
|
||||
the list of implementations to use during consolidation
|
||||
are retrieved with the help of `extract_impls/2`.
|
||||
|
||||
It returns the updated version of the protocol bytecode.
|
||||
A given bytecode or protocol implementation can be checked
|
||||
to be consolidated or not by analyzing the protocol
|
||||
attribute:
|
||||
|
||||
Protocol.consolidated?(Enumerable)
|
||||
|
||||
If the first element of the tuple is true, it means
|
||||
the protocol was consolidated.
|
||||
|
||||
This function does not load the protocol at any point
|
||||
nor loads the new bytecode for the compiled module.
|
||||
However each implementation must be available and
|
||||
it will be loaded.
|
||||
"""
|
||||
@spec consolidate(module, [module]) ::
|
||||
{:ok, binary} |
|
||||
{:error, :not_a_protocol} |
|
||||
{:error, :no_beam_info}
|
||||
def consolidate(protocol, types) when is_atom(protocol) do
|
||||
beam_protocol(protocol)
|
||||
|> if_ok(change_debug_info types)
|
||||
|> if_ok(compile)
|
||||
end
|
||||
|
||||
@docs_chunk 'ExDc'
|
||||
|
||||
defp beam_protocol(protocol) do
|
||||
chunk_ids = [:abstract_code, :attributes, @docs_chunk]
|
||||
opts = [:allow_missing_chunks]
|
||||
case :beam_lib.chunks(beam_file(protocol), chunk_ids, opts) do
|
||||
{:ok, {^protocol, [{:abstract_code, {_raw, abstract_code}},
|
||||
{:attributes, attributes},
|
||||
{@docs_chunk, docs}]}} ->
|
||||
case attributes[:protocol] do
|
||||
[fallback_to_any: any, consolidated: _] ->
|
||||
{:ok, {protocol, any, abstract_code, docs}}
|
||||
_ ->
|
||||
{:error, :not_a_protocol}
|
||||
end
|
||||
_ ->
|
||||
{:error, :no_beam_info}
|
||||
end
|
||||
end
|
||||
|
||||
defp beam_file(module) when is_atom(module) do
|
||||
case :code.which(module) do
|
||||
:non_existing -> module
|
||||
file -> file
|
||||
end
|
||||
end
|
||||
|
||||
# Change the debug information to the optimized
|
||||
# impl_for/1 dispatch version.
|
||||
defp change_debug_info({protocol, any, code, docs}, types) do
|
||||
types = if any, do: types, else: List.delete(types, Any)
|
||||
all = [Any] ++ for {_guard, mod} <- builtin, do: mod
|
||||
structs = types -- all
|
||||
case change_impl_for(code, protocol, types, structs, false, []) do
|
||||
{:ok, ret} -> {:ok, {ret, docs}}
|
||||
other -> other
|
||||
end
|
||||
end
|
||||
|
||||
defp change_impl_for([{:attribute, line, :protocol, opts}|t], protocol, types, structs, _, acc) do
|
||||
opts = [fallback_to_any: opts[:fallback_to_any], consolidated: true]
|
||||
change_impl_for(t, protocol, types, structs, true,
|
||||
[{:attribute, line, :protocol, opts}|acc])
|
||||
end
|
||||
|
||||
defp change_impl_for([{:function, line, :impl_for, 1, _}|t], protocol, types, structs, is_protocol, acc) do
|
||||
fallback = if Any in types, do: load_impl(protocol, Any), else: nil
|
||||
|
||||
clauses = for {guard, mod} <- builtin,
|
||||
mod in types,
|
||||
do: builtin_clause_for(mod, guard, protocol, line)
|
||||
|
||||
clauses = [struct_clause_for(line)|clauses] ++
|
||||
[fallback_clause_for(fallback, protocol, line)]
|
||||
|
||||
change_impl_for(t, protocol, types, structs, is_protocol,
|
||||
[{:function, line, :impl_for, 1, clauses}|acc])
|
||||
end
|
||||
|
||||
defp change_impl_for([{:function, line, :struct_impl_for, 1, _}|t], protocol, types, structs, is_protocol, acc) do
|
||||
fallback = if Any in types, do: load_impl(protocol, Any), else: nil
|
||||
clauses = for struct <- structs, do: each_struct_clause_for(struct, protocol, line)
|
||||
clauses = clauses ++ [fallback_clause_for(fallback, protocol, line)]
|
||||
|
||||
change_impl_for(t, protocol, types, structs, is_protocol,
|
||||
[{:function, line, :struct_impl_for, 1, clauses}|acc])
|
||||
end
|
||||
|
||||
defp change_impl_for([h|t], protocol, info, types, is_protocol, acc) do
|
||||
change_impl_for(t, protocol, info, types, is_protocol, [h|acc])
|
||||
end
|
||||
|
||||
defp change_impl_for([], protocol, _info, _types, is_protocol, acc) do
|
||||
if is_protocol do
|
||||
{:ok, {protocol, Enum.reverse(acc)}}
|
||||
else
|
||||
{:error, :not_a_protocol}
|
||||
end
|
||||
end
|
||||
|
||||
defp builtin_clause_for(mod, guard, protocol, line) do
|
||||
{:clause, line,
|
||||
[{:var, line, :x}],
|
||||
[[{:call, line,
|
||||
{:remote, line, {:atom, line, :erlang}, {:atom, line, guard}},
|
||||
[{:var, line, :x}],
|
||||
}]],
|
||||
[{:atom, line, load_impl(protocol, mod)}]}
|
||||
end
|
||||
|
||||
defp struct_clause_for(line) do
|
||||
{:clause, line,
|
||||
[{:map, line, [
|
||||
{:map_field_exact, line, {:atom, line, :__struct__}, {:var, line, :x}}
|
||||
]}],
|
||||
[[{:call, line,
|
||||
{:remote, line, {:atom, line, :erlang}, {:atom, line, :is_atom}},
|
||||
[{:var, line, :x}],
|
||||
}]],
|
||||
[{:call, line,
|
||||
{:atom, line, :struct_impl_for},
|
||||
[{:var, line, :x}]}]}
|
||||
end
|
||||
|
||||
defp each_struct_clause_for(other, protocol, line) do
|
||||
{:clause, line, [{:atom, line, other}], [],
|
||||
[{:atom, line, load_impl(protocol, other)}]}
|
||||
end
|
||||
|
||||
defp fallback_clause_for(value, _protocol, line) do
|
||||
{:clause, line, [{:var, line, :_}], [],
|
||||
[{:atom, line, value}]}
|
||||
end
|
||||
|
||||
defp load_impl(protocol, for) do
|
||||
Module.concat(protocol, for).__impl__(:target)
|
||||
end
|
||||
|
||||
# Finally compile the module and emit its bytecode.
|
||||
defp compile({{protocol, code}, docs}) do
|
||||
opts = if Code.compiler_options[:debug_info], do: [:debug_info], else: []
|
||||
{:ok, ^protocol, binary, _warnings} = :compile.forms(code, [:return|opts])
|
||||
unless docs == :missing_chunk do
|
||||
binary = :elixir_module.add_beam_chunk(binary, @docs_chunk, docs)
|
||||
end
|
||||
{:ok, binary}
|
||||
end
|
||||
|
||||
## Definition callbacks
|
||||
|
||||
@doc false
|
||||
def __protocol__(name, [do: block]) do
|
||||
quote do
|
||||
defmodule unquote(name) do
|
||||
# We don't allow function definition inside protocols
|
||||
import Kernel, except: [
|
||||
defmacrop: 1, defmacrop: 2, defmacro: 1, defmacro: 2,
|
||||
defp: 1, defp: 2, def: 1, def: 2
|
||||
]
|
||||
|
||||
# Import the new dsl that holds the new def
|
||||
import Protocol, only: [def: 1]
|
||||
|
||||
# Compile with debug info for consolidation
|
||||
@compile :debug_info
|
||||
|
||||
# Set up a clear slate to store defined functions
|
||||
@functions []
|
||||
@fallback_to_any false
|
||||
|
||||
# Invoke the user given block
|
||||
unquote(block)
|
||||
|
||||
# Finalize expansion
|
||||
unquote(after_defprotocol)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defp after_defprotocol do
|
||||
quote bind_quoted: [builtin: builtin] do
|
||||
@spec impl_for(term) :: module | nil
|
||||
Kernel.def impl_for(data)
|
||||
|
||||
# Define the implementation for structs.
|
||||
#
|
||||
# It simply delegates to struct_impl_for which is then
|
||||
# optimized during protocol consolidation.
|
||||
Kernel.def impl_for(%{__struct__: struct}) when :erlang.is_atom(struct) do
|
||||
struct_impl_for(struct)
|
||||
end
|
||||
|
||||
# Define the implementation for builtins.
|
||||
for {guard, mod} <- builtin do
|
||||
target = Module.concat(__MODULE__, mod)
|
||||
|
||||
Kernel.def impl_for(data) when :erlang.unquote(guard)(data) do
|
||||
case impl_for?(unquote(target)) do
|
||||
true -> unquote(target).__impl__(:target)
|
||||
false -> any_impl_for
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@spec impl_for!(term) :: module | no_return
|
||||
Kernel.def impl_for!(data) do
|
||||
impl_for(data) || raise(Protocol.UndefinedError, protocol: __MODULE__, value: data)
|
||||
end
|
||||
|
||||
# Internal handler for Any
|
||||
if @fallback_to_any do
|
||||
Kernel.defp any_impl_for do
|
||||
case impl_for?(__MODULE__.Any) do
|
||||
true -> __MODULE__.Any.__impl__(:target)
|
||||
false -> nil
|
||||
end
|
||||
end
|
||||
else
|
||||
Kernel.defp any_impl_for, do: nil
|
||||
end
|
||||
|
||||
# Internal handler for Structs
|
||||
Kernel.defp struct_impl_for(struct) do
|
||||
target = Module.concat(__MODULE__, struct)
|
||||
case impl_for?(target) do
|
||||
true -> target.__impl__(:target)
|
||||
false -> any_impl_for
|
||||
end
|
||||
end
|
||||
|
||||
# Check if compilation is available internally
|
||||
Kernel.defp impl_for?(target) do
|
||||
Code.ensure_compiled?(target) and
|
||||
function_exported?(target, :__impl__, 1)
|
||||
end
|
||||
|
||||
# Inline any and struct implementations
|
||||
@compile {:inline, any_impl_for: 0, struct_impl_for: 1, impl_for?: 1}
|
||||
|
||||
unless Kernel.Typespec.defines_type?(__MODULE__, :t, 0) do
|
||||
@type t :: term
|
||||
end
|
||||
|
||||
# Store information as an attribute so it
|
||||
# can be read without loading the module.
|
||||
Module.register_attribute(__MODULE__, :protocol, persist: true)
|
||||
@protocol [fallback_to_any: !!@fallback_to_any, consolidated: false]
|
||||
|
||||
@doc false
|
||||
@spec __protocol__(atom) :: term
|
||||
Kernel.def __protocol__(:name), do: __MODULE__
|
||||
Kernel.def __protocol__(:functions), do: unquote(:lists.sort(@functions))
|
||||
end
|
||||
end
|
||||
|
||||
@doc false
|
||||
def __impl__(protocol, opts) do
|
||||
do_defimpl(protocol, :lists.keysort(1, opts))
|
||||
end
|
||||
|
||||
defp do_defimpl(protocol, [do: block, for: for]) when is_list(for) do
|
||||
for f <- for, do: do_defimpl(protocol, [do: block, for: f])
|
||||
end
|
||||
|
||||
defp do_defimpl(protocol, [do: block, for: for]) do
|
||||
quote do
|
||||
protocol = unquote(protocol)
|
||||
for = unquote(for)
|
||||
name = Module.concat(protocol, for)
|
||||
|
||||
Protocol.assert_protocol!(protocol)
|
||||
|
||||
defmodule name do
|
||||
@behaviour protocol
|
||||
@protocol protocol
|
||||
@for for
|
||||
|
||||
unquote(block)
|
||||
|
||||
Module.register_attribute(__MODULE__, :impl, persist: true)
|
||||
@impl [protocol: @protocol, for: @for]
|
||||
|
||||
@doc false
|
||||
@spec __impl__(atom) :: term
|
||||
def __impl__(:target), do: __MODULE__
|
||||
def __impl__(:protocol), do: @protocol
|
||||
def __impl__(:for), do: @for
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@doc false
|
||||
def __derive__(derives, for, %Macro.Env{} = env) when is_atom(for) do
|
||||
struct =
|
||||
if for == env.module do
|
||||
Module.get_attribute(for, :struct) ||
|
||||
raise "struct is not defined for #{inspect for}"
|
||||
else
|
||||
for.__struct__
|
||||
end
|
||||
|
||||
:lists.foreach(fn
|
||||
proto when is_atom(proto) ->
|
||||
derive(proto, for, struct, [], env)
|
||||
{proto, opts} when is_atom(proto) ->
|
||||
derive(proto, for, struct, opts, env)
|
||||
end, :lists.flatten(derives))
|
||||
|
||||
:ok
|
||||
end
|
||||
|
||||
defp derive(protocol, for, struct, opts, env) do
|
||||
impl = Module.concat(protocol, Map)
|
||||
extra = ", cannot derive #{inspect protocol} for #{inspect for}"
|
||||
assert_protocol!(protocol, extra)
|
||||
assert_impl!(protocol, impl, extra)
|
||||
|
||||
# Clean up variables from eval context
|
||||
env = %{env | vars: [], export_vars: nil}
|
||||
args = [for, struct, opts]
|
||||
|
||||
:elixir_module.expand_callback(env.line, impl, :__deriving__, args, env, fn
|
||||
mod, fun, args ->
|
||||
if function_exported?(mod, fun, length(args)) do
|
||||
apply(mod, fun, args)
|
||||
else
|
||||
Module.create(Module.concat(protocol, for), quote do
|
||||
Module.register_attribute(__MODULE__, :impl, persist: true)
|
||||
@impl [protocol: unquote(protocol), for: unquote(for)]
|
||||
|
||||
@doc false
|
||||
@spec __impl__(atom) :: term
|
||||
def __impl__(:target), do: unquote(impl)
|
||||
def __impl__(:protocol), do: unquote(protocol)
|
||||
def __impl__(:for), do: unquote(for)
|
||||
end, Macro.Env.location(env))
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
@doc false
|
||||
def __spec__?(module, name, arity) do
|
||||
case :code.ensure_loaded(Kernel.Typespec) do
|
||||
{:module, Kernel.Typespec} ->
|
||||
tuple = {name, arity}
|
||||
specs = Module.get_attribute(module, :spec)
|
||||
signature = {name, arity}
|
||||
specs = Module.get_attribute(module, :spec)
|
||||
|
||||
found = for {k, v} <- specs, k == tuple do
|
||||
Kernel.Typespec.define_callback(module, tuple, v)
|
||||
found =
|
||||
for {:spec, expr, caller} <- specs,
|
||||
Kernel.Typespec.spec_to_signature(expr) == signature do
|
||||
Kernel.Typespec.define_spec(:callback, expr, caller)
|
||||
true
|
||||
end
|
||||
|
||||
found != []
|
||||
{:error, _} ->
|
||||
true
|
||||
end
|
||||
found != []
|
||||
end
|
||||
|
||||
## Helpers
|
||||
|
||||
defp builtin do
|
||||
[is_tuple: Tuple,
|
||||
is_atom: Atom,
|
||||
is_list: List,
|
||||
is_map: Map,
|
||||
is_bitstring: BitString,
|
||||
is_integer: Integer,
|
||||
is_float: Float,
|
||||
is_function: Function,
|
||||
is_pid: PID,
|
||||
is_port: Port,
|
||||
is_reference: Reference]
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,249 +0,0 @@
|
||||
defmodule Protocol.Consolidation do
|
||||
@moduledoc """
|
||||
Module responsible for consolidating protocols and helpers for
|
||||
extracting protocols and implementations from code paths for
|
||||
consolidation.
|
||||
"""
|
||||
|
||||
@doc """
|
||||
Extract all protocols from the given paths.
|
||||
|
||||
The paths can be either a char list or a string. Internally
|
||||
they are worked on as char lists, so passing them as lists
|
||||
avoid extra conversion.
|
||||
|
||||
## Examples
|
||||
|
||||
# Get Elixir's ebin and retrieve all protocols
|
||||
iex> path = :code.lib_dir(:elixir, :ebin)
|
||||
iex> mods = Protocol.Consolidation.extract_protocols([path])
|
||||
iex> Enumerable in mods
|
||||
true
|
||||
|
||||
"""
|
||||
@spec extract_protocols([char_list | String.t]) :: [atom]
|
||||
def extract_protocols(paths) do
|
||||
extract_matching_by_attribute paths, 'Elixir.',
|
||||
fn module, attributes ->
|
||||
case attributes[:protocol] do
|
||||
[fallback_to_any: _, consolidated: _] -> module
|
||||
_ -> nil
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Extract all types implemented for the given protocol from
|
||||
the given paths.
|
||||
|
||||
The paths can be either a char list or a string. Internally
|
||||
they are worked on as char lists, so passing them as lists
|
||||
avoid extra conversion.
|
||||
|
||||
## Examples
|
||||
|
||||
# Get Elixir's ebin and retrieve all protocols
|
||||
iex> path = :code.lib_dir(:elixir, :ebin)
|
||||
iex> mods = Protocol.Consolidation.extract_impls(Enumerable, [path])
|
||||
iex> List in mods
|
||||
true
|
||||
|
||||
"""
|
||||
@spec extract_impls(module, [char_list | String.t]) :: [atom]
|
||||
def extract_impls(protocol, paths) when is_atom(protocol) do
|
||||
prefix = atom_to_list(protocol) ++ '.'
|
||||
extract_matching_by_attribute paths, prefix, fn
|
||||
_mod, attributes ->
|
||||
case attributes[:impl] do
|
||||
[protocol: ^protocol, for: for] -> for
|
||||
_ -> nil
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defp extract_matching_by_attribute(paths, prefix, callback) do
|
||||
for path <- paths,
|
||||
file <- list_dir(path),
|
||||
mod = extract_from_file(path, file, prefix, callback),
|
||||
do: mod
|
||||
end
|
||||
|
||||
defp list_dir(path) when is_list(path) do
|
||||
case :file.list_dir(path) do
|
||||
{:ok, files} -> files
|
||||
_ -> []
|
||||
end
|
||||
end
|
||||
|
||||
defp list_dir(path), do: list_dir(to_char_list(path))
|
||||
|
||||
defp extract_from_file(path, file, prefix, callback) do
|
||||
if :lists.prefix(prefix, file) and :filename.extension(file) == '.beam' do
|
||||
extract_from_beam(:filename.join(path, file), callback)
|
||||
end
|
||||
end
|
||||
|
||||
defp extract_from_beam(file, callback) do
|
||||
case :beam_lib.chunks(file, [:attributes]) do
|
||||
{:ok, {module, [attributes: attributes]}} ->
|
||||
callback.(module, attributes)
|
||||
_ ->
|
||||
nil
|
||||
end
|
||||
end
|
||||
|
||||
defmacrop if_ok(expr, call) do
|
||||
quote do
|
||||
case unquote(expr) do
|
||||
{:ok, var} -> unquote(Macro.pipe(quote(do: var), call, 0))
|
||||
other -> other
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Receives a protocol and a list of implementations and
|
||||
consolidates the given protocol. Consolidation happens
|
||||
by changing the protocol `impl_for` in the abstract
|
||||
format to have fast lookup rules.
|
||||
|
||||
It returns the updated version of the protocol bytecode.
|
||||
A given bytecode or protocol implementation can be checked
|
||||
to be consolidated or not by analyzing the protocol
|
||||
attribute:
|
||||
|
||||
Enumerable.__info__(:attributes)[:protocol]
|
||||
|
||||
If the first element of the tuple is true, it means
|
||||
the protocol was consolidated.
|
||||
|
||||
This function does not load the protocol at any point
|
||||
nor loads the new bytecode for the compiled module.
|
||||
"""
|
||||
@spec apply_to(module, [module]) ::
|
||||
{:ok, binary} |
|
||||
{:error, :not_a_protocol} |
|
||||
{:error, :no_beam_info}
|
||||
def apply_to(protocol, types) when is_atom(protocol) do
|
||||
raise ArgumentError, message: "consolidation is disabled as we can't consolidate records " <>
|
||||
"and structs at once. Consolidation will be added back once " <>
|
||||
"polymorphic records are removed"
|
||||
ensure_protocol(protocol)
|
||||
|> if_ok(change_debug_info types)
|
||||
|> if_ok(compile)
|
||||
end
|
||||
|
||||
# Ensure the given module is loaded and is a protocol.
|
||||
defp ensure_protocol(protocol) do
|
||||
case :beam_lib.chunks(beam_file(protocol), [:abstract_code, :attributes]) do
|
||||
{:ok, {^protocol, [abstract_code: {_raw, abstract_code},
|
||||
attributes: attributes]}} ->
|
||||
case attributes[:protocol] do
|
||||
[fallback_to_any: any, consolidated: _] ->
|
||||
{:ok, {protocol, any, abstract_code}}
|
||||
_ ->
|
||||
{:error, :not_a_protocol}
|
||||
end
|
||||
_ ->
|
||||
{:error, :no_beam_info}
|
||||
end
|
||||
end
|
||||
|
||||
defp beam_file(module) when is_atom(module) do
|
||||
case :code.which(module) do
|
||||
:non_existing -> module
|
||||
file -> file
|
||||
end
|
||||
end
|
||||
|
||||
# Change the debug information to the optimized
|
||||
# impl_for/1 dispatch version.
|
||||
defp change_debug_info({protocol, any, code}, types) do
|
||||
types = if any, do: types, else: List.delete(types, Any)
|
||||
all = [Any] ++ for {_guard, mod} <- Protocol.builtin, do: mod
|
||||
structs = types -- all
|
||||
change_impl_for(code, protocol, types, structs, false, [])
|
||||
end
|
||||
|
||||
defp change_impl_for([{:attribute, line, :protocol, opts}|t], protocol, types, structs, _, acc) do
|
||||
opts = [fallback_to_any: opts[:fallback_to_any], consolidated: true]
|
||||
change_impl_for(t, protocol, types, structs, true,
|
||||
[{:attribute, line, :protocol, opts}|acc])
|
||||
end
|
||||
|
||||
defp change_impl_for([{:function, line, :impl_for, 1, _}|t], protocol, types, structs, is_protocol, acc) do
|
||||
fallback = if Any in types, do: Module.concat(protocol, Any), else: nil
|
||||
|
||||
clauses = for {guard, mod} <- Protocol.builtin,
|
||||
mod in types,
|
||||
do: builtin_clause_for(mod, guard, protocol, line)
|
||||
|
||||
clauses = [struct_clause_for(line)|clauses] ++
|
||||
[fallback_clause_for(fallback, protocol, line)]
|
||||
|
||||
change_impl_for(t, protocol, types, structs, is_protocol,
|
||||
[{:function, line, :impl_for, 1, clauses}|acc])
|
||||
end
|
||||
|
||||
defp change_impl_for([{:function, line, :struct_impl_for, 1, _}|t], protocol, types, structs, is_protocol, acc) do
|
||||
fallback = if Any in types, do: Module.concat(protocol, Any), else: nil
|
||||
clauses = for struct <- structs, do: each_struct_clause_for(struct, protocol, line)
|
||||
clauses = clauses ++ [fallback_clause_for(fallback, protocol, line)]
|
||||
|
||||
change_impl_for(t, protocol, types, structs, is_protocol,
|
||||
[{:function, line, :struct_impl_for, 1, clauses}|acc])
|
||||
end
|
||||
|
||||
defp change_impl_for([h|t], protocol, info, types, is_protocol, acc) do
|
||||
change_impl_for(t, protocol, info, types, is_protocol, [h|acc])
|
||||
end
|
||||
|
||||
defp change_impl_for([], protocol, _info, _types, is_protocol, acc) do
|
||||
if is_protocol do
|
||||
{:ok, {protocol, Enum.reverse(acc)}}
|
||||
else
|
||||
{:error, :not_a_protocol}
|
||||
end
|
||||
end
|
||||
|
||||
defp builtin_clause_for(mod, guard, protocol, line) do
|
||||
{:clause, line,
|
||||
[{:var, line, :x}],
|
||||
[[{:call, line,
|
||||
{:remote, line, {:atom, line, :erlang}, {:atom, line, guard}},
|
||||
[{:var, line, :x}],
|
||||
}]],
|
||||
[{:atom, line, Module.concat(protocol, mod)}]}
|
||||
end
|
||||
|
||||
defp struct_clause_for(line) do
|
||||
{:clause, line,
|
||||
[{:map, line, [
|
||||
{:map_field_exact, line, {:atom, line, :__struct__}, {:var, line, :x}}
|
||||
]}],
|
||||
[[{:call, line,
|
||||
{:remote, line, {:atom, line, :erlang}, {:atom, line, :is_atom}},
|
||||
[{:var, line, :x}],
|
||||
}]],
|
||||
[{:call, line,
|
||||
{:atom, line, :struct_impl_for},
|
||||
[{:var, line, :x}]}]}
|
||||
end
|
||||
|
||||
defp each_struct_clause_for(other, protocol, line) do
|
||||
{:clause, line, [{:atom, line, other}], [],
|
||||
[{:atom, line, Module.concat(protocol, other)}]}
|
||||
end
|
||||
|
||||
defp fallback_clause_for(value, _protocol, line) do
|
||||
{:clause, line, [{:var, line, :_}], [],
|
||||
[{:atom, line, value}]}
|
||||
end
|
||||
|
||||
# Finally compile the module and emit its bytecode.
|
||||
defp compile({protocol, code}) do
|
||||
opts = if Code.compiler_options[:debug_info], do: [:debug_info], else: []
|
||||
{:ok, ^protocol, binary, _warnings} = :compile.forms(code, [:return|opts])
|
||||
{:ok, binary}
|
||||
end
|
||||
end
|
||||
+120
-27
@@ -12,12 +12,10 @@ defmodule Record do
|
||||
manipulate the tuples, providing fast operations on top of
|
||||
the tuples compact structure.
|
||||
|
||||
In Elixir, records are used in two situations:
|
||||
In Elixir, records are used mostly in two situations:
|
||||
|
||||
1. To work with short, internal data. See `Inspect.Algebra`
|
||||
implementation for a good example;
|
||||
|
||||
2. To interface with Erlang records;
|
||||
1. to work with short, internal data
|
||||
2. to interface with Erlang records
|
||||
|
||||
The macros `defrecord/3` and `defrecordp/3` can be used to create
|
||||
records while `extract/2` can be used to extract records from Erlang
|
||||
@@ -103,21 +101,6 @@ defmodule Record do
|
||||
end
|
||||
end
|
||||
|
||||
@doc false
|
||||
def defmacros(name, values, env, tag \\ nil) do
|
||||
Record.Deprecated.defmacros(name, values, env, tag)
|
||||
end
|
||||
|
||||
@doc false
|
||||
def deftypes(values, types, env) do
|
||||
Record.Deprecated.deftypes(values, types, env)
|
||||
end
|
||||
|
||||
@doc false
|
||||
def deffunctions(values, env) do
|
||||
Record.Deprecated.deffunctions(values, env)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Defines a set of macros to create and access a record.
|
||||
|
||||
@@ -151,20 +134,21 @@ defmodule Record do
|
||||
Record.defrecord :user, User, name: nil
|
||||
end
|
||||
|
||||
user() #=> {User, nil}
|
||||
require User
|
||||
User.user() #=> {User, nil}
|
||||
|
||||
"""
|
||||
defmacro defrecord(name, tag \\ nil, kv) do
|
||||
quote bind_quoted: [name: name, tag: tag, kv: kv] do
|
||||
tag = tag || name
|
||||
fields = Macro.escape Record.Backend.default_fields(:record, kv)
|
||||
fields = Macro.escape Record.__fields__(:defrecord, kv)
|
||||
|
||||
defmacro(unquote(name)(args \\ [])) do
|
||||
Record.Backend.access(unquote(tag), unquote(fields), args, __CALLER__)
|
||||
Record.__access__(unquote(tag), unquote(fields), args, __CALLER__)
|
||||
end
|
||||
|
||||
defmacro(unquote(name)(record, args)) do
|
||||
Record.Backend.access(unquote(tag), unquote(fields), record, args, __CALLER__)
|
||||
Record.__access__(unquote(tag), unquote(fields), record, args, __CALLER__)
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -175,15 +159,124 @@ defmodule Record do
|
||||
defmacro defrecordp(name, tag \\ nil, kv) do
|
||||
quote bind_quoted: [name: name, tag: tag, kv: kv] do
|
||||
tag = tag || name
|
||||
fields = Macro.escape Record.Backend.default_fields(:record, kv)
|
||||
fields = Macro.escape Record.__fields__(:defrecordp, kv)
|
||||
|
||||
defmacrop(unquote(name)(args \\ [])) do
|
||||
Record.Backend.access(unquote(tag), unquote(fields), args, __CALLER__)
|
||||
Record.__access__(unquote(tag), unquote(fields), args, __CALLER__)
|
||||
end
|
||||
|
||||
defmacrop(unquote(name)(record, args)) do
|
||||
Record.Backend.access(unquote(tag), unquote(fields), record, args, __CALLER__)
|
||||
Record.__access__(unquote(tag), unquote(fields), record, args, __CALLER__)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Normalizes of record fields to have default values.
|
||||
@doc false
|
||||
def __fields__(type, fields) do
|
||||
:lists.map(fn
|
||||
{ key, _ } = pair when is_atom(key) -> pair
|
||||
key when is_atom(key) -> { key, nil }
|
||||
other -> raise ArgumentError, "#{type} fields must be atoms, got: #{inspect other}"
|
||||
end, fields)
|
||||
end
|
||||
|
||||
# Callback invoked from record/0 and record/1 macros.
|
||||
@doc false
|
||||
def __access__(atom, fields, args, caller) do
|
||||
cond do
|
||||
is_atom(args) ->
|
||||
index(atom, fields, args)
|
||||
Keyword.keyword?(args) ->
|
||||
create(atom, fields, args, caller)
|
||||
true ->
|
||||
msg = "expected arguments to be a compile time atom or keywords, got: #{Macro.to_string args}"
|
||||
raise ArgumentError, msg
|
||||
end
|
||||
end
|
||||
|
||||
# Callback invoked from the record/2 macro.
|
||||
@doc false
|
||||
def __access__(atom, fields, record, args, caller) do
|
||||
cond do
|
||||
is_atom(args) ->
|
||||
get(atom, fields, record, args)
|
||||
Keyword.keyword?(args) ->
|
||||
update(atom, fields, record, args, caller)
|
||||
true ->
|
||||
msg = "expected arguments to be a compile time atom or keywords, got: #{Macro.to_string args}"
|
||||
raise ArgumentError, msg
|
||||
end
|
||||
end
|
||||
|
||||
# Gets the index of field.
|
||||
defp index(atom, fields, field) do
|
||||
if index = find_index(fields, field, 0) do
|
||||
index - 1 # Convert to Elixir index
|
||||
else
|
||||
raise ArgumentError, "record #{inspect atom} does not have the key: #{inspect field}"
|
||||
end
|
||||
end
|
||||
|
||||
# Creates a new record with the given default fields and keyword values.
|
||||
defp create(atom, fields, keyword, caller) do
|
||||
in_match = Macro.Env.in_match?(caller)
|
||||
|
||||
{match, remaining} =
|
||||
Enum.map_reduce(fields, keyword, fn({field, default}, each_keyword) ->
|
||||
new_fields =
|
||||
case Keyword.has_key?(each_keyword, field) do
|
||||
true -> Keyword.get(each_keyword, field)
|
||||
false ->
|
||||
case in_match do
|
||||
true -> {:_, [], nil}
|
||||
false -> Macro.escape(default)
|
||||
end
|
||||
end
|
||||
|
||||
{new_fields, Keyword.delete(each_keyword, field)}
|
||||
end)
|
||||
|
||||
case remaining do
|
||||
[] ->
|
||||
{:{}, [], [atom|match]}
|
||||
_ ->
|
||||
keys = for {key, _} <- remaining, do: key
|
||||
raise ArgumentError, "record #{inspect atom} does not have the key: #{inspect hd(keys)}"
|
||||
end
|
||||
end
|
||||
|
||||
# Updates a record given by var with the given keyword.
|
||||
defp update(atom, fields, var, keyword, caller) do
|
||||
if Macro.Env.in_match?(caller) do
|
||||
raise ArgumentError, "cannot invoke update style macro inside match"
|
||||
end
|
||||
|
||||
Enum.reduce keyword, var, fn({key, value}, acc) ->
|
||||
index = find_index(fields, key, 0)
|
||||
if index do
|
||||
quote do
|
||||
:erlang.setelement(unquote(index), unquote(acc), unquote(value))
|
||||
end
|
||||
else
|
||||
raise ArgumentError, "record #{inspect atom} does not have the key: #{inspect key}"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Gets a record key from the given var.
|
||||
defp get(atom, fields, var, key) do
|
||||
index = find_index(fields, key, 0)
|
||||
if index do
|
||||
quote do
|
||||
:erlang.element(unquote(index), unquote(var))
|
||||
end
|
||||
else
|
||||
raise ArgumentError, "record #{inspect atom} does not have the key: #{inspect key}"
|
||||
end
|
||||
end
|
||||
|
||||
defp find_index([{k, _}|_], k, i), do: i + 2
|
||||
defp find_index([{_, _}|t], k, i), do: find_index(t, k, i + 1)
|
||||
defp find_index([], _k, _i), do: nil
|
||||
end
|
||||
|
||||
@@ -1,153 +0,0 @@
|
||||
defmodule Record.Backend do
|
||||
# Callback functions invoked by defrecord, defrecordp and friends.
|
||||
@moduledoc false
|
||||
|
||||
@doc """
|
||||
Normalizes a list of record or struct fields to have default values.
|
||||
"""
|
||||
def default_fields(type, fields) do
|
||||
Enum.map(fields, fn
|
||||
{ key, _ } = pair when is_atom(key) -> pair
|
||||
key when is_atom(key) -> { key, nil }
|
||||
other -> raise ArgumentError, message: "#{type} fields must be atoms, got: #{inspect other}"
|
||||
end)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Splits a keywords list into fields and types.
|
||||
|
||||
This logic is shared by records and structs.
|
||||
"""
|
||||
def split_fields_and_types(kv) do
|
||||
if Keyword.keyword?(kv) do
|
||||
split_fields_and_types(kv, [], [])
|
||||
else
|
||||
{kv, []}
|
||||
end
|
||||
end
|
||||
|
||||
defp split_fields_and_types([{field, {:::, _, [default, type]}}|t], fields, types) do
|
||||
split_fields_and_types(t, [{field, default}|fields], [{field, type}|types])
|
||||
end
|
||||
|
||||
defp split_fields_and_types([{field, default}|t], fields, types) do
|
||||
split_fields_and_types(t, [{field, default}|fields], [{field, quote(do: term)}|types])
|
||||
end
|
||||
|
||||
defp split_fields_and_types([field|t], fields, types) do
|
||||
split_fields_and_types(t, [field|fields], [{field, quote(do: term)}|types])
|
||||
end
|
||||
|
||||
defp split_fields_and_types([], fields, types) do
|
||||
{:lists.reverse(fields), :lists.reverse(types)}
|
||||
end
|
||||
|
||||
@doc """
|
||||
Callback invoked from record/0 and record/1 macros.
|
||||
"""
|
||||
def access(atom, fields, args, caller) do
|
||||
cond do
|
||||
is_atom(args) ->
|
||||
index(atom, fields, args)
|
||||
Keyword.keyword?(args) ->
|
||||
create(atom, fields, args, caller)
|
||||
true ->
|
||||
raise ArgumentError,
|
||||
message: "expected arguments to be a compile time atom or keywords, got: #{Macro.to_string args}"
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Callback invoked from the record/2 macro.
|
||||
"""
|
||||
def access(atom, fields, record, args, caller) do
|
||||
cond do
|
||||
is_atom(args) ->
|
||||
get(atom, fields, record, args)
|
||||
Keyword.keyword?(args) ->
|
||||
update(atom, fields, record, args, caller)
|
||||
true ->
|
||||
raise ArgumentError,
|
||||
message: "expected arguments to be a compile time atom or keywords, got: #{Macro.to_string args}"
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Gets the index of field.
|
||||
"""
|
||||
def index(atom, fields, field) do
|
||||
if index = find_index(fields, field, 0) do
|
||||
index - 1 # Convert to Elixir index
|
||||
else
|
||||
raise ArgumentError, message: "record #{inspect atom} does not have the key: #{inspect field}"
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Creates a new record with the given default fields and keyword values.
|
||||
"""
|
||||
def create(atom, fields, keyword, caller) do
|
||||
in_match = Macro.Env.in_match?(caller)
|
||||
|
||||
{match, remaining} =
|
||||
Enum.map_reduce(fields, keyword, fn({field, default}, each_keyword) ->
|
||||
new_fields =
|
||||
case Keyword.has_key?(each_keyword, field) do
|
||||
true -> Keyword.get(each_keyword, field)
|
||||
false ->
|
||||
case in_match do
|
||||
true -> {:_, [], nil}
|
||||
false -> Macro.escape(default)
|
||||
end
|
||||
end
|
||||
|
||||
{new_fields, Keyword.delete(each_keyword, field)}
|
||||
end)
|
||||
|
||||
case remaining do
|
||||
[] ->
|
||||
{:{}, [], [atom|match]}
|
||||
_ ->
|
||||
keys = for {key, _} <- remaining, do: key
|
||||
raise ArgumentError, message: "record #{inspect atom} does not have the key: #{inspect hd(keys)}"
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Updates a record given by var with the given keyword.
|
||||
"""
|
||||
def update(atom, fields, var, keyword, caller) do
|
||||
if Macro.Env.in_match?(caller) do
|
||||
raise ArgumentError, message: "cannot invoke update style macro inside match"
|
||||
end
|
||||
|
||||
Enum.reduce keyword, var, fn({key, value}, acc) ->
|
||||
index = find_index(fields, key, 0)
|
||||
if index do
|
||||
quote do
|
||||
:erlang.setelement(unquote(index), unquote(acc), unquote(value))
|
||||
end
|
||||
else
|
||||
raise ArgumentError, message: "record #{inspect atom} does not have the key: #{inspect key}"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Gets a record key from the given var.
|
||||
"""
|
||||
def get(atom, fields, var, key) do
|
||||
index = find_index(fields, key, 0)
|
||||
if index do
|
||||
quote do
|
||||
:erlang.element(unquote(index), unquote(var))
|
||||
end
|
||||
else
|
||||
raise ArgumentError, message: "record #{inspect atom} does not have the key: #{inspect key}"
|
||||
end
|
||||
end
|
||||
|
||||
defp find_index([{k, _}|_], k, i), do: i + 2
|
||||
defp find_index([{_, _}|t], k, i), do: find_index(t, k, i + 1)
|
||||
defp find_index([], _k, _i), do: nil
|
||||
end
|
||||
@@ -1,526 +0,0 @@
|
||||
defmodule Record.Deprecated do
|
||||
@moduledoc false
|
||||
|
||||
def defrecord(name, fields, opts) do
|
||||
block = Keyword.get(opts, :do, nil)
|
||||
record_check!(fields)
|
||||
|
||||
quote do
|
||||
unquoted_fields = unquote(fields)
|
||||
|
||||
defmodule unquote(name) do
|
||||
import Record.DSL
|
||||
|
||||
@record_fields []
|
||||
@record_types []
|
||||
|
||||
Record.Deprecated.deffunctions(unquoted_fields, __ENV__)
|
||||
value = unquote(block)
|
||||
Record.Deprecated.deftypes(@record_fields, @record_types, __ENV__)
|
||||
value
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defp record_check!([{field, {:::, _, [_, _]}}|_]) when is_atom(field) do
|
||||
raise ArgumentError, message: "typespecs are not supported inlined with defrecord, " <>
|
||||
"please use record_type instead"
|
||||
end
|
||||
|
||||
defp record_check!([_|t]), do: record_check!(t)
|
||||
defp record_check!(_), do: :ok
|
||||
|
||||
def defrecordp(name, tag, fields) do
|
||||
case recordp_split(fields, [], [], false) do
|
||||
{:ok, fields, types, def_type} ->
|
||||
types = Macro.escape(types)
|
||||
|
||||
# bind_quoted isn't available when bootstrapping record
|
||||
quoted = quote [unquote: false] do
|
||||
Record.Deprecated.defmacros(name, fields, __ENV__, tag)
|
||||
|
||||
if def_type do
|
||||
type = binary_to_atom(atom_to_binary(name) <> "_t")
|
||||
@typep unquote(type)() :: {unquote(tag || name), unquote_splicing(types)}
|
||||
end
|
||||
end
|
||||
|
||||
quote do
|
||||
def_type = unquote(def_type)
|
||||
fields = unquote(fields)
|
||||
types = unquote(types)
|
||||
tag = unquote(tag)
|
||||
name = unquote(name)
|
||||
unquote(quoted)
|
||||
end
|
||||
|
||||
:error ->
|
||||
quote do
|
||||
name = unquote(name)
|
||||
Record.Deprecated.defmacros(name, unquote(fields), __ENV__, unquote(tag))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defp recordp_split([{field, {:::, _, [default, type]}}|t], defaults, types, _) do
|
||||
recordp_split(t, [{field, default}|defaults], [type|types], true)
|
||||
end
|
||||
|
||||
defp recordp_split([other|t], defaults, types, def_type) do
|
||||
recordp_split(t, [other|defaults], [quote(do: term)|types], def_type)
|
||||
end
|
||||
|
||||
defp recordp_split([], defaults, types, def_type) do
|
||||
{:ok, :lists.reverse(defaults), :lists.reverse(types), def_type}
|
||||
end
|
||||
|
||||
defp recordp_split(_, _, _, _) do
|
||||
:error
|
||||
end
|
||||
|
||||
def deffunctions(values, env) do
|
||||
values = for value <- values, do: convert_value(value)
|
||||
escaped = Macro.escape(values)
|
||||
|
||||
contents = [
|
||||
reflection(escaped),
|
||||
initializer(escaped),
|
||||
conversions(values),
|
||||
record_optimizable(),
|
||||
updater(values),
|
||||
accessors(values, 1),
|
||||
switch_recorder()
|
||||
]
|
||||
|
||||
contents = [quote(do: @record_fields unquote(escaped))|contents]
|
||||
|
||||
# Special case for bootstrapping purposes
|
||||
if env == Macro.Env do
|
||||
Module.eval_quoted(env, contents, [], [])
|
||||
else
|
||||
Module.eval_quoted(env.module, contents, [], Macro.Env.location(env))
|
||||
end
|
||||
end
|
||||
|
||||
def deftypes(values, types, env) do
|
||||
types = types || []
|
||||
values = for value <- values do
|
||||
{name, default} = convert_value(value)
|
||||
{name, default, find_spec(types, name)}
|
||||
end
|
||||
|
||||
contents = [
|
||||
core_specs(values),
|
||||
accessor_specs(values, 1, [])
|
||||
]
|
||||
|
||||
# We need to handle bootstraping
|
||||
cond do
|
||||
:code.ensure_loaded(Kernel.Typespec) != {:module, Kernel.Typespec} ->
|
||||
nil
|
||||
env == Macro.Env ->
|
||||
Module.eval_quoted(env, contents, [], [])
|
||||
true ->
|
||||
Module.eval_quoted(env.module, contents, [], Macro.Env.location(env))
|
||||
end
|
||||
end
|
||||
|
||||
def defmacros(name, values, env, tag \\ nil)
|
||||
when is_atom(name) and is_list(values) and is_atom(tag) do
|
||||
|
||||
escaped = for value <- values do
|
||||
{key, value} = convert_value(value)
|
||||
{key, Macro.escape(value)}
|
||||
end
|
||||
|
||||
tag = tag || name
|
||||
|
||||
contents = quote do
|
||||
defmacrop unquote(name)() do
|
||||
Record.Deprecated.access(unquote(tag), unquote(escaped), [], __CALLER__)
|
||||
end
|
||||
|
||||
defmacrop unquote(name)(args) do
|
||||
Record.Deprecated.access(unquote(tag), unquote(escaped), args, __CALLER__)
|
||||
end
|
||||
|
||||
defmacrop unquote(name)(record, args) do
|
||||
Record.Deprecated.dispatch(unquote(tag), unquote(escaped), record, args, __CALLER__)
|
||||
end
|
||||
end
|
||||
|
||||
Module.eval_quoted(env.module, contents, [], Macro.Env.location(env))
|
||||
end
|
||||
|
||||
## Callbacks
|
||||
|
||||
defmacro __before_compile__(_) do
|
||||
quote do
|
||||
def __record__(:optimizable), do: @record_optimizable
|
||||
end
|
||||
end
|
||||
|
||||
def __on_definition__(env, kind, name, args, _guards, _body) do
|
||||
tuple = {name, length(args)}
|
||||
module = env.module
|
||||
functions = Module.get_attribute(module, :record_optimizable)
|
||||
|
||||
functions =
|
||||
if kind in [:def] and Module.get_attribute(module, :record_optimized) do
|
||||
[tuple|functions]
|
||||
else
|
||||
List.delete(functions, tuple)
|
||||
end
|
||||
|
||||
Module.put_attribute(module, :record_optimizable, functions)
|
||||
end
|
||||
|
||||
def access(atom, fields, arg, _caller) when is_atom(arg) do
|
||||
if index = find_index(fields, arg, 0) do
|
||||
index + 1
|
||||
else
|
||||
raise ArgumentError, message: "record #{inspect atom} does not have the key: #{inspect arg}"
|
||||
end
|
||||
end
|
||||
|
||||
def access(atom, fields, keyword, caller) do
|
||||
unless is_keyword(keyword) do
|
||||
raise ArgumentError, message: "expected contents inside brackets to be a keyword list or an atom, got: #{inspect keyword}"
|
||||
end
|
||||
|
||||
in_match = Macro.Env.in_match?(caller)
|
||||
|
||||
has_underscore_value = Keyword.has_key?(keyword, :_)
|
||||
underscore_value = Keyword.get(keyword, :_, {:_, [], nil})
|
||||
keyword = Keyword.delete keyword, :_
|
||||
|
||||
iterator = fn({field, default}, each_keyword) ->
|
||||
new_fields =
|
||||
case Keyword.has_key?(each_keyword, field) do
|
||||
true -> Keyword.get(each_keyword, field)
|
||||
false ->
|
||||
case in_match or has_underscore_value do
|
||||
true -> underscore_value
|
||||
false -> Macro.escape(default)
|
||||
end
|
||||
end
|
||||
|
||||
{new_fields, Keyword.delete(each_keyword, field)}
|
||||
end
|
||||
|
||||
{match, remaining} = :lists.mapfoldl(iterator, keyword, fields)
|
||||
|
||||
case remaining do
|
||||
[] ->
|
||||
{:{}, [], [atom|match]}
|
||||
_ ->
|
||||
keys = for {key, _} <- remaining, do: key
|
||||
raise ArgumentError, message: "record #{inspect atom} does not have the key: #{inspect hd(keys)}"
|
||||
end
|
||||
end
|
||||
|
||||
def dispatch(atom, fields, record, args, caller) do
|
||||
cond do
|
||||
is_atom(args) ->
|
||||
get(atom, fields, record, args)
|
||||
is_keyword(args) ->
|
||||
update(atom, fields, record, args, caller)
|
||||
true ->
|
||||
raise ArgumentError, message: "expected arguments to be a compile time atom or keywords"
|
||||
end
|
||||
end
|
||||
|
||||
defp update(atom, fields, var, keyword, caller) do
|
||||
unless is_keyword(keyword) do
|
||||
raise ArgumentError, message: "expected arguments to be compile time keywords"
|
||||
end
|
||||
|
||||
if Macro.Env.in_match?(caller) do
|
||||
raise ArgumentError, message: "cannot invoke update style macro inside match context"
|
||||
end
|
||||
|
||||
Enum.reduce keyword, var, fn({key, value}, acc) ->
|
||||
index = find_index(fields, key, 0)
|
||||
if index do
|
||||
quote do
|
||||
:erlang.setelement(unquote(index + 2), unquote(acc), unquote(value))
|
||||
end
|
||||
else
|
||||
raise ArgumentError, message: "record #{inspect atom} does not have the key: #{inspect key}"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defp get(atom, fields, var, key) do
|
||||
index = find_index(fields, key, 0)
|
||||
if index do
|
||||
quote do
|
||||
:erlang.element(unquote(index + 2), unquote(var))
|
||||
end
|
||||
else
|
||||
raise ArgumentError, message: "record #{inspect atom} does not have the key: #{inspect key}"
|
||||
end
|
||||
end
|
||||
|
||||
## Function generation
|
||||
|
||||
defp reflection(values) do
|
||||
quoted = for {k, _} <- values do
|
||||
index = find_index(values, k, 0)
|
||||
quote do
|
||||
def __record__(:index, unquote(k)), do: unquote(index + 1)
|
||||
end
|
||||
end
|
||||
|
||||
quote do
|
||||
unquote(quoted)
|
||||
|
||||
@doc false
|
||||
def __record__(:index, _), do: nil
|
||||
@doc false
|
||||
def __record__(:index, arg, _), do: __record__(:index, arg)
|
||||
|
||||
@doc false
|
||||
def __record__(kind, _), do: __record__(kind)
|
||||
|
||||
@doc false
|
||||
def __record__(:name), do: __MODULE__
|
||||
def __record__(:fields), do: unquote(values)
|
||||
end
|
||||
end
|
||||
|
||||
defp initializer(values) do
|
||||
defaults = for {_, value} <- values, do: value
|
||||
|
||||
# For each value, define a piece of code that will receive
|
||||
# an ordered dict of options (opts) and it will try to fetch
|
||||
# the given key from the ordered dict, falling back to the
|
||||
# default value if one does not exist.
|
||||
atom_selective = for {k, v} <- values, do: initialize_lookup(k, v)
|
||||
string_selective = for {k, v} <- values, do: initialize_lookup(atom_to_binary(k), v)
|
||||
|
||||
quote do
|
||||
@doc false
|
||||
def new(), do: new([])
|
||||
|
||||
@doc false
|
||||
def new([]), do: {__MODULE__, unquote_splicing(defaults)}
|
||||
def new([{key, _}|_] = opts) when is_atom(key), do: {__MODULE__, unquote_splicing(atom_selective)}
|
||||
def new([{key, _}|_] = opts) when is_binary(key), do: {__MODULE__, unquote_splicing(string_selective)}
|
||||
end
|
||||
end
|
||||
|
||||
defp initialize_lookup(k, v) do
|
||||
quote do
|
||||
case :lists.keyfind(unquote(k), 1, opts) do
|
||||
false -> unquote(v)
|
||||
{_, v} -> v
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defp conversions(values) do
|
||||
sorted = for {k, _} <- values do
|
||||
index = find_index(values, k, 0)
|
||||
{k, quote(do: :erlang.element(unquote(index + 2), record))}
|
||||
end
|
||||
|
||||
quote do
|
||||
@doc false
|
||||
def to_keywords(record) do
|
||||
unquote(sorted)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defp accessors([{:__exception__, _}|t], 1) do
|
||||
accessors(t, 2)
|
||||
end
|
||||
|
||||
defp accessors([{key, _default}|t], i) do
|
||||
update = binary_to_atom "update_" <> atom_to_binary(key)
|
||||
|
||||
contents = quote do
|
||||
@doc false
|
||||
def unquote(key)(record) do
|
||||
:erlang.element(unquote(i + 1), record)
|
||||
end
|
||||
|
||||
@doc false
|
||||
def unquote(key)(value, record) do
|
||||
:erlang.setelement(unquote(i + 1), record, value)
|
||||
end
|
||||
|
||||
@doc false
|
||||
def unquote(update)(function, record) do
|
||||
:erlang.setelement(unquote(i + 1), record,
|
||||
function.(:erlang.element(unquote(i + 1), record)))
|
||||
end
|
||||
end
|
||||
|
||||
[contents|accessors(t, i + 1)]
|
||||
end
|
||||
|
||||
defp accessors([], _i) do
|
||||
[]
|
||||
end
|
||||
|
||||
# Define an updater method that receives a
|
||||
# keyword list and updates the record.
|
||||
defp updater(values) do
|
||||
atom_fields =
|
||||
for {key, _default} <- values, do: updater_lookup(key, key, values)
|
||||
|
||||
string_fields =
|
||||
for {key, _default} <- values, do: updater_lookup(atom_to_binary(key), key, values)
|
||||
|
||||
atom_contents = quote do: {__MODULE__, unquote_splicing(atom_fields)}
|
||||
string_contents = quote do: {__MODULE__, unquote_splicing(string_fields)}
|
||||
|
||||
quote do
|
||||
@doc false
|
||||
def update([], record) do
|
||||
record
|
||||
end
|
||||
|
||||
def update([{key, _}|_] = keywords, record) when is_atom(key) do
|
||||
unquote(atom_contents)
|
||||
end
|
||||
def update([{key, _}|_] = keywords, record) when is_binary(key) do
|
||||
unquote(string_contents)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defp updater_lookup(k, key, values) do
|
||||
index = find_index(values, key, 0)
|
||||
|
||||
quote do
|
||||
case :lists.keyfind(unquote(k), 1, keywords) do
|
||||
false -> :erlang.element(unquote(index + 2), record)
|
||||
{_, value} -> value
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defp record_optimizable do
|
||||
quote do
|
||||
@record_optimized true
|
||||
@record_optimizable []
|
||||
@before_compile {unquote(__MODULE__), :__before_compile__}
|
||||
@on_definition {unquote(__MODULE__), :__on_definition__}
|
||||
end
|
||||
end
|
||||
|
||||
defp switch_recorder do
|
||||
quote do: @record_optimized false
|
||||
end
|
||||
|
||||
## Types/specs generation
|
||||
|
||||
defp core_specs(values) do
|
||||
types = for {_, _, spec} <- values, do: spec
|
||||
options = for {k, _, v} <- values, do: {k, v}
|
||||
|
||||
quote do
|
||||
unless Kernel.Typespec.defines_type?(__MODULE__, :t, 0) do
|
||||
@type t :: {__MODULE__, unquote_splicing(types)}
|
||||
end
|
||||
|
||||
unless Kernel.Typespec.defines_type?(__MODULE__, :options, 0) do
|
||||
@type options :: unquote(options) | [{String.t, term}]
|
||||
end
|
||||
|
||||
@spec new :: t
|
||||
@spec new(options) :: t
|
||||
@spec to_keywords(t) :: options
|
||||
@spec update(options, t) :: t
|
||||
@spec __record__(:name) :: atom
|
||||
@spec __record__(:fields) :: [{atom, any}]
|
||||
@spec __record__(:index, atom) :: non_neg_integer | nil
|
||||
end
|
||||
end
|
||||
|
||||
defp accessor_specs([{:__exception__, _, _}|t], 1, acc) do
|
||||
accessor_specs(t, 2, acc)
|
||||
end
|
||||
|
||||
defp accessor_specs([{key, _default, spec}|t], i, acc) do
|
||||
update = binary_to_atom "update_" <> atom_to_binary(key)
|
||||
|
||||
contents = quote do
|
||||
@spec unquote(key)(t) :: unquote(spec)
|
||||
@spec unquote(key)(unquote(spec), t) :: t
|
||||
@spec unquote(update)((unquote(spec) -> unquote(spec)), t) :: t
|
||||
end
|
||||
|
||||
accessor_specs(t, i + 1, [contents | acc])
|
||||
end
|
||||
|
||||
defp accessor_specs([], _i, acc), do: acc
|
||||
|
||||
## Helpers
|
||||
|
||||
defp is_keyword(list) when is_list(list), do: :lists.all(&is_keyword_tuple/1, list)
|
||||
defp is_keyword(_), do: false
|
||||
|
||||
defp is_keyword_tuple({x, _}) when is_atom(x), do: true
|
||||
defp is_keyword_tuple(_), do: false
|
||||
|
||||
defp convert_value(atom) when is_atom(atom), do: {atom, nil}
|
||||
|
||||
defp convert_value({atom, other}) when is_atom(atom), do:
|
||||
{atom, check_value(atom, other)}
|
||||
|
||||
defp convert_value({field, _}), do:
|
||||
raise(ArgumentError, message: "record field name has to be an atom, got #{inspect field}")
|
||||
|
||||
defp check_value(atom, other) when is_list(other) do
|
||||
for(i <- other, do: check_value(atom, i))
|
||||
other
|
||||
end
|
||||
|
||||
defp check_value(atom, other) when is_tuple(other) do
|
||||
for(i <- tuple_to_list(other), do: check_value(atom, i))
|
||||
other
|
||||
end
|
||||
|
||||
defp check_value(atom, other) when is_function(other) do
|
||||
unless :erlang.fun_info(other, :env) == {:env, []} and
|
||||
:erlang.fun_info(other, :type) == {:type, :external} do
|
||||
raise ArgumentError, message: "record field default value #{inspect atom} can only contain " <>
|
||||
"functions that point to an existing &Mod.fun/arity"
|
||||
end
|
||||
other
|
||||
end
|
||||
|
||||
defp check_value(atom, other) when is_reference(other) or is_pid(other) or is_port(other) do
|
||||
raise(ArgumentError, message: "record field default value #{inspect atom} cannot contain a reference, pid or port")
|
||||
end
|
||||
|
||||
defp check_value(_atom, other), do: other
|
||||
|
||||
defp find_index([{k, _}|_], k, i), do: i
|
||||
defp find_index([{_, _}|t], k, i), do: find_index(t, k, i + 1)
|
||||
defp find_index([], _k, _i), do: nil
|
||||
|
||||
defp find_spec(types, name) do
|
||||
matches = for {k, v} <- types, name == k, do: v
|
||||
case matches do
|
||||
[h|_] -> h
|
||||
_ -> quote do: term
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defmodule Record.DSL do
|
||||
@moduledoc false
|
||||
|
||||
defmacro record_type(opts) when is_list(opts) do
|
||||
escaped = for {k, v} <- opts, do: {k, Macro.escape(v)}
|
||||
|
||||
quote do
|
||||
@record_types Keyword.merge(@record_types || [], unquote(escaped))
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -4,7 +4,7 @@ defmodule Record.Extractor do
|
||||
# Retrieve a record definition from an Erlang file using
|
||||
# the same lookup as the *include* attribute from Erlang modules.
|
||||
def extract(name, from: file) when is_binary(file) do
|
||||
file = List.from_char_data!(file)
|
||||
file = String.to_char_list(file)
|
||||
|
||||
realfile =
|
||||
case :code.where_is_file(file) do
|
||||
@@ -18,11 +18,11 @@ defmodule Record.Extractor do
|
||||
# Retrieve a record definition from an Erlang file using
|
||||
# the same lookup as the *include_lib* attribute from Erlang modules.
|
||||
def extract(name, from_lib: file) when is_binary(file) do
|
||||
[app|path] = :filename.split(List.from_char_data!(file))
|
||||
[app|path] = :filename.split(String.to_char_list(file))
|
||||
|
||||
case :code.lib_dir(list_to_atom(app)) do
|
||||
case :code.lib_dir(List.to_atom(app)) do
|
||||
{:error, _} ->
|
||||
raise ArgumentError, message: "lib file #{file} could not be found"
|
||||
raise ArgumentError, "lib file #{file} could not be found"
|
||||
libpath ->
|
||||
extract_record name, :filename.join([libpath|path])
|
||||
end
|
||||
@@ -35,7 +35,7 @@ defmodule Record.Extractor do
|
||||
if record = List.keyfind(records, name, 0) do
|
||||
parse_record(record, form)
|
||||
else
|
||||
raise ArgumentError, message: "no record #{name} found at #{file}"
|
||||
raise ArgumentError, "no record #{name} found at #{file}"
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
+61
-59
@@ -22,55 +22,64 @@ defmodule Regex do
|
||||
|
||||
The modifiers available when creating a Regex are:
|
||||
|
||||
* `unicode` (u) - enables unicode specific patterns like \p. it expects valid unicode
|
||||
strings to be given on match
|
||||
* `caseless` (i) - add case insensitivity
|
||||
* `dotall` (s) - causes dot to match newlines and also set newline to anycrlf.
|
||||
The new line setting can be overridden by setting `(*CR)` or `(*LF)` or
|
||||
`(*CRLF)` or `(*ANY)` according to re documentation
|
||||
* `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
|
||||
newline, though the matched text may continue over the newline
|
||||
* `ungreedy` (r) - inverts the "greediness" of the regexp
|
||||
* `unicode` (u) - enables unicode specific patterns like `\p`; it expects
|
||||
valid unicode strings to be given on match
|
||||
|
||||
* `caseless` (i) - add case insensitivity
|
||||
|
||||
* `dotall` (s) - causes dot to match newlines and also set newline to
|
||||
anycrlf; the new line setting can be overridden by setting `(*CR)` or
|
||||
`(*LF)` or `(*CRLF)` or `(*ANY)` according to re documentation
|
||||
|
||||
* `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 newline, though the matched text may continue over the newline
|
||||
|
||||
* `ungreedy` (r) - inverts the "greediness" of the regexp
|
||||
|
||||
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)`
|
||||
or `(*ANY)` at the beginning of the regexp according to the re documentation
|
||||
* `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
|
||||
|
||||
## Captures
|
||||
|
||||
Many functions in this module allows what to capture in a regex
|
||||
match via the `:capture` option. The supported values are:
|
||||
|
||||
* `:all` - all captured subpatterns including the complete matching string.
|
||||
This is the default;
|
||||
* `:all` - all captured subpatterns including the complete matching string
|
||||
(this is the default)
|
||||
|
||||
* `:first` - only the first captured subpattern, which is always the complete
|
||||
matching part of the string. All explicitly captured subpatterns are
|
||||
discarded;
|
||||
* `:first` - only the first captured subpattern, which is always the
|
||||
complete matching part of the string; all explicitly captured subpatterns
|
||||
are discarded
|
||||
|
||||
* `:all_but_first`- all but the first matching subpattern, i.e. all explicitly
|
||||
captured subpatterns, but not the complete matching part of
|
||||
the string;
|
||||
* `:all_but_first`- all but the first matching subpattern, i.e. all
|
||||
explicitly captured subpatterns, but not the complete matching part of
|
||||
the string
|
||||
|
||||
* `:none` - do not return matching subpatterns at all;
|
||||
* `:none` - do not return matching subpatterns at all
|
||||
|
||||
* `:all_names` - captures all names in the Regex;
|
||||
* `:all_names` - captures all names in the Regex
|
||||
|
||||
* `list(binary)` - a list of named captures to capture;
|
||||
* `list(binary)` - a list of named captures to capture
|
||||
|
||||
"""
|
||||
|
||||
defstruct re_pattern: nil :: term, source: "" :: binary, opts: "" :: binary
|
||||
|
||||
defexception CompileError, message: "regex could not be compiled"
|
||||
defmodule CompileError do
|
||||
defexception message: "regex could not be compiled"
|
||||
end
|
||||
|
||||
@doc """
|
||||
Compiles the regular expression.
|
||||
@@ -165,9 +174,9 @@ defmodule Regex do
|
||||
|
||||
## Options
|
||||
|
||||
* `:return` - Set to `:index` to return indexes. Defaults to `:binary`;
|
||||
* `:capture` - What to capture in the result. Check the moduledoc for Regex
|
||||
to see the possible capture values;
|
||||
* `:return` - set to `:index` to return indexes. Defaults to `:binary`.
|
||||
* `:capture` - what to capture in the result. Check the moduledoc for `Regex`
|
||||
to see the possible capture values.
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -273,9 +282,9 @@ defmodule Regex do
|
||||
|
||||
## Options
|
||||
|
||||
* `:return` - Set to `:index` to return indexes. Defaults to `:binary`;
|
||||
* `:capture` - What to capture in the result. Check the moduledoc for Regex
|
||||
to see the possible capture values;
|
||||
* `:return` - set to `:index` to return indexes. Defaults to `:binary`.
|
||||
* `:capture` - what to capture in the result. Check the moduledoc for `Regex`
|
||||
to see the possible capture values.
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -308,13 +317,12 @@ defmodule Regex do
|
||||
|
||||
## Options
|
||||
|
||||
* `:parts` - when specified, splits the string into the
|
||||
given number of parts. If not specified, `:parts`
|
||||
is defaulted to `:infinity`, which will split the
|
||||
string into the maximum number of parts possible
|
||||
based on the given pattern.
|
||||
* `:parts` - when specified, splits the string into the given number of
|
||||
parts. If not specified, `:parts` is defaulted to `:infinity`, which will
|
||||
split the string into the maximum number of parts possible based on the
|
||||
given pattern.
|
||||
|
||||
* `:trim` - when true, remove blank strings from the result;
|
||||
* `:trim` - when true, remove blank strings from the result.
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -338,17 +346,8 @@ defmodule Regex do
|
||||
def split(regex, string, options \\ [])
|
||||
|
||||
def split(%Regex{re_pattern: compiled}, string, options) when is_binary(string) do
|
||||
if options[:global] != nil do
|
||||
IO.write :stderr, "Support for :global in Regex.split/3 is deprecated, please use :parts instead\n#{Exception.format_stacktrace}"
|
||||
end
|
||||
parts =
|
||||
cond do
|
||||
Keyword.get(options, :global) == false -> 2
|
||||
p = Keyword.get(options, :parts) -> if p == 0, do: :infinity, else: p
|
||||
true -> :infinity
|
||||
end
|
||||
|
||||
opts = [return: :binary, parts: parts]
|
||||
parts = Keyword.get(options, :parts, :infinity)
|
||||
opts = [return: :binary, parts: zero_to_infinity(parts)]
|
||||
splits = :re.split(string, compiled, opts)
|
||||
|
||||
if Keyword.get(options, :trim, false) do
|
||||
@@ -358,6 +357,9 @@ defmodule Regex do
|
||||
end
|
||||
end
|
||||
|
||||
defp zero_to_infinity(0), do: :infinity
|
||||
defp zero_to_infinity(n), do: n
|
||||
|
||||
@doc ~S"""
|
||||
Receives a regex, a binary and a replacement, returns a new
|
||||
binary where the all matches are replaced by replacement.
|
||||
@@ -374,8 +376,8 @@ defmodule Regex do
|
||||
|
||||
## Options
|
||||
|
||||
* `:global` - when `false`, replaces only the first occurrence
|
||||
(defaults to true)
|
||||
* `:global` - when `false`, replaces only the first occurrence
|
||||
(defaults to true)
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -414,9 +416,9 @@ defmodule Regex do
|
||||
:nomatch ->
|
||||
string
|
||||
{:match, [mlist|t]} when is_list(mlist) ->
|
||||
apply_list(string, replacement, [mlist|t]) |> iodata_to_binary
|
||||
apply_list(string, replacement, [mlist|t]) |> IO.iodata_to_binary
|
||||
{:match, slist} ->
|
||||
apply_list(string, replacement, [slist]) |> iodata_to_binary
|
||||
apply_list(string, replacement, [slist]) |> IO.iodata_to_binary
|
||||
end
|
||||
end
|
||||
|
||||
@@ -434,7 +436,7 @@ defmodule Regex do
|
||||
|
||||
defp precompile_replacement(<<?\\, rest :: binary>>) when byte_size(rest) > 0 do
|
||||
{ns, rest} = pick_int(rest)
|
||||
[list_to_integer(ns) | precompile_replacement(rest)]
|
||||
[List.to_integer(ns) | precompile_replacement(rest)]
|
||||
end
|
||||
|
||||
defp precompile_replacement(<<x, rest :: binary>>) do
|
||||
@@ -488,7 +490,7 @@ defmodule Regex do
|
||||
end
|
||||
|
||||
defp apply_replace(string, repl, indexes) do
|
||||
indexes = list_to_tuple(indexes)
|
||||
indexes = List.to_tuple(indexes)
|
||||
|
||||
for part <- repl do
|
||||
cond do
|
||||
|
||||
@@ -50,9 +50,6 @@ defmodule Set do
|
||||
case unquote(set) do
|
||||
%{__struct__: x} when is_atom(x) ->
|
||||
x
|
||||
x when is_tuple(x) ->
|
||||
IO.write :stderr, "Using records with the Set module is deprecated, please use structs instead\n#{Exception.format_stacktrace}"
|
||||
elem(x, 0)
|
||||
x ->
|
||||
unsupported_set(x)
|
||||
end
|
||||
@@ -334,6 +331,6 @@ defmodule Set do
|
||||
end
|
||||
|
||||
defp unsupported_set(set) do
|
||||
raise ArgumentError, message: "unsupported set: #{inspect set}"
|
||||
raise ArgumentError, "unsupported set: #{inspect set}"
|
||||
end
|
||||
end
|
||||
|
||||
+85
-72
@@ -76,8 +76,8 @@ defmodule Stream do
|
||||
There are many functions in Elixir's standard library that return
|
||||
streams, some examples are:
|
||||
|
||||
* `IO.stream/2` - Streams input lines, one by one;
|
||||
* `URI.query_decoder/1` - Decodes a query string, pair by pair;
|
||||
* `IO.stream/2` - streams input lines, one by one
|
||||
* `URI.query_decoder/1` - decodes a query string, pair by pair
|
||||
|
||||
This module also provides many convenience functions for creating streams,
|
||||
like `Stream.cycle/1`, `Stream.unfold/2`, `Stream.resource/3` and more.
|
||||
@@ -89,55 +89,7 @@ defmodule Stream do
|
||||
returns an anonymous function may return a struct in future releases.
|
||||
"""
|
||||
|
||||
defmodule Lazy do
|
||||
@moduledoc false
|
||||
defstruct enum: nil, funs: [], accs: [], done: nil
|
||||
end
|
||||
|
||||
defimpl Enumerable, for: Lazy do
|
||||
@compile :inline_list_funs
|
||||
|
||||
def reduce(lazy, acc, fun) do
|
||||
do_reduce(lazy, acc, fn x, [acc] ->
|
||||
{reason, acc} = fun.(x, acc)
|
||||
{reason, [acc]}
|
||||
end)
|
||||
end
|
||||
|
||||
def count(_lazy) do
|
||||
{:error, __MODULE__}
|
||||
end
|
||||
|
||||
def member?(_lazy, _value) do
|
||||
{:error, __MODULE__}
|
||||
end
|
||||
|
||||
defp do_reduce(%Lazy{enum: enum, funs: funs, accs: accs, done: done}, acc, fun) do
|
||||
composed = :lists.foldl(fn fun, acc -> fun.(acc) end, fun, funs)
|
||||
do_each(&Enumerable.reduce(enum, &1, composed),
|
||||
done && {done, fun}, :lists.reverse(accs), acc)
|
||||
end
|
||||
|
||||
defp do_each(reduce, done, accs, {command, acc}) do
|
||||
case reduce.({command, [acc|accs]}) do
|
||||
{:suspended, [acc|accs], continuation} ->
|
||||
{:suspended, acc, &do_each(continuation, done, accs, &1)}
|
||||
{:halted, [acc|_]} ->
|
||||
{:halted, acc}
|
||||
{:done, [acc|_] = accs} ->
|
||||
case done do
|
||||
nil ->
|
||||
{:done, acc}
|
||||
{done, fun} ->
|
||||
case done.(fun).(accs) do
|
||||
{:cont, [acc|_]} -> {:done, acc}
|
||||
{:halt, [acc|_]} -> {:halted, acc}
|
||||
{:suspend, [acc|_]} -> {:suspended, acc, &({:done, elem(&1, 1)})}
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
defstruct enum: nil, funs: [], accs: [], done: nil
|
||||
|
||||
@type acc :: any
|
||||
@type element :: any
|
||||
@@ -468,6 +420,7 @@ defmodule Stream do
|
||||
stream = File.stream!("code")
|
||||
|> Stream.map(&String.replace(&1, "#", "%"))
|
||||
|> Stream.into(File.stream!("new"))
|
||||
|> Stream.run
|
||||
|
||||
No computation will be done until we call one of the Enum functions
|
||||
or `Stream.run/1`.
|
||||
@@ -538,7 +491,7 @@ defmodule Stream do
|
||||
|
||||
"""
|
||||
@spec take(Enumerable.t, non_neg_integer) :: Enumerable.t
|
||||
def take(_enum, 0), do: %Lazy{enum: []}
|
||||
def take(_enum, 0), do: %Stream{enum: []}
|
||||
|
||||
def take(enum, n) when n > 0 do
|
||||
lazy enum, n, fn(f1) -> R.take(f1) end
|
||||
@@ -590,7 +543,7 @@ defmodule Stream do
|
||||
lazy enum, n, fn(f1) -> R.take_every(n, f1) end
|
||||
end
|
||||
|
||||
def take_every(_enum, 0), do: %Lazy{enum: []}
|
||||
def take_every(_enum, 0), do: %Stream{enum: []}
|
||||
|
||||
@doc """
|
||||
Lazily takes elements of the enumerable while the given
|
||||
@@ -656,8 +609,9 @@ defmodule Stream do
|
||||
user.(val, user_acc)
|
||||
catch
|
||||
kind, reason ->
|
||||
stacktrace = System.stacktrace
|
||||
next.({:halt, next_acc})
|
||||
:erlang.raise(kind, reason, :erlang.get_stacktrace)
|
||||
:erlang.raise(kind, reason, stacktrace)
|
||||
else
|
||||
{[], user_acc} ->
|
||||
do_transform(user_acc, user, fun, next_acc, next, inner_acc, inner)
|
||||
@@ -700,8 +654,9 @@ defmodule Stream do
|
||||
next.({:halt, next_acc})
|
||||
{:halted, h}
|
||||
kind, reason ->
|
||||
stacktrace = System.stacktrace
|
||||
next.({:halt, next_acc})
|
||||
:erlang.raise(kind, reason, :erlang.get_stacktrace)
|
||||
:erlang.raise(kind, reason, stacktrace)
|
||||
else
|
||||
{_, acc} ->
|
||||
do_transform(user_acc, user, fun, next_acc, next, {:cont, acc}, inner)
|
||||
@@ -835,8 +790,9 @@ defmodule Stream do
|
||||
do_zip(zips, acc, callback, [], [])
|
||||
catch
|
||||
kind, reason ->
|
||||
stacktrace = System.stacktrace
|
||||
do_zip_close(zips)
|
||||
:erlang.raise(kind, reason, :erlang.get_stacktrace)
|
||||
:erlang.raise(kind, reason, stacktrace)
|
||||
else
|
||||
{:next, buffer, acc} ->
|
||||
do_zip(buffer, acc, callback)
|
||||
@@ -856,7 +812,7 @@ defmodule Stream do
|
||||
end
|
||||
|
||||
defp do_zip([], acc, callback, list, buffer) do
|
||||
zipped = list_to_tuple(:lists.reverse(list))
|
||||
zipped = List.to_tuple(:lists.reverse(list))
|
||||
{:next, :lists.reverse(buffer), callback.(zipped, acc)}
|
||||
end
|
||||
|
||||
@@ -886,8 +842,9 @@ defmodule Stream do
|
||||
@spec cycle(Enumerable.t) :: Enumerable.t
|
||||
def cycle(enumerable) do
|
||||
fn acc, fun ->
|
||||
reduce = &Enumerable.reduce(enumerable, &1, fun)
|
||||
do_cycle(reduce, reduce, acc)
|
||||
inner = &do_cycle_each(&1, &2, fun)
|
||||
outer = &Enumerable.reduce(enumerable, &1, inner)
|
||||
do_cycle(outer, outer, acc)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -900,16 +857,26 @@ defmodule Stream do
|
||||
end
|
||||
|
||||
defp do_cycle(reduce, cycle, acc) do
|
||||
case reduce.(acc) do
|
||||
{:done, acc} ->
|
||||
do_cycle(cycle, cycle, {:cont, acc})
|
||||
{:halted, acc} ->
|
||||
try do
|
||||
reduce.(acc)
|
||||
catch
|
||||
{:stream_cycle, acc} ->
|
||||
{:halted, acc}
|
||||
else
|
||||
{state, acc} when state in [:done, :halted] ->
|
||||
do_cycle(cycle, cycle, {:cont, acc})
|
||||
{:suspended, acc, continuation} ->
|
||||
{:suspended, acc, &do_cycle(continuation, cycle, &1)}
|
||||
end
|
||||
end
|
||||
|
||||
defp do_cycle_each(x, acc, f) do
|
||||
case f.(x, acc) do
|
||||
{:halt, h} -> throw({:stream_cycle, h})
|
||||
{_, _} = o -> o
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Emit a sequence of values, starting with `start_value`. Successive
|
||||
values are generated by calling `next_fun` on the previous value.
|
||||
@@ -1006,8 +973,9 @@ defmodule Stream do
|
||||
end
|
||||
catch
|
||||
kind, reason ->
|
||||
stacktrace = System.stacktrace
|
||||
after_fun.(next_acc)
|
||||
:erlang.raise(kind, reason, :erlang.get_stacktrace)
|
||||
:erlang.raise(kind, reason, stacktrace)
|
||||
else
|
||||
nil ->
|
||||
after_fun.(next_acc)
|
||||
@@ -1021,7 +989,7 @@ defmodule Stream do
|
||||
Emits a sequence of values for the given accumulator.
|
||||
|
||||
Successive values are generated by calling `next_fun` with the previous
|
||||
accumulator and it must return a tuple with the current and next
|
||||
accumulator and it must return a tuple with the current value and next
|
||||
accumulator. The enumeration finishes if it returns `nil`.
|
||||
|
||||
## Examples
|
||||
@@ -1045,7 +1013,7 @@ defmodule Stream do
|
||||
|
||||
defp do_unfold(next_acc, next_fun, {:cont, acc}, fun) do
|
||||
case next_fun.(next_acc) do
|
||||
nil -> {:done, acc}
|
||||
nil -> {:done, acc}
|
||||
{v, next_acc} -> do_unfold(next_acc, next_fun, fun.(v, acc), fun)
|
||||
end
|
||||
end
|
||||
@@ -1054,18 +1022,63 @@ defmodule Stream do
|
||||
|
||||
@compile {:inline, lazy: 2, lazy: 3, lazy: 4}
|
||||
|
||||
defp lazy(%Lazy{funs: funs} = lazy, fun),
|
||||
defp lazy(%Stream{funs: funs} = lazy, fun),
|
||||
do: %{lazy | funs: [fun|funs] }
|
||||
defp lazy(enum, fun),
|
||||
do: %Lazy{enum: enum, funs: [fun]}
|
||||
do: %Stream{enum: enum, funs: [fun]}
|
||||
|
||||
defp lazy(%Lazy{funs: funs, accs: accs} = lazy, acc, fun),
|
||||
defp lazy(%Stream{funs: funs, accs: accs} = lazy, acc, fun),
|
||||
do: %{lazy | funs: [fun|funs], accs: [acc|accs] }
|
||||
defp lazy(enum, acc, fun),
|
||||
do: %Lazy{enum: enum, funs: [fun], accs: [acc]}
|
||||
do: %Stream{enum: enum, funs: [fun], accs: [acc]}
|
||||
|
||||
defp lazy(%Lazy{done: nil, funs: funs, accs: accs} = lazy, acc, fun, done),
|
||||
defp lazy(%Stream{done: nil, funs: funs, accs: accs} = lazy, acc, fun, done),
|
||||
do: %{lazy | funs: [fun|funs], accs: [acc|accs], done: done}
|
||||
defp lazy(enum, acc, fun, done),
|
||||
do: %Lazy{enum: enum, funs: [fun], accs: [acc], done: done}
|
||||
do: %Stream{enum: enum, funs: [fun], accs: [acc], done: done}
|
||||
end
|
||||
|
||||
defimpl Enumerable, for: Stream do
|
||||
@compile :inline_list_funs
|
||||
|
||||
def reduce(lazy, acc, fun) do
|
||||
do_reduce(lazy, acc, fn x, [acc] ->
|
||||
{reason, acc} = fun.(x, acc)
|
||||
{reason, [acc]}
|
||||
end)
|
||||
end
|
||||
|
||||
def count(_lazy) do
|
||||
{:error, __MODULE__}
|
||||
end
|
||||
|
||||
def member?(_lazy, _value) do
|
||||
{:error, __MODULE__}
|
||||
end
|
||||
|
||||
defp do_reduce(%Stream{enum: enum, funs: funs, accs: accs, done: done}, acc, fun) do
|
||||
composed = :lists.foldl(fn fun, acc -> fun.(acc) end, fun, funs)
|
||||
do_each(&Enumerable.reduce(enum, &1, composed),
|
||||
done && {done, fun}, :lists.reverse(accs), acc)
|
||||
end
|
||||
|
||||
defp do_each(reduce, done, accs, {command, acc}) do
|
||||
case reduce.({command, [acc|accs]}) do
|
||||
{:suspended, [acc|accs], continuation} ->
|
||||
{:suspended, acc, &do_each(continuation, done, accs, &1)}
|
||||
{:halted, [acc|_]} ->
|
||||
{:halted, acc}
|
||||
{:done, [acc|_] = accs} ->
|
||||
case done do
|
||||
nil ->
|
||||
{:done, acc}
|
||||
{done, fun} ->
|
||||
case done.(fun).(accs) do
|
||||
{:cont, [acc|_]} -> {:done, acc}
|
||||
{:halt, [acc|_]} -> {:halted, acc}
|
||||
{:suspend, [acc|_]} -> {:suspended, acc, &({:done, elem(&1, 1)})}
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
+200
-143
@@ -15,15 +15,11 @@ defmodule String do
|
||||
operations that work directly with binaries. Some
|
||||
of those can be found in the `Kernel` module, as:
|
||||
|
||||
* `Kernel.binary_part/3` - retrieves part of the binary
|
||||
* `Kernel.bit_size/1` and `Kernel.byte_size/1` - size related functions
|
||||
* `Kernel.is_bitstring/1` and `Kernel.is_binary/1` - type checking function
|
||||
* Plus a number of conversion functions, like `Kernel.binary_to_atom/1`,
|
||||
`Kernel.binary_to_integer/2`, `Kernel.binary_to_term/1` and their inverses,
|
||||
like `Kernel.integer_to_binary/2`
|
||||
|
||||
Finally, the [`:binary` module](http://erlang.org/doc/man/binary.html)
|
||||
provides a few other functions that work on the byte level.
|
||||
* `Kernel.binary_part/3` - retrieves part of the binary
|
||||
* `Kernel.bit_size/1` and `Kernel.byte_size/1` - size related functions
|
||||
* `Kernel.is_bitstring/1` and `Kernel.is_binary/1` - type checking function
|
||||
* Plus a number of functions for working with binaries (bytes)
|
||||
[in the `:binary` module](http://erlang.org/doc/man/binary.html)
|
||||
|
||||
## Codepoints and graphemes
|
||||
|
||||
@@ -147,6 +143,7 @@ defmodule String do
|
||||
def printable?(<<?\b, t :: binary>>), do: printable?(t)
|
||||
def printable?(<<?\f, t :: binary>>), do: printable?(t)
|
||||
def printable?(<<?\e, t :: binary>>), do: printable?(t)
|
||||
def printable?(<<?\d, t :: binary>>), do: printable?(t)
|
||||
def printable?(<<?\a, t :: binary>>), do: printable?(t)
|
||||
|
||||
def printable?(<<>>), do: true
|
||||
@@ -172,8 +169,9 @@ defmodule String do
|
||||
defdelegate split(binary), to: String.Unicode
|
||||
|
||||
@doc ~S"""
|
||||
Divides a string into substrings based on a pattern,
|
||||
returning a list of these substrings. The pattern can
|
||||
Divides a string into substrings based on a pattern.
|
||||
|
||||
Returns a list of these substrings. The pattern can
|
||||
be a string, a list of strings or a regular expression.
|
||||
|
||||
The string is split into as many parts as possible by
|
||||
@@ -236,22 +234,16 @@ defmodule String do
|
||||
def split(binary, "", options), do: split(binary, ~r""u, options)
|
||||
|
||||
def split(binary, pattern, options) do
|
||||
if options[:global] != nil do
|
||||
IO.write :stderr, "Support for :global in String.split/3 is deprecated, please use :parts instead\n#{Exception.format_stacktrace}"
|
||||
end
|
||||
if Regex.regex?(pattern) do
|
||||
Regex.split(pattern, binary, options)
|
||||
else
|
||||
splits = case options[:parts] do
|
||||
num when is_number(num) and num > 0 ->
|
||||
split_parts(binary, pattern, num - 1)
|
||||
num ->
|
||||
if options[:global] != false or num == 0 do
|
||||
splits =
|
||||
case Keyword.get(options, :parts, :infinity) do
|
||||
num when is_number(num) and num > 0 ->
|
||||
split_parts(binary, pattern, num - 1)
|
||||
_ ->
|
||||
:binary.split(binary, pattern, [:global])
|
||||
else
|
||||
:binary.split(binary, pattern, [])
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if Keyword.get(options, :trim, false) do
|
||||
for split <- splits, split != "", do: split
|
||||
@@ -266,10 +258,9 @@ defmodule String do
|
||||
defp split_parts("", _pattern, _num, parts), do: Enum.reverse([""|parts])
|
||||
defp split_parts(binary, _pattern, 0, parts), do: Enum.reverse([binary|parts])
|
||||
defp split_parts(binary, pattern, num, parts) do
|
||||
[head|tail] = :binary.split(binary, pattern)
|
||||
case tail do
|
||||
[] -> Enum.reverse([head|parts])
|
||||
[str] -> split_parts(str, pattern, num - 1, [head|parts])
|
||||
case :binary.split(binary, pattern) do
|
||||
[head] -> Enum.reverse([head|parts])
|
||||
[head, rest] -> split_parts(rest, pattern, num - 1, [head|parts])
|
||||
end
|
||||
end
|
||||
|
||||
@@ -654,7 +645,7 @@ defmodule String do
|
||||
do_reverse(next_grapheme(rest), [grapheme|acc])
|
||||
end
|
||||
|
||||
defp do_reverse(nil, acc), do: iodata_to_binary(acc)
|
||||
defp do_reverse(nil, acc), do: IO.iodata_to_binary(acc)
|
||||
|
||||
@doc """
|
||||
Returns a binary `subject` duplicated `n` times.
|
||||
@@ -776,6 +767,65 @@ defmodule String do
|
||||
def valid_character?(<<_ :: utf8>> = codepoint), do: valid?(codepoint)
|
||||
def valid_character?(_), do: false
|
||||
|
||||
@doc ~S"""
|
||||
Splits the string into chunks of characters that share a common trait.
|
||||
|
||||
The trait can be one of two options:
|
||||
|
||||
* `:valid` – the string is split into chunks of valid and invalid character
|
||||
sequences
|
||||
|
||||
* `:printable` – the string is split into chunks of printable and
|
||||
non-printable character sequences
|
||||
|
||||
Returns a list of binaries each of which contains only one kind of
|
||||
characters.
|
||||
|
||||
If the given string is empty, an empty list is returned.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> String.chunk(<<?a, ?b, ?c, 0>>, :valid)
|
||||
["abc\000"]
|
||||
|
||||
iex> String.chunk(<<?a, ?b, ?c, 0, 0x0ffff::utf8>>, :valid)
|
||||
["abc\000", <<0x0ffff::utf8>>]
|
||||
|
||||
iex> String.chunk(<<?a, ?b, ?c, 0, 0x0ffff::utf8>>, :printable)
|
||||
["abc", <<0, 0x0ffff::utf8>>]
|
||||
|
||||
"""
|
||||
@spec chunk(t, :valid | :printable) :: [t]
|
||||
|
||||
def chunk(string, trait)
|
||||
|
||||
def chunk("", _), do: []
|
||||
|
||||
def chunk(str, trait) when trait in [:valid, :printable] do
|
||||
{cp, _} = next_codepoint(str)
|
||||
pred_fn = make_chunk_pred(trait)
|
||||
do_chunk(str, pred_fn.(cp), pred_fn)
|
||||
end
|
||||
|
||||
|
||||
defp do_chunk(str, flag, pred_fn), do: do_chunk(str, [], <<>>, flag, pred_fn)
|
||||
|
||||
defp do_chunk(<<>>, acc, <<>>, _, _), do: Enum.reverse(acc)
|
||||
|
||||
defp do_chunk(<<>>, acc, chunk, _, _), do: Enum.reverse(acc, [chunk])
|
||||
|
||||
defp do_chunk(str, acc, chunk, flag, pred_fn) do
|
||||
{cp, rest} = next_codepoint(str)
|
||||
if pred_fn.(cp) != flag do
|
||||
do_chunk(rest, [chunk|acc], cp, not flag, pred_fn)
|
||||
else
|
||||
do_chunk(rest, acc, chunk <> cp, flag, pred_fn)
|
||||
end
|
||||
end
|
||||
|
||||
defp make_chunk_pred(:valid), do: &valid?/1
|
||||
defp make_chunk_pred(:printable), do: &printable?/1
|
||||
|
||||
@doc """
|
||||
Returns unicode graphemes in the string as per Extended Grapheme
|
||||
Cluster algorithm outlined in the [Unicode Standard Annex #29,
|
||||
@@ -877,7 +927,7 @@ defmodule String do
|
||||
|
||||
@doc """
|
||||
Returns the grapheme in the `position` of the given utf8 `string`.
|
||||
If `position` is greater than `string` length, than it returns `nil`.
|
||||
If `position` is greater than `string` length, then it returns `nil`.
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -924,7 +974,7 @@ defmodule String do
|
||||
@doc """
|
||||
Returns a substring starting at the offset given by the first, and
|
||||
a length given by the second.
|
||||
If the offset is greater than string length, than it returns `nil`.
|
||||
If the offset is greater than string length, then it returns `""`.
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -935,13 +985,13 @@ defmodule String do
|
||||
"lixir"
|
||||
|
||||
iex> String.slice("elixir", 10, 3)
|
||||
nil
|
||||
""
|
||||
|
||||
iex> String.slice("elixir", -4, 4)
|
||||
"ixir"
|
||||
|
||||
iex> String.slice("elixir", -10, 3)
|
||||
nil
|
||||
""
|
||||
|
||||
iex> String.slice("a", 0, 1500)
|
||||
"a"
|
||||
@@ -950,16 +1000,13 @@ defmodule String do
|
||||
""
|
||||
|
||||
iex> String.slice("a", 2, 1500)
|
||||
nil
|
||||
""
|
||||
|
||||
"""
|
||||
@spec slice(t, integer, integer) :: grapheme | nil
|
||||
@spec slice(t, integer, integer) :: grapheme
|
||||
|
||||
def slice(string, start, 0) do
|
||||
case abs(start) <= length(string) do
|
||||
true -> ""
|
||||
false -> nil
|
||||
end
|
||||
def slice(_, _, 0) do
|
||||
""
|
||||
end
|
||||
|
||||
def slice(string, start, len) when start >= 0 and len >= 0 do
|
||||
@@ -970,7 +1017,7 @@ defmodule String do
|
||||
real_start_pos = length(string) - abs(start)
|
||||
case real_start_pos >= 0 do
|
||||
true -> do_slice(next_grapheme(string), real_start_pos, real_start_pos + len - 1, 0, "")
|
||||
false -> nil
|
||||
false -> ""
|
||||
end
|
||||
end
|
||||
|
||||
@@ -979,7 +1026,7 @@ defmodule String do
|
||||
range to the offset given by the end of the range.
|
||||
|
||||
If the start of the range is not a valid offset for the given
|
||||
string or if the range is in reverse order, returns `nil`.
|
||||
string or if the range is in reverse order, returns `""`.
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -990,7 +1037,7 @@ defmodule String do
|
||||
"lixir"
|
||||
|
||||
iex> String.slice("elixir", 10..3)
|
||||
nil
|
||||
""
|
||||
|
||||
iex> String.slice("elixir", -4..-1)
|
||||
"ixir"
|
||||
@@ -1002,10 +1049,10 @@ defmodule String do
|
||||
"ixir"
|
||||
|
||||
iex> String.slice("elixir", -1..-4)
|
||||
nil
|
||||
""
|
||||
|
||||
iex> String.slice("elixir", -10..-7)
|
||||
nil
|
||||
""
|
||||
|
||||
iex> String.slice("a", 0..1500)
|
||||
"a"
|
||||
@@ -1014,10 +1061,10 @@ defmodule String do
|
||||
""
|
||||
|
||||
iex> String.slice("a", 2..1500)
|
||||
nil
|
||||
""
|
||||
|
||||
"""
|
||||
@spec slice(t, Range.t) :: t | nil
|
||||
@spec slice(t, Range.t) :: t
|
||||
|
||||
def slice(string, range)
|
||||
|
||||
@@ -1038,11 +1085,13 @@ defmodule String do
|
||||
|
||||
if first >= 0 do
|
||||
do_slice(next_grapheme(string), first, last, 0, "")
|
||||
else
|
||||
""
|
||||
end
|
||||
end
|
||||
|
||||
defp do_slice(_, start_pos, last_pos, _, _) when start_pos > last_pos do
|
||||
nil
|
||||
""
|
||||
end
|
||||
|
||||
defp do_slice({_, rest}, start_pos, last_pos, current_pos, acc) when current_pos < start_pos do
|
||||
@@ -1056,16 +1105,9 @@ defmodule String do
|
||||
defp do_slice({char, _}, start_pos, last_pos, current_pos, acc) when current_pos >= start_pos and current_pos == last_pos do
|
||||
acc <> char
|
||||
end
|
||||
|
||||
defp do_slice(nil, start_pos, _, current_pos, acc) when start_pos == current_pos do
|
||||
acc
|
||||
end
|
||||
|
||||
|
||||
defp do_slice(nil, _, _, _, acc) do
|
||||
case acc do
|
||||
"" -> nil
|
||||
_ -> acc
|
||||
end
|
||||
acc
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -1102,10 +1144,6 @@ defmodule String do
|
||||
Kernel.match?({0, _}, :binary.match(string, prefix))
|
||||
end
|
||||
|
||||
defp do_starts_with(_, _) do
|
||||
raise ArgumentError
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns `true` if `string` ends with any of the suffixes given, otherwise
|
||||
`false`. `suffixes` can be either a single suffix or a list of suffixes.
|
||||
@@ -1137,16 +1175,12 @@ defmodule String do
|
||||
end
|
||||
|
||||
defp do_ends_with(string, suffix) when is_binary(suffix) do
|
||||
string_size = size(string)
|
||||
suffix_size = size(suffix)
|
||||
string_size = byte_size(string)
|
||||
suffix_size = byte_size(suffix)
|
||||
scope = {string_size - suffix_size, suffix_size}
|
||||
(suffix_size <= string_size) and (:nomatch != :binary.match(string, suffix, [scope: scope]))
|
||||
end
|
||||
|
||||
defp do_ends_with(_, _) do
|
||||
raise ArgumentError
|
||||
end
|
||||
|
||||
@doc """
|
||||
Check if `string` matches the given regular expression.
|
||||
|
||||
@@ -1199,89 +1233,22 @@ defmodule String do
|
||||
:nomatch != :binary.match(string, match)
|
||||
end
|
||||
|
||||
defp do_contains(_, _) do
|
||||
raise ArgumentError
|
||||
end
|
||||
|
||||
@doc false
|
||||
def to_char_list(list) do
|
||||
IO.write :stderr, "String.to_char_list/1 is deprecated, please use List.from_char_data/1 instead\n#{Exception.format_stacktrace}"
|
||||
List.from_char_data(list)
|
||||
end
|
||||
|
||||
@doc false
|
||||
def to_char_list!(list) do
|
||||
IO.write :stderr, "String.to_char_list!/1 is deprecated, please use List.from_char_data!/1 instead\n#{Exception.format_stacktrace}"
|
||||
List.from_char_data!(list)
|
||||
end
|
||||
|
||||
@doc false
|
||||
def from_char_list(list) do
|
||||
IO.write :stderr, "String.from_char_list/1 is deprecated, please use String.from_char_data/1 instead\n#{Exception.format_stacktrace}"
|
||||
from_char_data(list)
|
||||
end
|
||||
|
||||
@doc false
|
||||
def from_char_list!(list) do
|
||||
IO.write :stderr, "String.from_char_list!/1 is deprecated, please use String.from_char_data!/1 instead\n#{Exception.format_stacktrace}"
|
||||
from_char_data!(list)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Converts char data (a list of integers and strings) into a string.
|
||||
|
||||
If a string is given, returns the string itself.
|
||||
Converts a string into a char list.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> String.from_char_data([0x00E6, 0x00DF])
|
||||
{:ok, "æß"}
|
||||
|
||||
iex> String.from_char_data([0x0061, "bc"])
|
||||
{:ok, "abc"}
|
||||
iex> String.to_char_list("æß")
|
||||
'æß'
|
||||
|
||||
Notice that this function expect a list of integer representing
|
||||
UTF-8 codepoints. If you have a raw binary, you must instead use
|
||||
[the `:binary` module](http://erlang.org/doc/man/binary.html).
|
||||
"""
|
||||
@spec from_char_data(char_data) :: {:ok, String.t} | {:error, binary, binary} | {:incomplete, binary, binary}
|
||||
def from_char_data(binary) when is_binary(binary) do
|
||||
binary
|
||||
end
|
||||
|
||||
def from_char_data(list) when is_list(list) do
|
||||
case :unicode.characters_to_binary(list) do
|
||||
result when is_binary(result) ->
|
||||
{:ok, result}
|
||||
|
||||
{:error, _, _} = error ->
|
||||
error
|
||||
|
||||
{:incomplete, _, _} = incomplete ->
|
||||
incomplete
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Converts char data (a list of integers and strings) into a string.
|
||||
|
||||
In case the conversion fails, it raises a `UnicodeConversionError`.
|
||||
If a string is given, returns the string itself.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> String.from_char_data!([0x00E6, 0x00DF])
|
||||
"æß"
|
||||
|
||||
iex> String.from_char_data!([0x0061, "bc"])
|
||||
"abc"
|
||||
|
||||
"""
|
||||
@spec from_char_data!(char_data) :: String.t | no_return
|
||||
def from_char_data!(binary) when is_binary(binary) do
|
||||
binary
|
||||
end
|
||||
|
||||
def from_char_data!(list) when is_list(list) do
|
||||
case :unicode.characters_to_binary(list) do
|
||||
result when is_binary(result) ->
|
||||
@spec to_char_list(t) :: char_list
|
||||
def to_char_list(string) when is_binary(string) do
|
||||
case :unicode.characters_to_list(string) do
|
||||
result when is_list(result) ->
|
||||
result
|
||||
|
||||
{:error, encoded, rest} ->
|
||||
@@ -1291,4 +1258,94 @@ defmodule String do
|
||||
raise UnicodeConversionError, encoded: encoded, rest: rest, kind: :incomplete
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Converts a string to an atom.
|
||||
|
||||
Currently Elixir does not support conversions from strings
|
||||
which contains Unicode codepoints greater than 0xFF.
|
||||
|
||||
Inlined by the compiler.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> String.to_atom("my_atom")
|
||||
:my_atom
|
||||
|
||||
"""
|
||||
@spec to_atom(String.t) :: atom
|
||||
def to_atom(string) do
|
||||
:erlang.binary_to_atom(string, :utf8)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Converts a string to an existing atom.
|
||||
|
||||
Currently Elixir does not support conversions from strings
|
||||
which contains Unicode codepoints greater than 0xFF.
|
||||
|
||||
Inlined by the compiler.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> :my_atom
|
||||
iex> String.to_existing_atom("my_atom")
|
||||
:my_atom
|
||||
|
||||
iex> String.to_existing_atom("this_atom_will_never_exist")
|
||||
** (ArgumentError) argument error
|
||||
|
||||
"""
|
||||
@spec to_existing_atom(String.t) :: atom
|
||||
def to_existing_atom(string) do
|
||||
:erlang.binary_to_existing_atom(string, :utf8)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns a integer whose text representation is `string`.
|
||||
|
||||
Inlined by the compiler.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> String.to_integer("123")
|
||||
123
|
||||
|
||||
"""
|
||||
@spec to_integer(String.t) :: integer
|
||||
def to_integer(string) do
|
||||
:erlang.binary_to_integer(string)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns an integer whose text representation is `string` in base `base`.
|
||||
|
||||
Inlined by the compiler.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> String.to_integer("3FF", 16)
|
||||
1023
|
||||
|
||||
"""
|
||||
@spec to_integer(String.t, pos_integer) :: integer
|
||||
def to_integer(string, base) do
|
||||
:erlang.binary_to_integer(string, base)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns a float whose text representation is `string`.
|
||||
|
||||
Inlined by the compiler.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> String.to_float("2.2017764e+0")
|
||||
2.2017764
|
||||
|
||||
"""
|
||||
@spec to_float(String.t) :: float
|
||||
def to_float(string) do
|
||||
:erlang.binary_to_float(string)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -18,23 +18,16 @@ defprotocol String.Chars do
|
||||
end
|
||||
|
||||
defimpl String.Chars, for: Atom do
|
||||
@doc """
|
||||
Convert the atom literally to a binary, except
|
||||
`nil` which is converted to an empty string.
|
||||
"""
|
||||
def to_string(nil) do
|
||||
""
|
||||
end
|
||||
|
||||
def to_string(atom) do
|
||||
atom_to_binary(atom)
|
||||
Atom.to_string(atom)
|
||||
end
|
||||
end
|
||||
|
||||
defimpl String.Chars, for: BitString do
|
||||
@doc """
|
||||
Returns the given binary or raises an error for bitstrings.
|
||||
"""
|
||||
def to_string(thing) when is_binary(thing) do
|
||||
thing
|
||||
end
|
||||
@@ -48,38 +41,17 @@ defimpl String.Chars, for: BitString do
|
||||
end
|
||||
|
||||
defimpl String.Chars, for: List do
|
||||
@doc """
|
||||
Consider the list is an iolist and converts it
|
||||
to a binary. This allows a list of binaries, or
|
||||
a charlist, or a mix of both, to be converted
|
||||
successfully.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> to_string('foo')
|
||||
"foo"
|
||||
|
||||
iex> to_string(["foo", 'bar'])
|
||||
"foobar"
|
||||
|
||||
"""
|
||||
def to_string(char_list), do: String.from_char_data!(char_list)
|
||||
def to_string(char_list), do: List.to_string(char_list)
|
||||
end
|
||||
|
||||
defimpl String.Chars, for: Integer do
|
||||
@doc """
|
||||
Simply converts the integer to a string.
|
||||
"""
|
||||
def to_string(thing) do
|
||||
integer_to_binary(thing)
|
||||
Integer.to_string(thing)
|
||||
end
|
||||
end
|
||||
|
||||
defimpl String.Chars, for: Float do
|
||||
@doc """
|
||||
Simply converts the float to a string.
|
||||
"""
|
||||
def to_string(thing) do
|
||||
iodata_to_binary(:io_lib_format.fwrite_g(thing))
|
||||
IO.iodata_to_binary(:io_lib_format.fwrite_g(thing))
|
||||
end
|
||||
end
|
||||
|
||||
@@ -10,7 +10,7 @@ defmodule StringIO do
|
||||
|
||||
"""
|
||||
|
||||
use GenServer.Behaviour
|
||||
use GenServer
|
||||
|
||||
@doc """
|
||||
Creates an IO device.
|
||||
@@ -106,12 +106,12 @@ defmodule StringIO do
|
||||
end
|
||||
|
||||
defp io_request({:put_chars, chars}, %{output: output} = s) do
|
||||
{:ok, %{s | output: << output :: binary, String.from_char_data!(chars) :: binary >>}}
|
||||
{:ok, %{s | output: << output :: binary, IO.chardata_to_string(chars) :: binary >>}}
|
||||
end
|
||||
|
||||
defp io_request({:put_chars, m, f, as}, %{output: output} = s) do
|
||||
chars = apply(m, f, as)
|
||||
{:ok, %{s | output: << output :: binary, String.from_char_data!(chars) :: binary >>}}
|
||||
{:ok, %{s | output: << output :: binary, IO.chardata_to_string(chars) :: binary >>}}
|
||||
end
|
||||
|
||||
defp io_request({:put_chars, _encoding, chars}, s) do
|
||||
@@ -183,7 +183,7 @@ defmodule StringIO do
|
||||
{error, s}
|
||||
{result, input} ->
|
||||
if capture_prompt do
|
||||
output = << output :: binary, String.from_char_data!(prompt) :: binary >>
|
||||
output = << output :: binary, IO.chardata_to_string(prompt) :: binary >>
|
||||
end
|
||||
|
||||
{result, %{s | input: input, output: output}}
|
||||
@@ -199,7 +199,7 @@ defmodule StringIO do
|
||||
end
|
||||
|
||||
defp do_get_chars(input, :latin1, n) do
|
||||
<< chars :: [ binary, size(n) ], rest :: binary >> = input
|
||||
<<chars :: [binary, size(n)], rest :: binary>> = input
|
||||
{chars, rest}
|
||||
end
|
||||
|
||||
@@ -209,7 +209,7 @@ defmodule StringIO do
|
||||
{buf_count, split_pos} when buf_count < n or split_pos == :none ->
|
||||
{input, ""}
|
||||
{_buf_count, split_pos} ->
|
||||
<< chars :: [ binary, size(split_pos) ], rest :: binary >> = input
|
||||
<<chars :: [binary, size(split_pos)], rest :: binary>> = input
|
||||
{chars, rest}
|
||||
end
|
||||
catch
|
||||
@@ -231,7 +231,7 @@ defmodule StringIO do
|
||||
{result, input} = do_get_line(chars, encoding)
|
||||
|
||||
if capture_prompt do
|
||||
output = << output :: binary, String.from_char_data!(prompt) :: binary >>
|
||||
output = << output :: binary, IO.chardata_to_string(prompt) :: binary >>
|
||||
end
|
||||
|
||||
{result, %{s | input: input, output: output}}
|
||||
@@ -261,7 +261,7 @@ defmodule StringIO do
|
||||
{result, input, count} = do_get_until(chars, encoding, mod, fun, args)
|
||||
|
||||
if capture_prompt do
|
||||
output = << output :: binary, :binary.copy(String.from_char_data!(prompt), count) :: binary >>
|
||||
output = << output :: binary, :binary.copy(IO.chardata_to_string(prompt), count) :: binary >>
|
||||
end
|
||||
|
||||
input =
|
||||
@@ -335,6 +335,6 @@ defmodule StringIO do
|
||||
send from, {:io_reply, reply_as, reply}
|
||||
end
|
||||
|
||||
defp to_reply(list) when is_list(list), do: String.from_char_data!(list)
|
||||
defp to_reply(list) when is_list(list), do: IO.chardata_to_string(list)
|
||||
defp to_reply(other), do: other
|
||||
end
|
||||
|
||||
@@ -0,0 +1,389 @@
|
||||
defmodule Supervisor do
|
||||
@moduledoc """
|
||||
A behaviour module for implementing supervision functionality.
|
||||
|
||||
A supervisor is a process which supervises other processes called
|
||||
child processes. Supervisors are used to build an hierarchical process
|
||||
structure called a supervision tree, a nice way to structure fault-tolerant
|
||||
applications.
|
||||
|
||||
A supervisor implemented using this module will have a standard set
|
||||
of interface functions and include functionality for tracing and error
|
||||
reporting. It will also fit into an supervision tree.
|
||||
|
||||
## Example
|
||||
|
||||
In order to define a supervisor, we need to first define a child process
|
||||
that is going to be supervised. In order to do so, we will define a GenServer
|
||||
that represents a stack:
|
||||
|
||||
defmodule Stack do
|
||||
use GenServer
|
||||
|
||||
def start_link(state) do
|
||||
GenServer.start_link(__MODULE__, state, [name: :sup_stack])
|
||||
end
|
||||
|
||||
def handle_call(:pop, _from, [h|t]) do
|
||||
{:reply, h, t}
|
||||
end
|
||||
|
||||
def handle_cast({:push, h}, _from, t) do
|
||||
{:noreply, [h|t]}
|
||||
end
|
||||
end
|
||||
|
||||
We can now define our supervisor and start it as follows:
|
||||
|
||||
# Import helpers for defining supervisors
|
||||
import Supervisor.Spec
|
||||
|
||||
# We are going to supervise the Stack server which will
|
||||
# be started with a single argument [:hello]
|
||||
children = [
|
||||
worker(Stack, [[:hello]])
|
||||
]
|
||||
|
||||
# Start the supervisor with our one child
|
||||
{:ok, pid} = Supervisor.start_link(children, strategy: :one_for_one)
|
||||
|
||||
Notice that when starting the GenServer, we have registered it
|
||||
with name `:sup_stack`, which allows us to call it directly and
|
||||
get what is on the stack:
|
||||
|
||||
GenServer.call(:sup_stack, :pop)
|
||||
#=> :hello
|
||||
|
||||
GenServer.cast(:sup_stack, {:push, :world})
|
||||
#=> :ok
|
||||
|
||||
GenServer.call(:sup_stack, :pop)
|
||||
#=> :world
|
||||
|
||||
However, there is a bug in our stack server. If we call `:pop` and
|
||||
the stack is empty, it is going to crash because no clause matches.
|
||||
Let's try it:
|
||||
|
||||
GenServer.call(:sup_stack, :pop)
|
||||
=ERROR REPORT====
|
||||
|
||||
Luckily, since the server is being supervised by a supervisor, the
|
||||
supervisor will automatically start a new one, with the default stack
|
||||
of `[:hello]` like before:
|
||||
|
||||
GenServer.call(:sup_stack, :pop) == :hello
|
||||
|
||||
Supervisors support different strategies; in the example above, we
|
||||
have chosen `:one_for_one`. Furthermore, each supervisor can have many
|
||||
workers and supervisors as children, each of them with their specific
|
||||
configuration, shutdown values, and restart strategies.
|
||||
|
||||
Continue reading this moduledoc to learn more about supervision strategies
|
||||
and then follow to the `Supervisor.Spec` module documentation to learn
|
||||
about the specification for workers and supervisors.
|
||||
|
||||
## Module-based supervisors
|
||||
|
||||
In the example above, a supervisor was dynamically created by passing
|
||||
the supervision structure to `start_link/2`. However, supervisors
|
||||
can also be created by explicitly defining a supervision module:
|
||||
|
||||
defmodule MyApp.Supervisor do
|
||||
use Supervisor
|
||||
|
||||
def start_link do
|
||||
Supervisor.start_link(__MODULE__, [])
|
||||
end
|
||||
|
||||
def init([]) do
|
||||
children = [
|
||||
worker(Stack, [[:hello]])
|
||||
]
|
||||
|
||||
supervise(children, strategy: :one_for_one)
|
||||
end
|
||||
end
|
||||
|
||||
You may want to use a module-based supervisor if:
|
||||
|
||||
* You need to do some particular action on supervisor
|
||||
initialization, like setting up a ETS table.
|
||||
|
||||
* You want to perform partial hot-code swapping of the
|
||||
tree. For example, if you add or remove a children,
|
||||
the module-based supervision will add and remove the
|
||||
new children directly, while the dynamic supervision
|
||||
requires the whole tree to be restarted in order to
|
||||
perform such swaps.
|
||||
|
||||
## Strategies
|
||||
|
||||
* `:one_for_one` - if a child process terminates, only that
|
||||
process is restarted.
|
||||
|
||||
* `:one_for_all` - if a child process terminates, all other child
|
||||
processes are terminated and then all child processes (including
|
||||
the terminated one) are restarted.
|
||||
|
||||
* `:rest_for_one` - if a child process terminates, the "rest" of
|
||||
the child processes, i.e. the child processes after the terminated
|
||||
one in start order, are terminated. Then the terminated child
|
||||
process and the rest of the child processes are restarted.
|
||||
|
||||
* `:simple_one_for_one` - similar to `:one_for_one` but suits better
|
||||
when dynamically attaching children. This strategy requires the
|
||||
supervisor specification to contain only one children. Many functions
|
||||
in this module behave slightly differently when this strategy is
|
||||
used.
|
||||
|
||||
## Name Registration
|
||||
|
||||
A supervisor is bound to the same name registration rules as a `GenServer`.
|
||||
Read more about it in the `GenServer` docs.
|
||||
"""
|
||||
|
||||
@doc false
|
||||
defmacro __using__(_) do
|
||||
quote location: :keep do
|
||||
@behaviour :supervisor
|
||||
import Supervisor.Spec
|
||||
end
|
||||
end
|
||||
|
||||
@typedoc "Return values of `start_link` functions"
|
||||
@type on_start :: {:ok, pid} | :ignore |
|
||||
{:error, {:already_started, pid} | {:shutdown, term} | term}
|
||||
|
||||
@typedoc "Return values of `start_child` functions"
|
||||
@type on_start_child :: {:ok, child} | {:ok, child, info :: term} |
|
||||
{:error, {:already_started, child} | :already_present | term}
|
||||
|
||||
@type child :: pid | :undefined
|
||||
|
||||
@typedoc "The Supervisor name"
|
||||
@type name :: atom | {:global, term} | {:via, module, term}
|
||||
|
||||
@typedoc "Options used by the `start*` functions"
|
||||
@type options :: [name: name,
|
||||
strategy: Supervisor.Spec.strategy,
|
||||
max_restarts: non_neg_integer,
|
||||
max_seconds: non_neg_integer]
|
||||
|
||||
@typedoc "The supervisor reference"
|
||||
@type supervisor :: pid | name | {atom, node}
|
||||
|
||||
@doc """
|
||||
Starts a supervisor with the given children.
|
||||
|
||||
A strategy is required to be given as an option. Furthermore,
|
||||
the `:max_restarts` and `:max_seconds` value can be configured
|
||||
as described in `Supervisor.Spec.supervise/2` docs.
|
||||
|
||||
The options can also be used to register a supervisor name.
|
||||
the supported values are described under the `Name Registration`
|
||||
section in the `GenServer` module docs.
|
||||
|
||||
If the supervisor and its child processes are successfully created
|
||||
(i.e. if the start function of all child processes returns `{:ok, child}`,
|
||||
`{:ok, child, info}`, or `:ignore`) the function returns
|
||||
`{:ok, pid}`, where `pid` is the pid of the supervisor. If there
|
||||
already exists a process with the specified name, the function returns
|
||||
`{:error, {:already_started, pid}}`, where pid is the pid of that
|
||||
process.
|
||||
|
||||
If any of the child process start functions fail or return an error tuple or
|
||||
an erroneous value, the supervisor will first terminate all already
|
||||
started child processes with reason `:shutdown` and then terminate
|
||||
itself and return `{:error, {:shutdown, reason}}`.
|
||||
|
||||
Note that the `Supervisor` is linked to the parent process
|
||||
and will exit not only on crashes but also if the parent process
|
||||
exits with `:normal` reason.
|
||||
"""
|
||||
@spec start_link([tuple], options) :: on_start
|
||||
def start_link(children, options) when is_list(children) do
|
||||
spec = Supervisor.Spec.supervise(children, options)
|
||||
start_link(Supervisor.Default, spec, options)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Starts a supervisor module with the given `arg`.
|
||||
|
||||
To start the supervisor, the `init/1` callback will be invoked
|
||||
in the given module. The `init/1` callback must return a
|
||||
supervision specification which can be created with the help
|
||||
of `Supervisor.Spec` module.
|
||||
|
||||
If the `init/1` callback returns `:ignore`, this function returns
|
||||
`:ignore` as well and the supervisor terminates with reason `:normal`.
|
||||
If it fails or returns an incorrect value, this function returns
|
||||
`{:error, term}` where `term` is a term with information about the
|
||||
error, and the supervisor terminates with reason `term`.
|
||||
|
||||
The `:name` option can also be given in order to register a supervisor
|
||||
name, the supported values are described under the `Name Registration`
|
||||
section in the `GenServer` module docs.
|
||||
|
||||
Other failure conditions are specified in `start_link/2` docs.
|
||||
"""
|
||||
@spec start_link(module, term, options) :: on_start
|
||||
def start_link(module, arg, options \\ []) when is_list(options) do
|
||||
case Keyword.get(options, :name) do
|
||||
nil ->
|
||||
:supervisor.start_link(module, arg)
|
||||
atom when is_atom(atom) ->
|
||||
:supervisor.start_link({:local, atom}, module, arg)
|
||||
other when is_tuple(other) ->
|
||||
:supervisor.start_link(other, module, arg)
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Dynamically adds and starts a child specification to the supervisor.
|
||||
|
||||
`child_spec` should be a valid child specification (unless the supervisor
|
||||
is a `:simple_one_for_one` supervisor, see below). The child process will
|
||||
be started as defined in the child specification.
|
||||
|
||||
In the case of `:simple_one_for_one`, the child specification defined in
|
||||
the supervisor will be used and instead of a `child_spec`, an arbitrary list
|
||||
of terms is expected. The child process will then be started by appending
|
||||
the given list to the existing function arguments in the child specification.
|
||||
|
||||
If there already exists a child specification with the specified id,
|
||||
`child_spec` is discarded and the function returns an error with `:already_started`
|
||||
or `:already_present` if the corresponding child process is running or not.
|
||||
|
||||
If the child process start function returns `{:ok, child}` or `{:ok, child, info}`,
|
||||
the child specification and pid is added to the supervisor and the function returns
|
||||
the same value.
|
||||
|
||||
If the child process start function returns `:ignore, the child specification is
|
||||
added to the supervisor, the pid is set to undefined and the function returns
|
||||
`{:ok, :undefined}`.
|
||||
|
||||
If the child process start function returns an error tuple or an erroneous value,
|
||||
or if it fails, the child specification is discarded and the function returns
|
||||
`{:error, error}` where `error` is a term containing information about the error
|
||||
and child specification.
|
||||
"""
|
||||
@spec start_child(supervisor, Supervisor.Spec.spec | [term]) :: on_start_child
|
||||
defdelegate start_child(supervisor, child_spec_or_args), to: :supervisor
|
||||
|
||||
@doc """
|
||||
Terminates the given pid or child id.
|
||||
|
||||
If the supervisor is not a `simple_one_for_one`, the child id is expected
|
||||
and the process, if there is one, is terminated; the child specification is
|
||||
kept unless the child is temporary.
|
||||
|
||||
In case of a `simple_one_for_one` supervisor, a pid is expected. If the child
|
||||
specification identifier is given instead of a `pid`, the function will
|
||||
return `{:error, :simple_one_for_one}`.
|
||||
|
||||
A non-temporary child process may later be restarted by the supervisor. The child
|
||||
process can also be restarted explicitly by calling `restart_child/2`. Use
|
||||
`delete_child/2` to remove the child specification.
|
||||
|
||||
If successful, the function returns `:ok`. If there is no child specification or
|
||||
pid, the function returns `{:error, :not_found}`.
|
||||
"""
|
||||
@spec terminate_child(supervisor, pid | Supervisor.Spec.child_id) :: :ok | {:error, error}
|
||||
when error: :not_found | :simple_one_for_one
|
||||
defdelegate terminate_child(supervisor, pid_or_child_id), to: :supervisor
|
||||
|
||||
@doc """
|
||||
Deletes the child specification identified by `child_id`.
|
||||
|
||||
The corresponding child process must not be running, use `terminate_child/2`
|
||||
to terminate it.
|
||||
|
||||
If successful, the function returns `:ok`. This function may error with an
|
||||
appropriate error tuple if the `child_id` is not found, or if the current
|
||||
process is running or being restarted.
|
||||
|
||||
This operation is not supported by `simple_one_for_one` supervisors.
|
||||
"""
|
||||
@spec delete_child(supervisor, Supervisor.Spec.child_id) :: :ok | {:error, error}
|
||||
when error: :not_found | :simple_one_for_one | :running | :restarting
|
||||
defdelegate delete_child(supervisor, child_id), to: :supervisor
|
||||
|
||||
@doc """
|
||||
Restarts a child process identified by `child_id`.
|
||||
|
||||
The child specification must exist and the corresponding child process must not
|
||||
be running.
|
||||
|
||||
Note that for temporary children, the child specification is automatically deleted
|
||||
when the child terminates, and thus it is not possible to restart such children.
|
||||
|
||||
If the child process start function returns `{:ok, child}` or
|
||||
`{:ok, child, info}`, the pid is added to the supervisor and the function returns
|
||||
the same value.
|
||||
|
||||
If the child process start function returns `:ignore`, the pid remains set to
|
||||
`:undefined` and the function returns `{:ok, :undefined}`.
|
||||
|
||||
This function may error with an appropriate error tuple if the `child_id` is not
|
||||
found, or if the current process is running or being restarted.
|
||||
|
||||
If the child process start function returns an error tuple or an erroneous value,
|
||||
or if it fails, the function returns `{:error, error}`.
|
||||
|
||||
This operation is not supported by `simple_one_for_one` supervisors.
|
||||
"""
|
||||
@spec restart_child(supervisor, Supervisor.Spec.child_id) ::
|
||||
{:ok, child} | {:ok, child, term} | {:error, error}
|
||||
when error: :not_found | :simple_one_for_one | :running | :restarting | term
|
||||
defdelegate restart_child(supervisor, child_id), to: :supervisor
|
||||
|
||||
@doc """
|
||||
Returns a list with information about all children.
|
||||
|
||||
Note that calling this function when supervising a large number of children
|
||||
under low memory conditions can cause an out of memory exception.
|
||||
|
||||
This function returns a list of tuples containing:
|
||||
|
||||
* `id` - as defined in the child specification or `:undefined` in the case
|
||||
of a `simple_one_for_one` supervisor
|
||||
|
||||
* `child` - the pid of the corresponding child process, the atom
|
||||
`:restarting` if the process is about to be restarted, or `:undefined` if
|
||||
there is no such process
|
||||
|
||||
* `type` - `:worker` or `:supervisor` as defined in the child specification
|
||||
|
||||
* `modules` – as defined in the child specification
|
||||
"""
|
||||
@spec which_children(supervisor) ::
|
||||
[{Supervisor.Spec.child_id | :undefined,
|
||||
child | :restarting,
|
||||
Supervisor.Spec.worker,
|
||||
Supervisor.Spec.modules}]
|
||||
defdelegate which_children(supervisor), to: :supervisor
|
||||
|
||||
@doc """
|
||||
Returns a map containing count values for the supervisor.
|
||||
|
||||
The map contains the following keys:
|
||||
|
||||
* `:specs` - the total count of children, dead or alive
|
||||
|
||||
* `:active` - the count of all actively running child processes managed by
|
||||
this supervisor
|
||||
|
||||
* `:supervisors` - the count of all supervisors whether or not the child
|
||||
process is still alive
|
||||
|
||||
* `:workers` - the count of all workers, whether or not the child process
|
||||
is still alive
|
||||
|
||||
"""
|
||||
@spec count_children(supervisor) ::
|
||||
[specs: non_neg_integer, active: non_neg_integer,
|
||||
supervisors: non_neg_integer, workers: non_neg_integer]
|
||||
def count_children(supervisor) do
|
||||
:supervisor.count_children(supervisor) |> :maps.from_list
|
||||
end
|
||||
end
|
||||
@@ -1,184 +0,0 @@
|
||||
defmodule Supervisor.Behaviour do
|
||||
@moduledoc """
|
||||
This module is a convenience to define Supervisor
|
||||
callbacks in Elixir. By using this module, you get
|
||||
the module behaviour automatically tagged as
|
||||
`:supervisor` and some helper functions are imported
|
||||
to make defining supervisors easier.
|
||||
|
||||
For more information on supervisors, please check the
|
||||
remaining functions defined in this module or refer to
|
||||
the following:
|
||||
|
||||
http://www.erlang.org/doc/man/supervisor.html
|
||||
http://www.erlang.org/doc/design_principles/sup_princ.html
|
||||
http://learnyousomeerlang.com/supervisors
|
||||
|
||||
## Example
|
||||
|
||||
defmodule ExUnit.Sup do
|
||||
use Supervisor.Behaviour
|
||||
|
||||
def init(user_options) do
|
||||
tree = [ worker(ExUnit.Runner, [user_options]) ]
|
||||
supervise(tree, strategy: :one_for_one)
|
||||
end
|
||||
end
|
||||
|
||||
{:ok, pid} = :supervisor.start_link(MyServer, [])
|
||||
|
||||
"""
|
||||
|
||||
@doc false
|
||||
defmacro __using__(_) do
|
||||
quote location: :keep do
|
||||
@behaviour :supervisor
|
||||
import unquote(__MODULE__)
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Receives a list of children (worker or supervisors) to
|
||||
supervise and a set of options. Returns a tuple containing
|
||||
the supervisor specification.
|
||||
|
||||
## Examples
|
||||
|
||||
supervise children, strategy: :one_for_one
|
||||
|
||||
## Options
|
||||
|
||||
* `:strategy` - the restart strategy option It can be either
|
||||
`:one_for_one`, `:rest_for_one`, `:one_for_all` and
|
||||
`:simple_one_for_one`;
|
||||
|
||||
* `:max_restarts` - the maximum amount of restarts allowed in
|
||||
a time frame. Defaults to 5;
|
||||
|
||||
* `:max_seconds` - the time frame in which max_restarts applies.
|
||||
Defaults to 5;
|
||||
|
||||
The `:strategy` option is required and by default maximum 5 restarts
|
||||
are allowed in 5 seconds.
|
||||
|
||||
## Strategies
|
||||
|
||||
* `:one_for_one` - If a child process terminates, only that
|
||||
process is restarted;
|
||||
|
||||
* `:one_for_all` - If a child process terminates, all other child
|
||||
processes are terminated and then all child processes, including
|
||||
the terminated one, are restarted;
|
||||
|
||||
* `:rest_for_one` - If a child process terminates, the "rest" of
|
||||
the child processes, i.e. the child processes after the terminated
|
||||
process in start order, are terminated. Then the terminated child
|
||||
process and the rest of the child processes are restarted;
|
||||
|
||||
* `:simple_one_for_one` - Similar to `:one_for_one` but suits better
|
||||
when dynamically attaching children;
|
||||
|
||||
"""
|
||||
def supervise(children, options) do
|
||||
unless strategy = options[:strategy] do
|
||||
raise ArgumentError, message: "expected :strategy option to be given to supervise"
|
||||
end
|
||||
|
||||
maxR = Keyword.get(options, :max_restarts, 5)
|
||||
maxS = Keyword.get(options, :max_seconds, 5)
|
||||
|
||||
{:ok, {{strategy, maxR, maxS}, children}}
|
||||
end
|
||||
|
||||
@child_doc """
|
||||
## Options
|
||||
|
||||
* `:id` - a name used to identify the child specification
|
||||
internally by the supervisor. Defaults to the module name;
|
||||
|
||||
* `:function` - the function to invoke on the child to start it.
|
||||
Defaults to `:start_link`;
|
||||
|
||||
* `:restart` - defines when the child process should restart.
|
||||
Defaults to `:permanent`;
|
||||
|
||||
* `:shutdown` - defines how a child process should be terminated.
|
||||
Defaults to `5000` for a worker and `:infinity` for a supervisor;
|
||||
|
||||
* `:modules` - it should be a list with one element `[module]`,
|
||||
where module is the name of the callback module only if the
|
||||
child process is a supervisor, `gen_server` or `gen_fsm`. If the
|
||||
child process is a gen_event, modules should be `:dynamic`.
|
||||
Defaults to a list with the given module;
|
||||
|
||||
## Restart values
|
||||
|
||||
The following restart values are supported:
|
||||
|
||||
* `:permanent` - the child process is always restarted;
|
||||
|
||||
* `:temporary` - the child process is never restarted (not even
|
||||
when the supervisor's strategy is `:rest_for_one` or `:one_for_all`);
|
||||
|
||||
* `:transient` - the child process is restarted only if it
|
||||
terminates abnormally, i.e. with another exit reason than
|
||||
`:normal`, `:shutdown` or `{:shutdown, term}`;
|
||||
|
||||
## Shutdown values
|
||||
|
||||
The following shutdown values are supported:
|
||||
|
||||
* `:brutal_kill` - the child process is unconditionally terminated
|
||||
using `exit(child, :kill)`;
|
||||
|
||||
* `:infinity` - if the child process is a supervisor, it is a mechanism
|
||||
to give the subtree enough time to shutdown. It can also be used with
|
||||
workers with care;
|
||||
|
||||
* Finally, it can also be any integer meaning that the supervisor tells
|
||||
the child process to terminate by calling `exit(child, :shutdown)` and
|
||||
then waits for an exit signal back. If no exit signal is received within
|
||||
the specified time (in miliseconds), the child process is unconditionally
|
||||
terminated using `exit(child, :kill)`;
|
||||
"""
|
||||
|
||||
@doc """
|
||||
Defines the given `module` as a worker which will be started
|
||||
with the given arguments.
|
||||
|
||||
worker ExUnit.Runner, [], restart: :permanent
|
||||
|
||||
By default, the function `:start_link` is invoked on the given module.
|
||||
|
||||
#{@child_doc}
|
||||
"""
|
||||
def worker(module, args, options \\ []) do
|
||||
child(:worker, module, args, options)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Defines the given `module` as a supervisor which will be started
|
||||
with the given arguments.
|
||||
|
||||
supervisor ExUnit.Runner, [], restart: :permanent
|
||||
|
||||
By default, the function `:start_link` is invoked on the given module.
|
||||
|
||||
#{@child_doc}
|
||||
"""
|
||||
def supervisor(module, args, options \\ []) do
|
||||
options = Keyword.update(options, :shutdown, :infinity, fn(x) -> x end)
|
||||
child(:supervisor, module, args, options)
|
||||
end
|
||||
|
||||
defp child(type, module, args, options) do
|
||||
id = Keyword.get(options, :id, module)
|
||||
modules = Keyword.get(options, :modules, [module])
|
||||
function = Keyword.get(options, :function, :start_link)
|
||||
restart = Keyword.get(options, :restart, :permanent)
|
||||
shutdown = Keyword.get(options, :shutdown, 5000)
|
||||
|
||||
{id, {module, function, args},
|
||||
restart, shutdown, type, modules}
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,13 @@
|
||||
defmodule Supervisor.Default do
|
||||
@moduledoc false
|
||||
@behaviour :supervisor
|
||||
|
||||
@doc """
|
||||
Supevisor callback that simply returns the given args.
|
||||
|
||||
This is the supervisor used by `Supervisor.start_link/2`.
|
||||
"""
|
||||
def init(args) do
|
||||
args
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,248 @@
|
||||
defmodule Supervisor.Spec do
|
||||
@moduledoc """
|
||||
Convenience functions for defining a supervision specification.
|
||||
|
||||
## Example
|
||||
|
||||
By using the functions in this module one can define a supervisor
|
||||
and start it with `Supervisor.start_link/2`:
|
||||
|
||||
import Supervisor.Spec
|
||||
|
||||
children = [
|
||||
worker(MyWorker, [arg1, arg2, arg3]),
|
||||
supervisor(MySupervisor, [arg1])
|
||||
]
|
||||
|
||||
Supervisor.start_link(children, strategy: :one_for_one)
|
||||
|
||||
In many situations, it may be handy to define supervisors backed
|
||||
by a module:
|
||||
|
||||
defmodule MySupervisor do
|
||||
use Supervisor
|
||||
|
||||
def start_link(arg) do
|
||||
Supervisor.start_link(__MODULE__, arg)
|
||||
end
|
||||
|
||||
def init(arg) do
|
||||
children = [
|
||||
worker(MyWorker, [arg], restart: :temporary)
|
||||
]
|
||||
|
||||
supervise(children, strategy: :simple_one_for_one)
|
||||
end
|
||||
end
|
||||
|
||||
Notice in this case we don't have to explicitly import
|
||||
`Supervisor.Spec` as `use Supervisor` automatically does so.
|
||||
|
||||
Explicit supervisors as above are required when there is a need to:
|
||||
|
||||
1. Partialy change the supervision tree during hot-code swaps.
|
||||
|
||||
2. Define supervisors inside other supervisors.
|
||||
|
||||
3. Perform actions inside the supervision `init/1` callback.
|
||||
|
||||
For example, you may want to start an ETS table that is linked to
|
||||
the supervisor (i.e. if the supervision tree needs to be restarted,
|
||||
the ETS table must be restarted too).
|
||||
|
||||
## Supervisor and worker options
|
||||
|
||||
In the example above, we have defined workers and supervisors
|
||||
and each accepts the following options:
|
||||
|
||||
* `:id` - a name used to identify the child specification
|
||||
internally by the supervisor; defaults to the given module
|
||||
name
|
||||
|
||||
* `:function` - the function to invoke on the child to start it
|
||||
|
||||
* `:restart` - defines when the child process should restart
|
||||
|
||||
* `:shutdown` - defines how a child process should be terminated
|
||||
|
||||
* `:modules` - it should be a list with one element `[module]`,
|
||||
where module is the name of the callback module only if the
|
||||
child process is a `Supervisor` or `GenServer`; if the child
|
||||
process is a `GenEvent`, modules should be `:dynamic`
|
||||
|
||||
### Restart values
|
||||
|
||||
The following restart values are supported:
|
||||
|
||||
* `:permanent` - the child process is always restarted
|
||||
|
||||
* `:temporary` - the child process is never restarted (not even
|
||||
when the supervisor's strategy is `:rest_for_one` or `:one_for_all`)
|
||||
|
||||
* `:transient` - the child process is restarted only if it
|
||||
terminates abnormally, i.e. with another exit reason than
|
||||
`:normal`, `:shutdown` or `{:shutdown, term}`
|
||||
|
||||
### Shutdown values
|
||||
|
||||
The following shutdown values are supported:
|
||||
|
||||
* `:brutal_kill` - the child process is unconditionally terminated
|
||||
using `exit(child, :kill)`.
|
||||
|
||||
* `:infinity` - if the child process is a supervisor, it is a mechanism
|
||||
to give the subtree enough time to shutdown. It can also be used with
|
||||
workers with care.
|
||||
|
||||
* Finally, it can also be any integer meaning that the supervisor tells
|
||||
the child process to terminate by calling `Process.exit(child, :shutdown)`
|
||||
and then waits for an exit signal back. If no exit signal is received
|
||||
within the specified time (in miliseconds), the child process is
|
||||
unconditionally terminated using `Process.exit(child, :kill)`.
|
||||
"""
|
||||
|
||||
@typedoc "Supported strategies"
|
||||
@type strategy :: :simple_one_for_one | :one_for_one | :one_for_all | :rest_for_one
|
||||
|
||||
@typedoc "Supported restart values"
|
||||
@type restart :: :permanent | :transient | :temporary
|
||||
|
||||
@typedoc "Supported shutdown values"
|
||||
@type shutdown :: :brutal_kill | :infinity | non_neg_integer
|
||||
|
||||
@typedoc "Supported worker values"
|
||||
@type worker :: :worker | :supervisor
|
||||
|
||||
@typedoc "Supported module values"
|
||||
@type modules :: :dynamic | [module]
|
||||
|
||||
@typedoc "Supported id values"
|
||||
@type child_id :: term
|
||||
|
||||
@typedoc "The supervisor specification"
|
||||
@type spec :: {child_id,
|
||||
start_fun :: {module, atom, [term]},
|
||||
restart,
|
||||
shutdown,
|
||||
worker,
|
||||
modules}
|
||||
|
||||
@doc """
|
||||
Receives a list of children (workers or supervisors) to
|
||||
supervise and a set of options.
|
||||
|
||||
Returns a tuple containing the supervisor specification.
|
||||
|
||||
## Examples
|
||||
|
||||
supervise children, strategy: :one_for_one
|
||||
|
||||
## Options
|
||||
|
||||
* `:strategy` - the restart strategy option. It can be either
|
||||
`:one_for_one`, `:rest_for_one`, `:one_for_all`, or
|
||||
`:simple_one_for_one`. You can learn more about strategies
|
||||
in the `Supervisor` module docs.
|
||||
|
||||
* `:max_restarts` - the maximum amount of restarts allowed in
|
||||
a time frame. Defaults to 5.
|
||||
|
||||
* `:max_seconds` - the time frame in which `:max_restarts` applies.
|
||||
Defaults to 5.
|
||||
|
||||
The `:strategy` option is required and by default maximum 5 restarts
|
||||
are allowed within 5 seconds. Please check the `Supervisor` module for
|
||||
a complete description of the available strategies.
|
||||
"""
|
||||
@spec supervise([spec], strategy: strategy,
|
||||
max_restarts: non_neg_integer,
|
||||
max_seconds: non_neg_integer) :: {:ok, tuple}
|
||||
def supervise(children, options) do
|
||||
unless strategy = options[:strategy] do
|
||||
raise ArgumentError, "expected :strategy option to be given"
|
||||
end
|
||||
|
||||
maxR = Keyword.get(options, :max_restarts, 5)
|
||||
maxS = Keyword.get(options, :max_seconds, 5)
|
||||
|
||||
assert_unique_ids(Enum.map(children, &elem(&1, 0)))
|
||||
{:ok, {{strategy, maxR, maxS}, children}}
|
||||
end
|
||||
|
||||
defp assert_unique_ids([id|rest]) do
|
||||
if id in rest do
|
||||
raise ArgumentError,
|
||||
"duplicated id #{inspect id} found in the supervisor specification, " <>
|
||||
"please explicitly pass the :id option when defining this worker/supervisor"
|
||||
else
|
||||
assert_unique_ids(rest)
|
||||
end
|
||||
end
|
||||
|
||||
defp assert_unique_ids([]) do
|
||||
:ok
|
||||
end
|
||||
|
||||
@doc """
|
||||
Defines the given `module` as a worker which will be started
|
||||
with the given arguments.
|
||||
|
||||
worker ExUnit.Runner, [], restart: :permanent
|
||||
|
||||
By default, the function `start_link` is invoked on the given
|
||||
module. Overall, the default values for the options are:
|
||||
|
||||
[id: module,
|
||||
function: :start_link,
|
||||
restart: :permanent,
|
||||
shutdown: 5000,
|
||||
modules: [module]]
|
||||
|
||||
Check `Supervisor.Spec` module docs for more information on
|
||||
the options.
|
||||
"""
|
||||
@spec worker(module, [term], [restart: restart, shutdown: shutdown,
|
||||
id: term, function: atom, modules: modules]) :: spec
|
||||
def worker(module, args, options \\ []) do
|
||||
child(:worker, module, args, options)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Defines the given `module` as a supervisor which will be started
|
||||
with the given arguments.
|
||||
|
||||
supervisor ExUnit.Runner, [], restart: :permanent
|
||||
|
||||
By default, the function `start_link` is invoked on the given
|
||||
module. Overall, the default values for the options are:
|
||||
|
||||
[id: module,
|
||||
function: :start_link,
|
||||
restart: :permanent,
|
||||
shutdown: :infinity,
|
||||
modules: [module]]
|
||||
|
||||
Check `Supervisor.Spec` module docs for more information on
|
||||
the options.
|
||||
"""
|
||||
@spec supervisor(module, [term], [restart: restart, shutdown: shutdown,
|
||||
id: term, function: atom, modules: modules]) :: spec
|
||||
def supervisor(module, args, options \\ []) do
|
||||
options = Keyword.put_new(options, :shutdown, :infinity)
|
||||
child(:supervisor, module, args, options)
|
||||
end
|
||||
|
||||
defp child(type, module, args, options) do
|
||||
id = Keyword.get(options, :id, module)
|
||||
modules = Keyword.get(options, :modules, modules(module))
|
||||
function = Keyword.get(options, :function, :start_link)
|
||||
restart = Keyword.get(options, :restart, :permanent)
|
||||
shutdown = Keyword.get(options, :shutdown, 5000)
|
||||
|
||||
{id, {module, function, args},
|
||||
restart, shutdown, type, modules}
|
||||
end
|
||||
|
||||
defp modules(GenEvent), do: :dynamic
|
||||
defp modules(module), do: [module]
|
||||
end
|
||||
+26
-26
@@ -44,7 +44,7 @@ defmodule System do
|
||||
|
||||
# Get the date at compilation time.
|
||||
defmacrop get_date do
|
||||
iodata_to_binary :httpd_util.rfc1123_date
|
||||
IO.iodata_to_binary :httpd_util.rfc1123_date
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -60,9 +60,9 @@ defmodule System do
|
||||
|
||||
Returns a keyword list with Elixir version, git tag info and compilation date.
|
||||
"""
|
||||
@spec build_info() :: Keyword.t
|
||||
@spec build_info() :: map
|
||||
def build_info do
|
||||
[version: version, tag: get_describe, date: get_date]
|
||||
%{version: version, tag: get_describe, date: get_date}
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -94,7 +94,7 @@ defmodule System do
|
||||
"""
|
||||
def cwd do
|
||||
case :file.get_cwd do
|
||||
{:ok, base} -> String.from_char_data!(base)
|
||||
{:ok, base} -> IO.chardata_to_string(base)
|
||||
_ -> nil
|
||||
end
|
||||
end
|
||||
@@ -153,11 +153,11 @@ defmodule System do
|
||||
Returns a writable temporary directory.
|
||||
Searches for directories in the following order:
|
||||
|
||||
1. The directory named by the TMPDIR environment variable
|
||||
2. The directory named by the TEMP environment variable
|
||||
3. The directory named by the TMP environment variable
|
||||
4. `C:\TMP` on Windows or `/tmp` on Unix
|
||||
5. As a last resort, the current working directory
|
||||
1. the directory named by the TMPDIR environment variable
|
||||
2. the directory named by the TEMP environment variable
|
||||
3. the directory named by the TMP environment variable
|
||||
4. `C:\TMP` on Windows or `/tmp` on Unix
|
||||
5. as a last resort, the current working directory
|
||||
|
||||
Returns `nil` if none of the above are writable.
|
||||
"""
|
||||
@@ -193,7 +193,7 @@ defmodule System do
|
||||
{:ok, stat} ->
|
||||
case {stat.type, stat.access} do
|
||||
{:directory, access} when access in [:read_write, :write] ->
|
||||
String.from_char_data!(dir)
|
||||
IO.chardata_to_string(dir)
|
||||
_ ->
|
||||
nil
|
||||
end
|
||||
@@ -233,7 +233,7 @@ defmodule System do
|
||||
end
|
||||
|
||||
def cmd(command) when is_binary(command) do
|
||||
String.from_char_data! :os.cmd(List.from_char_data!(command))
|
||||
List.to_string :os.cmd(String.to_char_list(command))
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -256,9 +256,9 @@ defmodule System do
|
||||
end
|
||||
|
||||
def find_executable(program) when is_binary(program) do
|
||||
case :os.find_executable(List.from_char_data!(program)) do
|
||||
case :os.find_executable(String.to_char_list(program)) do
|
||||
false -> nil
|
||||
other -> String.from_char_data!(other)
|
||||
other -> List.to_string(other)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -271,7 +271,7 @@ defmodule System do
|
||||
@spec get_env() :: %{String.t => String.t}
|
||||
def get_env do
|
||||
Enum.into(:os.getenv, %{}, fn var ->
|
||||
var = String.from_char_data! var
|
||||
var = IO.chardata_to_string var
|
||||
[k, v] = String.split var, "=", parts: 2
|
||||
{k, v}
|
||||
end)
|
||||
@@ -286,9 +286,9 @@ defmodule System do
|
||||
"""
|
||||
@spec get_env(binary) :: binary | nil
|
||||
def get_env(varname) when is_binary(varname) do
|
||||
case :os.getenv(List.from_char_data!(varname)) do
|
||||
case :os.getenv(String.to_char_list(varname)) do
|
||||
false -> nil
|
||||
other -> String.from_char_data!(other)
|
||||
other -> List.to_string(other)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -301,7 +301,7 @@ defmodule System do
|
||||
See http://www.erlang.org/doc/man/os.html#getpid-0 for more info.
|
||||
"""
|
||||
@spec get_pid() :: binary
|
||||
def get_pid, do: iodata_to_binary(:os.getpid)
|
||||
def get_pid, do: IO.iodata_to_binary(:os.getpid)
|
||||
|
||||
@doc """
|
||||
Set an environment variable value.
|
||||
@@ -310,7 +310,7 @@ defmodule System do
|
||||
"""
|
||||
@spec put_env(binary, binary) :: :ok
|
||||
def put_env(varname, value) when is_binary(varname) and is_binary(value) do
|
||||
:os.putenv List.from_char_data!(varname), List.from_char_data!(value)
|
||||
:os.putenv String.to_char_list(varname), String.to_char_list(value)
|
||||
:ok
|
||||
end
|
||||
|
||||
@@ -332,7 +332,7 @@ defmodule System do
|
||||
"""
|
||||
@spec delete_env(String.t) :: :ok
|
||||
def delete_env(varname) do
|
||||
:os.unsetenv(List.from_char_data!(varname))
|
||||
:os.unsetenv(String.to_char_list(varname))
|
||||
:ok
|
||||
end
|
||||
|
||||
@@ -355,14 +355,14 @@ defmodule System do
|
||||
Halts the Erlang runtime system where the argument `status` must be a
|
||||
non-negative integer, the atom `:abort` or a binary.
|
||||
|
||||
* If an integer, the runtime system exits with the integer value which
|
||||
is returned to the operating system;
|
||||
* If an integer, the runtime system exits with the integer value which
|
||||
is returned to the operating system.
|
||||
|
||||
* If `:abort`, the runtime system aborts producing a core dump, if that is
|
||||
enabled in the operating system;
|
||||
* If `:abort`, the runtime system aborts producing a core dump, if that is
|
||||
enabled in the operating system.
|
||||
|
||||
* If a string, an erlang crash dump is produced with status as slogan,
|
||||
and then the runtime system exits with status code 1;
|
||||
* If a string, an erlang crash dump is produced with status as slogan,
|
||||
and then the runtime system exits with status code 1.
|
||||
|
||||
Note that on many platforms, only the status codes 0-255 are supported
|
||||
by the operating system.
|
||||
@@ -385,6 +385,6 @@ defmodule System do
|
||||
end
|
||||
|
||||
def halt(status) when is_binary(status) do
|
||||
:erlang.halt(List.from_char_data!(status))
|
||||
:erlang.halt(String.to_char_list(status))
|
||||
end
|
||||
end
|
||||
|
||||
@@ -0,0 +1,220 @@
|
||||
defmodule Task do
|
||||
@moduledoc """
|
||||
Conveniences for spawning and awaiting for tasks.
|
||||
|
||||
Tasks are processes meant to execute one particular
|
||||
action throughout their life-cycle, often with little or no
|
||||
communication with other processes. The most common use case
|
||||
for tasks is to compute a value asynchronously:
|
||||
|
||||
task = Task.async(fn -> do_some_work() end)
|
||||
res = do_some_other_work()
|
||||
res + Task.await(task)
|
||||
|
||||
Tasks spawned with `async` can be awaited on by its caller
|
||||
process (and only its caller) as shown in the example above.
|
||||
They are implemented by spawning a process that sends a message
|
||||
to the caller once the given computation is performed.
|
||||
|
||||
Besides `async/1` and `await/2`, tasks can also be
|
||||
started as part of supervision trees and dynamically spawned
|
||||
in remote nodes. We will explore all three scenarios next.
|
||||
|
||||
## async and await
|
||||
|
||||
The most common way to spawn a task is with `Task.async/1`. A new
|
||||
process will be created, linked and monitored by the caller. Once
|
||||
the task action finishes, a message will be sent to the caller
|
||||
with the result.
|
||||
|
||||
`Task.await/2` is used to read the message sent by the task. On
|
||||
`await`, Elixir will also setup a monitor to verify if the process
|
||||
exited for any abnormal reason (or in case exits are being
|
||||
trapped by the caller).
|
||||
|
||||
## Supervised tasks
|
||||
|
||||
It is also possible to spawn a task inside a supervision tree
|
||||
with `start_link/1` and `start_link/3`:
|
||||
|
||||
Task.start_link(fn -> IO.puts "ok" end)
|
||||
|
||||
Such tasks can be mounted in your supervision tree as:
|
||||
|
||||
import Supervisor.Spec
|
||||
|
||||
children = [
|
||||
worker(Task, [fn -> IO.puts "ok" end])
|
||||
]
|
||||
|
||||
Since these tasks are supervised and not directly linked to
|
||||
the caller, they cannot be awaited on. Note `start_link/1`,
|
||||
unlike `async/1`, returns `{:ok, pid}` (which is
|
||||
the result expected by supervision trees).
|
||||
|
||||
## Supervision trees
|
||||
|
||||
The `Task.Supervisor` module allows developers to start supervisors
|
||||
that dynamically supervise tasks:
|
||||
|
||||
{:ok, pid} = Task.Supervisor.start_link()
|
||||
Task.Supervisor.async(pid, fn -> do_work() end)
|
||||
|
||||
`Task.Supervisor` also makes it possible to spawn tasks in remote nodes as
|
||||
long as the supervisor is registered locally or globally:
|
||||
|
||||
# In the remote node
|
||||
Task.Supervisor.start_link(name: :tasks_sup)
|
||||
|
||||
# In the client
|
||||
Task.Supervisor.async({:tasks_sup, :remote@local}, fn -> do_work() end)
|
||||
|
||||
`Task.Supervisor` is more often started in your supervision tree as:
|
||||
|
||||
import Supervisor.Spec
|
||||
|
||||
children = [
|
||||
supervisor(Task.Supervisor, [[name: :tasks_sup]])
|
||||
]
|
||||
|
||||
Check `Task.Supervisor` for other operations supported by the Task supervisor.
|
||||
"""
|
||||
|
||||
@doc """
|
||||
The Task struct.
|
||||
|
||||
It contains two fields:
|
||||
|
||||
* `:pid` - the process reference of the task process; it may be a pid
|
||||
or a tuple containing the process and node names
|
||||
|
||||
* `:ref` - the task monitor reference
|
||||
|
||||
"""
|
||||
defstruct pid: nil, ref: nil
|
||||
|
||||
@doc """
|
||||
Starts a task as part of a supervision tree.
|
||||
"""
|
||||
@spec start_link(fun) :: {:ok, pid}
|
||||
def start_link(fun) do
|
||||
start_link(:erlang, :apply, [fun, []])
|
||||
end
|
||||
|
||||
@doc """
|
||||
Starts a task as part of a supervision tree.
|
||||
"""
|
||||
@spec start_link(module, atom, [term]) :: {:ok, pid}
|
||||
def start_link(mod, fun, args) do
|
||||
Task.Supervised.start_link(get_info(self), {mod, fun, args})
|
||||
end
|
||||
|
||||
@doc """
|
||||
Starts a task that can be awaited on.
|
||||
|
||||
This function spawns a process that is linked to and monitored
|
||||
by the caller process. A `Task` struct is returned containing
|
||||
the relevant information.
|
||||
|
||||
## Task's message format
|
||||
|
||||
The reply sent by the task will be in the format `{ref, msg}`,
|
||||
where `ref` is the monitoring reference held by the task.
|
||||
"""
|
||||
@spec async(fun) :: t
|
||||
def async(fun) do
|
||||
async(:erlang, :apply, [fun, []])
|
||||
end
|
||||
|
||||
@doc """
|
||||
Starts a task that can be awaited on.
|
||||
|
||||
Similar to `async/1`, but the task is specified by the given
|
||||
module, function and arguments.
|
||||
"""
|
||||
@spec async(module, atom, [term]) :: t
|
||||
def async(mod, fun, args) do
|
||||
mfa = {mod, fun, args}
|
||||
pid = :proc_lib.spawn_link(Task.Supervised, :async, [self, get_info(self), mfa])
|
||||
ref = Process.monitor(pid)
|
||||
send(pid, {self(), ref})
|
||||
%Task{pid: pid, ref: ref}
|
||||
end
|
||||
|
||||
defp get_info(self) do
|
||||
{node(),
|
||||
case Process.info(self, :registered_name) do
|
||||
{:registered_name, []} -> self()
|
||||
{:registered_name, name} -> name
|
||||
end}
|
||||
end
|
||||
|
||||
@doc """
|
||||
Awaits for a task reply.
|
||||
|
||||
A timeout, in milliseconds, can be given with default value
|
||||
of `5000`. In case the task process dies, this function will
|
||||
exit with the same reason as the task.
|
||||
"""
|
||||
@spec await(t, timeout) :: term | no_return
|
||||
def await(%Task{ref: ref}=task, timeout \\ 5000) do
|
||||
receive do
|
||||
{^ref, reply} ->
|
||||
Process.demonitor(ref, [:flush])
|
||||
reply
|
||||
{:DOWN, ^ref, _, _, :noconnection} ->
|
||||
mfa = {__MODULE__, :await, [task, timeout]}
|
||||
exit({{:nodedown, node(task.pid)}, mfa})
|
||||
{:DOWN, ^ref, _, _, reason} ->
|
||||
exit({reason, {__MODULE__, :await, [task, timeout]}})
|
||||
after
|
||||
timeout ->
|
||||
Process.demonitor(ref, [:flush])
|
||||
exit({:timeout, {__MODULE__, :await, [task, timeout]}})
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Receives a group of tasks and a message and finds
|
||||
a task that matches the given message.
|
||||
|
||||
This function returns a tuple with the task and the
|
||||
returned value in case the message matches a task that
|
||||
exited with success, it raises in case the found task
|
||||
failed or `nil` if no task was found.
|
||||
|
||||
This function is useful in situations where multiple
|
||||
tasks are spawned and their results are collected
|
||||
later on. For example, a `GenServer` can spawn tasks,
|
||||
store the tasks in a list and later use `Task.find/2`
|
||||
to see if incoming messages are from any of the tasks.
|
||||
"""
|
||||
@spec find([t], any) :: {term, t} | nil | no_return
|
||||
def find(tasks, msg)
|
||||
|
||||
def find(tasks, {ref, reply}) when is_reference(ref) do
|
||||
Enum.find_value tasks, fn
|
||||
%Task{ref: task_ref} = t when ref == task_ref ->
|
||||
Process.demonitor(ref, [:flush])
|
||||
{reply, t}
|
||||
%Task{} ->
|
||||
nil
|
||||
end
|
||||
end
|
||||
|
||||
def find(tasks, {:DOWN, ref, _, _, reason} = msg) when is_reference(ref) do
|
||||
find = fn(%Task{ref: task_ref}) -> task_ref == ref end
|
||||
case Enum.find(tasks, find) do
|
||||
%Task{pid: pid} when reason == :noconnection ->
|
||||
exit({{:nodedown, node(pid)}, {__MODULE__, :find, [tasks, msg]}})
|
||||
%Task{} ->
|
||||
exit({reason, {__MODULE__, :find, [tasks, msg]}})
|
||||
nil ->
|
||||
nil
|
||||
end
|
||||
end
|
||||
|
||||
def find(_tasks, _msg) do
|
||||
nil
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,107 @@
|
||||
defmodule Task.Supervised do
|
||||
@moduledoc false
|
||||
|
||||
def start_link(info, fun) do
|
||||
{:ok, :proc_lib.spawn_link(__MODULE__, :noreply, [info, fun])}
|
||||
end
|
||||
|
||||
def start_link(caller, info, fun) do
|
||||
:proc_lib.start_link(__MODULE__, :reply, [caller, info, fun])
|
||||
end
|
||||
|
||||
def async(caller, info, mfa) do
|
||||
initial_call(mfa)
|
||||
ref = receive do: ({^caller, ref} -> ref)
|
||||
send caller, {ref, do_apply(info, mfa)}
|
||||
end
|
||||
|
||||
def reply(caller, info, mfa) do
|
||||
initial_call(mfa)
|
||||
:erlang.link(caller)
|
||||
:proc_lib.init_ack({:ok, self()})
|
||||
|
||||
ref =
|
||||
# There is a race condition on this operation when working accross
|
||||
# node that manifests if a `Task.Supervisor.async/1` call is made
|
||||
# while the supervisor is busy spawning previous tasks.
|
||||
#
|
||||
# Imagine the following workflow:
|
||||
#
|
||||
# 1. The nodes disconnect
|
||||
# 2. The async call fails and is caught, the calling process does not exit
|
||||
# 3. The task is spawned and links to the calling process, causing the nodes to reconnect
|
||||
# 4. The calling process has not exited and so does not send its monitor reference
|
||||
# 5. The spawned task waits forever for the monitor reference so it can begin
|
||||
#
|
||||
# We have solved this by specifying a timeout of 5000 seconds.
|
||||
# Given no work is done in the client in between the task start and
|
||||
# sending the reference, 5000 should be enough to not raise false
|
||||
# negatives unless the nodes are indeed not available.
|
||||
receive do
|
||||
{^caller, ref} -> ref
|
||||
after
|
||||
5000 -> exit(:timeout)
|
||||
end
|
||||
|
||||
send caller, {ref, do_apply(info, mfa)}
|
||||
end
|
||||
|
||||
def noreply(info, mfa) do
|
||||
initial_call(mfa)
|
||||
do_apply(info, mfa)
|
||||
end
|
||||
|
||||
defp initial_call(mfa) do
|
||||
Process.put(:"$initial_call", get_initial_call(mfa))
|
||||
end
|
||||
|
||||
defp get_initial_call({:erlang, :apply, [fun, []]}) when is_function(fun, 0) do
|
||||
{:module, module} = :erlang.fun_info(fun, :module)
|
||||
{:name, name} = :erlang.fun_info(fun, :name)
|
||||
{module, name, 0}
|
||||
end
|
||||
|
||||
defp get_initial_call({mod, fun, args}) do
|
||||
{mod, fun, length(args)}
|
||||
end
|
||||
|
||||
defp do_apply(info, {module, fun, args} = mfa) do
|
||||
try do
|
||||
apply(module, fun, args)
|
||||
catch
|
||||
:error, value ->
|
||||
exit(info, mfa, {value, System.stacktrace()})
|
||||
:throw, value ->
|
||||
exit(info, mfa, {{:nocatch, value}, System.stacktrace()})
|
||||
:exit, value ->
|
||||
exit(info, mfa, value)
|
||||
end
|
||||
end
|
||||
|
||||
defp exit(_info, _mfa, reason)
|
||||
when reason == :normal
|
||||
when reason == :shutdown
|
||||
when tuple_size(reason) == 2 and elem(reason, 0) == :shutdown do
|
||||
exit(reason)
|
||||
end
|
||||
|
||||
defp exit(info, mfa, reason) do
|
||||
{fun, args} = get_running(mfa)
|
||||
|
||||
:error_logger.format(
|
||||
"** Task ~p terminating~n" <>
|
||||
"** Started from ~p~n" <>
|
||||
"** When function == ~p~n" <>
|
||||
"** arguments == ~p~n" <>
|
||||
"** Reason for termination == ~n" <>
|
||||
"** ~p~n", [self, get_from(info), fun, args, reason])
|
||||
|
||||
exit(reason)
|
||||
end
|
||||
|
||||
defp get_from({node, pid_or_name}) when node == node(), do: pid_or_name
|
||||
defp get_from(other), do: other
|
||||
|
||||
defp get_running({:erlang, :apply, [fun, []]}) when is_function(fun, 0), do: {fun, []}
|
||||
defp get_running({mod, fun, args}), do: {:erlang.make_fun(mod, fun, length(args)), args}
|
||||
end
|
||||
@@ -0,0 +1,112 @@
|
||||
defmodule Task.Supervisor do
|
||||
@moduledoc """
|
||||
A tasks supervisor.
|
||||
|
||||
This module defines a supervisor which can be used to dynamically
|
||||
supervise tasks. Behind the scenes, this module is implemented as a
|
||||
`:simple_one_for_one` supervisor where the workers are temporary
|
||||
(i.e. they are not restarted after they die).
|
||||
|
||||
The functions in this module allow tasks to be spawned and awaited
|
||||
from a supervisor, similar to the functions defined in the `Task` module.
|
||||
|
||||
## Name Registration
|
||||
|
||||
A `Task.Supervisor` is bound to the same name registration rules as a
|
||||
`GenServer`. Read more about it in the `GenServer` docs.
|
||||
"""
|
||||
|
||||
@doc """
|
||||
Starts a new supervisor.
|
||||
|
||||
The supported options are:
|
||||
|
||||
* `:name` - used to register a supervisor name, the supported values are
|
||||
described under the `Name Registration` section in the `GenServer` module
|
||||
docs;
|
||||
|
||||
* `:shutdown` - `:brutal_kill` if the tasks must be killed directly on shutdown
|
||||
or an integer indicating the timeout value, defaults to 5000 milliseconds;
|
||||
"""
|
||||
@spec start_link(Supervisor.options) :: Supervisor.on_start
|
||||
def start_link(opts \\ []) do
|
||||
import Supervisor.Spec
|
||||
{shutdown, opts} = Keyword.pop(opts, :shutdown, 5000)
|
||||
children = [worker(Task.Supervised, [], restart: :temporary, shutdown: shutdown)]
|
||||
Supervisor.start_link(children, [strategy: :simple_one_for_one] ++ opts)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Starts a task that can be awaited on.
|
||||
|
||||
The `supervisor` must be a reference as defined in `Task.Supervisor`.
|
||||
For more information on tasks, check the `Task` module.
|
||||
"""
|
||||
@spec async(Supervisor.supervisor, fun) :: Task.t
|
||||
def async(supervisor, fun) do
|
||||
async(supervisor, :erlang, :apply, [fun, []])
|
||||
end
|
||||
|
||||
@doc """
|
||||
Starts a task that can be awaited on.
|
||||
|
||||
The `supervisor` must be a reference as defined in `Task.Supervisor`.
|
||||
For more information on tasks, check the `Task` module.
|
||||
"""
|
||||
@spec async(Supervisor.supervisor, module, atom, [term]) :: Task.t
|
||||
def async(supervisor, module, fun, args) do
|
||||
args = [self, get_info(self), {module, fun, args}]
|
||||
{:ok, pid} = Supervisor.start_child(supervisor, args)
|
||||
ref = Process.monitor(pid)
|
||||
send pid, {self(), ref}
|
||||
%Task{pid: pid, ref: ref}
|
||||
end
|
||||
|
||||
@doc """
|
||||
Terminates the child with the given `pid`.
|
||||
"""
|
||||
@spec terminate_child(Supervisor.supervisor, pid) :: :ok
|
||||
def terminate_child(supervisor, pid) when is_pid(pid) do
|
||||
:supervisor.terminate_child(supervisor, pid)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns all children pids.
|
||||
"""
|
||||
@spec children(Supervisor.supervisor) :: [pid]
|
||||
def children(supervisor) do
|
||||
:supervisor.which_children(supervisor) |> Enum.map(&elem(&1, 1))
|
||||
end
|
||||
|
||||
@doc """
|
||||
Starts a task as child of the given `supervisor`.
|
||||
|
||||
Note that the spawned process is not linked to the caller, but
|
||||
only to the supervisor. This command is useful in case the
|
||||
task needs to perform side-effects (like I/O) and does not need
|
||||
to report back to the caller.
|
||||
"""
|
||||
@spec start_child(Supervisor.supervisor, fun) :: {:ok, pid}
|
||||
def start_child(supervisor, fun) do
|
||||
start_child(supervisor, :erlang, :apply, [fun, []])
|
||||
end
|
||||
|
||||
@doc """
|
||||
Starts a task as child of the given `supervisor`.
|
||||
|
||||
Similar to `start_child/2` except the task is specified
|
||||
by the given `module`, `fun` and `args`.
|
||||
"""
|
||||
@spec start_child(Supervisor.supervisor, module, atom, [term]) :: {:ok, pid}
|
||||
def start_child(supervisor, module, fun, args) do
|
||||
Supervisor.start_child(supervisor, [get_info(self), {module, fun, args}])
|
||||
end
|
||||
|
||||
defp get_info(self) do
|
||||
{node(),
|
||||
case Process.info(self, :registered_name) do
|
||||
{:registered_name, []} -> self()
|
||||
{:registered_name, name} -> name
|
||||
end}
|
||||
end
|
||||
end
|
||||
+13
-3
@@ -4,7 +4,7 @@ defmodule Tuple do
|
||||
"""
|
||||
|
||||
@doc """
|
||||
Create a new tuple.
|
||||
Creates a new tuple.
|
||||
|
||||
Creates a tuple of size `size` containing the
|
||||
given `data` at every position.
|
||||
@@ -23,7 +23,7 @@ defmodule Tuple do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Insert an element into a tuple.
|
||||
Inserts an element into a tuple.
|
||||
|
||||
Inserts `value` into `tuple` at the given zero-based `index`.
|
||||
Raises an `ArgumentError` if `index` is greater than the
|
||||
@@ -44,7 +44,7 @@ defmodule Tuple do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Remove an element from a tuple.
|
||||
Removes an element from a tuple.
|
||||
|
||||
Deletes the element at the zero-based `index` from `tuple`.
|
||||
Raises an `ArgumentError` if `index` is greater than
|
||||
@@ -63,4 +63,14 @@ defmodule Tuple do
|
||||
def delete_at(tuple, index) do
|
||||
:erlang.delete_element(index + 1, tuple)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Converts a tuple to a list.
|
||||
|
||||
Inlined by the compiler.
|
||||
"""
|
||||
@spec to_list(tuple) :: list
|
||||
def to_list(tuple) do
|
||||
:erlang.tuple_to_list(tuple)
|
||||
end
|
||||
end
|
||||
|
||||
+107
-40
@@ -58,7 +58,7 @@ defmodule URI do
|
||||
"""
|
||||
def default_port(scheme, port) when is_binary(scheme) and port > 0 do
|
||||
{:ok, dict} = Application.fetch_env(:elixir, :uri)
|
||||
Application.put_env(:elixir, :uri, Map.put(dict, scheme, port), persist: true)
|
||||
Application.put_env(:elixir, :uri, Map.put(dict, scheme, port), persistent: true)
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -126,49 +126,98 @@ defmodule URI do
|
||||
|
||||
current =
|
||||
case :binary.split(first, "=") do
|
||||
[ key, value ] -> {decode(key), decode(value)}
|
||||
[ key ] -> {decode(key), nil}
|
||||
[key, value] ->
|
||||
{decode_www_form(key), decode_www_form(value)}
|
||||
[key] ->
|
||||
{decode_www_form(key), nil}
|
||||
end
|
||||
|
||||
{current, next}
|
||||
end
|
||||
|
||||
defp pair({k, _}) when is_list(k) do
|
||||
raise ArgumentError, message: "encode_query/1 keys cannot be lists, got: #{inspect k}"
|
||||
raise ArgumentError, "encode_query/1 keys cannot be lists, got: #{inspect k}"
|
||||
end
|
||||
|
||||
defp pair({_, v}) when is_list(v) do
|
||||
raise ArgumentError, message: "encode_query/1 values cannot be lists, got: #{inspect v}"
|
||||
raise ArgumentError, "encode_query/1 values cannot be lists, got: #{inspect v}"
|
||||
end
|
||||
|
||||
defp pair({k, v}) do
|
||||
encode(to_string(k)) <> "=" <> encode(to_string(v))
|
||||
encode_www_form(to_string(k)) <>
|
||||
"=" <> encode_www_form(to_string(v))
|
||||
end
|
||||
|
||||
@doc """
|
||||
Checks if the character is a "reserved" character in a URI.
|
||||
|
||||
Reserved characters are specified in RFC3986, section 2.2.
|
||||
"""
|
||||
def char_reserved?(c) do
|
||||
c in ':/?#[]@!$&\'()*+,;='
|
||||
end
|
||||
|
||||
@doc """
|
||||
Checks if the character is a "unreserved" character in a URI.
|
||||
|
||||
Unreserved characters are specified in RFC3986, section 2.3.
|
||||
"""
|
||||
def char_unreserved?(c) do
|
||||
c in ?0..?9 or
|
||||
c in ?a..?z or
|
||||
c in ?A..?Z or
|
||||
c in '~_-.'
|
||||
end
|
||||
|
||||
@doc """
|
||||
Checks if the character is allowed unescaped in a URI.
|
||||
|
||||
This is the default used by `URI.encode/2` where both
|
||||
reserved and unreserved characters are kept unescaped.
|
||||
"""
|
||||
def char_unescaped?(c) do
|
||||
char_reserved?(c) or char_unreserved?(c)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Percent-escape a URI.
|
||||
Accepts `predicate` function as an argument to specify if char can be left as is.
|
||||
|
||||
## Example
|
||||
|
||||
iex> URI.encode("http://elixir-lang.org/getting_started/2.html")
|
||||
"http%3A%2F%2Felixir-lang.org%2Fgetting_started%2F2.html"
|
||||
iex> URI.encode("ftp://s-ite.tld/?value=put it+й")
|
||||
"ftp://s-ite.tld/?value=put%20it+%D0%B9"
|
||||
|
||||
"""
|
||||
def encode(s), do: for(<<c <- s>>, into: "", do: percent(c))
|
||||
|
||||
defp percent(32), do: <<?+>>
|
||||
defp percent(?-), do: <<?->>
|
||||
defp percent(?_), do: <<?_>>
|
||||
defp percent(?.), do: <<?.>>
|
||||
|
||||
defp percent(c)
|
||||
when c >= ?0 and c <= ?9
|
||||
when c >= ?a and c <= ?z
|
||||
when c >= ?A and c <= ?Z do
|
||||
<<c>>
|
||||
def encode(str, predicate \\ &char_unescaped?/1) when is_binary(str) do
|
||||
for <<c <- str>>, into: "", do: percent(c, predicate)
|
||||
end
|
||||
|
||||
defp percent(c), do: "%" <> hex(bsr(c, 4)) <> hex(band(c, 15))
|
||||
@doc """
|
||||
Encode a string as "x-www-urlencoded".
|
||||
|
||||
## Example
|
||||
|
||||
iex> URI.encode_www_form("put: it+й")
|
||||
"put%3A+it%2B%D0%B9"
|
||||
|
||||
"""
|
||||
def encode_www_form(str) when is_binary(str) do
|
||||
for <<c <- str>>, into: "" do
|
||||
case percent(c, &char_unreserved?/1) do
|
||||
"%20" -> "+"
|
||||
pct -> pct
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defp percent(c, predicate) do
|
||||
if predicate.(c) do
|
||||
<<c>>
|
||||
else
|
||||
"%" <> hex(bsr(c, 4)) <> hex(band(c, 15))
|
||||
end
|
||||
end
|
||||
|
||||
defp hex(n) when n <= 9, do: <<n + ?0>>
|
||||
defp hex(n), do: <<n + ?A - 10>>
|
||||
@@ -183,28 +232,44 @@ defmodule URI do
|
||||
|
||||
"""
|
||||
def decode(uri) do
|
||||
decode(uri, uri)
|
||||
unpercent(uri)
|
||||
catch
|
||||
:malformed_uri ->
|
||||
raise ArgumentError, "malformed URI #{inspect uri}"
|
||||
end
|
||||
|
||||
def decode(<<?%, hex1, hex2, tail :: binary >>, uri) do
|
||||
<< bsl(hex2dec(hex1, uri), 4) + hex2dec(hex2, uri) >> <> decode(tail, uri)
|
||||
@doc """
|
||||
Decode a string as "x-www-urlencoded".
|
||||
|
||||
## Examples
|
||||
|
||||
iex> URI.decode_www_form("%3Call+in%2F")
|
||||
"<all in/"
|
||||
|
||||
"""
|
||||
def decode_www_form(str) do
|
||||
String.split(str, "+") |> Enum.map_join(" ", &unpercent/1)
|
||||
catch
|
||||
:malformed_uri ->
|
||||
raise ArgumentError, "malformed URI #{inspect str}"
|
||||
end
|
||||
|
||||
def decode(<<head, tail :: binary >>, uri) do
|
||||
<<check_plus(head)>> <> decode(tail, uri)
|
||||
defp unpercent(<<?%, hex_1, hex_2, tail :: binary>>) do
|
||||
<<bsl(hex_to_dec(hex_1), 4) + hex_to_dec(hex_2)>> <> unpercent(tail)
|
||||
end
|
||||
defp unpercent(<<?%, _>>), do: throw(:malformed_uri)
|
||||
defp unpercent(<<?%>>), do: throw(:malformed_uri)
|
||||
|
||||
defp unpercent(<<head, tail :: binary>>) do
|
||||
<<head>> <> unpercent(tail)
|
||||
end
|
||||
|
||||
def decode(<<>>, _uri), do: <<>>
|
||||
defp unpercent(<<>>), do: <<>>
|
||||
|
||||
defp hex2dec(n, _uri) when n in ?A..?F, do: n - ?A + 10
|
||||
defp hex2dec(n, _uri) when n in ?a..?f, do: n - ?a + 10
|
||||
defp hex2dec(n, _uri) when n in ?0..?9, do: n - ?0
|
||||
defp hex2dec(_n, uri) do
|
||||
raise ArgumentError, message: "malformed URI #{inspect uri}"
|
||||
end
|
||||
|
||||
defp check_plus(?+), do: 32
|
||||
defp check_plus(c), do: c
|
||||
defp hex_to_dec(n) when n in ?A..?F, do: n - ?A + 10
|
||||
defp hex_to_dec(n) when n in ?a..?f, do: n - ?a + 10
|
||||
defp hex_to_dec(n) when n in ?0..?9, do: n - ?0
|
||||
defp hex_to_dec(_n), do: throw(:malformed_uri)
|
||||
|
||||
@doc """
|
||||
Parses a URI into components.
|
||||
@@ -222,6 +287,8 @@ defmodule URI do
|
||||
host: "elixir-lang.org", port: 80}
|
||||
|
||||
"""
|
||||
def parse(%URI{} = uri), do: uri
|
||||
|
||||
def parse(s) when is_binary(s) do
|
||||
# From http://tools.ietf.org/html/rfc3986#appendix-B
|
||||
regex = ~r/^(([^:\/?#]+):)?(\/\/([^\/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?/
|
||||
@@ -235,7 +302,7 @@ defmodule URI do
|
||||
|
||||
if userinfo, do: authority = authority <> userinfo <> "@"
|
||||
if host, do: authority = authority <> host
|
||||
if port, do: authority = authority <> ":" <> integer_to_binary(port)
|
||||
if port, do: authority = authority <> ":" <> Integer.to_string(port)
|
||||
end
|
||||
|
||||
scheme = normalize_scheme(scheme)
|
||||
@@ -257,7 +324,7 @@ defmodule URI do
|
||||
components = Regex.run ~r/(^(.*)@)?(\[[a-zA-Z0-9:.]*\]|[^:]*)(:(\d*))?/, s
|
||||
|
||||
destructure [_, _, userinfo, host, _, port], nillify(components)
|
||||
port = if port, do: binary_to_integer(port)
|
||||
port = if port, do: String.to_integer(port)
|
||||
host = if host, do: host |> String.lstrip(?[) |> String.rstrip(?])
|
||||
|
||||
{userinfo, host, port}
|
||||
@@ -267,7 +334,7 @@ defmodule URI do
|
||||
# to replace those with nil for consistency.
|
||||
defp nillify(l) do
|
||||
for s <- l do
|
||||
if size(s) > 0, do: s, else: nil
|
||||
if byte_size(s) > 0, do: s, else: nil
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -285,7 +352,7 @@ defimpl String.Chars, for: URI do
|
||||
if uri.scheme, do: result = result <> uri.scheme <> "://"
|
||||
if uri.userinfo, do: result = result <> uri.userinfo <> "@"
|
||||
if uri.host, do: result = result <> uri.host
|
||||
if uri.port, do: result = result <> ":" <> integer_to_binary(uri.port)
|
||||
if uri.port, do: result = result <> ":" <> Integer.to_string(uri.port)
|
||||
if uri.path, do: result = result <> uri.path
|
||||
if uri.query, do: result = result <> "?" <> uri.query
|
||||
if uri.fragment, do: result = result <> "#" <> uri.fragment
|
||||
|
||||
+19
-14
@@ -68,15 +68,20 @@ defmodule Version do
|
||||
defstruct [:source, :matchspec]
|
||||
end
|
||||
|
||||
defexception InvalidRequirement, [:message]
|
||||
defexception InvalidVersion, [:message]
|
||||
defmodule InvalidRequirementError do
|
||||
defexception [:message]
|
||||
end
|
||||
|
||||
defmodule InvalidVersionError do
|
||||
defexception [:message]
|
||||
end
|
||||
|
||||
@doc """
|
||||
Check if the given version matches the specification.
|
||||
|
||||
Returns `true` if `version` satisfies `requirement`, `false` otherwise.
|
||||
Raises a `Version.InvalidRequirement` exception if `requirement` is not
|
||||
parseable, or `Version.InvalidVersion` if `version` is not parseable.
|
||||
Raises a `Version.InvalidRequirementError` exception if `requirement` is not
|
||||
parseable, or `Version.InvalidVersionError` if `version` is not parseable.
|
||||
If given an already parsed version and requirement this function won't
|
||||
raise.
|
||||
|
||||
@@ -89,10 +94,10 @@ defmodule Version do
|
||||
false
|
||||
|
||||
iex> Version.match?("foo", "==1.0.0")
|
||||
** (Version.InvalidVersion) foo
|
||||
** (Version.InvalidVersionError) foo
|
||||
|
||||
iex> Version.match?("2.0.0", "== ==1.0.0")
|
||||
** (Version.InvalidRequirement) == ==1.0.0
|
||||
** (Version.InvalidRequirementError) == ==1.0.0
|
||||
|
||||
"""
|
||||
@spec match?(version, requirement) :: boolean
|
||||
@@ -101,7 +106,7 @@ defmodule Version do
|
||||
{:ok, req} ->
|
||||
match?(vsn, req)
|
||||
:error ->
|
||||
raise InvalidRequirement, message: req
|
||||
raise InvalidRequirementError, message: req
|
||||
end
|
||||
end
|
||||
|
||||
@@ -115,7 +120,7 @@ defmodule Version do
|
||||
the second and `:lt` for vice versa. If the two versions are equal `:eq`
|
||||
is returned
|
||||
|
||||
Raises a `Version.InvalidVersion` exception if `version` is not parseable.
|
||||
Raises a `Version.InvalidVersionError` exception if `version` is not parseable.
|
||||
If given an already parsed version this function won't raise.
|
||||
|
||||
## Examples
|
||||
@@ -127,7 +132,7 @@ defmodule Version do
|
||||
:eq
|
||||
|
||||
iex> Version.compare("invalid", "2.0.1")
|
||||
** (Version.InvalidVersion) invalid
|
||||
** (Version.InvalidVersionError) invalid
|
||||
|
||||
"""
|
||||
@spec compare(version, version) :: :gt | :eq | :lt
|
||||
@@ -200,7 +205,7 @@ defmodule Version do
|
||||
defp to_matchable(string) do
|
||||
case Version.Parser.parse_version(string) do
|
||||
{:ok, vsn} -> vsn
|
||||
:error -> raise InvalidVersion, message: string
|
||||
:error -> raise InvalidVersionError, message: string
|
||||
end
|
||||
end
|
||||
|
||||
@@ -308,9 +313,9 @@ defmodule Version do
|
||||
if nil?(minor) or (nil?(patch) and not approximate?) do
|
||||
:error
|
||||
else
|
||||
major = binary_to_integer(major)
|
||||
minor = binary_to_integer(minor)
|
||||
patch = patch && binary_to_integer(patch)
|
||||
major = String.to_integer(major)
|
||||
minor = String.to_integer(minor)
|
||||
patch = patch && String.to_integer(patch)
|
||||
|
||||
case parse_pre(pre) do
|
||||
{:ok, pre} ->
|
||||
@@ -330,7 +335,7 @@ defmodule Version do
|
||||
defp parse_pre([piece|t], acc) do
|
||||
cond do
|
||||
piece =~ ~r/^(0|[1-9][0-9]*)$/ ->
|
||||
parse_pre(t, [binary_to_integer(piece)|acc])
|
||||
parse_pre(t, [String.to_integer(piece)|acc])
|
||||
piece =~ ~r/^[0-9]*$/ ->
|
||||
:error
|
||||
true ->
|
||||
|
||||
+31
-25
@@ -9,9 +9,8 @@
|
||||
-include("elixir.hrl").
|
||||
|
||||
%% Top level types
|
||||
-export_type([char_list/0, char_data/0, as_boolean/1]).
|
||||
-export_type([char_list/0, as_boolean/1]).
|
||||
-type char_list() :: string().
|
||||
-type char_data() :: unicode:chardata().
|
||||
-type as_boolean(T) :: T.
|
||||
|
||||
%% OTP Application API
|
||||
@@ -19,21 +18,28 @@
|
||||
-export([start/2, stop/1, config_change/3]).
|
||||
|
||||
start(_Type, _Args) ->
|
||||
%% Set the shell to unicode so printing inside scripts work
|
||||
%% Those can take a while, so let's do it in a new process
|
||||
spawn(fun() ->
|
||||
io:setopts(standard_io, [binary,{encoding,utf8}]),
|
||||
io:setopts(standard_error, [{unicode,true}]),
|
||||
case file:native_name_encoding() of
|
||||
latin1 ->
|
||||
io:format(standard_error,
|
||||
"warning: the VM is running with native name encoding of latin1 which may cause "
|
||||
"Elixir to malfunction as it expects utf8. Please ensure your locale is set to UTF-8 "
|
||||
"(which can be verified by running \"locale\" in your shell)~n", []);
|
||||
_ ->
|
||||
ok
|
||||
end
|
||||
end),
|
||||
%% In case there is a shell, we can't really change its
|
||||
%% encoding, so we just set binary to true. Otherwise
|
||||
%% we must set the encoding as the user with no shell
|
||||
%% has encoding set to latin1.
|
||||
Opts =
|
||||
case init:get_argument(noshell) of
|
||||
{ok, _} -> [binary,{encoding,utf8}];
|
||||
error -> [binary]
|
||||
end,
|
||||
|
||||
io:setopts(standard_io, Opts),
|
||||
io:setopts(standard_error, [{unicode,true}]),
|
||||
case file:native_name_encoding() of
|
||||
latin1 ->
|
||||
io:format(standard_error,
|
||||
"warning: the VM is running with native name encoding of latin1 which may cause "
|
||||
"Elixir to malfunction as it expects utf8. Please ensure your locale is set to UTF-8 "
|
||||
"(which can be verified by running \"locale\" in your shell)~n", []);
|
||||
_ ->
|
||||
ok
|
||||
end,
|
||||
|
||||
elixir_sup:start_link().
|
||||
|
||||
stop(_S) ->
|
||||
@@ -66,37 +72,37 @@ env_for_eval(Opts) ->
|
||||
|
||||
env_for_eval(Env, Opts) ->
|
||||
Line = case lists:keyfind(line, 1, Opts) of
|
||||
{line, RawLine} when is_integer(RawLine) -> RawLine;
|
||||
{line, LineOpt} when is_integer(LineOpt) -> LineOpt;
|
||||
false -> ?m(Env, line)
|
||||
end,
|
||||
|
||||
File = case lists:keyfind(file, 1, Opts) of
|
||||
{file, RawFile} when is_binary(RawFile) -> RawFile;
|
||||
{file, FileOpt} when is_binary(FileOpt) -> FileOpt;
|
||||
false -> ?m(Env, file)
|
||||
end,
|
||||
|
||||
Local = case lists:keyfind(delegate_locals_to, 1, Opts) of
|
||||
{delegate_locals_to, LocalOpt} -> LocalOpt;
|
||||
{delegate_locals_to, LocalOpt} when is_atom(LocalOpt) -> LocalOpt;
|
||||
false -> ?m(Env, local)
|
||||
end,
|
||||
|
||||
Aliases = case lists:keyfind(aliases, 1, Opts) of
|
||||
{aliases, AliasesOpt} -> AliasesOpt;
|
||||
{aliases, AliasesOpt} when is_list(AliasesOpt) -> AliasesOpt;
|
||||
false -> ?m(Env, aliases)
|
||||
end,
|
||||
|
||||
Requires = case lists:keyfind(requires, 1, Opts) of
|
||||
{requires, List} -> ordsets:from_list(List);
|
||||
{requires, RequiresOpt} when is_list(RequiresOpt) -> ordsets:from_list(RequiresOpt);
|
||||
false -> ?m(Env, requires)
|
||||
end,
|
||||
|
||||
Functions = case lists:keyfind(functions, 1, Opts) of
|
||||
{functions, FunctionsOpt} -> FunctionsOpt;
|
||||
{functions, FunctionsOpt} when is_list(FunctionsOpt) -> FunctionsOpt;
|
||||
false -> ?m(Env, functions)
|
||||
end,
|
||||
|
||||
Macros = case lists:keyfind(macros, 1, Opts) of
|
||||
{macros, MacrosOpt} -> MacrosOpt;
|
||||
{macros, MacrosOpt} when is_list(MacrosOpt) -> MacrosOpt;
|
||||
false -> ?m(Env, macros)
|
||||
end,
|
||||
|
||||
@@ -109,7 +115,7 @@ env_for_eval(Env, Opts) ->
|
||||
file := File, local := Local, module := Module,
|
||||
macros := Macros, functions := Functions,
|
||||
requires := Requires, aliases := Aliases, line := Line
|
||||
}.
|
||||
}.
|
||||
|
||||
%% String evaluation
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
inspect(Atom) when is_atom(Atom) ->
|
||||
case elixir_compiler:get_opt(internal) of
|
||||
true -> atom_to_binary(Atom, utf8);
|
||||
true -> atom_to_binary(Atom, utf8);
|
||||
false -> 'Elixir.Inspect.Atom':inspect(Atom)
|
||||
end.
|
||||
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
%% Handle code related to args, guard and -> matching for case,
|
||||
%% fn, receive and friends. try is handled in elixir_try.
|
||||
-module(elixir_clauses).
|
||||
-export([match/3, clause/7, clauses/4, get_pairs/2, get_pairs/3,
|
||||
-export([match/3, clause/7, clauses/4, guards/4, get_pairs/3, get_pairs/4,
|
||||
extract_splat_guards/1, extract_guards/1]).
|
||||
-include("elixir.hrl").
|
||||
|
||||
%% Get pairs from a clause.
|
||||
|
||||
get_pairs(Key, Clauses) ->
|
||||
get_pairs(Key, Clauses, false).
|
||||
get_pairs(Key, Clauses, AllowNil) ->
|
||||
get_pairs(Key, Clauses, As) ->
|
||||
get_pairs(Key, Clauses, As, false).
|
||||
get_pairs(Key, Clauses, As, AllowNil) ->
|
||||
case lists:keyfind(Key, 1, Clauses) of
|
||||
{Key, Pairs} when is_list(Pairs) ->
|
||||
[{Key, Meta, Left, Right} || {'->', Meta, [Left, Right]} <- Pairs];
|
||||
[{As, Meta, Left, Right} || {'->', Meta, [Left, Right]} <- Pairs];
|
||||
{Key, nil} when AllowNil ->
|
||||
[];
|
||||
false ->
|
||||
@@ -35,18 +35,20 @@ clause(Line, Fun, Args, Expr, Guards, Return, S) when is_integer(Line) ->
|
||||
{TArgs, SA} = match(Fun, Args, S#elixir_scope{extra_guards=[]}),
|
||||
{TExpr, SE} = elixir_translator:translate_block(Expr, Return, SA#elixir_scope{extra_guards=nil}),
|
||||
|
||||
SG = SA#elixir_scope{context=guard, extra_guards=nil},
|
||||
Extra = SA#elixir_scope.extra_guards,
|
||||
|
||||
TGuards = case Guards of
|
||||
[] -> case Extra of [] -> []; _ -> [Extra] end;
|
||||
_ -> [translate_guard(Line, Guard, Extra, SG) || Guard <- Guards]
|
||||
end,
|
||||
|
||||
TGuards = guards(Line, Guards, Extra, SA),
|
||||
{{clause, Line, TArgs, TGuards, unblock(TExpr)}, SE}.
|
||||
|
||||
% Translate/Extract guards from the given expression.
|
||||
|
||||
guards(Line, Guards, Extra, S) ->
|
||||
SG = S#elixir_scope{context=guard, extra_guards=nil},
|
||||
|
||||
case Guards of
|
||||
[] -> case Extra of [] -> []; _ -> [Extra] end;
|
||||
_ -> [translate_guard(Line, Guard, Extra, SG) || Guard <- Guards]
|
||||
end.
|
||||
|
||||
translate_guard(Line, Guard, Extra, S) ->
|
||||
[element(1, elixir_translator:translate(elixir_quote:linify(Line, Guard), S))|Extra].
|
||||
|
||||
@@ -77,9 +79,8 @@ do_clauses(Meta, DecoupledClauses, Return, S) ->
|
||||
% Transform tree just passing the variables counter forward
|
||||
% and storing variables defined inside each clause.
|
||||
Transformer = fun(X, {SAcc, VAcc}) ->
|
||||
{TX, TS} = each_clause(X, Return, SAcc),
|
||||
{TX,
|
||||
{elixir_scope:mergec(S, TS), [TS#elixir_scope.export_vars|VAcc]}}
|
||||
{TX, TS} = each_clause(Meta, X, Return, SAcc),
|
||||
{TX, {elixir_scope:mergec(S, TS), [TS#elixir_scope.export_vars|VAcc]}}
|
||||
end,
|
||||
|
||||
{TClauses, {TS, ReverseCV}} =
|
||||
@@ -138,19 +139,27 @@ expand_clauses(_Line, [], [], _FinalVars, Acc, S) ->
|
||||
|
||||
% Handle each key/value clause pair and translate them accordingly.
|
||||
|
||||
each_clause({do, Meta, [Condition], Expr}, Return, S) ->
|
||||
each_clause(Export, {match, Meta, [Condition], Expr}, Return, S) ->
|
||||
Fun = wrap_export_fun(Export, fun elixir_translator:translate_args/2),
|
||||
{Arg, Guards} = extract_guards(Condition),
|
||||
clause(?line(Meta), fun elixir_translator:translate_args/2, [Arg], Expr, Guards, Return, S);
|
||||
clause(?line(Meta), Fun, [Arg], Expr, Guards, Return, S);
|
||||
|
||||
each_clause({else, Meta, [Condition], Expr}, Return, S) ->
|
||||
{Arg, Guards} = extract_guards(Condition),
|
||||
clause(?line(Meta), fun elixir_translator:translate_args/2, [Arg], Expr, Guards, Return, S);
|
||||
|
||||
each_clause({'after', Meta, [Condition], Expr}, Return, S) ->
|
||||
{TCondition, SC} = elixir_translator:translate(Condition, S),
|
||||
each_clause(Export, {expr, Meta, [Condition], Expr}, Return, S) ->
|
||||
{TCondition, SC} = (wrap_export_fun(Export, fun elixir_translator:translate/2))(Condition, S),
|
||||
{TExpr, SB} = elixir_translator:translate_block(Expr, Return, SC),
|
||||
{{clause, ?line(Meta), [TCondition], [], unblock(TExpr)}, SB}.
|
||||
|
||||
wrap_export_fun(Meta, Fun) ->
|
||||
case lists:keyfind(export_head, 1, Meta) of
|
||||
{export_head, true} ->
|
||||
Fun;
|
||||
_ ->
|
||||
fun(Args, S) ->
|
||||
{TArgs, TS} = Fun(Args, S),
|
||||
{TArgs, TS#elixir_scope{export_vars = S#elixir_scope.export_vars}}
|
||||
end
|
||||
end.
|
||||
|
||||
% Check if the given expression is a match tuple.
|
||||
% This is a small optimization to allow us to change
|
||||
% existing assignments instead of creating new ones every time.
|
||||
@@ -179,7 +188,7 @@ normalize_vars(Key, Value, #elixir_scope{vars=Vars,export_vars=ClauseVars} = S)
|
||||
VS = S#elixir_scope{
|
||||
vars=orddict:store(Key, Value, Vars),
|
||||
export_vars=orddict:store(Key, Value, ClauseVars)
|
||||
},
|
||||
},
|
||||
|
||||
Expr = case orddict:find(Key, Vars) of
|
||||
{ok, {PreValue, _}} -> {var, 0, PreValue};
|
||||
|
||||
@@ -68,7 +68,9 @@ code_loading_compilation(Forms, Vars, #{line := Line} = E) ->
|
||||
|
||||
%% Pass {native, false} to speed up bootstrap
|
||||
%% process when native is set to true
|
||||
module(Form, ?m(E, file), [{native,false}], true, fun(_, Binary) ->
|
||||
AllOpts = elixir_code_server:call(erl_compiler_options),
|
||||
FinalOpts = AllOpts -- [native, warn_missing_spec],
|
||||
module(Form, ?m(E, file), FinalOpts, true, fun(_, Binary) ->
|
||||
%% If we have labeled locals, anonymous functions
|
||||
%% were created and therefore we cannot ditch the
|
||||
%% module
|
||||
@@ -130,14 +132,13 @@ module(Forms, File, Opts, Callback) ->
|
||||
Final =
|
||||
case (get_opt(debug_info) == true) orelse
|
||||
lists:member(debug_info, Opts) of
|
||||
true -> [debug_info];
|
||||
false -> []
|
||||
true -> [debug_info] ++ elixir_code_server:call(erl_compiler_options);
|
||||
false -> elixir_code_server:call(erl_compiler_options)
|
||||
end,
|
||||
module(Forms, File, Final, false, Callback).
|
||||
|
||||
module(Forms, File, RawOptions, Bootstrap, Callback) when
|
||||
is_binary(File), is_list(Forms), is_list(RawOptions), is_boolean(Bootstrap), is_function(Callback) ->
|
||||
Options = RawOptions ++ elixir_code_server:call(erl_compiler_options),
|
||||
module(Forms, File, Options, Bootstrap, Callback) when
|
||||
is_binary(File), is_list(Forms), is_list(Options), is_boolean(Bootstrap), is_function(Callback) ->
|
||||
Listname = elixir_utils:characters_to_list(File),
|
||||
|
||||
case compile:noenv_forms([no_auto_import()|Forms], [return,{source,Listname}|Options]) of
|
||||
@@ -172,38 +173,33 @@ core_file(File) ->
|
||||
end.
|
||||
|
||||
core_main() ->
|
||||
[
|
||||
<<"lib/elixir/lib/kernel.ex">>,
|
||||
<<"lib/elixir/lib/macro/env.ex">>,
|
||||
<<"lib/elixir/lib/keyword.ex">>,
|
||||
<<"lib/elixir/lib/module.ex">>,
|
||||
<<"lib/elixir/lib/list.ex">>,
|
||||
<<"lib/elixir/lib/record.ex">>,
|
||||
<<"lib/elixir/lib/record/backend.ex">>,
|
||||
<<"lib/elixir/lib/record/deprecated.ex">>,
|
||||
<<"lib/elixir/lib/macro.ex">>,
|
||||
<<"lib/elixir/lib/exception.ex">>,
|
||||
<<"lib/elixir/lib/code.ex">>,
|
||||
<<"lib/elixir/lib/module/locals_tracker.ex">>,
|
||||
<<"lib/elixir/lib/protocol.ex">>,
|
||||
<<"lib/elixir/lib/stream/reducers.ex">>,
|
||||
<<"lib/elixir/lib/enum.ex">>,
|
||||
<<"lib/elixir/lib/inspect/algebra.ex">>,
|
||||
<<"lib/elixir/lib/inspect.ex">>,
|
||||
<<"lib/elixir/lib/range.ex">>,
|
||||
<<"lib/elixir/lib/regex.ex">>,
|
||||
<<"lib/elixir/lib/string.ex">>,
|
||||
<<"lib/elixir/lib/string/chars.ex">>,
|
||||
<<"lib/elixir/lib/collectable.ex">>,
|
||||
<<"lib/elixir/lib/io.ex">>,
|
||||
<<"lib/elixir/lib/path.ex">>,
|
||||
<<"lib/elixir/lib/system.ex">>,
|
||||
<<"lib/elixir/lib/kernel/typespec.ex">>,
|
||||
<<"lib/elixir/lib/kernel/cli.ex">>,
|
||||
<<"lib/elixir/lib/kernel/error_handler.ex">>,
|
||||
<<"lib/elixir/lib/kernel/parallel_compiler.ex">>,
|
||||
<<"lib/elixir/lib/kernel/lexical_tracker.ex">>
|
||||
].
|
||||
[<<"lib/elixir/lib/kernel.ex">>,
|
||||
<<"lib/elixir/lib/macro/env.ex">>,
|
||||
<<"lib/elixir/lib/keyword.ex">>,
|
||||
<<"lib/elixir/lib/module.ex">>,
|
||||
<<"lib/elixir/lib/list.ex">>,
|
||||
<<"lib/elixir/lib/macro.ex">>,
|
||||
<<"lib/elixir/lib/code.ex">>,
|
||||
<<"lib/elixir/lib/module/locals_tracker.ex">>,
|
||||
<<"lib/elixir/lib/kernel/typespec.ex">>,
|
||||
<<"lib/elixir/lib/exception.ex">>,
|
||||
<<"lib/elixir/lib/protocol.ex">>,
|
||||
<<"lib/elixir/lib/stream/reducers.ex">>,
|
||||
<<"lib/elixir/lib/enum.ex">>,
|
||||
<<"lib/elixir/lib/inspect/algebra.ex">>,
|
||||
<<"lib/elixir/lib/inspect.ex">>,
|
||||
<<"lib/elixir/lib/range.ex">>,
|
||||
<<"lib/elixir/lib/regex.ex">>,
|
||||
<<"lib/elixir/lib/string.ex">>,
|
||||
<<"lib/elixir/lib/string/chars.ex">>,
|
||||
<<"lib/elixir/lib/io.ex">>,
|
||||
<<"lib/elixir/lib/path.ex">>,
|
||||
<<"lib/elixir/lib/file.ex">>,
|
||||
<<"lib/elixir/lib/system.ex">>,
|
||||
<<"lib/elixir/lib/kernel/cli.ex">>,
|
||||
<<"lib/elixir/lib/kernel/error_handler.ex">>,
|
||||
<<"lib/elixir/lib/kernel/parallel_compiler.ex">>,
|
||||
<<"lib/elixir/lib/kernel/lexical_tracker.ex">>].
|
||||
|
||||
binary_to_path({ModuleName, Binary}, CompilePath) ->
|
||||
Path = filename:join(CompilePath, atom_to_list(ModuleName) ++ ".beam"),
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
% Holds the logic responsible for functions definition (def(p) and defmacro(p)).
|
||||
% Holds the logic responsible for function definitions (def(p) and defmacro(p)).
|
||||
-module(elixir_def).
|
||||
-export([table/1, clauses_table/1, setup/1,
|
||||
cleanup/1, reset_last/1, lookup_definition/2,
|
||||
@@ -129,20 +129,28 @@ make_struct_available(_, _, _, _) ->
|
||||
%% Retrieve location from meta file or @file, otherwise nil
|
||||
|
||||
retrieve_location(Line, File, Module) ->
|
||||
case not(elixir_compiler:get_opt(internal)) andalso
|
||||
'Elixir.Module':get_attribute(Module, file) of
|
||||
X when X == nil; X == false ->
|
||||
if
|
||||
is_binary(File) ->
|
||||
{elixir_utils:characters_to_list(elixir_utils:relative_to_cwd(File)), Line};
|
||||
true ->
|
||||
nil
|
||||
end;
|
||||
case get_location_attribute(Module) of
|
||||
nil when not is_binary(File) ->
|
||||
nil;
|
||||
nil ->
|
||||
{normalize_location(File), Line};
|
||||
X when is_binary(X) ->
|
||||
'Elixir.Module':delete_attribute(Module, file),
|
||||
{elixir_utils:characters_to_list(X), 0}
|
||||
{normalize_location(X), 0};
|
||||
{X, L} when is_binary(X) andalso is_integer(L) ->
|
||||
'Elixir.Module':delete_attribute(Module, file),
|
||||
{normalize_location(X), L}
|
||||
end.
|
||||
|
||||
get_location_attribute(Module) ->
|
||||
case elixir_compiler:get_opt(internal) of
|
||||
true -> nil;
|
||||
false -> 'Elixir.Module':get_attribute(Module, file)
|
||||
end.
|
||||
|
||||
normalize_location(X) ->
|
||||
elixir_utils:characters_to_list(elixir_utils:relative_to_cwd(X)).
|
||||
|
||||
%% Compile super
|
||||
|
||||
compile_super(Module, true, #{function := Function}) ->
|
||||
@@ -158,16 +166,10 @@ translate_definition(Kind, Line, Module, Name, Args, Guards, Body, E) when is_in
|
||||
{EArgs, EGuards, EBody, _} = elixir_exp_clauses:def(fun elixir_def_defaults:expand/2,
|
||||
Args, Guards, expr_from_body(Line, Body), E),
|
||||
|
||||
%% Macros receive a special argument on invocation. Notice it does
|
||||
%% not affect the arity of the stored function, but the clause
|
||||
%% already contains it.
|
||||
EAllArgs = case is_macro(Kind) of
|
||||
true -> [{'_@CALLER', [{line,Line}], nil}|EArgs];
|
||||
false -> EArgs
|
||||
end,
|
||||
Body == nil andalso check_args_for_bodyless_clause(Line, EArgs, E),
|
||||
|
||||
S = elixir_env:env_to_scope(E),
|
||||
{Unpacked, Defaults} = elixir_def_defaults:unpack(Kind, Name, EAllArgs, S),
|
||||
{Unpacked, Defaults} = elixir_def_defaults:unpack(Kind, Name, EArgs, S),
|
||||
{Clauses, Super} = translate_clause(Body, Line, Kind, Unpacked, EGuards, EBody, S),
|
||||
|
||||
run_on_definition_callbacks(Kind, Line, Module, Name, EArgs, EGuards, EBody, E),
|
||||
@@ -182,22 +184,30 @@ translate_clause(_, Line, Kind, Args, Guards, Body, S) ->
|
||||
{TClause, TS} = elixir_clauses:clause(Line,
|
||||
fun elixir_translator:translate_args/2, Args, Body, Guards, true, S),
|
||||
|
||||
%% Set __CALLER__ if used
|
||||
FClause = case is_macro(Kind) andalso TS#elixir_scope.caller of
|
||||
true ->
|
||||
FBody = {'match', Line,
|
||||
{'var', Line, '__CALLER__'},
|
||||
elixir_utils:erl_call(Line, elixir_env, linify, [{var, Line, '_@CALLER'}])
|
||||
},
|
||||
setelement(5, TClause, [FBody|element(5, TClause)]);
|
||||
false -> TClause
|
||||
FClause = case is_macro(Kind) of
|
||||
true ->
|
||||
FArgs = {var, Line, '_@CALLER'},
|
||||
MClause = setelement(3, TClause, [FArgs|element(3, TClause)]),
|
||||
|
||||
case TS#elixir_scope.caller of
|
||||
true ->
|
||||
FBody = {'match', Line,
|
||||
{'var', Line, '__CALLER__'},
|
||||
elixir_utils:erl_call(Line, elixir_env, linify, [{var, Line, '_@CALLER'}])
|
||||
},
|
||||
setelement(5, MClause, [FBody|element(5, TClause)]);
|
||||
false ->
|
||||
MClause
|
||||
end;
|
||||
false ->
|
||||
TClause
|
||||
end,
|
||||
|
||||
{[FClause], TS#elixir_scope.super}.
|
||||
|
||||
expr_from_body(_Line, nil) -> nil;
|
||||
expr_from_body(_Line, nil) -> nil;
|
||||
expr_from_body(_Line, [{do, Expr}]) -> Expr;
|
||||
expr_from_body(Line, Else) -> {'try', [{line,Line}], [Else]}.
|
||||
expr_from_body(Line, Else) -> {'try', [{line,Line}], [Else]}.
|
||||
|
||||
is_macro(defmacro) -> true;
|
||||
is_macro(defmacrop) -> true;
|
||||
@@ -327,6 +337,18 @@ defaults_conflict(A, D, Arity, Defaults) ->
|
||||
((Arity >= (A - D)) andalso (Arity < A)) orelse
|
||||
((A >= (Arity - Defaults)) andalso (A < Arity)).
|
||||
|
||||
check_args_for_bodyless_clause(Line, Args, E) ->
|
||||
[ begin
|
||||
elixir_errors:form_error(Line, ?m(E, file), ?MODULE, invalid_args_for_bodyless_clause)
|
||||
end || Arg <- Args, invalid_arg(Arg) ].
|
||||
|
||||
invalid_arg({Name, _, Kind}) when is_atom(Name), is_atom(Kind) ->
|
||||
false;
|
||||
invalid_arg({'\\\\', _, [{Name, _, Kind}, _]}) when is_atom(Name), is_atom(Kind) ->
|
||||
false;
|
||||
invalid_arg(_) ->
|
||||
true.
|
||||
|
||||
assert_no_aliases_name(Line, '__aliases__', [Atom], #{file := File}) when is_atom(Atom) ->
|
||||
elixir_errors:form_error(Line, File, ?MODULE, {no_alias, Atom});
|
||||
|
||||
@@ -366,5 +388,8 @@ format_error({no_alias, Atom}) ->
|
||||
format_error({invalid_def, Kind, NameAndArgs}) ->
|
||||
io_lib:format("invalid syntax in ~ts ~ts", [Kind, 'Elixir.Macro':to_string(NameAndArgs)]);
|
||||
|
||||
format_error(invalid_args_for_bodyless_clause) ->
|
||||
"can use only variables and \\\\ as arguments of bodyless clause";
|
||||
|
||||
format_error({missing_do, Kind}) ->
|
||||
io_lib:format("missing do keyword in ~ts", [Kind]).
|
||||
|
||||
@@ -21,17 +21,19 @@ unpack(Kind, Name, Args, S) ->
|
||||
%% Unpack default from given args.
|
||||
%% Returns the given arguments without their default
|
||||
%% clauses and a list of clauses for the default calls.
|
||||
unpack_each(Kind, Name, [{'\\\\', Line, [Expr, _]}|T] = List, Acc, Clauses, S) ->
|
||||
Base = build_match(Acc, Line, []),
|
||||
{Args, Invoke} = extract_defaults(List, Line, length(Base), [], []),
|
||||
unpack_each(Kind, Name, [{'\\\\', DefMeta, [Expr, _]}|T] = List, Acc, Clauses, S) ->
|
||||
Base = wrap_kind(Kind, build_match(Acc, [])),
|
||||
{Args, Invoke} = extract_defaults(List, length(Base), [], []),
|
||||
|
||||
{DefArgs, SA} = elixir_clauses:match(fun elixir_translator:translate_args/2, Base ++ Args, S),
|
||||
{DefInvoke, _} = elixir_translator:translate_args(Base ++ Invoke, SA),
|
||||
|
||||
Line = ?line(DefMeta),
|
||||
|
||||
Call = {call, Line,
|
||||
{atom, Line, name_for_kind(Kind, Name)},
|
||||
DefInvoke
|
||||
},
|
||||
},
|
||||
|
||||
Clause = {clause, Line, DefArgs, [], [Call]},
|
||||
unpack_each(Kind, Name, T, [Expr|Acc], [Clause|Clauses], S);
|
||||
@@ -44,25 +46,28 @@ unpack_each(_Kind, _Name, [], Acc, Clauses, _S) ->
|
||||
|
||||
% Extract default values from args following the current default clause.
|
||||
|
||||
extract_defaults([{'\\\\', _, [_Expr, Default]}|T], Line, Counter, NewArgs, NewInvoke) ->
|
||||
extract_defaults(T, Line, Counter, NewArgs, [Default|NewInvoke]);
|
||||
extract_defaults([{'\\\\', _, [_Expr, Default]}|T], Counter, NewArgs, NewInvoke) ->
|
||||
extract_defaults(T, Counter, NewArgs, [Default|NewInvoke]);
|
||||
|
||||
extract_defaults([_|T], Line, Counter, NewArgs, NewInvoke) ->
|
||||
H = {elixir_utils:atom_concat(["_@D", Counter]), Line, nil},
|
||||
extract_defaults(T, Line, Counter + 1, [H|NewArgs], [H|NewInvoke]);
|
||||
extract_defaults([_|T], Counter, NewArgs, NewInvoke) ->
|
||||
H = {elixir_utils:atom_concat(["x", Counter]), [], nil},
|
||||
extract_defaults(T, Counter + 1, [H|NewArgs], [H|NewInvoke]);
|
||||
|
||||
extract_defaults([], _Line, _Counter, NewArgs, NewInvoke) ->
|
||||
extract_defaults([], _Counter, NewArgs, NewInvoke) ->
|
||||
{lists:reverse(NewArgs), lists:reverse(NewInvoke)}.
|
||||
|
||||
% Build matches for all the previous argument until the current default clause.
|
||||
|
||||
build_match([], _Line, Acc) -> Acc;
|
||||
build_match([], Acc) -> Acc;
|
||||
|
||||
build_match([_|T], Line, Acc) ->
|
||||
Var = {elixir_utils:atom_concat(["_@D", length(T)]), Line, nil},
|
||||
build_match(T, Line, [Var|Acc]).
|
||||
build_match([_|T], Acc) ->
|
||||
Var = {elixir_utils:atom_concat(["x", length(T)]), [], nil},
|
||||
build_match(T, [Var|Acc]).
|
||||
|
||||
% Given the invoked function name based on the kind
|
||||
|
||||
wrap_kind(Kind, Args) when Kind == defmacro; Kind == defmacrop -> [{c, [], nil}|Args];
|
||||
wrap_kind(_Kind, Args) -> Args.
|
||||
|
||||
name_for_kind(Kind, Name) when Kind == defmacro; Kind == defmacrop -> elixir_utils:macro_name(Name);
|
||||
name_for_kind(_Kind, Name) -> Name.
|
||||
@@ -10,19 +10,25 @@
|
||||
-include("elixir.hrl").
|
||||
-import(ordsets, [is_element/2]).
|
||||
|
||||
-define(atom, 'Elixir.Atom').
|
||||
-define(float, 'Elixir.Float').
|
||||
-define(io, 'Elixir.IO').
|
||||
-define(integer, 'Elixir.Integer').
|
||||
-define(kernel, 'Elixir.Kernel').
|
||||
-define(list, 'Elixir.List').
|
||||
-define(map, 'Elixir.Map').
|
||||
-define(node, 'Elixir.Node').
|
||||
-define(process, 'Elixir.Process').
|
||||
-define(string, 'Elixir.String').
|
||||
-define(system, 'Elixir.System').
|
||||
-define(tuple, 'Elixir.Tuple').
|
||||
|
||||
default_functions() ->
|
||||
[ {?kernel, elixir_imported_functions()} ].
|
||||
[{?kernel, elixir_imported_functions()}].
|
||||
default_macros() ->
|
||||
[ {?kernel, elixir_imported_macros()} ].
|
||||
[{?kernel, elixir_imported_macros()}].
|
||||
default_requires() ->
|
||||
[ 'Elixir.Kernel', 'Elixir.Kernel.Typespec' ].
|
||||
['Elixir.Kernel', 'Elixir.Kernel.Typespec'].
|
||||
|
||||
find_import(Meta, Name, Arity, E) ->
|
||||
Tuple = {Name, Arity},
|
||||
@@ -48,7 +54,7 @@ import_function(Meta, Name, Arity, E) ->
|
||||
{function, Receiver} ->
|
||||
elixir_lexical:record_import(Receiver, ?m(E, lexical_tracker)),
|
||||
elixir_locals:record_import(Tuple, Receiver, ?m(E, module), ?m(E, function)),
|
||||
remote_function(Receiver, Name, Arity, E);
|
||||
remote_function(Meta, Receiver, Name, Arity, E);
|
||||
{macro, _Receiver} ->
|
||||
false;
|
||||
{import, Receiver} ->
|
||||
@@ -62,17 +68,19 @@ import_function(Meta, Name, Arity, E) ->
|
||||
end
|
||||
end.
|
||||
|
||||
require_function(_Meta, Receiver, Name, Arity, E) ->
|
||||
require_function(Meta, Receiver, Name, Arity, E) ->
|
||||
case is_element({Name, Arity}, get_optional_macros(Receiver)) of
|
||||
true -> false;
|
||||
false -> remote_function(Receiver, Name, Arity, E)
|
||||
false -> remote_function(Meta, Receiver, Name, Arity, E)
|
||||
end.
|
||||
|
||||
remote_function(Receiver, Name, Arity, E) ->
|
||||
remote_function(Meta, Receiver, Name, Arity, E) ->
|
||||
check_deprecation(Meta, Receiver, Name, Arity, E),
|
||||
|
||||
elixir_lexical:record_remote(Receiver, ?m(E, lexical_tracker)),
|
||||
case inline(Receiver, Name, Arity) of
|
||||
{AR, AN} -> {remote, AR, AN, Arity};
|
||||
false -> {remote, Receiver, Name, Arity}
|
||||
false -> {remote, Receiver, Name, Arity}
|
||||
end.
|
||||
|
||||
%% Dispatches
|
||||
@@ -82,8 +90,8 @@ dispatch_import(Meta, Name, Args, E, Callback) ->
|
||||
case expand_import(Meta, {Name, Arity}, Args, E, []) of
|
||||
{ok, Receiver, Quoted} ->
|
||||
expand_quoted(Meta, Receiver, Name, Arity, Quoted, E);
|
||||
{ok, Receiver} ->
|
||||
elixir_exp:expand({{'.', [], [Receiver, Name]}, Meta, Args}, E);
|
||||
{ok, Receiver, NewName, NewArgs} ->
|
||||
elixir_exp:expand({{'.', [], [Receiver, NewName]}, Meta, NewArgs}, E);
|
||||
error ->
|
||||
Callback()
|
||||
end.
|
||||
@@ -139,33 +147,39 @@ do_expand_import(Meta, {Name, Arity} = Tuple, Args, Module, E, Result) ->
|
||||
{function, Receiver} ->
|
||||
elixir_lexical:record_import(Receiver, ?m(E, lexical_tracker)),
|
||||
elixir_locals:record_import(Tuple, Receiver, Module, ?m(E, function)),
|
||||
case rewrite(Receiver, Name, Args, Arity) of
|
||||
{ok, AR, AN, AA} ->
|
||||
{ok, AR, {{'.', [], [AR, AN]}, [], AA}};
|
||||
false ->
|
||||
{ok, Receiver}
|
||||
|
||||
%% DEPRECATED
|
||||
case {Receiver, Name, Arity} of
|
||||
{?kernel, size, 1} ->
|
||||
check_deprecation(Meta, Receiver, Name, Arity, E),
|
||||
{ok, erlang, size, Args};
|
||||
_ ->
|
||||
case rewrite(Receiver, Name, Args, Arity) of
|
||||
{ok, _, _, _} = Res -> Res;
|
||||
false -> {ok, Receiver, Name, Args}
|
||||
end
|
||||
end;
|
||||
{macro, Receiver} ->
|
||||
check_deprecation(Meta, Receiver, Name, Arity, E),
|
||||
elixir_lexical:record_import(Receiver, ?m(E, lexical_tracker)),
|
||||
elixir_locals:record_import(Tuple, Receiver, Module, ?m(E, function)),
|
||||
{ok, Receiver, expand_macro_named(Meta, Receiver, Name, Arity, Args, E)};
|
||||
{import, Receiver} ->
|
||||
case expand_require([{require,false}|Meta], Receiver, Tuple, Args, E) of
|
||||
{ok, _, _} = Response -> Response;
|
||||
error -> {ok, Receiver}
|
||||
error -> {ok, Receiver, Name, Args}
|
||||
end;
|
||||
false when Module == ?kernel ->
|
||||
case rewrite(Module, Name, Args, Arity) of
|
||||
{ok, AR, AN, AA} ->
|
||||
{ok, AR, {{'.', [], [AR, AN]}, [], AA}};
|
||||
false ->
|
||||
error
|
||||
{ok, _, _, _} = Res -> Res;
|
||||
false -> error
|
||||
end;
|
||||
false ->
|
||||
error
|
||||
end.
|
||||
|
||||
expand_require(Meta, Receiver, {Name, Arity} = Tuple, Args, E) ->
|
||||
check_deprecation(Meta, Receiver, Name, Arity, E),
|
||||
Module = ?m(E, module),
|
||||
|
||||
case is_element(Tuple, get_optional_macros(Receiver)) of
|
||||
@@ -331,27 +345,12 @@ elixir_imported_macros() ->
|
||||
error:undef -> []
|
||||
end.
|
||||
|
||||
rewrite(?kernel, atom_to_binary, [Arg], _) ->
|
||||
rewrite(?atom, to_string, [Arg], _) ->
|
||||
{ok, erlang, atom_to_binary, [Arg, utf8]};
|
||||
rewrite(?kernel, binary_to_atom, [Arg], _) ->
|
||||
{ok, erlang, binary_to_atom, [Arg, utf8]};
|
||||
rewrite(?kernel, binary_to_existing_atom, [Arg], _) ->
|
||||
{ok, erlang, binary_to_existing_atom, [Arg, utf8]};
|
||||
rewrite(?kernel, elem, [Tuple, Index], _) ->
|
||||
{ok, erlang, element, [increment(Index), Tuple]};
|
||||
rewrite(?kernel, set_elem, [Tuple, Index, Value], _) ->
|
||||
rewrite(?kernel, put_elem, [Tuple, Index, Value], _) ->
|
||||
{ok, erlang, setelement, [increment(Index), Tuple, Value]};
|
||||
|
||||
rewrite(?process, monitor, [Arg], _) ->
|
||||
{ok, erlang, monitor, [process, Arg]};
|
||||
|
||||
rewrite(?tuple, insert_at, [Tuple, Index, Term], _) ->
|
||||
{ok, erlang, insert_element, [increment(Index), Tuple, Term]};
|
||||
rewrite(?tuple, delete_at, [Tuple, Index], _) ->
|
||||
{ok, erlang, delete_element, [increment(Index), Tuple]};
|
||||
rewrite(?tuple, duplicate, [Data, Size], _) ->
|
||||
{ok, erlang, make_tuple, [Size, Data]};
|
||||
|
||||
rewrite(?map, 'has_key?', [Map, Key], _) ->
|
||||
{ok, maps, is_key, [Key, Map]};
|
||||
rewrite(?map, fetch, [Map, Key], _) ->
|
||||
@@ -360,6 +359,19 @@ rewrite(?map, put, [Map, Key, Value], _) ->
|
||||
{ok, maps, put, [Key, Value, Map]};
|
||||
rewrite(?map, delete, [Map, Key], _) ->
|
||||
{ok, maps, remove, [Key, Map]};
|
||||
rewrite(?process, monitor, [Arg], _) ->
|
||||
{ok, erlang, monitor, [process, Arg]};
|
||||
rewrite(?string, to_atom, [Arg], _) ->
|
||||
{ok, erlang, binary_to_atom, [Arg, utf8]};
|
||||
rewrite(?string, to_existing_atom, [Arg], _) ->
|
||||
{ok, erlang, binary_to_existing_atom, [Arg, utf8]};
|
||||
rewrite(?tuple, insert_at, [Tuple, Index, Term], _) ->
|
||||
{ok, erlang, insert_element, [increment(Index), Tuple, Term]};
|
||||
rewrite(?tuple, delete_at, [Tuple, Index], _) ->
|
||||
{ok, erlang, delete_element, [increment(Index), Tuple]};
|
||||
rewrite(?tuple, duplicate, [Data, Size], _) ->
|
||||
{ok, erlang, make_tuple, [Size, Data]};
|
||||
|
||||
rewrite(Receiver, Name, Args, Arity) ->
|
||||
case inline(Receiver, Name, Arity) of
|
||||
{AR, AN} -> {ok, AR, AN, Args};
|
||||
@@ -371,6 +383,22 @@ increment(Number) when is_number(Number) ->
|
||||
increment(Other) ->
|
||||
{{'.', [], [erlang, '+']}, [], [Other, 1]}.
|
||||
|
||||
inline(?atom, to_char_list, 1) -> {erlang, atom_to_list};
|
||||
inline(?io, iodata_length, 1) -> {erlang, iolist_size};
|
||||
inline(?io, iodata_to_binary, 1) -> {erlang, iolist_to_binary};
|
||||
inline(?integer, to_string, 1) -> {erlang, integer_to_binary};
|
||||
inline(?integer, to_string, 2) -> {erlang, integer_to_binary};
|
||||
inline(?integer, to_char_list, 1) -> {erlang, integer_to_list};
|
||||
inline(?integer, to_char_list, 2) -> {erlang, integer_to_list};
|
||||
inline(?float, to_string, 1) -> {erlang, float_to_binary};
|
||||
inline(?float, to_char_list, 1) -> {erlang, float_to_list};
|
||||
inline(?list, to_atom, 1) -> {erlang, list_to_atom};
|
||||
inline(?list, to_existing_atom, 1) -> {erlang, list_to_existing_atom};
|
||||
inline(?list, to_float, 1) -> {erlang, list_to_float};
|
||||
inline(?list, to_integer, 1) -> {erlang, list_to_integer};
|
||||
inline(?list, to_integer, 2) -> {erlang, list_to_integer};
|
||||
inline(?list, to_tuple, 1) -> {erlang, list_to_tuple};
|
||||
|
||||
inline(?kernel, '+', 2) -> {erlang, '+'};
|
||||
inline(?kernel, '-', 2) -> {erlang, '-'};
|
||||
inline(?kernel, '+', 1) -> {erlang, '+'};
|
||||
@@ -392,26 +420,12 @@ inline(?kernel, '!==', 2) -> {erlang, '=/='};
|
||||
inline(?kernel, abs, 1) -> {erlang, abs};
|
||||
inline(?kernel, apply, 2) -> {erlang, apply};
|
||||
inline(?kernel, apply, 3) -> {erlang, apply};
|
||||
inline(?kernel, atom_to_list, 1) -> {erlang, atom_to_list};
|
||||
inline(?kernel, binary_part, 3) -> {erlang, binary_part};
|
||||
inline(?kernel, binary_to_float, 1) -> {erlang, binary_to_float};
|
||||
inline(?kernel, binary_to_float, 2) -> {erlang, binary_to_float};
|
||||
inline(?kernel, binary_to_integer, 1) -> {erlang, binary_to_integer};
|
||||
inline(?kernel, binary_to_integer, 2) -> {erlang, binary_to_integer};
|
||||
inline(?kernel, bit_size, 1) -> {erlang, bit_size};
|
||||
inline(?kernel, bitstring_to_list, 1) -> {erlang, bitstring_to_list};
|
||||
inline(?kernel, byte_size, 1) -> {erlang, byte_size};
|
||||
inline(?kernel, 'div', 2) -> {erlang, 'div'};
|
||||
inline(?kernel, exit, 1) -> {erlang, exit};
|
||||
inline(?kernel, float_to_binary, 1) -> {erlang, float_to_binary};
|
||||
inline(?kernel, float_to_list, 1) -> {erlang, float_to_list};
|
||||
inline(?kernel, hd, 1) -> {erlang, hd};
|
||||
inline(?kernel, integer_to_binary, 1) -> {erlang, integer_to_binary};
|
||||
inline(?kernel, integer_to_binary, 2) -> {erlang, integer_to_binary};
|
||||
inline(?kernel, integer_to_list, 1) -> {erlang, integer_to_list};
|
||||
inline(?kernel, integer_to_list, 2) -> {erlang, integer_to_list};
|
||||
inline(?kernel, iodata_length, 1) -> {erlang, iolist_size};
|
||||
inline(?kernel, iodata_to_binary, 1) -> {erlang, iolist_to_binary};
|
||||
inline(?kernel, is_atom, 1) -> {erlang, is_atom};
|
||||
inline(?kernel, is_binary, 1) -> {erlang, is_binary};
|
||||
inline(?kernel, is_bitstring, 1) -> {erlang, is_bitstring};
|
||||
@@ -428,13 +442,6 @@ inline(?kernel, is_port, 1) -> {erlang, is_port};
|
||||
inline(?kernel, is_reference, 1) -> {erlang, is_reference};
|
||||
inline(?kernel, is_tuple, 1) -> {erlang, is_tuple};
|
||||
inline(?kernel, length, 1) -> {erlang, length};
|
||||
inline(?kernel, list_to_atom, 1) -> {erlang, list_to_atom};
|
||||
inline(?kernel, list_to_bitstring, 1) -> {erlang, list_to_bitstring};
|
||||
inline(?kernel, list_to_existing_atom, 1) -> {erlang, list_to_existing_atom};
|
||||
inline(?kernel, list_to_float, 1) -> {erlang, list_to_float};
|
||||
inline(?kernel, list_to_integer, 1) -> {erlang, list_to_integer};
|
||||
inline(?kernel, list_to_integer, 2) -> {erlang, list_to_integer};
|
||||
inline(?kernel, list_to_tuple, 1) -> {erlang, list_to_tuple};
|
||||
inline(?kernel, make_ref, 0) -> {erlang, make_ref};
|
||||
inline(?kernel, map_size, 1) -> {erlang, map_size};
|
||||
inline(?kernel, max, 2) -> {erlang, max};
|
||||
@@ -445,7 +452,6 @@ inline(?kernel, 'rem', 2) -> {erlang, 'rem'};
|
||||
inline(?kernel, round, 1) -> {erlang, round};
|
||||
inline(?kernel, self, 0) -> {erlang, self};
|
||||
inline(?kernel, send, 2) -> {erlang, send};
|
||||
inline(?kernel, size, 1) -> {erlang, size};
|
||||
inline(?kernel, spawn, 1) -> {erlang, spawn};
|
||||
inline(?kernel, spawn, 3) -> {erlang, spawn};
|
||||
inline(?kernel, spawn_link, 1) -> {erlang, spawn_link};
|
||||
@@ -456,7 +462,19 @@ inline(?kernel, throw, 1) -> {erlang, throw};
|
||||
inline(?kernel, tl, 1) -> {erlang, tl};
|
||||
inline(?kernel, trunc, 1) -> {erlang, trunc};
|
||||
inline(?kernel, tuple_size, 1) -> {erlang, tuple_size};
|
||||
inline(?kernel, tuple_to_list, 1) -> {erlang, tuple_to_list};
|
||||
|
||||
inline(?map, keys, 1) -> {maps, keys};
|
||||
inline(?map, merge, 2) -> {maps, merge};
|
||||
inline(?map, size, 1) -> {maps, size};
|
||||
inline(?map, values, 1) -> {maps, values};
|
||||
inline(?map, to_list, 1) -> {maps, to_list};
|
||||
|
||||
inline(?node, spawn, 2) -> {erlang, spawn};
|
||||
inline(?node, spawn, 3) -> {erlang, spawn_opt};
|
||||
inline(?node, spawn, 4) -> {erlang, spawn};
|
||||
inline(?node, spawn, 5) -> {erlang, spawn_opt};
|
||||
inline(?node, spawn_link, 2) -> {erlang, spawn_link};
|
||||
inline(?node, spawn_link, 4) -> {erlang, spawn_link};
|
||||
|
||||
inline(?process, exit, 2) -> {erlang, exit};
|
||||
inline(?process, spawn, 2) -> {erlang, spawn_opt};
|
||||
@@ -466,21 +484,47 @@ inline(?process, demonitor, 2) -> {erlang, demonitor};
|
||||
inline(?process, link, 1) -> {erlang, link};
|
||||
inline(?process, unlink, 1) -> {erlang, unlink};
|
||||
|
||||
inline(?node, spawn, 2) -> {erlang, spawn};
|
||||
inline(?node, spawn, 3) -> {erlang, spawn_opt};
|
||||
inline(?node, spawn, 4) -> {erlang, spawn};
|
||||
inline(?node, spawn, 5) -> {erlang, spawn_opt};
|
||||
inline(?node, spawn_link, 2) -> {erlang, spawn_link};
|
||||
inline(?node, spawn_link, 4) -> {erlang, spawn_link};
|
||||
inline(?node, spawn_monitor, 2) -> {erlang, spawn_monitor};
|
||||
inline(?node, spawn_monitor, 4) -> {erlang, spawn_monitor};
|
||||
|
||||
inline(?string, to_float, 1) -> {erlang, binary_to_float};
|
||||
inline(?string, to_integer, 1) -> {erlang, binary_to_integer};
|
||||
inline(?string, to_integer, 2) -> {erlang, binary_to_integer};
|
||||
inline(?system, stacktrace, 0) -> {erlang, get_stacktrace};
|
||||
|
||||
inline(?map, keys, 1) -> {maps, keys};
|
||||
inline(?map, merge, 2) -> {maps, merge};
|
||||
inline(?map, size, 1) -> {maps, size};
|
||||
inline(?map, values, 1) -> {maps, values};
|
||||
inline(?map, to_list, 1) -> {maps, to_list};
|
||||
inline(?tuple, to_list, 1) -> {erlang, tuple_to_list};
|
||||
|
||||
inline(_, _, _) -> false.
|
||||
|
||||
check_deprecation(Meta, Receiver, Name, Arity, #{file := File}) ->
|
||||
case deprecation(Receiver, Name, Arity) of
|
||||
false -> ok;
|
||||
Message ->
|
||||
Warning = deprecation_message(Receiver, Name, Arity, Message),
|
||||
elixir_errors:warn(?line(Meta), File, Warning)
|
||||
end.
|
||||
|
||||
deprecation_message(Receiver, '__using__', _Arity, Message) ->
|
||||
Warning = io_lib:format("use ~s is deprecated", [elixir_aliases:inspect(Receiver)]),
|
||||
deprecation_message(Warning, Message);
|
||||
|
||||
deprecation_message(Receiver, Name, Arity, Message) ->
|
||||
Warning = io_lib:format("~s.~s/~B is deprecated",
|
||||
[elixir_aliases:inspect(Receiver), Name, Arity]),
|
||||
deprecation_message(Warning, Message).
|
||||
|
||||
deprecation_message(Warning, Message) ->
|
||||
case Message of
|
||||
true -> Warning;
|
||||
Message -> Warning ++ ", " ++ Message
|
||||
end.
|
||||
|
||||
deprecation('Elixir.Kernel', 'size', 1) ->
|
||||
"use byte_size/1 or tuple_size/1 instead";
|
||||
% deprecation('Elixir.Mix.Generator', 'from_file', _) ->
|
||||
% "instead pass [from_file: file] to embed_text/2 and embed_template/2 macros. "
|
||||
% "Note that [from_file: file] expects paths relative to the current working "
|
||||
% "directory and not to the current file";
|
||||
% deprecation('Elixir.EEx.TransformerEngine', '__using__', _) ->
|
||||
% "check EEx.SmartEngine for how to build custom engines";
|
||||
% deprecation('Elixir.EEx.AssignsEngine', '__using__', _) ->
|
||||
% "check EEx.SmartEngine for how to build custom engines";
|
||||
|
||||
deprecation(_, _, _) ->
|
||||
false.
|
||||
|
||||
@@ -3,8 +3,7 @@
|
||||
-module(elixir_errors).
|
||||
-export([compile_error/3, compile_error/4,
|
||||
form_error/4, parse_error/4, warn/2, warn/3,
|
||||
handle_file_warning/2, handle_file_warning/3, handle_file_error/2,
|
||||
deprecation/3, deprecation/4]).
|
||||
handle_file_warning/2, handle_file_warning/3, handle_file_error/2]).
|
||||
-include("elixir.hrl").
|
||||
|
||||
-type line_or_meta() :: integer() | list().
|
||||
@@ -21,7 +20,7 @@ warn(Warning) ->
|
||||
warn(Caller, Warning) ->
|
||||
warn([Caller, "warning: ", Warning]).
|
||||
|
||||
warn(Line, File, Warning) ->
|
||||
warn(Line, File, Warning) when is_integer(Line) ->
|
||||
warn(file_format(Line, File, "warning: " ++ Warning)).
|
||||
|
||||
%% Raised during expansion/translation/compilation.
|
||||
@@ -56,23 +55,24 @@ parse_error(Meta, File, <<"syntax error before: ">>, <<"'end'">>) ->
|
||||
raise(Meta, File, 'Elixir.SyntaxError', <<"unexpected token: end">>);
|
||||
|
||||
%% Binaries are wrapped in [<<...>>], so we need to unwrap them
|
||||
parse_error(Meta, File, Error, <<"[<<", Token/binary>>) when is_binary(Error) ->
|
||||
Rest = binary_part(Token, 0, byte_size(Token) - 3),
|
||||
Message = <<Error / binary, Rest / binary >>,
|
||||
raise(Meta, File, 'Elixir.SyntaxError', Message);
|
||||
parse_error(Meta, File, Error, <<"[", _/binary>> = Full) when is_binary(Error) ->
|
||||
Rest =
|
||||
case binary:split(Full, <<"<<">>) of
|
||||
[Lead, Token] ->
|
||||
case binary:split(Token, <<">>">>) of
|
||||
[Part, _] when Lead == <<$[>> -> Part;
|
||||
_ -> <<$">>
|
||||
end;
|
||||
[_] ->
|
||||
<<$">>
|
||||
end,
|
||||
raise(Meta, File, 'Elixir.SyntaxError', <<Error/binary, Rest/binary >>);
|
||||
|
||||
%% Everything else is fine as is
|
||||
parse_error(Meta, File, Error, Token) when is_binary(Error), is_binary(Token) ->
|
||||
Message = <<Error / binary, Token / binary >>,
|
||||
raise(Meta, File, 'Elixir.SyntaxError', Message).
|
||||
|
||||
%% Shows a deprecation message
|
||||
|
||||
deprecation(Meta, File, Message) -> deprecation(Meta, File, Message, []).
|
||||
|
||||
deprecation(Meta, File, Message, Args) ->
|
||||
io:format(file_format(?line(Meta), File, io_lib:format(Message, Args))).
|
||||
|
||||
%% Handle warnings and errors (called during module compilation)
|
||||
|
||||
%% Ignore on bootstrap
|
||||
@@ -177,7 +177,7 @@ raise(Line, File, Kind, Message) when is_integer(Line), is_binary(File) ->
|
||||
ok -> ok
|
||||
end,
|
||||
Stacktrace = erlang:get_stacktrace(),
|
||||
Exception = Kind:new([{description, Message}, {file, File}, {line, Line}]),
|
||||
Exception = Kind:exception([{description, Message}, {file, File}, {line, Line}]),
|
||||
erlang:raise(error, Exception, tl(Stacktrace)).
|
||||
|
||||
file_format(0, File, Message) when is_binary(File) ->
|
||||
|
||||
@@ -169,22 +169,15 @@ expand({quote, Meta, [KV, Do]}, E) when is_list(Do) ->
|
||||
false -> compile_error(Meta, E#elixir_scope.file, "missing do keyword in quote")
|
||||
end,
|
||||
|
||||
ValidOpts = [hygiene, context, var_context, location, line, unquote, bind_quoted],
|
||||
ValidOpts = [context, location, line, unquote, bind_quoted],
|
||||
{EKV, ET} = expand_opts(Meta, quote, ValidOpts, KV, E),
|
||||
|
||||
Hygiene = case lists:keyfind(hygiene, 1, EKV) of
|
||||
{hygiene, List} when is_list(List) ->
|
||||
List;
|
||||
false ->
|
||||
[]
|
||||
end,
|
||||
|
||||
Context = case lists:keyfind(context, 1, EKV) of
|
||||
{context, Atom} when is_atom(Atom) ->
|
||||
Atom;
|
||||
{context, Ctx} when is_atom(Ctx) and (Ctx /= nil) ->
|
||||
Ctx;
|
||||
{context, Ctx} ->
|
||||
compile_error(Meta, ?m(E, file), "invalid :context for quote, "
|
||||
"expected a compile time atom or alias, got: ~ts", ['Elixir.Kernel':inspect(Ctx)]);
|
||||
"expected non nil compile time atom or alias, got: ~ts", ['Elixir.Kernel':inspect(Ctx)]);
|
||||
false ->
|
||||
case ?m(E, module) of
|
||||
nil -> 'Elixir';
|
||||
@@ -192,10 +185,6 @@ expand({quote, Meta, [KV, Do]}, E) when is_list(Do) ->
|
||||
end
|
||||
end,
|
||||
|
||||
Vars = lists:keyfind(vars, 1, Hygiene) /= {vars, false},
|
||||
Aliases = lists:keyfind(aliases, 1, Hygiene) /= {aliases, false},
|
||||
Imports = lists:keyfind(imports, 1, Hygiene) /= {imports, false},
|
||||
|
||||
Keep = lists:keyfind(location, 1, EKV) == {location, keep},
|
||||
Line = proplists:get_value(line, EKV, false),
|
||||
|
||||
@@ -209,8 +198,7 @@ expand({quote, Meta, [KV, Do]}, E) when is_list(Do) ->
|
||||
false -> DefaultUnquote
|
||||
end,
|
||||
|
||||
Q = #elixir_quote{vars_hygiene=Vars, line=Line, keep=Keep, unquote=Unquote,
|
||||
aliases_hygiene=Aliases, imports_hygiene=Imports, context=Context},
|
||||
Q = #elixir_quote{line=Line, keep=Keep, unquote=Unquote, context=Context},
|
||||
|
||||
{Quoted, _Q} = elixir_quote:quote(Exprs, Binding, Q, ET),
|
||||
expand(Quoted, ET);
|
||||
@@ -237,11 +225,22 @@ expand({fn, Meta, Pairs}, E) ->
|
||||
|
||||
%% Case/Receive/Try
|
||||
|
||||
expand({'cond', Meta, [KV]}, E) ->
|
||||
assert_no_match_or_guard_scope(Meta, 'cond', E),
|
||||
{EClauses, EC} = elixir_exp_clauses:'cond'(Meta, KV, E),
|
||||
{{'cond', Meta, [EClauses]}, EC};
|
||||
|
||||
expand({'case', Meta, [Expr, KV]}, E) ->
|
||||
assert_no_match_or_guard_scope(Meta, 'case', E),
|
||||
{EExpr, EE} = expand(Expr, E),
|
||||
{EClauses, EC} = elixir_exp_clauses:'case'(Meta, KV, EE),
|
||||
{{'case', Meta, [EExpr, EClauses]}, EC};
|
||||
FClauses =
|
||||
case (lists:keyfind(optimize_boolean, 1, Meta) == {optimize_boolean, true}) and
|
||||
elixir_utils:returns_boolean(EExpr) of
|
||||
true -> rewrite_case_clauses(EClauses);
|
||||
false -> EClauses
|
||||
end,
|
||||
{{'case', Meta, [EExpr, FClauses]}, EC};
|
||||
|
||||
expand({'receive', Meta, [KV]}, E) ->
|
||||
assert_no_match_or_guard_scope(Meta, 'receive', E),
|
||||
@@ -255,11 +254,7 @@ expand({'try', Meta, [KV]}, E) ->
|
||||
|
||||
%% Comprehensions
|
||||
|
||||
expand({Kind, Meta, Args}, E) when is_list(Args), (Kind == lc) orelse (Kind == bc) ->
|
||||
elixir_errors:deprecation(Meta, ?m(E, file), "~ts is deprecated, please use for comprehensions instead", [Kind]),
|
||||
expand_comprehension(Meta, Kind, Args, E);
|
||||
|
||||
expand({for, Meta, Args}, E) when is_list(Args) ->
|
||||
expand({for, Meta, [_|_] = Args}, E) ->
|
||||
elixir_for:expand(Meta, Args, E);
|
||||
|
||||
%% Super
|
||||
@@ -288,7 +283,7 @@ expand({'_', _, Kind} = Var, E) when is_atom(Kind) ->
|
||||
expand({Name, Meta, Kind} = Var, #{context := match, export_vars := Export} = E) when is_atom(Name), is_atom(Kind) ->
|
||||
Pair = {Name, var_kind(Meta, Kind)},
|
||||
NewVars = ordsets:add_element(Pair, ?m(E, vars)),
|
||||
NewExport = case (Export /= nil) andalso (lists:keyfind(export, 1, Meta) /= {export, false}) of
|
||||
NewExport = case (Export /= nil) of
|
||||
true -> ordsets:add_element(Pair, Export);
|
||||
false -> Export
|
||||
end,
|
||||
@@ -417,6 +412,9 @@ expand_arg(Arg, {Acc1, Acc2}) ->
|
||||
{EArg, EAcc} = expand(Arg, Acc1),
|
||||
{EArg, {elixir_env:mergea(Acc1, EAcc), elixir_env:mergev(Acc2, EAcc)}}.
|
||||
|
||||
expand_args([Arg], E) ->
|
||||
{EArg, EE} = expand(Arg, E),
|
||||
{[EArg], EE};
|
||||
expand_args(Args, #{context := match} = E) ->
|
||||
expand_many(Args, E);
|
||||
expand_args(Args, E) ->
|
||||
@@ -539,27 +537,29 @@ expand_as({as, Other}, Meta, _IncludeByDefault, _Ref, E) ->
|
||||
compile_error(Meta, ?m(E, file),
|
||||
"invalid value for keyword :as, expected an alias, got: ~ts", ['Elixir.Macro':to_string(Other)]).
|
||||
|
||||
%% Comprehensions
|
||||
|
||||
expand_comprehension(Meta, Kind, Args, E) ->
|
||||
case elixir_utils:split_last(Args) of
|
||||
{Cases, [{do,Expr}]} ->
|
||||
{ECases, EC} = lists:mapfoldl(fun expand_comprehension_clause/2, E, Cases),
|
||||
{EExpr, _} = expand(Expr, EC),
|
||||
{{Kind, Meta, ECases ++ [[{do,EExpr}]]}, E};
|
||||
_ ->
|
||||
compile_error(Meta, ?m(E, file), "missing do keyword in comprehension ~ts", [Kind])
|
||||
end.
|
||||
|
||||
expand_comprehension_clause({Gen, Meta, [Left, Right]}, E) when Gen == inbits; Gen == inlist ->
|
||||
{ERight, ER} = expand(Right, E),
|
||||
{ELeft, EL} = elixir_exp_clauses:match(fun expand/2, Left, E),
|
||||
{{Gen, Meta, [ELeft, ERight]}, elixir_env:mergev(EL, ER)};
|
||||
expand_comprehension_clause(X, E) ->
|
||||
expand(X, E).
|
||||
|
||||
%% Assertions
|
||||
|
||||
rewrite_case_clauses([{do,[
|
||||
{'->', FalseMeta, [
|
||||
[{'when', _, [Var, {'__op__', _,[
|
||||
'orelse',
|
||||
{{'.', _, [erlang, '=:=']}, _, [Var, nil]},
|
||||
{{'.', _, [erlang, '=:=']}, _, [Var, false]}
|
||||
]}]}],
|
||||
FalseExpr
|
||||
]},
|
||||
{'->', TrueMeta, [
|
||||
[{'_', _, _}],
|
||||
TrueExpr
|
||||
]}
|
||||
]}]) ->
|
||||
[{do, [
|
||||
{'->', FalseMeta, [[false], FalseExpr]},
|
||||
{'->', TrueMeta, [[true], TrueExpr]}
|
||||
]}];
|
||||
rewrite_case_clauses(Clauses) ->
|
||||
Clauses.
|
||||
|
||||
assert_no_match_or_guard_scope(Meta, Kind, E) ->
|
||||
assert_no_match_scope(Meta, Kind, E),
|
||||
assert_no_guard_scope(Meta, Kind, E).
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
%% Handle code related to args, guard and -> matching for case,
|
||||
%% fn, receive and friends. try is handled in elixir_try.
|
||||
-module(elixir_exp_clauses).
|
||||
-export([match/3, clause/5, 'case'/3, 'receive'/3, 'try'/3, def/5]).
|
||||
-export([match/3, clause/5, def/5, head/2,
|
||||
'case'/3, 'receive'/3, 'try'/3, 'cond'/3]).
|
||||
-import(elixir_errors, [compile_error/3, compile_error/4]).
|
||||
-include("elixir.hrl").
|
||||
|
||||
@@ -18,19 +19,19 @@ def(Fun, Args, Guards, Body, E) ->
|
||||
clause(Meta, Kind, Fun, {'->', ClauseMeta, [_, _]} = Clause, E) when is_function(Fun, 3) ->
|
||||
clause(Meta, Kind, fun(X, Acc) -> Fun(ClauseMeta, X, Acc) end, Clause, E);
|
||||
clause(_Meta, _Kind, Fun, {'->', Meta, [Left, Right]}, E) ->
|
||||
{ELeft, EL} = head(Fun, Left, E),
|
||||
{ELeft, EL} = Fun(Left, E),
|
||||
{ERight, ER} = elixir_exp:expand(Right, EL),
|
||||
{{'->', Meta, [ELeft, ERight]}, ER};
|
||||
clause(Meta, Kind, _Fun, _, E) ->
|
||||
compile_error(Meta, ?m(E, file), "expected -> clauses in ~ts", [Kind]).
|
||||
|
||||
head(Fun, [{'when', Meta, [_,_|_] = All}], E) ->
|
||||
head([{'when', Meta, [_,_|_] = All}], E) ->
|
||||
{Args, Guard} = elixir_utils:split_last(All),
|
||||
{EArgs, EA} = match(Fun, Args, E),
|
||||
{EArgs, EA} = match(fun elixir_exp:expand_args/2, Args, E),
|
||||
{EGuard, EG} = guard(Guard, EA#{context := guard}),
|
||||
{[{'when', Meta, EArgs ++ [EGuard]}], EG#{context := ?m(E, context)}};
|
||||
head(Fun, Args, E) ->
|
||||
match(Fun, Args, E).
|
||||
head(Args, E) ->
|
||||
match(fun elixir_exp:expand_args/2, Args, E).
|
||||
|
||||
guard({'when', Meta, [Left, Right]}, E) ->
|
||||
{ELeft, EL} = guard(Left, E),
|
||||
@@ -51,10 +52,28 @@ guard(Other, E) ->
|
||||
{EClauses, elixir_env:mergev(EVars, E)}.
|
||||
|
||||
do_case(Meta, {'do', _} = Do, Acc, E) ->
|
||||
expand_with_export(Meta, 'case', expand_arg(Meta, 'case', 'do'), Do, Acc, E);
|
||||
Fun = expand_one(Meta, 'case', 'do', fun head/2),
|
||||
expand_with_export(Meta, 'case', Fun, Do, Acc, E);
|
||||
do_case(Meta, {Key, _}, _Acc, E) ->
|
||||
compile_error(Meta, ?m(E, file), "unexpected keyword ~ts in case", [Key]).
|
||||
|
||||
%% Cond
|
||||
|
||||
'cond'(Meta, [], E) ->
|
||||
compile_error(Meta, ?m(E, file), "missing do keyword in cond");
|
||||
'cond'(Meta, KV, E) when not is_list(KV) ->
|
||||
compile_error(Meta, ?m(E, file), "invalid arguments for cond");
|
||||
'cond'(Meta, KV, E) ->
|
||||
EE = E#{export_vars := []},
|
||||
{EClauses, EVars} = lists:mapfoldl(fun(X, Acc) -> do_cond(Meta, X, Acc, EE) end, [], KV),
|
||||
{EClauses, elixir_env:mergev(EVars, E)}.
|
||||
|
||||
do_cond(Meta, {'do', _} = Do, Acc, E) ->
|
||||
Fun = expand_one(Meta, 'cond', 'do', fun elixir_exp:expand_args/2),
|
||||
expand_with_export(Meta, 'cond', Fun, Do, Acc, E);
|
||||
do_cond(Meta, {Key, _}, _Acc, E) ->
|
||||
compile_error(Meta, ?m(E, file), "unexpected keyword ~ts in cond", [Key]).
|
||||
|
||||
%% Receive
|
||||
|
||||
'receive'(Meta, [], E) ->
|
||||
@@ -69,12 +88,11 @@ do_case(Meta, {Key, _}, _Acc, E) ->
|
||||
do_receive(_Meta, {'do', nil} = Do, Acc, _E) ->
|
||||
{Do, Acc};
|
||||
do_receive(Meta, {'do', _} = Do, Acc, E) ->
|
||||
expand_with_export(Meta, 'receive', expand_arg(Meta, 'receive', 'do'), Do, Acc, E);
|
||||
do_receive(_Meta, {'after', [{'->', Meta, [[Left], Right]}]}, Acc, E) ->
|
||||
{ELeft, EL} = elixir_exp:expand(Left, E),
|
||||
{ERight, ER} = elixir_exp:expand(Right, EL),
|
||||
EClause = {'after', [{'->', Meta, [[ELeft], ERight]}]},
|
||||
{EClause, elixir_env:merge_vars(Acc, ?m(ER, export_vars))};
|
||||
Fun = expand_one(Meta, 'receive', 'do', fun head/2),
|
||||
expand_with_export(Meta, 'receive', Fun, Do, Acc, E);
|
||||
do_receive(Meta, {'after', [_]} = After, Acc, E) ->
|
||||
Fun = expand_one(Meta, 'receive', 'after', fun elixir_exp:expand_args/2),
|
||||
expand_with_export(Meta, 'receive', Fun, After, Acc, E);
|
||||
do_receive(Meta, {'after', _}, _Acc, E) ->
|
||||
compile_error(Meta, ?m(E, file), "expected a single -> clause for after in receive");
|
||||
do_receive(Meta, {Key, _}, _Acc, E) ->
|
||||
@@ -96,14 +114,22 @@ do_try(_Meta, {'after', Expr}, E) ->
|
||||
{EExpr, _} = elixir_exp:expand(Expr, E),
|
||||
{'after', EExpr};
|
||||
do_try(Meta, {'else', _} = Else, E) ->
|
||||
expand_without_export(Meta, 'try', expand_arg(Meta, 'try', 'else'), Else, E);
|
||||
Fun = expand_one(Meta, 'try', 'else', fun head/2),
|
||||
expand_without_export(Meta, 'try', Fun, Else, E);
|
||||
do_try(Meta, {'catch', _} = Catch, E) ->
|
||||
expand_without_export(Meta, 'try', fun elixir_exp:expand_args/2, Catch, E);
|
||||
expand_without_export(Meta, 'try', fun expand_catch/3, Catch, E);
|
||||
do_try(Meta, {'rescue', _} = Rescue, E) ->
|
||||
expand_without_export(Meta, 'try', fun expand_rescue/3, Rescue, E);
|
||||
do_try(Meta, {Key, _}, E) ->
|
||||
compile_error(Meta, ?m(E, file), "unexpected keyword ~ts in try", [Key]).
|
||||
|
||||
expand_catch(_Meta, [_] = Args, E) ->
|
||||
head(Args, E);
|
||||
expand_catch(_Meta, [_, _] = Args, E) ->
|
||||
head(Args, E);
|
||||
expand_catch(Meta, _, E) ->
|
||||
compile_error(Meta, ?m(E, file), "expected one or two args for catch clauses (->) in try").
|
||||
|
||||
expand_rescue(Meta, [Arg], E) ->
|
||||
case expand_rescue(Arg, E) of
|
||||
{EArg, EA} ->
|
||||
@@ -121,14 +147,14 @@ expand_rescue({Name, _, Atom} = Var, E) when is_atom(Name), is_atom(Atom) ->
|
||||
|
||||
%% rescue var in [Exprs]
|
||||
expand_rescue({in, Meta, [Left, Right]}, E) ->
|
||||
{ERight, ER} = elixir_exp:expand(Right, E#{context := nil}),
|
||||
{ELeft, EL} = elixir_exp:expand(Left, ER#{context := match}),
|
||||
{ELeft, EL} = match(fun elixir_exp:expand/2, Left, E),
|
||||
{ERight, ER} = elixir_exp:expand(Right, EL),
|
||||
|
||||
case ELeft of
|
||||
{Name, _, Atom} when is_atom(Name), is_atom(Atom) ->
|
||||
case normalize_rescue(ERight) of
|
||||
false -> false;
|
||||
Other -> {{in, Meta, [ELeft, Other]}, EL}
|
||||
Other -> {{in, Meta, [ELeft, Other]}, ER}
|
||||
end;
|
||||
_ ->
|
||||
false
|
||||
@@ -141,44 +167,32 @@ expand_rescue(Arg, E) ->
|
||||
normalize_rescue({'_', _, Atom} = N) when is_atom(Atom) -> N;
|
||||
normalize_rescue(Atom) when is_atom(Atom) -> [Atom];
|
||||
normalize_rescue(Other) ->
|
||||
is_list(Other)
|
||||
andalso lists:all(fun is_var_or_atom/1, Other)
|
||||
andalso Other.
|
||||
|
||||
is_var_or_atom({Name, _, Atom}) when is_atom(Name), is_atom(Atom) -> true;
|
||||
is_var_or_atom(Atom) when is_atom(Atom) -> true;
|
||||
is_var_or_atom(_) -> false.
|
||||
is_list(Other) andalso lists:all(fun is_atom/1, Other) andalso Other.
|
||||
|
||||
%% Expansion helpers
|
||||
|
||||
export_vars({Left, Meta, Right}) when is_atom(Left), is_list(Meta), is_atom(Right) ->
|
||||
{Left, [{export,false}|Meta], Right};
|
||||
export_vars({Left, Meta, Right}) ->
|
||||
{export_vars(Left), Meta, export_vars(Right)};
|
||||
export_vars({Left, Right}) ->
|
||||
{export_vars(Left), export_vars(Right)};
|
||||
export_vars(List) when is_list(List) ->
|
||||
[export_vars(X) || X <- List];
|
||||
export_vars(Other) ->
|
||||
Other.
|
||||
|
||||
%% Returns a function that expands arguments
|
||||
%% considering we have at maximum one entry.
|
||||
expand_arg(Meta, Kind, Key) ->
|
||||
expand_one(Meta, Kind, Key, Fun) ->
|
||||
fun
|
||||
([Arg], E) ->
|
||||
{EArg, EA} = elixir_exp:expand(Arg, E),
|
||||
{[EArg], EA};
|
||||
([_] = Args, E) ->
|
||||
Fun(Args, E);
|
||||
(_, E) ->
|
||||
compile_error(Meta, ?m(E, file), "expected one arg for ~ts clauses (->) in ~ts", [Key, Kind])
|
||||
compile_error(Meta, ?m(E, file),
|
||||
"expected one arg for ~ts clauses (->) in ~ts", [Key, Kind])
|
||||
end.
|
||||
|
||||
%% Expands all -> pairs in a given key keeping the overall vars.
|
||||
expand_with_export(Meta, Kind, Fun, {Key, Clauses}, Acc, E) when is_list(Clauses) ->
|
||||
EFun =
|
||||
case lists:keyfind(export_head, 1, Meta) of
|
||||
{export_head, true} -> Fun;
|
||||
_ -> fun(ExportArgs, ExportE) -> Fun(export_vars(ExportArgs), ExportE) end
|
||||
{export_head, true} ->
|
||||
Fun;
|
||||
_ ->
|
||||
fun(Args, #{export_vars := ExportVars} = EE) ->
|
||||
{FArgs, FE} = Fun(Args, EE),
|
||||
{FArgs, FE#{export_vars := ExportVars}}
|
||||
end
|
||||
end,
|
||||
Transformer = fun(Clause, Vars) ->
|
||||
{EClause, EC} = clause(Meta, Kind, EFun, Clause, E),
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user