Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7a24767137 | ||
|
|
ad0818da9b | ||
|
|
c0debf66ae | ||
|
|
e15c8545c3 | ||
|
|
29313e8dd5 | ||
|
|
b0032e8df5 | ||
|
|
698d1f6440 | ||
|
|
609fca162c | ||
|
|
23b0c8c5b1 | ||
|
|
eb7c5415db | ||
|
|
80b31833a4 | ||
|
|
d5f35d4905 | ||
|
|
0908e55971 | ||
|
|
164d1d0049 | ||
|
|
e8c53a8e72 | ||
|
|
e7cd2511cf | ||
|
|
7a977307c2 | ||
|
|
0eccb76930 | ||
|
|
dad0f3fb5a | ||
|
|
7ca57afbba | ||
|
|
0c6c72e8f6 | ||
|
|
41c8f83c6b | ||
|
|
1f61ba454c | ||
|
|
55d0f568da | ||
|
|
8bfe32448e | ||
|
|
19ad387943 | ||
|
|
8010e44bd4 | ||
|
|
8b203a8379 | ||
|
|
de26484bda | ||
|
|
cbef7e9629 | ||
|
|
a184b4061e | ||
|
|
f3e26c28b0 | ||
|
|
b5ad462f27 | ||
|
|
a9b2a95011 | ||
|
|
86bafe865c | ||
|
|
fc5050d122 | ||
|
|
011ad07d7d | ||
|
|
e9d13d5858 | ||
|
|
25a5dbc08b | ||
|
|
b76387a394 | ||
|
|
64c5acb923 | ||
|
|
9ed4d91f94 | ||
|
|
45df7c4ae4 | ||
|
|
73eb206c86 | ||
|
|
d08e2bb2fd | ||
|
|
9bc8925018 | ||
|
|
30d7114922 | ||
|
|
1e5633c4a1 | ||
|
|
6d44180ec2 | ||
|
|
c2ceeb9ea4 | ||
|
|
b0a5197f42 | ||
|
|
235c4d61b9 | ||
|
|
c5ffa76663 | ||
|
|
31e639f34a | ||
|
|
f1effcb485 | ||
|
|
237c485411 | ||
|
|
78ba84612c | ||
|
|
f8da2fb9c5 | ||
|
|
7665a50a2d | ||
|
|
dfb8f38ce3 | ||
|
|
cd99add62d | ||
|
|
615a54e8bf | ||
|
|
ad00d0ad6d | ||
|
|
5b53459f86 | ||
|
|
82238a8d20 | ||
|
|
66e70ac72f | ||
|
|
2d48d2ed5b | ||
|
|
8b54e28086 | ||
|
|
b2004b8e86 | ||
|
|
52ad74cdfc | ||
|
|
5f83cf9bb3 | ||
|
|
b2608e4fe7 | ||
|
|
aed52e44d1 | ||
|
|
91497b9f15 | ||
|
|
a3d85c0bad | ||
|
|
839afc4f3e | ||
|
|
abc293bff8 | ||
|
|
cbdfe4a14a | ||
|
|
46b0f7b29e | ||
|
|
52ebeb1e7d | ||
|
|
8797280b9e | ||
|
|
8a8b641271 | ||
|
|
e67ada5cfb | ||
|
|
07d7d538dc | ||
|
|
939af3b459 | ||
|
|
da784ebc1a | ||
|
|
b670450b4c | ||
|
|
03b1c70433 | ||
|
|
e04bab1d5e | ||
|
|
a97e1fabce | ||
|
|
c86ecfdf7e | ||
|
|
a90580b0d1 | ||
|
|
60e663f26c | ||
|
|
bf81156dbc | ||
|
|
a67fe46ca5 | ||
|
|
043f6c74b1 | ||
|
|
2b1f561d91 | ||
|
|
87cd24651e | ||
|
|
61f28c796a | ||
|
|
1555b79121 | ||
|
|
452a33b085 | ||
|
|
2d3a3b1ca5 | ||
|
|
ba40cb8a3e | ||
|
|
917a8ecd99 | ||
|
|
5539ad4536 | ||
|
|
a5fb3c9e0b | ||
|
|
14ead54ac6 | ||
|
|
c74a7781a8 | ||
|
|
fe66565765 | ||
|
|
0966aea8a5 | ||
|
|
ea6272f59e | ||
|
|
9d3b7bb4d2 | ||
|
|
39e5581105 | ||
|
|
3b8ac0194b | ||
|
|
21f2f0f112 | ||
|
|
92ccf4bf1d | ||
|
|
fe042c5463 | ||
|
|
910810360d | ||
|
|
90378ab53f | ||
|
|
3c6e63a962 | ||
|
|
27a787e531 | ||
|
|
f75666515a | ||
|
|
02a1538a3c | ||
|
|
b6991100f5 | ||
|
|
fdf53787cf | ||
|
|
c435dbe38d | ||
|
|
e5c13e6dd2 | ||
|
|
b1abcdcc41 | ||
|
|
d53a6a3491 | ||
|
|
37d05da0ac | ||
|
|
9407c7eedb | ||
|
|
dc0fd7675b | ||
|
|
9c0258b605 | ||
|
|
bb1dc00de4 | ||
|
|
be0564b536 | ||
|
|
97d4c02a8f | ||
|
|
4049a79b28 | ||
|
|
d7df6d20a7 | ||
|
|
1679f6b4d7 | ||
|
|
9caab33613 | ||
|
|
cae8f0933b | ||
|
|
92c3bddf7b | ||
|
|
a49c539a9d | ||
|
|
68d1bd9308 | ||
|
|
7f8a52c264 | ||
|
|
565a7656c4 | ||
|
|
dc768eb501 | ||
|
|
ee78e10089 | ||
|
|
6a5348b780 | ||
|
|
0814d0b442 | ||
|
|
c57978ba30 | ||
|
|
8e4a8e9ed6 | ||
|
|
7415382c72 | ||
|
|
41f8c4edfe | ||
|
|
09244d3d79 | ||
|
|
b6b58ffae2 | ||
|
|
e343f210f6 | ||
|
|
caaef2fb8f | ||
|
|
5cadeeea67 | ||
|
|
199ff77acc | ||
|
|
0117ea15ed | ||
|
|
3a1857c5ba | ||
|
|
eec0211521 | ||
|
|
93182b53cf | ||
|
|
0a9b58eb8f | ||
|
|
5e7b57669e | ||
|
|
50fdf47d05 | ||
|
|
6309ffb366 | ||
|
|
bc6975cdae | ||
|
|
76a1602c5f | ||
|
|
9c14d3bd10 | ||
|
|
025a31900a | ||
|
|
39d1ab741a | ||
|
|
5f9fa7014a | ||
|
|
09ea0d8e6f | ||
|
|
33792e6972 | ||
|
|
107055d4d6 | ||
|
|
532c31c009 | ||
|
|
7d426641c6 | ||
|
|
7fc5d979e0 | ||
|
|
70386a8fb9 | ||
|
|
f3bbb1ea43 | ||
|
|
d868b4d066 | ||
|
|
32b8371759 | ||
|
|
29e0fadec1 | ||
|
|
c91723b444 | ||
|
|
a4b6770174 | ||
|
|
c03d42b0cc | ||
|
|
450250704a | ||
|
|
e8d7b267b5 | ||
|
|
d525b0f189 | ||
|
|
a1ff5b6440 | ||
|
|
c2c1ebde39 | ||
|
|
6f509808e5 | ||
|
|
e667e2ec45 | ||
|
|
1d12f3a1d0 | ||
|
|
26d2df6c5f | ||
|
|
00c2db54a6 | ||
|
|
76d0adfe3c | ||
|
|
91b7eb56ab | ||
|
|
c4de5f1849 | ||
|
|
651417a424 | ||
|
|
31c34dc04e | ||
|
|
ad8587a853 | ||
|
|
0ddcba788d | ||
|
|
55451fdcdc | ||
|
|
659a4794ed | ||
|
|
d8a24ba05a | ||
|
|
3f5423837e | ||
|
|
4df91f14ee | ||
|
|
45427c3b33 | ||
|
|
665c9b3117 | ||
|
|
a04df8c543 | ||
|
|
89ab7a2bdf | ||
|
|
6c934c6f7f | ||
|
|
87ee9995d0 | ||
|
|
4e0e174063 | ||
|
|
611bdbd0bc | ||
|
|
0243bf1a40 | ||
|
|
05b223f322 | ||
|
|
c6dc65c418 | ||
|
|
ca2ea6c0bd | ||
|
|
38aaeebfbc | ||
|
|
89a6ad3a46 | ||
|
|
f35dd567a1 | ||
|
|
74c4b951a4 | ||
|
|
f932937928 | ||
|
|
56f15d9f4e | ||
|
|
7ccdb2ab29 | ||
|
|
c7d96af2de | ||
|
|
ec776c7d68 | ||
|
|
ea9b686b28 | ||
|
|
915b8d60d1 | ||
|
|
f6a24985b2 | ||
|
|
76bee630a2 | ||
|
|
66214e97b2 | ||
|
|
832405c2cf | ||
|
|
440f696f9b | ||
|
|
8a7435e571 | ||
|
|
7178b80ad1 | ||
|
|
7b719fdf61 | ||
|
|
e8f976a007 | ||
|
|
0a2f184e0e | ||
|
|
c40a37f677 | ||
|
|
20042a51ba | ||
|
|
fc249de987 | ||
|
|
6624f31472 | ||
|
|
cd900764d2 | ||
|
|
1eb1704219 | ||
|
|
363b2b9352 | ||
|
|
deba7e39a3 | ||
|
|
b1a6049622 | ||
|
|
4f0e1c7ef9 | ||
|
|
024edf3d40 | ||
|
|
6a725ab91f | ||
|
|
323a7eef1f | ||
|
|
233a3ed4ec | ||
|
|
f87e5d3c12 |
+86
-5
@@ -1,3 +1,82 @@
|
||||
# v0.13.2 (2014-05-11)
|
||||
|
||||
* Enhancements
|
||||
* [Application] Add an Application module with common functions to work with OTP applications
|
||||
* [Exception] Add `Exception.message/1`, `Exception.format_banner/1`, `Exception.format_exit/1` and `Exception.format/1`
|
||||
* [File] Add `File.ln_s/1`
|
||||
* [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 user-wide configuration with `~/.mix/config.exs`
|
||||
* [Mix] `mix help` now uses ANSI formatting to print guides
|
||||
* [Regex] Support functions in `Regex.replace/4`
|
||||
* [String] Support `:parts` in `String.split/3`
|
||||
|
||||
* Bug fixes
|
||||
* [Code] Ensure we don't lose the caller stacktrace on code evaluation
|
||||
* [IEx] Exit signals now exits the IEx evaluator and a new one is spawned on its place
|
||||
* [IEx] Ensure we don't prune too much stacktrace when reporting failures
|
||||
* [IEx] Fix an issue where `iex.bat` on Windows was not passing the proper parameters forward
|
||||
* [Kernel] Ensure modules defined on root respect defined aliases
|
||||
* [Kernel] Do not wrap single lists in `:__block__`
|
||||
* [Kernel] Ensure emitted beam code works nicely with dialyzer
|
||||
* [Kernel] Do not allow a module named `Elixir` to be defined
|
||||
* [Kernel] Create remote funs even if mod is a variable in capture `&mod.fun/arity`
|
||||
* [Kernel] Improve compiler message when duplicated modules are detected
|
||||
* [Mix] Generate `.gitignore` for `--umbrella` projects
|
||||
* [Mix] Verify if a git dependency in deps has a proper git checkout and clean it automatically when it doesn't
|
||||
* [Mix] Ensure `mix test` works with `IEx.pry/0`
|
||||
* [System] Convert remaining functions in System to rely on char data
|
||||
|
||||
* Soft deprecations (no warnings emitted)
|
||||
* [Application] `use Application.Behaviour` is deprecated in favor of `use Application`
|
||||
* [Exception] `exception.message` is deprecated in favor `Exception.message/1` for retrieving exception messages
|
||||
* [Kernel] `is_exception/1`, `is_record/1` and `is_record/2` are deprecated in favor of `Exception.exception?1`, `Record.record?/1` and `Record.record?/2`
|
||||
* [Mix] `Mix.project/0` is deprecated in favor of `Mix.Project.config/0`
|
||||
* [Process] `Process.spawn/1`, `Process.spawn/3`, `Process.spawn_link/1`, `Process.spawn_link/3`, `Process.spawn_monitor/1`, `Process.spawn_monitor/3`, `Process.send/2` and `Process.self/0` are deprecated in favor of the ones in `Kernel`
|
||||
|
||||
* Deprecations
|
||||
* [IEx] IEx.Options is deprecated in favor of `IEx.configure/1` and `IEx.configuration/0`
|
||||
* [Kernel] `lc` and `bc` comprehensions are deprecated in favor of `for`
|
||||
* [Macro] `Macro.safe_terms/1` is deprecated
|
||||
* [Process] `Process.delete/0` is deprecated
|
||||
* [Regex] Deprecate `:global` option in `Regex.split/3` in favor of `parts: :infinity`
|
||||
* [String] Deprecate `:global` option in `String.split/3` in favor of `parts: :infinity`
|
||||
|
||||
* Backwards incompatible changes
|
||||
* [ExUnit] `ExUnit.Test` and `ExUnit.TestCase` has been converted to structs
|
||||
* [ExUnit] The test and callback context has been converted to maps
|
||||
* [Kernel] `File.Stat`, `HashDict`, `HashSet`, `Inspect.Opts`, `Macro.Env`, `Range`, `Regex` and `Version.Requirement` have been converted to structs. This means `is_record/2` checks will no longer work, instead, you can pattern match on them using `%Range{}` and similar
|
||||
* [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)
|
||||
|
||||
* Enhancements
|
||||
* [Mix] Support `MIX_EXS` as configuration for running the current mix.exs file
|
||||
* [Mix] Support Hex out of the box. This means users do not need to install Hex directly, instead, Mix will prompt whenever there is a need to have Hex installed
|
||||
|
||||
* Bug fixes
|
||||
* [ExUnit] Ensure doctest failures are properly reported
|
||||
* [Kernel] Fix a bug where comprehensions arguments were not properly take into account in the variable scope
|
||||
* [Mix] Fix issue on rebar install when the endpoint was redirecting to a relative uri
|
||||
|
||||
* Soft deprecations (no warnings emitted)
|
||||
* [Kernel] `iolist_size` and `iolist_to_binary` are deprecated in favor of `iodata_length` and `iodata_to_binary`
|
||||
* [String] `String.to_char_list/1` is deprecated in favor of `List.from_char_data/1`
|
||||
* [String] `String.from_char_list/1` is deprecated in favor of `String.from_char_data/1`
|
||||
|
||||
* Deprecations
|
||||
* [Mix] `:env` key in project configuration is deprecated
|
||||
* [Regex] `Regex.groups/1` is deprecated in favor of `Regex.names/1`
|
||||
|
||||
* Backwards incompatible changes
|
||||
* [Macro] `Macro.unpipe/1` now returns tuples and `Macro.pipe/2` was removed in favor of `Macro.pipe/3` which explicitly expects the second element of the tuple returned by the new `Macro.unpipe/1`
|
||||
* [Path] The functions in Path now only emit strings as result, regardless if the input was a char list or a string
|
||||
* [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)
|
||||
|
||||
* Enhancements
|
||||
@@ -37,12 +116,14 @@
|
||||
* [Mix] Allow queries in `mix local.install` URL
|
||||
* [OptionParser] Do not recognize undefined aliases as switches
|
||||
|
||||
* Soft deprecations (no warnings emitted)
|
||||
* [Kernel] `lc` and `bc` comprehensions are deprecated in favor of `for`
|
||||
* [ListDict] `ListDict` is deprecated in favor of `Map`
|
||||
* [Record] `defrecord/2`, `defrecordp/3`, `is_record/1` and `is_record/2` macros in Kernel are deprecated. Instead, use the new macros and API defined in the `Record` module
|
||||
|
||||
* Deprecations
|
||||
* [Dict] `Dict.empty/1`, `Dict.new/1` and `Dict.new/2` are deprecated
|
||||
* [Exception] `Exception.normalize/1` is deprecated in favor of `Exception.normalize/2`
|
||||
* [Kernel] `lc` and `bc` comprehensions are deprecated in favor of `for` (this is a soft deprecation, no warning will be emitted)
|
||||
* [ListDict] `ListDict` is deprecated in favor of `Map` (this is a soft deprecation, no warning will be emitted)
|
||||
* [Record] `defrecord/2`, `defrecordp/3`, `is_record/1` and `is_record/2` macros in Kernel are deprecated. Instead, use the new macros and API defined in the `Record` module (this is a soft deprecation, no warnings will be emitted)
|
||||
|
||||
* Backwards incompatible changes
|
||||
* [ExUnit] Formatters are now required to be a GenEvent and `ExUnit.run/2` returns a map with results
|
||||
@@ -791,7 +872,7 @@
|
||||
* [ExUnit] Support context data in `setup_all`, `setup`, `teardown` and `teardown_all` callbacks
|
||||
* [IEx] Support `after_spawn` callbacks which are invoked after each process is spawned
|
||||
* [Kernel] Better error messages when invalid options are given to `import`, `alias` or `require`
|
||||
* [Kernel] Allow partial application on literals, for example: `{ &1, &2 }` to build tuples or `[&1|&2]` to build cons cells
|
||||
* [Kernel] Allow partial application on literals, for example: `{&1, &2}` to build tuples or `[&1|&2]` to build cons cells
|
||||
* [Kernel] Added `integer_to_binary` and `binary_to_integer`
|
||||
* [Kernel] Added `float_to_binary` and `binary_to_float`
|
||||
* [Kernel] Many improvements to `unquote` and `unquote_splicing`. For example, `unquote(foo).unquote(bar)(args)` is supported and no longer need to be written via `apply`
|
||||
@@ -844,7 +925,7 @@
|
||||
* [Kernel] Deprecate `__LINE__` and `__FUNCTION__` in favor of `__ENV__.line` and `__ENV__.function`
|
||||
* [Kernel] Deprecate `in_guard` in favor of `__CALLER__.in_guard?`
|
||||
* [Kernel] `refer` is deprecated in favor of `alias`
|
||||
* [Module] `Module.add_compile_callback(module, target, callback)` is deprecated in favor of `Module.put_attribute(module, :before_compile, { target, callback })`
|
||||
* [Module] `Module.add_compile_callback(module, target, callback)` is deprecated in favor of `Module.put_attribute(module, :before_compile, {target, callback})`
|
||||
* [Module] `Module.function_defined?` is deprecated in favor of `Module.defines?`
|
||||
* [Module] `Module.defined_functions` is deprecated in favor of `Module.definitions_in`
|
||||
|
||||
|
||||
+28
-17
@@ -34,16 +34,15 @@ Guidelines for bug reports:
|
||||
reported.
|
||||
|
||||
2. **Check if the issue has been fixed** — try to reproduce it using the
|
||||
latest `master` or development branch in the repository.
|
||||
`master` branch in the repository.
|
||||
|
||||
3. **Isolate the problem** — ideally create a reduced test
|
||||
3. **Isolate and report the problem** — ideally create a reduced test
|
||||
case.
|
||||
|
||||
A good bug report shouldn't leave others needing to chase you up for more
|
||||
information. Please try to be as detailed as possible in your report. What is
|
||||
your environment? What steps will reproduce the issue? What version of Erlang
|
||||
and Elixir experience the problem? What would you expect to be the outcome?
|
||||
All these details will help people to fix any potential bugs.
|
||||
Please try to be as detailed as possible in your report. Include information about
|
||||
your Operating System, your Erlang and Elixir versions. Please provide steps to
|
||||
reproduce the issue as well as the outcome you were expecting! All these details
|
||||
will help developers to fix any potential bugs.
|
||||
|
||||
Example:
|
||||
|
||||
@@ -67,8 +66,8 @@ Example:
|
||||
|
||||
Feature requests are welcome and should be discussed on [the elixir-core mailing list](http://groups.google.com/group/elixir-lang-core). But take a moment to find
|
||||
out whether your idea fits with the scope and aims of the project. It's up to *you*
|
||||
to make a strong case to convince the project's developers of the merits of this
|
||||
feature. Please provide as much detail and context as possible.
|
||||
to make a strong case to convince the community of the merits of this feature.
|
||||
Please provide as much detail and context as possible.
|
||||
|
||||
## Contributing
|
||||
|
||||
@@ -91,6 +90,16 @@ You can run all tests in the root directory with `make test` and you can
|
||||
also run tests for a specific framework `make test_#{NAME}`, for example,
|
||||
`make test_ex_unit`.
|
||||
|
||||
In case you are changing a single file, you can compile and run tests only
|
||||
for that particular file for fast development cycles. For example, if you
|
||||
are changing the String module, you can compile it and run its tests as:
|
||||
|
||||
$ bin/elixirc lib/elixir/lib/string.ex -o lib/elixir/ebin
|
||||
$ bin/elixir lib/elixir/test/elixir/string_test.exs
|
||||
|
||||
After your changes are done, please remember to run the full suite with
|
||||
`make test`.
|
||||
|
||||
From time to time, your tests may fail in an existing Elixir checkout and
|
||||
may require a clean start by running `make clean compile`. You can always
|
||||
check [the official build status on Travis-CI](https://travis-ci.org/elixir-lang/elixir).
|
||||
@@ -108,23 +117,25 @@ something like:
|
||||
|
||||
```elixir
|
||||
@doc """
|
||||
Return only those elements for which `fun` is true.
|
||||
Returns only those elements for which `fun` is true.
|
||||
|
||||
...
|
||||
"""
|
||||
def filter(collection, fun) ...
|
||||
```
|
||||
|
||||
For modules, records, protocols and types say what it is. For example write
|
||||
For modules, protocols and types say what it is. For example write
|
||||
something like:
|
||||
|
||||
```elixir
|
||||
defrecord File.Stat, [...] do
|
||||
defmodule File.Stat do
|
||||
@moduledoc """
|
||||
Information about a file.
|
||||
|
||||
...
|
||||
"""
|
||||
|
||||
defstruct [...]
|
||||
end
|
||||
```
|
||||
|
||||
@@ -154,7 +165,7 @@ def filter(collection, fun) ...
|
||||
```
|
||||
|
||||
This makes it easy to test the examples so that they don't go stale and examples
|
||||
are often a great help in explaining what a function does.
|
||||
are often a great help in explaining what a function does.
|
||||
|
||||
## Pull requests
|
||||
|
||||
@@ -165,10 +176,10 @@ commits.
|
||||
**IMPORTANT**: By submitting a patch, you agree that your work will be
|
||||
licensed under the license used by the project.
|
||||
|
||||
If you have any significant pull request in mind (e.g. implementing features,
|
||||
refactoring code, porting to a different language), **please ask first**
|
||||
otherwise you risk spending a lot of time working on something that the
|
||||
project's developers might not want to merge into the project.
|
||||
If you have any large pull request in mind (e.g. implementing features,
|
||||
refactoring code, etc), **please ask first** otherwise you risk spending
|
||||
a lot of time working on something that the project's developers might
|
||||
not want to merge into the project.
|
||||
|
||||
Please adhere to the coding conventions in the project (indentation,
|
||||
accurate comments, etc.) and don't forget to add your own tests and
|
||||
|
||||
@@ -11,10 +11,20 @@ INSTALL_DIR = $(INSTALL) -m755 -d
|
||||
INSTALL_DATA = $(INSTALL) -m644
|
||||
INSTALL_PROGRAM = $(INSTALL) -m755
|
||||
|
||||
.PHONY: install compile erlang elixir dialyze test clean docs release_docs release_zip
|
||||
.PHONY: install compile erlang elixir dialyze test clean docs release_docs release_zip check_erlang_release
|
||||
.NOTPARALLEL: compile
|
||||
|
||||
#==> Templates
|
||||
#==> Functions
|
||||
|
||||
# This check should work for older versions like R16B
|
||||
# as well as new verions like 17.1 and 18
|
||||
define CHECK_ERLANG_RELEASE
|
||||
$(Q) erl -noshell -eval 'io:fwrite("~s", [erlang:system_info(otp_release)])' -s erlang halt | grep -q '^1[789]'; \
|
||||
if [ $$? != 0 ]; then \
|
||||
echo "At least Erlang 17.0 is required to build Elixir"; \
|
||||
exit 1; \
|
||||
fi;
|
||||
endef
|
||||
|
||||
define APP_TEMPLATE
|
||||
$(1): lib/$(1)/ebin/Elixir.$(2).beam lib/$(1)/ebin/$(1).app
|
||||
@@ -46,6 +56,7 @@ default: compile
|
||||
compile: lib/elixir/src/elixir.app.src erlang elixir
|
||||
|
||||
lib/elixir/src/elixir.app.src: src/elixir.app.src
|
||||
$(Q) $(call CHECK_ERLANG_RELEASE)
|
||||
$(Q) rm -rf lib/elixir/src/elixir.app.src
|
||||
$(Q) echo "%% This file is automatically generated from <project_root>/src/elixir.app.src" \
|
||||
>lib/elixir/src/elixir.app.src
|
||||
@@ -61,7 +72,7 @@ elixir: kernel lib/eex/ebin/Elixir.EEx.beam mix ex_unit eex iex
|
||||
|
||||
kernel: $(KERNEL) VERSION
|
||||
$(KERNEL): lib/elixir/lib/*.ex lib/elixir/lib/*/*.ex
|
||||
$(Q) if [ ! -f $(KERNEL) ]; then \
|
||||
$(Q) if [ ! -f $(KERNEL) ]; then \
|
||||
echo "==> bootstrap (compile)"; \
|
||||
$(ERL) -s elixir_compiler core -s erlang halt; \
|
||||
fi
|
||||
@@ -85,14 +96,14 @@ $(eval $(call APP_TEMPLATE,iex,IEx))
|
||||
|
||||
install: compile
|
||||
@ echo "==> elixir (install)"
|
||||
for dir in lib/*; do \
|
||||
$(Q) for dir in lib/*; do \
|
||||
$(INSTALL_DIR) "$(DESTDIR)$(PREFIX)/$(LIBDIR)/elixir/$$dir/ebin"; \
|
||||
$(INSTALL_DATA) $$dir/ebin/* "$(DESTDIR)$(PREFIX)/$(LIBDIR)/elixir/$$dir/ebin"; \
|
||||
done
|
||||
$(INSTALL_DIR) "$(DESTDIR)$(PREFIX)/$(LIBDIR)/elixir/bin"
|
||||
$(INSTALL_PROGRAM) $(filter-out %.bat, $(wildcard bin/*)) "$(DESTDIR)$(PREFIX)/$(LIBDIR)/elixir/bin"
|
||||
$(INSTALL_DIR) "$(DESTDIR)$(PREFIX)/bin"
|
||||
for file in "$(DESTDIR)$(PREFIX)"/$(LIBDIR)/elixir/bin/* ; do \
|
||||
$(Q) $(INSTALL_DIR) "$(DESTDIR)$(PREFIX)/$(LIBDIR)/elixir/bin"
|
||||
$(Q) $(INSTALL_PROGRAM) $(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/" ; \
|
||||
done
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||

|
||||
=========
|
||||
[](http://travis-ci.org/elixir-lang/elixir)
|
||||
[](http://travis-ci.org/elixir-lang/elixir)
|
||||
|
||||
For more about Elixir, installation and documentation, [check Elixir's website](http://elixir-lang.org/).
|
||||
|
||||
@@ -18,7 +18,7 @@ If tests pass, you are ready to move on to the [Getting Started guide][1] or to
|
||||
|
||||
However, if tests fail, it is likely you have an outdated Erlang version (Elixir requires Erlang 17.0 or later). You can check your Erlang version by calling `erl` in the command line. You will see some information as follows:
|
||||
|
||||
Erlang/OTP 17 [RELEASE CANDIDATE 1] [erts-6.0] [source] [64-bit] [smp:4:4] [async-threads:10] [hipe] [kernel-poll:false]
|
||||
Erlang/OTP 17 [erts-6.0] [source-07b8f44] [64-bit] [smp:4:4] [async-threads:10] [hipe] [kernel-poll:false]
|
||||
|
||||
If you have the correct version and tests still fail, feel free to [open an issue][2].
|
||||
|
||||
|
||||
+9
-6
@@ -1,11 +1,14 @@
|
||||
@echo off
|
||||
set argc=0
|
||||
for %%x in (%*) do set /A argc+=1
|
||||
if %argc%== 0 (
|
||||
goto documentation
|
||||
) else (
|
||||
goto parseopts
|
||||
for %%A in (%*) do (
|
||||
if "%%A"=="--help" goto documentation
|
||||
if "%%A"=="-h" goto documentation
|
||||
if "%%A"=="/h" goto documentation
|
||||
set /A argc+=1
|
||||
)
|
||||
if %argc%==0 goto documentation
|
||||
goto parseopts
|
||||
|
||||
:documentation
|
||||
echo Usage: %~nx0 [options] [.exs file] [data]
|
||||
echo.
|
||||
@@ -95,4 +98,4 @@ IF %useWerl% EQU 1 (
|
||||
werl %ext_libs% -noshell %ELIXIR_ERL_OPTS% %parsErlang% -s elixir start_cli %beforeExtra% -extra %*
|
||||
) ELSE (
|
||||
erl %ext_libs% -noshell %ELIXIR_ERL_OPTS% %parsErlang% -s elixir start_cli %beforeExtra% -extra %*
|
||||
)
|
||||
)
|
||||
|
||||
+8
-5
@@ -1,11 +1,14 @@
|
||||
@echo off
|
||||
set argc=0
|
||||
for %%x in (%*) do set /A argc+=1
|
||||
if %argc% == 0 (
|
||||
goto documentation
|
||||
) else (
|
||||
goto run
|
||||
for %%A in (%*) do (
|
||||
if "%%A"=="--help" goto documentation
|
||||
if "%%A"=="-h" goto documentation
|
||||
if "%%A"=="/h" goto documentation
|
||||
set /A argc+=1
|
||||
)
|
||||
if %argc%==0 goto documentation
|
||||
goto run
|
||||
|
||||
:documentation
|
||||
echo Usage: %~nx0 [elixir switches] [compiler switches] [.ex files]
|
||||
echo.
|
||||
|
||||
+1
-1
@@ -1,2 +1,2 @@
|
||||
@echo off
|
||||
call "%~dp0\elixir.bat" +iex --erl "-user Elixir.IEx.CLI" --no-halt %
|
||||
call "%~dp0\elixir.bat" +iex --erl "-user Elixir.IEx.CLI" --no-halt %*
|
||||
|
||||
+3
-4
@@ -38,7 +38,6 @@ defmodule EEx do
|
||||
Defaults to the given file the template is read from
|
||||
or to "nofile" when compiling from a string;
|
||||
* `:engine` - the EEx engine to be used for compilation.
|
||||
Defaults to `EEx.Engine`;
|
||||
|
||||
## Engine
|
||||
|
||||
@@ -108,7 +107,7 @@ defmodule EEx do
|
||||
defmacro function_from_string(kind, name, source, args \\ [], options \\ []) do
|
||||
quote bind_quoted: binding do
|
||||
info = Keyword.merge [file: __ENV__.file, line: __ENV__.line], options
|
||||
args = Enum.map args, fn arg -> { arg, [line: info[:line]], nil } end
|
||||
args = Enum.map args, fn arg -> {arg, [line: info[:line]], nil} end
|
||||
compiled = EEx.compile_string(source, info)
|
||||
|
||||
case kind do
|
||||
@@ -144,7 +143,7 @@ defmodule EEx do
|
||||
defmacro function_from_file(kind, name, file, args \\ [], options \\ []) do
|
||||
quote bind_quoted: binding do
|
||||
info = Keyword.merge options, [file: file, line: 1]
|
||||
args = Enum.map args, fn arg -> { arg, [line: 1], nil } end
|
||||
args = Enum.map args, fn arg -> {arg, [line: 1], nil} end
|
||||
compiled = EEx.compile_file(file, info)
|
||||
|
||||
@file file
|
||||
@@ -207,7 +206,7 @@ defmodule EEx do
|
||||
### Helpers
|
||||
|
||||
defp do_eval(compiled, bindings, options) do
|
||||
{ result, _ } = Code.eval_quoted(compiled, bindings, options)
|
||||
{result, _} = Code.eval_quoted(compiled, bindings, options)
|
||||
result
|
||||
end
|
||||
end
|
||||
|
||||
+39
-34
@@ -1,53 +1,57 @@
|
||||
defrecord EEx.State, engine: EEx.SmartEngine, dict: [], file: "nofile", line: 1, start_line: 1
|
||||
|
||||
defmodule EEx.Compiler do
|
||||
@moduledoc false
|
||||
|
||||
# when changing this setting, don't forget to update the docs for EEx
|
||||
@default_engine EEx.SmartEngine
|
||||
|
||||
@doc """
|
||||
This is the compilation entry point. It glues the tokenizer
|
||||
and the engine together by handling the tokens and invoking
|
||||
the engine every time a full expression or text is received.
|
||||
"""
|
||||
def compile(source, options) do
|
||||
line = Keyword.get(options, :line, 1)
|
||||
def compile(source, opts) do
|
||||
file = opts[:file] || "nofile"
|
||||
line = opts[:line] || 1
|
||||
tokens = EEx.Tokenizer.tokenize(source, line)
|
||||
state = EEx.State.new(options)
|
||||
state = %{engine: opts[:engine] || @default_engine,
|
||||
file: file, line: line, quoted: [], start_line: nil}
|
||||
generate_buffer(tokens, "", [], state)
|
||||
end
|
||||
|
||||
# Generates the buffers by handling each expression from the tokenizer
|
||||
|
||||
defp generate_buffer([{ :text, chars }|t], buffer, scope, state) do
|
||||
buffer = state.engine.handle_text(buffer, String.from_char_list!(chars))
|
||||
defp generate_buffer([{:text, chars}|t], buffer, scope, state) do
|
||||
buffer = state.engine.handle_text(buffer, String.from_char_data!(chars))
|
||||
generate_buffer(t, buffer, scope, state)
|
||||
end
|
||||
|
||||
defp generate_buffer([{ :expr, line, mark, chars }|t], buffer, scope, state) do
|
||||
defp generate_buffer([{:expr, line, mark, chars}|t], buffer, scope, state) do
|
||||
expr = Code.string_to_quoted!(chars, [line: line, file: state.file])
|
||||
buffer = state.engine.handle_expr(buffer, mark, expr)
|
||||
generate_buffer(t, buffer, scope, state)
|
||||
end
|
||||
|
||||
defp generate_buffer([{ :start_expr, start_line, mark, chars }|t], buffer, scope, state) do
|
||||
{ contents, line, t } = look_ahead_text(t, start_line, chars)
|
||||
{ contents, t } = generate_buffer(t, "", [contents|scope], state.dict([]).line(line).start_line(start_line))
|
||||
defp generate_buffer([{:start_expr, start_line, mark, chars}|t], buffer, scope, state) do
|
||||
{contents, line, t} = look_ahead_text(t, start_line, chars)
|
||||
{contents, t} = generate_buffer(t, "", [contents|scope],
|
||||
%{state | quoted: [], line: line, start_line: start_line})
|
||||
buffer = state.engine.handle_expr(buffer, mark, contents)
|
||||
generate_buffer(t, buffer, scope, state)
|
||||
end
|
||||
|
||||
defp generate_buffer([{ :middle_expr, line, _, chars }|t], buffer, [current|scope], state) do
|
||||
{ wrapped, state } = wrap_expr(current, line, buffer, chars, state)
|
||||
generate_buffer(t, "", [wrapped|scope], state.line(line))
|
||||
defp generate_buffer([{:middle_expr, line, _, chars}|t], buffer, [current|scope], state) do
|
||||
{wrapped, state} = wrap_expr(current, line, buffer, chars, state)
|
||||
generate_buffer(t, "", [wrapped|scope], %{state | line: line})
|
||||
end
|
||||
|
||||
defp generate_buffer([{ :end_expr, line, _, chars }|t], buffer, [current|_], state) do
|
||||
{ wrapped, state } = wrap_expr(current, line, buffer, chars, state)
|
||||
defp generate_buffer([{:end_expr, line, _, chars}|t], buffer, [current|_], state) do
|
||||
{wrapped, state} = wrap_expr(current, line, buffer, chars, state)
|
||||
tuples = Code.string_to_quoted!(wrapped, [line: state.start_line, file: state.file])
|
||||
buffer = insert_quotes(tuples, state.dict)
|
||||
{ buffer, t }
|
||||
buffer = insert_quoted(tuples, state.quoted)
|
||||
{buffer, t}
|
||||
end
|
||||
|
||||
defp generate_buffer([{ :end_expr, line, _, chars }|_], _buffer, [], _state) do
|
||||
defp generate_buffer([{:end_expr, line, _, chars}|_], _buffer, [], _state) do
|
||||
raise EEx.SyntaxError, message: "unexpected token: #{inspect chars} at line #{inspect line}"
|
||||
end
|
||||
|
||||
@@ -63,23 +67,24 @@ defmodule EEx.Compiler do
|
||||
|
||||
defp wrap_expr(current, line, buffer, chars, state) do
|
||||
new_lines = List.duplicate(?\n, line - state.line)
|
||||
key = length(state.dict)
|
||||
key = length(state.quoted)
|
||||
placeholder = '__EEX__(' ++ integer_to_list(key) ++ ');'
|
||||
{ current ++ placeholder ++ new_lines ++ chars, state.update_dict(&[{key, buffer}|&1]) }
|
||||
{current ++ placeholder ++ new_lines ++ chars,
|
||||
%{state | quoted: [{key, buffer}|state.quoted]}}
|
||||
end
|
||||
|
||||
# Look text ahead on expressions
|
||||
|
||||
defp look_ahead_text([{ :text, text }, { :middle_expr, line, _, chars }|t]=list, start, contents) do
|
||||
defp look_ahead_text([{:text, text}, {:middle_expr, line, _, chars}|t]=list, start, contents) do
|
||||
if only_spaces?(text) do
|
||||
{ contents ++ text ++ chars, line, t }
|
||||
{contents ++ text ++ chars, line, t}
|
||||
else
|
||||
{ contents, start, list }
|
||||
{contents, start, list}
|
||||
end
|
||||
end
|
||||
|
||||
defp look_ahead_text(t, start, contents) do
|
||||
{ contents, start, t }
|
||||
{contents, start, t}
|
||||
end
|
||||
|
||||
defp only_spaces?(chars) do
|
||||
@@ -88,24 +93,24 @@ defmodule EEx.Compiler do
|
||||
|
||||
# Changes placeholder to real expression
|
||||
|
||||
defp insert_quotes({ :__EEX__, _, [key] }, dict) do
|
||||
{ ^key, value } = List.keyfind dict, key, 0
|
||||
defp insert_quoted({:__EEX__, _, [key]}, quoted) do
|
||||
{^key, value} = List.keyfind quoted, key, 0
|
||||
value
|
||||
end
|
||||
|
||||
defp insert_quotes({ left, line, right }, dict) do
|
||||
{ insert_quotes(left, dict), line, insert_quotes(right, dict) }
|
||||
defp insert_quoted({left, line, right}, quoted) do
|
||||
{insert_quoted(left, quoted), line, insert_quoted(right, quoted)}
|
||||
end
|
||||
|
||||
defp insert_quotes({ left, right }, dict) do
|
||||
{ insert_quotes(left, dict), insert_quotes(right, dict) }
|
||||
defp insert_quoted({left, right}, quoted) do
|
||||
{insert_quoted(left, quoted), insert_quoted(right, quoted)}
|
||||
end
|
||||
|
||||
defp insert_quotes(list, dict) when is_list(list) do
|
||||
Enum.map list, &insert_quotes(&1, dict)
|
||||
defp insert_quoted(list, quoted) when is_list(list) do
|
||||
Enum.map list, &insert_quoted(&1, quoted)
|
||||
end
|
||||
|
||||
defp insert_quotes(other, _dict) do
|
||||
defp insert_quoted(other, _quoted) do
|
||||
other
|
||||
end
|
||||
end
|
||||
|
||||
@@ -27,12 +27,12 @@ defmodule EEx.TransformerEngine do
|
||||
EEx.Engine.handle_expr(buffer, mark, transform(expr))
|
||||
end
|
||||
|
||||
defp transform({ a, b, c }) do
|
||||
{ transform(a), b, transform(c) }
|
||||
defp transform({a, b, c}) do
|
||||
{transform(a), b, transform(c)}
|
||||
end
|
||||
|
||||
defp transform({ a, b }) do
|
||||
{ transform(a), transform(b) }
|
||||
defp transform({a, b}) do
|
||||
{transform(a), transform(b)}
|
||||
end
|
||||
|
||||
defp transform(list) when is_list(list) do
|
||||
@@ -91,7 +91,7 @@ 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
|
||||
defp transform({:@, line, [{name, _, atom}]}) when is_atom(name) and is_atom(atom) do
|
||||
quote do: Dict.get(var!(assigns), unquote(name))
|
||||
end
|
||||
|
||||
|
||||
@@ -5,15 +5,15 @@ defmodule EEx.Tokenizer do
|
||||
Tokenizes the given char list or binary.
|
||||
It returns 4 different types of tokens as result:
|
||||
|
||||
* { :text, contents }
|
||||
* { :expr, line, marker, contents }
|
||||
* { :start_expr, line, marker, contents }
|
||||
* { :middle_expr, line, marker, contents }
|
||||
* { :end_expr, line, marker, contents }
|
||||
* {:text, contents}
|
||||
* {:expr, line, marker, contents}
|
||||
* {:start_expr, line, marker, contents}
|
||||
* {:middle_expr, line, marker, contents}
|
||||
* {:end_expr, line, marker, contents}
|
||||
|
||||
"""
|
||||
def tokenize(bin, line) when is_binary(bin) do
|
||||
tokenize(String.to_char_list!(bin), line)
|
||||
tokenize(List.from_char_data!(bin), line)
|
||||
end
|
||||
|
||||
def tokenize(list, line) do
|
||||
@@ -21,22 +21,22 @@ defmodule EEx.Tokenizer do
|
||||
end
|
||||
|
||||
defp tokenize('<%%' ++ t, line, buffer, acc) do
|
||||
{ buffer, new_line, rest } = tokenize_expr t, line, [?%, ?<|buffer]
|
||||
{buffer, new_line, rest} = tokenize_expr t, line, [?%, ?<|buffer]
|
||||
tokenize rest, new_line, [?>, ?%|buffer], acc
|
||||
end
|
||||
|
||||
defp tokenize('<%#' ++ t, line, buffer, acc) do
|
||||
{ _, new_line, rest } = tokenize_expr t, line, []
|
||||
{_, new_line, rest} = tokenize_expr t, line, []
|
||||
tokenize rest, new_line, buffer, acc
|
||||
end
|
||||
|
||||
defp tokenize('<%' ++ t, line, buffer, acc) do
|
||||
{ marker, t } = retrieve_marker(t)
|
||||
{ expr, new_line, rest } = tokenize_expr t, line, []
|
||||
{marker, t} = retrieve_marker(t)
|
||||
{expr, new_line, rest} = tokenize_expr t, line, []
|
||||
|
||||
token = token_name(expr)
|
||||
acc = tokenize_text(buffer, acc)
|
||||
final = { token, line, marker, Enum.reverse(expr) }
|
||||
final = {token, line, marker, Enum.reverse(expr)}
|
||||
tokenize rest, new_line, [], [final | acc]
|
||||
end
|
||||
|
||||
@@ -55,17 +55,17 @@ defmodule EEx.Tokenizer do
|
||||
# Retrieve marker for <%
|
||||
|
||||
defp retrieve_marker('=' ++ t) do
|
||||
{ "=", t }
|
||||
{"=", t}
|
||||
end
|
||||
|
||||
defp retrieve_marker(t) do
|
||||
{ "", t }
|
||||
{"", t}
|
||||
end
|
||||
|
||||
# Tokenize an expression until we find %>
|
||||
|
||||
defp tokenize_expr([?%, ?>|t], line, buffer) do
|
||||
{ buffer, line, t }
|
||||
{buffer, line, t}
|
||||
end
|
||||
|
||||
defp tokenize_expr('\n' ++ t, line, buffer) do
|
||||
@@ -104,7 +104,7 @@ defmodule EEx.Tokenizer do
|
||||
# token and, if so, it is not followed by an "end"
|
||||
# token. If this is the case, we are on a start expr.
|
||||
case :elixir_tokenizer.tokenize(rest, 1, file: "eex", check_terminators: false) do
|
||||
{ :ok, _line, tokens } ->
|
||||
{:ok, _line, tokens} ->
|
||||
tokens = Enum.reverse(tokens)
|
||||
fn_index = fn_index(tokens)
|
||||
|
||||
@@ -130,14 +130,14 @@ defmodule EEx.Tokenizer do
|
||||
|
||||
defp fn_index(tokens) do
|
||||
Enum.find_index tokens, fn
|
||||
{ :fn_paren, _ } -> true
|
||||
{ :fn, _ } -> true
|
||||
{:fn_paren, _} -> true
|
||||
{:fn, _} -> true
|
||||
_ -> false
|
||||
end
|
||||
end
|
||||
|
||||
defp end_index(tokens) do
|
||||
Enum.find_index(tokens, &match?({ :end, _ }, &1)) || :infinity
|
||||
Enum.find_index(tokens, &match?({:end, _}, &1)) || :infinity
|
||||
end
|
||||
|
||||
defp check_spaces(string, token) do
|
||||
@@ -156,6 +156,6 @@ defmodule EEx.Tokenizer do
|
||||
end
|
||||
|
||||
defp tokenize_text(buffer, acc) do
|
||||
[{ :text, Enum.reverse(buffer) } | acc]
|
||||
[{:text, Enum.reverse(buffer)} | acc]
|
||||
end
|
||||
end
|
||||
|
||||
+3
-1
@@ -2,6 +2,8 @@ defmodule EEx.Mixfile do
|
||||
use Mix.Project
|
||||
|
||||
def project do
|
||||
[app: :eex, version: System.version, build_per_environment: false]
|
||||
[app: :eex,
|
||||
version: System.version,
|
||||
build_per_environment: false]
|
||||
end
|
||||
end
|
||||
|
||||
@@ -5,23 +5,23 @@ defmodule EEx.TokenizerTest do
|
||||
require EEx.Tokenizer, as: T
|
||||
|
||||
test "simple chars lists" do
|
||||
assert T.tokenize('foo', 1) == [ { :text, 'foo' } ]
|
||||
assert T.tokenize('foo', 1) == [ {:text, 'foo'} ]
|
||||
end
|
||||
|
||||
test "simple strings" do
|
||||
assert T.tokenize("foo", 1) == [ { :text, 'foo' } ]
|
||||
assert T.tokenize("foo", 1) == [ {:text, 'foo'} ]
|
||||
end
|
||||
|
||||
test "strings with embedded code" do
|
||||
assert T.tokenize('foo <% bar %>', 1) == [ { :text, 'foo ' }, { :expr, 1, "", ' bar ' } ]
|
||||
assert T.tokenize('foo <% bar %>', 1) == [ {:text, 'foo '}, {:expr, 1, "", ' bar '} ]
|
||||
end
|
||||
|
||||
test "strings with embedded equals code" do
|
||||
assert T.tokenize('foo <%= bar %>', 1) == [ { :text, 'foo ' }, { :expr, 1, "=", ' bar ' } ]
|
||||
assert T.tokenize('foo <%= bar %>', 1) == [ {:text, 'foo '}, {:expr, 1, "=", ' bar '} ]
|
||||
end
|
||||
|
||||
test "strings with more than one line" do
|
||||
assert T.tokenize('foo\n<%= bar %>', 1) == [ { :text, 'foo\n' }, { :expr, 2, "=", ' bar ' } ]
|
||||
assert T.tokenize('foo\n<%= bar %>', 1) == [ {:text, 'foo\n'}, {:expr, 2, "=", ' bar '} ]
|
||||
end
|
||||
|
||||
test "strings with more than one line and expression with more than one line" do
|
||||
@@ -43,57 +43,57 @@ baz %>
|
||||
|
||||
test "quotation" do
|
||||
assert T.tokenize('foo <%% true %>', 1) == [
|
||||
{ :text, 'foo <% true %>' }
|
||||
{:text, 'foo <% true %>'}
|
||||
]
|
||||
end
|
||||
|
||||
test "quotation with do/end" do
|
||||
assert T.tokenize('foo <%% true do %>bar<%% end %>', 1) == [
|
||||
{ :text, 'foo <% true do %>bar<% end %>' }
|
||||
{:text, 'foo <% true do %>bar<% end %>'}
|
||||
]
|
||||
end
|
||||
|
||||
test "comments" do
|
||||
assert T.tokenize('foo <%# true %>', 1) == [
|
||||
{ :text, 'foo ' }
|
||||
{:text, 'foo '}
|
||||
]
|
||||
end
|
||||
|
||||
test "comments with do/end" do
|
||||
assert T.tokenize('foo <%# true do %>bar<%# end %>', 1) == [
|
||||
{ :text, 'foo bar' }
|
||||
{:text, 'foo bar'}
|
||||
]
|
||||
end
|
||||
|
||||
test "strings with embedded do end" do
|
||||
assert T.tokenize('foo <% if true do %>bar<% end %>', 1) == [
|
||||
{ :text, 'foo ' },
|
||||
{ :start_expr, 1, "", ' if true do ' },
|
||||
{ :text, 'bar' },
|
||||
{ :end_expr, 1, "", ' end ' }
|
||||
{:text, 'foo '},
|
||||
{:start_expr, 1, "", ' if true do '},
|
||||
{:text, 'bar'},
|
||||
{:end_expr, 1, "", ' end '}
|
||||
]
|
||||
end
|
||||
|
||||
test "strings with embedded -> end" do
|
||||
assert T.tokenize('foo <% cond do %><% false -> %>bar<% true -> %>baz<% end %>', 1) == [
|
||||
{ :text, 'foo ' },
|
||||
{ :start_expr, 1, "", ' cond do ' },
|
||||
{ :middle_expr, 1, "", ' false -> ' },
|
||||
{ :text, 'bar' },
|
||||
{ :middle_expr, 1, "", ' true -> ' },
|
||||
{ :text, 'baz' },
|
||||
{ :end_expr, 1, "", ' end ' }
|
||||
{:text, 'foo '},
|
||||
{:start_expr, 1, "", ' cond do '},
|
||||
{:middle_expr, 1, "", ' false -> '},
|
||||
{:text, 'bar'},
|
||||
{:middle_expr, 1, "", ' true -> '},
|
||||
{:text, 'baz'},
|
||||
{:end_expr, 1, "", ' end '}
|
||||
]
|
||||
end
|
||||
|
||||
test "strings with embedded keywords blocks" do
|
||||
assert T.tokenize('foo <% if true do %>bar<% else %>baz<% end %>', 1) == [
|
||||
{ :text, 'foo ' },
|
||||
{ :start_expr, 1, "", ' if true do ' },
|
||||
{ :text, 'bar' },
|
||||
{ :middle_expr, 1, "", ' else ' },
|
||||
{ :text, 'baz' },
|
||||
{ :end_expr, 1, "", ' end ' }
|
||||
{:text, 'foo '},
|
||||
{:start_expr, 1, "", ' if true do '},
|
||||
{:text, 'bar'},
|
||||
{:middle_expr, 1, "", ' else '},
|
||||
{:text, 'baz'},
|
||||
{:end_expr, 1, "", ' end '}
|
||||
]
|
||||
end
|
||||
|
||||
|
||||
+29
-20
@@ -5,9 +5,9 @@ require EEx
|
||||
defmodule EExText.Compiled do
|
||||
def before_compile do
|
||||
fill_in_stacktrace
|
||||
{ __ENV__.line, hd(tl(System.stacktrace)) }
|
||||
{__ENV__.line, hd(tl(System.stacktrace))}
|
||||
end
|
||||
{ :erlang, 1, 2 }.tuple_to_list
|
||||
{:erlang, 1, 2}.tuple_to_list
|
||||
EEx.function_from_string :def, :string_sample, "<%= a + b %>", [:a, :b]
|
||||
|
||||
filename = Path.join(__DIR__, "fixtures/eex_template_with_bindings.eex")
|
||||
@@ -16,13 +16,13 @@ defmodule EExText.Compiled do
|
||||
|
||||
def after_compile do
|
||||
fill_in_stacktrace
|
||||
{ __ENV__.line, hd(tl(System.stacktrace)) }
|
||||
{__ENV__.line, hd(tl(System.stacktrace))}
|
||||
end
|
||||
|
||||
@file "unknown"
|
||||
def unknown do
|
||||
fill_in_stacktrace
|
||||
{ __ENV__.line, hd(tl(System.stacktrace)) }
|
||||
{__ENV__.line, hd(tl(System.stacktrace))}
|
||||
end
|
||||
|
||||
defp fill_in_stacktrace do
|
||||
@@ -282,6 +282,15 @@ foo
|
||||
assert_eval "\n\n Good\n \n", string
|
||||
end
|
||||
|
||||
test "for comprehensions" do
|
||||
string = """
|
||||
<%= for _name <- packages || [] do %>
|
||||
<% end %>
|
||||
<%= all || :done %>
|
||||
"""
|
||||
assert_eval "\ndone\n", string, packages: nil, all: nil
|
||||
end
|
||||
|
||||
test "unicode" do
|
||||
template = """
|
||||
• <%= "•" %> •
|
||||
@@ -320,38 +329,38 @@ foo
|
||||
|
||||
test "defined from file do not affect backtrace" do
|
||||
assert EExText.Compiled.before_compile ==
|
||||
{ 8,
|
||||
{ EExText.Compiled,
|
||||
{8,
|
||||
{EExText.Compiled,
|
||||
:before_compile,
|
||||
0,
|
||||
[file: to_char_list(Path.relative_to_cwd(__ENV__.file)), line: 7]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
assert EExText.Compiled.after_compile ==
|
||||
{ 19,
|
||||
{ EExText.Compiled,
|
||||
{19,
|
||||
{EExText.Compiled,
|
||||
:after_compile,
|
||||
0,
|
||||
[file: to_char_list(Path.relative_to_cwd(__ENV__.file)), line: 18]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
assert EExText.Compiled.unknown ==
|
||||
{ 25,
|
||||
{ EExText.Compiled,
|
||||
{25,
|
||||
{EExText.Compiled,
|
||||
:unknown,
|
||||
0,
|
||||
[file: 'unknown', line: 24]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
defmodule TestEngine do
|
||||
@behaviour EEx.Engine
|
||||
|
||||
def handle_body(body) do
|
||||
{ :wrapped, body }
|
||||
{:wrapped, body}
|
||||
end
|
||||
|
||||
def handle_text(buffer, text) do
|
||||
@@ -364,11 +373,11 @@ foo
|
||||
end
|
||||
|
||||
test "calls handle_body" do
|
||||
assert { :wrapped, "foo" } = EEx.eval_string("foo", [], engine: TestEngine)
|
||||
assert {:wrapped, "foo"} = EEx.eval_string("foo", [], engine: TestEngine)
|
||||
end
|
||||
|
||||
defp assert_eval(expected, actual) do
|
||||
result = EEx.eval_string(actual, [], file: __ENV__.file, engine: EEx.Engine)
|
||||
defp assert_eval(expected, actual, binding \\ []) do
|
||||
result = EEx.eval_string(actual, binding, file: __ENV__.file, engine: EEx.Engine)
|
||||
assert result == expected
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,11 +1,4 @@
|
||||
-define(wrap_call(Line, Module, Function, Args),
|
||||
{ call, Line,
|
||||
{ remote, Line, { atom, Line, Module }, { atom, Line, Function } },
|
||||
Args
|
||||
}).
|
||||
|
||||
-define(atom_concat(Atoms), list_to_atom(lists:concat(Atoms))).
|
||||
-define(elixir_macro(Macro), list_to_atom(lists:concat(['MACRO-',Macro]))).
|
||||
-define(m(M, K), maps:get(K, M)).
|
||||
-define(line(Opts), elixir_utils:get_line(Opts)).
|
||||
|
||||
-record(elixir_scope, {
|
||||
@@ -26,24 +19,6 @@
|
||||
file=(<<"nofile">>) %% the current scope filename
|
||||
}).
|
||||
|
||||
-record(elixir_env, {
|
||||
module=nil, %% the current module
|
||||
file=(<<"nofile">>), %% the current filename
|
||||
line=1, %% the current line
|
||||
function=nil, %% the current function
|
||||
context=nil, %% can be match_vars, guards or nil
|
||||
requires=[], %% a set with modules required
|
||||
aliases=[], %% an orddict with aliases by new -> old names
|
||||
functions=[], %% a list with functions imported from module
|
||||
macros=[], %% a list with macros imported from module
|
||||
macro_aliases=[], %% keep aliases defined inside a macro
|
||||
context_modules=[], %% modules defined in the current context
|
||||
vars=[], %% a set of defined variables
|
||||
export_vars=nil, %% a set of variables to be exported in certain constructs, may be nil
|
||||
lexical_tracker=nil, %% holds the lexical tracker pid
|
||||
local=nil %% the module to delegate local functions to
|
||||
}).
|
||||
|
||||
-record(elixir_quote, {
|
||||
line=false,
|
||||
keep=false,
|
||||
|
||||
@@ -14,11 +14,11 @@ defprotocol Access do
|
||||
iex> keywords[:a]
|
||||
1
|
||||
|
||||
iex> map = %{ a: 1, b: 2 }
|
||||
iex> map = %{a: 1, b: 2}
|
||||
iex> map[:a]
|
||||
1
|
||||
|
||||
iex> star_ratings = %{ 1.0 => "★", 1.5 => "★☆", 2.0 => "★★" }
|
||||
iex> star_ratings = %{1.0 => "★", 1.5 => "★☆", 2.0 => "★★"}
|
||||
iex> star_ratings[1.5]
|
||||
"★☆"
|
||||
|
||||
@@ -34,16 +34,22 @@ defprotocol Access do
|
||||
end
|
||||
|
||||
defimpl Access, for: List do
|
||||
def access(dict, key)
|
||||
def access([{ key, value }|_], key), do: value
|
||||
def access([{ _, _ }|t], key), do: access(t, key)
|
||||
def access([], _key), do: nil
|
||||
def access(dict, key) when is_atom(key) do
|
||||
case :lists.keyfind(key, 1, dict) do
|
||||
{^key, value} -> value
|
||||
false -> nil
|
||||
end
|
||||
end
|
||||
|
||||
def access(_dict, key) do
|
||||
raise ArgumentError, message: "the access protocol for lists expect the key to be an atom, got: #{inspect key}"
|
||||
end
|
||||
end
|
||||
|
||||
defimpl Access, for: Map do
|
||||
def access(map, key) do
|
||||
case :maps.find(key, map) do
|
||||
{ :ok, value } -> value
|
||||
{:ok, value} -> value
|
||||
:error -> nil
|
||||
end
|
||||
end
|
||||
|
||||
@@ -0,0 +1,403 @@
|
||||
defmodule Application do
|
||||
@moduledoc """
|
||||
A module for working with applications and defining application callbacks.
|
||||
|
||||
In Elixir (actually, in Erlang/OTP), an application is a component
|
||||
implementing some specific functionality, that can be started and stopped
|
||||
as a unit, and which can be re-used in other systems as well.
|
||||
|
||||
Applications are defined with an application file named `APP.app` where
|
||||
`APP` is the APP name, usually in `underscore_case` convention. The
|
||||
application file must reside in the same `ebin` directory as the
|
||||
application's modules bytecode.
|
||||
|
||||
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
|
||||
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.
|
||||
|
||||
You can learn more about Mix compilation of `.app` files by typing
|
||||
`mix help compile.app`.
|
||||
|
||||
## Application environment
|
||||
|
||||
Once an application is started, OTP provides an application environment
|
||||
that can be used to configure applications.
|
||||
|
||||
Assuming you are inside a Mix project, you can edit your application
|
||||
function in the `mix.exs` file to the following:
|
||||
|
||||
def application do
|
||||
[env: [hello: :world]]
|
||||
end
|
||||
|
||||
In the application function, we can define the default environment values
|
||||
for our application. By starting your application with `iex -S mix`, you
|
||||
can access the default value:
|
||||
|
||||
Application.get_env(:APP_NAME, :hello)
|
||||
#=> {:ok, :hello}
|
||||
|
||||
It is also possible to put and delete values from the application value,
|
||||
including new values that are not defined in the environment file (although
|
||||
those should be avoided).
|
||||
|
||||
In the future, we plan to support configuration files which allows
|
||||
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
|
||||
the environment of other application (as it may lead to inconsistent
|
||||
data in the application environment).
|
||||
|
||||
## Application module callback
|
||||
|
||||
Often times, an application defines a supervision tree that must be started
|
||||
and stopped when the application starts and stops. For such, we need to
|
||||
define an application module callback. The first step is to define the
|
||||
module callback in the application definition in the `mix.exs` file:
|
||||
|
||||
def application do
|
||||
[mod: {MyApp, []}]
|
||||
end
|
||||
|
||||
Our application now requires the `MyApp` module to provide an application
|
||||
callback. This can be done by invoking `use Application` in that module
|
||||
and defining a `start/2` callback, for example:
|
||||
|
||||
defmodule MyApp do
|
||||
use Application
|
||||
|
||||
def start(_type, _args) do
|
||||
MyApp.Supervisor.start_link()
|
||||
end
|
||||
end
|
||||
|
||||
`start/2` most commonly returns `{:ok, pid}` or `{:ok, pid, state}` where
|
||||
`pid` identifies the supervision tree and the state is the application state.
|
||||
`args` is second element of the tuple given to the `:mod` option.
|
||||
|
||||
The `type` passed into `start/2` is usually `:normal` unless in a distributed
|
||||
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
|
||||
|
||||
A developer may also implement the `stop/1` callback (automatically defined
|
||||
by `use Application`) which does any application cleanup. It receives the
|
||||
application state and can return any value. Notice that shutting down the
|
||||
supervisor is automatically handled by the VM;
|
||||
"""
|
||||
|
||||
@doc false
|
||||
defmacro __using__(_) do
|
||||
quote location: :keep do
|
||||
@behaviour :application
|
||||
|
||||
@doc false
|
||||
def stop(_state) do
|
||||
:ok
|
||||
end
|
||||
|
||||
defoverridable [stop: 1]
|
||||
end
|
||||
end
|
||||
|
||||
@type app :: atom
|
||||
@type key :: atom
|
||||
@type value :: term
|
||||
@type start_type :: :permanent | :transient | :temporary
|
||||
|
||||
@doc """
|
||||
Returns all key-value pairs for `app`.
|
||||
"""
|
||||
@spec get_all_env(app) :: [{key,value}]
|
||||
def get_all_env(app) do
|
||||
:application.get_all_env(app)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns the value for `key` in `app`'s environment.
|
||||
|
||||
If the specified application is not loaded, or the configuration parameter
|
||||
does not exist, the function returns the `default` value.
|
||||
"""
|
||||
@spec get_env(app, key, value) :: value
|
||||
def get_env(app, key, default \\ nil) do
|
||||
case :application.get_env(app, key) do
|
||||
{:ok, value} -> value
|
||||
:undefined -> default
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns the value for `key` in `app`'s environment in a tuple.
|
||||
|
||||
If the specified application is not loaded, or the configuration parameter
|
||||
does not exist, the function returns `:error`.
|
||||
"""
|
||||
@spec fetch_env(app, key) :: {:ok, value} | :error
|
||||
def fetch_env(app, key) do
|
||||
case :application.get_env(app, key) do
|
||||
{:ok, value} -> {:ok, value}
|
||||
:undefined -> :error
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Puts the `value` in `key` for the given `app`.
|
||||
|
||||
## Options
|
||||
|
||||
* `: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
|
||||
previously set.
|
||||
|
||||
The persistent option can be set to true when there is a need to guarantee
|
||||
parameters set with this function will not be overridden by the ones defined
|
||||
in the application resource file on load. This means persistent values will
|
||||
stick after the application is loaded and also on application reload.
|
||||
"""
|
||||
@spec put_env(app, key, value, [timeout: timeout, persistent: boolean]) :: :ok
|
||||
def put_env(app, key, value, opts \\ []) do
|
||||
:application.set_env(app, key, value, opts)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Deletes the `key` from the given `app` environment.
|
||||
|
||||
See `put_env/4` for a description of the options.
|
||||
"""
|
||||
@spec delete_env(app, key, [timeout: timeout, persistent: boolean]) :: :ok
|
||||
def delete_env(app, key, opts \\ []) do
|
||||
:application.unset_env(app, key, opts)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Ensures the given `app` is started.
|
||||
|
||||
Same as `start/2` but returns `:ok` if the application was already
|
||||
started. This is useful in scripts and in test setup, where test
|
||||
applications need to be explicitly started:
|
||||
|
||||
:ok = Application.ensure_started(:my_test_dep)
|
||||
|
||||
"""
|
||||
@spec ensure_started(app, start_type) :: :ok | {:error, term}
|
||||
def ensure_started(app, type \\ :temporary) when is_atom(app) do
|
||||
:application.ensure_started(app, type)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Ensures the given `app` and its applications are started.
|
||||
|
||||
Same as `start/2` but also starts the applications listed under
|
||||
`:applications` in the `.app` file in case they were not previously
|
||||
started.
|
||||
"""
|
||||
@spec ensure_all_started(app, start_type) :: {:ok, [app]} | {:error, term}
|
||||
def ensure_all_started(app, type \\ :temporary) when is_atom(app) do
|
||||
:application.ensure_all_started(app, type)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Starts the given `app`.
|
||||
|
||||
If the `app` is not loaded, the application will first be loaded using `load/1`.
|
||||
Any included application, defined in the `:included_applications` key of the
|
||||
`.app` file will also be loaded, but they won't be started.
|
||||
|
||||
Furthermore, all applications listed in the `:applications` key must be explicitly
|
||||
started before this application is. If not, `{:error, {:not_started, app}}` is
|
||||
returned, where `app` is the name of the missing application.
|
||||
|
||||
In case you want to automatically load **and start** all of `app`'s dependencies,
|
||||
see `ensure_all_started/2`.
|
||||
|
||||
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);
|
||||
|
||||
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
|
||||
be affected.
|
||||
|
||||
Note also that the `:transient` type is of little practical use, since when a
|
||||
supervision tree terminates, the reason is set to `:shutdown`, not `:normal`.
|
||||
"""
|
||||
@spec start(app, start_type) :: :ok | {:error, term}
|
||||
def start(app, type \\ :temporary) when is_atom(app) do
|
||||
:application.start(app, type)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Stops the given `app`.
|
||||
|
||||
When stopped, the application is still loaded.
|
||||
"""
|
||||
@spec stop(app) :: :ok | {:error, term}
|
||||
def stop(app) do
|
||||
:application.stop(app)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Loads the given `app`.
|
||||
|
||||
In order to be loaded, an `.app` file must be in the load paths.
|
||||
All `:included_applications` will also be loaded.
|
||||
|
||||
Loading the application does not start it nor load its modules, but
|
||||
it does load its environment.
|
||||
"""
|
||||
@spec load(app) :: :ok | {:error, term}
|
||||
def load(app) when is_atom(app) do
|
||||
:application.load(app)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Unloads the given `app`.
|
||||
|
||||
It will also unload all `:included_applications`.
|
||||
Note that the function does not purge the application modules.
|
||||
"""
|
||||
@spec unload(app) :: :ok | {:error, term}
|
||||
def unload(app) when is_atom(app) do
|
||||
:application.unload(app)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Gets the directory for app.
|
||||
|
||||
This information is returned based on the code path. Here is an
|
||||
example:
|
||||
|
||||
File.mkdir_p!("foo/ebin")
|
||||
Code.prepend_path("foo/ebin")
|
||||
Application.app_dir(:foo)
|
||||
#=> "foo"
|
||||
|
||||
Even though the directory is empty and there is no `.app` file
|
||||
it is considered the application directory based on the name
|
||||
"foo/ebin". The name may contain a dash `-` which is considered
|
||||
to be the app version and it is removed for the lookup purposes:
|
||||
|
||||
File.mkdir_p!("bar-123/ebin")
|
||||
Code.prepend_path("bar-123/ebin")
|
||||
Application.app_dir(:bar)
|
||||
#=> "bar-123"
|
||||
|
||||
For more information on code paths, check the `Code` module in
|
||||
Elixir and also Erlang's `:code` module.
|
||||
"""
|
||||
@spec app_dir(app) :: String.t
|
||||
def app_dir(app) when is_atom(app) do
|
||||
case :code.lib_dir(app) do
|
||||
lib when is_list(lib) -> String.from_char_data!(lib)
|
||||
{:error, :bad_name} -> raise ArgumentError, message: "unknown application: #{inspect app}"
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns the given path inside `app_dir/1`.
|
||||
"""
|
||||
@spec app_dir(app, String.t) :: String.t
|
||||
def app_dir(app, path) when is_binary(path) do
|
||||
Path.join(app_dir(app), path)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Formats the error reason returned by `start/2`,
|
||||
`ensure_started/2, `stop/1`, `load/1` and `unload/1`,
|
||||
returns a string.
|
||||
"""
|
||||
@spec format_error(any) :: String.t
|
||||
def format_error(reason) do
|
||||
try do
|
||||
impl_format_error(reason)
|
||||
catch
|
||||
# A user could create an error that looks like a builtin one
|
||||
# causing an error.
|
||||
:error, _ ->
|
||||
inspect(reason)
|
||||
end
|
||||
end
|
||||
|
||||
# exit(:normal) call is special cased, undo the special case.
|
||||
defp impl_format_error({{:EXIT, :normal}, {mod, :start, args}}) do
|
||||
Exception.format_exit({:normal, {mod, :start, args}})
|
||||
end
|
||||
|
||||
# {:error, reason} return value
|
||||
defp impl_format_error({reason, {mod, :start, args}}) do
|
||||
Exception.format_mfa(mod, :start, args) <> " returned an error: " <>
|
||||
Exception.format_exit(reason)
|
||||
end
|
||||
|
||||
# error or exit(reason) call, use exit reason as reason.
|
||||
defp impl_format_error({:bad_return, {{mod, :start, args}, {:EXIT, reason}}}) do
|
||||
Exception.format_exit({reason, {mod, :start, args}})
|
||||
end
|
||||
|
||||
# bad return value
|
||||
defp impl_format_error({:bad_return, {{mod, :start, args}, return}}) do
|
||||
Exception.format_mfa(mod, :start, args) <>
|
||||
" returned a bad value: " <> inspect(return)
|
||||
end
|
||||
|
||||
defp impl_format_error({:already_started, app}) when is_atom(app) do
|
||||
"already started application #{app}"
|
||||
end
|
||||
|
||||
defp impl_format_error({:not_started, app}) when is_atom(app) do
|
||||
"not started application #{app}"
|
||||
end
|
||||
|
||||
defp impl_format_error({:bad_application, app}) do
|
||||
"bad application: #{inspect(app)}"
|
||||
end
|
||||
|
||||
defp impl_format_error({:already_loaded, app}) when is_atom(app) do
|
||||
"already loaded application #{app}"
|
||||
end
|
||||
|
||||
defp impl_format_error({:not_loaded, app}) when is_atom(app) do
|
||||
"not loaded application #{app}"
|
||||
end
|
||||
|
||||
defp impl_format_error({:invalid_restart_type, restart}) do
|
||||
"invalid application restart type: #{inspect(restart)}"
|
||||
end
|
||||
|
||||
defp impl_format_error({:invalid_name, name}) do
|
||||
"invalid application name: #{inspect(name)}"
|
||||
end
|
||||
|
||||
defp impl_format_error({:invalid_options, opts}) do
|
||||
"invalid application options: #{inspect(opts)}"
|
||||
end
|
||||
|
||||
defp impl_format_error({:badstartspec, spec}) do
|
||||
"bad application start specs: #{inspect(spec)}"
|
||||
end
|
||||
|
||||
defp impl_format_error({'no such file or directory', file}) do
|
||||
"could not find application file: #{file}"
|
||||
end
|
||||
|
||||
defp impl_format_error(reason) do
|
||||
Exception.format_exit(reason)
|
||||
end
|
||||
end
|
||||
@@ -1,55 +1,8 @@
|
||||
defmodule Application.Behaviour do
|
||||
@moduledoc """
|
||||
Default callbacks for applications.
|
||||
@moduledoc false
|
||||
|
||||
In Erlang/OTP, an application is a component that can be started
|
||||
and stopped as a unit, and which can be reused in other systems.
|
||||
|
||||
The first step in creating an application is to define an application specification.
|
||||
For example, if your application is named `:my_app`, an app specification
|
||||
should exist at `ebin/my_app.app`. This file is usually defined by
|
||||
build tools like Mix.
|
||||
|
||||
With the app specification in hand, we must define
|
||||
application module callbacks that control how to start and stop
|
||||
instances of the application. This module is about defining such callbacks.
|
||||
|
||||
There are two callbacks which must be implemented:
|
||||
|
||||
1. `start(type, args)` - must return `{ :ok, pid }` or
|
||||
`{ :ok, pid, state }`, where `pid` is the process identifier
|
||||
of the supervisor tree root and `state` is application defined
|
||||
state information;
|
||||
|
||||
2. `stop(state)` - receives the `state` returned by `start` and should
|
||||
do any necessary clean up. Notice that shutting down the supervisor
|
||||
is automatically handled by the VM;
|
||||
|
||||
When using this module, it tags the module behaviour as
|
||||
`:application` and provides a default `stop/1` callback. The `start/2` callback
|
||||
still needs to be implemented by the user.
|
||||
|
||||
You can learn more about the `:application` module, the application
|
||||
specification and the application module callbacks from these sources:
|
||||
|
||||
* http://www.erlang.org/doc/man/application.html
|
||||
* http://www.erlang.org/doc/design_principles/applications.html
|
||||
* http://learnyousomeerlang.com/building-otp-applications
|
||||
|
||||
## Example
|
||||
|
||||
defmodule MyApp do
|
||||
use Application.Behaviour
|
||||
|
||||
def start(_type, args) do
|
||||
MyApp.Sup.start_link(args)
|
||||
end
|
||||
end
|
||||
|
||||
"""
|
||||
|
||||
@doc false
|
||||
defmacro __using__(_) do
|
||||
# IO.write :stderr, "use Application.Behaviour is deprecated, please use Application instead\n#{Exception.format_stacktrace}"
|
||||
quote location: :keep do
|
||||
@behaviour :application
|
||||
|
||||
|
||||
+15
-15
@@ -15,11 +15,11 @@ defmodule Base do
|
||||
b32_alphabet = Enum.with_index 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567'
|
||||
b32hex_alphabet = Enum.with_index '0123456789ABCDEFGHIJKLMNOPQRSTUV'
|
||||
|
||||
Enum.each [ { :enc16, :dec16, b16_alphabet },
|
||||
{ :enc64, :dec64, b64_alphabet },
|
||||
{ :enc32, :dec32, b32_alphabet },
|
||||
{ :enc64url, :dec64url, b64url_alphabet },
|
||||
{ :enc32hex, :dec32hex, b32hex_alphabet } ], fn({enc, dec, alphabet}) ->
|
||||
Enum.each [ {:enc16, :dec16, b16_alphabet},
|
||||
{:enc64, :dec64, b64_alphabet},
|
||||
{:enc32, :dec32, b32_alphabet},
|
||||
{:enc64url, :dec64url, b64url_alphabet},
|
||||
{:enc32hex, :dec32hex, b32hex_alphabet} ], fn({enc, dec, alphabet}) ->
|
||||
for {encoding, value} <- alphabet do
|
||||
defp unquote(enc)(unquote(value)), do: unquote(encoding)
|
||||
defp unquote(dec)(unquote(encoding)), do: unquote(value)
|
||||
@@ -61,9 +61,9 @@ defmodule Base do
|
||||
{:ok, "foobar"}
|
||||
|
||||
"""
|
||||
@spec decode16(binary) :: { :ok, binary } | :error
|
||||
@spec decode16(binary) :: {:ok, binary} | :error
|
||||
def decode16(string) when is_binary(string) do
|
||||
{ :ok, decode16!(string) }
|
||||
{:ok, decode16!(string)}
|
||||
rescue
|
||||
ArgumentError -> :error
|
||||
end
|
||||
@@ -130,9 +130,9 @@ defmodule Base do
|
||||
{:ok, "foobar"}
|
||||
|
||||
"""
|
||||
@spec decode64(binary) :: { :ok, binary } | :error
|
||||
@spec decode64(binary) :: {:ok, binary} | :error
|
||||
def decode64(string) when is_binary(string) do
|
||||
{ :ok, do_decode64(string, &dec64/1) }
|
||||
{:ok, do_decode64(string, &dec64/1)}
|
||||
rescue
|
||||
ArgumentError -> :error
|
||||
end
|
||||
@@ -203,9 +203,9 @@ defmodule Base do
|
||||
{:ok, <<255,127,254,252>>}
|
||||
|
||||
"""
|
||||
@spec url_decode64(binary) :: { :ok, binary } | :error
|
||||
@spec url_decode64(binary) :: {:ok, binary} | :error
|
||||
def url_decode64(string) when is_binary(string) do
|
||||
{ :ok, do_decode64(string, &dec64url/1) }
|
||||
{:ok, do_decode64(string, &dec64url/1)}
|
||||
rescue
|
||||
ArgumentError -> :error
|
||||
end
|
||||
@@ -265,9 +265,9 @@ defmodule Base do
|
||||
{:ok, "foobar"}
|
||||
|
||||
"""
|
||||
@spec decode32(binary) :: { :ok, binary } | :error
|
||||
@spec decode32(binary) :: {:ok, binary} | :error
|
||||
def decode32(string) do
|
||||
{ :ok, do_decode32(string, &dec32/1) }
|
||||
{:ok, do_decode32(string, &dec32/1)}
|
||||
rescue
|
||||
ArgumentError -> :error
|
||||
end
|
||||
@@ -328,9 +328,9 @@ defmodule Base do
|
||||
{:ok, "foobar"}
|
||||
|
||||
"""
|
||||
@spec hex_decode32(binary) :: { :ok, binary } | :error
|
||||
@spec hex_decode32(binary) :: {:ok, binary} | :error
|
||||
def hex_decode32(string) when is_binary(string) do
|
||||
{ :ok, do_decode32(string, &dec32hex/1) }
|
||||
{:ok, do_decode32(string, &dec32hex/1)}
|
||||
rescue
|
||||
ArgumentError -> :error
|
||||
end
|
||||
|
||||
+15
-15
@@ -12,7 +12,7 @@ defmodule Behaviour do
|
||||
use Behaviour
|
||||
|
||||
@doc "Parses the given URL"
|
||||
defcallback parse(uri_info :: URI.Info.t) :: URI.Info.t
|
||||
defcallback parse(uri_info :: URI.t) :: URI.t
|
||||
|
||||
@doc "Defines a default port"
|
||||
defcallback default_port() :: integer
|
||||
@@ -57,34 +57,34 @@ defmodule Behaviour do
|
||||
do_defmacrocallback(split_spec(spec, quote(do: Macro.t)), __CALLER__)
|
||||
end
|
||||
|
||||
defp split_spec({ :when, _, [{ :::, _, [spec, return] }, guard] }, _default) do
|
||||
{ spec, return, guard }
|
||||
defp split_spec({:when, _, [{:::, _, [spec, return]}, guard]}, _default) do
|
||||
{spec, return, guard}
|
||||
end
|
||||
|
||||
defp split_spec({ :when, _, [spec, guard] }, default) do
|
||||
{ spec, default, guard }
|
||||
defp split_spec({:when, _, [spec, guard]}, default) do
|
||||
{spec, default, guard}
|
||||
end
|
||||
|
||||
defp split_spec({ :::, _, [spec, return] }, _default) do
|
||||
{ spec, return, [] }
|
||||
defp split_spec({:::, _, [spec, return]}, _default) do
|
||||
{spec, return, []}
|
||||
end
|
||||
|
||||
defp split_spec(spec, default) do
|
||||
{ spec, default, [] }
|
||||
{spec, default, []}
|
||||
end
|
||||
|
||||
defp do_defcallback({ spec, return, guards }, caller) do
|
||||
defp do_defcallback({spec, return, guards}, caller) do
|
||||
case Macro.decompose_call(spec) do
|
||||
{ name, args } ->
|
||||
{name, args} ->
|
||||
do_callback(:def, name, args, name, length(args), args, return, guards, caller)
|
||||
_ ->
|
||||
raise ArgumentError, message: "invalid syntax in defcallback #{Macro.to_string(spec)}"
|
||||
end
|
||||
end
|
||||
|
||||
defp do_defmacrocallback({ spec, return, guards }, caller) do
|
||||
defp do_defmacrocallback({spec, return, guards}, caller) do
|
||||
case Macro.decompose_call(spec) do
|
||||
{ name, args } ->
|
||||
{name, args} ->
|
||||
do_callback(:defmacro, :"MACRO-#{name}", [quote(do: env :: Macro.Env.t)|args],
|
||||
name, length(args), args, return, guards, caller)
|
||||
_ ->
|
||||
@@ -94,7 +94,7 @@ defmodule Behaviour do
|
||||
|
||||
defp do_callback(kind, name, args, docs_name, docs_arity, _docs_args, return, guards, caller) do
|
||||
Enum.each args, fn
|
||||
{ :::, _, [left, right] } ->
|
||||
{:::, _, [left, right]} ->
|
||||
ensure_not_default(left)
|
||||
ensure_not_default(right)
|
||||
left
|
||||
@@ -110,7 +110,7 @@ defmodule Behaviour do
|
||||
end
|
||||
end
|
||||
|
||||
defp ensure_not_default({ :\\, _, [_, _] }) do
|
||||
defp ensure_not_default({:\\, _, [_, _]}) do
|
||||
raise ArgumentError, message: "default arguments \\\\ not supported in defcallback/defmacrocallback"
|
||||
end
|
||||
|
||||
@@ -120,7 +120,7 @@ defmodule Behaviour do
|
||||
def store_docs(module, line, kind, name, arity) do
|
||||
doc = Module.get_attribute module, :doc
|
||||
Module.delete_attribute module, :doc
|
||||
Module.put_attribute module, :behaviour_docs, { { name, arity }, line, kind, doc }
|
||||
Module.put_attribute module, :behaviour_docs, {{name, arity}, line, kind, doc}
|
||||
end
|
||||
|
||||
@doc false
|
||||
|
||||
+39
-39
@@ -28,7 +28,7 @@ defmodule Code do
|
||||
allowing them to be required again.
|
||||
"""
|
||||
def unload_files(files) do
|
||||
:elixir_code_server.cast { :unload_files, files }
|
||||
:elixir_code_server.cast {:unload_files, files}
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -37,7 +37,7 @@ defmodule Code do
|
||||
The path is expanded with `Path.expand/1` before being appended.
|
||||
"""
|
||||
def append_path(path) do
|
||||
:code.add_pathz(Path.expand to_char_list(path))
|
||||
:code.add_pathz(to_char_list(Path.expand path))
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -46,7 +46,7 @@ defmodule Code do
|
||||
The path is expanded with `Path.expand/1` before being prepended.
|
||||
"""
|
||||
def prepend_path(path) do
|
||||
:code.add_patha(Path.expand to_char_list(path))
|
||||
:code.add_patha(to_char_list(Path.expand path))
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -55,7 +55,7 @@ defmodule Code do
|
||||
The path is expanded with `Path.expand/1` before being deleted.
|
||||
"""
|
||||
def delete_path(path) do
|
||||
:code.del_path(Path.expand to_char_list(path))
|
||||
:code.del_path(to_char_list(Path.expand path))
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -88,8 +88,8 @@ defmodule Code do
|
||||
`:macros` will no longer auto-import `Kernel` macros like `if`, `case`,
|
||||
etc.
|
||||
|
||||
Returns a tuple of the form `{ value, binding }`,
|
||||
where `value` is the the value returned from evaluating `string`.
|
||||
Returns a tuple of the form `{value, binding}`,
|
||||
where `value` is the value returned from evaluating `string`.
|
||||
If an error occurs while evaluating `string` an exception will be raised.
|
||||
|
||||
`binding` is a keyword list with the value of all variable bindings
|
||||
@@ -117,15 +117,15 @@ defmodule Code do
|
||||
"""
|
||||
def eval_string(string, binding \\ [], opts \\ [])
|
||||
|
||||
def eval_string(string, binding, Macro.Env[] = env) do
|
||||
{ value, binding, _env, _scope } = :elixir.eval to_char_list(string), binding, env.to_keywords
|
||||
{ value, binding }
|
||||
def eval_string(string, binding, %Macro.Env{} = env) do
|
||||
{value, binding, _env, _scope} = :elixir.eval to_char_list(string), binding, Map.to_list(env)
|
||||
{value, binding}
|
||||
end
|
||||
|
||||
def eval_string(string, binding, opts) when is_list(opts) do
|
||||
validate_eval_opts(opts)
|
||||
{ value, binding, _env, _scope } = :elixir.eval to_char_list(string), binding, opts
|
||||
{ value, binding }
|
||||
{value, binding, _env, _scope} = :elixir.eval to_char_list(string), binding, opts
|
||||
{value, binding}
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -149,15 +149,15 @@ defmodule Code do
|
||||
"""
|
||||
def eval_quoted(quoted, binding \\ [], opts \\ [])
|
||||
|
||||
def eval_quoted(quoted, binding, Macro.Env[] = env) do
|
||||
{ value, binding, _env, _scope } = :elixir.eval_quoted quoted, binding, env.to_keywords
|
||||
{ value, binding }
|
||||
def eval_quoted(quoted, binding, %Macro.Env{} = env) do
|
||||
{value, binding, _env, _scope} = :elixir.eval_quoted quoted, binding, Map.to_list(env)
|
||||
{value, binding}
|
||||
end
|
||||
|
||||
def eval_quoted(quoted, binding, opts) when is_list(opts) do
|
||||
validate_eval_opts(opts)
|
||||
{ value, binding, _env, _scope } = :elixir.eval_quoted quoted, binding, opts
|
||||
{ value, binding }
|
||||
{value, binding, _env, _scope} = :elixir.eval_quoted quoted, binding, opts
|
||||
{value, binding}
|
||||
end
|
||||
|
||||
defp validate_eval_opts(opts) do
|
||||
@@ -176,32 +176,32 @@ defmodule Code do
|
||||
end
|
||||
|
||||
defp validate_aliases(kind, aliases) do
|
||||
valid = is_list(aliases) and Enum.all?(aliases, fn { k, v } ->
|
||||
valid = is_list(aliases) and Enum.all?(aliases, fn {k, v} ->
|
||||
is_atom(k) and is_atom(v)
|
||||
end)
|
||||
|
||||
unless valid do
|
||||
raise ArgumentError, message: "expected :#{kind} option given to eval in the format: [{ module, module }]"
|
||||
raise ArgumentError, message: "expected :#{kind} option given to eval in the format: [{module, module}]"
|
||||
end
|
||||
end
|
||||
|
||||
defp validate_imports(kind, imports) do
|
||||
valid = is_list(imports) and Enum.all?(imports, fn { k, v } ->
|
||||
is_atom(k) and is_list(v) and Enum.all?(v, fn { name, arity } ->
|
||||
valid = is_list(imports) and Enum.all?(imports, fn {k, v} ->
|
||||
is_atom(k) and is_list(v) and Enum.all?(v, fn {name, arity} ->
|
||||
is_atom(name) and is_integer(arity)
|
||||
end)
|
||||
end)
|
||||
|
||||
unless valid do
|
||||
raise ArgumentError, message: "expected :#{kind} option given to eval in the format: [{ module, [{ name, arity }] }]"
|
||||
raise ArgumentError, message: "expected :#{kind} option given to eval in the format: [{module, [{name, arity}]}]"
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Convert the given string to its quoted form.
|
||||
|
||||
Returns `{ :ok, quoted_form }`
|
||||
if it succeeds, `{ :error, { line, error, token } }` otherwise.
|
||||
Returns `{:ok, quoted_form}`
|
||||
if it succeeds, `{:error, {line, error, token}}` otherwise.
|
||||
|
||||
## Options
|
||||
|
||||
@@ -261,7 +261,7 @@ defmodule Code do
|
||||
Accepts `relative_to` as an argument to tell where the file is located.
|
||||
If the file was already required/loaded, loads it again.
|
||||
|
||||
It returns a list of tuples `{ ModuleName, <<byte_code>> }`, one tuple for
|
||||
It returns a list of tuples `{ModuleName, <<byte_code>>}`, one tuple for
|
||||
each module defined in the file.
|
||||
|
||||
Notice that if `load_file` is invoked by different processes concurrently,
|
||||
@@ -270,9 +270,9 @@ defmodule Code do
|
||||
"""
|
||||
def load_file(file, relative_to \\ nil) when is_binary(file) do
|
||||
file = find_file(file, relative_to)
|
||||
:elixir_code_server.call { :acquire, file }
|
||||
:elixir_code_server.call {:acquire, file}
|
||||
loaded = :elixir_compiler.file file
|
||||
:elixir_code_server.cast { :loaded, file }
|
||||
:elixir_code_server.cast {:loaded, file}
|
||||
loaded
|
||||
end
|
||||
|
||||
@@ -294,14 +294,14 @@ defmodule Code do
|
||||
def require_file(file, relative_to \\ nil) when is_binary(file) do
|
||||
file = find_file(file, relative_to)
|
||||
|
||||
case :elixir_code_server.call({ :acquire, file }) do
|
||||
case :elixir_code_server.call({:acquire, file}) do
|
||||
:loaded ->
|
||||
nil
|
||||
{ :queued, ref } ->
|
||||
receive do { :elixir_code_server, ^ref, :loaded } -> nil end
|
||||
{:queued, ref} ->
|
||||
receive do {:elixir_code_server, ^ref, :loaded} -> nil end
|
||||
:proceed ->
|
||||
loaded = :elixir_compiler.file file
|
||||
:elixir_code_server.cast { :loaded, file }
|
||||
:elixir_code_server.cast {:loaded, file}
|
||||
loaded
|
||||
end
|
||||
end
|
||||
@@ -336,7 +336,7 @@ defmodule Code do
|
||||
|
||||
"""
|
||||
def compiler_options(opts) do
|
||||
:elixir_code_server.cast { :compiler_options, opts }
|
||||
:elixir_code_server.cast {:compiler_options, opts}
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -367,8 +367,8 @@ defmodule Code do
|
||||
If the module is already loaded, this works as no-op. If the module
|
||||
was not yet loaded, it tries to load it.
|
||||
|
||||
If it succeeds loading the module, it returns `{ :module, module }`.
|
||||
If not, returns `{ :error, reason }` with the error reason.
|
||||
If it succeeds loading the module, it returns `{:module, module}`.
|
||||
If not, returns `{:error, reason}` with the error reason.
|
||||
|
||||
## Code loading on the Erlang VM
|
||||
|
||||
@@ -411,7 +411,7 @@ defmodule Code do
|
||||
otherwise.
|
||||
"""
|
||||
def ensure_loaded?(module) do
|
||||
match?({ :module, ^module }, ensure_loaded(module))
|
||||
match?({:module, ^module}, ensure_loaded(module))
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -421,21 +421,21 @@ defmodule Code do
|
||||
not loaded yet, it checks if it needs to be compiled first and then
|
||||
tries to load it.
|
||||
|
||||
If it succeeds loading the module, it returns `{ :module, module }`.
|
||||
If not, returns `{ :error, reason }` with the error reason.
|
||||
If it succeeds loading the module, it returns `{:module, module}`.
|
||||
If not, returns `{:error, reason}` with the error reason.
|
||||
|
||||
Check `ensure_loaded/1` for more information on module loading
|
||||
and when to use `ensure_loaded/1` or `ensure_compiled/1`.
|
||||
"""
|
||||
def ensure_compiled(module) when is_atom(module) do
|
||||
case :code.ensure_loaded(module) do
|
||||
{ :error, :nofile } = error ->
|
||||
{:error, :nofile} = error ->
|
||||
case :erlang.get(:elixir_ensure_compiled) do
|
||||
:undefined -> error
|
||||
_ ->
|
||||
try do
|
||||
module.__info__(:module)
|
||||
{ :module, module }
|
||||
{:module, module}
|
||||
rescue
|
||||
UndefinedFunctionError -> error
|
||||
end
|
||||
@@ -452,7 +452,7 @@ defmodule Code do
|
||||
Returns `false` otherwise.
|
||||
"""
|
||||
def ensure_compiled?(module) do
|
||||
match?({ :module, ^module }, ensure_compiled(module))
|
||||
match?({:module, ^module}, ensure_compiled(module))
|
||||
end
|
||||
|
||||
## Helpers
|
||||
|
||||
@@ -11,7 +11,7 @@ defprotocol Collectable do
|
||||
If a collection implements both `Enumerable` and `Collectable`, both
|
||||
operations can be combined with `Enum.traverse/2`:
|
||||
|
||||
iex> Enum.traverse(%{ a: 1, b: 2 }, fn { k, v } -> { k, v * 2 } end)
|
||||
iex> Enum.traverse(%{a: 1, b: 2}, fn {k, v} -> {k, v * 2} end)
|
||||
%{a: 2, b: 4}
|
||||
|
||||
## Why Collectable?
|
||||
@@ -38,7 +38,7 @@ defprotocol Collectable do
|
||||
and `empty/1`, one can, for example, implement a traversal mechanism.
|
||||
"""
|
||||
|
||||
@type command :: { :cont, term } | :done | :halt
|
||||
@type command :: {:cont, term} | :done | :halt
|
||||
|
||||
@doc """
|
||||
Receives a collectable structure and returns an empty one.
|
||||
@@ -51,7 +51,7 @@ defprotocol Collectable do
|
||||
the initial accumulation value.
|
||||
|
||||
The returned function receives a collectable and injects a given
|
||||
value into it for every `{ :cont, term }` instruction.
|
||||
value into it for every `{:cont, term}` instruction.
|
||||
|
||||
`:done` is passed when no further values will be injected, useful
|
||||
for closing resources and normalizing values. A collectable must
|
||||
@@ -60,7 +60,7 @@ defprotocol Collectable do
|
||||
If injection is suddenly interrupted, `:halt` is passed and it can
|
||||
return any value, as it won't be used.
|
||||
"""
|
||||
@spec into(t) :: { term, (term, command -> t | term) }
|
||||
@spec into(t) :: {term, (term, command -> t | term)}
|
||||
def into(collectable)
|
||||
end
|
||||
|
||||
@@ -70,11 +70,11 @@ defimpl Collectable, for: List do
|
||||
end
|
||||
|
||||
def into(original) do
|
||||
{ [], fn
|
||||
list, { :cont, x } -> [x|list]
|
||||
{[], fn
|
||||
list, {:cont, x} -> [x|list]
|
||||
list, :done -> original ++ :lists.reverse(list)
|
||||
_, :halt -> :ok
|
||||
end }
|
||||
end}
|
||||
end
|
||||
end
|
||||
|
||||
@@ -84,11 +84,11 @@ defimpl Collectable, for: BitString do
|
||||
end
|
||||
|
||||
def into(original) do
|
||||
{ original, fn
|
||||
bitstring, { :cont, x } -> <<bitstring :: bits, x :: bits>>
|
||||
{original, fn
|
||||
bitstring, {:cont, x} -> <<bitstring :: bits, x :: bits>>
|
||||
bitstring, :done -> bitstring
|
||||
_, :halt -> :ok
|
||||
end }
|
||||
end}
|
||||
end
|
||||
end
|
||||
|
||||
@@ -98,7 +98,7 @@ defimpl Collectable, for: Function do
|
||||
end
|
||||
|
||||
def into(function) do
|
||||
{ function, function }
|
||||
{function, function}
|
||||
end
|
||||
end
|
||||
|
||||
@@ -108,10 +108,10 @@ defimpl Collectable, for: Map do
|
||||
end
|
||||
|
||||
def into(original) do
|
||||
{ original, fn
|
||||
map, { :cont, { k, v } } -> :maps.put(k, v, map)
|
||||
{original, fn
|
||||
map, {:cont, {k, v}} -> :maps.put(k, v, map)
|
||||
map, :done -> map
|
||||
_, :halt -> :ok
|
||||
end }
|
||||
end}
|
||||
end
|
||||
end
|
||||
|
||||
+20
-20
@@ -37,16 +37,15 @@ defmodule Dict do
|
||||
|
||||
@type key :: any
|
||||
@type value :: any
|
||||
@type t :: tuple | list | map
|
||||
@type t :: list | map
|
||||
|
||||
defcallback new :: t
|
||||
defcallback delete(t, key) :: t
|
||||
defcallback drop(t, Enum.t) :: t
|
||||
defcallback empty(t) :: t
|
||||
defcallback equal?(t, t) :: boolean
|
||||
defcallback get(t, key) :: value
|
||||
defcallback get(t, key, value) :: value
|
||||
defcallback fetch(t, key) :: { :ok, value } | :error
|
||||
defcallback fetch(t, key) :: {:ok, value} | :error
|
||||
defcallback fetch!(t, key) :: value | no_return
|
||||
defcallback has_key?(t, key) :: boolean
|
||||
defcallback keys(t) :: [key]
|
||||
@@ -72,9 +71,10 @@ defmodule Dict do
|
||||
%{} ->
|
||||
Map
|
||||
x when is_tuple(x) ->
|
||||
IO.write :stderr, "Using records with the Dict module is deprecated, please use structs instead\n#{Exception.format_stacktrace}"
|
||||
elem(x, 0)
|
||||
x when is_list(x) ->
|
||||
ListDict
|
||||
Keyword
|
||||
x ->
|
||||
unsupported_dict(x)
|
||||
end
|
||||
@@ -165,14 +165,14 @@ defmodule Dict do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns `{ :ok, value }` associated with `key` in `dict`.
|
||||
Returns `{:ok, value}` associated with `key` in `dict`.
|
||||
If `dict` does not contain `key`, returns `:error`.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> d = Enum.into([a: 1], dict_impl.new)
|
||||
iex> Dict.fetch(d, :a)
|
||||
{ :ok, 1 }
|
||||
{:ok, 1}
|
||||
iex> Dict.fetch(d, :b)
|
||||
:error
|
||||
|
||||
@@ -288,8 +288,8 @@ defmodule Dict do
|
||||
if target1 == target2 do
|
||||
target1.merge(dict1, dict2, fun)
|
||||
else
|
||||
Enumerable.reduce(dict2, { :cont, dict1 }, fn({ k, v }, acc) ->
|
||||
{ :cont, target1.update(acc, k, v, fn(other) -> fun.(k, other, v) end) }
|
||||
Enumerable.reduce(dict2, {:cont, dict1}, fn({k, v}, acc) ->
|
||||
{:cont, target1.update(acc, k, v, fn(other) -> fun.(k, other, v) end)}
|
||||
end) |> elem(1)
|
||||
end
|
||||
end
|
||||
@@ -366,19 +366,19 @@ defmodule Dict do
|
||||
## Examples
|
||||
|
||||
iex> d = Enum.into([a: 1, b: 2, c: 3, d: 4], dict_impl.new)
|
||||
iex> { d1, d2 } = Dict.split(d, [:a, :c, :e])
|
||||
iex> { Dict.to_list(d1) |> Enum.sort, Dict.to_list(d2) |> Enum.sort }
|
||||
{ [a: 1, c: 3], [b: 2, d: 4] }
|
||||
iex> {d1, d2} = Dict.split(d, [:a, :c, :e])
|
||||
iex> {Dict.to_list(d1) |> Enum.sort, Dict.to_list(d2) |> Enum.sort}
|
||||
{[a: 1, c: 3], [b: 2, d: 4]}
|
||||
|
||||
iex> d = Enum.into([], dict_impl.new)
|
||||
iex> { d1, d2 } = Dict.split(d, [:a, :c])
|
||||
iex> { Dict.to_list(d1), Dict.to_list(d2) }
|
||||
{ [], [] }
|
||||
iex> {d1, d2} = Dict.split(d, [:a, :c])
|
||||
iex> {Dict.to_list(d1), Dict.to_list(d2)}
|
||||
{[], []}
|
||||
|
||||
iex> d = Enum.into([a: 1, b: 2], dict_impl.new)
|
||||
iex> { d1, d2 } = Dict.split(d, [:a, :b, :c])
|
||||
iex> { Dict.to_list(d1) |> Enum.sort, Dict.to_list(d2) }
|
||||
{ [a: 1, b: 2], [] }
|
||||
iex> {d1, d2} = Dict.split(d, [:a, :b, :c])
|
||||
iex> {Dict.to_list(d1) |> Enum.sort, Dict.to_list(d2)}
|
||||
{[a: 1, b: 2], []}
|
||||
|
||||
"""
|
||||
@spec split(t, [key]) :: {t, t}
|
||||
@@ -465,10 +465,10 @@ defmodule Dict do
|
||||
target1.equal?(dict1, dict2)
|
||||
|
||||
target1.size(dict1) == target2.size(dict2) ->
|
||||
Enumerable.reduce(dict2, { :cont, true }, fn({ k, v }, _acc) ->
|
||||
Enumerable.reduce(dict2, {:cont, true}, fn({k, v}, _acc) ->
|
||||
case target1.fetch(dict1, k) do
|
||||
{ :ok, ^v } -> { :cont, true }
|
||||
_ -> { :halt, false }
|
||||
{:ok, ^v} -> {:cont, true}
|
||||
_ -> {:halt, false}
|
||||
end
|
||||
end) |> elem(1)
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ defmodule Dict.Behaviour do
|
||||
|
||||
# implement required functions (see below)
|
||||
|
||||
# override default implementations if needed
|
||||
# override default implementations if optimization is needed
|
||||
end
|
||||
|
||||
The client module must contain the following functions:
|
||||
@@ -22,7 +22,6 @@ defmodule Dict.Behaviour do
|
||||
* `put/3`
|
||||
* `reduce/3`
|
||||
* `size/1`
|
||||
* `update/4`
|
||||
|
||||
All functions, except `reduce/3`, are required by the Dict behaviour.
|
||||
`reduce/3` must be implemtented as per the Enumerable protocol.
|
||||
@@ -39,10 +38,15 @@ defmodule Dict.Behaviour do
|
||||
* `keys/1`
|
||||
* `merge/2`
|
||||
* `merge/3`
|
||||
* `pop/2`
|
||||
* `pop/3`
|
||||
* `put_new/3`
|
||||
* `split/2`
|
||||
* `take/2`
|
||||
* `to_list/1`
|
||||
* `values/1`
|
||||
* `update/4`
|
||||
* `update!/3`
|
||||
|
||||
All of these functions are defined as overridable, so you can provide your own
|
||||
implementation if needed.
|
||||
@@ -71,20 +75,20 @@ defmodule Dict.Behaviour do
|
||||
|
||||
def get(dict, key, default \\ nil) do
|
||||
case fetch(dict, key) do
|
||||
{ :ok, value } -> value
|
||||
{:ok, value} -> value
|
||||
:error -> default
|
||||
end
|
||||
end
|
||||
|
||||
def fetch!(dict, key) do
|
||||
case fetch(dict, key) do
|
||||
{ :ok, value } -> value
|
||||
{:ok, value} -> value
|
||||
:error -> raise KeyError, key: key, term: dict
|
||||
end
|
||||
end
|
||||
|
||||
def has_key?(dict, key) do
|
||||
match? { :ok, _ }, fetch(dict, key)
|
||||
match? {:ok, _}, fetch(dict, key)
|
||||
end
|
||||
|
||||
def put_new(dict, key, value) do
|
||||
@@ -98,27 +102,27 @@ defmodule Dict.Behaviour do
|
||||
def take(dict, keys) do
|
||||
Enum.reduce keys, new, fn key, acc ->
|
||||
case fetch(dict, key) do
|
||||
{ :ok, value } -> put(acc, key, value)
|
||||
{:ok, value} -> put(acc, key, value)
|
||||
:error -> acc
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def to_list(dict) do
|
||||
reduce(dict, { :cont, [] }, fn
|
||||
kv, acc -> { :cont, [kv|acc] }
|
||||
reduce(dict, {:cont, []}, fn
|
||||
kv, acc -> {:cont, [kv|acc]}
|
||||
end) |> elem(1) |> :lists.reverse
|
||||
end
|
||||
|
||||
def keys(dict) do
|
||||
reduce(dict, { :cont, [] }, fn
|
||||
{k, _}, acc -> { :cont, [k|acc] }
|
||||
reduce(dict, {:cont, []}, fn
|
||||
{k, _}, acc -> {:cont, [k|acc]}
|
||||
end) |> elem(1) |> :lists.reverse
|
||||
end
|
||||
|
||||
def values(dict) do
|
||||
reduce(dict, { :cont, [] }, fn
|
||||
{_, v}, acc -> { :cont, [v|acc] }
|
||||
reduce(dict, {:cont, []}, fn
|
||||
{_, v}, acc -> {:cont, [v|acc]}
|
||||
end) |> elem(1) |> :lists.reverse
|
||||
end
|
||||
|
||||
@@ -129,24 +133,67 @@ defmodule Dict.Behaviour do
|
||||
case size(dict1) == size(dict2) do
|
||||
false -> false
|
||||
true ->
|
||||
reduce(dict1, { :cont, true }, fn({ k, v }, _acc) ->
|
||||
reduce(dict1, {:cont, true}, fn({k, v}, _acc) ->
|
||||
case fetch(dict2, k) do
|
||||
{ :ok, ^v } -> { :cont, true }
|
||||
_ -> { :halt, false }
|
||||
{:ok, ^v} -> {:cont, true}
|
||||
_ -> {:halt, false}
|
||||
end
|
||||
end) |> elem(1)
|
||||
end
|
||||
end
|
||||
|
||||
def merge(dict1, dict2, fun \\ fn(_k, _v1, v2) -> v2 end) do
|
||||
reduce(dict1, { :cont, dict2 }, fn { k, v1 }, acc ->
|
||||
{ :cont, update(acc, k, v1, &fun.(k, v1, &1)) }
|
||||
reduce(dict1, {:cont, dict2}, fn {k, v1}, acc ->
|
||||
{:cont, update(acc, k, v1, &fun.(k, v1, &1))}
|
||||
end) |> elem(1)
|
||||
end
|
||||
|
||||
def update(dict, key, initial, fun) do
|
||||
case fetch(dict, key) do
|
||||
{ :ok, value } ->
|
||||
put(dict, key, fun.(value))
|
||||
|
||||
:error ->
|
||||
put(dict, key, initial)
|
||||
end
|
||||
end
|
||||
|
||||
def update!(dict, key, fun) do
|
||||
case fetch(dict, key) do
|
||||
{ :ok, value } ->
|
||||
put(dict, key, fun.(value))
|
||||
|
||||
:error ->
|
||||
raise KeyError, key: key, term: dict
|
||||
end
|
||||
end
|
||||
|
||||
def pop(dict, key, default \\ nil) do
|
||||
case fetch(dict, key) do
|
||||
{ :ok, value } ->
|
||||
{ value, delete(dict, key) }
|
||||
|
||||
:error ->
|
||||
{ default, dict }
|
||||
end
|
||||
end
|
||||
|
||||
def split(dict, keys) do
|
||||
Enum.reduce keys, { new, dict }, fn key, { inc, exc } = acc ->
|
||||
case fetch(exc, key) do
|
||||
{ :ok, value } ->
|
||||
{ put(inc, key, value), delete(exc, key) }
|
||||
|
||||
:error ->
|
||||
acc
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defoverridable merge: 2, merge: 3, equal?: 2, to_list: 1, keys: 1,
|
||||
values: 1, take: 2, drop: 2, get: 2, get: 3, fetch!: 2,
|
||||
has_key?: 2, put_new: 3
|
||||
has_key?: 2, put_new: 3, pop: 2, pop: 3, update: 4, update!: 3,
|
||||
split: 2
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
+214
-205
@@ -16,8 +16,8 @@ defprotocol Enumerable do
|
||||
Internally, `Enum.map/2` is implemented as follows:
|
||||
|
||||
def map(enum, fun) do
|
||||
reducer = fn x, acc -> { :cont, [fun.(x)|acc] } end
|
||||
Enumerable.reduce(enum, { :cont, [] }, reducer) |> elem(1) |> :lists.reverse()
|
||||
reducer = fn x, acc -> {:cont, [fun.(x)|acc]} end
|
||||
Enumerable.reduce(enum, {:cont, []}, reducer) |> elem(1) |> :lists.reverse()
|
||||
end
|
||||
|
||||
Notice the user given function is wrapped into a `reducer` function.
|
||||
@@ -50,7 +50,7 @@ defprotocol Enumerable do
|
||||
In case a reducer function returns a `:suspend` accumulator,
|
||||
it must be explicitly handled by the caller and never leak.
|
||||
"""
|
||||
@type acc :: { :cont, term } | { :halt, term } | { :suspend, term }
|
||||
@type acc :: {:cont, term} | {:halt, term} | {:suspend, term}
|
||||
|
||||
@typedoc """
|
||||
The reducer function.
|
||||
@@ -76,7 +76,7 @@ defprotocol Enumerable do
|
||||
Furthermore, a `:suspend` call must always be followed by another call,
|
||||
eventually halting or continuing until the end.
|
||||
"""
|
||||
@type result :: { :done, term } | { :halted, term } | { :suspended, term, continuation }
|
||||
@type result :: {:done, term} | {:halted, term} | {:suspended, term, continuation}
|
||||
|
||||
@typedoc """
|
||||
A partially applied reduce function.
|
||||
@@ -102,10 +102,10 @@ defprotocol Enumerable do
|
||||
|
||||
As an example, here is the implementation of `reduce` for lists:
|
||||
|
||||
def reduce(_, { :halt, acc }, _fun), do: { :halted, acc }
|
||||
def reduce(list, { :suspend, acc }, fun), do: { :suspended, acc, &reduce(list, &1, fun) }
|
||||
def reduce([], { :cont, acc }, _fun), do: { :done, acc }
|
||||
def reduce([h|t], { :cont, acc }, fun), do: reduce(t, fun.(h, acc), fun)
|
||||
def reduce(_, {:halt, acc}, _fun), do: {:halted, acc}
|
||||
def reduce(list, {:suspend, acc}, fun), do: {:suspended, acc, &reduce(list, &1, fun)}
|
||||
def reduce([], {:cont, acc}, _fun), do: {:done, acc}
|
||||
def reduce([h|t], {:cont, acc}, fun), do: reduce(t, fun.(h, acc), fun)
|
||||
|
||||
"""
|
||||
@spec reduce(t, acc, reducer) :: result
|
||||
@@ -114,20 +114,29 @@ defprotocol Enumerable do
|
||||
@doc """
|
||||
Checks if a value exists within the collection.
|
||||
|
||||
It should return `{ :ok, boolean }` if membership can be tested
|
||||
faster than linear time with the match (`===`) operator, otherwise
|
||||
should return `{ :error, __MODULE__ }`.
|
||||
It should return `{:ok, boolean}`.
|
||||
|
||||
If `{:error, __MODULE__}` is returned a default algorithm using `reduce` and
|
||||
the match (`===`) operator is used. This algorithm runs in linear time.
|
||||
|
||||
Please force use of the default algorithm unless you can implement an
|
||||
algorithm that is significantly faster.
|
||||
"""
|
||||
@spec member?(t, term) :: { :ok, boolean } | { :error, module }
|
||||
@spec member?(t, term) :: {:ok, boolean} | {:error, module}
|
||||
def member?(collection, value)
|
||||
|
||||
@doc """
|
||||
Retrieves the collection's size.
|
||||
|
||||
Should return `{ :ok, size }` if the size is pre-calculated,
|
||||
`{ :error, __MODULE__ }` otherwise.
|
||||
It should return `{:ok, size}`.
|
||||
|
||||
If `{:error, __MODULE__}` is returned a default algorithm using `reduce` and
|
||||
the match (`===`) operator is used. This algorithm runs in linear time.
|
||||
|
||||
Please force use of the default algorithm unless you can implement an
|
||||
algorithm that is significantly faster.
|
||||
"""
|
||||
@spec count(t) :: { :ok, non_neg_integer } | { :error, module }
|
||||
@spec count(t) :: {:ok, non_neg_integer} | {:error, module}
|
||||
def count(collection)
|
||||
end
|
||||
|
||||
@@ -142,10 +151,10 @@ defmodule Enum do
|
||||
[2,4,6]
|
||||
|
||||
Some particular types, like dictionaries, yield a specific format on
|
||||
enumeration. For dicts, the argument is always a `{ key, value }` tuple:
|
||||
enumeration. For dicts, the argument is always a `{key, value}` tuple:
|
||||
|
||||
iex> dict = %{a: 1, b: 2}
|
||||
iex> Enum.map(dict, fn { k, v } -> { k, v * 2 } end)
|
||||
iex> Enum.map(dict, fn {k, v} -> {k, v * 2} end)
|
||||
[a: 2, b: 4]
|
||||
|
||||
Note that the functions in the `Enum` module are eager: they always start
|
||||
@@ -172,16 +181,16 @@ defmodule Enum do
|
||||
require Stream.Reducers, as: R
|
||||
|
||||
defmacrop cont(_, entry, acc) do
|
||||
quote do: { :cont, [unquote(entry)|unquote(acc)] }
|
||||
quote do: {:cont, [unquote(entry)|unquote(acc)]}
|
||||
end
|
||||
|
||||
defmacrop acc(h, n, _) do
|
||||
quote do: { unquote(h), unquote(n) }
|
||||
quote do: {unquote(h), unquote(n)}
|
||||
end
|
||||
|
||||
defmacrop cont_with_acc(f, entry, h, n, _) do
|
||||
quote do
|
||||
{ :cont, { [unquote(entry)|unquote(h)], unquote(n) } }
|
||||
{:cont, {[unquote(entry)|unquote(h)], unquote(n)}}
|
||||
end
|
||||
end
|
||||
|
||||
@@ -217,8 +226,8 @@ defmodule Enum do
|
||||
end
|
||||
|
||||
def all?(collection, fun) do
|
||||
Enumerable.reduce(collection, { :cont, true }, fn(entry, _) ->
|
||||
if fun.(entry), do: { :cont, true }, else: { :halt, false }
|
||||
Enumerable.reduce(collection, {:cont, true}, fn(entry, _) ->
|
||||
if fun.(entry), do: {:cont, true}, else: {:halt, false}
|
||||
end) |> elem(1)
|
||||
end
|
||||
|
||||
@@ -254,8 +263,8 @@ defmodule Enum do
|
||||
end
|
||||
|
||||
def any?(collection, fun) do
|
||||
Enumerable.reduce(collection, { :cont, false }, fn(entry, _) ->
|
||||
if fun.(entry), do: { :halt, true }, else: { :cont, false }
|
||||
Enumerable.reduce(collection, {:cont, false}, fn(entry, _) ->
|
||||
if fun.(entry), do: {:halt, true}, else: {:cont, false}
|
||||
end) |> elem(1)
|
||||
end
|
||||
|
||||
@@ -282,7 +291,7 @@ defmodule Enum do
|
||||
@spec at(t, integer, default) :: element | default
|
||||
def at(collection, n, default \\ nil) do
|
||||
case fetch(collection, n) do
|
||||
{ :ok, h } -> h
|
||||
{:ok, h} -> h
|
||||
:error -> default
|
||||
end
|
||||
end
|
||||
@@ -326,8 +335,8 @@ defmodule Enum do
|
||||
def chunk(coll, n, step, pad \\ nil) when n > 0 and step > 0 do
|
||||
limit = :erlang.max(n, step)
|
||||
|
||||
{ _, { acc, { buffer, i } } } =
|
||||
Enumerable.reduce(coll, { :cont, { [], { [], 0 } } }, R.chunk(n, step, limit))
|
||||
{_, {acc, {buffer, i}}} =
|
||||
Enumerable.reduce(coll, {:cont, {[], {[], 0}}}, R.chunk(n, step, limit))
|
||||
|
||||
if nil?(pad) || i == 0 do
|
||||
:lists.reverse(acc)
|
||||
@@ -348,11 +357,11 @@ defmodule Enum do
|
||||
"""
|
||||
@spec chunk_by(t, (element -> any)) :: [list]
|
||||
def chunk_by(coll, fun) do
|
||||
{ _, { acc, res } } =
|
||||
Enumerable.reduce(coll, { :cont, { [], nil } }, R.chunk_by(fun))
|
||||
{_, {acc, res}} =
|
||||
Enumerable.reduce(coll, {:cont, {[], nil}}, R.chunk_by(fun))
|
||||
|
||||
case res do
|
||||
{ buffer, _ } ->
|
||||
{buffer, _} ->
|
||||
:lists.reverse([:lists.reverse(buffer) | acc])
|
||||
nil ->
|
||||
[]
|
||||
@@ -420,11 +429,11 @@ defmodule Enum do
|
||||
|
||||
def count(collection) do
|
||||
case Enumerable.count(collection) do
|
||||
{ :ok, value } when is_integer(value) ->
|
||||
{:ok, value} when is_integer(value) ->
|
||||
value
|
||||
{ :error, module } ->
|
||||
module.reduce(collection, { :cont, 0 }, fn
|
||||
_, acc -> { :cont, acc + 1 }
|
||||
{:error, module} ->
|
||||
module.reduce(collection, {:cont, 0}, fn
|
||||
_, acc -> {:cont, acc + 1}
|
||||
end) |> elem(1)
|
||||
end
|
||||
end
|
||||
@@ -441,8 +450,8 @@ defmodule Enum do
|
||||
"""
|
||||
@spec count(t, (element -> as_boolean(term))) :: non_neg_integer
|
||||
def count(collection, fun) do
|
||||
Enumerable.reduce(collection, { :cont, 0 }, fn(entry, acc) ->
|
||||
{ :cont, if(fun.(entry), do: acc + 1, else: acc) }
|
||||
Enumerable.reduce(collection, {:cont, 0}, fn(entry, acc) ->
|
||||
{:cont, if(fun.(entry), do: acc + 1, else: acc)}
|
||||
end) |> elem(1)
|
||||
end
|
||||
|
||||
@@ -503,8 +512,8 @@ defmodule Enum do
|
||||
end
|
||||
|
||||
def drop_while(collection, fun) do
|
||||
{ _, { res, _ } } =
|
||||
Enumerable.reduce(collection, { :cont, { [], true } }, R.drop_while(fun))
|
||||
{_, {res, _}} =
|
||||
Enumerable.reduce(collection, {:cont, {[], true}}, R.drop_while(fun))
|
||||
:lists.reverse(res)
|
||||
end
|
||||
|
||||
@@ -552,12 +561,12 @@ defmodule Enum do
|
||||
end
|
||||
|
||||
def empty?(collection) do
|
||||
Enumerable.reduce(collection, { :cont, true }, fn(_, _) -> { :halt, false } end) |> elem(1)
|
||||
Enumerable.reduce(collection, {:cont, true}, fn(_, _) -> {:halt, false} end) |> elem(1)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Finds the element at the given index (zero-based).
|
||||
Returns `{ :ok, element }` if found, otherwise `:error`.
|
||||
Returns `{:ok, element}` if found, otherwise `:error`.
|
||||
|
||||
A negative index can be passed, which means the collection is
|
||||
enumerated once and the index is counted from the end (i.e.
|
||||
@@ -566,33 +575,33 @@ defmodule Enum do
|
||||
## Examples
|
||||
|
||||
iex> Enum.fetch([2, 4, 6], 0)
|
||||
{ :ok, 2 }
|
||||
{:ok, 2}
|
||||
|
||||
iex> Enum.fetch([2, 4, 6], 2)
|
||||
{ :ok, 6 }
|
||||
{:ok, 6}
|
||||
|
||||
iex> Enum.fetch([2, 4, 6], 4)
|
||||
:error
|
||||
|
||||
"""
|
||||
@spec fetch(t, integer) :: { :ok, element } | :error
|
||||
@spec fetch(t, integer) :: {:ok, element} | :error
|
||||
def fetch(collection, n) when is_list(collection) and n >= 0 do
|
||||
do_fetch(collection, n)
|
||||
end
|
||||
|
||||
def fetch(collection, n) when n >= 0 do
|
||||
res =
|
||||
Enumerable.reduce(collection, { :cont, 0 }, fn(entry, acc) ->
|
||||
Enumerable.reduce(collection, {:cont, 0}, fn(entry, acc) ->
|
||||
if acc == n do
|
||||
{ :halt, entry }
|
||||
{:halt, entry}
|
||||
else
|
||||
{ :cont, acc + 1 }
|
||||
{:cont, acc + 1}
|
||||
end
|
||||
end)
|
||||
|
||||
case res do
|
||||
{ :halted, entry } -> { :ok, entry }
|
||||
{ :done, _ } -> :error
|
||||
{:halted, entry} -> {:ok, entry}
|
||||
{:done, _} -> :error
|
||||
end
|
||||
end
|
||||
|
||||
@@ -620,7 +629,7 @@ defmodule Enum do
|
||||
@spec fetch!(t, integer) :: element | no_return
|
||||
def fetch!(collection, n) do
|
||||
case fetch(collection, n) do
|
||||
{ :ok, h } -> h
|
||||
{:ok, h} -> h
|
||||
:error -> raise Enum.OutOfBoundsError
|
||||
end
|
||||
end
|
||||
@@ -641,7 +650,7 @@ defmodule Enum do
|
||||
end
|
||||
|
||||
def filter(collection, fun) do
|
||||
Enumerable.reduce(collection, { :cont, [] }, R.filter(fun))
|
||||
Enumerable.reduce(collection, {:cont, []}, R.filter(fun))
|
||||
|> elem(1) |> :lists.reverse
|
||||
end
|
||||
|
||||
@@ -660,7 +669,7 @@ defmodule Enum do
|
||||
end
|
||||
|
||||
def filter_map(collection, filter, mapper) do
|
||||
Enumerable.reduce(collection, { :cont, [] }, R.filter_map(filter, mapper))
|
||||
Enumerable.reduce(collection, {:cont, []}, R.filter_map(filter, mapper))
|
||||
|> elem(1) |> :lists.reverse
|
||||
end
|
||||
|
||||
@@ -689,8 +698,8 @@ defmodule Enum do
|
||||
end
|
||||
|
||||
def find(collection, ifnone, fun) do
|
||||
Enumerable.reduce(collection, { :cont, ifnone }, fn(entry, ifnone) ->
|
||||
if fun.(entry), do: { :halt, entry }, else: { :cont, ifnone }
|
||||
Enumerable.reduce(collection, {:cont, ifnone}, fn(entry, ifnone) ->
|
||||
if fun.(entry), do: {:halt, entry}, else: {:cont, ifnone}
|
||||
end) |> elem(1)
|
||||
end
|
||||
|
||||
@@ -716,9 +725,9 @@ defmodule Enum do
|
||||
end
|
||||
|
||||
def find_value(collection, ifnone, fun) do
|
||||
Enumerable.reduce(collection, { :cont, ifnone }, fn(entry, ifnone) ->
|
||||
Enumerable.reduce(collection, {:cont, ifnone}, fn(entry, ifnone) ->
|
||||
fun_entry = fun.(entry)
|
||||
if fun_entry, do: { :halt, fun_entry }, else: { :cont, ifnone }
|
||||
if fun_entry, do: {:halt, fun_entry}, else: {:cont, ifnone}
|
||||
end) |> elem(1)
|
||||
end
|
||||
|
||||
@@ -742,13 +751,13 @@ defmodule Enum do
|
||||
|
||||
def find_index(collection, fun) do
|
||||
res =
|
||||
Enumerable.reduce(collection, { :cont, 0 }, fn(entry, acc) ->
|
||||
if fun.(entry), do: { :halt, acc }, else: { :cont, acc + 1 }
|
||||
Enumerable.reduce(collection, {:cont, 0}, fn(entry, acc) ->
|
||||
if fun.(entry), do: {:halt, acc}, else: {:cont, acc + 1}
|
||||
end)
|
||||
|
||||
case res do
|
||||
{ :halted, entry } -> entry
|
||||
{ :done, _ } -> nil
|
||||
{:halted, entry} -> entry
|
||||
{:done, _} -> nil
|
||||
end
|
||||
end
|
||||
|
||||
@@ -787,26 +796,26 @@ defmodule Enum do
|
||||
iex> enum = 1..100
|
||||
iex> n = 3
|
||||
iex> Enum.flat_map_reduce(enum, 0, fn i, acc ->
|
||||
...> if acc < n, do: { [i], acc + 1 }, else: { :halt, acc }
|
||||
...> if acc < n, do: {[i], acc + 1}, else: {:halt, acc}
|
||||
...> end)
|
||||
{ [1,2,3], 3 }
|
||||
{[1,2,3], 3}
|
||||
|
||||
"""
|
||||
@spec flat_map_reduce(t, acc, fun) :: { [any], any } when
|
||||
fun: (element, acc -> { t, acc } | { :halt, acc }),
|
||||
@spec flat_map_reduce(t, acc, fun) :: {[any], any} when
|
||||
fun: (element, acc -> {t, acc} | {:halt, acc}),
|
||||
acc: any
|
||||
def flat_map_reduce(collection, acc, fun) do
|
||||
{ _, { list, acc } } =
|
||||
Enumerable.reduce(collection, { :cont, { [], acc } }, fn(entry, { list, acc }) ->
|
||||
{_, {list, acc}} =
|
||||
Enumerable.reduce(collection, {:cont, {[], acc}}, fn(entry, {list, acc}) ->
|
||||
case fun.(entry, acc) do
|
||||
{ :halt, acc } ->
|
||||
{ :halt, { list, acc } }
|
||||
{ entries, acc } ->
|
||||
{ :cont, { reduce(entries, list, &[&1|&2]), acc } }
|
||||
{:halt, acc} ->
|
||||
{:halt, {list, acc}}
|
||||
{entries, acc} ->
|
||||
{:cont, {reduce(entries, list, &[&1|&2]), acc}}
|
||||
end
|
||||
end)
|
||||
|
||||
{ :lists.reverse(list), acc }
|
||||
{:lists.reverse(list), acc}
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -856,14 +865,14 @@ defmodule Enum do
|
||||
list ++ to_list(collection)
|
||||
end
|
||||
|
||||
def into(collection, %{} = map) when is_list(collection) and (map_size(map) == 0) do
|
||||
def into(collection, %{} = map) when is_list(collection) and map_size(map) == 0 do
|
||||
:maps.from_list(collection)
|
||||
end
|
||||
|
||||
def into(collection, collectable) do
|
||||
{ initial, fun } = Collectable.into(collectable)
|
||||
{initial, fun} = Collectable.into(collectable)
|
||||
into(collection, initial, fun, fn x, acc ->
|
||||
fun.(acc, { :cont, x })
|
||||
fun.(acc, {:cont, x})
|
||||
end)
|
||||
end
|
||||
|
||||
@@ -884,9 +893,9 @@ defmodule Enum do
|
||||
end
|
||||
|
||||
def into(collection, collectable, transform) when is_function(transform, 1) do
|
||||
{ initial, fun } = Collectable.into(collectable)
|
||||
{initial, fun} = Collectable.into(collectable)
|
||||
into(collection, initial, fun, fn x, acc ->
|
||||
fun.(acc, { :cont, transform.(x) })
|
||||
fun.(acc, {:cont, transform.(x)})
|
||||
end)
|
||||
end
|
||||
|
||||
@@ -944,7 +953,7 @@ defmodule Enum do
|
||||
iex> Enum.map([1, 2, 3], fn(x) -> x * 2 end)
|
||||
[2, 4, 6]
|
||||
|
||||
iex> Enum.map([a: 1, b: 2], fn({k, v}) -> { k, -v } end)
|
||||
iex> Enum.map([a: 1, b: 2], fn({k, v}) -> {k, -v} end)
|
||||
[a: -1, b: -2]
|
||||
|
||||
"""
|
||||
@@ -954,7 +963,7 @@ defmodule Enum do
|
||||
end
|
||||
|
||||
def map(collection, fun) do
|
||||
Enumerable.reduce(collection, { :cont, [] }, R.map(fun)) |> elem(1) |> :lists.reverse
|
||||
Enumerable.reduce(collection, {:cont, []}, R.map(fun)) |> elem(1) |> :lists.reverse
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -993,13 +1002,13 @@ defmodule Enum do
|
||||
the first element is the mapped collection and the second
|
||||
one is the final accumulator.
|
||||
|
||||
For dicts, the first tuple element must be a `{ key, value }`
|
||||
For dicts, the first tuple element must be a `{key, value}`
|
||||
tuple.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Enum.map_reduce([1, 2, 3], 0, fn(x, acc) -> { x * 2, x + acc } end)
|
||||
{ [2, 4, 6], 6 }
|
||||
iex> Enum.map_reduce([1, 2, 3], 0, fn(x, acc) -> {x * 2, x + acc} end)
|
||||
{[2, 4, 6], 6}
|
||||
|
||||
"""
|
||||
@spec map_reduce(t, any, (element, any -> any)) :: any
|
||||
@@ -1008,11 +1017,11 @@ defmodule Enum do
|
||||
end
|
||||
|
||||
def map_reduce(collection, acc, fun) do
|
||||
{ list, acc } = reduce(collection, { [], acc }, fn(entry, { list, acc }) ->
|
||||
{ new_entry, acc } = fun.(entry, acc)
|
||||
{ [new_entry|list], acc }
|
||||
{list, acc} = reduce(collection, {[], acc}, fn(entry, {list, acc}) ->
|
||||
{new_entry, acc} = fun.(entry, acc)
|
||||
{[new_entry|list], acc}
|
||||
end)
|
||||
{ :lists.reverse(list), acc }
|
||||
{:lists.reverse(list), acc}
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -1042,9 +1051,9 @@ defmodule Enum do
|
||||
"""
|
||||
@spec max_by(t, (element -> any)) :: element | no_return
|
||||
def max_by([h|t], fun) do
|
||||
reduce(t, { h, fun.(h) }, fn(entry, { _, fun_max } = old) ->
|
||||
reduce(t, {h, fun.(h)}, fn(entry, {_, fun_max} = old) ->
|
||||
fun_entry = fun.(entry)
|
||||
if(fun_entry > fun_max, do: { entry, fun_entry }, else: old)
|
||||
if(fun_entry > fun_max, do: {entry, fun_entry}, else: old)
|
||||
end) |> elem(0)
|
||||
end
|
||||
|
||||
@@ -1055,16 +1064,16 @@ defmodule Enum do
|
||||
def max_by(collection, fun) do
|
||||
result =
|
||||
reduce(collection, :first, fn
|
||||
entry, { _, fun_max } = old ->
|
||||
entry, {_, fun_max} = old ->
|
||||
fun_entry = fun.(entry)
|
||||
if(fun_entry > fun_max, do: { entry, fun_entry }, else: old)
|
||||
if(fun_entry > fun_max, do: {entry, fun_entry}, else: old)
|
||||
entry, :first ->
|
||||
{ entry, fun.(entry) }
|
||||
{entry, fun.(entry)}
|
||||
end)
|
||||
|
||||
case result do
|
||||
:first -> raise Enum.EmptyError
|
||||
{ entry, _ } -> entry
|
||||
{entry, _} -> entry
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1091,12 +1100,12 @@ defmodule Enum do
|
||||
|
||||
def member?(collection, value) do
|
||||
case Enumerable.member?(collection, value) do
|
||||
{ :ok, value } when is_boolean(value) ->
|
||||
{:ok, value} when is_boolean(value) ->
|
||||
value
|
||||
{ :error, module } ->
|
||||
module.reduce(collection, { :cont, false }, fn
|
||||
v, _ when v === value -> { :halt, true }
|
||||
_, _ -> { :cont, false }
|
||||
{:error, module} ->
|
||||
module.reduce(collection, {:cont, false}, fn
|
||||
v, _ when v === value -> {:halt, true}
|
||||
_, _ -> {:cont, false}
|
||||
end) |> elem(1)
|
||||
end
|
||||
end
|
||||
@@ -1128,9 +1137,9 @@ defmodule Enum do
|
||||
"""
|
||||
@spec min_by(t, (element -> any)) :: element | no_return
|
||||
def min_by([h|t], fun) do
|
||||
reduce(t, { h, fun.(h) }, fn(entry, { _, fun_min } = old) ->
|
||||
reduce(t, {h, fun.(h)}, fn(entry, {_, fun_min} = old) ->
|
||||
fun_entry = fun.(entry)
|
||||
if(fun_entry < fun_min, do: { entry, fun_entry }, else: old)
|
||||
if(fun_entry < fun_min, do: {entry, fun_entry}, else: old)
|
||||
end) |> elem(0)
|
||||
end
|
||||
|
||||
@@ -1141,16 +1150,16 @@ defmodule Enum do
|
||||
def min_by(collection, fun) do
|
||||
result =
|
||||
reduce(collection, :first, fn
|
||||
entry, { _, fun_min } = old ->
|
||||
entry, {_, fun_min} = old ->
|
||||
fun_entry = fun.(entry)
|
||||
if(fun_entry < fun_min, do: { entry, fun_entry }, else: old)
|
||||
if(fun_entry < fun_min, do: {entry, fun_entry}, else: old)
|
||||
entry, :first ->
|
||||
{ entry, fun.(entry) }
|
||||
{entry, fun.(entry)}
|
||||
end)
|
||||
|
||||
case result do
|
||||
:first -> raise Enum.EmptyError
|
||||
{ entry, _ } -> entry
|
||||
{entry, _} -> entry
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1178,21 +1187,21 @@ defmodule Enum do
|
||||
## Examples
|
||||
|
||||
iex> Enum.partition([1, 2, 3], fn(x) -> rem(x, 2) == 0 end)
|
||||
{ [2], [1,3] }
|
||||
{[2], [1,3]}
|
||||
|
||||
"""
|
||||
@spec partition(t, (element -> any)) :: {list, list}
|
||||
def partition(collection, fun) do
|
||||
{ acc1, acc2 } =
|
||||
reduce(collection, { [], [] }, fn(entry, { acc1, acc2 }) ->
|
||||
{acc1, acc2} =
|
||||
reduce(collection, {[], []}, fn(entry, {acc1, acc2}) ->
|
||||
if fun.(entry) do
|
||||
{ [entry|acc1], acc2 }
|
||||
{[entry|acc1], acc2}
|
||||
else
|
||||
{ acc1, [entry|acc2] }
|
||||
{acc1, [entry|acc2]}
|
||||
end
|
||||
end)
|
||||
|
||||
{ :lists.reverse(acc1), :lists.reverse(acc2) }
|
||||
{:lists.reverse(acc1), :lists.reverse(acc2)}
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -1206,10 +1215,10 @@ defmodule Enum do
|
||||
## Examples
|
||||
|
||||
iex> Enum.group_by(~w{ant buffalo cat dingo}, &String.length/1)
|
||||
%{ 3 => ["cat", "ant"], 7 => ["buffalo"], 5 => ["dingo"] }
|
||||
%{3 => ["cat", "ant"], 7 => ["buffalo"], 5 => ["dingo"]}
|
||||
|
||||
"""
|
||||
@spec group_by(t, (element -> any)) :: HashDict
|
||||
@spec group_by(t, dict, (element -> any)) :: dict when dict: Dict.t
|
||||
def group_by(collection, dict \\ %{}, fun) do
|
||||
reduce(collection, dict, fn(entry, categories) ->
|
||||
Dict.update(categories, fun.(entry), [entry], &[entry|&1])
|
||||
@@ -1233,8 +1242,8 @@ defmodule Enum do
|
||||
end
|
||||
|
||||
def reduce(collection, acc, fun) do
|
||||
Enumerable.reduce(collection, { :cont, acc },
|
||||
fn x, acc -> { :cont, fun.(x, acc) } end) |> elem(1)
|
||||
Enumerable.reduce(collection, {:cont, acc},
|
||||
fn x, acc -> {:cont, fun.(x, acc)} end) |> elem(1)
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -1260,16 +1269,16 @@ defmodule Enum do
|
||||
|
||||
def reduce(collection, fun) do
|
||||
result =
|
||||
Enumerable.reduce(collection, { :cont, :first }, fn
|
||||
Enumerable.reduce(collection, {:cont, :first}, fn
|
||||
x, :first ->
|
||||
{ :cont, { :acc, x } }
|
||||
x, { :acc, acc } ->
|
||||
{ :cont, { :acc, fun.(x, acc) } }
|
||||
{:cont, {:acc, x}}
|
||||
x, {:acc, acc} ->
|
||||
{:cont, {:acc, fun.(x, acc)}}
|
||||
end) |> elem(1)
|
||||
|
||||
case result do
|
||||
:first -> raise Enum.EmptyError
|
||||
{ :acc, acc } -> acc
|
||||
{:acc, acc} -> acc
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1288,7 +1297,7 @@ defmodule Enum do
|
||||
end
|
||||
|
||||
def reject(collection, fun) do
|
||||
Enumerable.reduce(collection, { :cont, [] }, R.reject(fun)) |> elem(1) |> :lists.reverse
|
||||
Enumerable.reduce(collection, {:cont, []}, R.reject(fun)) |> elem(1) |> :lists.reverse
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -1344,8 +1353,8 @@ defmodule Enum do
|
||||
"""
|
||||
@spec scan(t, (element, any -> any)) :: list
|
||||
def scan(enum, fun) do
|
||||
{ _, { res, _ } } =
|
||||
Enumerable.reduce(enum, { :cont, { [], :first } }, R.scan_2(fun))
|
||||
{_, {res, _}} =
|
||||
Enumerable.reduce(enum, {:cont, {[], :first}}, R.scan_2(fun))
|
||||
:lists.reverse(res)
|
||||
end
|
||||
|
||||
@@ -1362,8 +1371,8 @@ defmodule Enum do
|
||||
"""
|
||||
@spec scan(t, any, (element, any -> any)) :: list
|
||||
def scan(enum, acc, fun) do
|
||||
{ _, { res, _ } } =
|
||||
Enumerable.reduce(enum, { :cont, { [], acc } }, R.scan_3(fun))
|
||||
{_, {res, _}} =
|
||||
Enumerable.reduce(enum, {:cont, {[], acc}}, R.scan_3(fun))
|
||||
:lists.reverse(res)
|
||||
end
|
||||
|
||||
@@ -1389,7 +1398,7 @@ defmodule Enum do
|
||||
@spec shuffle(t) :: list
|
||||
def shuffle(collection) do
|
||||
randomized = reduce(collection, [], fn x, acc ->
|
||||
[{ :random.uniform, x }|acc]
|
||||
[{:random.uniform, x}|acc]
|
||||
end)
|
||||
unwrap(:lists.keysort(1, randomized), [])
|
||||
end
|
||||
@@ -1407,7 +1416,7 @@ defmodule Enum do
|
||||
@spec slice(t, integer, non_neg_integer) :: list
|
||||
|
||||
def slice(coll, start, count) when start < 0 do
|
||||
{ list, new_start } = enumerate_and_count(coll, start)
|
||||
{list, new_start} = enumerate_and_count(coll, start)
|
||||
if new_start >= 0, do: slice(list, new_start, count)
|
||||
end
|
||||
|
||||
@@ -1416,13 +1425,13 @@ 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
|
||||
_entry, { start, count, _list } when start > 0 ->
|
||||
{ :cont, { start-1, count, [] } }
|
||||
entry, { start, count, list } when count > 1 ->
|
||||
{ :cont, { start, count-1, [entry|list] } }
|
||||
entry, { start, count, list } ->
|
||||
{ :halt, { start, count, [entry|list] } }
|
||||
{start, _, 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 ->
|
||||
{:cont, {start, count-1, [entry|list]}}
|
||||
entry, {start, count, list} ->
|
||||
{:halt, {start, count, [entry|list]}}
|
||||
end) |> elem(1)
|
||||
|
||||
if start <= 0, do: :lists.reverse(list)
|
||||
@@ -1430,8 +1439,8 @@ defmodule Enum do
|
||||
|
||||
def slice(coll, start, 0) do
|
||||
res =
|
||||
Enumerable.reduce(coll, { :cont, start }, fn _, start ->
|
||||
if start > 0, do: { :cont, start-1 }, else: { :halt, [] }
|
||||
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
|
||||
end
|
||||
@@ -1463,7 +1472,7 @@ defmodule Enum do
|
||||
end
|
||||
|
||||
def slice(coll, first..last) do
|
||||
{ list, count } = enumerate_and_count(coll, 0)
|
||||
{list, count} = enumerate_and_count(coll, 0)
|
||||
corr_first = if first >= 0, do: first, else: first + count
|
||||
corr_last = if last >= 0, do: last, else: last + count
|
||||
length = corr_last - corr_first + 1
|
||||
@@ -1538,19 +1547,19 @@ defmodule Enum do
|
||||
## Examples
|
||||
|
||||
iex> Enum.split([1, 2, 3], 2)
|
||||
{ [1,2], [3] }
|
||||
{[1,2], [3]}
|
||||
|
||||
iex> Enum.split([1, 2, 3], 10)
|
||||
{ [1,2,3], [] }
|
||||
{[1,2,3], []}
|
||||
|
||||
iex> Enum.split([1, 2, 3], 0)
|
||||
{ [], [1,2,3] }
|
||||
{[], [1,2,3]}
|
||||
|
||||
iex> Enum.split([1, 2, 3], -1)
|
||||
{ [1,2], [3] }
|
||||
{[1,2], [3]}
|
||||
|
||||
iex> Enum.split([1, 2, 3], -5)
|
||||
{ [], [1,2,3] }
|
||||
{[], [1,2,3]}
|
||||
|
||||
"""
|
||||
@spec split(t, integer) :: {list, list}
|
||||
@@ -1559,16 +1568,16 @@ defmodule Enum do
|
||||
end
|
||||
|
||||
def split(collection, count) when count >= 0 do
|
||||
{ _, list1, list2 } =
|
||||
reduce(collection, { count, [], [] }, fn(entry, { counter, acc1, acc2 }) ->
|
||||
{_, list1, list2} =
|
||||
reduce(collection, {count, [], []}, fn(entry, {counter, acc1, acc2}) ->
|
||||
if counter > 0 do
|
||||
{ counter - 1, [entry|acc1], acc2 }
|
||||
{counter - 1, [entry|acc1], acc2}
|
||||
else
|
||||
{ counter, acc1, [entry|acc2] }
|
||||
{counter, acc1, [entry|acc2]}
|
||||
end
|
||||
end)
|
||||
|
||||
{ :lists.reverse(list1), :lists.reverse(list2) }
|
||||
{:lists.reverse(list1), :lists.reverse(list2)}
|
||||
end
|
||||
|
||||
def split(collection, count) when count < 0 do
|
||||
@@ -1581,7 +1590,7 @@ defmodule Enum do
|
||||
## Examples
|
||||
|
||||
iex> Enum.split_while([1, 2, 3, 4], fn(x) -> x < 3 end)
|
||||
{ [1, 2], [3, 4] }
|
||||
{[1, 2], [3, 4]}
|
||||
|
||||
"""
|
||||
@spec split_while(t, (element -> as_boolean(term))) :: {list, list}
|
||||
@@ -1590,15 +1599,15 @@ defmodule Enum do
|
||||
end
|
||||
|
||||
def split_while(collection, fun) do
|
||||
{ list1, list2 } =
|
||||
reduce(collection, { [], [] }, fn
|
||||
entry, { acc1, [] } ->
|
||||
if(fun.(entry), do: { [entry|acc1], [] }, else: { acc1, [entry] })
|
||||
entry, { acc1, acc2 } ->
|
||||
{ acc1, [entry|acc2] }
|
||||
{list1, list2} =
|
||||
reduce(collection, {[], []}, fn
|
||||
entry, {acc1, []} ->
|
||||
if(fun.(entry), do: {[entry|acc1], []}, else: {acc1, [entry]})
|
||||
entry, {acc1, acc2} ->
|
||||
{acc1, [entry|acc2]}
|
||||
end)
|
||||
|
||||
{ :lists.reverse(list1), :lists.reverse(list2) }
|
||||
{:lists.reverse(list1), :lists.reverse(list2)}
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -1635,19 +1644,19 @@ defmodule Enum do
|
||||
end
|
||||
|
||||
def take(collection, count) when count > 0 do
|
||||
{ _, { res, _ } } =
|
||||
Enumerable.reduce(collection, { :cont, { [], count } }, fn(entry, { list, count }) ->
|
||||
{_, {res, _}} =
|
||||
Enumerable.reduce(collection, {:cont, {[], count}}, fn(entry, {list, count}) ->
|
||||
if count > 1 do
|
||||
{ :cont, { [entry|list], count - 1 } }
|
||||
{:cont, {[entry|list], count - 1}}
|
||||
else
|
||||
{ :halt, { [entry|list], count } }
|
||||
{:halt, {[entry|list], count}}
|
||||
end
|
||||
end)
|
||||
:lists.reverse(res)
|
||||
end
|
||||
|
||||
def take(collection, count) when count < 0 do
|
||||
Stream.take(collection, count).({ :cont, [] }, &{ :cont, [&1|&2] })
|
||||
Stream.take(collection, count).({:cont, []}, &{:cont, [&1|&2]})
|
||||
|> elem(1) |> :lists.reverse
|
||||
end
|
||||
|
||||
@@ -1664,8 +1673,8 @@ defmodule Enum do
|
||||
@spec take_every(t, integer) :: list
|
||||
def take_every(_collection, 0), do: []
|
||||
def take_every(collection, nth) do
|
||||
{ _, { res, _ } } =
|
||||
Enumerable.reduce(collection, { :cont, { [], :first } }, R.take_every(nth))
|
||||
{_, {res, _}} =
|
||||
Enumerable.reduce(collection, {:cont, {[], :first}}, R.take_every(nth))
|
||||
:lists.reverse(res)
|
||||
end
|
||||
|
||||
@@ -1684,7 +1693,7 @@ defmodule Enum do
|
||||
end
|
||||
|
||||
def take_while(collection, fun) do
|
||||
Enumerable.reduce(collection, { :cont, [] }, R.take_while(fun))
|
||||
Enumerable.reduce(collection, {:cont, []}, R.take_while(fun))
|
||||
|> elem(1) |> :lists.reverse
|
||||
end
|
||||
|
||||
@@ -1714,7 +1723,7 @@ defmodule Enum do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Enum.traverse(%{a: 1, b: 2}, fn { k, v } -> { k, v * 2 } end)
|
||||
iex> Enum.traverse(%{a: 1, b: 2}, fn {k, v} -> {k, v * 2} end)
|
||||
%{a: 2, b: 4}
|
||||
|
||||
"""
|
||||
@@ -1748,8 +1757,8 @@ defmodule Enum do
|
||||
end
|
||||
|
||||
def uniq(collection, fun) do
|
||||
{ _, { list, _ } } =
|
||||
Enumerable.reduce(collection, { :cont, { [], [] } }, R.uniq(fun))
|
||||
{_, {list, _}} =
|
||||
Enumerable.reduce(collection, {:cont, {[], []}}, R.uniq(fun))
|
||||
:lists.reverse(list)
|
||||
end
|
||||
|
||||
@@ -1774,7 +1783,7 @@ defmodule Enum do
|
||||
end
|
||||
|
||||
def zip(coll1, coll2) do
|
||||
Stream.zip(coll1, coll2).({ :cont, [] }, &{ :cont, [&1|&2] }) |> elem(1) |> :lists.reverse
|
||||
Stream.zip(coll1, coll2).({:cont, []}, &{:cont, [&1|&2]}) |> elem(1) |> :lists.reverse
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -1787,23 +1796,23 @@ defmodule Enum do
|
||||
[{1,0},{2,1},{3,2}]
|
||||
|
||||
"""
|
||||
@spec with_index(t) :: list({ element, non_neg_integer })
|
||||
@spec with_index(t) :: list({element, non_neg_integer})
|
||||
def with_index(collection) do
|
||||
map_reduce(collection, 0, fn x, acc ->
|
||||
{ { x, acc }, acc + 1 }
|
||||
{{x, acc}, acc + 1}
|
||||
end) |> elem(0)
|
||||
end
|
||||
|
||||
## Helpers
|
||||
|
||||
@compile { :inline, to_string: 2 }
|
||||
@compile {:inline, to_string: 2}
|
||||
|
||||
defp enumerate_and_count(collection, count) when is_list(collection) do
|
||||
{ collection, length(collection) - abs(count) }
|
||||
{collection, length(collection) - abs(count)}
|
||||
end
|
||||
|
||||
defp enumerate_and_count(collection, count) do
|
||||
map_reduce(collection, -abs(count), fn(x, acc) -> { x, acc + 1 } end)
|
||||
map_reduce(collection, -abs(count), fn(x, acc) -> {x, acc + 1} end)
|
||||
end
|
||||
|
||||
defp to_string(mapper, entry) do
|
||||
@@ -1845,7 +1854,7 @@ defmodule Enum do
|
||||
|
||||
## fetch
|
||||
|
||||
defp do_fetch([h|_], 0), do: { :ok, h }
|
||||
defp do_fetch([h|_], 0), do: {:ok, h}
|
||||
defp do_fetch([_|t], n), do: do_fetch(t, n - 1)
|
||||
defp do_fetch([], _), do: :error
|
||||
|
||||
@@ -1925,45 +1934,45 @@ defmodule Enum do
|
||||
|
||||
## sort
|
||||
|
||||
defp sort_reducer(entry, { :split, y, x, r, rs, bool }, fun) do
|
||||
defp sort_reducer(entry, {:split, y, x, r, rs, bool}, fun) do
|
||||
cond do
|
||||
fun.(y, entry) == bool ->
|
||||
{ :split, entry, y, [x|r], rs, bool }
|
||||
{:split, entry, y, [x|r], rs, bool}
|
||||
fun.(x, entry) == bool ->
|
||||
{ :split, y, entry, [x|r], rs, bool }
|
||||
{:split, y, entry, [x|r], rs, bool}
|
||||
r == [] ->
|
||||
{ :split, y, x, [entry], rs, bool }
|
||||
{:split, y, x, [entry], rs, bool}
|
||||
true ->
|
||||
{ :pivot, y, x, r, rs, entry, bool }
|
||||
{:pivot, y, x, r, rs, entry, bool}
|
||||
end
|
||||
end
|
||||
|
||||
defp sort_reducer(entry, { :pivot, y, x, r, rs, s, bool }, fun) do
|
||||
defp sort_reducer(entry, {:pivot, y, x, r, rs, s, bool}, fun) do
|
||||
cond do
|
||||
fun.(y, entry) == bool ->
|
||||
{ :pivot, entry, y, [x | r], rs, s, bool }
|
||||
{:pivot, entry, y, [x | r], rs, s, bool}
|
||||
fun.(x, entry) == bool ->
|
||||
{ :pivot, y, entry, [x | r], rs, s, bool }
|
||||
{:pivot, y, entry, [x | r], rs, s, bool}
|
||||
fun.(s, entry) == bool ->
|
||||
{ :split, entry, s, [], [[y, x | r] | rs], bool }
|
||||
{:split, entry, s, [], [[y, x | r] | rs], bool}
|
||||
true ->
|
||||
{ :split, s, entry, [], [[y, x | r] | rs], bool }
|
||||
{:split, s, entry, [], [[y, x | r] | rs], bool}
|
||||
end
|
||||
end
|
||||
|
||||
defp sort_reducer(entry, [x], fun) do
|
||||
{ :split, entry, x, [], [], fun.(x, entry) }
|
||||
{:split, entry, x, [], [], fun.(x, entry)}
|
||||
end
|
||||
|
||||
defp sort_reducer(entry, acc, _fun) do
|
||||
[entry|acc]
|
||||
end
|
||||
|
||||
defp sort_terminator({ :split, y, x, r, rs, bool }, fun) do
|
||||
defp sort_terminator({:split, y, x, r, rs, bool}, fun) do
|
||||
sort_merge([[y, x | r] | rs], fun, bool)
|
||||
end
|
||||
|
||||
defp sort_terminator({ :pivot, y, x, r, rs, s, bool }, fun) do
|
||||
defp sort_terminator({:pivot, y, x, r, rs, s, bool}, fun) do
|
||||
sort_merge([[s], [y, x | r] | rs], fun, bool)
|
||||
end
|
||||
|
||||
@@ -2036,11 +2045,11 @@ defmodule Enum do
|
||||
end
|
||||
|
||||
defp do_split(list, 0, acc) do
|
||||
{ :lists.reverse(acc), list }
|
||||
{:lists.reverse(acc), list}
|
||||
end
|
||||
|
||||
defp do_split([], _, acc) do
|
||||
{ :lists.reverse(acc), [] }
|
||||
{:lists.reverse(acc), []}
|
||||
end
|
||||
|
||||
defp do_split_reverse([h|t], counter, acc) when counter > 0 do
|
||||
@@ -2048,11 +2057,11 @@ defmodule Enum do
|
||||
end
|
||||
|
||||
defp do_split_reverse(list, 0, acc) do
|
||||
{ :lists.reverse(list), acc }
|
||||
{:lists.reverse(list), acc}
|
||||
end
|
||||
|
||||
defp do_split_reverse([], _, acc) do
|
||||
{ [], acc }
|
||||
{[], acc}
|
||||
end
|
||||
|
||||
## split_while
|
||||
@@ -2061,12 +2070,12 @@ defmodule Enum do
|
||||
if fun.(h) do
|
||||
do_split_while(t, fun, [h|acc])
|
||||
else
|
||||
{ :lists.reverse(acc), [h|t] }
|
||||
{:lists.reverse(acc), [h|t]}
|
||||
end
|
||||
end
|
||||
|
||||
defp do_split_while([], _, acc) do
|
||||
{ :lists.reverse(acc), [] }
|
||||
{:lists.reverse(acc), []}
|
||||
end
|
||||
|
||||
## take
|
||||
@@ -2114,7 +2123,7 @@ defmodule Enum do
|
||||
## zip
|
||||
|
||||
defp do_zip([h1|next1], [h2|next2]) do
|
||||
[{ h1, h2 }|do_zip(next1, next2)]
|
||||
[{h1, h2}|do_zip(next1, next2)]
|
||||
end
|
||||
|
||||
defp do_zip(_, []), do: []
|
||||
@@ -2140,15 +2149,15 @@ defmodule Enum do
|
||||
end
|
||||
|
||||
defimpl Enumerable, for: List do
|
||||
def reduce(_, { :halt, acc }, _fun), do: { :halted, acc }
|
||||
def reduce(list, { :suspend, acc }, fun), do: { :suspended, acc, &reduce(list, &1, fun) }
|
||||
def reduce([], { :cont, acc }, _fun), do: { :done, acc }
|
||||
def reduce([h|t], { :cont, acc }, fun), do: reduce(t, fun.(h, acc), fun)
|
||||
def reduce(_, {:halt, acc}, _fun), do: {:halted, acc}
|
||||
def reduce(list, {:suspend, acc}, fun), do: {:suspended, acc, &reduce(list, &1, fun)}
|
||||
def reduce([], {:cont, acc}, _fun), do: {:done, acc}
|
||||
def reduce([h|t], {:cont, acc}, fun), do: reduce(t, fun.(h, acc), fun)
|
||||
|
||||
def member?(_list, _value),
|
||||
do: { :error, __MODULE__ }
|
||||
do: {:error, __MODULE__}
|
||||
def count(_list),
|
||||
do: { :error, __MODULE__ }
|
||||
do: {:error, __MODULE__}
|
||||
end
|
||||
|
||||
defimpl Enumerable, for: Map do
|
||||
@@ -2156,21 +2165,21 @@ defimpl Enumerable, for: Map do
|
||||
do_reduce(:maps.to_list(map), acc, fun)
|
||||
end
|
||||
|
||||
defp do_reduce(_, { :halt, acc }, _fun), do: { :halted, acc }
|
||||
defp do_reduce(list, { :suspend, acc }, fun), do: { :suspended, acc, &do_reduce(list, &1, fun) }
|
||||
defp do_reduce([], { :cont, acc }, _fun), do: { :done, acc }
|
||||
defp do_reduce([h|t], { :cont, acc }, fun), do: do_reduce(t, fun.(h, acc), fun)
|
||||
defp do_reduce(_, {:halt, acc}, _fun), do: {:halted, acc}
|
||||
defp do_reduce(list, {:suspend, acc}, fun), do: {:suspended, acc, &do_reduce(list, &1, fun)}
|
||||
defp do_reduce([], {:cont, acc}, _fun), do: {:done, acc}
|
||||
defp do_reduce([h|t], {:cont, acc}, fun), do: do_reduce(t, fun.(h, acc), fun)
|
||||
|
||||
def member?(map, { key, value }) do
|
||||
{ :ok, match?({ :ok, ^value }, :maps.find(key, map)) }
|
||||
def member?(map, {key, value}) do
|
||||
{:ok, match?({:ok, ^value}, :maps.find(key, map))}
|
||||
end
|
||||
|
||||
def member?(_map, _other) do
|
||||
{ :ok, false }
|
||||
{:ok, false}
|
||||
end
|
||||
|
||||
def count(map) do
|
||||
{ :ok, map_size(map) }
|
||||
{:ok, map_size(map)}
|
||||
end
|
||||
end
|
||||
|
||||
@@ -2178,7 +2187,7 @@ defimpl Enumerable, for: Function do
|
||||
def reduce(function, acc, fun),
|
||||
do: function.(acc, fun)
|
||||
def member?(_function, _value),
|
||||
do: { :error, __MODULE__ }
|
||||
do: {:error, __MODULE__}
|
||||
def count(_function),
|
||||
do: { :error, __MODULE__ }
|
||||
do: {:error, __MODULE__}
|
||||
end
|
||||
|
||||
+329
-51
@@ -63,7 +63,7 @@ defexception BadArityError, [function: nil, args: nil] do
|
||||
fun = exception.function
|
||||
args = exception.args
|
||||
insp = Enum.map_join(args, ", ", &inspect/1)
|
||||
{ :arity, arity } = :erlang.fun_info(fun, :arity)
|
||||
{:arity, arity} = :erlang.fun_info(fun, :arity)
|
||||
"#{inspect(fun)} with arity #{arity} called with #{count(length(args), insp)}"
|
||||
end
|
||||
|
||||
@@ -117,20 +117,37 @@ defexception KeyError, key: nil, term: nil do
|
||||
end
|
||||
end
|
||||
|
||||
defexception UnicodeConversionError, [:encoded, :message] do
|
||||
def exception(opts) do
|
||||
UnicodeConversionError[
|
||||
encoded: opts[:encoded],
|
||||
message: "#{opts[:kind]} #{detail(opts[:rest])}"
|
||||
]
|
||||
end
|
||||
|
||||
defp detail(rest) when is_binary(rest) do
|
||||
"encoding starting at #{inspect rest}"
|
||||
end
|
||||
|
||||
defp detail([h|_]) do
|
||||
"code point #{h}"
|
||||
end
|
||||
end
|
||||
|
||||
defexception Enum.OutOfBoundsError, message: "out of bounds error"
|
||||
|
||||
defexception Enum.EmptyError, message: "empty error"
|
||||
|
||||
defexception File.Error, [reason: nil, action: "", path: nil] do
|
||||
def message(exception) do
|
||||
formatted = iolist_to_binary(:file.format_error(reason exception))
|
||||
formatted = iodata_to_binary(:file.format_error(reason exception))
|
||||
"could not #{action exception} #{path exception}: #{formatted}"
|
||||
end
|
||||
end
|
||||
|
||||
defexception File.CopyError, [reason: nil, action: "", source: nil, destination: nil, on: nil] do
|
||||
def message(exception) do
|
||||
formatted = iolist_to_binary(:file.format_error(reason exception))
|
||||
formatted = iodata_to_binary(:file.format_error(reason exception))
|
||||
location = if on = on(exception), do: ". #{on}", else: ""
|
||||
"could not #{action exception} from #{source exception} to " <>
|
||||
"#{destination exception}#{location}: #{formatted}"
|
||||
@@ -154,15 +171,39 @@ defmodule Exception do
|
||||
exceptions as in the current Elixir version being used.
|
||||
"""
|
||||
|
||||
@type t :: tuple
|
||||
@type stacktrace :: [stacktrace_entry]
|
||||
|
||||
@type process ::
|
||||
pid | atom | {atom, node} | {:global, term} | {:via, module, term}
|
||||
@type stacktrace_entry ::
|
||||
{ module, function, arity_or_args, location } |
|
||||
{ function, arity_or_args, location }
|
||||
{module, function, arity_or_args, location} |
|
||||
{function, arity_or_args, location}
|
||||
|
||||
@typep arity_or_args :: non_neg_integer | list
|
||||
@typep location :: Keyword.t
|
||||
|
||||
@doc """
|
||||
Returns true if the given argument is an exception.
|
||||
"""
|
||||
def exception?(exception) do
|
||||
is_tuple(exception) and tuple_size(exception) > 1 and
|
||||
:erlang.element(2, exception) == :__exception__
|
||||
end
|
||||
|
||||
@doc """
|
||||
Gets the message for an exception.
|
||||
"""
|
||||
def message(exception) do
|
||||
try do
|
||||
exception.message
|
||||
rescue
|
||||
e ->
|
||||
raise ArgumentError, message:
|
||||
"Got #{inspect e.__record__(:name)} with message " <>
|
||||
"\"#{message(e)}\" while retrieving message for #{inspect(exception)}"
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Normalizes an exception, converting Erlang exceptions
|
||||
to Elixir exceptions.
|
||||
@@ -170,74 +211,311 @@ defmodule Exception do
|
||||
It takes the `kind` spilled by `catch` as an argument and
|
||||
normalizes only `:error`, returning the untouched payload
|
||||
for others.
|
||||
|
||||
The third argument, a stacktrace, is optional. If it is
|
||||
not supplied `System.stacktrace/0` will sometimes be used
|
||||
to get additional information for the `kind` `:error`. If
|
||||
the stacktrace is unknown and `System.stacktrace/0` would
|
||||
not return the stacktrace corresponding to the exception
|
||||
an empty stacktrace, `[]`, must be used.
|
||||
"""
|
||||
def normalize(:error, exception), do: normalize_error(exception)
|
||||
def normalize(_kind, payload), do: payload
|
||||
@spec normalize(:error, any, stacktrace) :: t
|
||||
@spec normalize(:exit | :throw, any, stacktrace) :: any
|
||||
# Generating a stacktrace is expensive, default to nil to only fetch it when
|
||||
# needed.
|
||||
def normalize(kind, payload, stacktrace \\ nil)
|
||||
|
||||
@doc false
|
||||
def normalize(error) do
|
||||
IO.write :stderr, "Exception.normalize/1 is deprecated, please use Exception.normalize/2 instead\n#{Exception.format_stacktrace}"
|
||||
normalize_error(error)
|
||||
def normalize(:error, exception, stacktrace) do
|
||||
if exception?(exception) do
|
||||
exception
|
||||
else
|
||||
normalize_error(exception, stacktrace)
|
||||
end
|
||||
end
|
||||
|
||||
defp normalize_error(exception) when is_exception(exception) do
|
||||
exception
|
||||
def normalize(_kind, payload, _stacktrace) do
|
||||
payload
|
||||
end
|
||||
|
||||
defp normalize_error(:badarg) do
|
||||
defp normalize_error(:badarg, _stacktrace) do
|
||||
ArgumentError[]
|
||||
end
|
||||
|
||||
defp normalize_error(:badarith) do
|
||||
defp normalize_error(:badarith, _stacktrace) do
|
||||
ArithmeticError[]
|
||||
end
|
||||
|
||||
defp normalize_error(:system_limit) do
|
||||
defp normalize_error(:system_limit, _stacktrace) do
|
||||
SystemLimitError[]
|
||||
end
|
||||
|
||||
defp normalize_error({ :badarity, { fun, args } }) do
|
||||
defp normalize_error({:badarity, {fun, args}}, _stacktrace) do
|
||||
BadArityError[function: fun, args: args]
|
||||
end
|
||||
|
||||
defp normalize_error({ :badfun, term }) do
|
||||
defp normalize_error({:badfun, term}, _stacktrace) do
|
||||
BadFunctionError[term: term]
|
||||
end
|
||||
|
||||
defp normalize_error({ :badstruct, struct, term }) do
|
||||
defp normalize_error({:badstruct, struct, term}, _stacktrace) do
|
||||
BadStructError[struct: struct, term: term]
|
||||
end
|
||||
|
||||
defp normalize_error({ :badmatch, term }) do
|
||||
defp normalize_error({:badmatch, term}, _stacktrace) do
|
||||
MatchError[term: term]
|
||||
end
|
||||
|
||||
defp normalize_error({ :case_clause, term }) do
|
||||
defp normalize_error({:case_clause, term}, _stacktrace) do
|
||||
CaseClauseError[term: term]
|
||||
end
|
||||
|
||||
defp normalize_error({ :try_clause, term }) do
|
||||
defp normalize_error({:try_clause, term}, _stacktrace) do
|
||||
TryClauseError[term: term]
|
||||
end
|
||||
|
||||
defp normalize_error(:undef) do
|
||||
{ mod, fun, arity } = from_stacktrace(:erlang.get_stacktrace)
|
||||
defp normalize_error(:undef, stacktrace) do
|
||||
{mod, fun, arity} = from_stacktrace(stacktrace || :erlang.get_stacktrace)
|
||||
UndefinedFunctionError[module: mod, function: fun, arity: arity]
|
||||
end
|
||||
|
||||
defp normalize_error(:function_clause) do
|
||||
{ mod, fun, arity } = from_stacktrace(:erlang.get_stacktrace)
|
||||
defp normalize_error(:function_clause, stacktrace) do
|
||||
{mod, fun, arity} = from_stacktrace(stacktrace || :erlang.get_stacktrace)
|
||||
FunctionClauseError[module: mod, function: fun, arity: arity]
|
||||
end
|
||||
|
||||
defp normalize_error({ :badarg, payload }) do
|
||||
defp normalize_error({:badarg, payload}, _stacktrace) do
|
||||
ArgumentError[message: "argument error: #{inspect(payload)}"]
|
||||
end
|
||||
|
||||
defp normalize_error(other) do
|
||||
defp normalize_error(other, _stacktrace) do
|
||||
ErlangError[original: other]
|
||||
end
|
||||
|
||||
@doc """
|
||||
Normalizes and formats any throw, error and exit.
|
||||
|
||||
The message is formatted and displayed in the same
|
||||
format as used by Elixir's CLI.
|
||||
|
||||
The third argument, a stacktrace, is optional. If it is
|
||||
not supplied `System.stacktrace/0` will sometimes be used
|
||||
to get additional information for the `kind` `:error`. If
|
||||
the stacktrace is unknown and `System.stacktrace/0` would
|
||||
not return the stacktrace corresponding to the exception
|
||||
an empty stacktrace, `[]`, must be used.
|
||||
"""
|
||||
@spec format_banner(:error | :exit | :throw, any, stacktrace) :: String.t
|
||||
def format_banner(kind, exception, stacktrace \\ nil)
|
||||
|
||||
def format_banner(:error, exception, stacktrace) do
|
||||
exception = normalize(:error, exception, stacktrace)
|
||||
"** (" <> inspect(exception.__record__(:name)) <> ") " <> message(exception)
|
||||
end
|
||||
|
||||
def format_banner(:throw, reason, _stacktrace) do
|
||||
"** (throw) " <> inspect(reason)
|
||||
end
|
||||
|
||||
def format_banner(:exit, reason, _stacktrace) do
|
||||
"** (exit) " <> format_exit(reason, <<"\n ">>)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Normalizes and formats an exception and stacktrace.
|
||||
|
||||
Behaves the same as `format_banner/3` but also includes
|
||||
the formatted stacktrace.
|
||||
"""
|
||||
@spec format(:error | :exit | :throw, any, stacktrace) :: String.t
|
||||
def format(kind, exception, stacktrace \\ :erlang.get_stacktrace()) do
|
||||
message = format_banner(kind, exception, stacktrace)
|
||||
case stacktrace do
|
||||
[] -> message
|
||||
_ -> message <> "\n" <> format_stacktrace(stacktrace)
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Formats an exit, returns a string.
|
||||
|
||||
Often there are errors/exceptions inside exits. Exits are often
|
||||
wrapped by the caller and provide stacktraces too. This function
|
||||
formats exits in a way to nicely show the exit reason, caller
|
||||
and stacktrace.
|
||||
"""
|
||||
@spec format_exit(any) :: String.t
|
||||
def format_exit(reason) do
|
||||
format_exit(reason, <<"\n ">>)
|
||||
end
|
||||
|
||||
# 2-Tuple could be caused by an error if the second element is a stacktrace.
|
||||
defp format_exit({exception, maybe_stacktrace} = reason, joiner)
|
||||
when is_list(maybe_stacktrace) and maybe_stacktrace !== [] do
|
||||
try do
|
||||
Enum.map(maybe_stacktrace, &format_stacktrace_entry/1)
|
||||
else
|
||||
formatted_stacktrace ->
|
||||
# Assume a non-empty list formattable as stacktrace is a
|
||||
# stacktrace, so exit was caused by an error.
|
||||
message = "an exception was raised:" <> joiner <>
|
||||
format_banner(:error, exception, maybe_stacktrace)
|
||||
Enum.join([message | formatted_stacktrace], joiner <> <<" ">>)
|
||||
catch
|
||||
:error, _ ->
|
||||
# Not a stacktrace, was an exit.
|
||||
format_exit_reason(reason)
|
||||
end
|
||||
end
|
||||
|
||||
# :supervisor.start_link returns this error reason when it fails to init
|
||||
# because a child's start_link raises.
|
||||
defp format_exit({:shutdown,
|
||||
{:failed_to_start_child, child, {:EXIT, reason}}}, joiner) do
|
||||
format_start_child(child, reason, joiner)
|
||||
end
|
||||
|
||||
# :supervisor.start_link returns this error reason when it fails to init
|
||||
# because a child's start_link returns {:error, reason}.
|
||||
defp format_exit({:shutdown, {:failed_to_start_child, child, reason}},
|
||||
joiner) do
|
||||
format_start_child(child, reason, joiner)
|
||||
end
|
||||
|
||||
# 2-Tuple could be an exit caused by mfa if second element is mfa, args
|
||||
# must be a list of arguments - max length 255 due to max arity.
|
||||
defp format_exit({reason2, {mod, fun, args}} = reason, joiner)
|
||||
when length(args) < 256 do
|
||||
try do
|
||||
format_mfa(mod, fun, args)
|
||||
else
|
||||
mfa ->
|
||||
# Assume tuple formattable as an mfa is an mfa, so exit was caused by
|
||||
# failed mfa.
|
||||
"exited in: " <> mfa <> joiner <>
|
||||
"** (EXIT) " <> format_exit(reason2, joiner <> <<" ">>)
|
||||
catch
|
||||
:error, _ ->
|
||||
# Not an mfa, was an exit.
|
||||
format_exit_reason(reason)
|
||||
end
|
||||
end
|
||||
|
||||
defp format_exit(reason, _joiner) do
|
||||
format_exit_reason(reason)
|
||||
end
|
||||
|
||||
defp format_exit_reason(:normal), do: "normal"
|
||||
defp format_exit_reason(:shutdown), do: "shutdown"
|
||||
|
||||
defp format_exit_reason({:shutdown, reason}) do
|
||||
"shutdown: #{inspect(reason)}"
|
||||
end
|
||||
|
||||
defp format_exit_reason(:timeout), do: "time out"
|
||||
defp format_exit_reason(:killed), do: "killed"
|
||||
defp format_exit_reason(:noconnection), do: "no connection"
|
||||
|
||||
defp format_exit_reason(:noproc) do
|
||||
"no process"
|
||||
end
|
||||
|
||||
defp format_exit_reason({:nodedown, node_name}) when is_atom(node_name) do
|
||||
"no connection to #{node_name}"
|
||||
end
|
||||
|
||||
# :gen_server exit reasons
|
||||
|
||||
defp format_exit_reason({:already_started, pid}) do
|
||||
"already started: " <> inspect(pid)
|
||||
end
|
||||
|
||||
defp format_exit_reason({:bad_return_value, value}) do
|
||||
"bad return value: " <> inspect(value)
|
||||
end
|
||||
|
||||
defp format_exit_reason({:bad_call, request}) do
|
||||
"bad call: " <> inspect(request)
|
||||
end
|
||||
|
||||
defp format_exit_reason({:bad_cast, request}) do
|
||||
"bad cast: " <> inspect(request)
|
||||
end
|
||||
|
||||
# :supervisor.start_link error reasons
|
||||
|
||||
# If value is a list will be be formatted by mfa exit in format_exit/1
|
||||
defp format_exit_reason({:bad_return, {mod, :init, value}})
|
||||
when is_atom(mod) do
|
||||
format_mfa(mod, :init, 1) <> " returned a bad value: " <> inspect(value)
|
||||
end
|
||||
|
||||
defp format_exit_reason({:bad_start_spec, start_spec}) do
|
||||
"bad start spec: invalid children: " <> inspect(start_spec)
|
||||
end
|
||||
|
||||
defp format_exit_reason({:start_spec, start_spec}) do
|
||||
"bad start spec: " <> format_sup_spec(start_spec)
|
||||
end
|
||||
|
||||
defp format_exit_reason({:supervisor_data, data}) do
|
||||
"bad supervisor data: " <> format_sup_data(data)
|
||||
end
|
||||
|
||||
defp format_exit_reason(reason), do: inspect(reason)
|
||||
|
||||
defp format_start_child(child, reason, joiner) do
|
||||
"shutdown: failed to start child: " <> inspect(child) <> joiner <>
|
||||
"** (EXIT) " <> format_exit(reason, joiner <> <<" ">>)
|
||||
end
|
||||
|
||||
defp format_sup_data({:invalid_type, type}) do
|
||||
"invalid type: " <> inspect(type)
|
||||
end
|
||||
|
||||
defp format_sup_data({:invalid_strategy, strategy}) do
|
||||
"invalid strategy: " <> inspect(strategy)
|
||||
end
|
||||
|
||||
defp format_sup_data({:invalid_intensity, intensity}) do
|
||||
"invalid intensity: " <> inspect(intensity)
|
||||
end
|
||||
|
||||
defp format_sup_data({:invalid_period, period}) do
|
||||
"invalid period: " <> inspect(period)
|
||||
end
|
||||
|
||||
defp format_sup_data(other), do: inspect(other)
|
||||
|
||||
defp format_sup_spec({:invalid_child_spec, child_spec}) do
|
||||
"invalid child spec: " <> inspect(child_spec)
|
||||
end
|
||||
|
||||
defp format_sup_spec({:invalid_child_type, type}) do
|
||||
"invalid child type: " <> inspect(type)
|
||||
end
|
||||
|
||||
defp format_sup_spec({:invalid_mfa, mfa}) do
|
||||
"invalid mfa: " <> inspect(mfa)
|
||||
end
|
||||
|
||||
defp format_sup_spec({:invalid_restart_type, restart}) do
|
||||
"invalid restart type: " <> inspect(restart)
|
||||
end
|
||||
|
||||
defp format_sup_spec({:invalid_shutdown, shutdown}) do
|
||||
"invalid shutdown: " <> inspect(shutdown)
|
||||
end
|
||||
|
||||
defp format_sup_spec({:invalid_module, mod}) do
|
||||
"invalid module: " <> inspect(mod)
|
||||
end
|
||||
|
||||
defp format_sup_spec({:invalid_modules, modules}) do
|
||||
"invalid modules: " <> inspect(modules)
|
||||
end
|
||||
|
||||
defp format_sup_spec(other), do: inspect(other)
|
||||
|
||||
@doc """
|
||||
Receives a stacktrace entry and formats it into a string.
|
||||
"""
|
||||
@@ -245,31 +523,31 @@ defmodule Exception do
|
||||
def format_stacktrace_entry(entry)
|
||||
|
||||
# From Macro.Env.stacktrace
|
||||
def format_stacktrace_entry({ module, :__MODULE__, 0, location }) do
|
||||
def format_stacktrace_entry({module, :__MODULE__, 0, location}) do
|
||||
format_location(location) <> inspect(module) <> " (module)"
|
||||
end
|
||||
|
||||
# From :elixir_compiler_*
|
||||
def format_stacktrace_entry({ _module, :__MODULE__, 1, location }) do
|
||||
def format_stacktrace_entry({_module, :__MODULE__, 1, location}) do
|
||||
format_location(location) <> "(module)"
|
||||
end
|
||||
|
||||
# From :elixir_compiler_*
|
||||
def format_stacktrace_entry({ _module, :__FILE__, 1, location }) do
|
||||
def format_stacktrace_entry({_module, :__FILE__, 1, location}) do
|
||||
format_location(location) <> "(file)"
|
||||
end
|
||||
|
||||
def format_stacktrace_entry({ module, fun, arity, location }) do
|
||||
def format_stacktrace_entry({module, fun, arity, location}) do
|
||||
format_application(module) <> format_location(location) <> format_mfa(module, fun, arity)
|
||||
end
|
||||
|
||||
def format_stacktrace_entry({ fun, arity, location }) do
|
||||
def format_stacktrace_entry({fun, arity, location}) do
|
||||
format_location(location) <> format_fa(fun, arity)
|
||||
end
|
||||
|
||||
defp format_application(module) do
|
||||
case :application.get_application(module) do
|
||||
{ :ok, app } -> "(" <> atom_to_binary(app) <> ") "
|
||||
{:ok, app} -> "(" <> atom_to_binary(app) <> ") "
|
||||
:undefined -> ""
|
||||
end
|
||||
end
|
||||
@@ -277,16 +555,14 @@ defmodule Exception do
|
||||
@doc """
|
||||
Formats the stacktrace.
|
||||
|
||||
A stacktrace must be given as an argument. If not, this function
|
||||
calculates a new stacktrace based on the caller and formats it. As
|
||||
a consequence, the value of `System.stacktrace` is changed.
|
||||
A stacktrace must be given as an argument. If not, the stacktrace
|
||||
is retrieved from `Process.info/2`.
|
||||
"""
|
||||
def format_stacktrace(trace \\ nil) do
|
||||
trace = trace || try do
|
||||
throw(:stacktrace)
|
||||
catch
|
||||
:stacktrace -> Enum.drop(:erlang.get_stacktrace, 1)
|
||||
trace = trace || case Process.info(self, :current_stacktrace) do
|
||||
{:current_stacktrace, t} -> Enum.drop(t, 3)
|
||||
end
|
||||
|
||||
case trace do
|
||||
[] -> "\n"
|
||||
s -> " " <> Enum.map_join(s, "\n ", &format_stacktrace_entry(&1)) <> "\n"
|
||||
@@ -303,7 +579,7 @@ defmodule Exception do
|
||||
#=> "#Function<...>/1"
|
||||
|
||||
"""
|
||||
def format_fa(fun, arity) do
|
||||
def format_fa(fun, arity) when is_function(fun) do
|
||||
"#{inspect fun}#{format_arity(arity)}"
|
||||
end
|
||||
|
||||
@@ -327,7 +603,7 @@ defmodule Exception do
|
||||
where func is the name of the enclosing function. Convert to
|
||||
"anonymous fn in func/arity"
|
||||
"""
|
||||
def format_mfa(module, fun, arity) when is_atom(fun) do
|
||||
def format_mfa(module, fun, arity) when is_atom(module) and is_atom(fun) do
|
||||
fun =
|
||||
case inspect(fun) do
|
||||
":" <> fun -> fun
|
||||
@@ -347,7 +623,9 @@ defmodule Exception do
|
||||
"(#{Enum.join(inspected, ", ")})"
|
||||
end
|
||||
|
||||
defp format_arity(arity), do: "/#{arity}"
|
||||
defp format_arity(arity) when is_integer(arity) do
|
||||
"/" <> integer_to_binary(arity)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Formats the given file and line as shown in stacktraces.
|
||||
@@ -381,19 +659,19 @@ defmodule Exception do
|
||||
end
|
||||
end
|
||||
|
||||
defp format_location(opts) do
|
||||
defp format_location(opts) when is_list(opts) do
|
||||
format_file_line Keyword.get(opts, :file), Keyword.get(opts, :line), " "
|
||||
end
|
||||
|
||||
defp from_stacktrace([{ module, function, args, _ }|_]) when is_list(args) do
|
||||
{ module, function, length(args) }
|
||||
defp from_stacktrace([{module, function, args, _}|_]) when is_list(args) do
|
||||
{module, function, length(args)}
|
||||
end
|
||||
|
||||
defp from_stacktrace([{ module, function, arity, _ }|_]) do
|
||||
{ module, function, arity }
|
||||
defp from_stacktrace([{module, function, arity, _}|_]) do
|
||||
{module, function, arity}
|
||||
end
|
||||
|
||||
defp from_stacktrace(_) do
|
||||
{ nil, nil, nil }
|
||||
{nil, nil, nil}
|
||||
end
|
||||
end
|
||||
|
||||
+320
-196
File diff suppressed because it is too large
Load Diff
@@ -6,7 +6,7 @@ defmodule Float do
|
||||
@doc """
|
||||
Parses a binary into a float.
|
||||
|
||||
If successful, returns a tuple of the form `{ float, remainder_of_binary }`.
|
||||
If successful, returns a tuple of the form `{float, remainder_of_binary}`.
|
||||
Otherwise `:error`.
|
||||
|
||||
## Examples
|
||||
@@ -24,11 +24,11 @@ defmodule Float do
|
||||
:error
|
||||
|
||||
"""
|
||||
@spec parse(binary) :: { float, binary } | :error
|
||||
@spec parse(binary) :: {float, binary} | :error
|
||||
def parse("-" <> binary) do
|
||||
case parse_unsign(binary) do
|
||||
:error -> :error
|
||||
{ number, remainder } -> { -number, remainder }
|
||||
{number, remainder} -> {-number, remainder}
|
||||
end
|
||||
end
|
||||
|
||||
@@ -40,7 +40,7 @@ defmodule Float do
|
||||
defp parse_unsign(binary) when is_binary(binary) do
|
||||
case Integer.parse binary do
|
||||
:error -> :error
|
||||
{ integer_part, after_integer } -> parse_unsign after_integer, integer_part
|
||||
{integer_part, after_integer} -> parse_unsign after_integer, integer_part
|
||||
end
|
||||
end
|
||||
|
||||
@@ -50,7 +50,7 @@ defmodule Float do
|
||||
end
|
||||
|
||||
defp parse_unsign(rest, int) do
|
||||
{ :erlang.float(int), rest }
|
||||
{:erlang.float(int), rest}
|
||||
end
|
||||
|
||||
# Handle decimal points
|
||||
@@ -65,14 +65,14 @@ defmodule Float do
|
||||
# the function clause because the current approach copies a binary
|
||||
# just on this branch. If we broke it apart in the function clause,
|
||||
# the copy would happen when calling Integer.parse/1.
|
||||
{ floatify(int, float, decimal), << ?e, after_e :: binary >> }
|
||||
{ exponential, after_exponential } ->
|
||||
{ floatify(int, float, decimal, exponential), after_exponential }
|
||||
{floatify(int, float, decimal), << ?e, after_e :: binary >>}
|
||||
{exponential, after_exponential} ->
|
||||
{floatify(int, float, decimal, exponential), after_exponential}
|
||||
end
|
||||
end
|
||||
|
||||
defp parse_unsign(bitstring, float, decimal, int) do
|
||||
{ floatify(int, float, decimal), bitstring }
|
||||
{floatify(int, float, decimal), bitstring}
|
||||
end
|
||||
|
||||
defp floatify(int, float, decimal, exponential \\ 0) do
|
||||
|
||||
@@ -15,11 +15,11 @@ defmodule GenEvent.Behaviour do
|
||||
# Callbacks
|
||||
|
||||
def init(_) do
|
||||
{ :ok, [] }
|
||||
{:ok, []}
|
||||
end
|
||||
|
||||
def handle_event({:notification, x}, notifications) do
|
||||
{ :ok, [x|notifications] }
|
||||
{:ok, [x|notifications]}
|
||||
end
|
||||
|
||||
def handle_call(:notifications, notifications) do
|
||||
@@ -27,7 +27,7 @@ defmodule GenEvent.Behaviour do
|
||||
end
|
||||
end
|
||||
|
||||
{ :ok, pid } = :gen_event.start_link
|
||||
{:ok, pid} = :gen_event.start_link
|
||||
#=> {:ok,#PID<0.42.0>}
|
||||
|
||||
:gen_event.add_handler(pid, MyEventHandler, [])
|
||||
@@ -63,22 +63,22 @@ defmodule GenEvent.Behaviour do
|
||||
|
||||
@doc false
|
||||
def init(args) do
|
||||
{ :ok, args }
|
||||
{:ok, args}
|
||||
end
|
||||
|
||||
@doc false
|
||||
def handle_event(_event, state) do
|
||||
{ :ok, state }
|
||||
{:ok, state}
|
||||
end
|
||||
|
||||
@doc false
|
||||
def handle_call(_request, state) do
|
||||
{ :ok, :ok, state }
|
||||
{:ok, :ok, state}
|
||||
end
|
||||
|
||||
@doc false
|
||||
def handle_info(_msg, state) do
|
||||
{ :ok, state }
|
||||
{:ok, state}
|
||||
end
|
||||
|
||||
@doc false
|
||||
@@ -88,7 +88,7 @@ defmodule GenEvent.Behaviour do
|
||||
|
||||
@doc false
|
||||
def code_change(_old, state, _extra) do
|
||||
{ :ok, state }
|
||||
{:ok, state}
|
||||
end
|
||||
|
||||
defoverridable [init: 1,
|
||||
|
||||
@@ -17,7 +17,7 @@ defmodule GenServer.Behaviour do
|
||||
# Callbacks
|
||||
|
||||
def handle_call(:pop, _from, [h|t]) do
|
||||
{ :reply, h, t }
|
||||
{:reply, h, t}
|
||||
end
|
||||
|
||||
def handle_call(request, from, config) do
|
||||
@@ -25,8 +25,8 @@ defmodule GenServer.Behaviour do
|
||||
super(request, from, config)
|
||||
end
|
||||
|
||||
def handle_cast({ :push, item }, config) do
|
||||
{ :noreply, [item|config] }
|
||||
def handle_cast({:push, item}, config) do
|
||||
{:noreply, [item|config]}
|
||||
end
|
||||
|
||||
def handle_cast(request, config) do
|
||||
@@ -34,12 +34,12 @@ defmodule GenServer.Behaviour do
|
||||
end
|
||||
end
|
||||
|
||||
{ :ok, pid } = :gen_server.start_link(MyServer, [:hello], [])
|
||||
{:ok, pid} = :gen_server.start_link(MyServer, [:hello], [])
|
||||
|
||||
:gen_server.call(pid, :pop)
|
||||
#=> :hello
|
||||
|
||||
:gen_server.cast(pid, { :push, :world })
|
||||
:gen_server.cast(pid, {:push, :world})
|
||||
#=> :ok
|
||||
|
||||
:gen_server.call(pid, :pop)
|
||||
@@ -51,14 +51,14 @@ defmodule GenServer.Behaviour do
|
||||
GenServer, there are 8 different values a callback such as
|
||||
`handle_call` or `handle_cast` can return:
|
||||
|
||||
{ :reply, reply, new_state }
|
||||
{ :reply, reply, new_state, timeout }
|
||||
{ :reply, reply, new_state, :hibernate }
|
||||
{ :noreply, new_state }
|
||||
{ :noreply, new_state, timeout }
|
||||
{ :noreply, new_state, :hibernate }
|
||||
{ :stop, reason, new_state }
|
||||
{ :stop, reason, reply, new_state }
|
||||
{:reply, reply, new_state}
|
||||
{:reply, reply, new_state, timeout}
|
||||
{:reply, reply, new_state, :hibernate}
|
||||
{:noreply, new_state}
|
||||
{:noreply, new_state, timeout}
|
||||
{:noreply, new_state, :hibernate}
|
||||
{:stop, reason, new_state}
|
||||
{:stop, reason, reply, new_state}
|
||||
|
||||
There are 6 callbacks required to be implemented in a GenServer. The
|
||||
`GenServer.Behaviour` module defines all of them automatically, but
|
||||
@@ -90,22 +90,22 @@ defmodule GenServer.Behaviour do
|
||||
|
||||
@doc false
|
||||
def init(args) do
|
||||
{ :ok, args }
|
||||
{:ok, args}
|
||||
end
|
||||
|
||||
@doc false
|
||||
def handle_call(request, _from, state) do
|
||||
{ :stop, { :bad_call, request }, state }
|
||||
{:stop, {:bad_call, request}, state}
|
||||
end
|
||||
|
||||
@doc false
|
||||
def handle_info(_msg, state) do
|
||||
{ :noreply, state }
|
||||
{:noreply, state}
|
||||
end
|
||||
|
||||
@doc false
|
||||
def handle_cast(msg, state) do
|
||||
{ :stop, { :bad_cast, msg }, state }
|
||||
{:stop, {:bad_cast, msg}, state}
|
||||
end
|
||||
|
||||
@doc false
|
||||
@@ -115,7 +115,7 @@ defmodule GenServer.Behaviour do
|
||||
|
||||
@doc false
|
||||
def code_change(_old, state, _extra) do
|
||||
{ :ok, state }
|
||||
{:ok, state}
|
||||
end
|
||||
|
||||
defoverridable [init: 1, handle_call: 3, handle_info: 2,
|
||||
|
||||
+44
-63
@@ -2,8 +2,14 @@ defmodule HashDict do
|
||||
@moduledoc """
|
||||
A key-value store.
|
||||
|
||||
The `HashDict` is implemented using tries, which grows in
|
||||
space as the number of keys grows, working well with both
|
||||
The `HashDict` is represented internally as a struct, therefore
|
||||
`%HashDict{}` can be used whenever there is a need to match
|
||||
on any `HashDict`. Note though the struct fields are private and
|
||||
must not be accessed directly. Instead, use the functions on this
|
||||
or in the `Dict` module.
|
||||
|
||||
Implementation-wise, `HashDict` is implemented using tries, which
|
||||
grows in space as the number of keys grows, working well with both
|
||||
small and large set of keys. For more information about the
|
||||
functions and their APIs, please consult the `Dict` module.
|
||||
"""
|
||||
@@ -15,119 +21,94 @@ defmodule HashDict do
|
||||
@node_size 8
|
||||
@node_template :erlang.make_tuple(@node_size, [])
|
||||
|
||||
defrecordp :trie, HashDict,
|
||||
size: 0,
|
||||
root: @node_template
|
||||
@opaque t :: map
|
||||
defstruct size: 0, root: @node_template
|
||||
|
||||
# Inline common instructions
|
||||
@compile :inline_list_funcs
|
||||
@compile { :inline, key_hash: 1, key_mask: 1, key_shift: 1 }
|
||||
@compile {:inline, key_hash: 1, key_mask: 1, key_shift: 1}
|
||||
|
||||
@doc """
|
||||
Creates a new empty dict.
|
||||
"""
|
||||
@spec new :: Dict.t
|
||||
def new do
|
||||
trie()
|
||||
%HashDict{}
|
||||
end
|
||||
|
||||
@doc false
|
||||
@spec new(Enum.t) :: Dict.t
|
||||
def new(enum) do
|
||||
IO.write :stderr, "HashDict.new/1 is deprecated, please use Enum.into/2 instead\n#{Exception.format_stacktrace}"
|
||||
Enum.reduce enum, trie(), fn { k, v }, dict ->
|
||||
put(dict, k, v)
|
||||
end
|
||||
def put(%HashDict{root: root, size: size}, key, value) do
|
||||
{root, counter} = do_put(root, key, value, key_hash(key))
|
||||
%HashDict{root: root, size: size + counter}
|
||||
end
|
||||
|
||||
@doc false
|
||||
@spec new(Enum.t, (term -> {key :: term, value ::term})) :: Dict.t
|
||||
def new(enum, transform) when is_function(transform) do
|
||||
IO.write :stderr, "HashDict.new/2 is deprecated, please use Enum.into/3 instead\n#{Exception.format_stacktrace}"
|
||||
Enum.reduce enum, trie(), fn i, dict ->
|
||||
{ k, v } = transform.(i)
|
||||
put(dict, k, v)
|
||||
end
|
||||
end
|
||||
|
||||
def empty(trie()) do
|
||||
IO.write :stderr, "HashDict.empty/1 is deprecated, please use Collectable.empty/1 instead\n#{Exception.format_stacktrace}"
|
||||
trie()
|
||||
end
|
||||
|
||||
def put(trie(root: root, size: size), key, value) do
|
||||
{ root, counter } = do_put(root, key, value, key_hash(key))
|
||||
trie(root: root, size: size + counter)
|
||||
end
|
||||
|
||||
def update!(trie(root: root, size: size) = dict, key, fun) when is_function(fun, 1) do
|
||||
{ root, counter } = do_update(root, key, fn -> raise KeyError, key: key, term: dict end,
|
||||
def update!(%HashDict{root: root, size: size} = dict, key, fun) when is_function(fun, 1) do
|
||||
{root, counter} = do_update(root, key, fn -> raise KeyError, key: key, term: dict end,
|
||||
fun, key_hash(key))
|
||||
trie(root: root, size: size + counter)
|
||||
%HashDict{root: root, size: size + counter}
|
||||
end
|
||||
|
||||
def update(trie(root: root, size: size), key, initial, fun) when is_function(fun, 1) do
|
||||
{ root, counter } = do_update(root, key, fn -> initial end, fun, key_hash(key))
|
||||
trie(root: root, size: size + counter)
|
||||
def update(%HashDict{root: root, size: size}, key, initial, fun) when is_function(fun, 1) do
|
||||
{root, counter} = do_update(root, key, fn -> initial end, fun, key_hash(key))
|
||||
%HashDict{root: root, size: size + counter}
|
||||
end
|
||||
|
||||
def fetch(trie(root: root), key) do
|
||||
def fetch(%HashDict{root: root}, key) do
|
||||
do_fetch(root, key, key_hash(key))
|
||||
end
|
||||
|
||||
def delete(dict, key) do
|
||||
case dict_delete(dict, key) do
|
||||
{ dict, _value } -> dict
|
||||
{dict, _value} -> dict
|
||||
:error -> dict
|
||||
end
|
||||
end
|
||||
|
||||
def pop(dict, key, default \\ nil) do
|
||||
case dict_delete(dict, key) do
|
||||
{ dict, value } -> { value, dict }
|
||||
:error -> { default, dict }
|
||||
{dict, value} -> {value, dict}
|
||||
:error -> {default, dict}
|
||||
end
|
||||
end
|
||||
|
||||
def size(trie(size: size)) do
|
||||
def size(%HashDict{size: size}) do
|
||||
size
|
||||
end
|
||||
|
||||
@doc false
|
||||
def reduce(trie(root: root), acc, fun) do
|
||||
def reduce(%HashDict{root: root}, acc, fun) do
|
||||
do_reduce(root, acc, fun, @node_size, fn
|
||||
{:suspend, acc} -> {:suspended, acc, &{ :done, elem(&1, 1) }}
|
||||
{:suspend, acc} -> {:suspended, acc, &{:done, elem(&1, 1)}}
|
||||
{:halt, acc} -> {:halted, acc}
|
||||
{:cont, acc} -> {:done, acc}
|
||||
end)
|
||||
end
|
||||
|
||||
def split(dict, keys) do
|
||||
Enum.reduce keys, { new, dict }, fn key, { inc, exc } = acc ->
|
||||
Enum.reduce keys, {new, dict}, fn key, {inc, exc} = acc ->
|
||||
case dict_delete(exc, key) do
|
||||
{ exc, value } -> { put(inc, key, value), exc }
|
||||
{exc, value} -> {put(inc, key, value), exc}
|
||||
:error -> acc
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def merge(trie(size: size1) = dict1, trie(size: size2) = dict2, callback) when size1 < size2 do
|
||||
reduce(dict1, { :cont, dict2 }, fn { k, v1 }, acc ->
|
||||
{ :cont, update(acc, k, v1, &callback.(k, v1, &1)) }
|
||||
def merge(%HashDict{size: size1} = dict1, %HashDict{size: size2} = dict2, callback) when size1 < size2 do
|
||||
reduce(dict1, {:cont, dict2}, fn {k, v1}, acc ->
|
||||
{:cont, update(acc, k, v1, &callback.(k, v1, &1))}
|
||||
end) |> elem(1)
|
||||
end
|
||||
|
||||
def merge(trie() = dict1, trie() = dict2, callback) do
|
||||
reduce(dict2, { :cont, dict1 }, fn { k, v2 }, acc ->
|
||||
{ :cont, update(acc, k, v2, &callback.(k, &1, v2)) }
|
||||
def merge(%HashDict{} = dict1, %HashDict{} = dict2, callback) do
|
||||
reduce(dict2, {:cont, dict1}, fn {k, v2}, acc ->
|
||||
{:cont, update(acc, k, v2, &callback.(k, &1, v2))}
|
||||
end) |> elem(1)
|
||||
end
|
||||
|
||||
## General helpers
|
||||
|
||||
defp dict_delete(trie(root: root, size: size), key) do
|
||||
defp dict_delete(%HashDict{root: root, size: size}, key) do
|
||||
case do_delete(root, key, key_hash(key)) do
|
||||
{ root, value } -> { trie(root: root, size: size - 1), value }
|
||||
{root, value} -> {%HashDict{root: root, size: size - 1}, value}
|
||||
:error -> :error
|
||||
end
|
||||
end
|
||||
@@ -266,9 +247,9 @@ end
|
||||
|
||||
defimpl Enumerable, for: HashDict do
|
||||
def reduce(dict, acc, fun), do: HashDict.reduce(dict, acc, fun)
|
||||
def member?(dict, { k, v }), do: { :ok, match?({ :ok, ^v }, HashDict.fetch(dict, k)) }
|
||||
def member?(_dict, _), do: { :ok, false }
|
||||
def count(dict), do: { :ok, HashDict.size(dict) }
|
||||
def member?(dict, {k, v}), do: {:ok, match?({:ok, ^v}, HashDict.fetch(dict, k))}
|
||||
def member?(_dict, _), do: {:ok, false}
|
||||
def count(dict), do: {:ok, HashDict.size(dict)}
|
||||
end
|
||||
|
||||
defimpl Access, for: HashDict do
|
||||
@@ -281,10 +262,10 @@ defimpl Collectable, for: HashDict do
|
||||
end
|
||||
|
||||
def into(original) do
|
||||
{ original, fn
|
||||
dict, { :cont, { k, v } } -> Dict.put(dict, k, v)
|
||||
{original, fn
|
||||
dict, {:cont, {k, v}} -> Dict.put(dict, k, v)
|
||||
dict, :done -> dict
|
||||
_, :halt -> :ok
|
||||
end }
|
||||
end}
|
||||
end
|
||||
end
|
||||
|
||||
+38
-56
@@ -2,6 +2,12 @@ defmodule HashSet do
|
||||
@moduledoc """
|
||||
A set store.
|
||||
|
||||
The `HashSet` is represented internally as a struct, therefore
|
||||
`%HashSet{}` can be used whenever there is a need to match
|
||||
on any `HashSet`. Note though the struct fields are private and
|
||||
must not be accessed directly. Instead, use the functions on this
|
||||
or in the `Set` module.
|
||||
|
||||
The `HashSet` is implemented using tries, which grows in
|
||||
space as the number of keys grows, working well with both
|
||||
small and large set of keys. For more information about the
|
||||
@@ -15,55 +21,36 @@ defmodule HashSet do
|
||||
@node_size 8
|
||||
@node_template :erlang.make_tuple(@node_size, [])
|
||||
|
||||
defrecordp :trie, HashSet,
|
||||
size: 0,
|
||||
root: @node_template
|
||||
@opaque t :: map
|
||||
defstruct size: 0, root: @node_template
|
||||
|
||||
# Inline common instructions
|
||||
@compile :inline_list_funcs
|
||||
@compile { :inline, key_hash: 1, key_mask: 1, key_shift: 1 }
|
||||
@compile {:inline, key_hash: 1, key_mask: 1, key_shift: 1}
|
||||
|
||||
@doc """
|
||||
Creates a new empty set.
|
||||
"""
|
||||
@spec new :: Set.t
|
||||
def new do
|
||||
trie()
|
||||
%HashSet{}
|
||||
end
|
||||
|
||||
@doc false
|
||||
@spec new(Enum.t) :: Set.t
|
||||
def new(enum) do
|
||||
IO.write :stderr, "HashSet.new/1 is deprecated, please use Enum.into/2 instead\n#{Exception.format_stacktrace}"
|
||||
Enum.reduce enum, trie(), fn i, set ->
|
||||
put(set, i)
|
||||
end
|
||||
end
|
||||
|
||||
@doc false
|
||||
@spec new(Enum.t, (term -> term)) :: Set.t
|
||||
def new(enum, transform) when is_function(transform) do
|
||||
IO.write :stderr, "HashSet.new/2 is deprecated, please use Enum.into/3 instead\n#{Exception.format_stacktrace}"
|
||||
Enum.reduce enum, trie(), fn i, set ->
|
||||
put(set, transform.(i))
|
||||
end
|
||||
end
|
||||
|
||||
def union(trie(size: size1) = set1, trie(size: size2) = set2) when size1 <= size2 do
|
||||
def union(%HashSet{size: size1} = set1, %HashSet{size: size2} = set2) when size1 <= size2 do
|
||||
set_fold set1, set2, fn v, acc -> put(acc, v) end
|
||||
end
|
||||
|
||||
def union(trie() = set1, trie() = set2) do
|
||||
def union(%HashSet{} = set1, %HashSet{} = set2) do
|
||||
set_fold set2, set1, fn v, acc -> put(acc, v) end
|
||||
end
|
||||
|
||||
def intersection(trie() = set1, trie() = set2) do
|
||||
set_fold set1, trie(), fn v, acc ->
|
||||
def intersection(%HashSet{} = set1, %HashSet{} = set2) do
|
||||
set_fold set1, %HashSet{}, fn v, acc ->
|
||||
if member?(set2, v), do: put(acc, v), else: acc
|
||||
end
|
||||
end
|
||||
|
||||
def difference(trie() = set1, trie() = set2) do
|
||||
def difference(%HashSet{} = set1, %HashSet{} = set2) do
|
||||
set_fold set2, set1, fn v, acc -> delete(acc, v) end
|
||||
end
|
||||
|
||||
@@ -71,68 +58,63 @@ defmodule HashSet do
|
||||
set_fold(set, [], &[&1|&2]) |> :lists.reverse
|
||||
end
|
||||
|
||||
def equal?(trie(size: size1) = set1, trie(size: size2) = set2) do
|
||||
def equal?(%HashSet{size: size1} = set1, %HashSet{size: size2} = set2) do
|
||||
case size1 do
|
||||
^size2 -> subset?(set1, set2)
|
||||
_ -> false
|
||||
end
|
||||
end
|
||||
|
||||
def subset?(trie() = set1, trie() = set2) do
|
||||
reduce(set1, { :cont, true }, fn member, acc ->
|
||||
def subset?(%HashSet{} = set1, %HashSet{} = set2) do
|
||||
reduce(set1, {:cont, true}, fn member, acc ->
|
||||
case member?(set2, member) do
|
||||
true -> { :cont, acc }
|
||||
_ -> { :halt, false }
|
||||
true -> {:cont, acc}
|
||||
_ -> {:halt, false}
|
||||
end
|
||||
end) |> elem(1)
|
||||
end
|
||||
|
||||
def disjoint?(trie() = set1, trie() = set2) do
|
||||
reduce(set2, { :cont, true }, fn member, acc ->
|
||||
def disjoint?(%HashSet{} = set1, %HashSet{} = set2) do
|
||||
reduce(set2, {:cont, true}, fn member, acc ->
|
||||
case member?(set1, member) do
|
||||
false -> { :cont, acc }
|
||||
_ -> { :halt, false }
|
||||
false -> {:cont, acc}
|
||||
_ -> {:halt, false}
|
||||
end
|
||||
end) |> elem(1)
|
||||
end
|
||||
|
||||
def empty(trie()) do
|
||||
IO.write :stderr, "HashSet.empty/1 is deprecated, please use Collectable.empty/1 instead\n#{Exception.format_stacktrace}"
|
||||
trie()
|
||||
end
|
||||
|
||||
def member?(trie(root: root), term) do
|
||||
def member?(%HashSet{root: root}, term) do
|
||||
do_member?(root, term, key_hash(term))
|
||||
end
|
||||
|
||||
def put(trie(root: root, size: size), term) do
|
||||
def put(%HashSet{root: root, size: size}, term) do
|
||||
{root, counter} = do_put(root, term, key_hash(term))
|
||||
trie(root: root, size: size + counter)
|
||||
%HashSet{root: root, size: size + counter}
|
||||
end
|
||||
|
||||
def delete(trie(root: root, size: size) = set, term) do
|
||||
def delete(%HashSet{root: root, size: size} = set, term) do
|
||||
case do_delete(root, term, key_hash(term)) do
|
||||
{:ok, root} -> trie(root: root, size: size - 1)
|
||||
{:ok, root} -> %HashSet{root: root, size: size - 1}
|
||||
:error -> set
|
||||
end
|
||||
end
|
||||
|
||||
@doc false
|
||||
def reduce(trie(root: root), acc, fun) do
|
||||
def reduce(%HashSet{root: root}, acc, fun) do
|
||||
do_reduce(root, acc, fun, @node_size, fn
|
||||
{:suspend, acc} -> {:suspended, acc, &{ :done, elem(&1, 1) }}
|
||||
{:suspend, acc} -> {:suspended, acc, &{:done, elem(&1, 1)}}
|
||||
{:halt, acc} -> {:halted, acc}
|
||||
{:cont, acc} -> {:done, acc}
|
||||
end)
|
||||
end
|
||||
|
||||
def size(trie(size: size)) do
|
||||
def size(%HashSet{size: size}) do
|
||||
size
|
||||
end
|
||||
|
||||
## Set helpers
|
||||
|
||||
defp set_fold(trie(root: root), acc, fun) do
|
||||
defp set_fold(%HashSet{root: root}, acc, fun) do
|
||||
do_fold(root, acc, fun, @node_size)
|
||||
end
|
||||
|
||||
@@ -265,8 +247,8 @@ end
|
||||
|
||||
defimpl Enumerable, for: HashSet do
|
||||
def reduce(set, acc, fun), do: HashSet.reduce(set, acc, fun)
|
||||
def member?(set, v), do: { :ok, HashSet.member?(set, v) }
|
||||
def count(set), do: { :ok, HashSet.size(set) }
|
||||
def member?(set, v), do: {:ok, HashSet.member?(set, v)}
|
||||
def count(set), do: {:ok, HashSet.size(set)}
|
||||
end
|
||||
|
||||
defimpl Collectable, for: HashSet do
|
||||
@@ -275,10 +257,10 @@ defimpl Collectable, for: HashSet do
|
||||
end
|
||||
|
||||
def into(original) do
|
||||
{ original, fn
|
||||
set, { :cont, x } -> HashSet.put(set, x)
|
||||
{original, fn
|
||||
set, {:cont, x} -> HashSet.put(set, x)
|
||||
set, :done -> set
|
||||
_, :halt -> :ok
|
||||
end }
|
||||
end}
|
||||
end
|
||||
end
|
||||
|
||||
+57
-125
@@ -1,15 +1,6 @@
|
||||
import Kernel, except: [inspect: 1]
|
||||
import Inspect.Algebra
|
||||
|
||||
defrecord Inspect.Opts,
|
||||
records: true,
|
||||
structs: true,
|
||||
binaries: :infer,
|
||||
char_lists: :infer,
|
||||
limit: 50,
|
||||
pretty: false,
|
||||
width: 80
|
||||
|
||||
defprotocol Inspect do
|
||||
@moduledoc """
|
||||
The `Inspect` protocol is responsible for converting any Elixir
|
||||
@@ -50,10 +41,11 @@ defprotocol Inspect do
|
||||
## Error handling
|
||||
|
||||
In case there is an error while your structure is being inspected,
|
||||
Elixir will automatically fall back to tuple inspection for records.
|
||||
Elixir will automatically fall back to a raw representation.
|
||||
|
||||
You can however access the underlying error by invoking the Inspect
|
||||
implementation directly. For example, to test Inspect.HashSet above,
|
||||
you just need to do:
|
||||
you can invoke it as:
|
||||
|
||||
Inspect.HashSet.inspect(HashSet.new, Inspect.Opts.new)
|
||||
|
||||
@@ -68,26 +60,6 @@ end
|
||||
defimpl Inspect, for: Atom do
|
||||
require Macro
|
||||
|
||||
@doc """
|
||||
Represents the atom as an Elixir term. The atoms `false`, `true`
|
||||
and `nil` are simply quoted. Modules are properly represented
|
||||
as modules using the dot notation.
|
||||
|
||||
Notice that in Elixir, all operators can be represented using
|
||||
literal atoms (`:+`, `:-`, etc).
|
||||
|
||||
## Examples
|
||||
|
||||
iex> inspect(:foo)
|
||||
":foo"
|
||||
|
||||
iex> inspect(nil)
|
||||
"nil"
|
||||
|
||||
iex> inspect(Foo.Bar)
|
||||
"Foo.Bar"
|
||||
|
||||
"""
|
||||
def inspect(atom, _opts) do
|
||||
inspect(atom)
|
||||
end
|
||||
@@ -96,18 +68,21 @@ defimpl Inspect, for: Atom do
|
||||
def inspect(true), do: "true"
|
||||
def inspect(nil), do: "nil"
|
||||
def inspect(:""), do: ":\"\""
|
||||
def inspect(Elixir), do: "Elixir"
|
||||
|
||||
def inspect(atom) do
|
||||
binary = atom_to_binary(atom)
|
||||
|
||||
cond do
|
||||
valid_ref_identifier?(binary) ->
|
||||
if only_elixir?(binary) do
|
||||
binary
|
||||
else
|
||||
"Elixir." <> rest = binary
|
||||
rest
|
||||
end
|
||||
valid_atom_identifier?(binary) ->
|
||||
":" <> binary
|
||||
valid_ref_identifier?(binary) ->
|
||||
"Elixir." <> rest = binary
|
||||
rest
|
||||
atom in [:%{}, :{}, :<<>>, :..., :[]] ->
|
||||
atom in [:%{}, :{}, :<<>>, :..., :%] ->
|
||||
":" <> binary
|
||||
atom in Macro.binary_ops or atom in Macro.unary_ops ->
|
||||
":" <> binary
|
||||
@@ -116,6 +91,10 @@ defimpl Inspect, for: Atom do
|
||||
end
|
||||
end
|
||||
|
||||
defp only_elixir?("Elixir." <> rest), do: only_elixir?(rest)
|
||||
defp only_elixir?("Elixir"), do: true
|
||||
defp only_elixir?(_), do: false
|
||||
|
||||
# Detect if atom is an atom alias (Elixir.Foo.Bar.Baz)
|
||||
|
||||
defp valid_ref_identifier?("Elixir" <> rest) do
|
||||
@@ -161,24 +140,7 @@ defimpl Inspect, for: Atom do
|
||||
end
|
||||
|
||||
defimpl Inspect, for: BitString do
|
||||
@doc ~S"""
|
||||
Represents a string as itself escaping all necessary
|
||||
characters. Binaries that contain non-printable characters
|
||||
are printed using the bitstring syntax.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> inspect("bar")
|
||||
"\"bar\""
|
||||
|
||||
iex> inspect("f\"oo")
|
||||
"\"f\\\"oo\""
|
||||
|
||||
iex> inspect(<<0,1,2>>)
|
||||
"<<0, 1, 2>>"
|
||||
|
||||
"""
|
||||
def inspect(thing, Inspect.Opts[binaries: bins] = opts) when is_binary(thing) do
|
||||
def inspect(thing, %Inspect.Opts{binaries: bins} = opts) when is_binary(thing) do
|
||||
if bins == :as_strings or (bins == :infer and String.printable?(thing)) do
|
||||
<< ?", escape(thing, ?") :: binary, ?" >>
|
||||
else
|
||||
@@ -201,7 +163,7 @@ defimpl Inspect, for: BitString do
|
||||
escape(t, char, << binary :: binary, ?\\, char >>)
|
||||
end
|
||||
defp escape(<<?#, ?{, t :: binary>>, char, binary) do
|
||||
escape(t, char, << binary :: binary, ?\\, ?#, ?{ >>)
|
||||
escape(t, char, << binary :: binary, ?\\, ?#, ?{>>)
|
||||
end
|
||||
defp escape(<<?\a, t :: binary>>, char, binary) do
|
||||
escape(t, char, << binary :: binary, ?\\, ?a >>)
|
||||
@@ -258,7 +220,7 @@ defimpl Inspect, for: BitString do
|
||||
|
||||
## Bitstrings
|
||||
|
||||
defp inspect_bitstring(bitstring, Inspect.Opts[] = opts) do
|
||||
defp inspect_bitstring(bitstring, opts) do
|
||||
each_bit(bitstring, opts.limit, "<<") <> ">>"
|
||||
end
|
||||
|
||||
@@ -310,10 +272,10 @@ defimpl Inspect, for: List do
|
||||
|
||||
def inspect([], _opts), do: "[]"
|
||||
|
||||
def inspect(thing, Inspect.Opts[char_lists: lists] = opts) do
|
||||
def inspect(thing, %Inspect.Opts{char_lists: lists} = opts) do
|
||||
cond do
|
||||
lists == :as_char_lists or (lists == :infer and :io_lib.printable_list(thing)) ->
|
||||
<< ?', Inspect.BitString.escape(String.from_char_list!(thing), ?') :: binary, ?' >>
|
||||
lists == :as_char_lists or (lists == :infer and printable?(thing)) ->
|
||||
<< ?', Inspect.BitString.escape(String.from_char_data!(thing), ?') :: binary, ?' >>
|
||||
keyword?(thing) ->
|
||||
surround_many("[", thing, "]", opts.limit, &keyword(&1, opts))
|
||||
true ->
|
||||
@@ -328,14 +290,7 @@ defimpl Inspect, for: List do
|
||||
)
|
||||
end
|
||||
|
||||
defp key_to_binary(key) do
|
||||
case Inspect.Atom.inspect(key) do
|
||||
":" <> right -> right
|
||||
other -> other
|
||||
end
|
||||
end
|
||||
|
||||
def keyword?([{ key, _value } | rest]) when is_atom(key) do
|
||||
def keyword?([{key, _value} | rest]) when is_atom(key) do
|
||||
case atom_to_list(key) do
|
||||
'Elixir.' ++ _ -> false
|
||||
_ -> keyword?(rest)
|
||||
@@ -344,26 +299,33 @@ defimpl Inspect, for: List do
|
||||
|
||||
def keyword?([]), do: true
|
||||
def keyword?(_other), do: false
|
||||
|
||||
## Private
|
||||
|
||||
defp key_to_binary(key) do
|
||||
case Inspect.Atom.inspect(key) do
|
||||
":" <> right -> right
|
||||
other -> other
|
||||
end
|
||||
end
|
||||
|
||||
defp printable?([c|cs]) when is_integer(c) and c in 32..126, do: printable?(cs)
|
||||
defp printable?([?\n|cs]), do: printable?(cs)
|
||||
defp printable?([?\r|cs]), do: printable?(cs)
|
||||
defp printable?([?\t|cs]), do: printable?(cs)
|
||||
defp printable?([?\v|cs]), do: printable?(cs)
|
||||
defp printable?([?\b|cs]), do: printable?(cs)
|
||||
defp printable?([?\f|cs]), do: printable?(cs)
|
||||
defp printable?([?\e|cs]), do: printable?(cs)
|
||||
defp printable?([?\a|cs]), do: printable?(cs)
|
||||
defp printable?([]), do: true
|
||||
defp printable?(_), do: false
|
||||
end
|
||||
|
||||
defimpl Inspect, for: Tuple do
|
||||
@doc """
|
||||
Represents tuples. If the tuple represents a record,
|
||||
it shows it nicely formatted using the access syntax.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> inspect({1, 2, 3})
|
||||
"{1, 2, 3}"
|
||||
|
||||
iex> inspect(ArgumentError.new)
|
||||
"ArgumentError[message: \\\"argument error\\\"]"
|
||||
|
||||
"""
|
||||
|
||||
def inspect({}, _opts), do: "{}"
|
||||
|
||||
def inspect(tuple, Inspect.Opts[] = opts) do
|
||||
def inspect(tuple, opts) do
|
||||
if opts.records do
|
||||
record_inspect(tuple, opts)
|
||||
else
|
||||
@@ -373,7 +335,7 @@ defimpl Inspect, for: Tuple do
|
||||
|
||||
## Helpers
|
||||
|
||||
defp record_inspect(record, Inspect.Opts[] = opts) do
|
||||
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
|
||||
@@ -395,17 +357,17 @@ defimpl Inspect, for: Tuple do
|
||||
end
|
||||
end
|
||||
|
||||
defp surround_record(name, fields, tail, Inspect.Opts[] = opts) do
|
||||
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
|
||||
defp zip_fields([{key, _}|tk], [value|tv]) do
|
||||
case atom_to_binary(key) do
|
||||
"_" <> _ -> zip_fields(tk, tv)
|
||||
key -> [{ key, value }|zip_fields(tk, tv)]
|
||||
key -> [{key, value}|zip_fields(tk, tv)]
|
||||
end
|
||||
end
|
||||
|
||||
@@ -413,7 +375,7 @@ defimpl Inspect, for: Tuple do
|
||||
[]
|
||||
end
|
||||
|
||||
defp keyword({ k, v }, opts) do
|
||||
defp keyword({k, v}, opts) do
|
||||
concat(k <> ": ", to_doc(v, opts))
|
||||
end
|
||||
end
|
||||
@@ -445,51 +407,20 @@ defimpl Inspect, for: Map do
|
||||
end
|
||||
|
||||
defimpl Inspect, for: Integer do
|
||||
@doc """
|
||||
Represents the integer as a string.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> inspect(1)
|
||||
"1"
|
||||
|
||||
"""
|
||||
def inspect(thing, _opts) do
|
||||
integer_to_binary(thing)
|
||||
end
|
||||
end
|
||||
|
||||
defimpl Inspect, for: Float do
|
||||
@doc """
|
||||
Floats are represented using the shortened, correctly rounded string
|
||||
that converts to float when read back with `binary_to_float/1`. This
|
||||
is done via the Erlang implementation of _Printing Floating-Point
|
||||
Numbers Quickly and Accurately_ in Proceedings of the SIGPLAN '96
|
||||
Conference on Programming Language Design and Implementation.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> inspect(1.0)
|
||||
"1.0"
|
||||
|
||||
"""
|
||||
def inspect(thing, _opts) do
|
||||
iolist_to_binary(:io_lib_format.fwrite_g(thing))
|
||||
iodata_to_binary(:io_lib_format.fwrite_g(thing))
|
||||
end
|
||||
end
|
||||
|
||||
defimpl Inspect, for: Regex do
|
||||
@doc ~S"""
|
||||
Represents the Regex using the `~r""` syntax.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> inspect(~r/foo/m)
|
||||
"~r\"foo\"m"
|
||||
|
||||
"""
|
||||
def inspect(regex, opts) when size(regex) == 4 do
|
||||
concat ["~r", to_doc(Regex.source(regex), opts), Regex.opts(regex)]
|
||||
def inspect(regex, opts) do
|
||||
concat ["~r", to_doc(regex.source, opts), regex.opts]
|
||||
end
|
||||
end
|
||||
|
||||
@@ -524,7 +455,8 @@ defimpl Inspect, for: Function do
|
||||
end
|
||||
|
||||
defp extract_name(name) do
|
||||
case :binary.split(atom_to_binary(name), "-", [:global]) do
|
||||
name = atom_to_binary(name)
|
||||
case :binary.split(name, "-", [:global]) do
|
||||
["", name | _] -> "." <> name
|
||||
_ -> "." <> name
|
||||
end
|
||||
@@ -538,20 +470,20 @@ end
|
||||
|
||||
defimpl Inspect, for: PID do
|
||||
def inspect(pid, _opts) do
|
||||
"#PID" <> iolist_to_binary(:erlang.pid_to_list(pid))
|
||||
"#PID" <> iodata_to_binary(:erlang.pid_to_list(pid))
|
||||
end
|
||||
end
|
||||
|
||||
defimpl Inspect, for: Port do
|
||||
def inspect(port, _opts) do
|
||||
iolist_to_binary :erlang.port_to_list(port)
|
||||
iodata_to_binary :erlang.port_to_list(port)
|
||||
end
|
||||
end
|
||||
|
||||
defimpl Inspect, for: Reference do
|
||||
def inspect(ref, _opts) do
|
||||
'#Ref' ++ rest = :erlang.ref_to_list(ref)
|
||||
"#Reference" <> iolist_to_binary(rest)
|
||||
"#Reference" <> iodata_to_binary(rest)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -1,3 +1,41 @@
|
||||
defmodule Inspect.Opts do
|
||||
@moduledoc """
|
||||
Defines the Inspect.Opts used by the Inspect protocol.
|
||||
|
||||
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;
|
||||
|
||||
* `: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;
|
||||
|
||||
* `: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;
|
||||
|
||||
* `: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,
|
||||
binaries: :infer :: :infer | :as_binaries | :as_strings,
|
||||
char_lists: :infer :: :infer | :as_lists | :as_char_lists,
|
||||
limit: 50 :: pos_integer,
|
||||
width: 80 :: pos_integer | :infinity,
|
||||
pretty: false :: boolean,
|
||||
records: true :: boolean
|
||||
end
|
||||
|
||||
defmodule Inspect.Algebra do
|
||||
@moduledoc ~S"""
|
||||
A set of functions for creating and manipulating algebra
|
||||
@@ -74,14 +112,24 @@ defmodule Inspect.Algebra do
|
||||
|
||||
# Functional interface to `doc` records
|
||||
|
||||
@type t :: :doc_nil | :doc_line | doc_cons_t | doc_nest_t | doc_break_t | doc_group_t | binary
|
||||
defrecordp :doc_cons, left: :doc_nil :: t, right: :doc_nil :: t
|
||||
defrecordp :doc_nest, indent: 1 :: non_neg_integer, doc: :doc_nil :: t
|
||||
defrecordp :doc_break, str: " " :: binary
|
||||
defrecordp :doc_group, doc: :doc_nil :: t
|
||||
@type t :: :doc_nil | :doc_line | doc_cons | doc_nest | doc_break | doc_group | binary
|
||||
|
||||
require Record
|
||||
|
||||
@typep doc_cons :: {:doc_cons, t, t}
|
||||
Record.defrecordp :doc_cons, left: :doc_nil, right: :doc_nil
|
||||
|
||||
@typep doc_nest :: {:doc_nest, non_neg_integer, t}
|
||||
Record.defrecordp :doc_nest, indent: 1, doc: :doc_nil
|
||||
|
||||
@typep doc_break :: {:doc_break, binary}
|
||||
Record.defrecordp :doc_break, str: " "
|
||||
|
||||
@typep doc_group :: {:doc_group, t}
|
||||
Record.defrecordp :doc_group, doc: :doc_nil
|
||||
|
||||
defmacrop is_doc(doc) do
|
||||
if __CALLER__.in_guard? do
|
||||
if Macro.Env.in_guard?(__CALLER__) do
|
||||
do_is_doc(doc)
|
||||
else
|
||||
var = quote do: doc
|
||||
@@ -106,39 +154,40 @@ defmodule Inspect.Algebra do
|
||||
according to the inspect protocol.
|
||||
"""
|
||||
@spec to_doc(any, Inspect.Opts.t) :: t
|
||||
def to_doc(arg, opts) when is_record(arg) and is_record(opts, Inspect.Opts) do
|
||||
def to_doc(arg, %Inspect.Opts{} = opts) when is_record(arg) do
|
||||
# IO.write :stderr, "inspect records is deprecated (just remove this code when protocols+records are removed)\n#{Exception.format_stacktrace}"
|
||||
if opts.records do
|
||||
try do
|
||||
Inspect.inspect(arg, opts)
|
||||
rescue
|
||||
e ->
|
||||
res = Inspect.Tuple.inspect(arg, opts)
|
||||
IO.puts :stderr, "** (Inspect.Error) Got #{inspect e.__record__(:name)} with message " <>
|
||||
"#{e.message} while inspecting #{pretty(res, opts.width)}"
|
||||
res
|
||||
raise ArgumentError, message:
|
||||
"Got #{inspect e.__record__(:name)} with message " <>
|
||||
"#{Exception.message(e)} while inspecting #{pretty(res, opts.width)}"
|
||||
end
|
||||
else
|
||||
Inspect.Tuple.inspect(arg, opts)
|
||||
end
|
||||
end
|
||||
|
||||
def to_doc(%{ __struct__: struct } = map, opts) when is_atom(struct) and is_record(opts, Inspect.Opts) do
|
||||
def to_doc(%{__struct__: struct} = map, %Inspect.Opts{} = opts) when is_atom(struct) do
|
||||
if opts.structs do
|
||||
try do
|
||||
Inspect.inspect(map, opts)
|
||||
rescue
|
||||
e ->
|
||||
res = Inspect.Map.inspect(map, opts)
|
||||
IO.puts :stderr, "** (Inspect.Error) Got #{inspect e.__record__(:name)} with message " <>
|
||||
"#{e.message} while inspecting #{pretty(res, opts.width)}"
|
||||
res
|
||||
raise ArgumentError, message:
|
||||
"Got #{inspect e.__record__(:name)} with message " <>
|
||||
"\"#{Exception.message(e)}\" while inspecting #{pretty(res, opts.width)}"
|
||||
end
|
||||
else
|
||||
Inspect.Map.inspect(map, opts)
|
||||
end
|
||||
end
|
||||
|
||||
def to_doc(arg, opts) when is_record(opts, Inspect.Opts) do
|
||||
def to_doc(arg, %Inspect.Opts{} = opts) do
|
||||
Inspect.inspect(arg, opts)
|
||||
end
|
||||
|
||||
@@ -166,7 +215,7 @@ defmodule Inspect.Algebra do
|
||||
"TastelessArtosis"
|
||||
|
||||
"""
|
||||
@spec concat(t, t) :: doc_cons_t
|
||||
@spec concat(t, t) :: doc_cons
|
||||
def concat(x, y) when is_doc(x) and is_doc(y) do
|
||||
doc_cons(left: x, right: y)
|
||||
end
|
||||
@@ -174,7 +223,7 @@ defmodule Inspect.Algebra do
|
||||
@doc """
|
||||
Concatenates a list of documents.
|
||||
"""
|
||||
@spec concat([t]) :: doc_cons_t
|
||||
@spec concat([t]) :: doc_cons
|
||||
def concat(docs) do
|
||||
folddoc(docs, &concat(&1, &2))
|
||||
end
|
||||
@@ -190,7 +239,7 @@ defmodule Inspect.Algebra do
|
||||
" 6"
|
||||
|
||||
"""
|
||||
@spec nest(t, non_neg_integer) :: doc_nest_t
|
||||
@spec nest(t, non_neg_integer) :: doc_nest
|
||||
def nest(x, 0) when is_doc(x) do
|
||||
x
|
||||
end
|
||||
@@ -220,23 +269,23 @@ defmodule Inspect.Algebra do
|
||||
"aaaaaaaaaaaaaaaaaaaa\nb"
|
||||
|
||||
"""
|
||||
@spec break(binary) :: doc_break_t
|
||||
@spec break(binary) :: doc_break
|
||||
def break(s) when is_binary(s), do: doc_break(str: s)
|
||||
|
||||
@spec break() :: doc_break_t
|
||||
@spec break() :: doc_break
|
||||
def break(), do: doc_break(str: @break)
|
||||
|
||||
@doc """
|
||||
Inserts a break between two docs. See `break/1` for more info.
|
||||
"""
|
||||
@spec glue(t, t) :: doc_cons_t
|
||||
@spec glue(t, t) :: doc_cons
|
||||
def glue(x, y), do: concat(x, concat(break, y))
|
||||
|
||||
@doc """
|
||||
Inserts a break, passed as the second argument, between two docs,
|
||||
the first and the third arguments.
|
||||
"""
|
||||
@spec glue(t, binary, t) :: doc_cons_t
|
||||
@spec glue(t, binary, t) :: doc_cons
|
||||
def glue(x, g, y) when is_binary(g), do: concat(x, concat(break(g), y))
|
||||
|
||||
@doc ~S"""
|
||||
@@ -266,7 +315,7 @@ defmodule Inspect.Algebra do
|
||||
"Hello,\nA B"
|
||||
|
||||
"""
|
||||
@spec group(t) :: doc_group_t
|
||||
@spec group(t) :: doc_group
|
||||
def group(d) when is_doc(d) do
|
||||
doc_group(doc: d)
|
||||
end
|
||||
@@ -281,7 +330,7 @@ defmodule Inspect.Algebra do
|
||||
"Hughes Wadler"
|
||||
|
||||
"""
|
||||
@spec space(t, t) :: doc_cons_t
|
||||
@spec space(t, t) :: doc_cons
|
||||
def space(x, y), do: concat(x, concat(" ", y))
|
||||
|
||||
@doc ~S"""
|
||||
@@ -294,7 +343,7 @@ defmodule Inspect.Algebra do
|
||||
"Hughes\nWadler"
|
||||
|
||||
"""
|
||||
@spec line(t, t) :: doc_cons_t
|
||||
@spec line(t, t) :: doc_cons
|
||||
def line(x, y), do: concat(x, concat(:doc_line, y))
|
||||
|
||||
@doc """
|
||||
@@ -415,7 +464,7 @@ defmodule Inspect.Algebra do
|
||||
@typep mode :: :flat | :break
|
||||
|
||||
@doc false
|
||||
@spec fits?(integer, [{ integer, mode, t }]) :: boolean
|
||||
@spec fits?(integer, [{integer, mode, t}]) :: boolean
|
||||
def fits?(w, _) when w < 0, do: false
|
||||
def fits?(_, []), do: true
|
||||
def fits?(_, [{_, _, :doc_line} | _]), do: true
|
||||
@@ -428,7 +477,7 @@ defmodule Inspect.Algebra do
|
||||
def fits?(_, [{_, :break, doc_break(str: _)} | _]), do: true
|
||||
|
||||
@doc false
|
||||
@spec format(integer | :infinity, integer, [{ integer, mode, t }]) :: [binary]
|
||||
@spec format(integer | :infinity, integer, [{integer, mode, t}]) :: [binary]
|
||||
def format(_, _, []), do: []
|
||||
def format(w, _, [{i, _, :doc_line} | t]), do: [indent(i) | format(w, i, t)]
|
||||
def format(w, k, [{_, _, :doc_nil} | t]), do: format(w, k, t)
|
||||
@@ -453,6 +502,6 @@ defmodule Inspect.Algebra do
|
||||
@doc false
|
||||
@spec render([binary]) :: binary
|
||||
def render(sdoc) do
|
||||
iolist_to_binary sdoc
|
||||
iodata_to_binary sdoc
|
||||
end
|
||||
end
|
||||
|
||||
@@ -28,7 +28,7 @@ defmodule Integer do
|
||||
@doc """
|
||||
Converts a binary to an integer.
|
||||
|
||||
If successful, returns a tuple of the form `{ integer, remainder_of_binary }`.
|
||||
If successful, returns a tuple of the form `{integer, remainder_of_binary}`.
|
||||
Otherwise `:error`.
|
||||
|
||||
## Examples
|
||||
@@ -43,11 +43,11 @@ defmodule Integer do
|
||||
:error
|
||||
|
||||
"""
|
||||
@spec parse(binary) :: { integer, binary } | :error
|
||||
@spec parse(binary) :: {integer, binary} | :error
|
||||
def parse(<< ?-, bin :: binary >>) do
|
||||
case do_parse(bin) do
|
||||
:error -> :error
|
||||
{ number, remainder } -> { -number, remainder }
|
||||
{number, remainder} -> {-number, remainder}
|
||||
end
|
||||
end
|
||||
|
||||
@@ -67,6 +67,6 @@ defmodule Integer do
|
||||
end
|
||||
|
||||
defp do_parse(bitstring, acc) do
|
||||
{ acc, bitstring }
|
||||
{acc, bitstring}
|
||||
end
|
||||
end
|
||||
|
||||
+64
-56
@@ -1,7 +1,7 @@
|
||||
defexception IO.StreamError, [:reason, :message] do
|
||||
def exception(opts) do
|
||||
reason = opts[:reason]
|
||||
formatted = iolist_to_binary(:file.format_error(reason))
|
||||
formatted = iodata_to_binary(:file.format_error(reason))
|
||||
IO.StreamError[message: "error during streaming: #{formatted}", reason: reason]
|
||||
end
|
||||
end
|
||||
@@ -25,13 +25,13 @@ defmodule IO.Stream do
|
||||
stream
|
||||
end
|
||||
|
||||
def into(%{ device: device, raw: raw } = stream) do
|
||||
{ :ok, into(stream, device, raw) }
|
||||
def into(%{device: device, raw: raw} = stream) do
|
||||
{:ok, into(stream, device, raw)}
|
||||
end
|
||||
|
||||
defp into(stream, device, raw) do
|
||||
fn
|
||||
:ok, { :cont, x } ->
|
||||
:ok, {:cont, x} ->
|
||||
case raw do
|
||||
true -> IO.binwrite(device, x)
|
||||
false -> IO.write(device, x)
|
||||
@@ -42,7 +42,7 @@ defmodule IO.Stream do
|
||||
end
|
||||
|
||||
defimpl Enumerable do
|
||||
def reduce(%{ device: device, raw: raw, line_or_bytes: line_or_bytes }, acc, fun) do
|
||||
def reduce(%{device: device, raw: raw, line_or_bytes: line_or_bytes}, acc, fun) do
|
||||
next_fun =
|
||||
case raw do
|
||||
true -> &IO.each_binstream(&1, line_or_bytes)
|
||||
@@ -52,11 +52,11 @@ defmodule IO.Stream do
|
||||
end
|
||||
|
||||
def count(_stream) do
|
||||
{ :error, __MODULE__ }
|
||||
{:error, __MODULE__}
|
||||
end
|
||||
|
||||
def member?(_stream, _term) do
|
||||
{ :error, __MODULE__ }
|
||||
{:error, __MODULE__}
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -70,22 +70,35 @@ defmodule IO do
|
||||
For convenience, Elixir provides `:stdio` and `:stderr` as
|
||||
shortcuts to Erlang's `:standard_io` and `:standard_error`.
|
||||
|
||||
The majority of the functions expect data encoded in UTF-8
|
||||
and will do a conversion to string, via the `String.Chars`
|
||||
protocol (as shown in typespecs).
|
||||
The majority of the functions expect char data, i.e. strings or
|
||||
lists of characters and strings. In case another type is given,
|
||||
it will do a conversion to string via the `String.Chars` protocol
|
||||
(as shown in typespecs).
|
||||
|
||||
The functions starting with `bin*` expects iodata as arguments,
|
||||
i.e. iolists or binaries with no particular encoding.
|
||||
The functions starting with `bin*` expects iodata as argument,
|
||||
i.e. binaries or lists of bytes and binaries.
|
||||
|
||||
## IO devices
|
||||
|
||||
An IO device may be an atom or a pid. In case it is an atom,
|
||||
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`
|
||||
|
||||
* `:stdio` - is a shortcut for `:standard_io`
|
||||
|
||||
* `:stderr` - is a shortcut for `:standard_error`
|
||||
|
||||
"""
|
||||
|
||||
@type device :: atom | pid
|
||||
@type chardata :: char_list | String.Chars.t
|
||||
@type nodata :: { :error, term } | :eof
|
||||
@type device :: atom | pid
|
||||
@type nodata :: {:error, term} | :eof
|
||||
|
||||
import :erlang, only: [group_leader: 0]
|
||||
|
||||
defmacrop is_iolist(data) do
|
||||
defmacrop is_iodata(data) do
|
||||
quote do
|
||||
is_list(unquote(data)) or is_binary(unquote(data))
|
||||
end
|
||||
@@ -103,7 +116,7 @@ defmodule IO do
|
||||
for instance `{:error, :estale}` if reading from an
|
||||
NFS file system.
|
||||
"""
|
||||
@spec read(device, :line | non_neg_integer) :: chardata | nodata
|
||||
@spec read(device, :line | non_neg_integer) :: char_data | nodata
|
||||
def read(device \\ group_leader, chars_or_line)
|
||||
|
||||
def read(device, :line) do
|
||||
@@ -131,24 +144,22 @@ defmodule IO do
|
||||
|
||||
def binread(device, :line) do
|
||||
case :file.read_line(map_dev(device)) do
|
||||
{ :ok, data } -> data
|
||||
{:ok, data} -> data
|
||||
other -> other
|
||||
end
|
||||
end
|
||||
|
||||
def binread(device, count) when count >= 0 do
|
||||
case :file.read(map_dev(device), count) do
|
||||
{ :ok, data } -> data
|
||||
{:ok, data} -> data
|
||||
other -> other
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Writes the given argument to the given device.
|
||||
By default the device is the standard output.
|
||||
The argument is expected to be a chardata (i.e.
|
||||
a char list or an unicode binary).
|
||||
|
||||
By default the device is the standard output.
|
||||
It returns `:ok` if it succeeds.
|
||||
|
||||
## Examples
|
||||
@@ -160,9 +171,9 @@ defmodule IO do
|
||||
#=> "error"
|
||||
|
||||
"""
|
||||
@spec write(device, chardata) :: :ok
|
||||
@spec write(device, char_data | String.Chars.t) :: :ok
|
||||
def write(device \\ group_leader(), item) do
|
||||
:io.put_chars map_dev(device), to_chardata(item)
|
||||
:io.put_chars map_dev(device), to_char_data(item)
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -171,29 +182,32 @@ defmodule IO do
|
||||
|
||||
Check `write/2` for more information.
|
||||
"""
|
||||
@spec binwrite(device, iodata) :: :ok | { :error, term }
|
||||
def binwrite(device \\ group_leader(), item) when is_iolist(item) do
|
||||
@spec binwrite(device, iodata) :: :ok | {:error, term}
|
||||
def binwrite(device \\ group_leader(), item) when is_iodata(item) do
|
||||
:file.write map_dev(device), item
|
||||
end
|
||||
|
||||
@doc """
|
||||
Writes the argument to the device, similar to `write/2`,
|
||||
but adds a newline at the end. The argument is expected
|
||||
to be a chardata.
|
||||
to be a char_data.
|
||||
"""
|
||||
@spec puts(device, chardata) :: :ok
|
||||
@spec puts(device, char_data | String.Chars.t) :: :ok
|
||||
def puts(device \\ group_leader(), item) do
|
||||
erl_dev = map_dev(device)
|
||||
:io.put_chars erl_dev, [to_chardata(item), ?\n]
|
||||
:io.put_chars erl_dev, [to_char_data(item), ?\n]
|
||||
end
|
||||
|
||||
@doc """
|
||||
Inspects and writes the given argument to the device
|
||||
followed by a newline. A set of options can be given.
|
||||
Inspects and writes the given argument to the device.
|
||||
|
||||
It sets by default pretty printing to true and the
|
||||
width to be the width of the device, with a minimum
|
||||
of 80 characters.
|
||||
It sets by default pretty printing to true and returns
|
||||
the item itself.
|
||||
|
||||
Note this function does not use the IO device width
|
||||
because some IO devices does not implement the
|
||||
appropriate functions. Setting the width must be done
|
||||
explicitly by passing the `:width` option.
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -211,14 +225,6 @@ defmodule IO do
|
||||
@spec inspect(device, term, Keyword.t) :: term
|
||||
def inspect(device, item, opts) when is_list(opts) do
|
||||
opts = Keyword.put_new(opts, :pretty, true)
|
||||
|
||||
unless Keyword.get(opts, :width) do
|
||||
opts = case :io.columns(device) do
|
||||
{ :ok, width } -> [width: width] ++ opts
|
||||
{ :error, _ } -> opts
|
||||
end
|
||||
end
|
||||
|
||||
puts device, Kernel.inspect(item, opts)
|
||||
item
|
||||
end
|
||||
@@ -238,8 +244,8 @@ defmodule IO do
|
||||
for instance `{:error, :estale}` if reading from an
|
||||
NFS file system.
|
||||
"""
|
||||
@spec getn(chardata, pos_integer) :: chardata | nodata
|
||||
@spec getn(device, chardata) :: chardata | nodata
|
||||
@spec getn(char_data | String.Chars.t, pos_integer) :: char_data | nodata
|
||||
@spec getn(device, char_data | String.Chars.t) :: char_data | nodata
|
||||
def getn(prompt, count \\ 1)
|
||||
|
||||
def getn(prompt, count) when is_integer(count) do
|
||||
@@ -256,9 +262,9 @@ defmodule IO do
|
||||
the number of unicode codepoints to be retrieved.
|
||||
Otherwise, `count` is the number of raw bytes to be retrieved.
|
||||
"""
|
||||
@spec getn(device, chardata, pos_integer) :: chardata | nodata
|
||||
@spec getn(device, char_data | String.Chars.t, pos_integer) :: char_data | nodata
|
||||
def getn(device, prompt, count) do
|
||||
:io.get_chars(map_dev(device), to_chardata(prompt), count)
|
||||
:io.get_chars(map_dev(device), to_char_data(prompt), count)
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -273,9 +279,9 @@ defmodule IO do
|
||||
for instance `{:error, :estale}` if reading from an
|
||||
NFS file system.
|
||||
"""
|
||||
@spec gets(device, chardata) :: chardata | nodata
|
||||
@spec gets(device, char_data | String.Chars.t) :: char_data | nodata
|
||||
def gets(device \\ group_leader(), prompt) do
|
||||
:io.get_line(map_dev(device), to_chardata(prompt))
|
||||
:io.get_line(map_dev(device), to_char_data(prompt))
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -304,7 +310,7 @@ defmodule IO do
|
||||
"""
|
||||
@spec stream(device, :line | pos_integer) :: Enumerable.t
|
||||
def stream(device, line_or_codepoints) do
|
||||
%IO.Stream{ device: map_dev(device), raw: false, line_or_bytes: line_or_codepoints }
|
||||
%IO.Stream{device: map_dev(device), raw: false, line_or_bytes: line_or_codepoints}
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -322,7 +328,7 @@ defmodule IO do
|
||||
"""
|
||||
@spec binstream(device, :line | pos_integer) :: Enumerable.t
|
||||
def binstream(device, line_or_bytes) do
|
||||
%IO.Stream{ device: map_dev(device), raw: true, line_or_bytes: line_or_bytes }
|
||||
%IO.Stream{device: map_dev(device), raw: true, line_or_bytes: line_or_bytes}
|
||||
end
|
||||
|
||||
@doc false
|
||||
@@ -330,10 +336,10 @@ defmodule IO do
|
||||
case read(device, what) do
|
||||
:eof ->
|
||||
nil
|
||||
{ :error, reason } ->
|
||||
{:error, reason} ->
|
||||
raise IO.StreamError, reason: reason
|
||||
data ->
|
||||
{ data, device }
|
||||
{data, device}
|
||||
end
|
||||
end
|
||||
|
||||
@@ -342,18 +348,20 @@ defmodule IO do
|
||||
case binread(device, what) do
|
||||
:eof ->
|
||||
nil
|
||||
{ :error, reason } ->
|
||||
{:error, reason} ->
|
||||
raise IO.StreamError, reason: reason
|
||||
data ->
|
||||
{ data, device }
|
||||
{data, device}
|
||||
end
|
||||
end
|
||||
|
||||
@compile {:inline, map_dev: 1, to_char_data: 1}
|
||||
|
||||
# Map the Elixir names for standard io and error to Erlang names
|
||||
defp map_dev(:stdio), do: :standard_io
|
||||
defp map_dev(:stderr), do: :standard_error
|
||||
defp map_dev(other) when is_atom(other) or is_pid(other) or is_tuple(other), do: other
|
||||
|
||||
defp to_chardata(list) when is_list(list), do: list
|
||||
defp to_chardata(other), do: to_string(other)
|
||||
defp to_char_data(list) when is_list(list), do: list
|
||||
defp to_char_data(other), do: to_string(other)
|
||||
end
|
||||
|
||||
+45
-40
@@ -7,8 +7,8 @@ defmodule IO.ANSI.Sequence do
|
||||
"\e[#{unquote(code)}#{unquote(terminator)}"
|
||||
end
|
||||
|
||||
defp escape_sequence(<< unquote(atom_to_binary(name)), rest :: binary >>) do
|
||||
{ "\e[#{unquote(code)}#{unquote(terminator)}", rest }
|
||||
defp escape_sequence(unquote(atom_to_list(name))) do
|
||||
unquote(name)()
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -28,12 +28,15 @@ defmodule IO.ANSI do
|
||||
Checks whether the default I/O device is a terminal or a file.
|
||||
|
||||
Used to identify whether printing ANSI escape sequences will likely
|
||||
be displayed as intended.
|
||||
be displayed as intended. This is checked by sending a message to
|
||||
the group leader. In case the group leader does not support the message,
|
||||
it will likely lead to a timeout (and a slow down on execution time).
|
||||
"""
|
||||
@spec terminal? :: boolean
|
||||
@spec terminal?(:io.device) :: boolean
|
||||
def terminal?(device \\ :erlang.group_leader) do
|
||||
match?({:ok, _}, :io.columns(device))
|
||||
!match?({:win32, _}, :os.type()) and
|
||||
match?({:ok, _}, :io.columns(device))
|
||||
end
|
||||
|
||||
@doc "Resets all attributes"
|
||||
@@ -92,7 +95,7 @@ defmodule IO.ANSI do
|
||||
colors = [:black, :red, :green, :yellow, :blue, :magenta, :cyan, :white]
|
||||
colors = Enum.zip(0..(length(colors)-1), colors)
|
||||
|
||||
for { code, color } <- colors do
|
||||
for {code, color} <- colors do
|
||||
@doc "Sets foreground color to #{color}"
|
||||
defsequence color, code + 30
|
||||
|
||||
@@ -127,15 +130,8 @@ defmodule IO.ANSI do
|
||||
@doc "Clear screen"
|
||||
defsequence :clear, "2", "J"
|
||||
|
||||
|
||||
# Catch spaces between codes
|
||||
defp escape_sequence(<< ?\s, rest :: binary >>) do
|
||||
escape_sequence(rest)
|
||||
end
|
||||
|
||||
defp escape_sequence(other) do
|
||||
[spec|_] = String.split(other, ~r/(,|\})/)
|
||||
raise ArgumentError, message: "invalid ANSI sequence specification: #{spec}"
|
||||
raise ArgumentError, message: "invalid ANSI sequence specification: #{other}"
|
||||
end
|
||||
|
||||
@doc ~S"""
|
||||
@@ -160,8 +156,8 @@ defmodule IO.ANSI do
|
||||
"""
|
||||
@spec escape(String.t, emit :: boolean) :: String.t
|
||||
def escape(string, emit \\ terminal?) do
|
||||
{rendered, emitted} = do_escape(string, false, emit, false, [])
|
||||
if emitted and emit do
|
||||
{rendered, emitted} = do_escape(string, emit, false, nil, [])
|
||||
if emitted do
|
||||
rendered <> reset
|
||||
else
|
||||
rendered
|
||||
@@ -190,34 +186,43 @@ defmodule IO.ANSI do
|
||||
"""
|
||||
@spec escape_fragment(String.t, emit :: boolean) :: String.t
|
||||
def escape_fragment(string, emit \\ terminal?) do
|
||||
{rendered, _emitted} = do_escape(string, false, emit, false, [])
|
||||
rendered
|
||||
{escaped, _emitted} = do_escape(string, emit, false, nil, [])
|
||||
escaped
|
||||
end
|
||||
|
||||
defp do_escape(<< ?%, ?{, rest :: binary >>, false, emit, _emitted, acc) do
|
||||
do_escape_sequence(rest, emit, acc)
|
||||
end
|
||||
defp do_escape(<< ?,, rest :: binary >>, true, emit, _emitted, acc) do
|
||||
do_escape_sequence(rest, emit, acc)
|
||||
end
|
||||
defp do_escape(<< ?\s, rest :: binary >>, true, emit, emitted, acc) do
|
||||
do_escape(rest, true, emit, emitted, acc)
|
||||
end
|
||||
defp do_escape(<< ?}, rest :: binary >>, true, emit, emitted, acc) do
|
||||
do_escape(rest, false, emit, emitted, acc)
|
||||
end
|
||||
defp do_escape(<< x :: [binary, size(1)], rest :: binary>>, false, emit, emitted, acc) do
|
||||
do_escape(rest, false, emit, emitted, [x|acc])
|
||||
end
|
||||
defp do_escape("", false, _emit, emitted, acc) do
|
||||
{iolist_to_binary(Enum.reverse(acc)), emitted}
|
||||
end
|
||||
defp do_escape(<<?}, t :: binary>>, emit, emitted, buffer, acc) when is_list(buffer) do
|
||||
sequences =
|
||||
buffer
|
||||
|> Enum.reverse()
|
||||
|> :string.tokens(',')
|
||||
|> Enum.map(&(&1 |> :string.strip |> escape_sequence))
|
||||
|> Enum.reverse()
|
||||
|
||||
defp do_escape_sequence(rest, emit, acc) do
|
||||
{code, rest} = escape_sequence(rest)
|
||||
if emit do
|
||||
acc = [code|acc]
|
||||
if emit and sequences != [] do
|
||||
do_escape(t, emit, true, nil, sequences ++ acc)
|
||||
else
|
||||
do_escape(t, emit, emitted, nil, acc)
|
||||
end
|
||||
do_escape(rest, true, emit, true, acc)
|
||||
end
|
||||
|
||||
defp do_escape(<<h, t :: binary>>, emit, emitted, buffer, acc) when is_list(buffer) do
|
||||
do_escape(t, emit, emitted, [h|buffer], acc)
|
||||
end
|
||||
|
||||
defp do_escape(<<>>, _emit, _emitted, buffer, _acc) when is_list(buffer) do
|
||||
buffer = iodata_to_binary Enum.reverse(buffer)
|
||||
raise ArgumentError, message: "missing } for escape fragment #{buffer}"
|
||||
end
|
||||
|
||||
defp do_escape(<<?%, ?{, t :: binary>>, emit, emitted, nil, acc) do
|
||||
do_escape(t, emit, emitted, [], acc)
|
||||
end
|
||||
|
||||
defp do_escape(<<h, t :: binary>>, emit, emitted, nil, acc) do
|
||||
do_escape(t, emit, emitted, nil, [h|acc])
|
||||
end
|
||||
|
||||
defp do_escape(<<>>, _emit, emitted, nil, acc) do
|
||||
{iodata_to_binary(Enum.reverse(acc)), emitted}
|
||||
end
|
||||
end
|
||||
|
||||
+133
-134
@@ -3,33 +3,10 @@ defmodule IO.ANSI.Docs do
|
||||
|
||||
@bullets [?*, ?-, ?+]
|
||||
|
||||
@default_colors [ enabled: true,
|
||||
doc_code: "cyan,bright",
|
||||
doc_inline_code: "cyan",
|
||||
doc_headings: "yellow,bright",
|
||||
doc_title: "reverse,yellow,bright",
|
||||
doc_bold: "bright",
|
||||
doc_underline: "underline" ]
|
||||
|
||||
@doc """
|
||||
Prints the head of the documentation (i.e. the function signature).
|
||||
The default options used by this module.
|
||||
|
||||
See `print/3` for docs on the supported options.
|
||||
"""
|
||||
def print_heading(heading, colors \\ @default_colors) do
|
||||
IO.puts IO.ANSI.reset
|
||||
width = column_width()
|
||||
padding = div(width + String.length(heading), 2)
|
||||
heading = heading |> String.rjust(padding) |> String.ljust(width)
|
||||
write(:doc_title, heading, colors)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Prints the documentation body.
|
||||
|
||||
In addition to the priting string, takes a truth value for whether to use ANSI
|
||||
escape codes, and a keyword list for the printing color settings. Supported
|
||||
keys for the color settings are:
|
||||
The supported values are:
|
||||
|
||||
* `:enabled` - toggles coloring on and off (true)
|
||||
* `:doc_code` - code blocks (cyan, bright)
|
||||
@@ -38,53 +15,82 @@ defmodule IO.ANSI.Docs do
|
||||
* `: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 attributes.
|
||||
Supported attributes are:
|
||||
|
||||
* Colors: `black red green yellow blue magenta cyan white`
|
||||
* Intensity: `normal bright`
|
||||
* Decoration: `underline reverse`
|
||||
Values for the color settings are strings with
|
||||
comma-separated ANSI values.
|
||||
"""
|
||||
def print(doc, colors \\ @default_colors) do
|
||||
def default_options do
|
||||
[enabled: true,
|
||||
doc_code: "cyan,bright",
|
||||
doc_inline_code: "cyan",
|
||||
doc_headings: "yellow,bright",
|
||||
doc_title: "reverse,yellow,bright",
|
||||
doc_bold: "bright",
|
||||
doc_underline: "underline",
|
||||
width: 80]
|
||||
end
|
||||
|
||||
@doc """
|
||||
Prints the head of the documentation (i.e. the function signature).
|
||||
|
||||
See `default_options/0` for docs on the supported options.
|
||||
"""
|
||||
def print_heading(heading, options \\ []) do
|
||||
IO.puts IO.ANSI.reset
|
||||
options = Keyword.merge(default_options, options)
|
||||
width = options[:width]
|
||||
padding = div(width + String.length(heading), 2)
|
||||
heading = heading |> String.rjust(padding) |> String.ljust(width)
|
||||
write(:doc_title, heading, options)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Prints the documentation body.
|
||||
|
||||
In addition to the priting string, takes a set of options
|
||||
defined in `default_options/1`.
|
||||
"""
|
||||
def print(doc, options \\ []) do
|
||||
options = Keyword.merge(default_options, options)
|
||||
doc
|
||||
|> String.split(["\r\n","\n"], trim: false)
|
||||
|> Enum.map(&String.rstrip/1)
|
||||
|> process("", colors)
|
||||
|> process("", options)
|
||||
end
|
||||
|
||||
defp process([], _indent, _colors), do: nil
|
||||
defp process([], _indent, _options), do: nil
|
||||
|
||||
defp process(["# " <> heading | rest], _indent, colors) do
|
||||
write_h1(String.strip(heading), colors)
|
||||
process(rest, "", colors)
|
||||
defp process(["# " <> heading | rest], _indent, options) do
|
||||
write_h1(String.strip(heading), options)
|
||||
process(rest, "", options)
|
||||
end
|
||||
|
||||
defp process(["## " <> heading | rest], _indent, colors) do
|
||||
write_h2(String.strip(heading), colors)
|
||||
process(rest, "", colors)
|
||||
defp process(["## " <> heading | rest], _indent, options) do
|
||||
write_h2(String.strip(heading), options)
|
||||
process(rest, "", options)
|
||||
end
|
||||
|
||||
defp process(["### " <> heading | rest], indent, colors) do
|
||||
write_h3(String.strip(heading), indent, colors)
|
||||
process(rest, indent, colors)
|
||||
defp process(["### " <> heading | rest], indent, options) do
|
||||
write_h3(String.strip(heading), indent, options)
|
||||
process(rest, indent, options)
|
||||
end
|
||||
|
||||
defp process(["" | rest], indent, colors) do
|
||||
process(rest, indent, colors)
|
||||
defp process(["" | rest], indent, options) do
|
||||
process(rest, indent, options)
|
||||
end
|
||||
|
||||
defp process([" " <> line | rest], indent, colors) do
|
||||
process_code(rest, [line], indent, colors)
|
||||
defp process([" " <> line | rest], indent, options) do
|
||||
process_code(rest, [line], indent, options)
|
||||
end
|
||||
|
||||
defp process([line | rest], indent, colors) do
|
||||
{ stripped, count } = strip_spaces(line, 0)
|
||||
defp process([line | rest], indent, options) do
|
||||
{stripped, count} = strip_spaces(line, 0)
|
||||
case stripped do
|
||||
<<bullet, ?\s, item :: binary >> when bullet in @bullets ->
|
||||
process_list(item, rest, count, indent, colors)
|
||||
process_list(item, rest, count, indent, options)
|
||||
_ ->
|
||||
process_text(rest, [line], indent, false, colors)
|
||||
process_text(rest, [line], indent, false, options)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -93,32 +99,32 @@ defmodule IO.ANSI.Docs do
|
||||
end
|
||||
|
||||
defp strip_spaces(rest, acc) do
|
||||
{ rest, acc }
|
||||
{rest, acc}
|
||||
end
|
||||
|
||||
## Headings
|
||||
|
||||
defp write_h1(heading, colors) do
|
||||
write_h2(String.upcase(heading), colors)
|
||||
defp write_h1(heading, options) do
|
||||
write_h2(String.upcase(heading), options)
|
||||
end
|
||||
|
||||
defp write_h2(heading, colors) do
|
||||
write(:doc_headings, heading, colors)
|
||||
defp write_h2(heading, options) do
|
||||
write(:doc_headings, heading, options)
|
||||
end
|
||||
|
||||
defp write_h3(heading, indent, colors) do
|
||||
defp write_h3(heading, indent, options) do
|
||||
IO.write(indent)
|
||||
write(:doc_headings, heading, colors)
|
||||
write(:doc_headings, heading, options)
|
||||
end
|
||||
|
||||
## Lists
|
||||
|
||||
defp process_list(line, rest, count, indent, colors) do
|
||||
defp process_list(line, rest, count, indent, options) do
|
||||
IO.write indent <> "• "
|
||||
{ contents, rest, done } = process_list_next(rest, count, false, [])
|
||||
process_text(contents, [line], indent <> " ", true, colors)
|
||||
{contents, rest, done} = process_list_next(rest, count, false, [])
|
||||
process_text(contents, [line], indent <> " ", true, options)
|
||||
if done, do: IO.puts(IO.ANSI.reset)
|
||||
process(rest, indent, colors)
|
||||
process(rest, indent, options)
|
||||
end
|
||||
|
||||
# Process the thing after a list item entry. It can be either:
|
||||
@@ -129,13 +135,13 @@ defmodule IO.ANSI.Docs do
|
||||
#
|
||||
defp process_list_next([" " <> _ = line | rest], count, _done, acc) do
|
||||
case list_next(line, count) do
|
||||
:done -> { Enum.reverse(acc), [line|rest], false }
|
||||
:done -> {Enum.reverse(acc), [line|rest], false}
|
||||
chopped -> process_list_next(rest, count, false, [chopped|acc])
|
||||
end
|
||||
end
|
||||
|
||||
defp process_list_next([<<bullet, ?\s, _ :: binary>> | _] = rest, _count, _done, acc) when bullet in @bullets do
|
||||
{ Enum.reverse(acc), rest, false }
|
||||
{Enum.reverse(acc), rest, false}
|
||||
end
|
||||
|
||||
defp process_list_next(["" | rest], count, _done, acc) do
|
||||
@@ -143,7 +149,7 @@ defmodule IO.ANSI.Docs do
|
||||
end
|
||||
|
||||
defp process_list_next(rest, _count, done, acc) do
|
||||
{ Enum.reverse(acc), rest, done }
|
||||
{Enum.reverse(acc), rest, done}
|
||||
end
|
||||
|
||||
defp list_next(<<bullet, ?\s, _ :: binary>>, 0) when bullet in @bullets, do: :done
|
||||
@@ -156,69 +162,69 @@ defmodule IO.ANSI.Docs do
|
||||
|
||||
## Text (paragraphs / lists)
|
||||
|
||||
defp process_text(doc=["" | _], para, indent, from_list, colors) do
|
||||
write_text(Enum.reverse(para), indent, from_list, colors)
|
||||
process(doc, indent, colors)
|
||||
defp process_text(doc=["" | _], para, indent, from_list, options) do
|
||||
write_text(Enum.reverse(para), indent, from_list, options)
|
||||
process(doc, indent, options)
|
||||
end
|
||||
|
||||
defp process_text([], para, indent, from_list, colors) do
|
||||
write_text(Enum.reverse(para), indent, from_list, colors)
|
||||
defp process_text([], para, indent, from_list, options) do
|
||||
write_text(Enum.reverse(para), indent, from_list, options)
|
||||
end
|
||||
|
||||
defp process_text([line | rest], para, indent, true, colors) do
|
||||
{ stripped, count } = strip_spaces(line, 0)
|
||||
defp process_text([line | rest], para, indent, true, options) do
|
||||
{stripped, count} = strip_spaces(line, 0)
|
||||
case stripped do
|
||||
<<bullet, ?\s, item :: binary>> when bullet in @bullets ->
|
||||
write_text(Enum.reverse(para), indent, true, colors)
|
||||
process_list(item, rest, count, indent, colors)
|
||||
write_text(Enum.reverse(para), indent, true, options)
|
||||
process_list(item, rest, count, indent, options)
|
||||
_ ->
|
||||
process_text(rest, [line | para], indent, true, colors)
|
||||
process_text(rest, [line | para], indent, true, options)
|
||||
end
|
||||
end
|
||||
|
||||
defp process_text([line | rest], para, indent, from_list, colors) do
|
||||
process_text(rest, [line | para], indent, from_list, colors)
|
||||
defp process_text([line | rest], para, indent, from_list, options) do
|
||||
process_text(rest, [line | para], indent, from_list, options)
|
||||
end
|
||||
|
||||
defp write_text(lines, indent, from_list, colors) do
|
||||
defp write_text(lines, indent, from_list, options) do
|
||||
lines
|
||||
|> Enum.join(" ")
|
||||
|> handle_links
|
||||
|> handle_inline(nil, [], [], colors)
|
||||
|> handle_inline(nil, [], [], options)
|
||||
|> String.split(~r{\s})
|
||||
|> write_with_wrap(column_width() - size(indent), indent, from_list)
|
||||
|> write_with_wrap(options[:width] - size(indent), indent, from_list)
|
||||
|
||||
unless from_list, do: IO.puts(IO.ANSI.reset)
|
||||
end
|
||||
|
||||
## Code blocks
|
||||
|
||||
defp process_code([], code, indent, colors) do
|
||||
write_code(code, indent, colors)
|
||||
defp process_code([], code, indent, options) do
|
||||
write_code(code, indent, options)
|
||||
end
|
||||
|
||||
# Blank line between code blocks
|
||||
defp process_code([ "", " " <> line | rest ], code, indent, colors) do
|
||||
process_code(rest, [line, "" | code], indent, colors)
|
||||
defp process_code([ "", " " <> line | rest ], code, indent, options) do
|
||||
process_code(rest, [line, "" | code], indent, options)
|
||||
end
|
||||
|
||||
defp process_code([ " " <> line | rest ], code, indent, colors) do
|
||||
process_code(rest, [line|code], indent, colors)
|
||||
defp process_code([ " " <> line | rest ], code, indent, options) do
|
||||
process_code(rest, [line|code], indent, options)
|
||||
end
|
||||
|
||||
defp process_code(rest, code, indent, colors) do
|
||||
write_code(code, indent, colors)
|
||||
process(rest, indent, colors)
|
||||
defp process_code(rest, code, indent, options) do
|
||||
write_code(code, indent, options)
|
||||
process(rest, indent, options)
|
||||
end
|
||||
|
||||
defp write_code(code, indent, colors) do
|
||||
write(:doc_code, "#{indent}┃ #{Enum.join(Enum.reverse(code), "\n#{indent}┃ ")}", colors)
|
||||
defp write_code(code, indent, options) do
|
||||
write(:doc_code, "#{indent}┃ #{Enum.join(Enum.reverse(code), "\n#{indent}┃ ")}", options)
|
||||
end
|
||||
|
||||
## Helpers
|
||||
|
||||
defp write(style, string, colors) do
|
||||
IO.puts color(style, colors) <> string <> IO.ANSI.reset
|
||||
defp write(style, string, options) do
|
||||
IO.puts color(style, options) <> string <> IO.ANSI.reset
|
||||
IO.puts IO.ANSI.reset
|
||||
end
|
||||
|
||||
@@ -227,7 +233,7 @@ defmodule IO.ANSI.Docs do
|
||||
end
|
||||
|
||||
defp write_with_wrap(words, available, indent, first) do
|
||||
{ words, rest } = take_words(words, available, [])
|
||||
{words, rest} = take_words(words, available, [])
|
||||
IO.puts (if first, do: "", else: indent) <> Enum.join(words, " ")
|
||||
write_with_wrap(rest, available, indent, false)
|
||||
end
|
||||
@@ -242,16 +248,16 @@ defmodule IO.ANSI.Docs do
|
||||
|
||||
# No space but we got no words
|
||||
acc == [] ->
|
||||
{ [word], words }
|
||||
{[word], words}
|
||||
|
||||
# Otherwise
|
||||
true ->
|
||||
{ Enum.reverse(acc), [word|words] }
|
||||
{Enum.reverse(acc), [word|words]}
|
||||
end
|
||||
end
|
||||
|
||||
defp take_words([], _available, acc) do
|
||||
{ Enum.reverse(acc), [] }
|
||||
{Enum.reverse(acc), []}
|
||||
end
|
||||
|
||||
defp length_without_escape(<< ?\e, ?[, _, _, ?m, rest :: binary >>, count) do
|
||||
@@ -264,7 +270,7 @@ defmodule IO.ANSI.Docs do
|
||||
|
||||
defp length_without_escape(rest, count) do
|
||||
case String.next_grapheme(rest) do
|
||||
{ _, rest } -> length_without_escape(rest, count + 1)
|
||||
{_, rest} -> length_without_escape(rest, count + 1)
|
||||
nil -> count
|
||||
end
|
||||
end
|
||||
@@ -295,66 +301,66 @@ defmodule IO.ANSI.Docs do
|
||||
@spaced [?_, ?*]
|
||||
|
||||
# Clauses for handling spaces
|
||||
defp handle_inline(<<?*, ?*, ?\s, rest :: binary>>, nil, buffer, acc, colors) do
|
||||
handle_inline(rest, nil, [?\s, ?*, ?*|buffer], acc, colors)
|
||||
defp handle_inline(<<?*, ?*, ?\s, rest :: binary>>, nil, buffer, acc, options) do
|
||||
handle_inline(rest, nil, [?\s, ?*, ?*|buffer], acc, options)
|
||||
end
|
||||
|
||||
defp handle_inline(<<mark, ?\s, rest :: binary>>, nil, buffer, acc, colors) when mark in @spaced do
|
||||
handle_inline(rest, nil, [?\s, mark|buffer], acc, colors)
|
||||
defp handle_inline(<<mark, ?\s, rest :: binary>>, nil, buffer, acc, options) when mark in @spaced do
|
||||
handle_inline(rest, nil, [?\s, mark|buffer], acc, options)
|
||||
end
|
||||
|
||||
defp handle_inline(<<?\s, ?*, ?*, rest :: binary>>, limit, buffer, acc, colors) do
|
||||
handle_inline(rest, limit, [?*, ?*, ?\s|buffer], acc, colors)
|
||||
defp handle_inline(<<?\s, ?*, ?*, rest :: binary>>, limit, buffer, acc, options) do
|
||||
handle_inline(rest, limit, [?*, ?*, ?\s|buffer], acc, options)
|
||||
end
|
||||
|
||||
defp handle_inline(<<?\s, mark, rest :: binary>>, limit, buffer, acc, colors) when mark in @spaced do
|
||||
handle_inline(rest, limit, [mark, ?\s|buffer], acc, colors)
|
||||
defp handle_inline(<<?\s, mark, rest :: binary>>, limit, buffer, acc, options) when mark in @spaced do
|
||||
handle_inline(rest, limit, [mark, ?\s|buffer], acc, options)
|
||||
end
|
||||
|
||||
# Clauses for handling escape
|
||||
defp handle_inline(<<?\\, ?\\, rest :: binary>>, limit, buffer, acc, colors) do
|
||||
handle_inline(rest, limit, [?\\|buffer], acc, colors)
|
||||
defp handle_inline(<<?\\, ?\\, rest :: binary>>, limit, buffer, acc, options) do
|
||||
handle_inline(rest, limit, [?\\|buffer], acc, options)
|
||||
end
|
||||
|
||||
defp handle_inline(<<?\\, ?*, ?*, rest :: binary>>, limit, buffer, acc, colors) do
|
||||
handle_inline(rest, limit, [?*, ?*|buffer], acc, colors)
|
||||
defp handle_inline(<<?\\, ?*, ?*, rest :: binary>>, limit, buffer, acc, options) do
|
||||
handle_inline(rest, limit, [?*, ?*|buffer], acc, options)
|
||||
end
|
||||
|
||||
# A escape is not valid inside `
|
||||
defp handle_inline(<<?\\, mark, rest :: binary>>, limit, buffer, acc, colors)
|
||||
defp handle_inline(<<?\\, mark, rest :: binary>>, limit, buffer, acc, options)
|
||||
when mark in [?_, ?*, ?`] and not(mark == limit and mark == ?`) do
|
||||
handle_inline(rest, limit, [mark|buffer], acc, colors)
|
||||
handle_inline(rest, limit, [mark|buffer], acc, options)
|
||||
end
|
||||
|
||||
# Inline start
|
||||
defp handle_inline(<<?*, ?*, rest :: binary>>, nil, buffer, acc, colors) when rest != "" do
|
||||
handle_inline(rest, ?d, ["**"], [Enum.reverse(buffer)|acc], colors)
|
||||
defp handle_inline(<<?*, ?*, rest :: binary>>, nil, buffer, acc, options) when rest != "" do
|
||||
handle_inline(rest, ?d, ["**"], [Enum.reverse(buffer)|acc], options)
|
||||
end
|
||||
|
||||
defp handle_inline(<<mark, rest :: binary>>, nil, buffer, acc, colors) when rest != "" and mark in @single do
|
||||
handle_inline(rest, mark, [<<mark>>], [Enum.reverse(buffer)|acc], colors)
|
||||
defp handle_inline(<<mark, rest :: binary>>, nil, buffer, acc, options) when rest != "" and mark in @single do
|
||||
handle_inline(rest, mark, [<<mark>>], [Enum.reverse(buffer)|acc], options)
|
||||
end
|
||||
|
||||
# Inline end
|
||||
defp handle_inline(<<?*, ?*, rest :: binary>>, ?d, buffer, acc, colors) do
|
||||
handle_inline(rest, nil, [], [inline_buffer(buffer, colors)|acc], colors)
|
||||
defp handle_inline(<<?*, ?*, rest :: binary>>, ?d, buffer, acc, options) do
|
||||
handle_inline(rest, nil, [], [inline_buffer(buffer, options)|acc], options)
|
||||
end
|
||||
|
||||
defp handle_inline(<<mark, rest :: binary>>, mark, buffer, acc, colors) when mark in @single do
|
||||
handle_inline(rest, nil, [], [inline_buffer(buffer, colors)|acc], colors)
|
||||
defp handle_inline(<<mark, rest :: binary>>, mark, buffer, acc, options) when mark in @single do
|
||||
handle_inline(rest, nil, [], [inline_buffer(buffer, options)|acc], options)
|
||||
end
|
||||
|
||||
defp handle_inline(<<char, rest :: binary>>, mark, buffer, acc, colors) do
|
||||
handle_inline(rest, mark, [char|buffer], acc, colors)
|
||||
defp handle_inline(<<char, rest :: binary>>, mark, buffer, acc, options) do
|
||||
handle_inline(rest, mark, [char|buffer], acc, options)
|
||||
end
|
||||
|
||||
defp handle_inline(<<>>, _mark, buffer, acc, _colors) do
|
||||
iolist_to_binary Enum.reverse([Enum.reverse(buffer)|acc])
|
||||
defp handle_inline(<<>>, _mark, buffer, acc, _options) do
|
||||
iodata_to_binary Enum.reverse([Enum.reverse(buffer)|acc])
|
||||
end
|
||||
|
||||
defp inline_buffer(buffer, colors) do
|
||||
defp inline_buffer(buffer, options) do
|
||||
[h|t] = Enum.reverse([IO.ANSI.reset|buffer])
|
||||
[color_for(h, colors)|t]
|
||||
[color_for(h, options)|t]
|
||||
end
|
||||
|
||||
defp color_for("`", colors), do: color(:doc_inline_code, colors)
|
||||
@@ -367,11 +373,4 @@ defmodule IO.ANSI.Docs do
|
||||
enabled = colors[:enabled]
|
||||
IO.ANSI.escape_fragment("%{#{color}}", enabled)
|
||||
end
|
||||
|
||||
defp column_width() do
|
||||
case :io.columns do
|
||||
{ :ok, width } -> min(width, 80)
|
||||
_ -> 80
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
+257
-219
File diff suppressed because it is too large
Load Diff
@@ -1,22 +1,22 @@
|
||||
defmodule Kernel.CLI do
|
||||
@moduledoc false
|
||||
|
||||
defrecord Config, commands: [], output: ".", compile: [],
|
||||
halt: true, compiler_options: [], errors: [],
|
||||
verbose_compile: false
|
||||
@blank_config %{commands: [], output: ".", compile: [],
|
||||
halt: true, compiler_options: [], errors: [],
|
||||
verbose_compile: false}
|
||||
|
||||
@doc """
|
||||
This is the API invoked by Elixir boot process.
|
||||
"""
|
||||
def main(argv) do
|
||||
argv = for arg <- argv, do: String.from_char_list!(arg)
|
||||
argv = for arg <- argv, do: String.from_char_data!(arg)
|
||||
|
||||
{ config, argv } = process_argv(argv, Kernel.CLI.Config.new)
|
||||
{config, argv} = process_argv(argv, @blank_config)
|
||||
System.argv(argv)
|
||||
|
||||
run fn ->
|
||||
command_results = Enum.map(Enum.reverse(config.commands), &process_command(&1, config))
|
||||
command_errors = for { :error, msg } <- command_results, do: msg
|
||||
command_errors = for {:error, msg} <- command_results, do: msg
|
||||
errors = Enum.reverse(config.errors) ++ command_errors
|
||||
|
||||
if errors != [] do
|
||||
@@ -50,7 +50,7 @@ defmodule Kernel.CLI do
|
||||
System.halt(0)
|
||||
kind, reason ->
|
||||
at_exit(1)
|
||||
print_error(kind, Exception.normalize(kind, reason), System.stacktrace)
|
||||
print_error(kind, reason, System.stacktrace)
|
||||
System.halt(1)
|
||||
end
|
||||
end
|
||||
@@ -65,7 +65,7 @@ defmodule Kernel.CLI do
|
||||
hook.(status)
|
||||
catch
|
||||
kind, reason ->
|
||||
print_error(kind, Exception.normalize(kind, reason), System.stacktrace)
|
||||
print_error(kind, reason, System.stacktrace)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -76,35 +76,25 @@ defmodule Kernel.CLI do
|
||||
|
||||
defp shared_option?(list, config, callback) do
|
||||
case process_shared(list, config) do
|
||||
{ [h|hs], _ } when h == hd(list) ->
|
||||
new_config = config.update_errors &["#{h} : Unknown option" | &1]
|
||||
{[h|hs], _} when h == hd(list) ->
|
||||
new_config = %{config | errors: ["#{h} : Unknown option" | config.errors]}
|
||||
callback.(hs, new_config)
|
||||
{ new_list, new_config } ->
|
||||
{new_list, new_config} ->
|
||||
callback.(new_list, new_config)
|
||||
end
|
||||
end
|
||||
|
||||
defp print_error(:error, exception, trace) do
|
||||
IO.puts :stderr, "** (#{inspect exception.__record__(:name)}) #{exception.message}"
|
||||
IO.puts :stderr, format_stacktrace(trace)
|
||||
end
|
||||
|
||||
defp print_error(kind, reason, trace) do
|
||||
IO.puts :stderr, "** #{kind} #{inspect(reason)}"
|
||||
IO.puts :stderr, format_stacktrace(trace)
|
||||
IO.puts :stderr, Exception.format(kind, reason, prune_stacktrace(trace))
|
||||
end
|
||||
|
||||
defp format_stacktrace(stack) do
|
||||
Exception.format_stacktrace(prune_stacktrace(stack))
|
||||
end
|
||||
@elixir_internals [:elixir_compiler, :elixir_module, :elixir_translator, :elixir_expand]
|
||||
|
||||
@elixir_internals [:elixir_compiler, :elixir_module]
|
||||
|
||||
defp prune_stacktrace([{ mod, _, _, _ }|t]) when mod in @elixir_internals do
|
||||
defp prune_stacktrace([{mod, _, _, _}|t]) when mod in @elixir_internals do
|
||||
prune_stacktrace(t)
|
||||
end
|
||||
|
||||
defp prune_stacktrace([{ __MODULE__, :wrapper, 1, _ }|_]) do
|
||||
defp prune_stacktrace([{__MODULE__, :wrapper, 1, _}|_]) do
|
||||
[]
|
||||
end
|
||||
|
||||
@@ -134,23 +124,23 @@ defmodule Kernel.CLI do
|
||||
end
|
||||
|
||||
defp process_shared(["--app", h|t], config) do
|
||||
process_shared t, config.update_commands &[{:app, h}|&1]
|
||||
process_shared t, %{config | commands: &[{:app, h}|&1]}
|
||||
end
|
||||
|
||||
defp process_shared(["--no-halt"|t], config) do
|
||||
process_shared t, config.halt(false)
|
||||
process_shared t, %{config | halt: false}
|
||||
end
|
||||
|
||||
defp process_shared(["-e", h|t], config) do
|
||||
process_shared t, config.update_commands &[{:eval, h}|&1]
|
||||
process_shared t, %{config | commands: [{:eval, h} | config.commands]}
|
||||
end
|
||||
|
||||
defp process_shared(["-r", h|t], config) do
|
||||
process_shared t, config.update_commands &[{:require, h}|&1]
|
||||
process_shared t, %{config | commands: [{:require, h} | config.commands]}
|
||||
end
|
||||
|
||||
defp process_shared(["-pr", h|t], config) do
|
||||
process_shared t, config.update_commands &[{:parallel_require, h}|&1]
|
||||
process_shared t, %{config | commands: [{:parallel_require, h} | config.commands]}
|
||||
end
|
||||
|
||||
defp process_shared([erl, _|t], config) when erl in ["--erl", "--sname", "--name", "--cookie"] do
|
||||
@@ -162,13 +152,13 @@ defmodule Kernel.CLI do
|
||||
end
|
||||
|
||||
defp process_shared(list, config) do
|
||||
{ list, config }
|
||||
{list, config}
|
||||
end
|
||||
|
||||
# Process init options
|
||||
|
||||
defp process_argv(["--"|t], config) do
|
||||
{ config, t }
|
||||
{config, t}
|
||||
end
|
||||
|
||||
defp process_argv(["+elixirc"|t], config) do
|
||||
@@ -180,7 +170,7 @@ defmodule Kernel.CLI do
|
||||
end
|
||||
|
||||
defp process_argv(["-S", h|t], config) do
|
||||
{ config.update_commands(&[{:script, h}|&1]), t }
|
||||
{%{config | commands: [{:script, h} | config.commands]}, t}
|
||||
end
|
||||
|
||||
defp process_argv([h|t] = list, config) do
|
||||
@@ -188,42 +178,42 @@ defmodule Kernel.CLI do
|
||||
"-" <> _ ->
|
||||
shared_option? list, config, &process_argv(&1, &2)
|
||||
_ ->
|
||||
{ config.update_commands(&[{:file, h}|&1]), t }
|
||||
{%{config | commands: [{:file, h} | config.commands]}, t}
|
||||
end
|
||||
end
|
||||
|
||||
defp process_argv([], config) do
|
||||
{ config, [] }
|
||||
{config, []}
|
||||
end
|
||||
|
||||
# Process compiler options
|
||||
|
||||
defp process_compiler(["--"|t], config) do
|
||||
{ config, t }
|
||||
{config, t}
|
||||
end
|
||||
|
||||
defp process_compiler(["-o", h|t], config) do
|
||||
process_compiler t, config.output(h)
|
||||
process_compiler t, %{config | output: h}
|
||||
end
|
||||
|
||||
defp process_compiler(["--no-docs"|t], config) do
|
||||
process_compiler t, config.update_compiler_options(&[{:docs, false}|&1])
|
||||
process_compiler t, %{config | compiler_options: [{:docs, false} | config.compiler_options]}
|
||||
end
|
||||
|
||||
defp process_compiler(["--no-debug-info"|t], config) do
|
||||
process_compiler t, config.update_compiler_options(&[{:debug_info, false}|&1])
|
||||
process_compiler t, %{config | compiler_options: [{:debug_info, false} | config.compiler_options]}
|
||||
end
|
||||
|
||||
defp process_compiler(["--ignore-module-conflict"|t], config) do
|
||||
process_compiler t, config.update_compiler_options(&[{:ignore_module_conflict, true}|&1])
|
||||
process_compiler t, %{config | compiler_options: [{:ignore_module_conflict, true} | config.compiler_options]}
|
||||
end
|
||||
|
||||
defp process_compiler(["--warnings-as-errors"|t], config) do
|
||||
process_compiler t, config.update_compiler_options(&[{:warnings_as_errors, true}|&1])
|
||||
process_compiler t, %{config | compiler_options: [{:warnings_as_errors, true} | config.compiler_options]}
|
||||
end
|
||||
|
||||
defp process_compiler(["--verbose"|t], config) do
|
||||
process_compiler t, config.verbose_compile(true)
|
||||
process_compiler t, %{config | verbose_compile: true}
|
||||
end
|
||||
|
||||
defp process_compiler([h|t] = list, config) do
|
||||
@@ -232,18 +222,18 @@ defmodule Kernel.CLI do
|
||||
shared_option? list, config, &process_compiler(&1, &2)
|
||||
_ ->
|
||||
pattern = if :filelib.is_dir(h), do: "#{h}/**/*.ex", else: h
|
||||
process_compiler t, config.update_compile &[pattern|&1]
|
||||
process_compiler t, %{config | compile: [pattern | config.compile]}
|
||||
end
|
||||
end
|
||||
|
||||
defp process_compiler([], config) do
|
||||
{ config.update_commands(&[{:compile, config.compile}|&1]), [] }
|
||||
{%{config | commands: [{:compile, config.compile}|config.commands]}, []}
|
||||
end
|
||||
|
||||
# Process iex options
|
||||
|
||||
defp process_iex(["--"|t], config) do
|
||||
{ config, t }
|
||||
{config, t}
|
||||
end
|
||||
|
||||
# This clause is here so that Kernel.CLI does not error out with "unknown
|
||||
@@ -257,7 +247,7 @@ defmodule Kernel.CLI do
|
||||
end
|
||||
|
||||
defp process_iex(["-S", h|t], config) do
|
||||
{ config.update_commands(&[{:script, h}|&1]), t }
|
||||
{%{config | commands: [{:script, h} | config.commands]}, t}
|
||||
end
|
||||
|
||||
defp process_iex([h|t] = list, config) do
|
||||
@@ -265,12 +255,12 @@ defmodule Kernel.CLI do
|
||||
"-" <> _ ->
|
||||
shared_option? list, config, &process_iex(&1, &2)
|
||||
_ ->
|
||||
{ config.update_commands(&[{:file, h}|&1]), t }
|
||||
{%{config | commands: [{:file, h} | config.commands]}, t}
|
||||
end
|
||||
end
|
||||
|
||||
defp process_iex([], config) do
|
||||
{ config, [] }
|
||||
{config, []}
|
||||
end
|
||||
|
||||
# Process commands
|
||||
@@ -279,7 +269,7 @@ defmodule Kernel.CLI do
|
||||
if Node.alive? do
|
||||
wrapper fn -> Node.set_cookie(binary_to_atom(h)) end
|
||||
else
|
||||
{ :error, "--cookie : Cannot set cookie if the node is not alive (set --name or --sname)" }
|
||||
{:error, "--cookie : Cannot set cookie if the node is not alive (set --name or --sname)"}
|
||||
end
|
||||
end
|
||||
|
||||
@@ -288,10 +278,11 @@ defmodule Kernel.CLI do
|
||||
end
|
||||
|
||||
defp process_command({:app, app}, _config) when is_binary(app) do
|
||||
case :application.ensure_all_started(binary_to_atom(app)) do
|
||||
{ :error, reason } ->
|
||||
{ :error, "--app : Could not start application #{app}: #{inspect reason}" }
|
||||
{ :ok, _ } ->
|
||||
case Application.ensure_all_started(binary_to_atom(app)) do
|
||||
{:error, {app, reason}} ->
|
||||
{:error, "--app : Could not start application #{app}: " <>
|
||||
Application.format_error(reason)}
|
||||
{:ok, _} ->
|
||||
:ok
|
||||
end
|
||||
end
|
||||
@@ -300,7 +291,7 @@ defmodule Kernel.CLI do
|
||||
if exec = find_elixir_executable(file) do
|
||||
wrapper fn -> Code.require_file(exec) end
|
||||
else
|
||||
{ :error, "-S : Could not find executable #{file}" }
|
||||
{:error, "-S : Could not find executable #{file}"}
|
||||
end
|
||||
end
|
||||
|
||||
@@ -308,7 +299,7 @@ defmodule Kernel.CLI do
|
||||
if :filelib.is_regular(file) do
|
||||
wrapper fn -> Code.require_file(file) end
|
||||
else
|
||||
{ :error, "No file named #{file}" }
|
||||
{:error, "No file named #{file}"}
|
||||
end
|
||||
end
|
||||
|
||||
@@ -320,7 +311,7 @@ defmodule Kernel.CLI do
|
||||
if files != [] do
|
||||
wrapper fn -> Enum.map files, &Code.require_file(&1) end
|
||||
else
|
||||
{ :error, "-r : No files matched pattern #{pattern}" }
|
||||
{:error, "-r : No files matched pattern #{pattern}"}
|
||||
end
|
||||
end
|
||||
|
||||
@@ -332,7 +323,7 @@ defmodule Kernel.CLI do
|
||||
if files != [] do
|
||||
wrapper fn -> Kernel.ParallelRequire.files(files) end
|
||||
else
|
||||
{ :error, "-pr : No files matched pattern #{pattern}" }
|
||||
{:error, "-pr : No files matched pattern #{pattern}"}
|
||||
end
|
||||
end
|
||||
|
||||
@@ -340,24 +331,24 @@ defmodule Kernel.CLI do
|
||||
:filelib.ensure_dir(:filename.join(config.output, "."))
|
||||
|
||||
case match_regular_files(patterns) do
|
||||
{ :ok, [] } ->
|
||||
{ :error, "No files matched provided patterns" }
|
||||
{ :ok, files } ->
|
||||
{:ok, []} ->
|
||||
{:error, "No files matched provided patterns"}
|
||||
{:ok, files} ->
|
||||
wrapper fn ->
|
||||
Code.compiler_options(config.compiler_options)
|
||||
Kernel.ParallelCompiler.files_to_path(files, config.output,
|
||||
each_file: fn file -> if config.verbose_compile do IO.puts "Compiled #{file}" end end)
|
||||
end
|
||||
{ :missing, missing } ->
|
||||
{ :error, "No files matched pattern(s) #{Enum.join(missing, ",")}" }
|
||||
{:missing, missing} ->
|
||||
{:error, "No files matched pattern(s) #{Enum.join(missing, ",")}"}
|
||||
end
|
||||
end
|
||||
|
||||
defp match_regular_files(patterns) do
|
||||
matched_files = Enum.map patterns, fn(pattern) ->
|
||||
case Path.wildcard(pattern) do
|
||||
[] -> { :missing, pattern }
|
||||
files -> { :ok, files }
|
||||
[] -> {:missing, pattern}
|
||||
files -> {:ok, files}
|
||||
end
|
||||
end
|
||||
|
||||
@@ -372,9 +363,9 @@ defmodule Kernel.CLI do
|
||||
if missing_patterns == [] do
|
||||
files = Enum.uniq(Enum.concat(files))
|
||||
files = Enum.filter files, &:filelib.is_regular(&1)
|
||||
{ :ok, files }
|
||||
{:ok, files}
|
||||
else
|
||||
{ :missing, Enum.uniq(missing_patterns) }
|
||||
{:missing, Enum.uniq(missing_patterns)}
|
||||
end
|
||||
end
|
||||
|
||||
@@ -389,7 +380,7 @@ defmodule Kernel.CLI do
|
||||
# a .bat file that must be in the same directory as
|
||||
# the actual Elixir executable.
|
||||
case :os.type() do
|
||||
{ :win32, _ } ->
|
||||
{:win32, _} ->
|
||||
exec = Path.rootname(exec)
|
||||
if File.regular?(exec), do: exec
|
||||
_ ->
|
||||
|
||||
@@ -25,15 +25,15 @@ defmodule Kernel.ErrorHandler do
|
||||
|
||||
defp ensure_loaded(module) do
|
||||
case Code.ensure_loaded(module) do
|
||||
{ :module, _ } -> []
|
||||
{ :error, _ } ->
|
||||
{:module, _} -> []
|
||||
{:error, _} ->
|
||||
parent = :erlang.get(:elixir_compiler_pid)
|
||||
ref = :erlang.make_ref
|
||||
send parent, { :waiting, module, self(), ref, module }
|
||||
send parent, {:waiting, module, self(), ref, module}
|
||||
:erlang.garbage_collect(self)
|
||||
receive do
|
||||
{ ^ref, :ready } -> :ok
|
||||
{ ^ref, :release } -> release()
|
||||
{^ref, :ready} -> :ok
|
||||
{^ref, :release} -> release()
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -21,7 +21,7 @@ defmodule Kernel.LexicalTracker do
|
||||
# scope may be long gone, so it has no associated PID.
|
||||
if pid = to_pid(arg) do
|
||||
ets = :gen_server.call(pid, :ets, @timeout)
|
||||
:ets.match(ets, { :"$1", :_, :_ }) |> List.flatten
|
||||
:ets.match(ets, {:"$1", :_, :_}) |> List.flatten
|
||||
else
|
||||
[]
|
||||
end
|
||||
@@ -30,7 +30,7 @@ defmodule Kernel.LexicalTracker do
|
||||
defp to_pid(pid) when is_pid(pid), do: pid
|
||||
defp to_pid(mod) when is_atom(mod) do
|
||||
table = :elixir_module.data_table(mod)
|
||||
[{ _, val }] = :ets.lookup(table, :__lexical_tracker)
|
||||
[{_, val}] = :ets.lookup(table, :__lexical_tracker)
|
||||
val
|
||||
end
|
||||
|
||||
@@ -39,7 +39,7 @@ defmodule Kernel.LexicalTracker do
|
||||
# Starts the tracker and returns its pid.
|
||||
@doc false
|
||||
def start_link do
|
||||
{ :ok, pid } = :gen_server.start_link(__MODULE__, [], [])
|
||||
{:ok, pid} = :gen_server.start_link(__MODULE__, [], [])
|
||||
pid
|
||||
end
|
||||
|
||||
@@ -50,27 +50,27 @@ defmodule Kernel.LexicalTracker do
|
||||
|
||||
@doc false
|
||||
def add_import(pid, module, line, warn) do
|
||||
:gen_server.cast(pid, { :add_import, module, line, warn })
|
||||
:gen_server.cast(pid, {:add_import, module, line, warn})
|
||||
end
|
||||
|
||||
@doc false
|
||||
def add_alias(pid, module, line, warn) do
|
||||
:gen_server.cast(pid, { :add_alias, module, line, warn })
|
||||
:gen_server.cast(pid, {:add_alias, module, line, warn})
|
||||
end
|
||||
|
||||
@doc false
|
||||
def remote_dispatch(pid, module) do
|
||||
:gen_server.cast(pid, { :remote_dispatch, module })
|
||||
:gen_server.cast(pid, {:remote_dispatch, module})
|
||||
end
|
||||
|
||||
@doc false
|
||||
def import_dispatch(pid, module) do
|
||||
:gen_server.cast(pid, { :import_dispatch, module })
|
||||
:gen_server.cast(pid, {:import_dispatch, module})
|
||||
end
|
||||
|
||||
@doc false
|
||||
def alias_dispatch(pid, module) do
|
||||
:gen_server.cast(pid, { :alias_dispatch, module })
|
||||
:gen_server.cast(pid, {:alias_dispatch, module})
|
||||
end
|
||||
|
||||
@doc false
|
||||
@@ -86,8 +86,8 @@ defmodule Kernel.LexicalTracker do
|
||||
defp unused(pid, pos) do
|
||||
ets = :gen_server.call(pid, :ets, @timeout)
|
||||
:ets.foldl(fn
|
||||
{ module, _, _ } = tuple, acc when is_integer(:erlang.element(pos, tuple)) ->
|
||||
[{ module, :erlang.element(pos, tuple) }|acc]
|
||||
{module, _, _} = tuple, acc when is_integer(:erlang.element(pos, tuple)) ->
|
||||
[{module, :erlang.element(pos, tuple)}|acc]
|
||||
_, acc ->
|
||||
acc
|
||||
end, [], ets) |> Enum.sort
|
||||
@@ -97,52 +97,52 @@ defmodule Kernel.LexicalTracker do
|
||||
|
||||
|
||||
def init([]) do
|
||||
{ :ok, :ets.new(:lexical, [:protected]) }
|
||||
{:ok, :ets.new(:lexical, [:protected])}
|
||||
end
|
||||
|
||||
def handle_call(:ets, _from, d) do
|
||||
{ :reply, d, d }
|
||||
{:reply, d, d}
|
||||
end
|
||||
|
||||
def handle_call(request, _from, d) do
|
||||
{ :stop, { :bad_call, request }, d }
|
||||
{:stop, {:bad_call, request}, d}
|
||||
end
|
||||
|
||||
def handle_cast({ :remote_dispatch, module }, d) do
|
||||
def handle_cast({:remote_dispatch, module}, d) do
|
||||
add_module(d, module)
|
||||
{ :noreply, d }
|
||||
{:noreply, d}
|
||||
end
|
||||
|
||||
def handle_cast({ :import_dispatch, module }, d) do
|
||||
def handle_cast({:import_dispatch, module}, d) do
|
||||
add_dispatch(d, module, @import)
|
||||
{ :noreply, d }
|
||||
{:noreply, d}
|
||||
end
|
||||
|
||||
def handle_cast({ :alias_dispatch, module }, d) do
|
||||
def handle_cast({:alias_dispatch, module}, d) do
|
||||
add_dispatch(d, module, @alias)
|
||||
{ :noreply, d }
|
||||
{:noreply, d}
|
||||
end
|
||||
|
||||
def handle_cast({ :add_import, module, line, warn }, d) do
|
||||
def handle_cast({:add_import, module, line, warn}, d) do
|
||||
add_directive(d, module, line, warn, @import)
|
||||
{ :noreply, d }
|
||||
{:noreply, d}
|
||||
end
|
||||
|
||||
def handle_cast({ :add_alias, module, line, warn }, d) do
|
||||
def handle_cast({:add_alias, module, line, warn}, d) do
|
||||
add_directive(d, module, line, warn, @alias)
|
||||
{ :noreply, d }
|
||||
{:noreply, d}
|
||||
end
|
||||
|
||||
def handle_cast(:stop, d) do
|
||||
{ :stop, :normal, d }
|
||||
{:stop, :normal, d}
|
||||
end
|
||||
|
||||
def handle_cast(msg, d) do
|
||||
{ :stop, { :bad_cast, msg }, d }
|
||||
{:stop, {:bad_cast, msg}, d}
|
||||
end
|
||||
|
||||
def handle_info(_msg, d) do
|
||||
{ :noreply, d }
|
||||
{:noreply, d}
|
||||
end
|
||||
|
||||
def terminate(_reason, _d) do
|
||||
@@ -150,7 +150,7 @@ defmodule Kernel.LexicalTracker do
|
||||
end
|
||||
|
||||
def code_change(_old, d, _extra) do
|
||||
{ :ok, d }
|
||||
{:ok, d}
|
||||
end
|
||||
|
||||
# Callbacks helpers
|
||||
@@ -160,16 +160,16 @@ defmodule Kernel.LexicalTracker do
|
||||
# If the value is true, it was imported/aliased
|
||||
# If the value is a line, it was imported/aliased and has a pending warning
|
||||
defp add_module(d, module) do
|
||||
:ets.insert_new(d, { module, false, false })
|
||||
:ets.insert_new(d, {module, false, false})
|
||||
end
|
||||
|
||||
defp add_dispatch(d, module, pos) do
|
||||
:ets.update_element(d, module, { pos, true })
|
||||
:ets.update_element(d, module, {pos, true})
|
||||
end
|
||||
|
||||
defp add_directive(d, module, line, warn, pos) do
|
||||
add_module(d, module)
|
||||
marker = if warn, do: line, else: true
|
||||
:ets.update_element(d, module, { pos, marker })
|
||||
:ets.update_element(d, module, {pos, marker})
|
||||
end
|
||||
end
|
||||
|
||||
@@ -43,16 +43,16 @@ defmodule Kernel.ParallelCompiler do
|
||||
defp spawn_compilers(files, path, callbacks) do
|
||||
Code.ensure_loaded(Kernel.ErrorHandler)
|
||||
compiler_pid = self()
|
||||
:elixir_code_server.cast({ :reset_warnings, compiler_pid })
|
||||
:elixir_code_server.cast({:reset_warnings, compiler_pid})
|
||||
schedulers = max(:erlang.system_info(:schedulers_online), 2)
|
||||
|
||||
result = spawn_compilers(files, files, path, callbacks, [], [], schedulers, [])
|
||||
|
||||
# In case --warning-as-errors is enabled and there was a warning,
|
||||
# compilation status will be set to error and we fail with CompileError
|
||||
case :elixir_code_server.call({ :compilation_status, compiler_pid }) do
|
||||
case :elixir_code_server.call({:compilation_status, compiler_pid}) do
|
||||
:ok -> result
|
||||
:error -> raise CompileError, [], []
|
||||
:error -> exit(1)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -64,8 +64,8 @@ defmodule Kernel.ParallelCompiler do
|
||||
|
||||
# Release waiting processes
|
||||
defp spawn_compilers([h|t], original, output, callbacks, waiting, queued, schedulers, result) when is_pid(h) do
|
||||
{ _kind, ^h, ref, _module } = List.keyfind(waiting, h, 1)
|
||||
send h, { ref, :ready }
|
||||
{_kind, ^h, ref, _module} = List.keyfind(waiting, h, 1)
|
||||
send h, {ref, :ready}
|
||||
waiting = List.keydelete(waiting, h, 1)
|
||||
spawn_compilers(t, original, output, callbacks, waiting, queued, schedulers, result)
|
||||
end
|
||||
@@ -74,7 +74,7 @@ defmodule Kernel.ParallelCompiler do
|
||||
defp spawn_compilers([h|t], original, output, callbacks, waiting, queued, schedulers, result) do
|
||||
parent = self()
|
||||
|
||||
{ pid, ref } =
|
||||
{pid, ref} =
|
||||
:erlang.spawn_monitor fn ->
|
||||
# Notify Code.ensure_compiled/2 that we should
|
||||
# attempt to compile the module by doing a dispatch.
|
||||
@@ -90,27 +90,27 @@ defmodule Kernel.ParallelCompiler do
|
||||
else
|
||||
:elixir_compiler.file(h)
|
||||
end
|
||||
{ :compiled, h }
|
||||
{:compiled, h}
|
||||
catch
|
||||
kind, reason ->
|
||||
{ :failure, kind, reason, System.stacktrace }
|
||||
{:failure, kind, reason, System.stacktrace}
|
||||
end)
|
||||
end
|
||||
|
||||
spawn_compilers(t, original, output, callbacks, waiting,
|
||||
[{ pid, ref, h }|queued], schedulers, result)
|
||||
[{pid, ref, h}|queued], schedulers, result)
|
||||
end
|
||||
|
||||
# No more files, nothing waiting, queue is empty, we are done
|
||||
defp spawn_compilers([], _original, _output, _callbacks, [], [], _schedulers, result) do
|
||||
for { :module, mod } <- result, do: mod
|
||||
for {:module, mod} <- result, do: mod
|
||||
end
|
||||
|
||||
# Queued x, waiting for x: POSSIBLE ERROR! Release processes so we get the failures
|
||||
defp spawn_compilers([], original, output, callbacks, waiting, queued, schedulers, result) when length(waiting) == length(queued) do
|
||||
Enum.each queued, fn { child, _, _ } ->
|
||||
{ _kind, ^child, ref, _module } = List.keyfind(waiting, child, 1)
|
||||
send child, { ref, :release }
|
||||
Enum.each queued, fn {child, _, _} ->
|
||||
{_kind, ^child, ref, _module} = List.keyfind(waiting, child, 1)
|
||||
send child, {ref, :release}
|
||||
end
|
||||
wait_for_messages([], original, output, callbacks, waiting, queued, schedulers, result)
|
||||
end
|
||||
@@ -123,44 +123,44 @@ defmodule Kernel.ParallelCompiler do
|
||||
# Wait for messages from child processes
|
||||
defp wait_for_messages(entries, original, output, callbacks, waiting, queued, schedulers, result) do
|
||||
receive do
|
||||
{ :struct_available, module } ->
|
||||
available = for { :struct, pid, _, waiting_module } <- waiting,
|
||||
{:struct_available, module} ->
|
||||
available = for {:struct, pid, _, waiting_module} <- waiting,
|
||||
module == waiting_module,
|
||||
not pid in entries,
|
||||
do: pid
|
||||
|
||||
spawn_compilers(available ++ entries, original, output, callbacks,
|
||||
waiting, queued, schedulers, [{ :struct, module }|result])
|
||||
waiting, queued, schedulers, [{:struct, module}|result])
|
||||
|
||||
{ :module_available, child, ref, file, module, binary } ->
|
||||
{:module_available, child, ref, file, module, binary} ->
|
||||
if callback = Keyword.get(callbacks, :each_module) do
|
||||
callback.(file, module, binary)
|
||||
end
|
||||
|
||||
# Release the module loader which is waiting for an ack
|
||||
send child, { ref, :ack }
|
||||
send child, {ref, :ack}
|
||||
|
||||
available = for { _kind, pid, _, waiting_module } <- waiting,
|
||||
available = for {_kind, pid, _, waiting_module} <- waiting,
|
||||
module == waiting_module,
|
||||
not pid in entries,
|
||||
do: pid
|
||||
|
||||
spawn_compilers(available ++ entries, original, output, callbacks,
|
||||
waiting, queued, schedulers, [{ :module, module }|result])
|
||||
waiting, queued, schedulers, [{:module, module}|result])
|
||||
|
||||
{ :waiting, kind, child, ref, on } ->
|
||||
defined = fn { k, m } -> on == m and k in [kind, :module] end
|
||||
{:waiting, kind, child, ref, on} ->
|
||||
defined = fn {k, m} -> on == m and k in [kind, :module] end
|
||||
|
||||
# Oops, we already got it, do not put it on waiting.
|
||||
if :lists.any(defined, result) do
|
||||
send child, { ref, :ready }
|
||||
send child, {ref, :ready}
|
||||
else
|
||||
waiting = [{ kind, child, ref, on }|waiting]
|
||||
waiting = [{kind, child, ref, on}|waiting]
|
||||
end
|
||||
|
||||
spawn_compilers(entries, original, output, callbacks, waiting, queued, schedulers, result)
|
||||
|
||||
{ :DOWN, _down_ref, :process, down_pid, { :compiled, file } } ->
|
||||
{:DOWN, _down_ref, :process, down_pid, {:compiled, file}} ->
|
||||
if callback = Keyword.get(callbacks, :each_file) do
|
||||
callback.(file)
|
||||
end
|
||||
@@ -172,45 +172,76 @@ defmodule Kernel.ParallelCompiler do
|
||||
new_waiting = List.keydelete(waiting, down_pid, 1)
|
||||
spawn_compilers(new_entries, original, output, callbacks, new_waiting, new_queued, schedulers, result)
|
||||
|
||||
{ :DOWN, down_ref, :process, _down_pid, { :failure, kind, reason, stacktrace } } ->
|
||||
handle_failure(down_ref, kind, reason, stacktrace, entries, waiting, queued)
|
||||
wait_for_messages(entries, original, output, callbacks, waiting, queued, schedulers, result)
|
||||
|
||||
{ :DOWN, down_ref, :process, _down_pid, other } ->
|
||||
handle_failure(down_ref, :exit, other, [], entries, waiting, queued)
|
||||
{:DOWN, down_ref, :process, _down_pid, reason} ->
|
||||
handle_failure(down_ref, reason, entries, waiting, queued)
|
||||
wait_for_messages(entries, original, output, callbacks, waiting, queued, schedulers, result)
|
||||
end
|
||||
end
|
||||
|
||||
defp handle_failure(ref, kind, reason, stacktrace, entries, waiting, queued) do
|
||||
defp handle_failure(ref, reason, entries, waiting, queued) do
|
||||
if file = find_failure(ref, queued) do
|
||||
print_failure(file, reason)
|
||||
if all_missing?(entries, waiting, queued) do
|
||||
collect_failures(queued, length(queued) - 1)
|
||||
end
|
||||
exit(1)
|
||||
end
|
||||
end
|
||||
|
||||
defp find_failure(ref, queued) do
|
||||
case List.keyfind(queued, ref, 1) do
|
||||
{ child, ^ref, file } ->
|
||||
if many_missing?(child, entries, waiting, queued) do
|
||||
IO.puts "== Compilation failed =="
|
||||
IO.puts "Compilation failed on the following files:\n"
|
||||
|
||||
Enum.each Enum.reverse(queued), fn { pid, _ref, file } ->
|
||||
case List.keyfind(waiting, pid, 1) do
|
||||
{ _, ^pid, _, mod } -> IO.puts "* #{file} is missing module #{inspect mod}"
|
||||
_ -> :ok
|
||||
end
|
||||
end
|
||||
|
||||
IO.puts "\nOne of the failures is shown below..."
|
||||
end
|
||||
|
||||
IO.puts "== Compilation error on file #{file} =="
|
||||
:erlang.raise(kind, reason, stacktrace)
|
||||
_ ->
|
||||
:ok
|
||||
{_child, ^ref, file} -> file
|
||||
_ -> nil
|
||||
end
|
||||
end
|
||||
|
||||
defp many_missing?(child, entries, waiting, queued) do
|
||||
waiting_length = length(waiting)
|
||||
defp print_failure(_file, {:compiled, _}) do
|
||||
:ok
|
||||
end
|
||||
|
||||
match?({ _, ^child, _, _ }, List.keyfind(waiting, child, 1)) and
|
||||
waiting_length > 1 and entries == [] and
|
||||
waiting_length == length(queued)
|
||||
defp print_failure(file, {:failure, kind, reason, stacktrace}) do
|
||||
IO.puts "\n== Compilation error on file #{Path.relative_to_cwd(file)} =="
|
||||
IO.puts Exception.format(kind, reason, prune_stacktrace(stacktrace))
|
||||
end
|
||||
|
||||
defp print_failure(file, reason) do
|
||||
IO.puts "\n== Compilation error on file #{Path.relative_to_cwd(file)} =="
|
||||
IO.puts Exception.format(:exit, reason, [])
|
||||
end
|
||||
|
||||
@elixir_internals [:elixir_compiler, :elixir_module, :elixir_translator, :elixir_expand]
|
||||
|
||||
defp prune_stacktrace([{mod, _, _, _}|t]) when mod in @elixir_internals do
|
||||
prune_stacktrace(t)
|
||||
end
|
||||
|
||||
defp prune_stacktrace([h|t]) do
|
||||
[h|prune_stacktrace(t)]
|
||||
end
|
||||
|
||||
defp prune_stacktrace([]) do
|
||||
[]
|
||||
end
|
||||
|
||||
defp all_missing?(entries, waiting, queued) do
|
||||
entries == [] and waiting != [] and
|
||||
length(waiting) == length(queued)
|
||||
end
|
||||
|
||||
defp collect_failures(_queued, 0), do: :ok
|
||||
|
||||
defp collect_failures(queued, remaining) do
|
||||
receive do
|
||||
{:DOWN, down_ref, :process, _down_pid, reason} ->
|
||||
if file = find_failure(down_ref, queued) do
|
||||
print_failure(file, reason)
|
||||
collect_failures(queued, remaining - 1)
|
||||
else
|
||||
collect_failures(queued, remaining)
|
||||
end
|
||||
after
|
||||
# Give up if no failure appears in 5 seconds
|
||||
5000 -> :ok
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -33,9 +33,9 @@ defmodule Kernel.ParallelRequire do
|
||||
|
||||
compiler_pid = :erlang.get(:elixir_compiler_pid)
|
||||
ensure_compiled = :erlang.get(:elixir_ensure_compiled)
|
||||
{ :error_handler, handler } = :erlang.process_info(parent, :error_handler)
|
||||
{:error_handler, handler} = :erlang.process_info(parent, :error_handler)
|
||||
|
||||
{ pid, ref } = :erlang.spawn_monitor fn ->
|
||||
{pid, ref} = :erlang.spawn_monitor fn ->
|
||||
if compiler_pid != :undefined do
|
||||
:erlang.put(:elixir_compiler_pid, compiler_pid)
|
||||
end
|
||||
@@ -48,27 +48,27 @@ defmodule Kernel.ParallelRequire do
|
||||
|
||||
exit(try do
|
||||
new = Code.require_file(h) || []
|
||||
{ :required, Enum.map(new, &elem(&1, 0)), h }
|
||||
{:required, Enum.map(new, &elem(&1, 0)), h}
|
||||
catch
|
||||
kind, reason ->
|
||||
{ :failure, kind, reason, System.stacktrace }
|
||||
{:failure, kind, reason, System.stacktrace}
|
||||
end)
|
||||
end
|
||||
|
||||
spawn_requires(t, [{ pid, ref }|waiting], callback, schedulers, result)
|
||||
spawn_requires(t, [{pid, ref}|waiting], callback, schedulers, result)
|
||||
end
|
||||
|
||||
defp wait_for_messages(files, waiting, callback, schedulers, result) do
|
||||
receive do
|
||||
{ :DOWN, ref, :process, pid, status } ->
|
||||
tuple = { pid, ref }
|
||||
{:DOWN, ref, :process, pid, status} ->
|
||||
tuple = {pid, ref}
|
||||
if tuple in waiting do
|
||||
case status do
|
||||
{ :required, mods, file } ->
|
||||
{:required, mods, file} ->
|
||||
callback.(file)
|
||||
result = mods ++ result
|
||||
waiting = List.delete(waiting, tuple)
|
||||
{ :failure, kind, reason, stacktrace } ->
|
||||
{:failure, kind, reason, stacktrace} ->
|
||||
:erlang.raise(kind, reason, stacktrace)
|
||||
other ->
|
||||
:erlang.raise(:exit, other, [])
|
||||
|
||||
@@ -33,11 +33,11 @@ defmodule Kernel.SpecialForms do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> { 1, 2, 3 }
|
||||
{ 1, 2, 3 }
|
||||
iex> {1, 2, 3}
|
||||
{1, 2, 3}
|
||||
|
||||
iex> quote do: { 1, 2, 3 }
|
||||
{ :{}, [], [1,2,3] }
|
||||
iex> quote do: {1, 2, 3}
|
||||
{:{}, [], [1,2,3]}
|
||||
|
||||
"""
|
||||
defmacro unquote(:{})(args)
|
||||
@@ -49,19 +49,19 @@ defmodule Kernel.SpecialForms do
|
||||
using the match operator (`===`). Maps can be created with
|
||||
the `%{}` special form where keys are associated via `=>`:
|
||||
|
||||
%{ 1 => 2 }
|
||||
%{1 => 2}
|
||||
|
||||
Maps also support the keyword notation, as other special forms,
|
||||
as long as they are at the end of the argument list:
|
||||
|
||||
%{ hello: :world, with: :keywords }
|
||||
%{ :hello => :world, with: :keywords }
|
||||
%{hello: :world, with: :keywords}
|
||||
%{:hello => :world, with: :keywords}
|
||||
|
||||
If a map has duplicated keys, the last key will always have
|
||||
higher precedence:
|
||||
|
||||
iex> %{ a: :b, a: :c }
|
||||
%{ a: :c }
|
||||
iex> %{a: :b, a: :c}
|
||||
%{a: :c}
|
||||
|
||||
Conveniences for manipulating maps can be found in the
|
||||
`Map` module.
|
||||
@@ -72,7 +72,7 @@ defmodule Kernel.SpecialForms do
|
||||
like `Map.get/3` and `Map.fetch/2`, a map can be accessed using the
|
||||
`.` operator:
|
||||
|
||||
iex> map = %{ a: :b }
|
||||
iex> map = %{a: :b}
|
||||
iex> map.a
|
||||
:b
|
||||
|
||||
@@ -83,9 +83,9 @@ defmodule Kernel.SpecialForms do
|
||||
|
||||
Maps also support an update syntax:
|
||||
|
||||
iex> map = %{ :a => :b }
|
||||
iex> %{ map | :a => :c }
|
||||
%{ :a => :c }
|
||||
iex> map = %{:a => :b}
|
||||
iex> %{map | :a => :c}
|
||||
%{:a => :c}
|
||||
|
||||
Notice the update syntax requires the given keys to exist.
|
||||
Trying to update a key that does not exist will raise an `ArgumentError`.
|
||||
@@ -96,8 +96,8 @@ defmodule Kernel.SpecialForms do
|
||||
always represented internally as a list of two-items tuples
|
||||
for simplicity:
|
||||
|
||||
iex> quote do: %{ :a => :b, c: :d }
|
||||
{ :%{}, [], [{:a, :b}, {:c, :d}] }
|
||||
iex> quote do: %{:a => :b, c: :d}
|
||||
{:%{}, [], [{:a, :b}, {:c, :d}]}
|
||||
|
||||
"""
|
||||
defmacro unquote(:%{})(args)
|
||||
@@ -114,7 +114,7 @@ defmodule Kernel.SpecialForms do
|
||||
|
||||
defmodule User do
|
||||
def __struct__ do
|
||||
%{ name: "josé", age: 27 }
|
||||
%{name: "josé", age: 27}
|
||||
end
|
||||
end
|
||||
|
||||
@@ -125,13 +125,13 @@ defmodule Kernel.SpecialForms do
|
||||
Underneath, a struct is just a map with a `__struct__` field
|
||||
pointing to the User module:
|
||||
|
||||
%User{} == %{ __struct__: User, name: "josé", age: 27 }
|
||||
%User{} == %{__struct__: User, name: "josé", age: 27}
|
||||
|
||||
A struct also validates the given keys are part of the defined
|
||||
struct. The example below will fail because there is no key
|
||||
`:full_name` in the user struct:
|
||||
|
||||
%User{ full_name: "José Valim" }
|
||||
%User{full_name: "José Valim"}
|
||||
|
||||
Note that a struct specifies a minimum set of keys required
|
||||
for operations. Other keys can be added to structs via the
|
||||
@@ -142,7 +142,7 @@ defmodule Kernel.SpecialForms do
|
||||
|
||||
An update operation specific for structs is also available:
|
||||
|
||||
%User{ user | age: 28 }
|
||||
%User{user | age: 28}
|
||||
|
||||
The syntax above will guarantee the given keys are valid at
|
||||
compilation time and it will guarantee at runtime the given
|
||||
@@ -627,7 +627,7 @@ defmodule Kernel.SpecialForms do
|
||||
following example will match:
|
||||
|
||||
iex> x = 0
|
||||
iex> { x, ^x } = { 1, 0 }
|
||||
iex> {x, ^x} = {1, 0}
|
||||
iex> x
|
||||
1
|
||||
|
||||
@@ -640,7 +640,7 @@ defmodule Kernel.SpecialForms do
|
||||
## Examples
|
||||
|
||||
quote do: sum(1, 2, 3)
|
||||
#=> { :sum, [], [1, 2, 3] }
|
||||
#=> {:sum, [], [1, 2, 3]}
|
||||
|
||||
## Explanation
|
||||
|
||||
@@ -648,7 +648,7 @@ defmodule Kernel.SpecialForms do
|
||||
The building block of Elixir macros is a tuple with three elements,
|
||||
for example:
|
||||
|
||||
{ :sum, [], [1, 2, 3] }
|
||||
{:sum, [], [1, 2, 3]}
|
||||
|
||||
The tuple above represents a function call to `sum` passing 1, 2 and
|
||||
3 as arguments. The tuple elements are:
|
||||
@@ -1025,7 +1025,7 @@ defmodule Kernel.SpecialForms do
|
||||
functions on the fly. Consider this example:
|
||||
|
||||
kv = [foo: 1, bar: 2]
|
||||
Enum.each kv, fn { k, v } ->
|
||||
Enum.each kv, fn {k, v} ->
|
||||
def unquote(k)(), do: unquote(v)
|
||||
end
|
||||
|
||||
@@ -1034,7 +1034,7 @@ defmodule Kernel.SpecialForms do
|
||||
functionality into a macro:
|
||||
|
||||
defmacro defkv(kv) do
|
||||
Enum.map kv, fn { k, v } ->
|
||||
Enum.map kv, fn {k, v} ->
|
||||
quote do
|
||||
def unquote(k)(), do: unquote(v)
|
||||
end
|
||||
@@ -1061,7 +1061,7 @@ defmodule Kernel.SpecialForms do
|
||||
|
||||
defmacro defkv(kv) do
|
||||
quote do
|
||||
Enum.each unquote(kv), fn { k, v } ->
|
||||
Enum.each unquote(kv), fn {k, v} ->
|
||||
def unquote(k)(), do: unquote(v)
|
||||
end
|
||||
end
|
||||
@@ -1082,7 +1082,7 @@ defmodule Kernel.SpecialForms do
|
||||
|
||||
defmacro defkv(kv) do
|
||||
quote bind_quoted: [kv: kv] do
|
||||
Enum.each kv, fn { k, v } ->
|
||||
Enum.each kv, fn {k, v} ->
|
||||
def unquote(k)(), do: unquote(v)
|
||||
end
|
||||
end
|
||||
@@ -1107,13 +1107,13 @@ defmodule Kernel.SpecialForms do
|
||||
|
||||
Which would then return:
|
||||
|
||||
{ :sum, [], [1, { :value, [], quoted }, 3] }
|
||||
{:sum, [], [1, {:value, [], quoted}, 3]}
|
||||
|
||||
Which is not the expected result. For this, we use unquote:
|
||||
|
||||
value = 13
|
||||
quote do: sum(1, unquote(value), 3)
|
||||
#=> { :sum, [], [1, 13, 3] }
|
||||
#=> {:sum, [], [1, 13, 3]}
|
||||
|
||||
"""
|
||||
defmacro unquote(:unquote)(expr)
|
||||
@@ -1126,7 +1126,7 @@ defmodule Kernel.SpecialForms do
|
||||
|
||||
values = [2, 3, 4]
|
||||
quote do: sum(1, unquote_splicing(values), 5)
|
||||
#=> { :sum, [], [1, 2, 3, 4, 5] }
|
||||
#=> {:sum, [], [1, 2, 3, 4, 5]}
|
||||
|
||||
"""
|
||||
defmacro unquote(:unquote_splicing)(expr)
|
||||
@@ -1157,6 +1157,14 @@ defmodule Kernel.SpecialForms do
|
||||
iex> for n <- [1, 2, 3, 4, 5, 6], rem(n, 2) == 0, do: n
|
||||
[2, 4, 6]
|
||||
|
||||
Note generators can also be used to filter as it removes any value
|
||||
that doesn't match the left side of `<-`:
|
||||
|
||||
iex> for {:user, name} <- [user: "jose", admin: "john", user: "eric"] do
|
||||
...> String.upcase(name)
|
||||
...> end
|
||||
["JOSE", "ERIC"]
|
||||
|
||||
Bitstring generators are also supported and are very useful when you
|
||||
need to organize bitstring streams:
|
||||
|
||||
@@ -1211,7 +1219,7 @@ defmodule Kernel.SpecialForms do
|
||||
and should not be invoked directly:
|
||||
|
||||
iex> quote do: (1; 2; 3)
|
||||
{ :__block__, [], [1, 2, 3] }
|
||||
{:__block__, [], [1, 2, 3]}
|
||||
|
||||
"""
|
||||
defmacro __block__(args)
|
||||
@@ -1241,7 +1249,7 @@ defmodule Kernel.SpecialForms do
|
||||
|
||||
## Anonymous functions
|
||||
|
||||
The capture operator can be also be used to partially apply
|
||||
The capture operator can also be used to partially apply
|
||||
functions, where `&1`, `&2` and so on can be used as value
|
||||
placeholders. For example:
|
||||
|
||||
@@ -1266,7 +1274,7 @@ defmodule Kernel.SpecialForms do
|
||||
|
||||
iex> fun = &{&1, &2}
|
||||
iex> fun.(1, 2)
|
||||
{ 1, 2 }
|
||||
{1, 2}
|
||||
|
||||
iex> fun = &[&1|&2]
|
||||
iex> fun.(1, 2)
|
||||
@@ -1336,7 +1344,7 @@ defmodule Kernel.SpecialForms do
|
||||
## Examples
|
||||
|
||||
case thing do
|
||||
{ :selector, i, value } when is_integer(i) ->
|
||||
{:selector, i, value} when is_integer(i) ->
|
||||
value
|
||||
value ->
|
||||
value
|
||||
@@ -1352,7 +1360,7 @@ defmodule Kernel.SpecialForms do
|
||||
outer context:
|
||||
|
||||
case data do
|
||||
{ :ok, value } -> value
|
||||
{:ok, value} -> value
|
||||
:error -> nil
|
||||
end
|
||||
|
||||
@@ -1608,7 +1616,7 @@ defmodule Kernel.SpecialForms do
|
||||
## Examples
|
||||
|
||||
receive do
|
||||
{ :selector, i, value } when is_integer(i) ->
|
||||
{:selector, i, value} when is_integer(i) ->
|
||||
value
|
||||
value when is_atom(value) ->
|
||||
value
|
||||
@@ -1620,7 +1628,7 @@ defmodule Kernel.SpecialForms do
|
||||
received after the specified period of time:
|
||||
|
||||
receive do
|
||||
{ :selector, i, value } when is_integer(i) ->
|
||||
{:selector, i, value} when is_integer(i) ->
|
||||
value
|
||||
value when is_atom(value) ->
|
||||
value
|
||||
|
||||
+151
-143
@@ -69,7 +69,7 @@ defmodule Kernel.Typespec do
|
||||
|
||||
Tuple :: tuple # a tuple of any size
|
||||
| {} # empty tuple
|
||||
| { TList }
|
||||
| {TList}
|
||||
|
||||
TList :: Type
|
||||
| Type, TList
|
||||
@@ -119,7 +119,7 @@ defmodule Kernel.Typespec do
|
||||
`iodata` | `iolist` | `binary`
|
||||
`iolist` | `maybe_improper_list(byte` | `binary` | `iolist, binary` | `[])`
|
||||
`module` | `atom`
|
||||
`mfa` | `{ atom, atom, arity }`
|
||||
`mfa` | `{atom, atom, arity}`
|
||||
`arity` | `0..255`
|
||||
`node` | `atom`
|
||||
`timeout` | `:infinity` | `non_neg_integer`
|
||||
@@ -151,7 +151,7 @@ defmodule Kernel.Typespec do
|
||||
Types can be parametrised by defining variables as parameters, these variables
|
||||
can then be used to define the type.
|
||||
|
||||
@type dict(key, value) :: [{ key, value }]
|
||||
@type dict(key, value) :: [{key, value}]
|
||||
|
||||
Types can also be defined for records, see `defrecord/3`.
|
||||
|
||||
@@ -269,12 +269,12 @@ defmodule Kernel.Typespec do
|
||||
@doc """
|
||||
Defines a `type`, `typep` or `opaque` by receiving Erlang's typespec.
|
||||
"""
|
||||
def define_type(caller, kind, { name, _, vars } = type) when kind in [:type, :typep, :opaque] do
|
||||
{ kind, export } =
|
||||
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 }
|
||||
:type -> {:type, true}
|
||||
:typep -> {:type, false}
|
||||
:opaque -> {:opaque, true}
|
||||
end
|
||||
|
||||
module = caller.module
|
||||
@@ -283,7 +283,7 @@ defmodule Kernel.Typespec do
|
||||
Module.compile_typespec module, kind, type
|
||||
|
||||
if export do
|
||||
Module.compile_typespec(module, :export_type, [{ name, arity }])
|
||||
Module.compile_typespec(module, :export_type, [{name, arity}])
|
||||
end
|
||||
|
||||
define_doc(caller, kind, name, arity, export)
|
||||
@@ -296,7 +296,7 @@ defmodule Kernel.Typespec do
|
||||
|
||||
if doc do
|
||||
if export do
|
||||
Module.add_doc(module, caller.line, kind, { name, arity }, doc)
|
||||
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"
|
||||
@@ -310,14 +310,14 @@ defmodule Kernel.Typespec do
|
||||
Defines a `spec` by receiving Erlang's typespec.
|
||||
"""
|
||||
def define_spec(module, tuple, definition) do
|
||||
Module.compile_typespec module, :spec, { tuple, definition }
|
||||
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 }
|
||||
Module.compile_typespec module, :callback, {tuple, definition}
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -326,7 +326,7 @@ defmodule Kernel.Typespec do
|
||||
for modules being compiled.
|
||||
"""
|
||||
def defines_type?(module, name, arity) do
|
||||
finder = &match?({ ^name, _, vars } when length(vars) == arity, &1)
|
||||
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)
|
||||
end
|
||||
@@ -336,7 +336,7 @@ defmodule Kernel.Typespec do
|
||||
This function is only available for modules being compiled.
|
||||
"""
|
||||
def defines_spec?(module, name, arity) do
|
||||
tuple = { name, arity }
|
||||
tuple = {name, arity}
|
||||
Enum.any?(Module.get_attribute(module, :spec), &match?(^tuple, &1))
|
||||
end
|
||||
|
||||
@@ -345,39 +345,39 @@ defmodule Kernel.Typespec do
|
||||
This function is only available for modules being compiled.
|
||||
"""
|
||||
def defines_callback?(module, name, arity) do
|
||||
tuple = { name, arity }
|
||||
tuple = {name, arity}
|
||||
Enum.any?(Module.get_attribute(module, :callback), &match?(^tuple, &1))
|
||||
end
|
||||
|
||||
@doc """
|
||||
Converts a spec clause back to Elixir AST.
|
||||
"""
|
||||
def spec_to_ast(name, { :type, line, :fun, [{:type, _, :product, args}, result] }) do
|
||||
def spec_to_ast(name, {:type, line, :fun, [{:type, _, :product, args}, result]}) do
|
||||
meta = [line: line]
|
||||
body = { name, meta, Enum.map(args, &typespec_to_ast/1) }
|
||||
body = {name, meta, Enum.map(args, &typespec_to_ast/1)}
|
||||
|
||||
vars = args ++ [result]
|
||||
|> Enum.flat_map(&collect_vars/1)
|
||||
|> Enum.uniq
|
||||
|> Enum.map(&{ &1, { :var, meta, nil } })
|
||||
|> Enum.map(&{&1, {:var, meta, nil}})
|
||||
|
||||
spec = { :::, meta, [body, typespec_to_ast(result)] }
|
||||
spec = {:::, meta, [body, typespec_to_ast(result)]}
|
||||
|
||||
if vars == [] do
|
||||
spec
|
||||
else
|
||||
{ :when, meta, [spec, vars] }
|
||||
{:when, meta, [spec, vars]}
|
||||
end
|
||||
end
|
||||
|
||||
def spec_to_ast(name, { :type, line, :fun, [] }) do
|
||||
{ :::, [line: line], [{ name, [line: line], [] }, quote(do: term)] }
|
||||
def spec_to_ast(name, {:type, line, :fun, []}) do
|
||||
{:::, [line: line], [{name, [line: line], []}, quote(do: term)]}
|
||||
end
|
||||
|
||||
def spec_to_ast(name, { :type, line, :bounded_fun, [{ :type, _, :fun, [{ :type, _, :product, args }, result] }, constraints] }) do
|
||||
def spec_to_ast(name, {:type, line, :bounded_fun, [{:type, _, :fun, [{:type, _, :product, args}, result]}, constraints]}) do
|
||||
guards =
|
||||
for { :type, _, :constraint, [{ :atom, _, :is_subtype }, [{ :var, _, var }, type]] } <- constraints do
|
||||
{ var, typespec_to_ast(type) }
|
||||
for {:type, _, :constraint, [{:atom, _, :is_subtype}, [{:var, _, var}, type]]} <- constraints do
|
||||
{var, typespec_to_ast(type)}
|
||||
end
|
||||
|
||||
meta = [line: line]
|
||||
@@ -386,27 +386,27 @@ defmodule Kernel.Typespec do
|
||||
|> Enum.flat_map(&collect_vars/1)
|
||||
|> Enum.uniq
|
||||
|> Kernel.--(Keyword.keys(guards))
|
||||
|> Enum.map(&{ &1, { :var, meta, nil } })
|
||||
|> Enum.map(&{&1, {:var, meta, nil}})
|
||||
|
||||
args = for arg <- args, do: typespec_to_ast(arg)
|
||||
|
||||
{ :when, meta, [
|
||||
{ :::, meta, [{ name, [line: line], args }, typespec_to_ast(result)] },
|
||||
{:when, meta, [
|
||||
{:::, meta, [{name, [line: line], args}, typespec_to_ast(result)]},
|
||||
guards ++ vars
|
||||
] }
|
||||
]}
|
||||
end
|
||||
|
||||
@doc """
|
||||
Converts a type clause back to Elixir AST.
|
||||
"""
|
||||
def type_to_ast({ { :record, record }, fields, args }) when is_atom(record) do
|
||||
def type_to_ast({{:record, record}, fields, args}) when is_atom(record) do
|
||||
fields = for field <- fields, do: typespec_to_ast(field)
|
||||
args = for arg <- args, do: typespec_to_ast(arg)
|
||||
type = { :{}, [], [record|fields] }
|
||||
type = {:{}, [], [record|fields]}
|
||||
quote do: unquote(record)(unquote_splicing(args)) :: unquote(type)
|
||||
end
|
||||
|
||||
def type_to_ast({ name, type, args }) do
|
||||
def type_to_ast({name, type, args}) do
|
||||
args = for arg <- args, do: typespec_to_ast(arg)
|
||||
quote do: unquote(name)(unquote_splicing(args)) :: unquote(typespec_to_ast(type))
|
||||
end
|
||||
@@ -423,8 +423,8 @@ defmodule Kernel.Typespec do
|
||||
@spec beam_typedocs(module | binary) :: [tuple] | nil
|
||||
def beam_typedocs(module) when is_atom(module) or is_binary(module) do
|
||||
case abstract_code(module) do
|
||||
{ :ok, abstract_code } ->
|
||||
type_docs = for { :attribute, _, :typedoc, tup } <- abstract_code, do: tup
|
||||
{:ok, abstract_code} ->
|
||||
type_docs = for {:attribute, _, :typedoc, tup} <- abstract_code, do: tup
|
||||
List.flatten(type_docs)
|
||||
_ ->
|
||||
nil
|
||||
@@ -443,15 +443,15 @@ defmodule Kernel.Typespec do
|
||||
@spec beam_types(module | binary) :: [tuple] | nil
|
||||
def beam_types(module) when is_atom(module) or is_binary(module) do
|
||||
case abstract_code(module) do
|
||||
{ :ok, abstract_code } ->
|
||||
exported_types = for { :attribute, _, :export_type, types } <- abstract_code, do: types
|
||||
{:ok, abstract_code} ->
|
||||
exported_types = for {:attribute, _, :export_type, types} <- abstract_code, do: types
|
||||
exported_types = List.flatten(exported_types)
|
||||
|
||||
for { :attribute, _, kind, { name, _, args } = type } <- abstract_code, kind in [:opaque, :type] do
|
||||
for {:attribute, _, kind, {name, _, args} = type} <- abstract_code, kind in [:opaque, :type] do
|
||||
cond do
|
||||
kind == :opaque -> { :opaque, type }
|
||||
{ name, length(args) } in exported_types -> { :type, type }
|
||||
true -> { :typep, type }
|
||||
kind == :opaque -> {:opaque, type}
|
||||
{name, length(args)} in exported_types -> {:type, type}
|
||||
true -> {:typep, type}
|
||||
end
|
||||
end
|
||||
_ ->
|
||||
@@ -489,8 +489,8 @@ defmodule Kernel.Typespec do
|
||||
|
||||
defp from_abstract_code(module, kind) do
|
||||
case abstract_code(module) do
|
||||
{ :ok, abstract_code } ->
|
||||
for { :attribute, _, abs_kind, value } <- abstract_code, kind == abs_kind, do: value
|
||||
{:ok, abstract_code} ->
|
||||
for {:attribute, _, abs_kind, value} <- abstract_code, kind == abs_kind, do: value
|
||||
:error ->
|
||||
nil
|
||||
end
|
||||
@@ -498,8 +498,8 @@ defmodule Kernel.Typespec do
|
||||
|
||||
defp abstract_code(module) do
|
||||
case :beam_lib.chunks(abstract_code_beam(module), [:abstract_code]) do
|
||||
{ :ok, { _, [{ :abstract_code, { _raw_abstract_v1, abstract_code } }] } } ->
|
||||
{ :ok, abstract_code }
|
||||
{:ok, {_, [{:abstract_code, {_raw_abstract_v1, abstract_code}}]}} ->
|
||||
{:ok, abstract_code}
|
||||
_ ->
|
||||
:error
|
||||
end
|
||||
@@ -507,7 +507,7 @@ defmodule Kernel.Typespec do
|
||||
|
||||
defp abstract_code_beam(module) when is_atom(module) do
|
||||
case :code.get_object_code(module) do
|
||||
{ ^module, beam, _filename } -> beam
|
||||
{^module, beam, _filename} -> beam
|
||||
:error -> module
|
||||
end
|
||||
end
|
||||
@@ -519,7 +519,7 @@ defmodule Kernel.Typespec do
|
||||
## Macro callbacks
|
||||
|
||||
@doc false
|
||||
def deftype(kind, { :::, _, [{ name, _, args }, definition] }, caller) when is_atom(name) and name != ::: do
|
||||
def deftype(kind, {:::, _, [{name, _, args}, definition]}, caller) when is_atom(name) and name != ::: do
|
||||
args =
|
||||
if is_atom(args) do
|
||||
[]
|
||||
@@ -527,11 +527,11 @@ defmodule Kernel.Typespec do
|
||||
for(arg <- args, do: variable(arg))
|
||||
end
|
||||
|
||||
vars = for { :var, _, var } <- args, do: var
|
||||
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}
|
||||
|
||||
define_type(caller, kind, type)
|
||||
end
|
||||
@@ -543,7 +543,7 @@ defmodule Kernel.Typespec do
|
||||
|
||||
@doc false
|
||||
|
||||
def defspec(type, { :when, _meta, [spec, guard] }, caller) do
|
||||
def defspec(type, {:when, _meta, [spec, guard]}, caller) do
|
||||
defspec(type, spec, guard, caller)
|
||||
end
|
||||
|
||||
@@ -551,7 +551,7 @@ defmodule Kernel.Typespec do
|
||||
defspec(type, spec, [], caller)
|
||||
end
|
||||
|
||||
defp defspec(type, { :::, meta, [{ name, _, args }, return] }, guard, caller) when is_atom(name) and name != ::: do
|
||||
defp defspec(type, {:::, meta, [{name, _, args}, return]}, guard, caller) when is_atom(name) and name != ::: do
|
||||
if is_atom(args), do: args = []
|
||||
|
||||
unless Keyword.keyword?(guard) do
|
||||
@@ -562,12 +562,12 @@ defmodule Kernel.Typespec do
|
||||
vars = Keyword.keys(guard)
|
||||
constraints = guard_to_constraints(guard, vars, meta, caller)
|
||||
|
||||
spec = { :type, line(meta), :fun, fn_args(meta, args, return, vars, caller) }
|
||||
spec = {:type, line(meta), :fun, fn_args(meta, args, return, vars, caller)}
|
||||
if constraints != [] do
|
||||
spec = { :type, line(meta), :bounded_fun, [spec, constraints] }
|
||||
spec = {:type, line(meta), :bounded_fun, [spec, constraints]}
|
||||
end
|
||||
|
||||
code = { { name, Kernel.length(args) }, spec }
|
||||
code = {{name, Kernel.length(args)}, spec}
|
||||
Module.compile_typespec(caller.module, type, code)
|
||||
code
|
||||
end
|
||||
@@ -581,38 +581,38 @@ defmodule Kernel.Typespec do
|
||||
line = line(meta)
|
||||
|
||||
Enum.reduce(guard, [], fn
|
||||
{ _name, { :var, _, context } }, acc when is_atom(context) ->
|
||||
{_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 }
|
||||
{name, type}, acc ->
|
||||
constraint = [{:atom, line, :is_subtype}, [{:var, line, name}, typespec(type, vars, caller)]]
|
||||
type = {:type, line, :constraint, constraint}
|
||||
[type|acc]
|
||||
end) |> Enum.reverse
|
||||
end
|
||||
|
||||
## To AST conversion
|
||||
|
||||
defp collect_vars({ :ann_type, _line, args }) when is_list(args) do
|
||||
defp collect_vars({:ann_type, _line, args}) when is_list(args) do
|
||||
[]
|
||||
end
|
||||
|
||||
defp collect_vars({ :type, _line, _kind, args }) when is_list(args) do
|
||||
defp collect_vars({:type, _line, _kind, args}) when is_list(args) do
|
||||
Enum.flat_map(args, &collect_vars/1)
|
||||
end
|
||||
|
||||
defp collect_vars({ :remote_type, _line, args }) when is_list(args) do
|
||||
defp collect_vars({:remote_type, _line, args}) when is_list(args) do
|
||||
Enum.flat_map(args, &collect_vars/1)
|
||||
end
|
||||
|
||||
defp collect_vars({ :typed_record_field, _line, type }) do
|
||||
defp collect_vars({:typed_record_field, _line, type}) do
|
||||
collect_vars(type)
|
||||
end
|
||||
|
||||
defp collect_vars({ :paren_type, _line, [type] }) do
|
||||
defp collect_vars({:paren_type, _line, [type]}) do
|
||||
collect_vars(type)
|
||||
end
|
||||
|
||||
defp collect_vars({ :var, _line, var }) do
|
||||
defp collect_vars({:var, _line, var}) do
|
||||
[erl_to_ex_var(var)]
|
||||
end
|
||||
|
||||
@@ -620,42 +620,42 @@ defmodule Kernel.Typespec do
|
||||
[]
|
||||
end
|
||||
|
||||
defp typespec_to_ast({ :type, line, :tuple, :any }) do
|
||||
{ :tuple, [line: line], [] }
|
||||
defp typespec_to_ast({:type, line, :tuple, :any}) do
|
||||
{:tuple, [line: line], []}
|
||||
end
|
||||
|
||||
defp typespec_to_ast({ :type, line, :tuple, args }) do
|
||||
defp typespec_to_ast({:type, line, :tuple, args}) do
|
||||
args = for arg <- args, do: typespec_to_ast(arg)
|
||||
{ :{}, [line: line], args }
|
||||
{:{}, [line: line], args}
|
||||
end
|
||||
|
||||
defp typespec_to_ast({ :type, _line, :list, [{ :type, _, :union, unions } = arg] }) do
|
||||
defp typespec_to_ast({:type, _line, :list, [{:type, _, :union, unions} = arg]}) do
|
||||
case unpack_typespec_kw(unions, []) do
|
||||
{ :ok, ast } -> ast
|
||||
{:ok, ast} -> ast
|
||||
:error -> [typespec_to_ast(arg)]
|
||||
end
|
||||
end
|
||||
|
||||
defp typespec_to_ast({ :type, _line, :list, args }) do
|
||||
defp typespec_to_ast({:type, _line, :list, args}) do
|
||||
for arg <- args, do: typespec_to_ast(arg)
|
||||
end
|
||||
|
||||
defp typespec_to_ast({ :type, line, :map, fields }) do
|
||||
fields = Enum.map fields, fn { :type, _, :map_field_assoc, k, v } ->
|
||||
{ typespec_to_ast(k), typespec_to_ast(v) }
|
||||
defp typespec_to_ast({:type, line, :map, fields}) do
|
||||
fields = Enum.map fields, fn {:type, _, :map_field_assoc, k, v} ->
|
||||
{typespec_to_ast(k), typespec_to_ast(v)}
|
||||
end
|
||||
|
||||
{ struct, fields } = Keyword.pop(fields, :__struct__)
|
||||
map = { :%{}, [line: line], fields }
|
||||
{struct, fields} = Keyword.pop(fields, :__struct__)
|
||||
map = {:%{}, [line: line], fields}
|
||||
|
||||
if struct do
|
||||
{ :%, [line: line], [struct, map] }
|
||||
{:%, [line: line], [struct, map]}
|
||||
else
|
||||
map
|
||||
end
|
||||
end
|
||||
|
||||
defp typespec_to_ast({ :type, line, :binary, [arg1, arg2] }) do
|
||||
defp typespec_to_ast({:type, line, :binary, [arg1, arg2]}) do
|
||||
[arg1, arg2] = for arg <- [arg1, arg2], do: typespec_to_ast(arg)
|
||||
cond do
|
||||
arg2 == 0 ->
|
||||
@@ -667,75 +667,79 @@ defmodule Kernel.Typespec do
|
||||
end
|
||||
end
|
||||
|
||||
defp typespec_to_ast({ :type, line, :union, args }) do
|
||||
defp typespec_to_ast({:type, line, :union, args}) do
|
||||
args = for arg <- args, do: typespec_to_ast(arg)
|
||||
Enum.reduce Enum.reverse(args), fn(arg, expr) -> { :|, [line: line], [arg, expr] } end
|
||||
Enum.reduce Enum.reverse(args), fn(arg, expr) -> {:|, [line: line], [arg, expr]} end
|
||||
end
|
||||
|
||||
defp typespec_to_ast({ :type, line, :fun, [{:type, _, :product, args}, result] }) do
|
||||
defp typespec_to_ast({:type, line, :fun, [{:type, _, :product, args}, result]}) do
|
||||
args = for arg <- args, do: typespec_to_ast(arg)
|
||||
[{ :->, [line: line], [args, typespec_to_ast(result)] }]
|
||||
[{:->, [line: line], [args, typespec_to_ast(result)]}]
|
||||
end
|
||||
|
||||
defp typespec_to_ast({ :type, line, :fun, [args, result] }) do
|
||||
[{ :->, [line: line], [[typespec_to_ast(args)], typespec_to_ast(result)] }]
|
||||
defp typespec_to_ast({:type, line, :fun, [args, result]}) do
|
||||
[{:->, [line: line], [[typespec_to_ast(args)], typespec_to_ast(result)]}]
|
||||
end
|
||||
|
||||
defp typespec_to_ast({ :type, line, :fun, [] }) do
|
||||
typespec_to_ast({ :type, line, :fun, [{:type, line, :any}, {:type, line, :any, []} ] })
|
||||
defp typespec_to_ast({:type, line, :fun, []}) do
|
||||
typespec_to_ast({:type, line, :fun, [{:type, line, :any}, {:type, line, :any, []} ]})
|
||||
end
|
||||
|
||||
defp typespec_to_ast({ :type, line, :range, [left, right] }) do
|
||||
{ :"..", [line: line], [typespec_to_ast(left), typespec_to_ast(right)] }
|
||||
defp typespec_to_ast({:type, line, :range, [left, right]}) do
|
||||
{:"..", [line: line], [typespec_to_ast(left), typespec_to_ast(right)]}
|
||||
end
|
||||
|
||||
defp typespec_to_ast({ :type, line, name, args }) do
|
||||
defp typespec_to_ast({:type, line, name, args}) do
|
||||
args = for arg <- args, do: typespec_to_ast(arg)
|
||||
{ name, [line: line], args }
|
||||
{name, [line: line], args}
|
||||
end
|
||||
|
||||
defp typespec_to_ast({ :var, line, var }) do
|
||||
{ erl_to_ex_var(var), line, nil }
|
||||
defp typespec_to_ast({:var, line, var}) do
|
||||
{erl_to_ex_var(var), line, nil}
|
||||
end
|
||||
|
||||
defp typespec_to_ast({ :op, line, op, arg }) do
|
||||
{ op, [line: line], [typespec_to_ast(arg)] }
|
||||
defp typespec_to_ast({:op, line, op, arg}) do
|
||||
{op, [line: line], [typespec_to_ast(arg)]}
|
||||
end
|
||||
|
||||
# Special shortcut(s)
|
||||
defp typespec_to_ast({ :remote_type, line, [{:atom, _, :elixir}, {:atom, _, :char_list}, []] }) do
|
||||
defp typespec_to_ast({:remote_type, line, [{:atom, _, :elixir}, {:atom, _, :char_list}, []]}) do
|
||||
typespec_to_ast({:type, line, :char_list, []})
|
||||
end
|
||||
|
||||
defp typespec_to_ast({ :remote_type, line, [{:atom, _, :elixir}, {:atom, _, :as_boolean}, [arg]] }) do
|
||||
defp typespec_to_ast({:remote_type, line, [{:atom, _, :elixir}, {:atom, _, :char_data}, []]}) do
|
||||
typespec_to_ast({:type, line, :char_data, []})
|
||||
end
|
||||
|
||||
defp typespec_to_ast({:remote_type, line, [{:atom, _, :elixir}, {:atom, _, :as_boolean}, [arg]]}) do
|
||||
typespec_to_ast({:type, line, :as_boolean, [arg]})
|
||||
end
|
||||
|
||||
defp typespec_to_ast({ :remote_type, line, [mod, name, args] }) do
|
||||
defp typespec_to_ast({:remote_type, line, [mod, name, args]}) do
|
||||
args = for arg <- args, do: typespec_to_ast(arg)
|
||||
dot = { :., [line: line], [typespec_to_ast(mod), typespec_to_ast(name)] }
|
||||
{ dot, [line: line], args }
|
||||
dot = {:., [line: line], [typespec_to_ast(mod), typespec_to_ast(name)]}
|
||||
{dot, [line: line], args}
|
||||
end
|
||||
|
||||
defp typespec_to_ast({ :ann_type, line, [var, type] }) do
|
||||
{ :::, [line: line], [typespec_to_ast(var), typespec_to_ast(type)] }
|
||||
defp typespec_to_ast({:ann_type, line, [var, type]}) do
|
||||
{:::, [line: line], [typespec_to_ast(var), typespec_to_ast(type)]}
|
||||
end
|
||||
|
||||
defp typespec_to_ast({ :typed_record_field,
|
||||
{ :record_field, line, { :atom, line1, name } },
|
||||
type }) do
|
||||
typespec_to_ast({ :ann_type, line, [{ :var, line1, name }, type] })
|
||||
defp typespec_to_ast({:typed_record_field,
|
||||
{:record_field, line, {:atom, line1, name}},
|
||||
type}) do
|
||||
typespec_to_ast({:ann_type, line, [{:var, line1, name}, type]})
|
||||
end
|
||||
|
||||
defp typespec_to_ast({ :type, _, :any }) do
|
||||
defp typespec_to_ast({:type, _, :any}) do
|
||||
quote do: ...
|
||||
end
|
||||
|
||||
defp typespec_to_ast({ :paren_type, _, [type] }) do
|
||||
defp typespec_to_ast({:paren_type, _, [type]}) do
|
||||
typespec_to_ast(type)
|
||||
end
|
||||
|
||||
defp typespec_to_ast({ t, _line, atom }) when is_atom(t) do
|
||||
defp typespec_to_ast({t, _line, atom}) when is_atom(t) do
|
||||
atom
|
||||
end
|
||||
|
||||
@@ -754,16 +758,16 @@ defmodule Kernel.Typespec do
|
||||
|
||||
defp line(meta) do
|
||||
case :lists.keyfind(:line, 1, meta) do
|
||||
{ :line, line } -> line
|
||||
{:line, line} -> line
|
||||
false -> 0
|
||||
end
|
||||
end
|
||||
|
||||
# Handle unions
|
||||
defp typespec({ :|, meta, [_, _] } = exprs, vars, caller) do
|
||||
defp typespec({:|, meta, [_, _]} = exprs, vars, caller) do
|
||||
exprs = collect_union(exprs)
|
||||
union = for e <- exprs, do: typespec(e, vars, caller)
|
||||
{ :type, line(meta), :union, union }
|
||||
{:type, line(meta), :union, union}
|
||||
end
|
||||
|
||||
# Handle binaries
|
||||
@@ -781,7 +785,7 @@ defmodule Kernel.Typespec do
|
||||
|
||||
## Handle maps and structs
|
||||
defp typespec({:%{}, meta, fields}, vars, caller) do
|
||||
fields = Enum.map(fields, fn { k, v } ->
|
||||
fields = Enum.map(fields, fn {k, v} ->
|
||||
{:type, line(meta), :map_field_assoc, typespec(k, vars, caller), typespec(v, vars, caller)}
|
||||
end)
|
||||
{:type, line(meta), :map, fields}
|
||||
@@ -809,25 +813,25 @@ defmodule Kernel.Typespec do
|
||||
# Handle funs
|
||||
defp typespec([{:->, meta, [arguments, return]}], vars, caller) when is_list(arguments) do
|
||||
args = fn_args(meta, arguments, return, vars, caller)
|
||||
{ :type, line(meta), :fun, args }
|
||||
{:type, line(meta), :fun, args}
|
||||
end
|
||||
|
||||
# Handle type operator
|
||||
defp typespec({:::, meta, [var, expr] }, vars, caller) do
|
||||
defp typespec({:::, meta, [var, expr]}, vars, caller) do
|
||||
left = typespec(var, [elem(var, 0)|vars], caller)
|
||||
right = typespec(expr, vars, caller)
|
||||
{ :ann_type, line(meta), [left, right] }
|
||||
{:ann_type, line(meta), [left, right]}
|
||||
end
|
||||
|
||||
# Handle unary ops
|
||||
defp typespec({op, meta, [integer]}, _, _) when op in [:+, :-] and is_integer(integer) do
|
||||
{ :op, line(meta), op, {:integer, line(meta), integer} }
|
||||
{: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, [], [] }]]}
|
||||
[target, args ++ [_: {:any, [], []}]]}
|
||||
typespec(Macro.expand(access, caller), vars, caller)
|
||||
end
|
||||
|
||||
@@ -842,16 +846,16 @@ defmodule Kernel.Typespec do
|
||||
|
||||
# Handle tuples
|
||||
defp typespec({:tuple, meta, args}, _vars, _caller) when args == [] or is_atom(args) do
|
||||
{ :type, line(meta), :tuple, :any }
|
||||
{:type, line(meta), :tuple, :any}
|
||||
end
|
||||
|
||||
defp typespec({:{}, meta, t}, vars, caller) when is_list(t) do
|
||||
args = for e <- t, do: typespec(e, vars, caller)
|
||||
{ :type, line(meta), :tuple, args }
|
||||
{:type, line(meta), :tuple, args}
|
||||
end
|
||||
|
||||
defp typespec({ left, right }, vars, caller) do
|
||||
typespec({ :{}, [], [left, right] }, vars, caller)
|
||||
defp typespec({left, right}, vars, caller) do
|
||||
typespec({:{}, [], [left, right]}, vars, caller)
|
||||
end
|
||||
|
||||
# Handle blocks
|
||||
@@ -862,7 +866,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
|
||||
{ :var, line(meta), name }
|
||||
{:var, line(meta), name}
|
||||
else
|
||||
typespec({name, meta, []}, vars, caller)
|
||||
end
|
||||
@@ -871,51 +875,55 @@ defmodule Kernel.Typespec do
|
||||
# Handle local calls
|
||||
defp typespec({:string, meta, arguments}, vars, caller) do
|
||||
:elixir_errors.warn caller.line, caller.file, "string() type use is discouraged. For character lists, use " <>
|
||||
"char_list() type, for strings, String.t()\n#{Exception.format_stacktrace(caller.stacktrace)}"
|
||||
"char_list() type, for strings, String.t()\n#{Exception.format_stacktrace(Macro.Env.stacktrace(caller))}"
|
||||
arguments = for arg <- arguments, do: typespec(arg, vars, caller)
|
||||
{ :type, line(meta), :string, arguments }
|
||||
{:type, line(meta), :string, arguments}
|
||||
end
|
||||
|
||||
defp typespec({:char_list, _meta, arguments}, vars, caller) do
|
||||
typespec((quote do: :elixir.char_list(unquote_splicing(arguments))), vars, caller)
|
||||
defp typespec({:char_list, _meta, []}, vars, caller) do
|
||||
typespec((quote do: :elixir.char_list()), vars, caller)
|
||||
end
|
||||
|
||||
defp typespec({:as_boolean, _meta, arguments}, vars, caller) do
|
||||
typespec((quote do: :elixir.as_boolean(unquote_splicing(arguments))), vars, caller)
|
||||
defp typespec({:char_data, _meta, []}, vars, caller) do
|
||||
typespec((quote do: :elixir.char_data()), vars, caller)
|
||||
end
|
||||
|
||||
defp typespec({:as_boolean, _meta, [arg]}, vars, caller) do
|
||||
typespec((quote do: :elixir.as_boolean(unquote(arg))), vars, caller)
|
||||
end
|
||||
|
||||
defp typespec({name, meta, arguments}, vars, caller) do
|
||||
arguments = for arg <- arguments, do: typespec(arg, vars, caller)
|
||||
{ :type, line(meta), name, arguments }
|
||||
{:type, line(meta), name, arguments}
|
||||
end
|
||||
|
||||
# Handle literals
|
||||
defp typespec(atom, _, _) when is_atom(atom) do
|
||||
{ :atom, 0, atom }
|
||||
{:atom, 0, atom}
|
||||
end
|
||||
|
||||
defp typespec(integer, _, _) when is_integer(integer) do
|
||||
{ :integer, 0, integer }
|
||||
{:integer, 0, integer}
|
||||
end
|
||||
|
||||
defp typespec([], vars, caller) do
|
||||
typespec({ nil, [], [] }, vars, caller)
|
||||
typespec({nil, [], []}, vars, caller)
|
||||
end
|
||||
|
||||
defp typespec([spec], vars, caller) do
|
||||
typespec({ :list, [], [spec] }, vars, caller)
|
||||
typespec({:list, [], [spec]}, vars, caller)
|
||||
end
|
||||
|
||||
defp typespec([spec, {:"...", _, quoted}], vars, caller) when is_atom(quoted) do
|
||||
typespec({ :nonempty_list, [], [spec] }, vars, caller)
|
||||
typespec({:nonempty_list, [], [spec]}, vars, caller)
|
||||
end
|
||||
|
||||
defp typespec(list, vars, caller) do
|
||||
[h|t] = Enum.reverse(list)
|
||||
union = Enum.reduce(t, validate_kw(h, list, caller), fn(x, acc) ->
|
||||
{ :|, [], [validate_kw(x, list, caller), acc] }
|
||||
{:|, [], [validate_kw(x, list, caller), acc]}
|
||||
end)
|
||||
typespec({ :list, [], [union] }, vars, caller)
|
||||
typespec({:list, [], [union]}, vars, caller)
|
||||
end
|
||||
|
||||
## Helpers
|
||||
@@ -926,13 +934,13 @@ defmodule Kernel.Typespec do
|
||||
|
||||
defp remote_type({remote, meta, name, arguments}, vars, caller) do
|
||||
arguments = for arg <- arguments, do: typespec(arg, vars, caller)
|
||||
{ :remote_type, line(meta), [ remote, name, arguments ] }
|
||||
{:remote_type, line(meta), [ remote, name, arguments ]}
|
||||
end
|
||||
|
||||
defp collect_union({ :|, _, [a, b] }), do: [a|collect_union(b)]
|
||||
defp collect_union({:|, _, [a, b]}), do: [a|collect_union(b)]
|
||||
defp collect_union(v), do: [v]
|
||||
|
||||
defp validate_kw({ key, _ } = t, _, _caller) when is_atom(key), do: t
|
||||
defp validate_kw({key, _} = t, _, _caller) when is_atom(key), do: t
|
||||
defp validate_kw(_, original, caller) do
|
||||
compile_error(caller, "unexpected list in typespec: #{Macro.to_string original}")
|
||||
end
|
||||
@@ -945,24 +953,24 @@ defmodule Kernel.Typespec do
|
||||
end
|
||||
|
||||
defp fn_args(meta, [{:"...", _, _}], _vars, _caller) do
|
||||
{ :type, line(meta), :any }
|
||||
{:type, line(meta), :any}
|
||||
end
|
||||
|
||||
defp fn_args(meta, args, vars, caller) do
|
||||
args = for arg <- args, do: typespec(arg, vars, caller)
|
||||
{ :type, line(meta), :product, args }
|
||||
{:type, line(meta), :product, args}
|
||||
end
|
||||
|
||||
defp variable({name, meta, _}) do
|
||||
{:var, line(meta), name}
|
||||
end
|
||||
|
||||
defp unpack_typespec_kw([{ :type, _, :tuple, [{ :atom, _, atom }, type] }|t], acc) do
|
||||
defp unpack_typespec_kw([{:type, _, :tuple, [{:atom, _, atom}, type]}|t], acc) do
|
||||
unpack_typespec_kw(t, [{atom, typespec_to_ast(type)}|acc])
|
||||
end
|
||||
|
||||
defp unpack_typespec_kw([], acc) do
|
||||
{ :ok, :lists.reverse(acc) }
|
||||
{:ok, :lists.reverse(acc)}
|
||||
end
|
||||
|
||||
defp unpack_typespec_kw(_, _acc) do
|
||||
|
||||
+42
-39
@@ -23,28 +23,19 @@ defmodule Keyword do
|
||||
in `Enum` and `List` can also be applied.
|
||||
"""
|
||||
|
||||
@behaviour Dict
|
||||
|
||||
@type key :: atom
|
||||
@type value :: any
|
||||
|
||||
@type t :: [{key, value}]
|
||||
@type t(value) :: [{ key, value }]
|
||||
|
||||
@doc """
|
||||
Creates a Keyword from an enum. Unlike `Keyword.new`
|
||||
which behaves as a dict, `Keyword.from_enum` does not remove
|
||||
duplicated entries.
|
||||
"""
|
||||
@spec from_enum(Enum.t) :: t
|
||||
def from_enum(enum) do
|
||||
IO.write :stderr, "Keyword.from_enum/1 is deprecated, please use Enum.into/2 instead\n#{Exception.format_stacktrace}"
|
||||
Enum.to_list(enum)
|
||||
end
|
||||
@type t(value) :: [{key, value}]
|
||||
|
||||
@doc """
|
||||
Checks if the given argument is a keywords list or not.
|
||||
"""
|
||||
@spec keyword?(term) :: boolean
|
||||
def keyword?([{ key, _value } | rest]) when is_atom(key) do
|
||||
def keyword?([{key, _value} | rest]) when is_atom(key) do
|
||||
keyword?(rest)
|
||||
end
|
||||
|
||||
@@ -74,7 +65,7 @@ defmodule Keyword do
|
||||
"""
|
||||
@spec new(Enum.t) :: t
|
||||
def new(pairs) do
|
||||
Enum.reduce pairs, [], fn { k, v }, keywords ->
|
||||
Enum.reduce pairs, [], fn {k, v}, keywords ->
|
||||
put(keywords, k, v)
|
||||
end
|
||||
end
|
||||
@@ -95,7 +86,7 @@ defmodule Keyword do
|
||||
@spec new(Enum.t, ({key, value} -> {key, value})) :: t
|
||||
def new(pairs, transform) do
|
||||
Enum.reduce pairs, [], fn i, keywords ->
|
||||
{ k, v } = transform.(i)
|
||||
{k, v} = transform.(i)
|
||||
put(keywords, k, v)
|
||||
end
|
||||
end
|
||||
@@ -124,7 +115,7 @@ defmodule Keyword do
|
||||
@spec get(t, key, value) :: value
|
||||
def get(keywords, key, default \\ nil) when is_list(keywords) and is_atom(key) do
|
||||
case :lists.keyfind(key, 1, keywords) do
|
||||
{ ^key, value } -> value
|
||||
{^key, value} -> value
|
||||
false -> default
|
||||
end
|
||||
end
|
||||
@@ -136,16 +127,16 @@ defmodule Keyword do
|
||||
## Examples
|
||||
|
||||
iex> Keyword.fetch([a: 1], :a)
|
||||
{ :ok, 1 }
|
||||
{:ok, 1}
|
||||
|
||||
iex> Keyword.fetch([a: 1], :b)
|
||||
:error
|
||||
|
||||
"""
|
||||
@spec fetch(t, key) :: value
|
||||
@spec fetch(t, key) :: {:ok, value}
|
||||
def fetch(keywords, key) when is_list(keywords) and is_atom(key) do
|
||||
case :lists.keyfind(key, 1, keywords) do
|
||||
{ ^key, value } -> { :ok, value }
|
||||
{^key, value} -> {:ok, value}
|
||||
false -> :error
|
||||
end
|
||||
end
|
||||
@@ -166,7 +157,7 @@ defmodule Keyword do
|
||||
@spec fetch!(t, key) :: value | no_return
|
||||
def fetch!(keywords, key) when is_list(keywords) and is_atom(key) do
|
||||
case :lists.keyfind(key, 1, keywords) do
|
||||
{ ^key, value } -> value
|
||||
{^key, value} -> value
|
||||
false -> raise(KeyError, key: key, term: keywords)
|
||||
end
|
||||
end
|
||||
@@ -182,7 +173,7 @@ 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
|
||||
for {k, v} <- keywords, key == k, do: v
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -200,7 +191,7 @@ defmodule Keyword do
|
||||
"""
|
||||
@spec keys(t) :: [key]
|
||||
def keys(keywords) when is_list(keywords) do
|
||||
for { key, _ } <- keywords, do: key
|
||||
for {key, _} <- keywords, do: key
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -214,7 +205,7 @@ defmodule Keyword do
|
||||
"""
|
||||
@spec values(t) :: [value]
|
||||
def values(keywords) when is_list(keywords) do
|
||||
for { _, value } <- keywords, do: value
|
||||
for {_, value} <- keywords, do: value
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -235,7 +226,7 @@ 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
|
||||
for {k, v} = tuple <- keywords, key != k or value != v, do: tuple
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -258,7 +249,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
|
||||
for {k, _} = tuple <- keywords, key != k, do: tuple
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -315,7 +306,7 @@ defmodule Keyword do
|
||||
@spec put_new(t, key, value) :: t
|
||||
def put_new(keywords, key, value) when is_list(keywords) and is_atom(key) do
|
||||
case :lists.keyfind(key, 1, keywords) do
|
||||
{ ^key, _ } -> keywords
|
||||
{^key, _} -> keywords
|
||||
false -> [{key, value}|keywords]
|
||||
end
|
||||
end
|
||||
@@ -347,7 +338,7 @@ 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)
|
||||
d2 ++ for({k, _} = tuple <- d1, not has_key?(d2, k), do: tuple)
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -367,7 +358,7 @@ defmodule Keyword do
|
||||
do_merge(d2, d1, fun)
|
||||
end
|
||||
|
||||
defp do_merge([{ k, v2 }|t], acc, fun) do
|
||||
defp do_merge([{k, v2}|t], acc, fun) do
|
||||
do_merge t, update(acc, k, v2, fn(v1) -> fun.(k, v1, v2) end), fun
|
||||
end
|
||||
|
||||
@@ -456,24 +447,24 @@ defmodule Keyword do
|
||||
|
||||
iex> d = [a: 1, b: 2, c: 3, d: 4]
|
||||
iex> Keyword.split(d, [:a, :c, :e])
|
||||
{ [a: 1, c: 3], [b: 2, d: 4] }
|
||||
{[a: 1, c: 3], [b: 2, d: 4]}
|
||||
|
||||
iex> d = [a: 1, b: 2, c: 3, d: 4, a: 5]
|
||||
iex> Keyword.split(d, [:a, :c, :e])
|
||||
{ [a: 1, c: 3, a: 5], [b: 2, d: 4] }
|
||||
{[a: 1, c: 3, a: 5], [b: 2, d: 4]}
|
||||
|
||||
"""
|
||||
def split(keywords, keys) when is_list(keywords) do
|
||||
acc = { [], [] }
|
||||
acc = {[], []}
|
||||
|
||||
{ take, drop } = Enum.reduce keywords, acc, fn({ k, v }, { take, drop }) ->
|
||||
{take, drop} = Enum.reduce keywords, acc, fn({k, v}, {take, drop}) ->
|
||||
case k in keys do
|
||||
true -> { [{k, v}|take], drop }
|
||||
false -> { take, [{k, v}|drop] }
|
||||
true -> {[{k, v}|take], drop}
|
||||
false -> {take, [{k, v}|drop]}
|
||||
end
|
||||
end
|
||||
|
||||
{ Enum.reverse(take), Enum.reverse(drop) }
|
||||
{Enum.reverse(take), Enum.reverse(drop)}
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -492,7 +483,7 @@ defmodule Keyword do
|
||||
|
||||
"""
|
||||
def take(keywords, keys) when is_list(keywords) do
|
||||
for { k, _ } = tuple <- keywords, k in keys, do: tuple
|
||||
for {k, _} = tuple <- keywords, k in keys, do: tuple
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -511,7 +502,7 @@ defmodule Keyword do
|
||||
|
||||
"""
|
||||
def drop(keywords, keys) when is_list(keywords) do
|
||||
for { k, _ } = tuple <- keywords, not k in keys, do: tuple
|
||||
for {k, _} = tuple <- keywords, not k in keys, do: tuple
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -540,7 +531,7 @@ defmodule Keyword do
|
||||
|
||||
"""
|
||||
def pop(keywords, key, default \\ nil) when is_list(keywords) do
|
||||
{ get(keywords, key, default), delete(keywords, key) }
|
||||
{get(keywords, key, default), delete(keywords, key)}
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -569,6 +560,18 @@ defmodule Keyword do
|
||||
|
||||
"""
|
||||
def pop_first(keywords, key, default \\ nil) when is_list(keywords) do
|
||||
{ get(keywords, key, default), delete_first(keywords, key) }
|
||||
{get(keywords, key, default), delete_first(keywords, key)}
|
||||
end
|
||||
|
||||
# Dict callbacks
|
||||
|
||||
@doc false
|
||||
def size(keyword) do
|
||||
length(keyword)
|
||||
end
|
||||
|
||||
@doc false
|
||||
def to_list(keyword) do
|
||||
keyword
|
||||
end
|
||||
end
|
||||
|
||||
+69
-8
@@ -164,10 +164,10 @@ defmodule List do
|
||||
## Examples
|
||||
|
||||
iex> List.keyfind([a: 1, b: 2], :a, 0)
|
||||
{ :a, 1 }
|
||||
{:a, 1}
|
||||
|
||||
iex> List.keyfind([a: 1, b: 2], 2, 1)
|
||||
{ :b, 2 }
|
||||
{:b, 2}
|
||||
|
||||
iex> List.keyfind([a: 1, b: 2], :c, 0)
|
||||
nil
|
||||
@@ -206,7 +206,7 @@ defmodule List do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> List.keyreplace([a: 1, b: 2], :a, 0, { :a, 3 })
|
||||
iex> List.keyreplace([a: 1, b: 2], :a, 0, {:a, 3})
|
||||
[a: 3, b: 2]
|
||||
|
||||
"""
|
||||
@@ -240,10 +240,10 @@ defmodule List do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> List.keystore([a: 1, b: 2], :a, 0, { :a, 3 })
|
||||
iex> List.keystore([a: 1, b: 2], :a, 0, {:a, 3})
|
||||
[a: 3, b: 2]
|
||||
|
||||
iex> List.keystore([a: 1, b: 2], :c, 0, { :c, 3 })
|
||||
iex> List.keystore([a: 1, b: 2], :c, 0, {:c, 3})
|
||||
[a: 1, b: 2, c: 3]
|
||||
|
||||
"""
|
||||
@@ -453,6 +453,67 @@ defmodule List do
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Converts char data into a char list.
|
||||
|
||||
A char data is a string or a list of integers and strings and it
|
||||
is converted into a list with each codepoint represented as its
|
||||
respective integer value.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> List.from_char_data("æß")
|
||||
{:ok, 'æß'}
|
||||
iex> List.from_char_data([?a, "bc"])
|
||||
{:ok, 'abc'}
|
||||
|
||||
"""
|
||||
@spec from_char_data(char_data) :: {:ok, char_list} | {:error, list, binary} | {:incomplete, list, binary}
|
||||
def from_char_data(char_data) do
|
||||
case :unicode.characters_to_list(char_data) do
|
||||
result when is_list(result) ->
|
||||
{:ok, result}
|
||||
|
||||
{:error, _, _} = error ->
|
||||
error
|
||||
|
||||
{:incomplete, _, _} = incomplete ->
|
||||
incomplete
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Converts char data into a char list.
|
||||
|
||||
A char data is a string or a list of integers and strings and it
|
||||
is converted into a list with each codepoint represented as its
|
||||
respective integer value.
|
||||
|
||||
In case the conversion fails or is incomplete, it raises a
|
||||
`UnicodeConversionError`.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> List.from_char_data!("æß")
|
||||
'æß'
|
||||
iex> List.from_char_data!("abc")
|
||||
'abc'
|
||||
|
||||
"""
|
||||
@spec from_char_data!(char_data) :: char_list | no_return
|
||||
def from_char_data!(char_data) do
|
||||
case :unicode.characters_to_list(char_data) do
|
||||
result when is_list(result) ->
|
||||
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
|
||||
@@ -536,15 +597,15 @@ defmodule List do
|
||||
end
|
||||
|
||||
defp do_zip_each(_, nil) do
|
||||
{ nil, nil }
|
||||
{nil, nil}
|
||||
end
|
||||
|
||||
defp do_zip_each([h|t], acc) do
|
||||
{ t, [h|acc] }
|
||||
{t, [h|acc]}
|
||||
end
|
||||
|
||||
defp do_zip_each([], _) do
|
||||
{ nil, nil }
|
||||
{nil, nil}
|
||||
end
|
||||
|
||||
defp to_list(tuple) when is_tuple(tuple), do: tuple_to_list(tuple)
|
||||
|
||||
@@ -21,7 +21,7 @@ defimpl List.Chars, for: BitString do
|
||||
Returns the given binary converted to a char list.
|
||||
"""
|
||||
def to_char_list(thing) when is_binary(thing) do
|
||||
String.to_char_list!(thing)
|
||||
List.from_char_data!(thing)
|
||||
end
|
||||
|
||||
def to_char_list(thing) do
|
||||
|
||||
@@ -1,156 +0,0 @@
|
||||
defmodule ListDict do
|
||||
@moduledoc """
|
||||
A Dict implementation that works on lists of two-items tuples.
|
||||
|
||||
This dictionary is only recommended for keeping a small amount
|
||||
of values. Other dict alternatives are more viable for keeping
|
||||
any other amount than a handful.
|
||||
|
||||
For more information about the functions and their APIs, please
|
||||
consult the `Dict` module.
|
||||
"""
|
||||
|
||||
@doc """
|
||||
Returns a new `ListDict`, i.e. an empty list.
|
||||
"""
|
||||
def new, do: []
|
||||
|
||||
@doc false
|
||||
def new(pairs) do
|
||||
IO.write :stderr, "ListDict.new/1 is deprecated, please use Enum.into/2 instead\n#{Exception.format_stacktrace}"
|
||||
Enum.to_list pairs
|
||||
end
|
||||
|
||||
@doc false
|
||||
def new(list, transform) when is_function(transform) do
|
||||
IO.write :stderr, "ListDict.new/2 is deprecated, please use Enum.into/3 instead\n#{Exception.format_stacktrace}"
|
||||
Enum.map list, transform
|
||||
end
|
||||
|
||||
def keys(dict) do
|
||||
for { key, _ } <- dict, do: key
|
||||
end
|
||||
|
||||
def values(dict) do
|
||||
for { _, value } <- dict, do: value
|
||||
end
|
||||
|
||||
def size(dict) do
|
||||
length(dict)
|
||||
end
|
||||
|
||||
def has_key?(dict, key)
|
||||
def has_key?([{ key, _ }|_], key), do: true
|
||||
def has_key?([{ _, _ }|t], key), do: has_key?(t, key)
|
||||
def has_key?([], _key), do: false
|
||||
|
||||
def get(dict, key, default \\ nil)
|
||||
def get([{ key, value }|_], key, _default), do: value
|
||||
def get([{ _, _ }|t], key, default), do: get(t, key, default)
|
||||
def get([], _key, default), do: default
|
||||
|
||||
def fetch(dict, key)
|
||||
def fetch([{ key, value }|_], key), do: { :ok, value }
|
||||
def fetch([{ _, _ }|t], key), do: fetch(t, key)
|
||||
def fetch([], _key), do: :error
|
||||
|
||||
def fetch!(dict, key) do
|
||||
case fetch(dict, key) do
|
||||
{ :ok, value } -> value
|
||||
:error -> raise(KeyError, key: key, term: dict)
|
||||
end
|
||||
end
|
||||
|
||||
def pop(dict, key, default \\ nil) do
|
||||
{ get(dict, key, default), delete(dict, key) }
|
||||
end
|
||||
|
||||
def put(dict, key, val) do
|
||||
[{key, val}|delete(dict, key)]
|
||||
end
|
||||
|
||||
def put_new(dict, key, val) do
|
||||
case has_key?(dict, key) do
|
||||
true -> dict
|
||||
false -> [{key, val}|dict]
|
||||
end
|
||||
end
|
||||
|
||||
def delete(dict, key)
|
||||
def delete([{ key, _ }|t], key), do: t
|
||||
def delete([{ _, _ } = h|t], key), do: [h|delete(t, key)]
|
||||
def delete([], _key), do: []
|
||||
|
||||
def merge(dict, enum, callback \\ fn(_k, _v1, v2) -> v2 end) do
|
||||
Enum.reduce enum, dict, fn { k, v2 }, acc ->
|
||||
update(acc, k, v2, fn(v1) -> callback.(k, v1, v2) end)
|
||||
end
|
||||
end
|
||||
|
||||
def split(dict, keys) do
|
||||
acc = { [], [] }
|
||||
|
||||
{take, drop} = Enum.reduce dict, acc, fn({ k, v }, { take, drop }) ->
|
||||
if k in keys do
|
||||
{ [{k, v}|take], drop }
|
||||
else
|
||||
{ take, [{k, v}|drop] }
|
||||
end
|
||||
end
|
||||
|
||||
{Enum.reverse(take), Enum.reverse(drop)}
|
||||
end
|
||||
|
||||
def take(dict, keys) do
|
||||
for { k, _ } = tuple <- dict, k in keys, do: tuple
|
||||
end
|
||||
|
||||
def drop(dict, keys) do
|
||||
for { k, _ } = tuple <- dict, not k in keys, do: tuple
|
||||
end
|
||||
|
||||
def update!(list, key, fun) do
|
||||
update!(list, key, fun, list)
|
||||
end
|
||||
|
||||
defp update!([{key, value}|list], key, fun, _dict) do
|
||||
[{key, fun.(value)}|delete(list, key)]
|
||||
end
|
||||
|
||||
defp update!([{_, _} = e|list], key, fun, dict) do
|
||||
[e|update!(list, key, fun, dict)]
|
||||
end
|
||||
|
||||
defp update!([], key, _fun, dict) do
|
||||
raise(KeyError, key: key, term: dict)
|
||||
end
|
||||
|
||||
def update([{key, value}|dict], key, _initial, fun) do
|
||||
[{key, fun.(value)}|delete(dict, key)]
|
||||
end
|
||||
|
||||
def update([{_, _} = e|dict], key, initial, fun) do
|
||||
[e|update(dict, key, initial, fun)]
|
||||
end
|
||||
|
||||
def update([], key, initial, _fun) do
|
||||
[{key, initial}]
|
||||
end
|
||||
|
||||
def empty(_dict) do
|
||||
IO.write :stderr, "ListDict.empty/1 is deprecated, please use Collectable.empty/1 instead\n#{Exception.format_stacktrace}"
|
||||
[]
|
||||
end
|
||||
|
||||
def equal?(dict, other) do
|
||||
:lists.keysort(1, dict) === :lists.keysort(1, other)
|
||||
end
|
||||
|
||||
@doc false
|
||||
def reduce(_, { :halt, acc }, _fun), do: { :halted, acc }
|
||||
def reduce(list, { :suspend, acc }, fun), do: { :suspended, acc, &reduce(list, &1, fun) }
|
||||
def reduce([], { :cont, acc }, _fun), do: { :done, acc }
|
||||
def reduce([{_,_}=h|t], { :cont, acc }, fun), do: reduce(t, fun.(h, acc), fun)
|
||||
|
||||
def to_list(dict), do: dict
|
||||
end
|
||||
+121
-121
@@ -6,10 +6,10 @@ defmodule Macro do
|
||||
"""
|
||||
|
||||
@typedoc "Abstract Syntax Tree (AST)"
|
||||
@type t :: expr | { t, t } | atom | number | binary | pid | fun | [t]
|
||||
@type t :: expr | {t, t} | atom | number | binary | pid | fun | [t]
|
||||
|
||||
@typedoc "Expr node (remaining ones are literals)"
|
||||
@type expr :: { expr | atom, Keyword.t, atom | [t] }
|
||||
@type expr :: {expr | atom, Keyword.t, atom | [t]}
|
||||
|
||||
@binary_ops [:===, :!==,
|
||||
:==, :!=, :<=, :>=,
|
||||
@@ -27,7 +27,7 @@ defmodule Macro do
|
||||
@doc false
|
||||
defmacro unary_ops, do: @unary_ops
|
||||
|
||||
@spec binary_op_props(atom) :: { :left | :right, precedence :: integer }
|
||||
@spec binary_op_props(atom) :: {:left | :right, precedence :: integer}
|
||||
defp binary_op_props(o) do
|
||||
case o do
|
||||
o when o in [:<-, :inlist, :inbits, :\\, :::] -> {:left, 40}
|
||||
@@ -53,31 +53,30 @@ defmodule Macro do
|
||||
Raises if the pipeline is ill-formed.
|
||||
"""
|
||||
@spec unpipe(Macro.t) :: [Macro.t]
|
||||
def unpipe({ :|> , _, [left, right] }) do
|
||||
[left|unpipe(right)]
|
||||
def unpipe({:|> , _, [left, right]}) do
|
||||
[{left, 0}|unpipe(right)]
|
||||
end
|
||||
|
||||
def unpipe(other) do
|
||||
[other]
|
||||
[{other, 0}]
|
||||
end
|
||||
|
||||
@doc """
|
||||
Pipes `expr` into the `call_expr` as the
|
||||
argument in the given `position`.
|
||||
Pipes `expr` into the `call_args` at the given `position`.
|
||||
"""
|
||||
@spec pipe(Macro.t, Macro.t, integer) :: Macro.t | no_return
|
||||
def pipe(expr, call_args, integer \\ 0)
|
||||
def pipe(expr, call_args, position)
|
||||
|
||||
def pipe(expr, { :&, _, _ } = call_args, _integer) do
|
||||
def pipe(expr, {:&, _, _} = call_args, _integer) do
|
||||
raise ArgumentError, message: "cannot pipe #{to_string expr} into #{to_string call_args}"
|
||||
end
|
||||
|
||||
def pipe(expr, { call, line, atom }, integer) when is_atom(atom) do
|
||||
{ call, line, List.insert_at([], integer, expr) }
|
||||
def pipe(expr, {call, line, atom}, integer) when is_atom(atom) do
|
||||
{call, line, List.insert_at([], integer, expr)}
|
||||
end
|
||||
|
||||
def pipe(expr, { call, line, args }, integer) when is_list(args) do
|
||||
{ call, line, List.insert_at(args, integer, expr) }
|
||||
def pipe(expr, {call, line, args}, integer) when is_list(args) do
|
||||
{call, line, List.insert_at(args, integer, expr)}
|
||||
end
|
||||
|
||||
def pipe(expr, call_args, _integer) do
|
||||
@@ -104,12 +103,12 @@ defmodule Macro do
|
||||
@spec update_meta(t, (Keyword.t -> Keyword.t)) :: t
|
||||
def update_meta(quoted, fun)
|
||||
|
||||
def update_meta({ left, meta, right }, fun) when is_list(meta) do
|
||||
{ update_meta(left, fun), fun.(meta), update_meta(right, fun) }
|
||||
def update_meta({left, meta, right}, fun) when is_list(meta) do
|
||||
{update_meta(left, fun), fun.(meta), update_meta(right, fun)}
|
||||
end
|
||||
|
||||
def update_meta({ left, right }, fun) do
|
||||
{ update_meta(left, fun), update_meta(right, fun) }
|
||||
def update_meta({left, right}, fun) do
|
||||
{update_meta(left, fun), update_meta(right, fun)}
|
||||
end
|
||||
|
||||
def update_meta(list, fun) when is_list(list) do
|
||||
@@ -129,30 +128,30 @@ defmodule Macro do
|
||||
## Examples
|
||||
|
||||
iex> Macro.decompose_call(quote do: foo)
|
||||
{ :foo, [] }
|
||||
{:foo, []}
|
||||
|
||||
iex> Macro.decompose_call(quote do: foo())
|
||||
{ :foo, [] }
|
||||
{:foo, []}
|
||||
|
||||
iex> Macro.decompose_call(quote do: foo(1, 2, 3))
|
||||
{ :foo, [1, 2, 3] }
|
||||
{:foo, [1, 2, 3]}
|
||||
|
||||
iex> Macro.decompose_call(quote do: Elixir.M.foo(1, 2, 3))
|
||||
{ { :__aliases__, [], [:Elixir, :M] }, :foo, [1, 2, 3] }
|
||||
{{:__aliases__, [], [:Elixir, :M]}, :foo, [1, 2, 3]}
|
||||
|
||||
iex> Macro.decompose_call(quote do: 42)
|
||||
:error
|
||||
|
||||
"""
|
||||
@spec decompose_call(Macro.t) :: { atom, [Macro.t] } | { Macro.t, atom, [Macro.t] } | :error
|
||||
def decompose_call({ { :., _, [remote, function] }, _, args }) when is_tuple(remote) or is_atom(remote),
|
||||
do: { remote, function, args }
|
||||
@spec decompose_call(Macro.t) :: {atom, [Macro.t]} | {Macro.t, atom, [Macro.t]} | :error
|
||||
def decompose_call({{:., _, [remote, function]}, _, args}) when is_tuple(remote) or is_atom(remote),
|
||||
do: {remote, function, args}
|
||||
|
||||
def decompose_call({ name, _, args }) when is_atom(name) and is_atom(args),
|
||||
do: { name, [] }
|
||||
def decompose_call({name, _, args}) when is_atom(name) and is_atom(args),
|
||||
do: {name, []}
|
||||
|
||||
def decompose_call({ name, _, args }) when is_atom(name) and is_list(args),
|
||||
do: { name, args }
|
||||
def decompose_call({name, _, args}) when is_atom(name) and is_list(args),
|
||||
do: {name, args}
|
||||
|
||||
def decompose_call(_),
|
||||
do: :error
|
||||
@@ -170,10 +169,10 @@ defmodule Macro do
|
||||
iex> Macro.escape(:foo)
|
||||
:foo
|
||||
|
||||
iex> Macro.escape({ :a, :b, :c })
|
||||
{ :{}, [], [:a, :b, :c] }
|
||||
iex> Macro.escape({:a, :b, :c})
|
||||
{:{}, [], [:a, :b, :c]}
|
||||
|
||||
iex> Macro.escape({ :unquote, [], [1] }, unquote: true)
|
||||
iex> Macro.escape({:unquote, [], [1]}, unquote: true)
|
||||
1
|
||||
|
||||
"""
|
||||
@@ -300,27 +299,27 @@ defmodule Macro do
|
||||
def to_string(tree, fun \\ fn(_ast, string) -> string end)
|
||||
|
||||
# Variables
|
||||
def to_string({ var, _, atom } = ast, fun) when is_atom(atom) do
|
||||
def to_string({var, _, atom} = ast, fun) when is_atom(atom) do
|
||||
fun.(ast, atom_to_binary(var))
|
||||
end
|
||||
|
||||
# Aliases
|
||||
def to_string({ :__aliases__, _, refs } = ast, fun) do
|
||||
def to_string({:__aliases__, _, refs} = ast, fun) do
|
||||
fun.(ast, Enum.map_join(refs, ".", &call_to_string(&1, fun)))
|
||||
end
|
||||
|
||||
# Blocks
|
||||
def to_string({ :__block__, _, [expr] } = ast, fun) do
|
||||
def to_string({:__block__, _, [expr]} = ast, fun) do
|
||||
fun.(ast, to_string(expr, fun))
|
||||
end
|
||||
|
||||
def to_string({ :__block__, _, _ } = ast, fun) do
|
||||
def to_string({:__block__, _, _} = ast, fun) do
|
||||
block = adjust_new_lines block_to_string(ast, fun), "\n "
|
||||
fun.(ast, "(\n " <> block <> "\n)")
|
||||
end
|
||||
|
||||
# Bits containers
|
||||
def to_string({ :<<>>, _, args } = ast, fun) do
|
||||
def to_string({:<<>>, _, args} = ast, fun) do
|
||||
fun.(ast, case Enum.map_join(args, ", ", &to_string(&1, fun)) do
|
||||
"<" <> rest -> "<< <" <> rest <> " >>"
|
||||
rest -> "<<" <> rest <> ">>"
|
||||
@@ -328,45 +327,45 @@ defmodule Macro do
|
||||
end
|
||||
|
||||
# Tuple containers
|
||||
def to_string({ :{}, _, args } = ast, fun) do
|
||||
def to_string({:{}, _, args} = ast, fun) do
|
||||
tuple = "{" <> Enum.map_join(args, ", ", &to_string(&1, fun)) <> "}"
|
||||
fun.(ast, tuple)
|
||||
end
|
||||
|
||||
# Map containers
|
||||
def to_string({ :%{}, _, args } = ast, fun) do
|
||||
def to_string({:%{}, _, args} = ast, fun) do
|
||||
map = "%{" <> map_to_string(args, fun) <> "}"
|
||||
fun.(ast, map)
|
||||
end
|
||||
|
||||
def to_string({ :%, _, [structname, map] } = ast, fun) do
|
||||
{ :%{}, _, args } = map
|
||||
def to_string({:%, _, [structname, map]} = ast, fun) do
|
||||
{:%{}, _, args} = map
|
||||
struct = "%" <> to_string(structname, fun) <> "{" <> map_to_string(args, fun) <> "}"
|
||||
fun.(ast, struct)
|
||||
end
|
||||
|
||||
# Fn keyword
|
||||
def to_string({ :fn, _, [{ :->, _, [_, tuple] }] = arrow } = ast, fun)
|
||||
def to_string({:fn, _, [{:->, _, [_, tuple]}] = arrow} = ast, fun)
|
||||
when not is_tuple(tuple) or elem(tuple, 0) != :__block__ do
|
||||
fun.(ast, "fn " <> arrow_to_string(arrow, fun) <> " end")
|
||||
end
|
||||
|
||||
def to_string({ :fn, _, [{ :->, _, _ }] = block } = ast, fun) do
|
||||
def to_string({:fn, _, [{:->, _, _}] = block} = ast, fun) do
|
||||
fun.(ast, "fn " <> block_to_string(block, fun) <> "\nend")
|
||||
end
|
||||
|
||||
def to_string({ :fn, _, block } = ast, fun) do
|
||||
def to_string({:fn, _, block} = ast, fun) do
|
||||
block = adjust_new_lines block_to_string(block, fun), "\n "
|
||||
fun.(ast, "fn\n " <> block <> "\nend")
|
||||
end
|
||||
|
||||
# left -> right
|
||||
def to_string([{ :->, _, _ }|_] = ast, fun) do
|
||||
def to_string([{:->, _, _}|_] = ast, fun) do
|
||||
fun.(ast, "(" <> arrow_to_string(ast, fun, true) <> ")")
|
||||
end
|
||||
|
||||
# left when right
|
||||
def to_string({ :when, _, [left, right] } = ast, fun) do
|
||||
def to_string({:when, _, [left, right]} = ast, fun) do
|
||||
if right != [] and Keyword.keyword?(right) do
|
||||
right = kw_list_to_string(right, fun)
|
||||
else
|
||||
@@ -377,38 +376,38 @@ defmodule Macro do
|
||||
end
|
||||
|
||||
# Binary ops
|
||||
def to_string({ op, _, [left, right] } = ast, fun) when op in unquote(@binary_ops) do
|
||||
def to_string({op, _, [left, right]} = ast, fun) when op in unquote(@binary_ops) do
|
||||
fun.(ast, op_to_string(left, fun, op, :left) <> " #{op} " <> op_to_string(right, fun, op, :right))
|
||||
end
|
||||
|
||||
# Splat when
|
||||
def to_string({ :when, _, args } = ast, fun) do
|
||||
{ left, right } = :elixir_utils.split_last(args)
|
||||
def to_string({:when, _, args} = ast, fun) do
|
||||
{left, right} = :elixir_utils.split_last(args)
|
||||
fun.(ast, "(" <> Enum.map_join(left, ", ", &to_string(&1, fun)) <> ") when " <> to_string(right, fun))
|
||||
end
|
||||
|
||||
# Unary ops
|
||||
def to_string({ unary, _, [{ binary, _, [_, _] } = arg] } = ast, fun)
|
||||
def to_string({unary, _, [{binary, _, [_, _]} = arg]} = ast, fun)
|
||||
when unary in unquote(@unary_ops) and binary in unquote(@binary_ops) do
|
||||
fun.(ast, atom_to_binary(unary) <> "(" <> to_string(arg, fun) <> ")")
|
||||
end
|
||||
|
||||
def to_string({ :not, _, [arg] } = ast, fun) do
|
||||
def to_string({:not, _, [arg]} = ast, fun) do
|
||||
fun.(ast, "not " <> to_string(arg, fun))
|
||||
end
|
||||
|
||||
def to_string({ op, _, [arg] } = ast, fun) when op in unquote(@unary_ops) do
|
||||
def to_string({op, _, [arg]} = ast, fun) when op in unquote(@unary_ops) do
|
||||
fun.(ast, atom_to_binary(op) <> to_string(arg, fun))
|
||||
end
|
||||
|
||||
# Access
|
||||
def to_string({ { :., _, [Kernel, :access] }, _, [left, right] } = ast, fun) do
|
||||
def to_string({{:., _, [Kernel, :access]}, _, [left, right]} = ast, fun) do
|
||||
fun.(ast, to_string(left, fun) <> to_string(right, fun))
|
||||
end
|
||||
|
||||
# All other calls
|
||||
def to_string({ target, _, args } = ast, fun) when is_list(args) do
|
||||
{ list, last } = :elixir_utils.split_last(args)
|
||||
def to_string({target, _, args} = ast, fun) when is_list(args) do
|
||||
{list, last} = :elixir_utils.split_last(args)
|
||||
fun.(ast, case kw_blocks?(last) do
|
||||
true -> call_to_string_with_args(target, list, fun) <> kw_blocks_to_string(last, fun)
|
||||
false -> call_to_string_with_args(target, args, fun)
|
||||
@@ -416,8 +415,8 @@ defmodule Macro do
|
||||
end
|
||||
|
||||
# Two-item tuples
|
||||
def to_string({ left, right }, fun) do
|
||||
to_string({ :{}, [], [left, right] }, fun)
|
||||
def to_string({left, right}, fun) do
|
||||
to_string({:{}, [], [left, right]}, fun)
|
||||
end
|
||||
|
||||
# Lists
|
||||
@@ -426,7 +425,7 @@ defmodule Macro do
|
||||
list == [] ->
|
||||
"[]"
|
||||
:io_lib.printable_list(list) ->
|
||||
"'" <> Inspect.BitString.escape(String.from_char_list!(list), ?') <> "'"
|
||||
"'" <> Inspect.BitString.escape(String.from_char_data!(list), ?') <> "'"
|
||||
Keyword.keyword?(list) ->
|
||||
"[" <> kw_list_to_string(list, fun) <> "]"
|
||||
true ->
|
||||
@@ -449,8 +448,8 @@ defmodule Macro do
|
||||
defp module_to_string(other, fun), do: call_to_string(other, fun)
|
||||
|
||||
defp call_to_string(atom, _fun) when is_atom(atom), do: atom_to_binary(atom)
|
||||
defp call_to_string({ :., _, [arg] }, fun), do: module_to_string(arg, fun) <> "."
|
||||
defp call_to_string({ :., _, [left, right] }, fun), do: module_to_string(left, fun) <> "." <> call_to_string(right, fun)
|
||||
defp call_to_string({:., _, [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
|
||||
@@ -460,7 +459,7 @@ defmodule Macro do
|
||||
end
|
||||
|
||||
defp args_to_string(args, fun) do
|
||||
{ list, last } = :elixir_utils.split_last(args)
|
||||
{list, last} = :elixir_utils.split_last(args)
|
||||
|
||||
if last != [] and Keyword.keyword?(last) do
|
||||
args = Enum.map_join(list, ", ", &to_string(&1, fun))
|
||||
@@ -485,14 +484,14 @@ defmodule Macro do
|
||||
atom_to_binary(key) <> "\n " <> block <> "\n"
|
||||
end
|
||||
|
||||
defp block_to_string([{ :->, _, _ }|_] = block, fun) do
|
||||
Enum.map_join(block, "\n", fn({ :->, _, [left, right] }) ->
|
||||
defp block_to_string([{:->, _, _}|_] = block, fun) do
|
||||
Enum.map_join(block, "\n", fn({:->, _, [left, right]}) ->
|
||||
left = comma_join_or_empty_paren(left, fun, false)
|
||||
left <> "->\n " <> adjust_new_lines block_to_string(right, fun), "\n "
|
||||
end)
|
||||
end
|
||||
|
||||
defp block_to_string({ :__block__, _, exprs }, fun) do
|
||||
defp block_to_string({:__block__, _, exprs}, fun) do
|
||||
Enum.map_join(exprs, "\n", &to_string(&1, fun))
|
||||
end
|
||||
|
||||
@@ -510,13 +509,13 @@ defmodule Macro do
|
||||
end
|
||||
|
||||
defp kw_list_to_string(list, fun) do
|
||||
Enum.map_join(list, ", ", fn { key, value } ->
|
||||
Enum.map_join(list, ", ", fn {key, value} ->
|
||||
atom_to_binary(key) <> ": " <> to_string(value, fun)
|
||||
end)
|
||||
end
|
||||
|
||||
defp map_list_to_string(list, fun) do
|
||||
Enum.map_join(list, ", ", fn { key, value } ->
|
||||
Enum.map_join(list, ", ", fn {key, value} ->
|
||||
to_string(key, fun) <> " => " <> to_string(value, fun)
|
||||
end)
|
||||
end
|
||||
@@ -525,9 +524,9 @@ defmodule Macro do
|
||||
"(" <> to_string(expr, fun) <> ")"
|
||||
end
|
||||
|
||||
defp op_to_string({ op, _, [_, _] } = expr, fun, parent_op, side) when op in unquote(@binary_ops) do
|
||||
{ parent_assoc, parent_prec } = binary_op_props(parent_op)
|
||||
{ _, prec } = binary_op_props(op)
|
||||
defp op_to_string({op, _, [_, _]} = expr, fun, parent_op, side) when op in unquote(@binary_ops) do
|
||||
{parent_assoc, parent_prec} = binary_op_props(parent_op)
|
||||
{_, prec} = binary_op_props(op)
|
||||
cond do
|
||||
parent_prec < prec -> to_string(expr, fun)
|
||||
parent_prec > prec -> parenthise(expr, fun)
|
||||
@@ -544,7 +543,7 @@ defmodule Macro do
|
||||
defp op_to_string(expr, fun, _, _), do: to_string(expr, fun)
|
||||
|
||||
defp arrow_to_string(pairs, fun, paren \\ false) do
|
||||
Enum.map_join(pairs, "; ", fn({ :->, _, [left, right] }) ->
|
||||
Enum.map_join(pairs, "; ", fn({:->, _, [left, right]}) ->
|
||||
left = comma_join_or_empty_paren(left, fun, paren)
|
||||
left <> "-> " <> to_string(right, fun)
|
||||
end)
|
||||
@@ -610,7 +609,7 @@ defmodule Macro do
|
||||
The compilation will fail because `My.Module` when quoted
|
||||
is not an atom, but a syntax tree as follow:
|
||||
|
||||
{:__aliases__, [], [:My, :Module] }
|
||||
{:__aliases__, [], [:My, :Module]}
|
||||
|
||||
That said, we need to expand the aliases node above to an
|
||||
atom, so we can retrieve its length. Expanding the node is
|
||||
@@ -646,11 +645,11 @@ defmodule Macro do
|
||||
elem(do_expand_once(ast, env), 0)
|
||||
end
|
||||
|
||||
defp do_expand_once({ :__aliases__, _, _ } = original, env) do
|
||||
defp do_expand_once({:__aliases__, _, _} = original, env) do
|
||||
case :elixir_aliases.expand(original, env.aliases, env.macro_aliases, env.lexical_tracker) do
|
||||
receiver when is_atom(receiver) ->
|
||||
:elixir_lexical.record_remote(receiver, env.lexical_tracker)
|
||||
{ receiver, true }
|
||||
{receiver, true}
|
||||
aliases ->
|
||||
aliases = for alias <- aliases, do: elem(do_expand_once(alias, env), 0)
|
||||
|
||||
@@ -658,100 +657,100 @@ defmodule Macro do
|
||||
true ->
|
||||
receiver = :elixir_aliases.concat(aliases)
|
||||
:elixir_lexical.record_remote(receiver, env.lexical_tracker)
|
||||
{ receiver, true }
|
||||
{receiver, true}
|
||||
false ->
|
||||
{ original, false }
|
||||
{original, false}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Expand @ calls
|
||||
defp do_expand_once({ :@, _, [{ name, _, args }] } = original, env) when is_atom(args) or args == [] do
|
||||
defp do_expand_once({:@, _, [{name, _, args}]} = original, env) when is_atom(args) or args == [] do
|
||||
case (module = env.module) && Module.open?(module) do
|
||||
true -> { Module.get_attribute(module, name), true }
|
||||
false -> { original, false }
|
||||
true -> {escape(Module.get_attribute(module, name)), true}
|
||||
false -> {original, false}
|
||||
end
|
||||
end
|
||||
|
||||
# Expand pseudo-variables
|
||||
defp do_expand_once({ :__MODULE__, _, atom }, env) when is_atom(atom),
|
||||
do: { env.module, true }
|
||||
defp do_expand_once({ :__DIR__, _, atom }, env) when is_atom(atom),
|
||||
do: { :filename.dirname(env.file), true }
|
||||
defp do_expand_once({ :__ENV__, _, atom }, env) when is_atom(atom),
|
||||
do: { { :{}, [], tuple_to_list(env) }, true }
|
||||
defp do_expand_once({ { :., _, [{ :__ENV__, _, atom }, field] }, _, [] } = original, env) when
|
||||
defp do_expand_once({:__MODULE__, _, atom}, env) when is_atom(atom),
|
||||
do: {env.module, true}
|
||||
defp do_expand_once({:__DIR__, _, atom}, env) when is_atom(atom),
|
||||
do: {:filename.dirname(env.file), true}
|
||||
defp do_expand_once({:__ENV__, _, atom}, env) when is_atom(atom),
|
||||
do: {{:%{}, [], Map.to_list(env)}, true}
|
||||
defp do_expand_once({{:., _, [{:__ENV__, _, atom}, field]}, _, []} = original, env) when
|
||||
is_atom(atom) and is_atom(field) do
|
||||
case :erlang.function_exported(Macro.Env, field, 1) do
|
||||
true -> { apply(env, field, []), true }
|
||||
false -> { original, false }
|
||||
if Map.has_key?(env, field) do
|
||||
{Map.get(env, field), true}
|
||||
else
|
||||
{original, false}
|
||||
end
|
||||
end
|
||||
|
||||
# Expand possible macro import invocation
|
||||
defp do_expand_once({ atom, meta, context } = original, env)
|
||||
defp do_expand_once({atom, meta, context} = original, env)
|
||||
when is_atom(atom) and is_list(meta) and is_atom(context) do
|
||||
if :lists.member({ atom, Keyword.get(meta, :counter, context) }, env.vars) do
|
||||
{ original, false }
|
||||
if :lists.member({atom, Keyword.get(meta, :counter, context)}, env.vars) do
|
||||
{original, false}
|
||||
else
|
||||
case do_expand_once({ atom, meta, [] }, env) do
|
||||
{ _, true } = exp -> exp
|
||||
{ _, false } -> { original, false }
|
||||
case do_expand_once({atom, meta, []}, env) do
|
||||
{_, true} = exp -> exp
|
||||
{_, false} -> {original, false}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defp do_expand_once({ atom, meta, args } = original, env)
|
||||
defp do_expand_once({atom, meta, args} = original, env)
|
||||
when is_atom(atom) and is_list(args) and is_list(meta) do
|
||||
arity = length(args)
|
||||
|
||||
if :elixir_import.special_form(atom, arity) do
|
||||
{ original, false }
|
||||
{original, false}
|
||||
else
|
||||
module = env.module
|
||||
extra = if function_exported?(module, :__info__, 1) do
|
||||
[{ module, module.__info__(:macros) }]
|
||||
[{module, module.__info__(:macros)}]
|
||||
else
|
||||
[]
|
||||
end
|
||||
|
||||
expand = :elixir_dispatch.expand_import(meta, { atom, length(args) }, args,
|
||||
:elixir_env.ex_to_env(env), extra)
|
||||
expand = :elixir_dispatch.expand_import(meta, {atom, length(args)}, args,
|
||||
env, extra)
|
||||
|
||||
case expand do
|
||||
{ :ok, receiver, quoted } ->
|
||||
{:ok, receiver, quoted} ->
|
||||
next = :elixir_counter.next
|
||||
{ :elixir_quote.linify_with_context_counter(0, { receiver, next }, quoted), true }
|
||||
{ :ok, _receiver } ->
|
||||
{ original, false }
|
||||
{:elixir_quote.linify_with_context_counter(0, {receiver, next}, quoted), true}
|
||||
{:ok, _receiver} ->
|
||||
{original, false}
|
||||
:error ->
|
||||
{ original, false }
|
||||
{original, false}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Expand possible macro require invocation
|
||||
defp do_expand_once({ { :., _, [left, right] }, meta, args } = original, env) when is_atom(right) do
|
||||
{ receiver, _ } = do_expand_once(left, env)
|
||||
defp do_expand_once({{:., _, [left, right]}, meta, args} = original, env) when is_atom(right) do
|
||||
{receiver, _} = do_expand_once(left, env)
|
||||
|
||||
case is_atom(receiver) do
|
||||
false -> { original, false }
|
||||
false -> {original, false}
|
||||
true ->
|
||||
expand = :elixir_dispatch.expand_require(meta, receiver, { right, length(args) },
|
||||
args, :elixir_env.ex_to_env(env))
|
||||
expand = :elixir_dispatch.expand_require(meta, receiver, {right, length(args)}, args, env)
|
||||
|
||||
case expand do
|
||||
{ :ok, receiver, quoted } ->
|
||||
{:ok, receiver, quoted} ->
|
||||
next = :elixir_counter.next
|
||||
{ :elixir_quote.linify_with_context_counter(0, { receiver, next }, quoted), true }
|
||||
{:elixir_quote.linify_with_context_counter(0, {receiver, next}, quoted), true}
|
||||
:error ->
|
||||
{ original, false }
|
||||
{original, false}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Anything else is just returned
|
||||
defp do_expand_once(other, _env), do: { other, false }
|
||||
defp do_expand_once(other, _env), do: {other, false}
|
||||
|
||||
@doc """
|
||||
Receives an AST node and expands it until it can no longer
|
||||
@@ -761,14 +760,14 @@ defmodule Macro do
|
||||
`expand_once/2` for more information and exmaples.
|
||||
"""
|
||||
def expand(tree, env) do
|
||||
expand_until({ tree, true }, env)
|
||||
expand_until({tree, true}, env)
|
||||
end
|
||||
|
||||
defp expand_until({ tree, true }, env) do
|
||||
defp expand_until({tree, true}, env) do
|
||||
expand_until(do_expand_once(tree, env), env)
|
||||
end
|
||||
|
||||
defp expand_until({ tree, false }, _env) do
|
||||
defp expand_until({tree, false}, _env) do
|
||||
tree
|
||||
end
|
||||
|
||||
@@ -778,22 +777,23 @@ defmodule Macro do
|
||||
|
||||
Terms are considered safe if they represent data structures and don't actually
|
||||
evaluate code. Returns `:ok` unless a given term is unsafe,
|
||||
which is returned as `{ :unsafe, term }`.
|
||||
which is returned as `{:unsafe, term}`.
|
||||
"""
|
||||
def safe_term(terms) do
|
||||
IO.write :stderr, "Macro.safe_term/1 is deprecated\n#{Exception.format_stacktrace}"
|
||||
do_safe_term(terms) || :ok
|
||||
end
|
||||
|
||||
defp do_safe_term({ local, _, terms }) when local in [:{}, :%{}, :__aliases__] do
|
||||
defp do_safe_term({local, _, terms}) when local in [:{}, :%{}, :__aliases__] do
|
||||
do_safe_term(terms)
|
||||
end
|
||||
|
||||
defp do_safe_term({ unary, _, [term] }) when unary in [:+, :-] do
|
||||
defp do_safe_term({unary, _, [term]}) when unary in [:+, :-] do
|
||||
do_safe_term(term)
|
||||
end
|
||||
|
||||
defp do_safe_term({ left, right }), do: do_safe_term(left) || do_safe_term(right)
|
||||
defp do_safe_term({left, right}), do: do_safe_term(left) || do_safe_term(right)
|
||||
defp do_safe_term(terms) when is_list(terms), do: Enum.find_value(terms, &do_safe_term(&1))
|
||||
defp do_safe_term(terms) when is_tuple(terms), do: { :unsafe, terms }
|
||||
defp do_safe_term(terms) when is_tuple(terms), do: {:unsafe, terms}
|
||||
defp do_safe_term(_), do: nil
|
||||
end
|
||||
|
||||
+56
-33
@@ -1,6 +1,6 @@
|
||||
defmodule Macro.Env do
|
||||
@moduledoc """
|
||||
A record that holds compile time environment information.
|
||||
A struct that holds compile time environment information.
|
||||
|
||||
The current environment can be accessed at any time as
|
||||
`__ENV__`. Inside macros, the caller environment can be
|
||||
@@ -9,7 +9,7 @@ defmodule Macro.Env do
|
||||
* `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
|
||||
* `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
|
||||
@@ -21,76 +21,99 @@ defmodule Macro.Env do
|
||||
* `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 }
|
||||
* `vars` - a list keeping all defined variables as {var, context}
|
||||
* `export_vars` - a list keeping all variables to be exported in a construct (may be nil)
|
||||
* `lexical_tracker` - PID to the lexical tracker which is responsible to keep user info
|
||||
* `local` - the module to expand local functions to
|
||||
"""
|
||||
|
||||
@type name_arity :: { atom, non_neg_integer }
|
||||
@type name_arity :: {atom, non_neg_integer}
|
||||
@type file :: binary
|
||||
@type line :: non_neg_integer
|
||||
@type aliases :: [{ module, module }]
|
||||
@type macro_aliases :: [{ module, { integer, module } }]
|
||||
@type aliases :: [{module, module}]
|
||||
@type macro_aliases :: [{module, {integer, module}}]
|
||||
@type context :: :match | :guard | nil
|
||||
@type requires :: [module]
|
||||
@type functions :: [{ module, [name_arity] }]
|
||||
@type macros :: [{ module, [name_arity] }]
|
||||
@type functions :: [{module, [name_arity]}]
|
||||
@type macros :: [{module, [name_arity]}]
|
||||
@type context_modules :: [module]
|
||||
@type vars :: [{ atom, atom | non_neg_integer }]
|
||||
@type vars :: [{atom, atom | non_neg_integer}]
|
||||
@type export_vars :: vars | nil
|
||||
@type lexical_tracker :: pid
|
||||
@type local :: module | nil
|
||||
|
||||
fields = [:module, :file, :line, :function, :context, :requires, :aliases, :functions,
|
||||
:macros, :macro_aliases, :context_modules, :vars, :export_vars, :lexical_tracker,
|
||||
:local]
|
||||
@type t :: %{__struct__: __MODULE__,
|
||||
module: module,
|
||||
file: file,
|
||||
line: line,
|
||||
function: name_arity | nil,
|
||||
context: context,
|
||||
requires: requires,
|
||||
aliases: aliases,
|
||||
functions: functions,
|
||||
macros: macros,
|
||||
macro_aliases: aliases,
|
||||
context_modules: context_modules,
|
||||
vars: vars,
|
||||
export_vars: export_vars,
|
||||
lexical_tracker: lexical_tracker,
|
||||
local: local}
|
||||
|
||||
types = quote do: [module: module, file: file, line: line,
|
||||
function: name_arity, context: context, requires: requires, aliases: aliases,
|
||||
functions: functions, macros: macros, macro_aliases: aliases,
|
||||
context_modules: context_modules, vars: vars, export_vars: export_vars,
|
||||
lexical_tracker: lexical_tracker, local: local]
|
||||
|
||||
Record.deffunctions(fields, __MODULE__)
|
||||
Record.deftypes(fields, types, __MODULE__)
|
||||
def __struct__ do
|
||||
%{__struct__: __MODULE__,
|
||||
module: nil,
|
||||
file: "nofile",
|
||||
line: 0,
|
||||
function: nil,
|
||||
context: nil,
|
||||
requires: [],
|
||||
aliases: [],
|
||||
functions: [],
|
||||
macros: [],
|
||||
macro_aliases: [],
|
||||
context_modules: [],
|
||||
vars: [],
|
||||
export_vars: nil,
|
||||
lexical_tracker: nil,
|
||||
local: nil}
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns a keyword list containing the file and line
|
||||
information as keys.
|
||||
"""
|
||||
def location(record) do
|
||||
[file: file(record), line: line(record)]
|
||||
def location(%{__struct__: Macro.Env, file: file, line: line}) do
|
||||
[file: file, line: line]
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns whether the compilation environment is currently
|
||||
inside a guard.
|
||||
"""
|
||||
def in_guard?(record), do: context(record) == :guard
|
||||
def in_guard?(%{__struct__: Macro.Env, context: context}), do: context == :guard
|
||||
|
||||
@doc """
|
||||
Returns whether the compilation environment is currently
|
||||
inside a match clause.
|
||||
"""
|
||||
def in_match?(record), do: context(record) == :match
|
||||
def in_match?(%{__struct__: Macro.Env, context: context}), do: context == :match
|
||||
|
||||
@doc """
|
||||
Returns the environment stacktrace.
|
||||
"""
|
||||
def stacktrace(record) do
|
||||
def stacktrace(%{__struct__: Macro.Env} = env) do
|
||||
cond do
|
||||
nil?(record.module) ->
|
||||
[{ :elixir_compiler, :__FILE__, 1, relative_location(record) }]
|
||||
nil?(record.function) ->
|
||||
[{ module(record), :__MODULE__, 0, relative_location(record) }]
|
||||
nil?(env.module) ->
|
||||
[{:elixir_compiler, :__FILE__, 1, relative_location(env)}]
|
||||
nil?(env.function) ->
|
||||
[{env.module, :__MODULE__, 0, relative_location(env)}]
|
||||
true ->
|
||||
{ name, arity } = record.function
|
||||
[{ module(record), name, arity, relative_location(record) }]
|
||||
{name, arity} = env.function
|
||||
[{env.module, name, arity, relative_location(env)}]
|
||||
end
|
||||
end
|
||||
|
||||
defp relative_location(record) do
|
||||
[file: Path.relative_to_cwd(file(record)), line: line(record)]
|
||||
defp relative_location(env) do
|
||||
[file: Path.relative_to_cwd(env.file), line: env.line]
|
||||
end
|
||||
end
|
||||
|
||||
+8
-42
@@ -13,46 +13,19 @@ defmodule Map do
|
||||
|
||||
use Dict.Behaviour
|
||||
|
||||
defdelegate [keys(map), values(map), size(map), merge(map1, map2)], to: :maps
|
||||
defdelegate [keys(map), values(map), size(map), merge(map1, map2), to_list(map)], to: :maps
|
||||
|
||||
@doc """
|
||||
Returns a new empty map.
|
||||
"""
|
||||
def new, do: %{}
|
||||
|
||||
@doc """
|
||||
Creates a new map from the given pairs.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Map.new [{:b, 1}, {:a, 2}]
|
||||
%{a: 2, b: 1}
|
||||
|
||||
"""
|
||||
def new(pairs) do
|
||||
:maps.from_list pairs
|
||||
end
|
||||
|
||||
@doc """
|
||||
Creates a new map from the given pairs
|
||||
via the given transformation function.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Map.new ["a", "b"], fn x -> {x, x} end
|
||||
%{"a" => "a", "b" => "b"}
|
||||
|
||||
"""
|
||||
def new(list, transform) when is_function(transform) do
|
||||
Enum.map(list, transform) |> :maps.from_list
|
||||
end
|
||||
|
||||
def has_key?(map, key), do: :maps.is_key(key, map)
|
||||
|
||||
def fetch(map, key), do: :maps.find(key, map)
|
||||
|
||||
def pop(map, key, default \\ nil) do
|
||||
{ get(map, key, default), delete(map, key) }
|
||||
{get(map, key, default), delete(map, key)}
|
||||
end
|
||||
|
||||
def put(map, key, val) do
|
||||
@@ -75,13 +48,13 @@ defmodule Map do
|
||||
end
|
||||
|
||||
def split(map, keys) do
|
||||
acc = { %{}, %{} }
|
||||
acc = {%{}, %{}}
|
||||
|
||||
:maps.fold fn k, v, { take, drop } ->
|
||||
:maps.fold fn k, v, {take, drop} ->
|
||||
if k in keys do
|
||||
{ put(take, k, v), drop }
|
||||
{put(take, k, v), drop}
|
||||
else
|
||||
{ take, put(drop, k, v) }
|
||||
{take, put(drop, k, v)}
|
||||
end
|
||||
end, acc, map
|
||||
end
|
||||
@@ -90,7 +63,7 @@ defmodule Map do
|
||||
case :maps.find(key, map) do
|
||||
:error ->
|
||||
raise(KeyError, key: key, term: map)
|
||||
{ :ok, val } ->
|
||||
{:ok, val} ->
|
||||
:maps.put(key, fun.(val), map)
|
||||
end
|
||||
end
|
||||
@@ -99,18 +72,11 @@ defmodule Map do
|
||||
case :maps.find(key, map) do
|
||||
:error ->
|
||||
:maps.put(key, initial, map)
|
||||
{ :ok, val } ->
|
||||
{:ok, val} ->
|
||||
:maps.put(key, fun.(val), map)
|
||||
end
|
||||
end
|
||||
|
||||
def empty(_) do
|
||||
IO.write :stderr, "Map.empty/1 is deprecated, please use Collectable.empty/1 instead\n#{Exception.format_stacktrace}"
|
||||
%{}
|
||||
end
|
||||
|
||||
def equal?(map, map), do: true
|
||||
def equal?(_, _), do: false
|
||||
|
||||
def to_list(map), do: :maps.to_list map
|
||||
end
|
||||
|
||||
+76
-70
@@ -1,10 +1,4 @@
|
||||
defmodule Module do
|
||||
defmacrop is_env(env) do
|
||||
quote do
|
||||
is_tuple(unquote(env)) and size(unquote(env)) > 1 and elem(unquote(env), 0) == Macro.Env
|
||||
end
|
||||
end
|
||||
|
||||
@moduledoc ~S'''
|
||||
This module provides many functions to deal with modules during
|
||||
compilation time. It allows a developer to dynamically attach
|
||||
@@ -24,7 +18,7 @@ defmodule Module do
|
||||
|
||||
A hook that will be invoked right after the current module is compiled.
|
||||
|
||||
Accepts a module or a tuple `{ <module>, <function atom> }`. The function
|
||||
Accepts a module or a tuple `{<module>, <function atom>}`. The function
|
||||
must take two arguments: the module environment and its bytecode.
|
||||
When just a module is provided, the function is assumed to be
|
||||
`__after_compile__/2`.
|
||||
@@ -43,7 +37,7 @@ defmodule Module do
|
||||
|
||||
A hook that will be invoked before the module is compiled.
|
||||
|
||||
Accepts a module or a tuple `{ <module>, <function/macro atom> }`. The
|
||||
Accepts a module or a tuple `{<module>, <function/macro atom>}`. The
|
||||
function/macro must take one argument: the module environment. If it's a
|
||||
macro, its returned value will be injected at the end of the module definition
|
||||
before the compilation starts.
|
||||
@@ -94,7 +88,7 @@ defmodule Module do
|
||||
### Example
|
||||
|
||||
defmodule M do
|
||||
@compile { :inline, myfun: 1 }
|
||||
@compile {:inline, myfun: 1}
|
||||
|
||||
def myfun(arg) do
|
||||
to_string(arg)
|
||||
@@ -167,7 +161,7 @@ defmodule Module do
|
||||
A hook that will be invoked when each function or macro in the current
|
||||
module is defined. Useful when annotating functions.
|
||||
|
||||
Accepts a module or a tuple `{ <module>, <function atom> }`. The function
|
||||
Accepts a module or a tuple `{<module>, <function atom>}`. The function
|
||||
must take 6 arguments:
|
||||
|
||||
- the module environment
|
||||
@@ -206,7 +200,7 @@ defmodule Module do
|
||||
end
|
||||
|
||||
defmodule M do
|
||||
@on_definition { H, :on_def }
|
||||
@on_definition {H, :on_def}
|
||||
|
||||
def hello(arg) when is_binary(arg) or is_list(arg) do
|
||||
"Hello" <> to_string(arg)
|
||||
@@ -293,7 +287,7 @@ defmodule Module do
|
||||
* `:docs` - list of all docstrings attached to functions and macros
|
||||
using the `@doc` attribute
|
||||
|
||||
* `:moduledoc` - tuple `{ <line>, <doc> }` where `line` is the line on
|
||||
* `: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
|
||||
|
||||
@@ -344,20 +338,20 @@ defmodule Module do
|
||||
"""
|
||||
def eval_quoted(module, quoted, binding \\ [], opts \\ [])
|
||||
|
||||
def eval_quoted(env, quoted, binding, opts) when is_env(env) do
|
||||
eval_quoted(env.module, quoted, binding, Keyword.merge(env.to_keywords, opts))
|
||||
def eval_quoted(%Macro.Env{} = env, quoted, binding, opts) do
|
||||
eval_quoted(env.module, quoted, binding, Keyword.merge(Map.to_list(env), opts))
|
||||
end
|
||||
|
||||
def eval_quoted(module, quoted, binding, env) when is_env(env) do
|
||||
eval_quoted(module, quoted, binding, env.to_keywords)
|
||||
def eval_quoted(module, quoted, binding, %Macro.Env{} = env) do
|
||||
eval_quoted(module, quoted, binding, Map.to_list(env))
|
||||
end
|
||||
|
||||
def eval_quoted(module, quoted, binding, opts) do
|
||||
assert_not_compiled!(:eval_quoted, module)
|
||||
:elixir_def.reset_last(module)
|
||||
{ value, binding, _env, _scope } =
|
||||
{value, binding, _env, _scope} =
|
||||
:elixir.eval_quoted quoted, binding, Keyword.put(opts, :module, module)
|
||||
{ value, binding }
|
||||
{value, binding}
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -372,7 +366,7 @@ defmodule Module do
|
||||
def world, do: true
|
||||
end
|
||||
|
||||
Module.create(Hello, contents, __ENV__.location)
|
||||
Module.create(Hello, contents, Macro.Env.location(__ENV__))
|
||||
|
||||
Hello.world #=> true
|
||||
|
||||
@@ -391,8 +385,8 @@ defmodule Module do
|
||||
"""
|
||||
def create(module, quoted, opts \\ [])
|
||||
|
||||
def create(module, quoted, env) when is_env(env) do
|
||||
create(module, quoted, env.to_keywords)
|
||||
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
|
||||
@@ -497,7 +491,7 @@ defmodule Module do
|
||||
## Examples
|
||||
|
||||
defmodule MyModule do
|
||||
Module.add_doc(__MODULE__, __ENV__.line + 1, :def, { :version, 0 }, [], "Manually added docs")
|
||||
Module.add_doc(__MODULE__, __ENV__.line + 1, :def, {:version, 0}, [], "Manually added docs")
|
||||
def version, do: 1
|
||||
end
|
||||
|
||||
@@ -505,7 +499,7 @@ defmodule Module do
|
||||
def add_doc(module, line, kind, tuple, signature \\ [], doc)
|
||||
|
||||
def add_doc(_module, _line, kind, _tuple, _signature, doc) when kind in [:defp, :defmacrop, :typep] do
|
||||
if doc, do: { :error, :private_doc }, else: :ok
|
||||
if doc, do: {:error, :private_doc}, else: :ok
|
||||
end
|
||||
|
||||
def add_doc(module, line, kind, tuple, signature, doc) when
|
||||
@@ -513,56 +507,68 @@ defmodule Module do
|
||||
assert_not_compiled!(:add_doc, module)
|
||||
table = docs_table_for(module)
|
||||
|
||||
{ signature, _ } = Enum.map_reduce signature, 1, fn(x, acc) ->
|
||||
{ simplify_signature(x, acc), acc + 1 }
|
||||
{signature, _} = Enum.map_reduce signature, 1, fn(x, acc) ->
|
||||
{simplify_signature(x, acc), acc + 1}
|
||||
end
|
||||
|
||||
case :ets.lookup(table, tuple) do
|
||||
[] ->
|
||||
:ets.insert(table, { tuple, line, kind, signature, doc })
|
||||
:ets.insert(table, {tuple, line, kind, signature, doc})
|
||||
:ok
|
||||
[{ tuple, line, _old_kind, old_sign, old_doc }] ->
|
||||
[{tuple, line, _old_kind, old_sign, old_doc}] ->
|
||||
:ets.insert(table, {
|
||||
tuple,
|
||||
line,
|
||||
kind,
|
||||
merge_signatures(old_sign, signature, 1),
|
||||
if(nil?(doc), do: old_doc, else: doc)
|
||||
})
|
||||
})
|
||||
:ok
|
||||
end
|
||||
end
|
||||
|
||||
# Simplify signatures to be stored in docs
|
||||
|
||||
defp simplify_signature({ :\\, _, [left, right ] }, i) do
|
||||
{ :\\, [], [simplify_signature(left, i), right] }
|
||||
defp simplify_signature({:\\, _, [left, right ]}, i) do
|
||||
{:\\, [], [simplify_signature(left, i), right]}
|
||||
end
|
||||
|
||||
defp simplify_signature({ :%, _, [left, _] }, _i) when is_atom(left) do
|
||||
defp simplify_signature({:%, _, [left, _]}, _i) when is_atom(left) do
|
||||
last = List.last(String.split(atom_to_binary(left), "."))
|
||||
atom = binary_to_atom(String.downcase(last))
|
||||
{ atom, [], nil }
|
||||
atom = binary_to_atom(downcase(last))
|
||||
{atom, [], nil}
|
||||
end
|
||||
|
||||
defp simplify_signature({ :=, _, [_, right] }, i) do
|
||||
defp simplify_signature({:=, _, [_, right]}, i) do
|
||||
simplify_signature(right, i)
|
||||
end
|
||||
|
||||
defp simplify_signature({ var, _, atom }, _i) when is_atom(atom) do
|
||||
defp simplify_signature({var, _, atom}, _i) when is_atom(atom) do
|
||||
case atom_to_binary(var) do
|
||||
"_" <> rest -> { binary_to_atom(rest), [], Elixir }
|
||||
_ -> { var, [], nil }
|
||||
"_" <> rest -> {binary_to_atom(rest), [], Elixir}
|
||||
_ -> {var, [], nil}
|
||||
end
|
||||
end
|
||||
|
||||
defp simplify_signature(other, i) when is_integer(other), do: { :"int#{i}", [], Elixir }
|
||||
defp simplify_signature(other, i) when is_boolean(other), do: { :"bool#{i}", [], Elixir }
|
||||
defp simplify_signature(other, i) when is_atom(other), do: { :"atom#{i}", [], Elixir }
|
||||
defp simplify_signature(other, i) when is_list(other), do: { :"list#{i}", [], Elixir }
|
||||
defp simplify_signature(other, i) when is_float(other), do: { :"float#{i}", [], Elixir }
|
||||
defp simplify_signature(other, i) when is_binary(other), do: { :"binary#{i}", [], Elixir }
|
||||
defp simplify_signature(_, i), do: { :"arg#{i}", [], Elixir }
|
||||
defp simplify_signature(other, i) when is_integer(other), do: {:"int#{i}", [], Elixir}
|
||||
defp simplify_signature(other, i) when is_boolean(other), do: {:"bool#{i}", [], Elixir}
|
||||
defp simplify_signature(other, i) when is_atom(other), do: {:"atom#{i}", [], Elixir}
|
||||
defp simplify_signature(other, i) when is_list(other), do: {:"list#{i}", [], Elixir}
|
||||
defp simplify_signature(other, i) when is_float(other), do: {:"float#{i}", [], Elixir}
|
||||
defp simplify_signature(other, i) when is_binary(other), do: {:"binary#{i}", [], Elixir}
|
||||
defp simplify_signature(_, i), do: {:"arg#{i}", [], Elixir}
|
||||
|
||||
defp downcase(<<c :: utf8, rest :: binary>>) when c >= ?A and c <= ?Z do
|
||||
<<c + 32 :: utf8, downcase(rest) :: binary>>
|
||||
end
|
||||
|
||||
defp downcase(<<c, rest :: binary>>) do
|
||||
<<c, downcase(rest) :: binary>>
|
||||
end
|
||||
|
||||
defp downcase(<<>>) do
|
||||
<<>>
|
||||
end
|
||||
|
||||
# Merge
|
||||
|
||||
@@ -574,23 +580,23 @@ defmodule Module do
|
||||
[]
|
||||
end
|
||||
|
||||
defp merge_signature({ :\\, line, [left, right] }, newer, i) do
|
||||
{ :\\, line, [merge_signature(left, newer, i), right] }
|
||||
defp merge_signature({:\\, line, [left, right]}, newer, i) do
|
||||
{:\\, line, [merge_signature(left, newer, i), right]}
|
||||
end
|
||||
|
||||
defp merge_signature(older, { :\\, _, [left, _] }, i) do
|
||||
defp merge_signature(older, {:\\, _, [left, _]}, i) do
|
||||
merge_signature(older, left, i)
|
||||
end
|
||||
|
||||
# The older signature, when given, always have higher precedence
|
||||
defp merge_signature({ _, _, nil } = older, _newer, _), do: older
|
||||
defp merge_signature(_older, { _, _, nil } = newer, _), do: newer
|
||||
defp merge_signature({_, _, nil} = older, _newer, _), do: older
|
||||
defp merge_signature(_older, {_, _, nil} = newer, _), do: newer
|
||||
|
||||
# Both are a guess, so check if they are the same guess
|
||||
defp merge_signature({ var, _, _ } = older, { var, _, _ }, _), do: older
|
||||
defp merge_signature({var, _, _} = older, {var, _, _}, _), do: older
|
||||
|
||||
# Otherwise, returns a generic guess
|
||||
defp merge_signature({ _, line, _ }, _newer, i), do: { :"arg#{i}", line, Elixir }
|
||||
defp merge_signature({_, line, _}, _newer, i), do: {:"arg#{i}", line, Elixir}
|
||||
|
||||
@doc """
|
||||
Checks if the module defines the given function or macro.
|
||||
@@ -599,9 +605,9 @@ defmodule Module do
|
||||
## Examples
|
||||
|
||||
defmodule Example do
|
||||
Module.defines? __MODULE__, { :version, 0 } #=> false
|
||||
Module.defines? __MODULE__, {:version, 0} #=> false
|
||||
def version, do: 1
|
||||
Module.defines? __MODULE__, { :version, 0 } #=> true
|
||||
Module.defines? __MODULE__, {:version, 0} #=> true
|
||||
end
|
||||
|
||||
"""
|
||||
@@ -619,9 +625,9 @@ defmodule Module do
|
||||
## Examples
|
||||
|
||||
defmodule Example do
|
||||
Module.defines? __MODULE__, { :version, 0 }, :defp #=> false
|
||||
Module.defines? __MODULE__, {:version, 0}, :defp #=> false
|
||||
def version, do: 1
|
||||
Module.defines? __MODULE__, { :version, 0 }, :defp #=> false
|
||||
Module.defines? __MODULE__, {:version, 0}, :defp #=> false
|
||||
end
|
||||
|
||||
"""
|
||||
@@ -629,7 +635,7 @@ defmodule Module do
|
||||
assert_not_compiled!(:defines?, module)
|
||||
table = function_table_for(module)
|
||||
case :ets.lookup(table, tuple) do
|
||||
[{ _, ^kind, _, _, _, _, _ }] -> true
|
||||
[{_, ^kind, _, _, _, _, _}] -> true
|
||||
_ -> false
|
||||
end
|
||||
end
|
||||
@@ -648,7 +654,7 @@ defmodule Module do
|
||||
def definitions_in(module) do
|
||||
assert_not_compiled!(:definitions_in, module)
|
||||
table = function_table_for(module)
|
||||
for { tuple, _, _, _, _, _, _ } <- :ets.tab2list(table), do: tuple
|
||||
for {tuple, _, _, _, _, _, _} <- :ets.tab2list(table), do: tuple
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -667,7 +673,7 @@ defmodule Module do
|
||||
def definitions_in(module, kind) do
|
||||
assert_not_compiled!(:definitions_in, module)
|
||||
table = function_table_for(module)
|
||||
for { tuple, stored_kind, _, _, _, _, _ } <- :ets.tab2list(table), stored_kind == kind, do: tuple
|
||||
for {tuple, stored_kind, _, _, _, _, _} <- :ets.tab2list(table), stored_kind == kind, do: tuple
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -682,7 +688,7 @@ defmodule Module do
|
||||
for tuple <- tuples do
|
||||
case :elixir_def.lookup_definition(module, tuple) do
|
||||
false ->
|
||||
{ name, arity } = tuple
|
||||
{name, arity} = tuple
|
||||
raise "Cannot make function #{name}/#{arity} overridable because it was not defined"
|
||||
clause ->
|
||||
:elixir_def.delete_definition(module, tuple)
|
||||
@@ -694,9 +700,9 @@ defmodule Module do
|
||||
end
|
||||
|
||||
old = get_attribute(module, :__overridable)
|
||||
merged = :orddict.update(tuple, fn({ count, _, _, _ }) ->
|
||||
{ count + 1, clause, neighbours, false }
|
||||
end, { 1, clause, neighbours, false }, old)
|
||||
merged = :orddict.update(tuple, fn({count, _, _, _}) ->
|
||||
{count + 1, clause, neighbours, false}
|
||||
end, {1, clause, neighbours, false}, old)
|
||||
|
||||
put_attribute(module, :__overridable, merged)
|
||||
end
|
||||
@@ -738,7 +744,7 @@ defmodule Module do
|
||||
value
|
||||
end
|
||||
|
||||
:ets.insert(table, { key, new })
|
||||
:ets.insert(table, {key, new})
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -860,12 +866,12 @@ defmodule Module do
|
||||
|
||||
if Keyword.get(opts, :persist) do
|
||||
old = :ets.lookup_element(table, :__persisted_attributes, 2)
|
||||
:ets.insert(table, { :__persisted_attributes, [new|old] })
|
||||
:ets.insert(table, {:__persisted_attributes, [new|old]})
|
||||
end
|
||||
|
||||
if Keyword.get(opts, :accumulate) do
|
||||
old = :ets.lookup_element(table, :__acc_attributes, 2)
|
||||
:ets.insert(table, { :__acc_attributes, [new|old] })
|
||||
:ets.insert(table, {:__acc_attributes, [new|old]})
|
||||
end
|
||||
end
|
||||
|
||||
@@ -889,13 +895,13 @@ defmodule Module do
|
||||
module = env.module
|
||||
line = env.line
|
||||
arity = length(args)
|
||||
pair = { name, arity }
|
||||
pair = {name, arity}
|
||||
doc = get_attribute(module, :doc)
|
||||
|
||||
case add_doc(module, line, kind, pair, args, doc) do
|
||||
:ok ->
|
||||
:ok
|
||||
{ :error, :private_doc } ->
|
||||
{:error, :private_doc} ->
|
||||
:elixir_errors.warn line, env.file, "function #{name}/#{arity} is private, @doc's are always discarded for private functions\n"
|
||||
end
|
||||
|
||||
@@ -915,13 +921,13 @@ defmodule Module do
|
||||
[] -> [value]
|
||||
end
|
||||
|
||||
:ets.insert(table, { key, new })
|
||||
:ets.insert(table, {key, new})
|
||||
end
|
||||
|
||||
## Helpers
|
||||
|
||||
defp normalize_attribute(:on_load, atom) when is_atom(atom) do
|
||||
{ atom, 0 }
|
||||
{atom, 0}
|
||||
end
|
||||
|
||||
defp normalize_attribute(:behaviour, atom) when is_atom(atom) do
|
||||
@@ -935,7 +941,7 @@ defmodule Module do
|
||||
|
||||
defp normalize_attribute(key, atom) when is_atom(atom) and
|
||||
key in [:before_compile, :after_compile, :on_definition] do
|
||||
{ atom, :"__#{key}__" }
|
||||
{atom, :"__#{key}__"}
|
||||
end
|
||||
|
||||
defp normalize_attribute(key, _value) when key in [:type, :typep, :export_type, :opaque, :callback] do
|
||||
|
||||
@@ -12,21 +12,21 @@
|
||||
# We also have can the following vertices:
|
||||
#
|
||||
# * `Module` - a module that was invoked via an import
|
||||
# * `{ name, arity }` - a local function/arity pair
|
||||
# * `{ :import, name, arity }` - an invoked function/arity import
|
||||
# * `{name, arity}` - a local function/arity pair
|
||||
# * `{:import, name, arity}` - an invoked function/arity import
|
||||
#
|
||||
# Each of those vertices can associate to other vertices
|
||||
# as described below:
|
||||
#
|
||||
# * `Module`
|
||||
# * in neighbours: `{ :import, name, arity }`
|
||||
# * in neighbours: `{:import, name, arity}`
|
||||
#
|
||||
# * `{ name, arity }`
|
||||
# * in neighbours: `:local`, `{ name, arity }`
|
||||
# * out neighbours: `{ :import, name, arity }`
|
||||
# * `{name, arity}`
|
||||
# * in neighbours: `:local`, `{name, arity}`
|
||||
# * out neighbours: `{:import, name, arity}`
|
||||
#
|
||||
# * `{ :import, name, arity }`
|
||||
# * in neighbours: `{ name, arity }`
|
||||
# * `{:import, name, arity}`
|
||||
# * in neighbours: `{name, arity}`
|
||||
# * out neighbours: `Module`
|
||||
#
|
||||
# Note that since this is required for bootstrap, we can't use
|
||||
@@ -39,21 +39,21 @@ defmodule Module.LocalsTracker do
|
||||
|
||||
@type ref :: pid | module
|
||||
@type name :: atom
|
||||
@type name_arity :: { name, arity }
|
||||
@type name_arity :: {name, arity}
|
||||
|
||||
@type local :: { name, arity }
|
||||
@type import :: { :import, name, arity }
|
||||
@type local :: {name, arity}
|
||||
@type import :: {:import, name, arity}
|
||||
|
||||
# Public API
|
||||
|
||||
@doc """
|
||||
Returns all imported modules that had the given
|
||||
`{ name, arity }` invoked.
|
||||
`{name, arity}` invoked.
|
||||
"""
|
||||
@spec imports_with_dispatch(ref, name_arity) :: [module]
|
||||
def imports_with_dispatch(ref, { name, arity }) do
|
||||
def imports_with_dispatch(ref, {name, arity}) do
|
||||
d = :gen_server.call(to_pid(ref), :digraph, @timeout)
|
||||
:digraph.out_neighbours(d, { :import, name, arity })
|
||||
:digraph.out_neighbours(d, {:import, name, arity})
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -71,7 +71,7 @@ defmodule Module.LocalsTracker do
|
||||
|
||||
defp reduce_reachable(d, vertex, vertices) do
|
||||
neighbours = :digraph.out_neighbours(d, vertex)
|
||||
neighbours = (for { _, _ } = t <- neighbours, do: t) |> :ordsets.from_list
|
||||
neighbours = (for {_, _} = t <- neighbours, do: t) |> :ordsets.from_list
|
||||
remaining = :ordsets.subtract(neighbours, vertices)
|
||||
vertices = :ordsets.union(neighbours, vertices)
|
||||
:lists.foldl(&reduce_reachable(d, &1, &2), vertices, remaining)
|
||||
@@ -80,7 +80,7 @@ defmodule Module.LocalsTracker do
|
||||
defp to_pid(pid) when is_pid(pid), do: pid
|
||||
defp to_pid(mod) when is_atom(mod) do
|
||||
table = :elixir_module.data_table(mod)
|
||||
[{ _, val }] = :ets.lookup(table, :__locals_tracker)
|
||||
[{_, val}] = :ets.lookup(table, :__locals_tracker)
|
||||
val
|
||||
end
|
||||
|
||||
@@ -89,7 +89,7 @@ defmodule Module.LocalsTracker do
|
||||
# Starts the tracker and returns its pid.
|
||||
@doc false
|
||||
def start_link do
|
||||
{ :ok, pid } = :gen_server.start_link(__MODULE__, [], [])
|
||||
{:ok, pid} = :gen_server.start_link(__MODULE__, [], [])
|
||||
pid
|
||||
end
|
||||
|
||||
@@ -99,36 +99,36 @@ defmodule Module.LocalsTracker do
|
||||
# a call is made to.
|
||||
@doc false
|
||||
def add_definition(pid, kind, tuple) when kind in [:def, :defp, :defmacro, :defmacrop] do
|
||||
:gen_server.cast(pid, { :add_definition, kind, tuple })
|
||||
:gen_server.cast(pid, {:add_definition, kind, tuple})
|
||||
end
|
||||
|
||||
# Adds and tracks defaults for a definition into the tracker.
|
||||
@doc false
|
||||
def add_defaults(pid, kind, tuple, defaults) when kind in [:def, :defp, :defmacro, :defmacrop] do
|
||||
:gen_server.cast(pid, { :add_defaults, kind, tuple, defaults })
|
||||
:gen_server.cast(pid, {:add_defaults, kind, tuple, defaults})
|
||||
end
|
||||
|
||||
# Adds a local dispatch to the given target.
|
||||
def add_local(pid, to) when is_tuple(to) do
|
||||
:gen_server.cast(pid, { :add_local, :local, to })
|
||||
:gen_server.cast(pid, {:add_local, :local, to})
|
||||
end
|
||||
|
||||
# Adds a local dispatch from-to the given target.
|
||||
@doc false
|
||||
def add_local(pid, from, to) when is_tuple(from) and is_tuple(to) do
|
||||
:gen_server.cast(pid, { :add_local, from, to })
|
||||
:gen_server.cast(pid, {:add_local, from, to})
|
||||
end
|
||||
|
||||
# Adds a import dispatch to the given target.
|
||||
@doc false
|
||||
def add_import(pid, function, module, target) when is_atom(module) and is_tuple(target) do
|
||||
:gen_server.cast(pid, { :add_import, function, module, target })
|
||||
:gen_server.cast(pid, {:add_import, function, module, target})
|
||||
end
|
||||
|
||||
# Yanks a local node. Returns its in and out vertices in a tuple.
|
||||
@doc false
|
||||
def yank(pid, local) do
|
||||
:gen_server.call(to_pid(pid), { :yank, local }, @timeout)
|
||||
:gen_server.call(to_pid(pid), {:yank, local}, @timeout)
|
||||
end
|
||||
|
||||
# Reattach a previously yanked node
|
||||
@@ -136,7 +136,7 @@ defmodule Module.LocalsTracker do
|
||||
def reattach(pid, kind, tuple, neighbours) do
|
||||
pid = to_pid(pid)
|
||||
add_definition(pid, kind, tuple)
|
||||
:gen_server.cast(pid, { :reattach, tuple, neighbours })
|
||||
:gen_server.cast(pid, {:reattach, tuple, neighbours})
|
||||
end
|
||||
|
||||
# Collecting all conflicting imports with the given functions
|
||||
@@ -144,11 +144,11 @@ defmodule Module.LocalsTracker do
|
||||
def collect_imports_conflicts(pid, all_defined) do
|
||||
d = :gen_server.call(pid, :digraph, @timeout)
|
||||
|
||||
for { name, arity } <- all_defined,
|
||||
:digraph.in_neighbours(d, { :import, name, arity }) != [],
|
||||
n = :digraph.out_neighbours(d, { :import, name, arity }),
|
||||
for {name, arity} <- all_defined,
|
||||
:digraph.in_neighbours(d, {:import, name, arity}) != [],
|
||||
n = :digraph.out_neighbours(d, {:import, name, arity}),
|
||||
n != [] do
|
||||
{ n, name, arity }
|
||||
{n, name, arity}
|
||||
end
|
||||
end
|
||||
|
||||
@@ -161,40 +161,40 @@ defmodule Module.LocalsTracker do
|
||||
:lists.foldl(&collect_unused_locals(&1, &2, reachable), [], private)
|
||||
end
|
||||
|
||||
defp collect_unused_locals({ tuple, kind, 0 }, acc, reachable) do
|
||||
defp collect_unused_locals({tuple, kind, 0}, acc, reachable) do
|
||||
if :lists.member(tuple, reachable) do
|
||||
acc
|
||||
else
|
||||
[{ :unused_def, tuple, kind }|acc]
|
||||
[{:unused_def, tuple, kind}|acc]
|
||||
end
|
||||
end
|
||||
|
||||
defp collect_unused_locals({ tuple, kind, default }, acc, reachable) when default > 0 do
|
||||
{ name, arity } = tuple
|
||||
defp collect_unused_locals({tuple, kind, default}, acc, reachable) when default > 0 do
|
||||
{name, arity} = tuple
|
||||
min = arity - default
|
||||
max = arity
|
||||
|
||||
invoked = for { n, a } <- reachable, n == name, a in min..max, do: a
|
||||
invoked = for {n, a} <- reachable, n == name, a in min..max, do: a
|
||||
|
||||
if invoked == [] do
|
||||
[{ :unused_def, tuple, kind }|acc]
|
||||
[{:unused_def, tuple, kind}|acc]
|
||||
else
|
||||
case :lists.min(invoked) - min do
|
||||
0 -> acc
|
||||
^default -> [{ :unused_args, tuple }|acc]
|
||||
unused_args -> [{ :unused_args, tuple, unused_args }|acc]
|
||||
^default -> [{:unused_args, tuple}|acc]
|
||||
unused_args -> [{:unused_args, tuple, unused_args}|acc]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@doc false
|
||||
def cache_env(pid, env) do
|
||||
:gen_server.call(pid, { :cache_env, env }, @timeout)
|
||||
:gen_server.call(pid, {:cache_env, env}, @timeout)
|
||||
end
|
||||
|
||||
@doc false
|
||||
def get_cached_env(pid, ref) do
|
||||
:gen_server.call(pid, { :get_cached_env, ref }, @timeout)
|
||||
:gen_server.call(pid, {:get_cached_env, ref}, @timeout)
|
||||
end
|
||||
|
||||
# Stops the gen server
|
||||
@@ -208,78 +208,78 @@ defmodule Module.LocalsTracker do
|
||||
def init([]) do
|
||||
d = :digraph.new([:protected])
|
||||
:digraph.add_vertex(d, :local)
|
||||
{ :ok, { d, [] } }
|
||||
{:ok, {d, []}}
|
||||
end
|
||||
|
||||
def handle_call({ :cache_env, env }, _from, { d, cache }) do
|
||||
def handle_call({:cache_env, env}, _from, {d, cache}) do
|
||||
case cache do
|
||||
[{i,^env}|_] ->
|
||||
{ :reply, i, { d, cache } }
|
||||
{:reply, i, {d, cache}}
|
||||
t ->
|
||||
i = length(t)
|
||||
{ :reply, i, { d, [{i,env}|t] } }
|
||||
{:reply, i, {d, [{i,env}|t]}}
|
||||
end
|
||||
end
|
||||
|
||||
def handle_call({ :get_cached_env, ref }, _from, { _, cache } = state) do
|
||||
{ ^ref, env } = :lists.keyfind(ref, 1, cache)
|
||||
{ :reply, env, state }
|
||||
def handle_call({:get_cached_env, ref}, _from, {_, cache} = state) do
|
||||
{^ref, env} = :lists.keyfind(ref, 1, cache)
|
||||
{:reply, env, state}
|
||||
end
|
||||
|
||||
def handle_call({ :yank, local }, _from, { d, _ } = state) do
|
||||
def handle_call({:yank, local}, _from, {d, _} = state) do
|
||||
in_vertices = :digraph.in_neighbours(d, local)
|
||||
out_vertices = :digraph.out_neighbours(d, local)
|
||||
:digraph.del_vertex(d, local)
|
||||
{ :reply, { in_vertices, out_vertices }, state }
|
||||
{:reply, {in_vertices, out_vertices}, state}
|
||||
end
|
||||
|
||||
def handle_call(:digraph, _from, { d, _ } = state) do
|
||||
{ :reply, d, state }
|
||||
def handle_call(:digraph, _from, {d, _} = state) do
|
||||
{:reply, d, state}
|
||||
end
|
||||
|
||||
def handle_call(request, _from, state) do
|
||||
{ :stop, { :bad_call, request }, state }
|
||||
{:stop, {:bad_call, request}, state}
|
||||
end
|
||||
|
||||
def handle_info(_msg, state) do
|
||||
{ :noreply, state }
|
||||
{:noreply, state}
|
||||
end
|
||||
|
||||
def handle_cast({ :add_local, from, to }, { d, _ } = state) do
|
||||
def handle_cast({:add_local, from, to}, {d, _} = state) do
|
||||
handle_add_local(d, from, to)
|
||||
{ :noreply, state }
|
||||
{:noreply, state}
|
||||
end
|
||||
|
||||
def handle_cast({ :add_import, function, module, { name, arity } }, { d, _ } = state) do
|
||||
def handle_cast({:add_import, function, module, {name, arity}}, {d, _} = state) do
|
||||
handle_import(d, function, module, name, arity)
|
||||
{ :noreply, state }
|
||||
{:noreply, state}
|
||||
end
|
||||
|
||||
def handle_cast({ :add_definition, kind, tuple }, { d, _ } = state) do
|
||||
def handle_cast({:add_definition, kind, tuple}, {d, _} = state) do
|
||||
handle_add_definition(d, kind, tuple)
|
||||
{ :noreply, state }
|
||||
{:noreply, state}
|
||||
end
|
||||
|
||||
def handle_cast({ :add_defaults, kind, { name, arity }, defaults }, { d, _ } = state) do
|
||||
def handle_cast({:add_defaults, kind, {name, arity}, defaults}, {d, _} = state) do
|
||||
for i <- :lists.seq(arity - defaults, arity - 1) do
|
||||
handle_add_definition(d, kind, { name, i })
|
||||
handle_add_local(d, { name, i }, { name, i + 1 })
|
||||
handle_add_definition(d, kind, {name, i})
|
||||
handle_add_local(d, {name, i}, {name, i + 1})
|
||||
end
|
||||
{ :noreply, state }
|
||||
{:noreply, state}
|
||||
end
|
||||
|
||||
def handle_cast({ :reattach, tuple, { in_neigh, out_neigh } }, { d, _ } = state) do
|
||||
def handle_cast({:reattach, tuple, {in_neigh, out_neigh}}, {d, _} = state) do
|
||||
for from <- in_neigh, do: replace_edge(d, from, tuple)
|
||||
for to <- out_neigh, do: replace_edge(d, tuple, to)
|
||||
{ :noreply, state }
|
||||
{:noreply, state}
|
||||
end
|
||||
|
||||
def handle_cast(:stop, state) do
|
||||
{ :stop, :normal, state }
|
||||
{:stop, :normal, state}
|
||||
end
|
||||
|
||||
def handle_cast(msg, state) do
|
||||
{ :stop, { :bad_cast, msg }, state }
|
||||
{:stop, {:bad_cast, msg}, state}
|
||||
end
|
||||
|
||||
def terminate(_reason, _state) do
|
||||
@@ -287,13 +287,13 @@ defmodule Module.LocalsTracker do
|
||||
end
|
||||
|
||||
def code_change(_old, state, _extra) do
|
||||
{ :ok, state }
|
||||
{:ok, state}
|
||||
end
|
||||
|
||||
defp handle_import(d, function, module, name, arity) do
|
||||
:digraph.add_vertex(d, module)
|
||||
|
||||
tuple = { :import, name, arity }
|
||||
tuple = {:import, name, arity}
|
||||
:digraph.add_vertex(d, tuple)
|
||||
replace_edge!(d, tuple, module)
|
||||
|
||||
|
||||
@@ -4,80 +4,85 @@ defmodule OptionParser do
|
||||
"""
|
||||
|
||||
@doc """
|
||||
Parses `argv` and returns a tuple with the parsed options, its
|
||||
arguments, and a list of options that couldn't be parsed.
|
||||
Parses `argv` into a keywords list.
|
||||
|
||||
It returns the parsed values, remaining arguments and the
|
||||
invalid options.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> OptionParser.parse(["--debug"])
|
||||
{ [debug: true], [], [] }
|
||||
{[debug: true], [], []}
|
||||
|
||||
iex> OptionParser.parse(["--source", "lib"])
|
||||
{ [source: "lib"], [], [] }
|
||||
{[source: "lib"], [], []}
|
||||
|
||||
iex> OptionParser.parse(["--source-path", "lib", "test/enum_test.exs", "--verbose"])
|
||||
{ [source_path: "lib", verbose: true], ["test/enum_test.exs"], [] }
|
||||
{[source_path: "lib", verbose: true], ["test/enum_test.exs"], []}
|
||||
|
||||
Notice how Elixir automatically translates the "--source-path"
|
||||
switch to the underscored atom `:source_path`, which better follows
|
||||
Elixir conventions.
|
||||
By default, Elixir will try to automatically parse switches.
|
||||
Switches without an argument, like `--debug` will automatically
|
||||
be set to true. Switches followed by a value will be assigned
|
||||
to the value, always as strings.
|
||||
|
||||
## Aliases
|
||||
|
||||
A set of aliases can be given as the second argument:
|
||||
|
||||
iex> OptionParser.parse(["-d"], aliases: [d: :debug])
|
||||
{ [debug: true], [], [] }
|
||||
Note Elixir also converts the switches to underscore atoms, as
|
||||
`--source-path` becomes `:source_path`, to better suit Elixir
|
||||
conventions.
|
||||
|
||||
## Switches
|
||||
|
||||
Extra information about switches can be given as arguments, too.
|
||||
This is useful when a switch must behave as a boolean
|
||||
or if duplicated switches should be kept, overridden or accumulated.
|
||||
|
||||
The following types are supported:
|
||||
Many times though, it is better to explicitly list the available
|
||||
switches and their formats. 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 is (the default);
|
||||
* `:string` - Returns the switch as a string;
|
||||
|
||||
If a switch can't be parsed, the option is returned in the invalid
|
||||
options list (third element of the returned tuple).
|
||||
|
||||
The following extra options are supported:
|
||||
The following extra "types" are supported:
|
||||
|
||||
* `:keep` - Keeps duplicated items in the list instead of overriding;
|
||||
|
||||
Examples:
|
||||
|
||||
iex> OptionParser.parse(["--unlock", "path/to/file"], switches: [unlock: :boolean])
|
||||
{ [unlock: true], ["path/to/file"], [] }
|
||||
{[unlock: true], ["path/to/file"], []}
|
||||
|
||||
iex> OptionParser.parse(["--unlock", "--limit", "0", "path/to/file"],
|
||||
...> switches: [unlock: :boolean, limit: :integer])
|
||||
{ [unlock: true, limit: 0], ["path/to/file"], [] }
|
||||
{[unlock: true, limit: 0], ["path/to/file"], []}
|
||||
|
||||
iex> OptionParser.parse(["--limit", "3"], switches: [limit: :integer])
|
||||
{ [limit: 3], [], [] }
|
||||
{[limit: 3], [], []}
|
||||
|
||||
iex> OptionParser.parse(["--limit", "yyz"], switches: [limit: :integer])
|
||||
{ [], [], [limit: "yyz"] }
|
||||
{[], [], [limit: "yyz"]}
|
||||
|
||||
## Negation switches
|
||||
|
||||
Any switches starting with `--no-` are always considered to be
|
||||
booleans and never parse the next value:
|
||||
All switches starting with `--no-` are considered to be booleans and never
|
||||
parse the next value:
|
||||
|
||||
iex> OptionParser.parse(["--no-op", "path/to/file"])
|
||||
{ [no_op: true], ["path/to/file"], [] }
|
||||
{[no_op: true], ["path/to/file"], []}
|
||||
|
||||
In case the negated switch exists as a boolean, it sets the boolean to false:
|
||||
However, in case the base switch exists, it sets that particular switch to
|
||||
false:
|
||||
|
||||
iex> OptionParser.parse(["--no-op", "path/to/file"], switches: [op: :boolean])
|
||||
{ [op: false], ["path/to/file"], [] }
|
||||
{[op: false], ["path/to/file"], []}
|
||||
|
||||
## Aliases
|
||||
|
||||
A set of aliases can be given as options too:
|
||||
|
||||
iex> OptionParser.parse(["-d"], aliases: [d: :debug])
|
||||
{[debug: true], [], []}
|
||||
|
||||
"""
|
||||
def parse(argv, opts \\ []) when is_list(argv) and is_list(opts) do
|
||||
@@ -93,10 +98,10 @@ defmodule OptionParser do
|
||||
## Example
|
||||
|
||||
iex> OptionParser.parse_head(["--source", "lib", "test/enum_test.exs", "--verbose"])
|
||||
{ [source: "lib"], ["test/enum_test.exs", "--verbose"], [] }
|
||||
{[source: "lib"], ["test/enum_test.exs", "--verbose"], []}
|
||||
|
||||
iex> OptionParser.parse_head(["--verbose", "--source", "lib", "test/enum_test.exs", "--unlock"])
|
||||
{ [verbose: true, source: "lib"], ["test/enum_test.exs", "--unlock"], [] }
|
||||
{[verbose: true, source: "lib"], ["test/enum_test.exs", "--unlock"], []}
|
||||
|
||||
"""
|
||||
def parse_head(argv, opts \\ []) when is_list(argv) and is_list(opts) do
|
||||
@@ -115,25 +120,15 @@ defmodule OptionParser do
|
||||
parse(argv, aliases, switches, [], [], [], all)
|
||||
end
|
||||
|
||||
defp parse(["--"|_] = value, _aliases, _switches, dict, _args, invalid, _all) do
|
||||
{ Enum.reverse(dict), value, Enum.reverse(invalid) }
|
||||
defp parse(["--"|_] = value, _aliases, _switches, dict, args, invalid, _all) do
|
||||
{Enum.reverse(dict), Enum.reverse(args, value), Enum.reverse(invalid)}
|
||||
end
|
||||
|
||||
defp parse(["-" <> option|t], aliases, switches, dict, args, invalid, all) do
|
||||
{ option, value } = split_option(option)
|
||||
{ option, kinds, value } = normalize_option(option, value, switches, aliases)
|
||||
|
||||
if nil?(value) do
|
||||
{ value, t } =
|
||||
if :boolean in kinds do
|
||||
{ true, t }
|
||||
else
|
||||
value_from_tail(t)
|
||||
end
|
||||
end
|
||||
|
||||
{ dict, invalid } = store_option(dict, invalid, option, value, kinds)
|
||||
|
||||
{option, value} = split_option(option)
|
||||
{option, kinds, value} = normalize_option(option, value, switches, aliases)
|
||||
{value, kinds, t} = normalize_value(value, kinds, t)
|
||||
{dict, invalid} = store_option(dict, invalid, option, value, kinds)
|
||||
parse(t, aliases, switches, dict, args, invalid, all)
|
||||
end
|
||||
|
||||
@@ -142,51 +137,47 @@ defmodule OptionParser do
|
||||
end
|
||||
|
||||
defp parse([], _, _switches, dict, args, invalid, true) do
|
||||
{ Enum.reverse(dict), Enum.reverse(args), Enum.reverse(invalid) }
|
||||
{Enum.reverse(dict), Enum.reverse(args), Enum.reverse(invalid)}
|
||||
end
|
||||
|
||||
defp parse(value, _, _switches, dict, _args, invalid, false) do
|
||||
{ Enum.reverse(dict), value, Enum.reverse(invalid) }
|
||||
{Enum.reverse(dict), value, Enum.reverse(invalid)}
|
||||
end
|
||||
|
||||
defp value_from_tail(["-" <> _|_] = t), do: { true, t }
|
||||
defp value_from_tail([h|t]), do: { h, t }
|
||||
defp value_from_tail([]), do: { true, [] }
|
||||
|
||||
defp store_option(dict, invalid, option, value, kinds) do
|
||||
{ invalid_option, value } =
|
||||
{invalid_option, value} =
|
||||
cond do
|
||||
:invalid in kinds ->
|
||||
{ option, value }
|
||||
{option, value}
|
||||
:boolean in kinds ->
|
||||
{ nil, value in [true, "true"] }
|
||||
{nil, value in [true, "true"]}
|
||||
:integer in kinds ->
|
||||
case Integer.parse(value) do
|
||||
{ value, "" } -> { nil, value }
|
||||
_ -> { option, value }
|
||||
{value, ""} -> {nil, value}
|
||||
_ -> {option, value}
|
||||
end
|
||||
:float in kinds ->
|
||||
case Float.parse(value) do
|
||||
{ value, "" } -> { nil, value }
|
||||
_ -> { option, value }
|
||||
{value, ""} -> {nil, value}
|
||||
_ -> {option, value}
|
||||
end
|
||||
true ->
|
||||
{ nil, value }
|
||||
{nil, value}
|
||||
end
|
||||
|
||||
if invalid_option do
|
||||
{ dict, [{ option, value }|invalid] }
|
||||
{dict, [{option, value}|invalid]}
|
||||
else
|
||||
{ do_store_option(dict, option, value, kinds), invalid }
|
||||
{do_store_option(dict, option, value, kinds), invalid}
|
||||
end
|
||||
end
|
||||
|
||||
defp do_store_option(dict, option, value, kinds) do
|
||||
cond do
|
||||
:keep in kinds ->
|
||||
[{ option, value }|dict]
|
||||
[{option, value}|dict]
|
||||
true ->
|
||||
[{ option, value }|Keyword.delete(dict, option)]
|
||||
[{option, value}|Keyword.delete(dict, option)]
|
||||
end
|
||||
end
|
||||
|
||||
@@ -197,30 +188,55 @@ defmodule OptionParser do
|
||||
defp normalize_option(option, value, switches, aliases) do
|
||||
option = get_option(option)
|
||||
if alias = aliases[option] do
|
||||
normalize_option({ :default, alias }, value, switches)
|
||||
normalize_option({:default, alias}, value, switches)
|
||||
else
|
||||
{ option, [:invalid], value }
|
||||
{option, [:invalid], value}
|
||||
end
|
||||
end
|
||||
|
||||
defp normalize_option({ :negated, option }, _value, switches) do
|
||||
defp normalize_option({:negated, option}, _value, switches) do
|
||||
kinds = List.wrap(switches[option])
|
||||
|
||||
if :boolean in kinds do
|
||||
{ option, kinds, false }
|
||||
else
|
||||
{ option, [:boolean], true }
|
||||
cond do
|
||||
:boolean in kinds ->
|
||||
{option, kinds, false}
|
||||
kinds == [] ->
|
||||
{option, kinds, true}
|
||||
true ->
|
||||
{option, [:invalid], false}
|
||||
end
|
||||
end
|
||||
|
||||
defp normalize_option({ :default, option }, value, switches) do
|
||||
{ option, List.wrap(switches[option]), value }
|
||||
defp normalize_option({:default, option}, value, switches) do
|
||||
{option, List.wrap(switches[option]), value}
|
||||
end
|
||||
|
||||
defp normalize_value(nil, kinds, t) do
|
||||
cond do
|
||||
:boolean in kinds ->
|
||||
{true, kinds, t}
|
||||
value_in_tail?(t) ->
|
||||
[h|t] = t
|
||||
{h, kinds, t}
|
||||
kinds == [] ->
|
||||
{true, kinds, t}
|
||||
true ->
|
||||
{true, [:invalid], t}
|
||||
end
|
||||
end
|
||||
|
||||
defp normalize_value(value, kinds, t) do
|
||||
{value, kinds, t}
|
||||
end
|
||||
|
||||
defp value_in_tail?(["-" <> _|_]), do: false
|
||||
defp value_in_tail?([]), do: false
|
||||
defp value_in_tail?(_), do: true
|
||||
|
||||
defp split_option(option) do
|
||||
case :binary.split(option, "=") do
|
||||
[h] -> { h, nil }
|
||||
[h, t] -> { h, t }
|
||||
[h] -> {h, nil}
|
||||
[h, t] -> {h, t}
|
||||
end
|
||||
end
|
||||
|
||||
@@ -235,10 +251,10 @@ defmodule OptionParser do
|
||||
defp get_negated("no-" <> rest = option, switches) do
|
||||
negated = get_option(rest)
|
||||
option = if Keyword.has_key?(switches, negated), do: negated, else: get_option(option)
|
||||
{ :negated, option }
|
||||
{:negated, option}
|
||||
end
|
||||
|
||||
defp get_negated(rest, _switches) do
|
||||
{ :default, get_option(rest) }
|
||||
{:default, get_option(rest)}
|
||||
end
|
||||
end
|
||||
|
||||
+116
-120
@@ -3,9 +3,9 @@ defmodule Path do
|
||||
This module provides conveniences for manipulating or
|
||||
retrieving file system paths.
|
||||
|
||||
The functions in this module may receive a char list or
|
||||
a binary as an argument and will return a value of the same
|
||||
type.
|
||||
The functions in this module may receive a char data as
|
||||
argument (i.e. a string or a list of characters / string)
|
||||
and will always return a string (encoded in UTF-8).
|
||||
|
||||
The majority of the functions in this module do not
|
||||
interact with the file system, except for a few functions
|
||||
@@ -13,9 +13,7 @@ defmodule Path do
|
||||
"""
|
||||
|
||||
alias :filename, as: FN
|
||||
|
||||
@type t :: char_list | atom | binary
|
||||
@type r :: char_list | binary
|
||||
@type t :: char_data
|
||||
|
||||
@doc """
|
||||
Converts the given path to an absolute one. Unlike
|
||||
@@ -37,8 +35,9 @@ defmodule Path do
|
||||
"D:/usr/local/../x"
|
||||
|
||||
"""
|
||||
@spec absname(t) :: binary
|
||||
def absname(path) do
|
||||
FN.absname(path, get_cwd(path))
|
||||
absname(path, System.cwd!)
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -57,8 +56,39 @@ defmodule Path do
|
||||
"bar/../x"
|
||||
|
||||
"""
|
||||
@spec absname(t, t) :: binary
|
||||
def absname(path, relative_to) do
|
||||
FN.absname(path, relative_to)
|
||||
path = String.from_char_data!(path)
|
||||
case type(path) do
|
||||
:relative -> join(relative_to, path)
|
||||
:absolute ->
|
||||
if :binary.last(path) == ?/ do
|
||||
binary_part(path, 0, byte_size(path) - 1)
|
||||
else
|
||||
path
|
||||
end
|
||||
:volumerelative ->
|
||||
relative_to = String.from_char_data!(relative_to)
|
||||
absname_vr(split(path), split(relative_to), relative_to)
|
||||
end
|
||||
end
|
||||
|
||||
## Absolute path on current drive
|
||||
defp absname_vr(["/"|rest], [volume|_], _relative),
|
||||
do: join([volume|rest])
|
||||
|
||||
## Relative to current directory on current drive.
|
||||
defp absname_vr([<<x, ?:>>|rest], [<<x, _ :: binary>>|_], relative),
|
||||
do: absname(join(rest), relative)
|
||||
|
||||
## Relative to current directory on another drive.
|
||||
defp absname_vr([<<x, ?:>>|name], _, _relative) do
|
||||
cwd =
|
||||
case :file.get_cwd([x, ?:]) do
|
||||
{:ok, dir} -> String.from_char_data!(dir)
|
||||
{:error, _} -> <<x, ?:, ?/>>
|
||||
end
|
||||
absname(join(name), cwd)
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -71,8 +101,9 @@ defmodule Path do
|
||||
"/foo/bar"
|
||||
|
||||
"""
|
||||
@spec expand(t) :: binary
|
||||
def expand(path) do
|
||||
normalize FN.absname(expand_home(path), get_cwd(path))
|
||||
normalize absname(expand_home(path), System.cwd!)
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -97,8 +128,9 @@ defmodule Path do
|
||||
"/foo/bar"
|
||||
|
||||
"""
|
||||
@spec expand(t, t) :: binary
|
||||
def expand(path, relative_to) do
|
||||
normalize FN.absname(FN.absname(expand_home(path), expand_home(relative_to)), get_cwd(path))
|
||||
normalize absname(absname(expand_home(path), expand_home(relative_to)), System.cwd!)
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -119,9 +151,10 @@ defmodule Path do
|
||||
Path.type("/bar/foo.ex") #=> :volumerelative
|
||||
|
||||
"""
|
||||
@spec type(t) :: :absolute | :relative | :volumerelative
|
||||
def type(name) when is_list(name) or is_binary(name) do
|
||||
case :os.type() do
|
||||
{ :win32, _ } -> win32_pathtype(name)
|
||||
{:win32, _} -> win32_pathtype(name)
|
||||
_ -> unix_pathtype(name)
|
||||
end |> elem(0)
|
||||
end
|
||||
@@ -143,53 +176,50 @@ defmodule Path do
|
||||
Path.relative("/bar/foo.ex") #=> "bar/foo.ex"
|
||||
|
||||
"""
|
||||
@spec relative(t) :: binary
|
||||
def relative(name) do
|
||||
case :os.type() do
|
||||
{ :win32, _ } -> win32_pathtype(name)
|
||||
{:win32, _} -> win32_pathtype(name)
|
||||
_ -> unix_pathtype(name)
|
||||
end |> elem(1)
|
||||
end |> elem(1) |> String.from_char_data!
|
||||
end
|
||||
|
||||
defp unix_pathtype(<<?/, relative :: binary>>), do:
|
||||
{ :absolute, relative }
|
||||
{:absolute, relative}
|
||||
defp unix_pathtype([?/|relative]), do:
|
||||
{ :absolute, relative }
|
||||
{:absolute, relative}
|
||||
defp unix_pathtype([list|rest]) when is_list(list), do:
|
||||
unix_pathtype(list ++ rest)
|
||||
defp unix_pathtype([atom|rest]) when is_atom(atom), do:
|
||||
unix_pathtype(atom_to_list(atom) ++ rest)
|
||||
defp unix_pathtype(relative), do:
|
||||
{ :relative, relative }
|
||||
{:relative, relative}
|
||||
|
||||
@slash [?/, ?\\]
|
||||
|
||||
defp win32_pathtype([list|rest]) when is_list(list), do:
|
||||
win32_pathtype(list++rest)
|
||||
defp win32_pathtype([atom|rest]) when is_atom(atom), do:
|
||||
win32_pathtype(atom_to_list(atom)++rest)
|
||||
defp win32_pathtype([char, list|rest]) when is_list(list), do:
|
||||
win32_pathtype([char|list++rest])
|
||||
defp win32_pathtype(<<c1, c2, relative :: binary>>) when c1 in @slash and c2 in @slash, do:
|
||||
{ :absolute, relative }
|
||||
{:absolute, relative}
|
||||
defp win32_pathtype(<<c, relative :: binary>>) when c in @slash, do:
|
||||
{ :volumerelative, relative }
|
||||
{:volumerelative, relative}
|
||||
defp win32_pathtype(<<_letter, ?:, c, relative :: binary>>) when c in @slash, do:
|
||||
{ :absolute, relative }
|
||||
{:absolute, relative}
|
||||
defp win32_pathtype(<<_letter, ?:, relative :: binary>>), do:
|
||||
{ :volumerelative, relative }
|
||||
{:volumerelative, relative}
|
||||
|
||||
defp win32_pathtype([c1, c2 | relative]) when c1 in @slash and c2 in @slash, do:
|
||||
{ :absolute, relative }
|
||||
{:absolute, relative}
|
||||
defp win32_pathtype([c | relative]) when c in @slash, do:
|
||||
{ :volumerelative, relative }
|
||||
{:volumerelative, relative}
|
||||
defp win32_pathtype([c1, c2, list|rest]) when is_list(list), do:
|
||||
win32_pathtype([c1, c2|list++rest])
|
||||
defp win32_pathtype([_letter, ?:, c | relative]) when c in @slash, do:
|
||||
{ :absolute, relative }
|
||||
{:absolute, relative}
|
||||
defp win32_pathtype([_letter, ?: | relative]), do:
|
||||
{ :volumerelative, relative }
|
||||
{:volumerelative, relative}
|
||||
defp win32_pathtype(relative), do:
|
||||
{ :relative, relative }
|
||||
{:relative, relative}
|
||||
|
||||
@doc """
|
||||
Returns the given `path` relative to the given `from` path.
|
||||
@@ -213,17 +243,10 @@ defmodule Path do
|
||||
"/usr/local/foo"
|
||||
|
||||
"""
|
||||
def relative_to(path, from) when is_list(path) and is_binary(from) do
|
||||
path = filename_string_to_binary(path)
|
||||
relative_to(FN.split(path), FN.split(from), path)
|
||||
end
|
||||
|
||||
def relative_to(path, from) when is_binary(path) and is_list(from) do
|
||||
relative_to(FN.split(path), FN.split(filename_string_to_binary(from)), path)
|
||||
end
|
||||
|
||||
@spec relative_to(t, t) :: binary
|
||||
def relative_to(path, from) do
|
||||
relative_to(FN.split(path), FN.split(from), path)
|
||||
path = String.from_char_data!(path)
|
||||
relative_to(split(path), split(from), path)
|
||||
end
|
||||
|
||||
defp relative_to([h|t1], [h|t2], original) do
|
||||
@@ -231,7 +254,7 @@ defmodule Path do
|
||||
end
|
||||
|
||||
defp relative_to([_|_] = l1, [], _original) do
|
||||
FN.join(l1)
|
||||
join(l1)
|
||||
end
|
||||
|
||||
defp relative_to(_, _, original) do
|
||||
@@ -243,9 +266,10 @@ defmodule Path do
|
||||
directory. If, for some reason, the current working directory
|
||||
cannot be retrieved, returns the full path.
|
||||
"""
|
||||
@spec relative_to_cwd(t) :: binary
|
||||
def relative_to_cwd(path) do
|
||||
case :file.get_cwd do
|
||||
{ :ok, base } -> relative_to(path, base)
|
||||
{:ok, base} -> relative_to(path, String.from_char_data!(base))
|
||||
_ -> path
|
||||
end
|
||||
end
|
||||
@@ -266,8 +290,9 @@ defmodule Path do
|
||||
""
|
||||
|
||||
"""
|
||||
@spec basename(t) :: binary
|
||||
def basename(path) do
|
||||
FN.basename(path)
|
||||
FN.basename(String.from_char_data!(path))
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -287,8 +312,9 @@ defmodule Path do
|
||||
"bar.old"
|
||||
|
||||
"""
|
||||
@spec basename(t, t) :: binary
|
||||
def basename(path, extension) do
|
||||
FN.basename(path, extension)
|
||||
FN.basename(String.from_char_data!(path), String.from_char_data!(extension))
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -302,8 +328,9 @@ defmodule Path do
|
||||
#=> "/foo/bar"
|
||||
|
||||
"""
|
||||
@spec dirname(t) :: binary
|
||||
def dirname(path) do
|
||||
FN.dirname(path)
|
||||
FN.dirname(String.from_char_data!(path))
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -318,8 +345,9 @@ defmodule Path do
|
||||
""
|
||||
|
||||
"""
|
||||
@spec extname(t) :: binary
|
||||
def extname(path) do
|
||||
FN.extension(path)
|
||||
FN.extension(String.from_char_data!(path))
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -334,8 +362,9 @@ defmodule Path do
|
||||
"/foo/bar"
|
||||
|
||||
"""
|
||||
@spec rootname(t) :: binary
|
||||
def rootname(path) do
|
||||
FN.rootname(path)
|
||||
FN.rootname(String.from_char_data!(path))
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -351,13 +380,16 @@ defmodule Path do
|
||||
"/foo/bar.erl"
|
||||
|
||||
"""
|
||||
@spec rootname(t, t) :: binary
|
||||
def rootname(path, extension) do
|
||||
FN.rootname(path, extension)
|
||||
FN.rootname(String.from_char_data!(path), String.from_char_data!(extension))
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns a string with one or more path components joined by the path separator.
|
||||
|
||||
This function should be used to convert a list of strings to a path.
|
||||
Note that any trailing slash is removed on join.
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -367,16 +399,15 @@ defmodule Path do
|
||||
iex> Path.join(["foo"])
|
||||
"foo"
|
||||
|
||||
iex> Path.join(["/", "foo", "bar"])
|
||||
iex> Path.join(["/", "foo", "bar/"])
|
||||
"/foo/bar"
|
||||
|
||||
"""
|
||||
@spec join([t]) :: binary
|
||||
def join([name1, name2|rest]), do:
|
||||
join([join(name1, name2)|rest])
|
||||
def join([name]) when is_list(name), do:
|
||||
binary_to_filename_string(do_join(filename_string_to_binary(name), <<>>, [], major_os_type()))
|
||||
def join([name]) when is_binary(name), do:
|
||||
do_join(name, <<>>, [], major_os_type())
|
||||
def join([name]), do:
|
||||
do_join(String.from_char_data!(name), <<>>, [], major_os_type())
|
||||
|
||||
@doc """
|
||||
Joins two paths.
|
||||
@@ -387,23 +418,9 @@ defmodule Path do
|
||||
"foo/bar"
|
||||
|
||||
"""
|
||||
def join(left, right) when is_binary(left) and is_binary(right), do:
|
||||
do_join(left, Path.relative(right), [], major_os_type())
|
||||
|
||||
def join(left, right) when is_binary(left) and is_list(right), do:
|
||||
join(left, filename_string_to_binary(right))
|
||||
|
||||
def join(left, right) when is_list(left) and is_binary(right), do:
|
||||
join(filename_string_to_binary(left), right)
|
||||
|
||||
def join(left, right) when is_list(left) and is_list(right), do:
|
||||
binary_to_filename_string join(filename_string_to_binary(left), filename_string_to_binary(right))
|
||||
|
||||
def join(left, right) when is_atom(left), do:
|
||||
join(atom_to_binary(left), right)
|
||||
|
||||
def join(left, right) when is_atom(right), do:
|
||||
join(left, atom_to_binary(right))
|
||||
@spec join(t, t) :: binary
|
||||
def join(left, right),
|
||||
do: do_join(String.from_char_data!(left), relative(right), [], major_os_type())
|
||||
|
||||
defp major_os_type do
|
||||
:os.type |> elem(0)
|
||||
@@ -417,17 +434,26 @@ defmodule Path do
|
||||
do_join(rest, relativename, [?/|result], os_type)
|
||||
defp do_join(<<?/, rest :: binary>>, relativename, [?/|result], os_type), do:
|
||||
do_join(rest, relativename, [?/|result], os_type)
|
||||
defp do_join(<<>>, <<>>, result, _os_type), do:
|
||||
iolist_to_binary(:lists.reverse(result))
|
||||
defp do_join(<<>>, <<>>, result, os_type), do:
|
||||
iodata_to_binary(maybe_remove_dirsep(result, os_type))
|
||||
defp do_join(<<>>, relativename, [?:|rest], :win32), do:
|
||||
do_join(relativename, <<>>, [?:|rest], :win32)
|
||||
defp do_join(<<>>, relativename, [?/|result], os_type), do:
|
||||
do_join(relativename, <<>>, [?/|result], os_type)
|
||||
defp do_join(<<>>, relativename, result, os_type), do:
|
||||
do_join(relativename, <<>>, [?/|result], os_type)
|
||||
defp do_join(<<char, rest :: binary>>, relativename, result, os_type) when is_integer(char), do:
|
||||
defp do_join(<<char, rest :: binary>>, relativename, result, os_type), do:
|
||||
do_join(rest, relativename, [char|result], os_type)
|
||||
|
||||
defp maybe_remove_dirsep([?/, ?:, letter], :win32), do:
|
||||
[letter, ?:, ?/]
|
||||
defp maybe_remove_dirsep([?/], _), do:
|
||||
[?/]
|
||||
defp maybe_remove_dirsep([?/|name], _), do:
|
||||
:lists.reverse(name)
|
||||
defp maybe_remove_dirsep(name, _), do:
|
||||
:lists.reverse(name)
|
||||
|
||||
@doc """
|
||||
Returns a list with the path split by the path separator.
|
||||
If an empty string is given, returns the root path.
|
||||
@@ -444,11 +470,13 @@ defmodule Path do
|
||||
["/", "foo", "bar"]
|
||||
|
||||
"""
|
||||
@spec split(t) :: [binary]
|
||||
|
||||
# Work around a bug in Erlang on UNIX
|
||||
def split(""), do: []
|
||||
|
||||
def split(path) do
|
||||
FN.split(path)
|
||||
FN.split(String.from_char_data!(path))
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -484,62 +512,30 @@ defmodule Path do
|
||||
Path.wildcard("projects/*/ebin/**/*.{beam,app}")
|
||||
|
||||
"""
|
||||
def wildcard(glob) when is_binary(glob) do
|
||||
paths = :filelib.wildcard binary_to_filename_string(glob)
|
||||
encoding = :file.native_name_encoding()
|
||||
Enum.map paths, &flatten_filename_to_binary(&1, encoding)
|
||||
end
|
||||
|
||||
def wildcard(glob) when is_list(glob) do
|
||||
:filelib.wildcard glob
|
||||
end
|
||||
|
||||
## Helpers
|
||||
|
||||
defp get_cwd(path) when is_list(path), do: System.cwd! |> binary_to_filename_string
|
||||
defp get_cwd(_), do: System.cwd!
|
||||
|
||||
defp binary_to_filename_string(binary) do
|
||||
case :unicode.characters_to_list(binary) do
|
||||
{ :error, _, _ } ->
|
||||
:erlang.error(:badarg)
|
||||
list when is_list(list) ->
|
||||
list
|
||||
end
|
||||
end
|
||||
|
||||
defp filename_string_to_binary(list) do
|
||||
flatten_filename_to_binary(:filename.flatten(list), :file.native_name_encoding())
|
||||
end
|
||||
|
||||
defp flatten_filename_to_binary(list, encoding) do
|
||||
case :unicode.characters_to_binary(list, :unicode, encoding) do
|
||||
{ :error, _, _ } ->
|
||||
:erlang.error(:badarg)
|
||||
bin when is_binary(bin) ->
|
||||
bin
|
||||
end
|
||||
@spec wildcard(t) :: [binary]
|
||||
def wildcard(glob) do
|
||||
glob
|
||||
|> List.from_char_data!
|
||||
|> :filelib.wildcard
|
||||
|> Enum.map(&String.from_char_data!/1)
|
||||
end
|
||||
|
||||
# Normalize the given path by expanding "..", "." and "~".
|
||||
|
||||
defp expand_home(<<?~, rest :: binary>>) do
|
||||
System.user_home! <> rest
|
||||
defp expand_home(type) do
|
||||
case String.from_char_data!(type) do
|
||||
"~" <> rest -> System.user_home! <> rest
|
||||
rest -> rest
|
||||
end
|
||||
end
|
||||
|
||||
defp expand_home('~' ++ rest) do
|
||||
(System.user_home! |> binary_to_filename_string) ++ rest
|
||||
end
|
||||
defp normalize(path), do: normalize(split(path), [])
|
||||
|
||||
defp expand_home(other), do: other
|
||||
|
||||
defp normalize(path), do: normalize(FN.split(path), [])
|
||||
|
||||
defp normalize([top|t], [_|acc]) when top in ["..", '..'] do
|
||||
defp normalize([".."|t], [_|acc]) do
|
||||
normalize t, acc
|
||||
end
|
||||
|
||||
defp normalize([top|t], acc) when top in [".", '.'] do
|
||||
defp normalize(["."|t], acc) do
|
||||
normalize t, acc
|
||||
end
|
||||
|
||||
@@ -548,6 +544,6 @@ defmodule Path do
|
||||
end
|
||||
|
||||
defp normalize([], acc) do
|
||||
join Enum.reverse(acc)
|
||||
join :lists.reverse(acc)
|
||||
end
|
||||
end
|
||||
|
||||
+49
-82
@@ -62,6 +62,7 @@ defmodule Process do
|
||||
"""
|
||||
@spec delete :: [{term, term}]
|
||||
def delete() do
|
||||
IO.write :stderr, "Process.delete/0 is deprecated\n#{Exception.format_stacktrace}"
|
||||
:erlang.erase()
|
||||
end
|
||||
|
||||
@@ -103,26 +104,6 @@ defmodule Process do
|
||||
:erlang.exit(pid, reason)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns the pid (process identifier) of the calling process.
|
||||
"""
|
||||
@spec self() :: pid
|
||||
def self() do
|
||||
:erlang.self()
|
||||
end
|
||||
|
||||
@doc """
|
||||
Sends a message to the given process.
|
||||
|
||||
It does the same as `Kernel.send/2`.
|
||||
"""
|
||||
@spec send(dest, msg) :: msg when
|
||||
dest: pid | port | atom | { atom, node },
|
||||
msg: any
|
||||
def send(dest, msg) do
|
||||
:erlang.send(dest, msg)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Sends a message to the given process.
|
||||
|
||||
@@ -137,12 +118,12 @@ defmodule Process do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Process.send({ :name, :node_does_not_exist }, :hi, [:noconnect])
|
||||
iex> Process.send({:name, :node_does_not_exist}, :hi, [:noconnect])
|
||||
:noconnect
|
||||
|
||||
"""
|
||||
@spec send(dest, msg, [option]) :: result when
|
||||
dest: pid | port | atom | { atom, node },
|
||||
dest: pid | port | atom | {atom, node},
|
||||
msg: any,
|
||||
option: :noconnect | :nosuspend,
|
||||
result: :ok | :noconnect | :nosuspend
|
||||
@@ -179,21 +160,16 @@ defmodule Process do
|
||||
@type spawn_opts :: [spawn_opt]
|
||||
|
||||
@doc """
|
||||
Returns the pid of a new process started by the application of `fun`.
|
||||
It behaves exactly the same as `Kernel.spawn/1`.
|
||||
Spawns the given module and function passing the given args
|
||||
according to the given options.
|
||||
|
||||
Inlined by the compiler.
|
||||
"""
|
||||
@spec spawn((() -> any)) :: pid
|
||||
def spawn(fun) do
|
||||
:erlang.spawn(fun)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns the pid of a new process started by the application of `fun`.
|
||||
The result depends on the given options. In particular,
|
||||
if `:monitor` is given as an option, it will return a tuple
|
||||
containing the pid and the monitoring reference, otherwise
|
||||
just the spawned process pid.
|
||||
|
||||
It also accepts extra options, for the list of available options
|
||||
check http://www.erlang.org/doc/man/erlang.html#spawn_opt-2
|
||||
check http://www.erlang.org/doc/man/erlang.html#spawn_opt-4
|
||||
|
||||
Inlined by the compiler.
|
||||
"""
|
||||
@@ -203,81 +179,72 @@ defmodule Process do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns the pid of a new process started by the application of
|
||||
`module.function(args)`. The new process created will be placed in the system
|
||||
scheduler queue and be run some time later.
|
||||
Spawns the given module and function passing the given args
|
||||
according to the given options.
|
||||
|
||||
It behaves exactly the same as the `Kernel.spawn/3` function.
|
||||
|
||||
Inlined by the compiler.
|
||||
"""
|
||||
@spec spawn(module, atom, [any]) :: pid
|
||||
def spawn(mod, fun, args) do
|
||||
:erlang.spawn(mod, fun, args)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns the pid of a new process started by the application of
|
||||
`module.function(args)`. The new process created will be placed in the system
|
||||
scheduler queue and be run some time later.
|
||||
The result depends on the given options. In particular,
|
||||
if `:monitor` is given as an option, it will return a tuple
|
||||
containing the pid and the monitoring reference, otherwise
|
||||
just the spawned process pid.
|
||||
|
||||
It also accepts extra options, for the list of available options
|
||||
check http://www.erlang.org/doc/man/erlang.html#spawn_opt-4
|
||||
|
||||
Inlined by the compiler.
|
||||
"""
|
||||
@spec spawn(module, atom, [any], spawn_opts) :: pid | {pid, reference}
|
||||
@spec spawn(module, atom, list, spawn_opts) :: pid | {pid, reference}
|
||||
def spawn(mod, fun, args, opts) do
|
||||
:erlang.spawn_opt(mod, fun, args, opts)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns the pid of a new process started by the application of `fun`.
|
||||
A link is created between the calling process and the new
|
||||
process, atomically.
|
||||
@doc false
|
||||
def spawn(fun) do
|
||||
# IO.write :stderr, "Process.spawn/1 is deprecated, use the one in Kernel instead\n#{Exception.format_stacktrace}"
|
||||
:erlang.spawn(fun)
|
||||
end
|
||||
|
||||
Inlined by the compiler.
|
||||
"""
|
||||
@spec spawn_link((() -> any)) :: pid
|
||||
@doc false
|
||||
def spawn(mod, fun, args) do
|
||||
# IO.write :stderr, "Process.spawn/3 is deprecated, use the one in Kernel instead\n#{Exception.format_stacktrace}"
|
||||
:erlang.spawn(mod, fun, args)
|
||||
end
|
||||
|
||||
@doc false
|
||||
def spawn_link(fun) do
|
||||
# IO.write :stderr, "Process.spawn_link/1 is deprecated, use the one in Kernel instead\n#{Exception.format_stacktrace}"
|
||||
:erlang.spawn_link(fun)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns the pid of a new process started by the application of
|
||||
`module.function(args)`. A link is created between the calling process
|
||||
and the new process, atomically. Otherwise works like spawn/3.
|
||||
|
||||
Inlined by the compiler.
|
||||
"""
|
||||
@spec spawn_link(module, atom, [any]) :: pid
|
||||
@doc false
|
||||
def spawn_link(mod, fun, args) do
|
||||
# IO.write :stderr, "Process.spawn_link/3 is deprecated, use the one in Kernel instead\n#{Exception.format_stacktrace}"
|
||||
:erlang.spawn_link(mod, fun, args)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns the pid of a new process started by the application of `fun`
|
||||
and reference for a monitor created to the new process.
|
||||
|
||||
Inlined by the compiler.
|
||||
"""
|
||||
@spec spawn_monitor((() -> any)) :: {pid, reference}
|
||||
@doc false
|
||||
def spawn_monitor(fun) do
|
||||
# IO.write :stderr, "Process.spawn_monitor/1 is deprecated, use the one in Kernel instead\n#{Exception.format_stacktrace}"
|
||||
:erlang.spawn_monitor(fun)
|
||||
end
|
||||
|
||||
@doc """
|
||||
A new process is started by the application of `module.function(args)`
|
||||
and the process is monitored at the same time. Returns the pid and a
|
||||
reference for the monitor. Otherwise works like spawn/3.
|
||||
|
||||
Inlined by the compiler.
|
||||
"""
|
||||
@spec spawn_monitor(module, atom, [any]) :: {pid, reference}
|
||||
@doc false
|
||||
def spawn_monitor(mod, fun, args) do
|
||||
# IO.write :stderr, "Process.spawn_monitor/3 is deprecated, use the one in Kernel instead\n#{Exception.format_stacktrace}"
|
||||
:erlang.spawn_monitor(mod, fun, args)
|
||||
end
|
||||
|
||||
@doc false
|
||||
def self() do
|
||||
# IO.write :stderr, "Process.self/0 is deprecated, use the one in Kernel instead\n#{Exception.format_stacktrace}"
|
||||
:erlang.self()
|
||||
end
|
||||
|
||||
@doc false
|
||||
def send(dest, msg) do
|
||||
# IO.write :stderr, "Process.send/2 is deprecated, use the one in Kernel instead\n#{Exception.format_stacktrace}"
|
||||
:erlang.send(dest, msg)
|
||||
end
|
||||
|
||||
@doc """
|
||||
The calling process starts monitoring the item given.
|
||||
It returns the monitor reference.
|
||||
@@ -457,7 +424,7 @@ defmodule Process do
|
||||
nillify :erlang.process_info(pid, spec)
|
||||
end
|
||||
|
||||
@compile { :inline, nillify: 1 }
|
||||
@compile {:inline, nillify: 1}
|
||||
defp nillify(:undefined), do: nil
|
||||
defp nillify(other), do: other
|
||||
end
|
||||
|
||||
+15
-15
@@ -34,7 +34,7 @@ defmodule Protocol do
|
||||
defp after_defprotocol do
|
||||
quote unquote: false do
|
||||
# == Deprecated records handling ==
|
||||
{ arg, impl } = Protocol.rec_impl_for(__MODULE__)
|
||||
{arg, impl} = Protocol.rec_impl_for(__MODULE__)
|
||||
Kernel.def impl_for(unquote(arg)) when Kernel.is_record(unquote(arg)), do: unquote(impl)
|
||||
# == Deprecated records handling ==
|
||||
|
||||
@@ -45,12 +45,12 @@ defmodule Protocol do
|
||||
#
|
||||
# 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
|
||||
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
|
||||
for {guard, mod} <- Protocol.builtin do
|
||||
target = Module.concat(__MODULE__, mod)
|
||||
|
||||
Kernel.def impl_for(data) when :erlang.unquote(guard)(data) do
|
||||
@@ -94,9 +94,9 @@ defmodule Protocol do
|
||||
end
|
||||
|
||||
# Inline any and struct implementations
|
||||
@compile { :inline, any_impl_for: 0, struct_impl_for: 1, impl_for?: 1 }
|
||||
@compile {:inline, any_impl_for: 0, struct_impl_for: 1, impl_for?: 1}
|
||||
|
||||
if :code.ensure_loaded(Kernel.Typespec) == { :module, Kernel.Typespec } and
|
||||
if :code.ensure_loaded(Kernel.Typespec) == {:module, Kernel.Typespec} and
|
||||
not Kernel.Typespec.defines_type?(__MODULE__, :t, 0) do
|
||||
@type t :: term
|
||||
end
|
||||
@@ -153,7 +153,7 @@ defmodule Protocol do
|
||||
@doc false
|
||||
def assert_protocol(module) do
|
||||
case Code.ensure_compiled(module) do
|
||||
{ :module, ^module } -> nil
|
||||
{:module, ^module} -> nil
|
||||
_ -> raise ArgumentError, message: "#{inspect module} is not loaded"
|
||||
end
|
||||
|
||||
@@ -185,7 +185,7 @@ defmodule Protocol do
|
||||
# returns the module to dispatch to.
|
||||
@doc false
|
||||
def rec_impl_for(current) do
|
||||
all = [Any] ++ for { _guard, mod } <- builtin, do: mod
|
||||
all = [Any] ++ for {_guard, mod} <- builtin, do: mod
|
||||
arg = quote do: arg
|
||||
target = Module.concat(current, Tuple)
|
||||
|
||||
@@ -211,7 +211,7 @@ defmodule Protocol do
|
||||
end
|
||||
end
|
||||
|
||||
{ arg, impl_for }
|
||||
{arg, impl_for}
|
||||
end
|
||||
end
|
||||
|
||||
@@ -219,18 +219,18 @@ defmodule Protocol.DSL do
|
||||
@moduledoc false
|
||||
|
||||
@doc false
|
||||
defmacro def({ _, _, args }) when args == [] or is_atom(args) do
|
||||
defmacro def({_, _, args}) when args == [] or is_atom(args) do
|
||||
raise ArgumentError, message: "protocol functions expect at least one argument"
|
||||
end
|
||||
|
||||
defmacro def({ name, _, args }) when is_atom(name) and is_list(args) do
|
||||
defmacro def({name, _, args}) when is_atom(name) and is_list(args) do
|
||||
arity = length(args)
|
||||
|
||||
type_args = for _ <- :lists.seq(2, arity), do: quote(do: term)
|
||||
type_args = [quote(do: t) | type_args]
|
||||
|
||||
call_args = for i <- :lists.seq(2, arity),
|
||||
do: { binary_to_atom(<<?x, i + 64>>), [], __MODULE__ }
|
||||
do: {binary_to_atom(<<?x, i + 64>>), [], __MODULE__}
|
||||
call_args = [quote(do: t) | call_args]
|
||||
|
||||
quote do
|
||||
@@ -262,17 +262,17 @@ defmodule Protocol.DSL do
|
||||
@doc false
|
||||
def __spec__?(module, name, arity) do
|
||||
case :code.ensure_loaded(Kernel.Typespec) do
|
||||
{ :module, Kernel.Typespec } ->
|
||||
tuple = { name, arity }
|
||||
{:module, Kernel.Typespec} ->
|
||||
tuple = {name, arity}
|
||||
specs = Module.get_attribute(module, :spec)
|
||||
|
||||
found = for { k, v } <- specs, k == tuple do
|
||||
found = for {k, v} <- specs, k == tuple do
|
||||
Kernel.Typespec.define_callback(module, tuple, v)
|
||||
true
|
||||
end
|
||||
|
||||
found != []
|
||||
{ :error, _ } ->
|
||||
{:error, _} ->
|
||||
true
|
||||
end
|
||||
end
|
||||
|
||||
@@ -70,7 +70,7 @@ defmodule Protocol.Consolidation do
|
||||
|
||||
defp list_dir(path) when is_list(path) do
|
||||
case :file.list_dir(path) do
|
||||
{ :ok, files } -> files
|
||||
{:ok, files} -> files
|
||||
_ -> []
|
||||
end
|
||||
end
|
||||
@@ -78,14 +78,14 @@ defmodule Protocol.Consolidation do
|
||||
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 Path.extname(file) == '.beam' do
|
||||
extract_from_beam(Path.join(path, file), callback)
|
||||
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] } } ->
|
||||
{:ok, {module, [attributes: attributes]}} ->
|
||||
callback.(module, attributes)
|
||||
_ ->
|
||||
nil
|
||||
@@ -95,7 +95,7 @@ defmodule Protocol.Consolidation do
|
||||
defmacrop if_ok(expr, call) do
|
||||
quote do
|
||||
case unquote(expr) do
|
||||
{ :ok, var } -> unquote(Macro.pipe(quote(do: var), call))
|
||||
{:ok, var} -> unquote(Macro.pipe(quote(do: var), call, 0))
|
||||
other -> other
|
||||
end
|
||||
end
|
||||
@@ -121,11 +121,11 @@ defmodule Protocol.Consolidation do
|
||||
nor loads the new bytecode for the compiled module.
|
||||
"""
|
||||
@spec apply_to(module, [module]) ::
|
||||
{ :ok, binary } |
|
||||
{ :error, :not_a_protocol } |
|
||||
{ :error, :no_beam_info }
|
||||
{: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 " <>
|
||||
raise ArgumentError, message: "consolidation is disabled as we can't consolidate records " <>
|
||||
"and structs at once. Consolidation will be added back once " <>
|
||||
"polymorphic records are removed"
|
||||
ensure_protocol(protocol)
|
||||
@@ -136,16 +136,16 @@ defmodule Protocol.Consolidation do
|
||||
# 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] } } ->
|
||||
{:ok, {^protocol, [abstract_code: {_raw, abstract_code},
|
||||
attributes: attributes]}} ->
|
||||
case attributes[:protocol] do
|
||||
[fallback_to_any: any, consolidated: _] ->
|
||||
{ :ok, { protocol, any, abstract_code } }
|
||||
{:ok, {protocol, any, abstract_code}}
|
||||
_ ->
|
||||
{ :error, :not_a_protocol }
|
||||
{:error, :not_a_protocol}
|
||||
end
|
||||
_ ->
|
||||
{ :error, :no_beam_info }
|
||||
{:error, :no_beam_info}
|
||||
end
|
||||
end
|
||||
|
||||
@@ -158,23 +158,23 @@ defmodule Protocol.Consolidation do
|
||||
|
||||
# Change the debug information to the optimized
|
||||
# impl_for/1 dispatch version.
|
||||
defp change_debug_info({ protocol, any, code }, types) do
|
||||
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
|
||||
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
|
||||
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])
|
||||
[{:attribute, line, :protocol, opts}|acc])
|
||||
end
|
||||
|
||||
defp change_impl_for([{ :function, line, :impl_for, 1, _ }|t], protocol, types, structs, is_protocol, acc) do
|
||||
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,
|
||||
clauses = for {guard, mod} <- Protocol.builtin,
|
||||
mod in types,
|
||||
do: builtin_clause_for(mod, guard, protocol, line)
|
||||
|
||||
@@ -182,16 +182,16 @@ defmodule Protocol.Consolidation do
|
||||
[fallback_clause_for(fallback, protocol, line)]
|
||||
|
||||
change_impl_for(t, protocol, types, structs, is_protocol,
|
||||
[{ :function, line, :impl_for, 1, clauses }|acc])
|
||||
[{: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
|
||||
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])
|
||||
[{:function, line, :struct_impl_for, 1, clauses}|acc])
|
||||
end
|
||||
|
||||
defp change_impl_for([h|t], protocol, info, types, is_protocol, acc) do
|
||||
@@ -200,9 +200,9 @@ defmodule Protocol.Consolidation do
|
||||
|
||||
defp change_impl_for([], protocol, _info, _types, is_protocol, acc) do
|
||||
if is_protocol do
|
||||
{ :ok, { protocol, Enum.reverse(acc) } }
|
||||
{:ok, {protocol, Enum.reverse(acc)}}
|
||||
else
|
||||
{ :error, :not_a_protocol }
|
||||
{:error, :not_a_protocol}
|
||||
end
|
||||
end
|
||||
|
||||
@@ -212,7 +212,7 @@ defmodule Protocol.Consolidation do
|
||||
[[{:call, line,
|
||||
{:remote, line, {:atom, line, :erlang}, {:atom, line, guard}},
|
||||
[{:var, line, :x}],
|
||||
}]],
|
||||
}]],
|
||||
[{:atom, line, Module.concat(protocol, mod)}]}
|
||||
end
|
||||
|
||||
@@ -224,7 +224,7 @@ defmodule Protocol.Consolidation do
|
||||
[[{:call, line,
|
||||
{:remote, line, {:atom, line, :erlang}, {:atom, line, :is_atom}},
|
||||
[{:var, line, :x}],
|
||||
}]],
|
||||
}]],
|
||||
[{:call, line,
|
||||
{:atom, line, :struct_impl_for},
|
||||
[{:var, line, :x}]}]}
|
||||
@@ -237,13 +237,13 @@ defmodule Protocol.Consolidation do
|
||||
|
||||
defp fallback_clause_for(value, _protocol, line) do
|
||||
{:clause, line, [{:var, line, :_}], [],
|
||||
[{ :atom, line, value }]}
|
||||
[{:atom, line, value}]}
|
||||
end
|
||||
|
||||
# Finally compile the module and emit its bytecode.
|
||||
defp compile({ protocol, code }) do
|
||||
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 }
|
||||
{:ok, ^protocol, binary, _warnings} = :compile.forms(code, [:return|opts])
|
||||
{:ok, binary}
|
||||
end
|
||||
end
|
||||
|
||||
+33
-29
@@ -1,30 +1,30 @@
|
||||
defmodule Range do
|
||||
@moduledoc """
|
||||
Defines a Range.
|
||||
|
||||
A Range are represented internally as a struct. However,
|
||||
the most common form of creating and matching on ranges
|
||||
is via the `../2` macro, auto-imported from Kernel:
|
||||
|
||||
iex> range = 1..3
|
||||
1..3
|
||||
iex> first .. last = range
|
||||
iex> first
|
||||
1
|
||||
iex> last
|
||||
3
|
||||
|
||||
"""
|
||||
|
||||
@type t :: { Range, any, any }
|
||||
@type t(first, last) :: { Range, first, last }
|
||||
defstruct first: nil, last: nil
|
||||
|
||||
@type t(first, last) :: %{__struct__: Range, first: first, last: last}
|
||||
|
||||
@doc """
|
||||
Creates a new range.
|
||||
"""
|
||||
def new(first, last) do
|
||||
{ Range, first, last }
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns the first item of the range.
|
||||
"""
|
||||
def first({ Range, first, _ }) do
|
||||
first
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns the last item of the range.
|
||||
"""
|
||||
def last({ Range, _, last }) do
|
||||
last
|
||||
%Range{first: first, last: last}
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -39,11 +39,15 @@ defmodule Range do
|
||||
false
|
||||
|
||||
"""
|
||||
def range?({ Range, _, _ }), do: true
|
||||
def range?(%Range{}), do: true
|
||||
def range?(_), do: false
|
||||
end
|
||||
|
||||
defprotocol Range.Iterator do
|
||||
@moduledoc """
|
||||
A protocol used for iterating range elements.
|
||||
"""
|
||||
|
||||
@doc """
|
||||
Returns the function that calculates the next item.
|
||||
"""
|
||||
@@ -60,36 +64,36 @@ defimpl Enumerable, for: Range do
|
||||
reduce(first, last, acc, fun, Range.Iterator.next(first, range), last >= first)
|
||||
end
|
||||
|
||||
defp reduce(_x, _y, { :halt, acc }, _fun, _next, _up) do
|
||||
{ :halted, acc }
|
||||
defp reduce(_x, _y, {:halt, acc}, _fun, _next, _up) do
|
||||
{:halted, acc}
|
||||
end
|
||||
|
||||
defp reduce(x, y, { :suspend, acc }, fun, next, up) do
|
||||
{ :suspended, acc, &reduce(x, y, &1, fun, next, up) }
|
||||
defp reduce(x, y, {:suspend, acc}, fun, next, up) do
|
||||
{:suspended, acc, &reduce(x, y, &1, fun, next, up)}
|
||||
end
|
||||
|
||||
defp reduce(x, y, { :cont, acc }, fun, next, true) when x <= y do
|
||||
defp reduce(x, y, {:cont, acc}, fun, next, true) when x <= y do
|
||||
reduce(next.(x), y, fun.(x, acc), fun, next, true)
|
||||
end
|
||||
|
||||
defp reduce(x, y, { :cont, acc }, fun, next, false) when x >= y do
|
||||
defp reduce(x, y, {:cont, acc}, fun, next, false) when x >= y do
|
||||
reduce(next.(x), y, fun.(x, acc), fun, next, false)
|
||||
end
|
||||
|
||||
defp reduce(_, _, { :cont, acc }, _fun, _next, _up) do
|
||||
{ :done, acc }
|
||||
defp reduce(_, _, {:cont, acc}, _fun, _next, _up) do
|
||||
{:done, acc}
|
||||
end
|
||||
|
||||
def member?(first .. last, value) do
|
||||
if first <= last do
|
||||
{ :ok, first <= value and value <= last }
|
||||
{:ok, first <= value and value <= last}
|
||||
else
|
||||
{ :ok, last <= value and value <= first }
|
||||
{:ok, last <= value and value <= first}
|
||||
end
|
||||
end
|
||||
|
||||
def count(first .. _ = range) do
|
||||
{ :ok, Range.Iterator.count(first, range) }
|
||||
{:ok, Range.Iterator.count(first, range)}
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
+11
-21
@@ -4,7 +4,7 @@ defmodule Record do
|
||||
|
||||
Records are simply tuples where the first element is an atom:
|
||||
|
||||
iex> Record.record? { User, "jose", 27 }
|
||||
iex> Record.record? {User, "jose", 27}
|
||||
true
|
||||
|
||||
This module provides conveniences for working with records at
|
||||
@@ -51,13 +51,13 @@ defmodule Record do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> record = { User, "jose", 27 }
|
||||
iex> record = {User, "jose", 27}
|
||||
iex> Record.record?(record, User)
|
||||
true
|
||||
|
||||
"""
|
||||
defmacro record?(data, kind) do
|
||||
case __CALLER__.in_guard? do
|
||||
case Macro.Env.in_guard?(__CALLER__) do
|
||||
true ->
|
||||
quote do
|
||||
is_tuple(unquote(data)) and tuple_size(unquote(data)) > 0
|
||||
@@ -79,7 +79,7 @@ defmodule Record do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> record = { User, "jose", 27 }
|
||||
iex> record = {User, "jose", 27}
|
||||
iex> Record.record?(record)
|
||||
true
|
||||
iex> tuple = {}
|
||||
@@ -88,7 +88,7 @@ defmodule Record do
|
||||
|
||||
"""
|
||||
defmacro record?(data) do
|
||||
case __CALLER__.in_guard? do
|
||||
case Macro.Env.in_guard?(__CALLER__) do
|
||||
true ->
|
||||
quote do
|
||||
is_tuple(unquote(data)) and tuple_size(unquote(data)) > 0
|
||||
@@ -135,14 +135,14 @@ defmodule Record do
|
||||
arities will be defined to manipulate the underlying record:
|
||||
|
||||
# To create records
|
||||
user() #=> { :user, "José", 25 }
|
||||
user(age: 26) #=> { :user, "José", 26 }
|
||||
user() #=> {:user, "José", 25}
|
||||
user(age: 26) #=> {:user, "José", 26}
|
||||
|
||||
# To get a field from the record
|
||||
user(record, :name) #=> "José"
|
||||
|
||||
# To update the record
|
||||
user(record, age: 26) #=> { :user, "José", 26 }
|
||||
user(record, age: 26) #=> {:user, "José", 26}
|
||||
|
||||
By default, Elixir uses the record name as the first element of
|
||||
the tuple (the tag). But it can be changed to something else:
|
||||
@@ -151,18 +151,13 @@ defmodule Record do
|
||||
Record.defrecord :user, User, name: nil
|
||||
end
|
||||
|
||||
user() #=> { User, nil }
|
||||
user() #=> {User, nil}
|
||||
|
||||
"""
|
||||
defmacro defrecord(name, tag \\ nil, kv) do
|
||||
kv = Macro.escape(kv, unquote: true)
|
||||
|
||||
quote bind_quoted: [name: name, tag: tag, kv: kv] do
|
||||
tag = tag || name
|
||||
kv = Macro.expand(kv, __ENV__)
|
||||
|
||||
{ fields, _types } = Record.Backend.split_fields_and_types(:defrecord, kv)
|
||||
fields = Macro.escape(fields)
|
||||
fields = Macro.escape Record.Backend.default_fields(:record, kv)
|
||||
|
||||
defmacro(unquote(name)(args \\ [])) do
|
||||
Record.Backend.access(unquote(tag), unquote(fields), args, __CALLER__)
|
||||
@@ -178,14 +173,9 @@ defmodule Record do
|
||||
Same as `defrecord/3` but generates private macros.
|
||||
"""
|
||||
defmacro defrecordp(name, tag \\ nil, kv) do
|
||||
kv = Macro.escape(kv, unquote: true)
|
||||
|
||||
quote bind_quoted: [name: name, tag: tag, kv: kv] do
|
||||
tag = tag || name
|
||||
kv = Macro.expand(kv, __ENV__)
|
||||
|
||||
{ fields, _types } = Record.Backend.split_fields_and_types(:defrecordp, kv)
|
||||
fields = Macro.escape(fields)
|
||||
fields = Macro.escape Record.Backend.default_fields(:record, kv)
|
||||
|
||||
defmacrop(unquote(name)(args \\ [])) do
|
||||
Record.Backend.access(unquote(tag), unquote(fields), args, __CALLER__)
|
||||
|
||||
@@ -2,37 +2,44 @@ defmodule Record.Backend do
|
||||
# Callback functions invoked by defrecord, defrecordp and friends.
|
||||
@moduledoc false
|
||||
|
||||
@doc """
|
||||
Normalizes a list of record or struct fields to have default values.
|
||||
"""
|
||||
def default_fields(type, fields) do
|
||||
Enum.map(fields, fn
|
||||
{ key, _ } = pair when is_atom(key) -> pair
|
||||
key when is_atom(key) -> { key, nil }
|
||||
other -> raise ArgumentError, message: "#{type} fields must be atoms, got: #{inspect other}"
|
||||
end)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Splits a keywords list into fields and types.
|
||||
|
||||
This logic is shared by records and structs.
|
||||
"""
|
||||
def split_fields_and_types(tag, kv) when is_list(kv) do
|
||||
split_fields_and_types(tag, kv, [], [])
|
||||
def split_fields_and_types(kv) do
|
||||
if Keyword.keyword?(kv) do
|
||||
split_fields_and_types(kv, [], [])
|
||||
else
|
||||
{kv, []}
|
||||
end
|
||||
end
|
||||
|
||||
def split_fields_and_types(tag, other) do
|
||||
raise ArgumentError, message: "#{tag} fields must be a keyword list, got: #{Macro.to_string other}"
|
||||
defp split_fields_and_types([{field, {:::, _, [default, type]}}|t], fields, types) do
|
||||
split_fields_and_types(t, [{field, default}|fields], [{field, type}|types])
|
||||
end
|
||||
|
||||
defp split_fields_and_types(tag, [{ field, { :::, _, [default, type] }}|t], fields, types) do
|
||||
split_fields_and_types(tag, t, [{ field, default }|fields], [{ field, type }|types])
|
||||
defp split_fields_and_types([{field, default}|t], fields, types) do
|
||||
split_fields_and_types(t, [{field, default}|fields], [{field, quote(do: term)}|types])
|
||||
end
|
||||
|
||||
defp split_fields_and_types(tag, [{ field, default }|t], fields, types) when is_atom(field) do
|
||||
split_fields_and_types(tag, t, [{ field, default }|fields], [{ field, quote(do: term) }|types])
|
||||
defp split_fields_and_types([field|t], fields, types) do
|
||||
split_fields_and_types(t, [field|fields], [{field, quote(do: term)}|types])
|
||||
end
|
||||
|
||||
defp split_fields_and_types(tag, [field|t], fields, types) when is_atom(field) do
|
||||
split_fields_and_types(tag, t, [{ field, nil }|fields], [{ field, quote(do: term) }|types])
|
||||
end
|
||||
|
||||
defp split_fields_and_types(tag, [other|_], _fields, _types) do
|
||||
raise ArgumentError, message: "#{tag} fields must be atoms, got: #{Macro.to_string other}"
|
||||
end
|
||||
|
||||
defp split_fields_and_types(_tag, [], fields, types) do
|
||||
{ :lists.reverse(fields), :lists.reverse(types) }
|
||||
defp split_fields_and_types([], fields, types) do
|
||||
{:lists.reverse(fields), :lists.reverse(types)}
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -80,28 +87,28 @@ defmodule Record.Backend do
|
||||
Creates a new record with the given default fields and keyword values.
|
||||
"""
|
||||
def create(atom, fields, keyword, caller) do
|
||||
in_match = caller.in_match?
|
||||
in_match = Macro.Env.in_match?(caller)
|
||||
|
||||
{ match, remaining } =
|
||||
Enum.map_reduce(fields, keyword, fn({ field, default }, each_keyword) ->
|
||||
{match, remaining} =
|
||||
Enum.map_reduce(fields, keyword, fn({field, default}, each_keyword) ->
|
||||
new_fields =
|
||||
case Keyword.has_key?(each_keyword, field) do
|
||||
true -> Keyword.get(each_keyword, field)
|
||||
false ->
|
||||
case in_match do
|
||||
true -> { :_, [], nil }
|
||||
true -> {:_, [], nil}
|
||||
false -> Macro.escape(default)
|
||||
end
|
||||
end
|
||||
|
||||
{ new_fields, Keyword.delete(each_keyword, field) }
|
||||
{new_fields, Keyword.delete(each_keyword, field)}
|
||||
end)
|
||||
|
||||
case remaining do
|
||||
[] ->
|
||||
{ :{}, [], [atom|match] }
|
||||
{:{}, [], [atom|match]}
|
||||
_ ->
|
||||
keys = for { key, _ } <- remaining, do: key
|
||||
keys = for {key, _} <- remaining, do: key
|
||||
raise ArgumentError, message: "record #{inspect atom} does not have the key: #{inspect hd(keys)}"
|
||||
end
|
||||
end
|
||||
@@ -110,11 +117,11 @@ defmodule Record.Backend do
|
||||
Updates a record given by var with the given keyword.
|
||||
"""
|
||||
def update(atom, fields, var, keyword, caller) do
|
||||
if caller.in_match? do
|
||||
if Macro.Env.in_match?(caller) do
|
||||
raise ArgumentError, message: "cannot invoke update style macro inside match"
|
||||
end
|
||||
|
||||
Enum.reduce keyword, var, fn({ key, value }, acc) ->
|
||||
Enum.reduce keyword, var, fn({key, value}, acc) ->
|
||||
index = find_index(fields, key, 0)
|
||||
if index do
|
||||
quote do
|
||||
@@ -140,7 +147,7 @@ defmodule Record.Backend do
|
||||
end
|
||||
end
|
||||
|
||||
defp find_index([{ k, _ }|_], k, i), do: i + 2
|
||||
defp find_index([{ _, _ }|t], k, i), do: find_index(t, k, i + 1)
|
||||
defp find_index([{k, _}|_], k, i), do: i + 2
|
||||
defp find_index([{_, _}|t], k, i), do: find_index(t, k, i + 1)
|
||||
defp find_index([], _k, _i), do: nil
|
||||
end
|
||||
|
||||
@@ -22,7 +22,7 @@ defmodule Record.Deprecated do
|
||||
end
|
||||
end
|
||||
|
||||
defp record_check!([{ field, { :::, _, [_, _] }}|_]) when is_atom(field) do
|
||||
defp record_check!([{field, {:::, _, [_, _]}}|_]) when is_atom(field) do
|
||||
raise ArgumentError, message: "typespecs are not supported inlined with defrecord, " <>
|
||||
"please use record_type instead"
|
||||
end
|
||||
@@ -32,7 +32,7 @@ defmodule Record.Deprecated do
|
||||
|
||||
def defrecordp(name, tag, fields) do
|
||||
case recordp_split(fields, [], [], false) do
|
||||
{ :ok, fields, types, def_type } ->
|
||||
{:ok, fields, types, def_type} ->
|
||||
types = Macro.escape(types)
|
||||
|
||||
# bind_quoted isn't available when bootstrapping record
|
||||
@@ -41,7 +41,7 @@ defmodule Record.Deprecated do
|
||||
|
||||
if def_type do
|
||||
type = binary_to_atom(atom_to_binary(name) <> "_t")
|
||||
@typep unquote(type)() :: { unquote(tag || name), unquote_splicing(types) }
|
||||
@typep unquote(type)() :: {unquote(tag || name), unquote_splicing(types)}
|
||||
end
|
||||
end
|
||||
|
||||
@@ -62,8 +62,8 @@ defmodule Record.Deprecated do
|
||||
end
|
||||
end
|
||||
|
||||
defp recordp_split([{ field, { :::, _, [default, type] }}|t], defaults, types, _) do
|
||||
recordp_split(t, [{ field, default }|defaults], [type|types], true)
|
||||
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
|
||||
@@ -71,7 +71,7 @@ defmodule Record.Deprecated do
|
||||
end
|
||||
|
||||
defp recordp_split([], defaults, types, def_type) do
|
||||
{ :ok, :lists.reverse(defaults), :lists.reverse(types), def_type }
|
||||
{:ok, :lists.reverse(defaults), :lists.reverse(types), def_type}
|
||||
end
|
||||
|
||||
defp recordp_split(_, _, _, _) do
|
||||
@@ -98,15 +98,15 @@ defmodule Record.Deprecated do
|
||||
if env == Macro.Env do
|
||||
Module.eval_quoted(env, contents, [], [])
|
||||
else
|
||||
Module.eval_quoted(env.module, contents, [], env.location)
|
||||
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) }
|
||||
{name, default} = convert_value(value)
|
||||
{name, default, find_spec(types, name)}
|
||||
end
|
||||
|
||||
contents = [
|
||||
@@ -116,12 +116,12 @@ defmodule Record.Deprecated do
|
||||
|
||||
# We need to handle bootstraping
|
||||
cond do
|
||||
:code.ensure_loaded(Kernel.Typespec) != { :module, Kernel.Typespec } ->
|
||||
:code.ensure_loaded(Kernel.Typespec) != {:module, Kernel.Typespec} ->
|
||||
nil
|
||||
env == Macro.Env ->
|
||||
Module.eval_quoted(env, contents, [], [])
|
||||
true ->
|
||||
Module.eval_quoted(env.module, contents, [], env.location)
|
||||
Module.eval_quoted(env.module, contents, [], Macro.Env.location(env))
|
||||
end
|
||||
end
|
||||
|
||||
@@ -129,8 +129,8 @@ defmodule Record.Deprecated do
|
||||
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) }
|
||||
{key, value} = convert_value(value)
|
||||
{key, Macro.escape(value)}
|
||||
end
|
||||
|
||||
tag = tag || name
|
||||
@@ -149,7 +149,7 @@ defmodule Record.Deprecated do
|
||||
end
|
||||
end
|
||||
|
||||
Module.eval_quoted(env.module, contents, [], env.location)
|
||||
Module.eval_quoted(env.module, contents, [], Macro.Env.location(env))
|
||||
end
|
||||
|
||||
## Callbacks
|
||||
@@ -161,7 +161,7 @@ defmodule Record.Deprecated do
|
||||
end
|
||||
|
||||
def __on_definition__(env, kind, name, args, _guards, _body) do
|
||||
tuple = { name, length(args) }
|
||||
tuple = {name, length(args)}
|
||||
module = env.module
|
||||
functions = Module.get_attribute(module, :record_optimizable)
|
||||
|
||||
@@ -188,10 +188,10 @@ defmodule Record.Deprecated do
|
||||
raise ArgumentError, message: "expected contents inside brackets to be a keyword list or an atom, got: #{inspect keyword}"
|
||||
end
|
||||
|
||||
in_match = caller.in_match?
|
||||
in_match = Macro.Env.in_match?(caller)
|
||||
|
||||
has_underscore_value = Keyword.has_key?(keyword, :_)
|
||||
underscore_value = Keyword.get(keyword, :_, { :_, [], nil })
|
||||
underscore_value = Keyword.get(keyword, :_, {:_, [], nil})
|
||||
keyword = Keyword.delete keyword, :_
|
||||
|
||||
iterator = fn({field, default}, each_keyword) ->
|
||||
@@ -205,16 +205,16 @@ defmodule Record.Deprecated do
|
||||
end
|
||||
end
|
||||
|
||||
{ new_fields, Keyword.delete(each_keyword, field) }
|
||||
{new_fields, Keyword.delete(each_keyword, field)}
|
||||
end
|
||||
|
||||
{ match, remaining } = :lists.mapfoldl(iterator, keyword, fields)
|
||||
{match, remaining} = :lists.mapfoldl(iterator, keyword, fields)
|
||||
|
||||
case remaining do
|
||||
[] ->
|
||||
{ :{}, [], [atom|match] }
|
||||
{:{}, [], [atom|match]}
|
||||
_ ->
|
||||
keys = for { key, _ } <- remaining, do: key
|
||||
keys = for {key, _} <- remaining, do: key
|
||||
raise ArgumentError, message: "record #{inspect atom} does not have the key: #{inspect hd(keys)}"
|
||||
end
|
||||
end
|
||||
@@ -235,11 +235,11 @@ defmodule Record.Deprecated do
|
||||
raise ArgumentError, message: "expected arguments to be compile time keywords"
|
||||
end
|
||||
|
||||
if caller.in_match? do
|
||||
if Macro.Env.in_match?(caller) do
|
||||
raise ArgumentError, message: "cannot invoke update style macro inside match context"
|
||||
end
|
||||
|
||||
Enum.reduce keyword, var, fn({ key, value }, acc) ->
|
||||
Enum.reduce keyword, var, fn({key, value}, acc) ->
|
||||
index = find_index(fields, key, 0)
|
||||
if index do
|
||||
quote do
|
||||
@@ -265,7 +265,7 @@ defmodule Record.Deprecated do
|
||||
## Function generation
|
||||
|
||||
defp reflection(values) do
|
||||
quoted = for { k, _ } <- values do
|
||||
quoted = for {k, _} <- values do
|
||||
index = find_index(values, k, 0)
|
||||
quote do
|
||||
def __record__(:index, unquote(k)), do: unquote(index + 1)
|
||||
@@ -290,23 +290,23 @@ defmodule Record.Deprecated do
|
||||
end
|
||||
|
||||
defp initializer(values) do
|
||||
defaults = for { _, value } <- values, do: value
|
||||
defaults = for {_, value} <- values, do: value
|
||||
|
||||
# For each value, define a piece of code that will receive
|
||||
# an ordered dict of options (opts) and it will try to fetch
|
||||
# the given key from the ordered dict, falling back to the
|
||||
# default value if one does not exist.
|
||||
atom_selective = for { k, v } <- values, do: initialize_lookup(k, v)
|
||||
string_selective = for { k, v } <- values, do: initialize_lookup(atom_to_binary(k), v)
|
||||
atom_selective = for {k, v} <- values, do: initialize_lookup(k, v)
|
||||
string_selective = for {k, v} <- values, do: initialize_lookup(atom_to_binary(k), v)
|
||||
|
||||
quote do
|
||||
@doc false
|
||||
def new(), do: new([])
|
||||
|
||||
@doc false
|
||||
def new([]), do: { __MODULE__, unquote_splicing(defaults) }
|
||||
def new([{key, _}|_] = opts) when is_atom(key), do: { __MODULE__, unquote_splicing(atom_selective) }
|
||||
def new([{key, _}|_] = opts) when is_binary(key), do: { __MODULE__, unquote_splicing(string_selective) }
|
||||
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
|
||||
|
||||
@@ -320,9 +320,9 @@ defmodule Record.Deprecated do
|
||||
end
|
||||
|
||||
defp conversions(values) do
|
||||
sorted = for { k, _ } <- values do
|
||||
sorted = for {k, _} <- values do
|
||||
index = find_index(values, k, 0)
|
||||
{ k, quote(do: :erlang.element(unquote(index + 2), record)) }
|
||||
{k, quote(do: :erlang.element(unquote(index + 2), record))}
|
||||
end
|
||||
|
||||
quote do
|
||||
@@ -333,11 +333,11 @@ defmodule Record.Deprecated do
|
||||
end
|
||||
end
|
||||
|
||||
defp accessors([{ :__exception__, _ }|t], 1) do
|
||||
defp accessors([{:__exception__, _}|t], 1) do
|
||||
accessors(t, 2)
|
||||
end
|
||||
|
||||
defp accessors([{ key, _default }|t], i) do
|
||||
defp accessors([{key, _default}|t], i) do
|
||||
update = binary_to_atom "update_" <> atom_to_binary(key)
|
||||
|
||||
contents = quote do
|
||||
@@ -374,8 +374,8 @@ defmodule Record.Deprecated do
|
||||
string_fields =
|
||||
for {key, _default} <- values, do: updater_lookup(atom_to_binary(key), key, values)
|
||||
|
||||
atom_contents = quote do: { __MODULE__, unquote_splicing(atom_fields) }
|
||||
string_contents = quote do: { __MODULE__, unquote_splicing(string_fields) }
|
||||
atom_contents = quote do: {__MODULE__, unquote_splicing(atom_fields)}
|
||||
string_contents = quote do: {__MODULE__, unquote_splicing(string_fields)}
|
||||
|
||||
quote do
|
||||
@doc false
|
||||
@@ -407,8 +407,8 @@ defmodule Record.Deprecated do
|
||||
quote do
|
||||
@record_optimized true
|
||||
@record_optimizable []
|
||||
@before_compile { unquote(__MODULE__), :__before_compile__ }
|
||||
@on_definition { unquote(__MODULE__), :__on_definition__ }
|
||||
@before_compile {unquote(__MODULE__), :__before_compile__}
|
||||
@on_definition {unquote(__MODULE__), :__on_definition__}
|
||||
end
|
||||
end
|
||||
|
||||
@@ -419,12 +419,12 @@ defmodule Record.Deprecated do
|
||||
## Types/specs generation
|
||||
|
||||
defp core_specs(values) do
|
||||
types = for { _, _, spec } <- values, do: spec
|
||||
options = for { k, _, v } <- values, do: { k, v }
|
||||
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) }
|
||||
@type t :: {__MODULE__, unquote_splicing(types)}
|
||||
end
|
||||
|
||||
unless Kernel.Typespec.defines_type?(__MODULE__, :options, 0) do
|
||||
@@ -441,11 +441,11 @@ defmodule Record.Deprecated do
|
||||
end
|
||||
end
|
||||
|
||||
defp accessor_specs([{ :__exception__, _, _ }|t], 1, acc) do
|
||||
defp accessor_specs([{:__exception__, _, _}|t], 1, acc) do
|
||||
accessor_specs(t, 2, acc)
|
||||
end
|
||||
|
||||
defp accessor_specs([{ key, _default, spec }|t], i, acc) do
|
||||
defp accessor_specs([{key, _default, spec}|t], i, acc) do
|
||||
update = binary_to_atom "update_" <> atom_to_binary(key)
|
||||
|
||||
contents = quote do
|
||||
@@ -464,15 +464,15 @@ defmodule Record.Deprecated do
|
||||
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({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) 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({atom, other}) when is_atom(atom), do:
|
||||
{atom, check_value(atom, other)}
|
||||
|
||||
defp convert_value({ field, _ }), do:
|
||||
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
|
||||
@@ -486,8 +486,8 @@ defmodule Record.Deprecated do
|
||||
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
|
||||
unless :erlang.fun_info(other, :env) == {:env, []} and
|
||||
:erlang.fun_info(other, :type) == {:type, :external} do
|
||||
raise ArgumentError, message: "record field default value #{inspect atom} can only contain " <>
|
||||
"functions that point to an existing &Mod.fun/arity"
|
||||
end
|
||||
@@ -500,12 +500,12 @@ defmodule Record.Deprecated do
|
||||
|
||||
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, _}|_], 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
|
||||
matches = for {k, v} <- types, name == k, do: v
|
||||
case matches do
|
||||
[h|_] -> h
|
||||
_ -> quote do: term
|
||||
@@ -517,7 +517,7 @@ 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) }
|
||||
escaped = for {k, v} <- opts, do: {k, Macro.escape(v)}
|
||||
|
||||
quote do
|
||||
@record_types Keyword.merge(@record_types || [], unquote(escaped))
|
||||
|
||||
@@ -4,7 +4,7 @@ defmodule Record.Extractor do
|
||||
# Retrieve a record definition from an Erlang file using
|
||||
# the same lookup as the *include* attribute from Erlang modules.
|
||||
def extract(name, from: file) when is_binary(file) do
|
||||
file = String.to_char_list!(file)
|
||||
file = List.from_char_data!(file)
|
||||
|
||||
realfile =
|
||||
case :code.where_is_file(file) do
|
||||
@@ -18,10 +18,10 @@ defmodule Record.Extractor do
|
||||
# Retrieve a record definition from an Erlang file using
|
||||
# the same lookup as the *include_lib* attribute from Erlang modules.
|
||||
def extract(name, from_lib: file) when is_binary(file) do
|
||||
[app|path] = :filename.split(String.to_char_list!(file))
|
||||
[app|path] = :filename.split(List.from_char_data!(file))
|
||||
|
||||
case :code.lib_dir(list_to_atom(app)) do
|
||||
{ :error, _ } ->
|
||||
{:error, _} ->
|
||||
raise ArgumentError, message: "lib file #{file} could not be found"
|
||||
libpath ->
|
||||
extract_record name, :filename.join([libpath|path])
|
||||
@@ -41,7 +41,7 @@ defmodule Record.Extractor do
|
||||
|
||||
# Parse the given file and extract all existent records.
|
||||
defp extract_records(form) do
|
||||
for { :attribute, _, :record, record } <- form, do: record
|
||||
for {:attribute, _, :record, record} <- form, do: record
|
||||
end
|
||||
|
||||
# Read a file and return its abstract syntax form that also
|
||||
@@ -49,7 +49,7 @@ defmodule Record.Extractor do
|
||||
# by using Erlang's epp_dodger.
|
||||
defp read_file(file) do
|
||||
case :epp_dodger.quick_parse_file(file) do
|
||||
{ :ok, form } ->
|
||||
{:ok, form} ->
|
||||
form
|
||||
other ->
|
||||
raise "error parsing file #{file}, got: #{inspect(other)}"
|
||||
@@ -59,34 +59,34 @@ defmodule Record.Extractor do
|
||||
# Parse a tuple with name and fields and returns a
|
||||
# list of tuples where the first element is the field
|
||||
# and the second is its default value.
|
||||
defp parse_record({ _name, fields }, form) do
|
||||
cons = List.foldr fields, { nil, 0 }, fn f, acc ->
|
||||
{ :cons, 0, parse_field(f), acc }
|
||||
defp parse_record({_name, fields}, form) do
|
||||
cons = List.foldr fields, {nil, 0}, fn f, acc ->
|
||||
{:cons, 0, parse_field(f), acc}
|
||||
end
|
||||
eval_record(cons, form)
|
||||
end
|
||||
|
||||
defp parse_field({ :typed_record_field, record_field, _type }) do
|
||||
defp parse_field({:typed_record_field, record_field, _type}) do
|
||||
parse_field(record_field)
|
||||
end
|
||||
|
||||
defp parse_field({ :record_field, _, key }) do
|
||||
{ :tuple, 0, [key, {:atom, 0, :undefined}] }
|
||||
defp parse_field({:record_field, _, key}) do
|
||||
{:tuple, 0, [key, {:atom, 0, :undefined}]}
|
||||
end
|
||||
|
||||
defp parse_field({ :record_field, _, key, value }) do
|
||||
{ :tuple, 0, [key, value] }
|
||||
defp parse_field({:record_field, _, key, value}) do
|
||||
{:tuple, 0, [key, value]}
|
||||
end
|
||||
|
||||
defp eval_record(cons, form) do
|
||||
form = form ++
|
||||
[ { :function, 0, :hello, 0, [
|
||||
{ :clause, 0, [], [], [ cons ] } ] } ]
|
||||
[ {:function, 0, :hello, 0, [
|
||||
{:clause, 0, [], [], [ cons ]} ]} ]
|
||||
|
||||
{ :function, 0, :hello, 0, [
|
||||
{ :clause, 0, [], [], [ record_ast ] } ] } = :erl_expand_records.module(form, []) |> List.last
|
||||
{:function, 0, :hello, 0, [
|
||||
{:clause, 0, [], [], [ record_ast ]} ]} = :erl_expand_records.module(form, []) |> List.last
|
||||
|
||||
{ :value, record, _ } = :erl_eval.expr(record_ast, [])
|
||||
{:value, record, _} = :erl_eval.expr(record_ast, [])
|
||||
record
|
||||
end
|
||||
end
|
||||
|
||||
+216
-86
@@ -1,8 +1,10 @@
|
||||
defmodule Regex do
|
||||
@moduledoc ~S"""
|
||||
Regular expressions for Elixir built on top of the `re` module
|
||||
in the Erlang Standard Library. More information can be found
|
||||
in the [`re` documentation](http://www.erlang.org/doc/man/re.html).
|
||||
Regular expressions for Elixir built on top of Erlang's `re` module.
|
||||
|
||||
As the `re` module, Regex is based on PCRE
|
||||
(Perl Compatible Regular Expressions). More information can be
|
||||
found in the [`re` documentation](http://www.erlang.org/doc/man/re.html).
|
||||
|
||||
Regular expressions in Elixir can be created using `Regex.compile!/2`
|
||||
or using the special form with [`~r`](Kernel.html#sigil_r/2):
|
||||
@@ -10,12 +12,18 @@ defmodule Regex do
|
||||
# A simple regular expressions that matches foo anywhere in the string
|
||||
~r/foo/
|
||||
|
||||
# A regular expression with case insensitive options
|
||||
~r/foo/i
|
||||
# A regular expression with case insensitive and unicode options
|
||||
~r/foo/iu
|
||||
|
||||
The `re` module provides several options, the ones available in Elixir, followed by
|
||||
their shortcut in parenthesis, are:
|
||||
A Regex is represented internally as the `Regex` struct. Therefore,
|
||||
`%Regex{}` can be used whenever there is a need to match on them.
|
||||
|
||||
## Modifiers
|
||||
|
||||
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
|
||||
@@ -54,14 +62,13 @@ defmodule Regex do
|
||||
|
||||
* `:none` - do not return matching subpatterns at all;
|
||||
|
||||
* `:groups` - captures only named captures in the Regex;
|
||||
* `:all_names` - captures all names in the Regex;
|
||||
|
||||
* `list(binary)` - a list of named captures to capture;
|
||||
|
||||
"""
|
||||
|
||||
defrecordp :regex, Regex, [:re_pattern, :source, :options]
|
||||
@type t :: { Regex, term, binary, binary, [atom] | nil }
|
||||
defstruct re_pattern: nil :: term, source: "" :: binary, opts: "" :: binary
|
||||
|
||||
defexception CompileError, message: "regex could not be compiled"
|
||||
|
||||
@@ -72,8 +79,8 @@ defmodule Regex do
|
||||
representing the same regex options given to the `~r` sigil,
|
||||
or a list of options, as expected by the [Erlang `re` docs](http://www.erlang.org/doc/man/re.html).
|
||||
|
||||
It returns `{ :ok, regex }` in case of success,
|
||||
`{ :error, reason }` otherwise.
|
||||
It returns `{:ok, regex}` in case of success,
|
||||
`{:error, reason}` otherwise.
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -84,18 +91,16 @@ defmodule Regex do
|
||||
{:error, {'nothing to repeat', 0}}
|
||||
|
||||
"""
|
||||
@spec compile(binary, binary | [term]) :: { :ok, t } | { :error, any }
|
||||
@spec compile(binary, binary | [term]) :: {:ok, t} | {:error, any}
|
||||
def compile(source, options \\ "")
|
||||
|
||||
def compile(source, options) when is_binary(options) do
|
||||
case translate_options(options) do
|
||||
{ :error, rest } ->
|
||||
{ :error, { :invalid_option, rest } }
|
||||
{:error, rest} ->
|
||||
{:error, {:invalid_option, rest}}
|
||||
|
||||
translated_options ->
|
||||
# Always use the unicode option, we don't have a latin1 legacy like
|
||||
# Erlang.
|
||||
compile(source, [:unicode|translated_options], options)
|
||||
compile(source, translated_options, options)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -105,8 +110,8 @@ defmodule Regex do
|
||||
|
||||
defp compile(source, opts, doc_opts) when is_binary(source) do
|
||||
case :re.compile(source, opts) do
|
||||
{ :ok, re_pattern } ->
|
||||
{ :ok, regex(re_pattern: re_pattern, source: source, options: doc_opts) }
|
||||
{:ok, re_pattern} ->
|
||||
{:ok, %Regex{re_pattern: re_pattern, source: source, opts: doc_opts}}
|
||||
error ->
|
||||
error
|
||||
end
|
||||
@@ -118,8 +123,8 @@ defmodule Regex do
|
||||
"""
|
||||
def compile!(source, options \\ "") do
|
||||
case compile(source, options) do
|
||||
{ :ok, regex } -> regex
|
||||
{ :error, { reason, at } } -> raise Regex.CompileError, message: "#{reason} at position #{at}"
|
||||
{:ok, regex} -> regex
|
||||
{:error, {reason, at}} -> raise Regex.CompileError, message: "#{reason} at position #{at}"
|
||||
end
|
||||
end
|
||||
|
||||
@@ -135,8 +140,8 @@ defmodule Regex do
|
||||
false
|
||||
|
||||
"""
|
||||
def match?(regex(re_pattern: compiled), string) when is_binary(string) do
|
||||
:re.run(string, compiled, [{ :capture, :none }]) == :match
|
||||
def match?(%Regex{re_pattern: compiled}, string) when is_binary(string) do
|
||||
:re.run(string, compiled, [{:capture, :none}]) == :match
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -151,7 +156,7 @@ defmodule Regex do
|
||||
false
|
||||
|
||||
"""
|
||||
def regex?(regex()), do: true
|
||||
def regex?(%Regex{}), do: true
|
||||
def regex?(_), do: false
|
||||
|
||||
@doc """
|
||||
@@ -178,49 +183,45 @@ defmodule Regex do
|
||||
"""
|
||||
def run(regex, string, options \\ [])
|
||||
|
||||
def run(regex(re_pattern: compiled) = regex, string, options) when is_binary(string) do
|
||||
return = Keyword.get(options, :return, :binary)
|
||||
def run(%Regex{re_pattern: compiled}, string, options) when is_binary(string) do
|
||||
return = Keyword.get(options, :return, :binary)
|
||||
captures = Keyword.get(options, :capture, :all)
|
||||
|
||||
captures =
|
||||
case Keyword.get(options, :capture, :all) do
|
||||
:groups -> groups(regex)
|
||||
others -> others
|
||||
end
|
||||
|
||||
case :re.run(string, compiled, [{ :capture, captures, return }]) do
|
||||
case :re.run(string, compiled, [{:capture, captures, return}]) do
|
||||
:nomatch -> nil
|
||||
:match -> []
|
||||
{ :match, results } -> results
|
||||
{:match, results} -> results
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns the given captures as a keyword list or `nil` if no captures
|
||||
are found. The option `:return` can be set to `:index` to get indexes
|
||||
Returns the given captures as a map or `nil` if no captures are
|
||||
found. The option `:return` can be set to `:index` to get indexes
|
||||
back.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Regex.named_captures(~r/c(?<foo>d)/, "abcd")
|
||||
[foo: "d"]
|
||||
%{"foo" => "d"}
|
||||
|
||||
iex> Regex.named_captures(~r/a(?<foo>b)c(?<bar>d)/, "abcd")
|
||||
[bar: "d", foo: "b"]
|
||||
%{"bar" => "d", "foo" => "b"}
|
||||
|
||||
iex> Regex.named_captures(~r/a(?<foo>b)c(?<bar>d)/, "efgh")
|
||||
nil
|
||||
|
||||
"""
|
||||
def named_captures(regex, string, options \\ []) when is_binary(string) do
|
||||
options = [capture: :groups] ++ options
|
||||
names = names(regex)
|
||||
options = Keyword.put(options, :capture, names)
|
||||
results = run(regex, string, options)
|
||||
if results, do: Enum.zip(groups(regex), results)
|
||||
if results, do: Enum.zip(names, results) |> Enum.into(%{})
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns the underlying `re_pattern` in the regular expression.
|
||||
"""
|
||||
def re_pattern(regex(re_pattern: compiled)) do
|
||||
def re_pattern(%Regex{re_pattern: compiled}) do
|
||||
compiled
|
||||
end
|
||||
|
||||
@@ -233,7 +234,7 @@ defmodule Regex do
|
||||
"foo"
|
||||
|
||||
"""
|
||||
def source(regex(source: source)) do
|
||||
def source(%Regex{source: source}) do
|
||||
source
|
||||
end
|
||||
|
||||
@@ -246,22 +247,22 @@ defmodule Regex do
|
||||
"m"
|
||||
|
||||
"""
|
||||
def opts(regex(options: options)) do
|
||||
options
|
||||
def opts(%Regex{opts: opts}) do
|
||||
opts
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns a list of named groups in the regex.
|
||||
Returns a list of names in the regex.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Regex.groups(~r/(?<foo>bar)/)
|
||||
[:foo]
|
||||
iex> Regex.names(~r/(?<foo>bar)/)
|
||||
["foo"]
|
||||
|
||||
"""
|
||||
def groups(regex(re_pattern: re_pattern)) do
|
||||
{ :namelist, groups } = :re.inspect(re_pattern, :namelist)
|
||||
for group <- groups, do: binary_to_atom(group)
|
||||
def names(%Regex{re_pattern: re_pattern}) do
|
||||
{:namelist, names} = :re.inspect(re_pattern, :namelist)
|
||||
names
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -290,20 +291,15 @@ defmodule Regex do
|
||||
"""
|
||||
def scan(regex, string, options \\ [])
|
||||
|
||||
def scan(regex(re_pattern: compiled) = regex, string, options) when is_binary(string) do
|
||||
return = Keyword.get(options, :return, :binary)
|
||||
def scan(%Regex{re_pattern: compiled}, string, options) when is_binary(string) do
|
||||
return = Keyword.get(options, :return, :binary)
|
||||
captures = Keyword.get(options, :capture, :all)
|
||||
options = [{:capture, captures, return}, :global]
|
||||
|
||||
captures =
|
||||
case Keyword.get(options, :capture, :all) do
|
||||
:groups -> groups(regex)
|
||||
others -> others
|
||||
end
|
||||
|
||||
options = [{ :capture, captures, return }, :global]
|
||||
case :re.run(string, compiled, options) do
|
||||
:match -> []
|
||||
:nomatch -> []
|
||||
{ :match, results } -> results
|
||||
{:match, results} -> results
|
||||
end
|
||||
end
|
||||
|
||||
@@ -312,11 +308,11 @@ defmodule Regex do
|
||||
|
||||
## Options
|
||||
|
||||
* `:global` - splits the string in all parts unless set to `false`,
|
||||
which leads the string to be split in 2. Defaults to true;
|
||||
|
||||
* `:parts` - when specified, splits the string into maximum the number
|
||||
of given parts;
|
||||
* `: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;
|
||||
|
||||
@@ -341,12 +337,15 @@ defmodule Regex do
|
||||
|
||||
def split(regex, string, options \\ [])
|
||||
|
||||
def split(regex(re_pattern: compiled), string, options) when is_binary(string) do
|
||||
def split(%Regex{re_pattern: compiled}, string, options) when is_binary(string) do
|
||||
if options[:global] != nil do
|
||||
IO.write :stderr, "Support for :global in Regex.split/3 is deprecated, please use :parts instead\n#{Exception.format_stacktrace}"
|
||||
end
|
||||
parts =
|
||||
cond do
|
||||
Keyword.get(options, :global) == false -> 2
|
||||
p = Keyword.get(options, :parts) -> p
|
||||
true -> :infinity
|
||||
Keyword.get(options, :global) == false -> 2
|
||||
p = Keyword.get(options, :parts) -> if p == 0, do: :infinity, else: p
|
||||
true -> :infinity
|
||||
end
|
||||
|
||||
opts = [return: :binary, parts: parts]
|
||||
@@ -363,10 +362,20 @@ defmodule Regex do
|
||||
Receives a regex, a binary and a replacement, returns a new
|
||||
binary where the all matches are replaced by replacement.
|
||||
|
||||
Inside the replacement, you can either give `&` to access the
|
||||
whole regular expression or `\N`, where `N` is in integer to access
|
||||
a specific matching parens. You can also set `:global` to `false`
|
||||
if you want to replace just the first occurrence.
|
||||
The replacement can be either a string or a function. The string
|
||||
is used as a replacement for every match and it allows specific
|
||||
captures to be accessed via `\N`, where `N` is the capture. In
|
||||
case `\0` is used, the whole match is inserted.
|
||||
|
||||
When the replacement is a function, the function may have arity
|
||||
N where each argument maps to a capture, with the first argument
|
||||
being the whole match. If the function expects more arguments
|
||||
than captures found, the remaining arguments will receive `""`.
|
||||
|
||||
## Options
|
||||
|
||||
* `:global` - when `false`, replaces only the first occurrence
|
||||
(defaults to true)
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -376,25 +385,145 @@ defmodule Regex do
|
||||
iex> Regex.replace(~r/b/, "abc", "d")
|
||||
"adc"
|
||||
|
||||
iex> Regex.replace(~r/b/, "abc", "[&]")
|
||||
iex> Regex.replace(~r/b/, "abc", "[\\0]")
|
||||
"a[b]c"
|
||||
|
||||
iex> Regex.replace(~r/b/, "abc", "[\\&]")
|
||||
"a[&]c"
|
||||
iex> Regex.replace(~r/a(b|d)c/, "abcadc", "[\\1]")
|
||||
"[b][d]"
|
||||
|
||||
iex> Regex.replace(~r/(b)/, "abc", "[\\1]")
|
||||
"a[b]c"
|
||||
iex> Regex.replace(~r/a(b|d)c/, "abcadc", fn _, x -> "[#{x}]" end)
|
||||
"[b][d]"
|
||||
|
||||
"""
|
||||
def replace(regex, string, replacement, options \\ [])
|
||||
|
||||
def replace(regex(re_pattern: compiled), string, replacement, options) when is_binary(string) do
|
||||
opts = if Keyword.get(options, :global) != false, do: [:global], else: []
|
||||
opts = [{ :return, :binary }|opts]
|
||||
:re.replace(string, compiled, replacement, opts)
|
||||
def replace(regex, string, replacement, options) when is_binary(replacement) do
|
||||
do_replace(regex, string, precompile_replacement(replacement), options)
|
||||
end
|
||||
|
||||
{ :ok, pattern } = :re.compile(~S"[.^$*+?()[{\\\|\s#]", [:unicode])
|
||||
def replace(regex, string, replacement, options) when is_function(replacement) do
|
||||
{:arity, arity} = :erlang.fun_info(replacement, :arity)
|
||||
do_replace(regex, string, {replacement, arity}, options)
|
||||
end
|
||||
|
||||
defp do_replace(%Regex{re_pattern: compiled}, string, replacement, options) do
|
||||
opts = if Keyword.get(options, :global) != false, do: [:global], else: []
|
||||
opts = [{:capture, :all, :index}|opts]
|
||||
|
||||
case :re.run(string, compiled, opts) do
|
||||
:nomatch ->
|
||||
string
|
||||
{:match, [mlist|t]} when is_list(mlist) ->
|
||||
apply_list(string, replacement, [mlist|t]) |> iodata_to_binary
|
||||
{:match, slist} ->
|
||||
apply_list(string, replacement, [slist]) |> iodata_to_binary
|
||||
end
|
||||
end
|
||||
|
||||
defp precompile_replacement(""),
|
||||
do: []
|
||||
|
||||
defp precompile_replacement(<<?\\, x, rest :: binary>>) when x < ?0 or x > ?9 do
|
||||
case precompile_replacement(rest) do
|
||||
[head | t] when is_binary(head) ->
|
||||
[<<x, head :: binary>> | t]
|
||||
other ->
|
||||
[<<x>> | other]
|
||||
end
|
||||
end
|
||||
|
||||
defp precompile_replacement(<<?\\, rest :: binary>>) when byte_size(rest) > 0 do
|
||||
{ns, rest} = pick_int(rest)
|
||||
[list_to_integer(ns) | precompile_replacement(rest)]
|
||||
end
|
||||
|
||||
defp precompile_replacement(<<x, rest :: binary>>) do
|
||||
case precompile_replacement(rest) do
|
||||
[head | t] when is_binary(head) ->
|
||||
[<<x, head :: binary>> | t]
|
||||
other ->
|
||||
[<<x>> | other]
|
||||
end
|
||||
end
|
||||
|
||||
defp pick_int(<<x, rest :: binary>>) when x in ?0..?9 do
|
||||
{found, rest} = pick_int(rest)
|
||||
{[x|found], rest}
|
||||
end
|
||||
|
||||
defp pick_int(bin) do
|
||||
{[], bin}
|
||||
end
|
||||
|
||||
defp apply_list(string, replacement, list) do
|
||||
apply_list(string, string, 0, replacement, list)
|
||||
end
|
||||
|
||||
defp apply_list(_, "", _, _, []) do
|
||||
[]
|
||||
end
|
||||
|
||||
defp apply_list(_, string, _, _, []) do
|
||||
string
|
||||
end
|
||||
|
||||
defp apply_list(whole, string, pos, replacement, [[{mpos, _} | _] | _] = list) when mpos > pos do
|
||||
length = mpos - pos
|
||||
<<untouched :: [size(length), binary], rest :: binary>> = string
|
||||
[untouched | apply_list(whole, rest, mpos, replacement, list)]
|
||||
end
|
||||
|
||||
defp apply_list(whole, string, pos, replacement, [[{mpos, length} | _] = head | tail]) when mpos == pos do
|
||||
<<_ :: [size(length), binary], rest :: binary>> = string
|
||||
new_data = apply_replace(whole, replacement, head)
|
||||
[new_data | apply_list(whole, rest, pos + length, replacement, tail)]
|
||||
end
|
||||
|
||||
defp apply_replace(string, {fun, arity}, indexes) do
|
||||
apply(fun, get_indexes(string, indexes, arity))
|
||||
end
|
||||
|
||||
defp apply_replace(_, [bin], _) when is_binary(bin) do
|
||||
bin
|
||||
end
|
||||
|
||||
defp apply_replace(string, repl, indexes) do
|
||||
indexes = list_to_tuple(indexes)
|
||||
|
||||
for part <- repl do
|
||||
cond do
|
||||
is_binary(part) ->
|
||||
part
|
||||
part > tuple_size(indexes) ->
|
||||
""
|
||||
true ->
|
||||
get_index(string, elem(indexes, part))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defp get_index(_string, {pos, _len}) when pos < 0 do
|
||||
""
|
||||
end
|
||||
|
||||
defp get_index(string, {pos, len}) do
|
||||
<<_ :: [size(pos), binary], res :: [size(len), binary], _ :: binary>> = string
|
||||
res
|
||||
end
|
||||
|
||||
defp get_indexes(_string, _, 0) do
|
||||
[]
|
||||
end
|
||||
|
||||
defp get_indexes(string, [], arity) do
|
||||
[""|get_indexes(string, [], arity - 1)]
|
||||
end
|
||||
|
||||
defp get_indexes(string, [h|t], arity) do
|
||||
[get_index(string, h)|get_indexes(string, t, arity - 1)]
|
||||
end
|
||||
|
||||
{:ok, pattern} = :re.compile(~S"[.^$*+?()[{\\\|\s#]", [:unicode])
|
||||
@escape_pattern pattern
|
||||
|
||||
@doc ~S"""
|
||||
@@ -411,7 +540,7 @@ defmodule Regex do
|
||||
"""
|
||||
@spec escape(String.t) :: String.t
|
||||
def escape(string) when is_binary(string) do
|
||||
:re.replace(string, @escape_pattern, "\\\\&", [:global, { :return, :binary }])
|
||||
:re.replace(string, @escape_pattern, "\\\\&", [:global, {:return, :binary}])
|
||||
end
|
||||
|
||||
# Helpers
|
||||
@@ -433,6 +562,7 @@ defmodule Regex do
|
||||
translate_options(t)
|
||||
end
|
||||
|
||||
defp translate_options(<<?u, t :: binary>>), do: [:unicode|translate_options(t)]
|
||||
defp translate_options(<<?i, t :: binary>>), do: [:caseless|translate_options(t)]
|
||||
defp translate_options(<<?x, t :: binary>>), do: [:extended|translate_options(t)]
|
||||
defp translate_options(<<?f, t :: binary>>), do: [:firstline|translate_options(t)]
|
||||
@@ -440,5 +570,5 @@ defmodule Regex do
|
||||
defp translate_options(<<?s, t :: binary>>), do: [:dotall, {:newline, :anycrlf}|translate_options(t)]
|
||||
defp translate_options(<<?m, t :: binary>>), do: [:multiline|translate_options(t)]
|
||||
defp translate_options(<<>>), do: []
|
||||
defp translate_options(rest), do: { :error, rest }
|
||||
defp translate_options(rest), do: {:error, rest}
|
||||
end
|
||||
|
||||
+14
-14
@@ -30,13 +30,12 @@ defmodule Set do
|
||||
|
||||
@type value :: any
|
||||
@type values :: [ value ]
|
||||
@type t :: map | tuple
|
||||
@type t :: map
|
||||
|
||||
defcallback new :: t
|
||||
defcallback delete(t, value) :: t
|
||||
defcallback difference(t, t) :: t
|
||||
defcallback disjoint?(t, t) :: boolean
|
||||
defcallback empty(t) :: t
|
||||
defcallback equal?(t, t) :: boolean
|
||||
defcallback intersection(t, t) :: t
|
||||
defcallback member?(t, value) :: boolean
|
||||
@@ -52,6 +51,7 @@ defmodule Set do
|
||||
%{__struct__: x} when is_atom(x) ->
|
||||
x
|
||||
x when is_tuple(x) ->
|
||||
IO.write :stderr, "Using records with the Set module is deprecated, please use structs instead\n#{Exception.format_stacktrace}"
|
||||
elem(x, 0)
|
||||
x ->
|
||||
unsupported_set(x)
|
||||
@@ -99,8 +99,8 @@ defmodule Set do
|
||||
if target1 == target2 do
|
||||
target1.difference(set1, set2)
|
||||
else
|
||||
target2.reduce(set2, { :cont, set1 }, fn v, acc ->
|
||||
{ :cont, target1.delete(acc, v) }
|
||||
target2.reduce(set2, {:cont, set1}, fn v, acc ->
|
||||
{:cont, target1.delete(acc, v)}
|
||||
end) |> elem(1)
|
||||
end
|
||||
end
|
||||
@@ -129,10 +129,10 @@ defmodule Set do
|
||||
if target1 == target2 do
|
||||
target1.disjoint?(set1, set2)
|
||||
else
|
||||
target2.reduce(set2, { :cont, true }, fn member, acc ->
|
||||
target2.reduce(set2, {:cont, true}, fn member, acc ->
|
||||
case target1.member?(set1, member) do
|
||||
false -> { :cont, acc }
|
||||
_ -> { :halt, false }
|
||||
false -> {:cont, acc}
|
||||
_ -> {:halt, false}
|
||||
end
|
||||
end) |> elem(1)
|
||||
end
|
||||
@@ -201,8 +201,8 @@ defmodule Set do
|
||||
if target1 == target2 do
|
||||
target1.intersection(set1, set2)
|
||||
else
|
||||
target1.reduce(set1, { :cont, Collectable.empty(set1) }, fn v, acc ->
|
||||
{ :cont, if(target2.member?(set2, v), do: target1.put(acc, v), else: acc) }
|
||||
target1.reduce(set1, {:cont, Collectable.empty(set1)}, fn v, acc ->
|
||||
{:cont, if(target2.member?(set2, v), do: target1.put(acc, v), else: acc)}
|
||||
end) |> elem(1)
|
||||
end
|
||||
end
|
||||
@@ -318,17 +318,17 @@ defmodule Set do
|
||||
if target1 == target2 do
|
||||
target1.union(set1, set2)
|
||||
else
|
||||
target2.reduce(set2, { :cont, set1 }, fn v, acc ->
|
||||
{ :cont, target1.put(acc, v) }
|
||||
target2.reduce(set2, {:cont, set1}, fn v, acc ->
|
||||
{:cont, target1.put(acc, v)}
|
||||
end) |> elem(1)
|
||||
end
|
||||
end
|
||||
|
||||
defp do_subset?(target1, target2, set1, set2) do
|
||||
target1.reduce(set1, { :cont, true }, fn member, acc ->
|
||||
target1.reduce(set1, {:cont, true}, fn member, acc ->
|
||||
case target2.member?(set2, member) do
|
||||
true -> { :cont, acc }
|
||||
_ -> { :halt, false }
|
||||
true -> {:cont, acc}
|
||||
_ -> {:halt, false}
|
||||
end
|
||||
end) |> elem(1)
|
||||
end
|
||||
|
||||
+152
-149
@@ -89,47 +89,50 @@ defmodule Stream do
|
||||
returns an anonymous function may return a struct in future releases.
|
||||
"""
|
||||
|
||||
defrecord Lazy, enum: nil, funs: [], accs: [], done: nil
|
||||
defmodule Lazy do
|
||||
@moduledoc false
|
||||
defstruct enum: nil, funs: [], accs: [], done: nil
|
||||
end
|
||||
|
||||
defimpl Enumerable, for: Lazy do
|
||||
@compile :inline_list_funs
|
||||
|
||||
def reduce(lazy, acc, fun) do
|
||||
do_reduce(lazy, acc, fn x, [acc] ->
|
||||
{ reason, acc } = fun.(x, acc)
|
||||
{ reason, [acc] }
|
||||
{reason, acc} = fun.(x, acc)
|
||||
{reason, [acc]}
|
||||
end)
|
||||
end
|
||||
|
||||
def count(_lazy) do
|
||||
{ :error, __MODULE__ }
|
||||
{:error, __MODULE__}
|
||||
end
|
||||
|
||||
def member?(_lazy, _value) do
|
||||
{ :error, __MODULE__ }
|
||||
{:error, __MODULE__}
|
||||
end
|
||||
|
||||
defp do_reduce(Lazy[enum: enum, funs: funs, accs: accs, done: done], acc, fun) do
|
||||
defp do_reduce(%Lazy{enum: enum, funs: funs, accs: accs, done: done}, acc, fun) do
|
||||
composed = :lists.foldl(fn fun, acc -> fun.(acc) end, fun, funs)
|
||||
do_each(&Enumerable.reduce(enum, &1, composed),
|
||||
done && { done, fun }, :lists.reverse(accs), acc)
|
||||
done && {done, fun}, :lists.reverse(accs), acc)
|
||||
end
|
||||
|
||||
defp do_each(reduce, done, accs, { command, acc }) do
|
||||
case reduce.({ command, [acc|accs] }) do
|
||||
{ :suspended, [acc|accs], continuation } ->
|
||||
{ :suspended, acc, &do_each(continuation, done, accs, &1) }
|
||||
{ :halted, [acc|_] } ->
|
||||
{ :halted, acc }
|
||||
{ :done, [acc|_] = accs } ->
|
||||
defp do_each(reduce, done, accs, {command, acc}) do
|
||||
case reduce.({command, [acc|accs]}) do
|
||||
{:suspended, [acc|accs], continuation} ->
|
||||
{:suspended, acc, &do_each(continuation, done, accs, &1)}
|
||||
{:halted, [acc|_]} ->
|
||||
{:halted, acc}
|
||||
{:done, [acc|_] = accs} ->
|
||||
case done do
|
||||
nil ->
|
||||
{ :done, acc }
|
||||
{ done, fun } ->
|
||||
{:done, acc}
|
||||
{done, fun} ->
|
||||
case done.(fun).(accs) do
|
||||
{ :cont, [acc|_] } -> { :done, acc }
|
||||
{ :halt, [acc|_] } -> { :halted, acc }
|
||||
{ :suspend, [acc|_] } -> { :suspended, acc, &({ :done, elem(&1, 1) }) }
|
||||
{:cont, [acc|_]} -> {:done, acc}
|
||||
{:halt, [acc|_]} -> {:halted, acc}
|
||||
{:suspend, [acc|_]} -> {:suspended, acc, &({:done, elem(&1, 1)})}
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -154,8 +157,8 @@ defmodule Stream do
|
||||
|
||||
defmacrop cont_with_acc(f, entry, h, n, t) do
|
||||
quote do
|
||||
{ reason, [h|t] } = unquote(f).(unquote(entry), [unquote(h)|unquote(t)])
|
||||
{ reason, [h,unquote(n)|t] }
|
||||
{reason, [h|t]} = unquote(f).(unquote(entry), [unquote(h)|unquote(t)])
|
||||
{reason, [h,unquote(n)|t]}
|
||||
end
|
||||
end
|
||||
|
||||
@@ -199,14 +202,14 @@ defmodule Stream do
|
||||
@spec chunk(Enumerable.t, non_neg_integer, non_neg_integer, Enumerable.t | nil) :: Enumerable.t
|
||||
def chunk(enum, n, step, pad \\ nil) when n > 0 and step > 0 do
|
||||
limit = :erlang.max(n, step)
|
||||
lazy enum, { [], 0 },
|
||||
lazy enum, {[], 0},
|
||||
fn(f1) -> R.chunk(n, step, limit, f1) end,
|
||||
fn(f1) -> &do_chunk(&1, n, pad, f1) end
|
||||
end
|
||||
|
||||
defp do_chunk(acc(h, { buffer, count } = old, t) = acc, n, pad, f1) do
|
||||
defp do_chunk(acc(h, {buffer, count} = old, t) = acc, n, pad, f1) do
|
||||
if nil?(pad) || count == 0 do
|
||||
{ :cont, acc }
|
||||
{:cont, acc}
|
||||
else
|
||||
buffer = :lists.reverse(buffer) ++ Enum.take(pad, n - count)
|
||||
cont_with_acc(f1, buffer, h, old, t)
|
||||
@@ -233,10 +236,10 @@ defmodule Stream do
|
||||
end
|
||||
|
||||
defp do_chunk_by(acc(_, nil, _) = acc, _f1) do
|
||||
{ :cont, acc }
|
||||
{:cont, acc}
|
||||
end
|
||||
|
||||
defp do_chunk_by(acc(h, { buffer, _ }, t), f1) do
|
||||
defp do_chunk_by(acc(h, {buffer, _}, t), f1) do
|
||||
cont_with_acc(f1, :lists.reverse(buffer), h, nil, t)
|
||||
end
|
||||
|
||||
@@ -267,12 +270,12 @@ defmodule Stream do
|
||||
def drop(enum, n) when n < 0 do
|
||||
n = abs(n)
|
||||
|
||||
lazy enum, { 0, [], [] }, fn(f1) ->
|
||||
lazy enum, {0, [], []}, fn(f1) ->
|
||||
fn
|
||||
entry, [h, { count, buf1, [] } | t] ->
|
||||
entry, [h, {count, buf1, []} | t] ->
|
||||
do_drop(:cont, n, entry, h, count, buf1, [], t)
|
||||
entry, [h, { count, buf1, [next|buf2] } | t] ->
|
||||
{ reason, [h|t] } = f1.(next, [h|t])
|
||||
entry, [h, {count, buf1, [next|buf2]} | t] ->
|
||||
{reason, [h|t]} = f1.(next, [h|t])
|
||||
do_drop(reason, n, entry, h, count, buf1, buf2, t)
|
||||
end
|
||||
end
|
||||
@@ -282,9 +285,9 @@ defmodule Stream do
|
||||
buf1 = [entry|buf1]
|
||||
count = count + 1
|
||||
if count == n do
|
||||
{ reason, [h, { 0, [], :lists.reverse(buf1) }|t] }
|
||||
{reason, [h, {0, [], :lists.reverse(buf1)}|t]}
|
||||
else
|
||||
{ reason, [h, { count, buf1, buf2 }|t] }
|
||||
{reason, [h, {count, buf1, buf2}|t]}
|
||||
end
|
||||
end
|
||||
|
||||
@@ -344,7 +347,7 @@ defmodule Stream do
|
||||
"""
|
||||
@spec flat_map(Enumerable.t, (element -> Enumerable.t)) :: Enumerable.t
|
||||
def flat_map(enum, mapper) do
|
||||
transform(enum, nil, fn val, nil -> { mapper.(val), nil } end)
|
||||
transform(enum, nil, fn val, nil -> {mapper.(val), nil} end)
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -393,29 +396,29 @@ defmodule Stream do
|
||||
end
|
||||
|
||||
defp do_into(enum, collectable, transform, acc, fun) do
|
||||
{ initial, into } = Collectable.into(collectable)
|
||||
{initial, into} = Collectable.into(collectable)
|
||||
composed = fn x, [acc|collectable] ->
|
||||
collectable = into.(collectable, { :cont, transform.(x) })
|
||||
{ reason, acc } = fun.(x, acc)
|
||||
{ reason, [acc|collectable] }
|
||||
collectable = into.(collectable, {:cont, transform.(x)})
|
||||
{reason, acc} = fun.(x, acc)
|
||||
{reason, [acc|collectable]}
|
||||
end
|
||||
do_into(&Enumerable.reduce(enum, &1, composed), initial, into, acc)
|
||||
end
|
||||
|
||||
defp do_into(reduce, collectable, into, { command, acc }) do
|
||||
defp do_into(reduce, collectable, into, {command, acc}) do
|
||||
try do
|
||||
reduce.({ command, [acc|collectable] })
|
||||
reduce.({command, [acc|collectable]})
|
||||
catch
|
||||
kind, reason ->
|
||||
stacktrace = System.stacktrace
|
||||
into.(collectable, :halt)
|
||||
:erlang.raise(kind, reason, stacktrace)
|
||||
else
|
||||
{ :suspended, [acc|collectable], continuation } ->
|
||||
{ :suspended, acc, &do_into(continuation, collectable, into, &1) }
|
||||
{ reason, [acc|collectable] } ->
|
||||
{:suspended, [acc|collectable], continuation} ->
|
||||
{:suspended, acc, &do_into(continuation, collectable, into, &1)}
|
||||
{reason, [acc|collectable]} ->
|
||||
into.(collectable, :done)
|
||||
{ reason, acc }
|
||||
{reason, acc}
|
||||
end
|
||||
end
|
||||
|
||||
@@ -471,7 +474,7 @@ defmodule Stream do
|
||||
"""
|
||||
@spec run(Enumerable.t) :: :ok
|
||||
def run(stream) do
|
||||
Enumerable.reduce(stream, { :cont, nil }, fn(_, _) -> { :cont, nil } end)
|
||||
Enumerable.reduce(stream, {:cont, nil}, fn(_, _) -> {:cont, nil} end)
|
||||
:ok
|
||||
end
|
||||
|
||||
@@ -535,7 +538,7 @@ defmodule Stream do
|
||||
|
||||
"""
|
||||
@spec take(Enumerable.t, non_neg_integer) :: Enumerable.t
|
||||
def take(_enum, 0), do: Lazy[enum: []]
|
||||
def take(_enum, 0), do: %Lazy{enum: []}
|
||||
|
||||
def take(enum, n) when n > 0 do
|
||||
lazy enum, n, fn(f1) -> R.take(f1) end
|
||||
@@ -546,15 +549,15 @@ defmodule Stream do
|
||||
end
|
||||
|
||||
defp do_take(enum, n, acc, f) do
|
||||
{ _, { _count, buf1, buf2 } } =
|
||||
Enumerable.reduce(enum, { :cont, { 0, [], [] } }, fn
|
||||
entry, { count, buf1, buf2 } ->
|
||||
{_, {_count, buf1, buf2}} =
|
||||
Enumerable.reduce(enum, {:cont, {0, [], []}}, fn
|
||||
entry, {count, buf1, buf2} ->
|
||||
buf1 = [entry|buf1]
|
||||
count = count + 1
|
||||
if count == n do
|
||||
{ :cont, { 0, [], buf1 } }
|
||||
{:cont, {0, [], buf1}}
|
||||
else
|
||||
{ :cont, { count, buf1, buf2 } }
|
||||
{:cont, {count, buf1, buf2}}
|
||||
end
|
||||
end)
|
||||
|
||||
@@ -587,7 +590,7 @@ defmodule Stream do
|
||||
lazy enum, n, fn(f1) -> R.take_every(n, f1) end
|
||||
end
|
||||
|
||||
def take_every(_enum, 0), do: Lazy[enum: []]
|
||||
def take_every(_enum, 0), do: %Lazy{enum: []}
|
||||
|
||||
@doc """
|
||||
Lazily takes elements of the enumerable while the given
|
||||
@@ -626,14 +629,14 @@ defmodule Stream do
|
||||
iex> enum = 1..100
|
||||
iex> n = 3
|
||||
iex> stream = Stream.transform(enum, 0, fn i, acc ->
|
||||
...> if acc < n, do: { [i], acc + 1 }, else: { :halt, acc }
|
||||
...> if acc < n, do: {[i], acc + 1}, else: {:halt, acc}
|
||||
...> end)
|
||||
iex> Enum.to_list(stream)
|
||||
[1,2,3]
|
||||
|
||||
"""
|
||||
@spec transform(Enumerable.t, acc, fun) :: Enumerable.t when
|
||||
fun: (element, acc -> { Enumerable.t, acc } | { :halt, acc }),
|
||||
fun: (element, acc -> {Enumerable.t, acc} | {:halt, acc}),
|
||||
acc: any
|
||||
def transform(enum, acc, reducer) do
|
||||
&do_transform(enum, acc, reducer, &1, &2)
|
||||
@@ -647,27 +650,27 @@ defmodule Stream do
|
||||
end
|
||||
|
||||
defp do_transform(user_acc, user, fun, next_acc, next, inner_acc, inner) do
|
||||
case next.({ :cont, next_acc }) do
|
||||
{ :suspended, [val|next_acc], next } ->
|
||||
case next.({:cont, next_acc}) do
|
||||
{:suspended, [val|next_acc], next} ->
|
||||
try do
|
||||
user.(val, user_acc)
|
||||
catch
|
||||
kind, reason ->
|
||||
next.({ :halt, next_acc })
|
||||
next.({:halt, next_acc})
|
||||
:erlang.raise(kind, reason, :erlang.get_stacktrace)
|
||||
else
|
||||
{ [], user_acc } ->
|
||||
{[], user_acc} ->
|
||||
do_transform(user_acc, user, fun, next_acc, next, inner_acc, inner)
|
||||
{ list, user_acc } when is_list(list) ->
|
||||
{list, user_acc} when is_list(list) ->
|
||||
do_list_transform(user_acc, user, fun, next_acc, next, inner_acc, inner, &Enumerable.List.reduce(list, &1, fun))
|
||||
{ :halt, _user_acc } ->
|
||||
next.({ :halt, next_acc })
|
||||
{ :halted, elem(inner_acc, 1) }
|
||||
{ other, user_acc } ->
|
||||
{:halt, _user_acc} ->
|
||||
next.({:halt, next_acc})
|
||||
{:halted, elem(inner_acc, 1)}
|
||||
{other, user_acc} ->
|
||||
do_other_transform(user_acc, user, fun, next_acc, next, inner_acc, inner, &Enumerable.reduce(other, &1, inner))
|
||||
end
|
||||
{ reason, _ } ->
|
||||
{ reason, elem(inner_acc, 1) }
|
||||
{reason, _} ->
|
||||
{reason, elem(inner_acc, 1)}
|
||||
end
|
||||
end
|
||||
|
||||
@@ -676,16 +679,16 @@ defmodule Stream do
|
||||
reduce.(inner_acc)
|
||||
catch
|
||||
kind, reason ->
|
||||
next.({ :halt, next_acc })
|
||||
next.({:halt, next_acc})
|
||||
:erlang.raise(kind, reason, :erlang.get_stacktrace)
|
||||
else
|
||||
{ :done, acc } ->
|
||||
do_transform(user_acc, user, fun, next_acc, next, { :cont, acc }, inner)
|
||||
{ :halted, acc } ->
|
||||
next.({ :halt, next_acc })
|
||||
{ :halted, acc }
|
||||
{ :suspended, acc, c } ->
|
||||
{ :suspended, acc, &do_list_transform(user_acc, user, fun, next_acc, next, &1, inner, c) }
|
||||
{:done, acc} ->
|
||||
do_transform(user_acc, user, fun, next_acc, next, {:cont, acc}, inner)
|
||||
{:halted, acc} ->
|
||||
next.({:halt, next_acc})
|
||||
{:halted, acc}
|
||||
{:suspended, acc, c} ->
|
||||
{:suspended, acc, &do_list_transform(user_acc, user, fun, next_acc, next, &1, inner, c)}
|
||||
end
|
||||
end
|
||||
|
||||
@@ -693,29 +696,29 @@ defmodule Stream do
|
||||
try do
|
||||
reduce.(inner_acc)
|
||||
catch
|
||||
{ :stream_transform, h } ->
|
||||
next.({ :halt, next_acc })
|
||||
{ :halted, h }
|
||||
{:stream_transform, h} ->
|
||||
next.({:halt, next_acc})
|
||||
{:halted, h}
|
||||
kind, reason ->
|
||||
next.({ :halt, next_acc })
|
||||
next.({:halt, next_acc})
|
||||
:erlang.raise(kind, reason, :erlang.get_stacktrace)
|
||||
else
|
||||
{ _, acc } ->
|
||||
do_transform(user_acc, user, fun, next_acc, next, { :cont, acc }, inner)
|
||||
{ :suspended, acc, c } ->
|
||||
{ :suspended, acc, &do_other_transform(user_acc, user, fun, next_acc, next, &1, inner, c) }
|
||||
{_, acc} ->
|
||||
do_transform(user_acc, user, fun, next_acc, next, {:cont, acc}, inner)
|
||||
{:suspended, acc, c} ->
|
||||
{:suspended, acc, &do_other_transform(user_acc, user, fun, next_acc, next, &1, inner, c)}
|
||||
end
|
||||
end
|
||||
|
||||
defp do_transform_each(x, acc, f) do
|
||||
case f.(x, acc) do
|
||||
{ :halt, h } -> throw({ :stream_transform, h })
|
||||
{ _, _ } = o -> o
|
||||
{:halt, h} -> throw({:stream_transform, h})
|
||||
{_, _} = o -> o
|
||||
end
|
||||
end
|
||||
|
||||
defp do_transform_step(x, acc) do
|
||||
{ :suspend, [x|acc] }
|
||||
{:suspend, [x|acc]}
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -815,19 +818,19 @@ defmodule Stream do
|
||||
right_fun = &Enumerable.reduce(right, &1, step)
|
||||
|
||||
# Return a function as a lazy enumerator.
|
||||
&do_zip([{ left_fun, [] }, { right_fun, [] }], &1, &2)
|
||||
&do_zip([{left_fun, []}, {right_fun, []}], &1, &2)
|
||||
end
|
||||
|
||||
defp do_zip(zips, { :halt, acc }, _fun) do
|
||||
defp do_zip(zips, {:halt, acc}, _fun) do
|
||||
do_zip_close(zips)
|
||||
{ :halted, acc }
|
||||
{:halted, acc}
|
||||
end
|
||||
|
||||
defp do_zip(zips, { :suspend, acc }, fun) do
|
||||
{ :suspended, acc, &do_zip(zips, &1, fun) }
|
||||
defp do_zip(zips, {:suspend, acc}, fun) do
|
||||
{:suspended, acc, &do_zip(zips, &1, fun)}
|
||||
end
|
||||
|
||||
defp do_zip(zips, { :cont, acc }, callback) do
|
||||
defp do_zip(zips, {:cont, acc}, callback) do
|
||||
try do
|
||||
do_zip(zips, acc, callback, [], [])
|
||||
catch
|
||||
@@ -835,36 +838,36 @@ defmodule Stream do
|
||||
do_zip_close(zips)
|
||||
:erlang.raise(kind, reason, :erlang.get_stacktrace)
|
||||
else
|
||||
{ :next, buffer, acc } ->
|
||||
{:next, buffer, acc} ->
|
||||
do_zip(buffer, acc, callback)
|
||||
{ :done, _ } = o ->
|
||||
{:done, _} = o ->
|
||||
o
|
||||
end
|
||||
end
|
||||
|
||||
defp do_zip([{ fun, fun_acc }|t], acc, callback, list, buffer) do
|
||||
case fun.({ :cont, fun_acc }) do
|
||||
{ :suspended, [i|fun_acc], fun } ->
|
||||
do_zip(t, acc, callback, [i|list], [{ fun, fun_acc }|buffer])
|
||||
{ _, _ } ->
|
||||
defp do_zip([{fun, fun_acc}|t], acc, callback, list, buffer) do
|
||||
case fun.({:cont, fun_acc}) do
|
||||
{:suspended, [i|fun_acc], fun} ->
|
||||
do_zip(t, acc, callback, [i|list], [{fun, fun_acc}|buffer])
|
||||
{_, _} ->
|
||||
do_zip_close(:lists.reverse(buffer) ++ t)
|
||||
{ :done, acc }
|
||||
{:done, acc}
|
||||
end
|
||||
end
|
||||
|
||||
defp do_zip([], acc, callback, list, buffer) do
|
||||
zipped = list_to_tuple(:lists.reverse(list))
|
||||
{ :next, :lists.reverse(buffer), callback.(zipped, acc) }
|
||||
{:next, :lists.reverse(buffer), callback.(zipped, acc)}
|
||||
end
|
||||
|
||||
defp do_zip_close([]), do: :ok
|
||||
defp do_zip_close([{ fun, acc }|t]) do
|
||||
fun.({ :halt, acc })
|
||||
defp do_zip_close([{fun, acc}|t]) do
|
||||
fun.({:halt, acc})
|
||||
do_zip_close(t)
|
||||
end
|
||||
|
||||
defp do_zip_step(x, acc) do
|
||||
{ :suspend, [x|acc] }
|
||||
{:suspend, [x|acc]}
|
||||
end
|
||||
|
||||
## Sources
|
||||
@@ -888,22 +891,22 @@ defmodule Stream do
|
||||
end
|
||||
end
|
||||
|
||||
defp do_cycle(_reduce, _cycle, { :halt, acc }) do
|
||||
{ :halted, acc }
|
||||
defp do_cycle(_reduce, _cycle, {:halt, acc}) do
|
||||
{:halted, acc}
|
||||
end
|
||||
|
||||
defp do_cycle(reduce, cycle, { :suspend, acc }) do
|
||||
{ :suspended, acc, &do_cycle(reduce, cycle, &1) }
|
||||
defp do_cycle(reduce, cycle, {:suspend, acc}) do
|
||||
{:suspended, acc, &do_cycle(reduce, cycle, &1)}
|
||||
end
|
||||
|
||||
defp do_cycle(reduce, cycle, acc) do
|
||||
case reduce.(acc) do
|
||||
{ :done, acc } ->
|
||||
do_cycle(cycle, cycle, { :cont, acc })
|
||||
{ :halted, acc } ->
|
||||
{ :halted, acc }
|
||||
{ :suspended, acc, continuation } ->
|
||||
{ :suspended, acc, &do_cycle(continuation, cycle, &1) }
|
||||
{:done, acc} ->
|
||||
do_cycle(cycle, cycle, {:cont, acc})
|
||||
{:halted, acc} ->
|
||||
{:halted, acc}
|
||||
{:suspended, acc, continuation} ->
|
||||
{:suspended, acc, &do_cycle(continuation, cycle, &1)}
|
||||
end
|
||||
end
|
||||
|
||||
@@ -919,12 +922,12 @@ defmodule Stream do
|
||||
"""
|
||||
@spec iterate(element, (element -> element)) :: Enumerable.t
|
||||
def iterate(start_value, next_fun) do
|
||||
unfold({ :ok, start_value}, fn
|
||||
{ :ok, value } ->
|
||||
{ value, { :next, value } }
|
||||
{ :next, value } ->
|
||||
unfold({:ok, start_value}, fn
|
||||
{:ok, value} ->
|
||||
{value, {:next, value}}
|
||||
{:next, value} ->
|
||||
next = next_fun.(value)
|
||||
{ next, { :next, next } }
|
||||
{next, {:next, next}}
|
||||
end)
|
||||
end
|
||||
|
||||
@@ -942,15 +945,15 @@ defmodule Stream do
|
||||
&do_repeatedly(generator_fun, &1, &2)
|
||||
end
|
||||
|
||||
defp do_repeatedly(generator_fun, { :suspend, acc }, fun) do
|
||||
{ :suspended, acc, &do_repeatedly(generator_fun, &1, fun) }
|
||||
defp do_repeatedly(generator_fun, {:suspend, acc}, fun) do
|
||||
{:suspended, acc, &do_repeatedly(generator_fun, &1, fun)}
|
||||
end
|
||||
|
||||
defp do_repeatedly(_generator_fun, { :halt, acc }, _fun) do
|
||||
{ :halted, acc }
|
||||
defp do_repeatedly(_generator_fun, {:halt, acc}, _fun) do
|
||||
{:halted, acc}
|
||||
end
|
||||
|
||||
defp do_repeatedly(generator_fun, { :cont, acc }, fun) do
|
||||
defp do_repeatedly(generator_fun, {:cont, acc}, fun) do
|
||||
do_repeatedly(generator_fun, fun.(generator_fun.(), acc), fun)
|
||||
end
|
||||
|
||||
@@ -974,32 +977,32 @@ defmodule Stream do
|
||||
Stream.resource(fn -> File.open!("sample") end,
|
||||
fn file ->
|
||||
case IO.read(file, :line) do
|
||||
data when is_binary(data) -> { data, file }
|
||||
data when is_binary(data) -> {data, file}
|
||||
_ -> nil
|
||||
end
|
||||
end,
|
||||
fn file -> File.close(file) end)
|
||||
|
||||
"""
|
||||
@spec resource((() -> acc), (acc -> { element, acc } | nil), (acc -> term)) :: Enumerable.t
|
||||
@spec resource((() -> acc), (acc -> {element, acc} | nil), (acc -> term)) :: Enumerable.t
|
||||
def resource(start_fun, next_fun, after_fun) do
|
||||
&do_resource(start_fun.(), next_fun, &1, &2, after_fun)
|
||||
end
|
||||
|
||||
defp do_resource(next_acc, next_fun, { :suspend, acc }, fun, after_fun) do
|
||||
{ :suspended, acc, &do_resource(next_acc, next_fun, &1, fun, after_fun) }
|
||||
defp do_resource(next_acc, next_fun, {:suspend, acc}, fun, after_fun) do
|
||||
{:suspended, acc, &do_resource(next_acc, next_fun, &1, fun, after_fun)}
|
||||
end
|
||||
|
||||
defp do_resource(next_acc, _next_fun, { :halt, acc }, _fun, after_fun) do
|
||||
defp do_resource(next_acc, _next_fun, {:halt, acc}, _fun, after_fun) do
|
||||
after_fun.(next_acc)
|
||||
{ :halted, acc }
|
||||
{:halted, acc}
|
||||
end
|
||||
|
||||
defp do_resource(next_acc, next_fun, { :cont, acc }, fun, after_fun) do
|
||||
defp do_resource(next_acc, next_fun, {:cont, acc}, fun, after_fun) do
|
||||
try do
|
||||
case next_fun.(next_acc) do
|
||||
nil -> nil
|
||||
{ v, next_acc } -> { fun.(v, acc), next_acc }
|
||||
{v, next_acc} -> {fun.(v, acc), next_acc}
|
||||
end
|
||||
catch
|
||||
kind, reason ->
|
||||
@@ -1008,8 +1011,8 @@ defmodule Stream do
|
||||
else
|
||||
nil ->
|
||||
after_fun.(next_acc)
|
||||
{ :done, acc }
|
||||
{ acc, next_acc } ->
|
||||
{:done, acc}
|
||||
{acc, next_acc} ->
|
||||
do_resource(next_acc, next_fun, acc, fun, after_fun)
|
||||
end
|
||||
end
|
||||
@@ -1027,42 +1030,42 @@ defmodule Stream do
|
||||
[5, 4, 3, 2, 1]
|
||||
|
||||
"""
|
||||
@spec unfold(acc, (acc -> { element, acc } | nil)) :: Enumerable.t
|
||||
@spec unfold(acc, (acc -> {element, acc} | nil)) :: Enumerable.t
|
||||
def unfold(next_acc, next_fun) do
|
||||
&do_unfold(next_acc, next_fun, &1, &2)
|
||||
end
|
||||
|
||||
defp do_unfold(next_acc, next_fun, { :suspend, acc }, fun) do
|
||||
{ :suspended, acc, &do_unfold(next_acc, next_fun, &1, fun) }
|
||||
defp do_unfold(next_acc, next_fun, {:suspend, acc}, fun) do
|
||||
{:suspended, acc, &do_unfold(next_acc, next_fun, &1, fun)}
|
||||
end
|
||||
|
||||
defp do_unfold(_next_acc, _next_fun, { :halt, acc }, _fun) do
|
||||
{ :halted, acc }
|
||||
defp do_unfold(_next_acc, _next_fun, {:halt, acc}, _fun) do
|
||||
{:halted, acc}
|
||||
end
|
||||
|
||||
defp do_unfold(next_acc, next_fun, { :cont, acc }, fun) do
|
||||
defp do_unfold(next_acc, next_fun, {:cont, acc}, fun) do
|
||||
case next_fun.(next_acc) do
|
||||
nil -> { :done, acc }
|
||||
{ v, next_acc } -> do_unfold(next_acc, next_fun, fun.(v, acc), fun)
|
||||
nil -> {:done, acc}
|
||||
{v, next_acc} -> do_unfold(next_acc, next_fun, fun.(v, acc), fun)
|
||||
end
|
||||
end
|
||||
|
||||
## Helpers
|
||||
|
||||
@compile { :inline, lazy: 2, lazy: 3, lazy: 4 }
|
||||
@compile {:inline, lazy: 2, lazy: 3, lazy: 4}
|
||||
|
||||
defp lazy(Lazy[funs: funs] = lazy, fun),
|
||||
do: lazy.funs([fun|funs])
|
||||
defp lazy(%Lazy{funs: funs} = lazy, fun),
|
||||
do: %{lazy | funs: [fun|funs] }
|
||||
defp lazy(enum, fun),
|
||||
do: Lazy[enum: enum, funs: [fun]]
|
||||
do: %Lazy{enum: enum, funs: [fun]}
|
||||
|
||||
defp lazy(Lazy[funs: funs, accs: accs] = lazy, acc, fun),
|
||||
do: lazy.funs([fun|funs]).accs([acc|accs])
|
||||
defp lazy(%Lazy{funs: funs, accs: accs} = lazy, acc, fun),
|
||||
do: %{lazy | funs: [fun|funs], accs: [acc|accs] }
|
||||
defp lazy(enum, acc, fun),
|
||||
do: Lazy[enum: enum, funs: [fun], accs: [acc]]
|
||||
do: %Lazy{enum: enum, funs: [fun], accs: [acc]}
|
||||
|
||||
defp lazy(Lazy[done: nil, funs: funs, accs: accs] = lazy, acc, fun, done),
|
||||
do: lazy.funs([fun|funs]).accs([acc|accs]).done(done)
|
||||
defp lazy(%Lazy{done: nil, funs: funs, accs: accs} = lazy, acc, fun, done),
|
||||
do: %{lazy | funs: [fun|funs], accs: [acc|accs], done: done}
|
||||
defp lazy(enum, acc, fun, done),
|
||||
do: Lazy[enum: enum, funs: [fun], accs: [acc], done: done]
|
||||
do: %Lazy{enum: enum, funs: [fun], accs: [acc], done: done}
|
||||
end
|
||||
|
||||
@@ -4,22 +4,22 @@ defmodule Stream.Reducers do
|
||||
|
||||
defmacro chunk(n, step, limit, f \\ nil) do
|
||||
quote do
|
||||
fn entry, acc(h, { buffer, count }, t) ->
|
||||
fn entry, acc(h, {buffer, count}, t) ->
|
||||
buffer = [entry|buffer]
|
||||
count = count + 1
|
||||
|
||||
new =
|
||||
if count >= unquote(limit) do
|
||||
left = count - unquote(step)
|
||||
{ Enum.take(buffer, left), left }
|
||||
{Enum.take(buffer, left), left}
|
||||
else
|
||||
{ buffer, count }
|
||||
{buffer, count}
|
||||
end
|
||||
|
||||
if count == unquote(n) do
|
||||
cont_with_acc(unquote(f), :lists.reverse(buffer), h, new, t)
|
||||
else
|
||||
{ :cont, acc(h, new, t) }
|
||||
{:cont, acc(h, new, t)}
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -28,15 +28,15 @@ defmodule Stream.Reducers do
|
||||
defmacro chunk_by(callback, f \\ nil) do
|
||||
quote do
|
||||
fn
|
||||
entry, acc(h, { buffer, value }, t) ->
|
||||
entry, acc(h, {buffer, value}, t) ->
|
||||
new_value = unquote(callback).(entry)
|
||||
if new_value == value do
|
||||
{ :cont, acc(h, { [entry|buffer], value }, t) }
|
||||
{:cont, acc(h, {[entry|buffer], value}, t)}
|
||||
else
|
||||
cont_with_acc(unquote(f), :lists.reverse(buffer), h, { [entry], new_value }, t)
|
||||
cont_with_acc(unquote(f), :lists.reverse(buffer), h, {[entry], new_value}, t)
|
||||
end
|
||||
entry, acc(h, nil, t) ->
|
||||
{ :cont, acc(h, { [entry], unquote(callback).(entry) }, t) }
|
||||
{:cont, acc(h, {[entry], unquote(callback).(entry)}, t)}
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -45,7 +45,7 @@ defmodule Stream.Reducers do
|
||||
quote do
|
||||
fn
|
||||
_entry, acc(h, n, t) when n > 0 ->
|
||||
{ :cont, acc(h, n-1, t) }
|
||||
{:cont, acc(h, n-1, t)}
|
||||
entry, acc(h, n, t) ->
|
||||
cont_with_acc(unquote(f), entry, h, n, t)
|
||||
end
|
||||
@@ -56,7 +56,7 @@ defmodule Stream.Reducers do
|
||||
quote do
|
||||
fn entry, acc(h, bool, t) = orig ->
|
||||
if bool and unquote(callback).(entry) do
|
||||
{ :cont, orig }
|
||||
{:cont, orig}
|
||||
else
|
||||
cont_with_acc(unquote(f), entry, h, false, t)
|
||||
end
|
||||
@@ -70,7 +70,7 @@ defmodule Stream.Reducers do
|
||||
if unquote(callback).(entry) do
|
||||
cont(unquote(f), entry, acc)
|
||||
else
|
||||
{ :cont, acc }
|
||||
{:cont, acc}
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -82,7 +82,7 @@ defmodule Stream.Reducers do
|
||||
if unquote(filter).(entry) do
|
||||
cont(unquote(f), unquote(mapper).(entry), acc)
|
||||
else
|
||||
{ :cont, acc }
|
||||
{:cont, acc}
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -102,7 +102,7 @@ defmodule Stream.Reducers do
|
||||
unless unquote(callback).(entry) do
|
||||
cont(unquote(f), entry, acc)
|
||||
else
|
||||
{ :cont, acc }
|
||||
{:cont, acc}
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -112,10 +112,10 @@ defmodule Stream.Reducers do
|
||||
quote do
|
||||
fn
|
||||
entry, acc(h, :first, t) ->
|
||||
cont_with_acc(unquote(f), entry, h, { :ok, entry }, t)
|
||||
entry, acc(h, { :ok, acc }, t) ->
|
||||
cont_with_acc(unquote(f), entry, h, {:ok, entry}, t)
|
||||
entry, acc(h, {:ok, acc}, t) ->
|
||||
value = unquote(callback).(entry, acc)
|
||||
cont_with_acc(unquote(f), value, h, { :ok, value }, t)
|
||||
cont_with_acc(unquote(f), value, h, {:ok, value}, t)
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -135,7 +135,7 @@ defmodule Stream.Reducers do
|
||||
if n >= 1 do
|
||||
cont_with_acc(unquote(f), entry, h, n-1, t)
|
||||
else
|
||||
{ :halt, orig }
|
||||
{:halt, orig}
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -148,7 +148,7 @@ defmodule Stream.Reducers do
|
||||
when n === unquote(nth) ->
|
||||
cont_with_acc(unquote(f), entry, h, 1, t)
|
||||
entry, acc(h, n, t) ->
|
||||
{ :cont, acc(h, n+1, t) }
|
||||
{:cont, acc(h, n+1, t)}
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -159,7 +159,7 @@ defmodule Stream.Reducers do
|
||||
if unquote(callback).(entry) do
|
||||
cont(unquote(f), entry, acc)
|
||||
else
|
||||
{ :halt, acc }
|
||||
{:halt, acc}
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -170,7 +170,7 @@ defmodule Stream.Reducers do
|
||||
fn(entry, acc(h, prev, t) = acc) ->
|
||||
value = unquote(callback).(entry)
|
||||
if :lists.member(value, prev) do
|
||||
{ :cont, acc }
|
||||
{:cont, acc}
|
||||
else
|
||||
cont_with_acc(unquote(f), entry, h, [value|prev], t)
|
||||
end
|
||||
@@ -181,7 +181,7 @@ defmodule Stream.Reducers do
|
||||
defmacro with_index(f \\ nil) do
|
||||
quote do
|
||||
fn(entry, acc(h, counter, t)) ->
|
||||
cont_with_acc(unquote(f), { entry, counter }, h, counter + 1, t)
|
||||
cont_with_acc(unquote(f), {entry, counter}, h, counter + 1, t)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
+136
-106
@@ -177,7 +177,8 @@ defmodule String do
|
||||
be a string, a list of strings or a regular expression.
|
||||
|
||||
The string is split into as many parts as possible by
|
||||
default, unless the `global` option is set to `false`.
|
||||
default, but can be controlled via the `parts: num` option.
|
||||
If you pass `parts: :infinity`, it will return all possible parts.
|
||||
|
||||
Empty strings are only removed from the result if the
|
||||
`trim` option is set to `true`.
|
||||
@@ -189,7 +190,7 @@ defmodule String do
|
||||
iex> String.split("a,b,c", ",")
|
||||
["a", "b", "c"]
|
||||
|
||||
iex> String.split("a,b,c", ",", global: false)
|
||||
iex> String.split("a,b,c", ",", parts: 2)
|
||||
["a", "b,c"]
|
||||
|
||||
iex> String.split(" a b c ", " ", trim: true)
|
||||
@@ -205,7 +206,7 @@ defmodule String do
|
||||
iex> String.split("a,b,c", ~r{,})
|
||||
["a", "b", "c"]
|
||||
|
||||
iex> String.split("a,b,c", ~r{,}, global: false)
|
||||
iex> String.split("a,b,c", ~r{,}, parts: 2)
|
||||
["a", "b,c"]
|
||||
|
||||
iex> String.split(" a b c ", ~r{\s}, trim: true)
|
||||
@@ -222,7 +223,7 @@ defmodule String do
|
||||
iex> String.split("abc", "", trim: true)
|
||||
["a", "b", "c"]
|
||||
|
||||
iex> String.split("abc", "", global: false)
|
||||
iex> String.split("abc", "", parts: 2)
|
||||
["a", "bc"]
|
||||
|
||||
"""
|
||||
@@ -232,14 +233,25 @@ defmodule String do
|
||||
|
||||
def split("", _pattern, _options), do: [""]
|
||||
|
||||
def split(binary, "", options), do: split(binary, ~r"", options)
|
||||
def split(binary, "", options), do: split(binary, ~r""u, options)
|
||||
|
||||
def split(binary, pattern, options) do
|
||||
if options[:global] != nil do
|
||||
IO.write :stderr, "Support for :global in String.split/3 is deprecated, please use :parts instead\n#{Exception.format_stacktrace}"
|
||||
end
|
||||
if Regex.regex?(pattern) do
|
||||
Regex.split(pattern, binary, options)
|
||||
else
|
||||
opts = if options[:global] != false, do: [:global], else: []
|
||||
splits = :binary.split(binary, pattern, opts)
|
||||
splits = case options[:parts] do
|
||||
num when is_number(num) and num > 0 ->
|
||||
split_parts(binary, pattern, num - 1)
|
||||
num ->
|
||||
if options[:global] != false or num == 0 do
|
||||
:binary.split(binary, pattern, [:global])
|
||||
else
|
||||
:binary.split(binary, pattern, [])
|
||||
end
|
||||
end
|
||||
|
||||
if Keyword.get(options, :trim, false) do
|
||||
for split <- splits, split != "", do: split
|
||||
@@ -249,6 +261,65 @@ defmodule String do
|
||||
end
|
||||
end
|
||||
|
||||
defp split_parts("", _pattern, _num), do: [""]
|
||||
defp split_parts(binary, pattern, num), do: split_parts(binary, pattern, num, [])
|
||||
defp split_parts("", _pattern, _num, parts), do: Enum.reverse([""|parts])
|
||||
defp split_parts(binary, _pattern, 0, parts), do: Enum.reverse([binary|parts])
|
||||
defp split_parts(binary, pattern, num, parts) do
|
||||
[head|tail] = :binary.split(binary, pattern)
|
||||
case tail do
|
||||
[] -> Enum.reverse([head|parts])
|
||||
[str] -> split_parts(str, pattern, num - 1, [head|parts])
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Splits a string into two at the specified offset. When the offset given is
|
||||
negative, location is counted from the end of the string.
|
||||
|
||||
The offset is capped to the length of the string.
|
||||
|
||||
Returns a tuple with two elements.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> String.split_at "sweetelixir", 5
|
||||
{"sweet", "elixir"}
|
||||
|
||||
iex> String.split_at "sweetelixir", -6
|
||||
{"sweet", "elixir"}
|
||||
|
||||
iex> String.split_at "abc", 0
|
||||
{"", "abc"}
|
||||
|
||||
iex> String.split_at "abc", 1000
|
||||
{"abc", ""}
|
||||
|
||||
iex> String.split_at "abc", -1000
|
||||
{"", "abc"}
|
||||
|
||||
"""
|
||||
@spec split_at(t, integer) :: {t, t}
|
||||
def split_at(string, offset)
|
||||
|
||||
def split_at(binary, index) when index == 0, do:
|
||||
{"", binary}
|
||||
|
||||
def split_at(binary, index) when index > 0, do:
|
||||
do_split_at(next_grapheme(binary), 0, index, "")
|
||||
|
||||
def split_at(binary, index) when index < 0, do:
|
||||
do_split_at(next_grapheme(binary), 0, max(0, byte_size(binary)+index), "")
|
||||
|
||||
defp do_split_at(nil, _, _, acc), do:
|
||||
{acc, ""}
|
||||
|
||||
defp do_split_at({grapheme, rest}, current_pos, target_pos, acc) when current_pos < target_pos, do:
|
||||
do_split_at(next_grapheme(rest), current_pos+1, target_pos, acc <> grapheme)
|
||||
|
||||
defp do_split_at({grapheme, rest}, pos, pos, acc), do:
|
||||
{acc, grapheme <> rest}
|
||||
|
||||
@doc """
|
||||
Convert all characters on the given string to uppercase.
|
||||
|
||||
@@ -308,7 +379,7 @@ defmodule String do
|
||||
"""
|
||||
@spec capitalize(t) :: t
|
||||
def capitalize(string) when is_binary(string) do
|
||||
{ char, rest } = String.Unicode.titlecase_once(string)
|
||||
{char, rest} = String.Unicode.titlecase_once(string)
|
||||
char <> downcase(rest)
|
||||
end
|
||||
|
||||
@@ -540,9 +611,7 @@ defmodule String do
|
||||
@spec replace(t, t, t) :: t
|
||||
@spec replace(t, t, t, Keyword.t) :: t
|
||||
|
||||
def replace(subject, pattern, replacement, options \\ [])
|
||||
|
||||
def replace(subject, pattern, replacement, options) do
|
||||
def replace(subject, pattern, replacement, options \\ []) when is_binary(replacement) do
|
||||
if Regex.regex?(pattern) do
|
||||
Regex.replace(pattern, subject, replacement, global: options[:global])
|
||||
else
|
||||
@@ -552,9 +621,9 @@ defmodule String do
|
||||
end
|
||||
|
||||
defp translate_replace_options(options) do
|
||||
opts = if options[:global] != false, do: [:global], else: []
|
||||
opts = if Keyword.get(options, :global) != false, do: [:global], else: []
|
||||
|
||||
if insert = options[:insert_replaced] do
|
||||
if insert = Keyword.get(options, :insert_replaced) do
|
||||
opts = [{:insert_replaced, insert}|opts]
|
||||
end
|
||||
|
||||
@@ -585,7 +654,7 @@ defmodule String do
|
||||
do_reverse(next_grapheme(rest), [grapheme|acc])
|
||||
end
|
||||
|
||||
defp do_reverse(nil, acc), do: iolist_to_binary(acc)
|
||||
defp do_reverse(nil, acc), do: iodata_to_binary(acc)
|
||||
|
||||
@doc """
|
||||
Returns a binary `subject` duplicated `n` times.
|
||||
@@ -640,10 +709,10 @@ defmodule String do
|
||||
## Examples
|
||||
|
||||
iex> String.next_codepoint("josé")
|
||||
{ "j", "osé" }
|
||||
{"j", "osé"}
|
||||
|
||||
"""
|
||||
@compile { :inline, next_codepoint: 1 }
|
||||
@compile {:inline, next_codepoint: 1}
|
||||
@spec next_codepoint(t) :: {codepoint, t} | nil
|
||||
defdelegate next_codepoint(string), to: String.Unicode
|
||||
|
||||
@@ -731,11 +800,11 @@ defmodule String do
|
||||
## Examples
|
||||
|
||||
iex> String.next_grapheme("josé")
|
||||
{ "j", "osé" }
|
||||
{"j", "osé"}
|
||||
|
||||
"""
|
||||
@compile { :inline, next_grapheme: 1 }
|
||||
@spec next_grapheme(t) :: { grapheme, t } | nil
|
||||
@compile {:inline, next_grapheme: 1}
|
||||
@spec next_grapheme(t) :: {grapheme, t} | nil
|
||||
defdelegate next_grapheme(string), to: String.Graphemes
|
||||
|
||||
@doc """
|
||||
@@ -754,7 +823,7 @@ defmodule String do
|
||||
@spec first(t) :: grapheme | nil
|
||||
def first(string) do
|
||||
case next_grapheme(string) do
|
||||
{ char, _ } -> char
|
||||
{char, _} -> char
|
||||
nil -> nil
|
||||
end
|
||||
end
|
||||
@@ -1134,130 +1203,91 @@ defmodule String do
|
||||
raise ArgumentError
|
||||
end
|
||||
|
||||
defexception UnicodeConversionError, [:encoded, :message] do
|
||||
def exception(opts) do
|
||||
UnicodeConversionError[
|
||||
encoded: opts[:encoded],
|
||||
message: "#{opts[:kind]} #{detail(opts[:rest])}"
|
||||
]
|
||||
end
|
||||
@doc false
|
||||
def to_char_list(list) do
|
||||
IO.write :stderr, "String.to_char_list/1 is deprecated, please use List.from_char_data/1 instead\n#{Exception.format_stacktrace}"
|
||||
List.from_char_data(list)
|
||||
end
|
||||
|
||||
defp detail(rest) when is_binary(rest) do
|
||||
"encoding starting at #{inspect rest}"
|
||||
end
|
||||
@doc false
|
||||
def to_char_list!(list) do
|
||||
IO.write :stderr, "String.to_char_list!/1 is deprecated, please use List.from_char_data!/1 instead\n#{Exception.format_stacktrace}"
|
||||
List.from_char_data!(list)
|
||||
end
|
||||
|
||||
defp detail([h|_]) do
|
||||
"code point #{h}"
|
||||
end
|
||||
@doc false
|
||||
def from_char_list(list) do
|
||||
IO.write :stderr, "String.from_char_list/1 is deprecated, please use String.from_char_data/1 instead\n#{Exception.format_stacktrace}"
|
||||
from_char_data(list)
|
||||
end
|
||||
|
||||
@doc false
|
||||
def from_char_list!(list) do
|
||||
IO.write :stderr, "String.from_char_list!/1 is deprecated, please use String.from_char_data!/1 instead\n#{Exception.format_stacktrace}"
|
||||
from_char_data!(list)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Converts a string into a char list converting each codepoint to its
|
||||
respective integer value.
|
||||
Converts char data (a list of integers and strings) into a string.
|
||||
|
||||
If a string is given, returns the string itself.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> String.to_char_list("æß")
|
||||
{ :ok, 'æß' }
|
||||
iex> String.from_char_data([0x00E6, 0x00DF])
|
||||
{:ok, "æß"}
|
||||
|
||||
iex> String.to_char_list("abc")
|
||||
{ :ok, 'abc' }
|
||||
iex> String.from_char_data([0x0061, "bc"])
|
||||
{:ok, "abc"}
|
||||
|
||||
"""
|
||||
@spec to_char_list(String.t) :: { :ok, char_list } | { :error, list, binary } | { :incomplete, list, binary }
|
||||
def to_char_list(string) when is_binary(string) do
|
||||
case :unicode.characters_to_list(string) do
|
||||
result when is_list(result) ->
|
||||
{ :ok, result }
|
||||
|
||||
{ :error, _, _ } = error ->
|
||||
error
|
||||
|
||||
{ :incomplete, _, _ } = incomplete ->
|
||||
incomplete
|
||||
end
|
||||
@spec from_char_data(char_data) :: {:ok, String.t} | {:error, binary, binary} | {:incomplete, binary, binary}
|
||||
def from_char_data(binary) when is_binary(binary) do
|
||||
binary
|
||||
end
|
||||
|
||||
@doc """
|
||||
Converts a string into a char list converting each codepoint to its
|
||||
respective integer value.
|
||||
|
||||
In case the conversion fails or is incomplete,
|
||||
it raises a `String.UnicodeConversionError`.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> String.to_char_list!("æß")
|
||||
'æß'
|
||||
|
||||
iex> String.to_char_list!("abc")
|
||||
'abc'
|
||||
|
||||
"""
|
||||
@spec to_char_list!(String.t) :: char_list | no_return
|
||||
def to_char_list!(string) when is_binary(string) do
|
||||
case :unicode.characters_to_list(string) do
|
||||
result when is_list(result) ->
|
||||
result
|
||||
|
||||
{ :error, encoded, rest } ->
|
||||
raise UnicodeConversionError, encoded: encoded, rest: rest, kind: :invalid
|
||||
|
||||
{ :incomplete, encoded, rest } ->
|
||||
raise UnicodeConversionError, encoded: encoded, rest: rest, kind: :incomplete
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Converts a list of integer codepoints to a string.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> String.from_char_list([0x00E6, 0x00DF])
|
||||
{ :ok, "æß" }
|
||||
|
||||
iex> String.from_char_list([0x0061, 0x0062, 0x0063])
|
||||
{ :ok, "abc" }
|
||||
|
||||
"""
|
||||
@spec from_char_list(char_list) :: { :ok, String.t } | { :error, binary, binary } | { :incomplete, binary, binary }
|
||||
def from_char_list(list) when is_list(list) do
|
||||
def from_char_data(list) when is_list(list) do
|
||||
case :unicode.characters_to_binary(list) do
|
||||
result when is_binary(result) ->
|
||||
{ :ok, result }
|
||||
{:ok, result}
|
||||
|
||||
{ :error, _, _ } = error ->
|
||||
{:error, _, _} = error ->
|
||||
error
|
||||
|
||||
{ :incomplete, _, _ } = incomplete ->
|
||||
{:incomplete, _, _} = incomplete ->
|
||||
incomplete
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Converts a list of integer codepoints to a string.
|
||||
Converts char data (a list of integers and strings) into a string.
|
||||
|
||||
In case the conversion fails, it raises a `String.UnicodeConversionError`.
|
||||
In case the conversion fails, it raises a `UnicodeConversionError`.
|
||||
If a string is given, returns the string itself.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> String.from_char_list!([0x00E6, 0x00DF])
|
||||
iex> String.from_char_data!([0x00E6, 0x00DF])
|
||||
"æß"
|
||||
|
||||
iex> String.from_char_list!([0x0061, 0x0062, 0x0063])
|
||||
iex> String.from_char_data!([0x0061, "bc"])
|
||||
"abc"
|
||||
|
||||
"""
|
||||
@spec from_char_list!(char_list) :: String.t | no_return
|
||||
def from_char_list!(list) when is_list(list) do
|
||||
@spec from_char_data!(char_data) :: String.t | no_return
|
||||
def from_char_data!(binary) when is_binary(binary) do
|
||||
binary
|
||||
end
|
||||
|
||||
def from_char_data!(list) when is_list(list) do
|
||||
case :unicode.characters_to_binary(list) do
|
||||
result when is_binary(result) ->
|
||||
result
|
||||
|
||||
{ :error, encoded, rest } ->
|
||||
{:error, encoded, rest} ->
|
||||
raise UnicodeConversionError, encoded: encoded, rest: rest, kind: :invalid
|
||||
|
||||
{ :incomplete, encoded, rest } ->
|
||||
{:incomplete, encoded, rest} ->
|
||||
raise UnicodeConversionError, encoded: encoded, rest: rest, kind: :incomplete
|
||||
end
|
||||
end
|
||||
|
||||
@@ -63,7 +63,7 @@ defimpl String.Chars, for: List do
|
||||
"foobar"
|
||||
|
||||
"""
|
||||
def to_string(char_list), do: String.from_char_list!(char_list)
|
||||
def to_string(char_list), do: String.from_char_data!(char_list)
|
||||
end
|
||||
|
||||
defimpl String.Chars, for: Integer do
|
||||
@@ -80,6 +80,6 @@ defimpl String.Chars, for: Float do
|
||||
Simply converts the float to a string.
|
||||
"""
|
||||
def to_string(thing) do
|
||||
iolist_to_binary(:io_lib_format.fwrite_g(thing))
|
||||
iodata_to_binary(:io_lib_format.fwrite_g(thing))
|
||||
end
|
||||
end
|
||||
|
||||
+97
-102
@@ -4,7 +4,7 @@ defmodule StringIO do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> { :ok, pid } = StringIO.open("foo")
|
||||
iex> {:ok, pid} = StringIO.open("foo")
|
||||
iex> IO.read(pid, 2)
|
||||
"fo"
|
||||
|
||||
@@ -12,8 +12,6 @@ defmodule StringIO do
|
||||
|
||||
use GenServer.Behaviour
|
||||
|
||||
defrecordp :state, input: "", output: "", capture_prompt: false
|
||||
|
||||
@doc """
|
||||
Creates an IO device.
|
||||
|
||||
@@ -23,22 +21,22 @@ defmodule StringIO do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> { :ok, pid } = StringIO.open("foo")
|
||||
iex> {:ok, pid} = StringIO.open("foo")
|
||||
iex> IO.gets(pid, ">")
|
||||
"foo"
|
||||
iex> StringIO.contents(pid)
|
||||
{ "", "" }
|
||||
{"", ""}
|
||||
|
||||
iex> { :ok, pid } = StringIO.open("foo", capture_prompt: true)
|
||||
iex> {:ok, pid} = StringIO.open("foo", capture_prompt: true)
|
||||
iex> IO.gets(pid, ">")
|
||||
"foo"
|
||||
iex> StringIO.contents(pid)
|
||||
{ "", ">" }
|
||||
{"", ">"}
|
||||
|
||||
"""
|
||||
@spec open(binary, Keyword.t) :: { :ok, pid }
|
||||
@spec open(binary, Keyword.t) :: {:ok, pid}
|
||||
def open(string, options \\ []) when is_binary(string) do
|
||||
:gen_server.start_link(__MODULE__, { string, options }, [])
|
||||
:gen_server.start_link(__MODULE__, {string, options}, [])
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -46,13 +44,13 @@ defmodule StringIO do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> { :ok, pid } = StringIO.open("in")
|
||||
iex> {:ok, pid} = StringIO.open("in")
|
||||
iex> IO.write(pid, "out")
|
||||
iex> StringIO.contents(pid)
|
||||
{ "in", "out" }
|
||||
{"in", "out"}
|
||||
|
||||
"""
|
||||
@spec contents(pid) :: { binary, binary }
|
||||
@spec contents(pid) :: {binary, binary}
|
||||
def contents(pid) when is_pid(pid) do
|
||||
:gen_server.call(pid, :contents)
|
||||
end
|
||||
@@ -62,39 +60,39 @@ defmodule StringIO do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> { :ok, pid } = StringIO.open("in")
|
||||
iex> {:ok, pid} = StringIO.open("in")
|
||||
iex> IO.write(pid, "out")
|
||||
iex> StringIO.close(pid)
|
||||
{ :ok, { "in", "out" } }
|
||||
{:ok, {"in", "out"}}
|
||||
|
||||
"""
|
||||
@spec close(pid) :: { :ok, { binary, binary } }
|
||||
@spec close(pid) :: {:ok, {binary, binary}}
|
||||
def close(pid) when is_pid(pid) do
|
||||
:gen_server.call(pid, :close)
|
||||
end
|
||||
|
||||
## callbacks
|
||||
|
||||
def init({ string, options }) do
|
||||
def init({string, options}) do
|
||||
capture_prompt = options[:capture_prompt] || false
|
||||
{ :ok, state(input: string, capture_prompt: capture_prompt) }
|
||||
{:ok, %{input: string, output: "", capture_prompt: capture_prompt}}
|
||||
end
|
||||
|
||||
def handle_info({ :io_request, from, reply_as, req }, s) do
|
||||
def handle_info({:io_request, from, reply_as, req}, s) do
|
||||
s = io_request(from, reply_as, req, s)
|
||||
{ :noreply, s }
|
||||
{:noreply, s}
|
||||
end
|
||||
|
||||
def handle_info(msg, s) do
|
||||
super(msg, s)
|
||||
end
|
||||
|
||||
def handle_call(:contents, _from, state(input: input, output: output) = s) do
|
||||
{ :reply, { input, output }, s }
|
||||
def handle_call(:contents, _from, %{input: input, output: output} = s) do
|
||||
{:reply, {input, output}, s}
|
||||
end
|
||||
|
||||
def handle_call(:close, _from, state(input: input, output: output) = s) do
|
||||
{ :stop, :normal, { :ok, { input, output } }, s }
|
||||
def handle_call(:close, _from, %{input: input, output: output} = s) do
|
||||
{:stop, :normal, {:ok, {input, output}}, s}
|
||||
end
|
||||
|
||||
def handle_call(request, from, s) do
|
||||
@@ -102,168 +100,168 @@ defmodule StringIO do
|
||||
end
|
||||
|
||||
defp io_request(from, reply_as, req, s) do
|
||||
{ reply, s } = io_request(req, s)
|
||||
{reply, s} = io_request(req, s)
|
||||
io_reply(from, reply_as, to_reply(reply))
|
||||
s
|
||||
end
|
||||
|
||||
defp io_request({ :put_chars, chars }, state(output: output) = s) do
|
||||
{ :ok, state(s, output: << output :: binary, to_binary(chars) :: binary >>) }
|
||||
defp io_request({:put_chars, chars}, %{output: output} = s) do
|
||||
{:ok, %{s | output: << output :: binary, String.from_char_data!(chars) :: binary >>}}
|
||||
end
|
||||
|
||||
defp io_request({ :put_chars, m, f, as }, state(output: output) = s) do
|
||||
defp io_request({:put_chars, m, f, as}, %{output: output} = s) do
|
||||
chars = apply(m, f, as)
|
||||
{ :ok, state(s, output: << output :: binary, to_binary(chars) :: binary >>) }
|
||||
{:ok, %{s | output: << output :: binary, String.from_char_data!(chars) :: binary >>}}
|
||||
end
|
||||
|
||||
defp io_request({ :put_chars, _encoding, chars }, s) do
|
||||
io_request({ :put_chars, chars }, s)
|
||||
defp io_request({:put_chars, _encoding, chars}, s) do
|
||||
io_request({:put_chars, chars}, s)
|
||||
end
|
||||
|
||||
defp io_request({ :put_chars, _encoding, mod, func, args }, s) do
|
||||
io_request({ :put_chars, mod, func, args }, s)
|
||||
defp io_request({:put_chars, _encoding, mod, func, args}, s) do
|
||||
io_request({:put_chars, mod, func, args}, s)
|
||||
end
|
||||
|
||||
defp io_request({ :get_chars, prompt, n }, s) when n >= 0 do
|
||||
io_request({ :get_chars, :latin1, prompt, n }, s)
|
||||
defp io_request({:get_chars, prompt, n}, s) when n >= 0 do
|
||||
io_request({:get_chars, :latin1, prompt, n}, s)
|
||||
end
|
||||
|
||||
defp io_request({ :get_chars, encoding, prompt, n }, s) when n >= 0 do
|
||||
defp io_request({:get_chars, encoding, prompt, n}, s) when n >= 0 do
|
||||
get_chars(encoding, prompt, n, s)
|
||||
end
|
||||
|
||||
defp io_request({ :get_line, prompt }, s) do
|
||||
io_request({ :get_line, :latin1, prompt }, s)
|
||||
defp io_request({:get_line, prompt}, s) do
|
||||
io_request({:get_line, :latin1, prompt}, s)
|
||||
end
|
||||
|
||||
defp io_request({ :get_line, encoding, prompt }, s) do
|
||||
defp io_request({:get_line, encoding, prompt}, s) do
|
||||
get_line(encoding, prompt, s)
|
||||
end
|
||||
|
||||
defp io_request({ :get_until, prompt, mod, fun, args }, s) do
|
||||
io_request({ :get_until, :latin1, prompt, mod, fun, args }, s)
|
||||
defp io_request({:get_until, prompt, mod, fun, args}, s) do
|
||||
io_request({:get_until, :latin1, prompt, mod, fun, args}, s)
|
||||
end
|
||||
|
||||
defp io_request({ :get_until, encoding, prompt, mod, fun, args }, s) do
|
||||
defp io_request({:get_until, encoding, prompt, mod, fun, args}, s) do
|
||||
get_until(encoding, prompt, mod, fun, args, s)
|
||||
end
|
||||
|
||||
defp io_request({ :get_password, encoding }, s) do
|
||||
defp io_request({:get_password, encoding}, s) do
|
||||
get_line(encoding, "", s)
|
||||
end
|
||||
|
||||
defp io_request({ :setopts, _opts }, s) do
|
||||
{ { :error, :enotsup }, s }
|
||||
defp io_request({:setopts, _opts}, s) do
|
||||
{{:error, :enotsup}, s}
|
||||
end
|
||||
|
||||
defp io_request(:getopts, s) do
|
||||
{ { :ok, [binary: true, encoding: :unicode] }, s }
|
||||
{{:ok, [binary: true, encoding: :unicode]}, s}
|
||||
end
|
||||
|
||||
defp io_request({ :get_geometry, :columns }, s) do
|
||||
{ { :error, :enotsup }, s }
|
||||
defp io_request({:get_geometry, :columns}, s) do
|
||||
{{:error, :enotsup}, s}
|
||||
end
|
||||
|
||||
defp io_request({ :get_geometry, :rows }, s) do
|
||||
{ { :error, :enotsup }, s }
|
||||
defp io_request({:get_geometry, :rows}, s) do
|
||||
{{:error, :enotsup}, s}
|
||||
end
|
||||
|
||||
defp io_request({ :requests, reqs }, s) do
|
||||
io_requests(reqs, { :ok, s })
|
||||
defp io_request({:requests, reqs}, s) do
|
||||
io_requests(reqs, {:ok, s})
|
||||
end
|
||||
|
||||
defp io_request(_, s) do
|
||||
{ { :error, :request }, s }
|
||||
{{:error, :request}, s}
|
||||
end
|
||||
|
||||
## get_chars
|
||||
|
||||
defp get_chars(encoding, prompt, n,
|
||||
state(input: input, output: output, capture_prompt: capture_prompt) = s) do
|
||||
%{input: input, output: output, capture_prompt: capture_prompt} = s) do
|
||||
case do_get_chars(input, encoding, n) do
|
||||
{ :error, _ } = error ->
|
||||
{ error, s }
|
||||
{ result, input } ->
|
||||
{:error, _} = error ->
|
||||
{error, s}
|
||||
{result, input} ->
|
||||
if capture_prompt do
|
||||
output = << output :: binary, to_binary(prompt) :: binary >>
|
||||
output = << output :: binary, String.from_char_data!(prompt) :: binary >>
|
||||
end
|
||||
|
||||
{ result, state(s, input: input, output: output) }
|
||||
{result, %{s | input: input, output: output}}
|
||||
end
|
||||
end
|
||||
|
||||
defp do_get_chars("", _encoding, _n) do
|
||||
{ :eof, "" }
|
||||
{:eof, ""}
|
||||
end
|
||||
|
||||
defp do_get_chars(input, :latin1, n) when byte_size(input) < n do
|
||||
{ input, "" }
|
||||
{input, ""}
|
||||
end
|
||||
|
||||
defp do_get_chars(input, :latin1, n) do
|
||||
<< chars :: [ binary, size(n) ], rest :: binary >> = input
|
||||
{ chars, rest }
|
||||
{chars, rest}
|
||||
end
|
||||
|
||||
defp do_get_chars(input, encoding, n) do
|
||||
try do
|
||||
case :file_io_server.count_and_find(input, n, encoding) do
|
||||
{ buf_count, split_pos } when buf_count < n or split_pos == :none ->
|
||||
{ input, "" }
|
||||
{ _buf_count, split_pos } ->
|
||||
{buf_count, split_pos} when buf_count < n or split_pos == :none ->
|
||||
{input, ""}
|
||||
{_buf_count, split_pos} ->
|
||||
<< chars :: [ binary, size(split_pos) ], rest :: binary >> = input
|
||||
{ chars, rest }
|
||||
{chars, rest}
|
||||
end
|
||||
catch
|
||||
:exit, :invalid_unicode ->
|
||||
{ :error, :invalid_unicode }
|
||||
{:error, :invalid_unicode}
|
||||
end
|
||||
end
|
||||
|
||||
## get_line
|
||||
|
||||
defp get_line(encoding, prompt,
|
||||
state(input: input, output: output, capture_prompt: capture_prompt) = s) do
|
||||
%{input: input, output: output, capture_prompt: capture_prompt} = s) do
|
||||
case :unicode.characters_to_list(input, encoding) do
|
||||
{ :error, _, _ } ->
|
||||
{ { :error, :collect_line }, s }
|
||||
{ :incomplete, _, _ } ->
|
||||
{ { :error, :collect_line }, s }
|
||||
{:error, _, _} ->
|
||||
{{:error, :collect_line}, s}
|
||||
{:incomplete, _, _} ->
|
||||
{{:error, :collect_line}, s}
|
||||
chars ->
|
||||
{ result, input } = do_get_line(chars, encoding)
|
||||
{result, input} = do_get_line(chars, encoding)
|
||||
|
||||
if capture_prompt do
|
||||
output = << output :: binary, to_binary(prompt) :: binary >>
|
||||
output = << output :: binary, String.from_char_data!(prompt) :: binary >>
|
||||
end
|
||||
|
||||
{ result, state(s, input: input, output: output) }
|
||||
{result, %{s | input: input, output: output}}
|
||||
end
|
||||
end
|
||||
|
||||
defp do_get_line('', _encoding) do
|
||||
{ :eof, "" }
|
||||
{:eof, ""}
|
||||
end
|
||||
|
||||
defp do_get_line(chars, encoding) do
|
||||
{ line, rest } = collect_line(chars)
|
||||
{ :unicode.characters_to_binary(line, encoding),
|
||||
:unicode.characters_to_binary(rest, encoding) }
|
||||
{line, rest} = collect_line(chars)
|
||||
{:unicode.characters_to_binary(line, encoding),
|
||||
:unicode.characters_to_binary(rest, encoding)}
|
||||
end
|
||||
|
||||
## get_until
|
||||
|
||||
defp get_until(encoding, prompt, mod, fun, args,
|
||||
state(input: input, output: output, capture_prompt: capture_prompt) = s) do
|
||||
%{input: input, output: output, capture_prompt: capture_prompt} = s) do
|
||||
case :unicode.characters_to_list(input, encoding) do
|
||||
{ :error, _, _ } ->
|
||||
{ :error, s }
|
||||
{ :incomplete, _, _ } ->
|
||||
{ :error, s }
|
||||
{:error, _, _} ->
|
||||
{:error, s}
|
||||
{:incomplete, _, _} ->
|
||||
{:error, s}
|
||||
chars ->
|
||||
{ result, input, count } = do_get_until(chars, encoding, mod, fun, args)
|
||||
{result, input, count} = do_get_until(chars, encoding, mod, fun, args)
|
||||
|
||||
if capture_prompt do
|
||||
output = << output :: binary, :binary.copy(to_binary(prompt), count) :: binary >>
|
||||
output = << output :: binary, :binary.copy(String.from_char_data!(prompt), count) :: binary >>
|
||||
end
|
||||
|
||||
input =
|
||||
@@ -272,7 +270,7 @@ defmodule StringIO do
|
||||
_ -> :unicode.characters_to_binary(input, encoding)
|
||||
end
|
||||
|
||||
{ result, state(s, input: input, output: output) }
|
||||
{result, %{s | input: input, output: output}}
|
||||
end
|
||||
end
|
||||
|
||||
@@ -280,30 +278,30 @@ defmodule StringIO do
|
||||
|
||||
defp do_get_until('', encoding, mod, fun, args, continuation, count) do
|
||||
case apply(mod, fun, [continuation, :eof | args]) do
|
||||
{ :done, result, rest } ->
|
||||
{ result, rest, count + 1 }
|
||||
{ :more, next_continuation } ->
|
||||
{:done, result, rest} ->
|
||||
{result, rest, count + 1}
|
||||
{:more, next_continuation} ->
|
||||
do_get_until('', encoding, mod, fun, args, next_continuation, count + 1)
|
||||
end
|
||||
end
|
||||
|
||||
defp do_get_until(chars, encoding, mod, fun, args, continuation, count) do
|
||||
{ line, rest } = collect_line(chars)
|
||||
{line, rest} = collect_line(chars)
|
||||
|
||||
case apply(mod, fun, [continuation, line | args]) do
|
||||
{ :done, result, rest1 } ->
|
||||
{:done, result, rest1} ->
|
||||
unless rest1 == :eof do
|
||||
rest = rest1 ++ rest
|
||||
end
|
||||
{ result, rest, count + 1 }
|
||||
{ :more, next_continuation } ->
|
||||
{result, rest, count + 1}
|
||||
{:more, next_continuation} ->
|
||||
do_get_until(rest, encoding, mod, fun, args, next_continuation, count + 1)
|
||||
end
|
||||
end
|
||||
|
||||
## io_requests
|
||||
|
||||
defp io_requests([r|rs], { :ok, s }) do
|
||||
defp io_requests([r|rs], {:ok, s}) do
|
||||
io_requests(rs, io_request(r, s))
|
||||
end
|
||||
|
||||
@@ -318,15 +316,15 @@ defmodule StringIO do
|
||||
end
|
||||
|
||||
defp collect_line([], stack) do
|
||||
{ :lists.reverse(stack), [] }
|
||||
{:lists.reverse(stack), []}
|
||||
end
|
||||
|
||||
defp collect_line([?\r, ?\n | rest], stack) do
|
||||
{ :lists.reverse([?\n|stack]), rest }
|
||||
{:lists.reverse([?\n|stack]), rest}
|
||||
end
|
||||
|
||||
defp collect_line([?\n | rest], stack) do
|
||||
{ :lists.reverse([?\n|stack]), rest }
|
||||
{:lists.reverse([?\n|stack]), rest}
|
||||
end
|
||||
|
||||
defp collect_line([h|t], stack) do
|
||||
@@ -334,12 +332,9 @@ defmodule StringIO do
|
||||
end
|
||||
|
||||
defp io_reply(from, reply_as, reply) do
|
||||
send from, { :io_reply, reply_as, reply }
|
||||
send from, {:io_reply, reply_as, reply}
|
||||
end
|
||||
|
||||
defp to_binary(list) when is_list(list), do: String.from_char_list!(list)
|
||||
defp to_binary(binary) when is_binary(binary), do: binary
|
||||
|
||||
defp to_reply(list) when is_list(list), do: String.from_char_list!(list)
|
||||
defp to_reply(list) when is_list(list), do: String.from_char_data!(list)
|
||||
defp to_reply(other), do: other
|
||||
end
|
||||
|
||||
@@ -25,7 +25,7 @@ defmodule Supervisor.Behaviour do
|
||||
end
|
||||
end
|
||||
|
||||
{ :ok, pid } = :supervisor.start_link(MyServer, [])
|
||||
{:ok, pid} = :supervisor.start_link(MyServer, [])
|
||||
|
||||
"""
|
||||
|
||||
@@ -87,7 +87,7 @@ defmodule Supervisor.Behaviour do
|
||||
maxR = Keyword.get(options, :max_restarts, 5)
|
||||
maxS = Keyword.get(options, :max_seconds, 5)
|
||||
|
||||
{ :ok, { { strategy, maxR, maxS }, children } }
|
||||
{:ok, {{strategy, maxR, maxS}, children}}
|
||||
end
|
||||
|
||||
@child_doc """
|
||||
@@ -122,7 +122,7 @@ defmodule Supervisor.Behaviour do
|
||||
|
||||
* `:transient` - the child process is restarted only if it
|
||||
terminates abnormally, i.e. with another exit reason than
|
||||
`:normal`, `:shutdown` or `{ :shutdown, term }`;
|
||||
`:normal`, `:shutdown` or `{:shutdown, term}`;
|
||||
|
||||
## Shutdown values
|
||||
|
||||
@@ -178,7 +178,7 @@ defmodule Supervisor.Behaviour do
|
||||
restart = Keyword.get(options, :restart, :permanent)
|
||||
shutdown = Keyword.get(options, :shutdown, 5000)
|
||||
|
||||
{ id, { module, function, args },
|
||||
restart, shutdown, type, modules }
|
||||
{id, {module, function, args},
|
||||
restart, shutdown, type, modules}
|
||||
end
|
||||
end
|
||||
|
||||
+30
-36
@@ -11,7 +11,7 @@ defmodule System do
|
||||
|
||||
defp read_stripped(path) do
|
||||
case :file.read_file(path) do
|
||||
{ :ok, binary } ->
|
||||
{:ok, binary} ->
|
||||
strip_re(binary, "^\s+|\s+$")
|
||||
_ -> ""
|
||||
end
|
||||
@@ -31,7 +31,7 @@ defmodule System do
|
||||
defmacrop get_describe do
|
||||
dirpath = :filename.join(__DIR__, "../../../.git")
|
||||
case :file.read_file_info(dirpath) do
|
||||
{ :ok, _ } ->
|
||||
{:ok, _} ->
|
||||
if :os.find_executable('git') do
|
||||
data = :os.cmd('git describe --always --tags')
|
||||
strip_re(data, "\n")
|
||||
@@ -44,7 +44,7 @@ defmodule System do
|
||||
|
||||
# Get the date at compilation time.
|
||||
defmacrop get_date do
|
||||
iolist_to_binary :httpd_util.rfc1123_date
|
||||
iodata_to_binary :httpd_util.rfc1123_date
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -83,7 +83,7 @@ defmodule System do
|
||||
"""
|
||||
@spec argv([String.t]) :: :ok
|
||||
def argv(args) do
|
||||
:elixir_code_server.cast({ :argv, args })
|
||||
:elixir_code_server.cast({:argv, args})
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -94,7 +94,7 @@ defmodule System do
|
||||
"""
|
||||
def cwd do
|
||||
case :file.get_cwd do
|
||||
{ :ok, base } -> String.from_char_list!(base)
|
||||
{:ok, base} -> String.from_char_data!(base)
|
||||
_ -> nil
|
||||
end
|
||||
end
|
||||
@@ -117,7 +117,7 @@ defmodule System do
|
||||
"""
|
||||
def user_home do
|
||||
case :os.type() do
|
||||
{ :win32, _ } -> get_windows_home
|
||||
{:win32, _} -> get_windows_home
|
||||
_ -> get_unix_home
|
||||
end
|
||||
end
|
||||
@@ -189,17 +189,15 @@ defmodule System do
|
||||
end
|
||||
|
||||
defp write_tmp_dir(dir) do
|
||||
case :file.read_file_info(dir) do
|
||||
{:ok, info} ->
|
||||
type_index = File.Stat.__record__(:index, :type)
|
||||
access_index = File.Stat.__record__(:index, :access)
|
||||
case { elem(info, type_index), elem(info, access_index) } do
|
||||
{ :directory, access } when access in [:read_write, :write] ->
|
||||
String.from_char_list!(dir)
|
||||
case File.stat(dir) do
|
||||
{:ok, stat} ->
|
||||
case {stat.type, stat.access} do
|
||||
{:directory, access} when access in [:read_write, :write] ->
|
||||
String.from_char_data!(dir)
|
||||
_ ->
|
||||
nil
|
||||
end
|
||||
{ :error, _ } -> nil
|
||||
{:error, _} -> nil
|
||||
end
|
||||
end
|
||||
|
||||
@@ -214,7 +212,7 @@ defmodule System do
|
||||
as an argument.
|
||||
"""
|
||||
def at_exit(fun) when is_function(fun, 1) do
|
||||
:elixir_code_server.cast { :at_exit, fun }
|
||||
:elixir_code_server.cast {:at_exit, fun}
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -225,9 +223,9 @@ defmodule System do
|
||||
the result as a binary.
|
||||
|
||||
If `command` is a char list, a char list is returned.
|
||||
Returns a binary otherwise.
|
||||
Otherwise a string, correctly encoded in UTF-8, is expected.
|
||||
"""
|
||||
@spec cmd(binary) :: binary
|
||||
@spec cmd(String.t) :: String.t
|
||||
@spec cmd(char_list) :: char_list
|
||||
|
||||
def cmd(command) when is_list(command) do
|
||||
@@ -235,9 +233,7 @@ defmodule System do
|
||||
end
|
||||
|
||||
def cmd(command) when is_binary(command) do
|
||||
# Notice we don't use unicode for conversion
|
||||
# because the OS is expecting and returning raw bytes
|
||||
:binary.list_to_bin :os.cmd(:binary.bin_to_list(command))
|
||||
String.from_char_data! :os.cmd(List.from_char_data!(command))
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -260,11 +256,9 @@ defmodule System do
|
||||
end
|
||||
|
||||
def find_executable(program) when is_binary(program) do
|
||||
# Notice we don't use unicode for conversion
|
||||
# because the OS is expecting and returning raw bytes
|
||||
case :os.find_executable(:binary.bin_to_list(program)) do
|
||||
case :os.find_executable(List.from_char_data!(program)) do
|
||||
false -> nil
|
||||
other -> :binary.list_to_bin(other)
|
||||
other -> String.from_char_data!(other)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -274,12 +268,12 @@ defmodule System do
|
||||
Returns a list of all environment variables. Each variable is given as a
|
||||
`{name, value}` tuple where both `name` and `value` are strings.
|
||||
"""
|
||||
@spec get_env() :: [{String.t, String.t}]
|
||||
@spec get_env() :: %{String.t => String.t}
|
||||
def get_env do
|
||||
Enum.map(:os.getenv, fn var ->
|
||||
var = String.from_char_list! var
|
||||
[k, v] = String.split var, "=", global: false
|
||||
{k, v}
|
||||
Enum.into(:os.getenv, %{}, fn var ->
|
||||
var = String.from_char_data! var
|
||||
[k, v] = String.split var, "=", parts: 2
|
||||
{k, v}
|
||||
end)
|
||||
end
|
||||
|
||||
@@ -292,9 +286,9 @@ defmodule System do
|
||||
"""
|
||||
@spec get_env(binary) :: binary | nil
|
||||
def get_env(varname) when is_binary(varname) do
|
||||
case :os.getenv(String.to_char_list!(varname)) do
|
||||
case :os.getenv(List.from_char_data!(varname)) do
|
||||
false -> nil
|
||||
other -> String.from_char_list!(other)
|
||||
other -> String.from_char_data!(other)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -307,7 +301,7 @@ defmodule System do
|
||||
See http://www.erlang.org/doc/man/os.html#getpid-0 for more info.
|
||||
"""
|
||||
@spec get_pid() :: binary
|
||||
def get_pid, do: iolist_to_binary(:os.getpid)
|
||||
def get_pid, do: iodata_to_binary(:os.getpid)
|
||||
|
||||
@doc """
|
||||
Set an environment variable value.
|
||||
@@ -316,7 +310,7 @@ defmodule System do
|
||||
"""
|
||||
@spec put_env(binary, binary) :: :ok
|
||||
def put_env(varname, value) when is_binary(varname) and is_binary(value) do
|
||||
:os.putenv :binary.bin_to_list(varname), String.to_char_list!(value)
|
||||
:os.putenv List.from_char_data!(varname), List.from_char_data!(value)
|
||||
:ok
|
||||
end
|
||||
|
||||
@@ -338,7 +332,7 @@ defmodule System do
|
||||
"""
|
||||
@spec delete_env(String.t) :: :ok
|
||||
def delete_env(varname) do
|
||||
:os.unsetenv(String.to_char_list!(varname))
|
||||
:os.unsetenv(List.from_char_data!(varname))
|
||||
:ok
|
||||
end
|
||||
|
||||
@@ -367,7 +361,7 @@ defmodule System do
|
||||
* If `:abort`, the runtime system aborts producing a core dump, if that is
|
||||
enabled in the operating system;
|
||||
|
||||
* If a char list, an erlang crash dump is produced with status as slogan,
|
||||
* 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
|
||||
@@ -391,6 +385,6 @@ defmodule System do
|
||||
end
|
||||
|
||||
def halt(status) when is_binary(status) do
|
||||
:erlang.halt(:binary.bin_to_list(status))
|
||||
:erlang.halt(List.from_char_data!(status))
|
||||
end
|
||||
end
|
||||
|
||||
+11
-5
@@ -9,10 +9,12 @@ defmodule Tuple do
|
||||
Creates a tuple of size `size` containing the
|
||||
given `data` at every position.
|
||||
|
||||
Inlined by the compiler.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Tuple.duplicate(:hello, 3)
|
||||
{ :hello, :hello, :hello }
|
||||
{:hello, :hello, :hello}
|
||||
|
||||
"""
|
||||
@spec duplicate(term, non_neg_integer) :: tuple
|
||||
@@ -27,11 +29,13 @@ defmodule Tuple do
|
||||
Raises an `ArgumentError` if `index` is greater than the
|
||||
length of `tuple`.
|
||||
|
||||
Inlined by the compiler.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> tuple = { :bar, :baz }
|
||||
iex> tuple = {:bar, :baz}
|
||||
iex> Tuple.insert_at(tuple, 0, :foo)
|
||||
{ :foo, :bar, :baz }
|
||||
{:foo, :bar, :baz}
|
||||
|
||||
"""
|
||||
@spec insert_at(tuple, non_neg_integer, term) :: tuple
|
||||
@@ -46,11 +50,13 @@ defmodule Tuple do
|
||||
Raises an `ArgumentError` if `index` is greater than
|
||||
or equal to the length of `tuple`.
|
||||
|
||||
Inlined by the compiler.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> tuple = { :foo, :bar, :baz }
|
||||
iex> tuple = {:foo, :bar, :baz}
|
||||
iex> Tuple.delete_at(tuple, 0)
|
||||
{ :bar, :baz }
|
||||
{:bar, :baz}
|
||||
|
||||
"""
|
||||
@spec delete_at(tuple, non_neg_integer) :: tuple
|
||||
|
||||
+38
-34
@@ -3,22 +3,22 @@ defmodule URI do
|
||||
Utilities for working with and creating URIs.
|
||||
"""
|
||||
|
||||
defrecord Info, [scheme: nil, path: nil, query: nil,
|
||||
fragment: nil, authority: nil,
|
||||
userinfo: nil, host: nil, port: nil]
|
||||
defstruct scheme: nil, path: nil, query: nil,
|
||||
fragment: nil, authority: nil,
|
||||
userinfo: nil, host: nil, port: nil
|
||||
|
||||
import Bitwise
|
||||
|
||||
@ports [
|
||||
{ "ftp", 21 },
|
||||
{ "http", 80 },
|
||||
{ "https", 443 },
|
||||
{ "ldap", 389 },
|
||||
{ "sftp", 22 },
|
||||
{ "tftp", 69 },
|
||||
]
|
||||
@ports %{
|
||||
"ftp" => 21,
|
||||
"http" => 80,
|
||||
"https" => 443,
|
||||
"ldap" => 389,
|
||||
"sftp" => 22,
|
||||
"tftp" => 69,
|
||||
}
|
||||
|
||||
Enum.each @ports, fn { scheme, port } ->
|
||||
Enum.each @ports, fn {scheme, port} ->
|
||||
def normalize_scheme(unquote(scheme)), do: unquote(scheme)
|
||||
def default_port(unquote(scheme)), do: unquote(port)
|
||||
end
|
||||
@@ -45,16 +45,20 @@ defmodule URI do
|
||||
|
||||
"""
|
||||
def default_port(scheme) when is_binary(scheme) do
|
||||
{ :ok, dict } = :application.get_env(:elixir, :uri)
|
||||
Dict.get(dict, scheme)
|
||||
{:ok, dict} = Application.fetch_env(:elixir, :uri)
|
||||
Map.get(dict, scheme)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Registers a scheme with a default port.
|
||||
|
||||
It is recommended for this function to be invoked in your
|
||||
application start callback in case you want to register
|
||||
new URIs.
|
||||
"""
|
||||
def default_port(scheme, port) when is_binary(scheme) and port > 0 do
|
||||
{ :ok, dict } = :application.get_env(:elixir, :uri)
|
||||
:application.set_env(:elixir, :uri, Dict.put(dict, scheme, port))
|
||||
{:ok, dict} = Application.fetch_env(:elixir, :uri)
|
||||
Application.put_env(:elixir, :uri, Map.put(dict, scheme, port), persist: true)
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -80,7 +84,7 @@ defmodule URI do
|
||||
Decodes a query string into a dictionary (by default uses a map).
|
||||
|
||||
Given a query string of the form "key1=value1&key2=value2...", produces a
|
||||
`HashDict` with one entry for each key-value pair. Each key and value will be a
|
||||
map with one entry for each key-value pair. Each key and value will be a
|
||||
binary. Keys and values will be percent-unescaped.
|
||||
|
||||
Use `query_decoder/1` if you want to iterate over each value manually.
|
||||
@@ -92,7 +96,7 @@ defmodule URI do
|
||||
|
||||
"""
|
||||
def decode_query(q, dict \\ %{}) when is_binary(q) do
|
||||
Enum.reduce query_decoder(q), dict, fn({ k, v }, acc) -> Dict.put(acc, k, v) end
|
||||
Enum.reduce query_decoder(q), dict, fn({k, v}, acc) -> Dict.put(acc, k, v) end
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -114,19 +118,19 @@ defmodule URI do
|
||||
end
|
||||
|
||||
defp do_decoder(q) do
|
||||
{ first, next } =
|
||||
{first, next} =
|
||||
case :binary.split(q, "&") do
|
||||
[first, rest] -> { first, rest }
|
||||
[first] -> { first, "" }
|
||||
[first, rest] -> {first, rest}
|
||||
[first] -> {first, ""}
|
||||
end
|
||||
|
||||
current =
|
||||
case :binary.split(first, "=") do
|
||||
[ key, value ] -> { decode(key), decode(value) }
|
||||
[ key ] -> { decode(key), nil }
|
||||
[ key, value ] -> {decode(key), decode(value)}
|
||||
[ key ] -> {decode(key), nil}
|
||||
end
|
||||
|
||||
{ current, next }
|
||||
{current, next}
|
||||
end
|
||||
|
||||
defp pair({k, _}) when is_list(k) do
|
||||
@@ -213,9 +217,9 @@ defmodule URI do
|
||||
## Examples
|
||||
|
||||
iex> URI.parse("http://elixir-lang.org/")
|
||||
URI.Info[scheme: "http", path: "/", query: nil, fragment: nil,
|
||||
authority: "elixir-lang.org", userinfo: nil,
|
||||
host: "elixir-lang.org", port: 80]
|
||||
%URI{scheme: "http", path: "/", query: nil, fragment: nil,
|
||||
authority: "elixir-lang.org", userinfo: nil,
|
||||
host: "elixir-lang.org", port: 80}
|
||||
|
||||
"""
|
||||
def parse(s) when is_binary(s) do
|
||||
@@ -224,7 +228,7 @@ defmodule URI do
|
||||
parts = nillify(Regex.run(regex, s))
|
||||
|
||||
destructure [_, _, scheme, _, authority, path, _, query, _, fragment], parts
|
||||
{ userinfo, host, port } = split_authority(authority)
|
||||
{userinfo, host, port} = split_authority(authority)
|
||||
|
||||
if authority do
|
||||
authority = ""
|
||||
@@ -240,11 +244,11 @@ defmodule URI do
|
||||
port = default_port(scheme)
|
||||
end
|
||||
|
||||
URI.Info[
|
||||
%URI{
|
||||
scheme: scheme, path: path, query: query,
|
||||
fragment: fragment, authority: authority,
|
||||
userinfo: userinfo, host: host, port: port
|
||||
]
|
||||
}
|
||||
end
|
||||
|
||||
# Split an authority into its userinfo, host and port parts.
|
||||
@@ -256,7 +260,7 @@ defmodule URI do
|
||||
port = if port, do: binary_to_integer(port)
|
||||
host = if host, do: host |> String.lstrip(?[) |> String.rstrip(?])
|
||||
|
||||
{ userinfo, host, port }
|
||||
{userinfo, host, port}
|
||||
end
|
||||
|
||||
# Regex.run returns empty strings sometimes. We want
|
||||
@@ -268,12 +272,12 @@ defmodule URI do
|
||||
end
|
||||
end
|
||||
|
||||
defimpl String.Chars, for: URI.Info do
|
||||
def to_string(URI.Info[] = uri) do
|
||||
defimpl String.Chars, for: URI do
|
||||
def to_string(uri) do
|
||||
scheme = uri.scheme
|
||||
|
||||
if scheme && (port = URI.default_port(scheme)) do
|
||||
if uri.port == port, do: uri = uri.port(nil)
|
||||
if uri.port == port, do: uri = %{uri | port: nil}
|
||||
end
|
||||
|
||||
result = ""
|
||||
|
||||
+93
-86
@@ -2,7 +2,7 @@ defmodule Version do
|
||||
@moduledoc ~S"""
|
||||
Functions for parsing and matching versions against requirements.
|
||||
|
||||
A version is a string in a specific format or a `Version.Schema`
|
||||
A version is a string in a specific format or a `Version`
|
||||
generated after parsing via `Version.parse/1`.
|
||||
|
||||
`Version` parsing and requirements follow
|
||||
@@ -22,6 +22,12 @@ defmodule Version do
|
||||
|
||||
"1.0.0-alpha.3+20130417140000"
|
||||
|
||||
## Struct
|
||||
|
||||
The version is represented by the Version struct and it has its
|
||||
fields named according to Semver: `:major`, `:minor`, `:patch`,
|
||||
`:pre` and `:build`.
|
||||
|
||||
## Requirements
|
||||
|
||||
Requirements allow you to specify which versions of a given
|
||||
@@ -47,17 +53,20 @@ defmodule Version do
|
||||
|
||||
"""
|
||||
|
||||
@type t :: String.t | Version.Schema.t
|
||||
@type requirement :: String.t | Version.Requirement.t
|
||||
@type matchable :: { major :: String.t | non_neg_integer,
|
||||
minor :: non_neg_integer | nil,
|
||||
patch :: non_neg_integer | nil,
|
||||
pre :: [String.t] }
|
||||
|
||||
import Kernel, except: [match?: 2]
|
||||
defstruct [:major, :minor, :patch, :pre, :build]
|
||||
|
||||
defrecord Schema, [:major, :minor, :patch, :pre, :build, :source]
|
||||
defrecord Requirement, [:source, :matchspec]
|
||||
@type version :: String.t | t
|
||||
@type requirement :: String.t | Version.Requirement.t
|
||||
@type matchable :: {major :: String.t | non_neg_integer,
|
||||
minor :: non_neg_integer | nil,
|
||||
patch :: non_neg_integer | nil,
|
||||
pre :: [String.t]}
|
||||
|
||||
defmodule Requirement do
|
||||
@moduledoc false
|
||||
defstruct [:source, :matchspec]
|
||||
end
|
||||
|
||||
defexception InvalidRequirement, [:message]
|
||||
defexception InvalidVersion, [:message]
|
||||
@@ -86,18 +95,18 @@ defmodule Version do
|
||||
** (Version.InvalidRequirement) == ==1.0.0
|
||||
|
||||
"""
|
||||
@spec match?(t, requirement) :: boolean
|
||||
@spec match?(version, requirement) :: boolean
|
||||
def match?(vsn, req) when is_binary(req) do
|
||||
case parse_requirement(req) do
|
||||
{ :ok, req } ->
|
||||
{:ok, req} ->
|
||||
match?(vsn, req)
|
||||
:error ->
|
||||
raise InvalidRequirement, message: req
|
||||
end
|
||||
end
|
||||
|
||||
def match?(version, Requirement[matchspec: spec]) do
|
||||
{ :ok, result } = :ets.test_ms(to_matchable(version), spec)
|
||||
def match?(version, %Requirement{matchspec: spec}) do
|
||||
{:ok, result} = :ets.test_ms(to_matchable(version), spec)
|
||||
result != false
|
||||
end
|
||||
|
||||
@@ -121,15 +130,15 @@ defmodule Version do
|
||||
** (Version.InvalidVersion) invalid
|
||||
|
||||
"""
|
||||
@spec compare(String.t | Schema.t, String.t | Schema.t) :: :gt | :eq | :lt
|
||||
@spec compare(version, version) :: :gt | :eq | :lt
|
||||
def compare(vsn1, vsn2) do
|
||||
do_compare(to_matchable(vsn1), to_matchable(vsn2))
|
||||
end
|
||||
|
||||
defp do_compare({ major1, minor1, patch1, pre1 }, { major2, minor2, patch2, pre2 }) do
|
||||
defp do_compare({major1, minor1, patch1, pre1}, {major2, minor2, patch2, pre2}) do
|
||||
cond do
|
||||
{ major1, minor1, patch1 } > { major2, minor2, patch2 } -> :gt
|
||||
{ major1, minor1, patch1 } < { major2, minor2, patch2 } -> :lt
|
||||
{major1, minor1, patch1} > {major2, minor2, patch2} -> :gt
|
||||
{major1, minor1, patch1} < {major2, minor2, patch2} -> :lt
|
||||
pre1 == [] and pre2 != [] -> :gt
|
||||
pre1 != [] and pre2 == [] -> :lt
|
||||
pre1 > pre2 -> :gt
|
||||
@@ -139,31 +148,31 @@ defmodule Version do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Parse a version string into a `Version.Schema`.
|
||||
Parse a version string into a `Version`.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Version.parse("2.0.1-alpha1") |> elem(1)
|
||||
#Version.Schema<2.0.1-alpha1>
|
||||
#Version<2.0.1-alpha1>
|
||||
|
||||
iex> Version.parse("2.0-alpha1")
|
||||
:error
|
||||
|
||||
"""
|
||||
@spec parse(String.t) :: { :ok, Schema.t } | :error
|
||||
@spec parse(String.t) :: {:ok, t} | :error
|
||||
def parse(string) when is_binary(string) do
|
||||
case Version.Parser.parse_version(string) do
|
||||
{ :ok, { major, minor, patch, pre } } ->
|
||||
vsn = Version.Schema[major: major, minor: minor, patch: patch,
|
||||
pre: pre, source: string, build: get_build(string)]
|
||||
{ :ok, vsn }
|
||||
{:ok, {major, minor, patch, pre}} ->
|
||||
vsn = %Version{major: major, minor: minor, patch: patch,
|
||||
pre: pre, build: get_build(string)}
|
||||
{:ok, vsn}
|
||||
:error ->
|
||||
:error
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Parse a version requirement string into a `Version.Schema`.
|
||||
Parse a version requirement string into a `Version.Requirement`.
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -174,23 +183,23 @@ defmodule Version do
|
||||
:error
|
||||
|
||||
"""
|
||||
@spec parse_requirement(String.t) :: { :ok, Requirement.t } | :error
|
||||
@spec parse_requirement(String.t) :: {:ok, Requirement.t} | :error
|
||||
def parse_requirement(string) when is_binary(string) do
|
||||
case Version.Parser.parse_requirement(string) do
|
||||
{ :ok, spec } ->
|
||||
{ :ok, Requirement[source: string, matchspec: spec] }
|
||||
{:ok, spec} ->
|
||||
{:ok, %Requirement{source: string, matchspec: spec}}
|
||||
:error ->
|
||||
:error
|
||||
end
|
||||
end
|
||||
|
||||
defp to_matchable(Schema[major: major, minor: minor, patch: patch, pre: pre]) do
|
||||
{ major, minor, patch, pre }
|
||||
defp to_matchable(%Version{major: major, minor: minor, patch: patch, pre: pre}) do
|
||||
{major, minor, patch, pre}
|
||||
end
|
||||
|
||||
defp to_matchable(string) do
|
||||
case Version.Parser.parse_version(string) do
|
||||
{ :ok, vsn } -> vsn
|
||||
{:ok, vsn} -> vsn
|
||||
:error -> raise InvalidVersion, message: string
|
||||
end
|
||||
end
|
||||
@@ -304,8 +313,8 @@ defmodule Version do
|
||||
patch = patch && binary_to_integer(patch)
|
||||
|
||||
case parse_pre(pre) do
|
||||
{ :ok, pre } ->
|
||||
{ :ok, { major, minor, patch, pre } }
|
||||
{:ok, pre} ->
|
||||
{:ok, {major, minor, patch, pre}}
|
||||
:error ->
|
||||
:error
|
||||
end
|
||||
@@ -315,7 +324,7 @@ defmodule Version do
|
||||
end
|
||||
end
|
||||
|
||||
defp parse_pre(nil), do: { :ok, [] }
|
||||
defp parse_pre(nil), do: {:ok, []}
|
||||
defp parse_pre(pre), do: parse_pre(String.split(pre, "."), [])
|
||||
|
||||
defp parse_pre([piece|t], acc) do
|
||||
@@ -330,7 +339,7 @@ defmodule Version do
|
||||
end
|
||||
|
||||
defp parse_pre([], acc) do
|
||||
{ :ok, Enum.reverse(acc) }
|
||||
{:ok, Enum.reverse(acc)}
|
||||
end
|
||||
|
||||
defp valid_requirement?([]), do: false
|
||||
@@ -367,11 +376,11 @@ defmodule Version do
|
||||
|
||||
defp approximate_upper(version) do
|
||||
case version do
|
||||
{ major, _minor, nil, _ } ->
|
||||
{ major + 1, 0, 0, [0] }
|
||||
{major, _minor, nil, _} ->
|
||||
{major + 1, 0, 0, [0]}
|
||||
|
||||
{ major, minor, _patch, _ } ->
|
||||
{ major, minor + 1, 0, [0] }
|
||||
{major, minor, _patch, _} ->
|
||||
{major, minor + 1, 0, [0]}
|
||||
end
|
||||
end
|
||||
|
||||
@@ -379,7 +388,7 @@ defmodule Version do
|
||||
if valid_requirement?(lexed) do
|
||||
first = to_condition(lexed)
|
||||
rest = Enum.drop(lexed, 2)
|
||||
{ :ok, [{{ :'$1', :'$2', :'$3', :'$4' }, [to_condition(first, rest)], [:'$_'] }] }
|
||||
{:ok, [{{:'$1', :'$2', :'$3', :'$4'}, [to_condition(first, rest)], [:'$_']}]}
|
||||
else
|
||||
:error
|
||||
end
|
||||
@@ -389,68 +398,64 @@ defmodule Version do
|
||||
|
||||
defp to_condition([:'==', version | _]) do
|
||||
version = parse_condition(version)
|
||||
{ :'==', :'$_', { :const, version } }
|
||||
{:'==', :'$_', {:const, version}}
|
||||
end
|
||||
|
||||
defp to_condition([:'!=', version | _]) do
|
||||
version = parse_condition(version)
|
||||
{ :'/=', :'$_', { :const, version } }
|
||||
{:'/=', :'$_', {:const, version}}
|
||||
end
|
||||
|
||||
defp to_condition([:'~>', version | _]) do
|
||||
from = parse_condition(version, true)
|
||||
to = approximate_upper(from)
|
||||
|
||||
{ :andalso, to_condition([:'>=', matchable_to_string(from)]),
|
||||
to_condition([:'<', matchable_to_string(to)]) }
|
||||
{:andalso, to_condition([:'>=', matchable_to_string(from)]),
|
||||
to_condition([:'<', matchable_to_string(to)])}
|
||||
end
|
||||
|
||||
defp to_condition([:'>', version | _]) do
|
||||
{ major, minor, patch, pre } = parse_condition(version)
|
||||
{major, minor, patch, pre} = parse_condition(version)
|
||||
|
||||
{ :andalso, { :not, { :is_binary, :'$1' } },
|
||||
{ :orelse, { :'>', {{ :'$1', :'$2', :'$3' }},
|
||||
{ :const, { major, minor, patch } } },
|
||||
{ :andalso, { :'==', {{ :'$1', :'$2', :'$3' }},
|
||||
{ :const, { major, minor, patch } } },
|
||||
{ :orelse, { :andalso, { :'==', { :length, :'$4' }, 0 },
|
||||
{ :'/=', length(pre), 0 } },
|
||||
{ :andalso, { :'/=', length(pre), 0 },
|
||||
{ :orelse, { :'>', { :length, :'$4' }, length(pre) },
|
||||
{ :andalso, { :'==', { :length, :'$4' }, length(pre) },
|
||||
{ :'>', :'$4', { :const, pre } } } } } } } } }
|
||||
{:orelse, {:'>', {{:'$1', :'$2', :'$3'}},
|
||||
{:const, {major, minor, patch}}},
|
||||
{:andalso, {:'==', {{:'$1', :'$2', :'$3'}},
|
||||
{:const, {major, minor, patch}}},
|
||||
{:orelse, {:andalso, {:'==', {:length, :'$4'}, 0},
|
||||
{:'/=', length(pre), 0}},
|
||||
{:andalso, {:'/=', length(pre), 0},
|
||||
{:orelse, {:'>', {:length, :'$4'}, length(pre)},
|
||||
{:andalso, {:'==', {:length, :'$4'}, length(pre)},
|
||||
{:'>', :'$4', {:const, pre}}}}}}}}
|
||||
end
|
||||
|
||||
defp to_condition([:'>=', version | _]) do
|
||||
matchable = parse_condition(version)
|
||||
|
||||
{ :orelse, { :andalso, { :not, { :is_binary, :'$1' } },
|
||||
{ :'==', :'$_', { :const, matchable } } },
|
||||
to_condition([:'>', version]) }
|
||||
{:orelse, {:'==', :'$_', {:const, matchable}},
|
||||
to_condition([:'>', version])}
|
||||
end
|
||||
|
||||
defp to_condition([:'<', version | _]) do
|
||||
{ major, minor, patch, pre } = parse_condition(version)
|
||||
{major, minor, patch, pre} = parse_condition(version)
|
||||
|
||||
{ :andalso, { :not, { :is_binary, :'$1' } },
|
||||
{ :orelse, { :'<', {{ :'$1', :'$2', :'$3' }},
|
||||
{ :const, { major, minor, patch } } },
|
||||
{ :andalso, { :'==', {{ :'$1', :'$2', :'$3' }},
|
||||
{ :const, { major, minor, patch } } },
|
||||
{ :orelse, { :andalso, { :'/=', { :length, :'$4' }, 0 },
|
||||
{ :'==', length(pre), 0 } },
|
||||
{ :andalso, { :'/=', { :length, :'$4' }, 0 },
|
||||
{ :orelse, { :'<', { :length, :'$4' }, length(pre) },
|
||||
{ :andalso, { :'==', { :length, :'$4' }, length(pre) },
|
||||
{ :'<', :'$4', { :const, pre } } } } } } } } }
|
||||
{:orelse, {:'<', {{:'$1', :'$2', :'$3'}},
|
||||
{:const, {major, minor, patch}}},
|
||||
{:andalso, {:'==', {{:'$1', :'$2', :'$3'}},
|
||||
{:const, {major, minor, patch}}},
|
||||
{:orelse, {:andalso, {:'/=', {:length, :'$4'}, 0},
|
||||
{:'==', length(pre), 0}},
|
||||
{:andalso, {:'/=', {:length, :'$4'}, 0},
|
||||
{:orelse, {:'<', {:length, :'$4'}, length(pre)},
|
||||
{:andalso, {:'==', {:length, :'$4'}, length(pre)},
|
||||
{:'<', :'$4', {:const, pre}}}}}}}}
|
||||
end
|
||||
|
||||
defp to_condition([:'<=', version | _]) do
|
||||
matchable = parse_condition(version)
|
||||
|
||||
{ :orelse, { :andalso, { :not, { :is_binary, :'$1' } },
|
||||
{ :'==', :'$_', { :const, matchable } } },
|
||||
to_condition([:'<', version]) }
|
||||
{:orelse, {:'==', :'$_', {:const, matchable}},
|
||||
to_condition([:'<', version])}
|
||||
end
|
||||
|
||||
defp to_condition(current, []) do
|
||||
@@ -458,21 +463,21 @@ defmodule Version do
|
||||
end
|
||||
|
||||
defp to_condition(current, [:'&&', operator, version | rest]) do
|
||||
to_condition({ :andalso, current, to_condition([operator, version]) }, rest)
|
||||
to_condition({:andalso, current, to_condition([operator, version])}, rest)
|
||||
end
|
||||
|
||||
defp to_condition(current, [:'||', operator, version | rest]) do
|
||||
to_condition({ :orelse, current, to_condition([operator, version]) }, rest)
|
||||
to_condition({:orelse, current, to_condition([operator, version])}, rest)
|
||||
end
|
||||
|
||||
defp parse_condition(version, approximate? \\ false) do
|
||||
case parse_version(version, approximate?) do
|
||||
{ :ok, version } -> version
|
||||
{:ok, version} -> version
|
||||
:error -> throw :invalid_matchspec
|
||||
end
|
||||
end
|
||||
|
||||
defp matchable_to_string({ major, minor, patch, pre }) do
|
||||
defp matchable_to_string({major, minor, patch, pre}) do
|
||||
patch = if patch, do: "#{patch}", else: "0"
|
||||
pre = if pre != [], do: "-#{Enum.join(pre, ".")}"
|
||||
"#{major}.#{minor}.#{patch}#{pre}"
|
||||
@@ -480,26 +485,28 @@ defmodule Version do
|
||||
end
|
||||
end
|
||||
|
||||
defimpl String.Chars, for: Version.Schema do
|
||||
def to_string(Version.Schema[source: source]) do
|
||||
source
|
||||
defimpl String.Chars, for: Version do
|
||||
def to_string(version) do
|
||||
pre = unless Enum.empty?(pre = version.pre), do: "-#{pre}"
|
||||
build = if build = version.build, do: "+#{build}"
|
||||
"#{version.major}.#{version.minor}.#{version.patch}#{pre}#{build}"
|
||||
end
|
||||
end
|
||||
|
||||
defimpl Inspect, for: Version.Schema do
|
||||
defimpl Inspect, for: Version do
|
||||
def inspect(self, _opts) do
|
||||
"#Version.Schema<" <> to_string(self) <> ">"
|
||||
"#Version<" <> to_string(self) <> ">"
|
||||
end
|
||||
end
|
||||
|
||||
defimpl String.Chars, for: Version.Requirement do
|
||||
def to_string(Version.Requirement[source: source]) do
|
||||
def to_string(%Version.Requirement{source: source}) do
|
||||
source
|
||||
end
|
||||
end
|
||||
|
||||
defimpl Inspect, for: Version.Requirement do
|
||||
def inspect(Version.Requirement[source: source], _opts) do
|
||||
def inspect(%Version.Requirement{source: source}, _opts) do
|
||||
"#Version.Requirement<" <> source <> ">"
|
||||
end
|
||||
end
|
||||
|
||||
+6
-6
@@ -2,11 +2,11 @@ defmodule Elixir.Mixfile do
|
||||
use Mix.Project
|
||||
|
||||
def project do
|
||||
[ app: :elixir,
|
||||
version: System.version,
|
||||
build_per_environment: false,
|
||||
escript_embed_elixir: false,
|
||||
escript_main_module: :elixir,
|
||||
escript_emu_args: "%%! -noshell\n" ]
|
||||
[app: :elixir,
|
||||
version: System.version,
|
||||
build_per_environment: false,
|
||||
escript_embed_elixir: false,
|
||||
escript_main_module: :elixir,
|
||||
escript_emu_args: "%%! -noshell\n"]
|
||||
end
|
||||
end
|
||||
|
||||
@@ -21,5 +21,3 @@
|
||||
{report, true},
|
||||
{verbose, false}
|
||||
]}.
|
||||
|
||||
{require_otp_vsn,"(17).*"}.
|
||||
|
||||
+89
-51
@@ -9,8 +9,9 @@
|
||||
-include("elixir.hrl").
|
||||
|
||||
%% Top level types
|
||||
-export_type([char_list/0, as_boolean/1]).
|
||||
-export_type([char_list/0, char_data/0, as_boolean/1]).
|
||||
-type char_list() :: string().
|
||||
-type char_data() :: unicode:chardata().
|
||||
-type as_boolean(T) :: T.
|
||||
|
||||
%% OTP Application API
|
||||
@@ -22,7 +23,16 @@ start(_Type, _Args) ->
|
||||
%% 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}])
|
||||
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),
|
||||
elixir_sup:start_link().
|
||||
|
||||
@@ -47,59 +57,59 @@ start_cli() ->
|
||||
%% EVAL HOOKS
|
||||
|
||||
env_for_eval(Opts) ->
|
||||
env_for_eval(#elixir_env{
|
||||
local = nil,
|
||||
requires = elixir_dispatch:default_requires(),
|
||||
functions = elixir_dispatch:default_functions(),
|
||||
macros = elixir_dispatch:default_macros()
|
||||
}, Opts).
|
||||
env_for_eval((elixir_env:new())#{
|
||||
local := nil,
|
||||
requires := elixir_dispatch:default_requires(),
|
||||
functions := elixir_dispatch:default_functions(),
|
||||
macros := elixir_dispatch:default_macros()
|
||||
}, Opts).
|
||||
|
||||
env_for_eval(Env, Opts) ->
|
||||
Line = case lists:keyfind(line, 1, Opts) of
|
||||
{ line, RawLine } when is_integer(RawLine) -> RawLine;
|
||||
false -> Env#elixir_env.line
|
||||
{line, RawLine} when is_integer(RawLine) -> RawLine;
|
||||
false -> ?m(Env, line)
|
||||
end,
|
||||
|
||||
File = case lists:keyfind(file, 1, Opts) of
|
||||
{ file, RawFile } when is_binary(RawFile) -> RawFile;
|
||||
false -> Env#elixir_env.file
|
||||
{file, RawFile} when is_binary(RawFile) -> RawFile;
|
||||
false -> ?m(Env, file)
|
||||
end,
|
||||
|
||||
Local = case lists:keyfind(delegate_locals_to, 1, Opts) of
|
||||
{ delegate_locals_to, LocalOpt } -> LocalOpt;
|
||||
false -> Env#elixir_env.local
|
||||
{delegate_locals_to, LocalOpt} -> LocalOpt;
|
||||
false -> ?m(Env, local)
|
||||
end,
|
||||
|
||||
Aliases = case lists:keyfind(aliases, 1, Opts) of
|
||||
{ aliases, AliasesOpt } -> AliasesOpt;
|
||||
false -> Env#elixir_env.aliases
|
||||
{aliases, AliasesOpt} -> AliasesOpt;
|
||||
false -> ?m(Env, aliases)
|
||||
end,
|
||||
|
||||
Requires = case lists:keyfind(requires, 1, Opts) of
|
||||
{ requires, List } -> ordsets:from_list(List);
|
||||
false -> Env#elixir_env.requires
|
||||
{requires, List} -> ordsets:from_list(List);
|
||||
false -> ?m(Env, requires)
|
||||
end,
|
||||
|
||||
Functions = case lists:keyfind(functions, 1, Opts) of
|
||||
{ functions, FunctionsOpt } -> FunctionsOpt;
|
||||
false -> Env#elixir_env.functions
|
||||
{functions, FunctionsOpt} -> FunctionsOpt;
|
||||
false -> ?m(Env, functions)
|
||||
end,
|
||||
|
||||
Macros = case lists:keyfind(macros, 1, Opts) of
|
||||
{ macros, MacrosOpt } -> MacrosOpt;
|
||||
false -> Env#elixir_env.macros
|
||||
{macros, MacrosOpt} -> MacrosOpt;
|
||||
false -> ?m(Env, macros)
|
||||
end,
|
||||
|
||||
Module = case lists:keyfind(module, 1, Opts) of
|
||||
{ module, ModuleOpt } when is_atom(ModuleOpt) -> ModuleOpt;
|
||||
{module, ModuleOpt} when is_atom(ModuleOpt) -> ModuleOpt;
|
||||
false -> nil
|
||||
end,
|
||||
|
||||
Env#elixir_env{
|
||||
file=File, local=Local, module=Module,
|
||||
macros=Macros, functions=Functions,
|
||||
requires=Requires, aliases=Aliases, line=Line
|
||||
}.
|
||||
Env#{
|
||||
file := File, local := Local, module := Module,
|
||||
macros := Macros, functions := Functions,
|
||||
requires := Requires, aliases := Aliases, line := Line
|
||||
}.
|
||||
|
||||
%% String evaluation
|
||||
|
||||
@@ -108,7 +118,7 @@ eval(String, Binding) ->
|
||||
|
||||
eval(String, Binding, Opts) when is_list(Opts) ->
|
||||
eval(String, Binding, env_for_eval(Opts));
|
||||
eval(String, Binding, #elixir_env{line=Line,file=File} = E) when
|
||||
eval(String, Binding, #{line := Line, file := File} = E) when
|
||||
is_list(String), is_list(Binding), is_integer(Line), is_binary(File) ->
|
||||
Forms = 'string_to_quoted!'(String, Line, File, []),
|
||||
eval_forms(Forms, Binding, E).
|
||||
@@ -117,59 +127,87 @@ eval(String, Binding, #elixir_env{line=Line,file=File} = E) when
|
||||
|
||||
eval_quoted(Tree, Binding, Opts) when is_list(Opts) ->
|
||||
eval_quoted(Tree, Binding, env_for_eval(Opts));
|
||||
eval_quoted(Tree, Binding, #elixir_env{line=Line} = E) ->
|
||||
eval_quoted(Tree, Binding, #{line := Line} = E) ->
|
||||
eval_forms(elixir_quote:linify(Line, Tree), Binding, E).
|
||||
|
||||
%% Handle forms evaluation. The main difference to
|
||||
%% to eval_quoted is that it does not linefy the given
|
||||
%% args.
|
||||
|
||||
eval_forms(Tree, Binding, #elixir_env{} = E) ->
|
||||
eval_forms(Tree, Binding, E, elixir_env:env_to_scope(E));
|
||||
eval_forms(Tree, Binding, Opts) when is_list(Opts) ->
|
||||
eval_forms(Tree, Binding, env_for_eval(Opts)).
|
||||
eval_forms(Tree, Binding, env_for_eval(Opts));
|
||||
eval_forms(Tree, Binding, E) ->
|
||||
eval_forms(Tree, Binding, E, elixir_env:env_to_scope(E)).
|
||||
eval_forms(Tree, Binding, Env, Scope) ->
|
||||
{ ParsedBinding, ParsedScope } = elixir_scope:load_binding(Binding, Scope),
|
||||
ParsedEnv = Env#elixir_env{vars=[K || {K,_} <- ParsedScope#elixir_scope.vars]},
|
||||
{ Erl, NewEnv, NewScope } = quoted_to_erl(Tree, ParsedEnv, ParsedScope),
|
||||
{ParsedBinding, ParsedScope} = elixir_scope:load_binding(Binding, Scope),
|
||||
ParsedEnv = Env#{vars := [K || {K,_} <- ParsedScope#elixir_scope.vars]},
|
||||
{Erl, NewEnv, NewScope} = quoted_to_erl(Tree, ParsedEnv, ParsedScope),
|
||||
|
||||
case Erl of
|
||||
{ atom, _, Atom } ->
|
||||
{ Atom, Binding, NewEnv, NewScope };
|
||||
{atom, _, Atom} ->
|
||||
{Atom, Binding, NewEnv, NewScope};
|
||||
_ ->
|
||||
{ value, Value, NewBinding } = erl_eval:expr(Erl, ParsedBinding),
|
||||
{ Value, elixir_scope:dump_binding(NewBinding, NewScope), NewEnv, NewScope }
|
||||
{value, Value, NewBinding} = erl_eval(Erl, ParsedBinding),
|
||||
{Value, elixir_scope:dump_binding(NewBinding, NewScope), NewEnv, NewScope}
|
||||
end.
|
||||
|
||||
erl_eval(Erl, ParsedBinding) ->
|
||||
% Below must be all one line for locations to be the same when the stacktrace
|
||||
% needs to be extended to the full stacktrace.
|
||||
try erl_eval:expr(Erl, ParsedBinding) catch Class:Exception -> erlang:raise(Class, Exception, get_stacktrace()) end.
|
||||
|
||||
get_stacktrace() ->
|
||||
Stacktrace = erlang:get_stacktrace(),
|
||||
% eval_eval and eval_bits can call :erlang.raise/3 without the full
|
||||
% stacktrace. When this occurs re-add the current stacktrace so that no
|
||||
% stack information is lost.
|
||||
try
|
||||
throw(stack)
|
||||
catch
|
||||
throw:stack ->
|
||||
% Ignore stack item for current function.
|
||||
[_ | CurrentStack] = erlang:get_stacktrace(),
|
||||
get_stacktrace(Stacktrace, CurrentStack)
|
||||
end.
|
||||
|
||||
% The stacktrace did not include the current stack, re-add it.
|
||||
get_stacktrace([], CurrentStack) ->
|
||||
CurrentStack;
|
||||
% The stacktrace includes the current stack.
|
||||
get_stacktrace(CurrentStack, CurrentStack) ->
|
||||
CurrentStack;
|
||||
get_stacktrace([StackItem | Stacktrace], CurrentStack) ->
|
||||
[StackItem | get_stacktrace(Stacktrace, CurrentStack)].
|
||||
|
||||
%% Converts a quoted expression to erlang abstract format
|
||||
|
||||
quoted_to_erl(Quoted, Env) ->
|
||||
quoted_to_erl(Quoted, Env, elixir_env:env_to_scope(Env)).
|
||||
|
||||
quoted_to_erl(Quoted, Env, Scope) ->
|
||||
{ Expanded, NewEnv } = elixir_exp:expand(Quoted, Env),
|
||||
{ Erl, NewScope } = elixir_translator:translate(Expanded, Scope),
|
||||
{ Erl, NewEnv, NewScope }.
|
||||
{Expanded, NewEnv} = elixir_exp:expand(Quoted, Env),
|
||||
{Erl, NewScope} = elixir_translator:translate(Expanded, Scope),
|
||||
{Erl, NewEnv, NewScope}.
|
||||
|
||||
%% Converts a given string (char list) into quote expression
|
||||
|
||||
string_to_quoted(String, StartLine, File, Opts) when is_integer(StartLine), is_binary(File) ->
|
||||
case elixir_tokenizer:tokenize(String, StartLine, [{ file, File }|Opts]) of
|
||||
{ ok, _Line, Tokens } ->
|
||||
case elixir_tokenizer:tokenize(String, StartLine, [{file, File}|Opts]) of
|
||||
{ok, _Line, Tokens} ->
|
||||
try elixir_parser:parse(Tokens) of
|
||||
{ ok, Forms } -> { ok, Forms };
|
||||
{ error, { Line, _, [Error, Token] } } -> { error, { Line, to_binary(Error), to_binary(Token) } }
|
||||
{ok, Forms} -> {ok, Forms};
|
||||
{error, {Line, _, [Error, Token]}} -> {error, {Line, to_binary(Error), to_binary(Token)}}
|
||||
catch
|
||||
{ error, { Line, _, [Error, Token] } } -> { error, { Line, to_binary(Error), to_binary(Token) } }
|
||||
{error, {Line, _, [Error, Token]}} -> {error, {Line, to_binary(Error), to_binary(Token)}}
|
||||
end;
|
||||
{ error, { Line, Error, Token }, _Rest, _SoFar } -> { error, { Line, to_binary(Error), to_binary(Token) } }
|
||||
{error, {Line, Error, Token}, _Rest, _SoFar} -> {error, {Line, to_binary(Error), to_binary(Token)}}
|
||||
end.
|
||||
|
||||
'string_to_quoted!'(String, StartLine, File, Opts) ->
|
||||
case string_to_quoted(String, StartLine, File, Opts) of
|
||||
{ ok, Forms } ->
|
||||
{ok, Forms} ->
|
||||
Forms;
|
||||
{ error, { Line, Error, Token } } ->
|
||||
{error, {Line, Error, Token}} ->
|
||||
elixir_errors:parse_error(Line, File, Error, Token)
|
||||
end.
|
||||
|
||||
|
||||
@@ -11,19 +11,19 @@ inspect(Atom) when is_atom(Atom) ->
|
||||
|
||||
%% Store an alias in the given scope
|
||||
store(_Meta, New, New, _TKV, Aliases, MacroAliases, _Lexical) ->
|
||||
{ Aliases, MacroAliases };
|
||||
{Aliases, MacroAliases};
|
||||
store(Meta, New, Old, TKV, Aliases, MacroAliases, Lexical) ->
|
||||
record_warn(Meta, New, TKV, Lexical),
|
||||
{ store_alias(New, Old, Aliases),
|
||||
store_macro_alias(Meta, New, Old, MacroAliases) }.
|
||||
{store_alias(New, Old, Aliases),
|
||||
store_macro_alias(Meta, New, Old, MacroAliases)}.
|
||||
|
||||
store_alias(New, Old, Aliases) ->
|
||||
lists:keystore(New, 1, Aliases, { New, Old }).
|
||||
lists:keystore(New, 1, Aliases, {New, Old}).
|
||||
store_macro_alias(Meta, New, Old, Aliases) ->
|
||||
case lists:keymember(context, 1, Meta) andalso
|
||||
lists:keyfind(counter, 1, Meta) of
|
||||
{ counter, Counter } when is_integer(Counter) ->
|
||||
lists:keystore(New, 1, Aliases, { New, { Counter, Old } });
|
||||
{counter, Counter} when is_integer(Counter) ->
|
||||
lists:keystore(New, 1, Aliases, {New, {Counter, Old}});
|
||||
_ ->
|
||||
Aliases
|
||||
end.
|
||||
@@ -31,8 +31,8 @@ store_macro_alias(Meta, New, Old, Aliases) ->
|
||||
record_warn(Meta, Ref, Opts, Lexical) ->
|
||||
Warn =
|
||||
case lists:keyfind(warn, 1, Opts) of
|
||||
{ warn, false } -> false;
|
||||
{ warn, true } -> true;
|
||||
{warn, false} -> false;
|
||||
{warn, true} -> true;
|
||||
false -> not lists:keymember(context, 1, Meta)
|
||||
end,
|
||||
elixir_lexical:record_alias(Ref, ?line(Meta), Warn, Lexical).
|
||||
@@ -40,23 +40,23 @@ record_warn(Meta, Ref, Opts, Lexical) ->
|
||||
%% Expand an alias. It returns an atom (meaning that there
|
||||
%% was an expansion) or a list of atoms.
|
||||
|
||||
expand({ '__aliases__', _Meta, ['Elixir'|_] = List }, _Aliases, _MacroAliases, _LexicalTracker) ->
|
||||
expand({'__aliases__', _Meta, ['Elixir'|_] = List}, _Aliases, _MacroAliases, _LexicalTracker) ->
|
||||
concat(List);
|
||||
|
||||
expand({ '__aliases__', Meta, _ } = Alias, Aliases, MacroAliases, LexicalTracker) ->
|
||||
expand({'__aliases__', Meta, _} = Alias, Aliases, MacroAliases, LexicalTracker) ->
|
||||
case lists:keyfind(alias, 1, Meta) of
|
||||
{ alias, false } ->
|
||||
{alias, false} ->
|
||||
expand(Alias, MacroAliases, LexicalTracker);
|
||||
{ alias, Atom } when is_atom(Atom) ->
|
||||
{alias, Atom} when is_atom(Atom) ->
|
||||
Atom;
|
||||
false ->
|
||||
expand(Alias, Aliases, LexicalTracker)
|
||||
end.
|
||||
|
||||
expand({ '__aliases__', Meta, [H|T] }, Aliases, LexicalTracker) when is_atom(H) ->
|
||||
expand({'__aliases__', Meta, [H|T]}, Aliases, LexicalTracker) when is_atom(H) ->
|
||||
Lookup = list_to_atom("Elixir." ++ atom_to_list(H)),
|
||||
Counter = case lists:keyfind(counter, 1, Meta) of
|
||||
{ counter, C } -> C;
|
||||
{counter, C} -> C;
|
||||
_ -> nil
|
||||
end,
|
||||
case lookup(Lookup, Aliases, Counter) of
|
||||
@@ -69,7 +69,7 @@ expand({ '__aliases__', Meta, [H|T] }, Aliases, LexicalTracker) when is_atom(H)
|
||||
end
|
||||
end;
|
||||
|
||||
expand({ '__aliases__', _Meta, List }, _Aliases, _LexicalTracker) ->
|
||||
expand({'__aliases__', _Meta, List}, _Aliases, _LexicalTracker) ->
|
||||
List.
|
||||
|
||||
%% Ensure a module is loaded before its usage.
|
||||
@@ -80,11 +80,11 @@ ensure_loaded(Meta, Ref, E) ->
|
||||
Ref:module_info(compile)
|
||||
catch
|
||||
error:undef ->
|
||||
Kind = case lists:member(Ref, E#elixir_env.context_modules) of
|
||||
Kind = case lists:member(Ref, ?m(E, context_modules)) of
|
||||
true -> scheduled_module;
|
||||
false -> unloaded_module
|
||||
end,
|
||||
elixir_errors:form_error(Meta, E#elixir_env.file, ?MODULE, { Kind, Ref })
|
||||
elixir_errors:form_error(Meta, ?m(E, file), ?MODULE, {Kind, Ref})
|
||||
end.
|
||||
|
||||
%% Receives an atom and returns the last bit as an alias.
|
||||
@@ -129,8 +129,8 @@ to_partial(Arg) when is_binary(Arg) -> Arg.
|
||||
|
||||
lookup(Else, Dict, Counter) ->
|
||||
case lists:keyfind(Else, 1, Dict) of
|
||||
{ Else, { Counter, Value } } -> lookup(Value, Dict, Counter);
|
||||
{ Else, Value } when is_atom(Value) -> lookup(Value, Dict, Counter);
|
||||
{Else, {Counter, Value}} -> lookup(Value, Dict, Counter);
|
||||
{Else, Value} when is_atom(Value) -> lookup(Value, Dict, Counter);
|
||||
_ -> Else
|
||||
end.
|
||||
|
||||
|
||||
@@ -5,32 +5,32 @@
|
||||
%% Expansion
|
||||
|
||||
expand(Meta, Args, E) ->
|
||||
case E#elixir_env.context of
|
||||
case ?m(E, context) of
|
||||
match ->
|
||||
{ EArgs, EA } = expand_bitstr(fun elixir_exp:expand/2, Args, [], E),
|
||||
{ { '<<>>', Meta, EArgs }, EA };
|
||||
{EArgs, EA} = expand_bitstr(fun elixir_exp:expand/2, Args, [], E),
|
||||
{{'<<>>', Meta, EArgs}, EA};
|
||||
_ ->
|
||||
{ EArgs, { EC, EV } } = expand_bitstr(fun elixir_exp:expand_arg/2, Args, [], { E, E }),
|
||||
{ { '<<>>', Meta, EArgs }, elixir_env:mergea(EV, EC) }
|
||||
{EArgs, {EC, EV}} = expand_bitstr(fun elixir_exp:expand_arg/2, Args, [], {E, E}),
|
||||
{{'<<>>', Meta, EArgs}, elixir_env:mergea(EV, EC)}
|
||||
end.
|
||||
|
||||
expand_bitstr(_Fun, [], Acc, E) ->
|
||||
{ lists:reverse(Acc), E };
|
||||
{lists:reverse(Acc), E};
|
||||
expand_bitstr(Fun, [{'::',Meta,[Left,Right]}|T], Acc, E) ->
|
||||
{ ELeft, EL } = Fun(Left, E),
|
||||
{ELeft, EL} = Fun(Left, E),
|
||||
|
||||
%% Variables defined outside the binary can be accounted
|
||||
%% on subparts, however we can't assign new variables.
|
||||
case E of
|
||||
{ ER, _ } -> ok; %% expand_arg, no assigns
|
||||
_ -> ER = E#elixir_env{context=nil} %% expand_each, revert assigns
|
||||
{ER, _} -> ok; %% expand_arg, no assigns
|
||||
_ -> ER = E#{context := nil} %% expand_each, revert assigns
|
||||
end,
|
||||
|
||||
ERight = expand_bit_info(Meta, Right, ER),
|
||||
expand_bitstr(Fun, T, [{'::',Meta,[ELeft,ERight]}|Acc], EL);
|
||||
|
||||
expand_bitstr(Fun, [H|T], Acc, E) ->
|
||||
{ Expr, ES } = Fun(H, E),
|
||||
{Expr, ES} = Fun(H, E),
|
||||
expand_bitstr(Fun, T, [Expr|Acc], ES).
|
||||
|
||||
%% Expand bit info
|
||||
@@ -41,28 +41,28 @@ expand_bit_info(Meta, Info, E) when is_list(Info) ->
|
||||
expand_bit_info(Meta, Info, E) ->
|
||||
expand_bit_info(Meta, [Info], E).
|
||||
|
||||
expand_bit_info(Meta, [{ Expr, ExprMeta, Args }|T], Size, Types, E) when is_atom(Expr) ->
|
||||
expand_bit_info(Meta, [{Expr, ExprMeta, Args}|T], Size, Types, E) when is_atom(Expr) ->
|
||||
ListArgs = if is_atom(Args) -> []; is_list(Args) -> Args end,
|
||||
case expand_bit_type_or_size(Expr, ListArgs) of
|
||||
type ->
|
||||
{ EArgs, EE } = elixir_exp:expand_args(ListArgs, E),
|
||||
expand_bit_info(Meta, T, Size, [{ Expr, [], EArgs }|Types], EE);
|
||||
{EArgs, EE} = elixir_exp:expand_args(ListArgs, E),
|
||||
expand_bit_info(Meta, T, Size, [{Expr, [], EArgs}|Types], EE);
|
||||
size ->
|
||||
case Size of
|
||||
default -> ok;
|
||||
_ -> elixir_errors:compile_error(Meta, E#elixir_env.file, "duplicated size definition in bitstring")
|
||||
_ -> elixir_errors:compile_error(Meta, ?m(E, file), "duplicated size definition in bitstring")
|
||||
end,
|
||||
{ EArgs, EE } = elixir_exp:expand_args(ListArgs, E),
|
||||
expand_bit_info(Meta, T, { Expr, [], EArgs }, Types, EE);
|
||||
{EArgs, EE} = elixir_exp:expand_args(ListArgs, E),
|
||||
expand_bit_info(Meta, T, {Expr, [], EArgs}, Types, EE);
|
||||
none ->
|
||||
handle_unknown_bit_info(Meta, { Expr, ExprMeta, ListArgs }, T, Size, Types, E)
|
||||
handle_unknown_bit_info(Meta, {Expr, ExprMeta, ListArgs}, T, Size, Types, E)
|
||||
end;
|
||||
|
||||
expand_bit_info(Meta, [Int|T], Size, Types, E) when is_integer(Int) ->
|
||||
expand_bit_info(Meta, [{ size, [], [Int] }|T], Size, Types, E);
|
||||
expand_bit_info(Meta, [{size, [], [Int]}|T], Size, Types, E);
|
||||
|
||||
expand_bit_info(Meta, [Expr|_], _Size, _Types, E) ->
|
||||
elixir_errors:compile_error(Meta, E#elixir_env.file,
|
||||
elixir_errors:compile_error(Meta, ?m(E, file),
|
||||
"unknown bitstring specifier ~ts", ['Elixir.Kernel':inspect(Expr)]);
|
||||
|
||||
expand_bit_info(_Meta, [], Size, Types, _) ->
|
||||
@@ -89,10 +89,10 @@ expand_bit_type_or_size(unit, [_]) -> type;
|
||||
expand_bit_type_or_size(size, [_]) -> size;
|
||||
expand_bit_type_or_size(_, _) -> none.
|
||||
|
||||
handle_unknown_bit_info(Meta, { _, ExprMeta, _ } = Expr, T, Size, Types, E) ->
|
||||
case 'Elixir.Macro':expand(Expr, elixir_env:env_to_ex({ ?line(ExprMeta), E })) of
|
||||
handle_unknown_bit_info(Meta, {_, ExprMeta, _} = Expr, T, Size, Types, E) ->
|
||||
case 'Elixir.Macro':expand(Expr, elixir_env:linify({?line(ExprMeta), E})) of
|
||||
Expr ->
|
||||
elixir_errors:compile_error(ExprMeta, E#elixir_env.file,
|
||||
elixir_errors:compile_error(ExprMeta, ?m(E, file),
|
||||
"unknown bitstring specifier ~ts", ['Elixir.Macro':to_string(Expr)]);
|
||||
Other ->
|
||||
List = case is_list(Other) of true -> Other; false -> [Other] end,
|
||||
@@ -101,8 +101,8 @@ handle_unknown_bit_info(Meta, { _, ExprMeta, _ } = Expr, T, Size, Types, E) ->
|
||||
|
||||
%% Translation
|
||||
|
||||
has_size({ bin, _, Elements }) ->
|
||||
not lists:any(fun({ bin_element, _Line, _Expr, Size, Types }) ->
|
||||
has_size({bin, _, Elements}) ->
|
||||
not lists:any(fun({bin_element, _Line, _Expr, Size, Types}) ->
|
||||
(Types /= default) andalso (Size == default) andalso
|
||||
lists:any(fun(X) -> lists:member(X, Types) end,
|
||||
[bits, bytes, bitstring, binary])
|
||||
@@ -117,14 +117,14 @@ translate(Meta, Args, S) ->
|
||||
end.
|
||||
|
||||
build_bitstr(Fun, Exprs, Meta, S) ->
|
||||
{ Final, FinalS } = build_bitstr_each(Fun, Exprs, Meta, S, []),
|
||||
{ { bin, ?line(Meta), lists:reverse(Final) }, FinalS }.
|
||||
{Final, FinalS} = build_bitstr_each(Fun, Exprs, Meta, S, []),
|
||||
{{bin, ?line(Meta), lists:reverse(Final)}, FinalS}.
|
||||
|
||||
build_bitstr_each(_Fun, [], _Meta, S, Acc) ->
|
||||
{ Acc, S };
|
||||
{Acc, S};
|
||||
|
||||
build_bitstr_each(Fun, [{'::',_,[H,V]}|T], Meta, S, Acc) ->
|
||||
{ Size, Types } = extract_bit_info(Meta, V, S#elixir_scope{context=nil}),
|
||||
{Size, Types} = extract_bit_info(Meta, V, S#elixir_scope{context=nil}),
|
||||
build_bitstr_each(Fun, T, Meta, S, Acc, H, Size, Types);
|
||||
|
||||
build_bitstr_each(Fun, [H|T], Meta, S, Acc) ->
|
||||
@@ -136,11 +136,11 @@ build_bitstr_each(Fun, T, Meta, S, Acc, H, default, Types) when is_binary(H) ->
|
||||
true ->
|
||||
%% See explanation in elixir_utils:elixir_to_erl/1 to know
|
||||
%% why we can simply convert the binary to a list.
|
||||
{ bin_element, ?line(Meta), { string, 0, binary_to_list(H) }, default, default };
|
||||
{bin_element, ?line(Meta), {string, 0, binary_to_list(H)}, default, default};
|
||||
false ->
|
||||
case types_require_conversion(Types) of
|
||||
true ->
|
||||
{ bin_element, ?line(Meta), { string, 0, elixir_utils:characters_to_list(H) }, default, Types };
|
||||
{bin_element, ?line(Meta), {string, 0, elixir_utils:characters_to_list(H)}, default, Types};
|
||||
false ->
|
||||
elixir_errors:compile_error(Meta, S#elixir_scope.file, "invalid types for literal string in <<>>. "
|
||||
"Accepted types are: little, big, utf8, utf16, utf32, bits, bytes, binary, bitstring")
|
||||
@@ -157,16 +157,16 @@ build_bitstr_each(_Fun, _T, Meta, S, _Acc, H, _Size, _Types) when is_list(H); is
|
||||
['Elixir.Macro':to_string(H)]);
|
||||
|
||||
build_bitstr_each(Fun, T, Meta, S, Acc, H, Size, Types) ->
|
||||
{ Expr, NS } = Fun(H, S),
|
||||
{Expr, NS} = Fun(H, S),
|
||||
|
||||
case Expr of
|
||||
{ bin, _, Elements } ->
|
||||
{bin, _, Elements} ->
|
||||
case (Size == default) andalso types_allow_splice(Types, Elements) of
|
||||
true -> build_bitstr_each(Fun, T, Meta, NS, lists:reverse(Elements) ++ Acc);
|
||||
false -> build_bitstr_each(Fun, T, Meta, NS, [{ bin_element, ?line(Meta), Expr, Size, Types }|Acc])
|
||||
false -> build_bitstr_each(Fun, T, Meta, NS, [{bin_element, ?line(Meta), Expr, Size, Types}|Acc])
|
||||
end;
|
||||
_ ->
|
||||
build_bitstr_each(Fun, T, Meta, NS, [{ bin_element, ?line(Meta), Expr, Size, Types }|Acc])
|
||||
build_bitstr_each(Fun, T, Meta, NS, [{bin_element, ?line(Meta), Expr, Size, Types}|Acc])
|
||||
end.
|
||||
|
||||
types_require_conversion([End|T]) when End == little; End == big -> types_require_conversion(T);
|
||||
@@ -201,23 +201,23 @@ unit_size([], Guess) -> Guess.
|
||||
|
||||
%% Extra bitstring specifiers
|
||||
|
||||
extract_bit_info(Meta, [{ size, _, [Arg] }|T], S) ->
|
||||
extract_bit_info(Meta, [{size, _, [Arg]}|T], S) ->
|
||||
case elixir_translator:translate(Arg, S) of
|
||||
{ { Kind, _, _ } = Size, _ } when Kind == integer; Kind == var ->
|
||||
{ Size, extract_bit_type(Meta, T, S) };
|
||||
{{Kind, _, _} = Size, _} when Kind == integer; Kind == var ->
|
||||
{Size, extract_bit_type(Meta, T, S)};
|
||||
_ ->
|
||||
elixir_errors:compile_error(Meta, S#elixir_scope.file,
|
||||
"size in bitstring expects an integer or a variable as argument, got: ~ts", ['Elixir.Macro':to_string(Arg)])
|
||||
end;
|
||||
extract_bit_info(Meta, T, S) ->
|
||||
{ default, extract_bit_type(Meta, T, S) }.
|
||||
{default, extract_bit_type(Meta, T, S)}.
|
||||
|
||||
extract_bit_type(Meta, [{ unit, _, [Arg] }|T], S) when is_integer(Arg) ->
|
||||
[{ unit, Arg }|extract_bit_type(Meta, T, S)];
|
||||
extract_bit_type(Meta, [{ unit, _, [Arg] }|_], S) ->
|
||||
extract_bit_type(Meta, [{unit, _, [Arg]}|T], S) when is_integer(Arg) ->
|
||||
[{unit, Arg}|extract_bit_type(Meta, T, S)];
|
||||
extract_bit_type(Meta, [{unit, _, [Arg]}|_], S) ->
|
||||
elixir_errors:compile_error(Meta, S#elixir_scope.file,
|
||||
"unit in bitstring expects an integer as argument, got: ~ts", ['Elixir.Macro':to_string(Arg)]);
|
||||
extract_bit_type(Meta, [{ Other, _, [] }|T], S) ->
|
||||
extract_bit_type(Meta, [{Other, _, []}|T], S) ->
|
||||
[Other|extract_bit_type(Meta, T, S)];
|
||||
extract_bit_type(_Meta, [], _S) ->
|
||||
[].
|
||||
|
||||
@@ -19,14 +19,14 @@
|
||||
'MACRO-defmacrop'(Caller, Call, Expr) -> define(Caller, defmacrop, Call, Expr).
|
||||
|
||||
'MACRO-defmodule'(_Caller, Alias, [{do,Block}]) ->
|
||||
{ Escaped, _ } = elixir_quote:escape(Block, false),
|
||||
{Escaped, _} = elixir_quote:escape(Block, false),
|
||||
Args = [Alias, Escaped, [], env()],
|
||||
{ { '.', [], [elixir_module, compile] }, [], Args }.
|
||||
{{'.', [], [elixir_module, compile]}, [], Args}.
|
||||
|
||||
'__info__'(functions) ->
|
||||
[];
|
||||
'__info__'(macros) ->
|
||||
[{ '@', 1 },
|
||||
[{'@', 1},
|
||||
{def,1},
|
||||
{def,2},
|
||||
{defmacro,1},
|
||||
@@ -36,16 +36,16 @@
|
||||
{defp,2}].
|
||||
|
||||
define({Line,E}, Kind, Call, Expr) ->
|
||||
{ EscapedCall, UC } = elixir_quote:escape(Call, true),
|
||||
{ EscapedExpr, UE } = elixir_quote:escape(Expr, true),
|
||||
{EscapedCall, UC} = elixir_quote:escape(Call, true),
|
||||
{EscapedExpr, UE} = elixir_quote:escape(Expr, true),
|
||||
Args = [Line, Kind, not(UC or UE), EscapedCall, EscapedExpr, elixir_locals:cache_env(E)],
|
||||
{ { '.', [], [elixir_def, store_definition] }, [], Args }.
|
||||
{{'.', [], [elixir_def, store_definition]}, [], Args}.
|
||||
|
||||
unless_loaded(Fun, Args, Callback) ->
|
||||
case code:is_loaded(?kernel) of
|
||||
{ _, _} -> apply(?kernel, Fun, Args);
|
||||
{_, _} -> apply(?kernel, Fun, Args);
|
||||
false -> Callback()
|
||||
end.
|
||||
|
||||
env() ->
|
||||
{ '__ENV__', [], nil }.
|
||||
{'__ENV__', [], nil}.
|
||||
@@ -11,9 +11,9 @@ get_pairs(Key, Clauses) ->
|
||||
get_pairs(Key, Clauses, false).
|
||||
get_pairs(Key, Clauses, AllowNil) ->
|
||||
case lists:keyfind(Key, 1, Clauses) of
|
||||
{ Key, Pairs } when is_list(Pairs) ->
|
||||
[{ Key, Meta, Left, Right } || { '->', Meta, [Left, Right] } <- Pairs];
|
||||
{ Key, nil } when AllowNil ->
|
||||
{Key, Pairs} when is_list(Pairs) ->
|
||||
[{Key, Meta, Left, Right} || {'->', Meta, [Left, Right]} <- Pairs];
|
||||
{Key, nil} when AllowNil ->
|
||||
[];
|
||||
false ->
|
||||
[]
|
||||
@@ -23,17 +23,17 @@ get_pairs(Key, Clauses, AllowNil) ->
|
||||
|
||||
match(Fun, Args, #elixir_scope{context=Context, match_vars=MatchVars,
|
||||
backup_vars=BackupVars, vars=Vars} = S) when Context /= match ->
|
||||
{ Result, NewS } = match(Fun, Args, S#elixir_scope{context=match,
|
||||
{Result, NewS} = match(Fun, Args, S#elixir_scope{context=match,
|
||||
match_vars=ordsets:new(), backup_vars=Vars}),
|
||||
{ Result, NewS#elixir_scope{context=Context,
|
||||
match_vars=MatchVars, backup_vars=BackupVars} };
|
||||
{Result, NewS#elixir_scope{context=Context,
|
||||
match_vars=MatchVars, backup_vars=BackupVars}};
|
||||
match(Fun, Args, S) -> Fun(Args, S).
|
||||
|
||||
%% Translate clauses with args, guards and expressions
|
||||
|
||||
clause(Line, Fun, Args, Expr, Guards, Return, S) when is_integer(Line) ->
|
||||
{ TArgs, SA } = match(Fun, Args, S#elixir_scope{extra_guards=[]}),
|
||||
{ TExpr, SE } = elixir_translator:translate_block(Expr, Return, SA#elixir_scope{extra_guards=nil}),
|
||||
{TArgs, SA} = match(Fun, Args, S#elixir_scope{extra_guards=[]}),
|
||||
{TExpr, SE} = elixir_translator:translate_block(Expr, Return, SA#elixir_scope{extra_guards=nil}),
|
||||
|
||||
SG = SA#elixir_scope{context=guard, extra_guards=nil},
|
||||
Extra = SA#elixir_scope.extra_guards,
|
||||
@@ -43,46 +43,46 @@ clause(Line, Fun, Args, Expr, Guards, Return, S) when is_integer(Line) ->
|
||||
_ -> [translate_guard(Line, Guard, Extra, SG) || Guard <- Guards]
|
||||
end,
|
||||
|
||||
{ { clause, Line, TArgs, TGuards, unblock(TExpr) }, SE }.
|
||||
{{clause, Line, TArgs, TGuards, unblock(TExpr)}, SE}.
|
||||
|
||||
% Translate/Extract guards from the given expression.
|
||||
|
||||
translate_guard(Line, Guard, Extra, S) ->
|
||||
[element(1, elixir_translator:translate(elixir_quote:linify(Line, Guard), S))|Extra].
|
||||
|
||||
extract_guards({ 'when', _, [Left, Right] }) -> { Left, extract_or_guards(Right) };
|
||||
extract_guards(Else) -> { Else, [] }.
|
||||
extract_guards({'when', _, [Left, Right]}) -> {Left, extract_or_guards(Right)};
|
||||
extract_guards(Else) -> {Else, []}.
|
||||
|
||||
extract_or_guards({ 'when', _, [Left, Right] }) -> [Left|extract_or_guards(Right)];
|
||||
extract_or_guards({'when', _, [Left, Right]}) -> [Left|extract_or_guards(Right)];
|
||||
extract_or_guards(Term) -> [Term].
|
||||
|
||||
% Extract guards when multiple left side args are allowed.
|
||||
|
||||
extract_splat_guards([{ 'when', _, [_,_|_] = Args }]) ->
|
||||
{ Left, Right } = elixir_utils:split_last(Args),
|
||||
{ Left, extract_or_guards(Right) };
|
||||
extract_splat_guards([{'when', _, [_,_|_] = Args}]) ->
|
||||
{Left, Right} = elixir_utils:split_last(Args),
|
||||
{Left, extract_or_guards(Right)};
|
||||
extract_splat_guards(Else) ->
|
||||
{ Else, [] }.
|
||||
{Else, []}.
|
||||
|
||||
% Function for translating macros with match style like case and receive.
|
||||
|
||||
clauses(Meta, Clauses, Return, #elixir_scope{export_vars=CV} = S) ->
|
||||
{ TC, TS } = do_clauses(Meta, Clauses, Return, S#elixir_scope{export_vars=[]}),
|
||||
{ TC, TS#elixir_scope{export_vars=elixir_scope:merge_opt_vars(CV, TS#elixir_scope.export_vars)} }.
|
||||
{TC, TS} = do_clauses(Meta, Clauses, Return, S#elixir_scope{export_vars=[]}),
|
||||
{TC, TS#elixir_scope{export_vars=elixir_scope:merge_opt_vars(CV, TS#elixir_scope.export_vars)}}.
|
||||
|
||||
do_clauses(_Meta, [], _Return, S) ->
|
||||
{ [], S };
|
||||
{[], S};
|
||||
|
||||
do_clauses(Meta, DecoupledClauses, Return, S) ->
|
||||
% Transform tree just passing the variables counter forward
|
||||
% and storing variables defined inside each clause.
|
||||
Transformer = fun(X, {SAcc, VAcc}) ->
|
||||
{ TX, TS } = each_clause(X, Return, SAcc),
|
||||
{ TX,
|
||||
{ elixir_scope:mergec(S, TS), [TS#elixir_scope.export_vars|VAcc] } }
|
||||
{TX, TS} = each_clause(X, Return, SAcc),
|
||||
{TX,
|
||||
{elixir_scope:mergec(S, TS), [TS#elixir_scope.export_vars|VAcc]}}
|
||||
end,
|
||||
|
||||
{ TClauses, { TS, ReverseCV } } =
|
||||
{TClauses, {TS, ReverseCV}} =
|
||||
lists:mapfoldl(Transformer, {S, []}, DecoupledClauses),
|
||||
|
||||
% Now get all the variables defined inside each clause
|
||||
@@ -94,7 +94,7 @@ do_clauses(Meta, DecoupledClauses, Return, S) ->
|
||||
% a list of tuples where the first element is the variable name,
|
||||
% the second one is the new pointer to the variable and the third
|
||||
% is the old pointer.
|
||||
{ FinalVars, FS } = lists:mapfoldl(fun({ Key, Val }, Acc) ->
|
||||
{FinalVars, FS} = lists:mapfoldl(fun({Key, Val}, Acc) ->
|
||||
normalize_vars(Key, Val, Acc)
|
||||
end, TS, AllVars),
|
||||
|
||||
@@ -104,9 +104,9 @@ do_clauses(Meta, DecoupledClauses, Return, S) ->
|
||||
|
||||
expand_clauses(Line, [Clause|T], [ClauseVars|V], FinalVars, Acc, S) ->
|
||||
case generate_match_vars(FinalVars, ClauseVars, [], []) of
|
||||
{ [], [] } ->
|
||||
{[], []} ->
|
||||
expand_clauses(Line, T, V, FinalVars, [Clause|Acc], S);
|
||||
{ Left, Right } ->
|
||||
{Left, Right} ->
|
||||
MatchExpr = generate_match(Line, Left, Right),
|
||||
ClauseExprs = element(5, Clause),
|
||||
[Final|RawClauseExprs] = lists:reverse(ClauseExprs),
|
||||
@@ -114,19 +114,19 @@ expand_clauses(Line, [Clause|T], [ClauseVars|V], FinalVars, Acc, S) ->
|
||||
% If the last sentence has a match clause, we need to assign its value
|
||||
% in the variable list. If not, we insert the variable list before the
|
||||
% final clause in order to keep it tail call optimized.
|
||||
{ FinalClauseExprs, FS } = case has_match_tuple(Final) of
|
||||
{FinalClauseExprs, FS} = case has_match_tuple(Final) of
|
||||
true ->
|
||||
case Final of
|
||||
{ match, _, { var, _, UserVarName } = UserVar, _ } when UserVarName /= '_' ->
|
||||
{ [UserVar,MatchExpr,Final|RawClauseExprs], S };
|
||||
{match, _, {var, _, UserVarName} = UserVar, _} when UserVarName /= '_' ->
|
||||
{[UserVar,MatchExpr,Final|RawClauseExprs], S};
|
||||
_ ->
|
||||
{ VarName, _, SS } = elixir_scope:build_var('_', S),
|
||||
StorageVar = { var, Line, VarName },
|
||||
StorageExpr = { match, Line, StorageVar, Final },
|
||||
{ [StorageVar,MatchExpr,StorageExpr|RawClauseExprs], SS }
|
||||
{VarName, _, SS} = elixir_scope:build_var('_', S),
|
||||
StorageVar = {var, Line, VarName},
|
||||
StorageExpr = {match, Line, StorageVar, Final},
|
||||
{[StorageVar,MatchExpr,StorageExpr|RawClauseExprs], SS}
|
||||
end;
|
||||
false ->
|
||||
{ [Final,MatchExpr|RawClauseExprs], S }
|
||||
{[Final,MatchExpr|RawClauseExprs], S}
|
||||
end,
|
||||
|
||||
FinalClause = setelement(5, Clause, lists:reverse(FinalClauseExprs)),
|
||||
@@ -134,22 +134,22 @@ expand_clauses(Line, [Clause|T], [ClauseVars|V], FinalVars, Acc, S) ->
|
||||
end;
|
||||
|
||||
expand_clauses(_Line, [], [], _FinalVars, Acc, S) ->
|
||||
{ lists:reverse(Acc), S }.
|
||||
{lists:reverse(Acc), S}.
|
||||
|
||||
% Handle each key/value clause pair and translate them accordingly.
|
||||
|
||||
each_clause({ do, Meta, [Condition], Expr }, Return, S) ->
|
||||
{ Arg, Guards } = extract_guards(Condition),
|
||||
each_clause({do, 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({ else, Meta, [Condition], Expr }, Return, S) ->
|
||||
{ Arg, Guards } = extract_guards(Condition),
|
||||
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),
|
||||
{ TExpr, SB } = elixir_translator:translate_block(Expr, Return, SC),
|
||||
{ { clause, ?line(Meta), [TCondition], [], unblock(TExpr) }, SB }.
|
||||
each_clause({'after', Meta, [Condition], Expr}, Return, S) ->
|
||||
{TCondition, SC} = elixir_translator:translate(Condition, S),
|
||||
{TExpr, SB} = elixir_translator:translate_block(Expr, Return, SC),
|
||||
{{clause, ?line(Meta), [TCondition], [], unblock(TExpr)}, SB}.
|
||||
|
||||
% Check if the given expression is a match tuple.
|
||||
% This is a small optimization to allow us to change
|
||||
@@ -163,7 +163,7 @@ has_match_tuple({'case', _, _, _}) ->
|
||||
true;
|
||||
has_match_tuple({match, _, _, _}) ->
|
||||
true;
|
||||
has_match_tuple({'fun', _, { clauses, _ }}) ->
|
||||
has_match_tuple({'fun', _, {clauses, _}}) ->
|
||||
false;
|
||||
has_match_tuple(H) when is_tuple(H) ->
|
||||
has_match_tuple(tuple_to_list(H));
|
||||
@@ -179,39 +179,39 @@ 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 };
|
||||
error -> { atom, 0, nil }
|
||||
{ok, {PreValue, _}} -> {var, 0, PreValue};
|
||||
error -> {atom, 0, nil}
|
||||
end,
|
||||
|
||||
{ { Key, Value, Expr }, VS }.
|
||||
{{Key, Value, Expr}, VS}.
|
||||
|
||||
% Generate match vars by checking if they were updated
|
||||
% or not and assigning the previous value.
|
||||
|
||||
generate_match_vars([{ Key, Value, Expr }|T], ClauseVars, Left, Right) ->
|
||||
generate_match_vars([{Key, Value, Expr}|T], ClauseVars, Left, Right) ->
|
||||
case orddict:find(Key, ClauseVars) of
|
||||
{ ok, Value } ->
|
||||
{ok, Value} ->
|
||||
generate_match_vars(T, ClauseVars, Left, Right);
|
||||
{ ok, Clause } ->
|
||||
{ok, Clause} ->
|
||||
generate_match_vars(T, ClauseVars,
|
||||
[{ var, 0, element(1, Value) }|Left],
|
||||
[{ var, 0, element(1, Clause) }|Right]);
|
||||
[{var, 0, element(1, Value)}|Left],
|
||||
[{var, 0, element(1, Clause)}|Right]);
|
||||
error ->
|
||||
generate_match_vars(T, ClauseVars,
|
||||
[{ var, 0, element(1, Value) }|Left], [Expr|Right])
|
||||
[{var, 0, element(1, Value)}|Left], [Expr|Right])
|
||||
end;
|
||||
|
||||
generate_match_vars([], _ClauseVars, Left, Right) ->
|
||||
{ Left, Right }.
|
||||
{Left, Right}.
|
||||
|
||||
generate_match(Line, [Left], [Right]) ->
|
||||
{ match, Line, Left, Right };
|
||||
{match, Line, Left, Right};
|
||||
|
||||
generate_match(Line, LeftVars, RightVars) ->
|
||||
{ match, Line, { tuple, Line, LeftVars }, { tuple, Line, RightVars } }.
|
||||
{match, Line, {tuple, Line, LeftVars}, {tuple, Line, RightVars}}.
|
||||
|
||||
unblock({ 'block', _, Exprs }) -> Exprs;
|
||||
unblock({'block', _, Exprs}) -> Exprs;
|
||||
unblock(Exprs) -> [Exprs].
|
||||
|
||||
@@ -30,121 +30,121 @@ init(ok) ->
|
||||
code:ensure_loaded('Elixir.Macro.Env'),
|
||||
code:ensure_loaded('Elixir.Module.LocalsTracker'),
|
||||
code:ensure_loaded('Elixir.Kernel.LexicalTracker'),
|
||||
{ ok, #elixir_code_server{} }.
|
||||
{ok, #elixir_code_server{}}.
|
||||
|
||||
handle_call({ acquire, Path }, From, Config) ->
|
||||
handle_call({acquire, Path}, From, Config) ->
|
||||
Current = Config#elixir_code_server.loaded,
|
||||
case orddict:find(Path, Current) of
|
||||
{ ok, true } ->
|
||||
{ reply, loaded, Config };
|
||||
{ ok, { Ref, List } } when is_list(List), is_reference(Ref) ->
|
||||
Queued = orddict:store(Path, { Ref, [From|List] }, Current),
|
||||
{ reply, { queued, Ref }, Config#elixir_code_server{loaded=Queued} };
|
||||
{ok, true} ->
|
||||
{reply, loaded, Config};
|
||||
{ok, {Ref, List}} when is_list(List), is_reference(Ref) ->
|
||||
Queued = orddict:store(Path, {Ref, [From|List]}, Current),
|
||||
{reply, {queued, Ref}, Config#elixir_code_server{loaded=Queued}};
|
||||
error ->
|
||||
Queued = orddict:store(Path, { make_ref(), [] }, Current),
|
||||
{ reply, proceed, Config#elixir_code_server{loaded=Queued} }
|
||||
Queued = orddict:store(Path, {make_ref(), []}, Current),
|
||||
{reply, proceed, Config#elixir_code_server{loaded=Queued}}
|
||||
end;
|
||||
|
||||
handle_call(loaded, _From, Config) ->
|
||||
{ reply, [F || { F, true } <- Config#elixir_code_server.loaded], Config };
|
||||
{reply, [F || {F, true} <- Config#elixir_code_server.loaded], Config};
|
||||
|
||||
handle_call(at_exit, _From, Config) ->
|
||||
{ reply, Config#elixir_code_server.at_exit, Config };
|
||||
{reply, Config#elixir_code_server.at_exit, Config};
|
||||
|
||||
handle_call(flush_at_exit, _From, Config) ->
|
||||
{ reply, Config#elixir_code_server.at_exit, Config#elixir_code_server{at_exit=[]} };
|
||||
{reply, Config#elixir_code_server.at_exit, Config#elixir_code_server{at_exit=[]}};
|
||||
|
||||
handle_call(argv, _From, Config) ->
|
||||
{ reply, Config#elixir_code_server.argv, Config };
|
||||
{reply, Config#elixir_code_server.argv, Config};
|
||||
|
||||
handle_call(compiler_options, _From, Config) ->
|
||||
{ reply, Config#elixir_code_server.compiler_options, Config };
|
||||
{reply, Config#elixir_code_server.compiler_options, Config};
|
||||
|
||||
handle_call({ compilation_status, CompilerPid }, _From, Config) ->
|
||||
handle_call({compilation_status, CompilerPid}, _From, Config) ->
|
||||
CompilationStatusList = Config#elixir_code_server.compilation_status,
|
||||
CompilationStatusListNew = orddict:erase(CompilerPid, CompilationStatusList),
|
||||
CompilationStatus = orddict:fetch(CompilerPid, CompilationStatusList),
|
||||
{ reply, CompilationStatus, Config#elixir_code_server{compilation_status=CompilationStatusListNew} };
|
||||
{reply, CompilationStatus, Config#elixir_code_server{compilation_status=CompilationStatusListNew}};
|
||||
|
||||
handle_call(retrieve_module_name, _From, Config) ->
|
||||
case Config#elixir_code_server.pool of
|
||||
{ [H|T], Counter } ->
|
||||
{ reply, module_tuple(H), Config#elixir_code_server{pool={T,Counter}} };
|
||||
{ [], Counter } ->
|
||||
{ reply, module_tuple(Counter), Config#elixir_code_server{pool={[],Counter+1}} }
|
||||
{[H|T], Counter} ->
|
||||
{reply, module_tuple(H), Config#elixir_code_server{pool={T,Counter}}};
|
||||
{[], Counter} ->
|
||||
{reply, module_tuple(Counter), Config#elixir_code_server{pool={[],Counter+1}}}
|
||||
end;
|
||||
|
||||
handle_call(erl_compiler_options, _From, Config) ->
|
||||
case Config#elixir_code_server.erl_compiler_options of
|
||||
nil ->
|
||||
Opts = erl_compiler_options(),
|
||||
{ reply, Opts, Config#elixir_code_server{erl_compiler_options=Opts} };
|
||||
{reply, Opts, Config#elixir_code_server{erl_compiler_options=Opts}};
|
||||
Opts ->
|
||||
{ reply, Opts, Config }
|
||||
{reply, Opts, Config}
|
||||
end;
|
||||
|
||||
handle_call(Request, _From, Config) ->
|
||||
{ stop, { badcall, Request }, Config }.
|
||||
{stop, {badcall, Request}, Config}.
|
||||
|
||||
handle_cast({ at_exit, AtExit }, Config) ->
|
||||
{ noreply, Config#elixir_code_server{at_exit=[AtExit|Config#elixir_code_server.at_exit]} };
|
||||
handle_cast({at_exit, AtExit}, Config) ->
|
||||
{noreply, Config#elixir_code_server{at_exit=[AtExit|Config#elixir_code_server.at_exit]}};
|
||||
|
||||
handle_cast({ argv, Argv }, Config) ->
|
||||
{ noreply, Config#elixir_code_server{argv=Argv} };
|
||||
handle_cast({argv, Argv}, Config) ->
|
||||
{noreply, Config#elixir_code_server{argv=Argv}};
|
||||
|
||||
handle_cast({ compiler_options, Options }, Config) ->
|
||||
handle_cast({compiler_options, Options}, Config) ->
|
||||
Final = orddict:merge(fun(_,_,V) -> V end, Config#elixir_code_server.compiler_options, Options),
|
||||
{ noreply, Config#elixir_code_server{compiler_options=Final} };
|
||||
{noreply, Config#elixir_code_server{compiler_options=Final}};
|
||||
|
||||
handle_cast({ register_warning, CompilerPid }, Config) ->
|
||||
handle_cast({register_warning, CompilerPid}, Config) ->
|
||||
CompilationStatusCurrent = Config#elixir_code_server.compilation_status,
|
||||
CompilationStatusNew = orddict:store(CompilerPid, error, CompilationStatusCurrent),
|
||||
case orddict:find(warnings_as_errors, Config#elixir_code_server.compiler_options) of
|
||||
{ ok, true } -> { noreply, Config#elixir_code_server{compilation_status=CompilationStatusNew} };
|
||||
_ -> { noreply, Config }
|
||||
{ok, true} -> {noreply, Config#elixir_code_server{compilation_status=CompilationStatusNew}};
|
||||
_ -> {noreply, Config}
|
||||
end;
|
||||
|
||||
handle_cast({ reset_warnings, CompilerPid }, Config) ->
|
||||
handle_cast({reset_warnings, CompilerPid}, Config) ->
|
||||
CompilationStatusCurrent = Config#elixir_code_server.compilation_status,
|
||||
CompilationStatusNew = orddict:store(CompilerPid, ok, CompilationStatusCurrent),
|
||||
{ noreply, Config#elixir_code_server{compilation_status=CompilationStatusNew} };
|
||||
{noreply, Config#elixir_code_server{compilation_status=CompilationStatusNew}};
|
||||
|
||||
handle_cast({ loaded, Path }, Config) ->
|
||||
handle_cast({loaded, Path}, Config) ->
|
||||
Current = Config#elixir_code_server.loaded,
|
||||
case orddict:find(Path, Current) of
|
||||
{ ok, true } ->
|
||||
{ noreply, Config };
|
||||
{ ok, { Ref, List } } when is_list(List), is_reference(Ref) ->
|
||||
[Pid ! { elixir_code_server, Ref, loaded } || { Pid, _Tag } <- lists:reverse(List)],
|
||||
{ok, true} ->
|
||||
{noreply, Config};
|
||||
{ok, {Ref, List}} when is_list(List), is_reference(Ref) ->
|
||||
[Pid ! {elixir_code_server, Ref, loaded} || {Pid, _Tag} <- lists:reverse(List)],
|
||||
Done = orddict:store(Path, true, Current),
|
||||
{ noreply, Config#elixir_code_server{loaded=Done} };
|
||||
{noreply, Config#elixir_code_server{loaded=Done}};
|
||||
error ->
|
||||
Done = orddict:store(Path, true, Current),
|
||||
{ noreply, Config#elixir_code_server{loaded=Done} }
|
||||
{noreply, Config#elixir_code_server{loaded=Done}}
|
||||
end;
|
||||
|
||||
handle_cast({ unload_files, Files }, Config) ->
|
||||
handle_cast({unload_files, Files}, Config) ->
|
||||
Current = Config#elixir_code_server.loaded,
|
||||
Unloaded = lists:foldl(fun(File, Acc) -> orddict:erase(File, Acc) end, Current, Files),
|
||||
{ noreply, Config#elixir_code_server{loaded=Unloaded} };
|
||||
{noreply, Config#elixir_code_server{loaded=Unloaded}};
|
||||
|
||||
handle_cast({ return_module_name, H }, #elixir_code_server{pool={T,Counter}} = Config) ->
|
||||
{ noreply, Config#elixir_code_server{pool={[H|T],Counter}} };
|
||||
handle_cast({return_module_name, H}, #elixir_code_server{pool={T,Counter}} = Config) ->
|
||||
{noreply, Config#elixir_code_server{pool={[H|T],Counter}}};
|
||||
|
||||
handle_cast(Request, Config) ->
|
||||
{ stop, { badcast, Request }, Config }.
|
||||
{stop, {badcast, Request}, Config}.
|
||||
|
||||
handle_info(_Request, Config) ->
|
||||
{ noreply, Config }.
|
||||
{noreply, Config}.
|
||||
|
||||
terminate(_Reason, _Config) ->
|
||||
ok.
|
||||
|
||||
code_change(_Old, Config, _Extra) ->
|
||||
{ ok, Config }.
|
||||
{ok, Config}.
|
||||
|
||||
module_tuple(I) ->
|
||||
{ list_to_atom("elixir_compiler_" ++ integer_to_list(I)), I }.
|
||||
{list_to_atom("elixir_compiler_" ++ integer_to_list(I)), I}.
|
||||
|
||||
erl_compiler_options() ->
|
||||
Key = "ERL_COMPILER_OPTIONS",
|
||||
|
||||
@@ -9,7 +9,7 @@ get_opt(Key) ->
|
||||
Dict = elixir_code_server:call(compiler_options),
|
||||
case lists:keyfind(Key, 1, Dict) of
|
||||
false -> false;
|
||||
{ Key, Value } -> Value
|
||||
{Key, Value} -> Value
|
||||
end.
|
||||
|
||||
%% Compilation entry points.
|
||||
@@ -26,7 +26,7 @@ quoted(Forms, File) when is_binary(File) ->
|
||||
elixir_lexical:run(File, fun
|
||||
(Pid) ->
|
||||
Env = elixir:env_for_eval([{line,1},{file,File}]),
|
||||
eval_forms(Forms, [], Env#elixir_env{lexical_tracker=Pid})
|
||||
eval_forms(Forms, [], Env#{lexical_tracker := Pid})
|
||||
end),
|
||||
lists:reverse(get(elixir_compiled))
|
||||
after
|
||||
@@ -35,7 +35,7 @@ quoted(Forms, File) when is_binary(File) ->
|
||||
|
||||
file(Relative) when is_binary(Relative) ->
|
||||
File = filename:absname(Relative),
|
||||
{ ok, Bin } = file:read_file(File),
|
||||
{ok, Bin} = file:read_file(File),
|
||||
string(elixir_utils:characters_to_list(Bin), File).
|
||||
|
||||
file_to_path(File, Path) when is_binary(File), is_binary(Path) ->
|
||||
@@ -46,35 +46,35 @@ file_to_path(File, Path) when is_binary(File), is_binary(Path) ->
|
||||
%% Evaluation
|
||||
|
||||
eval_forms(Forms, Vars, E) ->
|
||||
case (E#elixir_env.module == nil) andalso allows_fast_compilation(Forms) of
|
||||
case (?m(E, module) == nil) andalso allows_fast_compilation(Forms) of
|
||||
true -> eval_compilation(Forms, Vars, E);
|
||||
false -> code_loading_compilation(Forms, Vars, E)
|
||||
end.
|
||||
|
||||
eval_compilation(Forms, Vars, E) ->
|
||||
Binding = [{ Key, Value } || { _Name, _Kind, Key, Value } <- Vars],
|
||||
{ Result, _Binding, EE, _S } = elixir:eval_forms(Forms, Binding, E),
|
||||
{ Result, EE }.
|
||||
Binding = [{Key, Value} || {_Name, _Kind, Key, Value} <- Vars],
|
||||
{Result, _Binding, EE, _S} = elixir:eval_forms(Forms, Binding, E),
|
||||
{Result, EE}.
|
||||
|
||||
code_loading_compilation(Forms, Vars, #elixir_env{line=Line} = E) ->
|
||||
Dict = [{ { Name, Kind }, { Value, 0 } } || { Name, Kind, Value, _ } <- Vars],
|
||||
code_loading_compilation(Forms, Vars, #{line := Line} = E) ->
|
||||
Dict = [{{Name, Kind}, {Value, 0}} || {Name, Kind, Value, _} <- Vars],
|
||||
S = elixir_env:env_to_scope_with_vars(E, Dict),
|
||||
{ Expr, EE, _S } = elixir:quoted_to_erl(Forms, E, S),
|
||||
{Expr, EE, _S} = elixir:quoted_to_erl(Forms, E, S),
|
||||
|
||||
{ Module, I } = retrieve_module_name(),
|
||||
Fun = code_fun(E#elixir_env.module),
|
||||
Form = code_mod(Fun, Expr, Line, E#elixir_env.file, Module, Vars),
|
||||
Args = list_to_tuple([V || { _, _, _, V } <- Vars]),
|
||||
{Module, I} = retrieve_module_name(),
|
||||
Fun = code_fun(?m(E, module)),
|
||||
Form = code_mod(Fun, Expr, Line, ?m(E, file), Module, Vars),
|
||||
Args = list_to_tuple([V || {_, _, _, V} <- Vars]),
|
||||
|
||||
%% Pass { native, false } to speed up bootstrap
|
||||
%% Pass {native, false} to speed up bootstrap
|
||||
%% process when native is set to true
|
||||
module(Form, E#elixir_env.file, [{native,false}], true, fun(_, Binary) ->
|
||||
module(Form, ?m(E, file), [{native,false}], true, fun(_, Binary) ->
|
||||
%% If we have labeled locals, anonymous functions
|
||||
%% were created and therefore we cannot ditch the
|
||||
%% module
|
||||
Purgeable =
|
||||
case beam_lib:chunks(Binary, [labeled_locals]) of
|
||||
{ ok, { _, [{ labeled_locals, []}] } } -> true;
|
||||
{ok, {_, [{labeled_locals, []}]}} -> true;
|
||||
_ -> false
|
||||
end,
|
||||
dispatch_loaded(Module, Fun, Args, Purgeable, I, EE)
|
||||
@@ -89,34 +89,34 @@ dispatch_loaded(Module, Fun, Args, Purgeable, I, E) ->
|
||||
true ->
|
||||
ok
|
||||
end,
|
||||
{ Res, E }.
|
||||
{Res, E}.
|
||||
|
||||
code_fun(nil) -> '__FILE__';
|
||||
code_fun(_) -> '__MODULE__'.
|
||||
|
||||
code_mod(Fun, Expr, Line, File, Module, Vars) when is_binary(File), is_integer(Line) ->
|
||||
Tuple = { tuple, Line, [{ var, Line, K } || { _, _, K, _ } <- Vars] },
|
||||
Tuple = {tuple, Line, [{var, Line, K} || {_, _, K, _} <- Vars]},
|
||||
Relative = elixir_utils:relative_to_cwd(File),
|
||||
|
||||
[
|
||||
{ attribute, Line, file, { elixir_utils:characters_to_list(Relative), 1 } },
|
||||
{ attribute, Line, module, Module },
|
||||
{ attribute, Line, export, [{ Fun, 1 }, { '__RELATIVE__', 0 }] },
|
||||
{ function, Line, Fun, 1, [
|
||||
{ clause, Line, [Tuple], [], [Expr] }
|
||||
] },
|
||||
{ function, Line, '__RELATIVE__', 0, [
|
||||
{ clause, Line, [], [], [elixir_utils:elixir_to_erl(Relative)] }
|
||||
] }
|
||||
{attribute, Line, file, {elixir_utils:characters_to_list(Relative), 1}},
|
||||
{attribute, Line, module, Module},
|
||||
{attribute, Line, export, [{Fun, 1}, {'__RELATIVE__', 0}]},
|
||||
{function, Line, Fun, 1, [
|
||||
{clause, Line, [Tuple], [], [Expr]}
|
||||
]},
|
||||
{function, Line, '__RELATIVE__', 0, [
|
||||
{clause, Line, [], [], [elixir_utils:elixir_to_erl(Relative)]}
|
||||
]}
|
||||
].
|
||||
|
||||
retrieve_module_name() ->
|
||||
elixir_code_server:call(retrieve_module_name).
|
||||
|
||||
return_module_name(I) ->
|
||||
elixir_code_server:cast({ return_module_name, I }).
|
||||
elixir_code_server:cast({return_module_name, I}).
|
||||
|
||||
allows_fast_compilation({ '__block__', _, Exprs }) ->
|
||||
allows_fast_compilation({'__block__', _, Exprs}) ->
|
||||
lists:all(fun allows_fast_compilation/1, Exprs);
|
||||
allows_fast_compilation({defmodule,_,_}) -> true;
|
||||
allows_fast_compilation(_) -> false.
|
||||
@@ -140,7 +140,7 @@ module(Forms, File, RawOptions, Bootstrap, Callback) when
|
||||
Options = RawOptions ++ elixir_code_server:call(erl_compiler_options),
|
||||
Listname = elixir_utils:characters_to_list(File),
|
||||
|
||||
case compile:noenv_forms(Forms, [no_auto_import,return,{source,Listname}|Options]) of
|
||||
case compile:noenv_forms([no_auto_import()|Forms], [return,{source,Listname}|Options]) of
|
||||
{ok, ModuleName, Binary, Warnings} ->
|
||||
format_warnings(Bootstrap, Warnings),
|
||||
code:load_binary(ModuleName, Listname, Binary),
|
||||
@@ -150,11 +150,14 @@ module(Forms, File, RawOptions, Bootstrap, Callback) when
|
||||
format_errors(Errors)
|
||||
end.
|
||||
|
||||
no_auto_import() ->
|
||||
{attribute, 0, compile, no_auto_import}.
|
||||
|
||||
%% CORE HANDLING
|
||||
|
||||
core() ->
|
||||
application:start(elixir),
|
||||
elixir_code_server:cast({ compiler_options, [{docs,false},{internal,true}] }),
|
||||
elixir_code_server:cast({compiler_options, [{docs,false},{internal,true}]}),
|
||||
[core_file(File) || File <- core_main()].
|
||||
|
||||
core_file(File) ->
|
||||
@@ -171,6 +174,7 @@ core_file(File) ->
|
||||
core_main() ->
|
||||
[
|
||||
<<"lib/elixir/lib/kernel.ex">>,
|
||||
<<"lib/elixir/lib/macro/env.ex">>,
|
||||
<<"lib/elixir/lib/keyword.ex">>,
|
||||
<<"lib/elixir/lib/module.ex">>,
|
||||
<<"lib/elixir/lib/list.ex">>,
|
||||
@@ -178,7 +182,6 @@ core_main() ->
|
||||
<<"lib/elixir/lib/record/backend.ex">>,
|
||||
<<"lib/elixir/lib/record/deprecated.ex">>,
|
||||
<<"lib/elixir/lib/macro.ex">>,
|
||||
<<"lib/elixir/lib/macro/env.ex">>,
|
||||
<<"lib/elixir/lib/exception.ex">>,
|
||||
<<"lib/elixir/lib/code.ex">>,
|
||||
<<"lib/elixir/lib/module/locals_tracker.ex">>,
|
||||
@@ -210,7 +213,7 @@ binary_to_path({ModuleName, Binary}, CompilePath) ->
|
||||
%% ERROR HANDLING
|
||||
|
||||
format_errors([]) ->
|
||||
exit({ nocompile, "compilation failed but no error was raised" });
|
||||
exit({nocompile, "compilation failed but no error was raised"});
|
||||
|
||||
format_errors(Errors) ->
|
||||
lists:foreach(fun ({File, Each}) ->
|
||||
|
||||
@@ -13,24 +13,24 @@ start_link() ->
|
||||
gen_server:start_link({local, ?MODULE}, ?MODULE, 0, []).
|
||||
|
||||
init(Counter) ->
|
||||
{ ok, Counter }.
|
||||
{ok, Counter}.
|
||||
|
||||
handle_call(next, _From, Counter) ->
|
||||
{ reply, Counter, bump(Counter) };
|
||||
{reply, Counter, bump(Counter)};
|
||||
handle_call(Request, _From, Counter) ->
|
||||
{ stop, { badcall, Request }, Counter }.
|
||||
{stop, {badcall, Request}, Counter}.
|
||||
|
||||
handle_cast(Request, Counter) ->
|
||||
{ stop, { badcast, Request }, Counter }.
|
||||
{stop, {badcast, Request}, Counter}.
|
||||
|
||||
handle_info(_Request, Counter) ->
|
||||
{ noreply, Counter }.
|
||||
{noreply, Counter}.
|
||||
|
||||
terminate(_Reason, _Counter) ->
|
||||
ok.
|
||||
|
||||
code_change(_Old, Counter, _Extra) ->
|
||||
{ ok, Counter }.
|
||||
{ok, Counter}.
|
||||
|
||||
bump(Counter) when Counter < ?limit ->
|
||||
Counter + 1;
|
||||
|
||||
@@ -18,8 +18,8 @@ clauses_table(Module) ->
|
||||
ets:lookup_element(Module, ?clauses_attr, 2).
|
||||
|
||||
setup(Module) ->
|
||||
ets:insert(Module, { ?attr, ets:new(Module, [set, public]) }),
|
||||
ets:insert(Module, { ?clauses_attr, ets:new(Module, [bag, public]) }),
|
||||
ets:insert(Module, {?attr, ets:new(Module, [set, public])}),
|
||||
ets:insert(Module, {?clauses_attr, ets:new(Module, [bag, public])}),
|
||||
reset_last(Module),
|
||||
ok.
|
||||
|
||||
@@ -29,14 +29,14 @@ cleanup(Module) ->
|
||||
|
||||
%% Reset the last item. Useful when evaling code.
|
||||
reset_last(Module) ->
|
||||
ets:insert(table(Module), { last, [] }).
|
||||
ets:insert(table(Module), {last, []}).
|
||||
|
||||
%% Looks up a definition from the database.
|
||||
lookup_definition(Module, Tuple) ->
|
||||
case ets:lookup(table(Module), Tuple) of
|
||||
[Result] ->
|
||||
CTable = clauses_table(Module),
|
||||
{ Result, [Clause || { _, Clause } <- ets:lookup(CTable, Tuple)] };
|
||||
{Result, [Clause || {_, Clause} <- ets:lookup(CTable, Tuple)]};
|
||||
_ ->
|
||||
false
|
||||
end.
|
||||
@@ -49,26 +49,26 @@ delete_definition(Module, Tuple) ->
|
||||
% Each function is then added to the function table.
|
||||
|
||||
store_definition(Line, Kind, CheckClauses, Call, Body, Pos) ->
|
||||
E = (elixir_locals:get_cached_env(Pos))#elixir_env{line=Line},
|
||||
{ NameAndArgs, Guards } = elixir_clauses:extract_guards(Call),
|
||||
E = (elixir_locals:get_cached_env(Pos))#{line := Line},
|
||||
{NameAndArgs, Guards} = elixir_clauses:extract_guards(Call),
|
||||
|
||||
{ Name, Args } = case NameAndArgs of
|
||||
{ N, _, A } when is_atom(N), is_atom(A) -> { N, [] };
|
||||
{ N, _, A } when is_atom(N), is_list(A) -> { N, A };
|
||||
_ -> elixir_errors:form_error(Line, E#elixir_env.file, ?MODULE, { invalid_def, Kind, NameAndArgs })
|
||||
{Name, Args} = case NameAndArgs of
|
||||
{N, _, A} when is_atom(N), is_atom(A) -> {N, []};
|
||||
{N, _, A} when is_atom(N), is_list(A) -> {N, A};
|
||||
_ -> elixir_errors:form_error(Line, ?m(E, file), ?MODULE, {invalid_def, Kind, NameAndArgs})
|
||||
end,
|
||||
|
||||
%% Now that we have verified the call format,
|
||||
%% extract meta information like file and context.
|
||||
{ _, Meta, _ } = Call,
|
||||
{_, Meta, _} = Call,
|
||||
DoCheckClauses = (not lists:keymember(context, 1, Meta)) andalso (CheckClauses),
|
||||
|
||||
%% Check if there is a file information in the definition.
|
||||
%% If so, we assume this come from another source and we need
|
||||
%% to linify taking into account keep line numbers.
|
||||
{ File, Key } = case lists:keyfind(file, 1, Meta) of
|
||||
{ file, Bin } when is_binary(Bin) -> { Bin, keep };
|
||||
_ -> { nil, line }
|
||||
{File, Key} = case lists:keyfind(file, 1, Meta) of
|
||||
{file, Bin} when is_binary(Bin) -> {Bin, keep};
|
||||
_ -> {nil, line}
|
||||
end,
|
||||
|
||||
LinifyArgs = elixir_quote:linify(Line, Key, Args),
|
||||
@@ -79,19 +79,19 @@ store_definition(Line, Kind, CheckClauses, Call, Body, Pos) ->
|
||||
store_definition(Line, Kind, DoCheckClauses, Name,
|
||||
LinifyArgs, LinifyGuards, LinifyBody, File, E).
|
||||
|
||||
store_definition(Line, Kind, CheckClauses, Name, Args, Guards, Body, MetaFile, #elixir_env{module=Module} = ER) ->
|
||||
store_definition(Line, Kind, CheckClauses, Name, Args, Guards, Body, MetaFile, #{module := Module} = ER) ->
|
||||
Arity = length(Args),
|
||||
Tuple = { Name, Arity },
|
||||
E = ER#elixir_env{function=Tuple},
|
||||
Tuple = {Name, Arity},
|
||||
E = ER#{function := Tuple},
|
||||
elixir_locals:record_definition(Tuple, Kind, Module),
|
||||
|
||||
Location = retrieve_location(Line, MetaFile, Module),
|
||||
{ Function, Defaults, Super } = translate_definition(Kind, Line, Module, Name, Args, Guards, Body, E),
|
||||
{Function, Defaults, Super} = translate_definition(Kind, Line, Module, Name, Args, Guards, Body, E),
|
||||
|
||||
DefaultsLength = length(Defaults),
|
||||
elixir_locals:record_defaults(Tuple, Kind, Module, DefaultsLength),
|
||||
|
||||
File = E#elixir_env.file,
|
||||
File = ?m(E, file),
|
||||
Table = table(Module),
|
||||
CTable = clauses_table(Module),
|
||||
|
||||
@@ -104,7 +104,7 @@ store_definition(Line, Kind, CheckClauses, Name, Args, Guards, Body, MetaFile, #
|
||||
default_function_for(Kind, Name, Default)) || Default <- Defaults],
|
||||
|
||||
make_struct_available(Kind, Module, Name, Args),
|
||||
{ Name, Arity }.
|
||||
{Name, Arity}.
|
||||
|
||||
%% @on_definition
|
||||
|
||||
@@ -113,15 +113,15 @@ run_on_definition_callbacks(Kind, Line, Module, Name, Args, Guards, Expr, E) ->
|
||||
true ->
|
||||
ok;
|
||||
_ ->
|
||||
Env = elixir_env:env_to_ex({ Line, E }),
|
||||
Env = elixir_env:linify({Line, E}),
|
||||
Callbacks = 'Elixir.Module':get_attribute(Module, on_definition),
|
||||
[Mod:Fun(Env, Kind, Name, Args, Guards, Expr) || { Mod, Fun } <- Callbacks]
|
||||
[Mod:Fun(Env, Kind, Name, Args, Guards, Expr) || {Mod, Fun} <- Callbacks]
|
||||
end.
|
||||
|
||||
make_struct_available(def, Module, '__struct__', []) ->
|
||||
case erlang:get(elixir_compiler_pid) of
|
||||
undefined -> ok;
|
||||
Pid -> Pid ! { struct_available, Module }
|
||||
Pid -> Pid ! {struct_available, Module}
|
||||
end;
|
||||
make_struct_available(_, _, _, _) ->
|
||||
ok.
|
||||
@@ -134,18 +134,18 @@ retrieve_location(Line, File, Module) ->
|
||||
X when X == nil; X == false ->
|
||||
if
|
||||
is_binary(File) ->
|
||||
{ elixir_utils:characters_to_list(elixir_utils:relative_to_cwd(File)), Line };
|
||||
{elixir_utils:characters_to_list(elixir_utils:relative_to_cwd(File)), Line};
|
||||
true ->
|
||||
nil
|
||||
end;
|
||||
X ->
|
||||
X when is_binary(X) ->
|
||||
'Elixir.Module':delete_attribute(Module, file),
|
||||
{ X, 0 }
|
||||
{elixir_utils:characters_to_list(X), 0}
|
||||
end.
|
||||
|
||||
%% Compile super
|
||||
|
||||
compile_super(Module, true, #elixir_env{function=Function}) ->
|
||||
compile_super(Module, true, #{function := Function}) ->
|
||||
elixir_def_overridable:store(Module, Function, true);
|
||||
compile_super(_Module, _, _E) -> ok.
|
||||
|
||||
@@ -155,49 +155,49 @@ compile_super(_Module, _, _E) -> ok.
|
||||
translate_definition(Kind, Line, Module, Name, Args, Guards, Body, E) when is_integer(Line) ->
|
||||
Arity = length(Args),
|
||||
|
||||
{ EArgs, EGuards, EBody, _ } = elixir_exp_clauses:def(fun elixir_def_defaults:expand/2,
|
||||
{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];
|
||||
true -> [{'_@CALLER', [{line,Line}], nil}|EArgs];
|
||||
false -> EArgs
|
||||
end,
|
||||
|
||||
S = elixir_env:env_to_scope(E),
|
||||
{ Unpacked, Defaults } = elixir_def_defaults:unpack(Kind, Name, EAllArgs, S),
|
||||
{ Clauses, Super } = translate_clause(Body, Line, Kind, Unpacked, EGuards, EBody, S),
|
||||
{Unpacked, Defaults} = elixir_def_defaults:unpack(Kind, Name, EAllArgs, S),
|
||||
{Clauses, Super} = translate_clause(Body, Line, Kind, Unpacked, EGuards, EBody, S),
|
||||
|
||||
run_on_definition_callbacks(Kind, Line, Module, Name, EArgs, EGuards, EBody, E),
|
||||
Function = { function, Line, Name, Arity, Clauses },
|
||||
{ Function, Defaults, Super }.
|
||||
Function = {function, Line, Name, Arity, Clauses},
|
||||
{Function, Defaults, Super}.
|
||||
|
||||
translate_clause(nil, _Line, _Kind, _Args, [], _Body, _S) ->
|
||||
{ [], false };
|
||||
{[], false};
|
||||
translate_clause(nil, Line, Kind, _Args, _Guards, _Body, #elixir_scope{file=File}) ->
|
||||
elixir_errors:form_error(Line, File, ?MODULE, { missing_do, Kind });
|
||||
elixir_errors:form_error(Line, File, ?MODULE, {missing_do, Kind});
|
||||
translate_clause(_, Line, Kind, Args, Guards, Body, S) ->
|
||||
{ TClause, TS } = elixir_clauses:clause(Line,
|
||||
{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__' },
|
||||
?wrap_call(Line, elixir_env, env_to_ex, [{ var, Line, '_@CALLER' }])
|
||||
},
|
||||
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
|
||||
end,
|
||||
|
||||
{ [FClause], TS#elixir_scope.super }.
|
||||
{[FClause], TS#elixir_scope.super}.
|
||||
|
||||
expr_from_body(_Line, nil) -> nil;
|
||||
expr_from_body(_Line, [{ do, Expr }]) -> Expr;
|
||||
expr_from_body(Line, Else) -> { 'try', [{line,Line}], [Else] }.
|
||||
expr_from_body(_Line, [{do, Expr}]) -> Expr;
|
||||
expr_from_body(Line, Else) -> {'try', [{line,Line}], [Else]}.
|
||||
|
||||
is_macro(defmacro) -> true;
|
||||
is_macro(defmacrop) -> true;
|
||||
@@ -214,18 +214,18 @@ unwrap_definitions(Module) ->
|
||||
|
||||
unwrap_definition([Fun|T], CTable, All, Exports, Private, Def, Defmacro, Functions, Tail) ->
|
||||
Tuple = element(1, Fun),
|
||||
Clauses = [Clause || { _, Clause } <- ets:lookup(CTable, Tuple)],
|
||||
Clauses = [Clause || {_, Clause} <- ets:lookup(CTable, Tuple)],
|
||||
|
||||
{ NewFun, NewExports, NewPrivate, NewDef, NewDefmacro } =
|
||||
{NewFun, NewExports, NewPrivate, NewDef, NewDefmacro} =
|
||||
case Clauses of
|
||||
[] -> { false, Exports, Private, Def, Defmacro };
|
||||
[] -> {false, Exports, Private, Def, Defmacro};
|
||||
_ -> unwrap_definition(element(2, Fun), Tuple, Fun, Exports, Private, Def, Defmacro)
|
||||
end,
|
||||
|
||||
{ NewFunctions, NewTail } = case NewFun of
|
||||
{NewFunctions, NewTail} = case NewFun of
|
||||
false ->
|
||||
NewAll = All,
|
||||
{ Functions, Tail };
|
||||
{Functions, Tail};
|
||||
_ ->
|
||||
NewAll = [Tuple|All],
|
||||
function_for_stored_definition(NewFun, Clauses, Functions, Tail)
|
||||
@@ -234,49 +234,49 @@ unwrap_definition([Fun|T], CTable, All, Exports, Private, Def, Defmacro, Functio
|
||||
unwrap_definition(T, CTable, NewAll, NewExports, NewPrivate,
|
||||
NewDef, NewDefmacro, NewFunctions, NewTail);
|
||||
unwrap_definition([], _CTable, All, Exports, Private, Def, Defmacro, Functions, Tail) ->
|
||||
{ All, Exports, Private, ordsets:from_list(Def),
|
||||
ordsets:from_list(Defmacro), lists:reverse(Tail ++ Functions) }.
|
||||
{All, Exports, Private, ordsets:from_list(Def),
|
||||
ordsets:from_list(Defmacro), lists:reverse(Tail ++ Functions)}.
|
||||
|
||||
unwrap_definition(def, Tuple, Fun, Exports, Private, Def, Defmacro) ->
|
||||
{ Fun, [Tuple|Exports], Private, [Tuple|Def], Defmacro };
|
||||
unwrap_definition(defmacro, { Name, Arity } = Tuple, Fun, Exports, Private, Def, Defmacro) ->
|
||||
Macro = { ?elixir_macro(Name), Arity + 1 },
|
||||
{ setelement(1, Fun, Macro), [Macro|Exports], Private, Def, [Tuple|Defmacro] };
|
||||
{Fun, [Tuple|Exports], Private, [Tuple|Def], Defmacro};
|
||||
unwrap_definition(defmacro, {Name, Arity} = Tuple, Fun, Exports, Private, Def, Defmacro) ->
|
||||
Macro = {elixir_utils:macro_name(Name), Arity + 1},
|
||||
{setelement(1, Fun, Macro), [Macro|Exports], Private, Def, [Tuple|Defmacro]};
|
||||
unwrap_definition(defp, Tuple, Fun, Exports, Private, Def, Defmacro) ->
|
||||
%% { Name, Arity }, Kind, Line, Check, Defaults
|
||||
Info = { Tuple, defp, element(3, Fun), element(5, Fun), element(7, Fun) },
|
||||
{ Fun, Exports, [Info|Private], Def, Defmacro };
|
||||
%% {Name, Arity}, Kind, Line, Check, Defaults
|
||||
Info = {Tuple, defp, element(3, Fun), element(5, Fun), element(7, Fun)},
|
||||
{Fun, Exports, [Info|Private], Def, Defmacro};
|
||||
unwrap_definition(defmacrop, Tuple, Fun, Exports, Private, Def, Defmacro) ->
|
||||
%% { Name, Arity }, Kind, Line, Check, Defaults
|
||||
Info = { Tuple, defmacrop, element(3, Fun), element(5, Fun), element(7, Fun) },
|
||||
{ false, Exports, [Info|Private], Def, Defmacro }.
|
||||
%% {Name, Arity}, Kind, Line, Check, Defaults
|
||||
Info = {Tuple, defmacrop, element(3, Fun), element(5, Fun), element(7, Fun)},
|
||||
{false, Exports, [Info|Private], Def, Defmacro}.
|
||||
|
||||
%% Helpers
|
||||
|
||||
function_for_stored_definition({{Name,Arity}, _, Line, _, _, nil, _}, Clauses, Functions, Tail) ->
|
||||
{ [{ function, Line, Name, Arity, Clauses }|Functions], Tail };
|
||||
{[{function, Line, Name, Arity, Clauses}|Functions], Tail};
|
||||
|
||||
function_for_stored_definition({{Name,Arity}, _, Line, _, _, Location, _}, Clauses, Functions, Tail) ->
|
||||
{ Functions, [
|
||||
{ function, Line, Name, Arity, Clauses },
|
||||
{ attribute, Line, file, Location } | Tail
|
||||
] }.
|
||||
{Functions, [
|
||||
{function, Line, Name, Arity, Clauses},
|
||||
{attribute, Line, file, Location} | Tail
|
||||
]}.
|
||||
|
||||
default_function_for(Kind, Name, { clause, Line, Args, _Guards, _Exprs } = Clause)
|
||||
default_function_for(Kind, Name, {clause, Line, Args, _Guards, _Exprs} = Clause)
|
||||
when Kind == defmacro; Kind == defmacrop ->
|
||||
{ function, Line, Name, length(Args) - 1, [Clause] };
|
||||
{function, Line, Name, length(Args) - 1, [Clause]};
|
||||
|
||||
default_function_for(_, Name, { clause, Line, Args, _Guards, _Exprs } = Clause) ->
|
||||
{ function, Line, Name, length(Args), [Clause] }.
|
||||
default_function_for(_, Name, {clause, Line, Args, _Guards, _Exprs} = Clause) ->
|
||||
{function, Line, Name, length(Args), [Clause]}.
|
||||
|
||||
%% Store each definition in the table.
|
||||
%% This function also checks and emit warnings in case
|
||||
%% the kind, of the visibility of the function changes.
|
||||
|
||||
store_each(Check, Kind, File, Location, Table, CTable, Defaults, {function, Line, Name, Arity, Clauses}) ->
|
||||
Tuple = { Name, Arity },
|
||||
Tuple = {Name, Arity},
|
||||
case ets:lookup(Table, Tuple) of
|
||||
[{ Tuple, StoredKind, StoredLine, StoredFile, StoredCheck, StoredLocation, StoredDefaults }] ->
|
||||
[{Tuple, StoredKind, StoredLine, StoredFile, StoredCheck, StoredLocation, StoredDefaults}] ->
|
||||
FinalLine = StoredLine,
|
||||
FinalLocation = StoredLocation,
|
||||
FinalDefaults = max(Defaults, StoredDefaults),
|
||||
@@ -289,16 +289,16 @@ store_each(Check, Kind, File, Location, Table, CTable, Defaults, {function, Line
|
||||
FinalLocation = Location,
|
||||
FinalDefaults = Defaults
|
||||
end,
|
||||
Check andalso ets:insert(Table, { last, { Name, Arity } }),
|
||||
ets:insert(CTable, [{ Tuple, Clause } || Clause <- Clauses ]),
|
||||
ets:insert(Table, { Tuple, Kind, FinalLine, File, Check, FinalLocation, FinalDefaults }).
|
||||
Check andalso ets:insert(Table, {last, {Name, Arity}}),
|
||||
ets:insert(CTable, [{Tuple, Clause} || Clause <- Clauses ]),
|
||||
ets:insert(Table, {Tuple, Kind, FinalLine, File, Check, FinalLocation, FinalDefaults}).
|
||||
|
||||
%% Validations
|
||||
|
||||
check_valid_kind(_Line, _File, _Name, _Arity, Kind, Kind) -> [];
|
||||
check_valid_kind(Line, File, Name, Arity, Kind, StoredKind) ->
|
||||
elixir_errors:form_error(Line, File, ?MODULE,
|
||||
{ changed_kind, { Name, Arity, StoredKind, Kind } }).
|
||||
{changed_kind, {Name, Arity, StoredKind, Kind}}).
|
||||
|
||||
check_valid_clause(Line, File, Name, Arity, Kind, Table, StoredLine, StoredFile) ->
|
||||
case ets:lookup_element(Table, last, 2) of
|
||||
@@ -306,29 +306,29 @@ check_valid_clause(Line, File, Name, Arity, Kind, Table, StoredLine, StoredFile)
|
||||
[] -> [];
|
||||
_ ->
|
||||
Relative = elixir_utils:relative_to_cwd(StoredFile),
|
||||
elixir_errors:handle_file_warning(File, { Line, ?MODULE,
|
||||
{ ungrouped_clause, { Kind, Name, Arity, StoredLine, Relative } } })
|
||||
elixir_errors:handle_file_warning(File, {Line, ?MODULE,
|
||||
{ungrouped_clause, {Kind, Name, Arity, StoredLine, Relative}}})
|
||||
end.
|
||||
|
||||
check_valid_defaults(_Line, _File, _Name, _Arity, _Kind, 0, _) -> [];
|
||||
check_valid_defaults(Line, File, Name, Arity, Kind, _, 0) ->
|
||||
elixir_errors:handle_file_warning(File, { Line, ?MODULE, { out_of_order_defaults, { Kind, Name, Arity } } });
|
||||
elixir_errors:handle_file_warning(File, {Line, ?MODULE, {out_of_order_defaults, {Kind, Name, Arity}}});
|
||||
check_valid_defaults(Line, File, Name, Arity, Kind, _, _) ->
|
||||
elixir_errors:form_error(Line, File, ?MODULE, { clauses_with_defaults, { Kind, Name, Arity } }).
|
||||
elixir_errors:form_error(Line, File, ?MODULE, {clauses_with_defaults, {Kind, Name, Arity}}).
|
||||
|
||||
check_previous_defaults(Table, Line, Name, Arity, Kind, Defaults, E) ->
|
||||
Matches = ets:match(Table, { { Name, '$2' }, '$1', '_', '_', '_', '_', '$3' }),
|
||||
Matches = ets:match(Table, {{Name, '$2'}, '$1', '_', '_', '_', '_', '$3'}),
|
||||
[ begin
|
||||
elixir_errors:form_error(Line, E#elixir_env.file, ?MODULE,
|
||||
{ defs_with_defaults, Name, { Kind, Arity }, { K, A } })
|
||||
elixir_errors:form_error(Line, ?m(E, file), ?MODULE,
|
||||
{defs_with_defaults, Name, {Kind, Arity}, {K, A}})
|
||||
end || [K, A, D] <- Matches, A /= Arity, D /= 0, defaults_conflict(A, D, Arity, Defaults)].
|
||||
|
||||
defaults_conflict(A, D, Arity, Defaults) ->
|
||||
((Arity >= (A - D)) andalso (Arity < A)) orelse
|
||||
((A >= (Arity - Defaults)) andalso (A < Arity)).
|
||||
|
||||
assert_no_aliases_name(Line, '__aliases__', [Atom], #elixir_env{file=File}) when is_atom(Atom) ->
|
||||
elixir_errors:form_error(Line, File, ?MODULE, { no_alias, Atom });
|
||||
assert_no_aliases_name(Line, '__aliases__', [Atom], #{file := File}) when is_atom(Atom) ->
|
||||
elixir_errors:form_error(Line, File, ?MODULE, {no_alias, Atom});
|
||||
|
||||
assert_no_aliases_name(_Meta, _Aliases, _Args, _S) ->
|
||||
ok.
|
||||
@@ -338,17 +338,17 @@ assert_no_aliases_name(_Meta, _Aliases, _Args, _S) ->
|
||||
format_error({no_module,{Kind,Name,Arity}}) ->
|
||||
io_lib:format("cannot define function outside module, invalid scope for ~ts ~ts/~B", [Kind, Name, Arity]);
|
||||
|
||||
format_error({defs_with_defaults, Name, { Kind, Arity }, { K, A } }) when Arity > A ->
|
||||
format_error({defs_with_defaults, Name, {Kind, Arity}, {K, A}}) when Arity > A ->
|
||||
io_lib:format("~ts ~ts/~B defaults conflicts with ~ts ~ts/~B",
|
||||
[Kind, Name, Arity, K, Name, A]);
|
||||
|
||||
format_error({defs_with_defaults, Name, { Kind, Arity }, { K, A } }) when Arity < A ->
|
||||
format_error({defs_with_defaults, Name, {Kind, Arity}, {K, A}}) when Arity < A ->
|
||||
io_lib:format("~ts ~ts/~B conflicts with defaults from ~ts ~ts/~B",
|
||||
[Kind, Name, Arity, K, Name, A]);
|
||||
|
||||
format_error({clauses_with_defaults,{Kind,Name,Arity}}) ->
|
||||
io_lib:format("~ts ~ts/~B has default values and multiple clauses, "
|
||||
"use a separate clause for declaring defaults", [Kind, Name, Arity]);
|
||||
"define a function head with the defaults", [Kind, Name, Arity]);
|
||||
|
||||
format_error({out_of_order_defaults,{Kind,Name,Arity}}) ->
|
||||
io_lib:format("clause with defaults should be the first clause in ~ts ~ts/~B", [Kind, Name, Arity]);
|
||||
|
||||
@@ -5,10 +5,10 @@
|
||||
|
||||
expand(Args, E) ->
|
||||
lists:mapfoldl(fun
|
||||
({ '\\\\', Meta, [Left, Right] }, Acc) ->
|
||||
{ ELeft, EL } = elixir_exp:expand(Left, Acc),
|
||||
{ ERight, _ } = elixir_exp:expand(Right, Acc#elixir_env{context=nil}),
|
||||
{ { '\\\\', Meta, [ELeft, ERight] }, EL };
|
||||
({'\\\\', Meta, [Left, Right]}, Acc) ->
|
||||
{ELeft, EL} = elixir_exp:expand(Left, Acc),
|
||||
{ERight, _} = elixir_exp:expand(Right, Acc#{context := nil}),
|
||||
{{'\\\\', Meta, [ELeft, ERight]}, EL};
|
||||
(Left, Acc) ->
|
||||
elixir_exp:expand(Left, Acc)
|
||||
end, E, Args).
|
||||
@@ -23,24 +23,24 @@ unpack(Kind, Name, Args, S) ->
|
||||
%% 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), [], []),
|
||||
{Args, Invoke} = extract_defaults(List, Line, length(Base), [], []),
|
||||
|
||||
{ DefArgs, SA } = elixir_clauses:match(fun elixir_translator:translate_args/2, Base ++ Args, S),
|
||||
{ DefInvoke, _ } = elixir_translator:translate_args(Base ++ Invoke, SA),
|
||||
{DefArgs, SA} = elixir_clauses:match(fun elixir_translator:translate_args/2, Base ++ Args, S),
|
||||
{DefInvoke, _} = elixir_translator:translate_args(Base ++ Invoke, SA),
|
||||
|
||||
Call = { call, Line,
|
||||
{ atom, Line, name_for_kind(Kind, Name) },
|
||||
Call = {call, Line,
|
||||
{atom, Line, name_for_kind(Kind, Name)},
|
||||
DefInvoke
|
||||
},
|
||||
},
|
||||
|
||||
Clause = { clause, Line, DefArgs, [], [Call] },
|
||||
Clause = {clause, Line, DefArgs, [], [Call]},
|
||||
unpack_each(Kind, Name, T, [Expr|Acc], [Clause|Clauses], S);
|
||||
|
||||
unpack_each(Kind, Name, [H|T], Acc, Clauses, S) ->
|
||||
unpack_each(Kind, Name, T, [H|Acc], Clauses, S);
|
||||
|
||||
unpack_each(_Kind, _Name, [], Acc, Clauses, _S) ->
|
||||
{ lists:reverse(Acc), lists:reverse(Clauses) }.
|
||||
{lists:reverse(Acc), lists:reverse(Clauses)}.
|
||||
|
||||
% Extract default values from args following the current default clause.
|
||||
|
||||
@@ -48,21 +48,21 @@ extract_defaults([{'\\\\', _, [_Expr, Default]}|T], Line, Counter, NewArgs, NewI
|
||||
extract_defaults(T, Line, Counter, NewArgs, [Default|NewInvoke]);
|
||||
|
||||
extract_defaults([_|T], Line, Counter, NewArgs, NewInvoke) ->
|
||||
H = { ?atom_concat(["_@D", Counter]), Line, nil },
|
||||
H = {elixir_utils:atom_concat(["_@D", Counter]), Line, nil},
|
||||
extract_defaults(T, Line, Counter + 1, [H|NewArgs], [H|NewInvoke]);
|
||||
|
||||
extract_defaults([], _Line, _Counter, NewArgs, NewInvoke) ->
|
||||
{ lists:reverse(NewArgs), lists:reverse(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([_|T], Line, Acc) ->
|
||||
Var = { ?atom_concat(["_@D", length(T)]), Line, nil },
|
||||
Var = {elixir_utils:atom_concat(["_@D", length(T)]), Line, nil},
|
||||
build_match(T, Line, [Var|Acc]).
|
||||
|
||||
% Given the invoked function name based on the kind
|
||||
|
||||
name_for_kind(Kind, Name) when Kind == defmacro; Kind == defmacrop -> ?elixir_macro(Name);
|
||||
name_for_kind(Kind, Name) when Kind == defmacro; Kind == defmacrop -> elixir_utils:macro_name(Name);
|
||||
name_for_kind(_Kind, Name) -> Name.
|
||||
@@ -8,15 +8,15 @@ overridable(Module) ->
|
||||
ets:lookup_element(elixir_module:data_table(Module), '__overridable', 2).
|
||||
|
||||
overridable(Module, Value) ->
|
||||
ets:insert(elixir_module:data_table(Module), { '__overridable', Value }).
|
||||
ets:insert(elixir_module:data_table(Module), {'__overridable', Value}).
|
||||
|
||||
%% Check if an overridable function is defined.
|
||||
|
||||
ensure_defined(Meta, Module, Tuple, S) ->
|
||||
Overridable = overridable(Module),
|
||||
case orddict:find(Tuple, Overridable) of
|
||||
{ ok, { _, _, _, _ } } -> ok;
|
||||
_ -> elixir_errors:form_error(Meta, S#elixir_scope.file, ?MODULE, { no_super, Module, Tuple })
|
||||
{ok, {_, _, _, _}} -> ok;
|
||||
_ -> elixir_errors:form_error(Meta, S#elixir_scope.file, ?MODULE, {no_super, Module, Tuple})
|
||||
end.
|
||||
|
||||
%% Gets the name based on the function and stored overridables
|
||||
@@ -24,33 +24,33 @@ ensure_defined(Meta, Module, Tuple, S) ->
|
||||
name(Module, Function) ->
|
||||
name(Module, Function, overridable(Module)).
|
||||
|
||||
name(_Module, { Name, _ } = Function, Overridable) ->
|
||||
{ Count, _, _, _ } = orddict:fetch(Function, Overridable),
|
||||
?atom_concat([Name, " (overridable ", Count, ")"]).
|
||||
name(_Module, {Name, _} = Function, Overridable) ->
|
||||
{Count, _, _, _} = orddict:fetch(Function, Overridable),
|
||||
elixir_utils:atom_concat([Name, " (overridable ", Count, ")"]).
|
||||
|
||||
%% Store
|
||||
|
||||
store(Module, Function, GenerateName) ->
|
||||
Overridable = overridable(Module),
|
||||
case orddict:fetch(Function, Overridable) of
|
||||
{ _Count, _Clause, _Neighbours, true } -> ok;
|
||||
{ Count, Clause, Neighbours, false } ->
|
||||
overridable(Module, orddict:store(Function, { Count, Clause, Neighbours, true }, Overridable)),
|
||||
{ { { Name, Arity }, Kind, Line, File, _Check, Location, Defaults }, Clauses } = Clause,
|
||||
{_Count, _Clause, _Neighbours, true} -> ok;
|
||||
{Count, Clause, Neighbours, false} ->
|
||||
overridable(Module, orddict:store(Function, {Count, Clause, Neighbours, true}, Overridable)),
|
||||
{{{Name, Arity}, Kind, Line, File, _Check, Location, Defaults}, Clauses} = Clause,
|
||||
|
||||
{ FinalKind, FinalName } = case GenerateName of
|
||||
true -> { defp, name(Module, Function, Overridable) };
|
||||
false -> { Kind, Name }
|
||||
{FinalKind, FinalName} = case GenerateName of
|
||||
true -> {defp, name(Module, Function, Overridable)};
|
||||
false -> {Kind, Name}
|
||||
end,
|
||||
|
||||
case code:is_loaded('Elixir.Module.LocalsTracker') of
|
||||
{ _, _ } ->
|
||||
'Elixir.Module.LocalsTracker':reattach(Module, Kind, { Name, Arity }, Neighbours);
|
||||
{_, _} ->
|
||||
'Elixir.Module.LocalsTracker':reattach(Module, Kind, {Name, Arity}, Neighbours);
|
||||
_ ->
|
||||
ok
|
||||
end,
|
||||
|
||||
Def = { function, Line, FinalName, Arity, Clauses },
|
||||
Def = {function, Line, FinalName, Arity, Clauses},
|
||||
elixir_def:store_each(false, FinalKind, File, Location,
|
||||
elixir_def:table(Module), elixir_def:clauses_table(Module), Defaults, Def)
|
||||
end.
|
||||
@@ -58,18 +58,18 @@ store(Module, Function, GenerateName) ->
|
||||
%% Store pending declarations that were not manually made concrete.
|
||||
|
||||
store_pending(Module) ->
|
||||
[store(Module, X, false) || { X, { _, _, _, false } } <- overridable(Module),
|
||||
[store(Module, X, false) || {X, {_, _, _, false}} <- overridable(Module),
|
||||
not 'Elixir.Module':'defines?'(Module, X)].
|
||||
|
||||
%% Error handling
|
||||
|
||||
format_error({ no_super, Module, { Name, Arity } }) ->
|
||||
Bins = [format_fa(X) || { X, { _, _, _, _ } } <- overridable(Module)],
|
||||
format_error({no_super, Module, {Name, Arity}}) ->
|
||||
Bins = [format_fa(X) || {X, {_, _, _, _}} <- overridable(Module)],
|
||||
Joined = 'Elixir.Enum':join(Bins, <<", ">>),
|
||||
io_lib:format("no super defined for ~ts/~B in module ~ts. Overridable functions available are: ~ts",
|
||||
[Name, Arity, elixir_aliases:inspect(Module), Joined]).
|
||||
|
||||
format_fa({ Name, Arity }) ->
|
||||
format_fa({Name, Arity}) ->
|
||||
A = atom_to_binary(Name, utf8),
|
||||
B = integer_to_binary(Arity),
|
||||
<< A/binary, $/, B/binary >>.
|
||||
+247
-220
@@ -10,30 +10,31 @@
|
||||
-include("elixir.hrl").
|
||||
-import(ordsets, [is_element/2]).
|
||||
|
||||
%% Module macros
|
||||
-define(kernel, 'Elixir.Kernel').
|
||||
-define(map, 'Elixir.Map').
|
||||
-define(node, 'Elixir.Node').
|
||||
-define(process, 'Elixir.Process').
|
||||
-define(system, 'Elixir.System').
|
||||
-define(tuple, 'Elixir.Tuple').
|
||||
|
||||
default_functions() ->
|
||||
[ { ?kernel, elixir_imported_functions() } ].
|
||||
[ {?kernel, elixir_imported_functions()} ].
|
||||
default_macros() ->
|
||||
[ { ?kernel, elixir_imported_macros() } ].
|
||||
[ {?kernel, elixir_imported_macros()} ].
|
||||
default_requires() ->
|
||||
[ 'Elixir.Kernel', 'Elixir.Kernel.Typespec' ].
|
||||
|
||||
find_import(Meta, Name, Arity, E) ->
|
||||
Tuple = { Name, Arity },
|
||||
Tuple = {Name, Arity},
|
||||
|
||||
case find_dispatch(Meta, Tuple, [], E) of
|
||||
{ function, Receiver } ->
|
||||
elixir_lexical:record_import(Receiver, E#elixir_env.lexical_tracker),
|
||||
elixir_locals:record_import(Tuple, Receiver, E#elixir_env.module, E#elixir_env.function),
|
||||
{function, Receiver} ->
|
||||
elixir_lexical:record_import(Receiver, ?m(E, lexical_tracker)),
|
||||
elixir_locals:record_import(Tuple, Receiver, ?m(E, module), ?m(E, function)),
|
||||
Receiver;
|
||||
{ macro, Receiver } ->
|
||||
elixir_lexical:record_import(Receiver, E#elixir_env.lexical_tracker),
|
||||
elixir_locals:record_import(Tuple, Receiver, E#elixir_env.module, E#elixir_env.function),
|
||||
{macro, Receiver} ->
|
||||
elixir_lexical:record_import(Receiver, ?m(E, lexical_tracker)),
|
||||
elixir_locals:record_import(Tuple, Receiver, ?m(E, module), ?m(E, function)),
|
||||
Receiver;
|
||||
_ ->
|
||||
false
|
||||
@@ -42,47 +43,47 @@ find_import(Meta, Name, Arity, E) ->
|
||||
%% Function retrieval
|
||||
|
||||
import_function(Meta, Name, Arity, E) ->
|
||||
Tuple = { Name, Arity },
|
||||
Tuple = {Name, Arity},
|
||||
case find_dispatch(Meta, Tuple, [], E) of
|
||||
{ function, Receiver } ->
|
||||
elixir_lexical:record_import(Receiver, E#elixir_env.lexical_tracker),
|
||||
elixir_locals:record_import(Tuple, Receiver, E#elixir_env.module, E#elixir_env.function),
|
||||
{function, Receiver} ->
|
||||
elixir_lexical:record_import(Receiver, ?m(E, lexical_tracker)),
|
||||
elixir_locals:record_import(Tuple, Receiver, ?m(E, module), ?m(E, function)),
|
||||
remote_function(Receiver, Name, Arity, E);
|
||||
{ macro, _Receiver } ->
|
||||
{macro, _Receiver} ->
|
||||
false;
|
||||
{ import, Receiver } ->
|
||||
{import, Receiver} ->
|
||||
require_function(Meta, Receiver, Name, Arity, E);
|
||||
false ->
|
||||
case elixir_import:special_form(Name, Arity) of
|
||||
true -> false;
|
||||
false ->
|
||||
elixir_locals:record_local(Tuple, E#elixir_env.module, E#elixir_env.function),
|
||||
{ local, Name, Arity }
|
||||
elixir_locals:record_local(Tuple, ?m(E, module), ?m(E, function)),
|
||||
{local, Name, Arity}
|
||||
end
|
||||
end.
|
||||
|
||||
require_function(_Meta, Receiver, Name, Arity, E) ->
|
||||
case is_element({ Name, Arity }, get_optional_macros(Receiver)) of
|
||||
case is_element({Name, Arity}, get_optional_macros(Receiver)) of
|
||||
true -> false;
|
||||
false -> remote_function(Receiver, Name, Arity, E)
|
||||
end.
|
||||
|
||||
remote_function(Receiver, Name, Arity, E) ->
|
||||
elixir_lexical:record_remote(Receiver, E#elixir_env.lexical_tracker),
|
||||
elixir_lexical:record_remote(Receiver, ?m(E, lexical_tracker)),
|
||||
case inline(Receiver, Name, Arity) of
|
||||
{ AR, AN } -> { remote, AR, AN, Arity };
|
||||
false -> { remote, Receiver, Name, Arity }
|
||||
{AR, AN} -> {remote, AR, AN, Arity};
|
||||
false -> {remote, Receiver, Name, Arity}
|
||||
end.
|
||||
|
||||
%% Dispatches
|
||||
|
||||
dispatch_import(Meta, Name, Args, E, Callback) ->
|
||||
Arity = length(Args),
|
||||
case expand_import(Meta, { Name, Arity }, Args, E, []) of
|
||||
{ ok, Receiver, Quoted } ->
|
||||
case expand_import(Meta, {Name, Arity}, Args, E, []) of
|
||||
{ok, Receiver, Quoted} ->
|
||||
expand_quoted(Meta, Receiver, Name, Arity, Quoted, E);
|
||||
{ ok, Receiver } ->
|
||||
elixir_exp:expand({ { '.', [], [Receiver, Name] }, Meta, Args }, E);
|
||||
{ok, Receiver} ->
|
||||
elixir_exp:expand({{'.', [], [Receiver, Name]}, Meta, Args}, E);
|
||||
error ->
|
||||
Callback()
|
||||
end.
|
||||
@@ -91,11 +92,11 @@ dispatch_require(Meta, Receiver, Name, Args, E, Callback) when is_atom(Receiver)
|
||||
Arity = length(Args),
|
||||
|
||||
case rewrite(Receiver, Name, Args, Arity) of
|
||||
{ ok, AR, AN, AA } ->
|
||||
{ok, AR, AN, AA} ->
|
||||
Callback(AR, AN, AA);
|
||||
false ->
|
||||
case expand_require(Meta, Receiver, { Name, Arity }, Args, E) of
|
||||
{ ok, Receiver, Quoted } -> expand_quoted(Meta, Receiver, Name, Arity, Quoted, E);
|
||||
case expand_require(Meta, Receiver, {Name, Arity}, Args, E) of
|
||||
{ok, Receiver, Quoted} -> expand_quoted(Meta, Receiver, Name, Arity, Quoted, E);
|
||||
error -> Callback(Receiver, Name, Args)
|
||||
end
|
||||
end;
|
||||
@@ -105,23 +106,23 @@ dispatch_require(_Meta, Receiver, Name, Args, _E, Callback) ->
|
||||
|
||||
%% Macros expansion
|
||||
|
||||
expand_import(Meta, { Name, Arity } = Tuple, Args, E, Extra) ->
|
||||
Module = E#elixir_env.module,
|
||||
expand_import(Meta, {Name, Arity} = Tuple, Args, E, Extra) ->
|
||||
Module = ?m(E, module),
|
||||
Dispatch = find_dispatch(Meta, Tuple, Extra, E),
|
||||
Function = E#elixir_env.function,
|
||||
Function = ?m(E, function),
|
||||
Local = (Function /= nil) andalso (Function /= Tuple) andalso
|
||||
elixir_locals:macro_for(Module, Name, Arity),
|
||||
|
||||
case Dispatch of
|
||||
%% In case it is an import, we dispatch the import.
|
||||
{ import, _ } ->
|
||||
{import, _} ->
|
||||
do_expand_import(Meta, Tuple, Args, Module, E, Dispatch);
|
||||
|
||||
%% There is a local and an import. This is a conflict unless
|
||||
%% the receiver is the same as module (happens on bootstrap).
|
||||
{ _, Receiver } when Local /= false, Receiver /= Module ->
|
||||
Error = { macro_conflict, { Receiver, Name, Arity } },
|
||||
elixir_errors:form_error(Meta, E#elixir_env.file, ?MODULE, Error);
|
||||
{_, Receiver} when Local /= false, Receiver /= Module ->
|
||||
Error = {macro_conflict, {Receiver, Name, Arity}},
|
||||
elixir_errors:form_error(Meta, ?m(E, file), ?MODULE, Error);
|
||||
|
||||
%% There is no local. Dispatch the import.
|
||||
_ when Local == false ->
|
||||
@@ -130,33 +131,33 @@ expand_import(Meta, { Name, Arity } = Tuple, Args, E, Extra) ->
|
||||
%% Dispatch to the local.
|
||||
_ ->
|
||||
elixir_locals:record_local(Tuple, Module, Function),
|
||||
{ ok, Module, expand_macro_fun(Meta, Local(), Module, Name, Args, E) }
|
||||
{ok, Module, expand_macro_fun(Meta, Local(), Module, Name, Args, E)}
|
||||
end.
|
||||
|
||||
do_expand_import(Meta, { Name, Arity } = Tuple, Args, Module, E, Result) ->
|
||||
do_expand_import(Meta, {Name, Arity} = Tuple, Args, Module, E, Result) ->
|
||||
case Result of
|
||||
{ function, Receiver } ->
|
||||
elixir_lexical:record_import(Receiver, E#elixir_env.lexical_tracker),
|
||||
elixir_locals:record_import(Tuple, Receiver, Module, E#elixir_env.function),
|
||||
{function, Receiver} ->
|
||||
elixir_lexical:record_import(Receiver, ?m(E, lexical_tracker)),
|
||||
elixir_locals:record_import(Tuple, Receiver, Module, ?m(E, function)),
|
||||
case rewrite(Receiver, Name, Args, Arity) of
|
||||
{ ok, AR, AN, AA } ->
|
||||
{ ok, AR, { { '.', [], [AR, AN] }, [], AA } };
|
||||
{ok, AR, AN, AA} ->
|
||||
{ok, AR, {{'.', [], [AR, AN]}, [], AA}};
|
||||
false ->
|
||||
{ ok, Receiver }
|
||||
{ok, Receiver}
|
||||
end;
|
||||
{ macro, Receiver } ->
|
||||
elixir_lexical:record_import(Receiver, E#elixir_env.lexical_tracker),
|
||||
elixir_locals:record_import(Tuple, Receiver, Module, E#elixir_env.function),
|
||||
{ ok, Receiver, expand_macro_named(Meta, Receiver, Name, Arity, Args, E) };
|
||||
{ import, Receiver } ->
|
||||
{macro, Receiver} ->
|
||||
elixir_lexical:record_import(Receiver, ?m(E, lexical_tracker)),
|
||||
elixir_locals:record_import(Tuple, Receiver, Module, ?m(E, function)),
|
||||
{ok, Receiver, expand_macro_named(Meta, Receiver, Name, Arity, Args, E)};
|
||||
{import, Receiver} ->
|
||||
case expand_require([{require,false}|Meta], Receiver, Tuple, Args, E) of
|
||||
{ ok, _, _ } = Response -> Response;
|
||||
error -> { ok, Receiver }
|
||||
{ok, _, _} = Response -> Response;
|
||||
error -> {ok, Receiver}
|
||||
end;
|
||||
false when Module == ?kernel ->
|
||||
case rewrite(Module, Name, Args, Arity) of
|
||||
{ ok, AR, AN, AA } ->
|
||||
{ ok, AR, { { '.', [], [AR, AN] }, [], AA } };
|
||||
{ok, AR, AN, AA} ->
|
||||
{ok, AR, {{'.', [], [AR, AN]}, [], AA}};
|
||||
false ->
|
||||
error
|
||||
end;
|
||||
@@ -164,19 +165,19 @@ do_expand_import(Meta, { Name, Arity } = Tuple, Args, Module, E, Result) ->
|
||||
error
|
||||
end.
|
||||
|
||||
expand_require(Meta, Receiver, { Name, Arity } = Tuple, Args, E) ->
|
||||
Module = E#elixir_env.module,
|
||||
expand_require(Meta, Receiver, {Name, Arity} = Tuple, Args, E) ->
|
||||
Module = ?m(E, module),
|
||||
|
||||
case is_element(Tuple, get_optional_macros(Receiver)) of
|
||||
true ->
|
||||
Requires = E#elixir_env.requires,
|
||||
Requires = ?m(E, requires),
|
||||
case (Receiver == Module) orelse is_element(Receiver, Requires) orelse skip_require(Meta) of
|
||||
true ->
|
||||
elixir_lexical:record_remote(Receiver, E#elixir_env.lexical_tracker),
|
||||
{ ok, Receiver, expand_macro_named(Meta, Receiver, Name, Arity, Args, E) };
|
||||
elixir_lexical:record_remote(Receiver, ?m(E, lexical_tracker)),
|
||||
{ok, Receiver, expand_macro_named(Meta, Receiver, Name, Arity, Args, E)};
|
||||
false ->
|
||||
Info = { unrequired_module, { Receiver, Name, length(Args), Requires } },
|
||||
elixir_errors:form_error(Meta, E#elixir_env.file, ?MODULE, Info)
|
||||
Info = {unrequired_module, {Receiver, Name, length(Args), Requires}},
|
||||
elixir_errors:form_error(Meta, ?m(E, file), ?MODULE, Info)
|
||||
end;
|
||||
false ->
|
||||
error
|
||||
@@ -186,18 +187,20 @@ expand_require(Meta, Receiver, { Name, Arity } = Tuple, Args, E) ->
|
||||
|
||||
expand_macro_fun(Meta, Fun, Receiver, Name, Args, E) ->
|
||||
Line = ?line(Meta),
|
||||
EArg = { Line, E },
|
||||
EArg = {Line, E},
|
||||
|
||||
try
|
||||
apply(Fun, [EArg|Args])
|
||||
catch
|
||||
Kind:Reason ->
|
||||
Info = [{ Receiver, Name, length(Args), location(Line, E) }, mfa(Line, E)],
|
||||
erlang:raise(Kind, Reason, prune_stacktrace(Info, erlang:get_stacktrace(), EArg))
|
||||
Arity = length(Args),
|
||||
MFA = {Receiver, elixir_utils:macro_name(Name), Arity+1},
|
||||
Info = [{Receiver, Name, Arity, [{file, "expanding macro"}]}, caller(Line, E)],
|
||||
erlang:raise(Kind, Reason, prune_stacktrace(erlang:get_stacktrace(), MFA, Info, EArg))
|
||||
end.
|
||||
|
||||
expand_macro_named(Meta, Receiver, Name, Arity, Args, E) ->
|
||||
ProperName = ?elixir_macro(Name),
|
||||
ProperName = elixir_utils:macro_name(Name),
|
||||
ProperArity = Arity + 1,
|
||||
Fun = fun Receiver:ProperName/ProperArity,
|
||||
expand_macro_fun(Meta, Fun, Receiver, Name, Args, E).
|
||||
@@ -208,59 +211,61 @@ expand_quoted(Meta, Receiver, Name, Arity, Quoted, E) ->
|
||||
|
||||
try
|
||||
elixir_exp:expand(
|
||||
elixir_quote:linify_with_context_counter(Line, { Receiver, Next }, Quoted),
|
||||
elixir_quote:linify_with_context_counter(Line, {Receiver, Next}, Quoted),
|
||||
E)
|
||||
catch
|
||||
Kind:Reason ->
|
||||
Info = [{ Receiver, Name, Arity, location(Line, E) }, mfa(Line, E)],
|
||||
erlang:raise(Kind, Reason, prune_stacktrace(Info, erlang:get_stacktrace(), nil))
|
||||
MFA = {Receiver, elixir_utils:macro_name(Name), Arity+1},
|
||||
Info = [{Receiver, Name, Arity, [{file, "expanding macro"}]}, caller(Line, E)],
|
||||
erlang:raise(Kind, Reason, prune_stacktrace(erlang:get_stacktrace(), MFA, Info, nil))
|
||||
end.
|
||||
|
||||
mfa(Line, #elixir_env{module=nil} = E) ->
|
||||
{ elixir_compiler, '__FILE__', 2, location(Line, E) };
|
||||
mfa(Line, #elixir_env{module=Module, function=nil} = E) ->
|
||||
{ Module, '__MODULE__', 0, location(Line, E) };
|
||||
mfa(Line, #elixir_env{module=Module, function={ Name, Arity }} = E) ->
|
||||
{ Module, Name, Arity, location(Line, E) }.
|
||||
caller(Line, #{module := nil} = E) ->
|
||||
{elixir_compiler, '__FILE__', 2, location(Line, E)};
|
||||
caller(Line, #{module := Module, function := nil} = E) ->
|
||||
{Module, '__MODULE__', 0, location(Line, E)};
|
||||
caller(Line, #{module := Module, function := {Name, Arity}} = E) ->
|
||||
{Module, Name, Arity, location(Line, E)}.
|
||||
|
||||
location(Line, E) ->
|
||||
[{ file, elixir_utils:characters_to_list(E#elixir_env.file) }, { line, Line }].
|
||||
[{file, elixir_utils:characters_to_list(elixir_utils:relative_to_cwd(?m(E, file)))},
|
||||
{line, Line}].
|
||||
|
||||
%% Helpers
|
||||
|
||||
skip_require(Meta) ->
|
||||
lists:keyfind(require, 1, Meta) == { require, false }.
|
||||
lists:keyfind(require, 1, Meta) == {require, false}.
|
||||
|
||||
find_dispatch(Meta, Tuple, Extra, E) ->
|
||||
case is_import(Meta) of
|
||||
{ import, _ } = Import ->
|
||||
{import, _} = Import ->
|
||||
Import;
|
||||
false ->
|
||||
Funs = E#elixir_env.functions,
|
||||
Macs = Extra ++ E#elixir_env.macros,
|
||||
Funs = ?m(E, functions),
|
||||
Macs = Extra ++ ?m(E, macros),
|
||||
FunMatch = find_dispatch(Tuple, Funs),
|
||||
MacMatch = find_dispatch(Tuple, Macs),
|
||||
|
||||
case { FunMatch, MacMatch } of
|
||||
{ [], [Receiver] } -> { macro, Receiver };
|
||||
{ [Receiver], [] } -> { function, Receiver };
|
||||
{ [], [] } -> false;
|
||||
case {FunMatch, MacMatch} of
|
||||
{[], [Receiver]} -> {macro, Receiver};
|
||||
{[Receiver], []} -> {function, Receiver};
|
||||
{[], []} -> false;
|
||||
_ ->
|
||||
{ Name, Arity } = Tuple,
|
||||
{Name, Arity} = Tuple,
|
||||
[First, Second|_] = FunMatch ++ MacMatch,
|
||||
Error = { ambiguous_call, { First, Second, Name, Arity } },
|
||||
elixir_errors:form_error(Meta, E#elixir_env.file, ?MODULE, Error)
|
||||
Error = {ambiguous_call, {First, Second, Name, Arity}},
|
||||
elixir_errors:form_error(Meta, ?m(E, file), ?MODULE, Error)
|
||||
end
|
||||
end.
|
||||
|
||||
find_dispatch(Tuple, List) ->
|
||||
[Receiver || { Receiver, Set } <- List, is_element(Tuple, Set)].
|
||||
[Receiver || {Receiver, Set} <- List, is_element(Tuple, Set)].
|
||||
|
||||
is_import(Meta) ->
|
||||
case lists:keyfind(import, 1, Meta) of
|
||||
{ import, _ } = Import ->
|
||||
{import, _} = Import ->
|
||||
case lists:keyfind(context, 1, Meta) of
|
||||
{ context, _ } -> Import;
|
||||
{context, _} -> Import;
|
||||
false ->
|
||||
false
|
||||
end;
|
||||
@@ -268,28 +273,31 @@ is_import(Meta) ->
|
||||
false
|
||||
end.
|
||||
|
||||
%% We've reached the invoked macro, skip it with the rest
|
||||
prune_stacktrace(Info, [{ _, _, [E|_], _ }|_], E) ->
|
||||
% %% We've reached the macro wrapper fun, skip it with the rest
|
||||
prune_stacktrace([{_, _, [E|_], _}|_], _MFA, Info, E) ->
|
||||
Info;
|
||||
%% We've reached the invoked macro, skip it
|
||||
prune_stacktrace([{M, F, A, _}|_], {M, F, A}, Info, _E) ->
|
||||
Info;
|
||||
%% We've reached the elixir_dispatch internals, skip it with the rest
|
||||
prune_stacktrace(Info, [{ Mod, _, _, _ }|_], _) when Mod == elixir_dispatch; Mod == elixir_exp ->
|
||||
prune_stacktrace([{Mod, _, _, _}|_], _MFA, Info, _E) when Mod == elixir_dispatch; Mod == elixir_exp ->
|
||||
Info;
|
||||
prune_stacktrace(Info, [H|T], E) ->
|
||||
[H|prune_stacktrace(Info, T, E)];
|
||||
prune_stacktrace(Info, [], _) ->
|
||||
prune_stacktrace([H|T], MFA, Info, E) ->
|
||||
[H|prune_stacktrace(T, MFA, Info, E)];
|
||||
prune_stacktrace([], _MFA, Info, _E) ->
|
||||
Info.
|
||||
|
||||
%% ERROR HANDLING
|
||||
|
||||
format_error({ unrequired_module, { Receiver, Name, Arity, _Required }}) ->
|
||||
format_error({unrequired_module, {Receiver, Name, Arity, _Required}}) ->
|
||||
Module = elixir_aliases:inspect(Receiver),
|
||||
io_lib:format("you must require ~ts before invoking the macro ~ts.~ts/~B",
|
||||
[Module, Module, Name, Arity]);
|
||||
format_error({ macro_conflict, { Receiver, Name, Arity } }) ->
|
||||
format_error({macro_conflict, {Receiver, Name, Arity}}) ->
|
||||
io_lib:format("call to local macro ~ts/~B conflicts with imported ~ts.~ts/~B, "
|
||||
"please rename the local macro or remove the conflicting import",
|
||||
[Name, Arity, elixir_aliases:inspect(Receiver), Name, Arity]);
|
||||
format_error({ ambiguous_call, { Mod1, Mod2, Name, Arity }}) ->
|
||||
format_error({ambiguous_call, {Mod1, Mod2, Name, Arity}}) ->
|
||||
io_lib:format("function ~ts/~B imported from both ~ts and ~ts, call is ambiguous",
|
||||
[Name, Arity, elixir_aliases:inspect(Mod1), elixir_aliases:inspect(Mod2)]).
|
||||
|
||||
@@ -300,13 +308,13 @@ get_optional_macros(erlang) -> [];
|
||||
|
||||
get_optional_macros(Receiver) ->
|
||||
case code:ensure_loaded(Receiver) of
|
||||
{ module, Receiver } ->
|
||||
{module, Receiver} ->
|
||||
try
|
||||
Receiver:'__info__'(macros)
|
||||
catch
|
||||
error:undef -> []
|
||||
end;
|
||||
{ error, _ } -> []
|
||||
{error, _} -> []
|
||||
end.
|
||||
|
||||
elixir_imported_functions() ->
|
||||
@@ -323,137 +331,156 @@ elixir_imported_macros() ->
|
||||
error:undef -> []
|
||||
end.
|
||||
|
||||
rewrite(?kernel, atom_to_binary, [Arg], 1) ->
|
||||
{ ok, erlang, atom_to_binary, [Arg, utf8] };
|
||||
rewrite(?kernel, binary_to_atom, [Arg], 1) ->
|
||||
{ ok, erlang, binary_to_atom, [Arg, utf8] };
|
||||
rewrite(?kernel, binary_to_existing_atom, [Arg], 1) ->
|
||||
{ ok, erlang, binary_to_existing_atom, [Arg, utf8] };
|
||||
rewrite(?kernel, elem, [Tuple, Index], 2) ->
|
||||
{ ok, erlang, element, [increment(Index), Tuple] };
|
||||
rewrite(?kernel, set_elem, [Tuple, Index, Value], 2) ->
|
||||
{ ok, erlang, setelement, [increment(Index), Tuple, Value] };
|
||||
rewrite(?process, monitor, [Arg], 1) ->
|
||||
{ ok, erlang, monitor, [process, Arg] };
|
||||
rewrite(?kernel, atom_to_binary, [Arg], _) ->
|
||||
{ok, erlang, atom_to_binary, [Arg, utf8]};
|
||||
rewrite(?kernel, binary_to_atom, [Arg], _) ->
|
||||
{ok, erlang, binary_to_atom, [Arg, utf8]};
|
||||
rewrite(?kernel, binary_to_existing_atom, [Arg], _) ->
|
||||
{ok, erlang, binary_to_existing_atom, [Arg, utf8]};
|
||||
rewrite(?kernel, elem, [Tuple, Index], _) ->
|
||||
{ok, erlang, element, [increment(Index), Tuple]};
|
||||
rewrite(?kernel, set_elem, [Tuple, Index, Value], _) ->
|
||||
{ok, erlang, setelement, [increment(Index), Tuple, Value]};
|
||||
|
||||
rewrite(?process, monitor, [Arg], _) ->
|
||||
{ok, erlang, monitor, [process, Arg]};
|
||||
|
||||
rewrite(?tuple, insert_at, [Tuple, Index, Term], _) ->
|
||||
{ok, erlang, insert_element, [increment(Index), Tuple, Term]};
|
||||
rewrite(?tuple, delete_at, [Tuple, Index], _) ->
|
||||
{ok, erlang, delete_element, [increment(Index), Tuple]};
|
||||
rewrite(?tuple, duplicate, [Data, Size], _) ->
|
||||
{ok, erlang, make_tuple, [Size, Data]};
|
||||
|
||||
rewrite(?map, 'has_key?', [Map, Key], _) ->
|
||||
{ok, maps, is_key, [Key, Map]};
|
||||
rewrite(?map, fetch, [Map, Key], _) ->
|
||||
{ok, maps, find, [Key, Map]};
|
||||
rewrite(?map, put, [Map, Key, Value], _) ->
|
||||
{ok, maps, put, [Key, Value, Map]};
|
||||
rewrite(?map, delete, [Map, Key], _) ->
|
||||
{ok, maps, remove, [Key, Map]};
|
||||
rewrite(Receiver, Name, Args, Arity) ->
|
||||
case inline(Receiver, Name, Arity) of
|
||||
{ AR, AN } -> { ok, AR, AN, Args };
|
||||
false -> false
|
||||
{AR, AN} -> {ok, AR, AN, Args};
|
||||
false -> false
|
||||
end.
|
||||
|
||||
increment(Number) when is_number(Number) ->
|
||||
Number + 1;
|
||||
increment(Other) ->
|
||||
{ { '.', [], [erlang, '+'] }, [], [Other, 1] }.
|
||||
{{'.', [], [erlang, '+']}, [], [Other, 1]}.
|
||||
|
||||
inline(?kernel, '+', 2) -> { erlang, '+' };
|
||||
inline(?kernel, '-', 2) -> { erlang, '-' };
|
||||
inline(?kernel, '+', 1) -> { erlang, '+' };
|
||||
inline(?kernel, '-', 1) -> { erlang, '-' };
|
||||
inline(?kernel, '*', 2) -> { erlang, '*' };
|
||||
inline(?kernel, '/', 2) -> { erlang, '/' };
|
||||
inline(?kernel, '++', 2) -> { erlang, '++' };
|
||||
inline(?kernel, '--', 2) -> { erlang, '--' };
|
||||
inline(?kernel, 'xor', 2) -> { erlang, 'xor' };
|
||||
inline(?kernel, 'not', 1) -> { erlang, 'not' };
|
||||
inline(?kernel, '<', 2) -> { erlang, '<' };
|
||||
inline(?kernel, '>', 2) -> { erlang, '>' };
|
||||
inline(?kernel, '<=', 2) -> { erlang, '=<' };
|
||||
inline(?kernel, '>=', 2) -> { erlang, '>=' };
|
||||
inline(?kernel, '==', 2) -> { erlang, '==' };
|
||||
inline(?kernel, '!=', 2) -> { erlang, '/=' };
|
||||
inline(?kernel, '===', 2) -> { erlang, '=:=' };
|
||||
inline(?kernel, '!==', 2) -> { erlang, '=/=' };
|
||||
inline(?kernel, abs, 1) -> { erlang, abs };
|
||||
inline(?kernel, apply, 2) -> { erlang, apply };
|
||||
inline(?kernel, apply, 3) -> { erlang, apply };
|
||||
inline(?kernel, atom_to_list, 1) -> { erlang, atom_to_list };
|
||||
inline(?kernel, binary_part, 3) -> { erlang, binary_part };
|
||||
inline(?kernel, binary_to_float, 1) -> { erlang, binary_to_float };
|
||||
inline(?kernel, binary_to_float, 2) -> { erlang, binary_to_float };
|
||||
inline(?kernel, binary_to_integer, 1) -> { erlang, binary_to_integer };
|
||||
inline(?kernel, binary_to_integer, 2) -> { erlang, binary_to_integer };
|
||||
inline(?kernel, bit_size, 1) -> { erlang, bit_size };
|
||||
inline(?kernel, bitstring_to_list, 1) -> { erlang, bitstring_to_list };
|
||||
inline(?kernel, byte_size, 1) -> { erlang, byte_size };
|
||||
inline(?kernel, 'div', 2) -> { erlang, 'div' };
|
||||
inline(?kernel, exit, 1) -> { erlang, exit };
|
||||
inline(?kernel, float_to_binary, 1) -> { erlang, float_to_binary };
|
||||
inline(?kernel, float_to_list, 1) -> { erlang, float_to_list };
|
||||
inline(?kernel, hd, 1) -> { erlang, hd };
|
||||
inline(?kernel, integer_to_binary, 1) -> { erlang, integer_to_binary };
|
||||
inline(?kernel, integer_to_binary, 2) -> { erlang, integer_to_binary };
|
||||
inline(?kernel, integer_to_list, 1) -> { erlang, integer_to_list };
|
||||
inline(?kernel, integer_to_list, 2) -> { erlang, integer_to_list };
|
||||
inline(?kernel, iolist_size, 1) -> { erlang, iolist_size };
|
||||
inline(?kernel, iolist_to_binary, 1) -> { erlang, iolist_to_binary };
|
||||
inline(?kernel, is_atom, 1) -> { erlang, is_atom };
|
||||
inline(?kernel, is_binary, 1) -> { erlang, is_binary };
|
||||
inline(?kernel, is_bitstring, 1) -> { erlang, is_bitstring };
|
||||
inline(?kernel, is_boolean, 1) -> { erlang, is_boolean };
|
||||
inline(?kernel, is_float, 1) -> { erlang, is_float };
|
||||
inline(?kernel, is_function, 1) -> { erlang, is_function };
|
||||
inline(?kernel, is_function, 2) -> { erlang, is_function };
|
||||
inline(?kernel, is_integer, 1) -> { erlang, is_integer };
|
||||
inline(?kernel, is_list, 1) -> { erlang, is_list };
|
||||
inline(?kernel, is_map, 1) -> { erlang, is_map };
|
||||
inline(?kernel, is_number, 1) -> { erlang, is_number };
|
||||
inline(?kernel, is_pid, 1) -> { erlang, is_pid };
|
||||
inline(?kernel, is_port, 1) -> { erlang, is_port };
|
||||
inline(?kernel, is_reference, 1) -> { erlang, is_reference };
|
||||
inline(?kernel, is_tuple, 1) -> { erlang, is_tuple };
|
||||
inline(?kernel, length, 1) -> { erlang, length };
|
||||
inline(?kernel, list_to_atom, 1) -> { erlang, list_to_atom };
|
||||
inline(?kernel, list_to_bitstring, 1) -> { erlang, list_to_bitstring };
|
||||
inline(?kernel, list_to_existing_atom, 1) -> { erlang, list_to_existing_atom };
|
||||
inline(?kernel, list_to_float, 1) -> { erlang, list_to_float };
|
||||
inline(?kernel, list_to_integer, 1) -> { erlang, list_to_integer };
|
||||
inline(?kernel, list_to_integer, 2) -> { erlang, list_to_integer };
|
||||
inline(?kernel, list_to_tuple, 1) -> { erlang, list_to_tuple };
|
||||
inline(?kernel, make_ref, 0) -> { erlang, make_ref };
|
||||
inline(?kernel, map_size, 1) -> { erlang, map_size };
|
||||
inline(?kernel, max, 2) -> { erlang, max };
|
||||
inline(?kernel, min, 2) -> { erlang, min };
|
||||
inline(?kernel, node, 0) -> { erlang, node };
|
||||
inline(?kernel, node, 1) -> { erlang, node };
|
||||
inline(?kernel, 'rem', 2) -> { erlang, 'rem' };
|
||||
inline(?kernel, round, 1) -> { erlang, round };
|
||||
inline(?kernel, self, 0) -> { erlang, self };
|
||||
inline(?kernel, send, 2) -> { erlang, send };
|
||||
inline(?kernel, size, 1) -> { erlang, size };
|
||||
inline(?kernel, spawn, 1) -> { erlang, spawn };
|
||||
inline(?kernel, spawn, 3) -> { erlang, spawn };
|
||||
inline(?kernel, spawn_link, 1) -> { erlang, spawn_link };
|
||||
inline(?kernel, spawn_link, 3) -> { erlang, spawn_link };
|
||||
inline(?kernel, throw, 1) -> { erlang, throw };
|
||||
inline(?kernel, tl, 1) -> { erlang, tl };
|
||||
inline(?kernel, trunc, 1) -> { erlang, trunc };
|
||||
inline(?kernel, tuple_size, 1) -> { erlang, tuple_size };
|
||||
inline(?kernel, tuple_to_list, 1) -> { erlang, tuple_to_list };
|
||||
inline(?kernel, '+', 2) -> {erlang, '+'};
|
||||
inline(?kernel, '-', 2) -> {erlang, '-'};
|
||||
inline(?kernel, '+', 1) -> {erlang, '+'};
|
||||
inline(?kernel, '-', 1) -> {erlang, '-'};
|
||||
inline(?kernel, '*', 2) -> {erlang, '*'};
|
||||
inline(?kernel, '/', 2) -> {erlang, '/'};
|
||||
inline(?kernel, '++', 2) -> {erlang, '++'};
|
||||
inline(?kernel, '--', 2) -> {erlang, '--'};
|
||||
inline(?kernel, 'xor', 2) -> {erlang, 'xor'};
|
||||
inline(?kernel, 'not', 1) -> {erlang, 'not'};
|
||||
inline(?kernel, '<', 2) -> {erlang, '<'};
|
||||
inline(?kernel, '>', 2) -> {erlang, '>'};
|
||||
inline(?kernel, '<=', 2) -> {erlang, '=<'};
|
||||
inline(?kernel, '>=', 2) -> {erlang, '>='};
|
||||
inline(?kernel, '==', 2) -> {erlang, '=='};
|
||||
inline(?kernel, '!=', 2) -> {erlang, '/='};
|
||||
inline(?kernel, '===', 2) -> {erlang, '=:='};
|
||||
inline(?kernel, '!==', 2) -> {erlang, '=/='};
|
||||
inline(?kernel, abs, 1) -> {erlang, abs};
|
||||
inline(?kernel, apply, 2) -> {erlang, apply};
|
||||
inline(?kernel, apply, 3) -> {erlang, apply};
|
||||
inline(?kernel, atom_to_list, 1) -> {erlang, atom_to_list};
|
||||
inline(?kernel, binary_part, 3) -> {erlang, binary_part};
|
||||
inline(?kernel, binary_to_float, 1) -> {erlang, binary_to_float};
|
||||
inline(?kernel, binary_to_float, 2) -> {erlang, binary_to_float};
|
||||
inline(?kernel, binary_to_integer, 1) -> {erlang, binary_to_integer};
|
||||
inline(?kernel, binary_to_integer, 2) -> {erlang, binary_to_integer};
|
||||
inline(?kernel, bit_size, 1) -> {erlang, bit_size};
|
||||
inline(?kernel, bitstring_to_list, 1) -> {erlang, bitstring_to_list};
|
||||
inline(?kernel, byte_size, 1) -> {erlang, byte_size};
|
||||
inline(?kernel, 'div', 2) -> {erlang, 'div'};
|
||||
inline(?kernel, exit, 1) -> {erlang, exit};
|
||||
inline(?kernel, float_to_binary, 1) -> {erlang, float_to_binary};
|
||||
inline(?kernel, float_to_list, 1) -> {erlang, float_to_list};
|
||||
inline(?kernel, hd, 1) -> {erlang, hd};
|
||||
inline(?kernel, integer_to_binary, 1) -> {erlang, integer_to_binary};
|
||||
inline(?kernel, integer_to_binary, 2) -> {erlang, integer_to_binary};
|
||||
inline(?kernel, integer_to_list, 1) -> {erlang, integer_to_list};
|
||||
inline(?kernel, integer_to_list, 2) -> {erlang, integer_to_list};
|
||||
inline(?kernel, iodata_length, 1) -> {erlang, iolist_size};
|
||||
inline(?kernel, iodata_to_binary, 1) -> {erlang, iolist_to_binary};
|
||||
inline(?kernel, is_atom, 1) -> {erlang, is_atom};
|
||||
inline(?kernel, is_binary, 1) -> {erlang, is_binary};
|
||||
inline(?kernel, is_bitstring, 1) -> {erlang, is_bitstring};
|
||||
inline(?kernel, is_boolean, 1) -> {erlang, is_boolean};
|
||||
inline(?kernel, is_float, 1) -> {erlang, is_float};
|
||||
inline(?kernel, is_function, 1) -> {erlang, is_function};
|
||||
inline(?kernel, is_function, 2) -> {erlang, is_function};
|
||||
inline(?kernel, is_integer, 1) -> {erlang, is_integer};
|
||||
inline(?kernel, is_list, 1) -> {erlang, is_list};
|
||||
inline(?kernel, is_map, 1) -> {erlang, is_map};
|
||||
inline(?kernel, is_number, 1) -> {erlang, is_number};
|
||||
inline(?kernel, is_pid, 1) -> {erlang, is_pid};
|
||||
inline(?kernel, is_port, 1) -> {erlang, is_port};
|
||||
inline(?kernel, is_reference, 1) -> {erlang, is_reference};
|
||||
inline(?kernel, is_tuple, 1) -> {erlang, is_tuple};
|
||||
inline(?kernel, length, 1) -> {erlang, length};
|
||||
inline(?kernel, list_to_atom, 1) -> {erlang, list_to_atom};
|
||||
inline(?kernel, list_to_bitstring, 1) -> {erlang, list_to_bitstring};
|
||||
inline(?kernel, list_to_existing_atom, 1) -> {erlang, list_to_existing_atom};
|
||||
inline(?kernel, list_to_float, 1) -> {erlang, list_to_float};
|
||||
inline(?kernel, list_to_integer, 1) -> {erlang, list_to_integer};
|
||||
inline(?kernel, list_to_integer, 2) -> {erlang, list_to_integer};
|
||||
inline(?kernel, list_to_tuple, 1) -> {erlang, list_to_tuple};
|
||||
inline(?kernel, make_ref, 0) -> {erlang, make_ref};
|
||||
inline(?kernel, map_size, 1) -> {erlang, map_size};
|
||||
inline(?kernel, max, 2) -> {erlang, max};
|
||||
inline(?kernel, min, 2) -> {erlang, min};
|
||||
inline(?kernel, node, 0) -> {erlang, node};
|
||||
inline(?kernel, node, 1) -> {erlang, node};
|
||||
inline(?kernel, 'rem', 2) -> {erlang, 'rem'};
|
||||
inline(?kernel, round, 1) -> {erlang, round};
|
||||
inline(?kernel, self, 0) -> {erlang, self};
|
||||
inline(?kernel, send, 2) -> {erlang, send};
|
||||
inline(?kernel, size, 1) -> {erlang, size};
|
||||
inline(?kernel, spawn, 1) -> {erlang, spawn};
|
||||
inline(?kernel, spawn, 3) -> {erlang, spawn};
|
||||
inline(?kernel, spawn_link, 1) -> {erlang, spawn_link};
|
||||
inline(?kernel, spawn_link, 3) -> {erlang, spawn_link};
|
||||
inline(?kernel, spawn_monitor, 1) -> {erlang, spawn_monitor};
|
||||
inline(?kernel, spawn_monitor, 3) -> {erlang, spawn_monitor};
|
||||
inline(?kernel, throw, 1) -> {erlang, throw};
|
||||
inline(?kernel, tl, 1) -> {erlang, tl};
|
||||
inline(?kernel, trunc, 1) -> {erlang, trunc};
|
||||
inline(?kernel, tuple_size, 1) -> {erlang, tuple_size};
|
||||
inline(?kernel, tuple_to_list, 1) -> {erlang, tuple_to_list};
|
||||
|
||||
inline(?process, exit, 2) -> { erlang, exit };
|
||||
inline(?process, spawn, 1) -> { erlang, spawn };
|
||||
inline(?process, spawn, 2) -> { erlang, spawn_opt };
|
||||
inline(?process, spawn, 3) -> { erlang, spawn };
|
||||
inline(?process, spawn, 4) -> { erlang, spawn_opt };
|
||||
inline(?process, spawn_link, 1) -> { erlang, spawn_link };
|
||||
inline(?process, spawn_link, 3) -> { erlang, spawn_link };
|
||||
inline(?process, spawn_monitor, 1) -> { erlang, spawn_monitor };
|
||||
inline(?process, spawn_monitor, 3) -> { erlang, spawn_monitor };
|
||||
inline(?process, demonitor, 1) -> { erlang, demonitor };
|
||||
inline(?process, demonitor, 2) -> { erlang, demonitor };
|
||||
inline(?process, link, 1) -> { erlang, link };
|
||||
inline(?process, unlink, 1) -> { erlang, unlink };
|
||||
inline(?process, exit, 2) -> {erlang, exit};
|
||||
inline(?process, spawn, 2) -> {erlang, spawn_opt};
|
||||
inline(?process, spawn, 4) -> {erlang, spawn_opt};
|
||||
inline(?process, demonitor, 1) -> {erlang, demonitor};
|
||||
inline(?process, demonitor, 2) -> {erlang, demonitor};
|
||||
inline(?process, link, 1) -> {erlang, link};
|
||||
inline(?process, unlink, 1) -> {erlang, unlink};
|
||||
|
||||
inline(?node, spawn, 2) -> { erlang, spawn };
|
||||
inline(?node, spawn, 3) -> { erlang, spawn_opt };
|
||||
inline(?node, spawn, 4) -> { erlang, spawn };
|
||||
inline(?node, spawn, 5) -> { erlang, spawn_opt };
|
||||
inline(?node, spawn_link, 2) -> { erlang, spawn_link };
|
||||
inline(?node, spawn_link, 4) -> { erlang, spawn_link };
|
||||
inline(?node, spawn_monitor, 2) -> { erlang, spawn_monitor };
|
||||
inline(?node, spawn_monitor, 4) -> { erlang, spawn_monitor };
|
||||
inline(?node, spawn, 2) -> {erlang, spawn};
|
||||
inline(?node, spawn, 3) -> {erlang, spawn_opt};
|
||||
inline(?node, spawn, 4) -> {erlang, spawn};
|
||||
inline(?node, spawn, 5) -> {erlang, spawn_opt};
|
||||
inline(?node, spawn_link, 2) -> {erlang, spawn_link};
|
||||
inline(?node, spawn_link, 4) -> {erlang, spawn_link};
|
||||
inline(?node, spawn_monitor, 2) -> {erlang, spawn_monitor};
|
||||
inline(?node, spawn_monitor, 4) -> {erlang, spawn_monitor};
|
||||
|
||||
inline(?system, stacktrace, 0) -> { erlang, get_stacktrace };
|
||||
inline(?system, stacktrace, 0) -> {erlang, get_stacktrace};
|
||||
|
||||
inline(?map, keys, 1) -> {maps, keys};
|
||||
inline(?map, merge, 2) -> {maps, merge};
|
||||
inline(?map, size, 1) -> {maps, size};
|
||||
inline(?map, values, 1) -> {maps, values};
|
||||
inline(?map, to_list, 1) -> {maps, to_list};
|
||||
|
||||
inline(_, _, _) -> false.
|
||||
|
||||
@@ -1,46 +1,59 @@
|
||||
-module(elixir_env).
|
||||
-include("elixir.hrl").
|
||||
-export([ex_to_env/1, env_to_scope/1, env_to_scope_with_vars/2, env_to_ex/1]).
|
||||
-export([new/0, linify/1, env_to_scope/1, env_to_scope_with_vars/2]).
|
||||
-export([mergea/2, mergev/2, merge_vars/2, merge_opt_vars/2]).
|
||||
|
||||
%% Conversion in between #elixir_env, #elixir_scope and Macro.Env
|
||||
new() ->
|
||||
#{'__struct__' => 'Elixir.Macro.Env',
|
||||
module => nil, %% the current module
|
||||
file => <<"nofile">>, %% the current filename
|
||||
line => 1, %% the current line
|
||||
function => nil, %% the current function
|
||||
context => nil, %% can be match_vars, guards or nil
|
||||
requires => [], %% a set with modules required
|
||||
aliases => [], %% an orddict with aliases by new -> old names
|
||||
functions => [], %% a list with functions imported from module
|
||||
macros => [], %% a list with macros imported from module
|
||||
macro_aliases => [], %% keep aliases defined inside a macro
|
||||
context_modules => [], %% modules defined in the current context
|
||||
vars => [], %% a set of defined variables
|
||||
export_vars => nil, %% a set of variables to be exported in some constructs
|
||||
lexical_tracker => nil, %% holds the lexical tracker pid
|
||||
local => nil}. %% the module to delegate local functions to
|
||||
|
||||
env_to_ex({ Line, #elixir_env{} = Env }) ->
|
||||
erlang:setelement(1, Env#elixir_env{line=Line}, 'Elixir.Macro.Env').
|
||||
linify({Line, Env}) ->
|
||||
Env#{line := Line}.
|
||||
|
||||
ex_to_env(Env) when element(1, Env) == 'Elixir.Macro.Env' ->
|
||||
erlang:setelement(1, Env, elixir_env).
|
||||
env_to_scope(#{module := Module, file := File, function := Function, context := Context}) ->
|
||||
#elixir_scope{module=Module, file=File, function=Function, context=Context}.
|
||||
|
||||
env_to_scope(#elixir_env{module=Module,file=File,function=Function,context=Context}) ->
|
||||
#elixir_scope{module=Module,file=File,function=Function,context=Context}.
|
||||
|
||||
env_to_scope_with_vars(#elixir_env{} = Env, Vars) ->
|
||||
env_to_scope_with_vars(Env, Vars) ->
|
||||
(env_to_scope(Env))#elixir_scope{
|
||||
vars=orddict:from_list(Vars),
|
||||
counter=[{'_',length(Vars)}]
|
||||
}.
|
||||
}.
|
||||
|
||||
%% SCOPE MERGING
|
||||
|
||||
%% Receives two scopes and return a new scope based on the second
|
||||
%% with their variables merged.
|
||||
mergev(E1, E2) when is_list(E1) ->
|
||||
E2#elixir_env{
|
||||
vars=merge_vars(E1, E2#elixir_env.vars),
|
||||
export_vars=merge_opt_vars(E1, E2#elixir_env.export_vars)
|
||||
};
|
||||
E2#{
|
||||
vars := merge_vars(E1, ?m(E2, vars)),
|
||||
export_vars := merge_opt_vars(E1, ?m(E2, export_vars))
|
||||
};
|
||||
mergev(E1, E2) ->
|
||||
E2#elixir_env{
|
||||
vars=merge_vars(E1#elixir_env.vars, E2#elixir_env.vars),
|
||||
export_vars=merge_opt_vars(E1#elixir_env.export_vars, E2#elixir_env.export_vars)
|
||||
}.
|
||||
E2#{
|
||||
vars := merge_vars(?m(E1, vars), ?m(E2, vars)),
|
||||
export_vars := merge_opt_vars(?m(E1, export_vars), ?m(E2, export_vars))
|
||||
}.
|
||||
|
||||
%% Receives two scopes and return the later scope
|
||||
%% keeping the variables from the first (imports
|
||||
%% and everything else are passed forward).
|
||||
|
||||
mergea(E1, E2) ->
|
||||
E2#elixir_env{vars=E1#elixir_env.vars}.
|
||||
E2#{vars := ?m(E1, vars)}.
|
||||
|
||||
merge_vars(V1, V2) -> ordsets:union(V1, V2).
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@ warn(Warning) ->
|
||||
CompilerPid = get(elixir_compiler_pid),
|
||||
if
|
||||
CompilerPid =/= undefined ->
|
||||
elixir_code_server:cast({ register_warning, CompilerPid });
|
||||
elixir_code_server:cast({register_warning, CompilerPid});
|
||||
true -> false
|
||||
end,
|
||||
io:put_chars(standard_error, Warning).
|
||||
@@ -42,7 +42,7 @@ compile_error(Meta, File, Format, Args) when is_list(Format) ->
|
||||
|
||||
%% Raised on tokenizing/parsing
|
||||
|
||||
-spec parse_error(line_or_meta(), binary(), iolist() | atom(), [] | iolist()) -> no_return().
|
||||
-spec parse_error(line_or_meta(), binary(), binary(), binary()) -> no_return().
|
||||
|
||||
parse_error(Meta, File, Error, <<>>) ->
|
||||
Message = case Error of
|
||||
@@ -51,9 +51,17 @@ parse_error(Meta, File, Error, <<>>) ->
|
||||
end,
|
||||
raise(Meta, File, 'Elixir.TokenMissingError', Message);
|
||||
|
||||
%% Show a nicer message for missing end tokens
|
||||
parse_error(Meta, File, <<"syntax error before: ">>, <<"'end'">>) ->
|
||||
raise(Meta, File, 'Elixir.SyntaxError', <<"unexpected token: end">>);
|
||||
|
||||
%% Binaries are wrapped in [<<...>>], so we need to unwrap them
|
||||
parse_error(Meta, File, Error, <<"[<<", Token/binary>>) when is_binary(Error) ->
|
||||
Rest = binary_part(Token, 0, byte_size(Token) - 3),
|
||||
Message = <<Error / binary, Rest / binary >>,
|
||||
raise(Meta, File, 'Elixir.SyntaxError', Message);
|
||||
|
||||
%% Everything else is fine as is
|
||||
parse_error(Meta, File, Error, Token) when is_binary(Error), is_binary(Token) ->
|
||||
Message = <<Error / binary, Token / binary >>,
|
||||
raise(Meta, File, 'Elixir.SyntaxError', Message).
|
||||
@@ -68,26 +76,26 @@ deprecation(Meta, File, Message, Args) ->
|
||||
%% Handle warnings and errors (called during module compilation)
|
||||
|
||||
%% Ignore on bootstrap
|
||||
handle_file_warning(true, _File, { _Line, sys_core_fold, nomatch_guard }) -> [];
|
||||
handle_file_warning(true, _File, { _Line, sys_core_fold, { nomatch_shadow, _ } }) -> [];
|
||||
handle_file_warning(true, _File, {_Line, sys_core_fold, nomatch_guard}) -> [];
|
||||
handle_file_warning(true, _File, {_Line, sys_core_fold, {nomatch_shadow, _}}) -> [];
|
||||
|
||||
%% Ignore always
|
||||
handle_file_warning(_, _File, { _Line, sys_core_fold, useless_building }) -> [];
|
||||
handle_file_warning(_, _File, {_Line, sys_core_fold, useless_building}) -> [];
|
||||
|
||||
%% This is an Erlang bug, it considers { tuple, _ }.call to always fail
|
||||
handle_file_warning(_, _File, { _Line, v3_kernel, bad_call }) -> [];
|
||||
%% This is an Erlang bug, it considers {tuple, _}.call to always fail
|
||||
handle_file_warning(_, _File, {_Line, v3_kernel, bad_call}) -> [];
|
||||
|
||||
%% We handle unused local warnings ourselves
|
||||
handle_file_warning(_, _File, { _Line, erl_lint, { unused_function, _ } }) -> [];
|
||||
handle_file_warning(_, _File, {_Line, erl_lint, {unused_function, _}}) -> [];
|
||||
|
||||
%% Make no_effect clauses pretty
|
||||
handle_file_warning(_, File, { Line, sys_core_fold, { no_effect, { erlang, F, A } } }) ->
|
||||
{ Fmt, Args } = case erl_internal:comp_op(F, A) of
|
||||
true -> { "use of operator ~ts has no effect", [translate_comp_op(F)] };
|
||||
handle_file_warning(_, File, {Line, sys_core_fold, {no_effect, {erlang, F, A}}}) ->
|
||||
{Fmt, Args} = case erl_internal:comp_op(F, A) of
|
||||
true -> {"use of operator ~ts has no effect", [translate_comp_op(F)]};
|
||||
false ->
|
||||
case erl_internal:bif(F, A) of
|
||||
false -> { "the call to :erlang.~ts/~B has no effect", [F,A] };
|
||||
true -> { "the call to ~ts/~B has no effect", [F,A] }
|
||||
false -> {"the call to :erlang.~ts/~B has no effect", [F,A]};
|
||||
true -> {"the call to ~ts/~B has no effect", [F,A]}
|
||||
end
|
||||
end,
|
||||
Message = io_lib:format(Fmt, Args),
|
||||
@@ -95,10 +103,10 @@ handle_file_warning(_, File, { Line, sys_core_fold, { no_effect, { erlang, F, A
|
||||
|
||||
%% Rewrite undefined behaviour to check for protocols
|
||||
handle_file_warning(_, File, {Line,erl_lint,{undefined_behaviour_func,{Fun,Arity},Module}}) ->
|
||||
{ DefKind, Def, DefArity } =
|
||||
{DefKind, Def, DefArity} =
|
||||
case atom_to_list(Fun) of
|
||||
"MACRO-" ++ Rest -> { macro, list_to_atom(Rest), Arity - 1 };
|
||||
_ -> { function, Fun, Arity }
|
||||
"MACRO-" ++ Rest -> {macro, list_to_atom(Rest), Arity - 1};
|
||||
_ -> {function, Fun, Arity}
|
||||
end,
|
||||
|
||||
Kind = protocol_or_behaviour(Module),
|
||||
@@ -124,7 +132,7 @@ handle_file_warning(_, _File, {_Line,erl_lint,{shadowed_var,_Var,_Where}}) ->
|
||||
|
||||
%% Properly format other unused vars
|
||||
handle_file_warning(_, File, {Line,erl_lint,{unused_var,Var}}) ->
|
||||
Message = format_error(erl_lint, { unused_var, format_var(Var) }),
|
||||
Message = format_error(erl_lint, {unused_var, format_var(Var)}),
|
||||
warn(Line, File, Message);
|
||||
|
||||
%% Default behaviour
|
||||
@@ -195,10 +203,10 @@ protocol_or_behaviour(Module) ->
|
||||
|
||||
is_protocol(Module) ->
|
||||
case code:ensure_loaded(Module) of
|
||||
{ module, _ } ->
|
||||
{module, _} ->
|
||||
erlang:function_exported(Module, '__protocol__', 1) andalso
|
||||
Module:'__protocol__'(name) == Module;
|
||||
{ error, _ } ->
|
||||
{error, _} ->
|
||||
false
|
||||
end.
|
||||
|
||||
|
||||
+219
-218
@@ -5,233 +5,233 @@
|
||||
|
||||
%% =
|
||||
|
||||
expand({ '=', Meta, [Left, Right] }, E) ->
|
||||
expand({'=', Meta, [Left, Right]}, E) ->
|
||||
assert_no_guard_scope(Meta, '=', E),
|
||||
{ ERight, ER } = expand(Right, E),
|
||||
{ ELeft, EL } = elixir_exp_clauses:match(fun expand/2, Left, E),
|
||||
{ { '=', Meta, [ELeft, ERight] }, elixir_env:mergev(EL, ER) };
|
||||
{ERight, ER} = expand(Right, E),
|
||||
{ELeft, EL} = elixir_exp_clauses:match(fun expand/2, Left, E),
|
||||
{{'=', Meta, [ELeft, ERight]}, elixir_env:mergev(EL, ER)};
|
||||
|
||||
%% Literal operators
|
||||
|
||||
expand({ '{}', Meta, Args }, E) ->
|
||||
{ EArgs, EA } = expand_args(Args, E),
|
||||
{ { '{}', Meta, EArgs }, EA };
|
||||
expand({'{}', Meta, Args}, E) ->
|
||||
{EArgs, EA} = expand_args(Args, E),
|
||||
{{'{}', Meta, EArgs}, EA};
|
||||
|
||||
expand({ '%{}', Meta, Args }, E) ->
|
||||
expand({'%{}', Meta, Args}, E) ->
|
||||
elixir_map:expand_map(Meta, Args, E);
|
||||
|
||||
expand({ '%', Meta, [Left, Right] }, E) ->
|
||||
expand({'%', Meta, [Left, Right]}, E) ->
|
||||
elixir_map:expand_struct(Meta, Left, Right, E);
|
||||
|
||||
expand({ '<<>>', Meta, Args }, E) ->
|
||||
expand({'<<>>', Meta, Args}, E) ->
|
||||
elixir_bitstring:expand(Meta, Args, E);
|
||||
|
||||
%% Other operators
|
||||
|
||||
expand({ '__op__', Meta, [_, _] = Args }, E) ->
|
||||
{ EArgs, EA } = expand_args(Args, E),
|
||||
{ { '__op__', Meta, EArgs }, EA };
|
||||
expand({'__op__', Meta, [_, _] = Args}, E) ->
|
||||
{EArgs, EA} = expand_args(Args, E),
|
||||
{{'__op__', Meta, EArgs}, EA};
|
||||
|
||||
expand({ '__op__', Meta, [_, _, _] = Args }, E) ->
|
||||
{ EArgs, EA } = expand_args(Args, E),
|
||||
{ { '__op__', Meta, EArgs }, EA };
|
||||
expand({'__op__', Meta, [_, _, _] = Args}, E) ->
|
||||
{EArgs, EA} = expand_args(Args, E),
|
||||
{{'__op__', Meta, EArgs}, EA};
|
||||
|
||||
expand({ '->', Meta, _Args }, E) ->
|
||||
compile_error(Meta, E#elixir_env.file, "unhandled operator ->");
|
||||
expand({'->', Meta, _Args}, E) ->
|
||||
compile_error(Meta, ?m(E, file), "unhandled operator ->");
|
||||
|
||||
%% __block__
|
||||
|
||||
expand({ '__block__', _Meta, [] }, E) ->
|
||||
{ nil, E };
|
||||
expand({ '__block__', _Meta, [Arg] }, E) ->
|
||||
expand({'__block__', _Meta, []}, E) ->
|
||||
{nil, E};
|
||||
expand({'__block__', _Meta, [Arg]}, E) ->
|
||||
expand(Arg, E);
|
||||
expand({ '__block__', Meta, Args }, E) when is_list(Args) ->
|
||||
{ EArgs, EA } = expand_many(Args, E),
|
||||
{ { '__block__', Meta, EArgs }, EA };
|
||||
expand({'__block__', Meta, Args}, E) when is_list(Args) ->
|
||||
{EArgs, EA} = expand_many(Args, E),
|
||||
{{'__block__', Meta, EArgs}, EA};
|
||||
|
||||
%% __aliases__
|
||||
|
||||
expand({ '__aliases__', _, _ } = Alias, E) ->
|
||||
case elixir_aliases:expand(Alias, E#elixir_env.aliases,
|
||||
E#elixir_env.macro_aliases, E#elixir_env.lexical_tracker) of
|
||||
expand({'__aliases__', _, _} = Alias, E) ->
|
||||
case elixir_aliases:expand(Alias, ?m(E, aliases),
|
||||
?m(E, macro_aliases), ?m(E, lexical_tracker)) of
|
||||
Receiver when is_atom(Receiver) ->
|
||||
elixir_lexical:record_remote(Receiver, E#elixir_env.lexical_tracker),
|
||||
{ Receiver, E };
|
||||
elixir_lexical:record_remote(Receiver, ?m(E, lexical_tracker)),
|
||||
{Receiver, E};
|
||||
Aliases ->
|
||||
{ EAliases, EA } = expand_args(Aliases, E),
|
||||
{EAliases, EA} = expand_args(Aliases, E),
|
||||
|
||||
case lists:all(fun is_atom/1, EAliases) of
|
||||
true ->
|
||||
Receiver = elixir_aliases:concat(EAliases),
|
||||
elixir_lexical:record_remote(Receiver, E#elixir_env.lexical_tracker),
|
||||
{ Receiver, EA };
|
||||
elixir_lexical:record_remote(Receiver, ?m(E, lexical_tracker)),
|
||||
{Receiver, EA};
|
||||
false ->
|
||||
{ { { '.', [], [elixir_aliases, concat] }, [], [EAliases] }, EA }
|
||||
{{{'.', [], [elixir_aliases, concat]}, [], [EAliases]}, EA}
|
||||
end
|
||||
end;
|
||||
|
||||
%% alias
|
||||
|
||||
expand({ alias, Meta, [Ref] }, E) ->
|
||||
expand({ alias, Meta, [Ref,[]] }, E);
|
||||
expand({ alias, Meta, [Ref, KV] }, E) ->
|
||||
expand({alias, Meta, [Ref]}, E) ->
|
||||
expand({alias, Meta, [Ref,[]]}, E);
|
||||
expand({alias, Meta, [Ref, KV]}, E) ->
|
||||
assert_no_match_or_guard_scope(Meta, alias, E),
|
||||
{ ERef, ER } = expand(Ref, E),
|
||||
{ EKV, ET } = expand_opts(Meta, alias, [as, warn], no_alias_opts(KV), ER),
|
||||
{ERef, ER} = expand(Ref, E),
|
||||
{EKV, ET} = expand_opts(Meta, alias, [as, warn], no_alias_opts(KV), ER),
|
||||
|
||||
if
|
||||
is_atom(ERef) ->
|
||||
{ { alias, Meta, [ERef, EKV] },
|
||||
expand_alias(Meta, true, ERef, EKV, ET) };
|
||||
{{alias, Meta, [ERef, EKV]},
|
||||
expand_alias(Meta, true, ERef, EKV, ET)};
|
||||
true ->
|
||||
compile_error(Meta, E#elixir_env.file,
|
||||
compile_error(Meta, ?m(E, file),
|
||||
"invalid argument for alias, expected a compile time atom or alias, got: ~ts",
|
||||
['Elixir.Kernel':inspect(ERef)])
|
||||
end;
|
||||
|
||||
expand({ require, Meta, [Ref] }, E) ->
|
||||
expand({ require, Meta, [Ref, []] }, E);
|
||||
expand({ require, Meta, [Ref, KV] }, E) ->
|
||||
expand({require, Meta, [Ref]}, E) ->
|
||||
expand({require, Meta, [Ref, []]}, E);
|
||||
expand({require, Meta, [Ref, KV]}, E) ->
|
||||
assert_no_match_or_guard_scope(Meta, require, E),
|
||||
|
||||
{ ERef, ER } = expand(Ref, E),
|
||||
{ EKV, ET } = expand_opts(Meta, require, [as, warn], no_alias_opts(KV), ER),
|
||||
{ERef, ER} = expand(Ref, E),
|
||||
{EKV, ET} = expand_opts(Meta, require, [as, warn], no_alias_opts(KV), ER),
|
||||
|
||||
if
|
||||
is_atom(ERef) ->
|
||||
elixir_aliases:ensure_loaded(Meta, ERef, ET),
|
||||
{ { require, Meta, [ERef, EKV] },
|
||||
expand_require(Meta, ERef, EKV, ET) };
|
||||
{{require, Meta, [ERef, EKV]},
|
||||
expand_require(Meta, ERef, EKV, ET)};
|
||||
true ->
|
||||
compile_error(Meta, E#elixir_env.file,
|
||||
compile_error(Meta, ?m(E, file),
|
||||
"invalid argument for require, expected a compile time atom or alias, got: ~ts",
|
||||
['Elixir.Kernel':inspect(ERef)])
|
||||
end;
|
||||
|
||||
expand({ import, Meta, [Left] }, E) ->
|
||||
expand({ import, Meta, [Left, []]}, E);
|
||||
expand({import, Meta, [Left]}, E) ->
|
||||
expand({import, Meta, [Left, []]}, E);
|
||||
|
||||
expand({ import, Meta, [Ref, KV] }, E) ->
|
||||
expand({import, Meta, [Ref, KV]}, E) ->
|
||||
assert_no_match_or_guard_scope(Meta, import, E),
|
||||
{ ERef, ER } = expand(Ref, E),
|
||||
{ EKV, ET } = expand_opts(Meta, import, [only, except, warn], KV, ER),
|
||||
{ERef, ER} = expand(Ref, E),
|
||||
{EKV, ET} = expand_opts(Meta, import, [only, except, warn], KV, ER),
|
||||
|
||||
if
|
||||
is_atom(ERef) ->
|
||||
elixir_aliases:ensure_loaded(Meta, ERef, ET),
|
||||
{ Functions, Macros } = elixir_import:import(Meta, ERef, EKV, ET),
|
||||
{ { import, Meta, [ERef, EKV] },
|
||||
expand_require(Meta, ERef, EKV, ET#elixir_env{functions=Functions, macros=Macros}) };
|
||||
{Functions, Macros} = elixir_import:import(Meta, ERef, EKV, ET),
|
||||
{{import, Meta, [ERef, EKV]},
|
||||
expand_require(Meta, ERef, EKV, ET#{functions := Functions, macros := Macros})};
|
||||
true ->
|
||||
compile_error(Meta, E#elixir_env.file,
|
||||
compile_error(Meta, ?m(E, file),
|
||||
"invalid argument for import, expected a compile time atom or alias, got: ~ts",
|
||||
['Elixir.Kernel':inspect(ERef)])
|
||||
end;
|
||||
|
||||
%% Pseudo vars
|
||||
|
||||
expand({ '__MODULE__', _, Atom }, E) when is_atom(Atom) ->
|
||||
{ E#elixir_env.module, E };
|
||||
expand({ '__DIR__', _, Atom }, E) when is_atom(Atom) ->
|
||||
{ filename:dirname(E#elixir_env.file), E };
|
||||
expand({ '__CALLER__', _, Atom } = Caller, E) when is_atom(Atom) ->
|
||||
{ Caller, E };
|
||||
expand({ '__ENV__', Meta, Atom }, E) when is_atom(Atom) ->
|
||||
Env = elixir_env:env_to_ex({ ?line(Meta), E }),
|
||||
{ { '{}', [], tuple_to_list(Env) }, E };
|
||||
expand({ { '.', DotMeta, [{ '__ENV__', Meta, Atom }, Field] }, CallMeta, [] }, E) when is_atom(Atom), is_atom(Field) ->
|
||||
Env = elixir_env:env_to_ex({ ?line(Meta), E }),
|
||||
case erlang:function_exported('Elixir.Macro.Env', Field, 1) of
|
||||
true -> { Env:Field(), E };
|
||||
false -> { { { '.', DotMeta, [{ '{}', [], tuple_to_list(Env) }, Field] }, CallMeta, [] }, E }
|
||||
expand({'__MODULE__', _, Atom}, E) when is_atom(Atom) ->
|
||||
{?m(E, module), E};
|
||||
expand({'__DIR__', _, Atom}, E) when is_atom(Atom) ->
|
||||
{filename:dirname(?m(E, file)), E};
|
||||
expand({'__CALLER__', _, Atom} = Caller, E) when is_atom(Atom) ->
|
||||
{Caller, E};
|
||||
expand({'__ENV__', Meta, Atom}, E) when is_atom(Atom) ->
|
||||
Env = elixir_env:linify({?line(Meta), E}),
|
||||
{{'%{}', [], maps:to_list(Env)}, E};
|
||||
expand({{'.', DotMeta, [{'__ENV__', Meta, Atom}, Field]}, CallMeta, []}, E) when is_atom(Atom), is_atom(Field) ->
|
||||
Env = elixir_env:linify({?line(Meta), E}),
|
||||
case maps:is_key(Field, Env) of
|
||||
true -> {maps:get(Field, Env), E};
|
||||
false -> {{{'.', DotMeta, [{'%{}', [], maps:to_list(Env)}, Field]}, CallMeta, []}, E}
|
||||
end;
|
||||
|
||||
%% Quote
|
||||
|
||||
expand({ Unquote, Meta, [_] }, E) when Unquote == unquote; Unquote == unquote_splicing ->
|
||||
compile_error(Meta, E#elixir_env.file, "~p called outside quote", [Unquote]);
|
||||
expand({Unquote, Meta, [_]}, E) when Unquote == unquote; Unquote == unquote_splicing ->
|
||||
compile_error(Meta, ?m(E, file), "~p called outside quote", [Unquote]);
|
||||
|
||||
expand({ quote, Meta, [Opts] }, E) when is_list(Opts) ->
|
||||
expand({quote, Meta, [Opts]}, E) when is_list(Opts) ->
|
||||
case lists:keyfind(do, 1, Opts) of
|
||||
{ do, Do } ->
|
||||
expand({ quote, Meta, [lists:keydelete(do, 1, Opts), [{do,Do}]] }, E);
|
||||
{do, Do} ->
|
||||
expand({quote, Meta, [lists:keydelete(do, 1, Opts), [{do,Do}]]}, E);
|
||||
false ->
|
||||
compile_error(Meta, E#elixir_env.file, "missing do keyword in quote")
|
||||
compile_error(Meta, ?m(E, file), "missing do keyword in quote")
|
||||
end;
|
||||
|
||||
expand({ quote, Meta, [_] }, E) ->
|
||||
compile_error(Meta, E#elixir_env.file, "invalid arguments for quote");
|
||||
expand({quote, Meta, [_]}, E) ->
|
||||
compile_error(Meta, ?m(E, file), "invalid arguments for quote");
|
||||
|
||||
expand({ quote, Meta, [KV, Do] }, E) when is_list(Do) ->
|
||||
expand({quote, Meta, [KV, Do]}, E) when is_list(Do) ->
|
||||
Exprs =
|
||||
case lists:keyfind(do, 1, Do) of
|
||||
{ do, Expr } -> Expr;
|
||||
{do, Expr} -> Expr;
|
||||
false -> compile_error(Meta, E#elixir_scope.file, "missing do keyword in quote")
|
||||
end,
|
||||
|
||||
ValidOpts = [hygiene, context, var_context, location, line, unquote, bind_quoted],
|
||||
{ EKV, ET } = expand_opts(Meta, quote, ValidOpts, KV, E),
|
||||
{EKV, ET} = expand_opts(Meta, quote, ValidOpts, KV, E),
|
||||
|
||||
Hygiene = case lists:keyfind(hygiene, 1, EKV) of
|
||||
{ hygiene, List } when is_list(List) ->
|
||||
{hygiene, List} when is_list(List) ->
|
||||
List;
|
||||
false ->
|
||||
[]
|
||||
end,
|
||||
|
||||
Context = case lists:keyfind(context, 1, EKV) of
|
||||
{ context, Atom } when is_atom(Atom) ->
|
||||
{context, Atom} when is_atom(Atom) ->
|
||||
Atom;
|
||||
{ context, Ctx } ->
|
||||
compile_error(Meta, E#elixir_env.file, "invalid :context for quote, "
|
||||
{context, Ctx} ->
|
||||
compile_error(Meta, ?m(E, file), "invalid :context for quote, "
|
||||
"expected a compile time atom or alias, got: ~ts", ['Elixir.Kernel':inspect(Ctx)]);
|
||||
false ->
|
||||
case E#elixir_env.module of
|
||||
case ?m(E, module) of
|
||||
nil -> 'Elixir';
|
||||
Mod -> Mod
|
||||
end
|
||||
end,
|
||||
|
||||
Vars = lists:keyfind(vars, 1, Hygiene) /= { vars, false },
|
||||
Aliases = lists:keyfind(aliases, 1, Hygiene) /= { aliases, false },
|
||||
Imports = lists:keyfind(imports, 1, Hygiene) /= { imports, false },
|
||||
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 },
|
||||
Keep = lists:keyfind(location, 1, EKV) == {location, keep},
|
||||
Line = proplists:get_value(line, EKV, false),
|
||||
|
||||
{ Binding, DefaultUnquote } = case lists:keyfind(bind_quoted, 1, EKV) of
|
||||
{ bind_quoted, BQ } -> { BQ, false };
|
||||
false -> { nil, true }
|
||||
{Binding, DefaultUnquote} = case lists:keyfind(bind_quoted, 1, EKV) of
|
||||
{bind_quoted, BQ} -> {BQ, false};
|
||||
false -> {nil, true}
|
||||
end,
|
||||
|
||||
Unquote = case lists:keyfind(unquote, 1, EKV) of
|
||||
{ unquote, Bool } when is_boolean(Bool) -> Bool;
|
||||
{unquote, Bool} when is_boolean(Bool) -> Bool;
|
||||
false -> DefaultUnquote
|
||||
end,
|
||||
|
||||
Q = #elixir_quote{vars_hygiene=Vars, line=Line, keep=Keep, unquote=Unquote,
|
||||
aliases_hygiene=Aliases, imports_hygiene=Imports, context=Context},
|
||||
|
||||
{ Quoted, _Q } = elixir_quote:quote(Exprs, Binding, Q, ET),
|
||||
{Quoted, _Q} = elixir_quote:quote(Exprs, Binding, Q, ET),
|
||||
expand(Quoted, ET);
|
||||
|
||||
expand({ quote, Meta, [_, _] }, E) ->
|
||||
compile_error(Meta, E#elixir_env.file, "invalid arguments for quote");
|
||||
expand({quote, Meta, [_, _]}, E) ->
|
||||
compile_error(Meta, ?m(E, file), "invalid arguments for quote");
|
||||
|
||||
%% Functions
|
||||
|
||||
expand({ '&', _, [Arg] } = Original, E) when is_integer(Arg) ->
|
||||
{ Original, E };
|
||||
expand({ '&', Meta, [Arg] }, E) ->
|
||||
expand({'&', _, [Arg]} = Original, E) when is_integer(Arg) ->
|
||||
{Original, E};
|
||||
expand({'&', Meta, [Arg]}, E) ->
|
||||
assert_no_match_or_guard_scope(Meta, '&', E),
|
||||
case elixir_fn:capture(Meta, Arg, E) of
|
||||
{ local, Fun, Arity } ->
|
||||
{ { '&', Meta, [{ '/', [], [{ Fun, [], nil }, Arity] }] }, E };
|
||||
{ expanded, Expr, EE } ->
|
||||
{local, Fun, Arity} ->
|
||||
{{'&', Meta, [{'/', [], [{Fun, [], nil}, Arity]}]}, E};
|
||||
{expanded, Expr, EE} ->
|
||||
expand(Expr, EE)
|
||||
end;
|
||||
|
||||
expand({ fn, Meta, Pairs }, E) ->
|
||||
expand({fn, Meta, Pairs}, E) ->
|
||||
assert_no_match_or_guard_scope(Meta, fn, E),
|
||||
elixir_fn:expand(Meta, Pairs, E);
|
||||
|
||||
@@ -239,82 +239,83 @@ expand({ fn, Meta, Pairs }, E) ->
|
||||
|
||||
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 };
|
||||
{EExpr, EE} = expand(Expr, E),
|
||||
{EClauses, EC} = elixir_exp_clauses:'case'(Meta, KV, EE),
|
||||
{{'case', Meta, [EExpr, EClauses]}, EC};
|
||||
|
||||
expand({'receive', Meta, [KV]}, E) ->
|
||||
assert_no_match_or_guard_scope(Meta, 'receive', E),
|
||||
{ EClauses, EC } = elixir_exp_clauses:'receive'(Meta, KV, E),
|
||||
{ { 'receive', Meta, [EClauses] }, EC };
|
||||
{EClauses, EC} = elixir_exp_clauses:'receive'(Meta, KV, E),
|
||||
{{'receive', Meta, [EClauses]}, EC};
|
||||
|
||||
expand({'try', Meta, [KV]}, E) ->
|
||||
assert_no_match_or_guard_scope(Meta, 'try', E),
|
||||
{ EClauses, EC } = elixir_exp_clauses:'try'(Meta, KV, E),
|
||||
{ { 'try', Meta, [EClauses] }, EC };
|
||||
{EClauses, EC} = elixir_exp_clauses:'try'(Meta, KV, E),
|
||||
{{'try', Meta, [EClauses]}, EC};
|
||||
|
||||
%% Comprehensions
|
||||
|
||||
expand({ Kind, Meta, Args }, E) when is_list(Args), (Kind == lc) orelse (Kind == bc) ->
|
||||
expand({Kind, Meta, Args}, E) when is_list(Args), (Kind == lc) orelse (Kind == bc) ->
|
||||
elixir_errors:deprecation(Meta, ?m(E, file), "~ts is deprecated, please use for comprehensions instead", [Kind]),
|
||||
expand_comprehension(Meta, Kind, Args, E);
|
||||
|
||||
expand({ for, Meta, Args }, E) when is_list(Args) ->
|
||||
expand({for, Meta, Args}, E) when is_list(Args) ->
|
||||
elixir_for:expand(Meta, Args, E);
|
||||
|
||||
%% Super
|
||||
|
||||
expand({ super, Meta, Args }, E) when is_list(Args) ->
|
||||
expand({super, Meta, Args}, E) when is_list(Args) ->
|
||||
assert_no_match_or_guard_scope(Meta, super, E),
|
||||
{ EArgs, EA } = expand_args(Args, E),
|
||||
{ { super, Meta, EArgs }, EA };
|
||||
{EArgs, EA} = expand_args(Args, E),
|
||||
{{super, Meta, EArgs}, EA};
|
||||
|
||||
%% Vars
|
||||
|
||||
expand({ '^', Meta, [Arg] }, #elixir_env{context=match} = E) ->
|
||||
expand({'^', Meta, [Arg]}, #{context := match} = E) ->
|
||||
case expand(Arg, E) of
|
||||
{ { Name, _, Kind } = EArg, EA } when is_atom(Name), is_atom(Kind) ->
|
||||
{ { '^', Meta, [EArg] }, EA };
|
||||
{{Name, _, Kind} = EArg, EA} when is_atom(Name), is_atom(Kind) ->
|
||||
{{'^', Meta, [EArg]}, EA};
|
||||
_ ->
|
||||
Msg = "invalid argument for unary operator ^, expected an existing variable, got: ^~ts",
|
||||
compile_error(Meta, E#elixir_env.file, Msg, ['Elixir.Macro':to_string(Arg)])
|
||||
compile_error(Meta, ?m(E, file), Msg, ['Elixir.Macro':to_string(Arg)])
|
||||
end;
|
||||
expand({ '^', Meta, [Arg] }, E) ->
|
||||
compile_error(Meta, E#elixir_env.file,
|
||||
expand({'^', Meta, [Arg]}, E) ->
|
||||
compile_error(Meta, ?m(E, file),
|
||||
"cannot use ^~ts outside of match clauses", ['Elixir.Macro':to_string(Arg)]);
|
||||
|
||||
expand({ '_', _, Kind } = Var, E) when is_atom(Kind) ->
|
||||
{ Var, E };
|
||||
expand({ Name, Meta, Kind } = Var, #elixir_env{context=match, export_vars=Export} = E) when is_atom(Name), is_atom(Kind) ->
|
||||
Pair = { Name, var_kind(Meta, Kind) },
|
||||
NewVars = ordsets:add_element(Pair, E#elixir_env.vars),
|
||||
NewExport = case (Export /= nil) andalso (lists:keyfind(export, 1, Meta) /= { export, false }) of
|
||||
expand({'_', _, Kind} = Var, E) when is_atom(Kind) ->
|
||||
{Var, E};
|
||||
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
|
||||
true -> ordsets:add_element(Pair, Export);
|
||||
false -> Export
|
||||
end,
|
||||
{ Var, E#elixir_env{vars=NewVars, export_vars=NewExport} };
|
||||
expand({ Name, Meta, Kind } = Var, #elixir_env{vars=Vars} = E) when is_atom(Name), is_atom(Kind) ->
|
||||
case lists:member({ Name, var_kind(Meta, Kind) }, Vars) of
|
||||
{Var, E#{vars := NewVars, export_vars := NewExport}};
|
||||
expand({Name, Meta, Kind} = Var, #{vars := Vars} = E) when is_atom(Name), is_atom(Kind) ->
|
||||
case lists:member({Name, var_kind(Meta, Kind)}, Vars) of
|
||||
true ->
|
||||
{ Var, E };
|
||||
{Var, E};
|
||||
false ->
|
||||
VarMeta = lists:keyfind(var, 1, Meta),
|
||||
if
|
||||
VarMeta == { var, true } ->
|
||||
VarMeta == {var, true} ->
|
||||
Extra = case Kind of
|
||||
nil -> "";
|
||||
_ -> io_lib:format(" (context ~ts)", [elixir_aliases:inspect(Kind)])
|
||||
end,
|
||||
|
||||
compile_error(Meta, E#elixir_env.file, "expected var ~ts~ts to expand to an existing "
|
||||
compile_error(Meta, ?m(E, file), "expected var ~ts~ts to expand to an existing "
|
||||
"variable or be a part of a match", [Name, Extra]);
|
||||
true ->
|
||||
expand({ Name, Meta, [] }, E)
|
||||
expand({Name, Meta, []}, E)
|
||||
end
|
||||
end;
|
||||
|
||||
%% Local calls
|
||||
|
||||
expand({ Atom, Meta, Args }, E) when is_atom(Atom), is_list(Meta), is_list(Args) ->
|
||||
expand({Atom, Meta, Args}, E) when is_atom(Atom), is_list(Meta), is_list(Args) ->
|
||||
assert_no_ambiguous_op(Atom, Meta, Args, E),
|
||||
|
||||
elixir_dispatch:dispatch_import(Meta, Atom, Args, E, fun() ->
|
||||
@@ -323,9 +324,9 @@ expand({ Atom, Meta, Args }, E) when is_atom(Atom), is_list(Meta), is_list(Args)
|
||||
|
||||
%% Remote calls
|
||||
|
||||
expand({ { '.', DotMeta, [Left, Right] }, Meta, Args }, E)
|
||||
expand({{'.', DotMeta, [Left, Right]}, Meta, Args}, E)
|
||||
when (is_tuple(Left) orelse is_atom(Left)), is_atom(Right), is_list(Meta), is_list(Args) ->
|
||||
{ ELeft, EL } = expand(Left, E),
|
||||
{ELeft, EL} = expand(Left, E),
|
||||
|
||||
elixir_dispatch:dispatch_require(Meta, ELeft, Right, Args, EL, fun(AR, AF, AA) ->
|
||||
expand_remote(AR, DotMeta, AF, Meta, AA, E, EL)
|
||||
@@ -333,66 +334,66 @@ expand({ { '.', DotMeta, [Left, Right] }, Meta, Args }, E)
|
||||
|
||||
%% Anonymous calls
|
||||
|
||||
expand({ { '.', DotMeta, [Expr] }, Meta, Args }, E) when is_list(Args) ->
|
||||
{ EExpr, EE } = expand(Expr, E),
|
||||
expand({{'.', DotMeta, [Expr]}, Meta, Args}, E) when is_list(Args) ->
|
||||
{EExpr, EE} = expand(Expr, E),
|
||||
if
|
||||
is_atom(EExpr) ->
|
||||
compile_error(Meta, E#elixir_env.file, "invalid function call :~ts.()", [EExpr]);
|
||||
compile_error(Meta, ?m(E, file), "invalid function call :~ts.()", [EExpr]);
|
||||
true ->
|
||||
{ EArgs, EA } = expand_args(Args, elixir_env:mergea(E, EE)),
|
||||
{ { { '.', DotMeta, [EExpr] }, Meta, EArgs }, elixir_env:mergev(EE, EA) }
|
||||
{EArgs, EA} = expand_args(Args, elixir_env:mergea(E, EE)),
|
||||
{{{'.', DotMeta, [EExpr]}, Meta, EArgs}, elixir_env:mergev(EE, EA)}
|
||||
end;
|
||||
|
||||
%% Invalid calls
|
||||
|
||||
expand({ _, Meta, Args } = Invalid, E) when is_list(Meta) and is_list(Args) ->
|
||||
compile_error(Meta, E#elixir_env.file, "invalid call ~ts",
|
||||
expand({_, Meta, Args} = Invalid, E) when is_list(Meta) and is_list(Args) ->
|
||||
compile_error(Meta, ?m(E, file), "invalid call ~ts",
|
||||
['Elixir.Macro':to_string(Invalid)]);
|
||||
|
||||
expand({ _, _, _ } = Tuple, E) ->
|
||||
compile_error([{line,0}], E#elixir_env.file, "invalid quoted expression: ~ts",
|
||||
expand({_, _, _} = Tuple, E) ->
|
||||
compile_error([{line,0}], ?m(E, file), "invalid quoted expression: ~ts",
|
||||
['Elixir.Kernel':inspect(Tuple, [{records,false}])]);
|
||||
|
||||
%% Literals
|
||||
|
||||
expand({ Left, Right }, E) ->
|
||||
{ [ELeft, ERight], EE } = expand_args([Left, Right], E),
|
||||
{ { ELeft, ERight }, EE };
|
||||
expand({Left, Right}, E) ->
|
||||
{[ELeft, ERight], EE} = expand_args([Left, Right], E),
|
||||
{{ELeft, ERight}, EE};
|
||||
|
||||
expand(List, #elixir_env{context=match} = E) when is_list(List) ->
|
||||
expand(List, #{context := match} = E) when is_list(List) ->
|
||||
expand_list(List, fun expand/2, E, []);
|
||||
|
||||
expand(List, E) when is_list(List) ->
|
||||
{ EArgs, { EC, EV } } = expand_list(List, fun expand_arg/2, {E, E}, []),
|
||||
{ EArgs, elixir_env:mergea(EV, EC) };
|
||||
{EArgs, {EC, EV}} = expand_list(List, fun expand_arg/2, {E, E}, []),
|
||||
{EArgs, elixir_env:mergea(EV, EC)};
|
||||
|
||||
expand(Function, E) when is_function(Function) ->
|
||||
case (erlang:fun_info(Function, type) == { type, external }) andalso
|
||||
(erlang:fun_info(Function, env) == { env, [] }) of
|
||||
case (erlang:fun_info(Function, type) == {type, external}) andalso
|
||||
(erlang:fun_info(Function, env) == {env, []}) of
|
||||
true ->
|
||||
{ Function, E };
|
||||
{Function, E};
|
||||
false ->
|
||||
compile_error([{line,0}], E#elixir_env.file,
|
||||
compile_error([{line,0}], ?m(E, file),
|
||||
"invalid quoted expression: ~ts", ['Elixir.Kernel':inspect(Function)])
|
||||
end;
|
||||
|
||||
expand(Other, E) when is_number(Other); is_atom(Other); is_binary(Other); is_pid(Other) ->
|
||||
{ Other, E };
|
||||
{Other, E};
|
||||
|
||||
expand(Other, E) ->
|
||||
compile_error([{line,0}], E#elixir_env.file,
|
||||
compile_error([{line,0}], ?m(E, file),
|
||||
"invalid quoted expression: ~ts", ['Elixir.Kernel':inspect(Other)]).
|
||||
|
||||
%% Helpers
|
||||
|
||||
expand_list([{ '|', Meta, [_, _] = Args }], Fun, Acc, List) ->
|
||||
{ EArgs, EAcc } = lists:mapfoldl(Fun, Acc, Args),
|
||||
expand_list([], Fun, EAcc, [{ '|', Meta, EArgs }|List]);
|
||||
expand_list([{'|', Meta, [_, _] = Args}], Fun, Acc, List) ->
|
||||
{EArgs, EAcc} = lists:mapfoldl(Fun, Acc, Args),
|
||||
expand_list([], Fun, EAcc, [{'|', Meta, EArgs}|List]);
|
||||
expand_list([H|T], Fun, Acc, List) ->
|
||||
{ EArg, EAcc } = Fun(H, Acc),
|
||||
{EArg, EAcc} = Fun(H, Acc),
|
||||
expand_list(T, Fun, EAcc, [EArg|List]);
|
||||
expand_list([], _Fun, Acc, List) ->
|
||||
{ lists:reverse(List), Acc }.
|
||||
{lists:reverse(List), Acc}.
|
||||
|
||||
expand_many(Args, E) ->
|
||||
lists:mapfoldl(fun expand/2, E, Args).
|
||||
@@ -411,22 +412,22 @@ expand_many(Args, E) ->
|
||||
%%
|
||||
%% However, lexical information is.
|
||||
expand_arg(Arg, Acc) when is_number(Arg); is_atom(Arg); is_binary(Arg); is_pid(Arg) ->
|
||||
{ Arg, Acc };
|
||||
expand_arg(Arg, { Acc1, Acc2 }) ->
|
||||
{ EArg, EAcc } = expand(Arg, Acc1),
|
||||
{ EArg, { elixir_env:mergea(Acc1, EAcc), elixir_env:mergev(Acc2, EAcc) } }.
|
||||
{Arg, Acc};
|
||||
expand_arg(Arg, {Acc1, Acc2}) ->
|
||||
{EArg, EAcc} = expand(Arg, Acc1),
|
||||
{EArg, {elixir_env:mergea(Acc1, EAcc), elixir_env:mergev(Acc2, EAcc)}}.
|
||||
|
||||
expand_args(Args, #elixir_env{context=match} = E) ->
|
||||
expand_args(Args, #{context := match} = E) ->
|
||||
expand_many(Args, E);
|
||||
expand_args(Args, E) ->
|
||||
{ EArgs, { EC, EV } } = lists:mapfoldl(fun expand_arg/2, {E, E}, Args),
|
||||
{ EArgs, elixir_env:mergea(EV, EC) }.
|
||||
{EArgs, {EC, EV}} = lists:mapfoldl(fun expand_arg/2, {E, E}, Args),
|
||||
{EArgs, elixir_env:mergea(EV, EC)}.
|
||||
|
||||
%% Match/var helpers
|
||||
|
||||
var_kind(Meta, Kind) ->
|
||||
case lists:keyfind(counter, 1, Meta) of
|
||||
{ counter, Counter } -> Counter;
|
||||
{counter, Counter} -> Counter;
|
||||
false -> Kind
|
||||
end.
|
||||
|
||||
@@ -434,10 +435,10 @@ var_kind(Meta, Kind) ->
|
||||
|
||||
assert_no_ambiguous_op(Name, Meta, [Arg], E) ->
|
||||
case lists:keyfind(ambiguous_op, 1, Meta) of
|
||||
{ ambiguous_op, Kind } ->
|
||||
case lists:member({ Name, Kind }, E#elixir_env.vars) of
|
||||
{ambiguous_op, Kind} ->
|
||||
case lists:member({Name, Kind}, ?m(E, vars)) of
|
||||
true ->
|
||||
compile_error(Meta, E#elixir_env.file, "\"~ts ~ts\" looks like a function call but "
|
||||
compile_error(Meta, ?m(E, file), "\"~ts ~ts\" looks like a function call but "
|
||||
"there is a variable named \"~ts\", please use explicit parenthesis or even spaces",
|
||||
[Name, 'Elixir.Macro':to_string(Arg), Name]);
|
||||
false ->
|
||||
@@ -449,59 +450,59 @@ assert_no_ambiguous_op(Name, Meta, [Arg], E) ->
|
||||
assert_no_ambiguous_op(_Atom, _Meta, _Args, _E) ->
|
||||
ok.
|
||||
|
||||
expand_local(Meta, Name, Args, #elixir_env{local=nil, function=nil} = E) ->
|
||||
{ EArgs, EA } = expand_args(Args, E),
|
||||
{ { Name, Meta, EArgs }, EA };
|
||||
expand_local(Meta, Name, Args, #elixir_env{local=nil, module=Module, function=Function} = E) ->
|
||||
elixir_locals:record_local({ Name, length(Args) }, Module, Function),
|
||||
{ EArgs, EA } = expand_args(Args, E),
|
||||
{ { Name, Meta, EArgs }, EA };
|
||||
expand_local(Meta, Name, Args, #{local := nil, function := nil} = E) ->
|
||||
{EArgs, EA} = expand_args(Args, E),
|
||||
{{Name, Meta, EArgs}, EA};
|
||||
expand_local(Meta, Name, Args, #{local := nil, module := Module, function := Function} = E) ->
|
||||
elixir_locals:record_local({Name, length(Args)}, Module, Function),
|
||||
{EArgs, EA} = expand_args(Args, E),
|
||||
{{Name, Meta, EArgs}, EA};
|
||||
expand_local(Meta, Name, Args, E) ->
|
||||
expand({ { '.', Meta, [E#elixir_env.local, Name] }, Meta, Args }, E).
|
||||
expand({{'.', Meta, [?m(E, local), Name]}, Meta, Args}, E).
|
||||
|
||||
%% Remote
|
||||
|
||||
expand_remote(Receiver, DotMeta, Right, Meta, Args, E, EL) ->
|
||||
if
|
||||
is_atom(Receiver) -> elixir_lexical:record_remote(Receiver, E#elixir_env.lexical_tracker);
|
||||
is_atom(Receiver) -> elixir_lexical:record_remote(Receiver, ?m(E, lexical_tracker));
|
||||
true -> ok
|
||||
end,
|
||||
{ EArgs, EA } = expand_args(Args, E),
|
||||
{ { { '.', DotMeta, [Receiver, Right] }, Meta, EArgs }, elixir_env:mergev(EL, EA) }.
|
||||
{EArgs, EA} = expand_args(Args, E),
|
||||
{{{'.', DotMeta, [Receiver, Right]}, Meta, EArgs}, elixir_env:mergev(EL, EA)}.
|
||||
|
||||
%% Lexical helpers
|
||||
|
||||
expand_opts(Meta, Kind, Allowed, Opts, E) ->
|
||||
{ EOpts, EE } = expand(Opts, E),
|
||||
{EOpts, EE} = expand(Opts, E),
|
||||
validate_opts(Meta, Kind, Allowed, EOpts, EE),
|
||||
{ EOpts, EE }.
|
||||
{EOpts, EE}.
|
||||
|
||||
validate_opts(Meta, Kind, Allowed, Opts, E) when is_list(Opts) ->
|
||||
[begin
|
||||
compile_error(Meta, E#elixir_env.file,
|
||||
compile_error(Meta, ?m(E, file),
|
||||
"unsupported option ~ts given to ~s", ['Elixir.Kernel':inspect(Key), Kind])
|
||||
end || { Key, _ } <- Opts, not lists:member(Key, Allowed)];
|
||||
end || {Key, _} <- Opts, not lists:member(Key, Allowed)];
|
||||
|
||||
validate_opts(Meta, Kind, _Allowed, _Opts, S) ->
|
||||
compile_error(Meta, S#elixir_scope.file, "invalid options for ~s, expected a keyword list", [Kind]).
|
||||
|
||||
no_alias_opts(KV) when is_list(KV) ->
|
||||
case lists:keyfind(as, 1, KV) of
|
||||
{ as, As } -> lists:keystore(as, 1, KV, { as, no_alias_expansion(As) });
|
||||
{as, As} -> lists:keystore(as, 1, KV, {as, no_alias_expansion(As)});
|
||||
false -> KV
|
||||
end;
|
||||
no_alias_opts(KV) -> KV.
|
||||
|
||||
no_alias_expansion({ '__aliases__', Meta, [H|T] }) when (H /= 'Elixir') and is_atom(H) ->
|
||||
{ '__aliases__', Meta, ['Elixir',H|T] };
|
||||
no_alias_expansion({'__aliases__', Meta, [H|T]}) when (H /= 'Elixir') and is_atom(H) ->
|
||||
{'__aliases__', Meta, ['Elixir',H|T]};
|
||||
no_alias_expansion(Other) ->
|
||||
Other.
|
||||
|
||||
expand_require(Meta, Ref, KV, E) ->
|
||||
RE = E#elixir_env{requires=ordsets:add_element(Ref, E#elixir_env.requires)},
|
||||
RE = E#{requires := ordsets:add_element(Ref, ?m(E, requires))},
|
||||
expand_alias(Meta, false, Ref, KV, RE).
|
||||
|
||||
expand_alias(Meta, IncludeByDefault, Ref, KV, #elixir_env{context_modules=Context} = E) ->
|
||||
expand_alias(Meta, IncludeByDefault, Ref, KV, #{context_modules := Context} = E) ->
|
||||
New = expand_as(lists:keyfind(as, 1, KV), Meta, IncludeByDefault, Ref, E),
|
||||
|
||||
%% Add the alias to context_modules if defined is true.
|
||||
@@ -509,51 +510,51 @@ expand_alias(Meta, IncludeByDefault, Ref, KV, #elixir_env{context_modules=Contex
|
||||
%% module in context modules.
|
||||
NewContext =
|
||||
case lists:keyfind(defined, 1, Meta) of
|
||||
{ defined, Mod } when is_atom(Mod) -> [Mod|Context];
|
||||
{defined, Mod} when is_atom(Mod) -> [Mod|Context];
|
||||
false -> Context
|
||||
end,
|
||||
|
||||
{ Aliases, MacroAliases } = elixir_aliases:store(Meta, New, Ref, KV, E#elixir_env.aliases,
|
||||
E#elixir_env.macro_aliases, E#elixir_env.lexical_tracker),
|
||||
{Aliases, MacroAliases} = elixir_aliases:store(Meta, New, Ref, KV, ?m(E, aliases),
|
||||
?m(E, macro_aliases), ?m(E, lexical_tracker)),
|
||||
|
||||
E#elixir_env{aliases=Aliases, macro_aliases=MacroAliases, context_modules=NewContext}.
|
||||
E#{aliases := Aliases, macro_aliases := MacroAliases, context_modules := NewContext}.
|
||||
|
||||
expand_as({ as, true }, _Meta, _IncludeByDefault, Ref, _E) ->
|
||||
expand_as({as, true}, _Meta, _IncludeByDefault, Ref, _E) ->
|
||||
elixir_aliases:last(Ref);
|
||||
expand_as({ as, false }, _Meta, _IncludeByDefault, Ref, _E) ->
|
||||
expand_as({as, false}, _Meta, _IncludeByDefault, Ref, _E) ->
|
||||
Ref;
|
||||
expand_as({ as, Atom }, Meta, _IncludeByDefault, _Ref, E) when is_atom(Atom) ->
|
||||
expand_as({as, Atom}, Meta, _IncludeByDefault, _Ref, E) when is_atom(Atom) ->
|
||||
case length(string:tokens(atom_to_list(Atom), ".")) of
|
||||
1 -> compile_error(Meta, E#elixir_env.file,
|
||||
1 -> compile_error(Meta, ?m(E, file),
|
||||
"invalid value for keyword :as, expected an alias, got atom: ~ts", [elixir_aliases:inspect(Atom)]);
|
||||
2 -> Atom;
|
||||
_ -> compile_error(Meta, E#elixir_env.file,
|
||||
_ -> compile_error(Meta, ?m(E, file),
|
||||
"invalid value for keyword :as, expected an alias, got nested alias: ~ts", [elixir_aliases:inspect(Atom)])
|
||||
end;
|
||||
expand_as(false, _Meta, IncludeByDefault, Ref, _E) ->
|
||||
if IncludeByDefault -> elixir_aliases:last(Ref);
|
||||
true -> Ref
|
||||
end;
|
||||
expand_as({ as, Other }, Meta, _IncludeByDefault, _Ref, E) ->
|
||||
compile_error(Meta, E#elixir_env.file,
|
||||
expand_as({as, Other}, Meta, _IncludeByDefault, _Ref, E) ->
|
||||
compile_error(Meta, ?m(E, file),
|
||||
"invalid value for keyword :as, expected an alias, got: ~ts", ['Elixir.Macro':to_string(Other)]).
|
||||
|
||||
%% Comprehensions
|
||||
|
||||
expand_comprehension(Meta, Kind, Args, E) ->
|
||||
case elixir_utils:split_last(Args) of
|
||||
{ Cases, [{do,Expr}] } ->
|
||||
{ ECases, EC } = lists:mapfoldl(fun expand_comprehension_clause/2, E, Cases),
|
||||
{ EExpr, _ } = expand(Expr, EC),
|
||||
{ { Kind, Meta, ECases ++ [[{do,EExpr}]] }, E };
|
||||
{Cases, [{do,Expr}]} ->
|
||||
{ECases, EC} = lists:mapfoldl(fun expand_comprehension_clause/2, E, Cases),
|
||||
{EExpr, _} = expand(Expr, EC),
|
||||
{{Kind, Meta, ECases ++ [[{do,EExpr}]]}, E};
|
||||
_ ->
|
||||
compile_error(Meta, E#elixir_env.file, "missing do keyword in comprehension ~ts", [Kind])
|
||||
compile_error(Meta, ?m(E, file), "missing do keyword in comprehension ~ts", [Kind])
|
||||
end.
|
||||
|
||||
expand_comprehension_clause({Gen, Meta, [Left, Right]}, E) when Gen == inbits; Gen == inlist ->
|
||||
{ ERight, ER } = expand(Right, E),
|
||||
{ ELeft, EL } = elixir_exp_clauses:match(fun expand/2, Left, E),
|
||||
{ { Gen, Meta, [ELeft, ERight] }, elixir_env:mergev(EL, ER) };
|
||||
{ERight, ER} = expand(Right, E),
|
||||
{ELeft, EL} = elixir_exp_clauses:match(fun expand/2, Left, E),
|
||||
{{Gen, Meta, [ELeft, ERight]}, elixir_env:mergev(EL, ER)};
|
||||
expand_comprehension_clause(X, E) ->
|
||||
expand(X, E).
|
||||
|
||||
@@ -562,9 +563,9 @@ expand_comprehension_clause(X, E) ->
|
||||
assert_no_match_or_guard_scope(Meta, Kind, E) ->
|
||||
assert_no_match_scope(Meta, Kind, E),
|
||||
assert_no_guard_scope(Meta, Kind, E).
|
||||
assert_no_match_scope(Meta, _Kind, #elixir_env{context=match,file=File}) ->
|
||||
assert_no_match_scope(Meta, _Kind, #{context := match, file := File}) ->
|
||||
compile_error(Meta, File, "invalid expression in match");
|
||||
assert_no_match_scope(_Meta, _Kind, _E) -> [].
|
||||
assert_no_guard_scope(Meta, _Kind, #elixir_env{context=guard,file=File}) ->
|
||||
assert_no_guard_scope(Meta, _Kind, #{context := guard, file := File}) ->
|
||||
compile_error(Meta, File, "invalid expression in guard");
|
||||
assert_no_guard_scope(_Meta, _Kind, _E) -> [].
|
||||
|
||||
@@ -5,130 +5,130 @@
|
||||
-import(elixir_errors, [compile_error/3, compile_error/4]).
|
||||
-include("elixir.hrl").
|
||||
|
||||
match(Fun, Expr, #elixir_env{context=Context} = E) ->
|
||||
{ EExpr, EE } = Fun(Expr, E#elixir_env{context=match}),
|
||||
{ EExpr, EE#elixir_env{context=Context} }.
|
||||
match(Fun, Expr, #{context := Context} = E) ->
|
||||
{EExpr, EE} = Fun(Expr, E#{context := match}),
|
||||
{EExpr, EE#{context := Context}}.
|
||||
|
||||
def(Fun, Args, Guards, Body, E) ->
|
||||
{ EArgs, EA } = match(Fun, Args, E),
|
||||
{ EGuards, EG } = guard(Guards, EA#elixir_env{context=guard}),
|
||||
{ EBody, EB } = elixir_exp:expand(Body, EG#elixir_env{context=E#elixir_env.context}),
|
||||
{ EArgs, EGuards, EBody, EB }.
|
||||
{EArgs, EA} = match(Fun, Args, E),
|
||||
{EGuards, EG} = guard(Guards, EA#{context := guard}),
|
||||
{EBody, EB} = elixir_exp:expand(Body, EG#{context := ?m(E, context)}),
|
||||
{EArgs, EGuards, EBody, EB}.
|
||||
|
||||
clause(Meta, Kind, Fun, { '->', ClauseMeta, [_, _] } = Clause, E) when is_function(Fun, 3) ->
|
||||
clause(Meta, Kind, Fun, {'->', ClauseMeta, [_, _]} = Clause, E) when is_function(Fun, 3) ->
|
||||
clause(Meta, Kind, fun(X, Acc) -> Fun(ClauseMeta, X, Acc) end, Clause, E);
|
||||
clause(_Meta, _Kind, Fun, { '->', Meta, [Left, Right] }, E) ->
|
||||
{ ELeft, EL } = head(Fun, Left, E),
|
||||
{ ERight, ER } = elixir_exp:expand(Right, EL),
|
||||
{ { '->', Meta, [ELeft, ERight] }, ER };
|
||||
clause(_Meta, _Kind, Fun, {'->', Meta, [Left, Right]}, E) ->
|
||||
{ELeft, EL} = head(Fun, Left, E),
|
||||
{ERight, ER} = elixir_exp:expand(Right, EL),
|
||||
{{'->', Meta, [ELeft, ERight]}, ER};
|
||||
clause(Meta, Kind, _Fun, _, E) ->
|
||||
compile_error(Meta, E#elixir_env.file, "expected -> clauses in ~ts", [Kind]).
|
||||
compile_error(Meta, ?m(E, file), "expected -> clauses in ~ts", [Kind]).
|
||||
|
||||
head(Fun, [{ 'when', Meta, [_,_|_] = All }], E) ->
|
||||
{ Args, Guard } = elixir_utils:split_last(All),
|
||||
{ EArgs, EA } = match(Fun, Args, E),
|
||||
{ EGuard, EG } = guard(Guard, EA#elixir_env{context=guard}),
|
||||
{ [{ 'when', Meta, EArgs ++ [EGuard] }], EG#elixir_env{context=E#elixir_env.context} };
|
||||
head(Fun, [{'when', Meta, [_,_|_] = All}], E) ->
|
||||
{Args, Guard} = elixir_utils:split_last(All),
|
||||
{EArgs, EA} = match(Fun, 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).
|
||||
|
||||
guard({ 'when', Meta, [Left, Right] }, E) ->
|
||||
{ ELeft, EL } = guard(Left, E),
|
||||
{ ERight, ER } = guard(Right, EL),
|
||||
{ { 'when', Meta, [ELeft, ERight] }, ER };
|
||||
guard({'when', Meta, [Left, Right]}, E) ->
|
||||
{ELeft, EL} = guard(Left, E),
|
||||
{ERight, ER} = guard(Right, EL),
|
||||
{{'when', Meta, [ELeft, ERight]}, ER};
|
||||
guard(Other, E) ->
|
||||
elixir_exp:expand(Other, E).
|
||||
|
||||
%% Case
|
||||
|
||||
'case'(Meta, [], E) ->
|
||||
compile_error(Meta, E#elixir_env.file, "missing do keyword in case");
|
||||
compile_error(Meta, ?m(E, file), "missing do keyword in case");
|
||||
'case'(Meta, KV, E) when not is_list(KV) ->
|
||||
compile_error(Meta, E#elixir_env.file, "invalid arguments for case");
|
||||
compile_error(Meta, ?m(E, file), "invalid arguments for case");
|
||||
'case'(Meta, KV, E) ->
|
||||
EE = E#elixir_env{export_vars=[]},
|
||||
{ EClauses, EVars } = lists:mapfoldl(fun(X, Acc) -> do_case(Meta, X, Acc, EE) end, [], KV),
|
||||
{ EClauses, elixir_env:mergev(EVars, E) }.
|
||||
EE = E#{export_vars := []},
|
||||
{EClauses, EVars} = lists:mapfoldl(fun(X, Acc) -> do_case(Meta, X, Acc, EE) end, [], KV),
|
||||
{EClauses, elixir_env:mergev(EVars, E)}.
|
||||
|
||||
do_case(Meta, { 'do', _ } = Do, Acc, E) ->
|
||||
do_case(Meta, {'do', _} = Do, Acc, E) ->
|
||||
expand_with_export(Meta, 'case', expand_arg(Meta, 'case', 'do'), Do, Acc, E);
|
||||
do_case(Meta, { Key, _ }, _Acc, E) ->
|
||||
compile_error(Meta, E#elixir_env.file, "unexpected keyword ~ts in case", [Key]).
|
||||
do_case(Meta, {Key, _}, _Acc, E) ->
|
||||
compile_error(Meta, ?m(E, file), "unexpected keyword ~ts in case", [Key]).
|
||||
|
||||
%% Receive
|
||||
|
||||
'receive'(Meta, [], E) ->
|
||||
compile_error(Meta, E#elixir_env.file, "missing do or after keyword in receive");
|
||||
compile_error(Meta, ?m(E, file), "missing do or after keyword in receive");
|
||||
'receive'(Meta, KV, E) when not is_list(KV) ->
|
||||
compile_error(Meta, E#elixir_env.file, "invalid arguments for receive");
|
||||
compile_error(Meta, ?m(E, file), "invalid arguments for receive");
|
||||
'receive'(Meta, KV, E) ->
|
||||
EE = E#elixir_env{export_vars=[]},
|
||||
{ EClauses, EVars } = lists:mapfoldl(fun(X, Acc) -> do_receive(Meta, X, Acc, EE) end, [], KV),
|
||||
{ EClauses, elixir_env:mergev(EVars, E) }.
|
||||
EE = E#{export_vars := []},
|
||||
{EClauses, EVars} = lists:mapfoldl(fun(X, Acc) -> do_receive(Meta, X, Acc, EE) end, [], KV),
|
||||
{EClauses, elixir_env:mergev(EVars, E)}.
|
||||
|
||||
do_receive(_Meta, { 'do', nil } = Do, Acc, _E) ->
|
||||
{ Do, Acc };
|
||||
do_receive(Meta, { 'do', _ } = Do, Acc, E) ->
|
||||
do_receive(_Meta, {'do', nil} = Do, Acc, _E) ->
|
||||
{Do, Acc};
|
||||
do_receive(Meta, {'do', _} = Do, Acc, E) ->
|
||||
expand_with_export(Meta, 'receive', expand_arg(Meta, 'receive', 'do'), Do, Acc, E);
|
||||
do_receive(_Meta, { 'after', [{ '->', Meta, [[Left], Right] }] }, Acc, E) ->
|
||||
{ ELeft, EL } = elixir_exp:expand(Left, E),
|
||||
{ ERight, ER } = elixir_exp:expand(Right, EL),
|
||||
EClause = { 'after', [{ '->', Meta, [[ELeft], ERight] }] },
|
||||
{ EClause, elixir_env:merge_vars(Acc, ER#elixir_env.export_vars) };
|
||||
do_receive(Meta, { 'after', _ }, _Acc, E) ->
|
||||
compile_error(Meta, E#elixir_env.file, "expected a single -> clause for after in receive");
|
||||
do_receive(Meta, { Key, _ }, _Acc, E) ->
|
||||
compile_error(Meta, E#elixir_env.file, "unexpected keyword ~ts in receive", [Key]).
|
||||
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))};
|
||||
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) ->
|
||||
compile_error(Meta, ?m(E, file), "unexpected keyword ~ts in receive", [Key]).
|
||||
|
||||
%% Try
|
||||
|
||||
'try'(Meta, [], E) ->
|
||||
compile_error(Meta, E#elixir_env.file, "missing do keywords in try");
|
||||
compile_error(Meta, ?m(E, file), "missing do keywords in try");
|
||||
'try'(Meta, KV, E) when not is_list(KV) ->
|
||||
elixir_errors:compile_error(Meta, E#elixir_env.file, "invalid arguments for try");
|
||||
elixir_errors:compile_error(Meta, ?m(E, file), "invalid arguments for try");
|
||||
'try'(Meta, KV, E) ->
|
||||
{ lists:map(fun(X) -> do_try(Meta, X, E) end, KV), E }.
|
||||
{lists:map(fun(X) -> do_try(Meta, X, E) end, KV), E}.
|
||||
|
||||
do_try(_Meta, { 'do', Expr }, E) ->
|
||||
{ EExpr, _ } = elixir_exp:expand(Expr, E),
|
||||
{ 'do', EExpr };
|
||||
do_try(_Meta, { 'after', Expr }, E) ->
|
||||
{ EExpr, _ } = elixir_exp:expand(Expr, E),
|
||||
{ 'after', EExpr };
|
||||
do_try(Meta, { 'else', _ } = Else, E) ->
|
||||
do_try(_Meta, {'do', Expr}, E) ->
|
||||
{EExpr, _} = elixir_exp:expand(Expr, E),
|
||||
{'do', EExpr};
|
||||
do_try(_Meta, {'after', Expr}, E) ->
|
||||
{EExpr, _} = elixir_exp:expand(Expr, E),
|
||||
{'after', EExpr};
|
||||
do_try(Meta, {'else', _} = Else, E) ->
|
||||
expand_without_export(Meta, 'try', expand_arg(Meta, 'try', 'else'), Else, E);
|
||||
do_try(Meta, { 'catch', _ } = Catch, E) ->
|
||||
do_try(Meta, {'catch', _} = Catch, E) ->
|
||||
expand_without_export(Meta, 'try', fun elixir_exp:expand_args/2, Catch, E);
|
||||
do_try(Meta, { 'rescue', _ } = Rescue, E) ->
|
||||
do_try(Meta, {'rescue', _} = Rescue, E) ->
|
||||
expand_without_export(Meta, 'try', fun expand_rescue/3, Rescue, E);
|
||||
do_try(Meta, { Key, _ }, E) ->
|
||||
compile_error(Meta, E#elixir_env.file, "unexpected keyword ~ts in try", [Key]).
|
||||
do_try(Meta, {Key, _}, E) ->
|
||||
compile_error(Meta, ?m(E, file), "unexpected keyword ~ts in try", [Key]).
|
||||
|
||||
expand_rescue(Meta, [Arg], E) ->
|
||||
case expand_rescue(Arg, E) of
|
||||
{ EArg, EA } ->
|
||||
{ [EArg], EA };
|
||||
{EArg, EA} ->
|
||||
{[EArg], EA};
|
||||
false ->
|
||||
compile_error(Meta, E#elixir_env.file, "invalid rescue clause. The clause should "
|
||||
compile_error(Meta, ?m(E, file), "invalid rescue clause. The clause should "
|
||||
"match on an alias, a variable or be in the `var in [alias]` format")
|
||||
end;
|
||||
expand_rescue(Meta, _, E) ->
|
||||
compile_error(Meta, E#elixir_env.file, "expected one arg for rescue clauses (->) in try").
|
||||
compile_error(Meta, ?m(E, file), "expected one arg for rescue clauses (->) in try").
|
||||
|
||||
%% rescue var => var in _
|
||||
expand_rescue({ Name, _, Atom } = Var, E) when is_atom(Name), is_atom(Atom) ->
|
||||
expand_rescue({ in, [], [Var, { '_', [], E#elixir_env.module }] }, E);
|
||||
expand_rescue({Name, _, Atom} = Var, E) when is_atom(Name), is_atom(Atom) ->
|
||||
expand_rescue({in, [], [Var, {'_', [], ?m(E, module)}]}, E);
|
||||
|
||||
%% rescue var in [Exprs]
|
||||
expand_rescue({ in, Meta, [Left, Right] }, E) ->
|
||||
{ ERight, ER } = elixir_exp:expand(Right, E#elixir_env{context=nil}),
|
||||
{ ELeft, EL } = elixir_exp:expand(Left, ER#elixir_env{context=match}),
|
||||
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}),
|
||||
|
||||
case ELeft of
|
||||
{ Name, _, Atom } when is_atom(Name), is_atom(Atom) ->
|
||||
{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]}, EL}
|
||||
end;
|
||||
_ ->
|
||||
false
|
||||
@@ -136,27 +136,27 @@ expand_rescue({ in, Meta, [Left, Right] }, E) ->
|
||||
|
||||
%% rescue Error => _ in [Error]
|
||||
expand_rescue(Arg, E) ->
|
||||
expand_rescue({ in, [], [{ '_', [], E#elixir_env.module }, Arg] }, E).
|
||||
expand_rescue({in, [], [{'_', [], ?m(E, module)}, Arg]}, E).
|
||||
|
||||
normalize_rescue({ '_', _, Atom } = N) when is_atom(Atom) -> N;
|
||||
normalize_rescue({'_', _, Atom} = N) when is_atom(Atom) -> N;
|
||||
normalize_rescue(Atom) when is_atom(Atom) -> [Atom];
|
||||
normalize_rescue(Other) ->
|
||||
is_list(Other)
|
||||
andalso lists:all(fun is_var_or_atom/1, Other)
|
||||
andalso Other.
|
||||
|
||||
is_var_or_atom({ Name, _, Atom }) when is_atom(Name), is_atom(Atom) -> true;
|
||||
is_var_or_atom({Name, _, Atom}) when is_atom(Name), is_atom(Atom) -> true;
|
||||
is_var_or_atom(Atom) when is_atom(Atom) -> true;
|
||||
is_var_or_atom(_) -> false.
|
||||
|
||||
%% Expansion helpers
|
||||
|
||||
export_vars({ Left, Meta, Right }) when is_atom(Left), is_list(Meta), is_atom(Right) ->
|
||||
{ Left, [{export,false}|Meta], Right };
|
||||
export_vars({ Left, Meta, Right }) ->
|
||||
{ export_vars(Left), Meta, export_vars(Right) };
|
||||
export_vars({ Left, Right }) ->
|
||||
{ export_vars(Left), export_vars(Right) };
|
||||
export_vars({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) ->
|
||||
@@ -167,34 +167,34 @@ export_vars(Other) ->
|
||||
expand_arg(Meta, Kind, Key) ->
|
||||
fun
|
||||
([Arg], E) ->
|
||||
{ EArg, EA } = elixir_exp:expand(Arg, E),
|
||||
{ [EArg], EA };
|
||||
{EArg, EA} = elixir_exp:expand(Arg, E),
|
||||
{[EArg], EA};
|
||||
(_, E) ->
|
||||
compile_error(Meta, E#elixir_env.file, "expected one arg for ~ts clauses (->) in ~ts", [Key, Kind])
|
||||
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) ->
|
||||
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;
|
||||
{export_head, true} -> Fun;
|
||||
_ -> fun(ExportArgs, ExportE) -> Fun(export_vars(ExportArgs), ExportE) end
|
||||
end,
|
||||
Transformer = fun(Clause, Vars) ->
|
||||
{ EClause, EC } = clause(Meta, Kind, EFun, Clause, E),
|
||||
{ EClause, elixir_env:merge_vars(Vars, EC#elixir_env.export_vars) }
|
||||
{EClause, EC} = clause(Meta, Kind, EFun, Clause, E),
|
||||
{EClause, elixir_env:merge_vars(Vars, ?m(EC, export_vars))}
|
||||
end,
|
||||
{ EClauses, EVars } = lists:mapfoldl(Transformer, Acc, Clauses),
|
||||
{ { Key, EClauses }, EVars };
|
||||
expand_with_export(Meta, Kind, _Fun, { Key, _ }, _Acc, E) ->
|
||||
compile_error(Meta, E#elixir_env.file, "expected -> clauses for ~ts in ~ts", [Key, Kind]).
|
||||
{EClauses, EVars} = lists:mapfoldl(Transformer, Acc, Clauses),
|
||||
{{Key, EClauses}, EVars};
|
||||
expand_with_export(Meta, Kind, _Fun, {Key, _}, _Acc, E) ->
|
||||
compile_error(Meta, ?m(E, file), "expected -> clauses for ~ts in ~ts", [Key, Kind]).
|
||||
|
||||
%% Expands all -> pairs in a given key but do not keep the overall vars.
|
||||
expand_without_export(Meta, Kind, Fun, { Key, Clauses }, E) when is_list(Clauses) ->
|
||||
expand_without_export(Meta, Kind, Fun, {Key, Clauses}, E) when is_list(Clauses) ->
|
||||
Transformer = fun(Clause) ->
|
||||
{ EClause, _ } = clause(Meta, Kind, Fun, Clause, E),
|
||||
{EClause, _} = clause(Meta, Kind, Fun, Clause, E),
|
||||
EClause
|
||||
end,
|
||||
{ Key, lists:map(Transformer, Clauses) };
|
||||
expand_without_export(Meta, Kind, _Fun, { Key, _ }, E) ->
|
||||
compile_error(Meta, E#elixir_env.file, "expected -> clauses for ~ts in ~ts", [Key, Kind]).
|
||||
{Key, lists:map(Transformer, Clauses)};
|
||||
expand_without_export(Meta, Kind, _Fun, {Key, _}, E) ->
|
||||
compile_error(Meta, ?m(E, file), "expected -> clauses for ~ts in ~ts", [Key, Kind]).
|
||||
|
||||
@@ -4,74 +4,74 @@
|
||||
-include("elixir.hrl").
|
||||
|
||||
translate(Meta, Clauses, S) ->
|
||||
Transformer = fun({ '->', CMeta, [ArgsWithGuards, Expr] }, Acc) ->
|
||||
{ Args, Guards } = elixir_clauses:extract_splat_guards(ArgsWithGuards),
|
||||
{ TClause, TS } = elixir_clauses:clause(?line(CMeta), fun translate_fn_match/2,
|
||||
Transformer = fun({'->', CMeta, [ArgsWithGuards, Expr]}, Acc) ->
|
||||
{Args, Guards} = elixir_clauses:extract_splat_guards(ArgsWithGuards),
|
||||
{TClause, TS } = elixir_clauses:clause(?line(CMeta), fun translate_fn_match/2,
|
||||
Args, Expr, Guards, true, Acc),
|
||||
{ TClause, elixir_scope:mergef(S, TS) }
|
||||
{TClause, elixir_scope:mergef(S, TS)}
|
||||
end,
|
||||
|
||||
{ TClauses, NS } = lists:mapfoldl(Transformer, S, Clauses),
|
||||
Arities = [length(Args) || { clause, _Line, Args, _Guards, _Exprs } <- TClauses],
|
||||
{TClauses, NS} = lists:mapfoldl(Transformer, S, Clauses),
|
||||
Arities = [length(Args) || {clause, _Line, Args, _Guards, _Exprs} <- TClauses],
|
||||
|
||||
case lists:usort(Arities) of
|
||||
[_] ->
|
||||
{ { 'fun', ?line(Meta), { clauses, TClauses } }, NS };
|
||||
{{'fun', ?line(Meta), {clauses, TClauses}}, NS};
|
||||
_ ->
|
||||
compile_error(Meta, S#elixir_scope.file,
|
||||
"cannot mix clauses with different arities in function definition")
|
||||
end.
|
||||
|
||||
translate_fn_match(Arg, S) ->
|
||||
{ TArg, TS } = elixir_translator:translate_args(Arg, S#elixir_scope{backup_vars=orddict:new()}),
|
||||
{ TArg, TS#elixir_scope{backup_vars=S#elixir_scope.backup_vars} }.
|
||||
{TArg, TS} = elixir_translator:translate_args(Arg, S#elixir_scope{backup_vars=orddict:new()}),
|
||||
{TArg, TS#elixir_scope{backup_vars=S#elixir_scope.backup_vars}}.
|
||||
|
||||
%% Expansion
|
||||
|
||||
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:expand_args/2, Clause, E),
|
||||
EClause
|
||||
end,
|
||||
{ { fn, Meta, lists:map(Transformer, Clauses) }, E }.
|
||||
{{fn, Meta, lists:map(Transformer, Clauses)}, E}.
|
||||
|
||||
%% Capture
|
||||
|
||||
capture(Meta, { '/', _, [{ { '.', _, [_, F] } = Dot, RequireMeta , [] }, A] }, E) when is_atom(F), is_integer(A) ->
|
||||
Args = [{ '&', [], [X] } || X <- lists:seq(1, A)],
|
||||
capture_require(Meta, { Dot, RequireMeta, Args }, E, true);
|
||||
capture(Meta, {'/', _, [{{'.', _, [_, F]} = Dot, RequireMeta , []}, A]}, E) when is_atom(F), is_integer(A) ->
|
||||
Args = [{'&', [], [X]} || X <- lists:seq(1, A)],
|
||||
capture_require(Meta, {Dot, RequireMeta, Args}, E, true);
|
||||
|
||||
capture(Meta, { '/', _, [{ F, _, C }, A] }, E) when is_atom(F), is_integer(A), is_atom(C) ->
|
||||
capture(Meta, {'/', _, [{F, _, C}, A]}, E) when is_atom(F), is_integer(A), is_atom(C) ->
|
||||
ImportMeta =
|
||||
case lists:keyfind(import_fa, 1, Meta) of
|
||||
{ import_fa, { Receiver, Context } } ->
|
||||
{import_fa, {Receiver, Context}} ->
|
||||
lists:keystore(context, 1,
|
||||
lists:keystore(import, 1, Meta, { import, Receiver }),
|
||||
{ context, Context }
|
||||
lists:keystore(import, 1, Meta, {import, Receiver}),
|
||||
{context, Context}
|
||||
);
|
||||
false -> Meta
|
||||
end,
|
||||
Args = [{ '&', [], [X] } || X <- lists:seq(1, A)],
|
||||
capture_import(Meta, { F, ImportMeta, Args }, E, true);
|
||||
Args = [{'&', [], [X]} || X <- lists:seq(1, A)],
|
||||
capture_import(Meta, {F, ImportMeta, Args}, E, true);
|
||||
|
||||
capture(Meta, { { '.', _, [_, Fun] }, _, Args } = Expr, E) when is_atom(Fun), is_list(Args) ->
|
||||
capture(Meta, {{'.', _, [_, Fun]}, _, Args} = Expr, E) when is_atom(Fun), is_list(Args) ->
|
||||
capture_require(Meta, Expr, E, is_sequential_and_not_empty(Args));
|
||||
|
||||
capture(Meta, { { '.', _, [_] }, _, Args } = Expr, E) when is_list(Args) ->
|
||||
capture(Meta, {{'.', _, [_]}, _, Args} = Expr, E) when is_list(Args) ->
|
||||
do_capture(Meta, Expr, E, false);
|
||||
|
||||
capture(Meta, { '__block__', _, [Expr] }, E) ->
|
||||
capture(Meta, {'__block__', _, [Expr]}, E) ->
|
||||
capture(Meta, Expr, E);
|
||||
|
||||
capture(Meta, { '__block__', _, _ } = Expr, E) ->
|
||||
capture(Meta, {'__block__', _, _} = Expr, E) ->
|
||||
Message = "invalid args for &, block expressions are not allowed, got: ~ts",
|
||||
compile_error(Meta, E#elixir_env.file, Message, ['Elixir.Macro':to_string(Expr)]);
|
||||
compile_error(Meta, ?m(E, file), Message, ['Elixir.Macro':to_string(Expr)]);
|
||||
|
||||
capture(Meta, { Atom, _, Args } = Expr, E) when is_atom(Atom), is_list(Args) ->
|
||||
capture(Meta, {Atom, _, Args} = Expr, E) when is_atom(Atom), is_list(Args) ->
|
||||
capture_import(Meta, Expr, E, is_sequential_and_not_empty(Args));
|
||||
|
||||
capture(Meta, { Left, Right }, E) ->
|
||||
capture(Meta, { '{}', Meta, [Left, Right] }, E);
|
||||
capture(Meta, {Left, Right}, E) ->
|
||||
capture(Meta, {'{}', Meta, [Left, Right]}, E);
|
||||
|
||||
capture(Meta, List, E) when is_list(List) ->
|
||||
do_capture(Meta, List, E, is_sequential_and_not_empty(List));
|
||||
@@ -79,80 +79,86 @@ capture(Meta, List, E) when is_list(List) ->
|
||||
capture(Meta, Arg, E) ->
|
||||
invalid_capture(Meta, Arg, E).
|
||||
|
||||
capture_import(Meta, { Atom, ImportMeta, Args } = Expr, E, Sequential) ->
|
||||
capture_import(Meta, {Atom, ImportMeta, Args} = Expr, E, Sequential) ->
|
||||
Res = Sequential andalso
|
||||
elixir_dispatch:import_function(ImportMeta, Atom, length(Args), E),
|
||||
handle_capture(Res, Meta, Expr, E, Sequential).
|
||||
|
||||
capture_require(Meta, { { '.', _, [Left, Right] }, RequireMeta, Args } = Expr, E, Sequential) ->
|
||||
{ Mod, EE } = elixir_exp:expand(Left, E),
|
||||
Res = Sequential andalso is_atom(Mod) andalso
|
||||
elixir_dispatch:require_function(RequireMeta, Mod, Right, length(Args), EE),
|
||||
capture_require(Meta, {{'.', _, [Left, Right]}, RequireMeta, Args} = Expr, E, Sequential) ->
|
||||
{Mod, EE} = elixir_exp:expand(Left, E),
|
||||
Res = Sequential andalso case Mod of
|
||||
{Name, _, Context} when is_atom(Name), is_atom(Context) ->
|
||||
{remote, Mod, Right, length(Args)};
|
||||
_ when is_atom(Mod) ->
|
||||
elixir_dispatch:require_function(RequireMeta, Mod, Right, length(Args), EE);
|
||||
_ ->
|
||||
false
|
||||
end,
|
||||
handle_capture(Res, Meta, Expr, EE, Sequential).
|
||||
|
||||
handle_capture({ local, Fun, Arity }, _Meta, _Expr, _E, _Sequential) ->
|
||||
{ local, Fun, Arity };
|
||||
handle_capture({ remote, Receiver, Fun, Arity }, Meta, _Expr, E, _Sequential) ->
|
||||
Tree = { { '.', [], [erlang, make_fun] }, Meta, [Receiver, Fun, Arity] },
|
||||
{ expanded, Tree, E };
|
||||
handle_capture({local, Fun, Arity}, _Meta, _Expr, _E, _Sequential) ->
|
||||
{local, Fun, Arity};
|
||||
handle_capture({remote, Receiver, Fun, Arity}, Meta, _Expr, E, _Sequential) ->
|
||||
Tree = {{'.', [], [erlang, make_fun]}, Meta, [Receiver, Fun, Arity]},
|
||||
{expanded, Tree, E};
|
||||
handle_capture(false, Meta, Expr, E, Sequential) ->
|
||||
do_capture(Meta, Expr, E, Sequential).
|
||||
|
||||
do_capture(Meta, Expr, E, Sequential) ->
|
||||
case do_escape(Expr, elixir_counter:next(), E, []) of
|
||||
{ _, [] } when not Sequential ->
|
||||
{_, []} when not Sequential ->
|
||||
invalid_capture(Meta, Expr, E);
|
||||
{ EExpr, EDict } ->
|
||||
{EExpr, EDict} ->
|
||||
EVars = validate(Meta, EDict, 1, E),
|
||||
Fn = { fn, Meta, [{ '->', Meta, [EVars, EExpr]}]},
|
||||
{ expanded, Fn, E }
|
||||
Fn = {fn, Meta, [{'->', Meta, [EVars, EExpr]}]},
|
||||
{expanded, Fn, E}
|
||||
end.
|
||||
|
||||
invalid_capture(Meta, Arg, E) ->
|
||||
Message = "invalid args for &, expected an expression in the format of &Mod.fun/arity, "
|
||||
"&local/arity or a capture containing at least one argument as &1, got: ~ts",
|
||||
compile_error(Meta, E#elixir_env.file, Message, ['Elixir.Macro':to_string(Arg)]).
|
||||
compile_error(Meta, ?m(E, file), Message, ['Elixir.Macro':to_string(Arg)]).
|
||||
|
||||
validate(Meta, [{ Pos, Var }|T], Pos, E) ->
|
||||
validate(Meta, [{Pos, Var}|T], Pos, E) ->
|
||||
[Var|validate(Meta, T, Pos + 1, E)];
|
||||
|
||||
validate(Meta, [{ Pos, _ }|_], Expected, E) ->
|
||||
compile_error(Meta, E#elixir_env.file, "capture &~B cannot be defined without &~B", [Pos, Expected]);
|
||||
validate(Meta, [{Pos, _}|_], Expected, E) ->
|
||||
compile_error(Meta, ?m(E, file), "capture &~B cannot be defined without &~B", [Pos, Expected]);
|
||||
|
||||
validate(_Meta, [], _Pos, _E) ->
|
||||
[].
|
||||
|
||||
do_escape({ '&', _, [Pos] }, Counter, _E, Dict) when is_integer(Pos), Pos > 0 ->
|
||||
Var = { list_to_atom([$x, $@+Pos]), [{ counter, Counter }], elixir_fn },
|
||||
{ Var, orddict:store(Pos, Var, Dict) };
|
||||
do_escape({'&', _, [Pos]}, Counter, _E, Dict) when is_integer(Pos), Pos > 0 ->
|
||||
Var = {list_to_atom([$x, $@+Pos]), [{counter, Counter}], elixir_fn},
|
||||
{Var, orddict:store(Pos, Var, Dict)};
|
||||
|
||||
do_escape({ '&', Meta, [Pos] }, _Counter, E, _Dict) when is_integer(Pos) ->
|
||||
compile_error(Meta, E#elixir_env.file, "capture &~B is not allowed", [Pos]);
|
||||
do_escape({'&', Meta, [Pos]}, _Counter, E, _Dict) when is_integer(Pos) ->
|
||||
compile_error(Meta, ?m(E, file), "capture &~B is not allowed", [Pos]);
|
||||
|
||||
do_escape({ '&', Meta, _ } = Arg, _Counter, E, _Dict) ->
|
||||
do_escape({'&', Meta, _} = Arg, _Counter, E, _Dict) ->
|
||||
Message = "nested captures via & are not allowed: ~ts",
|
||||
compile_error(Meta, E#elixir_env.file, Message, ['Elixir.Macro':to_string(Arg)]);
|
||||
compile_error(Meta, ?m(E, file), Message, ['Elixir.Macro':to_string(Arg)]);
|
||||
|
||||
do_escape({ Left, Meta, Right }, Counter, E, Dict0) ->
|
||||
{ TLeft, Dict1 } = do_escape(Left, Counter, E, Dict0),
|
||||
{ TRight, Dict2 } = do_escape(Right, Counter, E, Dict1),
|
||||
{ { TLeft, Meta, TRight }, Dict2 };
|
||||
do_escape({Left, Meta, Right}, Counter, E, Dict0) ->
|
||||
{TLeft, Dict1} = do_escape(Left, Counter, E, Dict0),
|
||||
{TRight, Dict2} = do_escape(Right, Counter, E, Dict1),
|
||||
{{TLeft, Meta, TRight}, Dict2};
|
||||
|
||||
do_escape({ Left, Right }, Counter, E, Dict0) ->
|
||||
{ TLeft, Dict1 } = do_escape(Left, Counter, E, Dict0),
|
||||
{ TRight, Dict2 } = do_escape(Right, Counter, E, Dict1),
|
||||
{ { TLeft, TRight }, Dict2 };
|
||||
do_escape({Left, Right}, Counter, E, Dict0) ->
|
||||
{TLeft, Dict1} = do_escape(Left, Counter, E, Dict0),
|
||||
{TRight, Dict2} = do_escape(Right, Counter, E, Dict1),
|
||||
{{TLeft, TRight}, Dict2};
|
||||
|
||||
do_escape(List, Counter, E, Dict) when is_list(List) ->
|
||||
lists:mapfoldl(fun(X, Acc) -> do_escape(X, Counter, E, Acc) end, Dict, List);
|
||||
|
||||
do_escape(Other, _Counter, _E, Dict) ->
|
||||
{ Other, Dict }.
|
||||
{Other, Dict}.
|
||||
|
||||
is_sequential_and_not_empty([]) -> false;
|
||||
is_sequential_and_not_empty(List) -> is_sequential(List, 1).
|
||||
|
||||
is_sequential([{ '&', _, [Int] }|T], Int) ->
|
||||
is_sequential([{'&', _, [Int]}|T], Int) ->
|
||||
is_sequential(T, Int + 1);
|
||||
is_sequential([], _Int) -> true;
|
||||
is_sequential(_, _Int) -> false.
|
||||
|
||||
+107
-107
@@ -5,42 +5,42 @@
|
||||
%% Expansion
|
||||
|
||||
expand(Meta, Args, E) ->
|
||||
{ Cases, Block } =
|
||||
{Cases, Block} =
|
||||
case elixir_utils:split_last(Args) of
|
||||
{ OuterCases, OuterOpts } when is_list(OuterOpts) ->
|
||||
{OuterCases, OuterOpts} when is_list(OuterOpts) ->
|
||||
case elixir_utils:split_last(OuterCases) of
|
||||
{ InnerCases, InnerOpts } when is_list(InnerOpts) ->
|
||||
{ InnerCases, InnerOpts ++ OuterOpts };
|
||||
{InnerCases, InnerOpts} when is_list(InnerOpts) ->
|
||||
{InnerCases, InnerOpts ++ OuterOpts};
|
||||
_ ->
|
||||
{ OuterCases, OuterOpts }
|
||||
{OuterCases, OuterOpts}
|
||||
end;
|
||||
_ ->
|
||||
{ Args, [] }
|
||||
{Args, []}
|
||||
end,
|
||||
|
||||
{ Expr, Opts } =
|
||||
{Expr, Opts} =
|
||||
case lists:keyfind(do, 1, Block) of
|
||||
{ do, Do } -> { Do, lists:keydelete(do, 1, Block) };
|
||||
_ -> elixir_errors:compile_error(Meta, E#elixir_env.file,
|
||||
{do, Do} -> {Do, lists:keydelete(do, 1, Block)};
|
||||
_ -> elixir_errors:compile_error(Meta, ?m(E, file),
|
||||
"missing do keyword in for comprehension")
|
||||
end,
|
||||
|
||||
{ EOpts, EO } = elixir_exp:expand(Opts, E),
|
||||
{ ECases, EC } = lists:mapfoldl(fun expand/2, EO, Cases),
|
||||
{ EExpr, _ } = elixir_exp:expand(Expr, EC),
|
||||
{ { for, Meta, ECases ++ [[{do,EExpr}|EOpts]] }, E }.
|
||||
{EOpts, EO} = elixir_exp:expand(Opts, E),
|
||||
{ECases, EC} = lists:mapfoldl(fun expand/2, EO, Cases),
|
||||
{EExpr, _} = elixir_exp:expand(Expr, EC),
|
||||
{{for, Meta, ECases ++ [[{do,EExpr}|EOpts]]}, E}.
|
||||
|
||||
expand({'<-', Meta, [Left, Right]}, E) ->
|
||||
{ ERight, ER } = elixir_exp:expand(Right, E),
|
||||
{ ELeft, EL } = elixir_exp_clauses:match(fun elixir_exp:expand/2, Left, E),
|
||||
{ { '<-', Meta, [ELeft, ERight] }, elixir_env:mergev(EL, ER) };
|
||||
expand({ '<<>>', Meta, Args } = X, E) when is_list(Args) ->
|
||||
{ERight, ER} = elixir_exp:expand(Right, E),
|
||||
{ELeft, EL} = elixir_exp_clauses:match(fun elixir_exp:expand/2, Left, E),
|
||||
{{'<-', Meta, [ELeft, ERight]}, elixir_env:mergev(EL, ER)};
|
||||
expand({'<<>>', Meta, Args} = X, E) when is_list(Args) ->
|
||||
case elixir_utils:split_last(Args) of
|
||||
{ LeftStart, {'<-', OpMeta, [LeftEnd, Right] } } ->
|
||||
{ ERight, ER } = elixir_exp:expand(Right, E),
|
||||
Left = { '<<>>', Meta, LeftStart ++ [LeftEnd] },
|
||||
{ ELeft, EL } = elixir_exp_clauses:match(fun elixir_exp:expand/2, Left, E),
|
||||
{ { '<<>>', [], [ { '<-', OpMeta, [ELeft, ERight] }] }, elixir_env:mergev(EL, ER) };
|
||||
{LeftStart, {'<-', OpMeta, [LeftEnd, Right]}} ->
|
||||
{ERight, ER} = elixir_exp:expand(Right, E),
|
||||
Left = {'<<>>', Meta, LeftStart ++ [LeftEnd]},
|
||||
{ELeft, EL} = elixir_exp_clauses:match(fun elixir_exp:expand/2, Left, E),
|
||||
{{'<<>>', [], [ {'<-', OpMeta, [ELeft, ERight]}]}, elixir_env:mergev(EL, ER)};
|
||||
_ ->
|
||||
elixir_exp:expand(X, E)
|
||||
end;
|
||||
@@ -51,40 +51,40 @@ expand(X, E) ->
|
||||
|
||||
translate(Meta, Args, #elixir_scope{return=Return} = RS) ->
|
||||
S = RS#elixir_scope{return=true},
|
||||
{ AccName, _, SA } = elixir_scope:build_var('_', S),
|
||||
{ VarName, _, SV } = elixir_scope:build_var('_', SA),
|
||||
{AccName, _, SA} = elixir_scope:build_var('_', S),
|
||||
{VarName, _, SV} = elixir_scope:build_var('_', SA),
|
||||
|
||||
Line = ?line(Meta),
|
||||
Acc = { var, Line, AccName },
|
||||
Var = { var, Line, VarName },
|
||||
Acc = {var, Line, AccName},
|
||||
Var = {var, Line, VarName},
|
||||
|
||||
{ Cases, [{do,Expr}|Opts] } = elixir_utils:split_last(Args),
|
||||
{Cases, [{do,Expr}|Opts]} = elixir_utils:split_last(Args),
|
||||
|
||||
{ TInto, SI } =
|
||||
{TInto, SI} =
|
||||
case lists:keyfind(into, 1, Opts) of
|
||||
{ into, Into } -> elixir_translator:translate(Into, SV);
|
||||
false when Return -> { { nil, Line }, SV };
|
||||
false -> { false, SV }
|
||||
{into, Into} -> elixir_translator:translate(Into, SV);
|
||||
false when Return -> {{nil, Line}, SV};
|
||||
false -> {false, SV}
|
||||
end,
|
||||
|
||||
{ TCases, SC } = translate_gen(Meta, Cases, [], SI),
|
||||
{ TExpr, SE } = elixir_translator:translate_block(Expr, Return, SC),
|
||||
SF = elixir_scope:mergef(SI, SE),
|
||||
{TCases, SC} = translate_gen(Meta, Cases, [], SI),
|
||||
{TExpr, SE} = elixir_translator:translate_block(Expr, Return, SC),
|
||||
SF = elixir_scope:mergec(SI, SE),
|
||||
|
||||
case comprehension_expr(TInto, TExpr) of
|
||||
{ inline, TIntoExpr } ->
|
||||
{ build_inline(Line, TCases, TIntoExpr, TInto, Var, Acc, SE), SF };
|
||||
{ into, TIntoExpr } ->
|
||||
{inline, TIntoExpr} ->
|
||||
{build_inline(Line, TCases, TIntoExpr, TInto, Var, Acc, SE), SF};
|
||||
{into, TIntoExpr} ->
|
||||
build_into(Line, TCases, TIntoExpr, TInto, Var, Acc, SF)
|
||||
end.
|
||||
|
||||
translate_gen(ForMeta, [{ '<-', Meta, [Left, Right] }|T], Acc, S) ->
|
||||
{ TLeft, TRight, TFilters, TT, TS } = translate_gen(Meta, Left, Right, T, S),
|
||||
TAcc = [{ enum, Meta, TLeft, TRight, TFilters }|Acc],
|
||||
translate_gen(ForMeta, [{'<-', Meta, [Left, Right]}|T], Acc, S) ->
|
||||
{TLeft, TRight, TFilters, TT, TS} = translate_gen(Meta, Left, Right, T, S),
|
||||
TAcc = [{enum, Meta, TLeft, TRight, TFilters}|Acc],
|
||||
translate_gen(ForMeta, TT, TAcc, TS);
|
||||
translate_gen(ForMeta, [{ '<<>>', _, [ { '<-', Meta, [Left, Right] } ] }|T], Acc, S) ->
|
||||
{ TLeft, TRight, TFilters, TT, TS } = translate_gen(Meta, Left, Right, T, S),
|
||||
TAcc = [{ bin, Meta, TLeft, TRight, TFilters }|Acc],
|
||||
translate_gen(ForMeta, [{'<<>>', _, [ {'<-', Meta, [Left, Right]} ]}|T], Acc, S) ->
|
||||
{TLeft, TRight, TFilters, TT, TS} = translate_gen(Meta, Left, Right, T, S),
|
||||
TAcc = [{bin, Meta, TLeft, TRight, TFilters}|Acc],
|
||||
case elixir_bitstring:has_size(TLeft) of
|
||||
true -> translate_gen(ForMeta, TT, TAcc, TS);
|
||||
false ->
|
||||
@@ -92,55 +92,55 @@ translate_gen(ForMeta, [{ '<<>>', _, [ { '<-', Meta, [Left, Right] } ] }|T], Acc
|
||||
"bitstring fields without size are not allowed in bitstring generators")
|
||||
end;
|
||||
translate_gen(_ForMeta, [], Acc, S) ->
|
||||
{ lists:reverse(Acc), S };
|
||||
{lists:reverse(Acc), S};
|
||||
translate_gen(ForMeta, _, _, S) ->
|
||||
elixir_errors:compile_error(ForMeta, S#elixir_scope.file,
|
||||
"for comprehensions must start with a generator").
|
||||
|
||||
translate_gen(_Meta, Left, Right, T, S) ->
|
||||
{ TRight, SR } = elixir_translator:translate(Right, S),
|
||||
{ TLeft, SL } = elixir_clauses:match(fun elixir_translator:translate/2, Left, SR),
|
||||
{ TT, { TFilters, TS } } = translate_filters(T, SL),
|
||||
{ TLeft, TRight, TFilters, TT, TS }.
|
||||
{TRight, SR} = elixir_translator:translate(Right, S),
|
||||
{TLeft, SL} = elixir_clauses:match(fun elixir_translator:translate/2, Left, SR),
|
||||
{TT, {TFilters, TS}} = translate_filters(T, SL),
|
||||
{TLeft, TRight, TFilters, TT, TS}.
|
||||
|
||||
translate_filters(T, S) ->
|
||||
{ Filters, Rest } = collect_filters(T, []),
|
||||
{ Rest, lists:mapfoldr(fun translate_filter/2, S, Filters) }.
|
||||
{Filters, Rest} = collect_filters(T, []),
|
||||
{Rest, lists:mapfoldr(fun translate_filter/2, S, Filters)}.
|
||||
|
||||
translate_filter(Filter, S) ->
|
||||
{ TFilter, TS } = elixir_translator:translate(Filter, S),
|
||||
{TFilter, TS} = elixir_translator:translate(Filter, S),
|
||||
case elixir_utils:returns_boolean(TFilter) of
|
||||
true ->
|
||||
{ { nil, TFilter }, TS };
|
||||
{{nil, TFilter}, TS};
|
||||
false ->
|
||||
{ Name, _, VS } = elixir_scope:build_var('_', TS),
|
||||
{ { { var, 0, Name }, TFilter }, VS }
|
||||
{Name, _, VS} = elixir_scope:build_var('_', TS),
|
||||
{{{var, 0, Name}, TFilter}, VS}
|
||||
end.
|
||||
|
||||
collect_filters([{ '<-', _, [_, _] }|_] = T, Acc) ->
|
||||
{ Acc, T };
|
||||
collect_filters([{ '<<>>', _, [{ '<-', _, [_, _] }] }|_] = T, Acc) ->
|
||||
{ Acc, T };
|
||||
collect_filters([{'<-', _, [_, _]}|_] = T, Acc) ->
|
||||
{Acc, T};
|
||||
collect_filters([{'<<>>', _, [{'<-', _, [_, _]}]}|_] = T, Acc) ->
|
||||
{Acc, T};
|
||||
collect_filters([H|T], Acc) ->
|
||||
collect_filters(T, [H|Acc]);
|
||||
collect_filters([], Acc) ->
|
||||
{ Acc, [] }.
|
||||
{Acc, []}.
|
||||
|
||||
%% If all we have is one enum generator, we check if it is a list
|
||||
%% for optimization otherwise fallback to the reduce generator.
|
||||
build_inline(Line, [{ enum, Meta, Left, Right, Filters }] = Orig, Expr, Into, Var, Acc, S) ->
|
||||
build_inline(Line, [{enum, Meta, Left, Right, Filters}] = Orig, Expr, Into, Var, Acc, S) ->
|
||||
case Right of
|
||||
{ cons, _, _, _ } ->
|
||||
{cons, _, _, _} ->
|
||||
build_comprehension(Line, Orig, Expr, Into);
|
||||
{ Other, _, _ } when Other == tuple; Other == map; Other == integer; Other == atom ->
|
||||
{Other, _, _} when Other == tuple; Other == map ->
|
||||
build_reduce(Orig, Expr, Into, Acc, S);
|
||||
_ ->
|
||||
Clauses = [{ enum, Meta, Left, Var, Filters }],
|
||||
Clauses = [{enum, Meta, Left, Var, Filters}],
|
||||
|
||||
{'case', -1, Right, [
|
||||
{clause, -1,
|
||||
[Var],
|
||||
[[?wrap_call(Line, erlang, is_list, [Var])]],
|
||||
[[elixir_utils:erl_call(Line, erlang, is_list, [Var])]],
|
||||
[build_comprehension(Line, Clauses, Expr, Into)]},
|
||||
{clause, -1,
|
||||
[Var],
|
||||
@@ -156,16 +156,16 @@ build_inline(Line, Clauses, Expr, Into, _Var, Acc, S) ->
|
||||
end.
|
||||
|
||||
build_into(Line, Clauses, Expr, Into, Fun, Acc, S) ->
|
||||
{ Kind, SK } = build_var(Line, S),
|
||||
{ Reason, SR } = build_var(Line, SK),
|
||||
{ Stack, ST } = build_var(Line, SR),
|
||||
{ Done, SD } = build_var(Line, ST),
|
||||
{Kind, SK} = build_var(Line, S),
|
||||
{Reason, SR} = build_var(Line, SK),
|
||||
{Stack, ST} = build_var(Line, SR),
|
||||
{Done, SD} = build_var(Line, ST),
|
||||
|
||||
IntoExpr = {call, Line, Fun, [Acc, pair(Line, cont, Expr)]},
|
||||
MatchExpr = {match, Line,
|
||||
{tuple, Line, [Acc, Fun]},
|
||||
?wrap_call(Line, 'Elixir.Collectable', into, [Into])
|
||||
},
|
||||
elixir_utils:erl_call(Line, 'Elixir.Collectable', into, [Into])
|
||||
},
|
||||
|
||||
TryExpr =
|
||||
{'try', Line,
|
||||
@@ -177,12 +177,12 @@ build_into(Line, Clauses, Expr, Into, Fun, Acc, S) ->
|
||||
[{clause, Line,
|
||||
[{tuple, Line, [Kind, Reason, {var, Line, '_'}]}],
|
||||
[],
|
||||
[{match, Line, Stack, ?wrap_call(Line, erlang, get_stacktrace, [])},
|
||||
[{match, Line, Stack, elixir_utils:erl_call(Line, erlang, get_stacktrace, [])},
|
||||
{call, Line, Fun, [Acc, {atom, Line, halt}]},
|
||||
?wrap_call(Line, erlang, raise, [Kind, Reason, Stack])]}],
|
||||
elixir_utils:erl_call(Line, erlang, raise, [Kind, Reason, Stack])]}],
|
||||
[]},
|
||||
|
||||
{ {block, Line, [MatchExpr, TryExpr]}, SD }.
|
||||
{{block, Line, [MatchExpr, TryExpr]}, SD}.
|
||||
|
||||
%% Helpers
|
||||
|
||||
@@ -190,14 +190,14 @@ build_reduce(Clauses, Expr, false, Acc, S) ->
|
||||
build_reduce_clause(Clauses, Expr, {nil, 0}, Acc, S);
|
||||
build_reduce(Clauses, Expr, {nil, Line} = Into, Acc, S) ->
|
||||
ListExpr = {cons, Line, Expr, Acc},
|
||||
?wrap_call(Line, lists, reverse,
|
||||
elixir_utils:erl_call(Line, lists, reverse,
|
||||
[build_reduce_clause(Clauses, ListExpr, Into, Acc, S)]);
|
||||
build_reduce(Clauses, Expr, {bin, _, _} = Into, Acc, S) ->
|
||||
{bin, Line, Elements} = Expr,
|
||||
BinExpr = {bin, Line, [{bin_element, Line, Acc, default, [bitstring]}|Elements]},
|
||||
build_reduce_clause(Clauses, BinExpr, Into, Acc, S).
|
||||
|
||||
build_reduce_clause([{ enum, Meta, Left, Right, Filters }|T], Expr, Arg, Acc, S) ->
|
||||
build_reduce_clause([{enum, Meta, Left, Right, Filters}|T], Expr, Arg, Acc, S) ->
|
||||
Line = ?line(Meta),
|
||||
Inner = build_reduce_clause(T, Expr, Acc, Acc, S),
|
||||
|
||||
@@ -219,15 +219,15 @@ build_reduce_clause([{ enum, Meta, Left, Right, Filters }|T], Expr, Arg, Acc, S)
|
||||
[join_filters(Line, Filters, True, False)]}|Clauses0],
|
||||
|
||||
Args = [Right, pair(Line, cont, Arg), {'fun', Line, {clauses, Clauses1}}],
|
||||
Tuple = ?wrap_call(Line, 'Elixir.Enumerable', reduce, Args),
|
||||
Tuple = elixir_utils:erl_call(Line, 'Elixir.Enumerable', reduce, Args),
|
||||
|
||||
%% Use -1 because in case of no returns we don't care about the result
|
||||
?wrap_call(-1, erlang, element, [{integer, Line, 2}, Tuple]);
|
||||
elixir_utils:erl_call(-1, erlang, element, [{integer, Line, 2}, Tuple]);
|
||||
|
||||
build_reduce_clause([{ bin, Meta, Left, Right, Filters }|T], Expr, Arg, Acc, S) ->
|
||||
build_reduce_clause([{bin, Meta, Left, Right, Filters}|T], Expr, Arg, Acc, S) ->
|
||||
Line = ?line(Meta),
|
||||
{ Tail, ST } = build_var(Line, S),
|
||||
{ Fun, SF } = build_var(Line, ST),
|
||||
{Tail, ST} = build_var(Line, S),
|
||||
{Fun, SF} = build_var(Line, ST),
|
||||
|
||||
True = build_reduce_clause(T, Expr, Acc, Acc, SF),
|
||||
False = Acc,
|
||||
@@ -235,9 +235,9 @@ build_reduce_clause([{ bin, Meta, Left, Right, Filters }|T], Expr, Arg, Acc, S)
|
||||
{bin, _, Elements} = Left,
|
||||
|
||||
BinMatch =
|
||||
{bin, Line, Elements ++ [{ bin_element, Line, Tail, default, [bitstring] }]},
|
||||
{bin, Line, Elements ++ [{bin_element, Line, Tail, default, [bitstring]}]},
|
||||
NoVarMatch =
|
||||
{bin, Line, no_var(Elements) ++ [{ bin_element, Line, Tail, default, [bitstring] }]},
|
||||
{bin, Line, no_var(Elements) ++ [{bin_element, Line, Tail, default, [bitstring]}]},
|
||||
|
||||
Clauses =
|
||||
[{clause, Line,
|
||||
@@ -251,7 +251,7 @@ build_reduce_clause([{ bin, Meta, Left, Right, Filters }|T], Expr, Arg, Acc, S)
|
||||
[Acc]},
|
||||
{clause, -1,
|
||||
[Tail, {var, Line, '_'}], [],
|
||||
[?wrap_call(Line, erlang, error, [pair(Line, badarg, Tail)])]}],
|
||||
[elixir_utils:erl_call(Line, erlang, error, [pair(Line, badarg, Tail)])]}],
|
||||
|
||||
{call, Line,
|
||||
{named_fun, Line, element(3, Fun), Clauses},
|
||||
@@ -267,13 +267,13 @@ pair(Line, Atom, Arg) ->
|
||||
{tuple, Line, [{atom, Line, Atom}, Arg]}.
|
||||
|
||||
build_var(Line, S) ->
|
||||
{ Name, _, ST } = elixir_scope:build_var('_', S),
|
||||
{ { var, Line, Name }, ST }.
|
||||
{Name, _, ST} = elixir_scope:build_var('_', S),
|
||||
{{var, Line, Name}, ST}.
|
||||
|
||||
no_var(Elements) ->
|
||||
[{bin_element, Line, no_var_expr(Expr), Size, Types} ||
|
||||
{bin_element, Line, Expr, Size, Types} <- Elements].
|
||||
no_var_expr({ var, Line, _ }) ->
|
||||
no_var_expr({var, Line, _}) ->
|
||||
{var, Line, '_'}.
|
||||
|
||||
build_comprehension(Line, Clauses, Expr, false) ->
|
||||
@@ -284,7 +284,7 @@ build_comprehension(Line, Clauses, Expr, false) ->
|
||||
build_comprehension(Line, Clauses, Expr, Into) ->
|
||||
{comprehension_kind(Into), Line, Expr, comprehension_clause(Clauses)}.
|
||||
|
||||
comprehension_clause([{ Kind, Meta, Left, Right, Filters }|T]) ->
|
||||
comprehension_clause([{Kind, Meta, Left, Right, Filters}|T]) ->
|
||||
Line = ?line(Meta),
|
||||
[{comprehension_generator(Kind), Line, Left, Right}] ++
|
||||
comprehension_filter(Line, Filters) ++
|
||||
@@ -292,26 +292,26 @@ comprehension_clause([{ Kind, Meta, Left, Right, Filters }|T]) ->
|
||||
comprehension_clause([]) ->
|
||||
[].
|
||||
|
||||
comprehension_kind({ nil, _ }) -> lc;
|
||||
comprehension_kind({ bin, _, [] }) -> bc.
|
||||
comprehension_kind({nil, _}) -> lc;
|
||||
comprehension_kind({bin, _, []}) -> bc.
|
||||
|
||||
comprehension_generator(enum) -> generate;
|
||||
comprehension_generator(bin) -> b_generate.
|
||||
|
||||
comprehension_expr({ bin, _, [] }, { bin, _, _ } = Expr) ->
|
||||
{ inline, Expr };
|
||||
comprehension_expr({ bin, Line, [] }, Expr) ->
|
||||
BinExpr = { bin, Line, [{ bin_element, Line, Expr, default, [bitstring] }] },
|
||||
{ inline, BinExpr };
|
||||
comprehension_expr({ nil, _ }, Expr) ->
|
||||
{ inline, Expr };
|
||||
comprehension_expr({bin, _, []}, {bin, _, _} = Expr) ->
|
||||
{inline, Expr};
|
||||
comprehension_expr({bin, Line, []}, Expr) ->
|
||||
BinExpr = {bin, Line, [{bin_element, Line, Expr, default, [bitstring]}]},
|
||||
{inline, BinExpr};
|
||||
comprehension_expr({nil, _}, Expr) ->
|
||||
{inline, Expr};
|
||||
comprehension_expr(false, Expr) ->
|
||||
{ inline, Expr };
|
||||
{inline, Expr};
|
||||
comprehension_expr(_, Expr) ->
|
||||
{ into, Expr }.
|
||||
{into, Expr}.
|
||||
|
||||
comprehension_filter(Line, Filters) ->
|
||||
[join_filter(Line, Filter, { atom, Line, true }, { atom, Line, false }) ||
|
||||
[join_filter(Line, Filter, {atom, Line, true}, {atom, Line, false}) ||
|
||||
Filter <- lists:reverse(Filters)].
|
||||
|
||||
join_filters(_Line, [], True, _False) ->
|
||||
@@ -321,18 +321,18 @@ join_filters(Line, [H|T], True, False) ->
|
||||
join_filter(Line, Filter, Acc, False)
|
||||
end, join_filter(Line, H, True, False), T).
|
||||
|
||||
join_filter(Line, { nil, Filter }, True, False) ->
|
||||
join_filter(Line, {nil, Filter}, True, False) ->
|
||||
{'case', Line, Filter, [
|
||||
{clause, Line, [{atom, Line, true}], [], [True] },
|
||||
{clause, Line, [{atom, Line, false}], [], [False] }
|
||||
] };
|
||||
join_filter(Line, { Var, Filter }, True, False) ->
|
||||
{clause, Line, [{atom, Line, true}], [], [True]},
|
||||
{clause, Line, [{atom, Line, false}], [], [False]}
|
||||
]};
|
||||
join_filter(Line, {Var, Filter}, True, False) ->
|
||||
Guard =
|
||||
{op, Line, 'orelse',
|
||||
{op, Line, '==', Var, {atom, Line, false}},
|
||||
{op, Line, '==', Var, {atom, Line, nil}}},
|
||||
|
||||
{'case', Line, Filter, [
|
||||
{clause, Line, [Var], [[Guard]], [False] },
|
||||
{clause, Line, [{var, Line, '_'}], [], [True] }
|
||||
] }.
|
||||
{clause, Line, [Var], [[Guard]], [False]},
|
||||
{clause, Line, [{var, Line, '_'}], [], [True]}
|
||||
]}.
|
||||
|
||||
@@ -10,28 +10,28 @@
|
||||
import(Meta, Ref, Opts, E) ->
|
||||
Res =
|
||||
case keyfind(only, Opts) of
|
||||
{ only, functions } ->
|
||||
{ import_functions(Meta, Ref, Opts, E),
|
||||
E#elixir_env.macros };
|
||||
{ only, macros } ->
|
||||
{ E#elixir_env.functions,
|
||||
import_macros(true, Meta, Ref, Opts, E) };
|
||||
{ only, List } when is_list(List) ->
|
||||
{ import_functions(Meta, Ref, Opts, E),
|
||||
import_macros(false, Meta, Ref, Opts, E) };
|
||||
{only, functions} ->
|
||||
{import_functions(Meta, Ref, Opts, E),
|
||||
?m(E, macros)};
|
||||
{only, macros} ->
|
||||
{?m(E, functions),
|
||||
import_macros(true, Meta, Ref, Opts, E)};
|
||||
{only, List} when is_list(List) ->
|
||||
{import_functions(Meta, Ref, Opts, E),
|
||||
import_macros(false, Meta, Ref, Opts, E)};
|
||||
false ->
|
||||
{ import_functions(Meta, Ref, Opts, E),
|
||||
import_macros(false, Meta, Ref, Opts, E) }
|
||||
{import_functions(Meta, Ref, Opts, E),
|
||||
import_macros(false, Meta, Ref, Opts, E)}
|
||||
end,
|
||||
|
||||
record_warn(Meta, Ref, Opts, E),
|
||||
Res.
|
||||
|
||||
import_functions(Meta, Ref, Opts, E) ->
|
||||
calculate(Meta, Ref, Opts, E#elixir_env.functions, E, fun() -> get_functions(Ref) end).
|
||||
calculate(Meta, Ref, Opts, ?m(E, functions), E, fun() -> get_functions(Ref) end).
|
||||
|
||||
import_macros(Force, Meta, Ref, Opts, E) ->
|
||||
calculate(Meta, Ref, Opts, E#elixir_env.macros, E, fun() ->
|
||||
calculate(Meta, Ref, Opts, ?m(E, macros), E, fun() ->
|
||||
case Force of
|
||||
true -> get_macros(Meta, Ref, E);
|
||||
false -> get_optional_macros(Ref)
|
||||
@@ -41,29 +41,29 @@ import_macros(Force, Meta, Ref, Opts, E) ->
|
||||
record_warn(Meta, Ref, Opts, E) ->
|
||||
Warn =
|
||||
case keyfind(warn, Opts) of
|
||||
{ warn, false } -> false;
|
||||
{ warn, true } -> true;
|
||||
{warn, false} -> false;
|
||||
{warn, true} -> true;
|
||||
false -> not lists:keymember(context, 1, Meta)
|
||||
end,
|
||||
elixir_lexical:record_import(Ref, ?line(Meta), Warn, E#elixir_env.lexical_tracker).
|
||||
elixir_lexical:record_import(Ref, ?line(Meta), Warn, ?m(E, lexical_tracker)).
|
||||
|
||||
%% Calculates the imports based on only and except
|
||||
|
||||
calculate(Meta, Key, Opts, Old, E, Existing) ->
|
||||
New = case keyfind(only, Opts) of
|
||||
{ only, Only } when is_list(Only) ->
|
||||
{only, Only} when is_list(Only) ->
|
||||
case Only -- get_exports(Key) of
|
||||
[{Name,Arity}|_] ->
|
||||
Tuple = { invalid_import, { Key, Name, Arity } },
|
||||
elixir_errors:form_error(Meta, E#elixir_env.file, ?MODULE, Tuple);
|
||||
Tuple = {invalid_import, {Key, Name, Arity}},
|
||||
elixir_errors:form_error(Meta, ?m(E, file), ?MODULE, Tuple);
|
||||
_ ->
|
||||
intersection(Only, Existing())
|
||||
end;
|
||||
_ ->
|
||||
case keyfind(except, Opts) of
|
||||
false -> remove_underscored(Existing());
|
||||
{ except, [] } -> remove_underscored(Existing());
|
||||
{ except, Except } when is_list(Except) ->
|
||||
{except, []} -> remove_underscored(Existing());
|
||||
{except, Except} when is_list(Except) ->
|
||||
case keyfind(Key, Old) of
|
||||
false -> remove_underscored(Existing()) -- Except;
|
||||
{Key,OldImports} -> OldImports -- Except
|
||||
@@ -78,8 +78,8 @@ calculate(Meta, Key, Opts, Old, E, Existing) ->
|
||||
case Final of
|
||||
[] -> keydelete(Key, Old);
|
||||
_ ->
|
||||
ensure_no_special_form_conflict(Meta, E#elixir_env.file, Key, Final),
|
||||
[{ Key, Final }|keydelete(Key, Old)]
|
||||
ensure_no_special_form_conflict(Meta, ?m(E, file), Key, Final),
|
||||
[{Key, Final}|keydelete(Key, Old)]
|
||||
end.
|
||||
|
||||
%% Retrieve functions and macros from modules
|
||||
@@ -103,19 +103,19 @@ get_macros(Meta, Module, E) ->
|
||||
Module:'__info__'(macros)
|
||||
catch
|
||||
error:undef ->
|
||||
Tuple = { no_macros, Module },
|
||||
elixir_errors:form_error(Meta, E#elixir_env.file, ?MODULE, Tuple)
|
||||
Tuple = {no_macros, Module},
|
||||
elixir_errors:form_error(Meta, ?m(E, file), ?MODULE, Tuple)
|
||||
end.
|
||||
|
||||
get_optional_macros(Module) ->
|
||||
case code:ensure_loaded(Module) of
|
||||
{ module, Module } ->
|
||||
{module, Module} ->
|
||||
try
|
||||
Module:'__info__'(macros)
|
||||
catch
|
||||
error:undef -> []
|
||||
end;
|
||||
{ error, _ } -> []
|
||||
{error, _} -> []
|
||||
end.
|
||||
|
||||
%% VALIDATION HELPERS
|
||||
@@ -123,7 +123,7 @@ get_optional_macros(Module) ->
|
||||
ensure_no_special_form_conflict(Meta, File, Key, [{Name,Arity}|T]) ->
|
||||
case special_form(Name, Arity) of
|
||||
true ->
|
||||
Tuple = { special_form_conflict, { Key, Name, Arity } },
|
||||
Tuple = {special_form_conflict, {Key, Name, Arity}},
|
||||
elixir_errors:form_error(Meta, File, ?MODULE, Tuple);
|
||||
false ->
|
||||
ensure_no_special_form_conflict(Meta, File, Key, T)
|
||||
@@ -141,7 +141,7 @@ format_error({special_form_conflict,{Receiver, Name, Arity}}) ->
|
||||
io_lib:format("cannot import ~ts.~ts/~B because it conflicts with Elixir special forms",
|
||||
[elixir_aliases:inspect(Receiver), Name, Arity]);
|
||||
|
||||
format_error({ no_macros, Module }) ->
|
||||
format_error({no_macros, Module}) ->
|
||||
io_lib:format("could not load macros from module ~ts", [elixir_aliases:inspect(Module)]).
|
||||
|
||||
%% LIST HELPERS
|
||||
@@ -163,7 +163,7 @@ intersection([], _All) -> [].
|
||||
%% Internal funs that are never imported etc.
|
||||
|
||||
remove_underscored(List) ->
|
||||
lists:filter(fun({ Name, _ }) ->
|
||||
lists:filter(fun({Name, _}) ->
|
||||
case atom_to_list(Name) of
|
||||
"_" ++ _ -> false;
|
||||
_ -> true
|
||||
@@ -171,8 +171,8 @@ remove_underscored(List) ->
|
||||
end, List).
|
||||
|
||||
remove_internals(Set) ->
|
||||
ordsets:del_element({ module_info, 1 },
|
||||
ordsets:del_element({ module_info, 0 }, Set)).
|
||||
ordsets:del_element({module_info, 1},
|
||||
ordsets:del_element({module_info, 0}, Set)).
|
||||
|
||||
%% Special forms
|
||||
|
||||
|
||||
@@ -17,13 +17,13 @@ extract(Line, _Scope, _Interpol, [], Buffer, 0, Output, []) ->
|
||||
finish_extraction(Line, Buffer, Output, []);
|
||||
|
||||
extract(Line, _Scope, _Interpol, [], _Buffer, 0, _Output, Last) ->
|
||||
{ error, { string, Line, io_lib:format("missing terminator: ~ts", [[Last]]), [] } };
|
||||
{error, {string, Line, io_lib:format("missing terminator: ~ts", [[Last]]), []}};
|
||||
|
||||
extract(Line, _Scope, _Interpol, [Last|Remaining], Buffer, 0, 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]]), [] } };
|
||||
{error, {string, Line, io_lib:format("missing terminator: ~ts", [[Last]]), []}};
|
||||
|
||||
%% Going through the string
|
||||
|
||||
@@ -46,13 +46,13 @@ extract(Line, Scope, true, [$#, ${|Rest], Buffer, Search, Output, Last) ->
|
||||
Output1 = build_string(Line, Buffer, Output),
|
||||
|
||||
case elixir_tokenizer:tokenize(Rest, Line, Scope) of
|
||||
{ error, { EndLine, _, "}" }, [$}|NewRest], Tokens } ->
|
||||
{error, {EndLine, _, "}"}, [$}|NewRest], Tokens} ->
|
||||
Output2 = build_interpol(Line, Tokens, Output1),
|
||||
extract(EndLine, Scope, true, NewRest, [], Search, Output2, Last);
|
||||
{ error, Reason, _, _ } ->
|
||||
{ error, Reason };
|
||||
{ ok, _EndLine, _ } ->
|
||||
{ error, { string, Line, "missing interpolation terminator: }", [] } }
|
||||
{error, Reason, _, _} ->
|
||||
{error, Reason};
|
||||
{ok, _EndLine, _} ->
|
||||
{error, {string, Line, "missing interpolation terminator:}", []}}
|
||||
end;
|
||||
|
||||
%% Matching () [] {} <> inside sigils
|
||||
@@ -173,11 +173,11 @@ finish_extraction(Line, Buffer, Output, Remaining) ->
|
||||
[] -> Final = [<<>>];
|
||||
Final -> []
|
||||
end,
|
||||
{ Line, lists:reverse(Final), Remaining }.
|
||||
{Line, lists:reverse(Final), Remaining}.
|
||||
|
||||
build_string(_Line, [], Output) -> Output;
|
||||
build_string(_Line, Buffer, Output) ->
|
||||
[elixir_utils:characters_to_binary(lists:reverse(Buffer))|Output].
|
||||
|
||||
build_interpol(Line, Buffer, Output) ->
|
||||
[{ Line, lists:reverse(Buffer) }|Output].
|
||||
[{Line, lists:reverse(Buffer)}|Output].
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user