Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
91e8e21242 | ||
|
|
d3013aa1d1 | ||
|
|
2c98c0999d | ||
|
|
94f9d4e3f6 | ||
|
|
66476b31d1 | ||
|
|
7f84211bb3 | ||
|
|
474250e6f4 | ||
|
|
65e67cdc27 | ||
|
|
6b91e46702 | ||
|
|
c83f136ad4 | ||
|
|
59a9ddb7d5 | ||
|
|
1ef67c570e | ||
|
|
2403d0c4b9 | ||
|
|
a1ec5f1552 | ||
|
|
45485fba59 | ||
|
|
97ea102a79 | ||
|
|
6e8fb6c630 | ||
|
|
854942d3ee | ||
|
|
d99ec5addf | ||
|
|
3f946d4af0 | ||
|
|
9318f747f4 | ||
|
|
e216f18010 | ||
|
|
ed14ce4bc8 | ||
|
|
0cb936a600 | ||
|
|
328541be87 | ||
|
|
85b76843b0 | ||
|
|
f688899b95 | ||
|
|
e3fb2a5acf | ||
|
|
5a99fbfdcd | ||
|
|
c634a5d232 | ||
|
|
9a6ac4f76c | ||
|
|
6af7e0cc6b | ||
|
|
7ff3e1f833 | ||
|
|
d6d630316e | ||
|
|
4714b54222 | ||
|
|
4d51bd64f0 | ||
|
|
fc1945acff | ||
|
|
5c0ddbb063 | ||
|
|
880ae1116d | ||
|
|
68923112cc | ||
|
|
865608b5de | ||
|
|
26640e3940 | ||
|
|
59d6448609 | ||
|
|
31b4290797 | ||
|
|
39c33c7bb1 | ||
|
|
37054c8226 | ||
|
|
9751ca70c1 | ||
|
|
054ff457be | ||
|
|
b5fce07ec0 | ||
|
|
d722685c6a | ||
|
|
beaa746fcb | ||
|
|
c3d6a96dac | ||
|
|
d238d50579 | ||
|
|
4bde3c85ed | ||
|
|
9b0353a747 | ||
|
|
b6cb6ca88a | ||
|
|
33ab2895a0 | ||
|
|
245bc09d7b | ||
|
|
0abeef0724 | ||
|
|
771708764f | ||
|
|
08e6afe5a9 | ||
|
|
c4c0798c7e | ||
|
|
392a2fbef2 | ||
|
|
bcd4b97253 | ||
|
|
614dc52af4 | ||
|
|
8907edca3e | ||
|
|
a893d805aa | ||
|
|
0142587fd2 | ||
|
|
56a52f162b | ||
|
|
5ec2de4b76 | ||
|
|
d9b98a62c8 | ||
|
|
78a0abc08f | ||
|
|
0328d867a9 | ||
|
|
05b650f373 | ||
|
|
b3e8a3d8fe | ||
|
|
021dc541b8 | ||
|
|
af0970fd0b | ||
|
|
565fc8bffe | ||
|
|
a6317d525e | ||
|
|
e96963c4a4 | ||
|
|
a57be7379b | ||
|
|
79020c768f | ||
|
|
714b4440e3 | ||
|
|
1c505ad451 | ||
|
|
5cbe0baa2d | ||
|
|
98ed3045bb | ||
|
|
441c333939 | ||
|
|
bf356495a4 | ||
|
|
08211b7c52 | ||
|
|
216019c34f | ||
|
|
16555c974a | ||
|
|
869e90f2e5 | ||
|
|
80633a2ec6 | ||
|
|
b5d20d3135 | ||
|
|
911eb23ee2 | ||
|
|
4a3e741070 | ||
|
|
3e44e98299 | ||
|
|
b08289768a | ||
|
|
6e5c4feac0 | ||
|
|
be16bf487a | ||
|
|
647344ea9d | ||
|
|
6589d17527 | ||
|
|
2d870cf528 | ||
|
|
36fab9dafb | ||
|
|
d61a82565b | ||
|
|
a63e4da167 | ||
|
|
de1d47f9d5 | ||
|
|
1051b61605 | ||
|
|
f8f37c2b24 | ||
|
|
5e0f171655 | ||
|
|
521249edfe | ||
|
|
a67e445883 | ||
|
|
a0544478a6 | ||
|
|
056df8c5d3 | ||
|
|
6d6ec1ea11 | ||
|
|
020c5b0dd2 | ||
|
|
d8e374df56 | ||
|
|
37ff5a6fda | ||
|
|
ba5a9326ed | ||
|
|
5d885a97d3 | ||
|
|
eedd68e661 | ||
|
|
1aa589afa0 | ||
|
|
c0b9c2545a | ||
|
|
1bf4d053b9 | ||
|
|
b81bf4a839 | ||
|
|
06f1f3ccd7 | ||
|
|
abc5d6f954 | ||
|
|
69fe431dd9 | ||
|
|
ad2a6774b1 | ||
|
|
44a140490e | ||
|
|
1250c70d93 | ||
|
|
62b6ed1b4c | ||
|
|
66eb369b3d | ||
|
|
6e018c5226 | ||
|
|
95377a36a5 | ||
|
|
05c1630605 | ||
|
|
46fc45df9b | ||
|
|
93f71c8624 | ||
|
|
8154066d79 | ||
|
|
a0a06941ec | ||
|
|
b47bae9e4c | ||
|
|
79dde1e97e | ||
|
|
23cbf3e853 | ||
|
|
51529dab0d | ||
|
|
26aee950fd | ||
|
|
1d6fd3c4c8 | ||
|
|
feed5c13ff | ||
|
|
baf5fcf096 | ||
|
|
e18c1602ba | ||
|
|
32ae6d3424 | ||
|
|
02d0aa1d9a | ||
|
|
5ce473152f | ||
|
|
f38cf28cbd | ||
|
|
1385bde667 | ||
|
|
06f8a12bc8 | ||
|
|
dec997e3a4 | ||
|
|
fcba92606c | ||
|
|
609d66adce | ||
|
|
8de27bf9af | ||
|
|
9118b36069 | ||
|
|
3d9fa35680 | ||
|
|
fc30aaf68c | ||
|
|
b90b8d1385 | ||
|
|
57f1a1c29c | ||
|
|
930be9d99f | ||
|
|
b9d25cb2b7 | ||
|
|
0c6ee2d297 | ||
|
|
9867b8c296 | ||
|
|
dfee7d7443 | ||
|
|
63b93d5dc9 | ||
|
|
e40f0c1ec5 | ||
|
|
26aa75318b | ||
|
|
117667de0c | ||
|
|
da45fde90c | ||
|
|
4df1149ec5 | ||
|
|
16128651dc | ||
|
|
b97343faff | ||
|
|
7b41ff3c01 | ||
|
|
132ea1569e | ||
|
|
95a30f3227 | ||
|
|
10675504d5 | ||
|
|
54988ae535 | ||
|
|
03a11b45fd | ||
|
|
dda7a4daf7 | ||
|
|
dd9ba46641 | ||
|
|
6f26ff3ba0 | ||
|
|
0e8bac7e9e | ||
|
|
bef8caf8c6 | ||
|
|
a514bfa97c | ||
|
|
993c28a4de | ||
|
|
5e2557a3a9 | ||
|
|
3b8b127753 | ||
|
|
7bbb6806e3 | ||
|
|
7bae0029f2 | ||
|
|
65f7d56a8e | ||
|
|
315d5b5608 | ||
|
|
c6222a05da | ||
|
|
1bd8484146 | ||
|
|
18c4e2b810 | ||
|
|
6d2569ff9b | ||
|
|
ce39f23388 | ||
|
|
f09453440a | ||
|
|
5a9cb7970b | ||
|
|
78ce20742d | ||
|
|
45ad425793 | ||
|
|
3f73a73da1 | ||
|
|
1d2b4de7dd | ||
|
|
a3a136bb81 | ||
|
|
a93264be81 | ||
|
|
cbe82e0912 | ||
|
|
560510c71a | ||
|
|
975d7f8dfb | ||
|
|
ddb87a96ca | ||
|
|
c53314dfc4 | ||
|
|
fa3a7031f0 | ||
|
|
f9fe24f524 | ||
|
|
3b7cd15fbc | ||
|
|
90093bd4e2 | ||
|
|
ace80048f7 | ||
|
|
2b866abb6c | ||
|
|
74e244a129 | ||
|
|
a8ab6b5a2a | ||
|
|
5841f6849b | ||
|
|
d77ea82a27 | ||
|
|
7d1a439e29 | ||
|
|
4b65fa1601 | ||
|
|
38539faaf4 | ||
|
|
1019879bd0 | ||
|
|
c14ee51d15 | ||
|
|
a0787fb4bf | ||
|
|
901fedf266 | ||
|
|
c1c4d7571f | ||
|
|
2f981b914d | ||
|
|
4c6dbcf719 | ||
|
|
33367e4224 | ||
|
|
82e597e31e | ||
|
|
e4fd9fd4c1 | ||
|
|
66756e2686 | ||
|
|
b59888a6b7 | ||
|
|
0af35cd011 | ||
|
|
5c96708ce5 | ||
|
|
6bb284d776 | ||
|
|
e33c31e4ee | ||
|
|
5c58956812 | ||
|
|
88d674b0b3 | ||
|
|
44ec3705f3 | ||
|
|
23a625b8de | ||
|
|
83c257937d | ||
|
|
43f8448623 | ||
|
|
47931bb5dd | ||
|
|
3ddf8edc93 | ||
|
|
644dd0f4fb | ||
|
|
475bdea0bb | ||
|
|
6bf1d2b84d | ||
|
|
80ae2e9bf2 | ||
|
|
ee0643c17c | ||
|
|
16e18240c6 | ||
|
|
f484c9960f | ||
|
|
17cbf7bd98 | ||
|
|
3f7dda69fb | ||
|
|
35abb1d7ef | ||
|
|
ee9f77600b | ||
|
|
61727e5c5d | ||
|
|
f791bd117c | ||
|
|
55d1e3767e | ||
|
|
5a22ba8aa2 | ||
|
|
f5c08e9df3 | ||
|
|
034cb18af8 | ||
|
|
1080e290a0 | ||
|
|
6c0d43c1c7 | ||
|
|
0b77a541eb | ||
|
|
028421ffea | ||
|
|
cebbbd5102 | ||
|
|
7d1a7635e5 | ||
|
|
af76eff96a | ||
|
|
6619e3277e | ||
|
|
ebb867d361 | ||
|
|
77bde77d25 | ||
|
|
2e96d1de4d | ||
|
|
b5ab795cdc | ||
|
|
7f9e95e223 | ||
|
|
f134066de3 | ||
|
|
90a9be6cce | ||
|
|
f45dbea495 | ||
|
|
46c5c69211 | ||
|
|
4d1b22b69c | ||
|
|
f76347235b | ||
|
|
a13eaaf1c3 | ||
|
|
7f388d8bf7 | ||
|
|
01e848058a | ||
|
|
2e26d17d83 | ||
|
|
e2fd5c257b | ||
|
|
ae0a6cefd0 | ||
|
|
e9a91d1f7a | ||
|
|
c37101a52c | ||
|
|
aad2d97baf | ||
|
|
f1b2980e8e | ||
|
|
8a4b550a90 | ||
|
|
52f36a4a96 | ||
|
|
92ac9ab144 | ||
|
|
7bd288c7cb | ||
|
|
efb61518d7 | ||
|
|
414fc86a7d | ||
|
|
2eb8fc766a | ||
|
|
2889d7c998 | ||
|
|
4f54dc07f5 | ||
|
|
b71d14fc03 | ||
|
|
02bb4970dd | ||
|
|
4a996bea17 | ||
|
|
3e6e6ee99b | ||
|
|
9f60f46044 | ||
|
|
ea29be40a4 | ||
|
|
e793ee2c67 | ||
|
|
bf3dd3f265 | ||
|
|
c03ea65115 | ||
|
|
cd7879b1b9 | ||
|
|
160631b2d5 | ||
|
|
87d57a38a3 | ||
|
|
907920de44 | ||
|
|
53d34099f1 | ||
|
|
4c70f1b3cd | ||
|
|
d2dbef1611 | ||
|
|
70432ca7f2 | ||
|
|
0cab23e3e7 |
@@ -7,7 +7,3 @@ notifications:
|
||||
- yrashk@gmail.com
|
||||
otp_release:
|
||||
- R16B
|
||||
- R15B03
|
||||
- R15B02
|
||||
- R15B01
|
||||
- R15B
|
||||
|
||||
+67
-3
@@ -1,3 +1,68 @@
|
||||
# v0.9.0 (2013-05-23)
|
||||
|
||||
* enhancements
|
||||
* [ExUnit] `ExUnit.CaptureIO` now accepts an input to be used during capture
|
||||
* [IEx] Add support for .iex files that are loaded during shell's boot process
|
||||
* [IEx] Add `import_file/1` helper
|
||||
|
||||
* backwards incompatible changes
|
||||
* [Enum] `Enum.Iterator` was replaced by the more composable and functional `Enumerable` protocol which supports reductions
|
||||
* [File] `File.iterator/1` and `File.biniterator/1` have been removed in favor of the safe `File.iterator!/1` and `File.biniterator!/1` ones
|
||||
* [Kernel] Erlang R15 is no longer supported
|
||||
* [Kernel] Elixir modules are now represented as `Elixir.ModuleName` (using `.` instead of `-` as separator)
|
||||
|
||||
# v0.8.3 (2013-05-22)
|
||||
|
||||
* enhancements
|
||||
* [CLI] Flags `-p` and `-pr` fails if pattern match no files
|
||||
* [CLI] Support `--hidden` and `--cookie` flags for distributed Erlang
|
||||
* [Enum] Add `Enum.to_list/1`, `Enum.member?/2`, `Enum.uniq/2`, `Enum.max/1`, `Enum.max/2`, `Enum.min/1` and `Enum.min/2`
|
||||
* [ExUnit] Add `ExUnit.CaptureIO` for IO capturing during tests
|
||||
* [ExUnit] Consider load time on ExUnit time reports
|
||||
* [IEx] Support `ls` with colored output
|
||||
* [IEx] Add `#iex:break` to break incomplete expressions
|
||||
* [Kernel] Add `Enum.at`, `Enum.fetch` and `Enum.fetch!`
|
||||
* [Kernel] Add `String.to_integer` and `String.to_float`
|
||||
* [Kernel] Add `Dict.take`, `Dict.drop`, `Dict.split`, `Dict.pop` and `Dict.fetch!`
|
||||
* [Kernel] Many optimizations for code compilation
|
||||
* [Kernel] `in` can be used with right side expression outside guards
|
||||
* [Kernel] Add `Node.get_cookie/0` and `Node.set_cookie/2`
|
||||
* [Kernel] Add `__DIR__`
|
||||
* [Kernel] Expand macros and attributes on quote, import, alias and require
|
||||
* [Kernel] Improve warnings related to default arguments
|
||||
* [Keyword] Add `Keyword.delete_first/2`
|
||||
* [Mix] Add `local.rebar` to download a local copy of rebar, and change `deps.compile` to use it if needed
|
||||
* [Mix] Support umbrella applications
|
||||
* [Mix] Load beam files available at `MIX_PATH` on CLI usage
|
||||
* [String] Add `String.valid?` and `String.valid_character?`
|
||||
|
||||
* bug fix
|
||||
* [ExUnit] Handle exit messages from in ExUnit
|
||||
* [ExUnit] Failures on ExUnit's setup_all now invalidates all tests
|
||||
* [Kernel] Ensure we don't splice keyword args unecessarily
|
||||
* [Kernel] Private functions used by private macros no longer emit an unused warning
|
||||
* [Kernel] Ensure Elixir won't trip on empty receive blocks
|
||||
* [Kernel] `String.slice` now returns an empty string when out of range by 1
|
||||
* [Mix] Generate manifest files after compilation to avoid depending on directory timestamps and to remove unused .beam files
|
||||
* [Path] `Path.expand/2` now correctly expands `~` in the second argument
|
||||
* [Regex] Fix badmatch with `Regex.captures(%r/(.)/g, "cat")`
|
||||
* [URI] Downcase host and scheme and URIs
|
||||
|
||||
* deprecations
|
||||
* [Code] `Code.eval` is deprecated in favor of `Code.eval_string`
|
||||
* [Exception] `Exception.format_entry` is deprecated in favor of `Exception.format_stacktrace_entry`
|
||||
* [ExUnit] `assert left inlist right` is deprecated in favor of `assert left in right`
|
||||
* [IO] `IO.getb` is deprecated in favor of `IO.getn`
|
||||
* [List] `List.member?/2` is deprecated in favor of `Enum.member?/2`
|
||||
* [Kernel] `var_context` in quote was deprecated in favor of `context`
|
||||
* [Kernel] `Enum.at!` and `Dict.get!` is deprecated in favor of `Enum.fetch!` and `Dict.fetch!`
|
||||
|
||||
* backwards incompatible changes
|
||||
* [Dict] `List.Dict` was moved to `ListDict`
|
||||
* [IO] `IO.gets`, `IO.getn` and friends now return binaries when reading from stdio
|
||||
* [Kernel] Precedence of `|>` has changed to lower to support constructs like `1..5 |> Enum.to_list`
|
||||
* [Mix] `mix escriptize` now receives arguments as binaries
|
||||
|
||||
# v0.8.2 (2013-04-20)
|
||||
|
||||
* enhancements
|
||||
@@ -73,7 +138,7 @@
|
||||
* [Binary] Support syntax shortcut for specifying size in bit syntax
|
||||
* [CLI] Support `--app` option to start an application and its dependencies
|
||||
* [Dict] Support `put_new` in `Dict` and `Keyword`
|
||||
* [Dict] Add `List.Dict` and a faster `HashDict` implementation
|
||||
* [Dict] Add `ListDict` and a faster `HashDict` implementation
|
||||
* [ExUnit] ExUnit now supports multiple runs in the same process
|
||||
* [ExUnit] Failures in ExUnit now shows a tailored stacktrace
|
||||
* [ExUnit] Introduce `ExUnit.ExpectationError` to provide better error messages
|
||||
@@ -100,7 +165,7 @@
|
||||
* [Kernel] The AST now allows metadata to be attached to each node. This means the second item in the AST is no longer an integer (representing the line), but a keywords list. Code that relies on the line information from AST or that manually generate AST nodes need to be properly updated
|
||||
|
||||
* deprecations
|
||||
* [Dict] Deprecate `Binary.Dict` and `OrdDict` in favor of `HashDict` and `List.Dict`
|
||||
* [Dict] Deprecate `Binary.Dict` and `OrdDict` in favor of `HashDict` and `ListDict`
|
||||
* [File] Deprecate path related functions in favor of the module `Path`
|
||||
* [Kernel] The `/>` operator has been deprecated in favor of `|>`
|
||||
* [Mix] `Mix.Project.sources` is deprecated in favor of `Mix.Project.config_files`
|
||||
@@ -129,7 +194,6 @@
|
||||
|
||||
* deprecations
|
||||
* [Enum] `Enum.qsort` is deprecated in favor of `Enum.sort`
|
||||
* [ExUnit] `assert left in right` is deprecated in favor of `assert left inlist right`
|
||||
* [List] `List.sort` and `List.uniq` have been deprecated in favor of their `Enum` counterparts
|
||||
* [Record] Default-based generated functions are deprecated
|
||||
* [Typespec] Enhancements and deprecations to the `@spec/@callback` and the fun type syntax
|
||||
|
||||
+163
@@ -0,0 +1,163 @@
|
||||
# Contributing to Elixir
|
||||
|
||||
Please take a moment to review this document in order to make the contribution
|
||||
process easy and effective for everyone involved!
|
||||
|
||||
## Using the issue tracker
|
||||
|
||||
The issue tracker is the preferred channel for [bug reports](#bugs-reports),
|
||||
[features requests](#feature-requests) and [submitting pull
|
||||
requests](#pull-requests), but please **do not** use the issue tracker for
|
||||
personal support requests. Instead, use
|
||||
[the mailing list](http://groups.google.com/group/elixir-lang-core),
|
||||
[Stack Overflow](http://stackoverflow.com/questions/ask?tags=elixir), or
|
||||
[#elixir-lang](irc://chat.freenode.net/elixir-lang) on Freenode).
|
||||
|
||||
We do our best to keep the issues tracker tidy and organized, making it useful
|
||||
for everyone. For example, we classify open issues per application and perceived
|
||||
difficulty of the issue, making it easier for developers to
|
||||
[contribute to Elixir](#contributing).
|
||||
|
||||
## Bug reports
|
||||
|
||||
A bug is a _demonstrable problem_ that is caused by the code in the repository.
|
||||
Good bug reports are extremely helpful - thank you!
|
||||
|
||||
Guidelines for bug reports:
|
||||
|
||||
1. **Use the GitHub issue search** — check if the issue has already been
|
||||
reported.
|
||||
|
||||
2. **Check if the issue has been fixed** — try to reproduce it using the
|
||||
latest `master` or development branch in the repository.
|
||||
|
||||
3. **Isolate 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.
|
||||
|
||||
Example:
|
||||
|
||||
> Short and descriptive example bug report title
|
||||
>
|
||||
> A summary of the issue and the environment in which it occurs. If suitable,
|
||||
> include the steps required to reproduce the bug.
|
||||
>
|
||||
> 1. This is the first step
|
||||
> 2. This is the second step
|
||||
> 3. Further steps, etc.
|
||||
>
|
||||
> `<url>` - a link to the reduced test case (e.g. a GitHub Gist)
|
||||
>
|
||||
> Any other information you want to share that is relevant to the issue being
|
||||
> reported. This might include the lines of code that you have identified as
|
||||
> causing the bug, and potential solutions (and your opinions on their
|
||||
> merits).
|
||||
|
||||
## Feature requests
|
||||
|
||||
Feature requests are welcome. 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.
|
||||
|
||||
## Contributing
|
||||
|
||||
We incentivate everyone to contribute to Elixir and help us tackle
|
||||
existing issues! To do so, there are a few things you need to know
|
||||
about the code. First, Elixir code is divided in applications inside
|
||||
the `lib` folder:
|
||||
|
||||
* `elixir` - Contains Elixir's kernel and stdlib
|
||||
|
||||
* `eex` - Template engine that allows you to embed Elixir
|
||||
|
||||
* `ex_unit` - Simple test framework that ships with Elixir
|
||||
|
||||
* `iex` — IEx, Elixir's interactive shell
|
||||
|
||||
* `mix` — Elixir's build tool
|
||||
|
||||
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`.
|
||||
|
||||
With tests running and passing, you are ready to contribute to Elixir and
|
||||
send your pull requests.
|
||||
|
||||
## Pull requests
|
||||
|
||||
Good pull requests - patches, improvements, new features - are a fantastic
|
||||
help. They should remain focused in scope and avoid containing unrelated
|
||||
commits.
|
||||
|
||||
**IMPORTANT**: By submitting a patch, you agree to allow the project owner to
|
||||
license your work under the same license as that 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.
|
||||
|
||||
Please adhere to the coding conventions in the project (indentation,
|
||||
accurate comments, etc.) and don't forget to add your own tests and
|
||||
documentation. When working with git, we recommend the following process
|
||||
in order to craft an excellent pull request:
|
||||
|
||||
1. [Fork](http://help.github.com/fork-a-repo/) the project, clone your fork,
|
||||
and configure the remotes:
|
||||
|
||||
```bash
|
||||
# Clone your fork of the repo into the current directory
|
||||
git clone https://github.com/<your-username>/elixir
|
||||
# Navigate to the newly cloned directory
|
||||
cd elixir
|
||||
# Assign the original repo to a remote called "upstream"
|
||||
git remote add upstream https://github.com/elixir-lang/elixir
|
||||
```
|
||||
|
||||
2. If you cloned a while ago, get the latest changes from upstream:
|
||||
|
||||
```bash
|
||||
git checkout <dev-branch>
|
||||
git pull upstream <dev-branch>
|
||||
```
|
||||
|
||||
3. Create a new topic branch (off master) to contain your feature, change, or fix:
|
||||
|
||||
```bash
|
||||
git checkout -b <topic-branch-name>
|
||||
```
|
||||
|
||||
4. Commit your changes in logical chunks. Keep your commit messages organized,
|
||||
with a short description in the first line and more detailed information on
|
||||
the following lines. Feel free to use Git's
|
||||
[interactive rebase](https://help.github.com/articles/interactive-rebase)
|
||||
feature to tidy up your commits before making them public.
|
||||
|
||||
5. Locally merge (or rebase) the upstream development branch into your topic branch:
|
||||
|
||||
```bash
|
||||
git pull [--rebase] upstream <dev-branch>
|
||||
```
|
||||
|
||||
6. Push your topic branch up to your fork:
|
||||
|
||||
```bash
|
||||
git push origin <topic-branch-name>
|
||||
```
|
||||
|
||||
7. [Open a Pull Request](https://help.github.com/articles/using-pull-requests/)
|
||||
with a clear title and description.
|
||||
|
||||
We have saved some excellent pull requests we have received in the past in case
|
||||
you are looking for some examples:
|
||||
|
||||
* https://github.com/elixir-lang/elixir/pull/992
|
||||
* https://github.com/elixir-lang/elixir/pull/1041
|
||||
* https://github.com/elixir-lang/elixir/pull/1058
|
||||
* https://github.com/elixir-lang/elixir/pull/1059
|
||||
@@ -11,12 +11,12 @@ INSTALL_PATH := /usr/local
|
||||
#==> Templates
|
||||
|
||||
define APP_TEMPLATE
|
||||
$(1): lib/$(1)/ebin/Elixir-$(2).beam lib/$(1)/ebin/$(1).app
|
||||
$(1): lib/$(1)/ebin/Elixir.$(2).beam lib/$(1)/ebin/$(1).app
|
||||
|
||||
lib/$(1)/ebin/$(1).app:
|
||||
@ cd lib/$(1) && ../../bin/elixir -e "Mix.Server.start_link(:dev)" -r mix.exs -e "Mix.Task.run('compile.app')"
|
||||
|
||||
lib/$(1)/ebin/Elixir-$(2).beam: $(wildcard lib/$(1)/lib/*.ex) $(wildcard lib/$(1)/lib/*/*.ex) $(wildcard lib/$(1)/lib/*/*/*.ex)
|
||||
lib/$(1)/ebin/Elixir.$(2).beam: $(wildcard lib/$(1)/lib/*.ex) $(wildcard lib/$(1)/lib/*/*.ex) $(wildcard lib/$(1)/lib/*/*/*.ex)
|
||||
@ echo "==> $(1) (compile)"
|
||||
@ $$(ELIXIRC) "lib/$(1)/lib/**/*.ex" -o lib/$(1)/ebin
|
||||
|
||||
@@ -27,8 +27,8 @@ endef
|
||||
|
||||
#==> Compilation tasks
|
||||
|
||||
KERNEL:=lib/elixir/ebin/Elixir-Kernel.beam
|
||||
UNICODE:=lib/elixir/ebin/Elixir-String-Unicode.beam
|
||||
KERNEL:=lib/elixir/ebin/Elixir.Kernel.beam
|
||||
UNICODE:=lib/elixir/ebin/Elixir.String.Unicode.beam
|
||||
|
||||
default: compile
|
||||
|
||||
@@ -44,7 +44,7 @@ erlang:
|
||||
# Since Mix depends on EEx and EEx depends on
|
||||
# Mix, we first compile EEx without the .app
|
||||
# file, then mix and then compile eex fully
|
||||
elixir: kernel unicode lib/eex/ebin/Elixir-EEx.beam mix ex_unit eex iex
|
||||
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
|
||||
@@ -54,6 +54,7 @@ $(KERNEL): lib/elixir/lib/*.ex lib/elixir/lib/*/*.ex
|
||||
fi
|
||||
@ echo "==> kernel (compile)";
|
||||
@ $(ELIXIRC) "lib/elixir/lib/**/*.ex" -o lib/elixir/ebin;
|
||||
@ $(MAKE) unicode
|
||||
@ rm -rf lib/elixir/ebin/elixir.app
|
||||
@ cd lib/elixir && $(REBAR) compile
|
||||
|
||||
@@ -93,11 +94,13 @@ clean:
|
||||
|
||||
#==> Release tasks
|
||||
|
||||
SOURCE_REF = $(shell head="$$(git rev-parse HEAD)" tag="$$(git tag --points-at $$head | tail -1)" ; echo "$${tag:-$$head}\c")
|
||||
|
||||
docs: compile
|
||||
mkdir -p ebin
|
||||
rm -rf docs
|
||||
cp -R -f lib/*/ebin/*.beam ./ebin
|
||||
bin/elixir ../exdoc/bin/exdoc "Elixir" "$(VERSION)" -m Kernel -u "https://github.com/elixir-lang/elixir/blob/master/%{path}#L%{line}"
|
||||
bin/elixir ../ex_doc/bin/ex_doc "Elixir" "$(VERSION)" -m Kernel -u "https://github.com/elixir-lang/elixir" --source-ref "$(call SOURCE_REF)"
|
||||
rm -rf ebin
|
||||
|
||||
release_zip: compile
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
For more about Elixir, installation and documentation, [check Elixir's website](http://elixir-lang.org/).
|
||||
|
||||
# Usage
|
||||
## Usage
|
||||
|
||||
If you want to contribute to Elixir or run it from source, clone this repository to your machine, compile and test it:
|
||||
|
||||
@@ -14,29 +14,17 @@ If you want to contribute to Elixir or run it from source, clone this repository
|
||||
|
||||
If tests pass, you are ready to move on to the [Getting Started guide][1] or to try Interactive Elixir by running: `bin/iex` in your terminal.
|
||||
|
||||
However, if tests fail, it is likely you have an outdated Erlang version (Elixir requires Erlang R15B or later). You can check your Erlang version by calling `erl` in the command line. You will see some information as follow:
|
||||
However, if tests fail, it is likely you have an outdated Erlang version (Elixir requires Erlang R16B or later). You can check your Erlang version by calling `erl` in the command line. You will see some information as follow:
|
||||
|
||||
Erlang R15B (erts-5.8.4) [source] [64-bit] [smp:2:2] [rq:2] [async-threads:0] [hipe] [kernel-poll:false]
|
||||
Erlang R16B (erts-5.10.1) [source] [64-bit] [smp:2:2] [rq:2] [async-threads:0] [hipe] [kernel-poll:false]
|
||||
|
||||
If you have the correct version and tests still fail, feel free to [open an issue][2].
|
||||
|
||||
# Contributing
|
||||
## Contributing
|
||||
|
||||
If you want to contribute, Elixir code is divided in applications inside the `lib` folder:
|
||||
We appreciate any contribution to Elixir, so check out our [CONTRIBUTING.md](CONTRIBUTING.md) guide for more information. We usually keep a list of features and bugs [in the issue tracker][2].
|
||||
|
||||
* `elixir` - Contains Elixir's kernel and stdlib;
|
||||
|
||||
* `eex` - Template engine that allows you to embed Elixir;
|
||||
|
||||
* `ex_unit` - Simple test framework that ships with Elixir;
|
||||
|
||||
* `iex` — IEx, Elixir's interactive shell
|
||||
|
||||
* `mix` — Elixir's build tool
|
||||
|
||||
We usually keep a list of features and bugs [in the issue tracker][2].
|
||||
|
||||
# Important links
|
||||
## Important links
|
||||
|
||||
* #elixir-lang on freenode IRC
|
||||
* [Website][1]
|
||||
@@ -47,7 +35,7 @@ We usually keep a list of features and bugs [in the issue tracker][2].
|
||||
[2]: https://github.com/elixir-lang/elixir/issues
|
||||
[3]: http://groups.google.com/group/elixir-lang-core
|
||||
|
||||
# License
|
||||
## License
|
||||
|
||||
"Elixir" and the Elixir logo are copyright (c) 2012 Plataformatec.
|
||||
|
||||
|
||||
+4
-2
@@ -13,6 +13,8 @@ if [ $# -eq 0 ] || [ $1 = "--help" ] || [ $1 = "-h" ]; then
|
||||
--erl \"switches\" Switches to be passed down to erlang (*)
|
||||
--name \"name\" Makes and assigns a name to the distributed node
|
||||
--sname \"name\" Makes and assigns a short name to the distributed node
|
||||
--cookie \"cookie\" Sets a cookie for this distributed node
|
||||
--hidden Makes a hidden node
|
||||
--detached Starts the Erlang VM detached from console
|
||||
--no-halt Does not halt the Erlang VM after execution
|
||||
|
||||
@@ -41,10 +43,10 @@ while [ $I -le $# ]; do
|
||||
case "$PEEK" in
|
||||
-v|--compile|--no-halt)
|
||||
;;
|
||||
-e|-r|-pr|-pa|-pz|--remsh|-S)
|
||||
-e|-r|-pr|-pa|-pz|--remsh|--cookie|-S)
|
||||
S=2
|
||||
;;
|
||||
--detached)
|
||||
--detached|--hidden)
|
||||
ERL="$ERL `echo $PEEK | cut -c 2-`"
|
||||
;;
|
||||
--sname|--name)
|
||||
|
||||
+1
-1
@@ -24,4 +24,4 @@ readlink_f () {
|
||||
|
||||
SELF=$(readlink_f "$0")
|
||||
SCRIPT_PATH=$(dirname "$SELF")
|
||||
exec "$SCRIPT_PATH"/elixir --compile "$@"
|
||||
exec "$SCRIPT_PATH"/elixir +compile "$@"
|
||||
|
||||
+1
-1
@@ -18,4 +18,4 @@ echo ** Options marked with (*) can be given more than once
|
||||
echo ** Options given after -- are passed down to the executed code
|
||||
echo ** Options can be passed to the erlang runtime using ELIXIR_ERL_OPTS" >&2
|
||||
:run
|
||||
call "%~dp0\elixir.bat" --compile %*
|
||||
call "%~dp0\elixir.bat" +compile %*
|
||||
|
||||
@@ -14,6 +14,8 @@ if [ $# -gt 0 ] && ([ $1 = "--help" ] || [ $1 = "-h" ]); then
|
||||
--name \"name\" Makes and assigns a name to the distributed node
|
||||
--sname \"name\" Makes and assigns a short name to the distributed node
|
||||
--remsh \"name\" Connects to a node using a remote shell
|
||||
--dot-iex \"path\" Overrides default .iex file and uses path instead;
|
||||
path can be empty, then no file will be loaded
|
||||
|
||||
** Options marked with (*) can be given more than once
|
||||
** Options given after the .exs file or -- are passed down to the executed code
|
||||
@@ -33,4 +35,4 @@ readlink_f () {
|
||||
|
||||
SELF=$(readlink_f "$0")
|
||||
SCRIPT_PATH=$(dirname "$SELF")
|
||||
ELIXIR_NO_CLI=1 exec "$SCRIPT_PATH"/elixir --no-halt --erl "-user Elixir-IEx-CLI" "$@"
|
||||
ELIXIR_NO_CLI=1 exec "$SCRIPT_PATH"/elixir --no-halt --erl "-user Elixir.IEx.CLI" +iex "$@"
|
||||
|
||||
+1
-1
@@ -1,2 +1,2 @@
|
||||
@echo off
|
||||
call "%~dp0\elixir.bat" --no-halt -e "IEx.start" %*
|
||||
call "%~dp0\elixir.bat" +iex --no-halt -e "IEx.start" %*
|
||||
@@ -12,7 +12,7 @@ defmodule EEx.Tokenizer do
|
||||
|
||||
"""
|
||||
def tokenize(bin, line) when is_binary(bin) do
|
||||
tokenize(binary_to_list(bin), line)
|
||||
tokenize(:unicode.characters_to_list(bin), line)
|
||||
end
|
||||
|
||||
def tokenize(list, line) do
|
||||
@@ -154,6 +154,6 @@ defmodule EEx.Tokenizer do
|
||||
end
|
||||
|
||||
defp tokenize_text(line, buffer, acc) do
|
||||
[{ :text, line, list_to_binary(Enum.reverse(buffer)) } | acc]
|
||||
[{ :text, line, :unicode.characters_to_binary(Enum.reverse(buffer)) } | acc]
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
Code.require_file "../../test_helper.exs", __FILE__
|
||||
Code.require_file "../test_helper.exs", __DIR__
|
||||
|
||||
defmodule EEx.SmartEngineTest do
|
||||
use ExUnit.Case, async: true
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
Code.require_file "../../test_helper.exs", __FILE__
|
||||
Code.require_file "../test_helper.exs", __DIR__
|
||||
|
||||
defmodule EEx.TokenizerTest do
|
||||
use ExUnit.Case, async: true
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
Code.require_file "../test_helper.exs", __FILE__
|
||||
Code.require_file "test_helper.exs", __DIR__
|
||||
|
||||
require EEx
|
||||
|
||||
@@ -10,7 +10,7 @@ defmodule EExText.Compiled do
|
||||
{ :erlang, 1, 2 }.tuple_to_list
|
||||
EEx.function_from_string :def, :string_sample, "<%= a + b %>", [:a, :b]
|
||||
|
||||
filename = Path.expand("../fixtures/eex_template_with_bindings.eex", __FILE__)
|
||||
filename = Path.join(__DIR__, "fixtures/eex_template_with_bindings.eex")
|
||||
EEx.function_from_file :defp, :private_file_sample, filename, [:bar]
|
||||
def file_sample(arg), do: private_file_sample(arg)
|
||||
|
||||
@@ -270,14 +270,23 @@ foo
|
||||
assert_eval expected, string
|
||||
end
|
||||
|
||||
test "unicode" do
|
||||
template = """
|
||||
• <%= "•" %> •
|
||||
<%= "Jößé Vâlìm" %> Jößé Vâlìm
|
||||
"""
|
||||
result = EEx.eval_string(template)
|
||||
assert result == " • • •\n Jößé Vâlìm Jößé Vâlìm\n"
|
||||
end
|
||||
|
||||
test "evaluates the source from a given file" do
|
||||
filename = Path.expand("../fixtures/eex_template.eex", __FILE__)
|
||||
filename = Path.join(__DIR__, "fixtures/eex_template.eex")
|
||||
result = EEx.eval_file(filename)
|
||||
assert result == "foo bar.\n"
|
||||
end
|
||||
|
||||
test "evaluates the source from a given file with bindings" do
|
||||
filename = Path.expand("../fixtures/eex_template_with_bindings.eex", __FILE__)
|
||||
filename = Path.join(__DIR__, "fixtures/eex_template_with_bindings.eex")
|
||||
result = EEx.eval_file(filename, [bar: 1])
|
||||
assert result == "foo 1\n"
|
||||
end
|
||||
|
||||
@@ -11,7 +11,6 @@
|
||||
-record(elixir_scope, {
|
||||
context=nil, %% can be assign, guards or nil
|
||||
noname=false, %% when true, don't add new names (used by try)
|
||||
check_clauses=true, %% when true, check def clauses ordering
|
||||
super=false, %% when true, it means super was invoked
|
||||
caller=false, %% when true, it means caller was invoked
|
||||
name_args=false, %% when true, it means arguments should be named
|
||||
@@ -23,7 +22,7 @@
|
||||
extra_guards=nil, %% extra guards from args expansion
|
||||
counter=[], %% a counter for the variables defined
|
||||
local=nil, %% the scope to evaluate local functions against
|
||||
scheduled=[], %% scheduled modules to be loaded
|
||||
context_modules=[], %% modules defined in the current context
|
||||
macro_aliases=[], %% keep aliases defined inside a macro
|
||||
aliases, %% an orddict with aliases by new -> old names
|
||||
file, %% the current scope filename
|
||||
@@ -36,21 +35,42 @@
|
||||
|
||||
-record(elixir_quote, {
|
||||
line=0,
|
||||
vars_hygiene=nil,
|
||||
context=nil,
|
||||
vars_hygiene=true,
|
||||
aliases_hygiene=true,
|
||||
imports_hygiene=true,
|
||||
unquote=true
|
||||
unquote=true,
|
||||
unquoted=false,
|
||||
mark=true
|
||||
}).
|
||||
|
||||
%% Introspection
|
||||
-define(defs(Kind), Kind == def; Kind == defp; Kind == defmacro; Kind == defmacrop).
|
||||
|
||||
%% Used in tokenization and interpolation
|
||||
|
||||
-define(is_digit(S), S >= $0 andalso S =< $9).
|
||||
%% Numbers
|
||||
-define(is_hex(S), ?is_digit(S) orelse (S >= $A andalso S =< $F) orelse (S >= $a andalso S =< $f)).
|
||||
-define(is_bin(S), S >= $0 andalso S =< $1).
|
||||
-define(is_octal(S), S >= $0 andalso S =< $7).
|
||||
-define(is_leading_octal(S), S >= $0 andalso S =< $3).
|
||||
|
||||
%% Digits and letters
|
||||
-define(is_digit(S), S >= $0 andalso S =< $9).
|
||||
-define(is_upcase(S), S >= $A andalso S =< $Z).
|
||||
-define(is_downcase(S), S >= $a andalso S =< $z).
|
||||
-define(is_word(S), ?is_digit(S) orelse ?is_upcase(S) orelse ?is_downcase(S)).
|
||||
|
||||
%% Atoms
|
||||
-define(is_new_atom(S), ?is_quote(S) orelse ?is_upcase(S) orelse ?is_downcase(S) orelse (S == $_)).
|
||||
-define(is_atom(S), ?is_identifier(S) orelse (S == $@)).
|
||||
|
||||
-define(is_identifier(S), ?is_digit(S) orelse ?is_upcase(S) orelse ?is_downcase(S) orelse (S == $_)).
|
||||
-define(is_terminator(S), (S == $?) orelse (S == $!) orelse (S == $:)).
|
||||
|
||||
%% Quotes
|
||||
-define(is_quote(S), S == $" orelse S == $').
|
||||
-define(is_space(S), S == $\s; S == $\r; S == $\t; S == $\n).
|
||||
|
||||
%% Spaces
|
||||
-define(is_horizontal_space(S), (S == $\s) orelse (S == $\t)).
|
||||
-define(is_vertical_space(S), (S == $\r) orelse (S == $\n)).
|
||||
-define(is_space(S), ?is_horizontal_space(S) orelse ?is_vertical_space(S)).
|
||||
|
||||
@@ -51,9 +51,9 @@ defimpl Binary.Chars, for: List do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> to_binary 'foo'
|
||||
iex> to_binary('foo')
|
||||
"foo"
|
||||
iex> to_binary ["foo", 'bar']
|
||||
iex> to_binary(["foo", 'bar'])
|
||||
"foobar"
|
||||
|
||||
"""
|
||||
@@ -66,11 +66,19 @@ defimpl Binary.Chars, for: Number do
|
||||
@doc """
|
||||
Simply converts the number (integer or a float) to a binary.
|
||||
"""
|
||||
|
||||
@digits 20
|
||||
@limit :math.pow(10, @digits)
|
||||
|
||||
def to_binary(thing) when is_integer(thing) do
|
||||
list_to_binary integer_to_list(thing)
|
||||
integer_to_binary(thing)
|
||||
end
|
||||
|
||||
def to_binary(thing) when thing > @limit do
|
||||
float_to_binary(thing, scientific: @digits)
|
||||
end
|
||||
|
||||
def to_binary(thing) do
|
||||
list_to_binary float_to_list(thing)
|
||||
float_to_binary(thing, compact: true, decimals: @digits)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -11,8 +11,6 @@ defprotocol Binary.Inspect do
|
||||
printing.
|
||||
"""
|
||||
|
||||
@only [BitString, List, Tuple, Atom, Number, Function, PID, Port, Reference]
|
||||
|
||||
def inspect(thing, opts)
|
||||
end
|
||||
|
||||
@@ -34,15 +32,15 @@ defmodule Binary.Inspect.Utils do
|
||||
end
|
||||
|
||||
defp do_container_join([h], opts, _counter) do
|
||||
Binary.Inspect.inspect(h, opts)
|
||||
Kernel.inspect(h, opts)
|
||||
end
|
||||
|
||||
defp do_container_join([h|t], opts, counter) when is_list(t) do
|
||||
Binary.Inspect.inspect(h, opts) <> "," <> do_container_join(t, opts, decrement(counter))
|
||||
Kernel.inspect(h, opts) <> "," <> do_container_join(t, opts, decrement(counter))
|
||||
end
|
||||
|
||||
defp do_container_join([h|t], opts, _counter) do
|
||||
Binary.Inspect.inspect(h, opts) <> "|" <> Binary.Inspect.inspect(t, opts)
|
||||
Kernel.inspect(h, opts) <> "|" <> Kernel.inspect(t, opts)
|
||||
end
|
||||
|
||||
defp do_container_join([], _opts, _counter) do
|
||||
@@ -146,7 +144,7 @@ defimpl Binary.Inspect, for: Atom do
|
||||
end
|
||||
end
|
||||
|
||||
# Detect if atom is an atom alias (Elixir-Foo-Bar-Baz)
|
||||
# Detect if atom is an atom alias (Elixir.Foo.Bar.Baz)
|
||||
|
||||
defp valid_ref_identifier?("Elixir" <> rest) do
|
||||
valid_ref_piece?(rest)
|
||||
@@ -154,7 +152,7 @@ defimpl Binary.Inspect, for: Atom do
|
||||
|
||||
defp valid_ref_identifier?(_), do: false
|
||||
|
||||
defp valid_ref_piece?(<<?-, h, t :: binary>>) when h in ?A..?Z do
|
||||
defp valid_ref_piece?(<<?., h, t :: binary>>) when h in ?A..?Z do
|
||||
valid_ref_piece? valid_identifier?(t)
|
||||
end
|
||||
|
||||
@@ -276,7 +274,7 @@ defimpl Binary.Inspect, for: List do
|
||||
cond do
|
||||
:io_lib.printable_list(thing) ->
|
||||
escape(:unicode.characters_to_binary(thing), ?')
|
||||
Keyword.keyword?(thing) ->
|
||||
keyword?(thing) ->
|
||||
"[" <> join_keywords(thing, opts) <> "]"
|
||||
true ->
|
||||
container_join(thing, "[", "]", opts)
|
||||
@@ -285,7 +283,7 @@ defimpl Binary.Inspect, for: List do
|
||||
|
||||
defp join_keywords(thing, opts) do
|
||||
Enum.join(lc {key, value} inlist thing do
|
||||
key_to_binary(key, opts) <> ": " <> Binary.Inspect.inspect(value, opts)
|
||||
key_to_binary(key, opts) <> ": " <> Kernel.inspect(value, opts)
|
||||
end, ", ")
|
||||
end
|
||||
|
||||
@@ -295,6 +293,16 @@ defimpl Binary.Inspect, for: List do
|
||||
other -> other
|
||||
end
|
||||
end
|
||||
|
||||
defp keyword?([{ key, _value } | rest]) when is_atom(key) do
|
||||
case atom_to_list(key) do
|
||||
'Elixir.' ++ _ -> false
|
||||
_ -> keyword?(rest)
|
||||
end
|
||||
end
|
||||
|
||||
defp keyword?([]), do: true
|
||||
defp keyword?(_other), do: false
|
||||
end
|
||||
|
||||
defimpl Binary.Inspect, for: Tuple do
|
||||
@@ -317,28 +325,12 @@ defimpl Binary.Inspect, for: Tuple do
|
||||
|
||||
def inspect(tuple, opts) do
|
||||
unless opts[:raw] do
|
||||
record_protocol(tuple, opts)
|
||||
record_inspect(tuple, opts)
|
||||
end || container_join(tuple, "{", "}", opts)
|
||||
end
|
||||
|
||||
## Helpers
|
||||
|
||||
defp record_protocol(tuple, opts) do
|
||||
name = elem(tuple, 0)
|
||||
|
||||
if is_atom(name) and match?("Elixir-" <> _, atom_to_binary(name)) do
|
||||
unless name in [BitString, List, Tuple, Atom, Number, Any] do
|
||||
try do
|
||||
target = Module.concat(Binary.Inspect, name)
|
||||
target.inspect(tuple, opts)
|
||||
rescue
|
||||
UndefinedFunctionError ->
|
||||
record_inspect(tuple, opts)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defp record_inspect(record, opts) do
|
||||
list = tuple_to_list(record)
|
||||
[name|tail] = list
|
||||
@@ -367,12 +359,12 @@ defimpl Binary.Inspect, for: Tuple do
|
||||
end
|
||||
|
||||
defp record_join([f], [v], opts) do
|
||||
atom_to_binary(f, :utf8) <> ": " <> Binary.Inspect.inspect(v, opts)
|
||||
atom_to_binary(f, :utf8) <> ": " <> Kernel.inspect(v, opts)
|
||||
end
|
||||
|
||||
defp record_join([fh|ft], [vh|vt], opts) do
|
||||
atom_to_binary(fh, :utf8) <> ": " <>
|
||||
Binary.Inspect.inspect(vh, opts) <> ", " <>
|
||||
Kernel.inspect(vh, opts) <> ", " <>
|
||||
record_join(ft, vt, opts)
|
||||
end
|
||||
|
||||
@@ -392,12 +384,19 @@ defimpl Binary.Inspect, for: Number do
|
||||
|
||||
"""
|
||||
|
||||
@digits 20
|
||||
@limit :math.pow(10, @digits)
|
||||
|
||||
def inspect(thing, _) when is_integer(thing) do
|
||||
list_to_binary integer_to_list(thing)
|
||||
integer_to_binary(thing)
|
||||
end
|
||||
|
||||
def inspect(thing, _) when thing > @limit do
|
||||
float_to_binary(thing, scientific: @digits)
|
||||
end
|
||||
|
||||
def inspect(thing, _) do
|
||||
list_to_binary :io_lib.format("~p", [thing])
|
||||
float_to_binary(thing, compact: true, decimals: @digits)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -413,11 +412,11 @@ defimpl Binary.Inspect, for: Regex do
|
||||
"""
|
||||
|
||||
def inspect(regex, _opts) when size(regex) == 5 do
|
||||
"%r" <> Binary.Inspect.inspect(Regex.source(regex), []) <> Regex.opts(regex)
|
||||
"%r" <> Kernel.inspect(Regex.source(regex), []) <> Regex.opts(regex)
|
||||
end
|
||||
|
||||
def inspect(other, opts) do
|
||||
Binary.Inspect.inspect other, Keyword.put(opts, :raw, true)
|
||||
Kernel.inspect other, Keyword.put(opts, :raw, true)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
+34
-23
@@ -57,8 +57,14 @@ defmodule Code do
|
||||
:code.del_path(Path.expand to_char_list(path))
|
||||
end
|
||||
|
||||
@doc false
|
||||
def eval(string, binding // [], opts // []) do
|
||||
IO.write "[WARNING] Code.eval is deprecated, please use Code.eval_string instead\n#{Exception.format_stacktrace}"
|
||||
eval_string(string, binding, opts)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Evalutes the contents given by string. The second argument is the
|
||||
Evaluates the contents given by string. The second argument is the
|
||||
binding (which should be a keyword) followed by a keyword list of
|
||||
environment options. Those options can be:
|
||||
|
||||
@@ -84,46 +90,46 @@ defmodule Code do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Code.eval "a + b", [a: 1, b: 2], file: __ENV__.file, line: __ENV__.line
|
||||
iex> Code.eval_string("a + b", [a: 1, b: 2], file: __ENV__.file, line: __ENV__.line)
|
||||
{ 3, [ {:a, 1}, {:b, 2} ] }
|
||||
|
||||
For convenience, you can my pass `__ENV__` as argument and
|
||||
all imports, requires and aliases will be automatically carried
|
||||
over:
|
||||
|
||||
iex> Code.eval "a + b", [a: 1, b: 2], __ENV__
|
||||
iex> Code.eval_string("a + b", [a: 1, b: 2], __ENV__)
|
||||
{ 3, [ {:a, 1}, {:b, 2} ] }
|
||||
|
||||
"""
|
||||
def eval(string, binding // [], opts // [])
|
||||
def eval_string(string, binding // [], opts // [])
|
||||
|
||||
def eval(string, binding, Macro.Env[] = env) do
|
||||
eval(string, binding, env.to_keywords)
|
||||
def eval_string(string, binding, Macro.Env[] = env) do
|
||||
eval_string(string, binding, env.to_keywords)
|
||||
end
|
||||
|
||||
def eval(string, binding, opts) do
|
||||
def eval_string(string, binding, opts) do
|
||||
{ value, binding, _scope } =
|
||||
:elixir.eval :unicode.characters_to_list(string), binding, opts
|
||||
{ value, binding }
|
||||
end
|
||||
|
||||
@doc """
|
||||
Evalutes the quoted contents.
|
||||
Evaluates the quoted contents.
|
||||
|
||||
This function accepts a list of environment options.
|
||||
Check `Code.eval` for more information.
|
||||
Check `Code.eval_string` for more information.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> contents = quote hygiene: [vars: false], do: a + b
|
||||
...> Code.eval_quoted contents, [a: 1, b: 2], file: __ENV__.file, line: __ENV__.line
|
||||
iex> contents = quote(hygiene: [vars: false], do: a + b)
|
||||
...> Code.eval_quoted(contents, [a: 1, b: 2], file: __ENV__.file, line: __ENV__.line)
|
||||
{ 3, [ {:a, 1}, {:b, 2} ] }
|
||||
|
||||
For convenience, you can my pass `__ENV__` as argument and
|
||||
all options will be automatically extracted from the environment:
|
||||
|
||||
iex> contents = quote hygiene: [vars: false], do: a + b
|
||||
...> Code.eval_quoted contents, [a: 1, b: 2], __ENV__
|
||||
iex> contents = quote(hygiene: [vars: false], do: a + b)
|
||||
...> Code.eval_quoted(contents, [a: 1, b: 2], __ENV__)
|
||||
{ 3, [ {:a, 1}, {:b, 2} ] }
|
||||
|
||||
"""
|
||||
@@ -153,6 +159,11 @@ defmodule Code do
|
||||
* `:existing_atoms_only` - When true, raises an error
|
||||
when non-existing atoms are found by the tokenizer.
|
||||
|
||||
## Macro.to_binary/1
|
||||
|
||||
The opposite of converting a string to its AST is
|
||||
`Macro.to_binary`, which converts a AST to a binary
|
||||
representation.
|
||||
"""
|
||||
def string_to_ast(string, opts // []) do
|
||||
file = Keyword.get opts, :file, "nofile"
|
||||
@@ -205,17 +216,17 @@ defmodule Code do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Requires the given `file`. Accepts `relative_to` as an argument
|
||||
to tell where the file is located. If the file was already
|
||||
required/loaded, loads it again. It returns all the modules
|
||||
defined in the file.
|
||||
Requires the given `file`. Accepts `relative_to` as an argument to tell where
|
||||
the file is located. It returns all the modules defined in the file. If the
|
||||
file was already required/loaded, doesn't do anything and returns nil.
|
||||
|
||||
Notice that if `require_file` is invoked by different processes
|
||||
concurrently, the first process to invoke `require_file` acquires
|
||||
a lock and the remaining ones will block until the file is
|
||||
available. I.e. if `require_file` is called N times with a given
|
||||
file, the given file will be loaded only once. Check `load_file`
|
||||
if you want a file to be loaded concurrently.
|
||||
Notice that if `require_file` is invoked by different processes concurrently,
|
||||
the first process to invoke `require_file` acquires a lock and the remaining
|
||||
ones will block until the file is available. I.e. if `require_file` is called
|
||||
N times with a given file, it will be loaded only once. The first process to
|
||||
call `require_file` will get the list of loaded modules, others will get nil.
|
||||
|
||||
Check `load_file` if you want a file to be loaded concurrently.
|
||||
"""
|
||||
def require_file(file, relative_to // nil) when is_binary(file) do
|
||||
file = find_file(file, relative_to)
|
||||
|
||||
+216
-49
@@ -25,15 +25,15 @@ defmodule Dict do
|
||||
protocol:
|
||||
|
||||
iex> dict = HashDict.new
|
||||
...> dict = Dict.put dict, :hello, :world
|
||||
...> dict = Dict.put(dict, :hello, :world)
|
||||
...> dict[:hello]
|
||||
:world
|
||||
|
||||
And also the `Enum.Iterator` protocol, allowing one to write:
|
||||
And also the `Enumerable` protocol, allowing one to write:
|
||||
|
||||
Enum.each dict, fn { k, v } ->
|
||||
Enum.each(dict, fn ({ k, v }) ->
|
||||
IO.puts "#{k}: #{v}"
|
||||
end
|
||||
end)
|
||||
|
||||
"""
|
||||
|
||||
@@ -41,10 +41,13 @@ defmodule Dict do
|
||||
|
||||
@type key :: any
|
||||
@type value :: any
|
||||
@type keys :: [ key ]
|
||||
@type t :: tuple | list
|
||||
|
||||
defcallback delete(t, key) :: t
|
||||
defcallback drop(t, keys) :: t
|
||||
defcallback empty(t) :: t
|
||||
defcallback equal?(t, t) :: boolean
|
||||
defcallback get(t, key) :: value
|
||||
defcallback get(t, key, value) :: value
|
||||
defcallback get!(t, key) :: value | no_return
|
||||
@@ -52,9 +55,13 @@ defmodule Dict do
|
||||
defcallback keys(t) :: list(key)
|
||||
defcallback merge(t, t) :: t
|
||||
defcallback merge(t, t, (key, value, value -> value)) :: t
|
||||
defcallback pop(t, key) :: {value, t}
|
||||
defcallback pop(t, key, value) :: {value, t}
|
||||
defcallback put(t, key, value) :: t
|
||||
defcallback put_new(t, key, value) :: t
|
||||
defcallback size(t) :: non_neg_integer()
|
||||
defcallback split(t, keys) :: {t, t}
|
||||
defcallback take(t, keys) :: t
|
||||
defcallback to_list(t) :: list()
|
||||
defcallback update(t, key, (value -> value)) :: t | no_return
|
||||
defcallback update(t, key, value, (value -> value)) :: t
|
||||
@@ -66,7 +73,7 @@ defmodule Dict do
|
||||
is_tuple(unquote(dict)) ->
|
||||
elem(unquote(dict), 0)
|
||||
is_list(unquote(dict)) ->
|
||||
List.Dict
|
||||
ListDict
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -78,8 +85,8 @@ defmodule Dict do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> d = HashDict.new [a: 1, b: 2]
|
||||
...> Enum.sort Dict.keys d
|
||||
iex> d = HashDict.new([a: 1, b: 2])
|
||||
...> Enum.sort(Dict.keys(d))
|
||||
[:a,:b]
|
||||
|
||||
"""
|
||||
@@ -93,8 +100,8 @@ defmodule Dict do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> d = HashDict.new [a: 1, b: 2]
|
||||
...> Enum.sort Dict.values d
|
||||
iex> d = HashDict.new([a: 1, b: 2])
|
||||
...> Enum.sort(Dict.values(d))
|
||||
[1,2]
|
||||
|
||||
"""
|
||||
@@ -108,8 +115,8 @@ defmodule Dict do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> d = HashDict.new [a: 1, b: 2]
|
||||
...> Dict.size d
|
||||
iex> d = HashDict.new([a: 1, b: 2])
|
||||
...> Dict.size(d)
|
||||
2
|
||||
|
||||
"""
|
||||
@@ -123,11 +130,11 @@ defmodule Dict do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> d = HashDict.new [a: 1]
|
||||
iex> d = HashDict.new([a: 1])
|
||||
...> Dict.has_key?(d, :a)
|
||||
true
|
||||
|
||||
iex> d = HashDict.new [a: 1]
|
||||
iex> d = HashDict.new([a: 1])
|
||||
...> Dict.has_key?(d, :b)
|
||||
false
|
||||
|
||||
@@ -143,16 +150,16 @@ defmodule Dict do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> d = HashDict.new [a: 1]
|
||||
...> Dict.get d, :a
|
||||
iex> d = HashDict.new([a: 1])
|
||||
...> Dict.get(d, :a)
|
||||
1
|
||||
|
||||
iex> d = HashDict.new [a: 1]
|
||||
...> Dict.get d, :b
|
||||
iex> d = HashDict.new([a: 1])
|
||||
...> Dict.get(d, :b)
|
||||
nil
|
||||
|
||||
iex> d = HashDict.new [a: 1]
|
||||
...> Dict.get d, :b, 3
|
||||
iex> d = HashDict.new([a: 1])
|
||||
...> Dict.get(d, :b, 3)
|
||||
3
|
||||
"""
|
||||
@spec get(t, key, value) :: value
|
||||
@@ -160,23 +167,48 @@ defmodule Dict do
|
||||
target(dict).get(dict, key, default)
|
||||
end
|
||||
|
||||
@doc false
|
||||
def get!(dict, key) do
|
||||
target(dict).get!(dict, key)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns the `{ :ok, value }` associated with `key` in `dict`.
|
||||
If `dict` does not contain `key`, returns `:error`.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> d = HashDict.new([a: 1])
|
||||
...> Dict.fetch(d, :a)
|
||||
{ :ok, 1 }
|
||||
|
||||
iex> d = HashDict.new([a: 1])
|
||||
...> Dict.fetch(d, :b)
|
||||
:error
|
||||
|
||||
"""
|
||||
@spec fetch(t, key) :: value
|
||||
def fetch(dict, key) do
|
||||
target(dict).fetch(dict, key)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns the value associated with `key` in `dict`. If `dict` does not
|
||||
contain `key`, it raises `KeyError`.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> d = HashDict.new [a: 1]
|
||||
...> Dict.get d, :a
|
||||
iex> d = HashDict.new([a: 1])
|
||||
...> Dict.fetch!(d, :a)
|
||||
1
|
||||
iex> d = HashDict.new [a: 1]
|
||||
...> Dict.get! d, :b
|
||||
iex> d = HashDict.new([a: 1])
|
||||
...> Dict.fetch!(d, :b)
|
||||
** (KeyError) key not found: :b
|
||||
|
||||
"""
|
||||
@spec get!(t, key) :: value | no_return
|
||||
def get!(dict, key) do
|
||||
target(dict).get!(dict, key)
|
||||
@spec fetch!(t, key) :: value | no_return
|
||||
def fetch!(dict, key) do
|
||||
target(dict).fetch!(dict, key)
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -185,9 +217,9 @@ defmodule Dict do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> d = HashDict.new [a: 1, b: 2]
|
||||
...> d = Dict.put d, :a, 3
|
||||
...> Dict.get d, :a
|
||||
iex> d = HashDict.new([a: 1, b: 2])
|
||||
...> d = Dict.put(d, :a, 3)
|
||||
...> Dict.get(d, :a)
|
||||
3
|
||||
|
||||
"""
|
||||
@@ -201,9 +233,9 @@ defmodule Dict do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> d = HashDict.new [a: 1, b: 2]
|
||||
...> d = Dict.put_new d, :a, 3
|
||||
...> Dict.get d, :a
|
||||
iex> d = HashDict.new([a: 1, b: 2])
|
||||
...> d = Dict.put_new(d, :a, 3)
|
||||
...> Dict.get(d, :a)
|
||||
1
|
||||
|
||||
"""
|
||||
@@ -218,12 +250,12 @@ defmodule Dict do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> d = HashDict.new [a: 1, b: 2]
|
||||
...> d = Dict.delete d, :a
|
||||
...> Dict.get d, :a
|
||||
iex> d = HashDict.new([a: 1, b: 2])
|
||||
...> d = Dict.delete(d, :a)
|
||||
...> Dict.get(d, :a)
|
||||
nil
|
||||
|
||||
iex> d = HashDict.new [b: 2]
|
||||
iex> d = HashDict.new([b: 2])
|
||||
...> Dict.delete(d, :a) == d
|
||||
true
|
||||
|
||||
@@ -239,9 +271,9 @@ defmodule Dict do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> d1 = HashDict.new [a: 1, b: 2]
|
||||
...> d2 = HashDict.new [a: 3, d: 4]
|
||||
...> d = Dict.merge d1, d2
|
||||
iex> d1 = HashDict.new([a: 1, b: 2])
|
||||
...> d2 = HashDict.new([a: 3, d: 4])
|
||||
...> d = Dict.merge(d1, d2)
|
||||
...> [a: Dict.get(d, :a), b: Dict.get(d, :b), d: Dict.get(d, :d)]
|
||||
[a: 3, b: 2, d: 4]
|
||||
|
||||
@@ -258,11 +290,11 @@ defmodule Dict do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> d1 = HashDict.new [a: 1, b: 2]
|
||||
...> d2 = HashDict.new [a: 3, d: 4]
|
||||
...> d = Dict.merge d1, d2, fn _k, v1, v2 ->
|
||||
iex> d1 = HashDict.new([a: 1, b: 2])
|
||||
...> d2 = HashDict.new([a: 3, d: 4])
|
||||
...> d = Dict.merge(d1, d2, fn(_k, v1, v2) ->
|
||||
...> v1 + v2
|
||||
...> end
|
||||
...> end)
|
||||
...> [a: Dict.get(d, :a), b: Dict.get(d, :b), d: Dict.get(d, :d)]
|
||||
[a: 4, b: 2, d: 4]
|
||||
|
||||
@@ -272,15 +304,42 @@ defmodule Dict do
|
||||
target(dict).merge(dict, enum, fun)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns the value associated with `key` in `dict` as
|
||||
well as the `dict` without `key`.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> dict = HashDict.new [a: 1]
|
||||
...> {v, d} = Dict.pop dict, :a
|
||||
...> {v, Enum.sort(d)}
|
||||
{1,[]}
|
||||
|
||||
iex> dict = HashDict.new [a: 1]
|
||||
...> {v, d} = Dict.pop dict, :b
|
||||
...> {v, Enum.sort(d)}
|
||||
{nil,[a: 1]}
|
||||
|
||||
iex> dict = HashDict.new [a: 1]
|
||||
...> {v, d} = Dict.pop dict, :b, 3
|
||||
...> {v, Enum.sort(d)}
|
||||
{3,[a: 1]}
|
||||
|
||||
"""
|
||||
@spec pop(t, key, value) :: {value, t}
|
||||
def pop(dict, key, default // nil) do
|
||||
target(dict).pop(dict, key, default)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Update a value in `dict` by calling `fun` on the value to get a new
|
||||
value. An exception is generated if `key` is not present in the dict.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> d = HashDict.new [a: 1, b: 2]
|
||||
...> d = Dict.update d, :a, fn val -> -val end
|
||||
...> Dict.get d, :a
|
||||
iex> d = HashDict.new([a: 1, b: 2])
|
||||
...> d = Dict.update(d, :a, fn(val) -> -val end)
|
||||
...> Dict.get(d, :a)
|
||||
-1
|
||||
|
||||
"""
|
||||
@@ -296,9 +355,9 @@ defmodule Dict do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> d = HashDict.new [a: 1, b: 2]
|
||||
...> d = Dict.update d, :c, 3, fn val -> -val end
|
||||
...> Dict.get d, :c
|
||||
iex> d = HashDict.new([a: 1, b: 2])
|
||||
...> d = Dict.update(d, :c, 3, fn(val) -> -val end)
|
||||
...> Dict.get(d, :c)
|
||||
3
|
||||
|
||||
"""
|
||||
@@ -307,6 +366,80 @@ defmodule Dict do
|
||||
target(dict).update(dict, key, initial, fun)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns a tuple of two dicts, where the first dict contains only
|
||||
entries from `dict` with keys in `keys`, and the second dict
|
||||
contains only entries from `dict` with keys not in `keys`
|
||||
|
||||
Any non-member keys are ignored.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> d = HashDict.new([a: 1, b: 2])
|
||||
...> { d1, d2 } = Dict.split(d, [:a, :c])
|
||||
...> { Dict.to_list(d1), Dict.to_list(d2) }
|
||||
{ [a: 1], [b: 2] }
|
||||
|
||||
iex> d = HashDict.new([])
|
||||
...> { d1, d2 } = Dict.split(d, [:a, :c])
|
||||
...> { Dict.to_list(d1), Dict.to_list(d2) }
|
||||
{ [], [] }
|
||||
|
||||
iex> d = HashDict.new([a: 1, b: 2])
|
||||
...> { d1, d2 } = Dict.split(d, [:a, :b, :c])
|
||||
...> { Dict.to_list(d1), Dict.to_list(d2) }
|
||||
{ [a: 1, b: 2], [] }
|
||||
|
||||
"""
|
||||
@spec split(t, keys) :: {t, t}
|
||||
def split(dict, keys) do
|
||||
target(dict).split(dict, keys)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns a new dict where the the given `keys` a removed from `dict`.
|
||||
Any non-member keys are ignored.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> d = HashDict.new([a: 1, b: 2])
|
||||
...> d = Dict.drop(d, [:a, :c, :d])
|
||||
...> Dict.to_list(d)
|
||||
[b: 2]
|
||||
|
||||
iex> d = HashDict.new([a: 1, b: 2])
|
||||
...> d = Dict.drop(d, [:c, :d])
|
||||
...> Dict.to_list(d)
|
||||
[a: 1, b: 2]
|
||||
|
||||
"""
|
||||
@spec drop(t, keys) :: t
|
||||
def drop(dict, keys) do
|
||||
target(dict).drop(dict, keys)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns a new dict where only the keys in `keys` from `dict` are
|
||||
included. Any non-member keys are ignored.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> d = HashDict.new([a: 1, b: 2])
|
||||
...> d = Dict.take(d, [:a, :c, :d])
|
||||
...> Dict.to_list(d)
|
||||
[a: 1]
|
||||
|
||||
iex> d = HashDict.new([a: 1, b: 2])
|
||||
...> d = Dict.take(d, [:c, :d])
|
||||
...> Dict.to_list(d)
|
||||
[]
|
||||
|
||||
"""
|
||||
@spec take(t, keys) :: t
|
||||
def take(dict, keys) do
|
||||
target(dict).take(dict, keys)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns an empty dict of the same type as `dict`.
|
||||
"""
|
||||
@@ -315,6 +448,40 @@ defmodule Dict do
|
||||
target(dict).empty(dict)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Check if two dicts are equal, if the dicts are of different types they're
|
||||
first converted to lists.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> a = HashDict.new(a: 2, b: 3, f: 5, c: 123)
|
||||
...> b = ListDict.new(a: 2, b: 3, f: 5, c: 123)
|
||||
...> Dict.equal?(a, b)
|
||||
true
|
||||
|
||||
iex> a = HashDict.new(a: 2, b: 3, f: 5, c: 123)
|
||||
...> b = []
|
||||
...> Dict.equal?(a, b)
|
||||
false
|
||||
|
||||
"""
|
||||
@spec equal?(t, t) :: boolean
|
||||
def equal?(a, b) do
|
||||
a_target = target(a)
|
||||
b_target = target(b)
|
||||
|
||||
cond do
|
||||
a_target == b_target ->
|
||||
a_target.equal?(a, b)
|
||||
|
||||
a_target.size(a) == b_target.size(b) ->
|
||||
ListDict.equal?(a_target.to_list(a), b_target.to_list(b))
|
||||
|
||||
true ->
|
||||
false
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns a list of key-value pairs stored in `dict`.
|
||||
No particular order is enforced.
|
||||
|
||||
+610
-775
File diff suppressed because it is too large
Load Diff
+124
-119
@@ -1,3 +1,94 @@
|
||||
defexception RuntimeError, message: "runtime error"
|
||||
defexception ArgumentError, message: "argument error"
|
||||
defexception ArithmeticError, message: "bad argument in arithmetic expression"
|
||||
defexception SystemLimitError, message: "a system limit has been reached"
|
||||
|
||||
defexception SyntaxError, [file: nil, line: nil, description: "syntax error"] do
|
||||
def message(exception) do
|
||||
"#{Exception.format_file_line(exception.file, exception.line, nil)}#{exception.description}"
|
||||
end
|
||||
end
|
||||
|
||||
defexception TokenMissingError, [file: nil, line: nil, description: "expression is incomplete"] do
|
||||
def message(exception) do
|
||||
"#{Exception.format_file_line(exception.file, exception.line, nil)}#{exception.description}"
|
||||
end
|
||||
end
|
||||
|
||||
defexception CompileError, [file: nil, line: nil, description: "compile error"] do
|
||||
def message(exception) do
|
||||
"#{Exception.format_file_line(exception.file, exception.line, nil)}#{exception.description}"
|
||||
end
|
||||
end
|
||||
|
||||
defexception BadFunctionError, [actual: nil] do
|
||||
def message(exception) do
|
||||
"expected a function, got: #{inspect(exception.actual)}"
|
||||
end
|
||||
end
|
||||
|
||||
defexception MatchError, [actual: nil] do
|
||||
def message(exception) do
|
||||
"no match of right hand side value: #{inspect(exception.actual)}"
|
||||
end
|
||||
end
|
||||
|
||||
defexception CaseClauseError, [actual: nil] do
|
||||
def message(exception) do
|
||||
"no case clause matching: #{inspect(exception.actual)}"
|
||||
end
|
||||
end
|
||||
|
||||
defexception BadArityError, [function: nil, args: nil] do
|
||||
def message(exception) do
|
||||
"bad arity error: #{inspect(exception.function)} called with #{inspect(exception.args)}"
|
||||
end
|
||||
end
|
||||
|
||||
defexception UndefinedFunctionError, [module: nil, function: nil, arity: nil] do
|
||||
def message(exception) do
|
||||
if exception.function do
|
||||
formatted = Exception.format_module_fun_arity exception.module, exception.function, exception.arity
|
||||
"undefined function: #{formatted}"
|
||||
else
|
||||
"undefined function"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defexception FunctionClauseError, [module: nil, function: nil, arity: nil] do
|
||||
def message(exception) do
|
||||
if exception.function do
|
||||
formatted = Exception.format_module_fun_arity exception.module, exception.function, exception.arity
|
||||
"no function clause matching in #{formatted}"
|
||||
else
|
||||
"no function clause matches"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defexception Protocol.UndefinedError, [protocol: nil, structure: nil] do
|
||||
def message(exception) do
|
||||
"protocol #{inspect exception.protocol} not implemented for #{inspect exception.structure}"
|
||||
end
|
||||
end
|
||||
|
||||
defexception ErlangError, [original: nil] do
|
||||
def message(exception) do
|
||||
"erlang error: #{inspect(exception.original)}"
|
||||
end
|
||||
end
|
||||
|
||||
defexception KeyError, key: nil do
|
||||
def message(exception) do
|
||||
"key not found: #{inspect exception.key}"
|
||||
end
|
||||
end
|
||||
|
||||
defexception Enum.OutOfBoundsError, message: "out of bounds error"
|
||||
|
||||
defexception Enum.EmptyError, message: "empty error"
|
||||
|
||||
defmodule Exception do
|
||||
@moduledoc """
|
||||
Several convenience functions to work and pretty print
|
||||
@@ -14,47 +105,55 @@ defmodule Exception do
|
||||
end
|
||||
|
||||
def normalize(:badarg) do
|
||||
ArgumentError.new
|
||||
ArgumentError[]
|
||||
end
|
||||
|
||||
def normalize(:badarith) do
|
||||
ArithmeticError.new
|
||||
ArithmeticError[]
|
||||
end
|
||||
|
||||
def normalize(:system_limit) do
|
||||
SystemLimitError.new
|
||||
SystemLimitError[]
|
||||
end
|
||||
|
||||
def normalize({ :badarity, { fun, args } }) do
|
||||
BadArityError.new(function: fun, args: args)
|
||||
BadArityError[function: fun, args: args]
|
||||
end
|
||||
|
||||
def normalize({ :badfun, actual }) do
|
||||
BadFunctionError.new(actual: actual)
|
||||
BadFunctionError[actual: actual]
|
||||
end
|
||||
|
||||
def normalize({ :badmatch, actual }) do
|
||||
MatchError.new(actual: actual)
|
||||
MatchError[actual: actual]
|
||||
end
|
||||
|
||||
def normalize({ :case_clause, actual }) do
|
||||
CaseClauseError.new(actual: actual)
|
||||
CaseClauseError[actual: actual]
|
||||
end
|
||||
|
||||
def normalize(:undef) do
|
||||
UndefinedFunctionError.new from_stacktrace(:erlang.get_stacktrace)
|
||||
{ mod, fun, arity } = from_stacktrace(:erlang.get_stacktrace)
|
||||
UndefinedFunctionError[module: mod, function: fun, arity: arity]
|
||||
end
|
||||
|
||||
def normalize(:function_clause) do
|
||||
FunctionClauseError.new from_stacktrace(:erlang.get_stacktrace)
|
||||
{ mod, fun, arity } = from_stacktrace(:erlang.get_stacktrace)
|
||||
FunctionClauseError[module: mod, function: fun, arity: arity]
|
||||
end
|
||||
|
||||
def normalize({ :badarg, payload }) do
|
||||
ArgumentError.new message: "argument error: #{inspect(payload)}"
|
||||
ArgumentError[message: "argument error: #{inspect(payload)}"]
|
||||
end
|
||||
|
||||
def normalize(other) do
|
||||
ErlangError.new original: other
|
||||
ErlangError[original: other]
|
||||
end
|
||||
|
||||
@doc false
|
||||
def format_entry(entry, cwd // nil) do
|
||||
IO.write "[WARNING] Exception.format_stacktrace is deprecated, please use Exception.format_stacktrace_entry instead\n#{Exception.format_stacktrace}"
|
||||
format_stacktrace_entry(entry, cwd)
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -62,28 +161,28 @@ defmodule Exception do
|
||||
The current working directory may be given as argument, which
|
||||
is used to prettify the stacktrace.
|
||||
"""
|
||||
def format_entry(entry, cwd // nil)
|
||||
def format_stacktrace_entry(entry, cwd // nil)
|
||||
|
||||
# From Macro.Env.stacktrace
|
||||
def format_entry({ module, :__MODULE__, 0, file_line }, cwd) do
|
||||
def format_stacktrace_entry({ module, :__MODULE__, 0, file_line }, cwd) do
|
||||
"#{format_file_line(file_line, cwd)}#{inspect module} (module)"
|
||||
end
|
||||
|
||||
# From :elixir_compiler
|
||||
def format_entry({ _module, :__MODULE__, 2, file_line }, cwd) do
|
||||
def format_stacktrace_entry({ _module, :__MODULE__, 2, file_line }, cwd) do
|
||||
"#{format_file_line(file_line, cwd)}(module)"
|
||||
end
|
||||
|
||||
# From :elixir_compiler
|
||||
def format_entry({ _module, :__FILE__, 2, file_line }, cwd) do
|
||||
def format_stacktrace_entry({ _module, :__FILE__, 2, file_line }, cwd) do
|
||||
"#{format_file_line(file_line, cwd)}(file)"
|
||||
end
|
||||
|
||||
def format_entry({module, fun, arity, file_line}, cwd) do
|
||||
def format_stacktrace_entry({module, fun, arity, file_line}, cwd) do
|
||||
"#{format_file_line(file_line, cwd)}#{format_module_fun_arity(module, fun, arity)}"
|
||||
end
|
||||
|
||||
def format_entry({fun, arity, file_line}, cwd) do
|
||||
def format_stacktrace_entry({fun, arity, file_line}, cwd) do
|
||||
"#{format_file_line(file_line, cwd)}#{format_fun_arity(fun, arity)}"
|
||||
end
|
||||
|
||||
@@ -105,7 +204,7 @@ defmodule Exception do
|
||||
|
||||
case trace do
|
||||
[] -> "\n"
|
||||
s -> " " <> Enum.map_join(s, "\n ", format_entry(&1)) <> "\n"
|
||||
s -> " " <> Enum.map_join(s, "\n ", format_stacktrace_entry(&1)) <> "\n"
|
||||
end
|
||||
end
|
||||
|
||||
@@ -122,14 +221,6 @@ defmodule Exception do
|
||||
|
||||
## Helpers
|
||||
|
||||
# Check the given module is a valid exception record.
|
||||
@doc false
|
||||
def check!(module) do
|
||||
unless :erlang.function_exported(module, :message, 1) do
|
||||
raise "Expected #{inspect module} to implement message/1"
|
||||
end
|
||||
end
|
||||
|
||||
# Format fun and arity
|
||||
@doc false
|
||||
def format_fun_arity(fun, arity) do
|
||||
@@ -184,101 +275,15 @@ defmodule Exception do
|
||||
end
|
||||
end
|
||||
|
||||
defp from_stacktrace([{ module, function, args, _ }|_]) when is_list(args) do
|
||||
{ module, function, length(args) }
|
||||
end
|
||||
|
||||
defp from_stacktrace([{ module, function, arity, _ }|_]) do
|
||||
[module: module, function: function, arity: arity]
|
||||
{ module, function, arity }
|
||||
end
|
||||
|
||||
defp from_stacktrace(_), do: []
|
||||
end
|
||||
|
||||
defexception RuntimeError, message: "runtime error"
|
||||
defexception ArgumentError, message: "argument error"
|
||||
defexception ArithmeticError, message: "bad argument in arithmetic expression"
|
||||
defexception SystemLimitError, message: "a system limit has been reached"
|
||||
|
||||
defexception SyntaxError, [file: nil, line: nil, description: "syntax error"] do
|
||||
def message(exception) do
|
||||
"#{Exception.format_file_line(exception.file, exception.line, nil)}#{exception.description}"
|
||||
defp from_stacktrace(_) do
|
||||
{ nil, nil, nil }
|
||||
end
|
||||
end
|
||||
|
||||
defexception TokenMissingError, [file: nil, line: nil, description: "expression is incomplete"] do
|
||||
def message(exception) do
|
||||
"#{Exception.format_file_line(exception.file, exception.line, nil)}#{exception.description}"
|
||||
end
|
||||
end
|
||||
|
||||
defexception CompileError, [file: nil, line: nil, description: "compile error"] do
|
||||
def message(exception) do
|
||||
"#{Exception.format_file_line(exception.file, exception.line, nil)}#{exception.description}"
|
||||
end
|
||||
end
|
||||
|
||||
defexception BadFunctionError, [actual: nil] do
|
||||
def message(exception) do
|
||||
"expected a function, got: #{inspect(exception.actual)}"
|
||||
end
|
||||
end
|
||||
|
||||
defexception MatchError, [actual: nil] do
|
||||
def message(exception) do
|
||||
"no match of right hand side value: #{inspect(exception.actual)}"
|
||||
end
|
||||
end
|
||||
|
||||
defexception CaseClauseError, [actual: nil] do
|
||||
def message(exception) do
|
||||
"no case clause matching: #{inspect(exception.actual)}"
|
||||
end
|
||||
end
|
||||
|
||||
defexception BadArityError, [function: nil, args: nil] do
|
||||
def message(exception) do
|
||||
"bad arity error: #{inspect(exception.function)} called with #{inspect(exception.args)}"
|
||||
end
|
||||
end
|
||||
|
||||
defexception UndefinedFunctionError, [module: nil, function: nil, arity: nil] do
|
||||
def message(exception) do
|
||||
if exception.function do
|
||||
formatted = Exception.format_module_fun_arity exception.module, exception.function, to_arity(exception.arity)
|
||||
"undefined function: #{formatted}"
|
||||
else
|
||||
"undefined function"
|
||||
end
|
||||
end
|
||||
|
||||
defp to_arity(arity) when is_integer(arity), do: arity
|
||||
defp to_arity(list) when is_list(list), do: length(list)
|
||||
end
|
||||
|
||||
defexception FunctionClauseError, [module: nil, function: nil, arity: nil] do
|
||||
def message(exception) do
|
||||
if exception.function do
|
||||
formatted = Exception.format_module_fun_arity exception.module, exception.function, exception.arity
|
||||
"no function clause matching: #{formatted}"
|
||||
else
|
||||
"no function clause matches"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defexception Protocol.UndefinedError, [protocol: nil, structure: nil] do
|
||||
def message(exception) do
|
||||
"protocol #{inspect exception.protocol} not implemented for #{inspect exception.structure}"
|
||||
end
|
||||
end
|
||||
|
||||
defexception ErlangError, [original: nil] do
|
||||
def message(exception) do
|
||||
"erlang error: #{inspect(exception.original)}"
|
||||
end
|
||||
end
|
||||
|
||||
defexception KeyError, key: nil do
|
||||
def message(exception) do
|
||||
"key not found: #{inspect exception.key}"
|
||||
end
|
||||
end
|
||||
|
||||
defexception Enum.OutOfBoundsError, message: "out of bounds error"
|
||||
|
||||
+48
-77
@@ -930,11 +930,6 @@ defmodule File do
|
||||
`close/1` might return an old write error and not even try to close the file.
|
||||
See `open/2`.
|
||||
"""
|
||||
def close(io_device) when is_function(io_device) do
|
||||
{ iterator, _ } = io_device.()
|
||||
iterator.(:close)
|
||||
end
|
||||
|
||||
def close(io_device) do
|
||||
F.close(io_device)
|
||||
end
|
||||
@@ -962,50 +957,27 @@ defmodule File do
|
||||
end
|
||||
|
||||
"""
|
||||
def iterator(device)
|
||||
|
||||
def iterator(file) when is_binary(file) or is_list(file) do
|
||||
iterator(file, [])
|
||||
end
|
||||
|
||||
def iterator(device) do
|
||||
fn ->
|
||||
function = fn
|
||||
:ok ->
|
||||
case :io.get_line(device, '') do
|
||||
:eof ->
|
||||
close(device)
|
||||
:stop
|
||||
{ :error, reason } ->
|
||||
raise File.IteratorError, reason: reason
|
||||
data ->
|
||||
{ data, :ok }
|
||||
end
|
||||
:close ->
|
||||
close(device)
|
||||
end
|
||||
{ function, :ok }
|
||||
fn(fun, acc) ->
|
||||
do_iterator(device, fun, acc)
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Opens the given `file` with the given `mode` and
|
||||
returns its iterator. Fails for the same reasons
|
||||
as `File.open`.
|
||||
"""
|
||||
def iterator(file, mode) do
|
||||
case open(file, mode) do
|
||||
{ :ok, device } -> { :ok, iterator(device) }
|
||||
error -> error
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Same as `iterator/2` but raises if the file
|
||||
cannot be opened.
|
||||
returns its iterator. The returned iterator will
|
||||
fail for the same reasons as `File.open!`. Note
|
||||
that the file is opened when the iteration begins.
|
||||
"""
|
||||
def iterator!(file, mode // []) do
|
||||
open!(file, mode) |> iterator
|
||||
fn(fun, acc) ->
|
||||
device = open!(file, mode)
|
||||
try do
|
||||
do_iterator(device, fun, acc)
|
||||
after
|
||||
F.close(device)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -1013,50 +985,27 @@ defmodule File do
|
||||
be passed into `Enum` to iterate line by line as a
|
||||
binary. Check `iterator/1` for more information.
|
||||
"""
|
||||
def biniterator(device)
|
||||
|
||||
def biniterator(file) when is_binary(file) or is_list(file) do
|
||||
biniterator(file, [])
|
||||
end
|
||||
|
||||
def biniterator(device) do
|
||||
fn ->
|
||||
function = fn
|
||||
:ok ->
|
||||
case :file.read_line(device) do
|
||||
:eof ->
|
||||
close(device)
|
||||
:stop
|
||||
{ :error, reason } ->
|
||||
raise File.IteratorError, reason: reason
|
||||
{ :ok, data } ->
|
||||
{ data, :ok }
|
||||
end
|
||||
:close ->
|
||||
close(device)
|
||||
end
|
||||
{ function, :ok }
|
||||
fn(fun, acc) ->
|
||||
do_biniterator(device, fun, acc)
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Opens the given `file` with the given `mode` and
|
||||
returns its biniterator. Fails for the same reasons
|
||||
as `File.open`.
|
||||
"""
|
||||
def biniterator(file, mode) do
|
||||
case open(file, mode) do
|
||||
{ :ok, device } -> { :ok, biniterator(device) }
|
||||
error -> error
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Same as `biniterator/2` but raises if the file
|
||||
cannot be opened.
|
||||
returns its biniterator. The returned iterator will
|
||||
fail for the same reasons as `File.open!`. Note
|
||||
that the file is opened when the iteration begins.
|
||||
"""
|
||||
def biniterator!(file, mode // []) do
|
||||
open!(file, mode) |> biniterator
|
||||
fn(fun, acc) ->
|
||||
device = open!(file, mode)
|
||||
try do
|
||||
do_biniterator(device, fun, acc)
|
||||
after
|
||||
F.close(device)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
## Helpers
|
||||
@@ -1075,4 +1024,26 @@ defmodule File do
|
||||
|
||||
defp open_defaults([], true), do: [:binary]
|
||||
defp open_defaults([], false), do: []
|
||||
|
||||
defp do_iterator(device, acc, fun) do
|
||||
case :io.get_line(device, '') do
|
||||
:eof ->
|
||||
acc
|
||||
{ :error, reason } ->
|
||||
raise File.IteratorError, reason: reason
|
||||
data ->
|
||||
do_iterator(device, fun.(data, acc), fun)
|
||||
end
|
||||
end
|
||||
|
||||
defp do_biniterator(device, acc, fun) do
|
||||
case F.read_line(device) do
|
||||
:eof ->
|
||||
acc
|
||||
{ :error, reason } ->
|
||||
raise File.IteratorError, reason: reason
|
||||
{ :ok, data } ->
|
||||
do_iterator(device, fun.(data, acc), fun)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
+177
-71
@@ -33,27 +33,26 @@ defmodule HashDict do
|
||||
# set of buckets
|
||||
|
||||
# The ordered record contains a single bucket
|
||||
ordered_threshold = 8
|
||||
@ordered_threshold 8
|
||||
|
||||
defrecordp :ordered,
|
||||
size: 0,
|
||||
bucket: []
|
||||
|
||||
# The bucketed record contains a series of buckets.
|
||||
expand_load = 5
|
||||
contract_load = 2
|
||||
node_bitmap = 0b111
|
||||
node_shift = 3
|
||||
node_size = 8
|
||||
node_template = :erlang.make_tuple(node_size, [])
|
||||
node_escaped = Macro.escape(node_template)
|
||||
@expand_load 5
|
||||
@contract_load 2
|
||||
@node_bitmap 0b111
|
||||
@node_shift 3
|
||||
@node_size 8
|
||||
@node_template :erlang.make_tuple(@node_size, [])
|
||||
|
||||
defrecordp :trie,
|
||||
size: 0,
|
||||
depth: 0,
|
||||
expand_on: node_size * expand_load,
|
||||
contract_on: contract_load,
|
||||
root: node_template
|
||||
expand_on: @node_size * @expand_load,
|
||||
contract_on: @contract_load,
|
||||
root: @node_template
|
||||
|
||||
import Bitwise
|
||||
|
||||
@@ -152,17 +151,34 @@ defmodule HashDict do
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Gets the value under key from the dict,
|
||||
raises KeyError if such key does not exist.
|
||||
"""
|
||||
@doc false
|
||||
def get!(dict, key) when is_tuple(dict) do
|
||||
IO.write "[WARNING] Dict.get! and HashDict.get! are deprecated, please use Dict.fetch! and HashDict.fetch! instead\n#{Exception.format_stacktrace}"
|
||||
case dict_get(dict, key) do
|
||||
{ ^key, value } -> value
|
||||
false -> raise(KeyError, key: key)
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Fetches the value under key from the dict
|
||||
and return it in a tagged tuple.
|
||||
"""
|
||||
def fetch(dict, key) do
|
||||
case dict_get(dict, key) do
|
||||
{ ^key, value } -> { :ok, value }
|
||||
false -> :error
|
||||
end
|
||||
end
|
||||
|
||||
def fetch!(dict, key) when is_tuple(dict) do
|
||||
case dict_get(dict, key) do
|
||||
{ ^key, value } -> value
|
||||
false -> raise(KeyError, key: key)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@doc """
|
||||
Checks if the dict has the given key.
|
||||
"""
|
||||
@@ -171,35 +187,21 @@ defmodule HashDict do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Deletes a value from the dict.
|
||||
Returns the value under key from the dict as well as the dict without key.
|
||||
"""
|
||||
def delete(ordered(bucket: bucket, size: size) = dict, key) do
|
||||
case bucket_delete(bucket, key) do
|
||||
{ _, 0 } ->
|
||||
dict
|
||||
{ new_bucket, -1 } ->
|
||||
ordered(dict, size: size - 1, bucket: new_bucket)
|
||||
def pop(dict, key, default // nil) do
|
||||
case dict_delete(dict, key) do
|
||||
{ dict, _, 0 } -> { default, dict }
|
||||
{ dict, value, _ } -> { value, dict }
|
||||
end
|
||||
end
|
||||
|
||||
def delete(trie(root: root, size: size, depth: depth) = dict, key) do
|
||||
pos = bucket_hash(key)
|
||||
case node_delete(root, depth, pos, key) do
|
||||
{ _, 0 } ->
|
||||
dict
|
||||
{ root, -1 } ->
|
||||
if depth > 0 and trie(dict, :contract_on) == size do
|
||||
root = node_contract(root, depth, depth - 1)
|
||||
trie(dict,
|
||||
root: root,
|
||||
size: size - 1,
|
||||
depth: depth - 1,
|
||||
contract_on: div(size, unquote(node_size)),
|
||||
expand_on: div(trie(dict, :expand_on), unquote(node_size)))
|
||||
else
|
||||
trie(dict, size: size - 1, root: root)
|
||||
end
|
||||
end
|
||||
@doc """
|
||||
Deletes a value from the dict.
|
||||
"""
|
||||
def delete(dict, key) do
|
||||
{ dict, _, _ } = dict_delete(dict, key)
|
||||
dict
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -216,6 +218,26 @@ defmodule HashDict do
|
||||
ordered()
|
||||
end
|
||||
|
||||
def equal?(ordered(bucket: a, size: size), ordered(bucket: b, size: ^size)) do
|
||||
a == b
|
||||
end
|
||||
|
||||
def equal?(trie(size: size) = a, trie(size: ^size) = b) do
|
||||
a == b
|
||||
end
|
||||
|
||||
def equal?(ordered() = a, trie() = b) do
|
||||
equal?(b, a)
|
||||
end
|
||||
|
||||
def equal?(trie(size: size) = a, ordered(bucket: b, size: ^size)) do
|
||||
:lists.keysort(1, to_list(a)) == :lists.keysort(1, b)
|
||||
end
|
||||
|
||||
def equal?(_, _) do
|
||||
false
|
||||
end
|
||||
|
||||
@doc """
|
||||
Converts the dict to a list.
|
||||
"""
|
||||
@@ -264,6 +286,61 @@ defmodule HashDict do
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Splits a dict into two dicts,
|
||||
one containing entries with key in the keys list,
|
||||
and another containing entries with key not in keys.
|
||||
Returns a 2-tuple of the new dicts.
|
||||
"""
|
||||
def split(dict, keys) do
|
||||
split(keys, new, dict)
|
||||
end
|
||||
|
||||
defp split([], including, excluding) do
|
||||
{ including, excluding }
|
||||
end
|
||||
|
||||
defp split([key|keys], including, excluding) do
|
||||
case dict_delete(excluding, key) do
|
||||
{ excluding, _, 0 } -> split(keys, including, excluding)
|
||||
{ excluding, value, _ } -> split(keys, put(including, key, value), excluding)
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns a new dict with only the entries
|
||||
which key is in keys.
|
||||
"""
|
||||
def take(dict, keys) do
|
||||
take(dict, keys, new)
|
||||
end
|
||||
|
||||
defp take(_dict, [], acc), do: acc
|
||||
defp take(dict, [key|keys], acc) do
|
||||
case fetch(dict, key) do
|
||||
{ :ok, value } -> take(dict, keys, put(acc, key, value))
|
||||
:error -> take(dict, keys, acc)
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns a new dict with only the entries
|
||||
which key is not in keys
|
||||
"""
|
||||
def drop(dict, []), do: dict
|
||||
|
||||
def drop(dict, [key|keys]) do
|
||||
drop(delete(dict, key), keys)
|
||||
end
|
||||
|
||||
def reduce(ordered(bucket: bucket), acc, fun) do
|
||||
:lists.foldl(fun, acc, bucket)
|
||||
end
|
||||
|
||||
def reduce(trie() = dict, acc, fun) do
|
||||
dict_fold(dict, acc, fun)
|
||||
end
|
||||
|
||||
## Dict-wide functions
|
||||
|
||||
defp dict_get(ordered(bucket: bucket), key) do
|
||||
@@ -279,12 +356,12 @@ defmodule HashDict do
|
||||
end
|
||||
|
||||
defp dict_fold(trie(root: root, depth: depth), acc, fun) do
|
||||
node_fold(root, depth, acc, fun, unquote(node_size))
|
||||
node_fold(root, depth, acc, fun, @node_size)
|
||||
end
|
||||
|
||||
defp dict_put(ordered(size: unquote(ordered_threshold), bucket: bucket), key, value) do
|
||||
defp dict_put(ordered(size: @ordered_threshold, bucket: bucket), key, value) do
|
||||
root = node_relocate(bucket, 0)
|
||||
dict_put(trie(size: unquote(ordered_threshold), root: root), key, value)
|
||||
dict_put(trie(size: @ordered_threshold, root: root), key, value)
|
||||
end
|
||||
|
||||
defp dict_put(ordered(size: size, bucket: bucket) = dict, key, value) do
|
||||
@@ -295,7 +372,7 @@ defmodule HashDict do
|
||||
defp dict_put(trie(root: root, depth: depth, size: size, expand_on: size, contract_on: contract_on) = dict, key, value) do
|
||||
root = node_expand(root, depth, depth + 1)
|
||||
dict = trie(dict, root: root, depth: depth + 1,
|
||||
expand_on: size * unquote(node_size), contract_on: contract_on * unquote(node_size))
|
||||
expand_on: size * @node_size, contract_on: contract_on * @node_size)
|
||||
dict_put(dict, key, value)
|
||||
end
|
||||
|
||||
@@ -305,6 +382,35 @@ defmodule HashDict do
|
||||
{ trie(dict, size: size + count, root: root), count }
|
||||
end
|
||||
|
||||
defp dict_delete(ordered(bucket: bucket, size: size) = dict, key) do
|
||||
case bucket_delete(bucket, key) do
|
||||
{ _, value, 0 } ->
|
||||
{ dict, value, 0 }
|
||||
{ new_bucket, value, -1 } ->
|
||||
{ ordered(dict, size: size - 1, bucket: new_bucket), value, -1 }
|
||||
end
|
||||
end
|
||||
|
||||
defp dict_delete(trie(root: root, size: size, depth: depth) = dict, key) do
|
||||
pos = bucket_hash(key)
|
||||
case node_delete(root, depth, pos, key) do
|
||||
{ _, value, 0 } ->
|
||||
{ dict, value, 0 }
|
||||
{ root, value, -1 } ->
|
||||
{ if depth > 0 and trie(dict, :contract_on) == size do
|
||||
root = node_contract(root, depth, depth - 1)
|
||||
trie(dict,
|
||||
root: root,
|
||||
size: size - 1,
|
||||
depth: depth - 1,
|
||||
contract_on: div(size, @node_size),
|
||||
expand_on: div(trie(dict, :expand_on), @node_size))
|
||||
else
|
||||
trie(dict, size: size - 1, root: root)
|
||||
end, value, -1 }
|
||||
end
|
||||
end
|
||||
|
||||
## Bucket helpers
|
||||
|
||||
# Puts a value in the bucket
|
||||
@@ -346,20 +452,20 @@ defmodule HashDict do
|
||||
|
||||
# Deletes a key from the bucket
|
||||
defp bucket_delete([{k,_}|_] = bucket, key) when key < k do
|
||||
{ bucket, 0 }
|
||||
{ bucket, nil, 0 }
|
||||
end
|
||||
|
||||
defp bucket_delete([{k,_}=e|bucket], key) when key > k do
|
||||
{ rest, count } = bucket_delete(bucket, key)
|
||||
{ [e|rest], count }
|
||||
{ rest, value, count } = bucket_delete(bucket, key)
|
||||
{ [e|rest], value, count }
|
||||
end
|
||||
|
||||
defp bucket_delete([{_,_}|bucket], _key) do
|
||||
{ bucket, -1 }
|
||||
defp bucket_delete([{_,value}|bucket], _key) do
|
||||
{ bucket, value, -1 }
|
||||
end
|
||||
|
||||
defp bucket_delete([], _key) do
|
||||
{ [], 0 }
|
||||
{ [], nil, 0 }
|
||||
end
|
||||
|
||||
# Folds the bucket
|
||||
@@ -372,15 +478,15 @@ defmodule HashDict do
|
||||
end
|
||||
|
||||
defp bucket_index(hash) do
|
||||
hash &&& unquote(node_bitmap)
|
||||
hash &&& @node_bitmap
|
||||
end
|
||||
|
||||
defp bucket_nth_index(hash, n) do
|
||||
(hash >>> (unquote(node_shift) * n)) &&& unquote(node_bitmap)
|
||||
(hash >>> (@node_shift * n)) &&& @node_bitmap
|
||||
end
|
||||
|
||||
defp bucket_next(hash) do
|
||||
hash >>> unquote(node_shift)
|
||||
hash >>> @node_shift
|
||||
end
|
||||
|
||||
## Node helpers
|
||||
@@ -412,16 +518,16 @@ defmodule HashDict do
|
||||
defp node_delete(node, 0, hash, key) do
|
||||
pos = bucket_index(hash)
|
||||
case bucket_delete(elem(node, pos), key) do
|
||||
{ _, 0 } -> { node, 0 }
|
||||
{ new, -1 } -> { set_elem(node, pos, new), -1 }
|
||||
{ _, value, 0 } -> { node, value, 0 }
|
||||
{ new, value, -1 } -> { set_elem(node, pos, new), value, -1 }
|
||||
end
|
||||
end
|
||||
|
||||
defp node_delete(node, depth, hash, key) do
|
||||
pos = bucket_index(hash)
|
||||
case node_delete(elem(node, pos), depth - 1, bucket_next(hash), key) do
|
||||
{ _, 0 } -> { node, 0 }
|
||||
{ new, -1 } -> { set_elem(node, pos, new), -1 }
|
||||
{ _, value, 0 } -> { node, value, 0 }
|
||||
{ new, value, -1 } -> { set_elem(node, pos, new), value, -1 }
|
||||
end
|
||||
end
|
||||
|
||||
@@ -431,7 +537,7 @@ defmodule HashDict do
|
||||
end
|
||||
|
||||
defp node_fold(node, depth, acc, fun, count) when count >= 1 do
|
||||
acc = node_fold(:erlang.element(count, node), depth - 1, acc, fun, unquote(node_size))
|
||||
acc = node_fold(:erlang.element(count, node), depth - 1, acc, fun, @node_size)
|
||||
node_fold(node, depth, acc, fun, count - 1)
|
||||
end
|
||||
|
||||
@@ -461,20 +567,18 @@ defmodule HashDict do
|
||||
end
|
||||
|
||||
defp node_contract({ b1, b2, b3, b4, b5, b6, b7, b8 }, 1, n) do
|
||||
unquote(node_escaped)
|
||||
|> each_contract(b1, n) |> each_contract(b2, n) |> each_contract(b3, n)
|
||||
|> each_contract(b4, n) |> each_contract(b5, n) |> each_contract(b6, n)
|
||||
|> each_contract(b7, n) |> each_contract(b8, n)
|
||||
@node_template |> each_contract(b1, n) |> each_contract(b2, n) |> each_contract(b3, n)
|
||||
|> each_contract(b4, n) |> each_contract(b5, n) |> each_contract(b6, n)
|
||||
|> each_contract(b7, n) |> each_contract(b8, n)
|
||||
end
|
||||
|
||||
defp each_contract(acc, { b1, b2, b3, b4, b5, b6, b7, b8 }, n) do
|
||||
acc
|
||||
|> node_relocate(b1, n) |> node_relocate(b2, n) |> node_relocate(b3, n)
|
||||
|> node_relocate(b4, n) |> node_relocate(b5, n) |> node_relocate(b6, n)
|
||||
|> node_relocate(b7, n) |> node_relocate(b8, n)
|
||||
acc |> node_relocate(b1, n) |> node_relocate(b2, n) |> node_relocate(b3, n)
|
||||
|> node_relocate(b4, n) |> node_relocate(b5, n) |> node_relocate(b6, n)
|
||||
|> node_relocate(b7, n) |> node_relocate(b8, n)
|
||||
end
|
||||
|
||||
defp node_relocate(node // unquote(node_escaped), bucket, n) do
|
||||
defp node_relocate(node // @node_template, bucket, n) do
|
||||
:lists.foldl fn { key, value }, acc ->
|
||||
pos = key |> bucket_hash() |> bucket_nth_index(n)
|
||||
set_elem(acc, pos, bucket_put!(elem(acc, pos), key, value))
|
||||
@@ -482,9 +586,11 @@ defmodule HashDict do
|
||||
end
|
||||
end
|
||||
|
||||
defimpl Enum.Iterator, for: HashDict do
|
||||
def iterator(dict), do: HashDict.to_list(dict)
|
||||
def count(dict), do: HashDict.size(dict)
|
||||
defimpl Enumerable, for: HashDict do
|
||||
def reduce(dict, acc, fun), do: HashDict.reduce(dict, acc, fun)
|
||||
def member?(dict, { k, v }), do: match?({ :ok, ^v }, HashDict.fetch(dict, k))
|
||||
def member?(_dict, _), do: false
|
||||
def count(dict), do: HashDict.size(dict)
|
||||
end
|
||||
|
||||
defimpl Access, for: HashDict do
|
||||
@@ -495,6 +601,6 @@ defimpl Binary.Inspect, for: HashDict do
|
||||
import Kernel, except: [inspect: 2]
|
||||
|
||||
def inspect(dict, opts) do
|
||||
"#HashDict<" <> Binary.Inspect.inspect(HashDict.to_list(dict), opts) <> ">"
|
||||
"#HashDict<" <> Kernel.inspect(HashDict.to_list(dict), opts) <> ">"
|
||||
end
|
||||
end
|
||||
|
||||
+41
-3
@@ -142,12 +142,15 @@ defmodule IO do
|
||||
Inspects the item with options using the given device.
|
||||
"""
|
||||
def inspect(device, item, opts) do
|
||||
puts device, Binary.Inspect.inspect(item, opts)
|
||||
puts device, Kernel.inspect(item, opts)
|
||||
item
|
||||
end
|
||||
|
||||
@doc """
|
||||
Gets `count` bytes from the IO device. It returns:
|
||||
Gets a number of bytes from the io device. If the
|
||||
io device is a unicode device, count implies
|
||||
the number of unicode codepoints to be retrieved.
|
||||
Otherwise, the number of raw bytes. It returns:
|
||||
|
||||
* `data` - The input characters.
|
||||
|
||||
@@ -157,10 +160,45 @@ defmodule IO do
|
||||
for instance {:error, :estale} if reading from an
|
||||
NFS file system.
|
||||
"""
|
||||
def getb(device // group_leader(), prompt, count // 1) do
|
||||
def getn(prompt, count // 1)
|
||||
|
||||
def getn(prompt, count) when is_integer(count) do
|
||||
getn(group_leader, prompt, count)
|
||||
end
|
||||
|
||||
def getn(device, prompt) do
|
||||
getn(device, prompt, 1)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Gets a number of bytes from the io device. If the
|
||||
io device is a unicode device, count implies
|
||||
the number of unicode codepoints to be retrieved.
|
||||
Otherwise, the number of raw bytes.
|
||||
"""
|
||||
def getn(device, prompt, count) do
|
||||
:io.get_chars(map_dev(device), to_iodata(prompt), count)
|
||||
end
|
||||
|
||||
@doc false
|
||||
def getb(prompt, count // 1)
|
||||
|
||||
def getb(prompt, count) when is_integer(count) do
|
||||
IO.write "[WARNING] IO.getb is deprecated, please use IO.getn instead\n#{Exception.format_stacktrace}"
|
||||
getn(prompt, count)
|
||||
end
|
||||
|
||||
def getb(device, prompt) do
|
||||
IO.write "[WARNING] IO.getb is deprecated, please use IO.getn instead\n#{Exception.format_stacktrace}"
|
||||
getn(device, prompt)
|
||||
end
|
||||
|
||||
@doc false
|
||||
def getb(device, prompt, count) do
|
||||
IO.write "[WARNING] IO.getb is deprecated, please use IO.getn instead\n#{Exception.format_stacktrace}"
|
||||
getn(device, prompt, count)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Reads a line from the IO device. It returns:
|
||||
|
||||
|
||||
+19
-36
@@ -2,16 +2,18 @@ defmodule IO.ANSI.Sequence do
|
||||
@moduledoc false
|
||||
|
||||
defmacro defsequence(name, code) do
|
||||
binary_name = atom_to_binary(name)
|
||||
|
||||
quote do
|
||||
def unquote(name)() do
|
||||
"\e[#{unquote(code)}m"
|
||||
end
|
||||
name = unquote(name)
|
||||
code = unquote(code)
|
||||
|
||||
defp escape_sequence(<< unquote(binary_name), rest :: binary >>) do
|
||||
{ "\e[#{unquote(code)}m", rest }
|
||||
end
|
||||
def name, [], [], do:
|
||||
quote do: "\e[#{unquote(code)}m"
|
||||
|
||||
args =
|
||||
quote do: [<< unquote(atom_to_binary(name)), rest :: binary >> ]
|
||||
|
||||
defp :escape_sequence, args, [], do:
|
||||
quote do: { "\e[#{unquote(code)}m", rest }
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -22,9 +24,6 @@ defmodule IO.ANSI do
|
||||
(http://en.wikipedia.org/wiki/ANSI_escape_code) — characters embedded
|
||||
in the text used to control formatting, color, and other output options
|
||||
on video text terminals.
|
||||
|
||||
Please be aware that in Erlang/OTP versions prior to R16, you will not
|
||||
be able to render ANSI escape sequences in iex or erlang shell
|
||||
"""
|
||||
|
||||
import IO.ANSI.Sequence
|
||||
@@ -34,23 +33,11 @@ defmodule IO.ANSI do
|
||||
|
||||
Used to identify whether printing ANSI escape sequences will likely
|
||||
be printed as intended.
|
||||
|
||||
Please note that invoked while in shell (iex) in Erlang/OTP
|
||||
prior to R16, terminal?/0 will always return false because
|
||||
Erlang shell did not support ANSI escape sequences up until
|
||||
R16.
|
||||
"""
|
||||
@spec terminal? :: boolean
|
||||
@spec terminal?(:io.device) :: boolean
|
||||
def terminal?(device // :erlang.group_leader) do
|
||||
if :erlang.system_info(:otp_release) < 'R16' and
|
||||
Process.whereis(:user) != device do
|
||||
# Shell prior to R16 doesn't support ANSI escape
|
||||
# sequences
|
||||
false
|
||||
else
|
||||
match?({:ok, _}, :io.columns(device))
|
||||
end
|
||||
match?({:ok, _}, :io.columns(device))
|
||||
end
|
||||
|
||||
@doc "Resets all attributes"
|
||||
@@ -90,10 +77,8 @@ defmodule IO.ANSI do
|
||||
defsequence :primary_font, 10
|
||||
|
||||
lc font_n inlist [1,2,3,4,5,6,7,8,9] do
|
||||
Module.eval_quoted __ENV__, (quote do
|
||||
@doc "Sets alternative font #{unquote(font_n)}"
|
||||
defsequence(unquote(:"font_#{font_n}"), unquote(font_n + 10))
|
||||
end)
|
||||
@doc "Sets alternative font #{font_n}"
|
||||
defsequence :"font_#{font_n}", font_n + 10
|
||||
end
|
||||
|
||||
@doc "Normal color or intensity"
|
||||
@@ -112,13 +97,11 @@ defmodule IO.ANSI do
|
||||
colors = Enum.zip(0..(length(colors)-1), colors)
|
||||
|
||||
lc { code, color } inlist colors do
|
||||
Module.eval_quoted __ENV__, (quote do
|
||||
@doc "Sets foreground color to #{unquote(color)}"
|
||||
defsequence unquote(color), unquote(code + 30)
|
||||
@doc "Sets foreground color to #{color}"
|
||||
defsequence color, code + 30
|
||||
|
||||
@doc "Sets background color to #{unquote(color)}"
|
||||
defsequence unquote(:"#{color}_background"), unquote(code + 40)
|
||||
end)
|
||||
@doc "Sets background color to #{color}"
|
||||
defsequence :"#{color}_background", code + 40
|
||||
end
|
||||
|
||||
@doc "Default text color"
|
||||
@@ -168,7 +151,7 @@ defmodule IO.ANSI do
|
||||
|
||||
## Example
|
||||
|
||||
iex> IO.ANSI.escape "Hello %{red,bright,green}yes"
|
||||
iex> IO.ANSI.escape("Hello %{red,bright,green}yes")
|
||||
"Hello \e[31m\e[1m\e[32myes\e[0m"
|
||||
"""
|
||||
@spec escape(String.t, emit :: boolean) :: String.t
|
||||
@@ -194,7 +177,7 @@ defmodule IO.ANSI do
|
||||
|
||||
## Example
|
||||
|
||||
iex> IO.ANSI.escape "Hello %{red,bright,green}yes"
|
||||
iex> IO.ANSI.escape("Hello %{red,bright,green}yes")
|
||||
"Hello \e[31m\e[1m\e[32myes\e[0m"
|
||||
|
||||
"""
|
||||
|
||||
+195
-191
@@ -134,13 +134,16 @@ defmodule Kernel do
|
||||
defmacro left and right
|
||||
|
||||
@doc """
|
||||
Boolean xor. Arguments must be booleans.
|
||||
Boolean exclusive-or. Arguments must be booleans. Returns true if and only if
|
||||
both arguments are different.
|
||||
Allowed in guard clauses.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> true xor false
|
||||
true
|
||||
iex> true xor true
|
||||
false
|
||||
|
||||
"""
|
||||
defmacro left xor right
|
||||
@@ -296,7 +299,7 @@ defmodule Kernel do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> apply fn x -> x * 2 end, [2]
|
||||
iex> apply(fn x -> x * 2 end, [2])
|
||||
4
|
||||
|
||||
"""
|
||||
@@ -307,7 +310,7 @@ defmodule Kernel do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> apply Enum, :reverse, [[1,2,3]]
|
||||
iex> apply(Enum, :reverse, [[1,2,3]])
|
||||
[3,2,1]
|
||||
|
||||
"""
|
||||
@@ -373,12 +376,12 @@ defmodule Kernel do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> binary_part "foo", 1, 2
|
||||
iex> binary_part("foo", 1, 2)
|
||||
"oo"
|
||||
|
||||
A negative length can be used to extract bytes at the end of a binary:
|
||||
|
||||
iex> binary_part "foo", 3, -1
|
||||
iex> binary_part("foo", 3, -1)
|
||||
"o"
|
||||
|
||||
"""
|
||||
@@ -1317,6 +1320,45 @@ defmodule Kernel do
|
||||
"""
|
||||
defmacro defp(name, args, guards, do: contents)
|
||||
|
||||
@doc """
|
||||
Defines a macro with the given name and contents.
|
||||
|
||||
## Examples
|
||||
|
||||
defmodule MyLogic do
|
||||
defmacro unless(expr, opts) do
|
||||
quote do
|
||||
if !unquote(expr), unquote(opts)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
require MyLogic
|
||||
MyLogic.unless false do
|
||||
IO.puts "It works"
|
||||
end
|
||||
|
||||
"""
|
||||
defmacro defmacro(name, do: contents)
|
||||
|
||||
@doc """
|
||||
The same as `def/4` but generates a macro.
|
||||
"""
|
||||
defmacro defmacro(name, args, guards, do: contents)
|
||||
|
||||
@doc """
|
||||
Defines a macro that is private. Private macros
|
||||
can only be accessible from the same module it is defined.
|
||||
|
||||
Check `defmacro/2` for more information
|
||||
"""
|
||||
defmacro defmacrop(name, do: contents)
|
||||
|
||||
@doc """
|
||||
The same as `def/4` but generates a private macro.
|
||||
"""
|
||||
defmacro defmacrop(name, args, guards, do: contents)
|
||||
|
||||
@doc %B"""
|
||||
Defines a record.
|
||||
|
||||
@@ -1389,6 +1431,26 @@ defmodule Kernel do
|
||||
|
||||
Which provides faster get and set times for record operations.
|
||||
|
||||
## Runtime introspection
|
||||
|
||||
At runtime, developers can use `__record__` to get information
|
||||
about the given record:
|
||||
|
||||
FileInfo.__record__(:name)
|
||||
#=> FileInfo
|
||||
|
||||
FileInfo.__record__(:fields)
|
||||
#=> [atime: nil, accesses: 0]
|
||||
|
||||
In order to quickly access the index of a field, one can use
|
||||
the `__index__` function:
|
||||
|
||||
FileInfo.__index__(:atime)
|
||||
#=> 0
|
||||
|
||||
FileInfo.__index__(:unknown)
|
||||
#=> nil
|
||||
|
||||
## Compile-time introspection
|
||||
|
||||
At the compile time, one can access following information about the record
|
||||
@@ -1443,8 +1505,14 @@ defmodule Kernel do
|
||||
|
||||
See `Record.import/2` and `defrecordp/2` documentation for more information
|
||||
"""
|
||||
defmacro defrecord(name, fields, opts // [], do_block // []) do
|
||||
Record.defrecord(name, fields, Keyword.merge(opts, do_block))
|
||||
|
||||
defmacro defrecord(name, fields, do_block // [])
|
||||
|
||||
defmacro defrecord(name, fields, do_block) do
|
||||
case is_list(fields) and Keyword.get(fields, :do, false) do
|
||||
false -> Record.defrecord(name, fields, do_block)
|
||||
other -> Record.defrecord(name, Keyword.delete(fields, :do), do: other)
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -1525,11 +1593,13 @@ defmodule Kernel do
|
||||
fields = [{ :__exception__, :__exception__ }|fields]
|
||||
record = Record.defrecord(name, fields, opts)
|
||||
|
||||
check = quote do
|
||||
Exception.check! Module.concat(__MODULE__, unquote(name))
|
||||
end
|
||||
quote do
|
||||
unquote(record)
|
||||
|
||||
[record, check]
|
||||
unless :erlang.function_exported(unquote(name), :message, 1) do
|
||||
raise "expected exception #{inspect unquote(name)} to implement message/1"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -1582,7 +1652,7 @@ defmodule Kernel do
|
||||
false ->
|
||||
quote do
|
||||
result = unquote(thing)
|
||||
is_tuple(result) and tuple_size(unquote(thing)) > 0
|
||||
is_tuple(result) and tuple_size(result) > 0
|
||||
and :erlang.element(1, result) == unquote(kind)
|
||||
end
|
||||
end
|
||||
@@ -1601,7 +1671,7 @@ defmodule Kernel do
|
||||
false ->
|
||||
quote do
|
||||
result = unquote(thing)
|
||||
is_tuple(result) and tuple_size(unquote(thing)) > 0
|
||||
is_tuple(result) and tuple_size(result) > 0
|
||||
and is_atom(:erlang.element(1, result))
|
||||
end
|
||||
end
|
||||
@@ -1842,7 +1912,14 @@ defmodule Kernel do
|
||||
|
||||
"""
|
||||
defmacro inspect(arg, opts // []) do
|
||||
quote do: Binary.Inspect.inspect(unquote(arg), unquote(opts))
|
||||
quote do
|
||||
arg = unquote(arg)
|
||||
opts = unquote(opts)
|
||||
case is_tuple(arg) && Keyword.get(opts, :raw, false) do
|
||||
true -> Binary.Inspect.Tuple.inspect(arg, opts)
|
||||
false -> Binary.Inspect.inspect(arg, opts)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -1923,10 +2000,6 @@ defmodule Kernel do
|
||||
Elixir conventions (i.e. it expects the tuple as first argument,
|
||||
zero-index based).
|
||||
|
||||
Please note that in versions of Erlang prior to R16B there is no BIF
|
||||
for this operation and it is emulated by converting the tuple to a list
|
||||
and back and is, therefore, inefficient.
|
||||
|
||||
## Example
|
||||
|
||||
iex> tuple = { :bar, :baz }
|
||||
@@ -1934,30 +2007,11 @@ defmodule Kernel do
|
||||
{ :foo, :bar, :baz }
|
||||
"""
|
||||
defmacro insert_elem(tuple, index, value) when is_integer(index) do
|
||||
case :proplists.get_value(:insert_element,
|
||||
:proplists.get_value(:exports, :erlang.module_info,[])) do
|
||||
3 ->
|
||||
quote do: :erlang.insert_element(unquote(index + 1), unquote(tuple), unquote(value))
|
||||
:undefined ->
|
||||
do_insert_elem(tuple, index, value)
|
||||
end
|
||||
end
|
||||
defmacro insert_elem(tuple, index, value) do
|
||||
case :proplists.get_value(:insert_element,
|
||||
:proplists.get_value(:exports, :erlang.module_info,[])) do
|
||||
3 ->
|
||||
quote do: :erlang.insert_element(unquote(index) + 1, unquote(tuple), unquote(value))
|
||||
:undefined ->
|
||||
do_insert_elem(tuple, index, value)
|
||||
end
|
||||
quote do: :erlang.insert_element(unquote(index + 1), unquote(tuple), unquote(value))
|
||||
end
|
||||
|
||||
defp do_insert_elem(tuple, index, value) do
|
||||
quote do
|
||||
{h, t} = :lists.split(unquote(index),
|
||||
tuple_to_list(unquote(tuple)))
|
||||
list_to_tuple(h ++ [unquote(value)|t])
|
||||
end
|
||||
defmacro insert_elem(tuple, index, value) do
|
||||
quote do: :erlang.insert_element(unquote(index) + 1, unquote(tuple), unquote(value))
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -1976,30 +2030,11 @@ defmodule Kernel do
|
||||
{ :bar, :baz }
|
||||
"""
|
||||
defmacro delete_elem(tuple, index) when is_integer(index) do
|
||||
case :proplists.get_value(:delete_element,
|
||||
:proplists.get_value(:exports, :erlang.module_info,[])) do
|
||||
2 ->
|
||||
quote do: :erlang.delete_element(unquote(index + 1), unquote(tuple))
|
||||
:undefined ->
|
||||
do_delete_elem(tuple, index)
|
||||
end
|
||||
end
|
||||
defmacro delete_elem(tuple, index) do
|
||||
case :proplists.get_value(:delete_element,
|
||||
:proplists.get_value(:exports, :erlang.module_info,[])) do
|
||||
2 ->
|
||||
quote do: :erlang.delete_element(unquote(index) + 1, unquote(tuple))
|
||||
:undefined ->
|
||||
do_delete_elem(tuple, index)
|
||||
end
|
||||
quote do: :erlang.delete_element(unquote(index + 1), unquote(tuple))
|
||||
end
|
||||
|
||||
defp do_delete_elem(tuple, index) do
|
||||
quote do
|
||||
{h, [_|t]} = :lists.split(unquote(index),
|
||||
tuple_to_list(unquote(tuple)))
|
||||
list_to_tuple(h ++ t)
|
||||
end
|
||||
defmacro delete_elem(tuple, index) do
|
||||
quote do: :erlang.delete_element(unquote(index) + 1, unquote(tuple))
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -2009,7 +2044,7 @@ defmodule Kernel do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> div 5, 2
|
||||
iex> div(5, 2)
|
||||
2
|
||||
|
||||
"""
|
||||
@@ -2024,7 +2059,7 @@ defmodule Kernel do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> rem 5, 2
|
||||
iex> rem(5, 2)
|
||||
1
|
||||
|
||||
"""
|
||||
@@ -2038,9 +2073,9 @@ defmodule Kernel do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> nil? 1
|
||||
iex> nil?(1)
|
||||
false
|
||||
iex> nil? nil
|
||||
iex> nil?(nil)
|
||||
true
|
||||
|
||||
"""
|
||||
@@ -2184,7 +2219,7 @@ defmodule Kernel do
|
||||
defmacro function(module, function, arity)
|
||||
|
||||
@doc """
|
||||
Matches the given condition against the match clauses.
|
||||
Matches the given expression against the match clauses.
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -2194,9 +2229,9 @@ defmodule Kernel do
|
||||
value -> value
|
||||
end
|
||||
|
||||
In the example above, we compare `thing` with each given
|
||||
match clause and execute the first one that matches. If no
|
||||
clause matches, an error is raised.
|
||||
In the example above, we compare `thing` with each given match
|
||||
clause and evaluate the expression corresponding to the first clause
|
||||
that matches. If no clause matches, an error is raised.
|
||||
|
||||
Since Elixir variables can be assigned more than once, variables
|
||||
in a match clause will always be assigned instead of matching with
|
||||
@@ -2217,22 +2252,11 @@ defmodule Kernel do
|
||||
end
|
||||
|
||||
The example above will actually fail because 10 does not match 1.
|
||||
|
||||
Finally, `case` accepts an `else:` branch as a fallback if none
|
||||
of the clauses match:
|
||||
|
||||
case thing do
|
||||
{ :selector, i, value } when is_integer(i) ->
|
||||
value
|
||||
_ ->
|
||||
thing
|
||||
end
|
||||
|
||||
"""
|
||||
defmacro case(condition, blocks)
|
||||
|
||||
@doc """
|
||||
Execute the given expressions and catch any error, exit
|
||||
Evaluate the given expressions and catch any error, exit
|
||||
or throw that may have happened.
|
||||
|
||||
## Examples
|
||||
@@ -2312,7 +2336,7 @@ defmodule Kernel do
|
||||
|
||||
## Variable visibility
|
||||
|
||||
Since an expression inside `try` may not have been evaluted
|
||||
Since an expression inside `try` may not have been evaluated
|
||||
due to an exception, any variable created inside `try` cannot
|
||||
be accessed externaly. For instance:
|
||||
|
||||
@@ -2436,7 +2460,7 @@ defmodule Kernel do
|
||||
|
||||
if(foo, do: bar)
|
||||
|
||||
In the example above, bar will be returned if foo evalutes to
|
||||
In the example above, bar will be returned if foo evaluates to
|
||||
true (i.e. it is not false nor nil). Otherwise, nil will be returned.
|
||||
|
||||
An else option can be given to specify the opposite:
|
||||
@@ -2477,8 +2501,9 @@ defmodule Kernel do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Execute the first clause where the condition returns true,
|
||||
raises an error otherwise.
|
||||
Evaluates the expression corresponding to the first clause that
|
||||
evaluates to true. Raises an error if all conditions evaluate to
|
||||
to falsy values (nil or false).
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -2513,9 +2538,18 @@ defmodule Kernel do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Provides a unless macro that executes the expression
|
||||
unless a value evalutes to true. Check `if` for examples
|
||||
and documentation.
|
||||
Evaluates and returns the do-block passed in as a second argument
|
||||
unless clause evaluates to true.
|
||||
Returns nil otherwise.
|
||||
See also `if`.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> unless(1, do: "Hello")
|
||||
nil
|
||||
iex> unless(false, do: "Hello")
|
||||
"Hello"
|
||||
|
||||
"""
|
||||
defmacro unless(clause, options) do
|
||||
do_clause = Keyword.get(options, :do, nil)
|
||||
@@ -2533,7 +2567,7 @@ defmodule Kernel do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> destructure [x,y,z], [1,2,3,4,5]
|
||||
iex> destructure([x,y,z], [1,2,3,4,5])
|
||||
...> {x, y, z}
|
||||
{1, 2, 3}
|
||||
|
||||
@@ -2542,7 +2576,7 @@ defmodule Kernel do
|
||||
fine. If the right size is smaller, the remaining items
|
||||
are simply assigned to nil:
|
||||
|
||||
iex> destructure [x,y,z], [1]
|
||||
iex> destructure([x,y,z], [1])
|
||||
...> {x, y, z}
|
||||
{1, nil, nil}
|
||||
|
||||
@@ -2550,7 +2584,7 @@ defmodule Kernel do
|
||||
on the left side of a match:
|
||||
|
||||
x = 1
|
||||
destructure [^x,y,z], [1,2,3]
|
||||
destructure([^x,y,z], [1,2,3])
|
||||
|
||||
The example above will only work if x matches
|
||||
the first value from the right side. Otherwise,
|
||||
@@ -2574,18 +2608,12 @@ defmodule Kernel do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> binary_to_integer "123"
|
||||
iex> binary_to_integer("123")
|
||||
123
|
||||
|
||||
"""
|
||||
defmacro binary_to_integer(some_binary) do
|
||||
case :proplists.get_value(:binary_to_integer,
|
||||
:proplists.get_value(:exports, :erlang.module_info, [])) do
|
||||
2 ->
|
||||
quote do: :erlang.binary_to_integer(unquote(some_binary))
|
||||
:undefined ->
|
||||
quote do: list_to_integer(binary_to_list(unquote(some_binary)))
|
||||
end
|
||||
def binary_to_integer(some_binary) do
|
||||
:erlang.binary_to_integer(some_binary)
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -2598,14 +2626,8 @@ defmodule Kernel do
|
||||
1023
|
||||
|
||||
"""
|
||||
defmacro binary_to_integer(some_binary, base) do
|
||||
case :proplists.get_value(:binary_to_integer,
|
||||
:proplists.get_value(:exports, :erlang.module_info, [])) do
|
||||
2 ->
|
||||
quote do: :erlang.binary_to_integer(unquote(some_binary), unquote(base))
|
||||
:undefined ->
|
||||
quote do: list_to_integer(binary_to_list(unquote(some_binary)), unquote(base))
|
||||
end
|
||||
def binary_to_integer(some_binary, base) do
|
||||
:erlang.binary_to_integer(some_binary, base)
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -2613,18 +2635,12 @@ defmodule Kernel do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> binary_to_float "2.2017764e+0"
|
||||
iex> binary_to_float("2.2017764e+0")
|
||||
2.2017764
|
||||
|
||||
"""
|
||||
defmacro binary_to_float(some_binary) do
|
||||
case :proplists.get_value(:binary_to_float,
|
||||
:proplists.get_value(:exports, :erlang.module_info, [])) do
|
||||
1 ->
|
||||
quote do: :erlang.binary_to_float(unquote(some_binary))
|
||||
:undefined ->
|
||||
quote do: list_to_float(binary_to_list(unquote(some_binary)))
|
||||
end
|
||||
def binary_to_float(some_binary) do
|
||||
:erlang.binary_to_float(some_binary)
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -2633,18 +2649,12 @@ defmodule Kernel do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> integer_to_binary 123
|
||||
iex> integer_to_binary(123)
|
||||
"123"
|
||||
|
||||
"""
|
||||
defmacro integer_to_binary(some_integer) do
|
||||
case :proplists.get_value(:integer_to_binary,
|
||||
:proplists.get_value(:exports, :erlang.module_info, [])) do
|
||||
2 ->
|
||||
quote do: :erlang.integer_to_binary(unquote(some_integer))
|
||||
:undefined ->
|
||||
quote do: list_to_binary(integer_to_list(unquote(some_integer)))
|
||||
end
|
||||
def integer_to_binary(some_integer) do
|
||||
:erlang.integer_to_binary(some_integer)
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -2653,18 +2663,12 @@ defmodule Kernel do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> integer_to_binary 77
|
||||
"77"
|
||||
iex> integer_to_binary(100, 16)
|
||||
"64"
|
||||
|
||||
"""
|
||||
defmacro integer_to_binary(some_integer, base) do
|
||||
case :proplists.get_value(:integer_to_binary,
|
||||
:proplists.get_value(:exports, :erlang.module_info, [])) do
|
||||
2 ->
|
||||
quote do: :erlang.integer_to_binary(unquote(some_integer), unquote(base))
|
||||
:undefined ->
|
||||
quote do: list_to_binary(integer_to_list(unquote(some_integer), unquote(base)))
|
||||
end
|
||||
def integer_to_binary(some_integer, base) do
|
||||
:erlang.integer_to_binary(some_integer, base)
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -2673,53 +2677,50 @@ defmodule Kernel do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> float_to_binary 7.0
|
||||
iex> float_to_binary(7.0)
|
||||
"7.00000000000000000000e+00"
|
||||
|
||||
"""
|
||||
defmacro float_to_binary(some_float) do
|
||||
case :proplists.get_value(:float_to_binary,
|
||||
:proplists.get_value(:exports, :erlang.module_info, [])) do
|
||||
2 ->
|
||||
quote do: :erlang.float_to_binary(unquote(some_float))
|
||||
:undefined ->
|
||||
quote do: list_to_binary(float_to_list(unquote(some_float)))
|
||||
end
|
||||
def float_to_binary(some_float) do
|
||||
:erlang.float_to_binary(some_float)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns a binary which corresponds to the text representation
|
||||
of `some_float`.
|
||||
of `float`.
|
||||
|
||||
## Options
|
||||
|
||||
* `:decimals` — number of decimal points to show
|
||||
* `:scientific` — number of decimal points to show, in scientific format
|
||||
* `:compact` — If true, use the most compact representation. Ignored with the `scientific` option
|
||||
* `:compact` — If true, use the most compact representation (ignored with the `scientific` option)
|
||||
|
||||
## Examples
|
||||
|
||||
float_to_binary 7.1, [decimals: 2, compact: true] #=> "7.1"
|
||||
|
||||
"""
|
||||
defmacro float_to_binary(some_float, options) do
|
||||
options = :lists.reverse(:lists.foldl(fn({:compact, false}, acc) -> acc -- [:compact]
|
||||
({:compact, true}, acc) -> [:compact|acc]
|
||||
(x, acc) -> [x|acc]
|
||||
end, [], options))
|
||||
case :proplists.get_value(:float_to_binary,
|
||||
:proplists.get_value(:exports, :erlang.module_info, [])) do
|
||||
2 ->
|
||||
quote do: :erlang.float_to_binary(unquote(some_float), unquote(options))
|
||||
:undefined ->
|
||||
case :proplists.get_value(:float_to_list,
|
||||
:proplists.get_value(:exports, :erlang.module_info, [])) do
|
||||
2 ->
|
||||
quote do: list_to_binary(float_to_list(unquote(some_float), unquote(options)))
|
||||
1 ->
|
||||
throw(:badarg)
|
||||
end
|
||||
end
|
||||
def float_to_binary(float, options) do
|
||||
:erlang.float_to_binary(float, expand_compact(options))
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns a list which corresponds to the text representation
|
||||
of `float`.
|
||||
|
||||
## Options
|
||||
|
||||
* `:decimals` — number of decimal points to show
|
||||
* `:scientific` — number of decimal points to show, in scientific format
|
||||
* `:compact` — If true, use the most compact representation (ignored with the `scientific` option)
|
||||
|
||||
## Examples
|
||||
|
||||
float_to_list 7.1, [decimals: 2, compact: true] #=> '7.1'
|
||||
|
||||
"""
|
||||
def float_to_list(float, options) do
|
||||
:erlang.float_to_list(float, expand_compact(options))
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -2728,7 +2729,7 @@ defmodule Kernel do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> binary_to_atom "my_atom"
|
||||
iex> binary_to_atom("my_atom")
|
||||
:my_atom
|
||||
|
||||
"""
|
||||
@@ -2744,10 +2745,10 @@ defmodule Kernel do
|
||||
## Examples
|
||||
|
||||
iex> :my_atom
|
||||
...> binary_to_existing_atom "my_atom"
|
||||
...> binary_to_existing_atom("my_atom")
|
||||
:my_atom
|
||||
|
||||
iex> binary_to_existing_atom "this_atom_will_never_exist"
|
||||
iex> binary_to_existing_atom("this_atom_will_never_exist")
|
||||
** (ArgumentError) argument error
|
||||
|
||||
"""
|
||||
@@ -2763,7 +2764,7 @@ defmodule Kernel do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> atom_to_binary :my_atom
|
||||
iex> atom_to_binary(:my_atom)
|
||||
"my_atom"
|
||||
|
||||
"""
|
||||
@@ -2815,9 +2816,10 @@ defmodule Kernel do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Provides a short-circuit operator that executes the second
|
||||
expression only if the first one evalutes to true (i.e. it is
|
||||
not nil nor false). Returns the first expression otherwise.
|
||||
Provides a short-circuit operator that evaluates and returns
|
||||
the second expression only if the first one evaluates to true
|
||||
(i.e. it is not nil nor false). Returns the first expression
|
||||
otherwise.
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -2846,9 +2848,9 @@ defmodule Kernel do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Provides a short-circuit operator that executes the second
|
||||
expression only if the first one does not evalute to true (i.e. it
|
||||
is not nil nor false). Returns the first expression otherwise.
|
||||
Provides a short-circuit operator that evaluates and returns the second
|
||||
expression only if the first one does not evaluate to true (i.e. it
|
||||
is either nil or false). Returns the first expression otherwise.
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -2878,8 +2880,7 @@ defmodule Kernel do
|
||||
|
||||
@doc """
|
||||
Returns true if the element on the left is equal (==) to
|
||||
any of the items in the right. For now, it only accepts
|
||||
a list as the right argument.
|
||||
any of the items in the right.
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -2891,6 +2892,9 @@ defmodule Kernel do
|
||||
|
||||
x == 1 or x == 2 or x == 3
|
||||
|
||||
with the exception that the expression on the left of `in`
|
||||
is evaluated only once.
|
||||
|
||||
## Clauses
|
||||
|
||||
Whenever used inside a function or a case clause, you can
|
||||
@@ -2956,7 +2960,7 @@ defmodule Kernel do
|
||||
|
||||
String.graphemes("Hello" |> Enum.reverse)
|
||||
|
||||
Which will result in an error as Enum.Iterator protocol
|
||||
Which will result in an error as Enumerable protocol
|
||||
is not defined for binaries. Adding explicit parenthesis
|
||||
is recommended:
|
||||
|
||||
@@ -3030,7 +3034,7 @@ defmodule Kernel do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> raise ArgumentError, message: "Sample"
|
||||
iex> raise(ArgumentError, message: "Sample")
|
||||
** (ArgumentError) Sample
|
||||
|
||||
"""
|
||||
@@ -3165,14 +3169,9 @@ defmodule Kernel do
|
||||
true ->
|
||||
fields =
|
||||
try do
|
||||
module = caller.module
|
||||
|
||||
# We are using the access protocol in the same
|
||||
# module that defines it. It works, but we need
|
||||
# to read the field values from @record_fields.
|
||||
case atom do
|
||||
^module -> Module.get_attribute(module, :record_fields)
|
||||
_ -> atom.__record__(:fields)
|
||||
case :lists.member(atom, caller.context_modules) and Module.open?(atom) do
|
||||
true -> Module.get_attribute(atom, :record_fields)
|
||||
false -> atom.__record__(:fields)
|
||||
end
|
||||
rescue
|
||||
UndefinedFunctionError ->
|
||||
@@ -3181,7 +3180,7 @@ defmodule Kernel do
|
||||
# conflicts.
|
||||
case :code.ensure_loaded(atom) do
|
||||
{ :error, _ } ->
|
||||
raise "expected module #{inspect atom} to be loaded and defined"
|
||||
:elixir_aliases.ensure_loaded(caller.line, caller.file, atom, caller.context_modules)
|
||||
_ ->
|
||||
raise "cannot use module #{inspect atom} in access protocol because it does not export __record__/1"
|
||||
end
|
||||
@@ -3346,7 +3345,7 @@ defmodule Kernel do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Regex.match? %r(foo), "foo"
|
||||
iex> Regex.match?(%r(foo), "foo")
|
||||
true
|
||||
|
||||
"""
|
||||
@@ -3367,7 +3366,7 @@ defmodule Kernel do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Regex.match? %R(f\#{1,3}o), "f\#o"
|
||||
iex> Regex.match?(%R(f\#{1,3}o), "f\#o")
|
||||
true
|
||||
|
||||
"""
|
||||
@@ -3471,6 +3470,11 @@ defmodule Kernel do
|
||||
|
||||
defp build_cond_clauses([], acc), do: acc
|
||||
|
||||
defp expand_compact([{ :compact, false }|t]), do: expand_compact(t)
|
||||
defp expand_compact([{ :compact, true }|t]), do: [:compact|expand_compact(t)]
|
||||
defp expand_compact([h|t]), do: [h|expand_compact(t)]
|
||||
defp expand_compact([]), do: []
|
||||
|
||||
defp split_words(string, modifiers) do
|
||||
mod = case modifiers do
|
||||
[] -> ?b
|
||||
|
||||
+131
-48
@@ -3,19 +3,26 @@ defmodule Kernel.CLI do
|
||||
Module responsible for controlling Elixir's CLI
|
||||
"""
|
||||
|
||||
defrecord Config, commands: [], output: ".",
|
||||
compile: [], halt: true, compiler_options: []
|
||||
defrecord Config, commands: [], output: ".", compile: [],
|
||||
halt: true, compiler_options: [], errors: []
|
||||
|
||||
# This is the API invoked by Elixir boot process.
|
||||
@doc false
|
||||
def main(argv) do
|
||||
argv = lc arg inlist argv, do: list_to_binary(arg)
|
||||
argv = lc arg inlist argv, do: :unicode.characters_to_binary(arg)
|
||||
|
||||
{ config, argv } = process_argv(argv, Kernel.CLI.Config.new)
|
||||
:elixir_code_server.cast({ :argv, argv })
|
||||
|
||||
run fn ->
|
||||
Enum.map Enum.reverse(config.commands), process_command(&1, config)
|
||||
command_results = Enum.map(Enum.reverse(config.commands), process_command(&1, config))
|
||||
command_errors = lc { :error, msg } inlist command_results, do: msg
|
||||
errors = Enum.reverse(config.errors) ++ command_errors
|
||||
|
||||
if errors != [] do
|
||||
Enum.each(errors, IO.puts(:stderr, &1))
|
||||
System.halt(1)
|
||||
end
|
||||
end, config.halt
|
||||
end
|
||||
|
||||
@@ -39,7 +46,7 @@ defmodule Kernel.CLI do
|
||||
at_exit(1)
|
||||
trace = System.stacktrace
|
||||
IO.puts :stderr, "** (#{inspect exception.__record__(:name)}) #{exception.message}"
|
||||
IO.puts Exception.format_stacktrace(trace)
|
||||
IO.puts :stderr, Exception.format_stacktrace(trace)
|
||||
System.halt(1)
|
||||
catch
|
||||
:exit, reason when is_integer(reason) ->
|
||||
@@ -52,12 +59,12 @@ defmodule Kernel.CLI do
|
||||
at_exit(1)
|
||||
trace = System.stacktrace
|
||||
IO.puts :stderr, "** (#{kind}) #{inspect(reason)}"
|
||||
IO.puts Exception.format_stacktrace(trace)
|
||||
IO.puts :stderr, Exception.format_stacktrace(trace)
|
||||
System.halt(1)
|
||||
end
|
||||
end
|
||||
|
||||
## Private
|
||||
## Helpers
|
||||
|
||||
defp at_exit(status) do
|
||||
hooks = :elixir_code_server.call(:flush_at_exit)
|
||||
@@ -69,12 +76,12 @@ defmodule Kernel.CLI do
|
||||
exception ->
|
||||
trace = System.stacktrace
|
||||
IO.puts :stderr, "** (#{inspect exception.__record__(:name)}) #{exception.message}"
|
||||
IO.puts Exception.format_stacktrace(trace)
|
||||
IO.puts :stderr, Exception.format_stacktrace(trace)
|
||||
catch
|
||||
kind, reason ->
|
||||
trace = System.stacktrace
|
||||
IO.puts :stderr, "** #{kind} #{inspect(reason)}"
|
||||
IO.puts Exception.format_stacktrace(trace)
|
||||
IO.puts :stderr, Exception.format_stacktrace(trace)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -83,15 +90,11 @@ defmodule Kernel.CLI do
|
||||
unless hooks == [], do: at_exit(status)
|
||||
end
|
||||
|
||||
defp invalid_option(option) do
|
||||
IO.puts(:stderr, "Unknown option #{option}")
|
||||
System.halt(1)
|
||||
end
|
||||
|
||||
defp shared_option?(list, config, callback) do
|
||||
case process_shared(list, config) do
|
||||
{ [h|_], _ } when h == hd(list) ->
|
||||
invalid_option h
|
||||
{ [h|hs], _ } when h == hd(list) ->
|
||||
new_config = config.update_errors ["#{h} : Unknown option" | &1]
|
||||
callback.(hs, new_config)
|
||||
{ new_list, new_config } ->
|
||||
callback.(new_list, new_config)
|
||||
end
|
||||
@@ -104,6 +107,16 @@ defmodule Kernel.CLI do
|
||||
process_shared t, config
|
||||
end
|
||||
|
||||
defp process_shared(["-pa",h|t], config) do
|
||||
Enum.each Path.wildcard(Path.expand(h)), Code.prepend_path(&1)
|
||||
process_shared t, config
|
||||
end
|
||||
|
||||
defp process_shared(["-pz",h|t], config) do
|
||||
Enum.each Path.wildcard(Path.expand(h)), Code.append_path(&1)
|
||||
process_shared t, config
|
||||
end
|
||||
|
||||
defp process_shared(["--app",h|t], config) do
|
||||
process_shared t, config.update_commands [{:app,h}|&1]
|
||||
end
|
||||
@@ -116,27 +129,19 @@ defmodule Kernel.CLI do
|
||||
process_shared t, config.update_commands [{:eval,h}|&1]
|
||||
end
|
||||
|
||||
defp process_shared(["-pa",h|t], config) do
|
||||
Enum.each Path.wildcard(Path.expand(h)), Code.prepend_path(&1)
|
||||
process_shared t, config
|
||||
end
|
||||
|
||||
defp process_shared(["-pz",h|t], config) do
|
||||
Enum.each Path.wildcard(Path.expand(h)), Code.append_path(&1)
|
||||
process_shared t, config
|
||||
end
|
||||
|
||||
defp process_shared(["-r",h|t], config) do
|
||||
process_shared t, Enum.reduce(Path.wildcard(h), config, fn path, config ->
|
||||
config.update_commands [{:require,path}|&1]
|
||||
end)
|
||||
process_shared t, config.update_commands [{:require,h}|&1]
|
||||
end
|
||||
|
||||
defp process_shared(["-pr",h|t], config) do
|
||||
process_shared t, config.update_commands [{:parallel_require,h}|&1]
|
||||
end
|
||||
|
||||
defp process_shared([erl,_|t], config) when erl in ["--erl", "--sname", "--remsh", "--name"] do
|
||||
defp process_shared(["--cookie",h|t], config) do
|
||||
process_shared t, config.update_commands [{:cookie,h}|&1]
|
||||
end
|
||||
|
||||
defp process_shared([erl,_|t], config) when erl in ["--erl", "--sname", "--name"] do
|
||||
process_shared t, config
|
||||
end
|
||||
|
||||
@@ -150,18 +155,16 @@ defmodule Kernel.CLI do
|
||||
{ config, t }
|
||||
end
|
||||
|
||||
defp process_argv(["--compile"|t], config) do
|
||||
defp process_argv(["+compile"|t], config) do
|
||||
process_compiler t, config
|
||||
end
|
||||
|
||||
defp process_argv(["+iex"|t], config) do
|
||||
process_iex t, config
|
||||
end
|
||||
|
||||
defp process_argv(["-S",h|t], config) do
|
||||
exec = System.find_executable(h)
|
||||
if exec do
|
||||
{ config.update_commands([{:require,exec}|&1]), t }
|
||||
else
|
||||
IO.puts(:stderr, "Could not find executable #{h}")
|
||||
System.halt(1)
|
||||
end
|
||||
{ config.update_commands([{:script,h}|&1]), t }
|
||||
end
|
||||
|
||||
defp process_argv([h|t] = list, config) do
|
||||
@@ -169,7 +172,7 @@ defmodule Kernel.CLI do
|
||||
"-" <> _ ->
|
||||
shared_option? list, config, process_argv(&1, &2)
|
||||
_ ->
|
||||
{ config.update_commands([{:require,h}|&1]), t }
|
||||
{ config.update_commands([{:file,h}|&1]), t }
|
||||
end
|
||||
end
|
||||
|
||||
@@ -213,31 +216,106 @@ defmodule Kernel.CLI do
|
||||
{ config.update_commands([{:compile,config.compile}|&1]), [] }
|
||||
end
|
||||
|
||||
# Process iex options
|
||||
|
||||
defp process_iex(["--"|t], config) do
|
||||
{ config, t }
|
||||
end
|
||||
|
||||
# This clause is here so that Kernel.CLI does not error out with "unknown
|
||||
# option"
|
||||
defp process_iex(["--dot-iex",_|t], config) do
|
||||
process_iex t, config
|
||||
end
|
||||
|
||||
defp process_iex([opt,_|t], config) when opt in ["--remsh"] do
|
||||
process_iex t, config
|
||||
end
|
||||
|
||||
defp process_iex(["-S",h|t], config) do
|
||||
{ config.update_commands([{:script,h}|&1]), t }
|
||||
end
|
||||
|
||||
defp process_iex([h|t] = list, config) do
|
||||
case h do
|
||||
"-" <> _ ->
|
||||
shared_option? list, config, process_iex(&1, &2)
|
||||
_ ->
|
||||
{ config.update_commands([{:file,h}|&1]), t }
|
||||
end
|
||||
end
|
||||
|
||||
defp process_iex([], config) do
|
||||
{ config, [] }
|
||||
end
|
||||
|
||||
# Process commands
|
||||
|
||||
defp process_command({:cookie,h}, _config) do
|
||||
if Node.alive? do
|
||||
Node.set_cookie(binary_to_atom(h))
|
||||
:ok
|
||||
else
|
||||
{ :error, "--cookie : Cannot set cookie if the node is not alive (set --name or --sname)" }
|
||||
end
|
||||
end
|
||||
|
||||
defp process_command({:eval, expr}, _config) when is_binary(expr) do
|
||||
Code.eval(expr, [])
|
||||
Code.eval_string(expr, [])
|
||||
:ok
|
||||
end
|
||||
|
||||
defp process_command({:app, app}, _config) when is_binary(app) do
|
||||
case Application.Behaviour.start(binary_to_atom(app)) do
|
||||
{ :error, reason } ->
|
||||
IO.puts(:stderr, "Could not start application #{app}: #{inspect reason}")
|
||||
System.halt(1)
|
||||
{ :error, "--app : Could not start application #{app}: #{inspect reason}" }
|
||||
:ok ->
|
||||
:ok
|
||||
end
|
||||
end
|
||||
|
||||
defp process_command({:require, file}, _config) when is_binary(file) do
|
||||
Code.require_file(file)
|
||||
defp process_command({:script, file}, _config) when is_binary(file) do
|
||||
if exec = System.find_executable(file) do
|
||||
Code.require_file(exec)
|
||||
:ok
|
||||
else
|
||||
{ :error, "-S : Could not find executable #{file}" }
|
||||
end
|
||||
end
|
||||
|
||||
defp process_command({:file, file}, _config) when is_binary(file) do
|
||||
if File.regular?(file) do
|
||||
Code.require_file(file)
|
||||
:ok
|
||||
else
|
||||
{ :error, "No file named #{file}" }
|
||||
end
|
||||
end
|
||||
|
||||
defp process_command({:require, pattern}, _config) when is_binary(pattern) do
|
||||
files = Path.wildcard(pattern)
|
||||
files = Enum.uniq(files)
|
||||
files = Enum.filter files, File.regular?(&1)
|
||||
|
||||
if files != [] do
|
||||
Enum.map files, Code.require_file(&1)
|
||||
:ok
|
||||
else
|
||||
{ :error, "-r : No files matched pattern #{pattern}" }
|
||||
end
|
||||
end
|
||||
|
||||
defp process_command({:parallel_require, pattern}, _config) when is_binary(pattern) do
|
||||
files = Path.wildcard(pattern)
|
||||
files = Enum.uniq(files)
|
||||
files = Enum.filter files, File.regular?(&1)
|
||||
Kernel.ParallelRequire.files(files)
|
||||
|
||||
if files != [] do
|
||||
Kernel.ParallelRequire.files(files)
|
||||
:ok
|
||||
else
|
||||
{ :error, "-pr : No files matched pattern #{pattern}" }
|
||||
end
|
||||
end
|
||||
|
||||
defp process_command({:compile, patterns}, config) do
|
||||
@@ -247,8 +325,13 @@ defmodule Kernel.CLI do
|
||||
files = Enum.uniq(List.concat(files))
|
||||
files = Enum.filter files, File.regular?(&1)
|
||||
|
||||
Code.compiler_options(config.compiler_options)
|
||||
Kernel.ParallelCompiler.files_to_path(files, config.output,
|
||||
fn file -> IO.puts "Compiled #{file}" end)
|
||||
if files != [] do
|
||||
Code.compiler_options(config.compiler_options)
|
||||
Kernel.ParallelCompiler.files_to_path(files, config.output,
|
||||
fn file -> IO.puts "Compiled #{file}" end)
|
||||
:ok
|
||||
else
|
||||
{ :error, "--compile : No files matched patterns #{Enum.join(patterns, ",")}" }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -72,7 +72,7 @@ defmodule Kernel.RecordRewriter do
|
||||
case record_fields(module, record) do
|
||||
{ fields, optimizable } ->
|
||||
opt_call =
|
||||
if List.member?(optimizable, { function, length(args) + 1 }) do
|
||||
if :lists.member({ function, length(args) + 1 }, optimizable) do
|
||||
case record_field_info(function) do
|
||||
{ kind, field } ->
|
||||
if index = Enum.find_index(fields, field == &1) do
|
||||
@@ -350,6 +350,10 @@ defmodule Kernel.RecordRewriter do
|
||||
vars
|
||||
end
|
||||
|
||||
defp join_dict([]) do
|
||||
[]
|
||||
end
|
||||
|
||||
defp join_dict([{ _, dict, _ }|t]) do
|
||||
join_dict(t, dict)
|
||||
end
|
||||
@@ -363,6 +367,8 @@ defmodule Kernel.RecordRewriter do
|
||||
{ :ok, _ } -> :orddict.store(key, nil, acc)
|
||||
:error -> :orddict.erase(key, acc)
|
||||
end
|
||||
{ key, nil }, acc ->
|
||||
:orddict.store(key, nil, acc)
|
||||
end
|
||||
|
||||
join_dict(t, other)
|
||||
@@ -372,6 +378,10 @@ defmodule Kernel.RecordRewriter do
|
||||
other
|
||||
end
|
||||
|
||||
defp join_result([]) do
|
||||
[]
|
||||
end
|
||||
|
||||
defp join_result([{ _, _, res }|t]) do
|
||||
join_result(t, res)
|
||||
end
|
||||
|
||||
@@ -305,6 +305,19 @@ defmodule Kernel.SpecialForms do
|
||||
also accepts `as:` as an option so it automatically sets up
|
||||
an alias. Please check `alias` for more information.
|
||||
|
||||
## Warnings
|
||||
|
||||
If you import a module and you don't use any of the imported
|
||||
functions or macros from this module, Elixir is going to issue
|
||||
a warning implying the import is not being used.
|
||||
|
||||
In case the import is generated automatically by a macro,
|
||||
Elixir won't emit any warnings though, since the import
|
||||
was not explicitly defined.
|
||||
|
||||
Both warning behaviors could be changed by explicitily
|
||||
setting the `:warn` option to true or false.
|
||||
|
||||
## Ambiguous function/macro names
|
||||
|
||||
If two modules `A` and `B` are imported and they both contain
|
||||
@@ -335,6 +348,11 @@ defmodule Kernel.SpecialForms do
|
||||
"""
|
||||
defmacro __FILE__
|
||||
|
||||
@doc """
|
||||
Returns the current directory as a binary.
|
||||
"""
|
||||
defmacro __DIR__
|
||||
|
||||
@doc """
|
||||
Allows you to get the representation of any expression.
|
||||
|
||||
@@ -346,18 +364,17 @@ defmodule Kernel.SpecialForms do
|
||||
## Explanation
|
||||
|
||||
Any Elixir code can be represented using Elixir data structures.
|
||||
The building block of Elixir homoiconicity is a tuple with three
|
||||
elements, for example:
|
||||
The building block of Elixir macros is a tuple with three elements,
|
||||
for example:
|
||||
|
||||
{ :sum, 1, [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:
|
||||
|
||||
* The first element of the tuple is always an atom or
|
||||
another tuple in the same representation;
|
||||
* The second element of the tuple is always an integer
|
||||
representing the line number;
|
||||
* The second element of the tuple represents metadata;
|
||||
* The third element of the tuple are the arguments for the
|
||||
function call. The third argument may be an atom, which is
|
||||
usually a variable (or a local call);
|
||||
@@ -370,6 +387,7 @@ defmodule Kernel.SpecialForms do
|
||||
* `:location` - When set to `:keep`, keeps the current line and file on quotes.
|
||||
Read the Stacktrace information section below for more information;
|
||||
* `:hygiene` - Allows a developer to disable hygiene selectively;
|
||||
* `:context` - Sets the context resolution happens at;
|
||||
|
||||
## Macro literals
|
||||
|
||||
@@ -383,17 +401,34 @@ defmodule Kernel.SpecialForms do
|
||||
"binaries" #=> Binaries
|
||||
{key, value} #=> Tuple with two elements
|
||||
|
||||
## Hygiene
|
||||
## Hygiene and context
|
||||
|
||||
Elixir macros are hygienic via means of deferred resolution.
|
||||
This means variables, aliases and imports defined inside the
|
||||
quoted refer to the context that defined the macro and not
|
||||
the context where the macro is expanded.
|
||||
|
||||
This means aliases and imports defined inside the quoted refer
|
||||
to the context that defined the macro and not the context
|
||||
where the macro is expanded.
|
||||
For this mechanism to work, every quoted code is attached
|
||||
to a context. Consider the following example:
|
||||
|
||||
Furthermore, variables inside quote are also hygienic: a
|
||||
variable defined in a macro cannot affect the variables where
|
||||
the macro is expanded.
|
||||
defmodule ContextSample do
|
||||
def hello do
|
||||
quote do: world
|
||||
end
|
||||
end
|
||||
|
||||
ContextSample.hello
|
||||
#=> {:world,[],ContextSample}
|
||||
|
||||
Notice how the third element of the returned tuple is the
|
||||
module name. This means that the variable is associated to the
|
||||
ContextSample module and only code generated by this module
|
||||
will be able to access that particular `world` variable.
|
||||
|
||||
The context can be disabled or changed by explicitly setting
|
||||
the context option. All hygiene mechanisms are based on such
|
||||
context and we are going to explore each of them in the following
|
||||
subsections.
|
||||
|
||||
### Hygiene in variables
|
||||
|
||||
@@ -616,6 +651,46 @@ defmodule Kernel.SpecialForms do
|
||||
"""
|
||||
defmacro alias!(alias)
|
||||
|
||||
@doc """
|
||||
Unquotes the given expression from inside a macro.
|
||||
|
||||
## Examples
|
||||
|
||||
Imagine the situation you have a variable `name` and
|
||||
you want to inject it inside some quote. The first attempt
|
||||
would be:
|
||||
|
||||
value = 13
|
||||
quote do: sum(1, value, 3)
|
||||
|
||||
Which would then return:
|
||||
|
||||
{ :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] }
|
||||
|
||||
"""
|
||||
name = :unquote
|
||||
defmacro unquote(name)(expr)
|
||||
|
||||
@doc """
|
||||
Unquotes the given list expanding its arguments. Similar
|
||||
to unquote.
|
||||
|
||||
## Examples
|
||||
|
||||
values = [2,3,4]
|
||||
quote do: sum(1, unquote_splicing(values), 5)
|
||||
#=> { :sum, [], [1, 2, 3, 4, 5] }
|
||||
|
||||
"""
|
||||
name = :unquote_splicing
|
||||
defmacro unquote(name)(expr)
|
||||
|
||||
@doc """
|
||||
List comprehensions allow you to quickly build a list from another list:
|
||||
|
||||
@@ -689,7 +764,7 @@ defmodule Kernel.SpecialForms do
|
||||
it belonged to another file.
|
||||
|
||||
quote location: :keep, do: 1
|
||||
#=> { :__scope__, 1,[[file: "iex"],[do: 1]] }
|
||||
#=> { :__scope__, [line: 1], [[file: "iex"],[do: 1]] }
|
||||
|
||||
Check `quote/1` for more information.
|
||||
"""
|
||||
|
||||
@@ -46,9 +46,9 @@ defmodule Kernel.Typespec do
|
||||
|
||||
<<_ :: _ * 8>>
|
||||
|
||||
### Functions
|
||||
### Anonymous functions
|
||||
|
||||
Any function:
|
||||
Any anonymous function:
|
||||
|
||||
(fun(...) -> any)
|
||||
or
|
||||
@@ -56,13 +56,13 @@ defmodule Kernel.Typespec do
|
||||
or
|
||||
(... -> any)
|
||||
|
||||
Function with arity of zero:
|
||||
Anonymous function with arity of zero:
|
||||
|
||||
(fun() -> type)
|
||||
or
|
||||
(() -> type)
|
||||
|
||||
Function with some arity:
|
||||
Anonymous function with some arity:
|
||||
|
||||
(fun(type, type) -> type)
|
||||
or
|
||||
@@ -279,7 +279,7 @@ defmodule Kernel.Typespec do
|
||||
lc { :attribute, _, kind, { name, _, args } = type } inlist abstract_code, kind in [:opaque, :type] do
|
||||
cond do
|
||||
kind == :opaque -> { :opaque, type }
|
||||
List.member?(exported_types, { name, length(args) }) -> { :type, type }
|
||||
:lists.member({ name, length(args) }, exported_types) -> { :type, type }
|
||||
true -> { :typep, type }
|
||||
end
|
||||
end
|
||||
@@ -333,9 +333,9 @@ defmodule Kernel.Typespec do
|
||||
end
|
||||
|
||||
defp abstract_code_beam(module) when is_atom(module) do
|
||||
case :code.which(module) do
|
||||
:non_existing -> module
|
||||
file -> file
|
||||
case :code.get_object_code(module) do
|
||||
{ ^module, beam, _filename } -> beam
|
||||
:error -> module
|
||||
end
|
||||
end
|
||||
|
||||
@@ -350,9 +350,8 @@ defmodule Kernel.Typespec do
|
||||
do_deftype(kind, type, definition, caller)
|
||||
end
|
||||
|
||||
def deftype(kind, {name, _meta, args} = type, caller) when
|
||||
is_atom(name) and
|
||||
not is_list(args) do
|
||||
def deftype(kind, {name, _meta, args} = type, caller)
|
||||
when is_atom(name) and not is_list(args) do
|
||||
do_deftype(kind, type, { :term, [line: caller.line], nil }, caller)
|
||||
end
|
||||
|
||||
@@ -617,7 +616,7 @@ defmodule Kernel.Typespec do
|
||||
|
||||
# Handle variables or local calls
|
||||
defp typespec({name, meta, atom}, vars, caller) when is_atom(atom) do
|
||||
if List.member?(vars, name) do
|
||||
if :lists.member(name, vars) do
|
||||
{ :var, line(meta), name }
|
||||
else
|
||||
typespec({name, meta, []}, vars, caller)
|
||||
|
||||
+80
-36
@@ -2,7 +2,7 @@ defmodule Keyword do
|
||||
@moduledoc """
|
||||
A keyword is a list of tuples where the first element
|
||||
of the tuple is an atom and the second element can be
|
||||
any value.
|
||||
any value.
|
||||
|
||||
A keyword may have duplicated keys, so it is not strictly
|
||||
a dictionary. However most of the functions in this module
|
||||
@@ -36,10 +36,7 @@ defmodule Keyword do
|
||||
"""
|
||||
@spec keyword?(term) :: boolean
|
||||
def keyword?([{ key, _value } | rest]) when is_atom(key) do
|
||||
case atom_to_list(key) do
|
||||
'Elixir-' ++ _ -> false
|
||||
_ -> keyword?(rest)
|
||||
end
|
||||
keyword?(rest)
|
||||
end
|
||||
|
||||
def keyword?([]), do: true
|
||||
@@ -59,7 +56,7 @@ defmodule Keyword do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Keyword.new [{:b,1},{:a,2}]
|
||||
iex> Keyword.new([{:b,1},{:a,2}])
|
||||
[a: 2, b: 1]
|
||||
|
||||
"""
|
||||
@@ -77,7 +74,7 @@ defmodule Keyword do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Enum.sort Keyword.new [:a, :b], fn x -> {x,x} end
|
||||
iex> Keyword.new([:a, :b], fn (x) -> {x,x} end) |> Enum.sort
|
||||
[a: :a, b: :b]
|
||||
|
||||
"""
|
||||
@@ -100,11 +97,11 @@ defmodule Keyword do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Keyword.get [a: 1], :a
|
||||
iex> Keyword.get([a: 1], :a)
|
||||
1
|
||||
iex> Keyword.get [a: 1], :b
|
||||
iex> Keyword.get([a: 1], :b)
|
||||
nil
|
||||
iex> Keyword.get [a: 1], :b, 3
|
||||
iex> Keyword.get([a: 1], :b, 3)
|
||||
3
|
||||
|
||||
"""
|
||||
@@ -117,32 +114,62 @@ defmodule Keyword do
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Gets the value for specific key. If key does not exist,
|
||||
an error is raised.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Keyword.get! [a: 1], :a
|
||||
1
|
||||
iex> Keyword.get! [a: 1], :b
|
||||
** (KeyError) key not found: :b
|
||||
|
||||
"""
|
||||
@spec get!(t, key) :: value | no_return
|
||||
@doc false
|
||||
def get!(keywords, key) when is_atom(key) do
|
||||
IO.write "[WARNING] Keyword.get! is deprecated, please use Keyword.fetch! instead\n#{Exception.format_stacktrace}"
|
||||
case :lists.keyfind(key, 1, keywords) do
|
||||
{ ^key, value } -> value
|
||||
false -> raise(KeyError, key: key)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Fetchs the value for specific key and return it in a tuple.
|
||||
If the key does not exist, returns `:error`.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Keyword.fetch([a: 1], :a)
|
||||
{ :ok, 1 }
|
||||
|
||||
iex> Keyword.fetch([a: 1], :b)
|
||||
:error
|
||||
|
||||
"""
|
||||
@spec fetch(t, key) :: value
|
||||
def fetch(keywords, key) when is_atom(key) do
|
||||
case :lists.keyfind(key, 1, keywords) do
|
||||
{ ^key, value } -> { :ok, value }
|
||||
false -> :error
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Fetches the value for specific key. If key does not exist,
|
||||
an error is raised.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Keyword.fetch!([a: 1], :a)
|
||||
1
|
||||
iex> Keyword.fetch!([a: 1], :b)
|
||||
** (KeyError) key not found: :b
|
||||
|
||||
"""
|
||||
@spec fetch!(t, key) :: value | no_return
|
||||
def fetch!(keywords, key) when is_atom(key) do
|
||||
case :lists.keyfind(key, 1, keywords) do
|
||||
{ ^key, value } -> value
|
||||
false -> raise(KeyError, key: key)
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Gets all values for a specific key.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Keyword.get_values [a: 1, a: 2], :a
|
||||
iex> Keyword.get_values([a: 1, a: 2], :a)
|
||||
[1,2]
|
||||
|
||||
"""
|
||||
@@ -157,7 +184,7 @@ defmodule Keyword do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Keyword.keys [a: 1, b: 2]
|
||||
iex> Keyword.keys([a: 1, b: 2])
|
||||
[:a,:b]
|
||||
|
||||
"""
|
||||
@@ -171,7 +198,7 @@ defmodule Keyword do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Keyword.values [a: 1, b: 2]
|
||||
iex> Keyword.values([a: 1, b: 2])
|
||||
[1,2]
|
||||
|
||||
"""
|
||||
@@ -188,9 +215,9 @@ defmodule Keyword do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Keyword.delete [a: 1, b: 2], :a
|
||||
iex> Keyword.delete([a: 1, b: 2], :a)
|
||||
[b: 2]
|
||||
iex> Keyword.delete [b: 2], :a
|
||||
iex> Keyword.delete([b: 2], :a)
|
||||
[b: 2]
|
||||
|
||||
"""
|
||||
@@ -199,6 +226,23 @@ defmodule Keyword do
|
||||
lc { k, _ } = tuple inlist keywords, key != k, do: tuple
|
||||
end
|
||||
|
||||
@doc """
|
||||
Deletes the first entry in the keyword list for a specific key.
|
||||
If the key does not exist, returns the keyword list unchanged.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Keyword.delete_first([a: 1, b: 2, a: 3], :a)
|
||||
[b: 2, a: 3]
|
||||
iex> Keyword.delete_first([b: 2], :a)
|
||||
[b: 2]
|
||||
|
||||
"""
|
||||
@spec delete_first(t, key) :: t
|
||||
def delete_first(keywords, key) when is_atom(key) do
|
||||
:lists.keydelete(key, 1, keywords)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Puts the given `value` under `key`.
|
||||
|
||||
@@ -207,7 +251,7 @@ defmodule Keyword do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Keyword.put [a: 1, b: 2], :a, 3
|
||||
iex> Keyword.put([a: 1, b: 2], :a, 3)
|
||||
[a: 3, b: 2]
|
||||
|
||||
"""
|
||||
@@ -222,7 +266,7 @@ defmodule Keyword do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Keyword.put_new [a: 1, b: 2], :a, 3
|
||||
iex> Keyword.put_new([a: 1, b: 2], :a, 3)
|
||||
[a: 1, b: 2]
|
||||
|
||||
"""
|
||||
@@ -240,7 +284,7 @@ defmodule Keyword do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Keyword.equal? [a: 1, b: 2], [b: 2, a: 1]
|
||||
iex> Keyword.equal?([a: 1, b: 2], [b: 2, a: 1])
|
||||
true
|
||||
|
||||
"""
|
||||
@@ -255,7 +299,7 @@ defmodule Keyword do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Enum.sort Keyword.merge [a: 1, b: 2], [a: 3, d: 4]
|
||||
iex> Keyword.merge([a: 1, b: 2], [a: 3, d: 4]) |> Enum.sort
|
||||
[a: 3, b: 2, d: 4]
|
||||
|
||||
"""
|
||||
@@ -270,9 +314,9 @@ defmodule Keyword do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Keyword.merge [a: 1, b: 2], [a: 3, d: 4], fn _k, v1, v2 ->
|
||||
iex> Keyword.merge([a: 1, b: 2], [a: 3, d: 4], fn (_k, v1, v2) ->
|
||||
...> v1 + v2
|
||||
iex> end
|
||||
iex> end)
|
||||
[a: 4, b: 2, d: 4]
|
||||
|
||||
"""
|
||||
@@ -342,7 +386,7 @@ defmodule Keyword do
|
||||
[a: 1, b: 11]
|
||||
|
||||
"""
|
||||
@spec update(t, key, value, (value -> value)) :: t
|
||||
@spec update(t, key, value, (value -> value)) :: t
|
||||
def update([{key, value}|keywords], key, _initial, fun) do
|
||||
[{key, fun.(value)}|delete(keywords, key)]
|
||||
end
|
||||
|
||||
+59
-32
@@ -15,7 +15,7 @@ defmodule List do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> List.concat [[1,[2],3], [4], [5,6]]
|
||||
iex> List.concat([[1,[2],3], [4], [5,6]])
|
||||
[1,[2],3,4,5,6]
|
||||
|
||||
"""
|
||||
@@ -32,7 +32,7 @@ defmodule List do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> List.concat [1,2,3], [4,5,6]
|
||||
iex> List.concat([1,2,3], [4,5,6])
|
||||
[1,2,3,4,5,6]
|
||||
|
||||
"""
|
||||
@@ -60,10 +60,10 @@ defmodule List do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> List.duplicate "hello", 3
|
||||
iex> List.duplicate("hello", 3)
|
||||
["hello","hello","hello"]
|
||||
|
||||
iex> List.duplicate [1,2], 2
|
||||
iex> List.duplicate([1,2], 2)
|
||||
[[1,2],[1,2]]
|
||||
"""
|
||||
def duplicate(elem, n) do
|
||||
@@ -77,10 +77,10 @@ defmodule List do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> List.flatten [1,[[2],3]]
|
||||
iex> List.flatten([1,[[2],3]])
|
||||
[1,2,3]
|
||||
|
||||
iex> List.flatten [1,[[2],3]], [4,5]
|
||||
iex> List.flatten([1,[[2],3]], [4,5])
|
||||
[1,2,3,4,5]
|
||||
|
||||
"""
|
||||
@@ -98,10 +98,10 @@ defmodule List do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> List.foldl [5,5], 10, fn x, acc -> x + acc end
|
||||
iex> List.foldl([5,5], 10, fn (x, acc) -> x + acc end)
|
||||
20
|
||||
|
||||
iex> List.foldl [1,2,3,4], 0, fn x, acc -> x - acc end
|
||||
iex> List.foldl([1,2,3,4], 0, fn (x, acc) -> x - acc end)
|
||||
2
|
||||
|
||||
"""
|
||||
@@ -115,7 +115,7 @@ defmodule List do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> List.foldr [1,2,3,4], 0, fn x, acc -> x - acc end
|
||||
iex> List.foldr([1,2,3,4], 0, fn (x, acc) -> x - acc end)
|
||||
-2
|
||||
|
||||
"""
|
||||
@@ -128,11 +128,11 @@ defmodule List do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> List.last []
|
||||
iex> List.last([])
|
||||
nil
|
||||
iex> List.last [1]
|
||||
iex> List.last([1])
|
||||
1
|
||||
iex> List.last [1, 2, 3]
|
||||
iex> List.last([1, 2, 3])
|
||||
3
|
||||
|
||||
"""
|
||||
@@ -142,27 +142,15 @@ defmodule List do
|
||||
:lists.last(list)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Checks if the given `term` is included in the list.
|
||||
This function simply delegates to `lists:member`
|
||||
which is implemented in C for performance.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> List.member? [1,2,3], 1
|
||||
true
|
||||
|
||||
iex> List.member? [1,2,3], 0
|
||||
false
|
||||
|
||||
"""
|
||||
@doc false
|
||||
def member?(list, term) do
|
||||
IO.write "[WARNING] List.member? is deprecated, please use Enum.member? instead\n#{Exception.format_stacktrace}"
|
||||
:lists.member(term, list)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Receives a list of tuples and returns the first tuple
|
||||
where the item at position `posistion` matches with the
|
||||
where the item at position `position` matches with the
|
||||
given `item`.
|
||||
|
||||
## Examples
|
||||
@@ -259,7 +247,7 @@ defmodule List do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> List.wrap [1,2,3]
|
||||
iex> List.wrap([1,2,3])
|
||||
[1,2,3]
|
||||
|
||||
"""
|
||||
@@ -280,10 +268,10 @@ defmodule List do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> List.zip [[1, 2], [3, 4], [5, 6]]
|
||||
iex> List.zip([[1, 2], [3, 4], [5, 6]])
|
||||
[{1, 3, 5}, {2, 4, 6}]
|
||||
|
||||
iex> List.zip [[1, 2], [3], [5, 6]]
|
||||
iex> List.zip([[1, 2], [3], [5, 6]])
|
||||
[{1, 3, 5}]
|
||||
|
||||
"""
|
||||
@@ -298,10 +286,10 @@ defmodule List do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> List.unzip [{1, 2}, {3, 4}]
|
||||
iex> List.unzip([{1, 2}, {3, 4}])
|
||||
[[1, 3], [2, 4]]
|
||||
|
||||
iex> List.unzip [{1, :a, "apple"}, {2, :b, "banana"}, {3, :c}]
|
||||
iex> List.unzip([{1, :a, "apple"}, {2, :b, "banana"}, {3, :c}])
|
||||
[[1, 2, 3], [:a, :b, :c]]
|
||||
|
||||
"""
|
||||
@@ -309,8 +297,47 @@ defmodule List do
|
||||
:lists.map tuple_to_list(&1), zip(list)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns a list with an inserted value at specified index. Note that the index
|
||||
is capped at the list length and that negative indicies wraps around at the
|
||||
end of the list.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> List.insert_at([1, 2, 3, 4], 2, 0)
|
||||
[1, 2, 0, 3, 4]
|
||||
|
||||
iex> List.insert_at([1, 2, 3], 10, 0)
|
||||
[1, 2, 3, 0]
|
||||
|
||||
iex> List.insert_at([1, 2, 3], -1, 0)
|
||||
[1, 2, 0, 3]
|
||||
|
||||
"""
|
||||
def insert_at(list, index, value) do
|
||||
if index < 0 do
|
||||
do_insert_at(list, length(list) + index, value)
|
||||
else
|
||||
do_insert_at(list, index, value)
|
||||
end
|
||||
end
|
||||
|
||||
## Helpers
|
||||
|
||||
# insert_at
|
||||
|
||||
defp do_insert_at([], _index, value) do
|
||||
[ value ]
|
||||
end
|
||||
|
||||
defp do_insert_at(list, index, value) when index <= 0 do
|
||||
[ value | list ]
|
||||
end
|
||||
|
||||
defp do_insert_at([h|t], index, value) do
|
||||
[ h | do_insert_at(t, index - 1, value) ]
|
||||
end
|
||||
|
||||
# zip
|
||||
|
||||
defp do_zip(list, acc) do
|
||||
|
||||
@@ -27,6 +27,18 @@ defimpl List.Chars, for: List do
|
||||
end
|
||||
|
||||
defimpl List.Chars, for: Number do
|
||||
def to_char_list(integer) when is_integer(integer), do: integer_to_list(integer)
|
||||
def to_char_list(float) when is_float(float), do: float_to_list(float)
|
||||
end
|
||||
@digits 20
|
||||
@limit :math.pow(10, @digits)
|
||||
|
||||
def to_char_list(thing) when is_integer(thing) do
|
||||
integer_to_list(thing)
|
||||
end
|
||||
|
||||
def to_char_list(thing) when thing > @limit do
|
||||
float_to_list(thing, scientific: @digits)
|
||||
end
|
||||
|
||||
def to_char_list(thing) do
|
||||
float_to_list(thing, compact: true, decimals: @digits)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
defmodule List.Dict do
|
||||
defmodule ListDict do
|
||||
@moduledoc """
|
||||
A Dict implementation that works on lists of two-items tuples.
|
||||
|
||||
@@ -7,19 +7,19 @@ defmodule List.Dict do
|
||||
"""
|
||||
|
||||
@doc """
|
||||
Returns a new `List.Dict`, i.e. an empty list.
|
||||
Returns a new `ListDict`, i.e. an empty list.
|
||||
"""
|
||||
def new, do: []
|
||||
|
||||
@doc """
|
||||
Creates a new `List.Dict` from the given pairs.
|
||||
Creates a new `ListDict` from the given pairs.
|
||||
"""
|
||||
def new(pairs) do
|
||||
Enum.map pairs, fn({ k, v }) -> { k, v } end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Creates a new `List.Dict` from the given pairs
|
||||
Creates a new `ListDict` from the given pairs
|
||||
via the given transformation function.
|
||||
"""
|
||||
def new(list, transform) when is_function(transform) do
|
||||
@@ -65,17 +65,45 @@ defmodule List.Dict do
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns the value under the given key
|
||||
raises `KeyError` if the key does not exist.
|
||||
"""
|
||||
@doc false
|
||||
def get!(dict, key) do
|
||||
IO.write "[WARNING] Dict.get! and ListDict.get! are deprecated, please use Dict.fetch! and ListDict.fetch! instead\n#{Exception.format_stacktrace}"
|
||||
case :lists.keyfind(key, 1, dict) do
|
||||
{ ^key, value } -> value
|
||||
false -> raise(KeyError, key: key)
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns the value under key from the given
|
||||
dict in a tagged tuple, otherwise `:error`.
|
||||
"""
|
||||
def fetch(dict, key) do
|
||||
case :lists.keyfind(key, 1, dict) do
|
||||
{ ^key, value } -> { :ok, value }
|
||||
false -> :error
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns the value under the given key
|
||||
raises `KeyError` if the key does not exist.
|
||||
"""
|
||||
def fetch!(dict, key) do
|
||||
case :lists.keyfind(key, 1, dict) do
|
||||
{ ^key, value } -> value
|
||||
false -> raise(KeyError, key: key)
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns the value under the given key
|
||||
from the dict as well as the dict without that key.
|
||||
"""
|
||||
def pop(dict, key, default // nil) do
|
||||
{ get(dict, key, default), delete(dict, key) }
|
||||
end
|
||||
|
||||
@doc """
|
||||
Puts the given key-value pair in the dict.
|
||||
"""
|
||||
@@ -112,6 +140,39 @@ defmodule List.Dict do
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Splits a dict into two dicts,
|
||||
one containing entries with key in the keys list,
|
||||
and another containing entries with key not in keys.
|
||||
Returns a 2-tuple of the new dicts.
|
||||
"""
|
||||
def split(dict, keys) do
|
||||
acc = { new(), new() }
|
||||
Enum.reduce dict, acc, fn({ k, v }, { take, drop }) ->
|
||||
if :lists.member(k, keys) do
|
||||
{ [{k,v}|take], drop }
|
||||
else
|
||||
{ take, [{k,v}|drop] }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns a new dict with only the entries
|
||||
which key is in keys
|
||||
"""
|
||||
def take(dict, keys) do
|
||||
lc { k, _ } = tuple inlist dict, :lists.member(k, keys), do: tuple
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns a new dict with only the entries
|
||||
which key is not in keys
|
||||
"""
|
||||
def drop(dict, keys) do
|
||||
lc { k, _ } = tuple inlist dict, not :lists.member(k, keys), do: tuple
|
||||
end
|
||||
|
||||
@doc """
|
||||
Updates the key in the dict according to the given function.
|
||||
"""
|
||||
@@ -144,10 +205,17 @@ defmodule List.Dict do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns an empty `List.Dict`.
|
||||
Returns an empty `ListDict`.
|
||||
"""
|
||||
def empty(_dict), do: []
|
||||
|
||||
@doc """
|
||||
Check if the ListDict is equal to another ListDict.
|
||||
"""
|
||||
def equal?(dict, other) do
|
||||
:lists.keysort(1, dict) == :lists.keysort(1, other)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Converts the dict to a list.
|
||||
"""
|
||||
+6
-70
@@ -64,77 +64,12 @@ defmodule Macro do
|
||||
|
||||
"""
|
||||
def escape(expr) do
|
||||
do_escape(expr, false)
|
||||
:elixir_quote.escape(expr, false) |> elem(0)
|
||||
end
|
||||
|
||||
defp do_escape({ { { :., meta, [left, :unquote] }, _, [expr] }, _, args }, true) do
|
||||
all = [do_escape(meta, true), do_escape(left, true), expr, do_escape(args, true)]
|
||||
quote do
|
||||
apply :elixir_quote, :unquote, [unquote_splicing(all), __FILE__]
|
||||
end
|
||||
end
|
||||
|
||||
defp do_escape({ { :., meta, [left, :unquote] }, _, [expr] }, true) do
|
||||
all = [do_escape(meta, true), do_escape(left, true), expr, nil]
|
||||
quote do
|
||||
apply :elixir_quote, :unquote, [unquote_splicing(all), __FILE__]
|
||||
end
|
||||
end
|
||||
|
||||
defp do_escape({ :unquote, _meta, [expr] }, true) do
|
||||
expr
|
||||
end
|
||||
|
||||
defp do_escape({ left, right }, unquote) when
|
||||
(not is_tuple(left) or elem(left, 1) != :unquote_splicing) and
|
||||
(not is_tuple(right) or elem(right, 1) != :unquote_splicing) do
|
||||
{ do_escape(left, unquote), do_escape(right, unquote) }
|
||||
end
|
||||
|
||||
defp do_escape({ :quote, meta, args }, true) when length(args) in 1..2 do
|
||||
{ :{}, [], do_escape([:quote, meta, args], false) }
|
||||
end
|
||||
|
||||
defp do_escape(tuple, unquote) when is_tuple(tuple) do
|
||||
{ :{}, [], do_escape(tuple_to_list(tuple), unquote) }
|
||||
end
|
||||
|
||||
defp do_escape(list, true) when is_list(list) do
|
||||
do_splice(Enum.reverse(list))
|
||||
end
|
||||
|
||||
defp do_escape(list, false) when is_list(list) do
|
||||
lc item inlist list, do: do_escape(item, false)
|
||||
end
|
||||
|
||||
defp do_escape(other, _unquote), do: other
|
||||
|
||||
defp do_splice([{ :|, meta, [{ :unquote_splicing, _, [left] }, right] }|t]) do
|
||||
spliced = { :++, meta, [do_splice(t, [], []), left] }
|
||||
{ :++, meta, [spliced, right] }
|
||||
end
|
||||
|
||||
defp do_splice(list) do
|
||||
do_splice(list, [], [])
|
||||
end
|
||||
|
||||
defp do_splice([{ :unquote_splicing, _meta, [expr] }|t], buffer, acc) do
|
||||
do_splice(t, [], do_splice_join(do_splice_join(expr, buffer), acc))
|
||||
end
|
||||
|
||||
defp do_splice([h|t], buffer, acc) do
|
||||
do_splice t, [do_escape(h, true)|buffer], acc
|
||||
end
|
||||
|
||||
defp do_splice([], buffer, acc), do: do_splice_join(buffer, acc)
|
||||
|
||||
defp do_splice_join([], right), do: right
|
||||
defp do_splice_join(left, []), do: left
|
||||
defp do_splice_join(left, right), do: { :++, [], [left, right] }
|
||||
|
||||
@doc false
|
||||
def escape_quoted(expr) do
|
||||
do_escape(expr, true)
|
||||
:elixir_quote.escape(expr, true) |> elem(0)
|
||||
end
|
||||
|
||||
@doc %B"""
|
||||
@@ -152,7 +87,7 @@ defmodule Macro do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Macro.unescape_binary "example\\n"
|
||||
iex> Macro.unescape_binary("example\\n")
|
||||
"example\n"
|
||||
|
||||
In the example above, we pass a string with `\n` escaped
|
||||
@@ -336,7 +271,7 @@ defmodule Macro do
|
||||
end
|
||||
|
||||
# All other structures
|
||||
def to_binary(other), do: Binary.Inspect.inspect(other, raw: true)
|
||||
def to_binary(other), do: inspect(other, raw: true)
|
||||
|
||||
# Block keywords
|
||||
defmacrop kw_keywords, do: [:do, :catch, :rescue, :after, :else]
|
||||
@@ -346,7 +281,7 @@ defmodule Macro do
|
||||
end
|
||||
defp is_kw_blocks?(_), do: false
|
||||
|
||||
defp module_to_binary(atom) when is_atom(atom), do: Binary.Inspect.inspect(atom, raw: true)
|
||||
defp module_to_binary(atom) when is_atom(atom), do: inspect(atom, raw: true)
|
||||
defp module_to_binary(other), do: call_to_binary(other)
|
||||
|
||||
defp call_to_binary(atom) when is_atom(atom), do: atom_to_binary(atom, :utf8)
|
||||
@@ -532,6 +467,7 @@ defmodule Macro do
|
||||
# Expand pseudo-variables
|
||||
defp expand({ :__MODULE__, _, atom }, env, _cache) when is_atom(atom), do: env.module
|
||||
defp expand({ :__FILE__, _, atom }, env, _cache) when is_atom(atom), do: env.file
|
||||
defp expand({ :__DIR__, _, atom }, env, _cache) when is_atom(atom), do: :filename.dirname(env.file)
|
||||
defp expand({ :__ENV__, _, atom }, env, _cache) when is_atom(atom), do: env
|
||||
|
||||
# Expand possible macro import invocation
|
||||
|
||||
@@ -19,6 +19,7 @@ defmodule Macro.Env do
|
||||
* `requires` - the list of required modules
|
||||
* `functions` - a list of functions imported from each module
|
||||
* `macros` - a list of macros imported from each module
|
||||
* `context_modules` - a list of modules defined in the current context
|
||||
"""
|
||||
|
||||
@type name_arity :: { atom, non_neg_integer }
|
||||
@@ -29,13 +30,14 @@ defmodule Macro.Env do
|
||||
@type requires :: [module]
|
||||
@type functions :: [{ module, [name_arity] }]
|
||||
@type macros :: [{ module, [name_arity] }]
|
||||
@type context_modules :: [module]
|
||||
|
||||
fields = [:module, :file, :line, :function,
|
||||
:aliases, :context, :requires, :functions, :macros]
|
||||
fields = [:module, :file, :line, :function, :aliases,
|
||||
:context, :requires, :functions, :macros, :context_modules]
|
||||
|
||||
types = quote do: [module: module, file: file, line: line,
|
||||
function: name_arity, aliases: aliases, requires: requires,
|
||||
functions: functions, macros: macros]
|
||||
functions: functions, macros: macros, context_modules: context_modules]
|
||||
|
||||
Record.deffunctions(fields, __MODULE__)
|
||||
Record.deftypes(fields, types, __MODULE__)
|
||||
|
||||
+27
-30
@@ -28,10 +28,10 @@ defmodule Module do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Evalutes the quotes contents in the given module context.
|
||||
Evaluates the quoted contents in the given module's context.
|
||||
|
||||
A list of environment options can also be given as argument.
|
||||
Check `Code.eval` for more information.
|
||||
See `Code.eval_string` for more information.
|
||||
|
||||
Raises an error if the module was already compiled.
|
||||
|
||||
@@ -116,11 +116,11 @@ defmodule Module do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Module.concat [Foo, Bar]
|
||||
iex> Module.concat([Foo, Bar])
|
||||
Foo.Bar
|
||||
iex> Module.concat [Foo, "Bar"]
|
||||
iex> Module.concat([Foo, "Bar"])
|
||||
Foo.Bar
|
||||
iex> Module.concat [Foo, 'Bar']
|
||||
iex> Module.concat([Foo, 'Bar'])
|
||||
Foo.Bar
|
||||
|
||||
"""
|
||||
@@ -134,11 +134,11 @@ defmodule Module do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Module.concat Foo, Bar
|
||||
iex> Module.concat(Foo, Bar)
|
||||
Foo.Bar
|
||||
iex> Module.concat Foo, "Bar"
|
||||
iex> Module.concat(Foo, "Bar")
|
||||
Foo.Bar
|
||||
iex> Module.concat Foo, 'Bar'
|
||||
iex> Module.concat(Foo, 'Bar')
|
||||
Foo.Bar
|
||||
|
||||
"""
|
||||
@@ -154,10 +154,10 @@ defmodule Module do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Module.safe_concat [Unknown, Module]
|
||||
iex> Module.safe_concat([Unknown, Module])
|
||||
** (ArgumentError) argument error
|
||||
|
||||
iex> Module.safe_concat [List, Chars]
|
||||
iex> Module.safe_concat([List, Chars])
|
||||
List.Chars
|
||||
|
||||
"""
|
||||
@@ -173,10 +173,10 @@ defmodule Module do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Module.safe_concat Unknown, Module
|
||||
iex> Module.safe_concat(Unknown, Module)
|
||||
** (ArgumentError) argument error
|
||||
|
||||
iex> Module.safe_concat List, Chars
|
||||
iex> Module.safe_concat(List, Chars)
|
||||
List.Chars
|
||||
|
||||
"""
|
||||
@@ -320,7 +320,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
|
||||
@@ -339,7 +339,7 @@ defmodule Module do
|
||||
def definitions_in(module) do
|
||||
assert_not_compiled!(:definitions_in, module)
|
||||
table = function_table_for(module)
|
||||
lc { tuple, _, _, _, _, _, _, _ } inlist ETS.tab2list(table), do: tuple
|
||||
lc { tuple, _, _, _, _, _, _ } inlist ETS.tab2list(table), do: tuple
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -358,7 +358,7 @@ defmodule Module do
|
||||
def definitions_in(module, kind) do
|
||||
assert_not_compiled!(:definitions_in, module)
|
||||
table = function_table_for(module)
|
||||
lc { tuple, stored_kind, _, _, _, _, _, _ } inlist ETS.tab2list(table), stored_kind == kind, do: tuple
|
||||
lc { tuple, stored_kind, _, _, _, _, _ } inlist ETS.tab2list(table), stored_kind == kind, do: tuple
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -368,20 +368,20 @@ defmodule Module do
|
||||
"""
|
||||
def make_overridable(module, tuples) do
|
||||
assert_not_compiled!(:make_overridable, module)
|
||||
table = function_table_for(module)
|
||||
|
||||
lc tuple inlist tuples do
|
||||
case ETS.lookup(table, tuple) do
|
||||
[clause] ->
|
||||
ETS.delete(table, tuple)
|
||||
case :elixir_def.lookup_definition(module, tuple) do
|
||||
false ->
|
||||
{ name, arity } = tuple
|
||||
raise "Cannot make function #{name}/#{arity} overridable because it was not defined"
|
||||
clause ->
|
||||
:elixir_def.delete_definition(module, tuple)
|
||||
|
||||
old = get_attribute(module, :__overridable)
|
||||
new = [ { tuple, { 1, clause, false } } ]
|
||||
merged = :orddict.merge(fn(_k, { count, _, _ }, _v2) -> { count + 1, clause, false } end, old, new)
|
||||
|
||||
put_attribute(module, :__overridable, merged)
|
||||
_ ->
|
||||
{ name, arity } = tuple
|
||||
raise "Cannot make function #{name}/#{arity} overridable because it was not defined"
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -412,7 +412,7 @@ defmodule Module do
|
||||
acc = ETS.lookup_element(table, :__acc_attributes, 2)
|
||||
|
||||
new =
|
||||
if List.member?(acc, key) do
|
||||
if :lists.member(key, acc) do
|
||||
case ETS.lookup(table, key) do
|
||||
[{^key,old}] -> [value|old]
|
||||
[] -> [value]
|
||||
@@ -449,7 +449,7 @@ defmodule Module do
|
||||
[{^key,old}] -> old
|
||||
[] ->
|
||||
acc = ETS.lookup_element(table, :__acc_attributes, 2)
|
||||
if List.member?(acc, key), do: [], else: nil
|
||||
if :lists.member(key, acc), do: [], else: nil
|
||||
end
|
||||
end
|
||||
|
||||
@@ -528,7 +528,7 @@ defmodule Module do
|
||||
|
||||
"""
|
||||
def split(module) do
|
||||
tl(String.split(Binary.Chars.to_binary(module), "-"))
|
||||
tl(String.split(Binary.Chars.to_binary(module), "."))
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -537,13 +537,10 @@ defmodule Module do
|
||||
def to_binary(Elixir), do: "Elixir"
|
||||
|
||||
def to_binary(module) do
|
||||
"Elixir-" <> rest = Binary.Chars.to_binary(module)
|
||||
bc <<r>> inbits rest, do: <<to_dot(r)>>
|
||||
"Elixir." <> rest = Binary.Chars.to_binary(module)
|
||||
rest
|
||||
end
|
||||
|
||||
defp to_dot(?-), do: ?.
|
||||
defp to_dot(l), do: l
|
||||
|
||||
@doc false
|
||||
# Used internally to compile documentation. This function
|
||||
# is private and must be used only internally.
|
||||
|
||||
@@ -164,4 +164,23 @@ defmodule Node do
|
||||
def spawn_link(node, module, fun, args) do
|
||||
:erlang.spawn_link(node, module, fun, args)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Sets the magic cookie of `node` to the atom `cookie`. The default node
|
||||
is `Node.self`, the local node. If node is the local node, the function also
|
||||
sets the cookie of all other unknown nodes to `cookie`.
|
||||
|
||||
This function will raise `FunctionClauseError` if the given node is not alive.
|
||||
"""
|
||||
def set_cookie(node // Node.self, cookie) when is_atom(cookie) do
|
||||
:erlang.set_cookie(node, cookie)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns the magic cookie of the local node, if the node is alive;
|
||||
otherwise `:nocookie`.
|
||||
"""
|
||||
def get_cookie() do
|
||||
:erlang.get_cookie()
|
||||
end
|
||||
end
|
||||
@@ -166,7 +166,7 @@ defmodule OptionParser do
|
||||
defp is_no?("no-" <> _), do: true
|
||||
defp is_no?(_), do: false
|
||||
|
||||
defp is_switch_a?(kind, list) when is_list(list), do: List.member?(list, kind)
|
||||
defp is_switch_a?(kind, list) when is_list(list), do: kind in list
|
||||
defp is_switch_a?(kind, kind), do: true
|
||||
defp is_switch_a?(_, _), do: false
|
||||
end
|
||||
|
||||
+40
-29
@@ -1,15 +1,15 @@
|
||||
defmodule Path do
|
||||
@moduledoc """
|
||||
This module provides conveniences for manipulating or
|
||||
retrieving filesystem paths.
|
||||
retrieving file system paths.
|
||||
|
||||
The functions on this module may receive a char list or
|
||||
a binary as argument and will return the given type.
|
||||
The functions in this module may receive a char list or
|
||||
a binary as argument and will return a value of the same
|
||||
type.
|
||||
|
||||
The majority of the functions in this module do not
|
||||
interact with the file system, unless some few functions
|
||||
that needs to query the filesystem to retrieve paths
|
||||
(like `Path.wildcard` and `Path.expand`).
|
||||
interact with the file system, except for a few functions
|
||||
that require it (like `Path.wildcard` and `Path.expand`).
|
||||
"""
|
||||
|
||||
alias :filename, as: FN
|
||||
@@ -18,9 +18,8 @@ defmodule Path do
|
||||
@type r :: char_list | binary
|
||||
|
||||
@doc """
|
||||
Converts the given filename and returns an absolute name.
|
||||
Differently from `Path.expand/1`, no attempt is made to
|
||||
resolve `..`, `.` or `~`.
|
||||
Converts the given path to an absolute one. Differently from
|
||||
`Path.expand/1`, no attempt is made to resolve `..`, `.` or `~`.
|
||||
|
||||
## Unix examples
|
||||
|
||||
@@ -43,9 +42,8 @@ defmodule Path do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Converts the given filename and returns an absolute name
|
||||
relative to the given location. If the path is already
|
||||
an absolute path, the relative path is ignored.
|
||||
Builds a path from `relative_to` to `path`. If `path` is already
|
||||
an absolute path, `relative_to` is ignored. See also `Path.relative/2`.
|
||||
|
||||
Differently from `Path.expand/2`, no attempt is made to
|
||||
resolve `..`, `.` or `~`.
|
||||
@@ -64,8 +62,8 @@ defmodule Path do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Expands the path by returning its absolute name and expanding
|
||||
any `.` and `..` characters.
|
||||
Converts the path to an absolute one and expands
|
||||
any `.` and `..` characters and a leading `~`.
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -78,12 +76,21 @@ defmodule Path do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Expands the path to the relative location and expanding
|
||||
any `.` and `..` characters. If the path is already an
|
||||
absolute path, the relative location is ignored.
|
||||
Expands the path relative to the path given as the second argument
|
||||
expanding any `.` and `..` characters. If the path is already an
|
||||
absolute path, `relative_to` is ignored.
|
||||
|
||||
Note, that this function treats `path` with leading `~` as
|
||||
an absolute one.
|
||||
|
||||
The second argument is first expanded to an absolute path.
|
||||
|
||||
## Examples
|
||||
|
||||
# Assuming that the absolute path to baz is /quux/baz
|
||||
Path.expand("foo/bar/../bar", "baz")
|
||||
#=> "/quux/baz/foo/bar"
|
||||
|
||||
iex> Path.expand("foo/bar/../bar", "/baz")
|
||||
"/baz/foo/bar"
|
||||
iex> Path.expand("/foo/bar/../bar", "/baz")
|
||||
@@ -91,7 +98,7 @@ defmodule Path do
|
||||
|
||||
"""
|
||||
def expand(path, relative_to) do
|
||||
normalize FN.absname(FN.absname(expand_home(path), relative_to), get_cwd(path))
|
||||
normalize FN.absname(FN.absname(expand_home(path), expand_home(relative_to)), get_cwd(path))
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -102,6 +109,7 @@ defmodule Path do
|
||||
Path.type("/usr/local/bin") #=> :absolute
|
||||
Path.type("usr/local/bin") #=> :relative
|
||||
Path.type("../usr/local/bin") #=> :relative
|
||||
Path.type("~/file") #=> :relative
|
||||
|
||||
## Windows examples
|
||||
|
||||
@@ -185,8 +193,9 @@ defmodule Path do
|
||||
|
||||
@doc """
|
||||
Returns the given `path` relative to the given `from` path.
|
||||
In other words, it tries to strip the `from` prefix from `path`.
|
||||
|
||||
This function does not query the filesystem, so it assumes
|
||||
This function does not query the file system, so it assumes
|
||||
no symlinks in between the paths.
|
||||
|
||||
In case a direct relative path cannot be found, it returns
|
||||
@@ -267,12 +276,14 @@ defmodule Path do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Return the `directory` component of `path`.
|
||||
Returns the directory component of `path`.
|
||||
|
||||
## Examples
|
||||
|
||||
Path.dirname("/foo/bar.ex")
|
||||
#=> "foo"
|
||||
#=> "/foo"
|
||||
Path.dirname("/foo/bar/baz.ex")
|
||||
#=> "/foo/bar"
|
||||
|
||||
"""
|
||||
def dirname(path) do
|
||||
@@ -280,7 +291,7 @@ defmodule Path do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Return the `extension` of the last component of `path`.
|
||||
Returns the extension of the last component of `path`.
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -326,8 +337,8 @@ defmodule Path do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns a string with one or more paths components joint by the path separator.
|
||||
This function should be used to convert a list of strings in a path.
|
||||
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.
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -409,8 +420,8 @@ defmodule Path do
|
||||
:lists.reverse(name)
|
||||
|
||||
@doc """
|
||||
Returns a list with the path splitted by the path separator.
|
||||
If an empty string is given, then it returns the root path.
|
||||
Returns a list with the path split by the path separator.
|
||||
If an empty string is given, returns the root path.
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -449,8 +460,8 @@ defmodule Path do
|
||||
## Examples
|
||||
|
||||
Imagine you have a directory called `projects` with three Elixir projects
|
||||
inside of it: `elixir`, `exdoc` and `dynamo`. You can find all `.beam` files
|
||||
inside their ebin directories all projects as follows:
|
||||
inside of it: `elixir`, `ex_doc` and `dynamo`. You can find all `.beam` files
|
||||
inside the ebin directory of each project as follows:
|
||||
|
||||
Path.wildcard("projects/*/ebin/**/*.beam")
|
||||
|
||||
@@ -520,4 +531,4 @@ defmodule Path do
|
||||
defp normalize([], acc) do
|
||||
join Enum.reverse(acc)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -19,13 +19,6 @@ defmodule Process do
|
||||
:erlang.is_process_alive(pid)
|
||||
end
|
||||
|
||||
@doc false
|
||||
def self do
|
||||
IO.puts "Process.self is deprecated, please use Kernel.self instead"
|
||||
Exception.print_stacktrace
|
||||
:erlang.self()
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns all key-values in the dictionary.
|
||||
"""
|
||||
|
||||
@@ -267,7 +267,7 @@ defmodule Protocol do
|
||||
true -> unquote(fallback)
|
||||
false ->
|
||||
case atom_to_list(first) do
|
||||
'Elixir-' ++ _ -> __MODULE__.Record
|
||||
'Elixir.' ++ _ -> __MODULE__.Record
|
||||
_ -> unquote(fallback)
|
||||
end
|
||||
end
|
||||
|
||||
+28
-20
@@ -5,14 +5,7 @@ defrecord Range, [:first, :last] do
|
||||
end
|
||||
|
||||
defprotocol Range.Iterator do
|
||||
@doc """
|
||||
How to iterate the range, receives the first
|
||||
and range as arguments. It needs to return a
|
||||
function that receives an item and returns
|
||||
a tuple with two elements: the given item
|
||||
and the next item in the iteration.
|
||||
"""
|
||||
def iterator(first, range)
|
||||
def reduce(first, range, acc, fun)
|
||||
|
||||
@doc """
|
||||
Count how many items are in the range.
|
||||
@@ -20,10 +13,12 @@ defprotocol Range.Iterator do
|
||||
def count(first, range)
|
||||
end
|
||||
|
||||
defimpl Enum.Iterator, for: Range do
|
||||
def iterator(Range[first: first] = range) do
|
||||
iterator = Range.Iterator.iterator(first, range)
|
||||
{ iterator, iterator.(first) }
|
||||
defimpl Enumerable, for: Range do
|
||||
def reduce(Range[first: first] = range, acc, fun) do
|
||||
Range.Iterator.reduce(first, range, acc, fun)
|
||||
end
|
||||
def member?(Range[first: first, last: last], value) do
|
||||
value in first..last
|
||||
end
|
||||
|
||||
def count(Range[first: first] = range) do
|
||||
@@ -32,16 +27,29 @@ defimpl Enum.Iterator, for: Range do
|
||||
end
|
||||
|
||||
defimpl Range.Iterator, for: Number do
|
||||
def iterator(first, Range[last: last]) when is_integer(first) and is_integer(last) and last >= first do
|
||||
fn(current) ->
|
||||
if current > last, do: :stop, else: { current, current + 1 }
|
||||
def reduce(first, Range[last: last], acc, fun) when is_integer(first) and is_integer(last) do
|
||||
reducer = if last >= first do
|
||||
fn(acc, fun) -> do_reducer_up(first, last, acc, fun) end
|
||||
else
|
||||
fn(acc, fun) -> do_reducer_down(first, last, acc, fun) end
|
||||
end
|
||||
Enumerable.Function.reduce(reducer, acc, fun)
|
||||
end
|
||||
|
||||
def iterator(first, Range[last: last]) when is_integer(first) and is_integer(last) do
|
||||
fn(current) ->
|
||||
if current < last, do: :stop, else: { current, current - 1 }
|
||||
end
|
||||
defp do_reducer_up(counter, last, acc, _fun) when counter > last do
|
||||
acc
|
||||
end
|
||||
|
||||
defp do_reducer_up(counter, last, acc, fun) do
|
||||
do_reducer_up(counter + 1, last, fun.(counter, acc), fun)
|
||||
end
|
||||
|
||||
defp do_reducer_down(counter, last, acc, _fun) when counter < last do
|
||||
acc
|
||||
end
|
||||
|
||||
defp do_reducer_down(counter, last, acc, fun) do
|
||||
do_reducer_down(counter - 1, last, fun.(counter, acc), fun)
|
||||
end
|
||||
|
||||
def count(first, Range[last: last]) when is_integer(first) and is_integer(last) and last >= first do
|
||||
@@ -57,6 +65,6 @@ defimpl Binary.Inspect, for: Range do
|
||||
import Kernel, except: [inspect: 2]
|
||||
|
||||
def inspect(Range[first: first, last: last], opts) do
|
||||
Binary.Inspect.inspect(first, opts) <> ".." <> Binary.Inspect.inspect(last, opts)
|
||||
Kernel.inspect(first, opts) <> ".." <> Kernel.inspect(last, opts)
|
||||
end
|
||||
end
|
||||
|
||||
+14
-12
@@ -24,9 +24,10 @@ defmodule Record do
|
||||
"""
|
||||
def defrecord(name, values, opts) do
|
||||
block = Keyword.get(opts, :do, nil)
|
||||
opts = Keyword.delete(opts, :do)
|
||||
|
||||
quote do
|
||||
values = unquote(values)
|
||||
|
||||
defmodule unquote(name) do
|
||||
@moduledoc false
|
||||
import Record.DSL
|
||||
@@ -34,8 +35,6 @@ defmodule Record do
|
||||
@record_fields []
|
||||
@record_types []
|
||||
|
||||
values = unquote(values)
|
||||
opts = unquote(opts)
|
||||
Record.deffunctions(values, __ENV__)
|
||||
value = unquote(block)
|
||||
Record.deftypes(values, @record_types, __ENV__)
|
||||
@@ -157,7 +156,6 @@ defmodule Record do
|
||||
end
|
||||
|
||||
contents = quote do
|
||||
|
||||
defmacrop unquote(name)() do
|
||||
Record.access(unquote(tag) || __MODULE__, unquote(escaped), [], __CALLER__)
|
||||
end
|
||||
@@ -243,7 +241,8 @@ defmodule Record do
|
||||
{ match, remaining } = :lists.mapfoldl(iterator, keyword, fields)
|
||||
|
||||
case remaining do
|
||||
[] -> { :{}, [line: caller.line], [atom|match] }
|
||||
[] ->
|
||||
quote do: { unquote_splicing([atom|match]) }
|
||||
_ ->
|
||||
keys = lc { key, _ } inlist remaining, do: key
|
||||
raise "record #{inspect atom} does not have the keys: #{inspect keys}"
|
||||
@@ -417,6 +416,7 @@ defmodule Record do
|
||||
quoted = lc { k, _ } inlist values do
|
||||
index = find_index(values, k, 0)
|
||||
quote do
|
||||
@doc false
|
||||
def __index__(unquote(k)), do: unquote(index + 1)
|
||||
end
|
||||
end
|
||||
@@ -459,27 +459,27 @@ defmodule Record do
|
||||
#
|
||||
# It will define four methods:
|
||||
#
|
||||
# def :atime.(record) do
|
||||
# def atime(record) do
|
||||
# elem(record, 1)
|
||||
# end
|
||||
#
|
||||
# def :mtime.(record) do
|
||||
# def mtime(record) do
|
||||
# elem(record, 2)
|
||||
# end
|
||||
#
|
||||
# def :atime.(value, record) do
|
||||
# def atime(value, record) do
|
||||
# set_elem(record, 1, value)
|
||||
# end
|
||||
#
|
||||
# def :mtime.(record) do
|
||||
# def mtime(record) do
|
||||
# set_elem(record, 2, value)
|
||||
# end
|
||||
#
|
||||
# def :atime.(callback, record) do
|
||||
# def atime(callback, record) do
|
||||
# set_elem(record, 1, callback.(elem(record, 1)))
|
||||
# end
|
||||
#
|
||||
# def :mtime.(callback, record) do
|
||||
# def mtime(callback, record) do
|
||||
# set_elem(record, 2, callback.(elem(record, 2)))
|
||||
# end
|
||||
#
|
||||
@@ -526,7 +526,7 @@ defmodule Record do
|
||||
end
|
||||
end
|
||||
|
||||
contents = { :{}, [], [(quote do: __MODULE__)|fields] }
|
||||
contents = quote do: { __MODULE__, unquote_splicing(fields) }
|
||||
|
||||
quote do
|
||||
@doc false
|
||||
@@ -572,6 +572,8 @@ defmodule Record do
|
||||
@spec new(options | tuple) :: t
|
||||
@spec to_keywords(t) :: options
|
||||
@spec update(options, t) :: t
|
||||
@spec __record__(:name) :: atom
|
||||
@spec __record__(:fields) :: [{atom,any}]
|
||||
@spec __index__(atom) :: non_neg_integer | nil
|
||||
end
|
||||
end
|
||||
|
||||
+16
-14
@@ -86,9 +86,9 @@ defmodule Regex do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Regex.index %r/c(d)/, "abcd"
|
||||
iex> Regex.index(%r/c(d)/, "abcd")
|
||||
2
|
||||
iex> Regex.index %r/e/, "abcd"
|
||||
iex> Regex.index(%r/e/, "abcd")
|
||||
nil
|
||||
|
||||
"""
|
||||
@@ -104,9 +104,9 @@ defmodule Regex do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Regex.match? %r/foo/, "foo"
|
||||
iex> Regex.match?(%r/foo/, "foo")
|
||||
true
|
||||
iex> Regex.match? %r/foo/, "bar"
|
||||
iex> Regex.match?(%r/foo/, "bar")
|
||||
false
|
||||
|
||||
"""
|
||||
@@ -116,13 +116,14 @@ defmodule Regex do
|
||||
|
||||
@doc """
|
||||
Runs the regular expression against the given string.
|
||||
It returns a list with all matches or nil if no match ocurred.
|
||||
It returns a list with all matches, nil if no match ocurred, or []
|
||||
if it matched, /g was specified, but nothing was captured.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Regex.run %r/c(d)/, "abcd"
|
||||
iex> Regex.run(%r/c(d)/, "abcd")
|
||||
["cd", "d"]
|
||||
iex> Regex.run %r/e/, "abcd"
|
||||
iex> Regex.run(%r/e/, "abcd")
|
||||
nil
|
||||
|
||||
"""
|
||||
@@ -139,6 +140,7 @@ defmodule Regex do
|
||||
|
||||
case :re.run(string, compiled, [{ :capture, captures, return }]) do
|
||||
:nomatch -> nil
|
||||
:match -> []
|
||||
{ :match, results } -> results
|
||||
end
|
||||
end
|
||||
@@ -149,7 +151,7 @@ defmodule Regex do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Regex.captures %r/c(?<foo>d)/g, "abcd"
|
||||
iex> Regex.captures(%r/c(?<foo>d)/g, "abcd")
|
||||
[foo: "d"]
|
||||
|
||||
"""
|
||||
@@ -178,7 +180,7 @@ defmodule Regex do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Regex.source %r(foo)
|
||||
iex> Regex.source(%r(foo))
|
||||
"foo"
|
||||
|
||||
"""
|
||||
@@ -191,7 +193,7 @@ defmodule Regex do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Regex.opts %r(foo)m
|
||||
iex> Regex.opts(%r(foo)m)
|
||||
"m"
|
||||
|
||||
"""
|
||||
@@ -204,7 +206,7 @@ defmodule Regex do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Regex.groups %r/(?<foo>foo)/g
|
||||
iex> Regex.groups(%r/(?<foo>foo)/g)
|
||||
[:foo]
|
||||
|
||||
"""
|
||||
@@ -220,11 +222,11 @@ defmodule Regex do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Regex.scan %r/c(d|e)/, "abcd abce"
|
||||
iex> Regex.scan(%r/c(d|e)/, "abcd abce")
|
||||
[["d"], ["e"]]
|
||||
iex> Regex.scan %r/c(?:d|e)/, "abcd abce"
|
||||
iex> Regex.scan(%r/c(?:d|e)/, "abcd abce")
|
||||
["cd", "ce"]
|
||||
iex> Regex.scan %r/e/, "abcd"
|
||||
iex> Regex.scan(%r/e/, "abcd")
|
||||
[]
|
||||
|
||||
"""
|
||||
|
||||
+148
-21
@@ -62,7 +62,7 @@ defmodule String do
|
||||
Although codepoints could be represented as integers, this
|
||||
module represents all codepoints as strings. For example:
|
||||
|
||||
iex> String.codepoints "josé"
|
||||
iex> String.codepoints("josé")
|
||||
["j", "o", "s", "é"]
|
||||
|
||||
There are a couple of ways to retrieve a character integer
|
||||
@@ -139,18 +139,8 @@ defmodule String do
|
||||
def printable?(_), do: false
|
||||
|
||||
@doc """
|
||||
Divides a string into sub string based on a pattern,
|
||||
returning a list of these sub string. The pattern can
|
||||
be a string, a list of strings or a regular expression.
|
||||
|
||||
The string is split into as many parts as possible by
|
||||
default, unless the `global` option is set to false.
|
||||
If a pattern is not specified, the string is split on
|
||||
Unicode whitespace occurrences with leading and trailing
|
||||
whitespace ignored.
|
||||
|
||||
It returns a list with the original string if the pattern
|
||||
can't be matched.
|
||||
Splits a string on sub strings at each Unicode whitespace
|
||||
occurrence with leading and trailing whitespace ignored.
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -161,6 +151,20 @@ defmodule String do
|
||||
iex> String.split(" foo bar ")
|
||||
["foo", "bar"]
|
||||
|
||||
"""
|
||||
@spec split(t) :: [t]
|
||||
defdelegate split(binary), to: String.Unicode
|
||||
|
||||
@doc """
|
||||
Divides a string into sub strings based on a pattern,
|
||||
returning a list of these sub string. The pattern can
|
||||
be a string, a list of strings or a regular expression.
|
||||
|
||||
The string is split into as many parts as possible by
|
||||
default, unless the `global` option is set to false.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> String.split("a,b,c", ",")
|
||||
["a", "b", "c"]
|
||||
iex> String.split("a,b,c", ",", global: false)
|
||||
@@ -177,12 +181,8 @@ defmodule String do
|
||||
["a,b"]
|
||||
|
||||
"""
|
||||
@spec split(t) :: [t]
|
||||
@spec split(t, t | [t] | Regex.t) :: [t]
|
||||
@spec split(t, t | [t] | Regex.t, Keyword.t) :: [t]
|
||||
|
||||
defdelegate split(binary), to: String.Unicode
|
||||
|
||||
def split(binary, pattern, options // [])
|
||||
|
||||
def split(binary, pattern, options) when is_regex(pattern) do
|
||||
@@ -458,6 +458,61 @@ defmodule String do
|
||||
@spec next_codepoint(t) :: {codepoint, t} | :no_codepoint
|
||||
defdelegate next_codepoint(string), to: String.Unicode
|
||||
|
||||
@doc %B"""
|
||||
Checks whether `str` contains only valid characters.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> String.valid?("a")
|
||||
true
|
||||
iex> String.valid?("ø")
|
||||
true
|
||||
iex> String.valid?(<<0xffff :: 16>>)
|
||||
false
|
||||
iex> String.valid?("asd" <> <<0xffff :: 16>>)
|
||||
false
|
||||
|
||||
"""
|
||||
@spec valid?(t) :: boolean
|
||||
|
||||
noncharacters = Enum.to_list(?\x{FDD0}..?\x{FDEF}) ++
|
||||
[ ?\x{0FFFE}, ?\x{0FFFF}, ?\x{1FFFE}, ?\x{1FFFF}, ?\x{2FFFE}, ?\x{2FFFF},
|
||||
?\x{3FFFE}, ?\x{3FFFF}, ?\x{4FFFE}, ?\x{4FFFF}, ?\x{5FFFE}, ?\x{5FFFF},
|
||||
?\x{6FFFE}, ?\x{6FFFF}, ?\x{7FFFE}, ?\x{7FFFF}, ?\x{8FFFE}, ?\x{8FFFF},
|
||||
?\x{9FFFE}, ?\x{9FFFF}, ?\x{10FFFE}, ?\x{10FFFF} ]
|
||||
|
||||
lc noncharacter inlist noncharacters do
|
||||
def valid?(<< unquote(noncharacter) :: utf8, _ :: binary >>), do: false
|
||||
end
|
||||
|
||||
def valid?(<<_ :: utf8, t :: binary>>), do: valid?(t)
|
||||
def valid?(<<>>), do: true
|
||||
def valid?(_), do: false
|
||||
|
||||
@doc %B"""
|
||||
Checks whether `str` is a valid character.
|
||||
|
||||
All characters are codepoints, but some codepoints
|
||||
are not valid characters. They may be reserved, private,
|
||||
or other.
|
||||
|
||||
More info at: http://en.wikipedia.org/wiki/Mapping_of_Unicode_characters#Noncharacters
|
||||
|
||||
## Examples
|
||||
|
||||
iex> String.valid_character?("a")
|
||||
true
|
||||
iex> String.valid_character?("ø")
|
||||
true
|
||||
iex> String.valid_character?("\x{ffff}")
|
||||
false
|
||||
|
||||
"""
|
||||
@spec valid_character?(t) :: boolean
|
||||
|
||||
def valid_character?(<<_ :: utf8>> = codepoint), do: valid?(codepoint)
|
||||
def valid_character?(_), do: false
|
||||
|
||||
@doc %B"""
|
||||
Checks whether `str` is a valid codepoint.
|
||||
|
||||
@@ -470,7 +525,7 @@ defmodule String do
|
||||
true
|
||||
iex> String.valid_codepoint?("ø")
|
||||
true
|
||||
iex> String.valid_codepoint?("\xffff")
|
||||
iex> String.valid_codepoint?(<<0xffff :: 16>>)
|
||||
false
|
||||
iex> String.valid_codepoint?("asdf")
|
||||
false
|
||||
@@ -505,11 +560,12 @@ defmodule String do
|
||||
{ "j", "osé" }
|
||||
|
||||
"""
|
||||
@spec next_grapheme(t) :: grapheme | :no_grapheme
|
||||
@spec next_grapheme(t) :: { grapheme, t } | :no_grapheme
|
||||
defdelegate next_grapheme(string), to: String.Unicode
|
||||
|
||||
@doc """
|
||||
Returns the first grapheme from an utf8 string.
|
||||
Returns the first grapheme from an utf8 string,
|
||||
nil if the string is empty.
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -528,7 +584,8 @@ defmodule String do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns the last grapheme from an utf8 string.
|
||||
Returns the last grapheme from an utf8 string,
|
||||
nil if the string is empty.
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -630,6 +687,12 @@ defmodule String do
|
||||
"ixir"
|
||||
iex> String.slice("elixir", -10, 3)
|
||||
nil
|
||||
iex> String.slice("a", 0, 1500)
|
||||
"a"
|
||||
iex> String.slice("a", 1, 1500)
|
||||
""
|
||||
iex> String.slice("a", 2, 1500)
|
||||
nil
|
||||
|
||||
"""
|
||||
@spec slice(t, integer, integer) :: grapheme | nil
|
||||
@@ -662,10 +725,74 @@ defmodule String do
|
||||
acc <> char
|
||||
end
|
||||
|
||||
defp do_slice(:no_grapheme, start_pos, _, current_pos, acc) when start_pos == current_pos do
|
||||
acc
|
||||
end
|
||||
|
||||
defp do_slice(:no_grapheme, _, _, _, acc) do
|
||||
case acc do
|
||||
"" -> nil
|
||||
_ -> acc
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Converts a string to an integer. If successful, returns a
|
||||
tuple of form {integer, remainder of string}. If unsuccessful,
|
||||
returns :error.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> String.to_integer("34")
|
||||
{34,""}
|
||||
iex> String.to_integer("34.5")
|
||||
{34,".5"}
|
||||
iex> String.to_integer("three")
|
||||
:error
|
||||
|
||||
"""
|
||||
@spec to_integer(t) :: {integer, t} | :error
|
||||
|
||||
def to_integer(string) do
|
||||
{result, remainder} = :string.to_integer(binary_to_list(string))
|
||||
case result do
|
||||
:error -> :error
|
||||
_ -> {result, list_to_binary(remainder)}
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Converts a string to a float. If successful, returns a
|
||||
tuple of form {float, remainder of string}. If unsuccessful,
|
||||
returns :error. If given an integer value, will return
|
||||
same as to_integer/1.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> String.to_float("34")
|
||||
{34.0,""}
|
||||
iex> String.to_float("34.25")
|
||||
{34.25,""}
|
||||
iex> String.to_float("56.5xyz")
|
||||
{56.5,"xyz"}
|
||||
iex> String.to_float("pi")
|
||||
:error
|
||||
|
||||
"""
|
||||
@spec to_float(t) :: {integer, t} | :error
|
||||
|
||||
def to_float(string) do
|
||||
charlist = binary_to_list(string)
|
||||
{result, remainder} = :string.to_float(charlist)
|
||||
case result do
|
||||
:error ->
|
||||
{int_result, int_remainder} = :string.to_integer(charlist)
|
||||
case int_result do
|
||||
:error -> :error
|
||||
_ -> {float(int_result), list_to_binary(int_remainder)}
|
||||
end
|
||||
_ -> {result, list_to_binary(remainder)}
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
@@ -294,19 +294,11 @@ defmodule System do
|
||||
def halt(status // 0, options // [])
|
||||
|
||||
def halt(status, options) when is_integer(status) or status == :abort do
|
||||
do_halt(status, options)
|
||||
:erlang.halt(status, options)
|
||||
end
|
||||
|
||||
def halt(status, options) do
|
||||
do_halt(to_char_list(status), options)
|
||||
end
|
||||
|
||||
# Support R15B
|
||||
if List.member?(:erlang.module_info(:exports), { :halt, 2 }) do
|
||||
defp do_halt(status, options), do: :erlang.halt(status, options)
|
||||
else
|
||||
IO.puts "Using limited halt support. Upgrade to R15B01 or later is recommended."
|
||||
defp do_halt(status, _options), do: :erlang.halt(status)
|
||||
:erlang.halt(to_char_list(status), options)
|
||||
end
|
||||
|
||||
## Helpers
|
||||
|
||||
+17
-7
@@ -37,14 +37,16 @@ defmodule URI do
|
||||
the query string in steps.
|
||||
"""
|
||||
def query_decoder(q) when is_binary(q) do
|
||||
fn -> { do_decoder(&1), q } end
|
||||
fn(acc, fun) ->
|
||||
do_decoder(q, acc, fun)
|
||||
end
|
||||
end
|
||||
|
||||
defp do_decoder("") do
|
||||
:stop
|
||||
defp do_decoder("", acc, _fun) do
|
||||
acc
|
||||
end
|
||||
|
||||
defp do_decoder(q) do
|
||||
defp do_decoder(q, acc, fun) do
|
||||
next =
|
||||
case :binary.split(q, "&") do
|
||||
[first, rest] -> rest
|
||||
@@ -57,7 +59,7 @@ defmodule URI do
|
||||
[ key ] -> { decode(key), nil }
|
||||
end
|
||||
|
||||
{ current, next }
|
||||
do_decoder(next, fun.(current, acc), fun)
|
||||
end
|
||||
|
||||
defp pair({k, v}) do
|
||||
@@ -131,8 +133,16 @@ defmodule URI do
|
||||
destructure [_, _, scheme, _, authority, path, _, query, _, fragment], parts
|
||||
{ userinfo, host, port } = split_authority(authority)
|
||||
|
||||
if authority do
|
||||
authority = ""
|
||||
|
||||
if userinfo, do: authority = authority <> userinfo <> "@"
|
||||
if host, do: authority = authority <> host
|
||||
if port, do: authority = authority <> ":" <> integer_to_binary(port)
|
||||
end
|
||||
|
||||
info = URI.Info[
|
||||
scheme: scheme, path: path, query: query,
|
||||
scheme: scheme && String.downcase(scheme), path: path, query: query,
|
||||
fragment: fragment, authority: authority,
|
||||
userinfo: userinfo, host: host, port: port
|
||||
]
|
||||
@@ -169,7 +179,7 @@ defmodule URI do
|
||||
components = Regex.run %r/(^(.*)@)?([^:]*)(:(\d*))?/, s
|
||||
destructure [_, _, userinfo, host, _, port], nillify(components)
|
||||
port = if port, do: binary_to_integer(port)
|
||||
{ userinfo, host, port }
|
||||
{ userinfo, host && String.downcase(host), port }
|
||||
end
|
||||
|
||||
# Regex.run returns empty strings sometimes. We want
|
||||
|
||||
@@ -16,7 +16,7 @@ defmodule String.Unicode do
|
||||
end
|
||||
end
|
||||
|
||||
data_path = Path.expand("../UnicodeData.txt", __FILE__)
|
||||
data_path = Path.join(__DIR__, "UnicodeData.txt")
|
||||
|
||||
{ codes, whitespace } = Enum.reduce File.iterator!(data_path), { [], [] }, fn(line, { cacc, wacc }) ->
|
||||
[ codepoint, _name, _category,
|
||||
@@ -37,7 +37,7 @@ defmodule String.Unicode do
|
||||
end
|
||||
end
|
||||
|
||||
special_path = Path.expand("../SpecialCasing.txt", __FILE__)
|
||||
special_path = Path.join(__DIR__, "SpecialCasing.txt")
|
||||
|
||||
codes = Enum.reduce File.iterator!(special_path), codes, fn(line, acc) ->
|
||||
[ codepoint, lower, title, upper, _comment ] = :binary.split(line, "; ", [:global])
|
||||
@@ -45,7 +45,7 @@ defmodule String.Unicode do
|
||||
:lists.keystore(key, 1, acc, { key, to_binary.(upper), to_binary.(lower), to_binary.(title) })
|
||||
end
|
||||
|
||||
seqs_path = Path.expand("../NamedSequences.txt", __FILE__)
|
||||
seqs_path = Path.join(__DIR__, "NamedSequences.txt")
|
||||
|
||||
seqs = Enum.map File.iterator!(seqs_path), fn(line) ->
|
||||
[ _name, codepoints ] = :binary.split(line, ";", [:global])
|
||||
|
||||
@@ -24,4 +24,4 @@
|
||||
{verbose, false}
|
||||
]}.
|
||||
|
||||
{require_otp_vsn,"(R15|R16).*"}.
|
||||
{require_otp_vsn,"(R16).*"}.
|
||||
|
||||
+33
-23
@@ -1,11 +1,11 @@
|
||||
-module(elixir).
|
||||
-behaviour(application).
|
||||
-export([main/1, start_cli/0,
|
||||
scope_for_eval/1, eval/2, eval/3, eval/4,
|
||||
scope_for_eval/1, scope_for_eval/2,
|
||||
eval/2, eval/3, eval/4,
|
||||
eval_quoted/2, eval_quoted/3, eval_quoted/4,
|
||||
eval_forms/3, translate_forms/3]).
|
||||
-include("elixir.hrl").
|
||||
-compile({parse_transform, elixir_transform}).
|
||||
|
||||
%% Top level types
|
||||
-export_type([char_list/0, as_boolean/1]).
|
||||
@@ -20,8 +20,8 @@ start(_Type, _Args) ->
|
||||
%% Set the shell to unicode so printing inside scripts work
|
||||
%% Those can take a while, so let's do it in a new process
|
||||
spawn(fun() ->
|
||||
io:setopts(standard_io, [{encoding,unicode}]),
|
||||
io:setopts(standard_error, [{encoding,unicode}])
|
||||
io:setopts(standard_io, [binary,{encoding,unicode}]),
|
||||
io:setopts(standard_error, [binary,{encoding,unicode}])
|
||||
end),
|
||||
elixir_sup:start_link([]).
|
||||
|
||||
@@ -46,37 +46,47 @@ start_cli() ->
|
||||
%% EVAL HOOKS
|
||||
|
||||
scope_for_eval(Opts) ->
|
||||
case lists:keyfind(file, 1, Opts) of
|
||||
{ file, RawFile } -> File = to_binary(RawFile);
|
||||
false -> File = <<"nofile">>
|
||||
scope_for_eval(#elixir_scope{
|
||||
file = <<"nofile">>,
|
||||
local = nil,
|
||||
aliases = [],
|
||||
requires = elixir_dispatch:default_requires(),
|
||||
functions = elixir_dispatch:default_functions(),
|
||||
macros = elixir_dispatch:default_macros()
|
||||
}, Opts).
|
||||
|
||||
scope_for_eval(Scope, Opts) ->
|
||||
File = case lists:keyfind(file, 1, Opts) of
|
||||
{ file, RawFile } -> to_binary(RawFile);
|
||||
false -> Scope#elixir_scope.file
|
||||
end,
|
||||
|
||||
case lists:keyfind(delegate_locals_to, 1, Opts) of
|
||||
{ delegate_locals_to, Local } -> Local;
|
||||
false -> Local = nil
|
||||
Local = case lists:keyfind(delegate_locals_to, 1, Opts) of
|
||||
{ delegate_locals_to, LocalOpt } -> LocalOpt;
|
||||
false -> Scope#elixir_scope.local
|
||||
end,
|
||||
|
||||
case lists:keyfind(aliases, 1, Opts) of
|
||||
{ aliases, Aliases } -> Aliases;
|
||||
false -> Aliases = []
|
||||
Aliases = case lists:keyfind(aliases, 1, Opts) of
|
||||
{ aliases, AliasesOpt } -> AliasesOpt;
|
||||
false -> Scope#elixir_scope.aliases
|
||||
end,
|
||||
|
||||
case lists:keyfind(requires, 1, Opts) of
|
||||
{ requires, List } -> Requires = ordsets:from_list(List);
|
||||
false -> Requires = elixir_dispatch:default_requires()
|
||||
Requires = case lists:keyfind(requires, 1, Opts) of
|
||||
{ requires, List } -> ordsets:from_list(List);
|
||||
false -> Scope#elixir_scope.requires
|
||||
end,
|
||||
|
||||
case lists:keyfind(functions, 1, Opts) of
|
||||
{ functions, Functions } -> Functions;
|
||||
false -> Functions = elixir_dispatch:default_functions()
|
||||
Functions = case lists:keyfind(functions, 1, Opts) of
|
||||
{ functions, FunctionsOpt } -> FunctionsOpt;
|
||||
false -> Scope#elixir_scope.functions
|
||||
end,
|
||||
|
||||
case lists:keyfind(macros, 1, Opts) of
|
||||
{ macros, Macros } -> Macros;
|
||||
false -> Macros = elixir_dispatch:default_macros()
|
||||
Macros = case lists:keyfind(macros, 1, Opts) of
|
||||
{ macros, MacrosOpt } -> MacrosOpt;
|
||||
false -> Scope#elixir_scope.macros
|
||||
end,
|
||||
|
||||
#elixir_scope{
|
||||
Scope#elixir_scope{
|
||||
file=File, local=Local,
|
||||
macros=Macros, functions=Functions,
|
||||
requires=Requires, aliases=Aliases }.
|
||||
|
||||
@@ -1,8 +1,23 @@
|
||||
-module(elixir_aliases).
|
||||
-export([nesting_alias/2, last/1, concat/1, safe_concat/1,
|
||||
format_error/1, ensure_loaded/3, expand/3]).
|
||||
format_error/1, ensure_loaded/3, ensure_loaded/4, expand/3, store/4]).
|
||||
-include("elixir.hrl").
|
||||
-compile({parse_transform, elixir_transform}).
|
||||
|
||||
%% Store an alias in the given scope
|
||||
store(_Meta, New, New, S) -> S;
|
||||
store(Meta, New, Old, S) ->
|
||||
SA = S#elixir_scope{
|
||||
aliases=orddict:store(New, Old, S#elixir_scope.aliases)
|
||||
},
|
||||
|
||||
case lists:keymember(context, 1, Meta) of
|
||||
true ->
|
||||
SA#elixir_scope{
|
||||
macro_aliases=orddict:store(New, Old, S#elixir_scope.macro_aliases)
|
||||
};
|
||||
false ->
|
||||
SA
|
||||
end.
|
||||
|
||||
%% Expand an alias. It returns an atom (meaning that there
|
||||
%% was an expansion) or a list of atoms.
|
||||
@@ -41,7 +56,7 @@ expand({ '__aliases__', _Meta, List }, _Aliases) ->
|
||||
List.
|
||||
|
||||
expand_one(H, Aliases) ->
|
||||
Lookup = list_to_atom("Elixir-" ++ atom_to_list(H)),
|
||||
Lookup = list_to_atom("Elixir." ++ atom_to_list(H)),
|
||||
case lookup(Lookup, Aliases) of
|
||||
Lookup -> false;
|
||||
Else -> Else
|
||||
@@ -49,28 +64,31 @@ expand_one(H, Aliases) ->
|
||||
|
||||
%% Ensure a module is loaded before its usage.
|
||||
|
||||
ensure_loaded(_Line, 'Elixir.Kernel', _S) ->
|
||||
ensure_loaded(Line, Ref, S) ->
|
||||
ensure_loaded(Line, S#elixir_scope.file, Ref, S#elixir_scope.context_modules).
|
||||
|
||||
ensure_loaded(_Line, _File, 'Elixir.Kernel', _FileModules) ->
|
||||
ok;
|
||||
|
||||
ensure_loaded(Line, Ref, S) ->
|
||||
ensure_loaded(Line, File, Ref, FileModules) ->
|
||||
try
|
||||
Ref:module_info(compile)
|
||||
catch
|
||||
error:undef ->
|
||||
Kind = case lists:member(Ref, S#elixir_scope.scheduled) of
|
||||
Kind = case lists:member(Ref, FileModules) of
|
||||
true -> scheduled_module;
|
||||
false -> unloaded_module
|
||||
end,
|
||||
elixir_errors:form_error(Line, S#elixir_scope.file, ?MODULE, { Kind, Ref })
|
||||
elixir_errors:form_error(Line, File, ?MODULE, { Kind, Ref })
|
||||
end.
|
||||
|
||||
%% Receives an atom and returns the last bit as an alias.
|
||||
|
||||
last(Atom) ->
|
||||
Last = last(lists:reverse(atom_to_list(Atom)), []),
|
||||
list_to_atom("Elixir-" ++ Last).
|
||||
list_to_atom("Elixir." ++ Last).
|
||||
|
||||
last([$-|_], Acc) -> Acc;
|
||||
last([$.|_], Acc) -> Acc;
|
||||
last([H|T], Acc) -> last(T, [H|Acc]);
|
||||
last([], Acc) -> Acc.
|
||||
|
||||
@@ -99,12 +117,12 @@ nesting_alias(Prefix, Full) ->
|
||||
do_nesting([X|PreTail], [X|Tail], Acc) ->
|
||||
do_nesting(PreTail, Tail, [X|Acc]);
|
||||
do_nesting([], [H|_], Acc) ->
|
||||
{ list_to_atom("Elixir-" ++ H), concat(lists:reverse([H|Acc])) };
|
||||
{ list_to_atom("Elixir." ++ H), concat(lists:reverse([H|Acc])) };
|
||||
do_nesting(_, _, _Acc) ->
|
||||
false.
|
||||
|
||||
list_nesting(Atom) ->
|
||||
case string:tokens(atom_to_list(Atom), "-") of
|
||||
case string:tokens(atom_to_list(Atom), ".") of
|
||||
["Elixir"|T] -> T;
|
||||
_ -> []
|
||||
end.
|
||||
@@ -124,15 +142,9 @@ do_concat(Args) ->
|
||||
|
||||
to_partial(Arg) when is_binary(Arg) -> to_partial(binary_to_list(Arg));
|
||||
to_partial(Arg) when is_atom(Arg) -> to_partial(atom_to_list(Arg));
|
||||
to_partial("Elixir-" ++ Arg) -> dot_to_dash([$-|Arg]);
|
||||
to_partial([$-|_] = Arg) -> dot_to_dash(Arg);
|
||||
to_partial(Arg) when is_list(Arg) -> [$-|dot_to_dash(Arg)].
|
||||
|
||||
dot_to_dash(List) ->
|
||||
[case X of
|
||||
$. -> $-;
|
||||
_ -> X
|
||||
end || X <- List].
|
||||
to_partial("Elixir." ++ Arg) -> [$.|Arg];
|
||||
to_partial([$.|_] = Arg) -> Arg;
|
||||
to_partial(Arg) when is_list(Arg) -> [$.|Arg].
|
||||
|
||||
%% Lookup an alias in the current scope.
|
||||
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
-export([
|
||||
assigns/3, assigns_block/5, assigns_block/6, extract_last_guards/1,
|
||||
get_pairs/4, get_pairs/5, match/3, extract_args/1, extract_guards/1]).
|
||||
-import(elixir_scope, [umergec/2]).
|
||||
-include("elixir.hrl").
|
||||
|
||||
%% Get pairs from a clause.
|
||||
@@ -105,7 +104,7 @@ do_match(Meta, DecoupledClauses, S) ->
|
||||
% and storing variables defined inside each clause.
|
||||
Transformer = fun(X, {Acc, CV}) ->
|
||||
{ TX, TAcc } = each_clause(Meta, X, Acc),
|
||||
{ TX, { umergec(S, TAcc), [TAcc#elixir_scope.clause_vars|CV] } }
|
||||
{ TX, { merge_clauses_scope(S, TAcc), [TAcc#elixir_scope.clause_vars|CV] } }
|
||||
end,
|
||||
|
||||
{ TClauses, { TS, ReverseCV } } = lists:mapfoldl(Transformer, {S, []}, DecoupledClauses),
|
||||
@@ -245,4 +244,15 @@ generate_match(Line, LeftVars, RightVars) ->
|
||||
{ match, Line, { tuple, Line, LeftVars }, { tuple, Line, RightVars } }.
|
||||
|
||||
listify(Expr) when not is_list(Expr) -> [Expr];
|
||||
listify(Expr) -> Expr.
|
||||
listify(Expr) -> Expr.
|
||||
|
||||
%% We don't use umergec because imports, aliases and
|
||||
%% what not are not passed from one clause to the other.
|
||||
merge_clauses_scope(S1, S2) ->
|
||||
S1#elixir_scope{
|
||||
counter=S2#elixir_scope.counter,
|
||||
extra_guards=S2#elixir_scope.extra_guards,
|
||||
super=S1#elixir_scope.super orelse S2#elixir_scope.super,
|
||||
caller=S1#elixir_scope.caller orelse S2#elixir_scope.caller,
|
||||
name_args=S1#elixir_scope.name_args orelse S2#elixir_scope.name_args
|
||||
}.
|
||||
@@ -2,7 +2,6 @@
|
||||
-export([get_opts/0, get_opt/1, get_opt/2, string/2, file/1, file_to_path/2]).
|
||||
-export([core/0, module/3, eval_forms/4, format_error/1]).
|
||||
-include("elixir.hrl").
|
||||
-compile({parse_transform, elixir_transform}).
|
||||
|
||||
%% Public API
|
||||
|
||||
|
||||
+136
-106
@@ -1,36 +1,56 @@
|
||||
% Holds the logic responsible for functions definition (def(p) and defmacro(p)).
|
||||
-module(elixir_def).
|
||||
-export([table/1,
|
||||
clauses_table/1,
|
||||
build_table/1,
|
||||
delete_table/1,
|
||||
reset_last/1,
|
||||
wrap_definition/5,
|
||||
wrap_definition/7,
|
||||
store_definition/6,
|
||||
store_definition/8,
|
||||
store_each/7,
|
||||
lookup_definition/2,
|
||||
delete_definition/2,
|
||||
wrap_definition/6,
|
||||
wrap_definition/8,
|
||||
store_definition/7,
|
||||
store_definition/9,
|
||||
store_each/8,
|
||||
unwrap_stored_definitions/2,
|
||||
format_error/1]).
|
||||
-include("elixir.hrl").
|
||||
-compile({parse_transform, elixir_transform}).
|
||||
|
||||
%% Table management functions. Called internally.
|
||||
|
||||
table(Module) -> ?atom_concat([f, Module]).
|
||||
clauses_table(Module) -> ?atom_concat([c, Module]).
|
||||
|
||||
build_table(Module) ->
|
||||
FunctionTable = table(Module),
|
||||
ets:new(FunctionTable, [set, named_table, public]),
|
||||
FunctionsTable = table(Module),
|
||||
ClausesTable = clauses_table(Module),
|
||||
ets:new(FunctionsTable, [set, named_table, public]),
|
||||
ets:new(ClausesTable, [bag, named_table, public]),
|
||||
reset_last(Module),
|
||||
FunctionTable.
|
||||
{ FunctionsTable, ClausesTable }.
|
||||
|
||||
delete_table(Module) ->
|
||||
ets:delete(table(Module)).
|
||||
ets:delete(table(Module)),
|
||||
ets:delete(clauses_table(Module)).
|
||||
|
||||
%% Reset the last item. Useful when evaling code.
|
||||
reset_last(Module) ->
|
||||
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)] };
|
||||
_ ->
|
||||
false
|
||||
end.
|
||||
|
||||
delete_definition(Module, Tuple) ->
|
||||
ets:delete(table(Module), Tuple),
|
||||
ets:delete(clauses_table(Module), Tuple).
|
||||
|
||||
%% Wraps the function into a call to store_definition once the function
|
||||
%% definition is read. The function is compiled into a meta tree to ensure
|
||||
%% we will receive the full function.
|
||||
@@ -47,28 +67,30 @@ reset_last(Module) ->
|
||||
%% If we just analyzed the compiled structure (i.e. the function availables
|
||||
%% before evaluating the function body), we would see both definitions.
|
||||
|
||||
wrap_definition(Kind, Meta, Call, Expr, S) ->
|
||||
do_wrap_definition(Kind, Meta, [
|
||||
wrap_definition(Kind, Meta, Call, Expr, CheckClauses, S) ->
|
||||
do_wrap_definition(Kind, Meta, CheckClauses, [
|
||||
Call, Expr, elixir_scope:serialize(S)
|
||||
]).
|
||||
|
||||
wrap_definition(Kind, Meta, Name, Args, Guards, Expr, S) ->
|
||||
do_wrap_definition(Kind, Meta, [
|
||||
wrap_definition(Kind, Meta, Name, Args, Guards, Expr, CheckClauses, S) ->
|
||||
do_wrap_definition(Kind, Meta, CheckClauses, [
|
||||
Name, Args, Guards, Expr, elixir_scope:serialize(S)
|
||||
]).
|
||||
|
||||
do_wrap_definition(Kind, Meta, Extra) ->
|
||||
do_wrap_definition(Kind, Meta, CheckClauses, Extra) ->
|
||||
Line = ?line(Meta),
|
||||
Invoke =
|
||||
[{atom, Line, Kind},
|
||||
{integer, Line, Line},
|
||||
{atom, Line, CheckClauses},
|
||||
{var, Line, '_@MODULE'}] ++ Extra,
|
||||
|
||||
?wrap_call(Line, ?MODULE, store_definition, Invoke).
|
||||
|
||||
% Invoked by the wrap definition with the function abstract tree.
|
||||
% Each function is then added to the function table.
|
||||
store_definition(Kind, Line, Module, Call, Body, RawS) ->
|
||||
|
||||
store_definition(Kind, Line, CheckClauses, Module, Call, Body, RawS) ->
|
||||
S = elixir_scope:deserialize(RawS),
|
||||
{ NameAndArgs, Guards } = elixir_clauses:extract_guards(Call),
|
||||
|
||||
@@ -81,47 +103,36 @@ store_definition(Kind, Line, Module, Call, Body, RawS) ->
|
||||
end,
|
||||
|
||||
assert_no_aliases_name(Line, Name, Args, S),
|
||||
store_definition(Kind, Line, Module, Name, Args, Guards, Body, RawS).
|
||||
store_definition(Kind, Line, CheckClauses, Module, Name, Args, Guards, Body, S).
|
||||
|
||||
store_definition(Kind, Line, nil, _Name, _Args, _Guards, _Body, RawS) ->
|
||||
S = elixir_scope:deserialize(RawS),
|
||||
store_definition(Kind, Line, _CheckClauses, nil, _Name, _Args, _Guards, _Body, #elixir_scope{} = S) ->
|
||||
elixir_errors:syntax_error(Line, S#elixir_scope.file, "cannot define function outside module, invalid scope for ~ts", [Kind]);
|
||||
|
||||
store_definition(Kind, Line, Module, Name, Args, Guards, Body, RawS) ->
|
||||
do_store_definition(Kind, Line, Module, Name, Args, Guards, Body, elixir_scope:deserialize(RawS)).
|
||||
|
||||
do_store_definition(Kind, Line, Module, Name, Args, Guards, Body, DS) ->
|
||||
store_definition(Kind, Line, CheckClauses, Module, Name, Args, Guards, Body, #elixir_scope{} = DS) ->
|
||||
Arity = length(Args),
|
||||
S = DS#elixir_scope{function={Name,Arity}, module=Module},
|
||||
Expr = def_body(Line, Body),
|
||||
|
||||
CO = elixir_compiler:get_opts(),
|
||||
Location = retrieve_file(Line, Module, S, CO),
|
||||
run_on_definition_callbacks(Kind, Line, Module, Name, Args, Guards, Body, S, CO),
|
||||
|
||||
{ Function, Defaults, TS } = translate_definition(Kind, Line, Name, Args, Guards, Expr, S),
|
||||
{ Function, Defaults, TS } = translate_definition(Kind, Line, Name, Args, Guards, Body, S),
|
||||
|
||||
File = TS#elixir_scope.file,
|
||||
Table = table(Module),
|
||||
CTable = clauses_table(Module),
|
||||
|
||||
%% Store function
|
||||
if
|
||||
(Body == nil) -> [];
|
||||
true ->
|
||||
compile_super(Module, TS),
|
||||
CheckClauses = S#elixir_scope.check_clauses,
|
||||
store_each(CheckClauses, Kind, File, Location,
|
||||
Table, length(Defaults), Function)
|
||||
end,
|
||||
compile_super(Module, TS),
|
||||
store_each(CheckClauses, Kind, File, Location,
|
||||
Table, CTable, length(Defaults), Function),
|
||||
|
||||
[store_each(false, Kind, File, Location, Table, 0,
|
||||
[store_each(false, Kind, File, Location, Table, CTable, 0,
|
||||
default_function_for(Kind, Name, Default)) || Default <- Defaults],
|
||||
|
||||
{ Name, Arity }.
|
||||
{ Name, Arity };
|
||||
|
||||
def_body(_Line, nil) -> nil;
|
||||
def_body(_Line, [{ do, Expr }]) -> Expr;
|
||||
def_body(Line, Else) -> { 'try', [{line,Line}], [Else] }.
|
||||
store_definition(Kind, Line, CheckClauses, Module, Name, Args, Guards, Body, RawS) ->
|
||||
store_definition(Kind, Line, CheckClauses, Module, Name, Args, Guards, Body, elixir_scope:deserialize(RawS)).
|
||||
|
||||
%% @on_definition
|
||||
|
||||
@@ -159,22 +170,31 @@ compile_super(_Module, _S) -> ok.
|
||||
%% Translate the given call and expression given
|
||||
%% and then store it in memory.
|
||||
|
||||
translate_definition(Kind, Line, Name, RawArgs, RawGuards, RawExpr, S) when is_integer(Line) ->
|
||||
translate_definition(Kind, Line, Name, RawArgs, RawGuards, RawBody, S) when is_integer(Line) ->
|
||||
Args = elixir_quote:linify(Line, RawArgs),
|
||||
Guards = elixir_quote:linify(Line, RawGuards),
|
||||
Expr = elixir_quote:linify(Line, RawExpr),
|
||||
Body = elixir_quote:linify(Line, RawBody),
|
||||
Arity = length(Args),
|
||||
IsMacro = is_macro(Kind),
|
||||
|
||||
%% Macros receive a special argument on invocation. Notice it does
|
||||
%% not affect the arity of the stored function, but the clause
|
||||
%% already contains it.
|
||||
ExtendedArgs = case IsMacro of
|
||||
ExtendedArgs = case is_macro(Kind) of
|
||||
true -> [{ '_@CALLER', [{line,Line}], nil }|Args];
|
||||
false -> Args
|
||||
end,
|
||||
|
||||
{ Unpacked, Defaults } = elixir_def_defaults:unpack(Kind, Name, ExtendedArgs, S),
|
||||
{ Clauses, TS } = translate_clause(Line, Kind, Unpacked, Guards, Body, S),
|
||||
|
||||
Function = { function, Line, Name, Arity, Clauses },
|
||||
{ Function, Defaults, TS }.
|
||||
|
||||
translate_clause(_Line, _Kind, _Unpacked, _Guards, nil, S) ->
|
||||
{ [], S };
|
||||
|
||||
translate_clause(Line, Kind, Unpacked, Guards, Body, S) ->
|
||||
Expr = expr_from_body(Line, Body),
|
||||
|
||||
{ TClause, TS } = elixir_clauses:assigns_block(Line,
|
||||
fun elixir_translator:translate/2, Unpacked, [Expr], Guards, S),
|
||||
@@ -188,7 +208,7 @@ translate_definition(Kind, Line, Name, RawArgs, RawGuards, RawExpr, S) when is_i
|
||||
end,
|
||||
|
||||
%% Set __CALLER__ if used
|
||||
FClause = case IsMacro andalso TS#elixir_scope.caller of
|
||||
FClause = case is_macro(Kind) andalso TS#elixir_scope.caller of
|
||||
true ->
|
||||
FBody = { 'match', Line,
|
||||
{ 'var', Line, '__CALLER__' },
|
||||
@@ -198,8 +218,10 @@ translate_definition(Kind, Line, Name, RawArgs, RawGuards, RawExpr, S) when is_i
|
||||
false -> NClause
|
||||
end,
|
||||
|
||||
Function = { function, Line, Name, Arity, [FClause] },
|
||||
{ Function, Defaults, TS }.
|
||||
{ [FClause], TS }.
|
||||
|
||||
expr_from_body(_Line, [{ do, Expr }]) -> Expr;
|
||||
expr_from_body(Line, Else) -> { 'try', [{line,Line}], [Else] }.
|
||||
|
||||
is_macro(defmacro) -> true;
|
||||
is_macro(defmacrop) -> true;
|
||||
@@ -210,58 +232,59 @@ is_macro(_) -> false.
|
||||
% and the body of all functions.
|
||||
unwrap_stored_definitions(File, Module) ->
|
||||
Table = table(Module),
|
||||
CTable = clauses_table(Module),
|
||||
ets:delete(Table, last),
|
||||
unwrap_stored_definition(ets:tab2list(Table), File, [], [], [], [], [], {[],[]}).
|
||||
unwrap_stored_definition(ets:tab2list(Table), CTable, File, [], [], [], [], [], []).
|
||||
|
||||
unwrap_stored_definition([Fun|T], File, Exports, Private, Def, Defmacro, Defmacrop, Functions) when element(2, Fun) == def ->
|
||||
Tuple = element(1, Fun),
|
||||
unwrap_stored_definition(
|
||||
T, File, [Tuple|Exports], Private, [Tuple|Def], Defmacro, Defmacrop,
|
||||
function_for_stored_definition(Fun, File, Functions)
|
||||
);
|
||||
unwrap_stored_definition([Fun|T], CTable, File, Exports, Private, Def, Defmacro, Functions, Tail) ->
|
||||
Tuple = element(1, Fun),
|
||||
Clauses = [Clause || { _, Clause } <- ets:lookup(CTable, Tuple)],
|
||||
|
||||
unwrap_stored_definition([Fun|T], File, Exports, Private, Def, Defmacro, Defmacrop, Functions) when element(2, Fun) == defmacro ->
|
||||
{ Name, Arity } = Tuple = element(1, Fun),
|
||||
{ NewFun, NewExports, NewPrivate, NewDef, NewDefmacro } =
|
||||
case Clauses of
|
||||
[] -> { false, Exports, Private, Def, Defmacro };
|
||||
_ -> unwrap_stored_definition(element(2, Fun), Tuple, Fun, Exports, Private, Def, Defmacro)
|
||||
end,
|
||||
|
||||
{ NewFunctions, NewTail } = case NewFun of
|
||||
false -> { Functions, Tail };
|
||||
_ -> function_for_stored_definition(NewFun, Clauses, File, Functions, Tail)
|
||||
end,
|
||||
|
||||
unwrap_stored_definition(T, CTable, File, NewExports, NewPrivate,
|
||||
NewDef, NewDefmacro, NewFunctions, NewTail);
|
||||
|
||||
unwrap_stored_definition([], _CTable, _File, Exports, Private, Def, Defmacro, Functions, Tail) ->
|
||||
{ Exports, Private, ordsets:from_list(Def),
|
||||
ordsets:from_list(Defmacro), lists:reverse(Tail ++ Functions) }.
|
||||
|
||||
unwrap_stored_definition(def, Tuple, Fun, Exports, Private, Def, Defmacro) ->
|
||||
{ Fun, [Tuple|Exports], Private, [Tuple|Def], Defmacro };
|
||||
|
||||
unwrap_stored_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] };
|
||||
|
||||
unwrap_stored_definition(
|
||||
T, File, [Macro|Exports], Private, Def, [Tuple|Defmacro], Defmacrop,
|
||||
function_for_stored_definition(setelement(1, Fun, Macro), File, Functions)
|
||||
);
|
||||
unwrap_stored_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 };
|
||||
|
||||
unwrap_stored_definition([Fun|T], File, Exports, Private, Def, Defmacro, Defmacrop, Functions) when element(2, Fun) == defp ->
|
||||
unwrap_stored_definition(
|
||||
T, File, Exports, [element(1, Fun)|Private], Def, Defmacro, Defmacrop,
|
||||
function_for_stored_definition(Fun, File, Functions)
|
||||
);
|
||||
|
||||
unwrap_stored_definition([Fun|T], File, Exports, Private, Def, Defmacro, Defmacrop, Functions) when element(2, Fun) == defmacrop ->
|
||||
Tuple = element(1, Fun),
|
||||
unwrap_stored_definition(
|
||||
T, File, Exports, [Tuple|Private], Def, Defmacro,
|
||||
[{ Tuple, element(3, Fun), element(5, Fun) }|Defmacrop], Functions
|
||||
);
|
||||
|
||||
unwrap_stored_definition([], _File, Exports, Private, Def, Defmacro, Defmacrop, {Functions,Tail}) ->
|
||||
{ Exports, Private, ordsets:from_list(Def), ordsets:from_list(Defmacro),
|
||||
ordsets:from_list(Defmacrop), lists:reverse(Tail ++ Functions) }.
|
||||
unwrap_stored_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 }.
|
||||
|
||||
%% Helpers
|
||||
|
||||
function_for_stored_definition({{Name, Arity}, _, Line, _, _, { File, _ }, _, Clauses}, File, {Functions,Tail}) ->
|
||||
{
|
||||
[{ function, Line, Name, Arity, lists:reverse(Clauses) }|Functions],
|
||||
Tail
|
||||
};
|
||||
function_for_stored_definition({{Name,Arity}, _, Line, _, _, {File, _}, _}, Clauses, File, Functions, Tail) ->
|
||||
{ [{ function, Line, Name, Arity, Clauses }|Functions], Tail };
|
||||
|
||||
function_for_stored_definition({{Name, Arity}, _, Line, _, _, Location, _, Clauses}, _File, {Functions,Tail}) ->
|
||||
{
|
||||
Functions,
|
||||
[
|
||||
{ function, Line, Name, Arity, lists:reverse(Clauses) },
|
||||
{ attribute, Line, file, Location } | Tail
|
||||
]
|
||||
}.
|
||||
function_for_stored_definition({{Name,Arity}, _, Line, _, _, Location, _}, Clauses, _File, Functions, Tail) ->
|
||||
{ Functions, [
|
||||
{ function, Line, Name, Arity, Clauses },
|
||||
{ attribute, Line, file, Location } | Tail
|
||||
] }.
|
||||
|
||||
default_function_for(Kind, Name, { clause, Line, Args, _Guards, _Exprs } = Clause)
|
||||
when Kind == defmacro; Kind == defmacrop ->
|
||||
@@ -274,22 +297,24 @@ default_function_for(_, Name, { clause, Line, Args, _Guards, _Exprs } = Clause)
|
||||
%% 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, Defaults, {function, Line, Name, Arity, Clauses}) ->
|
||||
case ets:lookup(Table, {Name, Arity}) of
|
||||
[{{Name, Arity}, StoredKind, _, _, StoredCheck, StoredLocation, StoredDefaults, StoredClauses}] ->
|
||||
store_each(Check, Kind, File, Location, Table, CTable, Defaults, {function, Line, Name, Arity, Clauses}) ->
|
||||
Tuple = { Name, Arity },
|
||||
case ets:lookup(Table, Tuple) of
|
||||
[{ Tuple, StoredKind, StoredLine, _, StoredCheck, StoredLocation, StoredDefaults }] ->
|
||||
FinalLine = StoredLine,
|
||||
FinalLocation = StoredLocation,
|
||||
FinalDefaults = Defaults + StoredDefaults,
|
||||
FinalClauses = Clauses ++ StoredClauses,
|
||||
FinalDefaults = max(Defaults, StoredDefaults),
|
||||
check_valid_kind(Line, File, Name, Arity, Kind, StoredKind),
|
||||
check_valid_defaults(Line, File, Name, Arity, FinalDefaults),
|
||||
check_valid_defaults(Line, File, Name, Arity, Defaults, StoredDefaults),
|
||||
(Check and StoredCheck) andalso check_valid_clause(Line, File, Name, Arity, Table);
|
||||
[] ->
|
||||
FinalLine = Line,
|
||||
FinalLocation = Location,
|
||||
FinalDefaults = Defaults,
|
||||
FinalClauses = Clauses,
|
||||
Check andalso ets:insert(Table, { last, { Name, Arity } })
|
||||
FinalDefaults = Defaults
|
||||
end,
|
||||
ets:insert(Table, {{Name, Arity}, Kind, Line, File, Check, FinalLocation, FinalDefaults, FinalClauses}).
|
||||
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
|
||||
|
||||
@@ -302,14 +327,16 @@ check_valid_clause(Line, File, Name, Arity, Table) ->
|
||||
case ets:lookup_element(Table, last, 2) of
|
||||
{Name,Arity} -> [];
|
||||
[] -> [];
|
||||
{ElseName, ElseArity} ->
|
||||
_ ->
|
||||
elixir_errors:handle_file_warning(File, { Line, ?MODULE,
|
||||
{ changed_clause, { { Name, Arity }, { ElseName, ElseArity } } } })
|
||||
{ override_function, { Name, Arity } } })
|
||||
end.
|
||||
|
||||
check_valid_defaults(_Line, _File, _Name, _Arity, 0) -> [];
|
||||
check_valid_defaults(Line, File, Name, Arity, _) ->
|
||||
elixir_errors:handle_file_warning(File, { Line, ?MODULE, { clauses_with_docs, { Name, Arity } } }).
|
||||
check_valid_defaults(_Line, _File, _Name, _Arity, 0, _) -> [];
|
||||
check_valid_defaults(Line, File, Name, Arity, _, 0) ->
|
||||
elixir_errors:handle_file_warning(File, { Line, ?MODULE, { out_of_order_defaults, { Name, Arity } } });
|
||||
check_valid_defaults(Line, File, Name, Arity, _, _) ->
|
||||
elixir_errors:handle_file_warning(File, { Line, ?MODULE, { clauses_with_defaults, { Name, Arity } } }).
|
||||
|
||||
assert_no_aliases_name(Line, '__aliases__', [Atom], #elixir_scope{file=File}) when is_atom(Atom) ->
|
||||
Message = "function names should start with lowercase characters or underscore, invalid name ~ts",
|
||||
@@ -320,11 +347,14 @@ assert_no_aliases_name(_Meta, _Aliases, _Args, _S) ->
|
||||
|
||||
%% Format errors
|
||||
|
||||
format_error({clauses_with_docs,{Name,Arity}}) ->
|
||||
format_error({clauses_with_defaults,{Name,Arity}}) ->
|
||||
io_lib:format("function ~ts/~B has default values and multiple clauses, use a separate clause for declaring defaults", [Name, Arity]);
|
||||
|
||||
format_error({changed_clause,{{Name,Arity},{ElseName,ElseArity}}}) ->
|
||||
io_lib:format("function ~ts/~B does not match previous clause ~ts/~B", [Name, Arity, ElseName, ElseArity]);
|
||||
format_error({out_of_order_defaults,{Name,Arity}}) ->
|
||||
io_lib:format("clause with defaults should be the first clause in function ~ts/~B", [Name, Arity]);
|
||||
|
||||
format_error({override_function,{Name,Arity}}) ->
|
||||
io_lib:format("trying to override previously defined function ~ts/~B", [Name, Arity]);
|
||||
|
||||
format_error({changed_kind,{Name,Arity,Previous,Current}}) ->
|
||||
io_lib:format("~ts ~ts/~B already defined as ~ts", [Current, Name, Arity, Previous]).
|
||||
@@ -4,7 +4,7 @@
|
||||
macro_for/3,
|
||||
function_for/3,
|
||||
format_error/1,
|
||||
check_unused_local_macros/3
|
||||
check_unused_local/3
|
||||
]).
|
||||
-include("elixir.hrl").
|
||||
|
||||
@@ -12,8 +12,10 @@
|
||||
macro_for(_Tuple, _All, nil) -> false;
|
||||
|
||||
macro_for(Tuple, All, Module) ->
|
||||
try ets:lookup(elixir_def:table(Module), Tuple) of
|
||||
[{Tuple, Kind, Line, _, _, _, _, Clauses}] when Kind == defmacro; All, Kind == defmacrop ->
|
||||
try elixir_def:lookup_definition(Module, Tuple) of
|
||||
{ { Tuple, Kind, Line, _, _, _, _ }, Clauses } when Kind == defmacro; All, Kind == defmacrop ->
|
||||
%% This function is only called if S#elixir_scope.function /= Tuple
|
||||
elixir_import:record(Tuple, Module, Module),
|
||||
get_function(Line, Module, Clauses);
|
||||
_ ->
|
||||
false
|
||||
@@ -24,8 +26,11 @@ macro_for(Tuple, All, Module) ->
|
||||
%% Used on runtime by rewritten clauses, raises an error if function is not found
|
||||
function_for(Module, Name, Arity) ->
|
||||
Tuple = { Name, Arity },
|
||||
case ets:lookup(elixir_def:table(Module), Tuple) of
|
||||
[{Tuple, _, Line, _, _, _, _, Clauses}] ->
|
||||
case elixir_def:lookup_definition(Module, Tuple) of
|
||||
{ { Tuple, _, Line, _, _, _, _ }, Clauses } ->
|
||||
%% There is no need to record such calls
|
||||
%% since they come from funtions that were
|
||||
%% already analyzed at compilation time.
|
||||
get_function(Line, Module, Clauses);
|
||||
_ ->
|
||||
[_|T] = erlang:get_stacktrace(),
|
||||
@@ -36,11 +41,10 @@ function_for(Module, Name, Arity) ->
|
||||
|
||||
get_function(Line, Module, Clauses) ->
|
||||
RewrittenClauses = [rewrite_clause(Clause, Module) || Clause <- Clauses],
|
||||
Fun = { 'fun', Line, {clauses, lists:reverse(RewrittenClauses)} },
|
||||
Fun = { 'fun', Line, {clauses, RewrittenClauses } },
|
||||
{ value, Result, _Binding } = erl_eval:exprs([Fun], []),
|
||||
Result.
|
||||
|
||||
%% TODO: Consider caching functions in a table for performance.
|
||||
rewrite_clause({ call, Line, { atom, Line, RawName }, Args }, Module) ->
|
||||
Remote = { remote, Line,
|
||||
{ atom, Line, ?MODULE },
|
||||
@@ -69,10 +73,49 @@ rewrite_clause(Else, _) -> Else.
|
||||
|
||||
%% Error handling
|
||||
|
||||
check_unused_local_macros(File, Recorded, Defmacrop) ->
|
||||
[elixir_errors:handle_file_warning(File,
|
||||
{ Line, ?MODULE, { unused_macro, Fun } }) || { Fun, Line, Check } <- Defmacrop,
|
||||
Check, not lists:member(Fun, Recorded)].
|
||||
check_unused_local(File, Recorded, Private) ->
|
||||
[check_unused_local(Fun, Kind, Line, File, Defaults, Recorded) ||
|
||||
{ Fun, Kind, Line, true, Defaults } <- Private].
|
||||
|
||||
format_error({unused_macro,{Name, Arity}}) ->
|
||||
io_lib:format("macro ~ts/~B is unused", [Name, Arity]).
|
||||
check_unused_local(Fun, Kind, Line, File, 0, Recorded) ->
|
||||
not(lists:member(Fun, Recorded)) andalso
|
||||
elixir_errors:handle_file_warning(File, { Line, ?MODULE, { unused_def, Kind, Fun } });
|
||||
|
||||
check_unused_local({ _, Arity } = Fun, Kind, Line, File, Defaults, Recorded) when Defaults > 0 ->
|
||||
Min = Arity - Defaults,
|
||||
Max = Arity,
|
||||
|
||||
Invoked = [A || { _, A } <- Recorded, A >= Min, A =< Max],
|
||||
|
||||
case Invoked of
|
||||
[] ->
|
||||
elixir_errors:handle_file_warning(File, { Line, ?MODULE, { unused_def, Kind, Fun } });
|
||||
_ ->
|
||||
UnusedArgs = lists:min(Invoked) - Min,
|
||||
if
|
||||
UnusedArgs == 0 ->
|
||||
ok;
|
||||
UnusedArgs == Defaults ->
|
||||
elixir_errors:handle_file_warning(File, { Line, ?MODULE, { unused_args, Fun } });
|
||||
true ->
|
||||
elixir_errors:handle_file_warning(File, { Line, ?MODULE, { unused_args, Fun, UnusedArgs } })
|
||||
end
|
||||
end;
|
||||
|
||||
check_unused_local(_Fun, _Kind, _Line, _File, _Defaults, _Recorded) ->
|
||||
ok.
|
||||
|
||||
format_error({unused_def,defp,{Name, Arity}}) ->
|
||||
io_lib:format("function ~ts/~B is unused", [Name, Arity]);
|
||||
|
||||
format_error({unused_args,{Name, Arity}}) ->
|
||||
io_lib:format("default arguments in ~ts/~B are never used", [Name, Arity]);
|
||||
|
||||
format_error({unused_args,{Name, Arity},1}) ->
|
||||
io_lib:format("the first default argument in ~ts/~B is never used", [Name, Arity]);
|
||||
|
||||
format_error({unused_args,{Name, Arity},Count}) ->
|
||||
io_lib:format("the first ~B default arguments in ~ts/~B are never used", [Count, Name, Arity]);
|
||||
|
||||
format_error({unused_def,defmacrop,{Name, Arity}}) ->
|
||||
io_lib:format("macro ~ts/~B is unused", [Name, Arity]).
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
-export([store_pending/1, is_defined/2, ensure_defined/4,
|
||||
assign_args/3, retrieve_args/3, name/2, store/3, format_error/1]).
|
||||
-include("elixir.hrl").
|
||||
-compile({parse_transform, elixir_transform}).
|
||||
|
||||
overridable(Module) ->
|
||||
ets:lookup_element(elixir_module:data_table(Module), '__overridable', 2).
|
||||
@@ -65,7 +64,7 @@ store(Module, Function, GenerateName) ->
|
||||
{ _Count, _Clause, true } -> ok;
|
||||
{ Count, Clause, false } ->
|
||||
overridable(Module, orddict:store(Function, { Count, Clause, true }, Overridable)),
|
||||
{ { Name, Arity }, Kind, Line, File, _Check, Location, Defaults, Clauses } = Clause,
|
||||
{ { { Name, Arity }, Kind, Line, File, _Check, Location, Defaults }, Clauses } = Clause,
|
||||
|
||||
{ FinalKind, FinalName } = case GenerateName of
|
||||
true -> { defp, name(Module, Function, Overridable) };
|
||||
@@ -74,7 +73,7 @@ store(Module, Function, GenerateName) ->
|
||||
|
||||
Def = { function, Line, FinalName, Arity, Clauses },
|
||||
elixir_def:store_each(false, FinalKind, File, Location,
|
||||
elixir_def:table(Module), Defaults, Def)
|
||||
elixir_def:table(Module), elixir_def:clauses_table(Module), Defaults, Def)
|
||||
end.
|
||||
|
||||
%% Store pending declarations that were not manually made concrete.
|
||||
|
||||
@@ -8,7 +8,6 @@
|
||||
expand_import/6, expand_require/6, find_import/4,
|
||||
format_error/1, in_erlang_functions/0, in_erlang_macros/0]).
|
||||
-include("elixir.hrl").
|
||||
-compile({parse_transform, elixir_transform}).
|
||||
|
||||
-import(ordsets, [is_element/2]).
|
||||
-define(BUILTIN, 'Elixir.Kernel').
|
||||
@@ -35,13 +34,18 @@ import_function(Meta, Name, Arity, S) ->
|
||||
Tuple = { Name, Arity },
|
||||
case find_dispatch(Meta, Tuple, S) of
|
||||
{ function, Receiver } ->
|
||||
elixir_import:record(import, Tuple, Receiver, S#elixir_scope.module),
|
||||
elixir_import:record(Tuple, Receiver, S#elixir_scope.module),
|
||||
remote_function(Meta, Receiver, Name, Arity, S);
|
||||
{ macro, _Receiver } ->
|
||||
false;
|
||||
{ import, Receiver } ->
|
||||
require_function(Meta, Receiver, Name, Arity, S);
|
||||
nomatch ->
|
||||
Module = S#elixir_scope.module,
|
||||
case S#elixir_scope.function of
|
||||
Tuple -> ok;
|
||||
_ -> elixir_import:record(Tuple, Module, Module)
|
||||
end,
|
||||
{ { 'fun', ?line(Meta), { function, Name, Arity } }, S }
|
||||
end.
|
||||
|
||||
@@ -62,7 +66,7 @@ dispatch_import(Meta, Name, Args, S, Callback) ->
|
||||
|
||||
case find_dispatch(Meta, Tuple, S) of
|
||||
{ function, Receiver } ->
|
||||
elixir_import:record(import, Tuple, Receiver, Module),
|
||||
elixir_import:record(Tuple, Receiver, Module),
|
||||
Endpoint = case (Receiver == ?BUILTIN) andalso is_element(Tuple, in_erlang_functions()) of
|
||||
true -> erlang;
|
||||
false -> Receiver
|
||||
@@ -75,7 +79,7 @@ dispatch_import(Meta, Name, Args, S, Callback) ->
|
||||
{ error, noexpansion } ->
|
||||
Callback();
|
||||
{ error, internal } ->
|
||||
elixir_import:record(import, Tuple, ?BUILTIN, Module),
|
||||
elixir_import:record(Tuple, ?BUILTIN, Module),
|
||||
elixir_macros:translate({ Name, Meta, Args }, S);
|
||||
{ ok, _Receiver, Tree } ->
|
||||
translate_expansion(Meta, Tree, S)
|
||||
@@ -114,11 +118,11 @@ do_expand_import(Meta, { Name, Arity } = Tuple, Args, Module, S, Result) ->
|
||||
case is_element(Tuple, in_erlang_macros()) of
|
||||
true -> { error, internal };
|
||||
false ->
|
||||
elixir_import:record(import, Tuple, ?BUILTIN, Module),
|
||||
elixir_import:record(Tuple, ?BUILTIN, Module),
|
||||
{ ok, ?BUILTIN, expand_macro_named(Meta, ?BUILTIN, Name, Arity, Args, Module, S) }
|
||||
end;
|
||||
{ macro, Receiver } ->
|
||||
elixir_import:record(import, Tuple, Receiver, Module),
|
||||
elixir_import:record(Tuple, Receiver, Module),
|
||||
{ ok, Receiver, expand_macro_named(Meta, Receiver, Name, Arity, Args, Module, S) };
|
||||
{ import, Receiver } ->
|
||||
expand_require(Meta, Receiver, Tuple, Args, Module, S);
|
||||
@@ -128,7 +132,6 @@ do_expand_import(Meta, { Name, Arity } = Tuple, Args, Module, S, Result) ->
|
||||
case Fun of
|
||||
false -> { error, noexpansion };
|
||||
_ ->
|
||||
elixir_import:record(import, Tuple, Module, Module),
|
||||
{ ok, Module, expand_macro_fun(Meta, Fun, Module, Name, Args, Module, S) }
|
||||
end
|
||||
end.
|
||||
@@ -154,7 +157,6 @@ expand_require(Meta, Receiver, { Name, Arity } = Tuple, Args, Module, S) ->
|
||||
false -> { error, noexpansion }
|
||||
end;
|
||||
_ ->
|
||||
elixir_import:record(import, Tuple, Receiver, Module),
|
||||
{ ok, Receiver, expand_macro_fun(Meta, Fun, Receiver, Name, Args, Module, S) }
|
||||
end.
|
||||
|
||||
@@ -187,27 +189,10 @@ expand_macro_named(Meta, Receiver, Name, Arity, Args, Module, S) ->
|
||||
expand_macro_fun(Meta, Fun, Receiver, Name, Args, Module, S).
|
||||
|
||||
translate_expansion(Meta, Tree, S) ->
|
||||
{ TR, TS } = elixir_translator:translate_each(
|
||||
elixir_translator:translate_each(
|
||||
elixir_quote:linify(?line(Meta), Tree),
|
||||
S#elixir_scope{
|
||||
check_clauses=false,
|
||||
macro_functions=[],
|
||||
macro_macros=[],
|
||||
macro_aliases=[]
|
||||
}
|
||||
),
|
||||
{ TR, TS#elixir_scope{
|
||||
check_clauses=S#elixir_scope.check_clauses,
|
||||
macro_functions=merge_imports(S#elixir_scope.macro_functions, TS#elixir_scope.macro_functions),
|
||||
macro_macros=merge_imports(S#elixir_scope.macro_macros, TS#elixir_scope.macro_macros),
|
||||
macro_aliases=merge_aliases(S#elixir_scope.macro_aliases, TS#elixir_scope.macro_aliases)
|
||||
} }.
|
||||
|
||||
merge_aliases(A1, A2) ->
|
||||
orddict:merge(fun(_K,_V1,V2) -> V2 end, A1, A2).
|
||||
|
||||
merge_imports(A1, A2) ->
|
||||
A2 ++ lists:foldl(fun({ Key, _ }, Acc) -> lists:keydelete(Key, 1, Acc) end, A1, A2).
|
||||
S
|
||||
).
|
||||
|
||||
%% Helpers
|
||||
|
||||
@@ -246,15 +231,25 @@ find_dispatch(Tuple, List) ->
|
||||
is_import(Meta, Tuple, S) ->
|
||||
case lists:keyfind(import, 1, Meta) of
|
||||
{ import, _ } = Import ->
|
||||
not_an_import(Tuple, S#elixir_scope.macro_functions)
|
||||
andalso not_an_import(Tuple, S#elixir_scope.macro_macros)
|
||||
andalso Import;
|
||||
case lists:keyfind(context, 1, Meta) of
|
||||
{ context, Context } ->
|
||||
not_an_import(Tuple, Context, S#elixir_scope.macro_functions)
|
||||
andalso not_an_import(Tuple, Context, S#elixir_scope.macro_macros)
|
||||
andalso Import;
|
||||
false ->
|
||||
false
|
||||
end;
|
||||
false ->
|
||||
false
|
||||
end.
|
||||
|
||||
not_an_import(Tuple, Pairs) ->
|
||||
not lists:any(fun({ _, List }) -> lists:member(Tuple, List) end, Pairs).
|
||||
not_an_import(Tuple, Context, Dict) ->
|
||||
case orddict:find(Context, Dict) of
|
||||
{ ok, Pairs } ->
|
||||
not lists:any(fun({ _, List }) -> lists:member(Tuple, List) end, Pairs);
|
||||
error ->
|
||||
true
|
||||
end.
|
||||
|
||||
munge_stacktrace(Info, [{ _, _, [S|_], _ }|_], S) ->
|
||||
[Info];
|
||||
@@ -271,7 +266,7 @@ munge_stacktrace(_, [], _) ->
|
||||
%% ERROR HANDLING
|
||||
|
||||
format_error({ unrequired_module, { Receiver, Name, Arity, Required }}) ->
|
||||
String = string:join([elixir_errors:inspect(R) || R <- Required], ", "),
|
||||
String = 'Elixir.Enum':join([elixir_errors:inspect(R) || R <- Required], ", "),
|
||||
io_lib:format("tried to invoke macro ~ts.~ts/~B but module was not required. Required: ~ts",
|
||||
[elixir_errors:inspect(Receiver), Name, Arity, String]);
|
||||
|
||||
@@ -337,6 +332,10 @@ in_erlang_functions() ->
|
||||
{ binary_part, 3 },
|
||||
{ binary_to_atom, 2 },
|
||||
{ binary_to_existing_atom, 2 },
|
||||
{ binary_to_float, 1 },
|
||||
{ binary_to_float, 2 },
|
||||
{ binary_to_integer, 1 },
|
||||
{ binary_to_integer, 2 },
|
||||
{ binary_to_list, 1 },
|
||||
{ binary_to_list, 3 },
|
||||
{ binary_to_term, 1 },
|
||||
@@ -347,8 +346,11 @@ in_erlang_functions() ->
|
||||
% { date, 0 },
|
||||
{ exit, 1 },
|
||||
{ float, 1 },
|
||||
{ float_to_binary, 1 },
|
||||
{ float_to_list, 1 },
|
||||
{ hd, 1 },
|
||||
{ integer_to_binary, 1 },
|
||||
{ integer_to_binary, 2 },
|
||||
{ integer_to_list, 1 },
|
||||
{ integer_to_list, 2 },
|
||||
{ iolist_size, 1 },
|
||||
@@ -443,7 +445,6 @@ in_erlang_macros() ->
|
||||
{defp,2},
|
||||
{defp,4},
|
||||
{function,1},
|
||||
{function,2},
|
||||
{function,3},
|
||||
{in,2},
|
||||
{'not',1},
|
||||
|
||||
@@ -9,20 +9,11 @@
|
||||
handle_file_warning/2, handle_file_warning/3, handle_file_error/2,
|
||||
deprecation/3, deprecation/4, file_format/3]).
|
||||
-include("elixir.hrl").
|
||||
-compile({parse_transform, elixir_transform}).
|
||||
|
||||
%% Handle inspecting for exceptions
|
||||
|
||||
inspect(Atom) when is_atom(Atom) ->
|
||||
case atom_to_list(Atom) of
|
||||
"Elixir-" ++ Rest -> [to_dot(R) || R <- Rest];
|
||||
Else -> Else
|
||||
end;
|
||||
|
||||
inspect(Other) -> Other.
|
||||
|
||||
to_dot($-) -> $.;
|
||||
to_dot(L) -> L.
|
||||
'Elixir.Binary.Inspect.Atom':inspect(Atom, []).
|
||||
|
||||
%% Raised during macros translation.
|
||||
|
||||
@@ -37,7 +28,7 @@ syntax_error(Meta, File, Message) when is_binary(Message) ->
|
||||
|
||||
syntax_error(Meta, File, Format, Args) ->
|
||||
Message = io_lib:format(Format, Args),
|
||||
raise(Meta, File, 'Elixir.SyntaxError', iolist_to_binary(Message)).
|
||||
raise(Meta, File, 'Elixir.SyntaxError', unicode:characters_to_binary(Message)).
|
||||
|
||||
%% Raised on tokenizing/parsing
|
||||
|
||||
@@ -61,7 +52,7 @@ parse_error(Meta, File, Error, Token) ->
|
||||
|
||||
BinToken = if
|
||||
Token == [] -> <<>>;
|
||||
true -> iolist_to_binary(Token)
|
||||
true -> unicode:characters_to_binary(Token)
|
||||
end,
|
||||
|
||||
Message = <<BinError / binary, BinToken / binary >>,
|
||||
@@ -94,6 +85,9 @@ 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 }) -> [];
|
||||
|
||||
%% We handle unused local warnings ourselves
|
||||
handle_file_warning(_, _File, { _Line, erl_lint, { unused_function, _ } }) -> [];
|
||||
|
||||
%% Rewrite
|
||||
handle_file_warning(_, File, {Line,erl_lint,{undefined_behaviour_func,{Fun,Arity},Module}}) ->
|
||||
Kind = protocol_or_behaviour(Module),
|
||||
|
||||
@@ -1,29 +1,46 @@
|
||||
%% Module responsible for handling imports and conflicts.
|
||||
%% Module responsible for handling imports and conflicts
|
||||
%% in between local functions and imports.
|
||||
%% For imports dispatch, please check elixir_dispatch.
|
||||
-module(elixir_import).
|
||||
-export([import/5, recorded_locals/1, format_error/1,
|
||||
ensure_no_import_conflict/4, ensure_no_local_conflict/4,
|
||||
build_table/1, delete_table/1, record/4]).
|
||||
ensure_all_imports_used/3,
|
||||
build_table/1, delete_table/1, record/3]).
|
||||
-include("elixir.hrl").
|
||||
|
||||
table(Module) -> ?atom_concat([i, Module]).
|
||||
|
||||
build_table(Module) ->
|
||||
ets:new(table(Module), [set, named_table, public]).
|
||||
ets:new(table(Module), [bag, named_table, public]).
|
||||
|
||||
delete_table(Module) ->
|
||||
ets:delete(table(Module)).
|
||||
|
||||
record(_Kind, _Tuple, _Receiver, nil) ->
|
||||
record(_Tuple, _Receiver, nil) ->
|
||||
false;
|
||||
|
||||
record(import, Tuple, Receiver, Module) ->
|
||||
record(Tuple, Receiver, Module) ->
|
||||
try
|
||||
ets:insert(table(Module), { Tuple, Receiver })
|
||||
catch
|
||||
error:badarg -> false
|
||||
end.
|
||||
|
||||
record_warn(_Meta, _Ref, _Opts, #elixir_scope{module=nil}) -> false;
|
||||
|
||||
record_warn(Meta, Ref, Opts, S) ->
|
||||
Table = table(S#elixir_scope.module),
|
||||
ets:delete(Table, Ref),
|
||||
|
||||
Warn =
|
||||
case keyfind(warn, Opts) of
|
||||
{ warn, false } -> false;
|
||||
{ warn, true } -> true;
|
||||
false -> not lists:keymember(context, 1, Meta)
|
||||
end,
|
||||
|
||||
Warn andalso ets:insert(Table, { Ref, ?line(Meta) }).
|
||||
|
||||
recorded_locals(Module) ->
|
||||
Table = table(Module),
|
||||
Match = { '$1', Module },
|
||||
@@ -60,6 +77,7 @@ import(Meta, Ref, Opts, Selector, S) ->
|
||||
SF#elixir_scope{macros=Macros, macro_macros=TempM}
|
||||
end,
|
||||
|
||||
record_warn(Meta, Ref, Opts, S),
|
||||
SM.
|
||||
|
||||
%% IMPORT FUNCTION RELATED HELPERS
|
||||
@@ -97,10 +115,23 @@ calculate(Meta, Key, Opts, Old, Temp, AvailableFun, S) ->
|
||||
Final = remove_internals(Set),
|
||||
|
||||
case Final of
|
||||
[] -> { keydelete(Key, Old), keydelete(Key, Temp) };
|
||||
[] -> { keydelete(Key, Old), if_quoted(Meta, Temp, fun(Value) -> keydelete(Key, Value) end) };
|
||||
_ ->
|
||||
ensure_no_in_erlang_macro_conflict(Meta, File, Key, Final, internal_conflict),
|
||||
{ [{ Key, Final }|keydelete(Key, Old)], [{ Key, Final }|keydelete(Key, Temp)] }
|
||||
ensure_no_special_form_conflict(Meta, File, Key, Final, internal_conflict),
|
||||
{ [{ Key, Final }|keydelete(Key, Old)],
|
||||
if_quoted(Meta, Temp, fun(Value) -> [{ Key, Final }|keydelete(Key, Value)] end) }
|
||||
end.
|
||||
|
||||
if_quoted(Meta, Temp, Callback) ->
|
||||
case lists:keyfind(context, 1, Meta) of
|
||||
{ context, Context } ->
|
||||
Current = case orddict:find(Context, Temp) of
|
||||
{ ok, Value } -> Value;
|
||||
error -> []
|
||||
end,
|
||||
orddict:store(Context, Callback(Current), Temp);
|
||||
_ ->
|
||||
Temp
|
||||
end.
|
||||
|
||||
%% Ensure we are expanding macros and stuff
|
||||
@@ -169,7 +200,7 @@ get_optional_macros(Module) ->
|
||||
%% conflicts with an import is automatically done by Erlang.
|
||||
|
||||
ensure_no_local_conflict(Meta, File, Module, AllDefined) ->
|
||||
ensure_no_in_erlang_macro_conflict(Meta, File, Module, AllDefined, local_conflict).
|
||||
ensure_no_special_form_conflict(Meta, File, Module, AllDefined, local_conflict).
|
||||
|
||||
%% Find conlicts in the given list of functions with
|
||||
%% the recorded set of imports.
|
||||
@@ -180,29 +211,39 @@ ensure_no_import_conflict(Meta, File, Module, AllDefined) ->
|
||||
|
||||
case Matches of
|
||||
[{Name,Arity}|_] ->
|
||||
Key = ets:lookup_element(Table, {Name, Arity }, 2),
|
||||
Tuple = { import_conflict, { Key, Name, Arity } },
|
||||
Key = ets:lookup_element(Table, { Name, Arity }, 2),
|
||||
Tuple = { import_conflict, { hd(Key), Name, Arity } },
|
||||
elixir_errors:form_error(Meta, File, ?MODULE, Tuple);
|
||||
[] ->
|
||||
ok
|
||||
end.
|
||||
|
||||
ensure_all_imports_used(_Line, File, Module) ->
|
||||
Table = table(Module),
|
||||
[begin
|
||||
elixir_errors:handle_file_warning(File, { L, ?MODULE, { unused_import, M } })
|
||||
end || [M, L] <- ets:select(Table, module_line_spec()),
|
||||
ets:match(Table, { '$1', M }) == []].
|
||||
|
||||
module_line_spec() ->
|
||||
[{ { '$1', '$2' }, [{ is_integer, '$2' }], ['$$'] }].
|
||||
|
||||
%% Ensure the given functions don't clash with any
|
||||
%% of Elixir non overridable macros.
|
||||
|
||||
ensure_no_in_erlang_macro_conflict(Meta, File, Key, [{Name,Arity}|T], Reason) ->
|
||||
ensure_no_special_form_conflict(Meta, File, Key, [{Name,Arity}|T], Reason) ->
|
||||
Values = lists:filter(fun({X,Y}) ->
|
||||
(Name == X) andalso ((Y == '*') orelse (Y == Arity))
|
||||
end, non_overridable_macros()),
|
||||
end, special_form()),
|
||||
|
||||
case Values /= [] of
|
||||
true ->
|
||||
Tuple = { Reason, { Key, Name, Arity } },
|
||||
elixir_errors:form_error(Meta, File, ?MODULE, Tuple);
|
||||
false -> ensure_no_in_erlang_macro_conflict(Meta, File, Key, T, Reason)
|
||||
false -> ensure_no_special_form_conflict(Meta, File, Key, T, Reason)
|
||||
end;
|
||||
|
||||
ensure_no_in_erlang_macro_conflict(_Meta, _File, _Key, [], _) -> ok.
|
||||
ensure_no_special_form_conflict(_Meta, _File, _Key, [], _) -> ok.
|
||||
|
||||
%% ERROR HANDLING
|
||||
|
||||
@@ -224,6 +265,9 @@ format_error({internal_conflict,{Receiver, Name, Arity}}) ->
|
||||
io_lib:format("cannot import ~ts.~ts/~B because it conflicts with Elixir special forms",
|
||||
[elixir_errors:inspect(Receiver), Name, Arity]);
|
||||
|
||||
format_error({ unused_import, Module }) ->
|
||||
io_lib:format("unused import ~ts", [elixir_errors:inspect(Module)]);
|
||||
|
||||
format_error({ no_macros, Module }) ->
|
||||
io_lib:format("could not load macros from module ~ts", [elixir_errors:inspect(Module)]).
|
||||
|
||||
@@ -266,7 +310,7 @@ remove_internals(Set) ->
|
||||
|
||||
%% Macros implemented in Erlang that are not overridable.
|
||||
|
||||
non_overridable_macros() ->
|
||||
special_form() ->
|
||||
[
|
||||
{'^',1},
|
||||
{'=',2},
|
||||
@@ -290,6 +334,7 @@ non_overridable_macros() ->
|
||||
{'__CALLER__',0},
|
||||
{'__MODULE__',0},
|
||||
{'__FILE__',0},
|
||||
{'__DIR__',0},
|
||||
{'__aliases__','*'},
|
||||
{'quote',1},
|
||||
{'quote',2},
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
-export([extract/5, unescape_chars/1, unescape_chars/2,
|
||||
unescape_tokens/1, unescape_tokens/2, unescape_map/1]).
|
||||
-include("elixir.hrl").
|
||||
-compile({parse_transform, elixir_transform}).
|
||||
|
||||
%% Extract string interpolations
|
||||
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
-module(elixir_literal).
|
||||
-export([translate/2]).
|
||||
-import(elixir_translator, [translate_each/2, translate_args/2]).
|
||||
-import(elixir_scope, [umergev/2, umergec/2]).
|
||||
-import(elixir_scope, [umergec/2]).
|
||||
-include("elixir.hrl").
|
||||
-compile({parse_transform, elixir_transform}).
|
||||
|
||||
translate({ '<<>>', Meta, Args } = Original, S) when is_list(Args) ->
|
||||
case elixir_partials:handle(Original, S, allow_tail) of
|
||||
@@ -29,33 +28,23 @@ translate({ '{}', Meta, Args } = Original, S) when is_list(Args) ->
|
||||
translate({ '[]', _Meta, [] }, S) ->
|
||||
{ { nil, 0 }, S };
|
||||
|
||||
translate({ '[]', _Meta, Args } = Original, S) when is_list(Args) ->
|
||||
translate({ '[]', Meta, Args } = Original, S) when is_list(Args) ->
|
||||
case elixir_partials:handle(Original, S, allow_tail) of
|
||||
error ->
|
||||
[RTail|RArgs] = lists:reverse(Args),
|
||||
|
||||
case RTail of
|
||||
{'|',_,[Left,Right]} ->
|
||||
Exprs = [Left|RArgs],
|
||||
{ Tail, ST } = translate_each(Right, S),
|
||||
ListS = umergec(S, ST);
|
||||
RExprs = [Left|RArgs],
|
||||
TailFun = fun(ST) -> translate_each(Right, ST) end;
|
||||
_ ->
|
||||
Exprs = [RTail|RArgs],
|
||||
Tail = { nil, 0 },
|
||||
ST = S,
|
||||
ListS = S
|
||||
RExprs = [RTail|RArgs],
|
||||
TailFun = fun(ST) -> { { nil, ?line(Meta) }, ST } end
|
||||
end,
|
||||
|
||||
{ FExprs, FS } = case S#elixir_scope.context of
|
||||
match ->
|
||||
elixir_tree_helpers:build_reverse_list(fun elixir_translator:translate_each/2, Exprs, 0, ListS, Tail);
|
||||
_ ->
|
||||
{ TArgs, { SC, SV } } =
|
||||
elixir_tree_helpers:build_reverse_list(fun elixir_translator:translate_arg/2, Exprs, 0, { ListS, ListS }, Tail),
|
||||
{ TArgs, umergec(SV, SC) }
|
||||
end,
|
||||
|
||||
{ FExprs, umergev(ST, FS) };
|
||||
{ Exprs, SE } = translate_args(lists:reverse(RExprs), S),
|
||||
{ Tail, ST } = TailFun(SE),
|
||||
{ elixir_tree_helpers:list_to_cons(?line(Meta), Exprs, Tail), ST };
|
||||
Else -> Else
|
||||
end;
|
||||
|
||||
@@ -75,7 +64,7 @@ translate(Atom, S) when is_atom(Atom) ->
|
||||
{ { atom, 0, Atom }, S };
|
||||
|
||||
translate(Bitstring, S) when is_bitstring(Bitstring) ->
|
||||
{ elixir_tree_helpers:abstract_syntax(Bitstring), S }.
|
||||
{ elixir_tree_helpers:elixir_to_erl(Bitstring), S }.
|
||||
|
||||
%% Helpers
|
||||
|
||||
@@ -106,37 +95,28 @@ build_bitstr_each(Fun, [{'::',_,[H,V]}|T], Meta, S, Acc) ->
|
||||
build_bitstr_each(Fun, [H|T], Meta, S, Acc) ->
|
||||
build_bitstr_each(Fun, T, Meta, S, Acc, H, default, default).
|
||||
|
||||
build_bitstr_each(Fun, T, Meta, S, Acc, H, Size, Types) when is_list(H) ->
|
||||
case is_default_or_utf(Types) of
|
||||
true ->
|
||||
{ NewAcc, NewS } = lists:foldl(fun(L, { LA, LS }) ->
|
||||
{ FL, FS } = Fun(L, LS),
|
||||
{ [{ bin_element, ?line(Meta), FL, Size, Types }|LA], FS }
|
||||
end, { Acc, S }, H),
|
||||
build_bitstr_each(Fun, T, Meta, NewS, NewAcc);
|
||||
false ->
|
||||
build_bitstr_default(Fun, T, Meta, S, Acc, H, Size, Types)
|
||||
end;
|
||||
|
||||
build_bitstr_each(Fun, T, Meta, S, Acc, H, Size, Types) when is_bitstring(H) ->
|
||||
case is_default_or_utf(Types) of
|
||||
true ->
|
||||
Line = ?line(Meta),
|
||||
{ bin, _, Elements } = elixir_tree_helpers:abstract_syntax(H),
|
||||
NewAcc = lists:foldl(fun({ bin_element, _, Expr, _, _ }, FinalAcc) ->
|
||||
[{ bin_element, Line, Expr, Size, Types }|FinalAcc]
|
||||
end, Acc, Elements),
|
||||
build_bitstr_each(Fun, T, Meta, S, NewAcc);
|
||||
false ->
|
||||
build_bitstr_default(Fun, T, Meta, S, Acc, H, Size, Types)
|
||||
end;
|
||||
|
||||
build_bitstr_each(Fun, T, Meta, S, Acc, H, Size, Types) ->
|
||||
build_bitstr_default(Fun, T, Meta, S, Acc, H, Size, Types).
|
||||
|
||||
build_bitstr_default(Fun, T, Meta, S, Acc, H, Size, Types) ->
|
||||
{ Expr, NS } = Fun(H, S),
|
||||
build_bitstr_each(Fun, T, Meta, NS, [{ bin_element, ?line(Meta), Expr, Size, Types }|Acc]).
|
||||
case (is_default_or_utf(Types) andalso Expr) of
|
||||
{ bin, _, BinElements } ->
|
||||
build_bitstr_each(Fun, T, Meta, NS, rehash_bin_elements(BinElements, Size, Types, []) ++ Acc);
|
||||
{ cons, _, _, _ } = Cons ->
|
||||
build_bitstr_each(Fun, T, Meta, NS, rehash_cons(Cons, Size, Types, []) ++ Acc);
|
||||
{ nil, _ } ->
|
||||
build_bitstr_each(Fun, T, Meta, NS, Acc);
|
||||
_ ->
|
||||
build_bitstr_each(Fun, T, Meta, NS, [{ bin_element, ?line(Meta), Expr, Size, Types }|Acc])
|
||||
end.
|
||||
|
||||
rehash_cons({ nil, _ }, _Size, _Types, Acc) -> Acc;
|
||||
rehash_cons({ cons, Line, Left, Right }, Size, Types, Acc) ->
|
||||
rehash_cons(Right, Size, Types, [{ bin_element, Line, Left, Size, Types }|Acc]).
|
||||
|
||||
rehash_bin_elements([{ bin_element, Line, Expr, _S, _T }|T], Size, Types, Acc) ->
|
||||
rehash_bin_elements(T, Size, Types, [{ bin_element, Line, Expr, Size, Types }|Acc]);
|
||||
|
||||
rehash_bin_elements([], _Size, _Types, Acc) ->
|
||||
Acc.
|
||||
|
||||
is_default_or_utf(default) -> true;
|
||||
is_default_or_utf([UTF|_]) when UTF == utf8; UTF == utf16; UTF == utf32 -> true;
|
||||
|
||||
@@ -6,12 +6,9 @@
|
||||
-import(elixir_scope, [umergec/2]).
|
||||
-import(elixir_errors, [syntax_error/3, syntax_error/4,
|
||||
assert_no_function_scope/3, assert_module_scope/3, assert_no_match_or_guard_scope/3]).
|
||||
|
||||
-include("elixir.hrl").
|
||||
|
||||
-define(FUNS(Kind), Kind == def; Kind == defp; Kind == defmacro; Kind == defmacrop).
|
||||
-define(IN_TYPES(Kind), Kind == atom orelse Kind == integer orelse Kind == float).
|
||||
|
||||
-compile({parse_transform, elixir_transform}).
|
||||
-define(opt_in_types(Kind), Kind == atom orelse Kind == integer orelse Kind == float).
|
||||
|
||||
%% Operators
|
||||
|
||||
@@ -63,13 +60,17 @@ translate({ function, MetaFA, [{ '/', _, [{F, Meta, C}, A]}] }, S) when is_atom(
|
||||
assert_no_match_or_guard_scope(Meta, 'function', S),
|
||||
|
||||
WrappedMeta =
|
||||
case (lists:keyfind(import, 1, Meta) == false) andalso lists:keyfind(import_fa, 1, MetaFA) of
|
||||
{ import_fa, Receiver } -> [{ import, Receiver }|Meta];
|
||||
case lists:keyfind(import_fa, 1, MetaFA) of
|
||||
{ import_fa, { Receiver, Context } } ->
|
||||
lists:keystore(context, 1,
|
||||
lists:keystore(import, 1, Meta, { import, Receiver }),
|
||||
{ context, Context }
|
||||
);
|
||||
false -> Meta
|
||||
end,
|
||||
|
||||
case elixir_dispatch:import_function(WrappedMeta, F, A, S) of
|
||||
false -> syntax_error(WrappedMeta, S#elixir_scope.file, "cannot convert a macro to a function");
|
||||
false -> syntax_error(WrappedMeta, S#elixir_scope.file, "expected ~ts/~B to be a function, but it is a macro", [F, A]);
|
||||
Else -> Else
|
||||
end;
|
||||
|
||||
@@ -77,20 +78,6 @@ translate({ function, Meta, [_] }, S) ->
|
||||
assert_no_match_or_guard_scope(Meta, 'function', S),
|
||||
syntax_error(Meta, S#elixir_scope.file, "invalid args for function");
|
||||
|
||||
translate({ function, Meta, [_, _] = Args }, S) ->
|
||||
assert_no_match_or_guard_scope(Meta, 'function', S),
|
||||
|
||||
case translate_args(Args, S) of
|
||||
{ [{atom,_,Name}, {integer,_,Arity}], SA } ->
|
||||
elixir_errors:deprecation(Meta, S#elixir_scope.file, "function(:~ts, ~B) is deprecated, please use function(~ts/~B)", [Name, Arity, Name, Arity]),
|
||||
case elixir_dispatch:import_function(Meta, Name, Arity, SA) of
|
||||
false -> syntax_error(Meta, S#elixir_scope.file, "cannot convert a macro to a function");
|
||||
Else -> Else
|
||||
end;
|
||||
_ ->
|
||||
syntax_error(Meta, S#elixir_scope.file, "cannot dynamically retrieve local function, use function/3 instead")
|
||||
end;
|
||||
|
||||
translate({ function, Meta, [_,_,_] = Args }, S) when is_list(Args) ->
|
||||
assert_no_match_or_guard_scope(Meta, 'function', S),
|
||||
{ [A,B,C], SA } = translate_args(Args, S),
|
||||
@@ -136,7 +123,7 @@ translate({'@', Meta, [{ Name, _, Args }]}, S) ->
|
||||
}, S);
|
||||
_ ->
|
||||
Contents = 'Elixir.Module':get_attribute(S#elixir_scope.module, Name),
|
||||
{ elixir_tree_helpers:abstract_syntax(Contents), S }
|
||||
{ elixir_tree_helpers:elixir_to_erl(Contents), S }
|
||||
end;
|
||||
_ ->
|
||||
syntax_error(Meta, S#elixir_scope.file, "expected 0 or 1 argument for @~ts, got: ~p", [Name, length(Args)])
|
||||
@@ -211,43 +198,41 @@ translate({defmodule, Meta, [Ref, KV]}, S) ->
|
||||
FullModule = expand_module(Ref, Module, S),
|
||||
|
||||
RS = case elixir_aliases:nesting_alias(S#elixir_scope.module, FullModule) of
|
||||
{ New, Old } ->
|
||||
S#elixir_scope{
|
||||
aliases=orddict:store(New, Old, S#elixir_scope.aliases),
|
||||
macro_aliases=orddict:store(New, Old, S#elixir_scope.macro_aliases)};
|
||||
false ->
|
||||
S
|
||||
{ New, Old } -> elixir_aliases:store(Meta, New, Old, S);
|
||||
false -> S
|
||||
end,
|
||||
|
||||
{
|
||||
{ atom, Meta, FullModule },
|
||||
RS#elixir_scope{scheduled=[FullModule|S#elixir_scope.scheduled]}
|
||||
RS#elixir_scope{context_modules=[FullModule|S#elixir_scope.context_modules]}
|
||||
};
|
||||
_ ->
|
||||
{ TRef, S }
|
||||
end,
|
||||
|
||||
MS = FS#elixir_scope{check_clauses=true,local=nil},
|
||||
MS = FS#elixir_scope{local=nil},
|
||||
{ elixir_module:translate(Meta, FRef, Block, MS), FS };
|
||||
|
||||
translate({Kind, Meta, [Call]}, S) when ?FUNS(Kind) ->
|
||||
translate({Kind, Meta, [Call]}, S) when ?defs(Kind) ->
|
||||
translate({Kind, Meta, [Call, nil]}, S);
|
||||
|
||||
translate({Kind, Meta, [Call, Expr]}, S) when ?FUNS(Kind) ->
|
||||
translate({Kind, Meta, [Call, Expr]}, S) when ?defs(Kind) ->
|
||||
assert_module_scope(Meta, Kind, S),
|
||||
assert_no_function_scope(Meta, Kind, S),
|
||||
{ TCall, SC } = elixir_quote:user_quote(Call, S),
|
||||
{ TExpr, SE } = elixir_quote:user_quote(Expr, SC),
|
||||
{ elixir_def:wrap_definition(Kind, Meta, TCall, TExpr, SE), SE };
|
||||
{ TCall, QC, SC } = elixir_quote:erl_escape(Call, true, S),
|
||||
{ TExpr, QE, SE } = elixir_quote:erl_escape(Expr, true, SC),
|
||||
CheckClauses = (not lists:keymember(context, 1, Meta)) andalso
|
||||
(not QC#elixir_quote.unquoted) andalso (not QE#elixir_quote.unquoted),
|
||||
{ elixir_def:wrap_definition(Kind, Meta, TCall, TExpr, CheckClauses, SE), SE };
|
||||
|
||||
translate({Kind, Meta, [Name, Args, Guards, Expr]}, S) when ?FUNS(Kind) ->
|
||||
translate({Kind, Meta, [Name, Args, Guards, Expr]}, S) when ?defs(Kind) ->
|
||||
assert_module_scope(Meta, Kind, S),
|
||||
assert_no_function_scope(Meta, Kind, S),
|
||||
{ TName, NS } = translate_each(Name, S),
|
||||
{ TArgs, AS } = translate_each(Args, NS),
|
||||
{ TGuards, GS } = translate_each(Guards, AS),
|
||||
{ TExpr, ES } = translate_each(Expr, GS),
|
||||
{ elixir_def:wrap_definition(Kind, Meta, TName, TArgs, TGuards, TExpr, ES), ES };
|
||||
{ TName, SN } = translate_each(Name, S),
|
||||
{ TArgs, SA } = translate_each(Args, SN),
|
||||
{ TGuards, SG } = translate_each(Guards, SA),
|
||||
{ TExpr, SE } = translate_each(Expr, SG),
|
||||
{ elixir_def:wrap_definition(Kind, Meta, TName, TArgs, TGuards, TExpr, false, SE), SE };
|
||||
|
||||
%% Apply - Optimize apply by checking what doesn't need to be dispatched dynamically
|
||||
|
||||
@@ -281,21 +266,23 @@ translate_in(Meta, Left, Right, S) ->
|
||||
false -> { TLeft, SR }
|
||||
end,
|
||||
|
||||
Expr = case TRight of
|
||||
{ InGuard, TExpr } = case TRight of
|
||||
{ cons, _, _, _ } ->
|
||||
[H|T] = elixir_tree_helpers:cons_to_list(TRight),
|
||||
lists:foldr(fun(X, Acc) ->
|
||||
Expr = lists:foldr(fun(X, Acc) ->
|
||||
{ op, Line, 'orelse', { op, Line, '==', Var, X }, Acc }
|
||||
end, { op, Line, '==', Var, H }, T);
|
||||
end, { op, Line, '==', Var, H }, T),
|
||||
{ Cache, Expr };
|
||||
{ string, _, [H|T] } ->
|
||||
lists:foldl(fun(X, Acc) ->
|
||||
Expr = lists:foldl(fun(X, Acc) ->
|
||||
{ op, Line, 'orelse', { op, Line, '==', Var, { integer, Line, X } }, Acc }
|
||||
end, { op, Line, '==', Var, { integer, Line, H } }, T);
|
||||
end, { op, Line, '==', Var, { integer, Line, H } }, T),
|
||||
{ Cache, Expr };
|
||||
{ tuple, _, [{ atom, _, 'Elixir.Range' }, Start, End] } ->
|
||||
case { Start, End } of
|
||||
{ { K1, _, StartInt }, { K2, _, EndInt } } when ?IN_TYPES(K1), ?IN_TYPES(K2), StartInt =< EndInt ->
|
||||
Expr = case { Start, End } of
|
||||
{ { K1, _, StartInt }, { K2, _, EndInt } } when ?opt_in_types(K1), ?opt_in_types(K2), StartInt =< EndInt ->
|
||||
increasing_compare(Line, Var, Start, End);
|
||||
{ { K1, _, _ }, { K2, _, _ } } when ?IN_TYPES(K1), ?IN_TYPES(K2) ->
|
||||
{ { K1, _, _ }, { K2, _, _ } } when ?opt_in_types(K1), ?opt_in_types(K2) ->
|
||||
decreasing_compare(Line, Var, Start, End);
|
||||
_ ->
|
||||
{ op, Line, 'orelse',
|
||||
@@ -305,14 +292,20 @@ translate_in(Meta, Left, Right, S) ->
|
||||
{ op, Line, 'andalso',
|
||||
{ op, Line, '<', End, Start},
|
||||
decreasing_compare(Line, Var, Start, End) } }
|
||||
end;
|
||||
end,
|
||||
{ Cache, Expr };
|
||||
_ ->
|
||||
syntax_error(Meta, S#elixir_scope.file, "invalid args for operator in, it expects an explicit array or an explicit range on the right side")
|
||||
case Cache of
|
||||
true ->
|
||||
{ false, ?wrap_call(Line, 'Elixir.Enum', 'member?', [TRight, TLeft]) };
|
||||
false ->
|
||||
syntax_error(Meta, S#elixir_scope.file, "invalid args for operator in, it expects an explicit array or an explicit range on the right side when used in guard expressions")
|
||||
end
|
||||
end,
|
||||
|
||||
case Cache of
|
||||
true -> { Var, { block, Line, [ { match, Line, Var, TLeft }, Expr ] }, SV };
|
||||
false -> { Var, Expr, SV }
|
||||
case InGuard of
|
||||
true -> { Var, { block, Line, [ { match, Line, Var, TLeft }, TExpr ] }, SV };
|
||||
false -> { Var, TExpr, SV }
|
||||
end.
|
||||
|
||||
increasing_compare(Line, Var, Start, End) ->
|
||||
@@ -331,10 +324,16 @@ rewrite_case_clauses([{do,[{in,_,[{'_',_,_},[false,nil]]}],False},{do,[{'_',_,_}
|
||||
rewrite_case_clauses(Clauses) ->
|
||||
Clauses.
|
||||
|
||||
%% defmodule :foo
|
||||
expand_module(Raw, Module, #elixir_scope{module=Nesting}) when is_atom(Raw); Nesting == nil ->
|
||||
Module;
|
||||
|
||||
expand_module({ '__aliases__', _, List } = Alias, Module, S) when List /= ['Elixir'] ->
|
||||
%% defmodule Hello
|
||||
expand_module({ '__aliases__', _, [H] }, _Module, S) ->
|
||||
elixir_aliases:concat([S#elixir_scope.module, H]);
|
||||
|
||||
%% defmodule Hello.World
|
||||
expand_module({ '__aliases__', _, _ } = Alias, Module, S) ->
|
||||
case elixir_aliases:expand(Alias, S#elixir_scope.aliases, S#elixir_scope.macro_aliases) of
|
||||
Atom when is_atom(Atom) ->
|
||||
Module;
|
||||
@@ -342,6 +341,7 @@ expand_module({ '__aliases__', _, List } = Alias, Module, S) when List /= ['Elix
|
||||
elixir_aliases:concat([S#elixir_scope.module, Module])
|
||||
end;
|
||||
|
||||
%% defmodule Elixir.Hello.World
|
||||
expand_module(_Raw, Module, S) ->
|
||||
elixir_aliases:concat([S#elixir_scope.module, Module]).
|
||||
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
-export([translate/4, compile/5, data_table/1, eval_quoted/4,
|
||||
format_error/1, eval_callbacks/5]).
|
||||
-include("elixir.hrl").
|
||||
-compile({parse_transform, elixir_transform}).
|
||||
|
||||
eval_quoted(Module, Quoted, RawBinding, Opts) ->
|
||||
Binding = binding_for_eval(Module, RawBinding),
|
||||
@@ -15,7 +14,7 @@ eval_quoted(Module, Quoted, RawBinding, Opts) ->
|
||||
false -> Line = 1
|
||||
end,
|
||||
|
||||
{ Value, FinalBinding, _Scope } = elixir:eval_quoted([Quoted], Binding, Line, Scope#elixir_scope{check_clauses=false}),
|
||||
{ Value, FinalBinding, _Scope } = elixir:eval_quoted([Quoted], Binding, Line, Scope),
|
||||
{ Value, FinalBinding }.
|
||||
|
||||
scope_for_eval(Module, #elixir_scope{} = S) ->
|
||||
@@ -42,7 +41,7 @@ docs_table(Module) ->
|
||||
|
||||
translate(Meta, Ref, Block, S) ->
|
||||
Line = ?line(Meta),
|
||||
MetaBlock = elixir_tree_helpers:abstract_syntax(Block),
|
||||
MetaBlock = elixir_tree_helpers:elixir_to_erl(Block),
|
||||
{ MetaS, Vars } = elixir_scope:serialize_with_vars(Line, S),
|
||||
|
||||
Args = [{integer, Line, Line}, Ref, MetaBlock, Vars, MetaS],
|
||||
@@ -50,8 +49,13 @@ translate(Meta, Ref, Block, S) ->
|
||||
|
||||
%% The compilation hook.
|
||||
|
||||
compile(Line, Module, Block, Vars, #elixir_scope{} = S) when is_atom(Module) ->
|
||||
compile(Line, Module, Block, Vars, #elixir_scope{context_modules=FileModules} = RawS) when is_atom(Module) ->
|
||||
C = elixir_compiler:get_opts(),
|
||||
S = case lists:member(Module, FileModules) of
|
||||
true -> RawS;
|
||||
false -> RawS#elixir_scope{context_modules=[Module|FileModules]}
|
||||
end,
|
||||
|
||||
File = S#elixir_scope.file,
|
||||
FileList = binary_to_list(File),
|
||||
|
||||
@@ -60,12 +64,13 @@ compile(Line, Module, Block, Vars, #elixir_scope{} = S) when is_atom(Module) ->
|
||||
|
||||
try
|
||||
Result = eval_form(Line, Module, Block, Vars, S),
|
||||
{ Export, Private, Def, Defmacro, Defmacrop, Functions } = elixir_def:unwrap_stored_definitions(FileList, Module),
|
||||
{ Export, Private, Def, Defmacro, Functions } = elixir_def:unwrap_stored_definitions(FileList, Module),
|
||||
|
||||
{ All, Forms0 } = functions_form(Line, File, Module, Export, Private, Def, Defmacro, Defmacrop, Functions, C),
|
||||
Forms1 = specs_form(Line, Module, Defmacro, Defmacrop, Forms0, C),
|
||||
{ All, Forms0 } = functions_form(Line, File, Module, Export, Private, Def, Defmacro, Functions, C),
|
||||
Forms1 = specs_form(Line, Module, Private, Defmacro, Forms0, C),
|
||||
Forms2 = attributes_form(Line, File, Module, Forms1),
|
||||
|
||||
elixir_import:ensure_all_imports_used(Line, File, Module),
|
||||
elixir_import:ensure_no_local_conflict(Line, File, Module, All),
|
||||
elixir_import:ensure_no_import_conflict(Line, File, Module, All),
|
||||
|
||||
@@ -140,7 +145,8 @@ eval_form(Line, Module, Block, Vars, RawS) ->
|
||||
Value.
|
||||
|
||||
%% Return the form with exports and function declarations.
|
||||
functions_form(Line, File, Module, Export, Private, Def, Defmacro, Defmacrop, RawFunctions, C) ->
|
||||
|
||||
functions_form(Line, File, Module, Export, Private, Def, Defmacro, RawFunctions, C) ->
|
||||
Functions = case elixir_compiler:get_opt(internal, C) of
|
||||
true -> RawFunctions;
|
||||
false -> record_rewrite_functions(Module, RawFunctions)
|
||||
@@ -149,10 +155,15 @@ functions_form(Line, File, Module, Export, Private, Def, Defmacro, Defmacrop, Ra
|
||||
{ FinalExport, FinalFunctions } =
|
||||
add_info_function(Line, File, Module, Export, Functions, Def, Defmacro, C),
|
||||
|
||||
Recorded = elixir_import:recorded_locals(Module),
|
||||
elixir_def_local:check_unused_local_macros(File, Recorded, Defmacrop),
|
||||
Recorded =
|
||||
case ets:lookup(data_table(Module), 'on_load') of
|
||||
[] -> elixir_import:recorded_locals(Module);
|
||||
[{on_load,OnLoad}] -> elixir_import:recorded_locals(Module) ++ OnLoad
|
||||
end,
|
||||
|
||||
{ FinalExport ++ Private, [
|
||||
elixir_def_local:check_unused_local(File, Recorded, Private),
|
||||
PrivateTuple = [Tuple || { Tuple, _, _, _, _ } <- Private],
|
||||
{ FinalExport ++ PrivateTuple, [
|
||||
{attribute, Line, export, lists:sort(FinalExport)} | FinalFunctions
|
||||
] }.
|
||||
|
||||
@@ -191,8 +202,8 @@ attributes_form(Line, _File, Module, Current) ->
|
||||
|
||||
%% Specs
|
||||
|
||||
specs_form(Line, Module, Defmacro, DefmacropWithLine, Forms, C) ->
|
||||
Defmacrop = [Tuple || { Tuple, _, _ } <- DefmacropWithLine],
|
||||
specs_form(Line, Module, Private, Defmacro, Forms, C) ->
|
||||
Defmacrop = [Tuple || { Tuple, defmacrop, _, _, _ } <- Private],
|
||||
case elixir_compiler:get_opt(internal, C) of
|
||||
true -> Forms;
|
||||
_ ->
|
||||
@@ -289,11 +300,11 @@ add_info_function(Line, File, Module, Export, Functions, Def, Defmacro, C) ->
|
||||
end.
|
||||
|
||||
functions_clause(Def) ->
|
||||
{ clause, 0, [{ atom, 0, functions }], [], [elixir_tree_helpers:abstract_syntax(Def)] }.
|
||||
{ clause, 0, [{ atom, 0, functions }], [], [elixir_tree_helpers:elixir_to_erl(Def)] }.
|
||||
|
||||
macros_clause(Module, Def, Defmacro) ->
|
||||
All = handle_builtin_macros(Module, Def, Defmacro),
|
||||
{ clause, 0, [{ atom, 0, macros }], [], [elixir_tree_helpers:abstract_syntax(All)] }.
|
||||
{ clause, 0, [{ atom, 0, macros }], [], [elixir_tree_helpers:elixir_to_erl(All)] }.
|
||||
|
||||
handle_builtin_macros('Elixir.Kernel', Def, Defmacro) ->
|
||||
ordsets:subtract(ordsets:union(Defmacro,
|
||||
@@ -305,14 +316,14 @@ module_clause(Module) ->
|
||||
|
||||
docs_clause(Module, true) ->
|
||||
Docs = ordsets:from_list(ets:tab2list(docs_table(Module))),
|
||||
{ clause, 0, [{ atom, 0, docs }], [], [elixir_tree_helpers:abstract_syntax(Docs)] };
|
||||
{ clause, 0, [{ atom, 0, docs }], [], [elixir_tree_helpers:elixir_to_erl(Docs)] };
|
||||
|
||||
docs_clause(_Module, _) ->
|
||||
{ clause, 0, [{ atom, 0, docs }], [], [{ atom, 0, nil }] }.
|
||||
|
||||
moduledoc_clause(Line, Module, true) ->
|
||||
Docs = 'Elixir.Module':get_attribute(Module, moduledoc),
|
||||
{ clause, 0, [{ atom, 0, moduledoc }], [], [elixir_tree_helpers:abstract_syntax({ Line, Docs })] };
|
||||
{ clause, 0, [{ atom, 0, moduledoc }], [], [elixir_tree_helpers:elixir_to_erl({ Line, Docs })] };
|
||||
|
||||
moduledoc_clause(_Line, _Module, _) ->
|
||||
{ clause, 0, [{ atom, 0, moduledoc }], [], [{ atom, 0, nil }] }.
|
||||
@@ -323,8 +334,7 @@ else_clause() ->
|
||||
|
||||
% HELPERS
|
||||
|
||||
eval_callbacks(Line, Module, Name, Args, RawS) ->
|
||||
S = RawS#elixir_scope{check_clauses=false},
|
||||
eval_callbacks(Line, Module, Name, Args, S) ->
|
||||
Binding = binding_for_eval(Module, []),
|
||||
Callbacks = lists:reverse(ets:lookup_element(data_table(Module), Name, 2)),
|
||||
Meta = [{line,Line},{require,false}],
|
||||
@@ -332,15 +342,20 @@ eval_callbacks(Line, Module, Name, Args, RawS) ->
|
||||
lists:foreach(fun({M,F}) ->
|
||||
{ Tree, _ } = elixir_dispatch:dispatch_require(Meta, M, F, Args, S, fun() ->
|
||||
apply(M, F, Args),
|
||||
{ { nil, 0 }, S }
|
||||
{ { atom, 0, nil }, S }
|
||||
end),
|
||||
|
||||
try
|
||||
erl_eval:exprs([Tree], Binding)
|
||||
catch
|
||||
Kind:Reason ->
|
||||
Info = { M, F, Args, [{ file, binary_to_list(S#elixir_scope.file) }, { line, Line }] },
|
||||
erlang:raise(Kind, Reason, [Info|erlang:get_stacktrace()])
|
||||
case Tree of
|
||||
{ atom, _, Atom } ->
|
||||
Atom;
|
||||
_ ->
|
||||
try
|
||||
erl_eval:exprs([Tree], Binding)
|
||||
catch
|
||||
Kind:Reason ->
|
||||
Info = { M, F, Args, [{ file, binary_to_list(S#elixir_scope.file) }, { line, Line }] },
|
||||
erlang:raise(Kind, Reason, [Info|erlang:get_stacktrace()])
|
||||
end
|
||||
end
|
||||
end, Callbacks).
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ Nonterminals
|
||||
grammar expr_list
|
||||
expr paren_expr block_expr fn_expr bracket_expr call_expr bracket_at_expr max_expr
|
||||
base_expr matched_expr matched_op_expr unmatched_expr op_expr
|
||||
add_op mult_op unary_op two_op pipeline_op bin_concat_op
|
||||
add_op mult_op unary_op two_op right_op bin_concat_op
|
||||
match_op send_op default_op when_op pipe_op in_op inc_op range_op
|
||||
andand_op oror_op and_op or_op comp_expr_op colon_colon_op three_op at_op
|
||||
open_paren close_paren empty_paren
|
||||
@@ -10,8 +10,9 @@ Nonterminals
|
||||
open_curly close_curly
|
||||
open_bit close_bit
|
||||
base_comma_expr comma_expr optional_comma_expr matched_comma_expr
|
||||
call_args call_args_parens call_args_parens_not_one call_args_no_parens parens_call
|
||||
stab stab_op stab_expr stab_maybe_expr
|
||||
call_args call_args_parens parens_call
|
||||
call_args_no_parens call_args_no_parens_strict call_args_parens_not_one
|
||||
stab stab_eol stab_op stab_expr stab_maybe_expr
|
||||
kw_eol kw_expr kw_comma kw_base
|
||||
matched_kw_expr matched_kw_comma matched_kw_base
|
||||
dot_op dot_ref dot_identifier dot_op_identifier dot_do_identifier
|
||||
@@ -30,7 +31,7 @@ Terminals
|
||||
'true' 'false' 'nil'
|
||||
'=' '+' '-' '*' '/' '++' '--' '**' '//'
|
||||
'(' ')' '[' ']' '{' '}' '<<' '>>' '::'
|
||||
eol ',' '&' '|' '.' '^' '@' '<-' '<>' '->' '|>' '/>' '=~'
|
||||
eol ',' '&' '|' '.' '^' '@' '<-' '<>' '->' '|>' '=~'
|
||||
'&&' '||' '!' '...' '..'
|
||||
'<<<' '>>>' '&&&' '|||' '^^^' '~~~'
|
||||
.
|
||||
@@ -53,13 +54,13 @@ Left 140 or_op.
|
||||
Left 150 and_op.
|
||||
Left 160 comp_expr_op.
|
||||
Left 170 in_op.
|
||||
Left 180 range_op.
|
||||
Left 190 three_op.
|
||||
Left 200 add_op.
|
||||
Left 210 mult_op.
|
||||
Right 220 bin_concat_op.
|
||||
Right 230 two_op.
|
||||
Right 290 pipeline_op.
|
||||
Right 180 right_op.
|
||||
Left 190 range_op.
|
||||
Left 200 three_op.
|
||||
Left 210 add_op.
|
||||
Left 220 mult_op.
|
||||
Right 230 bin_concat_op.
|
||||
Right 240 two_op.
|
||||
Nonassoc 300 unary_op.
|
||||
Left 310 dot_call_op.
|
||||
Left 310 dot_op.
|
||||
@@ -102,7 +103,7 @@ op_expr -> match_op expr : { '$1', '$2' }.
|
||||
op_expr -> add_op expr : { '$1', '$2' }.
|
||||
op_expr -> mult_op expr : { '$1', '$2' }.
|
||||
op_expr -> two_op expr : { '$1', '$2' }.
|
||||
op_expr -> pipeline_op expr : { '$1', '$2' }.
|
||||
op_expr -> right_op expr : { '$1', '$2' }.
|
||||
op_expr -> andand_op expr : { '$1', '$2' }.
|
||||
op_expr -> three_op expr : { '$1', '$2' }.
|
||||
op_expr -> oror_op expr : { '$1', '$2' }.
|
||||
@@ -123,7 +124,7 @@ matched_op_expr -> match_op matched_expr : { '$1', '$2' }.
|
||||
matched_op_expr -> add_op matched_expr : { '$1', '$2' }.
|
||||
matched_op_expr -> mult_op matched_expr : { '$1', '$2' }.
|
||||
matched_op_expr -> two_op matched_expr : { '$1', '$2' }.
|
||||
matched_op_expr -> pipeline_op matched_expr : { '$1', '$2' }.
|
||||
matched_op_expr -> right_op matched_expr : { '$1', '$2' }.
|
||||
matched_op_expr -> andand_op matched_expr : { '$1', '$2' }.
|
||||
matched_op_expr -> three_op matched_expr : { '$1', '$2' }.
|
||||
matched_op_expr -> oror_op matched_expr : { '$1', '$2' }.
|
||||
@@ -142,16 +143,16 @@ matched_op_expr -> comp_expr_op matched_expr : { '$1', '$2' }.
|
||||
|
||||
block_expr -> parens_call call_args_parens do_block : build_identifier('$1', '$2' ++ '$3').
|
||||
block_expr -> parens_call call_args_parens call_args_parens do_block : build_nested_parens('$1', '$2', '$3' ++ '$4').
|
||||
block_expr -> dot_punctuated_identifier call_args_no_parens do_block : build_identifier('$1', '$2' ++ '$3').
|
||||
block_expr -> dot_punctuated_identifier call_args_no_parens_strict do_block : build_identifier('$1', '$2' ++ '$3').
|
||||
block_expr -> dot_do_identifier do_block : build_identifier('$1', '$2').
|
||||
block_expr -> dot_identifier call_args_no_parens do_block : build_identifier('$1', '$2' ++ '$3').
|
||||
block_expr -> dot_identifier call_args_no_parens_strict do_block : build_identifier('$1', '$2' ++ '$3').
|
||||
|
||||
fn_expr -> fn_eol stab end_eol : build_fn('$1', build_stab(lists:reverse('$2'))).
|
||||
fn_expr -> call_expr : '$1'.
|
||||
|
||||
call_expr -> dot_punctuated_identifier call_args_no_parens : build_identifier('$1', '$2').
|
||||
call_expr -> dot_op_identifier call_args_no_parens : build_identifier('$1', '$2').
|
||||
call_expr -> dot_identifier call_args_no_parens : build_identifier('$1', '$2').
|
||||
call_expr -> dot_punctuated_identifier call_args_no_parens_strict : build_identifier('$1', '$2').
|
||||
call_expr -> dot_op_identifier call_args_no_parens_strict : build_identifier('$1', '$2').
|
||||
call_expr -> dot_identifier call_args_no_parens_strict : build_identifier('$1', '$2').
|
||||
call_expr -> dot_punctuated_identifier : build_identifier('$1', []).
|
||||
call_expr -> dot_do_identifier : build_identifier('$1', nil).
|
||||
call_expr -> var : build_identifier('$1', nil).
|
||||
@@ -192,7 +193,7 @@ base_expr -> sigil : build_sigil('$1').
|
||||
do_block -> do_eol 'end' : [[{do,nil}]].
|
||||
do_block -> do_eol stab end_eol : [[{ do, build_stab(lists:reverse('$2')) }]].
|
||||
do_block -> do_eol block_list 'end' : [[{ do, nil }|'$2']].
|
||||
do_block -> do_eol stab eol block_list 'end' : [[{ do, build_stab(lists:reverse('$2')) }|'$4']].
|
||||
do_block -> do_eol stab_eol block_list 'end' : [[{ do, build_stab(lists:reverse('$2')) }|'$3']].
|
||||
|
||||
fn_eol -> 'fn' : '$1'.
|
||||
fn_eol -> 'fn' eol : '$1'.
|
||||
@@ -209,6 +210,9 @@ block_eol -> block_identifier eol : '$1'.
|
||||
stab -> stab_expr : ['$1'].
|
||||
stab -> stab eol stab_expr : ['$3'|'$1'].
|
||||
|
||||
stab_eol -> stab : '$1'.
|
||||
stab_eol -> stab eol : '$1'.
|
||||
|
||||
stab_expr -> expr : '$1'.
|
||||
stab_expr -> stab_op stab_maybe_expr : build_op('$1', [], '$2').
|
||||
stab_expr -> call_args_no_parens stab_op stab_maybe_expr : build_op('$2', '$1', '$3').
|
||||
@@ -217,7 +221,7 @@ stab_expr -> call_args_parens_not_one stab_op stab_maybe_expr : build_op('$2', '
|
||||
stab_maybe_expr -> 'expr' : '$1'.
|
||||
stab_maybe_expr -> '$empty' : nil.
|
||||
|
||||
block_item -> block_eol stab eol : { ?exprs('$1'), build_stab(lists:reverse('$2')) }.
|
||||
block_item -> block_eol stab_eol : { ?exprs('$1'), build_stab(lists:reverse('$2')) }.
|
||||
block_item -> block_eol : { ?exprs('$1'), nil }.
|
||||
|
||||
block_list -> block_item : ['$1'].
|
||||
@@ -268,12 +272,10 @@ two_op -> '--' eol : '$1'.
|
||||
two_op -> '**' : '$1'.
|
||||
two_op -> '**' eol : '$1'.
|
||||
|
||||
pipeline_op -> '=~' : '$1'.
|
||||
pipeline_op -> '=~' eol : '$1'.
|
||||
pipeline_op -> '|>' : '$1'.
|
||||
pipeline_op -> '|>' eol : '$1'.
|
||||
pipeline_op -> '/>' : '$1'.
|
||||
pipeline_op -> '/>' eol : '$1'.
|
||||
right_op -> '=~' : '$1'.
|
||||
right_op -> '=~' eol : '$1'.
|
||||
right_op -> '|>' : '$1'.
|
||||
right_op -> '|>' eol : '$1'.
|
||||
|
||||
three_op -> '&&&' : '$1'.
|
||||
three_op -> '&&&' eol : '$1'.
|
||||
@@ -387,6 +389,11 @@ parens_call -> matched_expr dot_call_op : { '.', [{line,?line('$2')}], ['$1'] }.
|
||||
matched_comma_expr -> matched_expr : ['$1'].
|
||||
matched_comma_expr -> matched_comma_expr ',' matched_expr : ['$3'|'$1'].
|
||||
|
||||
call_args_no_parens_strict -> call_args_no_parens : '$1'.
|
||||
call_args_no_parens_strict -> open_paren ')' : throw_no_parens_strict('$1').
|
||||
call_args_no_parens_strict -> open_paren matched_kw_base close_paren : throw_no_parens_strict('$1').
|
||||
call_args_no_parens_strict -> open_paren matched_expr ',' call_args_no_parens close_paren : throw_no_parens_strict('$1').
|
||||
|
||||
call_args_no_parens -> matched_comma_expr : lists:reverse('$1').
|
||||
call_args_no_parens -> matched_kw_base : ['$1'].
|
||||
call_args_no_parens -> matched_comma_expr ',' matched_kw_base : lists:reverse(['$3'|'$1']).
|
||||
@@ -490,6 +497,8 @@ build_tuple(Marker, Args) ->
|
||||
|
||||
build_block([nil]) -> { '__block__', [], [nil] };
|
||||
build_block([{Op,_,[_]}]=Exprs) when ?rearrange_uop(Op) -> { '__block__', [], Exprs };
|
||||
build_block([{unquote_splicing,_,Args}]=Exprs) when
|
||||
length(Args) =< 2 -> { '__block__', [], Exprs };
|
||||
build_block([Expr]) when not is_list(Expr) -> Expr;
|
||||
build_block(Exprs) -> { '__block__', [], Exprs }.
|
||||
|
||||
@@ -542,7 +551,7 @@ build_fn(Op, Stab) ->
|
||||
|
||||
build_access(Expr, Access) ->
|
||||
Meta = [{line,?line(Access)}],
|
||||
{ { '.', Meta, ['Elixir-Kernel', access] }, Meta, [ Expr, ?op(Access) ] }.
|
||||
{ { '.', Meta, ['Elixir.Kernel', access] }, Meta, [ Expr, ?op(Access) ] }.
|
||||
|
||||
%% Interpolation aware
|
||||
|
||||
@@ -581,3 +590,13 @@ build_stab([H|T], Marker, Temp, Acc) ->
|
||||
build_stab([], Marker, Temp, Acc) ->
|
||||
H = { Marker, build_block(lists:reverse(Temp)) },
|
||||
lists:reverse([H|Acc]).
|
||||
|
||||
%% Errors
|
||||
|
||||
throw(Line, Error, Token) ->
|
||||
throw({ error, { Line, ?MODULE, [Error, Token] }}).
|
||||
|
||||
throw_no_parens_strict(Token) ->
|
||||
throw(?line(Token), "invalid comma inside parenthesis. If you are making a "
|
||||
"function call, do not insert spaces in between the function name and the "
|
||||
"opening parentheses. Syntax error before: ", ")").
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
-module(elixir_partials).
|
||||
-export([handle/2, handle/3, is_sequential/1]).
|
||||
-include("elixir.hrl").
|
||||
-compile({parse_transform, elixir_transform}).
|
||||
|
||||
handle(Original, S) ->
|
||||
handle(Original, S, default).
|
||||
|
||||
+133
-132
@@ -1,5 +1,5 @@
|
||||
-module(elixir_quote).
|
||||
-export([quote/3, linify/2, unquote/5, user_quote/2]).
|
||||
-export([escape/2, erl_escape/3, erl_quote/3, linify/2, unquote/5]).
|
||||
-include("elixir.hrl").
|
||||
|
||||
%% Apply the line from site call on quoted contents.
|
||||
@@ -22,48 +22,102 @@ do_linify(Line, List) when is_list(List) ->
|
||||
do_linify(_, Else) -> Else.
|
||||
|
||||
%% Some expressions cannot be unquoted at compilation time.
|
||||
%% This function is responisble for doing runtime unquoting.
|
||||
unquote(Meta, Left, { '__aliases__', _, Args }, nil, _File) ->
|
||||
%% This function is responsible for doing runtime unquoting.
|
||||
unquote(_File, Meta, Left, { '__aliases__', _, Args }, nil) ->
|
||||
{ '__aliases__', Meta, [Left|Args] };
|
||||
|
||||
unquote(Meta, Left, Right, nil, _File) when is_atom(Right) ->
|
||||
unquote(_File, Meta, Left, Right, nil) when is_atom(Right) ->
|
||||
case atom_to_list(Right) of
|
||||
"Elixir-" ++ _ ->
|
||||
"Elixir." ++ _ ->
|
||||
{ '__aliases__', Meta, [Left, Right] };
|
||||
_ ->
|
||||
{ { '.', Meta, [Left, Right] }, Meta, [] }
|
||||
end;
|
||||
|
||||
unquote(Meta, Left, Right, Args, _File) when is_atom(Right) ->
|
||||
unquote(_File, Meta, Left, Right, Args) when is_atom(Right) ->
|
||||
{ { '.', Meta, [Left, Right] }, Meta, Args };
|
||||
|
||||
unquote(Meta, _Left, _Right, _Args, File) ->
|
||||
unquote(File, Meta, _Left, _Right, _Args) ->
|
||||
elixir_errors:syntax_error(Meta, File, "expected unquote after dot to return an atom or an alias").
|
||||
|
||||
%% Similar to quote, but the given code is meant to come directly from the user.
|
||||
%% Basically, lines are kept and hygiene mechanisms are disabled.
|
||||
user_quote(Expr, S) ->
|
||||
%% Escapes the given expression. It is similar to quote, but
|
||||
%% lines are kept and hygiene mechanisms are disabled.
|
||||
escape(Expr, Unquote) ->
|
||||
quote(Expr, #elixir_quote{
|
||||
line=keep,
|
||||
vars_hygiene=nil,
|
||||
vars_hygiene=false,
|
||||
aliases_hygiene=false,
|
||||
imports_hygiene=false,
|
||||
unquote=true
|
||||
}, S).
|
||||
unquote=Unquote,
|
||||
mark=false
|
||||
}, nil).
|
||||
|
||||
%% Quotes an expression into Erlang's AST
|
||||
erl_escape(Expr, Unquote, S) ->
|
||||
{ QExpr, TQ } = escape(Expr, Unquote),
|
||||
{ TExpr, TS } = elixir_translator:translate_each(QExpr, S),
|
||||
{ TExpr, TQ, TS }.
|
||||
|
||||
quote({ 'unquote_splicing', Meta, [_] } = Expr, #elixir_quote{unquote=true} = Q, S) ->
|
||||
do_quote({ '__block__', Meta, [Expr] }, Q, S);
|
||||
%% Quotes an expression and return its quoted Elixir AST.
|
||||
|
||||
quote(Else, Q, S) ->
|
||||
do_quote(Else, Q, S).
|
||||
quote(Expr, Q, S) ->
|
||||
do_quote(Expr, Q, S).
|
||||
|
||||
erl_quote({ 'unquote_splicing', Meta, [_] } = Expr, #elixir_quote{unquote=true} = Q, S) ->
|
||||
elixir_errors:deprecation(Meta, S#elixir_scope.file, "unquote_splicing in the quote body is deprecated, please use (unquote_splicing()) instead", []),
|
||||
erl_quote({ '__block__', Meta, [Expr] }, Q, S);
|
||||
|
||||
erl_quote(Expr, Q, S) ->
|
||||
{ QExpr, TQ } = quote(Expr, Q, S),
|
||||
{ TExpr, TS } = elixir_translator:translate_each(QExpr, S),
|
||||
{ TExpr, TQ, TS }.
|
||||
|
||||
%% Actual quoting and helpers
|
||||
|
||||
do_quote({ quote, _, Args } = Tuple, #elixir_quote{unquote=true} = Q, S) when length(Args) == 1; length(Args) == 2 ->
|
||||
do_quote_tuple(Tuple, Q#elixir_quote{unquote=false}, S);
|
||||
{ TTuple, TQ } = do_quote_tuple(Tuple, Q#elixir_quote{unquote=false}, S),
|
||||
{ TTuple, TQ#elixir_quote{unquote=true} };
|
||||
|
||||
do_quote({ unquote, _Meta, [Expr] }, #elixir_quote{unquote=true}, S) ->
|
||||
elixir_translator:translate_each(Expr, S);
|
||||
do_quote({ unquote, _Meta, [Expr] }, #elixir_quote{unquote=true} = Q, _) ->
|
||||
{ Expr, Q#elixir_quote{unquoted=true} };
|
||||
|
||||
%% Context mark
|
||||
|
||||
do_quote({ Def, Meta, Args }, #elixir_quote{mark=true} = Q, S) when ?defs(Def); Def == defmodule; Def == alias; Def == import ->
|
||||
NewMeta = lists:keystore(context, 1, Meta, { context, Q#elixir_quote.context }),
|
||||
do_quote_tuple({ Def, NewMeta, Args }, Q, S);
|
||||
|
||||
do_quote({ { '.', _, [_, Def] } = Target, Meta, Args }, #elixir_quote{mark=true} = Q, S) when ?defs(Def); Def == defmodule; Def == alias; Def == import ->
|
||||
NewMeta = lists:keystore(context, 1, Meta, { context, Q#elixir_quote.context }),
|
||||
do_quote_tuple({ Target, NewMeta, Args }, Q, S);
|
||||
|
||||
%% Aliases
|
||||
|
||||
do_quote({ 'alias!', _Meta, [Expr] }, #elixir_quote{aliases_hygiene=true} = Q, S) ->
|
||||
{ TExpr, TQ } = do_quote(Expr, Q#elixir_quote{aliases_hygiene=false}, S),
|
||||
{ TExpr, TQ#elixir_quote{aliases_hygiene=true} };
|
||||
|
||||
do_quote({ '__aliases__', Meta, [H|T] } = Alias, #elixir_quote{aliases_hygiene=true} = Q, S) when is_atom(H) and (H /= 'Elixir') ->
|
||||
Annotation = case elixir_aliases:expand(Alias, S#elixir_scope.aliases, S#elixir_scope.macro_aliases) of
|
||||
Atom when is_atom(Atom) -> Atom;
|
||||
Aliases when is_list(Aliases) -> false
|
||||
end,
|
||||
AliasMeta = lists:keystore(alias, 1, Meta, { alias, Annotation }),
|
||||
do_quote_tuple({ '__aliases__', AliasMeta, [H|T] }, Q, S);
|
||||
|
||||
%% Vars
|
||||
|
||||
do_quote({ Left, Meta, nil }, #elixir_quote{vars_hygiene=true} = Q, S) when is_atom(Left) ->
|
||||
do_quote_tuple({ Left, Meta, Q#elixir_quote.context }, Q, S);
|
||||
|
||||
%% Unquote
|
||||
|
||||
do_quote({ { { '.', Meta, [Left, unquote] }, _, [Expr] }, _, Args }, #elixir_quote{unquote=true} = Q, S) ->
|
||||
do_quote_call(Left, Meta, Expr, Args, Q, S);
|
||||
|
||||
do_quote({ { '.', Meta, [Left, unquote] }, _, [Expr] }, #elixir_quote{unquote=true} = Q, S) ->
|
||||
do_quote_call(Left, Meta, Expr, nil, Q, S);
|
||||
|
||||
%% Imports
|
||||
|
||||
do_quote({ function, Meta, [{ '/', _, [{F, _, C}, A]}] = Args },
|
||||
#elixir_quote{imports_hygiene=true} = Q, S) when is_atom(F), is_integer(A), is_atom(C) ->
|
||||
@@ -73,43 +127,6 @@ do_quote({ { '.', _, [_, function] } = Target, Meta, [{ '/', _, [{F, _, C}, A]}]
|
||||
#elixir_quote{imports_hygiene=true} = Q, S) when is_atom(F), is_integer(A), is_atom(C) ->
|
||||
do_quote_fa(Target, Meta, Args, F, A, Q, S);
|
||||
|
||||
do_quote({ 'alias!', _Meta, [Expr] }, Q, S) ->
|
||||
do_quote(Expr, Q#elixir_quote{aliases_hygiene=false}, S);
|
||||
|
||||
do_quote({ '__aliases__', Meta, [H|T] } = Alias, #elixir_quote{aliases_hygiene=true} = Q, S) when is_atom(H) and (H /= 'Elixir') ->
|
||||
Annotation = case elixir_aliases:expand(Alias, S#elixir_scope.aliases, S#elixir_scope.macro_aliases) of
|
||||
Atom when is_atom(Atom) -> Atom;
|
||||
Aliases when is_list(Aliases) -> false
|
||||
end,
|
||||
|
||||
Line = ?line(Meta),
|
||||
{ TAliases, SA } = do_quote([H|T], Q, S),
|
||||
|
||||
{ { tuple, Line, [
|
||||
{ atom, Line, '__aliases__' },
|
||||
meta([{alias,Annotation}|Meta], Q),
|
||||
TAliases
|
||||
] }, SA };
|
||||
|
||||
do_quote({ { { '.', Meta, [Left, unquote] }, _, [Expr] }, _, Args }, #elixir_quote{unquote=true} = Q, S) ->
|
||||
All = [Left, { unquote, Meta, [Expr] }, Args, S#elixir_scope.file],
|
||||
{ TAll, TS } = lists:mapfoldl(fun(X, Acc) -> do_quote(X, Q, Acc) end, S, All),
|
||||
{ ?wrap_call(?line(Meta), elixir_quote, unquote, [meta(Meta, Q)|TAll]), TS };
|
||||
|
||||
do_quote({ { '.', Meta, [Left, unquote] }, _, [Expr] }, #elixir_quote{unquote=true} = Q, S) ->
|
||||
All = [Left, { unquote, Meta, [Expr] }, nil, S#elixir_scope.file],
|
||||
{ TAll, TS } = lists:mapfoldl(fun(X, Acc) -> do_quote(X, Q, Acc) end, S, All),
|
||||
{ ?wrap_call(?line(Meta), elixir_quote, unquote, [meta(Meta, Q)|TAll]), TS };
|
||||
|
||||
do_quote({ Left, Meta, nil }, Q, S) when is_atom(Left) ->
|
||||
Line = ?line(Meta),
|
||||
Tuple = { tuple, Line, [
|
||||
{ atom, Line, Left },
|
||||
meta(Meta, Q),
|
||||
{ atom, Line, Q#elixir_quote.vars_hygiene }
|
||||
] },
|
||||
{ Tuple, S };
|
||||
|
||||
do_quote({ Name, Meta, ArgsOrAtom } = Tuple, #elixir_quote{imports_hygiene=true} = Q, S) when is_atom(Name) ->
|
||||
Arity = case is_atom(ArgsOrAtom) of
|
||||
true -> 0;
|
||||
@@ -119,101 +136,85 @@ do_quote({ Name, Meta, ArgsOrAtom } = Tuple, #elixir_quote{imports_hygiene=true}
|
||||
case (lists:keyfind(import, 1, Meta) == false) andalso
|
||||
elixir_dispatch:find_import(Meta, Name, Arity, S) of
|
||||
false -> do_quote_tuple(Tuple, Q, S);
|
||||
Receiver -> do_quote_tuple({ Name, [{import,Receiver}|Meta], ArgsOrAtom }, Q, S)
|
||||
Receiver ->
|
||||
ImportMeta = lists:keystore(import, 1,
|
||||
lists:keystore(context, 1, Meta, { context, Q#elixir_quote.context }),
|
||||
{ import, Receiver }),
|
||||
do_quote_tuple({ Name, ImportMeta, ArgsOrAtom }, Q, S)
|
||||
end;
|
||||
|
||||
do_quote({ _, _, _ } = Tuple, Q, S) ->
|
||||
do_quote_tuple(Tuple, Q, S);
|
||||
|
||||
% Handle two item tuples but still allow them to be spliced.
|
||||
do_quote({ Left, Right }, Q, S) when
|
||||
not is_tuple(Left) orelse (element(1, Left) /= unquote_splicing),
|
||||
not is_tuple(Right) orelse (element(1, Right) /= unquote_splicing) ->
|
||||
{ TLeft, LS } = do_quote(Left, Q, S),
|
||||
{ TRight, RS } = do_quote(Right, Q, LS),
|
||||
{ { tuple, line(Q), [TLeft, TRight] }, RS };
|
||||
%% Literals
|
||||
|
||||
do_quote({ Left, Right }, #elixir_quote{unquote=true} = Q, S) when
|
||||
is_tuple(Left) andalso (element(1, Left) == unquote_splicing);
|
||||
is_tuple(Right) andalso (element(1, Right) == unquote_splicing) ->
|
||||
do_quote({ '{}', [], [Left, Right] }, Q, S);
|
||||
|
||||
do_quote({ Left, Right }, Q, S) ->
|
||||
do_quote({ '{}', [{line,line(Q)}], [Left, Right] }, Q, S);
|
||||
{ TLeft, LQ } = do_quote(Left, Q, S),
|
||||
{ TRight, RQ } = do_quote(Right, LQ, S),
|
||||
{ { TLeft, TRight }, RQ };
|
||||
|
||||
do_quote(Tuple, Q, S) when is_tuple(Tuple) ->
|
||||
{ TT, TQ } = do_quote(tuple_to_list(Tuple), Q, S),
|
||||
{ { '{}', [], TT }, TQ };
|
||||
|
||||
do_quote(List, Q, S) when is_list(List) ->
|
||||
splice(List, Q, [], [], S);
|
||||
do_splice(lists:reverse(List), Q, S);
|
||||
|
||||
do_quote(Number, Q, S) when is_integer(Number) ->
|
||||
{ { integer, line(Q), Number }, S };
|
||||
do_quote(Other, Q, _) ->
|
||||
{ Other, Q }.
|
||||
|
||||
do_quote(Number, Q, S) when is_float(Number) ->
|
||||
{ { float, line(Q), Number }, S };
|
||||
%% Quote helpers
|
||||
|
||||
do_quote(Atom, Q, S) when is_atom(Atom) ->
|
||||
{ { atom, line(Q), Atom }, S };
|
||||
|
||||
do_quote(Bitstring, _Q, S) when is_bitstring(Bitstring) ->
|
||||
{ elixir_tree_helpers:abstract_syntax(Bitstring), S }.
|
||||
|
||||
do_quote_tuple({ Left, Meta, Right }, Q, S) ->
|
||||
{ TLeft, LS } = do_quote(Left, Q, S),
|
||||
{ TRight, RS } = do_quote(Right, Q, LS),
|
||||
|
||||
%% We need to remove line numbers from quoted exprs
|
||||
%% otherwise the line number quotes in the macro will
|
||||
%% get mixed with the unquoted contents
|
||||
Tuple = { tuple, ?line(Meta), [TLeft, meta(Meta, Q), TRight] },
|
||||
{ Tuple, RS }.
|
||||
do_quote_call(Left, Meta, Expr, Args, Q, S) ->
|
||||
All = [meta(Meta, Q), Left, { unquote, Meta, [Expr] }, Args],
|
||||
{ TAll, TQ } = lists:mapfoldl(fun(X, Acc) -> do_quote(X, Acc, S) end, Q, All),
|
||||
{ { { '.', Meta, [elixir_quote, unquote] }, Meta, [{ '__FILE__', [], nil }|TAll] }, TQ }.
|
||||
|
||||
do_quote_fa(Target, Meta, Args, F, A, Q, S) ->
|
||||
NewMeta =
|
||||
case (lists:keyfind(import_fa, 1, Meta) == false) andalso
|
||||
elixir_dispatch:find_import(Meta, F, A, S) of
|
||||
elixir_dispatch:find_import(Meta, F, A, S) of
|
||||
false -> Meta;
|
||||
Receiver -> [{ import_fa, Receiver }|Meta]
|
||||
Receiver -> lists:keystore(import_fa, 1, Meta, { import_fa, { Receiver, Q#elixir_quote.context } })
|
||||
end,
|
||||
|
||||
do_quote_tuple({ Target, NewMeta, Args }, Q, S).
|
||||
|
||||
% Loop through the list finding each unquote_splicing entry.
|
||||
|
||||
splice([{ unquote_splicing, _, [Args] }|T], #elixir_quote{unquote=true} = Q, Buffer, Acc, S) ->
|
||||
{ NewAcc, NewS } = from_buffer_to_acc(Buffer, Q, Acc, S),
|
||||
{ TArgs, TS } = elixir_translator:translate_each(Args, NewS),
|
||||
splice(T, Q, [], [TArgs|NewAcc], TS);
|
||||
|
||||
splice([{ '|', Meta, [{ unquote_splicing, _, [_] } = Left, Right] }], #elixir_quote{unquote=true} = Q, Buffer, Acc, S) ->
|
||||
{ TLeft, SL } = splice([Left], Q, Buffer, Acc, S),
|
||||
{ TRight, SR } = do_quote(Right, Q, SL),
|
||||
{ { op, ?line(Meta), '++', TLeft, TRight }, SR };
|
||||
|
||||
splice([H|T], Q, Buffer, Acc, S) ->
|
||||
splice(T, Q, [H|Buffer], Acc, S);
|
||||
|
||||
splice([], Q, Buffer, Acc, S) ->
|
||||
{ NewAcc, NewS } = from_buffer_to_acc(Buffer, Q, Acc, S),
|
||||
case NewAcc of
|
||||
[] ->
|
||||
{ { nil, line(Q) }, NewS };
|
||||
[H|T] ->
|
||||
Line = line(Q),
|
||||
{ lists:foldl(fun(L, R) -> { op, Line, '++', L, R } end, H, T), NewS }
|
||||
end.
|
||||
|
||||
from_buffer_to_acc([], _Q, Acc, S) ->
|
||||
{ Acc, S };
|
||||
|
||||
from_buffer_to_acc(Buffer, Q, Acc, S) ->
|
||||
{ New, NewS } = elixir_tree_helpers:build_reverse_list(
|
||||
fun(X, AccS) -> do_quote(X, Q, AccS) end, Buffer, line(Q), S),
|
||||
{ [New|Acc], NewS }.
|
||||
do_quote_tuple({ Left, Meta, Right }, Q, S) ->
|
||||
{ TLeft, LQ } = do_quote(Left, Q, S),
|
||||
{ TRight, RQ } = do_quote(Right, LQ, S),
|
||||
{ { '{}', [], [TLeft, meta(Meta, Q), TRight] }, RQ }.
|
||||
|
||||
meta(Meta, #elixir_quote{line=keep}) ->
|
||||
elixir_tree_helpers:abstract_syntax(Meta);
|
||||
Meta;
|
||||
meta(Meta, #elixir_quote{line=nil}) ->
|
||||
elixir_tree_helpers:abstract_syntax(lists:keydelete(line, 1, Meta));
|
||||
meta(Meta, #elixir_quote{line=Line} = Q) ->
|
||||
{ cons,
|
||||
line(Q),
|
||||
{ tuple, line(Q), [{ atom, line(Q), line }, Line] },
|
||||
elixir_tree_helpers:abstract_syntax(lists:keydelete(line, 1, Meta))
|
||||
}.
|
||||
lists:keydelete(line, 1, Meta);
|
||||
meta(Meta, #elixir_quote{line=Line}) ->
|
||||
lists:keystore(line, 1, Meta, { line, Line }).
|
||||
|
||||
line(#elixir_quote{line={ integer, _, Line }}) -> Line;
|
||||
line(_) -> 0.
|
||||
%% Quote splicing
|
||||
|
||||
do_splice([{ '|', _, [{ unquote_splicing, _, [Left] }, Right] }|T], #elixir_quote{unquote=true} = Q, S) ->
|
||||
{ TT, TQ } = do_splice(T, Q, S, [], []),
|
||||
{ do_splice_join(do_splice_join(TT, Left), Right), TQ#elixir_quote{unquoted=true} };
|
||||
|
||||
do_splice(List, Q, S) ->
|
||||
do_splice(List, Q, S, [], []).
|
||||
|
||||
do_splice([{ unquote_splicing, _, [Expr] }|T], #elixir_quote{unquote=true} = Q, S, Buffer, Acc) ->
|
||||
do_splice(T, Q#elixir_quote{unquoted=true}, S, [], do_splice_join(do_splice_join(Expr, Buffer), Acc));
|
||||
|
||||
do_splice([H|T], Q, S, Buffer, Acc) ->
|
||||
{ TH, TQ } = do_quote(H, Q, S),
|
||||
do_splice(T, TQ, S, [TH|Buffer], Acc);
|
||||
|
||||
do_splice([], Q, _S, Buffer, Acc) ->
|
||||
{ do_splice_join(Buffer, Acc), Q }.
|
||||
|
||||
do_splice_join(Left, []) -> Left;
|
||||
do_splice_join([], Right) -> Right;
|
||||
do_splice_join(Left, Right) -> { { '.', [], ['Elixir.Kernel', '++'] }, [], [Left, Right] }.
|
||||
|
||||
@@ -9,7 +9,6 @@
|
||||
umergev/2, umergec/2, merge_clause_vars/2
|
||||
]).
|
||||
-include("elixir.hrl").
|
||||
-compile({parse_transform, elixir_transform}).
|
||||
|
||||
translate_var(Meta, Name, Kind, S, Callback) ->
|
||||
Line = ?line(Meta),
|
||||
@@ -77,24 +76,27 @@ build_ex_var(Line, Key, Name, S) when is_integer(Line) ->
|
||||
|
||||
% Handle Macro.Env conversion
|
||||
|
||||
to_erl_env({ 'Elixir.Macro.Env', Module, File, _Line, Function, Aliases, Context, Requires, Functions, Macros }) ->
|
||||
to_erl_env({ 'Elixir.Macro.Env', Module, File, _Line, Function, Aliases, Context, Requires, Functions, Macros, FileModules }) ->
|
||||
#elixir_scope{module=Module,file=File,
|
||||
function=Function,aliases=Aliases,context=Context,
|
||||
requires=Requires,macros=Macros,functions=Functions}.
|
||||
requires=Requires,macros=Macros,functions=Functions,
|
||||
context_modules=FileModules}.
|
||||
|
||||
to_ex_env({ Line, #elixir_scope{module=Module,file=File,
|
||||
function=Function,aliases=Aliases,context=Context,
|
||||
requires=Requires,macros=Macros,functions=Functions} }) when is_integer(Line) ->
|
||||
{ 'Elixir.Macro.Env', Module, File, Line, Function, Aliases, Context, Requires, Functions, Macros }.
|
||||
requires=Requires,macros=Macros,functions=Functions,
|
||||
context_modules=FileModules} }) when is_integer(Line) ->
|
||||
{ 'Elixir.Macro.Env', Module, File, Line, Function, Aliases,
|
||||
Context, Requires, Functions, Macros, FileModules }.
|
||||
|
||||
% Provides a tuple with only the scope information we want to serialize.
|
||||
|
||||
serialize(S) ->
|
||||
elixir_tree_helpers:abstract_syntax(
|
||||
{ S#elixir_scope.file, S#elixir_scope.functions, S#elixir_scope.check_clauses,
|
||||
elixir_tree_helpers:elixir_to_erl(
|
||||
{ S#elixir_scope.file, S#elixir_scope.functions,
|
||||
S#elixir_scope.requires, S#elixir_scope.macros, S#elixir_scope.aliases,
|
||||
S#elixir_scope.macro_functions, S#elixir_scope.macro_macros, S#elixir_scope.macro_aliases,
|
||||
S#elixir_scope.scheduled }
|
||||
S#elixir_scope.context_modules }
|
||||
).
|
||||
|
||||
serialize_with_vars(Line, S) when is_integer(Line) ->
|
||||
@@ -112,19 +114,18 @@ serialize_with_vars(Line, S) when is_integer(Line) ->
|
||||
|
||||
deserialize(Tuple) -> deserialize_with_vars(Tuple, []).
|
||||
|
||||
deserialize_with_vars({ File, Functions, CheckClauses, Requires, Macros,
|
||||
Aliases, MacroFunctions, MacroMacros, MacroAliases, Scheduled }, Vars) ->
|
||||
deserialize_with_vars({ File, Functions, Requires, Macros,
|
||||
Aliases, MacroFunctions, MacroMacros, MacroAliases, FileModules }, Vars) ->
|
||||
#elixir_scope{
|
||||
file=File,
|
||||
functions=Functions,
|
||||
check_clauses=CheckClauses,
|
||||
requires=Requires,
|
||||
macros=Macros,
|
||||
aliases=Aliases,
|
||||
macro_functions=MacroFunctions,
|
||||
macro_macros=MacroMacros,
|
||||
macro_aliases=MacroAliases,
|
||||
scheduled=Scheduled,
|
||||
context_modules=FileModules,
|
||||
vars=orddict:from_list(Vars),
|
||||
counter=[{'',length(Vars)}]
|
||||
}.
|
||||
@@ -138,26 +139,30 @@ umergev(S1, S2) ->
|
||||
C1 = S1#elixir_scope.clause_vars,
|
||||
C2 = S2#elixir_scope.clause_vars,
|
||||
S2#elixir_scope{
|
||||
vars=orddict:merge(fun var_merger/3, V1, V2),
|
||||
vars=merge_vars(V1, V2),
|
||||
clause_vars=merge_clause_vars(C1, C2)
|
||||
}.
|
||||
|
||||
% Receives two scopes and return a new scope based on the first
|
||||
% with the counter values from the second one.
|
||||
% Receives two scopes and return the later scope
|
||||
% keeping the variables from the first (counters,
|
||||
% imports and everything else are passed forward).
|
||||
|
||||
umergec(S1, S2) ->
|
||||
S1#elixir_scope{
|
||||
counter=S2#elixir_scope.counter,
|
||||
extra_guards=S2#elixir_scope.extra_guards,
|
||||
super=S1#elixir_scope.super orelse S2#elixir_scope.super,
|
||||
caller=S1#elixir_scope.caller orelse S2#elixir_scope.caller,
|
||||
name_args=S1#elixir_scope.name_args orelse S2#elixir_scope.name_args
|
||||
S2#elixir_scope{
|
||||
vars=S1#elixir_scope.vars,
|
||||
temp_vars=S1#elixir_scope.temp_vars,
|
||||
clause_vars=S1#elixir_scope.clause_vars
|
||||
}.
|
||||
|
||||
% Merge variables trying to find the most recently created.
|
||||
|
||||
merge_vars(V, V) -> V;
|
||||
merge_vars(V1, V2) ->
|
||||
orddict:merge(fun var_merger/3, V1, V2).
|
||||
|
||||
merge_clause_vars(nil, _C2) -> nil;
|
||||
merge_clause_vars(_C1, nil) -> nil;
|
||||
merge_clause_vars(C, C) -> C;
|
||||
merge_clause_vars(C1, C2) ->
|
||||
orddict:merge(fun var_merger/3, C1, C2).
|
||||
|
||||
|
||||
+108
-118
@@ -141,7 +141,7 @@ tokenize([$%,S,H,H,H|T], Line, #scope{file=File} = Scope, Tokens) when ?is_quote
|
||||
tokenize(Final, Line, Scope, [{ sigil, Line, S, Parts, Modifiers }|Tokens])
|
||||
end;
|
||||
|
||||
tokenize([$%,S,H|T], Line, #scope{file=File} = Scope, Tokens) when not(?is_word(H)), ?is_upcase(S) orelse ?is_downcase(S) ->
|
||||
tokenize([$%,S,H|T], Line, #scope{file=File} = Scope, Tokens) when not(?is_identifier(H)), not(?is_terminator(H)), ?is_upcase(S) orelse ?is_downcase(S) ->
|
||||
case elixir_interpolation:extract(Line, File, ?is_downcase(S), T, sigil_terminator(H)) of
|
||||
{ NewLine, Parts, Rest } ->
|
||||
{ Final, Modifiers } = collect_modifiers(Rest, []),
|
||||
@@ -207,7 +207,7 @@ tokenize([$?,Char|T], Line, Scope, Tokens) ->
|
||||
% Dot identifier/operators
|
||||
|
||||
tokenize("..." ++ Rest, Line, Scope, Tokens) ->
|
||||
Token = tokenize_call_identifier(identifier, Line, '...', Rest),
|
||||
Token = check_call_identifier(identifier, Line, '...', Rest),
|
||||
tokenize(Rest, Line, Scope, [Token|Tokens]);
|
||||
|
||||
% ## Containers
|
||||
@@ -247,7 +247,7 @@ tokenize([$.,H|T], Line, #scope{file=File} = Scope, Tokens) when ?is_quote(H) ->
|
||||
case elixir_interpolation:extract(Line, File, true, T, H) of
|
||||
{ NewLine, [Part], Rest } when is_binary(Part) ->
|
||||
Atom = unsafe_to_atom(Part, Scope),
|
||||
Token = tokenize_call_identifier(identifier, Line, Atom, Rest),
|
||||
Token = check_call_identifier(identifier, Line, Atom, Rest),
|
||||
tokenize(Rest, NewLine, Scope, [Token|add_token_with_nl({ '.', Line }, Tokens)]);
|
||||
Error ->
|
||||
interpolation_error(Error, " (for function name starting at line ~B)", [Line])
|
||||
@@ -270,10 +270,6 @@ tokenize([$'|T], Line, Scope, Tokens) ->
|
||||
|
||||
% Atoms
|
||||
|
||||
tokenize([$:,T|String], Line, Scope, Tokens) when ?is_upcase(T); ?is_downcase(T); T == $_ ->
|
||||
{ Rest, Atom } = tokenize_atom([T|String], [], Scope),
|
||||
tokenize(Rest, Line, Scope, [{ atom, Line, Atom }|Tokens]);
|
||||
|
||||
tokenize([$:,H|T], Line, #scope{file=File} = Scope, Tokens) when ?is_quote(H) ->
|
||||
case elixir_interpolation:extract(Line, File, true, T, H) of
|
||||
{ NewLine, Parts, Rest } ->
|
||||
@@ -286,6 +282,10 @@ tokenize([$:,H|T], Line, #scope{file=File} = Scope, Tokens) when ?is_quote(H) ->
|
||||
interpolation_error(Error, " (for atom starting at line ~B)", [Line])
|
||||
end;
|
||||
|
||||
tokenize([$:,T|String], Line, Scope, Tokens) when ?is_new_atom(T) ->
|
||||
{ Rest, Atom } = tokenize_atom([T|String], [], Scope),
|
||||
tokenize(Rest, Line, Scope, [{ atom, Line, Atom }|Tokens]);
|
||||
|
||||
% Atom identifiers/operators
|
||||
|
||||
tokenize(":..." ++ Rest, Line, Scope, Tokens) ->
|
||||
@@ -347,18 +347,12 @@ tokenize([T1,T2,T3|Rest], Line, Scope, Tokens) when ?op3(T1, T2, T3) ->
|
||||
% ## Containers + punctuation tokens
|
||||
tokenize([T,T|Rest], Line, Scope, Tokens) when T == $<; T == $> ->
|
||||
Token = { list_to_atom([T,T]), Line },
|
||||
case handle_terminator(Token, Scope) of
|
||||
{ error, _ } = Error -> Error;
|
||||
New -> tokenize(Rest, Line, New, [Token|Tokens])
|
||||
end;
|
||||
handle_terminator(Rest, Line, Scope, Token, Tokens);
|
||||
|
||||
tokenize([T|Rest], Line, Scope, Tokens) when T == $(;
|
||||
T == ${; T == $}; T == $[; T == $]; T == $); T == $, ->
|
||||
Token = { list_to_atom([T]), Line },
|
||||
case handle_terminator(Token, Scope) of
|
||||
{ error, _ } = Error -> Error;
|
||||
New -> tokenize(Rest, Line, New, [Token|Tokens])
|
||||
end;
|
||||
handle_terminator(Rest, Line, Scope, Token, Tokens);
|
||||
|
||||
% ## Comparison two token operators
|
||||
tokenize([T1,T2|Rest], Line, Scope, Tokens) when ?comp2(T1, T2) ->
|
||||
@@ -388,10 +382,10 @@ tokenize([H|_] = String, Line, Scope, Tokens) when ?is_digit(H) ->
|
||||
% Aliases
|
||||
|
||||
tokenize([H|_] = String, Line, Scope, Tokens) when ?is_upcase(H) ->
|
||||
{ Rest, Alias } = tokenize_identifier(String, [], false),
|
||||
{ Rest, Alias } = tokenize_identifier(String, []),
|
||||
Atom = unsafe_to_atom(Alias, Scope),
|
||||
case Rest of
|
||||
[$:|T] when hd(T) /= $: ->
|
||||
[$:|T] when ?is_space(hd(T)) ->
|
||||
tokenize(T, Line, Scope, [{ kw_identifier, Line, Atom }|Tokens]);
|
||||
_ ->
|
||||
tokenize(Rest, Line, Scope, [{ aliases, Line, [Atom] }|Tokens])
|
||||
@@ -400,34 +394,33 @@ tokenize([H|_] = String, Line, Scope, Tokens) when ?is_upcase(H) ->
|
||||
% Identifier
|
||||
|
||||
tokenize([H|_] = String, Line, Scope, Tokens) when ?is_downcase(H); H == $_ ->
|
||||
{ Rest, { Kind, _, Identifier } } = tokenize_any_identifier(String, Line, [], Scope),
|
||||
case handle_keyword(Line, Kind, Identifier, Tokens) of
|
||||
nomatch ->
|
||||
tokenize(Rest, Line, Scope, [{ Kind, Line, Identifier }|Tokens]);
|
||||
{ ok, [Check|T] } ->
|
||||
case handle_terminator(Check, Scope) of
|
||||
{ error, _ } = Error -> Error;
|
||||
New -> tokenize(Rest, Line, New, [Check|T])
|
||||
end;
|
||||
{ error, Token } ->
|
||||
{ error, { Line, "syntax error before: ", Token } }
|
||||
case tokenize_any_identifier(String, Line, [], Scope) of
|
||||
{ error, _ } = Error -> Error;
|
||||
{ Rest, { Kind, _, Identifier } } ->
|
||||
case check_keyword(Line, Kind, Identifier, Tokens) of
|
||||
nomatch ->
|
||||
tokenize(Rest, Line, Scope, [{ Kind, Line, Identifier }|Tokens]);
|
||||
{ ok, [Check|T] } ->
|
||||
handle_terminator(Rest, Line, Scope, Check, T);
|
||||
{ error, Token } ->
|
||||
{ error, { Line, "syntax error before: ", Token } }
|
||||
end
|
||||
end;
|
||||
|
||||
% Ambiguous unary/binary operators tokens
|
||||
|
||||
tokenize([Space, Sign, NotMarker|T], Line, Scope, [{ Identifier, _, _ } = H|Tokens]) when
|
||||
Sign == $+ orelse Sign == $-,
|
||||
Space == $\s orelse Space == $\t,
|
||||
NotMarker /= $\s, NotMarker /= $\t, NotMarker /= $\r,
|
||||
NotMarker /= $\n, NotMarker /= $:, NotMarker /= $(,
|
||||
NotMarker /= $+, NotMarker /= $-, NotMarker /= $>,
|
||||
?is_horizontal_space(Space),
|
||||
not(?is_space(NotMarker)),
|
||||
NotMarker /= $(, NotMarker /= $+, NotMarker /= $-, NotMarker /= $>,
|
||||
Identifier == identifier orelse Identifier == punctuated_identifier ->
|
||||
Rest = [NotMarker|T],
|
||||
tokenize(Rest, Line, Scope, [{ list_to_atom([Sign]), Line }, setelement(1, H, op_identifier)|Tokens]);
|
||||
|
||||
% Spaces
|
||||
|
||||
tokenize([T|Rest], Line, Scope, Tokens) when T == $\s; T == $\r; T == $\t ->
|
||||
tokenize([T|Rest], Line, Scope, Tokens) when ?is_horizontal_space(T) ->
|
||||
tokenize(Rest, Line, Scope, Tokens);
|
||||
|
||||
tokenize([{line,Line}|Rest], _Line, Scope, Tokens) ->
|
||||
@@ -454,7 +447,7 @@ handle_heredocs(T, Line, H, #scope{file=File} = Scope, Tokens) ->
|
||||
|
||||
handle_strings(T, Line, H, #scope{file=File} = Scope, Tokens) ->
|
||||
case elixir_interpolation:extract(Line, File, true, T, H) of
|
||||
{ NewLine, Parts, [$:|Rest] } when hd(Rest) /= $: ->
|
||||
{ NewLine, Parts, [$:|Rest] } when ?is_space(hd(Rest)) ->
|
||||
case Parts of
|
||||
[Bin] when is_binary(Bin) ->
|
||||
Atom = unsafe_to_atom(unescape_chars(Bin), Scope),
|
||||
@@ -469,15 +462,13 @@ handle_strings(T, Line, H, #scope{file=File} = Scope, Tokens) ->
|
||||
interpolation_error(Error, " (for string starting at line ~B)", [Line])
|
||||
end.
|
||||
|
||||
handle_comp_op([$:|Rest], Line, Op, Scope, Tokens) when hd(Rest) /= $: ->
|
||||
verify_kw_and_space(Line, Op, Rest, Scope),
|
||||
handle_comp_op([$:|Rest], Line, Op, Scope, Tokens) when ?is_space(hd(Rest)) ->
|
||||
tokenize(Rest, Line, Scope, [{ kw_identifier, Line, Op }|Tokens]);
|
||||
|
||||
handle_comp_op(Rest, Line, Op, Scope, Tokens) ->
|
||||
tokenize(Rest, Line, Scope, add_token_with_nl({ comp_op, Line, Op }, Tokens)).
|
||||
|
||||
handle_op([$:|Rest], Line, Op, Scope, Tokens) when hd(Rest) /= $: ->
|
||||
verify_kw_and_space(Line, Op, Rest, Scope),
|
||||
handle_op([$:|Rest], Line, Op, Scope, Tokens) when ?is_space(hd(Rest)) ->
|
||||
tokenize(Rest, Line, Scope, [{ kw_identifier, Line, Op }|Tokens]);
|
||||
|
||||
handle_op(Rest, Line, Op, Scope, Tokens) when ?unary_op(Op) ->
|
||||
@@ -487,7 +478,7 @@ handle_op(Rest, Line, Op, Scope, Tokens) ->
|
||||
tokenize(Rest, Line, Scope, add_token_with_nl({ Op, Line }, Tokens)).
|
||||
|
||||
handle_call_identifier(Rest, Line, Op, Scope, Tokens) ->
|
||||
Token = tokenize_call_identifier(identifier, Line, Op, Rest),
|
||||
Token = check_call_identifier(identifier, Line, Op, Rest),
|
||||
tokenize(Rest, Line, Scope, [Token|add_token_with_nl({ '.', Line }, Tokens)]).
|
||||
|
||||
%% Helpers
|
||||
@@ -511,7 +502,7 @@ collect_modifiers([H|T], Buffer) when ?is_downcase(H) ->
|
||||
collect_modifiers(Rest, Buffer) ->
|
||||
{ Rest, lists:reverse(Buffer) }.
|
||||
|
||||
% Extract heredocs
|
||||
%% Heredocs
|
||||
|
||||
extract_heredoc_with_interpolation(Line, File, Interpol, T, H) ->
|
||||
case extract_heredoc(Line, T, H) of
|
||||
@@ -553,7 +544,7 @@ remove_heredoc_spaces(Body, 0) ->
|
||||
remove_heredoc_spaces(Body, Spaces) ->
|
||||
remove_heredoc_spaces([0|Body], [], Spaces, Spaces).
|
||||
|
||||
remove_heredoc_spaces([H,$\n|T], [Backtrack|Buffer], Spaces, Original) when Spaces > 0, H == $\s orelse H == $\t ->
|
||||
remove_heredoc_spaces([H,$\n|T], [Backtrack|Buffer], Spaces, Original) when Spaces > 0, ?is_horizontal_space(H) ->
|
||||
remove_heredoc_spaces([Backtrack,$\n|T], Buffer, Spaces - 1, Original);
|
||||
|
||||
remove_heredoc_spaces([$\n=H|T], Buffer, _Spaces, Original) ->
|
||||
@@ -595,7 +586,7 @@ extract_heredoc_line(_, _) ->
|
||||
|
||||
%% Extract each heredoc line trying to find a match according to the marker.
|
||||
|
||||
extract_heredoc_line(Marker, [H|T], Buffer, Counter) when H == $\t; H == $\s ->
|
||||
extract_heredoc_line(Marker, [H|T], Buffer, Counter) when ?is_horizontal_space(H) ->
|
||||
extract_heredoc_line(Marker, T, [H|Buffer], Counter + 1);
|
||||
|
||||
extract_heredoc_line(Marker, [Marker,Marker,Marker|T] = Rest, Buffer, Counter) ->
|
||||
@@ -607,9 +598,9 @@ extract_heredoc_line(Marker, [Marker,Marker,Marker|T] = Rest, Buffer, Counter) -
|
||||
extract_heredoc_line(_Marker, Rest, Buffer, _Counter) ->
|
||||
extract_heredoc_line(Rest, Buffer).
|
||||
|
||||
next_is_break([H|T]) when H == $\t; H == $\s -> next_is_break(T);
|
||||
next_is_break([$\r,$\n|T]) -> T;
|
||||
next_is_break([$\n|T]) -> T;
|
||||
next_is_break([H|T]) when ?is_horizontal_space(H) -> next_is_break(T);
|
||||
next_is_break("\r\n" ++ T) -> T;
|
||||
next_is_break("\n" ++ T) -> T;
|
||||
next_is_break([]) -> [];
|
||||
next_is_break(_) -> false.
|
||||
|
||||
@@ -625,121 +616,113 @@ merge_heredoc_extra([H|T], Buffer) ->
|
||||
merge_heredoc_extra([], Buffer) ->
|
||||
Buffer.
|
||||
|
||||
% Integers and floats
|
||||
% At this point, we are at least sure the first digit is a number.
|
||||
%% Integers and floats
|
||||
%% At this point, we are at least sure the first digit is a number.
|
||||
|
||||
% Check if we have a point followed by a number;
|
||||
%% Check if we have a point followed by a number;
|
||||
tokenize_number([$.,H|T], Acc, false) when ?is_digit(H) ->
|
||||
tokenize_number(T, [H,$.|Acc], true);
|
||||
|
||||
% Check if we have an underscore followed by a number;
|
||||
%% Check if we have an underscore followed by a number;
|
||||
tokenize_number([$_,H|T], Acc, Bool) when ?is_digit(H) ->
|
||||
tokenize_number(T, [H|Acc], Bool);
|
||||
|
||||
% Check if we have e- followed by numbers. Valid only for floats.
|
||||
%% Check if we have e- followed by numbers (valid only for floats);
|
||||
tokenize_number([$e,S,H|T], Acc, true) when ?is_digit(H), S == $+ orelse S == $- ->
|
||||
tokenize_number(T, [H,S,$e|Acc], true);
|
||||
|
||||
% Check if we have e followed by numbers. Valid only for floats.
|
||||
%% Check if we have e followed by numbers (valid only for floats);
|
||||
tokenize_number([$e,H|T], Acc, true) when ?is_digit(H) ->
|
||||
tokenize_number(T, [H,$e|Acc], true);
|
||||
|
||||
% Just numbers;
|
||||
%% Finally just numbers.
|
||||
tokenize_number([H|T], Acc, Bool) when ?is_digit(H) ->
|
||||
tokenize_number(T, [H|Acc], Bool);
|
||||
|
||||
% Cast to float...
|
||||
%% Cast to float...
|
||||
tokenize_number(Rest, Acc, true) ->
|
||||
{ Rest, list_to_float(lists:reverse(Acc)) };
|
||||
|
||||
% Or integer.
|
||||
%% Or integer.
|
||||
tokenize_number(Rest, Acc, false) ->
|
||||
{ Rest, list_to_integer(lists:reverse(Acc)) }.
|
||||
|
||||
% Hex
|
||||
tokenize_hex([H|T], Acc) when ?is_hex(H) -> tokenize_hex(T, [H|Acc]);
|
||||
tokenize_hex(Rest, Acc) -> { Rest, list_to_integer(lists:reverse(Acc), 16) }.
|
||||
|
||||
% Octal
|
||||
tokenize_octal([H|T], Acc) when ?is_octal(H) -> tokenize_octal(T, [H|Acc]);
|
||||
tokenize_octal(Rest, Acc) -> { Rest, list_to_integer(lists:reverse(Acc), 8) }.
|
||||
|
||||
% Bin
|
||||
tokenize_bin([H|T], Acc) when ?is_bin(H) -> tokenize_bin(T, [H|Acc]);
|
||||
tokenize_bin(Rest, Acc) -> { Rest, list_to_integer(lists:reverse(Acc), 2) }.
|
||||
|
||||
% Comments
|
||||
%% Comments
|
||||
|
||||
tokenize_comment("\r\n" ++ _ = Rest) -> Rest;
|
||||
tokenize_comment("\n" ++ _ = Rest) -> Rest;
|
||||
tokenize_comment([_|Rest]) -> tokenize_comment(Rest);
|
||||
tokenize_comment([]) -> [].
|
||||
|
||||
% Identifiers
|
||||
%% Atoms
|
||||
%% Handle atoms specially since they support @
|
||||
|
||||
% Tokenize identifier. At this point, the validity of
|
||||
% the first character was already verified.
|
||||
tokenize_atom([H|T], Acc, Scope) when ?is_atom(H) ->
|
||||
tokenize_atom(T, [H|Acc], Scope);
|
||||
|
||||
tokenize_identifier([H|T], Acc, Marker) when Marker == atom, H == $@;
|
||||
?is_digit(H); ?is_upcase(H); ?is_downcase(H); H == $_ ->
|
||||
tokenize_identifier(T, [H|Acc], Marker);
|
||||
tokenize_atom([H|T], Acc, Scope) when H == $?; H == $! ->
|
||||
{ T, unsafe_to_atom(lists:reverse([H|Acc]), Scope) };
|
||||
|
||||
tokenize_identifier([], Acc, _Marker) ->
|
||||
{ [], lists:reverse(Acc) };
|
||||
tokenize_atom(Rest, Acc, Scope) ->
|
||||
{ Rest, unsafe_to_atom(lists:reverse(Acc), Scope) }.
|
||||
|
||||
tokenize_identifier(Rest, Acc, _Marker) ->
|
||||
%% Identifiers
|
||||
%% At this point, the validity of the first character was already verified.
|
||||
|
||||
tokenize_identifier([H|T], Acc) when ?is_identifier(H) ->
|
||||
tokenize_identifier(T, [H|Acc]);
|
||||
|
||||
tokenize_identifier(Rest, Acc) ->
|
||||
{ Rest, lists:reverse(Acc) }.
|
||||
|
||||
% Tokenize atom identifier, which also accepts punctuated identifiers
|
||||
tokenize_atom(String, Acc, Scope) ->
|
||||
{ Rest, Identifier } = tokenize_identifier(String, Acc, atom),
|
||||
case Rest of
|
||||
[H|T] when H == $?; H == $! ->
|
||||
{ T, unsafe_to_atom(Identifier ++ [H], Scope) };
|
||||
_ ->
|
||||
{ Rest, unsafe_to_atom(Identifier, Scope) }
|
||||
end.
|
||||
%% Tokenize any identifier, handling kv, punctuated, paren, bracket and do identifiers.
|
||||
|
||||
% Tokenize any identifier, handling kv, punctuated, paren, bracket and do identifiers.
|
||||
tokenize_any_identifier(String, Line, Acc, Scope) ->
|
||||
{ Rest, Identifier } = tokenize_identifier(String, Acc, false),
|
||||
{ Rest, Identifier } = tokenize_identifier(String, Acc),
|
||||
|
||||
case Rest of
|
||||
[H,$:|T] when H == $? orelse H == $!, hd(T) /= $: ->
|
||||
Atom = unsafe_to_atom(Identifier ++ [H], Scope),
|
||||
verify_kw_and_space(Line, Atom, T, Scope),
|
||||
{ T, { kw_identifier, Line, Atom } };
|
||||
[H|T] when H == $?; H == $! ->
|
||||
Atom = unsafe_to_atom(Identifier ++ [H], Scope),
|
||||
{ T, tokenize_call_identifier(punctuated_identifier, Line, Atom, T) };
|
||||
[$:|T] when hd(T) /= $: ->
|
||||
tokenize_kw_or_call_identifier(punctuated_identifier, Line, Atom, T);
|
||||
_ ->
|
||||
Atom = unsafe_to_atom(Identifier, Scope),
|
||||
verify_kw_and_space(Line, Atom, T, Scope),
|
||||
{ T, { kw_identifier, Line, Atom } };
|
||||
_ ->
|
||||
{ Rest, tokenize_call_identifier(identifier, Line, unsafe_to_atom(Identifier, Scope), Rest) }
|
||||
tokenize_kw_or_call_identifier(identifier, Line, Atom, Rest)
|
||||
end.
|
||||
|
||||
tokenize_call_identifier(Kind, Line, Atom, Rest) ->
|
||||
case Rest of
|
||||
[$(|_] -> { paren_identifier, Line, Atom };
|
||||
[$[|_] -> { bracket_identifier, Line, Atom };
|
||||
_ ->
|
||||
case next_is_block(Rest) of
|
||||
false -> { Kind, Line, Atom };
|
||||
BlockIdentifier -> { BlockIdentifier, Line, Atom }
|
||||
end
|
||||
end.
|
||||
%% Tokenize kw or call identifier (paren | bracket | do)
|
||||
|
||||
verify_kw_and_space(_Line, _Atom, [H|_], #scope{}) when ?is_space(H) -> ok;
|
||||
verify_kw_and_space(Line, Atom, _, #scope{file=File}) ->
|
||||
io:format("~ts:~w: keyword argument ~ts: must be followed by space~n", [File, Line, Atom]).
|
||||
tokenize_kw_or_call_identifier(_Kind, Line, Atom, [$:,H|T]) when ?is_space(H) ->
|
||||
{ [H|T], { kw_identifier, Line, Atom } };
|
||||
|
||||
tokenize_kw_or_call_identifier(_Kind, Line, Atom, [$:,H|_]) when ?is_new_atom(H) ->
|
||||
{ error, { Line, "keyword argument must be followed by space after: ", atom_to_list(Atom) ++ [$:] } };
|
||||
|
||||
tokenize_kw_or_call_identifier(Kind, Line, Atom, Rest) ->
|
||||
{ Rest, check_call_identifier(Kind, Line, Atom, Rest) }.
|
||||
|
||||
%% Check if it is a call identifier (paren | bracket | do)
|
||||
|
||||
check_call_identifier(_Kind, Line, Atom, [$(|_]) -> { paren_identifier, Line, Atom };
|
||||
check_call_identifier(_Kind, Line, Atom, [$[|_]) -> { bracket_identifier, Line, Atom };
|
||||
check_call_identifier(Kind, Line, Atom, Rest) ->
|
||||
case next_is_block(Rest) of
|
||||
false -> { Kind, Line, Atom };
|
||||
BlockIdentifier -> { BlockIdentifier, Line, Atom }
|
||||
end.
|
||||
|
||||
next_is_block([Space|Tokens]) when Space == $\t; Space == $\s ->
|
||||
next_is_block(Tokens);
|
||||
|
||||
next_is_block([$d,$o,H|_]) when
|
||||
?is_digit(H); ?is_upcase(H); ?is_downcase(H); H == $_; H == $: ->
|
||||
next_is_block([$d,$o,H|_]) when ?is_identifier(H); ?is_terminator(H) ->
|
||||
false;
|
||||
|
||||
next_is_block([$d,$o|_]) ->
|
||||
@@ -751,21 +734,18 @@ next_is_block(_) ->
|
||||
add_token_with_nl(Left, [{eol,_,newline}|T]) -> [Left|T];
|
||||
add_token_with_nl(Left, T) -> [Left|T].
|
||||
|
||||
% Error handling
|
||||
%% Error handling
|
||||
|
||||
interpolation_error({ error, { Line, Message, Token } }, Extension, Args) ->
|
||||
{ error, { Line, io_lib:format("~ts" ++ Extension, [Message|Args]), Token } }.
|
||||
|
||||
% Terminators
|
||||
%% Terminators
|
||||
|
||||
string_type($") -> bin_string;
|
||||
string_type($') -> list_string.
|
||||
|
||||
sigil_terminator($() -> $);
|
||||
sigil_terminator($[) -> $];
|
||||
sigil_terminator(${) -> $};
|
||||
sigil_terminator($<) -> $>;
|
||||
sigil_terminator(O) -> O.
|
||||
handle_terminator(Rest, Line, Scope, Token, Tokens) ->
|
||||
case handle_terminator(Token, Scope) of
|
||||
{ error, _ } = Error -> Error;
|
||||
New -> tokenize(Rest, Line, New, [Token|Tokens])
|
||||
end.
|
||||
|
||||
handle_terminator(_, #scope{check_terminators=false} = Scope) ->
|
||||
Scope;
|
||||
@@ -808,6 +788,15 @@ check_terminator({ E, Line }, []) when
|
||||
check_terminator(_, Terminators) ->
|
||||
Terminators.
|
||||
|
||||
string_type($") -> bin_string;
|
||||
string_type($') -> list_string.
|
||||
|
||||
sigil_terminator($() -> $);
|
||||
sigil_terminator($[) -> $];
|
||||
sigil_terminator(${) -> $};
|
||||
sigil_terminator($<) -> $>;
|
||||
sigil_terminator(O) -> O.
|
||||
|
||||
terminator('fn') -> 'end';
|
||||
terminator('do') -> 'end';
|
||||
terminator('(') -> ')';
|
||||
@@ -815,11 +804,12 @@ terminator('[') -> ']';
|
||||
terminator('{') -> '}';
|
||||
terminator('<<') -> '>>'.
|
||||
|
||||
% Keywords check
|
||||
handle_keyword(Line, Identifier, Atom, [{ '.', _ }|_] = Tokens) ->
|
||||
%% Keywords checking
|
||||
|
||||
check_keyword(Line, Identifier, Atom, [{ '.', _ }|_] = Tokens) ->
|
||||
{ ok, [{ Identifier, Line, Atom }|Tokens] };
|
||||
|
||||
handle_keyword(Line, Identifier, Atom, Tokens) when
|
||||
check_keyword(Line, Identifier, Atom, Tokens) when
|
||||
Identifier == identifier; Identifier == do_identifier;
|
||||
Identifier == bracket_identifier; Identifier == paren_identifier ->
|
||||
case keyword(Atom) of
|
||||
@@ -834,13 +824,13 @@ handle_keyword(Line, Identifier, Atom, Tokens) when
|
||||
false -> nomatch
|
||||
end;
|
||||
|
||||
handle_keyword(_, _, _, _) -> nomatch.
|
||||
check_keyword(_, _, _, _) -> nomatch.
|
||||
|
||||
do_keyword_valid([{ Atom, _ }|_]) ->
|
||||
is_boolean(keyword(Atom));
|
||||
do_keyword_valid(_) -> true.
|
||||
|
||||
% Keywords
|
||||
% Regular keywords
|
||||
keyword('fn') -> true;
|
||||
keyword('end') -> true;
|
||||
keyword('true') -> true;
|
||||
|
||||
@@ -1,38 +0,0 @@
|
||||
-module(elixir_transform).
|
||||
-export([parse_transform/2]).
|
||||
|
||||
parse_transform(Forms, _) ->
|
||||
do_transform(Forms).
|
||||
|
||||
do_transform({ atom, Line, Atom }) ->
|
||||
case atom_to_list(Atom) of
|
||||
"Elixir." ++ _ = List ->
|
||||
Module = [case T of
|
||||
$. -> $-;
|
||||
_ -> T
|
||||
end || T <- List],
|
||||
{ atom, Line, list_to_atom(Module) };
|
||||
_ ->
|
||||
{ atom, Line, Atom }
|
||||
end;
|
||||
|
||||
do_transform({ Name, Item1 }) ->
|
||||
{ Name, do_transform(Item1) };
|
||||
|
||||
do_transform({ Name, Item1, Item2 }) ->
|
||||
{ Name, do_transform(Item1), do_transform(Item2) };
|
||||
|
||||
do_transform({ Name, Item1, Item2, Item3 }) ->
|
||||
{ Name, do_transform(Item1), do_transform(Item2), do_transform(Item3) };
|
||||
|
||||
do_transform({ Name, Item1, Item2, Item3, Item4 }) ->
|
||||
{ Name, do_transform(Item1), do_transform(Item2), do_transform(Item3), do_transform(Item4) };
|
||||
|
||||
do_transform({ Name, Item1, Item2, Item3, Item4, Item5 }) ->
|
||||
{ Name, do_transform(Item1), do_transform(Item2), do_transform(Item3), do_transform(Item4), do_transform(Item5) };
|
||||
|
||||
do_transform(List) when is_list(List) ->
|
||||
[do_transform(X) || X <- List];
|
||||
|
||||
do_transform(Other) when not is_tuple(Other) ->
|
||||
Other.
|
||||
@@ -9,14 +9,15 @@
|
||||
assert_function_scope/3, assert_module_scope/3, assert_no_guard_scope/3,
|
||||
assert_no_match_or_guard_scope/3]).
|
||||
-include("elixir.hrl").
|
||||
-compile({parse_transform, elixir_transform}).
|
||||
|
||||
forms(String, StartLine, File, Opts) ->
|
||||
try elixir_tokenizer:tokenize(String, StartLine, [{ file, File }|Opts]) of
|
||||
{ ok, Tokens } ->
|
||||
case elixir_parser:parse(Tokens) of
|
||||
try elixir_parser:parse(Tokens) of
|
||||
{ ok, Forms } -> { ok, Forms };
|
||||
{ error, { Line, _, [Error, Token] } } -> { error, { Line, Error, Token } }
|
||||
catch
|
||||
{ error, { Line, _, [Error, Token] } } -> { error, { Line, Error, Token } }
|
||||
end;
|
||||
{ error, { _, _, _ } } = Else -> Else
|
||||
catch
|
||||
@@ -35,13 +36,10 @@ translate(Forms, S) ->
|
||||
%% Those macros are "low-level". They are the basic mechanism
|
||||
%% that makes the language work and cannot be partially applied
|
||||
%% nor overwritten.
|
||||
%%
|
||||
%% =, ^, import, require and alias could be made non-special
|
||||
%% forms without causing any side effects.
|
||||
|
||||
%% Assignment operator
|
||||
|
||||
translate_each({'=', Meta, [Left, Right]}, S) ->
|
||||
translate_each({ '=', Meta, [Left, Right] }, S) ->
|
||||
assert_no_guard_scope(Meta, '=', S),
|
||||
{ TRight, SR } = translate_each(Right, S),
|
||||
{ TLeft, SL } = elixir_clauses:assigns(fun translate_each/2, Left, SR),
|
||||
@@ -64,6 +62,9 @@ translate_each({ '__block__', Meta, Args }, S) when is_list(Args) ->
|
||||
{ TArgs, NS } = translate(Args, S),
|
||||
{ { block, ?line(Meta), TArgs }, NS };
|
||||
|
||||
translate_each({ '__scope__', _Meta, [[{erl,true}],[{do,Expr}]] }, S) ->
|
||||
{ Expr, S };
|
||||
|
||||
translate_each({ '__scope__', _Meta, [[{file,File}],[{do,Expr}]] }, S) ->
|
||||
Old = S#elixir_scope.file,
|
||||
{ TExpr, TS } = translate_each(Expr, S#elixir_scope{file=File}),
|
||||
@@ -98,43 +99,12 @@ translate_each({ alias, Meta, [Ref] }, S) ->
|
||||
|
||||
translate_each({ alias, Meta, [Ref, KV] }, S) ->
|
||||
assert_no_match_or_guard_scope(Meta, alias, S),
|
||||
validate_opts(Meta, alias, [as], KV, S),
|
||||
|
||||
{ TRef, SR } = translate_each(Ref, S),
|
||||
{ TKV, ST } = translate_opts(Meta, alias, [as], no_alias_opts(KV), SR),
|
||||
|
||||
case TRef of
|
||||
{ atom, _, Old } ->
|
||||
{ New, SF } = case lists:keyfind(as, 1, KV) of
|
||||
Opt when Opt == { as, true }; Opt == false ->
|
||||
{ elixir_aliases:last(Old), SR };
|
||||
{ as, false } ->
|
||||
{ Old, SR };
|
||||
{ as, Other } ->
|
||||
{ TOther, SA } = translate_each(no_alias_expansion(Other),
|
||||
SR#elixir_scope{aliases=[],macro_aliases=[]}),
|
||||
case TOther of
|
||||
{ atom, _, Atom } -> { Atom, SA };
|
||||
_ -> syntax_error(Meta, S#elixir_scope.file,
|
||||
"invalid args for alias, expected an atom or alias as argument")
|
||||
end
|
||||
end,
|
||||
|
||||
%% Avoid creating aliases if first == last
|
||||
%% unecessarily polluting the aliases dict
|
||||
case New == Old of
|
||||
true -> { { nil, ?line(Meta) }, SF };
|
||||
false ->
|
||||
case string:tokens(atom_to_list(New), "-") of
|
||||
[_,_] -> [];
|
||||
_ -> syntax_error(Meta, S#elixir_scope.file,
|
||||
"invalid args for alias, cannot create nested alias ~s", [elixir_errors:inspect(New)])
|
||||
end,
|
||||
|
||||
{ { nil, ?line(Meta) }, SF#elixir_scope{
|
||||
aliases=orddict:store(New, Old, S#elixir_scope.aliases),
|
||||
macro_aliases=orddict:store(New, Old, S#elixir_scope.macro_aliases)
|
||||
} }
|
||||
end;
|
||||
translate_alias(Meta, true, Old, TKV, ST);
|
||||
_ ->
|
||||
syntax_error(Meta, S#elixir_scope.file, "invalid args for alias, expected an atom or alias as argument")
|
||||
end;
|
||||
@@ -143,25 +113,15 @@ translate_each({ require, Meta, [Ref] }, S) ->
|
||||
translate_each({ require, Meta, [Ref, []] }, S);
|
||||
|
||||
translate_each({ require, Meta, [Ref, KV] }, S) ->
|
||||
assert_no_match_or_guard_scope(Meta, 'require', S),
|
||||
validate_opts(Meta, require, [as], KV, S),
|
||||
assert_no_match_or_guard_scope(Meta, require, S),
|
||||
|
||||
{ TRef, SR } = translate_each(Ref, S),
|
||||
|
||||
As = case lists:keyfind(as, 1, KV) of
|
||||
false -> false;
|
||||
{ as, Value } -> Value
|
||||
end,
|
||||
{ TKV, ST } = translate_opts(Meta, require, [as], no_alias_opts(KV), SR),
|
||||
|
||||
case TRef of
|
||||
{ atom, _, Old } ->
|
||||
elixir_aliases:ensure_loaded(Meta, Old, SR),
|
||||
|
||||
SF = SR#elixir_scope{
|
||||
requires=ordsets:add_element(Old, S#elixir_scope.requires)
|
||||
},
|
||||
|
||||
translate_each({ alias, Meta, [Ref, [{ as, As }]] }, SF);
|
||||
elixir_aliases:ensure_loaded(Meta, Old, ST),
|
||||
translate_require(Meta, Old, TKV, ST);
|
||||
_ ->
|
||||
syntax_error(Meta, S#elixir_scope.file, "invalid args for require, expected an atom or alias as argument")
|
||||
end;
|
||||
@@ -172,34 +132,34 @@ translate_each({ import, Meta, [Left] }, S) ->
|
||||
translate_each({ import, Meta, [Left,Opts] }, S) when is_list(Opts) ->
|
||||
translate_each({ import, Meta, [default, Left, Opts]}, S);
|
||||
|
||||
translate_each({ import, Meta, [Selector, Left] }, S) ->
|
||||
translate_each({ import, Meta, [Selector, Left, []]}, S);
|
||||
translate_each({ import, Meta, [Left,Right] }, S) ->
|
||||
%% Second argument is ambiguous, translate it and take a peek
|
||||
case translate_each(Right, S) of
|
||||
{ { atom, _, _ }, _ } ->
|
||||
translate_each({ import, Meta, [Left, Right, []]}, S);
|
||||
_ ->
|
||||
translate_each({ import, Meta, [default, Left, Right]}, S)
|
||||
end;
|
||||
|
||||
translate_each({ import, Meta, [Left, Right, Opts] }, S) ->
|
||||
assert_no_match_or_guard_scope(Meta, 'import', S),
|
||||
translate_each({ import, Meta, [Left, Right, KV] }, S) ->
|
||||
assert_no_match_or_guard_scope(Meta, import, S),
|
||||
{ TSelector, SL } = translate_each(Left, S),
|
||||
{ TRef, SR } = translate_each(Right, SL),
|
||||
{ TKV, ST } = translate_opts(Meta, import, [as, only, except, warn], no_alias_opts(KV), SR),
|
||||
|
||||
Selector = case TSelector of
|
||||
{ atom, _, SelectorAtom } -> SelectorAtom;
|
||||
_ -> syntax_error(Meta, S#elixir_scope.file, "invalid selector for import, expected an atom")
|
||||
end,
|
||||
|
||||
Ref = case TRef of
|
||||
{ atom, _, RefAtom } -> RefAtom;
|
||||
_ -> syntax_error(Meta, S#elixir_scope.file, "invalid name for import, expected an atom or alias")
|
||||
end,
|
||||
|
||||
validate_opts(Meta, import, [as, only, except], Opts, S),
|
||||
|
||||
As = case lists:keyfind(as, 1, Opts) of
|
||||
false -> false;
|
||||
{ as, Value } -> Value
|
||||
end,
|
||||
|
||||
elixir_aliases:ensure_loaded(Meta, Ref, SR),
|
||||
SF = elixir_import:import(Meta, Ref, Opts, Selector, SR),
|
||||
translate_each({ require, Meta, [Ref, [{ as, As }]] }, SF);
|
||||
case TRef of
|
||||
{ atom, _, Old } ->
|
||||
elixir_aliases:ensure_loaded(Meta, Old, ST),
|
||||
SF = elixir_import:import(Meta, Old, TKV, Selector, ST),
|
||||
translate_require(Meta, Old, TKV, SF);
|
||||
_ ->
|
||||
syntax_error(Meta, S#elixir_scope.file, "invalid name for import, expected an atom or alias")
|
||||
end;
|
||||
|
||||
%% Pseudo variables
|
||||
|
||||
@@ -209,9 +169,12 @@ translate_each({ '__MODULE__', Meta, Atom }, S) when is_atom(Atom) ->
|
||||
translate_each({ '__FILE__', _Meta, Atom }, S) when is_atom(Atom) ->
|
||||
translate_each(S#elixir_scope.file, S);
|
||||
|
||||
translate_each({ '__DIR__', _Meta, Atom }, S) when is_atom(Atom) ->
|
||||
translate_each(filename:dirname(S#elixir_scope.file), S);
|
||||
|
||||
translate_each({ '__ENV__', Meta, Atom }, S) when is_atom(Atom) ->
|
||||
Env = elixir_scope:to_ex_env({ ?line(Meta), S }),
|
||||
{ elixir_tree_helpers:abstract_syntax(Env), S };
|
||||
{ elixir_tree_helpers:elixir_to_erl(Env), S };
|
||||
|
||||
translate_each({ '__CALLER__', Meta, Atom }, S) when is_atom(Atom) ->
|
||||
{ { var, ?line(Meta), '__CALLER__' }, S#elixir_scope{caller=true} };
|
||||
@@ -229,7 +192,7 @@ translate_each({ '__aliases__', Meta, _ } = Alias, S) ->
|
||||
Atoms = [Atom || { atom, _, Atom } <- TAliases],
|
||||
{ { atom, ?line(Meta), elixir_aliases:concat(Atoms) }, SA };
|
||||
false ->
|
||||
Args = [elixir_tree_helpers:build_simple_list(?line(Meta), TAliases)],
|
||||
Args = [elixir_tree_helpers:list_to_cons(?line(Meta), TAliases)],
|
||||
{ ?wrap_call(?line(Meta), elixir_aliases, concat, Args), SA }
|
||||
end
|
||||
end;
|
||||
@@ -239,37 +202,45 @@ translate_each({ '__aliases__', Meta, _ } = Alias, S) ->
|
||||
translate_each({ Unquote, Meta, [_|_] }, S) when Unquote == unquote; Unquote == unquote_splicing ->
|
||||
syntax_error(Meta, S#elixir_scope.file, "~p called outside quote", [Unquote]);
|
||||
|
||||
translate_each({ quote, Meta, [Left, Right] }, S) ->
|
||||
translate_each({ quote, Meta, [orddict:from_list(Left ++ Right)] }, S);
|
||||
translate_each({ quote, Meta, [Opts] }, S) when is_list(Opts) ->
|
||||
case lists:keyfind(do, 1, Opts) of
|
||||
{ do, Do } ->
|
||||
translate_each({ quote, Meta, [lists:keydelete(do, 1, Opts), [{do,Do}]] }, S);
|
||||
false ->
|
||||
syntax_error(Meta, S#elixir_scope.file, "invalid args for quote, do is missing")
|
||||
end;
|
||||
|
||||
translate_each({ quote, Meta, [T] }, S) when is_list(T) ->
|
||||
translate_each({ quote, Meta, [_] }, S) ->
|
||||
syntax_error(Meta, S#elixir_scope.file, "invalid args for quote");
|
||||
|
||||
translate_each({ quote, Meta, [KV, Do] }, S) when is_list(Do) ->
|
||||
Exprs =
|
||||
case lists:keyfind(do, 1, T) of
|
||||
case lists:keyfind(do, 1, Do) of
|
||||
{ do, E } -> E;
|
||||
false ->
|
||||
syntax_error(Meta, S#elixir_scope.file, "invalid args for quote")
|
||||
false -> syntax_error(Meta, S#elixir_scope.file, "invalid args for quote")
|
||||
end,
|
||||
|
||||
Hygiene = case lists:keyfind(hygiene, 1, T) of
|
||||
{ hygiene, true } ->
|
||||
elixir_errors:deprecation(Meta, S#elixir_scope.file, "hygiene: true is deprecated, please set it to [] instead", []),
|
||||
[];
|
||||
ValidOpts = [hygiene, context, var_context, location, line, file, unquote],
|
||||
{ TKV, ST } = translate_opts(Meta, quote, ValidOpts, KV, S),
|
||||
|
||||
Hygiene = case lists:keyfind(hygiene, 1, TKV) of
|
||||
{ hygiene, List } when is_list(List) ->
|
||||
List;
|
||||
{ hygiene, false } ->
|
||||
elixir_errors:deprecation(Meta, S#elixir_scope.file, "hygiene: false is deprecated, please set it to [vars: false] instead", []),
|
||||
[{vars,false}, {imports,false}, {aliases,false}];
|
||||
false ->
|
||||
[]
|
||||
end,
|
||||
|
||||
Vars = case lists:keyfind(var_context, 1, T) of
|
||||
Context = case lists:keyfind(var_context, 1, TKV) of
|
||||
{ var_context, VarContext } ->
|
||||
expand_var_context(Meta, VarContext, "invalid argument given for var_context in quote", S);
|
||||
elixir_errors:deprecation(Meta, S#elixir_scope.file, "var_context in quote is deprecated, please use context instead"),
|
||||
VarContext;
|
||||
false ->
|
||||
case lists:keyfind(vars, 1, Hygiene) of
|
||||
{ vars, false } -> nil;
|
||||
_ ->
|
||||
case lists:keyfind(context, 1, TKV) of
|
||||
{ context, Atom } when is_atom(Atom) ->
|
||||
Atom;
|
||||
{ context, _ } ->
|
||||
syntax_error(Meta, S#elixir_scope.file, "invalid :context for quote, expected an atom or an alias");
|
||||
false ->
|
||||
case S#elixir_scope.module of
|
||||
nil -> 'Elixir';
|
||||
Mod -> Mod
|
||||
@@ -277,36 +248,21 @@ translate_each({ quote, Meta, [T] }, S) when is_list(T) ->
|
||||
end
|
||||
end,
|
||||
|
||||
GetExpansion = fun(Key, New) ->
|
||||
case lists:keyfind(Key, 1, T) of
|
||||
{ Key, Bool } when is_boolean(Bool) ->
|
||||
elixir_errors:deprecation(Meta, S#elixir_scope.file, "the ~s option for quote is deprecated, please use hygiene: [~s: ~s] instead", [Key, New, Bool]),
|
||||
Bool;
|
||||
false ->
|
||||
not(lists:keyfind(New, 1, Hygiene) == { New, false })
|
||||
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 },
|
||||
|
||||
Aliases = GetExpansion(aliases_hygiene, aliases),
|
||||
Imports = GetExpansion(imports_hygiene, imports),
|
||||
|
||||
{ DefaultLine, DefaultFile } = case lists:keyfind(location, 1, T) of
|
||||
{ location, keep } -> { keep, keep };
|
||||
{ DefaultLine, DefaultFile } = case lists:keyfind(location, 1, TKV) of
|
||||
{ location, keep } -> { keep, keep };
|
||||
false -> { nil, nil }
|
||||
end,
|
||||
|
||||
Line = case lists:keyfind(line, 1, T) of
|
||||
Line = case lists:keyfind(line, 1, TKV) of
|
||||
{ line, LineValue } -> LineValue;
|
||||
false -> DefaultLine
|
||||
end,
|
||||
|
||||
{ TLine, SL } = case Line of
|
||||
keep -> { keep, S };
|
||||
nil -> { nil, S };
|
||||
_ -> translate_each(Line, S)
|
||||
end,
|
||||
|
||||
File = case lists:keyfind(file, 1, T) of
|
||||
File = case lists:keyfind(file, 1, TKV) of
|
||||
{ file, FileValue } -> FileValue;
|
||||
false -> DefaultFile
|
||||
end,
|
||||
@@ -316,7 +272,7 @@ translate_each({ quote, Meta, [T] }, S) when is_list(T) ->
|
||||
_ -> File
|
||||
end,
|
||||
|
||||
TExprs = if
|
||||
WExprs = if
|
||||
is_binary(TFile) ->
|
||||
{ '__scope__', Meta, [[{file,TFile}],[{do,Exprs}]] };
|
||||
File == nil ->
|
||||
@@ -325,17 +281,18 @@ translate_each({ quote, Meta, [T] }, S) when is_list(T) ->
|
||||
syntax_error(Meta, S#elixir_scope.file, "invalid args for quote, expected :file to be a binary")
|
||||
end,
|
||||
|
||||
Unquote = case lists:keyfind(unquote, 1, T) of
|
||||
Unquote = case lists:keyfind(unquote, 1, TKV) of
|
||||
{ unquote, false } -> false;
|
||||
_ -> true
|
||||
end,
|
||||
|
||||
Q = #elixir_quote{vars_hygiene=Vars, line=TLine, unquote=Unquote,
|
||||
aliases_hygiene=Aliases, imports_hygiene=Imports},
|
||||
Q = #elixir_quote{vars_hygiene=Vars, line=Line, unquote=Unquote,
|
||||
aliases_hygiene=Aliases, imports_hygiene=Imports, context=Context},
|
||||
|
||||
elixir_quote:quote(TExprs, Q, SL);
|
||||
{ TExprs, _TQ, TS } = elixir_quote:erl_quote(WExprs, Q, ST),
|
||||
{ TExprs, TS };
|
||||
|
||||
translate_each({ quote, Meta, [_] }, S) ->
|
||||
translate_each({ quote, Meta, [_, _] }, S) ->
|
||||
syntax_error(Meta, S#elixir_scope.file, "invalid args for quote");
|
||||
|
||||
translate_each({ 'alias!', _Meta, [Arg] }, S) ->
|
||||
@@ -348,7 +305,7 @@ translate_each({ 'var!', Meta, [{Name, _, Atom}, Kind] }, S) when is_atom(Name),
|
||||
translate_each({ 'var!', Meta, [Name, Kind] }, S);
|
||||
|
||||
translate_each({ 'var!', Meta, [Name, Kind] }, S) when is_atom(Name) ->
|
||||
Expanded = expand_var_context(Meta, Kind, "invalid second argument for var!", S),
|
||||
Expanded = expand_quote_context(Meta, Kind, "invalid second argument for var!", S),
|
||||
elixir_scope:translate_var(Meta, Name, Expanded, S, fun() ->
|
||||
syntax_error(Meta, S#elixir_scope.file, "expected var!(~ts) to expand to an existing variable or be a part of a match", [Name])
|
||||
end);
|
||||
@@ -433,10 +390,12 @@ translate_each({ Atom, Meta, Args } = Original, S) when is_atom(Atom) ->
|
||||
Callback = fun() ->
|
||||
case S#elixir_scope.context of
|
||||
match ->
|
||||
syntax_error(Meta, S#elixir_scope.file, "cannot invoke function ~ts/~B inside match", [Atom, length(Args)]);
|
||||
guard ->
|
||||
Arity = length(Args),
|
||||
File = S#elixir_scope.file,
|
||||
case Arity of
|
||||
0 -> syntax_error(Meta, File, "unknown variable ~ts or cannot invoke local ~ts/~B inside guard", [Atom, Atom, Arity]);
|
||||
0 -> syntax_error(Meta, File, "unknown variable ~ts or cannot invoke function ~ts/~B inside guard", [Atom, Atom, Arity]);
|
||||
_ -> syntax_error(Meta, File, "cannot invoke local ~ts/~B inside guard", [Atom, Arity])
|
||||
end;
|
||||
_ ->
|
||||
@@ -467,9 +426,23 @@ translate_each({ { '.', _, [Left, Right] }, Meta, Args } = Original, S) when is_
|
||||
|
||||
case TLeft of
|
||||
{ atom, _, Receiver } ->
|
||||
elixir_dispatch:dispatch_require(Meta, Receiver, Right, Args, umergev(SL, SR), Callback);
|
||||
elixir_dispatch:dispatch_require(Meta, Receiver, Right, Args, umergev(SL, SR), fun() ->
|
||||
case S#elixir_scope.context of
|
||||
Context when Receiver /= erlang, (Context == match) orelse (Context == guard) ->
|
||||
syntax_error(Meta, S#elixir_scope.file, "cannot invoke remote function ~ts.~ts/~B inside ~ts",
|
||||
[elixir_errors:inspect(Receiver), Right, length(Args), Context]);
|
||||
_ ->
|
||||
Callback()
|
||||
end
|
||||
end);
|
||||
_ ->
|
||||
Callback()
|
||||
case S#elixir_scope.context of
|
||||
Context when Context == match; Context == guard ->
|
||||
syntax_error(Meta, S#elixir_scope.file, "cannot invoke remote function ~ts/~B inside ~ts",
|
||||
[Right, length(Args), Context]);
|
||||
_ ->
|
||||
Callback()
|
||||
end
|
||||
end;
|
||||
Else -> Else
|
||||
end;
|
||||
@@ -482,14 +455,7 @@ translate_each({ { '.', _, [Expr] }, Meta, Args } = Original, S) ->
|
||||
{ TExpr, SE } = translate_each(Expr, S),
|
||||
case TExpr of
|
||||
{ atom, _, Atom } ->
|
||||
case atom_to_list(Atom) of
|
||||
[H|_] when (H >= $a andalso H =< $z); H == $_ ->
|
||||
elixir_errors:deprecation(Meta, S#elixir_scope.file, "the :~s.() syntax is deprecated, please use ~s() instead", [Atom, Atom]);
|
||||
_ ->
|
||||
elixir_errors:deprecation(Meta, S#elixir_scope.file, "the :~s.() syntax is deprecated", [Atom])
|
||||
end,
|
||||
|
||||
translate_each({ Atom, Meta, Args }, S);
|
||||
syntax_error(Meta, S#elixir_scope.file, "invalid function call :~ts.()", [Atom]);
|
||||
_ ->
|
||||
case elixir_partials:handle(Original, S) of
|
||||
error ->
|
||||
@@ -512,6 +478,14 @@ translate_each(Literal, S) ->
|
||||
|
||||
%% Helpers
|
||||
|
||||
%% Opts
|
||||
|
||||
translate_opts(Meta, Kind, Allowed, Opts, S) ->
|
||||
{ TOpts, TS } = translate_each(Opts, S),
|
||||
FOpts = elixir_tree_helpers:erl_to_elixir(TOpts),
|
||||
validate_opts(Meta, Kind, Allowed, FOpts, TS),
|
||||
{ FOpts, TS }.
|
||||
|
||||
validate_opts(Meta, Kind, Allowed, Opts, S) when is_list(Opts) ->
|
||||
[begin
|
||||
syntax_error(Meta, S#elixir_scope.file, "unsupported option ~s given to ~s", [Key, Kind])
|
||||
@@ -520,6 +494,69 @@ validate_opts(Meta, Kind, Allowed, Opts, S) when is_list(Opts) ->
|
||||
validate_opts(Meta, Kind, _Allowed, _Opts, S) ->
|
||||
syntax_error(Meta, S#elixir_scope.file, "invalid options for ~s, expected a keyword list", [Kind]).
|
||||
|
||||
%% Quote
|
||||
|
||||
expand_quote_context(_Meta, Atom, _Msg, _S) when is_atom(Atom) -> Atom;
|
||||
expand_quote_context(Meta, Alias, Msg, S) ->
|
||||
case translate_each(Alias, S) of
|
||||
{ { atom, _, Atom }, _ } ->
|
||||
Atom;
|
||||
_ ->
|
||||
syntax_error(Meta, S#elixir_scope.file, "~ts, expected a compile time available alias or an atom", [Msg])
|
||||
end.
|
||||
|
||||
%% Require
|
||||
|
||||
translate_require(Meta, Old, TKV, S) ->
|
||||
SF = S#elixir_scope{
|
||||
requires=ordsets:add_element(Old, S#elixir_scope.requires)
|
||||
},
|
||||
translate_alias(Meta, false, Old, TKV, SF).
|
||||
|
||||
%% Aliases
|
||||
|
||||
translate_alias(Meta, IncludeByDefault, Old, TKV, S) ->
|
||||
New = case lists:keyfind(as, 1, TKV) of
|
||||
{ as, true } ->
|
||||
elixir_aliases:last(Old);
|
||||
{ as, false } ->
|
||||
Old;
|
||||
{ as, Atom } when is_atom(Atom) ->
|
||||
Atom;
|
||||
false ->
|
||||
if
|
||||
IncludeByDefault -> elixir_aliases:last(Old);
|
||||
true -> Old
|
||||
end;
|
||||
_ ->
|
||||
syntax_error(Meta, S#elixir_scope.file,
|
||||
"invalid args for alias, expected an atom or alias in option :as")
|
||||
end,
|
||||
|
||||
case (New == Old) orelse (length(string:tokens(atom_to_list(New), ".")) == 2) of
|
||||
true -> ok;
|
||||
false -> syntax_error(Meta, S#elixir_scope.file,
|
||||
"invalid args for alias, cannot create nested alias ~s", [elixir_errors:inspect(New)])
|
||||
end,
|
||||
|
||||
{ { atom, ?line(Meta), nil }, elixir_aliases:store(Meta, New, Old, S) }.
|
||||
|
||||
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) });
|
||||
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(Other) ->
|
||||
Other.
|
||||
|
||||
%% Function
|
||||
|
||||
translate_fn(Meta, Clauses, S) ->
|
||||
Line = ?line(Meta),
|
||||
|
||||
@@ -538,7 +575,13 @@ translate_fn(Meta, Clauses, S) ->
|
||||
syntax_error(Meta, S#elixir_scope.file, "cannot mix clauses with different arities in function definition")
|
||||
end.
|
||||
|
||||
translate_local(Meta, Name, Args, #elixir_scope{local=nil} = S) ->
|
||||
%% Locals
|
||||
|
||||
translate_local(Meta, Name, Args, #elixir_scope{local=nil,module=Module,function=Function} = S) ->
|
||||
case { Name, length(Args) } of
|
||||
Function -> ok;
|
||||
Tuple -> elixir_import:record(Tuple, Module, Module)
|
||||
end,
|
||||
Line = ?line(Meta),
|
||||
{ TArgs, NS } = translate_args(Args, S),
|
||||
{ { call, Line, { atom, Line, Name }, TArgs }, NS };
|
||||
@@ -546,21 +589,6 @@ translate_local(Meta, Name, Args, #elixir_scope{local=nil} = S) ->
|
||||
translate_local(Meta, Name, Args, S) ->
|
||||
translate_each({ { '.', Meta, [S#elixir_scope.local, Name] }, Meta, Args }, S).
|
||||
|
||||
expand_var_context(_Meta, Atom, _Msg, _S) when is_atom(Atom) -> Atom;
|
||||
expand_var_context(Meta, Alias, Msg, S) ->
|
||||
case translate_each(Alias, S) of
|
||||
{ { atom, _, Atom }, _ } ->
|
||||
Atom;
|
||||
_ ->
|
||||
syntax_error(Meta, S#elixir_scope.file, "~ts, expected a compile time available alias or an atom", [Msg])
|
||||
end.
|
||||
|
||||
no_alias_expansion({ '__aliases__', Meta, [H|T] }) when (H /= 'Elixir') and is_atom(H) ->
|
||||
{ '__aliases__', Meta, ['Elixir',H|T] };
|
||||
|
||||
no_alias_expansion(Other) ->
|
||||
Other.
|
||||
|
||||
%% Translate args
|
||||
|
||||
%% Variables in arguments are not propagated from one
|
||||
|
||||
@@ -3,11 +3,9 @@
|
||||
%% as lists, condition clauses, etc.
|
||||
|
||||
-module(elixir_tree_helpers).
|
||||
-compile({parse_transform, elixir_transform}).
|
||||
-export([abstract_syntax/1, split_last/1, cons_to_list/1,
|
||||
convert_to_boolean/5, returns_boolean/1, get_line/1,
|
||||
build_list/4, build_list/5, build_simple_list/2,
|
||||
build_reverse_list/4, build_reverse_list/5, build_simple_reverse_list/2]).
|
||||
-export([elixir_to_erl/1, erl_to_elixir/1, split_last/1,
|
||||
cons_to_list/1, list_to_cons/2, list_to_cons/3,
|
||||
convert_to_boolean/5, returns_boolean/1, get_line/1]).
|
||||
-include("elixir.hrl").
|
||||
|
||||
get_line(Opts) ->
|
||||
@@ -21,53 +19,57 @@ split_last(List) -> split_last(List, []).
|
||||
split_last([H], Acc) -> { lists:reverse(Acc), H };
|
||||
split_last([H|T], Acc) -> split_last(T, [H|Acc]).
|
||||
|
||||
abstract_syntax(Tree) ->
|
||||
erl_syntax:revert(erl_syntax:abstract(Tree)).
|
||||
cons_to_list({ nil, _ }) -> [];
|
||||
cons_to_list({ cons, _, Left, Right }) -> [Left|cons_to_list(Right)].
|
||||
|
||||
cons_to_list({ cons, _, Left, { nil, _ } }) ->
|
||||
[Left];
|
||||
list_to_cons(Line, List) ->
|
||||
list_to_cons(Line, List, { nil, Line }).
|
||||
|
||||
cons_to_list({ cons, _, Left, Right }) ->
|
||||
[Left|cons_to_list(Right)].
|
||||
list_to_cons(Line, List, Tail) ->
|
||||
lists:foldr(fun(X, Acc) ->
|
||||
{ cons, Line, X, Acc }
|
||||
end, Tail, List).
|
||||
|
||||
% Build a list translating each expression and accumulating
|
||||
% vars in one pass. It uses tail-recursive form.
|
||||
%
|
||||
% It receives a function to translate each expression given
|
||||
% in Exprs, a Line used to build the List and the variables
|
||||
% scope V is passed down item by item.
|
||||
%
|
||||
% The function needs to return a tuple where the first element
|
||||
% is an erlang abstract form and the second is the new variables
|
||||
% list.
|
||||
build_list(Fun, Exprs, Line, S) ->
|
||||
build_list(Fun, Exprs, Line, S, {nil, Line}).
|
||||
%% erl <-> elixir
|
||||
|
||||
build_list(Fun, Exprs, Line, S, Tail) when is_integer(Line) ->
|
||||
build_list_each(Fun, lists:reverse(Exprs), Line, S, Tail).
|
||||
elixir_to_erl(Tree) when is_tuple(Tree) ->
|
||||
{ tuple, 0, [elixir_to_erl(X) || X <- tuple_to_list(Tree)] };
|
||||
|
||||
% Same as build_list, but the list given is in reverse other.
|
||||
build_reverse_list(Fun, Exprs, Line, S) ->
|
||||
build_list_each(Fun, Exprs, Line, S, {nil,Line}).
|
||||
elixir_to_erl(Tree) when is_list(Tree) ->
|
||||
elixir_to_erl_cons(lists:reverse(Tree), { nil, 0 });
|
||||
|
||||
build_reverse_list(Fun, Exprs, Line, S, Tail) when is_integer(Line) ->
|
||||
build_list_each(Fun, Exprs, Line, S, Tail).
|
||||
elixir_to_erl(Tree) when is_atom(Tree) ->
|
||||
{ atom, 0, Tree };
|
||||
|
||||
% Builds a simple list, without translatation, just by generating the cons-cell.
|
||||
build_simple_list(Line, Args) ->
|
||||
{ List, [] } = build_list(fun(X,Y) -> {X,Y} end, Args, Line, []),
|
||||
List.
|
||||
elixir_to_erl(Tree) when is_integer(Tree) ->
|
||||
{ integer, 0, Tree };
|
||||
|
||||
build_simple_reverse_list(Line, Args) ->
|
||||
{ List, [] } = build_reverse_list(fun(X,Y) -> {X,Y} end, Args, Line, []),
|
||||
List.
|
||||
elixir_to_erl(Tree) when is_float(Tree) ->
|
||||
{ float, 0, Tree };
|
||||
|
||||
build_list_each(_Fun, [], _Line, S, Acc) ->
|
||||
{ Acc, S };
|
||||
elixir_to_erl(Tree) when is_binary(Tree) ->
|
||||
{ bin, 0, [{ bin_element, 0, { string, 0, binary_to_list(Tree) }, default, default }] }.
|
||||
|
||||
build_list_each(Fun, [H|T], Line, S, Acc) ->
|
||||
{ Expr, NS } = Fun(H, S),
|
||||
build_list_each(Fun, T, Line, NS, { cons, Line, Expr, Acc }).
|
||||
elixir_to_erl_cons([H|T], Acc) ->
|
||||
elixir_to_erl_cons(T, { cons, 0, elixir_to_erl(H), Acc });
|
||||
|
||||
elixir_to_erl_cons([], Acc) -> Acc.
|
||||
|
||||
erl_to_elixir({ tuple, _, Args }) ->
|
||||
list_to_tuple([erl_to_elixir(X) || X <- Args]);
|
||||
|
||||
erl_to_elixir({ Kind, _, Value }) when Kind == atom; Kind == integer; Kind == float ->
|
||||
Value;
|
||||
|
||||
erl_to_elixir({ bin, _, [{ bin_element, 0, { string, 0, String }, default, default }] }) ->
|
||||
list_to_binary(String);
|
||||
|
||||
erl_to_elixir({ nil, _ }) -> [];
|
||||
|
||||
erl_to_elixir({ cons, _, Left, Right }) -> [erl_to_elixir(Left)|erl_to_elixir(Right)];
|
||||
|
||||
erl_to_elixir(Other) ->
|
||||
{ '__scope__', [], [[{erl,true}],[{do,Other}]] }.
|
||||
|
||||
%% Others
|
||||
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
-export([clauses/3, format_error/1]).
|
||||
-import(elixir_scope, [umergec/2]).
|
||||
-include("elixir.hrl").
|
||||
-compile({parse_transform, elixir_transform}).
|
||||
|
||||
clauses(Meta, Clauses, S) ->
|
||||
Catch = elixir_clauses:get_pairs(Meta, 'catch', Clauses, S),
|
||||
@@ -49,7 +48,6 @@ each_clause(Meta, { rescue, [Condition|T], Expr }, S) ->
|
||||
end
|
||||
end;
|
||||
_ ->
|
||||
validate_rescue_access(Meta, Condition, S),
|
||||
each_clause(Meta, { 'catch', [error, Condition|T], Expr }, S)
|
||||
end;
|
||||
|
||||
@@ -65,8 +63,12 @@ each_clause(Meta, {Key,_,_}, S) ->
|
||||
normalize_rescue(Meta, List, S) when is_list(List) ->
|
||||
normalize_rescue(Meta, { in, Meta, [{ '_', Meta, nil }, List] }, S);
|
||||
|
||||
%% rescue _
|
||||
normalize_rescue(_, { '_', _, Atom }, _S) when is_atom(Atom) ->
|
||||
false;
|
||||
|
||||
%% rescue var -> var in _
|
||||
normalize_rescue(_, { Name, Meta, Atom } = Rescue, S) when is_atom(Name), is_atom(Atom), Name /= '_' ->
|
||||
normalize_rescue(_, { Name, Meta, Atom } = Rescue, S) when is_atom(Name), is_atom(Atom) ->
|
||||
normalize_rescue(Meta, { in, Meta, [Rescue, { '_', Meta, nil }] }, S);
|
||||
|
||||
%% rescue var in [Exprs]
|
||||
@@ -85,22 +87,13 @@ normalize_rescue(_, { in, Meta, [Left, Right] }, S) ->
|
||||
end
|
||||
end;
|
||||
|
||||
normalize_rescue(_, { '=', Meta, [{ '__aliases__', _, _ } = Alias, { Name, _, Atom } = Var] }, S)
|
||||
when is_atom(Name) and is_atom(Atom) ->
|
||||
elixir_errors:handle_file_warning(S#elixir_scope.file, { Meta, ?MODULE, { rescue_no_match, Var, Alias } }),
|
||||
false;
|
||||
|
||||
normalize_rescue(_, { '=', Meta, [{ Name, _, Atom } = Var, { '__aliases__', _, _ } = Alias] }, S)
|
||||
when is_atom(Name) and is_atom(Atom) ->
|
||||
elixir_errors:handle_file_warning(S#elixir_scope.file, { Meta, ?MODULE, { rescue_no_match, Var, Alias } }),
|
||||
false;
|
||||
|
||||
normalize_rescue(Meta, Condition, S) ->
|
||||
case elixir_translator:translate_each(Condition, S#elixir_scope{context=match}) of
|
||||
{ { atom, _, Atom }, _ } ->
|
||||
normalize_rescue(Meta, { in, Meta, [{ '_', Meta, nil }, [Atom]] }, S);
|
||||
_ ->
|
||||
false
|
||||
elixir_errors:syntax_error(Meta, S#elixir_scope.file, "invalid rescue clause. The clause should match on an alias, "
|
||||
"a variable or be in the `var in [alias]` format")
|
||||
end.
|
||||
|
||||
%% Convert rescue clauses into guards.
|
||||
@@ -242,24 +235,6 @@ erlang_rescue_guard_for(Meta, Var, 'Elixir.ErlangError') ->
|
||||
] },
|
||||
{ 'or', Meta, [IsNotTuple, IsException] }.
|
||||
|
||||
%% Validate rescue access
|
||||
|
||||
validate_rescue_access(Meta, { '=', _, [Left, Right] }, S) ->
|
||||
validate_rescue_access(Meta, Left, S),
|
||||
validate_rescue_access(Meta, Right, S);
|
||||
|
||||
validate_rescue_access(Meta, { { '.', _, ['Elixir.Kernel', 'access'] }, _, [Element, _] }, S) ->
|
||||
case elixir_translator:translate_each(Element, S) of
|
||||
{ { atom, _, Atom }, _ } ->
|
||||
case lists:member(Atom, erlang_rescues()) of
|
||||
false -> [];
|
||||
true -> elixir_errors:syntax_error(Meta, S#elixir_scope.file, "cannot (yet) pattern match against erlang exceptions")
|
||||
end;
|
||||
_ -> []
|
||||
end;
|
||||
|
||||
validate_rescue_access(_, _, _) -> [].
|
||||
|
||||
%% Helpers
|
||||
|
||||
format_error({ rescue_no_match, Var, Alias }) ->
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
Code.require_file "../test_helper.exs", __FILE__
|
||||
Code.require_file "test_helper.exs", __DIR__
|
||||
|
||||
defmodule AccessTest do
|
||||
use ExUnit.Case, async: true
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
Code.require_file "../test_helper.exs", __FILE__
|
||||
Code.require_file "test_helper.exs", __DIR__
|
||||
|
||||
defmodule BehaviourTest do
|
||||
use ExUnit.Case, async: true
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
Code.require_file "../../test_helper.exs", __FILE__
|
||||
Code.require_file "../test_helper.exs", __DIR__
|
||||
|
||||
defmodule Binary.Chars.AtomTest do
|
||||
use ExUnit.Case, async: true
|
||||
@@ -23,7 +23,7 @@ defmodule Binary.Chars.AtomTest do
|
||||
end
|
||||
|
||||
test :alias_atom do
|
||||
assert to_binary(Foo.Bar) == "Elixir-Foo-Bar"
|
||||
assert to_binary(Foo.Bar) == "Elixir.Foo.Bar"
|
||||
end
|
||||
end
|
||||
|
||||
@@ -51,9 +51,8 @@ defmodule Binary.Chars.NumberTest do
|
||||
end
|
||||
|
||||
test :float do
|
||||
assert to_binary(1.0) == "1.00000000000000000000e+00"
|
||||
assert to_binary(1.0e10) == "1.00000000000000000000e+10"
|
||||
assert to_binary(1.0e+10) == "1.00000000000000000000e+10"
|
||||
assert to_binary(1.0) == "1.0"
|
||||
assert to_binary(1.0e10) == "10000000000.0"
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
Code.require_file "../../test_helper.exs", __FILE__
|
||||
Code.require_file "../test_helper.exs", __DIR__
|
||||
|
||||
defmodule Binary.Inspect.AtomTest do
|
||||
use ExUnit.Case, async: true
|
||||
@@ -88,9 +88,8 @@ defmodule Binary.Inspect.NumberTest do
|
||||
|
||||
test :float do
|
||||
assert inspect(1.0) == "1.0"
|
||||
assert inspect(1.0e10) == "1.0e10"
|
||||
assert inspect(1.0e+10) == "1.0e10"
|
||||
assert inspect(1.0e-10) == "1.0e-10"
|
||||
assert inspect(1.0e10) == "10000000000.0"
|
||||
assert inspect(1.0e-10) == "0.0000000001"
|
||||
end
|
||||
end
|
||||
|
||||
@@ -131,6 +130,12 @@ defmodule Binary.Inspect.TupleTest do
|
||||
assert inspect(RuntimeError.new) == "RuntimeError[message: \"runtime error\"]"
|
||||
end
|
||||
|
||||
defrecord :something, [:a, :b]
|
||||
|
||||
test :non_module_record do
|
||||
assert inspect(:something.new) == ":something[a: nil, b: nil]"
|
||||
end
|
||||
|
||||
test :empty do
|
||||
assert inspect({}) == "{}"
|
||||
end
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
Code.require_file "../test_helper.exs", __FILE__
|
||||
Code.require_file "test_helper.exs", __DIR__
|
||||
|
||||
defmodule Binary.LiteralTest do
|
||||
use ExUnit.Case, async: true
|
||||
@@ -98,6 +98,14 @@ bar
|
||||
assert <<0,0,0,106,0,0,0,111,0,0,0,115,0,0,0,101>> == << 'jose' :: utf32 >>
|
||||
end
|
||||
|
||||
@binary "new "
|
||||
@charlist 'old '
|
||||
|
||||
test :bitsyntax_with_expansion do
|
||||
assert <<@binary, "world">> == "new world"
|
||||
assert <<@charlist, "world">> == "old world"
|
||||
end
|
||||
|
||||
test :bitsyntax_translation do
|
||||
refb = "sample"
|
||||
sec_data = "another"
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
Code.require_file "../test_helper.exs", __FILE__
|
||||
Code.require_file "test_helper.exs", __DIR__
|
||||
|
||||
defmodule Bitwise.FunctionsTest do
|
||||
use ExUnit.Case, async: true
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
Code.require_file "../test_helper.exs", __FILE__
|
||||
Code.require_file "test_helper.exs", __DIR__
|
||||
|
||||
defmodule CharListTest do
|
||||
use ExUnit.Case, async: true
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
Code.require_file "../test_helper.exs", __FILE__
|
||||
Code.require_file "test_helper.exs", __DIR__
|
||||
|
||||
defmodule CodeTest do
|
||||
use ExUnit.Case, async: true
|
||||
@@ -14,17 +14,17 @@ defmodule CodeTest do
|
||||
|
||||
Code.eval_quoted contents, [], file: "sample.ex", line: 13
|
||||
|
||||
test :eval do
|
||||
assert Code.eval("1 + 2") == { 3, [] }
|
||||
assert { 3, _ } = Code.eval("a + b", [a: 1, b: 2], __ENV__.location)
|
||||
test :eval_string do
|
||||
assert Code.eval_string("1 + 2") == { 3, [] }
|
||||
assert { 3, _ } = Code.eval_string("a + b", [a: 1, b: 2], __ENV__.location)
|
||||
end
|
||||
|
||||
test :eval_with_scope do
|
||||
assert Code.eval("one", [], delegate_locals_to: __MODULE__) == { 1, [] }
|
||||
assert Code.eval_string("one", [], delegate_locals_to: __MODULE__) == { 1, [] }
|
||||
end
|
||||
|
||||
test :eval_with_requires do
|
||||
assert Code.eval("Kernel.if true, do: :ok", [], requires: [Z, Kernel]) == { :ok, [] }
|
||||
assert Code.eval_string("Kernel.if true, do: :ok", [], requires: [Z, Kernel]) == { :ok, [] }
|
||||
end
|
||||
|
||||
test :eval_quoted do
|
||||
@@ -39,21 +39,21 @@ defmodule CodeTest do
|
||||
|
||||
test :require do
|
||||
Code.require_file fixture_path("code_sample.exs")
|
||||
assert fixture_path("code_sample.exs") inlist Code.loaded_files
|
||||
assert fixture_path("code_sample.exs") in Code.loaded_files
|
||||
assert Code.require_file(fixture_path("code_sample.exs")) == nil
|
||||
|
||||
Code.unload_files [fixture_path("code_sample.exs")]
|
||||
refute fixture_path("code_sample.exs") inlist Code.loaded_files
|
||||
refute fixture_path("code_sample.exs") in Code.loaded_files
|
||||
assert Code.require_file(fixture_path("code_sample.exs")) != nil
|
||||
end
|
||||
|
||||
test :path_manipulation do
|
||||
path = Path.expand("../binary", __FILE__)
|
||||
path = Path.join(__DIR__, "binary")
|
||||
Code.prepend_path path
|
||||
assert binary_to_list(path) inlist :code.get_path
|
||||
assert binary_to_list(path) in :code.get_path
|
||||
|
||||
Code.delete_path path
|
||||
refute binary_to_list(path) inlist :code.get_path
|
||||
refute binary_to_list(path) in :code.get_path
|
||||
end
|
||||
|
||||
test :file do
|
||||
@@ -61,8 +61,7 @@ defmodule CodeTest do
|
||||
end
|
||||
|
||||
test :string_to_ast do
|
||||
assert Code.string_to_ast("1 + 2") == { :ok, quote hygiene: [imports: false], line: 1, do: 1 + 2 }
|
||||
assert Code.string_to_ast("1 + 2; 3 + 4") == { :ok, quote hygiene: [imports: false], line: 1, do: (1 + 2; 3 + 4) }
|
||||
assert Code.string_to_ast("1 + 2") == { :ok, { :+, [line: 1], [1, 2] } }
|
||||
assert { :error, _ } = Code.string_to_ast("a.1")
|
||||
end
|
||||
|
||||
@@ -72,7 +71,7 @@ defmodule CodeTest do
|
||||
end
|
||||
|
||||
test :string_to_ast! do
|
||||
assert Code.string_to_ast!("1 + 2") == quote hygiene: [imports: false], line: 1, do: 1 + 2
|
||||
assert Code.string_to_ast!("1 + 2") == { :+, [line: 1], [1, 2] }
|
||||
|
||||
assert_raise SyntaxError, fn ->
|
||||
Code.string_to_ast!("a.1")
|
||||
@@ -84,17 +83,7 @@ defmodule CodeTest do
|
||||
end
|
||||
|
||||
test :compile_source do
|
||||
compile = __MODULE__.__info__(:compile)
|
||||
|
||||
# Erlang has a bug that does not allow us to set the source
|
||||
# when compiling forms. In such cases, the source will be
|
||||
# under compile option. This is fixed and future Erlang
|
||||
# version will return the proper source always (source2).
|
||||
options = :proplists.get_value(:options, compile, [])
|
||||
source1 = :proplists.get_value(:source, options, nil)
|
||||
source2 = :proplists.get_value(:source, compile, nil)
|
||||
|
||||
assert (source1 || source2) == binary_to_list(__FILE__)
|
||||
assert __MODULE__.__info__(:compile)[:source] == binary_to_list(__FILE__)
|
||||
end
|
||||
|
||||
test :compile_info_returned_with_source_accessible_through_keyword_module do
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
Code.require_file "../test_helper.exs", __FILE__
|
||||
Code.require_file "test_helper.exs", __DIR__
|
||||
|
||||
defmodule DictTest.Common do
|
||||
defmacro __using__(module) do
|
||||
@@ -10,7 +10,7 @@ defmodule DictTest.Common do
|
||||
defmacrop dicts_equal(actual, expected) do
|
||||
quote do
|
||||
cmp = fn { k1, _ }, { k2, _ } -> k1 < k2 end
|
||||
Enum.sort(expected, cmp) == Enum.sort(actual, cmp)
|
||||
Enum.sort(unquote(expected), cmp) == Enum.sort(unquote(actual), cmp)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -43,11 +43,17 @@ defmodule DictTest.Common do
|
||||
assert "default" == Dict.get(empty_dict, "first_key", "default")
|
||||
end
|
||||
|
||||
test :get! do
|
||||
assert 1 == Dict.get!(new_dict, "first_key")
|
||||
assert 2 == Dict.get!(new_dict, "second_key")
|
||||
test :fetch do
|
||||
assert { :ok, 1 } == Dict.fetch(new_dict, "first_key")
|
||||
assert { :ok, 2 } == Dict.fetch(new_dict, "second_key")
|
||||
assert :error == Dict.fetch(new_dict, "other_key")
|
||||
end
|
||||
|
||||
test :fetch! do
|
||||
assert 1 == Dict.fetch!(new_dict, "first_key")
|
||||
assert 2 == Dict.fetch!(new_dict, "second_key")
|
||||
assert_raise KeyError, fn ->
|
||||
Dict.get!(new_dict, "other_key")
|
||||
Dict.fetch!(new_dict, "other_key")
|
||||
end
|
||||
end
|
||||
|
||||
@@ -132,6 +138,71 @@ defmodule DictTest.Common do
|
||||
assert "..." == Dict.get dict, "non-existent"
|
||||
end
|
||||
|
||||
test :pop do
|
||||
{v, actual} = Dict.pop(new_dict, "first_key")
|
||||
assert 1 == v
|
||||
assert dicts_equal actual, Dict.delete(new_dict, "first_key")
|
||||
|
||||
{v, actual} = Dict.pop(new_dict, "second_key")
|
||||
assert 2 == v
|
||||
assert dicts_equal actual, Dict.delete(new_dict, "second_key")
|
||||
|
||||
{v, actual} = Dict.pop(new_dict, "other_key")
|
||||
assert nil == v
|
||||
assert dicts_equal actual, new_dict
|
||||
|
||||
{v, actual} = Dict.pop(empty_dict, "first_key", "default")
|
||||
assert "default" == v
|
||||
assert dicts_equal actual, empty_dict
|
||||
|
||||
{v, actual} = Dict.pop(new_dict, "other_key", "default")
|
||||
assert "default" == v
|
||||
assert dicts_equal actual, new_dict
|
||||
end
|
||||
|
||||
test :split do
|
||||
split_keys = []
|
||||
{take, drop} = Dict.split(new_dict, split_keys)
|
||||
assert dicts_equal take, Dict.empty(new_dict)
|
||||
assert dicts_equal drop, new_dict
|
||||
|
||||
split_keys = ["unknown_key"]
|
||||
{take, drop} = Dict.split(new_dict, split_keys)
|
||||
assert dicts_equal take, Dict.empty(new_dict)
|
||||
assert dicts_equal drop, new_dict
|
||||
|
||||
split_keys = ["first_key", "second_key", "unknown_key"]
|
||||
{take, drop} = Dict.split(new_dict, split_keys)
|
||||
first_val = Dict.get(new_dict, "first_key")
|
||||
second_val = Dict.get(new_dict, "second_key")
|
||||
take_expected = Dict.empty(new_dict)
|
||||
take_expected = Dict.put(take_expected, "first_key", first_val)
|
||||
take_expected = Dict.put(take_expected, "second_key", second_val)
|
||||
drop_expected = new_dict
|
||||
drop_expected = Dict.delete(drop_expected, "first_key")
|
||||
drop_expected = Dict.delete(drop_expected, "second_key")
|
||||
assert dicts_equal take, take_expected
|
||||
assert dicts_equal drop, drop_expected
|
||||
end
|
||||
|
||||
test :take do
|
||||
result = Dict.take(new_dict, ["unknown_key"])
|
||||
assert dicts_equal result, Dict.empty(new_dict)
|
||||
|
||||
result = Dict.take(new_dict, ["first_key"])
|
||||
first_val = Dict.get(new_dict, "first_key")
|
||||
expected = Dict.put(Dict.empty(new_dict), "first_key", first_val)
|
||||
assert dicts_equal result, expected
|
||||
end
|
||||
|
||||
test :drop do
|
||||
result = Dict.drop(new_dict, ["unknown_key"])
|
||||
assert dicts_equal result, new_dict
|
||||
|
||||
result = Dict.drop(new_dict, ["first_key"])
|
||||
assert dicts_equal result, Dict.delete(new_dict, "first_key")
|
||||
end
|
||||
|
||||
test :empty do
|
||||
assert empty_dict == Dict.empty new_dict
|
||||
end
|
||||
@@ -145,6 +216,15 @@ defmodule DictTest.Common do
|
||||
defp new_dict(list, transform) do
|
||||
unquote(module).new list, transform
|
||||
end
|
||||
|
||||
test :equal? do
|
||||
dict1 = HashDict.new(a: 2, b: 3, f: 5, c: 123)
|
||||
dict2 = ListDict.new(a: 2, b: 3, f: 5, c: 123)
|
||||
assert Dict.equal?(dict1, dict2)
|
||||
|
||||
dict2 = Dict.put(dict2, :a, 3)
|
||||
refute Dict.equal?(dict1, dict2)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -154,5 +234,5 @@ defmodule Dict.HashDictTest do
|
||||
end
|
||||
|
||||
defmodule Dict.ListTest do
|
||||
use DictTest.Common, List.Dict
|
||||
use DictTest.Common, ListDict
|
||||
end
|
||||
|
||||
@@ -1,8 +1,32 @@
|
||||
Code.require_file "../test_helper.exs", __FILE__
|
||||
Code.require_file "test_helper.exs", __DIR__
|
||||
|
||||
defmodule EnumTest.List do
|
||||
use ExUnit.Case, async: true
|
||||
|
||||
test :empty? do
|
||||
assert Enum.empty?([])
|
||||
refute Enum.empty?([1,2,3])
|
||||
refute Enum.empty?(1..3)
|
||||
end
|
||||
|
||||
test :member? do
|
||||
assert Enum.member?([1, 2, 3], 2)
|
||||
refute Enum.member?([], 0)
|
||||
refute Enum.member?([1, 2, 3], 0)
|
||||
assert Enum.member?(1..3, 2)
|
||||
refute Enum.member?(1..3, 0)
|
||||
end
|
||||
|
||||
test :count do
|
||||
assert Enum.count([1,2,3]) == 3
|
||||
assert Enum.count([]) == 0
|
||||
end
|
||||
|
||||
test :count_fun do
|
||||
assert Enum.count([1,2,3], fn(x) -> rem(x, 2) == 0 end) == 1
|
||||
assert Enum.count([], fn(x) -> rem(x, 2) == 0 end) == 0
|
||||
end
|
||||
|
||||
test :all? do
|
||||
assert Enum.all?([2,4,6], fn(x) -> rem(x, 2) == 0 end)
|
||||
refute Enum.all?([2,3,4], fn(x) -> rem(x, 2) == 0 end)
|
||||
@@ -23,24 +47,21 @@ defmodule EnumTest.List do
|
||||
refute Enum.any?([])
|
||||
end
|
||||
|
||||
test :at! do
|
||||
assert Enum.at!([2,4,6], 0) == 2
|
||||
assert Enum.at!([2,4,6], 2) == 6
|
||||
test :at do
|
||||
assert Enum.at([2,4,6], 0) == 2
|
||||
assert Enum.at([2,4,6], 2) == 6
|
||||
assert Enum.at([2,4,6], 4) == nil
|
||||
assert Enum.at([2,4,6], 4, :none) == :none
|
||||
end
|
||||
|
||||
test :fetch! do
|
||||
assert Enum.fetch!([2,4,6], 0) == 2
|
||||
assert Enum.fetch!([2,4,6], 2) == 6
|
||||
assert_raise Enum.OutOfBoundsError, fn ->
|
||||
Enum.at!([2,4,6], 4)
|
||||
Enum.fetch!([2,4,6], 4)
|
||||
end
|
||||
end
|
||||
|
||||
test :count do
|
||||
assert Enum.count([1,2,3]) == 3
|
||||
assert Enum.count([]) == 0
|
||||
end
|
||||
|
||||
test :count_fun do
|
||||
assert Enum.count([1,2,3], fn(x) -> rem(x, 2) == 0 end) == 1
|
||||
assert Enum.count([], fn(x) -> rem(x, 2) == 0 end) == 0
|
||||
end
|
||||
|
||||
test :drop do
|
||||
assert Enum.drop([1,2,3], 0) == [1,2,3]
|
||||
assert Enum.drop([1,2,3], 1) == [2,3]
|
||||
@@ -75,11 +96,6 @@ defmodule EnumTest.List do
|
||||
assert Enum.find_index([2,3,4], fn(x) -> rem(x, 2) == 1 end) == 1
|
||||
end
|
||||
|
||||
test :empty? do
|
||||
assert Enum.empty?([])
|
||||
refute Enum.empty?([1,2,3])
|
||||
end
|
||||
|
||||
test :each do
|
||||
try do
|
||||
assert Enum.each([], fn(x) -> x end) == :ok
|
||||
@@ -97,6 +113,12 @@ defmodule EnumTest.List do
|
||||
end
|
||||
end
|
||||
|
||||
test :fetch do
|
||||
assert Enum.fetch([2,4,6], 0) == { :ok, 2 }
|
||||
assert Enum.fetch([2,4,6], 2) == { :ok, 6 }
|
||||
assert Enum.fetch([2,4,6], 4) == :error
|
||||
end
|
||||
|
||||
test :first do
|
||||
assert Enum.first([]) == nil
|
||||
assert Enum.first([1,2,3]) == 1
|
||||
@@ -147,6 +169,14 @@ defmodule EnumTest.List do
|
||||
assert Enum.map_join([1,2,3], ' = ', &1 * 2) == '2 = 4 = 6'
|
||||
end
|
||||
|
||||
test :join_empty do
|
||||
fun = fn (acc, _) -> acc end
|
||||
assert Enum.join(fun, ".") == ""
|
||||
assert Enum.map_join(fun, ".", &1 + 0) == ""
|
||||
assert Enum.join(fun, '.') == ''
|
||||
assert Enum.map_join(fun, '.', &1 + 0) == ''
|
||||
end
|
||||
|
||||
test :map do
|
||||
assert Enum.map([], fn x -> x * 2 end) == []
|
||||
assert Enum.map([1,2,3], fn x -> x * 2 end) == [2,4,6]
|
||||
@@ -209,28 +239,21 @@ defmodule EnumTest.List do
|
||||
|
||||
test :take_does_not_consume_next_without_a_need do
|
||||
import PathHelpers
|
||||
iterator = File.iterator!(fixture_path("one-liner.txt"))
|
||||
|
||||
try do
|
||||
File.open!(fixture_path("one-liner.txt"), [], fn file ->
|
||||
iterator = File.iterator(file)
|
||||
assert Enum.take(iterator, 1) == ["ONE"]
|
||||
assert Enum.take(iterator, 5) == []
|
||||
after
|
||||
File.close(iterator)
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
test :take_with_no_item_works_as_no_op do
|
||||
import PathHelpers
|
||||
iterator = File.iterator!(fixture_path("one-liner.txt"))
|
||||
|
||||
try do
|
||||
assert Enum.take(iterator, 0) == []
|
||||
assert Enum.take(iterator, 0) == []
|
||||
assert Enum.take(iterator, 0) == []
|
||||
assert Enum.take(iterator, 0) == []
|
||||
after
|
||||
File.close(iterator)
|
||||
end
|
||||
assert Enum.take(iterator, 0) == []
|
||||
assert Enum.take(iterator, 0) == []
|
||||
assert Enum.take(iterator, 0) == []
|
||||
assert Enum.take(iterator, 0) == []
|
||||
end
|
||||
|
||||
test :take_while do
|
||||
@@ -240,8 +263,14 @@ defmodule EnumTest.List do
|
||||
assert Enum.take_while([], fn(_) -> true end) == []
|
||||
end
|
||||
|
||||
test :to_list do
|
||||
assert Enum.to_list([]) == []
|
||||
assert Enum.to_list(1 .. 3) == [1, 2, 3]
|
||||
end
|
||||
|
||||
test :uniq do
|
||||
assert Enum.uniq([1,2,3,2,1]) == [1,2,3]
|
||||
assert Enum.uniq([1,2,3,2,1], fn x -> x end) == [1,2,3]
|
||||
end
|
||||
|
||||
test :zip do
|
||||
@@ -252,6 +281,34 @@ defmodule EnumTest.List do
|
||||
assert Enum.zip([1], []) == [{ 1, nil }]
|
||||
assert Enum.zip([], []) == []
|
||||
end
|
||||
|
||||
test :max do
|
||||
assert Enum.max([1]) == 1
|
||||
assert Enum.max([1,2,3]) == 3
|
||||
assert Enum.max([1,[],:a,{}]) == []
|
||||
assert_raise Enum.EmptyError, fn ->
|
||||
assert Enum.max([])
|
||||
end
|
||||
|
||||
assert Enum.max(["a", "aa", "aaa"], fn(x) -> String.length(x) end) == "aaa"
|
||||
assert_raise Enum.EmptyError, fn ->
|
||||
Enum.max([], fn(x) -> String.length(x) end)
|
||||
end
|
||||
end
|
||||
|
||||
test :min do
|
||||
assert Enum.min([1]) == 1
|
||||
assert Enum.min([1,2,3]) == 1
|
||||
assert Enum.min([[],:a,{}]) == :a
|
||||
assert_raise Enum.EmptyError, fn ->
|
||||
assert Enum.min([])
|
||||
end
|
||||
|
||||
assert Enum.min(["a", "aa", "aaa"], fn(x) -> String.length(x) end) == "a"
|
||||
assert_raise Enum.EmptyError, fn ->
|
||||
Enum.min([], fn(x) -> String.length(x) end)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defmodule EnumTest.Range do
|
||||
@@ -280,18 +337,18 @@ defmodule EnumTest.Range do
|
||||
assert Enum.any?(range)
|
||||
end
|
||||
|
||||
test :at! do
|
||||
assert Enum.at!(2..6, 0) == 2
|
||||
assert Enum.at!(2..6, 4) == 6
|
||||
assert Enum.at!(-2..-6, 0) == -2
|
||||
assert Enum.at!(-2..-6, 4) == -6
|
||||
test :fetch! do
|
||||
assert Enum.fetch!(2..6, 0) == 2
|
||||
assert Enum.fetch!(2..6, 4) == 6
|
||||
assert Enum.fetch!(-2..-6, 0) == -2
|
||||
assert Enum.fetch!(-2..-6, 4) == -6
|
||||
|
||||
assert_raise Enum.OutOfBoundsError, fn ->
|
||||
assert Enum.at!(2..6, 8)
|
||||
assert Enum.fetch!(2..6, 8)
|
||||
end
|
||||
|
||||
assert_raise Enum.OutOfBoundsError, fn ->
|
||||
assert Enum.at!(-2..-6, 8)
|
||||
assert Enum.fetch!(-2..-6, 8)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -533,6 +590,7 @@ defmodule EnumTest.Range do
|
||||
|
||||
test :uniq do
|
||||
assert Enum.uniq(1..3) == [1,2,3]
|
||||
assert Enum.uniq(1..3, fn x -> x end) == [1,2,3]
|
||||
end
|
||||
|
||||
test :zip do
|
||||
@@ -548,6 +606,24 @@ defmodule EnumTest.Range do
|
||||
assert Enum.zip(1..4, 1..2) == [{1, 1}, {2, 2}, {3, nil}, {4, nil}]
|
||||
assert Enum.zip(1..2, 1..4) == [{1, 1}, {2, 2}]
|
||||
end
|
||||
|
||||
test :max do
|
||||
assert Enum.max(1..1) == 1
|
||||
assert Enum.max(1..3) == 3
|
||||
assert Enum.max(3..1) == 3
|
||||
|
||||
assert Enum.max(1..1, fn(x) -> :math.pow(-2, x) end) == 1
|
||||
assert Enum.max(1..3, fn(x) -> :math.pow(-2, x) end) == 2
|
||||
end
|
||||
|
||||
test :min do
|
||||
assert Enum.min([1]) == 1
|
||||
assert Enum.min([1,2,3]) == 1
|
||||
assert Enum.min([[],:a,{}]) == :a
|
||||
|
||||
assert Enum.min(1..1, fn(x) -> :math.pow(-2, x) end) == 1
|
||||
assert Enum.min(1..3, fn(x) -> :math.pow(-2, x) end) == 3
|
||||
end
|
||||
end
|
||||
|
||||
defmodule EnumTest.Others do
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
Code.require_file "../test_helper.exs", __FILE__
|
||||
Code.require_file "test_helper.exs", __DIR__
|
||||
|
||||
defmodule Kernel.ExceptionTest do
|
||||
use ExUnit.Case, async: true
|
||||
@@ -10,32 +10,32 @@ defmodule Kernel.ExceptionTest do
|
||||
refute is_exception(a_list)
|
||||
end
|
||||
|
||||
test :format_entry_with_no_file_or_line do
|
||||
assert Exception.format_entry({Foo, :bar, [1, 2, 3], []}) == "Foo.bar(1, 2, 3)"
|
||||
assert Exception.format_entry({Foo, :bar, [], []}) == "Foo.bar()"
|
||||
assert Exception.format_entry({Foo, :bar, 1, []}) == "Foo.bar/1"
|
||||
test :format_stacktrace_entry_with_no_file_or_line do
|
||||
assert Exception.format_stacktrace_entry({Foo, :bar, [1, 2, 3], []}) == "Foo.bar(1, 2, 3)"
|
||||
assert Exception.format_stacktrace_entry({Foo, :bar, [], []}) == "Foo.bar()"
|
||||
assert Exception.format_stacktrace_entry({Foo, :bar, 1, []}) == "Foo.bar/1"
|
||||
end
|
||||
|
||||
test :format_entry_with_file_and_line do
|
||||
assert Exception.format_entry({Foo, :bar, [], [file: 'file.ex', line: 10]}) == "file.ex:10: Foo.bar()"
|
||||
assert Exception.format_entry({Foo, :bar, [1, 2, 3], [file: 'file.ex', line: 10]}) == "file.ex:10: Foo.bar(1, 2, 3)"
|
||||
assert Exception.format_entry({Foo, :bar, 1, [file: 'file.ex', line: 10]}) == "file.ex:10: Foo.bar/1"
|
||||
test :format_stacktrace_entry_with_file_and_line do
|
||||
assert Exception.format_stacktrace_entry({Foo, :bar, [], [file: 'file.ex', line: 10]}) == "file.ex:10: Foo.bar()"
|
||||
assert Exception.format_stacktrace_entry({Foo, :bar, [1, 2, 3], [file: 'file.ex', line: 10]}) == "file.ex:10: Foo.bar(1, 2, 3)"
|
||||
assert Exception.format_stacktrace_entry({Foo, :bar, 1, [file: 'file.ex', line: 10]}) == "file.ex:10: Foo.bar/1"
|
||||
end
|
||||
|
||||
test :format_entry_with_file_and_line_and_cwd do
|
||||
assert Exception.format_entry({Foo, :bar, [], [file: '/foo/file.ex', line: 10]}, "/foo") == "file.ex:10: Foo.bar()"
|
||||
test :format_stacktrace_entry_with_file_and_line_and_cwd do
|
||||
assert Exception.format_stacktrace_entry({Foo, :bar, [], [file: '/foo/file.ex', line: 10]}, "/foo") == "file.ex:10: Foo.bar()"
|
||||
end
|
||||
|
||||
test :format_entry_with_file_no_line do
|
||||
assert Exception.format_entry({Foo, :bar, [], [file: 'file.ex']}) == "file.ex: Foo.bar()"
|
||||
assert Exception.format_entry({Foo, :bar, [], [file: 'file.ex', line: 0]}) == "file.ex: Foo.bar()"
|
||||
assert Exception.format_entry({Foo, :bar, [1, 2, 3], [file: 'file.ex']}) == "file.ex: Foo.bar(1, 2, 3)"
|
||||
assert Exception.format_entry({Foo, :bar, 1, [file: 'file.ex']}) == "file.ex: Foo.bar/1"
|
||||
test :format_stacktrace_entry_with_file_no_line do
|
||||
assert Exception.format_stacktrace_entry({Foo, :bar, [], [file: 'file.ex']}) == "file.ex: Foo.bar()"
|
||||
assert Exception.format_stacktrace_entry({Foo, :bar, [], [file: 'file.ex', line: 0]}) == "file.ex: Foo.bar()"
|
||||
assert Exception.format_stacktrace_entry({Foo, :bar, [1, 2, 3], [file: 'file.ex']}) == "file.ex: Foo.bar(1, 2, 3)"
|
||||
assert Exception.format_stacktrace_entry({Foo, :bar, 1, [file: 'file.ex']}) == "file.ex: Foo.bar/1"
|
||||
end
|
||||
|
||||
test :format_entry_with_fun do
|
||||
assert Exception.format_entry({fn(x) -> x end, [1], []}) =~ %r"\(1\)"
|
||||
assert Exception.format_entry({fn(x, y) -> { x, y } end, 2, []}) =~ %r"/2"
|
||||
test :format_stacktrace_entry_with_fun do
|
||||
assert Exception.format_stacktrace_entry({fn(x) -> x end, [1], []}) =~ %r"\(1\)"
|
||||
assert Exception.format_stacktrace_entry({fn(x, y) -> { x, y } end, 2, []}) =~ %r"/2"
|
||||
end
|
||||
|
||||
test :format_module_function_arity do
|
||||
@@ -62,15 +62,11 @@ defmodule Kernel.ExceptionTest do
|
||||
test :undefined_function_message do
|
||||
assert UndefinedFunctionError.new.message == "undefined function"
|
||||
assert UndefinedFunctionError.new(module: Foo, function: :bar, arity: 1).message == "undefined function: Foo.bar/1"
|
||||
assert UndefinedFunctionError.new(module: Foo, function: :bar, arity: []).message == "undefined function: Foo.bar/0"
|
||||
assert UndefinedFunctionError.new(module: :foo, function: :bar, arity: []).message == "undefined function: :foo.bar/0"
|
||||
end
|
||||
|
||||
test :function_clause_message do
|
||||
assert FunctionClauseError.new.message == "no function clause matches"
|
||||
assert FunctionClauseError.new(module: Foo, function: :bar, arity: 1).message == "no function clause matching: Foo.bar/1"
|
||||
assert FunctionClauseError.new(module: Foo, function: :bar, arity: []).message == "no function clause matching: Foo.bar()"
|
||||
assert FunctionClauseError.new(module: :foo, function: :bar, arity: []).message == "no function clause matching: :foo.bar()"
|
||||
assert FunctionClauseError.new(module: Foo, function: :bar, arity: 1).message == "no function clause matching in Foo.bar/1"
|
||||
end
|
||||
|
||||
test :erlang_error_message do
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
Code.require_file "../test_helper.exs", __FILE__
|
||||
Code.require_file "test_helper.exs", __DIR__
|
||||
|
||||
defmodule FileTest do
|
||||
import PathHelpers
|
||||
@@ -187,9 +187,9 @@ defmodule FileTest do
|
||||
|
||||
{ :ok, files } = File.cp_r(src, dest)
|
||||
assert length(files) == 8
|
||||
assert tmp_path("tmp/cp_r/a") inlist files
|
||||
assert tmp_path("tmp/cp_r/c") inlist files
|
||||
assert tmp_path("tmp/cp_r/a/1.txt") inlist files
|
||||
assert tmp_path("tmp/cp_r/a") in files
|
||||
assert tmp_path("tmp/cp_r/c") in files
|
||||
assert tmp_path("tmp/cp_r/a/1.txt") in files
|
||||
|
||||
assert { :ok, 'certainly/invalid' } = :file.read_link(tmp_path("tmp/cp_r/c"))
|
||||
|
||||
@@ -399,16 +399,16 @@ defmodule FileTest do
|
||||
|
||||
test :ls do
|
||||
{ :ok, value } = File.ls(fixture_path)
|
||||
assert List.member?(value, "code_sample.exs")
|
||||
assert List.member?(value, "file.txt")
|
||||
assert "code_sample.exs" in value
|
||||
assert "file.txt" in value
|
||||
|
||||
{ :error, :enoent } = File.ls(fixture_path("non-existent-subdirectory"))
|
||||
end
|
||||
|
||||
test :ls! do
|
||||
value = File.ls!(fixture_path)
|
||||
assert List.member?(value, "code_sample.exs")
|
||||
assert List.member?(value, "file.txt")
|
||||
assert "code_sample.exs" in value
|
||||
assert "file.txt" in value
|
||||
|
||||
assert_raise File.Error, fn ->
|
||||
File.ls!(fixture_path("non-existent-subdirectory"))
|
||||
@@ -777,8 +777,8 @@ defmodule FileTest do
|
||||
|
||||
{ :ok, files } = File.rm_rf(fixture)
|
||||
assert length(files) == 8
|
||||
assert fixture inlist files
|
||||
assert tmp_path("tmp/a/1.txt") inlist files
|
||||
assert fixture in files
|
||||
assert tmp_path("tmp/a/1.txt") in files
|
||||
|
||||
refute File.exists?(tmp_path("tmp/a/1.txt"))
|
||||
refute File.exists?(tmp_path("tmp/a/a/2.txt"))
|
||||
@@ -811,8 +811,8 @@ defmodule FileTest do
|
||||
|
||||
{ :ok, files } = File.rm_rf(fixture)
|
||||
assert length(files) == 8
|
||||
assert fixture inlist files
|
||||
assert (tmp_path("tmp/a/1.txt") |> to_char_list) inlist files
|
||||
assert fixture in files
|
||||
assert (tmp_path("tmp/a/1.txt") |> to_char_list) in files
|
||||
|
||||
refute File.exists?(tmp_path("tmp/a/1.txt"))
|
||||
refute File.exists?(tmp_path("tmp/a/a/2.txt"))
|
||||
@@ -847,8 +847,8 @@ defmodule FileTest do
|
||||
|
||||
files = File.rm_rf!(fixture)
|
||||
assert length(files) == 8
|
||||
assert fixture inlist files
|
||||
assert tmp_path("tmp/a/1.txt") inlist files
|
||||
assert fixture in files
|
||||
assert tmp_path("tmp/a/1.txt") in files
|
||||
|
||||
refute File.exists?(tmp_path("tmp/a/1.txt"))
|
||||
refute File.exists?(tmp_path("tmp/a/a/2.txt"))
|
||||
@@ -905,7 +905,7 @@ defmodule FileTest do
|
||||
dest = tmp_path("tmp_test.txt")
|
||||
|
||||
try do
|
||||
{ :ok, iterator } = File.iterator(src)
|
||||
iterator = File.iterator!(src)
|
||||
File.open dest, [:write], fn(target) ->
|
||||
Enum.each iterator, fn(line) ->
|
||||
IO.write target, Regex.replace(%r/"/, line, "'")
|
||||
@@ -934,12 +934,6 @@ defmodule FileTest do
|
||||
end
|
||||
end
|
||||
|
||||
test :iterator_can_be_closed do
|
||||
src = fixture_path("file.txt")
|
||||
iterator = File.iterator!(src)
|
||||
assert File.close(iterator) == :ok
|
||||
end
|
||||
|
||||
test :biniterator do
|
||||
src = File.open! fixture_path("file.txt")
|
||||
dest = tmp_path("tmp_test.txt")
|
||||
@@ -962,7 +956,7 @@ defmodule FileTest do
|
||||
dest = tmp_path("tmp_test.txt")
|
||||
|
||||
try do
|
||||
{ :ok, iterator } = File.biniterator(src)
|
||||
iterator = File.biniterator!(src)
|
||||
File.open dest, [:write], fn(target) ->
|
||||
Enum.each iterator, fn(line) ->
|
||||
IO.write target, Regex.replace(%r/"/, line, "'")
|
||||
@@ -991,12 +985,6 @@ defmodule FileTest do
|
||||
end
|
||||
end
|
||||
|
||||
test :biniterator_can_be_closed do
|
||||
src = fixture_path("file.txt")
|
||||
iterator = File.biniterator!(src)
|
||||
assert File.close(iterator) == :ok
|
||||
end
|
||||
|
||||
test :copy do
|
||||
src = fixture_path("file.txt")
|
||||
dest = tmp_path("tmp_test.txt")
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
Code.require_file "../../test_helper.exs", __FILE__
|
||||
Code.require_file "../test_helper.exs", __DIR__
|
||||
|
||||
defmodule GenEvent.BehaviourTest do
|
||||
use ExUnit.Case, async: true
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
Code.require_file "../../test_helper.exs", __FILE__
|
||||
Code.require_file "../test_helper.exs", __DIR__
|
||||
|
||||
defmodule GenServer.BehaviourTest do
|
||||
use ExUnit.Case, async: true
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
Code.require_file "../test_helper.exs", __FILE__
|
||||
Code.require_file "test_helper.exs", __DIR__
|
||||
|
||||
defmodule HashDictTest do
|
||||
use ExUnit.Case, async: true
|
||||
@@ -18,11 +18,11 @@ defmodule HashDictTest do
|
||||
smoke_test(1200..1)
|
||||
end
|
||||
|
||||
test :get! do
|
||||
test :fetch! do
|
||||
dict = filled_dict(8)
|
||||
assert HashDict.get!(dict, 1) == 1
|
||||
assert HashDict.fetch!(dict, 1) == 1
|
||||
assert_raise KeyError, fn ->
|
||||
HashDict.get!(dict, 11)
|
||||
HashDict.fetch!(dict, 11)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -32,6 +32,12 @@ defmodule HashDictTest do
|
||||
assert HashDict.empty filled_dict(120) == HashDict.new
|
||||
end
|
||||
|
||||
test :fetch do
|
||||
dict = filled_dict(8)
|
||||
assert HashDict.fetch(dict, 4) == { :ok, 4 }
|
||||
assert HashDict.fetch(dict, 16) == :error
|
||||
end
|
||||
|
||||
test :has_key? do
|
||||
dict = filled_dict(8)
|
||||
assert HashDict.has_key? dict, 4
|
||||
@@ -81,47 +87,52 @@ defmodule HashDictTest do
|
||||
test :to_list do
|
||||
list = filled_dict(8) |> HashDict.to_list
|
||||
assert length(list) == 8
|
||||
assert { 1, 1 } inlist list
|
||||
assert { 1, 1 } in list
|
||||
|
||||
list = filled_dict(20) |> HashDict.to_list
|
||||
assert length(list) == 20
|
||||
assert { 1, 1 } inlist list
|
||||
assert { 1, 1 } in list
|
||||
|
||||
list = filled_dict(120) |> HashDict.to_list
|
||||
assert length(list) == 120
|
||||
assert { 1, 1 } inlist list
|
||||
assert { 1, 1 } in list
|
||||
end
|
||||
|
||||
test :keys do
|
||||
list = filled_dict(8) |> HashDict.keys
|
||||
assert length(list) == 8
|
||||
assert 1 inlist list
|
||||
assert 1 in list
|
||||
|
||||
list = filled_dict(20) |> HashDict.keys
|
||||
assert length(list) == 20
|
||||
assert 1 inlist list
|
||||
assert 1 in list
|
||||
|
||||
list = filled_dict(120) |> HashDict.keys
|
||||
assert length(list) == 120
|
||||
assert 1 inlist list
|
||||
assert 1 in list
|
||||
end
|
||||
|
||||
test :values do
|
||||
list = filled_dict(8) |> HashDict.values
|
||||
assert length(list) == 8
|
||||
assert 1 inlist list
|
||||
assert 1 in list
|
||||
|
||||
list = filled_dict(20) |> HashDict.values
|
||||
assert length(list) == 20
|
||||
assert 1 inlist list
|
||||
assert 1 in list
|
||||
|
||||
list = filled_dict(120) |> HashDict.values
|
||||
assert length(list) == 120
|
||||
assert 1 inlist list
|
||||
assert 1 in list
|
||||
end
|
||||
|
||||
test :enum do
|
||||
assert Enum.count(filled_dict(8)) == 8
|
||||
dict = filled_dict(10)
|
||||
assert Enum.empty?(HashDict.new)
|
||||
refute Enum.empty?(dict)
|
||||
assert Enum.member?(dict, { 5, 5 })
|
||||
refute Enum.member?(dict, { 5, 8 })
|
||||
assert Enum.count(dict) == 10
|
||||
assert Enum.map(filled_dict(3), fn({ k, v }) -> k + v end) == [2,4,6]
|
||||
end
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
Code.require_file "../../test_helper.exs", __FILE__
|
||||
Code.require_file "../test_helper.exs", __DIR__
|
||||
|
||||
defmodule IO.ANSITest do
|
||||
use ExUnit.Case, async: true
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
Code.require_file "../test_helper.exs", __FILE__
|
||||
Code.require_file "test_helper.exs", __DIR__
|
||||
|
||||
defmodule IOTest do
|
||||
use ExUnit.Case, async: true
|
||||
@@ -21,25 +21,25 @@ defmodule IOTest do
|
||||
assert File.close(file) == :ok
|
||||
end
|
||||
|
||||
test :getb do
|
||||
test :getn do
|
||||
{ :ok, file } = File.open(Path.expand('../fixtures/file.txt', __FILE__))
|
||||
assert "F" == IO.getb(file, "")
|
||||
assert "O" == IO.getb(file, "")
|
||||
assert "O" == IO.getb(file, "")
|
||||
assert "\n" == IO.getb(file, "")
|
||||
assert :eof == IO.getb(file, "")
|
||||
assert "F" == IO.getn(file, "")
|
||||
assert "O" == IO.getn(file, "")
|
||||
assert "O" == IO.getn(file, "")
|
||||
assert "\n" == IO.getn(file, "")
|
||||
assert :eof == IO.getn(file, "")
|
||||
assert File.close(file) == :ok
|
||||
end
|
||||
|
||||
test :getb_with_count do
|
||||
test :getn_with_count do
|
||||
{ :ok, file } = File.open(Path.expand('../fixtures/file.txt', __FILE__), [:charlist])
|
||||
assert 'FOO' == IO.getb(file, "", 3)
|
||||
assert 'FOO' == IO.getn(file, "", 3)
|
||||
assert File.close(file) == :ok
|
||||
end
|
||||
|
||||
test :getb_with_utf8_and_binary do
|
||||
test :getn_with_utf8_and_binary do
|
||||
{ :ok, file } = File.open(Path.expand('../fixtures/utf8.txt', __FILE__), [:utf8])
|
||||
assert "Русский" == IO.getb(file, "", 7)
|
||||
assert "Русский" == IO.getn(file, "", 7)
|
||||
assert File.close(file) == :ok
|
||||
end
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
Code.require_file "../../test_helper.exs", __FILE__
|
||||
Code.require_file "../test_helper.exs", __DIR__
|
||||
|
||||
defmodule Kernel.AliasTest.Nested do
|
||||
def value, do: 1
|
||||
@@ -12,8 +12,8 @@ defmodule Kernel.AliasTest do
|
||||
test :alias_erlang do
|
||||
alias :lists, as: MyList
|
||||
assert MyList.flatten([1,[2],3]) == [1,2,3]
|
||||
assert Elixir.MyList.Bar == :"Elixir-MyList-Bar"
|
||||
assert MyList.Bar == :"Elixir-lists-Bar"
|
||||
assert Elixir.MyList.Bar == :"Elixir.MyList.Bar"
|
||||
assert MyList.Bar == :"Elixir.lists.Bar"
|
||||
end
|
||||
|
||||
test :double_alias do
|
||||
@@ -27,6 +27,16 @@ defmodule Kernel.AliasTest do
|
||||
assert Nested.flatten([[13]]) == [13]
|
||||
end
|
||||
|
||||
test :lexical do
|
||||
if true do
|
||||
alias OMG, as: List
|
||||
else
|
||||
alias ABC, as: List
|
||||
end
|
||||
|
||||
assert List.flatten([1,[2],3]) == [1,2,3]
|
||||
end
|
||||
|
||||
defmodule Elixir do
|
||||
def sample, do: 1
|
||||
end
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
Code.require_file "../../test_helper.exs", __FILE__
|
||||
Code.require_file "../test_helper.exs", __DIR__
|
||||
|
||||
defmodule Kernel.CaseTest do
|
||||
use ExUnit.Case, async: true
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
Code.require_file "../../test_helper.exs", __FILE__
|
||||
Code.require_file "../test_helper.exs", __DIR__
|
||||
|
||||
defmodule Kernel.CLI.InitTest do
|
||||
use ExUnit.Case, async: true
|
||||
@@ -18,16 +18,16 @@ defmodule Kernel.CLI.OptionParsingTest do
|
||||
|
||||
test :path do
|
||||
root = fixture_path("../../..") |> to_char_list
|
||||
list = elixir('-e "IO.inspect :code.get_path" -pa "#{root}/*" -pz "#{root}/lib/*"')
|
||||
{ path, _ } = Code.eval list, []
|
||||
list = elixir('-pa "#{root}/*" -pz "#{root}/lib/*" -e "IO.inspect :code.get_path"')
|
||||
{ path, _ } = Code.eval_string list, []
|
||||
|
||||
# pa
|
||||
assert Path.expand('ebin', root) inlist path
|
||||
assert Path.expand('lib', root) inlist path
|
||||
assert Path.expand('src', root) inlist path
|
||||
assert Path.expand('ebin', root) in path
|
||||
assert Path.expand('lib', root) in path
|
||||
assert Path.expand('src', root) in path
|
||||
|
||||
# pz
|
||||
assert Path.expand('lib/list', root) inlist path
|
||||
assert Path.expand('lib/list', root) in path
|
||||
end
|
||||
end
|
||||
|
||||
@@ -63,6 +63,10 @@ defmodule Kernel.CLI.SyntaxErrorTest do
|
||||
assert :string.str(message, elixir('-e "[1,2"')) == 0
|
||||
message = '** (SyntaxError) nofile:1: syntax error before: \'end\''
|
||||
assert :string.str(message, elixir('-e "case 1 end"')) == 0
|
||||
message = '** (SyntaxError) nofile:1: invalid token: あ'
|
||||
assert :string.str(message, elixir('-e "あ"')) == 0
|
||||
message = '** (SyntaxError) nofile:1: invalid token: æ'
|
||||
assert :string.str(message, elixir('-e "æ"')) == 0
|
||||
end
|
||||
end
|
||||
|
||||
@@ -74,7 +78,7 @@ defmodule Kernel.CLI.CompileTest do
|
||||
fixture = fixture_path "compile_sample.ex"
|
||||
assert elixirc('#{fixture} -o #{tmp_path}') ==
|
||||
'Compiled #{fixture}\n'
|
||||
assert File.regular?(tmp_path "Elixir-CompileSample.beam")
|
||||
assert File.regular?(tmp_path "Elixir.CompileSample.beam")
|
||||
end
|
||||
end
|
||||
|
||||
@@ -92,8 +96,8 @@ defmodule Kernel.CLI.ParallelCompilerTest do
|
||||
output = elixirc('#{fixture_path("parallel_compiler")} -o #{tmp_path}')
|
||||
assert :string.str(output, 'message_from_foo') > 0,
|
||||
"Expected #{inspect output} to contain 'message_from_foo'"
|
||||
assert File.regular?(tmp_path "Elixir-Foo.beam")
|
||||
assert File.regular?(tmp_path "Elixir-Bar.beam")
|
||||
assert File.regular?(tmp_path "Elixir.Foo.beam")
|
||||
assert File.regular?(tmp_path "Elixir.Bar.beam")
|
||||
end
|
||||
|
||||
test :possible_deadlock do
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
Code.require_file "../../test_helper.exs", __FILE__
|
||||
Code.require_file "../test_helper.exs", __DIR__
|
||||
|
||||
defmodule Kernel.ComprehensionTest do
|
||||
use ExUnit.Case, async: true
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
Code.require_file "../../test_helper.exs", __FILE__
|
||||
Code.require_file "../test_helper.exs", __DIR__
|
||||
|
||||
defmodule Kernel.DefClause do
|
||||
defmacro defclause(expr, block) do
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
Code.require_file "../../test_helper.exs", __FILE__
|
||||
Code.require_file "../test_helper.exs", __DIR__
|
||||
|
||||
defmodule Kernel.DocTest do
|
||||
use ExUnit.Case
|
||||
|
||||
test :compiled_docs do
|
||||
path = Path.expand("../../fixtures/compiled_with_docs.ex", __FILE__)
|
||||
path = Path.join(__DIR__, "../fixtures/compiled_with_docs.ex")
|
||||
|
||||
try do
|
||||
Code.load_file path
|
||||
@@ -23,7 +23,7 @@ defmodule Kernel.DocTest do
|
||||
end
|
||||
|
||||
test :compiled_without_docs do
|
||||
path = Path.expand("../../fixtures/compiled_with_docs.ex", __FILE__)
|
||||
path = Path.join(__DIR__, "../fixtures/compiled_with_docs.ex")
|
||||
|
||||
try do
|
||||
Code.compiler_options(docs: false)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
Code.require_file "../../test_helper.exs", __FILE__
|
||||
Code.require_file "../test_helper.exs", __DIR__
|
||||
|
||||
defmodule Kernel.ErrorsTest do
|
||||
use ExUnit.Case, async: true
|
||||
@@ -43,6 +43,12 @@ defmodule Kernel.ErrorsTest do
|
||||
assert "nofile:1: syntax error before: '.'" == format_rescue '+.foo'
|
||||
end
|
||||
|
||||
test :syntax_error_on_parens_call do
|
||||
assert "nofile:1: invalid comma inside parenthesis. If you are making a function call, " <>
|
||||
"do not insert spaces in between the function name and the opening parentheses. " <>
|
||||
"Syntax error before: )" == format_rescue 'foo (hello, world)'
|
||||
end
|
||||
|
||||
test :syntax_error_with_no_token do
|
||||
assert "nofile:1: missing terminator: ) (for \"(\" starting at line 1)" == format_rescue 'case 1 ('
|
||||
end
|
||||
@@ -109,7 +115,7 @@ defmodule Kernel.ErrorsTest do
|
||||
end
|
||||
|
||||
test :function_import_conflict do
|
||||
assert "nofile:2: function exit/1 imported from both erlang and Kernel, call is ambiguous" ==
|
||||
assert "nofile:2: function exit/1 imported from both :erlang and Kernel, call is ambiguous" ==
|
||||
format_rescue 'defmodule Foo do import :erlang\n def foo, do: exit(:test)\nend'
|
||||
end
|
||||
|
||||
@@ -134,7 +140,7 @@ defmodule Kernel.ErrorsTest do
|
||||
end
|
||||
|
||||
test :no_macros do
|
||||
assert "nofile:2: could not load macros from module lists" ==
|
||||
assert "nofile:2: could not load macros from module :lists" ==
|
||||
format_rescue 'defmodule Foo do\nimport :macros, :lists\nend'
|
||||
end
|
||||
|
||||
@@ -205,7 +211,7 @@ defmodule Kernel.ErrorsTest do
|
||||
end
|
||||
|
||||
test :invalid_access_protocol_not_available do
|
||||
assert "expected module Unknown to be loaded and defined" ==
|
||||
assert "nofile:2: module Unknown is not loaded and could not be found" ==
|
||||
format_rescue 'defmodule Foo do\ndef sample(Unknown[integer: 0]), do: true\nend'
|
||||
end
|
||||
|
||||
@@ -229,8 +235,8 @@ defmodule Kernel.ErrorsTest do
|
||||
format_rescue 'Kernel.ErrorsTest.Config[foo: :bar]'
|
||||
end
|
||||
|
||||
test :invalid_access_protocol_on_rescue do
|
||||
assert "nofile:1: cannot (yet) pattern match against erlang exceptions" ==
|
||||
test :invalid_rescue_clause do
|
||||
assert "nofile:1: invalid rescue clause. The clause should match on an alias, a variable or be in the `var in [alias]` format" ==
|
||||
format_rescue 'try do\n1\nrescue\nUndefinedFunctionError[arity: 1] -> false\nend'
|
||||
end
|
||||
|
||||
@@ -259,6 +265,26 @@ defmodule Kernel.ErrorsTest do
|
||||
format_rescue 'if true do\n foo = [],\n baz\nend'
|
||||
end
|
||||
|
||||
test :invalid_var_or_function_on_guard do
|
||||
assert "nofile:1: unknown variable something_that_does_not_exist or cannot invoke function something_that_does_not_exist/0 inside guard" =
|
||||
format_rescue('case [] do; [] when something_that_does_not_exist == [] -> :ok; end')
|
||||
end
|
||||
|
||||
test :invalid_function_on_match do
|
||||
assert "nofile:1: cannot invoke function something_that_does_not_exist/0 inside match" =
|
||||
format_rescue('case [] do; something_that_does_not_exist() -> :ok; end')
|
||||
end
|
||||
|
||||
test :invalid_remote_on_match do
|
||||
assert "nofile:1: cannot invoke remote function Hello.something_that_does_not_exist/0 inside match" =
|
||||
format_rescue('case [] do; Hello.something_that_does_not_exist() -> :ok; end')
|
||||
end
|
||||
|
||||
test :invalid_remote_on_guard do
|
||||
assert "nofile:1: cannot invoke remote function Hello.something_that_does_not_exist/0 inside guard" =
|
||||
format_rescue('case [] do; [] when Hello.something_that_does_not_exist == [] -> :ok; end')
|
||||
end
|
||||
|
||||
test :macros_error_stacktrace do
|
||||
assert [{:erlang,:+,[1,:foo],_},{Foo,:sample,1,_}|_] =
|
||||
rescue_stacktrace("defmodule Foo do\ndefmacro sample(num), do: num + :foo\ndef other, do: sample(1)\nend")
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
Code.require_file "../../test_helper.exs", __FILE__
|
||||
Code.require_file "../test_helper.exs", __DIR__
|
||||
|
||||
defmodule Kernel.ImportAvailable do
|
||||
defmacro flatten do
|
||||
@@ -29,6 +29,15 @@ defmodule Kernel.ImportOnlyTest do
|
||||
import :lists, except: [each: 2]
|
||||
assert flatten([1,[2],3]) == [1,2,3]
|
||||
end
|
||||
|
||||
defmacrop dynamic_opts do
|
||||
[except: [each: 2]]
|
||||
end
|
||||
|
||||
test :import_with_dynamic_opts do
|
||||
import :lists, dynamic_opts
|
||||
assert flatten([1,[2],3]) == [1,2,3]
|
||||
end
|
||||
end
|
||||
|
||||
defmodule Kernel.DoubleImportTest do
|
||||
@@ -37,6 +46,7 @@ defmodule Kernel.DoubleImportTest do
|
||||
test :import_double_except do
|
||||
import :lists, except: [flatten: 1]
|
||||
import :lists, except: [each: 2]
|
||||
assert append([1], [2,3]) == [1,2,3]
|
||||
assert flatten([1,[2],3]) == [1,[2],3]
|
||||
end
|
||||
|
||||
@@ -111,7 +121,7 @@ defmodule Kernel.ImportMacrosTest do
|
||||
end
|
||||
end
|
||||
|
||||
defmodule Kernel.AmbiguousImportTest do
|
||||
defmodule Kernel.MultipleImportTest do
|
||||
use ExUnit.Case, async: true
|
||||
|
||||
test :import_ambiguous do
|
||||
@@ -121,4 +131,10 @@ defmodule Kernel.AmbiguousImportTest do
|
||||
import List
|
||||
import String
|
||||
end
|
||||
|
||||
test :import_many do
|
||||
[import(List), import(String)]
|
||||
assert capitalize("foo") == "Foo"
|
||||
assert flatten([1,[2],3]) == [1,2,3]
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
Code.require_file "../../test_helper.exs", __FILE__
|
||||
Code.require_file "../test_helper.exs", __DIR__
|
||||
|
||||
defmodule Kernel.MacrosTest.Nested do
|
||||
defmacro value, do: 1
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user