Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
44b0412042 | ||
|
|
d984a82587 | ||
|
|
e59d50a891 | ||
|
|
7bf568d0b3 | ||
|
|
f95cafe5ca | ||
|
|
f0e1443f21 | ||
|
|
cf619585a6 | ||
|
|
c639b01146 | ||
|
|
b4415f2a14 | ||
|
|
538b51c20a | ||
|
|
c8a0158791 | ||
|
|
b088d4b90e | ||
|
|
02a415facd | ||
|
|
5cee19067d | ||
|
|
3496681236 | ||
|
|
a7645a2c8b | ||
|
|
d9423627f3 | ||
|
|
5a8520fb0f | ||
|
|
4458ee1232 | ||
|
|
a6b1cca91f | ||
|
|
e2535731c8 | ||
|
|
393ff69b1e | ||
|
|
033747ad63 | ||
|
|
730d043631 | ||
|
|
c819bdbf74 | ||
|
|
64f8541ee6 | ||
|
|
d1f4e2d49b | ||
|
|
5a13aecbae | ||
|
|
61904ee24c | ||
|
|
5c7fdcc244 | ||
|
|
f772877039 | ||
|
|
e571218917 | ||
|
|
aa01dd1fc1 | ||
|
|
30971c9cb3 | ||
|
|
abfc69a6e2 | ||
|
|
ac1fc484e4 | ||
|
|
889bfebe4e | ||
|
|
e9899a5462 | ||
|
|
ec92bb118d | ||
|
|
fd9ca435b7 | ||
|
|
3ec0d20621 | ||
|
|
16f3b928a1 | ||
|
|
fb20b6ddf6 | ||
|
|
d77a4d6c3f | ||
|
|
d7900239c2 | ||
|
|
babd98be3f | ||
|
|
9a85b1556b | ||
|
|
d1da200af5 | ||
|
|
e260b92f89 | ||
|
|
e09e5c3f41 | ||
|
|
a76a281309 | ||
|
|
be42d00067 | ||
|
|
f21a998f81 | ||
|
|
89e0018d58 | ||
|
|
21b691cd4e | ||
|
|
a8a91c2282 | ||
|
|
5275646966 | ||
|
|
e60cd6a484 | ||
|
|
552006bea3 | ||
|
|
2077b584ad | ||
|
|
29f05fda35 | ||
|
|
cbb7c545e0 | ||
|
|
c909267077 | ||
|
|
a9bbabfabc | ||
|
|
c4d666a4ba | ||
|
|
8e824d3da1 | ||
|
|
41caa0d635 | ||
|
|
3a0108edf8 | ||
|
|
7123fb9d09 | ||
|
|
6d8e118192 | ||
|
|
846703b31c | ||
|
|
46905b39a7 | ||
|
|
0f43aca5bc | ||
|
|
587c83cbff | ||
|
|
e8194b1764 | ||
|
|
feb9cb645e | ||
|
|
29f9a4fd67 | ||
|
|
22f3a50eba | ||
|
|
470e5424bb | ||
|
|
8f272c4cc8 | ||
|
|
46c23b1547 | ||
|
|
4d607ace08 | ||
|
|
78adb5b200 | ||
|
|
311efb66a9 | ||
|
|
272be2c088 | ||
|
|
911acc3173 | ||
|
|
182cbfe541 | ||
|
|
c43bc6a06d | ||
|
|
38aa5c57e5 | ||
|
|
4f93dae442 | ||
|
|
41c74e9682 | ||
|
|
e0f5dd506a | ||
|
|
f90eeaa024 | ||
|
|
9451e69996 | ||
|
|
924350868a | ||
|
|
ee91be519a | ||
|
|
90951be944 | ||
|
|
65cf31d32d | ||
|
|
e14c9ea6ce | ||
|
|
8635fcc869 | ||
|
|
65809a5644 | ||
|
|
d53a315069 | ||
|
|
8d2199c3f3 | ||
|
|
54e69c023a | ||
|
|
55503239a4 | ||
|
|
19d8446ac0 | ||
|
|
5a94906f1d | ||
|
|
915ba1bb29 | ||
|
|
d35d03a8ee | ||
|
|
b959049c77 | ||
|
|
24c4bc3009 | ||
|
|
000a174353 | ||
|
|
b126e3b089 | ||
|
|
319957f5df | ||
|
|
cb2eb7d376 | ||
|
|
1857267872 | ||
|
|
91faec5f65 | ||
|
|
0f0a91faef | ||
|
|
baec57637e | ||
|
|
0c8c6f03e0 | ||
|
|
7bdbefb277 | ||
|
|
ec0e7d8056 | ||
|
|
c2e912bdfd | ||
|
|
14e1d410f1 | ||
|
|
15925573e8 | ||
|
|
f32ede4e3f | ||
|
|
1dc9193e69 | ||
|
|
dacb30ad05 | ||
|
|
362df17316 | ||
|
|
d54d0f6a6b | ||
|
|
72d2f38cb5 | ||
|
|
b3cc527126 | ||
|
|
6b45855f0e | ||
|
|
40835556da | ||
|
|
dc9a47d259 | ||
|
|
2d1cf2ebc1 | ||
|
|
9a56e5ef5a | ||
|
|
ab148551ba | ||
|
|
4d7c75f2ca | ||
|
|
e7f3db9fd4 | ||
|
|
4682511923 | ||
|
|
e942c07f34 | ||
|
|
dc403e4311 | ||
|
|
b3c6bc2bc5 | ||
|
|
c377483715 | ||
|
|
3ded858534 | ||
|
|
aa09b1e2d2 | ||
|
|
8cb4c719f9 | ||
|
|
ffa2abd8c6 | ||
|
|
14722ba102 | ||
|
|
7f4437ece5 | ||
|
|
afc7644fe0 | ||
|
|
7de880495b | ||
|
|
29addf9e93 | ||
|
|
6e9f9f254f | ||
|
|
db25e24eb8 | ||
|
|
8a184a6b3d | ||
|
|
fcd6213c4c | ||
|
|
5409b7e707 | ||
|
|
815772811f | ||
|
|
9cff61493e | ||
|
|
65a3434bd4 | ||
|
|
4ff9f9aa29 | ||
|
|
3d982fd1a9 | ||
|
|
396b1e68a8 | ||
|
|
9c549b3465 | ||
|
|
04194a189d | ||
|
|
35e04d0ea5 | ||
|
|
1038018e84 | ||
|
|
2b3e53670e | ||
|
|
dff93a44c2 | ||
|
|
f76c8fec7b | ||
|
|
8b19b0b3f4 | ||
|
|
77fad29a1b | ||
|
|
5bab9cae49 | ||
|
|
05507d1f3a | ||
|
|
e369900b06 | ||
|
|
a629cfce86 | ||
|
|
1a93d35d9c | ||
|
|
06cc1c183f | ||
|
|
cb5806bbf3 | ||
|
|
db70fd5a5f | ||
|
|
513ed0bbae | ||
|
|
3bbae0a368 | ||
|
|
2134db3a0d | ||
|
|
80f35eab7d | ||
|
|
ab8a2d195f | ||
|
|
a7096b5061 | ||
|
|
8552886a8a | ||
|
|
789a657490 | ||
|
|
2ee44eba29 | ||
|
|
e4a9fae0fd | ||
|
|
0030b282c1 | ||
|
|
9c90955863 | ||
|
|
61e626bdb8 | ||
|
|
3c40de4c0c | ||
|
|
86a8a6ee88 | ||
|
|
4d5696e074 | ||
|
|
892f211550 | ||
|
|
2caa16ac22 | ||
|
|
32c5694551 | ||
|
|
1aeb7ae7d0 | ||
|
|
fe8f3464b6 | ||
|
|
8941d2821d | ||
|
|
7ba3c3cfb7 | ||
|
|
5cc2724b40 | ||
|
|
40dff7ebe8 | ||
|
|
5158a19fbd | ||
|
|
fd936bb326 | ||
|
|
3e54d33481 | ||
|
|
0fe1e686b3 | ||
|
|
9e80f3b82b | ||
|
|
01bda975c3 | ||
|
|
05a8ce56a3 | ||
|
|
d498a89267 | ||
|
|
46db0d4304 | ||
|
|
f2ec77886a | ||
|
|
4e8ae3abdb | ||
|
|
dc7ffae763 | ||
|
|
b7897b224d | ||
|
|
c80a65b657 | ||
|
|
f02cd8437b | ||
|
|
7d87e84803 | ||
|
|
b4a0b4cc19 | ||
|
|
1940f490b2 | ||
|
|
69098968be | ||
|
|
85a04fe919 | ||
|
|
54db13ae56 | ||
|
|
f60d3668ed | ||
|
|
3471898364 | ||
|
|
bf7d172f11 | ||
|
|
28457563ce | ||
|
|
280a5ceedc | ||
|
|
a696cf41dc | ||
|
|
f3f0ec09b7 | ||
|
|
e040688bd9 | ||
|
|
9d84e1c007 | ||
|
|
2e6a83dff9 | ||
|
|
d866a26ac1 | ||
|
|
3453c30f4c | ||
|
|
50a7b0e1bc | ||
|
|
7ce790035d | ||
|
|
606cd2ff69 | ||
|
|
9c9d2e1ea0 | ||
|
|
6e7baa42ac | ||
|
|
633f92edbe | ||
|
|
b23f2fc360 | ||
|
|
5b31ec421d | ||
|
|
2d38f20b43 | ||
|
|
9f6f92b0f2 | ||
|
|
4a183b462c | ||
|
|
4d53d8c710 | ||
|
|
a09a0c2a1a | ||
|
|
6a42ab0797 | ||
|
|
183ed3ffdd | ||
|
|
8a8779fcbc | ||
|
|
bcb8cd763e | ||
|
|
97c0809845 | ||
|
|
e33a9557aa | ||
|
|
43706a4623 | ||
|
|
b816c98877 | ||
|
|
23f0ff81c4 | ||
|
|
6aada7fe3c | ||
|
|
e24cef0126 | ||
|
|
b161070345 | ||
|
|
0b4f9ad486 | ||
|
|
d484ca8ae2 | ||
|
|
d77c1a34f4 | ||
|
|
f6b0f29f5d | ||
|
|
436f563ad3 | ||
|
|
81b80d5b9c | ||
|
|
425cea5a84 | ||
|
|
b4c0826467 | ||
|
|
d94491841a | ||
|
|
bb1093ef2d | ||
|
|
863e22a7e2 | ||
|
|
aa682a4704 | ||
|
|
01d5f99437 | ||
|
|
34c2da2c65 | ||
|
|
a3d5be91f2 | ||
|
|
bbb77e0102 | ||
|
|
19164d2e55 | ||
|
|
a950d4fda1 | ||
|
|
33ac879521 | ||
|
|
6ef4c80724 | ||
|
|
3843ee6ce5 | ||
|
|
3ab1db6740 | ||
|
|
1f2dc0f716 | ||
|
|
83fa2fbbbf | ||
|
|
cc969f8d01 | ||
|
|
3de70b909a | ||
|
|
7782227182 | ||
|
|
58cd9e5c11 | ||
|
|
862ab01f94 | ||
|
|
2c630ff33c | ||
|
|
007571e979 | ||
|
|
53552d434d | ||
|
|
eb32937858 | ||
|
|
6720cb6830 | ||
|
|
16c0262122 | ||
|
|
2606f47138 | ||
|
|
9337d50775 | ||
|
|
069415595a | ||
|
|
4e88d82871 | ||
|
|
5e8d948e39 | ||
|
|
893700f65e | ||
|
|
c3f0929a77 | ||
|
|
8448513c8b | ||
|
|
bc65fc0276 | ||
|
|
b1f0d3ed99 | ||
|
|
211d9428b4 | ||
|
|
8a3f2cfc2f | ||
|
|
fff7a85769 | ||
|
|
377c64d1b0 |
@@ -4,7 +4,6 @@
|
||||
/ebin
|
||||
/lib/*/ebin/*
|
||||
/lib/*/tmp
|
||||
/lib/*/test/tmp
|
||||
/lib/elixir/src/elixir.app.src
|
||||
/lib/elixir/src/*_lexer.erl
|
||||
/lib/elixir/src/*_parser.erl
|
||||
|
||||
+139
-31
@@ -1,4 +1,112 @@
|
||||
# v0.13.3 (2014-05-24)
|
||||
# Changelog
|
||||
|
||||
## v0.14.2
|
||||
|
||||
* Enhancements
|
||||
* [Enum] Improve performance of `Enum.join/2` and `Enum.map_join/3` by using iolists
|
||||
* [Kernel] Ensure compatibility with Erlang 17.1
|
||||
* [Kernel] Support `@external_resource` attribute to external dependencies to a module
|
||||
* [Mix] Allow built Git dependencies to run on a system without Git by passing `--no-deps-check`
|
||||
* [Mix] Add `MIX_ARCHIVES` env variable (it is recommended for Elixir build tools to swap this environment)
|
||||
* [Task] Set `:proc_lib` initial call on task to aid debugging
|
||||
* [Typespec] Delay typespec compilation to after expansion
|
||||
* [URI] Allow `parse/1` now accepts `%URI{}` as argument and return the uri itself
|
||||
|
||||
* Bug fixes
|
||||
* [CLI] Support paths inside archives in `-pa` and `-pz` options
|
||||
* [IEx] Remove dely when printing data from the an application start callback
|
||||
* [IEx] Ensure we show a consistent error when we cannot evaluate `.iex.exs`
|
||||
* [Kernel] Ensure derived protocols are defined with a file
|
||||
* [Kernel] Change precedence of `&` to not special case `/`
|
||||
* [Kernel] Ensure we can only use variables and `\\` as arguments of bodyless clause
|
||||
|
||||
* Soft deprecations (no warnings emitted)
|
||||
* [EEx] Using `EEx.TransformerEngine` and `EEx.AssignsEngine` are deprecated in favor of function composition with `Macro.prewalk/1` (see `EEx.SmartEngine` for an example)
|
||||
* [Mix] `Mix.Generator.from_file/1` is deprecated in favor of passing `from_file: file` option to `embed_text/2` and `embed_template/2` (note though that `from_file/1` expects a path relative to the current file while the `from_file: file` expects one relative to the current working directory)
|
||||
|
||||
* Deprecations
|
||||
* [Kernel] `size/1` is deprecated in favor of `byte_size/1` and `tuple_size/1` (this change was soft deprecated two releases ago)
|
||||
|
||||
* Backwards incompatible changes
|
||||
* [CLI] Remove support for the `--gen-debug` option as its usage is not documented by OTP
|
||||
* [Kernel] Sigils no longer balance start and end tokens, e.g. the sigil `~s(f(o)o)` is no longer valid as it finishes in the first closing `)`
|
||||
* [Kernel] Variables set in `cond` clause heads are no longer available outside of that particular `cond` clause (this is the behaviour also found in `case`, `receive` and friends)
|
||||
* [System] `build_info/0` now returns a map
|
||||
|
||||
## v0.14.1 (2014-06-18)
|
||||
|
||||
* Enhancements
|
||||
* [Base] Decoding and encoding functions now accept the `:case` as an option
|
||||
* [ExUnit] The test process now exits with `:shutdown` reason
|
||||
* [GenEvent] `GenEvent.stream/2` now accepts `:sync` and `:async` modes
|
||||
* [Node] Add `Node.start/3` and `Node.stop/0`
|
||||
* [String] Updated Unicode database to 7.0
|
||||
* [Task] Log when tasks crash
|
||||
|
||||
* Bug fixes
|
||||
* [Enum] `Enum.slice/2` and `Enum.slice/3` always returns a list (and never nil)
|
||||
* [Kernel] Disambiguate (w)erl to (w)erl.exe
|
||||
* [Mix] Ensure umbrella project is recompiled when a dependency inside an umbrella child changes
|
||||
* [OptionParser] Do not allow underscores in option names
|
||||
* [Path] Fix path expansion of `"/.."`
|
||||
* [Path] Do not match files starting with `.` in `Path.wildcard/2` by default
|
||||
* [Process] `Process.info(pid, :registered_name)` returns `{:registered_name, nil}` if there is no registered name
|
||||
* [String] `String.slice/2` and `String.slice/3` always returns a list (and never nil)
|
||||
* [URI] `encode/1` does not escape reserved/unreserved characters by default nor encodes whitespace as `+` (check `URI.encode_www_form/1` and `URI.decode_www_form/1` for previous behaviour)
|
||||
|
||||
* Deprecations
|
||||
* [Mix] `:escript_*` options were moved into a single `:escript` group
|
||||
|
||||
* Backwards incompatible changes
|
||||
* [GenEvent] `GenEvent.stream/2` defaults to `:sync` mode
|
||||
* [Kernel] Remove `get_in/1`
|
||||
|
||||
## v0.14.0 (2014-06-08)
|
||||
|
||||
* Enhancements
|
||||
* [ExUnit] Add `on_exit/1` callbacks that are guaranteed to run once the test process exits and always in another process
|
||||
* [Kernel] Store documentation in the abstract code to avoid loading them when the module is loaded
|
||||
* [Kernel] Add `get_in/2`, `put_in/3`, `update_in/3` and `get_and_update_in/3` to handle nested data structure operations
|
||||
* [Kernel] Add `get_in/1`, `put_in/2`, `update_in/2` and `get_and_update_in/2` to handle nested data structure operations via paths
|
||||
* [Mix] Add `Mix.Config` to ease definition of configuration files
|
||||
* [Mix] Add `mix loadconfig` task that can be called multiple times to load external configs
|
||||
* [Mix] Support `--config` option on `mix run`
|
||||
* [Mix] Support `HTTP_PROXY` and `HTTPS_PROXY` on Mix url commands
|
||||
* [Mix] Support `--names` options in `mix help` which emit only names (useful for autocompletion)
|
||||
* [Protocol] Add `Protocol.consolidate/2`, `Protocol.consolidated?/1` and a `mix compile.protocols` task for protocol consolidation
|
||||
* [Protocol] Add `Protocol.derive/3` for runtime deriving of a struct
|
||||
* [String] Add `String.chunk/2`
|
||||
* [Struct] Add support for `@derive` before `defstruct/2` definitions
|
||||
|
||||
* Bug fixes
|
||||
* [File] `File.rm` now consistently deletes read-only across operating systems
|
||||
* [Kernel] Ensure Mix `_build` structure works on Windows when copying projects
|
||||
* [Kernel] Ensure `1.0E10` (with uppercase E) is also valid syntax
|
||||
* [Mix] Fix `mix do` task for Windows' powershell users
|
||||
* [Path] Fix `Path.absname("/")` and `Path.expand("/")` to return the absolute path `"/"`.
|
||||
|
||||
* Soft deprecations (no warnings emitted)
|
||||
* [Kernel] `size/1` is deprecated, please use `byte_size/1` or `tuple_size/1` instead
|
||||
* [ExUnit] `teardown/2` and `teardown_all/2` are deprecated in favor of `on_exit/1` callbacks
|
||||
|
||||
* Deprecations
|
||||
* [Access] `Access.access/2` is deprecated in favor of `Access.get/2`
|
||||
* [Dict] `Dict.Behaviour` is deprecated in favor of `Dict`
|
||||
* [Kernel] `Application.Behaviour`, `GenEvent.Behaviour`, `GenServer.Behaviour` and `Supervisor.Behaviour` are deprecated in favor of `Application`, `GenEvent`, `GenServer` and `Supervisor`
|
||||
* [Kernel] `defexception/3` is deprecated in favor of `defexception/1`
|
||||
* [Kernel] `raise/3` is deprecated in favor of `reraise/2`
|
||||
* [Kernel] `set_elem/3` is deprecated in favor of `put_elem/3`
|
||||
* [Kernel] Passing an atom `var!/1` is deprecated, variables can be built dynamically with `Macro.var/2`
|
||||
* [Mix] Exceptions that define a `:mix_error` field to be compatible with Mix are no longer supported. Instead please provide a `:mix` field and use `Mix.raise/1` and `Mix.raise/2`
|
||||
|
||||
* Backwards incompatible changes
|
||||
* [Access] `Kernel.access/2` no longer exists and the `Access` protocol now requires `get/2` (instead of `access/2`) and `get_and_update/3` to be implemented
|
||||
* [Kernel] Retrieving docs as `module.__info__(:docs)` is no longer supported, please use `Code.get_docs/2` instead
|
||||
* [Kernel] `Code.compiler_options/1` no longer accepts custom options, only the ones specified by Elixir (use mix config instead)
|
||||
* [Mix] `mix new` no longer generates a supevision tree by default, please pass `--sup` instead
|
||||
* [Task] Tasks are automatically linked to callers and a failure in the task will crash the caller directly
|
||||
|
||||
## v0.13.3 (2014-05-24)
|
||||
|
||||
* Enhancements
|
||||
* [OptionParser] Add `:strict` option that only parses known switches
|
||||
@@ -46,7 +154,7 @@
|
||||
* [OptionParser] Errors on parsing returns the switch and value as binaries (unparsed)
|
||||
* [String] `String.to_char_list/1` (previously deprecated) no longer returns a tuple but the char list only and raises in case of failure
|
||||
|
||||
# v0.13.2 (2014-05-11)
|
||||
## v0.13.2 (2014-05-11)
|
||||
|
||||
* Enhancements
|
||||
* [Application] Add an Application module with common functions to work with OTP applications
|
||||
@@ -55,7 +163,7 @@
|
||||
* [Mix] `mix deps.clean` now works accross environments
|
||||
* [Mix] Support line numbers in `mix test`, e.g. test/some/file_test.exs:12
|
||||
* [Mix] Use `@file` attributes to detect dependencies in between `.ex` and external files. This means changing an `.eex` file will no longer recompile the whole project only the files that depend directly on it
|
||||
* [Mix] Support application configurations in `config/config.exs` which can be customized by specifying your own `:config_path`
|
||||
* [Mix] Support application configurations in `config/config.exs`
|
||||
* [Mix] Support user-wide configuration with `~/.mix/config.exs`
|
||||
* [Mix] `mix help` now uses ANSI formatting to print guides
|
||||
* [Regex] Support functions in `Regex.replace/4`
|
||||
@@ -98,7 +206,7 @@
|
||||
* [URI] The `URI.Info` record has now become the `URI` struct
|
||||
* [Version] The `Version.Schema` record has now become the `Version` struct
|
||||
|
||||
# v0.13.1 (2014-04-27)
|
||||
## v0.13.1 (2014-04-27)
|
||||
|
||||
* Enhancements
|
||||
* [Mix] Support `MIX_EXS` as configuration for running the current mix.exs file
|
||||
@@ -124,7 +232,7 @@
|
||||
* [Path] Atoms are no longer supported in Path functions
|
||||
* [Regex] Regexes are no longer unicode by default. Instead, they must be explicitly marked with the `u` option
|
||||
|
||||
# v0.13.0 (2014-04-20)
|
||||
## v0.13.0 (2014-04-20)
|
||||
|
||||
* Enhancements
|
||||
* [Base] Add `Base` module which does conversions to bases 16, 32, hex32, 64 and url64
|
||||
@@ -175,7 +283,7 @@
|
||||
* Backwards incompatible changes
|
||||
* [ExUnit] Formatters are now required to be a GenEvent and `ExUnit.run/2` returns a map with results
|
||||
|
||||
# v0.12.5 (2014-03-09)
|
||||
## v0.12.5 (2014-03-09)
|
||||
|
||||
* Bug fixes
|
||||
* [Kernel] Ensure `try` does not generate an after clause. Generating an after clause forbade clauses in the `else` part from being tail recursive. This should improve performance and memory consumption of `Stream` functions
|
||||
@@ -193,7 +301,7 @@
|
||||
* Backwards incompatible changes
|
||||
* [Mix] Remove `MIX_GIT_FORCE_HTTPS` as Git itself already provides mechanisms for doing so
|
||||
|
||||
# v0.12.4 (2014-02-12)
|
||||
## v0.12.4 (2014-02-12)
|
||||
|
||||
* Enhancements
|
||||
* [Mix] `mix deps.get` and `mix deps.update` no longer compile dependencies afterwards. Instead, they mark the dependencies which are going to be automatically compiled next time `deps.check` is invoked (which is done automatically by most mix tasks). This means users should have a better workflow when migrating in between environments
|
||||
@@ -207,7 +315,7 @@
|
||||
* [ExUnit] `CaptureIO` returns an empty string instead of nil when there is no capture
|
||||
* [Version] The `Version` module now only works with SemVer. The functions `Version.parse/1` and `Version.parse_requirement/1` now return `{:ok,res} | :error` for the cases you want to handle non SemVer cases manually. All other functions will trigger errors on non semantics versions
|
||||
|
||||
# v0.12.3 (2014-02-02)
|
||||
## v0.12.3 (2014-02-02)
|
||||
|
||||
* Enhancements
|
||||
* [Kernel] Warnings now are explicitly tagged with "warning:" in messages
|
||||
@@ -240,7 +348,7 @@
|
||||
* [Range] `Range` is no longer a record, instead use `first .. last` if you need pattern matching
|
||||
* [Set] Implementations of `difference/2`, `disjoint?/2`, `equal?/2`, `intersection/2`, `subset?/2` and `union/2` in `HashSet` are no longer polymorphic. To get polymorphism, use the functions in `Set` instead
|
||||
|
||||
# v0.12.2 (2014-01-15)
|
||||
## v0.12.2 (2014-01-15)
|
||||
|
||||
* Enhancements
|
||||
* [EEx] Allow `EEx.AssignsEngine` to accept any Dict
|
||||
@@ -269,7 +377,7 @@
|
||||
* Backwards incompatible changes
|
||||
* [String] Change `String.next_grapheme/1` and `String.next_codepoint/1` to return `nil` on string end
|
||||
|
||||
# v0.12.1 (2014-01-04)
|
||||
## v0.12.1 (2014-01-04)
|
||||
|
||||
* Enhancements
|
||||
* [ExUnit] Support `:include` and `:exclude` configuration options to filter which tests should run based on their tags. Those options are also supported via `mix test` as `--include` and `--exclude`
|
||||
@@ -293,7 +401,7 @@
|
||||
* [GenServer] GenServer now stops on unknown call/cast requests
|
||||
* [Kernel] Change how `->` is represented in AST. Now each clause is represented by its own AST node which makes composition easier. See commit 51aef55 for more information.
|
||||
|
||||
# v0.12.0 (2013-12-15)
|
||||
## v0.12.0 (2013-12-15)
|
||||
|
||||
* Enhancements
|
||||
* [Exception] Allow `exception/1` to be overridden and promote it as the main mechanism to customize exceptions
|
||||
@@ -330,7 +438,7 @@
|
||||
* [Stream] The `Stream.Lazy` structure has changed to accumulate functions and accumulators as we go (its inspected representation has also changed)
|
||||
* [Typespec] `when` clauses were moved to the outer part of the spec and should be in the keywords format. So `add(a, b) when is_subtype(a, integer) and is_subtype(b, integer) :: integer` should now be written as `add(a, b) :: integer when a: integer, b: integer`
|
||||
|
||||
# v0.11.2 (2013-11-14)
|
||||
## v0.11.2 (2013-11-14)
|
||||
|
||||
* Enhancements
|
||||
* [Mix] Add `mix iex` that redirects users to the proper `iex -S mix` command
|
||||
@@ -339,7 +447,7 @@
|
||||
* Backwards incompatible changes
|
||||
* [Mix] Mix now compiles files to `_build`. Projects should update just fine, however documentation and books may want to update to the latest information
|
||||
|
||||
# v0.11.1 (2013-11-07)
|
||||
## v0.11.1 (2013-11-07)
|
||||
|
||||
* Enhancements
|
||||
* [Mix] Improve dependency convergence by explicitly checking each requirement instead of expecting all requirements to be equal
|
||||
@@ -357,7 +465,7 @@
|
||||
* Backwards incompatible changes
|
||||
* [Mix] Setting `:load_paths` in your project configuration is deprecated
|
||||
|
||||
# v0.11.0 (2013-11-02)
|
||||
## v0.11.0 (2013-11-02)
|
||||
|
||||
* Enhancements
|
||||
* [Code] Eval now returns variables from other contexts
|
||||
@@ -404,7 +512,7 @@
|
||||
* [Process] `Process.group_leader/2` args have been reversed so the "subject" comes first
|
||||
* [Protocol] Protocol no longer dispatches to `Number`, but to `Integer` and `Float`
|
||||
|
||||
# v0.10.3 (2013-10-02)
|
||||
## v0.10.3 (2013-10-02)
|
||||
|
||||
* Enhancements
|
||||
* [Enum] Add `Enum.take_every/2`
|
||||
@@ -439,7 +547,7 @@
|
||||
* [Mix] `File.IteratorError` was renamed to `IO.StreamError`
|
||||
* [Mix] `mix new` now defaults to the `--sup` option, use `--bare` to get the previous behaviour
|
||||
|
||||
# v0.10.2 (2013-09-03)
|
||||
## v0.10.2 (2013-09-03)
|
||||
|
||||
* Enhancements
|
||||
* [CLI] Add `--verbose` to elixirc, which now is non-verbose by default
|
||||
@@ -502,7 +610,7 @@
|
||||
* [Kernel] The previous ambiguous import syntax `import :functions, Foo` was removed in favor of `import Foo, only: :functions`
|
||||
* [OptionParser] `parse` and `parse_head` now returns a tuple with three elements instead of two
|
||||
|
||||
# v0.10.1 (2013-08-03)
|
||||
## v0.10.1 (2013-08-03)
|
||||
|
||||
* Enhancements
|
||||
* [Behaviour] Add support for `defmacrocallback/1`
|
||||
@@ -552,7 +660,7 @@
|
||||
* [Regex] `Regex.scan/3` now always returns a list of lists, normalizing the result, instead of list with mixed lists and binaries
|
||||
* [System] `System.halt/2` was removed since the current Erlang implementation of such function is bugged
|
||||
|
||||
# v0.10.0 (2013-07-15)
|
||||
## v0.10.0 (2013-07-15)
|
||||
|
||||
* Enhancements
|
||||
* [ExUnit] Support `trace: true` option which gives detailed reporting on test runs
|
||||
@@ -596,7 +704,7 @@
|
||||
* [Record] First element of a record via `defrecordp` is now the `defrecordp` name and no longer the current atom
|
||||
* [URI] Remove custom URI parsers in favor of `URI.default_port/2`
|
||||
|
||||
# v0.9.3 (2013-06-23)
|
||||
## v0.9.3 (2013-06-23)
|
||||
|
||||
* Enhancements
|
||||
* [File] Add `File.chgrp`, `File.chmod` and `File.chown`
|
||||
@@ -634,7 +742,7 @@
|
||||
* [Macro] `Macro.expand/2` now expands until final form. Although this is backwards incompatible, it is very likely you do not need to change your code, since expansion until its final form is recommended, particularly if you are expecting an atom out of it
|
||||
* [Mix] No longer support beam files on `mix local`
|
||||
|
||||
# v0.9.2 (2013-06-13)
|
||||
## v0.9.2 (2013-06-13)
|
||||
|
||||
* Enhancements
|
||||
* [ExUnit] `capture_io` now captures prompt by default
|
||||
@@ -659,7 +767,7 @@
|
||||
* Backwards incompatible changes
|
||||
* [Kernel] The `=~` operator now returns true or false instead of an index
|
||||
|
||||
# v0.9.1 (2013-05-30)
|
||||
## v0.9.1 (2013-05-30)
|
||||
|
||||
* Enhancements
|
||||
* [IEx] Limit the number of entries kept in history and allow it to be configured
|
||||
@@ -675,7 +783,7 @@
|
||||
* [Record] Fix a bug where nested records cannot be defined
|
||||
* [Record] Fix a bug where a record named Record cannot be defined
|
||||
|
||||
# v0.9.0 (2013-05-23)
|
||||
## v0.9.0 (2013-05-23)
|
||||
|
||||
* Enhancements
|
||||
* [ExUnit] `ExUnit.CaptureIO` now accepts an input to be used during capture
|
||||
@@ -688,7 +796,7 @@
|
||||
* [Kernel] Erlang R15 is no longer supported
|
||||
* [Kernel] Elixir modules are now represented as `Elixir.ModuleName` (using `.` instead of `-` as separator)
|
||||
|
||||
# v0.8.3 (2013-05-22)
|
||||
## v0.8.3 (2013-05-22)
|
||||
|
||||
* Enhancements
|
||||
* [CLI] Flags `-p` and `-pr` fails if pattern match no files
|
||||
@@ -740,7 +848,7 @@
|
||||
* [Kernel] Precedence of `|>` has changed to lower to support constructs like `1..5 |> Enum.to_list`
|
||||
* [Mix] `mix escriptize` now receives arguments as binaries
|
||||
|
||||
# v0.8.2 (2013-04-20)
|
||||
## v0.8.2 (2013-04-20)
|
||||
|
||||
* Enhancements
|
||||
* [ExUnit] Use ANSI escape codes in CLI output
|
||||
@@ -780,7 +888,7 @@
|
||||
unquote(Macro.escape(x))
|
||||
end
|
||||
|
||||
# v0.8.1 (2013-02-17)
|
||||
## v0.8.1 (2013-02-17)
|
||||
|
||||
* Enhancements
|
||||
* [ExUnit] Tests can now receive metadata set on setup/teardown callbacks
|
||||
@@ -807,7 +915,7 @@
|
||||
* [Kernel] Old `:local.(args)` syntax is deprecated
|
||||
* [Process] `Process.self` is deprecated in favor `Kernel.self`
|
||||
|
||||
# v0.8.0 (2013-01-28)
|
||||
## v0.8.0 (2013-01-28)
|
||||
|
||||
* Enhancements
|
||||
* [Binary] Support `<< "string" :: utf8 >>` as in Erlang
|
||||
@@ -849,7 +957,7 @@
|
||||
* [Mix] `mix iex` is no longer functional, please use `iex -S mix`
|
||||
* [OptionParser] `:flags` option was deprecated in favor of `:switches` to support many types
|
||||
|
||||
# v0.7.2 (2012-12-04)
|
||||
## v0.7.2 (2012-12-04)
|
||||
|
||||
* Enhancements
|
||||
* [CLI] `--debug-info` is now true by default
|
||||
@@ -875,7 +983,7 @@
|
||||
* [Record] Default-based generated functions are deprecated
|
||||
* [Typespec] Enhancements and deprecations to the `@spec/@callback` and the fun type syntax
|
||||
|
||||
# v0.7.1 (2012-11-18)
|
||||
## v0.7.1 (2012-11-18)
|
||||
|
||||
* Enhancements
|
||||
* [IEx] Only show documented functions and also show docs for default generated functions
|
||||
@@ -906,7 +1014,7 @@
|
||||
* [Enum] `Enum.times` is deprecated in favor of using ranges
|
||||
* [System] `halt` moved to `System` module
|
||||
|
||||
# v0.7.0 (2012-10-20)
|
||||
## v0.7.0 (2012-10-20)
|
||||
|
||||
* Enhancements
|
||||
* [Behaviour] Add Behaviour with a simple callback DSL to define callbacks
|
||||
@@ -955,7 +1063,7 @@
|
||||
* [Kernel] `Erlang.` syntax is deprecated in favor of simply using atoms
|
||||
* [Module] `Module.read_attribute` and `Module.add_attribute` deprecated in favor of `Module.get_attribute` and `Module.put_attribute` which mimics Dict API
|
||||
|
||||
# v0.6.0 (2012-08-01)
|
||||
## v0.6.0 (2012-08-01)
|
||||
|
||||
* Backwards incompatible changes
|
||||
* [Kernel] Compile files now follow `Elixir-ModuleName` convention to solve issues with Erlang embedded mode. This removes the `__MAIN__` pseudo-variable as modules are now located inside `Elixir` namespace
|
||||
@@ -1005,6 +1113,6 @@
|
||||
* [Regex] Back references are now properly supported
|
||||
* [System] Add `System.find_executable`
|
||||
|
||||
# v0.5.0 (2012-05-24)
|
||||
## v0.5.0 (2012-05-24)
|
||||
|
||||
* First official release
|
||||
|
||||
+21
-1
@@ -71,7 +71,7 @@ Please provide as much detail and context as possible.
|
||||
|
||||
## Contributing
|
||||
|
||||
We incentivate everyone to contribute to Elixir and help us tackle
|
||||
We incentivize everyone to contribute to Elixir and help us tackle
|
||||
existing issues! To do so, there are a few things you need to know
|
||||
about the code. First, Elixir code is divided in applications inside
|
||||
the `lib` folder:
|
||||
@@ -107,6 +107,26 @@ check [the official build status on Travis-CI](https://travis-ci.org/elixir-lang
|
||||
With tests running and passing, you are ready to contribute to Elixir and
|
||||
send your pull requests.
|
||||
|
||||
### Building on Windows
|
||||
|
||||
There are a few extra steps you'll need to take for contributing from Windows.
|
||||
Basically, once you have Erlang 17, Git, and MSYS from MinGW on your system,
|
||||
you're all set. Specifically, here's what you need to do to get up and running:
|
||||
|
||||
1. Install [Git](http://www.git-scm.com/download/win),
|
||||
[Erlang](http://www.erlang.org/download.html), and the
|
||||
[MinGW Installation Manager](http://sourceforge.net/projects/mingw/files/latest/download?source=files).
|
||||
2. Use the MinGW Installation Manager to install the msys-bash, msys-make, and
|
||||
msys-grep packages.
|
||||
3. Add `;C:\Program Files (x86)\Git\bin;C:\Program Files\erl6.0\bin;C:\Program Files\erl6.0\erts-6.0\bin;C:\MinGW\msys\1.0\bin`
|
||||
to your "Path" environment variable . (This is under Control Panel > System
|
||||
and Security > System > Advanced system settings > Environment Variables >
|
||||
System variables)
|
||||
|
||||
You can now work in the Command Prompt similar to how you would on other OS'es,
|
||||
except for some things (like creating symlinks) you'll need to run the Command
|
||||
Prompt as an Administrator.
|
||||
|
||||
## Contributing Documentation
|
||||
|
||||
Code documentation (`@doc`, `@moduledoc`, `@typedoc`) has a special convention:
|
||||
|
||||
@@ -32,7 +32,7 @@ $(1): lib/$(1)/ebin/Elixir.$(2).beam lib/$(1)/ebin/$(1).app
|
||||
lib/$(1)/ebin/$(1).app: lib/$(1)/mix.exs
|
||||
$(Q) mkdir -p lib/$(1)/_build/shared/lib/$(1)
|
||||
$(Q) cp -R lib/$(1)/ebin lib/$(1)/_build/shared/lib/$(1)/
|
||||
$(Q) cd lib/$(1) && ../../bin/elixir -e "Mix.Sup.start_link()" -r mix.exs -e "Mix.Task.run('compile.app')"
|
||||
$(Q) cd lib/$(1) && ../../bin/elixir -e "Mix.start(:permanent, [])" -r mix.exs -e "Mix.Task.run('compile.app')"
|
||||
$(Q) cp lib/$(1)/_build/shared/lib/$(1)/ebin/$(1).app lib/$(1)/ebin/$(1).app
|
||||
$(Q) rm -rf lib/$(1)/_build
|
||||
|
||||
@@ -101,7 +101,7 @@ install: compile
|
||||
$(INSTALL_DATA) $$dir/ebin/* "$(DESTDIR)$(PREFIX)/$(LIBDIR)/elixir/$$dir/ebin"; \
|
||||
done
|
||||
$(Q) $(INSTALL_DIR) "$(DESTDIR)$(PREFIX)/$(LIBDIR)/elixir/bin"
|
||||
$(Q) $(INSTALL_PROGRAM) $(filter-out %.bat, $(wildcard bin/*)) "$(DESTDIR)$(PREFIX)/$(LIBDIR)/elixir/bin"
|
||||
$(Q) $(INSTALL_PROGRAM) $(filter-out %.ps1, $(filter-out %.bat, $(wildcard bin/*))) "$(DESTDIR)$(PREFIX)/$(LIBDIR)/elixir/bin"
|
||||
$(Q) $(INSTALL_DIR) "$(DESTDIR)$(PREFIX)/bin"
|
||||
$(Q) for file in "$(DESTDIR)$(PREFIX)"/$(LIBDIR)/elixir/bin/* ; do \
|
||||
ln -sf "../$(LIBDIR)/elixir/bin/$${file##*/}" "$(DESTDIR)$(PREFIX)/bin/" ; \
|
||||
@@ -124,13 +124,15 @@ clean_exbeam:
|
||||
#==> Release tasks
|
||||
|
||||
SOURCE_REF = $(shell head="$$(git rev-parse HEAD)" tag="$$(git tag --points-at $$head | tail -1)" ; echo "$${tag:-$$head}\c")
|
||||
DOCS = bin/elixir ../ex_doc/bin/ex_doc "$(1)" "$(VERSION)" "lib/$(2)/ebin" -m "$(3)" -u "https://github.com/elixir-lang/elixir" --source-ref "$(call SOURCE_REF)" -o docs/$(2) -p http://elixir-lang.org/docs.html
|
||||
|
||||
docs: compile ../ex_doc/bin/ex_doc
|
||||
mkdir -p ebin
|
||||
rm -rf docs
|
||||
cp -R -f lib/*/ebin/*.beam ./ebin
|
||||
bin/elixir ../ex_doc/bin/ex_doc "Elixir" "$(VERSION)" "./ebin" -m Kernel -u "https://github.com/elixir-lang/elixir" --source-ref "$(call SOURCE_REF)"
|
||||
rm -rf ebin
|
||||
$(Q) rm -rf docs
|
||||
$(call DOCS,Elixir,elixir,Kernel)
|
||||
$(call DOCS,EEx,eex,EEx)
|
||||
$(call DOCS,Mix,mix,Mix)
|
||||
$(call DOCS,IEx,iex,IEx)
|
||||
$(call DOCS,ExUnit,ex_unit,ExUnit)
|
||||
|
||||
../ex_doc/bin/ex_doc:
|
||||
@ echo "ex_doc is not found in ../ex_doc as expected. See README for more information."
|
||||
@@ -170,6 +172,7 @@ test_doc_test: compile
|
||||
|
||||
test_stdlib: compile
|
||||
@ echo "==> elixir (exunit)"
|
||||
$(Q) exec epmd & exit
|
||||
$(Q) cd lib/elixir && ../../bin/elixir -r "test/elixir/test_helper.exs" -pr "test/elixir/**/*_test.exs";
|
||||
|
||||
.dialyzer.base_plt:
|
||||
|
||||
@@ -37,7 +37,7 @@ We appreciate any contribution to Elixir, so check out our [CONTRIBUTING.md](CON
|
||||
|
||||
## Important links
|
||||
|
||||
* #elixir-lang on freenode IRC
|
||||
* \#elixir-lang on freenode IRC
|
||||
* [Website][1]
|
||||
* [Issue tracker][2]
|
||||
* [elixir-talk Mailing list (questions)][3]
|
||||
|
||||
+13
-11
@@ -1,26 +1,28 @@
|
||||
## Release process
|
||||
# Release process
|
||||
|
||||
This document simply outlines the release process:
|
||||
|
||||
1) Remove `-dev` extension from VERSION
|
||||
1. Remove `-dev` extension from VERSION
|
||||
|
||||
2) Ensure CHANGELOG is updated and timestamp
|
||||
2. Ensure CHANGELOG is updated and timestamp
|
||||
|
||||
3) Commit changes above with title "Release vVERSION" and generate new tag
|
||||
3. Commit changes above with title "Release vVERSION" and generate new tag
|
||||
|
||||
4) Run `make clean test` to ensure all tests pass from scratch and the CI is green
|
||||
4. Run `make clean test` to ensure all tests pass from scratch and the CI is green
|
||||
|
||||
5) Push master and tags
|
||||
5. Push master and tags
|
||||
|
||||
6) Release new docs with `make release_docs`, move docs to `docs/stable`
|
||||
6. Release new docs with `make release_docs`, move docs to `docs/stable`
|
||||
|
||||
7) Release new zip with `make release_zip`, push new zip to GitHub Releases
|
||||
7. Release new zip with `make release_zip`, push new zip to GitHub Releases
|
||||
|
||||
8) Merge master into stable branch and push it
|
||||
8. Merge master into stable branch and push it
|
||||
|
||||
9) After release, bump versions, add `-dev` back and commit
|
||||
9. After release, bump versions, add `-dev` back and commit
|
||||
|
||||
10) `make release_docs` once again and push it to `elixir-lang.github.com`
|
||||
10. `make release_docs` once again and push it to `elixir-lang.org`
|
||||
|
||||
11. Also update `release` file in `elixir-lang.org`
|
||||
|
||||
## Places where version is mentioned
|
||||
|
||||
|
||||
+2
-6
@@ -17,7 +17,6 @@ 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
|
||||
@@ -72,9 +71,6 @@ while [ $I -le $# ]; do
|
||||
eval "VAL=\${$I}"
|
||||
ERL="$ERL "$VAL""
|
||||
;;
|
||||
--gen-debug)
|
||||
ERL="$ERL -generic_debug"
|
||||
;;
|
||||
*)
|
||||
break
|
||||
;;
|
||||
@@ -84,7 +80,7 @@ done
|
||||
|
||||
SELF=$(readlink_f "$0")
|
||||
SCRIPT_PATH=$(dirname "$SELF")
|
||||
if [ "$MODE" != "iex" ]; then ERL="$ERL -s elixir start_cli"; fi
|
||||
if [ "$MODE" != "iex" ]; then ERL="$ERL -noshell -s elixir start_cli"; fi
|
||||
|
||||
if [ -z "$ERL_PATH" ]; then
|
||||
if [ -f "$SCRIPT_PATH/../releases/RELEASES" ] && [ -f "$SCRIPT_PATH/erl" ]; then
|
||||
@@ -94,4 +90,4 @@ if [ -z "$ERL_PATH" ]; then
|
||||
fi
|
||||
fi
|
||||
|
||||
exec "$ERL_PATH" -pa "$SCRIPT_PATH"/../lib/*/ebin -noshell $ELIXIR_ERL_OPTIONS $ERL -extra "$@"
|
||||
exec "$ERL_PATH" -pa "$SCRIPT_PATH"/../lib/*/ebin $ELIXIR_ERL_OPTIONS $ERL -extra "$@"
|
||||
|
||||
+2
-4
@@ -23,7 +23,6 @@ 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
|
||||
@@ -68,7 +67,6 @@ IF NOT "%par%"=="%par:--cookie=%" (Set parsErlang=%parsErlang% -setcookie %1 &
|
||||
IF NOT "%par%"=="%par:--sname=%" (Set parsErlang=%parsErlang% -sname %1 && shift)
|
||||
IF NOT "%par%"=="%par:--name=%" (Set parsErlang=%parsErlang% -name %1 && shift)
|
||||
IF NOT "%par%"=="%par:--erl=%" (Set beforeExtra=%beforeExtra% %~1 && shift)
|
||||
IF NOT "%par%"=="%par:--gen-debug=%" (Set parsErlang=%parsErlang% -generic_debug)
|
||||
rem ******* elixir parameters **********************
|
||||
rem Note: we don't have to do anything with options that don't take an argument
|
||||
IF NOT "%par%"=="%par:-e=%" (shift)
|
||||
@@ -91,7 +89,7 @@ for /d %%d in ("%originPath%..\lib\*.") do (
|
||||
SETLOCAL disabledelayedexpansion
|
||||
:run
|
||||
IF %useWerl% EQU 1 (
|
||||
werl %ext_libs% -noshell %ELIXIR_ERL_OPTIONS% %parsErlang% -s elixir start_cli %beforeExtra% -extra %*
|
||||
werl.exe %ext_libs% %ELIXIR_ERL_OPTIONS% %parsErlang% -s elixir start_cli %beforeExtra% -extra %*
|
||||
) ELSE (
|
||||
erl %ext_libs% -noshell %ELIXIR_ERL_OPTIONS% %parsErlang% -s elixir start_cli %beforeExtra% -extra %*
|
||||
erl.exe %ext_libs% -noshell %ELIXIR_ERL_OPTIONS% %parsErlang% -s elixir start_cli %beforeExtra% -extra %*
|
||||
)
|
||||
|
||||
+1
-1
@@ -11,7 +11,7 @@ if [ $# -eq 0 ] || [ "$1" = "--help" ] || [ "$1" = "-h" ]; then
|
||||
|
||||
** Options given after -- are passed down to the executed code
|
||||
** Options can be passed to the erlang runtime using ELIXIR_ERL_OPTIONS
|
||||
** Options can be passed to the erlang compiler using ERL_COMPILER_OPTS" >&2
|
||||
** Options can be passed to the erlang compiler using ERL_COMPILER_OPTIONS" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
|
||||
+1
-1
@@ -21,6 +21,6 @@ echo --verbose Print informational messages.
|
||||
echo.
|
||||
echo ** Options given after -- are passed down to the executed code
|
||||
echo ** Options can be passed to the erlang runtime using ELIXIR_ERL_OPTIONS
|
||||
echo ** Options can be passed to the erlang compiler using ERL_COMPILER_OPTS >&2
|
||||
echo ** Options can be passed to the erlang compiler using ERL_COMPILER_OPTIONS >&2
|
||||
:run
|
||||
call "%~dp0\elixir.bat" +elixirc %*
|
||||
|
||||
@@ -16,7 +16,6 @@ if [ $# -gt 0 ] && ([ "$1" = "--help" ] || [ "$1" = "-h" ]); then
|
||||
--cookie \"cookie\" Sets a cookie for this distributed node
|
||||
--hidden Makes a hidden node
|
||||
--detached Starts the Erlang VM detached from console
|
||||
--gen-debug Turns on default debugging for all GenServers
|
||||
--remsh \"name\" Connects to a node using a remote shell
|
||||
--dot-iex \"path\" Overrides default .iex.exs file and uses path instead;
|
||||
path can be empty, then no file will be loaded
|
||||
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
# Initialize with path to mix.bat relative to caller's working directory
|
||||
$toCmd = '' + (Resolve-Path -relative (Split-Path $MyInvocation.MyCommand.Path)) + '\mix.bat'
|
||||
|
||||
foreach ($arg in $args)
|
||||
{
|
||||
$toCmd += ' '
|
||||
|
||||
if ($arg -is [array])
|
||||
{
|
||||
# Commas created the array so we need to reintroduce those commas
|
||||
for ($i = 0; $i -lt $arg.length; $i++)
|
||||
{
|
||||
$toCmd += $arg[$i]
|
||||
if ($i -ne ($arg.length - 1))
|
||||
{
|
||||
$toCmd += ', '
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
$toCmd += $arg
|
||||
}
|
||||
}
|
||||
|
||||
# Corrected arguments are ready to pass to batch file
|
||||
cmd /c $toCmd
|
||||
+21
-19
@@ -14,32 +14,31 @@ defmodule EEx do
|
||||
|
||||
This module provides 3 main APIs for you to use:
|
||||
|
||||
1) Evaluate a string (`eval_string`) or a file (`eval_file`)
|
||||
directly. This is the simplest API to use but also the
|
||||
slowest, since the code is evaluated and not compiled before;
|
||||
1. Evaluate a string (`eval_string`) or a file (`eval_file`)
|
||||
directly. This is the simplest API to use but also the
|
||||
slowest, since the code is evaluated and not compiled before.
|
||||
|
||||
2) Define a function from a string (`function_from_string`)
|
||||
or a file (`function_from_file`). This allows you to embed
|
||||
the template as a function inside a module which will then
|
||||
be compiled. This is the preferred API if you have access
|
||||
to the template at compilation time;
|
||||
2. Define a function from a string (`function_from_string`)
|
||||
or a file (`function_from_file`). This allows you to embed
|
||||
the template as a function inside a module which will then
|
||||
be compiled. This is the preferred API if you have access
|
||||
to the template at compilation time.
|
||||
|
||||
3) Compile a string (`compile_string`) or a file (`compile_file`)
|
||||
into Elixir syntax tree. This is the API used by both functions
|
||||
above and is available to you if you want to provide your own
|
||||
ways of handling the compiled template.
|
||||
3. Compile a string (`compile_string`) or a file (`compile_file`)
|
||||
into Elixir syntax tree. This is the API used by both functions
|
||||
above and is available to you if you want to provide your own
|
||||
ways of handling the compiled template.
|
||||
|
||||
## Options
|
||||
|
||||
All functions in this module accepts EEx-related options.
|
||||
They are:
|
||||
|
||||
* `:line` - the line to be used as the template start.
|
||||
Defaults to 1;
|
||||
* `:file` - the file to be used in the template.
|
||||
Defaults to the given file the template is read from
|
||||
or to "nofile" when compiling from a string;
|
||||
* `:engine` - the EEx engine to be used for compilation.
|
||||
* `:line` - the line to be used as the template start. Defaults to 1.
|
||||
* `:file` - the file to be used in the template. Defaults to the given
|
||||
file the template is read from or to "nofile" when compiling
|
||||
from a string.
|
||||
* `:engine` - the EEx engine to be used for compilation.
|
||||
|
||||
## Engine
|
||||
|
||||
@@ -83,7 +82,7 @@ defmodule EEx do
|
||||
iex> EEx.eval_string "<%= @foo %>", assigns: [foo: 1]
|
||||
"1"
|
||||
|
||||
In other words, <%= @foo %> is simply translated to:
|
||||
In other words, `<%= @foo %>` is simply translated to:
|
||||
|
||||
<%= Dict.get assigns, :foo %>
|
||||
|
||||
@@ -93,6 +92,7 @@ defmodule EEx do
|
||||
|
||||
@doc """
|
||||
Generates a function definition from the string.
|
||||
|
||||
The kind (`:def` or `:defp`) must be given, the
|
||||
function name, its arguments and the compilation options.
|
||||
|
||||
@@ -121,6 +121,7 @@ defmodule EEx do
|
||||
|
||||
@doc """
|
||||
Generates a function definition from the file contents.
|
||||
|
||||
The kind (`:def` or `:defp`) must be given, the
|
||||
function name, its arguments and the compilation options.
|
||||
|
||||
@@ -148,6 +149,7 @@ defmodule EEx do
|
||||
args = Enum.map args, fn arg -> {arg, [line: 1], nil} end
|
||||
compiled = EEx.compile_file(file, info)
|
||||
|
||||
@external_resource file
|
||||
@file file
|
||||
case kind do
|
||||
:def -> def(unquote(name)(unquote_splicing(args)), do: unquote(compiled))
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
defmodule EEx.Compiler do
|
||||
@moduledoc false
|
||||
|
||||
# when changing this setting, don't forget to update the docs for EEx
|
||||
# When changing this setting, don't forget to update the docs for EEx
|
||||
@default_engine EEx.SmartEngine
|
||||
|
||||
@doc """
|
||||
|
||||
+57
-15
@@ -1,27 +1,28 @@
|
||||
defmodule EEx.Engine do
|
||||
@moduledoc ~S"""
|
||||
This is the basic EEx engine that ships with Elixir.
|
||||
Basic EEx engine that ships with Elixir.
|
||||
|
||||
An engine needs to implement three functions:
|
||||
|
||||
* `handle_body(quoted)` - receives the final built quoted
|
||||
expression, should do final post-processing and return a
|
||||
quoted expression;
|
||||
* `handle_body(quoted)` - receives the final built quoted
|
||||
expression, should do final post-processing and return a
|
||||
quoted expression.
|
||||
|
||||
* `handle_text(buffer, text)` - it receives the buffer,
|
||||
the text and must return a new quoted expression;
|
||||
* `handle_text(buffer, text)` - it receives the buffer,
|
||||
the text and must return a new quoted expression.
|
||||
|
||||
* `handle_expr(buffer, marker, expr)` - it receives the buffer,
|
||||
the marker, the expr and must return a new quoted expression;
|
||||
* `handle_expr(buffer, marker, expr)` - it receives the buffer,
|
||||
the marker, the expr and must return a new quoted expression.
|
||||
|
||||
The marker is what follows exactly after `<%`. For example,
|
||||
`<% foo %>` has an empty marker, but `<%= foo %>` has `"="`
|
||||
as marker. The allowed markers so far are:
|
||||
The marker is what follows exactly after `<%`. For example,
|
||||
`<% foo %>` has an empty marker, but `<%= foo %>` has `"="`
|
||||
as marker. The allowed markers so far are: `""` and `"="`.
|
||||
|
||||
* `""`
|
||||
* `"="`
|
||||
Read `handle_expr/3` below for more information about the markers
|
||||
implemented by default by this engine.
|
||||
|
||||
Read `handle_expr/3` below for more information about the markers
|
||||
implemented by default by this engine.
|
||||
`EEx.Engine` can be used directly if one desires to use the
|
||||
default implementations for the functions above.
|
||||
"""
|
||||
|
||||
use Behaviour
|
||||
@@ -30,6 +31,47 @@ defmodule EEx.Engine do
|
||||
defcallback handle_text(Macro.t, binary) :: Macro.t
|
||||
defcallback handle_expr(Macro.t, binary, Macro.t) :: Macro.t
|
||||
|
||||
@doc false
|
||||
defmacro __using__(_) do
|
||||
quote do
|
||||
@behaviour EEx.Engine
|
||||
|
||||
def handle_body(body) do
|
||||
EEx.Engine.handle_body(body)
|
||||
end
|
||||
|
||||
def handle_text(buffer, text) do
|
||||
EEx.Engine.handle_text(buffer, text)
|
||||
end
|
||||
|
||||
def handle_expr(buffer, mark, expr) do
|
||||
EEx.Engine.handle_expr(buffer, mark, expr)
|
||||
end
|
||||
|
||||
defoverridable [handle_body: 1, handle_expr: 3, handle_text: 2]
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Handles assigns in quoted expressions.
|
||||
|
||||
This can be added to any custom engine by invoking
|
||||
`handle_assign/3` with `Macro.prewalk/1`:
|
||||
|
||||
def handle_expr(buffer, token, expr) do
|
||||
expr = Macro.prewalk(expr, &EEx.Engine.handle_assign/1)
|
||||
EEx.Engine.handle_expr(buffer, token, expr)
|
||||
end
|
||||
|
||||
"""
|
||||
def handle_assign({:@, line, [{name, _, atom}]}) when is_atom(name) and is_atom(atom) do
|
||||
quote line: line, do: Dict.get(var!(assigns), unquote(name))
|
||||
end
|
||||
|
||||
def handle_assign(arg) do
|
||||
arg
|
||||
end
|
||||
|
||||
@doc """
|
||||
The default implementation implementation simply returns the
|
||||
given expression.
|
||||
|
||||
@@ -1,14 +1,5 @@
|
||||
defmodule EEx.TransformerEngine do
|
||||
@moduledoc """
|
||||
An abstract engine that is meant to be used and
|
||||
built upon in other modules. This engine implements
|
||||
the `EEx.Engine` behaviour and provides a `transform`
|
||||
overridable directive that allows a developer to
|
||||
customize the expression returned by the engine.
|
||||
|
||||
Check `EEx.AssignsEngine` and `EEx.SmartEngine` for
|
||||
examples of using this module.
|
||||
"""
|
||||
@moduledoc false
|
||||
|
||||
@doc false
|
||||
defmacro __using__(_) do
|
||||
@@ -49,20 +40,33 @@ defmodule EEx.TransformerEngine do
|
||||
end
|
||||
|
||||
defmodule EEx.AssignsEngine do
|
||||
@moduledoc """
|
||||
An abstract engine that, when used with the
|
||||
`TransformerEngine`, allows a developer to access
|
||||
assigns using `@` as syntax.
|
||||
@moduledoc false
|
||||
|
||||
This engine is included by default on the SmartEngine.
|
||||
@doc false
|
||||
defmacro __using__(_) do
|
||||
quote unquote: false do
|
||||
defp transform({:@, line, [{name, _, atom}]}) when is_atom(name) and is_atom(atom) do
|
||||
quote do: Dict.get(var!(assigns), unquote(name))
|
||||
end
|
||||
|
||||
defp transform(arg) do
|
||||
super(arg)
|
||||
end
|
||||
|
||||
defoverridable [transform: 1]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defmodule EEx.SmartEngine do
|
||||
@moduledoc """
|
||||
The default engine used by EEx.
|
||||
|
||||
It includes assigns (like `@foo`) and possibly other
|
||||
conveniences in the future.
|
||||
|
||||
## Examples
|
||||
|
||||
defmodule MyEngine do
|
||||
use EEx.TransformerEngine
|
||||
use EEx.AssignsEngine
|
||||
end
|
||||
|
||||
iex> EEx.eval_string("<%= @foo %>", assigns: [foo: 1])
|
||||
"1"
|
||||
|
||||
@@ -88,29 +92,10 @@ defmodule EEx.AssignsEngine do
|
||||
|
||||
"""
|
||||
|
||||
@doc false
|
||||
defmacro __using__(_) do
|
||||
quote unquote: false do
|
||||
defp transform({:@, line, [{name, _, atom}]}) when is_atom(name) and is_atom(atom) do
|
||||
quote do: Dict.get(var!(assigns), unquote(name))
|
||||
end
|
||||
use EEx.Engine
|
||||
|
||||
defp transform(arg) do
|
||||
super(arg)
|
||||
end
|
||||
|
||||
defoverridable [transform: 1]
|
||||
end
|
||||
def handle_expr(buffer, mark, expr) do
|
||||
expr = Macro.prewalk(expr, &EEx.Engine.handle_assign/1)
|
||||
super(buffer, mark, expr)
|
||||
end
|
||||
end
|
||||
|
||||
defmodule EEx.SmartEngine do
|
||||
use EEx.TransformerEngine
|
||||
use EEx.AssignsEngine
|
||||
|
||||
@moduledoc """
|
||||
An engine meant for end-user usage that includes
|
||||
`EEx.AssignsEngine` and other conveniences. Read
|
||||
`EEx.AssignsEngine` for examples.
|
||||
"""
|
||||
end
|
||||
|
||||
@@ -5,11 +5,11 @@ defmodule EEx.Tokenizer do
|
||||
Tokenizes the given char list or binary.
|
||||
It returns 4 different types of tokens as result:
|
||||
|
||||
* {:text, contents}
|
||||
* {:expr, line, marker, contents}
|
||||
* {:start_expr, line, marker, contents}
|
||||
* {:middle_expr, line, marker, contents}
|
||||
* {:end_expr, line, marker, contents}
|
||||
* `{:text, contents}`
|
||||
* `{:expr, line, marker, contents}`
|
||||
* `{:start_expr, line, marker, contents}`
|
||||
* `{:middle_expr, line, marker, contents}`
|
||||
* `{:end_expr, line, marker, contents}`
|
||||
|
||||
"""
|
||||
def tokenize(bin, line) when is_binary(bin) do
|
||||
|
||||
+23
-13
@@ -2,7 +2,7 @@ Code.require_file "test_helper.exs", __DIR__
|
||||
|
||||
require EEx
|
||||
|
||||
defmodule EExText.Compiled do
|
||||
defmodule EExTest.Compiled do
|
||||
def before_compile do
|
||||
fill_in_stacktrace
|
||||
{__ENV__.line, hd(tl(System.stacktrace))}
|
||||
@@ -12,6 +12,10 @@ defmodule EExText.Compiled do
|
||||
|
||||
filename = Path.join(__DIR__, "fixtures/eex_template_with_bindings.eex")
|
||||
EEx.function_from_file :defp, :private_file_sample, filename, [:bar]
|
||||
|
||||
filename = Path.join(__DIR__, "fixtures/eex_template_with_bindings.eex")
|
||||
EEx.function_from_file :def, :public_file_sample, filename, [:bar]
|
||||
|
||||
def file_sample(arg), do: private_file_sample(arg)
|
||||
|
||||
def after_compile do
|
||||
@@ -319,39 +323,45 @@ foo
|
||||
end
|
||||
end
|
||||
|
||||
test "sets external resource attribute" do
|
||||
assert EExTest.Compiled.__info__(:attributes)[:external_resource] ==
|
||||
[Path.join(__DIR__, "fixtures/eex_template_with_bindings.eex")]
|
||||
end
|
||||
|
||||
test "defined from string" do
|
||||
assert EExText.Compiled.string_sample(1, 2) == "3"
|
||||
assert EExTest.Compiled.string_sample(1, 2) == "3"
|
||||
end
|
||||
|
||||
test "defined from file" do
|
||||
assert EExText.Compiled.file_sample(1) == "foo 1\n"
|
||||
assert EExTest.Compiled.file_sample(1) == "foo 1\n"
|
||||
assert EExTest.Compiled.public_file_sample(1) == "foo 1\n"
|
||||
end
|
||||
|
||||
test "defined from file do not affect backtrace" do
|
||||
assert EExText.Compiled.before_compile ==
|
||||
assert EExTest.Compiled.before_compile ==
|
||||
{8,
|
||||
{EExText.Compiled,
|
||||
{EExTest.Compiled,
|
||||
:before_compile,
|
||||
0,
|
||||
[file: to_char_list(Path.relative_to_cwd(__ENV__.file)), line: 7]
|
||||
}
|
||||
}
|
||||
|
||||
assert EExText.Compiled.after_compile ==
|
||||
{19,
|
||||
{EExText.Compiled,
|
||||
assert EExTest.Compiled.after_compile ==
|
||||
{23,
|
||||
{EExTest.Compiled,
|
||||
:after_compile,
|
||||
0,
|
||||
[file: to_char_list(Path.relative_to_cwd(__ENV__.file)), line: 18]
|
||||
[file: to_char_list(Path.relative_to_cwd(__ENV__.file)), line: 22]
|
||||
}
|
||||
}
|
||||
|
||||
assert EExText.Compiled.unknown ==
|
||||
{25,
|
||||
{EExText.Compiled,
|
||||
assert EExTest.Compiled.unknown ==
|
||||
{29,
|
||||
{EExTest.Compiled,
|
||||
:unknown,
|
||||
0,
|
||||
[file: 'unknown', line: 24]
|
||||
[file: 'unknown', line: 28]
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
+102
-20
@@ -1,13 +1,14 @@
|
||||
import Kernel, except: [access: 2]
|
||||
|
||||
defprotocol Access do
|
||||
@moduledoc """
|
||||
The Access protocol is the underlying protocol invoked
|
||||
when the brackets syntax is used. For instance, `foo[bar]`
|
||||
is translated to `access foo, bar` which, by default,
|
||||
invokes the `Access.access` protocol.
|
||||
The Access protocol is used by `foo[bar]` and also
|
||||
empowers the nested update functions in Kernel.
|
||||
|
||||
This protocol is implemented by default for Lists, Maps
|
||||
For instance, `foo[bar]` translates `Access.get(foo, bar)`.
|
||||
`Kernel.get_in/2`, `Kernel.put_in/3`, `Kernel.update_in/3` and
|
||||
`Kernel.get_and_update_in/3` are also all powered by the Access
|
||||
protocol.
|
||||
|
||||
This protocol is implemented by default for keywords, maps
|
||||
and dictionary like types:
|
||||
|
||||
iex> keywords = [a: 1, b: 2]
|
||||
@@ -22,46 +23,127 @@ defprotocol Access do
|
||||
iex> star_ratings[1.5]
|
||||
"★☆"
|
||||
|
||||
The key access must be implemented using the `===` operator.
|
||||
This protocol is limited and is implemented only for the
|
||||
following built-in types: keywords, records and functions.
|
||||
The key comparison must be implemented using the `===` operator.
|
||||
"""
|
||||
|
||||
@doc """
|
||||
Receives the element being accessed and the access item.
|
||||
Accesses the given key in the container.
|
||||
"""
|
||||
def access(container, key)
|
||||
@spec get(t, term) :: t
|
||||
def get(container, key)
|
||||
|
||||
@doc """
|
||||
Gets a value and updates the given `key` in one pass.
|
||||
|
||||
The function must receive the value for the given `key`
|
||||
(or `nil` if the key doesn't exist in `container`) and
|
||||
the function must return a tuple containing the `get`
|
||||
value and the new value to be stored in the `container`.
|
||||
"""
|
||||
@spec get_and_update(t, term, (term -> {get, term})) :: {get, t} when get: var
|
||||
def get_and_update(container, key, fun)
|
||||
end
|
||||
|
||||
defimpl Access, for: List do
|
||||
def access(dict, key) when is_atom(key) do
|
||||
def get(dict, key) when is_atom(key) do
|
||||
case :lists.keyfind(key, 1, dict) do
|
||||
{^key, value} -> value
|
||||
false -> nil
|
||||
end
|
||||
end
|
||||
|
||||
def access(_dict, key) do
|
||||
raise ArgumentError, "the access protocol for lists expect the key to be an atom, got: #{inspect key}"
|
||||
def get(_dict, key) do
|
||||
raise ArgumentError,
|
||||
"the access protocol for lists expect the key to be an atom, got: #{inspect key}"
|
||||
end
|
||||
|
||||
def get_and_update(dict, key, fun) when is_atom(key) do
|
||||
get_and_update(dict, [], key, fun)
|
||||
end
|
||||
|
||||
defp get_and_update([{key, value}|t], acc, key, fun) do
|
||||
{get, update} = fun.(value)
|
||||
{get, :lists.reverse(acc, [{key, update}|t])}
|
||||
end
|
||||
|
||||
defp get_and_update([h|t], acc, key, fun) do
|
||||
get_and_update(t, [h|acc], key, fun)
|
||||
end
|
||||
|
||||
defp get_and_update([], acc, key, fun) do
|
||||
{get, update} = fun.(nil)
|
||||
{get, [{key, update}|:lists.reverse(acc)]}
|
||||
end
|
||||
end
|
||||
|
||||
defimpl Access, for: Map do
|
||||
def access(map, key) do
|
||||
def get(map, key) do
|
||||
case :maps.find(key, map) do
|
||||
{:ok, value} -> value
|
||||
:error -> nil
|
||||
end
|
||||
end
|
||||
|
||||
def get_and_update(map, key, fun) do
|
||||
value =
|
||||
case :maps.find(key, map) do
|
||||
{:ok, value} -> value
|
||||
:error -> nil
|
||||
end
|
||||
|
||||
{get, update} = fun.(value)
|
||||
{get, :maps.put(key, update, map)}
|
||||
end
|
||||
|
||||
def get!(%{} = map, key) do
|
||||
case :maps.find(key, map) do
|
||||
{:ok, value} -> value
|
||||
:error -> raise KeyError, key: key, term: map
|
||||
end
|
||||
end
|
||||
|
||||
def get!(other, key) do
|
||||
raise ArgumentError,
|
||||
"could not get key #{inspect key}. Expected map/struct, got: #{inspect other}"
|
||||
end
|
||||
|
||||
def get_and_update!(%{} = map, key, fun) do
|
||||
case :maps.find(key, map) do
|
||||
{:ok, value} ->
|
||||
{get, update} = fun.(value)
|
||||
{get, :maps.put(key, update, map)}
|
||||
:error ->
|
||||
raise KeyError, key: key, term: map
|
||||
end
|
||||
end
|
||||
|
||||
def get_and_update!(other, key, _fun) do
|
||||
raise ArgumentError,
|
||||
"could not update key #{inspect key}. Expected map/struct, got: #{inspect other}"
|
||||
end
|
||||
end
|
||||
|
||||
defimpl Access, for: Atom do
|
||||
def access(nil, _) do
|
||||
def get(nil, _) do
|
||||
nil
|
||||
end
|
||||
|
||||
def access(atom, _) do
|
||||
raise "The access protocol can only be invoked for atoms at " <>
|
||||
"compilation time, tried to invoke it for #{inspect atom}"
|
||||
def get(atom, _) do
|
||||
undefined(atom)
|
||||
end
|
||||
|
||||
def get_and_update(nil, _, fun) do
|
||||
fun.(nil)
|
||||
end
|
||||
|
||||
def get_and_update(atom, _key, _fun) do
|
||||
undefined(atom)
|
||||
end
|
||||
|
||||
defp undefined(atom) do
|
||||
raise Protocol.UndefinedError,
|
||||
protocol: @protocol,
|
||||
value: atom,
|
||||
description: "only the nil atom is supported"
|
||||
end
|
||||
end
|
||||
|
||||
+15
-13
@@ -3,8 +3,8 @@ defmodule Agent do
|
||||
Agents are a simple abstraction around state.
|
||||
|
||||
Often in Elixir there is a need to share or store state that
|
||||
must be accessed from different processes or by a same process
|
||||
in different points in time.
|
||||
must be accessed from different processes or by the same process
|
||||
at different points in time.
|
||||
|
||||
The Agent module provides a basic server implementation that
|
||||
allows state to be retrieved and updated via a simple API.
|
||||
@@ -35,12 +35,12 @@ defmodule Agent do
|
||||
end
|
||||
end
|
||||
|
||||
Note that agents still provide a segregation in between the
|
||||
Note that agents still provide a segregation between the
|
||||
client and server APIs, as seen in GenServers. In particular,
|
||||
all code inside the function passed to the agent is executed
|
||||
by the agent. This distinction is important because you may
|
||||
want to avoid expensive operations inside the agent, as it will
|
||||
effectively block the agent until the request is fullfilled.
|
||||
effectively block the agent until the request is fulfilled.
|
||||
|
||||
Consider these two examples:
|
||||
|
||||
@@ -60,7 +60,7 @@ defmodule Agent do
|
||||
sent to the client cheaply or large enough to require processing on
|
||||
the server (or at least some initial processing).
|
||||
|
||||
## Name registration
|
||||
## Name Registration
|
||||
|
||||
An Agent is bound to the same name registration rules as GenServers.
|
||||
Read more about it in the `GenServer` docs.
|
||||
@@ -68,13 +68,13 @@ defmodule Agent do
|
||||
## A word on distributed agents
|
||||
|
||||
It is important to consider the limitations of distributed agents. Agents
|
||||
work by sending anonymous functions in between the caller and the agent.
|
||||
work by sending anonymous functions between the caller and the agent.
|
||||
In a distributed setup with multiple nodes, agents only work if the caller
|
||||
(client) and the agent have the same version of a given module.
|
||||
|
||||
This setup may exhibit issues when doing "rolling upgrades". By rolling
|
||||
upgrades we mean the following situation: you wish to deploy a new version of
|
||||
your software by *shutting down* some of your nodes and replacing them by
|
||||
your software by *shutting down* some of your nodes and replacing them with
|
||||
nodes running a new version of the software. In this setup, part of your
|
||||
environment will have one version of a given module and the other part
|
||||
another version (the newer one) of the same module; this may cause agents to
|
||||
@@ -122,20 +122,22 @@ defmodule Agent do
|
||||
## Options
|
||||
|
||||
The `:name` option is used for registration as described in the module
|
||||
documentation. If the `:timeout` option is present, the agent is allowed to
|
||||
spend at most the given amount of milliseconds on initialization or it will
|
||||
be terminated and the start function will return `{:error, :timeout}`.
|
||||
documentation.
|
||||
|
||||
If the `:timeout` option is present, the agent is allowed to spend at most
|
||||
the given number of milliseconds on initialization or it will be terminated
|
||||
and the start function will return `{:error, :timeout}`.
|
||||
|
||||
If the `:debug` option is present, the corresponding function in the
|
||||
[`:sys` module](http://www.erlang.org/doc/man/sys.html) will be invoked.
|
||||
|
||||
If the `:spawn_opt` option is present, its value will be passed as options
|
||||
to the underlying process as in `Process.spawn/3`.
|
||||
to the underlying process as in `Process.spawn/4`.
|
||||
|
||||
## Return values
|
||||
|
||||
If the server is successfully created and initialized, the function returns
|
||||
`{:ok, pid}`, where pid is the pid of the server. If there already exists
|
||||
`{:ok, pid}`, where `pid` is the pid of the server. If there already exists
|
||||
an agent with the specified name, the function returns
|
||||
`{:error, {:already_started, pid}}` with the pid of that process.
|
||||
|
||||
@@ -176,7 +178,7 @@ defmodule Agent do
|
||||
|
||||
The function `fun` is sent to the `agent` which invokes the function
|
||||
passing the agent state. The function must return a tuple with two
|
||||
elements, the first being the value to return (i.e. the get value)
|
||||
elements, the first being the value to return (i.e. the `get` value)
|
||||
and the second one is the new state.
|
||||
|
||||
A timeout can also be specified (it has a default value of 5000).
|
||||
|
||||
@@ -44,7 +44,7 @@ defmodule Agent.Server do
|
||||
# There is a race condition if the agent is
|
||||
# restarted too fast and it is registered.
|
||||
try do
|
||||
self |> Process.info(:registered_name) |> elem(1) |> Process.unregister
|
||||
self |> :erlang.process_info(:registered_name) |> elem(1) |> Process.unregister
|
||||
rescue
|
||||
_ -> :ok
|
||||
end
|
||||
|
||||
@@ -13,7 +13,7 @@ defmodule Application do
|
||||
|
||||
In Elixir, Mix is responsible for compiling your source code and
|
||||
generating your application `.app` file. Furthermore, Mix is also
|
||||
responsible for configuring, starting and stoping your application
|
||||
responsible for configuring, starting and stopping your application
|
||||
and its dependencies. For this reason, this documentation will focus
|
||||
on the remaining aspects of your application: the application environment,
|
||||
and the application callback module.
|
||||
@@ -48,7 +48,7 @@ defmodule Application do
|
||||
developers to configure the environment of their dependencies.
|
||||
|
||||
Keep in mind that each application is responsible for its environment.
|
||||
Do not use the fucntions in this module for directly access or modify
|
||||
Do not use the functions in this module for directly access or modify
|
||||
the environment of other application (as it may lead to inconsistent
|
||||
data in the application environment).
|
||||
|
||||
@@ -83,8 +83,8 @@ defmodule Application do
|
||||
setup where applications takeover and failovers are configured. This particular
|
||||
aspect of applications can be read with more detail in the OTP documentation:
|
||||
|
||||
* http://www.erlang.org/doc/man/application.html
|
||||
* http://www.erlang.org/doc/design_principles/applications.html
|
||||
* http://www.erlang.org/doc/man/application.html
|
||||
* http://www.erlang.org/doc/design_principles/applications.html
|
||||
|
||||
A developer may also implement the `stop/1` callback (automatically defined
|
||||
by `use Application`) which does any application cleanup. It receives the
|
||||
@@ -152,9 +152,8 @@ defmodule Application do
|
||||
|
||||
## Options
|
||||
|
||||
* `:timeout` - the timeout for the change (defaults to 5000ms);
|
||||
|
||||
* `:persistent` - persists the given value on application load and reloads;
|
||||
* `:timeout` - the timeout for the change (defaults to 5000ms)
|
||||
* `:persistent` - persists the given value on application load and reloads
|
||||
|
||||
If `put_env/4` is called before the application is loaded, the application
|
||||
environment values specified in the `.app` file will override the ones
|
||||
@@ -223,13 +222,16 @@ defmodule Application do
|
||||
|
||||
The `type` argument specifies the type of the application:
|
||||
|
||||
* `:permanent` - if `app` terminates, all other applications and the entire
|
||||
node are also terminated;
|
||||
* `:transient` - if `app` terminates with `:normal` reason, it is reported
|
||||
but no other applications are terminated. If a transient application terminates
|
||||
abnormally, all other applications and the entire node are also terminated;
|
||||
* `:temporary` - if `app` termiantes, it is reported but no other applications
|
||||
are terminated (the default);
|
||||
* `:permanent` - if `app` terminates, all other applications and the entire
|
||||
node are also terminated.
|
||||
|
||||
* `:transient` - if `app` terminates with `:normal` reason, it is reported
|
||||
but no other applications are terminated. If a transient application
|
||||
terminates abnormally, all other applications and the entire node are
|
||||
also terminated.
|
||||
|
||||
* `:temporary` - if `app` terminates, it is reported but no other
|
||||
applications are terminated (the default).
|
||||
|
||||
Note that it is always possible to stop an application explicitly by calling
|
||||
`stop/1`. Regardless of the type of the application, no other applications will
|
||||
|
||||
@@ -1,16 +0,0 @@
|
||||
defmodule Application.Behaviour do
|
||||
@moduledoc false
|
||||
|
||||
defmacro __using__(_) do
|
||||
quote location: :keep do
|
||||
@behaviour :application
|
||||
|
||||
@doc false
|
||||
def stop(_state) do
|
||||
:ok
|
||||
end
|
||||
|
||||
defoverridable [stop: 1]
|
||||
end
|
||||
end
|
||||
end
|
||||
+226
-98
@@ -5,8 +5,91 @@ defmodule Base do
|
||||
This module provides data encoding and decoding functions
|
||||
according to [RFC 4648](http://tools.ietf.org/html/rfc4648).
|
||||
|
||||
This document defines the commonly used base 64, base 32, and base
|
||||
16 encoding schemes.
|
||||
This document defines the commonly used base 16, base 32, and base
|
||||
64 encoding schemes.
|
||||
|
||||
## Base 16 alphabet
|
||||
|
||||
| Value | Encoding | Value | Encoding | Value | Encoding | Value | Encoding |
|
||||
|------:|---------:|------:|---------:|------:|---------:|------:|---------:|
|
||||
| 0| 0| 4| 4| 8| 8| 12| C|
|
||||
| 1| 1| 5| 5| 9| 9| 13| D|
|
||||
| 2| 2| 6| 6| 10| A| 14| E|
|
||||
| 3| 3| 7| 7| 11| B| 15| F|
|
||||
|
||||
## Base 32 alphabet
|
||||
|
||||
| Value | Encoding | Value | Encoding | Value | Encoding | Value | Encoding |
|
||||
|------:|---------:|------:|---------:|------:|---------:|------:|---------:|
|
||||
| 0| A| 9| J| 18| S| 27| 3|
|
||||
| 1| B| 10| K| 19| T| 28| 4|
|
||||
| 2| C| 11| L| 20| U| 29| 5|
|
||||
| 3| D| 12| M| 21| V| 30| 6|
|
||||
| 4| E| 13| N| 22| W| 31| 7|
|
||||
| 5| F| 14| O| 23| X| | |
|
||||
| 6| G| 15| P| 24| Y| (pad)| =|
|
||||
| 7| H| 16| Q| 25| Z| | |
|
||||
| 8| I| 17| R| 26| 2| | |
|
||||
|
||||
|
||||
## Base 32 (extended hex) alphabet
|
||||
|
||||
| Value | Encoding | Value | Encoding | Value | Encoding | Value | Encoding |
|
||||
|------:|---------:|------:|---------:|------:|---------:|------:|---------:|
|
||||
| 0| 0| 9| 9| 18| I| 27| R|
|
||||
| 1| 1| 10| A| 19| J| 28| S|
|
||||
| 2| 2| 11| B| 20| K| 29| T|
|
||||
| 3| 3| 12| C| 21| L| 30| U|
|
||||
| 4| 4| 13| D| 22| M| 31| V|
|
||||
| 5| 5| 14| E| 23| N| | |
|
||||
| 6| 6| 15| F| 24| O| (pad)| =|
|
||||
| 7| 7| 16| G| 25| P| | |
|
||||
| 8| 8| 17| H| 26| Q| | |
|
||||
|
||||
## Base 64 alphabet
|
||||
|
||||
| Value | Encoding | Value | Encoding | Value | Encoding | Value | Encoding |
|
||||
|------:|---------:|------:|---------:|------:|---------:|------:|---------:|
|
||||
| 0| A| 17| R| 34| i| 51| z|
|
||||
| 1| B| 18| S| 35| j| 52| 0|
|
||||
| 2| C| 19| T| 36| k| 53| 1|
|
||||
| 3| D| 20| U| 37| l| 54| 2|
|
||||
| 4| E| 21| V| 38| m| 55| 3|
|
||||
| 5| F| 22| W| 39| n| 56| 4|
|
||||
| 6| G| 23| X| 40| o| 57| 5|
|
||||
| 7| H| 24| Y| 41| p| 58| 6|
|
||||
| 8| I| 25| Z| 42| q| 59| 7|
|
||||
| 9| J| 26| a| 43| r| 60| 8|
|
||||
| 10| K| 27| b| 44| s| 61| 9|
|
||||
| 11| L| 28| c| 45| t| 62| +|
|
||||
| 12| M| 29| d| 46| u| 63| /|
|
||||
| 13| N| 30| e| 47| v| | |
|
||||
| 14| O| 31| f| 48| w| (pad)| =|
|
||||
| 15| P| 32| g| 49| x| | |
|
||||
| 16| Q| 33| h| 50| y| | |
|
||||
|
||||
## Base 64 (URL and filename safe) alphabet
|
||||
|
||||
| Value | Encoding | Value | Encoding | Value | Encoding | Value | Encoding |
|
||||
|------:|---------:|------:|---------:|------:|---------:|------:|---------:|
|
||||
| 0| A| 17| R| 34| i| 51| z|
|
||||
| 1| B| 18| S| 35| j| 52| 0|
|
||||
| 2| C| 19| T| 36| k| 53| 1|
|
||||
| 3| D| 20| U| 37| l| 54| 2|
|
||||
| 4| E| 21| V| 38| m| 55| 3|
|
||||
| 5| F| 22| W| 39| n| 56| 4|
|
||||
| 6| G| 23| X| 40| o| 57| 5|
|
||||
| 7| H| 24| Y| 41| p| 58| 6|
|
||||
| 8| I| 25| Z| 42| q| 59| 7|
|
||||
| 9| J| 26| a| 43| r| 60| 8|
|
||||
| 10| K| 27| b| 44| s| 61| 9|
|
||||
| 11| L| 28| c| 45| t| 62| -|
|
||||
| 12| M| 29| d| 46| u| 63| _|
|
||||
| 13| N| 30| e| 47| v| | |
|
||||
| 14| O| 31| f| 48| w| (pad)| =|
|
||||
| 15| P| 32| g| 49| x| | |
|
||||
| 16| Q| 33| h| 50| y| | |
|
||||
|
||||
"""
|
||||
|
||||
b16_alphabet = Enum.with_index '0123456789ABCDEF'
|
||||
@@ -29,41 +112,82 @@ defmodule Base do
|
||||
end
|
||||
end
|
||||
|
||||
defp encode_case(:upper, func),
|
||||
do: func
|
||||
defp encode_case(:lower, func),
|
||||
do: &to_lower(func.(&1))
|
||||
|
||||
defp decode_case(:upper, func),
|
||||
do: func
|
||||
defp decode_case(:lower, func),
|
||||
do: &func.(from_lower(&1))
|
||||
defp decode_case(:mixed, func),
|
||||
do: &func.(from_mixed(&1))
|
||||
|
||||
defp to_lower(char) when char in ?A..?Z,
|
||||
do: char + (?a - ?A)
|
||||
defp to_lower(char),
|
||||
do: char
|
||||
|
||||
defp from_lower(char) when char in ?a..?z,
|
||||
do: char - (?a - ?A)
|
||||
defp from_lower(char) when not char in ?A..?Z,
|
||||
do: char
|
||||
defp from_lower(char),
|
||||
do: raise(ArgumentError, "non-alphabet digit found: #{<<char>>}")
|
||||
|
||||
defp from_mixed(char) when char in ?a..?z,
|
||||
do: char - (?a - ?A)
|
||||
defp from_mixed(char),
|
||||
do: char
|
||||
|
||||
@doc """
|
||||
Encodes a binary string into a base 16 encoded string.
|
||||
|
||||
Accepts an atom `:upper` (default) for encoding to upper case characters or
|
||||
`:lower` for lower case characters.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Base.encode16("foobar")
|
||||
"666F6F626172"
|
||||
|
||||
iex> Base.encode16("foobar", case: :lower)
|
||||
"666f6f626172"
|
||||
|
||||
"""
|
||||
@spec encode16(binary) :: binary
|
||||
def encode16(data) when is_binary(data) do
|
||||
do_encode16(data, &enc16/1)
|
||||
@spec encode16(binary, Keyword.t) :: binary
|
||||
def encode16(data, opts \\ []) when is_binary(data) do
|
||||
case = Keyword.get(opts, :case, :upper)
|
||||
do_encode16(data, encode_case(case, &enc16/1))
|
||||
end
|
||||
|
||||
|
||||
@doc """
|
||||
Decodes a base 16 encoded string into a binary string.
|
||||
|
||||
The following alphabet is used both for encoding and decoding:
|
||||
|
||||
| Value | Encoding | Value | Encoding | Value | Encoding | Value | Encoding |
|
||||
|------:|---------:|------:|---------:|------:|---------:|------:|---------:|
|
||||
| 0| 0| 4| 4| 8| 8| 12| C|
|
||||
| 1| 1| 5| 5| 9| 9| 13| D|
|
||||
| 2| 2| 6| 6| 10| A| 14| E|
|
||||
| 3| 3| 7| 7| 11| B| 15| F|
|
||||
Accepts an atom `:upper` (default) for decoding from upper case characters or
|
||||
`:lower` for lower case characters. `:mixed` can be given for mixed case
|
||||
characters.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Base.decode16("666F6F626172")
|
||||
{:ok, "foobar"}
|
||||
|
||||
iex> Base.decode16("666f6f626172", case: :lower)
|
||||
{:ok, "foobar"}
|
||||
|
||||
iex> Base.decode16("666f6F626172", case: :mixed)
|
||||
{:ok, "foobar"}
|
||||
|
||||
"""
|
||||
@spec decode16(binary) :: {:ok, binary} | :error
|
||||
def decode16(string) when is_binary(string) do
|
||||
{:ok, decode16!(string)}
|
||||
@spec decode16(binary, Keyword.t) :: {:ok, binary} | :error
|
||||
def decode16(string, opts \\ []) when is_binary(string) do
|
||||
case = Keyword.get(opts, :case, :upper)
|
||||
{:ok, do_decode16(string, decode_case(case, &dec16/1))}
|
||||
rescue
|
||||
ArgumentError -> :error
|
||||
end
|
||||
@@ -71,6 +195,10 @@ defmodule Base do
|
||||
@doc """
|
||||
Decodes a base 16 encoded string into a binary string.
|
||||
|
||||
Accepts an atom `:upper` (default) for decoding from upper case characters or
|
||||
`:lower` for lower case characters. `:mixed` can be given for mixed case
|
||||
characters.
|
||||
|
||||
An `ArgumentError` exception is raised if the padding is incorrect or
|
||||
a non-alphabet character is present in the string.
|
||||
|
||||
@@ -79,10 +207,18 @@ defmodule Base do
|
||||
iex> Base.decode16!("666F6F626172")
|
||||
"foobar"
|
||||
|
||||
iex> Base.decode16!("666f6f626172", case: :lower)
|
||||
"foobar"
|
||||
|
||||
iex> Base.decode16!("666f6F626172", case: :mixed)
|
||||
"foobar"
|
||||
|
||||
"""
|
||||
@spec decode16!(binary) :: binary
|
||||
def decode16!(string) when is_binary(string) do
|
||||
do_decode16(string, &dec16/1)
|
||||
@spec decode16!(binary, Keyword.t) :: binary
|
||||
def decode16!(string, opts \\ []) when is_binary(string) do
|
||||
case = Keyword.get(opts, :case, :upper)
|
||||
do_decode16(string, decode_case(case, &dec16/1))
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -102,28 +238,6 @@ defmodule Base do
|
||||
@doc """
|
||||
Decodes a base 64 encoded string into a binary string.
|
||||
|
||||
The following alphabet is used both for encoding and decoding:
|
||||
|
||||
| Value | Encoding | Value | Encoding | Value | Encoding | Value | Encoding |
|
||||
|------:|---------:|------:|---------:|------:|---------:|------:|---------:|
|
||||
| 0| A| 17| R| 34| i| 51| z|
|
||||
| 1| B| 18| S| 35| j| 52| 0|
|
||||
| 2| C| 19| T| 36| k| 53| 1|
|
||||
| 3| D| 20| U| 37| l| 54| 2|
|
||||
| 4| E| 21| V| 38| m| 55| 3|
|
||||
| 5| F| 22| W| 39| n| 56| 4|
|
||||
| 6| G| 23| X| 40| o| 57| 5|
|
||||
| 7| H| 24| Y| 41| p| 58| 6|
|
||||
| 8| I| 25| Z| 42| q| 59| 7|
|
||||
| 9| J| 26| a| 43| r| 60| 8|
|
||||
| 10| K| 27| b| 44| s| 61| 9|
|
||||
| 11| L| 28| c| 45| t| 62| +|
|
||||
| 12| M| 29| d| 46| u| 63| /|
|
||||
| 13| N| 30| e| 47| v| | |
|
||||
| 14| O| 31| f| 48| w| (pad)| =|
|
||||
| 15| P| 32| g| 49| x| | |
|
||||
| 16| Q| 33| h| 50| y| | |
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Base.decode64("Zm9vYmFy")
|
||||
@@ -175,28 +289,6 @@ defmodule Base do
|
||||
Decodes a base 64 encoded string with URL and filename safe alphabet
|
||||
into a binary string.
|
||||
|
||||
The following alphabet is used both for encoding and decoding:
|
||||
|
||||
| Value | Encoding | Value | Encoding | Value | Encoding | Value | Encoding |
|
||||
|------:|---------:|------:|---------:|------:|---------:|------:|---------:|
|
||||
| 0| A| 17| R| 34| i| 51| z|
|
||||
| 1| B| 18| S| 35| j| 52| 0|
|
||||
| 2| C| 19| T| 36| k| 53| 1|
|
||||
| 3| D| 20| U| 37| l| 54| 2|
|
||||
| 4| E| 21| V| 38| m| 55| 3|
|
||||
| 5| F| 22| W| 39| n| 56| 4|
|
||||
| 6| G| 23| X| 40| o| 57| 5|
|
||||
| 7| H| 24| Y| 41| p| 58| 6|
|
||||
| 8| I| 25| Z| 42| q| 59| 7|
|
||||
| 9| J| 26| a| 43| r| 60| 8|
|
||||
| 10| K| 27| b| 44| s| 61| 9|
|
||||
| 11| L| 28| c| 45| t| 62| -|
|
||||
| 12| M| 29| d| 46| u| 63| _|
|
||||
| 13| N| 30| e| 47| v| | |
|
||||
| 14| O| 31| f| 48| w| (pad)| =|
|
||||
| 15| P| 32| g| 49| x| | |
|
||||
| 16| Q| 33| h| 50| y| | |
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Base.url_decode64("_3_-_A==")
|
||||
@@ -231,43 +323,49 @@ defmodule Base do
|
||||
@doc """
|
||||
Encodes a binary string into a base 32 encoded string.
|
||||
|
||||
Accepts an atom `:upper` (default) for encoding to upper case characters or
|
||||
`:lower` for lower case characters.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Base.encode32("foobar")
|
||||
"MZXW6YTBOI======"
|
||||
|
||||
iex> Base.encode32("foobar", case: :lower)
|
||||
"mzxw6ytboi======"
|
||||
|
||||
"""
|
||||
@spec encode32(binary) :: binary
|
||||
def encode32(data) when is_binary(data) do
|
||||
do_encode32(data, &enc32/1)
|
||||
@spec encode32(binary, Keyword.t) :: binary
|
||||
def encode32(data, opts \\ []) when is_binary(data) do
|
||||
case = Keyword.get(opts, :case, :upper)
|
||||
do_encode32(data, encode_case(case, &enc32/1))
|
||||
end
|
||||
|
||||
@doc """
|
||||
Decodes a base 32 encoded string into a binary string.
|
||||
|
||||
The following alphabet is used both for encoding and decoding:
|
||||
|
||||
| Value | Encoding | Value | Encoding | Value | Encoding | Value | Encoding |
|
||||
|------:|---------:|------:|---------:|------:|---------:|------:|---------:|
|
||||
| 0| A| 9| J| 18| S| 27| 3|
|
||||
| 1| B| 10| K| 19| T| 28| 4|
|
||||
| 2| C| 11| L| 20| U| 29| 5|
|
||||
| 3| D| 12| M| 21| V| 30| 6|
|
||||
| 4| E| 13| N| 22| W| 31| 7|
|
||||
| 5| F| 14| O| 23| X| | |
|
||||
| 6| G| 15| P| 24| Y| (pad)| =|
|
||||
| 7| H| 16| Q| 25| Z| | |
|
||||
| 8| I| 17| R| 26| 2| | |
|
||||
Accepts an atom `:upper` (default) for decoding from upper case characters or
|
||||
`:lower` for lower case characters. `:mixed` can be given for mixed case
|
||||
characters.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Base.decode32("MZXW6YTBOI======")
|
||||
{:ok, "foobar"}
|
||||
|
||||
iex> Base.decode32("mzxw6ytboi======", case: :lower)
|
||||
{:ok, "foobar"}
|
||||
|
||||
iex> Base.decode32("mzXW6ytBOi======", case: :mixed)
|
||||
{:ok, "foobar"}
|
||||
|
||||
"""
|
||||
@spec decode32(binary) :: {:ok, binary} | :error
|
||||
def decode32(string) do
|
||||
{:ok, do_decode32(string, &dec32/1)}
|
||||
@spec decode32(binary, Keyword.t) :: {:ok, binary} | :error
|
||||
def decode32(string, opts \\ []) do
|
||||
case = Keyword.get(opts, :case, :upper)
|
||||
{:ok, do_decode32(string, decode_case(case, &dec32/1))}
|
||||
rescue
|
||||
ArgumentError -> :error
|
||||
end
|
||||
@@ -275,6 +373,10 @@ defmodule Base do
|
||||
@doc """
|
||||
Decodes a base 32 encoded string into a binary string.
|
||||
|
||||
Accepts an atom `:upper` (default) for decoding from upper case characters or
|
||||
`:lower` for lower case characters. `:mixed` can be given for mixed case
|
||||
characters.
|
||||
|
||||
An `ArgumentError` exception is raised if the padding is incorrect or
|
||||
a non-alphabet character is present in the string.
|
||||
|
||||
@@ -283,54 +385,68 @@ defmodule Base do
|
||||
iex> Base.decode32!("MZXW6YTBOI======")
|
||||
"foobar"
|
||||
|
||||
iex> Base.decode32!("mzxw6ytboi======", case: :lower)
|
||||
"foobar"
|
||||
|
||||
iex> Base.decode32!("mzXW6ytBOi======", case: :mixed)
|
||||
"foobar"
|
||||
|
||||
"""
|
||||
@spec decode32!(binary) :: binary
|
||||
def decode32!(string) do
|
||||
do_decode32(string, &dec32/1)
|
||||
@spec decode32!(binary, Keyword.t) :: binary
|
||||
def decode32!(string, opts \\ []) do
|
||||
case = Keyword.get(opts, :case, :upper)
|
||||
do_decode32(string, decode_case(case, &dec32/1))
|
||||
end
|
||||
|
||||
@doc """
|
||||
Encodes a binary string into a base 32 encoded string with an
|
||||
extended hexadecimal alphabet.
|
||||
|
||||
Accepts an atom `:upper` (default) for encoding to upper case characters or
|
||||
`:lower` for lower case characters.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Base.hex_encode32("foobar")
|
||||
"CPNMUOJ1E8======"
|
||||
|
||||
iex> Base.hex_encode32("foobar", case: :lower)
|
||||
"cpnmuoj1e8======"
|
||||
|
||||
"""
|
||||
@spec hex_encode32(binary) :: binary
|
||||
def hex_encode32(data) when is_binary(data) do
|
||||
do_encode32(data, &enc32hex/1)
|
||||
@spec hex_encode32(binary, Keyword.t) :: binary
|
||||
def hex_encode32(data, opts \\ []) when is_binary(data) do
|
||||
case = Keyword.get(opts, :case, :upper)
|
||||
do_encode32(data, encode_case(case, &enc32hex/1))
|
||||
end
|
||||
|
||||
@doc """
|
||||
Decodes a base 32 encoded string with extended hexadecimal alphabet
|
||||
into a binary string.
|
||||
|
||||
The following alphabet is used both for encoding and decoding:
|
||||
|
||||
| Value | Encoding | Value | Encoding | Value | Encoding | Value | Encoding |
|
||||
|------:|---------:|------:|---------:|------:|---------:|------:|---------:|
|
||||
| 0| 0| 9| 9| 18| I| 27| R|
|
||||
| 1| 1| 10| A| 19| J| 28| S|
|
||||
| 2| 2| 11| B| 20| K| 29| T|
|
||||
| 3| 3| 12| C| 21| L| 30| U|
|
||||
| 4| 4| 13| D| 22| M| 31| V|
|
||||
| 5| 5| 14| E| 23| N| | |
|
||||
| 6| 6| 15| F| 24| O| (pad)| =|
|
||||
| 7| 7| 16| G| 25| P| | |
|
||||
| 8| 8| 17| H| 26| Q| | |
|
||||
Accepts an atom `:upper` (default) for decoding from upper case characters or
|
||||
`:lower` for lower case characters. `:mixed` can be given for mixed case
|
||||
characters.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Base.hex_decode32("CPNMUOJ1E8======")
|
||||
{:ok, "foobar"}
|
||||
|
||||
iex> Base.hex_decode32("cpnmuoj1e8======", case: :lower)
|
||||
{:ok, "foobar"}
|
||||
|
||||
iex> Base.hex_decode32("cpnMuOJ1E8======", case: :mixed)
|
||||
{:ok, "foobar"}
|
||||
|
||||
"""
|
||||
@spec hex_decode32(binary) :: {:ok, binary} | :error
|
||||
def hex_decode32(string) when is_binary(string) do
|
||||
{:ok, do_decode32(string, &dec32hex/1)}
|
||||
@spec hex_decode32(binary, Keyword.t) :: {:ok, binary} | :error
|
||||
def hex_decode32(string, opts \\ []) when is_binary(string) do
|
||||
case = Keyword.get(opts, :case, :upper)
|
||||
{:ok, do_decode32(string, decode_case(case, &dec32hex/1))}
|
||||
rescue
|
||||
ArgumentError -> :error
|
||||
end
|
||||
@@ -339,6 +455,10 @@ defmodule Base do
|
||||
Decodes a base 32 encoded string with extended hexadecimal alphabet
|
||||
into a binary string.
|
||||
|
||||
Accepts an atom `:upper` (default) for decoding from upper case characters or
|
||||
`:lower` for lower case characters. `:mixed` can be given for mixed case
|
||||
characters.
|
||||
|
||||
An `ArgumentError` exception is raised if the padding is incorrect or
|
||||
a non-alphabet character is present in the string.
|
||||
|
||||
@@ -347,10 +467,18 @@ defmodule Base do
|
||||
iex> Base.hex_decode32!("CPNMUOJ1E8======")
|
||||
"foobar"
|
||||
|
||||
iex> Base.hex_decode32!("cpnmuoj1e8======", case: :lower)
|
||||
"foobar"
|
||||
|
||||
iex> Base.hex_decode32!("cpnMuOJ1E8======", case: :mixed)
|
||||
"foobar"
|
||||
|
||||
"""
|
||||
@spec hex_decode32!(binary) :: binary
|
||||
def hex_decode32!(string) when is_binary(string) do
|
||||
do_decode32(string, &dec32hex/1)
|
||||
@spec hex_decode32!(binary, Keyword.t) :: binary
|
||||
def hex_decode32!(string, opts \\ []) when is_binary(string) do
|
||||
case = Keyword.get(opts, :case, :upper)
|
||||
do_decode32(string, decode_case(case, &dec32hex/1))
|
||||
end
|
||||
|
||||
defp do_encode16(<<>>, _), do: <<>>
|
||||
|
||||
@@ -24,8 +24,8 @@ defmodule Bitwise do
|
||||
Allow a developer to use this module in their programs with
|
||||
the following options:
|
||||
|
||||
* `:only_operators` - Include only operators;
|
||||
* `:skip_operators` - Skip operators;
|
||||
* `:only_operators` - include only operators
|
||||
* `:skip_operators` - skip operators
|
||||
|
||||
"""
|
||||
defmacro __using__(options) do
|
||||
|
||||
+98
-26
@@ -59,21 +59,24 @@ defmodule Code do
|
||||
|
||||
Those options can be:
|
||||
|
||||
* `:file` - the file to be considered in the evaluation
|
||||
* `:line` - the line on which the script starts
|
||||
* `:delegate_locals_to` - delegate local calls to the given module,
|
||||
the default is to not delegate
|
||||
* `:file` - the file to be considered in the evaluation
|
||||
* `:line` - the line on which the script starts
|
||||
* `:delegate_locals_to` - delegate local calls to the given module,
|
||||
the default is to not delegate
|
||||
|
||||
Additionally, the following scope values can be configured:
|
||||
|
||||
* `:aliases` - a list of tuples with the alias and its target
|
||||
* `:requires` - a list of modules required
|
||||
* `:functions` - a list of tuples where the first element is a module
|
||||
and the second a list of imported function names and arity. The list
|
||||
of function names and arity must be sorted
|
||||
* `:macros` - a list of tuples where the first element is a module
|
||||
and the second a list of imported macro names and arity. The list
|
||||
of function names and arity must be sorted
|
||||
* `:aliases` - a list of tuples with the alias and its target
|
||||
|
||||
* `:requires` - a list of modules required
|
||||
|
||||
* `:functions` - a list of tuples where the first element is a module
|
||||
and the second a list of imported function names and arity; the list
|
||||
of function names and arity must be sorted
|
||||
|
||||
* `:macros` - a list of tuples where the first element is a module
|
||||
and the second a list of imported macro names and arity; the list
|
||||
of function names and arity must be sorted
|
||||
|
||||
Notice that setting any of the values above overrides Elixir's default
|
||||
values. For example, setting `:requires` to `[]`, will no longer
|
||||
@@ -198,13 +201,13 @@ defmodule Code do
|
||||
|
||||
## Options
|
||||
|
||||
* `:file` - The filename to be used in stacktraces
|
||||
and the file reported in the `__ENV__` variable.
|
||||
* `:file` - the filename to be used in stacktraces
|
||||
and the file reported in the `__ENV__` variable
|
||||
|
||||
* `:line` - The line reported in the `__ENV__` variable.
|
||||
* `:line` - the line reported in the `__ENV__` variable
|
||||
|
||||
* `:existing_atoms_only` - When `true`, raises an error
|
||||
when non-existing atoms are found by the tokenizer.
|
||||
* `:existing_atoms_only` - when `true`, raises an error
|
||||
when non-existing atoms are found by the tokenizer
|
||||
|
||||
## Macro.to_string/2
|
||||
|
||||
@@ -245,7 +248,7 @@ defmodule Code do
|
||||
"""
|
||||
def eval_file(file, relative_to \\ nil) do
|
||||
file = find_file(file, relative_to)
|
||||
eval_string File.read!(file), [], []
|
||||
eval_string File.read!(file), [], [file: file, line: 1]
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -308,6 +311,15 @@ defmodule Code do
|
||||
:elixir_code_server.call :compiler_options
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns a list with the available compiler options.
|
||||
|
||||
See `Code.compiler_options/1` for more info.
|
||||
"""
|
||||
def available_compiler_options do
|
||||
[:docs, :debug_info, :ignore_module_conflict, :warnings_as_errors]
|
||||
end
|
||||
|
||||
@doc """
|
||||
Sets compilation options.
|
||||
|
||||
@@ -315,20 +327,26 @@ defmodule Code do
|
||||
|
||||
Available options are:
|
||||
|
||||
* `:docs` - when `true`, retain documentation in the compiled module,
|
||||
`true` by default;
|
||||
* `:docs` - when `true`, retain documentation in the compiled module,
|
||||
`true` by default
|
||||
|
||||
* `:debug_info` - when `true`, retain debug information in the compiled module.
|
||||
This allows a developer to reconstruct the original source
|
||||
code, `false` by default;
|
||||
* `:debug_info` - when `true`, retain debug information in the compiled
|
||||
module; this allows a developer to reconstruct the original source
|
||||
code, `false` by default
|
||||
|
||||
* `:ignore_module_conflict` - when `true`, override modules that were already defined
|
||||
without raising errors, `false` by default;
|
||||
* `:ignore_module_conflict` - when `true`, override modules that were
|
||||
already defined without raising errors, `false` by default
|
||||
|
||||
* `:warnings_as_errors` - cause compilation to fail when warnings are generated;
|
||||
* `:warnings_as_errors` - cause compilation to fail when warnings are
|
||||
generated
|
||||
|
||||
"""
|
||||
def compiler_options(opts) do
|
||||
{opts, bad} = Keyword.split(opts, available_compiler_options)
|
||||
if bad != [] do
|
||||
bad = bad |> Keyword.keys |> Enum.join(", ")
|
||||
raise ArgumentError, message: "unknown compiler options: #{bad}"
|
||||
end
|
||||
:elixir_code_server.cast {:compiler_options, opts}
|
||||
end
|
||||
|
||||
@@ -448,6 +466,60 @@ defmodule Code do
|
||||
match?({:module, ^module}, ensure_compiled(module))
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns the docs for the given module.
|
||||
|
||||
When given a module name, it finds its BEAM code and reads the docs from it.
|
||||
|
||||
When given a path to a .beam file, it will load the docs directly from that
|
||||
file.
|
||||
|
||||
The return value depends on the `kind` value:
|
||||
|
||||
* `:docs` - list of all docstrings attached to functions and macros
|
||||
using the `@doc` attribute
|
||||
|
||||
* `:moduledoc` - tuple `{<line>, <doc>}` where `line` is the line on
|
||||
which module definition starts and `doc` is the string
|
||||
attached to the module using the `@moduledoc` attribute
|
||||
|
||||
* `:all` - a keyword list with both `:docs` and `:moduledoc`
|
||||
|
||||
"""
|
||||
def get_docs(module, kind) when is_atom(module) do
|
||||
case :code.get_object_code(module) do
|
||||
{_module, bin, _beam_path} ->
|
||||
do_get_docs(bin, kind)
|
||||
|
||||
:error -> nil
|
||||
end
|
||||
end
|
||||
|
||||
def get_docs(binpath, kind) when is_binary(binpath) do
|
||||
do_get_docs(String.to_char_list(binpath), kind)
|
||||
end
|
||||
|
||||
@docs_chunk 'ExDc'
|
||||
|
||||
defp do_get_docs(bin_or_path, kind) do
|
||||
case :beam_lib.chunks(bin_or_path, [@docs_chunk]) do
|
||||
{:ok, {_module, [{@docs_chunk, bin}]}} ->
|
||||
lookup_docs(:erlang.binary_to_term(bin), kind)
|
||||
|
||||
{:error, :beam_lib, {:missing_chunk, _, @docs_chunk}} -> nil
|
||||
end
|
||||
end
|
||||
|
||||
defp lookup_docs({:elixir_docs_v1, docs}, kind),
|
||||
do: do_lookup_docs(docs, kind)
|
||||
|
||||
# unsupported chunk version
|
||||
defp lookup_docs(_, _), do: nil
|
||||
|
||||
defp do_lookup_docs(docs, :all), do: docs
|
||||
defp do_lookup_docs(docs, kind) when kind in [:docs, :moduledoc],
|
||||
do: Keyword.get(docs, kind)
|
||||
|
||||
## Helpers
|
||||
|
||||
# Finds the file given the relative_to path.
|
||||
|
||||
+24
-24
@@ -40,7 +40,7 @@ defmodule Dict do
|
||||
For example:
|
||||
|
||||
defmodule MyDict do
|
||||
use Dict.Behaviour
|
||||
use Dict
|
||||
|
||||
# implement required functions (see below)
|
||||
# override default implementations if optimization
|
||||
@@ -49,11 +49,11 @@ defmodule Dict do
|
||||
|
||||
The client module must contain the following functions:
|
||||
|
||||
* `delete/2`
|
||||
* `fetch/2`
|
||||
* `put/3`
|
||||
* `reduce/3`
|
||||
* `size/1`
|
||||
* `delete/2`
|
||||
* `fetch/2`
|
||||
* `put/3`
|
||||
* `reduce/3`
|
||||
* `size/1`
|
||||
|
||||
All functions, except `reduce/3`, are required by the Dict behaviour.
|
||||
`reduce/3` must be implemtented as per the Enumerable protocol.
|
||||
@@ -61,24 +61,24 @@ defmodule Dict do
|
||||
Based on these functions, `Dict` generates default implementations
|
||||
for the following functions:
|
||||
|
||||
* `drop/2`
|
||||
* `equal?/2`
|
||||
* `fetch!/2`
|
||||
* `get/2`
|
||||
* `get/3`
|
||||
* `has_key?/2`
|
||||
* `keys/1`
|
||||
* `merge/2`
|
||||
* `merge/3`
|
||||
* `pop/2`
|
||||
* `pop/3`
|
||||
* `put_new/3`
|
||||
* `split/2`
|
||||
* `take/2`
|
||||
* `to_list/1`
|
||||
* `update/4`
|
||||
* `update!/3`
|
||||
* `values/1`
|
||||
* `drop/2`
|
||||
* `equal?/2`
|
||||
* `fetch!/2`
|
||||
* `get/2`
|
||||
* `get/3`
|
||||
* `has_key?/2`
|
||||
* `keys/1`
|
||||
* `merge/2`
|
||||
* `merge/3`
|
||||
* `pop/2`
|
||||
* `pop/3`
|
||||
* `put_new/3`
|
||||
* `split/2`
|
||||
* `take/2`
|
||||
* `to_list/1`
|
||||
* `update/4`
|
||||
* `update!/3`
|
||||
* `values/1`
|
||||
|
||||
All of these functions are defined as overridable, so you can provide
|
||||
your own implementation if needed.
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
defmodule Dict.Behaviour do
|
||||
@moduledoc false
|
||||
|
||||
defmacro __using__(_) do
|
||||
quote do
|
||||
use Dict
|
||||
end
|
||||
end
|
||||
end
|
||||
+51
-32
@@ -39,9 +39,9 @@ defprotocol Enumerable do
|
||||
|
||||
It must be a tagged tuple with one of the following "tags":
|
||||
|
||||
* `:cont` - the enumeration should continue
|
||||
* `:halt` - the enumeration should halt immediately
|
||||
* `:suspend` - the enumeration should be suspended immediately
|
||||
* `:cont` - the enumeration should continue
|
||||
* `:halt` - the enumeration should halt immediately
|
||||
* `:suspend` - the enumeration should be suspended immediately
|
||||
|
||||
Depending on the accumulator value, the result returned by
|
||||
`Enumerable.reduce/3` will change. Please check the `result`
|
||||
@@ -937,13 +937,13 @@ defmodule Enum do
|
||||
|
||||
def join(collection, joiner) when is_binary(joiner) do
|
||||
reduced = reduce(collection, :first, fn
|
||||
entry, :first -> to_string(entry)
|
||||
entry, acc -> acc <> joiner <> to_string(entry)
|
||||
entry, :first -> enum_to_string(entry)
|
||||
entry, acc -> [acc, joiner|enum_to_string(entry)]
|
||||
end)
|
||||
if reduced == :first do
|
||||
""
|
||||
else
|
||||
reduced
|
||||
IO.iodata_to_binary reduced
|
||||
end
|
||||
end
|
||||
|
||||
@@ -996,14 +996,14 @@ defmodule Enum do
|
||||
|
||||
def map_join(collection, joiner, mapper) when is_binary(joiner) do
|
||||
reduced = reduce(collection, :first, fn
|
||||
entry, :first -> to_string(mapper, entry)
|
||||
entry, acc -> acc <> joiner <> to_string(mapper, entry)
|
||||
entry, :first -> enum_to_string(mapper.(entry))
|
||||
entry, acc -> [acc, joiner|enum_to_string(mapper.(entry))]
|
||||
end)
|
||||
|
||||
if reduced == :first do
|
||||
""
|
||||
else
|
||||
reduced
|
||||
IO.iodata_to_binary reduced
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1417,18 +1417,33 @@ defmodule Enum do
|
||||
@doc """
|
||||
Returns a subset list of the given collection. Drops elements
|
||||
until element position `start`, then takes `count` elements.
|
||||
|
||||
If the count is greater than collection length, it returns as
|
||||
much as possible. If zero, then it returns `[]`.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Enum.slice(1..100, 5, 10)
|
||||
[6, 7, 8, 9, 10, 11, 12, 13, 14, 15]
|
||||
|
||||
iex> Enum.slice(1..10, 5, 100)
|
||||
[6, 7, 8, 9, 10]
|
||||
|
||||
iex> Enum.slice(1..10, 5, 0)
|
||||
[]
|
||||
|
||||
"""
|
||||
@spec slice(t, integer, non_neg_integer) :: list
|
||||
|
||||
def slice(_coll, _start, 0), do: []
|
||||
|
||||
def slice(coll, start, count) when start < 0 do
|
||||
{list, new_start} = enumerate_and_count(coll, start)
|
||||
if new_start >= 0, do: slice(list, new_start, count)
|
||||
if new_start >= 0 do
|
||||
slice(list, new_start, count)
|
||||
else
|
||||
[]
|
||||
end
|
||||
end
|
||||
|
||||
def slice(coll, start, count) when is_list(coll) and start >= 0 and count > 0 do
|
||||
@@ -1436,7 +1451,7 @@ defmodule Enum do
|
||||
end
|
||||
|
||||
def slice(coll, start, count) when start >= 0 and count > 0 do
|
||||
{start, _, list} = Enumerable.reduce(coll, {:cont, {start, count, []}}, fn
|
||||
{_, _, list} = Enumerable.reduce(coll, {:cont, {start, count, []}}, fn
|
||||
_entry, {start, count, _list} when start > 0 ->
|
||||
{:cont, {start-1, count, []}}
|
||||
entry, {start, count, list} when count > 1 ->
|
||||
@@ -1445,15 +1460,7 @@ defmodule Enum do
|
||||
{:halt, {start, count, [entry|list]}}
|
||||
end) |> elem(1)
|
||||
|
||||
if start <= 0, do: :lists.reverse(list)
|
||||
end
|
||||
|
||||
def slice(coll, start, 0) do
|
||||
res =
|
||||
Enumerable.reduce(coll, {:cont, start}, fn _, start ->
|
||||
if start > 0, do: {:cont, start-1}, else: {:halt, []}
|
||||
end) |> elem(1)
|
||||
if is_list(res), do: res
|
||||
:lists.reverse(list)
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -1468,17 +1475,31 @@ defmodule Enum do
|
||||
The first position (after adding count to negative positions) must be smaller
|
||||
or equal to the last position.
|
||||
|
||||
If the start of the range is not a valid offset for the given
|
||||
collection or if the range is in reverse order, returns `[]`.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Enum.slice(1..100, 5..10)
|
||||
[6, 7, 8, 9, 10, 11]
|
||||
|
||||
iex> Enum.slice(1..10, 5..20)
|
||||
[6, 7, 8, 9, 10]
|
||||
|
||||
iex> Enum.slice(1..10, 11..20)
|
||||
[]
|
||||
|
||||
iex> Enum.slice(1..10, 6..5)
|
||||
[]
|
||||
|
||||
"""
|
||||
@spec slice(t, Range.t) :: list
|
||||
def slice(coll, first..last) when first >= 0 and last >= 0 do
|
||||
# Simple case, which works on infinite collections
|
||||
if last - first >= 0 do
|
||||
slice(coll, first, last - first + 1)
|
||||
else
|
||||
[]
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1489,6 +1510,8 @@ defmodule Enum do
|
||||
length = corr_last - corr_first + 1
|
||||
if corr_first >= 0 and length > 0 do
|
||||
slice(list, corr_first, length)
|
||||
else
|
||||
[]
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1816,7 +1839,7 @@ defmodule Enum do
|
||||
|
||||
## Helpers
|
||||
|
||||
@compile {:inline, to_string: 2}
|
||||
@compile {:inline, enum_to_string: 1}
|
||||
|
||||
defp enumerate_and_count(collection, count) when is_list(collection) do
|
||||
{collection, length(collection) - abs(count)}
|
||||
@@ -1826,12 +1849,8 @@ defmodule Enum do
|
||||
map_reduce(collection, -abs(count), fn(x, acc) -> {x, acc + 1} end)
|
||||
end
|
||||
|
||||
defp to_string(mapper, entry) do
|
||||
case mapper.(entry) do
|
||||
x when is_binary(x) -> x
|
||||
o -> String.Chars.to_string(o)
|
||||
end
|
||||
end
|
||||
defp enum_to_string(entry) when is_binary(entry), do: entry
|
||||
defp enum_to_string(entry), do: String.Chars.to_string(entry)
|
||||
|
||||
## Implementations
|
||||
|
||||
@@ -2142,12 +2161,12 @@ defmodule Enum do
|
||||
|
||||
## slice
|
||||
|
||||
defp do_slice([], start, _count) do
|
||||
if start == 0, do: []
|
||||
defp do_slice([], _start, _count) do
|
||||
[]
|
||||
end
|
||||
|
||||
defp do_slice(list, start, 0) do
|
||||
if start < length(list), do: []
|
||||
defp do_slice(_list, _start, 0) do
|
||||
[]
|
||||
end
|
||||
|
||||
defp do_slice([h|t], 0, count) do
|
||||
@@ -2195,7 +2214,7 @@ defimpl Enumerable, for: Map do
|
||||
end
|
||||
|
||||
defimpl Enumerable, for: Function do
|
||||
def reduce(function, acc, fun),
|
||||
def reduce(function, acc, fun) when is_function(function, 2),
|
||||
do: function.(acc, fun)
|
||||
def member?(_function, _value),
|
||||
do: {:error, __MODULE__}
|
||||
|
||||
@@ -578,6 +578,14 @@ defmodule CaseClauseError do
|
||||
end
|
||||
end
|
||||
|
||||
defmodule CondClauseError do
|
||||
defexception []
|
||||
|
||||
def message(_exception) do
|
||||
"no cond clause evaluated to a true value"
|
||||
end
|
||||
end
|
||||
|
||||
defmodule TryClauseError do
|
||||
defexception [term: nil]
|
||||
|
||||
@@ -632,7 +640,7 @@ defmodule Code.LoadError do
|
||||
defexception [:file, :message]
|
||||
|
||||
def exception(opts) do
|
||||
file = opts[:file]
|
||||
file = Keyword.fetch!(opts, :file)
|
||||
%Code.LoadError{message: "could not load #{file}", file: file}
|
||||
end
|
||||
end
|
||||
@@ -663,8 +671,8 @@ defmodule UnicodeConversionError do
|
||||
|
||||
def exception(opts) do
|
||||
%UnicodeConversionError{
|
||||
encoded: opts[:encoded],
|
||||
message: "#{opts[:kind]} #{detail(opts[:rest])}"
|
||||
encoded: Keyword.fetch!(opts, :encoded),
|
||||
message: "#{Keyword.fetch!(opts, :kind)} #{detail Keyword.fetch!(opts, :rest)}"
|
||||
}
|
||||
end
|
||||
|
||||
@@ -733,6 +741,10 @@ defmodule ErlangError do
|
||||
%SystemLimitError{}
|
||||
end
|
||||
|
||||
def normalize(:cond_clause, _stacktrace) do
|
||||
%CondClauseError{}
|
||||
end
|
||||
|
||||
def normalize({:badarity, {fun, args}}, _stacktrace) do
|
||||
%BadArityError{function: fun, args: args}
|
||||
end
|
||||
|
||||
+133
-227
@@ -1,144 +1,3 @@
|
||||
require Record
|
||||
|
||||
defmodule File.Stat do
|
||||
@moduledoc """
|
||||
A struct responsible to hold file information.
|
||||
|
||||
In Erlang, this struct is represented by a `:file_info` record.
|
||||
Therefore this module also provides functions for converting
|
||||
in between the Erlang record and the Elixir struct.
|
||||
|
||||
Its fields are:
|
||||
|
||||
* `size` - Size of file in bytes.
|
||||
* `type` - `:device`, `:directory`, `:regular`, `:other`. The type of the file.
|
||||
* `access` - `:read`, `:write`, `:read_write`, `:none`. The current system access to
|
||||
the file.
|
||||
* `atime` - The last time the file was read.
|
||||
* `mtime` - The last time the file was written.
|
||||
* `ctime` - The interpretation of this time field depends on the operating
|
||||
system. On Unix, it is the last time the file or the inode was
|
||||
changed. In Windows, it is the create time.
|
||||
* `mode` - The file permissions.
|
||||
* `links` - The number of links to this file. This is always 1 for file
|
||||
systems which have no concept of links.
|
||||
* `major_device` - Identifies the file system where the file is located.
|
||||
In windows, the number indicates a drive as follows:
|
||||
0 means A:, 1 means B:, and so on.
|
||||
* `minor_device` - Only valid for character devices on Unix. In all other
|
||||
cases, this field is zero.
|
||||
* `inode` - Gives the inode number. On non-Unix file systems, this field
|
||||
will be zero.
|
||||
* `uid` - Indicates the owner of the file.
|
||||
* `gid` - Gives the group that the owner of the file belongs to. Will be
|
||||
zero for non-Unix file systems.
|
||||
|
||||
The time type returned in `atime`, `mtime`, and `ctime` is dependent on the
|
||||
time type set in options. `{:time, type}` where type can be `:local`,
|
||||
`:universal`, or `:posix`. Default is `:local`.
|
||||
"""
|
||||
|
||||
record = Record.extract(:file_info, from_lib: "kernel/include/file.hrl")
|
||||
keys = :lists.map(&elem(&1, 0), record)
|
||||
vals = :lists.map(&{&1, [], nil}, keys)
|
||||
pairs = :lists.zip(keys, vals)
|
||||
|
||||
defstruct keys
|
||||
|
||||
@doc """
|
||||
Converts a `File.Stat` struct to a `:file_info` record.
|
||||
"""
|
||||
def to_record(%File.Stat{unquote_splicing(pairs)}) do
|
||||
{:file_info, unquote_splicing(vals)}
|
||||
end
|
||||
|
||||
@doc """
|
||||
Converts a `:file_info` record into a `File.Stat`.
|
||||
"""
|
||||
def from_record({:file_info, unquote_splicing(vals)}) do
|
||||
%File.Stat{unquote_splicing(pairs)}
|
||||
end
|
||||
end
|
||||
|
||||
defmodule File.Stream do
|
||||
@moduledoc """
|
||||
Defines a `File.Stream` struct returned by `File.stream!/2`.
|
||||
|
||||
The following fields are public:
|
||||
|
||||
* `path` - the file path
|
||||
* `modes` - the file modes
|
||||
* `raw` - a boolean indicating if bin functions should be used
|
||||
* `line_or_bytes` - if reading should read lines or a given amount of bytes
|
||||
|
||||
"""
|
||||
|
||||
defstruct path: nil, modes: [], line_or_bytes: :line, raw: true
|
||||
|
||||
defimpl Collectable do
|
||||
def empty(stream) do
|
||||
stream
|
||||
end
|
||||
|
||||
def into(%{path: path, modes: modes, raw: raw} = stream) do
|
||||
modes = for mode <- modes, not mode in [:read], do: mode
|
||||
|
||||
case :file.open(path, [:write|modes]) do
|
||||
{:ok, device} ->
|
||||
{:ok, into(device, stream, raw)}
|
||||
{:error, reason} ->
|
||||
raise File.Error, reason: reason, action: "stream", path: path
|
||||
end
|
||||
end
|
||||
|
||||
defp into(device, stream, raw) do
|
||||
fn
|
||||
:ok, {:cont, x} ->
|
||||
case raw do
|
||||
true -> IO.binwrite(device, x)
|
||||
false -> IO.write(device, x)
|
||||
end
|
||||
:ok, :done ->
|
||||
:file.close(device)
|
||||
stream
|
||||
:ok, :halt ->
|
||||
:file.close(device)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defimpl Enumerable do
|
||||
def reduce(%{path: path, modes: modes, line_or_bytes: line_or_bytes, raw: raw}, acc, fun) do
|
||||
modes = for mode <- modes, not mode in [:write, :append], do: mode
|
||||
|
||||
start_fun =
|
||||
fn ->
|
||||
case :file.open(path, modes) do
|
||||
{:ok, device} -> device
|
||||
{:error, reason} ->
|
||||
raise File.Error, reason: reason, action: "stream", path: path
|
||||
end
|
||||
end
|
||||
|
||||
next_fun =
|
||||
case raw do
|
||||
true -> &IO.each_binstream(&1, line_or_bytes)
|
||||
false -> &IO.each_stream(&1, line_or_bytes)
|
||||
end
|
||||
|
||||
Stream.resource(start_fun, next_fun, &:file.close/1).(acc, fun)
|
||||
end
|
||||
|
||||
def count(_stream) do
|
||||
{:error, __MODULE__}
|
||||
end
|
||||
|
||||
def member?(_stream, _term) do
|
||||
{:error, __MODULE__}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defmodule File do
|
||||
@moduledoc ~S"""
|
||||
This module contains functions to manipulate files.
|
||||
@@ -211,8 +70,7 @@ defmodule File do
|
||||
about such options and other performance considerations.
|
||||
"""
|
||||
|
||||
alias :file, as: F
|
||||
alias :filelib, as: FL
|
||||
alias :file, as: F
|
||||
|
||||
@type posix :: :file.posix()
|
||||
@type io_device :: :file.io_device()
|
||||
@@ -228,7 +86,7 @@ defmodule File do
|
||||
"""
|
||||
@spec regular?(Path.t) :: boolean
|
||||
def regular?(path) do
|
||||
FL.is_regular(IO.chardata_to_string(path))
|
||||
:elixir_utils.read_file_type(IO.chardata_to_string(path)) == {:ok, :regular}
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -236,7 +94,7 @@ defmodule File do
|
||||
"""
|
||||
@spec dir?(Path.t) :: boolean
|
||||
def dir?(path) do
|
||||
FL.is_dir(IO.chardata_to_string(path))
|
||||
:elixir_utils.read_file_type(IO.chardata_to_string(path)) == {:ok, :directory}
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -267,12 +125,13 @@ defmodule File do
|
||||
|
||||
Typical error reasons are:
|
||||
|
||||
* :eacces - Missing search or write permissions for the parent directories of `path`.
|
||||
* :eexist - There is already a file or directory named `path`.
|
||||
* :enoent - A component of `path` does not exist.
|
||||
* :enospc - There is a no space left on the device.
|
||||
* :enotdir - A component of `path` is not a directory
|
||||
On some platforms, `:enoent` is returned instead.
|
||||
* `:eacces` - missing search or write permissions for the parent
|
||||
directories of `path`
|
||||
* `:eexist` - there is already a file or directory named `path`
|
||||
* `:enoent` - a component of `path` does not exist
|
||||
* `:enospc` - there is a no space left on the device
|
||||
* `:enotdir` - a component of `path` is not a directory;
|
||||
on some platforms, `:enoent` is returned instead
|
||||
"""
|
||||
@spec mkdir(Path.t) :: :ok | {:error, posix}
|
||||
def mkdir(path) do
|
||||
@@ -298,13 +157,38 @@ defmodule File do
|
||||
|
||||
Typical error reasons are:
|
||||
|
||||
* :eacces - Missing search or write permissions for the parent directories of `path`.
|
||||
* :enospc - There is a no space left on the device.
|
||||
* :enotdir - A component of `path` is not a directory.
|
||||
* `:eacces` - missing search or write permissions for the parent
|
||||
directories of `path`
|
||||
* `:enospc` - there is a no space left on the device
|
||||
* `:enotdir` - a component of `path` is not a directory
|
||||
"""
|
||||
@spec mkdir_p(Path.t) :: :ok | {:error, posix}
|
||||
def mkdir_p(path) do
|
||||
FL.ensure_dir(Path.join(path, "."))
|
||||
do_mkdir_p(IO.chardata_to_string(path))
|
||||
end
|
||||
|
||||
defp do_mkdir_p("/") do
|
||||
:ok
|
||||
end
|
||||
|
||||
defp do_mkdir_p(path) do
|
||||
if dir?(path) do
|
||||
:ok
|
||||
else
|
||||
parent = Path.dirname(path)
|
||||
if parent == path do
|
||||
# Protect against infinite loop
|
||||
{:error, :einval}
|
||||
else
|
||||
_ = do_mkdir_p(parent)
|
||||
case :file.make_dir(path) do
|
||||
{:error, :eexist} = error ->
|
||||
if dir?(path), do: :ok, else: error
|
||||
other ->
|
||||
other
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -326,13 +210,13 @@ defmodule File do
|
||||
|
||||
Typical error reasons:
|
||||
|
||||
* :enoent - The file does not exist.
|
||||
* :eacces - Missing permission for reading the file,
|
||||
or for searching one of the parent directories.
|
||||
* :eisdir - The named file is a directory.
|
||||
* :enotdir - A component of the file name is not a directory.
|
||||
On some platforms, `:enoent` is returned instead.
|
||||
* :enomem - There is not enough memory for the contents of the file.
|
||||
* `:enoent` - the file does not exist
|
||||
* `:eacces` - missing permission for reading the file,
|
||||
or for searching one of the parent directories
|
||||
* `:eisdir` - the named file is a directory
|
||||
* `:enotdir` - a component of the file name is not a directory;
|
||||
on some platforms, `:enoent` is returned instead
|
||||
* `:enomem` - there is not enough memory for the contents of the file
|
||||
|
||||
You can use `:file.format_error/1` to get a descriptive string of the error.
|
||||
"""
|
||||
@@ -359,15 +243,14 @@ defmodule File do
|
||||
@doc """
|
||||
Returns information about the `path`. If it exists, it
|
||||
returns a `{:ok, info}` tuple, where info is a
|
||||
`File.Stat` record. Returns `{:error, reason}` with
|
||||
`File.Stat` struct. Returns `{:error, reason}` with
|
||||
the same reasons as `read/1` if a failure occurs.
|
||||
|
||||
## Options
|
||||
|
||||
The accepted options are:
|
||||
|
||||
* `:time` if the time should be `:local`, `:universal` or `:posix`.
|
||||
Default is `:local`.
|
||||
* `:time` - `:local | :universal | :posix`; default: `:local`
|
||||
|
||||
"""
|
||||
@spec stat(Path.t, stat_options) :: {:ok, File.Stat.t} | {:error, posix}
|
||||
@@ -622,7 +505,7 @@ defmodule File do
|
||||
# src may be a file or a directory, dest is definitely
|
||||
# a directory. Returns nil unless an error is found.
|
||||
defp do_cp_r(src, dest, callback, acc) when is_list(acc) do
|
||||
case :elixir_utils.file_type(src) do
|
||||
case :elixir_utils.read_link_type(src) do
|
||||
{:ok, :regular} ->
|
||||
do_cp_file(src, dest, callback, acc)
|
||||
{:ok, :symlink} ->
|
||||
@@ -713,19 +596,19 @@ defmodule File do
|
||||
|
||||
Typical error reasons are:
|
||||
|
||||
* :enoent - A component of the file name does not exist.
|
||||
* :enotdir - A component of the file name is not a directory.
|
||||
On some platforms, enoent is returned instead.
|
||||
* :enospc - There is a no space left on the device.
|
||||
* :eacces - Missing permission for writing the file or searching one of the parent directories.
|
||||
* :eisdir - The named file is a directory.
|
||||
* `:enoent` - a component of the file name does not exist
|
||||
* `:enotdir` - a component of the file name is not a directory;
|
||||
on some platforms, enoent is returned instead
|
||||
* `:enospc` - there is a no space left on the device
|
||||
* `:eacces` - missing permission for writing the file or searching one of
|
||||
the parent directories
|
||||
* `:eisdir` - the named file is a directory
|
||||
|
||||
The writing is automatically done in `:raw` mode. Check
|
||||
`File.open/2` for other available options.
|
||||
Check `File.open/2` for other available options.
|
||||
"""
|
||||
@spec write(Path.t, iodata, list) :: :ok | {:error, posix}
|
||||
def write(path, content, modes \\ []) do
|
||||
F.write_file(IO.chardata_to_string(path), content, [:raw|modes])
|
||||
F.write_file(IO.chardata_to_string(path), content, modes)
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -743,16 +626,18 @@ defmodule File do
|
||||
|
||||
@doc """
|
||||
Tries to delete the file `path`.
|
||||
|
||||
Returns `:ok` if successful, or `{:error, reason}` if an error occurs.
|
||||
Note the file is deleted even if in read-only mode.
|
||||
|
||||
Typical error reasons are:
|
||||
|
||||
* :enoent - The file does not exist.
|
||||
* :eacces - Missing permission for the file or one of its parents.
|
||||
* :eperm - The file is a directory and user is not super-user.
|
||||
* :enotdir - A component of the file name is not a directory.
|
||||
On some platforms, enoent is returned instead.
|
||||
* :einval - Filename had an improper type, such as tuple.
|
||||
* `:enoent` - the file does not exist
|
||||
* `:eacces` - missing permission for the file or one of its parents
|
||||
* `:eperm` - the file is a directory and user is not super-user
|
||||
* `:enotdir` - a component of the file name is not a directory;
|
||||
on some platforms, enoent is returned instead
|
||||
* `:einval` - filename had an improper type, such as tuple
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -765,7 +650,27 @@ defmodule File do
|
||||
"""
|
||||
@spec rm(Path.t) :: :ok | {:error, posix}
|
||||
def rm(path) do
|
||||
F.delete(IO.chardata_to_string(path))
|
||||
path = IO.chardata_to_string(path)
|
||||
case F.delete(path) do
|
||||
:ok ->
|
||||
:ok
|
||||
{:error, :eacces} = e ->
|
||||
change_mode_windows(path) || e
|
||||
{:error, _} = e ->
|
||||
e
|
||||
end
|
||||
end
|
||||
|
||||
defp change_mode_windows(path) do
|
||||
if match? {:win32, _}, :os.type do
|
||||
case F.read_file_info(IO.chardata_to_string(path)) do
|
||||
{:ok, file_info} when elem(file_info, 3) in [:read, :none] ->
|
||||
File.chmod(path, (elem(file_info, 7) + 0200))
|
||||
F.delete(path)
|
||||
_ ->
|
||||
nil
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -886,9 +791,9 @@ defmodule File do
|
||||
end
|
||||
|
||||
defp safe_list_dir(path) do
|
||||
case :elixir_utils.file_type(path) do
|
||||
case :elixir_utils.read_link_type(path) do
|
||||
{:ok, :symlink} ->
|
||||
case :elixir_utils.file_type(path, :read_file_info) do
|
||||
case :elixir_utils.read_file_type(path) do
|
||||
{:ok, :directory} -> {:ok, :directory}
|
||||
_ -> {:ok, :regular}
|
||||
end
|
||||
@@ -928,43 +833,55 @@ defmodule File do
|
||||
|
||||
The allowed modes:
|
||||
|
||||
* `:read` - The file, which must exist, is opened for reading.
|
||||
* `:read` - the file, which must exist, is opened for reading.
|
||||
|
||||
* `:write` - The file is opened for writing. It is created if it does not exist.
|
||||
If the file exists, and if write is not combined with read, the file will be truncated.
|
||||
* `:write` - the file is opened for writing. It is created if it does not
|
||||
exist.
|
||||
|
||||
* `:append` - The file will be opened for writing, and it will be created if it does not exist.
|
||||
Every write operation to a file opened with append will take place at the end of the file.
|
||||
If the file does exists, and if write is not combined with read, the file
|
||||
will be truncated.
|
||||
|
||||
* `:exclusive` - The file, when opened for writing, is created if it does not exist.
|
||||
If the file exists, open will return `{:error, :eexist}`.
|
||||
* `:append` - the file will be opened for writing, and it will be created
|
||||
if it does not exist. Every write operation to a file opened with append
|
||||
will take place at the end of the file.
|
||||
|
||||
* `:char_list` - When this term is given, read operations on the file will return char lists rather than binaries;
|
||||
* `:exclusive` - the file, when opened for writing, is created if it does
|
||||
not exist. If the file exists, open will return `{:error, :eexist}`.
|
||||
|
||||
* `:compressed` - Makes it possible to read or write gzip compressed files.
|
||||
The compressed option must be combined with either read or write, but not both.
|
||||
Note that the file size obtained with `stat/1` will most probably not
|
||||
match the number of bytes that can be read from a compressed file.
|
||||
* `:char_list` - when this term is given, read operations on the file will
|
||||
return char lists rather than binaries.
|
||||
|
||||
* `:utf8` - This option denotes how data is actually stored in the disk file and
|
||||
makes the file perform automatic translation of characters to and from utf-8.
|
||||
If data is sent to a file in a format that cannot be converted to the utf-8
|
||||
or if data is read by a function that returns data in a format that cannot cope
|
||||
with the character range of the data, an error occurs and the file will be closed.
|
||||
* `:compressed` - makes it possible to read or write gzip compressed files.
|
||||
|
||||
The compressed option must be combined with either read or write, but not
|
||||
both. Note that the file size obtained with `stat/1` will most probably
|
||||
not match the number of bytes that can be read from a compressed file.
|
||||
|
||||
* `:utf8` - this option denotes how data is actually stored in the disk
|
||||
file and makes the file perform automatic translation of characters to
|
||||
and from utf-8.
|
||||
|
||||
If data is sent to a file in a format that cannot be converted to the
|
||||
utf-8 or if data is read by a function that returns data in a format that
|
||||
cannot cope with the character range of the data, an error occurs and the
|
||||
file will be closed.
|
||||
|
||||
Check http://www.erlang.org/doc/man/file.html#open-2 for more information about
|
||||
other options like `:read_ahead` and `:delayed_write`.
|
||||
|
||||
This function returns:
|
||||
|
||||
* `{:ok, io_device}` - The file has been opened in the requested mode.
|
||||
`io_device` is actually the pid of the process which handles the file.
|
||||
This process is linked to the process which originally opened the file.
|
||||
If any process to which the `io_device` is linked terminates, the file will
|
||||
be closed and the process itself will be terminated. An `io_device` returned
|
||||
from this call can be used as an argument to the `IO` module functions.
|
||||
* `{:ok, io_device}` - the file has been opened in the requested mode.
|
||||
|
||||
* `{:error, reason}` - The file could not be opened.
|
||||
`io_device` is actually the pid of the process which handles the file.
|
||||
This process is linked to the process which originally opened the file.
|
||||
If any process to which the `io_device` is linked terminates, the file
|
||||
will be closed and the process itself will be terminated.
|
||||
|
||||
An `io_device` returned from this call can be used as an argument to the
|
||||
`IO` module functions.
|
||||
|
||||
* `{:error, reason}` - the file could not be opened.
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -989,14 +906,15 @@ defmodule File do
|
||||
|
||||
The file is opened, given to the function as argument and
|
||||
automatically closed after the function returns, regardless
|
||||
if there was an error or not.
|
||||
if there was an error when executing the function.
|
||||
|
||||
It returns `{:ok, function_result}` in case of success,
|
||||
`{:error, reason}` otherwise.
|
||||
|
||||
Do not use this function with `:delayed_write` option
|
||||
since automatically closing the file may fail
|
||||
(as writes are delayed).
|
||||
This function expects the file to be closed with success,
|
||||
which is usually the case unless the `:delayed_write` option
|
||||
is given. For this reason, we do not recommend passing
|
||||
`:delayed_write` to this function.
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -1020,6 +938,7 @@ defmodule File do
|
||||
|
||||
@doc """
|
||||
Same as `open/2` but raises an error if file could not be opened.
|
||||
|
||||
Returns the `io_device` otherwise.
|
||||
"""
|
||||
@spec open!(Path.t, list) :: io_device | no_return
|
||||
@@ -1034,6 +953,7 @@ defmodule File do
|
||||
|
||||
@doc """
|
||||
Same as `open/3` but raises an error if file could not be opened.
|
||||
|
||||
Returns the function result otherwise.
|
||||
"""
|
||||
@spec open!(Path.t, list, (io_device -> res)) :: res | no_return when res: var
|
||||
@@ -1184,21 +1104,7 @@ defmodule File do
|
||||
"""
|
||||
def stream!(path, modes \\ [], line_or_bytes \\ :line) do
|
||||
modes = open_defaults(modes, true)
|
||||
raw = :lists.keyfind(:encoding, 1, modes) == false
|
||||
|
||||
modes =
|
||||
if raw do
|
||||
if :lists.keyfind(:read_ahead, 1, modes) do
|
||||
[:raw|modes]
|
||||
else
|
||||
[:raw, :read_ahead|modes]
|
||||
end
|
||||
else
|
||||
modes
|
||||
end
|
||||
|
||||
%File.Stream{path: IO.chardata_to_string(path), modes: modes,
|
||||
raw: raw, line_or_bytes: line_or_bytes}
|
||||
File.Stream.__build__(IO.chardata_to_string(path), modes, line_or_bytes)
|
||||
end
|
||||
|
||||
@doc """
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
require Record
|
||||
|
||||
defmodule File.Stat do
|
||||
@moduledoc """
|
||||
A struct responsible to hold file information.
|
||||
|
||||
In Erlang, this struct is represented by a `:file_info` record.
|
||||
Therefore this module also provides functions for converting
|
||||
in between the Erlang record and the Elixir struct.
|
||||
|
||||
Its fields are:
|
||||
|
||||
* `size` - size of file in bytes.
|
||||
|
||||
* `type` - `:device | :directory | :regular | :other`; the type of the
|
||||
file.
|
||||
|
||||
* `access` - `:read | :write | :read_write | :none`; the current system
|
||||
access to the file.
|
||||
|
||||
* `atime` - the last time the file was read.
|
||||
|
||||
* `mtime` - the last time the file was written.
|
||||
|
||||
* `ctime` - the interpretation of this time field depends on the operating
|
||||
system. On Unix, it is the last time the file or the inode was changed.
|
||||
In Windows, it is the time of creation.
|
||||
|
||||
* `mode` - the file permissions.
|
||||
|
||||
* `links` - the number of links to this file. This is always 1 for file
|
||||
systems which have no concept of links.
|
||||
|
||||
* `major_device` - identifies the file system where the file is located.
|
||||
In windows, the number indicates a drive as follows: 0 means A:, 1 means
|
||||
B:, and so on.
|
||||
|
||||
* `minor_device` - only valid for character devices on Unix. In all other
|
||||
cases, this field is zero.
|
||||
|
||||
* `inode` - gives the inode number. On non-Unix file systems, this field
|
||||
will be zero.
|
||||
|
||||
* `uid` - indicates the owner of the file.
|
||||
|
||||
* `gid` - gives the group that the owner of the file belongs to. Will be
|
||||
zero for non-Unix file systems.
|
||||
|
||||
The time type returned in `atime`, `mtime`, and `ctime` is dependent on the
|
||||
time type set in options. `{:time, type}` where type can be `:local`,
|
||||
`:universal`, or `:posix`. Default is `:local`.
|
||||
"""
|
||||
|
||||
record = Record.extract(:file_info, from_lib: "kernel/include/file.hrl")
|
||||
keys = :lists.map(&elem(&1, 0), record)
|
||||
vals = :lists.map(&{&1, [], nil}, keys)
|
||||
pairs = :lists.zip(keys, vals)
|
||||
|
||||
defstruct keys
|
||||
|
||||
@doc """
|
||||
Converts a `File.Stat` struct to a `:file_info` record.
|
||||
"""
|
||||
def to_record(%File.Stat{unquote_splicing(pairs)}) do
|
||||
{:file_info, unquote_splicing(vals)}
|
||||
end
|
||||
|
||||
@doc """
|
||||
Converts a `:file_info` record into a `File.Stat`.
|
||||
"""
|
||||
def from_record({:file_info, unquote_splicing(vals)}) do
|
||||
%File.Stat{unquote_splicing(pairs)}
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,96 @@
|
||||
defmodule File.Stream do
|
||||
@moduledoc """
|
||||
Defines a `File.Stream` struct returned by `File.stream!/3`.
|
||||
|
||||
The following fields are public:
|
||||
|
||||
* `path` - the file path
|
||||
* `modes` - the file modes
|
||||
* `raw` - a boolean indicating if bin functions should be used
|
||||
* `line_or_bytes` - if reading should read lines or a given amount of bytes
|
||||
|
||||
"""
|
||||
|
||||
defstruct path: nil, modes: [], line_or_bytes: :line, raw: true
|
||||
|
||||
@doc false
|
||||
def __build__(path, modes, line_or_bytes) do
|
||||
raw = :lists.keyfind(:encoding, 1, modes) == false
|
||||
|
||||
modes =
|
||||
if raw do
|
||||
if :lists.keyfind(:read_ahead, 1, modes) == {:read_ahead, false} do
|
||||
[:raw|modes]
|
||||
else
|
||||
[:raw, :read_ahead|modes]
|
||||
end
|
||||
else
|
||||
modes
|
||||
end
|
||||
|
||||
%File.Stream{path: path, modes: modes, raw: raw, line_or_bytes: line_or_bytes}
|
||||
end
|
||||
|
||||
defimpl Collectable do
|
||||
def empty(stream) do
|
||||
stream
|
||||
end
|
||||
|
||||
def into(%{path: path, modes: modes, raw: raw} = stream) do
|
||||
modes = for mode <- modes, not mode in [:read], do: mode
|
||||
|
||||
case :file.open(path, [:write|modes]) do
|
||||
{:ok, device} ->
|
||||
{:ok, into(device, stream, raw)}
|
||||
{:error, reason} ->
|
||||
raise File.Error, reason: reason, action: "stream", path: path
|
||||
end
|
||||
end
|
||||
|
||||
defp into(device, stream, raw) do
|
||||
fn
|
||||
:ok, {:cont, x} ->
|
||||
case raw do
|
||||
true -> IO.binwrite(device, x)
|
||||
false -> IO.write(device, x)
|
||||
end
|
||||
:ok, :done ->
|
||||
:file.close(device)
|
||||
stream
|
||||
:ok, :halt ->
|
||||
:file.close(device)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defimpl Enumerable do
|
||||
def reduce(%{path: path, modes: modes, line_or_bytes: line_or_bytes, raw: raw}, acc, fun) do
|
||||
modes = for mode <- modes, not mode in [:write, :append], do: mode
|
||||
|
||||
start_fun =
|
||||
fn ->
|
||||
case :file.open(path, modes) do
|
||||
{:ok, device} -> device
|
||||
{:error, reason} ->
|
||||
raise File.Error, reason: reason, action: "stream", path: path
|
||||
end
|
||||
end
|
||||
|
||||
next_fun =
|
||||
case raw do
|
||||
true -> &IO.each_binstream(&1, line_or_bytes)
|
||||
false -> &IO.each_stream(&1, line_or_bytes)
|
||||
end
|
||||
|
||||
Stream.resource(start_fun, next_fun, &:file.close/1).(acc, fun)
|
||||
end
|
||||
|
||||
def count(_stream) do
|
||||
{:error, __MODULE__}
|
||||
end
|
||||
|
||||
def member?(_stream, _term) do
|
||||
{:error, __MODULE__}
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -192,9 +192,10 @@ defmodule Float do
|
||||
|
||||
## Options
|
||||
|
||||
* `:decimals` — number of decimal points to show
|
||||
* `:scientific` — number of decimal points to show, in scientific format
|
||||
* `:compact` — when true, use the most compact representation (ignored with the `scientific` option)
|
||||
* `:decimals` — number of decimal points to show
|
||||
* `:scientific` — number of decimal points to show, in scientific format
|
||||
* `:compact` — when true, use the most compact representation (ignored
|
||||
with the `scientific` option)
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -230,9 +231,10 @@ defmodule Float do
|
||||
|
||||
## Options
|
||||
|
||||
* `:decimals` — number of decimal points to show
|
||||
* `:scientific` — number of decimal points to show, in scientific format
|
||||
* `:compact` — when true, use the most compact representation (ignored with the `scientific` option)
|
||||
* `:decimals` — number of decimal points to show
|
||||
* `:scientific` — number of decimal points to show, in scientific format
|
||||
* `:compact` — when true, use the most compact representation (ignored
|
||||
with the `scientific` option)
|
||||
|
||||
## Examples
|
||||
|
||||
|
||||
+181
-98
@@ -67,60 +67,62 @@ defmodule GenEvent do
|
||||
all 6 callbacks for you, leaving it up to you to implement the ones
|
||||
you want to customize. The callbacks are:
|
||||
|
||||
* `init(args)` - invoked when the event handler is added
|
||||
* `init(args)` - invoked when the event handler is added.
|
||||
|
||||
It must return:
|
||||
It must return:
|
||||
|
||||
{:ok, state}
|
||||
{:ok, state, :hibernate}
|
||||
{:error, reason}
|
||||
- `{:ok, state}`
|
||||
- `{:ok, state, :hibernate}`
|
||||
- `{:error, reason}`
|
||||
|
||||
* `handle_event(msg, state)` - invoked whenever an event is sent via
|
||||
`notify/2` or `sync_notify/2`.
|
||||
* `handle_event(msg, state)` - invoked whenever an event is sent via
|
||||
`notify/2` or `sync_notify/2`.
|
||||
|
||||
It must return:
|
||||
It must return:
|
||||
|
||||
{:ok, new_state}
|
||||
{:ok, new_state, :hibernate}
|
||||
{:swap_handler, args1, new_state, handler2, args2}
|
||||
:remove_handler
|
||||
- `{:ok, new_state}`
|
||||
- `{:ok, new_state, :hibernate}`
|
||||
- `{:swap_handler, args1, new_state, handler2, args2}`
|
||||
- `:remove_handler`
|
||||
|
||||
* `handle_call(msg, state)` - invoked when a `call/3` is done to a specific handler.
|
||||
* `handle_call(msg, state)` - invoked when a `call/3` is done to a specific
|
||||
handler.
|
||||
|
||||
It must return:
|
||||
It must return:
|
||||
|
||||
{:ok, reply, new_state}
|
||||
{:ok, reply, new_state, :hibernate}
|
||||
{:swap_handler, reply, args1, new_state, handler2, args2}
|
||||
{:remove_handler, reply}
|
||||
- `{:ok, reply, new_state}`
|
||||
- `{:ok, reply, new_state, :hibernate}`
|
||||
- `{:swap_handler, reply, args1, new_state, handler2, args2}`
|
||||
- `{:remove_handler, reply}`
|
||||
|
||||
* `handle_info(msg, state)` - invoked to handle all other messages which
|
||||
are received by the process. Must return the same values as `handle_event/2`;
|
||||
* `handle_info(msg, state)` - invoked to handle all other messages which
|
||||
are received by the process. Must return the same values as
|
||||
`handle_event/2`.
|
||||
|
||||
It must return:
|
||||
It must return:
|
||||
|
||||
{:noreply, state}
|
||||
{:noreply, state, timeout}
|
||||
{:stop, reason, state}
|
||||
- `{:noreply, state}`
|
||||
- `{:noreply, state, timeout}`
|
||||
- `{:stop, reason, state}`
|
||||
|
||||
* `terminate(reason, state)` - called when the event handler is removed or the
|
||||
event manager is terminating. It can return any term.
|
||||
* `terminate(reason, state)` - called when the event handler is removed or
|
||||
the event manager is terminating. It can return any term.
|
||||
|
||||
* `code_change(old_vsn, state, extra)` - called when the application
|
||||
code is being upgraded live (hot code swap).
|
||||
* `code_change(old_vsn, state, extra)` - called when the application
|
||||
code is being upgraded live (hot code swapping).
|
||||
|
||||
It must return:
|
||||
It must return:
|
||||
|
||||
{:ok, new_state}
|
||||
- `{:ok, new_state}`
|
||||
|
||||
## Name registering
|
||||
## Name Registration
|
||||
|
||||
A GenEvent is bound to the same name registering rules as a `GenServer`.
|
||||
A GenEvent is bound to the same name registration rules as a `GenServer`.
|
||||
Read more about it in the `GenServer` docs.
|
||||
|
||||
## Streaming
|
||||
|
||||
`GenEvent`'s can be streamed from and streamed with the help of `stream/2`.
|
||||
`GenEvent`s can be streamed from and streamed with the help of `stream/2`.
|
||||
Here are some examples:
|
||||
|
||||
stream = GenEvent.stream(pid)
|
||||
@@ -128,7 +130,7 @@ defmodule GenEvent do
|
||||
# Take the next 10 events
|
||||
Enum.take(stream, 10)
|
||||
|
||||
# Print all other events
|
||||
# Print all remaining events
|
||||
for event <- stream do
|
||||
IO.inspect event
|
||||
end
|
||||
@@ -138,13 +140,13 @@ defmodule GenEvent do
|
||||
|
||||
## Learn more
|
||||
|
||||
In case you desire to learn more about gen events, Elixir getting started
|
||||
If you wish to find out more about gen events, Elixir getting started
|
||||
guides provide a tutorial-like introduction. The documentation and links
|
||||
in Erlang can also provide extra insight.
|
||||
|
||||
* http://elixir-lang.org/getting_started/mix/1.html
|
||||
* http://www.erlang.org/doc/man/gen_event.html
|
||||
* http://learnyousomeerlang.com/event-handlers
|
||||
* http://elixir-lang.org/getting_started/mix/1.html
|
||||
* http://www.erlang.org/doc/man/gen_event.html
|
||||
* http://learnyousomeerlang.com/event-handlers
|
||||
"""
|
||||
|
||||
@typedoc "Return values of `start*` functions"
|
||||
@@ -168,12 +170,13 @@ defmodule GenEvent do
|
||||
This is a struct returned by `stream/2`. The struct is public and
|
||||
contains the following fields:
|
||||
|
||||
* `:manager` - the manager reference given to `GenEvent.stream/2`
|
||||
* `:id` - the event stream id for cancellation
|
||||
* `:timeout` - the timeout in between events, defaults to `:infinity`
|
||||
* `:duration` - the duration of the subscription, defaults to `:infinity`
|
||||
* `:manager` - the manager reference given to `GenEvent.stream/2`
|
||||
* `:id` - the event stream id for cancellation
|
||||
* `:timeout` - the timeout in between events, defaults to `:infinity`
|
||||
* `:duration` - the duration of the subscription, defaults to `:infinity`
|
||||
* `:mode` - if the subscription mode is sync or async, defaults to `:sync`
|
||||
"""
|
||||
defstruct manager: nil, id: nil, timeout: :infinity, duration: :infinity
|
||||
defstruct manager: nil, id: nil, timeout: :infinity, duration: :infinity, mode: :sync
|
||||
|
||||
@doc false
|
||||
defmacro __using__(_) do
|
||||
@@ -218,15 +221,19 @@ defmodule GenEvent do
|
||||
@doc """
|
||||
Starts an event manager linked to the current process.
|
||||
|
||||
This is often used to start the `GenServer` as part of a supervision tree.
|
||||
This is often used to start the `GenEvent` as part of a supervision tree.
|
||||
|
||||
It accepts a the `:name` which is described under the `Name Registering`
|
||||
It accepts the `:name` option which is described under the `Name Registration`
|
||||
section in the `GenServer` module docs.
|
||||
|
||||
If the event manager is successfully created and initialized the function
|
||||
If the event manager is successfully created and initialized, the function
|
||||
returns `{:ok, pid}`, where pid is the pid of the server. If there already
|
||||
exists a process with the specified server name the function returns
|
||||
exists a process with the specified server name, the function returns
|
||||
`{:error, {:already_started, pid}}` with the pid of that process.
|
||||
|
||||
Note that a `GenEvent` started with `start_link/1` is linked to the
|
||||
parent process and will exit not only on crashes but also if the parent
|
||||
process exits with `:normal` reason.
|
||||
"""
|
||||
@spec start_link(options) :: on_start
|
||||
def start_link(options \\ []) when is_list(options) do
|
||||
@@ -236,7 +243,7 @@ defmodule GenEvent do
|
||||
@doc """
|
||||
Starts an event manager process without links (outside of a supervision tree).
|
||||
|
||||
See `spawn_link/1` for more information.
|
||||
See `start_link/1` for more information.
|
||||
"""
|
||||
@spec start(options) :: on_start
|
||||
def start(options \\ []) when is_list(options) do
|
||||
@@ -260,17 +267,25 @@ defmodule GenEvent do
|
||||
The stream is a `GenEvent` struct that implements the `Enumerable`
|
||||
protocol. The supported options are:
|
||||
|
||||
* `:id` - an id to identify all live stream instances. When an `:id` is given,
|
||||
existing streams can be called with via `cancel_streams`;
|
||||
* `:timeout` (Enumerable) - raises if no event arrives in X milliseconds;
|
||||
* `:duration` (Enumerable) - only consume events during the X milliseconds
|
||||
from the streaming start;
|
||||
* `:id` - an id to identify all live stream instances; when an `:id` is
|
||||
given, existing streams can be called with via `cancel_streams`.
|
||||
|
||||
* `:timeout` (Enumerable) - raises if no event arrives in X milliseconds.
|
||||
|
||||
* `:duration` (Enumerable) - only consume events during the X milliseconds
|
||||
from the streaming start.
|
||||
|
||||
* `:mode` - the mode to consume events, can be `:sync` (default) or
|
||||
`:async`. On sync, the event manager waits for the event to be consumed
|
||||
before moving on to the next event handler.
|
||||
|
||||
"""
|
||||
def stream(manager, options \\ []) do
|
||||
%GenEvent{manager: manager,
|
||||
id: Keyword.get(options, :id),
|
||||
timeout: Keyword.get(options, :timeout, :infinity),
|
||||
duration: Keyword.get(options, :duration, :infinity)}
|
||||
duration: Keyword.get(options, :duration, :infinity),
|
||||
mode: Keyword.get(options, :mode, :sync)}
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -287,7 +302,7 @@ defmodule GenEvent do
|
||||
|
||||
## Linked handlers
|
||||
|
||||
When adding a handler, a `:link` option may be given as true.
|
||||
When adding a handler, a `:link` option with value `true` can be given.
|
||||
This means the event handler and the calling process are now linked.
|
||||
|
||||
If the calling process later terminates with `reason`, the event manager
|
||||
@@ -296,18 +311,18 @@ defmodule GenEvent do
|
||||
the event manager sends a message `{:gen_event_EXIT, handler, reason}`
|
||||
to the calling process. Reason is one of the following:
|
||||
|
||||
* `:normal` - if the event handler has been removed due to a call to
|
||||
`remove_handler/3`, or `:remove_handler` has been returned by a callback
|
||||
function;
|
||||
* `:normal` - if the event handler has been removed due to a call to
|
||||
`remove_handler/3`, or `:remove_handler` has been returned by a callback
|
||||
function
|
||||
|
||||
* `:shutdown` - if the event handler has been removed because the event
|
||||
manager is terminating;
|
||||
* `:shutdown` - if the event handler has been removed because the event
|
||||
manager is terminating
|
||||
|
||||
* `{:swapped, new_handler, pid}` - if the process pid has replaced the
|
||||
event handler by another;
|
||||
* `{:swapped, new_handler, pid}` - if the process pid has replaced the
|
||||
event handler by another
|
||||
|
||||
* a term - if the event handler is removed due to an error. Which term
|
||||
depends on the error;
|
||||
* a term - if the event handler is removed due to an error. Which term
|
||||
depends on the error
|
||||
|
||||
"""
|
||||
@spec add_handler(manager, handler, term, [link: boolean]) :: :ok | {:EXIT, term} | {:error, term}
|
||||
@@ -406,7 +421,7 @@ defmodule GenEvent do
|
||||
terminate with a given reason.
|
||||
|
||||
If there was a linked connection between handler1 and a process pid, there
|
||||
will be a link connection between handle2 and pid instead. A new link in
|
||||
will be a link connection between handler2 and pid instead. A new link in
|
||||
between the caller process and the new handler can also be set with by
|
||||
giving `link: true` as option. See `add_handler/4` for more information.
|
||||
|
||||
@@ -441,16 +456,31 @@ defimpl Enumerable, for: GenEvent do
|
||||
use GenEvent
|
||||
|
||||
@doc false
|
||||
def init({mon_pid, pid, ref}) do
|
||||
def init({_mode, mon_pid, _pid, ref} = state) do
|
||||
# Tell the mon_pid we are good to go, and send self() so that this handler
|
||||
# can be removed later without using the managers name.
|
||||
send(mon_pid, {:UP, ref, self()})
|
||||
{:ok, {pid, ref}}
|
||||
{:ok, state}
|
||||
end
|
||||
|
||||
@doc false
|
||||
def handle_event(event, {pid, ref} = state) do
|
||||
send pid, {ref, event}
|
||||
def handle_event(event, {:sync, mon_pid, pid, ref} = state) do
|
||||
sync = Process.monitor(mon_pid)
|
||||
send pid, {ref, sync, event}
|
||||
receive do
|
||||
{^sync, :done} ->
|
||||
Process.demonitor(sync, [:flush])
|
||||
:remove_handler
|
||||
{^sync, :next} ->
|
||||
Process.demonitor(sync, [:flush])
|
||||
{:ok, state}
|
||||
{:DOWN, ^sync, _, _, _} ->
|
||||
{:ok, state}
|
||||
end
|
||||
end
|
||||
|
||||
def handle_event(event, {:async, _mon_pid, pid, ref} = state) do
|
||||
send pid, {ref, nil, event}
|
||||
{:ok, state}
|
||||
end
|
||||
|
||||
@@ -458,7 +488,7 @@ defimpl Enumerable, for: GenEvent do
|
||||
start_fun = fn() -> start(stream) end
|
||||
next_fun = &next(stream, &1)
|
||||
stop_fun = &stop(stream, &1)
|
||||
Stream.resource(start_fun, next_fun, stop_fun).(acc, fun)
|
||||
Stream.resource(start_fun, next_fun, stop_fun).(acc, wrap_reducer(fun))
|
||||
end
|
||||
|
||||
def count(_stream) do
|
||||
@@ -469,38 +499,63 @@ defimpl Enumerable, for: GenEvent do
|
||||
{:error, __MODULE__}
|
||||
end
|
||||
|
||||
defp start(%{manager: manager, id: id, duration: duration} = stream) do
|
||||
{mon_pid, mon_ref} = add_handler(manager, id, duration)
|
||||
defp wrap_reducer(fun) do
|
||||
fn
|
||||
{nil, _manager, event}, acc ->
|
||||
fun.(event, acc)
|
||||
{ref, manager, event}, acc ->
|
||||
try do
|
||||
fun.(event, acc)
|
||||
after
|
||||
send manager, {ref, :next}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defp start(%{manager: manager, id: id, duration: duration, mode: mode} = stream) do
|
||||
{mon_pid, mon_ref} = add_handler(mode, manager, id, duration)
|
||||
send mon_pid, {:UP, mon_ref, self()}
|
||||
|
||||
receive do
|
||||
# The subscription process gave us a go.
|
||||
{:UP, ^mon_ref, manager_pid} ->
|
||||
{mon_ref, manager_pid}
|
||||
# The subscription process died due to an abnormal reason.
|
||||
{mon_ref, mon_pid, manager_pid}
|
||||
# The subscription process died due to an abnormal reason.
|
||||
{:DOWN, ^mon_ref, _, _, reason} ->
|
||||
exit({reason, {__MODULE__, :start, [stream]}})
|
||||
end
|
||||
end
|
||||
|
||||
defp next(%{timeout: timeout} = stream, {mon_ref, _manager_pid} = acc) do
|
||||
defp next(%{timeout: timeout} = stream, {mon_ref, mon_pid, manager_pid} = acc) do
|
||||
# If :DOWN is received must resend it to self so that stop/2 can receive it
|
||||
# and know that the handler has been removed.
|
||||
receive do
|
||||
{^mon_ref, event} -> {event, acc}
|
||||
{:DOWN, ^mon_ref, _, _, :normal} -> nil
|
||||
{:DOWN, ^mon_ref, _, _, :normal} ->
|
||||
send(self(), {:DOWN, mon_ref, :process, mon_pid, :normal})
|
||||
nil
|
||||
{:DOWN, ^mon_ref, _, _, reason} ->
|
||||
send(self(), {:DOWN, mon_ref, :process, mon_pid, :normal})
|
||||
exit({reason, {__MODULE__, :next, [stream, acc]}})
|
||||
{^mon_ref, sync_ref, event} ->
|
||||
{{sync_ref, manager_pid, event}, acc}
|
||||
after
|
||||
timeout ->
|
||||
exit({:timeout, {__MODULE__, :next, [stream, acc]}})
|
||||
end
|
||||
end
|
||||
|
||||
defp stop(%{id: id}, {mon_ref, manager_pid}) do
|
||||
remove_handler(mon_ref, manager_pid, id)
|
||||
flush_events(mon_ref)
|
||||
defp stop(%{mode: mode} = stream, {mon_ref, mon_pid, manager_pid} = acc) do
|
||||
case remove_handler(mon_ref, mon_pid, manager_pid) do
|
||||
:ok when mode == :async ->
|
||||
flush_events(mon_ref)
|
||||
:ok ->
|
||||
:ok
|
||||
{:error, reason} ->
|
||||
exit({reason, {__MODULE__, :stop, [stream, acc]}})
|
||||
end
|
||||
end
|
||||
|
||||
defp add_handler(manager, id, duration) do
|
||||
defp add_handler(mode, manager, id, duration) do
|
||||
parent = self()
|
||||
|
||||
# The subscription is managed by another process, that dies if
|
||||
@@ -522,13 +577,17 @@ defimpl Enumerable, for: GenEvent do
|
||||
|
||||
cancel = cancel_ref(id, mon_ref)
|
||||
:ok = :gen_event.add_sup_handler(manager, {__MODULE__, cancel},
|
||||
{self(), parent, mon_ref})
|
||||
{mode, self(), parent, mon_ref})
|
||||
|
||||
receive do
|
||||
# This message is already in the mailbox if we got this far.
|
||||
{:UP, ^mon_ref, manager_pid} ->
|
||||
send(parent, {:UP, mon_ref, manager_pid})
|
||||
receive do
|
||||
# The stream has finished, remove the handler.
|
||||
{:DONE, ^mon_ref} ->
|
||||
exit_handler(manager_pid, parent_ref, cancel)
|
||||
|
||||
# If the parent died, we can exit normally.
|
||||
{:DOWN, ^parent_ref, _, _, _} ->
|
||||
exit(:normal)
|
||||
@@ -554,26 +613,50 @@ defimpl Enumerable, for: GenEvent do
|
||||
defp cancel_ref(nil, mon_ref), do: mon_ref
|
||||
defp cancel_ref(id, mon_ref), do: {id, mon_ref}
|
||||
|
||||
defp remove_handler(mon_ref, manager_pid, id) do
|
||||
Process.demonitor(mon_ref, [:flush])
|
||||
handler = {__MODULE__, cancel_ref(id, mon_ref)}
|
||||
# handler may nolonger be there, if it is the removal will cause the monitor
|
||||
# process to exit. If this returns successfuly then no more events will be
|
||||
# forwarded.
|
||||
_ = :gen_event.delete_handler(manager_pid, handler, :remove_handler)
|
||||
catch
|
||||
# Do not want to overide the exit reason of the mon_pid so catch errors.
|
||||
# However if the exit is due to a disconnection, exit because messages could
|
||||
# leak if the nodes are reconnected before the manager on the other node
|
||||
# removes the handler. In this case it is very likely that the mon_pid
|
||||
# exited with the same reason.
|
||||
:exit, reason when reason !== {:nodedown, node(manager_pid)} ->
|
||||
:ok
|
||||
defp exit_handler(manager_pid, parent_ref, cancel) do
|
||||
# Send exit signal so manager removes handler.
|
||||
Process.exit(manager_pid, :shutdown)
|
||||
receive do
|
||||
# If the parent died, we can exit normally.
|
||||
{:DOWN, ^parent_ref, _, _, _} ->
|
||||
exit(:normal)
|
||||
|
||||
# Probably the reason is :shutdown, which occurs when the manager receives
|
||||
# an exit signal from a handler supervising process. However whatever the
|
||||
# reason the handler has been removed so it is ok.
|
||||
{:gen_event_EXIT, {__MODULE__, ^cancel}, _} ->
|
||||
exit(:normal)
|
||||
|
||||
# The connection broke, perhaps the handler might try to forward events
|
||||
# before it removes the handler, so must exit abnormally.
|
||||
{:EXIT, ^manager_pid, :noconnection} ->
|
||||
exit({:nodedown, node(manager_pid)})
|
||||
|
||||
# The manager has exited but don't exit abnormally as the handler has died
|
||||
# with the manager and all expected events have been handled. This is ok.
|
||||
{:EXIT, ^manager_pid, _} ->
|
||||
exit(:normal)
|
||||
end
|
||||
end
|
||||
|
||||
defp remove_handler(mon_ref, mon_pid, manager_pid) do
|
||||
send(mon_pid, {:DONE, mon_ref})
|
||||
receive do
|
||||
{^mon_ref, sync, _} when sync != nil ->
|
||||
send(manager_pid, {sync, :done})
|
||||
Process.demonitor(mon_ref, [:flush])
|
||||
:ok
|
||||
{:DOWN, ^mon_ref, _, _, :normal} ->
|
||||
:ok
|
||||
{:DOWN, ^mon_ref, _, _, reason} ->
|
||||
{:error, reason}
|
||||
end
|
||||
end
|
||||
|
||||
defp flush_events(mon_ref) do
|
||||
receive do
|
||||
{^mon_ref, _} -> flush_events(mon_ref)
|
||||
{^mon_ref, _, _} ->
|
||||
flush_events(mon_ref)
|
||||
after
|
||||
0 -> :ok
|
||||
end
|
||||
|
||||
@@ -1,45 +0,0 @@
|
||||
defmodule GenEvent.Behaviour do
|
||||
@moduledoc false
|
||||
|
||||
@doc false
|
||||
defmacro __using__(_) do
|
||||
quote location: :keep do
|
||||
@behaviour :gen_event
|
||||
|
||||
@doc false
|
||||
def init(args) do
|
||||
{:ok, args}
|
||||
end
|
||||
|
||||
@doc false
|
||||
def handle_event(_event, state) do
|
||||
{:ok, state}
|
||||
end
|
||||
|
||||
@doc false
|
||||
def handle_call(_request, state) do
|
||||
{:ok, :ok, state}
|
||||
end
|
||||
|
||||
@doc false
|
||||
def handle_info(_msg, state) do
|
||||
{:ok, state}
|
||||
end
|
||||
|
||||
@doc false
|
||||
def terminate(reason, state) do
|
||||
:ok
|
||||
end
|
||||
|
||||
@doc false
|
||||
def code_change(_old, state, _extra) do
|
||||
{:ok, state}
|
||||
end
|
||||
|
||||
defoverridable [init: 1,
|
||||
handle_event: 2,
|
||||
handle_call: 2, handle_info: 2,
|
||||
terminate: 2, code_change: 3]
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -45,14 +45,14 @@ defmodule GenServer do
|
||||
GenServer.call(pid, :pop)
|
||||
#=> :world
|
||||
|
||||
We start our `Stack` by calling `start_link/2`, passing the module
|
||||
We start our `Stack` by calling `start_link/3`, passing the module
|
||||
with the server implementation and its initial argument (a list
|
||||
representing the stack containing the item `:hello`). We can primarily
|
||||
interact with the server by sending two types of messages. **call**
|
||||
messages expect a reply from the server (and is therefore synchronous)
|
||||
messages expect a reply from the server (and are therefore synchronous)
|
||||
while **cast** messages do not.
|
||||
|
||||
Every time you do a `GenServer.call/2`, the client will send a message
|
||||
Every time you do a `GenServer.call/3`, the client will send a message
|
||||
that must be handled by the `handle_call/3` callback in the GenServer.
|
||||
A `cast/2` message must be handled by `handle_cast/2`.
|
||||
|
||||
@@ -63,64 +63,64 @@ defmodule GenServer do
|
||||
all 6 callbacks for you, leaving it up to you to implement the ones
|
||||
you want to customize. The callbacks are:
|
||||
|
||||
* `init(args)` - invoked when the server is started
|
||||
* `init(args)` - invoked when the server is started.
|
||||
|
||||
It must return:
|
||||
It must return:
|
||||
|
||||
{:ok, state}
|
||||
{:ok, state, timeout}
|
||||
:ignore
|
||||
{:stop, reason}
|
||||
- `{:ok, state}`
|
||||
- `{:ok, state, timeout}`
|
||||
- `:ignore`
|
||||
- `{:stop, reason}`
|
||||
|
||||
* `handle_call(msg, {from, ref}, state)` and `handle_cast(msg, state)` -
|
||||
invoked to handle call (sync) and cast (async) messages.
|
||||
* `handle_call(msg, {from, ref}, state)` and `handle_cast(msg, state)` -
|
||||
invoked to handle call (sync) and cast (async) messages.
|
||||
|
||||
It must return:
|
||||
It must return:
|
||||
|
||||
{:reply, reply, new_state}
|
||||
{:reply, reply, new_state, timeout}
|
||||
{:reply, reply, new_state, :hibernate}
|
||||
{:noreply, new_state}
|
||||
{:noreply, new_state, timeout}
|
||||
{:noreply, new_state, :hibernate}
|
||||
{:stop, reason, new_state}
|
||||
{:stop, reason, reply, new_state}
|
||||
- `{:reply, reply, new_state}`
|
||||
- `{:reply, reply, new_state, timeout}`
|
||||
- `{:reply, reply, new_state, :hibernate}`
|
||||
- `{:noreply, new_state}`
|
||||
- `{:noreply, new_state, timeout}`
|
||||
- `{:noreply, new_state, :hibernate}`
|
||||
- `{:stop, reason, new_state}`
|
||||
- `{:stop, reason, reply, new_state}`
|
||||
|
||||
* `handle_info(msg, state)` - invoked to handle all other messages which
|
||||
are received by the process.
|
||||
* `handle_info(msg, state)` - invoked to handle all other messages which
|
||||
are received by the process.
|
||||
|
||||
It must return:
|
||||
It must return:
|
||||
|
||||
{:noreply, state}
|
||||
{:noreply, state, timeout}
|
||||
{:stop, reason, state}
|
||||
- `{:noreply, state}`
|
||||
- `{:noreply, state, timeout}`
|
||||
- `{:stop, reason, state}`
|
||||
|
||||
* `terminate(reason, state)` - called when the server is about to
|
||||
terminate, useful for cleaning up. It must return `:ok`
|
||||
* `terminate(reason, state)` - called when the server is about to
|
||||
terminate, useful for cleaning up. It must return `:ok`.
|
||||
|
||||
* `code_change(old_vsn, state, extra)` - called when the application
|
||||
code is being upgraded live (hot code swap).
|
||||
* `code_change(old_vsn, state, extra)` - called when the application
|
||||
code is being upgraded live (hot code swapping).
|
||||
|
||||
It must return:
|
||||
It must return:
|
||||
|
||||
{:ok, new_state}
|
||||
{:error, reason}
|
||||
- `{:ok, new_state}`
|
||||
- `{:error, reason}`
|
||||
|
||||
## Names registering
|
||||
## Name Registration
|
||||
|
||||
Both `start_link/3` and `start/3` support the `GenServer` to register
|
||||
a name on start via the `:name` option. Registered names are also
|
||||
automatically clean up on termination. The supported values are:
|
||||
automatically cleaned up on termination. The supported values are:
|
||||
|
||||
* an atom - the GenServer is registered locally with the given name
|
||||
using `Process.register/2`;
|
||||
* an atom - the GenServer is registered locally with the given name
|
||||
using `Process.register/2`.
|
||||
|
||||
* `{:global, term}`- the GenServer is registered globally with the given
|
||||
term using the functions in the `:global` module;
|
||||
* `{:global, term}`- the GenServer is registered globally with the given
|
||||
term using the functions in the `:global` module.
|
||||
|
||||
* `{:via, module, term}` - the GenServer is registered with the given
|
||||
mechanism and name. The `:via` option expects a module name to control
|
||||
the registration mechanism along side a name which can be any term;
|
||||
* `{:via, module, term}` - the GenServer is registered with the given
|
||||
mechanism and name. The `:via` option expects a module name to control
|
||||
the registration mechanism alongside a name which can be any term.
|
||||
|
||||
For example, we could start and register our Stack server locally as follows:
|
||||
|
||||
@@ -130,22 +130,23 @@ defmodule GenServer do
|
||||
# Now messages can be sent directly to MyStack
|
||||
GenServer.call(MyStack, :pop) #=> :hello
|
||||
|
||||
Once the server is started, the remaining functions in this module (`call/2`,
|
||||
`cast/2` and friends) will also accept an atom, or any `:global` or `:via`
|
||||
Once the server is started, the remaining functions in this module (`call/3`,
|
||||
`cast/2`, and friends) will also accept an atom, or any `:global` or `:via`
|
||||
tuples. In general, the following formats are supported:
|
||||
|
||||
* a `pid`
|
||||
* an `atom` if the server is locally registered
|
||||
* `{atom, node}` if the server is locally registered at another node
|
||||
* `{:global, term}` if the server is globally registered
|
||||
* `{:via, module, name}` if the server is registered through an alternative registry
|
||||
* a `pid`
|
||||
* an `atom` if the server is locally registered
|
||||
* `{atom, node}` if the server is locally registered at another node
|
||||
* `{:global, term}` if the server is globally registered
|
||||
* `{:via, module, name}` if the server is registered through an alternative
|
||||
registry
|
||||
|
||||
## Client / Server APIs
|
||||
|
||||
Although in the example above we have used `GenServer.start_link/2` and
|
||||
Although in the example above we have used `GenServer.start_link/3` and
|
||||
friends to directly start and communicate with the server, most of the
|
||||
time we don't call the `GenServer` functions directly, instead, we wrap
|
||||
them in functions too.
|
||||
time we don't call the `GenServer` functions directly. Instead, we wrap
|
||||
the calls in new functions representing the public API of the server.
|
||||
|
||||
Here is a better implementation of our Stack module:
|
||||
|
||||
@@ -192,14 +193,14 @@ defmodule GenServer do
|
||||
|
||||
## Learn more
|
||||
|
||||
In case you desire to learn more about gen servers, Elixir getting started
|
||||
If you wish to find out more about gen servers, Elixir getting started
|
||||
guides provide a tutorial-like introduction. The documentation and links
|
||||
in Erlang can also provide extra insight.
|
||||
|
||||
* http://elixir-lang.org/getting_started/mix/1.html
|
||||
* http://www.erlang.org/doc/man/gen_server.html
|
||||
* http://www.erlang.org/doc/design_principles/gen_server_concepts.html
|
||||
* http://learnyousomeerlang.com/clients-and-servers
|
||||
* http://elixir-lang.org/getting_started/mix/1.html
|
||||
* http://www.erlang.org/doc/man/gen_server.html
|
||||
* http://www.erlang.org/doc/design_principles/gen_server_concepts.html
|
||||
* http://learnyousomeerlang.com/clients-and-servers
|
||||
"""
|
||||
|
||||
@typedoc "Return values of `start*` functions"
|
||||
@@ -269,27 +270,32 @@ defmodule GenServer do
|
||||
passing the given `args` to initialize it. To ensure a synchronized start-up
|
||||
procedure, this function does not return until `init/1` has returned.
|
||||
|
||||
Note that a `GenServer` started with `start_link/3` is linked to the
|
||||
parent process and will exit in case of crashes. The GenServer will also
|
||||
exit due to the `:normal` reasons in case it is configured to trap exits
|
||||
in the `init/1` callback.
|
||||
|
||||
## Options
|
||||
|
||||
The `:name` option is used for name registered as described in the module
|
||||
The `:name` option is used for name registration as described in the module
|
||||
documentation. If the option `:timeout` option is present, the server is
|
||||
allowed to spend the given milliseconds initializing or it will be
|
||||
terminated and the start function will return `{:error, :timeout}`.
|
||||
|
||||
If the option `:debug` is present, the corresponding function in the
|
||||
If the `:debug` option is present, the corresponding function in the
|
||||
[`:sys` module](http://www.erlang.org/doc/man/sys.html) will be invoked.
|
||||
|
||||
If the option `:spawn_opt` is present, the given options will be passed
|
||||
to the underlying process as in `Process.spawn/3`.
|
||||
If the `:spawn_opt` option is present, its value will be passed as options
|
||||
to the underlying process as in `Process.spawn/4`.
|
||||
|
||||
## Return values
|
||||
|
||||
If the server is successfully created and initialized the function returns
|
||||
If the server is successfully created and initialized, the function returns
|
||||
`{:ok, pid}`, where pid is the pid of the server. If there already exists a
|
||||
process with the specified server name the function returns
|
||||
process with the specified server name, the function returns
|
||||
`{:error, {:already_started, pid}}` with the pid of that process.
|
||||
|
||||
If the `init/1` callback fails with reason, the function returns
|
||||
If the `init/1` callback fails with `reason`, the function returns
|
||||
`{:error, reason}`. Otherwise, if it returns `{:stop, reason}`
|
||||
or `:ignore`, the process is terminated and the function returns
|
||||
`{:error, reason}` or `:ignore`, respectively.
|
||||
@@ -321,13 +327,13 @@ defmodule GenServer do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Makes a synchronous call to the `server` and wait for its reply.
|
||||
Makes a synchronous call to the `server` and waits for its reply.
|
||||
|
||||
The client sends the given `request` to the server and waits until a reply
|
||||
arrives or a timeout occurs. `handle_call/3` will be called on the server
|
||||
to handle the request.
|
||||
|
||||
The server can be any of the values described in the `Name Registering`
|
||||
The server can be any of the values described in the `Name Registration`
|
||||
section of the module documentation.
|
||||
|
||||
## Timeouts
|
||||
@@ -349,9 +355,9 @@ defmodule GenServer do
|
||||
@doc """
|
||||
Sends an asynchronous request to the `server`.
|
||||
|
||||
This function returns `:ok` immediately, ignoring if the destination node
|
||||
or server does not exist. `handle_cast/2` will be called on the server
|
||||
to handle the request.
|
||||
This function returns `:ok` immediately, regardless of whether the
|
||||
destination node or server does exists. `handle_cast/2` will be called on the
|
||||
server to handle the request.
|
||||
"""
|
||||
@spec cast(server, term) :: :ok
|
||||
defdelegate cast(server, request), to: :gen_server
|
||||
@@ -392,7 +398,7 @@ defmodule GenServer do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Replies a client.
|
||||
Replies to a client.
|
||||
|
||||
This function can be used by a server to explicitly send a reply to a
|
||||
client that called `call/3` or `multi_call/4`. When the reply cannot be
|
||||
@@ -402,7 +408,7 @@ defmodule GenServer do
|
||||
in `handle_call/3` callbacks. Reply is an arbitrary term which will be
|
||||
given back to the client as the return value of the call.
|
||||
|
||||
This function always return `:ok`.
|
||||
This function always returns `:ok`.
|
||||
"""
|
||||
@spec reply({pid, reference}, term) :: :ok
|
||||
def reply(client, reply)
|
||||
|
||||
@@ -1,43 +0,0 @@
|
||||
defmodule GenServer.Behaviour do
|
||||
@moduledoc false
|
||||
|
||||
@doc false
|
||||
defmacro __using__(_) do
|
||||
quote location: :keep do
|
||||
@behaviour :gen_server
|
||||
|
||||
@doc false
|
||||
def init(args) do
|
||||
{:ok, args}
|
||||
end
|
||||
|
||||
@doc false
|
||||
def handle_call(request, _from, state) do
|
||||
{:stop, {:bad_call, request}, state}
|
||||
end
|
||||
|
||||
@doc false
|
||||
def handle_info(_msg, state) do
|
||||
{:noreply, state}
|
||||
end
|
||||
|
||||
@doc false
|
||||
def handle_cast(msg, state) do
|
||||
{:stop, {:bad_cast, msg}, state}
|
||||
end
|
||||
|
||||
@doc false
|
||||
def terminate(_reason, _state) do
|
||||
:ok
|
||||
end
|
||||
|
||||
@doc false
|
||||
def code_change(_old, state, _extra) do
|
||||
{:ok, state}
|
||||
end
|
||||
|
||||
defoverridable [init: 1, handle_call: 3, handle_info: 2,
|
||||
handle_cast: 2, terminate: 2, code_change: 3]
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -14,7 +14,7 @@ defmodule HashDict do
|
||||
functions and their APIs, please consult the `Dict` module.
|
||||
"""
|
||||
|
||||
use Dict.Behaviour
|
||||
use Dict
|
||||
|
||||
@node_bitmap 0b111
|
||||
@node_shift 3
|
||||
@@ -22,6 +22,7 @@ defmodule HashDict do
|
||||
@node_template :erlang.make_tuple(@node_size, [])
|
||||
|
||||
@opaque t :: map
|
||||
@doc false
|
||||
defstruct size: 0, root: @node_template
|
||||
|
||||
# Inline common instructions
|
||||
@@ -85,10 +86,11 @@ defmodule HashDict do
|
||||
|
||||
## General helpers
|
||||
|
||||
defp dict_delete(%HashDict{root: root, size: size}, key) do
|
||||
@doc false
|
||||
def dict_delete(%HashDict{root: root, size: size}, key) do
|
||||
case do_delete(root, key, key_hash(key)) do
|
||||
{root, value} -> {%HashDict{root: root, size: size - 1}, value}
|
||||
:error -> :error
|
||||
:error -> :error
|
||||
end
|
||||
end
|
||||
|
||||
@@ -232,7 +234,14 @@ defimpl Enumerable, for: HashDict do
|
||||
end
|
||||
|
||||
defimpl Access, for: HashDict do
|
||||
def access(dict, key), do: HashDict.get(dict, key, nil)
|
||||
def get(dict, key) do
|
||||
HashDict.get(dict, key, nil)
|
||||
end
|
||||
|
||||
def get_and_update(dict, key, fun) do
|
||||
{get, update} = fun.(HashDict.get(dict, key, nil))
|
||||
{get, HashDict.put(dict, key, update)}
|
||||
end
|
||||
end
|
||||
|
||||
defimpl Collectable, for: HashDict do
|
||||
@@ -248,3 +257,11 @@ defimpl Collectable, for: HashDict do
|
||||
end}
|
||||
end
|
||||
end
|
||||
|
||||
defimpl Inspect, for: HashDict do
|
||||
import Inspect.Algebra
|
||||
|
||||
def inspect(dict, opts) do
|
||||
concat ["#HashDict<", Inspect.List.inspect(HashDict.to_list(dict), opts), ">"]
|
||||
end
|
||||
end
|
||||
|
||||
@@ -22,6 +22,7 @@ defmodule HashSet do
|
||||
@node_template :erlang.make_tuple(@node_size, [])
|
||||
|
||||
@opaque t :: map
|
||||
@doc false
|
||||
defstruct size: 0, root: @node_template
|
||||
|
||||
# Inline common instructions
|
||||
@@ -264,3 +265,11 @@ defimpl Collectable, for: HashSet do
|
||||
end}
|
||||
end
|
||||
end
|
||||
|
||||
defimpl Inspect, for: HashSet do
|
||||
import Inspect.Algebra
|
||||
|
||||
def inspect(set, opts) do
|
||||
concat ["#HashSet<", Inspect.List.inspect(HashSet.to_list(set), opts), ">"]
|
||||
end
|
||||
end
|
||||
|
||||
@@ -8,7 +8,7 @@ defprotocol Inspect do
|
||||
formatted, either in pretty printing format or a regular one.
|
||||
|
||||
The `inspect/2` function receives the entity to be inspected
|
||||
followed by the inspecting options, represented by the record
|
||||
followed by the inspecting options, represented by the struct
|
||||
`Inspect.Opts`.
|
||||
|
||||
Inspection is done using the functions available in `Inspect.Algebra`.
|
||||
@@ -210,9 +210,8 @@ defimpl Inspect, for: BitString do
|
||||
end
|
||||
defp escape(<<>>, _char, binary), do: binary
|
||||
|
||||
|
||||
@doc false
|
||||
# also used by Regex
|
||||
# Also used by Regex
|
||||
def escape_char(char) when char in ?\000..?\377,
|
||||
do: octify(char)
|
||||
|
||||
@@ -237,7 +236,6 @@ defimpl Inspect, for: BitString do
|
||||
defp to_hex(c) when c in 0..9, do: ?0+c
|
||||
defp to_hex(c) when c in 10..15, do: ?a+c-10
|
||||
|
||||
|
||||
defp append(<<h, t :: binary>>, binary), do: append(t, << binary :: binary, h >>)
|
||||
defp append(<<>>, binary), do: binary
|
||||
|
||||
@@ -349,63 +347,13 @@ defimpl Inspect, for: Tuple do
|
||||
def inspect({}, _opts), do: "{}"
|
||||
|
||||
def inspect(tuple, opts) do
|
||||
if opts.records do
|
||||
record_inspect(tuple, opts)
|
||||
else
|
||||
surround_many("{", Tuple.to_list(tuple), "}", opts.limit, &to_doc(&1, opts))
|
||||
end
|
||||
end
|
||||
|
||||
## Helpers
|
||||
|
||||
defp record_inspect(record, opts) do
|
||||
[name|tail] = Tuple.to_list(record)
|
||||
|
||||
if is_atom(name) && (fields = record_fields(name)) && (length(fields) == size(record) - 1) do
|
||||
surround_record(name, fields, tail, opts)
|
||||
else
|
||||
surround_many("{", [name|tail], "}", opts.limit, &to_doc(&1, opts))
|
||||
end
|
||||
end
|
||||
|
||||
defp record_fields(name) do
|
||||
case Atom.to_string(name) do
|
||||
"Elixir." <> _ ->
|
||||
try do
|
||||
name.__record__(:fields)
|
||||
rescue
|
||||
_ -> nil
|
||||
end
|
||||
_ -> nil
|
||||
end
|
||||
end
|
||||
|
||||
defp surround_record(name, fields, tail, opts) do
|
||||
concat(
|
||||
Inspect.Atom.inspect(name, opts),
|
||||
surround_many("[", zip_fields(fields, tail), "]", opts.limit, &keyword(&1, opts))
|
||||
)
|
||||
end
|
||||
|
||||
defp zip_fields([{key, _}|tk], [value|tv]) do
|
||||
case Atom.to_string(key) do
|
||||
"_" <> _ -> zip_fields(tk, tv)
|
||||
key -> [{key, value}|zip_fields(tk, tv)]
|
||||
end
|
||||
end
|
||||
|
||||
defp zip_fields([], []) do
|
||||
[]
|
||||
end
|
||||
|
||||
defp keyword({k, v}, opts) do
|
||||
concat(k <> ": ", to_doc(v, opts))
|
||||
surround_many("{", Tuple.to_list(tuple), "}", opts.limit, &to_doc(&1, opts))
|
||||
end
|
||||
end
|
||||
|
||||
defimpl Inspect, for: Map do
|
||||
def inspect(map, opts) do
|
||||
inspect(map, "", opts)
|
||||
nest inspect(map, "", opts), 1
|
||||
end
|
||||
|
||||
def inspect(map, name, opts) do
|
||||
@@ -443,13 +391,12 @@ end
|
||||
|
||||
defimpl Inspect, for: Regex do
|
||||
def inspect(regex, _opts) do
|
||||
delim =?/
|
||||
delim = ?/
|
||||
concat ["~r",
|
||||
<<delim, escape(regex.source, delim)::binary, delim>>,
|
||||
regex.opts]
|
||||
end
|
||||
|
||||
|
||||
defp escape(bin, term),
|
||||
do: escape(bin, <<>>, term)
|
||||
|
||||
@@ -575,15 +522,3 @@ defimpl Inspect, for: Any do
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defimpl Inspect, for: HashDict do
|
||||
def inspect(dict, opts) do
|
||||
concat ["#HashDict<", Inspect.List.inspect(HashDict.to_list(dict), opts), ">"]
|
||||
end
|
||||
end
|
||||
|
||||
defimpl Inspect, for: HashSet do
|
||||
def inspect(set, opts) do
|
||||
concat ["#HashSet<", Inspect.List.inspect(HashSet.to_list(set), opts), ">"]
|
||||
end
|
||||
end
|
||||
|
||||
@@ -4,27 +4,32 @@ defmodule Inspect.Opts do
|
||||
|
||||
The following fields are available:
|
||||
|
||||
* `:structs` - when false, structs are not formatted by the inspect protocol,
|
||||
they are instead printed as maps, defaults to true;
|
||||
* `:structs` - when false, structs are not formatted by the inspect
|
||||
protocol, they are instead printed as maps, defaults to true.
|
||||
|
||||
* `:binaries` - when `:as_strings` all binaries will be printed as strings,
|
||||
non-printable bytes will be escaped; when `:as_binaries` all
|
||||
binaries will be printed in bit syntax; when the default
|
||||
`:infer`, the binary will be printed as a string if it is
|
||||
printable, otherwise in bit syntax;
|
||||
* `:binaries` - when `:as_strings` all binaries will be printed as strings,
|
||||
non-printable bytes will be escaped.
|
||||
|
||||
* `:char_lists` - when `:as_char_lists` all lists will be printed as char lists,
|
||||
non-printable elements will be escaped; when `:as_lists` all
|
||||
lists will be printed as lists; when the default `:infer`, the
|
||||
list will be printed as a char list if it is printable,
|
||||
otherwise as list;
|
||||
When `:as_binaries` all binaries will be printed in bit syntax.
|
||||
|
||||
* `:limit` - limits the number of items that are printed for tuples, bitstrings,
|
||||
and lists, does not apply to strings nor char lists, defaults to 50;
|
||||
When the default `:infer`, the binary will be printed as a string if it
|
||||
is printable, otherwise in bit syntax.
|
||||
|
||||
* `:pretty` - if set to true enables pretty printing, defaults to false;
|
||||
* `:char_lists` - when `:as_char_lists` all lists will be printed as char
|
||||
lists, non-printable elements will be escaped.
|
||||
|
||||
* `:width` - defaults to the 80 characters;
|
||||
When `:as_lists` all lists will be printed as lists.
|
||||
|
||||
When the default `:infer`, the list will be printed as a char list if it
|
||||
is printable, otherwise as list.
|
||||
|
||||
* `:limit` - limits the number of items that are printed for tuples,
|
||||
bitstrings, and lists, does not apply to strings nor char lists, defaults
|
||||
to 50.
|
||||
|
||||
* `:pretty` - if set to true enables pretty printing, defaults to false.
|
||||
|
||||
* `:width` - defaults to the 80 characters.
|
||||
"""
|
||||
|
||||
defstruct structs: true :: boolean,
|
||||
@@ -32,8 +37,7 @@ defmodule Inspect.Opts do
|
||||
char_lists: :infer :: :infer | :as_lists | :as_char_lists,
|
||||
limit: 50 :: pos_integer,
|
||||
width: 80 :: pos_integer | :infinity,
|
||||
pretty: false :: boolean,
|
||||
records: true :: boolean
|
||||
pretty: false :: boolean
|
||||
end
|
||||
|
||||
defmodule Inspect.Algebra do
|
||||
@@ -160,19 +164,6 @@ defmodule Inspect.Algebra do
|
||||
according to the inspect protocol.
|
||||
"""
|
||||
@spec to_doc(any, Inspect.Opts.t) :: t
|
||||
def to_doc(arg, %Inspect.Opts{} = opts) when is_tuple(arg) and is_atom(elem(arg, 0)) do
|
||||
# Remove this code when protocols+records are removed"
|
||||
if opts.records do
|
||||
try do
|
||||
Inspect.inspect(arg, opts)
|
||||
rescue
|
||||
_ -> Inspect.Tuple.inspect(arg, opts)
|
||||
end
|
||||
else
|
||||
Inspect.Tuple.inspect(arg, opts)
|
||||
end
|
||||
end
|
||||
|
||||
def to_doc(%{__struct__: struct} = map, %Inspect.Opts{} = opts) when is_atom(struct) do
|
||||
if opts.structs do
|
||||
try do
|
||||
@@ -194,8 +185,7 @@ defmodule Inspect.Algebra do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns `:doc_nil` which is a document entity used to represent
|
||||
nothingness. Takes no arguments.
|
||||
Returns a document entity used to represent nothingness.
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -207,8 +197,7 @@ defmodule Inspect.Algebra do
|
||||
def empty, do: :doc_nil
|
||||
|
||||
@doc """
|
||||
Concatenates two document entities. Takes two arguments:
|
||||
left doc and right doc. Returns a DocCons doc
|
||||
Concatenates two document entities.
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -230,15 +219,16 @@ defmodule Inspect.Algebra do
|
||||
folddoc(docs, &concat(&1, &2))
|
||||
end
|
||||
|
||||
@doc """
|
||||
Nests document entity `x` positions deep. Nesting will be
|
||||
appended to the line breaks.
|
||||
@doc ~S"""
|
||||
Nests document entity `x` positions deep.
|
||||
|
||||
Nesting will be appended to the line breaks.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> doc = Inspect.Algebra.nest(Inspect.Algebra.concat(Inspect.Algebra.break, "6"), 5)
|
||||
iex> Inspect.Algebra.pretty(doc, 80)
|
||||
" 6"
|
||||
iex> doc = Inspect.Algebra.nest(Inspect.Algebra.glue("hello", "world"), 5)
|
||||
iex> Inspect.Algebra.pretty(doc, 5)
|
||||
"hello\n world"
|
||||
|
||||
"""
|
||||
@spec nest(t, non_neg_integer) :: doc_nest
|
||||
@@ -251,9 +241,11 @@ defmodule Inspect.Algebra do
|
||||
end
|
||||
|
||||
@doc ~S"""
|
||||
Document entity representing a break. This break can
|
||||
be rendered as a linebreak or as spaces, depending on the
|
||||
`mode` of the chosen layout or the provided separator.
|
||||
Document entity representing a break.
|
||||
|
||||
This break can be rendered as a linebreak or as spaces,
|
||||
depending on the `mode` of the chosen layout or the provided
|
||||
separator.
|
||||
|
||||
## Examples
|
||||
|
||||
|
||||
+27
-29
@@ -21,12 +21,12 @@ defmodule IO do
|
||||
the atom must be the name of a registered process. However,
|
||||
there are three exceptions for this rule:
|
||||
|
||||
* `:standard_io` - when the `:standard_io` atom is given,
|
||||
it is treated as a shortcut for `Process.group_leader`
|
||||
* `:standard_io` - when the `:standard_io` atom is given,
|
||||
it is treated as a shortcut for `Process.group_leader`
|
||||
|
||||
* `:stdio` - is a shortcut for `:standard_io`
|
||||
* `:stdio` - is a shortcut for `:standard_io`
|
||||
|
||||
* `:stderr` - is a shortcut for `:standard_error`
|
||||
* `:stderr` - is a shortcut for `:standard_error`
|
||||
|
||||
"""
|
||||
|
||||
@@ -46,13 +46,13 @@ defmodule IO do
|
||||
Reads `count` characters from the IO device or until
|
||||
the end of the line if `:line` is given. It returns:
|
||||
|
||||
* `data` - The input characters.
|
||||
* `data` - the input characters
|
||||
|
||||
* `:eof` - End of file was encountered.
|
||||
* `:eof` - end of file was encountered
|
||||
|
||||
* `{:error, reason}` - Other (rare) error condition,
|
||||
for instance `{:error, :estale}` if reading from an
|
||||
NFS file system.
|
||||
* `{:error, reason}` - other (rare) error condition;
|
||||
for instance, `{:error, :estale}` if reading from an
|
||||
NFS volume
|
||||
"""
|
||||
@spec read(device, :line | non_neg_integer) :: chardata | nodata
|
||||
def read(device \\ group_leader, chars_or_line)
|
||||
@@ -69,13 +69,13 @@ defmodule IO do
|
||||
Reads `count` bytes from the IO device or until
|
||||
the end of the line if `:line` is given. It returns:
|
||||
|
||||
* `data` - The input characters.
|
||||
* `data` - the input characters
|
||||
|
||||
* `:eof` - End of file was encountered.
|
||||
* `:eof` - end of file was encountered
|
||||
|
||||
* `{:error, reason}` - Other (rare) error condition,
|
||||
for instance `{:error, :estale}` if reading from an
|
||||
NFS file system.
|
||||
* `{:error, reason}` - other (rare) error condition;
|
||||
for instance, `{:error, :estale}` if reading from an
|
||||
NFS volume
|
||||
"""
|
||||
@spec binread(device, :line | non_neg_integer) :: iodata | nodata
|
||||
def binread(device \\ group_leader, chars_or_line)
|
||||
@@ -174,13 +174,13 @@ defmodule IO do
|
||||
Otherwise, `count` is the number of raw bytes to be retrieved.
|
||||
It returns:
|
||||
|
||||
* `data` - The input characters.
|
||||
* `data` - the input characters
|
||||
|
||||
* `:eof` - End of file was encountered.
|
||||
* `:eof` - end of file was encountered
|
||||
|
||||
* `{:error, reason}` - Other (rare) error condition,
|
||||
for instance `{:error, :estale}` if reading from an
|
||||
NFS file system.
|
||||
* `{:error, reason}` - other (rare) error condition;
|
||||
for instance, `{:error, :estale}` if reading from an
|
||||
NFS volume
|
||||
"""
|
||||
@spec getn(chardata | String.Chars.t, pos_integer) :: chardata | nodata
|
||||
@spec getn(device, chardata | String.Chars.t) :: chardata | nodata
|
||||
@@ -208,14 +208,14 @@ defmodule IO do
|
||||
@doc """
|
||||
Reads a line from the IO device. It returns:
|
||||
|
||||
* `data` - The characters in the line terminated
|
||||
by a LF (or end of file).
|
||||
* `data` - the characters in the line terminated
|
||||
by a LF (or end of file)
|
||||
|
||||
* `:eof` - End of file was encountered.
|
||||
* `:eof` - end of file was encountered
|
||||
|
||||
* `{:error, reason}` - Other (rare) error condition,
|
||||
for instance `{:error, :estale}` if reading from an
|
||||
NFS file system.
|
||||
* `{:error, reason}` - other (rare) error condition;
|
||||
for instance, `{:error, :estale}` if reading from an
|
||||
NFS volume
|
||||
"""
|
||||
@spec gets(device, chardata | String.Chars.t) :: chardata | nodata
|
||||
def gets(device \\ group_leader(), prompt) do
|
||||
@@ -248,8 +248,7 @@ defmodule IO do
|
||||
"""
|
||||
@spec stream(device, :line | pos_integer) :: Enumerable.t
|
||||
def stream(device, line_or_codepoints) do
|
||||
struct IO.Stream,
|
||||
device: map_dev(device), raw: false, line_or_bytes: line_or_codepoints
|
||||
IO.Stream.__build__(map_dev(device), false, line_or_codepoints)
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -267,8 +266,7 @@ defmodule IO do
|
||||
"""
|
||||
@spec binstream(device, :line | pos_integer) :: Enumerable.t
|
||||
def binstream(device, line_or_bytes) do
|
||||
struct IO.Stream,
|
||||
device: map_dev(device), raw: true, line_or_bytes: line_or_bytes
|
||||
IO.Stream.__build__(map_dev(device), true, line_or_bytes)
|
||||
end
|
||||
|
||||
@doc """
|
||||
|
||||
@@ -155,7 +155,7 @@ defmodule IO.ANSI do
|
||||
|
||||
"""
|
||||
@spec escape(String.t, emit :: boolean) :: String.t
|
||||
def escape(string, emit \\ terminal?) do
|
||||
def escape(string, emit \\ terminal?) when is_binary(string) and is_boolean(emit) do
|
||||
{rendered, emitted} = do_escape(string, emit, false, nil, [])
|
||||
if emitted do
|
||||
rendered <> reset
|
||||
@@ -185,7 +185,7 @@ defmodule IO.ANSI do
|
||||
|
||||
"""
|
||||
@spec escape_fragment(String.t, emit :: boolean) :: String.t
|
||||
def escape_fragment(string, emit \\ terminal?) do
|
||||
def escape_fragment(string, emit \\ terminal?) when is_binary(string) and is_boolean(emit) do
|
||||
{escaped, _emitted} = do_escape(string, emit, false, nil, [])
|
||||
escaped
|
||||
end
|
||||
|
||||
@@ -8,14 +8,14 @@ defmodule IO.ANSI.Docs do
|
||||
|
||||
The supported values are:
|
||||
|
||||
* `:enabled` - toggles coloring on and off (true)
|
||||
* `:doc_code` - code blocks (cyan, bright)
|
||||
* `:doc_inline_code` - inline code (cyan)
|
||||
* `:doc_headings` - h1 and h2 headings (yellow, bright)
|
||||
* `:doc_title` - top level heading (reverse, yellow, bright)
|
||||
* `:doc_bold` - bold text (bright)
|
||||
* `:doc_underline` - underlined text (underline)
|
||||
* `:width` - the width to format the text (80)
|
||||
* `:enabled` - toggles coloring on and off (true)
|
||||
* `:doc_code` - code blocks (cyan, bright)
|
||||
* `:doc_inline_code` - inline code (cyan)
|
||||
* `:doc_headings` - h1 and h2 headings (yellow, bright)
|
||||
* `:doc_title` - top level heading (reverse, yellow, bright)
|
||||
* `:doc_bold` - bold text (bright)
|
||||
* `:doc_underline` - underlined text (underline)
|
||||
* `:width` - the width to format the text (80)
|
||||
|
||||
Values for the color settings are strings with
|
||||
comma-separated ANSI values.
|
||||
@@ -192,7 +192,7 @@ defmodule IO.ANSI.Docs do
|
||||
|> handle_links
|
||||
|> handle_inline(nil, [], [], options)
|
||||
|> String.split(~r{\s})
|
||||
|> write_with_wrap(options[:width] - size(indent), indent, from_list)
|
||||
|> write_with_wrap(options[:width] - byte_size(indent), indent, from_list)
|
||||
|
||||
unless from_list, do: IO.puts(IO.ANSI.reset)
|
||||
end
|
||||
|
||||
@@ -14,14 +14,19 @@ defmodule IO.Stream do
|
||||
|
||||
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
|
||||
* `device` - the IO device
|
||||
* `raw` - a boolean indicating if bin functions should be used
|
||||
* `line_or_bytes` - if reading should read lines or a given amount of bytes
|
||||
|
||||
"""
|
||||
|
||||
defstruct device: nil, raw: true, line_or_bytes: :line
|
||||
|
||||
@doc false
|
||||
def __build__(device, raw, line_or_bytes) do
|
||||
%IO.Stream{device: device, raw: raw, line_or_bytes: line_or_bytes}
|
||||
end
|
||||
|
||||
defimpl Collectable do
|
||||
def empty(stream) do
|
||||
stream
|
||||
|
||||
+414
-420
File diff suppressed because it is too large
Load Diff
@@ -129,17 +129,17 @@ defmodule Kernel.CLI do
|
||||
end
|
||||
|
||||
defp parse_shared(["-pa", h|t], config) do
|
||||
Enum.each Path.wildcard(Path.expand(h)), &Code.prepend_path(&1)
|
||||
add_code_path(h, &Code.prepend_path/1)
|
||||
parse_shared t, config
|
||||
end
|
||||
|
||||
defp parse_shared(["-pz", h|t], config) do
|
||||
Enum.each Path.wildcard(Path.expand(h)), &Code.append_path(&1)
|
||||
add_code_path(h, &Code.append_path/1)
|
||||
parse_shared t, config
|
||||
end
|
||||
|
||||
defp parse_shared(["--app", h|t], config) do
|
||||
parse_shared t, %{config | commands: &[{:app, h}|&1]}
|
||||
parse_shared t, %{config | commands: [{:app, h} | config.commands]}
|
||||
end
|
||||
|
||||
defp parse_shared(["--no-halt"|t], config) do
|
||||
@@ -162,7 +162,7 @@ defmodule Kernel.CLI do
|
||||
parse_shared t, config
|
||||
end
|
||||
|
||||
defp parse_shared([erl|t], config) when erl in ["--detached", "--hidden", "--gen-debug"] do
|
||||
defp parse_shared([erl|t], config) when erl in ["--detached", "--hidden"] do
|
||||
parse_shared t, config
|
||||
end
|
||||
|
||||
@@ -170,6 +170,15 @@ defmodule Kernel.CLI do
|
||||
{list, config}
|
||||
end
|
||||
|
||||
|
||||
defp add_code_path(path, fun) do
|
||||
path = Path.expand(path)
|
||||
case Path.wildcard(path) do
|
||||
[] -> fun.(path)
|
||||
list -> Enum.each(list, fun)
|
||||
end
|
||||
end
|
||||
|
||||
# Process init options
|
||||
|
||||
defp parse_argv(["--"|t], config) do
|
||||
@@ -240,7 +249,7 @@ defmodule Kernel.CLI do
|
||||
"-" <> _ ->
|
||||
shared_option? list, config, &parse_compiler(&1, &2)
|
||||
_ ->
|
||||
pattern = if :filelib.is_dir(h), do: "#{h}/**/*.ex", else: h
|
||||
pattern = if File.dir?(h), do: "#{h}/**/*.ex", else: h
|
||||
parse_compiler t, %{config | compile: [pattern | config.compile]}
|
||||
end
|
||||
end
|
||||
@@ -315,7 +324,7 @@ defmodule Kernel.CLI do
|
||||
end
|
||||
|
||||
defp process_command({:file, file}, _config) when is_binary(file) do
|
||||
if :filelib.is_regular(file) do
|
||||
if File.regular?(file) do
|
||||
wrapper fn -> Code.require_file(file) end
|
||||
else
|
||||
{:error, "No file named #{file}"}
|
||||
@@ -323,9 +332,7 @@ defmodule Kernel.CLI do
|
||||
end
|
||||
|
||||
defp process_command({:require, pattern}, _config) when is_binary(pattern) do
|
||||
files = Path.wildcard(pattern)
|
||||
files = Enum.uniq(files)
|
||||
files = Enum.filter files, &:filelib.is_regular(&1)
|
||||
files = filter_patterns(pattern)
|
||||
|
||||
if files != [] do
|
||||
wrapper fn -> Enum.map files, &Code.require_file(&1) end
|
||||
@@ -335,9 +342,7 @@ defmodule Kernel.CLI do
|
||||
end
|
||||
|
||||
defp process_command({:parallel_require, pattern}, _config) when is_binary(pattern) do
|
||||
files = Path.wildcard(pattern)
|
||||
files = Enum.uniq(files)
|
||||
files = Enum.filter files, &:filelib.is_regular(&1)
|
||||
files = filter_patterns(pattern)
|
||||
|
||||
if files != [] do
|
||||
wrapper fn -> Kernel.ParallelRequire.files(files) end
|
||||
@@ -349,7 +354,7 @@ defmodule Kernel.CLI do
|
||||
defp process_command({:compile, patterns}, config) do
|
||||
:filelib.ensure_dir(:filename.join(config.output, "."))
|
||||
|
||||
case match_regular_files(patterns) do
|
||||
case filter_multiple_patterns(patterns) do
|
||||
{:ok, []} ->
|
||||
{:error, "No files matched provided patterns"}
|
||||
{:ok, files} ->
|
||||
@@ -363,9 +368,13 @@ defmodule Kernel.CLI do
|
||||
end
|
||||
end
|
||||
|
||||
defp match_regular_files(patterns) do
|
||||
defp filter_patterns(pattern) do
|
||||
Enum.filter(Enum.uniq(Path.wildcard(pattern)), &File.regular?(&1))
|
||||
end
|
||||
|
||||
defp filter_multiple_patterns(patterns) do
|
||||
matched_files = Enum.map patterns, fn(pattern) ->
|
||||
case Path.wildcard(pattern) do
|
||||
case filter_patterns(pattern) do
|
||||
[] -> {:missing, pattern}
|
||||
files -> {:ok, files}
|
||||
end
|
||||
@@ -380,9 +389,7 @@ defmodule Kernel.CLI do
|
||||
&elem(&1, 1)
|
||||
|
||||
if missing_patterns == [] do
|
||||
files = Enum.uniq(Enum.concat(files))
|
||||
files = Enum.filter files, &:filelib.is_regular(&1)
|
||||
{:ok, files}
|
||||
{:ok, Enum.uniq(Enum.concat(files))}
|
||||
else
|
||||
{:missing, Enum.uniq(missing_patterns)}
|
||||
end
|
||||
|
||||
@@ -17,9 +17,11 @@ defmodule Kernel.ParallelCompiler do
|
||||
|
||||
This function receives a set of callbacks as options:
|
||||
|
||||
* `:each_file` - for each file compiled, invokes the callback passing the file
|
||||
* `:each_module` - for each module compiled, invokes the callback
|
||||
passing the file, module and the module bytecode
|
||||
* `:each_file` - for each file compiled, invokes the callback passing the
|
||||
file
|
||||
|
||||
* `:each_module` - for each module compiled, invokes the callback passing
|
||||
the file, module and the module bytecode
|
||||
|
||||
The compiler doesn't care about the return values of the callbacks.
|
||||
Returns the modules generated by each compiled file.
|
||||
|
||||
@@ -118,18 +118,25 @@ defmodule Kernel.SpecialForms do
|
||||
end
|
||||
end
|
||||
|
||||
Now a struct can be created as follow:
|
||||
In practice though, structs are usually defined with the
|
||||
`Kernel.defstruct/1` macro:
|
||||
|
||||
defmodule User do
|
||||
defstruct name: "josé", age: 27
|
||||
end
|
||||
|
||||
Now a struct can be created as follows:
|
||||
|
||||
%User{}
|
||||
|
||||
Underneath, a struct is just a map with a `__struct__` field
|
||||
pointing to the User module:
|
||||
Underneath a struct is just a map with a `__struct__` field
|
||||
pointing to the `User` module:
|
||||
|
||||
%User{} == %{__struct__: User, name: "josé", age: 27}
|
||||
|
||||
A struct also validates the given keys are part of the defined
|
||||
A struct also validates that the given keys are part of the defined
|
||||
struct. The example below will fail because there is no key
|
||||
`:full_name` in the user struct:
|
||||
`:full_name` in the `User` struct:
|
||||
|
||||
%User{full_name: "José Valim"}
|
||||
|
||||
@@ -148,8 +155,10 @@ defmodule Kernel.SpecialForms do
|
||||
compilation time and it will guarantee at runtime the given
|
||||
argument is a struct, failing with `BadStructError` otherwise.
|
||||
|
||||
Check `Kernel.defprotocol/2` for more information on how structs
|
||||
can be used with protocols for polymorphic dispatch. Also,
|
||||
Although structs are maps, by default structs do not implement
|
||||
any of the protocols implemented for maps. Check
|
||||
`Kernel.defprotocol/2` for more information on how structs
|
||||
can be used with protocols for polymorphic dispatch. Also
|
||||
see `Kernel.struct/2` for examples on how to create and update
|
||||
structs dynamically.
|
||||
"""
|
||||
@@ -580,7 +589,7 @@ defmodule Kernel.SpecialForms do
|
||||
defmacro import(module, opts)
|
||||
|
||||
@doc """
|
||||
Returns the current environment information as a `Macro.Env[]` record.
|
||||
Returns the current environment information as a `Macro.Env` struct.
|
||||
|
||||
In the environment you can access the current filename,
|
||||
line numbers, set up aliases, the current function and others.
|
||||
@@ -603,6 +612,14 @@ defmodule Kernel.SpecialForms do
|
||||
"""
|
||||
defmacro __DIR__
|
||||
|
||||
@doc """
|
||||
Returns the current calling environment as a `Macro.Env` struct.
|
||||
|
||||
In the environment you can access the filename, line numbers,
|
||||
set up aliases, the function and others.
|
||||
"""
|
||||
defmacro __CALLER__
|
||||
|
||||
@doc """
|
||||
Accesses an already bound variable in match clauses.
|
||||
|
||||
@@ -653,23 +670,28 @@ defmodule Kernel.SpecialForms do
|
||||
The tuple above represents a function call to `sum` passing 1, 2 and
|
||||
3 as arguments. The tuple elements are:
|
||||
|
||||
* The first element of the tuple is always an atom or
|
||||
another tuple in the same representation;
|
||||
* The second element of the tuple represents metadata;
|
||||
* The third element of the tuple are the arguments for the
|
||||
function call. The third argument may be an atom, which is
|
||||
usually a variable (or a local call);
|
||||
* The first element of the tuple is always an atom or
|
||||
another tuple in the same representation.
|
||||
|
||||
* The second element of the tuple represents metadata.
|
||||
|
||||
* The third element of the tuple are the arguments for the
|
||||
function call. The third argument may be an atom, which is
|
||||
usually a variable (or a local call).
|
||||
|
||||
## Options
|
||||
|
||||
* `:unquote` - When false, disables unquoting. Useful when you have a quote
|
||||
inside another quote and want to control what quote is
|
||||
able to unquote;
|
||||
* `:location` - When set to `:keep`, keeps the current line and file from quote.
|
||||
Read the Stacktrace information section below for more information;
|
||||
* `:context` - Sets the resolution context;
|
||||
* `:bind_quoted` - Passes a binding to the macro. Whenever a binding is given,
|
||||
`unquote` is automatically disabled;
|
||||
* `:unquote` - when false, disables unquoting. Useful when you have a quote
|
||||
inside another quote and want to control what quote is able to unquote.
|
||||
|
||||
* `:location` - when set to `:keep`, keeps the current line and file from
|
||||
quote. Read the Stacktrace information section below for more
|
||||
information.
|
||||
|
||||
* `:context` - sets the resolution context.
|
||||
|
||||
* `:bind_quoted` - passes a binding to the macro. Whenever a binding is
|
||||
given, `unquote` is automatically disabled.
|
||||
|
||||
## Quote literals
|
||||
|
||||
@@ -1047,9 +1069,9 @@ defmodule Kernel.SpecialForms do
|
||||
we are passing the representation of the variable `kv`, our
|
||||
code fails.
|
||||
|
||||
This is actually a common pitfall when developing macros. In
|
||||
practice, we want to avoid doing work at compilation time as
|
||||
much as possible. That said, let's attempt to improve our macro:
|
||||
This is actually a common pitfall when developing macros. We are
|
||||
assuming a particular shape in the macro. We can work around it
|
||||
by unquoting the variable inside the quoted expression:
|
||||
|
||||
defmacro defkv(kv) do
|
||||
quote do
|
||||
@@ -1305,21 +1327,21 @@ defmodule Kernel.SpecialForms do
|
||||
|
||||
On the other hand, aliases holds some properties:
|
||||
|
||||
1) The head element of aliases can be any term;
|
||||
1. The head element of aliases can be any term.
|
||||
|
||||
2) The tail elements of aliases are guaranteed to always be atoms;
|
||||
2. The tail elements of aliases are guaranteed to always be atoms.
|
||||
|
||||
3) When the head element of aliases is the atom `:Elixir`, no expansion happen;
|
||||
3. When the head element of aliases is the atom `:Elixir`, no expansion happen.
|
||||
|
||||
4) When the head element of aliases is not an atom, it is expanded at runtime:
|
||||
4. When the head element of aliases is not an atom, it is expanded at runtime:
|
||||
|
||||
quote do: some_var.Foo
|
||||
{:__aliases__, [], [{:some_var, [], Elixir}, :Foo]}
|
||||
quote do: some_var.Foo
|
||||
{:__aliases__, [], [{:some_var, [], Elixir}, :Foo]}
|
||||
|
||||
Since `some_var` is not available at compilation time, the compiler
|
||||
expands such expression to:
|
||||
Since `some_var` is not available at compilation time, the compiler
|
||||
expands such expression to:
|
||||
|
||||
Module.concat [some_var, Foo]
|
||||
Module.concat [some_var, Foo]
|
||||
|
||||
"""
|
||||
defmacro __aliases__(args)
|
||||
@@ -1375,7 +1397,27 @@ defmodule Kernel.SpecialForms do
|
||||
case, clauses that do not explicitly bind a value have the variable
|
||||
bound to nil.
|
||||
"""
|
||||
defmacro case(condition, blocks)
|
||||
defmacro case(condition, clauses)
|
||||
|
||||
@doc """
|
||||
Evaluates the expression corresponding to the first clause that
|
||||
evaluates to truth value.
|
||||
|
||||
Raises an error if all conditions evaluate to to nil or false.
|
||||
|
||||
## Examples
|
||||
|
||||
cond do
|
||||
1 + 1 == 1 ->
|
||||
"This will never match"
|
||||
2 * 2 != 4 ->
|
||||
"Nor this"
|
||||
true ->
|
||||
"This will"
|
||||
end
|
||||
|
||||
"""
|
||||
defmacro cond(clauses)
|
||||
|
||||
@doc ~S"""
|
||||
Evaluate the given expressions and handle any error, exit
|
||||
@@ -1634,11 +1676,11 @@ defmodule Kernel.SpecialForms do
|
||||
The `after` clause can be specified even if there are no match clauses.
|
||||
There are two special cases for the timeout value given to `after`
|
||||
|
||||
* `:infinity` - The process should wait indefinitely for a matching
|
||||
message, this is the same as not using a timeout.
|
||||
* `:infinity` - the process should wait indefinitely for a matching
|
||||
message, this is the same as not using a timeout
|
||||
|
||||
* 0 - if there is no matching message in the mailbox, the timeout
|
||||
will occur immediately.
|
||||
* 0 - if there is no matching message in the mailbox, the timeout
|
||||
will occur immediately
|
||||
|
||||
## Variables handling
|
||||
|
||||
|
||||
+115
-116
@@ -104,36 +104,36 @@ defmodule Kernel.Typespec do
|
||||
|
||||
## Built-in types
|
||||
|
||||
Built-in type | Defined as
|
||||
:-------------------- | :---------
|
||||
`term` | `any`
|
||||
`binary` | `<< _ :: _ * 8 >>`
|
||||
`bitstring` | `<< _ :: _ * 1 >>`
|
||||
`boolean` | `false` | `true`
|
||||
`byte` | `0..255`
|
||||
`char` | `0..0xffff`
|
||||
`number` | `integer` | `float`
|
||||
`list` | `[any]`
|
||||
`maybe_improper_list` | `maybe_improper_list(any, any)`
|
||||
`nonempty_list` | `nonempty_list(any)`
|
||||
`iodata` | `iolist` | `binary`
|
||||
`iolist` | `maybe_improper_list(byte` | `binary` | `iolist, binary` | `[])`
|
||||
`module` | `atom`
|
||||
`mfa` | `{atom, atom, arity}`
|
||||
`arity` | `0..255`
|
||||
`node` | `atom`
|
||||
`timeout` | `:infinity` | `non_neg_integer`
|
||||
`no_return` | `none`
|
||||
`fun` | `(... -> any)`
|
||||
Built-in type | Defined as
|
||||
:-------------------- | :---------
|
||||
`term` | `any`
|
||||
`binary` | `<< _ :: _ * 8 >>`
|
||||
`bitstring` | `<< _ :: _ * 1 >>`
|
||||
`boolean` | `false` | `true`
|
||||
`byte` | `0..255`
|
||||
`char` | `0..0xffff`
|
||||
`number` | `integer` | `float`
|
||||
`list` | `[any]`
|
||||
`maybe_improper_list` | `maybe_improper_list(any, any)`
|
||||
`nonempty_list` | `nonempty_list(any)`
|
||||
`iodata` | `iolist` | `binary`
|
||||
`iolist` | `maybe_improper_list(byte` | `binary` | `iolist, binary` | `[])`
|
||||
`module` | `atom`
|
||||
`mfa` | `{atom, atom, arity}`
|
||||
`arity` | `0..255`
|
||||
`node` | `atom`
|
||||
`timeout` | `:infinity` | `non_neg_integer`
|
||||
`no_return` | `none`
|
||||
`fun` | `(... -> any)`
|
||||
|
||||
Some built-in types cannot be expressed with valid syntax according to the
|
||||
language defined above.
|
||||
|
||||
Built-in type | Can be interpreted as
|
||||
:---------------- | :--------------------
|
||||
`non_neg_integer` | `0..`
|
||||
`pos_integer` | `1..`
|
||||
`neg_integer` | `..-1`
|
||||
Built-in type | Can be interpreted as
|
||||
:---------------- | :--------------------
|
||||
`non_neg_integer` | `0..`
|
||||
`pos_integer` | `1..`
|
||||
`neg_integer` | `..-1`
|
||||
|
||||
Types defined in other modules are referred to as "remote types", they are
|
||||
referenced as `Module.type_name` (ex. `Enum.t` or `String.t`).
|
||||
@@ -262,60 +262,18 @@ defmodule Kernel.Typespec do
|
||||
end
|
||||
end
|
||||
|
||||
## Helpers
|
||||
|
||||
@doc """
|
||||
Defines a `type`, `typep` or `opaque` by receiving Erlang's typespec.
|
||||
Defines a `type`, `typep` or `opaque` by receiving a typespec expression.
|
||||
"""
|
||||
def define_type(caller, kind, {name, _, vars} = type) when kind in [:type, :typep, :opaque] do
|
||||
{kind, export} =
|
||||
case kind do
|
||||
:type -> {:type, true}
|
||||
:typep -> {:type, false}
|
||||
:opaque -> {:opaque, true}
|
||||
end
|
||||
|
||||
module = caller.module
|
||||
arity = length(vars)
|
||||
|
||||
Module.compile_typespec module, kind, type
|
||||
|
||||
if export do
|
||||
Module.compile_typespec(module, :export_type, [{name, arity}])
|
||||
end
|
||||
|
||||
define_doc(caller, kind, name, arity, export)
|
||||
type
|
||||
end
|
||||
|
||||
defp define_doc(caller, kind, name, arity, export) do
|
||||
module = caller.module
|
||||
doc = Module.get_attribute(module, :typedoc)
|
||||
|
||||
if doc do
|
||||
if export do
|
||||
Module.add_doc(module, caller.line, kind, {name, arity}, doc)
|
||||
else
|
||||
:elixir_errors.warn caller.line, caller.file, "type #{name}/#{arity} is private, " <>
|
||||
"@typedoc's are always discarded for private types\n"
|
||||
end
|
||||
end
|
||||
|
||||
Module.delete_attribute(module, :typedoc)
|
||||
def define_type(kind, expr, doc \\ nil, env) do
|
||||
Module.store_typespec(env.module, kind, {kind, expr, doc, env})
|
||||
end
|
||||
|
||||
@doc """
|
||||
Defines a `spec` by receiving Erlang's typespec.
|
||||
Defines a `spec` by receiving a typespec expression.
|
||||
"""
|
||||
def define_spec(module, tuple, definition) do
|
||||
Module.compile_typespec module, :spec, {tuple, definition}
|
||||
end
|
||||
|
||||
@doc """
|
||||
Defines a `callback` by receiving Erlang's typespec.
|
||||
"""
|
||||
def define_callback(module, tuple, definition) do
|
||||
Module.compile_typespec module, :callback, {tuple, definition}
|
||||
def define_spec(kind, expr, env) do
|
||||
Module.store_typespec(env.module, kind, {kind, expr, env})
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -324,9 +282,12 @@ defmodule Kernel.Typespec do
|
||||
for modules being compiled.
|
||||
"""
|
||||
def defines_type?(module, name, arity) do
|
||||
finder = &match?({^name, _, vars} when length(vars) == arity, &1)
|
||||
Enum.any?(Module.get_attribute(module, :type), finder) or
|
||||
Enum.any?(Module.get_attribute(module, :opaque), finder)
|
||||
finder = fn {_kind, expr, _doc, _caller} ->
|
||||
type_to_signature(expr) == {name, arity}
|
||||
end
|
||||
|
||||
:lists.any(finder, Module.get_attribute(module, :type)) or
|
||||
:lists.any(finder, Module.get_attribute(module, :opaque))
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -334,8 +295,10 @@ defmodule Kernel.Typespec do
|
||||
This function is only available for modules being compiled.
|
||||
"""
|
||||
def defines_spec?(module, name, arity) do
|
||||
tuple = {name, arity}
|
||||
Enum.any?(Module.get_attribute(module, :spec), &match?(^tuple, &1))
|
||||
finder = fn {_kind, expr, _caller} ->
|
||||
spec_to_signature(expr) == {name, arity}
|
||||
end
|
||||
:lists.any(finder, Module.get_attribute(module, :spec))
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -343,8 +306,10 @@ defmodule Kernel.Typespec do
|
||||
This function is only available for modules being compiled.
|
||||
"""
|
||||
def defines_callback?(module, name, arity) do
|
||||
tuple = {name, arity}
|
||||
Enum.any?(Module.get_attribute(module, :callback), &match?(^tuple, &1))
|
||||
finder = fn {_kind, expr, _caller} ->
|
||||
spec_to_signature(expr) == {name, arity}
|
||||
end
|
||||
:lists.any(finder, Module.get_attribute(module, :callback))
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -423,7 +388,7 @@ defmodule Kernel.Typespec do
|
||||
case abstract_code(module) do
|
||||
{:ok, abstract_code} ->
|
||||
type_docs = for {:attribute, _, :typedoc, tup} <- abstract_code, do: tup
|
||||
List.flatten(type_docs)
|
||||
:lists.flatten(type_docs)
|
||||
_ ->
|
||||
nil
|
||||
end
|
||||
@@ -443,7 +408,7 @@ defmodule Kernel.Typespec do
|
||||
case abstract_code(module) do
|
||||
{:ok, abstract_code} ->
|
||||
exported_types = for {:attribute, _, :export_type, types} <- abstract_code, do: types
|
||||
exported_types = List.flatten(exported_types)
|
||||
exported_types = :lists.flatten(exported_types)
|
||||
|
||||
for {:attribute, _, kind, {name, _, args} = type} <- abstract_code, kind in [:opaque, :type] do
|
||||
cond do
|
||||
@@ -514,10 +479,40 @@ defmodule Kernel.Typespec do
|
||||
binary
|
||||
end
|
||||
|
||||
## Helpers
|
||||
|
||||
@doc false
|
||||
def spec_to_signature({:when, _, [spec, _]}),
|
||||
do: type_to_signature(spec)
|
||||
def spec_to_signature(other),
|
||||
do: type_to_signature(other)
|
||||
|
||||
@doc false
|
||||
def type_to_signature({:::, _, [{name, _, nil}, _]}),
|
||||
do: {name, 0}
|
||||
def type_to_signature({:::, _, [{name, _, args}, _]}),
|
||||
do: {name, length(args)}
|
||||
|
||||
## Macro callbacks
|
||||
|
||||
@doc false
|
||||
def deftype(kind, {:::, _, [{name, _, args}, definition]}, caller) when is_atom(name) and name != ::: do
|
||||
def defspec(kind, expr, caller) do
|
||||
Module.store_typespec(caller.module, kind, {kind, expr, caller})
|
||||
end
|
||||
|
||||
@doc false
|
||||
def deftype(kind, expr, caller) do
|
||||
module = caller.module
|
||||
doc = Module.get_attribute(module, :typedoc)
|
||||
|
||||
Module.delete_attribute(module, :typedoc)
|
||||
Module.store_typespec(module, kind, {kind, expr, doc, caller})
|
||||
end
|
||||
|
||||
## Translation from Elixir AST to typespec AST
|
||||
|
||||
@doc false
|
||||
def translate_type(kind, {:::, _, [{name, _, args}, definition]}, doc, caller) when is_atom(name) and name != ::: do
|
||||
args =
|
||||
if is_atom(args) do
|
||||
[]
|
||||
@@ -525,31 +520,43 @@ defmodule Kernel.Typespec do
|
||||
for(arg <- args, do: variable(arg))
|
||||
end
|
||||
|
||||
vars = for {:var, _, var} <- args, do: var
|
||||
spec = typespec(definition, vars, caller)
|
||||
vars = for {:var, _, var} <- args, do: var
|
||||
spec = typespec(definition, vars, caller)
|
||||
|
||||
vars = for {:var, _, _} = var <- args, do: var
|
||||
type = {name, spec, vars}
|
||||
vars = for {:var, _, _} = var <- args, do: var
|
||||
type = {name, spec, vars}
|
||||
arity = length(vars)
|
||||
|
||||
define_type(caller, kind, type)
|
||||
{kind, export} =
|
||||
case kind do
|
||||
:type -> {:type, true}
|
||||
:typep -> {:type, false}
|
||||
:opaque -> {:opaque, true}
|
||||
end
|
||||
|
||||
if not export and doc do
|
||||
:elixir_errors.warn(caller.line, caller.file, "type #{name}/#{arity} is private, " <>
|
||||
"@typedoc's are always discarded for private types\n")
|
||||
end
|
||||
|
||||
{{kind, {name, arity}, type}, caller.line, export, doc}
|
||||
end
|
||||
|
||||
def deftype(_kind, other, caller) do
|
||||
def translate_type(_kind, other, _doc, caller) do
|
||||
type_spec = Macro.to_string(other)
|
||||
compile_error caller, "invalid type specification: #{type_spec}"
|
||||
end
|
||||
|
||||
@doc false
|
||||
|
||||
def defspec(type, {:when, _meta, [spec, guard]}, caller) do
|
||||
defspec(type, spec, guard, caller)
|
||||
def translate_spec(kind, {:when, _meta, [spec, guard]}, caller) do
|
||||
translate_spec(kind, spec, guard, caller)
|
||||
end
|
||||
|
||||
def defspec(type, spec, caller) do
|
||||
defspec(type, spec, [], caller)
|
||||
def translate_spec(kind, spec, caller) do
|
||||
translate_spec(kind, spec, [], caller)
|
||||
end
|
||||
|
||||
defp defspec(type, {:::, meta, [{name, _, args}, return]}, guard, caller) when is_atom(name) and name != ::: do
|
||||
defp translate_spec(kind, {:::, meta, [{name, _, args}, return]}, guard, caller) when is_atom(name) and name != ::: do
|
||||
if is_atom(args), do: args = []
|
||||
|
||||
unless Keyword.keyword?(guard) do
|
||||
@@ -565,12 +572,11 @@ defmodule Kernel.Typespec do
|
||||
spec = {:type, line(meta), :bounded_fun, [spec, constraints]}
|
||||
end
|
||||
|
||||
code = {{name, Kernel.length(args)}, spec}
|
||||
Module.compile_typespec(caller.module, type, code)
|
||||
code
|
||||
arity = length(args)
|
||||
{{kind, {name, arity}, spec}, caller.line}
|
||||
end
|
||||
|
||||
defp defspec(_type, spec, _guard, caller) do
|
||||
defp translate_spec(_kind, spec, _guard, caller) do
|
||||
spec = Macro.to_string(spec)
|
||||
compile_error caller, "invalid function type specification: #{spec}"
|
||||
end
|
||||
@@ -578,14 +584,14 @@ defmodule Kernel.Typespec do
|
||||
defp guard_to_constraints(guard, vars, meta, caller) do
|
||||
line = line(meta)
|
||||
|
||||
Enum.reduce(guard, [], fn
|
||||
:lists.foldl(fn
|
||||
{_name, {:var, _, context}}, acc when is_atom(context) ->
|
||||
acc
|
||||
{name, type}, acc ->
|
||||
constraint = [{:atom, line, :is_subtype}, [{:var, line, name}, typespec(type, vars, caller)]]
|
||||
type = {:type, line, :constraint, constraint}
|
||||
[type|acc]
|
||||
end) |> Enum.reverse
|
||||
end, [], guard) |> :lists.reverse
|
||||
end
|
||||
|
||||
## To AST conversion
|
||||
@@ -748,7 +754,7 @@ defmodule Kernel.Typespec do
|
||||
end
|
||||
end
|
||||
|
||||
## From AST conversion
|
||||
## To typespec conversion
|
||||
|
||||
defp line(meta) do
|
||||
case :lists.keyfind(:line, 1, meta) do
|
||||
@@ -779,9 +785,9 @@ defmodule Kernel.Typespec do
|
||||
|
||||
## Handle maps and structs
|
||||
defp typespec({:%{}, meta, fields}, vars, caller) do
|
||||
fields = Enum.map(fields, fn {k, v} ->
|
||||
fields = :lists.map(fn {k, v} ->
|
||||
{:type, line(meta), :map_field_assoc, typespec(k, vars, caller), typespec(v, vars, caller)}
|
||||
end)
|
||||
end, fields)
|
||||
{:type, line(meta), :map, fields}
|
||||
end
|
||||
|
||||
@@ -822,13 +828,6 @@ defmodule Kernel.Typespec do
|
||||
{:op, line(meta), op, {:integer, line(meta), integer}}
|
||||
end
|
||||
|
||||
# Handle access macro
|
||||
defp typespec({{:., meta, [Kernel, :access]}, meta1, [target, args]}, vars, caller) do
|
||||
access = {{:., meta, [Kernel, :access]}, meta1,
|
||||
[target, args ++ [_: {:any, [], []}]]}
|
||||
typespec(Macro.expand(access, caller), vars, caller)
|
||||
end
|
||||
|
||||
# Handle remote calls
|
||||
defp typespec({{:., meta, [remote, name]}, _, args} = orig, vars, caller) do
|
||||
remote = Macro.expand remote, caller
|
||||
@@ -859,7 +858,7 @@ defmodule Kernel.Typespec do
|
||||
|
||||
# Handle variables or local calls
|
||||
defp typespec({name, meta, atom}, vars, caller) when is_atom(atom) do
|
||||
if name in vars do
|
||||
if :lists.member(name, vars) do
|
||||
{:var, line(meta), name}
|
||||
else
|
||||
typespec({name, meta, []}, vars, caller)
|
||||
@@ -909,10 +908,10 @@ defmodule Kernel.Typespec do
|
||||
end
|
||||
|
||||
defp typespec(list, vars, caller) do
|
||||
[h|t] = Enum.reverse(list)
|
||||
union = Enum.reduce(t, validate_kw(h, list, caller), fn(x, acc) ->
|
||||
[h|t] = :lists.reverse(list)
|
||||
union = :lists.foldl(fn(x, acc) ->
|
||||
{:|, [], [validate_kw(x, list, caller), acc]}
|
||||
end)
|
||||
end, validate_kw(h, list, caller), t)
|
||||
typespec({:list, [], [union]}, vars, caller)
|
||||
end
|
||||
|
||||
|
||||
+21
-14
@@ -24,6 +24,7 @@ defmodule Keyword do
|
||||
in `Enum` and `List` can also be applied.
|
||||
"""
|
||||
|
||||
@compile :inline_list_funcs
|
||||
@behaviour Dict
|
||||
|
||||
@type key :: atom
|
||||
@@ -174,7 +175,12 @@ defmodule Keyword do
|
||||
"""
|
||||
@spec get_values(t, key) :: [value]
|
||||
def get_values(keywords, key) when is_list(keywords) and is_atom(key) do
|
||||
for {k, v} <- keywords, key == k, do: v
|
||||
fun = fn
|
||||
{k, v} when k === key -> {true, v}
|
||||
{_, _} -> false
|
||||
end
|
||||
|
||||
:lists.filtermap(fun, keywords)
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -192,7 +198,7 @@ defmodule Keyword do
|
||||
"""
|
||||
@spec keys(t) :: [key]
|
||||
def keys(keywords) when is_list(keywords) do
|
||||
for {key, _} <- keywords, do: key
|
||||
:lists.map(fn {k, _} -> k end, keywords)
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -206,11 +212,11 @@ defmodule Keyword do
|
||||
"""
|
||||
@spec values(t) :: [value]
|
||||
def values(keywords) when is_list(keywords) do
|
||||
for {_, value} <- keywords, do: value
|
||||
:lists.map(fn {_, v} -> v end, keywords)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Deletes the entry in the keyword list for a `key` with `value`.
|
||||
Deletes the entries in the keyword list for a `key` with `value`.
|
||||
If no `key` with `value` exists, returns the keyword list unchanged.
|
||||
|
||||
## Examples
|
||||
@@ -227,11 +233,11 @@ defmodule Keyword do
|
||||
"""
|
||||
@spec delete(t, key, value) :: t
|
||||
def delete(keywords, key, value) when is_list(keywords) and is_atom(key) do
|
||||
for {k, v} = tuple <- keywords, key != k or value != v, do: tuple
|
||||
:lists.filter(fn {k, v} -> k != key or v != value end, keywords)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Deletes all entries in the keyword list for a specific `key`.
|
||||
Deletes the entries in the keyword list for a specific `key`.
|
||||
If the `key` does not exist, returns the keyword list unchanged.
|
||||
Use `delete_first` to delete just the first entry in case of
|
||||
duplicated keys.
|
||||
@@ -250,7 +256,7 @@ defmodule Keyword do
|
||||
"""
|
||||
@spec delete(t, key) :: t
|
||||
def delete(keywords, key) when is_list(keywords) and is_atom(key) do
|
||||
for {k, _} = tuple <- keywords, key != k, do: tuple
|
||||
:lists.filter(fn {k, _} -> k != key end, keywords)
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -339,7 +345,8 @@ defmodule Keyword do
|
||||
"""
|
||||
@spec merge(t, t) :: t
|
||||
def merge(d1, d2) when is_list(d1) and is_list(d2) do
|
||||
d2 ++ for({k, _} = tuple <- d1, not has_key?(d2, k), do: tuple)
|
||||
fun = fn {k, _v} -> not has_key?(d2, k) end
|
||||
d2 ++ :lists.filter(fun, d1)
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -467,16 +474,16 @@ defmodule Keyword do
|
||||
|
||||
"""
|
||||
def split(keywords, keys) when is_list(keywords) do
|
||||
acc = {[], []}
|
||||
|
||||
{take, drop} = Enum.reduce keywords, acc, fn({k, v}, {take, drop}) ->
|
||||
fun = fn {k, v}, {take, drop} ->
|
||||
case k in keys do
|
||||
true -> {[{k, v}|take], drop}
|
||||
false -> {take, [{k, v}|drop]}
|
||||
end
|
||||
end
|
||||
|
||||
{Enum.reverse(take), Enum.reverse(drop)}
|
||||
acc = {[], []}
|
||||
{take, drop} = :lists.foldl(fun, acc, keywords)
|
||||
{:lists.reverse(take), :lists.reverse(drop)}
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -497,7 +504,7 @@ defmodule Keyword do
|
||||
|
||||
"""
|
||||
def take(keywords, keys) when is_list(keywords) do
|
||||
for {k, _} = tuple <- keywords, k in keys, do: tuple
|
||||
:lists.filter(fn {k, _} -> k in keys end, keywords)
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -517,7 +524,7 @@ defmodule Keyword do
|
||||
|
||||
"""
|
||||
def drop(keywords, keys) when is_list(keywords) do
|
||||
for {k, _} = tuple <- keywords, not k in keys, do: tuple
|
||||
:lists.filter(fn {k, _} -> not k in keys end, keywords)
|
||||
end
|
||||
|
||||
@doc """
|
||||
|
||||
@@ -580,34 +580,6 @@ defmodule List do
|
||||
end
|
||||
end
|
||||
|
||||
@doc false
|
||||
def from_char_data(char_data) do
|
||||
case :unicode.characters_to_list(char_data) do
|
||||
result when is_list(result) ->
|
||||
{:ok, result}
|
||||
|
||||
{:error, _, _} = error ->
|
||||
error
|
||||
|
||||
{:incomplete, _, _} = incomplete ->
|
||||
incomplete
|
||||
end
|
||||
end
|
||||
|
||||
@doc false
|
||||
def from_char_data!(char_data) do
|
||||
case :unicode.characters_to_list(char_data) do
|
||||
result when is_list(result) ->
|
||||
result
|
||||
|
||||
{:error, encoded, rest} ->
|
||||
raise UnicodeConversionError, encoded: encoded, rest: rest, kind: :invalid
|
||||
|
||||
{:incomplete, encoded, rest} ->
|
||||
raise UnicodeConversionError, encoded: encoded, rest: rest, kind: :incomplete
|
||||
end
|
||||
end
|
||||
|
||||
## Helpers
|
||||
|
||||
# replace_at
|
||||
|
||||
+35
-10
@@ -109,6 +109,31 @@ defmodule Macro do
|
||||
other
|
||||
end
|
||||
|
||||
@doc """
|
||||
Genrates a AST node representing the variable given
|
||||
by the atoms `var` and `context`.
|
||||
|
||||
## Examples
|
||||
|
||||
In order to build a variable, a context is expected.
|
||||
Most of the times, in order to preserve hygiene, the
|
||||
context must be `__MODULE__`:
|
||||
|
||||
iex> Macro.var(:foo, __MODULE__)
|
||||
{:foo, [], __MODULE__}
|
||||
|
||||
However, if there is a need to access the user variable,
|
||||
nil can be given:
|
||||
|
||||
iex> Macro.var(:foo, nil)
|
||||
{:foo, [], nil}
|
||||
|
||||
"""
|
||||
@spec var(var, context) :: {var, [], context} when var: atom, context: atom
|
||||
def var(var, context) when is_atom(var) and is_atom(context) do
|
||||
{var, [], context}
|
||||
end
|
||||
|
||||
@doc """
|
||||
Performs a depth-first, pre-order traversal of quoted expressions.
|
||||
"""
|
||||
@@ -488,8 +513,8 @@ defmodule Macro do
|
||||
end
|
||||
|
||||
# Access
|
||||
def to_string({{:., _, [Kernel, :access]}, _, [left, right]} = ast, fun) do
|
||||
fun.(ast, to_string(left, fun) <> to_string(right, fun))
|
||||
def to_string({{:., _, [Access, :get]}, _, [left, right]} = ast, fun) do
|
||||
fun.(ast, to_string(left, fun) <> to_string([right], fun))
|
||||
end
|
||||
|
||||
# All other calls
|
||||
@@ -521,7 +546,7 @@ defmodule Macro do
|
||||
end
|
||||
|
||||
# All other structures
|
||||
def to_string(other, fun), do: fun.(other, inspect(other, records: false))
|
||||
def to_string(other, fun), do: fun.(other, inspect(other, []))
|
||||
|
||||
# Block keywords
|
||||
@kw_keywords [:do, :catch, :rescue, :after, :else]
|
||||
@@ -531,12 +556,12 @@ defmodule Macro do
|
||||
end
|
||||
defp kw_blocks?(_), do: false
|
||||
|
||||
defp module_to_string(atom, _fun) when is_atom(atom), do: inspect(atom, records: false)
|
||||
defp module_to_string(atom, _fun) when is_atom(atom), do: inspect(atom, [])
|
||||
defp module_to_string(other, fun), do: call_to_string(other, fun)
|
||||
|
||||
defp call_to_string(atom, _fun) when is_atom(atom), do: Atom.to_string(atom)
|
||||
defp call_to_string({:., _, [arg]}, fun), do: module_to_string(arg, fun) <> "."
|
||||
defp call_to_string({:., _, [left, right]}, fun), do: module_to_string(left, fun) <> "." <> call_to_string(right, fun)
|
||||
defp call_to_string({:., _, [arg]}, fun), do: module_to_string(arg, fun) <> "."
|
||||
defp call_to_string({:., _, [left, right]}, fun), do: module_to_string(left, fun) <> "." <> call_to_string(right, fun)
|
||||
defp call_to_string(other, fun), do: to_string(other, fun)
|
||||
|
||||
defp call_to_string_with_args(target, args, fun) do
|
||||
@@ -661,10 +686,10 @@ defmodule Macro do
|
||||
|
||||
The following contents are expanded:
|
||||
|
||||
* Macros (local or remote);
|
||||
* Aliases are expanded (if possible) and return atoms;
|
||||
* Pseudo-variables (`__ENV__`, `__MODULE__` and `__DIR__`);
|
||||
* Module attributes reader (`@foo`);
|
||||
* Macros (local or remote)
|
||||
* Aliases are expanded (if possible) and return atoms
|
||||
* Pseudo-variables (`__ENV__`, `__MODULE__` and `__DIR__`)
|
||||
* Module attributes reader (`@foo`)
|
||||
|
||||
If the expression cannot be expanded, it returns the expression
|
||||
itself. Notice that `expand_once/2` performs the expansion just
|
||||
|
||||
+21
-19
@@ -6,25 +6,27 @@ defmodule Macro.Env do
|
||||
`__ENV__`. Inside macros, the caller environment can be
|
||||
accessed as `__CALLER__`. It contains the following fields:
|
||||
|
||||
* `module` - the current module name.
|
||||
* `file` - the current file name as a binary
|
||||
* `line` - the current line as an integer
|
||||
* `function` - a tuple as `{atom, integer`}, where the first
|
||||
element is the function name and the seconds its arity. Returns
|
||||
`nil` if not inside a function
|
||||
* `context` - the context of the environment. It can be nil
|
||||
(default context), inside a guard or inside an assign
|
||||
* `aliases` - a list of two item tuples, where the first
|
||||
item is the aliased name and the second the actual name
|
||||
* `requires` - the list of required modules
|
||||
* `functions` - a list of functions imported from each module
|
||||
* `macros` - a list of macros imported from each module
|
||||
* `macro_aliases` - a list of aliases defined inside the current macro
|
||||
* `context_modules` - a list of modules defined in the current context
|
||||
* `vars` - a list keeping all defined variables as {var, context}
|
||||
* `export_vars` - a list keeping all variables to be exported in a construct (may be nil)
|
||||
* `lexical_tracker` - PID to the lexical tracker which is responsible to keep user info
|
||||
* `local` - the module to expand local functions to
|
||||
* `module` - the current module name
|
||||
* `file` - the current file name as a binary
|
||||
* `line` - the current line as an integer
|
||||
* `function` - a tuple as `{atom, integer`}, where the first
|
||||
element is the function name and the seconds its arity; returns
|
||||
`nil` if not inside a function
|
||||
* `context` - the context of the environment; it can be `nil`
|
||||
(default context), inside a guard or inside an assign
|
||||
* `aliases` - a list of two item tuples, where the first
|
||||
item is the aliased name and the second the actual name
|
||||
* `requires` - the list of required modules
|
||||
* `functions` - a list of functions imported from each module
|
||||
* `macros` - a list of macros imported from each module
|
||||
* `macro_aliases` - a list of aliases defined inside the current macro
|
||||
* `context_modules` - a list of modules defined in the current context
|
||||
* `vars` - a list keeping all defined variables as `{var, context}`
|
||||
* `export_vars` - a list keeping all variables to be exported in a
|
||||
construct (may be `nil`)
|
||||
* `lexical_tracker` - PID of the lexical tracker which is responsible to
|
||||
keep user info
|
||||
* `local` - the module to expand local functions to
|
||||
"""
|
||||
|
||||
@type name_arity :: {atom, non_neg_integer}
|
||||
|
||||
@@ -11,7 +11,7 @@ defmodule Map do
|
||||
their APIs, please consult the `Dict` module.
|
||||
"""
|
||||
|
||||
use Dict.Behaviour
|
||||
use Dict
|
||||
|
||||
defdelegate [keys(map), values(map), size(map), merge(map1, map2), to_list(map)], to: :maps
|
||||
|
||||
|
||||
+48
-39
@@ -14,7 +14,7 @@ defmodule Module do
|
||||
Each module can be decorated with one or more attributes. The following ones
|
||||
are currently defined by Elixir:
|
||||
|
||||
* `@after_compile`
|
||||
* `@after_compile`
|
||||
|
||||
A hook that will be invoked right after the current module is compiled.
|
||||
|
||||
@@ -33,7 +33,7 @@ defmodule Module do
|
||||
end
|
||||
end
|
||||
|
||||
* `@before_compile`
|
||||
* `@before_compile`
|
||||
|
||||
A hook that will be invoked before the module is compiled.
|
||||
|
||||
@@ -63,7 +63,7 @@ defmodule Module do
|
||||
@before_compile A
|
||||
end
|
||||
|
||||
* `@behaviour` (notice the British spelling)
|
||||
* `@behaviour` (notice the British spelling)
|
||||
|
||||
Specify an OTP or user-defined behaviour.
|
||||
|
||||
@@ -75,7 +75,7 @@ defmodule Module do
|
||||
# ...
|
||||
end
|
||||
|
||||
* `@compile`
|
||||
* `@compile`
|
||||
|
||||
Define options for module compilation that are passed to the Erlang
|
||||
compiler.
|
||||
@@ -95,7 +95,7 @@ defmodule Module do
|
||||
end
|
||||
end
|
||||
|
||||
* `@doc`
|
||||
* `@doc`
|
||||
|
||||
Provide documentation for the function or macro that follows the
|
||||
attribute.
|
||||
@@ -122,7 +122,7 @@ defmodule Module do
|
||||
end
|
||||
end
|
||||
|
||||
* `@file`
|
||||
* `@file`
|
||||
|
||||
Change the filename used in stacktraces for the function or macro that
|
||||
follows the attribute.
|
||||
@@ -139,7 +139,7 @@ defmodule Module do
|
||||
end
|
||||
end
|
||||
|
||||
* `@moduledoc`
|
||||
* `@moduledoc`
|
||||
|
||||
Provide documentation for the current module.
|
||||
|
||||
@@ -156,7 +156,7 @@ defmodule Module do
|
||||
end
|
||||
|
||||
|
||||
* `@on_definition`
|
||||
* `@on_definition`
|
||||
|
||||
A hook that will be invoked when each function or macro in the current
|
||||
module is defined. Useful when annotating functions.
|
||||
@@ -211,7 +211,7 @@ defmodule Module do
|
||||
end
|
||||
end
|
||||
|
||||
* `@on_load`
|
||||
* `@on_load`
|
||||
|
||||
A hook that will be invoked whenever the module is loaded.
|
||||
|
||||
@@ -237,7 +237,7 @@ defmodule Module do
|
||||
end
|
||||
end
|
||||
|
||||
* `@vsn`
|
||||
* `@vsn`
|
||||
|
||||
Specify the module version. Accepts any valid Elixir value.
|
||||
|
||||
@@ -247,14 +247,25 @@ defmodule Module do
|
||||
@vsn "1.0"
|
||||
end
|
||||
|
||||
* `@external_resource`
|
||||
|
||||
Specify an external resource to the current module.
|
||||
|
||||
Many times a module embeds information from an external file. This
|
||||
attribute allows the module to annotate which external resources
|
||||
have been used.
|
||||
|
||||
Tools like Mix may use this information to ensure the module is
|
||||
recompiled in case any of the external resources change.
|
||||
|
||||
The following attributes are part of typespecs and are also reserved by
|
||||
Elixir (see `Kernel.Typespec` for more information about typespecs):
|
||||
|
||||
* `@type` - defines a type to be used in `@spec`
|
||||
* `@typep` - defines a private type to be used in `@spec`
|
||||
* `@opaque` - defines an opaque type to be used in `@spec`
|
||||
* `@spec` - provides a specification for a function
|
||||
* `@callback` - provides a specification for the behaviour callback
|
||||
* `@type` - defines a type to be used in `@spec`
|
||||
* `@typep` - defines a private type to be used in `@spec`
|
||||
* `@opaque` - defines an opaque type to be used in `@spec`
|
||||
* `@spec` - provides a specification for a function
|
||||
* `@callback` - provides a specification for the behaviour callback
|
||||
|
||||
In addition to the built-in attributes outlined above, custom attributes may
|
||||
also be added. A custom attribute is any valid identifier prefixed with an
|
||||
@@ -280,18 +291,11 @@ defmodule Module do
|
||||
Each module gets an `__info__/1` function when it's compiled. The function
|
||||
takes one of the following atoms:
|
||||
|
||||
* `:functions` - keyword list of public functions along with their arities
|
||||
* `:functions` - keyword list of public functions along with their arities
|
||||
|
||||
* `:macros` - keyword list of public macros along with their arities
|
||||
* `:macros` - keyword list of public macros along with their arities
|
||||
|
||||
* `:docs` - list of all docstrings attached to functions and macros
|
||||
using the `@doc` attribute
|
||||
|
||||
* `:moduledoc` - tuple `{<line>, <doc>}` where `line` is the line on
|
||||
which module definition starts and `doc` is the string
|
||||
attached to the module using the `@moduledoc` attribute
|
||||
|
||||
* `:module` - module name (`Module == Module.__info__(:module)`)
|
||||
* `:module` - module name (`Module == Module.__info__(:module)`)
|
||||
|
||||
In addition to the above, you may also pass to `__info__/1` any atom supported
|
||||
by Erlang's `module_info` function which also gets defined for each compiled
|
||||
@@ -356,8 +360,10 @@ defmodule Module do
|
||||
|
||||
@doc """
|
||||
Creates a module with the given name and defined by
|
||||
the given quoted expressions. The line where the module
|
||||
is defined and its file can be passed as options.
|
||||
the given quoted expressions.
|
||||
|
||||
The line where the module is defined and its file **must**
|
||||
be passed as options.
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -383,13 +389,16 @@ defmodule Module do
|
||||
when defining the module, while `defmodule` automatically
|
||||
shares the same environment.
|
||||
"""
|
||||
def create(module, quoted, opts \\ [])
|
||||
def create(module, quoted, opts)
|
||||
|
||||
def create(module, quoted, %Macro.Env{} = env) do
|
||||
create(module, quoted, Map.to_list(env))
|
||||
end
|
||||
|
||||
def create(module, quoted, opts) when is_atom(module) do
|
||||
def create(module, quoted, opts) when is_atom(module) and is_list(opts) do
|
||||
unless Keyword.has_key?(opts, :file) do
|
||||
raise ArgumentError, "expected :file to be given as option"
|
||||
end
|
||||
:elixir_module.compile(module, quoted, [], :elixir.env_for_eval(opts))
|
||||
end
|
||||
|
||||
@@ -507,9 +516,9 @@ defmodule Module do
|
||||
assert_not_compiled!(:add_doc, module)
|
||||
table = docs_table_for(module)
|
||||
|
||||
{signature, _} = Enum.map_reduce signature, 1, fn(x, acc) ->
|
||||
{signature, _} = :lists.mapfoldl fn(x, acc) ->
|
||||
{simplify_signature(x, acc), acc + 1}
|
||||
end
|
||||
end, 1, signature
|
||||
|
||||
case :ets.lookup(table, tuple) do
|
||||
[] ->
|
||||
@@ -544,7 +553,7 @@ defmodule Module do
|
||||
end
|
||||
|
||||
defp simplify_signature({var, _, atom}, _i) when is_atom(atom) do
|
||||
case atom_to_binary(var) do
|
||||
case Atom.to_string(var) do
|
||||
"_" <> rest -> {String.to_atom(rest), [], Elixir}
|
||||
_ -> {var, [], nil}
|
||||
end
|
||||
@@ -804,7 +813,7 @@ defmodule Module do
|
||||
end
|
||||
|
||||
defp warn_info([entry|_]) do
|
||||
opts = elem(entry, size(entry) - 1)
|
||||
opts = elem(entry, tuple_size(entry) - 1)
|
||||
Exception.format_file_line(Keyword.get(opts, :file), Keyword.get(opts, :line)) <> " "
|
||||
end
|
||||
|
||||
@@ -838,12 +847,12 @@ defmodule Module do
|
||||
|
||||
When registering an attribute, two options can be given:
|
||||
|
||||
* `:accumulate` - Several calls to the same attribute will
|
||||
accumulate instead of override the previous one. New attributes
|
||||
are always added to the top of the accumulated list.
|
||||
* `:accumulate` - several calls to the same attribute will
|
||||
accumulate instead of override the previous one. New attributes
|
||||
are always added to the top of the accumulated list.
|
||||
|
||||
* `:persist` - The attribute will be persisted in the Erlang
|
||||
Abstract Format. Useful when interfacing with Erlang libraries.
|
||||
* `:persist` - the attribute will be persisted in the Erlang
|
||||
Abstract Format. Useful when interfacing with Erlang libraries.
|
||||
|
||||
By default, both options are `false`.
|
||||
|
||||
@@ -911,7 +920,7 @@ defmodule Module do
|
||||
@doc false
|
||||
# Used internally to compile types. This function
|
||||
# is private and must be used only internally.
|
||||
def compile_typespec(module, key, value) when is_atom(key) do
|
||||
def store_typespec(module, key, value) when is_atom(key) do
|
||||
assert_not_compiled!(:put_attribute, module)
|
||||
table = data_table_for(module)
|
||||
|
||||
|
||||
@@ -10,6 +10,31 @@ defmodule Node do
|
||||
|
||||
@type t :: node
|
||||
|
||||
@doc """
|
||||
Turns a non-distributed node into a distributed node.
|
||||
|
||||
This functionality starts the `:net_kernel` and other
|
||||
related processes.
|
||||
"""
|
||||
@spec start(node, :longnames | :shortnames, non_neg_integer) ::
|
||||
{:ok, pid} | {:error, term}
|
||||
def start(name, type \\ :longnames, tick_time \\ 15000) do
|
||||
:net_kernel.start([name, type, tick_time])
|
||||
end
|
||||
|
||||
@doc """
|
||||
Turns a distributed node into a non-distributed node.
|
||||
|
||||
For other nodes in the network, this is the same as the node going down.
|
||||
Only possible when the node was started with `Node.start/3`, otherwise
|
||||
returns `{:error, :not_allowed}`. Returns `{:error, :not_found}` if the
|
||||
local node is not alive.
|
||||
"""
|
||||
@spec stop() :: :ok | {:error, term}
|
||||
def stop() do
|
||||
:net_kernel.stop()
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns the current node.
|
||||
|
||||
|
||||
@@ -32,7 +32,9 @@ defmodule OptionParser do
|
||||
|
||||
Note Elixir also converts the switches to underscore atoms, as
|
||||
`--source-path` becomes `:source_path`, to better suit Elixir
|
||||
conventions.
|
||||
conventions. This means that option names on the command line cannot contain
|
||||
underscores; such options will be reported as `:undefined` (in strict mode)
|
||||
or `:invalid` (in basic mode).
|
||||
|
||||
## Switches
|
||||
|
||||
@@ -40,22 +42,22 @@ defmodule OptionParser do
|
||||
switches and their formats. The switches can be specified via two
|
||||
different options:
|
||||
|
||||
* `:strict` - the switches are strict. Any switch that does not
|
||||
exist in the switch list is treated as an error;
|
||||
* `:strict` - the switches are strict. Any switch that does not
|
||||
exist in the switch list is treated as an error.
|
||||
|
||||
* `:switches` - configure some switches. Switches that does not
|
||||
exist in the switch list are still attempted to be parsed;
|
||||
* `:switches` - configure some switches. Switches that does not
|
||||
exist in the switch list are still attempted to be parsed.
|
||||
|
||||
Note only `:strict` or `:switches` may be given at once.
|
||||
|
||||
For each switch, the following types are supported:
|
||||
|
||||
* `:boolean` - Marks the given switch as a boolean. Boolean switches
|
||||
never consume the following value unless it is
|
||||
`true` or `false`;
|
||||
* `:integer` - Parses the switch as an integer;
|
||||
* `:float` - Parses the switch as a float;
|
||||
* `:string` - Returns the switch as a string;
|
||||
* `:boolean` - marks the given switch as a boolean. Boolean switches
|
||||
never consume the following value unless it is `true` or
|
||||
`false`.
|
||||
* `:integer` - parses the switch as an integer.
|
||||
* `:float` - parses the switch as a float.
|
||||
* `:string` - returns the switch as a string.
|
||||
|
||||
If a switch can't be parsed or is not specfied in the strict case,
|
||||
the option is returned in the invalid options list (third element
|
||||
@@ -63,7 +65,7 @@ defmodule OptionParser do
|
||||
|
||||
The following extra "types" are supported:
|
||||
|
||||
* `:keep` - Keeps duplicated items in the list instead of overriding;
|
||||
* `:keep` - keeps duplicated items in the list instead of overriding
|
||||
|
||||
Examples:
|
||||
|
||||
@@ -176,15 +178,17 @@ defmodule OptionParser do
|
||||
as both functions are built on top of next. This function
|
||||
may return:
|
||||
|
||||
* `{:ok, key, value, rest}` - the option `key` with `value` was successfully parsed
|
||||
* `{:ok, key, value, rest}` - the option `key` with `value` was
|
||||
successfully parsed
|
||||
|
||||
* `{:invalid, key, value, rest}` - the option `key` is invalid with `value`
|
||||
(returned when the switch type does not match the one given via the command line)
|
||||
* `{:invalid, key, value, rest}` - the option `key` is invalid with `value`
|
||||
(returned when the switch type does not match the one given via the
|
||||
command line)
|
||||
|
||||
* `{:undefined, key, value, rest}` - the option `key` is undefined
|
||||
(returned on strict cases and the switch is unknown)
|
||||
* `{:undefined, key, value, rest}` - the option `key` is undefined
|
||||
(returned on strict cases and the switch is unknown)
|
||||
|
||||
* `{:error, rest}` - there are no switches at the top of the given argv
|
||||
* `{:error, rest}` - there are no switches at the top of the given argv
|
||||
"""
|
||||
|
||||
@spec next(argv, options) ::
|
||||
@@ -224,7 +228,7 @@ defmodule OptionParser do
|
||||
else
|
||||
{opt_name, kinds, value} = normalize_option(tagged, value, switches)
|
||||
{value, kinds, rest} = normalize_value(value, kinds, rest, strict)
|
||||
case validate_option(opt_name, value, kinds) do
|
||||
case validate_option(value, kinds) do
|
||||
{:ok, new_value} -> {:ok, opt_name, new_value, rest}
|
||||
:invalid -> {:invalid, opt_name_bin, value, rest}
|
||||
end
|
||||
@@ -252,31 +256,31 @@ defmodule OptionParser do
|
||||
{aliases, switches, strict, all}
|
||||
end
|
||||
|
||||
defp validate_option(option, value, kinds) do
|
||||
{invalid_opt, value} = cond do
|
||||
defp validate_option(value, kinds) do
|
||||
{is_invalid, value} = cond do
|
||||
:invalid in kinds ->
|
||||
{option, value}
|
||||
{true, value}
|
||||
:boolean in kinds ->
|
||||
case value do
|
||||
t when t in [true, "true"] -> {nil, true}
|
||||
f when f in [false, "false"] -> {nil, false}
|
||||
_ -> {option, value}
|
||||
_ -> {true, value}
|
||||
end
|
||||
:integer in kinds ->
|
||||
case Integer.parse(value) do
|
||||
{value, ""} -> {nil, value}
|
||||
_ -> {option, value}
|
||||
_ -> {true, value}
|
||||
end
|
||||
:float in kinds ->
|
||||
case Float.parse(value) do
|
||||
{value, ""} -> {nil, value}
|
||||
_ -> {option, value}
|
||||
_ -> {true, value}
|
||||
end
|
||||
true ->
|
||||
{nil, value}
|
||||
end
|
||||
|
||||
if invalid_opt do
|
||||
if is_invalid do
|
||||
:invalid
|
||||
else
|
||||
{:ok, value}
|
||||
@@ -301,11 +305,11 @@ defmodule OptionParser do
|
||||
if alias = aliases[opt] do
|
||||
{:default, alias}
|
||||
else
|
||||
{:unknown, opt}
|
||||
:unknown
|
||||
end
|
||||
end
|
||||
|
||||
defp option_defined?({:unknown, _option}, _switches) do
|
||||
defp option_defined?(:unknown, _switches) do
|
||||
false
|
||||
end
|
||||
|
||||
@@ -317,8 +321,8 @@ defmodule OptionParser do
|
||||
Keyword.has_key?(switches, option)
|
||||
end
|
||||
|
||||
defp normalize_option({:unknown, option}, value, _switches) do
|
||||
{option, [:invalid], value}
|
||||
defp normalize_option(:unknown, value, _switches) do
|
||||
{nil, [:invalid], value}
|
||||
end
|
||||
|
||||
defp normalize_option({:negated, option}, nil, switches) do
|
||||
@@ -374,12 +378,22 @@ defmodule OptionParser do
|
||||
end
|
||||
end
|
||||
|
||||
defp to_underscore(option) do
|
||||
for <<c <- option>>, into: "", do: << if(c == ?-, do: ?_, else: c) >>
|
||||
end
|
||||
defp to_underscore(option), do: to_underscore(option, <<>>)
|
||||
|
||||
defp to_underscore("_" <> _rest, _acc), do: nil
|
||||
|
||||
defp to_underscore("-" <> rest, acc),
|
||||
do: to_underscore(rest, acc <> "_")
|
||||
|
||||
defp to_underscore(<<c>> <> rest, acc),
|
||||
do: to_underscore(rest, <<acc::binary, c>>)
|
||||
|
||||
defp to_underscore(<<>>, acc), do: acc
|
||||
|
||||
defp get_option(option) do
|
||||
option |> to_underscore |> String.to_atom
|
||||
if str = to_underscore(option) do
|
||||
String.to_atom(str)
|
||||
end
|
||||
end
|
||||
|
||||
defp reverse_negated(negated) do
|
||||
@@ -387,16 +401,23 @@ defmodule OptionParser do
|
||||
end
|
||||
|
||||
defp get_negated("no-" <> rest = option, value, switches) do
|
||||
negated = get_option(rest)
|
||||
option = if Keyword.has_key?(switches, negated) and value == nil do
|
||||
negated
|
||||
if negated = get_option(rest) do
|
||||
option = if Keyword.has_key?(switches, negated) and value == nil do
|
||||
negated
|
||||
else
|
||||
get_option(option)
|
||||
end
|
||||
{:negated, option}
|
||||
else
|
||||
get_option(option)
|
||||
:unknown
|
||||
end
|
||||
{:negated, option}
|
||||
end
|
||||
|
||||
defp get_negated(rest, _value, _switches) do
|
||||
{:default, get_option(rest)}
|
||||
if option = get_option(rest) do
|
||||
{:default, option}
|
||||
else
|
||||
:unknown
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
+61
-15
@@ -62,10 +62,13 @@ defmodule Path do
|
||||
case type(path) do
|
||||
:relative -> join(relative_to, path)
|
||||
:absolute ->
|
||||
if :binary.last(path) == ?/ do
|
||||
binary_part(path, 0, byte_size(path) - 1)
|
||||
else
|
||||
path
|
||||
cond do
|
||||
path == "/" ->
|
||||
path
|
||||
:binary.last(path) == ?/ ->
|
||||
binary_part(path, 0, byte_size(path) - 1)
|
||||
true ->
|
||||
path
|
||||
end
|
||||
:volumerelative ->
|
||||
relative_to = IO.chardata_to_string(relative_to)
|
||||
@@ -138,6 +141,7 @@ defmodule Path do
|
||||
|
||||
## Unix examples
|
||||
|
||||
Path.type("/") #=> :absolute
|
||||
Path.type("/usr/local/bin") #=> :absolute
|
||||
Path.type("usr/local/bin") #=> :relative
|
||||
Path.type("../usr/local/bin") #=> :relative
|
||||
@@ -479,6 +483,32 @@ defmodule Path do
|
||||
FN.split(IO.chardata_to_string(path))
|
||||
end
|
||||
|
||||
defmodule Wildcard do
|
||||
@moduledoc false
|
||||
|
||||
def read_file_info(file) do
|
||||
call({:read_link_info, file})
|
||||
end
|
||||
|
||||
def list_dir(dir) do
|
||||
case call({:list_dir, dir}) do
|
||||
{:ok, files} ->
|
||||
{:ok, for(file <- files, hd(file) != ?., do: file)}
|
||||
other ->
|
||||
other
|
||||
end
|
||||
end
|
||||
|
||||
@compile {:inline, call: 1}
|
||||
|
||||
defp call(tuple) do
|
||||
x = :erlang.dt_spread_tag(true)
|
||||
y = :gen_server.call(:file_server_2, tuple)
|
||||
:erlang.dt_restore_tag(x)
|
||||
y
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Traverses paths according to the given `glob` expression.
|
||||
|
||||
@@ -486,19 +516,26 @@ defmodule Path do
|
||||
"wildcard characters" are interpreted in a special way. The
|
||||
following characters are special:
|
||||
|
||||
* `?` - Matches one character.
|
||||
* `*` - Matches any number of characters up to the end of
|
||||
the filename, the next dot, or the next slash.
|
||||
* `**` - Two adjacent <c>*</c>'s used as a single pattern will
|
||||
match all files and zero or more directories and subdirectories.
|
||||
* `[char1,char2,...]` - Matches any of the characters listed. Two characters
|
||||
separated by a hyphen will match a range of characters.
|
||||
* `{item1,item2,...}` - Matches one of the alternatives.
|
||||
* `?` - matches one character
|
||||
|
||||
* `*` - matches any number of characters up to the end of the filename, the
|
||||
next dot, or the next slash
|
||||
|
||||
* `**` - two adjacent `*`'s used as a single pattern will match all
|
||||
files and zero or more directories and subdirectories
|
||||
|
||||
* `[char1,char2,...]` - matches any of the characters listed; two
|
||||
characters separated by a hyphen will match a range of characters
|
||||
|
||||
* `{item1,item2,...}` - matches one of the alternatives
|
||||
|
||||
Other characters represent themselves. Only paths that have
|
||||
exactly the same character in the same position will match. Note
|
||||
that matching is case-sensitive; i.e. "a" will not match "A".
|
||||
|
||||
By default, the patterns `*` and `?` do not match files starting
|
||||
with a dot `.` unless `match_dot: true` is given.
|
||||
|
||||
## Examples
|
||||
|
||||
Imagine you have a directory called `projects` with three Elixir projects
|
||||
@@ -513,10 +550,11 @@ defmodule Path do
|
||||
|
||||
"""
|
||||
@spec wildcard(t) :: [binary]
|
||||
def wildcard(glob) do
|
||||
def wildcard(glob, opts \\ []) do
|
||||
mod = if Keyword.get(opts, :match_dot), do: :file, else: Path.Wildcard
|
||||
glob
|
||||
|> chardata_to_list
|
||||
|> :filelib.wildcard
|
||||
|> chardata_to_list()
|
||||
|> :filelib.wildcard(mod)
|
||||
|> Enum.map(&IO.chardata_to_string/1)
|
||||
end
|
||||
|
||||
@@ -544,6 +582,14 @@ defmodule Path do
|
||||
|
||||
defp normalize(path), do: normalize(split(path), [])
|
||||
|
||||
defp normalize([".."|t], ["/"|_] = acc) do
|
||||
normalize t, acc
|
||||
end
|
||||
|
||||
defp normalize([".."|t], [<<letter, ?:, ?/>>|_] = acc) when letter in ?a..?z do
|
||||
normalize t, acc
|
||||
end
|
||||
|
||||
defp normalize([".."|t], [_|acc]) do
|
||||
normalize t, acc
|
||||
end
|
||||
|
||||
+27
-57
@@ -6,11 +6,11 @@ defmodule Process do
|
||||
exposes and auto-imports some basic functionality related to processes
|
||||
available through the functions:
|
||||
|
||||
* `Kernel.spawn/1` and `Kernel.spawn/3`
|
||||
* `Kernel.spawn_link/1` and `Kernel.spawn_link/3`
|
||||
* `Kernel.spawn_monitor/1` and `Kernel.spawn_monitor/3`
|
||||
* `Kernel.self/0`
|
||||
* `Kernel.send/2`
|
||||
* `Kernel.spawn/1` and `Kernel.spawn/3`
|
||||
* `Kernel.spawn_link/1` and `Kernel.spawn_link/3`
|
||||
* `Kernel.spawn_monitor/1` and `Kernel.spawn_monitor/3`
|
||||
* `Kernel.self/0`
|
||||
* `Kernel.send/2`
|
||||
|
||||
"""
|
||||
|
||||
@@ -76,18 +76,18 @@ defmodule Process do
|
||||
|
||||
The following behaviour applies if reason is any term except `:normal` or `:kill`:
|
||||
|
||||
1) If pid is not trapping exits, pid will exit with the given reason;
|
||||
1. If pid is not trapping exits, pid will exit with the given reason.
|
||||
|
||||
2) If pid is trapping exits, the exit signal is transformed into a message
|
||||
{:EXIT, from, reason} and delivered to the message queue of pid;
|
||||
2. If pid is trapping exits, the exit signal is transformed into a message
|
||||
{:EXIT, from, reason} and delivered to the message queue of pid.
|
||||
|
||||
3) If reason is the atom `:normal`, pid will not exit. If it is trapping exits,
|
||||
the exit signal is transformed into a message {:EXIT, from, :normal} and
|
||||
delivered to its message queue;
|
||||
3. If reason is the atom `:normal`, pid will not exit. If it is trapping
|
||||
exits, the exit signal is transformed into a message {:EXIT, from,
|
||||
:normal} and delivered to its message queue.
|
||||
|
||||
4) If reason is the atom `:kill`, that is if `exit(pid, :kill)` is called, an
|
||||
untrappable exit signal is sent to pid which will unconditionally exit with
|
||||
exit reason `:killed`.
|
||||
4. If reason is the atom `:kill`, that is if `exit(pid, :kill)` is called,
|
||||
an untrappable exit signal is sent to pid which will unconditionally
|
||||
exit with exit reason `:killed`.
|
||||
|
||||
Inlined by the compiler.
|
||||
|
||||
@@ -194,46 +194,6 @@ defmodule Process do
|
||||
:erlang.spawn_opt(mod, fun, args, opts)
|
||||
end
|
||||
|
||||
@doc false
|
||||
def spawn(fun) do
|
||||
:erlang.spawn(fun)
|
||||
end
|
||||
|
||||
@doc false
|
||||
def spawn(mod, fun, args) do
|
||||
:erlang.spawn(mod, fun, args)
|
||||
end
|
||||
|
||||
@doc false
|
||||
def spawn_link(fun) do
|
||||
:erlang.spawn_link(fun)
|
||||
end
|
||||
|
||||
@doc false
|
||||
def spawn_link(mod, fun, args) do
|
||||
:erlang.spawn_link(mod, fun, args)
|
||||
end
|
||||
|
||||
@doc false
|
||||
def spawn_monitor(fun) do
|
||||
:erlang.spawn_monitor(fun)
|
||||
end
|
||||
|
||||
@doc false
|
||||
def spawn_monitor(mod, fun, args) do
|
||||
:erlang.spawn_monitor(mod, fun, args)
|
||||
end
|
||||
|
||||
@doc false
|
||||
def self() do
|
||||
:erlang.self()
|
||||
end
|
||||
|
||||
@doc false
|
||||
def send(dest, msg) do
|
||||
:erlang.send(dest, msg)
|
||||
end
|
||||
|
||||
@doc """
|
||||
The calling process starts monitoring the item given.
|
||||
It returns the monitor reference.
|
||||
@@ -314,7 +274,7 @@ defmodule Process do
|
||||
already registered (check with `registered?/1`).
|
||||
"""
|
||||
@spec register(pid | port, atom) :: true
|
||||
def register(pid, name) do
|
||||
def register(pid, name) when not name in [nil, false, true] do
|
||||
:erlang.register(name, pid)
|
||||
end
|
||||
|
||||
@@ -408,8 +368,18 @@ defmodule Process do
|
||||
|
||||
See http://www.erlang.org/doc/man/erlang.html#process_info-2 for more info.
|
||||
"""
|
||||
@spec info(pid, atom) :: {atom, term}
|
||||
def info(pid, spec) do
|
||||
@spec info(pid, atom) :: {atom, term} | nil
|
||||
def info(pid, spec)
|
||||
|
||||
def info(pid, :registered_name) do
|
||||
case :erlang.process_info(pid, :registered_name) do
|
||||
:undefined -> nil
|
||||
[] -> {:registered_name, []}
|
||||
other -> other
|
||||
end
|
||||
end
|
||||
|
||||
def info(pid, spec) when is_atom(spec) do
|
||||
nillify :erlang.process_info(pid, spec)
|
||||
end
|
||||
|
||||
|
||||
+567
-229
@@ -1,225 +1,17 @@
|
||||
defmodule Protocol do
|
||||
@moduledoc false
|
||||
@moduledoc """
|
||||
Functions for working with protocols.
|
||||
"""
|
||||
|
||||
# Callback for defprotocol.
|
||||
@doc false
|
||||
def defprotocol(name, [do: block]) do
|
||||
quote do
|
||||
defmodule unquote(name) do
|
||||
# We don't allow function definition inside protocols
|
||||
import Kernel, except: [
|
||||
defmacrop: 1, defmacrop: 2, defmacro: 1, defmacro: 2,
|
||||
defp: 1, defp: 2, def: 1, def: 2
|
||||
]
|
||||
@doc """
|
||||
Defines a new protocol function.
|
||||
|
||||
# Import the new dsl that holds the new def
|
||||
import Protocol.DSL, only: :macros
|
||||
Protocols do not allow functions to be defined directly, instead, the
|
||||
regular `Kernel.def/*` macros are replaced by this macro which
|
||||
defines the protocol functions with the appropriate callbacks.
|
||||
"""
|
||||
defmacro def(signature)
|
||||
|
||||
# Compile with debug info for consolidation
|
||||
@compile :debug_info
|
||||
|
||||
# Set up a clear slate to store defined functions
|
||||
@functions []
|
||||
@fallback_to_any false
|
||||
|
||||
# Invoke the user given block
|
||||
unquote(block)
|
||||
|
||||
# Finalize expansion
|
||||
unquote(after_defprotocol)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defp after_defprotocol do
|
||||
quote unquote: false do
|
||||
# == Deprecated records handling ==
|
||||
{arg, impl} = Protocol.rec_impl_for(__MODULE__)
|
||||
Kernel.def impl_for(unquote(arg)) when
|
||||
is_tuple(unquote(arg)) and is_atom(elem(unquote(arg), 0)), do: unquote(impl)
|
||||
# == Deprecated records handling ==
|
||||
|
||||
@spec impl_for(term) :: module | nil
|
||||
Kernel.def impl_for(data)
|
||||
|
||||
# Define the implementation for structs.
|
||||
#
|
||||
# It simply delegates to struct_impl_for which is then
|
||||
# optimized during protocol consolidation.
|
||||
Kernel.def impl_for(%{__struct__: struct}) when :erlang.is_atom(struct) do
|
||||
struct_impl_for(struct)
|
||||
end
|
||||
|
||||
# Define the implementation for builtins.
|
||||
for {guard, mod} <- Protocol.builtin do
|
||||
target = Module.concat(__MODULE__, mod)
|
||||
|
||||
Kernel.def impl_for(data) when :erlang.unquote(guard)(data) do
|
||||
case impl_for?(unquote(target)) do
|
||||
true -> unquote(target).__impl__(:name)
|
||||
false -> any_impl_for
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@spec impl_for!(term) :: module | no_return
|
||||
Kernel.def impl_for!(data) do
|
||||
impl_for(data) || raise(Protocol.UndefinedError, protocol: __MODULE__, value: data)
|
||||
end
|
||||
|
||||
# Internal handler for Any
|
||||
if @fallback_to_any do
|
||||
Kernel.defp any_impl_for do
|
||||
case impl_for?(__MODULE__.Any) do
|
||||
true -> __MODULE__.Any.__impl__(:name)
|
||||
false -> nil
|
||||
end
|
||||
end
|
||||
else
|
||||
Kernel.defp any_impl_for, do: nil
|
||||
end
|
||||
|
||||
# Internal handler for Structs
|
||||
Kernel.defp struct_impl_for(struct) do
|
||||
target = Module.concat(__MODULE__, struct)
|
||||
case impl_for?(target) do
|
||||
true -> target.__impl__(:name)
|
||||
false -> any_impl_for
|
||||
end
|
||||
end
|
||||
|
||||
# Check if compilation is available internally
|
||||
Kernel.defp impl_for?(target) do
|
||||
Code.ensure_compiled?(target) and
|
||||
function_exported?(target, :__impl__, 1)
|
||||
end
|
||||
|
||||
# Inline any and struct implementations
|
||||
@compile {:inline, any_impl_for: 0, struct_impl_for: 1, impl_for?: 1}
|
||||
|
||||
if :code.ensure_loaded(Kernel.Typespec) == {:module, Kernel.Typespec} and
|
||||
not Kernel.Typespec.defines_type?(__MODULE__, :t, 0) do
|
||||
@type t :: term
|
||||
end
|
||||
|
||||
# Store information as an attribute so it
|
||||
# can be read without loading the module.
|
||||
Module.register_attribute(__MODULE__, :protocol, persist: true)
|
||||
@protocol [fallback_to_any: !!@fallback_to_any, consolidated: false]
|
||||
|
||||
@doc false
|
||||
Kernel.def __protocol__(:name), do: __MODULE__
|
||||
Kernel.def __protocol__(:functions), do: unquote(:lists.sort(@functions))
|
||||
end
|
||||
end
|
||||
|
||||
# Callback for defimpl.
|
||||
@doc false
|
||||
def defimpl(protocol, opts) do
|
||||
do_defimpl(protocol, :lists.keysort(1, opts))
|
||||
end
|
||||
|
||||
defp do_defimpl(protocol, [do: block, for: for]) when is_list(for) do
|
||||
for f <- for, do: do_defimpl(protocol, [do: block, for: f])
|
||||
end
|
||||
|
||||
defp do_defimpl(protocol, [do: block, for: for]) do
|
||||
quote do
|
||||
protocol = unquote(protocol)
|
||||
for = unquote(for)
|
||||
name = Module.concat(protocol, for)
|
||||
|
||||
Protocol.assert_protocol(protocol)
|
||||
|
||||
defmodule name do
|
||||
@behaviour unquote(protocol)
|
||||
@protocol unquote(protocol)
|
||||
@for unquote(for)
|
||||
|
||||
unquote(block)
|
||||
|
||||
Module.register_attribute(__MODULE__, :impl, persist: true)
|
||||
@impl [protocol: @protocol, for: @for]
|
||||
|
||||
@doc false
|
||||
def __impl__(:name), do: __MODULE__
|
||||
def __impl__(:protocol), do: @protocol
|
||||
def __impl__(:for), do: @for
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Check if the given module is a protocol. Raises an error
|
||||
# if not loaded or not a protocol.
|
||||
@doc false
|
||||
def assert_protocol(module) do
|
||||
case Code.ensure_compiled(module) do
|
||||
{:module, ^module} -> nil
|
||||
_ -> raise ArgumentError, "#{inspect module} is not loaded"
|
||||
end
|
||||
|
||||
try do
|
||||
module.__protocol__(:name)
|
||||
rescue
|
||||
UndefinedFunctionError ->
|
||||
raise ArgumentError, "#{inspect module} is not a protocol"
|
||||
end
|
||||
end
|
||||
|
||||
# Builtin types.
|
||||
@doc false
|
||||
def builtin do
|
||||
[is_tuple: Tuple,
|
||||
is_atom: Atom,
|
||||
is_list: List,
|
||||
is_map: Map,
|
||||
is_bitstring: BitString,
|
||||
is_integer: Integer,
|
||||
is_float: Float,
|
||||
is_function: Function,
|
||||
is_pid: PID,
|
||||
is_port: Port,
|
||||
is_reference: Reference]
|
||||
end
|
||||
|
||||
# Implements the function that detects the protocol and
|
||||
# returns the module to dispatch to.
|
||||
@doc false
|
||||
def rec_impl_for(current) do
|
||||
all = [Any] ++ for {_guard, mod} <- builtin, do: mod
|
||||
arg = quote do: arg
|
||||
target = Module.concat(current, Tuple)
|
||||
|
||||
fallback = quote do
|
||||
case impl_for?(unquote(target)) do
|
||||
true -> unquote(target).__impl__(:name)
|
||||
false -> any_impl_for
|
||||
end
|
||||
end
|
||||
|
||||
impl_for = quote do
|
||||
atom = :erlang.element(1, unquote(arg))
|
||||
|
||||
case not(atom in unquote(all)) and match?('Elixir.' ++ _, Atom.to_char_list(atom)) do
|
||||
true ->
|
||||
target = Module.concat(unquote(current), atom)
|
||||
case impl_for?(target) do
|
||||
true -> target.__impl__(:name)
|
||||
false -> unquote(fallback)
|
||||
end
|
||||
false ->
|
||||
unquote(fallback)
|
||||
end
|
||||
end
|
||||
|
||||
{arg, impl_for}
|
||||
end
|
||||
end
|
||||
|
||||
defmodule Protocol.DSL do
|
||||
@moduledoc false
|
||||
|
||||
@doc false
|
||||
defmacro def({_, _, args}) when args == [] or is_atom(args) do
|
||||
raise ArgumentError, "protocol functions expect at least one argument"
|
||||
end
|
||||
@@ -251,7 +43,7 @@ defmodule Protocol.DSL do
|
||||
|
||||
# Convert the spec to callback if possible,
|
||||
# otherwise generate a dummy callback
|
||||
Protocol.DSL.__spec__?(__MODULE__, name, arity) ||
|
||||
Protocol.__spec__?(__MODULE__, name, arity) ||
|
||||
@callback unquote(name)(unquote_splicing(type_args)) :: term
|
||||
end
|
||||
end
|
||||
@@ -260,21 +52,567 @@ defmodule Protocol.DSL do
|
||||
raise ArgumentError, "invalid args for def inside defprotocol"
|
||||
end
|
||||
|
||||
@doc """
|
||||
Checks if the given module is loaded and is protocol.
|
||||
|
||||
Returns `:ok` if so, otherwise raises ArgumentError.
|
||||
"""
|
||||
@spec assert_protocol!(module) :: :ok | no_return
|
||||
def assert_protocol!(module) do
|
||||
assert_protocol!(module, "")
|
||||
end
|
||||
|
||||
defp assert_protocol!(module, extra) do
|
||||
case Code.ensure_compiled(module) do
|
||||
{:module, ^module} -> :ok
|
||||
_ -> raise ArgumentError, "#{inspect module} is not available" <> extra
|
||||
end
|
||||
|
||||
try do
|
||||
module.__protocol__(:name)
|
||||
rescue
|
||||
UndefinedFunctionError ->
|
||||
raise ArgumentError, "#{inspect module} is not a protocol" <> extra
|
||||
end
|
||||
|
||||
:ok
|
||||
end
|
||||
|
||||
@doc """
|
||||
Checks if the given module is loaded and is an implementation
|
||||
of the given protocol.
|
||||
|
||||
Returns `:ok` if so, otherwise raises ArgumentError.
|
||||
"""
|
||||
@spec assert_impl!(module, module) :: :ok | no_return
|
||||
def assert_impl!(protocol, impl) do
|
||||
assert_impl!(protocol, impl, "")
|
||||
end
|
||||
|
||||
defp assert_impl!(protocol, impl, extra) do
|
||||
case Code.ensure_compiled(impl) do
|
||||
{:module, ^impl} -> :ok
|
||||
_ -> raise ArgumentError,
|
||||
"#{inspect impl} is not available" <> extra
|
||||
end
|
||||
|
||||
try do
|
||||
impl.__impl__(:protocol)
|
||||
rescue
|
||||
UndefinedFunctionError ->
|
||||
raise ArgumentError,
|
||||
"#{inspect impl} is not an implementation of a protocol" <> extra
|
||||
else
|
||||
^protocol ->
|
||||
:ok
|
||||
other ->
|
||||
raise ArgumentError,
|
||||
"expected #{inspect impl} to be an implementation of #{inspect protocol}, got: #{inspect other}" <> extra
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Derive the `protocol` for `module` with the given options.
|
||||
"""
|
||||
defmacro derive(protocol, module, options \\ []) do
|
||||
quote do
|
||||
module = unquote(module)
|
||||
Protocol.__derive__([{unquote(protocol), unquote(options)}], module, __ENV__)
|
||||
end
|
||||
end
|
||||
|
||||
## Consolidation
|
||||
|
||||
@doc """
|
||||
Extract all protocols from the given paths.
|
||||
|
||||
The paths can be either a char list or a string. Internally
|
||||
they are worked on as char lists, so passing them as lists
|
||||
avoid extra conversion.
|
||||
|
||||
Does not load any of the protocols.
|
||||
|
||||
## Examples
|
||||
|
||||
# Get Elixir's ebin and retrieve all protocols
|
||||
iex> path = :code.lib_dir(:elixir, :ebin)
|
||||
iex> mods = Protocol.extract_protocols([path])
|
||||
iex> Enumerable in mods
|
||||
true
|
||||
|
||||
"""
|
||||
@spec extract_protocols([char_list | String.t]) :: [atom]
|
||||
def extract_protocols(paths) do
|
||||
extract_matching_by_attribute paths, 'Elixir.',
|
||||
fn module, attributes ->
|
||||
case attributes[:protocol] do
|
||||
[fallback_to_any: _, consolidated: _] -> module
|
||||
_ -> nil
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Extract all types implemented for the given protocol from
|
||||
the given paths.
|
||||
|
||||
The paths can be either a char list or a string. Internally
|
||||
they are worked on as char lists, so passing them as lists
|
||||
avoid extra conversion.
|
||||
|
||||
Does not load any of the implementations.
|
||||
|
||||
## Examples
|
||||
|
||||
# Get Elixir's ebin and retrieve all protocols
|
||||
iex> path = :code.lib_dir(:elixir, :ebin)
|
||||
iex> mods = Protocol.extract_impls(Enumerable, [path])
|
||||
iex> List in mods
|
||||
true
|
||||
|
||||
"""
|
||||
@spec extract_impls(module, [char_list | String.t]) :: [atom]
|
||||
def extract_impls(protocol, paths) when is_atom(protocol) do
|
||||
prefix = Atom.to_char_list(protocol) ++ '.'
|
||||
extract_matching_by_attribute paths, prefix, fn
|
||||
_mod, attributes ->
|
||||
case attributes[:impl] do
|
||||
[protocol: ^protocol, for: for] -> for
|
||||
_ -> nil
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defp extract_matching_by_attribute(paths, prefix, callback) do
|
||||
for path <- paths,
|
||||
file <- list_dir(path),
|
||||
mod = extract_from_file(path, file, prefix, callback),
|
||||
do: mod
|
||||
end
|
||||
|
||||
defp list_dir(path) when is_list(path) do
|
||||
case :file.list_dir(path) do
|
||||
{:ok, files} -> files
|
||||
_ -> []
|
||||
end
|
||||
end
|
||||
|
||||
defp list_dir(path), do: list_dir(to_char_list(path))
|
||||
|
||||
defp extract_from_file(path, file, prefix, callback) do
|
||||
if :lists.prefix(prefix, file) and :filename.extension(file) == '.beam' do
|
||||
extract_from_beam(:filename.join(path, file), callback)
|
||||
end
|
||||
end
|
||||
|
||||
defp extract_from_beam(file, callback) do
|
||||
case :beam_lib.chunks(file, [:attributes]) do
|
||||
{:ok, {module, [attributes: attributes]}} ->
|
||||
callback.(module, attributes)
|
||||
_ ->
|
||||
nil
|
||||
end
|
||||
end
|
||||
|
||||
defmacrop if_ok(expr, call) do
|
||||
quote do
|
||||
case unquote(expr) do
|
||||
{:ok, var} -> unquote(Macro.pipe(quote(do: var), call, 0))
|
||||
other -> other
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns true if the protocol was consolidated.
|
||||
"""
|
||||
@spec consolidated?(module) :: boolean
|
||||
def consolidated?(protocol) do
|
||||
protocol.__info__(:attributes)[:protocol][:consolidated]
|
||||
end
|
||||
|
||||
@doc """
|
||||
Receives a protocol and a list of implementations and
|
||||
consolidates the given protocol.
|
||||
|
||||
Consolidation happens by changing the protocol `impl_for`
|
||||
in the abstract format to have fast lookup rules. Usually
|
||||
the list of implementations to use during consolidation
|
||||
are retrieved with the help of `extract_impls/2`.
|
||||
|
||||
It returns the updated version of the protocol bytecode.
|
||||
A given bytecode or protocol implementation can be checked
|
||||
to be consolidated or not by analyzing the protocol
|
||||
attribute:
|
||||
|
||||
Protocol.consolidated?(Enumerable)
|
||||
|
||||
If the first element of the tuple is true, it means
|
||||
the protocol was consolidated.
|
||||
|
||||
This function does not load the protocol at any point
|
||||
nor loads the new bytecode for the compiled module.
|
||||
However each implementation must be available and
|
||||
it will be loaded.
|
||||
"""
|
||||
@spec consolidate(module, [module]) ::
|
||||
{:ok, binary} |
|
||||
{:error, :not_a_protocol} |
|
||||
{:error, :no_beam_info}
|
||||
def consolidate(protocol, types) when is_atom(protocol) do
|
||||
beam_protocol(protocol)
|
||||
|> if_ok(change_debug_info types)
|
||||
|> if_ok(compile)
|
||||
end
|
||||
|
||||
@docs_chunk 'ExDc'
|
||||
|
||||
defp beam_protocol(protocol) do
|
||||
chunk_ids = [:abstract_code, :attributes, @docs_chunk]
|
||||
opts = [:allow_missing_chunks]
|
||||
case :beam_lib.chunks(beam_file(protocol), chunk_ids, opts) do
|
||||
{:ok, {^protocol, [{:abstract_code, {_raw, abstract_code}},
|
||||
{:attributes, attributes},
|
||||
{@docs_chunk, docs}]}} ->
|
||||
case attributes[:protocol] do
|
||||
[fallback_to_any: any, consolidated: _] ->
|
||||
{:ok, {protocol, any, abstract_code, docs}}
|
||||
_ ->
|
||||
{:error, :not_a_protocol}
|
||||
end
|
||||
_ ->
|
||||
{:error, :no_beam_info}
|
||||
end
|
||||
end
|
||||
|
||||
defp beam_file(module) when is_atom(module) do
|
||||
case :code.which(module) do
|
||||
:non_existing -> module
|
||||
file -> file
|
||||
end
|
||||
end
|
||||
|
||||
# Change the debug information to the optimized
|
||||
# impl_for/1 dispatch version.
|
||||
defp change_debug_info({protocol, any, code, docs}, types) do
|
||||
types = if any, do: types, else: List.delete(types, Any)
|
||||
all = [Any] ++ for {_guard, mod} <- builtin, do: mod
|
||||
structs = types -- all
|
||||
case change_impl_for(code, protocol, types, structs, false, []) do
|
||||
{:ok, ret} -> {:ok, {ret, docs}}
|
||||
other -> other
|
||||
end
|
||||
end
|
||||
|
||||
defp change_impl_for([{:attribute, line, :protocol, opts}|t], protocol, types, structs, _, acc) do
|
||||
opts = [fallback_to_any: opts[:fallback_to_any], consolidated: true]
|
||||
change_impl_for(t, protocol, types, structs, true,
|
||||
[{:attribute, line, :protocol, opts}|acc])
|
||||
end
|
||||
|
||||
defp change_impl_for([{:function, line, :impl_for, 1, _}|t], protocol, types, structs, is_protocol, acc) do
|
||||
fallback = if Any in types, do: load_impl(protocol, Any), else: nil
|
||||
|
||||
clauses = for {guard, mod} <- builtin,
|
||||
mod in types,
|
||||
do: builtin_clause_for(mod, guard, protocol, line)
|
||||
|
||||
clauses = [struct_clause_for(line)|clauses] ++
|
||||
[fallback_clause_for(fallback, protocol, line)]
|
||||
|
||||
change_impl_for(t, protocol, types, structs, is_protocol,
|
||||
[{:function, line, :impl_for, 1, clauses}|acc])
|
||||
end
|
||||
|
||||
defp change_impl_for([{:function, line, :struct_impl_for, 1, _}|t], protocol, types, structs, is_protocol, acc) do
|
||||
fallback = if Any in types, do: load_impl(protocol, Any), else: nil
|
||||
clauses = for struct <- structs, do: each_struct_clause_for(struct, protocol, line)
|
||||
clauses = clauses ++ [fallback_clause_for(fallback, protocol, line)]
|
||||
|
||||
change_impl_for(t, protocol, types, structs, is_protocol,
|
||||
[{:function, line, :struct_impl_for, 1, clauses}|acc])
|
||||
end
|
||||
|
||||
defp change_impl_for([h|t], protocol, info, types, is_protocol, acc) do
|
||||
change_impl_for(t, protocol, info, types, is_protocol, [h|acc])
|
||||
end
|
||||
|
||||
defp change_impl_for([], protocol, _info, _types, is_protocol, acc) do
|
||||
if is_protocol do
|
||||
{:ok, {protocol, Enum.reverse(acc)}}
|
||||
else
|
||||
{:error, :not_a_protocol}
|
||||
end
|
||||
end
|
||||
|
||||
defp builtin_clause_for(mod, guard, protocol, line) do
|
||||
{:clause, line,
|
||||
[{:var, line, :x}],
|
||||
[[{:call, line,
|
||||
{:remote, line, {:atom, line, :erlang}, {:atom, line, guard}},
|
||||
[{:var, line, :x}],
|
||||
}]],
|
||||
[{:atom, line, load_impl(protocol, mod)}]}
|
||||
end
|
||||
|
||||
defp struct_clause_for(line) do
|
||||
{:clause, line,
|
||||
[{:map, line, [
|
||||
{:map_field_exact, line, {:atom, line, :__struct__}, {:var, line, :x}}
|
||||
]}],
|
||||
[[{:call, line,
|
||||
{:remote, line, {:atom, line, :erlang}, {:atom, line, :is_atom}},
|
||||
[{:var, line, :x}],
|
||||
}]],
|
||||
[{:call, line,
|
||||
{:atom, line, :struct_impl_for},
|
||||
[{:var, line, :x}]}]}
|
||||
end
|
||||
|
||||
defp each_struct_clause_for(other, protocol, line) do
|
||||
{:clause, line, [{:atom, line, other}], [],
|
||||
[{:atom, line, load_impl(protocol, other)}]}
|
||||
end
|
||||
|
||||
defp fallback_clause_for(value, _protocol, line) do
|
||||
{:clause, line, [{:var, line, :_}], [],
|
||||
[{:atom, line, value}]}
|
||||
end
|
||||
|
||||
defp load_impl(protocol, for) do
|
||||
Module.concat(protocol, for).__impl__(:target)
|
||||
end
|
||||
|
||||
# Finally compile the module and emit its bytecode.
|
||||
defp compile({{protocol, code}, docs}) do
|
||||
opts = if Code.compiler_options[:debug_info], do: [:debug_info], else: []
|
||||
{:ok, ^protocol, binary, _warnings} = :compile.forms(code, [:return|opts])
|
||||
unless docs == :missing_chunk do
|
||||
binary = :elixir_module.add_beam_chunk(binary, @docs_chunk, docs)
|
||||
end
|
||||
{:ok, binary}
|
||||
end
|
||||
|
||||
## Definition callbacks
|
||||
|
||||
@doc false
|
||||
def __protocol__(name, [do: block]) do
|
||||
quote do
|
||||
defmodule unquote(name) do
|
||||
# We don't allow function definition inside protocols
|
||||
import Kernel, except: [
|
||||
defmacrop: 1, defmacrop: 2, defmacro: 1, defmacro: 2,
|
||||
defp: 1, defp: 2, def: 1, def: 2
|
||||
]
|
||||
|
||||
# Import the new dsl that holds the new def
|
||||
import Protocol, only: [def: 1]
|
||||
|
||||
# Compile with debug info for consolidation
|
||||
@compile :debug_info
|
||||
|
||||
# Set up a clear slate to store defined functions
|
||||
@functions []
|
||||
@fallback_to_any false
|
||||
|
||||
# Invoke the user given block
|
||||
unquote(block)
|
||||
|
||||
# Finalize expansion
|
||||
unquote(after_defprotocol)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defp after_defprotocol do
|
||||
quote bind_quoted: [builtin: builtin] do
|
||||
@spec impl_for(term) :: module | nil
|
||||
Kernel.def impl_for(data)
|
||||
|
||||
# Define the implementation for structs.
|
||||
#
|
||||
# It simply delegates to struct_impl_for which is then
|
||||
# optimized during protocol consolidation.
|
||||
Kernel.def impl_for(%{__struct__: struct}) when :erlang.is_atom(struct) do
|
||||
struct_impl_for(struct)
|
||||
end
|
||||
|
||||
# Define the implementation for builtins.
|
||||
for {guard, mod} <- builtin do
|
||||
target = Module.concat(__MODULE__, mod)
|
||||
|
||||
Kernel.def impl_for(data) when :erlang.unquote(guard)(data) do
|
||||
case impl_for?(unquote(target)) do
|
||||
true -> unquote(target).__impl__(:target)
|
||||
false -> any_impl_for
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@spec impl_for!(term) :: module | no_return
|
||||
Kernel.def impl_for!(data) do
|
||||
impl_for(data) || raise(Protocol.UndefinedError, protocol: __MODULE__, value: data)
|
||||
end
|
||||
|
||||
# Internal handler for Any
|
||||
if @fallback_to_any do
|
||||
Kernel.defp any_impl_for do
|
||||
case impl_for?(__MODULE__.Any) do
|
||||
true -> __MODULE__.Any.__impl__(:target)
|
||||
false -> nil
|
||||
end
|
||||
end
|
||||
else
|
||||
Kernel.defp any_impl_for, do: nil
|
||||
end
|
||||
|
||||
# Internal handler for Structs
|
||||
Kernel.defp struct_impl_for(struct) do
|
||||
target = Module.concat(__MODULE__, struct)
|
||||
case impl_for?(target) do
|
||||
true -> target.__impl__(:target)
|
||||
false -> any_impl_for
|
||||
end
|
||||
end
|
||||
|
||||
# Check if compilation is available internally
|
||||
Kernel.defp impl_for?(target) do
|
||||
Code.ensure_compiled?(target) and
|
||||
function_exported?(target, :__impl__, 1)
|
||||
end
|
||||
|
||||
# Inline any and struct implementations
|
||||
@compile {:inline, any_impl_for: 0, struct_impl_for: 1, impl_for?: 1}
|
||||
|
||||
unless Kernel.Typespec.defines_type?(__MODULE__, :t, 0) do
|
||||
@type t :: term
|
||||
end
|
||||
|
||||
# Store information as an attribute so it
|
||||
# can be read without loading the module.
|
||||
Module.register_attribute(__MODULE__, :protocol, persist: true)
|
||||
@protocol [fallback_to_any: !!@fallback_to_any, consolidated: false]
|
||||
|
||||
@doc false
|
||||
@spec __protocol__(atom) :: term
|
||||
Kernel.def __protocol__(:name), do: __MODULE__
|
||||
Kernel.def __protocol__(:functions), do: unquote(:lists.sort(@functions))
|
||||
end
|
||||
end
|
||||
|
||||
@doc false
|
||||
def __impl__(protocol, opts) do
|
||||
do_defimpl(protocol, :lists.keysort(1, opts))
|
||||
end
|
||||
|
||||
defp do_defimpl(protocol, [do: block, for: for]) when is_list(for) do
|
||||
for f <- for, do: do_defimpl(protocol, [do: block, for: f])
|
||||
end
|
||||
|
||||
defp do_defimpl(protocol, [do: block, for: for]) do
|
||||
quote do
|
||||
protocol = unquote(protocol)
|
||||
for = unquote(for)
|
||||
name = Module.concat(protocol, for)
|
||||
|
||||
Protocol.assert_protocol!(protocol)
|
||||
|
||||
defmodule name do
|
||||
@behaviour protocol
|
||||
@protocol protocol
|
||||
@for for
|
||||
|
||||
unquote(block)
|
||||
|
||||
Module.register_attribute(__MODULE__, :impl, persist: true)
|
||||
@impl [protocol: @protocol, for: @for]
|
||||
|
||||
@doc false
|
||||
@spec __impl__(atom) :: term
|
||||
def __impl__(:target), do: __MODULE__
|
||||
def __impl__(:protocol), do: @protocol
|
||||
def __impl__(:for), do: @for
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@doc false
|
||||
def __derive__(derives, for, %Macro.Env{} = env) when is_atom(for) do
|
||||
struct =
|
||||
if for == env.module do
|
||||
Module.get_attribute(for, :struct) ||
|
||||
raise "struct is not defined for #{inspect for}"
|
||||
else
|
||||
for.__struct__
|
||||
end
|
||||
|
||||
:lists.foreach(fn
|
||||
proto when is_atom(proto) ->
|
||||
derive(proto, for, struct, [], env)
|
||||
{proto, opts} when is_atom(proto) ->
|
||||
derive(proto, for, struct, opts, env)
|
||||
end, :lists.flatten(derives))
|
||||
|
||||
:ok
|
||||
end
|
||||
|
||||
defp derive(protocol, for, struct, opts, env) do
|
||||
impl = Module.concat(protocol, Map)
|
||||
extra = ", cannot derive #{inspect protocol} for #{inspect for}"
|
||||
assert_protocol!(protocol, extra)
|
||||
assert_impl!(protocol, impl, extra)
|
||||
|
||||
# Clean up variables from eval context
|
||||
env = %{env | vars: [], export_vars: nil}
|
||||
args = [for, struct, opts]
|
||||
|
||||
:elixir_module.expand_callback(env.line, impl, :__deriving__, args, env, fn
|
||||
mod, fun, args ->
|
||||
if function_exported?(mod, fun, length(args)) do
|
||||
apply(mod, fun, args)
|
||||
else
|
||||
Module.create(Module.concat(protocol, for), quote do
|
||||
Module.register_attribute(__MODULE__, :impl, persist: true)
|
||||
@impl [protocol: unquote(protocol), for: unquote(for)]
|
||||
|
||||
@doc false
|
||||
@spec __impl__(atom) :: term
|
||||
def __impl__(:target), do: unquote(impl)
|
||||
def __impl__(:protocol), do: unquote(protocol)
|
||||
def __impl__(:for), do: unquote(for)
|
||||
end, Macro.Env.location(env))
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
@doc false
|
||||
def __spec__?(module, name, arity) do
|
||||
case :code.ensure_loaded(Kernel.Typespec) do
|
||||
{:module, Kernel.Typespec} ->
|
||||
tuple = {name, arity}
|
||||
specs = Module.get_attribute(module, :spec)
|
||||
signature = {name, arity}
|
||||
specs = Module.get_attribute(module, :spec)
|
||||
|
||||
found = for {k, v} <- specs, k == tuple do
|
||||
Kernel.Typespec.define_callback(module, tuple, v)
|
||||
found =
|
||||
for {:spec, expr, caller} <- specs,
|
||||
Kernel.Typespec.spec_to_signature(expr) == signature do
|
||||
Kernel.Typespec.define_spec(:callback, expr, caller)
|
||||
true
|
||||
end
|
||||
|
||||
found != []
|
||||
{:error, _} ->
|
||||
true
|
||||
end
|
||||
found != []
|
||||
end
|
||||
|
||||
## Helpers
|
||||
|
||||
defp builtin do
|
||||
[is_tuple: Tuple,
|
||||
is_atom: Atom,
|
||||
is_list: List,
|
||||
is_map: Map,
|
||||
is_bitstring: BitString,
|
||||
is_integer: Integer,
|
||||
is_float: Float,
|
||||
is_function: Function,
|
||||
is_pid: PID,
|
||||
is_port: Port,
|
||||
is_reference: Reference]
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,249 +0,0 @@
|
||||
defmodule Protocol.Consolidation do
|
||||
@moduledoc """
|
||||
Module responsible for consolidating protocols and helpers for
|
||||
extracting protocols and implementations from code paths for
|
||||
consolidation.
|
||||
"""
|
||||
|
||||
@doc """
|
||||
Extract all protocols from the given paths.
|
||||
|
||||
The paths can be either a char list or a string. Internally
|
||||
they are worked on as char lists, so passing them as lists
|
||||
avoid extra conversion.
|
||||
|
||||
## Examples
|
||||
|
||||
# Get Elixir's ebin and retrieve all protocols
|
||||
iex> path = :code.lib_dir(:elixir, :ebin)
|
||||
iex> mods = Protocol.Consolidation.extract_protocols([path])
|
||||
iex> Enumerable in mods
|
||||
true
|
||||
|
||||
"""
|
||||
@spec extract_protocols([char_list | String.t]) :: [atom]
|
||||
def extract_protocols(paths) do
|
||||
extract_matching_by_attribute paths, 'Elixir.',
|
||||
fn module, attributes ->
|
||||
case attributes[:protocol] do
|
||||
[fallback_to_any: _, consolidated: _] -> module
|
||||
_ -> nil
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Extract all types implemented for the given protocol from
|
||||
the given paths.
|
||||
|
||||
The paths can be either a char list or a string. Internally
|
||||
they are worked on as char lists, so passing them as lists
|
||||
avoid extra conversion.
|
||||
|
||||
## Examples
|
||||
|
||||
# Get Elixir's ebin and retrieve all protocols
|
||||
iex> path = :code.lib_dir(:elixir, :ebin)
|
||||
iex> mods = Protocol.Consolidation.extract_impls(Enumerable, [path])
|
||||
iex> List in mods
|
||||
true
|
||||
|
||||
"""
|
||||
@spec extract_impls(module, [char_list | String.t]) :: [atom]
|
||||
def extract_impls(protocol, paths) when is_atom(protocol) do
|
||||
prefix = Atom.to_char_list(protocol) ++ '.'
|
||||
extract_matching_by_attribute paths, prefix, fn
|
||||
_mod, attributes ->
|
||||
case attributes[:impl] do
|
||||
[protocol: ^protocol, for: for] -> for
|
||||
_ -> nil
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defp extract_matching_by_attribute(paths, prefix, callback) do
|
||||
for path <- paths,
|
||||
file <- list_dir(path),
|
||||
mod = extract_from_file(path, file, prefix, callback),
|
||||
do: mod
|
||||
end
|
||||
|
||||
defp list_dir(path) when is_list(path) do
|
||||
case :file.list_dir(path) do
|
||||
{:ok, files} -> files
|
||||
_ -> []
|
||||
end
|
||||
end
|
||||
|
||||
defp list_dir(path), do: list_dir(to_char_list(path))
|
||||
|
||||
defp extract_from_file(path, file, prefix, callback) do
|
||||
if :lists.prefix(prefix, file) and :filename.extension(file) == '.beam' do
|
||||
extract_from_beam(:filename.join(path, file), callback)
|
||||
end
|
||||
end
|
||||
|
||||
defp extract_from_beam(file, callback) do
|
||||
case :beam_lib.chunks(file, [:attributes]) do
|
||||
{:ok, {module, [attributes: attributes]}} ->
|
||||
callback.(module, attributes)
|
||||
_ ->
|
||||
nil
|
||||
end
|
||||
end
|
||||
|
||||
defmacrop if_ok(expr, call) do
|
||||
quote do
|
||||
case unquote(expr) do
|
||||
{:ok, var} -> unquote(Macro.pipe(quote(do: var), call, 0))
|
||||
other -> other
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Receives a protocol and a list of implementations and
|
||||
consolidates the given protocol. Consolidation happens
|
||||
by changing the protocol `impl_for` in the abstract
|
||||
format to have fast lookup rules.
|
||||
|
||||
It returns the updated version of the protocol bytecode.
|
||||
A given bytecode or protocol implementation can be checked
|
||||
to be consolidated or not by analyzing the protocol
|
||||
attribute:
|
||||
|
||||
Enumerable.__info__(:attributes)[:protocol]
|
||||
|
||||
If the first element of the tuple is true, it means
|
||||
the protocol was consolidated.
|
||||
|
||||
This function does not load the protocol at any point
|
||||
nor loads the new bytecode for the compiled module.
|
||||
"""
|
||||
@spec apply_to(module, [module]) ::
|
||||
{:ok, binary} |
|
||||
{:error, :not_a_protocol} |
|
||||
{:error, :no_beam_info}
|
||||
def apply_to(protocol, types) when is_atom(protocol) do
|
||||
raise ArgumentError, "consolidation is disabled as we can't consolidate records " <>
|
||||
"and structs at once. Consolidation will be added back once " <>
|
||||
"polymorphic records are removed"
|
||||
ensure_protocol(protocol)
|
||||
|> if_ok(change_debug_info types)
|
||||
|> if_ok(compile)
|
||||
end
|
||||
|
||||
# Ensure the given module is loaded and is a protocol.
|
||||
defp ensure_protocol(protocol) do
|
||||
case :beam_lib.chunks(beam_file(protocol), [:abstract_code, :attributes]) do
|
||||
{:ok, {^protocol, [abstract_code: {_raw, abstract_code},
|
||||
attributes: attributes]}} ->
|
||||
case attributes[:protocol] do
|
||||
[fallback_to_any: any, consolidated: _] ->
|
||||
{:ok, {protocol, any, abstract_code}}
|
||||
_ ->
|
||||
{:error, :not_a_protocol}
|
||||
end
|
||||
_ ->
|
||||
{:error, :no_beam_info}
|
||||
end
|
||||
end
|
||||
|
||||
defp beam_file(module) when is_atom(module) do
|
||||
case :code.which(module) do
|
||||
:non_existing -> module
|
||||
file -> file
|
||||
end
|
||||
end
|
||||
|
||||
# Change the debug information to the optimized
|
||||
# impl_for/1 dispatch version.
|
||||
defp change_debug_info({protocol, any, code}, types) do
|
||||
types = if any, do: types, else: List.delete(types, Any)
|
||||
all = [Any] ++ for {_guard, mod} <- Protocol.builtin, do: mod
|
||||
structs = types -- all
|
||||
change_impl_for(code, protocol, types, structs, false, [])
|
||||
end
|
||||
|
||||
defp change_impl_for([{:attribute, line, :protocol, opts}|t], protocol, types, structs, _, acc) do
|
||||
opts = [fallback_to_any: opts[:fallback_to_any], consolidated: true]
|
||||
change_impl_for(t, protocol, types, structs, true,
|
||||
[{:attribute, line, :protocol, opts}|acc])
|
||||
end
|
||||
|
||||
defp change_impl_for([{:function, line, :impl_for, 1, _}|t], protocol, types, structs, is_protocol, acc) do
|
||||
fallback = if Any in types, do: Module.concat(protocol, Any), else: nil
|
||||
|
||||
clauses = for {guard, mod} <- Protocol.builtin,
|
||||
mod in types,
|
||||
do: builtin_clause_for(mod, guard, protocol, line)
|
||||
|
||||
clauses = [struct_clause_for(line)|clauses] ++
|
||||
[fallback_clause_for(fallback, protocol, line)]
|
||||
|
||||
change_impl_for(t, protocol, types, structs, is_protocol,
|
||||
[{:function, line, :impl_for, 1, clauses}|acc])
|
||||
end
|
||||
|
||||
defp change_impl_for([{:function, line, :struct_impl_for, 1, _}|t], protocol, types, structs, is_protocol, acc) do
|
||||
fallback = if Any in types, do: Module.concat(protocol, Any), else: nil
|
||||
clauses = for struct <- structs, do: each_struct_clause_for(struct, protocol, line)
|
||||
clauses = clauses ++ [fallback_clause_for(fallback, protocol, line)]
|
||||
|
||||
change_impl_for(t, protocol, types, structs, is_protocol,
|
||||
[{:function, line, :struct_impl_for, 1, clauses}|acc])
|
||||
end
|
||||
|
||||
defp change_impl_for([h|t], protocol, info, types, is_protocol, acc) do
|
||||
change_impl_for(t, protocol, info, types, is_protocol, [h|acc])
|
||||
end
|
||||
|
||||
defp change_impl_for([], protocol, _info, _types, is_protocol, acc) do
|
||||
if is_protocol do
|
||||
{:ok, {protocol, Enum.reverse(acc)}}
|
||||
else
|
||||
{:error, :not_a_protocol}
|
||||
end
|
||||
end
|
||||
|
||||
defp builtin_clause_for(mod, guard, protocol, line) do
|
||||
{:clause, line,
|
||||
[{:var, line, :x}],
|
||||
[[{:call, line,
|
||||
{:remote, line, {:atom, line, :erlang}, {:atom, line, guard}},
|
||||
[{:var, line, :x}],
|
||||
}]],
|
||||
[{:atom, line, Module.concat(protocol, mod)}]}
|
||||
end
|
||||
|
||||
defp struct_clause_for(line) do
|
||||
{:clause, line,
|
||||
[{:map, line, [
|
||||
{:map_field_exact, line, {:atom, line, :__struct__}, {:var, line, :x}}
|
||||
]}],
|
||||
[[{:call, line,
|
||||
{:remote, line, {:atom, line, :erlang}, {:atom, line, :is_atom}},
|
||||
[{:var, line, :x}],
|
||||
}]],
|
||||
[{:call, line,
|
||||
{:atom, line, :struct_impl_for},
|
||||
[{:var, line, :x}]}]}
|
||||
end
|
||||
|
||||
defp each_struct_clause_for(other, protocol, line) do
|
||||
{:clause, line, [{:atom, line, other}], [],
|
||||
[{:atom, line, Module.concat(protocol, other)}]}
|
||||
end
|
||||
|
||||
defp fallback_clause_for(value, _protocol, line) do
|
||||
{:clause, line, [{:var, line, :_}], [],
|
||||
[{:atom, line, value}]}
|
||||
end
|
||||
|
||||
# Finally compile the module and emit its bytecode.
|
||||
defp compile({protocol, code}) do
|
||||
opts = if Code.compiler_options[:debug_info], do: [:debug_info], else: []
|
||||
{:ok, ^protocol, binary, _warnings} = :compile.forms(code, [:return|opts])
|
||||
{:ok, binary}
|
||||
end
|
||||
end
|
||||
@@ -14,8 +14,8 @@ defmodule Record do
|
||||
|
||||
In Elixir, records are used mostly in two situations:
|
||||
|
||||
1. To work with short, internal data;
|
||||
2. To interface with Erlang records;
|
||||
1. to work with short, internal data
|
||||
2. to interface with Erlang records
|
||||
|
||||
The macros `defrecord/3` and `defrecordp/3` can be used to create
|
||||
records while `extract/2` can be used to extract records from Erlang
|
||||
@@ -101,21 +101,6 @@ defmodule Record do
|
||||
end
|
||||
end
|
||||
|
||||
@doc false
|
||||
def defmacros(name, values, env, tag \\ nil) do
|
||||
Record.Deprecated.defmacros(name, values, env, tag)
|
||||
end
|
||||
|
||||
@doc false
|
||||
def deftypes(values, types, env) do
|
||||
Record.Deprecated.deftypes(values, types, env)
|
||||
end
|
||||
|
||||
@doc false
|
||||
def deffunctions(values, env) do
|
||||
Record.Deprecated.deffunctions(values, env)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Defines a set of macros to create and access a record.
|
||||
|
||||
|
||||
@@ -1,539 +0,0 @@
|
||||
defmodule Record.Deprecated do
|
||||
@moduledoc false
|
||||
|
||||
def defexception(name, fields, opts) do
|
||||
quote do
|
||||
fields = unquote(fields)
|
||||
defmodule unquote(name) do
|
||||
defexception fields
|
||||
unquote(Keyword.get opts, :do)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
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, "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 = String.to_atom(Atom.to_string(name) <> "_t")
|
||||
@typep unquote(type)() :: {unquote(tag || name), unquote_splicing(types)}
|
||||
end
|
||||
end
|
||||
|
||||
quote do
|
||||
def_type = unquote(def_type)
|
||||
fields = unquote(fields)
|
||||
types = unquote(types)
|
||||
tag = unquote(tag)
|
||||
name = unquote(name)
|
||||
unquote(quoted)
|
||||
end
|
||||
|
||||
:error ->
|
||||
quote do
|
||||
name = unquote(name)
|
||||
Record.Deprecated.defmacros(name, unquote(fields), __ENV__, unquote(tag))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defp recordp_split([{field, {:::, _, [default, type]}}|t], defaults, types, _) do
|
||||
recordp_split(t, [{field, default}|defaults], [type|types], true)
|
||||
end
|
||||
|
||||
defp recordp_split([other|t], defaults, types, def_type) do
|
||||
recordp_split(t, [other|defaults], [quote(do: term)|types], def_type)
|
||||
end
|
||||
|
||||
defp recordp_split([], defaults, types, def_type) do
|
||||
{:ok, :lists.reverse(defaults), :lists.reverse(types), def_type}
|
||||
end
|
||||
|
||||
defp recordp_split(_, _, _, _) do
|
||||
:error
|
||||
end
|
||||
|
||||
def deffunctions(values, env) do
|
||||
values = for value <- values, do: convert_value(value)
|
||||
escaped = Macro.escape(values)
|
||||
|
||||
contents = [
|
||||
reflection(escaped),
|
||||
initializer(escaped),
|
||||
conversions(values),
|
||||
record_optimizable(),
|
||||
updater(values),
|
||||
accessors(values, 1),
|
||||
switch_recorder()
|
||||
]
|
||||
|
||||
contents = [quote(do: @record_fields unquote(escaped))|contents]
|
||||
|
||||
# Special case for bootstrapping purposes
|
||||
if env == Macro.Env do
|
||||
Module.eval_quoted(env, contents, [], [])
|
||||
else
|
||||
Module.eval_quoted(env.module, contents, [], Macro.Env.location(env))
|
||||
end
|
||||
end
|
||||
|
||||
def deftypes(values, types, env) do
|
||||
types = types || []
|
||||
values = for value <- values do
|
||||
{name, default} = convert_value(value)
|
||||
{name, default, find_spec(types, name)}
|
||||
end
|
||||
|
||||
contents = [
|
||||
core_specs(values),
|
||||
accessor_specs(values, 1, [])
|
||||
]
|
||||
|
||||
# We need to handle bootstraping
|
||||
cond do
|
||||
:code.ensure_loaded(Kernel.Typespec) != {:module, Kernel.Typespec} ->
|
||||
nil
|
||||
env == Macro.Env ->
|
||||
Module.eval_quoted(env, contents, [], [])
|
||||
true ->
|
||||
Module.eval_quoted(env.module, contents, [], Macro.Env.location(env))
|
||||
end
|
||||
end
|
||||
|
||||
def defmacros(name, values, env, tag \\ nil)
|
||||
when is_atom(name) and is_list(values) and is_atom(tag) do
|
||||
|
||||
escaped = for value <- values do
|
||||
{key, value} = convert_value(value)
|
||||
{key, Macro.escape(value)}
|
||||
end
|
||||
|
||||
tag = tag || name
|
||||
|
||||
contents = quote do
|
||||
defmacrop unquote(name)() do
|
||||
Record.Deprecated.access(unquote(tag), unquote(escaped), [], __CALLER__)
|
||||
end
|
||||
|
||||
defmacrop unquote(name)(args) do
|
||||
Record.Deprecated.access(unquote(tag), unquote(escaped), args, __CALLER__)
|
||||
end
|
||||
|
||||
defmacrop unquote(name)(record, args) do
|
||||
Record.Deprecated.dispatch(unquote(tag), unquote(escaped), record, args, __CALLER__)
|
||||
end
|
||||
end
|
||||
|
||||
Module.eval_quoted(env.module, contents, [], Macro.Env.location(env))
|
||||
end
|
||||
|
||||
## Callbacks
|
||||
|
||||
defmacro __before_compile__(_) do
|
||||
quote do
|
||||
@spec __record__(atom) :: term
|
||||
@spec __record__(atom, term) :: term
|
||||
@spec __record__(atom, term, term) :: term
|
||||
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, "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, "expected contents inside brackets to be a keyword list or an atom, got: #{inspect keyword}"
|
||||
end
|
||||
|
||||
in_match = Macro.Env.in_match?(caller)
|
||||
|
||||
has_underscore_value = Keyword.has_key?(keyword, :_)
|
||||
underscore_value = Keyword.get(keyword, :_, {:_, [], nil})
|
||||
keyword = Keyword.delete keyword, :_
|
||||
|
||||
iterator = fn({field, default}, each_keyword) ->
|
||||
new_fields =
|
||||
case Keyword.has_key?(each_keyword, field) do
|
||||
true -> Keyword.get(each_keyword, field)
|
||||
false ->
|
||||
case in_match or has_underscore_value do
|
||||
true -> underscore_value
|
||||
false -> Macro.escape(default)
|
||||
end
|
||||
end
|
||||
|
||||
{new_fields, Keyword.delete(each_keyword, field)}
|
||||
end
|
||||
|
||||
{match, remaining} = :lists.mapfoldl(iterator, keyword, fields)
|
||||
|
||||
case remaining do
|
||||
[] ->
|
||||
{:{}, [], [atom|match]}
|
||||
_ ->
|
||||
keys = for {key, _} <- remaining, do: key
|
||||
raise ArgumentError, "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, "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, "expected arguments to be compile time keywords"
|
||||
end
|
||||
|
||||
if Macro.Env.in_match?(caller) do
|
||||
raise ArgumentError, "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, "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, "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_string(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 = String.to_atom "update_" <> Atom.to_string(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_string(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 = String.to_atom "update_" <> Atom.to_string(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, "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
|
||||
+49
-43
@@ -22,49 +22,56 @@ defmodule Regex do
|
||||
|
||||
The modifiers available when creating a Regex are:
|
||||
|
||||
* `unicode` (u) - enables unicode specific patterns like \p. it expects valid unicode
|
||||
strings to be given on match
|
||||
* `caseless` (i) - add case insensitivity
|
||||
* `dotall` (s) - causes dot to match newlines and also set newline to anycrlf.
|
||||
The new line setting can be overridden by setting `(*CR)` or `(*LF)` or
|
||||
`(*CRLF)` or `(*ANY)` according to re documentation
|
||||
* `multiline` (m) - causes `^` and `$` to mark the beginning and end of each line.
|
||||
Use `\A` and `\z` to match the end or beginning of the string
|
||||
* `extended` (x) - whitespace characters are ignored except when escaped and
|
||||
allow `#` to delimit comments
|
||||
* `firstline` (f) - forces the unanchored pattern to match before or at the first
|
||||
newline, though the matched text may continue over the newline
|
||||
* `ungreedy` (r) - inverts the "greediness" of the regexp
|
||||
* `unicode` (u) - enables unicode specific patterns like `\p`; it expects
|
||||
valid unicode strings to be given on match
|
||||
|
||||
* `caseless` (i) - add case insensitivity
|
||||
|
||||
* `dotall` (s) - causes dot to match newlines and also set newline to
|
||||
anycrlf; the new line setting can be overridden by setting `(*CR)` or
|
||||
`(*LF)` or `(*CRLF)` or `(*ANY)` according to re documentation
|
||||
|
||||
* `multiline` (m) - causes `^` and `$` to mark the beginning and end of
|
||||
each line; use `\A` and `\z` to match the end or beginning of the string
|
||||
|
||||
* `extended` (x) - whitespace characters are ignored except when escaped
|
||||
and allow `#` to delimit comments
|
||||
|
||||
* `firstline` (f) - forces the unanchored pattern to match before or at the
|
||||
first newline, though the matched text may continue over the newline
|
||||
|
||||
* `ungreedy` (r) - inverts the "greediness" of the regexp
|
||||
|
||||
The options not available are:
|
||||
|
||||
* `anchored` - not available, use `^` or `\A` instead
|
||||
* `dollar_endonly` - not available, use `\z` instead
|
||||
* `no_auto_capture` - not available, use `?:` instead
|
||||
* `newline` - not available, use `(*CR)` or `(*LF)` or `(*CRLF)` or `(*ANYCRLF)`
|
||||
or `(*ANY)` at the beginning of the regexp according to the re documentation
|
||||
* `anchored` - not available, use `^` or `\A` instead
|
||||
* `dollar_endonly` - not available, use `\z` instead
|
||||
* `no_auto_capture` - not available, use `?:` instead
|
||||
* `newline` - not available, use `(*CR)` or `(*LF)` or `(*CRLF)` or
|
||||
`(*ANYCRLF)` or `(*ANY)` at the beginning of the regexp according to the
|
||||
re documentation
|
||||
|
||||
## Captures
|
||||
|
||||
Many functions in this module allows what to capture in a regex
|
||||
match via the `:capture` option. The supported values are:
|
||||
|
||||
* `:all` - all captured subpatterns including the complete matching string.
|
||||
This is the default;
|
||||
* `:all` - all captured subpatterns including the complete matching string
|
||||
(this is the default)
|
||||
|
||||
* `:first` - only the first captured subpattern, which is always the complete
|
||||
matching part of the string. All explicitly captured subpatterns are
|
||||
discarded;
|
||||
* `:first` - only the first captured subpattern, which is always the
|
||||
complete matching part of the string; all explicitly captured subpatterns
|
||||
are discarded
|
||||
|
||||
* `:all_but_first`- all but the first matching subpattern, i.e. all explicitly
|
||||
captured subpatterns, but not the complete matching part of
|
||||
the string;
|
||||
* `:all_but_first`- all but the first matching subpattern, i.e. all
|
||||
explicitly captured subpatterns, but not the complete matching part of
|
||||
the string
|
||||
|
||||
* `:none` - do not return matching subpatterns at all;
|
||||
* `:none` - do not return matching subpatterns at all
|
||||
|
||||
* `:all_names` - captures all names in the Regex;
|
||||
* `:all_names` - captures all names in the Regex
|
||||
|
||||
* `list(binary)` - a list of named captures to capture;
|
||||
* `list(binary)` - a list of named captures to capture
|
||||
|
||||
"""
|
||||
|
||||
@@ -167,9 +174,9 @@ defmodule Regex do
|
||||
|
||||
## Options
|
||||
|
||||
* `:return` - Set to `:index` to return indexes. Defaults to `:binary`;
|
||||
* `:capture` - What to capture in the result. Check the moduledoc for Regex
|
||||
to see the possible capture values;
|
||||
* `:return` - set to `:index` to return indexes. Defaults to `:binary`.
|
||||
* `:capture` - what to capture in the result. Check the moduledoc for `Regex`
|
||||
to see the possible capture values.
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -275,9 +282,9 @@ defmodule Regex do
|
||||
|
||||
## Options
|
||||
|
||||
* `:return` - Set to `:index` to return indexes. Defaults to `:binary`;
|
||||
* `:capture` - What to capture in the result. Check the moduledoc for Regex
|
||||
to see the possible capture values;
|
||||
* `:return` - set to `:index` to return indexes. Defaults to `:binary`.
|
||||
* `:capture` - what to capture in the result. Check the moduledoc for `Regex`
|
||||
to see the possible capture values.
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -310,13 +317,12 @@ defmodule Regex do
|
||||
|
||||
## Options
|
||||
|
||||
* `:parts` - when specified, splits the string into the
|
||||
given number of parts. If not specified, `:parts`
|
||||
is defaulted to `:infinity`, which will split the
|
||||
string into the maximum number of parts possible
|
||||
based on the given pattern.
|
||||
* `:parts` - when specified, splits the string into the given number of
|
||||
parts. If not specified, `:parts` is defaulted to `:infinity`, which will
|
||||
split the string into the maximum number of parts possible based on the
|
||||
given pattern.
|
||||
|
||||
* `:trim` - when true, remove blank strings from the result;
|
||||
* `:trim` - when true, remove blank strings from the result.
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -370,8 +376,8 @@ defmodule Regex do
|
||||
|
||||
## Options
|
||||
|
||||
* `:global` - when `false`, replaces only the first occurrence
|
||||
(defaults to true)
|
||||
* `:global` - when `false`, replaces only the first occurrence
|
||||
(defaults to true)
|
||||
|
||||
## Examples
|
||||
|
||||
|
||||
@@ -76,8 +76,8 @@ defmodule Stream do
|
||||
There are many functions in Elixir's standard library that return
|
||||
streams, some examples are:
|
||||
|
||||
* `IO.stream/2` - Streams input lines, one by one;
|
||||
* `URI.query_decoder/1` - Decodes a query string, pair by pair;
|
||||
* `IO.stream/2` - streams input lines, one by one
|
||||
* `URI.query_decoder/1` - decodes a query string, pair by pair
|
||||
|
||||
This module also provides many convenience functions for creating streams,
|
||||
like `Stream.cycle/1`, `Stream.unfold/2`, `Stream.resource/3` and more.
|
||||
@@ -420,6 +420,7 @@ defmodule Stream do
|
||||
stream = File.stream!("code")
|
||||
|> Stream.map(&String.replace(&1, "#", "%"))
|
||||
|> Stream.into(File.stream!("new"))
|
||||
|> Stream.run
|
||||
|
||||
No computation will be done until we call one of the Enum functions
|
||||
or `Stream.run/1`.
|
||||
@@ -988,7 +989,7 @@ defmodule Stream do
|
||||
Emits a sequence of values for the given accumulator.
|
||||
|
||||
Successive values are generated by calling `next_fun` with the previous
|
||||
accumulator and it must return a tuple with the current and next
|
||||
accumulator and it must return a tuple with the current value and next
|
||||
accumulator. The enumeration finishes if it returns `nil`.
|
||||
|
||||
## Examples
|
||||
|
||||
+86
-71
@@ -15,11 +15,11 @@ defmodule String do
|
||||
operations that work directly with binaries. Some
|
||||
of those can be found in the `Kernel` module, as:
|
||||
|
||||
* `Kernel.binary_part/3` - retrieves part of the binary
|
||||
* `Kernel.bit_size/1` and `Kernel.byte_size/1` - size related functions
|
||||
* `Kernel.is_bitstring/1` and `Kernel.is_binary/1` - type checking function
|
||||
* Plus a number of functions for working with binaries (bytes)
|
||||
[in the `:binary` module](http://erlang.org/doc/man/binary.html)
|
||||
* `Kernel.binary_part/3` - retrieves part of the binary
|
||||
* `Kernel.bit_size/1` and `Kernel.byte_size/1` - size related functions
|
||||
* `Kernel.is_bitstring/1` and `Kernel.is_binary/1` - type checking function
|
||||
* Plus a number of functions for working with binaries (bytes)
|
||||
[in the `:binary` module](http://erlang.org/doc/man/binary.html)
|
||||
|
||||
## Codepoints and graphemes
|
||||
|
||||
@@ -767,6 +767,65 @@ defmodule String do
|
||||
def valid_character?(<<_ :: utf8>> = codepoint), do: valid?(codepoint)
|
||||
def valid_character?(_), do: false
|
||||
|
||||
@doc ~S"""
|
||||
Splits the string into chunks of characters that share a common trait.
|
||||
|
||||
The trait can be one of two options:
|
||||
|
||||
* `:valid` – the string is split into chunks of valid and invalid character
|
||||
sequences
|
||||
|
||||
* `:printable` – the string is split into chunks of printable and
|
||||
non-printable character sequences
|
||||
|
||||
Returns a list of binaries each of which contains only one kind of
|
||||
characters.
|
||||
|
||||
If the given string is empty, an empty list is returned.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> String.chunk(<<?a, ?b, ?c, 0>>, :valid)
|
||||
["abc\000"]
|
||||
|
||||
iex> String.chunk(<<?a, ?b, ?c, 0, 0x0ffff::utf8>>, :valid)
|
||||
["abc\000", <<0x0ffff::utf8>>]
|
||||
|
||||
iex> String.chunk(<<?a, ?b, ?c, 0, 0x0ffff::utf8>>, :printable)
|
||||
["abc", <<0, 0x0ffff::utf8>>]
|
||||
|
||||
"""
|
||||
@spec chunk(t, :valid | :printable) :: [t]
|
||||
|
||||
def chunk(string, trait)
|
||||
|
||||
def chunk("", _), do: []
|
||||
|
||||
def chunk(str, trait) when trait in [:valid, :printable] do
|
||||
{cp, _} = next_codepoint(str)
|
||||
pred_fn = make_chunk_pred(trait)
|
||||
do_chunk(str, pred_fn.(cp), pred_fn)
|
||||
end
|
||||
|
||||
|
||||
defp do_chunk(str, flag, pred_fn), do: do_chunk(str, [], <<>>, flag, pred_fn)
|
||||
|
||||
defp do_chunk(<<>>, acc, <<>>, _, _), do: Enum.reverse(acc)
|
||||
|
||||
defp do_chunk(<<>>, acc, chunk, _, _), do: Enum.reverse(acc, [chunk])
|
||||
|
||||
defp do_chunk(str, acc, chunk, flag, pred_fn) do
|
||||
{cp, rest} = next_codepoint(str)
|
||||
if pred_fn.(cp) != flag do
|
||||
do_chunk(rest, [chunk|acc], cp, not flag, pred_fn)
|
||||
else
|
||||
do_chunk(rest, acc, chunk <> cp, flag, pred_fn)
|
||||
end
|
||||
end
|
||||
|
||||
defp make_chunk_pred(:valid), do: &valid?/1
|
||||
defp make_chunk_pred(:printable), do: &printable?/1
|
||||
|
||||
@doc """
|
||||
Returns unicode graphemes in the string as per Extended Grapheme
|
||||
Cluster algorithm outlined in the [Unicode Standard Annex #29,
|
||||
@@ -868,7 +927,7 @@ defmodule String do
|
||||
|
||||
@doc """
|
||||
Returns the grapheme in the `position` of the given utf8 `string`.
|
||||
If `position` is greater than `string` length, than it returns `nil`.
|
||||
If `position` is greater than `string` length, then it returns `nil`.
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -915,7 +974,7 @@ defmodule String do
|
||||
@doc """
|
||||
Returns a substring starting at the offset given by the first, and
|
||||
a length given by the second.
|
||||
If the offset is greater than string length, than it returns `nil`.
|
||||
If the offset is greater than string length, then it returns `""`.
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -926,13 +985,13 @@ defmodule String do
|
||||
"lixir"
|
||||
|
||||
iex> String.slice("elixir", 10, 3)
|
||||
nil
|
||||
""
|
||||
|
||||
iex> String.slice("elixir", -4, 4)
|
||||
"ixir"
|
||||
|
||||
iex> String.slice("elixir", -10, 3)
|
||||
nil
|
||||
""
|
||||
|
||||
iex> String.slice("a", 0, 1500)
|
||||
"a"
|
||||
@@ -941,16 +1000,13 @@ defmodule String do
|
||||
""
|
||||
|
||||
iex> String.slice("a", 2, 1500)
|
||||
nil
|
||||
""
|
||||
|
||||
"""
|
||||
@spec slice(t, integer, integer) :: grapheme | nil
|
||||
@spec slice(t, integer, integer) :: grapheme
|
||||
|
||||
def slice(string, start, 0) do
|
||||
case abs(start) <= length(string) do
|
||||
true -> ""
|
||||
false -> nil
|
||||
end
|
||||
def slice(_, _, 0) do
|
||||
""
|
||||
end
|
||||
|
||||
def slice(string, start, len) when start >= 0 and len >= 0 do
|
||||
@@ -961,7 +1017,7 @@ defmodule String do
|
||||
real_start_pos = length(string) - abs(start)
|
||||
case real_start_pos >= 0 do
|
||||
true -> do_slice(next_grapheme(string), real_start_pos, real_start_pos + len - 1, 0, "")
|
||||
false -> nil
|
||||
false -> ""
|
||||
end
|
||||
end
|
||||
|
||||
@@ -970,7 +1026,7 @@ defmodule String do
|
||||
range to the offset given by the end of the range.
|
||||
|
||||
If the start of the range is not a valid offset for the given
|
||||
string or if the range is in reverse order, returns `nil`.
|
||||
string or if the range is in reverse order, returns `""`.
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -981,7 +1037,7 @@ defmodule String do
|
||||
"lixir"
|
||||
|
||||
iex> String.slice("elixir", 10..3)
|
||||
nil
|
||||
""
|
||||
|
||||
iex> String.slice("elixir", -4..-1)
|
||||
"ixir"
|
||||
@@ -993,10 +1049,10 @@ defmodule String do
|
||||
"ixir"
|
||||
|
||||
iex> String.slice("elixir", -1..-4)
|
||||
nil
|
||||
""
|
||||
|
||||
iex> String.slice("elixir", -10..-7)
|
||||
nil
|
||||
""
|
||||
|
||||
iex> String.slice("a", 0..1500)
|
||||
"a"
|
||||
@@ -1005,10 +1061,10 @@ defmodule String do
|
||||
""
|
||||
|
||||
iex> String.slice("a", 2..1500)
|
||||
nil
|
||||
""
|
||||
|
||||
"""
|
||||
@spec slice(t, Range.t) :: t | nil
|
||||
@spec slice(t, Range.t) :: t
|
||||
|
||||
def slice(string, range)
|
||||
|
||||
@@ -1029,11 +1085,13 @@ defmodule String do
|
||||
|
||||
if first >= 0 do
|
||||
do_slice(next_grapheme(string), first, last, 0, "")
|
||||
else
|
||||
""
|
||||
end
|
||||
end
|
||||
|
||||
defp do_slice(_, start_pos, last_pos, _, _) when start_pos > last_pos do
|
||||
nil
|
||||
""
|
||||
end
|
||||
|
||||
defp do_slice({_, rest}, start_pos, last_pos, current_pos, acc) when current_pos < start_pos do
|
||||
@@ -1047,16 +1105,9 @@ defmodule String do
|
||||
defp do_slice({char, _}, start_pos, last_pos, current_pos, acc) when current_pos >= start_pos and current_pos == last_pos do
|
||||
acc <> char
|
||||
end
|
||||
|
||||
defp do_slice(nil, start_pos, _, current_pos, acc) when start_pos == current_pos do
|
||||
acc
|
||||
end
|
||||
|
||||
|
||||
defp do_slice(nil, _, _, _, acc) do
|
||||
case acc do
|
||||
"" -> nil
|
||||
_ -> acc
|
||||
end
|
||||
acc
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -1124,8 +1175,8 @@ defmodule String do
|
||||
end
|
||||
|
||||
defp do_ends_with(string, suffix) when is_binary(suffix) do
|
||||
string_size = size(string)
|
||||
suffix_size = size(suffix)
|
||||
string_size = byte_size(string)
|
||||
suffix_size = byte_size(suffix)
|
||||
scope = {string_size - suffix_size, suffix_size}
|
||||
(suffix_size <= string_size) and (:nomatch != :binary.match(string, suffix, [scope: scope]))
|
||||
end
|
||||
@@ -1297,40 +1348,4 @@ defmodule String do
|
||||
def to_float(string) do
|
||||
:erlang.binary_to_float(string)
|
||||
end
|
||||
|
||||
@doc false
|
||||
def from_char_data(binary) when is_binary(binary) do
|
||||
binary
|
||||
end
|
||||
|
||||
def from_char_data(list) when is_list(list) do
|
||||
case :unicode.characters_to_binary(list) do
|
||||
result when is_binary(result) ->
|
||||
{:ok, result}
|
||||
|
||||
{:error, _, _} = error ->
|
||||
error
|
||||
|
||||
{:incomplete, _, _} = incomplete ->
|
||||
incomplete
|
||||
end
|
||||
end
|
||||
|
||||
@doc false
|
||||
def from_char_data!(binary) when is_binary(binary) do
|
||||
binary
|
||||
end
|
||||
|
||||
def from_char_data!(list) when is_list(list) do
|
||||
case :unicode.characters_to_binary(list) do
|
||||
result when is_binary(result) ->
|
||||
result
|
||||
|
||||
{:error, encoded, rest} ->
|
||||
raise UnicodeConversionError, encoded: encoded, rest: rest, kind: :invalid
|
||||
|
||||
{:incomplete, encoded, rest} ->
|
||||
raise UnicodeConversionError, encoded: encoded, rest: rest, kind: :incomplete
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -199,7 +199,7 @@ defmodule StringIO do
|
||||
end
|
||||
|
||||
defp do_get_chars(input, :latin1, n) do
|
||||
<< chars :: [ binary, size(n) ], rest :: binary >> = input
|
||||
<<chars :: [binary, size(n)], rest :: binary>> = input
|
||||
{chars, rest}
|
||||
end
|
||||
|
||||
@@ -209,7 +209,7 @@ defmodule StringIO do
|
||||
{buf_count, split_pos} when buf_count < n or split_pos == :none ->
|
||||
{input, ""}
|
||||
{_buf_count, split_pos} ->
|
||||
<< chars :: [ binary, size(split_pos) ], rest :: binary >> = input
|
||||
<<chars :: [binary, size(split_pos)], rest :: binary>> = input
|
||||
{chars, rest}
|
||||
end
|
||||
catch
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
defmodule Supervisor do
|
||||
@moduledoc """
|
||||
A behaviour module for implementing supevision functionality.
|
||||
A behaviour module for implementing supervision functionality.
|
||||
|
||||
A supervisor is a process which supervises other processes called
|
||||
child processes. Supervisors are used to build an hierarchical process
|
||||
structure called a supervision tree, a nice way to structure a fault
|
||||
tolerant applications.
|
||||
structure called a supervision tree, a nice way to structure fault-tolerant
|
||||
applications.
|
||||
|
||||
A supervisor implemented using this module will have a standard set
|
||||
of interface functions and include functionality for tracing and error
|
||||
@@ -44,7 +44,7 @@ defmodule Supervisor do
|
||||
worker(Stack, [[:hello]])
|
||||
]
|
||||
|
||||
# Start the supervisor with our one children
|
||||
# Start the supervisor with our one child
|
||||
{:ok, pid} = Supervisor.start_link(children, strategy: :one_for_one)
|
||||
|
||||
Notice that when starting the GenServer, we have registered it
|
||||
@@ -73,12 +73,12 @@ defmodule Supervisor do
|
||||
|
||||
GenServer.call(:sup_stack, :pop) == :hello
|
||||
|
||||
Supervisors support different strategies, in the example above, we
|
||||
Supervisors support different strategies; in the example above, we
|
||||
have chosen `:one_for_one`. Furthermore, each supervisor can have many
|
||||
workers and supervisors as children, each of them with their specific
|
||||
configuration, shutdown values and restart strategies.
|
||||
configuration, shutdown values, and restart strategies.
|
||||
|
||||
Continue reading this module to learn more about supervision strategies
|
||||
Continue reading this moduledoc to learn more about supervision strategies
|
||||
and then follow to the `Supervisor.Spec` module documentation to learn
|
||||
about the specification for workers and supervisors.
|
||||
|
||||
@@ -86,7 +86,7 @@ defmodule Supervisor do
|
||||
|
||||
In the example above, a supervisor was dynamically created by passing
|
||||
the supervision structure to `start_link/2`. However, supervisors
|
||||
can also be created by explicitly defining a supevision module:
|
||||
can also be created by explicitly defining a supervision module:
|
||||
|
||||
defmodule MyApp.Supervisor do
|
||||
use Supervisor
|
||||
@@ -96,51 +96,49 @@ defmodule Supervisor do
|
||||
end
|
||||
|
||||
def init([]) do
|
||||
import Supervisor.Spec
|
||||
|
||||
children = [
|
||||
worker(Stack, [[:hello]])
|
||||
]
|
||||
|
||||
supervisor(children, strategy: :one_for_one)
|
||||
supervise(children, strategy: :one_for_one)
|
||||
end
|
||||
end
|
||||
|
||||
You may want to use a module-based supervisor if:
|
||||
|
||||
* You need to do some particular action on supervisor
|
||||
initialization, like setting up a ETS table;
|
||||
* You need to do some particular action on supervisor
|
||||
initialization, like setting up a ETS table.
|
||||
|
||||
* You want to perform partial hot-code swapping of the
|
||||
tree. For example, if you add or remove a children,
|
||||
the module-based supervision will add and remove the
|
||||
new children directly, while the dynamic supervision
|
||||
requires the whole tree to be restarted in order to
|
||||
perform such swaps;
|
||||
* You want to perform partial hot-code swapping of the
|
||||
tree. For example, if you add or remove a children,
|
||||
the module-based supervision will add and remove the
|
||||
new children directly, while the dynamic supervision
|
||||
requires the whole tree to be restarted in order to
|
||||
perform such swaps.
|
||||
|
||||
## Strategies
|
||||
|
||||
* `:one_for_one` - If a child process terminates, only that
|
||||
process is restarted;
|
||||
* `:one_for_one` - if a child process terminates, only that
|
||||
process is restarted.
|
||||
|
||||
* `:one_for_all` - If a child process terminates, all other child
|
||||
processes are terminated and then all child processes, including
|
||||
the terminated one, are restarted;
|
||||
* `:one_for_all` - if a child process terminates, all other child
|
||||
processes are terminated and then all child processes (including
|
||||
the terminated one) are restarted.
|
||||
|
||||
* `:rest_for_one` - If a child process terminates, the "rest" of
|
||||
the child processes, i.e. the child processes after the terminated
|
||||
process in start order, are terminated. Then the terminated child
|
||||
process and the rest of the child processes are restarted;
|
||||
* `:rest_for_one` - if a child process terminates, the "rest" of
|
||||
the child processes, i.e. the child processes after the terminated
|
||||
one in start order, are terminated. Then the terminated child
|
||||
process and the rest of the child processes are restarted.
|
||||
|
||||
* `:simple_one_for_one` - Similar to `:one_for_one` but suits better
|
||||
when dynamically attaching children. This strategy requires the
|
||||
supervisor specification to contain only one children. Many functions
|
||||
in this module behave slightly differently when this strategy is
|
||||
used;
|
||||
* `:simple_one_for_one` - similar to `:one_for_one` but suits better
|
||||
when dynamically attaching children. This strategy requires the
|
||||
supervisor specification to contain only one children. Many functions
|
||||
in this module behave slightly differently when this strategy is
|
||||
used.
|
||||
|
||||
## Name registering
|
||||
## Name Registration
|
||||
|
||||
A supervisor is bound to the same name registering rules as a `GenServer`.
|
||||
A supervisor is bound to the same name registration rules as a `GenServer`.
|
||||
Read more about it in the `GenServer` docs.
|
||||
"""
|
||||
|
||||
@@ -181,22 +179,26 @@ defmodule Supervisor do
|
||||
the `:max_restarts` and `:max_seconds` value can be configured
|
||||
as described in `Supervisor.Spec.supervise/2` docs.
|
||||
|
||||
The options can also be used to register a supervisor name,
|
||||
the supported values are described under the `Name Registering`
|
||||
The options can also be used to register a supervisor name.
|
||||
the supported values are described under the `Name Registration`
|
||||
section in the `GenServer` module docs.
|
||||
|
||||
If the supervisor and its child processes are successfully created
|
||||
(i.e. if all child process start functions return `{:ok, child}`,
|
||||
(i.e. if the start function of all child processes returns `{:ok, child}`,
|
||||
`{:ok, child, info}`, or `:ignore`) the function returns
|
||||
`{:ok, pid}`, where `pid` is the pid of the supervisor. If there
|
||||
already exists a process with the specified name the function returns
|
||||
already exists a process with the specified name, the function returns
|
||||
`{:error, {:already_started, pid}}`, where pid is the pid of that
|
||||
process.
|
||||
|
||||
If any child process start function fails or returns an error tuple or
|
||||
If any of the child process start functions fail or return an error tuple or
|
||||
an erroneous value, the supervisor will first terminate all already
|
||||
started child processes with reason `:shutdown` and then terminate
|
||||
itself and return `{:error, {:shutdown, reason}}`.
|
||||
|
||||
Note that the `Supervisor` is linked to the parent process
|
||||
and will exit not only on crashes but also if the parent process
|
||||
exits with `:normal` reason.
|
||||
"""
|
||||
@spec start_link([tuple], options) :: on_start
|
||||
def start_link(children, options) when is_list(children) do
|
||||
@@ -215,11 +217,11 @@ defmodule Supervisor do
|
||||
If the `init/1` callback returns `:ignore`, this function returns
|
||||
`:ignore` as well and the supervisor terminates with reason `:normal`.
|
||||
If it fails or returns an incorrect value, this function returns
|
||||
`{:error, term} where term is a term with information about the
|
||||
`{:error, term}` where `term` is a term with information about the
|
||||
error, and the supervisor terminates with reason `term`.
|
||||
|
||||
The `:name` option can also be given in order to register a supervisor
|
||||
name, the supported values are described under the `Name Registering`
|
||||
name, the supported values are described under the `Name Registration`
|
||||
section in the `GenServer` module docs.
|
||||
|
||||
Other failure conditions are specified in `start_link/2` docs.
|
||||
@@ -243,7 +245,7 @@ defmodule Supervisor do
|
||||
is a `:simple_one_for_one` supervisor, see below). The child process will
|
||||
be started as defined in the child specification.
|
||||
|
||||
In the case of a `:simple_one_for_one`, the child specification defined in
|
||||
In the case of `:simple_one_for_one`, the child specification defined in
|
||||
the supervisor will be used and instead of a `child_spec`, an arbitrary list
|
||||
of terms is expected. The child process will then be started by appending
|
||||
the given list to the existing function arguments in the child specification.
|
||||
@@ -262,7 +264,7 @@ defmodule Supervisor do
|
||||
|
||||
If the child process start function returns an error tuple or an erroneous value,
|
||||
or if it fails, the child specification is discarded and the function returns
|
||||
`{:error, error}` where error is a term containing information about the error
|
||||
`{:error, error}` where `error` is a term containing information about the error
|
||||
and child specification.
|
||||
"""
|
||||
@spec start_child(supervisor, Supervisor.Spec.spec | [term]) :: on_start_child
|
||||
@@ -272,18 +274,18 @@ defmodule Supervisor do
|
||||
Terminates the given pid or child id.
|
||||
|
||||
If the supervisor is not a `simple_one_for_one`, the child id is expected
|
||||
and the process, if there is one, is terminated and, the child specification is
|
||||
and the process, if there is one, is terminated; the child specification is
|
||||
kept unless the child is temporary.
|
||||
|
||||
In case of a `simple_one_for_one` supervisor, a pid is expected. If the child
|
||||
specification identifier is given instead instead of a `pid`, the function will
|
||||
specification identifier is given instead of a `pid`, the function will
|
||||
return `{:error, :simple_one_for_one}`.
|
||||
|
||||
Non-temporary child process may later be restarted by the supervisor. The child
|
||||
A non-temporary child process may later be restarted by the supervisor. The child
|
||||
process can also be restarted explicitly by calling `restart_child/2`. Use
|
||||
`delete_child/2` to remove the child specification.
|
||||
|
||||
If successful, the function returns ok. If there is no child specification or
|
||||
If successful, the function returns `:ok`. If there is no child specification or
|
||||
pid, the function returns `{:error, :not_found}`.
|
||||
"""
|
||||
@spec terminate_child(supervisor, pid | Supervisor.Spec.child_id) :: :ok | {:error, error}
|
||||
@@ -343,15 +345,16 @@ defmodule Supervisor do
|
||||
|
||||
This function returns a list of tuples containing:
|
||||
|
||||
* the `id` - as defined in the child specification or `:undefined` in the case
|
||||
of a `simple_one_for_one` supervisor;
|
||||
* `id` - as defined in the child specification or `:undefined` in the case
|
||||
of a `simple_one_for_one` supervisor
|
||||
|
||||
* the `child` - the pid of the corresponding child process, the atom `:restarting`
|
||||
if the process is about to be restarted or `:undefined` if there is no such process;
|
||||
* `child` - the pid of the corresponding child process, the atom
|
||||
`:restarting` if the process is about to be restarted, or `:undefined` if
|
||||
there is no such process
|
||||
|
||||
* the `type` - `:worker` or `:supervisor` as defined in the child specification;
|
||||
* `type` - `:worker` or `:supervisor` as defined in the child specification
|
||||
|
||||
* the `modules` as defined in the child specification;
|
||||
* `modules` – as defined in the child specification
|
||||
"""
|
||||
@spec which_children(supervisor) ::
|
||||
[{Supervisor.Spec.child_id | :undefined,
|
||||
@@ -361,17 +364,20 @@ defmodule Supervisor do
|
||||
defdelegate which_children(supervisor), to: :supervisor
|
||||
|
||||
@doc """
|
||||
Returns a map containing count value for the supervisor.
|
||||
Returns a map containing count values for the supervisor.
|
||||
|
||||
The map contains the following keys:
|
||||
|
||||
* `:specs` - the total count of children, dead or alive;
|
||||
* `:specs` - the total count of children, dead or alive
|
||||
|
||||
* `:active` - the count of all actively running child processes managed by this supervisor;
|
||||
* `:active` - the count of all actively running child processes managed by
|
||||
this supervisor
|
||||
|
||||
* `:supervisors` - the count of all supervisors whether or not the child process is still alive;
|
||||
* `:supervisors` - the count of all supervisors whether or not the child
|
||||
process is still alive
|
||||
|
||||
* `:workers` - the count of all workers, whether or not the child process is still alive;
|
||||
* `:workers` - the count of all workers, whether or not the child process
|
||||
is still alive
|
||||
|
||||
"""
|
||||
@spec count_children(supervisor) ::
|
||||
|
||||
@@ -1,156 +0,0 @@
|
||||
defmodule Supervisor.Behaviour do
|
||||
@moduledoc false
|
||||
|
||||
@doc false
|
||||
defmacro __using__(_) do
|
||||
quote location: :keep do
|
||||
@behaviour :supervisor
|
||||
import unquote(__MODULE__)
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Receives a list of children (worker or supervisors) to
|
||||
supervise and a set of options. Returns a tuple containing
|
||||
the supervisor specification.
|
||||
|
||||
## Examples
|
||||
|
||||
supervise children, strategy: :one_for_one
|
||||
|
||||
## Options
|
||||
|
||||
* `:strategy` - the restart strategy option It can be either
|
||||
`:one_for_one`, `:rest_for_one`, `:one_for_all` and
|
||||
`:simple_one_for_one`;
|
||||
|
||||
* `:max_restarts` - the maximum amount of restarts allowed in
|
||||
a time frame. Defaults to 5;
|
||||
|
||||
* `:max_seconds` - the time frame in which max_restarts applies.
|
||||
Defaults to 5;
|
||||
|
||||
The `:strategy` option is required and by default maximum 5 restarts
|
||||
are allowed in 5 seconds.
|
||||
|
||||
## Strategies
|
||||
|
||||
* `:one_for_one` - If a child process terminates, only that
|
||||
process is restarted;
|
||||
|
||||
* `:one_for_all` - If a child process terminates, all other child
|
||||
processes are terminated and then all child processes, including
|
||||
the terminated one, are restarted;
|
||||
|
||||
* `:rest_for_one` - If a child process terminates, the "rest" of
|
||||
the child processes, i.e. the child processes after the terminated
|
||||
process in start order, are terminated. Then the terminated child
|
||||
process and the rest of the child processes are restarted;
|
||||
|
||||
* `:simple_one_for_one` - Similar to `:one_for_one` but suits better
|
||||
when dynamically attaching children;
|
||||
|
||||
"""
|
||||
def supervise(children, options) do
|
||||
unless strategy = options[:strategy] do
|
||||
raise ArgumentError, "expected :strategy option to be given to supervise"
|
||||
end
|
||||
|
||||
maxR = Keyword.get(options, :max_restarts, 5)
|
||||
maxS = Keyword.get(options, :max_seconds, 5)
|
||||
|
||||
{:ok, {{strategy, maxR, maxS}, children}}
|
||||
end
|
||||
|
||||
@child_doc """
|
||||
## Options
|
||||
|
||||
* `:id` - a name used to identify the child specification
|
||||
internally by the supervisor. Defaults to the module name;
|
||||
|
||||
* `:function` - the function to invoke on the child to start it.
|
||||
Defaults to `:start_link`;
|
||||
|
||||
* `:restart` - defines when the child process should restart.
|
||||
Defaults to `:permanent`;
|
||||
|
||||
* `:shutdown` - defines how a child process should be terminated.
|
||||
Defaults to `5000` for a worker and `:infinity` for a supervisor;
|
||||
|
||||
* `:modules` - it should be a list with one element `[module]`,
|
||||
where module is the name of the callback module only if the
|
||||
child process is a supervisor, `gen_server` or `gen_fsm`. If the
|
||||
child process is a gen_event, modules should be `:dynamic`.
|
||||
Defaults to a list with the given module;
|
||||
|
||||
## Restart values
|
||||
|
||||
The following restart values are supported:
|
||||
|
||||
* `:permanent` - the child process is always restarted;
|
||||
|
||||
* `:temporary` - the child process is never restarted (not even
|
||||
when the supervisor's strategy is `:rest_for_one` or `:one_for_all`);
|
||||
|
||||
* `:transient` - the child process is restarted only if it
|
||||
terminates abnormally, i.e. with another exit reason than
|
||||
`:normal`, `:shutdown` or `{:shutdown, term}`;
|
||||
|
||||
## Shutdown values
|
||||
|
||||
The following shutdown values are supported:
|
||||
|
||||
* `:brutal_kill` - the child process is unconditionally terminated
|
||||
using `exit(child, :kill)`;
|
||||
|
||||
* `:infinity` - if the child process is a supervisor, it is a mechanism
|
||||
to give the subtree enough time to shutdown. It can also be used with
|
||||
workers with care;
|
||||
|
||||
* Finally, it can also be any integer meaning that the supervisor tells
|
||||
the child process to terminate by calling `exit(child, :shutdown)` and
|
||||
then waits for an exit signal back. If no exit signal is received within
|
||||
the specified time (in miliseconds), the child process is unconditionally
|
||||
terminated using `exit(child, :kill)`;
|
||||
"""
|
||||
|
||||
@doc """
|
||||
Defines the given `module` as a worker which will be started
|
||||
with the given arguments.
|
||||
|
||||
worker ExUnit.Runner, [], restart: :permanent
|
||||
|
||||
By default, the function `:start_link` is invoked on the given module.
|
||||
|
||||
#{@child_doc}
|
||||
"""
|
||||
def worker(module, args, options \\ []) do
|
||||
child(:worker, module, args, options)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Defines the given `module` as a supervisor which will be started
|
||||
with the given arguments.
|
||||
|
||||
supervisor ExUnit.Runner, [], restart: :permanent
|
||||
|
||||
By default, the function `:start_link` is invoked on the given module.
|
||||
|
||||
#{@child_doc}
|
||||
"""
|
||||
def supervisor(module, args, options \\ []) do
|
||||
options = Keyword.update(options, :shutdown, :infinity, fn(x) -> x end)
|
||||
child(:supervisor, module, args, options)
|
||||
end
|
||||
|
||||
defp child(type, module, args, options) do
|
||||
id = Keyword.get(options, :id, module)
|
||||
modules = Keyword.get(options, :modules, [module])
|
||||
function = Keyword.get(options, :function, :start_link)
|
||||
restart = Keyword.get(options, :restart, :permanent)
|
||||
shutdown = Keyword.get(options, :shutdown, 5000)
|
||||
|
||||
{id, {module, function, args},
|
||||
restart, shutdown, type, modules}
|
||||
end
|
||||
end
|
||||
@@ -40,64 +40,65 @@ defmodule Supervisor.Spec do
|
||||
|
||||
Explicit supervisors as above are required when there is a need to:
|
||||
|
||||
1. partialy change the supervision tree during hot-code swaps;
|
||||
1. Partialy change the supervision tree during hot-code swaps.
|
||||
|
||||
2. define supervisors inside other supervisors;
|
||||
2. Define supervisors inside other supervisors.
|
||||
|
||||
3. perform actions inside the supervision `init/1` callback.
|
||||
For example, you may want to start an ETS table that is linked to
|
||||
the supervisor (i.e. if the supervision tree needs to be restarted,
|
||||
the ETS table must be restarted too);
|
||||
3. Perform actions inside the supervision `init/1` callback.
|
||||
|
||||
For example, you may want to start an ETS table that is linked to
|
||||
the supervisor (i.e. if the supervision tree needs to be restarted,
|
||||
the ETS table must be restarted too).
|
||||
|
||||
## Supervisor and worker options
|
||||
|
||||
In the example above, we have defined workers and supervisors
|
||||
and each accepts the following options:
|
||||
|
||||
* `:id` - a name used to identify the child specification
|
||||
internally by the supervisor. Defaults to the given module
|
||||
name;
|
||||
* `:id` - a name used to identify the child specification
|
||||
internally by the supervisor; defaults to the given module
|
||||
name
|
||||
|
||||
* `:function` - the function to invoke on the child to start it;
|
||||
* `:function` - the function to invoke on the child to start it
|
||||
|
||||
* `:restart` - defines when the child process should restart;
|
||||
* `:restart` - defines when the child process should restart
|
||||
|
||||
* `:shutdown` - defines how a child process should be terminated;
|
||||
* `:shutdown` - defines how a child process should be terminated
|
||||
|
||||
* `:modules` - it should be a list with one element `[module]`,
|
||||
where module is the name of the callback module only if the
|
||||
child process is a `Supervisor` or `GenServer` If the child
|
||||
process is a `GenEvent`, modules should be `:dynamic`;
|
||||
* `:modules` - it should be a list with one element `[module]`,
|
||||
where module is the name of the callback module only if the
|
||||
child process is a `Supervisor` or `GenServer`; if the child
|
||||
process is a `GenEvent`, modules should be `:dynamic`
|
||||
|
||||
### Restart values
|
||||
|
||||
The following restart values are supported:
|
||||
|
||||
* `:permanent` - the child process is always restarted;
|
||||
* `:permanent` - the child process is always restarted
|
||||
|
||||
* `:temporary` - the child process is never restarted (not even
|
||||
when the supervisor's strategy is `:rest_for_one` or `:one_for_all`);
|
||||
* `:temporary` - the child process is never restarted (not even
|
||||
when the supervisor's strategy is `:rest_for_one` or `:one_for_all`)
|
||||
|
||||
* `:transient` - the child process is restarted only if it
|
||||
terminates abnormally, i.e. with another exit reason than
|
||||
`:normal`, `:shutdown` or `{:shutdown, term}`;
|
||||
* `:transient` - the child process is restarted only if it
|
||||
terminates abnormally, i.e. with another exit reason than
|
||||
`:normal`, `:shutdown` or `{:shutdown, term}`
|
||||
|
||||
### Shutdown values
|
||||
|
||||
The following shutdown values are supported:
|
||||
|
||||
* `:brutal_kill` - the child process is unconditionally terminated
|
||||
using `exit(child, :kill)`;
|
||||
* `:brutal_kill` - the child process is unconditionally terminated
|
||||
using `exit(child, :kill)`.
|
||||
|
||||
* `:infinity` - if the child process is a supervisor, it is a mechanism
|
||||
to give the subtree enough time to shutdown. It can also be used with
|
||||
workers with care;
|
||||
* `:infinity` - if the child process is a supervisor, it is a mechanism
|
||||
to give the subtree enough time to shutdown. It can also be used with
|
||||
workers with care.
|
||||
|
||||
* Finally, it can also be any integer meaning that the supervisor tells
|
||||
the child process to terminate by calling `Process.exit(child, :shutdown)`
|
||||
and then waits for an exit signal back. If no exit signal is received
|
||||
within the specified time (in miliseconds), the child process is
|
||||
unconditionally terminated using `Process.exit(child, :kill)`;
|
||||
* Finally, it can also be any integer meaning that the supervisor tells
|
||||
the child process to terminate by calling `Process.exit(child, :shutdown)`
|
||||
and then waits for an exit signal back. If no exit signal is received
|
||||
within the specified time (in miliseconds), the child process is
|
||||
unconditionally terminated using `Process.exit(child, :kill)`.
|
||||
"""
|
||||
|
||||
@typedoc "Supported strategies"
|
||||
@@ -138,19 +139,19 @@ defmodule Supervisor.Spec do
|
||||
|
||||
## Options
|
||||
|
||||
* `:strategy` - the restart strategy option It can be either
|
||||
`:one_for_one`, `:rest_for_one`, `:one_for_all` and
|
||||
`:simple_one_for_one`. You can learn more about strategies
|
||||
in the `Supervisor` module;
|
||||
* `:strategy` - the restart strategy option. It can be either
|
||||
`:one_for_one`, `:rest_for_one`, `:one_for_all`, or
|
||||
`:simple_one_for_one`. You can learn more about strategies
|
||||
in the `Supervisor` module docs.
|
||||
|
||||
* `:max_restarts` - the maximum amount of restarts allowed in
|
||||
a time frame. Defaults to 5;
|
||||
* `:max_restarts` - the maximum amount of restarts allowed in
|
||||
a time frame. Defaults to 5.
|
||||
|
||||
* `:max_seconds` - the time frame in which max_restarts applies.
|
||||
Defaults to 5;
|
||||
* `:max_seconds` - the time frame in which `:max_restarts` applies.
|
||||
Defaults to 5.
|
||||
|
||||
The `:strategy` option is required and by default maximum 5 restarts
|
||||
are allowed in 5 seconds. Please check the `Supervisor` module for
|
||||
are allowed within 5 seconds. Please check the `Supervisor` module for
|
||||
a complete description of the available strategies.
|
||||
"""
|
||||
@spec supervise([spec], strategy: strategy,
|
||||
@@ -188,7 +189,7 @@ defmodule Supervisor.Spec do
|
||||
|
||||
worker ExUnit.Runner, [], restart: :permanent
|
||||
|
||||
By default, the function `:start_link` is invoked on the given
|
||||
By default, the function `start_link` is invoked on the given
|
||||
module. Overall, the default values for the options are:
|
||||
|
||||
[id: module,
|
||||
@@ -212,7 +213,7 @@ defmodule Supervisor.Spec do
|
||||
|
||||
supervisor ExUnit.Runner, [], restart: :permanent
|
||||
|
||||
By default, the function `:start_link` is invoked on the given
|
||||
By default, the function `start_link` is invoked on the given
|
||||
module. Overall, the default values for the options are:
|
||||
|
||||
[id: module,
|
||||
|
||||
+13
-13
@@ -60,9 +60,9 @@ defmodule System do
|
||||
|
||||
Returns a keyword list with Elixir version, git tag info and compilation date.
|
||||
"""
|
||||
@spec build_info() :: Keyword.t
|
||||
@spec build_info() :: map
|
||||
def build_info do
|
||||
[version: version, tag: get_describe, date: get_date]
|
||||
%{version: version, tag: get_describe, date: get_date}
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -153,11 +153,11 @@ defmodule System do
|
||||
Returns a writable temporary directory.
|
||||
Searches for directories in the following order:
|
||||
|
||||
1. The directory named by the TMPDIR environment variable
|
||||
2. The directory named by the TEMP environment variable
|
||||
3. The directory named by the TMP environment variable
|
||||
4. `C:\TMP` on Windows or `/tmp` on Unix
|
||||
5. As a last resort, the current working directory
|
||||
1. the directory named by the TMPDIR environment variable
|
||||
2. the directory named by the TEMP environment variable
|
||||
3. the directory named by the TMP environment variable
|
||||
4. `C:\TMP` on Windows or `/tmp` on Unix
|
||||
5. as a last resort, the current working directory
|
||||
|
||||
Returns `nil` if none of the above are writable.
|
||||
"""
|
||||
@@ -355,14 +355,14 @@ defmodule System do
|
||||
Halts the Erlang runtime system where the argument `status` must be a
|
||||
non-negative integer, the atom `:abort` or a binary.
|
||||
|
||||
* If an integer, the runtime system exits with the integer value which
|
||||
is returned to the operating system;
|
||||
* If an integer, the runtime system exits with the integer value which
|
||||
is returned to the operating system.
|
||||
|
||||
* If `:abort`, the runtime system aborts producing a core dump, if that is
|
||||
enabled in the operating system;
|
||||
* If `:abort`, the runtime system aborts producing a core dump, if that is
|
||||
enabled in the operating system.
|
||||
|
||||
* If a string, an erlang crash dump is produced with status as slogan,
|
||||
and then the runtime system exits with status code 1;
|
||||
* If a string, an erlang crash dump is produced with status as slogan,
|
||||
and then the runtime system exits with status code 1.
|
||||
|
||||
Note that on many platforms, only the status codes 0-255 are supported
|
||||
by the operating system.
|
||||
|
||||
+44
-51
@@ -2,42 +2,35 @@ defmodule Task do
|
||||
@moduledoc """
|
||||
Conveniences for spawning and awaiting for tasks.
|
||||
|
||||
Tasks are processes that meant to execute one particular
|
||||
action throughout their life-cycle, often with little
|
||||
explicit communication with other processes. The most common
|
||||
use case for tasks is to compute a value asynchronously:
|
||||
Tasks are processes meant to execute one particular
|
||||
action throughout their life-cycle, often with little or no
|
||||
communication with other processes. The most common use case
|
||||
for tasks is to compute a value asynchronously:
|
||||
|
||||
task = Task.async(fn -> do_some_work() end)
|
||||
res = do_some_other_work()
|
||||
res + Task.await(task)
|
||||
|
||||
Tasks spawned with async can be awaited on by its caller
|
||||
Tasks spawned with `async` can be awaited on by its caller
|
||||
process (and only its caller) as shown in the example above.
|
||||
They are implemented by spawning a process that sends a message
|
||||
to the caller once the given computation is performed.
|
||||
|
||||
Besides `async/1` and `await/1`, tasks can also be used as part
|
||||
of supervision trees and dynamically spawned in remote nodes.
|
||||
We will explore all three scenarios next.
|
||||
Besides `async/1` and `await/2`, tasks can also be
|
||||
started as part of supervision trees and dynamically spawned
|
||||
in remote nodes. We will explore all three scenarios next.
|
||||
|
||||
## async and await
|
||||
|
||||
The most common way to spawn a task is with `Task.async/1`. A new
|
||||
process will be created and this process is linked and monitored
|
||||
by the caller. However, the processes are unlinked right before
|
||||
the task finishes, allowing the proper error to be triggered only
|
||||
on `await/1`.
|
||||
process will be created, linked and monitored by the caller. Once
|
||||
the task action finishes, a message will be sent to the caller
|
||||
with the result.
|
||||
|
||||
This implies three things:
|
||||
|
||||
1) In case the caller crashes, the task will be killed and its
|
||||
computation will abort;
|
||||
|
||||
2) In case the task crashes due to an error, the parent will
|
||||
crash only on `await/1`;
|
||||
|
||||
3) In case the task crashes because a linked process caused
|
||||
it to crash, the parent will crash immediately;
|
||||
`Task.await/2` is used to read the message sent by the task. On
|
||||
`await`, Elixir will also setup a monitor to verify if the process
|
||||
exited for any abnormal reason (or in case exits are being
|
||||
trapped by the caller).
|
||||
|
||||
## Supervised tasks
|
||||
|
||||
@@ -46,7 +39,7 @@ defmodule Task do
|
||||
|
||||
Task.start_link(fn -> IO.puts "ok" end)
|
||||
|
||||
Such can be mounted in your supervision tree as:
|
||||
Such tasks can be mounted in your supervision tree as:
|
||||
|
||||
import Supervisor.Spec
|
||||
|
||||
@@ -54,15 +47,10 @@ defmodule Task do
|
||||
worker(Task, [fn -> IO.puts "ok" end])
|
||||
]
|
||||
|
||||
Since such tasks are supervised and not directly linked to
|
||||
the caller, they cannot be awaited on. For such reason,
|
||||
differently from `async/1`, `start_link/1` returns `{:ok, pid}`
|
||||
(which is the result expected by supervision trees).
|
||||
|
||||
Such tasks are useful as workers that run during your application
|
||||
life-cycle and rarely communicate with other workers. For example,
|
||||
a worker that pushes data to another server or a worker that consumes
|
||||
events from an event manager and writes it to a log file.
|
||||
Since these tasks are supervised and not directly linked to
|
||||
the caller, they cannot be awaited on. Note `start_link/1`,
|
||||
unlike `async/1`, returns `{:ok, pid}` (which is
|
||||
the result expected by supervision trees).
|
||||
|
||||
## Supervision trees
|
||||
|
||||
@@ -78,7 +66,7 @@ defmodule Task do
|
||||
# In the remote node
|
||||
Task.Supervisor.start_link(name: :tasks_sup)
|
||||
|
||||
# On the client
|
||||
# In the client
|
||||
Task.Supervisor.async({:tasks_sup, :remote@local}, fn -> do_work() end)
|
||||
|
||||
`Task.Supervisor` is more often started in your supervision tree as:
|
||||
@@ -97,10 +85,10 @@ defmodule Task do
|
||||
|
||||
It contains two fields:
|
||||
|
||||
* `:pid` - the proces reference of the task process. It may be a pid
|
||||
or a tuple containing the process and node names;
|
||||
* `:pid` - the process reference of the task process; it may be a pid
|
||||
or a tuple containing the process and node names
|
||||
|
||||
* `:ref` - the task monitor reference;
|
||||
* `:ref` - the task monitor reference
|
||||
|
||||
"""
|
||||
defstruct pid: nil, ref: nil
|
||||
@@ -118,20 +106,20 @@ defmodule Task do
|
||||
"""
|
||||
@spec start_link(module, atom, [term]) :: {:ok, pid}
|
||||
def start_link(mod, fun, args) do
|
||||
Task.Supervised.start_link(:undefined, {mod, fun, args})
|
||||
Task.Supervised.start_link(get_info(self), {mod, fun, args})
|
||||
end
|
||||
|
||||
@doc """
|
||||
Starts a task that can be awaited on.
|
||||
|
||||
This function spawns a process that is linked and monitored
|
||||
to the caller process. A `Task` struct is returned containing
|
||||
This function spawns a process that is linked to and monitored
|
||||
by the caller process. A `Task` struct is returned containing
|
||||
the relevant information.
|
||||
|
||||
## Task's message format
|
||||
|
||||
The reply sent by the task will be in the format `{ref, msg}`,
|
||||
where `ref` is the monitoring reference hold by the task.
|
||||
where `ref` is the monitoring reference held by the task.
|
||||
"""
|
||||
@spec async(fun) :: t
|
||||
def async(fun) do
|
||||
@@ -147,16 +135,24 @@ defmodule Task do
|
||||
@spec async(module, atom, [term]) :: t
|
||||
def async(mod, fun, args) do
|
||||
mfa = {mod, fun, args}
|
||||
pid = :proc_lib.spawn_link(Task.Supervised, :async, [self(), mfa])
|
||||
pid = :proc_lib.spawn_link(Task.Supervised, :async, [self, get_info(self), mfa])
|
||||
ref = Process.monitor(pid)
|
||||
send(pid, {self(), ref})
|
||||
%Task{pid: pid, ref: ref}
|
||||
end
|
||||
|
||||
defp get_info(self) do
|
||||
{node(),
|
||||
case Process.info(self, :registered_name) do
|
||||
{:registered_name, []} -> self()
|
||||
{:registered_name, name} -> name
|
||||
end}
|
||||
end
|
||||
|
||||
@doc """
|
||||
Awaits for a task reply.
|
||||
|
||||
A timeout, in miliseconds, can be given with default value
|
||||
A timeout, in milliseconds, can be given with default value
|
||||
of `5000`. In case the task process dies, this function will
|
||||
exit with the same reason as the task.
|
||||
"""
|
||||
@@ -168,7 +164,7 @@ defmodule Task do
|
||||
reply
|
||||
{:DOWN, ^ref, _, _, :noconnection} ->
|
||||
mfa = {__MODULE__, :await, [task, timeout]}
|
||||
exit({{:nodedown, get_node(task.pid)}, mfa})
|
||||
exit({{:nodedown, node(task.pid)}, mfa})
|
||||
{:DOWN, ^ref, _, _, reason} ->
|
||||
exit({reason, {__MODULE__, :await, [task, timeout]}})
|
||||
after
|
||||
@@ -185,13 +181,13 @@ defmodule Task do
|
||||
This function returns a tuple with the task and the
|
||||
returned value in case the message matches a task that
|
||||
exited with success, it raises in case the found task
|
||||
failed or nil if no task was found.
|
||||
failed or `nil` if no task was found.
|
||||
|
||||
This function is useful in situations where multiple
|
||||
tasks are spawned and their results are collected just
|
||||
later on. For example, a GenServer can spawn tasks,
|
||||
tasks are spawned and their results are collected
|
||||
later on. For example, a `GenServer` can spawn tasks,
|
||||
store the tasks in a list and later use `Task.find/2`
|
||||
to see if upcoming messages are from any of the tasks.
|
||||
to see if incoming messages are from any of the tasks.
|
||||
"""
|
||||
@spec find([t], any) :: {term, t} | nil | no_return
|
||||
def find(tasks, msg)
|
||||
@@ -210,7 +206,7 @@ defmodule Task do
|
||||
find = fn(%Task{ref: task_ref}) -> task_ref == ref end
|
||||
case Enum.find(tasks, find) do
|
||||
%Task{pid: pid} when reason == :noconnection ->
|
||||
exit({{:nodedown, get_node(pid)}, {__MODULE__, :find, [tasks, msg]}})
|
||||
exit({{:nodedown, node(pid)}, {__MODULE__, :find, [tasks, msg]}})
|
||||
%Task{} ->
|
||||
exit({reason, {__MODULE__, :find, [tasks, msg]}})
|
||||
nil ->
|
||||
@@ -221,7 +217,4 @@ defmodule Task do
|
||||
def find(_tasks, _msg) do
|
||||
nil
|
||||
end
|
||||
|
||||
defp get_node({_, n}) when is_atom(n), do: n
|
||||
defp get_node(pid) when is_pid(pid), do: pid
|
||||
end
|
||||
|
||||
@@ -1,15 +1,25 @@
|
||||
defmodule Task.Supervised do
|
||||
@moduledoc false
|
||||
|
||||
def start_link(:undefined, fun) do
|
||||
:proc_lib.start_link(__MODULE__, :noreply, [fun])
|
||||
def start_link(info, fun) do
|
||||
{:ok, :proc_lib.spawn_link(__MODULE__, :noreply, [info, fun])}
|
||||
end
|
||||
|
||||
def start_link(caller, fun) do
|
||||
:proc_lib.start_link(__MODULE__, :reply, [caller, fun])
|
||||
def start_link(caller, info, fun) do
|
||||
:proc_lib.start_link(__MODULE__, :reply, [caller, info, fun])
|
||||
end
|
||||
|
||||
def async(caller, {module, fun, args}) do
|
||||
def async(caller, info, mfa) do
|
||||
initial_call(mfa)
|
||||
ref = receive do: ({^caller, ref} -> ref)
|
||||
send caller, {ref, do_apply(info, mfa)}
|
||||
end
|
||||
|
||||
def reply(caller, info, mfa) do
|
||||
initial_call(mfa)
|
||||
:erlang.link(caller)
|
||||
:proc_lib.init_ack({:ok, self()})
|
||||
|
||||
ref =
|
||||
# There is a race condition on this operation when working accross
|
||||
# node that manifests if a `Task.Supervisor.async/1` call is made
|
||||
@@ -33,36 +43,65 @@ defmodule Task.Supervised do
|
||||
5000 -> exit(:timeout)
|
||||
end
|
||||
|
||||
try do
|
||||
apply(module, fun, args)
|
||||
else
|
||||
result ->
|
||||
send caller, {ref, result}
|
||||
catch
|
||||
:error, reason ->
|
||||
exit({reason, System.stacktrace()})
|
||||
:throw, value ->
|
||||
exit({{:nocatch, value}, System.stacktrace()})
|
||||
after
|
||||
:erlang.unlink(caller)
|
||||
end
|
||||
send caller, {ref, do_apply(info, mfa)}
|
||||
end
|
||||
|
||||
def reply(caller, mfa) do
|
||||
:erlang.link(caller)
|
||||
:proc_lib.init_ack({:ok, self()})
|
||||
async(caller, mfa)
|
||||
def noreply(info, mfa) do
|
||||
initial_call(mfa)
|
||||
do_apply(info, mfa)
|
||||
end
|
||||
|
||||
def noreply({module, fun, args}) do
|
||||
:proc_lib.init_ack({:ok, self()})
|
||||
defp initial_call(mfa) do
|
||||
Process.put(:"$initial_call", get_initial_call(mfa))
|
||||
end
|
||||
|
||||
defp get_initial_call({:erlang, :apply, [fun, []]}) when is_function(fun, 0) do
|
||||
{:module, module} = :erlang.fun_info(fun, :module)
|
||||
{:name, name} = :erlang.fun_info(fun, :name)
|
||||
{module, name, 0}
|
||||
end
|
||||
|
||||
defp get_initial_call({mod, fun, args}) do
|
||||
{mod, fun, length(args)}
|
||||
end
|
||||
|
||||
defp do_apply(info, {module, fun, args} = mfa) do
|
||||
try do
|
||||
apply(module, fun, args)
|
||||
catch
|
||||
:error, reason ->
|
||||
exit({reason, System.stacktrace()})
|
||||
:error, value ->
|
||||
exit(info, mfa, {value, System.stacktrace()})
|
||||
:throw, value ->
|
||||
exit({{:nocatch, value}, System.stacktrace()})
|
||||
exit(info, mfa, {{:nocatch, value}, System.stacktrace()})
|
||||
:exit, value ->
|
||||
exit(info, mfa, value)
|
||||
end
|
||||
end
|
||||
|
||||
defp exit(_info, _mfa, reason)
|
||||
when reason == :normal
|
||||
when reason == :shutdown
|
||||
when tuple_size(reason) == 2 and elem(reason, 0) == :shutdown do
|
||||
exit(reason)
|
||||
end
|
||||
|
||||
defp exit(info, mfa, reason) do
|
||||
{fun, args} = get_running(mfa)
|
||||
|
||||
:error_logger.format(
|
||||
"** Task ~p terminating~n" <>
|
||||
"** Started from ~p~n" <>
|
||||
"** When function == ~p~n" <>
|
||||
"** arguments == ~p~n" <>
|
||||
"** Reason for termination == ~n" <>
|
||||
"** ~p~n", [self, get_from(info), fun, args, reason])
|
||||
|
||||
exit(reason)
|
||||
end
|
||||
|
||||
defp get_from({node, pid_or_name}) when node == node(), do: pid_or_name
|
||||
defp get_from(other), do: other
|
||||
|
||||
defp get_running({:erlang, :apply, [fun, []]}) when is_function(fun, 0), do: {fun, []}
|
||||
defp get_running({mod, fun, args}), do: {:erlang.make_fun(mod, fun, length(args)), args}
|
||||
end
|
||||
|
||||
@@ -7,12 +7,12 @@ defmodule Task.Supervisor do
|
||||
`:simple_one_for_one` supervisor where the workers are temporary
|
||||
(i.e. they are not restarted after they die).
|
||||
|
||||
The functions in this module allow tasks can be spawned and awaited
|
||||
The functions in this module allow tasks to be spawned and awaited
|
||||
from a supervisor, similar to the functions defined in the `Task` module.
|
||||
|
||||
## Name registering
|
||||
## Name Registration
|
||||
|
||||
A `Task.Supervisor` is bound to the same name registering rules as a
|
||||
A `Task.Supervisor` is bound to the same name registration rules as a
|
||||
`GenServer`. Read more about it in the `GenServer` docs.
|
||||
"""
|
||||
|
||||
@@ -22,11 +22,11 @@ defmodule Task.Supervisor do
|
||||
The supported options are:
|
||||
|
||||
* `:name` - used to register a supervisor name, the supported values are
|
||||
described under the `Name Registering` section in the `GenServer` module
|
||||
described under the `Name Registration` section in the `GenServer` module
|
||||
docs;
|
||||
|
||||
* `:shutdown` - `:brutal_kill` if the tasks must be killed directly on shutdown
|
||||
or an integer indicating the timeout value, defaults to 5000 miliseconds;
|
||||
or an integer indicating the timeout value, defaults to 5000 milliseconds;
|
||||
"""
|
||||
@spec start_link(Supervisor.options) :: Supervisor.on_start
|
||||
def start_link(opts \\ []) do
|
||||
@@ -55,14 +55,15 @@ defmodule Task.Supervisor do
|
||||
"""
|
||||
@spec async(Supervisor.supervisor, module, atom, [term]) :: Task.t
|
||||
def async(supervisor, module, fun, args) do
|
||||
{:ok, pid} = Supervisor.start_child(supervisor, [self(), {module, fun, args}])
|
||||
args = [self, get_info(self), {module, fun, args}]
|
||||
{:ok, pid} = Supervisor.start_child(supervisor, args)
|
||||
ref = Process.monitor(pid)
|
||||
send pid, {self(), ref}
|
||||
%Task{pid: pid, ref: ref}
|
||||
end
|
||||
|
||||
@doc """
|
||||
Terminates the given child at pid.
|
||||
Terminates the child with the given `pid`.
|
||||
"""
|
||||
@spec terminate_child(Supervisor.supervisor, pid) :: :ok
|
||||
def terminate_child(supervisor, pid) when is_pid(pid) do
|
||||
@@ -80,9 +81,9 @@ defmodule Task.Supervisor do
|
||||
@doc """
|
||||
Starts a task as child of the given `supervisor`.
|
||||
|
||||
Note the spawned process is not linked to the caller but
|
||||
Note that the spawned process is not linked to the caller, but
|
||||
only to the supervisor. This command is useful in case the
|
||||
task needs to emit side-effects (like I/O) and does not need
|
||||
task needs to perform side-effects (like I/O) and does not need
|
||||
to report back to the caller.
|
||||
"""
|
||||
@spec start_child(Supervisor.supervisor, fun) :: {:ok, pid}
|
||||
@@ -98,6 +99,14 @@ defmodule Task.Supervisor do
|
||||
"""
|
||||
@spec start_child(Supervisor.supervisor, module, atom, [term]) :: {:ok, pid}
|
||||
def start_child(supervisor, module, fun, args) do
|
||||
Supervisor.start_child(supervisor, [:undefined, {module, fun, args}])
|
||||
Supervisor.start_child(supervisor, [get_info(self), {module, fun, args}])
|
||||
end
|
||||
|
||||
defp get_info(self) do
|
||||
{node(),
|
||||
case Process.info(self, :registered_name) do
|
||||
{:registered_name, []} -> self()
|
||||
{:registered_name, name} -> name
|
||||
end}
|
||||
end
|
||||
end
|
||||
|
||||
+101
-34
@@ -126,8 +126,10 @@ defmodule URI do
|
||||
|
||||
current =
|
||||
case :binary.split(first, "=") do
|
||||
[ key, value ] -> {decode(key), decode(value)}
|
||||
[ key ] -> {decode(key), nil}
|
||||
[key, value] ->
|
||||
{decode_www_form(key), decode_www_form(value)}
|
||||
[key] ->
|
||||
{decode_www_form(key), nil}
|
||||
end
|
||||
|
||||
{current, next}
|
||||
@@ -142,33 +144,80 @@ defmodule URI do
|
||||
end
|
||||
|
||||
defp pair({k, v}) do
|
||||
encode(to_string(k)) <> "=" <> encode(to_string(v))
|
||||
encode_www_form(to_string(k)) <>
|
||||
"=" <> encode_www_form(to_string(v))
|
||||
end
|
||||
|
||||
@doc """
|
||||
Checks if the character is a "reserved" character in a URI.
|
||||
|
||||
Reserved characters are specified in RFC3986, section 2.2.
|
||||
"""
|
||||
def char_reserved?(c) do
|
||||
c in ':/?#[]@!$&\'()*+,;='
|
||||
end
|
||||
|
||||
@doc """
|
||||
Checks if the character is a "unreserved" character in a URI.
|
||||
|
||||
Unreserved characters are specified in RFC3986, section 2.3.
|
||||
"""
|
||||
def char_unreserved?(c) do
|
||||
c in ?0..?9 or
|
||||
c in ?a..?z or
|
||||
c in ?A..?Z or
|
||||
c in '~_-.'
|
||||
end
|
||||
|
||||
@doc """
|
||||
Checks if the character is allowed unescaped in a URI.
|
||||
|
||||
This is the default used by `URI.encode/2` where both
|
||||
reserved and unreserved characters are kept unescaped.
|
||||
"""
|
||||
def char_unescaped?(c) do
|
||||
char_reserved?(c) or char_unreserved?(c)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Percent-escape a URI.
|
||||
Accepts `predicate` function as an argument to specify if char can be left as is.
|
||||
|
||||
## Example
|
||||
|
||||
iex> URI.encode("http://elixir-lang.org/getting_started/2.html")
|
||||
"http%3A%2F%2Felixir-lang.org%2Fgetting_started%2F2.html"
|
||||
iex> URI.encode("ftp://s-ite.tld/?value=put it+й")
|
||||
"ftp://s-ite.tld/?value=put%20it+%D0%B9"
|
||||
|
||||
"""
|
||||
def encode(s), do: for(<<c <- s>>, into: "", do: percent(c))
|
||||
|
||||
defp percent(32), do: <<?+>>
|
||||
defp percent(?-), do: <<?->>
|
||||
defp percent(?_), do: <<?_>>
|
||||
defp percent(?.), do: <<?.>>
|
||||
|
||||
defp percent(c)
|
||||
when c >= ?0 and c <= ?9
|
||||
when c >= ?a and c <= ?z
|
||||
when c >= ?A and c <= ?Z do
|
||||
<<c>>
|
||||
def encode(str, predicate \\ &char_unescaped?/1) when is_binary(str) do
|
||||
for <<c <- str>>, into: "", do: percent(c, predicate)
|
||||
end
|
||||
|
||||
defp percent(c), do: "%" <> hex(bsr(c, 4)) <> hex(band(c, 15))
|
||||
@doc """
|
||||
Encode a string as "x-www-urlencoded".
|
||||
|
||||
## Example
|
||||
|
||||
iex> URI.encode_www_form("put: it+й")
|
||||
"put%3A+it%2B%D0%B9"
|
||||
|
||||
"""
|
||||
def encode_www_form(str) when is_binary(str) do
|
||||
for <<c <- str>>, into: "" do
|
||||
case percent(c, &char_unreserved?/1) do
|
||||
"%20" -> "+"
|
||||
pct -> pct
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defp percent(c, predicate) do
|
||||
if predicate.(c) do
|
||||
<<c>>
|
||||
else
|
||||
"%" <> hex(bsr(c, 4)) <> hex(band(c, 15))
|
||||
end
|
||||
end
|
||||
|
||||
defp hex(n) when n <= 9, do: <<n + ?0>>
|
||||
defp hex(n), do: <<n + ?A - 10>>
|
||||
@@ -183,28 +232,44 @@ defmodule URI do
|
||||
|
||||
"""
|
||||
def decode(uri) do
|
||||
decode(uri, uri)
|
||||
unpercent(uri)
|
||||
catch
|
||||
:malformed_uri ->
|
||||
raise ArgumentError, "malformed URI #{inspect uri}"
|
||||
end
|
||||
|
||||
def decode(<<?%, hex1, hex2, tail :: binary >>, uri) do
|
||||
<< bsl(hex2dec(hex1, uri), 4) + hex2dec(hex2, uri) >> <> decode(tail, uri)
|
||||
@doc """
|
||||
Decode a string as "x-www-urlencoded".
|
||||
|
||||
## Examples
|
||||
|
||||
iex> URI.decode_www_form("%3Call+in%2F")
|
||||
"<all in/"
|
||||
|
||||
"""
|
||||
def decode_www_form(str) do
|
||||
String.split(str, "+") |> Enum.map_join(" ", &unpercent/1)
|
||||
catch
|
||||
:malformed_uri ->
|
||||
raise ArgumentError, "malformed URI #{inspect str}"
|
||||
end
|
||||
|
||||
def decode(<<head, tail :: binary >>, uri) do
|
||||
<<check_plus(head)>> <> decode(tail, uri)
|
||||
defp unpercent(<<?%, hex_1, hex_2, tail :: binary>>) do
|
||||
<<bsl(hex_to_dec(hex_1), 4) + hex_to_dec(hex_2)>> <> unpercent(tail)
|
||||
end
|
||||
defp unpercent(<<?%, _>>), do: throw(:malformed_uri)
|
||||
defp unpercent(<<?%>>), do: throw(:malformed_uri)
|
||||
|
||||
defp unpercent(<<head, tail :: binary>>) do
|
||||
<<head>> <> unpercent(tail)
|
||||
end
|
||||
|
||||
def decode(<<>>, _uri), do: <<>>
|
||||
defp unpercent(<<>>), do: <<>>
|
||||
|
||||
defp hex2dec(n, _uri) when n in ?A..?F, do: n - ?A + 10
|
||||
defp hex2dec(n, _uri) when n in ?a..?f, do: n - ?a + 10
|
||||
defp hex2dec(n, _uri) when n in ?0..?9, do: n - ?0
|
||||
defp hex2dec(_n, uri) do
|
||||
raise ArgumentError, "malformed URI #{inspect uri}"
|
||||
end
|
||||
|
||||
defp check_plus(?+), do: 32
|
||||
defp check_plus(c), do: c
|
||||
defp hex_to_dec(n) when n in ?A..?F, do: n - ?A + 10
|
||||
defp hex_to_dec(n) when n in ?a..?f, do: n - ?a + 10
|
||||
defp hex_to_dec(n) when n in ?0..?9, do: n - ?0
|
||||
defp hex_to_dec(_n), do: throw(:malformed_uri)
|
||||
|
||||
@doc """
|
||||
Parses a URI into components.
|
||||
@@ -222,6 +287,8 @@ defmodule URI do
|
||||
host: "elixir-lang.org", port: 80}
|
||||
|
||||
"""
|
||||
def parse(%URI{} = uri), do: uri
|
||||
|
||||
def parse(s) when is_binary(s) do
|
||||
# From http://tools.ietf.org/html/rfc3986#appendix-B
|
||||
regex = ~r/^(([^:\/?#]+):)?(\/\/([^\/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?/
|
||||
@@ -267,7 +334,7 @@ defmodule URI do
|
||||
# to replace those with nil for consistency.
|
||||
defp nillify(l) do
|
||||
for s <- l do
|
||||
if size(s) > 0, do: s, else: nil
|
||||
if byte_size(s) > 0, do: s, else: nil
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -68,8 +68,13 @@ defmodule Version do
|
||||
defstruct [:source, :matchspec]
|
||||
end
|
||||
|
||||
defexception InvalidRequirementError, [:message]
|
||||
defexception InvalidVersionError, [:message]
|
||||
defmodule InvalidRequirementError do
|
||||
defexception [:message]
|
||||
end
|
||||
|
||||
defmodule InvalidVersionError do
|
||||
defexception [:message]
|
||||
end
|
||||
|
||||
@doc """
|
||||
Check if the given version matches the specification.
|
||||
|
||||
+30
-23
@@ -18,21 +18,28 @@
|
||||
-export([start/2, stop/1, config_change/3]).
|
||||
|
||||
start(_Type, _Args) ->
|
||||
%% Set the shell to unicode so printing inside scripts work
|
||||
%% Those can take a while, so let's do it in a new process
|
||||
spawn(fun() ->
|
||||
io:setopts(standard_io, [binary,{encoding,utf8}]),
|
||||
io:setopts(standard_error, [{unicode,true}]),
|
||||
case file:native_name_encoding() of
|
||||
latin1 ->
|
||||
io:format(standard_error,
|
||||
"warning: the VM is running with native name encoding of latin1 which may cause "
|
||||
"Elixir to malfunction as it expects utf8. Please ensure your locale is set to UTF-8 "
|
||||
"(which can be verified by running \"locale\" in your shell)~n", []);
|
||||
_ ->
|
||||
ok
|
||||
end
|
||||
end),
|
||||
%% In case there is a shell, we can't really change its
|
||||
%% encoding, so we just set binary to true. Otherwise
|
||||
%% we must set the encoding as the user with no shell
|
||||
%% has encoding set to latin1.
|
||||
Opts =
|
||||
case init:get_argument(noshell) of
|
||||
{ok, _} -> [binary,{encoding,utf8}];
|
||||
error -> [binary]
|
||||
end,
|
||||
|
||||
io:setopts(standard_io, Opts),
|
||||
io:setopts(standard_error, [{unicode,true}]),
|
||||
case file:native_name_encoding() of
|
||||
latin1 ->
|
||||
io:format(standard_error,
|
||||
"warning: the VM is running with native name encoding of latin1 which may cause "
|
||||
"Elixir to malfunction as it expects utf8. Please ensure your locale is set to UTF-8 "
|
||||
"(which can be verified by running \"locale\" in your shell)~n", []);
|
||||
_ ->
|
||||
ok
|
||||
end,
|
||||
|
||||
elixir_sup:start_link().
|
||||
|
||||
stop(_S) ->
|
||||
@@ -65,37 +72,37 @@ env_for_eval(Opts) ->
|
||||
|
||||
env_for_eval(Env, Opts) ->
|
||||
Line = case lists:keyfind(line, 1, Opts) of
|
||||
{line, RawLine} when is_integer(RawLine) -> RawLine;
|
||||
{line, LineOpt} when is_integer(LineOpt) -> LineOpt;
|
||||
false -> ?m(Env, line)
|
||||
end,
|
||||
|
||||
File = case lists:keyfind(file, 1, Opts) of
|
||||
{file, RawFile} when is_binary(RawFile) -> RawFile;
|
||||
{file, FileOpt} when is_binary(FileOpt) -> FileOpt;
|
||||
false -> ?m(Env, file)
|
||||
end,
|
||||
|
||||
Local = case lists:keyfind(delegate_locals_to, 1, Opts) of
|
||||
{delegate_locals_to, LocalOpt} -> LocalOpt;
|
||||
{delegate_locals_to, LocalOpt} when is_atom(LocalOpt) -> LocalOpt;
|
||||
false -> ?m(Env, local)
|
||||
end,
|
||||
|
||||
Aliases = case lists:keyfind(aliases, 1, Opts) of
|
||||
{aliases, AliasesOpt} -> AliasesOpt;
|
||||
{aliases, AliasesOpt} when is_list(AliasesOpt) -> AliasesOpt;
|
||||
false -> ?m(Env, aliases)
|
||||
end,
|
||||
|
||||
Requires = case lists:keyfind(requires, 1, Opts) of
|
||||
{requires, List} -> ordsets:from_list(List);
|
||||
{requires, RequiresOpt} when is_list(RequiresOpt) -> ordsets:from_list(RequiresOpt);
|
||||
false -> ?m(Env, requires)
|
||||
end,
|
||||
|
||||
Functions = case lists:keyfind(functions, 1, Opts) of
|
||||
{functions, FunctionsOpt} -> FunctionsOpt;
|
||||
{functions, FunctionsOpt} when is_list(FunctionsOpt) -> FunctionsOpt;
|
||||
false -> ?m(Env, functions)
|
||||
end,
|
||||
|
||||
Macros = case lists:keyfind(macros, 1, Opts) of
|
||||
{macros, MacrosOpt} -> MacrosOpt;
|
||||
{macros, MacrosOpt} when is_list(MacrosOpt) -> MacrosOpt;
|
||||
false -> ?m(Env, macros)
|
||||
end,
|
||||
|
||||
@@ -108,7 +115,7 @@ env_for_eval(Env, Opts) ->
|
||||
file := File, local := Local, module := Module,
|
||||
macros := Macros, functions := Functions,
|
||||
requires := Requires, aliases := Aliases, line := Line
|
||||
}.
|
||||
}.
|
||||
|
||||
%% String evaluation
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
inspect(Atom) when is_atom(Atom) ->
|
||||
case elixir_compiler:get_opt(internal) of
|
||||
true -> atom_to_binary(Atom, utf8);
|
||||
true -> atom_to_binary(Atom, utf8);
|
||||
false -> 'Elixir.Inspect.Atom':inspect(Atom)
|
||||
end.
|
||||
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
%% Handle code related to args, guard and -> matching for case,
|
||||
%% fn, receive and friends. try is handled in elixir_try.
|
||||
-module(elixir_clauses).
|
||||
-export([match/3, clause/7, clauses/4, guards/4, get_pairs/2, get_pairs/3,
|
||||
-export([match/3, clause/7, clauses/4, guards/4, get_pairs/3, get_pairs/4,
|
||||
extract_splat_guards/1, extract_guards/1]).
|
||||
-include("elixir.hrl").
|
||||
|
||||
%% Get pairs from a clause.
|
||||
|
||||
get_pairs(Key, Clauses) ->
|
||||
get_pairs(Key, Clauses, false).
|
||||
get_pairs(Key, Clauses, AllowNil) ->
|
||||
get_pairs(Key, Clauses, As) ->
|
||||
get_pairs(Key, Clauses, As, false).
|
||||
get_pairs(Key, Clauses, As, AllowNil) ->
|
||||
case lists:keyfind(Key, 1, Clauses) of
|
||||
{Key, Pairs} when is_list(Pairs) ->
|
||||
[{Key, Meta, Left, Right} || {'->', Meta, [Left, Right]} <- Pairs];
|
||||
[{As, Meta, Left, Right} || {'->', Meta, [Left, Right]} <- Pairs];
|
||||
{Key, nil} when AllowNil ->
|
||||
[];
|
||||
false ->
|
||||
@@ -79,9 +79,8 @@ do_clauses(Meta, DecoupledClauses, Return, S) ->
|
||||
% Transform tree just passing the variables counter forward
|
||||
% and storing variables defined inside each clause.
|
||||
Transformer = fun(X, {SAcc, VAcc}) ->
|
||||
{TX, TS} = each_clause(X, Return, SAcc),
|
||||
{TX,
|
||||
{elixir_scope:mergec(S, TS), [TS#elixir_scope.export_vars|VAcc]}}
|
||||
{TX, TS} = each_clause(Meta, X, Return, SAcc),
|
||||
{TX, {elixir_scope:mergec(S, TS), [TS#elixir_scope.export_vars|VAcc]}}
|
||||
end,
|
||||
|
||||
{TClauses, {TS, ReverseCV}} =
|
||||
@@ -140,19 +139,27 @@ expand_clauses(_Line, [], [], _FinalVars, Acc, S) ->
|
||||
|
||||
% Handle each key/value clause pair and translate them accordingly.
|
||||
|
||||
each_clause({do, Meta, [Condition], Expr}, Return, S) ->
|
||||
each_clause(Export, {match, Meta, [Condition], Expr}, Return, S) ->
|
||||
Fun = wrap_export_fun(Export, fun elixir_translator:translate_args/2),
|
||||
{Arg, Guards} = extract_guards(Condition),
|
||||
clause(?line(Meta), fun elixir_translator:translate_args/2, [Arg], Expr, Guards, Return, S);
|
||||
clause(?line(Meta), Fun, [Arg], Expr, Guards, Return, S);
|
||||
|
||||
each_clause({else, Meta, [Condition], Expr}, Return, S) ->
|
||||
{Arg, Guards} = extract_guards(Condition),
|
||||
clause(?line(Meta), fun elixir_translator:translate_args/2, [Arg], Expr, Guards, Return, S);
|
||||
|
||||
each_clause({'after', Meta, [Condition], Expr}, Return, S) ->
|
||||
{TCondition, SC} = elixir_translator:translate(Condition, S),
|
||||
each_clause(Export, {expr, Meta, [Condition], Expr}, Return, S) ->
|
||||
{TCondition, SC} = (wrap_export_fun(Export, fun elixir_translator:translate/2))(Condition, S),
|
||||
{TExpr, SB} = elixir_translator:translate_block(Expr, Return, SC),
|
||||
{{clause, ?line(Meta), [TCondition], [], unblock(TExpr)}, SB}.
|
||||
|
||||
wrap_export_fun(Meta, Fun) ->
|
||||
case lists:keyfind(export_head, 1, Meta) of
|
||||
{export_head, true} ->
|
||||
Fun;
|
||||
_ ->
|
||||
fun(Args, S) ->
|
||||
{TArgs, TS} = Fun(Args, S),
|
||||
{TArgs, TS#elixir_scope{export_vars = S#elixir_scope.export_vars}}
|
||||
end
|
||||
end.
|
||||
|
||||
% Check if the given expression is a match tuple.
|
||||
% This is a small optimization to allow us to change
|
||||
% existing assignments instead of creating new ones every time.
|
||||
@@ -181,7 +188,7 @@ normalize_vars(Key, Value, #elixir_scope{vars=Vars,export_vars=ClauseVars} = S)
|
||||
VS = S#elixir_scope{
|
||||
vars=orddict:store(Key, Value, Vars),
|
||||
export_vars=orddict:store(Key, Value, ClauseVars)
|
||||
},
|
||||
},
|
||||
|
||||
Expr = case orddict:find(Key, Vars) of
|
||||
{ok, {PreValue, _}} -> {var, 0, PreValue};
|
||||
|
||||
@@ -180,8 +180,9 @@ core_main() ->
|
||||
<<"lib/elixir/lib/list.ex">>,
|
||||
<<"lib/elixir/lib/macro.ex">>,
|
||||
<<"lib/elixir/lib/code.ex">>,
|
||||
<<"lib/elixir/lib/exception.ex">>,
|
||||
<<"lib/elixir/lib/module/locals_tracker.ex">>,
|
||||
<<"lib/elixir/lib/kernel/typespec.ex">>,
|
||||
<<"lib/elixir/lib/exception.ex">>,
|
||||
<<"lib/elixir/lib/protocol.ex">>,
|
||||
<<"lib/elixir/lib/stream/reducers.ex">>,
|
||||
<<"lib/elixir/lib/enum.ex">>,
|
||||
@@ -193,8 +194,8 @@ core_main() ->
|
||||
<<"lib/elixir/lib/string/chars.ex">>,
|
||||
<<"lib/elixir/lib/io.ex">>,
|
||||
<<"lib/elixir/lib/path.ex">>,
|
||||
<<"lib/elixir/lib/file.ex">>,
|
||||
<<"lib/elixir/lib/system.ex">>,
|
||||
<<"lib/elixir/lib/kernel/typespec.ex">>,
|
||||
<<"lib/elixir/lib/kernel/cli.ex">>,
|
||||
<<"lib/elixir/lib/kernel/error_handler.ex">>,
|
||||
<<"lib/elixir/lib/kernel/parallel_compiler.ex">>,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
% Holds the logic responsible for functions definition (def(p) and defmacro(p)).
|
||||
% Holds the logic responsible for function definitions (def(p) and defmacro(p)).
|
||||
-module(elixir_def).
|
||||
-export([table/1, clauses_table/1, setup/1,
|
||||
cleanup/1, reset_last/1, lookup_definition/2,
|
||||
@@ -129,20 +129,28 @@ make_struct_available(_, _, _, _) ->
|
||||
%% Retrieve location from meta file or @file, otherwise nil
|
||||
|
||||
retrieve_location(Line, File, Module) ->
|
||||
case not(elixir_compiler:get_opt(internal)) andalso
|
||||
'Elixir.Module':get_attribute(Module, file) of
|
||||
X when X == nil; X == false ->
|
||||
if
|
||||
is_binary(File) ->
|
||||
{elixir_utils:characters_to_list(elixir_utils:relative_to_cwd(File)), Line};
|
||||
true ->
|
||||
nil
|
||||
end;
|
||||
case get_location_attribute(Module) of
|
||||
nil when not is_binary(File) ->
|
||||
nil;
|
||||
nil ->
|
||||
{normalize_location(File), Line};
|
||||
X when is_binary(X) ->
|
||||
'Elixir.Module':delete_attribute(Module, file),
|
||||
{elixir_utils:characters_to_list(X), 0}
|
||||
{normalize_location(X), 0};
|
||||
{X, L} when is_binary(X) andalso is_integer(L) ->
|
||||
'Elixir.Module':delete_attribute(Module, file),
|
||||
{normalize_location(X), L}
|
||||
end.
|
||||
|
||||
get_location_attribute(Module) ->
|
||||
case elixir_compiler:get_opt(internal) of
|
||||
true -> nil;
|
||||
false -> 'Elixir.Module':get_attribute(Module, file)
|
||||
end.
|
||||
|
||||
normalize_location(X) ->
|
||||
elixir_utils:characters_to_list(elixir_utils:relative_to_cwd(X)).
|
||||
|
||||
%% Compile super
|
||||
|
||||
compile_super(Module, true, #{function := Function}) ->
|
||||
@@ -158,16 +166,10 @@ translate_definition(Kind, Line, Module, Name, Args, Guards, Body, E) when is_in
|
||||
{EArgs, EGuards, EBody, _} = elixir_exp_clauses:def(fun elixir_def_defaults:expand/2,
|
||||
Args, Guards, expr_from_body(Line, Body), E),
|
||||
|
||||
%% Macros receive a special argument on invocation. Notice it does
|
||||
%% not affect the arity of the stored function, but the clause
|
||||
%% already contains it.
|
||||
EAllArgs = case is_macro(Kind) of
|
||||
true -> [{'_@CALLER', [{line,Line}], nil}|EArgs];
|
||||
false -> EArgs
|
||||
end,
|
||||
Body == nil andalso check_args_for_bodyless_clause(Line, EArgs, E),
|
||||
|
||||
S = elixir_env:env_to_scope(E),
|
||||
{Unpacked, Defaults} = elixir_def_defaults:unpack(Kind, Name, EAllArgs, S),
|
||||
{Unpacked, Defaults} = elixir_def_defaults:unpack(Kind, Name, EArgs, S),
|
||||
{Clauses, Super} = translate_clause(Body, Line, Kind, Unpacked, EGuards, EBody, S),
|
||||
|
||||
run_on_definition_callbacks(Kind, Line, Module, Name, EArgs, EGuards, EBody, E),
|
||||
@@ -182,22 +184,30 @@ translate_clause(_, Line, Kind, Args, Guards, Body, S) ->
|
||||
{TClause, TS} = elixir_clauses:clause(Line,
|
||||
fun elixir_translator:translate_args/2, Args, Body, Guards, true, S),
|
||||
|
||||
%% Set __CALLER__ if used
|
||||
FClause = case is_macro(Kind) andalso TS#elixir_scope.caller of
|
||||
true ->
|
||||
FBody = {'match', Line,
|
||||
{'var', Line, '__CALLER__'},
|
||||
elixir_utils:erl_call(Line, elixir_env, linify, [{var, Line, '_@CALLER'}])
|
||||
},
|
||||
setelement(5, TClause, [FBody|element(5, TClause)]);
|
||||
false -> TClause
|
||||
FClause = case is_macro(Kind) of
|
||||
true ->
|
||||
FArgs = {var, Line, '_@CALLER'},
|
||||
MClause = setelement(3, TClause, [FArgs|element(3, TClause)]),
|
||||
|
||||
case TS#elixir_scope.caller of
|
||||
true ->
|
||||
FBody = {'match', Line,
|
||||
{'var', Line, '__CALLER__'},
|
||||
elixir_utils:erl_call(Line, elixir_env, linify, [{var, Line, '_@CALLER'}])
|
||||
},
|
||||
setelement(5, MClause, [FBody|element(5, TClause)]);
|
||||
false ->
|
||||
MClause
|
||||
end;
|
||||
false ->
|
||||
TClause
|
||||
end,
|
||||
|
||||
{[FClause], TS#elixir_scope.super}.
|
||||
|
||||
expr_from_body(_Line, nil) -> nil;
|
||||
expr_from_body(_Line, nil) -> nil;
|
||||
expr_from_body(_Line, [{do, Expr}]) -> Expr;
|
||||
expr_from_body(Line, Else) -> {'try', [{line,Line}], [Else]}.
|
||||
expr_from_body(Line, Else) -> {'try', [{line,Line}], [Else]}.
|
||||
|
||||
is_macro(defmacro) -> true;
|
||||
is_macro(defmacrop) -> true;
|
||||
@@ -327,6 +337,18 @@ defaults_conflict(A, D, Arity, Defaults) ->
|
||||
((Arity >= (A - D)) andalso (Arity < A)) orelse
|
||||
((A >= (Arity - Defaults)) andalso (A < Arity)).
|
||||
|
||||
check_args_for_bodyless_clause(Line, Args, E) ->
|
||||
[ begin
|
||||
elixir_errors:form_error(Line, ?m(E, file), ?MODULE, invalid_args_for_bodyless_clause)
|
||||
end || Arg <- Args, invalid_arg(Arg) ].
|
||||
|
||||
invalid_arg({Name, _, Kind}) when is_atom(Name), is_atom(Kind) ->
|
||||
false;
|
||||
invalid_arg({'\\\\', _, [{Name, _, Kind}, _]}) when is_atom(Name), is_atom(Kind) ->
|
||||
false;
|
||||
invalid_arg(_) ->
|
||||
true.
|
||||
|
||||
assert_no_aliases_name(Line, '__aliases__', [Atom], #{file := File}) when is_atom(Atom) ->
|
||||
elixir_errors:form_error(Line, File, ?MODULE, {no_alias, Atom});
|
||||
|
||||
@@ -366,5 +388,8 @@ format_error({no_alias, Atom}) ->
|
||||
format_error({invalid_def, Kind, NameAndArgs}) ->
|
||||
io_lib:format("invalid syntax in ~ts ~ts", [Kind, 'Elixir.Macro':to_string(NameAndArgs)]);
|
||||
|
||||
format_error(invalid_args_for_bodyless_clause) ->
|
||||
"can use only variables and \\\\ as arguments of bodyless clause";
|
||||
|
||||
format_error({missing_do, Kind}) ->
|
||||
io_lib:format("missing do keyword in ~ts", [Kind]).
|
||||
|
||||
@@ -21,17 +21,19 @@ unpack(Kind, Name, Args, S) ->
|
||||
%% Unpack default from given args.
|
||||
%% Returns the given arguments without their default
|
||||
%% clauses and a list of clauses for the default calls.
|
||||
unpack_each(Kind, Name, [{'\\\\', Line, [Expr, _]}|T] = List, Acc, Clauses, S) ->
|
||||
Base = build_match(Acc, Line, []),
|
||||
{Args, Invoke} = extract_defaults(List, Line, length(Base), [], []),
|
||||
unpack_each(Kind, Name, [{'\\\\', DefMeta, [Expr, _]}|T] = List, Acc, Clauses, S) ->
|
||||
Base = wrap_kind(Kind, build_match(Acc, [])),
|
||||
{Args, Invoke} = extract_defaults(List, length(Base), [], []),
|
||||
|
||||
{DefArgs, SA} = elixir_clauses:match(fun elixir_translator:translate_args/2, Base ++ Args, S),
|
||||
{DefInvoke, _} = elixir_translator:translate_args(Base ++ Invoke, SA),
|
||||
|
||||
Line = ?line(DefMeta),
|
||||
|
||||
Call = {call, Line,
|
||||
{atom, Line, name_for_kind(Kind, Name)},
|
||||
DefInvoke
|
||||
},
|
||||
},
|
||||
|
||||
Clause = {clause, Line, DefArgs, [], [Call]},
|
||||
unpack_each(Kind, Name, T, [Expr|Acc], [Clause|Clauses], S);
|
||||
@@ -44,25 +46,28 @@ unpack_each(_Kind, _Name, [], Acc, Clauses, _S) ->
|
||||
|
||||
% Extract default values from args following the current default clause.
|
||||
|
||||
extract_defaults([{'\\\\', _, [_Expr, Default]}|T], Line, Counter, NewArgs, NewInvoke) ->
|
||||
extract_defaults(T, Line, Counter, NewArgs, [Default|NewInvoke]);
|
||||
extract_defaults([{'\\\\', _, [_Expr, Default]}|T], Counter, NewArgs, NewInvoke) ->
|
||||
extract_defaults(T, Counter, NewArgs, [Default|NewInvoke]);
|
||||
|
||||
extract_defaults([_|T], Line, Counter, NewArgs, NewInvoke) ->
|
||||
H = {elixir_utils:atom_concat(["_@D", Counter]), Line, nil},
|
||||
extract_defaults(T, Line, Counter + 1, [H|NewArgs], [H|NewInvoke]);
|
||||
extract_defaults([_|T], Counter, NewArgs, NewInvoke) ->
|
||||
H = {elixir_utils:atom_concat(["x", Counter]), [], nil},
|
||||
extract_defaults(T, Counter + 1, [H|NewArgs], [H|NewInvoke]);
|
||||
|
||||
extract_defaults([], _Line, _Counter, NewArgs, NewInvoke) ->
|
||||
extract_defaults([], _Counter, NewArgs, NewInvoke) ->
|
||||
{lists:reverse(NewArgs), lists:reverse(NewInvoke)}.
|
||||
|
||||
% Build matches for all the previous argument until the current default clause.
|
||||
|
||||
build_match([], _Line, Acc) -> Acc;
|
||||
build_match([], Acc) -> Acc;
|
||||
|
||||
build_match([_|T], Line, Acc) ->
|
||||
Var = {elixir_utils:atom_concat(["_@D", length(T)]), Line, nil},
|
||||
build_match(T, Line, [Var|Acc]).
|
||||
build_match([_|T], Acc) ->
|
||||
Var = {elixir_utils:atom_concat(["x", length(T)]), [], nil},
|
||||
build_match(T, [Var|Acc]).
|
||||
|
||||
% Given the invoked function name based on the kind
|
||||
|
||||
wrap_kind(Kind, Args) when Kind == defmacro; Kind == defmacrop -> [{c, [], nil}|Args];
|
||||
wrap_kind(_Kind, Args) -> Args.
|
||||
|
||||
name_for_kind(Kind, Name) when Kind == defmacro; Kind == defmacrop -> elixir_utils:macro_name(Name);
|
||||
name_for_kind(_Kind, Name) -> Name.
|
||||
@@ -147,9 +147,17 @@ do_expand_import(Meta, {Name, Arity} = Tuple, Args, Module, E, Result) ->
|
||||
{function, Receiver} ->
|
||||
elixir_lexical:record_import(Receiver, ?m(E, lexical_tracker)),
|
||||
elixir_locals:record_import(Tuple, Receiver, Module, ?m(E, function)),
|
||||
case rewrite(Receiver, Name, Args, Arity) of
|
||||
{ok, _, _, _} = Res -> Res;
|
||||
false -> {ok, Receiver, Name, Args}
|
||||
|
||||
%% DEPRECATED
|
||||
case {Receiver, Name, Arity} of
|
||||
{?kernel, size, 1} ->
|
||||
check_deprecation(Meta, Receiver, Name, Arity, E),
|
||||
{ok, erlang, size, Args};
|
||||
_ ->
|
||||
case rewrite(Receiver, Name, Args, Arity) of
|
||||
{ok, _, _, _} = Res -> Res;
|
||||
false -> {ok, Receiver, Name, Args}
|
||||
end
|
||||
end;
|
||||
{macro, Receiver} ->
|
||||
check_deprecation(Meta, Receiver, Name, Arity, E),
|
||||
@@ -444,7 +452,6 @@ inline(?kernel, 'rem', 2) -> {erlang, 'rem'};
|
||||
inline(?kernel, round, 1) -> {erlang, round};
|
||||
inline(?kernel, self, 0) -> {erlang, self};
|
||||
inline(?kernel, send, 2) -> {erlang, send};
|
||||
inline(?kernel, size, 1) -> {erlang, size};
|
||||
inline(?kernel, spawn, 1) -> {erlang, spawn};
|
||||
inline(?kernel, spawn, 3) -> {erlang, spawn};
|
||||
inline(?kernel, spawn_link, 1) -> {erlang, spawn_link};
|
||||
@@ -508,54 +515,16 @@ deprecation_message(Warning, Message) ->
|
||||
Message -> Warning ++ ", " ++ Message
|
||||
end.
|
||||
|
||||
% deprecation('Elixir.Application.Behaviour', '__using__', _) ->
|
||||
% "use Application instead";
|
||||
% deprecation('Elixir.Dict.Behaviour', '__using__', _) ->
|
||||
% "use Dict instead";
|
||||
% deprecation('Elixir.GenEvent.Behaviour', '__using__', _) ->
|
||||
% "use GenEvent instead";
|
||||
% deprecation('Elixir.GenServer.Behaviour', '__using__', _) ->
|
||||
% "use GenServer instead";
|
||||
% deprecation('Elixir.Supervisor.Behaviour', '__using__', _) ->
|
||||
% "use Supervisor instead";
|
||||
% deprecation('Elixir.Kernel', raise, 3) ->
|
||||
% "use reraise/2 instead";
|
||||
% deprecation('Elixir.Kernel', defexception, 3) ->
|
||||
% "use defexception/1 instead";
|
||||
% deprecation('Elixir.List', 'from_char_data', 1) ->
|
||||
% "use String.to_char_list instead";
|
||||
% deprecation('Elixir.List', 'from_char_data!', 1) ->
|
||||
% "use String.to_char_list instead";
|
||||
% deprecation('Elixir.String', 'from_char_data', 1) ->
|
||||
% "use List.to_string instead";
|
||||
% deprecation('Elixir.String', 'from_char_data!', 1) ->
|
||||
% "use List.to_string instead";
|
||||
deprecation('Elixir.Kernel', 'size', 1) ->
|
||||
"use byte_size/1 or tuple_size/1 instead";
|
||||
% deprecation('Elixir.Mix.Generator', 'from_file', _) ->
|
||||
% "instead pass [from_file: file] to embed_text/2 and embed_template/2 macros. "
|
||||
% "Note that [from_file: file] expects paths relative to the current working "
|
||||
% "directory and not to the current file";
|
||||
% deprecation('Elixir.EEx.TransformerEngine', '__using__', _) ->
|
||||
% "check EEx.SmartEngine for how to build custom engines";
|
||||
% deprecation('Elixir.EEx.AssignsEngine', '__using__', _) ->
|
||||
% "check EEx.SmartEngine for how to build custom engines";
|
||||
|
||||
deprecation('Elixir.Kernel', is_record, _) ->
|
||||
"use Record.record? instead";
|
||||
deprecation('Elixir.Kernel', defrecord, _) ->
|
||||
"use structs instead";
|
||||
deprecation('Elixir.Kernel', defrecordp, _) ->
|
||||
"use Record.defrecordp/3 instead";
|
||||
deprecation('Elixir.Process', spawn, 1) ->
|
||||
"use the one in Kernel instead";
|
||||
deprecation('Elixir.Process', spawn, 3) ->
|
||||
"use the one in Kernel instead";
|
||||
deprecation('Elixir.Process', spawn_link, _) ->
|
||||
"use the one in Kernel instead";
|
||||
deprecation('Elixir.Process', spawn_monitor, _) ->
|
||||
"use the one in Kernel instead";
|
||||
deprecation('Elixir.Process', self, 0) ->
|
||||
"use the one in Kernel instead";
|
||||
deprecation('Elixir.Process', send, 2) ->
|
||||
"use the one in Kernel instead";
|
||||
deprecation('Elixir.Record', defmacros, 4) ->
|
||||
true;
|
||||
deprecation('Elixir.Record', deftypes, 3) ->
|
||||
true;
|
||||
deprecation('Elixir.Record', deffunctions, 2) ->
|
||||
true;
|
||||
deprecation('Elixir.Mix', project, 0) ->
|
||||
"use Mix.Project.config/0 instead";
|
||||
deprecation(_, _, _) ->
|
||||
false.
|
||||
|
||||
@@ -169,17 +169,9 @@ expand({quote, Meta, [KV, Do]}, E) when is_list(Do) ->
|
||||
false -> compile_error(Meta, E#elixir_scope.file, "missing do keyword in quote")
|
||||
end,
|
||||
|
||||
ValidOpts = [hygiene, context, location, line, unquote, bind_quoted],
|
||||
ValidOpts = [context, location, line, unquote, bind_quoted],
|
||||
{EKV, ET} = expand_opts(Meta, quote, ValidOpts, KV, E),
|
||||
|
||||
Hygiene = case lists:keyfind(hygiene, 1, EKV) of
|
||||
{hygiene, List} when is_list(List) ->
|
||||
elixir_errors:warn(?line(Meta), ?m(E, file), "the :hygiene option in quote is deprecated"),
|
||||
List;
|
||||
false ->
|
||||
[]
|
||||
end,
|
||||
|
||||
Context = case lists:keyfind(context, 1, EKV) of
|
||||
{context, Ctx} when is_atom(Ctx) and (Ctx /= nil) ->
|
||||
Ctx;
|
||||
@@ -193,10 +185,6 @@ expand({quote, Meta, [KV, Do]}, E) when is_list(Do) ->
|
||||
end
|
||||
end,
|
||||
|
||||
Vars = lists:keyfind(vars, 1, Hygiene) /= {vars, false},
|
||||
Aliases = lists:keyfind(aliases, 1, Hygiene) /= {aliases, false},
|
||||
Imports = lists:keyfind(imports, 1, Hygiene) /= {imports, false},
|
||||
|
||||
Keep = lists:keyfind(location, 1, EKV) == {location, keep},
|
||||
Line = proplists:get_value(line, EKV, false),
|
||||
|
||||
@@ -210,8 +198,7 @@ expand({quote, Meta, [KV, Do]}, E) when is_list(Do) ->
|
||||
false -> DefaultUnquote
|
||||
end,
|
||||
|
||||
Q = #elixir_quote{vars_hygiene=Vars, line=Line, keep=Keep, unquote=Unquote,
|
||||
aliases_hygiene=Aliases, imports_hygiene=Imports, context=Context},
|
||||
Q = #elixir_quote{line=Line, keep=Keep, unquote=Unquote, context=Context},
|
||||
|
||||
{Quoted, _Q} = elixir_quote:quote(Exprs, Binding, Q, ET),
|
||||
expand(Quoted, ET);
|
||||
@@ -238,11 +225,22 @@ expand({fn, Meta, Pairs}, E) ->
|
||||
|
||||
%% Case/Receive/Try
|
||||
|
||||
expand({'cond', Meta, [KV]}, E) ->
|
||||
assert_no_match_or_guard_scope(Meta, 'cond', E),
|
||||
{EClauses, EC} = elixir_exp_clauses:'cond'(Meta, KV, E),
|
||||
{{'cond', Meta, [EClauses]}, EC};
|
||||
|
||||
expand({'case', Meta, [Expr, KV]}, E) ->
|
||||
assert_no_match_or_guard_scope(Meta, 'case', E),
|
||||
{EExpr, EE} = expand(Expr, E),
|
||||
{EClauses, EC} = elixir_exp_clauses:'case'(Meta, KV, EE),
|
||||
{{'case', Meta, [EExpr, EClauses]}, EC};
|
||||
FClauses =
|
||||
case (lists:keyfind(optimize_boolean, 1, Meta) == {optimize_boolean, true}) and
|
||||
elixir_utils:returns_boolean(EExpr) of
|
||||
true -> rewrite_case_clauses(EClauses);
|
||||
false -> EClauses
|
||||
end,
|
||||
{{'case', Meta, [EExpr, FClauses]}, EC};
|
||||
|
||||
expand({'receive', Meta, [KV]}, E) ->
|
||||
assert_no_match_or_guard_scope(Meta, 'receive', E),
|
||||
@@ -256,7 +254,7 @@ expand({'try', Meta, [KV]}, E) ->
|
||||
|
||||
%% Comprehensions
|
||||
|
||||
expand({for, Meta, Args}, E) when is_list(Args) ->
|
||||
expand({for, Meta, [_|_] = Args}, E) ->
|
||||
elixir_for:expand(Meta, Args, E);
|
||||
|
||||
%% Super
|
||||
@@ -285,7 +283,7 @@ expand({'_', _, Kind} = Var, E) when is_atom(Kind) ->
|
||||
expand({Name, Meta, Kind} = Var, #{context := match, export_vars := Export} = E) when is_atom(Name), is_atom(Kind) ->
|
||||
Pair = {Name, var_kind(Meta, Kind)},
|
||||
NewVars = ordsets:add_element(Pair, ?m(E, vars)),
|
||||
NewExport = case (Export /= nil) andalso (lists:keyfind(export, 1, Meta) /= {export, false}) of
|
||||
NewExport = case (Export /= nil) of
|
||||
true -> ordsets:add_element(Pair, Export);
|
||||
false -> Export
|
||||
end,
|
||||
@@ -414,6 +412,9 @@ expand_arg(Arg, {Acc1, Acc2}) ->
|
||||
{EArg, EAcc} = expand(Arg, Acc1),
|
||||
{EArg, {elixir_env:mergea(Acc1, EAcc), elixir_env:mergev(Acc2, EAcc)}}.
|
||||
|
||||
expand_args([Arg], E) ->
|
||||
{EArg, EE} = expand(Arg, E),
|
||||
{[EArg], EE};
|
||||
expand_args(Args, #{context := match} = E) ->
|
||||
expand_many(Args, E);
|
||||
expand_args(Args, E) ->
|
||||
@@ -538,6 +539,27 @@ expand_as({as, Other}, Meta, _IncludeByDefault, _Ref, E) ->
|
||||
|
||||
%% Assertions
|
||||
|
||||
rewrite_case_clauses([{do,[
|
||||
{'->', FalseMeta, [
|
||||
[{'when', _, [Var, {'__op__', _,[
|
||||
'orelse',
|
||||
{{'.', _, [erlang, '=:=']}, _, [Var, nil]},
|
||||
{{'.', _, [erlang, '=:=']}, _, [Var, false]}
|
||||
]}]}],
|
||||
FalseExpr
|
||||
]},
|
||||
{'->', TrueMeta, [
|
||||
[{'_', _, _}],
|
||||
TrueExpr
|
||||
]}
|
||||
]}]) ->
|
||||
[{do, [
|
||||
{'->', FalseMeta, [[false], FalseExpr]},
|
||||
{'->', TrueMeta, [[true], TrueExpr]}
|
||||
]}];
|
||||
rewrite_case_clauses(Clauses) ->
|
||||
Clauses.
|
||||
|
||||
assert_no_match_or_guard_scope(Meta, Kind, E) ->
|
||||
assert_no_match_scope(Meta, Kind, E),
|
||||
assert_no_guard_scope(Meta, Kind, E).
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
%% Handle code related to args, guard and -> matching for case,
|
||||
%% fn, receive and friends. try is handled in elixir_try.
|
||||
-module(elixir_exp_clauses).
|
||||
-export([match/3, clause/5, 'case'/3, 'receive'/3, 'try'/3, def/5]).
|
||||
-export([match/3, clause/5, def/5, head/2,
|
||||
'case'/3, 'receive'/3, 'try'/3, 'cond'/3]).
|
||||
-import(elixir_errors, [compile_error/3, compile_error/4]).
|
||||
-include("elixir.hrl").
|
||||
|
||||
@@ -18,19 +19,19 @@ def(Fun, Args, Guards, Body, E) ->
|
||||
clause(Meta, Kind, Fun, {'->', ClauseMeta, [_, _]} = Clause, E) when is_function(Fun, 3) ->
|
||||
clause(Meta, Kind, fun(X, Acc) -> Fun(ClauseMeta, X, Acc) end, Clause, E);
|
||||
clause(_Meta, _Kind, Fun, {'->', Meta, [Left, Right]}, E) ->
|
||||
{ELeft, EL} = head(Fun, Left, E),
|
||||
{ELeft, EL} = Fun(Left, E),
|
||||
{ERight, ER} = elixir_exp:expand(Right, EL),
|
||||
{{'->', Meta, [ELeft, ERight]}, ER};
|
||||
clause(Meta, Kind, _Fun, _, E) ->
|
||||
compile_error(Meta, ?m(E, file), "expected -> clauses in ~ts", [Kind]).
|
||||
|
||||
head(Fun, [{'when', Meta, [_,_|_] = All}], E) ->
|
||||
head([{'when', Meta, [_,_|_] = All}], E) ->
|
||||
{Args, Guard} = elixir_utils:split_last(All),
|
||||
{EArgs, EA} = match(Fun, Args, E),
|
||||
{EArgs, EA} = match(fun elixir_exp:expand_args/2, Args, E),
|
||||
{EGuard, EG} = guard(Guard, EA#{context := guard}),
|
||||
{[{'when', Meta, EArgs ++ [EGuard]}], EG#{context := ?m(E, context)}};
|
||||
head(Fun, Args, E) ->
|
||||
match(Fun, Args, E).
|
||||
head(Args, E) ->
|
||||
match(fun elixir_exp:expand_args/2, Args, E).
|
||||
|
||||
guard({'when', Meta, [Left, Right]}, E) ->
|
||||
{ELeft, EL} = guard(Left, E),
|
||||
@@ -51,10 +52,28 @@ guard(Other, E) ->
|
||||
{EClauses, elixir_env:mergev(EVars, E)}.
|
||||
|
||||
do_case(Meta, {'do', _} = Do, Acc, E) ->
|
||||
expand_with_export(Meta, 'case', expand_arg(Meta, 'case', 'do'), Do, Acc, E);
|
||||
Fun = expand_one(Meta, 'case', 'do', fun head/2),
|
||||
expand_with_export(Meta, 'case', Fun, Do, Acc, E);
|
||||
do_case(Meta, {Key, _}, _Acc, E) ->
|
||||
compile_error(Meta, ?m(E, file), "unexpected keyword ~ts in case", [Key]).
|
||||
|
||||
%% Cond
|
||||
|
||||
'cond'(Meta, [], E) ->
|
||||
compile_error(Meta, ?m(E, file), "missing do keyword in cond");
|
||||
'cond'(Meta, KV, E) when not is_list(KV) ->
|
||||
compile_error(Meta, ?m(E, file), "invalid arguments for cond");
|
||||
'cond'(Meta, KV, E) ->
|
||||
EE = E#{export_vars := []},
|
||||
{EClauses, EVars} = lists:mapfoldl(fun(X, Acc) -> do_cond(Meta, X, Acc, EE) end, [], KV),
|
||||
{EClauses, elixir_env:mergev(EVars, E)}.
|
||||
|
||||
do_cond(Meta, {'do', _} = Do, Acc, E) ->
|
||||
Fun = expand_one(Meta, 'cond', 'do', fun elixir_exp:expand_args/2),
|
||||
expand_with_export(Meta, 'cond', Fun, Do, Acc, E);
|
||||
do_cond(Meta, {Key, _}, _Acc, E) ->
|
||||
compile_error(Meta, ?m(E, file), "unexpected keyword ~ts in cond", [Key]).
|
||||
|
||||
%% Receive
|
||||
|
||||
'receive'(Meta, [], E) ->
|
||||
@@ -69,12 +88,11 @@ do_case(Meta, {Key, _}, _Acc, E) ->
|
||||
do_receive(_Meta, {'do', nil} = Do, Acc, _E) ->
|
||||
{Do, Acc};
|
||||
do_receive(Meta, {'do', _} = Do, Acc, E) ->
|
||||
expand_with_export(Meta, 'receive', expand_arg(Meta, 'receive', 'do'), Do, Acc, E);
|
||||
do_receive(_Meta, {'after', [{'->', Meta, [[Left], Right]}]}, Acc, E) ->
|
||||
{ELeft, EL} = elixir_exp:expand(Left, E),
|
||||
{ERight, ER} = elixir_exp:expand(Right, EL),
|
||||
EClause = {'after', [{'->', Meta, [[ELeft], ERight]}]},
|
||||
{EClause, elixir_env:merge_vars(Acc, ?m(ER, export_vars))};
|
||||
Fun = expand_one(Meta, 'receive', 'do', fun head/2),
|
||||
expand_with_export(Meta, 'receive', Fun, Do, Acc, E);
|
||||
do_receive(Meta, {'after', [_]} = After, Acc, E) ->
|
||||
Fun = expand_one(Meta, 'receive', 'after', fun elixir_exp:expand_args/2),
|
||||
expand_with_export(Meta, 'receive', Fun, After, Acc, E);
|
||||
do_receive(Meta, {'after', _}, _Acc, E) ->
|
||||
compile_error(Meta, ?m(E, file), "expected a single -> clause for after in receive");
|
||||
do_receive(Meta, {Key, _}, _Acc, E) ->
|
||||
@@ -96,14 +114,22 @@ do_try(_Meta, {'after', Expr}, E) ->
|
||||
{EExpr, _} = elixir_exp:expand(Expr, E),
|
||||
{'after', EExpr};
|
||||
do_try(Meta, {'else', _} = Else, E) ->
|
||||
expand_without_export(Meta, 'try', expand_arg(Meta, 'try', 'else'), Else, E);
|
||||
Fun = expand_one(Meta, 'try', 'else', fun head/2),
|
||||
expand_without_export(Meta, 'try', Fun, Else, E);
|
||||
do_try(Meta, {'catch', _} = Catch, E) ->
|
||||
expand_without_export(Meta, 'try', fun elixir_exp:expand_args/2, Catch, E);
|
||||
expand_without_export(Meta, 'try', fun expand_catch/3, Catch, E);
|
||||
do_try(Meta, {'rescue', _} = Rescue, E) ->
|
||||
expand_without_export(Meta, 'try', fun expand_rescue/3, Rescue, E);
|
||||
do_try(Meta, {Key, _}, E) ->
|
||||
compile_error(Meta, ?m(E, file), "unexpected keyword ~ts in try", [Key]).
|
||||
|
||||
expand_catch(_Meta, [_] = Args, E) ->
|
||||
head(Args, E);
|
||||
expand_catch(_Meta, [_, _] = Args, E) ->
|
||||
head(Args, E);
|
||||
expand_catch(Meta, _, E) ->
|
||||
compile_error(Meta, ?m(E, file), "expected one or two args for catch clauses (->) in try").
|
||||
|
||||
expand_rescue(Meta, [Arg], E) ->
|
||||
case expand_rescue(Arg, E) of
|
||||
{EArg, EA} ->
|
||||
@@ -121,14 +147,14 @@ expand_rescue({Name, _, Atom} = Var, E) when is_atom(Name), is_atom(Atom) ->
|
||||
|
||||
%% rescue var in [Exprs]
|
||||
expand_rescue({in, Meta, [Left, Right]}, E) ->
|
||||
{ERight, ER} = elixir_exp:expand(Right, E#{context := nil}),
|
||||
{ELeft, EL} = elixir_exp:expand(Left, ER#{context := match}),
|
||||
{ELeft, EL} = match(fun elixir_exp:expand/2, Left, E),
|
||||
{ERight, ER} = elixir_exp:expand(Right, EL),
|
||||
|
||||
case ELeft of
|
||||
{Name, _, Atom} when is_atom(Name), is_atom(Atom) ->
|
||||
case normalize_rescue(ERight) of
|
||||
false -> false;
|
||||
Other -> {{in, Meta, [ELeft, Other]}, EL}
|
||||
Other -> {{in, Meta, [ELeft, Other]}, ER}
|
||||
end;
|
||||
_ ->
|
||||
false
|
||||
@@ -145,34 +171,28 @@ normalize_rescue(Other) ->
|
||||
|
||||
%% Expansion helpers
|
||||
|
||||
export_vars({Left, Meta, Right}) when is_atom(Left), is_list(Meta), is_atom(Right) ->
|
||||
{Left, [{export,false}|Meta], Right};
|
||||
export_vars({Left, Meta, Right}) ->
|
||||
{export_vars(Left), Meta, export_vars(Right)};
|
||||
export_vars({Left, Right}) ->
|
||||
{export_vars(Left), export_vars(Right)};
|
||||
export_vars(List) when is_list(List) ->
|
||||
[export_vars(X) || X <- List];
|
||||
export_vars(Other) ->
|
||||
Other.
|
||||
|
||||
%% Returns a function that expands arguments
|
||||
%% considering we have at maximum one entry.
|
||||
expand_arg(Meta, Kind, Key) ->
|
||||
expand_one(Meta, Kind, Key, Fun) ->
|
||||
fun
|
||||
([Arg], E) ->
|
||||
{EArg, EA} = elixir_exp:expand(Arg, E),
|
||||
{[EArg], EA};
|
||||
([_] = Args, E) ->
|
||||
Fun(Args, E);
|
||||
(_, E) ->
|
||||
compile_error(Meta, ?m(E, file), "expected one arg for ~ts clauses (->) in ~ts", [Key, Kind])
|
||||
compile_error(Meta, ?m(E, file),
|
||||
"expected one arg for ~ts clauses (->) in ~ts", [Key, Kind])
|
||||
end.
|
||||
|
||||
%% Expands all -> pairs in a given key keeping the overall vars.
|
||||
expand_with_export(Meta, Kind, Fun, {Key, Clauses}, Acc, E) when is_list(Clauses) ->
|
||||
EFun =
|
||||
case lists:keyfind(export_head, 1, Meta) of
|
||||
{export_head, true} -> Fun;
|
||||
_ -> fun(ExportArgs, ExportE) -> Fun(export_vars(ExportArgs), ExportE) end
|
||||
{export_head, true} ->
|
||||
Fun;
|
||||
_ ->
|
||||
fun(Args, #{export_vars := ExportVars} = EE) ->
|
||||
{FArgs, FE} = Fun(Args, EE),
|
||||
{FArgs, FE#{export_vars := ExportVars}}
|
||||
end
|
||||
end,
|
||||
Transformer = fun(Clause, Vars) ->
|
||||
{EClause, EC} = clause(Meta, Kind, EFun, Clause, E),
|
||||
|
||||
@@ -30,7 +30,7 @@ translate_fn_match(Arg, S) ->
|
||||
|
||||
expand(Meta, Clauses, E) when is_list(Clauses) ->
|
||||
Transformer = fun(Clause) ->
|
||||
{EClause, _} = elixir_exp_clauses:clause(Meta, fn, fun elixir_exp:expand_args/2, Clause, E),
|
||||
{EClause, _} = elixir_exp_clauses:clause(Meta, fn, fun elixir_exp_clauses:head/2, Clause, E),
|
||||
EClause
|
||||
end,
|
||||
{{fn, Meta, lists:map(Transformer, Clauses)}, E}.
|
||||
|
||||
@@ -109,7 +109,7 @@ translate_filters(T, S) ->
|
||||
|
||||
translate_filter(Filter, S) ->
|
||||
{TFilter, TS} = elixir_translator:translate(Filter, S),
|
||||
case elixir_utils:returns_boolean(TFilter) of
|
||||
case elixir_utils:returns_boolean(Filter) of
|
||||
true ->
|
||||
{{nil, TFilter}, TS};
|
||||
false ->
|
||||
|
||||
@@ -205,6 +205,7 @@ special_form('unquote_splicing', 1) -> true;
|
||||
special_form('fn', _) -> true;
|
||||
special_form('super', _) -> true;
|
||||
special_form('for', _) -> true;
|
||||
special_form('cond', 1) -> true;
|
||||
special_form('case', 2) -> true;
|
||||
special_form('try', 2) -> true;
|
||||
special_form('receive', 1) -> true;
|
||||
|
||||
@@ -9,73 +9,53 @@ unescape_tokens/1, unescape_tokens/2, unescape_map/1]).
|
||||
extract(Line, Raw, Interpol, String, Last) ->
|
||||
%% Ignore whatever is in the scope and enable terminator checking.
|
||||
Scope = Raw#elixir_tokenizer{terminators=[], check_terminators=true},
|
||||
extract(Line, Scope, Interpol, String, [], 0, [], Last).
|
||||
extract(Line, Scope, Interpol, String, [], [], Last).
|
||||
|
||||
%% Terminators
|
||||
|
||||
extract(Line, _Scope, _Interpol, [], Buffer, 0, Output, []) ->
|
||||
extract(Line, _Scope, _Interpol, [], Buffer, Output, []) ->
|
||||
finish_extraction(Line, Buffer, Output, []);
|
||||
|
||||
extract(Line, _Scope, _Interpol, [], _Buffer, 0, _Output, Last) ->
|
||||
extract(Line, _Scope, _Interpol, [], _Buffer, _Output, Last) ->
|
||||
{error, {string, Line, io_lib:format("missing terminator: ~ts", [[Last]]), []}};
|
||||
|
||||
extract(Line, _Scope, _Interpol, [Last|Remaining], Buffer, 0, Output, Last) ->
|
||||
extract(Line, _Scope, _Interpol, [Last|Remaining], Buffer, Output, Last) ->
|
||||
finish_extraction(Line, Buffer, Output, Remaining);
|
||||
|
||||
extract(Line, _Scope, _Interpol, [], _Buffer, _Search, _Output, Last) ->
|
||||
{error, {string, Line, io_lib:format("missing terminator: ~ts", [[Last]]), []}};
|
||||
|
||||
%% Going through the string
|
||||
|
||||
extract(Line, Scope, Interpol, [$\\, $\n|Rest], Buffer, Search, Output, Last) ->
|
||||
extract(Line+1, Scope, Interpol, Rest, Buffer, Search, Output, Last);
|
||||
extract(Line, Scope, Interpol, [$\\, $\n|Rest], Buffer, Output, Last) ->
|
||||
extract(Line+1, Scope, Interpol, Rest, Buffer, Output, Last);
|
||||
|
||||
extract(Line, Scope, Interpol, [$\\, $\r, $\n|Rest], Buffer, Search, Output, Last) ->
|
||||
extract(Line+1, Scope, Interpol, Rest, Buffer, Search, Output, Last);
|
||||
extract(Line, Scope, Interpol, [$\\, $\r, $\n|Rest], Buffer, Output, Last) ->
|
||||
extract(Line+1, Scope, Interpol, Rest, Buffer, Output, Last);
|
||||
|
||||
extract(Line, Scope, Interpol, [$\n|Rest], Buffer, Search, Output, Last) ->
|
||||
extract(Line+1, Scope, Interpol, Rest, [$\n|Buffer], Search, Output, Last);
|
||||
extract(Line, Scope, Interpol, [$\n|Rest], Buffer, Output, Last) ->
|
||||
extract(Line+1, Scope, Interpol, Rest, [$\n|Buffer], Output, Last);
|
||||
|
||||
extract(Line, Scope, Interpol, [$\\, $#, ${|Rest], Buffer, Search, Output, Last) ->
|
||||
extract(Line, Scope, Interpol, Rest, [${,$#|Buffer], Search, Output, Last);
|
||||
extract(Line, Scope, Interpol, [$\\, $#, ${|Rest], Buffer, Output, Last) ->
|
||||
extract(Line, Scope, Interpol, Rest, [${,$#|Buffer], Output, Last);
|
||||
|
||||
extract(Line, Scope, Interpol, [$\\,Char|Rest], Buffer, Search, Output, Last) ->
|
||||
extract(Line, Scope, Interpol, Rest, [Char,$\\|Buffer], Search, Output, Last);
|
||||
extract(Line, Scope, Interpol, [$\\,Char|Rest], Buffer, Output, Last) ->
|
||||
extract(Line, Scope, Interpol, Rest, [Char,$\\|Buffer], Output, Last);
|
||||
|
||||
extract(Line, Scope, true, [$#, ${|Rest], Buffer, Search, Output, Last) ->
|
||||
extract(Line, Scope, true, [$#, ${|Rest], Buffer, Output, Last) ->
|
||||
Output1 = build_string(Line, Buffer, Output),
|
||||
|
||||
case elixir_tokenizer:tokenize(Rest, Line, Scope) of
|
||||
{error, {EndLine, _, "}"}, [$}|NewRest], Tokens} ->
|
||||
Output2 = build_interpol(Line, Tokens, Output1),
|
||||
extract(EndLine, Scope, true, NewRest, [], Search, Output2, Last);
|
||||
extract(EndLine, Scope, true, NewRest, [], Output2, Last);
|
||||
{error, Reason, _, _} ->
|
||||
{error, Reason};
|
||||
{ok, _EndLine, _} ->
|
||||
{error, {string, Line, "missing interpolation terminator:}", []}}
|
||||
end;
|
||||
|
||||
%% Matching () [] {} <> inside sigils
|
||||
|
||||
extract(Line, Scope, Interpol, [$(|Rest], Buffer, Search, Output, $)) ->
|
||||
extract(Line, Scope, Interpol, Rest, [$(|Buffer], Search + 1, Output, $));
|
||||
|
||||
extract(Line, Scope, Interpol, [$[|Rest], Buffer, Search, Output, $]) ->
|
||||
extract(Line, Scope, Interpol, Rest, [$[|Buffer], Search + 1, Output, $]);
|
||||
|
||||
extract(Line, Scope, Interpol, [${|Rest], Buffer, Search, Output, $}) ->
|
||||
extract(Line, Scope, Interpol, Rest, [${|Buffer], Search + 1, Output, $});
|
||||
|
||||
extract(Line, Scope, Interpol, [$<|Rest], Buffer, Search, Output, $>) ->
|
||||
extract(Line, Scope, Interpol, Rest, [$<|Buffer], Search + 1, Output, $>);
|
||||
|
||||
extract(Line, Scope, Interpol, [Last|Rest], Buffer, Search, Output, Last) ->
|
||||
extract(Line, Scope, Interpol, Rest, [Last|Buffer], Search - 1, Output, Last);
|
||||
|
||||
%% Catch all clause
|
||||
|
||||
extract(Line, Scope, Interpol, [Char|Rest], Buffer, Search, Output, Last) ->
|
||||
extract(Line, Scope, Interpol, Rest, [Char|Buffer], Search, Output, Last).
|
||||
extract(Line, Scope, Interpol, [Char|Rest], Buffer, Output, Last) ->
|
||||
extract(Line, Scope, Interpol, Rest, [Char|Buffer], Output, Last).
|
||||
|
||||
%% Unescape a series of tokens as returned by extract.
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
-module(elixir_module).
|
||||
-export([compile/4, data_table/1, docs_table/1, is_open/1,
|
||||
format_error/1, eval_callbacks/5]).
|
||||
expand_callback/6, add_beam_chunk/3, format_error/1]).
|
||||
-include("elixir.hrl").
|
||||
|
||||
-define(acc_attr, '__acc_attributes').
|
||||
@@ -55,8 +55,8 @@ do_compile(Line, Module, Block, Vars, E) ->
|
||||
|
||||
{All, Forms0} = functions_form(Line, File, Module, Base, Export, Def, Defmacro, Functions),
|
||||
Forms1 = specs_form(Module, Private, Defmacro, Forms0),
|
||||
Forms2 = attributes_form(Line, File, Module, Forms1),
|
||||
Forms3 = typedocs_form(Module, Forms2),
|
||||
Forms2 = types_form(Module, Forms1),
|
||||
Forms3 = attributes_form(Line, File, Module, Forms2),
|
||||
|
||||
case ets:lookup(data_table(Module), 'on_load') of
|
||||
[] -> ok;
|
||||
@@ -111,8 +111,8 @@ build(Line, File, Module, Lexical) ->
|
||||
_ -> ets:insert(DataTable, {on_definition, []})
|
||||
end,
|
||||
|
||||
Attributes = [behaviour, on_load, spec, type, export_type, opaque, callback, compile],
|
||||
ets:insert(DataTable, {?acc_attr, [before_compile, after_compile, on_definition|Attributes]}),
|
||||
Attributes = [behaviour, on_load, spec, type, typep, opaque, callback, compile, external_resource],
|
||||
ets:insert(DataTable, {?acc_attr, [before_compile, after_compile, on_definition, derive|Attributes]}),
|
||||
ets:insert(DataTable, {?persisted_attr, [vsn|Attributes]}),
|
||||
ets:insert(DataTable, {?docs_attr, ets:new(DataTable, [ordered_set, public])}),
|
||||
ets:insert(DataTable, {?lexical_attr, Lexical}),
|
||||
@@ -128,10 +128,19 @@ build(Line, File, Module, Lexical) ->
|
||||
eval_form(Line, Module, Block, Vars, E) ->
|
||||
{Value, EE} = elixir_compiler:eval_forms(Block, Vars, E),
|
||||
elixir_def_overridable:store_pending(Module),
|
||||
EC = eval_callbacks(Line, Module, before_compile, [elixir_env:linify({Line, EE})], EE),
|
||||
EV = elixir_env:linify({Line, EE#{vars := [], export_vars := nil}}),
|
||||
EC = eval_callbacks(Line, Module, before_compile, [EV], EV),
|
||||
elixir_def_overridable:store_pending(Module),
|
||||
{Value, EC}.
|
||||
|
||||
eval_callbacks(Line, Module, Name, Args, E) ->
|
||||
Callbacks = lists:reverse(ets:lookup_element(data_table(Module), Name, 2)),
|
||||
|
||||
lists:foldl(fun({M,F}, Acc) ->
|
||||
expand_callback(Line, M, F, Args, Acc#{vars := [], export_vars := nil},
|
||||
fun(AM, AF, AA) -> apply(AM, AF, AA) end)
|
||||
end, E, Callbacks).
|
||||
|
||||
%% Return the form with exports and function declarations.
|
||||
|
||||
functions_form(Line, File, Module, BaseAll, BaseExport, Def, Defmacro, BaseFunctions) ->
|
||||
@@ -147,7 +156,7 @@ functions_form(Line, File, Module, BaseAll, BaseExport, Def, Defmacro, BaseFunct
|
||||
|
||||
%% Add attributes handling to the form
|
||||
|
||||
attributes_form(Line, _File, Module, Current) ->
|
||||
attributes_form(Line, File, Module, Current) ->
|
||||
Table = data_table(Module),
|
||||
|
||||
AccAttrs = ets:lookup_element(Table, '__acc_attributes', 2),
|
||||
@@ -157,72 +166,131 @@ attributes_form(Line, _File, Module, Current) ->
|
||||
case lists:member(Key, PersistedAttrs) of
|
||||
false -> Acc;
|
||||
true ->
|
||||
Attrs = case lists:member(Key, AccAttrs) of
|
||||
true -> Value;
|
||||
false -> [Value]
|
||||
end,
|
||||
lists:foldl(fun(X, Final) -> [{attribute, Line, Key, X}|Final] end, Acc, Attrs)
|
||||
Values =
|
||||
case lists:member(Key, AccAttrs) of
|
||||
true -> Value;
|
||||
false -> [Value]
|
||||
end,
|
||||
|
||||
lists:foldl(fun(X, Final) ->
|
||||
[{attribute, Line, Key, X}|Final]
|
||||
end, Acc, process_attribute(Line, File, Key, Values))
|
||||
end
|
||||
end,
|
||||
|
||||
ets:foldl(Transform, Current, Table).
|
||||
|
||||
%% Add typedocs to the form
|
||||
typedocs_form(Module, Current) ->
|
||||
Table = docs_table(Module),
|
||||
Transform = fun({Tuple, Line, Kind, _Sig, Doc}, Acc) ->
|
||||
case Kind of
|
||||
type -> [{attribute, Line, typedoc, {Tuple, Doc}} | Acc];
|
||||
opaque -> [{attribute, Line, typedoc, {Tuple, Doc}} | Acc];
|
||||
_ -> Acc
|
||||
end
|
||||
process_attribute(Line, File, external_resource, Values) ->
|
||||
lists:usort([process_external_resource(Line, File, Value) || Value <- Values]);
|
||||
process_attribute(_Line, _File, _Key, Values) ->
|
||||
Values.
|
||||
|
||||
process_external_resource(_Line, _File, Value) when is_binary(Value) ->
|
||||
Value;
|
||||
process_external_resource(Line, File, Value) ->
|
||||
elixir_errors:handle_file_error(File,
|
||||
{Line, ?MODULE, {invalid_external_resource, Value}}).
|
||||
|
||||
%% Types
|
||||
|
||||
types_form(Module, Forms0) ->
|
||||
case code:ensure_loaded('Elixir.Kernel.Typespec') of
|
||||
{module, 'Elixir.Kernel.Typespec'} ->
|
||||
Types0 = 'Elixir.Module':get_attribute(Module, type) ++
|
||||
'Elixir.Module':get_attribute(Module, typep) ++
|
||||
'Elixir.Module':get_attribute(Module, opaque),
|
||||
|
||||
Types1 = ['Elixir.Kernel.Typespec':translate_type(Kind, Expr, Doc, Caller) ||
|
||||
{Kind, Expr, Doc, Caller} <- Types0],
|
||||
|
||||
'Elixir.Module':delete_attribute(Module, type),
|
||||
'Elixir.Module':delete_attribute(Module, typep),
|
||||
'Elixir.Module':delete_attribute(Module, opaque),
|
||||
|
||||
Forms1 = types_attributes(Types1, Forms0),
|
||||
Forms2 = export_types_attributes(Types1, Forms1),
|
||||
typedocs_attributes(Types1, Forms2);
|
||||
|
||||
{error, _} ->
|
||||
Forms0
|
||||
end.
|
||||
|
||||
types_attributes(Types, Forms) ->
|
||||
Fun = fun({{Kind, _NameArity, Expr}, Line, _Export, _Doc}, Acc) ->
|
||||
[{attribute, Line, Kind, Expr}|Acc]
|
||||
end,
|
||||
ets:foldl(Transform, Current, Table).
|
||||
lists:foldl(Fun, Forms, Types).
|
||||
|
||||
export_types_attributes(Types, Forms) ->
|
||||
Fun = fun
|
||||
({{_Kind, NameArity, _Expr}, Line, true, _Doc}, Acc) ->
|
||||
[{attribute, Line, export_type, [NameArity]}|Acc];
|
||||
({_Type, _Line, false, _Doc}, Acc) ->
|
||||
Acc
|
||||
end,
|
||||
lists:foldl(Fun, Forms, Types).
|
||||
|
||||
typedocs_attributes(Types, Forms) ->
|
||||
Fun = fun
|
||||
({{_Kind, NameArity, _Expr}, Line, true, Doc}, Acc) when Doc =/= nil ->
|
||||
[{attribute, Line, typedoc, {NameArity, Doc}}|Acc];
|
||||
({_Type, _Line, _Export, _Doc}, Acc) ->
|
||||
Acc
|
||||
end,
|
||||
lists:foldl(Fun, Forms, Types).
|
||||
|
||||
%% Specs
|
||||
|
||||
specs_form(Module, Private, Defmacro, Forms) ->
|
||||
Defmacrop = [Tuple || {Tuple, defmacrop, _, _, _} <- Private],
|
||||
case code:ensure_loaded('Elixir.Kernel.Typespec') of
|
||||
{module, 'Elixir.Kernel.Typespec'} ->
|
||||
Callbacks = 'Elixir.Module':get_attribute(Module, callback),
|
||||
Specs = [translate_spec(Spec, Defmacro, Defmacrop) ||
|
||||
Spec <- 'Elixir.Module':get_attribute(Module, spec)],
|
||||
Defmacrop = [Tuple || {Tuple, defmacrop, _, _, _} <- Private],
|
||||
|
||||
Specs0 = 'Elixir.Module':get_attribute(Module, spec) ++
|
||||
'Elixir.Module':get_attribute(Module, callback),
|
||||
|
||||
Specs1 = ['Elixir.Kernel.Typespec':translate_spec(Kind, Expr, Caller) ||
|
||||
{Kind, Expr, Caller} <- Specs0],
|
||||
Specs2 = lists:flatmap(fun(Spec) ->
|
||||
translate_macro_spec(Spec, Defmacro, Defmacrop)
|
||||
end, Specs1),
|
||||
|
||||
'Elixir.Module':delete_attribute(Module, spec),
|
||||
'Elixir.Module':delete_attribute(Module, callback),
|
||||
specs_attributes(Forms, Specs2);
|
||||
|
||||
Temp = specs_attributes(spec, Forms, Specs),
|
||||
specs_attributes(callback, Temp, Callbacks);
|
||||
{error, _} ->
|
||||
Forms
|
||||
end.
|
||||
|
||||
specs_attributes(Type, Forms, Specs) ->
|
||||
Keys = lists:foldl(fun({Tuple, Value}, Acc) ->
|
||||
lists:keystore(Tuple, 1, Acc, {Tuple, Value})
|
||||
end, [], Specs),
|
||||
lists:foldl(fun({Tuple, _}, Acc) ->
|
||||
Values = [V || {K, V} <- Specs, K == Tuple],
|
||||
{type, Line, _, _} = hd(Values),
|
||||
[{attribute, Line, Type, {Tuple, Values}}|Acc]
|
||||
end, Forms, Keys).
|
||||
specs_attributes(Forms, Specs) ->
|
||||
Dict = lists:foldl(fun({{Kind, NameArity, Spec}, Line}, Acc) ->
|
||||
dict:append({Kind, NameArity}, {Spec, Line}, Acc)
|
||||
end, dict:new(), Specs),
|
||||
dict:fold(fun({Kind, NameArity}, ExprsLines, Acc) ->
|
||||
{Exprs, Lines} = lists:unzip(ExprsLines),
|
||||
Line = lists:min(Lines),
|
||||
[{attribute, Line, Kind, {NameArity, Exprs}}|Acc]
|
||||
end, Forms, Dict).
|
||||
|
||||
translate_spec({Spec, Rest}, Defmacro, Defmacrop) ->
|
||||
case ordsets:is_element(Spec, Defmacrop) of
|
||||
true -> {Spec, Rest};
|
||||
translate_macro_spec({{spec, NameArity, Spec}, Line}, Defmacro, Defmacrop) ->
|
||||
case ordsets:is_element(NameArity, Defmacrop) of
|
||||
true -> [];
|
||||
false ->
|
||||
case ordsets:is_element(Spec, Defmacro) of
|
||||
case ordsets:is_element(NameArity, Defmacro) of
|
||||
true ->
|
||||
{Name, Arity} = Spec,
|
||||
{{elixir_utils:macro_name(Name), Arity + 1}, spec_for_macro(Rest)};
|
||||
{Name, Arity} = NameArity,
|
||||
[{{spec, {elixir_utils:macro_name(Name), Arity + 1}, spec_for_macro(Spec)}, Line}];
|
||||
false ->
|
||||
{Spec, Rest}
|
||||
[{{spec, NameArity, Spec}, Line}]
|
||||
end
|
||||
end.
|
||||
end;
|
||||
|
||||
translate_macro_spec({{callback, NameArity, Spec}, Line}, _Defmacro, _Defmacrop) ->
|
||||
[{{callback, NameArity, Spec}, Line}].
|
||||
|
||||
spec_for_macro({type, Line, 'fun', [{type, _, product, Args}|T]}) ->
|
||||
NewArgs = [{type,Line,term,[]}|Args],
|
||||
NewArgs = [{type, Line, term, []}|Args],
|
||||
{type, Line, 'fun', [{type, Line, product, NewArgs}|T]};
|
||||
|
||||
spec_for_macro(Else) -> Else.
|
||||
@@ -236,9 +304,10 @@ compile_opts(Module) ->
|
||||
end.
|
||||
|
||||
load_form(Line, Forms, Opts, #{file := File} = E) ->
|
||||
elixir_compiler:module(Forms, File, Opts, fun(Module, Binary) ->
|
||||
Env = elixir_env:linify({Line, E}),
|
||||
eval_callbacks(Line, Module, after_compile, [Env, Binary], E),
|
||||
elixir_compiler:module(Forms, File, Opts, fun(Module, Binary0) ->
|
||||
Docs = elixir_compiler:get_opt(docs),
|
||||
Binary = add_docs_chunk(Binary0, Module, Line, Docs),
|
||||
eval_callbacks(Line, Module, after_compile, [E, Binary], E),
|
||||
|
||||
case get(elixir_compiled) of
|
||||
Current when is_list(Current) ->
|
||||
@@ -258,6 +327,25 @@ load_form(Line, Forms, Opts, #{file := File} = E) ->
|
||||
Binary
|
||||
end).
|
||||
|
||||
add_docs_chunk(Bin, Module, Line, true) ->
|
||||
ChunkData = term_to_binary({elixir_docs_v1, [
|
||||
{docs, get_docs(Module)},
|
||||
{moduledoc, get_moduledoc(Line, Module)}
|
||||
]}),
|
||||
add_beam_chunk(Bin, "ExDc", ChunkData);
|
||||
|
||||
add_docs_chunk(Bin, _, _, _) -> Bin.
|
||||
|
||||
get_docs(Module) ->
|
||||
ordsets:from_list(
|
||||
[{Tuple, Line, Kind, Sig, Doc} ||
|
||||
{Tuple, Line, Kind, Sig, Doc} <- ets:tab2list(docs_table(Module)),
|
||||
Kind =/= type, Kind =/= opaque]).
|
||||
|
||||
get_moduledoc(Line, Module) ->
|
||||
{Line, 'Elixir.Module':get_attribute(Module, moduledoc)}.
|
||||
|
||||
|
||||
check_module_availability(Line, File, Module) ->
|
||||
Reserved = ['Elixir.Any', 'Elixir.BitString', 'Elixir.Function', 'Elixir.PID',
|
||||
'Elixir.Reference', 'Elixir.Elixir', 'Elixir'],
|
||||
@@ -311,7 +399,6 @@ add_info_function(Line, File, Module, Export, Def, Defmacro) ->
|
||||
true ->
|
||||
elixir_errors:form_error(Line, File, ?MODULE, {internal_function_overridden, Pair});
|
||||
false ->
|
||||
Docs = elixir_compiler:get_opt(docs),
|
||||
{
|
||||
{attribute, Line, spec, {{'__info__', 1},
|
||||
[{type, Line, 'fun', [{type, Line, product, [ {type, Line, atom, []}]}, {type, Line, term, []} ]}]
|
||||
@@ -319,8 +406,6 @@ add_info_function(Line, File, Module, Export, Def, Defmacro) ->
|
||||
{function, 0, '__info__', 1, [
|
||||
functions_clause(Def),
|
||||
macros_clause(Defmacro),
|
||||
docs_clause(Module, Docs),
|
||||
moduledoc_clause(Line, Module, Docs),
|
||||
module_clause(Module),
|
||||
else_clause()
|
||||
]}
|
||||
@@ -336,53 +421,45 @@ macros_clause(Defmacro) ->
|
||||
module_clause(Module) ->
|
||||
{clause, 0, [{atom, 0, module}], [], [{atom, 0, Module}]}.
|
||||
|
||||
docs_clause(Module, true) ->
|
||||
Docs = ordsets:from_list(
|
||||
[{Tuple, Line, Kind, Sig, Doc} ||
|
||||
{Tuple, Line, Kind, Sig, Doc} <- ets:tab2list(docs_table(Module)),
|
||||
Kind =/= type, Kind =/= opaque]),
|
||||
{clause, 0, [{atom, 0, docs}], [], [elixir_utils:elixir_to_erl(Docs)]};
|
||||
|
||||
docs_clause(_Module, _) ->
|
||||
{clause, 0, [{atom, 0, docs}], [], [{atom, 0, nil}]}.
|
||||
|
||||
moduledoc_clause(Line, Module, true) ->
|
||||
Docs = 'Elixir.Module':get_attribute(Module, moduledoc),
|
||||
{clause, 0, [{atom, 0, moduledoc}], [], [elixir_utils:elixir_to_erl({Line, Docs})]};
|
||||
|
||||
moduledoc_clause(_Line, _Module, _) ->
|
||||
{clause, 0, [{atom, 0, moduledoc}], [], [{atom, 0, nil}]}.
|
||||
|
||||
else_clause() ->
|
||||
Info = {call, 0, {atom, 0, module_info}, [{var, 0, atom}]},
|
||||
{clause, 0, [{var, 0, atom}], [], [Info]}.
|
||||
|
||||
% HELPERS
|
||||
|
||||
eval_callbacks(Line, Module, Name, Args, E) ->
|
||||
Callbacks = lists:reverse(ets:lookup_element(data_table(Module), Name, 2)),
|
||||
Meta = [{line,Line},{require,false}],
|
||||
%% Adds custom chunk to a .beam binary
|
||||
add_beam_chunk(Bin, Id, ChunkData)
|
||||
when is_binary(Bin), is_list(Id), is_binary(ChunkData) ->
|
||||
{ok, _, Chunks0} = beam_lib:all_chunks(Bin),
|
||||
NewChunk = {Id, ChunkData},
|
||||
Chunks = [NewChunk|Chunks0],
|
||||
{ok, NewBin} = beam_lib:build_module(Chunks),
|
||||
NewBin.
|
||||
|
||||
lists:foldl(fun({M,F}, Acc) ->
|
||||
{Expr, ET} = elixir_dispatch:dispatch_require(Meta, M, F, Args, Acc, fun(AM, AF, AA) ->
|
||||
apply(AM, AF, AA),
|
||||
{nil, Acc}
|
||||
end),
|
||||
%% Expands a callback given by M:F(Args). In case
|
||||
%% the callback can't be expanded, invokes the given
|
||||
%% fun passing a possibly expanded AM:AF(Args).
|
||||
expand_callback(Line, M, F, Args, E, Fun) ->
|
||||
Meta = [{line,Line},{require,false}],
|
||||
|
||||
if
|
||||
is_atom(Expr) ->
|
||||
ET;
|
||||
true ->
|
||||
try
|
||||
{_Value, _Binding, EE, _S} = elixir:eval_forms(Expr, [], ET),
|
||||
EE
|
||||
catch
|
||||
Kind:Reason ->
|
||||
Info = {M, F, length(Args), location(Line, E)},
|
||||
erlang:raise(Kind, Reason, prune_stacktrace(Info, erlang:get_stacktrace()))
|
||||
end
|
||||
end
|
||||
end, E, Callbacks).
|
||||
{EE, ET} = elixir_dispatch:dispatch_require(Meta, M, F, Args, E, fun(AM, AF, AA) ->
|
||||
Fun(AM, AF, AA),
|
||||
{ok, E}
|
||||
end),
|
||||
|
||||
if
|
||||
is_atom(EE) ->
|
||||
ET;
|
||||
true ->
|
||||
try
|
||||
{_Value, _Binding, EF, _S} = elixir:eval_forms(EE, [], ET),
|
||||
EF
|
||||
catch
|
||||
Kind:Reason ->
|
||||
Info = {M, F, length(Args), location(Line, E)},
|
||||
erlang:raise(Kind, Reason, prune_stacktrace(Info, erlang:get_stacktrace()))
|
||||
end
|
||||
end.
|
||||
|
||||
location(Line, E) ->
|
||||
[{file, elixir_utils:characters_to_list(?m(E, file))}, {line, Line}].
|
||||
@@ -401,6 +478,9 @@ prune_stacktrace(Info, []) ->
|
||||
|
||||
format_error({invalid_clause, {Name, Arity}}) ->
|
||||
io_lib:format("empty clause provided for nonexistent function or macro ~ts/~B", [Name, Arity]);
|
||||
format_error({invalid_external_resource, Value}) ->
|
||||
io_lib:format("expected a string value for @external_resource, got: ~p",
|
||||
['Elixir.Kernel':inspect(Value)]);
|
||||
format_error({unused_doc, typedoc}) ->
|
||||
"@typedoc provided but no type follows it";
|
||||
format_error({unused_doc, doc}) ->
|
||||
@@ -408,7 +488,7 @@ format_error({unused_doc, doc}) ->
|
||||
format_error({internal_function_overridden, {Name, Arity}}) ->
|
||||
io_lib:format("function ~ts/~B is internal and should not be overridden", [Name, Arity]);
|
||||
format_error({invalid_module, Module}) ->
|
||||
io_lib:format("invalid module name: ~p", [Module]);
|
||||
io_lib:format("invalid module name: ~ts", ['Elixir.Kernel':inspect(Module)]);
|
||||
format_error({module_defined, Module}) ->
|
||||
io_lib:format("redefining module ~ts", [elixir_aliases:inspect(Module)]);
|
||||
format_error({module_reserved, Module}) ->
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
Nonterminals
|
||||
grammar expr_list
|
||||
expr container_expr block_expr no_parens_expr no_parens_one_expr access_expr
|
||||
bracket_expr bracket_at_expr matched_expr unmatched_expr max_expr
|
||||
bracket_expr bracket_at_expr bracket_arg matched_expr unmatched_expr max_expr
|
||||
op_expr matched_op_expr no_parens_op_expr
|
||||
comp_op_eol at_op_eol unary_op_eol and_op_eol or_op_eol
|
||||
comp_op_eol at_op_eol unary_op_eol and_op_eol or_op_eol capture_op_eol
|
||||
add_op_eol mult_op_eol exp_op_eol two_op_eol pipe_op_eol stab_op_eol
|
||||
arrow_op_eol match_op_eol when_op_eol in_op_eol in_match_op_eol type_op_eol
|
||||
open_paren close_paren empty_paren
|
||||
@@ -29,7 +29,7 @@ Terminals
|
||||
number signed_number atom atom_safe atom_unsafe bin_string list_string sigil
|
||||
dot_call_op op_identifier
|
||||
comp_op at_op unary_op and_op or_op arrow_op match_op in_op in_match_op type_op
|
||||
dual_op add_op mult_op exp_op two_op pipe_op stab_op when_op assoc_op
|
||||
dual_op add_op mult_op exp_op two_op pipe_op stab_op when_op assoc_op capture_op
|
||||
'true' 'false' 'nil' 'do' eol ',' '.'
|
||||
'(' ')' '[' ']' '{' '}' '<<' '>>' '%{}' '%'
|
||||
.
|
||||
@@ -45,6 +45,7 @@ Expect 2.
|
||||
Left 5 do.
|
||||
Right 10 stab_op_eol. %% ->
|
||||
Left 20 ','.
|
||||
Nonassoc 30 capture_op_eol. %% &
|
||||
Left 40 in_match_op_eol. %% <-, \\ (allowed in matches along =)
|
||||
Right 50 when_op_eol. %% when
|
||||
Right 60 type_op_eol. %% ::
|
||||
@@ -60,7 +61,7 @@ Right 200 two_op_eol. %% ++, --, .., <>
|
||||
Left 210 add_op_eol. %% + (op), - (op)
|
||||
Left 220 mult_op_eol. %% * (op), / (op)
|
||||
Left 250 exp_op_eol. %% ^ (op) (e.g ^^^)
|
||||
Nonassoc 300 unary_op_eol. %% +, -, !, ^, not, &, ~~~
|
||||
Nonassoc 300 unary_op_eol. %% +, -, !, ^, not, ~~~
|
||||
Left 310 dot_call_op.
|
||||
Left 310 dot_op. %% .
|
||||
Nonassoc 320 at_op_eol. %% @
|
||||
@@ -112,6 +113,8 @@ matched_expr -> unary_op_eol matched_expr : build_unary_op('$1', '$2').
|
||||
matched_expr -> unary_op_eol no_parens_expr : build_unary_op('$1', '$2').
|
||||
matched_expr -> at_op_eol matched_expr : build_unary_op('$1', '$2').
|
||||
matched_expr -> at_op_eol no_parens_expr : build_unary_op('$1', '$2').
|
||||
matched_expr -> capture_op_eol matched_expr : build_unary_op('$1', '$2').
|
||||
matched_expr -> capture_op_eol no_parens_expr : build_unary_op('$1', '$2').
|
||||
matched_expr -> no_parens_one_expr : '$1'.
|
||||
matched_expr -> access_expr : '$1'.
|
||||
|
||||
@@ -123,6 +126,7 @@ unmatched_expr -> matched_expr op_expr : build_op(element(1, '$2'), '$1', elemen
|
||||
unmatched_expr -> unmatched_expr op_expr : build_op(element(1, '$2'), '$1', element(2, '$2')).
|
||||
unmatched_expr -> unary_op_eol expr : build_unary_op('$1', '$2').
|
||||
unmatched_expr -> at_op_eol expr : build_unary_op('$1', '$2').
|
||||
unmatched_expr -> capture_op_eol expr : build_unary_op('$1', '$2').
|
||||
unmatched_expr -> block_expr : '$1'.
|
||||
|
||||
block_expr -> parens_call call_args_parens do_block : build_identifier('$1', '$2' ++ '$3').
|
||||
@@ -189,7 +193,9 @@ no_parens_one_expr -> dot_identifier : build_identifier('$1', nil).
|
||||
%% marks identifiers followed by brackets as bracket_identifier.
|
||||
access_expr -> bracket_at_expr : '$1'.
|
||||
access_expr -> bracket_expr : '$1'.
|
||||
access_expr -> at_op_eol number : build_unary_op('$1', ?exprs('$2')).
|
||||
access_expr -> unary_op_eol number : build_unary_op('$1', ?exprs('$2')).
|
||||
access_expr -> capture_op_eol number : build_unary_op('$1', ?exprs('$2')).
|
||||
access_expr -> fn_eol stab end_eol : build_fn('$1', build_stab(reverse('$2'))).
|
||||
access_expr -> open_paren stab close_paren : build_stab(reverse('$2')).
|
||||
access_expr -> number : ?exprs('$1').
|
||||
@@ -214,12 +220,17 @@ max_expr -> parens_call call_args_parens : build_identifier('$1', '$2').
|
||||
max_expr -> parens_call call_args_parens call_args_parens : build_nested_parens('$1', '$2', '$3').
|
||||
max_expr -> dot_alias : '$1'.
|
||||
|
||||
bracket_expr -> dot_bracket_identifier list : build_access(build_identifier('$1', nil), '$2').
|
||||
bracket_expr -> access_expr list : build_access('$1', '$2').
|
||||
bracket_arg -> open_bracket ']' : build_list('$1', []).
|
||||
bracket_arg -> open_bracket kw close_bracket : build_list('$1', '$2').
|
||||
bracket_arg -> open_bracket container_expr close_bracket : build_list('$1', '$2').
|
||||
bracket_arg -> open_bracket container_expr ',' close_bracket : build_list('$1', '$2').
|
||||
|
||||
bracket_at_expr -> at_op_eol dot_bracket_identifier list :
|
||||
bracket_expr -> dot_bracket_identifier bracket_arg : build_access(build_identifier('$1', nil), '$2').
|
||||
bracket_expr -> access_expr bracket_arg : build_access('$1', '$2').
|
||||
|
||||
bracket_at_expr -> at_op_eol dot_bracket_identifier bracket_arg :
|
||||
build_access(build_unary_op('$1', build_identifier('$2', nil)), '$3').
|
||||
bracket_at_expr -> at_op_eol access_expr list :
|
||||
bracket_at_expr -> at_op_eol access_expr bracket_arg :
|
||||
build_access(build_unary_op('$1', '$2'), '$3').
|
||||
|
||||
%% Blocks
|
||||
@@ -308,6 +319,9 @@ two_op_eol -> two_op eol : '$1'.
|
||||
pipe_op_eol -> pipe_op : '$1'.
|
||||
pipe_op_eol -> pipe_op eol : '$1'.
|
||||
|
||||
capture_op_eol -> capture_op : '$1'.
|
||||
capture_op_eol -> capture_op eol : '$1'.
|
||||
|
||||
unary_op_eol -> unary_op : '$1'.
|
||||
unary_op_eol -> unary_op eol : '$1'.
|
||||
unary_op_eol -> dual_op : '$1'.
|
||||
@@ -527,12 +541,6 @@ meta(Node) -> meta(?line(Node)).
|
||||
|
||||
%% Operators
|
||||
|
||||
build_op({_Kind, Line, '/'}, {'&', _, [{Kind, _, Atom} = Left]}, Right) when is_number(Right), is_atom(Atom), is_atom(Kind) ->
|
||||
{'&', meta(Line), [{'/', meta(Line), [Left, Right]}]};
|
||||
|
||||
build_op({_Kind, Line, '/'}, {'&', _, [{{'.', _, [_, _]}, _, []} = Left]}, Right) when is_number(Right) ->
|
||||
{'&', meta(Line), [{'/', meta(Line), [Left, Right]}]};
|
||||
|
||||
build_op({_Kind, Line, 'in'}, {UOp, _, [Left]}, Right) when ?rearrange_uop(UOp) ->
|
||||
{UOp, meta(Line), [{'in', meta(Line), [Left, Right]}]};
|
||||
|
||||
@@ -615,7 +623,7 @@ build_fn(Op, Stab) ->
|
||||
|
||||
build_access(Expr, {List, Line}) ->
|
||||
Meta = meta(Line),
|
||||
{{'.', Meta, ['Elixir.Kernel', access]}, Meta, [Expr, List]}.
|
||||
{{'.', Meta, ['Elixir.Access', get]}, Meta, [Expr, List]}.
|
||||
|
||||
%% Interpolation aware
|
||||
|
||||
|
||||
@@ -27,29 +27,27 @@ translate_var(Meta, Name, Kind, S) when is_atom(Kind); is_integer(Kind) ->
|
||||
true ->
|
||||
{{var, Line, Current}, S};
|
||||
false ->
|
||||
%% If the variable is not exported, we use a counter name.
|
||||
%% The same if the variable already exists or we are in a
|
||||
%% noname context.
|
||||
Private = (lists:keyfind(export, 1, Meta) == {export, false}),
|
||||
|
||||
%% We attempt to give vars a nice name because we
|
||||
%% still use the unused vars warnings from erl_lint.
|
||||
%%
|
||||
%% Once we move the warning to Elixir compiler, we
|
||||
%% can name vars as _@COUNTER.
|
||||
{NewVar, Counter, NS} =
|
||||
if
|
||||
Kind /= nil ->
|
||||
build_var('_', S);
|
||||
Private orelse Exists orelse S#elixir_scope.noname ->
|
||||
build_var(Name, S);
|
||||
true ->
|
||||
{Name, 0, S}
|
||||
build_var(Name, S)
|
||||
end,
|
||||
|
||||
FS = NS#elixir_scope{
|
||||
vars=orddict:store(Tuple, {NewVar, Counter}, Vars),
|
||||
match_vars=ordsets:add_element(Tuple, MatchVars),
|
||||
export_vars=case S#elixir_scope.export_vars of
|
||||
EV when Private; EV == nil -> EV;
|
||||
EV -> orddict:store(Tuple, {NewVar, Counter}, EV)
|
||||
nil -> nil;
|
||||
EV -> orddict:store(Tuple, {NewVar, Counter}, EV)
|
||||
end
|
||||
},
|
||||
},
|
||||
|
||||
{{var, Line, NewVar}, FS}
|
||||
end;
|
||||
|
||||
@@ -6,8 +6,10 @@
|
||||
-define(at_op(T),
|
||||
T == $@).
|
||||
|
||||
-define(capture_op(T),
|
||||
T == $&).
|
||||
|
||||
-define(unary_op(T),
|
||||
T == $&;
|
||||
T == $!;
|
||||
T == $^).
|
||||
|
||||
@@ -294,7 +296,7 @@ tokenize([$:,T1,T2|Rest], Line, Scope, Tokens) when
|
||||
|
||||
% ## Single Token Operators
|
||||
tokenize([$:,T|Rest], Line, Scope, Tokens) when
|
||||
?at_op(T); ?unary_op(T); ?dual_op(T); ?mult_op(T); ?comp_op(T);
|
||||
?at_op(T); ?unary_op(T); ?capture_op(T); ?dual_op(T); ?mult_op(T); ?comp_op(T);
|
||||
?match_op(T); ?pipe_op(T); T == $. ->
|
||||
tokenize(Rest, Line, Scope, [{atom, Line, list_to_atom([T])}|Tokens]);
|
||||
|
||||
@@ -386,6 +388,9 @@ tokenize([T1,T2|Rest], Line, Scope, Tokens) when ?stab_op(T1, T2) ->
|
||||
tokenize([T|Rest], Line, Scope, Tokens) when ?at_op(T) ->
|
||||
handle_unary_op(Rest, Line, at_op, list_to_atom([T]), Scope, Tokens);
|
||||
|
||||
tokenize([T|Rest], Line, Scope, Tokens) when ?capture_op(T) ->
|
||||
handle_unary_op(Rest, Line, capture_op, list_to_atom([T]), Scope, Tokens);
|
||||
|
||||
tokenize([T|Rest], Line, Scope, Tokens) when ?unary_op(T) ->
|
||||
handle_unary_op(Rest, Line, unary_op, list_to_atom([T]), Scope, Tokens);
|
||||
|
||||
@@ -549,7 +554,7 @@ handle_dot([$.,T1,T2|Rest], Line, Scope, Tokens) when
|
||||
|
||||
% ## Single Token Operators
|
||||
handle_dot([$.,T|Rest], Line, Scope, Tokens) when
|
||||
?at_op(T); ?unary_op(T); ?dual_op(T); ?mult_op(T); ?comp_op(T);
|
||||
?at_op(T); ?unary_op(T); ?capture_op(T); ?dual_op(T); ?mult_op(T); ?comp_op(T);
|
||||
?match_op(T); ?pipe_op(T); T == $% ->
|
||||
handle_call_identifier(Rest, Line, list_to_atom([T]), Scope, Tokens);
|
||||
|
||||
@@ -709,11 +714,13 @@ tokenize_number([$_,H|T], Acc, Bool) when ?is_digit(H) ->
|
||||
tokenize_number(T, [H|Acc], Bool);
|
||||
|
||||
%% Check if we have e- followed by numbers (valid only for floats);
|
||||
tokenize_number([$e,S,H|T], Acc, true) when ?is_digit(H), S == $+ orelse S == $- ->
|
||||
tokenize_number([E,S,H|T], Acc, true)
|
||||
when (E == $E) or (E == $e), ?is_digit(H), S == $+ orelse S == $- ->
|
||||
tokenize_number(T, [H,S,$e|Acc], true);
|
||||
|
||||
%% Check if we have e followed by numbers (valid only for floats);
|
||||
tokenize_number([$e,H|T], Acc, true) when ?is_digit(H) ->
|
||||
tokenize_number([E,H|T], Acc, true)
|
||||
when (E == $E) or (E == $e), ?is_digit(H) ->
|
||||
tokenize_number(T, [H,$e|Acc], true);
|
||||
|
||||
%% Finally just numbers.
|
||||
|
||||
@@ -70,24 +70,38 @@ translate({'&', Meta, [Arg]}, S) when is_integer(Arg) ->
|
||||
translate({fn, Meta, Clauses}, S) ->
|
||||
elixir_fn:translate(Meta, Clauses, S);
|
||||
|
||||
%% Cond
|
||||
|
||||
translate({'cond', _Meta, [[{do, Pairs}]]}, S) ->
|
||||
[{'->', Meta, [[Condition], Body]}|T] = lists:reverse(Pairs),
|
||||
Case =
|
||||
case Condition of
|
||||
{'_', _, Atom} when is_atom(Atom) ->
|
||||
compile_error(Meta, S#elixir_scope.file, "unbound variable _ inside cond. "
|
||||
"If you want the last clause to always match, you probably meant to use: true ->");
|
||||
X when is_atom(X) and (X /= false) and (X /= nil) ->
|
||||
build_cond_clauses(T, Body, Meta);
|
||||
_ ->
|
||||
{Truthy, Other} = build_truthy_clause(Meta, Condition, Body),
|
||||
Error = {{'.', Meta, [erlang, error]}, [], [cond_clause]},
|
||||
Falsy = {'->', Meta, [[Other], Error]},
|
||||
Acc = {'case', Meta, [Condition, [{do, [Truthy, Falsy]}]]},
|
||||
build_cond_clauses(T, Acc, Meta)
|
||||
end,
|
||||
translate(Case, S);
|
||||
|
||||
%% Case
|
||||
|
||||
translate({'case', Meta, [Expr, KV]}, #elixir_scope{return=Return} = RS) when is_list(KV) ->
|
||||
S = RS#elixir_scope{noname=true, return=true},
|
||||
Clauses = elixir_clauses:get_pairs(do, KV),
|
||||
translate({'case', Meta, [Expr, KV]}, #elixir_scope{return=Return} = RS) ->
|
||||
S = RS#elixir_scope{return=true},
|
||||
Clauses = elixir_clauses:get_pairs(do, KV, match),
|
||||
{TExpr, NS} = translate(Expr, S),
|
||||
|
||||
RClauses = case elixir_utils:returns_boolean(TExpr) of
|
||||
true -> rewrite_case_clauses(Clauses);
|
||||
false -> Clauses
|
||||
end,
|
||||
|
||||
{TClauses, TS} = elixir_clauses:clauses(Meta, RClauses, Return, NS),
|
||||
{TClauses, TS} = elixir_clauses:clauses(Meta, Clauses, Return, NS),
|
||||
{{'case', ?line(Meta), TExpr, TClauses}, TS};
|
||||
|
||||
%% Try
|
||||
|
||||
translate({'try', Meta, [Clauses]}, #elixir_scope{return=Return} = RS) when is_list(Clauses) ->
|
||||
translate({'try', Meta, [Clauses]}, #elixir_scope{return=Return} = RS) ->
|
||||
S = RS#elixir_scope{noname=true, return=true},
|
||||
Do = proplists:get_value('do', Clauses, nil),
|
||||
{TDo, SB} = elixir_translator:translate(Do, S),
|
||||
@@ -103,7 +117,7 @@ translate({'try', Meta, [Clauses]}, #elixir_scope{return=Return} = RS) when is_l
|
||||
{TAfter, SA} = {[], mergec(S, SC)}
|
||||
end,
|
||||
|
||||
Else = elixir_clauses:get_pairs(else, Clauses),
|
||||
Else = elixir_clauses:get_pairs(else, Clauses, match),
|
||||
{TElse, SE} = elixir_clauses:clauses(Meta, Else, Return, mergec(S, SA)),
|
||||
|
||||
SF = (mergec(S, SE))#elixir_scope{noname=RS#elixir_scope.noname},
|
||||
@@ -111,16 +125,16 @@ translate({'try', Meta, [Clauses]}, #elixir_scope{return=Return} = RS) when is_l
|
||||
|
||||
%% Receive
|
||||
|
||||
translate({'receive', Meta, [KV]}, #elixir_scope{return=Return} = RS) when is_list(KV) ->
|
||||
translate({'receive', Meta, [KV]}, #elixir_scope{return=Return} = RS) ->
|
||||
S = RS#elixir_scope{return=true},
|
||||
Do = elixir_clauses:get_pairs(do, KV, true),
|
||||
Do = elixir_clauses:get_pairs(do, KV, match, true),
|
||||
|
||||
case lists:keyfind('after', 1, KV) of
|
||||
false ->
|
||||
{TClauses, SC} = elixir_clauses:clauses(Meta, Do, Return, S),
|
||||
{{'receive', ?line(Meta), TClauses}, SC};
|
||||
_ ->
|
||||
After = elixir_clauses:get_pairs('after', KV),
|
||||
After = elixir_clauses:get_pairs('after', KV, expr),
|
||||
{TClauses, SC} = elixir_clauses:clauses(Meta, Do ++ After, Return, S),
|
||||
{FClauses, TAfter} = elixir_utils:split_last(TClauses),
|
||||
{_, _, [FExpr], _, FAfter} = TAfter,
|
||||
@@ -129,7 +143,7 @@ translate({'receive', Meta, [KV]}, #elixir_scope{return=Return} = RS) when is_li
|
||||
|
||||
%% Comprehensions
|
||||
|
||||
translate({for, Meta, Args}, S) when is_list(Args) ->
|
||||
translate({for, Meta, [_|_] = Args}, S) ->
|
||||
elixir_for:translate(Meta, Args, S);
|
||||
|
||||
%% Super
|
||||
@@ -154,7 +168,7 @@ translate({super, Meta, Args}, S) when is_list(Args) ->
|
||||
|
||||
%% Variables
|
||||
|
||||
translate({'^', Meta, [ {Name, VarMeta, Kind} ]}, #elixir_scope{context=match} = S) when is_atom(Name), is_atom(Kind) ->
|
||||
translate({'^', Meta, [{Name, VarMeta, Kind}]}, #elixir_scope{context=match} = S) when is_atom(Name), is_atom(Kind) ->
|
||||
Tuple = {Name, var_kind(VarMeta, Kind)},
|
||||
case orddict:find(Tuple, S#elixir_scope.backup_vars) of
|
||||
{ok, {Value, _Counter}} ->
|
||||
@@ -316,24 +330,9 @@ var_kind(Meta, Kind) ->
|
||||
false -> Kind
|
||||
end.
|
||||
|
||||
%% Case
|
||||
|
||||
rewrite_case_clauses([
|
||||
{do,Meta1,[{'when',_,[{V,M,C},{'__op__',_,['orelse',_,_]}]}],False},
|
||||
{do,Meta2,[{'_',_,UC}],True}] = Clauses)
|
||||
when is_atom(V), is_list(M), is_atom(C), is_atom(UC) ->
|
||||
case lists:keyfind('cond', 1, M) of
|
||||
{'cond',true} ->
|
||||
[{do,Meta1,[false],False},{do,Meta2,[true],True}];
|
||||
_ ->
|
||||
Clauses
|
||||
end;
|
||||
rewrite_case_clauses(Clauses) ->
|
||||
Clauses.
|
||||
|
||||
%% Pack a list of expressions from a block.
|
||||
unblock({'block', _, Exprs}) -> Exprs;
|
||||
unblock(Expr) -> [Expr].
|
||||
unblock(Expr) -> [Expr].
|
||||
|
||||
%% Translate args
|
||||
|
||||
@@ -367,15 +366,43 @@ translate_block(Expr, Return, S) ->
|
||||
false -> translate(Expr, S)
|
||||
end.
|
||||
|
||||
%% Expressions that can handle no return may receive
|
||||
%% return=false but must always return return=true.
|
||||
%% return is typically true, except when we find one
|
||||
%% of the expressions below, which may handle return=false
|
||||
%% but must always return return=true.
|
||||
handles_no_return({'try', _, [_]}) -> true;
|
||||
handles_no_return({'cond', _, [_]}) -> true;
|
||||
handles_no_return({'for', _, [_|_]}) -> true;
|
||||
handles_no_return({'case', _, [_, _]}) -> true;
|
||||
handles_no_return({'receive', _, [_]}) -> true;
|
||||
handles_no_return({'__block__', _, [_|_]}) -> true;
|
||||
handles_no_return(_) -> false.
|
||||
|
||||
%% Cond
|
||||
|
||||
build_cond_clauses([{'->', NewMeta, [[Condition], Body]}|T], Acc, OldMeta) ->
|
||||
{Truthy, Other} = build_truthy_clause(NewMeta, Condition, Body),
|
||||
Falsy = {'->', OldMeta, [[Other], Acc]},
|
||||
Case = {'case', NewMeta, [Condition, [{do, [Truthy, Falsy]}]]},
|
||||
build_cond_clauses(T, Case, NewMeta);
|
||||
build_cond_clauses([], Acc, _) ->
|
||||
Acc.
|
||||
|
||||
build_truthy_clause(Meta, Condition, Body) ->
|
||||
case elixir_utils:returns_boolean(Condition) of
|
||||
true ->
|
||||
{{'->', Meta, [[true], Body]}, false};
|
||||
false ->
|
||||
Var = {'cond', [], 'Elixir'},
|
||||
Head = {'when', [], [Var,
|
||||
{'__op__', [], [
|
||||
'andalso',
|
||||
{{'.', [], [erlang, '/=']}, [], [Var, nil]},
|
||||
{{'.', [], [erlang, '/=']}, [], [Var, false]}
|
||||
]}
|
||||
]},
|
||||
{{'->', Meta, [[Head], Body]}, {'_', [], nil}}
|
||||
end.
|
||||
|
||||
%% Assertions
|
||||
|
||||
assert_module_scope(Meta, Kind, #elixir_scope{module=nil,file=File}) ->
|
||||
|
||||
@@ -3,8 +3,8 @@
|
||||
-include("elixir.hrl").
|
||||
|
||||
clauses(_Meta, Clauses, Return, S) ->
|
||||
Catch = elixir_clauses:get_pairs('catch', Clauses),
|
||||
Rescue = elixir_clauses:get_pairs(rescue, Clauses),
|
||||
Catch = elixir_clauses:get_pairs('catch', Clauses, 'catch'),
|
||||
Rescue = elixir_clauses:get_pairs(rescue, Clauses, rescue),
|
||||
reduce_clauses(Rescue ++ Catch, [], S, Return, S).
|
||||
|
||||
reduce_clauses([H|T], Acc, SAcc, Return, S) ->
|
||||
@@ -18,10 +18,7 @@ each_clause({'catch', Meta, Raw, Expr}, Return, S) ->
|
||||
|
||||
Final = case Args of
|
||||
[X] -> [throw, X, {'_', Meta, nil}];
|
||||
[X,Y] -> [X, Y, {'_', Meta, nil}];
|
||||
_ ->
|
||||
elixir_errors:compile_error(Meta, S#elixir_scope.file,
|
||||
"too many arguments given for catch")
|
||||
[X,Y] -> [X, Y, {'_', Meta, nil}]
|
||||
end,
|
||||
|
||||
Condition = [{'{}', Meta, Final}],
|
||||
@@ -103,27 +100,17 @@ rescue_guards(Meta, Var, Aliases, S) ->
|
||||
%% Matching of variables is done with Erlang exceptions is done in
|
||||
%% function for optimization.
|
||||
|
||||
rescue_each_ref(Meta, Var, [H|T], Elixir, Erlang, S) when
|
||||
H == 'Elixir.UndefinedFunctionError'; H == 'Elixir.ErlangError';
|
||||
H == 'Elixir.ArgumentError'; H == 'Elixir.ArithmeticError';
|
||||
H == 'Elixir.BadArityError'; H == 'Elixir.BadFunctionError';
|
||||
H == 'Elixir.MatchError'; H == 'Elixir.CaseClauseError';
|
||||
H == 'Elixir.TryClauseError'; H == 'Elixir.FunctionClauseError';
|
||||
H == 'Elixir.SystemLimitError'; H == 'Elixir.BadStructError' ->
|
||||
Expr = erl_rescue_guard_for(Meta, Var, H),
|
||||
rescue_each_ref(Meta, Var, T, [H|Elixir], [Expr|Erlang], S);
|
||||
|
||||
rescue_each_ref(Meta, Var, [H|T], Elixir, Erlang, S) when is_atom(H) ->
|
||||
rescue_each_ref(Meta, Var, T, [H|Elixir], Erlang, S);
|
||||
case erl_rescue_guard_for(Meta, Var, H) of
|
||||
false -> rescue_each_ref(Meta, Var, T, [H|Elixir], Erlang, S);
|
||||
Expr -> rescue_each_ref(Meta, Var, T, [H|Elixir], [Expr|Erlang], S)
|
||||
end;
|
||||
|
||||
rescue_each_ref(_, _, [], Elixir, Erlang, _) ->
|
||||
{Elixir, Erlang}.
|
||||
|
||||
%% Handle erlang rescue matches.
|
||||
|
||||
erl_rescue_guard_for(Meta, Var, List) when is_list(List) ->
|
||||
join(Meta, fun erl_or/3, [erl_rescue_guard_for(Meta, Var, X) || X <- List]);
|
||||
|
||||
erl_rescue_guard_for(Meta, Var, 'Elixir.UndefinedFunctionError') ->
|
||||
{erl(Meta, '=='), Meta, [Var, undef]};
|
||||
|
||||
@@ -136,6 +123,9 @@ erl_rescue_guard_for(Meta, Var, 'Elixir.SystemLimitError') ->
|
||||
erl_rescue_guard_for(Meta, Var, 'Elixir.ArithmeticError') ->
|
||||
{erl(Meta, '=='), Meta, [Var, badarith]};
|
||||
|
||||
erl_rescue_guard_for(Meta, Var, 'Elixir.CondClauseError') ->
|
||||
{erl(Meta, '=='), Meta, [Var, cond_clause]};
|
||||
|
||||
erl_rescue_guard_for(Meta, Var, 'Elixir.BadArityError') ->
|
||||
erl_and(Meta,
|
||||
erl_tuple_size(Meta, Var, 2),
|
||||
@@ -178,7 +168,10 @@ erl_rescue_guard_for(Meta, Var, 'Elixir.ErlangError') ->
|
||||
IsException = {erl(Meta, '/='), Meta, [
|
||||
{erl(Meta, element), Meta, [2, Var]}, '__exception__'
|
||||
]},
|
||||
erl_or(Meta, IsNotTuple, IsException).
|
||||
erl_or(Meta, IsNotTuple, IsException);
|
||||
|
||||
erl_rescue_guard_for(_, _, _) ->
|
||||
false.
|
||||
|
||||
%% Helpers
|
||||
|
||||
@@ -191,9 +184,6 @@ erl_record_compare(Meta, Var, Expr) ->
|
||||
Expr
|
||||
]}.
|
||||
|
||||
join(Meta, Kind, [H|T]) ->
|
||||
lists:foldl(fun(X, Acc) -> Kind(Meta, Acc, X) end, H, T).
|
||||
|
||||
prepend_to_block(_Meta, Expr, {'__block__', Meta, Args}) ->
|
||||
{'__block__', Meta, [Expr|Args]};
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
-export([elixir_to_erl/1, get_line/1, split_last/1,
|
||||
characters_to_list/1, characters_to_binary/1, macro_name/1,
|
||||
convert_to_boolean/4, returns_boolean/1, atom_concat/1,
|
||||
file_type/1, file_type/2, relative_to_cwd/1, erl_call/4]).
|
||||
read_file_type/1, read_link_type/1, relative_to_cwd/1, erl_call/4]).
|
||||
-include("elixir.hrl").
|
||||
-include_lib("kernel/include/file.hrl").
|
||||
|
||||
@@ -31,11 +31,14 @@ split_last(List) -> split_last(List, []).
|
||||
split_last([H], Acc) -> {lists:reverse(Acc), H};
|
||||
split_last([H|T], Acc) -> split_last(T, [H|Acc]).
|
||||
|
||||
file_type(File) ->
|
||||
file_type(File, read_link_info).
|
||||
read_file_type(File) ->
|
||||
case file:read_file_info(File) of
|
||||
{ok, #file_info{type=Type}} -> {ok, Type};
|
||||
{error, _} = Error -> Error
|
||||
end.
|
||||
|
||||
file_type(File, Op) ->
|
||||
case file:Op(File) of
|
||||
read_link_type(File) ->
|
||||
case file:read_link_info(File) of
|
||||
{ok, #file_info{type=Type}} -> {ok, Type};
|
||||
{error, _} = Error -> Error
|
||||
end.
|
||||
@@ -126,36 +129,43 @@ elixir_to_erl_cons_2([], Acc) ->
|
||||
|
||||
%% Boolean checks
|
||||
|
||||
returns_boolean({op, _, Op, _}) when Op == 'not' -> true;
|
||||
returns_boolean(Bool) when is_boolean(Bool) -> true;
|
||||
|
||||
returns_boolean({op, _, Op, _, _}) when
|
||||
returns_boolean({{'.', _, [erlang, Op]}, _, [_]}) when Op == 'not' -> true;
|
||||
|
||||
returns_boolean({{'.', _, [erlang, Op]}, _, [_, _]}) when
|
||||
Op == 'and'; Op == 'or'; Op == 'xor';
|
||||
Op == '=='; Op == '/='; Op == '=<'; Op == '>=';
|
||||
Op == '<'; Op == '>'; Op == '=:='; Op == '=/=' -> true;
|
||||
|
||||
returns_boolean({op, _, Op, _, Right}) when Op == 'andalso'; Op == 'orelse' ->
|
||||
returns_boolean({'__op__', _, [Op, _, Right]}) when Op == 'andalso'; Op == 'orelse' ->
|
||||
returns_boolean(Right);
|
||||
|
||||
returns_boolean({call, _, {remote, _, {atom, _, erlang}, {atom, _, Fun}}, [_]}) when
|
||||
returns_boolean({{'.', _, [erlang, Fun]}, _, [_]}) when
|
||||
Fun == is_atom; Fun == is_binary; Fun == is_bitstring; Fun == is_boolean;
|
||||
Fun == is_float; Fun == is_function; Fun == is_integer; Fun == is_list;
|
||||
Fun == is_number; Fun == is_pid; Fun == is_port; Fun == is_reference;
|
||||
Fun == is_tuple -> true;
|
||||
|
||||
returns_boolean({call, _, {remote, _, {atom, _, erlang}, {atom, _, Fun}}, [_,_]}) when
|
||||
returns_boolean({{'.', _, [erlang, Fun]}, _, [_, _]}) when
|
||||
Fun == is_function -> true;
|
||||
|
||||
returns_boolean({call, _, {remote, _, {atom, _, erlang}, {atom, _, Fun}}, [_,_,_]}) when
|
||||
returns_boolean({{'.', _, [erlang, Fun]}, _, [_, _, _]}) when
|
||||
Fun == function_exported -> true;
|
||||
|
||||
returns_boolean({atom, _, Bool}) when is_boolean(Bool) -> true;
|
||||
|
||||
returns_boolean({'case', _, _, Clauses}) ->
|
||||
returns_boolean({'case', _, [_, [{do, Clauses}]]}) ->
|
||||
lists:all(fun
|
||||
({clause,_,_,_,[Expr]}) -> returns_boolean(Expr);
|
||||
(_) -> false
|
||||
({'->',_,[_, Expr]}) -> returns_boolean(Expr)
|
||||
end, Clauses);
|
||||
|
||||
returns_boolean({'cond', _, [[{do, Clauses}]]}) ->
|
||||
lists:all(fun
|
||||
({'->',_,[_, Expr]}) -> returns_boolean(Expr)
|
||||
end, Clauses);
|
||||
|
||||
returns_boolean({'__block__', [], Exprs}) ->
|
||||
returns_boolean(lists:last(Exprs));
|
||||
|
||||
returns_boolean(_) -> false.
|
||||
|
||||
convert_to_boolean(Line, Expr, Bool, S) when is_integer(Line) ->
|
||||
|
||||
@@ -17,30 +17,40 @@ defmodule AccessTest do
|
||||
@mod -13
|
||||
-13 = @mod
|
||||
|
||||
test :nil do
|
||||
test "for nil" do
|
||||
assert nil[:foo] == nil
|
||||
assert Access.get(nil, :foo) == nil
|
||||
assert Access.get_and_update(nil, :foo, fn nil -> {:ok, :bar} end) == {:ok, :bar}
|
||||
end
|
||||
|
||||
test :list do
|
||||
test "for keywords" do
|
||||
assert [foo: :bar][:foo] == :bar
|
||||
assert [foo: [bar: :baz]][:foo][:bar] == :baz
|
||||
assert [foo: [bar: :baz]][:fuu][:bar] == nil
|
||||
|
||||
assert Access.get([foo: :bar], :foo) == :bar
|
||||
assert Access.get_and_update([], :foo, fn nil -> {:ok, :baz} end) == {:ok, [foo: :baz]}
|
||||
assert Access.get_and_update([foo: :bar], :foo, fn :bar -> {:ok, :baz} end) == {:ok, [foo: :baz]}
|
||||
end
|
||||
|
||||
test :map do
|
||||
test "for maps" do
|
||||
assert %{foo: :bar}[:foo] == :bar
|
||||
assert %{1 => 1}[1] == 1
|
||||
assert %{1.0 => 1.0}[1.0] == 1.0
|
||||
assert %{1 => 1}[1.0] == nil
|
||||
|
||||
assert Access.get(%{foo: :bar}, :foo) == :bar
|
||||
assert Access.get_and_update(%{}, :foo, fn nil -> {:ok, :baz} end) == {:ok, %{foo: :baz}}
|
||||
assert Access.get_and_update(%{foo: :bar}, :foo, fn :bar -> {:ok, :baz} end) == {:ok, %{foo: :baz}}
|
||||
end
|
||||
|
||||
test :atom do
|
||||
exception = assert_raise RuntimeError, fn ->
|
||||
foo = :foo
|
||||
foo[:atom]
|
||||
test "for atoms" do
|
||||
assert_raise Protocol.UndefinedError, ~r"protocol Access not implemented for :foo", fn ->
|
||||
Access.get(:foo, :bar)
|
||||
end
|
||||
|
||||
assert_raise Protocol.UndefinedError, ~r"protocol Access not implemented for :foo", fn ->
|
||||
Access.get_and_update(:foo, :bar, fn _ -> {:ok, :baz} end)
|
||||
end
|
||||
assert Exception.message(exception) ==
|
||||
"The access protocol can only be invoked for atoms " <>
|
||||
"at compilation time, tried to invoke it for :foo"
|
||||
end
|
||||
end
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user