Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ada53524ca | ||
|
|
e6e3570961 | ||
|
|
db5adc1b35 | ||
|
|
5dd7a2ba3a | ||
|
|
edfb650c94 | ||
|
|
c012456573 | ||
|
|
860c056cbe | ||
|
|
897424b185 | ||
|
|
f4c61dae09 | ||
|
|
73a76c6f7c | ||
|
|
002bf16e2e | ||
|
|
47c922e571 | ||
|
|
60edb8489c | ||
|
|
d539d606de | ||
|
|
bb02b25e49 | ||
|
|
6ce51612e0 | ||
|
|
6243bc8dc8 | ||
|
|
e39b32f235 | ||
|
|
0843ea0fbb | ||
|
|
bbd6abb168 | ||
|
|
a29d2913f7 | ||
|
|
d1edad39c9 | ||
|
|
74584ad921 | ||
|
|
47f57d65e0 | ||
|
|
ed71b57c00 | ||
|
|
0246226487 | ||
|
|
7da2a3ccc6 | ||
|
|
d41f39a53a | ||
|
|
44c81ae0ed | ||
|
|
c6c6a27baf | ||
|
|
97e70e6de1 | ||
|
|
980dbc96d7 | ||
|
|
0883c75335 | ||
|
|
5761460bd6 | ||
|
|
d881d899a4 | ||
|
|
166685caad | ||
|
|
f60bdb8d5f | ||
|
|
c3ef484d14 | ||
|
|
3cb586a672 | ||
|
|
68828523a0 | ||
|
|
3139168007 | ||
|
|
03189d05b3 | ||
|
|
aa09359def | ||
|
|
9595fb18dd | ||
|
|
d2530e44eb | ||
|
|
21b1e51909 | ||
|
|
9e96966622 | ||
|
|
845a3d1fea | ||
|
|
8b70e07b2e | ||
|
|
f6ca23c1a5 | ||
|
|
cc03382831 | ||
|
|
09e7dbf3d5 | ||
|
|
238eda25e3 | ||
|
|
a7c6b8da5c | ||
|
|
1a26ea2c19 | ||
|
|
6d6e7d7137 | ||
|
|
f7d0c8c58e | ||
|
|
4e93dab9a8 | ||
|
|
4847214766 | ||
|
|
e56df8ad70 | ||
|
|
325c98ccbd | ||
|
|
2daf136a53 | ||
|
|
70b4c060e6 | ||
|
|
620e452f9f | ||
|
|
c373edff9b | ||
|
|
1bd50f4d36 | ||
|
|
be79bbf55a | ||
|
|
89f651f628 | ||
|
|
f3f2481e71 | ||
|
|
d33463dcd5 | ||
|
|
2de2322402 | ||
|
|
f13ae1839c | ||
|
|
29ebae3739 | ||
|
|
59dc733dfc | ||
|
|
0c9d5f16e9 | ||
|
|
b18ddd440f | ||
|
|
f4d852d292 | ||
|
|
c37a244d66 | ||
|
|
bdab8045b9 | ||
|
|
e8ea1969e4 | ||
|
|
f669e2ba96 | ||
|
|
17ddb2539d | ||
|
|
9cbe498a02 | ||
|
|
edf55a8424 | ||
|
|
41bce3e068 | ||
|
|
914a8cdcd9 | ||
|
|
a333f646d4 | ||
|
|
d73c108b89 | ||
|
|
580b9292d6 | ||
|
|
07648bd075 | ||
|
|
149091a1a1 | ||
|
|
34a3b41543 | ||
|
|
ad87292fa0 | ||
|
|
2d9dcf860a | ||
|
|
e2357d4b95 | ||
|
|
8fac8cc25f | ||
|
|
ada913c7ef | ||
|
|
df04a3788e | ||
|
|
65543f0766 | ||
|
|
a73d70176a | ||
|
|
14d2ce460e | ||
|
|
2ce02084ee | ||
|
|
6ab07f6f5b | ||
|
|
7d2babe272 | ||
|
|
913327749a | ||
|
|
9be2f7f3d3 | ||
|
|
09e9917fc5 | ||
|
|
4af5f2b12f | ||
|
|
14fb6d7337 | ||
|
|
ecd5b3d8ef | ||
|
|
a52bbfce8e | ||
|
|
6696184fc9 | ||
|
|
eda13d1e41 | ||
|
|
f69c2182f7 | ||
|
|
b5d48ada53 | ||
|
|
27c9cc5bbb | ||
|
|
fb211b0c44 | ||
|
|
c7c5d3af24 | ||
|
|
6e9a3fd24d | ||
|
|
34c2fb773f | ||
|
|
9bdfa41c4d | ||
|
|
85576f8fba | ||
|
|
b07fbcf8b7 | ||
|
|
453c731f8a | ||
|
|
d4dd48d2fb | ||
|
|
0f9f6152cf | ||
|
|
eb998862d7 | ||
|
|
4ab8225cfb | ||
|
|
187e1b4a49 | ||
|
|
318e068587 | ||
|
|
3d6dd50866 | ||
|
|
b3ebf924a9 | ||
|
|
cd3e6d9238 | ||
|
|
56db9fb043 | ||
|
|
455eb4c4ac | ||
|
|
897396447f | ||
|
|
d79f3ddffd | ||
|
|
a6f048b3de | ||
|
|
aa87d44cba | ||
|
|
9ba58c50ab | ||
|
|
deedadb528 | ||
|
|
96e5d8bb41 | ||
|
|
8c5eaa973b | ||
|
|
42ab7a5370 | ||
|
|
e73fce96d1 | ||
|
|
d8e414032f | ||
|
|
7362ca8fab | ||
|
|
705bf0cd90 | ||
|
|
71ca5672cc | ||
|
|
7494106933 | ||
|
|
cbc03036cb | ||
|
|
eaa52f1db3 | ||
|
|
a05d0d3b26 | ||
|
|
9fc7a1c43f | ||
|
|
d161986e93 | ||
|
|
28367d9073 | ||
|
|
04da85d19e | ||
|
|
866acc4f82 | ||
|
|
cf730f2dbf | ||
|
|
012256791d | ||
|
|
5c410d9c34 | ||
|
|
efb67a8416 | ||
|
|
126a5a61df | ||
|
|
26f5f640c9 | ||
|
|
7f3a9ed9c3 | ||
|
|
04e774a4ba | ||
|
|
38d1badb64 | ||
|
|
bb3cc518cf | ||
|
|
eaa2db1809 | ||
|
|
a38636ac08 | ||
|
|
6268dc3d22 | ||
|
|
d50405135a | ||
|
|
e5fed25efd | ||
|
|
293711ff46 | ||
|
|
1a17634de9 | ||
|
|
ae32bf1fe0 | ||
|
|
433a46a0ad | ||
|
|
b2fb6c9bb1 | ||
|
|
75de306c46 | ||
|
|
598648da1a | ||
|
|
accf75b118 | ||
|
|
0cbbefe11b | ||
|
|
17b4dbbdbb | ||
|
|
9e068fb0a2 | ||
|
|
59ca4728d1 | ||
|
|
7af4364121 | ||
|
|
e38173602a | ||
|
|
9d149a3070 | ||
|
|
9158cad331 | ||
|
|
00b1aadd76 | ||
|
|
c1fedcc643 | ||
|
|
2f7972c57a | ||
|
|
ef1b2ea49c | ||
|
|
69f2d5083f | ||
|
|
10b1f56ad9 | ||
|
|
a6f171d183 | ||
|
|
21a231a914 | ||
|
|
9e9583c29c | ||
|
|
f6a513c71c | ||
|
|
e1f9b3f291 | ||
|
|
3f2458f7e3 | ||
|
|
fb916430f5 | ||
|
|
c9402713c9 | ||
|
|
85b8fcf2d9 | ||
|
|
13d0a4d3cf | ||
|
|
9c1c424ed2 | ||
|
|
2121ce1875 | ||
|
|
26480d5755 | ||
|
|
ab4f1013d3 | ||
|
|
5913097c17 | ||
|
|
fd8d1f1e4c | ||
|
|
9ebe851b38 | ||
|
|
af26e9fd69 | ||
|
|
c6e8a905f5 | ||
|
|
a67411324b | ||
|
|
cb81c6b5c7 | ||
|
|
ccf5182e73 | ||
|
|
97e044cff5 | ||
|
|
e47417912d | ||
|
|
128d82fde6 | ||
|
|
61170bda23 | ||
|
|
89cc6feea0 | ||
|
|
409fec08d3 | ||
|
|
2cc4fd2eb5 | ||
|
|
d94e7b595e | ||
|
|
1339462c82 | ||
|
|
7f8265f85c | ||
|
|
2e8c53c85b | ||
|
|
933834aedd | ||
|
|
013303a44e | ||
|
|
bab252f1e8 | ||
|
|
655e54b551 | ||
|
|
24bf9d3f9a | ||
|
|
dab33cbddc | ||
|
|
ce73493cc5 | ||
|
|
3984ed9da3 | ||
|
|
a42a0188a1 | ||
|
|
f5660dad92 | ||
|
|
440bfdc3b4 | ||
|
|
d66a3aea39 | ||
|
|
db76866309 | ||
|
|
d0dca93fb2 | ||
|
|
0d66824965 | ||
|
|
fd8859abdd | ||
|
|
bf3852b7bd | ||
|
|
3512c1a860 | ||
|
|
8e38540470 | ||
|
|
33985326b1 | ||
|
|
cbe041bdda | ||
|
|
20b14b3221 | ||
|
|
dd4ede4c1d | ||
|
|
9f89ac920c | ||
|
|
d7f8f28ed9 | ||
|
|
4637b57977 | ||
|
|
ac61ddb09a | ||
|
|
141f0a3ebf | ||
|
|
207c626a6c | ||
|
|
7308d81729 | ||
|
|
177a900be3 | ||
|
|
cb48e7670e | ||
|
|
a5889cffc0 | ||
|
|
933b3af54c | ||
|
|
8fcb832edc | ||
|
|
16c90ef97d | ||
|
|
8084f0a6bf | ||
|
|
335d82e607 | ||
|
|
c8813445c2 | ||
|
|
c51a2a5462 | ||
|
|
d0b28f4828 | ||
|
|
1e9cd551ce | ||
|
|
8e5fec95fa | ||
|
|
969d48ccaf | ||
|
|
7715a09374 | ||
|
|
24203f8d9d | ||
|
|
2b04f540e2 | ||
|
|
f2c914926d | ||
|
|
11c4c2791d | ||
|
|
4051cf53dc | ||
|
|
543dfdeac8 | ||
|
|
45c99aee82 | ||
|
|
2d25a6fb25 | ||
|
|
aaf63a73b1 | ||
|
|
bacb1a530a | ||
|
|
c151739415 | ||
|
|
d44e8bd1f6 | ||
|
|
2a742e69e6 | ||
|
|
5bda5d503b | ||
|
|
e871f7a9ca | ||
|
|
0aa4be5086 | ||
|
|
6fed5068ce | ||
|
|
b1b1cd3263 | ||
|
|
55563dac1c | ||
|
|
548f24bbff | ||
|
|
b478462fe2 | ||
|
|
ec350645a4 | ||
|
|
e4aa6e1bdf | ||
|
|
f7f06010ae | ||
|
|
ea34b7a302 | ||
|
|
73dc4536b0 | ||
|
|
2b298ab9fc | ||
|
|
66ff417183 | ||
|
|
c28b9b0ab4 | ||
|
|
8058afe9c6 | ||
|
|
b6f80fc05f | ||
|
|
920e056465 | ||
|
|
6346029cf9 | ||
|
|
822dcd0557 | ||
|
|
5ab85c7621 | ||
|
|
dbb6f358cc | ||
|
|
3a36c2e6ae | ||
|
|
7e021d9bbb | ||
|
|
6448de834c | ||
|
|
4a4a407644 | ||
|
|
f4d145c3a0 | ||
|
|
0bacbaeff7 | ||
|
|
1844aec63e | ||
|
|
baabbdec28 | ||
|
|
2137c28e4e | ||
|
|
79d8dd0020 | ||
|
|
9bce9e4fde | ||
|
|
232a545d19 | ||
|
|
7a96e8131c | ||
|
|
675a2a60c8 | ||
|
|
b8c25bc258 | ||
|
|
f76a867c75 | ||
|
|
c1deb99b70 | ||
|
|
1625bdb454 | ||
|
|
0776cc5a64 | ||
|
|
371e96e2a8 | ||
|
|
9aa41361a6 | ||
|
|
f17dec2c74 | ||
|
|
b982822fdc | ||
|
|
50820dfc9c | ||
|
|
ee0fb39822 | ||
|
|
9760baff9c | ||
|
|
7641a5eded | ||
|
|
f7cd14d0f6 | ||
|
|
f3aad51922 | ||
|
|
b29603f1b7 | ||
|
|
afb04a19f9 | ||
|
|
848218cdac | ||
|
|
6a1cef052f | ||
|
|
455076446b | ||
|
|
50e9ae6ca7 | ||
|
|
6e83cb8d82 | ||
|
|
1f0861771a | ||
|
|
4917bfca29 | ||
|
|
b46481e12f | ||
|
|
e4c2fc2ac1 | ||
|
|
99e69bd6c5 | ||
|
|
4d366f4688 | ||
|
|
daa0049b32 | ||
|
|
bc06c5e43f | ||
|
|
da1541c791 | ||
|
|
1ce2433212 | ||
|
|
8cecbf5e9d | ||
|
|
a1b53d35ea | ||
|
|
de71ecf195 | ||
|
|
05a85a8e9c | ||
|
|
5a618258a3 | ||
|
|
ba54afbf45 | ||
|
|
2af50f7b78 | ||
|
|
1ea44670ce | ||
|
|
ff0df96cfc | ||
|
|
3476f224b7 | ||
|
|
e612592e21 | ||
|
|
cfbd5bbb7f | ||
|
|
46e000394b | ||
|
|
875b95f609 | ||
|
|
ffdf3d32b2 | ||
|
|
1fa74df6c7 | ||
|
|
cb9c17c025 | ||
|
|
923eeb9602 | ||
|
|
c5ff2d1dcf | ||
|
|
c65dcc9b19 | ||
|
|
15dd4d3c07 | ||
|
|
058d55b68c | ||
|
|
22ebd5f2d6 | ||
|
|
8744abc39f | ||
|
|
1574b9380b | ||
|
|
66441fb2cb | ||
|
|
2466307b74 | ||
|
|
6a5b25045c | ||
|
|
742a922b90 | ||
|
|
2e2d534257 | ||
|
|
271700692a | ||
|
|
6bc699672b | ||
|
|
c07b0d0912 | ||
|
|
61cac4eebc | ||
|
|
b723da6574 | ||
|
|
0159de8a21 | ||
|
|
c961a73c42 | ||
|
|
2bafb1704b | ||
|
|
cf84ed3881 | ||
|
|
95de5bad78 | ||
|
|
7cdb3b09d9 | ||
|
|
b3cf44631c | ||
|
|
e3d96c349f | ||
|
|
f0e42f0654 | ||
|
|
bf1da40976 | ||
|
|
9994dba0f7 | ||
|
|
294885be1a | ||
|
|
82cdc51ca8 | ||
|
|
e855a85c85 | ||
|
|
b9c49c93b8 | ||
|
|
dc393ed14a | ||
|
|
cbae2aecfc | ||
|
|
66e0c53593 | ||
|
|
d7bf2aa783 | ||
|
|
90d0e76eb6 | ||
|
|
6f3c635f6f | ||
|
|
2b12cb4a7e | ||
|
|
1a9fb9caca | ||
|
|
2da92475ba | ||
|
|
36d1dcd9cc | ||
|
|
eb62c3bd27 | ||
|
|
05cd7f170f | ||
|
|
0d4655d1db | ||
|
|
e42e3e55ca | ||
|
|
2a6039da98 | ||
|
|
33035e9978 | ||
|
|
5f682023a6 | ||
|
|
fe2e4a1010 | ||
|
|
50e479332c | ||
|
|
49c3770d87 | ||
|
|
ae24cad4b8 | ||
|
|
a8eab99e44 | ||
|
|
b6d97c3f1d | ||
|
|
6daca1b630 | ||
|
|
9a3695c949 | ||
|
|
9499b9f9be | ||
|
|
5fdc6b55ee | ||
|
|
3f2b44f983 | ||
|
|
18a8272fe5 | ||
|
|
070f6a00a8 | ||
|
|
12dabedf0d | ||
|
|
24da600a72 | ||
|
|
84561408d1 | ||
|
|
9065839328 | ||
|
|
3f30bf156c | ||
|
|
b659ecfa7e | ||
|
|
c240a49f04 | ||
|
|
e89b37190e | ||
|
|
ff61a74a8a | ||
|
|
39db5a1efd | ||
|
|
0802014779 | ||
|
|
bfc30274ff | ||
|
|
bec7a0becb | ||
|
|
fe8fc1be25 | ||
|
|
bcb7cff5f2 | ||
|
|
8023c29c3f | ||
|
|
147fafb067 | ||
|
|
674f4a2cb3 | ||
|
|
b326fe12d9 | ||
|
|
263f0d1c81 | ||
|
|
47ae5b4758 | ||
|
|
25c54dad84 | ||
|
|
48b8efe9c6 | ||
|
|
5d34fcdccd | ||
|
|
e63546bda6 | ||
|
|
04c8ca9718 | ||
|
|
08d69e3975 | ||
|
|
bfc165c46d | ||
|
|
9764f3f384 | ||
|
|
b1915b1ad6 | ||
|
|
5991eb04fa | ||
|
|
b3e07652cc | ||
|
|
cad7551caf | ||
|
|
8ac132fee1 | ||
|
|
ec94c93b24 | ||
|
|
96ed1ff950 | ||
|
|
9d4f15c1c9 | ||
|
|
6bcb7adeb4 | ||
|
|
d311e10bea | ||
|
|
adf7834529 | ||
|
|
c837d8e1d3 | ||
|
|
ce61c60af5 | ||
|
|
dc0dd43406 | ||
|
|
6c2744d373 | ||
|
|
f671e1204c | ||
|
|
c0af2622af | ||
|
|
6b511cc844 | ||
|
|
4ade0f98aa | ||
|
|
67a672e6ed | ||
|
|
54e8ea13f4 | ||
|
|
3110be1ca5 |
+1
-3
@@ -6,6 +6,4 @@ notifications:
|
||||
- jose.valim@plataformatec.com.br
|
||||
- eric.meadows.jonsson@gmail.com
|
||||
otp_release:
|
||||
- R16B02
|
||||
- R16B01
|
||||
- R16B
|
||||
- 17.0
|
||||
|
||||
+117
-3
@@ -1,9 +1,123 @@
|
||||
# v0.13.0 (2014-04-20)
|
||||
|
||||
* Enhancements
|
||||
* [Base] Add `Base` module which does conversions to bases 16, 32, hex32, 64 and url64
|
||||
* [Code] Add `Code.eval_file/2`
|
||||
* [Collectable] Add the `Collectable` protocol that empowers `Enum.into/2` and `Stream.into/2` and the `:into` option in comprehensions
|
||||
* [Collectable] Implement `Collectable` for lists, dicts, bitstrings, functions and provide both `File.Stream` and `IO.Stream`
|
||||
* [EEx] Add `handle_body/1` callback to `EEx.Engine`
|
||||
* [Enum] Add `Enum.group_by/2`, `Enum.into/2`, `Enum.into/3`, `Enum.traverse/2` and `Enum.sum/2`
|
||||
* [ExUnit] Randomize cases and tests suite runs, allow seed configuration and the `--seed` flag via `mix test`
|
||||
* [ExUnit] Support `--only` for filtering when running tests with `mix test`
|
||||
* [ExUnit] Raise an error if another `capture_io` process already captured the device
|
||||
* [ExUnit] Improve formatter to show source code and rely on lhs and rhs (instead of expected and actual)
|
||||
* [IEx] Allow prompt configuration with the `:prompt` option
|
||||
* [IEx] Use werl on Windows
|
||||
* [Kernel] Support `ERL_PATH` in `bin/elixir`
|
||||
* [Kernel] Support interpolation in keyword syntax
|
||||
* [Map] Add a Map module and support 17.0 maps and structs
|
||||
* [Mix] Add dependency option `:only` to specify the dependency environment. `mix deps.get` and `mix deps.update` works accross all environment unless `--only` is specified
|
||||
* [Mix] Add `Mix.Shell.prompt/1`
|
||||
* [Mix] Ensure the project is compiled in case Mix' CLI cannot find a task
|
||||
* [Node] Add `Node.ping/1`
|
||||
* [Process] Include `Process.send/3` and support the `--gen-debug` option
|
||||
* [Regex] Regexes no longer need the "g" option when there is a need to use named captures
|
||||
* [Stream] Add `Stream.into/2` and `Stream.into/3`
|
||||
* [StringIO] Add a `StringIO` module that allows a String to be used as IO device
|
||||
* [System] Add `System.delete_env/1` to remove a variable from the environment
|
||||
|
||||
* Bug fixes
|
||||
* [CLI] Ensure `--app` is handled as an atom before processing
|
||||
* [ExUnit] Ensure `ExUnit.Assertions` does not emit compiler warnings for `assert_receive`
|
||||
* [Kernel] Ensure the same pid is not queued twice in the parallel compiler
|
||||
* [Macro] `Macro.to_string/2` considers proper precedence when translating `!(foo > bar)` into a string
|
||||
* [Mix] Automatically recompile on outdated Elixir version and show proper error messages
|
||||
* [Mix] Ensure generated `.app` file includes core dependencies
|
||||
* [Mix] Allow a dependency with no SCM to be overridden
|
||||
* [Mix] Allow queries in `mix local.install` URL
|
||||
* [OptionParser] Do not recognize undefined aliases as switches
|
||||
|
||||
* Deprecations
|
||||
* [Dict] `Dict.empty/1`, `Dict.new/1` and `Dict.new/2` are deprecated
|
||||
* [Exception] `Exception.normalize/1` is deprecated in favor of `Exception.normalize/2`
|
||||
* [Kernel] `lc` and `bc` comprehensions are deprecated in favor of `for` (this is a soft deprecation, no warning will be emitted)
|
||||
* [ListDict] `ListDict` is deprecated in favor of `Map` (this is a soft deprecation, no warning will be emitted)
|
||||
* [Record] `defrecord/2`, `defrecordp/3`, `is_record/1` and `is_record/2` macros in Kernel are deprecated. Instead, use the new macros and API defined in the `Record` module (this is a soft deprecation, no warnings will be emitted)
|
||||
|
||||
* Backwards incompatible changes
|
||||
* [ExUnit] Formatters are now required to be a GenEvent and `ExUnit.run/2` returns a map with results
|
||||
|
||||
# v0.12.5 (2014-03-09)
|
||||
|
||||
* Bug fixes
|
||||
* [Kernel] Ensure `try` does not generate an after clause. Generating an after clause forbade clauses in the `else` part from being tail recursive. This should improve performance and memory consumption of `Stream` functions
|
||||
* [Mix] Automatically recompile on outdated Elixir version and show proper error messages
|
||||
|
||||
* Deprecations
|
||||
* [File] `File.stream_to!/3` is deprecated
|
||||
* [GenFSM] `GenFSM` is deprecated
|
||||
* [Kernel] `%` for sigils is deprecated in favor of `~`
|
||||
* [Kernel] `is_range/1` and `is_regex/1` are deprecated in favor of `Range.range?/1` and `Regex.regex?/1`
|
||||
* [Stream] `Stream.after/1` is deprecated
|
||||
* [URI] `URI.decode_query/1` is deprecated in favor of `URI.decode_query/2` with explicit dict argument
|
||||
* [URI] Passing lists as key or values in `URI.encode_query/1` is deprecated
|
||||
|
||||
* Backwards incompatible changes
|
||||
* [Mix] Remove `MIX_GIT_FORCE_HTTPS` as Git itself already provides mechanisms for doing so
|
||||
|
||||
# v0.12.4 (2014-02-12)
|
||||
|
||||
* Enhancements
|
||||
* [Mix] `mix deps.get` and `mix deps.update` no longer compile dependencies afterwards. Instead, they mark the dependencies which are going to be automatically compiled next time `deps.check` is invoked (which is done automatically by most mix tasks). This means users should have a better workflow when migrating in between environments
|
||||
|
||||
* Deprecations
|
||||
* [Kernel] `//` for default arguments is deprecated in favor of `\\`
|
||||
* [Kernel] Using `%` for sigils is deprecated in favor of `~`. This is a soft deprecation, no warnings will be emitted for it in this release
|
||||
* [Kernel] Using `^` inside function clause heads is deprecated, please use a guard instead
|
||||
|
||||
* Backwards incompatible changes
|
||||
* [ExUnit] `CaptureIO` returns an empty string instead of nil when there is no capture
|
||||
* [Version] The `Version` module now only works with SemVer. The functions `Version.parse/1` and `Version.parse_requirement/1` now return `{:ok,res} | :error` for the cases you want to handle non SemVer cases manually. All other functions will trigger errors on non semantics versions
|
||||
|
||||
# v0.12.3 (2014-02-02)
|
||||
|
||||
* Enhancements
|
||||
* [Kernel] Warnings now are explicitly tagged with "warning:" in messages
|
||||
* [Kernel] Explicit functions inlined by the compiler, including operators. This means that `Kernel.+/2` will now expand to `:erlang.+/2` and so on
|
||||
* [Mix] Do not fail if a Mix dependency relies on an outdated Elixir version
|
||||
* [Process] Add `Process.send/2` and `Process.send_after/3`
|
||||
* [Version] Add `Version.compare/2`
|
||||
|
||||
* Bug fixes
|
||||
* [Atom] Inspect `:...` and `:foo@bar` without quoting
|
||||
* [Keyword] The list `[1, 2, three: :four]` now correctly expands to `[1, 2, {:three, :four}]`
|
||||
* [Kernel] Ensure undefined `@attributes` shows proper stacktrace in warnings
|
||||
* [Kernel] Guarantee nullary funs/macros are allowed in guards
|
||||
* [Process] Ensure monitoring functions are inlined by the compiler
|
||||
|
||||
* Deprecations
|
||||
* [IEx] The helper `m/0` has been deprecated. The goal is to group all runtime statistic related helpers into a single module
|
||||
* [Kernel] `binary_to_term/1`, `binary_to_term/2`, `term_to_binary/1` and `term_to_binary/2` are deprecated in favor of their counterparts in the `:erlang` module
|
||||
* [Kernel] `//` for default arguments is deprecated in favor of `\\`. This is a soft deprecation, no warnings will be emitted for it in this release
|
||||
* [Kernel] Deprecated `@behavior` in favor of `@behaviour`
|
||||
* [Record] `to_keywords`, `getter` and `list getter` functionalities in `defrecordp` are deprecated
|
||||
* [Record] `Record.import/2` is deprecated
|
||||
|
||||
* Backwards incompatible changes
|
||||
* [Dict] Implementations of `equal?/2` and `merge/2` in `HashDict` and `ListDict` are no longer polymorphic. To get polymorphism, use the functions in `Dict` instead
|
||||
* [File] `File.cp/3` and `File.cp_r/3` no longer carry Unix semantics where the function behaves differently if the destination is an existing previous directory or not. It now always copies source to destination, doing it recursively in the latter
|
||||
* [IEx] IEx now loads the `.iex.exs` file instead of `.iex`
|
||||
* [Kernel] Remove `**` from the list of allowed operators
|
||||
* [Kernel] Limit sigils delimiters to one of the following: `<>`, `{}`, `[]`, `()`, `||`, `//`, `"` and `'`
|
||||
* [Range] `Range` is no longer a record, instead use `first .. last` if you need pattern matching
|
||||
* [Set] Implementations of `difference/2`, `disjoint?/2`, `equal?/2`, `intersection/2`, `subset?/2` and `union/2` in `HashSet` are no longer polymorphic. To get polymorphism, use the functions in `Set` instead
|
||||
|
||||
# v0.12.2 (2014-01-15)
|
||||
|
||||
* Enhancements
|
||||
* [EEx] Allow `EEx.AssignsEngine` to accept any Dict
|
||||
* [Enum] Add `Enum.flat_map_reduce/3`
|
||||
* [ExUnit] Support @moduletag in ExUnit cases
|
||||
* [ExUnit] Support `@moduletag` in ExUnit cases
|
||||
* [Kernel] Improve stacktraces to be relative to the compilation path and include the related application
|
||||
* [Stream] Add `Stream.transform/3`
|
||||
|
||||
@@ -18,7 +132,7 @@
|
||||
|
||||
* Deprecations
|
||||
* [Enum] Deprecate `Enum.first/1` in favor of `Enum.at/2` and `List.first/1`
|
||||
* [Kernel] Deprecate continuable heredocs. In previous versions, Elixir would continue parsing on the same line the heredoc started, this behavior has been deprecated
|
||||
* [Kernel] Deprecate continuable heredocs. In previous versions, Elixir would continue parsing on the same line the heredoc started, this behaviour has been deprecated
|
||||
* [Kernel] `is_alive/0` is deprecated in favor of `Node.alive?`
|
||||
* [Kernel] `Kernel.inspect/2` with `Inspect.Opts[]` is deprecated in favor of `Inspect.Algebra.to_doc/2`
|
||||
* [Kernel] `Kernel.inspect/2` with `:raw` option is deprecated, use `:records` option instead
|
||||
@@ -718,7 +832,7 @@
|
||||
* Backwards incompatible changes
|
||||
* [Kernel] Compile files now follow `Elixir-ModuleName` convention to solve issues with Erlang embedded mode. This removes the `__MAIN__` pseudo-variable as modules are now located inside `Elixir` namespace
|
||||
* [Kernel] `__using__` callback triggered by `use` now receives just one argument. Caller information can be accessed via macros using `__CALLER__`
|
||||
* [Kernel] Comprehensions syntax changed to be more compatible with Erlang behavior
|
||||
* [Kernel] Comprehensions syntax changed to be more compatible with Erlang behaviour
|
||||
* [Kernel] loop and recur are removed in favor of recursion with named functions
|
||||
* [Module] Removed data functions in favor of unifying the attributes API
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
REBAR := rebar
|
||||
ELIXIRC := bin/elixirc --verbose --ignore-module-conflict $(ELIXIRC_OPTS)
|
||||
ELIXIRC := bin/elixirc --verbose --ignore-module-conflict
|
||||
ERLC := erlc -I lib/elixir/include
|
||||
ERL := erl -I lib/elixir/include -noshell -pa lib/elixir/ebin
|
||||
VERSION := $(strip $(shell cat VERSION))
|
||||
@@ -11,7 +11,7 @@ INSTALL_DIR = $(INSTALL) -m755 -d
|
||||
INSTALL_DATA = $(INSTALL) -m644
|
||||
INSTALL_PROGRAM = $(INSTALL) -m755
|
||||
|
||||
.PHONY: install compile erlang elixir dialyze test clean docs release_docs release_zip release_erl
|
||||
.PHONY: install compile erlang elixir dialyze test clean docs release_docs release_zip
|
||||
.NOTPARALLEL: compile
|
||||
|
||||
#==> Templates
|
||||
@@ -76,7 +76,7 @@ unicode: $(UNICODE)
|
||||
$(UNICODE): lib/elixir/unicode/*
|
||||
@ echo "==> unicode (compile)";
|
||||
@ echo "This step can take up to a minute to compile in order to embed the Unicode database"
|
||||
$(Q) cd lib/elixir && ../../$(ELIXIRC) unicode/graphemes.ex unicode/unicode.ex -o ebin;
|
||||
$(Q) cd lib/elixir && ../../$(ELIXIRC) unicode/unicode.ex -o ebin;
|
||||
|
||||
$(eval $(call APP_TEMPLATE,ex_unit,ExUnit))
|
||||
$(eval $(call APP_TEMPLATE,eex,EEx))
|
||||
@@ -127,17 +127,13 @@ docs: compile ../ex_doc/bin/ex_doc
|
||||
|
||||
release_zip: compile
|
||||
rm -rf v$(VERSION).zip
|
||||
zip -9 -r v$(VERSION).zip bin CHANGELOG.md LEGAL lib/*/ebin LICENSE README.md rel VERSION
|
||||
zip -9 -r v$(VERSION).zip bin CHANGELOG.md LEGAL lib/*/ebin LICENSE README.md VERSION
|
||||
|
||||
release_docs: docs
|
||||
cd ../docs
|
||||
rm -rf ../docs/master
|
||||
mv docs ../docs/master
|
||||
|
||||
release_erl: compile
|
||||
$(Q) rm -rf rel/elixir
|
||||
$(Q) cd rel && ../rebar generate
|
||||
|
||||
#==> Tests tasks
|
||||
|
||||
test: test_erlang test_elixir
|
||||
|
||||
@@ -16,9 +16,9 @@ If Elixir fails to build (specifically when pulling in a new version via git), b
|
||||
|
||||
If tests pass, you are ready to move on to the [Getting Started guide][1] or to try Interactive Elixir by running: `bin/iex` in your terminal.
|
||||
|
||||
However, if tests fail, it is likely you have an outdated Erlang version (Elixir requires Erlang R16B or later). You can check your Erlang version by calling `erl` in the command line. You will see some information as follows:
|
||||
However, if tests fail, it is likely you have an outdated Erlang version (Elixir requires Erlang 17.0 or later). You can check your Erlang version by calling `erl` in the command line. You will see some information as follows:
|
||||
|
||||
Erlang R16B (erts-5.10.1) [source] [64-bit] [smp:2:2] [rq:2] [async-threads:0] [hipe] [kernel-poll:false]
|
||||
Erlang/OTP 17 [RELEASE CANDIDATE 1] [erts-6.0] [source] [64-bit] [smp:4:4] [async-threads:10] [hipe] [kernel-poll:false]
|
||||
|
||||
If you have the correct version and tests still fail, feel free to [open an issue][2].
|
||||
|
||||
|
||||
+7
-9
@@ -4,25 +4,23 @@ This document simply outlines the release process:
|
||||
|
||||
1) Remove `-dev` extension from VERSION
|
||||
|
||||
2) Run `make clean test` to ensure all tests pass from scratch and the CI is green
|
||||
2) Ensure CHANGELOG is updated and timestamp
|
||||
|
||||
3) Ensure CHANGELOG is updated and tag release version with timestamp in it
|
||||
3) Commit changes above with title "Release vVERSION" and generate new tag
|
||||
|
||||
4) Commit changes above with title "Release vVERSION"
|
||||
4) Run `make clean test` to ensure all tests pass from scratch and the CI is green
|
||||
|
||||
5) Push master and create tag vVERSION from master branch
|
||||
5) Push master and tags
|
||||
|
||||
6) Release new docs with `make release_docs`, move docs to `docs/stable`
|
||||
|
||||
7) Release new zip with `make release_zip`, push new zip to GitHub Releases
|
||||
|
||||
8) Push package to expm with `expm publish package.exs`
|
||||
8) Merge master into stable branch and push it
|
||||
|
||||
9) Merge master into stable branch and push it
|
||||
9) After release, bump versions, add `-dev` back and commit
|
||||
|
||||
10) After release, bump versions, add `-dev` back and commit
|
||||
|
||||
11) `make release_docs` once again and push `elixir-lang.github.com`
|
||||
10) `make release_docs` once again and push it to `elixir-lang.github.com`
|
||||
|
||||
## Places where version is mentioned
|
||||
|
||||
|
||||
+21
-7
@@ -17,6 +17,7 @@ if [ $# -eq 0 ] || [ "$1" = "--help" ] || [ "$1" = "-h" ]; then
|
||||
--hidden Makes a hidden node
|
||||
--detached Starts the Erlang VM detached from console
|
||||
--no-halt Does not halt the Erlang VM after execution
|
||||
--gen-debug Turns on default debugging for all GenServers
|
||||
|
||||
** Options marked with (*) can be given more than once
|
||||
** Options given after the .exs file or -- are passed down to the executed code
|
||||
@@ -34,6 +35,7 @@ readlink_f () {
|
||||
fi
|
||||
}
|
||||
|
||||
MODE="elixir"
|
||||
ERL=""
|
||||
I=1
|
||||
|
||||
@@ -41,7 +43,13 @@ while [ $I -le $# ]; do
|
||||
S=1
|
||||
eval "PEEK=\${$I}"
|
||||
case "$PEEK" in
|
||||
-v|--compile|--no-halt|+iex|+compile)
|
||||
+iex)
|
||||
MODE="iex"
|
||||
;;
|
||||
+elixirc)
|
||||
MODE="elixirc"
|
||||
;;
|
||||
-v|--compile|--no-halt)
|
||||
;;
|
||||
-e|-r|-pr|-pa|-pz|--remsh|--app)
|
||||
S=2
|
||||
@@ -64,6 +72,9 @@ while [ $I -le $# ]; do
|
||||
eval "VAL=\${$I}"
|
||||
ERL="$ERL "$VAL""
|
||||
;;
|
||||
--gen-debug)
|
||||
ERL="$ERL -generic_debug"
|
||||
;;
|
||||
*)
|
||||
break
|
||||
;;
|
||||
@@ -73,11 +84,14 @@ done
|
||||
|
||||
SELF=$(readlink_f "$0")
|
||||
SCRIPT_PATH=$(dirname "$SELF")
|
||||
if [ "$ELIXIR_NO_CLI" != "1" ]; then ERL="$ERL -s elixir start_cli"; fi
|
||||
if [ "$MODE" != "iex" ]; then ERL="$ERL -s elixir start_cli"; fi
|
||||
|
||||
if [ -f "$SCRIPT_PATH/../releases/RELEASES" ] && [ -f "$SCRIPT_PATH/erl" ]
|
||||
then
|
||||
exec "$SCRIPT_PATH"/erl -pa "$SCRIPT_PATH"/../lib/*/ebin -noshell $ELIXIR_ERL_OPTS $ERL -boot start -extra "$@"
|
||||
else
|
||||
exec erl -pa "$SCRIPT_PATH"/../lib/*/ebin -noshell $ELIXIR_ERL_OPTS $ERL -extra "$@"
|
||||
if [ -z "$ERL_PATH" ]; then
|
||||
if [ -f "$SCRIPT_PATH/../releases/RELEASES" ] && [ -f "$SCRIPT_PATH/erl" ]; then
|
||||
ERL_PATH="$SCRIPT_PATH"/erl
|
||||
else
|
||||
ERL_PATH=erl
|
||||
fi
|
||||
fi
|
||||
|
||||
exec "$ERL_PATH" -pa "$SCRIPT_PATH"/../lib/*/ebin -noshell $ELIXIR_ERL_OPTS $ERL -extra "$@"
|
||||
|
||||
+12
-2
@@ -24,6 +24,7 @@ echo --cookie cookie Sets a cookie for this distributed node
|
||||
echo --hidden Makes a hidden node
|
||||
echo --detached Starts the Erlang VM detached from console
|
||||
echo --no-halt Does not halt the Erlang VM after execution
|
||||
echo --gen-debug Turns on default debugging for all GenServers
|
||||
echo.
|
||||
echo ** Options marked with (*) can be given more than once
|
||||
echo ** Options given after the .exs file or -- are passed down to the executed code
|
||||
@@ -44,6 +45,9 @@ set originPath=%~dp0
|
||||
rem Optional parameters before the "-extra" parameter
|
||||
set beforeExtra=
|
||||
|
||||
rem Flag which determines whether or not to use werl vs erl
|
||||
set useWerl=0
|
||||
|
||||
rem Recursive loop called for each parameter that parses the cmd line parameters
|
||||
:startloop
|
||||
set par="%1"
|
||||
@@ -56,6 +60,8 @@ if "%par%"=="""" (
|
||||
rem if no parameters defined - special case for parameter that is already quoted
|
||||
goto :expand_erl_libs
|
||||
)
|
||||
rem ******* EXECUTION OPTIONS **********************
|
||||
IF "%par%"==""+iex"" (Set useWerl=1)
|
||||
rem ******* ERLANG PARAMETERS **********************
|
||||
IF NOT "%par%"=="%par:--detached=%" (Set parsErlang=%parsErlang% -detached)
|
||||
IF NOT "%par%"=="%par:--hidden=%" (Set parsErlang=%parsErlang% -hidden)
|
||||
@@ -63,6 +69,7 @@ IF NOT "%par%"=="%par:--cookie=%" (Set parsErlang=%parsErlang% -setcookie %1 &
|
||||
IF NOT "%par%"=="%par:--sname=%" (Set parsErlang=%parsErlang% -sname %1 && shift)
|
||||
IF NOT "%par%"=="%par:--name=%" (Set parsErlang=%parsErlang% -name %1 && shift)
|
||||
IF NOT "%par%"=="%par:--erl=%" (Set beforeExtra=%beforeExtra% %~1 && shift)
|
||||
IF NOT "%par%"=="%par:--gen-debug=%" (Set parsErlang=%parsErlang% -generic_debug)
|
||||
rem ******* elixir parameters **********************
|
||||
rem Note: we don't have to do anything with options that don't take an argument
|
||||
IF NOT "%par%"=="%par:-e=%" (shift)
|
||||
@@ -84,5 +91,8 @@ for /d %%d in ("%originPath%..\lib\*.") do (
|
||||
)
|
||||
SETLOCAL disabledelayedexpansion
|
||||
:run
|
||||
erl %ext_libs% -noshell %ELIXIR_ERL_OPTS% %parsErlang% -s elixir start_cli %beforeExtra% -extra %*
|
||||
|
||||
IF %useWerl% EQU 1 (
|
||||
werl %ext_libs% -noshell %ELIXIR_ERL_OPTS% %parsErlang% -s elixir start_cli %beforeExtra% -extra %*
|
||||
) ELSE (
|
||||
erl %ext_libs% -noshell %ELIXIR_ERL_OPTS% %parsErlang% -s elixir start_cli %beforeExtra% -extra %*
|
||||
)
|
||||
+1
-1
@@ -26,4 +26,4 @@ readlink_f () {
|
||||
|
||||
SELF=$(readlink_f "$0")
|
||||
SCRIPT_PATH=$(dirname "$SELF")
|
||||
exec "$SCRIPT_PATH"/elixir +compile "$@"
|
||||
exec "$SCRIPT_PATH"/elixir +elixirc "$@"
|
||||
|
||||
+1
-1
@@ -20,4 +20,4 @@ echo ** Options marked with (*) can be given more than once
|
||||
echo ** Options given after -- are passed down to the executed code
|
||||
echo ** Options can be passed to the erlang runtime using ELIXIR_ERL_OPTS" >&2
|
||||
:run
|
||||
call "%~dp0\elixir.bat" +compile %*
|
||||
call "%~dp0\elixir.bat" +elixirc %*
|
||||
|
||||
@@ -16,13 +16,14 @@ if [ $# -gt 0 ] && ([ "$1" = "--help" ] || [ "$1" = "-h" ]); then
|
||||
--cookie \"cookie\" Sets a cookie for this distributed node
|
||||
--hidden Makes a hidden node
|
||||
--detached Starts the Erlang VM detached from console
|
||||
--gen-debug Turns on default debugging for all GenServers
|
||||
--remsh \"name\" Connects to a node using a remote shell
|
||||
--dot-iex \"path\" Overrides default .iex file and uses path instead;
|
||||
--dot-iex \"path\" Overrides default .iex.exs file and uses path instead;
|
||||
path can be empty, then no file will be loaded
|
||||
|
||||
** Options marked with (*) can be given more than once
|
||||
** Options given after the .exs file or -- are passed down to the executed code
|
||||
** Options can be passed to the erlang runtime using ELIXIR_ERL_OPTS or --erl" >&2
|
||||
** Options can be passed to the VM using ELIXIR_ERL_OPTS or --erl" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
@@ -38,4 +39,4 @@ readlink_f () {
|
||||
|
||||
SELF=$(readlink_f "$0")
|
||||
SCRIPT_PATH=$(dirname "$SELF")
|
||||
ELIXIR_NO_CLI=1 exec "$SCRIPT_PATH"/elixir --no-halt --erl "-user Elixir.IEx.CLI" +iex "$@"
|
||||
exec "$SCRIPT_PATH"/elixir --no-halt --erl "-user Elixir.IEx.CLI" +iex "$@"
|
||||
|
||||
+1
-1
@@ -1,2 +1,2 @@
|
||||
@echo off
|
||||
call "%~dp0\elixir.bat" +iex --no-halt -e "IEx.start" %*
|
||||
call "%~dp0\elixir.bat" +iex --erl "-user Elixir.IEx.CLI" --no-halt %
|
||||
+22
-23
@@ -1,12 +1,12 @@
|
||||
defexception EEx.SyntaxError, message: nil
|
||||
|
||||
defmodule EEx do
|
||||
@moduledoc %S"""
|
||||
@moduledoc ~S"""
|
||||
EEx stands for Embedded Elixir. It allows you to embed
|
||||
Elixir code inside a string in a robust way:
|
||||
|
||||
EEx.eval_string "foo <%= bar %>", [bar: "baz"]
|
||||
#=> "foo baz"
|
||||
iex> EEx.eval_string "foo <%= bar %>", [bar: "baz"]
|
||||
"foo baz"
|
||||
|
||||
## API
|
||||
|
||||
@@ -31,7 +31,7 @@ defmodule EEx do
|
||||
|
||||
All functions in this module accepts EEx-related options.
|
||||
They are:
|
||||
|
||||
|
||||
* `:line` - the line to be used as the template start.
|
||||
Defaults to 1;
|
||||
* `:file` - the file to be used in the template.
|
||||
@@ -79,12 +79,12 @@ defmodule EEx do
|
||||
An example is the `@` macro which allows easy data access
|
||||
in a template:
|
||||
|
||||
EEx.eval_string "<%= @foo %>", assigns: [foo: 1]
|
||||
#=> 1
|
||||
iex> EEx.eval_string "<%= @foo %>", assigns: [foo: 1]
|
||||
"1"
|
||||
|
||||
In other words, <%= @foo %> is simply translated to:
|
||||
|
||||
<%= Keyword.get assigns, :foo %>
|
||||
<%= Dict.get assigns, :foo %>
|
||||
|
||||
The assigns extension is useful when the number of variables
|
||||
required by the template is not specified at compilation time.
|
||||
@@ -97,15 +97,15 @@ defmodule EEx do
|
||||
|
||||
## Examples
|
||||
|
||||
defmodule Sample do
|
||||
require EEx
|
||||
EEx.function_from_string :def, :sample, "<%= a + b %>", [:a, :b]
|
||||
end
|
||||
|
||||
Sample.sample(1, 2) #=> "3"
|
||||
iex> defmodule Sample do
|
||||
...> require EEx
|
||||
...> EEx.function_from_string :def, :sample, "<%= a + b %>", [:a, :b]
|
||||
...> end
|
||||
iex> Sample.sample(1, 2)
|
||||
"3"
|
||||
|
||||
"""
|
||||
defmacro function_from_string(kind, name, source, args // [], options // []) do
|
||||
defmacro function_from_string(kind, name, source, args \\ [], options \\ []) do
|
||||
quote bind_quoted: binding do
|
||||
info = Keyword.merge [file: __ENV__.file, line: __ENV__.line], options
|
||||
args = Enum.map args, fn arg -> { arg, [line: info[:line]], nil } end
|
||||
@@ -141,7 +141,7 @@ defmodule EEx do
|
||||
Sample.sample(1, 2) #=> "3"
|
||||
|
||||
"""
|
||||
defmacro function_from_file(kind, name, file, args // [], options // []) do
|
||||
defmacro function_from_file(kind, name, file, args \\ [], options \\ []) do
|
||||
quote bind_quoted: binding do
|
||||
info = Keyword.merge options, [file: file, line: 1]
|
||||
args = Enum.map args, fn arg -> { arg, [line: 1], nil } end
|
||||
@@ -159,7 +159,7 @@ defmodule EEx do
|
||||
Get a string `source` and generate a quoted expression
|
||||
that can be evaluated by Elixir or compiled to a function.
|
||||
"""
|
||||
def compile_string(source, options // []) do
|
||||
def compile_string(source, options \\ []) do
|
||||
EEx.Compiler.compile(source, options)
|
||||
end
|
||||
|
||||
@@ -167,7 +167,7 @@ defmodule EEx do
|
||||
Get a `filename` and generate a quoted expression
|
||||
that can be evaluated by Elixir or compiled to a function.
|
||||
"""
|
||||
def compile_file(filename, options // []) do
|
||||
def compile_file(filename, options \\ []) do
|
||||
options = Keyword.merge options, [file: filename, line: 1]
|
||||
compile_string(File.read!(filename), options)
|
||||
end
|
||||
@@ -177,11 +177,11 @@ defmodule EEx do
|
||||
|
||||
## Examples
|
||||
|
||||
EEx.eval_string "foo <%= bar %>", [bar: "baz"]
|
||||
#=> "foo baz"
|
||||
iex> EEx.eval_string "foo <%= bar %>", [bar: "baz"]
|
||||
"foo baz"
|
||||
|
||||
"""
|
||||
def eval_string(source, bindings // [], options // []) do
|
||||
def eval_string(source, bindings \\ [], options \\ []) do
|
||||
compiled = compile_string(source, options)
|
||||
do_eval(compiled, bindings, options)
|
||||
end
|
||||
@@ -195,11 +195,10 @@ defmodule EEx do
|
||||
foo <%= bar %>
|
||||
|
||||
# iex
|
||||
EEx.eval_file "sample.ex", [bar: "baz"]
|
||||
#=> "foo baz"
|
||||
EEx.eval_file "sample.ex", [bar: "baz"] #=> "foo baz"
|
||||
|
||||
"""
|
||||
def eval_file(filename, bindings // [], options // []) do
|
||||
def eval_file(filename, bindings \\ [], options \\ []) do
|
||||
options = Keyword.put options, :file, filename
|
||||
compiled = compile_file(filename, options)
|
||||
do_eval(compiled, bindings, options)
|
||||
|
||||
+21
-21
@@ -17,8 +17,8 @@ defmodule EEx.Compiler do
|
||||
|
||||
# Generates the buffers by handling each expression from the tokenizer
|
||||
|
||||
defp generate_buffer([{ :text, _line, chars }|t], buffer, scope, state) do
|
||||
buffer = state.engine.handle_text(buffer, chars)
|
||||
defp generate_buffer([{ :text, chars }|t], buffer, scope, state) do
|
||||
buffer = state.engine.handle_text(buffer, String.from_char_list!(chars))
|
||||
generate_buffer(t, buffer, scope, state)
|
||||
end
|
||||
|
||||
@@ -28,8 +28,9 @@ defmodule EEx.Compiler do
|
||||
generate_buffer(t, buffer, scope, state)
|
||||
end
|
||||
|
||||
defp generate_buffer([{ :start_expr, line, mark, chars }|t], buffer, scope, state) do
|
||||
{ contents, t } = generate_buffer(t, "", [chars|scope], state.dict([]).line(line).start_line(line))
|
||||
defp generate_buffer([{ :start_expr, start_line, mark, chars }|t], buffer, scope, state) do
|
||||
{ contents, line, t } = look_ahead_text(t, start_line, chars)
|
||||
{ contents, t } = generate_buffer(t, "", [contents|scope], state.dict([]).line(line).start_line(start_line))
|
||||
buffer = state.engine.handle_expr(buffer, mark, contents)
|
||||
generate_buffer(t, buffer, scope, state)
|
||||
end
|
||||
@@ -50,8 +51,8 @@ defmodule EEx.Compiler do
|
||||
raise EEx.SyntaxError, message: "unexpected token: #{inspect chars} at line #{inspect line}"
|
||||
end
|
||||
|
||||
defp generate_buffer([], buffer, [], _state) do
|
||||
buffer
|
||||
defp generate_buffer([], buffer, [], state) do
|
||||
state.engine.handle_body(buffer)
|
||||
end
|
||||
|
||||
defp generate_buffer([], _buffer, _scope, _state) do
|
||||
@@ -62,28 +63,27 @@ defmodule EEx.Compiler do
|
||||
|
||||
defp wrap_expr(current, line, buffer, chars, state) do
|
||||
new_lines = List.duplicate(?\n, line - state.line)
|
||||
key = length(state.dict)
|
||||
placeholder = '__EEX__(' ++ integer_to_list(key) ++ ');'
|
||||
{ current ++ placeholder ++ new_lines ++ chars, state.update_dict(&[{key, buffer}|&1]) }
|
||||
end
|
||||
|
||||
if state.dict == [] and empty?(buffer) do
|
||||
{ current ++ new_lines ++ chars, state }
|
||||
# Look text ahead on expressions
|
||||
|
||||
defp look_ahead_text([{ :text, text }, { :middle_expr, line, _, chars }|t]=list, start, contents) do
|
||||
if only_spaces?(text) do
|
||||
{ contents ++ text ++ chars, line, t }
|
||||
else
|
||||
key = length(state.dict)
|
||||
placeholder = '__EEX__(' ++ integer_to_list(key) ++ ');'
|
||||
{ current ++ placeholder ++ new_lines ++ chars, state.update_dict(&[{key, buffer}|&1]) }
|
||||
{ contents, start, list }
|
||||
end
|
||||
end
|
||||
|
||||
# Check if the syntax node represents an empty string
|
||||
|
||||
defp empty?(bin) when is_binary(bin) do
|
||||
bc(<<c>> inbits bin, not c in [?\s, ?\t, ?\r, ?\n], do: <<c>>) == ""
|
||||
defp look_ahead_text(t, start, contents) do
|
||||
{ contents, start, t }
|
||||
end
|
||||
|
||||
defp empty?({ :<>, _, [left, right] }) do
|
||||
empty?(left) and empty?(right)
|
||||
end
|
||||
|
||||
defp empty?(_) do
|
||||
false
|
||||
defp only_spaces?(chars) do
|
||||
Enum.all?(chars, &(&1 in [?\s, ?\t, ?\r, ?\n]))
|
||||
end
|
||||
|
||||
# Changes placeholder to real expression
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
defmodule EEx.Engine do
|
||||
@moduledoc %S"""
|
||||
@moduledoc ~S"""
|
||||
This is the basic EEx engine that ships with Elixir.
|
||||
An engine needs to implement two functions:
|
||||
An engine needs to implement three functions:
|
||||
|
||||
* `handle_body(quoted)` - receives the final built quoted
|
||||
expression, should do final post-processing and return a
|
||||
quoted expression;
|
||||
|
||||
* `handle_text(buffer, text)` - it receives the buffer,
|
||||
the text and must return a new quoted expression;
|
||||
@@ -10,11 +14,11 @@ defmodule EEx.Engine do
|
||||
the marker, the expr and must return a new quoted expression;
|
||||
|
||||
The marker is what follows exactly after `<%`. For example,
|
||||
`<% foo %>` has an empty marker, but `<%= foo %>` has `'='`
|
||||
`<% foo %>` has an empty marker, but `<%= foo %>` has `"="`
|
||||
as marker. The allowed markers so far are:
|
||||
|
||||
* `''`
|
||||
* `'='`
|
||||
* `""`
|
||||
* `"="`
|
||||
|
||||
Read `handle_expr/3` below for more information about the markers
|
||||
implemented by default by this engine.
|
||||
@@ -22,9 +26,18 @@ defmodule EEx.Engine do
|
||||
|
||||
use Behaviour
|
||||
|
||||
defcallback handle_body(Macro.t) :: Macro.t
|
||||
defcallback handle_text(Macro.t, binary) :: Macro.t
|
||||
defcallback handle_expr(Macro.t, binary, Macro.t) :: Macro.t
|
||||
|
||||
@doc """
|
||||
The default implementation implementation simply returns the
|
||||
given expression.
|
||||
"""
|
||||
def handle_body(quoted) do
|
||||
quoted
|
||||
end
|
||||
|
||||
@doc """
|
||||
The default implementation simply concatenates text to the buffer.
|
||||
"""
|
||||
@@ -54,4 +67,4 @@ defmodule EEx.Engine do
|
||||
tmp
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -2,7 +2,7 @@ defmodule EEx.TransformerEngine do
|
||||
@moduledoc """
|
||||
An abstract engine that is meant to be used and
|
||||
built upon in other modules. This engine implements
|
||||
the `EEx.Engine` behavior and provides a `transform`
|
||||
the `EEx.Engine` behaviour and provides a `transform`
|
||||
overridable directive that allows a developer to
|
||||
customize the expression returned by the engine.
|
||||
|
||||
@@ -13,7 +13,11 @@ defmodule EEx.TransformerEngine do
|
||||
@doc false
|
||||
defmacro __using__(_) do
|
||||
quote do
|
||||
@behavior EEx.Engine
|
||||
@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)
|
||||
@@ -32,14 +36,14 @@ defmodule EEx.TransformerEngine do
|
||||
end
|
||||
|
||||
defp transform(list) when is_list(list) do
|
||||
lc i inlist list, do: transform(i)
|
||||
for i <- list, do: transform(i)
|
||||
end
|
||||
|
||||
defp transform(other) do
|
||||
other
|
||||
end
|
||||
|
||||
defoverridable [transform: 1, handle_expr: 3, handle_text: 2]
|
||||
defoverridable [transform: 1, handle_body: 1, handle_expr: 3, handle_text: 2]
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -59,8 +63,8 @@ defmodule EEx.AssignsEngine do
|
||||
use EEx.AssignsEngine
|
||||
end
|
||||
|
||||
EEx.eval_string("<%= @foo %>", assigns: [foo: 1])
|
||||
#=> 1
|
||||
iex> EEx.eval_string("<%= @foo %>", assigns: [foo: 1])
|
||||
"1"
|
||||
|
||||
In the example above, we can access the value `foo` under
|
||||
the binding `assigns` using `@foo`. This is useful when
|
||||
|
||||
@@ -5,9 +5,10 @@ defmodule EEx.Tokenizer do
|
||||
Tokenizes the given char list or binary.
|
||||
It returns 4 different types of tokens as result:
|
||||
|
||||
* { :text, line, contents }
|
||||
* { :text, contents }
|
||||
* { :expr, line, marker, contents }
|
||||
* { :start_expr, line, marker, contents }
|
||||
* { :middle_expr, line, marker, contents }
|
||||
* { :end_expr, line, marker, contents }
|
||||
|
||||
"""
|
||||
@@ -16,39 +17,39 @@ defmodule EEx.Tokenizer do
|
||||
end
|
||||
|
||||
def tokenize(list, line) do
|
||||
Enum.reverse(tokenize(list, line, line, [], []))
|
||||
Enum.reverse(tokenize(list, line, [], []))
|
||||
end
|
||||
|
||||
defp tokenize('<%%' ++ t, current_line, line, buffer, acc) do
|
||||
defp tokenize('<%%' ++ t, line, buffer, acc) do
|
||||
{ buffer, new_line, rest } = tokenize_expr t, line, [?%, ?<|buffer]
|
||||
tokenize rest, current_line, new_line, [?>, ?%|buffer], acc
|
||||
tokenize rest, new_line, [?>, ?%|buffer], acc
|
||||
end
|
||||
|
||||
defp tokenize('<%#' ++ t, current_line, line, buffer, acc) do
|
||||
defp tokenize('<%#' ++ t, line, buffer, acc) do
|
||||
{ _, new_line, rest } = tokenize_expr t, line, []
|
||||
tokenize rest, current_line, new_line, buffer, acc
|
||||
tokenize rest, new_line, buffer, acc
|
||||
end
|
||||
|
||||
defp tokenize('<%' ++ t, current_line, line, buffer, acc) do
|
||||
defp tokenize('<%' ++ t, line, buffer, acc) do
|
||||
{ marker, t } = retrieve_marker(t)
|
||||
{ expr, new_line, rest } = tokenize_expr t, line, []
|
||||
|
||||
token = token_name(expr)
|
||||
acc = tokenize_text(current_line, buffer, acc)
|
||||
acc = tokenize_text(buffer, acc)
|
||||
final = { token, line, marker, Enum.reverse(expr) }
|
||||
tokenize rest, new_line, new_line, [], [final | acc]
|
||||
tokenize rest, new_line, [], [final | acc]
|
||||
end
|
||||
|
||||
defp tokenize('\n' ++ t, current_line, line, buffer, acc) do
|
||||
tokenize t, current_line, line + 1, [?\n|buffer], acc
|
||||
defp tokenize('\n' ++ t, line, buffer, acc) do
|
||||
tokenize t, line + 1, [?\n|buffer], acc
|
||||
end
|
||||
|
||||
defp tokenize([h|t], current_line, line, buffer, acc) do
|
||||
tokenize t, current_line, line, [h|buffer], acc
|
||||
defp tokenize([h|t], line, buffer, acc) do
|
||||
tokenize t, line, [h|buffer], acc
|
||||
end
|
||||
|
||||
defp tokenize([], current_line, _line, buffer, acc) do
|
||||
tokenize_text(current_line, buffer, acc)
|
||||
defp tokenize([], _line, buffer, acc) do
|
||||
tokenize_text(buffer, acc)
|
||||
end
|
||||
|
||||
# Retrieve marker for <%
|
||||
@@ -140,20 +141,21 @@ defmodule EEx.Tokenizer do
|
||||
end
|
||||
|
||||
defp check_spaces(string, token) do
|
||||
if only_spaces?(string), do: token, else: :expr
|
||||
if Enum.all?(string, &(&1 in [?\s, ?\t])) do
|
||||
token
|
||||
else
|
||||
:expr
|
||||
end
|
||||
end
|
||||
|
||||
defp only_spaces?([h|t]) when h in [?\s, ?\t], do: only_spaces?(t)
|
||||
defp only_spaces?(other), do: other == []
|
||||
|
||||
# Tokenize the buffered text by appending
|
||||
# it to the given accumulator.
|
||||
|
||||
defp tokenize_text(_line, [], acc) do
|
||||
defp tokenize_text([], acc) do
|
||||
acc
|
||||
end
|
||||
|
||||
defp tokenize_text(line, buffer, acc) do
|
||||
[{ :text, line, String.from_char_list!(Enum.reverse(buffer)) } | acc]
|
||||
defp tokenize_text(buffer, acc) do
|
||||
[{ :text, Enum.reverse(buffer) } | acc]
|
||||
end
|
||||
end
|
||||
|
||||
+2
-2
@@ -2,6 +2,6 @@ defmodule EEx.Mixfile do
|
||||
use Mix.Project
|
||||
|
||||
def project do
|
||||
[app: :eex, version: System.version]
|
||||
[app: :eex, version: System.version, build_per_environment: false]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -7,20 +7,19 @@ defmodule EEx.SmartEngineTest do
|
||||
assert_eval "foo bar", "foo bar"
|
||||
end
|
||||
|
||||
test "evaluates with assigns as a ListDict" do
|
||||
test "evaluates with assigns as keywords" do
|
||||
assert_eval "1", "<%= @foo %>", assigns: [foo: 1]
|
||||
end
|
||||
|
||||
test "evaluates with assigns as a HashDict" do
|
||||
assigns = HashDict.new [foo: 1]
|
||||
assert_eval "1", "<%= @foo %>", assigns: assigns
|
||||
test "evaluates with assigns as a map" do
|
||||
assert_eval "1", "<%= @foo %>", assigns: %{foo: 1}
|
||||
end
|
||||
|
||||
test "evaluates with loops" do
|
||||
assert_eval "1\n2\n3\n", "<%= lc x inlist [1, 2, 3] do %><%= x %>\n<% end %>"
|
||||
assert_eval "1\n2\n3\n", "<%= for x <- [1, 2, 3] do %><%= x %>\n<% end %>"
|
||||
end
|
||||
|
||||
defp assert_eval(expected, actual, binding // []) do
|
||||
defp assert_eval(expected, actual, binding \\ []) do
|
||||
result = EEx.eval_string(actual, binding, file: __ENV__.file)
|
||||
assert result == expected
|
||||
end
|
||||
|
||||
@@ -5,23 +5,23 @@ defmodule EEx.TokenizerTest do
|
||||
require EEx.Tokenizer, as: T
|
||||
|
||||
test "simple chars lists" do
|
||||
assert T.tokenize('foo', 1) == [ { :text, 1, "foo" } ]
|
||||
assert T.tokenize('foo', 1) == [ { :text, 'foo' } ]
|
||||
end
|
||||
|
||||
test "simple strings" do
|
||||
assert T.tokenize("foo", 1) == [ { :text, 1, "foo" } ]
|
||||
assert T.tokenize("foo", 1) == [ { :text, 'foo' } ]
|
||||
end
|
||||
|
||||
test "strings with embedded code" do
|
||||
assert T.tokenize('foo <% bar %>', 1) == [ { :text, 1, "foo " }, { :expr, 1, "", ' bar ' } ]
|
||||
assert T.tokenize('foo <% bar %>', 1) == [ { :text, 'foo ' }, { :expr, 1, "", ' bar ' } ]
|
||||
end
|
||||
|
||||
test "strings with embedded equals code" do
|
||||
assert T.tokenize('foo <%= bar %>', 1) == [ { :text, 1, "foo " }, { :expr, 1, "=", ' bar ' } ]
|
||||
assert T.tokenize('foo <%= bar %>', 1) == [ { :text, 'foo ' }, { :expr, 1, "=", ' bar ' } ]
|
||||
end
|
||||
|
||||
test "strings with more than one line" do
|
||||
assert T.tokenize('foo\n<%= bar %>', 1) == [ { :text, 1, "foo\n" }, { :expr, 2, "=", ' bar ' } ]
|
||||
assert T.tokenize('foo\n<%= bar %>', 1) == [ { :text, 'foo\n' }, { :expr, 2, "=", ' bar ' } ]
|
||||
end
|
||||
|
||||
test "strings with more than one line and expression with more than one line" do
|
||||
@@ -33,66 +33,66 @@ baz %>
|
||||
'''
|
||||
|
||||
assert T.tokenize(string, 1) == [
|
||||
{:text, 1, "foo "},
|
||||
{:text, 'foo '},
|
||||
{:expr, 1, "=", ' bar\n\nbaz '},
|
||||
{:text, 3, "\n"},
|
||||
{:text, '\n'},
|
||||
{:expr, 4, "", ' foo '},
|
||||
{:text, 4, "\n"}
|
||||
{:text, '\n'}
|
||||
]
|
||||
end
|
||||
|
||||
test "quotation" do
|
||||
assert T.tokenize('foo <%% true %>', 1) == [
|
||||
{ :text, 1, "foo <% true %>" }
|
||||
{ :text, 'foo <% true %>' }
|
||||
]
|
||||
end
|
||||
|
||||
test "quotation with do/end" do
|
||||
assert T.tokenize('foo <%% true do %>bar<%% end %>', 1) == [
|
||||
{ :text, 1, "foo <% true do %>bar<% end %>" }
|
||||
{ :text, 'foo <% true do %>bar<% end %>' }
|
||||
]
|
||||
end
|
||||
|
||||
test "comments" do
|
||||
assert T.tokenize('foo <%# true %>', 1) == [
|
||||
{ :text, 1, "foo " }
|
||||
{ :text, 'foo ' }
|
||||
]
|
||||
end
|
||||
|
||||
test "comments with do/end" do
|
||||
assert T.tokenize('foo <%# true do %>bar<%# end %>', 1) == [
|
||||
{ :text, 1, "foo bar" }
|
||||
{ :text, 'foo bar' }
|
||||
]
|
||||
end
|
||||
|
||||
test "strings with embedded do end" do
|
||||
assert T.tokenize('foo <% if true do %>bar<% end %>', 1) == [
|
||||
{ :text, 1, "foo " },
|
||||
{ :text, 'foo ' },
|
||||
{ :start_expr, 1, "", ' if true do ' },
|
||||
{ :text, 1, "bar" },
|
||||
{ :text, 'bar' },
|
||||
{ :end_expr, 1, "", ' end ' }
|
||||
]
|
||||
end
|
||||
|
||||
test "strings with embedded -> end" do
|
||||
assert T.tokenize('foo <% cond do %><% false -> %>bar<% true -> %>baz<% end %>', 1) == [
|
||||
{ :text, 1, "foo " },
|
||||
{ :text, 'foo ' },
|
||||
{ :start_expr, 1, "", ' cond do ' },
|
||||
{ :middle_expr, 1, "", ' false -> ' },
|
||||
{ :text, 1, "bar" },
|
||||
{ :text, 'bar' },
|
||||
{ :middle_expr, 1, "", ' true -> ' },
|
||||
{ :text, 1, "baz" },
|
||||
{ :text, 'baz' },
|
||||
{ :end_expr, 1, "", ' end ' }
|
||||
]
|
||||
end
|
||||
|
||||
test "strings with embedded keywords blocks" do
|
||||
assert T.tokenize('foo <% if true do %>bar<% else %>baz<% end %>', 1) == [
|
||||
{ :text, 1, "foo " },
|
||||
{ :text, 'foo ' },
|
||||
{ :start_expr, 1, "", ' if true do ' },
|
||||
{ :text, 1, "bar" },
|
||||
{ :text, 'bar' },
|
||||
{ :middle_expr, 1, "", ' else ' },
|
||||
{ :text, 1, "baz" },
|
||||
{ :text, 'baz' },
|
||||
{ :end_expr, 1, "", ' end ' }
|
||||
]
|
||||
end
|
||||
|
||||
@@ -29,7 +29,7 @@ defmodule EExText.Compiled do
|
||||
try do
|
||||
:erlang.error "failed"
|
||||
catch
|
||||
:error, _, stack -> stack
|
||||
:error, _ -> System.stacktrace
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -45,6 +45,9 @@ end
|
||||
defmodule EExTest do
|
||||
use ExUnit.Case, async: true
|
||||
|
||||
doctest EEx
|
||||
doctest EEx.AssignsEngine
|
||||
|
||||
test "evaluates simple string" do
|
||||
assert_eval "foo bar", "foo bar"
|
||||
end
|
||||
@@ -344,6 +347,26 @@ foo
|
||||
}
|
||||
end
|
||||
|
||||
defmodule TestEngine do
|
||||
@behaviour EEx.Engine
|
||||
|
||||
def handle_body(body) do
|
||||
{ :wrapped, body }
|
||||
end
|
||||
|
||||
def handle_text(buffer, text) do
|
||||
EEx.Engine.handle_text(buffer, text)
|
||||
end
|
||||
|
||||
def handle_expr(buffer, mark, expr) do
|
||||
EEx.Engine.handle_expr(buffer, mark, expr)
|
||||
end
|
||||
end
|
||||
|
||||
test "calls handle_body" do
|
||||
assert { :wrapped, "foo" } = EEx.eval_string("foo", [], engine: TestEngine)
|
||||
end
|
||||
|
||||
defp assert_eval(expected, actual) do
|
||||
result = EEx.eval_string(actual, [], file: __ENV__.file, engine: EEx.Engine)
|
||||
assert result == expected
|
||||
|
||||
@@ -10,10 +10,11 @@
|
||||
|
||||
-record(elixir_scope, {
|
||||
context=nil, %% can be match, guards or nil
|
||||
extra=nil, %% extra information about the context, like fn_match and do_match
|
||||
extra=nil, %% extra information about the context, like fn_match and map_key
|
||||
noname=false, %% when true, don't add new names (used by try)
|
||||
super=false, %% when true, it means super was invoked
|
||||
caller=false, %% when true, it means caller was invoked
|
||||
return=true, %% when true, the return value is used
|
||||
module=nil, %% the current module
|
||||
function=nil, %% the current function
|
||||
vars=[], %% a dict of defined variables and their alias
|
||||
@@ -80,7 +81,8 @@
|
||||
-define(is_atom(S), ?is_identifier(S) orelse (S == $@)).
|
||||
|
||||
-define(is_identifier(S), ?is_digit(S) orelse ?is_upcase(S) orelse ?is_downcase(S) orelse (S == $_)).
|
||||
-define(is_terminator(S), (S == $?) orelse (S == $!) orelse (S == $:)).
|
||||
-define(is_sigil(S), (S == $/) orelse (S == $<) orelse (S == $") orelse (S == $') orelse
|
||||
(S == $[) orelse (S == $() orelse (S == ${) orelse (S == $|)).
|
||||
|
||||
%% Quotes
|
||||
-define(is_quote(S), S == $" orelse S == $').
|
||||
|
||||
+25
-20
@@ -7,6 +7,22 @@ defprotocol Access do
|
||||
is translated to `access foo, bar` which, by default,
|
||||
invokes the `Access.access` protocol.
|
||||
|
||||
This protocol is implemented by default for Lists, Maps
|
||||
and dictionary like types:
|
||||
|
||||
iex> keywords = [a: 1, b: 2]
|
||||
iex> keywords[:a]
|
||||
1
|
||||
|
||||
iex> map = %{ a: 1, b: 2 }
|
||||
iex> map[:a]
|
||||
1
|
||||
|
||||
iex> star_ratings = %{ 1.0 => "★", 1.5 => "★☆", 2.0 => "★★" }
|
||||
iex> star_ratings[1.5]
|
||||
"★☆"
|
||||
|
||||
The key access must be implemented using the `===` operator.
|
||||
This protocol is limited and is implemented only for the
|
||||
following built-in types: keywords, records and functions.
|
||||
"""
|
||||
@@ -18,33 +34,22 @@ defprotocol Access do
|
||||
end
|
||||
|
||||
defimpl Access, for: List do
|
||||
@doc """
|
||||
Access the given key in a tuple list.
|
||||
The key is found via the `===` operator.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> keywords = [a: 1, b: 2]
|
||||
...> keywords[:a]
|
||||
1
|
||||
|
||||
iex> star_ratings = [{1.0, "★"}, {1.5, "★☆"}, {2.0, "★★"}]
|
||||
...> star_ratings[1.5]
|
||||
"★☆"
|
||||
|
||||
"""
|
||||
def access(dict, key)
|
||||
def access([{ key, value }|_], key), do: value
|
||||
def access([{ _, _ }|t], key), do: access(t, key)
|
||||
def access([], _key), do: nil
|
||||
end
|
||||
|
||||
defimpl Access, for: Map do
|
||||
def access(map, key) do
|
||||
case :maps.find(key, map) do
|
||||
{ :ok, value } -> value
|
||||
:error -> nil
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defimpl Access, for: Atom do
|
||||
@doc """
|
||||
The access protocol can only be accessed by atoms
|
||||
at compilation time. If we reach this, we should raise
|
||||
an exception.
|
||||
"""
|
||||
def access(nil, _) do
|
||||
nil
|
||||
end
|
||||
|
||||
@@ -48,51 +48,10 @@ defmodule Application.Behaviour do
|
||||
|
||||
"""
|
||||
|
||||
# Starts the given application and all of its dependencies that
|
||||
# have not been started yet recursively.
|
||||
#
|
||||
# ## Supported types
|
||||
#
|
||||
# When starting an application, a type can be given:
|
||||
#
|
||||
# * `:permanent` - If a permanent application terminates, all other
|
||||
# applications and the runtime system are also terminated;
|
||||
# * `:transient` - If a transient application terminates with reason
|
||||
# `:normal`, this is reported but no other applications are terminated.
|
||||
# If a transient application terminates abnormally, all other
|
||||
# applications and the runtime system are also terminated;
|
||||
# * `:temporary` - If a temporary application terminates, this is reported
|
||||
# but no other applications are terminated.
|
||||
#
|
||||
# The type only applies to the application being started. Its dependencies
|
||||
# are all started with default type (which is :temporary).
|
||||
#
|
||||
# Note that transient mode is of little practical use, since when a
|
||||
# supervision tree terminates, the reason is set to shutdown, not normal.
|
||||
#
|
||||
# ## Examples
|
||||
#
|
||||
# Application.Behaviour.start(:my_app)
|
||||
#
|
||||
@doc false
|
||||
def start(app, type // :temporary) do
|
||||
case :application.start(app, type) do
|
||||
{ :error, { :not_started, dep } } ->
|
||||
case start(dep) do
|
||||
:ok -> start(app, type)
|
||||
other -> other
|
||||
end
|
||||
{ :error, { :already_started, _ } } ->
|
||||
:ok
|
||||
other ->
|
||||
other
|
||||
end
|
||||
end
|
||||
|
||||
@doc false
|
||||
defmacro __using__(_) do
|
||||
quote location: :keep do
|
||||
@behavior :application
|
||||
@behaviour :application
|
||||
|
||||
@doc false
|
||||
def stop(_state) do
|
||||
|
||||
@@ -0,0 +1,464 @@
|
||||
defmodule Base do
|
||||
import Bitwise
|
||||
|
||||
@moduledoc """
|
||||
This module provides data encoding and decoding functions
|
||||
according to [RFC 4648](http://tools.ietf.org/html/rfc4648).
|
||||
|
||||
This document defines the commonly used base 64, base 32, and base
|
||||
16 encoding schemes.
|
||||
"""
|
||||
|
||||
b16_alphabet = Enum.with_index '0123456789ABCDEF'
|
||||
b64_alphabet = Enum.with_index 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'
|
||||
b64url_alphabet = Enum.with_index 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_'
|
||||
b32_alphabet = Enum.with_index 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567'
|
||||
b32hex_alphabet = Enum.with_index '0123456789ABCDEFGHIJKLMNOPQRSTUV'
|
||||
|
||||
Enum.each [ { :enc16, :dec16, b16_alphabet },
|
||||
{ :enc64, :dec64, b64_alphabet },
|
||||
{ :enc32, :dec32, b32_alphabet },
|
||||
{ :enc64url, :dec64url, b64url_alphabet },
|
||||
{ :enc32hex, :dec32hex, b32hex_alphabet } ], fn({enc, dec, alphabet}) ->
|
||||
for {encoding, value} <- alphabet do
|
||||
defp unquote(enc)(unquote(value)), do: unquote(encoding)
|
||||
defp unquote(dec)(unquote(encoding)), do: unquote(value)
|
||||
end
|
||||
defp unquote(dec)(c) do
|
||||
raise ArgumentError, message: "non-alphabet digit found: #{<<c>>}"
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Encodes a binary string into a base 16 encoded string.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Base.encode16("foobar")
|
||||
"666F6F626172"
|
||||
|
||||
"""
|
||||
@spec encode16(binary) :: binary
|
||||
def encode16(data) when is_binary(data) do
|
||||
do_encode16(data, &enc16/1)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Decodes a base 16 encoded string into a binary string.
|
||||
|
||||
The following alphabet is used both for encoding and decoding:
|
||||
|
||||
| Value | Encoding | Value | Encoding | Value | Encoding | Value | Encoding |
|
||||
|------:|---------:|------:|---------:|------:|---------:|------:|---------:|
|
||||
| 0| 0| 4| 4| 8| 8| 12| C|
|
||||
| 1| 1| 5| 5| 9| 9| 13| D|
|
||||
| 2| 2| 6| 6| 10| A| 14| E|
|
||||
| 3| 3| 7| 7| 11| B| 15| F|
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Base.decode16("666F6F626172")
|
||||
{:ok, "foobar"}
|
||||
|
||||
"""
|
||||
@spec decode16(binary) :: { :ok, binary } | :error
|
||||
def decode16(string) when is_binary(string) do
|
||||
{ :ok, decode16!(string) }
|
||||
rescue
|
||||
ArgumentError -> :error
|
||||
end
|
||||
|
||||
@doc """
|
||||
Decodes a base 16 encoded string into a binary string.
|
||||
|
||||
An `ArgumentError` exception is raised if the padding is incorrect or
|
||||
a non-alphabet character is present in the string.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Base.decode16!("666F6F626172")
|
||||
"foobar"
|
||||
|
||||
"""
|
||||
@spec decode16!(binary) :: binary
|
||||
def decode16!(string) when is_binary(string) do
|
||||
do_decode16(string, &dec16/1)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Encodes a binary string into a base 64 encoded string.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Base.encode64("foobar")
|
||||
"Zm9vYmFy"
|
||||
|
||||
"""
|
||||
@spec encode64(binary) :: binary
|
||||
def encode64(data) when is_binary(data) do
|
||||
do_encode64(data, &enc64/1)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Decodes a base 64 encoded string into a binary string.
|
||||
|
||||
The following alphabet is used both for encoding and decoding:
|
||||
|
||||
| Value | Encoding | Value | Encoding | Value | Encoding | Value | Encoding |
|
||||
|------:|---------:|------:|---------:|------:|---------:|------:|---------:|
|
||||
| 0| A| 17| R| 34| i| 51| z|
|
||||
| 1| B| 18| S| 35| j| 52| 0|
|
||||
| 2| C| 19| T| 36| k| 53| 1|
|
||||
| 3| D| 20| U| 37| l| 54| 2|
|
||||
| 4| E| 21| V| 38| m| 55| 3|
|
||||
| 5| F| 22| W| 39| n| 56| 4|
|
||||
| 6| G| 23| X| 40| o| 57| 5|
|
||||
| 7| H| 24| Y| 41| p| 58| 6|
|
||||
| 8| I| 25| Z| 42| q| 59| 7|
|
||||
| 9| J| 26| a| 43| r| 60| 8|
|
||||
| 10| K| 27| b| 44| s| 61| 9|
|
||||
| 11| L| 28| c| 45| t| 62| +|
|
||||
| 12| M| 29| d| 46| u| 63| /|
|
||||
| 13| N| 30| e| 47| v| | |
|
||||
| 14| O| 31| f| 48| w| (pad)| =|
|
||||
| 15| P| 32| g| 49| x| | |
|
||||
| 16| Q| 33| h| 50| y| | |
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Base.decode64("Zm9vYmFy")
|
||||
{:ok, "foobar"}
|
||||
|
||||
"""
|
||||
@spec decode64(binary) :: { :ok, binary } | :error
|
||||
def decode64(string) when is_binary(string) do
|
||||
{ :ok, do_decode64(string, &dec64/1) }
|
||||
rescue
|
||||
ArgumentError -> :error
|
||||
end
|
||||
|
||||
@doc """
|
||||
Decodes a base 64 encoded string into a binary string.
|
||||
|
||||
The following alphabet is used both for encoding and decoding:
|
||||
|
||||
An `ArgumentError` exception is raised if the padding is incorrect or
|
||||
a non-alphabet character is present in the string.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Base.decode64!("Zm9vYmFy")
|
||||
"foobar"
|
||||
|
||||
"""
|
||||
@spec decode64!(binary) :: binary
|
||||
def decode64!(string) when is_binary(string) do
|
||||
do_decode64(string, &dec64/1)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Encodes a binary string into a base 64 encoded string with URL and filename
|
||||
safe alphabet.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Base.url_encode64(<<255,127,254,252>>)
|
||||
"_3_-_A=="
|
||||
|
||||
"""
|
||||
@spec url_encode64(binary) :: binary
|
||||
def url_encode64(data) when is_binary(data) do
|
||||
do_encode64(data, &enc64url/1)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Decodes a base 64 encoded string with URL and filename safe alphabet
|
||||
into a binary string.
|
||||
|
||||
The following alphabet is used both for encoding and decoding:
|
||||
|
||||
| Value | Encoding | Value | Encoding | Value | Encoding | Value | Encoding |
|
||||
|------:|---------:|------:|---------:|------:|---------:|------:|---------:|
|
||||
| 0| A| 17| R| 34| i| 51| z|
|
||||
| 1| B| 18| S| 35| j| 52| 0|
|
||||
| 2| C| 19| T| 36| k| 53| 1|
|
||||
| 3| D| 20| U| 37| l| 54| 2|
|
||||
| 4| E| 21| V| 38| m| 55| 3|
|
||||
| 5| F| 22| W| 39| n| 56| 4|
|
||||
| 6| G| 23| X| 40| o| 57| 5|
|
||||
| 7| H| 24| Y| 41| p| 58| 6|
|
||||
| 8| I| 25| Z| 42| q| 59| 7|
|
||||
| 9| J| 26| a| 43| r| 60| 8|
|
||||
| 10| K| 27| b| 44| s| 61| 9|
|
||||
| 11| L| 28| c| 45| t| 62| -|
|
||||
| 12| M| 29| d| 46| u| 63| _|
|
||||
| 13| N| 30| e| 47| v| | |
|
||||
| 14| O| 31| f| 48| w| (pad)| =|
|
||||
| 15| P| 32| g| 49| x| | |
|
||||
| 16| Q| 33| h| 50| y| | |
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Base.url_decode64("_3_-_A==")
|
||||
{:ok, <<255,127,254,252>>}
|
||||
|
||||
"""
|
||||
@spec url_decode64(binary) :: { :ok, binary } | :error
|
||||
def url_decode64(string) when is_binary(string) do
|
||||
{ :ok, do_decode64(string, &dec64url/1) }
|
||||
rescue
|
||||
ArgumentError -> :error
|
||||
end
|
||||
|
||||
@doc """
|
||||
Decodes a base 64 encoded string with URL and filename safe alphabet
|
||||
into a binary string.
|
||||
|
||||
An `ArgumentError` exception is raised if the padding is incorrect or
|
||||
a non-alphabet character is present in the string.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Base.url_decode64!("_3_-_A==")
|
||||
<<255,127,254,252>>
|
||||
|
||||
"""
|
||||
@spec url_decode64!(binary) :: binary
|
||||
def url_decode64!(string) when is_binary(string) do
|
||||
do_decode64(string, &dec64url/1)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Encodes a binary string into a base 32 encoded string.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Base.encode32("foobar")
|
||||
"MZXW6YTBOI======"
|
||||
|
||||
"""
|
||||
@spec encode32(binary) :: binary
|
||||
def encode32(data) when is_binary(data) do
|
||||
do_encode32(data, &enc32/1)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Decodes a base 32 encoded string into a binary string.
|
||||
|
||||
The following alphabet is used both for encoding and decoding:
|
||||
|
||||
| Value | Encoding | Value | Encoding | Value | Encoding | Value | Encoding |
|
||||
|------:|---------:|------:|---------:|------:|---------:|------:|---------:|
|
||||
| 0| A| 9| J| 18| S| 27| 3|
|
||||
| 1| B| 10| K| 19| T| 28| 4|
|
||||
| 2| C| 11| L| 20| U| 29| 5|
|
||||
| 3| D| 12| M| 21| V| 30| 6|
|
||||
| 4| E| 13| N| 22| W| 31| 7|
|
||||
| 5| F| 14| O| 23| X| | |
|
||||
| 6| G| 15| P| 24| Y| (pad)| =|
|
||||
| 7| H| 16| Q| 25| Z| | |
|
||||
| 8| I| 17| R| 26| 2| | |
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Base.decode32("MZXW6YTBOI======")
|
||||
{:ok, "foobar"}
|
||||
|
||||
"""
|
||||
@spec decode32(binary) :: { :ok, binary } | :error
|
||||
def decode32(string) do
|
||||
{ :ok, do_decode32(string, &dec32/1) }
|
||||
rescue
|
||||
ArgumentError -> :error
|
||||
end
|
||||
|
||||
@doc """
|
||||
Decodes a base 32 encoded string into a binary string.
|
||||
|
||||
An `ArgumentError` exception is raised if the padding is incorrect or
|
||||
a non-alphabet character is present in the string.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Base.decode32!("MZXW6YTBOI======")
|
||||
"foobar"
|
||||
|
||||
"""
|
||||
@spec decode32!(binary) :: binary
|
||||
def decode32!(string) do
|
||||
do_decode32(string, &dec32/1)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Encodes a binary string into a base 32 encoded string with an
|
||||
extended hexadecimal alphabet.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Base.hex_encode32("foobar")
|
||||
"CPNMUOJ1E8======"
|
||||
|
||||
"""
|
||||
@spec hex_encode32(binary) :: binary
|
||||
def hex_encode32(data) when is_binary(data) do
|
||||
do_encode32(data, &enc32hex/1)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Decodes a base 32 encoded string with extended hexadecimal alphabet
|
||||
into a binary string.
|
||||
|
||||
The following alphabet is used both for encoding and decoding:
|
||||
|
||||
| Value | Encoding | Value | Encoding | Value | Encoding | Value | Encoding |
|
||||
|------:|---------:|------:|---------:|------:|---------:|------:|---------:|
|
||||
| 0| 0| 9| 9| 18| I| 27| R|
|
||||
| 1| 1| 10| A| 19| J| 28| S|
|
||||
| 2| 2| 11| B| 20| K| 29| T|
|
||||
| 3| 3| 12| C| 21| L| 30| U|
|
||||
| 4| 4| 13| D| 22| M| 31| V|
|
||||
| 5| 5| 14| E| 23| N| | |
|
||||
| 6| 6| 15| F| 24| O| (pad)| =|
|
||||
| 7| 7| 16| G| 25| P| | |
|
||||
| 8| 8| 17| H| 26| Q| | |
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Base.hex_decode32("CPNMUOJ1E8======")
|
||||
{:ok, "foobar"}
|
||||
|
||||
"""
|
||||
@spec hex_decode32(binary) :: { :ok, binary } | :error
|
||||
def hex_decode32(string) when is_binary(string) do
|
||||
{ :ok, do_decode32(string, &dec32hex/1) }
|
||||
rescue
|
||||
ArgumentError -> :error
|
||||
end
|
||||
|
||||
@doc """
|
||||
Decodes a base 32 encoded string with extended hexadecimal alphabet
|
||||
into a binary string.
|
||||
|
||||
An `ArgumentError` exception is raised if the padding is incorrect or
|
||||
a non-alphabet character is present in the string.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Base.hex_decode32!("CPNMUOJ1E8======")
|
||||
"foobar"
|
||||
|
||||
"""
|
||||
@spec hex_decode32!(binary) :: binary
|
||||
def hex_decode32!(string) when is_binary(string) do
|
||||
do_decode32(string, &dec32hex/1)
|
||||
end
|
||||
|
||||
defp do_encode16(<<>>, _), do: <<>>
|
||||
defp do_encode16(data, enc) do
|
||||
for <<c::4 <- data>>, into: <<>>, do: <<enc.(c)::8>>
|
||||
end
|
||||
|
||||
defp do_decode16(<<>>, _), do: <<>>
|
||||
defp do_decode16(string, dec) when rem(byte_size(string), 2) == 0 do
|
||||
for <<c1::8, c2::8 <- string>>, into: <<>> do
|
||||
<<dec.(c1)::4, dec.(c2)::4>>
|
||||
end
|
||||
end
|
||||
defp do_decode16(_, _) do
|
||||
raise ArgumentError, message: "odd-length string"
|
||||
end
|
||||
|
||||
defp do_encode64(<<>>, _), do: <<>>
|
||||
defp do_encode64(data, enc) do
|
||||
split = 3 * div(byte_size(data), 3)
|
||||
<<main::[size(split), binary], rest::binary>> = data
|
||||
main = for <<c::6 <- main>>, into: <<>>, do: <<enc.(c)::8>>
|
||||
case rest do
|
||||
<<c1::6, c2::6, c3::4>> ->
|
||||
<<main::binary, enc.(c1)::8, enc.(c2)::8, enc.(bsl(c3, 2))::8, ?=>>
|
||||
<<c1::6, c2::2>> ->
|
||||
<<main::binary, enc.(c1)::8, enc.(bsl(c2, 4))::8, ?=, ?=>>
|
||||
<<>> ->
|
||||
main
|
||||
end
|
||||
end
|
||||
|
||||
defp do_decode64(<<>>, _), do: <<>>
|
||||
defp do_decode64(string, dec) when rem(byte_size(string), 4) == 0 do
|
||||
split = byte_size(string) - 4
|
||||
<<main::[size(split), binary], rest::binary>> = string
|
||||
main = for <<c::8 <- main>>, into: <<>>, do: <<dec.(c)::6>>
|
||||
case rest do
|
||||
<<c1::8, c2::8, ?=, ?=>> ->
|
||||
<<main::binary, dec.(c1)::6, bsr(dec.(c2), 4)::2>>
|
||||
<<c1::8, c2::8, c3::8, ?=>> ->
|
||||
<<main::binary, dec.(c1)::6, dec.(c2)::6, bsr(dec.(c3), 2)::4>>
|
||||
<<c1::8, c2::8, c3::8, c4::8>> ->
|
||||
<<main::binary, dec.(c1)::6, dec.(c2)::6, dec.(c3)::6, dec.(c4)::6>>
|
||||
<<>> ->
|
||||
main
|
||||
end
|
||||
end
|
||||
defp do_decode64(_, _) do
|
||||
raise ArgumentError, message: "incorrect padding"
|
||||
end
|
||||
|
||||
defp do_encode32(<<>>, _), do: <<>>
|
||||
defp do_encode32(data, enc) do
|
||||
split = 5 * div(byte_size(data), 5)
|
||||
<<main::[size(split), binary], rest::binary>> = data
|
||||
main = for <<c::5 <- main>>, into: <<>>, do: <<enc.(c)::8>>
|
||||
case rest do
|
||||
<<c1::5, c2::5, c3::5, c4::5, c5::5, c6::5, c7::2>> ->
|
||||
<<main::binary,
|
||||
enc.(c1)::8, enc.(c2)::8, enc.(c3)::8, enc.(c4)::8,
|
||||
enc.(c5)::8, enc.(c6)::8, enc.(bsl(c7, 3))::8, ?=>>
|
||||
<<c1::5, c2::5, c3::5, c4::5, c5::4>> ->
|
||||
<<main::binary,
|
||||
enc.(c1)::8, enc.(c2)::8, enc.(c3)::8, enc.(c4)::8,
|
||||
enc.(bsl(c5, 1))::8, ?=, ?=, ?=>>
|
||||
<<c1::5, c2::5, c3::5, c4::1>> ->
|
||||
<<main::binary,
|
||||
enc.(c1)::8, enc.(c2)::8, enc.(c3)::8, enc.(bsl(c4, 4))::8,
|
||||
?=, ?=, ?=, ?=>>
|
||||
<<c1::5, c2::3>> ->
|
||||
<<main::binary,
|
||||
enc.(c1)::8, enc.(bsl(c2, 2))::8, ?=, ?=,
|
||||
?=, ?=, ?=, ?=>>
|
||||
<<>> ->
|
||||
main
|
||||
end
|
||||
end
|
||||
|
||||
defp do_decode32(<<>>, _), do: <<>>
|
||||
defp do_decode32(string, dec) when rem(byte_size(string), 8) == 0 do
|
||||
split = byte_size(string) - 8
|
||||
<<main::[size(split), binary], rest::binary>> = string
|
||||
main = for <<c::8 <- main>>, into: <<>>, do: <<dec.(c)::5>>
|
||||
case rest do
|
||||
<<c1::8, c2::8, ?=, ?=, ?=, ?=, ?=, ?=>> ->
|
||||
<<main::binary, dec.(c1)::5, bsr(dec.(c2), 2)::3>>
|
||||
<<c1::8, c2::8, c3::8, c4::8, ?=, ?=, ?=, ?=>> ->
|
||||
<<main::binary,
|
||||
dec.(c1)::5, dec.(c2)::5, dec.(c3)::5, bsr(dec.(c4), 4)::1>>
|
||||
<<c1::8, c2::8, c3::8, c4::8, c5::8, ?=, ?=, ?=>> ->
|
||||
<<main::binary,
|
||||
dec.(c1)::5, dec.(c2)::5, dec.(c3)::5, dec.(c4)::5,
|
||||
bsr(dec.(c5), 1)::4>>
|
||||
<<c1::8, c2::8, c3::8, c4::8, c5::8, c6::8, c7::8, ?=>> ->
|
||||
<<main::binary,
|
||||
dec.(c1)::5, dec.(c2)::5, dec.(c3)::5, dec.(c4)::5,
|
||||
dec.(c5)::5, dec.(c6)::5, bsr(dec.(c7), 3)::2>>
|
||||
<<c1::8, c2::8, c3::8, c4::8, c5::8, c6::8, c7::8, c8::8>> ->
|
||||
<<main::binary,
|
||||
dec.(c1)::5, dec.(c2)::5, dec.(c3)::5, dec.(c4)::5,
|
||||
dec.(c5)::5, dec.(c6)::5, dec.(c7)::5, dec.(c8)::5>>
|
||||
<<>> ->
|
||||
main
|
||||
end
|
||||
end
|
||||
defp do_decode32(_, _) do
|
||||
raise ArgumentError, message: "incorrect padding"
|
||||
end
|
||||
|
||||
end
|
||||
+25
-82
@@ -1,6 +1,6 @@
|
||||
defmodule Behaviour do
|
||||
@moduledoc """
|
||||
Utilities for defining behaviour intefaces.
|
||||
Utilities for defining behaviour interfaces.
|
||||
|
||||
Behaviours can be referenced by other modules
|
||||
to ensure they implement required callbacks.
|
||||
@@ -46,60 +46,53 @@ defmodule Behaviour do
|
||||
@doc """
|
||||
Define a function callback according to the given type specification.
|
||||
"""
|
||||
defmacro defcallback({ :::, _, [fun, return_and_guard] }) do
|
||||
{ return, guard } = split_return_and_guard(return_and_guard)
|
||||
do_defcallback(fun, return, guard, __CALLER__)
|
||||
end
|
||||
|
||||
defmacro defcallback(fun) do
|
||||
do_defcallback(fun, quote(do: term), [], __CALLER__)
|
||||
defmacro defcallback(spec) do
|
||||
do_defcallback(split_spec(spec, quote(do: term)), __CALLER__)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Define a macro callback according to the given type specification.
|
||||
"""
|
||||
defmacro defmacrocallback({ :::, _, [fun, return_and_guard] }) do
|
||||
{ return, guard } = split_return_and_guard(return_and_guard)
|
||||
do_defmacrocallback(fun, return, guard, __CALLER__)
|
||||
defmacro defmacrocallback(spec) do
|
||||
do_defmacrocallback(split_spec(spec, quote(do: Macro.t)), __CALLER__)
|
||||
end
|
||||
|
||||
defmacro defmacrocallback(fun) do
|
||||
do_defmacrocallback(fun, quote(do: Macro.t), [], __CALLER__)
|
||||
defp split_spec({ :when, _, [{ :::, _, [spec, return] }, guard] }, _default) do
|
||||
{ spec, return, guard }
|
||||
end
|
||||
|
||||
defp split_return_and_guard({ :when, _, [return, guard] }) do
|
||||
{ return, guard }
|
||||
defp split_spec({ :when, _, [spec, guard] }, default) do
|
||||
{ spec, default, guard }
|
||||
end
|
||||
|
||||
defp split_return_and_guard({ :|, meta, [left, right] }) do
|
||||
{ return, guard } = split_return_and_guard(right)
|
||||
{ { :|, meta, [left, return] }, guard }
|
||||
defp split_spec({ :::, _, [spec, return] }, _default) do
|
||||
{ spec, return, [] }
|
||||
end
|
||||
|
||||
defp split_return_and_guard(other) do
|
||||
{ other, [] }
|
||||
defp split_spec(spec, default) do
|
||||
{ spec, default, [] }
|
||||
end
|
||||
|
||||
defp do_defcallback(fun, return, guards, caller) do
|
||||
case Macro.decompose_call(fun) do
|
||||
defp do_defcallback({ spec, return, guards }, caller) do
|
||||
case Macro.decompose_call(spec) do
|
||||
{ name, args } ->
|
||||
do_callback(:def, name, args, name, length(args), args, return, guards, caller)
|
||||
_ ->
|
||||
raise ArgumentError, message: "invalid syntax in defcallback #{Macro.to_string(fun)}"
|
||||
raise ArgumentError, message: "invalid syntax in defcallback #{Macro.to_string(spec)}"
|
||||
end
|
||||
end
|
||||
|
||||
defp do_defmacrocallback(fun, return, guards, caller) do
|
||||
case Macro.decompose_call(fun) do
|
||||
defp do_defmacrocallback({ spec, return, guards }, caller) do
|
||||
case Macro.decompose_call(spec) do
|
||||
{ name, args } ->
|
||||
do_callback(:defmacro, :"MACRO-#{name}", [quote(do: env :: Macro.Env.t)|args],
|
||||
name, length(args), args, return, guards, caller)
|
||||
_ ->
|
||||
raise ArgumentError, message: "invalid syntax in defmacrocallback #{Macro.to_string(fun)}"
|
||||
raise ArgumentError, message: "invalid syntax in defmacrocallback #{Macro.to_string(spec)}"
|
||||
end
|
||||
end
|
||||
|
||||
defp do_callback(kind, name, args, docs_name, docs_arity, docs_args, return, guards, caller) do
|
||||
defp do_callback(kind, name, args, docs_name, docs_arity, _docs_args, return, guards, caller) do
|
||||
Enum.each args, fn
|
||||
{ :::, _, [left, right] } ->
|
||||
ensure_not_default(left)
|
||||
@@ -111,75 +104,25 @@ defmodule Behaviour do
|
||||
end
|
||||
|
||||
quote do
|
||||
docs_args = unquote(Macro.escape(docs_args))
|
||||
@callback unquote(name)(unquote_splicing(args)) :: unquote(return) when unquote(guards)
|
||||
Behaviour.store_docs(__MODULE__, unquote(caller.line), unquote(kind),
|
||||
unquote(docs_name), docs_args, unquote(docs_arity))
|
||||
unquote(docs_name), unquote(docs_arity))
|
||||
end
|
||||
end
|
||||
|
||||
defp ensure_not_default({ ://, _, [_, _] }) do
|
||||
raise ArgumentError, message: "default arguments // not supported in defcallback/defmacrocallback"
|
||||
defp ensure_not_default({ :\\, _, [_, _] }) do
|
||||
raise ArgumentError, message: "default arguments \\\\ not supported in defcallback/defmacrocallback"
|
||||
end
|
||||
|
||||
defp ensure_not_default(_), do: :ok
|
||||
|
||||
@doc false
|
||||
def store_docs(module, line, kind, name, args, arity) do
|
||||
def store_docs(module, line, kind, name, arity) do
|
||||
doc = Module.get_attribute module, :doc
|
||||
Module.delete_attribute module, :doc
|
||||
|
||||
signature = Enum.map(Stream.with_index(args), fn { arg, ix } ->
|
||||
{ simplify_signature(arg, ix + 1), [line: line], nil }
|
||||
end)
|
||||
|
||||
Module.put_attribute module, :behaviour_docs, { { name, arity }, line, kind, signature, doc }
|
||||
Module.put_attribute module, :behaviour_docs, { { name, arity }, line, kind, doc }
|
||||
end
|
||||
|
||||
defp simplify_signature({ :::, _, [var, _] }, i) do
|
||||
simplify_signature(var, i)
|
||||
end
|
||||
|
||||
defp simplify_signature({ :|, _, [first, _] }, i) do
|
||||
simplify_signature(first, i)
|
||||
end
|
||||
|
||||
defp simplify_signature({ :., _, [_, name] }, _i) do
|
||||
name
|
||||
end
|
||||
|
||||
defp simplify_signature({ :->, _, list }, i) when is_list(list) do
|
||||
:"fun#{i}"
|
||||
end
|
||||
|
||||
defp simplify_signature({ :.., _, list }, i) when is_list(list) do
|
||||
:"range#{i}"
|
||||
end
|
||||
|
||||
defp simplify_signature({ :<<>>, _, list }, i) when is_list(list) do
|
||||
:"bitstring#{i}"
|
||||
end
|
||||
|
||||
defp simplify_signature({ :{}, _, list }, i) when is_list(list) do
|
||||
:"tuple#{i}"
|
||||
end
|
||||
|
||||
defp simplify_signature({ name, _, args }, _i)
|
||||
when is_atom(name) and (is_atom(args) or is_list(args)) do
|
||||
name
|
||||
end
|
||||
|
||||
defp simplify_signature({ ast, _, list }, i) when is_list(list) do
|
||||
simplify_signature(ast, i)
|
||||
end
|
||||
|
||||
defp simplify_signature(other, i) when is_integer(other), do: :"int#{i}"
|
||||
defp simplify_signature(other, i) when is_boolean(other), do: :"bool#{i}"
|
||||
defp simplify_signature(other, i) when is_atom(other), do: :"atom#{i}"
|
||||
defp simplify_signature(other, i) when is_list(other), do: :"list#{i}"
|
||||
defp simplify_signature(other, i) when is_tuple(other), do: :"tuple#{i}"
|
||||
defp simplify_signature(_, i), do: :"arg#{i}"
|
||||
|
||||
@doc false
|
||||
defmacro __using__(_) do
|
||||
quote do
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
defmodule Bitwise do
|
||||
@moduledoc """
|
||||
This module provide macros and operators for bitwise operators.
|
||||
This module provides macros and operators for bitwise operators.
|
||||
These macros can be used in guards.
|
||||
|
||||
The easiest way to use is to simply import them into
|
||||
@@ -15,7 +15,7 @@ defmodule Bitwise do
|
||||
You can select to include only or skip operators by passing options:
|
||||
|
||||
iex> use Bitwise, only_operators: true
|
||||
...> 1 &&& 1
|
||||
iex> 1 &&& 1
|
||||
1
|
||||
|
||||
"""
|
||||
|
||||
+87
-89
@@ -10,7 +10,7 @@ defmodule Code do
|
||||
Utilities for managing code compilation, code evaluation and code loading.
|
||||
|
||||
This module complements [Erlang's code module](http://www.erlang.org/doc/man/code.html)
|
||||
to add behavior which is specific to Elixir.
|
||||
to add behaviour which is specific to Elixir.
|
||||
"""
|
||||
|
||||
@doc """
|
||||
@@ -59,11 +59,11 @@ defmodule Code do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Evaluate the contents given by `string`.
|
||||
Evaluate the contents given by `string`.
|
||||
|
||||
The `binding` argument is a keyword list of variable bindings.
|
||||
The `opts` argument is a keyword list of environment options.
|
||||
|
||||
The `binding` argument is a keyword list of variable bindings.
|
||||
The `opts` argument is a keyword list of environment options.
|
||||
|
||||
Those options can be:
|
||||
|
||||
* `:file` - the file to be considered in the evaluation
|
||||
@@ -115,18 +115,15 @@ defmodule Code do
|
||||
{3, [a: 1, b: 2]}
|
||||
|
||||
"""
|
||||
def eval_string(string, binding // [], opts // [])
|
||||
def eval_string(string, binding \\ [], opts \\ [])
|
||||
|
||||
def eval_string(string, binding, Macro.Env[] = env) do
|
||||
do_eval_string(string, binding, env.to_keywords)
|
||||
{ value, binding, _env, _scope } = :elixir.eval to_char_list(string), binding, env.to_keywords
|
||||
{ value, binding }
|
||||
end
|
||||
|
||||
def eval_string(string, binding, opts) when is_list(opts) do
|
||||
validate_eval_opts(opts)
|
||||
do_eval_string(string, binding, opts)
|
||||
end
|
||||
|
||||
defp do_eval_string(string, binding, opts) when is_list(binding) do
|
||||
{ value, binding, _env, _scope } = :elixir.eval to_char_list(string), binding, opts
|
||||
{ value, binding }
|
||||
end
|
||||
@@ -139,29 +136,26 @@ defmodule Code do
|
||||
## Examples
|
||||
|
||||
iex> contents = quote(hygiene: [vars: false], do: a + b)
|
||||
...> Code.eval_quoted(contents, [a: 1, b: 2], file: __ENV__.file, line: __ENV__.line)
|
||||
iex> Code.eval_quoted(contents, [a: 1, b: 2], file: __ENV__.file, line: __ENV__.line)
|
||||
{3, [a: 1, b: 2]}
|
||||
|
||||
For convenience, you can pass `__ENV__` as the `opts` argument and
|
||||
all options will be automatically extracted from the current environment:
|
||||
|
||||
iex> contents = quote(hygiene: [vars: false], do: a + b)
|
||||
...> Code.eval_quoted(contents, [a: 1, b: 2], __ENV__)
|
||||
iex> Code.eval_quoted(contents, [a: 1, b: 2], __ENV__)
|
||||
{3, [a: 1, b: 2]}
|
||||
|
||||
"""
|
||||
def eval_quoted(quoted, binding // [], opts // [])
|
||||
def eval_quoted(quoted, binding \\ [], opts \\ [])
|
||||
|
||||
def eval_quoted(quoted, binding, Macro.Env[] = env) do
|
||||
do_eval_quoted(quoted, binding, env.to_keywords)
|
||||
{ value, binding, _env, _scope } = :elixir.eval_quoted quoted, binding, env.to_keywords
|
||||
{ value, binding }
|
||||
end
|
||||
|
||||
def eval_quoted(quoted, binding, opts) when is_list(opts) do
|
||||
validate_eval_opts(opts)
|
||||
do_eval_quoted(quoted, binding, opts)
|
||||
end
|
||||
|
||||
defp do_eval_quoted(quoted, binding, opts) when is_list(binding) do
|
||||
{ value, binding, _env, _scope } = :elixir.eval_quoted quoted, binding, opts
|
||||
{ value, binding }
|
||||
end
|
||||
@@ -204,8 +198,8 @@ defmodule Code do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Convert the given string to its quoted form.
|
||||
|
||||
Convert the given string to its quoted form.
|
||||
|
||||
Returns `{ :ok, quoted_form }`
|
||||
if it succeeds, `{ :error, { line, error, token } }` otherwise.
|
||||
|
||||
@@ -225,20 +219,15 @@ defmodule Code do
|
||||
`Macro.to_string/2`, which converts a quoted form to a string/binary
|
||||
representation.
|
||||
"""
|
||||
def string_to_quoted(string, opts // []) when is_list(opts) do
|
||||
def string_to_quoted(string, opts \\ []) when is_list(opts) do
|
||||
file = Keyword.get opts, :file, "nofile"
|
||||
line = Keyword.get opts, :line, 1
|
||||
res = :elixir.string_to_quoted(to_char_list(string), line, file, opts)
|
||||
|
||||
case res do
|
||||
{ :ok, forms } -> { :ok, forms }
|
||||
_ -> res
|
||||
end
|
||||
:elixir.string_to_quoted(to_char_list(string), line, file, opts)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Convert the given string to its quoted form.
|
||||
|
||||
Convert the given string to its quoted form.
|
||||
|
||||
It returns the ast if it succeeds,
|
||||
raises an exception otherwise. The exception is a `TokenMissingError`
|
||||
in case a token is missing (usually because the expression is incomplete),
|
||||
@@ -246,27 +235,40 @@ defmodule Code do
|
||||
|
||||
Check `string_to_quoted/2` for options information.
|
||||
"""
|
||||
def string_to_quoted!(string, opts // []) when is_list(opts) do
|
||||
def string_to_quoted!(string, opts \\ []) when is_list(opts) do
|
||||
file = Keyword.get opts, :file, "nofile"
|
||||
line = Keyword.get opts, :line, 1
|
||||
:elixir.string_to_quoted!(to_char_list(string), line, file, opts)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Load the given file.
|
||||
|
||||
Accepts `relative_to` as an argument to tell where
|
||||
the file is located. If the file was already required/loaded, loads it again.
|
||||
It returns a list of tuples `{ ModuleName, <<byte_code>> }`, one tuple for each
|
||||
module defined in the file.
|
||||
Evals the given file.
|
||||
|
||||
Notice that if `load_file` is invoked by different processes
|
||||
concurrently, the target file will be invoked concurrently
|
||||
many times. I.e. if `load_file` is called N times with
|
||||
a given file, the given file will be loaded N times. Check
|
||||
`require_file/2` if you don't want a file to be loaded concurrently.
|
||||
Accepts `relative_to` as an argument to tell where the file is located.
|
||||
|
||||
While `load_file` loads a file and returns the loaded modules and their
|
||||
byte code, `eval_file` simply evalutes the file contents and returns the
|
||||
evaluation result and its bindings.
|
||||
"""
|
||||
def load_file(file, relative_to // nil) when is_binary(file) do
|
||||
def eval_file(file, relative_to \\ nil) do
|
||||
file = find_file(file, relative_to)
|
||||
eval_string File.read!(file), [], []
|
||||
end
|
||||
|
||||
@doc """
|
||||
Load the given file.
|
||||
|
||||
Accepts `relative_to` as an argument to tell where the file is located.
|
||||
If the file was already required/loaded, loads it again.
|
||||
|
||||
It returns a list of tuples `{ ModuleName, <<byte_code>> }`, one tuple for
|
||||
each module defined in the file.
|
||||
|
||||
Notice that if `load_file` is invoked by different processes concurrently,
|
||||
the target file will be loaded concurrently many times. Check `require_file/2`
|
||||
if you don't want a file to be loaded concurrently.
|
||||
"""
|
||||
def load_file(file, relative_to \\ nil) when is_binary(file) do
|
||||
file = find_file(file, relative_to)
|
||||
:elixir_code_server.call { :acquire, file }
|
||||
loaded = :elixir_compiler.file file
|
||||
@@ -275,11 +277,11 @@ defmodule Code do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Require the given `file`.
|
||||
|
||||
Accepts `relative_to` as an argument to tell where
|
||||
the file is located. The return value is the same as that of `load_file/2`. If
|
||||
the file was already required/loaded, doesn't do anything and returns `nil`.
|
||||
Requires the given `file`.
|
||||
|
||||
Accepts `relative_to` as an argument to tell where the file is located.
|
||||
The return value is the same as that of `load_file/2`. If the file was already
|
||||
required/loaded, doesn't do anything and returns `nil`.
|
||||
|
||||
Notice that if `require_file` is invoked by different processes concurrently,
|
||||
the first process to invoke `require_file` acquires a lock and the remaining
|
||||
@@ -287,9 +289,9 @@ defmodule Code do
|
||||
N times with a given file, it will be loaded only once. The first process to
|
||||
call `require_file` will get the list of loaded modules, others will get `nil`.
|
||||
|
||||
Check `load_file/2` if you want a file to be loaded concurrently.
|
||||
Check `load_file/2` if you want a file to be loaded multiple times.
|
||||
"""
|
||||
def require_file(file, relative_to // nil) when is_binary(file) do
|
||||
def require_file(file, relative_to \\ nil) when is_binary(file) do
|
||||
file = find_file(file, relative_to)
|
||||
|
||||
case :elixir_code_server.call({ :acquire, file }) do
|
||||
@@ -305,7 +307,7 @@ defmodule Code do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Load the compilation options from the code server.
|
||||
Gets the compilation options from the code server.
|
||||
|
||||
Check `compiler_options/1` for more information.
|
||||
"""
|
||||
@@ -314,7 +316,7 @@ defmodule Code do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Set compilation options.
|
||||
Sets compilation options.
|
||||
|
||||
These options are global since they are stored by Elixir's Code Server.
|
||||
|
||||
@@ -338,39 +340,35 @@ defmodule Code do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Compile the given string.
|
||||
Compiles the given string.
|
||||
|
||||
Returns a list of tuples where
|
||||
the first element is the module name and the second one is its
|
||||
binary.
|
||||
Returns a list of tuples where the first element is the module name
|
||||
and the second one is its byte code (as a binary).
|
||||
|
||||
For compiling many files at once, check `Kernel.ParallelCompiler.files/2`.
|
||||
"""
|
||||
def compile_string(string, file // "nofile") when is_binary(file) do
|
||||
def compile_string(string, file \\ "nofile") when is_binary(file) do
|
||||
:elixir_compiler.string to_char_list(string), file
|
||||
end
|
||||
|
||||
@doc """
|
||||
Compile the quoted expression.
|
||||
|
||||
Returns a list of tuples where
|
||||
the first element is the module name and the second one is its
|
||||
binary.
|
||||
Compiles the quoted expression.
|
||||
|
||||
Returns a list of tuples where the first element is the module name and
|
||||
the second one is its byte code (as a binary).
|
||||
"""
|
||||
def compile_quoted(quoted, file // "nofile") when is_binary(file) do
|
||||
def compile_quoted(quoted, file \\ "nofile") when is_binary(file) do
|
||||
:elixir_compiler.quoted quoted, file
|
||||
end
|
||||
|
||||
@doc """
|
||||
Ensure the given module is loaded.
|
||||
|
||||
If the module is already
|
||||
loaded, this works as no-op. If the module was not yet loaded,
|
||||
it tries to load it.
|
||||
Ensures the given module is loaded.
|
||||
|
||||
If it succeeds loading the module, it returns
|
||||
`{ :module, module }`. If not, returns `{ :error, reason }` with
|
||||
the error reason.
|
||||
If the module is already loaded, this works as no-op. If the module
|
||||
was not yet loaded, it tries to load it.
|
||||
|
||||
If it succeeds loading the module, it returns `{ :module, module }`.
|
||||
If not, returns `{ :error, reason }` with the error reason.
|
||||
|
||||
## Code loading on the Erlang VM
|
||||
|
||||
@@ -385,47 +383,46 @@ defmodule Code do
|
||||
module uses this function to check if a specific parser exists for a given
|
||||
URI scheme.
|
||||
|
||||
## Code.ensure_compiled
|
||||
## `Code.ensure_compiled/1`
|
||||
|
||||
Elixir also contains an `ensure_compiled/1` function that is a
|
||||
superset of `ensure_loaded/1`.
|
||||
|
||||
Since Elixir's compilation happens in parallel, in some situations
|
||||
you may need to use a module but that was not yet compiled, therefore
|
||||
you may need to use a module that was not yet compiled, therefore
|
||||
it can't even be loaded.
|
||||
|
||||
`ensure_compiled/1` halts the current process until the
|
||||
module we are depending on is available.
|
||||
|
||||
In most cases, `ensure_loaded` is enough. `ensure_compiled`
|
||||
must be used in some rare cases, usually involving macros
|
||||
that need to invoke a module for callback information.
|
||||
In most cases, `ensure_loaded/1` is enough. `ensure_compiled/1`
|
||||
must be used in rare cases, usually involving macros that need to
|
||||
invoke a module for callback information.
|
||||
"""
|
||||
def ensure_loaded(module) when is_atom(module) do
|
||||
:code.ensure_loaded(module)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Ensure the given module is loaded.
|
||||
Ensures the given module is loaded.
|
||||
|
||||
Similar to `ensure_loaded/1`, but returns `true` if the module
|
||||
is already loaded or was successfully loaded. Returns `false` otherwise.
|
||||
is already loaded or was successfully loaded. Returns `false`
|
||||
otherwise.
|
||||
"""
|
||||
def ensure_loaded?(module) do
|
||||
match?({ :module, ^module }, ensure_loaded(module))
|
||||
end
|
||||
|
||||
@doc """
|
||||
Ensure the given module is compiled and loaded.
|
||||
|
||||
If the module
|
||||
is already loaded, it works as no-op. If the module was not
|
||||
loaded yet, it checks if it needs to be compiled first and
|
||||
then tries to load it.
|
||||
Ensures the given module is compiled and loaded.
|
||||
|
||||
If it succeeds loading the module, it returns
|
||||
`{ :module, module }`. If not, returns `{ :error, reason }` with
|
||||
the error reason.
|
||||
If the module is already loaded, it works as no-op. If the module was
|
||||
not loaded yet, it checks if it needs to be compiled first and then
|
||||
tries to load it.
|
||||
|
||||
If it succeeds loading the module, it returns `{ :module, module }`.
|
||||
If not, returns `{ :error, reason }` with the error reason.
|
||||
|
||||
Check `ensure_loaded/1` for more information on module loading
|
||||
and when to use `ensure_loaded/1` or `ensure_compiled/1`.
|
||||
@@ -448,10 +445,10 @@ defmodule Code do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Ensure the given module is compiled and loaded.
|
||||
Ensures the given module is compiled and loaded.
|
||||
|
||||
Similar to `ensure_compiled/1`, but returns `true` if the module
|
||||
is already loaded or was successfully loaded and compiled.
|
||||
is already loaded or was successfully loaded and compiled.
|
||||
Returns `false` otherwise.
|
||||
"""
|
||||
def ensure_compiled?(module) do
|
||||
@@ -461,6 +458,7 @@ defmodule Code do
|
||||
## Helpers
|
||||
|
||||
# Finds the file given the relative_to path.
|
||||
#
|
||||
# If the file is found, returns its path in binary, fails otherwise.
|
||||
defp find_file(file, relative_to) do
|
||||
file = if relative_to do
|
||||
|
||||
@@ -0,0 +1,117 @@
|
||||
defprotocol Collectable do
|
||||
@moduledoc """
|
||||
A protocol to traverse data structures.
|
||||
|
||||
The `Enum.into/2` function uses this protocol to insert an
|
||||
enumerable into a collection:
|
||||
|
||||
iex> Enum.into([a: 1, b: 2], %{})
|
||||
%{a: 1, b: 2}
|
||||
|
||||
If a collection implements both `Enumerable` and `Collectable`, both
|
||||
operations can be combined with `Enum.traverse/2`:
|
||||
|
||||
iex> Enum.traverse(%{ a: 1, b: 2 }, fn { k, v } -> { k, v * 2 } end)
|
||||
%{a: 2, b: 4}
|
||||
|
||||
## Why Collectable?
|
||||
|
||||
The `Enumerable` protocol is useful to take values out of a collection.
|
||||
In order to support a wide range of values, the functions provided by
|
||||
the `Enumerable` protocol do not keep shape. For example, passing a
|
||||
dictionary to `Enum.map/2` always returns a list.
|
||||
|
||||
This design is intentional. `Enumerable` was designed to support infinite
|
||||
collections, resources and other structures with fixed shape. For example,
|
||||
it doesn't make sense to insert values into a range, as it has a fixed
|
||||
shape where just the range limits are stored.
|
||||
|
||||
The `Collectable` module was designed to fill the gap left by the
|
||||
`Enumerable` protocol. It provides two functions: `into/1` and `empty/1`.
|
||||
|
||||
`into/1` can be seen as the opposite of `Enumerable.reduce/3`. If
|
||||
`Enumerable` is about taking values out, `Collectable.into/1` is about
|
||||
collecting those values into a structure.
|
||||
|
||||
`empty/1` receives a collectable and returns an empty version of the
|
||||
same collectable. By combining the enumerable functionality with `into/1`
|
||||
and `empty/1`, one can, for example, implement a traversal mechanism.
|
||||
"""
|
||||
|
||||
@type command :: { :cont, term } | :done | :halt
|
||||
|
||||
@doc """
|
||||
Receives a collectable structure and returns an empty one.
|
||||
"""
|
||||
@spec empty(t) :: t
|
||||
def empty(collectable)
|
||||
|
||||
@doc """
|
||||
Returns a function that collects values alongside
|
||||
the initial accumulation value.
|
||||
|
||||
The returned function receives a collectable and injects a given
|
||||
value into it for every `{ :cont, term }` instruction.
|
||||
|
||||
`:done` is passed when no further values will be injected, useful
|
||||
for closing resources and normalizing values. A collectable must
|
||||
be returned on `:done`.
|
||||
|
||||
If injection is suddenly interrupted, `:halt` is passed and it can
|
||||
return any value, as it won't be used.
|
||||
"""
|
||||
@spec into(t) :: { term, (term, command -> t | term) }
|
||||
def into(collectable)
|
||||
end
|
||||
|
||||
defimpl Collectable, for: List do
|
||||
def empty(_list) do
|
||||
[]
|
||||
end
|
||||
|
||||
def into(original) do
|
||||
{ [], fn
|
||||
list, { :cont, x } -> [x|list]
|
||||
list, :done -> original ++ :lists.reverse(list)
|
||||
_, :halt -> :ok
|
||||
end }
|
||||
end
|
||||
end
|
||||
|
||||
defimpl Collectable, for: BitString do
|
||||
def empty(_bitstring) do
|
||||
""
|
||||
end
|
||||
|
||||
def into(original) do
|
||||
{ original, fn
|
||||
bitstring, { :cont, x } -> <<bitstring :: bits, x :: bits>>
|
||||
bitstring, :done -> bitstring
|
||||
_, :halt -> :ok
|
||||
end }
|
||||
end
|
||||
end
|
||||
|
||||
defimpl Collectable, for: Function do
|
||||
def empty(function) do
|
||||
function
|
||||
end
|
||||
|
||||
def into(function) do
|
||||
{ function, function }
|
||||
end
|
||||
end
|
||||
|
||||
defimpl Collectable, for: Map do
|
||||
def empty(_map) do
|
||||
%{}
|
||||
end
|
||||
|
||||
def into(original) do
|
||||
{ original, fn
|
||||
map, { :cont, { k, v } } -> :maps.put(k, v, map)
|
||||
map, :done -> map
|
||||
_, :halt -> :ok
|
||||
end }
|
||||
end
|
||||
end
|
||||
+135
-153
@@ -1,5 +1,5 @@
|
||||
defmodule Dict do
|
||||
@moduledoc %S"""
|
||||
@moduledoc ~S"""
|
||||
This module specifies the Dict API expected to be
|
||||
implemented by different dictionaries. It also provides
|
||||
functions that redirect to the underlying Dict, allowing
|
||||
@@ -12,7 +12,7 @@ defmodule Dict do
|
||||
HashDict.new #=> creates an empty HashDict
|
||||
|
||||
In the examples below, `dict_impl` means a specific
|
||||
`Dict` implementation, for example `HashDict` or `ListDict`.
|
||||
`Dict` implementation, for example `HashDict` or `Map`.
|
||||
|
||||
## Protocols
|
||||
|
||||
@@ -21,34 +21,27 @@ defmodule Dict do
|
||||
protocol:
|
||||
|
||||
iex> dict = dict_impl.new
|
||||
...> dict = Dict.put(dict, :hello, :world)
|
||||
...> dict[:hello]
|
||||
iex> dict = Dict.put(dict, :hello, :world)
|
||||
iex> dict[:hello]
|
||||
:world
|
||||
|
||||
And the `Enumerable` protocol, allowing one to write:
|
||||
|
||||
Enum.each(dict, fn ({ k, v }) ->
|
||||
IO.puts "#{k}: #{v}"
|
||||
end)
|
||||
As well as the `Enumerable` and `Collectable` protocols.
|
||||
|
||||
## Match
|
||||
|
||||
Dictionaries are required to implement all operations
|
||||
using the match (`===`) operator. Any deviation from
|
||||
this behaviour should be avoided and explicitly documented.
|
||||
using the match (`===`) operator.
|
||||
"""
|
||||
|
||||
use Behaviour
|
||||
|
||||
@type key :: any
|
||||
@type value :: any
|
||||
@type keys :: [ key ]
|
||||
@type t :: tuple | list
|
||||
@type t :: tuple | list | map
|
||||
|
||||
defcallback new :: t
|
||||
defcallback new(Keyword.t) :: t
|
||||
defcallback delete(t, key) :: t
|
||||
defcallback drop(t, keys) :: t
|
||||
defcallback drop(t, Enum.t) :: t
|
||||
defcallback empty(t) :: t
|
||||
defcallback equal?(t, t) :: boolean
|
||||
defcallback get(t, key) :: value
|
||||
@@ -56,7 +49,7 @@ defmodule Dict do
|
||||
defcallback fetch(t, key) :: { :ok, value } | :error
|
||||
defcallback fetch!(t, key) :: value | no_return
|
||||
defcallback has_key?(t, key) :: boolean
|
||||
defcallback keys(t) :: list(key)
|
||||
defcallback keys(t) :: [key]
|
||||
defcallback merge(t, t) :: t
|
||||
defcallback merge(t, t, (key, value, value -> value)) :: t
|
||||
defcallback pop(t, key) :: {value, t}
|
||||
@@ -64,8 +57,8 @@ defmodule Dict do
|
||||
defcallback put(t, key, value) :: t
|
||||
defcallback put_new(t, key, value) :: t
|
||||
defcallback size(t) :: non_neg_integer()
|
||||
defcallback split(t, keys) :: {t, t}
|
||||
defcallback take(t, keys) :: t
|
||||
defcallback split(t, Enum.t) :: {t, t}
|
||||
defcallback take(t, Enum.t) :: t
|
||||
defcallback to_list(t) :: list()
|
||||
defcallback update(t, key, value, (value -> value)) :: t
|
||||
defcallback update!(t, key, (value -> value)) :: t | no_return
|
||||
@@ -73,13 +66,17 @@ defmodule Dict do
|
||||
|
||||
defmacrop target(dict) do
|
||||
quote do
|
||||
cond do
|
||||
is_tuple(unquote(dict)) ->
|
||||
elem(unquote(dict), 0)
|
||||
is_list(unquote(dict)) ->
|
||||
case unquote(dict) do
|
||||
%{__struct__: x} when is_atom(x) ->
|
||||
x
|
||||
%{} ->
|
||||
Map
|
||||
x when is_tuple(x) ->
|
||||
elem(x, 0)
|
||||
x when is_list(x) ->
|
||||
ListDict
|
||||
true ->
|
||||
unsupported_dict(unquote(dict))
|
||||
x ->
|
||||
unsupported_dict(x)
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -90,8 +87,8 @@ defmodule Dict do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> d = dict_impl.new([a: 1, b: 2])
|
||||
...> Enum.sort(Dict.keys(d))
|
||||
iex> d = Enum.into([a: 1, b: 2], dict_impl.new)
|
||||
iex> Enum.sort(Dict.keys(d))
|
||||
[:a,:b]
|
||||
|
||||
"""
|
||||
@@ -106,8 +103,8 @@ defmodule Dict do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> d = dict_impl.new([a: 1, b: 2])
|
||||
...> Enum.sort(Dict.values(d))
|
||||
iex> d = Enum.into([a: 1, b: 2], dict_impl.new)
|
||||
iex> Enum.sort(Dict.values(d))
|
||||
[1,2]
|
||||
|
||||
"""
|
||||
@@ -121,8 +118,8 @@ defmodule Dict do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> d = dict_impl.new([a: 1, b: 2])
|
||||
...> Dict.size(d)
|
||||
iex> d = Enum.into([a: 1, b: 2], dict_impl.new)
|
||||
iex> Dict.size(d)
|
||||
2
|
||||
|
||||
"""
|
||||
@@ -136,7 +133,7 @@ defmodule Dict do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> d = dict_impl.new([a: 1])
|
||||
iex> d = Enum.into([a: 1], dict_impl.new)
|
||||
iex> Dict.has_key?(d, :a)
|
||||
true
|
||||
iex> Dict.has_key?(d, :b)
|
||||
@@ -154,7 +151,7 @@ defmodule Dict do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> d = dict_impl.new([a: 1])
|
||||
iex> d = Enum.into([a: 1], dict_impl.new)
|
||||
iex> Dict.get(d, :a)
|
||||
1
|
||||
iex> Dict.get(d, :b)
|
||||
@@ -163,7 +160,7 @@ defmodule Dict do
|
||||
3
|
||||
"""
|
||||
@spec get(t, key, value) :: value
|
||||
def get(dict, key, default // nil) do
|
||||
def get(dict, key, default \\ nil) do
|
||||
target(dict).get(dict, key, default)
|
||||
end
|
||||
|
||||
@@ -173,7 +170,7 @@ defmodule Dict do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> d = dict_impl.new([a: 1])
|
||||
iex> d = Enum.into([a: 1], dict_impl.new)
|
||||
iex> Dict.fetch(d, :a)
|
||||
{ :ok, 1 }
|
||||
iex> Dict.fetch(d, :b)
|
||||
@@ -191,11 +188,9 @@ defmodule Dict do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> d = dict_impl.new([a: 1])
|
||||
iex> d = Enum.into([a: 1], dict_impl.new)
|
||||
iex> Dict.fetch!(d, :a)
|
||||
1
|
||||
iex> Dict.fetch!(d, :b)
|
||||
** (KeyError) key not found: :b
|
||||
|
||||
"""
|
||||
@spec fetch!(t, key) :: value | no_return
|
||||
@@ -209,9 +204,9 @@ defmodule Dict do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> d = dict_impl.new([a: 1, b: 2])
|
||||
...> d = Dict.put(d, :a, 3)
|
||||
...> Dict.get(d, :a)
|
||||
iex> d = Enum.into([a: 1, b: 2], dict_impl.new)
|
||||
iex> d = Dict.put(d, :a, 3)
|
||||
iex> Dict.get(d, :a)
|
||||
3
|
||||
|
||||
"""
|
||||
@@ -225,9 +220,9 @@ defmodule Dict do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> d = dict_impl.new([a: 1, b: 2])
|
||||
...> d = Dict.put_new(d, :a, 3)
|
||||
...> Dict.get(d, :a)
|
||||
iex> d = Enum.into([a: 1, b: 2], dict_impl.new)
|
||||
iex> d = Dict.put_new(d, :a, 3)
|
||||
iex> Dict.get(d, :a)
|
||||
1
|
||||
|
||||
"""
|
||||
@@ -242,13 +237,13 @@ defmodule Dict do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> d = dict_impl.new([a: 1, b: 2])
|
||||
...> d = Dict.delete(d, :a)
|
||||
...> Dict.get(d, :a)
|
||||
iex> d = Enum.into([a: 1, b: 2], dict_impl.new)
|
||||
iex> d = Dict.delete(d, :a)
|
||||
iex> Dict.get(d, :a)
|
||||
nil
|
||||
|
||||
iex> d = dict_impl.new([b: 2])
|
||||
...> Dict.delete(d, :a) == d
|
||||
iex> d = Enum.into([b: 2], dict_impl.new)
|
||||
iex> Dict.delete(d, :a) == d
|
||||
true
|
||||
|
||||
"""
|
||||
@@ -258,49 +253,45 @@ defmodule Dict do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Merges the given `enum` into `dict`. If one of the `enum` keys
|
||||
already exists in `dict`, the `dict` value is replaced by the `enum`
|
||||
value.
|
||||
Merges the dict `b` into dict `a`.
|
||||
|
||||
The `enum` must yield tuples with two elements on enumeration,
|
||||
where the first element represents the key and the second the value.
|
||||
If one of the dict `b` entries already exists in the `dict`,
|
||||
the functions in entries in `b` have higher precedence unless a
|
||||
function is given to resolve conflicts.
|
||||
|
||||
Notice this function is polymorphic as it merges dicts of any
|
||||
type. Each dict implementation also provides a `merge` function,
|
||||
but they can only merge dicts of the same type.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> d1 = dict_impl.new([a: 1, b: 2])
|
||||
...> d2 = dict_impl.new([a: 3, d: 4])
|
||||
...> d = Dict.merge(d1, d2)
|
||||
...> [a: Dict.get(d, :a), b: Dict.get(d, :b), d: Dict.get(d, :d)]
|
||||
iex> d1 = Enum.into([a: 1, b: 2], dict_impl.new)
|
||||
iex> d2 = Enum.into([a: 3, d: 4], dict_impl.new)
|
||||
iex> d = Dict.merge(d1, d2)
|
||||
iex> [a: Dict.get(d, :a), b: Dict.get(d, :b), d: Dict.get(d, :d)]
|
||||
[a: 3, b: 2, d: 4]
|
||||
|
||||
"""
|
||||
@spec merge(t, t) :: t
|
||||
def merge(dict, enum) do
|
||||
merge(dict, enum, fn(_k, _v1, v2) -> v2 end)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Merges the given `enum` into `dict`. If one of the `enum` entries
|
||||
already exists in `dict`, the given function is invoked to resolve
|
||||
the conflict.
|
||||
|
||||
The `enum` must yield tuples with two elements on enumeration,
|
||||
where the first element represents the key and the second the value.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> d1 = dict_impl.new([a: 1, b: 2])
|
||||
...> d2 = dict_impl.new([a: 3, d: 4])
|
||||
...> d = Dict.merge(d1, d2, fn(_k, v1, v2) ->
|
||||
iex> d1 = Enum.into([a: 1, b: 2], dict_impl.new)
|
||||
iex> d2 = Enum.into([a: 3, d: 4], dict_impl.new)
|
||||
iex> d = Dict.merge(d1, d2, fn(_k, v1, v2) ->
|
||||
...> v1 + v2
|
||||
...> end)
|
||||
...> [a: Dict.get(d, :a), b: Dict.get(d, :b), d: Dict.get(d, :d)]
|
||||
iex> [a: Dict.get(d, :a), b: Dict.get(d, :b), d: Dict.get(d, :d)]
|
||||
[a: 4, b: 2, d: 4]
|
||||
|
||||
"""
|
||||
@spec merge(t, t, (key, value, value -> value)) :: t
|
||||
def merge(dict, enum, fun) do
|
||||
target(dict).merge(dict, enum, fun)
|
||||
def merge(dict1, dict2, fun \\ fn(_k, _v1, v2) -> v2 end) do
|
||||
target1 = target(dict1)
|
||||
target2 = target(dict2)
|
||||
|
||||
if target1 == target2 do
|
||||
target1.merge(dict1, dict2, fun)
|
||||
else
|
||||
Enumerable.reduce(dict2, { :cont, dict1 }, fn({ k, v }, acc) ->
|
||||
{ :cont, target1.update(acc, k, v, fn(other) -> fun.(k, other, v) end) }
|
||||
end) |> elem(1)
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -309,24 +300,24 @@ defmodule Dict do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> dict = dict_impl.new [a: 1]
|
||||
...> {v, d} = Dict.pop dict, :a
|
||||
...> {v, Enum.sort(d)}
|
||||
iex> dict = Enum.into([a: 1], dict_impl.new)
|
||||
iex> {v, d} = Dict.pop dict, :a
|
||||
iex> {v, Enum.sort(d)}
|
||||
{1,[]}
|
||||
|
||||
iex> dict = dict_impl.new [a: 1]
|
||||
...> {v, d} = Dict.pop dict, :b
|
||||
...> {v, Enum.sort(d)}
|
||||
iex> dict = Enum.into([a: 1], dict_impl.new)
|
||||
iex> {v, d} = Dict.pop dict, :b
|
||||
iex> {v, Enum.sort(d)}
|
||||
{nil,[a: 1]}
|
||||
|
||||
iex> dict = dict_impl.new [a: 1]
|
||||
...> {v, d} = Dict.pop dict, :b, 3
|
||||
...> {v, Enum.sort(d)}
|
||||
iex> dict = Enum.into([a: 1], dict_impl.new)
|
||||
iex> {v, d} = Dict.pop dict, :b, 3
|
||||
iex> {v, Enum.sort(d)}
|
||||
{3,[a: 1]}
|
||||
|
||||
"""
|
||||
@spec pop(t, key, value) :: {value, t}
|
||||
def pop(dict, key, default // nil) do
|
||||
def pop(dict, key, default \\ nil) do
|
||||
target(dict).pop(dict, key, default)
|
||||
end
|
||||
|
||||
@@ -336,9 +327,9 @@ defmodule Dict do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> d = dict_impl.new([a: 1, b: 2])
|
||||
...> d = Dict.update!(d, :a, fn(val) -> -val end)
|
||||
...> Dict.get(d, :a)
|
||||
iex> d = Enum.into([a: 1, b: 2], dict_impl.new)
|
||||
iex> d = Dict.update!(d, :a, fn(val) -> -val end)
|
||||
iex> Dict.get(d, :a)
|
||||
-1
|
||||
|
||||
"""
|
||||
@@ -347,11 +338,6 @@ defmodule Dict do
|
||||
target(dict).update!(dict, key, fun)
|
||||
end
|
||||
|
||||
@doc false
|
||||
def update(dict, key, fun) do
|
||||
target(dict).update(dict, key, fun)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Update a value in `dict` by calling `fun` on the value to get a new value. If
|
||||
`key` is not present in `dict` then `initial` will be stored as the first
|
||||
@@ -359,9 +345,9 @@ defmodule Dict do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> d = dict_impl.new([a: 1, b: 2])
|
||||
...> d = Dict.update(d, :c, 3, fn(val) -> -val end)
|
||||
...> Dict.get(d, :c)
|
||||
iex> d = Enum.into([a: 1, b: 2], dict_impl.new)
|
||||
iex> d = Dict.update(d, :c, 3, fn(val) -> -val end)
|
||||
iex> Dict.get(d, :c)
|
||||
3
|
||||
|
||||
"""
|
||||
@@ -379,23 +365,23 @@ defmodule Dict do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> d = dict_impl.new([a: 1, b: 2, c: 3, d: 4])
|
||||
...> { d1, d2 } = Dict.split(d, [:a, :c, :e])
|
||||
...> { Dict.to_list(d1), Dict.to_list(d2) }
|
||||
iex> d = Enum.into([a: 1, b: 2, c: 3, d: 4], dict_impl.new)
|
||||
iex> { d1, d2 } = Dict.split(d, [:a, :c, :e])
|
||||
iex> { Dict.to_list(d1) |> Enum.sort, Dict.to_list(d2) |> Enum.sort }
|
||||
{ [a: 1, c: 3], [b: 2, d: 4] }
|
||||
|
||||
iex> d = dict_impl.new([])
|
||||
...> { d1, d2 } = Dict.split(d, [:a, :c])
|
||||
...> { Dict.to_list(d1), Dict.to_list(d2) }
|
||||
iex> d = Enum.into([], dict_impl.new)
|
||||
iex> { d1, d2 } = Dict.split(d, [:a, :c])
|
||||
iex> { Dict.to_list(d1), Dict.to_list(d2) }
|
||||
{ [], [] }
|
||||
|
||||
iex> d = dict_impl.new([a: 1, b: 2])
|
||||
...> { d1, d2 } = Dict.split(d, [:a, :b, :c])
|
||||
...> { Dict.to_list(d1), Dict.to_list(d2) }
|
||||
iex> d = Enum.into([a: 1, b: 2], dict_impl.new)
|
||||
iex> { d1, d2 } = Dict.split(d, [:a, :b, :c])
|
||||
iex> { Dict.to_list(d1) |> Enum.sort, Dict.to_list(d2) }
|
||||
{ [a: 1, b: 2], [] }
|
||||
|
||||
"""
|
||||
@spec split(t, keys) :: {t, t}
|
||||
@spec split(t, [key]) :: {t, t}
|
||||
def split(dict, keys) do
|
||||
target(dict).split(dict, keys)
|
||||
end
|
||||
@@ -406,89 +392,85 @@ defmodule Dict do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> d = dict_impl.new([a: 1, b: 2])
|
||||
...> d = Dict.drop(d, [:a, :c, :d])
|
||||
...> Dict.to_list(d)
|
||||
iex> d = Enum.into([a: 1, b: 2], dict_impl.new)
|
||||
iex> d = Dict.drop(d, [:a, :c, :d])
|
||||
iex> Dict.to_list(d)
|
||||
[b: 2]
|
||||
|
||||
iex> d = dict_impl.new([a: 1, b: 2])
|
||||
...> d = Dict.drop(d, [:c, :d])
|
||||
...> Dict.to_list(d)
|
||||
iex> d = Enum.into([a: 1, b: 2], dict_impl.new)
|
||||
iex> d = Dict.drop(d, [:c, :d])
|
||||
iex> Dict.to_list(d) |> Enum.sort
|
||||
[a: 1, b: 2]
|
||||
|
||||
"""
|
||||
@spec drop(t, keys) :: t
|
||||
@spec drop(t, [key]) :: t
|
||||
def drop(dict, keys) do
|
||||
target(dict).drop(dict, keys)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns a new dict where only the keys in `keys` from `dict` are
|
||||
included.
|
||||
Returns a new dict where only the keys in `keys` from `dict` are included.
|
||||
|
||||
Any non-member keys are ignored.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> d = dict_impl.new([a: 1, b: 2])
|
||||
...>
|
||||
...> d = Dict.take(d, [:a, :c, :d])
|
||||
...> Dict.to_list(d)
|
||||
iex> d = Enum.into([a: 1, b: 2], dict_impl.new)
|
||||
iex> d = Dict.take(d, [:a, :c, :d])
|
||||
iex> Dict.to_list(d)
|
||||
[a: 1]
|
||||
...>
|
||||
...> d = Dict.take(d, [:c, :d])
|
||||
...> Dict.to_list(d)
|
||||
iex> d = Dict.take(d, [:c, :d])
|
||||
iex> Dict.to_list(d)
|
||||
[]
|
||||
|
||||
"""
|
||||
@spec take(t, keys) :: t
|
||||
@spec take(t, [key]) :: t
|
||||
def take(dict, keys) do
|
||||
target(dict).take(dict, keys)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns an empty dict of the same type as `dict`.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> d = dict_impl.new([a: 1, b: 2])
|
||||
...> e = Dict.empty(d)
|
||||
...> Dict.to_list(e)
|
||||
[]
|
||||
|
||||
"""
|
||||
@doc false
|
||||
@spec empty(t) :: t
|
||||
def empty(dict) do
|
||||
target(dict).empty(dict)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Check if two dicts are equal using `===`. If the dicts are
|
||||
of different types, they are first converted to lists.
|
||||
Check if two dicts are equal using `===`.
|
||||
|
||||
Notice this function is polymorphic as it compares dicts of any
|
||||
type. Each dict implementation also provides an `equal?` function,
|
||||
but they can only compare dicts of the same type.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> a = dict_impl.new(a: 2, b: 3, f: 5, c: 123)
|
||||
...> b = ListDict.new(a: 2, b: 3, f: 5, c: 123)
|
||||
...> Dict.equal?(a, b)
|
||||
iex> a = Enum.into([a: 2, b: 3, f: 5, c: 123], dict_impl.new)
|
||||
iex> b = [a: 2, b: 3, f: 5, c: 123]
|
||||
iex> Dict.equal?(a, b)
|
||||
true
|
||||
|
||||
iex> a = dict_impl.new(a: 2, b: 3, f: 5, c: 123)
|
||||
...> b = []
|
||||
...> Dict.equal?(a, b)
|
||||
iex> a = Enum.into([a: 2, b: 3, f: 5, c: 123], dict_impl.new)
|
||||
iex> b = []
|
||||
iex> Dict.equal?(a, b)
|
||||
false
|
||||
|
||||
"""
|
||||
@spec equal?(t, t) :: boolean
|
||||
def equal?(a, b) do
|
||||
a_target = target(a)
|
||||
b_target = target(b)
|
||||
def equal?(dict1, dict2) do
|
||||
target1 = target(dict1)
|
||||
target2 = target(dict2)
|
||||
|
||||
cond do
|
||||
a_target == b_target ->
|
||||
a_target.equal?(a, b)
|
||||
target1 == target2 ->
|
||||
target1.equal?(dict1, dict2)
|
||||
|
||||
a_target.size(a) == b_target.size(b) ->
|
||||
ListDict.equal?(a_target.to_list(a), b_target.to_list(b))
|
||||
target1.size(dict1) == target2.size(dict2) ->
|
||||
Enumerable.reduce(dict2, { :cont, true }, fn({ k, v }, _acc) ->
|
||||
case target1.fetch(dict1, k) do
|
||||
{ :ok, ^v } -> { :cont, true }
|
||||
_ -> { :halt, false }
|
||||
end
|
||||
end) |> elem(1)
|
||||
|
||||
true ->
|
||||
false
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
defmodule Dict.Behaviour do
|
||||
@moduledoc """
|
||||
@moduledoc ~S"""
|
||||
Default implementations for some required functions in the `Dict` module.
|
||||
|
||||
This module makes it easier to create your own `Dict` compliant
|
||||
@@ -24,8 +24,8 @@ defmodule Dict.Behaviour do
|
||||
* `size/1`
|
||||
* `update/4`
|
||||
|
||||
All of which are part of the `Dict` behaviour, so no extra functions are
|
||||
actually required.
|
||||
All functions, except `reduce/3`, are required by the Dict behaviour.
|
||||
`reduce/3` must be implemtented as per the Enumerable protocol.
|
||||
|
||||
Based on these functions, `Dict.Behaviour` generates default implementations
|
||||
for the following functions:
|
||||
@@ -50,7 +50,7 @@ defmodule Dict.Behaviour do
|
||||
Note you can also test your custom module via `Dict`'s doctests:
|
||||
|
||||
defmodule MyDict do
|
||||
def new(keywords // []) do
|
||||
def new(keywords \\ []) do
|
||||
...
|
||||
end
|
||||
end
|
||||
@@ -63,10 +63,13 @@ defmodule Dict.Behaviour do
|
||||
|
||||
"""
|
||||
defmacro __using__(_) do
|
||||
# Use this import to guarantee proper code expansion
|
||||
import Kernel, except: [size: 1]
|
||||
|
||||
quote do
|
||||
@behaviour Dict
|
||||
|
||||
def get(dict, key, default // nil) do
|
||||
def get(dict, key, default \\ nil) do
|
||||
case fetch(dict, key) do
|
||||
{ :ok, value } -> value
|
||||
:error -> default
|
||||
@@ -76,7 +79,7 @@ defmodule Dict.Behaviour do
|
||||
def fetch!(dict, key) do
|
||||
case fetch(dict, key) do
|
||||
{ :ok, value } -> value
|
||||
:error -> raise KeyError, key: key
|
||||
:error -> raise KeyError, key: key, term: dict
|
||||
end
|
||||
end
|
||||
|
||||
@@ -88,21 +91,16 @@ defmodule Dict.Behaviour do
|
||||
update(dict, key, value, fn(v) -> v end)
|
||||
end
|
||||
|
||||
def drop(dict, []), do: dict
|
||||
|
||||
def drop(dict, [key|keys]) do
|
||||
drop(delete(dict, key), keys)
|
||||
def drop(dict, keys) do
|
||||
Enum.reduce keys, dict, &delete(&2, &1)
|
||||
end
|
||||
|
||||
def take(dict, keys) do
|
||||
take(dict, keys, new)
|
||||
end
|
||||
|
||||
defp take(_dict, [], acc), do: acc
|
||||
defp take(dict, [key|keys], acc) do
|
||||
case fetch(dict, key) do
|
||||
{ :ok, value } -> take(dict, keys, put(acc, key, value))
|
||||
:error -> take(dict, keys, acc)
|
||||
Enum.reduce keys, new, fn key, acc ->
|
||||
case fetch(dict, key) do
|
||||
{ :ok, value } -> put(acc, key, value)
|
||||
:error -> acc
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -125,24 +123,25 @@ defmodule Dict.Behaviour do
|
||||
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) ->
|
||||
unless fetch(dict2, k) == { :ok, v } do
|
||||
{ :halt, false }
|
||||
else
|
||||
{ :cont, true }
|
||||
case fetch(dict2, k) do
|
||||
{ :ok, ^v } -> { :cont, true }
|
||||
_ -> { :halt, false }
|
||||
end
|
||||
end) |> elem(1)
|
||||
end
|
||||
end
|
||||
|
||||
def merge(dict, enumerable, callback // fn(_k, _v1, v2) -> v2 end) do
|
||||
Enum.reduce(enumerable, dict, fn({key, value}, acc) ->
|
||||
update(acc, key, value, fn(v1) -> callback.(key, v1, value) end)
|
||||
end)
|
||||
def merge(dict1, dict2, fun \\ fn(_k, _v1, v2) -> v2 end) do
|
||||
reduce(dict1, { :cont, dict2 }, fn { k, v1 }, acc ->
|
||||
{ :cont, update(acc, k, v1, &fun.(k, v1, &1)) }
|
||||
end) |> elem(1)
|
||||
end
|
||||
|
||||
defoverridable merge: 2, merge: 3, equal?: 2, to_list: 1, keys: 1,
|
||||
|
||||
+214
-49
@@ -144,7 +144,7 @@ defmodule Enum do
|
||||
Some particular types, like dictionaries, yield a specific format on
|
||||
enumeration. For dicts, the argument is always a `{ key, value }` tuple:
|
||||
|
||||
iex> dict = HashDict.new [a: 1, b: 2]
|
||||
iex> dict = %{a: 1, b: 2}
|
||||
iex> Enum.map(dict, fn { k, v } -> { k, v * 2 } end)
|
||||
[a: 2, b: 4]
|
||||
|
||||
@@ -202,6 +202,7 @@ defmodule Enum do
|
||||
|
||||
iex> Enum.all?([1, 2, 3])
|
||||
true
|
||||
|
||||
iex> Enum.all?([1, nil, 3])
|
||||
false
|
||||
|
||||
@@ -209,7 +210,7 @@ defmodule Enum do
|
||||
@spec all?(t) :: boolean
|
||||
@spec all?(t, (element -> as_boolean(term))) :: boolean
|
||||
|
||||
def all?(collection, fun // fn(x) -> x end)
|
||||
def all?(collection, fun \\ fn(x) -> x end)
|
||||
|
||||
def all?(collection, fun) when is_list(collection) do
|
||||
do_all?(collection, fun)
|
||||
@@ -238,6 +239,7 @@ defmodule Enum do
|
||||
|
||||
iex> Enum.any?([false, false, false])
|
||||
false
|
||||
|
||||
iex> Enum.any?([false, true, false])
|
||||
true
|
||||
|
||||
@@ -245,7 +247,7 @@ defmodule Enum do
|
||||
@spec any?(t) :: boolean
|
||||
@spec any?(t, (element -> as_boolean(term))) :: boolean
|
||||
|
||||
def any?(collection, fun // fn(x) -> x end)
|
||||
def any?(collection, fun \\ fn(x) -> x end)
|
||||
|
||||
def any?(collection, fun) when is_list(collection) do
|
||||
do_any?(collection, fun)
|
||||
@@ -265,17 +267,20 @@ defmodule Enum do
|
||||
|
||||
iex> Enum.at([2, 4, 6], 0)
|
||||
2
|
||||
|
||||
iex> Enum.at([2, 4, 6], 2)
|
||||
6
|
||||
|
||||
iex> Enum.at([2, 4, 6], 4)
|
||||
nil
|
||||
|
||||
iex> Enum.at([2, 4, 6], 4, :none)
|
||||
:none
|
||||
|
||||
"""
|
||||
@spec at(t, integer) :: element | nil
|
||||
@spec at(t, integer, default) :: element | default
|
||||
def at(collection, n, default // nil) do
|
||||
def at(collection, n, default \\ nil) do
|
||||
case fetch(collection, n) do
|
||||
{ :ok, h } -> h
|
||||
:error -> default
|
||||
@@ -305,17 +310,20 @@ defmodule Enum do
|
||||
|
||||
iex> Enum.chunk([1, 2, 3, 4, 5, 6], 2)
|
||||
[[1, 2], [3, 4], [5, 6]]
|
||||
|
||||
iex> Enum.chunk([1, 2, 3, 4, 5, 6], 3, 2)
|
||||
[[1, 2, 3], [3, 4, 5]]
|
||||
|
||||
iex> Enum.chunk([1, 2, 3, 4, 5, 6], 3, 2, [7])
|
||||
[[1, 2, 3], [3, 4, 5], [5, 6, 7]]
|
||||
|
||||
iex> Enum.chunk([1, 2, 3, 4, 5, 6], 3, 3, [])
|
||||
[[1, 2, 3], [4, 5, 6]]
|
||||
|
||||
"""
|
||||
@spec chunk(t, non_neg_integer, non_neg_integer) :: [list]
|
||||
@spec chunk(t, non_neg_integer, non_neg_integer, t | nil) :: [list]
|
||||
def chunk(coll, n, step, pad // nil) when n > 0 and step > 0 do
|
||||
def chunk(coll, n, step, pad \\ nil) when n > 0 and step > 0 do
|
||||
limit = :erlang.max(n, step)
|
||||
|
||||
{ _, { acc, { buffer, i } } } =
|
||||
@@ -412,11 +420,6 @@ defmodule Enum do
|
||||
|
||||
def count(collection) do
|
||||
case Enumerable.count(collection) do
|
||||
value when is_integer(value) ->
|
||||
IO.write "Expected #{inspect Enumerable.impl_for(collection)}.count/1 to return " <>
|
||||
"{ :ok, boolean } if pre-calculated, otherwise { :error, module }, got " <>
|
||||
"an integer\n#{Exception.format_stacktrace}"
|
||||
value
|
||||
{ :ok, value } when is_integer(value) ->
|
||||
value
|
||||
{ :error, module } ->
|
||||
@@ -431,6 +434,7 @@ defmodule Enum do
|
||||
`fun` returns `true`.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Enum.count([1, 2, 3, 4, 5], fn(x) -> rem(x, 2) == 0 end)
|
||||
2
|
||||
|
||||
@@ -454,10 +458,13 @@ defmodule Enum do
|
||||
|
||||
iex> Enum.drop([1, 2, 3], 2)
|
||||
[3]
|
||||
|
||||
iex> Enum.drop([1, 2, 3], 10)
|
||||
[]
|
||||
|
||||
iex> Enum.drop([1, 2, 3], 0)
|
||||
[1,2,3]
|
||||
|
||||
iex> Enum.drop([1, 2, 3], -1)
|
||||
[1,2]
|
||||
|
||||
@@ -488,6 +495,7 @@ defmodule Enum do
|
||||
|
||||
iex> Enum.drop_while([1, 2, 3, 4, 5], fn(x) -> x < 3 end)
|
||||
[3,4,5]
|
||||
|
||||
"""
|
||||
@spec drop_while(t, (element -> as_boolean(term))) :: list
|
||||
def drop_while(collection, fun) when is_list(collection) do
|
||||
@@ -533,6 +541,7 @@ defmodule Enum do
|
||||
|
||||
iex> Enum.empty?([])
|
||||
true
|
||||
|
||||
iex> Enum.empty?([1, 2, 3])
|
||||
false
|
||||
|
||||
@@ -558,8 +567,10 @@ defmodule Enum do
|
||||
|
||||
iex> Enum.fetch([2, 4, 6], 0)
|
||||
{ :ok, 2 }
|
||||
|
||||
iex> Enum.fetch([2, 4, 6], 2)
|
||||
{ :ok, 6 }
|
||||
|
||||
iex> Enum.fetch([2, 4, 6], 4)
|
||||
:error
|
||||
|
||||
@@ -626,7 +637,7 @@ defmodule Enum do
|
||||
"""
|
||||
@spec filter(t, (element -> as_boolean(term))) :: list
|
||||
def filter(collection, fun) when is_list(collection) do
|
||||
lc item inlist collection, fun.(item), do: item
|
||||
for item <- collection, fun.(item), do: item
|
||||
end
|
||||
|
||||
def filter(collection, fun) do
|
||||
@@ -645,7 +656,7 @@ defmodule Enum do
|
||||
"""
|
||||
@spec filter_map(t, (element -> as_boolean(term)), (element -> element)) :: list
|
||||
def filter_map(collection, filter, mapper) when is_list(collection) do
|
||||
lc item inlist collection, filter.(item), do: mapper.(item)
|
||||
for item <- collection, filter.(item), do: mapper.(item)
|
||||
end
|
||||
|
||||
def filter_map(collection, filter, mapper) do
|
||||
@@ -671,7 +682,7 @@ defmodule Enum do
|
||||
"""
|
||||
@spec find(t, (element -> any)) :: element | nil
|
||||
@spec find(t, default, (element -> any)) :: element | default
|
||||
def find(collection, ifnone // nil, fun)
|
||||
def find(collection, ifnone \\ nil, fun)
|
||||
|
||||
def find(collection, ifnone, fun) when is_list(collection) do
|
||||
do_find(collection, ifnone, fun)
|
||||
@@ -698,7 +709,7 @@ defmodule Enum do
|
||||
"""
|
||||
@spec find_value(t, (element -> any)) :: any | :nil
|
||||
@spec find_value(t, any, (element -> any)) :: any | :nil
|
||||
def find_value(collection, ifnone // nil, fun)
|
||||
def find_value(collection, ifnone \\ nil, fun)
|
||||
|
||||
def find_value(collection, ifnone, fun) when is_list(collection) do
|
||||
do_find_value(collection, ifnone, fun)
|
||||
@@ -741,20 +752,6 @@ defmodule Enum do
|
||||
end
|
||||
end
|
||||
|
||||
@doc false
|
||||
@spec first(t) :: :nil | element
|
||||
def first(list) when is_list(list) do
|
||||
IO.write "Enum.first/1 is deprecated, please use Enum.at/2 or List.first/1 instead\n#{Exception.format_stacktrace}"
|
||||
List.first(list)
|
||||
end
|
||||
|
||||
def first(collection) do
|
||||
IO.write "Enum.first/1 is deprecated, please use Enum.at/2 instead\n#{Exception.format_stacktrace}"
|
||||
Enumerable.reduce(collection, { :cont, nil }, fn(entry, _) ->
|
||||
{ :halt, entry }
|
||||
end) |> elem(1)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns a new collection appending the result of invoking `fun`
|
||||
on each corresponding item of `collection`.
|
||||
@@ -795,7 +792,7 @@ defmodule Enum do
|
||||
{ [1,2,3], 3 }
|
||||
|
||||
"""
|
||||
@spec flat_map_reduce(t, acc, fun) :: list when
|
||||
@spec flat_map_reduce(t, acc, fun) :: { [any], any } when
|
||||
fun: (element, acc -> { t, acc } | { :halt, acc }),
|
||||
acc: any
|
||||
def flat_map_reduce(collection, acc, fun) do
|
||||
@@ -821,8 +818,10 @@ defmodule Enum do
|
||||
|
||||
iex> Enum.intersperse([1, 2, 3], 0)
|
||||
[1, 0, 2, 0, 3]
|
||||
|
||||
iex> Enum.intersperse([1], 0)
|
||||
[1]
|
||||
|
||||
iex> Enum.intersperse([], 0)
|
||||
[]
|
||||
|
||||
@@ -840,6 +839,70 @@ defmodule Enum do
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Inserts the given enumerable into a collectable.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Enum.into([1, 2], [0])
|
||||
[0, 1, 2]
|
||||
|
||||
iex> Enum.into([a: 1, b: 2], %{})
|
||||
%{a: 1, b: 2}
|
||||
|
||||
"""
|
||||
@spec into(Enumerable.t, Collectable.t) :: Collectable.t
|
||||
def into(collection, list) when is_list(list) do
|
||||
list ++ to_list(collection)
|
||||
end
|
||||
|
||||
def into(collection, %{} = map) when is_list(collection) and (map_size(map) == 0) do
|
||||
:maps.from_list(collection)
|
||||
end
|
||||
|
||||
def into(collection, collectable) do
|
||||
{ initial, fun } = Collectable.into(collectable)
|
||||
into(collection, initial, fun, fn x, acc ->
|
||||
fun.(acc, { :cont, x })
|
||||
end)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Inserts the given enumerable into a collectable
|
||||
according to the transformation function.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Enum.into([2, 3], [3], fn x -> x * 3 end)
|
||||
[3, 6, 9]
|
||||
|
||||
"""
|
||||
@spec into(Enumerable.t, Collectable.t, (term -> term)) :: Collectable.t
|
||||
|
||||
def into(collection, list, transform) when is_list(list) and is_function(transform, 1) do
|
||||
list ++ map(collection, transform)
|
||||
end
|
||||
|
||||
def into(collection, collectable, transform) when is_function(transform, 1) do
|
||||
{ initial, fun } = Collectable.into(collectable)
|
||||
into(collection, initial, fun, fn x, acc ->
|
||||
fun.(acc, { :cont, transform.(x) })
|
||||
end)
|
||||
end
|
||||
|
||||
defp into(collection, initial, fun, callback) do
|
||||
try do
|
||||
reduce(collection, initial, callback)
|
||||
catch
|
||||
kind, reason ->
|
||||
stacktrace = System.stacktrace
|
||||
fun.(initial, :halt)
|
||||
:erlang.raise(kind, reason, stacktrace)
|
||||
else
|
||||
acc -> fun.(acc, :done)
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Joins the given `collection` according to `joiner`.
|
||||
`joiner` can be either a binary or a list and the
|
||||
@@ -854,13 +917,14 @@ defmodule Enum do
|
||||
|
||||
iex> Enum.join([1, 2, 3])
|
||||
"123"
|
||||
|
||||
iex> Enum.join([1, 2, 3], " = ")
|
||||
"1 = 2 = 3"
|
||||
|
||||
"""
|
||||
@spec join(t) :: String.t
|
||||
@spec join(t, String.t) :: String.t
|
||||
def join(collection, joiner // "")
|
||||
def join(collection, joiner \\ "")
|
||||
|
||||
def join(collection, joiner) when is_binary(joiner) do
|
||||
reduce(collection, "", fn
|
||||
@@ -886,7 +950,7 @@ defmodule Enum do
|
||||
"""
|
||||
@spec map(t, (element -> any)) :: list
|
||||
def map(collection, fun) when is_list(collection) do
|
||||
lc item inlist collection, do: fun.(item)
|
||||
for item <- collection, do: fun.(item)
|
||||
end
|
||||
|
||||
def map(collection, fun) do
|
||||
@@ -907,13 +971,14 @@ defmodule Enum do
|
||||
|
||||
iex> Enum.map_join([1, 2, 3], &(&1 * 2))
|
||||
"246"
|
||||
|
||||
iex> Enum.map_join([1, 2, 3], " = ", &(&1 * 2))
|
||||
"2 = 4 = 6"
|
||||
|
||||
"""
|
||||
@spec map_join(t, (element -> any)) :: String.t
|
||||
@spec map_join(t, String.t, (element -> any)) :: String.t
|
||||
def map_join(collection, joiner // "", mapper)
|
||||
def map_join(collection, joiner \\ "", mapper)
|
||||
|
||||
def map_join(collection, joiner, mapper) when is_binary(joiner) do
|
||||
reduce(collection, "", fn
|
||||
@@ -1014,6 +1079,7 @@ defmodule Enum do
|
||||
|
||||
iex> Enum.member?(1..10, 5)
|
||||
true
|
||||
|
||||
iex> Enum.member?([:a, :b, :c], :d)
|
||||
false
|
||||
|
||||
@@ -1025,17 +1091,12 @@ defmodule Enum do
|
||||
|
||||
def member?(collection, value) do
|
||||
case Enumerable.member?(collection, value) do
|
||||
value when is_boolean(value) ->
|
||||
IO.write "Expected #{inspect Enumerable.impl_for(collection)}.member?/2 to return " <>
|
||||
"{ :ok, boolean } if faster than linear, otherwise { :error, __MODULE__ }, " <>
|
||||
"got a boolean\n#{Exception.format_stacktrace}"
|
||||
value
|
||||
{ :ok, value } when is_boolean(value) ->
|
||||
value
|
||||
{ :error, module } ->
|
||||
module.reduce(collection, { :cont, false }, fn
|
||||
^value, _ -> { :halt, true }
|
||||
_, _ -> { :cont, false }
|
||||
v, _ when v === value -> { :halt, true }
|
||||
_, _ -> { :cont, false }
|
||||
end) |> elem(1)
|
||||
end
|
||||
end
|
||||
@@ -1093,6 +1154,22 @@ defmodule Enum do
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns the sum of all values.
|
||||
|
||||
Raises `ArithmeticError` if collection contains a non-numeric value.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Enum.sum([1, 2, 3])
|
||||
6
|
||||
|
||||
"""
|
||||
@spec sum(t) :: number
|
||||
def sum(collection) do
|
||||
reduce(collection, 0, &+/2)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Partitions `collection` into two collections, where the first one contains elements
|
||||
for which `fun` returns a truthy value, and the second one -- for which `fun`
|
||||
@@ -1118,6 +1195,27 @@ defmodule Enum do
|
||||
{ :lists.reverse(acc1), :lists.reverse(acc2) }
|
||||
end
|
||||
|
||||
@doc """
|
||||
Splits `collection` into groups based on `fun`.
|
||||
|
||||
The result is a dict (by default a map) where each key is
|
||||
a group and each value is a list of elements from `collection`
|
||||
for which `fun` returned that group. Ordering is not necessarily
|
||||
preserved.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Enum.group_by(~w{ant buffalo cat dingo}, &String.length/1)
|
||||
%{ 3 => ["cat", "ant"], 7 => ["buffalo"], 5 => ["dingo"] }
|
||||
|
||||
"""
|
||||
@spec group_by(t, (element -> any)) :: HashDict
|
||||
def group_by(collection, dict \\ %{}, fun) do
|
||||
reduce(collection, dict, fn(entry, categories) ->
|
||||
Dict.update(categories, fun.(entry), [entry], &[entry|&1])
|
||||
end)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Invokes `fun` for each element in the collection passing that element and the
|
||||
accumulator `acc` as arguments. `fun`'s return value is stored in `acc`.
|
||||
@@ -1186,7 +1284,7 @@ defmodule Enum do
|
||||
"""
|
||||
@spec reject(t, (element -> as_boolean(term))) :: list
|
||||
def reject(collection, fun) when is_list(collection) do
|
||||
lc item inlist collection, !fun.(item), do: item
|
||||
for item <- collection, !fun.(item), do: item
|
||||
end
|
||||
|
||||
def reject(collection, fun) do
|
||||
@@ -1282,9 +1380,9 @@ defmodule Enum do
|
||||
|
||||
## Examples
|
||||
|
||||
iex(1)> Enum.shuffle([1, 2, 3])
|
||||
iex> Enum.shuffle([1, 2, 3])
|
||||
[3, 2, 1]
|
||||
iex(2)> Enum.shuffle([1, 2, 3])
|
||||
iex> Enum.shuffle([1, 2, 3])
|
||||
[3, 1, 2]
|
||||
|
||||
"""
|
||||
@@ -1375,12 +1473,14 @@ defmodule Enum do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns a sorted list of collection elements. Uses the merge sort algorithm.
|
||||
Sorts the collection according to Elixir's term ordering.
|
||||
|
||||
Uses the merge sort algorithm.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Enum.sort([3, 2, 1])
|
||||
[1,2,3]
|
||||
[1, 2, 3]
|
||||
|
||||
"""
|
||||
@spec sort(t) :: list
|
||||
@@ -1393,14 +1493,27 @@ defmodule Enum do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns a list of collection elements sorted by the given function.
|
||||
Sorts the collection by the given function.
|
||||
|
||||
Uses the merge sort algorithm.
|
||||
This function uses the merge sort algorithm. The given function
|
||||
must return false if the first argument is less than right one.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Enum.sort([1, 2, 3], &(&1 > &2))
|
||||
[3,2,1]
|
||||
[3, 2, 1]
|
||||
|
||||
The sorting algorithm will be stable as long as the given function
|
||||
returns true for values considered equal:
|
||||
|
||||
iex> Enum.sort ["some", "kind", "of", "monster"], &(byte_size(&1) <= byte_size(&2))
|
||||
["of", "some", "kind", "monster"]
|
||||
|
||||
If the function does not return true, the sorting is not stable and
|
||||
the order of equal terms may be shuffled:
|
||||
|
||||
iex> Enum.sort ["some", "kind", "of", "monster"], &(byte_size(&1) < byte_size(&2))
|
||||
["of", "kind", "some", "monster"]
|
||||
|
||||
"""
|
||||
@spec sort(t, (element, element -> boolean)) :: list
|
||||
@@ -1426,12 +1539,16 @@ defmodule Enum do
|
||||
|
||||
iex> Enum.split([1, 2, 3], 2)
|
||||
{ [1,2], [3] }
|
||||
|
||||
iex> Enum.split([1, 2, 3], 10)
|
||||
{ [1,2,3], [] }
|
||||
|
||||
iex> Enum.split([1, 2, 3], 0)
|
||||
{ [], [1,2,3] }
|
||||
|
||||
iex> Enum.split([1, 2, 3], -1)
|
||||
{ [1,2], [3] }
|
||||
|
||||
iex> Enum.split([1, 2, 3], -5)
|
||||
{ [], [1,2,3] }
|
||||
|
||||
@@ -1465,6 +1582,7 @@ defmodule Enum do
|
||||
|
||||
iex> Enum.split_while([1, 2, 3, 4], fn(x) -> x < 3 end)
|
||||
{ [1, 2], [3, 4] }
|
||||
|
||||
"""
|
||||
@spec split_while(t, (element -> as_boolean(term))) :: {list, list}
|
||||
def split_while(collection, fun) when is_list(collection) do
|
||||
@@ -1495,10 +1613,13 @@ defmodule Enum do
|
||||
|
||||
iex> Enum.take([1, 2, 3], 2)
|
||||
[1,2]
|
||||
|
||||
iex> Enum.take([1, 2, 3], 10)
|
||||
[1,2,3]
|
||||
|
||||
iex> Enum.take([1, 2, 3], 0)
|
||||
[]
|
||||
|
||||
iex> Enum.take([1, 2, 3], -1)
|
||||
[3]
|
||||
|
||||
@@ -1577,7 +1698,7 @@ defmodule Enum do
|
||||
|
||||
"""
|
||||
@spec to_list(t) :: [term]
|
||||
def to_list(collection) when is_list collection do
|
||||
def to_list(collection) when is_list(collection) do
|
||||
collection
|
||||
end
|
||||
|
||||
@@ -1585,6 +1706,27 @@ defmodule Enum do
|
||||
reverse(collection) |> :lists.reverse
|
||||
end
|
||||
|
||||
|
||||
@doc """
|
||||
Traverses the given enumerable keeping its shape.
|
||||
|
||||
It also expects the enumerable to implement the `Collectable` protocol.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Enum.traverse(%{a: 1, b: 2}, fn { k, v } -> { k, v * 2 } end)
|
||||
%{a: 2, b: 4}
|
||||
|
||||
"""
|
||||
@spec traverse(Enumerable.t, (term -> term)) :: Collectable.t
|
||||
def traverse(collection, transform) when is_list(collection) do
|
||||
:lists.map(transform, collection)
|
||||
end
|
||||
|
||||
def traverse(collection, transform) do
|
||||
into(collection, Collectable.empty(collection), transform)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Enumerates the collection, removing all duplicated items.
|
||||
|
||||
@@ -1599,7 +1741,7 @@ defmodule Enum do
|
||||
"""
|
||||
@spec uniq(t) :: list
|
||||
@spec uniq(t, (element -> term)) :: list
|
||||
def uniq(collection, fun // fn x -> x end)
|
||||
def uniq(collection, fun \\ fn x -> x end)
|
||||
|
||||
def uniq(collection, fun) when is_list(collection) do
|
||||
do_uniq(collection, [], fun)
|
||||
@@ -2009,6 +2151,29 @@ defimpl Enumerable, for: List do
|
||||
do: { :error, __MODULE__ }
|
||||
end
|
||||
|
||||
defimpl Enumerable, for: Map do
|
||||
def reduce(map, acc, fun) do
|
||||
do_reduce(:maps.to_list(map), acc, fun)
|
||||
end
|
||||
|
||||
defp do_reduce(_, { :halt, acc }, _fun), do: { :halted, acc }
|
||||
defp do_reduce(list, { :suspend, acc }, fun), do: { :suspended, acc, &do_reduce(list, &1, fun) }
|
||||
defp do_reduce([], { :cont, acc }, _fun), do: { :done, acc }
|
||||
defp do_reduce([h|t], { :cont, acc }, fun), do: do_reduce(t, fun.(h, acc), fun)
|
||||
|
||||
def member?(map, { key, value }) do
|
||||
{ :ok, match?({ :ok, ^value }, :maps.find(key, map)) }
|
||||
end
|
||||
|
||||
def member?(_map, _other) do
|
||||
{ :ok, false }
|
||||
end
|
||||
|
||||
def count(map) do
|
||||
{ :ok, map_size(map) }
|
||||
end
|
||||
end
|
||||
|
||||
defimpl Enumerable, for: Function do
|
||||
def reduce(function, acc, fun),
|
||||
do: function.(acc, fun)
|
||||
|
||||
+111
-99
@@ -10,53 +10,66 @@ 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
|
||||
" " <> 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
|
||||
" " <> 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
|
||||
" " <> exception.description
|
||||
end
|
||||
end
|
||||
|
||||
defexception BadFunctionError, [actual: nil] do
|
||||
defexception BadFunctionError, [term: nil] do
|
||||
def message(exception) do
|
||||
"expected a function, got: #{inspect(exception.actual)}"
|
||||
"expected a function, got: #{inspect(exception.term)}"
|
||||
end
|
||||
end
|
||||
|
||||
defexception MatchError, [actual: nil] do
|
||||
defexception BadStructError, [struct: nil, term: nil] do
|
||||
def message(exception) do
|
||||
"no match of right hand side value: #{inspect(exception.actual)}"
|
||||
"expected a struct named #{inspect(exception.struct)}, got: #{inspect(exception.term)}"
|
||||
end
|
||||
end
|
||||
|
||||
defexception CaseClauseError, [actual: nil] do
|
||||
defexception MatchError, [term: nil] do
|
||||
def message(exception) do
|
||||
"no case clause matching: #{inspect(exception.actual)}"
|
||||
"no match of right hand side value: #{inspect(exception.term)}"
|
||||
end
|
||||
end
|
||||
|
||||
defexception TryClauseError, [actual: nil] do
|
||||
defexception CaseClauseError, [term: nil] do
|
||||
def message(exception) do
|
||||
"no try clause matching: #{inspect(exception.actual)}"
|
||||
"no case clause matching: #{inspect(exception.term)}"
|
||||
end
|
||||
end
|
||||
|
||||
defexception TryClauseError, [term: nil] do
|
||||
def message(exception) do
|
||||
"no try clause matching: #{inspect(exception.term)}"
|
||||
end
|
||||
end
|
||||
|
||||
defexception BadArityError, [function: nil, args: nil] do
|
||||
def message(exception) do
|
||||
args = Enum.map_join(exception.args, ", ", &inspect/1)
|
||||
"bad arity error: #{inspect(exception.function)} called with (#{args})"
|
||||
fun = exception.function
|
||||
args = exception.args
|
||||
insp = Enum.map_join(args, ", ", &inspect/1)
|
||||
{ :arity, arity } = :erlang.fun_info(fun, :arity)
|
||||
"#{inspect(fun)} with arity #{arity} called with #{count(length(args), insp)}"
|
||||
end
|
||||
|
||||
defp count(0, _insp), do: "no arguments"
|
||||
defp count(1, insp), do: "1 argument (#{insp})"
|
||||
defp count(x, insp), do: "#{x} arguments (#{insp})"
|
||||
end
|
||||
|
||||
defexception UndefinedFunctionError, [module: nil, function: nil, arity: nil] do
|
||||
@@ -98,9 +111,9 @@ defexception ErlangError, [original: nil] do
|
||||
end
|
||||
end
|
||||
|
||||
defexception KeyError, key: nil do
|
||||
defexception KeyError, key: nil, term: nil do
|
||||
def message(exception) do
|
||||
"key not found: #{inspect exception.key}"
|
||||
"key #{inspect exception.key} not found in: #{inspect exception.term}"
|
||||
end
|
||||
end
|
||||
|
||||
@@ -126,22 +139,30 @@ end
|
||||
|
||||
defmodule Exception do
|
||||
@moduledoc """
|
||||
Convenience functions to work with and pretty print
|
||||
exceptions and stacktraces.
|
||||
Functions to work with and pretty print exceptions.
|
||||
|
||||
Notice that stacktraces in Elixir are updated on errors.
|
||||
For example, at any given moement, `System.stacktrace`
|
||||
will return the stacktrace for the last error that ocurred
|
||||
in the current process.
|
||||
Note that stacktraces in Elixir are updated on throw,
|
||||
errors and exits. For example, at any given moment,
|
||||
`System.stacktrace` will return the stacktrace for the
|
||||
last throw/error/exit that ocurred in the current process.
|
||||
|
||||
That said, many of the functions in this module will
|
||||
automatically calculate the stacktrace based on the caller,
|
||||
when invoked without arguments, changing the value of
|
||||
`System.stacktrace`. If instead you want to format the
|
||||
stacktrace of the latest error, you should instead explicitly
|
||||
pass the `System.stacktrace` as argument.
|
||||
Finally note developers should not rely on the particular
|
||||
format of the `format` functions provided by this module.
|
||||
They may be changed in future releases in order to better
|
||||
suit Elixir's tool chain. In other words, by using the
|
||||
functions in this module it is guarantee you will format
|
||||
exceptions as in the current Elixir version being used.
|
||||
"""
|
||||
|
||||
@type stacktrace :: [stacktrace_entry]
|
||||
|
||||
@type stacktrace_entry ::
|
||||
{ module, function, arity_or_args, location } |
|
||||
{ function, arity_or_args, location }
|
||||
|
||||
@typep arity_or_args :: non_neg_integer | list
|
||||
@typep location :: Keyword.t
|
||||
|
||||
@doc """
|
||||
Normalizes an exception, converting Erlang exceptions
|
||||
to Elixir exceptions.
|
||||
@@ -150,72 +171,77 @@ defmodule Exception do
|
||||
normalizes only `:error`, returning the untouched payload
|
||||
for others.
|
||||
"""
|
||||
def normalize(:error, exception), do: normalize(exception)
|
||||
def normalize(_kind, other), do: other
|
||||
def normalize(:error, exception), do: normalize_error(exception)
|
||||
def normalize(_kind, payload), do: payload
|
||||
|
||||
@doc """
|
||||
Normalizes an exception, converting Erlang exceptions
|
||||
to Elixir exceptions.
|
||||
@doc false
|
||||
def normalize(error) do
|
||||
IO.write :stderr, "Exception.normalize/1 is deprecated, please use Exception.normalize/2 instead\n#{Exception.format_stacktrace}"
|
||||
normalize_error(error)
|
||||
end
|
||||
|
||||
Useful when interfacing Erlang code with Elixir code.
|
||||
"""
|
||||
def normalize(exception) when is_exception(exception) do
|
||||
defp normalize_error(exception) when is_exception(exception) do
|
||||
exception
|
||||
end
|
||||
|
||||
def normalize(:badarg) do
|
||||
defp normalize_error(:badarg) do
|
||||
ArgumentError[]
|
||||
end
|
||||
|
||||
def normalize(:badarith) do
|
||||
defp normalize_error(:badarith) do
|
||||
ArithmeticError[]
|
||||
end
|
||||
|
||||
def normalize(:system_limit) do
|
||||
defp normalize_error(:system_limit) do
|
||||
SystemLimitError[]
|
||||
end
|
||||
|
||||
def normalize({ :badarity, { fun, args } }) do
|
||||
defp normalize_error({ :badarity, { fun, args } }) do
|
||||
BadArityError[function: fun, args: args]
|
||||
end
|
||||
|
||||
def normalize({ :badfun, actual }) do
|
||||
BadFunctionError[actual: actual]
|
||||
defp normalize_error({ :badfun, term }) do
|
||||
BadFunctionError[term: term]
|
||||
end
|
||||
|
||||
def normalize({ :badmatch, actual }) do
|
||||
MatchError[actual: actual]
|
||||
defp normalize_error({ :badstruct, struct, term }) do
|
||||
BadStructError[struct: struct, term: term]
|
||||
end
|
||||
|
||||
def normalize({ :case_clause, actual }) do
|
||||
CaseClauseError[actual: actual]
|
||||
defp normalize_error({ :badmatch, term }) do
|
||||
MatchError[term: term]
|
||||
end
|
||||
|
||||
def normalize({ :try_clause, actual }) do
|
||||
TryClauseError[actual: actual]
|
||||
defp normalize_error({ :case_clause, term }) do
|
||||
CaseClauseError[term: term]
|
||||
end
|
||||
|
||||
def normalize(:undef) do
|
||||
defp normalize_error({ :try_clause, term }) do
|
||||
TryClauseError[term: term]
|
||||
end
|
||||
|
||||
defp normalize_error(:undef) do
|
||||
{ mod, fun, arity } = from_stacktrace(:erlang.get_stacktrace)
|
||||
UndefinedFunctionError[module: mod, function: fun, arity: arity]
|
||||
end
|
||||
|
||||
def normalize(:function_clause) do
|
||||
defp normalize_error(:function_clause) do
|
||||
{ mod, fun, arity } = from_stacktrace(:erlang.get_stacktrace)
|
||||
FunctionClauseError[module: mod, function: fun, arity: arity]
|
||||
end
|
||||
|
||||
def normalize({ :badarg, payload }) do
|
||||
defp normalize_error({ :badarg, payload }) do
|
||||
ArgumentError[message: "argument error: #{inspect(payload)}"]
|
||||
end
|
||||
|
||||
def normalize(other) do
|
||||
defp normalize_error(other) do
|
||||
ErlangError[original: other]
|
||||
end
|
||||
|
||||
@doc """
|
||||
Receives a tuple representing a stacktrace entry and formats it.
|
||||
Receives a stacktrace entry and formats it into a string.
|
||||
"""
|
||||
@spec format_stacktrace_entry(stacktrace_entry) :: String.t
|
||||
def format_stacktrace_entry(entry)
|
||||
|
||||
# From Macro.Env.stacktrace
|
||||
@@ -233,11 +259,11 @@ defmodule Exception do
|
||||
format_location(location) <> "(file)"
|
||||
end
|
||||
|
||||
def format_stacktrace_entry({module, fun, arity, location}) do
|
||||
def format_stacktrace_entry({ module, fun, arity, location }) do
|
||||
format_application(module) <> format_location(location) <> format_mfa(module, fun, arity)
|
||||
end
|
||||
|
||||
def format_stacktrace_entry({fun, arity, location}) do
|
||||
def format_stacktrace_entry({ fun, arity, location }) do
|
||||
format_location(location) <> format_fa(fun, arity)
|
||||
end
|
||||
|
||||
@@ -255,43 +281,18 @@ defmodule Exception do
|
||||
calculates a new stacktrace based on the caller and formats it. As
|
||||
a consequence, the value of `System.stacktrace` is changed.
|
||||
"""
|
||||
def format_stacktrace(trace // nil) do
|
||||
def format_stacktrace(trace \\ nil) do
|
||||
trace = trace || try do
|
||||
throw(:stacktrace)
|
||||
catch
|
||||
:stacktrace -> Enum.drop(:erlang.get_stacktrace, 1)
|
||||
end
|
||||
|
||||
case trace do
|
||||
[] -> "\n"
|
||||
s -> " " <> Enum.map_join(s, "\n ", &format_stacktrace_entry(&1)) <> "\n"
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Formats the caller, i.e. the first entry in the stacktrace.
|
||||
|
||||
A stacktrace must be given as an argument. If not, this function
|
||||
calculates a new stacktrace based on the caller and formats it. As
|
||||
a consequence, the value of `System.stacktrace` is changed.
|
||||
|
||||
Notice that due to tail call optimization, the stacktrace
|
||||
may not report the direct caller of the function.
|
||||
"""
|
||||
def format_caller(trace // nil) do
|
||||
trace = trace || try do
|
||||
throw(:stacktrace)
|
||||
catch
|
||||
:stacktrace -> Enum.drop(:erlang.get_stacktrace, 1)
|
||||
end
|
||||
|
||||
if entry = Enum.at(trace, 1) do
|
||||
format_stacktrace_entry(entry)
|
||||
else
|
||||
"nofile:0: "
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Receives an anonymous function and arity and formats it as
|
||||
shown in stacktraces. The arity may also be a list of arguments.
|
||||
@@ -303,12 +304,7 @@ defmodule Exception do
|
||||
|
||||
"""
|
||||
def format_fa(fun, arity) do
|
||||
if is_list(arity) do
|
||||
inspected = lc x inlist arity, do: inspect(x)
|
||||
"#{inspect fun}(#{Enum.join(inspected, ", ")})"
|
||||
else
|
||||
"#{inspect fun}/#{arity}"
|
||||
end
|
||||
"#{inspect fun}#{format_arity(arity)}"
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -320,27 +316,39 @@ defmodule Exception do
|
||||
|
||||
iex> Exception.format_mfa Foo, :bar, 1
|
||||
"Foo.bar/1"
|
||||
|
||||
iex> Exception.format_mfa Foo, :bar, []
|
||||
"Foo.bar()"
|
||||
|
||||
iex> Exception.format_mfa nil, :bar, []
|
||||
"nil.bar()"
|
||||
|
||||
Anonymous functions are reported as -func/arity-anonfn-count-,
|
||||
where func is the name of the enclosing function. Convert to
|
||||
"anonymous fn in func/arity"
|
||||
"""
|
||||
def format_mfa(module, fun, arity) do
|
||||
def format_mfa(module, fun, arity) when is_atom(fun) do
|
||||
fun =
|
||||
case inspect(fun) do
|
||||
<< ?:, erl :: binary >> -> erl
|
||||
elixir -> elixir
|
||||
":" <> fun -> fun
|
||||
fun -> fun
|
||||
end
|
||||
|
||||
if is_list(arity) do
|
||||
inspected = lc x inlist arity, do: inspect(x)
|
||||
"#{inspect module}.#{fun}(#{Enum.join(inspected, ", ")})"
|
||||
else
|
||||
"#{inspect module}.#{fun}/#{arity}"
|
||||
case match?("\"-" <> _, fun) and String.split(fun, "-") do
|
||||
[ "\"", outer_fun, "fun", _count, "\"" ] ->
|
||||
"anonymous fn#{format_arity(arity)} in #{inspect module}.#{outer_fun}"
|
||||
_ ->
|
||||
"#{inspect module}.#{fun}#{format_arity(arity)}"
|
||||
end
|
||||
end
|
||||
|
||||
defp format_arity(arity) when is_list(arity) do
|
||||
inspected = for x <- arity, do: inspect(x)
|
||||
"(#{Enum.join(inspected, ", ")})"
|
||||
end
|
||||
|
||||
defp format_arity(arity), do: "/#{arity}"
|
||||
|
||||
@doc """
|
||||
Formats the given file and line as shown in stacktraces.
|
||||
If any of the values are nil, they are omitted.
|
||||
@@ -348,21 +356,25 @@ defmodule Exception do
|
||||
## Examples
|
||||
|
||||
iex> Exception.format_file_line("foo", 1)
|
||||
"foo:1: "
|
||||
"foo:1:"
|
||||
|
||||
iex> Exception.format_file_line("foo", nil)
|
||||
"foo: "
|
||||
"foo:"
|
||||
|
||||
iex> Exception.format_file_line(nil, nil)
|
||||
""
|
||||
|
||||
"""
|
||||
def format_file_line(file, line) do
|
||||
format_file_line(file, line, "")
|
||||
end
|
||||
|
||||
defp format_file_line(file, line, suffix) do
|
||||
if file do
|
||||
if line && line != 0 do
|
||||
"#{file}:#{line}: "
|
||||
"#{file}:#{line}:#{suffix}"
|
||||
else
|
||||
"#{file}: "
|
||||
"#{file}:#{suffix}"
|
||||
end
|
||||
else
|
||||
""
|
||||
@@ -370,7 +382,7 @@ defmodule Exception do
|
||||
end
|
||||
|
||||
defp format_location(opts) do
|
||||
format_file_line Keyword.get(opts, :file), Keyword.get(opts, :line)
|
||||
format_file_line Keyword.get(opts, :file), Keyword.get(opts, :line), " "
|
||||
end
|
||||
|
||||
defp from_stacktrace([{ module, function, args, _ }|_]) when is_list(args) do
|
||||
|
||||
+199
-131
@@ -1,3 +1,5 @@
|
||||
require Record
|
||||
|
||||
defrecord File.Stat, Record.extract(:file_info, from_lib: "kernel/include/file.hrl") do
|
||||
@moduledoc """
|
||||
A record responsible to hold file information. Its fields are:
|
||||
@@ -31,18 +33,99 @@ defrecord File.Stat, Record.extract(:file_info, from_lib: "kernel/include/file.h
|
||||
"""
|
||||
end
|
||||
|
||||
defmodule File do
|
||||
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.
|
||||
|
||||
Some of those functions are low-level, allowing the user
|
||||
to interact with the file or IO devices, like `open/2`,
|
||||
`copy/3` and others. This module also provides higher
|
||||
level functions that works with filenames and have their naming
|
||||
level functions that work with filenames and have their naming
|
||||
based on UNIX variants. For example, one can copy a file
|
||||
via `cp/3` and remove files and directories recursively
|
||||
via `rm_rf/1`
|
||||
|
||||
## Encoding
|
||||
|
||||
In order to write and read files, one must use the functions
|
||||
in the `IO` module. By default, a file is opened in binary mode
|
||||
which requires the functions `IO.binread/2` and `IO.binwrite/2`
|
||||
@@ -51,6 +134,8 @@ defmodule File do
|
||||
`IO.write/2` functions must be used as they are responsible for
|
||||
doing the proper conversions and data guarantees.
|
||||
|
||||
## API
|
||||
|
||||
Most of the functions in this module return `:ok` or
|
||||
`{ :ok, result }` in case of success, `{ :error, reason }`
|
||||
otherwise. Those function are also followed by a variant
|
||||
@@ -74,6 +159,23 @@ defmodule File do
|
||||
to react if the file does not exist. The latter should be used
|
||||
when the developer expects his software to fail in case the
|
||||
file cannot be read (i.e. it is literally an exception).
|
||||
|
||||
## Processes and raw files
|
||||
|
||||
Every time a file is opened, Elixir spawns a new process. Writing
|
||||
to a file is equivalent to sending messages to that process that
|
||||
writes to the file descriptor.
|
||||
|
||||
This means files can be passed between nodes and message passing
|
||||
guarantees they can write to the same file in a network.
|
||||
|
||||
However, you may not always want to pay the price for this abstraction.
|
||||
In such cases, a file can be opened in `:raw` mode. The options `:read_ahead`
|
||||
and `:delayed_write` are also useful when operating large files or
|
||||
working with files in tight loops.
|
||||
|
||||
Check http://www.erlang.org/doc/man/file.html#open-2 for more information
|
||||
about such options and other performance considerations.
|
||||
"""
|
||||
|
||||
alias :file, as: F
|
||||
@@ -209,7 +311,7 @@ defmodule File do
|
||||
@doc """
|
||||
Returns information about the `path`. If it exists, it
|
||||
returns a `{ :ok, info }` tuple, where info is a
|
||||
`File.Info` record. Retuns `{ :error, reason }` with
|
||||
`File.Stat` record. Returns `{ :error, reason }` with
|
||||
the same reasons as `read/1` if a failure occurs.
|
||||
|
||||
## Options
|
||||
@@ -220,7 +322,7 @@ defmodule File do
|
||||
Default is `:local`.
|
||||
|
||||
"""
|
||||
def stat(path, opts // []) do
|
||||
def stat(path, opts \\ []) do
|
||||
case F.read_file_info(path, opts) do
|
||||
{:ok, fileinfo} ->
|
||||
{:ok, set_elem(fileinfo, 0, File.Stat) }
|
||||
@@ -233,7 +335,7 @@ defmodule File do
|
||||
Same as `stat/2` but returns the `File.Stat` directly and
|
||||
throws `File.Error` if an error is returned.
|
||||
"""
|
||||
def stat!(path, opts // []) do
|
||||
def stat!(path, opts \\ []) do
|
||||
case stat(path, opts) do
|
||||
{:ok, info} -> info
|
||||
{:error, reason} ->
|
||||
@@ -245,7 +347,7 @@ defmodule File do
|
||||
Writes the given `File.Stat` back to the filesystem at the given
|
||||
path. Returns `:ok` or `{ :error, reason }`.
|
||||
"""
|
||||
def write_stat(path, File.Stat[] = stat, opts // []) do
|
||||
def write_stat(path, File.Stat[] = stat, opts \\ []) do
|
||||
F.write_file_info(path, set_elem(stat, 0, :file_info), opts)
|
||||
end
|
||||
|
||||
@@ -253,7 +355,7 @@ defmodule File do
|
||||
Same as `write_stat/3` but raises an exception if it fails.
|
||||
Returns `:ok` otherwise.
|
||||
"""
|
||||
def write_stat!(path, File.Stat[] = stat, opts // []) do
|
||||
def write_stat!(path, File.Stat[] = stat, opts \\ []) do
|
||||
case write_stat(path, stat, opts) do
|
||||
:ok -> :ok
|
||||
{ :error, reason } ->
|
||||
@@ -265,7 +367,7 @@ defmodule File do
|
||||
Updates modification time (mtime) and access time (atime) of
|
||||
the given file. File is created if it doesn’t exist.
|
||||
"""
|
||||
def touch(path, time // :calendar.local_time) do
|
||||
def touch(path, time \\ :calendar.local_time) do
|
||||
case F.change_time(path, time) do
|
||||
{ :error, :enoent } ->
|
||||
write(path, "")
|
||||
@@ -279,7 +381,7 @@ defmodule File do
|
||||
Same as `touch/2` but raises an exception if it fails.
|
||||
Returns `:ok` otherwise.
|
||||
"""
|
||||
def touch!(path, time // :calendar.local_time) do
|
||||
def touch!(path, time \\ :calendar.local_time) do
|
||||
case touch(path, time) do
|
||||
:ok -> :ok
|
||||
{ :error, reason } ->
|
||||
@@ -309,7 +411,7 @@ defmodule File do
|
||||
Typical error reasons are the same as in `open/2`,
|
||||
`read/1` and `write/3`.
|
||||
"""
|
||||
def copy(source, destination, bytes_count // :infinity) do
|
||||
def copy(source, destination, bytes_count \\ :infinity) do
|
||||
F.copy(source, destination, bytes_count)
|
||||
end
|
||||
|
||||
@@ -317,7 +419,7 @@ defmodule File do
|
||||
The same as `copy/3` but raises an `File.CopyError` if it fails.
|
||||
Returns the `bytes_copied` otherwise.
|
||||
"""
|
||||
def copy!(source, destination, bytes_count // :infinity) do
|
||||
def copy!(source, destination, bytes_count \\ :infinity) do
|
||||
case copy(source, destination, bytes_count) do
|
||||
{ :ok, bytes_count } -> bytes_count
|
||||
{ :error, reason } ->
|
||||
@@ -329,11 +431,6 @@ defmodule File do
|
||||
@doc """
|
||||
Copies the contents in `source` to `destination` preserving its mode.
|
||||
|
||||
Similar to the command `cp` in Unix systems, this function
|
||||
behaves differently depending if `destination` is a directory
|
||||
or not. In particular, if `destination` is a directory, it
|
||||
copies the contents of `source` to `destination/source`.
|
||||
|
||||
If a file already exists in the destination, it invokes a
|
||||
callback which should return `true` if the existing file
|
||||
should be overwritten, `false` otherwise. It defaults to return `true`.
|
||||
@@ -344,17 +441,14 @@ defmodule File do
|
||||
If you want to copy contents from an io device to another device
|
||||
or do a straight copy from a source to a destination without
|
||||
preserving modes, check `copy/3` instead.
|
||||
"""
|
||||
def cp(source, destination, callback // fn(_, _) -> true end) do
|
||||
output =
|
||||
if dir?(destination) do
|
||||
mkdir(destination)
|
||||
FN.join(destination, FN.basename(source))
|
||||
else
|
||||
destination
|
||||
end
|
||||
|
||||
case do_cp_file(source, output, callback, []) do
|
||||
Note: The command `cp` in Unix systems behaves differently depending
|
||||
if `destination` is an existing directory or not. We have chosen to
|
||||
explicitly disallow this behaviour. If destination is a directory, an
|
||||
error will be returned.
|
||||
"""
|
||||
def cp(source, destination, callback \\ fn(_, _) -> true end) do
|
||||
case do_cp_file(source, destination, callback, []) do
|
||||
{ :error, reason, _ } -> { :error, reason }
|
||||
_ -> :ok
|
||||
end
|
||||
@@ -364,7 +458,7 @@ defmodule File do
|
||||
The same as `cp/3`, but raises `File.CopyError` if it fails.
|
||||
Returns the list of copied files otherwise.
|
||||
"""
|
||||
def cp!(source, destination, callback // fn(_, _) -> true end) do
|
||||
def cp!(source, destination, callback \\ fn(_, _) -> true end) do
|
||||
case cp(source, destination, callback) do
|
||||
:ok -> :ok
|
||||
{ :error, reason } ->
|
||||
@@ -374,16 +468,12 @@ defmodule File do
|
||||
end
|
||||
end
|
||||
|
||||
@doc %S"""
|
||||
@doc ~S"""
|
||||
Copies the contents in source to destination.
|
||||
Similar to the command `cp -r` in Unix systems,
|
||||
this function behaves differently depending
|
||||
if `source` and `destination` are a file or a directory.
|
||||
|
||||
If both are files, it simply copies `source` to
|
||||
`destination`. However, if `destination` is a directory,
|
||||
it copies the contents of `source` to `destination/source`
|
||||
recursively.
|
||||
If the source is a file, it copies `source` to
|
||||
`destination`. If the source is a directory, it copies
|
||||
the contents inside source into the destination.
|
||||
|
||||
If a file already exists in the destination,
|
||||
it invokes a callback which should return
|
||||
@@ -403,33 +493,26 @@ defmodule File do
|
||||
success with all files and directories copied in no
|
||||
specific order, `{ :error, reason }` otherwise.
|
||||
|
||||
Note: The command `cp` in Unix systems behaves differently
|
||||
depending if `destination` is an existing directory or not.
|
||||
We have chosen to explicitly disallow this behaviour.
|
||||
|
||||
## Examples
|
||||
|
||||
# Copies "a.txt" to "tmp/a.txt"
|
||||
File.cp_r "a.txt", "tmp"
|
||||
|
||||
# Copies all files in "samples" to "tmp/samples"
|
||||
File.cp_r "samples", "tmp"
|
||||
# Copies "a.txt" to "tmp"
|
||||
File.cp_r "a.txt", "tmp.txt"
|
||||
|
||||
# Copies all files in "samples" to "tmp"
|
||||
File.cp_r "samples/.", "tmp"
|
||||
File.cp_r "samples", "tmp"
|
||||
|
||||
# Same as before, but asks the user how to proceed in case of conflicts
|
||||
File.cp_r "samples/.", "tmp", fn(source, destination) ->
|
||||
File.cp_r "samples", "tmp", fn(source, destination) ->
|
||||
IO.gets("Overwriting #{destination} by #{source}. Type y to confirm.") == "y"
|
||||
end
|
||||
|
||||
"""
|
||||
def cp_r(source, destination, callback // fn(_, _) -> true end) when is_function(callback) do
|
||||
output =
|
||||
if dir?(destination) || dir?(source) do
|
||||
mkdir(destination)
|
||||
FN.join(destination, FN.basename(source))
|
||||
else
|
||||
destination
|
||||
end
|
||||
|
||||
case do_cp_r(source, output, callback, []) do
|
||||
def cp_r(source, destination, callback \\ fn(_, _) -> true end) when is_function(callback) do
|
||||
case do_cp_r(source, destination, callback, []) do
|
||||
{ :error, _, _ } = error -> error
|
||||
res -> { :ok, res }
|
||||
end
|
||||
@@ -439,7 +522,7 @@ defmodule File do
|
||||
The same as `cp_r/3`, but raises `File.CopyError` if it fails.
|
||||
Returns the list of copied files otherwise.
|
||||
"""
|
||||
def cp_r!(source, destination, callback // fn(_, _) -> true end) do
|
||||
def cp_r!(source, destination, callback \\ fn(_, _) -> true end) do
|
||||
case cp_r(source, destination, callback) do
|
||||
{ :ok, files } -> files
|
||||
{ :error, reason, file } ->
|
||||
@@ -532,9 +615,17 @@ defmodule File do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Writes `content` to the file `path`. The file is created if it
|
||||
does not exist. If it exists, the previous contents are overwritten.
|
||||
Returns `:ok` if successful, or `{:error, reason}` if an error occurs.
|
||||
Writes `content` to the file `path`.
|
||||
|
||||
The file is created if it does not exist. If it exists, the previous
|
||||
contents are overwritten. Returns `:ok` if successful, or `{:error, reason}`
|
||||
if an error occurs.
|
||||
|
||||
**Warning:** Every time this function is invoked, a file descriptor is opened
|
||||
and a new process is spawned to write to the file. For this reason, if you are
|
||||
doing multiple writes in a loop, opening the file via `File.open/2` and using
|
||||
the functions in `IO` to write to the file will yield much better performance
|
||||
then calling this function multiple times.
|
||||
|
||||
Typical error reasons are:
|
||||
|
||||
@@ -545,16 +636,17 @@ defmodule File do
|
||||
* :eacces - Missing permission for writing the file or searching one of the parent directories.
|
||||
* :eisdir - The named file is a directory.
|
||||
|
||||
Check `File.open/2` for existing modes.
|
||||
The writing is automatically done in `:raw` mode. Check
|
||||
`File.open/2` for other available options.
|
||||
"""
|
||||
def write(path, content, modes // []) do
|
||||
F.write_file(path, content, modes)
|
||||
def write(path, content, modes \\ []) do
|
||||
F.write_file(path, content, [:raw|modes])
|
||||
end
|
||||
|
||||
@doc """
|
||||
Same as `write/3` but raises an exception if it fails, returns `:ok` otherwise.
|
||||
"""
|
||||
def write!(path, content, modes // []) do
|
||||
def write!(path, content, modes \\ []) do
|
||||
case F.write_file(path, content, modes) do
|
||||
:ok -> :ok
|
||||
{ :error, reason } ->
|
||||
@@ -729,7 +821,7 @@ defmodule File do
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
@doc ~S"""
|
||||
Opens the given `path` according to the given list of modes.
|
||||
|
||||
In order to write and read files, one must use the functions
|
||||
@@ -752,7 +844,7 @@ defmodule File do
|
||||
* `:exclusive` - The file, when opened for writing, is created if it does not exist.
|
||||
If the file exists, open will return { :error, :eexist }.
|
||||
|
||||
* `:charlist` - When this term is given, read operations on the file will return char lists rather than binaries;
|
||||
* `:char_list` - When this term is given, read operations on the file will return char lists rather than binaries;
|
||||
|
||||
* `: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.
|
||||
@@ -765,10 +857,8 @@ defmodule File do
|
||||
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.
|
||||
|
||||
If a function is given two modes (instead of a list), it dispatches to `open/3`.
|
||||
|
||||
Check http://www.erlang.org/doc/man/file.html#open-2 for more information about
|
||||
other options like `read_ahead` and `delayed_write`.
|
||||
other options like `:read_ahead` and `:delayed_write`.
|
||||
|
||||
This function returns:
|
||||
|
||||
@@ -788,7 +878,7 @@ defmodule File do
|
||||
File.close(file)
|
||||
|
||||
"""
|
||||
def open(path, modes // [])
|
||||
def open(path, modes \\ [])
|
||||
|
||||
def open(path, modes) when is_list(modes) do
|
||||
F.open(path, open_defaults(modes, true))
|
||||
@@ -808,7 +898,7 @@ defmodule File do
|
||||
It returns `{ :ok, function_result }` in case of success,
|
||||
`{ :error, reason }` otherwise.
|
||||
|
||||
Do not use this function with :delayed_write option
|
||||
Do not use this function with `:delayed_write` option
|
||||
since automatically closing the file may fail
|
||||
(as writes are delayed).
|
||||
|
||||
@@ -835,7 +925,7 @@ defmodule File do
|
||||
Same as `open/2` but raises an error if file could not be opened.
|
||||
Returns the `io_device` otherwise.
|
||||
"""
|
||||
def open!(path, modes // []) do
|
||||
def open!(path, modes \\ []) do
|
||||
case open(path, modes) do
|
||||
{ :ok, device } -> device
|
||||
{ :error, reason } ->
|
||||
@@ -922,7 +1012,7 @@ defmodule File do
|
||||
It returns `{ :ok, [files] }` in case of success,
|
||||
`{ :error, reason }` otherwise.
|
||||
"""
|
||||
def ls(path // ".") do
|
||||
def ls(path \\ ".") do
|
||||
case F.list_dir(path) do
|
||||
{ :ok, file_list } -> { :ok, Enum.map(file_list, &to_string(&1)) }
|
||||
{ :error, _ } = error -> error
|
||||
@@ -933,7 +1023,7 @@ defmodule File do
|
||||
The same as `ls/1` but raises `File.Error`
|
||||
in case of an error.
|
||||
"""
|
||||
def ls!(dir // ".") do
|
||||
def ls!(dir \\ ".") do
|
||||
case ls(dir) do
|
||||
{ :ok, value } -> value
|
||||
{ :error, reason } ->
|
||||
@@ -954,74 +1044,46 @@ defmodule File do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Opens the file at the given `path` with the given `modes` and
|
||||
returns a stream for each `:line` (default) or for a given number
|
||||
of bytes given by `line_or_bytes`.
|
||||
Returns a `File.Stream` for the given `path` with the given `modes`.
|
||||
|
||||
The returned stream will fail for the same reasons as
|
||||
`File.open!/2`. Note that the file is opened only and every time
|
||||
streaming begins.
|
||||
The stream implements both `Enumerable` and `Collectable` protocols,
|
||||
which means it can be used both for read and write.
|
||||
|
||||
Note that stream by default uses `IO.binread/2` unless
|
||||
the file is opened with an encoding, then the slower `IO.read/2`
|
||||
is used to do the proper data conversion and guarantees.
|
||||
The `line_or_byte` argument configures how the file is read when
|
||||
streaming, by `:line` (default) or by a given number of bytes.
|
||||
|
||||
Operating the stream can fail on open for the same reasons as
|
||||
`File.open!/2`. Note that the file is automatically opened only and
|
||||
every time streaming begins. There is no need to pass `:read` and
|
||||
`:write` modes, as those are automatically set by Elixir.
|
||||
|
||||
## Raw files
|
||||
|
||||
Since Elixir controls when the streamed file is opened, the underlying
|
||||
device cannot be shared and as such it is convenient to open the file
|
||||
in raw mode for performance reasons. Therefore, Elixir **will** open
|
||||
streams in `:raw` mode with the `:read_ahead` option unless an encoding
|
||||
is specified.
|
||||
|
||||
One may also consider passing the `:delayed_write` option if the stream
|
||||
is meant to be written to under a tight loop.
|
||||
"""
|
||||
def stream!(path, modes // [], line_or_bytes // :line) do
|
||||
def stream!(path, modes \\ [], line_or_bytes \\ :line) do
|
||||
modes = open_defaults(modes, true)
|
||||
bin = nil? List.keyfind(modes, :encoding, 0)
|
||||
raw = :lists.keyfind(:encoding, 1, modes) == false
|
||||
|
||||
start_fun =
|
||||
fn ->
|
||||
case F.open(path, modes) do
|
||||
{ :ok, device } -> device
|
||||
{ :error, reason } ->
|
||||
raise File.Error, reason: reason, action: "stream", path: to_string(path)
|
||||
modes =
|
||||
if raw do
|
||||
if :lists.keyfind(:read_ahead, 1, modes) do
|
||||
[:raw|modes]
|
||||
else
|
||||
[:raw, :read_ahead|modes]
|
||||
end
|
||||
else
|
||||
modes
|
||||
end
|
||||
|
||||
next_fun =
|
||||
case bin do
|
||||
true -> &IO.do_binstream(&1, line_or_bytes)
|
||||
false -> &IO.do_stream(&1, line_or_bytes)
|
||||
end
|
||||
|
||||
Stream.resource(start_fun, next_fun, &F.close/1)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Receives a stream and returns a new stream that will open the file
|
||||
at the given `path` for write with the extra `modes` and write
|
||||
each value to the file.
|
||||
|
||||
The returned stream will fail for the same reasons as
|
||||
`File.open!/2`. Note that the file is opened only and every time
|
||||
streaming begins.
|
||||
|
||||
Note that stream by default uses `IO.binwrite/2` unless
|
||||
the file is opened with an encoding, then the slower `IO.write/2`
|
||||
is used to do the proper data conversion and guarantees.
|
||||
"""
|
||||
def stream_to!(stream, path, modes // []) do
|
||||
modes = open_defaults([:write|List.delete(modes, :write)], true)
|
||||
bin = nil? List.keyfind(modes, :encoding, 0)
|
||||
|
||||
fn acc, f ->
|
||||
case F.open(path, modes) do
|
||||
{ :ok, device } ->
|
||||
each =
|
||||
case bin do
|
||||
true -> &IO.binwrite(device, &1)
|
||||
false -> &IO.write(device, &1)
|
||||
end
|
||||
|
||||
stream
|
||||
|> Stream.each(each)
|
||||
|> Stream.after(fn -> F.close(device) end)
|
||||
|> Enumerable.Stream.Lazy.reduce(acc, f)
|
||||
{ :error, reason } ->
|
||||
raise File.Error, reason: reason, action: "stream_to", path: to_string(path)
|
||||
end
|
||||
end
|
||||
%File.Stream{ path: to_string(path), modes: modes, raw: raw, line_or_bytes: line_or_bytes }
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -1086,7 +1148,9 @@ defmodule File do
|
||||
|
||||
## Helpers
|
||||
|
||||
defp open_defaults([:charlist|t], _add_binary) do
|
||||
@read_ahead 64*1024
|
||||
|
||||
defp open_defaults([:char_list|t], _add_binary) do
|
||||
open_defaults(t, false)
|
||||
end
|
||||
|
||||
@@ -1094,6 +1158,10 @@ defmodule File do
|
||||
open_defaults([{ :encoding, :utf8 }|t], add_binary)
|
||||
end
|
||||
|
||||
defp open_defaults([:read_ahead|t], add_binary) do
|
||||
open_defaults([{ :read_ahead, @read_ahead }|t], add_binary)
|
||||
end
|
||||
|
||||
defp open_defaults([h|t], add_binary) do
|
||||
[h|open_defaults(t, add_binary)]
|
||||
end
|
||||
|
||||
+14
-4
@@ -13,10 +13,13 @@ defmodule Float do
|
||||
|
||||
iex> Float.parse("34")
|
||||
{34.0,""}
|
||||
|
||||
iex> Float.parse("34.25")
|
||||
{34.25,""}
|
||||
|
||||
iex> Float.parse("56.5xyz")
|
||||
{56.5,"xyz"}
|
||||
|
||||
iex> Float.parse("pi")
|
||||
:error
|
||||
|
||||
@@ -72,7 +75,7 @@ defmodule Float do
|
||||
{ floatify(int, float, decimal), bitstring }
|
||||
end
|
||||
|
||||
defp floatify(int, float, decimal, exponential // 0) do
|
||||
defp floatify(int, float, decimal, exponential \\ 0) do
|
||||
multiplier = if int < 0, do: -1.0, else: 1.0
|
||||
|
||||
# Try to ensure the minimum amount of rounding errors
|
||||
@@ -94,14 +97,16 @@ defmodule Float do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Round a float to the largest integer less than or equal to `num`
|
||||
Rounds a float to the largest integer less than or equal to `num`.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Float.floor(34)
|
||||
34
|
||||
|
||||
iex> Float.floor(34.25)
|
||||
34
|
||||
|
||||
iex> Float.floor(-56.5)
|
||||
-57
|
||||
|
||||
@@ -117,14 +122,16 @@ defmodule Float do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Round a float to the largest integer greater than or equal to `num`
|
||||
Rounds a float to the largest integer greater than or equal to `num`.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Float.ceil(34)
|
||||
34
|
||||
|
||||
iex> Float.ceil(34.25)
|
||||
35
|
||||
|
||||
iex> Float.ceil(-56.5)
|
||||
-56
|
||||
|
||||
@@ -147,16 +154,19 @@ defmodule Float do
|
||||
|
||||
iex> Float.round(5.5674, 3)
|
||||
5.567
|
||||
|
||||
iex> Float.round(5.5675, 3)
|
||||
5.568
|
||||
|
||||
iex> Float.round(-5.5674, 3)
|
||||
-5.567
|
||||
|
||||
iex> Float.round(-5.5675, 3)
|
||||
-5.568
|
||||
|
||||
"""
|
||||
@spec round(float, integer) :: float
|
||||
def round(number, precision) when is_float(number) and is_integer(precision) and precision in 0..15 do
|
||||
Kernel.round(number * :math.pow(10, precision)) / :math.pow(10, precision)
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
@@ -1,200 +0,0 @@
|
||||
defmodule GenFSM.Behaviour do
|
||||
@moduledoc """
|
||||
This module is a convenience for defining GenFSM callbacks in Elixir.
|
||||
|
||||
A finite state machine (FSM) is responsible for reacting to events received;
|
||||
GenFSM is an OTP behaviour that encapsulates common FSM
|
||||
functionalities.
|
||||
|
||||
## Example
|
||||
|
||||
Below is an example of a GenFSM that runs a very simple minded
|
||||
coffee vending machine (CVM). The CVM treats all coins the same. If
|
||||
you press the request button then the CVM will brew coffee, if you
|
||||
have paid enough coins; if not, it will wait until you have inserted
|
||||
enough coins and then it will instantly brew the coffee, since you
|
||||
had already pressed the request button. As we told you - a very
|
||||
simple minded CVM! And greedy too. If you insert more coins than you
|
||||
need it will gladly eat them until you press the request button.
|
||||
|
||||
We will leave it to the service-minded reader to improve the way the CVM
|
||||
works - we hereby declare a full disclaimer for any lawsuits that the
|
||||
behaviour of the CVM in its original state might encur.
|
||||
|
||||
defmodule MyFsm do
|
||||
use GenFSM.Behaviour
|
||||
|
||||
# keeping track of what is going on inside the CVM.
|
||||
# 3 is the target price for a cup of coffee
|
||||
defrecord StateData, coins: 0, price: 3
|
||||
|
||||
# API functions
|
||||
|
||||
def start_link() do
|
||||
:gen_fsm.start_link({:local, :cvm}, __MODULE__, [], [])
|
||||
end
|
||||
|
||||
def insert_coin() do
|
||||
:gen_fsm.send_event(:cvm, :coin)
|
||||
end
|
||||
|
||||
def request_coffee() do
|
||||
:gen_fsm.send_event(:cvm, :request_coffee)
|
||||
end
|
||||
|
||||
# Callbacks
|
||||
|
||||
def init(_args) do
|
||||
{ :ok, :short_paid, StateData.new }
|
||||
end
|
||||
|
||||
def short_paid(:coin, state_data = StateData[coins: c, price: p]) when c + 1 < p do
|
||||
{ :next_state, :short_paid, state_data.coins(c + 1) }
|
||||
end
|
||||
|
||||
def short_paid(:coin, state_data) do
|
||||
{ :next_state, :paid_in_full, &state_data.update_coins(&1 + 1) }
|
||||
end
|
||||
|
||||
def short_paid(:request_coffee, state_data) do
|
||||
{ :next_state, :requested_short_paid, state_data }
|
||||
end
|
||||
|
||||
def requested_short_paid(:request_coffee, state_data) do
|
||||
{ :next_state, :requested_short_paid, state_data }
|
||||
end
|
||||
|
||||
def requested_short_paid(:coin, state_data=StateData[coins: c, price: p]) when c+1 < p do
|
||||
{ :next_state, :requested_short_paid, state_data.coins(c + 1) }
|
||||
end
|
||||
|
||||
def requested_short_paid(:coin, _state_data) do
|
||||
IO.puts "Here's your coffee!"
|
||||
{ :next_state, :short_paid, StateData.new }
|
||||
end
|
||||
|
||||
def paid_in_full(:coin, state_data) do
|
||||
{ :next_state, :paid_in_full, &state_data.update_coins(&1 + 1) }
|
||||
end
|
||||
|
||||
def paid_in_full(:request_coffee, _state_data) do
|
||||
IO.puts "Here's your coffee!"
|
||||
{ :next_state, :short_paid, StateData.new }
|
||||
end
|
||||
end
|
||||
|
||||
{ :ok, _pid } = MyFsm.start_link()
|
||||
|
||||
MyFsm.insert_coin
|
||||
#=> :ok
|
||||
MyFsm.insert_coin
|
||||
#=> :ok
|
||||
|
||||
MyFsm.request_coffee
|
||||
#=> :ok
|
||||
|
||||
MyFsm.insert_coin
|
||||
#=> :ok
|
||||
#=> Here's your coffee!
|
||||
|
||||
Notice we never call the GenFSM callbacks directly; they are called by
|
||||
OTP whenever we interact with the server throught the API. `send_event` is
|
||||
asynchronous, whereas `sync_send_event` is synchronous. For
|
||||
a GenFSM, the different values a callback can return depend
|
||||
on the type of callback.
|
||||
|
||||
State handling returns for `send_event` callbacks:
|
||||
|
||||
{ :next_state, next_state_name, new_state_data }
|
||||
{ :next_state, next_state_name, new_state_data, timeout }
|
||||
{ :next_state, next_state_name, new_state_data, :hibernate }
|
||||
{ :stop, reason, new_state_data }
|
||||
|
||||
State handling returns for `sync_send_event` callbacks:
|
||||
|
||||
{ :reply, reply, next_state_name, new_state_data }
|
||||
{ :reply, reply, next_state_name, new_state_data, timeout }
|
||||
{ :reply, reply, next_state_name, new_state_data, :hibernate }
|
||||
{ :next_state, next_state_name, new_state_data }
|
||||
{ :next_state, next_state_name, new_state_data, timeout }
|
||||
{ :next_state, next_state_name, new_state_data, :hibernate }
|
||||
{ :stop, reason, reply, new_state_data }
|
||||
{ :stop, reason, new_state_date }
|
||||
|
||||
There are 6 callbacks required to be implemented in a GenFsm plus 1
|
||||
or 2 for each state. The `GenFSM.Behaviour` module defines
|
||||
`handle_sync_event`, `handle_info`, `terminate` and `code_change`
|
||||
for you. The list of callbacks are:
|
||||
|
||||
* `init(args)` - invoked when the FSM is started;
|
||||
* `handle_sync_event(event, from, state_name, state_data)` - invoked to
|
||||
handle `sync_send_all_state_event` messages;
|
||||
* `handle_event(event, state_name, state_data)` - invoked to handle
|
||||
`send_all_state_event` messages;
|
||||
* `handle_info(msg, state_name, state_data)` - handle all other
|
||||
messages which are normally received by processes;
|
||||
* `terminate(reason, state_name, state_data)` - called when the FSM
|
||||
is about to terminate, useful for cleaning up;
|
||||
* `code_change(old_vsn, state, extra)` - called when the application
|
||||
code is being upgraded live (hot code swap);
|
||||
|
||||
Unlike `GenServer` and `GenEvent`, the callback `init/1` is not
|
||||
implemented by default, as it requires the next state to be returned.
|
||||
|
||||
For each state you need to define either or both of these:
|
||||
|
||||
* `state_name(event, state_data)` - invoked to handle
|
||||
`send_event` messages;
|
||||
* `state_name(event, from, state_data)`- invoked to handle
|
||||
`sync_send_event` messages;
|
||||
|
||||
If you send asynchronous events you only need to implement the
|
||||
`state_name/2` variant and vice-versa for synchronous events and
|
||||
`state_name/3`. Keep in mind that if you mix `send_event` and
|
||||
`sync_send_event` the best thing to do is to implement both
|
||||
callbacks for all states.
|
||||
|
||||
Starting and sending messages to the GenFSM is done via Erlang's
|
||||
`:gen_fsm` module. For more information, please refer to the
|
||||
following:
|
||||
|
||||
* http://www.erlang.org/doc/man/gen_fsm.html
|
||||
* http://www.erlang.org/doc/design_principles/fsm.html
|
||||
* http://learnyousomeerlang.com/finite-state-machines
|
||||
"""
|
||||
|
||||
@doc false
|
||||
defmacro __using__(_) do
|
||||
quote location: :keep do
|
||||
@behavior :gen_fsm
|
||||
|
||||
@doc false
|
||||
def handle_event(event, state_name, state_data) do
|
||||
{ :stop, {:bad_event, state_name, event}, state_data }
|
||||
end
|
||||
|
||||
@doc false
|
||||
def handle_sync_event(event, from, state_name, state_data) do
|
||||
{ :stop, {:bad_sync_event, state_name, event}, state_data }
|
||||
end
|
||||
|
||||
@doc false
|
||||
def handle_info(_msg, state_name, state_data) do
|
||||
{ :next_state, state_name, state_data }
|
||||
end
|
||||
|
||||
@doc false
|
||||
def terminate(_reason, _state_name, _state_data) do
|
||||
:ok
|
||||
end
|
||||
|
||||
@doc false
|
||||
def code_change(_old, state_name, state_data, _extra) do
|
||||
{ :ok, state_name, state_data }
|
||||
end
|
||||
|
||||
defoverridable [handle_event: 3, handle_sync_event: 4,
|
||||
handle_info: 3, terminate: 3, code_change: 4]
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -86,7 +86,7 @@ defmodule GenServer.Behaviour do
|
||||
@doc false
|
||||
defmacro __using__(_) do
|
||||
quote location: :keep do
|
||||
@behavior :gen_server
|
||||
@behaviour :gen_server
|
||||
|
||||
@doc false
|
||||
def init(args) do
|
||||
|
||||
+180
-378
@@ -2,46 +2,14 @@ defmodule HashDict do
|
||||
@moduledoc """
|
||||
A key-value store.
|
||||
|
||||
The `HashDict` is meant to work well with both small and
|
||||
large set of keys and it is an implementation of the `Dict`
|
||||
behaviour. For more information about the functions and
|
||||
their APIs, please consult the `Dict` module.
|
||||
The `HashDict` is implemented using tries, which grows in
|
||||
space as the number of keys grows, working well with both
|
||||
small and large set of keys. For more information about the
|
||||
functions and their APIs, please consult the `Dict` module.
|
||||
"""
|
||||
|
||||
use Dict.Behaviour
|
||||
|
||||
# A dictionary (key-value) implementation based on dynamic hashing.
|
||||
#
|
||||
# This implementation is based on hash tries. We first start with
|
||||
# a set of 8 buckets and expand when the density is about 5 entries
|
||||
# per bucket. We use bit shifting to make rehashing faster on
|
||||
# expansion.
|
||||
#
|
||||
# Compared to dict, it provides many enhancements:
|
||||
#
|
||||
# 1. HashDict buckets are ordered sets, this gives us faster access
|
||||
# and modification times
|
||||
#
|
||||
# 2. It uses phash2 to calculate the hash (instead of phash)
|
||||
#
|
||||
# 3. The dictionary first starts with a single bucket, instead of
|
||||
# a set of 8 buckets. This allow us to skip hashing altogher
|
||||
# for small dictionaries, providing faster operations and
|
||||
# reducing memory consumption
|
||||
#
|
||||
# 4. Once we reach 8 elements, the dictionary is promoted to a
|
||||
# set of buckets
|
||||
|
||||
# The ordered record contains a single bucket
|
||||
@ordered_threshold 8
|
||||
|
||||
defrecordp :ordered, HashDict,
|
||||
size: 0,
|
||||
bucket: []
|
||||
|
||||
# The bucketed record contains a series of buckets.
|
||||
@expand_load 5
|
||||
@contract_load 2
|
||||
@node_bitmap 0b111
|
||||
@node_shift 3
|
||||
@node_size 8
|
||||
@@ -49,431 +17,251 @@ defmodule HashDict do
|
||||
|
||||
defrecordp :trie, HashDict,
|
||||
size: 0,
|
||||
depth: 0,
|
||||
expand_on: @node_size * @expand_load,
|
||||
contract_on: @contract_load,
|
||||
root: @node_template
|
||||
|
||||
import Bitwise
|
||||
|
||||
# Let's inline common instructions
|
||||
# Inline common instructions
|
||||
@compile :inline_list_funcs
|
||||
@compile { :inline, bucket_hash: 1, bucket_index: 1, bucket_nth_index: 2, bucket_next: 1 }
|
||||
@compile { :inline, key_hash: 1, key_mask: 1, key_shift: 1 }
|
||||
|
||||
@doc """
|
||||
Creates a new empty dict.
|
||||
"""
|
||||
@spec new :: Dict.t
|
||||
def new do
|
||||
ordered()
|
||||
trie()
|
||||
end
|
||||
|
||||
@doc """
|
||||
Creates a new dict from the given enumerable.
|
||||
|
||||
## Examples
|
||||
|
||||
HashDict.new [{:b, 1}, {:a, 2}]
|
||||
#=> #HashDict<[a: 2, b: 1]>
|
||||
|
||||
"""
|
||||
@spec new(list({key :: term, value :: term})) :: Dict.t
|
||||
def new(pairs) do
|
||||
Enum.reduce pairs, ordered(), fn { k, v }, dict ->
|
||||
@doc false
|
||||
@spec new(Enum.t) :: Dict.t
|
||||
def new(enum) do
|
||||
IO.write :stderr, "HashDict.new/1 is deprecated, please use Enum.into/2 instead\n#{Exception.format_stacktrace}"
|
||||
Enum.reduce enum, trie(), fn { k, v }, dict ->
|
||||
put(dict, k, v)
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Creates a new dict from the enumerable with the
|
||||
help of the transformation function.
|
||||
|
||||
## Examples
|
||||
|
||||
HashDict.new ["a", "b"], fn x -> {x, x} end
|
||||
#=> #HashDict<[{"a","a"},{"b","b"}]>
|
||||
|
||||
"""
|
||||
@spec new(list, (term -> {key :: term, value ::term})) :: Dict.t
|
||||
def new(list, transform) when is_function(transform) do
|
||||
Enum.reduce list, new(), fn i, dict ->
|
||||
@doc false
|
||||
@spec new(Enum.t, (term -> {key :: term, value ::term})) :: Dict.t
|
||||
def new(enum, transform) when is_function(transform) do
|
||||
IO.write :stderr, "HashDict.new/2 is deprecated, please use Enum.into/3 instead\n#{Exception.format_stacktrace}"
|
||||
Enum.reduce enum, trie(), fn i, dict ->
|
||||
{ k, v } = transform.(i)
|
||||
put(dict, k, v)
|
||||
end
|
||||
end
|
||||
|
||||
def put(dict, key, value) do
|
||||
{ dict, _ } = dict_put(dict, key, { :put, value })
|
||||
dict
|
||||
def empty(trie()) do
|
||||
IO.write :stderr, "HashDict.empty/1 is deprecated, please use Collectable.empty/1 instead\n#{Exception.format_stacktrace}"
|
||||
trie()
|
||||
end
|
||||
|
||||
def update!(dict, key, fun) when is_function(fun, 1) do
|
||||
case dict_put(dict, key, { :update, nil, fun }) do
|
||||
{ dict, 0 } ->
|
||||
dict
|
||||
{ _dict, 1 } ->
|
||||
raise KeyError, key: key
|
||||
end
|
||||
def put(trie(root: root, size: size), key, value) do
|
||||
{ root, counter } = do_put(root, key, value, key_hash(key))
|
||||
trie(root: root, size: size + counter)
|
||||
end
|
||||
|
||||
def update(dict, key, initial, fun) when is_function(fun, 1) do
|
||||
{ dict, _ } = dict_put(dict, key, { :update, initial, fun })
|
||||
dict
|
||||
def update!(trie(root: root, size: size) = 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))
|
||||
trie(root: root, size: size + counter)
|
||||
end
|
||||
|
||||
def fetch(ordered(bucket: bucket), key) do
|
||||
bucket_get(bucket, key)
|
||||
def update(trie(root: root, size: size), key, initial, fun) when is_function(fun, 1) do
|
||||
{ root, counter } = do_update(root, key, fn -> initial end, fun, key_hash(key))
|
||||
trie(root: root, size: size + counter)
|
||||
end
|
||||
|
||||
def fetch(trie(root: root, depth: depth), key) do
|
||||
bucket_get(node_bucket(root, depth, bucket_hash(key)), key)
|
||||
end
|
||||
|
||||
def pop(dict, key, default // nil) do
|
||||
case dict_delete(dict, key) do
|
||||
{ dict, _, 0 } -> { default, dict }
|
||||
{ dict, value, _ } -> { value, dict }
|
||||
end
|
||||
def fetch(trie(root: root), key) do
|
||||
do_fetch(root, key, key_hash(key))
|
||||
end
|
||||
|
||||
def delete(dict, key) do
|
||||
{ dict, _, _ } = dict_delete(dict, key)
|
||||
dict
|
||||
end
|
||||
|
||||
def size(dict) do
|
||||
elem(dict, 1)
|
||||
end
|
||||
|
||||
def empty(_) do
|
||||
ordered()
|
||||
end
|
||||
|
||||
def merge(dict, enum, callback // fn(_k, _v1, v2) -> v2 end)
|
||||
|
||||
def merge(dict1, dict2, callback) when is_record(dict1, HashDict) and is_record(dict2, HashDict) and elem(dict1, 1) < elem(dict2, 1) do
|
||||
dict_fold dict1, dict2, fn [k|v1], acc ->
|
||||
update(acc, k, v1, &callback.(k, v1, &1))
|
||||
case dict_delete(dict, key) do
|
||||
{ dict, _value } -> dict
|
||||
:error -> dict
|
||||
end
|
||||
end
|
||||
|
||||
def merge(dict1, dict2, callback) when is_record(dict1, HashDict) and is_record(dict2, HashDict) do
|
||||
dict_fold dict2, dict1, fn [k|v2], acc ->
|
||||
update(acc, k, v2, &callback.(k, &1, v2))
|
||||
def pop(dict, key, default \\ nil) do
|
||||
case dict_delete(dict, key) do
|
||||
{ dict, value } -> { value, dict }
|
||||
:error -> { default, dict }
|
||||
end
|
||||
end
|
||||
|
||||
def merge(dict, enumerable, callback) when is_record(dict, HashDict) do
|
||||
super(dict, enumerable, callback)
|
||||
end
|
||||
|
||||
def split(dict, keys) do
|
||||
split(keys, new, dict)
|
||||
end
|
||||
|
||||
defp split([], including, excluding) do
|
||||
{ including, excluding }
|
||||
end
|
||||
|
||||
defp split([key|keys], including, excluding) do
|
||||
case dict_delete(excluding, key) do
|
||||
{ excluding, _, 0 } -> split(keys, including, excluding)
|
||||
{ excluding, value, _ } -> split(keys, put(including, key, value), excluding)
|
||||
end
|
||||
def size(trie(size: size)) do
|
||||
size
|
||||
end
|
||||
|
||||
@doc false
|
||||
def reduce(ordered(bucket: bucket), acc, fun) do
|
||||
bucket_reduce(bucket, acc, fun)
|
||||
def reduce(trie(root: root), acc, fun) do
|
||||
do_reduce(root, acc, fun, @node_size, fn
|
||||
{:suspend, acc} -> {:suspended, acc, &{ :done, elem(&1, 1) }}
|
||||
{:halt, acc} -> {:halted, acc}
|
||||
{:cont, acc} -> {:done, acc}
|
||||
end)
|
||||
end
|
||||
|
||||
def reduce(trie(root: root, depth: depth), acc, fun) do
|
||||
node_reduce(root, depth, acc, fun, @node_size,
|
||||
fn { :cont, acc }, _f -> { :done, acc } end)
|
||||
end
|
||||
|
||||
## Dict-wide functions
|
||||
|
||||
defp dict_fold(ordered(bucket: bucket), acc, fun) do
|
||||
bucket_fold(bucket, acc, fun)
|
||||
end
|
||||
|
||||
defp dict_fold(trie(root: root, depth: depth), acc, fun) do
|
||||
node_fold(root, depth, acc, fun, @node_size)
|
||||
end
|
||||
|
||||
defp dict_put(ordered(size: @ordered_threshold, bucket: bucket), key, value) do
|
||||
root = node_relocate(bucket, 0)
|
||||
dict_put(trie(size: @ordered_threshold, root: root), key, value)
|
||||
end
|
||||
|
||||
defp dict_put(ordered(size: size, bucket: bucket) = dict, key, value) do
|
||||
{ new, count } = bucket_put(bucket, key, value)
|
||||
{ ordered(dict, size: size + count, bucket: new), count }
|
||||
end
|
||||
|
||||
defp dict_put(trie(root: root, depth: depth, size: size, expand_on: size, contract_on: contract_on) = dict, key, value) do
|
||||
root = node_expand(root, depth, depth + 1)
|
||||
dict = trie(dict, root: root, depth: depth + 1,
|
||||
expand_on: size * @node_size, contract_on: contract_on * @node_size)
|
||||
dict_put(dict, key, value)
|
||||
end
|
||||
|
||||
defp dict_put(trie(root: root, size: size, depth: depth) = dict, key, value) do
|
||||
pos = bucket_hash(key)
|
||||
{ root, count } = node_put(root, depth, pos, key, value)
|
||||
{ trie(dict, size: size + count, root: root), count }
|
||||
end
|
||||
|
||||
defp dict_delete(ordered(bucket: bucket, size: size) = dict, key) do
|
||||
case bucket_delete(bucket, key) do
|
||||
{ _, value, 0 } ->
|
||||
{ dict, value, 0 }
|
||||
{ new_bucket, value, -1 } ->
|
||||
{ ordered(dict, size: size - 1, bucket: new_bucket), value, -1 }
|
||||
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
|
||||
|
||||
defp dict_delete(trie(root: root, size: size, depth: depth) = dict, key) do
|
||||
pos = bucket_hash(key)
|
||||
case node_delete(root, depth, pos, key) do
|
||||
{ _, value, 0 } ->
|
||||
{ dict, value, 0 }
|
||||
{ root, value, -1 } ->
|
||||
{ if depth > 0 and trie(dict, :contract_on) == size do
|
||||
root = node_contract(root, depth)
|
||||
trie(dict,
|
||||
root: root,
|
||||
size: size - 1,
|
||||
depth: depth - 1,
|
||||
contract_on: div(size, @node_size),
|
||||
expand_on: div(trie(dict, :expand_on), @node_size))
|
||||
else
|
||||
trie(dict, size: size - 1, root: root)
|
||||
end, value, -1 }
|
||||
def merge(trie(size: size1) = dict1, trie(size: size2) = dict2, callback) when size1 < size2 do
|
||||
reduce(dict1, { :cont, dict2 }, fn { k, v1 }, acc ->
|
||||
{ :cont, update(acc, k, v1, &callback.(k, v1, &1)) }
|
||||
end) |> elem(1)
|
||||
end
|
||||
|
||||
def merge(trie() = dict1, trie() = dict2, callback) do
|
||||
reduce(dict2, { :cont, dict1 }, fn { k, v2 }, acc ->
|
||||
{ :cont, update(acc, k, v2, &callback.(k, &1, v2)) }
|
||||
end) |> elem(1)
|
||||
end
|
||||
|
||||
## General helpers
|
||||
|
||||
defp dict_delete(trie(root: root, size: size), key) do
|
||||
case do_delete(root, key, key_hash(key)) do
|
||||
{ root, value } -> { trie(root: root, size: size - 1), value }
|
||||
:error -> :error
|
||||
end
|
||||
end
|
||||
|
||||
## Bucket helpers
|
||||
## Dict manipulation
|
||||
|
||||
# Get value from the bucket
|
||||
defp bucket_get([[k|_]|_bucket], key) when k > key do
|
||||
:error
|
||||
defp do_fetch(node, key, hash) do
|
||||
index = key_mask(hash)
|
||||
case elem(node, index) do
|
||||
[^key|v] -> {:ok, v}
|
||||
{^key, v, _} -> {:ok, v}
|
||||
{_, _, n} -> do_fetch(n, key, key_shift(hash))
|
||||
_ -> :error
|
||||
end
|
||||
end
|
||||
|
||||
defp bucket_get([[key|value]|_bucket], key) do
|
||||
{ :ok, value }
|
||||
defp do_put(node, key, value, hash) do
|
||||
index = key_mask(hash)
|
||||
case elem(node, index) do
|
||||
[] ->
|
||||
{set_elem(node, index, [key|value]), 1}
|
||||
[^key|_] ->
|
||||
{set_elem(node, index, [key|value]), 0}
|
||||
[k|v] ->
|
||||
n = set_elem(@node_template, key_mask(key_shift(hash)), [key|value])
|
||||
{set_elem(node, index, {k, v, n}), 1}
|
||||
{^key, _, n} ->
|
||||
{set_elem(node, index, {key, value, n}), 0}
|
||||
{k, v, n} ->
|
||||
{n, counter} = do_put(n, key, value, key_shift(hash))
|
||||
{set_elem(node, index, {k, v, n}), counter}
|
||||
end
|
||||
end
|
||||
|
||||
defp bucket_get([_e|bucket], key) do
|
||||
bucket_get(bucket, key)
|
||||
defp do_update(node, key, initial, fun, hash) do
|
||||
index = key_mask(hash)
|
||||
case elem(node, index) do
|
||||
[] ->
|
||||
{set_elem(node, index, [key|initial.()]), 1}
|
||||
[^key|value] ->
|
||||
{set_elem(node, index, [key|fun.(value)]), 0}
|
||||
[k|v] ->
|
||||
n = set_elem(@node_template, key_mask(key_shift(hash)), [key|initial.()])
|
||||
{set_elem(node, index, {k, v, n}), 1}
|
||||
{^key, value, n} ->
|
||||
{set_elem(node, index, {key, fun.(value), n}), 0}
|
||||
{k, v, n} ->
|
||||
{n, counter} = do_update(n, key, initial, fun, key_shift(hash))
|
||||
{set_elem(node, index, {k, v, n}), counter}
|
||||
end
|
||||
end
|
||||
|
||||
defp bucket_get([], _key) do
|
||||
:error
|
||||
defp do_delete(node, key, hash) do
|
||||
index = key_mask(hash)
|
||||
case elem(node, index) do
|
||||
[] ->
|
||||
:error
|
||||
[^key|value] ->
|
||||
{set_elem(node, index, []), value}
|
||||
[_|_] ->
|
||||
:error
|
||||
{^key, value, n} ->
|
||||
{set_elem(node, index, do_compact_node(n)), value}
|
||||
{k, v, n} ->
|
||||
case do_delete(n, key, key_shift(hash)) do
|
||||
{@node_template, value} ->
|
||||
{set_elem(node, index, [k|v]), value}
|
||||
{n, value} ->
|
||||
{set_elem(node, index, {k, v, n}), value}
|
||||
:error ->
|
||||
:error
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Puts a value in the bucket
|
||||
defp bucket_put([[k|_]|_]=bucket, key, { :put, value }) when k > key do
|
||||
{ [[key|value]|bucket], 1 }
|
||||
Enum.each 0..(@node_size - 1), fn index ->
|
||||
defp do_compact_node(node) when elem(node, unquote(index)) != [] do
|
||||
case elem(node, unquote(index)) do
|
||||
[k|v] ->
|
||||
case set_elem(node, unquote(index), []) do
|
||||
@node_template -> [k|v]
|
||||
n -> {k, v, n}
|
||||
end
|
||||
{k, v, n} ->
|
||||
{k, v, set_elem(node, unquote(index), do_compact_node(n))}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defp bucket_put([[k|_]|_]=bucket, key, { :update, initial, _fun }) when k > key do
|
||||
{ [[key|initial]|bucket], 1 }
|
||||
## Dict reduce
|
||||
|
||||
defp do_reduce_each(_node, {:halt, acc}, _fun, _next) do
|
||||
{:halted, acc}
|
||||
end
|
||||
|
||||
defp bucket_put([[key|_]|bucket], key, { :put, value }) do
|
||||
{ [[key|value]|bucket], 0 }
|
||||
defp do_reduce_each(node, {:suspend, acc}, fun, next) do
|
||||
{:suspended, acc, &do_reduce_each(node, &1, fun, next)}
|
||||
end
|
||||
|
||||
defp bucket_put([[key|value]|bucket], key, { :update, _initial, fun }) do
|
||||
{ [[key|fun.(value)]|bucket], 0 }
|
||||
defp do_reduce_each([], acc, _fun, next) do
|
||||
next.(acc)
|
||||
end
|
||||
|
||||
defp bucket_put([e|bucket], key, value) do
|
||||
{ rest, count } = bucket_put(bucket, key, value)
|
||||
{ [e|rest], count }
|
||||
defp do_reduce_each([k|v], {:cont, acc}, fun, next) do
|
||||
next.(fun.({k,v}, acc))
|
||||
end
|
||||
|
||||
defp bucket_put([], key, { :put, value }) do
|
||||
{ [[key|value]], 1 }
|
||||
defp do_reduce_each({k, v, n}, {:cont, acc}, fun, next) do
|
||||
do_reduce(n, fun.({k, v}, acc), fun, @node_size, next)
|
||||
end
|
||||
|
||||
defp bucket_put([], key, { :update, initial, _fun }) do
|
||||
{ [[key|initial]], 1 }
|
||||
defp do_reduce(node, acc, fun, count, next) when count > 0 do
|
||||
do_reduce_each(:erlang.element(count, node), acc, fun, &do_reduce(node, &1, fun, count - 1, next))
|
||||
end
|
||||
|
||||
# Puts a value in the bucket without returning
|
||||
# the operation value
|
||||
defp bucket_put!([[k|_]|_]=bucket, key, value) when k > key, do: [[key|value]|bucket]
|
||||
defp bucket_put!([[key|_]|bucket], key, value), do: [[key|value]|bucket]
|
||||
defp bucket_put!([e|bucket], key, value), do: [e|bucket_put!(bucket, key, value)]
|
||||
defp bucket_put!([], key, value), do: [[key|value]]
|
||||
|
||||
# Deletes a key from the bucket
|
||||
defp bucket_delete([[k|_]|_]=bucket, key) when k > key do
|
||||
{ bucket, nil, 0 }
|
||||
defp do_reduce(_node, acc, _fun, 0, next) do
|
||||
next.(acc)
|
||||
end
|
||||
|
||||
defp bucket_delete([[key|value]|bucket], key) do
|
||||
{ bucket, value, -1 }
|
||||
end
|
||||
## Key operations
|
||||
|
||||
defp bucket_delete([e|bucket], key) do
|
||||
{ rest, value, count } = bucket_delete(bucket, key)
|
||||
{ [e|rest], value, count }
|
||||
end
|
||||
import Bitwise
|
||||
|
||||
defp bucket_delete([], _key) do
|
||||
{ [], nil, 0 }
|
||||
end
|
||||
|
||||
# Reduces the bucket
|
||||
defp bucket_reduce(_, { :halt, acc }, _fun), do: { :halted, acc }
|
||||
defp bucket_reduce(list, { :suspend, acc }, fun), do: { :suspended, acc, &bucket_reduce(list, &1, fun) }
|
||||
defp bucket_reduce([[k|v]|t], { :cont, acc }, fun), do: bucket_reduce(t, fun.({ k, v }, acc), fun)
|
||||
defp bucket_reduce([], { :cont, acc }, _fun), do: { :done, acc }
|
||||
|
||||
defp bucket_fold(bucket, acc, fun) do
|
||||
:lists.foldl(fun, acc, bucket)
|
||||
end
|
||||
|
||||
defp bucket_hash(key) do
|
||||
defp key_hash(key) do
|
||||
:erlang.phash2(key)
|
||||
end
|
||||
|
||||
defp bucket_index(hash) do
|
||||
defp key_mask(hash) do
|
||||
hash &&& @node_bitmap
|
||||
end
|
||||
|
||||
defp bucket_nth_index(hash, n) do
|
||||
(hash >>> (@node_shift * n)) &&& @node_bitmap
|
||||
end
|
||||
|
||||
defp bucket_next(hash) do
|
||||
defp key_shift(hash) do
|
||||
hash >>> @node_shift
|
||||
end
|
||||
|
||||
## Node helpers
|
||||
|
||||
# Gets a bucket from the node
|
||||
defp node_bucket(node, 0, hash) do
|
||||
elem(node, bucket_index(hash))
|
||||
end
|
||||
|
||||
defp node_bucket(node, depth, hash) do
|
||||
child = elem(node, bucket_index(hash))
|
||||
node_bucket(child, depth - 1, bucket_next(hash))
|
||||
end
|
||||
|
||||
# Puts a key-value into a node
|
||||
defp node_put(node, 0, hash, key, value) do
|
||||
pos = bucket_index(hash)
|
||||
{ new, count } = bucket_put(elem(node, pos), key, value)
|
||||
{ set_elem(node, pos, new), count }
|
||||
end
|
||||
|
||||
defp node_put(node, depth, hash, key, value) do
|
||||
pos = bucket_index(hash)
|
||||
{ new, count } = node_put(elem(node, pos), depth - 1, bucket_next(hash), key, value)
|
||||
{ set_elem(node, pos, new), count }
|
||||
end
|
||||
|
||||
# Deletes a key from the bucket
|
||||
defp node_delete(node, 0, hash, key) do
|
||||
pos = bucket_index(hash)
|
||||
case bucket_delete(elem(node, pos), key) do
|
||||
{ _, value, 0 } -> { node, value, 0 }
|
||||
{ new, value, -1 } -> { set_elem(node, pos, new), value, -1 }
|
||||
end
|
||||
end
|
||||
|
||||
defp node_delete(node, depth, hash, key) do
|
||||
pos = bucket_index(hash)
|
||||
case node_delete(elem(node, pos), depth - 1, bucket_next(hash), key) do
|
||||
{ _, value, 0 } -> { node, value, 0 }
|
||||
{ new, value, -1 } -> { set_elem(node, pos, new), value, -1 }
|
||||
end
|
||||
end
|
||||
|
||||
# Reduces a node recursively
|
||||
defp node_reduce(_, -1, { :halt, acc }, _fun, _count, _next) do
|
||||
{ :halted, acc }
|
||||
end
|
||||
|
||||
defp node_reduce(list, -1, { :suspend, acc }, fun, count, next) do
|
||||
{ :suspended, acc, &node_reduce(list, -1, &1, fun, count, next) }
|
||||
end
|
||||
|
||||
defp node_reduce([[k|v]|t], -1, { :cont, acc }, fun, _count, next) do
|
||||
node_reduce(t, -1, fun.({ k, v }, acc), fun, _count, next)
|
||||
end
|
||||
|
||||
defp node_reduce([], -1, { :cont, _ } = acc, fun, _count, next) do
|
||||
next.(acc, fun)
|
||||
end
|
||||
|
||||
defp node_reduce(node, depth, acc, fun, count, next) when count >= 1 do
|
||||
node_reduce(:erlang.element(count, node), depth - 1, acc, fun, @node_size,
|
||||
&node_reduce(node, depth, &1, &2, count - 1, next))
|
||||
end
|
||||
|
||||
defp node_reduce(_node, _, acc, fun, 0, next) do
|
||||
next.(acc, fun)
|
||||
end
|
||||
|
||||
# Folds a node recursively
|
||||
defp node_fold(bucket, -1, acc, fun, _) do
|
||||
bucket_fold(bucket, acc, fun)
|
||||
end
|
||||
|
||||
defp node_fold(node, depth, acc, fun, count) when count >= 1 do
|
||||
acc = node_fold(:erlang.element(count, node), depth - 1, acc, fun, @node_size)
|
||||
node_fold(node, depth, acc, fun, count - 1)
|
||||
end
|
||||
|
||||
defp node_fold(_node, _, acc, _fun, 0) do
|
||||
acc
|
||||
end
|
||||
|
||||
# Node resizing
|
||||
defp node_expand({ b1, b2, b3, b4, b5, b6, b7, b8 }, 0, n) do
|
||||
{ node_relocate(b1, n), node_relocate(b2, n), node_relocate(b3, n),
|
||||
node_relocate(b4, n), node_relocate(b5, n), node_relocate(b6, n),
|
||||
node_relocate(b7, n), node_relocate(b8, n) }
|
||||
end
|
||||
|
||||
defp node_expand({ b1, b2, b3, b4, b5, b6, b7, b8 }, depth, n) do
|
||||
depth = depth - 1
|
||||
{ node_expand(b1, depth, n), node_expand(b2, depth, n), node_expand(b3, depth, n),
|
||||
node_expand(b4, depth, n), node_expand(b5, depth, n), node_expand(b6, depth, n),
|
||||
node_expand(b7, depth, n), node_expand(b8, depth, n) }
|
||||
end
|
||||
|
||||
defp node_contract({ b1, b2, b3, b4, b5, b6, b7, b8 }, depth) when depth > 0 do
|
||||
depth = depth - 1
|
||||
{ node_contract(b1, depth), node_contract(b2, depth), node_contract(b3, depth),
|
||||
node_contract(b4, depth), node_contract(b5, depth), node_contract(b6, depth),
|
||||
node_contract(b7, depth), node_contract(b8, depth) }
|
||||
end
|
||||
|
||||
defp node_contract({ b1, b2, b3, b4, b5, b6, b7, b8 }, 0) do
|
||||
b1 |> each_contract(b2) |> each_contract(b3) |> each_contract(b4)
|
||||
|> each_contract(b5) |> each_contract(b6) |> each_contract(b7)
|
||||
|> each_contract(b8)
|
||||
end
|
||||
|
||||
defp each_contract([[k|_v]=e|acc], [[key|_value]|_]=bucket) when k < key, do: [e|each_contract(acc, bucket)]
|
||||
defp each_contract(acc, [e|bucket]), do: [e|each_contract(acc, bucket)]
|
||||
defp each_contract([], bucket), do: bucket
|
||||
defp each_contract(acc, []), do: acc
|
||||
|
||||
defp node_relocate(node // @node_template, bucket, n) do
|
||||
:lists.foldl fn [key|value], acc ->
|
||||
pos = key |> bucket_hash() |> bucket_nth_index(n)
|
||||
set_elem(acc, pos, bucket_put!(elem(acc, pos), key, value))
|
||||
end, node, bucket
|
||||
end
|
||||
end
|
||||
|
||||
defimpl Enumerable, for: HashDict do
|
||||
@@ -486,3 +274,17 @@ end
|
||||
defimpl Access, for: HashDict do
|
||||
def access(dict, key), do: HashDict.get(dict, key, nil)
|
||||
end
|
||||
|
||||
defimpl Collectable, for: HashDict do
|
||||
def empty(_dict) do
|
||||
HashDict.new
|
||||
end
|
||||
|
||||
def into(original) do
|
||||
{ original, fn
|
||||
dict, { :cont, { k, v } } -> Dict.put(dict, k, v)
|
||||
dict, :done -> dict
|
||||
_, :halt -> :ok
|
||||
end }
|
||||
end
|
||||
end
|
||||
|
||||
+178
-404
@@ -2,230 +2,83 @@ defmodule HashSet do
|
||||
@moduledoc """
|
||||
A set store.
|
||||
|
||||
The `HashSet` is meant to work well with both small and
|
||||
large sets. It is an implementation of the `Set` behaviour.
|
||||
For more information about the functions and their APIs,
|
||||
please consult the `Set` module.
|
||||
The `HashSet` is implemented using tries, which grows in
|
||||
space as the number of keys grows, working well with both
|
||||
small and large set of keys. For more information about the
|
||||
functions and their APIs, please consult the `Set` module.
|
||||
"""
|
||||
|
||||
@behaviour Set
|
||||
|
||||
# The ordered record contains a single bucket.
|
||||
@ordered_threshold 8
|
||||
|
||||
defrecordp :ordered, HashSet,
|
||||
size: 0,
|
||||
bucket: []
|
||||
|
||||
# The bucketed record contains a series of buckets.
|
||||
@expand_load 5
|
||||
@contract_load 2
|
||||
@node_bitmap 0b1111
|
||||
@node_shift 4
|
||||
@node_size 16
|
||||
@node_bitmap 0b111
|
||||
@node_shift 3
|
||||
@node_size 8
|
||||
@node_template :erlang.make_tuple(@node_size, [])
|
||||
|
||||
@expand_default (@node_size * @expand_load)
|
||||
@contract_default @contract_load
|
||||
|
||||
defrecordp :trie, HashSet,
|
||||
size: 0,
|
||||
depth: 0,
|
||||
expand_on: @expand_default,
|
||||
contract_on: @contract_default,
|
||||
root: @node_template
|
||||
|
||||
import Bitwise
|
||||
|
||||
# Inline common instructions
|
||||
@compile :inline_list_funcs
|
||||
@compile { :inline, bucket_hash: 1, bucket_index: 1, bucket_nth_index: 2, bucket_next: 1 }
|
||||
@compile { :inline, key_hash: 1, key_mask: 1, key_shift: 1 }
|
||||
|
||||
@doc """
|
||||
Creates a new empty set.
|
||||
"""
|
||||
def new() do
|
||||
ordered()
|
||||
@spec new :: Set.t
|
||||
def new do
|
||||
trie()
|
||||
end
|
||||
|
||||
@doc """
|
||||
Creates a new set from the given enumerable.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> HashSet.new [1, 1, 2, 3, 3] |> HashSet.to_list
|
||||
[1,2,3]
|
||||
|
||||
"""
|
||||
def new(members) do
|
||||
Enum.reduce members, ordered(), fn member, set ->
|
||||
put(set, member)
|
||||
@doc false
|
||||
@spec new(Enum.t) :: Set.t
|
||||
def new(enum) do
|
||||
IO.write :stderr, "HashSet.new/1 is deprecated, please use Enum.into/2 instead\n#{Exception.format_stacktrace}"
|
||||
Enum.reduce enum, trie(), fn i, set ->
|
||||
put(set, i)
|
||||
end
|
||||
end
|
||||
|
||||
def union(set1, set2) when is_record(set1, HashSet) and is_record(set2, HashSet) and elem(set1, 1) <= elem(set2, 1) do
|
||||
set_fold set1, set2, fn v1, acc ->
|
||||
put(acc, v1)
|
||||
@doc false
|
||||
@spec new(Enum.t, (term -> term)) :: Set.t
|
||||
def new(enum, transform) when is_function(transform) do
|
||||
IO.write :stderr, "HashSet.new/2 is deprecated, please use Enum.into/3 instead\n#{Exception.format_stacktrace}"
|
||||
Enum.reduce enum, trie(), fn i, set ->
|
||||
put(set, transform.(i))
|
||||
end
|
||||
end
|
||||
|
||||
def union(set1, set2) when is_record(set1, HashSet) and is_record(set2, HashSet) do
|
||||
set_fold set2, set1, fn v, acc ->
|
||||
put(acc, v)
|
||||
def union(trie(size: size1) = set1, trie(size: size2) = set2) when size1 <= size2 do
|
||||
set_fold set1, set2, fn v, acc -> put(acc, v) end
|
||||
end
|
||||
|
||||
def union(trie() = set1, trie() = set2) do
|
||||
set_fold set2, set1, fn v, acc -> put(acc, v) end
|
||||
end
|
||||
|
||||
def intersection(trie() = set1, trie() = set2) do
|
||||
set_fold set1, trie(), fn v, acc ->
|
||||
if member?(set2, v), do: put(acc, v), else: acc
|
||||
end
|
||||
end
|
||||
|
||||
def intersection(set1, set2) when is_record(set1, HashSet) and is_record(set2, HashSet) and elem(set1, 1) <= elem(set2, 1) do
|
||||
set_filter set1, fn e -> set_member?(set2, e) end
|
||||
end
|
||||
|
||||
def intersection(set1, set2) when is_record(set1, HashSet) and is_record(set2, HashSet) do
|
||||
set_filter set2, fn e -> set_member?(set1, e) end
|
||||
end
|
||||
|
||||
def difference(set1, set2) when is_record(set1, HashSet) and is_record(set2, HashSet) do
|
||||
set_filter set1, fn m -> not set_member?(set2, m) end
|
||||
end
|
||||
|
||||
def member?(set, member) when is_record(set, HashSet) do
|
||||
set_member?(set, member)
|
||||
end
|
||||
|
||||
def empty(_) do
|
||||
ordered()
|
||||
end
|
||||
|
||||
def size(set) do
|
||||
elem(set, 1)
|
||||
end
|
||||
|
||||
def to_list(ordered(bucket: bucket)) do
|
||||
bucket
|
||||
def difference(trie() = set1, trie() = set2) do
|
||||
set_fold set2, set1, fn v, acc -> delete(acc, v) end
|
||||
end
|
||||
|
||||
def to_list(set) do
|
||||
set_fold(set, [], &[&1|&2]) |> :lists.reverse
|
||||
end
|
||||
|
||||
def put(set, member) do
|
||||
{ set, _ } = set_put(set, member)
|
||||
set
|
||||
end
|
||||
|
||||
def delete(set, member) do
|
||||
{ set, _ } = set_delete(set, member)
|
||||
set
|
||||
end
|
||||
|
||||
def equal?(set1, set2) do
|
||||
size = elem(set1, 1)
|
||||
case elem(set2, 1) do
|
||||
^size ->
|
||||
set_equal?(set1, set2)
|
||||
_ ->
|
||||
false
|
||||
def equal?(trie(size: size1) = set1, trie(size: size2) = set2) do
|
||||
case size1 do
|
||||
^size2 -> subset?(set1, set2)
|
||||
_ -> false
|
||||
end
|
||||
end
|
||||
|
||||
def subset?(set1, set2) do
|
||||
set_equal?(set1, set2)
|
||||
end
|
||||
|
||||
def disjoint?(set1, set2) do
|
||||
set_disjoint?(set1, set2)
|
||||
end
|
||||
|
||||
@doc false
|
||||
def reduce(ordered(bucket: bucket), acc, fun) do
|
||||
bucket_reduce(bucket, acc, fun)
|
||||
end
|
||||
|
||||
def reduce(trie(root: root, depth: depth), acc, fun) do
|
||||
node_reduce(root, depth, acc, fun, @node_size,
|
||||
fn { :cont, acc }, _f -> { :done, acc } end)
|
||||
end
|
||||
|
||||
## HashSet-wide functions
|
||||
|
||||
defp set_filter(ordered(bucket: bucket, size: size), fun) do
|
||||
{ new, removed_count } = bucket_filter(bucket, fun, [], 0)
|
||||
ordered(bucket: new, size: size - removed_count)
|
||||
end
|
||||
|
||||
defp set_filter(trie(root: root, depth: depth, size: size) = set, fun) do
|
||||
{ new, removed_count } = node_filter(root, depth, fun, @node_size)
|
||||
|
||||
if depth > 0 and trie(set, :contract_on) >= (size - removed_count) do
|
||||
contract_trie(trie(root: new,
|
||||
size: size - removed_count,
|
||||
depth: depth,
|
||||
contract_on: trie(set, :contract_on),
|
||||
expand_on: trie(set, :expand_on)))
|
||||
else
|
||||
trie(size: size - removed_count, root: new, depth: depth)
|
||||
end
|
||||
end
|
||||
|
||||
defp set_put(ordered(size: @ordered_threshold, bucket: bucket), member) do
|
||||
root = node_relocate(bucket, 0)
|
||||
set_put(trie(size: @ordered_threshold, root: root), member)
|
||||
end
|
||||
|
||||
defp set_put(ordered(size: size, bucket: bucket) = set, member) do
|
||||
{ new, count } = bucket_put(bucket, member)
|
||||
{ ordered(set, size: size + count, bucket: new), count }
|
||||
end
|
||||
|
||||
defp set_put(trie(root: root, depth: depth, size: size, expand_on: size, contract_on: contract_on) = set, member) do
|
||||
root = node_expand(root, depth, depth + 1)
|
||||
set = trie(set, root: root, depth: depth + 1,
|
||||
expand_on: size * @node_size, contract_on: contract_on * @node_size)
|
||||
set_put(set, member)
|
||||
end
|
||||
|
||||
defp set_put(trie(root: root, size: size, depth: depth) = set, member) do
|
||||
pos = bucket_hash(member)
|
||||
{ root, count } = node_put(root, depth, pos, member)
|
||||
{ trie(set, size: size + count, root: root), count }
|
||||
end
|
||||
|
||||
defp set_member?(ordered(bucket: bucket), member) do
|
||||
:lists.member(member, bucket)
|
||||
end
|
||||
|
||||
defp set_member?(trie(root: root, depth: depth), member) do
|
||||
:lists.member(member, node_bucket(root, depth, bucket_hash(member)))
|
||||
end
|
||||
|
||||
defp set_delete(ordered(bucket: bucket, size: size) = set, member) do
|
||||
case bucket_delete(bucket, member) do
|
||||
{ _, 0 } ->
|
||||
{ set, 0 }
|
||||
{ new_bucket, -1 } ->
|
||||
{ ordered(set, size: size - 1, bucket: new_bucket), -1 }
|
||||
end
|
||||
end
|
||||
|
||||
defp set_delete(trie(root: root, size: size, depth: depth) = set, member) do
|
||||
pos = bucket_hash(member)
|
||||
case node_delete(root, depth, pos, member) do
|
||||
{ _, 0 } ->
|
||||
{ set, 0 }
|
||||
{ root, -1 } ->
|
||||
{ if depth > 0 and trie(set, :contract_on) == size do
|
||||
root = node_contract(root, depth)
|
||||
trie(set,
|
||||
root: root,
|
||||
size: size - 1,
|
||||
depth: depth - 1,
|
||||
contract_on: div(size, @node_size),
|
||||
expand_on: div(trie(set, :expand_on), @node_size))
|
||||
else
|
||||
trie(set, size: size - 1, root: root)
|
||||
end, -1 }
|
||||
end
|
||||
end
|
||||
|
||||
defp set_equal?(set1, set2) do
|
||||
def subset?(trie() = set1, trie() = set2) do
|
||||
reduce(set1, { :cont, true }, fn member, acc ->
|
||||
case member?(set2, member) do
|
||||
true -> { :cont, acc }
|
||||
@@ -234,273 +87,180 @@ defmodule HashSet do
|
||||
end) |> elem(1)
|
||||
end
|
||||
|
||||
defp set_disjoint?(set1, set2) do
|
||||
reduce(set1, { :cont, true }, fn member, acc ->
|
||||
case member?(set2, member) do
|
||||
def disjoint?(trie() = set1, trie() = set2) do
|
||||
reduce(set2, { :cont, true }, fn member, acc ->
|
||||
case member?(set1, member) do
|
||||
false -> { :cont, acc }
|
||||
_ -> { :halt, false }
|
||||
end
|
||||
end) |> elem(1)
|
||||
end
|
||||
|
||||
defp set_fold(ordered(bucket: bucket), acc, fun) do
|
||||
bucket_fold(bucket, acc, fun)
|
||||
def empty(trie()) do
|
||||
IO.write :stderr, "HashSet.empty/1 is deprecated, please use Collectable.empty/1 instead\n#{Exception.format_stacktrace}"
|
||||
trie()
|
||||
end
|
||||
|
||||
defp set_fold(trie(root: root, depth: depth), acc, fun) do
|
||||
node_fold(root, depth, acc, fun, @node_size)
|
||||
def member?(trie(root: root), term) do
|
||||
do_member?(root, term, key_hash(term))
|
||||
end
|
||||
|
||||
## Bucket helpers
|
||||
def put(trie(root: root, size: size), term) do
|
||||
{root, counter} = do_put(root, term, key_hash(term))
|
||||
trie(root: root, size: size + counter)
|
||||
end
|
||||
|
||||
defp bucket_filter([e|bucket], fun, acc, count) do
|
||||
case fun.(e) do
|
||||
true -> bucket_filter(bucket, fun, [e|acc], count)
|
||||
false -> bucket_filter(bucket, fun, acc, count + 1)
|
||||
def delete(trie(root: root, size: size) = set, term) do
|
||||
case do_delete(root, term, key_hash(term)) do
|
||||
{:ok, root} -> trie(root: root, size: size - 1)
|
||||
:error -> set
|
||||
end
|
||||
end
|
||||
|
||||
defp bucket_filter([], _fun, acc, count) do
|
||||
{ :lists.reverse(acc), count }
|
||||
@doc false
|
||||
def reduce(trie(root: root), acc, fun) do
|
||||
do_reduce(root, acc, fun, @node_size, fn
|
||||
{:suspend, acc} -> {:suspended, acc, &{ :done, elem(&1, 1) }}
|
||||
{:halt, acc} -> {:halted, acc}
|
||||
{:cont, acc} -> {:done, acc}
|
||||
end)
|
||||
end
|
||||
|
||||
defp bucket_put([m|_]=bucket, member) when m > member do
|
||||
{ [member|bucket], 1 }
|
||||
def size(trie(size: size)) do
|
||||
size
|
||||
end
|
||||
|
||||
defp bucket_put([member|bucket], member) do
|
||||
{ [member|bucket], 0 }
|
||||
## Set helpers
|
||||
|
||||
defp set_fold(trie(root: root), acc, fun) do
|
||||
do_fold(root, acc, fun, @node_size)
|
||||
end
|
||||
|
||||
defp bucket_put([e|bucket], member) do
|
||||
{ rest, count } = bucket_put(bucket, member)
|
||||
{ [e|rest], count }
|
||||
end
|
||||
## Set manipulation
|
||||
|
||||
defp bucket_put([], member) do
|
||||
{ [member], 1 }
|
||||
end
|
||||
|
||||
defp bucket_put!([m|_]=bucket, member) when m > member, do: [member|bucket]
|
||||
defp bucket_put!([member|bucket], member), do: [member|bucket]
|
||||
defp bucket_put!([e|bucket], member), do: [e|bucket_put!(bucket, member)]
|
||||
defp bucket_put!([], member), do: [member]
|
||||
|
||||
# Deletes a key from the bucket
|
||||
defp bucket_delete([m,_|_]=bucket, member) when m > member do
|
||||
{ bucket, 0 }
|
||||
end
|
||||
|
||||
defp bucket_delete([member|bucket], member) do
|
||||
{ bucket, -1 }
|
||||
end
|
||||
|
||||
defp bucket_delete([e|bucket], member) do
|
||||
{ rest, count } = bucket_delete(bucket, member)
|
||||
{ [e|rest], count }
|
||||
end
|
||||
|
||||
defp bucket_delete([], _member) do
|
||||
{ [], 0 }
|
||||
end
|
||||
|
||||
defp bucket_reduce(_, { :halt, acc }, _fun), do: { :halted, acc }
|
||||
defp bucket_reduce(list, { :suspend, acc }, fun), do: { :suspended, acc, &bucket_reduce(list, &1, fun) }
|
||||
defp bucket_reduce([h|t], { :cont, acc }, fun), do: bucket_reduce(t, fun.(h, acc), fun)
|
||||
defp bucket_reduce([], { :cont, acc }, _fun), do: { :done, acc }
|
||||
|
||||
defp bucket_fold(bucket, acc, fun) do
|
||||
:lists.foldl(fun, acc, bucket)
|
||||
end
|
||||
|
||||
defp bucket_hash(key) do
|
||||
:erlang.phash2(key)
|
||||
end
|
||||
|
||||
defp bucket_nth_index(hash, n) do
|
||||
(hash >>> (@node_shift * n)) &&& @node_bitmap
|
||||
end
|
||||
|
||||
defp bucket_index(hash) do
|
||||
hash &&& @node_bitmap
|
||||
end
|
||||
|
||||
defp bucket_next(hash) do
|
||||
hash >>> @node_shift
|
||||
end
|
||||
|
||||
# Trie resizing
|
||||
|
||||
defp contract_trie(trie(depth: 0) = set) do
|
||||
set
|
||||
end
|
||||
|
||||
defp contract_trie(trie(root: root, depth: depth, size: size, contract_on: contract_on, expand_on: expand_on) = set) when size <= contract_on do
|
||||
new_contract_on = div(contract_on, @node_size)
|
||||
new_expand_on = div(expand_on, @node_size)
|
||||
|
||||
if new_contract_on == 0, do: new_contract_on = @contract_default
|
||||
if new_expand_on == 0, do: new_expand_on = @expand_default
|
||||
|
||||
contract_trie(trie(set, root: node_contract(root, depth),
|
||||
size: size,
|
||||
depth: depth - 1,
|
||||
contract_on: new_contract_on,
|
||||
expand_on: new_expand_on))
|
||||
end
|
||||
|
||||
defp contract_trie(set) do
|
||||
set
|
||||
end
|
||||
|
||||
# Node helpers
|
||||
|
||||
# Gets a bucket from the node
|
||||
defp node_bucket(node, 0, hash) do
|
||||
elem(node, bucket_index(hash))
|
||||
end
|
||||
|
||||
defp node_bucket(node, depth, hash) do
|
||||
child = elem(node, bucket_index(hash))
|
||||
node_bucket(child, depth - 1, bucket_next(hash))
|
||||
end
|
||||
|
||||
defp node_put(node, 0, hash, member) do
|
||||
pos = bucket_index(hash)
|
||||
{ new, count } = bucket_put(elem(node, pos), member)
|
||||
{ set_elem(node, pos, new), count }
|
||||
end
|
||||
|
||||
defp node_put(node, depth, hash, member) do
|
||||
pos = bucket_index(hash)
|
||||
{ new, count } = node_put(elem(node, pos), depth - 1, bucket_next(hash), member)
|
||||
{ set_elem(node, pos, new), count }
|
||||
end
|
||||
|
||||
# Deletes a key from the bucket
|
||||
defp node_delete(node, 0, hash, member) do
|
||||
pos = bucket_index(hash)
|
||||
case bucket_delete(elem(node, pos), member) do
|
||||
{ _, 0 } -> { node, 0 }
|
||||
{ new, -1 } -> { set_elem(node, pos, new), -1 }
|
||||
defp do_member?(node, term, hash) do
|
||||
index = key_mask(hash)
|
||||
case elem(node, index) do
|
||||
[] -> false
|
||||
[^term|_] -> true
|
||||
[_] -> false
|
||||
[_|n] -> do_member?(n, term, key_shift(hash))
|
||||
end
|
||||
end
|
||||
|
||||
defp node_delete(node, depth, hash, member) do
|
||||
pos = bucket_index(hash)
|
||||
case node_delete(elem(node, pos), depth - 1, bucket_next(hash), member) do
|
||||
{ _, 0 } -> { node, 0 }
|
||||
{ new, -1 } -> { set_elem(node, pos, new), -1 }
|
||||
defp do_put(node, term, hash) do
|
||||
index = key_mask(hash)
|
||||
case elem(node, index) do
|
||||
[] ->
|
||||
{set_elem(node, index, [term]), 1}
|
||||
[^term|_] ->
|
||||
{node, 0}
|
||||
[t] ->
|
||||
n = set_elem(@node_template, key_mask(key_shift(hash)), [term])
|
||||
{set_elem(node, index, [t|n]), 1}
|
||||
[t|n] ->
|
||||
{n, counter} = do_put(n, term, key_shift(hash))
|
||||
{set_elem(node, index, [t|n]), counter}
|
||||
end
|
||||
end
|
||||
|
||||
# Reduces a node recursively
|
||||
defp node_reduce(_, -1, { :halt, acc }, _fun, _count, _next) do
|
||||
{ :halted, acc }
|
||||
defp do_delete(node, term, hash) do
|
||||
index = key_mask(hash)
|
||||
case elem(node, index) do
|
||||
[] ->
|
||||
:error
|
||||
[^term] ->
|
||||
{:ok, set_elem(node, index, [])}
|
||||
[_] ->
|
||||
:error
|
||||
[^term|n] ->
|
||||
{:ok, set_elem(node, index, do_compact_node(n))}
|
||||
[t|n] ->
|
||||
case do_delete(n, term, key_shift(hash)) do
|
||||
{:ok, @node_template} ->
|
||||
{:ok, set_elem(node, index, [t])}
|
||||
{:ok, n} ->
|
||||
{:ok, set_elem(node, index, [t|n])}
|
||||
:error ->
|
||||
:error
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defp node_reduce(list, -1, { :suspend, acc }, fun, count, next) do
|
||||
{ :suspended, acc, &node_reduce(list, -1, &1, fun, count, next) }
|
||||
Enum.each 0..(@node_size - 1), fn index ->
|
||||
defp do_compact_node(node) when elem(node, unquote(index)) != [] do
|
||||
case elem(node, unquote(index)) do
|
||||
[t] ->
|
||||
case set_elem(node, unquote(index), []) do
|
||||
@node_template -> [t]
|
||||
n -> [t|n]
|
||||
end
|
||||
[t|n] ->
|
||||
[t|set_elem(node, unquote(index), do_compact_node(n))]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defp node_reduce([h|t], -1, { :cont, acc }, fun, _count, next) do
|
||||
node_reduce(t, -1, fun.(h, acc), fun, _count, next)
|
||||
## Set fold
|
||||
|
||||
defp do_fold_each([], acc, _fun), do: acc
|
||||
defp do_fold_each([t], acc, fun), do: fun.(t, acc)
|
||||
defp do_fold_each([t|n], acc, fun), do: do_fold(n, fun.(t, acc), fun, @node_size)
|
||||
|
||||
defp do_fold(node, acc, fun, count) when count > 0 do
|
||||
acc = do_fold_each(:erlang.element(count, node), acc, fun)
|
||||
do_fold(node, acc, fun, count - 1)
|
||||
end
|
||||
|
||||
defp node_reduce([], -1, { :cont, _ } = acc, fun, _count, next) do
|
||||
next.(acc, fun)
|
||||
end
|
||||
|
||||
defp node_reduce(node, depth, acc, fun, count, next) when count >= 1 do
|
||||
node_reduce(:erlang.element(count, node), depth - 1, acc, fun, @node_size,
|
||||
&node_reduce(node, depth, &1, &2, count - 1, next))
|
||||
end
|
||||
|
||||
defp node_reduce(_node, _, acc, fun, 0, next) do
|
||||
next.(acc, fun)
|
||||
end
|
||||
|
||||
# Folds a node recursively
|
||||
defp node_fold(bucket, -1, acc, fun, _) do
|
||||
bucket_fold(bucket, acc, fun)
|
||||
end
|
||||
|
||||
defp node_fold(node, depth, acc, fun, count) when count >= 1 do
|
||||
acc = node_fold(:erlang.element(count, node), depth - 1, acc, fun, @node_size)
|
||||
node_fold(node, depth, acc, fun, count - 1)
|
||||
end
|
||||
|
||||
defp node_fold(_node, _, acc, _fun, 0) do
|
||||
defp do_fold(_node, acc, _fun, 0) do
|
||||
acc
|
||||
end
|
||||
|
||||
defp node_filter(bucket, -1, fun, _) do
|
||||
bucket_filter(bucket, fun, [], 0)
|
||||
## Set reduce
|
||||
|
||||
defp do_reduce_each(_node, {:halt, acc}, _fun, _next) do
|
||||
{:halted, acc}
|
||||
end
|
||||
|
||||
defp node_filter(node, depth, fun, count) when count >= 1 do
|
||||
case node_filter(:erlang.element(count, node), depth - 1, fun, @node_size) do
|
||||
{ _, 0 } ->
|
||||
node_filter(node, depth, fun, count - 1)
|
||||
{ new_element, count1 } ->
|
||||
{ new_node, count2 } = node_filter(:erlang.setelement(count, node, new_element), depth, fun, count - 1)
|
||||
{ new_node, count1 + count2 }
|
||||
end
|
||||
defp do_reduce_each(node, {:suspend, acc}, fun, next) do
|
||||
{:suspended, acc, &do_reduce_each(node, &1, fun, next)}
|
||||
end
|
||||
|
||||
defp node_filter(node, _, _fun, 0) do
|
||||
{ node, 0 }
|
||||
defp do_reduce_each([], acc, _fun, next) do
|
||||
next.(acc)
|
||||
end
|
||||
|
||||
defp node_relocate(node // @node_template, bucket, n) do
|
||||
:lists.foldl fn member, acc ->
|
||||
pos = member |> bucket_hash() |> bucket_nth_index(n)
|
||||
set_elem(acc, pos, bucket_put!(elem(acc, pos), member))
|
||||
end, node, bucket
|
||||
defp do_reduce_each([t], {:cont, acc}, fun, next) do
|
||||
next.(fun.(t, acc))
|
||||
end
|
||||
|
||||
# Node resizing
|
||||
defp node_expand({ b1, b2, b3, b4, b5, b6, b7, b8, b9, b10, b11, b12, b13, b14, b15, b16 }, 0, n) do
|
||||
{ node_relocate(b1, n), node_relocate(b2, n), node_relocate(b3, n),
|
||||
node_relocate(b4, n), node_relocate(b5, n), node_relocate(b6, n),
|
||||
node_relocate(b7, n), node_relocate(b8, n), node_relocate(b9, n),
|
||||
node_relocate(b10, n), node_relocate(b11, n), node_relocate(b12, n),
|
||||
node_relocate(b13, n), node_relocate(b14, n), node_relocate(b15, n),
|
||||
node_relocate(b16, n) }
|
||||
defp do_reduce_each([t|n], {:cont, acc}, fun, next) do
|
||||
do_reduce(n, fun.(t, acc), fun, @node_size, next)
|
||||
end
|
||||
|
||||
defp node_expand({ b1, b2, b3, b4, b5, b6, b7, b8, b9, b10, b11, b12, b13, b14, b15, b16 }, depth, n) do
|
||||
depth = depth - 1
|
||||
{ node_expand(b1, depth, n), node_expand(b2, depth, n), node_expand(b3, depth, n),
|
||||
node_expand(b4, depth, n), node_expand(b5, depth, n), node_expand(b6, depth, n),
|
||||
node_expand(b7, depth, n), node_expand(b8, depth, n), node_expand(b9, depth, n),
|
||||
node_expand(b10, depth, n), node_expand(b11, depth, n), node_expand(b12, depth, n),
|
||||
node_expand(b13, depth, n), node_expand(b14, depth, n), node_expand(b15, depth, n),
|
||||
node_expand(b16, depth, n) }
|
||||
defp do_reduce(node, acc, fun, count, next) when count > 0 do
|
||||
do_reduce_each(:erlang.element(count, node), acc, fun, &do_reduce(node, &1, fun, count - 1, next))
|
||||
end
|
||||
|
||||
defp node_contract({ b1, b2, b3, b4, b5, b6, b7, b8, b9, b10, b11, b12, b13, b14, b15, b16 }, depth) when depth > 0 do
|
||||
depth = depth - 1
|
||||
{ node_contract(b1, depth), node_contract(b2, depth), node_contract(b3, depth),
|
||||
node_contract(b4, depth), node_contract(b5, depth), node_contract(b6, depth),
|
||||
node_contract(b7, depth), node_contract(b8, depth), node_contract(b9, depth),
|
||||
node_contract(b10, depth), node_contract(b11, depth), node_contract(b12, depth),
|
||||
node_contract(b13, depth), node_contract(b14, depth), node_contract(b15, depth),
|
||||
node_contract(b16, depth) }
|
||||
defp do_reduce(_node, acc, _fun, 0, next) do
|
||||
next.(acc)
|
||||
end
|
||||
|
||||
defp node_contract({ b1, b2, b3, b4, b5, b6, b7, b8, b9, b10, b11, b12, b13, b14, b15, b16 }, 0) do
|
||||
b1 |> each_contract(b2) |> each_contract(b3) |> each_contract(b4)
|
||||
|> each_contract(b5) |> each_contract(b6) |> each_contract(b7)
|
||||
|> each_contract(b8) |> each_contract(b9) |> each_contract(b10)
|
||||
|> each_contract(b11) |> each_contract(b12) |> each_contract(b13)
|
||||
|> each_contract(b14) |> each_contract(b15) |> each_contract(b16)
|
||||
## Key operations
|
||||
|
||||
import Bitwise
|
||||
|
||||
defp key_hash(key) do
|
||||
:erlang.phash2(key)
|
||||
end
|
||||
|
||||
defp each_contract([m1|acc], [m2|_]=bucket) when m1 < m2, do: [m1|each_contract(acc, bucket)]
|
||||
defp each_contract(acc, [m|bucket]), do: [m|each_contract(acc, bucket)]
|
||||
defp each_contract([], bucket), do: bucket
|
||||
defp each_contract(acc, []), do: acc
|
||||
defp key_mask(hash) do
|
||||
hash &&& @node_bitmap
|
||||
end
|
||||
|
||||
defp key_shift(hash) do
|
||||
hash >>> @node_shift
|
||||
end
|
||||
end
|
||||
|
||||
defimpl Enumerable, for: HashSet do
|
||||
@@ -508,3 +268,17 @@ defimpl Enumerable, for: HashSet do
|
||||
def member?(set, v), do: { :ok, HashSet.member?(set, v) }
|
||||
def count(set), do: { :ok, HashSet.size(set) }
|
||||
end
|
||||
|
||||
defimpl Collectable, for: HashSet do
|
||||
def empty(_dict) do
|
||||
HashSet.new
|
||||
end
|
||||
|
||||
def into(original) do
|
||||
{ original, fn
|
||||
set, { :cont, x } -> HashSet.put(set, x)
|
||||
set, :done -> set
|
||||
_, :halt -> :ok
|
||||
end }
|
||||
end
|
||||
end
|
||||
|
||||
+90
-27
@@ -3,6 +3,7 @@ import Inspect.Algebra
|
||||
|
||||
defrecord Inspect.Opts,
|
||||
records: true,
|
||||
structs: true,
|
||||
binaries: :infer,
|
||||
char_lists: :infer,
|
||||
limit: 50,
|
||||
@@ -57,6 +58,10 @@ defprotocol Inspect do
|
||||
Inspect.HashSet.inspect(HashSet.new, Inspect.Opts.new)
|
||||
|
||||
"""
|
||||
|
||||
# Handle structs in Any
|
||||
@fallback_to_any true
|
||||
|
||||
def inspect(thing, opts)
|
||||
end
|
||||
|
||||
@@ -75,8 +80,10 @@ defimpl Inspect, for: Atom do
|
||||
|
||||
iex> inspect(:foo)
|
||||
":foo"
|
||||
|
||||
iex> inspect(nil)
|
||||
"nil"
|
||||
|
||||
iex> inspect(Foo.Bar)
|
||||
"Foo.Bar"
|
||||
|
||||
@@ -100,7 +107,7 @@ defimpl Inspect, for: Atom do
|
||||
valid_ref_identifier?(binary) ->
|
||||
"Elixir." <> rest = binary
|
||||
rest
|
||||
atom in [:{}, :<<>>] ->
|
||||
atom in [:%{}, :{}, :<<>>, :..., :[]] ->
|
||||
":" <> binary
|
||||
atom in Macro.binary_ops or atom in Macro.unary_ops ->
|
||||
":" <> binary
|
||||
@@ -127,16 +134,21 @@ defimpl Inspect, for: Atom do
|
||||
# Detect if atom
|
||||
|
||||
defp valid_atom_identifier?(<<h, t :: binary>>) when h in ?a..?z or h in ?A..?Z or h == ?_ do
|
||||
case valid_identifier?(t) do
|
||||
<<>> -> true
|
||||
<<??>> -> true
|
||||
<<?!>> -> true
|
||||
_ -> false
|
||||
end
|
||||
valid_atom_piece?(t)
|
||||
end
|
||||
|
||||
defp valid_atom_identifier?(_), do: false
|
||||
|
||||
defp valid_atom_piece?(t) do
|
||||
case valid_identifier?(t) do
|
||||
<<>> -> true
|
||||
<<??>> -> true
|
||||
<<?!>> -> true
|
||||
<<?@, t::binary>> -> valid_atom_piece?(t)
|
||||
_ -> false
|
||||
end
|
||||
end
|
||||
|
||||
defp valid_identifier?(<<h, t :: binary>>)
|
||||
when h in ?a..?z
|
||||
when h in ?A..?Z
|
||||
@@ -149,7 +161,7 @@ defimpl Inspect, for: Atom do
|
||||
end
|
||||
|
||||
defimpl Inspect, for: BitString do
|
||||
@doc %S"""
|
||||
@doc ~S"""
|
||||
Represents a string as itself escaping all necessary
|
||||
characters. Binaries that contain non-printable characters
|
||||
are printed using the bitstring syntax.
|
||||
@@ -158,8 +170,10 @@ defimpl Inspect, for: BitString do
|
||||
|
||||
iex> inspect("bar")
|
||||
"\"bar\""
|
||||
|
||||
iex> inspect("f\"oo")
|
||||
"\"f\\\"oo\""
|
||||
|
||||
iex> inspect(<<0,1,2>>)
|
||||
"<<0, 1, 2>>"
|
||||
|
||||
@@ -275,7 +289,7 @@ defimpl Inspect, for: BitString do
|
||||
end
|
||||
|
||||
defimpl Inspect, for: List do
|
||||
@doc %S"""
|
||||
@doc ~S"""
|
||||
Represents a list, checking if it can be printed or not.
|
||||
If so, a single-quoted representation is returned,
|
||||
otherwise the brackets syntax is used. Keywords are
|
||||
@@ -285,8 +299,10 @@ defimpl Inspect, for: List do
|
||||
|
||||
iex> inspect('bar')
|
||||
"'bar'"
|
||||
|
||||
iex> inspect([0|'bar'])
|
||||
"[0, 98, 97, 114]"
|
||||
|
||||
iex> inspect([:foo,:bar])
|
||||
"[:foo, :bar]"
|
||||
|
||||
@@ -305,7 +321,7 @@ defimpl Inspect, for: List do
|
||||
end
|
||||
end
|
||||
|
||||
defp keyword({key, value}, opts) do
|
||||
def keyword({key, value}, opts) do
|
||||
concat(
|
||||
key_to_binary(key) <> ": ",
|
||||
to_doc(value, opts)
|
||||
@@ -319,15 +335,15 @@ defimpl Inspect, for: List do
|
||||
end
|
||||
end
|
||||
|
||||
defp keyword?([{ key, _value } | rest]) when is_atom(key) do
|
||||
def keyword?([{ key, _value } | rest]) when is_atom(key) do
|
||||
case atom_to_list(key) do
|
||||
'Elixir.' ++ _ -> false
|
||||
_ -> keyword?(rest)
|
||||
end
|
||||
end
|
||||
|
||||
defp keyword?([]), do: true
|
||||
defp keyword?(_other), do: false
|
||||
def keyword?([]), do: true
|
||||
def keyword?(_other), do: false
|
||||
end
|
||||
|
||||
defimpl Inspect, for: Tuple do
|
||||
@@ -339,6 +355,7 @@ defimpl Inspect, for: Tuple do
|
||||
|
||||
iex> inspect({1, 2, 3})
|
||||
"{1, 2, 3}"
|
||||
|
||||
iex> inspect(ArgumentError.new)
|
||||
"ArgumentError[message: \\\"argument error\\\"]"
|
||||
|
||||
@@ -401,6 +418,32 @@ defimpl Inspect, for: Tuple do
|
||||
end
|
||||
end
|
||||
|
||||
defimpl Inspect, for: Map do
|
||||
def inspect(map, opts) do
|
||||
inspect(map, "", opts)
|
||||
end
|
||||
|
||||
def inspect(map, name, opts) do
|
||||
map = :maps.to_list(map)
|
||||
surround_many("%" <> name <> "{", map, "}", opts.limit, traverse_fun(map, opts))
|
||||
end
|
||||
|
||||
defp traverse_fun(list, opts) do
|
||||
if Inspect.List.keyword?(list) do
|
||||
&Inspect.List.keyword(&1, opts)
|
||||
else
|
||||
&to_map(&1, opts)
|
||||
end
|
||||
end
|
||||
|
||||
defp to_map({key, value}, opts) do
|
||||
concat(
|
||||
concat(to_doc(key, opts), " => "),
|
||||
to_doc(value, opts)
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
defimpl Inspect, for: Integer do
|
||||
@doc """
|
||||
Represents the integer as a string.
|
||||
@@ -436,21 +479,17 @@ defimpl Inspect, for: Float do
|
||||
end
|
||||
|
||||
defimpl Inspect, for: Regex do
|
||||
@doc %S"""
|
||||
Represents the Regex using the `%r""` syntax.
|
||||
@doc ~S"""
|
||||
Represents the Regex using the `~r""` syntax.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> inspect(%r/foo/m)
|
||||
"%r\"foo\"m"
|
||||
iex> inspect(~r/foo/m)
|
||||
"~r\"foo\"m"
|
||||
|
||||
"""
|
||||
def inspect(regex, opts) when size(regex) == 5 do
|
||||
concat ["%r", to_doc(Regex.source(regex), opts), Regex.opts(regex)]
|
||||
end
|
||||
|
||||
def inspect(other, Inspect.Opts[] = opts) do
|
||||
to_doc(other, opts.records(false))
|
||||
def inspect(regex, opts) when size(regex) == 4 do
|
||||
concat ["~r", to_doc(Regex.source(regex), opts), Regex.opts(regex)]
|
||||
end
|
||||
end
|
||||
|
||||
@@ -476,18 +515,24 @@ defimpl Inspect, for: Function do
|
||||
end
|
||||
|
||||
defp default_inspect(mod, fun_info) do
|
||||
"#Function<#{uniq(fun_info)} in #{Inspect.Atom.inspect(mod)}.#{extract_name(fun_info[:name])}>"
|
||||
"#Function<#{uniq(fun_info)}/#{fun_info[:arity]} in " <>
|
||||
"#{Inspect.Atom.inspect(mod)}#{extract_name(fun_info[:name])}>"
|
||||
end
|
||||
|
||||
defp extract_name([]) do
|
||||
""
|
||||
end
|
||||
|
||||
defp extract_name(name) do
|
||||
case :binary.split(atom_to_binary(name), "-", [:global]) do
|
||||
["", name | _] -> name
|
||||
_ -> name
|
||||
["", name | _] -> "." <> name
|
||||
_ -> "." <> name
|
||||
end
|
||||
end
|
||||
|
||||
defp uniq(fun_info) do
|
||||
integer_to_binary(fun_info[:new_index]) <> "." <> integer_to_binary(fun_info[:uniq])
|
||||
integer_to_binary(fun_info[:new_index]) <> "." <>
|
||||
integer_to_binary(fun_info[:uniq])
|
||||
end
|
||||
end
|
||||
|
||||
@@ -510,6 +555,24 @@ defimpl Inspect, for: Reference do
|
||||
end
|
||||
end
|
||||
|
||||
defimpl Inspect, for: Any do
|
||||
def inspect(%{__struct__: struct} = map, opts) do
|
||||
try do
|
||||
struct.__struct__
|
||||
rescue
|
||||
_ -> Inspect.Map.inspect(map, opts)
|
||||
else
|
||||
dunder ->
|
||||
if :maps.keys(dunder) == :maps.keys(map) do
|
||||
Inspect.Map.inspect(:maps.remove(:__struct__, map),
|
||||
Inspect.Atom.inspect(struct, opts), opts)
|
||||
else
|
||||
Inspect.Map.inspect(map, opts)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defimpl Inspect, for: HashDict do
|
||||
def inspect(dict, opts) do
|
||||
concat ["#HashDict<", Inspect.List.inspect(HashDict.to_list(dict), opts), ">"]
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
defmodule Inspect.Algebra do
|
||||
@moduledoc %S"""
|
||||
@moduledoc ~S"""
|
||||
A set of functions for creating and manipulating algebra
|
||||
documents, as described in ["Strictly Pretty" (2000) by Christian Lindig][0].
|
||||
|
||||
@@ -8,6 +8,7 @@ defmodule Inspect.Algebra do
|
||||
|
||||
iex> Inspect.Algebra.empty
|
||||
:doc_nil
|
||||
|
||||
iex> "foo"
|
||||
"foo"
|
||||
|
||||
@@ -73,7 +74,7 @@ defmodule Inspect.Algebra do
|
||||
|
||||
# Functional interface to `doc` records
|
||||
|
||||
@type t :: :doc_nil | doc_cons_t | doc_nest_t | doc_break_t | doc_group_t | binary
|
||||
@type t :: :doc_nil | :doc_line | doc_cons_t | doc_nest_t | doc_break_t | doc_group_t | binary
|
||||
defrecordp :doc_cons, left: :doc_nil :: t, right: :doc_nil :: t
|
||||
defrecordp :doc_nest, indent: 1 :: non_neg_integer, doc: :doc_nil :: t
|
||||
defrecordp :doc_break, str: " " :: binary
|
||||
@@ -94,7 +95,7 @@ defmodule Inspect.Algebra do
|
||||
defp do_is_doc(doc) do
|
||||
quote do
|
||||
is_binary(unquote(doc)) or
|
||||
unquote(doc) == :doc_nil or
|
||||
unquote(doc) in [:doc_nil, :doc_line] or
|
||||
(is_tuple(unquote(doc)) and
|
||||
elem(unquote(doc), 0) in [:doc_cons, :doc_nest, :doc_break, :doc_group])
|
||||
end
|
||||
@@ -105,22 +106,42 @@ defmodule Inspect.Algebra do
|
||||
according to the inspect protocol.
|
||||
"""
|
||||
@spec to_doc(any, Inspect.Opts.t) :: t
|
||||
def to_doc(arg, opts) when is_record(opts, Inspect.Opts) do
|
||||
if is_tuple(arg) do
|
||||
if elem(opts, Inspect.Opts.__record__(:index, :records)) do
|
||||
try do
|
||||
Inspect.inspect(arg, opts)
|
||||
catch
|
||||
_, _ -> Inspect.Tuple.inspect(arg, opts)
|
||||
end
|
||||
else
|
||||
Inspect.Tuple.inspect(arg, opts)
|
||||
def to_doc(arg, opts) when is_record(arg) and is_record(opts, Inspect.Opts) do
|
||||
if opts.records do
|
||||
try do
|
||||
Inspect.inspect(arg, opts)
|
||||
rescue
|
||||
e ->
|
||||
res = Inspect.Tuple.inspect(arg, opts)
|
||||
IO.puts :stderr, "** (Inspect.Error) Got #{inspect e.__record__(:name)} with message " <>
|
||||
"#{e.message} while inspecting #{pretty(res, opts.width)}"
|
||||
res
|
||||
end
|
||||
else
|
||||
Inspect.inspect(arg, opts)
|
||||
Inspect.Tuple.inspect(arg, opts)
|
||||
end
|
||||
end
|
||||
|
||||
def to_doc(%{ __struct__: struct } = map, opts) when is_atom(struct) and is_record(opts, Inspect.Opts) do
|
||||
if opts.structs do
|
||||
try do
|
||||
Inspect.inspect(map, opts)
|
||||
rescue
|
||||
e ->
|
||||
res = Inspect.Map.inspect(map, opts)
|
||||
IO.puts :stderr, "** (Inspect.Error) Got #{inspect e.__record__(:name)} with message " <>
|
||||
"#{e.message} while inspecting #{pretty(res, opts.width)}"
|
||||
res
|
||||
end
|
||||
else
|
||||
Inspect.Map.inspect(map, opts)
|
||||
end
|
||||
end
|
||||
|
||||
def to_doc(arg, opts) when is_record(opts, Inspect.Opts) do
|
||||
Inspect.inspect(arg, opts)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns `:doc_nil` which is a document entity used to represent
|
||||
nothingness. Takes no arguments.
|
||||
@@ -178,7 +199,7 @@ defmodule Inspect.Algebra do
|
||||
doc_nest(indent: i, doc: x)
|
||||
end
|
||||
|
||||
@doc %S"""
|
||||
@doc ~S"""
|
||||
Document entity representing a break. This break can
|
||||
be rendered as a linebreak or as spaces, depending on the
|
||||
`mode` of the chosen layout or the provided separator.
|
||||
@@ -218,7 +239,7 @@ defmodule Inspect.Algebra do
|
||||
@spec glue(t, binary, t) :: doc_cons_t
|
||||
def glue(x, g, y) when is_binary(g), do: concat(x, concat(break(g), y))
|
||||
|
||||
@doc %S"""
|
||||
@doc ~S"""
|
||||
Returns a group containing the specified document.
|
||||
|
||||
## Examples
|
||||
@@ -263,7 +284,7 @@ defmodule Inspect.Algebra do
|
||||
@spec space(t, t) :: doc_cons_t
|
||||
def space(x, y), do: concat(x, concat(" ", y))
|
||||
|
||||
@doc %S"""
|
||||
@doc ~S"""
|
||||
Inserts a mandatory linebreak between two document entities.
|
||||
|
||||
## Examples
|
||||
@@ -274,7 +295,7 @@ defmodule Inspect.Algebra do
|
||||
|
||||
"""
|
||||
@spec line(t, t) :: doc_cons_t
|
||||
def line(x, y), do: concat(x, concat(@newline, y))
|
||||
def line(x, y), do: concat(x, concat(:doc_line, y))
|
||||
|
||||
@doc """
|
||||
Folds a list of document entities into a document entity
|
||||
@@ -297,7 +318,7 @@ defmodule Inspect.Algebra do
|
||||
|
||||
# Elixir conveniences
|
||||
|
||||
@doc %S"""
|
||||
@doc ~S"""
|
||||
Surrounds a document with characters.
|
||||
|
||||
Puts the document between left and right enclosing and nesting it.
|
||||
@@ -316,7 +337,7 @@ defmodule Inspect.Algebra do
|
||||
group concat left, concat(nest(doc, @nesting), right)
|
||||
end
|
||||
|
||||
@doc %S"""
|
||||
@doc ~S"""
|
||||
Maps and glues a collection of items together using the given separator
|
||||
and surrounds them. A limit can be passed which, once reached, stops
|
||||
gluing and outputs "..." instead.
|
||||
@@ -337,7 +358,7 @@ defmodule Inspect.Algebra do
|
||||
|
||||
"""
|
||||
@spec surround_many(binary, [any], binary, integer | :infinity, (term -> t), binary) :: t
|
||||
def surround_many(left, docs, right, limit, fun, separator // @surround_separator)
|
||||
def surround_many(left, docs, right, limit, fun, separator \\ @surround_separator)
|
||||
|
||||
def surround_many(left, [], right, _, _fun, _) do
|
||||
concat(left, right)
|
||||
@@ -397,6 +418,7 @@ defmodule Inspect.Algebra do
|
||||
@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])
|
||||
@@ -408,6 +430,7 @@ defmodule Inspect.Algebra do
|
||||
@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])
|
||||
@@ -420,11 +443,13 @@ defmodule Inspect.Algebra do
|
||||
if w == :infinity or fits?(w - k, t) do
|
||||
[s | format(w, k, t)]
|
||||
else
|
||||
prefix = :binary.copy(" ", i)
|
||||
[@newline, prefix | format(w, i, t)]
|
||||
[indent(i) | format(w, i, t)]
|
||||
end
|
||||
end
|
||||
|
||||
defp indent(0), do: @newline
|
||||
defp indent(i), do: @newline <> :binary.copy(" ", i)
|
||||
|
||||
@doc false
|
||||
@spec render([binary]) :: binary
|
||||
def render(sdoc) do
|
||||
|
||||
@@ -26,8 +26,8 @@ defmodule Integer do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Converts a binary to an integer.
|
||||
|
||||
Converts a binary to an integer.
|
||||
|
||||
If successful, returns a tuple of the form `{ integer, remainder_of_binary }`.
|
||||
Otherwise `:error`.
|
||||
|
||||
@@ -35,8 +35,10 @@ defmodule Integer do
|
||||
|
||||
iex> Integer.parse("34")
|
||||
{34,""}
|
||||
|
||||
iex> Integer.parse("34.5")
|
||||
{34,".5"}
|
||||
|
||||
iex> Integer.parse("three")
|
||||
:error
|
||||
|
||||
|
||||
+85
-20
@@ -6,6 +6,61 @@ defexception IO.StreamError, [:reason, :message] do
|
||||
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.
|
||||
@@ -49,7 +104,7 @@ defmodule IO do
|
||||
NFS file system.
|
||||
"""
|
||||
@spec read(device, :line | non_neg_integer) :: chardata | nodata
|
||||
def read(device // group_leader, chars_or_line)
|
||||
def read(device \\ group_leader, chars_or_line)
|
||||
|
||||
def read(device, :line) do
|
||||
:io.get_line(map_dev(device), '')
|
||||
@@ -72,7 +127,7 @@ defmodule IO do
|
||||
NFS file system.
|
||||
"""
|
||||
@spec binread(device, :line | non_neg_integer) :: iodata | nodata
|
||||
def binread(device // group_leader, chars_or_line)
|
||||
def binread(device \\ group_leader, chars_or_line)
|
||||
|
||||
def binread(device, :line) do
|
||||
case :file.read_line(map_dev(device)) do
|
||||
@@ -106,7 +161,7 @@ defmodule IO do
|
||||
|
||||
"""
|
||||
@spec write(device, chardata) :: :ok
|
||||
def write(device // group_leader(), item) do
|
||||
def write(device \\ group_leader(), item) do
|
||||
:io.put_chars map_dev(device), to_chardata(item)
|
||||
end
|
||||
|
||||
@@ -117,7 +172,7 @@ defmodule IO do
|
||||
Check `write/2` for more information.
|
||||
"""
|
||||
@spec binwrite(device, iodata) :: :ok | { :error, term }
|
||||
def binwrite(device // group_leader(), item) when is_iolist(item) do
|
||||
def binwrite(device \\ group_leader(), item) when is_iolist(item) do
|
||||
:file.write map_dev(device), item
|
||||
end
|
||||
|
||||
@@ -127,7 +182,7 @@ defmodule IO do
|
||||
to be a chardata.
|
||||
"""
|
||||
@spec puts(device, chardata) :: :ok
|
||||
def puts(device // group_leader(), item) do
|
||||
def puts(device \\ group_leader(), item) do
|
||||
erl_dev = map_dev(device)
|
||||
:io.put_chars erl_dev, [to_chardata(item), ?\n]
|
||||
end
|
||||
@@ -146,7 +201,7 @@ defmodule IO do
|
||||
|
||||
"""
|
||||
@spec inspect(term, Keyword.t) :: term
|
||||
def inspect(item, opts // []) do
|
||||
def inspect(item, opts \\ []) do
|
||||
inspect group_leader(), item, opts
|
||||
end
|
||||
|
||||
@@ -159,7 +214,7 @@ defmodule IO do
|
||||
|
||||
unless Keyword.get(opts, :width) do
|
||||
opts = case :io.columns(device) do
|
||||
{ :ok, width } -> Keyword.put(opts, :width, width)
|
||||
{ :ok, width } -> [width: width] ++ opts
|
||||
{ :error, _ } -> opts
|
||||
end
|
||||
end
|
||||
@@ -185,7 +240,7 @@ defmodule IO do
|
||||
"""
|
||||
@spec getn(chardata, pos_integer) :: chardata | nodata
|
||||
@spec getn(device, chardata) :: chardata | nodata
|
||||
def getn(prompt, count // 1)
|
||||
def getn(prompt, count \\ 1)
|
||||
|
||||
def getn(prompt, count) when is_integer(count) do
|
||||
getn(group_leader, prompt, count)
|
||||
@@ -219,14 +274,19 @@ defmodule IO do
|
||||
NFS file system.
|
||||
"""
|
||||
@spec gets(device, chardata) :: chardata | nodata
|
||||
def gets(device // group_leader(), prompt) do
|
||||
def gets(device \\ group_leader(), prompt) do
|
||||
:io.get_line(map_dev(device), to_chardata(prompt))
|
||||
end
|
||||
|
||||
@doc """
|
||||
Converts the io device into a Stream. The device is
|
||||
iterated line by line if `:line` is given or by a given
|
||||
number of codepoints.
|
||||
Converts the io device into a `IO.Stream`.
|
||||
|
||||
An `IO.Stream` implements both `Enumerable` and
|
||||
`Collectable`, allowing it to be used for both read
|
||||
and write.
|
||||
|
||||
The device is iterated line by line if `:line` is given or
|
||||
by a given number of codepoints.
|
||||
|
||||
This reads the IO as utf-8. Check out
|
||||
`IO.binstream/2` to handle the IO as a raw binary.
|
||||
@@ -244,24 +304,29 @@ defmodule IO do
|
||||
"""
|
||||
@spec stream(device, :line | pos_integer) :: Enumerable.t
|
||||
def stream(device, line_or_codepoints) do
|
||||
Stream.unfold(map_dev(device), &do_stream(&1, line_or_codepoints))
|
||||
%IO.Stream{ device: map_dev(device), raw: false, line_or_bytes: line_or_codepoints }
|
||||
end
|
||||
|
||||
@doc """
|
||||
Converts the io device into a Stream. The device is
|
||||
iterated line by line or by a number of bytes. This
|
||||
reads the IO as a raw binary.
|
||||
Converts the IO device into a `IO.Stream`.
|
||||
|
||||
An `IO.Stream` implements both `Enumerable` and
|
||||
`Collectable`, allowing it to be used for both read
|
||||
and write.
|
||||
|
||||
The device is iterated line by line or by a number of bytes.
|
||||
This reads the IO device as a raw binary.
|
||||
|
||||
Note that an IO stream has side effects and every time
|
||||
you go over the stream you may get different results.
|
||||
"""
|
||||
@spec binstream(device, :line | pos_integer) :: Enumerable.t
|
||||
def binstream(device, line_or_bytes) do
|
||||
Stream.unfold(map_dev(device), &do_binstream(&1, line_or_bytes))
|
||||
%IO.Stream{ device: map_dev(device), raw: true, line_or_bytes: line_or_bytes }
|
||||
end
|
||||
|
||||
@doc false
|
||||
def do_stream(device, what) do
|
||||
def each_stream(device, what) do
|
||||
case read(device, what) do
|
||||
:eof ->
|
||||
nil
|
||||
@@ -273,7 +338,7 @@ defmodule IO do
|
||||
end
|
||||
|
||||
@doc false
|
||||
def do_binstream(device, what) do
|
||||
def each_binstream(device, what) do
|
||||
case binread(device, what) do
|
||||
:eof ->
|
||||
nil
|
||||
@@ -287,7 +352,7 @@ defmodule IO do
|
||||
# Map the Elixir names for standard io and error to Erlang names
|
||||
defp map_dev(:stdio), do: :standard_io
|
||||
defp map_dev(:stderr), do: :standard_error
|
||||
defp map_dev(other), do: other
|
||||
defp map_dev(other) when is_atom(other) or is_pid(other) or is_tuple(other), do: other
|
||||
|
||||
defp to_chardata(list) when is_list(list), do: list
|
||||
defp to_chardata(other), do: to_string(other)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
defmodule IO.ANSI.Sequence do
|
||||
@moduledoc false
|
||||
|
||||
defmacro defsequence(name, code // "", terminator // "m") do
|
||||
defmacro defsequence(name, code \\ "", terminator \\ "m") do
|
||||
quote bind_quoted: [name: name, code: code, terminator: terminator] do
|
||||
def unquote(name)() do
|
||||
"\e[#{unquote(code)}#{unquote(terminator)}"
|
||||
@@ -32,7 +32,7 @@ defmodule IO.ANSI do
|
||||
"""
|
||||
@spec terminal? :: boolean
|
||||
@spec terminal?(:io.device) :: boolean
|
||||
def terminal?(device // :erlang.group_leader) do
|
||||
def terminal?(device \\ :erlang.group_leader) do
|
||||
match?({:ok, _}, :io.columns(device))
|
||||
end
|
||||
|
||||
@@ -72,7 +72,7 @@ defmodule IO.ANSI do
|
||||
@doc "Sets primary (default) font"
|
||||
defsequence :primary_font, 10
|
||||
|
||||
lc font_n inlist [1, 2, 3, 4, 5, 6, 7, 8, 9] do
|
||||
for font_n <- [1, 2, 3, 4, 5, 6, 7, 8, 9] do
|
||||
@doc "Sets alternative font #{font_n}"
|
||||
defsequence :"font_#{font_n}", font_n + 10
|
||||
end
|
||||
@@ -92,7 +92,7 @@ defmodule IO.ANSI do
|
||||
colors = [:black, :red, :green, :yellow, :blue, :magenta, :cyan, :white]
|
||||
colors = Enum.zip(0..(length(colors)-1), colors)
|
||||
|
||||
lc { code, color } inlist colors do
|
||||
for { code, color } <- colors do
|
||||
@doc "Sets foreground color to #{color}"
|
||||
defsequence color, code + 30
|
||||
|
||||
@@ -134,11 +134,11 @@ defmodule IO.ANSI do
|
||||
end
|
||||
|
||||
defp escape_sequence(other) do
|
||||
[spec|_] = String.split(other, %r/(,|\})/)
|
||||
[spec|_] = String.split(other, ~r/(,|\})/)
|
||||
raise ArgumentError, message: "invalid ANSI sequence specification: #{spec}"
|
||||
end
|
||||
|
||||
@doc %S"""
|
||||
@doc ~S"""
|
||||
Escapes a string by converting named ANSI sequences into actual ANSI codes.
|
||||
|
||||
The format for referring to sequences is `%{red}` and `%{red,bright}` (for
|
||||
@@ -159,7 +159,7 @@ defmodule IO.ANSI do
|
||||
|
||||
"""
|
||||
@spec escape(String.t, emit :: boolean) :: String.t
|
||||
def escape(string, emit // terminal?) do
|
||||
def escape(string, emit \\ terminal?) do
|
||||
{rendered, emitted} = do_escape(string, false, emit, false, [])
|
||||
if emitted and emit do
|
||||
rendered <> reset
|
||||
@@ -168,7 +168,7 @@ defmodule IO.ANSI do
|
||||
end
|
||||
end
|
||||
|
||||
@doc %S"""
|
||||
@doc ~S"""
|
||||
Escapes a string by converting named ANSI sequences into actual ANSI codes.
|
||||
|
||||
The format for referring to sequences is `%{red}` and `%{red,bright}` (for
|
||||
@@ -183,12 +183,13 @@ defmodule IO.ANSI do
|
||||
|
||||
iex> IO.ANSI.escape_fragment("Hello %{red,bright,green}yes", true)
|
||||
"Hello \e[31m\e[1m\e[32myes"
|
||||
|
||||
iex> IO.ANSI.escape_fragment("%{reset}bye", true)
|
||||
"\e[0mbye"
|
||||
|
||||
"""
|
||||
@spec escape_fragment(String.t, emit :: boolean) :: String.t
|
||||
def escape_fragment(string, emit // terminal?) do
|
||||
def escape_fragment(string, emit \\ terminal?) do
|
||||
{rendered, _emitted} = do_escape(string, false, emit, false, [])
|
||||
rendered
|
||||
end
|
||||
|
||||
@@ -16,7 +16,7 @@ defmodule IO.ANSI.Docs do
|
||||
|
||||
See `print/3` for docs on the supported options.
|
||||
"""
|
||||
def print_heading(heading, colors // @default_colors) do
|
||||
def print_heading(heading, colors \\ @default_colors) do
|
||||
IO.puts IO.ANSI.reset
|
||||
width = column_width()
|
||||
padding = div(width + String.length(heading), 2)
|
||||
@@ -46,7 +46,7 @@ defmodule IO.ANSI.Docs do
|
||||
* Intensity: `normal bright`
|
||||
* Decoration: `underline reverse`
|
||||
"""
|
||||
def print(doc, colors // @default_colors) do
|
||||
def print(doc, colors \\ @default_colors) do
|
||||
doc
|
||||
|> String.split(["\r\n","\n"], trim: false)
|
||||
|> Enum.map(&String.rstrip/1)
|
||||
@@ -185,7 +185,7 @@ defmodule IO.ANSI.Docs do
|
||||
|> Enum.join(" ")
|
||||
|> handle_links
|
||||
|> handle_inline(nil, [], [], colors)
|
||||
|> String.split(%r{\s})
|
||||
|> String.split(~r{\s})
|
||||
|> write_with_wrap(column_width() - size(indent), indent, from_list)
|
||||
|
||||
unless from_list, do: IO.puts(IO.ANSI.reset)
|
||||
@@ -276,16 +276,16 @@ defmodule IO.ANSI.Docs do
|
||||
end
|
||||
|
||||
defp escape_underlines_in_link(text) do
|
||||
case Regex.match?(%r{.*(https?\S*)}, text) do
|
||||
case Regex.match?(~r{.*(https?\S*)}, text) do
|
||||
true ->
|
||||
Regex.replace(%r{_}, text, "\\\\_")
|
||||
Regex.replace(~r{_}, text, "\\\\_")
|
||||
_ ->
|
||||
text
|
||||
end
|
||||
end
|
||||
|
||||
defp remove_square_brackets_in_link(text) do
|
||||
Regex.replace(%r{\[(.*?)\]\((.*?)\)}, text, "\\1 (\\2)")
|
||||
Regex.replace(~r{\[(.*?)\]\((.*?)\)}, text, "\\1 (\\2)")
|
||||
end
|
||||
|
||||
# Single inline quotes.
|
||||
|
||||
+838
-681
File diff suppressed because it is too large
Load Diff
@@ -9,14 +9,14 @@ defmodule Kernel.CLI do
|
||||
This is the API invoked by Elixir boot process.
|
||||
"""
|
||||
def main(argv) do
|
||||
argv = lc arg inlist argv, do: String.from_char_list!(arg)
|
||||
argv = for arg <- argv, do: String.from_char_list!(arg)
|
||||
|
||||
{ config, argv } = process_argv(argv, Kernel.CLI.Config.new)
|
||||
System.argv(argv)
|
||||
|
||||
run fn ->
|
||||
command_results = Enum.map(Enum.reverse(config.commands), &process_command(&1, config))
|
||||
command_errors = lc { :error, msg } inlist command_results, do: msg
|
||||
command_errors = for { :error, msg } <- command_results, do: msg
|
||||
errors = Enum.reverse(config.errors) ++ command_errors
|
||||
|
||||
if errors != [] do
|
||||
@@ -34,7 +34,7 @@ defmodule Kernel.CLI do
|
||||
This function is used by Elixir's CLI and also
|
||||
by escripts generated by Elixir.
|
||||
"""
|
||||
def run(fun, halt // true) do
|
||||
def run(fun, halt \\ true) do
|
||||
try do
|
||||
fun.()
|
||||
if halt do
|
||||
@@ -60,7 +60,7 @@ defmodule Kernel.CLI do
|
||||
defp at_exit(status) do
|
||||
hooks = :elixir_code_server.call(:flush_at_exit)
|
||||
|
||||
lc hook inlist hooks do
|
||||
for hook <- hooks do
|
||||
try do
|
||||
hook.(status)
|
||||
catch
|
||||
@@ -157,7 +157,7 @@ defmodule Kernel.CLI do
|
||||
process_shared t, config
|
||||
end
|
||||
|
||||
defp process_shared([erl|t], config) when erl in ["--detached", "--hidden"] do
|
||||
defp process_shared([erl|t], config) when erl in ["--detached", "--hidden", "--gen-debug"] do
|
||||
process_shared t, config
|
||||
end
|
||||
|
||||
@@ -171,7 +171,7 @@ defmodule Kernel.CLI do
|
||||
{ config, t }
|
||||
end
|
||||
|
||||
defp process_argv(["+compile"|t], config) do
|
||||
defp process_argv(["+elixirc"|t], config) do
|
||||
process_compiler t, config
|
||||
end
|
||||
|
||||
@@ -288,10 +288,10 @@ defmodule Kernel.CLI do
|
||||
end
|
||||
|
||||
defp process_command({:app, app}, _config) when is_binary(app) do
|
||||
case Application.Behaviour.start(binary_to_atom(app)) do
|
||||
case :application.ensure_all_started(binary_to_atom(app)) do
|
||||
{ :error, reason } ->
|
||||
{ :error, "--app : Could not start application #{app}: #{inspect reason}" }
|
||||
:ok ->
|
||||
{ :ok, _ } ->
|
||||
:ok
|
||||
end
|
||||
end
|
||||
|
||||
@@ -13,24 +13,27 @@ defmodule Kernel.ErrorHandler do
|
||||
:error_handler.undefined_lambda(module, fun, args)
|
||||
end
|
||||
|
||||
def release() do
|
||||
# On release, no further allow elixir_ensure_compiled
|
||||
# directives and revert to the original error handler.
|
||||
# Note we should not delete the elixir_compiler_pid though,
|
||||
# as we still want to send notifications to the compiler.
|
||||
:erlang.erase(:elixir_ensure_compiled)
|
||||
:erlang.process_flag(:error_handler, :error_handler)
|
||||
:ok
|
||||
end
|
||||
|
||||
defp ensure_loaded(module) do
|
||||
case Code.ensure_loaded(module) do
|
||||
{ :module, _ } -> []
|
||||
{ :error, _ } ->
|
||||
parent = :erlang.get(:elixir_compiler_pid)
|
||||
ref = :erlang.make_ref
|
||||
send parent, { :waiting, self(), ref, module }
|
||||
send parent, { :waiting, module, self(), ref, module }
|
||||
:erlang.garbage_collect(self)
|
||||
receive do
|
||||
{ ^ref, :ready } ->
|
||||
:ok
|
||||
{ ^ref, :release } ->
|
||||
# On release, get rid of the compiler pid,
|
||||
# no further allow elixir_ensure_compiled directives
|
||||
# and revert to the original error handler.
|
||||
:erlang.erase(:elixir_compiler_pid)
|
||||
:erlang.erase(:elixir_ensure_compiled)
|
||||
:erlang.process_flag(:error_handler, :error_handler)
|
||||
{ ^ref, :ready } -> :ok
|
||||
{ ^ref, :release } -> release()
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -24,7 +24,7 @@ defmodule Kernel.ParallelCompiler do
|
||||
The compiler doesn't care about the return values of the callbacks.
|
||||
Returns the modules generated by each compiled file.
|
||||
"""
|
||||
def files(files, callbacks // [])
|
||||
def files(files, callbacks \\ [])
|
||||
|
||||
def files(files, callbacks) when is_list(callbacks) do
|
||||
spawn_compilers(files, nil, callbacks)
|
||||
@@ -34,7 +34,7 @@ defmodule Kernel.ParallelCompiler do
|
||||
Compiles the given files to the given path.
|
||||
Read `files/2` for more information.
|
||||
"""
|
||||
def files_to_path(files, path, callbacks // [])
|
||||
def files_to_path(files, path, callbacks \\ [])
|
||||
|
||||
def files_to_path(files, path, callbacks) when is_binary(path) and is_list(callbacks) do
|
||||
spawn_compilers(files, path, callbacks)
|
||||
@@ -57,16 +57,16 @@ defmodule Kernel.ParallelCompiler do
|
||||
end
|
||||
|
||||
# We already have 4 currently running, don't spawn new ones
|
||||
defp spawn_compilers(files, original, output, callbacks, waiting, queued, schedulers, result) when
|
||||
defp spawn_compilers(entries, original, output, callbacks, waiting, queued, schedulers, result) when
|
||||
length(queued) - length(waiting) >= schedulers do
|
||||
wait_for_messages(files, original, output, callbacks, waiting, queued, schedulers, result)
|
||||
wait_for_messages(entries, original, output, callbacks, waiting, queued, schedulers, result)
|
||||
end
|
||||
|
||||
# Release waiting processes
|
||||
defp spawn_compilers([h|t], original, output, callbacks, waiting, queued, schedulers, result) when is_pid(h) do
|
||||
{ ^h, ref, _ } = List.keyfind(waiting, h, 0)
|
||||
{ _kind, ^h, ref, _module } = List.keyfind(waiting, h, 1)
|
||||
send h, { ref, :ready }
|
||||
waiting = List.keydelete(waiting, h, 0)
|
||||
waiting = List.keydelete(waiting, h, 1)
|
||||
spawn_compilers(t, original, output, callbacks, waiting, queued, schedulers, result)
|
||||
end
|
||||
|
||||
@@ -97,16 +97,19 @@ defmodule Kernel.ParallelCompiler do
|
||||
end)
|
||||
end
|
||||
|
||||
spawn_compilers(t, original, output, callbacks, waiting, [{pid, ref, h}|queued], schedulers, result)
|
||||
spawn_compilers(t, original, output, callbacks, waiting,
|
||||
[{ pid, ref, h }|queued], schedulers, result)
|
||||
end
|
||||
|
||||
# No more files, nothing waiting, queue is empty, we are done
|
||||
defp spawn_compilers([], _original, _output, _callbacks, [], [], _schedulers, result), do: result
|
||||
defp spawn_compilers([], _original, _output, _callbacks, [], [], _schedulers, result) do
|
||||
for { :module, mod } <- result, do: mod
|
||||
end
|
||||
|
||||
# Queued x, waiting for x: POSSIBLE ERROR! Release processes so we get the failures
|
||||
defp spawn_compilers([], original, output, callbacks, waiting, queued, schedulers, result) when length(waiting) == length(queued) do
|
||||
Enum.each queued, fn { child, _, _ } ->
|
||||
{ ^child, ref, _ } = List.keyfind(waiting, child, 0)
|
||||
{ _kind, ^child, ref, _module } = List.keyfind(waiting, child, 1)
|
||||
send child, { ref, :release }
|
||||
end
|
||||
wait_for_messages([], original, output, callbacks, waiting, queued, schedulers, result)
|
||||
@@ -118,8 +121,17 @@ defmodule Kernel.ParallelCompiler do
|
||||
end
|
||||
|
||||
# Wait for messages from child processes
|
||||
defp wait_for_messages(files, original, output, callbacks, waiting, queued, schedulers, result) do
|
||||
defp wait_for_messages(entries, original, output, callbacks, waiting, queued, schedulers, result) do
|
||||
receive do
|
||||
{ :struct_available, module } ->
|
||||
available = for { :struct, pid, _, waiting_module } <- waiting,
|
||||
module == waiting_module,
|
||||
not pid in entries,
|
||||
do: pid
|
||||
|
||||
spawn_compilers(available ++ entries, original, output, callbacks,
|
||||
waiting, queued, schedulers, [{ :struct, module }|result])
|
||||
|
||||
{ :module_available, child, ref, file, module, binary } ->
|
||||
if callback = Keyword.get(callbacks, :each_module) do
|
||||
callback.(file, module, binary)
|
||||
@@ -128,21 +140,25 @@ defmodule Kernel.ParallelCompiler do
|
||||
# Release the module loader which is waiting for an ack
|
||||
send child, { ref, :ack }
|
||||
|
||||
available = lc { pid, _, waiting_module } inlist waiting,
|
||||
waiting_module == module,
|
||||
available = for { _kind, pid, _, waiting_module } <- waiting,
|
||||
module == waiting_module,
|
||||
not pid in entries,
|
||||
do: pid
|
||||
|
||||
spawn_compilers(available ++ files, original, output, callbacks,
|
||||
waiting, queued, schedulers, [module|result])
|
||||
spawn_compilers(available ++ entries, original, output, callbacks,
|
||||
waiting, queued, schedulers, [{ :module, module }|result])
|
||||
|
||||
{ :waiting, child, ref, on } ->
|
||||
# Oops, we already got this module. Do not put it on waiting.
|
||||
if :lists.member(on, result) do
|
||||
send child, { :release, ref }
|
||||
{ :waiting, kind, child, ref, on } ->
|
||||
defined = fn { k, m } -> on == m and k in [kind, :module] end
|
||||
|
||||
# Oops, we already got it, do not put it on waiting.
|
||||
if :lists.any(defined, result) do
|
||||
send child, { ref, :ready }
|
||||
else
|
||||
waiting = [{ child, ref, on }|waiting]
|
||||
waiting = [{ kind, child, ref, on }|waiting]
|
||||
end
|
||||
spawn_compilers(files, original, output, callbacks, waiting, queued, schedulers, result)
|
||||
|
||||
spawn_compilers(entries, original, output, callbacks, waiting, queued, schedulers, result)
|
||||
|
||||
{ :DOWN, _down_ref, :process, down_pid, { :compiled, file } } ->
|
||||
if callback = Keyword.get(callbacks, :each_file) do
|
||||
@@ -151,35 +167,36 @@ defmodule Kernel.ParallelCompiler do
|
||||
|
||||
# Sometimes we may have spurious entries in the waiting
|
||||
# list because someone invoked try/rescue UndefinedFunctionError
|
||||
new_waiting = List.keydelete(waiting, down_pid, 0)
|
||||
new_entries = List.delete(entries, down_pid)
|
||||
new_queued = List.keydelete(queued, down_pid, 0)
|
||||
spawn_compilers(files, original, output, callbacks, new_waiting, new_queued, schedulers, result)
|
||||
new_waiting = List.keydelete(waiting, down_pid, 1)
|
||||
spawn_compilers(new_entries, original, output, callbacks, new_waiting, new_queued, schedulers, result)
|
||||
|
||||
{ :DOWN, down_ref, :process, _down_pid, { :failure, kind, reason, stacktrace } } ->
|
||||
handle_failure(down_ref, kind, reason, stacktrace, files, waiting, queued)
|
||||
wait_for_messages(files, original, output, callbacks, waiting, queued, schedulers, result)
|
||||
handle_failure(down_ref, kind, reason, stacktrace, entries, waiting, queued)
|
||||
wait_for_messages(entries, original, output, callbacks, waiting, queued, schedulers, result)
|
||||
|
||||
{ :DOWN, down_ref, :process, _down_pid, other } ->
|
||||
handle_failure(down_ref, :exit, other, [], files, waiting, queued)
|
||||
wait_for_messages(files, original, output, callbacks, waiting, queued, schedulers, result)
|
||||
handle_failure(down_ref, :exit, other, [], entries, waiting, queued)
|
||||
wait_for_messages(entries, original, output, callbacks, waiting, queued, schedulers, result)
|
||||
end
|
||||
end
|
||||
|
||||
defp handle_failure(ref, kind, reason, stacktrace, files, waiting, queued) do
|
||||
defp handle_failure(ref, kind, reason, stacktrace, entries, waiting, queued) do
|
||||
case List.keyfind(queued, ref, 1) do
|
||||
{ child, ^ref, file } ->
|
||||
if many_missing?(child, files, waiting, queued) do
|
||||
if many_missing?(child, entries, waiting, queued) do
|
||||
IO.puts "== Compilation failed =="
|
||||
IO.puts "Compilation failed on the following files:\n"
|
||||
|
||||
Enum.each Enum.reverse(queued), fn { pid, _ref, file } ->
|
||||
case List.keyfind(waiting, pid, 0) do
|
||||
{ ^pid, _, mod } -> IO.puts "* #{file} is missing module #{inspect mod}"
|
||||
case List.keyfind(waiting, pid, 1) do
|
||||
{ _, ^pid, _, mod } -> IO.puts "* #{file} is missing module #{inspect mod}"
|
||||
_ -> :ok
|
||||
end
|
||||
end
|
||||
|
||||
IO.puts "\nThe first failure is shown below..."
|
||||
IO.puts "\nOne of the failures is shown below..."
|
||||
end
|
||||
|
||||
IO.puts "== Compilation error on file #{file} =="
|
||||
@@ -189,11 +206,11 @@ defmodule Kernel.ParallelCompiler do
|
||||
end
|
||||
end
|
||||
|
||||
defp many_missing?(child, files, waiting, queued) do
|
||||
defp many_missing?(child, entries, waiting, queued) do
|
||||
waiting_length = length(waiting)
|
||||
|
||||
match?({ ^child, _, _ }, List.keyfind(waiting, child, 0)) and
|
||||
waiting_length > 1 and files == [] and
|
||||
match?({ _, ^child, _, _ }, List.keyfind(waiting, child, 1)) and
|
||||
waiting_length > 1 and entries == [] and
|
||||
waiting_length == length(queued)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -13,7 +13,7 @@ defmodule Kernel.ParallelRequire do
|
||||
|
||||
Returns the modules generated by each required file.
|
||||
"""
|
||||
def files(files, callback // default_callback) do
|
||||
def files(files, callback \\ default_callback) do
|
||||
schedulers = max(:erlang.system_info(:schedulers_online), 2)
|
||||
spawn_requires(files, [], callback, schedulers, [])
|
||||
end
|
||||
|
||||
@@ -1,413 +0,0 @@
|
||||
# This is an optimization Elixir runs on function clauses.
|
||||
# Whenever a variables matches against a record (a tagged
|
||||
# tuple), this information is stored in order to optimize
|
||||
# record calls.
|
||||
defmodule Kernel.RecordRewriter do
|
||||
@moduledoc false
|
||||
|
||||
def optimize_clause(module, clause) do
|
||||
optimize_clause(clause, module, :orddict.new)
|
||||
end
|
||||
|
||||
## Clause
|
||||
|
||||
defp optimize_clause({ :clause, line, args, guards, body }, module, dict) do
|
||||
{ args, dict } = optimize_args(args, module, dict)
|
||||
{ body, dict, res } = optimize_body(body, module, dict, [])
|
||||
{ { :clause, line, args, guards, body }, dict, res }
|
||||
end
|
||||
|
||||
defp optimize_args(args, module, dict) do
|
||||
Enum.map_reduce args, dict, fn(arg, acc) ->
|
||||
{ new_arg, new_acc, _res } = optimize_expr(arg, module, acc)
|
||||
{ new_arg, new_acc }
|
||||
end
|
||||
end
|
||||
|
||||
defp optimize_body([], _module, dict, _acc) do
|
||||
{ [], dict, nil }
|
||||
end
|
||||
|
||||
defp optimize_body([h], module, dict, acc) do
|
||||
{ new_expr, new_dict, new_res } = optimize_expr(h, module, dict)
|
||||
{ Enum.reverse([new_expr|acc]), new_dict, new_res }
|
||||
end
|
||||
|
||||
defp optimize_body([h|t], module, dict, acc) do
|
||||
{ new_expr, new_dict, _ } = optimize_expr(h, module, dict)
|
||||
optimize_body(t, module, new_dict, [new_expr|acc])
|
||||
end
|
||||
|
||||
## Record helpers
|
||||
|
||||
defp record_fields(record, record) do
|
||||
fields = Module.get_attribute(record, :record_fields)
|
||||
optimizable = Module.get_attribute(record, :record_optimizable)
|
||||
unless nil?(fields) or nil?(optimizable) do
|
||||
{ (lc { k, _ } inlist fields, do: k), optimizable }
|
||||
end
|
||||
end
|
||||
|
||||
defp record_fields(_module, record) do
|
||||
if Code.ensure_loaded?(record) && function_exported?(record, :__record__, 1) do
|
||||
try do
|
||||
fields = lc { k, _ } inlist record.__record__(:fields), do: k
|
||||
optimizable = record.__record__(:optimizable)
|
||||
{ fields, optimizable }
|
||||
rescue
|
||||
[UndefinedFunctionError, FunctionClauseError] -> nil
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defp record_field_info(function) do
|
||||
case atom_to_list(function) do
|
||||
'update' -> nil
|
||||
'update_' ++ _field -> nil
|
||||
_ -> { :accessor, function }
|
||||
end
|
||||
end
|
||||
|
||||
defp optimize_call(line, module, { record, _ } = res, left, { :atom, _, function }, args) do
|
||||
case record_fields(module, record) do
|
||||
{ fields, optimizable } ->
|
||||
opt_call =
|
||||
if :lists.member({ function, length(args) + 1 }, optimizable) do
|
||||
case record_field_info(function) do
|
||||
{ kind, field } ->
|
||||
if index = Enum.find_index(fields, &(field == &1)) do
|
||||
optimize_record_accessor_call(line, res, kind, field, index, left, args)
|
||||
end
|
||||
nil ->
|
||||
optimize_record_other_call(line, record, res, function, left, args)
|
||||
end
|
||||
end
|
||||
|
||||
opt_call || optimize_record_other_call(line, record, nil, function, left, args)
|
||||
nil ->
|
||||
nil
|
||||
end
|
||||
end
|
||||
|
||||
defp optimize_call(_line, _module, _res, _left, _right, _args) do
|
||||
nil
|
||||
end
|
||||
|
||||
defp optimize_record_accessor_call(line, _res, :accessor, _field, index, left, []) do
|
||||
call = { :call, line,
|
||||
{ :remote, line, { :atom, 0, :erlang }, { :atom, 0, :element } },
|
||||
[{ :integer, 0, index + 2 }, left]
|
||||
}
|
||||
{ call, nil }
|
||||
end
|
||||
|
||||
defp optimize_record_accessor_call(line, res, :accessor, _field, index, left, [arg]) do
|
||||
call = { :call, line,
|
||||
{ :remote, line, { :atom, 0, :erlang }, { :atom, 0, :setelement } },
|
||||
[{ :integer, 0, index + 2 }, left, arg]
|
||||
}
|
||||
{ call, res }
|
||||
end
|
||||
|
||||
defp optimize_record_other_call(line, record, res, function, left, args) do
|
||||
call = { :call, line,
|
||||
{ :remote, line, { :atom, 0, record }, { :atom, 0, function } },
|
||||
args ++ [left]
|
||||
}
|
||||
{ call, res }
|
||||
end
|
||||
|
||||
## Expr
|
||||
|
||||
defp optimize_expr({ :call, call_line,
|
||||
{ :remote, _, { :atom, _, :erlang }, { :atom, _, :setelement } } = remote,
|
||||
[{ :integer, _, pos } = integer, tuple, value] }, module, dict) when pos > 1 do
|
||||
|
||||
{ tuple, dict, res } = optimize_expr(tuple, module, dict)
|
||||
{ value, dict, _ } = optimize_expr(value, module, dict)
|
||||
{ { :call, call_line, remote, [integer, tuple, value] }, dict, res }
|
||||
end
|
||||
|
||||
defp optimize_expr({ :call, call_line, { :remote, line, left, right }, args }, module, dict) do
|
||||
{ left, dict, res } = optimize_expr(left, module, dict)
|
||||
{ right, dict, _ } = optimize_expr(right, module, dict)
|
||||
{ args, dict } = optimize_args(args, module, dict)
|
||||
|
||||
case optimize_call(call_line, module, res, left, right, args) do
|
||||
{ call, call_res } ->
|
||||
{ call, dict, call_res }
|
||||
nil ->
|
||||
{ { :call, call_line, { :remote, line, left, right }, args }, dict, nil }
|
||||
end
|
||||
end
|
||||
|
||||
defp optimize_expr({ :call, line, expr, args }, module, dict) do
|
||||
{ expr, dict, _ } = optimize_expr(expr, module, dict)
|
||||
{ args, dict } = optimize_args(args, module, dict)
|
||||
{ { :call, line, expr, args }, dict, nil }
|
||||
end
|
||||
|
||||
defp optimize_expr({ :match, line, left, right }, module, dict) do
|
||||
{ left, dict, left_res } = optimize_expr(left, module, dict)
|
||||
{ right, dict, right_res } = optimize_expr(right, module, dict)
|
||||
|
||||
match = { :match, line, left, right }
|
||||
|
||||
if left_res do
|
||||
dict = assign_vars(extract_vars(right, []), dict, left_res)
|
||||
end
|
||||
|
||||
if right_res do
|
||||
dict = assign_vars(extract_vars(left, []), dict, right_res)
|
||||
end
|
||||
|
||||
{ match, dict, right_res || left_res }
|
||||
end
|
||||
|
||||
defp optimize_expr({ :op, line, op, left, right }, module, dict) do
|
||||
{ left, dict, _ } = optimize_expr(left, module, dict)
|
||||
{ right, dict, _ } = optimize_expr(right, module, dict)
|
||||
{ { :op, line, op, left, right }, dict, nil }
|
||||
end
|
||||
|
||||
defp optimize_expr({ :op, line, op, expr }, module, dict) do
|
||||
{ expr, dict, _ } = optimize_expr(expr, module, dict)
|
||||
{ { :op, line, op, expr }, dict, nil }
|
||||
end
|
||||
|
||||
defp optimize_expr({ :bin, line, elements }, module, dict) do
|
||||
{ elements, dict } = optimize_args(elements, module, dict)
|
||||
{ { :bin, line, elements }, dict, nil }
|
||||
end
|
||||
|
||||
defp optimize_expr({ :bin_element, line, expr, type1, type2 }, module, dict) do
|
||||
{ expr, dict, _ } = optimize_expr(expr, module, dict)
|
||||
{ { :bin_element, line, expr, type1, type2 }, dict, nil }
|
||||
end
|
||||
|
||||
defp optimize_expr({ :cons, line, left, right }, module, dict) do
|
||||
{ left, dict, _ } = optimize_expr(left, module, dict)
|
||||
{ right, dict, _ } = optimize_expr(right, module, dict)
|
||||
{ { :cons, line, left, right }, dict, nil }
|
||||
end
|
||||
|
||||
defp optimize_expr({ :block, line, args }, module, dict) do
|
||||
{ args, dict, res } = optimize_body(args, module, dict, [])
|
||||
{ { :block, line, args }, dict, res }
|
||||
end
|
||||
|
||||
defp optimize_expr({ :tuple, line, args }, module, dict) do
|
||||
{ args, dict, args_res } = optimize_tuple_args(args, module, dict)
|
||||
args_res = if Enum.any?(args_res), do: args_res, else: nil
|
||||
|
||||
res =
|
||||
case args do
|
||||
[{ :atom, _, atom }|_] -> atom
|
||||
_ -> nil
|
||||
end
|
||||
|
||||
{ { :tuple, line, args }, dict, { res, args_res } }
|
||||
end
|
||||
|
||||
defp optimize_expr({ :var, _, name } = var, _module, dict) do
|
||||
case :orddict.find(name, dict) do
|
||||
{ :ok, res } -> { var, dict, res }
|
||||
:error -> { var, dict, nil }
|
||||
end
|
||||
end
|
||||
|
||||
defp optimize_expr({ :case, line, expr, clauses }, module, dict) do
|
||||
{ expr, dict, _ } = optimize_expr(expr, module, dict)
|
||||
tuples = lc clause inlist clauses, do: optimize_clause(clause, module, dict)
|
||||
clauses = lc { clause, _, _ } inlist tuples, do: clause
|
||||
dict = join_dict(tuples)
|
||||
res = join_result(tuples)
|
||||
{ { :case, line, expr, clauses }, dict, res }
|
||||
end
|
||||
|
||||
defp optimize_expr({ :receive, line, clauses }, module, dict) do
|
||||
tuples = lc clause inlist clauses, do: optimize_clause(clause, module, dict)
|
||||
clauses = lc { clause, _, _ } inlist tuples, do: clause
|
||||
dict = join_dict(tuples)
|
||||
res = join_result(tuples)
|
||||
{ { :receive, line, clauses }, dict, res }
|
||||
end
|
||||
|
||||
defp optimize_expr({ :receive, line, clauses, after_key, after_value }, module, dict) do
|
||||
tuples = lc clause inlist clauses, do: optimize_clause(clause, module, dict)
|
||||
clauses = lc { clause, _, _ } inlist tuples, do: clause
|
||||
|
||||
{ after_key, dict, _ } = optimize_expr(after_key, module, dict)
|
||||
{ after_value, dict, res } = optimize_body(after_value, module, dict, [])
|
||||
|
||||
dict = join_dict(tuples, dict)
|
||||
res = join_result(tuples, res)
|
||||
|
||||
{ { :receive, line, clauses, after_key, after_value }, dict, res }
|
||||
end
|
||||
|
||||
defp optimize_expr({ :try, line, body, elses, catches, try_after }, module, dict) do
|
||||
tuples = lc clause inlist catches, do: optimize_clause(clause, module, dict)
|
||||
catches = lc { clause, _, _ } inlist tuples, do: clause
|
||||
|
||||
tuples = lc clause inlist elses, do: optimize_clause(clause, module, dict)
|
||||
elses = lc { clause, _, _ } inlist tuples, do: clause
|
||||
|
||||
{ body, _, res } = optimize_body(body, module, dict, [])
|
||||
res = join_result(tuples, res)
|
||||
|
||||
{ try_after, _, _ } = optimize_body(try_after, module, dict, [])
|
||||
{ { :try, line, body, elses, catches, try_after }, dict, res }
|
||||
end
|
||||
|
||||
defp optimize_expr({ :fun, line, { :function, receiver, name, arity } }, module, dict) do
|
||||
{ receiver, dict, _ } = optimize_expr(receiver, module, dict)
|
||||
{ name, dict, _ } = optimize_expr(name, module, dict)
|
||||
{ arity, dict, _ } = optimize_expr(arity, module, dict)
|
||||
{ { :fun, line, { :function, receiver, name, arity } }, dict, nil }
|
||||
end
|
||||
|
||||
defp optimize_expr({ :fun, line, { :clauses, clauses } }, module, dict) do
|
||||
clauses = lc clause inlist clauses do
|
||||
{ clause, _, _ } = optimize_clause(clause, module, dict)
|
||||
clause
|
||||
end
|
||||
|
||||
{ { :fun, line, { :clauses, clauses } }, dict, nil }
|
||||
end
|
||||
|
||||
defp optimize_expr({ comprehension, line, expr, args }, module, dict) when comprehension in [:lc, :bc] do
|
||||
{ args, new_dict } = optimize_args(args, module, dict)
|
||||
{ expr, _, _ } = optimize_expr(expr, module, new_dict)
|
||||
{ { comprehension, line, expr, args }, dict, nil }
|
||||
end
|
||||
|
||||
defp optimize_expr({ generate, line, left, right }, module, dict) when generate in [:generate, :b_generate] do
|
||||
{ left, dict, _ } = optimize_expr(left, module, dict)
|
||||
{ right, dict, _ } = optimize_expr(right, module, dict)
|
||||
{ { generate, line, left, right }, dict, nil }
|
||||
end
|
||||
|
||||
defp optimize_expr(other, _module, dict) when elem(other, 0) in [:string, :atom, :integer, :float, :nil, :fun] do
|
||||
{ other, dict, nil }
|
||||
end
|
||||
|
||||
## Helpers
|
||||
|
||||
defp optimize_tuple_args(args, module, dict) do
|
||||
{ final_args, { final_dict, final_acc } } =
|
||||
Enum.map_reduce args, { dict, [] }, fn(arg, { acc_dict, acc_res }) ->
|
||||
{ new_arg, new_acc, res } = optimize_expr(arg, module, acc_dict)
|
||||
{ new_arg, { new_acc, [res|acc_res] } }
|
||||
end
|
||||
|
||||
{ final_args, final_dict, Enum.reverse(final_acc) }
|
||||
end
|
||||
|
||||
defp assign_vars([key|t], dict, { _, value } = res) when is_list(key) and is_list(value) and length(key) == length(value) do
|
||||
assign_vars t, assign_nested_vars(key, dict, value), res
|
||||
end
|
||||
|
||||
defp assign_vars([key|t], dict, { value, _ } = res) when is_atom(key) and value != nil do
|
||||
dict =
|
||||
case :orddict.find(key, dict) do
|
||||
{ :ok, ^res } ->
|
||||
dict
|
||||
{ :ok, { ^value, _ } } ->
|
||||
:orddict.store(key, { value, nil }, dict)
|
||||
{ :ok, _ } ->
|
||||
# We are overriding a type of an existing variable,
|
||||
# which means the source code is invalid.
|
||||
:orddict.store(key, nil, dict)
|
||||
:error ->
|
||||
:orddict.store(key, res, dict)
|
||||
end
|
||||
|
||||
assign_vars t, dict, res
|
||||
end
|
||||
|
||||
defp assign_vars([_|t], dict, res) do
|
||||
assign_vars t, dict, res
|
||||
end
|
||||
|
||||
defp assign_vars([], dict, _res) do
|
||||
dict
|
||||
end
|
||||
|
||||
defp assign_nested_vars([vars|vt], dict, [res|rt]) do
|
||||
assign_nested_vars(vt, assign_vars(vars, dict, res), rt)
|
||||
end
|
||||
|
||||
defp assign_nested_vars([], dict, []) do
|
||||
dict
|
||||
end
|
||||
|
||||
defp extract_vars({ :match, _, left, right }, vars) do
|
||||
vars = extract_vars(right, vars)
|
||||
extract_vars(left, vars)
|
||||
end
|
||||
|
||||
defp extract_vars({ :var, _, name }, vars) do
|
||||
[name|vars]
|
||||
end
|
||||
|
||||
defp extract_vars({ :tuple, _, args }, vars) do
|
||||
[Enum.map(args, &extract_vars(&1, []))|vars]
|
||||
end
|
||||
|
||||
defp extract_vars(_, vars) do
|
||||
vars
|
||||
end
|
||||
|
||||
defp join_dict([]) do
|
||||
[]
|
||||
end
|
||||
|
||||
defp join_dict([{ _, dict, _ }|t]) do
|
||||
join_dict(t, dict)
|
||||
end
|
||||
|
||||
defp join_dict([{ _, dict, _ }|t], other) do
|
||||
other = Enum.reduce other, other, fn
|
||||
{ key, { value, _ } = res }, acc ->
|
||||
case :orddict.find(key, dict) do
|
||||
{ :ok, ^res } -> acc
|
||||
{ :ok, { ^value, _ } } -> :orddict.store(key, { value, nil }, acc)
|
||||
{ :ok, _ } -> :orddict.store(key, nil, acc)
|
||||
:error -> :orddict.erase(key, acc)
|
||||
end
|
||||
{ key, nil }, acc ->
|
||||
:orddict.store(key, nil, acc)
|
||||
end
|
||||
|
||||
join_dict(t, other)
|
||||
end
|
||||
|
||||
defp join_dict([], other) do
|
||||
other
|
||||
end
|
||||
|
||||
defp join_result([]) do
|
||||
[]
|
||||
end
|
||||
|
||||
defp join_result([{ _, _, res }|t]) do
|
||||
join_result(t, res)
|
||||
end
|
||||
|
||||
defp join_result([{ _, _, res }|t], res) do
|
||||
join_result(t, res)
|
||||
end
|
||||
|
||||
defp join_result([{ _, _, { res, _ } }|t], { res, _ }) do
|
||||
join_result(t, { res, nil })
|
||||
end
|
||||
|
||||
defp join_result([{ _, _, _ }|_], _res) do
|
||||
nil
|
||||
end
|
||||
|
||||
defp join_result([], res) do
|
||||
res
|
||||
end
|
||||
end
|
||||
@@ -20,22 +20,141 @@ defmodule Kernel.SpecialForms do
|
||||
"""
|
||||
|
||||
@doc """
|
||||
Defines a new tuple.
|
||||
Creates a tuple.
|
||||
|
||||
Only two item tuples are considered literals in Elixir.
|
||||
Therefore all other tuples are represented in the AST
|
||||
as a call to the special form `:{}`.
|
||||
|
||||
Conveniences for manipulating tuples can be found in the
|
||||
`Tuple` module. Some functions for working with tuples are
|
||||
also available in `Kernel`, namely `Kernel.elem/2`,
|
||||
`Kernel.set_elem/3` and `Kernel.tuple_size/1`.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> { 1, 2, 3 }
|
||||
{ 1, 2, 3 }
|
||||
|
||||
iex> quote do: { 1, 2, 3 }
|
||||
{ :{}, [], [1,2,3] }
|
||||
|
||||
"""
|
||||
defmacro unquote(:{})(args)
|
||||
|
||||
@doc """
|
||||
Creates a map.
|
||||
|
||||
Maps are key-value stores where keys are compared
|
||||
using the match operator (`===`). Maps can be created with
|
||||
the `%{}` special form where keys are associated via `=>`:
|
||||
|
||||
%{ 1 => 2 }
|
||||
|
||||
Maps also support the keyword notation, as other special forms,
|
||||
as long as they are at the end of the argument list:
|
||||
|
||||
%{ hello: :world, with: :keywords }
|
||||
%{ :hello => :world, with: :keywords }
|
||||
|
||||
If a map has duplicated keys, the last key will always have
|
||||
higher precedence:
|
||||
|
||||
iex> %{ a: :b, a: :c }
|
||||
%{ a: :c }
|
||||
|
||||
Conveniences for manipulating maps can be found in the
|
||||
`Map` module.
|
||||
|
||||
## Access syntax
|
||||
|
||||
Besides the access functions available in the `Map` module,
|
||||
like `Map.get/3` and `Map.fetch/2`, a map can be accessed using the
|
||||
`.` operator:
|
||||
|
||||
iex> map = %{ a: :b }
|
||||
iex> map.a
|
||||
:b
|
||||
|
||||
Note that the `.` operator expects the field to exist in the map.
|
||||
If not, an `ArgumentError` is raised.
|
||||
|
||||
## Update syntax
|
||||
|
||||
Maps also support an update syntax:
|
||||
|
||||
iex> map = %{ :a => :b }
|
||||
iex> %{ map | :a => :c }
|
||||
%{ :a => :c }
|
||||
|
||||
Notice the update syntax requires the given keys to exist.
|
||||
Trying to update a key that does not exist will raise an `ArgumentError`.
|
||||
|
||||
## AST representation
|
||||
|
||||
Regardless if `=>` or the keywords syntax is used, Maps are
|
||||
always represented internally as a list of two-items tuples
|
||||
for simplicity:
|
||||
|
||||
iex> quote do: %{ :a => :b, c: :d }
|
||||
{ :%{}, [], [{:a, :b}, {:c, :d}] }
|
||||
|
||||
"""
|
||||
defmacro unquote(:%{})(args)
|
||||
|
||||
@doc """
|
||||
Creates a struct.
|
||||
|
||||
A struct is a tagged map that allows developers to provide
|
||||
default values for keys, tags to be used in polymorphic
|
||||
dispatches and compile time assertions.
|
||||
|
||||
To define a struct, you just need to implement the `__struct__/0`
|
||||
function in a module:
|
||||
|
||||
defmodule User do
|
||||
def __struct__ do
|
||||
%{ name: "josé", age: 27 }
|
||||
end
|
||||
end
|
||||
|
||||
Now a struct can be created as follow:
|
||||
|
||||
%User{}
|
||||
|
||||
Underneath, a struct is just a map with a `__struct__` field
|
||||
pointing to the User module:
|
||||
|
||||
%User{} == %{ __struct__: User, name: "josé", age: 27 }
|
||||
|
||||
A struct also validates the given keys are part of the defined
|
||||
struct. The example below will fail because there is no key
|
||||
`:full_name` in the user struct:
|
||||
|
||||
%User{ full_name: "José Valim" }
|
||||
|
||||
Note that a struct specifies a minimum set of keys required
|
||||
for operations. Other keys can be added to structs via the
|
||||
regular map operations:
|
||||
|
||||
user = %User{}
|
||||
Map.put(user, :a_non_struct_key, :value)
|
||||
|
||||
An update operation specific for structs is also available:
|
||||
|
||||
%User{ user | age: 28 }
|
||||
|
||||
The syntax above will guarantee the given keys are valid at
|
||||
compilation time and it will guarantee at runtime the given
|
||||
argument is a struct, failing with `BadStructError` otherwise.
|
||||
|
||||
Check `Kernel.defprotocol/2` for more information on how structs
|
||||
can be used with protocols for polymorphic dispatch. Also,
|
||||
see `Kernel.struct/2` for examples on how to create and update
|
||||
structs dynamically.
|
||||
"""
|
||||
defmacro unquote(:%)(struct, map)
|
||||
|
||||
@doc """
|
||||
Defines a new bitstring.
|
||||
|
||||
@@ -108,7 +227,7 @@ defmodule Kernel.SpecialForms do
|
||||
signedness, e.g.:
|
||||
|
||||
iex> <<val, _rest :: binary>> = <<-100, "foo">>
|
||||
...> val
|
||||
iex> val
|
||||
156
|
||||
|
||||
Here, `val` is interpreted as unsigned.
|
||||
@@ -200,12 +319,16 @@ defmodule Kernel.SpecialForms do
|
||||
|
||||
iex> Kernel.Sample
|
||||
Kernel.Sample
|
||||
|
||||
iex> Kernel.length([1,2,3])
|
||||
3
|
||||
|
||||
iex> Kernel.+(1, 2)
|
||||
3
|
||||
|
||||
iex> Kernel."length"([1,2,3])
|
||||
3
|
||||
|
||||
iex> Kernel.'+'(1, 2)
|
||||
3
|
||||
|
||||
@@ -229,6 +352,7 @@ defmodule Kernel.SpecialForms do
|
||||
|
||||
iex> apply(:erlang, :+, [1,2])
|
||||
3
|
||||
|
||||
iex> Module.concat(Kernel, Sample)
|
||||
Kernel.Sample
|
||||
|
||||
@@ -331,7 +455,7 @@ defmodule Kernel.SpecialForms do
|
||||
Elixir won't emit any warnings though, since the alias
|
||||
was not explicitly defined.
|
||||
|
||||
Both warning behaviors could be changed by explicitily
|
||||
Both warning behaviours could be changed by explicitly
|
||||
setting the `:warn` option to true or false.
|
||||
"""
|
||||
defmacro alias(module, opts)
|
||||
@@ -377,7 +501,7 @@ defmodule Kernel.SpecialForms do
|
||||
for example:
|
||||
|
||||
iex> import List
|
||||
...> flatten([1, [2], 3])
|
||||
iex> flatten([1, [2], 3])
|
||||
[1,2,3]
|
||||
|
||||
## Selector
|
||||
@@ -443,7 +567,7 @@ defmodule Kernel.SpecialForms do
|
||||
Elixir won't emit any warnings though, since the import
|
||||
was not explicitly defined.
|
||||
|
||||
Both warning behaviors could be changed by explicitily
|
||||
Both warning behaviours could be changed by explicitly
|
||||
setting the `:warn` option to true or false.
|
||||
|
||||
## Ambiguous function/macro names
|
||||
@@ -495,8 +619,8 @@ defmodule Kernel.SpecialForms do
|
||||
value, instead of rebinding. This can be done with the `^` special form:
|
||||
|
||||
iex> x = 1
|
||||
iex> ^x = 1
|
||||
iex> ^x = 2
|
||||
iex> ^x = List.first([1])
|
||||
iex> ^x = List.first([2])
|
||||
** (MatchError) no match of right hand side value: 2
|
||||
|
||||
Note that `^` always refers to the value of x prior to the match. The
|
||||
@@ -510,13 +634,13 @@ defmodule Kernel.SpecialForms do
|
||||
"""
|
||||
defmacro ^(var)
|
||||
|
||||
@doc %S"""
|
||||
@doc ~S"""
|
||||
Gets the representation of any expression.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> quote do: sum(1, 2, 3)
|
||||
{ :sum, [], [1, 2, 3] }
|
||||
quote do: sum(1, 2, 3)
|
||||
#=> { :sum, [], [1, 2, 3] }
|
||||
|
||||
## Explanation
|
||||
|
||||
@@ -1007,77 +1131,65 @@ defmodule Kernel.SpecialForms do
|
||||
"""
|
||||
defmacro unquote(:unquote_splicing)(expr)
|
||||
|
||||
@doc """
|
||||
List comprehensions allow you to quickly build a list from another list:
|
||||
@doc ~S"""
|
||||
Comprehensions allow you to quickly build a data structure from
|
||||
an enumerable or a bitstring.
|
||||
|
||||
iex> lc n inlist [1, 2, 3, 4], do: n * 2
|
||||
[2,4,6,8]
|
||||
Let's start with an example:
|
||||
|
||||
A comprehension accepts many generators and also filters. Generators
|
||||
are defined using both `inlist` and `inbits` operators, allowing you
|
||||
to loop lists and bitstrings:
|
||||
iex> for n <- [1, 2, 3, 4], do: n * 2
|
||||
[2, 4, 6, 8]
|
||||
|
||||
A comprehension accepts many generators and filters. Enumerable
|
||||
generators are defined using `<-`:
|
||||
|
||||
# A list generator:
|
||||
iex> lc n inlist [1, 2, 3, 4], do: n * 2
|
||||
[2,4,6,8]
|
||||
|
||||
# A bit string generator:
|
||||
iex> lc <<n>> inbits <<1, 2, 3, 4>>, do: n * 2
|
||||
[2,4,6,8]
|
||||
|
||||
# A generator from a variable:
|
||||
iex> list = [1, 2, 3, 4]
|
||||
...> lc n inlist list, do: n * 2
|
||||
[2,4,6,8]
|
||||
iex> for n <- [1, 2, 3, 4], do: n * 2
|
||||
[2, 4, 6, 8]
|
||||
|
||||
# A comprehension with two generators
|
||||
iex> lc x inlist [1, 2], y inlist [2, 3], do: x*y
|
||||
[2,3,4,6]
|
||||
iex> for x <- [1, 2], y <- [2, 3], do: x*y
|
||||
[2, 3, 4, 6]
|
||||
|
||||
Filters can also be given:
|
||||
|
||||
# A comprehension with a generator and a filter
|
||||
iex> lc n inlist [1, 2, 3, 4, 5, 6], rem(n, 2) == 0, do: n
|
||||
[2,4,6]
|
||||
iex> for n <- [1, 2, 3, 4, 5, 6], rem(n, 2) == 0, do: n
|
||||
[2, 4, 6]
|
||||
|
||||
Bit string generators are quite useful when you need to
|
||||
organize bit string streams:
|
||||
Bitstring generators are also supported and are very useful when you
|
||||
need to organize bitstring streams:
|
||||
|
||||
iex> pixels = <<213, 45, 132, 64, 76, 32, 76, 0, 0, 234, 32, 15>>
|
||||
iex> lc <<r::8, g::8, b::8>> inbits pixels, do: {r, g, b}
|
||||
iex> for <<r::8, g::8, b::8 <- pixels >>, do: {r, g, b}
|
||||
[{213,45,132},{64,76,32},{76,0,0},{234,32,15}]
|
||||
|
||||
Variable assignments in the `do` block of the comprehension
|
||||
are not reflected outside of the block.
|
||||
Variable assignments inside the comprehension, be it in generators,
|
||||
filters or inside the block, are not reflected outside of the
|
||||
comprehension.
|
||||
|
||||
Note: Unlike Erlang, Elixir comprehension filters
|
||||
never behave as guards when it comes to errors. Errors in
|
||||
list comprehensions will always be raised. Consider this
|
||||
Erlang example:
|
||||
## Into
|
||||
|
||||
erl> [I || I <- [1,2,3], hd(I)].
|
||||
[]
|
||||
In the examples above, the result returned by the comprehension was
|
||||
always a list. The returned result can be configured by passing an
|
||||
`:into` option, that accepts any structure as long as it implements
|
||||
the `Collectable` protocol.
|
||||
|
||||
In Elixir, it will raise:
|
||||
For example, we can use bitstring generators with the `:into` option
|
||||
to easily remove all spaces in a string:
|
||||
|
||||
iex> lc i inlist [1,2,3], hd(i), do: i
|
||||
** (ArgumentError) argument error
|
||||
|
||||
"""
|
||||
defmacro lc(args)
|
||||
|
||||
@doc """
|
||||
Defines a bit comprehension.
|
||||
|
||||
It follows the same syntax and behaviour as a list comprehension
|
||||
but expects each element returned to be a bitstring. For example,
|
||||
here is how to remove all spaces from a string:
|
||||
|
||||
iex> bc <<c>> inbits " hello world ", c != ? , do: <<c>>
|
||||
iex> for <<c <- " hello world ">>, c != ?\s, into: "", do: <<c>>
|
||||
"helloworld"
|
||||
|
||||
The `IO` module provides streams, that are both `Enumerable` and
|
||||
`Collectable`, here is an upcase echo server using comprehensions:
|
||||
|
||||
for line <- IO.stream(:stdio, :line), into: IO.stream(:stdio, :line) do
|
||||
String.upcase(line)
|
||||
end
|
||||
|
||||
"""
|
||||
defmacro bc(args)
|
||||
defmacro for(args)
|
||||
|
||||
@doc """
|
||||
Defines an anonymous function.
|
||||
@@ -1105,10 +1217,12 @@ defmodule Kernel.SpecialForms do
|
||||
defmacro __block__(args)
|
||||
|
||||
@doc """
|
||||
Captures an anonymous function.
|
||||
Captures or creates an anonymous function.
|
||||
|
||||
The most common format to capture a function is via module,
|
||||
name and arity:
|
||||
## Capture
|
||||
|
||||
The capture operator is most commonly used to capture a
|
||||
function with given name and arity from a module:
|
||||
|
||||
iex> fun = &Kernel.is_atom/1
|
||||
iex> fun.(:atom)
|
||||
@@ -1116,47 +1230,39 @@ defmodule Kernel.SpecialForms do
|
||||
iex> fun.("string")
|
||||
false
|
||||
|
||||
Local functions, including private ones, and imported ones
|
||||
can also be captured by omitting the module name:
|
||||
In the example above, we captured `Kernel.is_atom/1` as an
|
||||
anonymous function and then invoked it.
|
||||
|
||||
The capture operator can also be used to capture local functions,
|
||||
including private ones, and imported functions by omitting the
|
||||
module name:
|
||||
|
||||
&local_function/1
|
||||
|
||||
A capture also allows the captured functions to be partially
|
||||
applied, for example:
|
||||
## Anonymous functions
|
||||
|
||||
iex> fun = &is_record(&1, Range)
|
||||
iex> fun.(1..3)
|
||||
The capture operator can be also be used to partially apply
|
||||
functions, where `&1`, `&2` and so on can be used as value
|
||||
placeholders. For example:
|
||||
|
||||
iex> double = &(&1 * 2)
|
||||
iex> double.(2)
|
||||
4
|
||||
|
||||
In other words, `&(&1 * 2)` is equivalent to `fn x -> x * 2 end`.
|
||||
Another example using a local function:
|
||||
|
||||
iex> fun = &is_atom(&1)
|
||||
iex> fun.(:atom)
|
||||
true
|
||||
|
||||
In the example above, we use &1 as a placeholder, generating
|
||||
a function with one argument. We could also use `&2` and `&3`
|
||||
to delimit more arguments:
|
||||
|
||||
iex> fun = &is_record(&1, &2)
|
||||
iex> fun.(1..3, Range)
|
||||
true
|
||||
|
||||
Since operators are calls, they are also supported, although
|
||||
they require explicit parentheses around:
|
||||
|
||||
iex> fun = &(&1 + &2)
|
||||
iex> fun.(1, 2)
|
||||
3
|
||||
|
||||
And even more complex call expressions:
|
||||
The `&` operator can be used with more complex expressions:
|
||||
|
||||
iex> fun = &(&1 + &2 + &3)
|
||||
iex> fun.(1, 2, 3)
|
||||
6
|
||||
|
||||
Record-like calls are also allowed:
|
||||
|
||||
iex> fun = &(&1.first)
|
||||
iex> fun.(1..3)
|
||||
1
|
||||
|
||||
Remember tuple and lists are represented as calls in the AST and
|
||||
therefore are also allowed:
|
||||
As well as with lists and tuples:
|
||||
|
||||
iex> fun = &{&1, &2}
|
||||
iex> fun.(1, 2)
|
||||
@@ -1166,16 +1272,14 @@ defmodule Kernel.SpecialForms do
|
||||
iex> fun.(1, 2)
|
||||
[1|2]
|
||||
|
||||
Anything that is not a call is not allowed though, examples:
|
||||
The only restrictions when creating anonymous functions is that at
|
||||
least one placeholder must be present, i.e. it must contain at least
|
||||
`&1`:
|
||||
|
||||
# An atom is not a call
|
||||
&:foo
|
||||
|
||||
# A var is not a call
|
||||
var = 1
|
||||
# No placeholder fails to compile
|
||||
&var
|
||||
|
||||
# A block expression is not a call
|
||||
# Block expressions are also not supported
|
||||
&(foo(&1, &2); &3 + &4)
|
||||
|
||||
"""
|
||||
@@ -1273,7 +1377,7 @@ defmodule Kernel.SpecialForms do
|
||||
"""
|
||||
defmacro case(condition, blocks)
|
||||
|
||||
@doc """
|
||||
@doc ~S"""
|
||||
Evaluate the given expressions and handle any error, exit
|
||||
or throw that may have happened.
|
||||
|
||||
@@ -1286,10 +1390,10 @@ defmodule Kernel.SpecialForms do
|
||||
IO.puts "Invalid argument given"
|
||||
catch
|
||||
value ->
|
||||
IO.puts "caught \#{value}"
|
||||
IO.puts "caught #{value}"
|
||||
else
|
||||
value ->
|
||||
IO.puts "Success! The result was \#{value}"
|
||||
IO.puts "Success! The result was #{value}"
|
||||
after
|
||||
IO.puts "This is printed regardless if it failed or succeed"
|
||||
end
|
||||
@@ -1335,10 +1439,39 @@ defmodule Kernel.SpecialForms do
|
||||
x -> nil
|
||||
end
|
||||
|
||||
## Catching exits and Erlang errors
|
||||
## Erlang errors
|
||||
|
||||
The catch clause works exactly the same as in erlang. Therefore,
|
||||
one can also handle exits/errors coming from Erlang as below:
|
||||
Erlang errors are transformed into Elixir ones during rescue:
|
||||
|
||||
try do
|
||||
:erlang.error(:badarg)
|
||||
rescue
|
||||
ArgumentError -> :ok
|
||||
end
|
||||
|
||||
The most common Erlang errors will be transformed into their
|
||||
Elixir counter-part. Those which are not will be transformed
|
||||
into `ErlangError`:
|
||||
|
||||
try do
|
||||
:erlang.error(:unknown)
|
||||
rescue
|
||||
ErlangError -> :ok
|
||||
end
|
||||
|
||||
In fact, ErlangError can be used to rescue any error that is
|
||||
not an Elixir error proper. For example, it can be used to rescue
|
||||
the earlier `:badarg` error too, prior to transformation:
|
||||
|
||||
try do
|
||||
:erlang.error(:badarg)
|
||||
rescue
|
||||
ErlangError -> :ok
|
||||
end
|
||||
|
||||
## Catching throws and exits
|
||||
|
||||
The catch clause can be used to catch throws values and exits.
|
||||
|
||||
try do
|
||||
exit(1)
|
||||
@@ -1347,14 +1480,14 @@ defmodule Kernel.SpecialForms do
|
||||
end
|
||||
|
||||
try do
|
||||
error(:sample)
|
||||
throw(:sample)
|
||||
catch
|
||||
:error, :sample ->
|
||||
IO.puts "sample error"
|
||||
:throw, :sample ->
|
||||
IO.puts "sample thrown"
|
||||
end
|
||||
|
||||
Although the second form should be avoided in favor of raise/rescue
|
||||
control mechanisms.
|
||||
catch values also support `:error`, as in Erlang, although it is
|
||||
commonly avoided in favor of raise/rescue control mechanisms.
|
||||
|
||||
## Else clauses
|
||||
|
||||
@@ -1428,7 +1561,7 @@ defmodule Kernel.SpecialForms do
|
||||
:exit_b
|
||||
end
|
||||
|
||||
This means the VM nolonger needs to keep the stacktrace once inside
|
||||
This means the VM no longer needs to keep the stacktrace once inside
|
||||
an else clause and so tail recursion is possible when using a `try`
|
||||
with a tail call as the final call inside an else clause. The same
|
||||
is true for rescue and catch clauses.
|
||||
@@ -1437,7 +1570,7 @@ defmodule Kernel.SpecialForms do
|
||||
|
||||
Since an expression inside `try` may not have been evaluated
|
||||
due to an exception, any variable created inside `try` cannot
|
||||
be accessed externaly. For instance:
|
||||
be accessed externally. For instance:
|
||||
|
||||
try do
|
||||
x = 1
|
||||
@@ -1499,7 +1632,7 @@ defmodule Kernel.SpecialForms do
|
||||
end
|
||||
|
||||
The `after` clause can be specified even if there are no match clauses.
|
||||
There are two special cases for the timout value given to after
|
||||
There are two special cases for the timeout value given to `after`
|
||||
|
||||
* `:infinity` - The process should wait indefinitely for a matching
|
||||
message, this is the same as not using a timeout.
|
||||
|
||||
@@ -298,7 +298,7 @@ defmodule Kernel.Typespec do
|
||||
if export do
|
||||
Module.add_doc(module, caller.line, kind, { name, arity }, doc)
|
||||
else
|
||||
:elixir_errors.warn "#{caller.file}:#{caller.line}: type #{name}/#{arity} is private, " <>
|
||||
:elixir_errors.warn caller.line, caller.file, "type #{name}/#{arity} is private, " <>
|
||||
"@typedoc's are always discarded for private types\n"
|
||||
end
|
||||
end
|
||||
@@ -361,13 +361,13 @@ defmodule Kernel.Typespec do
|
||||
|> Enum.uniq
|
||||
|> Enum.map(&{ &1, { :var, meta, nil } })
|
||||
|
||||
result = if vars == [] do
|
||||
typespec_to_ast(result)
|
||||
else
|
||||
{ :when, meta, [typespec_to_ast(result), vars] }
|
||||
end
|
||||
spec = { :::, meta, [body, typespec_to_ast(result)] }
|
||||
|
||||
{ :::, meta, [body, result] }
|
||||
if vars == [] do
|
||||
spec
|
||||
else
|
||||
{ :when, meta, [spec, vars] }
|
||||
end
|
||||
end
|
||||
|
||||
def spec_to_ast(name, { :type, line, :fun, [] }) do
|
||||
@@ -376,23 +376,23 @@ defmodule Kernel.Typespec do
|
||||
|
||||
def spec_to_ast(name, { :type, line, :bounded_fun, [{ :type, _, :fun, [{ :type, _, :product, args }, result] }, constraints] }) do
|
||||
guards =
|
||||
lc {:type, _, :constraint, [{:atom, _, :is_subtype}, [{ :var, _, var }, type]]} inlist constraints do
|
||||
for { :type, _, :constraint, [{ :atom, _, :is_subtype }, [{ :var, _, var }, type]] } <- constraints do
|
||||
{ var, typespec_to_ast(type) }
|
||||
end
|
||||
|
||||
meta = [line: line]
|
||||
|
||||
vars = args ++ [result]
|
||||
|> Enum.flat_map(&collect_vars/1)
|
||||
|> Enum.uniq
|
||||
|> Kernel.--(Keyword.keys(guards))
|
||||
|> Enum.map(&{ &1, { :var, meta, nil } })
|
||||
|> Enum.flat_map(&collect_vars/1)
|
||||
|> Enum.uniq
|
||||
|> Kernel.--(Keyword.keys(guards))
|
||||
|> Enum.map(&{ &1, { :var, meta, nil } })
|
||||
|
||||
args = lc arg inlist args, do: typespec_to_ast(arg)
|
||||
args = for arg <- args, do: typespec_to_ast(arg)
|
||||
|
||||
{ :::, meta, [
|
||||
{ name, [line: line], args },
|
||||
{ :when, meta, [typespec_to_ast(result), guards ++ vars] }
|
||||
{ :when, meta, [
|
||||
{ :::, meta, [{ name, [line: line], args }, typespec_to_ast(result)] },
|
||||
guards ++ vars
|
||||
] }
|
||||
end
|
||||
|
||||
@@ -400,14 +400,14 @@ defmodule Kernel.Typespec do
|
||||
Converts a type clause back to Elixir AST.
|
||||
"""
|
||||
def type_to_ast({ { :record, record }, fields, args }) when is_atom(record) do
|
||||
fields = lc field inlist fields, do: typespec_to_ast(field)
|
||||
args = lc arg inlist args, do: typespec_to_ast(arg)
|
||||
fields = for field <- fields, do: typespec_to_ast(field)
|
||||
args = for arg <- args, do: typespec_to_ast(arg)
|
||||
type = { :{}, [], [record|fields] }
|
||||
quote do: unquote(record)(unquote_splicing(args)) :: unquote(type)
|
||||
end
|
||||
|
||||
def type_to_ast({ name, type, args }) do
|
||||
args = lc arg inlist args, do: typespec_to_ast(arg)
|
||||
args = for arg <- args, do: typespec_to_ast(arg)
|
||||
quote do: unquote(name)(unquote_splicing(args)) :: unquote(typespec_to_ast(type))
|
||||
end
|
||||
|
||||
@@ -424,7 +424,7 @@ defmodule Kernel.Typespec do
|
||||
def beam_typedocs(module) when is_atom(module) or is_binary(module) do
|
||||
case abstract_code(module) do
|
||||
{ :ok, abstract_code } ->
|
||||
type_docs = lc { :attribute, _, :typedoc, tup } inlist abstract_code, do: tup
|
||||
type_docs = for { :attribute, _, :typedoc, tup } <- abstract_code, do: tup
|
||||
List.flatten(type_docs)
|
||||
_ ->
|
||||
nil
|
||||
@@ -444,10 +444,10 @@ defmodule Kernel.Typespec do
|
||||
def beam_types(module) when is_atom(module) or is_binary(module) do
|
||||
case abstract_code(module) do
|
||||
{ :ok, abstract_code } ->
|
||||
exported_types = lc { :attribute, _, :export_type, types } inlist abstract_code, do: types
|
||||
exported_types = for { :attribute, _, :export_type, types } <- abstract_code, do: types
|
||||
exported_types = List.flatten(exported_types)
|
||||
|
||||
lc { :attribute, _, kind, { name, _, args } = type } inlist abstract_code, kind in [:opaque, :type] do
|
||||
for { :attribute, _, kind, { name, _, args } = type } <- abstract_code, kind in [:opaque, :type] do
|
||||
cond do
|
||||
kind == :opaque -> { :opaque, type }
|
||||
{ name, length(args) } in exported_types -> { :type, type }
|
||||
@@ -490,7 +490,7 @@ defmodule Kernel.Typespec do
|
||||
defp from_abstract_code(module, kind) do
|
||||
case abstract_code(module) do
|
||||
{ :ok, abstract_code } ->
|
||||
lc { :attribute, _, abs_kind, value } inlist abstract_code, kind == abs_kind, do: value
|
||||
for { :attribute, _, abs_kind, value } <- abstract_code, kind == abs_kind, do: value
|
||||
:error ->
|
||||
nil
|
||||
end
|
||||
@@ -498,7 +498,7 @@ defmodule Kernel.Typespec do
|
||||
|
||||
defp abstract_code(module) do
|
||||
case :beam_lib.chunks(abstract_code_beam(module), [:abstract_code]) do
|
||||
{:ok, { _, [{ :abstract_code, { _raw_abstract_v1, abstract_code } }] } } ->
|
||||
{ :ok, { _, [{ :abstract_code, { _raw_abstract_v1, abstract_code } }] } } ->
|
||||
{ :ok, abstract_code }
|
||||
_ ->
|
||||
:error
|
||||
@@ -519,13 +519,21 @@ defmodule Kernel.Typespec do
|
||||
## Macro callbacks
|
||||
|
||||
@doc false
|
||||
def deftype(kind, { :::, _, [type, definition] }, caller) do
|
||||
do_deftype(kind, type, definition, caller)
|
||||
end
|
||||
def deftype(kind, { :::, _, [{ name, _, args }, definition] }, caller) when is_atom(name) and name != ::: do
|
||||
args =
|
||||
if is_atom(args) do
|
||||
[]
|
||||
else
|
||||
for(arg <- args, do: variable(arg))
|
||||
end
|
||||
|
||||
def deftype(kind, {name, _meta, args} = type, caller)
|
||||
when is_atom(name) and not is_list(args) do
|
||||
do_deftype(kind, type, { :term, [line: caller.line], nil }, caller)
|
||||
vars = for { :var, _, var } <- args, do: var
|
||||
spec = typespec(definition, vars, caller)
|
||||
|
||||
vars = for { :var, _, _ } = var <- args, do: var
|
||||
type = { name, spec, vars }
|
||||
|
||||
define_type(caller, kind, type)
|
||||
end
|
||||
|
||||
def deftype(_kind, other, caller) do
|
||||
@@ -533,27 +541,18 @@ defmodule Kernel.Typespec do
|
||||
compile_error caller, "invalid type specification: #{type_spec}"
|
||||
end
|
||||
|
||||
defp do_deftype(kind, { name, _, args }, definition, caller) do
|
||||
args =
|
||||
if is_atom(args) do
|
||||
[]
|
||||
else
|
||||
lc(arg inlist args, do: variable(arg))
|
||||
end
|
||||
@doc false
|
||||
|
||||
vars = lc { :var, _, var } inlist args, do: var
|
||||
spec = typespec(definition, vars, caller)
|
||||
|
||||
vars = lc { :var, _, _ } = var inlist args, do: var
|
||||
type = { name, spec, vars }
|
||||
|
||||
define_type(caller, kind, type)
|
||||
def defspec(type, { :when, _meta, [spec, guard] }, caller) do
|
||||
defspec(type, spec, guard, caller)
|
||||
end
|
||||
|
||||
@doc false
|
||||
def defspec(type, { :::, meta, [{ name, _, args }, return_and_guard] }, caller) do
|
||||
def defspec(type, spec, caller) do
|
||||
defspec(type, spec, [], caller)
|
||||
end
|
||||
|
||||
defp defspec(type, { :::, meta, [{ name, _, args }, return] }, guard, caller) when is_atom(name) and name != ::: do
|
||||
if is_atom(args), do: args = []
|
||||
{ return, guard } = split_return_and_guard(return_and_guard)
|
||||
|
||||
unless Keyword.keyword?(guard) do
|
||||
guard = Macro.to_string(guard)
|
||||
@@ -573,24 +572,11 @@ defmodule Kernel.Typespec do
|
||||
code
|
||||
end
|
||||
|
||||
def defspec(_type, other, caller) do
|
||||
spec = Macro.to_string(other)
|
||||
defp defspec(_type, spec, _guard, caller) do
|
||||
spec = Macro.to_string(spec)
|
||||
compile_error caller, "invalid function type specification: #{spec}"
|
||||
end
|
||||
|
||||
defp split_return_and_guard({ :when, _, [return, guard] }) do
|
||||
{ return, guard }
|
||||
end
|
||||
|
||||
defp split_return_and_guard({ :|, meta, [left, right] }) do
|
||||
{ return, guard } = split_return_and_guard(right)
|
||||
{ { :|, meta, [left, return] }, guard }
|
||||
end
|
||||
|
||||
defp split_return_and_guard(other) do
|
||||
{ other, [] }
|
||||
end
|
||||
|
||||
defp guard_to_constraints(guard, vars, meta, caller) do
|
||||
line = line(meta)
|
||||
|
||||
@@ -622,11 +608,11 @@ defmodule Kernel.Typespec do
|
||||
collect_vars(type)
|
||||
end
|
||||
|
||||
defp collect_vars({:paren_type, _line, [type]}) do
|
||||
defp collect_vars({ :paren_type, _line, [type] }) do
|
||||
collect_vars(type)
|
||||
end
|
||||
|
||||
defp collect_vars({:var, _line, var}) do
|
||||
defp collect_vars({ :var, _line, var }) do
|
||||
[erl_to_ex_var(var)]
|
||||
end
|
||||
|
||||
@@ -639,7 +625,7 @@ defmodule Kernel.Typespec do
|
||||
end
|
||||
|
||||
defp typespec_to_ast({ :type, line, :tuple, args }) do
|
||||
args = lc arg inlist args, do: typespec_to_ast(arg)
|
||||
args = for arg <- args, do: typespec_to_ast(arg)
|
||||
{ :{}, [line: line], args }
|
||||
end
|
||||
|
||||
@@ -651,11 +637,26 @@ defmodule Kernel.Typespec do
|
||||
end
|
||||
|
||||
defp typespec_to_ast({ :type, _line, :list, args }) do
|
||||
lc arg inlist args, do: typespec_to_ast(arg)
|
||||
for arg <- args, do: typespec_to_ast(arg)
|
||||
end
|
||||
|
||||
defp typespec_to_ast({ :type, line, :map, fields }) do
|
||||
fields = Enum.map fields, fn { :type, _, :map_field_assoc, k, v } ->
|
||||
{ typespec_to_ast(k), typespec_to_ast(v) }
|
||||
end
|
||||
|
||||
{ struct, fields } = Keyword.pop(fields, :__struct__)
|
||||
map = { :%{}, [line: line], fields }
|
||||
|
||||
if struct do
|
||||
{ :%, [line: line], [struct, map] }
|
||||
else
|
||||
map
|
||||
end
|
||||
end
|
||||
|
||||
defp typespec_to_ast({ :type, line, :binary, [arg1, arg2] }) do
|
||||
[arg1, arg2] = lc arg inlist [arg1, arg2], do: typespec_to_ast(arg)
|
||||
[arg1, arg2] = for arg <- [arg1, arg2], do: typespec_to_ast(arg)
|
||||
cond do
|
||||
arg2 == 0 ->
|
||||
quote line: line, do: <<_ :: unquote(arg1)>>
|
||||
@@ -667,12 +668,12 @@ defmodule Kernel.Typespec do
|
||||
end
|
||||
|
||||
defp typespec_to_ast({ :type, line, :union, args }) do
|
||||
args = lc arg inlist args, do: typespec_to_ast(arg)
|
||||
args = for arg <- args, do: typespec_to_ast(arg)
|
||||
Enum.reduce Enum.reverse(args), fn(arg, expr) -> { :|, [line: line], [arg, expr] } end
|
||||
end
|
||||
|
||||
defp typespec_to_ast({ :type, line, :fun, [{:type, _, :product, args}, result] }) do
|
||||
args = lc arg inlist args, do: typespec_to_ast(arg)
|
||||
args = for arg <- args, do: typespec_to_ast(arg)
|
||||
[{ :->, [line: line], [args, typespec_to_ast(result)] }]
|
||||
end
|
||||
|
||||
@@ -689,7 +690,7 @@ defmodule Kernel.Typespec do
|
||||
end
|
||||
|
||||
defp typespec_to_ast({ :type, line, name, args }) do
|
||||
args = lc arg inlist args, do: typespec_to_ast(arg)
|
||||
args = for arg <- args, do: typespec_to_ast(arg)
|
||||
{ name, [line: line], args }
|
||||
end
|
||||
|
||||
@@ -711,7 +712,7 @@ defmodule Kernel.Typespec do
|
||||
end
|
||||
|
||||
defp typespec_to_ast({ :remote_type, line, [mod, name, args] }) do
|
||||
args = lc arg inlist args, do: typespec_to_ast(arg)
|
||||
args = for arg <- args, do: typespec_to_ast(arg)
|
||||
dot = { :., [line: line], [typespec_to_ast(mod), typespec_to_ast(name)] }
|
||||
{ dot, [line: line], args }
|
||||
end
|
||||
@@ -721,16 +722,16 @@ defmodule Kernel.Typespec do
|
||||
end
|
||||
|
||||
defp typespec_to_ast({ :typed_record_field,
|
||||
{ :record_field, line, { :atom, line1, name }},
|
||||
{ :record_field, line, { :atom, line1, name } },
|
||||
type }) do
|
||||
typespec_to_ast({ :ann_type, line, [{ :var, line1, name }, type] })
|
||||
end
|
||||
|
||||
defp typespec_to_ast({:type, _, :any}) do
|
||||
defp typespec_to_ast({ :type, _, :any }) do
|
||||
quote do: ...
|
||||
end
|
||||
|
||||
defp typespec_to_ast({:paren_type, _, [type]}) do
|
||||
defp typespec_to_ast({ :paren_type, _, [type] }) do
|
||||
typespec_to_ast(type)
|
||||
end
|
||||
|
||||
@@ -761,7 +762,7 @@ defmodule Kernel.Typespec do
|
||||
# Handle unions
|
||||
defp typespec({ :|, meta, [_, _] } = exprs, vars, caller) do
|
||||
exprs = collect_union(exprs)
|
||||
union = lc e inlist exprs, do: typespec(e, vars, caller)
|
||||
union = for e <- exprs, do: typespec(e, vars, caller)
|
||||
{ :type, line(meta), :union, union }
|
||||
end
|
||||
|
||||
@@ -778,8 +779,20 @@ defmodule Kernel.Typespec do
|
||||
{:type, line(meta), :binary, [{:integer, line(meta1), base}, {:integer, line(meta2), 0}]}
|
||||
end
|
||||
|
||||
## Handle maps and structs
|
||||
defp typespec({:%{}, meta, fields}, vars, caller) do
|
||||
fields = Enum.map(fields, fn { k, v } ->
|
||||
{:type, line(meta), :map_field_assoc, typespec(k, vars, caller), typespec(v, vars, caller)}
|
||||
end)
|
||||
{:type, line(meta), :map, fields}
|
||||
end
|
||||
|
||||
defp typespec({:%, _, [name, {:%{}, meta, fields}]}, vars, caller) do
|
||||
typespec({:%{}, meta, [{:__struct__, name}|fields]}, vars, caller)
|
||||
end
|
||||
|
||||
# Handle ranges
|
||||
defp typespec({:"..", meta, args}, vars, caller) do
|
||||
defp typespec({:.., meta, args}, vars, caller) do
|
||||
typespec({:range, meta, args}, vars, caller)
|
||||
end
|
||||
|
||||
@@ -800,7 +813,7 @@ defmodule Kernel.Typespec do
|
||||
end
|
||||
|
||||
# Handle type operator
|
||||
defp typespec({:"::", meta, [var, expr] }, vars, caller) do
|
||||
defp typespec({:::, meta, [var, expr] }, vars, caller) do
|
||||
left = typespec(var, [elem(var, 0)|vars], caller)
|
||||
right = typespec(expr, vars, caller)
|
||||
{ :ann_type, line(meta), [left, right] }
|
||||
@@ -833,7 +846,7 @@ defmodule Kernel.Typespec do
|
||||
end
|
||||
|
||||
defp typespec({:{}, meta, t}, vars, caller) when is_list(t) do
|
||||
args = lc e inlist t, do: typespec(e, vars, caller)
|
||||
args = for e <- t, do: typespec(e, vars, caller)
|
||||
{ :type, line(meta), :tuple, args }
|
||||
end
|
||||
|
||||
@@ -857,9 +870,9 @@ defmodule Kernel.Typespec do
|
||||
|
||||
# Handle local calls
|
||||
defp typespec({:string, meta, arguments}, vars, caller) do
|
||||
:elixir_errors.warn "warning: string() type use is discouraged. For character lists, use " <>
|
||||
:elixir_errors.warn caller.line, caller.file, "string() type use is discouraged. For character lists, use " <>
|
||||
"char_list() type, for strings, String.t()\n#{Exception.format_stacktrace(caller.stacktrace)}"
|
||||
arguments = lc arg inlist arguments, do: typespec(arg, vars, caller)
|
||||
arguments = for arg <- arguments, do: typespec(arg, vars, caller)
|
||||
{ :type, line(meta), :string, arguments }
|
||||
end
|
||||
|
||||
@@ -872,7 +885,7 @@ defmodule Kernel.Typespec do
|
||||
end
|
||||
|
||||
defp typespec({name, meta, arguments}, vars, caller) do
|
||||
arguments = lc arg inlist arguments, do: typespec(arg, vars, caller)
|
||||
arguments = for arg <- arguments, do: typespec(arg, vars, caller)
|
||||
{ :type, line(meta), name, arguments }
|
||||
end
|
||||
|
||||
@@ -912,7 +925,7 @@ defmodule Kernel.Typespec do
|
||||
end
|
||||
|
||||
defp remote_type({remote, meta, name, arguments}, vars, caller) do
|
||||
arguments = lc arg inlist arguments, do: typespec(arg, vars, caller)
|
||||
arguments = for arg <- arguments, do: typespec(arg, vars, caller)
|
||||
{ :remote_type, line(meta), [ remote, name, arguments ] }
|
||||
end
|
||||
|
||||
@@ -936,7 +949,7 @@ defmodule Kernel.Typespec do
|
||||
end
|
||||
|
||||
defp fn_args(meta, args, vars, caller) do
|
||||
args = lc arg inlist args, do: typespec(arg, vars, caller)
|
||||
args = for arg <- args, do: typespec(arg, vars, caller)
|
||||
{ :type, line(meta), :product, args }
|
||||
end
|
||||
|
||||
|
||||
+57
-46
@@ -36,6 +36,7 @@ defmodule Keyword do
|
||||
"""
|
||||
@spec from_enum(Enum.t) :: t
|
||||
def from_enum(enum) do
|
||||
IO.write :stderr, "Keyword.from_enum/1 is deprecated, please use Enum.into/2 instead\n#{Exception.format_stacktrace}"
|
||||
Enum.to_list(enum)
|
||||
end
|
||||
|
||||
@@ -59,8 +60,11 @@ defmodule Keyword do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Creates a Keyword from an enumerable. Similar to dicts,
|
||||
duplicated entries are removed, the latest one prevails.
|
||||
Creates a keyword from an enumerable.
|
||||
|
||||
Duplicated entries are removed, the latest one prevails.
|
||||
I.e. differently from `Enum.into(enumerable, [])`,
|
||||
`Keyword.new(enumerable)` guarantees the keys are unique.
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -76,9 +80,11 @@ defmodule Keyword do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Creates a Keyword from an enumerable with the
|
||||
help of the transformation function. Duplicated
|
||||
entries are removed, the latest one prevails.
|
||||
Creates a keyword from an enumerable via the transformation function.
|
||||
|
||||
Duplicated entries are removed, the latest one prevails.
|
||||
I.e. differently from `Enum.into(enumerable, [], fun)`,
|
||||
`Keyword.new(enumerable, fun)` guarantees the keys are unique.
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -116,7 +122,7 @@ defmodule Keyword do
|
||||
"""
|
||||
@spec get(t, key) :: value
|
||||
@spec get(t, key, value) :: value
|
||||
def get(keywords, key, default // nil) when is_atom(key) do
|
||||
def get(keywords, key, default \\ nil) when is_list(keywords) and is_atom(key) do
|
||||
case :lists.keyfind(key, 1, keywords) do
|
||||
{ ^key, value } -> value
|
||||
false -> default
|
||||
@@ -137,7 +143,7 @@ defmodule Keyword do
|
||||
|
||||
"""
|
||||
@spec fetch(t, key) :: value
|
||||
def fetch(keywords, key) when is_atom(key) do
|
||||
def fetch(keywords, key) when is_list(keywords) and is_atom(key) do
|
||||
case :lists.keyfind(key, 1, keywords) do
|
||||
{ ^key, value } -> { :ok, value }
|
||||
false -> :error
|
||||
@@ -154,14 +160,14 @@ defmodule Keyword do
|
||||
1
|
||||
|
||||
iex> Keyword.fetch!([a: 1], :b)
|
||||
** (KeyError) key not found: :b
|
||||
** (KeyError) key :b not found in: [a: 1]
|
||||
|
||||
"""
|
||||
@spec fetch!(t, key) :: value | no_return
|
||||
def fetch!(keywords, key) when is_atom(key) do
|
||||
def fetch!(keywords, key) when is_list(keywords) and is_atom(key) do
|
||||
case :lists.keyfind(key, 1, keywords) do
|
||||
{ ^key, value } -> value
|
||||
false -> raise(KeyError, key: key)
|
||||
false -> raise(KeyError, key: key, term: keywords)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -175,8 +181,8 @@ defmodule Keyword do
|
||||
|
||||
"""
|
||||
@spec get_values(t, key) :: [value]
|
||||
def get_values(keywords, key) when is_atom(key) do
|
||||
lc { k, v } inlist keywords, key == k, do: v
|
||||
def get_values(keywords, key) when is_list(keywords) and is_atom(key) do
|
||||
for { k, v } <- keywords, key == k, do: v
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -193,8 +199,8 @@ defmodule Keyword do
|
||||
|
||||
"""
|
||||
@spec keys(t) :: [key]
|
||||
def keys(keywords) do
|
||||
lc { key, _ } inlist keywords, do: key
|
||||
def keys(keywords) when is_list(keywords) do
|
||||
for { key, _ } <- keywords, do: key
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -207,8 +213,8 @@ defmodule Keyword do
|
||||
|
||||
"""
|
||||
@spec values(t) :: [value]
|
||||
def values(keywords) do
|
||||
lc { _, value } inlist keywords, do: value
|
||||
def values(keywords) when is_list(keywords) do
|
||||
for { _, value } <- keywords, do: value
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -228,8 +234,8 @@ defmodule Keyword do
|
||||
|
||||
"""
|
||||
@spec delete(t, key, value) :: t
|
||||
def delete(keywords, key, value) when is_atom(key) do
|
||||
lc { k, v } = tuple inlist keywords, key != k or value != v, do: tuple
|
||||
def delete(keywords, key, value) when is_list(keywords) and is_atom(key) do
|
||||
for { k, v } = tuple <- keywords, key != k or value != v, do: tuple
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -251,8 +257,8 @@ defmodule Keyword do
|
||||
|
||||
"""
|
||||
@spec delete(t, key) :: t
|
||||
def delete(keywords, key) when is_atom(key) do
|
||||
lc { k, _ } = tuple inlist keywords, key != k, do: tuple
|
||||
def delete(keywords, key) when is_list(keywords) and is_atom(key) do
|
||||
for { k, _ } = tuple <- keywords, key != k, do: tuple
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -269,7 +275,7 @@ defmodule Keyword do
|
||||
|
||||
"""
|
||||
@spec delete_first(t, key) :: t
|
||||
def delete_first(keywords, key) when is_atom(key) do
|
||||
def delete_first(keywords, key) when is_list(keywords) and is_atom(key) do
|
||||
:lists.keydelete(key, 1, keywords)
|
||||
end
|
||||
|
||||
@@ -289,7 +295,7 @@ defmodule Keyword do
|
||||
|
||||
"""
|
||||
@spec put(t, key, value) :: t
|
||||
def put(keywords, key, value) when is_atom(key) do
|
||||
def put(keywords, key, value) when is_list(keywords) and is_atom(key) do
|
||||
[{key, value}|delete(keywords, key)]
|
||||
end
|
||||
|
||||
@@ -298,6 +304,7 @@ defmodule Keyword do
|
||||
already exists.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Keyword.put_new([a: 1], :b, 2)
|
||||
[b: 2, a: 1]
|
||||
|
||||
@@ -306,7 +313,7 @@ defmodule Keyword do
|
||||
|
||||
"""
|
||||
@spec put_new(t, key, value) :: t
|
||||
def put_new(keywords, key, value) when is_atom(key) do
|
||||
def put_new(keywords, key, value) when is_list(keywords) and is_atom(key) do
|
||||
case :lists.keyfind(key, 1, keywords) do
|
||||
{ ^key, _ } -> keywords
|
||||
false -> [{key, value}|keywords]
|
||||
@@ -324,7 +331,7 @@ defmodule Keyword do
|
||||
|
||||
"""
|
||||
@spec equal?(t, t) :: boolean
|
||||
def equal?(left, right) do
|
||||
def equal?(left, right) when is_list(left) and is_list(right) do
|
||||
:lists.sort(left) == :lists.sort(right)
|
||||
end
|
||||
|
||||
@@ -339,8 +346,8 @@ defmodule Keyword do
|
||||
|
||||
"""
|
||||
@spec merge(t, t) :: t
|
||||
def merge(d1, d2) do
|
||||
d2 ++ lc({ k, _ } = tuple inlist d1, not has_key?(d2, k), do: tuple)
|
||||
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)
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -351,12 +358,12 @@ defmodule Keyword do
|
||||
|
||||
iex> Keyword.merge([a: 1, b: 2], [a: 3, d: 4], fn (_k, v1, v2) ->
|
||||
...> v1 + v2
|
||||
iex> end)
|
||||
...> end)
|
||||
[a: 4, b: 2, d: 4]
|
||||
|
||||
"""
|
||||
@spec merge(t, t, (key, value, value -> value)) :: t
|
||||
def merge(d1, d2, fun) do
|
||||
def merge(d1, d2, fun) when is_list(d1) and is_list(d2) do
|
||||
do_merge(d2, d1, fun)
|
||||
end
|
||||
|
||||
@@ -381,7 +388,7 @@ defmodule Keyword do
|
||||
|
||||
"""
|
||||
@spec has_key?(t, key) :: boolean
|
||||
def has_key?(keywords, key) when is_atom(key) do
|
||||
def has_key?(keywords, key) when is_list(keywords) and is_atom(key) do
|
||||
:lists.keymember(key, 1, keywords)
|
||||
end
|
||||
|
||||
@@ -395,20 +402,24 @@ defmodule Keyword do
|
||||
[a: 2]
|
||||
|
||||
iex> Keyword.update!([a: 1], :b, &(&1 * 2))
|
||||
** (KeyError) key not found: :b
|
||||
** (KeyError) key :b not found in: [a: 1]
|
||||
|
||||
"""
|
||||
@spec update!(t, key, (value -> value)) :: t | no_return
|
||||
def update!([{key, value}|keywords], key, fun) do
|
||||
def update!(keywords, key, fun) do
|
||||
update!(keywords, key, fun, keywords)
|
||||
end
|
||||
|
||||
defp update!([{key, value}|keywords], key, fun, _dict) do
|
||||
[{key, fun.(value)}|delete(keywords, key)]
|
||||
end
|
||||
|
||||
def update!([{_, _} = e|keywords], key, fun) do
|
||||
[e|update!(keywords, key, fun)]
|
||||
defp update!([{_, _} = e|keywords], key, fun, dict) do
|
||||
[e|update!(keywords, key, fun, dict)]
|
||||
end
|
||||
|
||||
def update!([], key, _fun) when is_atom(key) do
|
||||
raise(KeyError, key: key)
|
||||
defp update!([], key, _fun, dict) when is_atom(key) do
|
||||
raise(KeyError, key: key, term: dict)
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -452,11 +463,11 @@ defmodule Keyword do
|
||||
{ [a: 1, c: 3, a: 5], [b: 2, d: 4] }
|
||||
|
||||
"""
|
||||
def split(dict, keys) do
|
||||
def split(keywords, keys) when is_list(keywords) do
|
||||
acc = { [], [] }
|
||||
|
||||
{ take, drop } = Enum.reduce dict, acc, fn({ k, v }, { take, drop }) ->
|
||||
case :lists.member(k, keys) do
|
||||
{ take, drop } = Enum.reduce keywords, acc, fn({ k, v }, { take, drop }) ->
|
||||
case k in keys do
|
||||
true -> { [{k, v}|take], drop }
|
||||
false -> { take, [{k, v}|drop] }
|
||||
end
|
||||
@@ -480,8 +491,8 @@ defmodule Keyword do
|
||||
[a: 1, c: 3, a: 5]
|
||||
|
||||
"""
|
||||
def take(dict, keys) do
|
||||
lc { k, _ } = tuple inlist dict, :lists.member(k, keys), do: tuple
|
||||
def take(keywords, keys) when is_list(keywords) do
|
||||
for { k, _ } = tuple <- keywords, k in keys, do: tuple
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -499,8 +510,8 @@ defmodule Keyword do
|
||||
[a: 1, c: 3, a: 5]
|
||||
|
||||
"""
|
||||
def drop(dict, keys) do
|
||||
lc { k, _ } = tuple inlist dict, not :lists.member(k, keys), do: tuple
|
||||
def drop(keywords, keys) when is_list(keywords) do
|
||||
for { k, _ } = tuple <- keywords, not k in keys, do: tuple
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -528,8 +539,8 @@ defmodule Keyword do
|
||||
{1,[]}
|
||||
|
||||
"""
|
||||
def pop(dict, key, default // nil) do
|
||||
{ get(dict, key, default), delete(dict, key) }
|
||||
def pop(keywords, key, default \\ nil) when is_list(keywords) do
|
||||
{ get(keywords, key, default), delete(keywords, key) }
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -557,7 +568,7 @@ defmodule Keyword do
|
||||
{1,[a: 2]}
|
||||
|
||||
"""
|
||||
def pop_first(dict, key, default // nil) do
|
||||
{ get(dict, key, default), delete_first(dict, key) }
|
||||
def pop_first(keywords, key, default \\ nil) when is_list(keywords) do
|
||||
{ get(keywords, key, default), delete_first(keywords, key) }
|
||||
end
|
||||
end
|
||||
|
||||
@@ -47,6 +47,7 @@ defmodule List do
|
||||
|
||||
iex> List.duplicate([1, 2], 2)
|
||||
[[1,2],[1,2]]
|
||||
|
||||
"""
|
||||
@spec duplicate(elem, non_neg_integer) :: [elem] when elem: var
|
||||
def duplicate(elem, n) do
|
||||
@@ -123,8 +124,10 @@ defmodule List do
|
||||
|
||||
iex> List.first([])
|
||||
nil
|
||||
|
||||
iex> List.first([1])
|
||||
1
|
||||
|
||||
iex> List.first([1, 2, 3])
|
||||
1
|
||||
|
||||
@@ -140,8 +143,10 @@ defmodule List do
|
||||
|
||||
iex> List.last([])
|
||||
nil
|
||||
|
||||
iex> List.last([1])
|
||||
1
|
||||
|
||||
iex> List.last([1, 2, 3])
|
||||
3
|
||||
|
||||
@@ -169,7 +174,7 @@ defmodule List do
|
||||
|
||||
"""
|
||||
@spec keyfind([tuple], any, non_neg_integer, any) :: any
|
||||
def keyfind(list, key, position, default // nil) do
|
||||
def keyfind(list, key, position, default \\ nil) do
|
||||
:lists.keyfind(key, position + 1, list) || default
|
||||
end
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
defprotocol List.Chars do
|
||||
@moduledoc %S"""
|
||||
@moduledoc ~S"""
|
||||
The List.Chars protocol is responsible for
|
||||
converting a structure to a list (only if applicable).
|
||||
The only function required to be implemented is
|
||||
|
||||
+33
-23
@@ -15,27 +15,24 @@ defmodule ListDict do
|
||||
"""
|
||||
def new, do: []
|
||||
|
||||
@doc """
|
||||
Creates a new `ListDict` from the given pairs.
|
||||
"""
|
||||
@doc false
|
||||
def new(pairs) do
|
||||
IO.write :stderr, "ListDict.new/1 is deprecated, please use Enum.into/2 instead\n#{Exception.format_stacktrace}"
|
||||
Enum.to_list pairs
|
||||
end
|
||||
|
||||
@doc """
|
||||
Creates a new `ListDict` from the given pairs
|
||||
via the given transformation function.
|
||||
"""
|
||||
@doc false
|
||||
def new(list, transform) when is_function(transform) do
|
||||
IO.write :stderr, "ListDict.new/2 is deprecated, please use Enum.into/3 instead\n#{Exception.format_stacktrace}"
|
||||
Enum.map list, transform
|
||||
end
|
||||
|
||||
def keys(dict) do
|
||||
lc { key, _ } inlist dict, do: key
|
||||
for { key, _ } <- dict, do: key
|
||||
end
|
||||
|
||||
def values(dict) do
|
||||
lc { _, value } inlist dict, do: value
|
||||
for { _, value } <- dict, do: value
|
||||
end
|
||||
|
||||
def size(dict) do
|
||||
@@ -47,7 +44,7 @@ defmodule ListDict do
|
||||
def has_key?([{ _, _ }|t], key), do: has_key?(t, key)
|
||||
def has_key?([], _key), do: false
|
||||
|
||||
def get(dict, key, default // nil)
|
||||
def get(dict, key, default \\ nil)
|
||||
def get([{ key, value }|_], key, _default), do: value
|
||||
def get([{ _, _ }|t], key, default), do: get(t, key, default)
|
||||
def get([], _key, default), do: default
|
||||
@@ -60,11 +57,11 @@ defmodule ListDict do
|
||||
def fetch!(dict, key) do
|
||||
case fetch(dict, key) do
|
||||
{ :ok, value } -> value
|
||||
:error -> raise(KeyError, key: key)
|
||||
:error -> raise(KeyError, key: key, term: dict)
|
||||
end
|
||||
end
|
||||
|
||||
def pop(dict, key, default // nil) do
|
||||
def pop(dict, key, default \\ nil) do
|
||||
{ get(dict, key, default), delete(dict, key) }
|
||||
end
|
||||
|
||||
@@ -84,7 +81,7 @@ defmodule ListDict do
|
||||
def delete([{ _, _ } = h|t], key), do: [h|delete(t, key)]
|
||||
def delete([], _key), do: []
|
||||
|
||||
def merge(dict, enum, callback // fn(_k, _v1, v2) -> v2 end) do
|
||||
def merge(dict, enum, callback \\ fn(_k, _v1, v2) -> v2 end) do
|
||||
Enum.reduce enum, dict, fn { k, v2 }, acc ->
|
||||
update(acc, k, v2, fn(v1) -> callback.(k, v1, v2) end)
|
||||
end
|
||||
@@ -94,7 +91,7 @@ defmodule ListDict do
|
||||
acc = { [], [] }
|
||||
|
||||
{take, drop} = Enum.reduce dict, acc, fn({ k, v }, { take, drop }) ->
|
||||
if :lists.member(k, keys) do
|
||||
if k in keys do
|
||||
{ [{k, v}|take], drop }
|
||||
else
|
||||
{ take, [{k, v}|drop] }
|
||||
@@ -105,23 +102,27 @@ defmodule ListDict do
|
||||
end
|
||||
|
||||
def take(dict, keys) do
|
||||
lc { k, _ } = tuple inlist dict, :lists.member(k, keys), do: tuple
|
||||
for { k, _ } = tuple <- dict, k in keys, do: tuple
|
||||
end
|
||||
|
||||
def drop(dict, keys) do
|
||||
lc { k, _ } = tuple inlist dict, not :lists.member(k, keys), do: tuple
|
||||
for { k, _ } = tuple <- dict, not k in keys, do: tuple
|
||||
end
|
||||
|
||||
def update!([{key, value}|dict], key, fun) do
|
||||
[{key, fun.(value)}|delete(dict, key)]
|
||||
def update!(list, key, fun) do
|
||||
update!(list, key, fun, list)
|
||||
end
|
||||
|
||||
def update!([{_, _} = e|dict], key, fun) do
|
||||
[e|update!(dict, key, fun)]
|
||||
defp update!([{key, value}|list], key, fun, _dict) do
|
||||
[{key, fun.(value)}|delete(list, key)]
|
||||
end
|
||||
|
||||
def update!([], key, _fun) do
|
||||
raise(KeyError, key: key)
|
||||
defp update!([{_, _} = e|list], key, fun, dict) do
|
||||
[e|update!(list, key, fun, dict)]
|
||||
end
|
||||
|
||||
defp update!([], key, _fun, dict) do
|
||||
raise(KeyError, key: key, term: dict)
|
||||
end
|
||||
|
||||
def update([{key, value}|dict], key, _initial, fun) do
|
||||
@@ -136,11 +137,20 @@ defmodule ListDict do
|
||||
[{key, initial}]
|
||||
end
|
||||
|
||||
def empty(_dict), do: []
|
||||
def empty(_dict) do
|
||||
IO.write :stderr, "ListDict.empty/1 is deprecated, please use Collectable.empty/1 instead\n#{Exception.format_stacktrace}"
|
||||
[]
|
||||
end
|
||||
|
||||
def equal?(dict, other) do
|
||||
:lists.keysort(1, dict) === :lists.keysort(1, other)
|
||||
end
|
||||
|
||||
@doc false
|
||||
def reduce(_, { :halt, acc }, _fun), do: { :halted, acc }
|
||||
def reduce(list, { :suspend, acc }, fun), do: { :suspended, acc, &reduce(list, &1, fun) }
|
||||
def reduce([], { :cont, acc }, _fun), do: { :done, acc }
|
||||
def reduce([{_,_}=h|t], { :cont, acc }, fun), do: reduce(t, fun.(h, acc), fun)
|
||||
|
||||
def to_list(dict), do: dict
|
||||
end
|
||||
|
||||
+74
-44
@@ -13,7 +13,7 @@ defmodule Macro do
|
||||
|
||||
@binary_ops [:===, :!==,
|
||||
:==, :!=, :<=, :>=,
|
||||
:&&, :||, :<>, :++, :--, :**, ://, :::, :<-, :.., :|>, :=~,
|
||||
:&&, :||, :<>, :++, :--, :\\, :::, :<-, :.., :|>, :=~,
|
||||
:<, :>, :->,
|
||||
:+, :-, :*, :/, :=, :|, :.,
|
||||
:and, :or, :xor, :when, :in, :inlist, :inbits,
|
||||
@@ -30,16 +30,16 @@ defmodule Macro do
|
||||
@spec binary_op_props(atom) :: { :left | :right, precedence :: integer }
|
||||
defp binary_op_props(o) do
|
||||
case o do
|
||||
::: -> {:right, 40}
|
||||
o when o in [:inlist, :inbits, ://, :|] -> {:right, 50}
|
||||
o when o in [:<-, :inlist, :inbits, :\\, :::] -> {:left, 40}
|
||||
:| -> {:right, 50}
|
||||
:when -> {:right, 70}
|
||||
:= -> {:right, 80}
|
||||
o when o in [:||, :|||, :or, :xor] -> {:left, 130}
|
||||
o when o in [:&&, :&&&, :and] -> {:left, 140}
|
||||
o when o in [:==, :!=, :<, :<=, :>=, :>, :=~, :===, :!==] -> {:left, 150}
|
||||
o when o in [:<-, :|>, :<<<, :>>>] -> {:right, 160}
|
||||
o when o in [:|>, :<<<, :>>>] -> {:right, 160}
|
||||
:in -> {:left, 170}
|
||||
o when o in [:++, :--, :**, :.., :<>] -> {:right, 200}
|
||||
o when o in [:++, :--, :.., :<>] -> {:right, 200}
|
||||
o when o in [:+, :-] -> {:left, 210}
|
||||
o when o in [:*, :/] -> {:left, 220}
|
||||
:^^^ -> {:left, 250}
|
||||
@@ -66,7 +66,11 @@ defmodule Macro do
|
||||
argument in the given `position`.
|
||||
"""
|
||||
@spec pipe(Macro.t, Macro.t, integer) :: Macro.t | no_return
|
||||
def pipe(expr, call_args, integer // 0)
|
||||
def pipe(expr, call_args, integer \\ 0)
|
||||
|
||||
def pipe(expr, { :&, _, _ } = call_args, _integer) do
|
||||
raise ArgumentError, message: "cannot pipe #{to_string expr} into #{to_string call_args}"
|
||||
end
|
||||
|
||||
def pipe(expr, { call, line, atom }, integer) when is_atom(atom) do
|
||||
{ call, line, List.insert_at([], integer, expr) }
|
||||
@@ -109,7 +113,7 @@ defmodule Macro do
|
||||
end
|
||||
|
||||
def update_meta(list, fun) when is_list(list) do
|
||||
lc x inlist list, do: update_meta(x, fun)
|
||||
for x <- list, do: update_meta(x, fun)
|
||||
end
|
||||
|
||||
def update_meta(other, _fun) do
|
||||
@@ -175,24 +179,23 @@ defmodule Macro do
|
||||
"""
|
||||
@spec escape(term) :: Macro.t
|
||||
@spec escape(term, Keyword.t) :: Macro.t
|
||||
def escape(expr, opts // []) do
|
||||
def escape(expr, opts \\ []) do
|
||||
elem(:elixir_quote.escape(expr, Keyword.get(opts, :unquote, false)), 0)
|
||||
end
|
||||
|
||||
@doc %S"""
|
||||
@doc ~S"""
|
||||
Unescape the given chars.
|
||||
|
||||
This is the unescaping behavior
|
||||
used by default in Elixir single- and double-quoted strings.
|
||||
Check `unescape_string/2` for information on how to customize
|
||||
the escaping map.
|
||||
This is the unescaping behaviour used by default in Elixir
|
||||
single- and double-quoted strings. Check `unescape_string/2`
|
||||
for information on how to customize the escaping map.
|
||||
|
||||
In this setup, Elixir will escape the following: `\a`, `\b`,
|
||||
`\d`, `\e`, `\f`, `\n`, `\r`, `\s`, `\t` and `\v`. Octals are
|
||||
also escaped according to the latin1 set they represent.
|
||||
|
||||
This function is commonly used on sigil implementations
|
||||
(like `%r`, `%s` and others) which receive a raw, unescaped
|
||||
(like `~r`, `~s` and others) which receive a raw, unescaped
|
||||
string.
|
||||
|
||||
## Examples
|
||||
@@ -208,7 +211,7 @@ defmodule Macro do
|
||||
:elixir_interpolation.unescape_chars(chars)
|
||||
end
|
||||
|
||||
@doc %S"""
|
||||
@doc ~S"""
|
||||
Unescape the given chars according to the map given.
|
||||
|
||||
Check `unescape_string/1` if you want to use the same map
|
||||
@@ -294,7 +297,7 @@ defmodule Macro do
|
||||
"""
|
||||
@spec to_string(Macro.t) :: String.t
|
||||
@spec to_string(Macro.t, (Macro.t, String.t -> String.t)) :: String.t
|
||||
def to_string(tree, fun // fn(_ast, string) -> string end)
|
||||
def to_string(tree, fun \\ fn(_ast, string) -> string end)
|
||||
|
||||
# Variables
|
||||
def to_string({ var, _, atom } = ast, fun) when is_atom(atom) do
|
||||
@@ -330,6 +333,18 @@ defmodule Macro do
|
||||
fun.(ast, tuple)
|
||||
end
|
||||
|
||||
# Map containers
|
||||
def to_string({ :%{}, _, args } = ast, fun) do
|
||||
map = "%{" <> map_to_string(args, fun) <> "}"
|
||||
fun.(ast, map)
|
||||
end
|
||||
|
||||
def to_string({ :%, _, [structname, map] } = ast, fun) do
|
||||
{ :%{}, _, args } = map
|
||||
struct = "%" <> to_string(structname, fun) <> "{" <> map_to_string(args, fun) <> "}"
|
||||
fun.(ast, struct)
|
||||
end
|
||||
|
||||
# Fn keyword
|
||||
def to_string({ :fn, _, [{ :->, _, [_, tuple] }] = arrow } = ast, fun)
|
||||
when not is_tuple(tuple) or elem(tuple, 0) != :__block__ do
|
||||
@@ -362,7 +377,7 @@ defmodule Macro do
|
||||
end
|
||||
|
||||
# Binary ops
|
||||
def to_string({ op, _, [left, right] } = ast, fun) when op in @binary_ops do
|
||||
def to_string({ op, _, [left, right] } = ast, fun) when op in unquote(@binary_ops) do
|
||||
fun.(ast, op_to_string(left, fun, op, :left) <> " #{op} " <> op_to_string(right, fun, op, :right))
|
||||
end
|
||||
|
||||
@@ -373,11 +388,16 @@ defmodule Macro do
|
||||
end
|
||||
|
||||
# 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) <> ")")
|
||||
end
|
||||
|
||||
def to_string({ :not, _, [arg] } = ast, fun) do
|
||||
fun.(ast, "not " <> to_string(arg, fun))
|
||||
end
|
||||
|
||||
def to_string({ op, _, [arg] } = ast, fun) when op in @unary_ops do
|
||||
def to_string({ op, _, [arg] } = ast, fun) when op in unquote(@unary_ops) do
|
||||
fun.(ast, atom_to_binary(op) <> to_string(arg, fun))
|
||||
end
|
||||
|
||||
@@ -421,7 +441,7 @@ defmodule Macro do
|
||||
@kw_keywords [:do, :catch, :rescue, :after, :else]
|
||||
|
||||
defp kw_blocks?([_|_] = kw) do
|
||||
Enum.all?(kw, &match?({x, _} when x in @kw_keywords, &1))
|
||||
Enum.all?(kw, &match?({x, _} when x in unquote(@kw_keywords), &1))
|
||||
end
|
||||
defp kw_blocks?(_), do: false
|
||||
|
||||
@@ -478,17 +498,34 @@ defmodule Macro do
|
||||
|
||||
defp block_to_string(other, fun), do: to_string(other, fun)
|
||||
|
||||
defp map_to_string([{:|, _, [update_map, update_args]}], fun) do
|
||||
to_string(update_map, fun) <> " | " <> map_to_string(update_args, fun)
|
||||
end
|
||||
|
||||
defp map_to_string(list, fun) do
|
||||
cond do
|
||||
Keyword.keyword?(list) -> kw_list_to_string(list, fun)
|
||||
true -> map_list_to_string(list, fun)
|
||||
end
|
||||
end
|
||||
|
||||
defp kw_list_to_string(list, fun) do
|
||||
Enum.map_join(list, ", ", fn { key, value } ->
|
||||
atom_to_binary(key) <> ": " <> to_string(value, fun)
|
||||
end)
|
||||
end
|
||||
|
||||
defp map_list_to_string(list, fun) do
|
||||
Enum.map_join(list, ", ", fn { key, value } ->
|
||||
to_string(key, fun) <> " => " <> to_string(value, fun)
|
||||
end)
|
||||
end
|
||||
|
||||
defp parenthise(expr, fun) do
|
||||
"(" <> to_string(expr, fun) <> ")"
|
||||
end
|
||||
|
||||
defp op_to_string({ op, _, [_, _] } = expr, fun, parent_op, side) when op in @binary_ops do
|
||||
defp op_to_string({ op, _, [_, _] } = expr, fun, parent_op, side) when op in unquote(@binary_ops) do
|
||||
{ parent_assoc, parent_prec } = binary_op_props(parent_op)
|
||||
{ _, prec } = binary_op_props(op)
|
||||
cond do
|
||||
@@ -506,7 +543,7 @@ defmodule Macro do
|
||||
|
||||
defp op_to_string(expr, fun, _, _), do: to_string(expr, fun)
|
||||
|
||||
defp arrow_to_string(pairs, fun, paren // false) do
|
||||
defp arrow_to_string(pairs, fun, paren \\ false) do
|
||||
Enum.map_join(pairs, "; ", fn({ :->, _, [left, right] }) ->
|
||||
left = comma_join_or_empty_paren(left, fun, paren)
|
||||
left <> "-> " <> to_string(right, fun)
|
||||
@@ -521,11 +558,11 @@ defmodule Macro do
|
||||
end
|
||||
|
||||
defp adjust_new_lines(block, replacement) do
|
||||
bc <<x>> inbits block do
|
||||
<< case x == ?\n do
|
||||
for <<x <- block>>, into: "" do
|
||||
case x == ?\n do
|
||||
true -> replacement
|
||||
false -> <<x>>
|
||||
end :: binary >>
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -615,7 +652,7 @@ defmodule Macro do
|
||||
:elixir_lexical.record_remote(receiver, env.lexical_tracker)
|
||||
{ receiver, true }
|
||||
aliases ->
|
||||
aliases = lc alias inlist aliases, do: elem(do_expand_once(alias, env), 0)
|
||||
aliases = for alias <- aliases, do: elem(do_expand_once(alias, env), 0)
|
||||
|
||||
case :lists.all(&is_atom/1, aliases) do
|
||||
true ->
|
||||
@@ -641,12 +678,6 @@ defmodule Macro do
|
||||
do: { env.module, true }
|
||||
defp do_expand_once({ :__DIR__, _, atom }, env) when is_atom(atom),
|
||||
do: { :filename.dirname(env.file), true }
|
||||
|
||||
defp do_expand_once({ :__FILE__, _, atom }, env) when is_atom(atom) do
|
||||
IO.write "__FILE__ is deprecated, please use __DIR__ or __ENV__.file instead\n#{Exception.format_stacktrace}"
|
||||
{ env.file, true }
|
||||
end
|
||||
|
||||
defp do_expand_once({ :__ENV__, _, atom }, env) when is_atom(atom),
|
||||
do: { { :{}, [], tuple_to_list(env) }, true }
|
||||
defp do_expand_once({ { :., _, [{ :__ENV__, _, atom }, field] }, _, [] } = original, env) when
|
||||
@@ -672,19 +703,18 @@ defmodule Macro do
|
||||
|
||||
defp do_expand_once({ atom, meta, args } = original, env)
|
||||
when is_atom(atom) and is_list(args) and is_list(meta) do
|
||||
module = env.module
|
||||
|
||||
extra = if function_exported?(module, :__info__, 1) do
|
||||
[{ module, module.__info__(:macros) }]
|
||||
else
|
||||
[]
|
||||
end
|
||||
|
||||
arity = length(args)
|
||||
|
||||
if :elixir_import.special_form(atom, arity) do
|
||||
{ original, false }
|
||||
else
|
||||
module = env.module
|
||||
extra = if function_exported?(module, :__info__, 1) do
|
||||
[{ module, module.__info__(:macros) }]
|
||||
else
|
||||
[]
|
||||
end
|
||||
|
||||
expand = :elixir_dispatch.expand_import(meta, { atom, length(args) }, args,
|
||||
:elixir_env.ex_to_env(env), extra)
|
||||
|
||||
@@ -724,11 +754,11 @@ defmodule Macro do
|
||||
defp do_expand_once(other, _env), do: { other, false }
|
||||
|
||||
@doc """
|
||||
Receives an AST node and expands it until it no longer represents
|
||||
a macro.
|
||||
Receives an AST node and expands it until it can no longer
|
||||
be expanded.
|
||||
|
||||
Check `expand_once/2` for more information on how
|
||||
expansion works.
|
||||
This function uses `expand_once/2` under the hood. Check
|
||||
`expand_once/2` for more information and exmaples.
|
||||
"""
|
||||
def expand(tree, env) do
|
||||
expand_until({ tree, true }, env)
|
||||
@@ -754,7 +784,7 @@ defmodule Macro do
|
||||
do_safe_term(terms) || :ok
|
||||
end
|
||||
|
||||
defp do_safe_term({ local, _, terms }) when local in [:{}, :__aliases__] do
|
||||
defp do_safe_term({ local, _, terms }) when local in [:{}, :%{}, :__aliases__] do
|
||||
do_safe_term(terms)
|
||||
end
|
||||
|
||||
|
||||
@@ -81,12 +81,16 @@ defmodule Macro.Env do
|
||||
def stacktrace(record) do
|
||||
cond do
|
||||
nil?(record.module) ->
|
||||
[{ :elixir_compiler, :__FILE__, 1, location(record) }]
|
||||
[{ :elixir_compiler, :__FILE__, 1, relative_location(record) }]
|
||||
nil?(record.function) ->
|
||||
[{ module(record), :__MODULE__, 0, location(record) }]
|
||||
[{ module(record), :__MODULE__, 0, relative_location(record) }]
|
||||
true ->
|
||||
{ name, arity } = record.function
|
||||
[{ module(record), name, arity, location(record) }]
|
||||
[{ module(record), name, arity, relative_location(record) }]
|
||||
end
|
||||
end
|
||||
|
||||
defp relative_location(record) do
|
||||
[file: Path.relative_to_cwd(file(record)), line: line(record)]
|
||||
end
|
||||
end
|
||||
|
||||
@@ -0,0 +1,116 @@
|
||||
defmodule Map do
|
||||
@moduledoc """
|
||||
A Dict implementation that works on maps.
|
||||
|
||||
Maps are key-value stores where keys are compared using
|
||||
the match operator (`===`). Maps can be created with
|
||||
the `%{}` special form defined in the `Kernel.SpecialForms`
|
||||
module.
|
||||
|
||||
For more information about the functions in this module and
|
||||
their APIs, please consult the `Dict` module.
|
||||
"""
|
||||
|
||||
use Dict.Behaviour
|
||||
|
||||
defdelegate [keys(map), values(map), size(map), merge(map1, map2)], to: :maps
|
||||
|
||||
@doc """
|
||||
Returns a new empty map.
|
||||
"""
|
||||
def new, do: %{}
|
||||
|
||||
@doc """
|
||||
Creates a new map from the given pairs.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Map.new [{:b, 1}, {:a, 2}]
|
||||
%{a: 2, b: 1}
|
||||
|
||||
"""
|
||||
def new(pairs) do
|
||||
:maps.from_list pairs
|
||||
end
|
||||
|
||||
@doc """
|
||||
Creates a new map from the given pairs
|
||||
via the given transformation function.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Map.new ["a", "b"], fn x -> {x, x} end
|
||||
%{"a" => "a", "b" => "b"}
|
||||
|
||||
"""
|
||||
def new(list, transform) when is_function(transform) do
|
||||
Enum.map(list, transform) |> :maps.from_list
|
||||
end
|
||||
|
||||
def has_key?(map, key), do: :maps.is_key(key, map)
|
||||
|
||||
def fetch(map, key), do: :maps.find(key, map)
|
||||
|
||||
def pop(map, key, default \\ nil) do
|
||||
{ get(map, key, default), delete(map, key) }
|
||||
end
|
||||
|
||||
def put(map, key, val) do
|
||||
:maps.put(key, val, map)
|
||||
end
|
||||
|
||||
def put_new(map, key, val) do
|
||||
case has_key?(map, key) do
|
||||
true -> map
|
||||
false -> :maps.put(key, val, map)
|
||||
end
|
||||
end
|
||||
|
||||
def delete(map, key), do: :maps.remove(key, map)
|
||||
|
||||
def merge(map1, map2, callback) do
|
||||
:maps.fold fn k, v2, acc ->
|
||||
update(acc, k, v2, fn(v1) -> callback.(k, v1, v2) end)
|
||||
end, map1, map2
|
||||
end
|
||||
|
||||
def split(map, keys) do
|
||||
acc = { %{}, %{} }
|
||||
|
||||
:maps.fold fn k, v, { take, drop } ->
|
||||
if k in keys do
|
||||
{ put(take, k, v), drop }
|
||||
else
|
||||
{ take, put(drop, k, v) }
|
||||
end
|
||||
end, acc, map
|
||||
end
|
||||
|
||||
def update!(map, key, fun) do
|
||||
case :maps.find(key, map) do
|
||||
:error ->
|
||||
raise(KeyError, key: key, term: map)
|
||||
{ :ok, val } ->
|
||||
:maps.put(key, fun.(val), map)
|
||||
end
|
||||
end
|
||||
|
||||
def update(map, key, initial, fun) do
|
||||
case :maps.find(key, map) do
|
||||
:error ->
|
||||
:maps.put(key, initial, map)
|
||||
{ :ok, val } ->
|
||||
:maps.put(key, fun.(val), map)
|
||||
end
|
||||
end
|
||||
|
||||
def empty(_) do
|
||||
IO.write :stderr, "Map.empty/1 is deprecated, please use Collectable.empty/1 instead\n#{Exception.format_stacktrace}"
|
||||
%{}
|
||||
end
|
||||
|
||||
def equal?(map, map), do: true
|
||||
def equal?(_, _), do: false
|
||||
|
||||
def to_list(map), do: :maps.to_list map
|
||||
end
|
||||
+81
-63
@@ -5,13 +5,13 @@ defmodule Module do
|
||||
end
|
||||
end
|
||||
|
||||
@moduledoc %S'''
|
||||
@moduledoc ~S'''
|
||||
This module provides many functions to deal with modules during
|
||||
compilation time. It allows a developer to dynamically attach
|
||||
documentation, add, delete and register attributes and so forth.
|
||||
|
||||
After a module is compiled, using many of the functions in
|
||||
this module will raise errors, since it is out of their purpose
|
||||
this module will raise errors, since it is out of their scope
|
||||
to inspect runtime data. Most of the runtime data can be inspected
|
||||
via the `__info__(attr)` function attached to each compiled module.
|
||||
|
||||
@@ -51,7 +51,7 @@ defmodule Module do
|
||||
When just a module is provided, the function/macro is assumed to be
|
||||
`__before_compile__/1`.
|
||||
|
||||
Note: differently from `@after_compile`, the callback function/macro must
|
||||
Note: unlike `@after_compile`, the callback function/macro must
|
||||
be placed in a separate module (because when the callback is invoked,
|
||||
the current module does not yet exist).
|
||||
|
||||
@@ -164,7 +164,7 @@ defmodule Module do
|
||||
|
||||
* `@on_definition`
|
||||
|
||||
A hook that will be invoked after each function or macro in the current
|
||||
A hook that will be invoked when each function or macro in the current
|
||||
module is defined. Useful when annotating functions.
|
||||
|
||||
Accepts a module or a tuple `{ <module>, <function atom> }`. The function
|
||||
@@ -173,22 +173,25 @@ defmodule Module do
|
||||
- the module environment
|
||||
- kind: `:def`, `:defp`, `:defmacro`, or `:defmacrop`
|
||||
- function/macro name
|
||||
- list of quoted arguments
|
||||
- list of quoted guards
|
||||
- quoted function body
|
||||
- list of expanded arguments
|
||||
- list of expanded guards
|
||||
- expanded function body
|
||||
|
||||
Note the hook receives the expanded arguments and it is invoked before
|
||||
the function is stored in the module. So `Module.defines?/2` will return
|
||||
false for the first clause of every function.
|
||||
|
||||
If the function/macro being defined has multiple clauses, the hook will
|
||||
be called for each clause.
|
||||
|
||||
Unlike other hooks, `@on_definition` will only invoke functions
|
||||
and never macros. This is because the hook is invoked inside the context
|
||||
of the function (and nested function definitions are not allowed in
|
||||
Elixir).
|
||||
|
||||
When just a module is provided, the function is assumed to be
|
||||
`__on_definition__/6`.
|
||||
|
||||
Note that you can't provide the current module to `@on_definition`
|
||||
because the hook function will not be defined in time. Finally, since
|
||||
the `on_definition` hook is executed inside the context of the defined
|
||||
function (i.e. `env.function` returns the current function), the hook
|
||||
can only be a function, not a macro.
|
||||
|
||||
### Example
|
||||
|
||||
defmodule H do
|
||||
@@ -257,7 +260,7 @@ defmodule Module do
|
||||
* `@typep` - defines a private type to be used in `@spec`
|
||||
* `@opaque` - defines an opaque type to be used in `@spec`
|
||||
* `@spec` - provides a specification for a function
|
||||
* `@callback` - provides a specification for the behavior callback
|
||||
* `@callback` - provides a specification for the behaviour callback
|
||||
|
||||
In addition to the built-in attributes outlined above, custom attributes may
|
||||
also be added. A custom attribute is any valid identifier prefixed with an
|
||||
@@ -308,15 +311,14 @@ defmodule Module do
|
||||
and its attributes and functions can be modified.
|
||||
"""
|
||||
def open?(module) do
|
||||
table = data_table_for(module)
|
||||
table == :ets.info(table, :name)
|
||||
:elixir_module.is_open(module)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Evaluates the quoted contents in the given module's context.
|
||||
|
||||
A list of environment options can also be given as argument.
|
||||
See `Code.eval_string` for more information.
|
||||
See `Code.eval_string/3` for more information.
|
||||
|
||||
Raises an error if the module was already compiled.
|
||||
|
||||
@@ -340,7 +342,7 @@ defmodule Module do
|
||||
Foo.sum(1, 2) #=> 3
|
||||
|
||||
"""
|
||||
def eval_quoted(module, quoted, binding // [], opts // [])
|
||||
def eval_quoted(module, quoted, binding \\ [], opts \\ [])
|
||||
|
||||
def eval_quoted(env, quoted, binding, opts) when is_env(env) do
|
||||
eval_quoted(env.module, quoted, binding, Keyword.merge(env.to_keywords, opts))
|
||||
@@ -387,7 +389,7 @@ defmodule Module do
|
||||
when defining the module, while `defmodule` automatically
|
||||
shares the same environment.
|
||||
"""
|
||||
def create(module, quoted, opts // [])
|
||||
def create(module, quoted, opts \\ [])
|
||||
|
||||
def create(module, quoted, env) when is_env(env) do
|
||||
create(module, quoted, env.to_keywords)
|
||||
@@ -404,6 +406,7 @@ defmodule Module do
|
||||
|
||||
iex> Module.concat([Foo, Bar])
|
||||
Foo.Bar
|
||||
|
||||
iex> Module.concat([Foo, "Bar"])
|
||||
Foo.Bar
|
||||
|
||||
@@ -420,6 +423,7 @@ defmodule Module do
|
||||
|
||||
iex> Module.concat(Foo, Bar)
|
||||
Foo.Bar
|
||||
|
||||
iex> Module.concat(Foo, "Bar")
|
||||
Foo.Bar
|
||||
|
||||
@@ -498,7 +502,7 @@ defmodule Module do
|
||||
end
|
||||
|
||||
"""
|
||||
def add_doc(module, line, kind, tuple, signature // [], doc)
|
||||
def add_doc(module, line, kind, tuple, signature \\ [], doc)
|
||||
|
||||
def add_doc(_module, _line, kind, _tuple, _signature, doc) when kind in [:defp, :defmacrop, :typep] do
|
||||
if doc, do: { :error, :private_doc }, else: :ok
|
||||
@@ -510,7 +514,7 @@ defmodule Module do
|
||||
table = docs_table_for(module)
|
||||
|
||||
{ signature, _ } = Enum.map_reduce signature, 1, fn(x, acc) ->
|
||||
{ simplify_signature(x, line, acc), acc + 1 }
|
||||
{ simplify_signature(x, acc), acc + 1 }
|
||||
end
|
||||
|
||||
case :ets.lookup(table, tuple) do
|
||||
@@ -531,28 +535,34 @@ defmodule Module do
|
||||
|
||||
# Simplify signatures to be stored in docs
|
||||
|
||||
defp simplify_signature({ ://, defline, [left, right ] }, line, i) do
|
||||
{ ://, defline, [simplify_signature(left, line, i), right] }
|
||||
defp simplify_signature({ :\\, _, [left, right ] }, i) do
|
||||
{ :\\, [], [simplify_signature(left, i), right] }
|
||||
end
|
||||
|
||||
defp simplify_signature({ var, line, atom }, _, _i) when is_atom(atom) do
|
||||
case atom_to_list(var) do
|
||||
[?_|_] -> { var, line, :guess }
|
||||
_ -> { var, line, nil }
|
||||
defp simplify_signature({ :%, _, [left, _] }, _i) when is_atom(left) do
|
||||
last = List.last(String.split(atom_to_binary(left), "."))
|
||||
atom = binary_to_atom(String.downcase(last))
|
||||
{ atom, [], nil }
|
||||
end
|
||||
|
||||
defp simplify_signature({ :=, _, [_, right] }, i) do
|
||||
simplify_signature(right, i)
|
||||
end
|
||||
|
||||
defp simplify_signature({ var, _, atom }, _i) when is_atom(atom) do
|
||||
case atom_to_binary(var) do
|
||||
"_" <> rest -> { binary_to_atom(rest), [], Elixir }
|
||||
_ -> { var, [], nil }
|
||||
end
|
||||
end
|
||||
|
||||
defp simplify_signature({ :=, _, [_, right] }, line, i) do
|
||||
simplify_signature(right, line, i)
|
||||
end
|
||||
|
||||
defp simplify_signature(other, line, i) when is_integer(other), do: { :"int#{i}", line, :guess }
|
||||
defp simplify_signature(other, line, i) when is_boolean(other), do: { :"bool#{i}", line, :guess }
|
||||
defp simplify_signature(other, line, i) when is_atom(other), do: { :"atom#{i}", line, :guess }
|
||||
defp simplify_signature(other, line, i) when is_list(other), do: { :"list#{i}", line, :guess }
|
||||
defp simplify_signature(other, line, i) when is_float(other), do: { :"float#{i}", line, :guess }
|
||||
defp simplify_signature(other, line, i) when is_binary(other), do: { :"binary#{i}", line, :guess }
|
||||
defp simplify_signature(_, line, i), do: { :"arg#{i}", line, :guess }
|
||||
defp simplify_signature(other, i) when is_integer(other), do: { :"int#{i}", [], Elixir }
|
||||
defp simplify_signature(other, i) when is_boolean(other), do: { :"bool#{i}", [], Elixir }
|
||||
defp simplify_signature(other, i) when is_atom(other), do: { :"atom#{i}", [], Elixir }
|
||||
defp simplify_signature(other, i) when is_list(other), do: { :"list#{i}", [], Elixir }
|
||||
defp simplify_signature(other, i) when is_float(other), do: { :"float#{i}", [], Elixir }
|
||||
defp simplify_signature(other, i) when is_binary(other), do: { :"binary#{i}", [], Elixir }
|
||||
defp simplify_signature(_, i), do: { :"arg#{i}", [], Elixir }
|
||||
|
||||
# Merge
|
||||
|
||||
@@ -564,11 +574,11 @@ defmodule Module do
|
||||
[]
|
||||
end
|
||||
|
||||
defp merge_signature({ ://, line, [left, right] }, newer, i) do
|
||||
{ ://, line, [merge_signature(left, newer, i), right] }
|
||||
defp merge_signature({ :\\, line, [left, right] }, newer, i) do
|
||||
{ :\\, line, [merge_signature(left, newer, i), right] }
|
||||
end
|
||||
|
||||
defp merge_signature(older, { ://, _, [left, _] }, i) do
|
||||
defp merge_signature(older, { :\\, _, [left, _] }, i) do
|
||||
merge_signature(older, left, i)
|
||||
end
|
||||
|
||||
@@ -580,7 +590,7 @@ defmodule Module do
|
||||
defp merge_signature({ var, _, _ } = older, { var, _, _ }, _), do: older
|
||||
|
||||
# Otherwise, returns a generic guess
|
||||
defp merge_signature({ _, line, _ }, _newer, i), do: { :"arg#{i}", line, :guess }
|
||||
defp merge_signature({ _, line, _ }, _newer, i), do: { :"arg#{i}", line, Elixir }
|
||||
|
||||
@doc """
|
||||
Checks if the module defines the given function or macro.
|
||||
@@ -631,14 +641,14 @@ defmodule Module do
|
||||
|
||||
defmodule Example do
|
||||
def version, do: 1
|
||||
Module.definitions_in __MODULE__ #=> [{:version,1}]
|
||||
Module.definitions_in __MODULE__ #=> [{:version,0}]
|
||||
end
|
||||
|
||||
"""
|
||||
def definitions_in(module) do
|
||||
assert_not_compiled!(:definitions_in, module)
|
||||
table = function_table_for(module)
|
||||
lc { tuple, _, _, _, _, _, _ } inlist :ets.tab2list(table), do: tuple
|
||||
for { tuple, _, _, _, _, _, _ } <- :ets.tab2list(table), do: tuple
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -649,7 +659,7 @@ defmodule Module do
|
||||
|
||||
defmodule Example do
|
||||
def version, do: 1
|
||||
Module.definitions_in __MODULE__, :def #=> [{:version,1}]
|
||||
Module.definitions_in __MODULE__, :def #=> [{:version,0}]
|
||||
Module.definitions_in __MODULE__, :defp #=> []
|
||||
end
|
||||
|
||||
@@ -657,19 +667,19 @@ defmodule Module do
|
||||
def definitions_in(module, kind) do
|
||||
assert_not_compiled!(:definitions_in, module)
|
||||
table = function_table_for(module)
|
||||
lc { tuple, stored_kind, _, _, _, _, _ } inlist :ets.tab2list(table), stored_kind == kind, do: tuple
|
||||
for { tuple, stored_kind, _, _, _, _, _ } <- :ets.tab2list(table), stored_kind == kind, do: tuple
|
||||
end
|
||||
|
||||
@doc """
|
||||
Makes the given functions in `module` overridable.
|
||||
An overridable function is lazily defined, allowing a
|
||||
developer to customize it. See `Kernel.defoverridable` for
|
||||
developer to customize it. See `Kernel.defoverridable/1` for
|
||||
more information and documentation.
|
||||
"""
|
||||
def make_overridable(module, tuples) do
|
||||
assert_not_compiled!(:make_overridable, module)
|
||||
|
||||
lc tuple inlist tuples do
|
||||
for tuple <- tuples do
|
||||
case :elixir_def.lookup_definition(module, tuple) do
|
||||
false ->
|
||||
{ name, arity } = tuple
|
||||
@@ -703,7 +713,7 @@ defmodule Module do
|
||||
@doc """
|
||||
Puts an Erlang attribute to the given module with the given
|
||||
key and value. The semantics of putting the attribute depends
|
||||
if the attribute was registered or not via `register_attribute/2`.
|
||||
if the attribute was registered or not via `register_attribute/3`.
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -733,7 +743,7 @@ defmodule Module do
|
||||
|
||||
@doc """
|
||||
Gets the given attribute from a module. If the attribute
|
||||
was marked with `accumulate` with `Module.register_attribute`,
|
||||
was marked with `accumulate` with `Module.register_attribute/3`,
|
||||
a list is always returned.
|
||||
|
||||
The `@` macro compiles to a call to this function. For example,
|
||||
@@ -745,12 +755,11 @@ defmodule Module do
|
||||
|
||||
Module.get_attribute(__MODULE__, :foo, true)
|
||||
|
||||
Notice the third argument is used to indicate if a warning
|
||||
should be emitted when the attribute was not previously defined.
|
||||
`warn` may also be a stacktrace, which will be used in the
|
||||
warning. The default value for `warn` is false for direct calls
|
||||
but the `@foo` macro sets it to the proper stacktrace automatically,
|
||||
warning every time `@foo` is used but not set previously.
|
||||
Notice the third argument may be given to indicate a stacktrace
|
||||
to be emitted when the attribute was not previously defined.
|
||||
The default value for `warn` is nil for direct calls but the `@foo`
|
||||
macro sets it to the proper stacktrace automatically, warning
|
||||
every time `@foo` is used but not set previously.
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -764,8 +773,9 @@ defmodule Module do
|
||||
end
|
||||
|
||||
"""
|
||||
@spec get_attribute(module, atom, warn :: boolean | [tuple]) :: term
|
||||
def get_attribute(module, key, warn // false) when is_atom(key) do
|
||||
@spec get_attribute(module, atom, warn :: nil | [tuple]) :: term
|
||||
def get_attribute(module, key, warn \\ nil) when
|
||||
is_atom(key) and (is_list(warn) or nil?(warn)) do
|
||||
assert_not_compiled!(:get_attribute, module)
|
||||
table = data_table_for(module)
|
||||
|
||||
@@ -777,9 +787,8 @@ defmodule Module do
|
||||
cond do
|
||||
:lists.member(key, acc) ->
|
||||
[]
|
||||
warn ->
|
||||
stack = is_list(warn) and warn
|
||||
:elixir_errors.warn "#{Exception.format_caller(stack)} undefined module attribute @#{key}, " <>
|
||||
is_list(warn) ->
|
||||
:elixir_errors.warn warn_info(warn), "undefined module attribute @#{key}, " <>
|
||||
"please remove access to @#{key} or explicitly set it to nil before access\n"
|
||||
nil
|
||||
true ->
|
||||
@@ -788,6 +797,15 @@ defmodule Module do
|
||||
end
|
||||
end
|
||||
|
||||
defp warn_info([entry|_]) do
|
||||
opts = elem(entry, size(entry) - 1)
|
||||
Exception.format_file_line(Keyword.get(opts, :file), Keyword.get(opts, :line)) <> " "
|
||||
end
|
||||
|
||||
defp warn_info([]) do
|
||||
""
|
||||
end
|
||||
|
||||
@doc """
|
||||
Deletes all attributes that match the given key.
|
||||
|
||||
@@ -878,7 +896,7 @@ defmodule Module do
|
||||
:ok ->
|
||||
:ok
|
||||
{ :error, :private_doc } ->
|
||||
:elixir_errors.warn "#{env.file}:#{line} function #{name}/#{arity} is private, @doc's are always discarded for private functions\n"
|
||||
:elixir_errors.warn line, env.file, "function #{name}/#{arity} is private, @doc's are always discarded for private functions\n"
|
||||
end
|
||||
|
||||
delete_attribute(module, :doc)
|
||||
@@ -906,7 +924,7 @@ defmodule Module do
|
||||
{ atom, 0 }
|
||||
end
|
||||
|
||||
defp normalize_attribute(kind, atom) when kind in [:behavior, :behaviour] and is_atom(atom) do
|
||||
defp normalize_attribute(:behaviour, atom) when is_atom(atom) do
|
||||
Code.ensure_compiled(atom)
|
||||
atom
|
||||
end
|
||||
|
||||
@@ -34,8 +34,8 @@
|
||||
defmodule Module.LocalsTracker do
|
||||
@moduledoc false
|
||||
|
||||
@timeout 30_000
|
||||
@behavior :gen_server
|
||||
@timeout 30_000
|
||||
@behaviour :gen_server
|
||||
|
||||
@type ref :: pid | module
|
||||
@type name :: atom
|
||||
@@ -71,7 +71,7 @@ defmodule Module.LocalsTracker do
|
||||
|
||||
defp reduce_reachable(d, vertex, vertices) do
|
||||
neighbours = :digraph.out_neighbours(d, vertex)
|
||||
neighbours = (lc { _, _ } = t inlist neighbours, do: t) |> :ordsets.from_list
|
||||
neighbours = (for { _, _ } = t <- neighbours, do: t) |> :ordsets.from_list
|
||||
remaining = :ordsets.subtract(neighbours, vertices)
|
||||
vertices = :ordsets.union(neighbours, vertices)
|
||||
:lists.foldl(&reduce_reachable(d, &1, &2), vertices, remaining)
|
||||
@@ -144,9 +144,10 @@ defmodule Module.LocalsTracker do
|
||||
def collect_imports_conflicts(pid, all_defined) do
|
||||
d = :gen_server.call(pid, :digraph, @timeout)
|
||||
|
||||
lc { name, arity } inlist all_defined,
|
||||
n = :digraph.out_neighbours(d, { :import, name, arity }),
|
||||
n != [] do
|
||||
for { name, arity } <- all_defined,
|
||||
:digraph.in_neighbours(d, { :import, name, arity }) != [],
|
||||
n = :digraph.out_neighbours(d, { :import, name, arity }),
|
||||
n != [] do
|
||||
{ n, name, arity }
|
||||
end
|
||||
end
|
||||
@@ -173,7 +174,7 @@ defmodule Module.LocalsTracker do
|
||||
min = arity - default
|
||||
max = arity
|
||||
|
||||
invoked = lc { n, a } inlist reachable, n == name, a in min..max, do: a
|
||||
invoked = for { n, a } <- reachable, n == name, a in min..max, do: a
|
||||
|
||||
if invoked == [] do
|
||||
[{ :unused_def, tuple, kind }|acc]
|
||||
@@ -260,7 +261,7 @@ defmodule Module.LocalsTracker do
|
||||
end
|
||||
|
||||
def handle_cast({ :add_defaults, kind, { name, arity }, defaults }, { d, _ } = state) do
|
||||
lc i inlist :lists.seq(arity - defaults, arity - 1) do
|
||||
for i <- :lists.seq(arity - defaults, arity - 1) do
|
||||
handle_add_definition(d, kind, { name, i })
|
||||
handle_add_local(d, { name, i }, { name, i + 1 })
|
||||
end
|
||||
@@ -268,8 +269,8 @@ defmodule Module.LocalsTracker do
|
||||
end
|
||||
|
||||
def handle_cast({ :reattach, tuple, { in_neigh, out_neigh } }, { d, _ } = state) do
|
||||
lc from inlist in_neigh, do: replace_edge(d, from, tuple)
|
||||
lc to inlist out_neigh, do: replace_edge(d, tuple, to)
|
||||
for from <- in_neigh, do: replace_edge(d, from, tuple)
|
||||
for to <- out_neigh, do: replace_edge(d, tuple, to)
|
||||
{ :noreply, state }
|
||||
end
|
||||
|
||||
|
||||
+92
-44
@@ -1,12 +1,19 @@
|
||||
defmodule Node do
|
||||
@moduledoc """
|
||||
Functions related to Erlang nodes.
|
||||
Functions related to VM nodes.
|
||||
|
||||
Some of the functions in this module are inlined by the compiler,
|
||||
similar to functions in the `Kernel` module and they are explicitly
|
||||
marked in their docs as "inlined by the compiler". For more information
|
||||
about inlined functions, check out the `Kernel` module.
|
||||
"""
|
||||
|
||||
@type t :: atom
|
||||
@type t :: node
|
||||
|
||||
@doc """
|
||||
Returns the current node. It returns the same as the built-in `node()`.
|
||||
Returns the current node.
|
||||
|
||||
It returns the same as the built-in `node()`.
|
||||
"""
|
||||
@spec self :: t
|
||||
def self do
|
||||
@@ -14,8 +21,9 @@ defmodule Node do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns `true` if the local node is alive; that is, if the node can be
|
||||
part of a distributed system. Otherwise, it returns `false`.
|
||||
Returns `true` if the local node is alive.
|
||||
|
||||
That is, if the node can be part of a distributed system.
|
||||
"""
|
||||
@spec alive? :: boolean
|
||||
def alive? do
|
||||
@@ -24,7 +32,9 @@ defmodule Node do
|
||||
|
||||
@doc """
|
||||
Returns a list of all visible nodes in the system, excluding
|
||||
the local node. Same as `list(:visible)`.
|
||||
the local node.
|
||||
|
||||
Same as `list(:visible)`.
|
||||
"""
|
||||
@spec list :: [t]
|
||||
def list do
|
||||
@@ -32,8 +42,9 @@ defmodule Node do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns a list of nodes according to argument given. The result
|
||||
returned when the argument is a list, is the list of nodes
|
||||
Returns a list of nodes according to argument given.
|
||||
|
||||
The result returned when the argument is a list, is the list of nodes
|
||||
satisfying the disjunction(s) of the list elements.
|
||||
|
||||
See http://www.erlang.org/doc/man/erlang.html#nodes-1 for more info.
|
||||
@@ -45,8 +56,10 @@ defmodule Node do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Monitors the status of the node. If `flag` is `true`, monitoring is
|
||||
turned on. If `flag` is `false`, monitoring is turned off.
|
||||
Monitors the status of the node.
|
||||
|
||||
If `flag` is `true`, monitoring is turned on.
|
||||
If `flag` is `false`, monitoring is turned off.
|
||||
|
||||
See http://www.erlang.org/doc/man/erlang.html#monitor_node-2 for more info.
|
||||
"""
|
||||
@@ -67,10 +80,28 @@ defmodule Node do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Forces the disconnection of a node. This will appear to the `node` as if
|
||||
the local node has crashed. This BIF is mainly used in the Erlang network
|
||||
authentication protocols. Returns `true` if disconnection succeeds, otherwise
|
||||
`false`. If the local node is not alive, the function returns `:ignored`.
|
||||
Tries to set up a connection to node.
|
||||
|
||||
Returns `:pang` if it fails, or `:pong` if it is successful.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Node.ping(:unknown_node)
|
||||
:pang
|
||||
|
||||
"""
|
||||
@spec ping(t) :: :pong | :pang
|
||||
def ping(node) do
|
||||
:net_adm.ping(node)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Forces the disconnection of a node.
|
||||
|
||||
This will appear to the `node` as if the local node has crashed.
|
||||
This function is mainly used in the Erlang network authentication
|
||||
protocols. Returns `true` if disconnection succeeds, otherwise `false`.
|
||||
If the local node is not alive, the function returns `:ignored`.
|
||||
|
||||
See http://www.erlang.org/doc/man/erlang.html#disconnect_node-1 for more info.
|
||||
"""
|
||||
@@ -80,9 +111,10 @@ defmodule Node do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Establishes a connection to `node`. Returns `true` if successful,
|
||||
`false` if not, and the atom `:ignored` if the local node is not
|
||||
alive.
|
||||
Establishes a connection to `node`.
|
||||
|
||||
Returns `true` if successful, `false` if not, and the atom
|
||||
`:ignored` if the local node is not alive.
|
||||
|
||||
See http://erlang.org/doc/man/net_kernel.html#connect_node-1 for more info.
|
||||
"""
|
||||
@@ -97,6 +129,8 @@ defmodule Node do
|
||||
|
||||
Check http://www.erlang.org/doc/man/erlang.html#spawn-2 for
|
||||
the list of available options.
|
||||
|
||||
Inlined by the compiler.
|
||||
"""
|
||||
@spec spawn(t, (() -> any)) :: pid
|
||||
def spawn(node, fun) do
|
||||
@@ -105,10 +139,13 @@ defmodule Node do
|
||||
|
||||
@doc """
|
||||
Returns the pid of a new process started by the application of `fun`
|
||||
on `node`. If `node` does not exist, a useless pid is returned.
|
||||
on `node`.
|
||||
|
||||
Check http://www.erlang.org/doc/man/erlang.html#spawn_opt-3 for
|
||||
the list of available options.
|
||||
If `node` does not exist, a useless pid is returned. Check
|
||||
http://www.erlang.org/doc/man/erlang.html#spawn_opt-3 for the list of
|
||||
available options.
|
||||
|
||||
Inlined by the compiler.
|
||||
"""
|
||||
@spec spawn(t, (() -> any), Process.spawn_opts) :: pid | {pid, reference}
|
||||
def spawn(node, fun, opts) do
|
||||
@@ -117,11 +154,13 @@ defmodule Node do
|
||||
|
||||
@doc """
|
||||
Returns the pid of a new process started by the application of
|
||||
`module.function(args)` on `node`. If `node` does not exists,
|
||||
a useless pid is returned.
|
||||
`module.function(args)` on `node`.
|
||||
|
||||
Check http://www.erlang.org/doc/man/erlang.html#spawn-4 for
|
||||
the list of available options.
|
||||
If `node` does not exist, a useless pid is returned. Check
|
||||
http://www.erlang.org/doc/man/erlang.html#spawn-4 for the list of
|
||||
available options.
|
||||
|
||||
Inlined by the compiler.
|
||||
"""
|
||||
@spec spawn(t, module, atom, [any]) :: pid
|
||||
def spawn(node, module, fun, args) do
|
||||
@@ -130,11 +169,13 @@ defmodule Node do
|
||||
|
||||
@doc """
|
||||
Returns the pid of a new process started by the application of
|
||||
`module.function(args)` on `node`. If `node` does not exists,
|
||||
a useless pid is returned.
|
||||
`module.function(args)` on `node`.
|
||||
|
||||
Check http://www.erlang.org/doc/man/erlang.html#spawn_opt-5 for
|
||||
the list of available options.
|
||||
If `node` does not exist, a useless pid is returned. Check
|
||||
http://www.erlang.org/doc/man/erlang.html#spawn_opt-5 for the list of
|
||||
available options.
|
||||
|
||||
Inlined by the compiler.
|
||||
"""
|
||||
@spec spawn(t, module, atom, [any], Process.spawn_opts) :: pid | {pid, reference}
|
||||
def spawn(node, module, fun, args, opts) do
|
||||
@@ -142,11 +183,13 @@ defmodule Node do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns the pid of a new process started by the application of `fun`
|
||||
on `node`. A link is created between the calling process and the
|
||||
new process, atomically. If `node` does not exist, a useless pid is returned
|
||||
(and due to the link, an exit signal with exit reason `:noconnection` will be
|
||||
received).
|
||||
Returns the pid of a new linked process started by the application of `fun` on `node`.
|
||||
|
||||
A link is created between the calling process and the new process, atomically.
|
||||
If `node` does not exist, a useless pid is returned (and due to the link, an exit
|
||||
signal with exit reason `:noconnection` will be received).
|
||||
|
||||
Inlined by the compiler.
|
||||
"""
|
||||
@spec spawn_link(t, (() -> any)) :: pid
|
||||
def spawn_link(node, fun) do
|
||||
@@ -154,11 +197,14 @@ defmodule Node do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns the pid of a new process started by the application of
|
||||
`module.function(args)` on `node`. A link is created between the calling
|
||||
process and the new process, atomically. If `node` does not exist, a useless
|
||||
pid is returned (and due to the link, an exit signal with exit reason
|
||||
`:noconnection` will be received).
|
||||
Returns the pid of a new linked process started by the application of
|
||||
`module.function(args)` on `node`.
|
||||
|
||||
A link is created between the calling process and the new process, atomically.
|
||||
If `node` does not exist, a useless pid is returned (and due to the link, an exit
|
||||
signal with exit reason `:noconnection` will be received).
|
||||
|
||||
Inlined by the compiler.
|
||||
"""
|
||||
@spec spawn_link(t, module, atom, [any]) :: pid
|
||||
def spawn_link(node, module, fun, args) do
|
||||
@@ -166,19 +212,21 @@ defmodule Node do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Sets the magic cookie of `node` to the atom `cookie`. The default node
|
||||
is `Node.self`, the local node. If `node` is the local node, the function also
|
||||
sets the cookie of all other unknown nodes to `cookie`.
|
||||
Sets the magic cookie of `node` to the atom `cookie`.
|
||||
|
||||
The default node is `Node.self`, the local node. If `node` is the local node,
|
||||
the function also sets the cookie of all other unknown nodes to `cookie`.
|
||||
|
||||
This function will raise `FunctionClauseError` if the given `node` is not alive.
|
||||
"""
|
||||
def set_cookie(node // Node.self, cookie) when is_atom(cookie) do
|
||||
def set_cookie(node \\ Node.self, cookie) when is_atom(cookie) do
|
||||
:erlang.set_cookie(node, cookie)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns the magic cookie of the local node, if the node is alive;
|
||||
otherwise `:nocookie`.
|
||||
Returns the magic cookie of the local node.
|
||||
|
||||
Returns the cookie if the node is alive, otherwise `:nocookie`.
|
||||
"""
|
||||
def get_cookie() do
|
||||
:erlang.get_cookie()
|
||||
|
||||
@@ -5,7 +5,7 @@ defmodule OptionParser do
|
||||
|
||||
@doc """
|
||||
Parses `argv` and returns a tuple with the parsed options, its
|
||||
arguments, and a list options that couldn't be parsed.
|
||||
arguments, and a list of options that couldn't be parsed.
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -42,6 +42,7 @@ defmodule OptionParser do
|
||||
`true` or `false`;
|
||||
* `:integer` - Parses the switch as an integer;
|
||||
* `:float` - Parses the switch as a float;
|
||||
* `:string` - Returns the switch as is (the default);
|
||||
|
||||
If a switch can't be parsed, the option is returned in the invalid
|
||||
options list (third element of the returned tuple).
|
||||
@@ -59,10 +60,10 @@ defmodule OptionParser do
|
||||
...> switches: [unlock: :boolean, limit: :integer])
|
||||
{ [unlock: true, limit: 0], ["path/to/file"], [] }
|
||||
|
||||
iex> OptionParser.parse(["-limit", "3"], switches: [limit: :integer])
|
||||
iex> OptionParser.parse(["--limit", "3"], switches: [limit: :integer])
|
||||
{ [limit: 3], [], [] }
|
||||
|
||||
iex> OptionParser.parse(["-limit", "yyz"], switches: [limit: :integer])
|
||||
iex> OptionParser.parse(["--limit", "yyz"], switches: [limit: :integer])
|
||||
{ [], [], [limit: "yyz"] }
|
||||
|
||||
## Negation switches
|
||||
@@ -79,7 +80,7 @@ defmodule OptionParser do
|
||||
{ [op: false], ["path/to/file"], [] }
|
||||
|
||||
"""
|
||||
def parse(argv, opts // []) when is_list(argv) and is_list(opts) do
|
||||
def parse(argv, opts \\ []) when is_list(argv) and is_list(opts) do
|
||||
parse(argv, opts, true)
|
||||
end
|
||||
|
||||
@@ -96,8 +97,9 @@ defmodule OptionParser do
|
||||
|
||||
iex> OptionParser.parse_head(["--verbose", "--source", "lib", "test/enum_test.exs", "--unlock"])
|
||||
{ [verbose: true, source: "lib"], ["test/enum_test.exs", "--unlock"], [] }
|
||||
|
||||
"""
|
||||
def parse_head(argv, opts // []) when is_list(argv) and is_list(opts) do
|
||||
def parse_head(argv, opts \\ []) when is_list(argv) and is_list(opts) do
|
||||
parse(argv, opts, false)
|
||||
end
|
||||
|
||||
@@ -118,7 +120,8 @@ defmodule OptionParser do
|
||||
end
|
||||
|
||||
defp parse(["-" <> option|t], aliases, switches, dict, args, invalid, all) do
|
||||
{ option, kinds, value } = normalize_option(option, switches, aliases)
|
||||
{ option, value } = split_option(option)
|
||||
{ option, kinds, value } = normalize_option(option, value, switches, aliases)
|
||||
|
||||
if nil?(value) do
|
||||
{ value, t } =
|
||||
@@ -153,6 +156,8 @@ defmodule OptionParser do
|
||||
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 ->
|
||||
@@ -185,27 +190,33 @@ defmodule OptionParser do
|
||||
end
|
||||
end
|
||||
|
||||
defp normalize_option(<<?-, option :: binary>>, switches, aliases) do
|
||||
normalize_option(option, switches, aliases)
|
||||
defp normalize_option(<<?-, option :: binary>>, value, switches, _aliases) do
|
||||
normalize_option(get_negated(option, switches), value, switches)
|
||||
end
|
||||
|
||||
defp normalize_option(option, switches, aliases) do
|
||||
{ option, value } = split_option(option)
|
||||
|
||||
if non_neg = get_non_negated(option, aliases) do
|
||||
kinds = List.wrap(switches[non_neg])
|
||||
|
||||
if :boolean in kinds do
|
||||
{ non_neg, kinds, false }
|
||||
else
|
||||
{ get_aliased(option, aliases), [:boolean], true }
|
||||
end
|
||||
defp normalize_option(option, value, switches, aliases) do
|
||||
option = get_option(option)
|
||||
if alias = aliases[option] do
|
||||
normalize_option({ :default, alias }, value, switches)
|
||||
else
|
||||
atom = get_aliased(option, aliases)
|
||||
{ atom, List.wrap(switches[atom]), value }
|
||||
{ option, [:invalid], value }
|
||||
end
|
||||
end
|
||||
|
||||
defp normalize_option({ :negated, option }, _value, switches) do
|
||||
kinds = List.wrap(switches[option])
|
||||
|
||||
if :boolean in kinds do
|
||||
{ option, kinds, false }
|
||||
else
|
||||
{ option, [:boolean], true }
|
||||
end
|
||||
end
|
||||
|
||||
defp normalize_option({ :default, option }, value, switches) do
|
||||
{ option, List.wrap(switches[option]), value }
|
||||
end
|
||||
|
||||
defp split_option(option) do
|
||||
case :binary.split(option, "=") do
|
||||
[h] -> { h, nil }
|
||||
@@ -214,14 +225,20 @@ defmodule OptionParser do
|
||||
end
|
||||
|
||||
defp to_underscore(option) do
|
||||
bc <<c>> inbits option, do: << if(c == ?-, do: ?_, else: c) >>
|
||||
for <<c <- option>>, into: "", do: << if(c == ?-, do: ?_, else: c) >>
|
||||
end
|
||||
|
||||
defp get_aliased(option, aliases) do
|
||||
atom = option |> to_underscore |> binary_to_atom
|
||||
aliases[atom] || atom
|
||||
defp get_option(option) do
|
||||
option |> to_underscore |> binary_to_atom
|
||||
end
|
||||
|
||||
defp get_non_negated("no-" <> rest, aliases), do: get_aliased(rest, aliases)
|
||||
defp get_non_negated(_, _), do: nil
|
||||
defp get_negated("no-" <> rest = option, switches) do
|
||||
negated = get_option(rest)
|
||||
option = if Keyword.has_key?(switches, negated), do: negated, else: get_option(option)
|
||||
{ :negated, option }
|
||||
end
|
||||
|
||||
defp get_negated(rest, _switches) do
|
||||
{ :default, get_option(rest) }
|
||||
end
|
||||
end
|
||||
|
||||
+13
-11
@@ -205,8 +205,10 @@ defmodule Path do
|
||||
|
||||
iex> Path.relative_to("/usr/local/foo", "/usr/local")
|
||||
"foo"
|
||||
|
||||
iex> Path.relative_to("/usr/local/foo", "/")
|
||||
"usr/local/foo"
|
||||
|
||||
iex> Path.relative_to("/usr/local/foo", "/etc")
|
||||
"/usr/local/foo"
|
||||
|
||||
@@ -277,8 +279,10 @@ defmodule Path do
|
||||
|
||||
iex> Path.basename("~/foo/bar.ex", ".ex")
|
||||
"bar"
|
||||
|
||||
iex> Path.basename("~/foo/bar.exs", ".ex")
|
||||
"bar.exs"
|
||||
|
||||
iex> Path.basename("~/foo/bar.old.ex", ".ex")
|
||||
"bar.old"
|
||||
|
||||
@@ -309,6 +313,7 @@ defmodule Path do
|
||||
|
||||
iex> Path.extname("foo.erl")
|
||||
".erl"
|
||||
|
||||
iex> Path.extname("~/foo/bar")
|
||||
""
|
||||
|
||||
@@ -324,6 +329,7 @@ defmodule Path do
|
||||
|
||||
iex> Path.rootname("/foo/bar")
|
||||
"/foo/bar"
|
||||
|
||||
iex> Path.rootname("/foo/bar.ex")
|
||||
"/foo/bar"
|
||||
|
||||
@@ -340,6 +346,7 @@ defmodule Path do
|
||||
|
||||
iex> Path.rootname("/foo/bar.erl", ".erl")
|
||||
"/foo/bar"
|
||||
|
||||
iex> Path.rootname("/foo/bar.erl", ".ex")
|
||||
"/foo/bar.erl"
|
||||
|
||||
@@ -356,8 +363,10 @@ defmodule Path do
|
||||
|
||||
iex> Path.join(["~", "foo"])
|
||||
"~/foo"
|
||||
|
||||
iex> Path.join(["foo"])
|
||||
"foo"
|
||||
|
||||
iex> Path.join(["/", "foo", "bar"])
|
||||
"/foo/bar"
|
||||
|
||||
@@ -408,8 +417,8 @@ defmodule Path do
|
||||
do_join(rest, relativename, [?/|result], os_type)
|
||||
defp do_join(<<?/, rest :: binary>>, relativename, [?/|result], os_type), do:
|
||||
do_join(rest, relativename, [?/|result], os_type)
|
||||
defp do_join(<<>>, <<>>, result, os_type), do:
|
||||
iolist_to_binary(maybe_remove_dirsep(result, os_type))
|
||||
defp do_join(<<>>, <<>>, result, _os_type), do:
|
||||
iolist_to_binary(:lists.reverse(result))
|
||||
defp do_join(<<>>, relativename, [?:|rest], :win32), do:
|
||||
do_join(relativename, <<>>, [?:|rest], :win32)
|
||||
defp do_join(<<>>, relativename, [?/|result], os_type), do:
|
||||
@@ -419,15 +428,6 @@ defmodule Path do
|
||||
defp do_join(<<char, rest :: binary>>, relativename, result, os_type) when is_integer(char), do:
|
||||
do_join(rest, relativename, [char|result], os_type)
|
||||
|
||||
defp maybe_remove_dirsep([?/, ?:, letter], :win32), do:
|
||||
[letter, ?:, ?/]
|
||||
defp maybe_remove_dirsep([?/], _), do:
|
||||
[?/]
|
||||
defp maybe_remove_dirsep([?/|name], _), do:
|
||||
:lists.reverse(name)
|
||||
defp maybe_remove_dirsep(name, _), do:
|
||||
:lists.reverse(name)
|
||||
|
||||
@doc """
|
||||
Returns a list with the path split by the path separator.
|
||||
If an empty string is given, returns the root path.
|
||||
@@ -436,8 +436,10 @@ defmodule Path do
|
||||
|
||||
iex> Path.split("")
|
||||
[]
|
||||
|
||||
iex> Path.split("foo")
|
||||
["foo"]
|
||||
|
||||
iex> Path.split("/foo/bar")
|
||||
["/", "foo", "bar"]
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@ defmodule Port do
|
||||
@doc """
|
||||
See http://www.erlang.org/doc/man/erlang.html#port_command-2.
|
||||
"""
|
||||
def command(port, data, options // []) do
|
||||
def command(port, data, options \\ []) do
|
||||
:erlang.port_command(port, data, options)
|
||||
end
|
||||
|
||||
|
||||
+105
-17
@@ -1,11 +1,11 @@
|
||||
defmodule Process do
|
||||
@moduledoc """
|
||||
This module provides convenience functions around processes and
|
||||
the process dictionary. In Erlang, most of these functions are
|
||||
auto-imported, but in Elixir they are grouped in a module for
|
||||
convenience. Notice that these functions, different from Erlang's,
|
||||
always return nil instead of undefined. You can use their Erlang
|
||||
version if you want the undefined value.
|
||||
Conveniences for working with processes and the process dictionary.
|
||||
|
||||
Some of the functions in this module are inlined by the compiler,
|
||||
similar to functions in the `Kernel` module and they are explicitly
|
||||
marked in their docs as "inlined by the compiler". For more information
|
||||
about inlined functions, check out the `Kernel` module.
|
||||
"""
|
||||
|
||||
@doc """
|
||||
@@ -32,7 +32,7 @@ defmodule Process do
|
||||
"""
|
||||
@spec get(term) :: term
|
||||
@spec get(term, default :: term) :: term
|
||||
def get(key, default // nil) do
|
||||
def get(key, default \\ nil) do
|
||||
case :erlang.get(key) do
|
||||
:undefined ->
|
||||
default
|
||||
@@ -76,21 +76,23 @@ defmodule Process do
|
||||
@doc """
|
||||
Sends an exit signal with the given reason to the pid.
|
||||
|
||||
The following behavior applies if reason is any term except `:normal` or `:kill`:
|
||||
The following behaviour applies if reason is any term except `:normal` or `:kill`:
|
||||
|
||||
1) If pid is not trapping exits, pid will exit with the given reason;
|
||||
|
||||
2) If pid is trapping exits, the exit signal is transformed into a message
|
||||
{'EXIT', from, reason} and delivered to the message queue of pid;
|
||||
{:EXIT, from, reason} and delivered to the message queue of pid;
|
||||
|
||||
3) If reason is the atom `:normal`, pid will not exit. If it is trapping exits,
|
||||
the exit signal is transformed into a message {'EXIT', from, :normal} and
|
||||
the exit signal is transformed into a message {:EXIT, from, :normal} and
|
||||
delivered to its message queue;
|
||||
|
||||
4) If reason is the atom `:kill`, that is if `exit(pid, :kill)` is called, an
|
||||
untrappable exit signal is sent to pid which will unconditionally exit with
|
||||
exit reason `:killed`.
|
||||
|
||||
Inlined by the compiler.
|
||||
|
||||
## Examples
|
||||
|
||||
Process.exit(pid, :kill)
|
||||
@@ -110,12 +112,64 @@ defmodule Process do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns the pid of a new process started by the application of `fun`.
|
||||
It behaves exactly the same as `Kernel.spawn/1`.
|
||||
Sends a message to the given process.
|
||||
|
||||
It does the same as `Kernel.send/2`.
|
||||
"""
|
||||
@spec spawn((() -> any)) :: pid
|
||||
def spawn(fun) do
|
||||
:erlang.spawn(fun)
|
||||
@spec send(dest, msg) :: msg when
|
||||
dest: pid | port | atom | { atom, node },
|
||||
msg: any
|
||||
def send(dest, msg) do
|
||||
:erlang.send(dest, msg)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Sends a message to the given process.
|
||||
|
||||
If the option `:noconnect` is used and sending the message would require an
|
||||
auto-connection to another node the message is not sent and `:noconnect` is
|
||||
returned.
|
||||
|
||||
If the option `:nosuspend` is used and sending the message would cause the
|
||||
sender to be suspended the message is not sent and `:nosuspend` is returned.
|
||||
|
||||
Otherwise the message is sent and `:ok` is returned.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Process.send({ :name, :node_does_not_exist }, :hi, [:noconnect])
|
||||
:noconnect
|
||||
|
||||
"""
|
||||
@spec send(dest, msg, [option]) :: result when
|
||||
dest: pid | port | atom | { atom, node },
|
||||
msg: any,
|
||||
option: :noconnect | :nosuspend,
|
||||
result: :ok | :noconnect | :nosuspend
|
||||
def send(dest, msg, options) do
|
||||
:erlang.send(dest, msg, options)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Sends `msg` to `dest` after `time` millisecons.
|
||||
|
||||
If `dest` is a pid, it has to be a pid of a local process, dead or alive.
|
||||
If `dest` is an atom, it is supposed to be the name of a registered process
|
||||
which is looked up at the time of delivery. No error is given if the name does
|
||||
not refer to a process.
|
||||
|
||||
This function returns a timer reference, which can be read or canceled with
|
||||
`:erlang.read_timer/1`, `:erlang.start_timer/3` and `:erlang.cancel_timer/1`.
|
||||
Note `time` cannot be greater than `4294967295`.
|
||||
|
||||
Finally, the timer will be automatically canceled if the given `dest` is a pid
|
||||
which is not alive or when the given pid exits. Note that timers will not be
|
||||
automatically canceled when `dest` is an atom (as the atom resolution is done
|
||||
on delivery).
|
||||
"""
|
||||
@spec send_after(pid | atom, term, non_neg_integer) :: reference
|
||||
def send_after(dest, msg, time) do
|
||||
:erlang.send_after(time, dest, msg)
|
||||
end
|
||||
|
||||
@type spawn_opt :: :link | :monitor | {:priority, :low | :normal | :high} |
|
||||
@@ -124,11 +178,24 @@ defmodule Process do
|
||||
{:min_bin_vheap_size, non_neg_integer}
|
||||
@type spawn_opts :: [spawn_opt]
|
||||
|
||||
@doc """
|
||||
Returns the pid of a new process started by the application of `fun`.
|
||||
It behaves exactly the same as `Kernel.spawn/1`.
|
||||
|
||||
Inlined by the compiler.
|
||||
"""
|
||||
@spec spawn((() -> any)) :: pid
|
||||
def spawn(fun) do
|
||||
:erlang.spawn(fun)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns the pid of a new process started by the application of `fun`.
|
||||
|
||||
It also accepts extra options, for the list of available options
|
||||
check http://www.erlang.org/doc/man/erlang.html#spawn_opt-2
|
||||
|
||||
Inlined by the compiler.
|
||||
"""
|
||||
@spec spawn((() -> any), spawn_opts) :: pid | {pid, reference}
|
||||
def spawn(fun, opts) do
|
||||
@@ -141,6 +208,8 @@ defmodule Process do
|
||||
scheduler queue and be run some time later.
|
||||
|
||||
It behaves exactly the same as the `Kernel.spawn/3` function.
|
||||
|
||||
Inlined by the compiler.
|
||||
"""
|
||||
@spec spawn(module, atom, [any]) :: pid
|
||||
def spawn(mod, fun, args) do
|
||||
@@ -155,6 +224,7 @@ defmodule Process do
|
||||
It also accepts extra options, for the list of available options
|
||||
check http://www.erlang.org/doc/man/erlang.html#spawn_opt-4
|
||||
|
||||
Inlined by the compiler.
|
||||
"""
|
||||
@spec spawn(module, atom, [any], spawn_opts) :: pid | {pid, reference}
|
||||
def spawn(mod, fun, args, opts) do
|
||||
@@ -165,6 +235,8 @@ defmodule Process do
|
||||
Returns the pid of a new process started by the application of `fun`.
|
||||
A link is created between the calling process and the new
|
||||
process, atomically.
|
||||
|
||||
Inlined by the compiler.
|
||||
"""
|
||||
@spec spawn_link((() -> any)) :: pid
|
||||
def spawn_link(fun) do
|
||||
@@ -175,6 +247,8 @@ defmodule Process do
|
||||
Returns the pid of a new process started by the application of
|
||||
`module.function(args)`. A link is created between the calling process
|
||||
and the new process, atomically. Otherwise works like spawn/3.
|
||||
|
||||
Inlined by the compiler.
|
||||
"""
|
||||
@spec spawn_link(module, atom, [any]) :: pid
|
||||
def spawn_link(mod, fun, args) do
|
||||
@@ -184,6 +258,8 @@ defmodule Process do
|
||||
@doc """
|
||||
Returns the pid of a new process started by the application of `fun`
|
||||
and reference for a monitor created to the new process.
|
||||
|
||||
Inlined by the compiler.
|
||||
"""
|
||||
@spec spawn_monitor((() -> any)) :: {pid, reference}
|
||||
def spawn_monitor(fun) do
|
||||
@@ -194,6 +270,8 @@ defmodule Process do
|
||||
A new process is started by the application of `module.function(args)`
|
||||
and the process is monitored at the same time. Returns the pid and a
|
||||
reference for the monitor. Otherwise works like spawn/3.
|
||||
|
||||
Inlined by the compiler.
|
||||
"""
|
||||
@spec spawn_monitor(module, atom, [any]) :: {pid, reference}
|
||||
def spawn_monitor(mod, fun, args) do
|
||||
@@ -205,6 +283,8 @@ defmodule Process do
|
||||
It returns the monitor reference.
|
||||
|
||||
See http://www.erlang.org/doc/man/erlang.html#monitor-2 for more info.
|
||||
|
||||
Inlined by the compiler.
|
||||
"""
|
||||
@spec monitor(pid | {reg_name :: atom, node :: atom} | reg_name :: atom) :: reference
|
||||
def monitor(item) do
|
||||
@@ -217,10 +297,12 @@ defmodule Process do
|
||||
If the monitoring is already turned off, nothing happens.
|
||||
|
||||
See http://www.erlang.org/doc/man/erlang.html#demonitor-2 for more info.
|
||||
|
||||
Inlined by the compiler.
|
||||
"""
|
||||
@spec demonitor(reference) :: true
|
||||
@spec demonitor(reference, options :: [:flush | :info]) :: boolean
|
||||
def demonitor(monitor_ref, options // []) do
|
||||
def demonitor(monitor_ref, options \\ []) do
|
||||
:erlang.demonitor(monitor_ref, options)
|
||||
end
|
||||
|
||||
@@ -244,6 +326,8 @@ defmodule Process do
|
||||
(or port) `pid`, if there is not such a link already.
|
||||
|
||||
See http://www.erlang.org/doc/man/erlang.html#link-1 for more info.
|
||||
|
||||
Inlined by the compiler.
|
||||
"""
|
||||
@spec link(pid | port) :: true
|
||||
def link(pid) do
|
||||
@@ -256,6 +340,8 @@ defmodule Process do
|
||||
fail, even if there is no link or `id` does not exist
|
||||
|
||||
See http://www.erlang.org/doc/man/erlang.html#unlink-1 for more info.
|
||||
|
||||
Inlined by the compiler.
|
||||
"""
|
||||
@spec unlink(pid | port) :: true
|
||||
def unlink(pid) do
|
||||
@@ -267,7 +353,9 @@ defmodule Process do
|
||||
be an atom, can be used instead of the pid / port identifier with the
|
||||
`Kernel.send/2` function.
|
||||
|
||||
See http://www.erlang.org/doc/man/erlang.html#register-2 for more info.
|
||||
`Process.register/2` will fail with `ArgumentError` if the pid supplied
|
||||
is no longer alive, (check with `alive?/1`) or if the name is
|
||||
already registered (check with `registered?/1`).
|
||||
"""
|
||||
@spec register(pid | port, atom) :: true
|
||||
def register(pid, name) do
|
||||
|
||||
+79
-87
@@ -33,13 +33,32 @@ defmodule Protocol do
|
||||
|
||||
defp after_defprotocol do
|
||||
quote unquote: false do
|
||||
{ arg, bodies, rec } = Protocol.impl_for(__MODULE__)
|
||||
# == Deprecated records handling ==
|
||||
{ arg, impl } = Protocol.rec_impl_for(__MODULE__)
|
||||
Kernel.def impl_for(unquote(arg)) when Kernel.is_record(unquote(arg)), do: unquote(impl)
|
||||
# == Deprecated records handling ==
|
||||
|
||||
@spec impl_for(term) :: module | nil
|
||||
Kernel.def impl_for(data)
|
||||
|
||||
lc { guard, body } inlist bodies do
|
||||
Kernel.def impl_for(unquote(arg)) when unquote(guard), do: unquote(body)
|
||||
# Define the implementation for structs.
|
||||
#
|
||||
# It simply delegates to struct_impl_for which is then
|
||||
# optimized during protocol consolidation.
|
||||
Kernel.def impl_for(%{ __struct__: struct }) when :erlang.is_atom(struct) do
|
||||
struct_impl_for(struct)
|
||||
end
|
||||
|
||||
# Define the implementation for builtins.
|
||||
for { guard, mod } <- Protocol.builtin do
|
||||
target = Module.concat(__MODULE__, mod)
|
||||
|
||||
Kernel.def impl_for(data) when :erlang.unquote(guard)(data) do
|
||||
case impl_for?(unquote(target)) do
|
||||
true -> unquote(target).__impl__(:name)
|
||||
false -> any_impl_for
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@spec impl_for!(term) :: module | no_return
|
||||
@@ -47,25 +66,35 @@ defmodule Protocol do
|
||||
impl_for(data) || raise(Protocol.UndefinedError, protocol: __MODULE__, value: data)
|
||||
end
|
||||
|
||||
# Handle special Record type
|
||||
Kernel.defp rec_impl_for(unquote(arg)), do: unquote(rec)
|
||||
|
||||
# Handle special Any type
|
||||
# Internal handler for Any
|
||||
if @fallback_to_any do
|
||||
Kernel.defp any_impl_for do
|
||||
try do
|
||||
__MODULE__.Any.__impl__(:name)
|
||||
catch
|
||||
:error, :undef, [[{ __MODULE__.Any, :__impl__, [:name], _ }|_]|_] ->
|
||||
nil
|
||||
case impl_for?(__MODULE__.Any) do
|
||||
true -> __MODULE__.Any.__impl__(:name)
|
||||
false -> nil
|
||||
end
|
||||
end
|
||||
else
|
||||
Kernel.defp any_impl_for, do: nil
|
||||
end
|
||||
|
||||
# Inline both helpers
|
||||
@compile { :inline, any_impl_for: 0, rec_impl_for: 1 }
|
||||
# Internal handler for Structs
|
||||
Kernel.defp struct_impl_for(struct) do
|
||||
target = Module.concat(__MODULE__, struct)
|
||||
case impl_for?(target) do
|
||||
true -> target.__impl__(:name)
|
||||
false -> any_impl_for
|
||||
end
|
||||
end
|
||||
|
||||
# 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
|
||||
@@ -90,7 +119,7 @@ defmodule Protocol do
|
||||
end
|
||||
|
||||
defp do_defimpl(protocol, [do: block, for: for]) when is_list(for) do
|
||||
lc f inlist for, do: do_defimpl(protocol, [do: block, for: f])
|
||||
for f <- for, do: do_defimpl(protocol, [do: block, for: f])
|
||||
end
|
||||
|
||||
defp do_defimpl(protocol, [do: block, for: for]) do
|
||||
@@ -139,87 +168,50 @@ defmodule Protocol do
|
||||
# Builtin types.
|
||||
@doc false
|
||||
def builtin do
|
||||
[ Tuple, Atom, List, BitString, Integer, Float,
|
||||
Function, PID, Port, Reference, Any ]
|
||||
[is_tuple: Tuple,
|
||||
is_atom: Atom,
|
||||
is_list: List,
|
||||
is_map: Map,
|
||||
is_bitstring: BitString,
|
||||
is_integer: Integer,
|
||||
is_float: Float,
|
||||
is_function: Function,
|
||||
is_pid: PID,
|
||||
is_port: Port,
|
||||
is_reference: Reference]
|
||||
end
|
||||
|
||||
# Implements the function that detects the protocol and
|
||||
# returns the module to dispatch to.
|
||||
@doc false
|
||||
def impl_for(current) do
|
||||
arg = quote(do: arg)
|
||||
all = [Record|builtin]
|
||||
def rec_impl_for(current) do
|
||||
all = [Any] ++ for { _guard, mod } <- builtin, do: mod
|
||||
arg = quote do: arg
|
||||
target = Module.concat(current, Tuple)
|
||||
|
||||
{ arg,
|
||||
lc(mod inlist all, do: impl_for(current, mod, arg)),
|
||||
rec_impl_for(current, arg) }
|
||||
end
|
||||
fallback = quote do
|
||||
case impl_for?(unquote(target)) do
|
||||
true -> unquote(target).__impl__(:name)
|
||||
false -> any_impl_for
|
||||
end
|
||||
end
|
||||
|
||||
defp rec_impl_for(current, arg) do
|
||||
fallback = impl_for(current, Tuple, arg) |> elem(1)
|
||||
impl_for = quote do
|
||||
atom = :erlang.element(1, unquote(arg))
|
||||
|
||||
quote do
|
||||
target = Module.concat(unquote(current), unquote(arg))
|
||||
try do
|
||||
target.__impl__(:name)
|
||||
catch
|
||||
:error, :undef, [[{ ^target, :__impl__, [:name], _ }|_]|_] ->
|
||||
case not(atom in unquote(all)) and match?('Elixir.' ++ _, atom_to_list(atom)) do
|
||||
true ->
|
||||
target = Module.concat(unquote(current), atom)
|
||||
case impl_for?(target) do
|
||||
true -> target.__impl__(:name)
|
||||
false -> unquote(fallback)
|
||||
end
|
||||
false ->
|
||||
unquote(fallback)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defp impl_for(current, Record, arg) do
|
||||
fallback = impl_for(current, Tuple, arg) |> elem(1)
|
||||
|
||||
dispatch = quote do
|
||||
atom = :erlang.element(1, unquote(arg))
|
||||
|
||||
case not(atom in unquote(builtin)) and match?('Elixir.' ++ _, atom_to_list(atom)) do
|
||||
true -> rec_impl_for(atom)
|
||||
false -> unquote(fallback)
|
||||
end
|
||||
end
|
||||
|
||||
quote do
|
||||
{ is_record(unquote(arg)), unquote(dispatch) }
|
||||
end
|
||||
end
|
||||
|
||||
defp impl_for(current, Tuple, arg), do: impl_with_fallback(Tuple, :is_tuple, current, arg)
|
||||
defp impl_for(current, Atom, arg), do: impl_with_fallback(Atom, :is_atom, current, arg)
|
||||
defp impl_for(current, List, arg), do: impl_with_fallback(List, :is_list, current, arg)
|
||||
defp impl_for(current, BitString, arg), do: impl_with_fallback(BitString, :is_bitstring, current, arg)
|
||||
defp impl_for(current, Integer, arg), do: impl_with_fallback(Integer, :is_integer, current, arg)
|
||||
defp impl_for(current, Float, arg), do: impl_with_fallback(Float, :is_float, current, arg)
|
||||
defp impl_for(current, Function, arg), do: impl_with_fallback(Function, :is_function, current, arg)
|
||||
defp impl_for(current, PID, arg), do: impl_with_fallback(PID, :is_pid, current, arg)
|
||||
defp impl_for(current, Port, arg), do: impl_with_fallback(Port, :is_port, current, arg)
|
||||
defp impl_for(current, Reference, arg), do: impl_with_fallback(Reference, :is_reference, current, arg)
|
||||
|
||||
defp impl_for(_current, Any, _arg) do
|
||||
{ true, quote(do: any_impl_for) }
|
||||
end
|
||||
|
||||
# Defines an implementation with fallback to the given module.
|
||||
defp impl_with_fallback(mod, guard, current, arg) do
|
||||
quote do
|
||||
{ :erlang.unquote(guard)(unquote(arg)),
|
||||
unquote(with_fallback(Module.concat(current, mod))) }
|
||||
end
|
||||
end
|
||||
|
||||
# Tries to dispatch to a given target, fallbacks to the
|
||||
# given `fallback` implementation if the target does not exist.
|
||||
defp with_fallback(target) when is_atom(target) do
|
||||
quote do
|
||||
try do
|
||||
unquote(target).__impl__(:name)
|
||||
catch
|
||||
:error, :undef, [[{ unquote(target), :__impl__, [:name], _ }|_]|_] ->
|
||||
any_impl_for
|
||||
end
|
||||
end
|
||||
{ arg, impl_for }
|
||||
end
|
||||
end
|
||||
|
||||
@@ -234,10 +226,10 @@ defmodule Protocol.DSL do
|
||||
defmacro def({ name, _, args }) when is_atom(name) and is_list(args) do
|
||||
arity = length(args)
|
||||
|
||||
type_args = lc _ inlist :lists.seq(2, arity), do: quote(do: term)
|
||||
type_args = for _ <- :lists.seq(2, arity), do: quote(do: term)
|
||||
type_args = [quote(do: t) | type_args]
|
||||
|
||||
call_args = lc i inlist :lists.seq(2, arity),
|
||||
call_args = for i <- :lists.seq(2, arity),
|
||||
do: { binary_to_atom(<<?x, i + 64>>), [], __MODULE__ }
|
||||
call_args = [quote(do: t) | call_args]
|
||||
|
||||
@@ -274,7 +266,7 @@ defmodule Protocol.DSL do
|
||||
tuple = { name, arity }
|
||||
specs = Module.get_attribute(module, :spec)
|
||||
|
||||
found = lc { k, v } inlist specs, k == tuple do
|
||||
found = for { k, v } <- specs, k == tuple do
|
||||
Kernel.Typespec.define_callback(module, tuple, v)
|
||||
true
|
||||
end
|
||||
|
||||
@@ -62,8 +62,8 @@ defmodule Protocol.Consolidation do
|
||||
end
|
||||
|
||||
defp extract_matching_by_attribute(paths, prefix, callback) do
|
||||
lc path inlist paths,
|
||||
file inlist list_dir(path),
|
||||
for path <- paths,
|
||||
file <- list_dir(path),
|
||||
mod = extract_from_file(path, file, prefix, callback),
|
||||
do: mod
|
||||
end
|
||||
@@ -125,6 +125,9 @@ defmodule Protocol.Consolidation do
|
||||
{ :error, :not_a_protocol } |
|
||||
{ :error, :no_beam_info }
|
||||
def apply_to(protocol, types) when is_atom(protocol) do
|
||||
raise ArgumentError, "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)
|
||||
@@ -157,36 +160,38 @@ defmodule Protocol.Consolidation do
|
||||
# impl_for/1 dispatch version.
|
||||
defp change_debug_info({ protocol, any, code }, types) do
|
||||
types = if any, do: types, else: List.delete(types, Any)
|
||||
records = types -- Protocol.builtin
|
||||
builtin = Protocol.builtin -- (Protocol.builtin -- types)
|
||||
builtin = if records != [], do: [Record|builtin], else: builtin
|
||||
change_impl_for(code, protocol, builtin, records, false, [])
|
||||
all = [Any] ++ for { _guard, mod } <- Protocol.builtin, do: mod
|
||||
structs = types -- all
|
||||
change_impl_for(code, protocol, types, structs, false, [])
|
||||
end
|
||||
|
||||
defp change_impl_for([{ :attribute, line, :protocol, _ }|t], protocol, builtin, records, _, acc) do
|
||||
attr = [fallback_to_any: Any in builtin, consolidated: true]
|
||||
change_impl_for(t, protocol, builtin, records, true,
|
||||
[{ :attribute, line, :protocol, attr }|acc])
|
||||
defp change_impl_for([{ :attribute, line, :protocol, opts }|t], protocol, types, structs, _, acc) do
|
||||
opts = [fallback_to_any: opts[:fallback_to_any], consolidated: true]
|
||||
change_impl_for(t, protocol, types, structs, true,
|
||||
[{ :attribute, line, :protocol, opts }|acc])
|
||||
end
|
||||
|
||||
defp change_impl_for([{ :function, line, :impl_for, 1, _ }|t], protocol, builtin, records, is_protocol, acc) do
|
||||
clauses = lc type inlist builtin, do: clause_for(type, protocol, line)
|
||||
defp change_impl_for([{ :function, line, :impl_for, 1, _ }|t], protocol, types, structs, is_protocol, acc) do
|
||||
fallback = if Any in types, do: Module.concat(protocol, Any), else: nil
|
||||
|
||||
unless Any in builtin do
|
||||
clauses = clauses ++ [fallback_clause_for(nil, protocol, line)]
|
||||
end
|
||||
clauses = for { guard, mod } <- Protocol.builtin,
|
||||
mod in types,
|
||||
do: builtin_clause_for(mod, guard, protocol, line)
|
||||
|
||||
change_impl_for(t, protocol, builtin, records, is_protocol,
|
||||
clauses = [struct_clause_for(line)|clauses] ++
|
||||
[fallback_clause_for(fallback, protocol, line)]
|
||||
|
||||
change_impl_for(t, protocol, types, structs, is_protocol,
|
||||
[{ :function, line, :impl_for, 1, clauses }|acc])
|
||||
end
|
||||
|
||||
defp change_impl_for([{ :function, line, :rec_impl_for, 1, _ }|t], protocol, builtin, records, is_protocol, acc) do
|
||||
fallback = if Tuple in builtin, do: Module.concat(protocol, Tuple)
|
||||
clauses = lc type inlist records, do: record_clause_for(type, protocol, line)
|
||||
clauses = clauses ++ [fallback_clause_for(fallback, protocol, line)]
|
||||
defp change_impl_for([{ :function, line, :struct_impl_for, 1, _ }|t], protocol, types, structs, is_protocol, acc) do
|
||||
fallback = if Any in types, do: Module.concat(protocol, Any), else: nil
|
||||
clauses = for struct <- structs, do: each_struct_clause_for(struct, protocol, line)
|
||||
clauses = clauses ++ [fallback_clause_for(fallback, protocol, line)]
|
||||
|
||||
change_impl_for(t, protocol, builtin, records, is_protocol,
|
||||
[{ :function, line, :rec_impl_for, 1, clauses }|acc])
|
||||
change_impl_for(t, protocol, types, structs, is_protocol,
|
||||
[{ :function, line, :struct_impl_for, 1, clauses }|acc])
|
||||
end
|
||||
|
||||
defp change_impl_for([h|t], protocol, info, types, is_protocol, acc) do
|
||||
@@ -201,42 +206,6 @@ defmodule Protocol.Consolidation do
|
||||
end
|
||||
end
|
||||
|
||||
defp clause_for(Tuple, protocol, line), do: builtin_clause_for(Tuple, :is_tuple, protocol, line)
|
||||
defp clause_for(Atom, protocol, line), do: builtin_clause_for(Atom, :is_atom, protocol, line)
|
||||
defp clause_for(List, protocol, line), do: builtin_clause_for(List, :is_list, protocol, line)
|
||||
defp clause_for(BitString, protocol, line), do: builtin_clause_for(BitString, :is_bitstring, protocol, line)
|
||||
defp clause_for(Integer, protocol, line), do: builtin_clause_for(Integer, :is_integer, protocol, line)
|
||||
defp clause_for(Float, protocol, line), do: builtin_clause_for(Float, :is_float, protocol, line)
|
||||
defp clause_for(Function, protocol, line), do: builtin_clause_for(Function, :is_function, protocol, line)
|
||||
defp clause_for(PID, protocol, line), do: builtin_clause_for(PID, :is_pid, protocol, line)
|
||||
defp clause_for(Port, protocol, line), do: builtin_clause_for(Port, :is_port, protocol, line)
|
||||
defp clause_for(Reference, protocol, line), do: builtin_clause_for(Reference, :is_reference, protocol, line)
|
||||
|
||||
defp clause_for(Any, protocol, line) do
|
||||
{:clause, line, [{:var, line, :_}], [],
|
||||
[{ :atom, line, Module.concat(protocol, Any) }]}
|
||||
end
|
||||
|
||||
defp clause_for(Record, _protocol, line) do
|
||||
{:clause, line, [{:var, line, :x}],
|
||||
[[{:op, line, :andalso,
|
||||
{:call, line,
|
||||
{:remote, line, {:atom, line, :erlang}, {:atom, line, :is_tuple}},
|
||||
[{:var, line, :x}]},
|
||||
{:call, line,
|
||||
{:remote, line, {:atom, line, :erlang}, {:atom, line, :is_atom}},
|
||||
[{:call, line,
|
||||
{:remote, line, {:atom, line, :erlang}, {:atom, line, :element}},
|
||||
[{:integer, line, 1}, {:var, line, :x}]
|
||||
}]},
|
||||
}]],
|
||||
[{:call, line,
|
||||
{:atom, line, :rec_impl_for},
|
||||
[{:call, line,
|
||||
{:remote, line, {:atom, line, :erlang}, {:atom, line, :element}},
|
||||
[{:integer, line, 1}, {:var, line, :x}]}]}]}
|
||||
end
|
||||
|
||||
defp builtin_clause_for(mod, guard, protocol, line) do
|
||||
{:clause, line,
|
||||
[{:var, line, :x}],
|
||||
@@ -247,7 +216,21 @@ defmodule Protocol.Consolidation do
|
||||
[{:atom, line, Module.concat(protocol, mod)}]}
|
||||
end
|
||||
|
||||
defp record_clause_for(other, protocol, line) do
|
||||
defp struct_clause_for(line) do
|
||||
{:clause, line,
|
||||
[{:map, line, [
|
||||
{:map_field_exact, line, {:atom, line, :__struct__}, {:var, line, :x}}
|
||||
]}],
|
||||
[[{:call, line,
|
||||
{:remote, line, {:atom, line, :erlang}, {:atom, line, :is_atom}},
|
||||
[{:var, line, :x}],
|
||||
}]],
|
||||
[{:call, line,
|
||||
{:atom, line, :struct_impl_for},
|
||||
[{:var, line, :x}]}]}
|
||||
end
|
||||
|
||||
defp each_struct_clause_for(other, protocol, line) do
|
||||
{:clause, line, [{:atom, line, other}], [],
|
||||
[{:atom, line, Module.concat(protocol, other)}]}
|
||||
end
|
||||
|
||||
+43
-4
@@ -1,7 +1,46 @@
|
||||
defrecord Range, [:first, :last] do
|
||||
defmodule Range do
|
||||
@moduledoc """
|
||||
Defines a Range.
|
||||
"""
|
||||
|
||||
@type t :: { Range, any, any }
|
||||
@type t(first, last) :: { Range, first, last }
|
||||
|
||||
@doc """
|
||||
Creates a new range.
|
||||
"""
|
||||
def new(first, last) do
|
||||
{ Range, first, last }
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns the first item of the range.
|
||||
"""
|
||||
def first({ Range, first, _ }) do
|
||||
first
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns the last item of the range.
|
||||
"""
|
||||
def last({ Range, _, last }) do
|
||||
last
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns true if the given argument is a range.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Range.range?(1..3)
|
||||
true
|
||||
|
||||
iex> Range.range?(0)
|
||||
false
|
||||
|
||||
"""
|
||||
def range?({ Range, _, _ }), do: true
|
||||
def range?(_), do: false
|
||||
end
|
||||
|
||||
defprotocol Range.Iterator do
|
||||
@@ -55,7 +94,7 @@ defimpl Enumerable, for: Range do
|
||||
end
|
||||
|
||||
defimpl Range.Iterator, for: Integer do
|
||||
def next(first, Range[last: last]) when is_integer(last) do
|
||||
def next(first, _ .. last) when is_integer(last) do
|
||||
if last >= first do
|
||||
&(&1 + 1)
|
||||
else
|
||||
@@ -63,7 +102,7 @@ defimpl Range.Iterator, for: Integer do
|
||||
end
|
||||
end
|
||||
|
||||
def count(first, Range[last: last]) when is_integer(last) do
|
||||
def count(first, _ .. last) when is_integer(last) do
|
||||
if last >= first do
|
||||
last - first + 1
|
||||
else
|
||||
@@ -75,7 +114,7 @@ end
|
||||
defimpl Inspect, for: Range do
|
||||
import Inspect.Algebra
|
||||
|
||||
def inspect(Range[first: first, last: last], opts) do
|
||||
def inspect(first .. last, opts) do
|
||||
concat [to_doc(first, opts), "..", to_doc(last, opts)]
|
||||
end
|
||||
end
|
||||
|
||||
+154
-900
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,146 @@
|
||||
defmodule Record.Backend do
|
||||
# Callback functions invoked by defrecord, defrecordp and friends.
|
||||
@moduledoc false
|
||||
|
||||
@doc """
|
||||
Splits a keywords list into fields and types.
|
||||
|
||||
This logic is shared by records and structs.
|
||||
"""
|
||||
def split_fields_and_types(tag, kv) when is_list(kv) do
|
||||
split_fields_and_types(tag, kv, [], [])
|
||||
end
|
||||
|
||||
def split_fields_and_types(tag, other) do
|
||||
raise ArgumentError, message: "#{tag} fields must be a keyword list, got: #{Macro.to_string other}"
|
||||
end
|
||||
|
||||
defp split_fields_and_types(tag, [{ field, { :::, _, [default, type] }}|t], fields, types) do
|
||||
split_fields_and_types(tag, t, [{ field, default }|fields], [{ field, type }|types])
|
||||
end
|
||||
|
||||
defp split_fields_and_types(tag, [{ field, default }|t], fields, types) when is_atom(field) do
|
||||
split_fields_and_types(tag, t, [{ field, default }|fields], [{ field, quote(do: term) }|types])
|
||||
end
|
||||
|
||||
defp split_fields_and_types(tag, [field|t], fields, types) when is_atom(field) do
|
||||
split_fields_and_types(tag, t, [{ field, nil }|fields], [{ field, quote(do: term) }|types])
|
||||
end
|
||||
|
||||
defp split_fields_and_types(tag, [other|_], _fields, _types) do
|
||||
raise ArgumentError, message: "#{tag} fields must be atoms, got: #{Macro.to_string other}"
|
||||
end
|
||||
|
||||
defp split_fields_and_types(_tag, [], 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 = caller.in_match?
|
||||
|
||||
{ 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 caller.in_match? do
|
||||
raise ArgumentError, message: "cannot invoke update style macro inside match"
|
||||
end
|
||||
|
||||
Enum.reduce keyword, var, fn({ key, value }, acc) ->
|
||||
index = find_index(fields, key, 0)
|
||||
if index do
|
||||
quote do
|
||||
:erlang.setelement(unquote(index), unquote(acc), unquote(value))
|
||||
end
|
||||
else
|
||||
raise ArgumentError, message: "record #{inspect atom} does not have the key: #{inspect key}"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Gets a record key from the given var.
|
||||
"""
|
||||
def get(atom, fields, var, key) do
|
||||
index = find_index(fields, key, 0)
|
||||
if index do
|
||||
quote do
|
||||
:erlang.element(unquote(index), unquote(var))
|
||||
end
|
||||
else
|
||||
raise ArgumentError, message: "record #{inspect atom} does not have the key: #{inspect key}"
|
||||
end
|
||||
end
|
||||
|
||||
defp find_index([{ k, _ }|_], k, i), do: i + 2
|
||||
defp find_index([{ _, _ }|t], k, i), do: find_index(t, k, i + 1)
|
||||
defp find_index([], _k, _i), do: nil
|
||||
end
|
||||
@@ -0,0 +1,526 @@
|
||||
defmodule Record.Deprecated do
|
||||
@moduledoc false
|
||||
|
||||
def defrecord(name, fields, opts) do
|
||||
block = Keyword.get(opts, :do, nil)
|
||||
record_check!(fields)
|
||||
|
||||
quote do
|
||||
unquoted_fields = unquote(fields)
|
||||
|
||||
defmodule unquote(name) do
|
||||
import Record.DSL
|
||||
|
||||
@record_fields []
|
||||
@record_types []
|
||||
|
||||
Record.Deprecated.deffunctions(unquoted_fields, __ENV__)
|
||||
value = unquote(block)
|
||||
Record.Deprecated.deftypes(@record_fields, @record_types, __ENV__)
|
||||
value
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defp record_check!([{ field, { :::, _, [_, _] }}|_]) when is_atom(field) do
|
||||
raise ArgumentError, message: "typespecs are not supported inlined with defrecord, " <>
|
||||
"please use record_type instead"
|
||||
end
|
||||
|
||||
defp record_check!([_|t]), do: record_check!(t)
|
||||
defp record_check!(_), do: :ok
|
||||
|
||||
def defrecordp(name, tag, fields) do
|
||||
case recordp_split(fields, [], [], false) do
|
||||
{ :ok, fields, types, def_type } ->
|
||||
types = Macro.escape(types)
|
||||
|
||||
# bind_quoted isn't available when bootstrapping record
|
||||
quoted = quote [unquote: false] do
|
||||
Record.Deprecated.defmacros(name, fields, __ENV__, tag)
|
||||
|
||||
if def_type do
|
||||
type = binary_to_atom(atom_to_binary(name) <> "_t")
|
||||
@typep unquote(type)() :: { unquote(tag || name), unquote_splicing(types) }
|
||||
end
|
||||
end
|
||||
|
||||
quote do
|
||||
def_type = unquote(def_type)
|
||||
fields = unquote(fields)
|
||||
types = unquote(types)
|
||||
tag = unquote(tag)
|
||||
name = unquote(name)
|
||||
unquote(quoted)
|
||||
end
|
||||
|
||||
:error ->
|
||||
quote do
|
||||
name = unquote(name)
|
||||
Record.Deprecated.defmacros(name, unquote(fields), __ENV__, unquote(tag))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defp recordp_split([{ field, { :::, _, [default, type] }}|t], defaults, types, _) do
|
||||
recordp_split(t, [{ field, default }|defaults], [type|types], true)
|
||||
end
|
||||
|
||||
defp recordp_split([other|t], defaults, types, def_type) do
|
||||
recordp_split(t, [other|defaults], [quote(do: term)|types], def_type)
|
||||
end
|
||||
|
||||
defp recordp_split([], defaults, types, def_type) do
|
||||
{ :ok, :lists.reverse(defaults), :lists.reverse(types), def_type }
|
||||
end
|
||||
|
||||
defp recordp_split(_, _, _, _) do
|
||||
:error
|
||||
end
|
||||
|
||||
def deffunctions(values, env) do
|
||||
values = for value <- values, do: convert_value(value)
|
||||
escaped = Macro.escape(values)
|
||||
|
||||
contents = [
|
||||
reflection(escaped),
|
||||
initializer(escaped),
|
||||
conversions(values),
|
||||
record_optimizable(),
|
||||
updater(values),
|
||||
accessors(values, 1),
|
||||
switch_recorder()
|
||||
]
|
||||
|
||||
contents = [quote(do: @record_fields unquote(escaped))|contents]
|
||||
|
||||
# Special case for bootstrapping purposes
|
||||
if env == Macro.Env do
|
||||
Module.eval_quoted(env, contents, [], [])
|
||||
else
|
||||
Module.eval_quoted(env.module, contents, [], env.location)
|
||||
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, [], env.location)
|
||||
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, [], env.location)
|
||||
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 = caller.in_match?
|
||||
|
||||
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 caller.in_match? do
|
||||
raise ArgumentError, message: "cannot invoke update style macro inside match context"
|
||||
end
|
||||
|
||||
Enum.reduce keyword, var, fn({ key, value }, acc) ->
|
||||
index = find_index(fields, key, 0)
|
||||
if index do
|
||||
quote do
|
||||
:erlang.setelement(unquote(index + 2), unquote(acc), unquote(value))
|
||||
end
|
||||
else
|
||||
raise ArgumentError, message: "record #{inspect atom} does not have the key: #{inspect key}"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defp get(atom, fields, var, key) do
|
||||
index = find_index(fields, key, 0)
|
||||
if index do
|
||||
quote do
|
||||
:erlang.element(unquote(index + 2), unquote(var))
|
||||
end
|
||||
else
|
||||
raise ArgumentError, message: "record #{inspect atom} does not have the key: #{inspect key}"
|
||||
end
|
||||
end
|
||||
|
||||
## Function generation
|
||||
|
||||
defp reflection(values) do
|
||||
quoted = for { k, _ } <- values do
|
||||
index = find_index(values, k, 0)
|
||||
quote do
|
||||
def __record__(:index, unquote(k)), do: unquote(index + 1)
|
||||
end
|
||||
end
|
||||
|
||||
quote do
|
||||
unquote(quoted)
|
||||
|
||||
@doc false
|
||||
def __record__(:index, _), do: nil
|
||||
@doc false
|
||||
def __record__(:index, arg, _), do: __record__(:index, arg)
|
||||
|
||||
@doc false
|
||||
def __record__(kind, _), do: __record__(kind)
|
||||
|
||||
@doc false
|
||||
def __record__(:name), do: __MODULE__
|
||||
def __record__(:fields), do: unquote(values)
|
||||
end
|
||||
end
|
||||
|
||||
defp initializer(values) do
|
||||
defaults = for { _, value } <- values, do: value
|
||||
|
||||
# For each value, define a piece of code that will receive
|
||||
# an ordered dict of options (opts) and it will try to fetch
|
||||
# the given key from the ordered dict, falling back to the
|
||||
# default value if one does not exist.
|
||||
atom_selective = for { k, v } <- values, do: initialize_lookup(k, v)
|
||||
string_selective = for { k, v } <- values, do: initialize_lookup(atom_to_binary(k), v)
|
||||
|
||||
quote do
|
||||
@doc false
|
||||
def new(), do: new([])
|
||||
|
||||
@doc false
|
||||
def new([]), do: { __MODULE__, unquote_splicing(defaults) }
|
||||
def new([{key, _}|_] = opts) when is_atom(key), do: { __MODULE__, unquote_splicing(atom_selective) }
|
||||
def new([{key, _}|_] = opts) when is_binary(key), do: { __MODULE__, unquote_splicing(string_selective) }
|
||||
end
|
||||
end
|
||||
|
||||
defp initialize_lookup(k, v) do
|
||||
quote do
|
||||
case :lists.keyfind(unquote(k), 1, opts) do
|
||||
false -> unquote(v)
|
||||
{_, v} -> v
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defp conversions(values) do
|
||||
sorted = for { k, _ } <- values do
|
||||
index = find_index(values, k, 0)
|
||||
{ k, quote(do: :erlang.element(unquote(index + 2), record)) }
|
||||
end
|
||||
|
||||
quote do
|
||||
@doc false
|
||||
def to_keywords(record) do
|
||||
unquote(sorted)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defp accessors([{ :__exception__, _ }|t], 1) do
|
||||
accessors(t, 2)
|
||||
end
|
||||
|
||||
defp accessors([{ key, _default }|t], i) do
|
||||
update = binary_to_atom "update_" <> atom_to_binary(key)
|
||||
|
||||
contents = quote do
|
||||
@doc false
|
||||
def unquote(key)(record) do
|
||||
:erlang.element(unquote(i + 1), record)
|
||||
end
|
||||
|
||||
@doc false
|
||||
def unquote(key)(value, record) do
|
||||
:erlang.setelement(unquote(i + 1), record, value)
|
||||
end
|
||||
|
||||
@doc false
|
||||
def unquote(update)(function, record) do
|
||||
:erlang.setelement(unquote(i + 1), record,
|
||||
function.(:erlang.element(unquote(i + 1), record)))
|
||||
end
|
||||
end
|
||||
|
||||
[contents|accessors(t, i + 1)]
|
||||
end
|
||||
|
||||
defp accessors([], _i) do
|
||||
[]
|
||||
end
|
||||
|
||||
# Define an updater method that receives a
|
||||
# keyword list and updates the record.
|
||||
defp updater(values) do
|
||||
atom_fields =
|
||||
for {key, _default} <- values, do: updater_lookup(key, key, values)
|
||||
|
||||
string_fields =
|
||||
for {key, _default} <- values, do: updater_lookup(atom_to_binary(key), key, values)
|
||||
|
||||
atom_contents = quote do: { __MODULE__, unquote_splicing(atom_fields) }
|
||||
string_contents = quote do: { __MODULE__, unquote_splicing(string_fields) }
|
||||
|
||||
quote do
|
||||
@doc false
|
||||
def update([], record) do
|
||||
record
|
||||
end
|
||||
|
||||
def update([{key, _}|_] = keywords, record) when is_atom(key) do
|
||||
unquote(atom_contents)
|
||||
end
|
||||
def update([{key, _}|_] = keywords, record) when is_binary(key) do
|
||||
unquote(string_contents)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defp updater_lookup(k, key, values) do
|
||||
index = find_index(values, key, 0)
|
||||
|
||||
quote do
|
||||
case :lists.keyfind(unquote(k), 1, keywords) do
|
||||
false -> :erlang.element(unquote(index + 2), record)
|
||||
{_, value} -> value
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defp record_optimizable do
|
||||
quote do
|
||||
@record_optimized true
|
||||
@record_optimizable []
|
||||
@before_compile { unquote(__MODULE__), :__before_compile__ }
|
||||
@on_definition { unquote(__MODULE__), :__on_definition__ }
|
||||
end
|
||||
end
|
||||
|
||||
defp switch_recorder do
|
||||
quote do: @record_optimized false
|
||||
end
|
||||
|
||||
## Types/specs generation
|
||||
|
||||
defp core_specs(values) do
|
||||
types = for { _, _, spec } <- values, do: spec
|
||||
options = for { k, _, v } <- values, do: { k, v }
|
||||
|
||||
quote do
|
||||
unless Kernel.Typespec.defines_type?(__MODULE__, :t, 0) do
|
||||
@type t :: { __MODULE__, unquote_splicing(types) }
|
||||
end
|
||||
|
||||
unless Kernel.Typespec.defines_type?(__MODULE__, :options, 0) do
|
||||
@type options :: unquote(options) | [{String.t, term}]
|
||||
end
|
||||
|
||||
@spec new :: t
|
||||
@spec new(options) :: t
|
||||
@spec to_keywords(t) :: options
|
||||
@spec update(options, t) :: t
|
||||
@spec __record__(:name) :: atom
|
||||
@spec __record__(:fields) :: [{atom, any}]
|
||||
@spec __record__(:index, atom) :: non_neg_integer | nil
|
||||
end
|
||||
end
|
||||
|
||||
defp accessor_specs([{ :__exception__, _, _ }|t], 1, acc) do
|
||||
accessor_specs(t, 2, acc)
|
||||
end
|
||||
|
||||
defp accessor_specs([{ key, _default, spec }|t], i, acc) do
|
||||
update = binary_to_atom "update_" <> atom_to_binary(key)
|
||||
|
||||
contents = quote do
|
||||
@spec unquote(key)(t) :: unquote(spec)
|
||||
@spec unquote(key)(unquote(spec), t) :: t
|
||||
@spec unquote(update)((unquote(spec) -> unquote(spec)), t) :: t
|
||||
end
|
||||
|
||||
accessor_specs(t, i + 1, [contents | acc])
|
||||
end
|
||||
|
||||
defp accessor_specs([], _i, acc), do: acc
|
||||
|
||||
## Helpers
|
||||
|
||||
defp is_keyword(list) when is_list(list), do: :lists.all(&is_keyword_tuple/1, list)
|
||||
defp is_keyword(_), do: false
|
||||
|
||||
defp is_keyword_tuple({ x, _ }) when is_atom(x), do: true
|
||||
defp is_keyword_tuple(_), do: false
|
||||
|
||||
defp convert_value(atom) when is_atom(atom), do: { atom, nil }
|
||||
|
||||
defp convert_value({ atom, other }) when is_atom(atom), do:
|
||||
{ atom, check_value(atom, other) }
|
||||
|
||||
defp convert_value({ field, _ }), do:
|
||||
raise(ArgumentError, message: "record field name has to be an atom, got #{inspect field}")
|
||||
|
||||
defp check_value(atom, other) when is_list(other) do
|
||||
for(i <- other, do: check_value(atom, i))
|
||||
other
|
||||
end
|
||||
|
||||
defp check_value(atom, other) when is_tuple(other) do
|
||||
for(i <- tuple_to_list(other), do: check_value(atom, i))
|
||||
other
|
||||
end
|
||||
|
||||
defp check_value(atom, other) when is_function(other) do
|
||||
unless :erlang.fun_info(other, :env) == { :env, [] } and
|
||||
:erlang.fun_info(other, :type) == { :type, :external } do
|
||||
raise ArgumentError, message: "record field default value #{inspect atom} can only contain " <>
|
||||
"functions that point to an existing &Mod.fun/arity"
|
||||
end
|
||||
other
|
||||
end
|
||||
|
||||
defp check_value(atom, other) when is_reference(other) or is_pid(other) or is_port(other) do
|
||||
raise(ArgumentError, message: "record field default value #{inspect atom} cannot contain a reference, pid or port")
|
||||
end
|
||||
|
||||
defp check_value(_atom, other), do: other
|
||||
|
||||
defp find_index([{ k, _ }|_], k, i), do: i
|
||||
defp find_index([{ _, _ }|t], k, i), do: find_index(t, k, i + 1)
|
||||
defp find_index([], _k, _i), do: nil
|
||||
|
||||
defp find_spec(types, name) do
|
||||
matches = for { k, v } <- types, name == k, do: v
|
||||
case matches do
|
||||
[h|_] -> h
|
||||
_ -> quote do: term
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defmodule Record.DSL do
|
||||
@moduledoc false
|
||||
|
||||
defmacro record_type(opts) when is_list(opts) do
|
||||
escaped = for { k, v } <- opts, do: { k, Macro.escape(v) }
|
||||
|
||||
quote do
|
||||
@record_types Keyword.merge(@record_types || [], unquote(escaped))
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -3,7 +3,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 retrieve(name, from: file) when is_binary(file) do
|
||||
def extract(name, from: file) when is_binary(file) do
|
||||
file = String.to_char_list!(file)
|
||||
|
||||
realfile =
|
||||
@@ -12,26 +12,26 @@ defmodule Record.Extractor do
|
||||
realfile -> realfile
|
||||
end
|
||||
|
||||
retrieve_record(name, realfile)
|
||||
extract_record(name, realfile)
|
||||
end
|
||||
|
||||
# Retrieve a record definition from an Erlang file using
|
||||
# the same lookup as the *include_lib* attribute from Erlang modules.
|
||||
def retrieve(name, from_lib: file) when is_binary(file) do
|
||||
def extract(name, from_lib: file) when is_binary(file) do
|
||||
[app|path] = :filename.split(String.to_char_list!(file))
|
||||
|
||||
case :code.lib_dir(list_to_atom(app)) do
|
||||
{ :error, _ } ->
|
||||
raise ArgumentError, message: "lib file #{file} could not be found"
|
||||
libpath ->
|
||||
retrieve_record name, :filename.join([libpath|path])
|
||||
extract_record name, :filename.join([libpath|path])
|
||||
end
|
||||
end
|
||||
|
||||
# Retrieve the record with the given name from the given file
|
||||
defp retrieve_record(name, file) do
|
||||
defp extract_record(name, file) do
|
||||
form = read_file(file)
|
||||
records = retrieve_records(form)
|
||||
records = extract_records(form)
|
||||
if record = List.keyfind(records, name, 0) do
|
||||
parse_record(record, form)
|
||||
else
|
||||
@@ -39,9 +39,9 @@ defmodule Record.Extractor do
|
||||
end
|
||||
end
|
||||
|
||||
# Parse the given file and retrieve all existent records.
|
||||
defp retrieve_records(form) do
|
||||
lc { :attribute, _, :record, record } inlist form, do: record
|
||||
# Parse the given file and extract all existent records.
|
||||
defp extract_records(form) do
|
||||
for { :attribute, _, :record, record } <- form, do: record
|
||||
end
|
||||
|
||||
# Read a file and return its abstract syntax form that also
|
||||
|
||||
+146
-98
@@ -1,17 +1,17 @@
|
||||
defmodule Regex do
|
||||
@moduledoc %S"""
|
||||
@moduledoc ~S"""
|
||||
Regular expressions for Elixir built on top of the `re` module
|
||||
in the Erlang Standard Library. More information can be found
|
||||
in the [`re` documentation](http://www.erlang.org/doc/man/re.html).
|
||||
|
||||
Regular expressions in Elixir can be created using `Regex.compile!/2`
|
||||
or using the special form with [`%r`](Kernel.html#sigil_r/2):
|
||||
or using the special form with [`~r`](Kernel.html#sigil_r/2):
|
||||
|
||||
# A simple regular expressions that matches foo anywhere in the string
|
||||
%r/foo/
|
||||
~r/foo/
|
||||
|
||||
# A regular expression with case insensitive options
|
||||
%r/foo/i
|
||||
~r/foo/i
|
||||
|
||||
The `re` module provides several options, the ones available in Elixir, followed by
|
||||
their shortcut in parenthesis, are:
|
||||
@@ -27,7 +27,6 @@ defmodule Regex do
|
||||
* `firstline` (f) - forces the unanchored pattern to match before or at the first
|
||||
newline, though the matched text may continue over the newline
|
||||
* `ungreedy` (r) - inverts the "greediness" of the regexp
|
||||
* `groups` (g) - compiles with info about groups available
|
||||
|
||||
The options not available are:
|
||||
|
||||
@@ -37,12 +36,31 @@ defmodule Regex do
|
||||
* `newline` - not available, use `(*CR)` or `(*LF)` or `(*CRLF)` or `(*ANYCRLF)`
|
||||
or `(*ANY)` at the beginning of the regexp according to the re documentation
|
||||
|
||||
Most of the functions in this module accept either a binary or a char list
|
||||
as subject. The result is based on the argument (a binary will return
|
||||
a binary, a char list will return a char list).
|
||||
## Captures
|
||||
|
||||
Many functions in this module allows what to capture in a regex
|
||||
match via the `:capture` option. The supported values are:
|
||||
|
||||
* `:all` - all captured subpatterns including the complete matching string.
|
||||
This is the default;
|
||||
|
||||
* `:first` - only the first captured subpattern, which is always the complete
|
||||
matching part of the string. All explicitly captured subpatterns are
|
||||
discarded;
|
||||
|
||||
* `:all_but_first`- all but the first matching subpattern, i.e. all explicitly
|
||||
captured subpatterns, but not the complete matching part of
|
||||
the string;
|
||||
|
||||
* `:none` - do not return matching subpatterns at all;
|
||||
|
||||
* `:groups` - captures only named captures in the Regex;
|
||||
|
||||
* `list(binary)` - a list of named captures to capture;
|
||||
|
||||
"""
|
||||
|
||||
defrecordp :regex, Regex, [:re_pattern, :source, :options, :groups]
|
||||
defrecordp :regex, Regex, [:re_pattern, :source, :options]
|
||||
@type t :: { Regex, term, binary, binary, [atom] | nil }
|
||||
|
||||
defexception CompileError, message: "regex could not be compiled"
|
||||
@@ -51,7 +69,7 @@ defmodule Regex do
|
||||
Compiles the regular expression.
|
||||
|
||||
The given options can either be a binary with the characters
|
||||
representing the same regex options given to the `%r` sigil,
|
||||
representing the same regex options given to the `~r` sigil,
|
||||
or a list of options, as expected by the [Erlang `re` docs](http://www.erlang.org/doc/man/re.html).
|
||||
|
||||
It returns `{ :ok, regex }` in case of success,
|
||||
@@ -60,13 +78,14 @@ defmodule Regex do
|
||||
## Examples
|
||||
|
||||
iex> Regex.compile("foo")
|
||||
{:ok, %r"foo"}
|
||||
{:ok, ~r"foo"}
|
||||
|
||||
iex> Regex.compile("*foo")
|
||||
{:error, {'nothing to repeat', 0}}
|
||||
|
||||
"""
|
||||
@spec compile(binary, binary | [term]) :: { :ok, t } | { :error, any }
|
||||
def compile(source, options // "")
|
||||
def compile(source, options \\ "")
|
||||
|
||||
def compile(source, options) when is_binary(options) do
|
||||
case translate_options(options) do
|
||||
@@ -85,12 +104,9 @@ defmodule Regex do
|
||||
end
|
||||
|
||||
defp compile(source, opts, doc_opts) when is_binary(source) do
|
||||
re_opts = opts -- [:groups]
|
||||
groups = if opts != re_opts, do: parse_groups(source)
|
||||
|
||||
case :re.compile(source, re_opts) do
|
||||
case :re.compile(source, opts) do
|
||||
{ :ok, re_pattern } ->
|
||||
{ :ok, regex(re_pattern: re_pattern, source: source, options: doc_opts, groups: groups) }
|
||||
{ :ok, regex(re_pattern: re_pattern, source: source, options: doc_opts) }
|
||||
error ->
|
||||
error
|
||||
end
|
||||
@@ -100,7 +116,7 @@ defmodule Regex do
|
||||
Compiles the regular expression according to the given options.
|
||||
Fails with `Regex.CompileError` if the regex cannot be compiled.
|
||||
"""
|
||||
def compile!(source, options // "") do
|
||||
def compile!(source, options \\ "") do
|
||||
case compile(source, options) do
|
||||
{ :ok, regex } -> regex
|
||||
{ :error, { reason, at } } -> raise Regex.CompileError, message: "#{reason} at position #{at}"
|
||||
@@ -112,41 +128,62 @@ defmodule Regex do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Regex.match?(%r/foo/, "foo")
|
||||
iex> Regex.match?(~r/foo/, "foo")
|
||||
true
|
||||
iex> Regex.match?(%r/foo/, "bar")
|
||||
|
||||
iex> Regex.match?(~r/foo/, "bar")
|
||||
false
|
||||
|
||||
"""
|
||||
def match?(regex(re_pattern: compiled), string) do
|
||||
def match?(regex(re_pattern: compiled), string) when is_binary(string) do
|
||||
:re.run(string, compiled, [{ :capture, :none }]) == :match
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns true if the given argument is a regex.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Regex.regex?(~r/foo/)
|
||||
true
|
||||
|
||||
iex> Regex.regex?(0)
|
||||
false
|
||||
|
||||
"""
|
||||
def regex?(regex()), do: true
|
||||
def regex?(_), do: false
|
||||
|
||||
@doc """
|
||||
Runs the regular expression against the given string until the first match.
|
||||
It returns a list with all captures or `nil` if no match occurred.
|
||||
|
||||
When the option `:capture` is set to `:groups`, it will capture all
|
||||
the groups in the regex.
|
||||
## Options
|
||||
|
||||
* `:return` - Set to `:index` to return indexes. Defaults to `:binary`;
|
||||
* `:capture` - What to capture in the result. Check the moduledoc for Regex
|
||||
to see the possible capture values;
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Regex.run(%r/c(d)/, "abcd")
|
||||
iex> Regex.run(~r/c(d)/, "abcd")
|
||||
["cd", "d"]
|
||||
iex> Regex.run(%r/e/, "abcd")
|
||||
|
||||
iex> Regex.run(~r/e/, "abcd")
|
||||
nil
|
||||
iex> Regex.run(%r/c(d)/, "abcd", return: :index)
|
||||
|
||||
iex> Regex.run(~r/c(d)/, "abcd", return: :index)
|
||||
[{2,2},{3,1}]
|
||||
|
||||
"""
|
||||
def run(regex, string, options // [])
|
||||
def run(regex, string, options \\ [])
|
||||
|
||||
def run(regex(re_pattern: compiled, groups: groups), string, options) do
|
||||
return = Keyword.get(options, :return, return_for(string))
|
||||
def run(regex(re_pattern: compiled) = regex, string, options) when is_binary(string) do
|
||||
return = Keyword.get(options, :return, :binary)
|
||||
|
||||
captures =
|
||||
case Keyword.get(options, :capture, :all) do
|
||||
:groups -> groups || raise ArgumentError, message: "regex was not compiled with g"
|
||||
:groups -> groups(regex)
|
||||
others -> others
|
||||
end
|
||||
|
||||
@@ -159,22 +196,25 @@ defmodule Regex do
|
||||
|
||||
@doc """
|
||||
Returns the given captures as a keyword list or `nil` if no captures
|
||||
are found. Requires the regex to be compiled with the groups option.
|
||||
are found. The option `:return` can be set to `:index` to get indexes
|
||||
back.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Regex.named_captures(%r/c(?<foo>d)/g, "abcd")
|
||||
iex> Regex.named_captures(~r/c(?<foo>d)/, "abcd")
|
||||
[foo: "d"]
|
||||
iex> Regex.named_captures(%r/a(?<foo>b)c(?<bar>d)/g, "abcd")
|
||||
[foo: "b", bar: "d"]
|
||||
iex> Regex.named_captures(%r/a(?<foo>b)c(?<bar>d)/g, "efgh")
|
||||
|
||||
iex> Regex.named_captures(~r/a(?<foo>b)c(?<bar>d)/, "abcd")
|
||||
[bar: "d", foo: "b"]
|
||||
|
||||
iex> Regex.named_captures(~r/a(?<foo>b)c(?<bar>d)/, "efgh")
|
||||
nil
|
||||
|
||||
"""
|
||||
def named_captures(regex(groups: groups) = regex, string, options // []) do
|
||||
options = Keyword.put_new(options, :capture, :groups)
|
||||
def named_captures(regex, string, options \\ []) when is_binary(string) do
|
||||
options = [capture: :groups] ++ options
|
||||
results = run(regex, string, options)
|
||||
if results, do: Enum.zip(groups, results)
|
||||
if results, do: Enum.zip(groups(regex), results)
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -189,7 +229,7 @@ defmodule Regex do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Regex.source(%r(foo))
|
||||
iex> Regex.source(~r(foo))
|
||||
"foo"
|
||||
|
||||
"""
|
||||
@@ -202,7 +242,7 @@ defmodule Regex do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Regex.opts(%r(foo)m)
|
||||
iex> Regex.opts(~r(foo)m)
|
||||
"m"
|
||||
|
||||
"""
|
||||
@@ -215,12 +255,13 @@ defmodule Regex do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Regex.groups(%r/(?<foo>bar)/g)
|
||||
iex> Regex.groups(~r/(?<foo>bar)/)
|
||||
[:foo]
|
||||
|
||||
"""
|
||||
def groups(regex(groups: groups)) do
|
||||
groups
|
||||
def groups(regex(re_pattern: re_pattern)) do
|
||||
{ :namelist, groups } = :re.inspect(re_pattern, :namelist)
|
||||
for group <- groups, do: binary_to_atom(group)
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -229,30 +270,32 @@ defmodule Regex do
|
||||
where each entry in the primary list represents a match and each
|
||||
entry in the secondary list represents the captured contents.
|
||||
|
||||
The captured contents defaults to `:all`, which includes the whole
|
||||
regex match and each capture.
|
||||
## Options
|
||||
|
||||
When the option `:capture` is set to `:groups`, it will capture all
|
||||
the groups in the regex.
|
||||
* `:return` - Set to `:index` to return indexes. Defaults to `:binary`;
|
||||
* `:capture` - What to capture in the result. Check the moduledoc for Regex
|
||||
to see the possible capture values;
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Regex.scan(%r/c(d|e)/, "abcd abce")
|
||||
iex> Regex.scan(~r/c(d|e)/, "abcd abce")
|
||||
[["cd", "d"], ["ce", "e"]]
|
||||
iex> Regex.scan(%r/c(?:d|e)/, "abcd abce")
|
||||
|
||||
iex> Regex.scan(~r/c(?:d|e)/, "abcd abce")
|
||||
[["cd"], ["ce"]]
|
||||
iex> Regex.scan(%r/e/, "abcd")
|
||||
|
||||
iex> Regex.scan(~r/e/, "abcd")
|
||||
[]
|
||||
|
||||
"""
|
||||
def scan(regex, string, options // [])
|
||||
def scan(regex, string, options \\ [])
|
||||
|
||||
def scan(regex(re_pattern: compiled, groups: groups), string, options) do
|
||||
return = Keyword.get(options, :return, return_for(string))
|
||||
def scan(regex(re_pattern: compiled) = regex, string, options) when is_binary(string) do
|
||||
return = Keyword.get(options, :return, :binary)
|
||||
|
||||
captures =
|
||||
case Keyword.get(options, :capture, :all) do
|
||||
:groups -> groups || raise ArgumentError, message: "regex was not compiled with g"
|
||||
:groups -> groups(regex)
|
||||
others -> others
|
||||
end
|
||||
|
||||
@@ -266,24 +309,39 @@ defmodule Regex do
|
||||
|
||||
@doc """
|
||||
Splits the given target into the number of parts specified.
|
||||
If no number of parts is given, it defaults to `:infinity`.
|
||||
|
||||
## Options
|
||||
|
||||
* `:global` - splits the string in all parts unless set to `false`,
|
||||
which leads the string to be split in 2. Defaults to true;
|
||||
|
||||
* `:parts` - when specified, splits the string into maximum the number
|
||||
of given parts;
|
||||
|
||||
* `:trim` - when true, remove blank strings from the result;
|
||||
|
||||
## Examples
|
||||
iex> Regex.split(%r/-/, "a-b-c")
|
||||
|
||||
iex> Regex.split(~r/-/, "a-b-c")
|
||||
["a","b","c"]
|
||||
iex> Regex.split(%r/-/, "a-b-c", [parts: 2])
|
||||
|
||||
iex> Regex.split(~r/-/, "a-b-c", [parts: 2])
|
||||
["a","b-c"]
|
||||
iex> Regex.split(%r/-/, "abc")
|
||||
|
||||
iex> Regex.split(~r/-/, "abc")
|
||||
["abc"]
|
||||
iex> Regex.split(%r//, "abc")
|
||||
|
||||
iex> Regex.split(~r//, "abc")
|
||||
["a", "b", "c", ""]
|
||||
iex> Regex.split(%r//, "abc", trim: true)
|
||||
|
||||
iex> Regex.split(~r//, "abc", trim: true)
|
||||
["a", "b", "c"]
|
||||
|
||||
"""
|
||||
|
||||
def split(regex, string, options // [])
|
||||
def split(regex, string, options \\ [])
|
||||
|
||||
def split(regex(re_pattern: compiled), string, options) do
|
||||
def split(regex(re_pattern: compiled), string, options) when is_binary(string) do
|
||||
parts =
|
||||
cond do
|
||||
Keyword.get(options, :global) == false -> 2
|
||||
@@ -291,18 +349,17 @@ defmodule Regex do
|
||||
true -> :infinity
|
||||
end
|
||||
|
||||
return = Keyword.get(options, :return, return_for(string))
|
||||
opts = [return: return, parts: parts]
|
||||
opts = [return: :binary, parts: parts]
|
||||
splits = :re.split(string, compiled, opts)
|
||||
|
||||
if Keyword.get(options, :trim, false) do
|
||||
lc split inlist splits, split != "", do: split
|
||||
for split <- splits, split != "", do: split
|
||||
else
|
||||
splits
|
||||
end
|
||||
end
|
||||
|
||||
@doc %S"""
|
||||
@doc ~S"""
|
||||
Receives a regex, a binary and a replacement, returns a new
|
||||
binary where the all matches are replaced by replacement.
|
||||
|
||||
@@ -313,42 +370,48 @@ defmodule Regex do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Regex.replace(%r/d/, "abc", "d")
|
||||
iex> Regex.replace(~r/d/, "abc", "d")
|
||||
"abc"
|
||||
iex> Regex.replace(%r/b/, "abc", "d")
|
||||
|
||||
iex> Regex.replace(~r/b/, "abc", "d")
|
||||
"adc"
|
||||
iex> Regex.replace(%r/b/, "abc", "[&]")
|
||||
|
||||
iex> Regex.replace(~r/b/, "abc", "[&]")
|
||||
"a[b]c"
|
||||
iex> Regex.replace(%r/b/, "abc", "[\\&]")
|
||||
|
||||
iex> Regex.replace(~r/b/, "abc", "[\\&]")
|
||||
"a[&]c"
|
||||
iex> Regex.replace(%r/(b)/, "abc", "[\\1]")
|
||||
|
||||
iex> Regex.replace(~r/(b)/, "abc", "[\\1]")
|
||||
"a[b]c"
|
||||
|
||||
"""
|
||||
def replace(regex(re_pattern: compiled), string, replacement, options // []) do
|
||||
opts = if Keyword.get(options, :global) != false, do: [:global], else: []
|
||||
return = Keyword.get(options, :return, return_for(string))
|
||||
opts = [{ :return, return }|opts]
|
||||
def replace(regex, string, replacement, options \\ [])
|
||||
|
||||
def replace(regex(re_pattern: compiled), string, replacement, options) when is_binary(string) do
|
||||
opts = if Keyword.get(options, :global) != false, do: [:global], else: []
|
||||
opts = [{ :return, :binary }|opts]
|
||||
:re.replace(string, compiled, replacement, opts)
|
||||
end
|
||||
|
||||
{ :ok, pattern } = :re.compile(%S"[.^$*+?()[{\\\|\s#]", [:unicode])
|
||||
{ :ok, pattern } = :re.compile(~S"[.^$*+?()[{\\\|\s#]", [:unicode])
|
||||
@escape_pattern pattern
|
||||
|
||||
@doc %S"""
|
||||
@doc ~S"""
|
||||
Escapes a string to be literally matched in a regex.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Regex.escape(".")
|
||||
"\\."
|
||||
|
||||
iex> Regex.escape("\\what if")
|
||||
"\\\\what\\ if"
|
||||
|
||||
"""
|
||||
@spec escape(String.t | char_list) :: String.t | char_list
|
||||
def escape(string) do
|
||||
:re.replace(string, @escape_pattern, "\\\\&", [:global, { :return, return_for(string) }])
|
||||
@spec escape(String.t) :: String.t
|
||||
def escape(string) when is_binary(string) do
|
||||
:re.replace(string, @escape_pattern, "\\\\&", [:global, { :return, :binary }])
|
||||
end
|
||||
|
||||
# Helpers
|
||||
@@ -365,32 +428,17 @@ defmodule Regex do
|
||||
|
||||
# Private Helpers
|
||||
|
||||
defp return_for(element) when is_binary(element), do: :binary
|
||||
defp return_for(element) when is_list(element), do: :list
|
||||
|
||||
defp translate_options(<<?u, t :: binary>>) do
|
||||
IO.write "The /u flag for regular expressions is no longer needed\n#{Exception.format_stacktrace}"
|
||||
defp translate_options(<<?g, t :: binary>>) do
|
||||
IO.write :stderr, "The /g flag for regular expressions is no longer needed\n#{Exception.format_stacktrace}"
|
||||
translate_options(t)
|
||||
end
|
||||
|
||||
defp translate_options(<<?i, t :: binary>>), do: [:caseless|translate_options(t)]
|
||||
defp translate_options(<<?x, t :: binary>>), do: [:extended|translate_options(t)]
|
||||
defp translate_options(<<?f, t :: binary>>), do: [:firstline|translate_options(t)]
|
||||
defp translate_options(<<?r, t :: binary>>), do: [:ungreedy|translate_options(t)]
|
||||
defp translate_options(<<?s, t :: binary>>), do: [:dotall, {:newline, :anycrlf}|translate_options(t)]
|
||||
defp translate_options(<<?m, t :: binary>>), do: [:multiline|translate_options(t)]
|
||||
defp translate_options(<<?g, t :: binary>>), do: [:groups|translate_options(t)]
|
||||
defp translate_options(<<>>), do: []
|
||||
defp translate_options(rest), do: { :error, rest }
|
||||
|
||||
{ :ok, pattern } = :re.compile(%S"\(\?<(?<G>[^>]*)>")
|
||||
@groups_pattern pattern
|
||||
|
||||
defp parse_groups(source) do
|
||||
options = [:global, {:capture, ['G'], :binary}]
|
||||
case :re.run(source, @groups_pattern, options) do
|
||||
:nomatch -> []
|
||||
{ :match, results } ->
|
||||
lc [group] inlist results, do: binary_to_atom(group)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
+140
-49
@@ -1,5 +1,5 @@
|
||||
defmodule Set do
|
||||
@moduledoc %S"""
|
||||
@moduledoc ~S"""
|
||||
This module specifies the Set API expected to be
|
||||
implemented by different representations.
|
||||
|
||||
@@ -12,32 +12,27 @@ defmodule Set do
|
||||
|
||||
HashSet.new #=> creates an empty HashSet
|
||||
|
||||
For simplicity's sake, in the examples below every time
|
||||
`new` is used, it implies one of the module-specific
|
||||
calls like above.
|
||||
In the examples below, `set_impl` means a specific
|
||||
`Set` implementation, for example `HashSet`.
|
||||
|
||||
## Protocols
|
||||
|
||||
Sets are required to implement the `Enumerable` protocol,
|
||||
allowing one to write:
|
||||
|
||||
Enum.each(set, fn k ->
|
||||
IO.inspect k
|
||||
end)
|
||||
Sets are required to implement both `Enumerable` and `Collectable`
|
||||
protocols.
|
||||
|
||||
## Match
|
||||
|
||||
Sets are required to implement all operations
|
||||
using the match (`===`) operator. Any deviation from
|
||||
this behaviour should be avoided and explicitly documented.
|
||||
Sets are required to implement all operations using the match (`===`)
|
||||
operator.
|
||||
"""
|
||||
|
||||
use Behaviour
|
||||
|
||||
@type value :: any
|
||||
@type values :: [ value ]
|
||||
@type t :: tuple
|
||||
@type t :: map | tuple
|
||||
|
||||
defcallback new :: t
|
||||
defcallback delete(t, value) :: t
|
||||
defcallback difference(t, t) :: t
|
||||
defcallback disjoint?(t, t) :: boolean
|
||||
@@ -53,10 +48,13 @@ defmodule Set do
|
||||
|
||||
defmacrop target(set) do
|
||||
quote do
|
||||
if is_tuple(unquote(set)) do
|
||||
elem(unquote(set), 0)
|
||||
else
|
||||
unsupported_set(unquote(set))
|
||||
case unquote(set) do
|
||||
%{__struct__: x} when is_atom(x) ->
|
||||
x
|
||||
x when is_tuple(x) ->
|
||||
elem(x, 0)
|
||||
x ->
|
||||
unsupported_set(x)
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -66,13 +64,14 @@ defmodule Set do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> s = HashSet.new([1, 2, 3])
|
||||
...> Set.delete(s, 4) |> HashSet.to_list
|
||||
iex> s = Enum.into([1, 2, 3], set_impl.new)
|
||||
iex> Set.delete(s, 4) |> Enum.sort
|
||||
[1, 2, 3]
|
||||
|
||||
iex> s = HashSet.new([1, 2, 3])
|
||||
...> Set.delete(s, 2) |> HashSet.to_list
|
||||
iex> s = Enum.into([1, 2, 3], set_impl.new)
|
||||
iex> Set.delete(s, 2) |> Enum.sort
|
||||
[1, 3]
|
||||
|
||||
"""
|
||||
@spec delete(t, value) :: t
|
||||
def delete(set, value) do
|
||||
@@ -82,73 +81,130 @@ defmodule Set do
|
||||
@doc """
|
||||
Returns a set that is `set1` without the members of `set2`.
|
||||
|
||||
Notice this function is polymorphic as it calculates the difference
|
||||
for of any type. Each set implementation also provides a `difference`
|
||||
function, but they can only work with sets of the same type.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Set.difference(HashSet.new([1,2]), HashSet.new([2,3,4])) |> HashSet.to_list
|
||||
iex> Set.difference(Enum.into([1,2], set_impl.new), Enum.into([2,3,4], set_impl.new)) |> Enum.sort
|
||||
[1]
|
||||
|
||||
"""
|
||||
@spec difference(t, t) :: t
|
||||
def difference(set1, set2) do
|
||||
target(set1).difference(set1, set2)
|
||||
target1 = target(set1)
|
||||
target2 = target(set2)
|
||||
|
||||
if target1 == target2 do
|
||||
target1.difference(set1, set2)
|
||||
else
|
||||
target2.reduce(set2, { :cont, set1 }, fn v, acc ->
|
||||
{ :cont, target1.delete(acc, v) }
|
||||
end) |> elem(1)
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Checks if `set1` and `set2` have no members in common.
|
||||
|
||||
Notice this function is polymorphic as it checks for disjoint sets of
|
||||
any type. Each set implementation also provides a `disjoint?` function,
|
||||
but they can only work with sets of the same type.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Set.disjoint?(HashSet.new([1, 2]), HashSet.new([3, 4]))
|
||||
iex> Set.disjoint?(Enum.into([1, 2], set_impl.new), Enum.into([3, 4], set_impl.new))
|
||||
true
|
||||
iex> Set.disjoint?(HashSet.new([1, 2]), HashSet.new([2, 3]))
|
||||
|
||||
iex> Set.disjoint?(Enum.into([1, 2], set_impl.new), Enum.into([2, 3], set_impl.new))
|
||||
false
|
||||
|
||||
"""
|
||||
@spec disjoint?(t, t) :: boolean
|
||||
def disjoint?(set1, set2) do
|
||||
target(set1).disjoint?(set1, set2)
|
||||
target1 = target(set1)
|
||||
target2 = target(set2)
|
||||
|
||||
if target1 == target2 do
|
||||
target1.disjoint?(set1, set2)
|
||||
else
|
||||
target2.reduce(set2, { :cont, true }, fn member, acc ->
|
||||
case target1.member?(set1, member) do
|
||||
false -> { :cont, acc }
|
||||
_ -> { :halt, false }
|
||||
end
|
||||
end) |> elem(1)
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns an empty set of the same type as `set`.
|
||||
"""
|
||||
@doc false
|
||||
@spec empty(t) :: t
|
||||
def empty(set) do
|
||||
target(set).empty(set)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Checks if `set1` and `set2` are equal.
|
||||
Check if two sets are equal using `===`.
|
||||
|
||||
Notice this function is polymorphic as it compares sets of
|
||||
any type. Each set implementation also provides an `equal?`
|
||||
function, but they can only work with sets of the same type.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Set.equal?(HashSet.new([1, 2]), HashSet.new([2, 1, 1]))
|
||||
iex> Set.equal?(Enum.into([1, 2], set_impl.new), Enum.into([2, 1, 1], set_impl.new))
|
||||
true
|
||||
|
||||
iex> Set.equal?(HashSet.new([1, 2]), HashSet.new([3, 4]))
|
||||
|
||||
iex> Set.equal?(Enum.into([1, 2], set_impl.new), Enum.into([3, 4], set_impl.new))
|
||||
false
|
||||
|
||||
"""
|
||||
@spec equal?(t, t) :: boolean
|
||||
def equal?(set1, set2) do
|
||||
target(set1).equal?(set1, set2)
|
||||
target1 = target(set1)
|
||||
target2 = target(set2)
|
||||
|
||||
cond do
|
||||
target1 == target2 ->
|
||||
target1.equal?(set1, set2)
|
||||
|
||||
target1.size(set1) == target2.size(set2) ->
|
||||
do_subset?(target1, target2, set1, set2)
|
||||
|
||||
true ->
|
||||
false
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns a set containing only members in common between `set1` and `set2`.
|
||||
|
||||
Notice this function is polymorphic as it calculates the intersection of
|
||||
any type. Each set implementation also provides a `intersection` function,
|
||||
but they can only work with sets of the same type.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Set.intersection(HashSet.new([1,2]), HashSet.new([2,3,4])) |> HashSet.to_list
|
||||
iex> Set.intersection(Enum.into([1,2], set_impl.new), Enum.into([2,3,4], set_impl.new)) |> Enum.sort
|
||||
[2]
|
||||
|
||||
iex> Set.intersection(HashSet.new([1,2]), HashSet.new([3,4])) |> HashSet.to_list
|
||||
iex> Set.intersection(Enum.into([1,2], set_impl.new), Enum.into([3,4], set_impl.new)) |> Enum.sort
|
||||
[]
|
||||
|
||||
"""
|
||||
@spec intersection(t, t) :: t
|
||||
def intersection(set1, set2) do
|
||||
target(set1).intersection(set1, set2)
|
||||
target1 = target(set1)
|
||||
target2 = target(set2)
|
||||
|
||||
if target1 == target2 do
|
||||
target1.intersection(set1, set2)
|
||||
else
|
||||
target1.reduce(set1, { :cont, Collectable.empty(set1) }, fn v, acc ->
|
||||
{ :cont, if(target2.member?(set2, v), do: target1.put(acc, v), else: acc) }
|
||||
end) |> elem(1)
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -156,10 +212,10 @@ defmodule Set do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Set.member?(HashSet.new([1, 2, 3]), 2)
|
||||
iex> Set.member?(Enum.into([1, 2, 3], set_impl.new), 2)
|
||||
true
|
||||
|
||||
iex> Set.member?(HashSet.new([1, 2, 3]), 4)
|
||||
iex> Set.member?(Enum.into([1, 2, 3], set_impl.new), 4)
|
||||
false
|
||||
|
||||
"""
|
||||
@@ -173,10 +229,10 @@ defmodule Set do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Set.put(HashSet.new([1, 2, 3]), 3) |> Set.to_list
|
||||
iex> Set.put(Enum.into([1, 2, 3], set_impl.new), 3) |> Enum.sort
|
||||
[1, 2, 3]
|
||||
|
||||
iex> Set.put(HashSet.new([1, 2, 3]), 4) |> Set.to_list
|
||||
iex> Set.put(Enum.into([1, 2, 3], set_impl.new), 4) |> Enum.sort
|
||||
[1, 2, 3, 4]
|
||||
|
||||
"""
|
||||
@@ -190,7 +246,7 @@ defmodule Set do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Set.size(HashSet.new([1, 2, 3]))
|
||||
iex> Set.size(Enum.into([1, 2, 3], set_impl.new))
|
||||
3
|
||||
|
||||
"""
|
||||
@@ -202,16 +258,29 @@ defmodule Set do
|
||||
@doc """
|
||||
Checks if `set1`'s members are all contained in `set2`.
|
||||
|
||||
Notice this function is polymorphic as it checks the subset for
|
||||
any type. Each set implementation also provides a `subset?` function,
|
||||
but they can only work with sets of the same type.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Set.subset?(HashSet.new([1, 2]), HashSet.new([1, 2, 3]))
|
||||
iex> Set.subset?(Enum.into([1, 2], set_impl.new), Enum.into([1, 2, 3], set_impl.new))
|
||||
true
|
||||
iex> Set.subset?(HashSet.new([1, 2, 3]), HashSet.new([1, 2]))
|
||||
|
||||
iex> Set.subset?(Enum.into([1, 2, 3], set_impl.new), Enum.into([1, 2], set_impl.new))
|
||||
false
|
||||
|
||||
"""
|
||||
@spec subset?(t, t) :: boolean
|
||||
def subset?(set1, set2) do
|
||||
target(set1).subset?(set1, set2)
|
||||
target1 = target(set1)
|
||||
target2 = target(set2)
|
||||
|
||||
if target1 == target2 do
|
||||
target1.subset?(set1, set2)
|
||||
else
|
||||
do_subset?(target1, target2, set1, set2)
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -219,7 +288,7 @@ defmodule Set do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> HashSet.to_list(HashSet.new([1, 2, 3]))
|
||||
iex> set_impl.to_list(Enum.into([1, 2, 3], set_impl.new)) |> Enum.sort
|
||||
[1,2,3]
|
||||
|
||||
"""
|
||||
@@ -231,15 +300,37 @@ defmodule Set do
|
||||
@doc """
|
||||
Returns a set containing all members of `set1` and `set2`.
|
||||
|
||||
Notice this function is polymorphic as it calculates the union of
|
||||
any type. Each set implementation also provides a `union` function,
|
||||
but they can only work with sets of the same type.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Set.union(HashSet.new([1,2]), HashSet.new([2,3,4])) |> HashSet.to_list
|
||||
iex> Set.union(Enum.into([1,2], set_impl.new), Enum.into([2,3,4], set_impl.new)) |> Enum.sort
|
||||
[1,2,3,4]
|
||||
|
||||
"""
|
||||
@spec union(t, t) :: t
|
||||
def union(set1, set2) do
|
||||
target(set1).union(set1, set2)
|
||||
target1 = target(set1)
|
||||
target2 = target(set2)
|
||||
|
||||
if target1 == target2 do
|
||||
target1.union(set1, set2)
|
||||
else
|
||||
target2.reduce(set2, { :cont, set1 }, fn v, acc ->
|
||||
{ :cont, target1.put(acc, v) }
|
||||
end) |> elem(1)
|
||||
end
|
||||
end
|
||||
|
||||
defp do_subset?(target1, target2, set1, set2) do
|
||||
target1.reduce(set1, { :cont, true }, fn member, acc ->
|
||||
case target2.member?(set2, member) do
|
||||
true -> { :cont, acc }
|
||||
_ -> { :halt, false }
|
||||
end
|
||||
end) |> elem(1)
|
||||
end
|
||||
|
||||
defp unsupported_set(set) do
|
||||
|
||||
+66
-57
@@ -81,9 +81,15 @@ defmodule Stream do
|
||||
|
||||
This module also provides many convenience functions for creating streams,
|
||||
like `Stream.cycle/1`, `Stream.unfold/2`, `Stream.resource/3` and more.
|
||||
|
||||
Note the functions in this module are guaranteed to return enumerables.
|
||||
Since enumerables can have different shapes (structs, anonymous functions,
|
||||
and so on), the functions in this module may return any of those shapes
|
||||
and that it may change at any time. For example, a function that today
|
||||
returns an anonymous function may return a struct in future releases.
|
||||
"""
|
||||
|
||||
defrecord Lazy, enum: nil, funs: [], accs: [], after: [], last: nil
|
||||
defrecord Lazy, enum: nil, funs: [], accs: [], done: nil
|
||||
|
||||
defimpl Enumerable, for: Lazy do
|
||||
@compile :inline_list_funs
|
||||
@@ -103,46 +109,28 @@ defmodule Stream do
|
||||
{ :error, __MODULE__ }
|
||||
end
|
||||
|
||||
defp do_reduce(Lazy[enum: enum, funs: funs, accs: accs, last: last, after: after_funs], acc, fun) do
|
||||
defp do_reduce(Lazy[enum: enum, funs: funs, accs: accs, done: done], acc, fun) do
|
||||
composed = :lists.foldl(fn fun, acc -> fun.(acc) end, fun, funs)
|
||||
do_each(&Enumerable.reduce(enum, &1, composed), after_funs,
|
||||
last && { last, fun }, :lists.reverse(accs), acc)
|
||||
do_each(&Enumerable.reduce(enum, &1, composed),
|
||||
done && { done, fun }, :lists.reverse(accs), acc)
|
||||
end
|
||||
|
||||
defp do_each(reduce, [], last, accs, { command, acc }) do
|
||||
handle_reduce reduce.({ command, [acc|accs] }), [], last
|
||||
end
|
||||
|
||||
defp do_each(reduce, after_funs, last, accs, { command, acc }) do
|
||||
try do
|
||||
handle_reduce reduce.({ command, [acc|accs] }), after_funs, last
|
||||
catch
|
||||
kind, reason ->
|
||||
lc fun inlist after_funs, do: fun.()
|
||||
:erlang.raise(kind, reason, :erlang.get_stacktrace)
|
||||
end
|
||||
end
|
||||
|
||||
defp handle_reduce(res, after_funs, last) do
|
||||
case res do
|
||||
defp do_each(reduce, done, accs, { command, acc }) do
|
||||
case reduce.({ command, [acc|accs] }) do
|
||||
{ :suspended, [acc|accs], continuation } ->
|
||||
{ :suspended, acc, &do_each(continuation, after_funs, last, accs, &1) }
|
||||
{ :suspended, acc, &do_each(continuation, done, accs, &1) }
|
||||
{ :halted, [acc|_] } ->
|
||||
lc fun inlist after_funs, do: fun.()
|
||||
{ :halted, acc }
|
||||
{ :done, [acc|_] = accs } ->
|
||||
case last do
|
||||
case done do
|
||||
nil ->
|
||||
lc fun inlist after_funs, do: fun.()
|
||||
{ :done, acc }
|
||||
{ last, fun } ->
|
||||
res = case last.(fun).(accs) do
|
||||
{ done, fun } ->
|
||||
case done.(fun).(accs) do
|
||||
{ :cont, [acc|_] } -> { :done, acc }
|
||||
{ :halt, [acc|_] } -> { :halted, acc }
|
||||
{ :suspend, [acc|_] } -> { :suspended, acc, &({ :done, &1 |> elem(1) }) }
|
||||
{ :suspend, [acc|_] } -> { :suspended, acc, &({ :done, elem(&1, 1) }) }
|
||||
end
|
||||
lc fun inlist after_funs, do: fun.()
|
||||
res
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -173,26 +161,6 @@ defmodule Stream do
|
||||
|
||||
## Transformers
|
||||
|
||||
@doc """
|
||||
Executes the given function when the stream is done, halted
|
||||
or an error happened during streaming. Useful for resource
|
||||
clean up.
|
||||
|
||||
Callbacks registered later will be executed earlier.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> stream = Stream.after [1,2,3], fn -> Process.put(:done, true) end
|
||||
iex> Enum.to_list(stream)
|
||||
[1,2,3]
|
||||
iex> Process.get(:done)
|
||||
true
|
||||
|
||||
"""
|
||||
@spec unquote(:after)(Enumerable.t, (() -> term)) :: Enumerable.t
|
||||
def unquote(:after)(Lazy[after: funs] = lazy, fun), do: lazy.after([fun|funs])
|
||||
def unquote(:after)(enum, fun), do: Lazy[enum: enum, after: [fun]]
|
||||
|
||||
@doc """
|
||||
Shortcut to `chunk(enum, n, n)`.
|
||||
"""
|
||||
@@ -216,17 +184,20 @@ defmodule Stream do
|
||||
|
||||
iex> Stream.chunk([1, 2, 3, 4, 5, 6], 2) |> Enum.to_list
|
||||
[[1, 2], [3, 4], [5, 6]]
|
||||
|
||||
iex> Stream.chunk([1, 2, 3, 4, 5, 6], 3, 2) |> Enum.to_list
|
||||
[[1, 2, 3], [3, 4, 5]]
|
||||
|
||||
iex> Stream.chunk([1, 2, 3, 4, 5, 6], 3, 2, [7]) |> Enum.to_list
|
||||
[[1, 2, 3], [3, 4, 5], [5, 6, 7]]
|
||||
|
||||
iex> Stream.chunk([1, 2, 3, 4, 5, 6], 3, 3, []) |> Enum.to_list
|
||||
[[1, 2, 3], [4, 5, 6]]
|
||||
|
||||
"""
|
||||
@spec chunk(Enumerable.t, non_neg_integer, non_neg_integer) :: Enumerable.t
|
||||
@spec chunk(Enumerable.t, non_neg_integer, non_neg_integer, Enumerable.t | nil) :: Enumerable.t
|
||||
def chunk(enum, n, step, pad // nil) when n > 0 and step > 0 do
|
||||
def chunk(enum, n, step, pad \\ nil) when n > 0 and step > 0 do
|
||||
limit = :erlang.max(n, step)
|
||||
lazy enum, { [], 0 },
|
||||
fn(f1) -> R.chunk(n, step, limit, f1) end,
|
||||
@@ -410,6 +381,44 @@ defmodule Stream do
|
||||
lazy enum, fn(f1) -> R.filter_map(filter, mapper, f1) end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Injects the stream values into the given collectable as a side-effect.
|
||||
|
||||
This function is often used with `run/1` since any evaluation
|
||||
is delayed until the stream is executed. See `run/1` for an example.
|
||||
"""
|
||||
@spec into(Enumerable.t, Collectable.t) :: Enumerable.t
|
||||
def into(enum, collectable, transform \\ fn x -> x end) do
|
||||
&do_into(enum, collectable, transform, &1, &2)
|
||||
end
|
||||
|
||||
defp do_into(enum, collectable, transform, acc, fun) do
|
||||
{ initial, into } = Collectable.into(collectable)
|
||||
composed = fn x, [acc|collectable] ->
|
||||
collectable = into.(collectable, { :cont, transform.(x) })
|
||||
{ reason, acc } = fun.(x, acc)
|
||||
{ reason, [acc|collectable] }
|
||||
end
|
||||
do_into(&Enumerable.reduce(enum, &1, composed), initial, into, acc)
|
||||
end
|
||||
|
||||
defp do_into(reduce, collectable, into, { command, acc }) do
|
||||
try do
|
||||
reduce.({ command, [acc|collectable] })
|
||||
catch
|
||||
kind, reason ->
|
||||
stacktrace = System.stacktrace
|
||||
into.(collectable, :halt)
|
||||
:erlang.raise(kind, reason, stacktrace)
|
||||
else
|
||||
{ :suspended, [acc|collectable], continuation } ->
|
||||
{ :suspended, acc, &do_into(continuation, collectable, into, &1) }
|
||||
{ reason, [acc|collectable] } ->
|
||||
into.(collectable, :done)
|
||||
{ reason, acc }
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Creates a stream that will apply the given function on
|
||||
enumeration.
|
||||
@@ -455,7 +464,7 @@ defmodule Stream do
|
||||
|
||||
stream = File.stream!("code")
|
||||
|> Stream.map(&String.replace(&1, "#", "%"))
|
||||
|> File.stream_to!("new")
|
||||
|> Stream.into(File.stream!("new"))
|
||||
|
||||
No computation will be done until we call one of the Enum functions
|
||||
or `Stream.run/1`.
|
||||
@@ -596,7 +605,6 @@ defmodule Stream do
|
||||
lazy enum, fn(f1) -> R.take_while(fun, f1) end
|
||||
end
|
||||
|
||||
|
||||
@doc """
|
||||
Transforms an existing stream.
|
||||
|
||||
@@ -729,7 +737,7 @@ defmodule Stream do
|
||||
"""
|
||||
@spec uniq(Enumerable.t) :: Enumerable.t
|
||||
@spec uniq(Enumerable.t, (element -> term)) :: Enumerable.t
|
||||
def uniq(enum, fun // fn x -> x end) do
|
||||
def uniq(enum, fun \\ fn x -> x end) do
|
||||
lazy enum, [], fn f1 -> R.uniq(fun, f1) end
|
||||
end
|
||||
|
||||
@@ -1017,6 +1025,7 @@ defmodule Stream do
|
||||
|
||||
iex> Stream.unfold(5, fn 0 -> nil; n -> {n, n-1} end) |> Enum.to_list()
|
||||
[5, 4, 3, 2, 1]
|
||||
|
||||
"""
|
||||
@spec unfold(acc, (acc -> { element, acc } | nil)) :: Enumerable.t
|
||||
def unfold(next_acc, next_fun) do
|
||||
@@ -1052,8 +1061,8 @@ defmodule Stream do
|
||||
defp lazy(enum, acc, fun),
|
||||
do: Lazy[enum: enum, funs: [fun], accs: [acc]]
|
||||
|
||||
defp lazy(Lazy[last: nil, funs: funs, accs: accs] = lazy, acc, fun, last),
|
||||
do: lazy.funs([fun|funs]).accs([acc|accs]).last(last)
|
||||
defp lazy(enum, acc, fun, last),
|
||||
do: Lazy[enum: enum, funs: [fun], accs: [acc], last: last]
|
||||
defp lazy(Lazy[done: nil, funs: funs, accs: accs] = lazy, acc, fun, done),
|
||||
do: lazy.funs([fun|funs]).accs([acc|accs]).done(done)
|
||||
defp lazy(enum, acc, fun, done),
|
||||
do: Lazy[enum: enum, funs: [fun], accs: [acc], done: done]
|
||||
end
|
||||
|
||||
@@ -2,7 +2,7 @@ defmodule Stream.Reducers do
|
||||
# Collection of reducers shared by Enum and Stream.
|
||||
@moduledoc false
|
||||
|
||||
defmacro chunk(n, step, limit, f // nil) do
|
||||
defmacro chunk(n, step, limit, f \\ nil) do
|
||||
quote do
|
||||
fn entry, acc(h, { buffer, count }, t) ->
|
||||
buffer = [entry|buffer]
|
||||
@@ -25,7 +25,7 @@ defmodule Stream.Reducers do
|
||||
end
|
||||
end
|
||||
|
||||
defmacro chunk_by(callback, f // nil) do
|
||||
defmacro chunk_by(callback, f \\ nil) do
|
||||
quote do
|
||||
fn
|
||||
entry, acc(h, { buffer, value }, t) ->
|
||||
@@ -41,7 +41,7 @@ defmodule Stream.Reducers do
|
||||
end
|
||||
end
|
||||
|
||||
defmacro drop(f // nil) do
|
||||
defmacro drop(f \\ nil) do
|
||||
quote do
|
||||
fn
|
||||
_entry, acc(h, n, t) when n > 0 ->
|
||||
@@ -52,7 +52,7 @@ defmodule Stream.Reducers do
|
||||
end
|
||||
end
|
||||
|
||||
defmacro drop_while(callback, f // nil) do
|
||||
defmacro drop_while(callback, f \\ nil) do
|
||||
quote do
|
||||
fn entry, acc(h, bool, t) = orig ->
|
||||
if bool and unquote(callback).(entry) do
|
||||
@@ -64,7 +64,7 @@ defmodule Stream.Reducers do
|
||||
end
|
||||
end
|
||||
|
||||
defmacro filter(callback, f // nil) do
|
||||
defmacro filter(callback, f \\ nil) do
|
||||
quote do
|
||||
fn(entry, acc) ->
|
||||
if unquote(callback).(entry) do
|
||||
@@ -76,7 +76,7 @@ defmodule Stream.Reducers do
|
||||
end
|
||||
end
|
||||
|
||||
defmacro filter_map(filter, mapper, f // nil) do
|
||||
defmacro filter_map(filter, mapper, f \\ nil) do
|
||||
quote do
|
||||
fn(entry, acc) ->
|
||||
if unquote(filter).(entry) do
|
||||
@@ -88,7 +88,7 @@ defmodule Stream.Reducers do
|
||||
end
|
||||
end
|
||||
|
||||
defmacro map(callback, f // nil) do
|
||||
defmacro map(callback, f \\ nil) do
|
||||
quote do
|
||||
fn(entry, acc) ->
|
||||
cont(unquote(f), unquote(callback).(entry), acc)
|
||||
@@ -96,7 +96,7 @@ defmodule Stream.Reducers do
|
||||
end
|
||||
end
|
||||
|
||||
defmacro reject(callback, f // nil) do
|
||||
defmacro reject(callback, f \\ nil) do
|
||||
quote do
|
||||
fn(entry, acc) ->
|
||||
unless unquote(callback).(entry) do
|
||||
@@ -108,7 +108,7 @@ defmodule Stream.Reducers do
|
||||
end
|
||||
end
|
||||
|
||||
defmacro scan_2(callback, f // nil) do
|
||||
defmacro scan_2(callback, f \\ nil) do
|
||||
quote do
|
||||
fn
|
||||
entry, acc(h, :first, t) ->
|
||||
@@ -120,7 +120,7 @@ defmodule Stream.Reducers do
|
||||
end
|
||||
end
|
||||
|
||||
defmacro scan_3(callback, f // nil) do
|
||||
defmacro scan_3(callback, f \\ nil) do
|
||||
quote do
|
||||
fn(entry, acc(h, acc, t)) ->
|
||||
value = unquote(callback).(entry, acc)
|
||||
@@ -129,7 +129,7 @@ defmodule Stream.Reducers do
|
||||
end
|
||||
end
|
||||
|
||||
defmacro take(f // nil) do
|
||||
defmacro take(f \\ nil) do
|
||||
quote do
|
||||
fn(entry, acc(h, n, t) = orig) ->
|
||||
if n >= 1 do
|
||||
@@ -141,7 +141,7 @@ defmodule Stream.Reducers do
|
||||
end
|
||||
end
|
||||
|
||||
defmacro take_every(nth, f // nil) do
|
||||
defmacro take_every(nth, f \\ nil) do
|
||||
quote do
|
||||
fn
|
||||
entry, acc(h, n, t) when n === :first
|
||||
@@ -153,7 +153,7 @@ defmodule Stream.Reducers do
|
||||
end
|
||||
end
|
||||
|
||||
defmacro take_while(callback, f // nil) do
|
||||
defmacro take_while(callback, f \\ nil) do
|
||||
quote do
|
||||
fn(entry, acc) ->
|
||||
if unquote(callback).(entry) do
|
||||
@@ -165,7 +165,7 @@ defmodule Stream.Reducers do
|
||||
end
|
||||
end
|
||||
|
||||
defmacro uniq(callback, f // nil) do
|
||||
defmacro uniq(callback, f \\ nil) do
|
||||
quote do
|
||||
fn(entry, acc(h, prev, t) = acc) ->
|
||||
value = unquote(callback).(entry)
|
||||
@@ -178,7 +178,7 @@ defmodule Stream.Reducers do
|
||||
end
|
||||
end
|
||||
|
||||
defmacro with_index(f // nil) do
|
||||
defmacro with_index(f \\ nil) do
|
||||
quote do
|
||||
fn(entry, acc(h, counter, t)) ->
|
||||
cont_with_acc(unquote(f), { entry, counter }, h, counter + 1, t)
|
||||
|
||||
+126
-44
@@ -1,7 +1,7 @@
|
||||
import Kernel, except: [length: 1]
|
||||
|
||||
defmodule String do
|
||||
@moduledoc %S"""
|
||||
@moduledoc ~S"""
|
||||
A String in Elixir is a UTF-8 encoded binary.
|
||||
|
||||
## String and binary operations
|
||||
@@ -47,7 +47,7 @@ defmodule String do
|
||||
by the letter "e" followed by the accent ́:
|
||||
|
||||
iex> string = "\x{0065}\x{0301}"
|
||||
...> byte_size(string)
|
||||
iex> byte_size(string)
|
||||
3
|
||||
iex> String.length(string)
|
||||
1
|
||||
@@ -83,13 +83,14 @@ defmodule String do
|
||||
|
||||
iex> ?j
|
||||
106
|
||||
|
||||
iex> ?é
|
||||
233
|
||||
|
||||
Or also via pattern matching:
|
||||
|
||||
iex> << eacute :: utf8 >> = "é"
|
||||
...> eacute
|
||||
iex> eacute
|
||||
233
|
||||
|
||||
As we have seen above, codepoints can be inserted into
|
||||
@@ -159,8 +160,10 @@ defmodule String do
|
||||
|
||||
iex> String.split("foo bar")
|
||||
["foo", "bar"]
|
||||
|
||||
iex> String.split("foo" <> <<194, 133>> <> "bar")
|
||||
["foo", "bar"]
|
||||
|
||||
iex> String.split(" foo bar ")
|
||||
["foo", "bar"]
|
||||
|
||||
@@ -168,7 +171,7 @@ defmodule String do
|
||||
@spec split(t) :: [t]
|
||||
defdelegate split(binary), to: String.Unicode
|
||||
|
||||
@doc %S"""
|
||||
@doc ~S"""
|
||||
Divides a string into substrings based on a pattern,
|
||||
returning a list of these substrings. The pattern can
|
||||
be a string, a list of strings or a regular expression.
|
||||
@@ -185,8 +188,10 @@ defmodule String do
|
||||
|
||||
iex> String.split("a,b,c", ",")
|
||||
["a", "b", "c"]
|
||||
|
||||
iex> String.split("a,b,c", ",", global: false)
|
||||
["a", "b,c"]
|
||||
|
||||
iex> String.split(" a b c ", " ", trim: true)
|
||||
["a", "b", "c"]
|
||||
|
||||
@@ -197,45 +202,50 @@ defmodule String do
|
||||
|
||||
A regular expression:
|
||||
|
||||
iex> String.split("a,b,c", %r{,})
|
||||
iex> String.split("a,b,c", ~r{,})
|
||||
["a", "b", "c"]
|
||||
iex> String.split("a,b,c", %r{,}, global: false)
|
||||
|
||||
iex> String.split("a,b,c", ~r{,}, global: false)
|
||||
["a", "b,c"]
|
||||
iex> String.split(" a b c ", %r{\s}, trim: true)
|
||||
|
||||
iex> String.split(" a b c ", ~r{\s}, trim: true)
|
||||
["a", "b", "c"]
|
||||
|
||||
Splitting on empty patterns returns codepoints:
|
||||
|
||||
iex> String.split("abc", %r{})
|
||||
iex> String.split("abc", ~r{})
|
||||
["a", "b", "c", ""]
|
||||
|
||||
iex> String.split("abc", "")
|
||||
["a", "b", "c", ""]
|
||||
|
||||
iex> String.split("abc", "", trim: true)
|
||||
["a", "b", "c"]
|
||||
|
||||
iex> String.split("abc", "", global: false)
|
||||
["a", "bc"]
|
||||
|
||||
"""
|
||||
@spec split(t, t | [t] | Regex.t) :: [t]
|
||||
@spec split(t, t | [t] | Regex.t, Keyword.t) :: [t]
|
||||
def split(binary, pattern, options // [])
|
||||
def split(binary, pattern, options \\ [])
|
||||
|
||||
def split("", _pattern, _options), do: [""]
|
||||
|
||||
def split(binary, "", options), do: split(binary, %r"", options)
|
||||
|
||||
def split(binary, pattern, options) when is_regex(pattern) do
|
||||
Regex.split(pattern, binary, options)
|
||||
end
|
||||
def split(binary, "", options), do: split(binary, ~r"", options)
|
||||
|
||||
def split(binary, pattern, options) do
|
||||
opts = if options[:global] != false, do: [:global], else: []
|
||||
splits = :binary.split(binary, pattern, opts)
|
||||
|
||||
if Keyword.get(options, :trim, false) do
|
||||
lc split inlist splits, split != "", do: split
|
||||
if Regex.regex?(pattern) do
|
||||
Regex.split(pattern, binary, options)
|
||||
else
|
||||
splits
|
||||
opts = if options[:global] != false, do: [:global], else: []
|
||||
splits = :binary.split(binary, pattern, opts)
|
||||
|
||||
if Keyword.get(options, :trim, false) do
|
||||
for split <- splits, split != "", do: split
|
||||
else
|
||||
splits
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -246,8 +256,10 @@ defmodule String do
|
||||
|
||||
iex> String.upcase("abcd")
|
||||
"ABCD"
|
||||
|
||||
iex> String.upcase("ab 123 xpto")
|
||||
"AB 123 XPTO"
|
||||
|
||||
iex> String.upcase("josé")
|
||||
"JOSÉ"
|
||||
|
||||
@@ -262,8 +274,10 @@ defmodule String do
|
||||
|
||||
iex> String.downcase("ABCD")
|
||||
"abcd"
|
||||
|
||||
iex> String.downcase("AB 123 XPTO")
|
||||
"ab 123 xpto"
|
||||
|
||||
iex> String.downcase("JOSÉ")
|
||||
"josé"
|
||||
|
||||
@@ -284,8 +298,10 @@ defmodule String do
|
||||
|
||||
iex> String.capitalize("abcd")
|
||||
"Abcd"
|
||||
|
||||
iex> String.capitalize("fin")
|
||||
"Fin"
|
||||
|
||||
iex> String.capitalize("josé")
|
||||
"José"
|
||||
|
||||
@@ -333,7 +349,7 @@ defmodule String do
|
||||
end
|
||||
end
|
||||
|
||||
def rstrip(string, char) do
|
||||
def rstrip(string, char) when is_integer(char) do
|
||||
do_rstrip(string, "", char)
|
||||
end
|
||||
|
||||
@@ -373,11 +389,11 @@ defmodule String do
|
||||
|
||||
@spec lstrip(t, char) :: t
|
||||
|
||||
def lstrip(<<char :: utf8, rest :: binary>>, char) do
|
||||
def lstrip(<<char :: utf8, rest :: binary>>, char) when is_integer(char) do
|
||||
<<lstrip(rest, char) :: binary>>
|
||||
end
|
||||
|
||||
def lstrip(other, _char) do
|
||||
def lstrip(other, char) when is_integer(char) do
|
||||
other
|
||||
end
|
||||
|
||||
@@ -413,7 +429,7 @@ defmodule String do
|
||||
rstrip(lstrip(string, char), char)
|
||||
end
|
||||
|
||||
@doc %S"""
|
||||
@doc ~S"""
|
||||
Returns a new string of length `len` with `subject` right justified and
|
||||
padded with `padding`. If `padding` is not present, it defaults to
|
||||
whitespace. When `len` is less than the length of `subject`, `subject` is
|
||||
@@ -423,6 +439,7 @@ defmodule String do
|
||||
|
||||
iex> String.rjust("abc", 5)
|
||||
" abc"
|
||||
|
||||
iex> String.rjust("abc", 5, ?-)
|
||||
"--abc"
|
||||
|
||||
@@ -438,7 +455,7 @@ defmodule String do
|
||||
do_justify(subject, len, padding, :right)
|
||||
end
|
||||
|
||||
@doc %S"""
|
||||
@doc ~S"""
|
||||
Returns a new string of length `len` with `subject` left justified and padded
|
||||
with `padding`. If `padding` is not present, it defaults to whitespace. When
|
||||
`len` is less than the length of `subject`, `subject` is returned.
|
||||
@@ -447,6 +464,7 @@ defmodule String do
|
||||
|
||||
iex> String.ljust("abc", 5)
|
||||
"abc "
|
||||
|
||||
iex> String.ljust("abc", 5, ?-)
|
||||
"abc--"
|
||||
|
||||
@@ -482,7 +500,7 @@ defmodule String do
|
||||
end
|
||||
end
|
||||
|
||||
@doc %S"""
|
||||
@doc ~S"""
|
||||
Returns a new binary based on `subject` by replacing the parts
|
||||
matching `pattern` by `replacement`. By default, it replaces
|
||||
all entries, except if the `global` option is set to `false`.
|
||||
@@ -493,13 +511,14 @@ defmodule String do
|
||||
|
||||
iex> String.replace("a,b,c", ",", "-")
|
||||
"a-b-c"
|
||||
|
||||
iex> String.replace("a,b,c", ",", "-", global: false)
|
||||
"a-b,c"
|
||||
|
||||
The pattern can also be a regex. In those cases, one can give `\N`
|
||||
in the `replacement` string to access a specific capture in the regex:
|
||||
|
||||
iex> String.replace("a,b,c", %r/,(.)/, ",\\1\\1")
|
||||
iex> String.replace("a,b,c", ~r/,(.)/, ",\\1\\1")
|
||||
"a,bb,cc"
|
||||
|
||||
Notice we had to escape the escape character `\`. By giving `&`,
|
||||
@@ -510,8 +529,10 @@ defmodule String do
|
||||
|
||||
iex> String.replace("a,b,c", "b", "[]", insert_replaced: 1)
|
||||
"a,[b],c"
|
||||
|
||||
iex> String.replace("a,b,c", ",", "[]", insert_replaced: 2)
|
||||
"a[],b[],c"
|
||||
|
||||
iex> String.replace("a,b,c", ",", "[]", insert_replaced: [1, 1])
|
||||
"a[,,]b[,,]c"
|
||||
|
||||
@@ -519,15 +540,15 @@ defmodule String do
|
||||
@spec replace(t, t, t) :: t
|
||||
@spec replace(t, t, t, Keyword.t) :: t
|
||||
|
||||
def replace(subject, pattern, replacement, options // [])
|
||||
|
||||
def replace(subject, pattern, replacement, options) when is_regex(pattern) do
|
||||
Regex.replace(pattern, subject, replacement, global: options[:global])
|
||||
end
|
||||
def replace(subject, pattern, replacement, options \\ [])
|
||||
|
||||
def replace(subject, pattern, replacement, options) do
|
||||
opts = translate_replace_options(options)
|
||||
:binary.replace(subject, pattern, replacement, opts)
|
||||
if Regex.regex?(pattern) do
|
||||
Regex.replace(pattern, subject, replacement, global: options[:global])
|
||||
else
|
||||
opts = translate_replace_options(options)
|
||||
:binary.replace(subject, pattern, replacement, opts)
|
||||
end
|
||||
end
|
||||
|
||||
defp translate_replace_options(options) do
|
||||
@@ -547,8 +568,10 @@ defmodule String do
|
||||
|
||||
iex> String.reverse("abcd")
|
||||
"dcba"
|
||||
|
||||
iex> String.reverse("hello world")
|
||||
"dlrow olleh"
|
||||
|
||||
iex> String.reverse("hello ∂og")
|
||||
"go∂ olleh"
|
||||
|
||||
@@ -571,8 +594,10 @@ defmodule String do
|
||||
|
||||
iex> String.duplicate("abc", 0)
|
||||
""
|
||||
|
||||
iex> String.duplicate("abc", 1)
|
||||
"abc"
|
||||
|
||||
iex> String.duplicate("abc", 2)
|
||||
"abcabc"
|
||||
|
||||
@@ -589,8 +614,10 @@ defmodule String do
|
||||
|
||||
iex> String.codepoints("josé")
|
||||
["j", "o", "s", "é"]
|
||||
|
||||
iex> String.codepoints("оптими зации")
|
||||
["о","п","т","и","м","и"," ","з","а","ц","и","и"]
|
||||
|
||||
iex> String.codepoints("ἅἪῼ")
|
||||
["ἅ","Ἢ","ῼ"]
|
||||
|
||||
@@ -620,17 +647,20 @@ defmodule String do
|
||||
@spec next_codepoint(t) :: {codepoint, t} | nil
|
||||
defdelegate next_codepoint(string), to: String.Unicode
|
||||
|
||||
@doc %S"""
|
||||
@doc ~S"""
|
||||
Checks whether `str` contains only valid characters.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> String.valid?("a")
|
||||
true
|
||||
|
||||
iex> String.valid?("ø")
|
||||
true
|
||||
|
||||
iex> String.valid?(<<0xffff :: 16>>)
|
||||
false
|
||||
|
||||
iex> String.valid?("asd" <> <<0xffff :: 16>>)
|
||||
false
|
||||
|
||||
@@ -643,7 +673,7 @@ defmodule String do
|
||||
?\x{6FFFE}, ?\x{6FFFF}, ?\x{7FFFE}, ?\x{7FFFF}, ?\x{8FFFE}, ?\x{8FFFF},
|
||||
?\x{9FFFE}, ?\x{9FFFF}, ?\x{10FFFE}, ?\x{10FFFF} ]
|
||||
|
||||
lc noncharacter inlist noncharacters do
|
||||
for noncharacter <- noncharacters do
|
||||
def valid?(<< unquote(noncharacter) :: utf8, _ :: binary >>), do: false
|
||||
end
|
||||
|
||||
@@ -651,7 +681,7 @@ defmodule String do
|
||||
def valid?(<<>>), do: true
|
||||
def valid?(_), do: false
|
||||
|
||||
@doc %S"""
|
||||
@doc ~S"""
|
||||
Checks whether `str` is a valid character.
|
||||
|
||||
All characters are codepoints, but some codepoints
|
||||
@@ -664,8 +694,10 @@ defmodule String do
|
||||
|
||||
iex> String.valid_character?("a")
|
||||
true
|
||||
|
||||
iex> String.valid_character?("ø")
|
||||
true
|
||||
|
||||
iex> String.valid_character?("\x{ffff}")
|
||||
false
|
||||
|
||||
@@ -714,6 +746,7 @@ defmodule String do
|
||||
|
||||
iex> String.first("elixir")
|
||||
"e"
|
||||
|
||||
iex> String.first("եոգլի")
|
||||
"ե"
|
||||
|
||||
@@ -734,6 +767,7 @@ defmodule String do
|
||||
|
||||
iex> String.last("elixir")
|
||||
"r"
|
||||
|
||||
iex> String.last("եոգլի")
|
||||
"ի"
|
||||
|
||||
@@ -756,6 +790,7 @@ defmodule String do
|
||||
|
||||
iex> String.length("elixir")
|
||||
6
|
||||
|
||||
iex> String.length("եոգլի")
|
||||
5
|
||||
|
||||
@@ -779,12 +814,16 @@ defmodule String do
|
||||
|
||||
iex> String.at("elixir", 0)
|
||||
"e"
|
||||
|
||||
iex> String.at("elixir", 1)
|
||||
"l"
|
||||
|
||||
iex> String.at("elixir", 10)
|
||||
nil
|
||||
|
||||
iex> String.at("elixir", -1)
|
||||
"r"
|
||||
|
||||
iex> String.at("elixir", -10)
|
||||
nil
|
||||
|
||||
@@ -822,18 +861,25 @@ defmodule String do
|
||||
|
||||
iex> String.slice("elixir", 1, 3)
|
||||
"lix"
|
||||
|
||||
iex> String.slice("elixir", 1, 10)
|
||||
"lixir"
|
||||
|
||||
iex> String.slice("elixir", 10, 3)
|
||||
nil
|
||||
|
||||
iex> String.slice("elixir", -4, 4)
|
||||
"ixir"
|
||||
|
||||
iex> String.slice("elixir", -10, 3)
|
||||
nil
|
||||
|
||||
iex> String.slice("a", 0, 1500)
|
||||
"a"
|
||||
|
||||
iex> String.slice("a", 1, 1500)
|
||||
""
|
||||
|
||||
iex> String.slice("a", 2, 1500)
|
||||
nil
|
||||
|
||||
@@ -870,26 +916,34 @@ defmodule String do
|
||||
|
||||
iex> String.slice("elixir", 1..3)
|
||||
"lix"
|
||||
|
||||
iex> String.slice("elixir", 1..10)
|
||||
"lixir"
|
||||
|
||||
iex> String.slice("elixir", 10..3)
|
||||
nil
|
||||
|
||||
iex> String.slice("elixir", -4..-1)
|
||||
"ixir"
|
||||
|
||||
iex> String.slice("elixir", 2..-1)
|
||||
"ixir"
|
||||
|
||||
iex> String.slice("elixir", -4..6)
|
||||
"ixir"
|
||||
|
||||
iex> String.slice("elixir", -1..-4)
|
||||
nil
|
||||
|
||||
iex> String.slice("elixir", -10..-7)
|
||||
nil
|
||||
|
||||
iex> String.slice("a", 0..1500)
|
||||
"a"
|
||||
|
||||
iex> String.slice("a", 1..1500)
|
||||
""
|
||||
|
||||
iex> String.slice("a", 2..1500)
|
||||
nil
|
||||
|
||||
@@ -913,7 +967,7 @@ defmodule String do
|
||||
last = total + last
|
||||
end
|
||||
|
||||
if first > 0 do
|
||||
if first >= 0 do
|
||||
do_slice(next_grapheme(string), first, last, 0, "")
|
||||
end
|
||||
end
|
||||
@@ -953,8 +1007,10 @@ defmodule String do
|
||||
|
||||
iex> String.starts_with? "elixir", "eli"
|
||||
true
|
||||
|
||||
iex> String.starts_with? "elixir", ["erlang", "elixir"]
|
||||
true
|
||||
|
||||
iex> String.starts_with? "elixir", ["erlang", "ruby"]
|
||||
false
|
||||
|
||||
@@ -974,7 +1030,7 @@ defmodule String do
|
||||
end
|
||||
|
||||
defp do_starts_with(string, prefix) when is_binary(prefix) do
|
||||
match?({0, _}, :binary.match(string, prefix))
|
||||
Kernel.match?({0, _}, :binary.match(string, prefix))
|
||||
end
|
||||
|
||||
defp do_starts_with(_, _) do
|
||||
@@ -989,8 +1045,10 @@ defmodule String do
|
||||
|
||||
iex> String.ends_with? "language", "age"
|
||||
true
|
||||
|
||||
iex> String.ends_with? "language", ["youth", "age"]
|
||||
true
|
||||
|
||||
iex> String.ends_with? "language", ["youth", "elixir"]
|
||||
false
|
||||
|
||||
@@ -1021,27 +1079,47 @@ defmodule String do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns `true` if `string` contains match, otherwise `false`.
|
||||
Check if `string` matches the given regular expression.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> String.match?("foo", ~r/foo/)
|
||||
true
|
||||
|
||||
iex> String.match?("bar", ~r/foo/)
|
||||
false
|
||||
|
||||
"""
|
||||
@spec match?(t, Regex.t) :: boolean
|
||||
def match?(string, regex) do
|
||||
Regex.match?(regex, string)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Check if `string` contains any of the given `contents`.
|
||||
|
||||
`matches` can be either a single string or a list of strings.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> String.contains? "elixir of life", "of"
|
||||
true
|
||||
|
||||
iex> String.contains? "elixir of life", ["life", "death"]
|
||||
true
|
||||
|
||||
iex> String.contains? "elixir of life", ["death", "mercury"]
|
||||
false
|
||||
|
||||
"""
|
||||
@spec contains?(t, t | [t]) :: boolean
|
||||
|
||||
def contains?(string, matches) when is_list(matches) do
|
||||
Enum.any?(matches, &do_contains(string, &1))
|
||||
def contains?(string, contents) when is_list(contents) do
|
||||
Enum.any?(contents, &do_contains(string, &1))
|
||||
end
|
||||
|
||||
def contains?(string, match) do
|
||||
do_contains(string, match)
|
||||
def contains?(string, content) do
|
||||
do_contains(string, content)
|
||||
end
|
||||
|
||||
defp do_contains(string, "") when is_binary(string) do
|
||||
@@ -1081,6 +1159,7 @@ defmodule String do
|
||||
|
||||
iex> String.to_char_list("æß")
|
||||
{ :ok, 'æß' }
|
||||
|
||||
iex> String.to_char_list("abc")
|
||||
{ :ok, 'abc' }
|
||||
|
||||
@@ -1110,6 +1189,7 @@ defmodule String do
|
||||
|
||||
iex> String.to_char_list!("æß")
|
||||
'æß'
|
||||
|
||||
iex> String.to_char_list!("abc")
|
||||
'abc'
|
||||
|
||||
@@ -1135,6 +1215,7 @@ defmodule String do
|
||||
|
||||
iex> String.from_char_list([0x00E6, 0x00DF])
|
||||
{ :ok, "æß" }
|
||||
|
||||
iex> String.from_char_list([0x0061, 0x0062, 0x0063])
|
||||
{ :ok, "abc" }
|
||||
|
||||
@@ -1162,6 +1243,7 @@ defmodule String do
|
||||
|
||||
iex> String.from_char_list!([0x00E6, 0x00DF])
|
||||
"æß"
|
||||
|
||||
iex> String.from_char_list!([0x0061, 0x0062, 0x0063])
|
||||
"abc"
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import Kernel, except: [to_string: 1]
|
||||
|
||||
defprotocol String.Chars do
|
||||
@moduledoc %S"""
|
||||
@moduledoc ~S"""
|
||||
The String.Chars protocol is responsible for
|
||||
converting a structure to a Binary (only if applicable).
|
||||
The only function required to be implemented is
|
||||
@@ -58,6 +58,7 @@ defimpl String.Chars, for: List do
|
||||
|
||||
iex> to_string('foo')
|
||||
"foo"
|
||||
|
||||
iex> to_string(["foo", 'bar'])
|
||||
"foobar"
|
||||
|
||||
|
||||
@@ -0,0 +1,345 @@
|
||||
defmodule StringIO do
|
||||
@moduledoc """
|
||||
This module provides an IO device that wraps a string.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> { :ok, pid } = StringIO.open("foo")
|
||||
iex> IO.read(pid, 2)
|
||||
"fo"
|
||||
|
||||
"""
|
||||
|
||||
use GenServer.Behaviour
|
||||
|
||||
defrecordp :state, input: "", output: "", capture_prompt: false
|
||||
|
||||
@doc """
|
||||
Creates an IO device.
|
||||
|
||||
If the `:capture_prompt` option is set to `true`,
|
||||
prompts (specified as arguments to `IO.get*` functions)
|
||||
are captured.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> { :ok, pid } = StringIO.open("foo")
|
||||
iex> IO.gets(pid, ">")
|
||||
"foo"
|
||||
iex> StringIO.contents(pid)
|
||||
{ "", "" }
|
||||
|
||||
iex> { :ok, pid } = StringIO.open("foo", capture_prompt: true)
|
||||
iex> IO.gets(pid, ">")
|
||||
"foo"
|
||||
iex> StringIO.contents(pid)
|
||||
{ "", ">" }
|
||||
|
||||
"""
|
||||
@spec open(binary, Keyword.t) :: { :ok, pid }
|
||||
def open(string, options \\ []) when is_binary(string) do
|
||||
:gen_server.start_link(__MODULE__, { string, options }, [])
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns current buffers.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> { :ok, pid } = StringIO.open("in")
|
||||
iex> IO.write(pid, "out")
|
||||
iex> StringIO.contents(pid)
|
||||
{ "in", "out" }
|
||||
|
||||
"""
|
||||
@spec contents(pid) :: { binary, binary }
|
||||
def contents(pid) when is_pid(pid) do
|
||||
:gen_server.call(pid, :contents)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Stops the IO device and returns remaining buffers.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> { :ok, pid } = StringIO.open("in")
|
||||
iex> IO.write(pid, "out")
|
||||
iex> StringIO.close(pid)
|
||||
{ :ok, { "in", "out" } }
|
||||
|
||||
"""
|
||||
@spec close(pid) :: { :ok, { binary, binary } }
|
||||
def close(pid) when is_pid(pid) do
|
||||
:gen_server.call(pid, :close)
|
||||
end
|
||||
|
||||
## callbacks
|
||||
|
||||
def init({ string, options }) do
|
||||
capture_prompt = options[:capture_prompt] || false
|
||||
{ :ok, state(input: string, capture_prompt: capture_prompt) }
|
||||
end
|
||||
|
||||
def handle_info({ :io_request, from, reply_as, req }, s) do
|
||||
s = io_request(from, reply_as, req, s)
|
||||
{ :noreply, s }
|
||||
end
|
||||
|
||||
def handle_info(msg, s) do
|
||||
super(msg, s)
|
||||
end
|
||||
|
||||
def handle_call(:contents, _from, state(input: input, output: output) = s) do
|
||||
{ :reply, { input, output }, s }
|
||||
end
|
||||
|
||||
def handle_call(:close, _from, state(input: input, output: output) = s) do
|
||||
{ :stop, :normal, { :ok, { input, output } }, s }
|
||||
end
|
||||
|
||||
def handle_call(request, from, s) do
|
||||
super(request, from, s)
|
||||
end
|
||||
|
||||
defp io_request(from, reply_as, req, s) do
|
||||
{ reply, s } = io_request(req, s)
|
||||
io_reply(from, reply_as, to_reply(reply))
|
||||
s
|
||||
end
|
||||
|
||||
defp io_request({ :put_chars, chars }, state(output: output) = s) do
|
||||
{ :ok, state(s, output: << output :: binary, to_binary(chars) :: binary >>) }
|
||||
end
|
||||
|
||||
defp io_request({ :put_chars, m, f, as }, state(output: output) = s) do
|
||||
chars = apply(m, f, as)
|
||||
{ :ok, state(s, output: << output :: binary, to_binary(chars) :: binary >>) }
|
||||
end
|
||||
|
||||
defp io_request({ :put_chars, _encoding, chars }, s) do
|
||||
io_request({ :put_chars, chars }, s)
|
||||
end
|
||||
|
||||
defp io_request({ :put_chars, _encoding, mod, func, args }, s) do
|
||||
io_request({ :put_chars, mod, func, args }, s)
|
||||
end
|
||||
|
||||
defp io_request({ :get_chars, prompt, n }, s) when n >= 0 do
|
||||
io_request({ :get_chars, :latin1, prompt, n }, s)
|
||||
end
|
||||
|
||||
defp io_request({ :get_chars, encoding, prompt, n }, s) when n >= 0 do
|
||||
get_chars(encoding, prompt, n, s)
|
||||
end
|
||||
|
||||
defp io_request({ :get_line, prompt }, s) do
|
||||
io_request({ :get_line, :latin1, prompt }, s)
|
||||
end
|
||||
|
||||
defp io_request({ :get_line, encoding, prompt }, s) do
|
||||
get_line(encoding, prompt, s)
|
||||
end
|
||||
|
||||
defp io_request({ :get_until, prompt, mod, fun, args }, s) do
|
||||
io_request({ :get_until, :latin1, prompt, mod, fun, args }, s)
|
||||
end
|
||||
|
||||
defp io_request({ :get_until, encoding, prompt, mod, fun, args }, s) do
|
||||
get_until(encoding, prompt, mod, fun, args, s)
|
||||
end
|
||||
|
||||
defp io_request({ :get_password, encoding }, s) do
|
||||
get_line(encoding, "", s)
|
||||
end
|
||||
|
||||
defp io_request({ :setopts, _opts }, s) do
|
||||
{ { :error, :enotsup }, s }
|
||||
end
|
||||
|
||||
defp io_request(:getopts, s) do
|
||||
{ { :ok, [binary: true, encoding: :unicode] }, s }
|
||||
end
|
||||
|
||||
defp io_request({ :get_geometry, :columns }, s) do
|
||||
{ { :error, :enotsup }, s }
|
||||
end
|
||||
|
||||
defp io_request({ :get_geometry, :rows }, s) do
|
||||
{ { :error, :enotsup }, s }
|
||||
end
|
||||
|
||||
defp io_request({ :requests, reqs }, s) do
|
||||
io_requests(reqs, { :ok, s })
|
||||
end
|
||||
|
||||
defp io_request(_, s) do
|
||||
{ { :error, :request }, s }
|
||||
end
|
||||
|
||||
## get_chars
|
||||
|
||||
defp get_chars(encoding, prompt, n,
|
||||
state(input: input, output: output, capture_prompt: capture_prompt) = s) do
|
||||
case do_get_chars(input, encoding, n) do
|
||||
{ :error, _ } = error ->
|
||||
{ error, s }
|
||||
{ result, input } ->
|
||||
if capture_prompt do
|
||||
output = << output :: binary, to_binary(prompt) :: binary >>
|
||||
end
|
||||
|
||||
{ result, state(s, input: input, output: output) }
|
||||
end
|
||||
end
|
||||
|
||||
defp do_get_chars("", _encoding, _n) do
|
||||
{ :eof, "" }
|
||||
end
|
||||
|
||||
defp do_get_chars(input, :latin1, n) when byte_size(input) < n do
|
||||
{ input, "" }
|
||||
end
|
||||
|
||||
defp do_get_chars(input, :latin1, n) do
|
||||
<< chars :: [ binary, size(n) ], rest :: binary >> = input
|
||||
{ chars, rest }
|
||||
end
|
||||
|
||||
defp do_get_chars(input, encoding, n) do
|
||||
try do
|
||||
case :file_io_server.count_and_find(input, n, encoding) do
|
||||
{ buf_count, split_pos } when buf_count < n or split_pos == :none ->
|
||||
{ input, "" }
|
||||
{ _buf_count, split_pos } ->
|
||||
<< chars :: [ binary, size(split_pos) ], rest :: binary >> = input
|
||||
{ chars, rest }
|
||||
end
|
||||
catch
|
||||
:exit, :invalid_unicode ->
|
||||
{ :error, :invalid_unicode }
|
||||
end
|
||||
end
|
||||
|
||||
## get_line
|
||||
|
||||
defp get_line(encoding, prompt,
|
||||
state(input: input, output: output, capture_prompt: capture_prompt) = s) do
|
||||
case :unicode.characters_to_list(input, encoding) do
|
||||
{ :error, _, _ } ->
|
||||
{ { :error, :collect_line }, s }
|
||||
{ :incomplete, _, _ } ->
|
||||
{ { :error, :collect_line }, s }
|
||||
chars ->
|
||||
{ result, input } = do_get_line(chars, encoding)
|
||||
|
||||
if capture_prompt do
|
||||
output = << output :: binary, to_binary(prompt) :: binary >>
|
||||
end
|
||||
|
||||
{ result, state(s, input: input, output: output) }
|
||||
end
|
||||
end
|
||||
|
||||
defp do_get_line('', _encoding) do
|
||||
{ :eof, "" }
|
||||
end
|
||||
|
||||
defp do_get_line(chars, encoding) do
|
||||
{ line, rest } = collect_line(chars)
|
||||
{ :unicode.characters_to_binary(line, encoding),
|
||||
:unicode.characters_to_binary(rest, encoding) }
|
||||
end
|
||||
|
||||
## get_until
|
||||
|
||||
defp get_until(encoding, prompt, mod, fun, args,
|
||||
state(input: input, output: output, capture_prompt: capture_prompt) = s) do
|
||||
case :unicode.characters_to_list(input, encoding) do
|
||||
{ :error, _, _ } ->
|
||||
{ :error, s }
|
||||
{ :incomplete, _, _ } ->
|
||||
{ :error, s }
|
||||
chars ->
|
||||
{ result, input, count } = do_get_until(chars, encoding, mod, fun, args)
|
||||
|
||||
if capture_prompt do
|
||||
output = << output :: binary, :binary.copy(to_binary(prompt), count) :: binary >>
|
||||
end
|
||||
|
||||
input =
|
||||
case input do
|
||||
:eof -> ""
|
||||
_ -> :unicode.characters_to_binary(input, encoding)
|
||||
end
|
||||
|
||||
{ result, state(s, input: input, output: output) }
|
||||
end
|
||||
end
|
||||
|
||||
defp do_get_until(chars, encoding, mod, fun, args, continuation \\ [], count \\ 0)
|
||||
|
||||
defp do_get_until('', encoding, mod, fun, args, continuation, count) do
|
||||
case apply(mod, fun, [continuation, :eof | args]) do
|
||||
{ :done, result, rest } ->
|
||||
{ result, rest, count + 1 }
|
||||
{ :more, next_continuation } ->
|
||||
do_get_until('', encoding, mod, fun, args, next_continuation, count + 1)
|
||||
end
|
||||
end
|
||||
|
||||
defp do_get_until(chars, encoding, mod, fun, args, continuation, count) do
|
||||
{ line, rest } = collect_line(chars)
|
||||
|
||||
case apply(mod, fun, [continuation, line | args]) do
|
||||
{ :done, result, rest1 } ->
|
||||
unless rest1 == :eof do
|
||||
rest = rest1 ++ rest
|
||||
end
|
||||
{ result, rest, count + 1 }
|
||||
{ :more, next_continuation } ->
|
||||
do_get_until(rest, encoding, mod, fun, args, next_continuation, count + 1)
|
||||
end
|
||||
end
|
||||
|
||||
## io_requests
|
||||
|
||||
defp io_requests([r|rs], { :ok, s }) do
|
||||
io_requests(rs, io_request(r, s))
|
||||
end
|
||||
|
||||
defp io_requests(_, result) do
|
||||
result
|
||||
end
|
||||
|
||||
## helpers
|
||||
|
||||
defp collect_line(chars) do
|
||||
collect_line(chars, [])
|
||||
end
|
||||
|
||||
defp collect_line([], stack) do
|
||||
{ :lists.reverse(stack), [] }
|
||||
end
|
||||
|
||||
defp collect_line([?\r, ?\n | rest], stack) do
|
||||
{ :lists.reverse([?\n|stack]), rest }
|
||||
end
|
||||
|
||||
defp collect_line([?\n | rest], stack) do
|
||||
{ :lists.reverse([?\n|stack]), rest }
|
||||
end
|
||||
|
||||
defp collect_line([h|t], stack) do
|
||||
collect_line(t, [h|stack])
|
||||
end
|
||||
|
||||
defp io_reply(from, reply_as, reply) do
|
||||
send from, { :io_reply, reply_as, reply }
|
||||
end
|
||||
|
||||
defp to_binary(list) when is_list(list), do: String.from_char_list!(list)
|
||||
defp to_binary(binary) when is_binary(binary), do: binary
|
||||
|
||||
defp to_reply(list) when is_list(list), do: String.from_char_list!(list)
|
||||
defp to_reply(other), do: other
|
||||
end
|
||||
@@ -152,7 +152,7 @@ defmodule Supervisor.Behaviour do
|
||||
|
||||
#{@child_doc}
|
||||
"""
|
||||
def worker(module, args, options // []) do
|
||||
def worker(module, args, options \\ []) do
|
||||
child(:worker, module, args, options)
|
||||
end
|
||||
|
||||
@@ -166,7 +166,7 @@ defmodule Supervisor.Behaviour do
|
||||
|
||||
#{@child_doc}
|
||||
"""
|
||||
def supervisor(module, args, options // []) do
|
||||
def supervisor(module, args, options \\ []) do
|
||||
options = Keyword.update(options, :shutdown, :infinity, fn(x) -> x end)
|
||||
child(:supervisor, module, args, options)
|
||||
end
|
||||
|
||||
+26
-18
@@ -1,13 +1,4 @@
|
||||
defmodule System do
|
||||
defexception NoHomeError,
|
||||
message: "could not find the user home, please set the HOME environment variable"
|
||||
|
||||
defexception NoTmpDirError,
|
||||
message: "could not get a writable temporary directory, please set the TMPDIR environment variable"
|
||||
|
||||
defexception NoAccessCwdError,
|
||||
message: "could not get a current working directory, the current location is not accessible"
|
||||
|
||||
@moduledoc """
|
||||
The System module provides access to variables used or
|
||||
maintained by the VM and to functions that interact directly
|
||||
@@ -29,7 +20,7 @@ defmodule System do
|
||||
# Read and strip the version from the `VERSION` file.
|
||||
defmacrop get_version do
|
||||
case read_stripped(:filename.join(__DIR__, "../../../VERSION")) do
|
||||
"" -> raise ArgumentError, message: "could not read the version number from VERSION"
|
||||
"" -> raise RuntimeError, message: "could not read the version number from VERSION"
|
||||
data -> data
|
||||
end
|
||||
end
|
||||
@@ -111,10 +102,11 @@ defmodule System do
|
||||
@doc """
|
||||
Current working directory, exception on error.
|
||||
|
||||
Returns the current working directory or raises `System.NoAccessCwdError`.
|
||||
Returns the current working directory or raises `RuntimeError`.
|
||||
"""
|
||||
def cwd! do
|
||||
cwd || raise NoAccessCwdError
|
||||
cwd ||
|
||||
raise RuntimeError, message: "could not get a current working directory, the current location is not accessible"
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -133,11 +125,12 @@ defmodule System do
|
||||
@doc """
|
||||
User home directory, exception on error.
|
||||
|
||||
Same as `user_home/0` but raises `System.NoHomeError`
|
||||
Same as `user_home/0` but raises `RuntimeError`
|
||||
instead of returning `nil` if no user home is set.
|
||||
"""
|
||||
def user_home! do
|
||||
user_home || raise NoHomeError
|
||||
user_home ||
|
||||
raise RuntimeError, message: "could not find the user home, please set the HOME environment variable"
|
||||
end
|
||||
|
||||
defp get_unix_home do
|
||||
@@ -154,7 +147,7 @@ defmodule System do
|
||||
)
|
||||
end
|
||||
|
||||
@doc %S"""
|
||||
@doc ~S"""
|
||||
Writable temporary directory.
|
||||
|
||||
Returns a writable temporary directory.
|
||||
@@ -179,11 +172,13 @@ defmodule System do
|
||||
@doc """
|
||||
Writable temporary directory, exception on error.
|
||||
|
||||
Same as `tmp_dir/0` but raises `System.NoTmpDirError`
|
||||
Same as `tmp_dir/0` but raises `RuntimeError`
|
||||
instead of returning `nil` if no temp dir is set.
|
||||
"""
|
||||
def tmp_dir! do
|
||||
tmp_dir || raise NoTmpDirError
|
||||
tmp_dir ||
|
||||
raise RuntimeError, message: "could not get a writable temporary directory, " <>
|
||||
"please set the TMPDIR environment variable"
|
||||
end
|
||||
|
||||
defp write_env_tmp_dir(env) do
|
||||
@@ -336,12 +331,25 @@ defmodule System do
|
||||
Enum.each dict, fn {key, val} -> put_env key, val end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Deletes an environment variable.
|
||||
|
||||
Removes the variable `varname` from the environment.
|
||||
"""
|
||||
@spec delete_env(String.t) :: :ok
|
||||
def delete_env(varname) do
|
||||
:os.unsetenv(String.to_char_list!(varname))
|
||||
:ok
|
||||
end
|
||||
|
||||
@doc """
|
||||
Last exception stacktrace.
|
||||
|
||||
Note that the Erlang VM (and therefore this function) does not
|
||||
return the current stacktrace but rather the stacktrace of the
|
||||
latest exception.
|
||||
|
||||
Inlined by the compiler into `:erlang.get_stacktrace/0`.
|
||||
"""
|
||||
def stacktrace do
|
||||
:erlang.get_stacktrace
|
||||
@@ -376,7 +384,7 @@ defmodule System do
|
||||
"""
|
||||
@spec halt() :: no_return
|
||||
@spec halt(non_neg_integer | binary | :abort) :: no_return
|
||||
def halt(status // 0)
|
||||
def halt(status \\ 0)
|
||||
|
||||
def halt(status) when is_integer(status) or status == :abort do
|
||||
:erlang.halt(status)
|
||||
|
||||
@@ -30,7 +30,7 @@ defmodule Tuple do
|
||||
## Examples
|
||||
|
||||
iex> tuple = { :bar, :baz }
|
||||
...> Tuple.insert_at(tuple, 0, :foo)
|
||||
iex> Tuple.insert_at(tuple, 0, :foo)
|
||||
{ :foo, :bar, :baz }
|
||||
|
||||
"""
|
||||
@@ -43,13 +43,13 @@ defmodule Tuple do
|
||||
Remove an element from a tuple.
|
||||
|
||||
Deletes the element at the zero-based `index` from `tuple`.
|
||||
Raises an `ArgumentError` if `index` is greater than
|
||||
Raises an `ArgumentError` if `index` is greater than
|
||||
or equal to the length of `tuple`.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> tuple = { :foo, :bar, :baz }
|
||||
...> Tuple.delete_at(tuple, 0)
|
||||
iex> Tuple.delete_at(tuple, 0)
|
||||
{ :bar, :baz }
|
||||
|
||||
"""
|
||||
|
||||
+40
-30
@@ -62,13 +62,14 @@ defmodule URI do
|
||||
|
||||
Takes an enumerable (containing a sequence of two-item tuples)
|
||||
and returns a string of the form "key1=value1&key2=value2..." where
|
||||
keys and values are URL encoded as per `encode/1`. Keys and values can
|
||||
be any term that implements the `String.Chars` protocol (i.e. can be converted
|
||||
to a binary).
|
||||
keys and values are URL encoded as per `encode/1`.
|
||||
|
||||
Keys and values can be any term that implements the `String.Chars`
|
||||
protocol, except lists which are explicitly forbidden.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> hd = HashDict.new([{"foo", 1}, {"bar", "2"}])
|
||||
iex> hd = %{"foo" => 1, "bar" => 2}
|
||||
iex> URI.encode_query(hd)
|
||||
"bar=2&foo=1"
|
||||
|
||||
@@ -76,7 +77,7 @@ defmodule URI do
|
||||
def encode_query(l), do: Enum.map_join(l, "&", &pair/1)
|
||||
|
||||
@doc """
|
||||
Decodes a query string into a `HashDict`.
|
||||
Decodes a query string into a dictionary (by default uses a map).
|
||||
|
||||
Given a query string of the form "key1=value1&key2=value2...", produces a
|
||||
`HashDict` with one entry for each key-value pair. Each key and value will be a
|
||||
@@ -86,17 +87,11 @@ defmodule URI do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> URI.decode_query("foo=1&bar=2") |> Dict.to_list
|
||||
[{"bar", "2"}, {"foo", "1"}]
|
||||
|
||||
iex> hd = HashDict.new()
|
||||
iex> URI.decode_query("foo=1&bar=2", hd) |> HashDict.keys
|
||||
["bar", "foo"]
|
||||
iex> URI.decode_query("foo=1&bar=2", hd) |> HashDict.values
|
||||
["2", "1"]
|
||||
iex> URI.decode_query("foo=1&bar=2")
|
||||
%{"bar" => "2", "foo" => "1"}
|
||||
|
||||
"""
|
||||
def decode_query(q, dict // HashDict.new) when is_binary(q) do
|
||||
def decode_query(q, dict \\ %{}) when is_binary(q) do
|
||||
Enum.reduce query_decoder(q), dict, fn({ k, v }, acc) -> Dict.put(acc, k, v) end
|
||||
end
|
||||
|
||||
@@ -134,6 +129,14 @@ defmodule URI do
|
||||
{ current, next }
|
||||
end
|
||||
|
||||
defp pair({k, _}) when is_list(k) do
|
||||
raise ArgumentError, message: "encode_query/1 keys cannot be lists, got: #{inspect k}"
|
||||
end
|
||||
|
||||
defp pair({_, v}) when is_list(v) do
|
||||
raise ArgumentError, message: "encode_query/1 values cannot be lists, got: #{inspect v}"
|
||||
end
|
||||
|
||||
defp pair({k, v}) do
|
||||
encode(to_string(k)) <> "=" <> encode(to_string(v))
|
||||
end
|
||||
@@ -143,11 +146,11 @@ defmodule URI do
|
||||
|
||||
## Example
|
||||
|
||||
iex> URI.encode("http://elixir-lang.com/getting_started/2.html")
|
||||
"http%3A%2F%2Felixir-lang.com%2Fgetting_started%2F2.html"
|
||||
iex> URI.encode("http://elixir-lang.org/getting_started/2.html")
|
||||
"http%3A%2F%2Felixir-lang.org%2Fgetting_started%2F2.html"
|
||||
|
||||
"""
|
||||
def encode(s), do: bc(<<c>> inbits s, do: <<percent(c) :: binary>>)
|
||||
def encode(s), do: for(<<c <- s>>, into: "", do: percent(c))
|
||||
|
||||
defp percent(32), do: <<?+>>
|
||||
defp percent(?-), do: <<?->>
|
||||
@@ -171,23 +174,30 @@ defmodule URI do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> URI.decode("http%3A%2F%2Felixir-lang.com")
|
||||
"http://elixir-lang.com"
|
||||
iex> URI.decode("http%3A%2F%2Felixir-lang.org")
|
||||
"http://elixir-lang.org"
|
||||
|
||||
"""
|
||||
def decode(<<?%, hex1, hex2, tail :: binary >>) do
|
||||
<< bsl(hex2dec(hex1), 4) + hex2dec(hex2) >> <> decode(tail)
|
||||
def decode(uri) do
|
||||
decode(uri, uri)
|
||||
end
|
||||
|
||||
def decode(<<head, tail :: binary >>) do
|
||||
<<check_plus(head)>> <> decode(tail)
|
||||
def decode(<<?%, hex1, hex2, tail :: binary >>, uri) do
|
||||
<< bsl(hex2dec(hex1, uri), 4) + hex2dec(hex2, uri) >> <> decode(tail, uri)
|
||||
end
|
||||
|
||||
def decode(<<>>), do: <<>>
|
||||
def decode(<<head, tail :: binary >>, uri) do
|
||||
<<check_plus(head)>> <> decode(tail, uri)
|
||||
end
|
||||
|
||||
defp hex2dec(n) when n in ?A..?F, do: n - ?A + 10
|
||||
defp hex2dec(n) when n in ?a..?f, do: n - ?a + 10
|
||||
defp hex2dec(n) when n in ?0..?9, do: n - ?0
|
||||
def decode(<<>>, _uri), do: <<>>
|
||||
|
||||
defp hex2dec(n, _uri) when n in ?A..?F, do: n - ?A + 10
|
||||
defp hex2dec(n, _uri) when n in ?a..?f, do: n - ?a + 10
|
||||
defp hex2dec(n, _uri) when n in ?0..?9, do: n - ?0
|
||||
defp hex2dec(_n, uri) do
|
||||
raise ArgumentError, message: "malformed URI #{inspect uri}"
|
||||
end
|
||||
|
||||
defp check_plus(?+), do: 32
|
||||
defp check_plus(c), do: c
|
||||
@@ -210,7 +220,7 @@ defmodule URI do
|
||||
"""
|
||||
def parse(s) when is_binary(s) do
|
||||
# From http://tools.ietf.org/html/rfc3986#appendix-B
|
||||
regex = %r/^(([^:\/?#]+):)?(\/\/([^\/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?/
|
||||
regex = ~r/^(([^:\/?#]+):)?(\/\/([^\/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?/
|
||||
parts = nillify(Regex.run(regex, s))
|
||||
|
||||
destructure [_, _, scheme, _, authority, path, _, query, _, fragment], parts
|
||||
@@ -240,7 +250,7 @@ defmodule URI do
|
||||
# Split an authority into its userinfo, host and port parts.
|
||||
defp split_authority(s) do
|
||||
s = s || ""
|
||||
components = Regex.run %r/(^(.*)@)?(\[[a-zA-Z0-9:.]*\]|[^:]*)(:(\d*))?/, s
|
||||
components = Regex.run ~r/(^(.*)@)?(\[[a-zA-Z0-9:.]*\]|[^:]*)(:(\d*))?/, s
|
||||
|
||||
destructure [_, _, userinfo, host, _, port], nillify(components)
|
||||
port = if port, do: binary_to_integer(port)
|
||||
@@ -252,7 +262,7 @@ defmodule URI do
|
||||
# Regex.run returns empty strings sometimes. We want
|
||||
# to replace those with nil for consistency.
|
||||
defp nillify(l) do
|
||||
lc s inlist l do
|
||||
for s <- l do
|
||||
if size(s) > 0, do: s, else: nil
|
||||
end
|
||||
end
|
||||
|
||||
+189
-215
@@ -1,17 +1,12 @@
|
||||
defmodule Version do
|
||||
@moduledoc %S"""
|
||||
@moduledoc ~S"""
|
||||
Functions for parsing and matching versions against requirements.
|
||||
|
||||
A version is a string in a specific format or a `Version.Schema`
|
||||
generated after parsing via `Version.parse/1`.
|
||||
|
||||
`Version` parsing and requirements follow
|
||||
[SemVer 2.0 schema](http://semver.org/) and you will get
|
||||
the most of Mix's version system by following it. In order
|
||||
to support integration with projects that may
|
||||
follow different versioning schemas, Elixir won't choke
|
||||
on unknown versions, however you won't be able to use
|
||||
Mix requirements with such unformatted versions.
|
||||
[SemVer 2.0 schema](http://semver.org/).
|
||||
|
||||
## Versions
|
||||
|
||||
@@ -54,7 +49,6 @@ defmodule Version do
|
||||
|
||||
@type t :: String.t | Version.Schema.t
|
||||
@type requirement :: String.t | Version.Requirement.t
|
||||
|
||||
@type matchable :: { major :: String.t | non_neg_integer,
|
||||
minor :: non_neg_integer | nil,
|
||||
patch :: non_neg_integer | nil,
|
||||
@@ -62,98 +56,147 @@ defmodule Version do
|
||||
|
||||
import Kernel, except: [match?: 2]
|
||||
|
||||
defrecord Schema, major: 0, minor: 0, patch: 0, pre: nil, build: nil, source: nil
|
||||
defrecord Requirement, source: nil, matchspec: nil
|
||||
defrecord Schema, [:major, :minor, :patch, :pre, :build, :source]
|
||||
defrecord Requirement, [:source, :matchspec]
|
||||
|
||||
defexception InvalidRequirement, [:message] do
|
||||
def exception(opts) do
|
||||
message =
|
||||
if is_binary opts[:reason] do
|
||||
{ first, rest } = String.next_grapheme(opts[:reason])
|
||||
String.downcase(first) <> rest
|
||||
else
|
||||
"invalid version specification"
|
||||
end
|
||||
|
||||
InvalidRequirement[message: message]
|
||||
end
|
||||
end
|
||||
defexception InvalidRequirement, [:message]
|
||||
defexception InvalidVersion, [:message]
|
||||
|
||||
@doc """
|
||||
Check if the given version matches the specification.
|
||||
|
||||
Returns `true` if `version` satisfies `requirement`, `false` otherwise.
|
||||
Raises a `Version.InvalidRequirement` exception if `requirement` is not parseable.
|
||||
Raises a `Version.InvalidRequirement` exception if `requirement` is not
|
||||
parseable, or `Version.InvalidVersion` if `version` is not parseable.
|
||||
If given an already parsed version and requirement this function won't
|
||||
raise.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Version.match?("2.0", ">1.0")
|
||||
iex> Version.match?("2.0.0", ">1.0.0")
|
||||
true
|
||||
iex> Version.match?("2.0", "==1.0")
|
||||
|
||||
iex> Version.match?("2.0.0", "==1.0.0")
|
||||
false
|
||||
|
||||
iex> Version.match?("foo", "==1.0.0")
|
||||
** (Version.InvalidVersion) foo
|
||||
|
||||
iex> Version.match?("2.0.0", "== ==1.0.0")
|
||||
** (Version.InvalidRequirement) == ==1.0.0
|
||||
|
||||
"""
|
||||
@spec match?(t, requirement) :: boolean
|
||||
def match?(version, requirement) when is_binary(requirement) do
|
||||
case Version.Parser.parse_requirement(requirement) do
|
||||
def match?(vsn, req) when is_binary(req) do
|
||||
case parse_requirement(req) do
|
||||
{ :ok, req } ->
|
||||
match?(version, req)
|
||||
{ :error, reason } ->
|
||||
raise InvalidRequirement, reason: reason
|
||||
match?(vsn, req)
|
||||
:error ->
|
||||
raise InvalidRequirement, message: req
|
||||
end
|
||||
end
|
||||
|
||||
def match?(version, requirement) when is_binary(version) do
|
||||
match?(parse(version), requirement)
|
||||
end
|
||||
|
||||
def match?(Schema[] = version, Requirement[matchspec: spec]) do
|
||||
case :ets.test_ms(to_matchable(version), spec) do
|
||||
{ :ok, result } ->
|
||||
result != false
|
||||
{ :error, reason } ->
|
||||
raise InvalidRequirement, reason: reason
|
||||
end
|
||||
def match?(version, Requirement[matchspec: spec]) do
|
||||
{ :ok, result } = :ets.test_ms(to_matchable(version), spec)
|
||||
result != false
|
||||
end
|
||||
|
||||
@doc """
|
||||
Check if a version string is compatible with [semver](http://semver.org/).
|
||||
Compares two versions. Returns `:gt` if first version is greater than
|
||||
the second and `:lt` for vice versa. If the two versions are equal `:eq`
|
||||
is returned
|
||||
|
||||
Raises a `Version.InvalidVersion` exception if `version` is not parseable.
|
||||
If given an already parsed version this function won't raise.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Version.valid?("2.0")
|
||||
true
|
||||
iex> Version.valid?("invalid")
|
||||
false
|
||||
iex> Version.compare("2.0.1-alpha1", "2.0.0")
|
||||
:gt
|
||||
|
||||
iex> Version.compare("2.0.1+build0", "2.0.1")
|
||||
:eq
|
||||
|
||||
iex> Version.compare("invalid", "2.0.1")
|
||||
** (Version.InvalidVersion) invalid
|
||||
|
||||
"""
|
||||
@spec valid?(String.t | Schema.t) :: boolean
|
||||
def valid?(string) when is_binary(string) do
|
||||
Version.Parser.valid_version?(string)
|
||||
@spec compare(String.t | Schema.t, String.t | Schema.t) :: :gt | :eq | :lt
|
||||
def compare(vsn1, vsn2) do
|
||||
do_compare(to_matchable(vsn1), to_matchable(vsn2))
|
||||
end
|
||||
|
||||
def valid?(Version.Schema[major: nil]), do: false
|
||||
def valid?(Version.Schema[]), do: true
|
||||
defp do_compare({ major1, minor1, patch1, pre1 }, { major2, minor2, patch2, pre2 }) do
|
||||
cond do
|
||||
{ major1, minor1, patch1 } > { major2, minor2, patch2 } -> :gt
|
||||
{ major1, minor1, patch1 } < { major2, minor2, patch2 } -> :lt
|
||||
pre1 == [] and pre2 != [] -> :gt
|
||||
pre1 != [] and pre2 == [] -> :lt
|
||||
pre1 > pre2 -> :gt
|
||||
pre1 < pre2 -> :lt
|
||||
true -> :eq
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Parse a version string into a `Version.Schema`.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Version.parse("2.0.1-alpha1")
|
||||
iex> Version.parse("2.0.1-alpha1") |> elem(1)
|
||||
#Version.Schema<2.0.1-alpha1>
|
||||
|
||||
iex> Version.parse("2.0-alpha1")
|
||||
:error
|
||||
|
||||
"""
|
||||
@spec parse(String.t) :: Schema.t
|
||||
@spec parse(String.t) :: { :ok, Schema.t } | :error
|
||||
def parse(string) when is_binary(string) do
|
||||
case Version.Parser.parse_version(string) do
|
||||
{ :ok, matchable } -> from_matchable(matchable).source(string).build(get_build(string))
|
||||
{ :error, _ } -> Version.Schema[source: string]
|
||||
{ :ok, { major, minor, patch, pre } } ->
|
||||
vsn = Version.Schema[major: major, minor: minor, patch: patch,
|
||||
pre: pre, source: string, build: get_build(string)]
|
||||
{ :ok, vsn }
|
||||
:error ->
|
||||
:error
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Parse a version requirement string into a `Version.Schema`.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Version.parse_requirement("== 2.0.1") |> elem(1)
|
||||
#Version.Requirement<== 2.0.1>
|
||||
|
||||
iex> Version.parse_requirement("== == 2.0.1")
|
||||
:error
|
||||
|
||||
"""
|
||||
@spec parse_requirement(String.t) :: { :ok, Requirement.t } | :error
|
||||
def parse_requirement(string) when is_binary(string) do
|
||||
case Version.Parser.parse_requirement(string) do
|
||||
{ :ok, spec } ->
|
||||
{ :ok, Requirement[source: string, matchspec: spec] }
|
||||
:error ->
|
||||
:error
|
||||
end
|
||||
end
|
||||
|
||||
defp to_matchable(Schema[major: major, minor: minor, patch: patch, pre: pre]) do
|
||||
{ major, minor, patch, pre }
|
||||
end
|
||||
|
||||
defp to_matchable(string) do
|
||||
case Version.Parser.parse_version(string) do
|
||||
{ :ok, vsn } -> vsn
|
||||
:error -> raise InvalidVersion, message: string
|
||||
end
|
||||
end
|
||||
|
||||
defp get_build(string) do
|
||||
case Regex.run(%r/\+([^\s]+)$/, string) do
|
||||
case Regex.run(~r/\+([^\s]+)$/, string) do
|
||||
nil ->
|
||||
nil
|
||||
|
||||
@@ -162,69 +205,6 @@ defmodule Version do
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Convert a version to a `Version.matchable`
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Version.to_matchable("2.0.1-alpha.1")
|
||||
{2, 0, 1, ["alpha", 1]}
|
||||
|
||||
"""
|
||||
@spec to_matchable(String.t | Schema.t) :: Version.matchable
|
||||
def to_matchable(Schema[major: nil, source: source]) do
|
||||
{ source, nil, nil, [] }
|
||||
end
|
||||
|
||||
def to_matchable(Version.Schema[major: major, minor: minor, patch: patch, pre: nil]) do
|
||||
{ major, minor, patch, [] }
|
||||
end
|
||||
|
||||
def to_matchable(Version.Schema[major: major, minor: minor, patch: patch, pre: pre]) do
|
||||
{ major, minor, patch, Version.Parser.parse_pre(pre) }
|
||||
end
|
||||
|
||||
def to_matchable(string) do
|
||||
to_matchable(parse(string))
|
||||
end
|
||||
|
||||
@doc """
|
||||
Convert a matchable to a `Version.Schema`.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Version.from_matchable({2, 0, 1, ["alpha", 1]})
|
||||
#Version.Schema<2.0.1-alpha.1>
|
||||
|
||||
"""
|
||||
@spec from_matchable(Version.matchable) :: Schema.t
|
||||
def from_matchable({ source, nil, nil, nil }) when is_binary(source) do
|
||||
Version.Schema[source: source]
|
||||
end
|
||||
|
||||
def from_matchable({ major, minor, patch, pre }) do
|
||||
source = "#{major}"
|
||||
|
||||
if minor do
|
||||
source = "#{source}.#{minor}"
|
||||
|
||||
if patch do
|
||||
source = "#{source}.#{patch}"
|
||||
|
||||
case pre do
|
||||
[] ->
|
||||
pre = nil
|
||||
|
||||
list ->
|
||||
pre = Enum.join(list, ".")
|
||||
source = "#{source}-#{pre}"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
Version.Schema[major: major, minor: minor, patch: patch, pre: pre, source: source]
|
||||
end
|
||||
|
||||
defmodule Parser.DSL do
|
||||
@moduledoc false
|
||||
|
||||
@@ -292,78 +272,75 @@ defmodule Version do
|
||||
Enum.filter(Enum.reverse(acc), &(&1 != :' '))
|
||||
end
|
||||
|
||||
@version_regex %r/^(\d+)(?:\.(\d+)(?:\.(\d+))?)?(?:\-([^\s]+))?(?:\+[^\d]+)?$/
|
||||
@version_regex ~r/^
|
||||
(\d+) # major
|
||||
(?:\.(\d+))? # minor
|
||||
(?:\.(\d+))? # patch
|
||||
(?:\-([\d\w\.\-]+))? # pre
|
||||
(?:\+([\d\w\-]+))? # build
|
||||
$/x
|
||||
|
||||
@spec parse_requirement(String.t) :: { :ok, Version.Requirement.t } | { :error, binary | atom }
|
||||
@spec parse_requirement(String.t) :: {:ok, Version.Requirement.t} | :error
|
||||
def parse_requirement(source) do
|
||||
lexed = lexer(source, [])
|
||||
|
||||
if valid_requirement?(lexed) do
|
||||
spec = to_matchspec(lexed)
|
||||
|
||||
case :ets.test_ms({}, spec) do
|
||||
{ :ok, _ } ->
|
||||
{ :ok, Requirement[source: source, matchspec: spec] }
|
||||
|
||||
{ :error, errors } ->
|
||||
{ :error, Enum.map(errors, fn { :error, reason } ->
|
||||
to_string(reason)
|
||||
end) }
|
||||
end
|
||||
else
|
||||
{ :error, :invalid_requirement }
|
||||
end
|
||||
to_matchspec(lexed)
|
||||
end
|
||||
|
||||
defp nillify(""), do: nil
|
||||
defp nillify(o), do: o
|
||||
|
||||
@spec parse_version(String.t) :: { :ok, Version.matchable } | { :error, :invalid_version }
|
||||
def parse_version(string) when is_binary(string) do
|
||||
if valid_version?(string) do
|
||||
destructure [_, major, minor, patch, pre], Regex.run(@version_regex, string)
|
||||
@spec parse_version(String.t) :: {:ok, Version.matchable} | :error
|
||||
def parse_version(string, approximate? \\ false) when is_binary(string) do
|
||||
if parsed = Regex.run(@version_regex, string) do
|
||||
destructure [_, major, minor, patch, pre], parsed
|
||||
patch = nillify(patch)
|
||||
pre = nillify(pre)
|
||||
|
||||
major = binary_to_integer(major)
|
||||
minor = binary_to_integer(minor |> nillify || "0")
|
||||
patch = binary_to_integer(patch |> nillify || "0")
|
||||
pre = pre && parse_pre(pre) || []
|
||||
|
||||
{ :ok, { major, minor, patch, pre } }
|
||||
else
|
||||
{ :error, :invalid_version }
|
||||
end
|
||||
end
|
||||
|
||||
@doc false
|
||||
def parse_pre(pre) do
|
||||
String.split(pre, ".") |> Enum.map fn piece ->
|
||||
if piece =~ %r/^(0|[1-9][0-9]*)$/ do
|
||||
binary_to_integer(piece)
|
||||
if nil?(minor) or (nil?(patch) and not approximate?) do
|
||||
:error
|
||||
else
|
||||
piece
|
||||
major = binary_to_integer(major)
|
||||
minor = binary_to_integer(minor)
|
||||
patch = patch && binary_to_integer(patch)
|
||||
|
||||
case parse_pre(pre) do
|
||||
{ :ok, pre } ->
|
||||
{ :ok, { major, minor, patch, pre } }
|
||||
:error ->
|
||||
:error
|
||||
end
|
||||
end
|
||||
else
|
||||
:error
|
||||
end
|
||||
end
|
||||
|
||||
@spec valid_requirement?(list) :: boolean
|
||||
def valid_requirement?([]) do
|
||||
false
|
||||
defp parse_pre(nil), do: { :ok, [] }
|
||||
defp parse_pre(pre), do: parse_pre(String.split(pre, "."), [])
|
||||
|
||||
defp parse_pre([piece|t], acc) do
|
||||
cond do
|
||||
piece =~ ~r/^(0|[1-9][0-9]*)$/ ->
|
||||
parse_pre(t, [binary_to_integer(piece)|acc])
|
||||
piece =~ ~r/^[0-9]*$/ ->
|
||||
:error
|
||||
true ->
|
||||
parse_pre(t, [piece|acc])
|
||||
end
|
||||
end
|
||||
|
||||
def valid_requirement?([a | next]) do
|
||||
valid_requirement?(a, next)
|
||||
defp parse_pre([], acc) do
|
||||
{ :ok, Enum.reverse(acc) }
|
||||
end
|
||||
|
||||
defp valid_requirement?([]), do: false
|
||||
defp valid_requirement?([a | next]), do: valid_requirement?(a, next)
|
||||
|
||||
# it must finish with a version
|
||||
defp valid_requirement?(a, []) when is_binary(a) do
|
||||
true
|
||||
end
|
||||
|
||||
# version version
|
||||
defp valid_requirement?(a, [b | _]) when is_binary(a) and is_binary(b) do
|
||||
false
|
||||
end
|
||||
|
||||
# or <op> | and <op>
|
||||
defp valid_requirement?(a, [b | next]) when is_atom(a) and is_atom(b) and a in [:'||', :'&&'] do
|
||||
valid_requirement?(b, next)
|
||||
@@ -379,73 +356,57 @@ defmodule Version do
|
||||
valid_requirement?(b, next)
|
||||
end
|
||||
|
||||
# <op> <version>; also checks operators work on valid versions
|
||||
# <op> <version>
|
||||
defp valid_requirement?(a, [b | next]) when is_atom(a) and is_binary(b) do
|
||||
if valid_version?(b) do
|
||||
valid_requirement?(b, next)
|
||||
else
|
||||
if a in [:'==', :'!='] and Regex.match? %r/^\w/, b do
|
||||
valid_requirement?(b, next)
|
||||
else
|
||||
false
|
||||
end
|
||||
end
|
||||
valid_requirement?(b, next)
|
||||
end
|
||||
|
||||
defp valid_requirement?(_, _) do
|
||||
false
|
||||
end
|
||||
|
||||
@spec valid_version?(String.t) :: boolean
|
||||
def valid_version?(string) do
|
||||
Regex.match? %r/^\d+(\.\d+(\.\d+)?)?(\-[^\s]+)?(?:\+[^\s]+)?$/, string
|
||||
end
|
||||
defp approximate_upper(version) do
|
||||
case version do
|
||||
{ major, _minor, nil, _ } ->
|
||||
{ major + 1, 0, 0, [0] }
|
||||
|
||||
defp approximate(version) do
|
||||
Version.from_matchable(case Regex.run(@version_regex, version) do
|
||||
[_, major] ->
|
||||
{ binary_to_integer(major) + 1, 0, 0, [0] }
|
||||
|
||||
[_, major, _] ->
|
||||
{ binary_to_integer(major) + 1, 0, 0, [0] }
|
||||
|
||||
[_, major, minor, _] ->
|
||||
{ binary_to_integer(major), binary_to_integer(minor) + 1, 0, [0] }
|
||||
|
||||
[_, major, minor, _, _] ->
|
||||
{ binary_to_integer(major), binary_to_integer(minor) + 1, 0, [0] }
|
||||
end)
|
||||
{ major, minor, _patch, _ } ->
|
||||
{ major, minor + 1, 0, [0] }
|
||||
end
|
||||
end
|
||||
|
||||
defp to_matchspec(lexed) do
|
||||
first = to_condition(lexed)
|
||||
rest = Enum.drop(lexed, 2)
|
||||
|
||||
[{{ :'$1', :'$2', :'$3', :'$4' }, [to_condition(first, rest)], [:'$_'] }]
|
||||
if valid_requirement?(lexed) do
|
||||
first = to_condition(lexed)
|
||||
rest = Enum.drop(lexed, 2)
|
||||
{ :ok, [{{ :'$1', :'$2', :'$3', :'$4' }, [to_condition(first, rest)], [:'$_'] }] }
|
||||
else
|
||||
:error
|
||||
end
|
||||
catch
|
||||
:invalid_matchspec -> :error
|
||||
end
|
||||
|
||||
defp to_condition([:'==', version | _]) do
|
||||
version = Version.to_matchable(version)
|
||||
|
||||
version = parse_condition(version)
|
||||
{ :'==', :'$_', { :const, version } }
|
||||
end
|
||||
|
||||
defp to_condition([:'!=', version | _]) do
|
||||
version = Version.to_matchable(version)
|
||||
|
||||
version = parse_condition(version)
|
||||
{ :'/=', :'$_', { :const, version } }
|
||||
end
|
||||
|
||||
defp to_condition([:'~>', version | _]) do
|
||||
from = Version.parse(version)
|
||||
to = approximate(version)
|
||||
from = parse_condition(version, true)
|
||||
to = approximate_upper(from)
|
||||
|
||||
{ :andalso, to_condition([:'>=', to_string(from)]),
|
||||
to_condition([:'<', to_string(to)]) }
|
||||
{ :andalso, to_condition([:'>=', matchable_to_string(from)]),
|
||||
to_condition([:'<', matchable_to_string(to)]) }
|
||||
end
|
||||
|
||||
defp to_condition([:'>', version | _]) do
|
||||
{ major, minor, patch, pre } = Version.to_matchable(version)
|
||||
{ major, minor, patch, pre } = parse_condition(version)
|
||||
|
||||
{ :andalso, { :not, { :is_binary, :'$1' } },
|
||||
{ :orelse, { :'>', {{ :'$1', :'$2', :'$3' }},
|
||||
@@ -461,7 +422,7 @@ defmodule Version do
|
||||
end
|
||||
|
||||
defp to_condition([:'>=', version | _]) do
|
||||
matchable = Version.to_matchable(version)
|
||||
matchable = parse_condition(version)
|
||||
|
||||
{ :orelse, { :andalso, { :not, { :is_binary, :'$1' } },
|
||||
{ :'==', :'$_', { :const, matchable } } },
|
||||
@@ -469,7 +430,7 @@ defmodule Version do
|
||||
end
|
||||
|
||||
defp to_condition([:'<', version | _]) do
|
||||
{ major, minor, patch, pre } = Version.to_matchable(version)
|
||||
{ major, minor, patch, pre } = parse_condition(version)
|
||||
|
||||
{ :andalso, { :not, { :is_binary, :'$1' } },
|
||||
{ :orelse, { :'<', {{ :'$1', :'$2', :'$3' }},
|
||||
@@ -485,7 +446,7 @@ defmodule Version do
|
||||
end
|
||||
|
||||
defp to_condition([:'<=', version | _]) do
|
||||
matchable = Version.to_matchable(version)
|
||||
matchable = parse_condition(version)
|
||||
|
||||
{ :orelse, { :andalso, { :not, { :is_binary, :'$1' } },
|
||||
{ :'==', :'$_', { :const, matchable } } },
|
||||
@@ -503,6 +464,19 @@ defmodule Version do
|
||||
defp to_condition(current, [:'||', operator, version | rest]) do
|
||||
to_condition({ :orelse, current, to_condition([operator, version]) }, rest)
|
||||
end
|
||||
|
||||
defp parse_condition(version, approximate? \\ false) do
|
||||
case parse_version(version, approximate?) do
|
||||
{ :ok, version } -> version
|
||||
:error -> throw :invalid_matchspec
|
||||
end
|
||||
end
|
||||
|
||||
defp matchable_to_string({ major, minor, patch, pre }) do
|
||||
patch = if patch, do: "#{patch}", else: "0"
|
||||
pre = if pre != [], do: "-#{Enum.join(pre, ".")}"
|
||||
"#{major}.#{minor}.#{patch}#{pre}"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -519,13 +493,13 @@ defimpl Inspect, for: Version.Schema do
|
||||
end
|
||||
|
||||
defimpl String.Chars, for: Version.Requirement do
|
||||
def to_string({ _, source, _ }) do
|
||||
def to_string(Version.Requirement[source: source]) do
|
||||
source
|
||||
end
|
||||
end
|
||||
|
||||
defimpl Inspect, for: Version.Requirement do
|
||||
def inspect({ _, source, _ }, _opts) do
|
||||
def inspect(Version.Requirement[source: source], _opts) do
|
||||
"#Version.Requirement<" <> source <> ">"
|
||||
end
|
||||
end
|
||||
|
||||
@@ -4,6 +4,7 @@ defmodule Elixir.Mixfile do
|
||||
def project do
|
||||
[ app: :elixir,
|
||||
version: System.version,
|
||||
build_per_environment: false,
|
||||
escript_embed_elixir: false,
|
||||
escript_main_module: :elixir,
|
||||
escript_emu_args: "%%! -noshell\n" ]
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
{erl_first_files, ["elixir_transform"]}.
|
||||
|
||||
{erl_opts, [
|
||||
warn_unused_vars,
|
||||
warn_export_all,
|
||||
@@ -24,4 +22,4 @@
|
||||
{verbose, false}
|
||||
]}.
|
||||
|
||||
{require_otp_vsn,"(R16B|R17A).*"}.
|
||||
{require_otp_vsn,"(17).*"}.
|
||||
|
||||
@@ -21,8 +21,8 @@ start(_Type, _Args) ->
|
||||
%% Set the shell to unicode so printing inside scripts work
|
||||
%% Those can take a while, so let's do it in a new process
|
||||
spawn(fun() ->
|
||||
io:setopts(standard_io, [binary,{encoding,unicode}]),
|
||||
io:setopts(standard_error, [binary,{encoding,unicode}])
|
||||
io:setopts(standard_io, [binary,{encoding,utf8}]),
|
||||
io:setopts(standard_error, [{unicode,true}])
|
||||
end),
|
||||
elixir_sup:start_link().
|
||||
|
||||
@@ -158,11 +158,11 @@ string_to_quoted(String, StartLine, File, Opts) when is_integer(StartLine), is_b
|
||||
{ ok, _Line, Tokens } ->
|
||||
try elixir_parser:parse(Tokens) of
|
||||
{ ok, Forms } -> { ok, Forms };
|
||||
{ error, { Line, _, [Error, Token] } } -> { error, { Line, Error, Token } }
|
||||
{ error, { Line, _, [Error, Token] } } -> { error, { Line, to_binary(Error), to_binary(Token) } }
|
||||
catch
|
||||
{ error, { Line, _, [Error, Token] } } -> { error, { Line, Error, Token } }
|
||||
{ error, { Line, _, [Error, Token] } } -> { error, { Line, to_binary(Error), to_binary(Token) } }
|
||||
end;
|
||||
{ error, Reason, _Rest, _SoFar } -> { error, Reason }
|
||||
{ error, { Line, Error, Token }, _Rest, _SoFar } -> { error, { Line, to_binary(Error), to_binary(Token) } }
|
||||
end.
|
||||
|
||||
'string_to_quoted!'(String, StartLine, File, Opts) ->
|
||||
@@ -172,3 +172,6 @@ string_to_quoted(String, StartLine, File, Opts) when is_integer(StartLine), is_b
|
||||
{ error, { Line, Error, Token } } ->
|
||||
elixir_errors:parse_error(Line, File, Error, Token)
|
||||
end.
|
||||
|
||||
to_binary(List) when is_list(List) -> elixir_utils:characters_to_binary(List);
|
||||
to_binary(Atom) when is_atom(Atom) -> atom_to_binary(Atom, utf8).
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
%% Handle translation of Elixir literals to Erlang AST.
|
||||
-module(elixir_bitstring).
|
||||
-export([translate/3, expand/3]).
|
||||
-export([translate/3, expand/3, has_size/1]).
|
||||
-include("elixir.hrl").
|
||||
|
||||
%% Expansion
|
||||
@@ -102,6 +101,13 @@ handle_unknown_bit_info(Meta, { _, ExprMeta, _ } = Expr, T, Size, Types, E) ->
|
||||
|
||||
%% Translation
|
||||
|
||||
has_size({ bin, _, Elements }) ->
|
||||
not lists:any(fun({ bin_element, _Line, _Expr, Size, Types }) ->
|
||||
(Types /= default) andalso (Size == default) andalso
|
||||
lists:any(fun(X) -> lists:member(X, Types) end,
|
||||
[bits, bytes, bitstring, binary])
|
||||
end, Elements).
|
||||
|
||||
translate(Meta, Args, S) ->
|
||||
case S#elixir_scope.context of
|
||||
match ->
|
||||
@@ -168,15 +174,30 @@ types_require_conversion([UTF|T]) when UTF == utf8; UTF == utf16; UTF == utf32 -
|
||||
types_require_conversion([]) -> true;
|
||||
types_require_conversion(_) -> false.
|
||||
|
||||
types_allow_splice([bytes], Elements) -> lists:all(fun has_default_size/1, Elements);
|
||||
types_allow_splice([binary], Elements) -> lists:all(fun has_default_size/1, Elements);
|
||||
types_allow_splice([bytes], Elements) -> is_byte_size(Elements, 0);
|
||||
types_allow_splice([binary], Elements) -> is_byte_size(Elements, 0);
|
||||
types_allow_splice([bits], _) -> true;
|
||||
types_allow_splice([bitstring], _) -> true;
|
||||
types_allow_splice(default, _) -> true;
|
||||
types_allow_splice(_, _) -> false.
|
||||
|
||||
has_default_size({ bin_element, _, _, default, _ }) -> true;
|
||||
has_default_size({ bin_element, _, _, _, _ }) -> false.
|
||||
is_byte_size([Element|T], Acc) ->
|
||||
case elem_size(Element) of
|
||||
{unknown, Unit} when Unit rem 8 == 0 -> is_byte_size(T, Acc);
|
||||
{unknown, _Unit} -> false;
|
||||
{Size, Unit} -> is_byte_size(T, Size*Unit + Acc)
|
||||
end;
|
||||
is_byte_size([], Size) ->
|
||||
Size rem 8 == 0.
|
||||
|
||||
elem_size({bin_element, _, _, default, _}) -> {0, 0};
|
||||
elem_size({bin_element, _, _, {integer,_,Size}, Types}) -> {Size, unit_size(Types, 1)};
|
||||
elem_size({bin_element, _, _, _Size, Types}) -> {unknown, unit_size(Types, 1)}.
|
||||
|
||||
unit_size([binary|T], _) -> unit_size(T, 8);
|
||||
unit_size([{unit, Size}|_], _) -> Size;
|
||||
unit_size([_|T], Guess) -> unit_size(T, Guess);
|
||||
unit_size([], Guess) -> Guess.
|
||||
|
||||
%% Extra bitstring specifiers
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
%% Handle code related to args, guard and -> matching for case,
|
||||
%% fn, receive and friends. try is handled in elixir_try.
|
||||
-module(elixir_clauses).
|
||||
-export([match/3, clause/6, clauses/3, get_pairs/2, get_pairs/3,
|
||||
-export([match/3, clause/7, clauses/4, get_pairs/2, get_pairs/3,
|
||||
extract_splat_guards/1, extract_guards/1]).
|
||||
-include("elixir.hrl").
|
||||
|
||||
@@ -31,9 +31,9 @@ match(Fun, Args, S) -> Fun(Args, S).
|
||||
|
||||
%% Translate clauses with args, guards and expressions
|
||||
|
||||
clause(Line, Fun, Args, Expr, Guards, S) when is_integer(Line) ->
|
||||
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(Expr, SA#elixir_scope{extra_guards=nil}),
|
||||
{ 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,
|
||||
@@ -66,22 +66,18 @@ extract_splat_guards(Else) ->
|
||||
|
||||
% Function for translating macros with match style like case and receive.
|
||||
|
||||
clauses(Meta, Clauses, #elixir_scope{export_vars=CV} = S) ->
|
||||
{ TC, TS } = do_clauses(Meta, Clauses, S#elixir_scope{export_vars=[]}),
|
||||
clauses(Meta, Clauses, Return, #elixir_scope{export_vars=CV} = S) ->
|
||||
{ TC, TS } = do_clauses(Meta, Clauses, Return, S#elixir_scope{export_vars=[]}),
|
||||
{ TC, TS#elixir_scope{export_vars=elixir_scope:merge_opt_vars(CV, TS#elixir_scope.export_vars)} }.
|
||||
|
||||
do_clauses(_Meta, [], S) ->
|
||||
do_clauses(_Meta, [], _Return, S) ->
|
||||
{ [], S };
|
||||
|
||||
% do_clauses(_Meta, [DecoupledClause], S) ->
|
||||
% { TDecoupledClause, TS } = each_clause(DecoupledClause, S),
|
||||
% { [TDecoupledClause], TS };
|
||||
|
||||
do_clauses(Meta, DecoupledClauses, S) ->
|
||||
do_clauses(Meta, DecoupledClauses, Return, S) ->
|
||||
% Transform tree just passing the variables counter forward
|
||||
% and storing variables defined inside each clause.
|
||||
Transformer = fun(X, {SAcc, VAcc}) ->
|
||||
{ TX, TS } = each_clause(X, SAcc),
|
||||
{ TX, TS } = each_clause(X, Return, SAcc),
|
||||
{ TX,
|
||||
{ elixir_scope:mergec(S, TS), [TS#elixir_scope.export_vars|VAcc] } }
|
||||
end,
|
||||
@@ -142,17 +138,17 @@ expand_clauses(_Line, [], [], _FinalVars, Acc, S) ->
|
||||
|
||||
% Handle each key/value clause pair and translate them accordingly.
|
||||
|
||||
each_clause({ do, Meta, [Condition], Expr }, S) ->
|
||||
each_clause({ do, Meta, [Condition], Expr }, Return, S) ->
|
||||
{ Arg, Guards } = extract_guards(Condition),
|
||||
clause(?line(Meta), fun elixir_translator:translate_many/2, [Arg], Expr, Guards, S);
|
||||
clause(?line(Meta), fun elixir_translator:translate_args/2, [Arg], Expr, Guards, Return, S);
|
||||
|
||||
each_clause({ else, Meta, [Condition], Expr }, S) ->
|
||||
each_clause({ else, Meta, [Condition], Expr }, Return, S) ->
|
||||
{ Arg, Guards } = extract_guards(Condition),
|
||||
clause(?line(Meta), fun elixir_translator:translate_many/2, [Arg], Expr, Guards, S);
|
||||
clause(?line(Meta), fun elixir_translator:translate_args/2, [Arg], Expr, Guards, Return, S);
|
||||
|
||||
each_clause({ 'after', Meta, [Condition], Expr }, S) ->
|
||||
each_clause({ 'after', Meta, [Condition], Expr }, Return, S) ->
|
||||
{ TCondition, SC } = elixir_translator:translate(Condition, S),
|
||||
{ TExpr, SB } = elixir_translator:translate(Expr, SC),
|
||||
{ TExpr, SB } = elixir_translator:translate_block(Expr, Return, SC),
|
||||
{ { clause, ?line(Meta), [TCondition], [], unblock(TExpr) }, SB }.
|
||||
|
||||
% Check if the given expression is a match tuple.
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
-export([call/1, cast/1]).
|
||||
-export([start_link/0, init/1, handle_call/3, handle_cast/2,
|
||||
handle_info/2, terminate/2, code_change/3]).
|
||||
-behavior(gen_server).
|
||||
-behaviour(gen_server).
|
||||
|
||||
-define(timeout, 30000).
|
||||
-record(elixir_code_server, {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
-module(elixir_compiler).
|
||||
-export([get_opt/1, string/2, quoted/2, file/1, file_to_path/2]).
|
||||
-export([core/0, module/4, eval_forms/3, format_error/1]).
|
||||
-export([core/0, module/4, eval_forms/3]).
|
||||
-include("elixir.hrl").
|
||||
|
||||
%% Public API
|
||||
@@ -137,15 +137,11 @@ module(Forms, File, Opts, Callback) ->
|
||||
|
||||
module(Forms, File, RawOptions, Bootstrap, Callback) when
|
||||
is_binary(File), is_list(Forms), is_list(RawOptions), is_boolean(Bootstrap), is_function(Callback) ->
|
||||
{ Options, SkipNative } = compiler_options(Forms, RawOptions),
|
||||
Options = RawOptions ++ elixir_code_server:call(erl_compiler_options),
|
||||
Listname = elixir_utils:characters_to_list(File),
|
||||
|
||||
case compile:noenv_forms([no_auto_import()|Forms], [return,{source,Listname}|Options]) of
|
||||
{ok, ModuleName, Binary, RawWarnings} ->
|
||||
Warnings = case SkipNative of
|
||||
true -> [{?MODULE,[{0,?MODULE,{skip_native,ModuleName}}]}|RawWarnings];
|
||||
false -> RawWarnings
|
||||
end,
|
||||
case compile:noenv_forms(Forms, [no_auto_import,return,{source,Listname}|Options]) of
|
||||
{ok, ModuleName, Binary, Warnings} ->
|
||||
format_warnings(Bootstrap, Warnings),
|
||||
code:load_binary(ModuleName, Listname, Binary),
|
||||
Callback(ModuleName, Binary);
|
||||
@@ -154,23 +150,6 @@ module(Forms, File, RawOptions, Bootstrap, Callback) when
|
||||
format_errors(Errors)
|
||||
end.
|
||||
|
||||
compiler_options(Forms, Options) ->
|
||||
EnvOptions = elixir_code_server:call(erl_compiler_options),
|
||||
SkipNative = lists:member(native, EnvOptions) and contains_on_load(Forms),
|
||||
case SkipNative or lists:member([{native,false}], Options) of
|
||||
true -> { Options ++ lists:delete(native, EnvOptions), SkipNative };
|
||||
false -> { Options ++ EnvOptions, SkipNative }
|
||||
end.
|
||||
|
||||
contains_on_load([{ attribute, _, on_load, _ }|_]) -> true;
|
||||
contains_on_load([_|T]) -> contains_on_load(T);
|
||||
contains_on_load([]) -> false.
|
||||
|
||||
no_auto_import() ->
|
||||
Bifs = [{ Name, Arity } || { Name, Arity } <- erlang:module_info(exports), erl_internal:bif(Name, Arity)],
|
||||
{ attribute, 0, compile, { no_auto_import, Bifs } }.
|
||||
|
||||
|
||||
%% CORE HANDLING
|
||||
|
||||
core() ->
|
||||
@@ -186,7 +165,7 @@ core_file(File) ->
|
||||
catch
|
||||
Kind:Reason ->
|
||||
io:format("~p: ~p~nstacktrace: ~p~n", [Kind, Reason, erlang:get_stacktrace()]),
|
||||
exit(1)
|
||||
erlang:halt(1)
|
||||
end.
|
||||
|
||||
core_main() ->
|
||||
@@ -196,6 +175,8 @@ core_main() ->
|
||||
<<"lib/elixir/lib/module.ex">>,
|
||||
<<"lib/elixir/lib/list.ex">>,
|
||||
<<"lib/elixir/lib/record.ex">>,
|
||||
<<"lib/elixir/lib/record/backend.ex">>,
|
||||
<<"lib/elixir/lib/record/deprecated.ex">>,
|
||||
<<"lib/elixir/lib/macro.ex">>,
|
||||
<<"lib/elixir/lib/macro/env.ex">>,
|
||||
<<"lib/elixir/lib/exception.ex">>,
|
||||
@@ -210,6 +191,7 @@ core_main() ->
|
||||
<<"lib/elixir/lib/regex.ex">>,
|
||||
<<"lib/elixir/lib/string.ex">>,
|
||||
<<"lib/elixir/lib/string/chars.ex">>,
|
||||
<<"lib/elixir/lib/collectable.ex">>,
|
||||
<<"lib/elixir/lib/io.ex">>,
|
||||
<<"lib/elixir/lib/path.ex">>,
|
||||
<<"lib/elixir/lib/system.ex">>,
|
||||
@@ -217,7 +199,6 @@ core_main() ->
|
||||
<<"lib/elixir/lib/kernel/cli.ex">>,
|
||||
<<"lib/elixir/lib/kernel/error_handler.ex">>,
|
||||
<<"lib/elixir/lib/kernel/parallel_compiler.ex">>,
|
||||
<<"lib/elixir/lib/kernel/record_rewriter.ex">>,
|
||||
<<"lib/elixir/lib/kernel/lexical_tracker.ex">>
|
||||
].
|
||||
|
||||
@@ -228,10 +209,6 @@ binary_to_path({ModuleName, Binary}, CompilePath) ->
|
||||
|
||||
%% ERROR HANDLING
|
||||
|
||||
format_error({ skip_native, Module }) ->
|
||||
io_lib:format("skipping native compilation for ~ts because it contains on_load attribute",
|
||||
[elixir_aliases:inspect(Module)]).
|
||||
|
||||
format_errors([]) ->
|
||||
exit({ nocompile, "compilation failed but no error was raised" });
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
-module(elixir_counter).
|
||||
-export([start_link/0, init/1, handle_call/3, handle_cast/2,
|
||||
handle_info/2, terminate/2, code_change/3, next/0]).
|
||||
-behavior(gen_server).
|
||||
-behaviour(gen_server).
|
||||
|
||||
-define(timeout, 1000). %% 1 second
|
||||
-define(limit, 4294967296). %% 2^32
|
||||
|
||||
@@ -86,8 +86,7 @@ store_definition(Line, Kind, CheckClauses, Name, Args, Guards, Body, MetaFile, #
|
||||
elixir_locals:record_definition(Tuple, Kind, Module),
|
||||
|
||||
Location = retrieve_location(Line, MetaFile, Module),
|
||||
run_on_definition_callbacks(Kind, Line, Module, Name, Args, Guards, Body, E),
|
||||
{ Function, Defaults, Super } = translate_definition(Kind, Line, Name, Args, Guards, Body, E),
|
||||
{ Function, Defaults, Super } = translate_definition(Kind, Line, Module, Name, Args, Guards, Body, E),
|
||||
|
||||
DefaultsLength = length(Defaults),
|
||||
elixir_locals:record_defaults(Tuple, Kind, Module, DefaultsLength),
|
||||
@@ -104,6 +103,7 @@ store_definition(Line, Kind, CheckClauses, Name, Args, Guards, Body, MetaFile, #
|
||||
[store_each(false, Kind, File, Location, Table, CTable, 0,
|
||||
default_function_for(Kind, Name, Default)) || Default <- Defaults],
|
||||
|
||||
make_struct_available(Kind, Module, Name, Args),
|
||||
{ Name, Arity }.
|
||||
|
||||
%% @on_definition
|
||||
@@ -118,6 +118,14 @@ run_on_definition_callbacks(Kind, Line, Module, Name, Args, Guards, Expr, E) ->
|
||||
[Mod:Fun(Env, Kind, Name, Args, Guards, Expr) || { Mod, Fun } <- Callbacks]
|
||||
end.
|
||||
|
||||
make_struct_available(def, Module, '__struct__', []) ->
|
||||
case erlang:get(elixir_compiler_pid) of
|
||||
undefined -> ok;
|
||||
Pid -> Pid ! { struct_available, Module }
|
||||
end;
|
||||
make_struct_available(_, _, _, _) ->
|
||||
ok.
|
||||
|
||||
%% Retrieve location from meta file or @file, otherwise nil
|
||||
|
||||
retrieve_location(Line, File, Module) ->
|
||||
@@ -144,24 +152,25 @@ compile_super(_Module, _, _E) -> ok.
|
||||
%% Translate the given call and expression given
|
||||
%% and then store it in memory.
|
||||
|
||||
translate_definition(Kind, Line, Name, Args, Guards, Body, E) when is_integer(Line) ->
|
||||
translate_definition(Kind, Line, Module, Name, Args, Guards, Body, E) when is_integer(Line) ->
|
||||
Arity = length(Args),
|
||||
|
||||
{ EArgs, EGuards, EBody, _ } = elixir_exp_clauses:def(fun elixir_def_defaults:expand/2,
|
||||
Args, Guards, expr_from_body(Line, Body), E),
|
||||
|
||||
%% Macros receive a special argument on invocation. Notice it does
|
||||
%% not affect the arity of the stored function, but the clause
|
||||
%% already contains it.
|
||||
AllArgs = case is_macro(Kind) of
|
||||
true -> [{ '_@CALLER', [{line,Line}], nil }|Args];
|
||||
false -> Args
|
||||
EAllArgs = case is_macro(Kind) of
|
||||
true -> [{ '_@CALLER', [{line,Line}], nil }|EArgs];
|
||||
false -> EArgs
|
||||
end,
|
||||
|
||||
{ EArgs, EGuards, EBody, _ } = elixir_exp_clauses:def(fun elixir_def_defaults:expand/2,
|
||||
AllArgs, Guards, expr_from_body(Line, Body), E),
|
||||
|
||||
S = elixir_env:env_to_scope(E),
|
||||
{ Unpacked, Defaults } = elixir_def_defaults:unpack(Kind, Name, EArgs, S),
|
||||
{ Unpacked, Defaults } = elixir_def_defaults:unpack(Kind, Name, EAllArgs, S),
|
||||
{ Clauses, Super } = translate_clause(Body, Line, Kind, Unpacked, EGuards, EBody, S),
|
||||
|
||||
run_on_definition_callbacks(Kind, Line, Module, Name, EArgs, EGuards, EBody, E),
|
||||
Function = { function, Line, Name, Arity, Clauses },
|
||||
{ Function, Defaults, Super }.
|
||||
|
||||
@@ -171,7 +180,7 @@ translate_clause(nil, Line, Kind, _Args, _Guards, _Body, #elixir_scope{file=File
|
||||
elixir_errors:form_error(Line, File, ?MODULE, { missing_do, Kind });
|
||||
translate_clause(_, Line, Kind, Args, Guards, Body, S) ->
|
||||
{ TClause, TS } = elixir_clauses:clause(Line,
|
||||
fun elixir_translator:translate_many/2, Args, Body, Guards, S),
|
||||
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
|
||||
|
||||
@@ -5,10 +5,10 @@
|
||||
|
||||
expand(Args, E) ->
|
||||
lists:mapfoldl(fun
|
||||
({ '//', Meta, [Left, Right] }, Acc) ->
|
||||
({ '\\\\', Meta, [Left, Right] }, Acc) ->
|
||||
{ ELeft, EL } = elixir_exp:expand(Left, Acc),
|
||||
{ ERight, _ } = elixir_exp:expand(Right, Acc#elixir_env{context=nil}),
|
||||
{ { '//', Meta, [ELeft, ERight] }, EL };
|
||||
{ { '\\\\', Meta, [ELeft, ERight] }, EL };
|
||||
(Left, Acc) ->
|
||||
elixir_exp:expand(Left, Acc)
|
||||
end, E, Args).
|
||||
@@ -21,11 +21,11 @@ unpack(Kind, Name, Args, S) ->
|
||||
%% Unpack default from given args.
|
||||
%% Returns the given arguments without their default
|
||||
%% clauses and a list of clauses for the default calls.
|
||||
unpack_each(Kind, Name, [{'//', Line, [Expr, _]}|T] = List, Acc, Clauses, S) ->
|
||||
unpack_each(Kind, Name, [{'\\\\', Line, [Expr, _]}|T] = List, Acc, Clauses, S) ->
|
||||
Base = build_match(Acc, Line, []),
|
||||
{ Args, Invoke } = extract_defaults(List, Line, length(Base), [], []),
|
||||
|
||||
{ DefArgs, SA } = elixir_clauses:match(fun elixir_translator:translate_many/2, Base ++ Args, S),
|
||||
{ DefArgs, SA } = elixir_clauses:match(fun elixir_translator:translate_args/2, Base ++ Args, S),
|
||||
{ DefInvoke, _ } = elixir_translator:translate_args(Base ++ Invoke, SA),
|
||||
|
||||
Call = { call, Line,
|
||||
@@ -44,7 +44,7 @@ unpack_each(_Kind, _Name, [], Acc, Clauses, _S) ->
|
||||
|
||||
% Extract default values from args following the current default clause.
|
||||
|
||||
extract_defaults([{'//', _, [_Expr, Default]}|T], Line, Counter, NewArgs, NewInvoke) ->
|
||||
extract_defaults([{'\\\\', _, [_Expr, Default]}|T], Line, Counter, NewArgs, NewInvoke) ->
|
||||
extract_defaults(T, Line, Counter, NewArgs, [Default|NewInvoke]);
|
||||
|
||||
extract_defaults([_|T], Line, Counter, NewArgs, NewInvoke) ->
|
||||
|
||||
+170
-104
@@ -9,14 +9,19 @@
|
||||
find_import/4, format_error/1]).
|
||||
-include("elixir.hrl").
|
||||
-import(ordsets, [is_element/2]).
|
||||
|
||||
%% Module macros
|
||||
-define(kernel, 'Elixir.Kernel').
|
||||
-define(node, 'Elixir.Node').
|
||||
-define(process, 'Elixir.Process').
|
||||
-define(system, 'Elixir.System').
|
||||
|
||||
default_functions() ->
|
||||
[ { ?kernel, elixir_imported_functions() } ].
|
||||
default_macros() ->
|
||||
[ { ?kernel, elixir_imported_macros() } ].
|
||||
default_requires() ->
|
||||
[ 'Elixir.Integer', 'Elixir.Kernel', 'Elixir.Kernel.Typespec', 'Elixir.Record' ].
|
||||
[ 'Elixir.Kernel', 'Elixir.Kernel.Typespec' ].
|
||||
|
||||
find_import(Meta, Name, Arity, E) ->
|
||||
Tuple = { Name, Arity },
|
||||
@@ -64,13 +69,10 @@ require_function(_Meta, Receiver, Name, Arity, E) ->
|
||||
|
||||
remote_function(Receiver, Name, Arity, E) ->
|
||||
elixir_lexical:record_remote(Receiver, E#elixir_env.lexical_tracker),
|
||||
Tuple = { Name, Arity },
|
||||
Final =
|
||||
case (Receiver == ?kernel) andalso is_element(Tuple, in_erlang_functions()) of
|
||||
true -> erlang;
|
||||
false -> Receiver
|
||||
end,
|
||||
{ remote, Final, Name, Arity }.
|
||||
case inline(Receiver, Name, Arity) of
|
||||
{ AR, AN } -> { remote, AR, AN, Arity };
|
||||
false -> { remote, Receiver, Name, Arity }
|
||||
end.
|
||||
|
||||
%% Dispatches
|
||||
|
||||
@@ -87,19 +89,19 @@ dispatch_import(Meta, Name, Args, E, Callback) ->
|
||||
|
||||
dispatch_require(Meta, Receiver, Name, Args, E, Callback) when is_atom(Receiver) ->
|
||||
Arity = length(Args),
|
||||
Tuple = { Name, Arity },
|
||||
|
||||
case (Receiver == ?kernel) andalso is_element(Tuple, in_erlang_functions()) of
|
||||
true -> Callback(erlang);
|
||||
case rewrite(Receiver, Name, Args, Arity) of
|
||||
{ ok, AR, AN, AA } ->
|
||||
Callback(AR, AN, AA);
|
||||
false ->
|
||||
case expand_require(Meta, Receiver, Tuple, Args, E) of
|
||||
case expand_require(Meta, Receiver, { Name, Arity }, Args, E) of
|
||||
{ ok, Receiver, Quoted } -> expand_quoted(Meta, Receiver, Name, Arity, Quoted, E);
|
||||
error -> Callback(Receiver)
|
||||
error -> Callback(Receiver, Name, Args)
|
||||
end
|
||||
end;
|
||||
|
||||
dispatch_require(_Meta, Receiver, _Name, _Args, _E, Callback) ->
|
||||
Callback(Receiver).
|
||||
dispatch_require(_Meta, Receiver, Name, Args, _E, Callback) ->
|
||||
Callback(Receiver, Name, Args).
|
||||
|
||||
%% Macros expansion
|
||||
|
||||
@@ -111,13 +113,13 @@ expand_import(Meta, { Name, Arity } = Tuple, Args, E, Extra) ->
|
||||
elixir_locals:macro_for(Module, Name, Arity),
|
||||
|
||||
case Dispatch of
|
||||
%% In case it is an import, or the receive is the same as the
|
||||
%% current module (happens in bootstraping), we dispatch the import.
|
||||
{ Kind, Receiver } when Kind == import; Receiver == Module ->
|
||||
%% In case it is an import, we dispatch the import.
|
||||
{ import, _ } ->
|
||||
do_expand_import(Meta, Tuple, Args, Module, E, Dispatch);
|
||||
|
||||
%% There is a local and an import. This is a conflict.
|
||||
{ _, Receiver } when Local /= false ->
|
||||
%% There is a local and an import. This is a conflict unless
|
||||
%% the receiver is the same as module (happens on bootstrap).
|
||||
{ _, Receiver } when Local /= false, Receiver /= Module ->
|
||||
Error = { macro_conflict, { Receiver, Name, Arity } },
|
||||
elixir_errors:form_error(Meta, E#elixir_env.file, ?MODULE, Error);
|
||||
|
||||
@@ -136,11 +138,12 @@ do_expand_import(Meta, { Name, Arity } = Tuple, Args, Module, E, Result) ->
|
||||
{ function, Receiver } ->
|
||||
elixir_lexical:record_import(Receiver, E#elixir_env.lexical_tracker),
|
||||
elixir_locals:record_import(Tuple, Receiver, Module, E#elixir_env.function),
|
||||
Endpoint = case (Receiver == ?kernel) andalso is_element(Tuple, in_erlang_functions()) of
|
||||
true -> erlang;
|
||||
false -> Receiver
|
||||
end,
|
||||
{ ok, Endpoint };
|
||||
case rewrite(Receiver, Name, Args, Arity) of
|
||||
{ ok, AR, AN, AA } ->
|
||||
{ ok, AR, { { '.', [], [AR, AN] }, [], AA } };
|
||||
false ->
|
||||
{ ok, Receiver }
|
||||
end;
|
||||
{ macro, Receiver } ->
|
||||
elixir_lexical:record_import(Receiver, E#elixir_env.lexical_tracker),
|
||||
elixir_locals:record_import(Tuple, Receiver, Module, E#elixir_env.function),
|
||||
@@ -150,6 +153,13 @@ do_expand_import(Meta, { Name, Arity } = Tuple, Args, Module, E, Result) ->
|
||||
{ ok, _, _ } = Response -> Response;
|
||||
error -> { ok, Receiver }
|
||||
end;
|
||||
false when Module == ?kernel ->
|
||||
case rewrite(Module, Name, Args, Arity) of
|
||||
{ ok, AR, AN, AA } ->
|
||||
{ ok, AR, { { '.', [], [AR, AN] }, [], AA } };
|
||||
false ->
|
||||
error
|
||||
end;
|
||||
false ->
|
||||
error
|
||||
end.
|
||||
@@ -303,7 +313,7 @@ elixir_imported_functions() ->
|
||||
try
|
||||
?kernel:'__info__'(functions)
|
||||
catch
|
||||
error:undef -> in_erlang_functions()
|
||||
error:undef -> []
|
||||
end.
|
||||
|
||||
elixir_imported_macros() ->
|
||||
@@ -313,81 +323,137 @@ elixir_imported_macros() ->
|
||||
error:undef -> []
|
||||
end.
|
||||
|
||||
%% Functions imported from Erlang module. MUST BE SORTED.
|
||||
%% TODO: Inline operators here as well once bug in erl_eval is fixed.
|
||||
in_erlang_functions() ->
|
||||
[
|
||||
{ abs, 1 },
|
||||
{ apply, 2 },
|
||||
{ apply, 3 },
|
||||
{ atom_to_list, 1 },
|
||||
{ binary_part, 3 },
|
||||
{ binary_to_float, 1 },
|
||||
{ binary_to_float, 2 },
|
||||
{ binary_to_integer, 1 },
|
||||
{ binary_to_integer, 2 },
|
||||
{ binary_to_term, 1 },
|
||||
{ binary_to_term, 2 },
|
||||
{ bit_size, 1 },
|
||||
{ bitstring_to_list, 1 },
|
||||
{ byte_size, 1 },
|
||||
% { date, 0 },
|
||||
{ exit, 1 },
|
||||
% { float, 1 },
|
||||
{ float_to_binary, 1 },
|
||||
% { float_to_binary, 2 },
|
||||
{ float_to_list, 1 },
|
||||
% { float_to_list, 2 },
|
||||
{ hd, 1 },
|
||||
{ integer_to_binary, 1 },
|
||||
{ integer_to_binary, 2 },
|
||||
{ integer_to_list, 1 },
|
||||
{ integer_to_list, 2 },
|
||||
{ iolist_size, 1 },
|
||||
{ iolist_to_binary, 1 },
|
||||
{ is_atom, 1 },
|
||||
{ is_binary, 1 },
|
||||
{ is_bitstring, 1 },
|
||||
{ is_boolean, 1 },
|
||||
{ is_float, 1 },
|
||||
{ is_function, 1 },
|
||||
{ is_function, 2 },
|
||||
{ is_integer, 1 },
|
||||
{ is_list, 1 },
|
||||
{ is_number, 1 },
|
||||
{ is_pid, 1 },
|
||||
{ is_port, 1 },
|
||||
{ is_reference, 1 },
|
||||
{ is_tuple, 1 },
|
||||
{ length, 1 },
|
||||
{ list_to_atom, 1 },
|
||||
{ list_to_bitstring, 1 },
|
||||
{ list_to_existing_atom, 1 },
|
||||
{ list_to_float, 1 },
|
||||
{ list_to_integer, 1 },
|
||||
{ list_to_integer, 2 },
|
||||
{ list_to_tuple, 1 },
|
||||
{ make_ref, 0 },
|
||||
{ max, 2 },
|
||||
{ min, 2 },
|
||||
{ node, 0 },
|
||||
{ node, 1 },
|
||||
% { now, 0 },
|
||||
{ round, 1 },
|
||||
{ self, 0 },
|
||||
{ send, 2 },
|
||||
{ size, 1 },
|
||||
{ spawn, 1 },
|
||||
{ spawn, 3 },
|
||||
{ spawn_link, 1 },
|
||||
{ spawn_link, 3 },
|
||||
% { split_binary, 2 },
|
||||
{ term_to_binary, 1 },
|
||||
{ term_to_binary, 2 },
|
||||
{ throw, 1 },
|
||||
% { time, 0 },
|
||||
{ tl, 1 },
|
||||
{ trunc, 1 },
|
||||
{ tuple_size, 1 },
|
||||
{ tuple_to_list, 1 }
|
||||
].
|
||||
rewrite(?kernel, atom_to_binary, [Arg], 1) ->
|
||||
{ ok, erlang, atom_to_binary, [Arg, utf8] };
|
||||
rewrite(?kernel, binary_to_atom, [Arg], 1) ->
|
||||
{ ok, erlang, binary_to_atom, [Arg, utf8] };
|
||||
rewrite(?kernel, binary_to_existing_atom, [Arg], 1) ->
|
||||
{ ok, erlang, binary_to_existing_atom, [Arg, utf8] };
|
||||
rewrite(?kernel, elem, [Tuple, Index], 2) ->
|
||||
{ ok, erlang, element, [increment(Index), Tuple] };
|
||||
rewrite(?kernel, set_elem, [Tuple, Index, Value], 2) ->
|
||||
{ ok, erlang, setelement, [increment(Index), Tuple, Value] };
|
||||
rewrite(?process, monitor, [Arg], 1) ->
|
||||
{ ok, erlang, monitor, [process, Arg] };
|
||||
rewrite(Receiver, Name, Args, Arity) ->
|
||||
case inline(Receiver, Name, Arity) of
|
||||
{ AR, AN } -> { ok, AR, AN, Args };
|
||||
false -> false
|
||||
end.
|
||||
|
||||
increment(Number) when is_number(Number) ->
|
||||
Number + 1;
|
||||
increment(Other) ->
|
||||
{ { '.', [], [erlang, '+'] }, [], [Other, 1] }.
|
||||
|
||||
inline(?kernel, '+', 2) -> { erlang, '+' };
|
||||
inline(?kernel, '-', 2) -> { erlang, '-' };
|
||||
inline(?kernel, '+', 1) -> { erlang, '+' };
|
||||
inline(?kernel, '-', 1) -> { erlang, '-' };
|
||||
inline(?kernel, '*', 2) -> { erlang, '*' };
|
||||
inline(?kernel, '/', 2) -> { erlang, '/' };
|
||||
inline(?kernel, '++', 2) -> { erlang, '++' };
|
||||
inline(?kernel, '--', 2) -> { erlang, '--' };
|
||||
inline(?kernel, 'xor', 2) -> { erlang, 'xor' };
|
||||
inline(?kernel, 'not', 1) -> { erlang, 'not' };
|
||||
inline(?kernel, '<', 2) -> { erlang, '<' };
|
||||
inline(?kernel, '>', 2) -> { erlang, '>' };
|
||||
inline(?kernel, '<=', 2) -> { erlang, '=<' };
|
||||
inline(?kernel, '>=', 2) -> { erlang, '>=' };
|
||||
inline(?kernel, '==', 2) -> { erlang, '==' };
|
||||
inline(?kernel, '!=', 2) -> { erlang, '/=' };
|
||||
inline(?kernel, '===', 2) -> { erlang, '=:=' };
|
||||
inline(?kernel, '!==', 2) -> { erlang, '=/=' };
|
||||
inline(?kernel, abs, 1) -> { erlang, abs };
|
||||
inline(?kernel, apply, 2) -> { erlang, apply };
|
||||
inline(?kernel, apply, 3) -> { erlang, apply };
|
||||
inline(?kernel, atom_to_list, 1) -> { erlang, atom_to_list };
|
||||
inline(?kernel, binary_part, 3) -> { erlang, binary_part };
|
||||
inline(?kernel, binary_to_float, 1) -> { erlang, binary_to_float };
|
||||
inline(?kernel, binary_to_float, 2) -> { erlang, binary_to_float };
|
||||
inline(?kernel, binary_to_integer, 1) -> { erlang, binary_to_integer };
|
||||
inline(?kernel, binary_to_integer, 2) -> { erlang, binary_to_integer };
|
||||
inline(?kernel, bit_size, 1) -> { erlang, bit_size };
|
||||
inline(?kernel, bitstring_to_list, 1) -> { erlang, bitstring_to_list };
|
||||
inline(?kernel, byte_size, 1) -> { erlang, byte_size };
|
||||
inline(?kernel, 'div', 2) -> { erlang, 'div' };
|
||||
inline(?kernel, exit, 1) -> { erlang, exit };
|
||||
inline(?kernel, float_to_binary, 1) -> { erlang, float_to_binary };
|
||||
inline(?kernel, float_to_list, 1) -> { erlang, float_to_list };
|
||||
inline(?kernel, hd, 1) -> { erlang, hd };
|
||||
inline(?kernel, integer_to_binary, 1) -> { erlang, integer_to_binary };
|
||||
inline(?kernel, integer_to_binary, 2) -> { erlang, integer_to_binary };
|
||||
inline(?kernel, integer_to_list, 1) -> { erlang, integer_to_list };
|
||||
inline(?kernel, integer_to_list, 2) -> { erlang, integer_to_list };
|
||||
inline(?kernel, iolist_size, 1) -> { erlang, iolist_size };
|
||||
inline(?kernel, iolist_to_binary, 1) -> { erlang, iolist_to_binary };
|
||||
inline(?kernel, is_atom, 1) -> { erlang, is_atom };
|
||||
inline(?kernel, is_binary, 1) -> { erlang, is_binary };
|
||||
inline(?kernel, is_bitstring, 1) -> { erlang, is_bitstring };
|
||||
inline(?kernel, is_boolean, 1) -> { erlang, is_boolean };
|
||||
inline(?kernel, is_float, 1) -> { erlang, is_float };
|
||||
inline(?kernel, is_function, 1) -> { erlang, is_function };
|
||||
inline(?kernel, is_function, 2) -> { erlang, is_function };
|
||||
inline(?kernel, is_integer, 1) -> { erlang, is_integer };
|
||||
inline(?kernel, is_list, 1) -> { erlang, is_list };
|
||||
inline(?kernel, is_map, 1) -> { erlang, is_map };
|
||||
inline(?kernel, is_number, 1) -> { erlang, is_number };
|
||||
inline(?kernel, is_pid, 1) -> { erlang, is_pid };
|
||||
inline(?kernel, is_port, 1) -> { erlang, is_port };
|
||||
inline(?kernel, is_reference, 1) -> { erlang, is_reference };
|
||||
inline(?kernel, is_tuple, 1) -> { erlang, is_tuple };
|
||||
inline(?kernel, length, 1) -> { erlang, length };
|
||||
inline(?kernel, list_to_atom, 1) -> { erlang, list_to_atom };
|
||||
inline(?kernel, list_to_bitstring, 1) -> { erlang, list_to_bitstring };
|
||||
inline(?kernel, list_to_existing_atom, 1) -> { erlang, list_to_existing_atom };
|
||||
inline(?kernel, list_to_float, 1) -> { erlang, list_to_float };
|
||||
inline(?kernel, list_to_integer, 1) -> { erlang, list_to_integer };
|
||||
inline(?kernel, list_to_integer, 2) -> { erlang, list_to_integer };
|
||||
inline(?kernel, list_to_tuple, 1) -> { erlang, list_to_tuple };
|
||||
inline(?kernel, make_ref, 0) -> { erlang, make_ref };
|
||||
inline(?kernel, map_size, 1) -> { erlang, map_size };
|
||||
inline(?kernel, max, 2) -> { erlang, max };
|
||||
inline(?kernel, min, 2) -> { erlang, min };
|
||||
inline(?kernel, node, 0) -> { erlang, node };
|
||||
inline(?kernel, node, 1) -> { erlang, node };
|
||||
inline(?kernel, 'rem', 2) -> { erlang, 'rem' };
|
||||
inline(?kernel, round, 1) -> { erlang, round };
|
||||
inline(?kernel, self, 0) -> { erlang, self };
|
||||
inline(?kernel, send, 2) -> { erlang, send };
|
||||
inline(?kernel, size, 1) -> { erlang, size };
|
||||
inline(?kernel, spawn, 1) -> { erlang, spawn };
|
||||
inline(?kernel, spawn, 3) -> { erlang, spawn };
|
||||
inline(?kernel, spawn_link, 1) -> { erlang, spawn_link };
|
||||
inline(?kernel, spawn_link, 3) -> { erlang, spawn_link };
|
||||
inline(?kernel, throw, 1) -> { erlang, throw };
|
||||
inline(?kernel, tl, 1) -> { erlang, tl };
|
||||
inline(?kernel, trunc, 1) -> { erlang, trunc };
|
||||
inline(?kernel, tuple_size, 1) -> { erlang, tuple_size };
|
||||
inline(?kernel, tuple_to_list, 1) -> { erlang, tuple_to_list };
|
||||
|
||||
inline(?process, exit, 2) -> { erlang, exit };
|
||||
inline(?process, spawn, 1) -> { erlang, spawn };
|
||||
inline(?process, spawn, 2) -> { erlang, spawn_opt };
|
||||
inline(?process, spawn, 3) -> { erlang, spawn };
|
||||
inline(?process, spawn, 4) -> { erlang, spawn_opt };
|
||||
inline(?process, spawn_link, 1) -> { erlang, spawn_link };
|
||||
inline(?process, spawn_link, 3) -> { erlang, spawn_link };
|
||||
inline(?process, spawn_monitor, 1) -> { erlang, spawn_monitor };
|
||||
inline(?process, spawn_monitor, 3) -> { erlang, spawn_monitor };
|
||||
inline(?process, demonitor, 1) -> { erlang, demonitor };
|
||||
inline(?process, demonitor, 2) -> { erlang, demonitor };
|
||||
inline(?process, link, 1) -> { erlang, link };
|
||||
inline(?process, unlink, 1) -> { erlang, unlink };
|
||||
|
||||
inline(?node, spawn, 2) -> { erlang, spawn };
|
||||
inline(?node, spawn, 3) -> { erlang, spawn_opt };
|
||||
inline(?node, spawn, 4) -> { erlang, spawn };
|
||||
inline(?node, spawn, 5) -> { erlang, spawn_opt };
|
||||
inline(?node, spawn_link, 2) -> { erlang, spawn_link };
|
||||
inline(?node, spawn_link, 4) -> { erlang, spawn_link };
|
||||
inline(?node, spawn_monitor, 2) -> { erlang, spawn_monitor };
|
||||
inline(?node, spawn_monitor, 4) -> { erlang, spawn_monitor };
|
||||
|
||||
inline(?system, stacktrace, 0) -> { erlang, get_stacktrace };
|
||||
|
||||
inline(_, _, _) -> false.
|
||||
|
||||
@@ -36,8 +36,8 @@ mergev(E1, E2) ->
|
||||
}.
|
||||
|
||||
%% Receives two scopes and return the later scope
|
||||
%% keeping the variables from the first (counters,
|
||||
%% imports and everything else are passed forward).
|
||||
%% keeping the variables from the first (imports
|
||||
%% and everything else are passed forward).
|
||||
|
||||
mergea(E1, E2) ->
|
||||
E2#elixir_env{vars=E1#elixir_env.vars}.
|
||||
|
||||
@@ -2,11 +2,13 @@
|
||||
% This is not exposed in the Elixir language.
|
||||
-module(elixir_errors).
|
||||
-export([compile_error/3, compile_error/4,
|
||||
form_error/4, parse_error/4, warn/1,
|
||||
form_error/4, parse_error/4, warn/2, warn/3,
|
||||
handle_file_warning/2, handle_file_warning/3, handle_file_error/2,
|
||||
deprecation/3, deprecation/4]).
|
||||
-include("elixir.hrl").
|
||||
|
||||
-type line_or_meta() :: integer() | list().
|
||||
|
||||
warn(Warning) ->
|
||||
CompilerPid = get(elixir_compiler_pid),
|
||||
if
|
||||
@@ -16,15 +18,21 @@ warn(Warning) ->
|
||||
end,
|
||||
io:put_chars(standard_error, Warning).
|
||||
|
||||
warn(Caller, Warning) ->
|
||||
warn([Caller, "warning: ", Warning]).
|
||||
|
||||
warn(Line, File, Warning) ->
|
||||
warn(file_format(Line, File, "warning: " ++ Warning)).
|
||||
|
||||
%% Raised during expansion/translation/compilation.
|
||||
|
||||
-spec form_error(list(), binary(), module(), any()) -> no_return().
|
||||
-spec form_error(line_or_meta(), binary(), module(), any()) -> no_return().
|
||||
|
||||
form_error(Meta, File, Module, Desc) ->
|
||||
compile_error(Meta, File, format_error(Module, Desc)).
|
||||
|
||||
-spec compile_error(list(), binary(), iolist()) -> no_return().
|
||||
-spec compile_error(list(), binary(), iolist(), list()) -> no_return().
|
||||
-spec compile_error(line_or_meta(), binary(), iolist()) -> no_return().
|
||||
-spec compile_error(line_or_meta(), binary(), iolist(), list()) -> no_return().
|
||||
|
||||
compile_error(Meta, File, Message) when is_list(Message) ->
|
||||
raise(Meta, File, 'Elixir.CompileError', elixir_utils:characters_to_binary(Message)).
|
||||
@@ -34,30 +42,20 @@ compile_error(Meta, File, Format, Args) when is_list(Format) ->
|
||||
|
||||
%% Raised on tokenizing/parsing
|
||||
|
||||
-spec parse_error(non_neg_integer() | list(), file:filename_all(), iolist() | atom(), [] | iolist()) -> no_return().
|
||||
-spec parse_error(line_or_meta(), binary(), iolist() | atom(), [] | iolist()) -> no_return().
|
||||
|
||||
parse_error(Meta, File, Error, []) ->
|
||||
parse_error(Meta, File, Error, <<>>) ->
|
||||
Message = case Error of
|
||||
"syntax error before: " -> <<"syntax error: expression is incomplete">>;
|
||||
_ -> iolist_to_binary(Error)
|
||||
<<"syntax error before: ">> -> <<"syntax error: expression is incomplete">>;
|
||||
_ -> Error
|
||||
end,
|
||||
raise(Meta, File, 'Elixir.TokenMissingError', Message);
|
||||
|
||||
parse_error(Meta, File, "syntax error before: ", "'end'") ->
|
||||
parse_error(Meta, File, <<"syntax error before: ">>, <<"'end'">>) ->
|
||||
raise(Meta, File, 'Elixir.SyntaxError', <<"unexpected token: end">>);
|
||||
|
||||
parse_error(Meta, File, Error, Token) ->
|
||||
BinError = if
|
||||
is_atom(Error) -> atom_to_binary(Error, utf8);
|
||||
true -> iolist_to_binary(Error)
|
||||
end,
|
||||
|
||||
BinToken = if
|
||||
Token == [] -> <<>>;
|
||||
true -> elixir_utils:characters_to_binary(Token)
|
||||
end,
|
||||
|
||||
Message = <<BinError / binary, BinToken / binary >>,
|
||||
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
|
||||
@@ -93,7 +91,7 @@ handle_file_warning(_, File, { Line, sys_core_fold, { no_effect, { erlang, F, A
|
||||
end
|
||||
end,
|
||||
Message = io_lib:format(Fmt, Args),
|
||||
warn(file_format(Line, File, Message));
|
||||
warn(Line, File, Message);
|
||||
|
||||
%% Rewrite undefined behaviour to check for protocols
|
||||
handle_file_warning(_, File, {Line,erl_lint,{undefined_behaviour_func,{Fun,Arity},Module}}) ->
|
||||
@@ -106,14 +104,14 @@ handle_file_warning(_, File, {Line,erl_lint,{undefined_behaviour_func,{Fun,Arity
|
||||
Kind = protocol_or_behaviour(Module),
|
||||
Raw = "undefined ~ts ~ts ~ts/~B (for ~ts ~ts)",
|
||||
Message = io_lib:format(Raw, [Kind, DefKind, Def, DefArity, Kind, elixir_aliases:inspect(Module)]),
|
||||
warn(file_format(Line, File, Message));
|
||||
warn(Line, File, Message);
|
||||
|
||||
handle_file_warning(_, File, {Line,erl_lint,{undefined_behaviour,Module}}) ->
|
||||
case elixir_compiler:get_opt(internal) of
|
||||
true -> [];
|
||||
false ->
|
||||
Message = io_lib:format("behaviour ~ts undefined", [elixir_aliases:inspect(Module)]),
|
||||
warn(file_format(Line, File, Message))
|
||||
warn(Line, File, Message)
|
||||
end;
|
||||
|
||||
%% Ignore unused vars at "weird" lines (<= 0)
|
||||
@@ -127,12 +125,12 @@ handle_file_warning(_, _File, {_Line,erl_lint,{shadowed_var,_Var,_Where}}) ->
|
||||
%% Properly format other unused vars
|
||||
handle_file_warning(_, File, {Line,erl_lint,{unused_var,Var}}) ->
|
||||
Message = format_error(erl_lint, { unused_var, format_var(Var) }),
|
||||
warn(file_format(Line, File, Message));
|
||||
warn(Line, File, Message);
|
||||
|
||||
%% Default behavior
|
||||
%% Default behaviour
|
||||
handle_file_warning(_, File, {Line,Module,Desc}) ->
|
||||
Message = format_error(Module, Desc),
|
||||
warn(file_format(Line, File, Message)).
|
||||
warn(Line, File, Message).
|
||||
|
||||
handle_file_warning(File, Desc) ->
|
||||
handle_file_warning(false, File, Desc).
|
||||
@@ -198,10 +196,8 @@ protocol_or_behaviour(Module) ->
|
||||
is_protocol(Module) ->
|
||||
case code:ensure_loaded(Module) of
|
||||
{ module, _ } ->
|
||||
case erlang:function_exported(Module, '__protocol__', 1) of
|
||||
true -> Module:'__protocol__'(name) == Module;
|
||||
false -> false
|
||||
end;
|
||||
erlang:function_exported(Module, '__protocol__', 1) andalso
|
||||
Module:'__protocol__'(name) == Module;
|
||||
{ error, _ } ->
|
||||
false
|
||||
end.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
-module(elixir_exp).
|
||||
-export([expand/2, expand_many/2, expand_args/2, expand_arg/2]).
|
||||
-export([expand/2, expand_args/2, expand_arg/2]).
|
||||
-import(elixir_errors, [compile_error/3, compile_error/4]).
|
||||
-include("elixir.hrl").
|
||||
|
||||
@@ -17,6 +17,12 @@ expand({ '{}', Meta, Args }, E) ->
|
||||
{ EArgs, EA } = expand_args(Args, E),
|
||||
{ { '{}', Meta, EArgs }, EA };
|
||||
|
||||
expand({ '%{}', Meta, Args }, E) ->
|
||||
elixir_map:expand_map(Meta, Args, E);
|
||||
|
||||
expand({ '%', Meta, [Left, Right] }, E) ->
|
||||
elixir_map:expand_struct(Meta, Left, Right, E);
|
||||
|
||||
expand({ '<<>>', Meta, Args }, E) ->
|
||||
elixir_bitstring:expand(Meta, Args, E);
|
||||
|
||||
@@ -79,7 +85,8 @@ expand({ alias, Meta, [Ref, KV] }, E) ->
|
||||
expand_alias(Meta, true, ERef, EKV, ET) };
|
||||
true ->
|
||||
compile_error(Meta, E#elixir_env.file,
|
||||
"invalid arguments for alias, expected a compile time atom or alias as argument")
|
||||
"invalid argument for alias, expected a compile time atom or alias, got: ~ts",
|
||||
['Elixir.Kernel':inspect(ERef)])
|
||||
end;
|
||||
|
||||
expand({ require, Meta, [Ref] }, E) ->
|
||||
@@ -97,7 +104,8 @@ expand({ require, Meta, [Ref, KV] }, E) ->
|
||||
expand_require(Meta, ERef, EKV, ET) };
|
||||
true ->
|
||||
compile_error(Meta, E#elixir_env.file,
|
||||
"invalid arguments for require, expected a compile time atom or alias as argument")
|
||||
"invalid argument for require, expected a compile time atom or alias, got: ~ts",
|
||||
['Elixir.Kernel':inspect(ERef)])
|
||||
end;
|
||||
|
||||
expand({ import, Meta, [Left] }, E) ->
|
||||
@@ -115,16 +123,15 @@ expand({ import, Meta, [Ref, KV] }, E) ->
|
||||
{ { import, Meta, [ERef, EKV] },
|
||||
expand_require(Meta, ERef, EKV, ET#elixir_env{functions=Functions, macros=Macros}) };
|
||||
true ->
|
||||
compile_error(Meta, E#elixir_env.file, "invalid name for import, expected a compile time atom or alias")
|
||||
compile_error(Meta, E#elixir_env.file,
|
||||
"invalid argument for import, expected a compile time atom or alias, got: ~ts",
|
||||
['Elixir.Kernel':inspect(ERef)])
|
||||
end;
|
||||
|
||||
%% Pseudo vars
|
||||
|
||||
expand({ '__MODULE__', _, Atom }, E) when is_atom(Atom) ->
|
||||
{ E#elixir_env.module, E };
|
||||
expand({ '__FILE__', Meta, Atom }, E) when is_atom(Atom) ->
|
||||
elixir_errors:deprecation(Meta, E#elixir_env.file, "__FILE__ is deprecated, please use __DIR__ or __ENV__.file instead"),
|
||||
{ E#elixir_env.file, E };
|
||||
expand({ '__DIR__', _, Atom }, E) when is_atom(Atom) ->
|
||||
{ filename:dirname(E#elixir_env.file), E };
|
||||
expand({ '__CALLER__', _, Atom } = Caller, E) when is_atom(Atom) ->
|
||||
@@ -175,8 +182,9 @@ expand({ quote, Meta, [KV, Do] }, E) when is_list(Do) ->
|
||||
Context = case lists:keyfind(context, 1, EKV) of
|
||||
{ context, Atom } when is_atom(Atom) ->
|
||||
Atom;
|
||||
{ context, _ } ->
|
||||
compile_error(Meta, E#elixir_env.file, "invalid :context for quote, expected a compile time atom or an alias");
|
||||
{ context, Ctx } ->
|
||||
compile_error(Meta, E#elixir_env.file, "invalid :context for quote, "
|
||||
"expected a compile time atom or alias, got: ~ts", ['Elixir.Kernel':inspect(Ctx)]);
|
||||
false ->
|
||||
case E#elixir_env.module of
|
||||
nil -> 'Elixir';
|
||||
@@ -250,6 +258,9 @@ expand({'try', Meta, [KV]}, E) ->
|
||||
expand({ Kind, Meta, Args }, E) when is_list(Args), (Kind == lc) orelse (Kind == bc) ->
|
||||
expand_comprehension(Meta, Kind, Args, E);
|
||||
|
||||
expand({ for, Meta, Args }, E) when is_list(Args) ->
|
||||
elixir_for:expand(Meta, Args, E);
|
||||
|
||||
%% Super
|
||||
|
||||
expand({ super, Meta, Args }, E) when is_list(Args) ->
|
||||
@@ -296,9 +307,6 @@ expand({ Name, Meta, Kind } = Var, #elixir_env{vars=Vars} = E) when is_atom(Name
|
||||
|
||||
compile_error(Meta, E#elixir_env.file, "expected var ~ts~ts to expand to an existing "
|
||||
"variable or be a part of a match", [Name, Extra]);
|
||||
E#elixir_env.context == guard ->
|
||||
compile_error(Meta, E#elixir_env.file, "unknown variable ~ts or cannot invoke "
|
||||
"function ~ts/0 inside guard", [Name, Name]);
|
||||
true ->
|
||||
expand({ Name, Meta, [] }, E)
|
||||
end
|
||||
@@ -319,8 +327,8 @@ expand({ { '.', DotMeta, [Left, Right] }, Meta, Args }, E)
|
||||
when (is_tuple(Left) orelse is_atom(Left)), is_atom(Right), is_list(Meta), is_list(Args) ->
|
||||
{ ELeft, EL } = expand(Left, E),
|
||||
|
||||
elixir_dispatch:dispatch_require(Meta, ELeft, Right, Args, EL, fun(Receiver) ->
|
||||
expand_remote(Receiver, DotMeta, Right, Meta, Args, E, EL)
|
||||
elixir_dispatch:dispatch_require(Meta, ELeft, Right, Args, EL, fun(AR, AF, AA) ->
|
||||
expand_remote(AR, DotMeta, AF, Meta, AA, E, EL)
|
||||
end);
|
||||
|
||||
%% Anonymous calls
|
||||
@@ -402,14 +410,14 @@ expand_many(Args, E) ->
|
||||
%% 3
|
||||
%%
|
||||
%% However, lexical information is.
|
||||
expand_arg(Arg, Acc) when is_number(Arg); is_atom(Arg); is_binary(Arg); is_pid(Arg); is_function(Arg) ->
|
||||
expand_arg(Arg, Acc) when is_number(Arg); is_atom(Arg); is_binary(Arg); is_pid(Arg) ->
|
||||
{ Arg, Acc };
|
||||
expand_arg(Arg, { Acc1, Acc2 }) ->
|
||||
{ EArg, EAcc } = expand(Arg, Acc1),
|
||||
{ EArg, { elixir_env:mergea(Acc1, EAcc), elixir_env:mergev(Acc2, EAcc) } }.
|
||||
|
||||
expand_args(Args, #elixir_env{context=match} = E) ->
|
||||
lists:mapfoldl(fun expand/2, E, Args);
|
||||
expand_many(Args, E);
|
||||
expand_args(Args, E) ->
|
||||
{ EArgs, { EC, EV } } = lists:mapfoldl(fun expand_arg/2, {E, E}, Args),
|
||||
{ EArgs, elixir_env:mergea(EV, EC) }.
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user