Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ddc6b48a49 | ||
|
|
5a2e838def | ||
|
|
ebc6d76034 | ||
|
|
42fc70a0eb | ||
|
|
87fee37711 | ||
|
|
4870351d2f | ||
|
|
aba387c526 | ||
|
|
36d8e1ceee | ||
|
|
68c9a28d06 | ||
|
|
30c9af3631 | ||
|
|
f42055be57 | ||
|
|
17a5c37d06 | ||
|
|
8c45982cc6 | ||
|
|
6697100f95 | ||
|
|
94812b4242 | ||
|
|
4fae777e89 | ||
|
|
7ea4b02460 | ||
|
|
11423af777 | ||
|
|
23c67ac47c | ||
|
|
3ae00072ae | ||
|
|
b4b9ecccb4 | ||
|
|
53b68887b6 | ||
|
|
ea8e555f53 | ||
|
|
407427f627 | ||
|
|
2b2d9dc2de | ||
|
|
b4ad1ff5bc | ||
|
|
ab538670d3 | ||
|
|
691a9b8b2b | ||
|
|
ee3fcdb96a | ||
|
|
ba88ee7c92 | ||
|
|
9cd5a47bbe | ||
|
|
be3ab7d13d | ||
|
|
cdbd480f4d | ||
|
|
0538e46023 | ||
|
|
ebf84037cc | ||
|
|
1b3cdbc7fb | ||
|
|
1d13d189c1 | ||
|
|
7fcdecde95 | ||
|
|
bc852a6fba | ||
|
|
28ff6b3532 | ||
|
|
b0e1c09579 | ||
|
|
d3a5cd22a6 | ||
|
|
96a1628033 | ||
|
|
d6de0979fb | ||
|
|
19301b9005 | ||
|
|
02c76da51a | ||
|
|
435597286f | ||
|
|
8e3b5e38e4 | ||
|
|
9fcab6e881 | ||
|
|
afd5ebb563 | ||
|
|
e169c0f49b | ||
|
|
8c97056b0e | ||
|
|
4be1501f57 | ||
|
|
59402569ed | ||
|
|
0aff2a3139 | ||
|
|
9c4271891a | ||
|
|
7c240199f3 | ||
|
|
1e9ab79cad | ||
|
|
d37b7b3702 | ||
|
|
ac2c79a7ce | ||
|
|
84cc7d5eb0 | ||
|
|
27467bdb67 | ||
|
|
1ee1a3f687 | ||
|
|
05c6f9f822 | ||
|
|
952052869f | ||
|
|
df8b216357 | ||
|
|
79faa1f706 | ||
|
|
31cec42b23 | ||
|
|
05beafd5bb | ||
|
|
cba7c5bd80 | ||
|
|
9b22b6d883 | ||
|
|
2a6940014a | ||
|
|
2a1941112e | ||
|
|
5e2ea082d1 | ||
|
|
ba33806cc3 | ||
|
|
62eeb97eb8 | ||
|
|
0a24252fca | ||
|
|
243f4e6b0a | ||
|
|
3bc277f525 | ||
|
|
f0bfdfaf19 | ||
|
|
2979430137 | ||
|
|
1c4b759c4e | ||
|
|
f67022267f | ||
|
|
febee82e9e | ||
|
|
66d8cdc56e | ||
|
|
8d59c007c8 | ||
|
|
3a0b4988fc | ||
|
|
b7bbe13c91 | ||
|
|
4cbe174c13 | ||
|
|
bbe63e9a93 | ||
|
|
6f0f0d5136 | ||
|
|
e055c2ccf2 | ||
|
|
08d61e4b25 | ||
|
|
9c9d8e855e | ||
|
|
bb7b990055 | ||
|
|
0aee0805a9 | ||
|
|
9afc1043c9 | ||
|
|
1622eb63b4 | ||
|
|
d769f8f35e | ||
|
|
9cd56804ea | ||
|
|
6d17b42beb | ||
|
|
ac546881e8 | ||
|
|
77aca31ffe | ||
|
|
9395bb105d | ||
|
|
47feb1b6a0 | ||
|
|
f98afc1d83 | ||
|
|
6b407a339b | ||
|
|
c91d2d9c2b | ||
|
|
f82fbdcaed | ||
|
|
c6817c80b0 | ||
|
|
6fc468bddb | ||
|
|
5b6fc9b415 | ||
|
|
5c45e4aed4 | ||
|
|
5fa617a8aa | ||
|
|
387dd72d54 | ||
|
|
91bbfa52ba | ||
|
|
b80fe27705 | ||
|
|
dbcb3717d6 | ||
|
|
7836493d23 | ||
|
|
d27c7b0e1f | ||
|
|
26bc9d9d61 | ||
|
|
684cf05d75 | ||
|
|
417c719c78 | ||
|
|
dc63e25995 | ||
|
|
74e9f76aaa | ||
|
|
40d383faae | ||
|
|
eaac3bbd76 | ||
|
|
ae12432294 | ||
|
|
d0a6792bdf | ||
|
|
a5b67f3084 | ||
|
|
6e48e5dfcb | ||
|
|
83c93beaa6 | ||
|
|
ddd4afbdba | ||
|
|
a2cc2623a8 | ||
|
|
d4da5be4ee | ||
|
|
b03e2173a5 | ||
|
|
f547ef82e2 | ||
|
|
254065d3c4 | ||
|
|
29b38ab9e0 | ||
|
|
0e627c9b09 | ||
|
|
f8b019c7c8 | ||
|
|
c4419268b8 | ||
|
|
ade26dedd8 | ||
|
|
574e92fc57 | ||
|
|
8c6e31bda0 | ||
|
|
70d7a28a36 | ||
|
|
78def5da6e | ||
|
|
ffe2cc74b8 | ||
|
|
99b1d9b677 | ||
|
|
869378d1ca | ||
|
|
f306ca5a49 | ||
|
|
b39c762557 | ||
|
|
98e15914eb | ||
|
|
8c47368fff | ||
|
|
4176480f39 | ||
|
|
f81a15b7cb | ||
|
|
b3ba12593d | ||
|
|
51aef55c83 | ||
|
|
ec41e0fe1e | ||
|
|
2d36a76b26 | ||
|
|
9f5e7c4a41 | ||
|
|
e98f3eae10 | ||
|
|
5b5075b2c7 | ||
|
|
238baa14fa | ||
|
|
620532049c | ||
|
|
67566f155b | ||
|
|
d976937486 | ||
|
|
3f4410ff68 | ||
|
|
4c26585ef6 | ||
|
|
6fa7d64814 | ||
|
|
67eeedfb1d | ||
|
|
d0a823c8b0 | ||
|
|
df9d535769 | ||
|
|
219ded1b91 | ||
|
|
a0eef07958 | ||
|
|
c7af8bc2cc | ||
|
|
349873ab63 | ||
|
|
f2cf4a393f | ||
|
|
2c1c504fc5 | ||
|
|
625a27fac2 | ||
|
|
5164073bf0 | ||
|
|
66dd76e42b | ||
|
|
bb05011dca | ||
|
|
c296e62543 | ||
|
|
8847f8ed68 | ||
|
|
b536a2ac7c | ||
|
|
a33cbbe6b1 | ||
|
|
afb7efd11b | ||
|
|
613298c4ab | ||
|
|
d39401e2d3 | ||
|
|
a5602a71fa | ||
|
|
0bd45a090a | ||
|
|
68f570c0a5 | ||
|
|
f0456b6377 | ||
|
|
dfeeeacb8c | ||
|
|
72d683a86a | ||
|
|
ee337bbfb8 | ||
|
|
d4729a3795 | ||
|
|
352a2d8851 | ||
|
|
c7da787265 | ||
|
|
159ba856a6 | ||
|
|
6d3624147e | ||
|
|
f1e647f04b | ||
|
|
90d72c6a5e | ||
|
|
5ce7ee2e3b | ||
|
|
c1bcf5dab2 | ||
|
|
11ab91c0fb | ||
|
|
6c84492618 | ||
|
|
91dc65b7ee | ||
|
|
2ec9c24b3e | ||
|
|
eab8b46879 | ||
|
|
67e0367987 | ||
|
|
cd43f618e0 | ||
|
|
3687b0f104 | ||
|
|
b1d245651d | ||
|
|
020a37785a | ||
|
|
14b3dff8e7 | ||
|
|
1567af2f12 | ||
|
|
35f599d184 | ||
|
|
c361e0a8a6 | ||
|
|
38c60776f6 | ||
|
|
9bedb0faaf | ||
|
|
965915bffa | ||
|
|
a711bef57f | ||
|
|
a736b55499 | ||
|
|
8720c56291 | ||
|
|
2c5650ca9d | ||
|
|
f3ae3b0df5 | ||
|
|
37766232bf | ||
|
|
5e8717918c | ||
|
|
d624ceb72b | ||
|
|
fcb5076e42 | ||
|
|
067bdde570 | ||
|
|
fcad4a1343 | ||
|
|
cf0dd31714 | ||
|
|
e3e75e5720 | ||
|
|
5369f2449c | ||
|
|
a7dae16877 | ||
|
|
eb46fe30a2 | ||
|
|
aa1b678b54 | ||
|
|
e53f75f2b9 | ||
|
|
538b830c3e | ||
|
|
cb32338cae | ||
|
|
cf9284f057 | ||
|
|
3ac57066ce | ||
|
|
db19cdea42 | ||
|
|
2d7965b7e6 | ||
|
|
4a2d1d6555 | ||
|
|
1b93904a27 | ||
|
|
303789fc93 | ||
|
|
3c6417896e | ||
|
|
23a4fc65fe | ||
|
|
575e0b9494 | ||
|
|
e49998593e | ||
|
|
33c6eb3e37 | ||
|
|
405c14f7e1 | ||
|
|
b26304fa59 | ||
|
|
1c834e8938 | ||
|
|
8e51528dc1 | ||
|
|
898e01ccb5 | ||
|
|
24725090f5 | ||
|
|
cef74d0186 | ||
|
|
4e2a1230f3 | ||
|
|
a8959f3987 | ||
|
|
bce9116cff | ||
|
|
b78c3bc36b | ||
|
|
94be8a1b8a | ||
|
|
c19ea55ed7 | ||
|
|
34c2c50f42 | ||
|
|
6f219ad1da | ||
|
|
2487d741b6 | ||
|
|
f62c26b9b6 | ||
|
|
1d7c56d423 | ||
|
|
827507603a | ||
|
|
0977602647 | ||
|
|
ee86a5c1e0 | ||
|
|
c4ffe1b8b3 | ||
|
|
eaebcf77f3 | ||
|
|
fa56d91c08 | ||
|
|
04623a6edf | ||
|
|
a3afaeec8e | ||
|
|
5d7a1cf7a0 | ||
|
|
4e29f274c5 | ||
|
|
05a321dc8c | ||
|
|
7818551e6a | ||
|
|
5dfa6ae618 | ||
|
|
9b6f104ffe | ||
|
|
80118f4561 | ||
|
|
1fbb6871a0 | ||
|
|
474b05e5c4 | ||
|
|
7d1e6b545d | ||
|
|
01e31cf3c3 | ||
|
|
640cc06d63 | ||
|
|
b2f97f5406 | ||
|
|
d510356f20 | ||
|
|
cb6753742b | ||
|
|
73253206f2 | ||
|
|
90f9162e3f | ||
|
|
c74f203c0d | ||
|
|
afa7eee4ad | ||
|
|
228b20eb7c | ||
|
|
60c4ede916 | ||
|
|
85e2845fd8 | ||
|
|
c1bedd5dce | ||
|
|
e424f02812 | ||
|
|
7dd9da12a7 | ||
|
|
67109b291d | ||
|
|
bee0e28e5b | ||
|
|
9ee79a16bb | ||
|
|
bbc0fe0a63 | ||
|
|
96077905fb | ||
|
|
00431ebecb | ||
|
|
203ddd0c37 | ||
|
|
ab91165946 | ||
|
|
6a7609d707 | ||
|
|
ec6f95d970 | ||
|
|
0e54cad8ee | ||
|
|
c093e378d8 | ||
|
|
a829da1775 | ||
|
|
7d83083525 | ||
|
|
a1f104cd30 | ||
|
|
895852f1d2 | ||
|
|
9a0e8f38de | ||
|
|
ffb2258bb1 | ||
|
|
d95a7d1a58 | ||
|
|
3a8d0ccbc2 | ||
|
|
4ed4f52121 | ||
|
|
d416b700e3 | ||
|
|
d3055a9d2a | ||
|
|
cb66331e87 | ||
|
|
5263610aac | ||
|
|
5d53abc5ff | ||
|
|
89c4822fa0 | ||
|
|
1f4037b93c | ||
|
|
660f8607a6 | ||
|
|
52cd9b359c | ||
|
|
127a634ba2 | ||
|
|
09869ec135 | ||
|
|
4e52765755 | ||
|
|
2b3ae52fa5 | ||
|
|
63fdbfb8cf | ||
|
|
5c00882bff | ||
|
|
043edd4990 | ||
|
|
fd3d8b3b98 | ||
|
|
e0354005ea | ||
|
|
da34ccd54e | ||
|
|
4460770981 | ||
|
|
5782dae432 | ||
|
|
494659ab1c | ||
|
|
eae9177b18 | ||
|
|
08afaa7982 | ||
|
|
d7179b404b | ||
|
|
be71239b7c | ||
|
|
d330d77c33 | ||
|
|
a8bdd19a03 | ||
|
|
720d1e2a71 | ||
|
|
f685f5d2a5 | ||
|
|
0a84b90373 | ||
|
|
f98cda71c0 | ||
|
|
b28c95d31a | ||
|
|
abec6af3ac | ||
|
|
e802d2960b | ||
|
|
7d26bfbfad | ||
|
|
4e251b10b9 | ||
|
|
f8380e9dcd | ||
|
|
c2243e2c28 | ||
|
|
ffcde49975 | ||
|
|
f4144a63f8 | ||
|
|
2655a318c2 | ||
|
|
e553b6780b | ||
|
|
398e0fd3fb | ||
|
|
de3c0f639a | ||
|
|
1096cd00b9 | ||
|
|
260be7c8ea | ||
|
|
bed554cc50 | ||
|
|
f60fd6a5d4 | ||
|
|
a6c19950a0 | ||
|
|
e1ff60a57d | ||
|
|
5d324c0a6f | ||
|
|
4b17932bc8 | ||
|
|
fe3d1b1cfe | ||
|
|
42c5b63ad7 | ||
|
|
e65eee49c4 | ||
|
|
03e1e8e203 | ||
|
|
a135e14348 | ||
|
|
dbc8c7c607 | ||
|
|
404e8290b8 | ||
|
|
eba273a72a | ||
|
|
75a1f2edbf | ||
|
|
1bf4a95643 | ||
|
|
4358386906 | ||
|
|
f4bc50c20f | ||
|
|
985ce8e031 | ||
|
|
2b72bc08d7 | ||
|
|
fa348e5027 | ||
|
|
cfa2aad8b4 | ||
|
|
8c145c42f3 | ||
|
|
2b6b48688e | ||
|
|
9b0617f889 | ||
|
|
fb5c963f17 | ||
|
|
ff679125e6 | ||
|
|
91d7fa1f9c | ||
|
|
8310d62499 | ||
|
|
c6a50d960e | ||
|
|
bee44b382b | ||
|
|
ac28ed12b1 | ||
|
|
c4d039da37 | ||
|
|
b70fc122f8 | ||
|
|
0d5bf3a904 | ||
|
|
66019c36ca | ||
|
|
a73235a2de | ||
|
|
718b111797 | ||
|
|
fd57b5de4f | ||
|
|
bb9569db0c | ||
|
|
74c798585b | ||
|
|
cfdcf685cb | ||
|
|
572726cf65 | ||
|
|
1885841df6 | ||
|
|
80a466af35 | ||
|
|
83db533700 | ||
|
|
49fe9b05a0 | ||
|
|
c8425edb6d | ||
|
|
b9cb17ed39 | ||
|
|
5d48abb9ab | ||
|
|
1403350be0 | ||
|
|
df997f836b | ||
|
|
4544ab59cf | ||
|
|
7614b6c1d0 | ||
|
|
1da1f8c203 | ||
|
|
0addc221e0 | ||
|
|
f6a2046850 | ||
|
|
c345e51772 | ||
|
|
75e22706fc | ||
|
|
eac9929328 | ||
|
|
e1aead5ba3 | ||
|
|
6f5d4d04fd | ||
|
|
13eab25e6d | ||
|
|
2854fec36a | ||
|
|
f125d19854 | ||
|
|
1ce2e24f5d | ||
|
|
48208066b7 | ||
|
|
4914c4982d | ||
|
|
14a562f260 | ||
|
|
dd91696184 | ||
|
|
5c55f0fe91 | ||
|
|
a6474aecd4 | ||
|
|
5844e29443 | ||
|
|
38f7d709d1 | ||
|
|
4931629b51 | ||
|
|
343d9ef042 | ||
|
|
703c7dfd21 | ||
|
|
082af1c417 | ||
|
|
dc9caf4260 | ||
|
|
f7fd253918 | ||
|
|
e6be0628ce | ||
|
|
5cf7f22b70 | ||
|
|
7af9f8a976 | ||
|
|
fe5bfa0fe0 | ||
|
|
97bf67d8db |
+3
-5
@@ -1,5 +1,7 @@
|
||||
/.eunit/*
|
||||
/.eunit
|
||||
/.release
|
||||
/docs
|
||||
/ebin
|
||||
/lib/*/ebin/*
|
||||
/lib/*/tmp
|
||||
/lib/*/test/tmp
|
||||
@@ -7,11 +9,7 @@
|
||||
/lib/elixir/src/*_lexer.erl
|
||||
/lib/elixir/src/*_parser.erl
|
||||
/lib/elixir/test/ebin
|
||||
/deps/*
|
||||
/ebin
|
||||
/rel/elixir
|
||||
erl_crash.dump
|
||||
.dialyzer_plt
|
||||
.dialyzer.base_plt
|
||||
.*.swp
|
||||
docs
|
||||
|
||||
+4
-2
@@ -1,9 +1,11 @@
|
||||
language: erlang
|
||||
script: "make compile && make test"
|
||||
script: "make compile && rm -rf .git && make test"
|
||||
notifications:
|
||||
irc: "irc.freenode.org#elixir-lang"
|
||||
recipients:
|
||||
- jose.valim@plataformatec.com.br
|
||||
- yrashk@gmail.com
|
||||
- eric.meadows.jonsson@gmail.com
|
||||
otp_release:
|
||||
- R16B02
|
||||
- R16B01
|
||||
- R16B
|
||||
|
||||
+101
-2
@@ -1,3 +1,102 @@
|
||||
# v0.12.2 (2014-01-15)
|
||||
|
||||
* Enhancements
|
||||
* [EEx] Allow `EEx.AssignsEngine` to accept any Dict
|
||||
* [Enum] Add `Enum.flat_map_reduce/3`
|
||||
* [ExUnit] Support @moduletag in ExUnit cases
|
||||
* [Kernel] Improve stacktraces to be relative to the compilation path and include the related application
|
||||
* [Stream] Add `Stream.transform/3`
|
||||
|
||||
* Bug fixes
|
||||
* [ExUnit] `:include` in ExUnit only has effect if a test was previously excluded with `:exclude`
|
||||
* [ExUnit] Only run `setup_all` and `teardown_all` if there are tests in the case
|
||||
* [Kernel] Ensure bitstring modifier arguments are expanded
|
||||
* [Kernel] Ensure compiler does not block on missing modules
|
||||
* [Kernel] Ensure `<>/2` works only with binaries
|
||||
* [Kernel] Fix usage of string literals inside `<<>>` when `utf8`/`utf16`/`utf32` is used as specifier
|
||||
* [Mix] Ensure mix properly copies _build dependencies on Windows
|
||||
|
||||
* Deprecations
|
||||
* [Enum] Deprecate `Enum.first/1` in favor of `Enum.at/2` and `List.first/1`
|
||||
* [Kernel] Deprecate continuable heredocs. In previous versions, Elixir would continue parsing on the same line the heredoc started, this behavior has been deprecated
|
||||
* [Kernel] `is_alive/0` is deprecated in favor of `Node.alive?`
|
||||
* [Kernel] `Kernel.inspect/2` with `Inspect.Opts[]` is deprecated in favor of `Inspect.Algebra.to_doc/2`
|
||||
* [Kernel] `Kernel.inspect/2` with `:raw` option is deprecated, use `:records` option instead
|
||||
* [Kernel] Deprecate `<-/2` in favor of `send/2`
|
||||
|
||||
* Backwards incompatible changes
|
||||
* [String] Change `String.next_grapheme/1` and `String.next_codepoint/1` to return `nil` on string end
|
||||
|
||||
# v0.12.1 (2014-01-04)
|
||||
|
||||
* Enhancements
|
||||
* [ExUnit] Support `:include` and `:exclude` configuration options to filter which tests should run based on their tags. Those options are also supported via `mix test` as `--include` and `--exclude`
|
||||
* [ExUnit] Allow doctests to match against `#MyModule<>`
|
||||
|
||||
* Bug fixes
|
||||
* [CLI] Abort when a pattern given to elixirc does not match any file
|
||||
* [Float] Fix `Float.parse/1` to handle numbers of the form "-0.x"
|
||||
* [IEx] Improve error message for `IEx.Helpers.r` when module does not exist
|
||||
* [Mix] Ensure `deps.get` updates origin if lock origin and dep origin do not match
|
||||
* [Mix] Use relative symlinks in _build
|
||||
* [Typespec] Fix conversion of unary ops from typespec format to ast
|
||||
* [Typespec] Fix handling of `tuple()` and `{}`
|
||||
|
||||
* Deprecations
|
||||
* [Kernel] Do not leak clause heads. Previously, a variable defined in a case/receive head clauses would leak to the outer scope. This behaviour is deprecated and will be removed in the next release.
|
||||
* [Kernel] Deprecate `__FILE__` in favor of `__DIR__` or `__ENV__.file`
|
||||
|
||||
* Backwards incompatible changes
|
||||
* [GenFSM] GenServer now stops on unknown event/sync_event requests
|
||||
* [GenServer] GenServer now stops on unknown call/cast requests
|
||||
* [Kernel] Change how `->` is represented in AST. Now each clause is represented by its own AST node which makes composition easier. See commit 51aef55 for more information.
|
||||
|
||||
# v0.12.0 (2013-12-15)
|
||||
|
||||
* Enhancements
|
||||
* [Exception] Allow `exception/1` to be overridden and promote it as the main mechanism to customize exceptions
|
||||
* [File] Add `File.stream_to!/3`
|
||||
* [Float] Add `Float.floor/1`, `Float.ceil/1` and `Float.round/3`
|
||||
* [Kernel] Add `List.delete_at/2` and `List.updated_at/3`
|
||||
* [Kernel] Add `Enum.reverse/2`
|
||||
* [Kernel] Implement `defmodule/2`, `@/1`, `def/2` and friends in Elixir itself. `case/2`, `try/2` and `receive/1` have been made special forms. `var!/1`, `var!/2` and `alias!/1` have also been implemented in Elixir and demoted from special forms
|
||||
* [Record] Support dynamic fields in `defrecordp`
|
||||
* [Stream] Add `Stream.resource/3`
|
||||
* [Stream] Add `Stream.zip/2`, `Stream.filter_map/3`, `Stream.each/2`, `Stream.take_every/2`, `Stream.chunk/2`, `Stream.chunk/3`, `Stream.chunk/4`, `Stream.chunk_by/2`, `Stream.scan/2`, `Stream.scan/3`, `Stream.uniq/2`, `Stream.after/2` and `Stream.run/1`
|
||||
* [Stream] Support `Stream.take/2` and `Stream.drop/2` with negative counts
|
||||
|
||||
* Bug fixes
|
||||
* [HashDict] Ensure a `HashDict` stored in an attribute can be accessed via the attribute
|
||||
* [Enum] Fix bug in `Enum.chunk/4` where you'd get an extra element when the enumerable was a multiple of the counter and a pad was given
|
||||
* [IEx] Ensure `c/2` helper works with full paths
|
||||
* [Kernel] `quote location: :keep` now only affects definitions in order to keep the proper trace in definition exceptions
|
||||
* [Mix] Also symlink `include` directories in _build dependencies
|
||||
* [Version] Fix `Version.match?/2` with `~>` and versions with alphanumeric build info (like `-dev`)
|
||||
|
||||
* Deprecations
|
||||
* [Enum] `Enumerable.count/1` and `Enumerable.member?/2` should now return tagged tuples. Please see `Enumerable` docs for more info
|
||||
* [Enum] Deprecate `Enum.chunks/2`, `Enum.chunks/4` and `Enum.chunks_by/2` in favor of `Enum.chunk/2`, `Enum.chunk/4` and `Enum.chunk_by/2`
|
||||
* [File] `File.binstream!/3` is deprecated. Simply use `File.stream!/3` which is able to figure out if `stream` or `binstream` operations should be used
|
||||
* [Macro] `Macro.extract_args/1` is deprecated in favor of `Macro.decompose_call/1`
|
||||
|
||||
* Backwards incompatible changes
|
||||
* [Enum] Behaviour of `Enum.drop/2` and `Enum.take/2` has been switched when given negative counts
|
||||
* [Enum] Behaviour of `Enum.zip/2` has been changed to stop as soon as the first enumerable finishes
|
||||
* [Enum] `Enumerable.reduce/3` protocol has changed to support suspension. Please see `Enumerable` docs for more info
|
||||
* [Mix] Require `:escript_main_module` to be set before generating escripts
|
||||
* [Range] `Range.Iterator` protocol has changed in order to work with the new `Enumerable.reduce/3`. Please see `Range.Iterator` docs for more info
|
||||
* [Stream] The `Stream.Lazy` structure has changed to accumulate functions and accumulators as we go (its inspected representation has also changed)
|
||||
* [Typespec] `when` clauses were moved to the outer part of the spec and should be in the keywords format. So `add(a, b) when is_subtype(a, integer) and is_subtype(b, integer) :: integer` should now be written as `add(a, b) :: integer when a: integer, b: integer`
|
||||
|
||||
# v0.11.2 (2013-11-14)
|
||||
|
||||
* Enhancements
|
||||
* [Mix] Add `mix iex` that redirects users to the proper `iex -S mix` command
|
||||
* [Mix] Support `build_per_environment: true` in project configuration that manages a separete build per environment, useful when you have per-environment behaviour/compilation
|
||||
|
||||
* Backwards incompatible changes
|
||||
* [Mix] Mix now compiles files to `_build`. Projects should update just fine, however documentation and books may want to update to the latest information
|
||||
|
||||
# v0.11.1 (2013-11-07)
|
||||
|
||||
* Enhancements
|
||||
@@ -103,7 +202,7 @@
|
||||
* Enhancements
|
||||
* [CLI] Add `--verbose` to elixirc, which now is non-verbose by default
|
||||
* [Dict] Add `Dict.Behaviour` as a convenience to create your own dictionaries
|
||||
* [Enum] Add `Enum.split/2`, `Enum.reduce/2`, `Enum.flat_map/2`, `Enum.chunks/2`, `Enum.chunks/4`, `Enum.chunks_by/2`, `Enum.concat/1` and `Enum.concat/2`
|
||||
* [Enum] Add `Enum.split/2`, `Enum.reduce/2`, `Enum.flat_map/2`, `Enum.chunk/2`, `Enum.chunk/4`, `Enum.chunk_by/2`, `Enum.concat/1` and `Enum.concat/2`
|
||||
* [Enum] Support negative indices in `Enum.at/fetch/fetch!`
|
||||
* [ExUnit] Show failures on CLIFormatter as soon as they pop up
|
||||
* [IEx] Allow for strings in `h` helper
|
||||
@@ -289,7 +388,7 @@
|
||||
* [Bitwise] Precedence of operators used by the Bitwise module were changed, check `elixir_parser.yrl` for more information
|
||||
* [File] `rm_rf` and `cp_r` now returns a tuple with three elements on failures
|
||||
* [Kernel] The quoted representation for `->` clauses changed from a tuple with two elements to a tuple with three elements to support metadata
|
||||
* [Kernel] Sigils now dispatch to `sigil_$` instead of `__$__` where `$` is the sigil caracter
|
||||
* [Kernel] Sigils now dispatch to `sigil_$` instead of `__$__` where `$` is the sigil character
|
||||
* [Macro] `Macro.expand/2` now expands until final form. Although this is backwards incompatible, it is very likely you do not need to change your code, since expansion until its final form is recommended, particularly if you are expecting an atom out of it
|
||||
* [Mix] No longer support beam files on `mix local`
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
REBAR := "$(shell echo `pwd`/rebar)"
|
||||
REBAR := rebar
|
||||
ELIXIRC := bin/elixirc --verbose --ignore-module-conflict $(ELIXIRC_OPTS)
|
||||
ERLC := erlc -I lib/elixir/include
|
||||
ERL := erl -I lib/elixir/include -noshell -pa lib/elixir/ebin
|
||||
@@ -20,12 +20,16 @@ define APP_TEMPLATE
|
||||
$(1): lib/$(1)/ebin/Elixir.$(2).beam lib/$(1)/ebin/$(1).app
|
||||
|
||||
lib/$(1)/ebin/$(1).app: lib/$(1)/mix.exs
|
||||
$(Q) cd lib/$(1) && ../../bin/elixir -e "Mix.Server.start_link(:dev)" -r mix.exs -e "Mix.Task.run('compile.app')"
|
||||
$(Q) mkdir -p lib/$(1)/_build/shared/lib/$(1)
|
||||
$(Q) cp -R lib/$(1)/ebin lib/$(1)/_build/shared/lib/$(1)/
|
||||
$(Q) cd lib/$(1) && ../../bin/elixir -e "Mix.Sup.start_link()" -r mix.exs -e "Mix.Task.run('compile.app')"
|
||||
$(Q) cp lib/$(1)/_build/shared/lib/$(1)/ebin/$(1).app lib/$(1)/ebin/$(1).app
|
||||
$(Q) rm -rf lib/$(1)/_build
|
||||
|
||||
lib/$(1)/ebin/Elixir.$(2).beam: $(wildcard lib/$(1)/lib/*.ex) $(wildcard lib/$(1)/lib/*/*.ex) $(wildcard lib/$(1)/lib/*/*/*.ex)
|
||||
@ echo "==> $(1) (compile)"
|
||||
@ rm -rf lib/$(1)/ebin
|
||||
$(Q) $$(ELIXIRC) "lib/$(1)/lib/**/*.ex" -o lib/$(1)/ebin
|
||||
$(Q) cd lib/$(1) && ../../$$(ELIXIRC) "lib/**/*.ex" -o ebin
|
||||
|
||||
test_$(1): $(1)
|
||||
@ echo "==> $(1) (exunit)"
|
||||
@@ -48,7 +52,7 @@ lib/elixir/src/elixir.app.src: src/elixir.app.src
|
||||
$(Q) cat src/elixir.app.src >>lib/elixir/src/elixir.app.src
|
||||
|
||||
erlang:
|
||||
$(Q) cd lib/elixir && $(REBAR) compile
|
||||
$(Q) cd lib/elixir && ../../$(REBAR) compile
|
||||
|
||||
# Since Mix depends on EEx and EEx depends on
|
||||
# Mix, we first compile EEx without the .app
|
||||
@@ -61,17 +65,18 @@ $(KERNEL): lib/elixir/lib/*.ex lib/elixir/lib/*/*.ex
|
||||
echo "==> bootstrap (compile)"; \
|
||||
$(ERL) -s elixir_compiler core -s erlang halt; \
|
||||
fi
|
||||
@ echo "==> kernel (compile)";
|
||||
$(Q) $(ELIXIRC) "lib/elixir/lib/**/*.ex" -o lib/elixir/ebin;
|
||||
@ echo "==> elixir (compile)";
|
||||
$(Q) cd lib/elixir && ../../$(ELIXIRC) "lib/kernel.ex" -o ebin;
|
||||
$(Q) cd lib/elixir && ../../$(ELIXIRC) "lib/**/*.ex" -o ebin;
|
||||
$(Q) $(MAKE) unicode
|
||||
$(Q) rm -rf lib/elixir/ebin/elixir.app
|
||||
$(Q) cd lib/elixir && $(REBAR) compile
|
||||
$(Q) cd lib/elixir && ../../$(REBAR) compile
|
||||
|
||||
unicode: $(UNICODE)
|
||||
$(UNICODE): lib/elixir/unicode/unicode.ex lib/elixir/unicode/UnicodeData.txt lib/elixir/unicode/GraphemeBreakProperty.txt
|
||||
$(UNICODE): lib/elixir/unicode/*
|
||||
@ echo "==> unicode (compile)";
|
||||
@ echo "This step can take up to a minute to compile in order to embed the Unicode database"
|
||||
$(Q) $(ELIXIRC) lib/elixir/unicode/unicode.ex -o lib/elixir/ebin --no-debug-info;
|
||||
$(Q) cd lib/elixir && ../../$(ELIXIRC) unicode/graphemes.ex unicode/unicode.ex -o ebin;
|
||||
|
||||
$(eval $(call APP_TEMPLATE,ex_unit,ExUnit))
|
||||
$(eval $(call APP_TEMPLATE,eex,EEx))
|
||||
@@ -92,9 +97,10 @@ install: compile
|
||||
done
|
||||
|
||||
clean:
|
||||
cd lib/elixir && $(REBAR) clean
|
||||
cd lib/elixir && ../../$(REBAR) clean
|
||||
rm -rf ebin
|
||||
rm -rf lib/*/ebin
|
||||
rm -rf lib/elixir/test/ebin
|
||||
rm -rf lib/*/tmp
|
||||
rm -rf lib/mix/test/fixtures/git_repo
|
||||
rm -rf lib/mix/test/fixtures/deps_on_git_repo
|
||||
@@ -112,7 +118,7 @@ docs: compile ../ex_doc/bin/ex_doc
|
||||
mkdir -p ebin
|
||||
rm -rf docs
|
||||
cp -R -f lib/*/ebin/*.beam ./ebin
|
||||
bin/elixir ../ex_doc/bin/ex_doc "Elixir" "$(VERSION)" -m Kernel -u "https://github.com/elixir-lang/elixir" --source-ref "$(call SOURCE_REF)"
|
||||
bin/elixir ../ex_doc/bin/ex_doc "Elixir" "$(VERSION)" "./ebin" -m Kernel -u "https://github.com/elixir-lang/elixir" --source-ref "$(call SOURCE_REF)"
|
||||
rm -rf ebin
|
||||
|
||||
../ex_doc/bin/ex_doc:
|
||||
@@ -124,9 +130,9 @@ release_zip: compile
|
||||
zip -9 -r v$(VERSION).zip bin CHANGELOG.md LEGAL lib/*/ebin LICENSE README.md rel VERSION
|
||||
|
||||
release_docs: docs
|
||||
cd ../elixir-lang.github.com
|
||||
rm -rf ../elixir-lang.github.com/docs/master
|
||||
mv docs ../elixir-lang.github.com/docs/master
|
||||
cd ../docs
|
||||
rm -rf ../docs/master
|
||||
mv docs ../docs/master
|
||||
|
||||
release_erl: compile
|
||||
$(Q) rm -rf rel/elixir
|
||||
@@ -150,7 +156,7 @@ test_doc_test: compile
|
||||
$(Q) cd lib/elixir && ../../bin/elixir -r "test/doc_test.exs";
|
||||
|
||||
test_kernel: compile
|
||||
@ echo "==> kernel (exunit)"
|
||||
@ echo "==> elixir (exunit)"
|
||||
$(Q) cd lib/elixir && ../../bin/elixir -r "test/elixir/test_helper.exs" -pr "test/elixir/**/*_test.exs";
|
||||
|
||||
.dialyzer.base_plt:
|
||||
|
||||
+2
-2
@@ -14,13 +14,13 @@ This document simply outlines the release process:
|
||||
|
||||
6) Release new docs with `make release_docs`, move docs to `docs/stable`
|
||||
|
||||
7) Release new zip with `make release_zip`, push new zip to Github Releases
|
||||
7) Release new zip with `make release_zip`, push new zip to GitHub Releases
|
||||
|
||||
8) Push package to expm with `expm publish package.exs`
|
||||
|
||||
9) Merge master into stable branch and push it
|
||||
|
||||
10) After release, bump versions and add `-dev` back
|
||||
10) After release, bump versions, add `-dev` back and commit
|
||||
|
||||
11) `make release_docs` once again and push `elixir-lang.github.com`
|
||||
|
||||
|
||||
+1
-1
@@ -5,7 +5,7 @@ if [ $# -eq 0 ] || [ "$1" = "--help" ] || [ "$1" = "-h" ]; then
|
||||
-v Prints version and exit
|
||||
-e \"command\" Evaluates the given command (*)
|
||||
-r \"file\" Requires the given files/patterns (*)
|
||||
-S \"script\" Finds and executes the given script (*)
|
||||
-S \"script\" Finds and executes the given script
|
||||
-pr \"file\" Requires the given files/patterns in parallel (*)
|
||||
-pa \"path\" Prepends the given path to Erlang code path (*)
|
||||
-pz \"path\" Appends the given path to Erlang code path (*)
|
||||
|
||||
+1
-1
@@ -12,7 +12,7 @@ echo.
|
||||
echo -v Prints version and exit
|
||||
echo -e command Evaluates the given command (*)
|
||||
echo -r file Requires the given files/patterns (*)
|
||||
echo -S script Finds and executes the given script (*)
|
||||
echo -S script Finds and executes the given script
|
||||
echo -pr file Requires the given files/patterns in parallel (*)
|
||||
echo -pa path Prepends the given path to Erlang code path (*)
|
||||
echo -pz path Appends the given path to Erlang code path (*)
|
||||
|
||||
@@ -5,7 +5,7 @@ if [ $# -gt 0 ] && ([ "$1" = "--help" ] || [ "$1" = "-h" ]); then
|
||||
-v Prints version
|
||||
-e \"command\" Evaluates the given command (*)
|
||||
-r \"file\" Requires the given files/patterns (*)
|
||||
-S \"script\" Finds and executes the given script (*)
|
||||
-S \"script\" Finds and executes the given script
|
||||
-pr \"file\" Requires the given files/patterns in parallel (*)
|
||||
-pa \"path\" Prepends the given path to Erlang code path (*)
|
||||
-pz \"path\" Appends the given path to Erlang code path (*)
|
||||
|
||||
@@ -27,6 +27,19 @@ defmodule EEx do
|
||||
above and is available to you if you want to provide your own
|
||||
ways of handling the compiled template.
|
||||
|
||||
## Options
|
||||
|
||||
All functions in this module accepts EEx-related options.
|
||||
They are:
|
||||
|
||||
* `:line` - the line to be used as the template start.
|
||||
Defaults to 1;
|
||||
* `:file` - the file to be used in the template.
|
||||
Defaults to the given file the template is read from
|
||||
or to "nofile" when compiling from a string;
|
||||
* `:engine` - the EEx engine to be used for compilation.
|
||||
Defaults to `EEx.Engine`;
|
||||
|
||||
## Engine
|
||||
|
||||
EEx has the concept of engines which allows you to modify or
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
defrecord EEx.State, engine: EEx.SmartEngine, dict: [], file: 'nofile', line: 1, start_line: 1
|
||||
defrecord EEx.State, engine: EEx.SmartEngine, dict: [], file: "nofile", line: 1, start_line: 1
|
||||
|
||||
defmodule EEx.Compiler do
|
||||
@moduledoc false
|
||||
@@ -23,7 +23,7 @@ defmodule EEx.Compiler do
|
||||
end
|
||||
|
||||
defp generate_buffer([{ :expr, line, mark, chars }|t], buffer, scope, state) do
|
||||
expr = maybe_block :elixir_translator.forms!(chars, line, state.file, [])
|
||||
expr = Code.string_to_quoted!(chars, [line: line, file: state.file])
|
||||
buffer = state.engine.handle_expr(buffer, mark, expr)
|
||||
generate_buffer(t, buffer, scope, state)
|
||||
end
|
||||
@@ -41,7 +41,7 @@ defmodule EEx.Compiler do
|
||||
|
||||
defp generate_buffer([{ :end_expr, line, _, chars }|t], buffer, [current|_], state) do
|
||||
{ wrapped, state } = wrap_expr(current, line, buffer, chars, state)
|
||||
tuples = maybe_block :elixir_translator.forms!(wrapped, state.start_line, state.file, [])
|
||||
tuples = Code.string_to_quoted!(wrapped, [line: state.start_line, file: state.file])
|
||||
buffer = insert_quotes(tuples, state.dict)
|
||||
{ buffer, t }
|
||||
end
|
||||
@@ -63,7 +63,7 @@ defmodule EEx.Compiler do
|
||||
defp wrap_expr(current, line, buffer, chars, state) do
|
||||
new_lines = List.duplicate(?\n, line - state.line)
|
||||
|
||||
if state.dict == [] and is_empty?(buffer) do
|
||||
if state.dict == [] and empty?(buffer) do
|
||||
{ current ++ new_lines ++ chars, state }
|
||||
else
|
||||
key = length(state.dict)
|
||||
@@ -74,24 +74,18 @@ defmodule EEx.Compiler do
|
||||
|
||||
# Check if the syntax node represents an empty string
|
||||
|
||||
defp is_empty?(bin) when is_binary(bin) do
|
||||
defp empty?(bin) when is_binary(bin) do
|
||||
bc(<<c>> inbits bin, not c in [?\s, ?\t, ?\r, ?\n], do: <<c>>) == ""
|
||||
end
|
||||
|
||||
defp is_empty?({ :<>, _, [left, right] }) do
|
||||
is_empty?(left) and is_empty?(right)
|
||||
defp empty?({ :<>, _, [left, right] }) do
|
||||
empty?(left) and empty?(right)
|
||||
end
|
||||
|
||||
defp is_empty?(_) do
|
||||
defp empty?(_) do
|
||||
false
|
||||
end
|
||||
|
||||
# Block wrapping
|
||||
|
||||
defp maybe_block([]), do: nil
|
||||
defp maybe_block([h]), do: h
|
||||
defp maybe_block(other), do: { :__block__, [], other }
|
||||
|
||||
# Changes placeholder to real expression
|
||||
|
||||
defp insert_quotes({ :__EEX__, _, [key] }, dict) do
|
||||
|
||||
@@ -88,7 +88,7 @@ defmodule EEx.AssignsEngine do
|
||||
defmacro __using__(_) do
|
||||
quote unquote: false do
|
||||
defp transform({ :@, line, [{ name, _, atom }] }) when is_atom(name) and is_atom(atom) do
|
||||
quote do: Keyword.get(var!(assigns), unquote(name))
|
||||
quote do: Dict.get(var!(assigns), unquote(name))
|
||||
end
|
||||
|
||||
defp transform(arg) do
|
||||
|
||||
@@ -7,16 +7,21 @@ defmodule EEx.SmartEngineTest do
|
||||
assert_eval "foo bar", "foo bar"
|
||||
end
|
||||
|
||||
test "evaluates with assigns" do
|
||||
test "evaluates with assigns as a ListDict" do
|
||||
assert_eval "1", "<%= @foo %>", assigns: [foo: 1]
|
||||
end
|
||||
|
||||
test "evaluates with assigns as a HashDict" do
|
||||
assigns = HashDict.new [foo: 1]
|
||||
assert_eval "1", "<%= @foo %>", assigns: assigns
|
||||
end
|
||||
|
||||
test "evaluates with loops" do
|
||||
assert_eval "1\n2\n3\n", "<%= lc x inlist [1, 2, 3] do %><%= x %>\n<% end %>"
|
||||
end
|
||||
|
||||
defp assert_eval(expected, actual, binding // []) do
|
||||
result = EEx.eval_string(actual, binding, file: __FILE__)
|
||||
result = EEx.eval_string(actual, binding, file: __ENV__.file)
|
||||
assert result == expected
|
||||
end
|
||||
end
|
||||
|
||||
@@ -321,7 +321,7 @@ foo
|
||||
{ EExText.Compiled,
|
||||
:before_compile,
|
||||
0,
|
||||
[file: to_char_list(__FILE__), line: 7]
|
||||
[file: to_char_list(Path.relative_to_cwd(__ENV__.file)), line: 7]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -330,7 +330,7 @@ foo
|
||||
{ EExText.Compiled,
|
||||
:after_compile,
|
||||
0,
|
||||
[file: to_char_list(__FILE__), line: 18]
|
||||
[file: to_char_list(Path.relative_to_cwd(__ENV__.file)), line: 18]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -345,7 +345,7 @@ foo
|
||||
end
|
||||
|
||||
defp assert_eval(expected, actual) do
|
||||
result = EEx.eval_string(actual, [], file: __FILE__, engine: EEx.Engine)
|
||||
result = EEx.eval_string(actual, [], file: __ENV__.file, engine: EEx.Engine)
|
||||
assert result == expected
|
||||
end
|
||||
end
|
||||
|
||||
@@ -9,8 +9,8 @@
|
||||
-define(line(Opts), elixir_utils:get_line(Opts)).
|
||||
|
||||
-record(elixir_scope, {
|
||||
context=nil, %% can be assign, guards or nil
|
||||
extra=nil, %% extra information about the context, like fn_match for fns
|
||||
context=nil, %% can be match, guards or nil
|
||||
extra=nil, %% extra information about the context, like fn_match and do_match
|
||||
noname=false, %% when true, don't add new names (used by try)
|
||||
super=false, %% when true, it means super was invoked
|
||||
caller=false, %% when true, it means caller was invoked
|
||||
@@ -18,26 +18,34 @@
|
||||
function=nil, %% the current function
|
||||
vars=[], %% a dict of defined variables and their alias
|
||||
backup_vars=nil, %% a copy of vars to be used on ^var
|
||||
temp_vars=nil, %% a set of all variables defined in a particular assign
|
||||
clause_vars=nil, %% a dict of all variables defined in a particular clause
|
||||
match_vars=nil, %% a set of all variables defined in a particular match
|
||||
export_vars=nil, %% a dict of all variables defined in a particular clause
|
||||
extra_guards=nil, %% extra guards from args expansion
|
||||
counter=[], %% a counter for the variables defined
|
||||
local=nil, %% the scope to evaluate local functions against
|
||||
context_modules=[], %% modules defined in the current context
|
||||
counter=[], %% a dict counting the variables defined
|
||||
file=(<<"nofile">>) %% the current scope filename
|
||||
}).
|
||||
|
||||
-record(elixir_env, {
|
||||
module=nil, %% the current module
|
||||
file=(<<"nofile">>), %% the current filename
|
||||
line=1, %% the current line
|
||||
function=nil, %% the current function
|
||||
context=nil, %% can be match_vars, guards or nil
|
||||
requires=[], %% a set with modules required
|
||||
aliases=[], %% an orddict with aliases by new -> old names
|
||||
functions=[], %% a list with functions imported from module
|
||||
macros=[], %% a list with macros imported from module
|
||||
macro_aliases=[], %% keep aliases defined inside a macro
|
||||
macro_counter=0, %% macros expansions counter
|
||||
context_modules=[], %% modules defined in the current context
|
||||
vars=[], %% a set of defined variables
|
||||
export_vars=nil, %% a set of variables to be exported in certain constructs, may be nil
|
||||
lexical_tracker=nil, %% holds the lexical tracker pid
|
||||
aliases, %% an orddict with aliases by new -> old names
|
||||
file, %% the current scope filename
|
||||
requires, %% a set with modules required
|
||||
macro_macros=[], %% a list with macros imported from module inside a macro
|
||||
macros, %% a list with macros imported from module
|
||||
macro_functions=[], %% a list with functions imported from module inside a macro
|
||||
functions %% a list with functions imported from module
|
||||
local=nil %% the module to delegate local functions to
|
||||
}).
|
||||
|
||||
-record(elixir_quote, {
|
||||
line=nil,
|
||||
line=false,
|
||||
keep=false,
|
||||
context=nil,
|
||||
vars_hygiene=true,
|
||||
aliases_hygiene=true,
|
||||
@@ -54,9 +62,6 @@
|
||||
existing_atoms_only=false
|
||||
}).
|
||||
|
||||
%% Introspection
|
||||
-define(defs(Kind), Kind == def; Kind == defp; Kind == defmacro; Kind == defmacrop).
|
||||
|
||||
%% Used in tokenization and interpolation
|
||||
|
||||
%% Numbers
|
||||
|
||||
+88
-21
@@ -36,7 +36,7 @@ defmodule Behaviour do
|
||||
`@callback` attributes. `defcallback` is a simple
|
||||
mechanism that defines the `@callback` attribute
|
||||
according to the given type specification. `defcallback` allows
|
||||
documentsion to be created for the callback and defines
|
||||
documentation to be created for the callback and defines
|
||||
a custom function signature.
|
||||
|
||||
The callbacks and their documentation can be retrieved
|
||||
@@ -46,44 +46,60 @@ defmodule Behaviour do
|
||||
@doc """
|
||||
Define a function callback according to the given type specification.
|
||||
"""
|
||||
defmacro defcallback({ :::, _, [fun, return] }) do
|
||||
do_defcallback(fun, return, __CALLER__)
|
||||
defmacro defcallback({ :::, _, [fun, return_and_guard] }) do
|
||||
{ return, guard } = split_return_and_guard(return_and_guard)
|
||||
do_defcallback(fun, return, guard, __CALLER__)
|
||||
end
|
||||
|
||||
defmacro defcallback(fun) do
|
||||
do_defcallback(fun, quote(do: term), __CALLER__)
|
||||
do_defcallback(fun, quote(do: term), [], __CALLER__)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Define a macro callback according to the given type specification.
|
||||
"""
|
||||
defmacro defmacrocallback({ :::, _, [fun, return] }) do
|
||||
do_defmacrocallback(fun, return, __CALLER__)
|
||||
defmacro defmacrocallback({ :::, _, [fun, return_and_guard] }) do
|
||||
{ return, guard } = split_return_and_guard(return_and_guard)
|
||||
do_defmacrocallback(fun, return, guard, __CALLER__)
|
||||
end
|
||||
|
||||
defmacro defmacrocallback(fun) do
|
||||
do_defmacrocallback(fun, quote(do: Macro.t), __CALLER__)
|
||||
do_defmacrocallback(fun, quote(do: Macro.t), [], __CALLER__)
|
||||
end
|
||||
|
||||
defp do_defcallback(fun, spec, caller) do
|
||||
case Macro.extract_args(fun) do
|
||||
defp split_return_and_guard({ :when, _, [return, guard] }) do
|
||||
{ return, guard }
|
||||
end
|
||||
|
||||
defp split_return_and_guard({ :|, meta, [left, right] }) do
|
||||
{ return, guard } = split_return_and_guard(right)
|
||||
{ { :|, meta, [left, return] }, guard }
|
||||
end
|
||||
|
||||
defp split_return_and_guard(other) do
|
||||
{ other, [] }
|
||||
end
|
||||
|
||||
defp do_defcallback(fun, return, guards, caller) do
|
||||
case Macro.decompose_call(fun) do
|
||||
{ name, args } ->
|
||||
do_callback(:def, name, args, name, length(args), spec, caller)
|
||||
:error ->
|
||||
do_callback(:def, name, args, name, length(args), args, return, guards, caller)
|
||||
_ ->
|
||||
raise ArgumentError, message: "invalid syntax in defcallback #{Macro.to_string(fun)}"
|
||||
end
|
||||
end
|
||||
|
||||
defp do_defmacrocallback(fun, spec, caller) do
|
||||
case Macro.extract_args(fun) do
|
||||
defp do_defmacrocallback(fun, return, guards, caller) do
|
||||
case Macro.decompose_call(fun) do
|
||||
{ name, args } ->
|
||||
do_callback(:defmacro, :"MACRO-#{name}", [quote(do: env :: Macro.Env.t)|args], name, length(args), spec, caller)
|
||||
:error ->
|
||||
do_callback(:defmacro, :"MACRO-#{name}", [quote(do: env :: Macro.Env.t)|args],
|
||||
name, length(args), args, return, guards, caller)
|
||||
_ ->
|
||||
raise ArgumentError, message: "invalid syntax in defmacrocallback #{Macro.to_string(fun)}"
|
||||
end
|
||||
end
|
||||
|
||||
defp do_callback(kind, name, args, docs_name, docs_arity, return, caller) do
|
||||
defp do_callback(kind, name, args, docs_name, docs_arity, docs_args, return, guards, caller) do
|
||||
Enum.each args, fn
|
||||
{ :::, _, [left, right] } ->
|
||||
ensure_not_default(left)
|
||||
@@ -95,24 +111,75 @@ defmodule Behaviour do
|
||||
end
|
||||
|
||||
quote do
|
||||
@callback unquote(name)(unquote_splicing(args)) :: unquote(return)
|
||||
Behaviour.store_docs __MODULE__, unquote(caller.line), unquote(kind), unquote(docs_name), unquote(docs_arity)
|
||||
docs_args = unquote(Macro.escape(docs_args))
|
||||
@callback unquote(name)(unquote_splicing(args)) :: unquote(return) when unquote(guards)
|
||||
Behaviour.store_docs(__MODULE__, unquote(caller.line), unquote(kind),
|
||||
unquote(docs_name), docs_args, unquote(docs_arity))
|
||||
end
|
||||
end
|
||||
|
||||
defp ensure_not_default({ ://, _, [_, _] }) do
|
||||
raise ArgumentError, message: "default arguments // not supported in defcallback"
|
||||
raise ArgumentError, message: "default arguments // not supported in defcallback/defmacrocallback"
|
||||
end
|
||||
|
||||
defp ensure_not_default(_), do: :ok
|
||||
|
||||
@doc false
|
||||
def store_docs(module, line, kind, name, arity) do
|
||||
def store_docs(module, line, kind, name, args, arity) do
|
||||
doc = Module.get_attribute module, :doc
|
||||
Module.delete_attribute module, :doc
|
||||
Module.put_attribute module, :behaviour_docs, { { name, arity }, line, kind, doc }
|
||||
|
||||
signature = Enum.map(Stream.with_index(args), fn { arg, ix } ->
|
||||
{ simplify_signature(arg, ix + 1), [line: line], nil }
|
||||
end)
|
||||
|
||||
Module.put_attribute module, :behaviour_docs, { { name, arity }, line, kind, signature, doc }
|
||||
end
|
||||
|
||||
defp simplify_signature({ :::, _, [var, _] }, i) do
|
||||
simplify_signature(var, i)
|
||||
end
|
||||
|
||||
defp simplify_signature({ :|, _, [first, _] }, i) do
|
||||
simplify_signature(first, i)
|
||||
end
|
||||
|
||||
defp simplify_signature({ :., _, [_, name] }, _i) do
|
||||
name
|
||||
end
|
||||
|
||||
defp simplify_signature({ :->, _, list }, i) when is_list(list) do
|
||||
:"fun#{i}"
|
||||
end
|
||||
|
||||
defp simplify_signature({ :.., _, list }, i) when is_list(list) do
|
||||
:"range#{i}"
|
||||
end
|
||||
|
||||
defp simplify_signature({ :<<>>, _, list }, i) when is_list(list) do
|
||||
:"bitstring#{i}"
|
||||
end
|
||||
|
||||
defp simplify_signature({ :{}, _, list }, i) when is_list(list) do
|
||||
:"tuple#{i}"
|
||||
end
|
||||
|
||||
defp simplify_signature({ name, _, args }, _i)
|
||||
when is_atom(name) and (is_atom(args) or is_list(args)) do
|
||||
name
|
||||
end
|
||||
|
||||
defp simplify_signature({ ast, _, list }, i) when is_list(list) do
|
||||
simplify_signature(ast, i)
|
||||
end
|
||||
|
||||
defp simplify_signature(other, i) when is_integer(other), do: :"int#{i}"
|
||||
defp simplify_signature(other, i) when is_boolean(other), do: :"bool#{i}"
|
||||
defp simplify_signature(other, i) when is_atom(other), do: :"atom#{i}"
|
||||
defp simplify_signature(other, i) when is_list(other), do: :"list#{i}"
|
||||
defp simplify_signature(other, i) when is_tuple(other), do: :"tuple#{i}"
|
||||
defp simplify_signature(_, i), do: :"arg#{i}"
|
||||
|
||||
@doc false
|
||||
defmacro __using__(_) do
|
||||
quote do
|
||||
|
||||
+12
-12
@@ -46,83 +46,83 @@ defmodule Bitwise do
|
||||
Bitwise not.
|
||||
"""
|
||||
defmacro bnot(expr) do
|
||||
quote do: __op__(:bnot, unquote(expr))
|
||||
quote do: :erlang.bnot(unquote(expr))
|
||||
end
|
||||
|
||||
@doc """
|
||||
Bitwise not as operator.
|
||||
"""
|
||||
defmacro ~~~expr do
|
||||
quote do: __op__(:bnot, unquote(expr))
|
||||
quote do: :erlang.bnot(unquote(expr))
|
||||
end
|
||||
|
||||
@doc """
|
||||
Bitwise and.
|
||||
"""
|
||||
defmacro band(left, right) do
|
||||
quote do: __op__(:band, unquote(left), unquote(right))
|
||||
quote do: :erlang.band(unquote(left), unquote(right))
|
||||
end
|
||||
|
||||
@doc """
|
||||
Bitwise and as operator.
|
||||
"""
|
||||
defmacro left &&& right do
|
||||
quote do: __op__(:band, unquote(left), unquote(right))
|
||||
quote do: :erlang.band(unquote(left), unquote(right))
|
||||
end
|
||||
|
||||
@doc """
|
||||
Bitwise or.
|
||||
"""
|
||||
defmacro bor(left, right) do
|
||||
quote do: __op__(:bor, unquote(left), unquote(right))
|
||||
quote do: :erlang.bor(unquote(left), unquote(right))
|
||||
end
|
||||
|
||||
@doc """
|
||||
Bitwise or as operator.
|
||||
"""
|
||||
defmacro left ||| right do
|
||||
quote do: __op__(:bor, unquote(left), unquote(right))
|
||||
quote do: :erlang.bor(unquote(left), unquote(right))
|
||||
end
|
||||
|
||||
@doc """
|
||||
Bitwise xor.
|
||||
"""
|
||||
defmacro bxor(left, right) do
|
||||
quote do: __op__(:bxor, unquote(left), unquote(right))
|
||||
quote do: :erlang.bxor(unquote(left), unquote(right))
|
||||
end
|
||||
|
||||
@doc """
|
||||
Bitwise xor as operator.
|
||||
"""
|
||||
defmacro left ^^^ right do
|
||||
quote do: __op__(:bxor, unquote(left), unquote(right))
|
||||
quote do: :erlang.bxor(unquote(left), unquote(right))
|
||||
end
|
||||
|
||||
@doc """
|
||||
Arithmetic bitshift left.
|
||||
"""
|
||||
defmacro bsl(left, right) do
|
||||
quote do: __op__(:bsl, unquote(left), unquote(right))
|
||||
quote do: :erlang.bsl(unquote(left), unquote(right))
|
||||
end
|
||||
|
||||
@doc """
|
||||
Arithmetic bitshift left as operator.
|
||||
"""
|
||||
defmacro left <<< right do
|
||||
quote do: __op__(:bsl, unquote(left), unquote(right))
|
||||
quote do: :erlang.bsl(unquote(left), unquote(right))
|
||||
end
|
||||
|
||||
@doc """
|
||||
Arithmetic bitshift right.
|
||||
"""
|
||||
defmacro bsr(left, right) do
|
||||
quote do: __op__(:bsr, unquote(left), unquote(right))
|
||||
quote do: :erlang.bsr(unquote(left), unquote(right))
|
||||
end
|
||||
|
||||
@doc """
|
||||
Arithmetic bitshift right as operator.
|
||||
"""
|
||||
defmacro left >>> right do
|
||||
quote do: __op__(:bsr, unquote(left), unquote(right))
|
||||
quote do: :erlang.bsr(unquote(left), unquote(right))
|
||||
end
|
||||
end
|
||||
|
||||
+11
-17
@@ -1,7 +1,8 @@
|
||||
defmodule Code do
|
||||
defexception LoadError, file: nil do
|
||||
def message(exception) do
|
||||
"could not load #{exception.file}"
|
||||
defexception LoadError, [:file, :message] do
|
||||
def exception(opts) do
|
||||
file = opts[:file]
|
||||
LoadError[message: "could not load #{file}", file: file]
|
||||
end
|
||||
end
|
||||
|
||||
@@ -10,7 +11,6 @@ defmodule Code do
|
||||
|
||||
This module complements [Erlang's code module](http://www.erlang.org/doc/man/code.html)
|
||||
to add behavior which is specific to Elixir.
|
||||
|
||||
"""
|
||||
|
||||
@doc """
|
||||
@@ -127,7 +127,7 @@ defmodule Code do
|
||||
end
|
||||
|
||||
defp do_eval_string(string, binding, opts) when is_list(binding) do
|
||||
{ value, binding, _scope } = :elixir.eval to_char_list(string), binding, opts
|
||||
{ value, binding, _env, _scope } = :elixir.eval to_char_list(string), binding, opts
|
||||
{ value, binding }
|
||||
end
|
||||
|
||||
@@ -162,8 +162,7 @@ defmodule Code do
|
||||
end
|
||||
|
||||
defp do_eval_quoted(quoted, binding, opts) when is_list(binding) do
|
||||
{ value, binding, _scope } =
|
||||
:elixir.eval_quoted [quoted], binding, opts
|
||||
{ value, binding, _env, _scope } = :elixir.eval_quoted quoted, binding, opts
|
||||
{ value, binding }
|
||||
end
|
||||
|
||||
@@ -229,10 +228,10 @@ defmodule Code do
|
||||
def string_to_quoted(string, opts // []) when is_list(opts) do
|
||||
file = Keyword.get opts, :file, "nofile"
|
||||
line = Keyword.get opts, :line, 1
|
||||
res = :elixir_translator.forms(to_char_list(string), line, file, opts)
|
||||
res = :elixir.string_to_quoted(to_char_list(string), line, file, opts)
|
||||
|
||||
case res do
|
||||
{ :ok, forms } -> { :ok, unpack_quote(line, forms) }
|
||||
{ :ok, forms } -> { :ok, forms }
|
||||
_ -> res
|
||||
end
|
||||
end
|
||||
@@ -250,14 +249,9 @@ defmodule Code do
|
||||
def string_to_quoted!(string, opts // []) when is_list(opts) do
|
||||
file = Keyword.get opts, :file, "nofile"
|
||||
line = Keyword.get opts, :line, 1
|
||||
res = :elixir_translator.forms!(to_char_list(string), line, file, opts)
|
||||
unpack_quote(line, res)
|
||||
:elixir.string_to_quoted!(to_char_list(string), line, file, opts)
|
||||
end
|
||||
|
||||
defp unpack_quote(_line, []), do: nil
|
||||
defp unpack_quote(_line, [forms]) when not is_list(forms), do: forms
|
||||
defp unpack_quote(line, forms), do: { :__block__, [line: line], forms }
|
||||
|
||||
@doc """
|
||||
Load the given file.
|
||||
|
||||
@@ -353,7 +347,7 @@ defmodule Code do
|
||||
For compiling many files at once, check `Kernel.ParallelCompiler.files/2`.
|
||||
"""
|
||||
def compile_string(string, file // "nofile") when is_binary(file) do
|
||||
:elixir_compiler.string String.to_char_list!(string), file
|
||||
:elixir_compiler.string to_char_list(string), file
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -364,7 +358,7 @@ defmodule Code do
|
||||
binary.
|
||||
"""
|
||||
def compile_quoted(quoted, file // "nofile") when is_binary(file) do
|
||||
:elixir_compiler.quoted [quoted], file
|
||||
:elixir_compiler.quoted quoted, file
|
||||
end
|
||||
|
||||
@doc """
|
||||
|
||||
@@ -70,7 +70,6 @@ defmodule Dict do
|
||||
defcallback update(t, key, value, (value -> value)) :: t
|
||||
defcallback update!(t, key, (value -> value)) :: t | no_return
|
||||
defcallback values(t) :: list(value)
|
||||
defcallback reduce(t, any, ({key, value}, any -> any)) :: any
|
||||
|
||||
defmacrop target(dict) do
|
||||
quote do
|
||||
@@ -102,7 +101,7 @@ defmodule Dict do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns a list of all values in `dict`
|
||||
Returns a list of all values in `dict`.
|
||||
The values are not guaranteed to be in any order.
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
defmodule Dict.Behaviour do
|
||||
@moduledoc """
|
||||
Default implementations for some required functions in the `Dict` module.
|
||||
|
||||
This module makes it easier to create your own `Dict` compliant
|
||||
module, by providing default implementations for some required functions.
|
||||
|
||||
Usage:
|
||||
## Example
|
||||
|
||||
defmodule MyDict do
|
||||
use Dict.Behaviour
|
||||
@@ -13,17 +15,39 @@ defmodule Dict.Behaviour do
|
||||
# override default implementations if needed
|
||||
end
|
||||
|
||||
The client module must contain following functions: `size/1`, `fetch/2`,
|
||||
`put/3`, `update/4`, `delete/2` and `reduce/3`. All of them are part of
|
||||
the Dict behaviour, so no extra functions are actually required.
|
||||
The client module must contain the following functions:
|
||||
|
||||
* `delete/2`
|
||||
* `fetch/2`
|
||||
* `put/3`
|
||||
* `reduce/3`
|
||||
* `size/1`
|
||||
* `update/4`
|
||||
|
||||
All of which are part of the `Dict` behaviour, so no extra functions are
|
||||
actually required.
|
||||
|
||||
Based on these functions, `Dict.Behaviour` generates default implementations
|
||||
for other functions such as `drop`, `take`, etc. All of the functions are
|
||||
defined as overridable, so you can provide your own implementation if
|
||||
needed.
|
||||
for the following functions:
|
||||
|
||||
If you implement `new/0` and `new/1` functions, you can also test your custom
|
||||
module via `Dict` doctests:
|
||||
* `drop/2`
|
||||
* `equal?/2`
|
||||
* `fetch!/2`
|
||||
* `get/2`
|
||||
* `get/3`
|
||||
* `has_key?/2`
|
||||
* `keys/1`
|
||||
* `merge/2`
|
||||
* `merge/3`
|
||||
* `put_new/3`
|
||||
* `take/2`
|
||||
* `to_list/1`
|
||||
* `values/1`
|
||||
|
||||
All of these functions are defined as overridable, so you can provide your own
|
||||
implementation if needed.
|
||||
|
||||
Note you can also test your custom module via `Dict`'s doctests:
|
||||
|
||||
defmodule MyDict do
|
||||
def new(keywords // []) do
|
||||
@@ -48,37 +72,31 @@ defmodule Dict.Behaviour do
|
||||
:error -> default
|
||||
end
|
||||
end
|
||||
defoverridable get: 2, get: 3
|
||||
|
||||
def fetch!(dict, key) do
|
||||
case fetch(dict, key) do
|
||||
{ :ok, value } -> value
|
||||
:error -> raise(KeyError, key: key)
|
||||
:error -> raise KeyError, key: key
|
||||
end
|
||||
end
|
||||
defoverridable fetch!: 2
|
||||
|
||||
def has_key?(dict, key) do
|
||||
match? { :ok, _ }, fetch(dict, key)
|
||||
end
|
||||
defoverridable has_key?: 2
|
||||
|
||||
def put_new(dict, key, value) do
|
||||
update(dict, key, value, fn(v) -> v end)
|
||||
end
|
||||
defoverridable put_new: 3
|
||||
|
||||
def drop(dict, []), do: dict
|
||||
|
||||
def drop(dict, [key|keys]) do
|
||||
drop(delete(dict, key), keys)
|
||||
end
|
||||
defoverridable drop: 2
|
||||
|
||||
def take(dict, keys) do
|
||||
take(dict, keys, new)
|
||||
end
|
||||
defoverridable take: 2
|
||||
|
||||
defp take(_dict, [], acc), do: acc
|
||||
defp take(dict, [key|keys], acc) do
|
||||
@@ -88,38 +106,48 @@ defmodule Dict.Behaviour do
|
||||
end
|
||||
end
|
||||
|
||||
def to_list(dict), do: reduce(dict, [], &[&1|&2]) |> Enum.reverse
|
||||
defoverridable to_list: 1
|
||||
def to_list(dict) do
|
||||
reduce(dict, { :cont, [] }, fn
|
||||
kv, acc -> { :cont, [kv|acc] }
|
||||
end) |> elem(1) |> :lists.reverse
|
||||
end
|
||||
|
||||
def keys(dict), do: reduce(dict, [], fn({k, _}, acc) -> [k | acc] end) |> Enum.reverse
|
||||
defoverridable keys: 1
|
||||
def keys(dict) do
|
||||
reduce(dict, { :cont, [] }, fn
|
||||
{k, _}, acc -> { :cont, [k|acc] }
|
||||
end) |> elem(1) |> :lists.reverse
|
||||
end
|
||||
|
||||
def values(dict), do: reduce(dict, [], fn({_, v}, acc) -> [v | acc] end) |> Enum.reverse
|
||||
defoverridable values: 1
|
||||
def values(dict) do
|
||||
reduce(dict, { :cont, [] }, fn
|
||||
{_, v}, acc -> { :cont, [v|acc] }
|
||||
end) |> elem(1) |> :lists.reverse
|
||||
end
|
||||
|
||||
def equal?(dict1, dict2) do
|
||||
import Kernel, except: [size: 1]
|
||||
case size(dict1) == size(dict2) do
|
||||
false -> false
|
||||
true ->
|
||||
try do
|
||||
reduce(dict1, nil, fn({ k, v }, _acc) ->
|
||||
unless fetch(dict2, k) == { :ok, v }, do: throw(:error)
|
||||
end)
|
||||
true
|
||||
catch
|
||||
:error -> false
|
||||
end
|
||||
true ->
|
||||
reduce(dict1, { :cont, true }, fn({ k, v }, _acc) ->
|
||||
unless fetch(dict2, k) == { :ok, v } do
|
||||
{ :halt, false }
|
||||
else
|
||||
{ :cont, true }
|
||||
end
|
||||
end) |> elem(1)
|
||||
end
|
||||
end
|
||||
defoverridable equal?: 2
|
||||
|
||||
def merge(dict, enumerable, callback // fn(_k, _v1, v2) -> v2 end) do
|
||||
Enum.reduce(enumerable, dict, fn({key, value}, acc) ->
|
||||
update(acc, key, value, fn(v1) -> callback.(key, v1, value) end)
|
||||
end)
|
||||
end
|
||||
defoverridable merge: 2, merge: 3
|
||||
|
||||
defoverridable merge: 2, merge: 3, equal?: 2, to_list: 1, keys: 1,
|
||||
values: 1, take: 2, drop: 2, get: 2, get: 3, fetch!: 2,
|
||||
has_key?: 2, put_new: 3
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
+948
-798
File diff suppressed because it is too large
Load Diff
+80
-42
@@ -1,3 +1,7 @@
|
||||
# Some exceptions implement `message/1` instead of `exception/1` mostly
|
||||
# for bootstrap reasons. It is recommended for applications to implement
|
||||
# `exception/1` instead of `message/1` as described in `defexception/3` docs.
|
||||
|
||||
defexception RuntimeError, message: "runtime error"
|
||||
defexception ArgumentError, message: "argument error"
|
||||
defexception ArithmeticError, message: "bad argument in arithmetic expression"
|
||||
@@ -5,19 +9,22 @@ 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)}#{exception.description}"
|
||||
Exception.format_file_line(Path.relative_to_cwd(exception.file), exception.line) <>
|
||||
exception.description
|
||||
end
|
||||
end
|
||||
|
||||
defexception TokenMissingError, [file: nil, line: nil, description: "expression is incomplete"] do
|
||||
def message(exception) do
|
||||
"#{Exception.format_file_line(exception.file, exception.line)}#{exception.description}"
|
||||
Exception.format_file_line(Path.relative_to_cwd(exception.file), exception.line) <>
|
||||
exception.description
|
||||
end
|
||||
end
|
||||
|
||||
defexception CompileError, [file: nil, line: nil, description: "compile error"] do
|
||||
def message(exception) do
|
||||
"#{Exception.format_file_line(exception.file, exception.line)}#{exception.description}"
|
||||
Exception.format_file_line(Path.relative_to_cwd(exception.file), exception.line) <>
|
||||
exception.description
|
||||
end
|
||||
end
|
||||
|
||||
@@ -47,7 +54,8 @@ end
|
||||
|
||||
defexception BadArityError, [function: nil, args: nil] do
|
||||
def message(exception) do
|
||||
"bad arity error: #{inspect(exception.function)} called with #{inspect(exception.args)}"
|
||||
args = Enum.map_join(exception.args, ", ", &inspect/1)
|
||||
"bad arity error: #{inspect(exception.function)} called with (#{args})"
|
||||
end
|
||||
end
|
||||
|
||||
@@ -100,25 +108,56 @@ defexception Enum.OutOfBoundsError, message: "out of bounds error"
|
||||
|
||||
defexception Enum.EmptyError, message: "empty error"
|
||||
|
||||
defexception File.Error, [reason: nil, action: "", path: nil] do
|
||||
def message(exception) do
|
||||
formatted = iolist_to_binary(:file.format_error(reason exception))
|
||||
"could not #{action exception} #{path exception}: #{formatted}"
|
||||
end
|
||||
end
|
||||
|
||||
defexception File.CopyError, [reason: nil, action: "", source: nil, destination: nil, on: nil] do
|
||||
def message(exception) do
|
||||
formatted = iolist_to_binary(:file.format_error(reason exception))
|
||||
location = if on = on(exception), do: ". #{on}", else: ""
|
||||
"could not #{action exception} from #{source exception} to " <>
|
||||
"#{destination exception}#{location}: #{formatted}"
|
||||
end
|
||||
end
|
||||
|
||||
defmodule Exception do
|
||||
@moduledoc """
|
||||
Several convenience functions to work with and pretty print
|
||||
Convenience functions to work with and pretty print
|
||||
exceptions and stacktraces.
|
||||
|
||||
Notice that stacktraces in Elixir are updated on errors.
|
||||
For example, at any given moement, `System.stacktrace`
|
||||
will return the stacktrace for the last error that ocurred
|
||||
in the current process.
|
||||
|
||||
That said, many of the functions in this module will
|
||||
automatically calculate the stacktrace based on the caller,
|
||||
when invoked without arguments, changing the value of
|
||||
`System.stacktrace`. If instead you want to format the
|
||||
stacktrace of the latest error, you should instead explicitly
|
||||
pass the `System.stacktrace` as argument.
|
||||
"""
|
||||
|
||||
@doc """
|
||||
Normalizes an exception, converting Erlang exceptions
|
||||
to Elixir exceptions. It takes the kind spilled by
|
||||
`catch` as an argument as a convenience for converting only
|
||||
`:errors`, ignorning the others.
|
||||
to Elixir exceptions.
|
||||
|
||||
It takes the `kind` spilled by `catch` as an argument and
|
||||
normalizes only `:error`, returning the untouched payload
|
||||
for others.
|
||||
"""
|
||||
def normalize(:error, exception), do: normalize(exception)
|
||||
def normalize(_kind, other), do: other
|
||||
|
||||
@doc """
|
||||
Normalizes an exception, converting Erlang exceptions
|
||||
to Elixir exceptions. Useful when interfacing Erlang
|
||||
code with Elixir code.
|
||||
to Elixir exceptions.
|
||||
|
||||
Useful when interfacing Erlang code with Elixir code.
|
||||
"""
|
||||
def normalize(exception) when is_exception(exception) do
|
||||
exception
|
||||
@@ -176,41 +215,45 @@ defmodule Exception do
|
||||
|
||||
@doc """
|
||||
Receives a tuple representing a stacktrace entry and formats it.
|
||||
|
||||
The current working directory may be given as an argument,
|
||||
otherwise one is automatically retrieved.
|
||||
"""
|
||||
def format_stacktrace_entry(entry, cwd // nil)
|
||||
def format_stacktrace_entry(entry)
|
||||
|
||||
# From Macro.Env.stacktrace
|
||||
def format_stacktrace_entry({ module, :__MODULE__, 0, file_line }, cwd) do
|
||||
"#{format_location(file_line, cwd)}#{inspect module} (module)"
|
||||
def format_stacktrace_entry({ module, :__MODULE__, 0, location }) do
|
||||
format_location(location) <> inspect(module) <> " (module)"
|
||||
end
|
||||
|
||||
# From :elixir_compiler_*
|
||||
def format_stacktrace_entry({ _module, :__MODULE__, 2, file_line }, cwd) do
|
||||
"#{format_location(file_line, cwd)}(module)"
|
||||
def format_stacktrace_entry({ _module, :__MODULE__, 1, location }) do
|
||||
format_location(location) <> "(module)"
|
||||
end
|
||||
|
||||
# From :elixir_compiler_*
|
||||
def format_stacktrace_entry({ _module, :__FILE__, 2, file_line }, cwd) do
|
||||
"#{format_location(file_line, cwd)}(file)"
|
||||
def format_stacktrace_entry({ _module, :__FILE__, 1, location }) do
|
||||
format_location(location) <> "(file)"
|
||||
end
|
||||
|
||||
def format_stacktrace_entry({module, fun, arity, file_line}, cwd) do
|
||||
"#{format_location(file_line, cwd)}#{format_mfa(module, fun, arity)}"
|
||||
def format_stacktrace_entry({module, fun, arity, location}) do
|
||||
format_application(module) <> format_location(location) <> format_mfa(module, fun, arity)
|
||||
end
|
||||
|
||||
def format_stacktrace_entry({fun, arity, file_line}, cwd) do
|
||||
"#{format_location(file_line, cwd)}#{format_fa(fun, arity)}"
|
||||
def format_stacktrace_entry({fun, arity, location}) do
|
||||
format_location(location) <> format_fa(fun, arity)
|
||||
end
|
||||
|
||||
defp format_application(module) do
|
||||
case :application.get_application(module) do
|
||||
{ :ok, app } -> "(" <> atom_to_binary(app) <> ") "
|
||||
:undefined -> ""
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Formats the stacktrace.
|
||||
|
||||
A stacktrace must be given as an argument. If not, this function
|
||||
calculates the current stacktrace and formats it. As a consequence,
|
||||
the value of `System.stacktrace` is changed.
|
||||
calculates a new stacktrace based on the caller and formats it. As
|
||||
a consequence, the value of `System.stacktrace` is changed.
|
||||
"""
|
||||
def format_stacktrace(trace // nil) do
|
||||
trace = trace || try do
|
||||
@@ -219,16 +262,19 @@ defmodule Exception do
|
||||
:stacktrace -> Enum.drop(:erlang.get_stacktrace, 1)
|
||||
end
|
||||
|
||||
cwd = System.cwd
|
||||
|
||||
case trace do
|
||||
[] -> "\n"
|
||||
s -> " " <> Enum.map_join(s, "\n ", &format_stacktrace_entry(&1, cwd)) <> "\n"
|
||||
s -> " " <> Enum.map_join(s, "\n ", &format_stacktrace_entry(&1)) <> "\n"
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Formats the caller, i.e. the first entry in the stacktrace.
|
||||
|
||||
A stacktrace must be given as an argument. If not, this function
|
||||
calculates a new stacktrace based on the caller and formats it. As
|
||||
a consequence, the value of `System.stacktrace` is changed.
|
||||
|
||||
Notice that due to tail call optimization, the stacktrace
|
||||
may not report the direct caller of the function.
|
||||
"""
|
||||
@@ -289,21 +335,16 @@ defmodule Exception do
|
||||
|
||||
if is_list(arity) do
|
||||
inspected = lc x inlist arity, do: inspect(x)
|
||||
"#{format_module module}#{fun}(#{Enum.join(inspected, ", ")})"
|
||||
"#{inspect module}.#{fun}(#{Enum.join(inspected, ", ")})"
|
||||
else
|
||||
"#{format_module module}#{fun}/#{arity}"
|
||||
"#{inspect module}.#{fun}/#{arity}"
|
||||
end
|
||||
end
|
||||
|
||||
defp format_module(mod), do: "#{inspect mod}."
|
||||
|
||||
@doc """
|
||||
Formats the given file and line as shown in stacktraces.
|
||||
If any of the values are nil, they are omitted.
|
||||
|
||||
The current working directory may be given as an argument,
|
||||
otherwise one is automatically retrieved.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Exception.format_file_line("foo", 1)
|
||||
@@ -316,11 +357,8 @@ defmodule Exception do
|
||||
""
|
||||
|
||||
"""
|
||||
def format_file_line(file, line, cwd // nil) do
|
||||
def format_file_line(file, line) do
|
||||
if file do
|
||||
file = to_string(file)
|
||||
file = if cwd, do: Path.relative_to(file, cwd), else: Path.relative_to_cwd(file)
|
||||
|
||||
if line && line != 0 do
|
||||
"#{file}:#{line}: "
|
||||
else
|
||||
@@ -331,8 +369,8 @@ defmodule Exception do
|
||||
end
|
||||
end
|
||||
|
||||
defp format_location(opts, cwd) do
|
||||
format_file_line Keyword.get(opts, :file), Keyword.get(opts, :line), cwd
|
||||
defp format_location(opts) do
|
||||
format_file_line Keyword.get(opts, :file), Keyword.get(opts, :line)
|
||||
end
|
||||
|
||||
defp from_stacktrace([{ module, function, args, _ }|_]) when is_list(args) do
|
||||
|
||||
+67
-48
@@ -31,22 +31,6 @@ defrecord File.Stat, Record.extract(:file_info, from_lib: "kernel/include/file.h
|
||||
"""
|
||||
end
|
||||
|
||||
defexception File.Error, [reason: nil, action: "", path: nil] do
|
||||
def message(exception) do
|
||||
formatted = iolist_to_binary(:file.format_error(reason exception))
|
||||
"could not #{action exception} #{path exception}: #{formatted}"
|
||||
end
|
||||
end
|
||||
|
||||
defexception File.CopyError, [reason: nil, action: "", source: nil, destination: nil, on: nil] do
|
||||
def message(exception) do
|
||||
formatted = iolist_to_binary(:file.format_error(reason exception))
|
||||
location = if on = on(exception), do: ". #{on}", else: ""
|
||||
"could not #{action exception} from #{source exception} to " <>
|
||||
"#{destination exception}#{location}: #{formatted}"
|
||||
end
|
||||
end
|
||||
|
||||
defmodule File do
|
||||
@moduledoc """
|
||||
This module contains functions to manipulate files.
|
||||
@@ -63,9 +47,9 @@ defmodule File do
|
||||
in the `IO` module. By default, a file is opened in binary mode
|
||||
which requires the functions `IO.binread/2` and `IO.binwrite/2`
|
||||
to interact with the file. A developer may pass `:utf8` as an
|
||||
option when opening the file and then all other functions
|
||||
from `IO` are available, since they work directly with Unicode
|
||||
data.
|
||||
option when opening the file, then the slower `IO.read/2` and
|
||||
`IO.write/2` functions must be used as they are responsible for
|
||||
doing the proper conversions and data guarantees.
|
||||
|
||||
Most of the functions in this module return `:ok` or
|
||||
`{ :ok, result }` in case of success, `{ :error, reason }`
|
||||
@@ -101,7 +85,7 @@ defmodule File do
|
||||
|
||||
## Examples
|
||||
|
||||
File.regular? __FILE__ #=> true
|
||||
File.regular? __ENV__.file #=> true
|
||||
|
||||
"""
|
||||
def regular?(path) do
|
||||
@@ -560,6 +544,8 @@ defmodule File do
|
||||
* :enospc - There is a no space left on the device.
|
||||
* :eacces - Missing permission for writing the file or searching one of the parent directories.
|
||||
* :eisdir - The named file is a directory.
|
||||
|
||||
Check `File.open/2` for existing modes.
|
||||
"""
|
||||
def write(path, content, modes // []) do
|
||||
F.write_file(path, content, modes)
|
||||
@@ -663,7 +649,7 @@ defmodule File do
|
||||
do_rm_rf(path, { :ok, [] })
|
||||
end
|
||||
|
||||
defp do_rm_rf(path, { :ok, acc } = entry) do
|
||||
defp do_rm_rf(path, { :ok, _ } = entry) do
|
||||
case safe_list_dir(path) do
|
||||
{ :ok, files } when is_list(files) ->
|
||||
res =
|
||||
@@ -968,39 +954,72 @@ defmodule File do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Opens the given `file` with the given `mode` and returns
|
||||
a stream for each `:line` (default) or for a given number
|
||||
Opens the file at the given `path` with the given `modes` and
|
||||
returns a stream for each `:line` (default) or for a given number
|
||||
of bytes given by `line_or_bytes`.
|
||||
|
||||
The returned stream will fail for the same reasons as `File.open!`.
|
||||
Note that the file is opened only and every time streaming begins.
|
||||
The returned stream will fail for the same reasons as
|
||||
`File.open!/2`. Note that the file is opened only and every time
|
||||
streaming begins.
|
||||
|
||||
Note that stream by default uses `IO.binread/2` unless
|
||||
the file is opened with an encoding, then the slower `IO.read/2`
|
||||
is used to do the proper data conversion and guarantees.
|
||||
"""
|
||||
def stream!(file, mode // [], line_or_bytes // :line) do
|
||||
fn(acc, fun) ->
|
||||
device = open!(file, mode)
|
||||
try do
|
||||
IO.stream(device, line_or_bytes, acc, fun)
|
||||
after
|
||||
F.close(device)
|
||||
def stream!(path, modes // [], line_or_bytes // :line) do
|
||||
modes = open_defaults(modes, true)
|
||||
bin = nil? List.keyfind(modes, :encoding, 0)
|
||||
|
||||
start_fun =
|
||||
fn ->
|
||||
case F.open(path, modes) do
|
||||
{ :ok, device } -> device
|
||||
{ :error, reason } ->
|
||||
raise File.Error, reason: reason, action: "stream", path: to_string(path)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
next_fun =
|
||||
case bin do
|
||||
true -> &IO.do_binstream(&1, line_or_bytes)
|
||||
false -> &IO.do_stream(&1, line_or_bytes)
|
||||
end
|
||||
|
||||
Stream.resource(start_fun, next_fun, &F.close/1)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Opens the given `file` with the given `mode` and returns
|
||||
a binstream for each `:line` (default) or for a given number
|
||||
of bytes given by `line_or_bytes`.
|
||||
Receives a stream and returns a new stream that will open the file
|
||||
at the given `path` for write with the extra `modes` and write
|
||||
each value to the file.
|
||||
|
||||
The returned stream will fail for the same reasons as `File.open!`.
|
||||
Note that the file is opened only and every time streaming begins.
|
||||
The returned stream will fail for the same reasons as
|
||||
`File.open!/2`. Note that the file is opened only and every time
|
||||
streaming begins.
|
||||
|
||||
Note that stream by default uses `IO.binwrite/2` unless
|
||||
the file is opened with an encoding, then the slower `IO.write/2`
|
||||
is used to do the proper data conversion and guarantees.
|
||||
"""
|
||||
def binstream!(file, mode // [], line_or_bytes // :line) do
|
||||
fn(fun, acc) ->
|
||||
device = open!(file, mode)
|
||||
try do
|
||||
IO.binstream(device, line_or_bytes, fun, acc)
|
||||
after
|
||||
F.close(device)
|
||||
def stream_to!(stream, path, modes // []) do
|
||||
modes = open_defaults([:write|List.delete(modes, :write)], true)
|
||||
bin = nil? List.keyfind(modes, :encoding, 0)
|
||||
|
||||
fn acc, f ->
|
||||
case F.open(path, modes) do
|
||||
{ :ok, device } ->
|
||||
each =
|
||||
case bin do
|
||||
true -> &IO.binwrite(device, &1)
|
||||
false -> &IO.write(device, &1)
|
||||
end
|
||||
|
||||
stream
|
||||
|> Stream.each(each)
|
||||
|> Stream.after(fn -> F.close(device) end)
|
||||
|> Enumerable.Stream.Lazy.reduce(acc, f)
|
||||
{ :error, reason } ->
|
||||
raise File.Error, reason: reason, action: "stream_to", path: to_string(path)
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -1046,7 +1065,7 @@ defmodule File do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Changes the owner given by the user id `gid`
|
||||
Changes the owner given by the user id `uid`
|
||||
for a given `file`. Returns `:ok` on success,
|
||||
or `{:error, reason}` on failure.
|
||||
"""
|
||||
@@ -1057,8 +1076,8 @@ defmodule File do
|
||||
@doc """
|
||||
Same as `chown/2`, but raises an exception in case of failure. Otherwise `:ok`.
|
||||
"""
|
||||
def chown!(file, gid) do
|
||||
case chown(file, gid) do
|
||||
def chown!(file, uid) do
|
||||
case chown(file, uid) do
|
||||
:ok -> :ok
|
||||
{ :error, reason } ->
|
||||
raise File.Error, reason: reason, action: "change owner for", path: to_string(file)
|
||||
|
||||
+90
-11
@@ -4,8 +4,8 @@ defmodule Float do
|
||||
"""
|
||||
|
||||
@doc """
|
||||
Parses a binary into a float.
|
||||
|
||||
Parses a binary into a float.
|
||||
|
||||
If successful, returns a tuple of the form `{ float, remainder_of_binary }`.
|
||||
Otherwise `:error`.
|
||||
|
||||
@@ -22,28 +22,40 @@ defmodule Float do
|
||||
|
||||
"""
|
||||
@spec parse(binary) :: { float, binary } | :error
|
||||
def parse(binary) when is_binary(binary) do
|
||||
def parse("-" <> binary) do
|
||||
case parse_unsign(binary) do
|
||||
:error -> :error
|
||||
{ number, remainder } -> { -number, remainder }
|
||||
end
|
||||
end
|
||||
|
||||
def parse(binary) do
|
||||
parse_unsign(binary)
|
||||
end
|
||||
|
||||
defp parse_unsign("-" <> _), do: :error
|
||||
defp parse_unsign(binary) when is_binary(binary) do
|
||||
case Integer.parse binary do
|
||||
:error -> :error
|
||||
{ integer_part, after_integer } -> parse after_integer, integer_part
|
||||
{ integer_part, after_integer } -> parse_unsign after_integer, integer_part
|
||||
end
|
||||
end
|
||||
|
||||
# Dot followed by digit is required afterwards or we are done
|
||||
defp parse(<< ?., char, rest :: binary >>, int) when char in ?0..?9 do
|
||||
parse(rest, char - ?0, 1, int)
|
||||
defp parse_unsign(<< ?., char, rest :: binary >>, int) when char in ?0..?9 do
|
||||
parse_unsign(rest, char - ?0, 1, int)
|
||||
end
|
||||
|
||||
defp parse(rest, int) do
|
||||
defp parse_unsign(rest, int) do
|
||||
{ :erlang.float(int), rest }
|
||||
end
|
||||
|
||||
# Handle decimal points
|
||||
defp parse(<< char, rest :: binary >>, float, decimal, int) when char in ?0..?9 do
|
||||
parse rest, 10 * float + (char - ?0), decimal + 1, int
|
||||
defp parse_unsign(<< char, rest :: binary >>, float, decimal, int) when char in ?0..?9 do
|
||||
parse_unsign rest, 10 * float + (char - ?0), decimal + 1, int
|
||||
end
|
||||
|
||||
defp parse(<< ?e, after_e :: binary >>, float, decimal, int) do
|
||||
defp parse_unsign(<< ?e, after_e :: binary >>, float, decimal, int) do
|
||||
case Integer.parse after_e do
|
||||
:error ->
|
||||
# Note we rebuild the binary here instead of breaking it apart at
|
||||
@@ -56,7 +68,7 @@ defmodule Float do
|
||||
end
|
||||
end
|
||||
|
||||
defp parse(bitstring, float, decimal, int) do
|
||||
defp parse_unsign(bitstring, float, decimal, int) do
|
||||
{ floatify(int, float, decimal), bitstring }
|
||||
end
|
||||
|
||||
@@ -80,4 +92,71 @@ defmodule Float do
|
||||
result
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Round a float to the largest integer less than or equal to `num`
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Float.floor(34)
|
||||
34
|
||||
iex> Float.floor(34.25)
|
||||
34
|
||||
iex> Float.floor(-56.5)
|
||||
-57
|
||||
|
||||
"""
|
||||
@spec floor(float | integer) :: integer
|
||||
def floor(num) when is_integer(num), do: num
|
||||
def floor(num) when is_float(num) do
|
||||
truncated = :erlang.trunc(num)
|
||||
case :erlang.abs(num - truncated) do
|
||||
x when x > 0 and num < 0 -> truncated - 1
|
||||
_ -> truncated
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Round a float to the largest integer greater than or equal to `num`
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Float.ceil(34)
|
||||
34
|
||||
iex> Float.ceil(34.25)
|
||||
35
|
||||
iex> Float.ceil(-56.5)
|
||||
-56
|
||||
|
||||
"""
|
||||
@spec ceil(float | integer) :: integer
|
||||
def ceil(num) when is_integer(num), do: num
|
||||
def ceil(num) when is_float(num) do
|
||||
truncated = :erlang.trunc(num)
|
||||
case :erlang.abs(num - truncated) do
|
||||
x when x > 0 and num > 0 -> truncated + 1
|
||||
_ -> truncated
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Rounds a floating point value to an arbitrary number of fractional digits
|
||||
(between 0 and 15).
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Float.round(5.5674, 3)
|
||||
5.567
|
||||
iex> Float.round(5.5675, 3)
|
||||
5.568
|
||||
iex> Float.round(-5.5674, 3)
|
||||
-5.567
|
||||
iex> Float.round(-5.5675, 3)
|
||||
-5.568
|
||||
"""
|
||||
@spec round(float, integer) :: float
|
||||
def round(number, precision) when is_float(number) and is_integer(precision) and precision in 0..15 do
|
||||
Kernel.round(number * :math.pow(10, precision)) / :math.pow(10, precision)
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
@@ -25,7 +25,6 @@ defmodule GenEvent.Behaviour do
|
||||
def handle_call(:notifications, notifications) do
|
||||
{:ok, Enum.reverse(notifications), []}
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
{ :ok, pid } = :gen_event.start_link
|
||||
|
||||
@@ -21,78 +21,67 @@ defmodule GenFSM.Behaviour do
|
||||
works - we hereby declare a full disclaimer for any lawsuits that the
|
||||
behaviour of the CVM in its original state might encur.
|
||||
|
||||
|
||||
defmodule MyFsm do
|
||||
use GenFSM.Behaviour
|
||||
|
||||
# keeping track of what is going on inside the CVM.
|
||||
# 3 is the target price for a cup of coffee
|
||||
defrecord StateData, coins: 0, price: 3
|
||||
# keeping track of what is going on inside the CVM.
|
||||
# 3 is the target price for a cup of coffee
|
||||
defrecord StateData, coins: 0, price: 3
|
||||
|
||||
#
|
||||
# API functions
|
||||
#
|
||||
# API functions
|
||||
|
||||
def start_link() do
|
||||
:gen_fsm.start_link({:local, :cvm}, __MODULE__, [], [])
|
||||
end
|
||||
|
||||
def insert_coin() do
|
||||
:gen_fsm.send_event(:cvm, :coin)
|
||||
end
|
||||
|
||||
def request_coffee() do
|
||||
:gen_fsm.send_event(:cvm, :request_coffee)
|
||||
end
|
||||
|
||||
#
|
||||
# Callbacks
|
||||
#
|
||||
|
||||
def init(_args) do
|
||||
{ :ok, :short_paid, StateData.new }
|
||||
end
|
||||
|
||||
|
||||
def short_paid(:coin, state_data = StateData[coins: c, price: p])
|
||||
when c + 1 < p do
|
||||
{ :next_state, :short_paid, state_data.coins(c + 1) }
|
||||
end
|
||||
|
||||
def short_paid(:coin, state_data) do
|
||||
{ :next_state, :paid_in_full, &state_data.update_coins(&1 + 1) }
|
||||
end
|
||||
|
||||
def short_paid(:request_coffee, state_data) do
|
||||
{ :next_state, :requested_short_paid, state_data }
|
||||
end
|
||||
|
||||
|
||||
def requested_short_paid(:request_coffee, state_data) do
|
||||
{:next_state, :requested_short_paid, state_data }
|
||||
end
|
||||
|
||||
def requested_short_paid(:coin, state_data=StateData[coins: c, price: p])
|
||||
when c+1 < p do
|
||||
{ :next_state, :requested_short_paid, state_data.coins(c + 1) }
|
||||
end
|
||||
|
||||
def requested_short_paid(:coin, _state_data) do
|
||||
IO.puts "Here's your coffee!"
|
||||
{ :next_state, :short_paid, StateData.new }
|
||||
end
|
||||
|
||||
|
||||
def paid_in_full(:coin, state_data) do
|
||||
{ :next_state, :paid_in_full, &state_data.update_coins(&1 + 1) }
|
||||
end
|
||||
|
||||
def paid_in_full(:request_coffee, _state_data) do
|
||||
IO.puts "Here's your coffee!"
|
||||
{ :next_state, :short_paid, StateData.new }
|
||||
end
|
||||
def start_link() do
|
||||
:gen_fsm.start_link({:local, :cvm}, __MODULE__, [], [])
|
||||
end
|
||||
|
||||
def insert_coin() do
|
||||
:gen_fsm.send_event(:cvm, :coin)
|
||||
end
|
||||
|
||||
def request_coffee() do
|
||||
:gen_fsm.send_event(:cvm, :request_coffee)
|
||||
end
|
||||
|
||||
# Callbacks
|
||||
|
||||
def init(_args) do
|
||||
{ :ok, :short_paid, StateData.new }
|
||||
end
|
||||
|
||||
def short_paid(:coin, state_data = StateData[coins: c, price: p]) when c + 1 < p do
|
||||
{ :next_state, :short_paid, state_data.coins(c + 1) }
|
||||
end
|
||||
|
||||
def short_paid(:coin, state_data) do
|
||||
{ :next_state, :paid_in_full, &state_data.update_coins(&1 + 1) }
|
||||
end
|
||||
|
||||
def short_paid(:request_coffee, state_data) do
|
||||
{ :next_state, :requested_short_paid, state_data }
|
||||
end
|
||||
|
||||
def requested_short_paid(:request_coffee, state_data) do
|
||||
{ :next_state, :requested_short_paid, state_data }
|
||||
end
|
||||
|
||||
def requested_short_paid(:coin, state_data=StateData[coins: c, price: p]) when c+1 < p do
|
||||
{ :next_state, :requested_short_paid, state_data.coins(c + 1) }
|
||||
end
|
||||
|
||||
def requested_short_paid(:coin, _state_data) do
|
||||
IO.puts "Here's your coffee!"
|
||||
{ :next_state, :short_paid, StateData.new }
|
||||
end
|
||||
|
||||
def paid_in_full(:coin, state_data) do
|
||||
{ :next_state, :paid_in_full, &state_data.update_coins(&1 + 1) }
|
||||
end
|
||||
|
||||
def paid_in_full(:request_coffee, _state_data) do
|
||||
IO.puts "Here's your coffee!"
|
||||
{ :next_state, :short_paid, StateData.new }
|
||||
end
|
||||
end
|
||||
|
||||
{ :ok, _pid } = MyFsm.start_link()
|
||||
|
||||
@@ -138,15 +127,15 @@ defmodule GenFSM.Behaviour do
|
||||
for you. The list of callbacks are:
|
||||
|
||||
* `init(args)` - invoked when the FSM is started;
|
||||
* `handle_sync_event(event, from, state_name, state_data)` - invoked to
|
||||
* `handle_sync_event(event, from, state_name, state_data)` - invoked to
|
||||
handle `sync_send_all_state_event` messages;
|
||||
* `handle_event(event, state_name, state_data)` - invoked to handle
|
||||
* `handle_event(event, state_name, state_data)` - invoked to handle
|
||||
`send_all_state_event` messages;
|
||||
* `handle_info(msg, state_name, state_data)` - handle all other
|
||||
* `handle_info(msg, state_name, state_data)` - handle all other
|
||||
messages which are normally received by processes;
|
||||
* `terminate(reason, state_name, state_data)` - called when the FSM
|
||||
* `terminate(reason, state_name, state_data)` - called when the FSM
|
||||
is about to terminate, useful for cleaning up;
|
||||
* `code_change(old_vsn, state, extra)` - called when the application
|
||||
* `code_change(old_vsn, state, extra)` - called when the application
|
||||
code is being upgraded live (hot code swap);
|
||||
|
||||
Unlike `GenServer` and `GenEvent`, the callback `init/1` is not
|
||||
@@ -154,9 +143,9 @@ defmodule GenFSM.Behaviour do
|
||||
|
||||
For each state you need to define either or both of these:
|
||||
|
||||
* `state_name(event, state_data)` - invoked to handle
|
||||
* `state_name(event, state_data)` - invoked to handle
|
||||
`send_event` messages;
|
||||
* `state_name(event, from, state_data)`- invoked to handle
|
||||
* `state_name(event, from, state_data)`- invoked to handle
|
||||
`sync_send_event` messages;
|
||||
|
||||
If you send asynchronous events you only need to implement the
|
||||
@@ -180,13 +169,13 @@ defmodule GenFSM.Behaviour do
|
||||
@behavior :gen_fsm
|
||||
|
||||
@doc false
|
||||
def handle_event(_event, state_name, state_data) do
|
||||
{ :next_state, state_name, state_data }
|
||||
def handle_event(event, state_name, state_data) do
|
||||
{ :stop, {:bad_event, state_name, event}, state_data }
|
||||
end
|
||||
|
||||
@doc false
|
||||
def handle_sync_event(_event, from, state_name, state_data) do
|
||||
{ :reply, :ok, state_name, state_data }
|
||||
def handle_sync_event(event, from, state_name, state_data) do
|
||||
{ :stop, {:bad_sync_event, state_name, event}, state_data }
|
||||
end
|
||||
|
||||
@doc false
|
||||
|
||||
@@ -94,8 +94,8 @@ defmodule GenServer.Behaviour do
|
||||
end
|
||||
|
||||
@doc false
|
||||
def handle_call(_request, _from, state) do
|
||||
{ :noreply, state }
|
||||
def handle_call(request, _from, state) do
|
||||
{ :stop, { :bad_call, request }, state }
|
||||
end
|
||||
|
||||
@doc false
|
||||
@@ -104,8 +104,8 @@ defmodule GenServer.Behaviour do
|
||||
end
|
||||
|
||||
@doc false
|
||||
def handle_cast(_msg, state) do
|
||||
{ :noreply, state }
|
||||
def handle_cast(msg, state) do
|
||||
{ :stop, { :bad_cast, msg }, state }
|
||||
end
|
||||
|
||||
@doc false
|
||||
|
||||
+28
-18
@@ -188,7 +188,8 @@ defmodule HashDict do
|
||||
end
|
||||
|
||||
def reduce(trie(root: root, depth: depth), acc, fun) do
|
||||
node_reduce(root, depth, acc, fun, @node_size)
|
||||
node_reduce(root, depth, acc, fun, @node_size,
|
||||
fn { :cont, acc }, _f -> { :done, acc } end)
|
||||
end
|
||||
|
||||
## Dict-wide functions
|
||||
@@ -328,13 +329,10 @@ defmodule HashDict do
|
||||
end
|
||||
|
||||
# Reduces the bucket
|
||||
defp bucket_reduce([[k|v]|t], acc, fun) do
|
||||
bucket_reduce(t, fun.({ k, v }, acc), fun)
|
||||
end
|
||||
|
||||
defp bucket_reduce([], acc, _fun) do
|
||||
acc
|
||||
end
|
||||
defp bucket_reduce(_, { :halt, acc }, _fun), do: { :halted, acc }
|
||||
defp bucket_reduce(list, { :suspend, acc }, fun), do: { :suspended, acc, &bucket_reduce(list, &1, fun) }
|
||||
defp bucket_reduce([[k|v]|t], { :cont, acc }, fun), do: bucket_reduce(t, fun.({ k, v }, acc), fun)
|
||||
defp bucket_reduce([], { :cont, acc }, _fun), do: { :done, acc }
|
||||
|
||||
defp bucket_fold(bucket, acc, fun) do
|
||||
:lists.foldl(fun, acc, bucket)
|
||||
@@ -399,17 +397,29 @@ defmodule HashDict do
|
||||
end
|
||||
|
||||
# Reduces a node recursively
|
||||
defp node_reduce(bucket, -1, acc, fun, _) do
|
||||
bucket_reduce(bucket, acc, fun)
|
||||
defp node_reduce(_, -1, { :halt, acc }, _fun, _count, _next) do
|
||||
{ :halted, acc }
|
||||
end
|
||||
|
||||
defp node_reduce(node, depth, acc, fun, count) when count >= 1 do
|
||||
acc = node_reduce(:erlang.element(count, node), depth - 1, acc, fun, @node_size)
|
||||
node_reduce(node, depth, acc, fun, count - 1)
|
||||
defp node_reduce(list, -1, { :suspend, acc }, fun, count, next) do
|
||||
{ :suspended, acc, &node_reduce(list, -1, &1, fun, count, next) }
|
||||
end
|
||||
|
||||
defp node_reduce(_node, _, acc, _fun, 0) do
|
||||
acc
|
||||
defp node_reduce([[k|v]|t], -1, { :cont, acc }, fun, _count, next) do
|
||||
node_reduce(t, -1, fun.({ k, v }, acc), fun, _count, next)
|
||||
end
|
||||
|
||||
defp node_reduce([], -1, { :cont, _ } = acc, fun, _count, next) do
|
||||
next.(acc, fun)
|
||||
end
|
||||
|
||||
defp node_reduce(node, depth, acc, fun, count, next) when count >= 1 do
|
||||
node_reduce(:erlang.element(count, node), depth - 1, acc, fun, @node_size,
|
||||
&node_reduce(node, depth, &1, &2, count - 1, next))
|
||||
end
|
||||
|
||||
defp node_reduce(_node, _, acc, fun, 0, next) do
|
||||
next.(acc, fun)
|
||||
end
|
||||
|
||||
# Folds a node recursively
|
||||
@@ -468,9 +478,9 @@ end
|
||||
|
||||
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)
|
||||
def member?(dict, { k, v }), do: { :ok, match?({ :ok, ^v }, HashDict.fetch(dict, k)) }
|
||||
def member?(_dict, _), do: { :ok, false }
|
||||
def count(dict), do: { :ok, HashDict.size(dict) }
|
||||
end
|
||||
|
||||
defimpl Access, for: HashDict do
|
||||
|
||||
+50
-25
@@ -136,11 +136,12 @@ defmodule HashSet do
|
||||
|
||||
@doc false
|
||||
def reduce(ordered(bucket: bucket), acc, fun) do
|
||||
:lists.foldl(fun, acc, bucket)
|
||||
bucket_reduce(bucket, acc, fun)
|
||||
end
|
||||
|
||||
def reduce(trie() = set, acc, fun) do
|
||||
set_fold(set, acc, fun)
|
||||
def reduce(trie(root: root, depth: depth), acc, fun) do
|
||||
node_reduce(root, depth, acc, fun, @node_size,
|
||||
fn { :cont, acc }, _f -> { :done, acc } end)
|
||||
end
|
||||
|
||||
## HashSet-wide functions
|
||||
@@ -225,29 +226,21 @@ defmodule HashSet do
|
||||
end
|
||||
|
||||
defp set_equal?(set1, set2) do
|
||||
try do
|
||||
reduce(set1, true, fn member, acc ->
|
||||
case member?(set2, member) do
|
||||
true -> acc
|
||||
_ -> throw(:error)
|
||||
end
|
||||
end)
|
||||
catch
|
||||
:error -> false
|
||||
end
|
||||
reduce(set1, { :cont, true }, fn member, acc ->
|
||||
case member?(set2, member) do
|
||||
true -> { :cont, acc }
|
||||
_ -> { :halt, false }
|
||||
end
|
||||
end) |> elem(1)
|
||||
end
|
||||
|
||||
defp set_disjoint?(set1, set2) do
|
||||
try do
|
||||
reduce(set1, true, fn member, acc ->
|
||||
case member?(set2, member) do
|
||||
false -> acc
|
||||
_ -> throw(:error)
|
||||
end
|
||||
end)
|
||||
catch
|
||||
:error -> false
|
||||
end
|
||||
reduce(set1, { :cont, true }, fn member, acc ->
|
||||
case member?(set2, member) do
|
||||
false -> { :cont, acc }
|
||||
_ -> { :halt, false }
|
||||
end
|
||||
end) |> elem(1)
|
||||
end
|
||||
|
||||
defp set_fold(ordered(bucket: bucket), acc, fun) do
|
||||
@@ -311,6 +304,11 @@ defmodule HashSet do
|
||||
{ [], 0 }
|
||||
end
|
||||
|
||||
defp bucket_reduce(_, { :halt, acc }, _fun), do: { :halted, acc }
|
||||
defp bucket_reduce(list, { :suspend, acc }, fun), do: { :suspended, acc, &bucket_reduce(list, &1, fun) }
|
||||
defp bucket_reduce([h|t], { :cont, acc }, fun), do: bucket_reduce(t, fun.(h, acc), fun)
|
||||
defp bucket_reduce([], { :cont, acc }, _fun), do: { :done, acc }
|
||||
|
||||
defp bucket_fold(bucket, acc, fun) do
|
||||
:lists.foldl(fun, acc, bucket)
|
||||
end
|
||||
@@ -396,6 +394,33 @@ defmodule HashSet do
|
||||
end
|
||||
end
|
||||
|
||||
# Reduces a node recursively
|
||||
defp node_reduce(_, -1, { :halt, acc }, _fun, _count, _next) do
|
||||
{ :halted, acc }
|
||||
end
|
||||
|
||||
defp node_reduce(list, -1, { :suspend, acc }, fun, count, next) do
|
||||
{ :suspended, acc, &node_reduce(list, -1, &1, fun, count, next) }
|
||||
end
|
||||
|
||||
defp node_reduce([h|t], -1, { :cont, acc }, fun, _count, next) do
|
||||
node_reduce(t, -1, fun.(h, acc), fun, _count, next)
|
||||
end
|
||||
|
||||
defp node_reduce([], -1, { :cont, _ } = acc, fun, _count, next) do
|
||||
next.(acc, fun)
|
||||
end
|
||||
|
||||
defp node_reduce(node, depth, acc, fun, count, next) when count >= 1 do
|
||||
node_reduce(:erlang.element(count, node), depth - 1, acc, fun, @node_size,
|
||||
&node_reduce(node, depth, &1, &2, count - 1, next))
|
||||
end
|
||||
|
||||
defp node_reduce(_node, _, acc, fun, 0, next) do
|
||||
next.(acc, fun)
|
||||
end
|
||||
|
||||
# Folds a node recursively
|
||||
defp node_fold(bucket, -1, acc, fun, _) do
|
||||
bucket_fold(bucket, acc, fun)
|
||||
end
|
||||
@@ -480,6 +505,6 @@ end
|
||||
|
||||
defimpl Enumerable, for: HashSet do
|
||||
def reduce(set, acc, fun), do: HashSet.reduce(set, acc, fun)
|
||||
def member?(set, v), do: HashSet.member?(set, v)
|
||||
def count(set), do: HashSet.size(set)
|
||||
def member?(set, v), do: { :ok, HashSet.member?(set, v) }
|
||||
def count(set), do: { :ok, HashSet.size(set) }
|
||||
end
|
||||
|
||||
+77
-39
@@ -1,7 +1,13 @@
|
||||
import Kernel, except: [inspect: 1]
|
||||
import Inspect.Algebra
|
||||
|
||||
defrecord Inspect.Opts, raw: false, limit: 50, pretty: false, width: 80
|
||||
defrecord Inspect.Opts,
|
||||
records: true,
|
||||
binaries: :infer,
|
||||
char_lists: :infer,
|
||||
limit: 50,
|
||||
pretty: false,
|
||||
width: 80
|
||||
|
||||
defprotocol Inspect do
|
||||
@moduledoc """
|
||||
@@ -13,11 +19,9 @@ defprotocol Inspect do
|
||||
followed by the inspecting options, represented by the record
|
||||
`Inspect.Opts`.
|
||||
|
||||
Inspection is done using the functions available in
|
||||
`Inspect.Algebra` and by calling `Kernel.inspect/2` recursively
|
||||
passing the `Inspect.Opts` as an argument. When `Kernel.inspect/2`
|
||||
receives an `Inspect.Opts` record as the second argument, it returns
|
||||
the underlying algebra document instead of the formatted string.
|
||||
Inspection is done using the functions available in `Inspect.Algebra`.
|
||||
|
||||
## Examples
|
||||
|
||||
Many times, inspecting a structure can be implemented in function
|
||||
of existing entities. For example, here is `HashSet`'s `inspect`
|
||||
@@ -27,7 +31,7 @@ defprotocol Inspect do
|
||||
import Inspect.Algebra
|
||||
|
||||
def inspect(dict, opts) do
|
||||
concat ["#HashSet<", Kernel.inspect(HashSet.to_list(dict), opts), ">"]
|
||||
concat ["#HashSet<", to_doc(HashSet.to_list(dict), opts), ">"]
|
||||
end
|
||||
end
|
||||
|
||||
@@ -35,14 +39,24 @@ defprotocol Inspect do
|
||||
concatenates algebra documents together. In the example above,
|
||||
it is concatenating the string `"HashSet<"` (all strings are
|
||||
valid algebra documents that keep their formatting when pretty
|
||||
printed), the document returned by `Kernel.inspect/2` and the
|
||||
printed), the document returned by `Inspect.Algebra.to_doc/2` and the
|
||||
other string `">"`.
|
||||
|
||||
Since regular strings are valid entities in an algebra document,
|
||||
an implementation of inspect may simply return a string,
|
||||
although that will devoid it of any pretty-printing.
|
||||
"""
|
||||
|
||||
## Error handling
|
||||
|
||||
In case there is an error while your structure is being inspected,
|
||||
Elixir will automatically fall back to tuple inspection for records.
|
||||
You can however access the underlying error by invoking the Inspect
|
||||
implementation directly. For example, to test Inspect.HashSet above,
|
||||
you just need to do:
|
||||
|
||||
Inspect.HashSet.inspect(HashSet.new, Inspect.Opts.new)
|
||||
|
||||
"""
|
||||
def inspect(thing, opts)
|
||||
end
|
||||
|
||||
@@ -86,7 +100,7 @@ defimpl Inspect, for: Atom do
|
||||
valid_ref_identifier?(binary) ->
|
||||
"Elixir." <> rest = binary
|
||||
rest
|
||||
atom in [:{}, :[], :<<>>] ->
|
||||
atom in [:{}, :<<>>] ->
|
||||
":" <> binary
|
||||
atom in Macro.binary_ops or atom in Macro.unary_ops ->
|
||||
":" <> binary
|
||||
@@ -150,8 +164,8 @@ defimpl Inspect, for: BitString do
|
||||
"<<0, 1, 2>>"
|
||||
|
||||
"""
|
||||
def inspect(thing, opts) when is_binary(thing) do
|
||||
if String.printable?(thing) do
|
||||
def inspect(thing, Inspect.Opts[binaries: bins] = opts) when is_binary(thing) do
|
||||
if bins == :as_strings or (bins == :infer and String.printable?(thing)) do
|
||||
<< ?", escape(thing, ?") :: binary, ?" >>
|
||||
else
|
||||
inspect_bitstring(thing, opts)
|
||||
@@ -205,11 +219,29 @@ defimpl Inspect, for: BitString do
|
||||
defp escape(<<?\v, t :: binary>>, char, binary) do
|
||||
escape(t, char, << binary :: binary, ?\\, ?v >>)
|
||||
end
|
||||
defp escape(<<h :: utf8, t :: binary>>, char, binary) do
|
||||
head = << h :: utf8 >>
|
||||
if String.printable?(head) do
|
||||
escape(t, char, append(head, binary))
|
||||
else
|
||||
<< byte :: size(8), h :: binary >> = head
|
||||
t = << h :: binary, t :: binary >>
|
||||
escape(t, char, << binary :: binary, octify(byte) :: binary >>)
|
||||
end
|
||||
end
|
||||
defp escape(<<h, t :: binary>>, char, binary) do
|
||||
escape(t, char, << binary :: binary, h >>)
|
||||
escape(t, char, << binary :: binary, octify(h) :: binary >>)
|
||||
end
|
||||
defp escape(<<>>, _char, binary), do: binary
|
||||
|
||||
defp octify(byte) do
|
||||
<< hi :: size(2), mi :: size(3), lo :: size(3) >> = << byte >>
|
||||
<< ?\\, ?0 + hi, ?0 + mi, ?0 + lo >>
|
||||
end
|
||||
|
||||
defp append(<<h, t :: binary>>, binary), do: append(t, << binary :: binary, h >>)
|
||||
defp append(<<>>, binary), do: binary
|
||||
|
||||
## Bitstrings
|
||||
|
||||
defp inspect_bitstring(bitstring, Inspect.Opts[] = opts) do
|
||||
@@ -262,21 +294,21 @@ defimpl Inspect, for: List do
|
||||
|
||||
def inspect([], _opts), do: "[]"
|
||||
|
||||
def inspect(thing, Inspect.Opts[] = opts) do
|
||||
def inspect(thing, Inspect.Opts[char_lists: lists] = opts) do
|
||||
cond do
|
||||
:io_lib.printable_list(thing) ->
|
||||
lists == :as_char_lists or (lists == :infer and :io_lib.printable_list(thing)) ->
|
||||
<< ?', Inspect.BitString.escape(String.from_char_list!(thing), ?') :: binary, ?' >>
|
||||
keyword?(thing) && not opts.raw ->
|
||||
keyword?(thing) ->
|
||||
surround_many("[", thing, "]", opts.limit, &keyword(&1, opts))
|
||||
true ->
|
||||
surround_many("[", thing, "]", opts.limit, &Kernel.inspect(&1, opts))
|
||||
surround_many("[", thing, "]", opts.limit, &to_doc(&1, opts))
|
||||
end
|
||||
end
|
||||
|
||||
defp keyword({key, value}, opts) do
|
||||
concat(
|
||||
key_to_binary(key) <> ": ",
|
||||
Kernel.inspect(value, opts)
|
||||
to_doc(value, opts)
|
||||
)
|
||||
end
|
||||
|
||||
@@ -314,24 +346,24 @@ defimpl Inspect, for: Tuple do
|
||||
|
||||
def inspect({}, _opts), do: "{}"
|
||||
|
||||
def inspect(tuple, opts) do
|
||||
unless opts.raw do
|
||||
def inspect(tuple, Inspect.Opts[] = opts) do
|
||||
if opts.records do
|
||||
record_inspect(tuple, opts)
|
||||
end || surround_many("{", tuple_to_list(tuple), "}", opts.limit, &Kernel.inspect(&1, opts))
|
||||
else
|
||||
surround_many("{", tuple_to_list(tuple), "}", opts.limit, &to_doc(&1, opts))
|
||||
end
|
||||
end
|
||||
|
||||
## Helpers
|
||||
|
||||
defp record_inspect(record, opts) do
|
||||
defp record_inspect(record, Inspect.Opts[] = opts) do
|
||||
[name|tail] = tuple_to_list(record)
|
||||
|
||||
if is_atom(name) && (fields = record_fields(name)) && (length(fields) == size(record) - 1) do
|
||||
if Enum.first(tail) == :__exception__ do
|
||||
surround_record(name, tl(fields), tl(tail), opts)
|
||||
else
|
||||
surround_record(name, fields, tail, opts)
|
||||
end
|
||||
end || surround_many("{", [name|tail], "}", opts.limit, &Kernel.inspect(&1, opts))
|
||||
surround_record(name, fields, tail, opts)
|
||||
else
|
||||
surround_many("{", [name|tail], "}", opts.limit, &to_doc(&1, opts))
|
||||
end
|
||||
end
|
||||
|
||||
defp record_fields(name) do
|
||||
@@ -346,20 +378,26 @@ defimpl Inspect, for: Tuple do
|
||||
end
|
||||
end
|
||||
|
||||
defp surround_record(name, fields, tail, opts) do
|
||||
fields = lc { field, _ } inlist fields, do: field
|
||||
|
||||
defp surround_record(name, fields, tail, Inspect.Opts[] = opts) do
|
||||
concat(
|
||||
Inspect.Atom.inspect(name, opts),
|
||||
surround_many("[", Enum.zip(fields, tail), "]", opts.limit, &keyword(&1, opts))
|
||||
surround_many("[", zip_fields(fields, tail), "]", opts.limit, &keyword(&1, opts))
|
||||
)
|
||||
end
|
||||
|
||||
defp zip_fields([{ key, _ }|tk], [value|tv]) do
|
||||
case atom_to_binary(key) do
|
||||
"_" <> _ -> zip_fields(tk, tv)
|
||||
key -> [{ key, value }|zip_fields(tk, tv)]
|
||||
end
|
||||
end
|
||||
|
||||
defp zip_fields([], []) do
|
||||
[]
|
||||
end
|
||||
|
||||
defp keyword({ k, v }, opts) do
|
||||
concat(
|
||||
atom_to_binary(k) <> ": ",
|
||||
Kernel.inspect(v, opts)
|
||||
)
|
||||
concat(k <> ": ", to_doc(v, opts))
|
||||
end
|
||||
end
|
||||
|
||||
@@ -408,11 +446,11 @@ defimpl Inspect, for: Regex do
|
||||
|
||||
"""
|
||||
def inspect(regex, opts) when size(regex) == 5 do
|
||||
concat ["%r", Kernel.inspect(Regex.source(regex), opts), Regex.opts(regex)]
|
||||
concat ["%r", to_doc(Regex.source(regex), opts), Regex.opts(regex)]
|
||||
end
|
||||
|
||||
def inspect(other, opts) do
|
||||
Kernel.inspect(other, opts.raw(true))
|
||||
def inspect(other, Inspect.Opts[] = opts) do
|
||||
to_doc(other, opts.records(false))
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -71,9 +71,6 @@ defmodule Inspect.Algebra do
|
||||
@nesting 1
|
||||
@break " "
|
||||
|
||||
defp repeat(_, 0), do: ""
|
||||
defp repeat(s, i), do: :lists.duplicate(i, s)
|
||||
|
||||
# Functional interface to `doc` records
|
||||
|
||||
@type t :: :doc_nil | doc_cons_t | doc_nest_t | doc_break_t | doc_group_t | binary
|
||||
@@ -96,14 +93,34 @@ defmodule Inspect.Algebra do
|
||||
|
||||
defp do_is_doc(doc) do
|
||||
quote do
|
||||
unquote(doc) |> is_binary or
|
||||
unquote(doc) |> is_integer or
|
||||
is_binary(unquote(doc)) or
|
||||
unquote(doc) == :doc_nil or
|
||||
(unquote(doc) |> is_tuple and
|
||||
(is_tuple(unquote(doc)) and
|
||||
elem(unquote(doc), 0) in [:doc_cons, :doc_nest, :doc_break, :doc_group])
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Converts an Elixir structure to an algebra document
|
||||
according to the inspect protocol.
|
||||
"""
|
||||
@spec to_doc(any, Inspect.Opts.t) :: t
|
||||
def to_doc(arg, opts) when is_record(opts, Inspect.Opts) do
|
||||
if is_tuple(arg) do
|
||||
if elem(opts, Inspect.Opts.__record__(:index, :records)) do
|
||||
try do
|
||||
Inspect.inspect(arg, opts)
|
||||
catch
|
||||
_, _ -> Inspect.Tuple.inspect(arg, opts)
|
||||
end
|
||||
else
|
||||
Inspect.Tuple.inspect(arg, opts)
|
||||
end
|
||||
else
|
||||
Inspect.inspect(arg, opts)
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns `:doc_nil` which is a document entity used to represent
|
||||
nothingness. Takes no arguments.
|
||||
@@ -296,7 +313,7 @@ defmodule Inspect.Algebra do
|
||||
"""
|
||||
@spec surround(binary, t, binary) :: t
|
||||
def surround(left, doc, right) do
|
||||
group concat [left, nest(doc, @nesting), right]
|
||||
group concat left, concat(nest(doc, @nesting), right)
|
||||
end
|
||||
|
||||
@doc %S"""
|
||||
@@ -314,32 +331,38 @@ defmodule Inspect.Algebra do
|
||||
iex> Inspect.Algebra.pretty(doc, 20)
|
||||
"[1, 2, 3, ...]"
|
||||
|
||||
iex> doc = Inspect.Algebra.surround_many("[", Enum.to_list(1..5), "]", 3, &integer_to_binary(&1), "!")
|
||||
iex> Inspect.Algebra.pretty(doc, 20)
|
||||
"[1! 2! 3! ...]"
|
||||
|
||||
"""
|
||||
@spec surround_many(binary, [any], binary, integer | :infinity, (term -> t)) :: t
|
||||
def surround_many(left, [], right, _, _fun) do
|
||||
@spec surround_many(binary, [any], binary, integer | :infinity, (term -> t), binary) :: t
|
||||
def surround_many(left, docs, right, limit, fun, separator // @surround_separator)
|
||||
|
||||
def surround_many(left, [], right, _, _fun, _) do
|
||||
concat(left, right)
|
||||
end
|
||||
|
||||
def surround_many(left, docs, right, limit, fun) do
|
||||
surround(left, surround_many(docs, limit, fun), right)
|
||||
def surround_many(left, docs, right, limit, fun, sep) do
|
||||
surround(left, surround_many(docs, limit, fun, sep), right)
|
||||
end
|
||||
|
||||
defp surround_many(_, 0, _fun) do
|
||||
defp surround_many(_, 0, _fun, _sep) do
|
||||
"..."
|
||||
end
|
||||
|
||||
defp surround_many([h], _limit, fun) do
|
||||
defp surround_many([h], _limit, fun, _sep) do
|
||||
fun.(h)
|
||||
end
|
||||
|
||||
defp surround_many([h|t], limit, fun) when is_list(t) do
|
||||
defp surround_many([h|t], limit, fun, sep) when is_list(t) do
|
||||
glue(
|
||||
concat(fun.(h), @surround_separator),
|
||||
surround_many(t, decrement(limit), fun)
|
||||
concat(fun.(h), sep),
|
||||
surround_many(t, decrement(limit), fun, sep)
|
||||
)
|
||||
end
|
||||
|
||||
defp surround_many([h|t], _limit, fun) do
|
||||
defp surround_many([h|t], _limit, fun, _sep) do
|
||||
glue(
|
||||
concat(fun.(h), @tail_separator),
|
||||
fun.(t)
|
||||
@@ -367,58 +390,44 @@ defmodule Inspect.Algebra do
|
||||
|
||||
# Rendering and internal helpers
|
||||
|
||||
# Records representing __simple__ documents, already on a fixed layout
|
||||
# Those are generalized by `sdoc` type.
|
||||
@type sdoc :: :s_nil | s_text_t | s_line_t
|
||||
defrecordp :s_text, str: "" :: binary, sdoc: :s_nil :: sdoc
|
||||
defrecordp :s_line, indent: 1 :: non_neg_integer, sdoc: :s_nil :: sdoc
|
||||
|
||||
# Record representing the document mode to be rendered: flat or broken
|
||||
@typep mode :: :flat | :break
|
||||
|
||||
@doc false
|
||||
@spec fits?(integer, [{ integer, mode, t }]) :: boolean
|
||||
def fits?(:infinity, _), do: true # no pretty printing
|
||||
def fits?(w, _) when w < 0, do: false
|
||||
def fits?(_, []), do: true
|
||||
def fits?(w, [{_, _, :doc_nil} | t]), do: fits?(w, t)
|
||||
def fits?(w, [{i, m, doc_cons(left: x, right: y)} | t]), do: fits?(w, [{i, m, x} | [{i, m, y} | t]])
|
||||
def fits?(w, [{i, m, doc_nest(indent: j, doc: x)} | t]), do: fits?(w, [{i + j, m, x} | t])
|
||||
def fits?(w, [{i, _, doc_group(doc: x)} | t]), do: fits?(w, [{i, :flat, x} | t])
|
||||
def fits?(w, [{_, _, s} | t]) when is_binary(s), do: fits?((w - byte_size s), t)
|
||||
def fits?(w, [{_, :flat, doc_break(str: s)} | t]), do: fits?((w - byte_size s), t)
|
||||
def fits?(_, [{_, :break, doc_break(str: _)} | _]), do: true
|
||||
def fits?(w, [{i, _, doc_group(doc: x)} | t]), do: fits?(w, [{i, :flat, x} | t])
|
||||
|
||||
@doc false
|
||||
@spec format(integer | :infinity, integer, [{ integer, mode, t }]) :: atom | tuple
|
||||
def format(_, _, []), do: :s_nil
|
||||
@spec format(integer | :infinity, integer, [{ integer, mode, t }]) :: [binary]
|
||||
def format(_, _, []), do: []
|
||||
def format(w, k, [{_, _, :doc_nil} | t]), do: format(w, k, t)
|
||||
def format(w, k, [{i, m, doc_cons(left: x, right: y)} | t]), do: format(w, k, [{i, m, x} | [{i, m, y} | t]])
|
||||
def format(w, k, [{i, m, doc_nest(indent: j, doc: x)} | t]), do: format(w, k, [{i + j, m, x} | t])
|
||||
def format(w, k, [{_, _, s} | t]) when is_binary(s), do: s_text(str: s, sdoc: format(w, (k + byte_size s), t))
|
||||
def format(w, k, [{i, m, doc_group(doc: x)} | t]), do: format(w, k, [{i, m, x} | t])
|
||||
def format(w, k, [{_, :flat, doc_break(str: s)} | t]), do: s_text(str: s, sdoc: format(w, (k + byte_size s), t))
|
||||
def format(w, k, [{_, _, s} | t]) when is_binary(s), do: [s | format(w, (k + byte_size s), t)]
|
||||
def format(w, k, [{_, :flat, doc_break(str: s)} | t]), do: [s | format(w, (k + byte_size s), t)]
|
||||
def format(w, k, [{i, :break, doc_break(str: s)} | t]) do
|
||||
k = k + byte_size(s)
|
||||
|
||||
if w == :infinity or fits?(w - k, t) do
|
||||
s_text(str: s, sdoc: format(w, k, t))
|
||||
[s | format(w, k, t)]
|
||||
else
|
||||
s_line(indent: i, sdoc: format(w, i, t))
|
||||
prefix = :binary.copy(" ", i)
|
||||
[@newline, prefix | format(w, i, t)]
|
||||
end
|
||||
end
|
||||
|
||||
@doc false
|
||||
@spec render(sdoc) :: binary
|
||||
@spec render([binary]) :: binary
|
||||
def render(sdoc) do
|
||||
iolist_to_binary do_render sdoc
|
||||
end
|
||||
|
||||
@spec do_render(sdoc) :: [binary]
|
||||
defp do_render(:s_nil), do: [""]
|
||||
defp do_render(s_text(str: s, sdoc: d)), do: [s | do_render(d)]
|
||||
defp do_render(s_line(indent: i, sdoc: d)) do
|
||||
prefix = repeat " ", i
|
||||
[@newline | [prefix | do_render d]]
|
||||
iolist_to_binary sdoc
|
||||
end
|
||||
end
|
||||
|
||||
+13
-12
@@ -1,7 +1,8 @@
|
||||
defexception IO.StreamError, reason: nil do
|
||||
def message(exception) do
|
||||
formatted = iolist_to_binary(:file.format_error(reason exception))
|
||||
"error during streaming: #{formatted}"
|
||||
defexception IO.StreamError, [:reason, :message] do
|
||||
def exception(opts) do
|
||||
reason = opts[:reason]
|
||||
formatted = iolist_to_binary(:file.format_error(reason))
|
||||
IO.StreamError[message: "error during streaming: #{formatted}", reason: reason]
|
||||
end
|
||||
end
|
||||
|
||||
@@ -243,7 +244,7 @@ defmodule IO do
|
||||
"""
|
||||
@spec stream(device, :line | pos_integer) :: Enumerable.t
|
||||
def stream(device, line_or_codepoints) do
|
||||
fn(acc, f) -> stream(map_dev(device), line_or_codepoints, acc, f) end
|
||||
Stream.unfold(map_dev(device), &do_stream(&1, line_or_codepoints))
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -256,30 +257,30 @@ defmodule IO do
|
||||
"""
|
||||
@spec binstream(device, :line | pos_integer) :: Enumerable.t
|
||||
def binstream(device, line_or_bytes) do
|
||||
fn(acc, f) -> binstream(map_dev(device), line_or_bytes, acc, f) end
|
||||
Stream.unfold(map_dev(device), &do_binstream(&1, line_or_bytes))
|
||||
end
|
||||
|
||||
@doc false
|
||||
def stream(device, what, acc, fun) do
|
||||
def do_stream(device, what) do
|
||||
case read(device, what) do
|
||||
:eof ->
|
||||
acc
|
||||
nil
|
||||
{ :error, reason } ->
|
||||
raise IO.StreamError, reason: reason
|
||||
data ->
|
||||
stream(device, what, fun.(data, acc), fun)
|
||||
{ data, device }
|
||||
end
|
||||
end
|
||||
|
||||
@doc false
|
||||
def binstream(device, what, acc, fun) do
|
||||
def do_binstream(device, what) do
|
||||
case binread(device, what) do
|
||||
:eof ->
|
||||
acc
|
||||
nil
|
||||
{ :error, reason } ->
|
||||
raise IO.StreamError, reason: reason
|
||||
data ->
|
||||
binstream(device, what, fun.(data, acc), fun)
|
||||
{ data, device }
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -0,0 +1,377 @@
|
||||
defmodule IO.ANSI.Docs do
|
||||
@moduledoc false
|
||||
|
||||
@bullets [?*, ?-, ?+]
|
||||
|
||||
@default_colors [ enabled: true,
|
||||
doc_code: "cyan,bright",
|
||||
doc_inline_code: "cyan",
|
||||
doc_headings: "yellow,bright",
|
||||
doc_title: "reverse,yellow,bright",
|
||||
doc_bold: "bright",
|
||||
doc_underline: "underline" ]
|
||||
|
||||
@doc """
|
||||
Prints the head of the documentation (i.e. the function signature).
|
||||
|
||||
See `print/3` for docs on the supported options.
|
||||
"""
|
||||
def print_heading(heading, colors // @default_colors) do
|
||||
IO.puts IO.ANSI.reset
|
||||
width = column_width()
|
||||
padding = div(width + String.length(heading), 2)
|
||||
heading = heading |> String.rjust(padding) |> String.ljust(width)
|
||||
write(:doc_title, heading, colors)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Prints the documentation body.
|
||||
|
||||
In addition to the priting string, takes a truth value for whether to use ANSI
|
||||
escape codes, and a keyword list for the printing color settings. Supported
|
||||
keys for the color settings are:
|
||||
|
||||
* `:enabled` - toggles coloring on and off (true)
|
||||
* `:doc_code` - code blocks (cyan, bright)
|
||||
* `:doc_inline_code` - inline code (cyan)
|
||||
* `:doc_headings` - h1 and h2 headings (yellow, bright)
|
||||
* `:doc_title` - top level heading (reverse, yellow, bright)
|
||||
* `:doc_bold` - bold text (bright)
|
||||
* `:doc_underline` - underlined text (underline)
|
||||
|
||||
Values for the color settings are strings with comma-separated attributes.
|
||||
Supported attributes are:
|
||||
|
||||
* Colors: `black red green yellow blue magenta cyan white`
|
||||
* Intensity: `normal bright`
|
||||
* Decoration: `underline reverse`
|
||||
"""
|
||||
def print(doc, colors // @default_colors) do
|
||||
doc
|
||||
|> String.split(["\r\n","\n"], trim: false)
|
||||
|> Enum.map(&String.rstrip/1)
|
||||
|> process("", colors)
|
||||
end
|
||||
|
||||
defp process([], _indent, _colors), do: nil
|
||||
|
||||
defp process(["# " <> heading | rest], _indent, colors) do
|
||||
write_h1(String.strip(heading), colors)
|
||||
process(rest, "", colors)
|
||||
end
|
||||
|
||||
defp process(["## " <> heading | rest], _indent, colors) do
|
||||
write_h2(String.strip(heading), colors)
|
||||
process(rest, "", colors)
|
||||
end
|
||||
|
||||
defp process(["### " <> heading | rest], indent, colors) do
|
||||
write_h3(String.strip(heading), indent, colors)
|
||||
process(rest, indent, colors)
|
||||
end
|
||||
|
||||
defp process(["" | rest], indent, colors) do
|
||||
process(rest, indent, colors)
|
||||
end
|
||||
|
||||
defp process([" " <> line | rest], indent, colors) do
|
||||
process_code(rest, [line], indent, colors)
|
||||
end
|
||||
|
||||
defp process([line | rest], indent, colors) do
|
||||
{ stripped, count } = strip_spaces(line, 0)
|
||||
case stripped do
|
||||
<<bullet, ?\s, item :: binary >> when bullet in @bullets ->
|
||||
process_list(item, rest, count, indent, colors)
|
||||
_ ->
|
||||
process_text(rest, [line], indent, false, colors)
|
||||
end
|
||||
end
|
||||
|
||||
defp strip_spaces(" " <> line, acc) do
|
||||
strip_spaces(line, acc + 1)
|
||||
end
|
||||
|
||||
defp strip_spaces(rest, acc) do
|
||||
{ rest, acc }
|
||||
end
|
||||
|
||||
## Headings
|
||||
|
||||
defp write_h1(heading, colors) do
|
||||
write_h2(String.upcase(heading), colors)
|
||||
end
|
||||
|
||||
defp write_h2(heading, colors) do
|
||||
write(:doc_headings, heading, colors)
|
||||
end
|
||||
|
||||
defp write_h3(heading, indent, colors) do
|
||||
IO.write(indent)
|
||||
write(:doc_headings, heading, colors)
|
||||
end
|
||||
|
||||
## Lists
|
||||
|
||||
defp process_list(line, rest, count, indent, colors) do
|
||||
IO.write indent <> "• "
|
||||
{ contents, rest, done } = process_list_next(rest, count, false, [])
|
||||
process_text(contents, [line], indent <> " ", true, colors)
|
||||
if done, do: IO.puts(IO.ANSI.reset)
|
||||
process(rest, indent, colors)
|
||||
end
|
||||
|
||||
# Process the thing after a list item entry. It can be either:
|
||||
#
|
||||
# * Continuation of the list
|
||||
# * A nested list
|
||||
# * The end of the list
|
||||
#
|
||||
defp process_list_next([" " <> _ = line | rest], count, _done, acc) do
|
||||
case list_next(line, count) do
|
||||
:done -> { Enum.reverse(acc), [line|rest], false }
|
||||
chopped -> process_list_next(rest, count, false, [chopped|acc])
|
||||
end
|
||||
end
|
||||
|
||||
defp process_list_next([<<bullet, ?\s, _ :: binary>> | _] = rest, _count, _done, acc) when bullet in @bullets do
|
||||
{ Enum.reverse(acc), rest, false }
|
||||
end
|
||||
|
||||
defp process_list_next(["" | rest], count, _done, acc) do
|
||||
process_list_next(rest, count, true, [""|acc])
|
||||
end
|
||||
|
||||
defp process_list_next(rest, _count, done, acc) do
|
||||
{ Enum.reverse(acc), rest, done }
|
||||
end
|
||||
|
||||
defp list_next(<<bullet, ?\s, _ :: binary>>, 0) when bullet in @bullets, do: :done
|
||||
defp list_next(line, 0), do: chop(line, 2)
|
||||
defp list_next(" " <> line, acc), do: list_next(line, acc - 1)
|
||||
defp list_next(line, _acc), do: line
|
||||
|
||||
defp chop(" " <> line, acc) when acc > 0, do: chop(line, acc - 1)
|
||||
defp chop(line, _acc), do: line
|
||||
|
||||
## Text (paragraphs / lists)
|
||||
|
||||
defp process_text(doc=["" | _], para, indent, from_list, colors) do
|
||||
write_text(Enum.reverse(para), indent, from_list, colors)
|
||||
process(doc, indent, colors)
|
||||
end
|
||||
|
||||
defp process_text([], para, indent, from_list, colors) do
|
||||
write_text(Enum.reverse(para), indent, from_list, colors)
|
||||
end
|
||||
|
||||
defp process_text([line | rest], para, indent, true, colors) do
|
||||
{ stripped, count } = strip_spaces(line, 0)
|
||||
case stripped do
|
||||
<<bullet, ?\s, item :: binary>> when bullet in @bullets ->
|
||||
write_text(Enum.reverse(para), indent, true, colors)
|
||||
process_list(item, rest, count, indent, colors)
|
||||
_ ->
|
||||
process_text(rest, [line | para], indent, true, colors)
|
||||
end
|
||||
end
|
||||
|
||||
defp process_text([line | rest], para, indent, from_list, colors) do
|
||||
process_text(rest, [line | para], indent, from_list, colors)
|
||||
end
|
||||
|
||||
defp write_text(lines, indent, from_list, colors) do
|
||||
lines
|
||||
|> Enum.join(" ")
|
||||
|> handle_links
|
||||
|> handle_inline(nil, [], [], colors)
|
||||
|> String.split(%r{\s})
|
||||
|> write_with_wrap(column_width() - size(indent), indent, from_list)
|
||||
|
||||
unless from_list, do: IO.puts(IO.ANSI.reset)
|
||||
end
|
||||
|
||||
## Code blocks
|
||||
|
||||
defp process_code([], code, indent, colors) do
|
||||
write_code(code, indent, colors)
|
||||
end
|
||||
|
||||
# Blank line between code blocks
|
||||
defp process_code([ "", " " <> line | rest ], code, indent, colors) do
|
||||
process_code(rest, [line, "" | code], indent, colors)
|
||||
end
|
||||
|
||||
defp process_code([ " " <> line | rest ], code, indent, colors) do
|
||||
process_code(rest, [line|code], indent, colors)
|
||||
end
|
||||
|
||||
defp process_code(rest, code, indent, colors) do
|
||||
write_code(code, indent, colors)
|
||||
process(rest, indent, colors)
|
||||
end
|
||||
|
||||
defp write_code(code, indent, colors) do
|
||||
write(:doc_code, "#{indent}┃ #{Enum.join(Enum.reverse(code), "\n#{indent}┃ ")}", colors)
|
||||
end
|
||||
|
||||
## Helpers
|
||||
|
||||
defp write(style, string, colors) do
|
||||
IO.puts color(style, colors) <> string <> IO.ANSI.reset
|
||||
IO.puts IO.ANSI.reset
|
||||
end
|
||||
|
||||
defp write_with_wrap([], _available, _indent, _first) do
|
||||
:ok
|
||||
end
|
||||
|
||||
defp write_with_wrap(words, available, indent, first) do
|
||||
{ words, rest } = take_words(words, available, [])
|
||||
IO.puts (if first, do: "", else: indent) <> Enum.join(words, " ")
|
||||
write_with_wrap(rest, available, indent, false)
|
||||
end
|
||||
|
||||
defp take_words([word|words], available, acc) do
|
||||
available = available - length_without_escape(word, 0)
|
||||
|
||||
cond do
|
||||
# It fits, take one for space and continue decreasing
|
||||
available > 0 ->
|
||||
take_words(words, available - 1, [word|acc])
|
||||
|
||||
# No space but we got no words
|
||||
acc == [] ->
|
||||
{ [word], words }
|
||||
|
||||
# Otherwise
|
||||
true ->
|
||||
{ Enum.reverse(acc), [word|words] }
|
||||
end
|
||||
end
|
||||
|
||||
defp take_words([], _available, acc) do
|
||||
{ Enum.reverse(acc), [] }
|
||||
end
|
||||
|
||||
defp length_without_escape(<< ?\e, ?[, _, _, ?m, rest :: binary >>, count) do
|
||||
length_without_escape(rest, count)
|
||||
end
|
||||
|
||||
defp length_without_escape(<< ?\e, ?[, _, ?m, rest :: binary >>, count) do
|
||||
length_without_escape(rest, count)
|
||||
end
|
||||
|
||||
defp length_without_escape(rest, count) do
|
||||
case String.next_grapheme(rest) do
|
||||
{ _, rest } -> length_without_escape(rest, count + 1)
|
||||
nil -> count
|
||||
end
|
||||
end
|
||||
|
||||
defp handle_links(text) do
|
||||
text
|
||||
|> remove_square_brackets_in_link
|
||||
|> escape_underlines_in_link
|
||||
end
|
||||
|
||||
defp escape_underlines_in_link(text) do
|
||||
case Regex.match?(%r{.*(https?\S*)}, text) do
|
||||
true ->
|
||||
Regex.replace(%r{_}, text, "\\\\_")
|
||||
_ ->
|
||||
text
|
||||
end
|
||||
end
|
||||
|
||||
defp remove_square_brackets_in_link(text) do
|
||||
Regex.replace(%r{\[(.*?)\]\((.*?)\)}, text, "\\1 (\\2)")
|
||||
end
|
||||
|
||||
# Single inline quotes.
|
||||
@single [?`, ?_, ?*]
|
||||
|
||||
# ` does not require space in between
|
||||
@spaced [?_, ?*]
|
||||
|
||||
# Clauses for handling spaces
|
||||
defp handle_inline(<<?*, ?*, ?\s, rest :: binary>>, nil, buffer, acc, colors) do
|
||||
handle_inline(rest, nil, [?\s, ?*, ?*|buffer], acc, colors)
|
||||
end
|
||||
|
||||
defp handle_inline(<<mark, ?\s, rest :: binary>>, nil, buffer, acc, colors) when mark in @spaced do
|
||||
handle_inline(rest, nil, [?\s, mark|buffer], acc, colors)
|
||||
end
|
||||
|
||||
defp handle_inline(<<?\s, ?*, ?*, rest :: binary>>, limit, buffer, acc, colors) do
|
||||
handle_inline(rest, limit, [?*, ?*, ?\s|buffer], acc, colors)
|
||||
end
|
||||
|
||||
defp handle_inline(<<?\s, mark, rest :: binary>>, limit, buffer, acc, colors) when mark in @spaced do
|
||||
handle_inline(rest, limit, [mark, ?\s|buffer], acc, colors)
|
||||
end
|
||||
|
||||
# Clauses for handling escape
|
||||
defp handle_inline(<<?\\, ?\\, rest :: binary>>, limit, buffer, acc, colors) do
|
||||
handle_inline(rest, limit, [?\\|buffer], acc, colors)
|
||||
end
|
||||
|
||||
defp handle_inline(<<?\\, ?*, ?*, rest :: binary>>, limit, buffer, acc, colors) do
|
||||
handle_inline(rest, limit, [?*, ?*|buffer], acc, colors)
|
||||
end
|
||||
|
||||
# A escape is not valid inside `
|
||||
defp handle_inline(<<?\\, mark, rest :: binary>>, limit, buffer, acc, colors)
|
||||
when mark in [?_, ?*, ?`] and not(mark == limit and mark == ?`) do
|
||||
handle_inline(rest, limit, [mark|buffer], acc, colors)
|
||||
end
|
||||
|
||||
# Inline start
|
||||
defp handle_inline(<<?*, ?*, rest :: binary>>, nil, buffer, acc, colors) when rest != "" do
|
||||
handle_inline(rest, ?d, ["**"], [Enum.reverse(buffer)|acc], colors)
|
||||
end
|
||||
|
||||
defp handle_inline(<<mark, rest :: binary>>, nil, buffer, acc, colors) when rest != "" and mark in @single do
|
||||
handle_inline(rest, mark, [<<mark>>], [Enum.reverse(buffer)|acc], colors)
|
||||
end
|
||||
|
||||
# Inline end
|
||||
defp handle_inline(<<?*, ?*, rest :: binary>>, ?d, buffer, acc, colors) do
|
||||
handle_inline(rest, nil, [], [inline_buffer(buffer, colors)|acc], colors)
|
||||
end
|
||||
|
||||
defp handle_inline(<<mark, rest :: binary>>, mark, buffer, acc, colors) when mark in @single do
|
||||
handle_inline(rest, nil, [], [inline_buffer(buffer, colors)|acc], colors)
|
||||
end
|
||||
|
||||
defp handle_inline(<<char, rest :: binary>>, mark, buffer, acc, colors) do
|
||||
handle_inline(rest, mark, [char|buffer], acc, colors)
|
||||
end
|
||||
|
||||
defp handle_inline(<<>>, _mark, buffer, acc, _colors) do
|
||||
iolist_to_binary Enum.reverse([Enum.reverse(buffer)|acc])
|
||||
end
|
||||
|
||||
defp inline_buffer(buffer, colors) do
|
||||
[h|t] = Enum.reverse([IO.ANSI.reset|buffer])
|
||||
[color_for(h, colors)|t]
|
||||
end
|
||||
|
||||
defp color_for("`", colors), do: color(:doc_inline_code, colors)
|
||||
defp color_for("_", colors), do: color(:doc_underline, colors)
|
||||
defp color_for("*", colors), do: color(:doc_bold, colors)
|
||||
defp color_for("**", colors), do: color(:doc_bold, colors)
|
||||
|
||||
defp color(style, colors) do
|
||||
color = colors[style]
|
||||
enabled = colors[:enabled]
|
||||
IO.ANSI.escape_fragment("%{#{color}}", enabled)
|
||||
end
|
||||
|
||||
defp column_width() do
|
||||
case :io.columns do
|
||||
{ :ok, width } -> min(width, 80)
|
||||
_ -> 80
|
||||
end
|
||||
end
|
||||
end
|
||||
+1873
-1752
File diff suppressed because it is too large
Load Diff
@@ -221,7 +221,7 @@ defmodule Kernel.CLI do
|
||||
defp process_compiler(["--warnings-as-errors"|t], config) do
|
||||
process_compiler t, config.update_compiler_options(&[{:warnings_as_errors, true}|&1])
|
||||
end
|
||||
|
||||
|
||||
defp process_compiler(["--verbose"|t], config) do
|
||||
process_compiler t, config.verbose_compile(true)
|
||||
end
|
||||
@@ -339,18 +339,42 @@ defmodule Kernel.CLI do
|
||||
defp process_command({:compile, patterns}, config) do
|
||||
:filelib.ensure_dir(:filename.join(config.output, "."))
|
||||
|
||||
files = Enum.map patterns, &Path.wildcard(&1)
|
||||
files = Enum.uniq(Enum.concat(files))
|
||||
files = Enum.filter files, &:filelib.is_regular(&1)
|
||||
case match_regular_files(patterns) do
|
||||
{ :ok, [] } ->
|
||||
{ :error, "No files matched provided patterns" }
|
||||
{ :ok, files } ->
|
||||
wrapper fn ->
|
||||
Code.compiler_options(config.compiler_options)
|
||||
Kernel.ParallelCompiler.files_to_path(files, config.output,
|
||||
each_file: fn file -> if config.verbose_compile do IO.puts "Compiled #{file}" end end)
|
||||
end
|
||||
{ :missing, missing } ->
|
||||
{ :error, "No files matched pattern(s) #{Enum.join(missing, ",")}" }
|
||||
end
|
||||
end
|
||||
|
||||
if files != [] do
|
||||
wrapper fn ->
|
||||
Code.compiler_options(config.compiler_options)
|
||||
Kernel.ParallelCompiler.files_to_path(files, config.output,
|
||||
each_file: fn file -> if config.verbose_compile do IO.puts "Compiled #{file}" end end)
|
||||
defp match_regular_files(patterns) do
|
||||
matched_files = Enum.map patterns, fn(pattern) ->
|
||||
case Path.wildcard(pattern) do
|
||||
[] -> { :missing, pattern }
|
||||
files -> { :ok, files }
|
||||
end
|
||||
end
|
||||
|
||||
files = Enum.filter_map matched_files,
|
||||
fn(match) -> elem(match, 0) == :ok end,
|
||||
&elem(&1, 1)
|
||||
|
||||
missing_patterns = Enum.filter_map matched_files,
|
||||
fn(match) -> elem(match, 0) == :missing end,
|
||||
&elem(&1, 1)
|
||||
|
||||
if missing_patterns == [] do
|
||||
files = Enum.uniq(Enum.concat(files))
|
||||
files = Enum.filter files, &:filelib.is_regular(&1)
|
||||
{ :ok, files }
|
||||
else
|
||||
{ :error, "--compile : No files matched patterns #{Enum.join(patterns, ",")}" }
|
||||
{ :missing, Enum.uniq(missing_patterns) }
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -18,9 +18,19 @@ defmodule Kernel.ErrorHandler do
|
||||
{ :module, _ } -> []
|
||||
{ :error, _ } ->
|
||||
parent = :erlang.get(:elixir_compiler_pid)
|
||||
parent <- { :waiting, self(), module }
|
||||
ref = :erlang.make_ref
|
||||
send parent, { :waiting, self(), ref, module }
|
||||
:erlang.garbage_collect(self)
|
||||
receive do
|
||||
{ :release, ^parent } -> :ok
|
||||
{ ^ref, :ready } ->
|
||||
:ok
|
||||
{ ^ref, :release } ->
|
||||
# On release, get rid of the compiler pid,
|
||||
# no further allow elixir_ensure_compiled directives
|
||||
# and revert to the original error handler.
|
||||
:erlang.erase(:elixir_compiler_pid)
|
||||
:erlang.erase(:elixir_ensure_compiled)
|
||||
:erlang.process_flag(:error_handler, :error_handler)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
# any of the `GenServer.Behaviour` conveniences.
|
||||
defmodule Kernel.LexicalTracker do
|
||||
@timeout 30_000
|
||||
@behavior :gen_server
|
||||
@behaviour :gen_server
|
||||
|
||||
@import 2
|
||||
@alias 3
|
||||
@@ -104,8 +104,8 @@ defmodule Kernel.LexicalTracker do
|
||||
{ :reply, d, d }
|
||||
end
|
||||
|
||||
def handle_call(_request, _from, d) do
|
||||
{ :noreply, d }
|
||||
def handle_call(request, _from, d) do
|
||||
{ :stop, { :bad_call, request }, d }
|
||||
end
|
||||
|
||||
def handle_cast({ :remote_dispatch, module }, d) do
|
||||
@@ -137,8 +137,8 @@ defmodule Kernel.LexicalTracker do
|
||||
{ :stop, :normal, d }
|
||||
end
|
||||
|
||||
def handle_cast(_msg, d) do
|
||||
{ :noreply, d }
|
||||
def handle_cast(msg, d) do
|
||||
{ :stop, { :bad_cast, msg }, d }
|
||||
end
|
||||
|
||||
def handle_info(_msg, d) do
|
||||
|
||||
@@ -62,29 +62,42 @@ defmodule Kernel.ParallelCompiler do
|
||||
wait_for_messages(files, original, output, callbacks, waiting, queued, schedulers, result)
|
||||
end
|
||||
|
||||
# Release waiting processes
|
||||
defp spawn_compilers([h|t], original, output, callbacks, waiting, queued, schedulers, result) when is_pid(h) do
|
||||
{ ^h, ref, _ } = List.keyfind(waiting, h, 0)
|
||||
send h, { ref, :ready }
|
||||
waiting = List.keydelete(waiting, h, 0)
|
||||
spawn_compilers(t, original, output, callbacks, waiting, queued, schedulers, result)
|
||||
end
|
||||
|
||||
# Spawn a compiler for each file in the list until we reach the limit
|
||||
defp spawn_compilers([h|t], original, output, callbacks, waiting, queued, schedulers, result) do
|
||||
parent = self()
|
||||
|
||||
child = spawn_link fn ->
|
||||
:erlang.put(:elixir_compiler_pid, parent)
|
||||
:erlang.put(:elixir_ensure_compiled, true)
|
||||
:erlang.process_flag(:error_handler, Kernel.ErrorHandler)
|
||||
{ pid, ref } =
|
||||
:erlang.spawn_monitor fn ->
|
||||
# Notify Code.ensure_compiled/2 that we should
|
||||
# attempt to compile the module by doing a dispatch.
|
||||
:erlang.put(:elixir_ensure_compiled, true)
|
||||
|
||||
try do
|
||||
if output do
|
||||
:elixir_compiler.file_to_path(h, output)
|
||||
else
|
||||
:elixir_compiler.file(h)
|
||||
end
|
||||
parent <- { :compiled, self(), h }
|
||||
catch
|
||||
kind, reason ->
|
||||
parent <- { :failure, self(), kind, reason, System.stacktrace }
|
||||
# Set the elixir_compiler_pid used by our custom Kernel.ErrorHandler.
|
||||
:erlang.put(:elixir_compiler_pid, parent)
|
||||
:erlang.process_flag(:error_handler, Kernel.ErrorHandler)
|
||||
|
||||
exit(try do
|
||||
if output do
|
||||
:elixir_compiler.file_to_path(h, output)
|
||||
else
|
||||
:elixir_compiler.file(h)
|
||||
end
|
||||
{ :compiled, h }
|
||||
catch
|
||||
kind, reason ->
|
||||
{ :failure, kind, reason, System.stacktrace }
|
||||
end)
|
||||
end
|
||||
end
|
||||
|
||||
spawn_compilers(t, original, output, callbacks, waiting, [{child, h}|queued], schedulers, result)
|
||||
spawn_compilers(t, original, output, callbacks, waiting, [{pid, ref, h}|queued], schedulers, result)
|
||||
end
|
||||
|
||||
# No more files, nothing waiting, queue is empty, we are done
|
||||
@@ -92,7 +105,10 @@ defmodule Kernel.ParallelCompiler do
|
||||
|
||||
# Queued x, waiting for x: POSSIBLE ERROR! Release processes so we get the failures
|
||||
defp spawn_compilers([], original, output, callbacks, waiting, queued, schedulers, result) when length(waiting) == length(queued) do
|
||||
Enum.each queued, fn { child, _ } -> child <- { :release, self() } end
|
||||
Enum.each queued, fn { child, _, _ } ->
|
||||
{ ^child, ref, _ } = List.keyfind(waiting, child, 0)
|
||||
send child, { ref, :release }
|
||||
end
|
||||
wait_for_messages([], original, output, callbacks, waiting, queued, schedulers, result)
|
||||
end
|
||||
|
||||
@@ -104,38 +120,61 @@ defmodule Kernel.ParallelCompiler do
|
||||
# Wait for messages from child processes
|
||||
defp wait_for_messages(files, original, output, callbacks, waiting, queued, schedulers, result) do
|
||||
receive do
|
||||
{ :compiled, child, file } ->
|
||||
if callback = Keyword.get(callbacks, :each_file) do
|
||||
callback.(file)
|
||||
end
|
||||
new_queued = List.keydelete(queued, child, 0)
|
||||
|
||||
# Sometimes we may have spurious entries in the waiting
|
||||
# list because someone invoked try/rescue UndefinedFunctionError
|
||||
new_waiting = List.keydelete(waiting, child, 0)
|
||||
spawn_compilers(files, original, output, callbacks, new_waiting, new_queued, schedulers, result)
|
||||
{ :module_available, child, ref, file, module, binary } ->
|
||||
if callback = Keyword.get(callbacks, :each_module) do
|
||||
callback.(file, module, binary)
|
||||
end
|
||||
|
||||
# Release the module loader which is waiting for an ack
|
||||
child <- { ref, :ack }
|
||||
send child, { ref, :ack }
|
||||
|
||||
new_waiting = release_waiting_processes(module, waiting)
|
||||
new_result = [module|result]
|
||||
wait_for_messages(files, original, output, callbacks, new_waiting, queued, schedulers, new_result)
|
||||
{ :waiting, child, on } ->
|
||||
new_waiting = :orddict.store(child, on, waiting)
|
||||
spawn_compilers(files, original, output, callbacks, new_waiting, queued, schedulers, result)
|
||||
{ :failure, child, kind, reason, stacktrace } ->
|
||||
available = lc { pid, _, waiting_module } inlist waiting,
|
||||
waiting_module == module,
|
||||
do: pid
|
||||
|
||||
spawn_compilers(available ++ files, original, output, callbacks,
|
||||
waiting, queued, schedulers, [module|result])
|
||||
|
||||
{ :waiting, child, ref, on } ->
|
||||
# Oops, we already got this module. Do not put it on waiting.
|
||||
if :lists.member(on, result) do
|
||||
send child, { :release, ref }
|
||||
else
|
||||
waiting = [{ child, ref, on }|waiting]
|
||||
end
|
||||
spawn_compilers(files, original, output, callbacks, waiting, queued, schedulers, result)
|
||||
|
||||
{ :DOWN, _down_ref, :process, down_pid, { :compiled, file } } ->
|
||||
if callback = Keyword.get(callbacks, :each_file) do
|
||||
callback.(file)
|
||||
end
|
||||
|
||||
# Sometimes we may have spurious entries in the waiting
|
||||
# list because someone invoked try/rescue UndefinedFunctionError
|
||||
new_waiting = List.keydelete(waiting, down_pid, 0)
|
||||
new_queued = List.keydelete(queued, down_pid, 0)
|
||||
spawn_compilers(files, original, output, callbacks, new_waiting, new_queued, schedulers, result)
|
||||
|
||||
{ :DOWN, down_ref, :process, _down_pid, { :failure, kind, reason, stacktrace } } ->
|
||||
handle_failure(down_ref, kind, reason, stacktrace, files, waiting, queued)
|
||||
wait_for_messages(files, original, output, callbacks, waiting, queued, schedulers, result)
|
||||
|
||||
{ :DOWN, down_ref, :process, _down_pid, other } ->
|
||||
handle_failure(down_ref, :exit, other, [], files, waiting, queued)
|
||||
wait_for_messages(files, original, output, callbacks, waiting, queued, schedulers, result)
|
||||
end
|
||||
end
|
||||
|
||||
defp handle_failure(ref, kind, reason, stacktrace, files, waiting, queued) do
|
||||
case List.keyfind(queued, ref, 1) do
|
||||
{ child, ^ref, file } ->
|
||||
if many_missing?(child, files, waiting, queued) do
|
||||
IO.puts "== Compilation failed =="
|
||||
IO.puts "Compilation failed on the following files:\n"
|
||||
|
||||
Enum.each Enum.reverse(queued), fn { pid, file } ->
|
||||
Enum.each Enum.reverse(queued), fn { pid, _ref, file } ->
|
||||
case List.keyfind(waiting, pid, 0) do
|
||||
{ _, mod } -> IO.puts "* #{file} is missing module #{inspect mod}"
|
||||
{ ^pid, _, mod } -> IO.puts "* #{file} is missing module #{inspect mod}"
|
||||
_ -> :ok
|
||||
end
|
||||
end
|
||||
@@ -143,29 +182,18 @@ defmodule Kernel.ParallelCompiler do
|
||||
IO.puts "\nThe first failure is shown below..."
|
||||
end
|
||||
|
||||
{^child, file} = List.keyfind(queued, child, 0)
|
||||
IO.puts "== Compilation error on file #{file} =="
|
||||
:erlang.raise(kind, reason, stacktrace)
|
||||
_ ->
|
||||
:ok
|
||||
end
|
||||
end
|
||||
|
||||
defp many_missing?(child, files, waiting, queued) do
|
||||
waiting_length = length(waiting)
|
||||
|
||||
match?({ ^child, _ }, List.keyfind(waiting, child, 0)) and
|
||||
match?({ ^child, _, _ }, List.keyfind(waiting, child, 0)) and
|
||||
waiting_length > 1 and files == [] and
|
||||
waiting_length == length(queued)
|
||||
end
|
||||
|
||||
# Release waiting processes that are waiting for the given module
|
||||
defp release_waiting_processes(module, waiting) do
|
||||
Enum.filter waiting, fn { child, waiting_module } ->
|
||||
if waiting_module == module do
|
||||
child <- { :release, self() }
|
||||
false
|
||||
else
|
||||
true
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -35,7 +35,7 @@ defmodule Kernel.ParallelRequire do
|
||||
ensure_compiled = :erlang.get(:elixir_ensure_compiled)
|
||||
{ :error_handler, handler } = :erlang.process_info(parent, :error_handler)
|
||||
|
||||
child = spawn_link fn ->
|
||||
{ pid, ref } = :erlang.spawn_monitor fn ->
|
||||
if compiler_pid != :undefined do
|
||||
:erlang.put(:elixir_compiler_pid, compiler_pid)
|
||||
end
|
||||
@@ -46,25 +46,35 @@ defmodule Kernel.ParallelRequire do
|
||||
|
||||
:erlang.process_flag(:error_handler, handler)
|
||||
|
||||
try do
|
||||
exit(try do
|
||||
new = Code.require_file(h) || []
|
||||
parent <- { :required, self, Enum.map(new, &elem(&1, 0)), h }
|
||||
{ :required, Enum.map(new, &elem(&1, 0)), h }
|
||||
catch
|
||||
kind, reason ->
|
||||
parent <- { :failure, self, kind, reason, System.stacktrace }
|
||||
end
|
||||
{ :failure, kind, reason, System.stacktrace }
|
||||
end)
|
||||
end
|
||||
|
||||
spawn_requires(t, [child|waiting], callback, schedulers, result)
|
||||
spawn_requires(t, [{ pid, ref }|waiting], callback, schedulers, result)
|
||||
end
|
||||
|
||||
defp wait_for_messages(files, waiting, callback, schedulers, result) do
|
||||
receive do
|
||||
{ :required, child, mods, file } ->
|
||||
callback.(file)
|
||||
spawn_requires(files, List.delete(waiting, child), callback, schedulers, mods ++ result)
|
||||
{ :failure, _child, kind, reason, stacktrace } ->
|
||||
:erlang.raise(kind, reason, stacktrace)
|
||||
{ :DOWN, ref, :process, pid, status } ->
|
||||
tuple = { pid, ref }
|
||||
if tuple in waiting do
|
||||
case status do
|
||||
{ :required, mods, file } ->
|
||||
callback.(file)
|
||||
result = mods ++ result
|
||||
waiting = List.delete(waiting, tuple)
|
||||
{ :failure, kind, reason, stacktrace } ->
|
||||
:erlang.raise(kind, reason, stacktrace)
|
||||
other ->
|
||||
:erlang.raise(:exit, other, [])
|
||||
end
|
||||
end
|
||||
spawn_requires(files, waiting, callback, schedulers, result)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -111,7 +111,7 @@ defmodule Kernel.RecordRewriter do
|
||||
|
||||
defp optimize_record_other_call(line, record, res, function, left, args) do
|
||||
call = { :call, line,
|
||||
{ :remote, line, { :atom, line, record }, { :atom, 0, function } },
|
||||
{ :remote, line, { :atom, 0, record }, { :atom, 0, function } },
|
||||
args ++ [left]
|
||||
}
|
||||
{ call, res }
|
||||
@@ -128,7 +128,7 @@ defmodule Kernel.RecordRewriter do
|
||||
{ { :call, call_line, remote, [integer, tuple, value] }, dict, res }
|
||||
end
|
||||
|
||||
defp optimize_expr({ :call, call_line, { :remote, line, left, right }, args } = call, module, dict) do
|
||||
defp optimize_expr({ :call, call_line, { :remote, line, left, right }, args }, module, dict) do
|
||||
{ left, dict, res } = optimize_expr(left, module, dict)
|
||||
{ right, dict, _ } = optimize_expr(right, module, dict)
|
||||
{ args, dict } = optimize_args(args, module, dict)
|
||||
|
||||
@@ -1,22 +1,22 @@
|
||||
defmodule Kernel.SpecialForms do
|
||||
@moduledoc """
|
||||
In this module we define Elixir special forms. Special forms
|
||||
cannot be overriden by the developer and are the basic
|
||||
cannot be overridden by the developer and are the basic
|
||||
building blocks of Elixir code.
|
||||
|
||||
Some of those forms are lexical (like `alias`, `import`, etc).
|
||||
The macros `{}`, `[]` and `<<>>` are also special forms used
|
||||
to define data structures, respectively tuples, lists and binaries.
|
||||
Some of those forms are lexical (like `alias`, `case`, etc).
|
||||
The macros `{}` and `<<>>` are also special forms used to define
|
||||
tuple and binary data structures respectively.
|
||||
|
||||
This module also documents Elixir's pseudo variables (`__MODULE__`,
|
||||
`__FILE__`, `__ENV__`, `__DIR__` and `__CALLER__`). Pseudo variables
|
||||
return information about Elixir's compilation environment and can only
|
||||
This module also documents Elixir's pseudo variables (`__ENV__`,
|
||||
`__MODULE__`, `__DIR__` and `__CALLER__`). Pseudo variables return
|
||||
information about Elixir's compilation environment and can only
|
||||
be read, never assigned to.
|
||||
|
||||
Finally, it also documents 3 special forms (`__block__`,
|
||||
`__scope__` and `__aliases__`), which are not intended to be
|
||||
called directly by the developer but they appear in quoted
|
||||
contents since they are essential in Elixir's constructs.
|
||||
Finally, it also documents 2 special forms, `__block__` and
|
||||
`__aliases__`, which are not intended to be called directly by the
|
||||
developer but they appear in quoted contents since they are essential
|
||||
in Elixir's constructs.
|
||||
"""
|
||||
|
||||
@doc """
|
||||
@@ -34,7 +34,7 @@ defmodule Kernel.SpecialForms do
|
||||
{ :{}, [], [1,2,3] }
|
||||
|
||||
"""
|
||||
defmacro :{}.(args)
|
||||
defmacro unquote(:{})(args)
|
||||
|
||||
@doc """
|
||||
Defines a new bitstring.
|
||||
@@ -171,7 +171,7 @@ defmodule Kernel.SpecialForms do
|
||||
For floats, unit * size must result in 32 or 64, corresponding
|
||||
to binary32 and binary64, respectively.
|
||||
"""
|
||||
defmacro :<<>>.(args)
|
||||
defmacro unquote(:<<>>)(args)
|
||||
|
||||
@doc """
|
||||
Defines a remote call or an alias.
|
||||
@@ -227,7 +227,7 @@ defmodule Kernel.SpecialForms do
|
||||
In case the right-side is also dynamic, `.`'s behaviour can be reproduced
|
||||
at runtime via `apply/3` and `Module.concat/2`:
|
||||
|
||||
iex> apply(Kernel, :+, [1,2])
|
||||
iex> apply(:erlang, :+, [1,2])
|
||||
3
|
||||
iex> Module.concat(Kernel, Sample)
|
||||
Kernel.Sample
|
||||
@@ -284,7 +284,7 @@ defmodule Kernel.SpecialForms do
|
||||
[{:__aliases__, [alias: false], [:String]}, Sample]}
|
||||
|
||||
"""
|
||||
defmacro (:.).(left, right)
|
||||
defmacro unquote(:.)(left, right)
|
||||
|
||||
@doc """
|
||||
`alias` is used to setup aliases, often useful with modules names.
|
||||
@@ -471,19 +471,45 @@ defmodule Kernel.SpecialForms do
|
||||
"""
|
||||
defmacro __MODULE__
|
||||
|
||||
@doc """
|
||||
Returns the current file name as a binary.
|
||||
|
||||
Although the file can be accessed in the `__ENV__`, this macro
|
||||
is a convenient shortcut.
|
||||
"""
|
||||
defmacro __FILE__
|
||||
|
||||
@doc """
|
||||
Returns the current directory as a binary.
|
||||
|
||||
Although the directory can be accessed as `Path.dirname(__ENV__.file)`,
|
||||
this macro is a convenient shortcut.
|
||||
"""
|
||||
defmacro __DIR__
|
||||
|
||||
@doc """
|
||||
Accesses an already bound variable in match clauses.
|
||||
|
||||
## Examples
|
||||
|
||||
Elixir allows variables to be rebound via static single assignment:
|
||||
|
||||
iex> x = 1
|
||||
iex> x = 2
|
||||
iex> x
|
||||
2
|
||||
|
||||
However, in some situations, it is useful to match against an existing
|
||||
value, instead of rebinding. This can be done with the `^` special form:
|
||||
|
||||
iex> x = 1
|
||||
iex> ^x = 1
|
||||
iex> ^x = 2
|
||||
** (MatchError) no match of right hand side value: 2
|
||||
|
||||
Note that `^` always refers to the value of x prior to the match. The
|
||||
following example will match:
|
||||
|
||||
iex> x = 0
|
||||
iex> { x, ^x } = { 1, 0 }
|
||||
iex> x
|
||||
1
|
||||
|
||||
"""
|
||||
defmacro ^(var)
|
||||
|
||||
@doc %S"""
|
||||
Gets the representation of any expression.
|
||||
|
||||
@@ -532,14 +558,14 @@ defmodule Kernel.SpecialForms do
|
||||
2.0 #=> Floats
|
||||
[1, 2] #=> Lists
|
||||
"strings" #=> Strings
|
||||
{key, value} #=> Tuple with two elements
|
||||
{key, value} #=> Tuples with two elements
|
||||
|
||||
## Quote and macros
|
||||
|
||||
`quote` is commonly used with macros for code generation. As an exercise,
|
||||
let's define a macro that multiplies a number by itself (squared). Note
|
||||
there is no reason to define such as a macro (and it would actually be
|
||||
seen as a bad practice), but it is simple enough that allows us to focus
|
||||
seen as a bad practice), but it is simple enough that it allows us to focus
|
||||
on the important aspects of quotes and macros:
|
||||
|
||||
defmodule Math do
|
||||
@@ -557,8 +583,9 @@ defmodule Kernel.SpecialForms do
|
||||
|
||||
At first, there is nothing in this example that actually reveals it is a
|
||||
macro. But what is happening is that, at compilation time, `squared(5)`
|
||||
becomes `5 * 5`. We can see this behaviour in practice though
|
||||
because our macro actually has a bug:
|
||||
becomes `5 * 5`. The argument `5` is duplicated in the produced code, we
|
||||
can see this behaviour in practice though because our macro actually has
|
||||
a bug:
|
||||
|
||||
import Math
|
||||
my_number = fn ->
|
||||
@@ -613,11 +640,11 @@ defmodule Kernel.SpecialForms do
|
||||
end
|
||||
end
|
||||
|
||||
`:bind_quoted` will translate to the same example as above. `:bind_quoted`
|
||||
can be used in many cases and is seen as good practice, not only because
|
||||
it helps us from running into common mistakes but also because it allows
|
||||
us to leverage other tools exposed by macros, as unquote fragments discussed
|
||||
in some sections below.
|
||||
`:bind_quoted` will translate to the same code as the example above.
|
||||
`:bind_quoted` can be used in many cases and is seen as good practice,
|
||||
not only because it helps us from running into common mistakes but also
|
||||
because it allows us to leverage other tools exposed by macros, such as
|
||||
unquote fragments discussed in some sections below.
|
||||
|
||||
Before we finish this brief introduction, you will notice that, even though
|
||||
we defined a variable `x` inside our quote:
|
||||
@@ -637,7 +664,7 @@ defmodule Kernel.SpecialForms do
|
||||
because Elixir macros are hygienic, a topic we will discuss at length
|
||||
in the next sections as well.
|
||||
|
||||
### Hygiene in variables
|
||||
## Hygiene in variables
|
||||
|
||||
Consider the following example:
|
||||
|
||||
@@ -655,8 +682,8 @@ defmodule Kernel.SpecialForms do
|
||||
|
||||
In the example above, `a` returns 10 even if the macro
|
||||
is apparently setting it to 1 because variables defined
|
||||
in the macro does not affect the context the macro is executed.
|
||||
If you want to set or get a variable in the user context, you
|
||||
in the macro does not affect the context the macro is executed in.
|
||||
If you want to set or get a variable in the caller's context, you
|
||||
can do it with the help of the `var!` macro:
|
||||
|
||||
defmodule NoHygiene do
|
||||
@@ -671,7 +698,7 @@ defmodule Kernel.SpecialForms do
|
||||
NoHygiene.interference
|
||||
a #=> 1
|
||||
|
||||
Note that you cannot even access variables defined by the same
|
||||
Note that you cannot even access variables defined in the same
|
||||
module unless you explicitly give it a context:
|
||||
|
||||
defmodule Hygiene do
|
||||
@@ -717,7 +744,7 @@ defmodule Kernel.SpecialForms do
|
||||
|
||||
quote hygiene: [vars: false], do: x
|
||||
|
||||
### Hygiene in aliases
|
||||
## Hygiene in aliases
|
||||
|
||||
Aliases inside quote are hygienic by default.
|
||||
Consider the following example:
|
||||
@@ -737,10 +764,23 @@ defmodule Kernel.SpecialForms do
|
||||
in the context the macro is expanded, the code above works
|
||||
because `D` still expands to `HashDict`.
|
||||
|
||||
In some particular cases you may want to access an alias
|
||||
or a module defined in the caller. In such scenarios, you
|
||||
can access it by disabling hygiene with `hygiene: [aliases: false]`
|
||||
or by using the `alias!` macro inside the quote:
|
||||
Similarly, even if we defined an alias with the same name
|
||||
before invoking a macro, it won't affect the macro's result:
|
||||
|
||||
defmodule Hygiene do
|
||||
alias HashDict, as: D
|
||||
|
||||
defmacro no_interference do
|
||||
quote do: D.new
|
||||
end
|
||||
end
|
||||
|
||||
require Hygiene
|
||||
alias SomethingElse, as: D
|
||||
Hygiene.no_interference #=> #HashDict<[]>
|
||||
|
||||
In some cases, you want to access an alias or a module defined
|
||||
in the caller. For such, you can use the `alias!` macro:
|
||||
|
||||
defmodule Hygiene do
|
||||
# This will expand to Elixir.Nested.hello
|
||||
@@ -789,7 +829,16 @@ defmodule Kernel.SpecialForms do
|
||||
Hygiene.return_size #=> 5
|
||||
|
||||
Notice how `return_size` returns 5 even though the `size/1`
|
||||
function is not imported.
|
||||
function is not imported. In fact, even if `return_size` imported
|
||||
a function from another module, it wouldn't affect the function
|
||||
result:
|
||||
|
||||
def return_size do
|
||||
import Dict, only: [size: 1]
|
||||
get_size
|
||||
end
|
||||
|
||||
Calling this new `return_size` will still return 5 as result.
|
||||
|
||||
Elixir is smart enough to delay the resolution to the latest
|
||||
moment possible. So, if you call `size("hello")` inside quote,
|
||||
@@ -842,12 +891,8 @@ defmodule Kernel.SpecialForms do
|
||||
When using `location: :keep` and invalid arguments are given to
|
||||
`Sample.add/2`, the stacktrace information will point to the file
|
||||
and line inside the quote. Without `location: :keep`, the error is
|
||||
reported to where `defadd` was invoked.
|
||||
|
||||
Note that `location: :keep` evaluates the code as if it was defined
|
||||
inside the `Adder` file, in particular, the macro `__FILE__` and
|
||||
exceptions happening inside the quote will always point to the file
|
||||
where `Added` was defined.
|
||||
reported to where `defadd` was invoked. Note `location: :keep` affects
|
||||
only definitions inside the quote.
|
||||
|
||||
## Binding and unquote fragments
|
||||
|
||||
@@ -860,12 +905,12 @@ defmodule Kernel.SpecialForms do
|
||||
def unquote(k)(), do: unquote(v)
|
||||
end
|
||||
|
||||
In the example above, we have generated the function `foo/0` and
|
||||
In the example above, we have generated the functions `foo/0` and
|
||||
`bar/0` dynamically. Now, imagine that, we want to convert this
|
||||
functionality into a macro:
|
||||
|
||||
defmacro defkv(kv) do
|
||||
Enum.each kv, fn { k, v } ->
|
||||
Enum.map kv, fn { k, v } ->
|
||||
quote do
|
||||
def unquote(k)(), do: unquote(v)
|
||||
end
|
||||
@@ -882,13 +927,13 @@ defmodule Kernel.SpecialForms do
|
||||
defkv kv
|
||||
|
||||
This is because the macro is expecting its arguments to be a
|
||||
key-value at **compilation** time. Since in the example above
|
||||
keyword list at **compilation** time. Since in the example above
|
||||
we are passing the representation of the variable `kv`, our
|
||||
code fails.
|
||||
|
||||
This is actually a common pitfall when developing macros. In
|
||||
practice, we want to avoid doing work at compilation time as
|
||||
much as we can. That said, let's attempt to improve our macro:
|
||||
much as possible. That said, let's attempt to improve our macro:
|
||||
|
||||
defmacro defkv(kv) do
|
||||
quote do
|
||||
@@ -900,12 +945,12 @@ defmodule Kernel.SpecialForms do
|
||||
|
||||
If you try to run our new macro, you will notice it won't
|
||||
even compile, complaining that the variables `k` and `v`
|
||||
do not exist. This is because of the ambiguity: `unquote(k)`
|
||||
does not exist. This is because of the ambiguity: `unquote(k)`
|
||||
can either be an unquote fragment, as previously, or a regular
|
||||
unquote as in `unquote(kv)`.
|
||||
|
||||
One solution for this problem is to disable unquoting in the
|
||||
macro, however, doing that would make it impossible to inject
|
||||
One solution to this problem is to disable unquoting in the
|
||||
macro, however, doing that would make it impossible to inject the
|
||||
`kv` representation into the tree. That's when the `:bind_quoted`
|
||||
option comes to the rescue (again!). By using `:bind_quoted`, we
|
||||
can automatically disable unquoting while still injecting the
|
||||
@@ -924,27 +969,6 @@ defmodule Kernel.SpecialForms do
|
||||
"""
|
||||
defmacro quote(opts, block)
|
||||
|
||||
@doc """
|
||||
When used inside quoting, marks that the variable should
|
||||
not be hygienized. The argument can be either a variable
|
||||
unquoted or an atom representing the variable name.
|
||||
Check `quote/2` for more information.
|
||||
"""
|
||||
defmacro var!(var)
|
||||
|
||||
@doc """
|
||||
Defines a variable in the given context.
|
||||
Check `quote/2` for more information.
|
||||
"""
|
||||
defmacro var!(var, context)
|
||||
|
||||
@doc """
|
||||
When used inside quoting, marks that the alias should not
|
||||
be hygienezed. This means the alias will be expanded when
|
||||
the macro is expanded.
|
||||
"""
|
||||
defmacro alias!(alias)
|
||||
|
||||
@doc """
|
||||
Unquotes the given expression from inside a macro.
|
||||
|
||||
@@ -968,8 +992,7 @@ defmodule Kernel.SpecialForms do
|
||||
#=> { :sum, [], [1, 13, 3] }
|
||||
|
||||
"""
|
||||
name = :unquote
|
||||
defmacro unquote(name)(expr)
|
||||
defmacro unquote(:unquote)(expr)
|
||||
|
||||
@doc """
|
||||
Unquotes the given list expanding its arguments. Similar
|
||||
@@ -982,8 +1005,7 @@ defmodule Kernel.SpecialForms do
|
||||
#=> { :sum, [], [1, 2, 3, 4, 5] }
|
||||
|
||||
"""
|
||||
name = :unquote_splicing
|
||||
defmacro unquote(name)(expr)
|
||||
defmacro unquote(:unquote_splicing)(expr)
|
||||
|
||||
@doc """
|
||||
List comprehensions allow you to quickly build a list from another list:
|
||||
@@ -1067,8 +1089,7 @@ defmodule Kernel.SpecialForms do
|
||||
3
|
||||
|
||||
"""
|
||||
name = :fn
|
||||
defmacro unquote(name)(clauses)
|
||||
defmacro unquote(:fn)(clauses)
|
||||
|
||||
@doc """
|
||||
Internal special form for block expressions.
|
||||
@@ -1158,21 +1179,7 @@ defmodule Kernel.SpecialForms do
|
||||
&(foo(&1, &2); &3 + &4)
|
||||
|
||||
"""
|
||||
name = :&
|
||||
defmacro unquote(name)(expr)
|
||||
|
||||
@doc """
|
||||
Internal special form for modifying scope information.
|
||||
|
||||
Used when `quote` is invoked with `location: :keep` to execute
|
||||
a given block as if it belonged to another file.
|
||||
|
||||
quote location: :keep, do: 1
|
||||
#=> { :__scope__, [line: 1], [[file: "iex"],[do: 1]] }
|
||||
|
||||
Check `quote/2` for more information.
|
||||
"""
|
||||
defmacro __scope__(opts, args)
|
||||
defmacro unquote(:&)(expr)
|
||||
|
||||
@doc """
|
||||
Internal special form to hold aliases information.
|
||||
@@ -1218,4 +1225,292 @@ defmodule Kernel.SpecialForms do
|
||||
See `Kernel.defoverridable` for more information and documentation.
|
||||
"""
|
||||
defmacro super(args)
|
||||
|
||||
@doc """
|
||||
Matches the given expression against the given clauses.
|
||||
|
||||
## Examples
|
||||
|
||||
case thing do
|
||||
{ :selector, i, value } when is_integer(i) ->
|
||||
value
|
||||
value ->
|
||||
value
|
||||
end
|
||||
|
||||
In the example above, we match `thing` against each clause "head"
|
||||
and execute the clause "body" corresponding to the first clause
|
||||
that matches. If no clause matches, an error is raised.
|
||||
|
||||
## Variables handling
|
||||
|
||||
Notice that variables bound in a clause "head" do not leak to the
|
||||
outer context:
|
||||
|
||||
case data do
|
||||
{ :ok, value } -> value
|
||||
:error -> nil
|
||||
end
|
||||
|
||||
value #=> unbound variable value
|
||||
|
||||
However, variables explicitly bound in the clause "body" are
|
||||
accessible from the outer context:
|
||||
|
||||
value = 7
|
||||
|
||||
case lucky? do
|
||||
false -> value = 13
|
||||
true -> true
|
||||
end
|
||||
|
||||
value #=> 7 or 13
|
||||
|
||||
In the example above, value is going to be `7` or `13` depending on
|
||||
the value of `lucky?`. In case `value` has no previous value before
|
||||
case, clauses that do not explicitly bind a value have the variable
|
||||
bound to nil.
|
||||
"""
|
||||
defmacro case(condition, blocks)
|
||||
|
||||
@doc """
|
||||
Evaluate the given expressions and handle any error, exit
|
||||
or throw that may have happened.
|
||||
|
||||
## Examples
|
||||
|
||||
try do
|
||||
do_something_that_may_fail(some_arg)
|
||||
rescue
|
||||
ArgumentError ->
|
||||
IO.puts "Invalid argument given"
|
||||
catch
|
||||
value ->
|
||||
IO.puts "caught \#{value}"
|
||||
else
|
||||
value ->
|
||||
IO.puts "Success! The result was \#{value}"
|
||||
after
|
||||
IO.puts "This is printed regardless if it failed or succeed"
|
||||
end
|
||||
|
||||
The rescue clause is used to handle exceptions, while the catch
|
||||
clause can be used to catch thrown values. The else clause can
|
||||
be used to control flow based on the result of the expression.
|
||||
Catch, rescue and else clauses work based on pattern matching.
|
||||
|
||||
Note that calls inside `try` are not tail recursive since the VM
|
||||
needs to keep the stacktrace in case an exception happens.
|
||||
|
||||
## Rescue clauses
|
||||
|
||||
Besides relying on pattern matching, rescue clauses provides some
|
||||
conveniences around exceptions that allows one to rescue an
|
||||
exception by its name. All the following formats are valid rescue
|
||||
expressions:
|
||||
|
||||
try do
|
||||
UndefinedModule.undefined_function
|
||||
rescue
|
||||
UndefinedFunctionError -> nil
|
||||
end
|
||||
|
||||
try do
|
||||
UndefinedModule.undefined_function
|
||||
rescue
|
||||
[UndefinedFunctionError] -> nil
|
||||
end
|
||||
|
||||
# rescue and bind to x
|
||||
try do
|
||||
UndefinedModule.undefined_function
|
||||
rescue
|
||||
x in [UndefinedFunctionError] -> nil
|
||||
end
|
||||
|
||||
# rescue all and bind to x
|
||||
try do
|
||||
UndefinedModule.undefined_function
|
||||
rescue
|
||||
x -> nil
|
||||
end
|
||||
|
||||
## Catching exits and Erlang errors
|
||||
|
||||
The catch clause works exactly the same as in erlang. Therefore,
|
||||
one can also handle exits/errors coming from Erlang as below:
|
||||
|
||||
try do
|
||||
exit(1)
|
||||
catch
|
||||
:exit, 1 -> IO.puts "Exited with 1"
|
||||
end
|
||||
|
||||
try do
|
||||
error(:sample)
|
||||
catch
|
||||
:error, :sample ->
|
||||
IO.puts "sample error"
|
||||
end
|
||||
|
||||
Although the second form should be avoided in favor of raise/rescue
|
||||
control mechanisms.
|
||||
|
||||
## Else clauses
|
||||
|
||||
Else clauses allow the result of the expression to be pattern
|
||||
matched on:
|
||||
|
||||
x = 2
|
||||
try do
|
||||
1 / x
|
||||
rescue
|
||||
ArithmeticError ->
|
||||
:infinity
|
||||
else
|
||||
y when y < 1 and y > -1 ->
|
||||
:small
|
||||
_ ->
|
||||
:large
|
||||
end
|
||||
|
||||
If an else clause is not present the result of the expression will
|
||||
be return, if no exceptions are raised:
|
||||
|
||||
x = 1
|
||||
^x =
|
||||
try do
|
||||
1 / x
|
||||
rescue
|
||||
ArithmeticError ->
|
||||
:infinity
|
||||
end
|
||||
|
||||
However when an else clause is present but the result of the expression
|
||||
does not match any of the patterns an exception will be raised. This
|
||||
exception will not be caught by a catch or rescue in the same try:
|
||||
|
||||
x = 1
|
||||
try do
|
||||
try do
|
||||
1 / x
|
||||
rescue
|
||||
# The TryClauseError can not be rescued here:
|
||||
TryClauseError ->
|
||||
:error_a
|
||||
else
|
||||
0 ->
|
||||
:small
|
||||
end
|
||||
rescue
|
||||
# The TryClauseError is rescued here:
|
||||
TryClauseError ->
|
||||
:error_b
|
||||
end
|
||||
|
||||
Similarly an exception inside an else clause is not caught or rescued
|
||||
inside the same try:
|
||||
|
||||
try do
|
||||
try do
|
||||
nil
|
||||
catch
|
||||
# The exit(1) call below can not be caught here:
|
||||
:exit, _ ->
|
||||
:exit_a
|
||||
else
|
||||
_ ->
|
||||
exit(1)
|
||||
end
|
||||
catch
|
||||
# The exit is caught here:
|
||||
:exit, _ ->
|
||||
:exit_b
|
||||
end
|
||||
|
||||
This means the VM nolonger needs to keep the stacktrace once inside
|
||||
an else clause and so tail recursion is possible when using a `try`
|
||||
with a tail call as the final call inside an else clause. The same
|
||||
is true for rescue and catch clauses.
|
||||
|
||||
## Variable handling
|
||||
|
||||
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:
|
||||
|
||||
try do
|
||||
x = 1
|
||||
do_something_that_may_fail(same_arg)
|
||||
:ok
|
||||
catch
|
||||
_, _ -> :failed
|
||||
end
|
||||
|
||||
x #=> unbound variable `x`
|
||||
|
||||
In the example above, `x` cannot be accessed since it was defined
|
||||
inside the `try` clause. A common practice to address this issue
|
||||
is to return the variables defined inside `try`:
|
||||
|
||||
x =
|
||||
try do
|
||||
x = 1
|
||||
do_something_that_may_fail(same_arg)
|
||||
x
|
||||
catch
|
||||
_, _ -> :failed
|
||||
end
|
||||
|
||||
"""
|
||||
defmacro try(args)
|
||||
|
||||
@doc """
|
||||
Checks if there is a message matching the given clauses
|
||||
in the current process mailbox.
|
||||
|
||||
In case there is no such message, the current process hangs
|
||||
until a message arrives or waits until a given timeout value.
|
||||
|
||||
## Examples
|
||||
|
||||
receive do
|
||||
{ :selector, i, value } when is_integer(i) ->
|
||||
value
|
||||
value when is_atom(value) ->
|
||||
value
|
||||
_ ->
|
||||
IO.puts :stderr, "Unexpected message received"
|
||||
end
|
||||
|
||||
An optional after clause can be given in case the message was not
|
||||
received after the specified period of time:
|
||||
|
||||
receive do
|
||||
{ :selector, i, value } when is_integer(i) ->
|
||||
value
|
||||
value when is_atom(value) ->
|
||||
value
|
||||
_ ->
|
||||
IO.puts :stderr, "Unexpected message received"
|
||||
after
|
||||
5000 ->
|
||||
IO.puts :stderr, "No message in 5 seconds"
|
||||
end
|
||||
|
||||
The `after` clause can be specified even if there are no match clauses.
|
||||
There are two special cases for the timout value given to after
|
||||
|
||||
* `:infinity` - The process should wait indefinitely for a matching
|
||||
message, this is the same as not using a timeout.
|
||||
|
||||
* 0 - if there is no matching message in the mailbox, the timeout
|
||||
will occur immediately.
|
||||
|
||||
## Variables handling
|
||||
|
||||
The `receive` special form handles variables exactly as the `case`
|
||||
special macro. For more information, check the docs for `case/2`.
|
||||
"""
|
||||
defmacro receive(args)
|
||||
end
|
||||
|
||||
+307
-132
@@ -2,56 +2,95 @@ defmodule Kernel.Typespec do
|
||||
@moduledoc """
|
||||
Provides macros and functions for working with typespecs.
|
||||
|
||||
The attributes `@type`, `@opaque`, `@typep`, `@spec` and
|
||||
`@callback` available in modules are handled by the equivalent
|
||||
macros defined by this module.
|
||||
Elixir comes with a notation for declaring types and specifications. Elixir is
|
||||
dynamically typed, as such typespecs are never used by the compiler to
|
||||
optimize or modify code. Still, using typespecs is useful as documentation and
|
||||
tools such as [Dialyzer](http://www.erlang.org/doc/man/dialyzer.html) can
|
||||
analyze the code with typespecs to find bugs.
|
||||
|
||||
## Defining a type
|
||||
The attributes `@type`, `@opaque`, `@typep`, `@spec` and `@callback` available
|
||||
in modules are handled by the equivalent macros defined by this module. See
|
||||
sub-sections "Defining a type" and "Defining a specification" below.
|
||||
|
||||
@type type_name :: type
|
||||
@typep type_name :: type
|
||||
@opaque type_name :: type
|
||||
## Types and their syntax
|
||||
|
||||
For more details, see documentation for `deftype`, `deftypep` and `defopaque`
|
||||
below.
|
||||
The type syntax provided by Elixir is fairly similar to the one in
|
||||
[Erlang](http://www.erlang.org/doc/reference_manual/typespec.html).
|
||||
|
||||
## Defining a specification
|
||||
Most of the built-in types provided in Erlang (for example, `pid()`) are
|
||||
expressed the same way: `pid()` or simply `pid`. Parametrized types are also
|
||||
supported (`list(integer)`) and so are remote types (`Enum.t`).
|
||||
|
||||
@spec function_name(type, type) :: type
|
||||
@callback function_name(type, type) :: type
|
||||
Integers and atom literals are allowed as types (ex. `1`, `:atom` or
|
||||
`false`). All other types are built of unions of predefined types. Certain
|
||||
shorthands are allowed, such as `[...]`, `<<>>` and `{...}`.
|
||||
|
||||
For more details, see documentation for `defspec` and `defcallback` below.
|
||||
### Predefined types
|
||||
|
||||
## Types
|
||||
Type :: any # the top type, the set of all terms
|
||||
| none # the bottom type, contains no terms
|
||||
| pid
|
||||
| port
|
||||
| reference
|
||||
| Atom
|
||||
| Bitstring
|
||||
| float
|
||||
| Fun
|
||||
| Integer
|
||||
| List
|
||||
| Tuple
|
||||
| Union
|
||||
| UserDefined # Described in section "Defining a type"
|
||||
|
||||
The type syntax provided by Elixir is fairly similar to the one
|
||||
in Erlang.
|
||||
Atom :: atom
|
||||
| ElixirAtom # `:foo`, `:bar`, ...
|
||||
|
||||
Most of the built-in types provided in Erlang (for example, `pid()`)
|
||||
are expressed the same way: `pid()` or simply `pid`. Parametrized types
|
||||
are also supported (`list(integer())`) and so are remote types (`Enum.t`).
|
||||
Bitstring :: <<>>
|
||||
| << _ :: M >> # M is a positive integer
|
||||
| << _ :: _ * N >> # N is a positive integer
|
||||
| << _ :: M, _ :: _ * N >>
|
||||
|
||||
Certain data type shortcuts (`[...]`, `<<>>` and `{...}`) are supported as
|
||||
well.
|
||||
Fun :: (... -> any) # any function
|
||||
| (... -> Type) # any arity, returning Type
|
||||
| (() -> Type))
|
||||
| (TList -> Type)
|
||||
|
||||
Main differences lie in how bit strings and functions are defined:
|
||||
Integer :: integer
|
||||
| ElixirInteger # ..., -1, 0, 1, ... 42 ...
|
||||
| ElixirInteger..ElixirInteger # an integer range
|
||||
|
||||
### Bit Strings
|
||||
List :: list(Type) # proper list ([]-terminated)
|
||||
| improper_list(Type1, Type2) # Type1=contents, Type2=termination
|
||||
| maybe_improper_list(Type1, Type2) # Type1 and Type2 as above
|
||||
| nonempty_list(Type) # proper non-empty list
|
||||
| [] # empty list
|
||||
| [Type] # shorthand for list(Type)
|
||||
| [Type, ...] # shorthand for nonempty_list(Type)
|
||||
|
||||
Tuple :: tuple # a tuple of any size
|
||||
| {} # empty tuple
|
||||
| { TList }
|
||||
|
||||
TList :: Type
|
||||
| Type, TList
|
||||
|
||||
Union :: Type1 | Type2
|
||||
|
||||
### Bit strings
|
||||
|
||||
Bit string with a base size of 3:
|
||||
|
||||
<<_ :: 3>>
|
||||
<< _ :: 3 >>
|
||||
|
||||
Bit string with a unit size of 8:
|
||||
|
||||
<<_ :: _ * 8>>
|
||||
<< _ :: _ * 8 >>
|
||||
|
||||
### Anonymous functions
|
||||
|
||||
Any anonymous function:
|
||||
|
||||
((...) -> any)
|
||||
or
|
||||
(... -> any)
|
||||
|
||||
Anonymous function with arity of zero:
|
||||
@@ -61,27 +100,98 @@ defmodule Kernel.Typespec do
|
||||
Anonymous function with some arity:
|
||||
|
||||
((type, type) -> type)
|
||||
or
|
||||
(type, type -> type)
|
||||
|
||||
## Built-in types
|
||||
|
||||
Built-in type | Defined as
|
||||
:-------------------- | :---------
|
||||
`term` | `any`
|
||||
`binary` | `<< _ :: _ * 8 >>`
|
||||
`bitstring` | `<< _ :: _ * 1 >>`
|
||||
`boolean` | `false` | `true`
|
||||
`byte` | `0..255`
|
||||
`char` | `0..0xffff`
|
||||
`number` | `integer` | `float`
|
||||
`list` | `[any]`
|
||||
`maybe_improper_list` | `maybe_improper_list(any, any)`
|
||||
`nonempty_list` | `nonempty_list(any)`
|
||||
`iodata` | `iolist` | `binary`
|
||||
`iolist` | `maybe_improper_list(byte` | `binary` | `iolist, binary` | `[])`
|
||||
`module` | `atom`
|
||||
`mfa` | `{ atom, atom, arity }`
|
||||
`arity` | `0..255`
|
||||
`node` | `atom`
|
||||
`timeout` | `:infinity` | `non_neg_integer`
|
||||
`no_return` | `none`
|
||||
`fun` | `(... -> any)`
|
||||
|
||||
Some built-in types cannot be expressed with valid syntax according to the
|
||||
language defined above.
|
||||
|
||||
Built-in type | Can be interpreted as
|
||||
:---------------- | :--------------------
|
||||
`non_neg_integer` | `0..`
|
||||
`pos_integer` | `1..`
|
||||
`neg_integer` | `..-1`
|
||||
|
||||
Types defined in other modules are referred to as "remote types", they are
|
||||
referenced as `Module.type_name` (ex. `Enum.t` or `String.t`).
|
||||
|
||||
## Defining a type
|
||||
|
||||
@type type_name :: type
|
||||
@typep type_name :: type
|
||||
@opaque type_name :: type
|
||||
|
||||
A type defined with `@typep` is private. An opaque type, defined with
|
||||
`@opaque` is a type where the internal structure of the type will not be
|
||||
visible, but the type is still public.
|
||||
|
||||
Types can be parametrised by defining variables as parameters, these variables
|
||||
can then be used to define the type.
|
||||
|
||||
@type dict(key, value) :: [{ key, value }]
|
||||
|
||||
Types can also be defined for records, see `defrecord/3`.
|
||||
|
||||
## Defining a specification
|
||||
|
||||
@spec function_name(type1, type2) :: return_type
|
||||
@callback function_name(type1, type2) :: return_type
|
||||
|
||||
Callbacks are used to define the callbacks functions of behaviours (see
|
||||
`Behaviour`).
|
||||
|
||||
Guards can be used to restrict type variables given as arguments to the
|
||||
function.
|
||||
|
||||
@spec function(arg) :: [arg] when arg: atom
|
||||
|
||||
Type variables with no restriction can also be defined.
|
||||
|
||||
@spec function(arg) :: [arg] when arg: var
|
||||
|
||||
Specifications can be overloaded just like ordinary functions.
|
||||
|
||||
@spec function(integer) :: atom
|
||||
@spec function(atom) :: integer
|
||||
|
||||
## Notes
|
||||
|
||||
Elixir discourages the use of type `string()` as it might be confused
|
||||
with binaries which are referred to as "strings" in Elixir (as opposed to
|
||||
character lists). In order to use the type that is called `string()` in Erlang,
|
||||
one has to use the `char_list()` type which is a synonym for `string()`. If you
|
||||
use `string()`, you'll get a warning from the compiler.
|
||||
Elixir discourages the use of type `string` as it might be confused with
|
||||
binaries which are referred to as "strings" in Elixir (as opposed to character
|
||||
lists). In order to use the type that is called `string` in Erlang, one has to
|
||||
use the `char_list` type which is a synonym for `string`. If you use `string`,
|
||||
you'll get a warning from the compiler.
|
||||
|
||||
If you want to refer to the "string" type (the one operated by functions in the
|
||||
String module), use `String.t()` type instead.
|
||||
|
||||
See http://www.erlang.org/doc/reference_manual/typespec.html
|
||||
for more information.
|
||||
If you want to refer to the "string" type (the one operated on by functions in
|
||||
the `String` module), use `String.t` type instead.
|
||||
"""
|
||||
|
||||
@doc """
|
||||
Defines a type.
|
||||
This macro is the one responsible for handling the attribute `@type`.
|
||||
This macro is responsible for handling the attribute `@type`.
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -90,13 +200,13 @@ defmodule Kernel.Typespec do
|
||||
"""
|
||||
defmacro deftype(type) do
|
||||
quote do
|
||||
Kernel.Typespec.deftype(:type, unquote(Macro.escape type), __ENV__)
|
||||
Kernel.Typespec.deftype(:type, unquote(Macro.escape(type, unquote: true)), __ENV__)
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Defines an opaque type.
|
||||
This macro is the one responsible for handling the attribute `@opaque`.
|
||||
This macro is responsible for handling the attribute `@opaque`.
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -105,13 +215,13 @@ defmodule Kernel.Typespec do
|
||||
"""
|
||||
defmacro defopaque(type) do
|
||||
quote do
|
||||
Kernel.Typespec.deftype(:opaque, unquote(Macro.escape type), __ENV__)
|
||||
Kernel.Typespec.deftype(:opaque, unquote(Macro.escape(type, unquote: true)), __ENV__)
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Defines a private type.
|
||||
This macro is the one responsible for handling the attribute `@typep`.
|
||||
This macro is responsible for handling the attribute `@typep`.
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -120,13 +230,13 @@ defmodule Kernel.Typespec do
|
||||
"""
|
||||
defmacro deftypep(type) do
|
||||
quote do
|
||||
Kernel.Typespec.deftype(:typep, unquote(Macro.escape type), __ENV__)
|
||||
Kernel.Typespec.deftype(:typep, unquote(Macro.escape(type, unquote: true)), __ENV__)
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Defines a spec.
|
||||
This macro is the one responsible for handling the attribute `@spec`.
|
||||
This macro is responsible for handling the attribute `@spec`.
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -135,13 +245,13 @@ defmodule Kernel.Typespec do
|
||||
"""
|
||||
defmacro defspec(spec) do
|
||||
quote do
|
||||
Kernel.Typespec.defspec(:spec, unquote(Macro.escape spec), __ENV__)
|
||||
Kernel.Typespec.defspec(:spec, unquote(Macro.escape(spec, unquote: true)), __ENV__)
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Defines a callback.
|
||||
This macro is the one responsible for handling the attribute `@callback`.
|
||||
This macro is responsible for handling the attribute `@callback`.
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -150,7 +260,7 @@ defmodule Kernel.Typespec do
|
||||
"""
|
||||
defmacro defcallback(spec) do
|
||||
quote do
|
||||
Kernel.Typespec.defspec(:callback, unquote(Macro.escape spec), __ENV__)
|
||||
Kernel.Typespec.defspec(:callback, unquote(Macro.escape(spec, unquote: true)), __ENV__)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -217,8 +327,8 @@ defmodule Kernel.Typespec do
|
||||
"""
|
||||
def defines_type?(module, name, arity) do
|
||||
finder = &match?({ ^name, _, vars } when length(vars) == arity, &1)
|
||||
:lists.any(finder, Module.get_attribute(module, :type)) or
|
||||
:lists.any(finder, Module.get_attribute(module, :opaque))
|
||||
Enum.any?(Module.get_attribute(module, :type), finder) or
|
||||
Enum.any?(Module.get_attribute(module, :opaque), finder)
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -227,7 +337,7 @@ defmodule Kernel.Typespec do
|
||||
"""
|
||||
def defines_spec?(module, name, arity) do
|
||||
tuple = { name, arity }
|
||||
:lists.any(&match?(^tuple, &1), Module.get_attribute(module, :spec))
|
||||
Enum.any?(Module.get_attribute(module, :spec), &match?(^tuple, &1))
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -236,15 +346,28 @@ defmodule Kernel.Typespec do
|
||||
"""
|
||||
def defines_callback?(module, name, arity) do
|
||||
tuple = { name, arity }
|
||||
:lists.any(&match?(^tuple, &1), Module.get_attribute(module, :callback))
|
||||
Enum.any?(Module.get_attribute(module, :callback), &match?(^tuple, &1))
|
||||
end
|
||||
|
||||
@doc """
|
||||
Converts a spec clause back to Elixir AST.
|
||||
"""
|
||||
def spec_to_ast(name, { :type, line, :fun, [{:type, _, :product, args}, result] }) do
|
||||
args = lc arg inlist args, do: typespec_to_ast(arg)
|
||||
{ :::, [line: line], [{ name, [line: line], args }, typespec_to_ast(result)] }
|
||||
meta = [line: line]
|
||||
body = { name, meta, Enum.map(args, &typespec_to_ast/1) }
|
||||
|
||||
vars = args ++ [result]
|
||||
|> Enum.flat_map(&collect_vars/1)
|
||||
|> Enum.uniq
|
||||
|> Enum.map(&{ &1, { :var, meta, nil } })
|
||||
|
||||
result = if vars == [] do
|
||||
typespec_to_ast(result)
|
||||
else
|
||||
{ :when, meta, [typespec_to_ast(result), vars] }
|
||||
end
|
||||
|
||||
{ :::, meta, [body, result] }
|
||||
end
|
||||
|
||||
def spec_to_ast(name, { :type, line, :fun, [] }) do
|
||||
@@ -252,15 +375,25 @@ defmodule Kernel.Typespec do
|
||||
end
|
||||
|
||||
def spec_to_ast(name, { :type, line, :bounded_fun, [{ :type, _, :fun, [{ :type, _, :product, args }, result] }, constraints] }) do
|
||||
[h|t] =
|
||||
lc {:type, line, :constraint, [{:atom, _, :is_subtype}, [var, type]]} inlist constraints do
|
||||
{ :is_subtype, [line: line], [typespec_to_ast(var), typespec_to_ast(type)] }
|
||||
guards =
|
||||
lc {:type, _, :constraint, [{:atom, _, :is_subtype}, [{ :var, _, var }, type]]} inlist constraints do
|
||||
{ var, typespec_to_ast(type) }
|
||||
end
|
||||
|
||||
args = lc arg inlist args, do: typespec_to_ast(arg)
|
||||
guards = Enum.reduce t, h, fn(x, acc) -> { :and, line, [acc, x] } end
|
||||
meta = [line: line]
|
||||
|
||||
{ :::, [line: line], [{ :when, [line: line], [{ name, [line: line], args }, guards] }, typespec_to_ast(result)] }
|
||||
vars = args ++ [result]
|
||||
|> Enum.flat_map(&collect_vars/1)
|
||||
|> Enum.uniq
|
||||
|> Kernel.--(Keyword.keys(guards))
|
||||
|> Enum.map(&{ &1, { :var, meta, nil } })
|
||||
|
||||
args = lc arg inlist args, do: typespec_to_ast(arg)
|
||||
|
||||
{ :::, meta, [
|
||||
{ name, [line: line], args },
|
||||
{ :when, meta, [typespec_to_ast(result), guards ++ vars] }
|
||||
] }
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -281,10 +414,10 @@ defmodule Kernel.Typespec do
|
||||
@doc """
|
||||
Returns all type docs available from the module's beam code.
|
||||
|
||||
It is returned as a list of tuples where the first element is the pair of type
|
||||
The result is returned as a list of tuples where the first element is the pair of type
|
||||
name and arity and the second element is the documentation.
|
||||
|
||||
The module has to have a corresponding beam file on the disk which can be
|
||||
The module must have a corresponding beam file which can be
|
||||
located by the runtime system.
|
||||
"""
|
||||
@spec beam_typedocs(module | binary) :: [tuple] | nil
|
||||
@@ -301,10 +434,10 @@ defmodule Kernel.Typespec do
|
||||
@doc """
|
||||
Returns all types available from the module's beam code.
|
||||
|
||||
It is returned as a list of tuples where the first
|
||||
The result is returned as a list of tuples where the first
|
||||
element is the type (`:typep`, `:type` and `:opaque`).
|
||||
|
||||
The module has to have a corresponding beam file on the disk which can be
|
||||
The module must have a corresponding beam file which can be
|
||||
located by the runtime system.
|
||||
"""
|
||||
@spec beam_types(module | binary) :: [tuple] | nil
|
||||
@@ -317,7 +450,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 }
|
||||
:lists.member({ name, length(args) }, exported_types) -> { :type, type }
|
||||
{ name, length(args) } in exported_types -> { :type, type }
|
||||
true -> { :typep, type }
|
||||
end
|
||||
end
|
||||
@@ -329,10 +462,10 @@ defmodule Kernel.Typespec do
|
||||
@doc """
|
||||
Returns all specs available from the module's beam code.
|
||||
|
||||
It is returned as a list of tuples where the first
|
||||
The result is returned as a list of tuples where the first
|
||||
element is spec name and arity and the second is the spec.
|
||||
|
||||
The module has to have a corresponding beam file on the disk which can be
|
||||
The module must have a corresponding beam file which can be
|
||||
located by the runtime system.
|
||||
"""
|
||||
@spec beam_specs(module | binary) :: [tuple] | nil
|
||||
@@ -343,10 +476,10 @@ defmodule Kernel.Typespec do
|
||||
@doc """
|
||||
Returns all callbacks available from the module's beam code.
|
||||
|
||||
It is returned as a list of tuples where the first
|
||||
The result is returned as a list of tuples where the first
|
||||
element is spec name and arity and the second is the spec.
|
||||
|
||||
The module has to have a corresponding beam file on the disk
|
||||
The module must have a corresponding beam file
|
||||
which can be located by the runtime system.
|
||||
"""
|
||||
@spec beam_callbacks(module | binary) :: [tuple] | nil
|
||||
@@ -397,7 +530,7 @@ defmodule Kernel.Typespec do
|
||||
|
||||
def deftype(_kind, other, caller) do
|
||||
type_spec = Macro.to_string(other)
|
||||
compile_error caller, "invalid type specification #{type_spec}"
|
||||
compile_error caller, "invalid type specification: #{type_spec}"
|
||||
end
|
||||
|
||||
defp do_deftype(kind, { name, _, args }, definition, caller) do
|
||||
@@ -418,19 +551,23 @@ defmodule Kernel.Typespec do
|
||||
end
|
||||
|
||||
@doc false
|
||||
def defspec(type, {:::, _, [{ :when, _, [{ name, meta, args }, constraints_guard] }, return] }, caller) do
|
||||
def defspec(type, { :::, meta, [{ name, _, args }, return_and_guard] }, caller) do
|
||||
if is_atom(args), do: args = []
|
||||
constraints = guard_to_constraints(constraints_guard, caller)
|
||||
spec = { :type, line(meta), :fun, fn_args(meta, args, return, Keyword.keys(constraints), caller) }
|
||||
spec = { :type, line(meta), :bounded_fun, [spec, Keyword.values(constraints)] }
|
||||
code = { { name, Kernel.length(args) }, spec }
|
||||
Module.compile_typespec(caller.module, type, code)
|
||||
code
|
||||
end
|
||||
{ return, guard } = split_return_and_guard(return_and_guard)
|
||||
|
||||
unless Keyword.keyword?(guard) do
|
||||
guard = Macro.to_string(guard)
|
||||
compile_error caller, "expected keywords as guard in function type specification, got: #{guard}"
|
||||
end
|
||||
|
||||
vars = Keyword.keys(guard)
|
||||
constraints = guard_to_constraints(guard, vars, meta, caller)
|
||||
|
||||
spec = { :type, line(meta), :fun, fn_args(meta, args, return, vars, caller) }
|
||||
if constraints != [] do
|
||||
spec = { :type, line(meta), :bounded_fun, [spec, constraints] }
|
||||
end
|
||||
|
||||
def defspec(type, {:::, _, [{ name, meta, args }, return]}, caller) do
|
||||
if is_atom(args), do: args = []
|
||||
spec = { :type, line(meta), :fun, fn_args(meta, args, return, [], caller) }
|
||||
code = { { name, Kernel.length(args) }, spec }
|
||||
Module.compile_typespec(caller.module, type, code)
|
||||
code
|
||||
@@ -438,23 +575,67 @@ defmodule Kernel.Typespec do
|
||||
|
||||
def defspec(_type, other, caller) do
|
||||
spec = Macro.to_string(other)
|
||||
compile_error caller, "invalid function type specification #{spec}"
|
||||
compile_error caller, "invalid function type specification: #{spec}"
|
||||
end
|
||||
|
||||
defp guard_to_constraints({ :is_subtype, meta, [{ name, _, _ }, type] }, caller) do
|
||||
defp split_return_and_guard({ :when, _, [return, guard] }) do
|
||||
{ return, guard }
|
||||
end
|
||||
|
||||
defp split_return_and_guard({ :|, meta, [left, right] }) do
|
||||
{ return, guard } = split_return_and_guard(right)
|
||||
{ { :|, meta, [left, return] }, guard }
|
||||
end
|
||||
|
||||
defp split_return_and_guard(other) do
|
||||
{ other, [] }
|
||||
end
|
||||
|
||||
defp guard_to_constraints(guard, vars, meta, caller) do
|
||||
line = line(meta)
|
||||
contraints = [{ :atom, line, :is_subtype }, [{:var, line, name}, typespec(type, [], caller)]]
|
||||
[{ name, { :type, line, :constraint, contraints } }]
|
||||
end
|
||||
|
||||
defp guard_to_constraints({ :and, _, [left, right] }, caller) do
|
||||
guard_to_constraints(left, caller) ++ guard_to_constraints(right, caller)
|
||||
Enum.reduce(guard, [], fn
|
||||
{ _name, { :var, _, context } }, acc when is_atom(context) ->
|
||||
acc
|
||||
{ name, type }, acc ->
|
||||
constraint = [{ :atom, line, :is_subtype }, [{:var, line, name}, typespec(type, vars, caller)]]
|
||||
type = { :type, line, :constraint, constraint }
|
||||
[type|acc]
|
||||
end) |> Enum.reverse
|
||||
end
|
||||
|
||||
## To AST conversion
|
||||
|
||||
defp collect_vars({ :ann_type, _line, args }) when is_list(args) do
|
||||
[]
|
||||
end
|
||||
|
||||
defp collect_vars({ :type, _line, _kind, args }) when is_list(args) do
|
||||
Enum.flat_map(args, &collect_vars/1)
|
||||
end
|
||||
|
||||
defp collect_vars({ :remote_type, _line, args }) when is_list(args) do
|
||||
Enum.flat_map(args, &collect_vars/1)
|
||||
end
|
||||
|
||||
defp collect_vars({ :typed_record_field, _line, type }) do
|
||||
collect_vars(type)
|
||||
end
|
||||
|
||||
defp collect_vars({:paren_type, _line, [type]}) do
|
||||
collect_vars(type)
|
||||
end
|
||||
|
||||
defp collect_vars({:var, _line, var}) do
|
||||
[erl_to_ex_var(var)]
|
||||
end
|
||||
|
||||
defp collect_vars(_) do
|
||||
[]
|
||||
end
|
||||
|
||||
defp typespec_to_ast({ :type, line, :tuple, :any }) do
|
||||
typespec_to_ast({:type, line, :tuple, []})
|
||||
{ :tuple, [line: line], [] }
|
||||
end
|
||||
|
||||
defp typespec_to_ast({ :type, line, :tuple, args }) do
|
||||
@@ -462,8 +643,8 @@ defmodule Kernel.Typespec do
|
||||
{ :{}, [line: line], args }
|
||||
end
|
||||
|
||||
defp typespec_to_ast({ :type, _line, :list, [arg] }) do
|
||||
case unpack_typespec_kw(arg, []) do
|
||||
defp typespec_to_ast({ :type, _line, :list, [{ :type, _, :union, unions } = arg] }) do
|
||||
case unpack_typespec_kw(unions, []) do
|
||||
{ :ok, ast } -> ast
|
||||
:error -> [typespec_to_ast(arg)]
|
||||
end
|
||||
@@ -487,17 +668,16 @@ defmodule Kernel.Typespec do
|
||||
|
||||
defp typespec_to_ast({ :type, line, :union, args }) do
|
||||
args = lc arg inlist args, do: typespec_to_ast(arg)
|
||||
Enum.reduce tl(args), hd(args),
|
||||
fn(arg, expr) -> { :|, [line: line], [expr, arg] } end
|
||||
Enum.reduce Enum.reverse(args), fn(arg, expr) -> { :|, [line: line], [arg, expr] } end
|
||||
end
|
||||
|
||||
defp typespec_to_ast({ :type, line, :fun, [{:type, _, :product, args}, result] }) do
|
||||
args = lc arg inlist args, do: typespec_to_ast(arg)
|
||||
{ :->, [line: line], [{args, [line: line], typespec_to_ast(result)}] }
|
||||
[{ :->, [line: line], [args, typespec_to_ast(result)] }]
|
||||
end
|
||||
|
||||
defp typespec_to_ast({ :type, line, :fun, [args, result] }) do
|
||||
{ :->, [line: line], [{[typespec_to_ast(args)], [line: line], typespec_to_ast(result)}] }
|
||||
[{ :->, [line: line], [[typespec_to_ast(args)], typespec_to_ast(result)] }]
|
||||
end
|
||||
|
||||
defp typespec_to_ast({ :type, line, :fun, [] }) do
|
||||
@@ -514,14 +694,11 @@ defmodule Kernel.Typespec do
|
||||
end
|
||||
|
||||
defp typespec_to_ast({ :var, line, var }) do
|
||||
var =
|
||||
case atom_to_binary(var) do
|
||||
<<"_", c :: [binary, size(1)], rest :: binary>> ->
|
||||
binary_to_atom("_#{String.downcase(c)}#{rest}")
|
||||
<<c :: [binary, size(1)], rest :: binary>> ->
|
||||
binary_to_atom("#{String.downcase(c)}#{rest}")
|
||||
end
|
||||
{ var, line, nil }
|
||||
{ erl_to_ex_var(var), line, nil }
|
||||
end
|
||||
|
||||
defp typespec_to_ast({ :op, line, op, arg }) do
|
||||
{ op, [line: line], [typespec_to_ast(arg)] }
|
||||
end
|
||||
|
||||
# Special shortcut(s)
|
||||
@@ -563,6 +740,15 @@ defmodule Kernel.Typespec do
|
||||
|
||||
defp typespec_to_ast(other), do: other
|
||||
|
||||
defp erl_to_ex_var(var) do
|
||||
case atom_to_binary(var) do
|
||||
<<"_", c :: [binary, size(1)], rest :: binary>> ->
|
||||
binary_to_atom("_#{String.downcase(c)}#{rest}")
|
||||
<<c :: [binary, size(1)], rest :: binary>> ->
|
||||
binary_to_atom("#{String.downcase(c)}#{rest}")
|
||||
end
|
||||
end
|
||||
|
||||
## From AST conversion
|
||||
|
||||
defp line(meta) do
|
||||
@@ -574,7 +760,7 @@ defmodule Kernel.Typespec do
|
||||
|
||||
# Handle unions
|
||||
defp typespec({ :|, meta, [_, _] } = exprs, vars, caller) do
|
||||
exprs = Enum.reverse(collect_union(exprs))
|
||||
exprs = collect_union(exprs)
|
||||
union = lc e inlist exprs, do: typespec(e, vars, caller)
|
||||
{ :type, line(meta), :union, union }
|
||||
end
|
||||
@@ -608,11 +794,7 @@ defmodule Kernel.Typespec do
|
||||
end
|
||||
|
||||
# Handle funs
|
||||
defp typespec({:->, meta, [{[{:fun, _, arguments}], cmeta, return}]}, vars, caller) when is_list(arguments) do
|
||||
typespec({:->, meta, [{arguments, cmeta, return}]}, vars, caller)
|
||||
end
|
||||
|
||||
defp typespec({:->, meta, [{arguments, _, return}]}, vars, caller) when is_list(arguments) do
|
||||
defp typespec([{:->, meta, [arguments, return]}], vars, caller) when is_list(arguments) do
|
||||
args = fn_args(meta, arguments, return, vars, caller)
|
||||
{ :type, line(meta), :fun, args }
|
||||
end
|
||||
@@ -646,11 +828,7 @@ defmodule Kernel.Typespec do
|
||||
end
|
||||
|
||||
# Handle tuples
|
||||
defp typespec({:tuple, meta, atom}, vars, caller) when is_atom(atom) do
|
||||
typespec({:{}, meta, []}, vars, caller)
|
||||
end
|
||||
|
||||
defp typespec({:{}, meta, []}, _, _) do
|
||||
defp typespec({:tuple, meta, args}, _vars, _caller) when args == [] or is_atom(args) do
|
||||
{ :type, line(meta), :tuple, :any }
|
||||
end
|
||||
|
||||
@@ -659,6 +837,10 @@ defmodule Kernel.Typespec do
|
||||
{ :type, line(meta), :tuple, args }
|
||||
end
|
||||
|
||||
defp typespec({ left, right }, vars, caller) do
|
||||
typespec({ :{}, [], [left, right] }, vars, caller)
|
||||
end
|
||||
|
||||
# Handle blocks
|
||||
defp typespec({:__block__, _meta, [arg]}, vars, caller) do
|
||||
typespec(arg, vars, caller)
|
||||
@@ -666,7 +848,7 @@ defmodule Kernel.Typespec do
|
||||
|
||||
# Handle variables or local calls
|
||||
defp typespec({name, meta, atom}, vars, caller) when is_atom(atom) do
|
||||
if :lists.member(name, vars) do
|
||||
if name in vars do
|
||||
{ :var, line(meta), name }
|
||||
else
|
||||
typespec({name, meta, []}, vars, caller)
|
||||
@@ -675,7 +857,7 @@ defmodule Kernel.Typespec do
|
||||
|
||||
# Handle local calls
|
||||
defp typespec({:string, meta, arguments}, vars, caller) do
|
||||
IO.write "warning: string() type use is discouraged. For character lists, use " <>
|
||||
:elixir_errors.warn "warning: string() type use is discouraged. For character lists, use " <>
|
||||
"char_list() type, for strings, String.t()\n#{Exception.format_stacktrace(caller.stacktrace)}"
|
||||
arguments = lc arg inlist arguments, do: typespec(arg, vars, caller)
|
||||
{ :type, line(meta), :string, arguments }
|
||||
@@ -715,18 +897,14 @@ defmodule Kernel.Typespec do
|
||||
typespec({ :nonempty_list, [], [spec] }, vars, caller)
|
||||
end
|
||||
|
||||
defp typespec([h|t] = l, vars, caller) do
|
||||
union = Enum.reduce(t, validate_kw(h, l, caller), fn(x, acc) ->
|
||||
{ :|, [], [acc, validate_kw(x, l, caller)] }
|
||||
defp typespec(list, vars, caller) do
|
||||
[h|t] = Enum.reverse(list)
|
||||
union = Enum.reduce(t, validate_kw(h, list, caller), fn(x, acc) ->
|
||||
{ :|, [], [validate_kw(x, list, caller), acc] }
|
||||
end)
|
||||
typespec({ :list, [], [union] }, vars, caller)
|
||||
end
|
||||
|
||||
defp typespec(t, vars, caller) when is_tuple(t) do
|
||||
args = lc e inlist tuple_to_list(t), do: typespec(e, vars, caller)
|
||||
{ :type, 0, :tuple, args }
|
||||
end
|
||||
|
||||
## Helpers
|
||||
|
||||
defp compile_error(caller, desc) do
|
||||
@@ -738,12 +916,12 @@ defmodule Kernel.Typespec do
|
||||
{ :remote_type, line(meta), [ remote, name, arguments ] }
|
||||
end
|
||||
|
||||
defp collect_union({ :|, _, [a, b] }), do: [b|collect_union(a)]
|
||||
defp collect_union({ :|, _, [a, b] }), do: [a|collect_union(b)]
|
||||
defp collect_union(v), do: [v]
|
||||
|
||||
defp validate_kw({ key, _ } = t, _, _caller) when is_atom(key), do: t
|
||||
defp validate_kw(_, original, caller) do
|
||||
compile_error(caller, "unexpected list #{Macro.to_string original} in typespec")
|
||||
compile_error(caller, "unexpected list in typespec: #{Macro.to_string original}")
|
||||
end
|
||||
|
||||
defp fn_args(meta, args, return, vars, caller) do
|
||||
@@ -766,15 +944,12 @@ defmodule Kernel.Typespec do
|
||||
{:var, line(meta), name}
|
||||
end
|
||||
|
||||
defp unpack_typespec_kw({ :type, _, :union, [
|
||||
next,
|
||||
{ :type, _, :tuple, [{ :atom, _, atom }, type] }
|
||||
] }, acc) do
|
||||
unpack_typespec_kw(next, [{atom, typespec_to_ast(type)}|acc])
|
||||
defp unpack_typespec_kw([{ :type, _, :tuple, [{ :atom, _, atom }, type] }|t], acc) do
|
||||
unpack_typespec_kw(t, [{atom, typespec_to_ast(type)}|acc])
|
||||
end
|
||||
|
||||
defp unpack_typespec_kw({ :type, _, :tuple, [{ :atom, _, atom }, type] }, acc) do
|
||||
{ :ok, [{atom, typespec_to_ast(type)}|acc] }
|
||||
defp unpack_typespec_kw([], acc) do
|
||||
{ :ok, :lists.reverse(acc) }
|
||||
end
|
||||
|
||||
defp unpack_typespec_kw(_, _acc) do
|
||||
|
||||
@@ -25,7 +25,9 @@ defmodule Keyword do
|
||||
|
||||
@type key :: atom
|
||||
@type value :: any
|
||||
|
||||
@type t :: [{key, value}]
|
||||
@type t(value) :: [{ key, value }]
|
||||
|
||||
@doc """
|
||||
Creates a Keyword from an enum. Unlike `Keyword.new`
|
||||
@@ -209,6 +211,27 @@ defmodule Keyword do
|
||||
lc { _, value } inlist keywords, do: value
|
||||
end
|
||||
|
||||
@doc """
|
||||
Deletes the entry in the keyword list for a `key` with `value`.
|
||||
If no `key` with `value` exists, returns the keyword list unchanged.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Keyword.delete([a: 1, b: 2], :a, 1)
|
||||
[b: 2]
|
||||
|
||||
iex> Keyword.delete([a: 1, b: 2, a: 3], :a, 3)
|
||||
[a: 1, b: 2]
|
||||
|
||||
iex> Keyword.delete([b: 2], :a, 5)
|
||||
[b: 2]
|
||||
|
||||
"""
|
||||
@spec delete(t, key, value) :: t
|
||||
def delete(keywords, key, value) when is_atom(key) do
|
||||
lc { k, v } = tuple inlist keywords, key != k or value != v, do: tuple
|
||||
end
|
||||
|
||||
@doc """
|
||||
Deletes all entries in the keyword list for a specific `key`.
|
||||
If the `key` does not exist, returns the keyword list unchanged.
|
||||
@@ -254,7 +277,7 @@ defmodule Keyword do
|
||||
Puts the given `value` under `key`.
|
||||
|
||||
If a previous value is already stored, all entries are
|
||||
removed and the value is overriden.
|
||||
removed and the value is overridden.
|
||||
|
||||
## Examples
|
||||
|
||||
|
||||
+148
-18
@@ -4,6 +4,12 @@ defmodule List do
|
||||
and cannot be part of the Enum protocol. In general,
|
||||
favor using the Enum API instead of List.
|
||||
|
||||
Some functions in this module expect an index. Index
|
||||
access for list is linear. Negative indexes are also
|
||||
supported but they imply the list will be iterated twice,
|
||||
one to calculate the proper index and another to the
|
||||
operation.
|
||||
|
||||
A decision was taken to delegate most functions to
|
||||
Erlang's standard library but follow Elixir's convention
|
||||
of receiving the target (in this case, a list) as the
|
||||
@@ -13,9 +19,9 @@ defmodule List do
|
||||
@compile :inline_list_funcs
|
||||
|
||||
@doc """
|
||||
Deletes the given item from the list. Returns a list without the item.
|
||||
If the item occurs more than once in the list, just the first occurrence
|
||||
is removed.
|
||||
Deletes the given item from the list. Returns a list without
|
||||
the item. If the item occurs more than once in the list, just
|
||||
the first occurrence is removed.
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -26,6 +32,7 @@ defmodule List do
|
||||
[1, 2, 3]
|
||||
|
||||
"""
|
||||
@spec delete(list, any) :: list
|
||||
def delete(list, item) do
|
||||
:lists.delete(item, list)
|
||||
end
|
||||
@@ -41,6 +48,7 @@ defmodule List do
|
||||
iex> List.duplicate([1, 2], 2)
|
||||
[[1,2],[1,2]]
|
||||
"""
|
||||
@spec duplicate(elem, non_neg_integer) :: [elem] when elem: var
|
||||
def duplicate(elem, n) do
|
||||
:lists.duplicate(n, elem)
|
||||
end
|
||||
@@ -54,12 +62,13 @@ defmodule List do
|
||||
[1,2,3]
|
||||
|
||||
"""
|
||||
@spec flatten(deep_list) :: list when deep_list: [any | deep_list]
|
||||
def flatten(list) do
|
||||
:lists.flatten(list)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Flattens the given `list` of nested lists.
|
||||
Flattens the given `list` of nested lists.
|
||||
The list `tail` will be added at the end of
|
||||
the flattened list.
|
||||
|
||||
@@ -69,6 +78,7 @@ defmodule List do
|
||||
[1,2,3,4,5]
|
||||
|
||||
"""
|
||||
@spec flatten(deep_list, [elem]) :: [elem] when elem: var, deep_list: [elem | deep_list]
|
||||
def flatten(list, tail) do
|
||||
:lists.flatten(list, tail)
|
||||
end
|
||||
@@ -86,6 +96,7 @@ defmodule List do
|
||||
2
|
||||
|
||||
"""
|
||||
@spec foldl([elem], acc, (elem, acc -> acc)) :: acc when elem: var, acc: var
|
||||
def foldl(list, acc, function) when is_list(list) and is_function(function) do
|
||||
:lists.foldl(function, acc, list)
|
||||
end
|
||||
@@ -100,10 +111,28 @@ defmodule List do
|
||||
-2
|
||||
|
||||
"""
|
||||
@spec foldr([elem], acc, (elem, acc -> acc)) :: acc when elem: var, acc: var
|
||||
def foldr(list, acc, function) when is_list(list) and is_function(function) do
|
||||
:lists.foldr(function, acc, list)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns the first element in `list` or `nil` if `list` is empty.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> List.first([])
|
||||
nil
|
||||
iex> List.first([1])
|
||||
1
|
||||
iex> List.first([1, 2, 3])
|
||||
1
|
||||
|
||||
"""
|
||||
@spec first([elem]) :: nil | elem when elem: var
|
||||
def first([]), do: nil
|
||||
def first([h|_]), do: h
|
||||
|
||||
@doc """
|
||||
Returns the last element in `list` or `nil` if `list` is empty.
|
||||
|
||||
@@ -117,11 +146,10 @@ defmodule List do
|
||||
3
|
||||
|
||||
"""
|
||||
def last([]), do: nil
|
||||
|
||||
def last(list) do
|
||||
:lists.last(list)
|
||||
end
|
||||
@spec last([elem]) :: nil | elem when elem: var
|
||||
def last([]), do: nil
|
||||
def last([h]), do: h
|
||||
def last([_|t]), do: last(t)
|
||||
|
||||
@doc """
|
||||
Receives a list of tuples and returns the first tuple
|
||||
@@ -140,6 +168,7 @@ defmodule List do
|
||||
nil
|
||||
|
||||
"""
|
||||
@spec keyfind([tuple], any, non_neg_integer, any) :: any
|
||||
def keyfind(list, key, position, default // nil) do
|
||||
:lists.keyfind(key, position + 1, list) || default
|
||||
end
|
||||
@@ -161,6 +190,7 @@ defmodule List do
|
||||
false
|
||||
|
||||
"""
|
||||
@spec keymember?([tuple], any, non_neg_integer) :: any
|
||||
def keymember?(list, key, position) do
|
||||
:lists.keymember(key, position + 1, list)
|
||||
end
|
||||
@@ -175,6 +205,7 @@ defmodule List do
|
||||
[a: 3, b: 2]
|
||||
|
||||
"""
|
||||
@spec keyreplace([tuple], any, non_neg_integer, tuple) :: [tuple]
|
||||
def keyreplace(list, key, position, new_tuple) do
|
||||
:lists.keyreplace(key, position + 1, list, new_tuple)
|
||||
end
|
||||
@@ -192,6 +223,7 @@ defmodule List do
|
||||
[a: 5, b: 3, c: 1]
|
||||
|
||||
"""
|
||||
@spec keysort([tuple], non_neg_integer) :: [tuple]
|
||||
def keysort(list, position) do
|
||||
:lists.keysort(position + 1, list)
|
||||
end
|
||||
@@ -210,6 +242,7 @@ defmodule List do
|
||||
[a: 1, b: 2, c: 3]
|
||||
|
||||
"""
|
||||
@spec keystore([tuple], any, non_neg_integer, tuple) :: [tuple]
|
||||
def keystore(list, key, position, new_tuple) do
|
||||
:lists.keystore(key, position + 1, list, new_tuple)
|
||||
end
|
||||
@@ -222,15 +255,16 @@ defmodule List do
|
||||
## Examples
|
||||
|
||||
iex> List.keydelete([a: 1, b: 2], :a, 0)
|
||||
[{ :b, 2 }]
|
||||
[b: 2]
|
||||
|
||||
iex> List.keydelete([a: 1, b: 2], 2, 1)
|
||||
[{ :a, 1 }]
|
||||
[a: 1]
|
||||
|
||||
iex> List.keydelete([a: 1, b: 2], :c, 0)
|
||||
[{ :a, 1 }, { :b, 2 }]
|
||||
[a: 1, b: 2]
|
||||
|
||||
"""
|
||||
@spec keydelete([tuple], any, non_neg_integer) :: [tuple]
|
||||
def keydelete(list, key, position) do
|
||||
:lists.keydelete(key, position + 1, list)
|
||||
end
|
||||
@@ -252,6 +286,7 @@ defmodule List do
|
||||
[]
|
||||
|
||||
"""
|
||||
@spec wrap(list | any) :: list
|
||||
def wrap(list) when is_list(list) do
|
||||
list
|
||||
end
|
||||
@@ -276,6 +311,7 @@ defmodule List do
|
||||
[{1, 3, 5}]
|
||||
|
||||
"""
|
||||
@spec zip([list]) :: [tuple]
|
||||
def zip([]), do: []
|
||||
def zip(list_of_lists) when is_list(list_of_lists) do
|
||||
do_zip(list_of_lists, [])
|
||||
@@ -294,14 +330,15 @@ defmodule List do
|
||||
[[1, 2, 3], [:a, :b, :c]]
|
||||
|
||||
"""
|
||||
@spec unzip([tuple]) :: [list]
|
||||
def unzip(list) when is_list(list) do
|
||||
:lists.map &tuple_to_list/1, zip(list)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns a list with `value` inserted at the specified `index`. Note that `index`
|
||||
is capped at the list length. Negative indices indicate an offset from the
|
||||
end of the list.
|
||||
Returns a list with `value` inserted at the specified `index`.
|
||||
Note that `index` is capped at the list length. Negative indices
|
||||
indicate an offset from the end of the list.
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -318,6 +355,7 @@ defmodule List do
|
||||
[0, 1, 2, 3]
|
||||
|
||||
"""
|
||||
@spec insert_at(list, integer, any) :: list
|
||||
def insert_at(list, index, value) do
|
||||
if index < 0 do
|
||||
do_insert_at(list, length(list) + index + 1, value)
|
||||
@@ -327,9 +365,9 @@ defmodule List do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns a list with a replaced value at the specified `index`. Negative indices
|
||||
indicate an offset from the end of the list. If `index` is out of bounds, the
|
||||
original `list` is returned.
|
||||
Returns a list with a replaced value at the specified `index`.
|
||||
Negative indices indicate an offset from the end of the list.
|
||||
If `index` is out of bounds, the original `list` is returned.
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -346,6 +384,7 @@ defmodule List do
|
||||
[1, 2, 3]
|
||||
|
||||
"""
|
||||
@spec replace_at(list, integer, any) :: list
|
||||
def replace_at(list, index, value) do
|
||||
if index < 0 do
|
||||
do_replace_at(list, length(list) + index, value)
|
||||
@@ -354,6 +393,61 @@ defmodule List do
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns a list with an updated value at the specified `index`.
|
||||
Negative indices indicate an offset from the end of the list.
|
||||
If `index` is out of bounds, the original `list` is returned.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> List.update_at([1, 2, 3], 0, &(&1 + 10))
|
||||
[11, 2, 3]
|
||||
|
||||
iex> List.update_at([1, 2, 3], 10, &(&1 + 10))
|
||||
[1, 2, 3]
|
||||
|
||||
iex> List.update_at([1, 2, 3], -1, &(&1 + 10))
|
||||
[1, 2, 13]
|
||||
|
||||
iex> List.update_at([1, 2, 3], -10, &(&1 + 10))
|
||||
[1, 2, 3]
|
||||
|
||||
"""
|
||||
@spec update_at([elem], integer, (elem -> any)) :: list when elem: var
|
||||
def update_at(list, index, fun) do
|
||||
if index < 0 do
|
||||
do_update_at(list, length(list) + index, fun)
|
||||
else
|
||||
do_update_at(list, index, fun)
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Produces a new list by removing the value at the specified `index`.
|
||||
Negative indices indicate an offset from the end of the list.
|
||||
If `index` is out of bounds, the original `list` is returned.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> List.delete_at([1, 2, 3], 0)
|
||||
[2, 3]
|
||||
|
||||
iex List.delete_at([1, 2, 3], 10)
|
||||
[1, 2, 3]
|
||||
|
||||
iex> List.delete_at([1, 2, 3], -1)
|
||||
[1, 2]
|
||||
|
||||
"""
|
||||
@spec delete_at(list, integer) :: list
|
||||
def delete_at(list, index) do
|
||||
if index < 0 do
|
||||
do_delete_at(list, length(list) + index)
|
||||
else
|
||||
do_delete_at(list, index)
|
||||
end
|
||||
end
|
||||
|
||||
## Helpers
|
||||
|
||||
# replace_at
|
||||
@@ -388,6 +482,42 @@ defmodule List do
|
||||
[ h | do_insert_at(t, index - 1, value) ]
|
||||
end
|
||||
|
||||
# update_at
|
||||
|
||||
defp do_update_at([value|list], 0, fun) do
|
||||
[ fun.(value) | list ]
|
||||
end
|
||||
|
||||
defp do_update_at(list, index, _fun) when index < 0 do
|
||||
list
|
||||
end
|
||||
|
||||
defp do_update_at([h|t], index, fun) do
|
||||
[ h | do_update_at(t, index - 1, fun) ]
|
||||
end
|
||||
|
||||
defp do_update_at([], _index, _fun) do
|
||||
[]
|
||||
end
|
||||
|
||||
# delete_at
|
||||
|
||||
defp do_delete_at([], _index) do
|
||||
[]
|
||||
end
|
||||
|
||||
defp do_delete_at([_|t], 0) do
|
||||
t
|
||||
end
|
||||
|
||||
defp do_delete_at(list, index) when index < 0 do
|
||||
list
|
||||
end
|
||||
|
||||
defp do_delete_at([h|t], index) do
|
||||
[h | do_delete_at(t, index-1)]
|
||||
end
|
||||
|
||||
# zip
|
||||
|
||||
defp do_zip(list, acc) do
|
||||
|
||||
+255
-184
@@ -2,54 +2,55 @@ import Kernel, except: [to_string: 1]
|
||||
|
||||
defmodule Macro do
|
||||
@moduledoc """
|
||||
This module provides conveniences for working with macros.
|
||||
Conveniences for working with macros.
|
||||
"""
|
||||
|
||||
@typedoc "Abstract Syntax Tree (AST) node"
|
||||
@type t :: { t, t } | { t, Keyword.t, t } | atom | number | binary | list
|
||||
@typedoc "Abstract Syntax Tree (AST)"
|
||||
@type t :: expr | { t, t } | atom | number | binary | pid | fun | [t]
|
||||
|
||||
@typedoc "Expr node (remaining ones are literals)"
|
||||
@type expr :: { expr | atom, Keyword.t, atom | [t] }
|
||||
|
||||
@binary_ops [:===, :!==,
|
||||
:==, :!=, :<=, :>=,
|
||||
:&&, :||, :<>, :++, :--, :**, ://, :::, :<-, :.., :|>, :=~,
|
||||
:<, :>, :->,
|
||||
:+, :-, :*, :/, :=, :|, :.,
|
||||
:and, :or, :xor, :when, :in, :inlist, :inbits,
|
||||
:<<<, :>>>, :|||, :&&&, :^^^, :~~~]
|
||||
|
||||
@doc false
|
||||
defmacro binary_ops do
|
||||
[ :===, :!==,
|
||||
:==, :!=, :<=, :>=,
|
||||
:&&, :||, :<>, :++, :--, :**, ://, :::, :<-, :.., :|>, :=~,
|
||||
:<, :>, :->,
|
||||
:+, :-, :*, :/, :=, :|, :.,
|
||||
:and, :or, :xor, :when, :in, :inlist, :inbits,
|
||||
:<<<, :>>>, :|||, :&&&, :^^^, :~~~ ]
|
||||
end
|
||||
defmacro binary_ops, do: @binary_ops
|
||||
|
||||
@unary_ops [:!, :@, :^, :not, :+, :-, :~~~, :&]
|
||||
|
||||
@doc false
|
||||
defmacro unary_ops do
|
||||
[:!, :@, :^, :not, :+, :-, :~~~, :&]
|
||||
end
|
||||
defmacro unary_ops, do: @unary_ops
|
||||
|
||||
@spec binary_op_props(atom) :: { :left | :right, precedence :: integer }
|
||||
defp binary_op_props(o) do
|
||||
case o do
|
||||
::: -> {:right, 30}
|
||||
:when -> {:right, 40}
|
||||
o when o in [:inlist, :inbits] -> {:left, 50}
|
||||
:// -> {:right, 60}
|
||||
:| -> {:left, 70}
|
||||
::: -> {:right, 40}
|
||||
o when o in [:inlist, :inbits, ://, :|] -> {:right, 50}
|
||||
:when -> {:right, 70}
|
||||
:= -> {:right, 80}
|
||||
o when o in [:||, :|||, :or, :xor] -> {:left, 130}
|
||||
o when o in [:&&, :&&&, :and] -> {:left, 140}
|
||||
o when o in [:==, :!=, :<, :<=, :>=, :>, :=~, :===, :!==] -> {:left, 150}
|
||||
o when o in [:<-, :|>, :<<<, :>>>] -> {:right, 160}
|
||||
:in -> {:left, 170}
|
||||
:.. -> {:left, 200}
|
||||
o when o in [:++, :--, :**, :.., :<>] -> {:right, 200}
|
||||
o when o in [:+, :-] -> {:left, 210}
|
||||
o when o in [:*, :/] -> {:left, 220}
|
||||
o when o in [:<>] -> {:right, 230}
|
||||
:^^^ -> {:left, 250}
|
||||
:. -> {:left, 310}
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Breaks a pipeline expression into a list. Raises if
|
||||
the pipeline is ill-formed.
|
||||
Breaks a pipeline expression into a list.
|
||||
|
||||
Raises if the pipeline is ill-formed.
|
||||
"""
|
||||
@spec unpipe(Macro.t) :: [Macro.t]
|
||||
def unpipe({ :|> , _, [left, right] }) do
|
||||
@@ -61,7 +62,7 @@ defmodule Macro do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Pipes the given `expr` in to the `call_expr` as the
|
||||
Pipes `expr` into the `call_expr` as the
|
||||
argument in the given `position`.
|
||||
"""
|
||||
@spec pipe(Macro.t, Macro.t, integer) :: Macro.t | no_return
|
||||
@@ -81,33 +82,83 @@ defmodule Macro do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Receives an expression representing a possible definition
|
||||
and extracts its arguments. It returns a tuple with the
|
||||
function name and the arguments list or `:error` if not
|
||||
a valid call syntax.
|
||||
Recurs the quoted expression applying the given function to
|
||||
each metadata node.
|
||||
|
||||
This is useful for macros that want to provide the same
|
||||
argument syntax available in def/defp/defmacro and friends.
|
||||
This is often useful to remove information like lines and
|
||||
hygienic counters from the expression for either storage or
|
||||
comparison.
|
||||
|
||||
## Examples
|
||||
|
||||
extract_args(quote do: foo) == { :foo, [] }
|
||||
extract_args(quote do: foo()) == { :foo, [] }
|
||||
extract_args(quote do: foo(1, 2, 3)) == { :foo, [1, 2, 3] }
|
||||
extract_args(quote do: 1.(1, 2, 3)) == :error
|
||||
iex> quoted = quote line: 10, do: sample()
|
||||
{:sample, [line: 10], []}
|
||||
iex> Macro.update_meta(quoted, &Keyword.delete(&1, :line))
|
||||
{:sample, [], []}
|
||||
|
||||
"""
|
||||
@spec extract_args(Macro.t) :: { atom, [Macro.t] } | :error
|
||||
def extract_args(expr) do
|
||||
:elixir_clauses.extract_args(expr)
|
||||
@spec update_meta(t, (Keyword.t -> Keyword.t)) :: t
|
||||
def update_meta(quoted, fun)
|
||||
|
||||
def update_meta({ left, meta, right }, fun) when is_list(meta) do
|
||||
{ update_meta(left, fun), fun.(meta), update_meta(right, fun) }
|
||||
end
|
||||
|
||||
def update_meta({ left, right }, fun) do
|
||||
{ update_meta(left, fun), update_meta(right, fun) }
|
||||
end
|
||||
|
||||
def update_meta(list, fun) when is_list(list) do
|
||||
lc x inlist list, do: update_meta(x, fun)
|
||||
end
|
||||
|
||||
def update_meta(other, _fun) do
|
||||
other
|
||||
end
|
||||
|
||||
@doc """
|
||||
Decomposes a local or remote call into its remote part (when provided),
|
||||
function name and argument list.
|
||||
|
||||
Returns `:error` when an invalid call syntax is provided.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Macro.decompose_call(quote do: foo)
|
||||
{ :foo, [] }
|
||||
|
||||
iex> Macro.decompose_call(quote do: foo())
|
||||
{ :foo, [] }
|
||||
|
||||
iex> Macro.decompose_call(quote do: foo(1, 2, 3))
|
||||
{ :foo, [1, 2, 3] }
|
||||
|
||||
iex> Macro.decompose_call(quote do: Elixir.M.foo(1, 2, 3))
|
||||
{ { :__aliases__, [], [:Elixir, :M] }, :foo, [1, 2, 3] }
|
||||
|
||||
iex> Macro.decompose_call(quote do: 42)
|
||||
:error
|
||||
|
||||
"""
|
||||
@spec decompose_call(Macro.t) :: { atom, [Macro.t] } | { Macro.t, atom, [Macro.t] } | :error
|
||||
def decompose_call({ { :., _, [remote, function] }, _, args }) when is_tuple(remote) or is_atom(remote),
|
||||
do: { remote, function, args }
|
||||
|
||||
def decompose_call({ name, _, args }) when is_atom(name) and is_atom(args),
|
||||
do: { name, [] }
|
||||
|
||||
def decompose_call({ name, _, args }) when is_atom(name) and is_list(args),
|
||||
do: { name, args }
|
||||
|
||||
def decompose_call(_),
|
||||
do: :error
|
||||
|
||||
@doc """
|
||||
Recursively escapes a value so it can be inserted
|
||||
into a syntax tree.
|
||||
|
||||
One may pass `unquote: true` to `escape/2`
|
||||
which leaves unquote statements unescaped, effectively
|
||||
which leaves `unquote` statements unescaped, effectively
|
||||
unquoting the contents on escape.
|
||||
|
||||
## Examples
|
||||
@@ -129,7 +180,9 @@ defmodule Macro do
|
||||
end
|
||||
|
||||
@doc %S"""
|
||||
Unescape the given chars. This is the unescaping behavior
|
||||
Unescape the given chars.
|
||||
|
||||
This is the unescaping behavior
|
||||
used by default in Elixir single- and double-quoted strings.
|
||||
Check `unescape_string/2` for information on how to customize
|
||||
the escaping map.
|
||||
@@ -139,7 +192,7 @@ defmodule Macro do
|
||||
also escaped according to the latin1 set they represent.
|
||||
|
||||
This function is commonly used on sigil implementations
|
||||
(like `%r`, `%b` and others) which receive a raw, unescaped
|
||||
(like `%r`, `%s` and others) which receive a raw, unescaped
|
||||
string.
|
||||
|
||||
## Examples
|
||||
@@ -148,7 +201,7 @@ defmodule Macro do
|
||||
"example\n"
|
||||
|
||||
In the example above, we pass a string with `\n` escaped
|
||||
and we return a version with it unescaped.
|
||||
and return a version with it unescaped.
|
||||
"""
|
||||
@spec unescape_string(String.t) :: String.t
|
||||
def unescape_string(chars) do
|
||||
@@ -157,13 +210,14 @@ defmodule Macro do
|
||||
|
||||
@doc %S"""
|
||||
Unescape the given chars according to the map given.
|
||||
|
||||
Check `unescape_string/1` if you want to use the same map
|
||||
as Elixir single- and double-quoted strings.
|
||||
|
||||
## Map
|
||||
|
||||
The map must be a function. The function receives an integer
|
||||
representing the number of the characters it wants to unescape.
|
||||
representing the codepoint of the character it wants to unescape.
|
||||
Here is the default mapping function implemented by Elixir:
|
||||
|
||||
def unescape_map(?a), do: ?\a
|
||||
@@ -184,16 +238,16 @@ defmodule Macro do
|
||||
## Octals
|
||||
|
||||
Octals will by default be escaped unless the map function
|
||||
returns false for ?0.
|
||||
returns `false` for `?0`.
|
||||
|
||||
## Hex
|
||||
|
||||
Hexadecimals will by default be escaped unless the map function
|
||||
returns false for ?x.
|
||||
returns `false` for `?x`.
|
||||
|
||||
## Examples
|
||||
|
||||
Using the unescape_map defined above is easy:
|
||||
Using the `unescape_map` function defined above is easy:
|
||||
|
||||
Macro.unescape_string "example\\n", &unescape_map(&1)
|
||||
|
||||
@@ -205,12 +259,13 @@ defmodule Macro do
|
||||
|
||||
@doc """
|
||||
Unescape the given tokens according to the default map.
|
||||
|
||||
Check `unescape_string/1` and `unescape_string/2` for more
|
||||
information about unescaping.
|
||||
|
||||
Only tokens that are binaries are unescaped, all others are
|
||||
ignored. This function is useful when implementing your own
|
||||
sigils. Check the implementation of `Kernel.sigil_b`
|
||||
sigils. Check the implementation of `Kernel.sigil_s/2`
|
||||
for examples.
|
||||
"""
|
||||
@spec unescape_tokens([Macro.t]) :: [Macro.t]
|
||||
@@ -220,6 +275,7 @@ defmodule Macro do
|
||||
|
||||
@doc """
|
||||
Unescape the given tokens according to the given map.
|
||||
|
||||
Check `unescape_tokens/1` and `unescape_string/2` for more information.
|
||||
"""
|
||||
@spec unescape_tokens([Macro.t], (non_neg_integer -> non_neg_integer | false)) :: [Macro.t]
|
||||
@@ -270,31 +326,43 @@ defmodule Macro do
|
||||
|
||||
# Tuple containers
|
||||
def to_string({ :{}, _, args } = ast, fun) do
|
||||
fun.(ast, "{" <> Enum.map_join(args, ", ", &to_string(&1, fun)) <> "}")
|
||||
tuple = "{" <> Enum.map_join(args, ", ", &to_string(&1, fun)) <> "}"
|
||||
fun.(ast, tuple)
|
||||
end
|
||||
|
||||
# Fn keyword
|
||||
def to_string({ :fn, _, [{ :->, _, [{_, _, tuple}] } = arrow] } = ast, fun)
|
||||
def to_string({ :fn, _, [{ :->, _, [_, tuple] }] = arrow } = ast, fun)
|
||||
when not is_tuple(tuple) or elem(tuple, 0) != :__block__ do
|
||||
fun.(ast, "fn " <> arrow_to_string(arrow, fun) <> " end")
|
||||
end
|
||||
|
||||
def to_string({ :fn, _, [{ :->, _, [_] } = block] } = ast, fun) do
|
||||
def to_string({ :fn, _, [{ :->, _, _ }] = block } = ast, fun) do
|
||||
fun.(ast, "fn " <> block_to_string(block, fun) <> "\nend")
|
||||
end
|
||||
|
||||
def to_string({ :fn, _, [block] } = ast, fun) do
|
||||
def to_string({ :fn, _, block } = ast, fun) do
|
||||
block = adjust_new_lines block_to_string(block, fun), "\n "
|
||||
fun.(ast, "fn\n " <> block <> "\nend")
|
||||
end
|
||||
|
||||
# left -> right
|
||||
def to_string({ :->, _, _ } = ast, fun) do
|
||||
def to_string([{ :->, _, _ }|_] = ast, fun) do
|
||||
fun.(ast, "(" <> arrow_to_string(ast, fun, true) <> ")")
|
||||
end
|
||||
|
||||
# left when right
|
||||
def to_string({ :when, _, [left, right] } = ast, fun) do
|
||||
if right != [] and Keyword.keyword?(right) do
|
||||
right = kw_list_to_string(right, fun)
|
||||
else
|
||||
right = fun.(ast, op_to_string(right, fun, :when, :right))
|
||||
end
|
||||
|
||||
fun.(ast, op_to_string(left, fun, :when, :left) <> " when " <> right)
|
||||
end
|
||||
|
||||
# Binary ops
|
||||
def to_string({ op, _, [left, right] } = ast, fun) when op in binary_ops do
|
||||
def to_string({ op, _, [left, right] } = ast, fun) when op in @binary_ops do
|
||||
fun.(ast, op_to_string(left, fun, op, :left) <> " #{op} " <> op_to_string(right, fun, op, :right))
|
||||
end
|
||||
|
||||
@@ -309,7 +377,7 @@ defmodule Macro do
|
||||
fun.(ast, "not " <> to_string(arg, fun))
|
||||
end
|
||||
|
||||
def to_string({ op, _, [arg] } = ast, fun) when op in unary_ops do
|
||||
def to_string({ op, _, [arg] } = ast, fun) when op in @unary_ops do
|
||||
fun.(ast, atom_to_binary(op) <> to_string(arg, fun))
|
||||
end
|
||||
|
||||
@@ -321,7 +389,7 @@ defmodule Macro do
|
||||
# All other calls
|
||||
def to_string({ target, _, args } = ast, fun) when is_list(args) do
|
||||
{ list, last } = :elixir_utils.split_last(args)
|
||||
fun.(ast, case is_kw_blocks?(last) do
|
||||
fun.(ast, case kw_blocks?(last) do
|
||||
true -> call_to_string_with_args(target, list, fun) <> kw_blocks_to_string(last, fun)
|
||||
false -> call_to_string_with_args(target, args, fun)
|
||||
end)
|
||||
@@ -334,25 +402,30 @@ defmodule Macro do
|
||||
|
||||
# Lists
|
||||
def to_string(list, fun) when is_list(list) do
|
||||
if Keyword.keyword?(list) do
|
||||
fun.(list, "[" <> kw_list_to_string(list, fun) <> "]")
|
||||
else
|
||||
fun.(list, "[" <> Enum.map_join(list, ", ", &to_string(&1, fun)) <> "]")
|
||||
end
|
||||
fun.(list, cond do
|
||||
list == [] ->
|
||||
"[]"
|
||||
:io_lib.printable_list(list) ->
|
||||
"'" <> Inspect.BitString.escape(String.from_char_list!(list), ?') <> "'"
|
||||
Keyword.keyword?(list) ->
|
||||
"[" <> kw_list_to_string(list, fun) <> "]"
|
||||
true ->
|
||||
"[" <> Enum.map_join(list, ", ", &to_string(&1, fun)) <> "]"
|
||||
end)
|
||||
end
|
||||
|
||||
# All other structures
|
||||
def to_string(other, fun), do: fun.(other, inspect(other, raw: true))
|
||||
def to_string(other, fun), do: fun.(other, inspect(other, records: false))
|
||||
|
||||
# Block keywords
|
||||
defmacrop kw_keywords, do: [:do, :catch, :rescue, :after, :else]
|
||||
@kw_keywords [:do, :catch, :rescue, :after, :else]
|
||||
|
||||
defp is_kw_blocks?([_|_] = kw) do
|
||||
Enum.all?(kw, &match?({x, _} when x in kw_keywords, &1))
|
||||
defp kw_blocks?([_|_] = kw) do
|
||||
Enum.all?(kw, &match?({x, _} when x in @kw_keywords, &1))
|
||||
end
|
||||
defp is_kw_blocks?(_), do: false
|
||||
defp kw_blocks?(_), do: false
|
||||
|
||||
defp module_to_string(atom, _fun) when is_atom(atom), do: inspect(atom, raw: true)
|
||||
defp module_to_string(atom, _fun) when is_atom(atom), do: inspect(atom, records: false)
|
||||
defp module_to_string(other, fun), do: call_to_string(other, fun)
|
||||
|
||||
defp call_to_string(atom, _fun) when is_atom(atom), do: atom_to_binary(atom)
|
||||
@@ -361,23 +434,25 @@ defmodule Macro do
|
||||
defp call_to_string(other, fun), do: to_string(other, fun)
|
||||
|
||||
defp call_to_string_with_args(target, args, fun) do
|
||||
{ list, last } = :elixir_utils.split_last(args)
|
||||
target = call_to_string(target, fun)
|
||||
args = args_to_string(args, fun)
|
||||
target <> "(" <> args <> ")"
|
||||
end
|
||||
|
||||
case last != [] and Keyword.keyword?(last) do
|
||||
true ->
|
||||
args = Enum.map_join(list, ", ", &to_string(&1, fun))
|
||||
if list != [], do: args = args <> ", "
|
||||
args = args <> kw_list_to_string(last, fun)
|
||||
target <> "(" <> args <> ")"
|
||||
false ->
|
||||
args = Enum.map_join(args, ", ", &to_string(&1, fun))
|
||||
target <> "(" <> args <> ")"
|
||||
defp args_to_string(args, fun) do
|
||||
{ list, last } = :elixir_utils.split_last(args)
|
||||
|
||||
if last != [] and Keyword.keyword?(last) do
|
||||
args = Enum.map_join(list, ", ", &to_string(&1, fun))
|
||||
if list != [], do: args = args <> ", "
|
||||
args <> kw_list_to_string(last, fun)
|
||||
else
|
||||
Enum.map_join(args, ", ", &to_string(&1, fun))
|
||||
end
|
||||
end
|
||||
|
||||
defp kw_blocks_to_string(kw, fun) do
|
||||
Enum.reduce(kw_keywords, " ", fn(x, acc) ->
|
||||
Enum.reduce(@kw_keywords, " ", fn(x, acc) ->
|
||||
case Keyword.has_key?(kw, x) do
|
||||
true -> acc <> kw_block_to_string(x, Keyword.get(kw, x), fun)
|
||||
false -> acc
|
||||
@@ -390,8 +465,8 @@ defmodule Macro do
|
||||
atom_to_binary(key) <> "\n " <> block <> "\n"
|
||||
end
|
||||
|
||||
defp block_to_string({ :->, _, exprs }, fun) do
|
||||
Enum.map_join(exprs, "\n", fn({ left, _, right }) ->
|
||||
defp block_to_string([{ :->, _, _ }|_] = block, fun) do
|
||||
Enum.map_join(block, "\n", fn({ :->, _, [left, right] }) ->
|
||||
left = comma_join_or_empty_paren(left, fun, false)
|
||||
left <> "->\n " <> adjust_new_lines block_to_string(right, fun), "\n "
|
||||
end)
|
||||
@@ -413,7 +488,7 @@ defmodule Macro do
|
||||
"(" <> to_string(expr, fun) <> ")"
|
||||
end
|
||||
|
||||
defp op_to_string({ op, _, [_, _] } = expr, fun, parent_op, side) when op in binary_ops do
|
||||
defp op_to_string({ op, _, [_, _] } = expr, fun, parent_op, side) when op in @binary_ops do
|
||||
{ parent_assoc, parent_prec } = binary_op_props(parent_op)
|
||||
{ _, prec } = binary_op_props(op)
|
||||
cond do
|
||||
@@ -431,8 +506,8 @@ defmodule Macro do
|
||||
|
||||
defp op_to_string(expr, fun, _, _), do: to_string(expr, fun)
|
||||
|
||||
defp arrow_to_string({ :->, _, pairs }, fun, paren // false) do
|
||||
Enum.map_join(pairs, "; ", fn({ left, _, right }) ->
|
||||
defp arrow_to_string(pairs, fun, paren // false) do
|
||||
Enum.map_join(pairs, "; ", fn({ :->, _, [left, right] }) ->
|
||||
left = comma_join_or_empty_paren(left, fun, paren)
|
||||
left <> "-> " <> to_string(right, fun)
|
||||
end)
|
||||
@@ -455,18 +530,19 @@ defmodule Macro do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Receives a AST node and expands it once. The following contents are expanded:
|
||||
Receives an AST node and expands it once.
|
||||
|
||||
The following contents are expanded:
|
||||
|
||||
* Macros (local or remote);
|
||||
* Aliases are expanded (if possible) and return atoms;
|
||||
* All pseudo-variables (`__FILE__`, `__MODULE__`, etc);
|
||||
* Pseudo-variables (`__ENV__`, `__MODULE__` and `__DIR__`);
|
||||
* Module attributes reader (`@foo`);
|
||||
|
||||
In case the expression cannot be expanded, it returns the expression
|
||||
If the expression cannot be expanded, it returns the expression
|
||||
itself. Notice that `expand_once/2` performs the expansion just
|
||||
once and it is not recursive. Check `expand/2` for expansion
|
||||
until the node no longer represents a macro and `expand_all/2`
|
||||
for recursive expansion.
|
||||
until the node can no longer be expanded.
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -511,7 +587,7 @@ defmodule Macro do
|
||||
end
|
||||
|
||||
The final module name will be `MyHelpers.Module` and not
|
||||
`My.Module`. With `Macro.expand`, such aliases are taken
|
||||
`My.Module`. With `Macro.expand/2`, such aliases are taken
|
||||
into consideration. Local and remote macros are also
|
||||
expanded. We could rewrite our macro above to use this
|
||||
function as:
|
||||
@@ -530,153 +606,148 @@ defmodule Macro do
|
||||
|
||||
"""
|
||||
def expand_once(ast, env) do
|
||||
elem(expand_once(ast, env, nil), 0)
|
||||
elem(do_expand_once(ast, env), 0)
|
||||
end
|
||||
|
||||
defp expand_once({ :__aliases__, _, _ } = original, env, cache) do
|
||||
defp do_expand_once({ :__aliases__, _, _ } = original, env) do
|
||||
case :elixir_aliases.expand(original, env.aliases, env.macro_aliases, env.lexical_tracker) do
|
||||
receiver when is_atom(receiver) ->
|
||||
:elixir_lexical.record_remote(receiver, env.lexical_tracker)
|
||||
{ receiver, true, cache }
|
||||
{ receiver, true }
|
||||
aliases ->
|
||||
aliases = lc alias inlist aliases, do: elem(expand_once(alias, env, cache), 0)
|
||||
aliases = lc alias inlist aliases, do: elem(do_expand_once(alias, env), 0)
|
||||
|
||||
case :lists.all(&is_atom/1, aliases) do
|
||||
true ->
|
||||
receiver = :elixir_aliases.concat(aliases)
|
||||
:elixir_lexical.record_remote(receiver, env.lexical_tracker)
|
||||
{ receiver, true, cache }
|
||||
false -> { original, false, cache }
|
||||
{ receiver, true }
|
||||
false ->
|
||||
{ original, false }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Expand @ calls
|
||||
defp expand_once({ :@, _, [{ name, _, args }] } = original, env, cache) when is_atom(args) or args == [] do
|
||||
defp do_expand_once({ :@, _, [{ name, _, args }] } = original, env) when is_atom(args) or args == [] do
|
||||
case (module = env.module) && Module.open?(module) do
|
||||
true -> { Module.get_attribute(module, name), true, cache }
|
||||
false -> { original, false, cache }
|
||||
true -> { Module.get_attribute(module, name), true }
|
||||
false -> { original, false }
|
||||
end
|
||||
end
|
||||
|
||||
# Expand pseudo-variables
|
||||
defp expand_once({ :__MODULE__, _, atom }, env, cache) when is_atom(atom),
|
||||
do: { env.module, true, cache }
|
||||
defp expand_once({ :__FILE__, _, atom }, env, cache) when is_atom(atom),
|
||||
do: { env.file, true, cache }
|
||||
defp expand_once({ :__DIR__, _, atom }, env, cache) when is_atom(atom),
|
||||
do: { :filename.dirname(env.file), true, cache }
|
||||
defp expand_once({ :__ENV__, _, atom }, env, cache) when is_atom(atom),
|
||||
do: { env, true, cache }
|
||||
defp do_expand_once({ :__MODULE__, _, atom }, env) when is_atom(atom),
|
||||
do: { env.module, true }
|
||||
defp do_expand_once({ :__DIR__, _, atom }, env) when is_atom(atom),
|
||||
do: { :filename.dirname(env.file), true }
|
||||
|
||||
defp do_expand_once({ :__FILE__, _, atom }, env) when is_atom(atom) do
|
||||
IO.write "__FILE__ is deprecated, please use __DIR__ or __ENV__.file instead\n#{Exception.format_stacktrace}"
|
||||
{ env.file, true }
|
||||
end
|
||||
|
||||
defp do_expand_once({ :__ENV__, _, atom }, env) when is_atom(atom),
|
||||
do: { { :{}, [], tuple_to_list(env) }, true }
|
||||
defp do_expand_once({ { :., _, [{ :__ENV__, _, atom }, field] }, _, [] } = original, env) when
|
||||
is_atom(atom) and is_atom(field) do
|
||||
case :erlang.function_exported(Macro.Env, field, 1) do
|
||||
true -> { apply(env, field, []), true }
|
||||
false -> { original, false }
|
||||
end
|
||||
end
|
||||
|
||||
# Expand possible macro import invocation
|
||||
defp expand_once({ atom, line, args } = original, env, cache) when is_atom(atom) do
|
||||
args = case is_atom(args) do
|
||||
true -> []
|
||||
false -> args
|
||||
defp do_expand_once({ atom, meta, context } = original, env)
|
||||
when is_atom(atom) and is_list(meta) and is_atom(context) do
|
||||
if :lists.member({ atom, Keyword.get(meta, :counter, context) }, env.vars) do
|
||||
{ original, false }
|
||||
else
|
||||
case do_expand_once({ atom, meta, [] }, env) do
|
||||
{ _, true } = exp -> exp
|
||||
{ _, false } -> { original, false }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defp do_expand_once({ atom, meta, args } = original, env)
|
||||
when is_atom(atom) and is_list(args) and is_list(meta) do
|
||||
module = env.module
|
||||
|
||||
extra = if function_exported?(module, :__info__, 1) do
|
||||
[{ module, module.__info__(:macros) }]
|
||||
else
|
||||
[]
|
||||
end
|
||||
|
||||
case not is_partial?(args) do
|
||||
false -> { original, false, cache }
|
||||
true ->
|
||||
module = env.module
|
||||
arity = length(args)
|
||||
|
||||
extra = if function_exported?(module, :__info__, 1) do
|
||||
[{ module, module.__info__(:macros) }]
|
||||
else
|
||||
[]
|
||||
end
|
||||
if :elixir_import.special_form(atom, arity) do
|
||||
{ original, false }
|
||||
else
|
||||
expand = :elixir_dispatch.expand_import(meta, { atom, length(args) }, args,
|
||||
:elixir_env.ex_to_env(env), extra)
|
||||
|
||||
cache = to_erl_env(env, cache)
|
||||
expand = :elixir_dispatch.expand_import(line, { atom, length(args) }, args,
|
||||
env.module, extra, cache)
|
||||
|
||||
case expand do
|
||||
{ :ok, _, expanded } -> { expanded, true, cache }
|
||||
{ :error, _ } -> { original, false, cache }
|
||||
end
|
||||
case expand do
|
||||
{ :ok, receiver, quoted } ->
|
||||
next = :elixir_counter.next
|
||||
{ :elixir_quote.linify_with_context_counter(0, { receiver, next }, quoted), true }
|
||||
{ :ok, _receiver } ->
|
||||
{ original, false }
|
||||
:error ->
|
||||
{ original, false }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Expand possible macro require invocation
|
||||
defp expand_once({ { :., _, [left, right] }, line, args } = original, env, cache) when is_atom(right) do
|
||||
{ receiver, _, _ } = expand_once(left, env, cache)
|
||||
defp do_expand_once({ { :., _, [left, right] }, meta, args } = original, env) when is_atom(right) do
|
||||
{ receiver, _ } = do_expand_once(left, env)
|
||||
|
||||
case is_atom(receiver) and not is_partial?(args) do
|
||||
false -> { original, false, cache }
|
||||
case is_atom(receiver) do
|
||||
false -> { original, false }
|
||||
true ->
|
||||
cache = to_erl_env(env, cache)
|
||||
expand = :elixir_dispatch.expand_require(line, receiver, { right, length(args) },
|
||||
args, env.module, cache)
|
||||
expand = :elixir_dispatch.expand_require(meta, receiver, { right, length(args) },
|
||||
args, :elixir_env.ex_to_env(env))
|
||||
|
||||
case expand do
|
||||
{ :ok, _receiver, expanded } -> { expanded, true, cache }
|
||||
{ :error, _ } -> { original, false, cache }
|
||||
{ :ok, receiver, quoted } ->
|
||||
next = :elixir_counter.next
|
||||
{ :elixir_quote.linify_with_context_counter(0, { receiver, next }, quoted), true }
|
||||
:error ->
|
||||
{ original, false }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Anything else is just returned
|
||||
defp expand_once(other, _env, cache), do: { other, false, cache }
|
||||
|
||||
defp to_erl_env(env, nil), do: :elixir_scope.to_erl_env(env)
|
||||
defp to_erl_env(_env, cache), do: cache
|
||||
|
||||
defp is_partial?(args) do
|
||||
:lists.any(&match?({ :&, _, [_] }, &1), args)
|
||||
end
|
||||
defp do_expand_once(other, _env), do: { other, false }
|
||||
|
||||
@doc """
|
||||
Receives a AST node and expands it until it no longer represents
|
||||
a macro. Check `expand_once/2` for more information on how
|
||||
expansion works and `expand_all/2` for recursive expansion.
|
||||
Receives an AST node and expands it until it no longer represents
|
||||
a macro.
|
||||
|
||||
Check `expand_once/2` for more information on how
|
||||
expansion works.
|
||||
"""
|
||||
def expand(tree, env) do
|
||||
elem(expand(tree, env, nil), 0)
|
||||
expand_until({ tree, true }, env)
|
||||
end
|
||||
|
||||
@doc false # Used internally by Elixir
|
||||
def expand(tree, env, cache) do
|
||||
expand_until({ tree, true, cache }, env)
|
||||
defp expand_until({ tree, true }, env) do
|
||||
expand_until(do_expand_once(tree, env), env)
|
||||
end
|
||||
|
||||
defp expand_until({ tree, true, cache }, env) do
|
||||
expand_until(expand_once(tree, env, cache), env)
|
||||
end
|
||||
|
||||
defp expand_until({ tree, false, cache }, _env) do
|
||||
{ tree, cache }
|
||||
end
|
||||
|
||||
@doc false # Used internally by Elixir
|
||||
def expand_all(tree, env, cache) do
|
||||
expand_all_until(expand(tree, env, cache), env)
|
||||
end
|
||||
|
||||
defp expand_all_until({ { left, meta, right }, cache }, env) do
|
||||
{ left, cache } = expand_all(left, env, cache)
|
||||
{ right, cache } = expand_all(right, env, cache)
|
||||
{ { left, meta, right }, cache }
|
||||
end
|
||||
|
||||
defp expand_all_until({ { left, right }, cache }, env) do
|
||||
{ left, cache } = expand_all(left, env, cache)
|
||||
{ right, cache } = expand_all(right, env, cache)
|
||||
{ { left, right }, cache }
|
||||
end
|
||||
|
||||
defp expand_all_until({ list, cache }, env) when is_list(list) do
|
||||
:lists.mapfoldl(&expand_all(&1, env, &2), cache, list)
|
||||
end
|
||||
|
||||
defp expand_all_until({ other, cache }, _env) do
|
||||
{ other, cache }
|
||||
defp expand_until({ tree, false }, _env) do
|
||||
tree
|
||||
end
|
||||
|
||||
@doc """
|
||||
Recurs the quoted expression checking if all sub-terms are
|
||||
safe (i.e. they represent data structures and don't actually
|
||||
evaluate code) and returns `:ok` unless a given term is unsafe,
|
||||
Recursively traverses the quoted expression checking if all sub-terms are
|
||||
safe.
|
||||
|
||||
Terms are considered safe if they represent data structures and don't actually
|
||||
evaluate code. Returns `:ok` unless a given term is unsafe,
|
||||
which is returned as `{ :unsafe, term }`.
|
||||
"""
|
||||
def safe_term(terms) do
|
||||
|
||||
+21
-13
@@ -12,37 +12,45 @@ defmodule Macro.Env do
|
||||
* `function` - a tuple as `{ atom, integer` }, where the first
|
||||
element is the function name and the seconds its arity. Returns
|
||||
`nil` if not inside a function
|
||||
* `aliases` - a list of two item tuples, where the first
|
||||
item is the aliased name and the second the actual name
|
||||
* `context` - the context of the environment. It can be nil
|
||||
(default context), inside a guard or inside an assign
|
||||
* `aliases` - a list of two item tuples, where the first
|
||||
item is the aliased name and the second the actual name
|
||||
* `requires` - the list of required modules
|
||||
* `functions` - a list of functions imported from each module
|
||||
* `macros` - a list of macros imported from each module
|
||||
* `context_modules` - a list of modules defined in the current context
|
||||
* `macro_aliases` - a list of aliases defined inside the current macro
|
||||
* `vars` - a list keeping all defined varaibles as { var, context }
|
||||
* `context_modules` - a list of modules defined in the current context
|
||||
* `vars` - a list keeping all defined variables as { var, context }
|
||||
* `export_vars` - a list keeping all variables to be exported in a construct (may be nil)
|
||||
* `lexical_tracker` - PID to the lexical tracker which is responsible to keep user info
|
||||
* `local` - the module to expand local functions to
|
||||
"""
|
||||
|
||||
@type name_arity :: { atom, non_neg_integer }
|
||||
@type file :: binary
|
||||
@type line :: non_neg_integer
|
||||
@type aliases :: [{ module, module }]
|
||||
@type macro_aliases :: [{ module, { integer, module } }]
|
||||
@type context :: :match | :guard | nil
|
||||
@type requires :: [module]
|
||||
@type functions :: [{ module, [name_arity] }]
|
||||
@type macros :: [{ module, [name_arity] }]
|
||||
@type context_modules :: [module]
|
||||
@type vars :: [{ atom, atom }]
|
||||
@type vars :: [{ atom, atom | non_neg_integer }]
|
||||
@type export_vars :: vars | nil
|
||||
@type lexical_tracker :: pid
|
||||
@type local :: module | nil
|
||||
|
||||
fields = [:module, :file, :line, :function, :aliases, :context, :requires, :functions,
|
||||
:macros, :context_modules, :macro_aliases, :vars, :lexical_tracker]
|
||||
fields = [:module, :file, :line, :function, :context, :requires, :aliases, :functions,
|
||||
:macros, :macro_aliases, :context_modules, :vars, :export_vars, :lexical_tracker,
|
||||
:local]
|
||||
|
||||
types = quote do: [module: module, file: file, line: line,
|
||||
function: name_arity, aliases: aliases, requires: requires,
|
||||
functions: functions, macros: macros, context_modules: context_modules,
|
||||
macro_aliases: aliases, vars: vars, lexical_tracker: lexical_tracker]
|
||||
function: name_arity, context: context, requires: requires, aliases: aliases,
|
||||
functions: functions, macros: macros, macro_aliases: aliases,
|
||||
context_modules: context_modules, vars: vars, export_vars: export_vars,
|
||||
lexical_tracker: lexical_tracker, local: local]
|
||||
|
||||
Record.deffunctions(fields, __MODULE__)
|
||||
Record.deftypes(fields, types, __MODULE__)
|
||||
@@ -56,13 +64,13 @@ defmodule Macro.Env do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns wether the compilation environment is currently
|
||||
Returns whether the compilation environment is currently
|
||||
inside a guard.
|
||||
"""
|
||||
def in_guard?(record), do: context(record) == :guard
|
||||
|
||||
@doc """
|
||||
Returns wether the compilation environment is currently
|
||||
Returns whether the compilation environment is currently
|
||||
inside a match clause.
|
||||
"""
|
||||
def in_match?(record), do: context(record) == :match
|
||||
@@ -73,7 +81,7 @@ defmodule Macro.Env do
|
||||
def stacktrace(record) do
|
||||
cond do
|
||||
nil?(record.module) ->
|
||||
[{ :elixir_compiler, :__FILE__, 2, location(record) }]
|
||||
[{ :elixir_compiler, :__FILE__, 1, location(record) }]
|
||||
nil?(record.function) ->
|
||||
[{ module(record), :__MODULE__, 0, location(record) }]
|
||||
true ->
|
||||
|
||||
+64
-43
@@ -51,11 +51,13 @@ defmodule Module do
|
||||
When just a module is provided, the function/macro is assumed to be
|
||||
`__before_compile__/1`.
|
||||
|
||||
Note: differently from `@after_compile`, the callback function/macro must
|
||||
be placed in a separate module (because when the callback is invoked,
|
||||
the current module does not yet exist).
|
||||
|
||||
### Example
|
||||
|
||||
defmodule M do
|
||||
@before_compile __MODULE__
|
||||
|
||||
defmodule A do
|
||||
defmacro __before_compile__(_env) do
|
||||
quote do
|
||||
def hello, do: "world"
|
||||
@@ -63,7 +65,11 @@ defmodule Module do
|
||||
end
|
||||
end
|
||||
|
||||
* `@behaviour` (notice the british spelling)
|
||||
defmodule B do
|
||||
@before_compile A
|
||||
end
|
||||
|
||||
* `@behaviour` (notice the British spelling)
|
||||
|
||||
Specify an OTP or user-defined behaviour.
|
||||
|
||||
@@ -177,7 +183,7 @@ defmodule Module do
|
||||
When just a module is provided, the function is assumed to be
|
||||
`__on_definition__/6`.
|
||||
|
||||
Note that you can\'t provide the current module to `@on_definition`
|
||||
Note that you can't provide the current module to `@on_definition`
|
||||
because the hook function will not be defined in time. Finally, since
|
||||
the `on_definition` hook is executed inside the context of the defined
|
||||
function (i.e. `env.function` returns the current function), the hook
|
||||
@@ -292,7 +298,7 @@ defmodule Module do
|
||||
|
||||
In addition to the above, you may also pass to `__info__/1` any atom supported
|
||||
by Erlang's `module_info` function which also gets defined for each compiled
|
||||
module. See http://erlang.org/doc/reference_manual/modules.html#id74571 for
|
||||
module. See http://erlang.org/doc/reference_manual/modules.html#id69430 for
|
||||
more information.
|
||||
"""
|
||||
def __info__(kind)
|
||||
@@ -346,13 +352,16 @@ defmodule Module do
|
||||
|
||||
def eval_quoted(module, quoted, binding, opts) do
|
||||
assert_not_compiled!(:eval_quoted, module)
|
||||
:elixir_module.eval_quoted(module, quoted, binding, opts)
|
||||
:elixir_def.reset_last(module)
|
||||
{ value, binding, _env, _scope } =
|
||||
:elixir.eval_quoted quoted, binding, Keyword.put(opts, :module, module)
|
||||
{ value, binding }
|
||||
end
|
||||
|
||||
@doc """
|
||||
Creates a module with the given name and given by
|
||||
Creates a module with the given name and defined by
|
||||
the given quoted expressions. The line where the module
|
||||
is defined and its file can be given as options.
|
||||
is defined and its file can be passed as options.
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -365,7 +374,7 @@ defmodule Module do
|
||||
|
||||
Hello.world #=> true
|
||||
|
||||
## Differences with `defmodule`
|
||||
## Differences from `defmodule`
|
||||
|
||||
`Module.create` works similarly to `defmodule` and
|
||||
return the same results. While one could also use
|
||||
@@ -385,13 +394,11 @@ defmodule Module do
|
||||
end
|
||||
|
||||
def create(module, quoted, opts) when is_atom(module) do
|
||||
line = Keyword.get(opts, :line, 1)
|
||||
:elixir_module.compile(line, module, quoted, [], :elixir.scope_for_eval(opts))
|
||||
:elixir_module.compile(module, quoted, [], :elixir.env_for_eval(opts))
|
||||
end
|
||||
|
||||
@doc """
|
||||
Concatenates the list of aliases and returns a new alias.
|
||||
It handles char lists, binaries and atoms.
|
||||
Concatenates a list of aliases and returns a new alias.
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -399,17 +406,15 @@ defmodule Module do
|
||||
Foo.Bar
|
||||
iex> Module.concat([Foo, "Bar"])
|
||||
Foo.Bar
|
||||
iex> Module.concat([Foo, 'Bar'])
|
||||
Foo.Bar
|
||||
|
||||
"""
|
||||
@spec concat([binary | atom]) :: atom
|
||||
def concat(list) when is_list(list) do
|
||||
:elixir_aliases.concat(list)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Concatenates the two given aliases and returns a new alias.
|
||||
It handles char lists, binaries and atoms.
|
||||
Concatenates two aliases and returns a new alias.
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -417,18 +422,17 @@ defmodule Module do
|
||||
Foo.Bar
|
||||
iex> Module.concat(Foo, "Bar")
|
||||
Foo.Bar
|
||||
iex> Module.concat(Foo, 'Bar')
|
||||
Foo.Bar
|
||||
|
||||
"""
|
||||
@spec concat(binary | atom, binary | atom) :: atom
|
||||
def concat(left, right) do
|
||||
:elixir_aliases.concat([left, right])
|
||||
end
|
||||
|
||||
@doc """
|
||||
Concatenates the list aliases and returns a new alias only
|
||||
Concatenates a list of aliases and returns a new alias only
|
||||
if the alias was already referenced. If the alias was not
|
||||
referenced yet, fails with ArgumentError.
|
||||
referenced yet, fails with `ArgumentError`.
|
||||
It handles char lists, binaries and atoms.
|
||||
|
||||
## Examples
|
||||
@@ -440,14 +444,15 @@ defmodule Module do
|
||||
List.Chars
|
||||
|
||||
"""
|
||||
@spec safe_concat([binary | atom]) :: atom | no_return
|
||||
def safe_concat(list) when is_list(list) do
|
||||
:elixir_aliases.safe_concat(list)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Concatenates the two aliases and returns a new alias only
|
||||
Concatenates two aliases and returns a new alias only
|
||||
if the alias was already referenced. If the alias was not
|
||||
referenced yet, fails with ArgumentError.
|
||||
referenced yet, fails with `ArgumentError`.
|
||||
It handles char lists, binaries and atoms.
|
||||
|
||||
## Examples
|
||||
@@ -459,6 +464,7 @@ defmodule Module do
|
||||
List.Chars
|
||||
|
||||
"""
|
||||
@spec safe_concat(binary | atom, binary | atom) :: atom | no_return
|
||||
def safe_concat(left, right) do
|
||||
:elixir_aliases.safe_concat([left, right])
|
||||
end
|
||||
@@ -479,7 +485,7 @@ defmodule Module do
|
||||
@doc """
|
||||
Attaches documentation to a given function or type. It expects
|
||||
the module the function/type belongs to, the line (a non negative
|
||||
integer), the kind (def or defmacro), a tuple representing
|
||||
integer), the kind (`def` or `defmacro`), a tuple representing
|
||||
the function and its arity, the function signature (the signature
|
||||
should be omitted for types) and the documentation, which should
|
||||
be either a binary or a boolean.
|
||||
@@ -578,7 +584,7 @@ defmodule Module do
|
||||
|
||||
@doc """
|
||||
Checks if the module defines the given function or macro.
|
||||
Use `defines?/3` to assert for an specific type.
|
||||
Use `defines?/3` to assert for a specific type.
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -596,8 +602,8 @@ defmodule Module do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Checks if the module defines a function or macro with the
|
||||
given `kind`. `kind` can be either `:def`, `:defp`,
|
||||
Checks if the module defines a function or macro of the
|
||||
given `kind`. `kind` can be any of `:def`, `:defp`,
|
||||
`:defmacro` or `:defmacrop`.
|
||||
|
||||
## Examples
|
||||
@@ -619,7 +625,7 @@ defmodule Module do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Return all functions defined in the given module.
|
||||
Return all functions defined in `module`.
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -636,7 +642,7 @@ defmodule Module do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns all functions defined in te given module according
|
||||
Returns all functions defined in `module`, according
|
||||
to its kind.
|
||||
|
||||
## Examples
|
||||
@@ -655,7 +661,7 @@ defmodule Module do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Makes the given functions in the given module overridable.
|
||||
Makes the given functions in `module` overridable.
|
||||
An overridable function is lazily defined, allowing a
|
||||
developer to customize it. See `Kernel.defoverridable` for
|
||||
more information and documentation.
|
||||
@@ -670,7 +676,12 @@ defmodule Module do
|
||||
raise "Cannot make function #{name}/#{arity} overridable because it was not defined"
|
||||
clause ->
|
||||
:elixir_def.delete_definition(module, tuple)
|
||||
neighbours = Module.DispatchTracker.yank(module, tuple)
|
||||
|
||||
neighbours = if loaded?(Module.LocalsTracker) do
|
||||
Module.LocalsTracker.yank(module, tuple)
|
||||
else
|
||||
[]
|
||||
end
|
||||
|
||||
old = get_attribute(module, :__overridable)
|
||||
merged = :orddict.update(tuple, fn({ count, _, _, _ }) ->
|
||||
@@ -683,10 +694,10 @@ defmodule Module do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns true if the given tuple in module is marked as overridable.
|
||||
Returns `true` if `tuple` in `module` is marked as overridable.
|
||||
"""
|
||||
def overridable?(module, tuple) do
|
||||
!! List.keyfind(get_attribute(module, :__overridable), tuple, 0)
|
||||
!!List.keyfind(get_attribute(module, :__overridable), tuple, 0)
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -736,7 +747,10 @@ defmodule Module do
|
||||
|
||||
Notice the third argument is used to indicate if a warning
|
||||
should be emitted when the attribute was not previously defined.
|
||||
This is true for `@foo` attributes but false for direct calls.
|
||||
`warn` may also be a stacktrace, which will be used in the
|
||||
warning. The default value for `warn` is false for direct calls
|
||||
but the `@foo` macro sets it to the proper stacktrace automatically,
|
||||
warning every time `@foo` is used but not set previously.
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -750,6 +764,7 @@ defmodule Module do
|
||||
end
|
||||
|
||||
"""
|
||||
@spec get_attribute(module, atom, warn :: boolean | [tuple]) :: term
|
||||
def get_attribute(module, key, warn // false) when is_atom(key) do
|
||||
assert_not_compiled!(:get_attribute, module)
|
||||
table = data_table_for(module)
|
||||
@@ -759,18 +774,22 @@ defmodule Module do
|
||||
[] ->
|
||||
acc = :ets.lookup_element(table, :__acc_attributes, 2)
|
||||
|
||||
if :lists.member(key, acc) do
|
||||
[]
|
||||
else
|
||||
warn && :elixir_errors.warn "#{Exception.format_caller} undefined module attribute @#{key}, " <>
|
||||
"please remove access to @#{key} or explicitly set it to nil before access\n"
|
||||
nil
|
||||
cond do
|
||||
:lists.member(key, acc) ->
|
||||
[]
|
||||
warn ->
|
||||
stack = is_list(warn) and warn
|
||||
:elixir_errors.warn "#{Exception.format_caller(stack)} undefined module attribute @#{key}, " <>
|
||||
"please remove access to @#{key} or explicitly set it to nil before access\n"
|
||||
nil
|
||||
true ->
|
||||
nil
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Deletes all attributes that matches the given key.
|
||||
Deletes all attributes that match the given key.
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -802,7 +821,7 @@ defmodule Module do
|
||||
* `:persist` - The attribute will be persisted in the Erlang
|
||||
Abstract Format. Useful when interfacing with Erlang libraries.
|
||||
|
||||
By default, both options are false.
|
||||
By default, both options are `false`.
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -927,4 +946,6 @@ defmodule Module do
|
||||
raise ArgumentError,
|
||||
message: "could not call #{fun} on module #{inspect module} because it was already compiled"
|
||||
end
|
||||
|
||||
defp loaded?(module), do: is_tuple :code.is_loaded(module)
|
||||
end
|
||||
|
||||
+60
-95
@@ -11,32 +11,27 @@
|
||||
#
|
||||
# We also have can the following vertices:
|
||||
#
|
||||
# * `Module` - a module that was invoked via an import or remotely
|
||||
# * `Module` - a module that was invoked via an import
|
||||
# * `{ name, arity }` - a local function/arity pair
|
||||
# * `{ :import, name, arity }` - an invoked function/arity import
|
||||
# * `{ :remote, name, arity }` - an remotely invoked function/arity
|
||||
#
|
||||
# Each of those vertices can associate to other vertices
|
||||
# as described below:
|
||||
#
|
||||
# * `Module`
|
||||
# * in neighbours: `{ :import, name, arity }` and `{ :remote, name arity }`
|
||||
# * in neighbours: `{ :import, name, arity }`
|
||||
#
|
||||
# * `{ name, arity }`
|
||||
# * in neighbours: `:local`, `{ name, arity }`
|
||||
# * out neighbours: `{ :import, name, arity }` and `{ :remote, name arity }`
|
||||
# * out neighbours: `{ :import, name, arity }`
|
||||
#
|
||||
# * `{ :import, name, arity }`
|
||||
# * in neighbours: `{ name, arity }`
|
||||
# * out neighbours: `Module`
|
||||
#
|
||||
# * `{ :remote, name, arity }`
|
||||
# * in neighbours: `{ name, arity }`
|
||||
# * out neighbours: `Module`
|
||||
#
|
||||
# Note that since this is required for bootstrap, we can't use
|
||||
# any of the `GenServer.Behaviour` conveniences.
|
||||
defmodule Module.DispatchTracker do
|
||||
defmodule Module.LocalsTracker do
|
||||
@moduledoc false
|
||||
|
||||
@timeout 30_000
|
||||
@@ -48,44 +43,9 @@ defmodule Module.DispatchTracker do
|
||||
|
||||
@type local :: { name, arity }
|
||||
@type import :: { :import, name, arity }
|
||||
@type remote :: { :remote, name, arity }
|
||||
|
||||
# Public API
|
||||
|
||||
@doc """
|
||||
Receives a dispatch or a module and returns all dispatches
|
||||
that calls it.
|
||||
|
||||
In case the argument is a module, the response will be
|
||||
made by import and remote dispatches.
|
||||
|
||||
In case the argument is another dispatch, the response
|
||||
will be made by local dispatches.
|
||||
|
||||
This function is not recursive, so if A dispatches to
|
||||
B which dispatches to C, A does not appear in the result,
|
||||
only B.
|
||||
"""
|
||||
@spec dispatches_to(ref, module) :: [import | remote]
|
||||
@spec dispatches_to(ref, local | import | remote) :: [local]
|
||||
def dispatches_to(ref, dispatch) do
|
||||
d = :gen_server.call(to_pid(ref), :digraph, @timeout)
|
||||
:digraph.in_neighbours(d, dispatch) |> only_tuples
|
||||
end
|
||||
|
||||
@doc """
|
||||
Receives a local and returns all dispatches from that local.
|
||||
|
||||
This function is not recursive, so if A dispatches to
|
||||
B which dispatches to C, C does not appear in the result,
|
||||
only B.
|
||||
"""
|
||||
@spec dispatches_from(ref, local) :: [local | import | remote]
|
||||
def dispatches_from(ref, { name, arity }) do
|
||||
d = :gen_server.call(to_pid(ref), :digraph, @timeout)
|
||||
:digraph.out_neighbours(d, { name, arity }) |> only_tuples
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns all imported modules that had the given
|
||||
`{ name, arity }` invoked.
|
||||
@@ -96,16 +56,6 @@ defmodule Module.DispatchTracker do
|
||||
:digraph.out_neighbours(d, { :import, name, arity })
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns all modules that had the given `{ name, arity }`
|
||||
invoked remotely.
|
||||
"""
|
||||
@spec remotes_with_dispatch(ref, name_arity) :: [module]
|
||||
def remotes_with_dispatch(ref, { name, arity }) do
|
||||
d = :gen_server.call(to_pid(ref), :digraph, @timeout)
|
||||
:digraph.out_neighbours(d, { :remote, name, arity })
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns all locals that are reachable.
|
||||
|
||||
@@ -130,14 +80,10 @@ defmodule Module.DispatchTracker do
|
||||
defp to_pid(pid) when is_pid(pid), do: pid
|
||||
defp to_pid(mod) when is_atom(mod) do
|
||||
table = :elixir_module.data_table(mod)
|
||||
[{ _, val }] = :ets.lookup(table, :__dispatch_tracker)
|
||||
[{ _, val }] = :ets.lookup(table, :__locals_tracker)
|
||||
val
|
||||
end
|
||||
|
||||
defp only_tuples(list) do
|
||||
lc x inlist list, is_tuple(x), do: x
|
||||
end
|
||||
|
||||
# Internal API
|
||||
|
||||
# Starts the tracker and returns its pid.
|
||||
@@ -173,16 +119,10 @@ defmodule Module.DispatchTracker do
|
||||
:gen_server.cast(pid, { :add_local, from, to })
|
||||
end
|
||||
|
||||
# Adds a remote dispatch to the given target.
|
||||
@doc false
|
||||
def add_remote(pid, function, module, target) when is_atom(module) and is_tuple(target) do
|
||||
:gen_server.cast(pid, { :add_external, :remote, function, module, target })
|
||||
end
|
||||
|
||||
# Adds a import dispatch to the given target.
|
||||
@doc false
|
||||
def add_import(pid, function, module, target) when is_atom(module) and is_tuple(target) do
|
||||
:gen_server.cast(pid, { :add_external, :import, function, module, target })
|
||||
:gen_server.cast(pid, { :add_import, function, module, target })
|
||||
end
|
||||
|
||||
# Yanks a local node. Returns its in and out vertices in a tuple.
|
||||
@@ -246,6 +186,16 @@ defmodule Module.DispatchTracker do
|
||||
end
|
||||
end
|
||||
|
||||
@doc false
|
||||
def cache_env(pid, env) do
|
||||
:gen_server.call(pid, { :cache_env, env }, @timeout)
|
||||
end
|
||||
|
||||
@doc false
|
||||
def get_cached_env(pid, ref) do
|
||||
:gen_server.call(pid, { :get_cached_env, ref }, @timeout)
|
||||
end
|
||||
|
||||
# Stops the gen server
|
||||
@doc false
|
||||
def stop(pid) do
|
||||
@@ -257,77 +207,92 @@ defmodule Module.DispatchTracker do
|
||||
def init([]) do
|
||||
d = :digraph.new([:protected])
|
||||
:digraph.add_vertex(d, :local)
|
||||
{ :ok, d }
|
||||
{ :ok, { d, [] } }
|
||||
end
|
||||
|
||||
def handle_call({ :yank, local }, _from, d) do
|
||||
def handle_call({ :cache_env, env }, _from, { d, cache }) do
|
||||
case cache do
|
||||
[{i,^env}|_] ->
|
||||
{ :reply, i, { d, cache } }
|
||||
t ->
|
||||
i = length(t)
|
||||
{ :reply, i, { d, [{i,env}|t] } }
|
||||
end
|
||||
end
|
||||
|
||||
def handle_call({ :get_cached_env, ref }, _from, { _, cache } = state) do
|
||||
{ ^ref, env } = :lists.keyfind(ref, 1, cache)
|
||||
{ :reply, env, state }
|
||||
end
|
||||
|
||||
def handle_call({ :yank, local }, _from, { d, _ } = state) do
|
||||
in_vertices = :digraph.in_neighbours(d, local)
|
||||
out_vertices = :digraph.out_neighbours(d, local)
|
||||
:digraph.del_vertex(d, local)
|
||||
{ :reply, { in_vertices, out_vertices }, d }
|
||||
{ :reply, { in_vertices, out_vertices }, state }
|
||||
end
|
||||
|
||||
def handle_call(:digraph, _from, d) do
|
||||
{ :reply, d, d }
|
||||
def handle_call(:digraph, _from, { d, _ } = state) do
|
||||
{ :reply, d, state }
|
||||
end
|
||||
|
||||
def handle_call(_request, _from, d) do
|
||||
{ :noreply, d }
|
||||
def handle_call(request, _from, state) do
|
||||
{ :stop, { :bad_call, request }, state }
|
||||
end
|
||||
|
||||
def handle_info(_msg, d) do
|
||||
{ :noreply, d }
|
||||
def handle_info(_msg, state) do
|
||||
{ :noreply, state }
|
||||
end
|
||||
|
||||
def handle_cast({ :add_local, from, to }, d) do
|
||||
def handle_cast({ :add_local, from, to }, { d, _ } = state) do
|
||||
handle_add_local(d, from, to)
|
||||
{ :noreply, d }
|
||||
{ :noreply, state }
|
||||
end
|
||||
|
||||
def handle_cast({ :add_external, kind, function, module, { name, arity } }, d) do
|
||||
handle_import_or_remote(d, kind, function, module, name, arity)
|
||||
{ :noreply, d }
|
||||
def handle_cast({ :add_import, function, module, { name, arity } }, { d, _ } = state) do
|
||||
handle_import(d, function, module, name, arity)
|
||||
{ :noreply, state }
|
||||
end
|
||||
|
||||
def handle_cast({ :add_definition, kind, tuple }, d) do
|
||||
def handle_cast({ :add_definition, kind, tuple }, { d, _ } = state) do
|
||||
handle_add_definition(d, kind, tuple)
|
||||
{ :noreply, d }
|
||||
{ :noreply, state }
|
||||
end
|
||||
|
||||
def handle_cast({ :add_defaults, kind, { name, arity }, defaults }, d) do
|
||||
def handle_cast({ :add_defaults, kind, { name, arity }, defaults }, { d, _ } = state) do
|
||||
lc i inlist :lists.seq(arity - defaults, arity - 1) do
|
||||
handle_add_definition(d, kind, { name, i })
|
||||
handle_add_local(d, { name, i }, { name, i + 1 })
|
||||
end
|
||||
{ :noreply, d }
|
||||
{ :noreply, state }
|
||||
end
|
||||
|
||||
def handle_cast({ :reattach, tuple, { in_neigh, out_neigh } }, d) do
|
||||
def handle_cast({ :reattach, tuple, { in_neigh, out_neigh } }, { d, _ } = state) do
|
||||
lc from inlist in_neigh, do: replace_edge(d, from, tuple)
|
||||
lc to inlist out_neigh, do: replace_edge(d, tuple, to)
|
||||
{ :noreply, d }
|
||||
{ :noreply, state }
|
||||
end
|
||||
|
||||
def handle_cast(:stop, d) do
|
||||
{ :stop, :normal, d }
|
||||
def handle_cast(:stop, state) do
|
||||
{ :stop, :normal, state }
|
||||
end
|
||||
|
||||
def handle_cast(_msg, d) do
|
||||
{ :noreply, d }
|
||||
def handle_cast(msg, state) do
|
||||
{ :stop, { :bad_cast, msg }, state }
|
||||
end
|
||||
|
||||
def terminate(_reason, _d) do
|
||||
def terminate(_reason, _state) do
|
||||
:ok
|
||||
end
|
||||
|
||||
def code_change(_old, d, _extra) do
|
||||
{ :ok, d }
|
||||
def code_change(_old, state, _extra) do
|
||||
{ :ok, state }
|
||||
end
|
||||
|
||||
defp handle_import_or_remote(d, kind, function, module, name, arity) do
|
||||
defp handle_import(d, function, module, name, arity) do
|
||||
:digraph.add_vertex(d, module)
|
||||
|
||||
tuple = { kind, name, arity }
|
||||
tuple = { :import, name, arity }
|
||||
:digraph.add_vertex(d, tuple)
|
||||
replace_edge!(d, tuple, module)
|
||||
|
||||
@@ -33,7 +33,7 @@ defmodule OptionParser do
|
||||
|
||||
Extra information about switches can be given as arguments, too.
|
||||
This is useful when a switch must behave as a boolean
|
||||
or if duplicated switches should be kept, overriden or accumulated.
|
||||
or if duplicated switches should be kept, overridden or accumulated.
|
||||
|
||||
The following types are supported:
|
||||
|
||||
|
||||
@@ -264,8 +264,8 @@ defmodule Process do
|
||||
|
||||
@doc """
|
||||
Associates the name with a pid or a port identifier. name, which must
|
||||
be an atom, can be used instead of the pid / port identifier in the
|
||||
send operator (name <- message).
|
||||
be an atom, can be used instead of the pid / port identifier with the
|
||||
`Kernel.send/2` function.
|
||||
|
||||
See http://www.erlang.org/doc/man/erlang.html#register-2 for more info.
|
||||
"""
|
||||
|
||||
+17
-15
@@ -22,10 +22,6 @@ defmodule Protocol do
|
||||
@functions []
|
||||
@fallback_to_any false
|
||||
|
||||
# Deprecated
|
||||
@only nil
|
||||
@except nil
|
||||
|
||||
# Invoke the user given block
|
||||
unquote(block)
|
||||
|
||||
@@ -71,7 +67,8 @@ defmodule Protocol do
|
||||
# Inline both helpers
|
||||
@compile { :inline, any_impl_for: 0, rec_impl_for: 1 }
|
||||
|
||||
unless Kernel.Typespec.defines_type?(__MODULE__, :t, 0) do
|
||||
if :code.ensure_loaded(Kernel.Typespec) == { :module, Kernel.Typespec } and
|
||||
not Kernel.Typespec.defines_type?(__MODULE__, :t, 0) do
|
||||
@type t :: term
|
||||
end
|
||||
|
||||
@@ -207,7 +204,7 @@ defmodule Protocol do
|
||||
# Defines an implementation with fallback to the given module.
|
||||
defp impl_with_fallback(mod, guard, current, arg) do
|
||||
quote do
|
||||
{ unquote(guard)(unquote(arg)),
|
||||
{ :erlang.unquote(guard)(unquote(arg)),
|
||||
unquote(with_fallback(Module.concat(current, mod))) }
|
||||
end
|
||||
end
|
||||
@@ -261,7 +258,7 @@ defmodule Protocol.DSL do
|
||||
|
||||
# Convert the spec to callback if possible,
|
||||
# otherwise generate a dummy callback
|
||||
Protocol.DSL.callback_from_spec(__MODULE__, name, arity) ||
|
||||
Protocol.DSL.__spec__?(__MODULE__, name, arity) ||
|
||||
@callback unquote(name)(unquote_splicing(type_args)) :: term
|
||||
end
|
||||
end
|
||||
@@ -271,15 +268,20 @@ defmodule Protocol.DSL do
|
||||
end
|
||||
|
||||
@doc false
|
||||
def callback_from_spec(module, name, arity) do
|
||||
tuple = { name, arity }
|
||||
specs = Module.get_attribute(module, :spec)
|
||||
def __spec__?(module, name, arity) do
|
||||
case :code.ensure_loaded(Kernel.Typespec) do
|
||||
{ :module, Kernel.Typespec } ->
|
||||
tuple = { name, arity }
|
||||
specs = Module.get_attribute(module, :spec)
|
||||
|
||||
found = lc { k, v } inlist specs, k == tuple do
|
||||
Kernel.Typespec.define_callback(module, tuple, v)
|
||||
true
|
||||
found = lc { k, v } inlist specs, k == tuple do
|
||||
Kernel.Typespec.define_callback(module, tuple, v)
|
||||
true
|
||||
end
|
||||
|
||||
found != []
|
||||
{ :error, _ } ->
|
||||
true
|
||||
end
|
||||
|
||||
found != []
|
||||
end
|
||||
end
|
||||
|
||||
+42
-33
@@ -6,9 +6,9 @@ end
|
||||
|
||||
defprotocol Range.Iterator do
|
||||
@doc """
|
||||
Reduces the range based on the type of the first argument.
|
||||
Returns the function that calculates the next item.
|
||||
"""
|
||||
def reduce(first, range, acc, fun)
|
||||
def next(first, range)
|
||||
|
||||
@doc """
|
||||
Count how many items are in the range.
|
||||
@@ -17,56 +17,65 @@ defprotocol Range.Iterator do
|
||||
end
|
||||
|
||||
defimpl Enumerable, for: Range do
|
||||
def reduce(first .. _ = range, acc, fun) do
|
||||
Range.Iterator.reduce(first, range, acc, fun)
|
||||
def reduce(first .. last = range, acc, fun) do
|
||||
reduce(first, last, acc, fun, Range.Iterator.next(first, range), last >= first)
|
||||
end
|
||||
|
||||
defp reduce(_x, _y, { :halt, acc }, _fun, _next, _up) do
|
||||
{ :halted, acc }
|
||||
end
|
||||
|
||||
defp reduce(x, y, { :suspend, acc }, fun, next, up) do
|
||||
{ :suspended, acc, &reduce(x, y, &1, fun, next, up) }
|
||||
end
|
||||
|
||||
defp reduce(x, y, { :cont, acc }, fun, next, true) when x <= y do
|
||||
reduce(next.(x), y, fun.(x, acc), fun, next, true)
|
||||
end
|
||||
|
||||
defp reduce(x, y, { :cont, acc }, fun, next, false) when x >= y do
|
||||
reduce(next.(x), y, fun.(x, acc), fun, next, false)
|
||||
end
|
||||
|
||||
defp reduce(_, _, { :cont, acc }, _fun, _next, _up) do
|
||||
{ :done, acc }
|
||||
end
|
||||
|
||||
def member?(first .. last, value) do
|
||||
first <= value and value <= last
|
||||
if first <= last do
|
||||
{ :ok, first <= value and value <= last }
|
||||
else
|
||||
{ :ok, last <= value and value <= first }
|
||||
end
|
||||
end
|
||||
|
||||
def count(first .. _ = range) do
|
||||
Range.Iterator.count(first, range)
|
||||
{ :ok, Range.Iterator.count(first, range) }
|
||||
end
|
||||
end
|
||||
|
||||
defimpl Range.Iterator, for: Integer do
|
||||
def reduce(first, Range[last: last], acc, fun) when is_integer(last) do
|
||||
reducer = if last >= first do
|
||||
fn(acc, fun) -> do_reducer_up(first, last, acc, fun) end
|
||||
def next(first, Range[last: last]) when is_integer(last) do
|
||||
if last >= first do
|
||||
&(&1 + 1)
|
||||
else
|
||||
fn(acc, fun) -> do_reducer_down(first, last, acc, fun) end
|
||||
&(&1 - 1)
|
||||
end
|
||||
Enumerable.Function.reduce(reducer, acc, fun)
|
||||
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(last) and last >= first do
|
||||
last - first + 1
|
||||
end
|
||||
|
||||
def count(first, Range[last: last]) when is_integer(last) do
|
||||
first - last + 1
|
||||
if last >= first do
|
||||
last - first + 1
|
||||
else
|
||||
first - last + 1
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defimpl Inspect, for: Range do
|
||||
import Inspect.Algebra
|
||||
|
||||
def inspect(Range[first: first, last: last], opts) do
|
||||
Inspect.Algebra.concat [Kernel.inspect(first, opts), "..", Kernel.inspect(last, opts)]
|
||||
concat [to_doc(first, opts), "..", to_doc(last, opts)]
|
||||
end
|
||||
end
|
||||
|
||||
+64
-48
@@ -96,9 +96,9 @@ defmodule Record do
|
||||
|
||||
Notice that now since the record definition is accessible, Elixir
|
||||
shows the record nicely formatted, no longer as a simple tuple. We
|
||||
can get the raw formatting by passing `raw: true` to `inspect`:
|
||||
can get the raw formatting by passing `records: false` to `inspect`:
|
||||
|
||||
inspect user(), raw: true
|
||||
inspect user(), records: false
|
||||
{ User, "José", 25 }
|
||||
|
||||
Since working with external records is common, Elixir allows
|
||||
@@ -150,7 +150,7 @@ defmodule Record do
|
||||
whenever you need to dynamically set or update fields.
|
||||
|
||||
The standard library contains excellent examples of both use cases,
|
||||
with [`HashDict`](HashDict.html) being implemented with `defrecordp`
|
||||
with [`HashDict`](HashDict.html) being implemented with `defrecordp`
|
||||
and [`Range`](Range.html) with `defrecord`.
|
||||
|
||||
You can learn more about records in the `Kernel.defrecord/3` docs. Now
|
||||
@@ -188,16 +188,16 @@ defmodule Record do
|
||||
@type t :: tuple
|
||||
|
||||
@doc """
|
||||
Extract record information from an Erlang file.
|
||||
|
||||
Extract record information from an Erlang file.
|
||||
|
||||
Returns the fields as a list of tuples.
|
||||
|
||||
## Examples
|
||||
|
||||
> Record.extract(:file_info, from_lib: "kernel/include/file.hrl")
|
||||
[size: :undefined, type: :undefined, access: :undefined, atime: :undefined,
|
||||
mtime: :undefined, ctime: :undefined, mode: :undefined, links: :undefined,
|
||||
major_device: :undefined, minor_device: :undefined, inode: :undefined,
|
||||
Record.extract(:file_info, from_lib: "kernel/include/file.hrl")
|
||||
#=> [size: :undefined, type: :undefined, access: :undefined, atime: :undefined,
|
||||
mtime: :undefined, ctime: :undefined, mode: :undefined, links: :undefined,
|
||||
major_device: :undefined, minor_device: :undefined, inode: :undefined,
|
||||
uid: :undefined, gid: :undefined]
|
||||
|
||||
defrecord FileInfo, Record.extract(:file_info, from_lib: "kernel/include/file.hrl")
|
||||
@@ -268,30 +268,52 @@ defmodule Record do
|
||||
defp record_check!(_), do: :ok
|
||||
|
||||
@doc false
|
||||
def defrecordp(name, tag, fields) when is_atom(name) and is_atom(tag) and is_list(fields) do
|
||||
{ fields, types, def_type } = recordp_split(fields, [], [], false)
|
||||
type = binary_to_atom(atom_to_binary(name) <> "_t")
|
||||
tag = tag || name
|
||||
def defrecordp(name, tag, fields) do
|
||||
case recordp_split(fields, [], [], false) do
|
||||
{ :ok, fields, types, def_type } ->
|
||||
types = Macro.escape(types)
|
||||
|
||||
quote do
|
||||
Record.defmacros(unquote(name), unquote(fields), __ENV__, unquote(tag))
|
||||
# bind_quoted isn't available when bootstrapping record
|
||||
quoted = quote [unquote: false] do
|
||||
Record.defmacros(name, fields, __ENV__, tag)
|
||||
|
||||
if unquote(def_type) do
|
||||
@typep unquote(type)() :: { unquote(tag), unquote_splicing(types) }
|
||||
end
|
||||
if def_type do
|
||||
type = binary_to_atom(atom_to_binary(name) <> "_t")
|
||||
@typep unquote(type)() :: { unquote(tag || name), unquote_splicing(types) }
|
||||
end
|
||||
end
|
||||
|
||||
quote do
|
||||
def_type = unquote(def_type)
|
||||
fields = unquote(fields)
|
||||
types = unquote(types)
|
||||
tag = unquote(tag)
|
||||
name = unquote(name)
|
||||
unquote(quoted)
|
||||
end
|
||||
|
||||
:error ->
|
||||
quote do
|
||||
name = unquote(name)
|
||||
Record.defmacros(name, unquote(fields), __ENV__, unquote(tag))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defp recordp_split([{ field, { :::, _, [default, type] }}|t], defaults, types, _) do
|
||||
recordp_split t, [{ field, default }|defaults], [type|types], true
|
||||
recordp_split(t, [{ field, default }|defaults], [type|types], true)
|
||||
end
|
||||
|
||||
defp recordp_split([other|t], defaults, types, def_type) do
|
||||
recordp_split t, [other|defaults], [quote(do: term)|types], def_type
|
||||
recordp_split(t, [other|defaults], [quote(do: term)|types], def_type)
|
||||
end
|
||||
|
||||
defp recordp_split([], defaults, types, def_type) do
|
||||
{ :lists.reverse(defaults), :lists.reverse(types), def_type }
|
||||
{ :ok, :lists.reverse(defaults), :lists.reverse(types), def_type }
|
||||
end
|
||||
|
||||
defp recordp_split(_, _, _, _) do
|
||||
:error
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -347,16 +369,20 @@ defmodule Record do
|
||||
accessor_specs(values, 1, [])
|
||||
]
|
||||
|
||||
if env == Macro.Env do
|
||||
Module.eval_quoted(env, contents, [], [])
|
||||
else
|
||||
Module.eval_quoted(env.module, contents, [], env.location)
|
||||
# We need to handle bootstraping
|
||||
cond do
|
||||
:code.ensure_loaded(Kernel.Typespec) != { :module, Kernel.Typespec } ->
|
||||
nil
|
||||
env == Macro.Env ->
|
||||
Module.eval_quoted(env, contents, [], [])
|
||||
true ->
|
||||
Module.eval_quoted(env.module, contents, [], env.location)
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Define macros for manipulating records.
|
||||
|
||||
Define macros for manipulating records.
|
||||
|
||||
This is called
|
||||
directly by `Kernel.defrecordp/3`. It expects the macro name, the
|
||||
record values and the environment.
|
||||
@@ -368,7 +394,9 @@ defmodule Record do
|
||||
end
|
||||
|
||||
"""
|
||||
def defmacros(name, values, env, tag // nil) do
|
||||
def defmacros(name, values, env, tag // nil)
|
||||
when is_atom(name) and is_list(values) and is_atom(tag) do
|
||||
|
||||
escaped = lc value inlist values do
|
||||
{ key, value } = convert_value(value)
|
||||
{ key, Macro.escape(value) }
|
||||
@@ -408,7 +436,7 @@ defmodule Record do
|
||||
end
|
||||
|
||||
# Store fields that can be optimized and that cannot be
|
||||
# optimized as they are overriden
|
||||
# optimized as they are overridden
|
||||
@doc false
|
||||
def __on_definition__(env, kind, name, args, _guards, _body) do
|
||||
tuple = { name, length(args) }
|
||||
@@ -459,10 +487,10 @@ defmodule Record do
|
||||
|
||||
case remaining do
|
||||
[] ->
|
||||
quote do: { unquote_splicing([atom|match]) }
|
||||
{ :{}, [], [atom|match] }
|
||||
_ ->
|
||||
keys = lc { key, _ } inlist remaining, do: key
|
||||
raise ArgumentError, message: "record #{inspect atom} does not have the keys: #{inspect keys}"
|
||||
raise ArgumentError, message: "record #{inspect atom} does not have the key: #{inspect hd(keys)}"
|
||||
end
|
||||
end
|
||||
|
||||
@@ -685,7 +713,7 @@ defmodule Record do
|
||||
quote do
|
||||
@doc false
|
||||
def to_keywords(record) do
|
||||
unquote(:orddict.from_list(sorted))
|
||||
unquote(sorted)
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -780,11 +808,11 @@ defmodule Record do
|
||||
end
|
||||
|
||||
defp updater_lookup(k, key, values) do
|
||||
v = find_index(values, key, 1)
|
||||
index = find_index(values, key, 0)
|
||||
|
||||
quote do
|
||||
case :lists.keyfind(unquote(k), 1, keywords) do
|
||||
false -> elem(record, unquote(v))
|
||||
false -> :erlang.element(unquote(index + 2), record)
|
||||
{_, value} -> value
|
||||
end
|
||||
end
|
||||
@@ -807,8 +835,7 @@ defmodule Record do
|
||||
|
||||
defp core_specs(values) do
|
||||
types = lc { _, _, spec } inlist values, do: spec
|
||||
options = if values == [], do: [], else: [options_specs(values)]
|
||||
values_specs = if values == [], do: [], else: values_specs(values)
|
||||
options = lc { k, _, v } inlist values, do: { k, v }
|
||||
|
||||
quote do
|
||||
unless Kernel.Typespec.defines_type?(__MODULE__, :t, 0) do
|
||||
@@ -816,7 +843,7 @@ defmodule Record do
|
||||
end
|
||||
|
||||
unless Kernel.Typespec.defines_type?(__MODULE__, :options, 0) do
|
||||
@type options :: unquote(options) | [{String.t, unquote(values_specs)}]
|
||||
@type options :: unquote(options) | [{String.t, term}]
|
||||
end
|
||||
|
||||
@spec new :: t
|
||||
@@ -829,17 +856,6 @@ defmodule Record do
|
||||
end
|
||||
end
|
||||
|
||||
defp options_specs([{ k, _, v }|t]) do
|
||||
:lists.foldl fn { k, _, v }, acc ->
|
||||
{ :|, [], [{ k, v }, acc] }
|
||||
end, { k, v }, t
|
||||
end
|
||||
defp values_specs([{ _, _, v }|t]) do
|
||||
:lists.foldl fn { _, _, v }, acc ->
|
||||
{ :|, [], [v, acc] }
|
||||
end, v, t
|
||||
end
|
||||
|
||||
defp accessor_specs([{ :__exception__, _, _ }|t], 1, acc) do
|
||||
accessor_specs(t, 2, acc)
|
||||
end
|
||||
|
||||
@@ -6,10 +6,11 @@ defmodule Record.Extractor do
|
||||
def retrieve(name, from: file) when is_binary(file) do
|
||||
file = String.to_char_list!(file)
|
||||
|
||||
case :code.where_is_file(file) do
|
||||
:non_existing -> realfile = file
|
||||
realfile -> :ok
|
||||
end
|
||||
realfile =
|
||||
case :code.where_is_file(file) do
|
||||
:non_existing -> file
|
||||
realfile -> realfile
|
||||
end
|
||||
|
||||
retrieve_record(name, realfile)
|
||||
end
|
||||
|
||||
@@ -4,8 +4,8 @@ defmodule Regex do
|
||||
in the Erlang Standard Library. More information can be found
|
||||
in the [`re` documentation](http://www.erlang.org/doc/man/re.html).
|
||||
|
||||
Regular expressions in Elixir can be created using `Regex.compile!`
|
||||
or using the special form with `%r`:
|
||||
Regular expressions in Elixir can be created using `Regex.compile!/2`
|
||||
or using the special form with [`%r`](Kernel.html#sigil_r/2):
|
||||
|
||||
# A simple regular expressions that matches foo anywhere in the string
|
||||
%r/foo/
|
||||
|
||||
+851
-286
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,188 @@
|
||||
defmodule Stream.Reducers do
|
||||
# Collection of reducers shared by Enum and Stream.
|
||||
@moduledoc false
|
||||
|
||||
defmacro chunk(n, step, limit, f // nil) do
|
||||
quote do
|
||||
fn entry, acc(h, { buffer, count }, t) ->
|
||||
buffer = [entry|buffer]
|
||||
count = count + 1
|
||||
|
||||
new =
|
||||
if count >= unquote(limit) do
|
||||
left = count - unquote(step)
|
||||
{ Enum.take(buffer, left), left }
|
||||
else
|
||||
{ buffer, count }
|
||||
end
|
||||
|
||||
if count == unquote(n) do
|
||||
cont_with_acc(unquote(f), :lists.reverse(buffer), h, new, t)
|
||||
else
|
||||
{ :cont, acc(h, new, t) }
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defmacro chunk_by(callback, f // nil) do
|
||||
quote do
|
||||
fn
|
||||
entry, acc(h, { buffer, value }, t) ->
|
||||
new_value = unquote(callback).(entry)
|
||||
if new_value == value do
|
||||
{ :cont, acc(h, { [entry|buffer], value }, t) }
|
||||
else
|
||||
cont_with_acc(unquote(f), :lists.reverse(buffer), h, { [entry], new_value }, t)
|
||||
end
|
||||
entry, acc(h, nil, t) ->
|
||||
{ :cont, acc(h, { [entry], unquote(callback).(entry) }, t) }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defmacro drop(f // nil) do
|
||||
quote do
|
||||
fn
|
||||
_entry, acc(h, n, t) when n > 0 ->
|
||||
{ :cont, acc(h, n-1, t) }
|
||||
entry, acc(h, n, t) ->
|
||||
cont_with_acc(unquote(f), entry, h, n, t)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defmacro drop_while(callback, f // nil) do
|
||||
quote do
|
||||
fn entry, acc(h, bool, t) = orig ->
|
||||
if bool and unquote(callback).(entry) do
|
||||
{ :cont, orig }
|
||||
else
|
||||
cont_with_acc(unquote(f), entry, h, false, t)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defmacro filter(callback, f // nil) do
|
||||
quote do
|
||||
fn(entry, acc) ->
|
||||
if unquote(callback).(entry) do
|
||||
cont(unquote(f), entry, acc)
|
||||
else
|
||||
{ :cont, acc }
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defmacro filter_map(filter, mapper, f // nil) do
|
||||
quote do
|
||||
fn(entry, acc) ->
|
||||
if unquote(filter).(entry) do
|
||||
cont(unquote(f), unquote(mapper).(entry), acc)
|
||||
else
|
||||
{ :cont, acc }
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defmacro map(callback, f // nil) do
|
||||
quote do
|
||||
fn(entry, acc) ->
|
||||
cont(unquote(f), unquote(callback).(entry), acc)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defmacro reject(callback, f // nil) do
|
||||
quote do
|
||||
fn(entry, acc) ->
|
||||
unless unquote(callback).(entry) do
|
||||
cont(unquote(f), entry, acc)
|
||||
else
|
||||
{ :cont, acc }
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defmacro scan_2(callback, f // nil) do
|
||||
quote do
|
||||
fn
|
||||
entry, acc(h, :first, t) ->
|
||||
cont_with_acc(unquote(f), entry, h, { :ok, entry }, t)
|
||||
entry, acc(h, { :ok, acc }, t) ->
|
||||
value = unquote(callback).(entry, acc)
|
||||
cont_with_acc(unquote(f), value, h, { :ok, value }, t)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defmacro scan_3(callback, f // nil) do
|
||||
quote do
|
||||
fn(entry, acc(h, acc, t)) ->
|
||||
value = unquote(callback).(entry, acc)
|
||||
cont_with_acc(unquote(f), value, h, value, t)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defmacro take(f // nil) do
|
||||
quote do
|
||||
fn(entry, acc(h, n, t) = orig) ->
|
||||
if n >= 1 do
|
||||
cont_with_acc(unquote(f), entry, h, n-1, t)
|
||||
else
|
||||
{ :halt, orig }
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defmacro take_every(nth, f // nil) do
|
||||
quote do
|
||||
fn
|
||||
entry, acc(h, n, t) when n === :first
|
||||
when n === unquote(nth) ->
|
||||
cont_with_acc(unquote(f), entry, h, 1, t)
|
||||
entry, acc(h, n, t) ->
|
||||
{ :cont, acc(h, n+1, t) }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defmacro take_while(callback, f // nil) do
|
||||
quote do
|
||||
fn(entry, acc) ->
|
||||
if unquote(callback).(entry) do
|
||||
cont(unquote(f), entry, acc)
|
||||
else
|
||||
{ :halt, acc }
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defmacro uniq(callback, f // nil) do
|
||||
quote do
|
||||
fn(entry, acc(h, prev, t) = acc) ->
|
||||
value = unquote(callback).(entry)
|
||||
if :lists.member(value, prev) do
|
||||
{ :cont, acc }
|
||||
else
|
||||
cont_with_acc(unquote(f), entry, h, [value|prev], t)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defmacro with_index(f // nil) do
|
||||
quote do
|
||||
fn(entry, acc(h, counter, t)) ->
|
||||
cont_with_acc(unquote(f), { entry, counter }, h, counter + 1, t)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
+19
-16
@@ -149,7 +149,7 @@ defmodule String do
|
||||
def printable?(<<?\a, t :: binary>>), do: printable?(t)
|
||||
|
||||
def printable?(<<>>), do: true
|
||||
def printable?(_), do: false
|
||||
def printable?(b) when is_binary(b), do: false
|
||||
|
||||
@doc """
|
||||
Divides a string into substrings at each Unicode whitespace
|
||||
@@ -497,7 +497,7 @@ defmodule String do
|
||||
"a-b,c"
|
||||
|
||||
The pattern can also be a regex. In those cases, one can give `\N`
|
||||
in the `replacement` string to access a specific catpure in the regex:
|
||||
in the `replacement` string to access a specific capture in the regex:
|
||||
|
||||
iex> String.replace("a,b,c", %r/,(.)/, ",\\1\\1")
|
||||
"a,bb,cc"
|
||||
@@ -562,7 +562,7 @@ defmodule String do
|
||||
do_reverse(next_grapheme(rest), [grapheme|acc])
|
||||
end
|
||||
|
||||
defp do_reverse(:no_grapheme, acc), do: iolist_to_binary(acc)
|
||||
defp do_reverse(nil, acc), do: iolist_to_binary(acc)
|
||||
|
||||
@doc """
|
||||
Returns a binary `subject` duplicated `n` times.
|
||||
@@ -602,7 +602,7 @@ defmodule String do
|
||||
Returns the next codepoint in a String.
|
||||
|
||||
The result is a tuple with the codepoint and the
|
||||
remaining of the string or `:no_codepoint` in case
|
||||
remaining of the string or `nil` in case
|
||||
the string reached its end.
|
||||
|
||||
As with other functions in the String module, this
|
||||
@@ -617,7 +617,7 @@ defmodule String do
|
||||
|
||||
"""
|
||||
@compile { :inline, next_codepoint: 1 }
|
||||
@spec next_codepoint(t) :: {codepoint, t} | :no_codepoint
|
||||
@spec next_codepoint(t) :: {codepoint, t} | nil
|
||||
defdelegate next_codepoint(string), to: String.Unicode
|
||||
|
||||
@doc %S"""
|
||||
@@ -693,7 +693,7 @@ defmodule String do
|
||||
Returns the next grapheme in a String.
|
||||
|
||||
The result is a tuple with the grapheme and the
|
||||
remaining of the string or `:no_grapheme` in case
|
||||
remaining of the string or `nil` in case
|
||||
the String reached its end.
|
||||
|
||||
## Examples
|
||||
@@ -703,7 +703,7 @@ defmodule String do
|
||||
|
||||
"""
|
||||
@compile { :inline, next_grapheme: 1 }
|
||||
@spec next_grapheme(t) :: { grapheme, t } | :no_grapheme
|
||||
@spec next_grapheme(t) :: { grapheme, t } | nil
|
||||
defdelegate next_grapheme(string), to: String.Graphemes
|
||||
|
||||
@doc """
|
||||
@@ -722,7 +722,7 @@ defmodule String do
|
||||
def first(string) do
|
||||
case next_grapheme(string) do
|
||||
{ char, _ } -> char
|
||||
:no_grapheme -> nil
|
||||
nil -> nil
|
||||
end
|
||||
end
|
||||
|
||||
@@ -747,7 +747,7 @@ defmodule String do
|
||||
do_last(next_grapheme(rest), char)
|
||||
end
|
||||
|
||||
defp do_last(:no_grapheme, last_char), do: last_char
|
||||
defp do_last(nil, last_char), do: last_char
|
||||
|
||||
@doc """
|
||||
Returns the number of unicode graphemes in an utf8 string.
|
||||
@@ -769,7 +769,7 @@ defmodule String do
|
||||
1 + do_length(next_grapheme(rest))
|
||||
end
|
||||
|
||||
defp do_length(:no_grapheme), do: 0
|
||||
defp do_length(nil), do: 0
|
||||
|
||||
@doc """
|
||||
Returns the grapheme in the `position` of the given utf8 `string`.
|
||||
@@ -811,7 +811,7 @@ defmodule String do
|
||||
char
|
||||
end
|
||||
|
||||
defp do_at(:no_grapheme, _, _), do: nil
|
||||
defp do_at(nil, _, _), do: nil
|
||||
|
||||
@doc """
|
||||
Returns a substring starting at the offset given by the first, and
|
||||
@@ -934,11 +934,11 @@ defmodule String do
|
||||
acc <> char
|
||||
end
|
||||
|
||||
defp do_slice(:no_grapheme, start_pos, _, current_pos, acc) when start_pos == current_pos do
|
||||
defp do_slice(nil, start_pos, _, current_pos, acc) when start_pos == current_pos do
|
||||
acc
|
||||
end
|
||||
|
||||
defp do_slice(:no_grapheme, _, _, _, acc) do
|
||||
defp do_slice(nil, _, _, _, acc) do
|
||||
case acc do
|
||||
"" -> nil
|
||||
_ -> acc
|
||||
@@ -1056,9 +1056,12 @@ defmodule String do
|
||||
raise ArgumentError
|
||||
end
|
||||
|
||||
defexception UnicodeConversionError, encoded: nil, rest: nil, kind: nil do
|
||||
def message(exception) do
|
||||
"#{exception.kind} #{detail(exception.rest)}"
|
||||
defexception UnicodeConversionError, [:encoded, :message] do
|
||||
def exception(opts) do
|
||||
UnicodeConversionError[
|
||||
encoded: opts[:encoded],
|
||||
message: "#{opts[:kind]} #{detail(opts[:rest])}"
|
||||
]
|
||||
end
|
||||
|
||||
defp detail(rest) when is_binary(rest) do
|
||||
|
||||
@@ -28,8 +28,8 @@ defmodule System do
|
||||
|
||||
# Read and strip the version from the `VERSION` file.
|
||||
defmacrop get_version do
|
||||
case read_stripped("VERSION") do
|
||||
"" -> raise CompileError, message: "could not read the version number from VERSION"
|
||||
case read_stripped(:filename.join(__DIR__, "../../../VERSION")) do
|
||||
"" -> raise ArgumentError, message: "could not read the version number from VERSION"
|
||||
data -> data
|
||||
end
|
||||
end
|
||||
@@ -38,7 +38,7 @@ defmodule System do
|
||||
# the most recent tag. If that is not available, tries to read the commit hash
|
||||
# from .git/HEAD. If that fails, returns an empty string.
|
||||
defmacrop get_describe do
|
||||
dirpath = ".git"
|
||||
dirpath = :filename.join(__DIR__, "../../../.git")
|
||||
case :file.read_file_info(dirpath) do
|
||||
{ :ok, _ } ->
|
||||
if :os.find_executable('git') do
|
||||
|
||||
+79
-32
@@ -31,8 +31,18 @@ defmodule URI do
|
||||
|
||||
@doc """
|
||||
Returns the default port for a given scheme.
|
||||
|
||||
If the scheme is unknown to URI, returns `nil`.
|
||||
Any scheme may be registered via `default_port/2`.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> URI.default_port("ftp")
|
||||
21
|
||||
|
||||
iex> URI.default_port("ponzi")
|
||||
nil
|
||||
|
||||
"""
|
||||
def default_port(scheme) when is_binary(scheme) do
|
||||
{ :ok, dict } = :application.get_env(:elixir, :uri)
|
||||
@@ -48,20 +58,43 @@ defmodule URI do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Encodes an enumerable into a query string.
|
||||
|
||||
Takes an enumerable (containing a sequence of two-item tuples)
|
||||
and returns a string of the form "k=v&k2=v2..." where keys and values are
|
||||
URL encoded as per `encode/1`. Keys and values can be any term
|
||||
that implements the `String.Chars` protocol (i.e. can be converted
|
||||
and returns a string of the form "key1=value1&key2=value2..." where
|
||||
keys and values are URL encoded as per `encode/1`. Keys and values can
|
||||
be any term that implements the `String.Chars` protocol (i.e. can be converted
|
||||
to a binary).
|
||||
|
||||
## Examples
|
||||
|
||||
iex> hd = HashDict.new([{"foo", 1}, {"bar", "2"}])
|
||||
iex> URI.encode_query(hd)
|
||||
"bar=2&foo=1"
|
||||
|
||||
"""
|
||||
def encode_query(l), do: Enum.map_join(l, "&", &pair/1)
|
||||
|
||||
@doc """
|
||||
Given a query string of the form "key1=value1&key=value2...", produces an
|
||||
orddict with one entry for each key-value pair. Each key and value will be a
|
||||
binary. It also does percent-unescaping of both keys and values.
|
||||
Decodes a query string into a `HashDict`.
|
||||
|
||||
Given a query string of the form "key1=value1&key2=value2...", produces a
|
||||
`HashDict` with one entry for each key-value pair. Each key and value will be a
|
||||
binary. Keys and values will be percent-unescaped.
|
||||
|
||||
Use `query_decoder/1` if you want to iterate over each value manually.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> URI.decode_query("foo=1&bar=2") |> Dict.to_list
|
||||
[{"bar", "2"}, {"foo", "1"}]
|
||||
|
||||
iex> hd = HashDict.new()
|
||||
iex> URI.decode_query("foo=1&bar=2", hd) |> HashDict.keys
|
||||
["bar", "foo"]
|
||||
iex> URI.decode_query("foo=1&bar=2", hd) |> HashDict.values
|
||||
["2", "1"]
|
||||
|
||||
"""
|
||||
def decode_query(q, dict // HashDict.new) when is_binary(q) do
|
||||
Enum.reduce query_decoder(q), dict, fn({ k, v }, acc) -> Dict.put(acc, k, v) end
|
||||
@@ -70,22 +103,26 @@ defmodule URI do
|
||||
@doc """
|
||||
Returns an iterator function over the query string that decodes
|
||||
the query string in steps.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> URI.query_decoder("foo=1&bar=2") |> Enum.map &(&1)
|
||||
[{"foo", "1"}, {"bar", "2"}]
|
||||
|
||||
"""
|
||||
def query_decoder(q) when is_binary(q) do
|
||||
fn(acc, fun) ->
|
||||
do_decoder(q, acc, fun)
|
||||
end
|
||||
Stream.unfold(q, &do_decoder/1)
|
||||
end
|
||||
|
||||
defp do_decoder("", acc, _fun) do
|
||||
acc
|
||||
defp do_decoder("") do
|
||||
nil
|
||||
end
|
||||
|
||||
defp do_decoder(q, acc, fun) do
|
||||
next =
|
||||
defp do_decoder(q) do
|
||||
{ first, next } =
|
||||
case :binary.split(q, "&") do
|
||||
[first, rest] -> rest
|
||||
[first] -> ""
|
||||
[first, rest] -> { first, rest }
|
||||
[first] -> { first, "" }
|
||||
end
|
||||
|
||||
current =
|
||||
@@ -94,7 +131,7 @@ defmodule URI do
|
||||
[ key ] -> { decode(key), nil }
|
||||
end
|
||||
|
||||
do_decoder(next, fun.(current, acc), fun)
|
||||
{ current, next }
|
||||
end
|
||||
|
||||
defp pair({k, v}) do
|
||||
@@ -102,7 +139,13 @@ defmodule URI do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Percent (URL) encodes a URI.
|
||||
Percent-escape a URI.
|
||||
|
||||
## Example
|
||||
|
||||
iex> URI.encode("http://elixir-lang.com/getting_started/2.html")
|
||||
"http%3A%2F%2Felixir-lang.com%2Fgetting_started%2F2.html"
|
||||
|
||||
"""
|
||||
def encode(s), do: bc(<<c>> inbits s, do: <<percent(c) :: binary>>)
|
||||
|
||||
@@ -124,7 +167,13 @@ defmodule URI do
|
||||
defp hex(n), do: <<n + ?A - 10>>
|
||||
|
||||
@doc """
|
||||
Unpercent (URL) decodes a URI.
|
||||
Percent-unescape a URI.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> URI.decode("http%3A%2F%2Felixir-lang.com")
|
||||
"http://elixir-lang.com"
|
||||
|
||||
"""
|
||||
def decode(<<?%, hex1, hex2, tail :: binary >>) do
|
||||
<< bsl(hex2dec(hex1), 4) + hex2dec(hex2) >> <> decode(tail)
|
||||
@@ -146,20 +195,18 @@ defmodule URI do
|
||||
@doc """
|
||||
Parses a URI into components.
|
||||
|
||||
URIs have portions that are handled specially for the
|
||||
particular scheme of the URI. For example, http and https
|
||||
have different default ports. Sometimes the parsing
|
||||
of portions themselves are different. This parser
|
||||
is extensible via behavior modules. If you have a
|
||||
module named `URI.MYSCHEME` with a function called
|
||||
`parse` that takes a single argument, the generically
|
||||
parsed URI, that function will be called when this
|
||||
parse function is passed a URI of that scheme. This
|
||||
allows you to build on top of what the URI library
|
||||
currently offers. You also need to define `default_port`
|
||||
which takes no arguments and returns the default port
|
||||
for that particular scheme. Take a look at `URI.HTTPS` for an
|
||||
example of one of these extension modules.
|
||||
URIs have portions that are handled specially for the particular
|
||||
scheme of the URI. For example, http and https have different
|
||||
default ports. Such values can be accessed and registered via
|
||||
`URI.default_port/1` and `URI.default_port/2`.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> URI.parse("http://elixir-lang.org/")
|
||||
URI.Info[scheme: "http", path: "/", query: nil, fragment: nil,
|
||||
authority: "elixir-lang.org", userinfo: nil,
|
||||
host: "elixir-lang.org", port: 80]
|
||||
|
||||
"""
|
||||
def parse(s) when is_binary(s) do
|
||||
# From http://tools.ietf.org/html/rfc3986#appendix-B
|
||||
|
||||
+19
-16
@@ -2,7 +2,7 @@ defmodule Version do
|
||||
@moduledoc %S"""
|
||||
Functions for parsing and matching versions against requirements.
|
||||
|
||||
A version is a string in a specific format or a `Version.Schema`
|
||||
A version is a string in a specific format or a `Version.Schema`
|
||||
generated after parsing via `Version.parse/1`.
|
||||
|
||||
`Version` parsing and requirements follow
|
||||
@@ -31,7 +31,7 @@ defmodule Version do
|
||||
|
||||
Requirements allow you to specify which versions of a given
|
||||
dependency you are willing to work against. It supports common
|
||||
operators like `>=`, `<=`, `>`, `==` and friends that
|
||||
operators like `>=`, `<=`, `>`, `==` and friends that
|
||||
work as one would expect:
|
||||
|
||||
# Only version 2.0.0
|
||||
@@ -65,14 +65,17 @@ defmodule Version do
|
||||
defrecord Schema, major: 0, minor: 0, patch: 0, pre: nil, build: nil, source: nil
|
||||
defrecord Requirement, source: nil, matchspec: nil
|
||||
|
||||
defexception InvalidRequirement, reason: :invalid_requirement do
|
||||
def message(InvalidRequirement[reason: reason]) when is_binary(reason) do
|
||||
{ first, rest } = String.next_grapheme(reason)
|
||||
String.downcase(first) <> rest
|
||||
end
|
||||
defexception InvalidRequirement, [:message] do
|
||||
def exception(opts) do
|
||||
message =
|
||||
if is_binary opts[:reason] do
|
||||
{ first, rest } = String.next_grapheme(opts[:reason])
|
||||
String.downcase(first) <> rest
|
||||
else
|
||||
"invalid version specification"
|
||||
end
|
||||
|
||||
def message(InvalidRequirement[]) do
|
||||
"invalid version specification"
|
||||
InvalidRequirement[message: message]
|
||||
end
|
||||
end
|
||||
|
||||
@@ -80,7 +83,7 @@ defmodule Version do
|
||||
Check if the given version matches the specification.
|
||||
|
||||
Returns `true` if `version` satisfies `requirement`, `false` otherwise.
|
||||
Raises a `Version.InvalidRequirement` exception if `requirement` is not parseable.
|
||||
Raises a `Version.InvalidRequirement` exception if `requirement` is not parseable.
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -137,7 +140,7 @@ defmodule Version do
|
||||
|
||||
## Examples
|
||||
|
||||
> Version.parse("2.0.1-alpha1")
|
||||
iex> Version.parse("2.0.1-alpha1")
|
||||
#Version.Schema<2.0.1-alpha1>
|
||||
|
||||
"""
|
||||
@@ -190,7 +193,7 @@ defmodule Version do
|
||||
|
||||
## Examples
|
||||
|
||||
> Version.from_matchable({2, 0, 1, ["alpha", 1]})
|
||||
iex> Version.from_matchable({2, 0, 1, ["alpha", 1]})
|
||||
#Version.Schema<2.0.1-alpha.1>
|
||||
|
||||
"""
|
||||
@@ -401,16 +404,16 @@ defmodule Version do
|
||||
defp approximate(version) do
|
||||
Version.from_matchable(case Regex.run(@version_regex, version) do
|
||||
[_, major] ->
|
||||
{ binary_to_integer(major) + 1, 0, 0, [] }
|
||||
{ binary_to_integer(major) + 1, 0, 0, [0] }
|
||||
|
||||
[_, major, _] ->
|
||||
{ binary_to_integer(major) + 1, 0, 0, [] }
|
||||
{ binary_to_integer(major) + 1, 0, 0, [0] }
|
||||
|
||||
[_, major, minor, _] ->
|
||||
{ binary_to_integer(major), binary_to_integer(minor) + 1, 0, [] }
|
||||
{ binary_to_integer(major), binary_to_integer(minor) + 1, 0, [0] }
|
||||
|
||||
[_, major, minor, _, _] ->
|
||||
{ binary_to_integer(major), binary_to_integer(minor) + 1, 0, [] }
|
||||
{ binary_to_integer(major), binary_to_integer(minor) + 1, 0, [0] }
|
||||
end)
|
||||
end
|
||||
|
||||
|
||||
+88
-52
@@ -1,9 +1,11 @@
|
||||
%% Main entry point for Elixir functions. All of those functions are
|
||||
%% private to the Elixir compiler and reserved to be used by Elixir only.
|
||||
-module(elixir).
|
||||
-behaviour(application).
|
||||
-export([main/1, start_cli/0,
|
||||
scope_for_eval/1, scope_for_eval/2,
|
||||
eval/2, eval/3, eval/4, eval_forms/3,
|
||||
eval_quoted/2, eval_quoted/3, eval_quoted/4]).
|
||||
string_to_quoted/4, 'string_to_quoted!'/4,
|
||||
env_for_eval/1, env_for_eval/2, quoted_to_erl/2, quoted_to_erl/3,
|
||||
eval/2, eval/3, eval_forms/3, eval_forms/4, eval_quoted/3]).
|
||||
-include("elixir.hrl").
|
||||
|
||||
%% Top level types
|
||||
@@ -11,7 +13,7 @@
|
||||
-type char_list() :: string().
|
||||
-type as_boolean(T) :: T.
|
||||
|
||||
% OTP APPLICATION API
|
||||
%% OTP Application API
|
||||
|
||||
-export([start/2, stop/1, config_change/3]).
|
||||
|
||||
@@ -22,7 +24,7 @@ start(_Type, _Args) ->
|
||||
io:setopts(standard_io, [binary,{encoding,unicode}]),
|
||||
io:setopts(standard_error, [binary,{encoding,unicode}])
|
||||
end),
|
||||
elixir_sup:start_link([]).
|
||||
elixir_sup:start_link().
|
||||
|
||||
stop(_S) ->
|
||||
ok.
|
||||
@@ -44,95 +46,129 @@ start_cli() ->
|
||||
|
||||
%% EVAL HOOKS
|
||||
|
||||
scope_for_eval(Opts) ->
|
||||
scope_for_eval(#elixir_scope{
|
||||
file = <<"nofile">>,
|
||||
env_for_eval(Opts) ->
|
||||
env_for_eval(#elixir_env{
|
||||
local = nil,
|
||||
aliases = [],
|
||||
requires = elixir_dispatch:default_requires(),
|
||||
functions = elixir_dispatch:default_functions(),
|
||||
macros = elixir_dispatch:default_macros()
|
||||
}, Opts).
|
||||
|
||||
scope_for_eval(Scope, Opts) ->
|
||||
env_for_eval(Env, Opts) ->
|
||||
Line = case lists:keyfind(line, 1, Opts) of
|
||||
{ line, RawLine } when is_integer(RawLine) -> RawLine;
|
||||
false -> Env#elixir_env.line
|
||||
end,
|
||||
|
||||
File = case lists:keyfind(file, 1, Opts) of
|
||||
{ file, RawFile } when is_binary(RawFile) -> RawFile;
|
||||
false -> Scope#elixir_scope.file
|
||||
false -> Env#elixir_env.file
|
||||
end,
|
||||
|
||||
Local = case lists:keyfind(delegate_locals_to, 1, Opts) of
|
||||
{ delegate_locals_to, LocalOpt } -> LocalOpt;
|
||||
false -> Scope#elixir_scope.local
|
||||
false -> Env#elixir_env.local
|
||||
end,
|
||||
|
||||
Aliases = case lists:keyfind(aliases, 1, Opts) of
|
||||
{ aliases, AliasesOpt } -> AliasesOpt;
|
||||
false -> Scope#elixir_scope.aliases
|
||||
false -> Env#elixir_env.aliases
|
||||
end,
|
||||
|
||||
Requires = case lists:keyfind(requires, 1, Opts) of
|
||||
{ requires, List } -> ordsets:from_list(List);
|
||||
false -> Scope#elixir_scope.requires
|
||||
false -> Env#elixir_env.requires
|
||||
end,
|
||||
|
||||
Functions = case lists:keyfind(functions, 1, Opts) of
|
||||
{ functions, FunctionsOpt } -> FunctionsOpt;
|
||||
false -> Scope#elixir_scope.functions
|
||||
false -> Env#elixir_env.functions
|
||||
end,
|
||||
|
||||
Macros = case lists:keyfind(macros, 1, Opts) of
|
||||
{ macros, MacrosOpt } -> MacrosOpt;
|
||||
false -> Scope#elixir_scope.macros
|
||||
false -> Env#elixir_env.macros
|
||||
end,
|
||||
|
||||
Scope#elixir_scope{
|
||||
file=File, local=Local,
|
||||
Module = case lists:keyfind(module, 1, Opts) of
|
||||
{ module, ModuleOpt } when is_atom(ModuleOpt) -> ModuleOpt;
|
||||
false -> nil
|
||||
end,
|
||||
|
||||
Env#elixir_env{
|
||||
file=File, local=Local, module=Module,
|
||||
macros=Macros, functions=Functions,
|
||||
requires=Requires, aliases=Aliases }.
|
||||
requires=Requires, aliases=Aliases, line=Line
|
||||
}.
|
||||
|
||||
%% String evaluation
|
||||
|
||||
eval(String, Binding) -> eval(String, Binding, []).
|
||||
eval(String, Binding) ->
|
||||
eval(String, Binding, []).
|
||||
|
||||
eval(String, Binding, Opts) ->
|
||||
case lists:keyfind(line, 1, Opts) of
|
||||
false -> Line = 1;
|
||||
{ line, Line } -> []
|
||||
end,
|
||||
eval(String, Binding, Line, scope_for_eval(Opts)).
|
||||
|
||||
eval(String, Binding, Line, #elixir_scope{file=File} = S) when
|
||||
eval(String, Binding, Opts) when is_list(Opts) ->
|
||||
eval(String, Binding, env_for_eval(Opts));
|
||||
eval(String, Binding, #elixir_env{line=Line,file=File} = E) when
|
||||
is_list(String), is_list(Binding), is_integer(Line), is_binary(File) ->
|
||||
Forms = elixir_translator:'forms!'(String, Line, File, []),
|
||||
eval_forms(Forms, Binding, S).
|
||||
Forms = 'string_to_quoted!'(String, Line, File, []),
|
||||
eval_forms(Forms, Binding, E).
|
||||
|
||||
%% Quoted evaluation
|
||||
|
||||
eval_quoted(Tree, Binding) -> eval_quoted(Tree, Binding, []).
|
||||
eval_quoted(Tree, Binding, Opts) when is_list(Opts) ->
|
||||
eval_quoted(Tree, Binding, env_for_eval(Opts));
|
||||
eval_quoted(Tree, Binding, #elixir_env{line=Line} = E) ->
|
||||
eval_forms(elixir_quote:linify(Line, Tree), Binding, E).
|
||||
|
||||
eval_quoted(Tree, Binding, Opts) ->
|
||||
case lists:keyfind(line, 1, Opts) of
|
||||
{ line, Line } -> [];
|
||||
false -> Line = 1
|
||||
end,
|
||||
eval_quoted(Tree, Binding, Line, scope_for_eval(Opts)).
|
||||
|
||||
eval_quoted(Tree, Binding, Line, #elixir_scope{} = S) when is_integer(Line) ->
|
||||
eval_forms(elixir_quote:linify(Line, Tree), Binding, S).
|
||||
|
||||
%% Handle forms evaluation internally, it is an
|
||||
%% internal API not meant for external usage.
|
||||
%% Handle forms evaluation. The main difference to
|
||||
%% to eval_quoted is that it does not linefy the given
|
||||
%% args.
|
||||
|
||||
eval_forms(Tree, Binding, #elixir_env{} = E) ->
|
||||
eval_forms(Tree, Binding, E, elixir_env:env_to_scope(E));
|
||||
eval_forms(Tree, Binding, Opts) when is_list(Opts) ->
|
||||
eval_forms(Tree, Binding, scope_for_eval(Opts));
|
||||
eval_forms(Tree, Binding, env_for_eval(Opts)).
|
||||
eval_forms(Tree, Binding, Env, Scope) ->
|
||||
{ ParsedBinding, ParsedScope } = elixir_scope:load_binding(Binding, Scope),
|
||||
ParsedEnv = Env#elixir_env{vars=[K || {K,_} <- ParsedScope#elixir_scope.vars]},
|
||||
{ Erl, NewEnv, NewScope } = quoted_to_erl(Tree, ParsedEnv, ParsedScope),
|
||||
|
||||
eval_forms(Tree, Binding, Scope) ->
|
||||
{ ParsedBinding, ParsedScope } = elixir_scope:load_binding(Binding, Scope, nil),
|
||||
{ Exprs, NewScope } = elixir_translator:translate(Tree, ParsedScope),
|
||||
case Exprs of
|
||||
[] ->
|
||||
{ nil, Binding, NewScope };
|
||||
case Erl of
|
||||
{ atom, _, Atom } ->
|
||||
{ Atom, Binding, NewEnv, NewScope };
|
||||
_ ->
|
||||
{ value, Value, NewBinding } = erl_eval:exprs(Exprs, ParsedBinding),
|
||||
{ Value, elixir_scope:dump_binding(NewBinding, NewScope), NewScope }
|
||||
{ value, Value, NewBinding } = erl_eval:expr(Erl, ParsedBinding),
|
||||
{ Value, elixir_scope:dump_binding(NewBinding, NewScope), NewEnv, NewScope }
|
||||
end.
|
||||
|
||||
%% Converts a quoted expression to erlang abstract format
|
||||
|
||||
quoted_to_erl(Quoted, Env) ->
|
||||
quoted_to_erl(Quoted, Env, elixir_env:env_to_scope(Env)).
|
||||
|
||||
quoted_to_erl(Quoted, Env, Scope) ->
|
||||
{ Expanded, NewEnv } = elixir_exp:expand(Quoted, Env),
|
||||
{ Erl, NewScope } = elixir_translator:translate(Expanded, Scope),
|
||||
{ Erl, NewEnv, NewScope }.
|
||||
|
||||
%% Converts a given string (char list) into quote expression
|
||||
|
||||
string_to_quoted(String, StartLine, File, Opts) when is_integer(StartLine), is_binary(File) ->
|
||||
case elixir_tokenizer:tokenize(String, StartLine, [{ file, File }|Opts]) of
|
||||
{ ok, _Line, Tokens } ->
|
||||
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, Reason, _Rest, _SoFar } -> { error, Reason }
|
||||
end.
|
||||
|
||||
'string_to_quoted!'(String, StartLine, File, Opts) ->
|
||||
case string_to_quoted(String, StartLine, File, Opts) of
|
||||
{ ok, Forms } ->
|
||||
Forms;
|
||||
{ error, { Line, Error, Token } } ->
|
||||
elixir_errors:parse_error(Line, File, Error, Token)
|
||||
end.
|
||||
|
||||
@@ -1,98 +1,90 @@
|
||||
-module(elixir_aliases).
|
||||
-export([nesting_alias/2, last/1, concat/1, safe_concat/1,
|
||||
format_error/1, ensure_loaded/3, ensure_loaded/4, expand/4, store/5]).
|
||||
-export([inspect/1, last/1, concat/1, safe_concat/1, format_error/1,
|
||||
ensure_loaded/3, expand/4, store/7]).
|
||||
-include("elixir.hrl").
|
||||
|
||||
%% Store an alias in the given scope
|
||||
store(_Meta, New, New, _TKV, S) -> S;
|
||||
store(Meta, New, Old, TKV, S) ->
|
||||
record_warn(Meta, New, TKV, 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
|
||||
inspect(Atom) when is_atom(Atom) ->
|
||||
case elixir_compiler:get_opt(internal) of
|
||||
true -> atom_to_binary(Atom, utf8);
|
||||
false -> 'Elixir.Inspect.Atom':inspect(Atom)
|
||||
end.
|
||||
|
||||
record_warn(Meta, Ref, Opts, S) ->
|
||||
%% Store an alias in the given scope
|
||||
store(_Meta, New, New, _TKV, Aliases, MacroAliases, _Lexical) ->
|
||||
{ Aliases, MacroAliases };
|
||||
store(Meta, New, Old, TKV, Aliases, MacroAliases, Lexical) ->
|
||||
record_warn(Meta, New, TKV, Lexical),
|
||||
{ store_alias(New, Old, Aliases),
|
||||
store_macro_alias(Meta, New, Old, MacroAliases) }.
|
||||
|
||||
store_alias(New, Old, Aliases) ->
|
||||
lists:keystore(New, 1, Aliases, { New, Old }).
|
||||
store_macro_alias(Meta, New, Old, Aliases) ->
|
||||
case lists:keymember(context, 1, Meta) andalso
|
||||
lists:keyfind(counter, 1, Meta) of
|
||||
{ counter, Counter } when is_integer(Counter) ->
|
||||
lists:keystore(New, 1, Aliases, { New, { Counter, Old } });
|
||||
_ ->
|
||||
Aliases
|
||||
end.
|
||||
|
||||
record_warn(Meta, Ref, Opts, Lexical) ->
|
||||
Warn =
|
||||
case lists:keyfind(warn, 1, Opts) of
|
||||
{ warn, false } -> false;
|
||||
{ warn, true } -> true;
|
||||
false -> not lists:keymember(context, 1, Meta)
|
||||
end,
|
||||
elixir_lexical:record_alias(Ref, ?line(Meta), Warn, S#elixir_scope.lexical_tracker).
|
||||
elixir_lexical:record_alias(Ref, ?line(Meta), Warn, Lexical).
|
||||
|
||||
%% Expand an alias. It returns an atom (meaning that there
|
||||
%% was an expansion) or a list of atoms.
|
||||
|
||||
expand({ '__aliases__', _Meta, ['Elixir'|_] = List }, _Aliases, _MacroAliases, _LexicalTracker) ->
|
||||
concat(List);
|
||||
|
||||
expand({ '__aliases__', Meta, _ } = Alias, Aliases, MacroAliases, LexicalTracker) ->
|
||||
case lists:keyfind(alias, 1, Meta) of
|
||||
{ alias, false } ->
|
||||
expand(Alias, MacroAliases, LexicalTracker);
|
||||
{ alias, Atom } when is_atom(Atom) ->
|
||||
case expand(Alias, MacroAliases, LexicalTracker) of
|
||||
OtherAtom when is_atom(OtherAtom) -> OtherAtom;
|
||||
OtherAliases when is_list(OtherAliases) -> Atom
|
||||
end;
|
||||
Atom;
|
||||
false ->
|
||||
expand(Alias, Aliases, LexicalTracker)
|
||||
end.
|
||||
|
||||
expand({ '__aliases__', _Meta, [H] }, Aliases, LexicalTracker) when H /= 'Elixir' ->
|
||||
case expand_one(H, Aliases, LexicalTracker) of
|
||||
false -> [H];
|
||||
Atom -> Atom
|
||||
end;
|
||||
|
||||
expand({ '__aliases__', _Meta, [H|T] }, Aliases, LexicalTracker) when is_atom(H) ->
|
||||
case H of
|
||||
'Elixir' ->
|
||||
concat(T);
|
||||
_ ->
|
||||
case expand_one(H, Aliases, LexicalTracker) of
|
||||
false -> [H|T];
|
||||
Atom -> concat([Atom|T])
|
||||
expand({ '__aliases__', Meta, [H|T] }, Aliases, LexicalTracker) when is_atom(H) ->
|
||||
Lookup = list_to_atom("Elixir." ++ atom_to_list(H)),
|
||||
Counter = case lists:keyfind(counter, 1, Meta) of
|
||||
{ counter, C } -> C;
|
||||
_ -> nil
|
||||
end,
|
||||
case lookup(Lookup, Aliases, Counter) of
|
||||
Lookup -> [H|T];
|
||||
Atom ->
|
||||
elixir_lexical:record_alias(Lookup, LexicalTracker),
|
||||
case T of
|
||||
[] -> Atom;
|
||||
_ -> concat([Atom|T])
|
||||
end
|
||||
end;
|
||||
|
||||
expand({ '__aliases__', _Meta, List }, _Aliases, _LexicalTracker) ->
|
||||
List.
|
||||
|
||||
expand_one(H, Aliases, LexicalTracker) ->
|
||||
Lookup = list_to_atom("Elixir." ++ atom_to_list(H)),
|
||||
case lookup(Lookup, Aliases) of
|
||||
Lookup -> false;
|
||||
Else ->
|
||||
elixir_lexical:record_alias(Lookup, LexicalTracker),
|
||||
Else
|
||||
end.
|
||||
|
||||
%% Ensure a module is loaded before its usage.
|
||||
|
||||
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, File, Ref, FileModules) ->
|
||||
ensure_loaded(_Meta, 'Elixir.Kernel', _E) -> ok;
|
||||
ensure_loaded(Meta, Ref, E) ->
|
||||
try
|
||||
Ref:module_info(compile)
|
||||
catch
|
||||
error:undef ->
|
||||
Kind = case lists:member(Ref, FileModules) of
|
||||
Kind = case lists:member(Ref, E#elixir_env.context_modules) of
|
||||
true -> scheduled_module;
|
||||
false -> unloaded_module
|
||||
end,
|
||||
elixir_errors:form_error(Line, File, ?MODULE, { Kind, Ref })
|
||||
elixir_errors:form_error(Meta, E#elixir_env.file, ?MODULE, { Kind, Ref })
|
||||
end.
|
||||
|
||||
%% Receives an atom and returns the last bit as an alias.
|
||||
@@ -105,74 +97,48 @@ last([$.|_], Acc) -> Acc;
|
||||
last([H|T], Acc) -> last(T, [H|Acc]);
|
||||
last([], Acc) -> Acc.
|
||||
|
||||
%% Gets two modules names and return an alias
|
||||
%% which can be passed down to the alias directive
|
||||
%% and it will create a proper shortcut representing
|
||||
%% the given nesting.
|
||||
%%
|
||||
%% Examples:
|
||||
%%
|
||||
%% nesting_alias('Elixir.Foo.Bar', 'Elixir.Foo.Bar.Baz.Bat')
|
||||
%% { 'Elixir.Baz', 'Elixir.Foo.Bar.Baz' }
|
||||
%%
|
||||
%% When passed to alias, the example above will generate an
|
||||
%% alias like:
|
||||
%%
|
||||
%% 'Elixir.Baz' => 'Elixir.Foo.Bar.Baz'
|
||||
%%
|
||||
nesting_alias(nil, _Full) -> false;
|
||||
|
||||
nesting_alias(Prefix, Full) ->
|
||||
PrefixList = list_nesting(Prefix),
|
||||
FullList = list_nesting(Full),
|
||||
(PrefixList /= []) andalso do_nesting(PrefixList, FullList, []).
|
||||
|
||||
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])) };
|
||||
do_nesting(_, _, _Acc) ->
|
||||
false.
|
||||
|
||||
list_nesting(Atom) ->
|
||||
case string:tokens(atom_to_list(Atom), ".") of
|
||||
["Elixir"|T] -> T;
|
||||
_ -> []
|
||||
end.
|
||||
|
||||
%% Receives a list of atoms, binaries or lists
|
||||
%% representing modules and concatenates them.
|
||||
|
||||
concat(Args) -> list_to_atom(raw_concat(Args)).
|
||||
safe_concat(Args) -> list_to_existing_atom(raw_concat(Args)).
|
||||
concat(Args) -> binary_to_atom(do_concat(Args), utf8).
|
||||
safe_concat(Args) -> binary_to_existing_atom(do_concat(Args), utf8).
|
||||
|
||||
raw_concat(['Elixir'|Args]) -> do_concat(Args);
|
||||
raw_concat([nil|Args]) -> do_concat(Args);
|
||||
raw_concat(Args) -> do_concat(Args).
|
||||
do_concat([H|T]) when is_atom(H), H /= nil ->
|
||||
do_concat([atom_to_binary(H, utf8)|T]);
|
||||
do_concat([<<"Elixir.", _/binary>>=H|T]) ->
|
||||
do_concat(T, H);
|
||||
do_concat([<<"Elixir">>=H|T]) ->
|
||||
do_concat(T, H);
|
||||
do_concat(T) ->
|
||||
do_concat(T, <<"Elixir">>).
|
||||
|
||||
do_concat(Args) ->
|
||||
Aliases = [to_partial(Arg) || Arg <- Args],
|
||||
"Elixir" ++ lists:concat(Aliases).
|
||||
do_concat([nil|T], Acc) ->
|
||||
do_concat(T, Acc);
|
||||
do_concat([H|T], Acc) when is_atom(H) ->
|
||||
do_concat(T, <<Acc/binary, $., (to_partial(atom_to_binary(H, utf8)))/binary>>);
|
||||
do_concat([H|T], Acc) when is_binary(H) ->
|
||||
do_concat(T, <<Acc/binary, $., (to_partial(H))/binary>>);
|
||||
do_concat([], Acc) ->
|
||||
Acc.
|
||||
|
||||
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) -> [$.|Arg];
|
||||
to_partial([$.|_] = Arg) -> Arg;
|
||||
to_partial(Arg) when is_list(Arg) -> [$.|Arg].
|
||||
to_partial(<<"Elixir.", Arg/binary>>) -> Arg;
|
||||
to_partial(<<".", Arg/binary>>) -> Arg;
|
||||
to_partial(Arg) when is_binary(Arg) -> Arg.
|
||||
|
||||
%% Lookup an alias in the current scope.
|
||||
|
||||
lookup(Else, Dict) ->
|
||||
case orddict:find(Else, Dict) of
|
||||
{ ok, Value } when Value /= Else -> lookup(Value, Dict);
|
||||
lookup(Else, Dict, Counter) ->
|
||||
case lists:keyfind(Else, 1, Dict) of
|
||||
{ Else, { Counter, Value } } -> lookup(Value, Dict, Counter);
|
||||
{ Else, Value } when is_atom(Value) -> lookup(Value, Dict, Counter);
|
||||
_ -> Else
|
||||
end.
|
||||
|
||||
%% Errors
|
||||
|
||||
format_error({unloaded_module, Module}) ->
|
||||
io_lib:format("module ~ts is not loaded and could not be found", [elixir_errors:inspect(Module)]);
|
||||
io_lib:format("module ~ts is not loaded and could not be found", [elixir_aliases:inspect(Module)]);
|
||||
|
||||
format_error({scheduled_module, Module}) ->
|
||||
io_lib:format("module ~ts is not loaded but was defined. This happens because you are trying to use a module in the same context it is defined. Try defining the module outside the context that requires it.",
|
||||
[elixir_errors:inspect(Module)]).
|
||||
[inspect(Module)]).
|
||||
@@ -0,0 +1,202 @@
|
||||
%% Handle translation of Elixir literals to Erlang AST.
|
||||
-module(elixir_bitstring).
|
||||
-export([translate/3, expand/3]).
|
||||
-include("elixir.hrl").
|
||||
|
||||
%% Expansion
|
||||
|
||||
expand(Meta, Args, E) ->
|
||||
case E#elixir_env.context of
|
||||
match ->
|
||||
{ EArgs, EA } = expand_bitstr(fun elixir_exp:expand/2, Args, [], E),
|
||||
{ { '<<>>', Meta, EArgs }, EA };
|
||||
_ ->
|
||||
{ EArgs, { EC, EV } } = expand_bitstr(fun elixir_exp:expand_arg/2, Args, [], { E, E }),
|
||||
{ { '<<>>', Meta, EArgs }, elixir_env:mergea(EV, EC) }
|
||||
end.
|
||||
|
||||
expand_bitstr(_Fun, [], Acc, E) ->
|
||||
{ lists:reverse(Acc), E };
|
||||
expand_bitstr(Fun, [{'::',Meta,[Left,Right]}|T], Acc, E) ->
|
||||
{ ELeft, EL } = Fun(Left, E),
|
||||
|
||||
%% Variables defined outside the binary can be accounted
|
||||
%% on subparts, however we can't assign new variables.
|
||||
case E of
|
||||
{ ER, _ } -> ok; %% expand_arg, no assigns
|
||||
_ -> ER = E#elixir_env{context=nil} %% expand_each, revert assigns
|
||||
end,
|
||||
|
||||
ERight = expand_bit_info(Meta, Right, ER),
|
||||
expand_bitstr(Fun, T, [{'::',Meta,[ELeft,ERight]}|Acc], EL);
|
||||
|
||||
expand_bitstr(Fun, [H|T], Acc, E) ->
|
||||
{ Expr, ES } = Fun(H, E),
|
||||
expand_bitstr(Fun, T, [Expr|Acc], ES).
|
||||
|
||||
%% Expand bit info
|
||||
|
||||
expand_bit_info(Meta, Info, E) when is_list(Info) ->
|
||||
expand_bit_info(Meta, Info, default, [], E);
|
||||
|
||||
expand_bit_info(Meta, Info, E) ->
|
||||
expand_bit_info(Meta, [Info], E).
|
||||
|
||||
expand_bit_info(Meta, [{ Expr, ExprMeta, Args }|T], Size, Types, E) when is_atom(Expr) ->
|
||||
ListArgs = if is_atom(Args) -> []; is_list(Args) -> Args end,
|
||||
case expand_bit_type_or_size(Expr, ListArgs) of
|
||||
type ->
|
||||
{ EArgs, EE } = elixir_exp:expand_args(ListArgs, E),
|
||||
expand_bit_info(Meta, T, Size, [{ Expr, [], EArgs }|Types], EE);
|
||||
size ->
|
||||
case Size of
|
||||
default -> ok;
|
||||
_ -> elixir_errors:compile_error(Meta, E#elixir_env.file, "duplicated size definition in bitstring")
|
||||
end,
|
||||
{ EArgs, EE } = elixir_exp:expand_args(ListArgs, E),
|
||||
expand_bit_info(Meta, T, { Expr, [], EArgs }, Types, EE);
|
||||
none ->
|
||||
handle_unknown_bit_info(Meta, { Expr, ExprMeta, ListArgs }, T, Size, Types, E)
|
||||
end;
|
||||
|
||||
expand_bit_info(Meta, [Int|T], Size, Types, E) when is_integer(Int) ->
|
||||
expand_bit_info(Meta, [{ size, [], [Int] }|T], Size, Types, E);
|
||||
|
||||
expand_bit_info(Meta, [Expr|_], _Size, _Types, E) ->
|
||||
elixir_errors:compile_error(Meta, E#elixir_env.file,
|
||||
"unknown bitstring specifier ~ts", ['Elixir.Kernel':inspect(Expr)]);
|
||||
|
||||
expand_bit_info(_Meta, [], Size, Types, _) ->
|
||||
case Size of
|
||||
default -> lists:reverse(Types);
|
||||
_ -> [Size|lists:reverse(Types)]
|
||||
end.
|
||||
|
||||
expand_bit_type_or_size(binary, []) -> type;
|
||||
expand_bit_type_or_size(integer, []) -> type;
|
||||
expand_bit_type_or_size(float, []) -> type;
|
||||
expand_bit_type_or_size(bitstring, []) -> type;
|
||||
expand_bit_type_or_size(bytes, []) -> type;
|
||||
expand_bit_type_or_size(bits, []) -> type;
|
||||
expand_bit_type_or_size(utf8, []) -> type;
|
||||
expand_bit_type_or_size(utf16, []) -> type;
|
||||
expand_bit_type_or_size(utf32, []) -> type;
|
||||
expand_bit_type_or_size(signed, []) -> type;
|
||||
expand_bit_type_or_size(unsigned, []) -> type;
|
||||
expand_bit_type_or_size(big, []) -> type;
|
||||
expand_bit_type_or_size(little, []) -> type;
|
||||
expand_bit_type_or_size(native, []) -> type;
|
||||
expand_bit_type_or_size(unit, [_]) -> type;
|
||||
expand_bit_type_or_size(size, [_]) -> size;
|
||||
expand_bit_type_or_size(_, _) -> none.
|
||||
|
||||
handle_unknown_bit_info(Meta, { _, ExprMeta, _ } = Expr, T, Size, Types, E) ->
|
||||
case 'Elixir.Macro':expand(Expr, elixir_env:env_to_ex({ ?line(ExprMeta), E })) of
|
||||
Expr ->
|
||||
elixir_errors:compile_error(ExprMeta, E#elixir_env.file,
|
||||
"unknown bitstring specifier ~ts", ['Elixir.Macro':to_string(Expr)]);
|
||||
Other ->
|
||||
List = case is_list(Other) of true -> Other; false -> [Other] end,
|
||||
expand_bit_info(Meta, List ++ T, Size, Types, E)
|
||||
end.
|
||||
|
||||
%% Translation
|
||||
|
||||
translate(Meta, Args, S) ->
|
||||
case S#elixir_scope.context of
|
||||
match ->
|
||||
build_bitstr(fun elixir_translator:translate/2, Args, Meta, S);
|
||||
_ ->
|
||||
build_bitstr(fun(X, Acc) -> elixir_translator:translate_arg(X, Acc, S) end, Args, Meta, S)
|
||||
end.
|
||||
|
||||
build_bitstr(Fun, Exprs, Meta, S) ->
|
||||
{ Final, FinalS } = build_bitstr_each(Fun, Exprs, Meta, S, []),
|
||||
{ { bin, ?line(Meta), lists:reverse(Final) }, FinalS }.
|
||||
|
||||
build_bitstr_each(_Fun, [], _Meta, S, Acc) ->
|
||||
{ Acc, S };
|
||||
|
||||
build_bitstr_each(Fun, [{'::',_,[H,V]}|T], Meta, S, Acc) ->
|
||||
{ Size, Types } = extract_bit_info(Meta, V, S#elixir_scope{context=nil}),
|
||||
build_bitstr_each(Fun, T, Meta, S, Acc, H, Size, Types);
|
||||
|
||||
build_bitstr_each(Fun, [H|T], Meta, S, Acc) ->
|
||||
build_bitstr_each(Fun, T, Meta, S, Acc, H, default, default).
|
||||
|
||||
build_bitstr_each(Fun, T, Meta, S, Acc, H, default, Types) when is_binary(H) ->
|
||||
Element =
|
||||
case types_allow_splice(Types, []) of
|
||||
true ->
|
||||
%% See explanation in elixir_utils:elixir_to_erl/1 to know
|
||||
%% why we can simply convert the binary to a list.
|
||||
{ bin_element, ?line(Meta), { string, 0, binary_to_list(H) }, default, default };
|
||||
false ->
|
||||
case types_require_conversion(Types) of
|
||||
true ->
|
||||
{ bin_element, ?line(Meta), { string, 0, elixir_utils:characters_to_list(H) }, default, Types };
|
||||
false ->
|
||||
elixir_errors:compile_error(Meta, S#elixir_scope.file, "invalid types for literal string in <<>>. "
|
||||
"Accepted types are: little, big, utf8, utf16, utf32, bits, bytes, binary, bitstring")
|
||||
end
|
||||
end,
|
||||
|
||||
build_bitstr_each(Fun, T, Meta, S, [Element|Acc]);
|
||||
|
||||
build_bitstr_each(_Fun, _T, Meta, S, _Acc, H, _Size, _Types) when is_binary(H) ->
|
||||
elixir_errors:compile_error(Meta, S#elixir_scope.file, "size is not supported for literal string in <<>>");
|
||||
|
||||
build_bitstr_each(_Fun, _T, Meta, S, _Acc, H, _Size, _Types) when is_list(H); is_atom(H) ->
|
||||
elixir_errors:compile_error(Meta, S#elixir_scope.file, "invalid literal ~ts in <<>>",
|
||||
['Elixir.Macro':to_string(H)]);
|
||||
|
||||
build_bitstr_each(Fun, T, Meta, S, Acc, H, Size, Types) ->
|
||||
{ Expr, NS } = Fun(H, S),
|
||||
|
||||
case Expr of
|
||||
{ bin, _, Elements } ->
|
||||
case (Size == default) andalso types_allow_splice(Types, Elements) of
|
||||
true -> build_bitstr_each(Fun, T, Meta, NS, lists:reverse(Elements) ++ Acc);
|
||||
false -> build_bitstr_each(Fun, T, Meta, NS, [{ bin_element, ?line(Meta), Expr, Size, Types }|Acc])
|
||||
end;
|
||||
_ ->
|
||||
build_bitstr_each(Fun, T, Meta, NS, [{ bin_element, ?line(Meta), Expr, Size, Types }|Acc])
|
||||
end.
|
||||
|
||||
types_require_conversion([End|T]) when End == little; End == big -> types_require_conversion(T);
|
||||
types_require_conversion([UTF|T]) when UTF == utf8; UTF == utf16; UTF == utf32 -> types_require_conversion(T);
|
||||
types_require_conversion([]) -> true;
|
||||
types_require_conversion(_) -> false.
|
||||
|
||||
types_allow_splice([bytes], Elements) -> lists:all(fun has_default_size/1, Elements);
|
||||
types_allow_splice([binary], Elements) -> lists:all(fun has_default_size/1, Elements);
|
||||
types_allow_splice([bits], _) -> true;
|
||||
types_allow_splice([bitstring], _) -> true;
|
||||
types_allow_splice(default, _) -> true;
|
||||
types_allow_splice(_, _) -> false.
|
||||
|
||||
has_default_size({ bin_element, _, _, default, _ }) -> true;
|
||||
has_default_size({ bin_element, _, _, _, _ }) -> false.
|
||||
|
||||
%% Extra bitstring specifiers
|
||||
|
||||
extract_bit_info(Meta, [{ size, _, [Arg] }|T], S) ->
|
||||
case elixir_translator:translate(Arg, S) of
|
||||
{ { Kind, _, _ } = Size, _ } when Kind == integer; Kind == var ->
|
||||
{ Size, extract_bit_type(Meta, T, S) };
|
||||
_ ->
|
||||
elixir_errors:compile_error(Meta, S#elixir_scope.file,
|
||||
"size in bitstring expects an integer or a variable as argument, got: ~ts", ['Elixir.Macro':to_string(Arg)])
|
||||
end;
|
||||
extract_bit_info(Meta, T, S) ->
|
||||
{ default, extract_bit_type(Meta, T, S) }.
|
||||
|
||||
extract_bit_type(Meta, [{ unit, _, [Arg] }|T], S) when is_integer(Arg) ->
|
||||
[{ unit, Arg }|extract_bit_type(Meta, T, S)];
|
||||
extract_bit_type(Meta, [{ unit, _, [Arg] }|_], S) ->
|
||||
elixir_errors:compile_error(Meta, S#elixir_scope.file,
|
||||
"unit in bitstring expects an integer as argument, got: ~ts", ['Elixir.Macro':to_string(Arg)]);
|
||||
extract_bit_type(Meta, [{ Other, _, [] }|T], S) ->
|
||||
[Other|extract_bit_type(Meta, T, S)];
|
||||
extract_bit_type(_Meta, [], _S) ->
|
||||
[].
|
||||
@@ -0,0 +1,51 @@
|
||||
%% An Erlang module that behaves like an Elixir module
|
||||
%% used for bootstraping.
|
||||
-module(elixir_bootstrap).
|
||||
-export(['MACRO-def'/2, 'MACRO-def'/3, 'MACRO-defp'/3, 'MACRO-defmodule'/3,
|
||||
'MACRO-defmacro'/2, 'MACRO-defmacro'/3, 'MACRO-defmacrop'/3,
|
||||
'MACRO-@'/2, '__info__'/1]).
|
||||
-define(kernel, 'Elixir.Kernel').
|
||||
|
||||
%% Mock out @ to be a no-op unless Kernel is defined.
|
||||
'MACRO-@'(Caller, Tree) ->
|
||||
unless_loaded('MACRO-@', [Caller, Tree], fun() -> nil end).
|
||||
|
||||
'MACRO-def'(Caller, Call) -> 'MACRO-def'(Caller, Call, nil).
|
||||
'MACRO-def'(Caller, Call, Expr) -> define(Caller, def, Call, Expr).
|
||||
'MACRO-defp'(Caller, Call, Expr) -> define(Caller, defp, Call, Expr).
|
||||
|
||||
'MACRO-defmacro'(Caller, Call) -> 'MACRO-defmacro'(Caller, Call, nil).
|
||||
'MACRO-defmacro'(Caller, Call, Expr) -> define(Caller, defmacro, Call, Expr).
|
||||
'MACRO-defmacrop'(Caller, Call, Expr) -> define(Caller, defmacrop, Call, Expr).
|
||||
|
||||
'MACRO-defmodule'(_Caller, Alias, [{do,Block}]) ->
|
||||
{ Escaped, _ } = elixir_quote:escape(Block, false),
|
||||
Args = [Alias, Escaped, [], env()],
|
||||
{ { '.', [], [elixir_module, compile] }, [], Args }.
|
||||
|
||||
'__info__'(functions) ->
|
||||
[];
|
||||
'__info__'(macros) ->
|
||||
[{ '@', 1 },
|
||||
{def,1},
|
||||
{def,2},
|
||||
{defmacro,1},
|
||||
{defmacro,2},
|
||||
{defmacrop,2},
|
||||
{defmodule,2},
|
||||
{defp,2}].
|
||||
|
||||
define({Line,E}, Kind, Call, Expr) ->
|
||||
{ EscapedCall, UC } = elixir_quote:escape(Call, true),
|
||||
{ EscapedExpr, UE } = elixir_quote:escape(Expr, true),
|
||||
Args = [Line, Kind, not(UC or UE), EscapedCall, EscapedExpr, elixir_locals:cache_env(E)],
|
||||
{ { '.', [], [elixir_def, store_definition] }, [], Args }.
|
||||
|
||||
unless_loaded(Fun, Args, Callback) ->
|
||||
case code:is_loaded(?kernel) of
|
||||
{ _, _} -> apply(?kernel, Fun, Args);
|
||||
false -> Callback()
|
||||
end.
|
||||
|
||||
env() ->
|
||||
{ '__ENV__', [], nil }.
|
||||
@@ -1,134 +1,109 @@
|
||||
%% Handle code related to args, guard and -> matching for case,
|
||||
%% fn, receive and friends. try is handled in elixir_try.
|
||||
-module(elixir_clauses).
|
||||
-export([
|
||||
assigns/3, assigns_block/5, assigns_block/6, extract_splat_guards/1,
|
||||
get_pairs/4, get_pairs/5, match/3, extract_args/1, extract_guards/1]).
|
||||
-export([match/3, clause/6, clauses/3, get_pairs/2, get_pairs/3,
|
||||
extract_splat_guards/1, extract_guards/1]).
|
||||
-include("elixir.hrl").
|
||||
|
||||
%% Get pairs from a clause.
|
||||
|
||||
get_pairs(Meta, Key, Clauses, S) ->
|
||||
get_pairs(Meta, Key, Clauses, S, false).
|
||||
|
||||
get_pairs(Meta, Key, Clauses, S, AllowNil) ->
|
||||
get_pairs(Key, Clauses) ->
|
||||
get_pairs(Key, Clauses, false).
|
||||
get_pairs(Key, Clauses, AllowNil) ->
|
||||
case lists:keyfind(Key, 1, Clauses) of
|
||||
{ Key, { '->', _, Pairs } } ->
|
||||
[{ Key, PMeta, Left, Right } || { Left, PMeta, Right } <- Pairs];
|
||||
{ Key, Pairs } when is_list(Pairs) ->
|
||||
[{ Key, Meta, Left, Right } || { '->', Meta, [Left, Right] } <- Pairs];
|
||||
{ Key, nil } when AllowNil ->
|
||||
[];
|
||||
{ Key, _ } ->
|
||||
elixir_errors:syntax_error(Meta, S#elixir_scope.file, "expected pairs with -> for key ~ts", [Key]);
|
||||
_ ->
|
||||
false ->
|
||||
[]
|
||||
end.
|
||||
|
||||
% Function for translating assigns.
|
||||
%% Translate matches
|
||||
|
||||
assigns(Fun, Args, #elixir_scope{context=Context, temp_vars=TempVars,
|
||||
match(Fun, Args, #elixir_scope{context=Context, match_vars=MatchVars,
|
||||
backup_vars=BackupVars, vars=Vars} = S) when Context /= match ->
|
||||
{ Result, NewS } = assigns(Fun, Args, S#elixir_scope{context=match,
|
||||
temp_vars=ordsets:new(), backup_vars=Vars}),
|
||||
{ Result, NewS } = match(Fun, Args, S#elixir_scope{context=match,
|
||||
match_vars=ordsets:new(), backup_vars=Vars}),
|
||||
{ Result, NewS#elixir_scope{context=Context,
|
||||
temp_vars=TempVars, backup_vars=BackupVars} };
|
||||
match_vars=MatchVars, backup_vars=BackupVars} };
|
||||
match(Fun, Args, S) -> Fun(Args, S).
|
||||
|
||||
assigns(Fun, Args, S) -> Fun(Args, S).
|
||||
%% Translate clauses with args, guards and expressions
|
||||
|
||||
%% Function for translating a block that is preceeded by an
|
||||
%% assignment and optional guards. This is used by def* and fn.
|
||||
clause(Line, Fun, Args, Expr, Guards, S) when is_integer(Line) ->
|
||||
{ TArgs, SA } = match(Fun, Args, S#elixir_scope{extra_guards=[]}),
|
||||
{ TExpr, SE } = elixir_translator:translate(Expr, SA#elixir_scope{extra_guards=nil}),
|
||||
|
||||
assigns_block(Line, Fun, BareArgs, Exprs, S) ->
|
||||
{ Args, Guards } = extract_guards(BareArgs),
|
||||
assigns_block(Line, Fun, Args, Exprs, Guards, S).
|
||||
SG = SA#elixir_scope{context=guard, extra_guards=nil},
|
||||
Extra = SA#elixir_scope.extra_guards,
|
||||
|
||||
assigns_block(Line, Fun, Args, Exprs, Guards, S) when is_integer(Line) ->
|
||||
{ TArgs, SA } = assigns(Fun, Args, S#elixir_scope{extra_guards=[]}),
|
||||
{ TExprs, SE } = elixir_translator:translate(Exprs, SA#elixir_scope{extra_guards=nil}),
|
||||
|
||||
FArgs = listify(TArgs),
|
||||
SG = SA#elixir_scope{context=guard, extra_guards=nil},
|
||||
Extra = SA#elixir_scope.extra_guards,
|
||||
|
||||
FGuards = case Guards of
|
||||
TGuards = case Guards of
|
||||
[] -> case Extra of [] -> []; _ -> [Extra] end;
|
||||
_ -> [translate_guard(Line, Guard, Extra, SG) || Guard <- Guards]
|
||||
end,
|
||||
|
||||
% Uncompact expressions from the block.
|
||||
case TExprs of
|
||||
[{ block, _, FExprs }] -> [];
|
||||
_ -> FExprs = TExprs
|
||||
end,
|
||||
|
||||
{ { clause, Line, FArgs, FGuards, FExprs }, SE }.
|
||||
{ { clause, Line, TArgs, TGuards, unblock(TExpr) }, SE }.
|
||||
|
||||
% Translate/Extract guards from the given expression.
|
||||
|
||||
translate_guard(Line, Guard, Extra, S) ->
|
||||
[element(1, elixir_translator:translate_each(elixir_quote:linify(Line, Guard), S))|Extra].
|
||||
[element(1, elixir_translator:translate(elixir_quote:linify(Line, Guard), S))|Extra].
|
||||
|
||||
extract_guards({ 'when', _, [Left, Right] }) -> { Left, extract_or_clauses(Right, []) };
|
||||
extract_guards({ 'when', _, [Left, Right] }) -> { Left, extract_or_guards(Right) };
|
||||
extract_guards(Else) -> { Else, [] }.
|
||||
|
||||
extract_or_clauses({ 'when', _, [Left, Right] }, Acc) -> extract_or_clauses(Right, [Left|Acc]);
|
||||
extract_or_clauses(Term, Acc) -> [Term|Acc].
|
||||
|
||||
% Extract name and args from the given expression.
|
||||
|
||||
extract_args({ { '.', _, [Name] }, _, Args }) when is_atom(Name), is_list(Args) -> { Name, Args };
|
||||
extract_args({ Name, _, Args }) when is_atom(Name), is_atom(Args) -> { Name, [] };
|
||||
extract_args({ Name, _, Args }) when is_atom(Name), is_list(Args) -> { Name, Args };
|
||||
extract_args(_) -> error.
|
||||
extract_or_guards({ 'when', _, [Left, Right] }) -> [Left|extract_or_guards(Right)];
|
||||
extract_or_guards(Term) -> [Term].
|
||||
|
||||
% Extract guards when multiple left side args are allowed.
|
||||
|
||||
extract_splat_guards([{ 'when', _, [_,_|_] = Args }]) ->
|
||||
{ Left, Right } = elixir_utils:split_last(Args),
|
||||
{ Left, extract_or_clauses(Right, []) };
|
||||
{ Left, extract_or_guards(Right) };
|
||||
extract_splat_guards(Else) ->
|
||||
{ Else, [] }.
|
||||
|
||||
% Function for translating macros with match style like case and receive.
|
||||
|
||||
match(Meta, Clauses, #elixir_scope{clause_vars=C1} = S) ->
|
||||
{ TC, TS } = do_match(Meta, Clauses, S#elixir_scope{clause_vars=orddict:new()}),
|
||||
C2 = TS#elixir_scope.clause_vars,
|
||||
{ TC, TS#elixir_scope{clause_vars=elixir_scope:merge_clause_vars(C1, C2)} }.
|
||||
clauses(Meta, Clauses, #elixir_scope{export_vars=CV} = S) ->
|
||||
{ TC, TS } = do_clauses(Meta, Clauses, S#elixir_scope{export_vars=[]}),
|
||||
{ TC, TS#elixir_scope{export_vars=elixir_scope:merge_opt_vars(CV, TS#elixir_scope.export_vars)} }.
|
||||
|
||||
do_match(_Meta, [], S) ->
|
||||
do_clauses(_Meta, [], S) ->
|
||||
{ [], S };
|
||||
|
||||
do_match(_Meta, [DecoupledClause], S) ->
|
||||
{ TDecoupledClause, TS } = each_clause(DecoupledClause, S),
|
||||
{ [TDecoupledClause], TS };
|
||||
% do_clauses(_Meta, [DecoupledClause], S) ->
|
||||
% { TDecoupledClause, TS } = each_clause(DecoupledClause, S),
|
||||
% { [TDecoupledClause], TS };
|
||||
|
||||
do_match(Meta, DecoupledClauses, S) ->
|
||||
do_clauses(Meta, DecoupledClauses, S) ->
|
||||
% Transform tree just passing the variables counter forward
|
||||
% and storing variables defined inside each clause.
|
||||
Transformer = fun(X, {Acc, CV}) ->
|
||||
{ TX, TAcc } = each_clause(X, Acc),
|
||||
{ TX, { merge_clauses_scope(S, TAcc), [TAcc#elixir_scope.clause_vars|CV] } }
|
||||
Transformer = fun(X, {SAcc, VAcc}) ->
|
||||
{ TX, TS } = each_clause(X, SAcc),
|
||||
{ TX,
|
||||
{ elixir_scope:mergec(S, TS), [TS#elixir_scope.export_vars|VAcc] } }
|
||||
end,
|
||||
|
||||
{ TClauses, { TS, ReverseCV } } = lists:mapfoldl(Transformer, {S, []}, DecoupledClauses),
|
||||
{ TClauses, { TS, ReverseCV } } =
|
||||
lists:mapfoldl(Transformer, {S, []}, DecoupledClauses),
|
||||
|
||||
% Now get all the variables defined inside each clause
|
||||
CV = lists:reverse(ReverseCV),
|
||||
AllVars = lists:foldl(fun(KV, Acc) ->
|
||||
elixir_scope:merge_clause_vars(Acc, KV)
|
||||
end, orddict:new(), CV),
|
||||
AllVars = lists:foldl(fun elixir_scope:merge_vars/2, [], CV),
|
||||
|
||||
% Create a new scope that contains a list of all variables
|
||||
% defined inside all the clauses. It returns this new scope and
|
||||
% a list of tuples where the first element is the variable name,
|
||||
% the second one is the new pointer to the variable and the third
|
||||
% is the old pointer.
|
||||
{ FinalVars, FS } = lists:mapfoldl(fun({ Key, Ref }, Acc) ->
|
||||
normalize_vars(Key, Ref, Acc)
|
||||
{ FinalVars, FS } = lists:mapfoldl(fun({ Key, Val }, Acc) ->
|
||||
normalize_vars(Key, Val, Acc)
|
||||
end, TS, AllVars),
|
||||
|
||||
% Expand all clauses by adding a match operation at the end
|
||||
% that assigns variables missing in one clause to the others.
|
||||
% that defines variables missing in one clause to the others.
|
||||
expand_clauses(?line(Meta), TClauses, CV, FinalVars, [], FS).
|
||||
|
||||
expand_clauses(Line, [Clause|T], [ClauseVars|V], FinalVars, Acc, S) ->
|
||||
@@ -149,7 +124,8 @@ expand_clauses(Line, [Clause|T], [ClauseVars|V], FinalVars, Acc, S) ->
|
||||
{ match, _, { var, _, UserVarName } = UserVar, _ } when UserVarName /= '_' ->
|
||||
{ [UserVar,MatchExpr,Final|RawClauseExprs], S };
|
||||
_ ->
|
||||
{ StorageVar, SS } = elixir_scope:build_erl_var(Line, S),
|
||||
{ VarName, _, SS } = elixir_scope:build_var('_', S),
|
||||
StorageVar = { var, Line, VarName },
|
||||
StorageExpr = { match, Line, StorageVar, Final },
|
||||
{ [StorageVar,MatchExpr,StorageExpr|RawClauseExprs], SS }
|
||||
end;
|
||||
@@ -167,21 +143,17 @@ expand_clauses(_Line, [], [], _FinalVars, Acc, S) ->
|
||||
% Handle each key/value clause pair and translate them accordingly.
|
||||
|
||||
each_clause({ do, Meta, [Condition], Expr }, S) ->
|
||||
assigns_block(?line(Meta), fun elixir_translator:translate_each/2, Condition, [Expr], S);
|
||||
{ Arg, Guards } = extract_guards(Condition),
|
||||
clause(?line(Meta), fun elixir_translator:translate_many/2, [Arg], Expr, Guards, S);
|
||||
|
||||
each_clause({ else, Meta, [Condition], Expr }, S) ->
|
||||
assigns_block(?line(Meta), fun elixir_translator:translate_each/2, Condition, [Expr], S);
|
||||
{ Arg, Guards } = extract_guards(Condition),
|
||||
clause(?line(Meta), fun elixir_translator:translate_many/2, [Arg], Expr, Guards, S);
|
||||
|
||||
each_clause({ 'after', Meta, [Condition], Expr }, S) ->
|
||||
{ TCondition, SC } = elixir_translator:translate_each(Condition, S),
|
||||
{ TBody, SB } = elixir_translator:translate([Expr], SC),
|
||||
{ { clause, ?line(Meta), [TCondition], [], TBody }, SB };
|
||||
|
||||
each_clause({ Key, Meta, [_|_], _ }, S) when Key == do; Key == 'after' ->
|
||||
elixir_errors:syntax_error(Meta, S#elixir_scope.file, "too many arguments given for ~ts", [Key]);
|
||||
|
||||
each_clause({ Key, Meta, _, _ }, S) ->
|
||||
elixir_errors:syntax_error(Meta, S#elixir_scope.file, "invalid key ~ts", [Key]).
|
||||
{ TCondition, SC } = elixir_translator:translate(Condition, S),
|
||||
{ TExpr, SB } = elixir_translator:translate(Expr, SC),
|
||||
{ { clause, ?line(Meta), [TCondition], [], unblock(TExpr) }, SB }.
|
||||
|
||||
% Check if the given expression is a match tuple.
|
||||
% This is a small optimization to allow us to change
|
||||
@@ -189,55 +161,51 @@ each_clause({ Key, Meta, _, _ }, S) ->
|
||||
|
||||
has_match_tuple({'receive', _, _, _, _}) ->
|
||||
true;
|
||||
|
||||
has_match_tuple({'receive', _, _}) ->
|
||||
true;
|
||||
|
||||
has_match_tuple({'case', _, _, _}) ->
|
||||
true;
|
||||
|
||||
has_match_tuple({match, _, _, _}) ->
|
||||
true;
|
||||
|
||||
has_match_tuple({'fun', _, { clauses, _ }}) ->
|
||||
false;
|
||||
|
||||
has_match_tuple(H) when is_tuple(H) ->
|
||||
has_match_tuple(tuple_to_list(H));
|
||||
|
||||
has_match_tuple(H) when is_list(H) ->
|
||||
lists:any(fun has_match_tuple/1, H);
|
||||
|
||||
has_match_tuple(_) -> false.
|
||||
|
||||
% Normalize the given var in between clauses
|
||||
% by picking one value as reference and retriving
|
||||
% its previous value.
|
||||
|
||||
normalize_vars(Key, Value, #elixir_scope{vars=Vars,clause_vars=ClauseVars} = S) ->
|
||||
FS = S#elixir_scope{
|
||||
normalize_vars(Key, Value, #elixir_scope{vars=Vars,export_vars=ClauseVars} = S) ->
|
||||
VS = S#elixir_scope{
|
||||
vars=orddict:store(Key, Value, Vars),
|
||||
clause_vars=orddict:store(Key, Value, ClauseVars)
|
||||
export_vars=orddict:store(Key, Value, ClauseVars)
|
||||
},
|
||||
|
||||
Expr = case orddict:find(Key, Vars) of
|
||||
{ ok, OldValue } -> { var, 0, OldValue };
|
||||
{ ok, { PreValue, _ } } -> { var, 0, PreValue };
|
||||
error -> { atom, 0, nil }
|
||||
end,
|
||||
|
||||
{ { Key, Value, Expr }, FS }.
|
||||
{ { Key, Value, Expr }, VS }.
|
||||
|
||||
% Generate match vars by checking if they were updated
|
||||
% or not and assigning the previous value.
|
||||
|
||||
generate_match_vars([{ Key, NewValue, OldValue }|T], ClauseVars, Left, Right) ->
|
||||
generate_match_vars([{ Key, Value, Expr }|T], ClauseVars, Left, Right) ->
|
||||
case orddict:find(Key, ClauseVars) of
|
||||
{ ok, NewValue } ->
|
||||
{ ok, Value } ->
|
||||
generate_match_vars(T, ClauseVars, Left, Right);
|
||||
{ ok, ClauseValue } ->
|
||||
generate_match_vars(T, ClauseVars, [{ var, 0, NewValue }|Left], [{ var, 0, ClauseValue }|Right]);
|
||||
{ ok, Clause } ->
|
||||
generate_match_vars(T, ClauseVars,
|
||||
[{ var, 0, element(1, Value) }|Left],
|
||||
[{ var, 0, element(1, Clause) }|Right]);
|
||||
error ->
|
||||
generate_match_vars(T, ClauseVars, [{ var, 0, NewValue }|Left], [OldValue|Right])
|
||||
generate_match_vars(T, ClauseVars,
|
||||
[{ var, 0, element(1, Value) }|Left], [Expr|Right])
|
||||
end;
|
||||
|
||||
generate_match_vars([], _ClauseVars, Left, Right) ->
|
||||
@@ -249,15 +217,5 @@ generate_match(Line, [Left], [Right]) ->
|
||||
generate_match(Line, LeftVars, RightVars) ->
|
||||
{ match, Line, { tuple, Line, LeftVars }, { tuple, Line, RightVars } }.
|
||||
|
||||
listify(Expr) when not is_list(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
|
||||
}.
|
||||
unblock({ 'block', _, Exprs }) -> Exprs;
|
||||
unblock(Exprs) -> [Exprs].
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
-export([start_link/0, init/1, handle_call/3, handle_cast/2,
|
||||
handle_info/2, terminate/2, code_change/3]).
|
||||
-behavior(gen_server).
|
||||
|
||||
-define(timeout, 30000).
|
||||
-record(elixir_code_server, {
|
||||
compilation_status=[],
|
||||
argv=[],
|
||||
@@ -14,21 +16,19 @@
|
||||
}).
|
||||
|
||||
call(Args) ->
|
||||
gen_server:call(?MODULE, Args, get_timeout()).
|
||||
gen_server:call(?MODULE, Args, ?timeout).
|
||||
|
||||
cast(Args) ->
|
||||
gen_server:cast(?MODULE, Args).
|
||||
|
||||
get_timeout() ->
|
||||
30000.
|
||||
|
||||
%% Callbacks
|
||||
|
||||
start_link() ->
|
||||
{ ok, _ } = gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).
|
||||
gen_server:start_link({local, ?MODULE}, ?MODULE, ok, []).
|
||||
|
||||
init(_Args) ->
|
||||
code:ensure_loaded('Elixir.Module.DispatchTracker'),
|
||||
init(ok) ->
|
||||
code:ensure_loaded('Elixir.Macro.Env'),
|
||||
code:ensure_loaded('Elixir.Module.LocalsTracker'),
|
||||
code:ensure_loaded('Elixir.Kernel.LexicalTracker'),
|
||||
{ ok, #elixir_code_server{} }.
|
||||
|
||||
@@ -83,8 +83,8 @@ handle_call(erl_compiler_options, _From, Config) ->
|
||||
{ reply, Opts, Config }
|
||||
end;
|
||||
|
||||
handle_call(_Request, _From, Config) ->
|
||||
{ reply, undef, Config }.
|
||||
handle_call(Request, _From, Config) ->
|
||||
{ stop, { badcall, Request }, Config }.
|
||||
|
||||
handle_cast({ at_exit, AtExit }, Config) ->
|
||||
{ noreply, Config#elixir_code_server{at_exit=[AtExit|Config#elixir_code_server.at_exit]} };
|
||||
@@ -131,8 +131,8 @@ handle_cast({ unload_files, Files }, Config) ->
|
||||
handle_cast({ return_module_name, H }, #elixir_code_server{pool={T,Counter}} = Config) ->
|
||||
{ noreply, Config#elixir_code_server{pool={[H|T],Counter}} };
|
||||
|
||||
handle_cast(_Request, Config) ->
|
||||
{ noreply, Config }.
|
||||
handle_cast(Request, Config) ->
|
||||
{ stop, { badcast, Request }, Config }.
|
||||
|
||||
handle_info(_Request, Config) ->
|
||||
{ noreply, Config }.
|
||||
@@ -143,7 +143,8 @@ terminate(_Reason, _Config) ->
|
||||
code_change(_Old, Config, _Extra) ->
|
||||
{ ok, Config }.
|
||||
|
||||
module_tuple(I) -> { list_to_atom("elixir_compiler_" ++ integer_to_list(I)), I }.
|
||||
module_tuple(I) ->
|
||||
{ list_to_atom("elixir_compiler_" ++ integer_to_list(I)), I }.
|
||||
|
||||
erl_compiler_options() ->
|
||||
Key = "ERL_COMPILER_OPTIONS",
|
||||
|
||||
@@ -1,27 +1,21 @@
|
||||
-module(elixir_compiler).
|
||||
-export([get_opts/0, get_opt/1, get_opt/2, string/2, quoted/2, file/1, file_to_path/2]).
|
||||
-export([core/0, module/4, eval_forms/4, format_error/1]).
|
||||
-export([get_opt/1, string/2, quoted/2, file/1, file_to_path/2]).
|
||||
-export([core/0, module/4, eval_forms/3, format_error/1]).
|
||||
-include("elixir.hrl").
|
||||
|
||||
%% Public API
|
||||
|
||||
%% Get compilation options.
|
||||
|
||||
get_opt(Key) -> get_opt(Key, get_opts()).
|
||||
|
||||
get_opt(Key, Dict) ->
|
||||
get_opt(Key) ->
|
||||
Dict = elixir_code_server:call(compiler_options),
|
||||
case lists:keyfind(Key, 1, Dict) of
|
||||
false -> false;
|
||||
{ Key, Value } -> Value
|
||||
end.
|
||||
|
||||
get_opts() ->
|
||||
elixir_code_server:call(compiler_options).
|
||||
|
||||
%% Compilation entry points.
|
||||
|
||||
string(Contents, File) when is_list(Contents), is_binary(File) ->
|
||||
Forms = elixir_translator:'forms!'(Contents, 1, File, []),
|
||||
Forms = elixir:'string_to_quoted!'(Contents, 1, File, []),
|
||||
quoted(Forms, File).
|
||||
|
||||
quoted(Forms, File) when is_binary(File) ->
|
||||
@@ -31,8 +25,8 @@ quoted(Forms, File) when is_binary(File) ->
|
||||
put(elixir_compiled, []),
|
||||
elixir_lexical:run(File, fun
|
||||
(Pid) ->
|
||||
Scope = elixir:scope_for_eval([{file,File}]),
|
||||
eval_forms(Forms, 1, [], Scope#elixir_scope{lexical_tracker=Pid})
|
||||
Env = elixir:env_for_eval([{line,1},{file,File}]),
|
||||
eval_forms(Forms, [], Env#elixir_env{lexical_tracker=Pid})
|
||||
end),
|
||||
lists:reverse(get(elixir_compiled))
|
||||
after
|
||||
@@ -51,51 +45,65 @@ file_to_path(File, Path) when is_binary(File), is_binary(Path) ->
|
||||
|
||||
%% Evaluation
|
||||
|
||||
eval_forms(Forms, Line, Vars, S) ->
|
||||
{ Module, I } = retrieve_module_name(),
|
||||
{ Exprs, FS } = elixir_translator:translate(Forms, S),
|
||||
eval_forms(Forms, Vars, E) ->
|
||||
case (E#elixir_env.module == nil) andalso allows_fast_compilation(Forms) of
|
||||
true -> eval_compilation(Forms, Vars, E);
|
||||
false -> code_loading_compilation(Forms, Vars, E)
|
||||
end.
|
||||
|
||||
Fun = eval_fun(S#elixir_scope.module),
|
||||
Form = eval_mod(Fun, Exprs, Line, S#elixir_scope.file, Module, Vars),
|
||||
Args = [X || { _, _, _, X } <- Vars],
|
||||
eval_compilation(Forms, Vars, E) ->
|
||||
Binding = [{ Key, Value } || { _Name, _Kind, Key, Value } <- Vars],
|
||||
{ Result, _Binding, EE, _S } = elixir:eval_forms(Forms, Binding, E),
|
||||
{ Result, EE }.
|
||||
|
||||
code_loading_compilation(Forms, Vars, #elixir_env{line=Line} = E) ->
|
||||
Dict = [{ { Name, Kind }, { Value, 0 } } || { Name, Kind, Value, _ } <- Vars],
|
||||
S = elixir_env:env_to_scope_with_vars(E, Dict),
|
||||
{ Expr, EE, _S } = elixir:quoted_to_erl(Forms, E, S),
|
||||
|
||||
{ Module, I } = retrieve_module_name(),
|
||||
Fun = code_fun(E#elixir_env.module),
|
||||
Form = code_mod(Fun, Expr, Line, E#elixir_env.file, Module, Vars),
|
||||
Args = list_to_tuple([V || { _, _, _, V } <- Vars]),
|
||||
|
||||
%% Pass { native, false } to speed up bootstrap
|
||||
%% process when native is set to true
|
||||
{ module(Form, S#elixir_scope.file, [{native,false}], true, fun(_, Binary) ->
|
||||
Res = Module:Fun(S#elixir_scope.module, Args),
|
||||
code:delete(Module),
|
||||
|
||||
module(Form, E#elixir_env.file, [{native,false}], true, fun(_, Binary) ->
|
||||
%% If we have labeled locals, anonymous functions
|
||||
%% were created and therefore we cannot ditch the
|
||||
%% module
|
||||
case beam_lib:chunks(Binary, [labeled_locals]) of
|
||||
{ ok, { _, [{ labeled_locals, []}] } } ->
|
||||
code:purge(Module),
|
||||
return_module_name(I);
|
||||
_ ->
|
||||
ok
|
||||
end,
|
||||
Purgeable =
|
||||
case beam_lib:chunks(Binary, [labeled_locals]) of
|
||||
{ ok, { _, [{ labeled_locals, []}] } } -> true;
|
||||
_ -> false
|
||||
end,
|
||||
dispatch_loaded(Module, Fun, Args, Purgeable, I, EE)
|
||||
end).
|
||||
|
||||
Res
|
||||
end), FS }.
|
||||
dispatch_loaded(Module, Fun, Args, Purgeable, I, E) ->
|
||||
Res = Module:Fun(Args),
|
||||
code:delete(Module),
|
||||
if Purgeable ->
|
||||
code:purge(Module),
|
||||
return_module_name(I);
|
||||
true ->
|
||||
ok
|
||||
end,
|
||||
{ Res, E }.
|
||||
|
||||
eval_fun(nil) -> '__FILE__';
|
||||
eval_fun(_) -> '__MODULE__'.
|
||||
code_fun(nil) -> '__FILE__';
|
||||
code_fun(_) -> '__MODULE__'.
|
||||
|
||||
eval_mod(Fun, Exprs, Line, File, Module, Vars) when is_binary(File), is_integer(Line) ->
|
||||
Cons = lists:foldr(fun({ _, _, Var, _ }, Acc) ->
|
||||
{ cons, Line, { var, Line, Var }, Acc }
|
||||
end, { nil, Line }, Vars),
|
||||
|
||||
Args = [{ var, Line, '_@MODULE'}, Cons],
|
||||
code_mod(Fun, Expr, Line, File, Module, Vars) when is_binary(File), is_integer(Line) ->
|
||||
Tuple = { tuple, Line, [{ var, Line, K } || { _, _, K, _ } <- Vars] },
|
||||
Relative = elixir_utils:relative_to_cwd(File),
|
||||
|
||||
[
|
||||
{ attribute, Line, file, { elixir_utils:characters_to_list(File), 1 } },
|
||||
{ attribute, Line, file, { elixir_utils:characters_to_list(Relative), 1 } },
|
||||
{ attribute, Line, module, Module },
|
||||
{ attribute, Line, export, [{ Fun, 2 }, { '__RELATIVE__', 0 }] },
|
||||
{ function, Line, Fun, length(Args), [
|
||||
{ clause, Line, Args, [], Exprs }
|
||||
{ attribute, Line, export, [{ Fun, 1 }, { '__RELATIVE__', 0 }] },
|
||||
{ function, Line, Fun, 1, [
|
||||
{ clause, Line, [Tuple], [], [Expr] }
|
||||
] },
|
||||
{ function, Line, '__RELATIVE__', 0, [
|
||||
{ clause, Line, [], [], [elixir_utils:elixir_to_erl(Relative)] }
|
||||
@@ -108,6 +116,11 @@ retrieve_module_name() ->
|
||||
return_module_name(I) ->
|
||||
elixir_code_server:cast({ return_module_name, I }).
|
||||
|
||||
allows_fast_compilation({ '__block__', _, Exprs }) ->
|
||||
lists:all(fun allows_fast_compilation/1, Exprs);
|
||||
allows_fast_compilation({defmodule,_,_}) -> true;
|
||||
allows_fast_compilation(_) -> false.
|
||||
|
||||
%% INTERNAL API
|
||||
|
||||
%% Compile the module by forms based on the scope information
|
||||
@@ -182,13 +195,14 @@ core_main() ->
|
||||
<<"lib/elixir/lib/keyword.ex">>,
|
||||
<<"lib/elixir/lib/module.ex">>,
|
||||
<<"lib/elixir/lib/list.ex">>,
|
||||
<<"lib/elixir/lib/kernel/typespec.ex">>,
|
||||
<<"lib/elixir/lib/record.ex">>,
|
||||
<<"lib/elixir/lib/macro.ex">>,
|
||||
<<"lib/elixir/lib/macro/env.ex">>,
|
||||
<<"lib/elixir/lib/exception.ex">>,
|
||||
<<"lib/elixir/lib/code.ex">>,
|
||||
<<"lib/elixir/lib/module/locals_tracker.ex">>,
|
||||
<<"lib/elixir/lib/protocol.ex">>,
|
||||
<<"lib/elixir/lib/stream/reducers.ex">>,
|
||||
<<"lib/elixir/lib/enum.ex">>,
|
||||
<<"lib/elixir/lib/inspect/algebra.ex">>,
|
||||
<<"lib/elixir/lib/inspect.ex">>,
|
||||
@@ -199,12 +213,12 @@ core_main() ->
|
||||
<<"lib/elixir/lib/io.ex">>,
|
||||
<<"lib/elixir/lib/path.ex">>,
|
||||
<<"lib/elixir/lib/system.ex">>,
|
||||
<<"lib/elixir/lib/kernel/typespec.ex">>,
|
||||
<<"lib/elixir/lib/kernel/cli.ex">>,
|
||||
<<"lib/elixir/lib/kernel/error_handler.ex">>,
|
||||
<<"lib/elixir/lib/kernel/parallel_compiler.ex">>,
|
||||
<<"lib/elixir/lib/kernel/record_rewriter.ex">>,
|
||||
<<"lib/elixir/lib/kernel/lexical_tracker.ex">>,
|
||||
<<"lib/elixir/lib/module/dispatch_tracker.ex">>
|
||||
<<"lib/elixir/lib/kernel/lexical_tracker.ex">>
|
||||
].
|
||||
|
||||
binary_to_path({ModuleName, Binary}, CompilePath) ->
|
||||
@@ -216,17 +230,19 @@ binary_to_path({ModuleName, Binary}, CompilePath) ->
|
||||
|
||||
format_error({ skip_native, Module }) ->
|
||||
io_lib:format("skipping native compilation for ~ts because it contains on_load attribute",
|
||||
[elixir_errors:inspect(Module)]).
|
||||
[elixir_aliases:inspect(Module)]).
|
||||
|
||||
format_errors([]) ->
|
||||
exit({ nocompile, "compilation failed but no error was raised" });
|
||||
|
||||
format_errors(Errors) ->
|
||||
lists:foreach(fun ({File, Each}) ->
|
||||
lists:foreach(fun (Error) -> elixir_errors:handle_file_error(File, Error) end, Each)
|
||||
BinFile = elixir_utils:characters_to_binary(File),
|
||||
lists:foreach(fun (Error) -> elixir_errors:handle_file_error(BinFile, Error) end, Each)
|
||||
end, Errors).
|
||||
|
||||
format_warnings(Bootstrap, Warnings) ->
|
||||
lists:foreach(fun ({File, Each}) ->
|
||||
lists:foreach(fun (Warning) -> elixir_errors:handle_file_warning(Bootstrap, File, Warning) end, Each)
|
||||
BinFile = elixir_utils:characters_to_binary(File),
|
||||
lists:foreach(fun (Warning) -> elixir_errors:handle_file_warning(Bootstrap, BinFile, Warning) end, Each)
|
||||
end, Warnings).
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
-module(elixir_counter).
|
||||
-export([start_link/0, init/1, handle_call/3, handle_cast/2,
|
||||
handle_info/2, terminate/2, code_change/3, next/0]).
|
||||
-behavior(gen_server).
|
||||
|
||||
-define(timeout, 1000). %% 1 second
|
||||
-define(limit, 4294967296). %% 2^32
|
||||
|
||||
next() ->
|
||||
gen_server:call(?MODULE, next, ?timeout).
|
||||
|
||||
start_link() ->
|
||||
gen_server:start_link({local, ?MODULE}, ?MODULE, 0, []).
|
||||
|
||||
init(Counter) ->
|
||||
{ ok, Counter }.
|
||||
|
||||
handle_call(next, _From, Counter) ->
|
||||
{ reply, Counter, bump(Counter) };
|
||||
handle_call(Request, _From, Counter) ->
|
||||
{ stop, { badcall, Request }, Counter }.
|
||||
|
||||
handle_cast(Request, Counter) ->
|
||||
{ stop, { badcast, Request }, Counter }.
|
||||
|
||||
handle_info(_Request, Counter) ->
|
||||
{ noreply, Counter }.
|
||||
|
||||
terminate(_Reason, _Counter) ->
|
||||
ok.
|
||||
|
||||
code_change(_Old, Counter, _Extra) ->
|
||||
{ ok, Counter }.
|
||||
|
||||
bump(Counter) when Counter < ?limit ->
|
||||
Counter + 1;
|
||||
bump(_Counter) ->
|
||||
0.
|
||||
+106
-127
@@ -1,17 +1,9 @@
|
||||
% Holds the logic responsible for functions definition (def(p) and defmacro(p)).
|
||||
-module(elixir_def).
|
||||
-export([table/1,
|
||||
clauses_table/1,
|
||||
setup/1,
|
||||
cleanup/1,
|
||||
reset_last/1,
|
||||
lookup_definition/2,
|
||||
delete_definition/2,
|
||||
wrap_definition/6,
|
||||
store_definition/7,
|
||||
store_each/8,
|
||||
unwrap_stored_definitions/2,
|
||||
format_error/1]).
|
||||
-export([table/1, clauses_table/1, setup/1,
|
||||
cleanup/1, reset_last/1, lookup_definition/2,
|
||||
delete_definition/2, store_definition/6, unwrap_definitions/1,
|
||||
store_each/8, format_error/1]).
|
||||
-include("elixir.hrl").
|
||||
|
||||
-define(attr, '__def_table').
|
||||
@@ -53,76 +45,59 @@ 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.
|
||||
%%
|
||||
%% We need to wrap functions instead of eagerly defining them to ensure
|
||||
%% functions inside branches won't propagate, for example:
|
||||
%%
|
||||
%% if false do
|
||||
%% def bar, do: 1
|
||||
%% else
|
||||
%% def bar, do: 2
|
||||
%% end
|
||||
%%
|
||||
%% 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, CheckClauses, S) ->
|
||||
Line = ?line(Meta),
|
||||
?wrap_call(Line, ?MODULE, store_definition, [
|
||||
{atom, Line, Kind},
|
||||
{integer, Line, Line},
|
||||
{atom, Line, CheckClauses},
|
||||
{var, Line, '_@MODULE'},
|
||||
Call,
|
||||
Expr,
|
||||
elixir_scope:serialize(S)
|
||||
]).
|
||||
|
||||
% Invoked by the wrap definition with the function abstract tree.
|
||||
% Each function is then added to the function table.
|
||||
|
||||
store_definition(Kind, Line, CheckClauses, Module, Call, Body, RawS) ->
|
||||
S = elixir_scope:deserialize(RawS),
|
||||
store_definition(Line, Kind, CheckClauses, Call, Body, Pos) ->
|
||||
E = (elixir_locals:get_cached_env(Pos))#elixir_env{line=Line},
|
||||
{ NameAndArgs, Guards } = elixir_clauses:extract_guards(Call),
|
||||
|
||||
{ Name, Args } = case elixir_clauses:extract_args(NameAndArgs) of
|
||||
error ->
|
||||
Format = [Kind, 'Elixir.Macro':to_string(NameAndArgs)],
|
||||
elixir_errors:syntax_error(Line, S#elixir_scope.file, "invalid syntax in ~ts ~ts", Format);
|
||||
Tuple ->
|
||||
Tuple
|
||||
{ Name, Args } = case NameAndArgs of
|
||||
{ N, _, A } when is_atom(N), is_atom(A) -> { N, [] };
|
||||
{ N, _, A } when is_atom(N), is_list(A) -> { N, A };
|
||||
_ -> elixir_errors:form_error(Line, E#elixir_env.file, ?MODULE, { invalid_def, Kind, NameAndArgs })
|
||||
end,
|
||||
|
||||
assert_no_aliases_name(Line, Name, Args, S),
|
||||
store_definition(Kind, Line, CheckClauses, Module, Name, Args, Guards, Body, S).
|
||||
%% Now that we have verified the call format,
|
||||
%% extract meta information like file and context.
|
||||
{ _, Meta, _ } = Call,
|
||||
DoCheckClauses = (not lists:keymember(context, 1, Meta)) andalso (CheckClauses),
|
||||
|
||||
store_definition(Kind, Line, _CheckClauses, nil, _Name, _Args, _Guards, _Body, #elixir_scope{file=File}) ->
|
||||
elixir_errors:form_error(Line, File, ?MODULE, { no_module, Kind });
|
||||
%% Check if there is a file information in the definition.
|
||||
%% If so, we assume this come from another source and we need
|
||||
%% to linify taking into account keep line numbers.
|
||||
{ File, Key } = case lists:keyfind(file, 1, Meta) of
|
||||
{ file, Bin } when is_binary(Bin) -> { Bin, keep };
|
||||
_ -> { nil, line }
|
||||
end,
|
||||
|
||||
store_definition(Kind, Line, CheckClauses, Module, Name, Args, Guards, Body, #elixir_scope{} = DS) ->
|
||||
LinifyArgs = elixir_quote:linify(Line, Key, Args),
|
||||
LinifyGuards = elixir_quote:linify(Line, Key, Guards),
|
||||
LinifyBody = elixir_quote:linify(Line, Key, Body),
|
||||
|
||||
assert_no_aliases_name(Line, Name, Args, E),
|
||||
store_definition(Line, Kind, DoCheckClauses, Name,
|
||||
LinifyArgs, LinifyGuards, LinifyBody, File, E).
|
||||
|
||||
store_definition(Line, Kind, CheckClauses, Name, Args, Guards, Body, MetaFile, #elixir_env{module=Module} = ER) ->
|
||||
Arity = length(Args),
|
||||
Tuple = { Name, Arity },
|
||||
S = DS#elixir_scope{module=Module, function=Tuple},
|
||||
elixir_tracker:record_definition(Tuple, Kind, Module),
|
||||
E = ER#elixir_env{function=Tuple},
|
||||
elixir_locals:record_definition(Tuple, Kind, Module),
|
||||
|
||||
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, Body, S),
|
||||
Location = retrieve_location(Line, MetaFile, Module),
|
||||
run_on_definition_callbacks(Kind, Line, Module, Name, Args, Guards, Body, E),
|
||||
{ Function, Defaults, Super } = translate_definition(Kind, Line, Name, Args, Guards, Body, E),
|
||||
|
||||
DefaultsLength = length(Defaults),
|
||||
elixir_tracker:record_defaults(Tuple, Kind, Module, DefaultsLength),
|
||||
elixir_locals:record_defaults(Tuple, Kind, Module, DefaultsLength),
|
||||
|
||||
File = TS#elixir_scope.file,
|
||||
Table = table(Module),
|
||||
File = E#elixir_env.file,
|
||||
Table = table(Module),
|
||||
CTable = clauses_table(Module),
|
||||
|
||||
compile_super(Module, TS),
|
||||
check_previous_defaults(Table, Line, Name, Arity, Kind, DefaultsLength, S),
|
||||
compile_super(Module, Super, E),
|
||||
check_previous_defaults(Table, Line, Name, Arity, Kind, DefaultsLength, E),
|
||||
|
||||
store_each(CheckClauses, Kind, File, Location,
|
||||
Table, CTable, DefaultsLength, Function),
|
||||
@@ -133,85 +108,85 @@ store_definition(Kind, Line, CheckClauses, Module, Name, Args, Guards, Body, #el
|
||||
|
||||
%% @on_definition
|
||||
|
||||
run_on_definition_callbacks(Kind, Line, Module, Name, Args, Guards, Expr, S, CO) ->
|
||||
case elixir_compiler:get_opt(internal, CO) of
|
||||
run_on_definition_callbacks(Kind, Line, Module, Name, Args, Guards, Expr, E) ->
|
||||
case elixir_compiler:get_opt(internal) of
|
||||
true ->
|
||||
ok;
|
||||
_ ->
|
||||
Env = elixir_scope:to_ex_env({ Line, S }),
|
||||
Env = elixir_env:env_to_ex({ Line, E }),
|
||||
Callbacks = 'Elixir.Module':get_attribute(Module, on_definition),
|
||||
[Mod:Fun(Env, Kind, Name, Args, Guards, Expr) || { Mod, Fun } <- Callbacks]
|
||||
end.
|
||||
|
||||
%% Retrieve @file or fallback to default
|
||||
%% Retrieve location from meta file or @file, otherwise nil
|
||||
|
||||
retrieve_file(Line, Module, S, CO) ->
|
||||
case elixir_compiler:get_opt(internal, CO) of
|
||||
true -> { elixir_utils:characters_to_list(S#elixir_scope.file), Line };
|
||||
_ ->
|
||||
case 'Elixir.Module':get_attribute(Module, file) of
|
||||
nil -> { elixir_utils:characters_to_list(S#elixir_scope.file), Line };
|
||||
Else ->
|
||||
'Elixir.Module':delete_attribute(Module, file),
|
||||
{ Else, 0 }
|
||||
end
|
||||
retrieve_location(Line, File, Module) ->
|
||||
case not(elixir_compiler:get_opt(internal)) andalso
|
||||
'Elixir.Module':get_attribute(Module, file) of
|
||||
X when X == nil; X == false ->
|
||||
if
|
||||
is_binary(File) ->
|
||||
{ elixir_utils:characters_to_list(elixir_utils:relative_to_cwd(File)), Line };
|
||||
true ->
|
||||
nil
|
||||
end;
|
||||
X ->
|
||||
'Elixir.Module':delete_attribute(Module, file),
|
||||
{ X, 0 }
|
||||
end.
|
||||
|
||||
%% Compile super
|
||||
|
||||
compile_super(Module, #elixir_scope{function=Function, super=true}) ->
|
||||
compile_super(Module, true, #elixir_env{function=Function}) ->
|
||||
elixir_def_overridable:store(Module, Function, true);
|
||||
|
||||
compile_super(_Module, _S) -> ok.
|
||||
compile_super(_Module, _, _E) -> ok.
|
||||
|
||||
%% Translate the given call and expression given
|
||||
%% and then store it in memory.
|
||||
|
||||
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),
|
||||
Body = elixir_quote:linify(Line, RawBody),
|
||||
Arity = length(Args),
|
||||
translate_definition(Kind, Line, Name, Args, Guards, Body, E) when is_integer(Line) ->
|
||||
Arity = length(Args),
|
||||
|
||||
%% 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 is_macro(Kind) of
|
||||
AllArgs = 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),
|
||||
{ EArgs, EGuards, EBody, _ } = elixir_exp_clauses:def(fun elixir_def_defaults:expand/2,
|
||||
AllArgs, Guards, expr_from_body(Line, Body), E),
|
||||
|
||||
S = elixir_env:env_to_scope(E),
|
||||
{ Unpacked, Defaults } = elixir_def_defaults:unpack(Kind, Name, EArgs, S),
|
||||
{ Clauses, Super } = translate_clause(Body, Line, Kind, Unpacked, EGuards, EBody, S),
|
||||
|
||||
Function = { function, Line, Name, Arity, Clauses },
|
||||
{ Function, Defaults, TS }.
|
||||
{ Function, Defaults, Super }.
|
||||
|
||||
translate_clause(_Line, _Kind, _Unpacked, [], nil, S) ->
|
||||
{ [], S };
|
||||
|
||||
translate_clause(Line, Kind, _Unpacked, _Guards, nil, #elixir_scope{file=File}) ->
|
||||
elixir_errors:syntax_error(Line, File, "missing do keyword in ~ts", [Kind]);
|
||||
|
||||
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),
|
||||
translate_clause(nil, _Line, _Kind, _Args, [], _Body, _S) ->
|
||||
{ [], false };
|
||||
translate_clause(nil, Line, Kind, _Args, _Guards, _Body, #elixir_scope{file=File}) ->
|
||||
elixir_errors:form_error(Line, File, ?MODULE, { missing_do, Kind });
|
||||
translate_clause(_, Line, Kind, Args, Guards, Body, S) ->
|
||||
{ TClause, TS } = elixir_clauses:clause(Line,
|
||||
fun elixir_translator:translate_many/2, Args, Body, Guards, S),
|
||||
|
||||
%% Set __CALLER__ if used
|
||||
FClause = case is_macro(Kind) andalso TS#elixir_scope.caller of
|
||||
true ->
|
||||
FBody = { 'match', Line,
|
||||
{ 'var', Line, '__CALLER__' },
|
||||
?wrap_call(Line, elixir_scope, to_ex_env, [{ var, Line, '_@CALLER' }])
|
||||
?wrap_call(Line, elixir_env, env_to_ex, [{ var, Line, '_@CALLER' }])
|
||||
},
|
||||
setelement(5, TClause, [FBody|element(5, TClause)]);
|
||||
false -> TClause
|
||||
end,
|
||||
|
||||
{ [FClause], TS }.
|
||||
{ [FClause], TS#elixir_scope.super }.
|
||||
|
||||
expr_from_body(_Line, nil) -> nil;
|
||||
expr_from_body(_Line, [{ do, Expr }]) -> Expr;
|
||||
expr_from_body(Line, Else) -> { 'try', [{line,Line}], [Else] }.
|
||||
|
||||
@@ -222,20 +197,20 @@ is_macro(_) -> false.
|
||||
% Unwrap the functions stored in the functions table.
|
||||
% It returns a list of all functions to be exported, plus the macros,
|
||||
% and the body of all functions.
|
||||
unwrap_stored_definitions(File, Module) ->
|
||||
unwrap_definitions(Module) ->
|
||||
Table = table(Module),
|
||||
CTable = clauses_table(Module),
|
||||
ets:delete(Table, last),
|
||||
unwrap_stored_definition(ets:tab2list(Table), CTable, File, [], [], [], [], [], [], []).
|
||||
unwrap_definition(ets:tab2list(Table), CTable, [], [], [], [], [], [], []).
|
||||
|
||||
unwrap_stored_definition([Fun|T], CTable, File, All, Exports, Private, Def, Defmacro, Functions, Tail) ->
|
||||
unwrap_definition([Fun|T], CTable, All, Exports, Private, Def, Defmacro, Functions, Tail) ->
|
||||
Tuple = element(1, Fun),
|
||||
Clauses = [Clause || { _, Clause } <- ets:lookup(CTable, Tuple)],
|
||||
|
||||
{ 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)
|
||||
_ -> unwrap_definition(element(2, Fun), Tuple, Fun, Exports, Private, Def, Defmacro)
|
||||
end,
|
||||
|
||||
{ NewFunctions, NewTail } = case NewFun of
|
||||
@@ -244,39 +219,35 @@ unwrap_stored_definition([Fun|T], CTable, File, All, Exports, Private, Def, Defm
|
||||
{ Functions, Tail };
|
||||
_ ->
|
||||
NewAll = [Tuple|All],
|
||||
function_for_stored_definition(NewFun, Clauses, File, Functions, Tail)
|
||||
function_for_stored_definition(NewFun, Clauses, Functions, Tail)
|
||||
end,
|
||||
|
||||
unwrap_stored_definition(T, CTable, File, NewAll, NewExports, NewPrivate,
|
||||
unwrap_definition(T, CTable, NewAll, NewExports, NewPrivate,
|
||||
NewDef, NewDefmacro, NewFunctions, NewTail);
|
||||
|
||||
unwrap_stored_definition([], _CTable, _File, All, Exports, Private, Def, Defmacro, Functions, Tail) ->
|
||||
unwrap_definition([], _CTable, All, Exports, Private, Def, Defmacro, Functions, Tail) ->
|
||||
{ All, Exports, Private, ordsets:from_list(Def),
|
||||
ordsets:from_list(Defmacro), lists:reverse(Tail ++ Functions) }.
|
||||
|
||||
unwrap_stored_definition(def, Tuple, Fun, Exports, Private, Def, Defmacro) ->
|
||||
unwrap_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) ->
|
||||
unwrap_definition(defmacro, { Name, Arity } = Tuple, Fun, Exports, Private, Def, Defmacro) ->
|
||||
Macro = { ?elixir_macro(Name), Arity + 1 },
|
||||
{ setelement(1, Fun, Macro), [Macro|Exports], Private, Def, [Tuple|Defmacro] };
|
||||
|
||||
unwrap_stored_definition(defp, Tuple, Fun, Exports, Private, Def, Defmacro) ->
|
||||
unwrap_definition(defp, Tuple, Fun, Exports, Private, Def, Defmacro) ->
|
||||
%% { Name, Arity }, Kind, Line, Check, Defaults
|
||||
Info = { Tuple, defp, element(3, Fun), element(5, Fun), element(7, Fun) },
|
||||
{ Fun, Exports, [Info|Private], Def, Defmacro };
|
||||
|
||||
unwrap_stored_definition(defmacrop, Tuple, Fun, Exports, Private, Def, Defmacro) ->
|
||||
unwrap_definition(defmacrop, Tuple, Fun, Exports, Private, Def, Defmacro) ->
|
||||
%% { Name, Arity }, Kind, Line, Check, Defaults
|
||||
Info = { Tuple, defmacrop, element(3, Fun), element(5, Fun), element(7, Fun) },
|
||||
{ false, Exports, [Info|Private], Def, Defmacro }.
|
||||
|
||||
%% Helpers
|
||||
|
||||
function_for_stored_definition({{Name,Arity}, _, Line, _, _, {File, _}, _}, Clauses, File, Functions, Tail) ->
|
||||
function_for_stored_definition({{Name,Arity}, _, Line, _, _, nil, _}, Clauses, Functions, Tail) ->
|
||||
{ [{ function, Line, Name, Arity, Clauses }|Functions], Tail };
|
||||
|
||||
function_for_stored_definition({{Name,Arity}, _, Line, _, _, Location, _}, Clauses, _File, Functions, Tail) ->
|
||||
function_for_stored_definition({{Name,Arity}, _, Line, _, _, Location, _}, Clauses, Functions, Tail) ->
|
||||
{ Functions, [
|
||||
{ function, Line, Name, Arity, Clauses },
|
||||
{ attribute, Line, file, Location } | Tail
|
||||
@@ -336,10 +307,10 @@ check_valid_defaults(Line, File, Name, Arity, Kind, _, 0) ->
|
||||
check_valid_defaults(Line, File, Name, Arity, Kind, _, _) ->
|
||||
elixir_errors:form_error(Line, File, ?MODULE, { clauses_with_defaults, { Kind, Name, Arity } }).
|
||||
|
||||
check_previous_defaults(Table, Line, Name, Arity, Kind, Defaults, S) ->
|
||||
check_previous_defaults(Table, Line, Name, Arity, Kind, Defaults, E) ->
|
||||
Matches = ets:match(Table, { { Name, '$2' }, '$1', '_', '_', '_', '_', '$3' }),
|
||||
[ begin
|
||||
elixir_errors:form_error(Line, S#elixir_scope.file, ?MODULE,
|
||||
elixir_errors:form_error(Line, E#elixir_env.file, ?MODULE,
|
||||
{ defs_with_defaults, Name, { Kind, Arity }, { K, A } })
|
||||
end || [K, A, D] <- Matches, A /= Arity, D /= 0, defaults_conflict(A, D, Arity, Defaults)].
|
||||
|
||||
@@ -347,9 +318,8 @@ defaults_conflict(A, D, Arity, Defaults) ->
|
||||
((Arity >= (A - D)) andalso (Arity < A)) orelse
|
||||
((A >= (Arity - Defaults)) andalso (A < Arity)).
|
||||
|
||||
assert_no_aliases_name(Line, '__aliases__', [Atom], #elixir_scope{file=File}) when is_atom(Atom) ->
|
||||
Message = "function names should start with lowercase characters or underscore, invalid name ~ts",
|
||||
elixir_errors:syntax_error(Line, File, Message, [Atom]);
|
||||
assert_no_aliases_name(Line, '__aliases__', [Atom], #elixir_env{file=File}) when is_atom(Atom) ->
|
||||
elixir_errors:form_error(Line, File, ?MODULE, { no_alias, Atom });
|
||||
|
||||
assert_no_aliases_name(_Meta, _Aliases, _Args, _S) ->
|
||||
ok.
|
||||
@@ -379,4 +349,13 @@ format_error({ungrouped_clause,{Kind,Name,Arity,OrigLine,OrigFile}}) ->
|
||||
[Kind, Kind, Name, Arity, OrigFile, OrigLine]);
|
||||
|
||||
format_error({changed_kind,{Name,Arity,Previous,Current}}) ->
|
||||
io_lib:format("~ts ~ts/~B already defined as ~ts", [Current, Name, Arity, Previous]).
|
||||
io_lib:format("~ts ~ts/~B already defined as ~ts", [Current, Name, Arity, Previous]);
|
||||
|
||||
format_error({no_alias, Atom}) ->
|
||||
io_lib:format("function names should start with lowercase characters or underscore, invalid name ~ts", [Atom]);
|
||||
|
||||
format_error({invalid_def, Kind, NameAndArgs}) ->
|
||||
io_lib:format("invalid syntax in ~ts ~ts", [Kind, 'Elixir.Macro':to_string(NameAndArgs)]);
|
||||
|
||||
format_error({missing_do, Kind}) ->
|
||||
io_lib:format("missing do keyword in ~ts", [Kind]).
|
||||
|
||||
@@ -1,8 +1,18 @@
|
||||
% Handle default clauses for function definitions.
|
||||
-module(elixir_def_defaults).
|
||||
-export([unpack/4]).
|
||||
-export([expand/2, unpack/4]).
|
||||
-include("elixir.hrl").
|
||||
|
||||
expand(Args, E) ->
|
||||
lists:mapfoldl(fun
|
||||
({ '//', Meta, [Left, Right] }, Acc) ->
|
||||
{ ELeft, EL } = elixir_exp:expand(Left, Acc),
|
||||
{ ERight, _ } = elixir_exp:expand(Right, Acc#elixir_env{context=nil}),
|
||||
{ { '//', Meta, [ELeft, ERight] }, EL };
|
||||
(Left, Acc) ->
|
||||
elixir_exp:expand(Left, Acc)
|
||||
end, E, Args).
|
||||
|
||||
unpack(Kind, Name, Args, S) ->
|
||||
unpack_each(Kind, Name, Args, [], [], S).
|
||||
|
||||
@@ -15,12 +25,12 @@ unpack_each(Kind, Name, [{'//', Line, [Expr, _]}|T] = List, Acc, Clauses, S) ->
|
||||
Base = build_match(Acc, Line, []),
|
||||
{ Args, Invoke } = extract_defaults(List, Line, length(Base), [], []),
|
||||
|
||||
{ DefArgs, SA } = elixir_clauses:assigns(fun elixir_translator:translate/2, Base ++ Args, S),
|
||||
{ InvokeArgs, _ } = elixir_translator:translate_args(Base ++ Invoke, SA),
|
||||
{ DefArgs, SA } = elixir_clauses:match(fun elixir_translator:translate_many/2, Base ++ Args, S),
|
||||
{ DefInvoke, _ } = elixir_translator:translate_args(Base ++ Invoke, SA),
|
||||
|
||||
Call = { call, Line,
|
||||
{ atom, Line, name_for_kind(Kind, Name) },
|
||||
InvokeArgs
|
||||
DefInvoke
|
||||
},
|
||||
|
||||
Clause = { clause, Line, DefArgs, [], [Call] },
|
||||
|
||||
@@ -1,62 +0,0 @@
|
||||
%% Module responsible for local invocation of macros and functions.
|
||||
-module(elixir_def_local).
|
||||
-export([macro_for/3, function_for/3]).
|
||||
-include("elixir.hrl").
|
||||
|
||||
macro_for(_Tuple, _All, #elixir_scope{module=nil}) -> false;
|
||||
|
||||
macro_for(Tuple, All, #elixir_scope{module=Module}) ->
|
||||
try elixir_def:lookup_definition(Module, Tuple) of
|
||||
{ { Tuple, Kind, Line, _, _, _, _ }, Clauses } when Kind == defmacro, Clauses /= [];
|
||||
All, Kind == defmacrop, Clauses /= [] ->
|
||||
fun() -> get_function(Line, Module, Clauses) end;
|
||||
_ ->
|
||||
false
|
||||
catch
|
||||
error:badarg -> false
|
||||
end.
|
||||
|
||||
function_for(Module, Name, Arity) ->
|
||||
Tuple = { Name, Arity },
|
||||
case elixir_def:lookup_definition(Module, Tuple) of
|
||||
{ { Tuple, _, Line, _, _, _, _ }, Clauses } when 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(),
|
||||
erlang:raise(error, undef, [{Module,Name,Arity,[]}|T])
|
||||
end.
|
||||
|
||||
get_function(Line, Module, Clauses) ->
|
||||
RewrittenClauses = [rewrite_clause(Clause, Module) || Clause <- Clauses],
|
||||
Fun = { 'fun', Line, {clauses, RewrittenClauses } },
|
||||
{ value, Result, _Binding } = erl_eval:exprs([Fun], []),
|
||||
Result.
|
||||
|
||||
rewrite_clause({ call, Line, { atom, Line, RawName }, Args }, Module) ->
|
||||
Remote = { remote, Line,
|
||||
{ atom, Line, ?MODULE },
|
||||
{ atom, Line, function_for }
|
||||
},
|
||||
|
||||
%% If we have a macro, its arity in the table is
|
||||
%% actually one less than in the function call
|
||||
{ Name, Arity } = case atom_to_list(RawName) of
|
||||
"MACRO-" ++ Rest -> { list_to_atom(Rest), length(Args) - 1 };
|
||||
_ -> { RawName, length(Args) }
|
||||
end,
|
||||
|
||||
FunCall = { call, Line, Remote, [
|
||||
{ atom, Line, Module }, { atom, Line, Name }, { integer, Line, Arity }
|
||||
] },
|
||||
{ call, Line, FunCall, Args };
|
||||
|
||||
rewrite_clause(Tuple, Module) when is_tuple(Tuple) ->
|
||||
list_to_tuple(rewrite_clause(tuple_to_list(Tuple), Module));
|
||||
|
||||
rewrite_clause(List, Module) when is_list(List) ->
|
||||
[rewrite_clause(Item, Module) || Item <- List];
|
||||
|
||||
rewrite_clause(Else, _) -> Else.
|
||||
@@ -1,6 +1,6 @@
|
||||
% Holds the logic responsible for defining overridable functions and handling super.
|
||||
-module(elixir_def_overridable).
|
||||
-export([store_pending/1, is_defined/2, ensure_defined/4,
|
||||
-export([store_pending/1, ensure_defined/4,
|
||||
name/2, store/3, format_error/1]).
|
||||
-include("elixir.hrl").
|
||||
|
||||
@@ -12,17 +12,11 @@ overridable(Module, Value) ->
|
||||
|
||||
%% Check if an overridable function is defined.
|
||||
|
||||
is_defined(Module, Tuple) ->
|
||||
ensure_defined(Meta, Module, Tuple, S) ->
|
||||
Overridable = overridable(Module),
|
||||
case orddict:find(Tuple, Overridable) of
|
||||
{ ok, { _, _, _, _ } } -> true;
|
||||
_ -> false
|
||||
end.
|
||||
|
||||
ensure_defined(Meta, Module, Tuple, S) ->
|
||||
case is_defined(Module, Tuple) of
|
||||
true -> ok;
|
||||
_ -> elixir_errors:form_error(Meta, S#elixir_scope.file, ?MODULE, { no_super, Module, Tuple })
|
||||
{ ok, { _, _, _, _ } } -> ok;
|
||||
_ -> elixir_errors:form_error(Meta, S#elixir_scope.file, ?MODULE, { no_super, Module, Tuple })
|
||||
end.
|
||||
|
||||
%% Gets the name based on the function and stored overridables
|
||||
@@ -49,7 +43,12 @@ store(Module, Function, GenerateName) ->
|
||||
false -> { Kind, Name }
|
||||
end,
|
||||
|
||||
'Elixir.Module.DispatchTracker':reattach(Module, Kind, { Name, Arity }, Neighbours),
|
||||
case code:is_loaded('Elixir.Module.LocalsTracker') of
|
||||
{ _, _ } ->
|
||||
'Elixir.Module.LocalsTracker':reattach(Module, Kind, { Name, Arity }, Neighbours);
|
||||
_ ->
|
||||
ok
|
||||
end,
|
||||
|
||||
Def = { function, Line, FinalName, Arity, Clauses },
|
||||
elixir_def:store_each(false, FinalKind, File, Location,
|
||||
@@ -68,7 +67,7 @@ format_error({ no_super, Module, { Name, Arity } }) ->
|
||||
Bins = [format_fa(X) || { X, { _, _, _, _ } } <- overridable(Module)],
|
||||
Joined = 'Elixir.Enum':join(Bins, <<", ">>),
|
||||
io_lib:format("no super defined for ~ts/~B in module ~ts. Overridable functions available are: ~ts",
|
||||
[Name, Arity, elixir_errors:inspect(Module), Joined]).
|
||||
[Name, Arity, elixir_aliases:inspect(Module), Joined]).
|
||||
|
||||
format_fa({ Name, Arity }) ->
|
||||
A = atom_to_binary(Name, utf8),
|
||||
|
||||
+176
-299
@@ -2,34 +2,33 @@
|
||||
%% This module access the information stored on the scope
|
||||
%% by elixir_import and therefore assumes it is normalized (ordsets)
|
||||
-module(elixir_dispatch).
|
||||
-export([default_macros/0, default_functions/0, default_requires/0,
|
||||
dispatch_require/6, dispatch_import/5,
|
||||
-export([dispatch_import/5, dispatch_require/6,
|
||||
require_function/5, import_function/4,
|
||||
expand_import/6, expand_require/6, find_import/4,
|
||||
format_error/1, in_erlang_functions/0, in_erlang_macros/0]).
|
||||
expand_import/5, expand_require/5,
|
||||
default_functions/0, default_macros/0, default_requires/0,
|
||||
find_import/4, format_error/1]).
|
||||
-include("elixir.hrl").
|
||||
|
||||
-import(ordsets, [is_element/2]).
|
||||
-define(builtin, 'Elixir.Kernel').
|
||||
-define(kernel, 'Elixir.Kernel').
|
||||
|
||||
default_functions() ->
|
||||
[ { ?builtin, elixir_imported_functions() } ].
|
||||
[ { ?kernel, elixir_imported_functions() } ].
|
||||
default_macros() ->
|
||||
[ { ?builtin, elixir_imported_macros() } ].
|
||||
[ { ?kernel, elixir_imported_macros() } ].
|
||||
default_requires() ->
|
||||
[ 'Elixir.Integer', 'Elixir.Kernel', 'Elixir.Kernel.Typespec', 'Elixir.Record' ].
|
||||
|
||||
find_import(Meta, Name, Arity, S) ->
|
||||
find_import(Meta, Name, Arity, E) ->
|
||||
Tuple = { Name, Arity },
|
||||
|
||||
case find_dispatch(Meta, Tuple, S) of
|
||||
case find_dispatch(Meta, Tuple, [], E) of
|
||||
{ function, Receiver } ->
|
||||
elixir_lexical:record_import(Receiver, S#elixir_scope.lexical_tracker),
|
||||
elixir_tracker:record_import(Tuple, Receiver, S#elixir_scope.module, S#elixir_scope.function),
|
||||
elixir_lexical:record_import(Receiver, E#elixir_env.lexical_tracker),
|
||||
elixir_locals:record_import(Tuple, Receiver, E#elixir_env.module, E#elixir_env.function),
|
||||
Receiver;
|
||||
{ macro, Receiver } ->
|
||||
elixir_lexical:record_import(Receiver, S#elixir_scope.lexical_tracker),
|
||||
elixir_tracker:record_import(Tuple, Receiver, S#elixir_scope.module, S#elixir_scope.function),
|
||||
elixir_lexical:record_import(Receiver, E#elixir_env.lexical_tracker),
|
||||
elixir_locals:record_import(Tuple, Receiver, E#elixir_env.module, E#elixir_env.function),
|
||||
Receiver;
|
||||
_ ->
|
||||
false
|
||||
@@ -37,239 +36,200 @@ find_import(Meta, Name, Arity, S) ->
|
||||
|
||||
%% Function retrieval
|
||||
|
||||
import_function(Meta, Name, Arity, S) ->
|
||||
import_function(Meta, Name, Arity, E) ->
|
||||
Tuple = { Name, Arity },
|
||||
case find_dispatch(Meta, Tuple, S) of
|
||||
case find_dispatch(Meta, Tuple, [], E) of
|
||||
{ function, Receiver } ->
|
||||
elixir_lexical:record_import(Receiver, S#elixir_scope.lexical_tracker),
|
||||
elixir_tracker:record_import(Tuple, Receiver, S#elixir_scope.module, S#elixir_scope.function),
|
||||
remote_function(Meta, Receiver, Name, Arity, S);
|
||||
elixir_lexical:record_import(Receiver, E#elixir_env.lexical_tracker),
|
||||
elixir_locals:record_import(Tuple, Receiver, E#elixir_env.module, E#elixir_env.function),
|
||||
remote_function(Receiver, Name, Arity, E);
|
||||
{ macro, _Receiver } ->
|
||||
false;
|
||||
{ import, Receiver } ->
|
||||
require_function(Meta, Receiver, Name, Arity, S);
|
||||
require_function(Meta, Receiver, Name, Arity, E);
|
||||
false ->
|
||||
case elixir_import:special_form(Name, Arity) of
|
||||
true -> false;
|
||||
false ->
|
||||
elixir_tracker:record_local(Tuple, S#elixir_scope.module, S#elixir_scope.function),
|
||||
{ { 'fun', ?line(Meta), { function, Name, Arity } }, S }
|
||||
elixir_locals:record_local(Tuple, E#elixir_env.module, E#elixir_env.function),
|
||||
{ local, Name, Arity }
|
||||
end
|
||||
end.
|
||||
|
||||
require_function(Meta, Receiver, Name, Arity, S) ->
|
||||
require_function(_Meta, Receiver, Name, Arity, E) ->
|
||||
case is_element({ Name, Arity }, get_optional_macros(Receiver)) of
|
||||
true -> false;
|
||||
false -> remote_function(Receiver, Name, Arity, E)
|
||||
end.
|
||||
|
||||
remote_function(Receiver, Name, Arity, E) ->
|
||||
elixir_lexical:record_remote(Receiver, E#elixir_env.lexical_tracker),
|
||||
Tuple = { Name, Arity },
|
||||
Final =
|
||||
case (Receiver == ?kernel) andalso is_element(Tuple, in_erlang_functions()) of
|
||||
true -> erlang;
|
||||
false -> Receiver
|
||||
end,
|
||||
{ remote, Final, Name, Arity }.
|
||||
|
||||
%% Dispatches
|
||||
|
||||
dispatch_import(Meta, Name, Args, E, Callback) ->
|
||||
Arity = length(Args),
|
||||
case expand_import(Meta, { Name, Arity }, Args, E, []) of
|
||||
{ ok, Receiver, Quoted } ->
|
||||
expand_quoted(Meta, Receiver, Name, Arity, Quoted, E);
|
||||
{ ok, Receiver } ->
|
||||
elixir_exp:expand({ { '.', [], [Receiver, Name] }, Meta, Args }, E);
|
||||
error ->
|
||||
Callback()
|
||||
end.
|
||||
|
||||
dispatch_require(Meta, Receiver, Name, Args, E, Callback) when is_atom(Receiver) ->
|
||||
Arity = length(Args),
|
||||
Tuple = { Name, Arity },
|
||||
|
||||
case is_element(Tuple, get_optional_macros(Receiver)) of
|
||||
true -> false;
|
||||
case (Receiver == ?kernel) andalso is_element(Tuple, in_erlang_functions()) of
|
||||
true -> Callback(erlang);
|
||||
false ->
|
||||
elixir_lexical:record_remote(Receiver, S#elixir_scope.lexical_tracker),
|
||||
elixir_tracker:record_remote(Tuple, Receiver, S#elixir_scope.module, S#elixir_scope.function),
|
||||
remote_function(Meta, Receiver, Name, Arity, S)
|
||||
end.
|
||||
|
||||
%% Function dispatch
|
||||
|
||||
dispatch_import(Meta, Name, Args, S, Callback) ->
|
||||
Module = S#elixir_scope.module,
|
||||
Arity = length(Args),
|
||||
Tuple = { Name, Arity },
|
||||
|
||||
case find_dispatch(Meta, Tuple, S) of
|
||||
{ function, Receiver } ->
|
||||
elixir_lexical:record_import(Receiver, S#elixir_scope.lexical_tracker),
|
||||
elixir_tracker:record_import(Tuple, Receiver, Module, S#elixir_scope.function),
|
||||
Endpoint = case (Receiver == ?builtin) andalso is_element(Tuple, in_erlang_functions()) of
|
||||
true -> erlang;
|
||||
false -> Receiver
|
||||
end,
|
||||
elixir_translator:translate_each({ { '.', Meta, [Endpoint, Name] }, Meta, Args }, S);
|
||||
{ import, Receiver } ->
|
||||
elixir_lexical:record_remote(Receiver, S#elixir_scope.lexical_tracker),
|
||||
elixir_tracker:record_remote(Tuple, Receiver, S#elixir_scope.module, S#elixir_scope.function),
|
||||
elixir_translator:translate_each({ { '.', Meta, [Receiver, Name] }, [{require,false}|Meta], Args }, S);
|
||||
Result ->
|
||||
case do_expand_import(Meta, Tuple, Args, Module, S, Result) of
|
||||
{ error, noexpansion } ->
|
||||
Callback();
|
||||
{ error, internal } ->
|
||||
elixir_lexical:record_import(?builtin, S#elixir_scope.lexical_tracker),
|
||||
elixir_tracker:record_import(Tuple, ?builtin, Module, S#elixir_scope.function),
|
||||
elixir_macros:translate({ Name, Meta, Args }, S);
|
||||
{ ok, Receiver, Tree } ->
|
||||
translate_expansion(Meta, Receiver, Tree, S)
|
||||
end
|
||||
end.
|
||||
|
||||
dispatch_require(Meta, Receiver, Name, Args, S, Callback) ->
|
||||
Module = S#elixir_scope.module,
|
||||
Arity = length(Args),
|
||||
Tuple = { Name, Arity },
|
||||
|
||||
case (Receiver == ?builtin) andalso is_element(Tuple, in_erlang_functions()) of
|
||||
true ->
|
||||
elixir_lexical:record_remote(Receiver, S#elixir_scope.lexical_tracker),
|
||||
elixir_tracker:record_remote(Tuple, Receiver, S#elixir_scope.module, S#elixir_scope.function),
|
||||
{ TArgs, SA } = elixir_translator:translate_args(Args, S),
|
||||
{ ?wrap_call(?line(Meta), erlang, Name, TArgs), SA };
|
||||
false ->
|
||||
case expand_require(Meta, Receiver, Tuple, Args, Module, S) of
|
||||
{ error, noexpansion } ->
|
||||
Callback();
|
||||
{ error, internal } ->
|
||||
elixir_lexical:record_remote(?builtin, S#elixir_scope.lexical_tracker),
|
||||
elixir_tracker:record_remote(Tuple, ?builtin, S#elixir_scope.module, S#elixir_scope.function),
|
||||
elixir_macros:translate({ Name, Meta, Args }, S);
|
||||
{ ok, Receiver, Tree } ->
|
||||
translate_expansion(Meta, Receiver, Tree, S)
|
||||
end
|
||||
end.
|
||||
|
||||
%% Macros expansion
|
||||
|
||||
expand_import(Meta, Tuple, Args, Module, Extra, S) ->
|
||||
Result = find_dispatch(Meta, Tuple, Extra, S),
|
||||
do_expand_import(Meta, Tuple, Args, Module, S, Result).
|
||||
|
||||
do_expand_import(Meta, { Name, Arity } = Tuple, Args, Module, S, Result) ->
|
||||
Function = S#elixir_scope.function,
|
||||
|
||||
Fun = (Function /= nil) andalso (Function /= Tuple) andalso
|
||||
elixir_def_local:macro_for(Tuple, true, S),
|
||||
|
||||
case Fun of
|
||||
false ->
|
||||
do_expand_import_no_local(Meta, Tuple, Args, Module, S, Result);
|
||||
_ ->
|
||||
case Result of
|
||||
{ Kind, Receiver } when Kind == import; Receiver == Module ->
|
||||
do_expand_import_no_local(Meta, Tuple, Args, Module, S, Result);
|
||||
{ _, Receiver } ->
|
||||
Error = { macro_conflict, { Receiver, Name, Arity } },
|
||||
elixir_errors:form_error(Meta, S#elixir_scope.file, ?MODULE, Error);
|
||||
false ->
|
||||
elixir_tracker:record_local(Tuple, Module, S#elixir_scope.function),
|
||||
{ ok, Module, expand_macro_fun(Meta, Fun(), Module, Name, Args, Module, S) }
|
||||
end
|
||||
end.
|
||||
|
||||
do_expand_import_no_local(Meta, { Name, Arity } = Tuple, Args, Module, S, Result) ->
|
||||
case Result of
|
||||
{ macro, ?builtin } ->
|
||||
case is_element(Tuple, in_erlang_macros()) of
|
||||
true -> { error, internal };
|
||||
false ->
|
||||
elixir_lexical:record_import(?builtin, S#elixir_scope.lexical_tracker),
|
||||
elixir_tracker:record_import(Tuple, ?builtin, Module, S#elixir_scope.function),
|
||||
{ ok, ?builtin, expand_macro_named(Meta, ?builtin, Name, Arity, Args, Module, S) }
|
||||
end;
|
||||
{ macro, Receiver } ->
|
||||
elixir_lexical:record_import(Receiver, S#elixir_scope.lexical_tracker),
|
||||
elixir_tracker:record_import(Tuple, Receiver, Module, S#elixir_scope.function),
|
||||
{ ok, Receiver, expand_macro_named(Meta, Receiver, Name, Arity, Args, Module, S) };
|
||||
{ import, Receiver } ->
|
||||
expand_require(Meta, Receiver, Tuple, Args, Module, S);
|
||||
_ ->
|
||||
{ error, noexpansion }
|
||||
end.
|
||||
|
||||
expand_require(Meta, ?builtin, { Name, Arity } = Tuple, Args, Module, S) ->
|
||||
case is_element(Tuple, in_erlang_macros()) of
|
||||
true -> { error, internal };
|
||||
false ->
|
||||
case is_element(Tuple, in_elixir_macros()) of
|
||||
true ->
|
||||
elixir_lexical:record_remote(?builtin, S#elixir_scope.lexical_tracker),
|
||||
elixir_tracker:record_remote(Tuple, ?builtin, S#elixir_scope.module, S#elixir_scope.function),
|
||||
{ ok, ?builtin, expand_macro_named(Meta, ?builtin, Name, Arity, Args, Module, S) };
|
||||
false ->
|
||||
{ error, noexpansion }
|
||||
case expand_require(Meta, Receiver, Tuple, Args, E) of
|
||||
{ ok, Receiver, Quoted } -> expand_quoted(Meta, Receiver, Name, Arity, Quoted, E);
|
||||
error -> Callback(Receiver)
|
||||
end
|
||||
end;
|
||||
|
||||
expand_require(Meta, Receiver, { Name, Arity } = Tuple, Args, Module, S) ->
|
||||
Function = S#elixir_scope.function,
|
||||
dispatch_require(_Meta, Receiver, _Name, _Args, _E, Callback) ->
|
||||
Callback(Receiver).
|
||||
|
||||
%% Macros expansion
|
||||
|
||||
expand_import(Meta, { Name, Arity } = Tuple, Args, E, Extra) ->
|
||||
Module = E#elixir_env.module,
|
||||
Dispatch = find_dispatch(Meta, Tuple, Extra, E),
|
||||
Function = E#elixir_env.function,
|
||||
Local = (Function /= nil) andalso (Function /= Tuple) andalso
|
||||
elixir_locals:macro_for(Module, Name, Arity),
|
||||
|
||||
case Dispatch of
|
||||
%% In case it is an import, or the receive is the same as the
|
||||
%% current module (happens in bootstraping), we dispatch the import.
|
||||
{ Kind, Receiver } when Kind == import; Receiver == Module ->
|
||||
do_expand_import(Meta, Tuple, Args, Module, E, Dispatch);
|
||||
|
||||
%% There is a local and an import. This is a conflict.
|
||||
{ _, Receiver } when Local /= false ->
|
||||
Error = { macro_conflict, { Receiver, Name, Arity } },
|
||||
elixir_errors:form_error(Meta, E#elixir_env.file, ?MODULE, Error);
|
||||
|
||||
%% There is no local. Dispatch the import.
|
||||
_ when Local == false ->
|
||||
do_expand_import(Meta, Tuple, Args, Module, E, Dispatch);
|
||||
|
||||
%% Dispatch to the local.
|
||||
_ ->
|
||||
elixir_locals:record_local(Tuple, Module, Function),
|
||||
{ ok, Module, expand_macro_fun(Meta, Local(), Module, Name, Args, E) }
|
||||
end.
|
||||
|
||||
do_expand_import(Meta, { Name, Arity } = Tuple, Args, Module, E, Result) ->
|
||||
case Result of
|
||||
{ function, Receiver } ->
|
||||
elixir_lexical:record_import(Receiver, E#elixir_env.lexical_tracker),
|
||||
elixir_locals:record_import(Tuple, Receiver, Module, E#elixir_env.function),
|
||||
Endpoint = case (Receiver == ?kernel) andalso is_element(Tuple, in_erlang_functions()) of
|
||||
true -> erlang;
|
||||
false -> Receiver
|
||||
end,
|
||||
{ ok, Endpoint };
|
||||
{ macro, Receiver } ->
|
||||
elixir_lexical:record_import(Receiver, E#elixir_env.lexical_tracker),
|
||||
elixir_locals:record_import(Tuple, Receiver, Module, E#elixir_env.function),
|
||||
{ ok, Receiver, expand_macro_named(Meta, Receiver, Name, Arity, Args, E) };
|
||||
{ import, Receiver } ->
|
||||
case expand_require([{require,false}|Meta], Receiver, Tuple, Args, E) of
|
||||
{ ok, _, _ } = Response -> Response;
|
||||
error -> { ok, Receiver }
|
||||
end;
|
||||
false ->
|
||||
error
|
||||
end.
|
||||
|
||||
expand_require(Meta, Receiver, { Name, Arity } = Tuple, Args, E) ->
|
||||
Module = E#elixir_env.module,
|
||||
|
||||
case is_element(Tuple, get_optional_macros(Receiver)) of
|
||||
true ->
|
||||
elixir_lexical:record_remote(Receiver, S#elixir_scope.lexical_tracker),
|
||||
elixir_tracker:record_remote(Tuple, Receiver, Module, Function),
|
||||
{ ok, Receiver, expand_macro_named(Meta, Receiver, Name, Arity, Args, Module, S) };
|
||||
false -> { error, noexpansion }
|
||||
true ->
|
||||
Requires = E#elixir_env.requires,
|
||||
case (Receiver == Module) orelse is_element(Receiver, Requires) orelse skip_require(Meta) of
|
||||
true ->
|
||||
elixir_lexical:record_remote(Receiver, E#elixir_env.lexical_tracker),
|
||||
{ ok, Receiver, expand_macro_named(Meta, Receiver, Name, Arity, Args, E) };
|
||||
false ->
|
||||
Info = { unrequired_module, { Receiver, Name, length(Args), Requires } },
|
||||
elixir_errors:form_error(Meta, E#elixir_env.file, ?MODULE, Info)
|
||||
end;
|
||||
false ->
|
||||
error
|
||||
end.
|
||||
|
||||
%% Expansion helpers
|
||||
|
||||
expand_macro_fun(Meta, Fun, Receiver, Name, Args, Module, S) ->
|
||||
Requires = S#elixir_scope.requires,
|
||||
case (Receiver == Module) or is_element(Receiver, Requires) or skip_require(Meta) of
|
||||
true -> ok;
|
||||
false ->
|
||||
Tuple = { unrequired_module, { Receiver, Name, length(Args), Requires } },
|
||||
elixir_errors:form_error(Meta, S#elixir_scope.file, ?MODULE, Tuple)
|
||||
end,
|
||||
|
||||
expand_macro_fun(Meta, Fun, Receiver, Name, Args, E) ->
|
||||
Line = ?line(Meta),
|
||||
SArg = {Line,S},
|
||||
EArg = { Line, E },
|
||||
|
||||
try
|
||||
apply(Fun, [SArg|Args])
|
||||
apply(Fun, [EArg|Args])
|
||||
catch
|
||||
Kind:Reason ->
|
||||
Info = { Receiver, Name, length(Args), location(Line, S) },
|
||||
erlang:raise(Kind, Reason, prune_stacktrace(Info, erlang:get_stacktrace(), SArg))
|
||||
Info = [{ Receiver, Name, length(Args), location(Line, E) }, mfa(Line, E)],
|
||||
erlang:raise(Kind, Reason, prune_stacktrace(Info, erlang:get_stacktrace(), EArg))
|
||||
end.
|
||||
|
||||
expand_macro_named(Meta, Receiver, Name, Arity, Args, Module, S) ->
|
||||
expand_macro_named(Meta, Receiver, Name, Arity, Args, E) ->
|
||||
ProperName = ?elixir_macro(Name),
|
||||
ProperArity = Arity + 1,
|
||||
Fun = fun Receiver:ProperName/ProperArity,
|
||||
expand_macro_fun(Meta, Fun, Receiver, Name, Args, Module, S).
|
||||
expand_macro_fun(Meta, Fun, Receiver, Name, Args, E).
|
||||
|
||||
translate_expansion(Meta, Receiver, Tree, S) ->
|
||||
expand_quoted(Meta, Receiver, Name, Arity, Quoted, E) ->
|
||||
Line = ?line(Meta),
|
||||
New = S#elixir_scope.macro_counter + 1,
|
||||
Next = elixir_counter:next(),
|
||||
|
||||
try
|
||||
elixir_translator:translate_each(
|
||||
elixir_quote:linify(Line, { Receiver, New }, Tree),
|
||||
S#elixir_scope{macro_counter=New}
|
||||
)
|
||||
elixir_exp:expand(
|
||||
elixir_quote:linify_with_context_counter(Line, { Receiver, Next }, Quoted),
|
||||
E)
|
||||
catch
|
||||
Kind:Reason ->
|
||||
erlang:raise(Kind, Reason, prune_stacktrace(mfa(Line, S), erlang:get_stacktrace(), nil))
|
||||
Info = [{ Receiver, Name, Arity, location(Line, E) }, mfa(Line, E)],
|
||||
erlang:raise(Kind, Reason, prune_stacktrace(Info, erlang:get_stacktrace(), nil))
|
||||
end.
|
||||
|
||||
mfa(Line, #elixir_scope{module=nil} = S) ->
|
||||
{ elixir_compiler, '__FILE__', 2, location(Line, S) };
|
||||
mfa(Line, #elixir_env{module=nil} = E) ->
|
||||
{ elixir_compiler, '__FILE__', 2, location(Line, E) };
|
||||
mfa(Line, #elixir_env{module=Module, function=nil} = E) ->
|
||||
{ Module, '__MODULE__', 0, location(Line, E) };
|
||||
mfa(Line, #elixir_env{module=Module, function={ Name, Arity }} = E) ->
|
||||
{ Module, Name, Arity, location(Line, E) }.
|
||||
|
||||
mfa(Line, #elixir_scope{module=Module,function=nil} = S) ->
|
||||
{ Module, '__MODULE__', 0, location(Line, S) };
|
||||
|
||||
mfa(Line, #elixir_scope{module=Module,function={ Name, Arity }} = S) ->
|
||||
{ Module, Name, Arity, location(Line, S) }.
|
||||
|
||||
location(Line, S) ->
|
||||
[{ file, S#elixir_scope.file }, { line, Line }].
|
||||
location(Line, E) ->
|
||||
[{ file, elixir_utils:characters_to_list(E#elixir_env.file) }, { line, Line }].
|
||||
|
||||
%% Helpers
|
||||
|
||||
skip_require(Meta) ->
|
||||
lists:keyfind(require, 1, Meta) == { require, false }.
|
||||
|
||||
find_dispatch(Meta, Tuple, S) ->
|
||||
find_dispatch(Meta, Tuple, [], S).
|
||||
|
||||
find_dispatch(Meta, Tuple, Extra, S) ->
|
||||
case is_import(Meta, Tuple, S) of
|
||||
find_dispatch(Meta, Tuple, Extra, E) ->
|
||||
case is_import(Meta) of
|
||||
{ import, _ } = Import ->
|
||||
Import;
|
||||
false ->
|
||||
Functions = S#elixir_scope.functions,
|
||||
Macros = Extra ++ S#elixir_scope.macros,
|
||||
File = S#elixir_scope.file,
|
||||
FunMatch = find_dispatch(Tuple, Functions),
|
||||
MacMatch = find_dispatch(Tuple, Macros),
|
||||
Funs = E#elixir_env.functions,
|
||||
Macs = Extra ++ E#elixir_env.macros,
|
||||
FunMatch = find_dispatch(Tuple, Funs),
|
||||
MacMatch = find_dispatch(Tuple, Macs),
|
||||
|
||||
case { FunMatch, MacMatch } of
|
||||
{ [], [Receiver] } -> { macro, Receiver };
|
||||
@@ -279,21 +239,18 @@ find_dispatch(Meta, Tuple, Extra, S) ->
|
||||
{ Name, Arity } = Tuple,
|
||||
[First, Second|_] = FunMatch ++ MacMatch,
|
||||
Error = { ambiguous_call, { First, Second, Name, Arity } },
|
||||
elixir_errors:form_error(Meta, File, ?MODULE, Error)
|
||||
elixir_errors:form_error(Meta, E#elixir_env.file, ?MODULE, Error)
|
||||
end
|
||||
end.
|
||||
|
||||
find_dispatch(Tuple, List) ->
|
||||
[Receiver || { Receiver, Set } <- List, is_element(Tuple, Set)].
|
||||
|
||||
is_import(Meta, Tuple, S) ->
|
||||
is_import(Meta) ->
|
||||
case lists:keyfind(import, 1, Meta) of
|
||||
{ import, _ } = 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;
|
||||
{ context, _ } -> Import;
|
||||
false ->
|
||||
false
|
||||
end;
|
||||
@@ -301,59 +258,30 @@ is_import(Meta, Tuple, S) ->
|
||||
false
|
||||
end.
|
||||
|
||||
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.
|
||||
|
||||
%% We've reached the invoked macro, skip it with the rest
|
||||
prune_stacktrace(Info, [{ _, _, [S|_], _ }|_], S) ->
|
||||
[Info];
|
||||
|
||||
prune_stacktrace(Info, [{ _, _, [E|_], _ }|_], E) ->
|
||||
Info;
|
||||
%% We've reached the elixir_dispatch internals, skip it with the rest
|
||||
prune_stacktrace(Info, [{ elixir_dispatch, _, _, _ }|_], _) ->
|
||||
[Info];
|
||||
|
||||
prune_stacktrace(Info, [H|T], S) ->
|
||||
[H|prune_stacktrace(Info, T, S)];
|
||||
|
||||
prune_stacktrace(Info, [{ Mod, _, _, _ }|_], _) when Mod == elixir_dispatch; Mod == elixir_exp ->
|
||||
Info;
|
||||
prune_stacktrace(Info, [H|T], E) ->
|
||||
[H|prune_stacktrace(Info, T, E)];
|
||||
prune_stacktrace(Info, [], _) ->
|
||||
[Info].
|
||||
|
||||
remote_function(Meta, Receiver, Name, Arity, S) ->
|
||||
Line = ?line(Meta),
|
||||
|
||||
Final =
|
||||
case (Receiver == ?builtin) andalso is_element({ Name, Arity }, in_erlang_functions()) of
|
||||
true -> erlang;
|
||||
false -> Receiver
|
||||
end,
|
||||
|
||||
{ { 'fun', Line, { function,
|
||||
{ atom, Line, Final },
|
||||
{ atom, Line, Name },
|
||||
{ integer, Line, Arity}
|
||||
} }, S }.
|
||||
|
||||
Info.
|
||||
|
||||
%% ERROR HANDLING
|
||||
|
||||
format_error({ unrequired_module, { Receiver, Name, Arity, _Required }}) ->
|
||||
Module = elixir_errors:inspect(Receiver),
|
||||
Module = elixir_aliases:inspect(Receiver),
|
||||
io_lib:format("you must require ~ts before invoking the macro ~ts.~ts/~B",
|
||||
[Module, Module, Name, Arity]);
|
||||
|
||||
format_error({ macro_conflict, { Receiver, Name, Arity } }) ->
|
||||
io_lib:format("call to local macro ~ts/~B conflicts with imported ~ts.~ts/~B, "
|
||||
"please rename the local macro or remove the conflicting import",
|
||||
[Name, Arity, elixir_errors:inspect(Receiver), Name, Arity]);
|
||||
|
||||
[Name, Arity, elixir_aliases:inspect(Receiver), Name, Arity]);
|
||||
format_error({ ambiguous_call, { Mod1, Mod2, Name, Arity }}) ->
|
||||
io_lib:format("function ~ts/~B imported from both ~ts and ~ts, call is ambiguous",
|
||||
[Name, Arity, elixir_errors:inspect(Mod1), elixir_errors:inspect(Mod2)]).
|
||||
[Name, Arity, elixir_aliases:inspect(Mod1), elixir_aliases:inspect(Mod2)]).
|
||||
|
||||
%% INTROSPECTION
|
||||
|
||||
@@ -373,31 +301,25 @@ get_optional_macros(Receiver) ->
|
||||
|
||||
elixir_imported_functions() ->
|
||||
try
|
||||
?builtin:'__info__'(functions)
|
||||
?kernel:'__info__'(functions)
|
||||
catch
|
||||
error:undef -> in_erlang_functions()
|
||||
end.
|
||||
|
||||
elixir_imported_macros() ->
|
||||
try
|
||||
?builtin:'__info__'(macros)
|
||||
catch
|
||||
error:undef -> in_erlang_macros()
|
||||
end.
|
||||
|
||||
%% Macros imported from Kernel module. Sorted on compilation.
|
||||
|
||||
in_elixir_macros() ->
|
||||
try
|
||||
?builtin:'__info__'(macros)
|
||||
?kernel:'__info__'(macros)
|
||||
catch
|
||||
error:undef -> []
|
||||
end.
|
||||
|
||||
%% Functions imported from Erlang module. MUST BE SORTED.
|
||||
%% TODO: Inline operators here as well once bug in erl_eval is fixed.
|
||||
in_erlang_functions() ->
|
||||
[
|
||||
{ abs, 1 },
|
||||
{ apply, 2 },
|
||||
{ apply, 3 },
|
||||
{ atom_to_list, 1 },
|
||||
{ binary_part, 3 },
|
||||
{ binary_to_float, 1 },
|
||||
@@ -413,7 +335,9 @@ in_erlang_functions() ->
|
||||
{ exit, 1 },
|
||||
% { float, 1 },
|
||||
{ float_to_binary, 1 },
|
||||
% { float_to_binary, 2 },
|
||||
{ float_to_list, 1 },
|
||||
% { float_to_list, 2 },
|
||||
{ hd, 1 },
|
||||
{ integer_to_binary, 1 },
|
||||
{ integer_to_binary, 2 },
|
||||
@@ -421,7 +345,6 @@ in_erlang_functions() ->
|
||||
{ integer_to_list, 2 },
|
||||
{ iolist_size, 1 },
|
||||
{ iolist_to_binary, 1 },
|
||||
{ is_alive, 0 },
|
||||
{ is_atom, 1 },
|
||||
{ is_binary, 1 },
|
||||
{ is_bitstring, 1 },
|
||||
@@ -452,6 +375,7 @@ in_erlang_functions() ->
|
||||
% { now, 0 },
|
||||
{ round, 1 },
|
||||
{ self, 0 },
|
||||
{ send, 2 },
|
||||
{ size, 1 },
|
||||
{ spawn, 1 },
|
||||
{ spawn, 3 },
|
||||
@@ -467,50 +391,3 @@ in_erlang_functions() ->
|
||||
{ tuple_size, 1 },
|
||||
{ tuple_to_list, 1 }
|
||||
].
|
||||
|
||||
%% Macros implemented in Erlang. MUST BE SORTED.
|
||||
in_erlang_macros() ->
|
||||
[
|
||||
{'!',1},
|
||||
{'!=',2},
|
||||
{'!==',2},
|
||||
{'*',2},
|
||||
{'+',1},
|
||||
{'+',2},
|
||||
{'++',2},
|
||||
{'-',1},
|
||||
{'-',2},
|
||||
{'--',2},
|
||||
{'/',2},
|
||||
{'<',2},
|
||||
{'<-',2},
|
||||
{'<=',2},
|
||||
{'==',2},
|
||||
{'===',2},
|
||||
{'>',2},
|
||||
{'>=',2},
|
||||
{'@',1},
|
||||
{'and',2},
|
||||
{apply,2},
|
||||
{apply,3},
|
||||
{'case',2},
|
||||
{def,1},
|
||||
{def,2},
|
||||
{def,4},
|
||||
{defmacro,1},
|
||||
{defmacro,2},
|
||||
{defmacro,4},
|
||||
{defmacrop,1},
|
||||
{defmacrop,2},
|
||||
{defmacrop,4},
|
||||
{defmodule,2},
|
||||
{defp,1},
|
||||
{defp,2},
|
||||
{defp,4},
|
||||
{in,2},
|
||||
{'not',1},
|
||||
{'or',2},
|
||||
{'receive',1},
|
||||
{'try',1},
|
||||
{'xor',2}
|
||||
].
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
-module(elixir_env).
|
||||
-include("elixir.hrl").
|
||||
-export([ex_to_env/1, env_to_scope/1, env_to_scope_with_vars/2, env_to_ex/1]).
|
||||
-export([mergea/2, mergev/2, merge_vars/2, merge_opt_vars/2]).
|
||||
|
||||
%% Conversion in between #elixir_env, #elixir_scope and Macro.Env
|
||||
|
||||
env_to_ex({ Line, #elixir_env{} = Env }) ->
|
||||
erlang:setelement(1, Env#elixir_env{line=Line}, 'Elixir.Macro.Env').
|
||||
|
||||
ex_to_env(Env) when element(1, Env) == 'Elixir.Macro.Env' ->
|
||||
erlang:setelement(1, Env, elixir_env).
|
||||
|
||||
env_to_scope(#elixir_env{module=Module,file=File,function=Function,context=Context}) ->
|
||||
#elixir_scope{module=Module,file=File,function=Function,context=Context}.
|
||||
|
||||
env_to_scope_with_vars(#elixir_env{} = Env, Vars) ->
|
||||
(env_to_scope(Env))#elixir_scope{
|
||||
vars=orddict:from_list(Vars),
|
||||
counter=[{'_',length(Vars)}]
|
||||
}.
|
||||
|
||||
%% SCOPE MERGING
|
||||
|
||||
%% Receives two scopes and return a new scope based on the second
|
||||
%% with their variables merged.
|
||||
mergev(E1, E2) when is_list(E1) ->
|
||||
E2#elixir_env{
|
||||
vars=merge_vars(E1, E2#elixir_env.vars),
|
||||
export_vars=merge_opt_vars(E1, E2#elixir_env.export_vars)
|
||||
};
|
||||
mergev(E1, E2) ->
|
||||
E2#elixir_env{
|
||||
vars=merge_vars(E1#elixir_env.vars, E2#elixir_env.vars),
|
||||
export_vars=merge_opt_vars(E1#elixir_env.export_vars, E2#elixir_env.export_vars)
|
||||
}.
|
||||
|
||||
%% Receives two scopes and return the later scope
|
||||
%% keeping the variables from the first (counters,
|
||||
%% imports and everything else are passed forward).
|
||||
|
||||
mergea(E1, E2) ->
|
||||
E2#elixir_env{vars=E1#elixir_env.vars}.
|
||||
|
||||
merge_vars(V1, V2) -> ordsets:union(V1, V2).
|
||||
|
||||
merge_opt_vars(_V1, nil) -> nil;
|
||||
merge_opt_vars(nil, _V2) -> nil;
|
||||
merge_opt_vars(V1, V2) -> ordsets:union(V1, V2).
|
||||
@@ -1,57 +1,37 @@
|
||||
% A bunch of helpers to help to deal with errors in Elixir source code.
|
||||
% This is not exposed in the Elixir language.
|
||||
-module(elixir_errors).
|
||||
-export([syntax_error/3, syntax_error/4,
|
||||
compile_error/3, compile_error/4, inspect/1,
|
||||
form_error/4, parse_error/4, assert_module_scope/3,
|
||||
assert_no_function_scope/3, assert_function_scope/3,
|
||||
assert_no_match_scope/3, assert_no_guard_scope/3,
|
||||
assert_no_match_or_guard_scope/3, warn/1,
|
||||
-export([compile_error/3, compile_error/4,
|
||||
form_error/4, parse_error/4, warn/1,
|
||||
handle_file_warning/2, handle_file_warning/3, handle_file_error/2,
|
||||
deprecation/3, deprecation/4]).
|
||||
-include("elixir.hrl").
|
||||
|
||||
warn(Warning) ->
|
||||
CompilerPid = get(elixir_compiler_pid),
|
||||
|
||||
if
|
||||
CompilerPid =/= undefined ->
|
||||
elixir_code_server:cast({ register_warning, CompilerPid });
|
||||
true -> false
|
||||
end,
|
||||
|
||||
io:put_chars(standard_error, Warning).
|
||||
|
||||
%% Handle inspecting for exceptions modules
|
||||
%% Raised during expansion/translation/compilation.
|
||||
|
||||
inspect(Atom) when is_atom(Atom) ->
|
||||
'Elixir.Inspect.Atom':inspect(Atom).
|
||||
-spec form_error(list(), binary(), module(), any()) -> no_return().
|
||||
|
||||
%% Raised during macros translation.
|
||||
form_error(Meta, File, Module, Desc) ->
|
||||
compile_error(Meta, File, format_error(Module, Desc)).
|
||||
|
||||
-spec syntax_error(non_neg_integer() | list(), file:filename_all(), binary() | string()) -> no_return().
|
||||
-spec syntax_error(non_neg_integer() | list(), file:filename_all(), binary() | string(), list()) -> no_return().
|
||||
|
||||
syntax_error(Meta, File, Message) when is_list(Message) ->
|
||||
syntax_error(Meta, File, iolist_to_binary(Message));
|
||||
|
||||
syntax_error(Meta, File, Message) when is_binary(Message) ->
|
||||
raise(Meta, File, 'Elixir.SyntaxError', Message).
|
||||
|
||||
syntax_error(Meta, File, Format, Args) ->
|
||||
Message = io_lib:format(Format, Args),
|
||||
raise(Meta, File, 'Elixir.SyntaxError', elixir_utils:characters_to_binary(Message)).
|
||||
-spec compile_error(list(), binary(), iolist()) -> no_return().
|
||||
-spec compile_error(list(), binary(), iolist(), list()) -> no_return().
|
||||
|
||||
compile_error(Meta, File, Message) when is_list(Message) ->
|
||||
compile_error(Meta, File, iolist_to_binary(Message));
|
||||
|
||||
compile_error(Meta, File, Message) when is_binary(Message) ->
|
||||
raise(Meta, File, 'Elixir.CompileError', Message).
|
||||
|
||||
compile_error(Meta, File, Format, Args) ->
|
||||
Message = io_lib:format(Format, Args),
|
||||
raise(Meta, File, 'Elixir.CompileError', elixir_utils:characters_to_binary(Message)).
|
||||
|
||||
compile_error(Meta, File, Format, Args) when is_list(Format) ->
|
||||
compile_error(Meta, File, io_lib:format(Format, Args)).
|
||||
|
||||
%% Raised on tokenizing/parsing
|
||||
|
||||
-spec parse_error(non_neg_integer() | list(), file:filename_all(), iolist() | atom(), [] | iolist()) -> no_return().
|
||||
@@ -80,14 +60,6 @@ parse_error(Meta, File, Error, Token) ->
|
||||
Message = <<BinError / binary, BinToken / binary >>,
|
||||
raise(Meta, File, 'Elixir.SyntaxError', Message).
|
||||
|
||||
%% Raised during compilation
|
||||
|
||||
-spec form_error(non_neg_integer() | list(), file:filename_all(), module(), any()) -> no_return().
|
||||
|
||||
form_error(Meta, File, Module, Desc) ->
|
||||
Message = iolist_to_binary(format_error(Module, Desc)),
|
||||
raise(Meta, File, 'Elixir.CompileError', Message).
|
||||
|
||||
%% Shows a deprecation message
|
||||
|
||||
deprecation(Meta, File, Message) -> deprecation(Meta, File, Message, []).
|
||||
@@ -133,14 +105,14 @@ handle_file_warning(_, File, {Line,erl_lint,{undefined_behaviour_func,{Fun,Arity
|
||||
|
||||
Kind = protocol_or_behaviour(Module),
|
||||
Raw = "undefined ~ts ~ts ~ts/~B (for ~ts ~ts)",
|
||||
Message = io_lib:format(Raw, [Kind, DefKind, Def, DefArity, Kind, inspect(Module)]),
|
||||
Message = io_lib:format(Raw, [Kind, DefKind, Def, DefArity, Kind, elixir_aliases:inspect(Module)]),
|
||||
warn(file_format(Line, File, Message));
|
||||
|
||||
handle_file_warning(_, File, {Line,erl_lint,{undefined_behaviour,Module}}) ->
|
||||
case elixir_compiler:get_opt(internal) of
|
||||
true -> [];
|
||||
false ->
|
||||
Message = io_lib:format("behaviour ~ts undefined", [inspect(Module)]),
|
||||
Message = io_lib:format("behaviour ~ts undefined", [elixir_aliases:inspect(Module)]),
|
||||
warn(file_format(Line, File, Message))
|
||||
end;
|
||||
|
||||
@@ -177,38 +149,12 @@ handle_file_error(File, {Line,erl_lint,{unsafe_var,Var,{In,_Where}}}) ->
|
||||
raise(Line, File, 'Elixir.CompileError', iolist_to_binary(Message));
|
||||
|
||||
handle_file_error(File, {Line,erl_lint,{spec_fun_undefined,{M,F,A}}}) ->
|
||||
Message = io_lib:format("spec for undefined function ~ts.~ts/~B", [inspect(M), F, A]),
|
||||
Message = io_lib:format("spec for undefined function ~ts.~ts/~B", [elixir_aliases:inspect(M), F, A]),
|
||||
raise(Line, File, 'Elixir.CompileError', iolist_to_binary(Message));
|
||||
|
||||
handle_file_error(File, {Line,Module,Desc}) ->
|
||||
form_error(Line, File, Module, Desc).
|
||||
|
||||
%% Assertions
|
||||
|
||||
assert_no_function_scope(_Meta, _Kind, #elixir_scope{function=nil}) -> [];
|
||||
assert_no_function_scope(Meta, Kind, S) ->
|
||||
syntax_error(Meta, S#elixir_scope.file, "cannot invoke ~ts inside a function", [Kind]).
|
||||
|
||||
assert_module_scope(Meta, Kind, #elixir_scope{module=nil,file=File}) ->
|
||||
syntax_error(Meta, File, "cannot invoke ~ts outside module", [Kind]);
|
||||
assert_module_scope(_Meta, _Kind, #elixir_scope{module=Module}) -> Module.
|
||||
|
||||
assert_function_scope(Meta, Kind, #elixir_scope{function=nil,file=File}) ->
|
||||
syntax_error(Meta, File, "cannot invoke ~ts outside function", [Kind]);
|
||||
assert_function_scope(_Meta, _Kind, #elixir_scope{function=Function}) -> Function.
|
||||
|
||||
assert_no_match_or_guard_scope(Meta, Kind, S) ->
|
||||
assert_no_match_scope(Meta, Kind, S),
|
||||
assert_no_guard_scope(Meta, Kind, S).
|
||||
|
||||
assert_no_match_scope(Meta, _Kind, #elixir_scope{context=match} = S) ->
|
||||
compile_error(Meta, S#elixir_scope.file, "invalid pattern in match clause", []);
|
||||
assert_no_match_scope(_Meta, _Kind, _S) -> [].
|
||||
|
||||
assert_no_guard_scope(Meta, _Kind, #elixir_scope{context=guard} = S) ->
|
||||
compile_error(Meta, S#elixir_scope.file, "invalid pattern in guard", []);
|
||||
assert_no_guard_scope(_Meta, _Kind, _S) -> [].
|
||||
|
||||
%% Helpers
|
||||
|
||||
raise(Meta, File, Kind, Message) when is_list(Meta) ->
|
||||
@@ -217,7 +163,7 @@ raise(Meta, File, Kind, Message) when is_list(Meta) ->
|
||||
raise(none, File, Kind, Message) ->
|
||||
raise(0, File, Kind, Message);
|
||||
|
||||
raise(Line, File, Kind, Message) when is_integer(Line) ->
|
||||
raise(Line, File, Kind, Message) when is_integer(Line), is_binary(File) ->
|
||||
%% Populate the stacktrace so we can raise it
|
||||
try
|
||||
throw(ok)
|
||||
@@ -225,13 +171,13 @@ raise(Line, File, Kind, Message) when is_integer(Line) ->
|
||||
ok -> ok
|
||||
end,
|
||||
Stacktrace = erlang:get_stacktrace(),
|
||||
Exception = Kind:new([{description, Message}, {file, iolist_to_binary(File)}, {line, Line}]),
|
||||
Exception = Kind:new([{description, Message}, {file, File}, {line, Line}]),
|
||||
erlang:raise(error, Exception, tl(Stacktrace)).
|
||||
|
||||
file_format(0, File, Message) ->
|
||||
file_format(0, File, Message) when is_binary(File) ->
|
||||
io_lib:format("~ts: ~ts~n", [elixir_utils:relative_to_cwd(File), Message]);
|
||||
|
||||
file_format(Line, File, Message) ->
|
||||
file_format(Line, File, Message) when is_binary(File) ->
|
||||
io_lib:format("~ts:~w: ~ts~n", [elixir_utils:relative_to_cwd(File), Line, Message]).
|
||||
|
||||
format_var(Var) ->
|
||||
|
||||
@@ -0,0 +1,562 @@
|
||||
-module(elixir_exp).
|
||||
-export([expand/2, expand_many/2, expand_args/2, expand_arg/2]).
|
||||
-import(elixir_errors, [compile_error/3, compile_error/4]).
|
||||
-include("elixir.hrl").
|
||||
|
||||
%% =
|
||||
|
||||
expand({ '=', Meta, [Left, Right] }, E) ->
|
||||
assert_no_guard_scope(Meta, '=', E),
|
||||
{ ERight, ER } = expand(Right, E),
|
||||
{ ELeft, EL } = elixir_exp_clauses:match(fun expand/2, Left, E),
|
||||
{ { '=', Meta, [ELeft, ERight] }, elixir_env:mergev(EL, ER) };
|
||||
|
||||
%% Literal operators
|
||||
|
||||
expand({ '{}', Meta, Args }, E) ->
|
||||
{ EArgs, EA } = expand_args(Args, E),
|
||||
{ { '{}', Meta, EArgs }, EA };
|
||||
|
||||
expand({ '<<>>', Meta, Args }, E) ->
|
||||
elixir_bitstring:expand(Meta, Args, E);
|
||||
|
||||
%% Other operators
|
||||
|
||||
expand({ '__op__', Meta, [_, _] = Args }, E) ->
|
||||
{ EArgs, EA } = expand_args(Args, E),
|
||||
{ { '__op__', Meta, EArgs }, EA };
|
||||
|
||||
expand({ '__op__', Meta, [_, _, _] = Args }, E) ->
|
||||
{ EArgs, EA } = expand_args(Args, E),
|
||||
{ { '__op__', Meta, EArgs }, EA };
|
||||
|
||||
expand({ '->', Meta, _Args }, E) ->
|
||||
compile_error(Meta, E#elixir_env.file, "unhandled operator ->");
|
||||
|
||||
%% __block__
|
||||
|
||||
expand({ '__block__', _Meta, [] }, E) ->
|
||||
{ nil, E };
|
||||
expand({ '__block__', _Meta, [Arg] }, E) ->
|
||||
expand(Arg, E);
|
||||
expand({ '__block__', Meta, Args }, E) when is_list(Args) ->
|
||||
{ EArgs, EA } = expand_many(Args, E),
|
||||
{ { '__block__', Meta, EArgs }, EA };
|
||||
|
||||
%% __aliases__
|
||||
|
||||
expand({ '__aliases__', _, _ } = Alias, E) ->
|
||||
case elixir_aliases:expand(Alias, E#elixir_env.aliases,
|
||||
E#elixir_env.macro_aliases, E#elixir_env.lexical_tracker) of
|
||||
Receiver when is_atom(Receiver) ->
|
||||
elixir_lexical:record_remote(Receiver, E#elixir_env.lexical_tracker),
|
||||
{ Receiver, E };
|
||||
Aliases ->
|
||||
{ EAliases, EA } = expand_args(Aliases, E),
|
||||
|
||||
case lists:all(fun is_atom/1, EAliases) of
|
||||
true ->
|
||||
Receiver = elixir_aliases:concat(EAliases),
|
||||
elixir_lexical:record_remote(Receiver, E#elixir_env.lexical_tracker),
|
||||
{ Receiver, EA };
|
||||
false ->
|
||||
{ { { '.', [], [elixir_aliases, concat] }, [], [EAliases] }, EA }
|
||||
end
|
||||
end;
|
||||
|
||||
%% alias
|
||||
|
||||
expand({ alias, Meta, [Ref] }, E) ->
|
||||
expand({ alias, Meta, [Ref,[]] }, E);
|
||||
expand({ alias, Meta, [Ref, KV] }, E) ->
|
||||
assert_no_match_or_guard_scope(Meta, alias, E),
|
||||
{ ERef, ER } = expand(Ref, E),
|
||||
{ EKV, ET } = expand_opts(Meta, alias, [as, warn], no_alias_opts(KV), ER),
|
||||
|
||||
if
|
||||
is_atom(ERef) ->
|
||||
{ { alias, Meta, [ERef, EKV] },
|
||||
expand_alias(Meta, true, ERef, EKV, ET) };
|
||||
true ->
|
||||
compile_error(Meta, E#elixir_env.file,
|
||||
"invalid arguments for alias, expected a compile time atom or alias as argument")
|
||||
end;
|
||||
|
||||
expand({ require, Meta, [Ref] }, E) ->
|
||||
expand({ require, Meta, [Ref, []] }, E);
|
||||
expand({ require, Meta, [Ref, KV] }, E) ->
|
||||
assert_no_match_or_guard_scope(Meta, require, E),
|
||||
|
||||
{ ERef, ER } = expand(Ref, E),
|
||||
{ EKV, ET } = expand_opts(Meta, require, [as, warn], no_alias_opts(KV), ER),
|
||||
|
||||
if
|
||||
is_atom(ERef) ->
|
||||
elixir_aliases:ensure_loaded(Meta, ERef, ET),
|
||||
{ { require, Meta, [ERef, EKV] },
|
||||
expand_require(Meta, ERef, EKV, ET) };
|
||||
true ->
|
||||
compile_error(Meta, E#elixir_env.file,
|
||||
"invalid arguments for require, expected a compile time atom or alias as argument")
|
||||
end;
|
||||
|
||||
expand({ import, Meta, [Left] }, E) ->
|
||||
expand({ import, Meta, [Left, []]}, E);
|
||||
|
||||
expand({ import, Meta, [Ref, KV] }, E) ->
|
||||
assert_no_match_or_guard_scope(Meta, import, E),
|
||||
{ ERef, ER } = expand(Ref, E),
|
||||
{ EKV, ET } = expand_opts(Meta, import, [only, except, warn], KV, ER),
|
||||
|
||||
if
|
||||
is_atom(ERef) ->
|
||||
elixir_aliases:ensure_loaded(Meta, ERef, ET),
|
||||
{ Functions, Macros } = elixir_import:import(Meta, ERef, EKV, ET),
|
||||
{ { import, Meta, [ERef, EKV] },
|
||||
expand_require(Meta, ERef, EKV, ET#elixir_env{functions=Functions, macros=Macros}) };
|
||||
true ->
|
||||
compile_error(Meta, E#elixir_env.file, "invalid name for import, expected a compile time atom or alias")
|
||||
end;
|
||||
|
||||
%% Pseudo vars
|
||||
|
||||
expand({ '__MODULE__', _, Atom }, E) when is_atom(Atom) ->
|
||||
{ E#elixir_env.module, E };
|
||||
expand({ '__FILE__', Meta, Atom }, E) when is_atom(Atom) ->
|
||||
elixir_errors:deprecation(Meta, E#elixir_env.file, "__FILE__ is deprecated, please use __DIR__ or __ENV__.file instead"),
|
||||
{ E#elixir_env.file, E };
|
||||
expand({ '__DIR__', _, Atom }, E) when is_atom(Atom) ->
|
||||
{ filename:dirname(E#elixir_env.file), E };
|
||||
expand({ '__CALLER__', _, Atom } = Caller, E) when is_atom(Atom) ->
|
||||
{ Caller, E };
|
||||
expand({ '__ENV__', Meta, Atom }, E) when is_atom(Atom) ->
|
||||
Env = elixir_env:env_to_ex({ ?line(Meta), E }),
|
||||
{ { '{}', [], tuple_to_list(Env) }, E };
|
||||
expand({ { '.', DotMeta, [{ '__ENV__', Meta, Atom }, Field] }, CallMeta, [] }, E) when is_atom(Atom), is_atom(Field) ->
|
||||
Env = elixir_env:env_to_ex({ ?line(Meta), E }),
|
||||
case erlang:function_exported('Elixir.Macro.Env', Field, 1) of
|
||||
true -> { Env:Field(), E };
|
||||
false -> { { { '.', DotMeta, [{ '{}', [], tuple_to_list(Env) }, Field] }, CallMeta, [] }, E }
|
||||
end;
|
||||
|
||||
%% Quote
|
||||
|
||||
expand({ Unquote, Meta, [_] }, E) when Unquote == unquote; Unquote == unquote_splicing ->
|
||||
compile_error(Meta, E#elixir_env.file, "~p called outside quote", [Unquote]);
|
||||
|
||||
expand({ quote, Meta, [Opts] }, E) when is_list(Opts) ->
|
||||
case lists:keyfind(do, 1, Opts) of
|
||||
{ do, Do } ->
|
||||
expand({ quote, Meta, [lists:keydelete(do, 1, Opts), [{do,Do}]] }, E);
|
||||
false ->
|
||||
compile_error(Meta, E#elixir_env.file, "missing do keyword in quote")
|
||||
end;
|
||||
|
||||
expand({ quote, Meta, [_] }, E) ->
|
||||
compile_error(Meta, E#elixir_env.file, "invalid arguments for quote");
|
||||
|
||||
expand({ quote, Meta, [KV, Do] }, E) when is_list(Do) ->
|
||||
Exprs =
|
||||
case lists:keyfind(do, 1, Do) of
|
||||
{ do, Expr } -> Expr;
|
||||
false -> compile_error(Meta, E#elixir_scope.file, "missing do keyword in quote")
|
||||
end,
|
||||
|
||||
ValidOpts = [hygiene, context, var_context, location, line, unquote, bind_quoted],
|
||||
{ EKV, ET } = expand_opts(Meta, quote, ValidOpts, KV, E),
|
||||
|
||||
Hygiene = case lists:keyfind(hygiene, 1, EKV) of
|
||||
{ hygiene, List } when is_list(List) ->
|
||||
List;
|
||||
false ->
|
||||
[]
|
||||
end,
|
||||
|
||||
Context = case lists:keyfind(context, 1, EKV) of
|
||||
{ context, Atom } when is_atom(Atom) ->
|
||||
Atom;
|
||||
{ context, _ } ->
|
||||
compile_error(Meta, E#elixir_env.file, "invalid :context for quote, expected a compile time atom or an alias");
|
||||
false ->
|
||||
case E#elixir_env.module of
|
||||
nil -> 'Elixir';
|
||||
Mod -> Mod
|
||||
end
|
||||
end,
|
||||
|
||||
Vars = lists:keyfind(vars, 1, Hygiene) /= { vars, false },
|
||||
Aliases = lists:keyfind(aliases, 1, Hygiene) /= { aliases, false },
|
||||
Imports = lists:keyfind(imports, 1, Hygiene) /= { imports, false },
|
||||
|
||||
Keep = lists:keyfind(location, 1, EKV) == { location, keep },
|
||||
Line = proplists:get_value(line, EKV, false),
|
||||
|
||||
{ Binding, DefaultUnquote } = case lists:keyfind(bind_quoted, 1, EKV) of
|
||||
{ bind_quoted, BQ } -> { BQ, false };
|
||||
false -> { nil, true }
|
||||
end,
|
||||
|
||||
Unquote = case lists:keyfind(unquote, 1, EKV) of
|
||||
{ unquote, Bool } when is_boolean(Bool) -> Bool;
|
||||
false -> DefaultUnquote
|
||||
end,
|
||||
|
||||
Q = #elixir_quote{vars_hygiene=Vars, line=Line, keep=Keep, unquote=Unquote,
|
||||
aliases_hygiene=Aliases, imports_hygiene=Imports, context=Context},
|
||||
|
||||
{ Quoted, _Q } = elixir_quote:quote(Exprs, Binding, Q, ET),
|
||||
expand(Quoted, ET);
|
||||
|
||||
expand({ quote, Meta, [_, _] }, E) ->
|
||||
compile_error(Meta, E#elixir_env.file, "invalid arguments for quote");
|
||||
|
||||
%% Functions
|
||||
|
||||
expand({ '&', _, [Arg] } = Original, E) when is_integer(Arg) ->
|
||||
{ Original, E };
|
||||
expand({ '&', Meta, [Arg] }, E) ->
|
||||
assert_no_match_or_guard_scope(Meta, '&', E),
|
||||
case elixir_fn:capture(Meta, Arg, E) of
|
||||
{ local, Fun, Arity } ->
|
||||
{ { '&', Meta, [{ '/', [], [{ Fun, [], nil }, Arity] }] }, E };
|
||||
{ expanded, Expr, EE } ->
|
||||
expand(Expr, EE)
|
||||
end;
|
||||
|
||||
expand({ fn, Meta, Pairs }, E) ->
|
||||
assert_no_match_or_guard_scope(Meta, fn, E),
|
||||
elixir_fn:expand(Meta, Pairs, E);
|
||||
|
||||
%% Case/Receive/Try
|
||||
|
||||
expand({'case', Meta, [Expr, KV]}, E) ->
|
||||
assert_no_match_or_guard_scope(Meta, 'case', E),
|
||||
{ EExpr, EE } = expand(Expr, E),
|
||||
{ EClauses, EC } = elixir_exp_clauses:'case'(Meta, KV, EE),
|
||||
{ { 'case', Meta, [EExpr, EClauses] }, EC };
|
||||
|
||||
expand({'receive', Meta, [KV]}, E) ->
|
||||
assert_no_match_or_guard_scope(Meta, 'receive', E),
|
||||
{ EClauses, EC } = elixir_exp_clauses:'receive'(Meta, KV, E),
|
||||
{ { 'receive', Meta, [EClauses] }, EC };
|
||||
|
||||
expand({'try', Meta, [KV]}, E) ->
|
||||
assert_no_match_or_guard_scope(Meta, 'try', E),
|
||||
{ EClauses, EC } = elixir_exp_clauses:'try'(Meta, KV, E),
|
||||
{ { 'try', Meta, [EClauses] }, EC };
|
||||
|
||||
%% Comprehensions
|
||||
|
||||
expand({ Kind, Meta, Args }, E) when is_list(Args), (Kind == lc) orelse (Kind == bc) ->
|
||||
expand_comprehension(Meta, Kind, Args, E);
|
||||
|
||||
%% Super
|
||||
|
||||
expand({ super, Meta, Args }, E) when is_list(Args) ->
|
||||
assert_no_match_or_guard_scope(Meta, super, E),
|
||||
{ EArgs, EA } = expand_args(Args, E),
|
||||
{ { super, Meta, EArgs }, EA };
|
||||
|
||||
%% Vars
|
||||
|
||||
expand({ '^', Meta, [Arg] }, #elixir_env{context=match} = E) ->
|
||||
case expand(Arg, E) of
|
||||
{ { Name, _, Kind } = EArg, EA } when is_atom(Name), is_atom(Kind) ->
|
||||
{ { '^', Meta, [EArg] }, EA };
|
||||
_ ->
|
||||
Msg = "invalid argument for unary operator ^, expected an existing variable, got: ^~ts",
|
||||
compile_error(Meta, E#elixir_env.file, Msg, ['Elixir.Macro':to_string(Arg)])
|
||||
end;
|
||||
expand({ '^', Meta, [Arg] }, E) ->
|
||||
compile_error(Meta, E#elixir_env.file,
|
||||
"cannot use ^~ts outside of match clauses", ['Elixir.Macro':to_string(Arg)]);
|
||||
|
||||
expand({ '_', _, Kind } = Var, E) when is_atom(Kind) ->
|
||||
{ Var, E };
|
||||
expand({ Name, Meta, Kind } = Var, #elixir_env{context=match, export_vars=Export} = E) when is_atom(Name), is_atom(Kind) ->
|
||||
Pair = { Name, var_kind(Meta, Kind) },
|
||||
NewVars = ordsets:add_element(Pair, E#elixir_env.vars),
|
||||
NewExport = case (Export /= nil) andalso (lists:keyfind(export, 1, Meta) /= { export, false }) of
|
||||
true -> ordsets:add_element(Pair, Export);
|
||||
false -> Export
|
||||
end,
|
||||
{ Var, E#elixir_env{vars=NewVars, export_vars=NewExport} };
|
||||
expand({ Name, Meta, Kind } = Var, #elixir_env{vars=Vars} = E) when is_atom(Name), is_atom(Kind) ->
|
||||
case lists:member({ Name, var_kind(Meta, Kind) }, Vars) of
|
||||
true ->
|
||||
{ Var, E };
|
||||
false ->
|
||||
VarMeta = lists:keyfind(var, 1, Meta),
|
||||
if
|
||||
VarMeta == { var, true } ->
|
||||
Extra = case Kind of
|
||||
nil -> "";
|
||||
_ -> io_lib:format(" (context ~ts)", [elixir_aliases:inspect(Kind)])
|
||||
end,
|
||||
|
||||
compile_error(Meta, E#elixir_env.file, "expected var ~ts~ts to expand to an existing "
|
||||
"variable or be a part of a match", [Name, Extra]);
|
||||
E#elixir_env.context == guard ->
|
||||
compile_error(Meta, E#elixir_env.file, "unknown variable ~ts or cannot invoke "
|
||||
"function ~ts/0 inside guard", [Name, Name]);
|
||||
true ->
|
||||
expand({ Name, Meta, [] }, E)
|
||||
end
|
||||
end;
|
||||
|
||||
%% Local calls
|
||||
|
||||
expand({ Atom, Meta, Args }, E) when is_atom(Atom), is_list(Meta), is_list(Args) ->
|
||||
assert_no_ambiguous_op(Atom, Meta, Args, E),
|
||||
|
||||
elixir_dispatch:dispatch_import(Meta, Atom, Args, E, fun() ->
|
||||
expand_local(Meta, Atom, Args, E)
|
||||
end);
|
||||
|
||||
%% Remote calls
|
||||
|
||||
expand({ { '.', DotMeta, [Left, Right] }, Meta, Args }, E)
|
||||
when (is_tuple(Left) orelse is_atom(Left)), is_atom(Right), is_list(Meta), is_list(Args) ->
|
||||
{ ELeft, EL } = expand(Left, E),
|
||||
|
||||
elixir_dispatch:dispatch_require(Meta, ELeft, Right, Args, EL, fun(Receiver) ->
|
||||
expand_remote(Receiver, DotMeta, Right, Meta, Args, E, EL)
|
||||
end);
|
||||
|
||||
%% Anonymous calls
|
||||
|
||||
expand({ { '.', DotMeta, [Expr] }, Meta, Args }, E) when is_list(Args) ->
|
||||
{ EExpr, EE } = expand(Expr, E),
|
||||
if
|
||||
is_atom(EExpr) ->
|
||||
compile_error(Meta, E#elixir_env.file, "invalid function call :~ts.()", [EExpr]);
|
||||
true ->
|
||||
{ EArgs, EA } = expand_args(Args, elixir_env:mergea(E, EE)),
|
||||
{ { { '.', DotMeta, [EExpr] }, Meta, EArgs }, elixir_env:mergev(EE, EA) }
|
||||
end;
|
||||
|
||||
%% Invalid calls
|
||||
|
||||
expand({ _, Meta, Args } = Invalid, E) when is_list(Meta) and is_list(Args) ->
|
||||
compile_error(Meta, E#elixir_env.file, "invalid call ~ts",
|
||||
['Elixir.Macro':to_string(Invalid)]);
|
||||
|
||||
expand({ _, _, _ } = Tuple, E) ->
|
||||
compile_error([{line,0}], E#elixir_env.file, "invalid quoted expression: ~ts",
|
||||
['Elixir.Kernel':inspect(Tuple, [{records,false}])]);
|
||||
|
||||
%% Literals
|
||||
|
||||
expand({ Left, Right }, E) ->
|
||||
{ [ELeft, ERight], EE } = expand_args([Left, Right], E),
|
||||
{ { ELeft, ERight }, EE };
|
||||
|
||||
expand(List, #elixir_env{context=match} = E) when is_list(List) ->
|
||||
expand_list(List, fun expand/2, E, []);
|
||||
|
||||
expand(List, E) when is_list(List) ->
|
||||
{ EArgs, { EC, EV } } = expand_list(List, fun expand_arg/2, {E, E}, []),
|
||||
{ EArgs, elixir_env:mergea(EV, EC) };
|
||||
|
||||
expand(Function, E) when is_function(Function) ->
|
||||
case (erlang:fun_info(Function, type) == { type, external }) andalso
|
||||
(erlang:fun_info(Function, env) == { env, [] }) of
|
||||
true ->
|
||||
{ Function, E };
|
||||
false ->
|
||||
compile_error([{line,0}], E#elixir_env.file,
|
||||
"invalid quoted expression: ~ts", ['Elixir.Kernel':inspect(Function)])
|
||||
end;
|
||||
|
||||
expand(Other, E) when is_number(Other); is_atom(Other); is_binary(Other); is_pid(Other) ->
|
||||
{ Other, E };
|
||||
|
||||
expand(Other, E) ->
|
||||
compile_error([{line,0}], E#elixir_env.file,
|
||||
"invalid quoted expression: ~ts", ['Elixir.Kernel':inspect(Other)]).
|
||||
|
||||
%% Helpers
|
||||
|
||||
expand_list([{ '|', Meta, [_, _] = Args }], Fun, Acc, List) ->
|
||||
{ EArgs, EAcc } = lists:mapfoldl(Fun, Acc, Args),
|
||||
expand_list([], Fun, EAcc, [{ '|', Meta, EArgs }|List]);
|
||||
expand_list([H|T], Fun, Acc, List) ->
|
||||
{ EArg, EAcc } = Fun(H, Acc),
|
||||
expand_list(T, Fun, EAcc, [EArg|List]);
|
||||
expand_list([], _Fun, Acc, List) ->
|
||||
{ lists:reverse(List), Acc }.
|
||||
|
||||
expand_many(Args, E) ->
|
||||
lists:mapfoldl(fun expand/2, E, Args).
|
||||
|
||||
%% Variables in arguments are not propagated from one
|
||||
%% argument to the other. For instance:
|
||||
%%
|
||||
%% x = 1
|
||||
%% foo(x = x + 2, x)
|
||||
%% x
|
||||
%%
|
||||
%% Should be the same as:
|
||||
%%
|
||||
%% foo(3, 1)
|
||||
%% 3
|
||||
%%
|
||||
%% However, lexical information is.
|
||||
expand_arg(Arg, Acc) when is_number(Arg); is_atom(Arg); is_binary(Arg); is_pid(Arg); is_function(Arg) ->
|
||||
{ Arg, Acc };
|
||||
expand_arg(Arg, { Acc1, Acc2 }) ->
|
||||
{ EArg, EAcc } = expand(Arg, Acc1),
|
||||
{ EArg, { elixir_env:mergea(Acc1, EAcc), elixir_env:mergev(Acc2, EAcc) } }.
|
||||
|
||||
expand_args(Args, #elixir_env{context=match} = E) ->
|
||||
lists:mapfoldl(fun expand/2, E, Args);
|
||||
expand_args(Args, E) ->
|
||||
{ EArgs, { EC, EV } } = lists:mapfoldl(fun expand_arg/2, {E, E}, Args),
|
||||
{ EArgs, elixir_env:mergea(EV, EC) }.
|
||||
|
||||
%% Match/var helpers
|
||||
|
||||
var_kind(Meta, Kind) ->
|
||||
case lists:keyfind(counter, 1, Meta) of
|
||||
{ counter, Counter } -> Counter;
|
||||
false -> Kind
|
||||
end.
|
||||
|
||||
%% Locals
|
||||
|
||||
assert_no_ambiguous_op(Name, Meta, [Arg], E) ->
|
||||
case lists:keyfind(ambiguous_op, 1, Meta) of
|
||||
{ ambiguous_op, Kind } ->
|
||||
case lists:member({ Name, Kind }, E#elixir_env.vars) of
|
||||
true ->
|
||||
compile_error(Meta, E#elixir_env.file, "\"~ts ~ts\" looks like a function call but "
|
||||
"there is a variable named \"~ts\", please use explicit parenthesis or even spaces",
|
||||
[Name, 'Elixir.Macro':to_string(Arg), Name]);
|
||||
false ->
|
||||
ok
|
||||
end;
|
||||
_ ->
|
||||
ok
|
||||
end;
|
||||
assert_no_ambiguous_op(_Atom, _Meta, _Args, _E) ->
|
||||
ok.
|
||||
|
||||
expand_local(Meta, Name, Args, #elixir_env{local=nil, function=nil} = E) ->
|
||||
{ EArgs, EA } = expand_args(Args, E),
|
||||
{ { Name, Meta, EArgs }, EA };
|
||||
expand_local(Meta, Name, Args, #elixir_env{local=nil, module=Module, function=Function} = E) ->
|
||||
elixir_locals:record_local({ Name, length(Args) }, Module, Function),
|
||||
{ EArgs, EA } = expand_args(Args, E),
|
||||
{ { Name, Meta, EArgs }, EA };
|
||||
expand_local(Meta, Name, Args, E) ->
|
||||
expand({ { '.', Meta, [E#elixir_env.local, Name] }, Meta, Args }, E).
|
||||
|
||||
%% Remote
|
||||
|
||||
expand_remote(Receiver, DotMeta, Right, Meta, Args, E, EL) ->
|
||||
if
|
||||
is_atom(Receiver) -> elixir_lexical:record_remote(Receiver, E#elixir_env.lexical_tracker);
|
||||
true -> ok
|
||||
end,
|
||||
{ EArgs, EA } = expand_args(Args, E),
|
||||
{ { { '.', DotMeta, [Receiver, Right] }, Meta, EArgs }, elixir_env:mergev(EL, EA) }.
|
||||
|
||||
%% Lexical helpers
|
||||
|
||||
expand_opts(Meta, Kind, Allowed, Opts, E) ->
|
||||
{ EOpts, EE } = expand(Opts, E),
|
||||
validate_opts(Meta, Kind, Allowed, EOpts, EE),
|
||||
{ EOpts, EE }.
|
||||
|
||||
validate_opts(Meta, Kind, Allowed, Opts, E) when is_list(Opts) ->
|
||||
[begin
|
||||
compile_error(Meta, E#elixir_env.file,
|
||||
"unsupported option ~ts given to ~s", ['Elixir.Kernel':inspect(Key), Kind])
|
||||
end || { Key, _ } <- Opts, not lists:member(Key, Allowed)];
|
||||
|
||||
validate_opts(Meta, Kind, _Allowed, _Opts, S) ->
|
||||
compile_error(Meta, S#elixir_scope.file, "invalid options for ~s, expected a keyword list", [Kind]).
|
||||
|
||||
no_alias_opts(KV) when is_list(KV) ->
|
||||
case lists:keyfind(as, 1, KV) of
|
||||
{ as, As } -> lists:keystore(as, 1, KV, { as, no_alias_expansion(As) });
|
||||
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.
|
||||
|
||||
expand_require(Meta, Ref, KV, E) ->
|
||||
RE = E#elixir_env{requires=ordsets:add_element(Ref, E#elixir_env.requires)},
|
||||
expand_alias(Meta, false, Ref, KV, RE).
|
||||
|
||||
expand_alias(Meta, IncludeByDefault, Ref, KV, #elixir_env{context_modules=Context} = E) ->
|
||||
New = expand_as(lists:keyfind(as, 1, KV), Meta, IncludeByDefault, Ref, E),
|
||||
|
||||
%% Add the alias to context_modules if defined is true.
|
||||
%% This is used by defmodule in order to store the defined
|
||||
%% module in context modules.
|
||||
NewContext =
|
||||
case lists:keyfind(defined, 1, Meta) of
|
||||
{ defined, Mod } when is_atom(Mod) -> [Mod|Context];
|
||||
false -> Context
|
||||
end,
|
||||
|
||||
{ Aliases, MacroAliases } = elixir_aliases:store(Meta, New, Ref, KV, E#elixir_env.aliases,
|
||||
E#elixir_env.macro_aliases, E#elixir_env.lexical_tracker),
|
||||
|
||||
E#elixir_env{aliases=Aliases, macro_aliases=MacroAliases, context_modules=NewContext}.
|
||||
|
||||
expand_as({ as, true }, _Meta, _IncludeByDefault, Ref, _E) ->
|
||||
elixir_aliases:last(Ref);
|
||||
expand_as({ as, false }, _Meta, _IncludeByDefault, Ref, _E) ->
|
||||
Ref;
|
||||
expand_as({ as, Atom }, Meta, _IncludeByDefault, _Ref, E) when is_atom(Atom) ->
|
||||
case length(string:tokens(atom_to_list(Atom), ".")) of
|
||||
1 -> compile_error(Meta, E#elixir_env.file,
|
||||
"invalid value for keyword :as, expected an alias, got atom: ~ts", [elixir_aliases:inspect(Atom)]);
|
||||
2 -> Atom;
|
||||
_ -> compile_error(Meta, E#elixir_env.file,
|
||||
"invalid value for keyword :as, expected an alias, got nested alias: ~ts", [elixir_aliases:inspect(Atom)])
|
||||
end;
|
||||
expand_as(false, _Meta, IncludeByDefault, Ref, _E) ->
|
||||
if IncludeByDefault -> elixir_aliases:last(Ref);
|
||||
true -> Ref
|
||||
end;
|
||||
expand_as({ as, Other }, Meta, _IncludeByDefault, _Ref, E) ->
|
||||
compile_error(Meta, E#elixir_env.file,
|
||||
"invalid value for keyword :as, expected an alias, got: ~ts", ['Elixir.Macro':to_string(Other)]).
|
||||
|
||||
%% Comprehensions
|
||||
|
||||
expand_comprehension(Meta, Kind, Args, E) ->
|
||||
case elixir_utils:split_last(Args) of
|
||||
{ Cases, [{do,Expr}] } ->
|
||||
{ ECases, EC } = lists:mapfoldl(fun expand_comprehension_clause/2, E, Cases),
|
||||
{ EExpr, _ } = expand(Expr, EC),
|
||||
{ { Kind, Meta, ECases ++ [[{do,EExpr}]] }, E };
|
||||
_ ->
|
||||
compile_error(Meta, E#elixir_env.file, "missing do keyword in comprehension ~ts", [Kind])
|
||||
end.
|
||||
|
||||
expand_comprehension_clause({Gen, Meta, [Left, Right]}, E) when Gen == inbits; Gen == inlist ->
|
||||
{ ERight, ER } = expand(Right, E),
|
||||
{ ELeft, EL } = elixir_exp_clauses:match(fun expand/2, Left, E),
|
||||
{ { Gen, Meta, [ELeft, ERight] }, elixir_env:mergev(EL, ER) };
|
||||
expand_comprehension_clause(X, E) ->
|
||||
expand(X, E).
|
||||
|
||||
%% Assertions
|
||||
|
||||
assert_no_match_or_guard_scope(Meta, Kind, E) ->
|
||||
assert_no_match_scope(Meta, Kind, E),
|
||||
assert_no_guard_scope(Meta, Kind, E).
|
||||
assert_no_match_scope(Meta, _Kind, #elixir_env{context=match,file=File}) ->
|
||||
compile_error(Meta, File, "invalid expression in match");
|
||||
assert_no_match_scope(_Meta, _Kind, _E) -> [].
|
||||
assert_no_guard_scope(Meta, _Kind, #elixir_env{context=guard,file=File}) ->
|
||||
compile_error(Meta, File, "invalid expression in guard");
|
||||
assert_no_guard_scope(_Meta, _Kind, _E) -> [].
|
||||
@@ -0,0 +1,200 @@
|
||||
%% Handle code related to args, guard and -> matching for case,
|
||||
%% fn, receive and friends. try is handled in elixir_try.
|
||||
-module(elixir_exp_clauses).
|
||||
-export([match/3, clause/5, 'case'/3, 'receive'/3, 'try'/3, def/5]).
|
||||
-import(elixir_errors, [compile_error/3, compile_error/4]).
|
||||
-include("elixir.hrl").
|
||||
|
||||
match(Fun, Expr, #elixir_env{context=Context} = E) ->
|
||||
{ EExpr, EE } = Fun(Expr, E#elixir_env{context=match}),
|
||||
{ EExpr, EE#elixir_env{context=Context} }.
|
||||
|
||||
def(Fun, Args, Guards, Body, E) ->
|
||||
{ EArgs, EA } = match(Fun, Args, E),
|
||||
{ EGuards, EG } = guard(Guards, EA#elixir_env{context=guard}),
|
||||
{ EBody, EB } = elixir_exp:expand(Body, EG#elixir_env{context=E#elixir_env.context}),
|
||||
{ EArgs, EGuards, EBody, EB }.
|
||||
|
||||
clause(Meta, Kind, Fun, { '->', ClauseMeta, [_, _] } = Clause, E) when is_function(Fun, 3) ->
|
||||
clause(Meta, Kind, fun(X, Acc) -> Fun(ClauseMeta, X, Acc) end, Clause, E);
|
||||
clause(_Meta, _Kind, Fun, { '->', Meta, [Left, Right] }, E) ->
|
||||
{ ELeft, EL } = head(Fun, Left, E),
|
||||
{ ERight, ER } = elixir_exp:expand(Right, EL),
|
||||
{ { '->', Meta, [ELeft, ERight] }, ER };
|
||||
clause(Meta, Kind, _Fun, _, E) ->
|
||||
compile_error(Meta, E#elixir_env.file, "expected -> clauses in ~ts", [Kind]).
|
||||
|
||||
head(Fun, [{ 'when', Meta, [_,_|_] = All }], E) ->
|
||||
{ Args, Guard } = elixir_utils:split_last(All),
|
||||
{ EArgs, EA } = match(Fun, Args, E),
|
||||
{ EGuard, EG } = guard(Guard, EA#elixir_env{context=guard}),
|
||||
{ [{ 'when', Meta, EArgs ++ [EGuard] }], EG#elixir_env{context=E#elixir_env.context} };
|
||||
head(Fun, Args, E) ->
|
||||
match(Fun, Args, E).
|
||||
|
||||
guard({ 'when', Meta, [Left, Right] }, E) ->
|
||||
{ ELeft, EL } = guard(Left, E),
|
||||
{ ERight, ER } = guard(Right, EL),
|
||||
{ { 'when', Meta, [ELeft, ERight] }, ER };
|
||||
guard(Other, E) ->
|
||||
elixir_exp:expand(Other, E).
|
||||
|
||||
%% Case
|
||||
|
||||
'case'(Meta, [], E) ->
|
||||
compile_error(Meta, E#elixir_env.file, "missing do keyword in case");
|
||||
'case'(Meta, KV, E) when not is_list(KV) ->
|
||||
compile_error(Meta, E#elixir_env.file, "invalid arguments for case");
|
||||
'case'(Meta, KV, E) ->
|
||||
EE = E#elixir_env{export_vars=[]},
|
||||
{ EClauses, EVars } = lists:mapfoldl(fun(X, Acc) -> do_case(Meta, X, Acc, EE) end, [], KV),
|
||||
{ EClauses, elixir_env:mergev(EVars, E) }.
|
||||
|
||||
do_case(Meta, { 'do', _ } = Do, Acc, E) ->
|
||||
expand_with_export(Meta, 'case', expand_arg(Meta, 'case', 'do'), Do, Acc, E);
|
||||
do_case(Meta, { Key, _ }, _Acc, E) ->
|
||||
compile_error(Meta, E#elixir_env.file, "unexpected keyword ~ts in case", [Key]).
|
||||
|
||||
%% Receive
|
||||
|
||||
'receive'(Meta, [], E) ->
|
||||
compile_error(Meta, E#elixir_env.file, "missing do or after keyword in receive");
|
||||
'receive'(Meta, KV, E) when not is_list(KV) ->
|
||||
compile_error(Meta, E#elixir_env.file, "invalid arguments for receive");
|
||||
'receive'(Meta, KV, E) ->
|
||||
EE = E#elixir_env{export_vars=[]},
|
||||
{ EClauses, EVars } = lists:mapfoldl(fun(X, Acc) -> do_receive(Meta, X, Acc, EE) end, [], KV),
|
||||
{ EClauses, elixir_env:mergev(EVars, E) }.
|
||||
|
||||
do_receive(_Meta, { 'do', nil } = Do, Acc, _E) ->
|
||||
{ Do, Acc };
|
||||
do_receive(Meta, { 'do', _ } = Do, Acc, E) ->
|
||||
expand_with_export(Meta, 'receive', expand_arg(Meta, 'receive', 'do'), Do, Acc, E);
|
||||
do_receive(_Meta, { 'after', [{ '->', Meta, [[Left], Right] }] }, Acc, E) ->
|
||||
{ ELeft, EL } = elixir_exp:expand(Left, E),
|
||||
{ ERight, ER } = elixir_exp:expand(Right, EL),
|
||||
EClause = { 'after', [{ '->', Meta, [[ELeft], ERight] }] },
|
||||
{ EClause, elixir_env:merge_vars(Acc, ER#elixir_env.export_vars) };
|
||||
do_receive(Meta, { 'after', _ }, _Acc, E) ->
|
||||
compile_error(Meta, E#elixir_env.file, "expected a single -> clause for after in receive");
|
||||
do_receive(Meta, { Key, _ }, _Acc, E) ->
|
||||
compile_error(Meta, E#elixir_env.file, "unexpected keyword ~ts in receive", [Key]).
|
||||
|
||||
%% Try
|
||||
|
||||
'try'(Meta, [], E) ->
|
||||
compile_error(Meta, E#elixir_env.file, "missing do keywords in try");
|
||||
'try'(Meta, KV, E) when not is_list(KV) ->
|
||||
elixir_errors:compile_error(Meta, E#elixir_env.file, "invalid arguments for try");
|
||||
'try'(Meta, KV, E) ->
|
||||
{ lists:map(fun(X) -> do_try(Meta, X, E) end, KV), E }.
|
||||
|
||||
do_try(_Meta, { 'do', Expr }, E) ->
|
||||
{ EExpr, _ } = elixir_exp:expand(Expr, E),
|
||||
{ 'do', EExpr };
|
||||
do_try(_Meta, { 'after', Expr }, E) ->
|
||||
{ EExpr, _ } = elixir_exp:expand(Expr, E),
|
||||
{ 'after', EExpr };
|
||||
do_try(Meta, { 'else', _ } = Else, E) ->
|
||||
expand_without_export(Meta, 'try', expand_arg(Meta, 'try', 'else'), Else, E);
|
||||
do_try(Meta, { 'catch', _ } = Catch, E) ->
|
||||
expand_without_export(Meta, 'try', fun elixir_exp:expand_many/2, Catch, E);
|
||||
do_try(Meta, { 'rescue', _ } = Rescue, E) ->
|
||||
expand_without_export(Meta, 'try', fun expand_rescue/3, Rescue, E);
|
||||
do_try(Meta, { Key, _ }, E) ->
|
||||
compile_error(Meta, E#elixir_env.file, "unexpected keyword ~ts in try", [Key]).
|
||||
|
||||
expand_rescue(Meta, [Arg], E) ->
|
||||
case expand_rescue(Arg, E) of
|
||||
{ EArg, EA } ->
|
||||
{ [EArg], EA };
|
||||
false ->
|
||||
compile_error(Meta, E#elixir_env.file, "invalid rescue clause. The clause should "
|
||||
"match on an alias, a variable or be in the `var in [alias]` format")
|
||||
end;
|
||||
expand_rescue(Meta, _, E) ->
|
||||
compile_error(Meta, E#elixir_env.file, "expected one arg for rescue clauses (->) in try").
|
||||
|
||||
%% rescue var => var in _
|
||||
expand_rescue({ Name, _, Atom } = Var, E) when is_atom(Name), is_atom(Atom) ->
|
||||
expand_rescue({ in, [], [Var, { '_', [], E#elixir_env.module }] }, E);
|
||||
|
||||
%% rescue var in [Exprs]
|
||||
expand_rescue({ in, Meta, [Left, Right] }, E) ->
|
||||
{ ERight, ER } = elixir_exp:expand(Right, E#elixir_env{context=nil}),
|
||||
{ ELeft, EL } = elixir_exp:expand(Left, ER#elixir_env{context=match}),
|
||||
|
||||
case ELeft of
|
||||
{ Name, _, Atom } when is_atom(Name), is_atom(Atom) ->
|
||||
case normalize_rescue(ERight) of
|
||||
false -> false;
|
||||
Other -> { { in, Meta, [ELeft, Other] }, EL }
|
||||
end;
|
||||
_ ->
|
||||
false
|
||||
end;
|
||||
|
||||
%% rescue Error => _ in [Error]
|
||||
expand_rescue(Arg, E) ->
|
||||
expand_rescue({ in, [], [{ '_', [], E#elixir_env.module }, Arg] }, E).
|
||||
|
||||
normalize_rescue({ '_', _, Atom } = N) when is_atom(Atom) -> N;
|
||||
normalize_rescue(Atom) when is_atom(Atom) -> [Atom];
|
||||
normalize_rescue(Other) ->
|
||||
is_list(Other)
|
||||
andalso lists:all(fun is_var_or_atom/1, Other)
|
||||
andalso Other.
|
||||
|
||||
is_var_or_atom({ Name, _, Atom }) when is_atom(Name), is_atom(Atom) -> true;
|
||||
is_var_or_atom(Atom) when is_atom(Atom) -> true;
|
||||
is_var_or_atom(_) -> false.
|
||||
|
||||
%% Expansion helpers
|
||||
|
||||
export_vars({ Left, Meta, Right }) when is_atom(Left), is_list(Meta), is_atom(Right) ->
|
||||
{ Left, [{export,false}|Meta], Right };
|
||||
export_vars({ Left, Meta, Right }) ->
|
||||
{ export_vars(Left), Meta, export_vars(Right) };
|
||||
export_vars({ Left, Right }) ->
|
||||
{ export_vars(Left), export_vars(Right) };
|
||||
export_vars(List) when is_list(List) ->
|
||||
[export_vars(X) || X <- List];
|
||||
export_vars(Other) ->
|
||||
Other.
|
||||
|
||||
%% Returns a function that expands arguments
|
||||
%% considering we have at maximum one entry.
|
||||
expand_arg(Meta, Kind, Key) ->
|
||||
fun
|
||||
([Arg], E) ->
|
||||
{ EArg, EA } = elixir_exp:expand(Arg, E),
|
||||
{ [EArg], EA };
|
||||
(_, E) ->
|
||||
compile_error(Meta, E#elixir_env.file, "expected one arg for ~ts clauses (->) in ~ts", [Key, Kind])
|
||||
end.
|
||||
|
||||
%% Expands all -> pairs in a given key keeping the overall vars.
|
||||
expand_with_export(Meta, Kind, Fun, { Key, Clauses }, Acc, E) when is_list(Clauses) ->
|
||||
EFun =
|
||||
case lists:keyfind(export_all, 1, Meta) of
|
||||
{ export_all, true } -> Fun;
|
||||
_ -> fun(ExportArgs, ExportE) -> Fun(export_vars(ExportArgs), ExportE) end
|
||||
end,
|
||||
Transformer = fun(Clause, Vars) ->
|
||||
{ EClause, EC } = clause(Meta, Kind, EFun, Clause, E),
|
||||
{ EClause, elixir_env:merge_vars(Vars, EC#elixir_env.export_vars) }
|
||||
end,
|
||||
{ EClauses, EVars } = lists:mapfoldl(Transformer, Acc, Clauses),
|
||||
{ { Key, EClauses }, EVars };
|
||||
expand_with_export(Meta, Kind, _Fun, { Key, _ }, _Acc, E) ->
|
||||
compile_error(Meta, E#elixir_env.file, "expected -> clauses for ~ts in ~ts", [Key, Kind]).
|
||||
|
||||
%% Expands all -> pairs in a given key but do not keep the overall vars.
|
||||
expand_without_export(Meta, Kind, Fun, { Key, Clauses }, E) when is_list(Clauses) ->
|
||||
Transformer = fun(Clause) ->
|
||||
{ EClause, _ } = clause(Meta, Kind, Fun, Clause, E),
|
||||
EClause
|
||||
end,
|
||||
{ Key, lists:map(Transformer, Clauses) };
|
||||
expand_without_export(Meta, Kind, _Fun, { Key, _ }, E) ->
|
||||
compile_error(Meta, E#elixir_env.file, "expected -> clauses for ~ts in ~ts", [Key, Kind]).
|
||||
@@ -1,13 +1,13 @@
|
||||
-module(elixir_fn).
|
||||
-export([fn/3, capture/3]).
|
||||
-import(elixir_scope, [umergec/2]).
|
||||
-import(elixir_errors, [syntax_error/3, syntax_error/4, compile_error/4]).
|
||||
-export([translate/3, capture/3, expand/3]).
|
||||
-import(elixir_errors, [compile_error/3, compile_error/4]).
|
||||
-include("elixir.hrl").
|
||||
|
||||
fn(Meta, Clauses, S) ->
|
||||
Transformer = fun({ ArgsWithGuards, CMeta, Expr }, Acc) ->
|
||||
translate(Meta, Clauses, S) ->
|
||||
Transformer = fun({ '->', CMeta, [ArgsWithGuards, Expr] }, Acc) ->
|
||||
{ Args, Guards } = elixir_clauses:extract_splat_guards(ArgsWithGuards),
|
||||
elixir_clauses:assigns_block(?line(CMeta), fun translate_fn_match/2, Args, [Expr], Guards, umergec(S, Acc))
|
||||
{ TClause, TS } = elixir_clauses:clause(?line(CMeta), fun translate_fn_match/2, Args, Expr, Guards, Acc),
|
||||
{ TClause, elixir_scope:mergef(S, TS) }
|
||||
end,
|
||||
|
||||
{ TClauses, NS } = lists:mapfoldl(Transformer, S, Clauses),
|
||||
@@ -15,21 +15,32 @@ fn(Meta, Clauses, S) ->
|
||||
|
||||
case length(lists:usort(Arities)) of
|
||||
1 ->
|
||||
{ { 'fun', ?line(Meta), { clauses, TClauses } }, umergec(S, NS) };
|
||||
{ { 'fun', ?line(Meta), { clauses, TClauses } }, NS };
|
||||
_ ->
|
||||
syntax_error(Meta, S#elixir_scope.file,
|
||||
"cannot mix clauses with different arities in function definition")
|
||||
compile_error(Meta, S#elixir_scope.file,
|
||||
"cannot mix clauses with different arities in function definition")
|
||||
end.
|
||||
|
||||
translate_fn_match(Arg, S) ->
|
||||
{ TArg, TS } = elixir_translator:translate(Arg, S#elixir_scope{extra=fn_match}),
|
||||
{ TArg, TS } = elixir_translator:translate_many(Arg, S#elixir_scope{extra=fn_match}),
|
||||
{ TArg, TS#elixir_scope{extra=S#elixir_scope.extra} }.
|
||||
|
||||
capture(Meta, { '/', _, [{ { '.', _, [_, F] } = Dot, RequireMeta , [] }, A] }, S) when is_atom(F), is_integer(A) ->
|
||||
Args = [{ '&', [], [X] } || X <- lists:seq(1, A)],
|
||||
capture_require(Meta, { Dot, RequireMeta, Args }, S, true);
|
||||
%% Expansion
|
||||
|
||||
capture(Meta, { '/', _, [{ F, _, C }, A] }, S) when is_atom(F), is_integer(A), is_atom(C) ->
|
||||
expand(Meta, Clauses, E) when is_list(Clauses) ->
|
||||
Transformer = fun(Clause) ->
|
||||
{ EClause, _ } = elixir_exp_clauses:clause(Meta, fn, fun elixir_exp:expand_many/2, Clause, E),
|
||||
EClause
|
||||
end,
|
||||
{ { fn, Meta, lists:map(Transformer, Clauses) }, E }.
|
||||
|
||||
%% Capture
|
||||
|
||||
capture(Meta, { '/', _, [{ { '.', _, [_, F] } = Dot, RequireMeta , [] }, A] }, E) when is_atom(F), is_integer(A) ->
|
||||
Args = [{ '&', [], [X] } || X <- lists:seq(1, A)],
|
||||
capture_require(Meta, { Dot, RequireMeta, Args }, E, true);
|
||||
|
||||
capture(Meta, { '/', _, [{ F, _, C }, A] }, E) when is_atom(F), is_integer(A), is_atom(C) ->
|
||||
ImportMeta =
|
||||
case lists:keyfind(import_fa, 1, Meta) of
|
||||
{ import_fa, { Receiver, Context } } ->
|
||||
@@ -40,115 +51,102 @@ capture(Meta, { '/', _, [{ F, _, C }, A] }, S) when is_atom(F), is_integer(A), i
|
||||
false -> Meta
|
||||
end,
|
||||
Args = [{ '&', [], [X] } || X <- lists:seq(1, A)],
|
||||
capture_import(Meta, { F, ImportMeta, Args }, S, true);
|
||||
capture_import(Meta, { F, ImportMeta, Args }, E, true);
|
||||
|
||||
capture(Meta, { { '.', _, [_, Fun] }, _, Args } = Expr, S) when is_atom(Fun), is_list(Args) ->
|
||||
capture_require(Meta, Expr, S, is_sequential_and_not_empty(Args));
|
||||
capture(Meta, { { '.', _, [_, Fun] }, _, Args } = Expr, E) when is_atom(Fun), is_list(Args) ->
|
||||
capture_require(Meta, Expr, E, is_sequential_and_not_empty(Args));
|
||||
|
||||
capture(Meta, { { '.', _, [_] }, _, Args } = Expr, S) when is_list(Args) ->
|
||||
do_capture(Meta, Expr, S, false);
|
||||
capture(Meta, { { '.', _, [_] }, _, Args } = Expr, E) when is_list(Args) ->
|
||||
do_capture(Meta, Expr, E, false);
|
||||
|
||||
capture(Meta, { '__block__', _, [Expr] }, S) ->
|
||||
capture(Meta, Expr, S);
|
||||
capture(Meta, { '__block__', _, [Expr] }, E) ->
|
||||
capture(Meta, Expr, E);
|
||||
|
||||
capture(Meta, { '__block__', _, _ } = Expr, S) ->
|
||||
capture(Meta, { '__block__', _, _ } = Expr, E) ->
|
||||
Message = "invalid args for &, block expressions are not allowed, got: ~ts",
|
||||
syntax_error(Meta, S#elixir_scope.file, Message, ['Elixir.Macro':to_string(Expr)]);
|
||||
compile_error(Meta, E#elixir_env.file, Message, ['Elixir.Macro':to_string(Expr)]);
|
||||
|
||||
capture(Meta, { Atom, _, Args } = Expr, S) when is_atom(Atom), is_list(Args) ->
|
||||
capture_import(Meta, Expr, S, is_sequential_and_not_empty(Args));
|
||||
capture(Meta, { Atom, _, Args } = Expr, E) when is_atom(Atom), is_list(Args) ->
|
||||
capture_import(Meta, Expr, E, is_sequential_and_not_empty(Args));
|
||||
|
||||
capture(Meta, { Left, Right }, S) ->
|
||||
capture(Meta, { '{}', Meta, [Left, Right] }, S);
|
||||
capture(Meta, { Left, Right }, E) ->
|
||||
capture(Meta, { '{}', Meta, [Left, Right] }, E);
|
||||
|
||||
capture(Meta, List, S) when is_list(List) ->
|
||||
do_capture(Meta, List, S, is_sequential_and_not_empty(List));
|
||||
capture(Meta, List, E) when is_list(List) ->
|
||||
do_capture(Meta, List, E, is_sequential_and_not_empty(List));
|
||||
|
||||
capture(Meta, Arg, S) when is_integer(Arg) ->
|
||||
compile_error(Meta, S#elixir_scope.file, "unhandled &~B outside of a capture", [Arg]);
|
||||
capture(Meta, Arg, E) ->
|
||||
invalid_capture(Meta, Arg, E).
|
||||
|
||||
capture(Meta, Arg, S) ->
|
||||
invalid_capture(Meta, Arg, S).
|
||||
capture_import(Meta, { Atom, ImportMeta, Args } = Expr, E, Sequential) ->
|
||||
Res = Sequential andalso
|
||||
elixir_dispatch:import_function(ImportMeta, Atom, length(Args), E),
|
||||
handle_capture(Res, Meta, Expr, E, Sequential).
|
||||
|
||||
%% Helpers
|
||||
capture_require(Meta, { { '.', _, [Left, Right] }, RequireMeta, Args } = Expr, E, Sequential) ->
|
||||
{ Mod, EE } = elixir_exp:expand(Left, E),
|
||||
Res = Sequential andalso is_atom(Mod) andalso
|
||||
elixir_dispatch:require_function(RequireMeta, Mod, Right, length(Args), EE),
|
||||
handle_capture(Res, Meta, Expr, EE, Sequential).
|
||||
|
||||
capture_import(Meta, { Atom, ImportMeta, Args } = Expr, S, Sequential) ->
|
||||
case Sequential andalso
|
||||
elixir_dispatch:import_function(ImportMeta, Atom, length(Args), S) of
|
||||
false -> do_capture(Meta, Expr, S, Sequential);
|
||||
Else -> Else
|
||||
handle_capture({ local, Fun, Arity }, _Meta, _Expr, _E, _Sequential) ->
|
||||
{ local, Fun, Arity };
|
||||
handle_capture({ remote, Receiver, Fun, Arity }, Meta, _Expr, E, _Sequential) ->
|
||||
Tree = { { '.', [], [erlang, make_fun] }, Meta, [Receiver, Fun, Arity] },
|
||||
{ expanded, Tree, E };
|
||||
handle_capture(false, Meta, Expr, E, Sequential) ->
|
||||
do_capture(Meta, Expr, E, Sequential).
|
||||
|
||||
do_capture(Meta, Expr, E, Sequential) ->
|
||||
case do_escape(Expr, elixir_counter:next(), E, []) of
|
||||
{ _, [] } when not Sequential ->
|
||||
invalid_capture(Meta, Expr, E);
|
||||
{ EExpr, EDict } ->
|
||||
EVars = validate(Meta, EDict, 1, E),
|
||||
Fn = { fn, Meta, [{ '->', Meta, [EVars, EExpr]}]},
|
||||
{ expanded, Fn, E }
|
||||
end.
|
||||
|
||||
capture_require(Meta, { { '.', _, [Left, Right] }, RequireMeta, Args } = Expr, S, Sequential) ->
|
||||
{ Mod, SE } = 'Elixir.Macro':expand_all(Left, elixir_scope:to_ex_env({ ?line(Meta), S }), S),
|
||||
|
||||
case Sequential andalso is_atom(Mod) andalso
|
||||
elixir_dispatch:require_function(RequireMeta, Mod, Right, length(Args), SE) of
|
||||
false -> do_capture(Meta, Expr, S, Sequential);
|
||||
Else -> Else
|
||||
end.
|
||||
|
||||
do_capture(Meta, Expr, S, Sequential) ->
|
||||
case do_escape(Expr, S, []) of
|
||||
{ _, _, [] } when not Sequential ->
|
||||
invalid_capture(Meta, Expr, S);
|
||||
{ TExpr, TS, TDict } ->
|
||||
TVars = validate(Meta, TDict, 1, S),
|
||||
fn(Meta, [{ TVars, Meta, TExpr }], TS)
|
||||
end.
|
||||
|
||||
invalid_capture(Meta, Arg, S) ->
|
||||
invalid_capture(Meta, Arg, E) ->
|
||||
Message = "invalid args for &, expected an expression in the format of &Mod.fun/arity, "
|
||||
"&local/arity or a capture containing at least one argument as &1, got: ~ts",
|
||||
syntax_error(Meta, S#elixir_scope.file, Message, ['Elixir.Macro':to_string(Arg)]).
|
||||
compile_error(Meta, E#elixir_env.file, Message, ['Elixir.Macro':to_string(Arg)]).
|
||||
|
||||
validate(Meta, [{ Pos, Var }|T], Pos, S) ->
|
||||
[Var|validate(Meta, T, Pos + 1, S)];
|
||||
validate(Meta, [{ Pos, Var }|T], Pos, E) ->
|
||||
[Var|validate(Meta, T, Pos + 1, E)];
|
||||
|
||||
validate(Meta, [{ Pos, _ }|_], Expected, S) ->
|
||||
compile_error(Meta, S#elixir_scope.file, "capture &~B cannot be defined without &~B", [Pos, Expected]);
|
||||
validate(Meta, [{ Pos, _ }|_], Expected, E) ->
|
||||
compile_error(Meta, E#elixir_env.file, "capture &~B cannot be defined without &~B", [Pos, Expected]);
|
||||
|
||||
validate(_Meta, [], _Pos, _S) ->
|
||||
validate(_Meta, [], _Pos, _E) ->
|
||||
[].
|
||||
|
||||
do_escape({ '&', Meta, [Pos] }, S, Dict) when is_integer(Pos), Pos > 0 ->
|
||||
case orddict:find(Pos, Dict) of
|
||||
{ ok, Var } ->
|
||||
{ Var, S, Dict };
|
||||
error ->
|
||||
{ Var, SC } = elixir_scope:build_ex_var(?line(Meta), S),
|
||||
{ Var, SC, orddict:store(Pos, Var, Dict) }
|
||||
end;
|
||||
do_escape({ '&', _, [Pos] }, Counter, _E, Dict) when is_integer(Pos), Pos > 0 ->
|
||||
Var = { list_to_atom([$x, $@+Pos]), [{ counter, Counter }], elixir_fn },
|
||||
{ Var, orddict:store(Pos, Var, Dict) };
|
||||
|
||||
do_escape({ '&', Meta, [Pos] }, S, _Dict) when is_integer(Pos) ->
|
||||
compile_error(Meta, S#elixir_scope.file, "capture &~B is not allowed", [Pos]);
|
||||
do_escape({ '&', Meta, [Pos] }, _Counter, E, _Dict) when is_integer(Pos) ->
|
||||
compile_error(Meta, E#elixir_env.file, "capture &~B is not allowed", [Pos]);
|
||||
|
||||
do_escape({ '&', Meta, _ } = Arg, S, _Dict) ->
|
||||
do_escape({ '&', Meta, _ } = Arg, _Counter, E, _Dict) ->
|
||||
Message = "nested captures via & are not allowed: ~ts",
|
||||
compile_error(Meta, S#elixir_scope.file, Message, ['Elixir.Macro':to_string(Arg)]);
|
||||
compile_error(Meta, E#elixir_env.file, Message, ['Elixir.Macro':to_string(Arg)]);
|
||||
|
||||
do_escape({ Left, Meta, Right }, S0, Dict0) ->
|
||||
{ TLeft, S1, Dict1 } = do_escape(Left, S0, Dict0),
|
||||
{ TRight, S2, Dict2 } = do_escape(Right, S1, Dict1),
|
||||
{ { TLeft, Meta, TRight }, S2, Dict2 };
|
||||
do_escape({ Left, Meta, Right }, Counter, E, Dict0) ->
|
||||
{ TLeft, Dict1 } = do_escape(Left, Counter, E, Dict0),
|
||||
{ TRight, Dict2 } = do_escape(Right, Counter, E, Dict1),
|
||||
{ { TLeft, Meta, TRight }, Dict2 };
|
||||
|
||||
do_escape({ Left, Right }, S0, Dict0) ->
|
||||
{ TLeft, S1, Dict1 } = do_escape(Left, S0, Dict0),
|
||||
{ TRight, S2, Dict2 } = do_escape(Right, S1, Dict1),
|
||||
{ { TLeft, TRight }, S2, Dict2 };
|
||||
do_escape({ Left, Right }, Counter, E, Dict0) ->
|
||||
{ TLeft, Dict1 } = do_escape(Left, Counter, E, Dict0),
|
||||
{ TRight, Dict2 } = do_escape(Right, Counter, E, Dict1),
|
||||
{ { TLeft, TRight }, Dict2 };
|
||||
|
||||
do_escape(List, S, Dict) when is_list(List) ->
|
||||
do_escape_list(List, S, Dict, []);
|
||||
do_escape(List, Counter, E, Dict) when is_list(List) ->
|
||||
lists:mapfoldl(fun(X, Acc) -> do_escape(X, Counter, E, Acc) end, Dict, List);
|
||||
|
||||
do_escape(Other, S, Dict) ->
|
||||
{ Other, S, Dict }.
|
||||
|
||||
do_escape_list([H|T], S, Dict, Acc) ->
|
||||
{ TH, TS, TDict } = do_escape(H, S, Dict),
|
||||
do_escape_list(T, TS, TDict, [TH|Acc]);
|
||||
|
||||
do_escape_list([], S, Dict, Acc) ->
|
||||
{ lists:reverse(Acc), S, Dict }.
|
||||
do_escape(Other, _Counter, _E, Dict) ->
|
||||
{ Other, Dict }.
|
||||
|
||||
is_sequential_and_not_empty([]) -> false;
|
||||
is_sequential_and_not_empty(List) -> is_sequential(List, 1).
|
||||
|
||||
@@ -7,55 +7,55 @@
|
||||
|
||||
%% IMPORT
|
||||
|
||||
import(Meta, Ref, Opts, S) ->
|
||||
SI =
|
||||
import(Meta, Ref, Opts, E) ->
|
||||
Res =
|
||||
case keyfind(only, Opts) of
|
||||
{ only, functions } ->
|
||||
import_functions(Meta, Ref, Opts, S);
|
||||
{ import_functions(Meta, Ref, Opts, E),
|
||||
E#elixir_env.macros };
|
||||
{ only, macros } ->
|
||||
import_macros(true, Meta, Ref, Opts, S);
|
||||
{ E#elixir_env.functions,
|
||||
import_macros(true, Meta, Ref, Opts, E) };
|
||||
{ only, List } when is_list(List) ->
|
||||
import_macros(false, Meta, Ref, Opts, import_functions(Meta, Ref, Opts, S));
|
||||
{ import_functions(Meta, Ref, Opts, E),
|
||||
import_macros(false, Meta, Ref, Opts, E) };
|
||||
false ->
|
||||
import_macros(false, Meta, Ref, Opts, import_functions(Meta, Ref, Opts, S))
|
||||
{ import_functions(Meta, Ref, Opts, E),
|
||||
import_macros(false, Meta, Ref, Opts, E) }
|
||||
end,
|
||||
record_warn(Meta, Ref, Opts, SI),
|
||||
SI.
|
||||
|
||||
import_functions(Meta, Ref, Opts, S) ->
|
||||
{ Functions, Temp } = calculate(Meta, Ref, Opts, S#elixir_scope.functions,
|
||||
S#elixir_scope.macro_functions, fun() -> get_functions(Ref) end, S),
|
||||
S#elixir_scope{functions=Functions, macro_functions=Temp}.
|
||||
record_warn(Meta, Ref, Opts, E),
|
||||
Res.
|
||||
|
||||
import_macros(Force, Meta, Ref, Opts, S) ->
|
||||
Existing = fun() ->
|
||||
import_functions(Meta, Ref, Opts, E) ->
|
||||
calculate(Meta, Ref, Opts, E#elixir_env.functions, E, fun() -> get_functions(Ref) end).
|
||||
|
||||
import_macros(Force, Meta, Ref, Opts, E) ->
|
||||
calculate(Meta, Ref, Opts, E#elixir_env.macros, E, fun() ->
|
||||
case Force of
|
||||
true -> get_macros(Meta, Ref, S);
|
||||
true -> get_macros(Meta, Ref, E);
|
||||
false -> get_optional_macros(Ref)
|
||||
end
|
||||
end,
|
||||
{ Macros, Temp } = calculate(Meta, Ref, Opts, S#elixir_scope.macros,
|
||||
S#elixir_scope.macro_macros, Existing, S),
|
||||
S#elixir_scope{macros=Macros, macro_macros=Temp}.
|
||||
end).
|
||||
|
||||
record_warn(Meta, Ref, Opts, S) ->
|
||||
record_warn(Meta, Ref, Opts, E) ->
|
||||
Warn =
|
||||
case keyfind(warn, Opts) of
|
||||
{ warn, false } -> false;
|
||||
{ warn, true } -> true;
|
||||
false -> not lists:keymember(context, 1, Meta)
|
||||
end,
|
||||
elixir_lexical:record_import(Ref, ?line(Meta), Warn, S#elixir_scope.lexical_tracker).
|
||||
elixir_lexical:record_import(Ref, ?line(Meta), Warn, E#elixir_env.lexical_tracker).
|
||||
|
||||
%% Calculates the imports based on only and except
|
||||
|
||||
calculate(Meta, Key, Opts, Old, Temp, Existing, S) ->
|
||||
calculate(Meta, Key, Opts, Old, E, Existing) ->
|
||||
New = case keyfind(only, Opts) of
|
||||
{ only, Only } when is_list(Only) ->
|
||||
case Only -- get_exports(Key) of
|
||||
[{Name,Arity}|_] ->
|
||||
Tuple = { invalid_import, { Key, Name, Arity } },
|
||||
elixir_errors:form_error(Meta, S#elixir_scope.file, ?MODULE, Tuple);
|
||||
elixir_errors:form_error(Meta, E#elixir_env.file, ?MODULE, Tuple);
|
||||
_ ->
|
||||
intersection(Only, Existing())
|
||||
end;
|
||||
@@ -76,25 +76,10 @@ calculate(Meta, Key, Opts, Old, Temp, Existing, S) ->
|
||||
Final = remove_internals(Set),
|
||||
|
||||
case Final of
|
||||
[] ->
|
||||
{ keydelete(Key, Old),
|
||||
if_quoted(Meta, Temp, fun(Value) -> keydelete(Key, Value) end) };
|
||||
[] -> keydelete(Key, Old);
|
||||
_ ->
|
||||
ensure_no_special_form_conflict(Meta, S#elixir_scope.file, Key, Final),
|
||||
{ [{ 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
|
||||
ensure_no_special_form_conflict(Meta, E#elixir_env.file, Key, Final),
|
||||
[{ Key, Final }|keydelete(Key, Old)]
|
||||
end.
|
||||
|
||||
%% Retrieve functions and macros from modules
|
||||
@@ -113,13 +98,13 @@ get_functions(Module) ->
|
||||
error:undef -> Module:module_info(exports)
|
||||
end.
|
||||
|
||||
get_macros(Meta, Module, S) ->
|
||||
get_macros(Meta, Module, E) ->
|
||||
try
|
||||
Module:'__info__'(macros)
|
||||
catch
|
||||
error:undef ->
|
||||
Tuple = { no_macros, Module },
|
||||
elixir_errors:form_error(Meta, S#elixir_scope.file, ?MODULE, Tuple)
|
||||
elixir_errors:form_error(Meta, E#elixir_env.file, ?MODULE, Tuple)
|
||||
end.
|
||||
|
||||
get_optional_macros(Module) ->
|
||||
@@ -150,14 +135,14 @@ ensure_no_special_form_conflict(_Meta, _File, _Key, []) -> ok.
|
||||
|
||||
format_error({invalid_import,{Receiver, Name, Arity}}) ->
|
||||
io_lib:format("cannot import ~ts.~ts/~B because it doesn't exist",
|
||||
[elixir_errors:inspect(Receiver), Name, Arity]);
|
||||
[elixir_aliases:inspect(Receiver), Name, Arity]);
|
||||
|
||||
format_error({special_form_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]);
|
||||
[elixir_aliases:inspect(Receiver), Name, Arity]);
|
||||
|
||||
format_error({ no_macros, Module }) ->
|
||||
io_lib:format("could not load macros from module ~ts", [elixir_errors:inspect(Module)]).
|
||||
io_lib:format("could not load macros from module ~ts", [elixir_aliases:inspect(Module)]).
|
||||
|
||||
%% LIST HELPERS
|
||||
|
||||
@@ -196,7 +181,6 @@ special_form('^',1) -> true;
|
||||
special_form('=',2) -> true;
|
||||
special_form('__op__',2) -> true;
|
||||
special_form('__op__',3) -> true;
|
||||
special_form('__scope__',2) -> true;
|
||||
special_form('__block__',_) -> true;
|
||||
special_form('->',_) -> true;
|
||||
special_form('<<>>',_) -> true;
|
||||
@@ -210,7 +194,6 @@ special_form('import',2) -> true;
|
||||
special_form('__ENV__',0) -> true;
|
||||
special_form('__CALLER__',0) -> true;
|
||||
special_form('__MODULE__',0) -> true;
|
||||
special_form('__FILE__',0) -> true;
|
||||
special_form('__DIR__',0) -> true;
|
||||
special_form('__aliases__',_) -> true;
|
||||
special_form('quote',1) -> true;
|
||||
@@ -219,10 +202,9 @@ special_form('unquote',1) -> true;
|
||||
special_form('unquote_splicing',1) -> true;
|
||||
special_form('fn',_) -> true;
|
||||
special_form('super',_) -> true;
|
||||
special_form('super?',0) -> true;
|
||||
special_form('bc',_) -> true;
|
||||
special_form('lc',_) -> true;
|
||||
special_form('var!',1) -> true;
|
||||
special_form('var!',2) -> true;
|
||||
special_form('alias!',1) -> true;
|
||||
special_form('case',2) -> true;
|
||||
special_form('try',2) -> true;
|
||||
special_form('receive',1) -> true;
|
||||
special_form(_, _) -> false.
|
||||
|
||||
@@ -138,7 +138,13 @@ unescape_chars(<<>>, _Map, _Octals, _Hex, Acc) -> Acc.
|
||||
|
||||
append_escaped(Rest, Map, List, Octal, Hex, Acc, Base) ->
|
||||
Codepoint = list_to_integer(List, Base),
|
||||
unescape_chars(Rest, Map, Octal, Hex, <<Acc/binary, Codepoint/utf8>>).
|
||||
try <<Acc/binary, Codepoint/utf8>> of
|
||||
Binary -> unescape_chars(Rest, Map, Octal, Hex, Binary)
|
||||
catch
|
||||
error:badarg ->
|
||||
Msg = <<"invalid or reserved unicode codepoint ", (integer_to_binary(Codepoint))/binary>>,
|
||||
error('Elixir.ArgumentError':exception([{message,Msg}]))
|
||||
end.
|
||||
|
||||
% Unescape Helpers
|
||||
|
||||
|
||||
@@ -13,7 +13,6 @@ run(File, Callback) ->
|
||||
case code:is_loaded(?tracker) of
|
||||
{ file, _ } ->
|
||||
Pid = ?tracker:start_link(),
|
||||
|
||||
try
|
||||
Callback(Pid)
|
||||
after
|
||||
@@ -75,6 +74,6 @@ warn_unused_aliases(File, Pid) ->
|
||||
end || { M, L } <- ?tracker:collect_unused_aliases(Pid)].
|
||||
|
||||
format_error({unused_alias, Module}) ->
|
||||
io_lib:format("unused alias ~ts", [elixir_errors:inspect(Module)]);
|
||||
io_lib:format("unused alias ~ts", [elixir_aliases:inspect(Module)]);
|
||||
format_error({unused_import, Module}) ->
|
||||
io_lib:format("unused import ~ts", [elixir_errors:inspect(Module)]).
|
||||
io_lib:format("unused import ~ts", [elixir_aliases:inspect(Module)]).
|
||||
|
||||
@@ -1,199 +0,0 @@
|
||||
%% Handle translation of Elixir literals to Erlang AST.
|
||||
-module(elixir_literal).
|
||||
-export([translate/2]).
|
||||
-import(elixir_translator, [translate_each/2, translate_args/2]).
|
||||
-import(elixir_scope, [umergec/2]).
|
||||
-include("elixir.hrl").
|
||||
|
||||
translate({ '<<>>', Meta, Args }, S) when is_list(Args) ->
|
||||
case S#elixir_scope.context of
|
||||
match ->
|
||||
build_bitstr(fun elixir_translator:translate_each/2, Args, Meta, S);
|
||||
_ ->
|
||||
{ TArgs, { SC, SV } } = build_bitstr(fun elixir_translator:translate_arg/2, Args, Meta, { S, S }),
|
||||
{ TArgs, umergec(SV, SC) }
|
||||
end;
|
||||
|
||||
translate({ '{}', Meta, Args }, S) when is_list(Args) ->
|
||||
{ TArgs, SE } = translate_args(Args, S),
|
||||
{ { tuple, ?line(Meta), TArgs }, SE };
|
||||
|
||||
translate({ Left, Right }, S) ->
|
||||
translate({ '{}', [], [Left, Right]}, S);
|
||||
|
||||
translate([], S) ->
|
||||
{ { nil, 0 }, S };
|
||||
|
||||
translate([_|_] = Args, S) ->
|
||||
[RTail|RArgs] = lists:reverse(Args),
|
||||
|
||||
case RTail of
|
||||
{ '|', _, [Left,Right] } ->
|
||||
RExprs = [Left|RArgs],
|
||||
TailFun = fun(ST) -> translate_each(Right, ST) end;
|
||||
_ ->
|
||||
RExprs = [RTail|RArgs],
|
||||
TailFun = fun(ST) -> { { nil, 0 }, ST } end
|
||||
end,
|
||||
|
||||
{ Exprs, SE } = translate_args(lists:reverse(RExprs), S),
|
||||
{ Tail, ST } = TailFun(SE),
|
||||
{ elixir_utils:list_to_cons(0, Exprs, Tail), ST };
|
||||
|
||||
translate(Tuple, S) when is_tuple(Tuple) ->
|
||||
elixir_errors:compile_error(0, S#elixir_scope.file,
|
||||
"tuples in quoted expressions must have 2 or 3 items, invalid quoted expression: ~ts",
|
||||
['Elixir.Kernel':inspect(Tuple)]);
|
||||
|
||||
translate(Other, S) ->
|
||||
{ elixir_utils:elixir_to_erl(0, Other, S), S }.
|
||||
|
||||
%% Helpers
|
||||
|
||||
% Build a bitstring taking into accounts the following types:
|
||||
%
|
||||
% * If a bitstring or a list is given, we just append its items
|
||||
% * If '::' is given, extract the bitstring information
|
||||
% * All the other types are simply translated and handled with Erlang's default
|
||||
%
|
||||
build_bitstr(Fun, Exprs, Meta, S) ->
|
||||
{ Final, FinalS } = build_bitstr_each(Fun, Exprs, Meta, S, []),
|
||||
{ { bin, ?line(Meta), lists:reverse(Final) }, FinalS }.
|
||||
|
||||
build_bitstr_each(_Fun, [], _Meta, S, Acc) ->
|
||||
{ Acc, S };
|
||||
|
||||
build_bitstr_each(Fun, [{'::',_,[H,V]}|T], Meta, S, Acc) ->
|
||||
%% Variables defined outside the binary can be accounted
|
||||
%% on subparts, however we can't assign new variables.
|
||||
case S of
|
||||
{ ES, _ } -> []; %% translate_arg, no assigns
|
||||
_ -> ES = S#elixir_scope{context=nil} %% translate_each, revert assigns
|
||||
end,
|
||||
|
||||
{ Size, Types } = extract_bit_values(Meta, V, ES),
|
||||
build_bitstr_each(Fun, T, Meta, S, Acc, H, Size, Types);
|
||||
|
||||
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) ->
|
||||
{ Expr, NS } = Fun(H, S),
|
||||
|
||||
AllowString = types_allow_string(Types),
|
||||
AllowSplice = types_allow_splice(Types),
|
||||
AllowAny = (AllowString orelse AllowSplice) andalso (Size == default),
|
||||
|
||||
case AllowAny andalso Expr of
|
||||
{ bin, _, [{ bin_element, 0, { string, 0, String }, default, default }] } when AllowString ->
|
||||
build_bitstr_each(Fun, T, Meta, NS, [{ bin_element, ?line(Meta), { string, 0, String }, Size, Types }|Acc]);
|
||||
{ bin, _, Elements } when AllowSplice ->
|
||||
build_bitstr_each(Fun, T, Meta, NS, lists:reverse(Elements) ++ 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]).
|
||||
|
||||
types_allow_string([End|T]) when End == little; End == big -> types_allow_string(T);
|
||||
types_allow_string([UTF|T]) when UTF == utf8; UTF == utf16; UTF == utf32 -> types_allow_string(T);
|
||||
types_allow_string([]) -> true;
|
||||
types_allow_string(_) -> false.
|
||||
|
||||
types_allow_splice(default) -> true;
|
||||
types_allow_splice([bytes]) -> true;
|
||||
types_allow_splice([binary]) -> true;
|
||||
types_allow_splice([bits]) -> true;
|
||||
types_allow_splice([bitstring]) -> true;
|
||||
types_allow_splice(_) -> false.
|
||||
|
||||
%% Extra bitstring specifiers
|
||||
|
||||
extract_bit_values(Meta, V, S) when is_list(V) ->
|
||||
extract_bit_values(Meta, V, default, [], S);
|
||||
|
||||
extract_bit_values(Meta, V, S) ->
|
||||
extract_bit_values(Meta, [V], S).
|
||||
|
||||
extract_bit_values(Meta, [{ Value, _Meta, Atom }|T] = All, Size, Types, S)
|
||||
when is_atom(Value) andalso (is_atom(Atom) orelse Atom == []) ->
|
||||
case extract_bit_type(Value, Types) of
|
||||
{ error, unknown } ->
|
||||
handle_unknown_specifier(Meta, All, Size, Types, S);
|
||||
NewTypes when is_list(NewTypes) ->
|
||||
extract_bit_values(Meta, T, Size, NewTypes, S)
|
||||
end;
|
||||
|
||||
extract_bit_values(Meta, [{ Value, CallMeta, [_] = Args }|T] = All, Size, Types, S) when is_atom(Value) ->
|
||||
{ TArgs, _ } = elixir_translator:translate_args(Args, S),
|
||||
case extract_bit_type_or_size(Value, TArgs, Size, Types) of
|
||||
{ error, unknown } ->
|
||||
handle_unknown_specifier(Meta, All, Size, Types, S);
|
||||
{ error, Msg } ->
|
||||
elixir_errors:syntax_error(CallMeta, S#elixir_scope.file, Msg);
|
||||
{ NewSize, NewTypes } ->
|
||||
extract_bit_values(Meta, T, NewSize, NewTypes, S)
|
||||
end;
|
||||
|
||||
extract_bit_values(Meta, [Size|T], default, Types, S) when is_integer(Size) andalso Size >= 0 ->
|
||||
extract_bit_values(Meta, T, {integer, Meta, Size}, Types, S);
|
||||
|
||||
extract_bit_values(Meta, [{ '|', _, [Left, Right] }], Size, Types, S) ->
|
||||
{ Expanded, ES } = 'Elixir.Macro':expand_all(Right, elixir_scope:to_ex_env({ ?line(Meta), S }), S),
|
||||
extract_bit_values(Meta, [Left|join_expansion(Expanded,[])], Size, Types, ES);
|
||||
|
||||
extract_bit_values(Meta, [_|_] = All, Size, Types, S) ->
|
||||
handle_unknown_specifier(Meta, All, Size, Types, S);
|
||||
|
||||
extract_bit_values(_Meta, [], Size, [], _S) ->
|
||||
{ Size, default };
|
||||
|
||||
extract_bit_values(_Meta, [], Size, Types, _S) ->
|
||||
{ Size, lists:reverse(Types) }.
|
||||
|
||||
extract_bit_type(Value, Types) when
|
||||
Value == binary; Value == integer; Value == float; Value == bitstring;
|
||||
Value == bytes; Value == bits;
|
||||
Value == utf8; Value == utf16; Value == utf32;
|
||||
Value == signed; Value == unsigned;
|
||||
Value == big; Value == little; Value == native ->
|
||||
[Value|Types];
|
||||
|
||||
extract_bit_type(_Value, _Types) ->
|
||||
{ error, unknown }.
|
||||
|
||||
extract_bit_type_or_size(size, [{ Kind, _, _ } = Arg], default, Types) when Kind == var; Kind == integer ->
|
||||
{ Arg, Types };
|
||||
|
||||
extract_bit_type_or_size(size, _Args, default, _Types) ->
|
||||
{ error, "size in bitstring expects an integer or a variable as argument" };
|
||||
|
||||
extract_bit_type_or_size(size, _Args, _Other, _Types) ->
|
||||
{ error, "duplicated size definition for bitstring" };
|
||||
|
||||
extract_bit_type_or_size(unit, [{ integer, _, Arg }], Size, Types) ->
|
||||
{ Size, [{ unit, Arg }|Types] };
|
||||
|
||||
extract_bit_type_or_size(unit, _Args, _Other, _Types) ->
|
||||
{ error, "unit in bitstring expects an integer as argument" };
|
||||
|
||||
extract_bit_type_or_size(_Value, _Args, _Other, _Types) ->
|
||||
{ error, unknown }.
|
||||
|
||||
handle_unknown_specifier(Meta, [H|T], Size, Types, S) ->
|
||||
case 'Elixir.Macro':expand_all(H, elixir_scope:to_ex_env({ ?line(Meta), S }), S) of
|
||||
{ H, ES } ->
|
||||
elixir_errors:syntax_error(Meta, ES#elixir_scope.file, "unknown bitstring specifier ~ts", ['Elixir.Macro':to_string(H)]);
|
||||
{ E, ES } ->
|
||||
extract_bit_values(Meta, join_expansion(E,T), Size, Types, ES)
|
||||
end.
|
||||
|
||||
join_expansion({ '__block__', _, [Expanded] }, Tail) -> join_expansion(Expanded, Tail);
|
||||
join_expansion(Expanded, Tail) when is_list(Expanded) -> Expanded ++ Tail;
|
||||
join_expansion(Expanded, Tail) -> [Expanded|Tail].
|
||||
@@ -1,19 +1,76 @@
|
||||
%% Module responsible for tracking invocations of module calls.
|
||||
-module(elixir_tracker).
|
||||
-module(elixir_locals).
|
||||
-export([
|
||||
setup/1, cleanup/1,
|
||||
record_local/2, record_local/3,
|
||||
record_import/4, record_remote/4,
|
||||
setup/1, cleanup/1, cache_env/1, get_cached_env/1,
|
||||
record_local/2, record_local/3, record_import/4,
|
||||
record_definition/3, record_defaults/4,
|
||||
ensure_no_function_conflict/4, warn_unused_local/3,
|
||||
format_error/1
|
||||
ensure_no_function_conflict/4, warn_unused_local/3, format_error/1
|
||||
]).
|
||||
-export([macro_for/3, function_for/3]).
|
||||
|
||||
-include("elixir.hrl").
|
||||
-define(attr, '__locals_tracker').
|
||||
-define(tracker, 'Elixir.Module.LocalsTracker').
|
||||
|
||||
-define(attr, '__dispatch_tracker').
|
||||
-define(tracker, 'Elixir.Module.DispatchTracker').
|
||||
macro_for(nil, _Name, _Arity) -> false;
|
||||
macro_for(Module, Name, Arity) ->
|
||||
Tuple = { Name, Arity },
|
||||
try elixir_def:lookup_definition(Module, Tuple) of
|
||||
{ { Tuple, Kind, Line, _, _, _, _ }, [_|_] = Clauses }
|
||||
when Kind == defmacro; Kind == defmacrop ->
|
||||
fun() -> get_function(Line, Module, Clauses) end;
|
||||
_ ->
|
||||
false
|
||||
catch
|
||||
error:badarg -> false
|
||||
end.
|
||||
|
||||
%% RECORD
|
||||
function_for(Module, Name, Arity) ->
|
||||
Tuple = { Name, Arity },
|
||||
case elixir_def:lookup_definition(Module, Tuple) of
|
||||
{ { Tuple, _, Line, _, _, _, _ }, Clauses } when 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(),
|
||||
erlang:raise(error, undef, [{Module,Name,Arity,[]}|T])
|
||||
end.
|
||||
|
||||
get_function(Line, Module, Clauses) ->
|
||||
RewrittenClauses = [rewrite_clause(Clause, Module) || Clause <- Clauses],
|
||||
Fun = { 'fun', Line, {clauses, RewrittenClauses } },
|
||||
{ value, Result, _Binding } = erl_eval:exprs([Fun], []),
|
||||
Result.
|
||||
|
||||
rewrite_clause({ call, Line, { atom, Line, RawName }, Args }, Module) ->
|
||||
Remote = { remote, Line,
|
||||
{ atom, Line, ?MODULE },
|
||||
{ atom, Line, function_for }
|
||||
},
|
||||
|
||||
%% If we have a macro, its arity in the table is
|
||||
%% actually one less than in the function call
|
||||
{ Name, Arity } = case atom_to_list(RawName) of
|
||||
"MACRO-" ++ Rest -> { list_to_atom(Rest), length(Args) - 1 };
|
||||
_ -> { RawName, length(Args) }
|
||||
end,
|
||||
|
||||
FunCall = { call, Line, Remote, [
|
||||
{ atom, Line, Module }, { atom, Line, Name }, { integer, Line, Arity }
|
||||
] },
|
||||
{ call, Line, FunCall, Args };
|
||||
|
||||
rewrite_clause(Tuple, Module) when is_tuple(Tuple) ->
|
||||
list_to_tuple(rewrite_clause(tuple_to_list(Tuple), Module));
|
||||
|
||||
rewrite_clause(List, Module) when is_list(List) ->
|
||||
[rewrite_clause(Item, Module) || Item <- List];
|
||||
|
||||
rewrite_clause(Else, _) -> Else.
|
||||
|
||||
%% TRACKING
|
||||
|
||||
setup(Module) ->
|
||||
case code:is_loaded(?tracker) of
|
||||
@@ -45,14 +102,6 @@ record_import(Tuple, Receiver, Module, Function) ->
|
||||
true
|
||||
end).
|
||||
|
||||
record_remote(_Tuple, Receiver, Module, _Function)
|
||||
when Module == nil; Module == Receiver -> false;
|
||||
record_remote(Tuple, Receiver, Module, Function) ->
|
||||
if_tracker(Module, fun(Pid) ->
|
||||
?tracker:add_remote(Pid, Function, Receiver, Tuple),
|
||||
true
|
||||
end).
|
||||
|
||||
record_definition(Tuple, Kind, Module) ->
|
||||
if_tracker(Module, fun(Pid) ->
|
||||
?tracker:add_definition(Pid, Kind, Tuple),
|
||||
@@ -67,8 +116,6 @@ record_defaults(Tuple, Kind, Module, Defaults) ->
|
||||
true
|
||||
end).
|
||||
|
||||
%% HELPERS
|
||||
|
||||
if_tracker(Module, Callback) ->
|
||||
try ets:lookup_element(Module, ?attr, 2) of
|
||||
Pid -> Callback(Pid)
|
||||
@@ -76,6 +123,24 @@ if_tracker(Module, Callback) ->
|
||||
error:badarg -> false
|
||||
end.
|
||||
|
||||
%% CACHING
|
||||
|
||||
cache_env(#elixir_env{module=Module} = RE) ->
|
||||
E = RE#elixir_env{line=nil,vars=[]},
|
||||
try ets:lookup_element(Module, ?attr, 2) of
|
||||
Pid ->
|
||||
{ Pid, ?tracker:cache_env(Pid, E) }
|
||||
catch
|
||||
error:badarg ->
|
||||
{ Escaped, _ } = elixir_quote:escape(E, false),
|
||||
Escaped
|
||||
end;
|
||||
cache_env(ExEnv) ->
|
||||
cache_env(elixir_env:ex_to_env(ExEnv)).
|
||||
|
||||
get_cached_env({Pid,Ref}) -> ?tracker:get_cached_env(Pid, Ref);
|
||||
get_cached_env(Env) -> Env.
|
||||
|
||||
%% ERROR HANDLING
|
||||
|
||||
ensure_no_function_conflict(Meta, File, Module, AllDefined) ->
|
||||
@@ -101,7 +166,7 @@ warn_unused_local(File, Module, Private) ->
|
||||
|
||||
format_error({function_conflict,{Receivers, Name, Arity}}) ->
|
||||
io_lib:format("imported ~ts.~ts/~B conflicts with local function",
|
||||
[elixir_errors:inspect(hd(Receivers)), Name, Arity]);
|
||||
[elixir_aliases:inspect(hd(Receivers)), Name, Arity]);
|
||||
|
||||
format_error({unused_args,{Name, Arity}}) ->
|
||||
io_lib:format("default arguments in ~ts/~B are never used", [Name, Arity]);
|
||||
@@ -1,324 +0,0 @@
|
||||
%% Those macros behave like they belong to Kernel,
|
||||
%% but do not since they need to be implemented in Erlang.
|
||||
-module(elixir_macros).
|
||||
-export([translate/2]).
|
||||
-import(elixir_translator, [translate_each/2, translate_args/2, translate_apply/7]).
|
||||
-import(elixir_scope, [umergec/2, umergea/2]).
|
||||
-import(elixir_errors, [compile_error/3, 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(opt_in_types(Kind), Kind == atom orelse Kind == integer orelse Kind == float).
|
||||
|
||||
%% Operators
|
||||
|
||||
translate({ '+', _Meta, [Expr] }, S) when is_number(Expr) ->
|
||||
translate_each(Expr, S);
|
||||
|
||||
translate({ '-', _Meta, [Expr] }, S) when is_number(Expr) ->
|
||||
translate_each(-1 * Expr, S);
|
||||
|
||||
translate({ Op, Meta, Exprs }, S) when is_list(Exprs),
|
||||
Op == '<-' orelse Op == '--' ->
|
||||
assert_no_match_or_guard_scope(Meta, Op, S),
|
||||
translate_each({ '__op__', Meta, [Op|Exprs] }, S);
|
||||
|
||||
translate({ Op, Meta, Exprs }, S) when is_list(Exprs),
|
||||
Op == '+' orelse Op == '-' orelse Op == '*' orelse Op == '/' orelse
|
||||
Op == '++' orelse Op == 'not' orelse Op == 'and' orelse Op == 'or' orelse
|
||||
Op == 'xor' orelse Op == '<' orelse Op == '>' orelse Op == '<=' orelse
|
||||
Op == '>=' orelse Op == '==' orelse Op == '!=' orelse Op == '===' orelse
|
||||
Op == '!==' ->
|
||||
translate_each({ '__op__', Meta, [Op|Exprs] }, S);
|
||||
|
||||
translate({ '!', Meta, [{ '!', _, [Expr] }] }, S) ->
|
||||
{ TExpr, SE } = translate_each(Expr, S),
|
||||
elixir_utils:convert_to_boolean(?line(Meta), TExpr, true, S#elixir_scope.context == guard, SE);
|
||||
|
||||
translate({ '!', Meta, [Expr] }, S) ->
|
||||
{ TExpr, SE } = translate_each(Expr, S),
|
||||
elixir_utils:convert_to_boolean(?line(Meta), TExpr, false, S#elixir_scope.context == guard, SE);
|
||||
|
||||
translate({ in, Meta, [Left, Right] }, #elixir_scope{extra_guards=nil} = S) ->
|
||||
translate_in(Meta, Left, Right, S);
|
||||
|
||||
%% @
|
||||
|
||||
translate({'@', Meta, [{ Name, _, [Arg] }]}, S) when Name == typep; Name == type; Name == spec; Name == callback; Name == opaque ->
|
||||
case elixir_compiler:get_opt(internal) of
|
||||
true -> { { nil, ?line(Meta) }, S };
|
||||
false ->
|
||||
Call = { { '.', Meta, ['Elixir.Kernel.Typespec', spec_to_macro(Name)] }, Meta, [Arg] },
|
||||
translate_each(Call, S)
|
||||
end;
|
||||
|
||||
translate({'@', Meta, [{ Name, _, Args }]}, S) ->
|
||||
assert_module_scope(Meta, '@', S),
|
||||
|
||||
case is_reserved_data(Name) andalso elixir_compiler:get_opt(internal) of
|
||||
true ->
|
||||
{ { nil, ?line(Meta) }, S };
|
||||
_ ->
|
||||
case Args of
|
||||
[Arg] ->
|
||||
case S#elixir_scope.function of
|
||||
nil ->
|
||||
translate_each({
|
||||
{ '.', Meta, ['Elixir.Module', put_attribute] },
|
||||
Meta,
|
||||
[ { '__MODULE__', Meta, false }, Name, Arg ]
|
||||
}, S);
|
||||
_ ->
|
||||
syntax_error(Meta, S#elixir_scope.file,
|
||||
"cannot dynamically set attribute @~ts inside a function", [Name])
|
||||
end;
|
||||
_ when is_atom(Args) or (Args == []) ->
|
||||
case S#elixir_scope.function of
|
||||
nil ->
|
||||
translate_each({
|
||||
{ '.', Meta, ['Elixir.Module', get_attribute] },
|
||||
Meta,
|
||||
[ { '__MODULE__', Meta, false }, Name, true ]
|
||||
}, S);
|
||||
_ ->
|
||||
Contents = 'Elixir.Module':get_attribute(S#elixir_scope.module, Name),
|
||||
{ elixir_utils:elixir_to_erl(?line(Meta), Contents, S), S }
|
||||
end;
|
||||
_ ->
|
||||
syntax_error(Meta, S#elixir_scope.file, "expected 0 or 1 argument for @~ts, got: ~p", [Name, length(Args)])
|
||||
end
|
||||
end;
|
||||
|
||||
%% Case
|
||||
|
||||
translate({'case', Meta, [Expr, KV]}, S) when is_list(KV) ->
|
||||
assert_no_match_or_guard_scope(Meta, 'case', S),
|
||||
Clauses = elixir_clauses:get_pairs(Meta, do, KV, S),
|
||||
{ TExpr, NS } = translate_each(Expr, S),
|
||||
|
||||
RClauses = case elixir_utils:returns_boolean(TExpr) of
|
||||
true -> rewrite_case_clauses(Clauses);
|
||||
false -> Clauses
|
||||
end,
|
||||
|
||||
{ TClauses, TS } = elixir_clauses:match(Meta, RClauses, NS),
|
||||
{ { 'case', ?line(Meta), TExpr, TClauses }, TS };
|
||||
|
||||
%% Try
|
||||
|
||||
translate({'try', Meta, [Clauses]}, S) when is_list(Clauses) ->
|
||||
assert_no_match_or_guard_scope(Meta, 'try', S),
|
||||
|
||||
Do = proplists:get_value('do', Clauses, nil),
|
||||
{ TDo, SB } = elixir_translator:translate_each(Do, S#elixir_scope{noname=true}),
|
||||
|
||||
Catch = [Tuple || { X, _ } = Tuple <- Clauses, X == 'rescue' orelse X == 'catch'],
|
||||
{ TCatch, SC } = elixir_try:clauses(Meta, Catch, umergea(S, SB)),
|
||||
|
||||
After = proplists:get_value('after', Clauses, nil),
|
||||
{ TAfter, SA } = elixir_translator:translate_each(After, umergea(S, SC)),
|
||||
|
||||
Else = elixir_clauses:get_pairs(Meta, else, Clauses, S),
|
||||
{ TElse, SE } = elixir_clauses:match(Meta, Else, umergea(S, SA)),
|
||||
|
||||
SF = (umergec(S, SE))#elixir_scope{noname=S#elixir_scope.noname},
|
||||
{ { 'try', ?line(Meta), pack(TDo), TElse, TCatch, pack(TAfter) }, SF };
|
||||
|
||||
%% Receive
|
||||
|
||||
translate({'receive', Meta, [KV] }, S) when is_list(KV) ->
|
||||
assert_no_match_or_guard_scope(Meta, 'receive', S),
|
||||
Do = elixir_clauses:get_pairs(Meta, do, KV, S, true),
|
||||
|
||||
case lists:keyfind('after', 1, KV) of
|
||||
false ->
|
||||
{ TClauses, SC } = elixir_clauses:match(Meta, Do, S),
|
||||
{ { 'receive', ?line(Meta), TClauses }, SC };
|
||||
_ ->
|
||||
After = elixir_clauses:get_pairs(Meta, 'after', KV, S),
|
||||
{ TClauses, SC } = elixir_clauses:match(Meta, Do ++ After, S),
|
||||
{ FClauses, TAfter } = elixir_utils:split_last(TClauses),
|
||||
{ _, _, [FExpr], _, FAfter } = TAfter,
|
||||
{ { 'receive', ?line(Meta), FClauses, FExpr, FAfter }, SC }
|
||||
end;
|
||||
|
||||
%% Definitions
|
||||
|
||||
translate({defmodule, Meta, [Ref, KV]}, S) when is_list(KV) ->
|
||||
{ TRef, _ } = translate_each(Ref, S),
|
||||
|
||||
Block = case lists:keyfind(do, 1, KV) of
|
||||
{ do, DoValue } -> DoValue;
|
||||
false -> syntax_error(Meta, S#elixir_scope.file, "missing do keyword in defmodule")
|
||||
end,
|
||||
|
||||
{ FRef, FS } = case TRef of
|
||||
{ atom, _, Module } ->
|
||||
FullModule = expand_module(Ref, Module, S),
|
||||
|
||||
RS = case elixir_aliases:nesting_alias(S#elixir_scope.module, FullModule) of
|
||||
{ New, Old } -> elixir_aliases:store(Meta, New, Old, [{warn,false}], S);
|
||||
false -> S
|
||||
end,
|
||||
|
||||
{
|
||||
{ atom, Meta, FullModule },
|
||||
RS#elixir_scope{context_modules=[FullModule|S#elixir_scope.context_modules]}
|
||||
};
|
||||
_ ->
|
||||
{ TRef, S }
|
||||
end,
|
||||
|
||||
MS = FS#elixir_scope{local=nil},
|
||||
{ elixir_module:translate(Meta, FRef, Block, MS), FS };
|
||||
|
||||
translate({Kind, Meta, [Call]}, S) when ?defs(Kind) ->
|
||||
translate({Kind, Meta, [Call, nil]}, S);
|
||||
|
||||
translate({Kind, Meta, [Call, Expr]}, S) when ?defs(Kind) ->
|
||||
assert_module_scope(Meta, Kind, S),
|
||||
assert_no_function_scope(Meta, Kind, S),
|
||||
{ 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 };
|
||||
|
||||
%% Apply - Optimize apply by checking what doesn't need to be dispatched dynamically
|
||||
|
||||
translate({ apply, Meta, [Left, Right, Args] }, S) when is_list(Args) ->
|
||||
{ TLeft, SL } = translate_each(Left, S),
|
||||
{ TRight, SR } = translate_each(Right, umergec(S, SL)),
|
||||
translate_apply(Meta, TLeft, TRight, Args, S, SL, SR);
|
||||
|
||||
translate({ apply, Meta, Args }, S) ->
|
||||
{ TArgs, NS } = translate_args(Args, S),
|
||||
{ ?wrap_call(?line(Meta), erlang, apply, TArgs), NS };
|
||||
|
||||
translate({ Name, Meta, Args }, S) ->
|
||||
syntax_error(Meta, S#elixir_scope.file,
|
||||
"invalid arguments for macro ~ts/~B", [Name, length(Args)]).
|
||||
|
||||
%% Helpers
|
||||
|
||||
translate_in(Meta, Left, Right, S) ->
|
||||
Line = ?line(Meta),
|
||||
|
||||
{ TLeft, SL } = translate_each(Left, S),
|
||||
{ TRight, SR } = translate_each(Right, SL),
|
||||
|
||||
Cache = (S#elixir_scope.context == nil),
|
||||
|
||||
{ Var, SV } = case Cache of
|
||||
true -> elixir_scope:build_erl_var(Line, SR);
|
||||
false -> { TLeft, SR }
|
||||
end,
|
||||
|
||||
{ TCache, TExpr } = case TRight of
|
||||
{ nil, _ } ->
|
||||
Expr = { atom, Line, false },
|
||||
{ Cache, Expr };
|
||||
{ cons, _, _, _ } ->
|
||||
[H|T] = elixir_utils:cons_to_list(TRight),
|
||||
Expr = lists:foldr(fun(X, Acc) ->
|
||||
{ op, Line, 'orelse', { op, Line, '=:=', Var, X }, Acc }
|
||||
end, { op, Line, '=:=', Var, H }, T),
|
||||
{ Cache, Expr };
|
||||
{ string, _, [H|T] } ->
|
||||
Expr = lists:foldl(fun(X, Acc) ->
|
||||
{ op, Line, 'orelse', { op, Line, '=:=', Var, { integer, Line, X } }, Acc }
|
||||
end, { op, Line, '=:=', Var, { integer, Line, H } }, T),
|
||||
{ Cache, Expr };
|
||||
{ tuple, _, [{ atom, _, 'Elixir.Range' }, Start, End] } ->
|
||||
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 ?opt_in_types(K1), ?opt_in_types(K2) ->
|
||||
decreasing_compare(Line, Var, Start, End);
|
||||
_ ->
|
||||
{ op, Line, 'orelse',
|
||||
{ op, Line, 'andalso',
|
||||
{ op, Line, '=<', Start, End},
|
||||
increasing_compare(Line, Var, Start, End) },
|
||||
{ op, Line, 'andalso',
|
||||
{ op, Line, '<', End, Start},
|
||||
decreasing_compare(Line, Var, Start, End) } }
|
||||
end,
|
||||
{ Cache, Expr };
|
||||
_ ->
|
||||
case Cache of
|
||||
true ->
|
||||
{ false, ?wrap_call(Line, 'Elixir.Enum', 'member?', [TRight, TLeft]) };
|
||||
false ->
|
||||
compile_error(Meta, S#elixir_scope.file, "invalid args for operator in, it expects a "
|
||||
"compile time list or range on the right side when used in guard expressions")
|
||||
end
|
||||
end,
|
||||
|
||||
case TCache of
|
||||
true -> { { block, Line, [ { match, Line, Var, TLeft }, TExpr ] }, SV };
|
||||
false -> { TExpr, SV }
|
||||
end.
|
||||
|
||||
increasing_compare(Line, Var, Start, End) ->
|
||||
{ op, Line, 'andalso',
|
||||
{ op, Line, '>=', Var, Start },
|
||||
{ op, Line, '=<', Var, End } }.
|
||||
|
||||
decreasing_compare(Line, Var, Start, End) ->
|
||||
{ op, Line, 'andalso',
|
||||
{ op, Line, '=<', Var, Start },
|
||||
{ op, Line, '>=', Var, End } }.
|
||||
|
||||
%% TODO: Once we have elixir_exp, we can move this
|
||||
%% clause to Elixir code and out of case.
|
||||
rewrite_case_clauses([
|
||||
{do,Meta1,[{'when',_,[{V,M,C},{in,_,[{V,M,C},[false,nil]]}]}],False},
|
||||
{do,Meta2,[{'_',_,UC}],True}] = Clauses)
|
||||
when is_atom(V), is_list(M), is_atom(C), is_atom(UC) ->
|
||||
case lists:keyfind('cond', 1, M) of
|
||||
{ 'cond', true } ->
|
||||
[{do,Meta1,[false],False},{do,Meta2,[true],True}];
|
||||
_ ->
|
||||
Clauses
|
||||
end;
|
||||
|
||||
rewrite_case_clauses(Clauses) ->
|
||||
Clauses.
|
||||
|
||||
%% defmodule :foo
|
||||
expand_module(Raw, _Module, _S) when is_atom(Raw) ->
|
||||
Raw;
|
||||
|
||||
%% 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,
|
||||
S#elixir_scope.lexical_tracker) of
|
||||
Atom when is_atom(Atom) ->
|
||||
Module;
|
||||
Aliases when is_list(Aliases) ->
|
||||
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]).
|
||||
|
||||
is_reserved_data(moduledoc) -> true;
|
||||
is_reserved_data(typedoc) -> true;
|
||||
is_reserved_data(doc) -> true;
|
||||
is_reserved_data(_) -> false.
|
||||
|
||||
spec_to_macro(type) -> deftype;
|
||||
spec_to_macro(typep) -> deftypep;
|
||||
spec_to_macro(opaque) -> defopaque;
|
||||
spec_to_macro(spec) -> defspec;
|
||||
spec_to_macro(callback) -> defcallback.
|
||||
|
||||
% Pack a list of expressions from a block.
|
||||
pack({ 'block', _, Exprs }) -> Exprs;
|
||||
pack(Expr) -> [Expr].
|
||||
@@ -1,6 +1,6 @@
|
||||
-module(elixir_module).
|
||||
-export([translate/4, compile/5, data_table/1, docs_table/1,
|
||||
eval_quoted/4, format_error/1, eval_callbacks/5]).
|
||||
-export([compile/4, data_table/1, docs_table/1,
|
||||
format_error/1, eval_callbacks/5]).
|
||||
-include("elixir.hrl").
|
||||
|
||||
-define(acc_attr, '__acc_attributes').
|
||||
@@ -9,25 +9,6 @@
|
||||
-define(persisted_attr, '__persisted_attributes').
|
||||
-define(overridable_attr, '__overridable').
|
||||
|
||||
eval_quoted(Module, Quoted, RawBinding, Opts) ->
|
||||
Binding = orddict:store('_@MODULE', Module, RawBinding),
|
||||
Scope = scope_for_eval(Module, Opts),
|
||||
|
||||
elixir_def:reset_last(Module),
|
||||
|
||||
case lists:keyfind(line, 1, Opts) of
|
||||
{ line, Line } -> Line;
|
||||
false -> Line = 1
|
||||
end,
|
||||
|
||||
{ Value, FinalBinding, _Scope } = elixir:eval_quoted([Quoted], Binding, Line, Scope),
|
||||
{ Value, FinalBinding }.
|
||||
|
||||
scope_for_eval(Module, #elixir_scope{} = S) ->
|
||||
S#elixir_scope{module=Module};
|
||||
scope_for_eval(Module, Opts) ->
|
||||
scope_for_eval(Module, elixir:scope_for_eval(Opts)).
|
||||
|
||||
%% TABLE METHODS
|
||||
|
||||
data_table(Module) ->
|
||||
@@ -36,82 +17,73 @@ data_table(Module) ->
|
||||
docs_table(Module) ->
|
||||
ets:lookup_element(Module, ?docs_attr, 2).
|
||||
|
||||
%% TRANSFORMATION FUNCTIONS
|
||||
%% Compilation hook
|
||||
|
||||
%% Transformation of args and scope into a compiled erlang call.
|
||||
%% The abstract form for extra arguments may be given and they
|
||||
%% will be passed to the invoked function.
|
||||
|
||||
translate(Meta, Ref, Block, S) ->
|
||||
Line = ?line(Meta),
|
||||
LexS = case S#elixir_scope.function of
|
||||
nil -> S;
|
||||
_ -> S#elixir_scope{lexical_tracker=nil}
|
||||
compile(Module, Block, Vars, #elixir_env{line=Line} = Env) when is_atom(Module) ->
|
||||
%% In case we are generating a module from inside a function,
|
||||
%% we get rid of the lexical tracker information as, at this
|
||||
%% point, the lexical tracker process is long gone.
|
||||
LexEnv = case Env#elixir_env.function of
|
||||
nil -> Env#elixir_env{module=Module, local=nil};
|
||||
_ -> Env#elixir_env{lexical_tracker=nil, function=nil, module=Module, local=nil}
|
||||
end,
|
||||
|
||||
MetaBlock = elixir_utils:elixir_to_erl(Line, Block, LexS),
|
||||
{ MetaS, Vars } = elixir_scope:serialize_with_vars(Line, LexS),
|
||||
case LexEnv#elixir_env.lexical_tracker of
|
||||
nil ->
|
||||
elixir_lexical:run(LexEnv#elixir_env.file, fun(Pid) ->
|
||||
do_compile(Line, Module, Block, Vars, LexEnv#elixir_env{lexical_tracker=Pid})
|
||||
end);
|
||||
_ ->
|
||||
do_compile(Line, Module, Block, Vars, LexEnv)
|
||||
end;
|
||||
|
||||
Args = [{integer, Line, Line}, Ref, MetaBlock, Vars, MetaS],
|
||||
?wrap_call(Line, ?MODULE, compile, Args).
|
||||
compile(Module, _Block, _Vars, #elixir_env{line=Line,file=File}) ->
|
||||
elixir_errors:form_error(Line, File, ?MODULE, { invalid_module, Module });
|
||||
|
||||
%% The compilation hook.
|
||||
compile(Module, Block, Vars, ExEnv) ->
|
||||
compile(Module, Block, Vars, elixir_env:ex_to_env(ExEnv)).
|
||||
|
||||
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 = elixir_utils:characters_to_list(File),
|
||||
|
||||
check_module_availability(Line, File, Module, C),
|
||||
build(Line, File, Module, S#elixir_scope.lexical_tracker),
|
||||
do_compile(Line, Module, Block, Vars, E) ->
|
||||
File = E#elixir_env.file,
|
||||
check_module_availability(Line, File, Module),
|
||||
build(Line, File, Module, E#elixir_env.lexical_tracker),
|
||||
|
||||
try
|
||||
Result = eval_form(Line, Module, Block, Vars, S),
|
||||
{ Base, Export, Private, Def, Defmacro, Functions } = elixir_def:unwrap_stored_definitions(FileList, Module),
|
||||
{ Result, NE } = eval_form(Line, Module, Block, Vars, E),
|
||||
{ Base, Export, Private, Def, Defmacro, Functions } = elixir_def:unwrap_definitions(Module),
|
||||
|
||||
{ All, Forms0 } = functions_form(Line, File, Module, Base, Export, Def, Defmacro, Functions, C),
|
||||
Forms1 = specs_form(Module, Private, Defmacro, Forms0, C),
|
||||
{ All, Forms0 } = functions_form(Line, File, Module, Base, Export, Def, Defmacro, Functions),
|
||||
Forms1 = specs_form(Module, Private, Defmacro, Forms0),
|
||||
Forms2 = attributes_form(Line, File, Module, Forms1),
|
||||
Forms3 = typedocs_form(Module, Forms2),
|
||||
|
||||
case ets:lookup(data_table(Module), 'on_load') of
|
||||
[] -> ok;
|
||||
[{on_load,OnLoad}] ->
|
||||
[elixir_tracker:record_local(Tuple, Module) || Tuple <- OnLoad]
|
||||
[elixir_locals:record_local(Tuple, Module) || Tuple <- OnLoad]
|
||||
end,
|
||||
|
||||
AllFunctions = Def ++ [T || { T, defp, _, _, _ } <- Private],
|
||||
elixir_tracker:ensure_no_function_conflict(Line, File, Module, AllFunctions),
|
||||
elixir_tracker:warn_unused_local(File, Module, Private),
|
||||
elixir_locals:ensure_no_function_conflict(Line, File, Module, AllFunctions),
|
||||
elixir_locals:warn_unused_local(File, Module, Private),
|
||||
warn_invalid_clauses(Line, File, Module, All),
|
||||
warn_unused_docs(Line, File, Module),
|
||||
|
||||
Location = { elixir_utils:relative_to_cwd(elixir_utils:characters_to_list(File)), Line },
|
||||
|
||||
Final = [
|
||||
{ attribute, Line, file, { FileList, Line } },
|
||||
{ attribute, Line, file, Location },
|
||||
{ attribute, Line, module, Module } | Forms3
|
||||
],
|
||||
|
||||
Binary = load_form(Line, Final, compile_opts(Module), S),
|
||||
Binary = load_form(Line, Final, compile_opts(Module), NE),
|
||||
{ module, Module, Binary, Result }
|
||||
after
|
||||
elixir_tracker:cleanup(Module),
|
||||
elixir_locals:cleanup(Module),
|
||||
elixir_def:cleanup(Module),
|
||||
ets:delete(docs_table(Module)),
|
||||
ets:delete(data_table(Module))
|
||||
end;
|
||||
|
||||
compile(Line, Other, _Block, _Vars, #elixir_scope{file=File}) ->
|
||||
elixir_errors:form_error(Line, File, ?MODULE, { invalid_module, Other });
|
||||
|
||||
compile(Line, Module, Block, Vars, RawS) ->
|
||||
Dict = [{ { Name, Kind }, Value } || { Name, Kind, Value, _ } <- Vars],
|
||||
S = elixir_scope:deserialize_with_vars(RawS, Dict),
|
||||
compile(Line, Module, Block, Vars, S).
|
||||
end.
|
||||
|
||||
%% Hook that builds both attribute and functions and set up common hooks.
|
||||
|
||||
@@ -142,28 +114,26 @@ build(Line, File, Module, Lexical) ->
|
||||
|
||||
%% Setup other modules
|
||||
elixir_def:setup(Module),
|
||||
elixir_tracker:setup(Module).
|
||||
elixir_locals:setup(Module).
|
||||
|
||||
%% Receives the module representation and evaluates it.
|
||||
|
||||
eval_form(Line, Module, Block, Vars, RawS) ->
|
||||
S = scope_for_eval(Module, RawS),
|
||||
{ Value, NewS } = elixir_compiler:eval_forms([Block], Line, Vars, S),
|
||||
eval_form(Line, Module, Block, Vars, E) ->
|
||||
{ Value, EE } = elixir_compiler:eval_forms(Block, Vars, E),
|
||||
elixir_def_overridable:store_pending(Module),
|
||||
Env = elixir_scope:to_ex_env({ Line, S }),
|
||||
eval_callbacks(Line, Module, before_compile, [Env], NewS),
|
||||
EC = eval_callbacks(Line, Module, before_compile, [elixir_env:env_to_ex({ Line, EE })], EE),
|
||||
elixir_def_overridable:store_pending(Module),
|
||||
Value.
|
||||
{ Value, EC }.
|
||||
|
||||
%% Return the form with exports and function declarations.
|
||||
|
||||
functions_form(Line, File, Module, BaseAll, BaseExport, Def, Defmacro, RawFunctions, C) ->
|
||||
BaseFunctions = case elixir_compiler:get_opt(internal, C) of
|
||||
functions_form(Line, File, Module, BaseAll, BaseExport, Def, Defmacro, RawFunctions) ->
|
||||
BaseFunctions = case elixir_compiler:get_opt(internal) of
|
||||
true -> RawFunctions;
|
||||
false -> record_rewrite_functions(Module, RawFunctions)
|
||||
end,
|
||||
|
||||
Info = add_info_function(Line, File, Module, BaseExport, Def, Defmacro, C),
|
||||
Info = add_info_function(Line, File, Module, BaseExport, Def, Defmacro),
|
||||
|
||||
All = [{ '__info__', 1 }|BaseAll],
|
||||
Export = [{ '__info__', 1 }|BaseExport],
|
||||
@@ -220,11 +190,10 @@ typedocs_form(Module, Current) ->
|
||||
|
||||
%% Specs
|
||||
|
||||
specs_form(Module, Private, Defmacro, Forms, C) ->
|
||||
specs_form(Module, Private, Defmacro, Forms) ->
|
||||
Defmacrop = [Tuple || { Tuple, defmacrop, _, _, _ } <- Private],
|
||||
case elixir_compiler:get_opt(internal, C) of
|
||||
true -> Forms;
|
||||
_ ->
|
||||
case code:ensure_loaded('Elixir.Kernel.Typespec') of
|
||||
{ module, 'Elixir.Kernel.Typespec' } ->
|
||||
Callbacks = 'Elixir.Module':get_attribute(Module, callback),
|
||||
Specs = [translate_spec(Spec, Defmacro, Defmacrop) ||
|
||||
Spec <- 'Elixir.Module':get_attribute(Module, spec)],
|
||||
@@ -233,7 +202,9 @@ specs_form(Module, Private, Defmacro, Forms, C) ->
|
||||
'Elixir.Module':delete_attribute(Module, callback),
|
||||
|
||||
Temp = specs_attributes(spec, Forms, Specs),
|
||||
specs_attributes(callback, Temp, Callbacks)
|
||||
specs_attributes(callback, Temp, Callbacks);
|
||||
{ error, _ } ->
|
||||
Forms
|
||||
end.
|
||||
|
||||
specs_attributes(Type, Forms, Specs) ->
|
||||
@@ -273,11 +244,10 @@ compile_opts(Module) ->
|
||||
[] -> []
|
||||
end.
|
||||
|
||||
load_form(Line, Forms, Opts, #elixir_scope{file=File} = S) ->
|
||||
load_form(Line, Forms, Opts, #elixir_env{file=File} = E) ->
|
||||
elixir_compiler:module(Forms, File, Opts, fun(Module, Binary) ->
|
||||
EvalS = scope_for_eval(Module, S),
|
||||
Env = elixir_scope:to_ex_env({ Line, EvalS }),
|
||||
eval_callbacks(Line, Module, after_compile, [Env, Binary], EvalS),
|
||||
Env = elixir_env:env_to_ex({ Line, E }),
|
||||
eval_callbacks(Line, Module, after_compile, [Env, Binary], E),
|
||||
|
||||
case get(elixir_compiled) of
|
||||
Current when is_list(Current) ->
|
||||
@@ -297,8 +267,8 @@ load_form(Line, Forms, Opts, #elixir_scope{file=File} = S) ->
|
||||
Binary
|
||||
end).
|
||||
|
||||
check_module_availability(Line, File, Module, Compiler) ->
|
||||
case elixir_compiler:get_opt(ignore_module_conflict, Compiler) of
|
||||
check_module_availability(Line, File, Module) ->
|
||||
case elixir_compiler:get_opt(ignore_module_conflict) of
|
||||
false ->
|
||||
case code:ensure_loaded(Module) of
|
||||
{ module, _ } ->
|
||||
@@ -310,7 +280,6 @@ check_module_availability(Line, File, Module, Compiler) ->
|
||||
[]
|
||||
end.
|
||||
|
||||
warn_invalid_clauses(_Line, _File, 'Elixir.Kernel', _All) -> ok;
|
||||
warn_invalid_clauses(_Line, _File, 'Elixir.Kernel.SpecialForms', _All) -> ok;
|
||||
warn_invalid_clauses(_Line, File, Module, All) ->
|
||||
ets:foldl(fun
|
||||
@@ -337,16 +306,16 @@ warn_unused_docs(Line, File, Module) ->
|
||||
|
||||
% EXTRA FUNCTIONS
|
||||
|
||||
add_info_function(Line, File, Module, Export, Def, Defmacro, C) ->
|
||||
add_info_function(Line, File, Module, Export, Def, Defmacro) ->
|
||||
Pair = { '__info__', 1 },
|
||||
case lists:member(Pair, Export) of
|
||||
true ->
|
||||
elixir_errors:form_error(Line, File, ?MODULE, {internal_function_overridden, Pair});
|
||||
false ->
|
||||
Docs = elixir_compiler:get_opt(docs, C),
|
||||
Docs = elixir_compiler:get_opt(docs),
|
||||
{ function, 0, '__info__', 1, [
|
||||
functions_clause(Def),
|
||||
macros_clause(Module, Def, Defmacro),
|
||||
macros_clause(Defmacro),
|
||||
docs_clause(Module, Docs),
|
||||
moduledoc_clause(Line, Module, Docs),
|
||||
module_clause(Module),
|
||||
@@ -357,14 +326,8 @@ add_info_function(Line, File, Module, Export, Def, Defmacro, C) ->
|
||||
functions_clause(Def) ->
|
||||
{ clause, 0, [{ atom, 0, functions }], [], [elixir_utils:elixir_to_erl(Def)] }.
|
||||
|
||||
macros_clause(Module, Def, Defmacro) ->
|
||||
All = handle_builtin_macros(Module, Def, Defmacro),
|
||||
{ clause, 0, [{ atom, 0, macros }], [], [elixir_utils:elixir_to_erl(All)] }.
|
||||
|
||||
handle_builtin_macros('Elixir.Kernel', Def, Defmacro) ->
|
||||
ordsets:subtract(ordsets:union(Defmacro,
|
||||
elixir_dispatch:in_erlang_macros()), Def);
|
||||
handle_builtin_macros(_, _Def, Defmacro) -> Defmacro.
|
||||
macros_clause(Defmacro) ->
|
||||
{ clause, 0, [{ atom, 0, macros }], [], [elixir_utils:elixir_to_erl(Defmacro)] }.
|
||||
|
||||
module_clause(Module) ->
|
||||
{ clause, 0, [{ atom, 0, module }], [], [{ atom, 0, Module }] }.
|
||||
@@ -392,41 +355,41 @@ else_clause() ->
|
||||
|
||||
% HELPERS
|
||||
|
||||
eval_callbacks(Line, Module, Name, Args, RawS) ->
|
||||
{ Binding, S } = elixir_scope:load_binding([], RawS, Module),
|
||||
eval_callbacks(Line, Module, Name, Args, E) ->
|
||||
Callbacks = lists:reverse(ets:lookup_element(data_table(Module), Name, 2)),
|
||||
Meta = [{line,Line},{require,false}],
|
||||
|
||||
lists:foreach(fun({M,F}) ->
|
||||
{ Tree, _ } = elixir_dispatch:dispatch_require(Meta, M, F, Args, S, fun() ->
|
||||
lists:foldl(fun({M,F}, Acc) ->
|
||||
{ Expr, ET } = elixir_dispatch:dispatch_require(Meta, M, F, Args, Acc, fun(_) ->
|
||||
apply(M, F, Args),
|
||||
{ { atom, 0, nil }, S }
|
||||
{ nil, Acc }
|
||||
end),
|
||||
|
||||
case Tree of
|
||||
{ atom, _, Atom } ->
|
||||
Atom;
|
||||
_ ->
|
||||
if
|
||||
is_atom(Expr) ->
|
||||
ET;
|
||||
true ->
|
||||
try
|
||||
erl_eval:exprs([Tree], Binding)
|
||||
{ _Value, _Binding, EE, _S } = elixir:eval_forms(Expr, [], ET),
|
||||
EE
|
||||
catch
|
||||
Kind:Reason ->
|
||||
Info = { M, F, length(Args), [{ file, elixir_utils:characters_to_list(S#elixir_scope.file) }, { line, Line }] },
|
||||
Info = { M, F, length(Args), location(Line, E) },
|
||||
erlang:raise(Kind, Reason, prune_stacktrace(Info, erlang:get_stacktrace()))
|
||||
end
|
||||
end
|
||||
end, Callbacks).
|
||||
end, E, Callbacks).
|
||||
|
||||
%% We've reached the elixir_module or erl_eval internals, skip it with the rest
|
||||
prune_stacktrace(Info, [{ erl_eval, _, _, _ }|_]) ->
|
||||
location(Line, E) ->
|
||||
[{ file, elixir_utils:characters_to_list(E#elixir_env.file) }, { line, Line }].
|
||||
|
||||
%% We've reached the elixir_module or eval internals, skip it with the rest
|
||||
prune_stacktrace(Info, [{ elixir, eval_forms, _, _ }|_]) ->
|
||||
[Info];
|
||||
|
||||
prune_stacktrace(Info, [{ elixir_module, _, _, _ }|_]) ->
|
||||
[Info];
|
||||
|
||||
prune_stacktrace(Info, [H|T]) ->
|
||||
[H|prune_stacktrace(Info, T)];
|
||||
|
||||
prune_stacktrace(Info, []) ->
|
||||
[Info].
|
||||
|
||||
@@ -434,22 +397,16 @@ prune_stacktrace(Info, []) ->
|
||||
|
||||
format_error({ invalid_clause, { Name, Arity } }) ->
|
||||
io_lib:format("empty clause provided for nonexistent function or macro ~ts/~B", [Name, Arity]);
|
||||
|
||||
format_error({ unused_doc, typedoc }) ->
|
||||
"@typedoc provided but no type follows it";
|
||||
|
||||
format_error({ unused_doc, doc }) ->
|
||||
"@doc provided but no definition follows it";
|
||||
|
||||
format_error({ internal_function_overridden, { Name, Arity } }) ->
|
||||
io_lib:format("function ~ts/~B is internal and should not be overridden", [Name, Arity]);
|
||||
|
||||
format_error({ invalid_module, Module}) ->
|
||||
io_lib:format("invalid module name: ~p", [Module]);
|
||||
|
||||
format_error({ module_defined, Module }) ->
|
||||
io_lib:format("redefining module ~ts", [elixir_errors:inspect(Module)]);
|
||||
|
||||
io_lib:format("redefining module ~ts", [elixir_aliases:inspect(Module)]);
|
||||
format_error({ module_in_definition, Module }) ->
|
||||
io_lib:format("cannot define module ~ts because it is currently being defined",
|
||||
[elixir_errors:inspect(Module)]).
|
||||
[elixir_aliases:inspect(Module)]).
|
||||
|
||||
@@ -3,10 +3,9 @@ Nonterminals
|
||||
expr container_expr block_expr no_parens_expr identifier_expr max_expr
|
||||
bracket_expr bracket_at_expr base_expr matched_expr unmatched_expr
|
||||
op_expr matched_op_expr no_parens_op_expr
|
||||
comp_op_eol at_op_eol unary_op_eol and_op_eol or_op_eol tail_op_eol
|
||||
comp_op_eol at_op_eol unary_op_eol and_op_eol or_op_eol
|
||||
add_op_eol mult_op_eol exp_op_eol two_op_eol type_op_eol stab_op_eol
|
||||
arrow_op_eol range_op_eol than_op_eol default_op_eol match_op_eol
|
||||
when_op_eol in_op_eol inc_op_eol
|
||||
arrow_op_eol match_op_eol when_op_eol in_op_eol in_match_op_eol
|
||||
open_paren close_paren empty_paren
|
||||
open_bracket close_bracket
|
||||
open_curly close_curly
|
||||
@@ -30,9 +29,8 @@ Terminals
|
||||
fn 'end' aliases
|
||||
number signed_number atom atom_string bin_string list_string sigil
|
||||
dot_call_op op_identifier
|
||||
comp_op at_op unary_op and_op or_op arrow_op match_op
|
||||
range_op in_op inc_op when_op than_op default_op tail_op
|
||||
dual_op add_op mult_op exp_op two_op type_op stab_op
|
||||
comp_op at_op unary_op and_op or_op arrow_op match_op in_op in_match_op
|
||||
dual_op add_op mult_op exp_op two_op type_op stab_op when_op
|
||||
'true' 'false' 'nil' 'do' eol ',' '.' '&'
|
||||
'(' ')' '[' ']' '{' '}' '<<' '>>'
|
||||
.
|
||||
@@ -46,22 +44,18 @@ Expect 2.
|
||||
Left 5 do.
|
||||
Right 10 stab_op_eol. %% ->
|
||||
Left 20 ','.
|
||||
Right 30 type_op_eol. %% ::
|
||||
Right 40 when_op_eol. %% when
|
||||
Left 50 inc_op_eol. %% inlist, inbits
|
||||
Right 60 default_op_eol. %% //
|
||||
Left 70 tail_op_eol. %% |
|
||||
Right 40 type_op_eol. %% ::
|
||||
Right 50 in_match_op_eol. %% inlist, inbits, //, | (allowed in matches, lower precedence than =)
|
||||
Right 70 when_op_eol. %% when
|
||||
Right 80 match_op_eol. %% =
|
||||
Left 130 or_op_eol. %% ||, |||, or, xor
|
||||
Left 140 and_op_eol. %% &&, &&&, and
|
||||
Left 150 comp_op_eol. %% <, >, <=, >=, ==, !=, =~, ===, !==
|
||||
Right 160 arrow_op_eol. %% < (op), (op) > (e.g <-, |>, <<<, >>>)
|
||||
Left 170 in_op_eol. %% in
|
||||
Left 200 range_op_eol. %% ..
|
||||
Right 200 two_op_eol. %% ++, --, **, .., <>
|
||||
Left 210 add_op_eol. %% + (op), - (op)
|
||||
Left 220 mult_op_eol. %% * (op), / (op)
|
||||
Right 230 than_op_eol. %% < (op) > (e.g <>)
|
||||
Right 240 two_op_eol. %% ++, --, **
|
||||
Left 250 exp_op_eol. %% ^ (op) (e.g ^^^)
|
||||
Nonassoc 300 unary_op_eol. %% +, -, !, ^, not, &, ~~~
|
||||
Left 310 dot_call_op.
|
||||
@@ -71,12 +65,12 @@ Nonassoc 330 var.
|
||||
|
||||
%%% MAIN FLOW OF EXPRESSIONS
|
||||
|
||||
grammar -> eol : [nil].
|
||||
grammar -> expr_list : lists:reverse('$1').
|
||||
grammar -> eol expr_list : lists:reverse('$2').
|
||||
grammar -> expr_list eol : lists:reverse('$1').
|
||||
grammar -> eol expr_list eol : lists:reverse('$2').
|
||||
grammar -> '$empty' : [nil].
|
||||
grammar -> eol : nil.
|
||||
grammar -> expr_list : to_block('$1').
|
||||
grammar -> eol expr_list : to_block('$2').
|
||||
grammar -> expr_list eol : to_block('$1').
|
||||
grammar -> eol expr_list eol : to_block('$2').
|
||||
grammar -> '$empty' : nil.
|
||||
|
||||
% Note expressions are on reverse order
|
||||
expr_list -> expr : ['$1'].
|
||||
@@ -87,6 +81,28 @@ expr -> matched_expr : '$1'.
|
||||
expr -> no_parens_expr : '$1'.
|
||||
expr -> unmatched_expr : '$1'.
|
||||
|
||||
%% In Elixir we have three main call syntaxes: with parentheses,
|
||||
%% without parentheses and with do blocks. They are represented
|
||||
%% in the AST as matched, no_parens and unmatched.
|
||||
%%
|
||||
%% The distinction is required because we can't, for example, have
|
||||
%% a function call with a do block as argument inside another do
|
||||
%% block call, unless there are parentheses:
|
||||
%%
|
||||
%% if if true do true else false end do #=> invalid
|
||||
%% if(if true do true else false end) do #=> valid
|
||||
%%
|
||||
%% Similarly, it is not possible to nest calls without parentheses
|
||||
%% if their arity is more than 1:
|
||||
%%
|
||||
%% foo a, bar b, c #=> invalid
|
||||
%% foo(a, bar b, c) #=> invalid
|
||||
%% foo a, bar b #=> valid
|
||||
%% foo a, bar(b, c) #=> valid
|
||||
%%
|
||||
%% So the different grammar rules need to take into account
|
||||
%% if calls without parentheses are do blocks in particular
|
||||
%% segments and act accordingly.
|
||||
matched_expr -> matched_expr matched_op_expr : build_op(element(1, '$2'), '$1', element(2, '$2')).
|
||||
matched_expr -> matched_expr no_parens_op_expr : build_op(element(1, '$2'), '$1', element(2, '$2')).
|
||||
matched_expr -> unary_op_eol matched_expr : build_unary_op('$1', '$2').
|
||||
@@ -118,13 +134,9 @@ op_expr -> exp_op_eol expr : { '$1', '$2' }.
|
||||
op_expr -> two_op_eol expr : { '$1', '$2' }.
|
||||
op_expr -> and_op_eol expr : { '$1', '$2' }.
|
||||
op_expr -> or_op_eol expr : { '$1', '$2' }.
|
||||
op_expr -> tail_op_eol expr : { '$1', '$2' }.
|
||||
op_expr -> than_op_eol expr : { '$1', '$2' }.
|
||||
op_expr -> in_op_eol expr : { '$1', '$2' }.
|
||||
op_expr -> inc_op_eol expr : { '$1', '$2' }.
|
||||
op_expr -> in_match_op_eol expr : { '$1', '$2' }.
|
||||
op_expr -> when_op_eol expr : { '$1', '$2' }.
|
||||
op_expr -> range_op_eol expr : { '$1', '$2' }.
|
||||
op_expr -> default_op_eol expr : { '$1', '$2' }.
|
||||
op_expr -> type_op_eol expr : { '$1', '$2' }.
|
||||
op_expr -> comp_op_eol expr : { '$1', '$2' }.
|
||||
op_expr -> arrow_op_eol expr : { '$1', '$2' }.
|
||||
@@ -136,17 +148,16 @@ no_parens_op_expr -> exp_op_eol no_parens_expr : { '$1', '$2' }.
|
||||
no_parens_op_expr -> two_op_eol no_parens_expr : { '$1', '$2' }.
|
||||
no_parens_op_expr -> and_op_eol no_parens_expr : { '$1', '$2' }.
|
||||
no_parens_op_expr -> or_op_eol no_parens_expr : { '$1', '$2' }.
|
||||
no_parens_op_expr -> tail_op_eol no_parens_expr : { '$1', '$2' }.
|
||||
no_parens_op_expr -> than_op_eol no_parens_expr : { '$1', '$2' }.
|
||||
no_parens_op_expr -> in_op_eol no_parens_expr : { '$1', '$2' }.
|
||||
no_parens_op_expr -> inc_op_eol no_parens_expr : { '$1', '$2' }.
|
||||
no_parens_op_expr -> when_op_eol no_parens_expr : { '$1', '$2' }.
|
||||
no_parens_op_expr -> range_op_eol no_parens_expr : { '$1', '$2' }.
|
||||
no_parens_op_expr -> default_op_eol no_parens_expr : { '$1', '$2' }.
|
||||
no_parens_op_expr -> in_match_op_eol no_parens_expr : { '$1', '$2' }.
|
||||
no_parens_op_expr -> type_op_eol no_parens_expr : { '$1', '$2' }.
|
||||
no_parens_op_expr -> comp_op_eol no_parens_expr : { '$1', '$2' }.
|
||||
no_parens_op_expr -> arrow_op_eol no_parens_expr : { '$1', '$2' }.
|
||||
|
||||
%% Allow when (and only when) with keywords
|
||||
no_parens_op_expr -> when_op_eol no_parens_expr : { '$1', '$2' }.
|
||||
no_parens_op_expr -> when_op_eol call_args_no_parens_kw : { '$1', '$2' }.
|
||||
|
||||
matched_op_expr -> match_op_eol matched_expr : { '$1', '$2' }.
|
||||
matched_op_expr -> add_op_eol matched_expr : { '$1', '$2' }.
|
||||
matched_op_expr -> mult_op_eol matched_expr : { '$1', '$2' }.
|
||||
@@ -154,13 +165,9 @@ matched_op_expr -> exp_op_eol matched_expr : { '$1', '$2' }.
|
||||
matched_op_expr -> two_op_eol matched_expr : { '$1', '$2' }.
|
||||
matched_op_expr -> and_op_eol matched_expr : { '$1', '$2' }.
|
||||
matched_op_expr -> or_op_eol matched_expr : { '$1', '$2' }.
|
||||
matched_op_expr -> tail_op_eol matched_expr : { '$1', '$2' }.
|
||||
matched_op_expr -> than_op_eol matched_expr : { '$1', '$2' }.
|
||||
matched_op_expr -> in_op_eol matched_expr : { '$1', '$2' }.
|
||||
matched_op_expr -> inc_op_eol matched_expr : { '$1', '$2' }.
|
||||
matched_op_expr -> in_match_op_eol matched_expr : { '$1', '$2' }.
|
||||
matched_op_expr -> when_op_eol matched_expr : { '$1', '$2' }.
|
||||
matched_op_expr -> range_op_eol matched_expr : { '$1', '$2' }.
|
||||
matched_op_expr -> default_op_eol matched_expr : { '$1', '$2' }.
|
||||
matched_op_expr -> type_op_eol matched_expr : { '$1', '$2' }.
|
||||
matched_op_expr -> comp_op_eol matched_expr : { '$1', '$2' }.
|
||||
matched_op_expr -> arrow_op_eol matched_expr : { '$1', '$2' }.
|
||||
@@ -191,19 +198,19 @@ bracket_at_expr -> bracket_at_expr list :
|
||||
|
||||
base_expr -> atom : ?exprs('$1').
|
||||
base_expr -> number : ?exprs('$1').
|
||||
base_expr -> signed_number : { element(4, '$1'), [{line,?line('$1')}], ?exprs('$1') }.
|
||||
base_expr -> signed_number : { element(4, '$1'), meta('$1'), ?exprs('$1') }.
|
||||
base_expr -> list : element(1, '$1').
|
||||
base_expr -> tuple : '$1'.
|
||||
base_expr -> 'true' : ?id('$1').
|
||||
base_expr -> 'false' : ?id('$1').
|
||||
base_expr -> 'nil' : ?id('$1').
|
||||
base_expr -> aliases : { '__aliases__', [{line,?line('$1')}], ?exprs('$1') }.
|
||||
base_expr -> aliases : { '__aliases__', meta('$1', 0), ?exprs('$1') }.
|
||||
base_expr -> bin_string : build_bin_string('$1').
|
||||
base_expr -> list_string : build_list_string('$1').
|
||||
base_expr -> atom_string : build_atom_string('$1').
|
||||
base_expr -> bit_string : '$1'.
|
||||
base_expr -> sigil : build_sigil('$1').
|
||||
base_expr -> '&' number : { '&', [{line,?line('$1')}], [?exprs('$2')] }.
|
||||
base_expr -> '&' number : { '&', meta('$1'), [?exprs('$2')] }.
|
||||
|
||||
%% Blocks
|
||||
|
||||
@@ -237,7 +244,7 @@ stab_expr -> call_args_no_parens_all stab_op_eol stab_maybe_expr :
|
||||
stab_expr -> stab_parens_many stab_op_eol stab_maybe_expr :
|
||||
build_op('$2', unwrap_splice('$1'), '$3').
|
||||
stab_expr -> stab_parens_many when_op expr stab_op_eol stab_maybe_expr :
|
||||
build_op('$4', [{ 'when', [{line,?line('$2')}], unwrap_splice('$1') ++ ['$3'] }], '$5').
|
||||
build_op('$4', [{ 'when', meta('$2'), unwrap_splice('$1') ++ ['$3'] }], '$5').
|
||||
|
||||
stab_maybe_expr -> 'expr' : '$1'.
|
||||
stab_maybe_expr -> '$empty' : nil.
|
||||
@@ -290,9 +297,6 @@ exp_op_eol -> exp_op eol : '$1'.
|
||||
two_op_eol -> two_op : '$1'.
|
||||
two_op_eol -> two_op eol : '$1'.
|
||||
|
||||
default_op_eol -> default_op : '$1'.
|
||||
default_op_eol -> default_op eol : '$1'.
|
||||
|
||||
type_op_eol -> type_op : '$1'.
|
||||
type_op_eol -> type_op eol : '$1'.
|
||||
|
||||
@@ -310,17 +314,11 @@ and_op_eol -> and_op eol : '$1'.
|
||||
or_op_eol -> or_op : '$1'.
|
||||
or_op_eol -> or_op eol : '$1'.
|
||||
|
||||
tail_op_eol -> tail_op : '$1'.
|
||||
tail_op_eol -> tail_op eol : '$1'.
|
||||
|
||||
than_op_eol -> than_op : '$1'.
|
||||
than_op_eol -> than_op eol : '$1'.
|
||||
|
||||
in_op_eol -> in_op : '$1'.
|
||||
in_op_eol -> in_op eol : '$1'.
|
||||
|
||||
inc_op_eol -> inc_op : '$1'.
|
||||
inc_op_eol -> inc_op eol : '$1'.
|
||||
in_match_op_eol -> in_match_op : '$1'.
|
||||
in_match_op_eol -> in_match_op eol : '$1'.
|
||||
|
||||
when_op_eol -> when_op : '$1'.
|
||||
when_op_eol -> when_op eol : '$1'.
|
||||
@@ -328,9 +326,6 @@ when_op_eol -> when_op eol : '$1'.
|
||||
stab_op_eol -> stab_op : '$1'.
|
||||
stab_op_eol -> stab_op eol : '$1'.
|
||||
|
||||
range_op_eol -> range_op : '$1'.
|
||||
range_op_eol -> range_op eol : '$1'.
|
||||
|
||||
at_op_eol -> at_op : '$1'.
|
||||
at_op_eol -> at_op eol : '$1'.
|
||||
|
||||
@@ -363,7 +358,7 @@ dot_paren_identifier -> paren_identifier : '$1'.
|
||||
dot_paren_identifier -> matched_expr dot_op paren_identifier : build_dot('$2', '$1', '$3').
|
||||
|
||||
parens_call -> dot_paren_identifier : '$1'.
|
||||
parens_call -> matched_expr dot_call_op : { '.', [{line,?line('$2')}], ['$1'] }. % Fun/local calls
|
||||
parens_call -> matched_expr dot_call_op : { '.', meta('$2'), ['$1'] }. % Fun/local calls
|
||||
|
||||
% Function calls with no parentheses
|
||||
|
||||
@@ -455,14 +450,15 @@ tuple -> open_curly container_expr ',' container_args close_curly : build_tuple
|
||||
|
||||
% Bitstrings
|
||||
|
||||
bit_string -> open_bit '>>' : { '<<>>', [{line,?line('$1')}], [] }.
|
||||
bit_string -> open_bit container_args close_bit : { '<<>>', [{line,?line('$1')}], '$2' }.
|
||||
bit_string -> open_bit '>>' : { '<<>>', meta('$1'), [] }.
|
||||
bit_string -> open_bit container_args close_bit : { '<<>>', meta('$1'), '$2' }.
|
||||
|
||||
Erlang code.
|
||||
|
||||
-define(id(Node), element(1, Node)).
|
||||
-define(line(Node), element(2, Node)).
|
||||
-define(exprs(Node), element(3, Node)).
|
||||
-define(lexical(Kind), Kind == import; Kind == alias; Kind == '__aliases__').
|
||||
|
||||
-define(rearrange_uop(Op), Op == 'not' orelse Op == '!').
|
||||
-define(rearrange_bop(Op), Op == 'in' orelse Op == 'inlist' orelse Op == 'inbits').
|
||||
@@ -471,28 +467,32 @@ Erlang code.
|
||||
%% compilation of the generated .erl file by the HiPE compiler
|
||||
-compile([{hipe,[{regalloc,linear_scan}]}]).
|
||||
|
||||
meta(Line, Counter) -> [{counter,Counter}|meta(Line)].
|
||||
meta(Line) when is_integer(Line) -> [{line,Line}];
|
||||
meta(Node) -> meta(?line(Node)).
|
||||
|
||||
%% Operators
|
||||
|
||||
build_op({ _Kind, Line, '/' }, { '&', _, [{ Kind, _, Atom } = Left] }, Right) when is_number(Right), is_atom(Atom), is_atom(Kind) ->
|
||||
{ '&', [{line,Line}], [{ '/', [{line,Line}], [Left, Right] }] };
|
||||
{ '&', meta(Line), [{ '/', meta(Line), [Left, Right] }] };
|
||||
|
||||
build_op({ _Kind, Line, '/' }, { '&', _, [{ { '.', _, [_, _] }, _, [] } = Left] }, Right) when is_number(Right) ->
|
||||
{ '&', [{line,Line}], [{ '/', [{line,Line}], [Left, Right] }] };
|
||||
{ '&', meta(Line), [{ '/', meta(Line), [Left, Right] }] };
|
||||
|
||||
build_op({ _Kind, Line, BOp }, { UOp, _, [Left] }, Right) when ?rearrange_bop(BOp), ?rearrange_uop(UOp) ->
|
||||
{ UOp, [{line,Line}], [{ BOp, [{line,Line}], [Left, Right] }] };
|
||||
{ UOp, meta(Line), [{ BOp, meta(Line), [Left, Right] }] };
|
||||
|
||||
build_op({ _Kind, Line, Op }, Left, Right) ->
|
||||
{ Op, [{line,Line}], [Left, Right] }.
|
||||
{ Op, meta(Line), [Left, Right] }.
|
||||
|
||||
build_unary_op({ _Kind, Line, Op }, Expr) ->
|
||||
{ Op, [{line,Line}], [Expr] }.
|
||||
{ Op, meta(Line), [Expr] }.
|
||||
|
||||
build_tuple(_Marker, [Left, Right]) ->
|
||||
{ Left, Right };
|
||||
|
||||
build_tuple(Marker, Args) ->
|
||||
{ '{}', [{line,?line(Marker)}], Args }.
|
||||
{ '{}', meta(Marker), Args }.
|
||||
|
||||
%% Blocks
|
||||
|
||||
@@ -505,13 +505,13 @@ build_block(Exprs) -> { '__block__', [], Ex
|
||||
%% Dots
|
||||
|
||||
build_dot_alias(Dot, { '__aliases__', _, Left }, { 'aliases', _, Right }) ->
|
||||
{ '__aliases__', [{line,?line(Dot)}], Left ++ Right };
|
||||
{ '__aliases__', meta(Dot), Left ++ Right };
|
||||
|
||||
build_dot_alias(Dot, Other, { 'aliases', _, Right }) ->
|
||||
{ '__aliases__', [{line,?line(Dot)}], [Other|Right] }.
|
||||
{ '__aliases__', meta(Dot), [Other|Right] }.
|
||||
|
||||
build_dot(Dot, Left, Right) ->
|
||||
{ '.', [{line,?line(Dot)}], [Left, extract_identifier(Right)] }.
|
||||
{ '.', meta(Dot), [Left, extract_identifier(Right)] }.
|
||||
|
||||
%% Identifiers
|
||||
|
||||
@@ -527,13 +527,16 @@ build_identifier({ '.', Meta, _ } = Dot, Args) ->
|
||||
{ Dot, Meta, FArgs };
|
||||
|
||||
build_identifier({ Keyword, Line }, Args) when Keyword == fn ->
|
||||
{ fn, [{line,Line}], Args };
|
||||
{ fn, meta(Line), Args };
|
||||
|
||||
build_identifier({ op_identifier, Line, Identifier }, [Arg]) ->
|
||||
{ Identifier, [{ambiguous_op,nil},{line,Line}], [Arg] };
|
||||
{ Identifier, [{ambiguous_op,nil}|meta(Line)], [Arg] };
|
||||
|
||||
build_identifier({ _, Line, Identifier }, Args) when ?lexical(Identifier) ->
|
||||
{ Identifier, meta(Line, 0), Args };
|
||||
|
||||
build_identifier({ _, Line, Identifier }, Args) ->
|
||||
{ Identifier, [{line,Line}], Args }.
|
||||
{ Identifier, meta(Line), Args }.
|
||||
|
||||
extract_identifier({ Kind, _, Identifier }) when
|
||||
Kind == identifier; Kind == bracket_identifier; Kind == paren_identifier;
|
||||
@@ -545,35 +548,35 @@ extract_identifier(Other) -> Other.
|
||||
%% Fn
|
||||
|
||||
build_fn(Op, Stab) ->
|
||||
{ fn, [{line,?line(Op)}], [Stab] }.
|
||||
{ fn, meta(Op), Stab }.
|
||||
|
||||
%% Access
|
||||
|
||||
build_access(Expr, { List, Line }) ->
|
||||
Meta = [{line,Line}],
|
||||
Meta = meta(Line),
|
||||
{ { '.', Meta, ['Elixir.Kernel', access] }, Meta, [Expr, List] }.
|
||||
|
||||
%% Interpolation aware
|
||||
|
||||
build_sigil({ sigil, Line, Sigil, Parts, Modifiers }) ->
|
||||
Meta = [{line,Line}],
|
||||
Meta = meta(Line),
|
||||
{ list_to_atom("sigil_" ++ [Sigil]), Meta, [ { '<<>>', Meta, string_parts(Parts) }, Modifiers ] }.
|
||||
|
||||
build_bin_string({ bin_string, _Line, [H] }) when is_binary(H) ->
|
||||
H;
|
||||
build_bin_string({ bin_string, Line, Args }) ->
|
||||
{ '<<>>', [{line,Line}], string_parts(Args) }.
|
||||
{ '<<>>', meta(Line), string_parts(Args) }.
|
||||
|
||||
build_list_string({ list_string, _Line, [H] }) when is_binary(H) ->
|
||||
elixir_utils:characters_to_list(H);
|
||||
build_list_string({ list_string, Line, Args }) ->
|
||||
Meta = [{line,Line}],
|
||||
Meta = meta(Line),
|
||||
{ { '.', Meta, ['Elixir.String', 'to_char_list!'] }, Meta, [{ '<<>>', Meta, string_parts(Args) }] }.
|
||||
|
||||
build_atom_string({ atom_string, _Line, Safe, [H] }) when is_binary(H) ->
|
||||
Op = binary_to_atom_op(Safe), erlang:Op(H, utf8);
|
||||
build_atom_string({ atom_string, Line, Safe, Args }) ->
|
||||
Meta = [{line,Line}],
|
||||
Meta = meta(Line),
|
||||
{ { '.', Meta, [erlang, binary_to_atom_op(Safe)] }, Meta, [{ '<<>>', Meta, string_parts(Args) }, utf8] }.
|
||||
|
||||
binary_to_atom_op(true) -> binary_to_existing_atom;
|
||||
@@ -584,35 +587,33 @@ string_parts(Parts) ->
|
||||
string_part(Binary) when is_binary(Binary) ->
|
||||
Binary;
|
||||
string_part({ Line, Tokens }) ->
|
||||
Form = string_tokens_parse(Line, Tokens),
|
||||
Meta = [{line,Line}],
|
||||
Form = string_tokens_parse(Tokens),
|
||||
Meta = meta(Line),
|
||||
{ '::', Meta, [{ { '.', Meta, ['Elixir.Kernel', to_string] }, Meta, [Form]}, { binary, Meta, nil }]}.
|
||||
|
||||
string_tokens_parse(Line, Tokens) ->
|
||||
string_tokens_parse(Tokens) ->
|
||||
case parse(Tokens) of
|
||||
{ ok, [] } -> nil;
|
||||
{ ok, [Forms] } when not is_list(Forms) -> Forms;
|
||||
{ ok, Forms } -> { '__block__', [{line, Line}], Forms };
|
||||
{ ok, Forms } -> Forms;
|
||||
{ error, _ } = Error -> throw(Error)
|
||||
end.
|
||||
|
||||
%% Keywords
|
||||
|
||||
build_stab([{ '->', Meta, [Left, Right] }|T]) ->
|
||||
{ '->', Meta, build_stab(Meta, T, Left, [Right], []) };
|
||||
build_stab(Meta, T, Left, [Right], []);
|
||||
|
||||
build_stab(Else) ->
|
||||
build_block(Else).
|
||||
|
||||
build_stab(Old, [{ '->', New, [Left, Right] }|T], Marker, Temp, Acc) ->
|
||||
H = { Marker, Old, build_block(lists:reverse(Temp)) },
|
||||
H = { '->', Old, [Marker, build_block(lists:reverse(Temp))] },
|
||||
build_stab(New, T, Left, [Right], [H|Acc]);
|
||||
|
||||
build_stab(Meta, [H|T], Marker, Temp, Acc) ->
|
||||
build_stab(Meta, T, Marker, [H|Temp], Acc);
|
||||
|
||||
build_stab(Meta, [], Marker, Temp, Acc) ->
|
||||
H = { Marker, Meta, build_block(lists:reverse(Temp)) },
|
||||
H = { '->', Meta, [Marker, build_block(lists:reverse(Temp))] },
|
||||
lists:reverse([H|Acc]).
|
||||
|
||||
%% Every time the parser sees a (unquote_splicing())
|
||||
@@ -635,6 +636,9 @@ unwrap_when(Args) ->
|
||||
Args
|
||||
end.
|
||||
|
||||
to_block([One]) when not is_list(One) -> One;
|
||||
to_block(Other) -> { '__block__', [], lists:reverse(Other) }.
|
||||
|
||||
%% Errors
|
||||
|
||||
throw(Line, Error, Token) ->
|
||||
|
||||
+178
-159
@@ -1,47 +1,73 @@
|
||||
%% Implements Elixir quote.
|
||||
-module(elixir_quote).
|
||||
-export([escape/2, erl_escape/3, erl_quote/4,
|
||||
linify/2, linify/3, unquote/4, tail_join/3, join/2]).
|
||||
-export([escape/2, linify/2, linify/3, linify_with_context_counter/3, quote/4]).
|
||||
-export([dot/6, tail_list/3, list/2]). %% Quote callbacks
|
||||
|
||||
-include("elixir.hrl").
|
||||
-define(defs(Kind), Kind == def; Kind == defp; Kind == defmacro; Kind == defmacrop).
|
||||
-define(lexical(Kind), Kind == import; Kind == alias; Kind == '__aliases__').
|
||||
-compile({ inline, [keyfind/2, keystore/3, keydelete/2, keyreplace/3, keynew/3] }).
|
||||
|
||||
%% Apply the line from site call on quoted contents.
|
||||
%% Receives a Key to look for the default line as argument.
|
||||
linify(Line, Exprs) when is_integer(Line) ->
|
||||
do_linify(Line, nil, Exprs).
|
||||
do_linify(Line, line, nil, Exprs).
|
||||
|
||||
linify(Line, Var, Exprs) when is_integer(Line) ->
|
||||
do_linify(Line, Var, Exprs).
|
||||
linify(Line, Key, Exprs) when is_integer(Line) ->
|
||||
do_linify(Line, Key, nil, Exprs).
|
||||
|
||||
do_linify(Line, { Receiver, Counter } = Var, { Left, Meta, Receiver }) when is_atom(Left), is_list(Meta) ->
|
||||
NewMeta = case keyfind(counter, Meta) of
|
||||
{ counter, _ } -> Meta;
|
||||
_ -> keystore(counter, Meta, Counter)
|
||||
end,
|
||||
do_tuple_linify(Line, Var, NewMeta, Left, Receiver);
|
||||
%% Same as linify but also considers the context counter.
|
||||
linify_with_context_counter(Line, Var, Exprs) when is_integer(Line) ->
|
||||
do_linify(Line, line, Var, Exprs).
|
||||
|
||||
do_linify(Line, Var, { Left, Meta, Right }) when is_list(Meta) ->
|
||||
do_tuple_linify(Line, Var, Meta, Left, Right);
|
||||
do_linify(Line, Key, { Receiver, Counter } = Var, { Left, Meta, Receiver })
|
||||
when is_atom(Left), is_list(Meta), Left /= '_' ->
|
||||
do_tuple_linify(Line, Key, Var, keynew(counter, Meta, Counter), Left, Receiver);
|
||||
|
||||
do_linify(Line, Var, { Left, Right }) ->
|
||||
{ do_linify(Line, Var, Left), do_linify(Line, Var, Right) };
|
||||
do_linify(Line, Key, { _, Counter } = Var, { Lexical, [_|_] = Meta, [_|_] = Args }) when ?lexical(Lexical) ->
|
||||
do_tuple_linify(Line, Key, Var, keynew(counter, Meta, Counter), Lexical, Args);
|
||||
|
||||
do_linify(Line, Var, List) when is_list(List) ->
|
||||
[do_linify(Line, Var, X) || X <- List];
|
||||
do_linify(Line, Key, Var, { Left, Meta, Right }) when is_list(Meta) ->
|
||||
do_tuple_linify(Line, Key, Var, Meta, Left, Right);
|
||||
|
||||
do_linify(_, _, Else) -> Else.
|
||||
do_linify(Line, Key, Var, { Left, Right }) ->
|
||||
{ do_linify(Line, Key, Var, Left), do_linify(Line, Key, Var, Right) };
|
||||
|
||||
do_tuple_linify(Line, Var, Meta, Left, Right) ->
|
||||
NewMeta = case ?line(Meta) of
|
||||
0 -> keystore(line, Meta, Line);
|
||||
_ -> Meta
|
||||
end,
|
||||
{ do_linify(Line, Var, Left), NewMeta, do_linify(Line, Var, Right) }.
|
||||
do_linify(Line, Key, Var, List) when is_list(List) ->
|
||||
[do_linify(Line, Key, Var, X) || X <- List];
|
||||
|
||||
do_linify(_, _, _, Else) -> Else.
|
||||
|
||||
do_tuple_linify(Line, Key, Var, Meta, Left, Right) ->
|
||||
{ do_linify(Line, Key, Var, Left),
|
||||
do_linify_meta(Line, Key, Meta),
|
||||
do_linify(Line, Key, Var, Right) }.
|
||||
|
||||
do_linify_meta(0, line, Meta) ->
|
||||
Meta;
|
||||
do_linify_meta(Line, line, Meta) ->
|
||||
case keyfind(line, Meta) of
|
||||
{ line, Int } when is_integer(Int), Int /= 0 ->
|
||||
Meta;
|
||||
_ ->
|
||||
keystore(line, Meta, Line)
|
||||
end;
|
||||
do_linify_meta(Line, Key, Meta) ->
|
||||
case keyfind(Key, Meta) of
|
||||
{ Key, Int } when is_integer(Int), Int /= 0 ->
|
||||
keyreplace(Key, Meta, { line, Int });
|
||||
_ ->
|
||||
do_linify_meta(Line, line, Meta)
|
||||
end.
|
||||
|
||||
%% Some expressions cannot be unquoted at compilation time.
|
||||
%% This function is responsible for doing runtime unquoting.
|
||||
unquote(Meta, Left, { '__aliases__', _, Args }, nil) ->
|
||||
dot(Meta, Left, Right, Args, Context, File) ->
|
||||
annotate(dot(Meta, Left, Right, Args), Context, File).
|
||||
|
||||
dot(Meta, Left, { '__aliases__', _, Args }, nil) ->
|
||||
{ '__aliases__', Meta, [Left|Args] };
|
||||
|
||||
unquote(Meta, Left, Right, nil) when is_atom(Right) ->
|
||||
dot(Meta, Left, Right, nil) when is_atom(Right) ->
|
||||
case atom_to_list(Right) of
|
||||
"Elixir." ++ _ ->
|
||||
{ '__aliases__', Meta, [Left, Right] };
|
||||
@@ -49,71 +75,84 @@ unquote(Meta, Left, Right, nil) when is_atom(Right) ->
|
||||
{ { '.', Meta, [Left, Right] }, Meta, [] }
|
||||
end;
|
||||
|
||||
unquote(Meta, Left, { Right, _, Context }, nil) when is_atom(Right), is_atom(Context) ->
|
||||
dot(Meta, Left, { Right, _, Context }, nil) when is_atom(Right), is_atom(Context) ->
|
||||
{ { '.', Meta, [Left, Right] }, Meta, [] };
|
||||
|
||||
unquote(Meta, Left, { Right, _, Args }, nil) when is_atom(Right) ->
|
||||
dot(Meta, Left, { Right, _, Args }, nil) when is_atom(Right) ->
|
||||
{ { '.', Meta, [Left, Right] }, Meta, Args };
|
||||
|
||||
unquote(_Meta, _Left, Right, nil) ->
|
||||
dot(_Meta, _Left, Right, nil) ->
|
||||
argument_error(<<"expected unquote after dot to return an atom, an alias or a quoted call, got: ",
|
||||
('Elixir.Macro':to_string(Right))/binary>>);
|
||||
|
||||
unquote(Meta, Left, Right, Args) when is_atom(Right) ->
|
||||
dot(Meta, Left, Right, Args) when is_atom(Right) ->
|
||||
{ { '.', Meta, [Left, Right] }, Meta, Args };
|
||||
|
||||
unquote(Meta, Left, { Right, _, Context }, Args) when is_atom(Right), is_atom(Context) ->
|
||||
dot(Meta, Left, { Right, _, Context }, Args) when is_atom(Right), is_atom(Context) ->
|
||||
{ { '.', Meta, [Left, Right] }, Meta, Args };
|
||||
|
||||
unquote(_Meta, _Left, Right, _Args) ->
|
||||
dot(_Meta, _Left, Right, _Args) ->
|
||||
argument_error(<<"expected unquote after dot with args to return an atom or a quoted call, got: ",
|
||||
('Elixir.Macro':to_string(Right))/binary>>).
|
||||
|
||||
join(Left, Right) when is_list(Right) ->
|
||||
validate_join(Left),
|
||||
list(Left, Right) when is_list(Right) ->
|
||||
validate_list(Left),
|
||||
Left ++ Right.
|
||||
|
||||
tail_join(Left, Right, Tail) when is_list(Right), is_list(Tail) ->
|
||||
validate_join(Left),
|
||||
tail_list(Left, Right, Tail) when is_list(Right), is_list(Tail) ->
|
||||
validate_list(Left),
|
||||
Tail ++ Left ++ Right;
|
||||
|
||||
tail_join(Left, Right, Tail) when is_list(Left) ->
|
||||
validate_join(Left),
|
||||
tail_list(Left, Right, Tail) when is_list(Left) ->
|
||||
validate_list(Left),
|
||||
[H|T] = lists:reverse(Tail ++ Left),
|
||||
lists:reverse([{ '|', [], [H, Right] }|T]).
|
||||
|
||||
validate_join(List) when is_list(List) ->
|
||||
validate_list(List) when is_list(List) ->
|
||||
ok;
|
||||
validate_join(List) when not is_list(List) ->
|
||||
validate_list(List) when not is_list(List) ->
|
||||
argument_error(<<"expected a list with quoted expressions in unquote_splicing/1, got: ",
|
||||
('Elixir.Kernel':inspect(List))/binary>>).
|
||||
|
||||
argument_error(Message) ->
|
||||
error('Elixir.ArgumentError':exception([{message,Message}])).
|
||||
|
||||
%% Annotates the AST with context and other info
|
||||
|
||||
annotate({ Def, Meta, [{ H, M, A }|T] }, Context, File) when ?defs(Def) ->
|
||||
%% Store the context information in the first element of the
|
||||
%% definition tuple so we can access it later on.
|
||||
MM = keystore(context, keystore(file, M, File), Context),
|
||||
{ Def, Meta, [{ H, MM, A }|T] };
|
||||
annotate({ { '.', _, [_, Def] } = Target, Meta, [{ H, M, A }|T] }, Context, File) when ?defs(Def) ->
|
||||
MM = keystore(context, keystore(file, M, File), Context),
|
||||
{ Target, Meta, [{ H, MM, A }|T] };
|
||||
|
||||
annotate({ Lexical, Meta, [_|_] = Args }, Context, _File) when Lexical == import; Lexical == alias ->
|
||||
NewMeta = keystore(context, keydelete(counter, Meta), Context),
|
||||
{ Lexical, NewMeta, Args };
|
||||
annotate(Tree, _Context, _File) -> Tree.
|
||||
|
||||
%% Escapes the given expression. It is similar to quote, but
|
||||
%% lines are kept and hygiene mechanisms are disabled.
|
||||
escape(Expr, Unquote) ->
|
||||
quote(Expr, nil, #elixir_quote{
|
||||
line=keep,
|
||||
{ Res, Q } = quote(Expr, nil, #elixir_quote{
|
||||
line=true,
|
||||
keep=false,
|
||||
vars_hygiene=false,
|
||||
aliases_hygiene=false,
|
||||
imports_hygiene=false,
|
||||
unquote=Unquote,
|
||||
escape=true
|
||||
}, nil).
|
||||
|
||||
erl_escape(Expr, Unquote, S) ->
|
||||
{ QExpr, TQ } = escape(Expr, Unquote),
|
||||
{ TExpr, TS } = elixir_translator:translate_each(QExpr, S),
|
||||
{ TExpr, TQ, TS }.
|
||||
}, nil),
|
||||
{ Res, Q#elixir_quote.unquoted }.
|
||||
|
||||
%% Quotes an expression and return its quoted Elixir AST.
|
||||
|
||||
quote(Expr, nil, Q, S) ->
|
||||
do_quote(Expr, Q, S);
|
||||
quote(Expr, nil, Q, E) ->
|
||||
do_quote(Expr, Q, E);
|
||||
|
||||
quote(Expr, Binding, Q, S) ->
|
||||
quote(Expr, Binding, Q, E) ->
|
||||
Context = Q#elixir_quote.context,
|
||||
|
||||
Vars = [ { '{}', [],
|
||||
@@ -123,170 +162,139 @@ quote(Expr, Binding, Q, S) ->
|
||||
] ]
|
||||
} || { K, V } <- Binding],
|
||||
|
||||
{ TExprs, TQ } = do_quote(Expr, Q, S),
|
||||
{ TExprs, TQ } = do_quote(Expr, Q, E),
|
||||
{ { '{}',[], ['__block__',[], Vars ++ [TExprs] ] }, TQ }.
|
||||
|
||||
erl_quote(Expr, Binding, Q, S) ->
|
||||
{ QExpr, TQ } = quote(Expr, Binding, 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 ->
|
||||
{ TTuple, TQ } = do_quote_tuple(Tuple, Q#elixir_quote{unquote=false}, S),
|
||||
do_quote({ quote, _, Args } = Tuple, #elixir_quote{unquote=true} = Q, E) when length(Args) == 1; length(Args) == 2 ->
|
||||
{ TTuple, TQ } = do_quote_tuple(Tuple, Q#elixir_quote{unquote=false}, E),
|
||||
{ TTuple, TQ#elixir_quote{unquote=true} };
|
||||
|
||||
do_quote({ unquote, _Meta, [Expr] }, #elixir_quote{unquote=true} = Q, _) ->
|
||||
{ Expr, Q#elixir_quote{unquoted=true} };
|
||||
|
||||
%% Context mark
|
||||
|
||||
do_quote({ defmodule, Meta, [_|_] = Args }, #elixir_quote{escape=false} = Q, S) ->
|
||||
%% Only store the context if we actually have a full alias as
|
||||
%% argument, otherwise the expression is being automatically
|
||||
%% generated and we don't want the alias to count.
|
||||
NewMeta =
|
||||
case hd(Args) of
|
||||
{ '__aliases__', _, Atoms } ->
|
||||
case lists:all(fun is_atom/1, Atoms) of
|
||||
true -> keystore(context, Meta, Q#elixir_quote.context);
|
||||
false -> Meta
|
||||
end;
|
||||
_ ->
|
||||
Meta
|
||||
end,
|
||||
|
||||
do_quote_tuple({ defmodule, NewMeta, Args }, Q, S);
|
||||
|
||||
do_quote({ Def, Meta, [_|_] = Args }, #elixir_quote{escape=false} = Q, S) when ?defs(Def); Def == alias; Def == import ->
|
||||
NewMeta = keystore(context, Meta, Q#elixir_quote.context),
|
||||
do_quote_tuple({ Def, NewMeta, Args }, Q, S);
|
||||
|
||||
do_quote({ { '.', _, [_, Def] } = Target, Meta, Args }, #elixir_quote{escape=false} = Q, S) when ?defs(Def); Def == defmodule; Def == alias; Def == import ->
|
||||
NewMeta = keystore(context, Meta, 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, S#elixir_scope.lexical_tracker) of
|
||||
do_quote({ '__aliases__', Meta, [H|T] } = Alias, #elixir_quote{aliases_hygiene=true} = Q, E) when is_atom(H) and (H /= 'Elixir') ->
|
||||
Annotation = case elixir_aliases:expand(Alias, E#elixir_env.aliases,
|
||||
E#elixir_env.macro_aliases, E#elixir_env.lexical_tracker) of
|
||||
Atom when is_atom(Atom) -> Atom;
|
||||
Aliases when is_list(Aliases) -> false
|
||||
end,
|
||||
AliasMeta = keystore(alias, Meta, Annotation),
|
||||
do_quote_tuple({ '__aliases__', AliasMeta, [H|T] }, Q, S);
|
||||
AliasMeta = keystore(alias, keydelete(counter, Meta), Annotation),
|
||||
do_quote_tuple({ '__aliases__', AliasMeta, [H|T] }, Q, E);
|
||||
|
||||
%% 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);
|
||||
do_quote({ Left, Meta, nil }, #elixir_quote{vars_hygiene=true} = Q, E) when is_atom(Left) ->
|
||||
do_quote_tuple({ Left, Meta, Q#elixir_quote.context }, Q, E);
|
||||
|
||||
%% 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] }, _, Args }, #elixir_quote{unquote=true} = Q, E) ->
|
||||
do_quote_call(Left, Meta, Expr, Args, Q, E);
|
||||
|
||||
do_quote({ { '.', Meta, [Left, unquote] }, _, [Expr] }, #elixir_quote{unquote=true} = Q, S) ->
|
||||
do_quote_call(Left, Meta, Expr, nil, Q, S);
|
||||
do_quote({ { '.', Meta, [Left, unquote] }, _, [Expr] }, #elixir_quote{unquote=true} = Q, E) ->
|
||||
do_quote_call(Left, Meta, Expr, nil, Q, E);
|
||||
|
||||
%% Imports
|
||||
|
||||
do_quote({ '&', Meta, [{ '/', _, [{F, _, C}, A]}] = Args },
|
||||
#elixir_quote{imports_hygiene=true} = Q, S) when is_atom(F), is_integer(A), is_atom(C) ->
|
||||
do_quote_fa('&', Meta, Args, F, A, Q, S);
|
||||
#elixir_quote{imports_hygiene=true} = Q, E) when is_atom(F), is_integer(A), is_atom(C) ->
|
||||
do_quote_fa('&', Meta, Args, F, A, Q, E);
|
||||
|
||||
do_quote({ Name, Meta, ArgsOrAtom }, #elixir_quote{imports_hygiene=true} = Q, S) when is_atom(Name) ->
|
||||
do_quote({ Name, Meta, ArgsOrAtom }, #elixir_quote{imports_hygiene=true} = Q, E) when is_atom(Name) ->
|
||||
Arity = case is_atom(ArgsOrAtom) of
|
||||
true -> 0;
|
||||
false -> length(ArgsOrAtom)
|
||||
end,
|
||||
|
||||
case (keyfind(import, Meta) == false) andalso
|
||||
elixir_dispatch:find_import(Meta, Name, Arity, S) of
|
||||
NewMeta = case (keyfind(import, Meta) == false) andalso
|
||||
elixir_dispatch:find_import(Meta, Name, Arity, E) of
|
||||
false ->
|
||||
AmbMeta =
|
||||
case (Arity == 1) andalso keyfind(ambiguous_op, Meta) of
|
||||
{ ambiguous_op, nil } -> keystore(ambiguous_op, Meta, Q#elixir_quote.context);
|
||||
_ -> Meta
|
||||
end,
|
||||
do_quote_tuple({ Name, AmbMeta, ArgsOrAtom }, Q, S);
|
||||
case (Arity == 1) andalso keyfind(ambiguous_op, Meta) of
|
||||
{ ambiguous_op, nil } -> keystore(ambiguous_op, Meta, Q#elixir_quote.context);
|
||||
_ -> Meta
|
||||
end;
|
||||
Receiver ->
|
||||
ImportMeta = keystore(import,
|
||||
keystore(context, Meta, Q#elixir_quote.context),
|
||||
Receiver),
|
||||
do_quote_tuple({ Name, ImportMeta, ArgsOrAtom }, Q, S)
|
||||
end;
|
||||
keystore(import, keystore(context, Meta, Q#elixir_quote.context), Receiver)
|
||||
end,
|
||||
|
||||
do_quote({ _, _, _ } = Tuple, #elixir_quote{escape=false} = Q, S) ->
|
||||
do_quote_tuple(Tuple, Q, S);
|
||||
Annotated = annotate({ Name, NewMeta, ArgsOrAtom }, Q#elixir_quote.context, file(E, Q)),
|
||||
do_quote_tuple(Annotated, Q, E);
|
||||
|
||||
do_quote({ _, _, _ } = Tuple, #elixir_quote{escape=false} = Q, E) ->
|
||||
Annotated = annotate(Tuple, Q#elixir_quote.context, file(E, Q)),
|
||||
do_quote_tuple(Annotated, Q, E);
|
||||
|
||||
%% Literals
|
||||
|
||||
do_quote({ Left, Right }, #elixir_quote{unquote=true} = Q, S) when
|
||||
do_quote({ Left, Right }, #elixir_quote{unquote=true} = Q, E) 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, E);
|
||||
|
||||
do_quote({ Left, Right }, Q, S) ->
|
||||
{ TLeft, LQ } = do_quote(Left, Q, S),
|
||||
{ TRight, RQ } = do_quote(Right, LQ, S),
|
||||
do_quote({ Left, Right }, Q, E) ->
|
||||
{ TLeft, LQ } = do_quote(Left, Q, E),
|
||||
{ TRight, RQ } = do_quote(Right, LQ, E),
|
||||
{ { TLeft, TRight }, RQ };
|
||||
|
||||
do_quote(Tuple, #elixir_quote{escape=true} = Q, S) when is_tuple(Tuple) ->
|
||||
{ TT, TQ } = do_quote(tuple_to_list(Tuple), Q, S),
|
||||
do_quote(Tuple, #elixir_quote{escape=true} = Q, E) when is_tuple(Tuple) ->
|
||||
{ TT, TQ } = do_quote(tuple_to_list(Tuple), Q, E),
|
||||
{ { '{}', [], TT }, TQ };
|
||||
|
||||
do_quote(List, #elixir_quote{escape=true} = Q, S) when is_list(List) ->
|
||||
do_quote(List, #elixir_quote{escape=true} = Q, E) when is_list(List) ->
|
||||
% The improper case is pretty inefficient, but improper lists are hopefully
|
||||
% rare.
|
||||
case reverse_improper(List) of
|
||||
{ L } -> do_splice(L, Q, S);
|
||||
{ L } -> do_splice(L, Q, E);
|
||||
{ L, R } ->
|
||||
{ TL, QL } = do_splice(L, Q, S, [], []),
|
||||
{ TR, QR } = do_quote(R, QL, S),
|
||||
{ TL, QL } = do_splice(L, Q, E, [], []),
|
||||
{ TR, QR } = do_quote(R, QL, E),
|
||||
{ update_last(TL, fun(X) -> { '|', [], [X, TR] } end), QR }
|
||||
end;
|
||||
do_quote(List, Q, S) when is_list(List) ->
|
||||
do_splice(lists:reverse(List), Q, S);
|
||||
do_quote(List, Q, E) when is_list(List) ->
|
||||
do_splice(lists:reverse(List), Q, E);
|
||||
|
||||
do_quote(Other, Q, _) ->
|
||||
{ Other, Q }.
|
||||
|
||||
%% Quote helpers
|
||||
|
||||
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, TAll }, TQ }.
|
||||
do_quote_call(Left, Meta, Expr, Args, Q, E) ->
|
||||
All = [meta(Meta, Q), Left, { unquote, Meta, [Expr] }, Args,
|
||||
Q#elixir_quote.context, file(E, Q)],
|
||||
{ TAll, TQ } = lists:mapfoldl(fun(X, Acc) -> do_quote(X, Acc, E) end, Q, All),
|
||||
{ { { '.', Meta, [elixir_quote, dot] }, Meta, TAll }, TQ }.
|
||||
|
||||
do_quote_fa(Target, Meta, Args, F, A, Q, S) ->
|
||||
do_quote_fa(Target, Meta, Args, F, A, Q, E) ->
|
||||
NewMeta =
|
||||
case (keyfind(import_fa, Meta) == false) andalso
|
||||
elixir_dispatch:find_import(Meta, F, A, S) of
|
||||
elixir_dispatch:find_import(Meta, F, A, E) of
|
||||
false -> Meta;
|
||||
Receiver -> keystore(import_fa, Meta, { Receiver, Q#elixir_quote.context })
|
||||
end,
|
||||
do_quote_tuple({ Target, NewMeta, Args }, Q, S).
|
||||
do_quote_tuple({ Target, NewMeta, Args }, Q, E).
|
||||
|
||||
do_quote_tuple({ Left, Meta, Right }, Q, S) ->
|
||||
{ TLeft, LQ } = do_quote(Left, Q, S),
|
||||
{ TRight, RQ } = do_quote(Right, LQ, S),
|
||||
do_quote_tuple({ Left, Meta, Right }, Q, E) ->
|
||||
{ TLeft, LQ } = do_quote(Left, Q, E),
|
||||
{ TRight, RQ } = do_quote(Right, LQ, E),
|
||||
{ { '{}', [], [TLeft, meta(Meta, Q), TRight] }, RQ }.
|
||||
|
||||
meta(Meta, #elixir_quote{line=keep}) ->
|
||||
file(#elixir_env{file=File}, #elixir_quote{keep=true}) -> File;
|
||||
file(_, _) -> nil.
|
||||
|
||||
meta(Meta, #elixir_quote{keep=true}) ->
|
||||
[case KV of { line, V } -> { keep, V }; _ -> KV end || KV <- Meta];
|
||||
meta(Meta, #elixir_quote{line=true}) ->
|
||||
Meta;
|
||||
meta(Meta, #elixir_quote{line=nil}) ->
|
||||
meta(Meta, #elixir_quote{line=false}) ->
|
||||
keydelete(line, Meta);
|
||||
meta(Meta, #elixir_quote{line=Line}) ->
|
||||
keystore(line, Meta, Line).
|
||||
|
||||
reverse_improper(L) -> reverse_improper(L, []).
|
||||
|
||||
reverse_improper([], Acc) -> { Acc };
|
||||
reverse_improper([H|T], Acc) when is_list(T) -> reverse_improper(T, [H|Acc]);
|
||||
reverse_improper([H|T], Acc) -> { [H|Acc], T }.
|
||||
@@ -299,35 +307,46 @@ keyfind(Key, Meta) ->
|
||||
lists:keyfind(Key, 1, Meta).
|
||||
keydelete(Key, Meta) ->
|
||||
lists:keydelete(Key, 1, Meta).
|
||||
keystore(_Key, Meta, nil) ->
|
||||
Meta;
|
||||
keystore(Key, Meta, Value) ->
|
||||
lists:keystore(Key, 1, Meta, { Key, Value }).
|
||||
keyreplace(Key, Meta, { Key, _V }) ->
|
||||
Meta;
|
||||
keyreplace(Key, Meta, Tuple) ->
|
||||
lists:keyreplace(Key, 1, Meta, Tuple).
|
||||
keynew(Key, Meta, Value) ->
|
||||
case keyfind(Key, Meta) of
|
||||
{ Key, _ } -> Meta;
|
||||
_ -> keystore(Key, Meta, Value)
|
||||
end.
|
||||
|
||||
%% Quote splicing
|
||||
|
||||
do_splice([{ '|', Meta, [{ unquote_splicing, _, [Left] }, Right] }|T], #elixir_quote{unquote=true} = Q, S) ->
|
||||
do_splice([{ '|', Meta, [{ unquote_splicing, _, [Left] }, Right] }|T], #elixir_quote{unquote=true} = Q, E) ->
|
||||
%% Process the remaining entries on the list.
|
||||
%% For [1, 2, 3, unquote_splicing(arg)|tail], this will quote
|
||||
%% 1, 2 and 3, which could even be unquotes.
|
||||
{ TT, QT } = do_splice(T, Q, S, [], []),
|
||||
{ TR, QR } = do_quote(Right, QT, S),
|
||||
{ do_runtime_join(Meta, tail_join, [Left, TR, TT]), QR#elixir_quote{unquoted=true} };
|
||||
{ TT, QT } = do_splice(T, Q, E, [], []),
|
||||
{ TR, QR } = do_quote(Right, QT, E),
|
||||
{ do_runtime_list(Meta, tail_list, [Left, TR, TT]), QR#elixir_quote{unquoted=true} };
|
||||
|
||||
do_splice(List, Q, S) ->
|
||||
do_splice(List, Q, S, [], []).
|
||||
do_splice(List, Q, E) ->
|
||||
do_splice(List, Q, E, [], []).
|
||||
|
||||
do_splice([{ unquote_splicing, Meta, [Expr] }|T], #elixir_quote{unquote=true} = Q, S, Buffer, Acc) ->
|
||||
do_splice(T, Q#elixir_quote{unquoted=true}, S, [], do_runtime_join(Meta, join, [Expr, do_join(Buffer, Acc)]));
|
||||
do_splice([{ unquote_splicing, Meta, [Expr] }|T], #elixir_quote{unquote=true} = Q, E, Buffer, Acc) ->
|
||||
do_splice(T, Q#elixir_quote{unquoted=true}, E, [], do_runtime_list(Meta, list, [Expr, do_join(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([H|T], Q, E, Buffer, Acc) ->
|
||||
{ TH, TQ } = do_quote(H, Q, E),
|
||||
do_splice(T, TQ, E, [TH|Buffer], Acc);
|
||||
|
||||
do_splice([], Q, _S, Buffer, Acc) ->
|
||||
do_splice([], Q, _E, Buffer, Acc) ->
|
||||
{ do_join(Buffer, Acc), Q }.
|
||||
|
||||
do_join(Left, []) -> Left;
|
||||
do_join([], Right) -> Right;
|
||||
do_join(Left, Right) -> { { '.', [], ['Elixir.Kernel', '++'] }, [], [Left, Right] }.
|
||||
do_join(Left, Right) -> { { '.', [], [erlang, '++'] }, [], [Left, Right] }.
|
||||
|
||||
do_runtime_join(Meta, Fun, Args) ->
|
||||
do_runtime_list(Meta, Fun, Args) ->
|
||||
{ { '.', Meta, [elixir_quote, Fun] }, Meta, Args }.
|
||||
|
||||
+74
-193
@@ -1,255 +1,136 @@
|
||||
%% Convenience functions used to manipulate scope and its variables.
|
||||
-module(elixir_scope).
|
||||
-export([translate_var/5,
|
||||
build_erl_var/2, build_ex_var/2,
|
||||
serialize/1, deserialize/1, to_erl/1,
|
||||
serialize_with_vars/2, deserialize_with_vars/2,
|
||||
to_erl_env/1, to_ex_env/1,
|
||||
load_binding/3, dump_binding/2,
|
||||
umergev/2, umergec/2, umergea/2, merge_clause_vars/2
|
||||
-export([translate_var/4, build_var/2,
|
||||
load_binding/2, dump_binding/2,
|
||||
mergev/2, mergec/2, mergef/2,
|
||||
merge_vars/2, merge_opt_vars/2
|
||||
]).
|
||||
-include("elixir.hrl").
|
||||
|
||||
%% VAR HANDLING
|
||||
|
||||
translate_var(Meta, Name, Kind, S, Callback) when is_atom(Kind); is_integer(Kind) ->
|
||||
Line = ?line(Meta),
|
||||
Vars = S#elixir_scope.vars,
|
||||
Temp = lists:keyfind(temp, 1, Meta) == { temp, true },
|
||||
translate_var(Meta, Name, Kind, S) when is_atom(Kind); is_integer(Kind) ->
|
||||
Line = ?line(Meta),
|
||||
Tuple = { Name, Kind },
|
||||
Vars = S#elixir_scope.vars,
|
||||
|
||||
case Name of
|
||||
'_' ->
|
||||
{ { var, Line, Name }, S };
|
||||
_ ->
|
||||
case S#elixir_scope.context of
|
||||
match ->
|
||||
TempVars = S#elixir_scope.temp_vars,
|
||||
case { orddict:is_key(Tuple, Vars), is_list(TempVars) andalso ordsets:is_element(Tuple, TempVars) } of
|
||||
{ true, true } ->
|
||||
{ { var, Line, orddict:fetch(Tuple, Vars) }, S };
|
||||
{ Else, _ } ->
|
||||
{ NewVar, NS } = if
|
||||
Kind /= nil -> build_erl_var(Line, S);
|
||||
Else -> build_erl_var(Line, Name, Name, S);
|
||||
S#elixir_scope.noname -> build_erl_var(Line, Name, Name, S);
|
||||
true -> { { var, Line, Name }, S }
|
||||
end,
|
||||
RealName = element(3, NewVar),
|
||||
ClauseVars = S#elixir_scope.clause_vars,
|
||||
{ NewVar, NS#elixir_scope{
|
||||
vars=orddict:store(Tuple, RealName, Vars),
|
||||
temp_vars=if
|
||||
TempVars == nil -> TempVars;
|
||||
true -> ordsets:add_element(Tuple, TempVars)
|
||||
end,
|
||||
clause_vars=if
|
||||
ClauseVars == nil; Temp == true -> ClauseVars;
|
||||
true -> orddict:store(Tuple, RealName, ClauseVars)
|
||||
end
|
||||
} }
|
||||
end;
|
||||
_ ->
|
||||
case orddict:find({ Name, Kind }, Vars) of
|
||||
{ ok, VarName } -> { { var, Line, VarName }, S };
|
||||
error -> Callback()
|
||||
end
|
||||
end
|
||||
end.
|
||||
|
||||
% Handle variables translation
|
||||
|
||||
build_ex_var(Line, S) -> build_ex_var(Line, '', "_", S).
|
||||
build_erl_var(Line, S) -> build_erl_var(Line, '', "_", S).
|
||||
|
||||
build_var_counter(Key, #elixir_scope{counter=Counter} = S) ->
|
||||
New = orddict:update_counter(Key, 1, Counter),
|
||||
{ orddict:fetch(Key, New), S#elixir_scope{counter=New} }.
|
||||
|
||||
build_erl_var(Line, Key, Name, S) when is_integer(Line) ->
|
||||
{ Counter, NS } = build_var_counter(Key, S),
|
||||
Var = { var, Line, ?atom_concat([Name, "@", Counter]) },
|
||||
{ Var, NS }.
|
||||
|
||||
build_ex_var(Line, Key, Name, S) when is_integer(Line) ->
|
||||
Context = case S#elixir_scope.module of
|
||||
nil -> 'Elixir';
|
||||
Mod -> Mod
|
||||
case orddict:find({ Name, Kind }, Vars) of
|
||||
{ ok, { Current, _ } } -> Exists = true;
|
||||
error -> Current = nil, Exists = false
|
||||
end,
|
||||
|
||||
{ Counter, NS } = build_var_counter(Key, S),
|
||||
Var = { ?atom_concat([Name, "@", Counter]), [{line,Line}], Context },
|
||||
{ Var, NS }.
|
||||
case S#elixir_scope.context of
|
||||
match ->
|
||||
MatchVars = S#elixir_scope.match_vars,
|
||||
|
||||
%% Macro.Env <-> #elixir_scope conversion
|
||||
case Exists andalso ordsets:is_element(Tuple, MatchVars) of
|
||||
true ->
|
||||
{ { var, Line, Current }, S };
|
||||
false ->
|
||||
%% If the variable is not exported, we use a counter name.
|
||||
%% The same if the variable already exists or we are in a
|
||||
%% noname context.
|
||||
Private = (lists:keyfind(export, 1, Meta) == { export, false }),
|
||||
|
||||
to_erl_env({ 'Elixir.Macro.Env', Module, File, _Line, Function, Aliases, Context,
|
||||
Requires, Functions, Macros, ContextModules, MacroAliases, _Vars, Lexical }) ->
|
||||
#elixir_scope{module=Module,file=File,
|
||||
function=Function,aliases=Aliases,context=Context,
|
||||
requires=Requires,macros=Macros,functions=Functions,
|
||||
context_modules=ContextModules,macro_aliases=MacroAliases,
|
||||
lexical_tracker=Lexical}.
|
||||
{ NewVar, Counter, NS } =
|
||||
if
|
||||
Kind /= nil ->
|
||||
build_var('_', S);
|
||||
Private orelse Exists orelse S#elixir_scope.noname ->
|
||||
build_var(Name, S);
|
||||
true ->
|
||||
{ Name, 0, S }
|
||||
end,
|
||||
|
||||
to_ex_env({ Line, #elixir_scope{module=Module,file=File,
|
||||
function=Function,aliases=Aliases,context=Context,
|
||||
requires=Requires,macros=Macros,functions=Functions,
|
||||
context_modules=ContextModules,macro_aliases=MacroAliases,
|
||||
vars=Vars,lexical_tracker=Lexical} }) when is_integer(Line) ->
|
||||
{ 'Elixir.Macro.Env', Module, File, Line, Function, Aliases,
|
||||
Context, Requires, Functions, Macros, ContextModules, MacroAliases,
|
||||
list_vars(Vars), Lexical }.
|
||||
FS = NS#elixir_scope{
|
||||
vars=orddict:store(Tuple, { NewVar, Counter }, Vars),
|
||||
match_vars=ordsets:add_element(Tuple, MatchVars),
|
||||
export_vars=case S#elixir_scope.export_vars of
|
||||
EV when Private; EV == nil -> EV;
|
||||
EV -> orddict:store(Tuple, { NewVar, Counter }, EV)
|
||||
end
|
||||
},
|
||||
|
||||
list_vars(Vars) -> [Pair || { { _, K } = Pair, _ } <- Vars, is_atom(K)].
|
||||
{ { var, Line, NewVar }, FS }
|
||||
end;
|
||||
_ when Exists ->
|
||||
{ { var, Line, Current }, S }
|
||||
end.
|
||||
|
||||
%% SERIALIZATION
|
||||
|
||||
%% When serializing scopes, we support serialization of pids.
|
||||
to_erl(Structure) ->
|
||||
elixir_utils:elixir_to_erl(Structure, fun
|
||||
(X) when is_pid(X) ->
|
||||
?wrap_call(0, erlang, binary_to_term, [elixir_utils:elixir_to_erl(term_to_binary(X))]);
|
||||
(Other) ->
|
||||
error({ badarg, Other })
|
||||
end).
|
||||
|
||||
serialize(S) ->
|
||||
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.context_modules, S#elixir_scope.lexical_tracker }).
|
||||
|
||||
serialize_with_vars(Line, S) when is_integer(Line) ->
|
||||
{ Vars, _ } = orddict:fold(fun({ Key, Kind }, Value, { Acc, Counter }) ->
|
||||
KindKey = if
|
||||
is_atom(Kind) -> atom;
|
||||
is_integer(Kind) -> integer
|
||||
end,
|
||||
|
||||
{ { cons, Line, { tuple, Line, [
|
||||
{ atom, Line, Key },
|
||||
{ KindKey, Line, Kind },
|
||||
{ atom, Line, ?atom_concat(["_@", Counter]) },
|
||||
{ var, Line, Value }
|
||||
] }, Acc }, Counter + 1 }
|
||||
end, { { nil, Line }, 0 }, S#elixir_scope.vars),
|
||||
{ serialize(S), Vars }.
|
||||
|
||||
% Fill in the scope with the variables serialization set in serialize_scope.
|
||||
|
||||
deserialize(Tuple) -> deserialize_with_vars(Tuple, []).
|
||||
|
||||
deserialize_with_vars({ File, Functions, Requires, Macros, Aliases, MacroFunctions,
|
||||
MacroMacros, MacroAliases, FileModules, LexicalTracker }, Vars) ->
|
||||
#elixir_scope{
|
||||
file=File,
|
||||
functions=Functions,
|
||||
requires=Requires,
|
||||
macros=Macros,
|
||||
aliases=Aliases,
|
||||
macro_functions=MacroFunctions,
|
||||
macro_macros=MacroMacros,
|
||||
macro_aliases=MacroAliases,
|
||||
context_modules=FileModules,
|
||||
vars=orddict:from_list(Vars),
|
||||
lexical_tracker=LexicalTracker,
|
||||
counter=[{'',length(Vars)}]
|
||||
}.
|
||||
build_var(Key, S) ->
|
||||
New = orddict:update_counter(Key, 1, S#elixir_scope.counter),
|
||||
Cnt = orddict:fetch(Key, New),
|
||||
{ ?atom_concat([Key, "@", Cnt]), Cnt, S#elixir_scope{counter=New} }.
|
||||
|
||||
%% SCOPE MERGING
|
||||
|
||||
%% Receives two scopes and return a new scope based on the second
|
||||
%% with their variables merged.
|
||||
%% Receives two scopes and return a new scope based on
|
||||
%% the second with their variables merged.
|
||||
|
||||
umergev(S1, S2) ->
|
||||
V1 = S1#elixir_scope.vars,
|
||||
V2 = S2#elixir_scope.vars,
|
||||
C1 = S1#elixir_scope.clause_vars,
|
||||
C2 = S2#elixir_scope.clause_vars,
|
||||
mergev(S1, S2) ->
|
||||
S2#elixir_scope{
|
||||
vars=merge_vars(V1, V2),
|
||||
clause_vars=merge_clause_vars(C1, C2)
|
||||
vars=merge_vars(S1#elixir_scope.vars, S2#elixir_scope.vars),
|
||||
export_vars=merge_opt_vars(S1#elixir_scope.export_vars, S2#elixir_scope.export_vars)
|
||||
}.
|
||||
|
||||
%% Receives two scopes and return the first scope with
|
||||
%% counters and flags from the later.
|
||||
|
||||
umergec(S1, S2) ->
|
||||
mergec(S1, S2) ->
|
||||
S1#elixir_scope{
|
||||
counter=S2#elixir_scope.counter,
|
||||
macro_counter=S2#elixir_scope.macro_counter,
|
||||
extra_guards=S2#elixir_scope.extra_guards,
|
||||
super=S2#elixir_scope.super,
|
||||
caller=S2#elixir_scope.caller
|
||||
}.
|
||||
|
||||
%% Receives two scopes and return the later scope
|
||||
%% keeping the variables from the first (counters,
|
||||
%% imports and everything else are passed forward).
|
||||
%% Similar to mergec but does not merge the user vars counter.
|
||||
|
||||
umergea(S1, S2) ->
|
||||
S2#elixir_scope{
|
||||
vars=S1#elixir_scope.vars,
|
||||
temp_vars=S1#elixir_scope.temp_vars,
|
||||
clause_vars=S1#elixir_scope.clause_vars
|
||||
mergef(S1, S2) ->
|
||||
S1#elixir_scope{
|
||||
super=S2#elixir_scope.super,
|
||||
caller=S2#elixir_scope.caller
|
||||
}.
|
||||
|
||||
% Merge variables trying to find the most recently created.
|
||||
%% Mergers.
|
||||
|
||||
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) ->
|
||||
merge_opt_vars(nil, _C2) -> nil;
|
||||
merge_opt_vars(_C1, nil) -> nil;
|
||||
merge_opt_vars(C, C) -> C;
|
||||
merge_opt_vars(C1, C2) ->
|
||||
orddict:merge(fun var_merger/3, C1, C2).
|
||||
|
||||
var_merger({ Var, _ }, Var, K2) -> K2;
|
||||
var_merger({ Var, _ }, K1, Var) -> K1;
|
||||
var_merger(_Var, K1, K2) ->
|
||||
V1 = var_number(atom_to_list(K1), []),
|
||||
V2 = var_number(atom_to_list(K2), []),
|
||||
case V1 > V2 of
|
||||
true -> K1;
|
||||
false -> K2
|
||||
end.
|
||||
|
||||
var_number([$@|T], _Acc) -> var_number(T, []);
|
||||
var_number([H|T], Acc) -> var_number(T, [H|Acc]);
|
||||
var_number([], Acc) -> list_to_integer(lists:reverse(Acc)).
|
||||
var_merger(_Var, { _, V1 } = K1, { _, V2 }) when V1 > V2 -> K1;
|
||||
var_merger(_Var, _K1, K2) -> K2.
|
||||
|
||||
%% BINDINGS
|
||||
|
||||
load_binding(Binding, Scope, Module) ->
|
||||
{ NewBinding, NewVars, NewCounter } = load_binding(Binding, [], [], 0, Module),
|
||||
load_binding(Binding, Scope) ->
|
||||
{ NewBinding, NewVars, NewCounter } = load_binding(Binding, [], [], 0),
|
||||
{ NewBinding, Scope#elixir_scope{
|
||||
vars=NewVars,
|
||||
temp_vars=[],
|
||||
clause_vars=nil,
|
||||
counter=[{'',NewCounter}]
|
||||
counter=[{'_',NewCounter}]
|
||||
} }.
|
||||
|
||||
load_binding([{'_@MODULE',Value}|T], Binding, Vars, Counter, _Module) ->
|
||||
load_binding(T, Binding, Vars, Counter, Value);
|
||||
load_binding([{Key,Value}|T], Binding, Vars, Counter, Module) ->
|
||||
load_binding([{Key,Value}|T], Binding, Vars, Counter) ->
|
||||
Actual = case Key of
|
||||
{ _Name, _Kind } -> Key;
|
||||
Name when is_atom(Name) -> { Name, nil }
|
||||
end,
|
||||
InternalName = ?atom_concat(["_@", Counter]),
|
||||
load_binding(T,
|
||||
[{InternalName,Value}|Binding],
|
||||
orddict:store(Actual, InternalName, Vars),
|
||||
Counter + 1, Module);
|
||||
load_binding([], Binding, Vars, Counter, Module) ->
|
||||
{ lists:reverse([{'_@MODULE',Module}|Binding]), Vars, Counter }.
|
||||
orddict:store(InternalName, Value, Binding),
|
||||
orddict:store(Actual, { InternalName, 0 }, Vars), Counter + 1);
|
||||
load_binding([], Binding, Vars, Counter) ->
|
||||
{ Binding, Vars, Counter }.
|
||||
|
||||
dump_binding(Binding, #elixir_scope{vars=Vars}) ->
|
||||
dump_binding(Vars, Binding, []).
|
||||
|
||||
dump_binding([{{Var,Kind}=Key,InternalName}|T], Binding, Acc) when is_atom(Kind) ->
|
||||
dump_binding([{{ Var, Kind } = Key, { InternalName,_ }}|T], Binding, Acc) when is_atom(Kind) ->
|
||||
Actual = case Kind of
|
||||
nil -> Var;
|
||||
_ -> Key
|
||||
|
||||
@@ -1,21 +1,31 @@
|
||||
-module(elixir_sup).
|
||||
-behaviour(supervisor).
|
||||
-export([init/1,start_link/1]).
|
||||
-export([init/1, start_link/0]).
|
||||
|
||||
start_link(Args) ->
|
||||
supervisor:start_link({local, ?MODULE}, ?MODULE, Args).
|
||||
start_link() ->
|
||||
supervisor:start_link({local, ?MODULE}, ?MODULE, ok).
|
||||
|
||||
init(Args) ->
|
||||
Supervisors = [
|
||||
init(ok) ->
|
||||
Workers = [
|
||||
{
|
||||
elixir_code_server_sup,
|
||||
{ elixir_code_server, start_link, Args },
|
||||
elixir_code_server,
|
||||
{ elixir_code_server, start_link, [] },
|
||||
|
||||
permanent, % Restart = permanent | transient | temporary
|
||||
2000, % Shutdown = brutal_kill | int() >= 0 | infinity
|
||||
worker, % Type = worker | supervisor
|
||||
[elixir_code_server] % Modules = [Module] | dynamic
|
||||
},
|
||||
|
||||
{
|
||||
elixir_counter,
|
||||
{ elixir_counter, start_link, [] },
|
||||
|
||||
permanent, % Restart = permanent | transient | temporary
|
||||
2000, % Shutdown = brutal_kill | int() >= 0 | infinity
|
||||
worker, % Type = worker | supervisor
|
||||
[elixir_counter] % Modules = [Module] | dynamic
|
||||
}
|
||||
],
|
||||
|
||||
{ok, {{one_for_one, 3, 10}, Supervisors}}.
|
||||
{ok, {{one_for_one, 3, 10}, Workers}}.
|
||||
|
||||
@@ -4,8 +4,7 @@
|
||||
-import(elixir_interpolation, [unescape_chars/1, unescape_tokens/1]).
|
||||
|
||||
-define(container(T1, T2),
|
||||
T1 == ${, T2 == $};
|
||||
T1 == $[, T2 == $]
|
||||
T1 == ${, T2 == $}
|
||||
).
|
||||
|
||||
-define(at_op(T),
|
||||
@@ -25,10 +24,9 @@
|
||||
-define(two_op(T1, T2),
|
||||
T1 == $+, T2 == $+;
|
||||
T1 == $-, T2 == $-;
|
||||
T1 == $*, T2 == $*).
|
||||
|
||||
-define(than_op(T1, T2),
|
||||
T1 == $<, T2 == $>).
|
||||
T1 == $*, T2 == $*;
|
||||
T1 == $<, T2 == $>;
|
||||
T1 == $., T2 == $.).
|
||||
|
||||
-define(mult_op(T),
|
||||
T == $* orelse T == $/).
|
||||
@@ -36,9 +34,6 @@
|
||||
-define(dual_op(T),
|
||||
T == $+ orelse T == $-).
|
||||
|
||||
-define(range_op(T1, T2),
|
||||
T1 == $., T2 == $.).
|
||||
|
||||
-define(arrow_op3(T1, T2, T3),
|
||||
T1 == $<, T2 == $<, T3 == $<;
|
||||
T1 == $>, T2 == $>, T3 == $>).
|
||||
@@ -77,10 +72,10 @@
|
||||
-define(match_op(T),
|
||||
T == $=).
|
||||
|
||||
-define(tail_op(T),
|
||||
-define(in_match_op(T),
|
||||
T == $|).
|
||||
|
||||
-define(default_op(T1, T2),
|
||||
-define(in_match_op2(T1, T2),
|
||||
T1 == $/, T2 == $/).
|
||||
|
||||
-define(stab_op(T1, T2),
|
||||
@@ -228,13 +223,6 @@ tokenize([$.,T|Tail], Line, Scope, Tokens) when ?is_space(T) ->
|
||||
[_|Rest] -> tokenize([$.|Rest], Line, Scope, Tokens)
|
||||
end;
|
||||
|
||||
% ## Containers
|
||||
tokenize(".<<>>" ++ Rest, Line, Scope, Tokens) ->
|
||||
handle_call_identifier(Rest, Line, '<<>>', Scope, Tokens);
|
||||
|
||||
tokenize([$.,T1,T2|Rest], Line, Scope, Tokens) when ?container(T1, T2) ->
|
||||
handle_call_identifier(Rest, Line, list_to_atom([T1, T2]), Scope, Tokens);
|
||||
|
||||
% ## Three Token Operators
|
||||
tokenize([$.,T1,T2,T3|Rest], Line, Scope, Tokens) when
|
||||
?unary_op3(T1, T2, T3); ?comp_op3(T1, T2, T3); ?and_op3(T1, T2, T3); ?or_op3(T1, T2, T3);
|
||||
@@ -244,14 +232,13 @@ tokenize([$.,T1,T2,T3|Rest], Line, Scope, Tokens) when
|
||||
% ## Two Token Operators
|
||||
tokenize([$.,T1,T2|Rest], Line, Scope, Tokens) when
|
||||
?comp_op2(T1, T2); ?and_op(T1, T2); ?or_op(T1, T2); ?arrow_op(T1, T2);
|
||||
?range_op(T1, T2); ?than_op(T1, T2); ?default_op(T1, T2); ?two_op(T1, T2);
|
||||
?stab_op(T1, T2); ?type_op(T1, T2) ->
|
||||
?in_match_op2(T1, T2); ?two_op(T1, T2); ?stab_op(T1, T2); ?type_op(T1, T2) ->
|
||||
handle_call_identifier(Rest, Line, list_to_atom([T1, T2]), Scope, Tokens);
|
||||
|
||||
% ## Single Token Operators
|
||||
tokenize([$.,T|Rest], Line, Scope, Tokens) when
|
||||
?at_op(T); ?unary_op(T); ?dual_op(T); ?mult_op(T); ?comp_op(T);
|
||||
?match_op(T); ?tail_op(T) ->
|
||||
?match_op(T); ?in_match_op(T) ->
|
||||
handle_call_identifier(Rest, Line, list_to_atom([T]), Scope, Tokens);
|
||||
|
||||
% Dot call
|
||||
@@ -331,14 +318,13 @@ tokenize([$:,T1,T2,T3|Rest], Line, Scope, Tokens) when
|
||||
% ## Two Token Operators
|
||||
tokenize([$:,T1,T2|Rest], Line, Scope, Tokens) when
|
||||
?comp_op2(T1, T2); ?and_op(T1, T2); ?or_op(T1, T2); ?arrow_op(T1, T2);
|
||||
?range_op(T1, T2); ?than_op(T1, T2); ?default_op(T1, T2); ?two_op(T1, T2);
|
||||
?stab_op(T1, T2); ?type_op(T1, T2) ->
|
||||
?in_match_op2(T1, T2); ?two_op(T1, T2); ?stab_op(T1, T2); ?type_op(T1, T2) ->
|
||||
tokenize(Rest, Line, Scope, [{ atom, Line, list_to_atom([T1,T2]) }|Tokens]);
|
||||
|
||||
% ## Single Token Operators
|
||||
tokenize([$:,T|Rest], Line, Scope, Tokens) when
|
||||
?at_op(T); ?unary_op(T); ?dual_op(T); ?mult_op(T); ?comp_op(T);
|
||||
?match_op(T); ?tail_op(T); T == $. ->
|
||||
?match_op(T); ?in_match_op(T); T == $. ->
|
||||
tokenize(Rest, Line, Scope, [{ atom, Line, list_to_atom([T]) }|Tokens]);
|
||||
|
||||
% End of line
|
||||
@@ -396,12 +382,6 @@ tokenize([T|Rest], Line, Scope, Tokens) when T == $(;
|
||||
tokenize([T1,T2|Rest], Line, Scope, Tokens) when ?two_op(T1, T2) ->
|
||||
handle_op(Rest, Line, two_op, list_to_atom([T1, T2]), Scope, Tokens);
|
||||
|
||||
tokenize([T1,T2|Rest], Line, Scope, Tokens) when ?than_op(T1, T2) ->
|
||||
handle_op(Rest, Line, than_op, list_to_atom([T1, T2]), Scope, Tokens);
|
||||
|
||||
tokenize([T1,T2|Rest], Line, Scope, Tokens) when ?range_op(T1, T2) ->
|
||||
handle_op(Rest, Line, range_op, list_to_atom([T1, T2]), Scope, Tokens);
|
||||
|
||||
tokenize([T1,T2|Rest], Line, Scope, Tokens) when ?arrow_op(T1, T2) ->
|
||||
handle_op(Rest, Line, arrow_op, list_to_atom([T1, T2]), Scope, Tokens);
|
||||
|
||||
@@ -414,8 +394,8 @@ tokenize([T1,T2|Rest], Line, Scope, Tokens) when ?and_op(T1, T2) ->
|
||||
tokenize([T1,T2|Rest], Line, Scope, Tokens) when ?or_op(T1, T2) ->
|
||||
handle_op(Rest, Line, or_op, list_to_atom([T1, T2]), Scope, Tokens);
|
||||
|
||||
tokenize([T1,T2|Rest], Line, Scope, Tokens) when ?default_op(T1, T2) ->
|
||||
handle_op(Rest, Line, default_op, list_to_atom([T1, T2]), Scope, Tokens);
|
||||
tokenize([T1,T2|Rest], Line, Scope, Tokens) when ?in_match_op2(T1, T2) ->
|
||||
handle_op(Rest, Line, in_match_op, list_to_atom([T1, T2]), Scope, Tokens);
|
||||
|
||||
tokenize([T1,T2|Rest], Line, Scope, Tokens) when ?stab_op(T1, T2) ->
|
||||
handle_op(Rest, Line, stab_op, list_to_atom([T1, T2]), Scope, Tokens);
|
||||
@@ -447,8 +427,8 @@ tokenize([T|Rest], Line, Scope, Tokens) when ?mult_op(T) ->
|
||||
tokenize([T|Rest], Line, Scope, Tokens) when ?match_op(T) ->
|
||||
handle_op(Rest, Line, match_op, list_to_atom([T]), Scope, Tokens);
|
||||
|
||||
tokenize([T|Rest], Line, Scope, Tokens) when ?tail_op(T) ->
|
||||
handle_op(Rest, Line, tail_op, list_to_atom([T]), Scope, Tokens);
|
||||
tokenize([T|Rest], Line, Scope, Tokens) when ?in_match_op(T) ->
|
||||
handle_op(Rest, Line, in_match_op, list_to_atom([T]), Scope, Tokens);
|
||||
|
||||
tokenize([$.|Rest], Line, Scope, Tokens) ->
|
||||
tokenize(Rest, Line, Scope, add_token_with_nl({ '.', Line }, Tokens));
|
||||
@@ -503,6 +483,7 @@ tokenize([Space, Sign, NotMarker|T], Line, Scope, [{ Identifier, _, _ } = H|Toke
|
||||
tokenize([T|Rest], Line, Scope, Tokens) when ?is_horizontal_space(T) ->
|
||||
tokenize(Rest, Line, Scope, Tokens);
|
||||
|
||||
%% TODO: This token is deprecated once continuable heredocs are removed
|
||||
tokenize([{line,Line}|Rest], _Line, Scope, Tokens) ->
|
||||
tokenize(Rest, Line, Scope, Tokens);
|
||||
|
||||
@@ -594,7 +575,7 @@ collect_modifiers(Rest, Buffer) ->
|
||||
%% Heredocs
|
||||
|
||||
extract_heredoc_with_interpolation(Line, Scope, Interpol, T, H) ->
|
||||
case extract_heredoc(Line, T, H) of
|
||||
case extract_heredoc(Line, T, H, Scope) of
|
||||
{ error, _ } = Error ->
|
||||
Error;
|
||||
{ Body, Rest } ->
|
||||
@@ -606,8 +587,8 @@ extract_heredoc_with_interpolation(Line, Scope, Interpol, T, H) ->
|
||||
end
|
||||
end.
|
||||
|
||||
extract_heredoc(Line0, Rest0, Marker) ->
|
||||
case extract_heredoc_line(Rest0, []) of
|
||||
extract_heredoc(Line0, Rest0, Marker, Scope) ->
|
||||
case extract_heredoc_header(Rest0, [], {Line0,Scope#elixir_tokenizer.file}) of
|
||||
{ ok, Extra, Rest1 } ->
|
||||
%% We prepend a new line so we can transparently remove
|
||||
%% spaces later. This new line is removed by calling `tl`
|
||||
@@ -615,42 +596,52 @@ extract_heredoc(Line0, Rest0, Marker) ->
|
||||
case extract_heredoc_body(Line0, Marker, [$\n|Rest1], []) of
|
||||
{ Line1, Body, Rest2, Spaces } ->
|
||||
{ tl(remove_heredoc_spaces(Body, Spaces)), merge_heredoc_extra(Line1, Extra, Rest2) };
|
||||
{ error, Line1 } ->
|
||||
heredoc_error(Line1, Line0, Marker)
|
||||
{ error, ErrorLine } ->
|
||||
Terminator = [Marker, Marker, Marker],
|
||||
Message = "missing terminator: ~ts (for heredoc starting at line ~B)",
|
||||
{ error, { ErrorLine, io_lib:format(Message, [Terminator, Line0]), [] } }
|
||||
end;
|
||||
{ error, eof } ->
|
||||
heredoc_error(Line0, Line0, Marker)
|
||||
error ->
|
||||
%% TODO: Test me once the deprecated continuable heredocs are removed
|
||||
Terminator = [Marker, Marker, Marker],
|
||||
Message = "heredoc start ~ts must be followed by a new line",
|
||||
{ error, { Line0, io_lib:format(Message, [Terminator]), [] } }
|
||||
end.
|
||||
|
||||
heredoc_error(ErrorLine, StartLine, Marker) ->
|
||||
Terminator = [Marker, Marker, Marker],
|
||||
Message = io_lib:format("missing terminator: ~ts (for heredoc starting at line ~B)", [Terminator, StartLine]),
|
||||
{ error, { ErrorLine, Message, [] } }.
|
||||
|
||||
%% Remove spaces from heredoc based on the position of the final quotes.
|
||||
|
||||
remove_heredoc_spaces(Body, 0) ->
|
||||
lists:reverse([0|Body]);
|
||||
|
||||
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, ?is_horizontal_space(H) ->
|
||||
remove_heredoc_spaces([Backtrack,$\n|T], Buffer, Spaces - 1, Original);
|
||||
|
||||
remove_heredoc_spaces([$\n=H|T], Buffer, _Spaces, Original) ->
|
||||
remove_heredoc_spaces(T, [H|Buffer], Original, Original);
|
||||
|
||||
remove_heredoc_spaces([H|T], Buffer, Spaces, Original) ->
|
||||
remove_heredoc_spaces(T, [H|Buffer], Spaces, Original);
|
||||
|
||||
remove_heredoc_spaces([], Buffer, _Spaces, _Original) ->
|
||||
Buffer.
|
||||
|
||||
%% Extract the heredoc header.
|
||||
|
||||
extract_heredoc_header("\r\n" ++ Rest, Buffer, _Scope) ->
|
||||
{ ok, [$\n|Buffer], Rest };
|
||||
extract_heredoc_header("\n" ++ Rest, Buffer, _Scope) ->
|
||||
{ ok, [$\n|Buffer], Rest };
|
||||
extract_heredoc_header([H|T], Buffer, {Line,File}) when not ?is_horizontal_space(H) ->
|
||||
elixir_errors:deprecation([{line,Line}], File, "continuable heredocs are deprecated, parsing will no longer continue on the same line as the heredoc starts"),
|
||||
extract_heredoc_header(T, [H|Buffer], nil);
|
||||
extract_heredoc_header([H|T], Buffer, _Scope) ->
|
||||
extract_heredoc_header(T, [H|Buffer], _Scope);
|
||||
extract_heredoc_header(_, _, _Scope) ->
|
||||
error.
|
||||
|
||||
%% Extract heredoc body. It returns the heredoc body (in reverse order),
|
||||
%% the remaining of the document and the number of spaces the heredoc
|
||||
%% is aligned.
|
||||
|
||||
%% TODO: This token is deprecated once continuable heredocs are removed
|
||||
extract_heredoc_body(_Line, Marker, [{line,NewLine}|Rest], Buffer) ->
|
||||
extract_heredoc_body(NewLine, Marker, Rest, Buffer);
|
||||
|
||||
@@ -659,7 +650,7 @@ extract_heredoc_body(Line, Marker, Rest, Buffer) ->
|
||||
{ ok, NewBuffer, NewRest } ->
|
||||
extract_heredoc_body(Line + 1, Marker, NewRest, NewBuffer);
|
||||
{ ok, NewBuffer, NewRest, Spaces } ->
|
||||
{ Line + 1, NewBuffer, NewRest, Spaces };
|
||||
{ Line, NewBuffer, NewRest, Spaces };
|
||||
{ error, eof } ->
|
||||
{ error, Line }
|
||||
end.
|
||||
@@ -668,13 +659,10 @@ extract_heredoc_body(Line, Marker, Rest, Buffer) ->
|
||||
|
||||
extract_heredoc_line("\r\n" ++ Rest, Buffer) ->
|
||||
{ ok, [$\n|Buffer], Rest };
|
||||
|
||||
extract_heredoc_line("\n" ++ Rest, Buffer) ->
|
||||
{ ok, [$\n|Buffer], Rest };
|
||||
|
||||
extract_heredoc_line([H|T], Buffer) ->
|
||||
extract_heredoc_line(T, [H|Buffer]);
|
||||
|
||||
extract_heredoc_line(_, _) ->
|
||||
{ error, eof }.
|
||||
|
||||
@@ -682,31 +670,19 @@ extract_heredoc_line(_, _) ->
|
||||
|
||||
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) ->
|
||||
case next_is_break(T) of
|
||||
false -> extract_heredoc_line(Rest, Buffer);
|
||||
Final -> { ok, Buffer, Final, Counter }
|
||||
end;
|
||||
|
||||
extract_heredoc_line(Marker, [Marker,Marker,Marker|T], Buffer, Counter) ->
|
||||
{ ok, Buffer, T, Counter };
|
||||
extract_heredoc_line(_Marker, Rest, Buffer, _Counter) ->
|
||||
extract_heredoc_line(Rest, Buffer).
|
||||
|
||||
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.
|
||||
|
||||
%% Merge heredoc extra by replying it on the buffer. It also adds
|
||||
%% a special { line, Line } token to force a line change along the way.
|
||||
|
||||
%% TODO: This token is deprecated once continuable heredocs are removed
|
||||
merge_heredoc_extra(Line, Extra, Buffer) ->
|
||||
merge_heredoc_extra(Extra, [{line,Line}|Buffer]).
|
||||
|
||||
merge_heredoc_extra([H|T], Buffer) ->
|
||||
merge_heredoc_extra(T, [H|Buffer]);
|
||||
|
||||
merge_heredoc_extra([], Buffer) ->
|
||||
Buffer.
|
||||
|
||||
@@ -952,8 +928,8 @@ keyword('or') -> or_op;
|
||||
keyword('xor') -> or_op;
|
||||
keyword('when') -> when_op;
|
||||
keyword('in') -> in_op;
|
||||
keyword('inlist') -> inc_op;
|
||||
keyword('inbits') -> inc_op;
|
||||
keyword('inlist') -> in_match_op;
|
||||
keyword('inbits') -> in_match_op;
|
||||
|
||||
% Block keywords
|
||||
keyword('after') -> block;
|
||||
|
||||
@@ -1,299 +1,125 @@
|
||||
%% Main entry point for translations. All macros that cannot be
|
||||
%% overriden are defined in this file.
|
||||
%% Translate Elixir quoted expressions to Erlang Abstract Format.
|
||||
%% Expects the tree to be expanded.
|
||||
-module(elixir_translator).
|
||||
-export([forms/4, 'forms!'/4]).
|
||||
-export([translate/2, translate_each/2, translate_arg/2,
|
||||
translate_args/2, translate_apply/7]).
|
||||
-import(elixir_scope, [umergev/2, umergec/2, umergea/2]).
|
||||
-import(elixir_errors, [syntax_error/3, syntax_error/4,
|
||||
compile_error/3, compile_error/4,
|
||||
assert_function_scope/3, assert_module_scope/3,
|
||||
assert_no_guard_scope/3, assert_no_match_or_guard_scope/3]).
|
||||
-export([translate_many/2, translate/2, translate_arg/3, translate_args/2]).
|
||||
-import(elixir_scope, [mergev/2, mergec/2, mergef/2]).
|
||||
-import(elixir_errors, [compile_error/3, compile_error/4]).
|
||||
-include("elixir.hrl").
|
||||
|
||||
forms(String, StartLine, File, Opts) ->
|
||||
case elixir_tokenizer:tokenize(String, StartLine, [{ file, File }|Opts]) of
|
||||
{ ok, _Line, Tokens } ->
|
||||
try elixir_parser:parse(Tokens) of
|
||||
{ ok, Forms } -> { ok, Forms };
|
||||
{ error, { Line, _, [Error, Token] } } -> { error, { Line, Error, Token } }
|
||||
catch
|
||||
{ error, { Line, _, [Error, Token] } } -> { error, { Line, Error, Token } }
|
||||
end;
|
||||
{ error, Reason, _Rest, _SoFar } -> { error, Reason }
|
||||
end.
|
||||
translate_many(Forms, S) ->
|
||||
lists:mapfoldl(fun translate/2, S, Forms).
|
||||
|
||||
'forms!'(String, StartLine, File, Opts) ->
|
||||
case forms(String, StartLine, File, Opts) of
|
||||
{ ok, Forms } ->
|
||||
Forms;
|
||||
{ error, { Line, Error, Token } } ->
|
||||
elixir_errors:parse_error(Line, File, Error, Token)
|
||||
end.
|
||||
%% =
|
||||
|
||||
translate(Forms, S) ->
|
||||
lists:mapfoldl(fun translate_each/2, S, Forms).
|
||||
|
||||
%% Assignment operator
|
||||
|
||||
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),
|
||||
translate({ '=', Meta, [Left, Right] }, S) ->
|
||||
{ TRight, SR } = translate(Right, S),
|
||||
{ TLeft, SL } = elixir_clauses:match(fun translate/2, Left, SR),
|
||||
{ { match, ?line(Meta), TLeft, TRight }, SL };
|
||||
|
||||
%% Containers
|
||||
|
||||
translate_each({ C, _, _ } = Original, S) when C == '{}'; C == '<<>>' ->
|
||||
elixir_literal:translate(Original, S);
|
||||
translate({ '{}', Meta, Args }, S) when is_list(Args) ->
|
||||
{ TArgs, SE } = translate_args(Args, S),
|
||||
{ { tuple, ?line(Meta), TArgs }, SE };
|
||||
|
||||
translate({ '<<>>', Meta, Args }, S) when is_list(Args) ->
|
||||
elixir_bitstring:translate(Meta, Args, S);
|
||||
|
||||
%% Blocks and scope rewriters
|
||||
|
||||
translate_each({ '__block__', Meta, [] }, S) ->
|
||||
{ { atom, ?line(Meta), nil }, S };
|
||||
|
||||
translate_each({ '__block__', _Line, [Arg] }, S) ->
|
||||
translate_each(Arg, S);
|
||||
|
||||
translate_each({ '__block__', Meta, Args }, S) when is_list(Args) ->
|
||||
{ TArgs, NS } = translate(Args, S),
|
||||
translate({ '__block__', Meta, Args }, S) when is_list(Args) ->
|
||||
{ TArgs, NS } = translate_many(Args, S),
|
||||
{ { block, ?line(Meta), TArgs }, NS };
|
||||
|
||||
translate_each({ '__scope__', _Meta, [[{file,File}],[{do,Expr}]] }, S) ->
|
||||
Old = S#elixir_scope.file,
|
||||
{ TExpr, TS } = translate_each(Expr, S#elixir_scope{file=File}),
|
||||
{ TExpr, TS#elixir_scope{file=Old} };
|
||||
|
||||
%% Erlang op
|
||||
|
||||
translate_each({ '__op__', Meta, [Op, Expr] }, S) when is_atom(Op) ->
|
||||
{ TExpr, NS } = translate_each(Expr, S),
|
||||
{ { op, ?line(Meta), convert_op(Op), TExpr }, NS };
|
||||
translate({ '__op__', Meta, [Op, Expr] }, S) when is_atom(Op) ->
|
||||
{ TExpr, NS } = translate(Expr, S),
|
||||
{ { op, ?line(Meta), Op, TExpr }, NS };
|
||||
|
||||
translate_each({ '__op__', Meta, [Op, Left, Right] }, S) when is_atom(Op) ->
|
||||
assert_no_match_scope_for_bin_op(Meta, Op, S),
|
||||
translate({ '__op__', Meta, [Op, Left, Right] }, S) when is_atom(Op) ->
|
||||
{ [TLeft, TRight], NS } = translate_args([Left, Right], S),
|
||||
{ { op, ?line(Meta), convert_op(Op), TLeft, TRight }, NS };
|
||||
{ { op, ?line(Meta), Op, TLeft, TRight }, NS };
|
||||
|
||||
%% Lexical
|
||||
|
||||
translate_each({ alias, Meta, [Ref] }, S) ->
|
||||
translate_each({ alias, Meta, [Ref,[]] }, S);
|
||||
|
||||
translate_each({ alias, Meta, [Ref, KV] }, S) ->
|
||||
assert_no_match_or_guard_scope(Meta, alias, S),
|
||||
{ TRef, SR } = translate_each(Ref, S),
|
||||
{ TKV, ST } = translate_opts(Meta, alias, [as, warn], no_alias_opts(KV), SR),
|
||||
|
||||
case TRef of
|
||||
{ atom, _, Old } ->
|
||||
translate_alias(Meta, true, Old, TKV, ST);
|
||||
_ ->
|
||||
compile_error(Meta, S#elixir_scope.file, "invalid args for alias, expected a compile time atom or alias as argument")
|
||||
end;
|
||||
|
||||
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),
|
||||
|
||||
{ TRef, SR } = translate_each(Ref, S),
|
||||
{ TKV, ST } = translate_opts(Meta, require, [as, warn], no_alias_opts(KV), SR),
|
||||
|
||||
case TRef of
|
||||
{ atom, _, Old } ->
|
||||
elixir_aliases:ensure_loaded(Meta, Old, ST),
|
||||
translate_require(Meta, Old, TKV, ST);
|
||||
_ ->
|
||||
compile_error(Meta, S#elixir_scope.file, "invalid args for require, expected a compile time atom or alias as argument")
|
||||
end;
|
||||
|
||||
translate_each({ import, Meta, [Left] }, S) ->
|
||||
translate_each({ import, Meta, [Left, []]}, S);
|
||||
|
||||
translate_each({ import, Meta, [Ref, KV] }, S) ->
|
||||
assert_no_match_or_guard_scope(Meta, import, S),
|
||||
{ TRef, SR } = translate_each(Ref, S),
|
||||
{ TKV, ST } = translate_opts(Meta, import, [only, except, warn], KV, SR),
|
||||
|
||||
case TRef of
|
||||
{ atom, _, Atom } ->
|
||||
elixir_aliases:ensure_loaded(Meta, Atom, ST),
|
||||
SF = elixir_import:import(Meta, Atom, TKV, ST),
|
||||
translate_require(Meta, Atom, TKV, SF);
|
||||
_ ->
|
||||
compile_error(Meta, S#elixir_scope.file, "invalid name for import, expected a compile time atom or alias")
|
||||
end;
|
||||
translate({ Lexical, _, [_, _] }, S) when Lexical == import; Lexical == alias; Lexical == require ->
|
||||
{ { atom, 0, nil }, S };
|
||||
|
||||
%% Pseudo variables
|
||||
|
||||
translate_each({ '__MODULE__', Meta, Atom }, S) when is_atom(Atom) ->
|
||||
{ { atom, ?line(Meta), S#elixir_scope.module }, S };
|
||||
|
||||
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_scope:to_erl(Env), S };
|
||||
|
||||
translate_each({ '__CALLER__', Meta, Atom }, S) when is_atom(Atom) ->
|
||||
translate({ '__CALLER__', Meta, Atom }, S) when is_atom(Atom) ->
|
||||
{ { var, ?line(Meta), '__CALLER__' }, S#elixir_scope{caller=true} };
|
||||
|
||||
%% Aliases
|
||||
|
||||
translate_each({ '__aliases__', Meta, _ } = Alias, S) ->
|
||||
case elixir_aliases:expand(Alias, S#elixir_scope.aliases, S#elixir_scope.macro_aliases, S#elixir_scope.lexical_tracker) of
|
||||
Receiver when is_atom(Receiver) ->
|
||||
elixir_lexical:record_remote(Receiver, S#elixir_scope.lexical_tracker),
|
||||
{ { atom, ?line(Meta), Receiver }, S };
|
||||
Aliases ->
|
||||
{ TAliases, SA } = translate_args(Aliases, S),
|
||||
|
||||
case lists:all(fun is_atom_tuple/1, TAliases) of
|
||||
true ->
|
||||
Atoms = [Atom || { atom, _, Atom } <- TAliases],
|
||||
Receiver = elixir_aliases:concat(Atoms),
|
||||
elixir_lexical:record_remote(Receiver, S#elixir_scope.lexical_tracker),
|
||||
{ { atom, ?line(Meta), Receiver }, SA };
|
||||
false ->
|
||||
Args = [elixir_utils:list_to_cons(?line(Meta), TAliases)],
|
||||
{ ?wrap_call(?line(Meta), elixir_aliases, concat, Args), SA }
|
||||
end
|
||||
end;
|
||||
|
||||
%% Quoting
|
||||
|
||||
translate_each({ Unquote, Meta, [_|_] }, S) when Unquote == unquote; Unquote == unquote_splicing ->
|
||||
compile_error(Meta, S#elixir_scope.file, "~p called outside quote", [Unquote]);
|
||||
|
||||
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, "missing do keyword in quote")
|
||||
end;
|
||||
|
||||
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, Do) of
|
||||
{ do, E } -> E;
|
||||
false -> syntax_error(Meta, S#elixir_scope.file, "missing do keyword in quote")
|
||||
end,
|
||||
|
||||
ValidOpts = [hygiene, context, var_context, location, line, file, unquote, bind_quoted],
|
||||
{ TKV, ST } = translate_opts(Meta, quote, ValidOpts, KV, S),
|
||||
|
||||
Hygiene = case lists:keyfind(hygiene, 1, TKV) of
|
||||
{ hygiene, List } when is_list(List) ->
|
||||
List;
|
||||
false ->
|
||||
[]
|
||||
end,
|
||||
|
||||
Context = case lists:keyfind(context, 1, TKV) of
|
||||
{ context, Atom } when is_atom(Atom) ->
|
||||
Atom;
|
||||
{ context, _ } ->
|
||||
compile_error(Meta, S#elixir_scope.file, "invalid :context for quote, expected a compile time atom or an alias");
|
||||
false ->
|
||||
case S#elixir_scope.module of
|
||||
nil -> 'Elixir';
|
||||
Mod -> Mod
|
||||
end
|
||||
end,
|
||||
|
||||
Vars = lists:keyfind(vars, 1, Hygiene) /= { vars, false },
|
||||
Aliases = lists:keyfind(aliases, 1, Hygiene) /= { aliases, false },
|
||||
Imports = lists:keyfind(imports, 1, Hygiene) /= { imports, false },
|
||||
|
||||
{ DefaultLine, DefaultFile } = case lists:keyfind(location, 1, TKV) of
|
||||
{ location, keep } -> { keep, keep };
|
||||
false -> { nil, nil }
|
||||
end,
|
||||
|
||||
Line = proplists:get_value(line, TKV, DefaultLine),
|
||||
File = proplists:get_value(file, TKV, DefaultFile),
|
||||
|
||||
Scope = case File of
|
||||
keep -> S#elixir_scope.file;
|
||||
_ -> File
|
||||
end,
|
||||
|
||||
QExprs = if
|
||||
is_binary(Scope) ->
|
||||
{ '__scope__', Meta, [[{file,Scope}],[{do,Exprs}]] };
|
||||
File == nil ->
|
||||
Exprs;
|
||||
true ->
|
||||
compile_error(Meta, S#elixir_scope.file, "invalid :file for quote, expected a compile time binary")
|
||||
end,
|
||||
|
||||
{ Binding, DefaultUnquote } = case lists:keyfind(bind_quoted, 1, TKV) of
|
||||
{ bind_quoted, BQ } -> { BQ, false };
|
||||
false -> { nil, true }
|
||||
end,
|
||||
|
||||
Unquote = case lists:keyfind(unquote, 1, TKV) of
|
||||
{ unquote, Bool } when is_boolean(Bool) -> Bool;
|
||||
false -> DefaultUnquote
|
||||
end,
|
||||
|
||||
Q = #elixir_quote{vars_hygiene=Vars, line=Line, unquote=Unquote,
|
||||
aliases_hygiene=Aliases, imports_hygiene=Imports, context=Context},
|
||||
|
||||
{ TExprs, _TQ, TS } = elixir_quote:erl_quote(QExprs, Binding, Q, ST),
|
||||
{ TExprs, TS };
|
||||
|
||||
translate_each({ quote, Meta, [_, _] }, S) ->
|
||||
syntax_error(Meta, S#elixir_scope.file, "invalid args for quote");
|
||||
|
||||
translate_each({ 'alias!', _Meta, [Arg] }, S) ->
|
||||
translate_each(Arg, S);
|
||||
|
||||
translate_each({ 'var!', Meta, [Arg] }, S) ->
|
||||
translate_each({ 'var!', Meta, [Arg, nil] }, S);
|
||||
|
||||
translate_each({ 'var!', Meta, [{Name, _, Atom}, Kind] }, S) when is_atom(Name), is_atom(Atom) ->
|
||||
translate_each({ 'var!', Meta, [Name, Kind] }, S);
|
||||
|
||||
translate_each({ 'var!', Meta, [Name, Kind] }, S) when is_atom(Name) ->
|
||||
Expanded = expand_quote_context(Meta, Kind, "invalid second argument for var!", S),
|
||||
elixir_scope:translate_var(Meta, Name, Expanded, S, fun() ->
|
||||
compile_error(Meta, S#elixir_scope.file, "expected var!(~ts) to expand to an existing "
|
||||
"variable or be a part of a match", [Name])
|
||||
end);
|
||||
|
||||
translate_each({ 'var!', Meta, [_, _] }, S) ->
|
||||
syntax_error(Meta, S#elixir_scope.file, "invalid first argument for var!, expected a var or an atom");
|
||||
|
||||
%% Functions
|
||||
|
||||
translate_each({ '&', Meta, [Arg] }, S) ->
|
||||
assert_no_match_or_guard_scope(Meta, '&', S),
|
||||
elixir_fn:capture(Meta, Arg, S);
|
||||
translate({ '&', Meta, [{ '/', [], [{ Fun, [], Atom }, Arity] }] }, S)
|
||||
when is_atom(Fun), is_atom(Atom), is_integer(Arity) ->
|
||||
{ { 'fun', ?line(Meta), { function, Fun, Arity } }, S };
|
||||
translate({ '&', Meta, [Arg] }, S) when is_integer(Arg) ->
|
||||
compile_error(Meta, S#elixir_scope.file, "unhandled &~B outside of a capture", [Arg]);
|
||||
|
||||
translate_each({ fn, Meta, [{ '->', _, Pairs }] }, S) ->
|
||||
assert_no_match_or_guard_scope(Meta, 'fn', S),
|
||||
elixir_fn:fn(Meta, Pairs, S);
|
||||
translate({ fn, Meta, Clauses }, S) ->
|
||||
elixir_fn:translate(Meta, Clauses, S);
|
||||
|
||||
%% Case
|
||||
|
||||
translate({'case', Meta, [Expr, KV]}, S) when is_list(KV) ->
|
||||
Clauses = elixir_clauses:get_pairs(do, KV),
|
||||
{ TExpr, NS } = translate(Expr, S),
|
||||
|
||||
RClauses = case elixir_utils:returns_boolean(TExpr) of
|
||||
true -> rewrite_case_clauses(Clauses);
|
||||
false -> Clauses
|
||||
end,
|
||||
|
||||
{ TClauses, TS } = elixir_clauses:clauses(Meta, RClauses, NS),
|
||||
{ { 'case', ?line(Meta), TExpr, TClauses }, TS };
|
||||
|
||||
%% Try
|
||||
|
||||
translate({'try', Meta, [Clauses]}, RS) when is_list(Clauses) ->
|
||||
S = RS#elixir_scope{noname=true},
|
||||
Do = proplists:get_value('do', Clauses, nil),
|
||||
{ TDo, SB } = elixir_translator:translate(Do, S),
|
||||
|
||||
Catch = [Tuple || { X, _ } = Tuple <- Clauses, X == 'rescue' orelse X == 'catch'],
|
||||
{ TCatch, SC } = elixir_try:clauses(Meta, Catch, mergec(S, SB)),
|
||||
|
||||
After = proplists:get_value('after', Clauses, nil),
|
||||
{ TAfter, SA } = translate(After, mergec(S, SC)),
|
||||
|
||||
Else = elixir_clauses:get_pairs(else, Clauses),
|
||||
{ TElse, SE } = elixir_clauses:clauses(Meta, Else, mergec(S, SA)),
|
||||
|
||||
SF = (mergec(S, SE))#elixir_scope{noname=RS#elixir_scope.noname},
|
||||
{ { 'try', ?line(Meta), unblock(TDo), TElse, TCatch, unblock(TAfter) }, SF };
|
||||
|
||||
%% Receive
|
||||
|
||||
translate({'receive', Meta, [KV] }, S) when is_list(KV) ->
|
||||
Do = elixir_clauses:get_pairs(do, KV, true),
|
||||
|
||||
case lists:keyfind('after', 1, KV) of
|
||||
false ->
|
||||
{ TClauses, SC } = elixir_clauses:clauses(Meta, Do, S),
|
||||
{ { 'receive', ?line(Meta), TClauses }, SC };
|
||||
_ ->
|
||||
After = elixir_clauses:get_pairs('after', KV),
|
||||
{ TClauses, SC } = elixir_clauses:clauses(Meta, Do ++ After, S),
|
||||
{ FClauses, TAfter } = elixir_utils:split_last(TClauses),
|
||||
{ _, _, [FExpr], _, FAfter } = TAfter,
|
||||
{ { 'receive', ?line(Meta), FClauses, FExpr, FAfter }, SC }
|
||||
end;
|
||||
|
||||
%% Comprehensions
|
||||
|
||||
translate_each({ Kind, Meta, Args }, S) when is_list(Args), (Kind == lc) orelse (Kind == bc) ->
|
||||
translate({ Kind, Meta, Args }, S) when is_list(Args), (Kind == lc) orelse (Kind == bc) ->
|
||||
translate_comprehension(Meta, Kind, Args, S);
|
||||
|
||||
%% Super
|
||||
|
||||
translate_each({ super, Meta, Args }, S) when is_list(Args) ->
|
||||
assert_no_match_or_guard_scope(Meta, super, S),
|
||||
translate({ super, Meta, Args }, S) when is_list(Args) ->
|
||||
Module = assert_module_scope(Meta, super, S),
|
||||
Function = assert_function_scope(Meta, super, S),
|
||||
elixir_def_overridable:ensure_defined(Meta, Module, Function, S),
|
||||
@@ -304,389 +130,207 @@ translate_each({ super, Meta, Args }, S) when is_list(Args) ->
|
||||
length(Args) == Arity ->
|
||||
translate_args(Args, S);
|
||||
true ->
|
||||
syntax_error(Meta, S#elixir_scope.file, "super must be called with the same number of "
|
||||
"arguments as the current function")
|
||||
compile_error(Meta, S#elixir_scope.file, "super must be called with the same number of "
|
||||
"arguments as the current function")
|
||||
end,
|
||||
|
||||
Super = elixir_def_overridable:name(Module, Function),
|
||||
{ { call, ?line(Meta), { atom, ?line(Meta), Super }, TArgs }, TS#elixir_scope{super=true} };
|
||||
|
||||
translate_each({ 'super?', Meta, [] }, S) ->
|
||||
Module = assert_module_scope(Meta, 'super?', S),
|
||||
Function = assert_function_scope(Meta, 'super?', S),
|
||||
Bool = elixir_def_overridable:is_defined(Module, Function),
|
||||
{ { atom, ?line(Meta), Bool }, S };
|
||||
|
||||
%% Variables
|
||||
|
||||
translate_each({ '^', Meta, [ { Name, VarMeta, Kind } = Var ] },
|
||||
translate({ '^', Meta, [ { Name, VarMeta, Kind } = Var ] },
|
||||
#elixir_scope{extra=fn_match, extra_guards=Extra} = S) when is_atom(Name), is_atom(Kind) ->
|
||||
case orddict:find({ Name, var_kind(VarMeta, Kind) }, S#elixir_scope.backup_vars) of
|
||||
{ ok, Value } ->
|
||||
Tuple = { Name, var_kind(VarMeta, Kind) },
|
||||
case orddict:find(Tuple, S#elixir_scope.backup_vars) of
|
||||
{ ok, { Value, _Counter } } ->
|
||||
Line = ?line(Meta),
|
||||
{ TVar, TS } = translate_each(Var, S),
|
||||
{ TVar, TS } = translate(Var, S),
|
||||
Guard = { op, Line, '=:=', { var, ?line(Meta), Value }, TVar },
|
||||
{ TVar, TS#elixir_scope{extra_guards=[Guard|Extra]} };
|
||||
error ->
|
||||
compile_error(Meta, S#elixir_scope.file, "unbound variable ^~ts", [Name])
|
||||
end;
|
||||
|
||||
translate_each({ '^', Meta, [ { Name, VarMeta, Kind } ] }, #elixir_scope{context=match} = S) when is_atom(Name), is_atom(Kind) ->
|
||||
case orddict:find({ Name, var_kind(VarMeta, Kind) }, S#elixir_scope.backup_vars) of
|
||||
{ ok, Value } ->
|
||||
translate({ '^', Meta, [ { Name, VarMeta, Kind } ] }, #elixir_scope{context=match} = S) when is_atom(Name), is_atom(Kind) ->
|
||||
Tuple = { Name, var_kind(VarMeta, Kind) },
|
||||
case orddict:find(Tuple, S#elixir_scope.backup_vars) of
|
||||
{ ok, { Value, _Counter } } ->
|
||||
{ { var, ?line(Meta), Value }, S };
|
||||
error ->
|
||||
compile_error(Meta, S#elixir_scope.file, "unbound variable ^~ts", [Name])
|
||||
end;
|
||||
|
||||
translate_each({ '^', Meta, [ { Name, _, Kind } ] }, S) when is_atom(Name), is_atom(Kind) ->
|
||||
compile_error(Meta, S#elixir_scope.file,
|
||||
"cannot use ^~ts outside of match clauses", [Name]);
|
||||
translate({ '_', Meta, Kind }, #elixir_scope{context=match} = S) when is_atom(Kind) ->
|
||||
{ { var, ?line(Meta), '_' }, S };
|
||||
|
||||
translate_each({ '^', Meta, [ Expr ] }, S) ->
|
||||
syntax_error(Meta, S#elixir_scope.file,
|
||||
"the unary operator ^ can only be used with variables, invalid expression ^~ts", ['Elixir.Macro':to_string(Expr)]);
|
||||
translate({ '_', Meta, Kind }, S) when is_atom(Kind) ->
|
||||
compile_error(Meta, S#elixir_scope.file, "unbound variable _");
|
||||
|
||||
translate_each({ Name, Meta, Kind }, S) when is_atom(Name), is_atom(Kind) ->
|
||||
elixir_scope:translate_var(Meta, Name, var_kind(Meta, Kind), S, fun() ->
|
||||
translate_each({ Name, Meta, [] }, S)
|
||||
end);
|
||||
translate({ Name, Meta, Kind }, S) when is_atom(Name), is_atom(Kind) ->
|
||||
elixir_scope:translate_var(Meta, Name, var_kind(Meta, Kind), S);
|
||||
|
||||
%% Local calls
|
||||
|
||||
translate_each({ '->', Meta, _Args }, S) ->
|
||||
syntax_error(Meta, S#elixir_scope.file, "unhandled operator ->");
|
||||
|
||||
translate_each({ Atom, Meta, Args }, S) when is_atom(Atom), is_list(Meta), is_list(Args) ->
|
||||
assert_no_ambiguous_op(Atom, Meta, Args, S),
|
||||
|
||||
Callback = fun() ->
|
||||
case S#elixir_scope.context of
|
||||
match ->
|
||||
compile_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 -> compile_error(Meta, File, "unknown variable ~ts or cannot invoke "
|
||||
"function ~ts/~B inside guard", [Atom, Atom, Arity]);
|
||||
_ -> compile_error(Meta, File, "cannot invoke local ~ts/~B inside guard",
|
||||
[Atom, Arity])
|
||||
end;
|
||||
_ ->
|
||||
translate_local(Meta, Atom, Args, S)
|
||||
end
|
||||
end,
|
||||
|
||||
elixir_dispatch:dispatch_import(Meta, Atom, Args, S, Callback);
|
||||
translate({ Name, Meta, Args }, S) when is_atom(Name), is_list(Meta), is_list(Args) ->
|
||||
if
|
||||
S#elixir_scope.context == match ->
|
||||
compile_error(Meta, S#elixir_scope.file,
|
||||
"cannot invoke function ~ts/~B inside match", [Name, length(Args)]);
|
||||
S#elixir_scope.context == guard ->
|
||||
Arity = length(Args),
|
||||
File = S#elixir_scope.file,
|
||||
case Arity of
|
||||
0 -> compile_error(Meta, File, "unknown variable ~ts or cannot invoke "
|
||||
"function ~ts/~B inside guard", [Name, Name, Arity]);
|
||||
_ -> compile_error(Meta, File, "cannot invoke local ~ts/~B inside guard",
|
||||
[Name, Arity])
|
||||
end;
|
||||
S#elixir_scope.function == nil ->
|
||||
compile_error(Meta, S#elixir_scope.file, "undefined function ~ts/~B", [Name, length(Args)]);
|
||||
true ->
|
||||
Line = ?line(Meta),
|
||||
{ TArgs, NS } = translate_args(Args, S),
|
||||
{ { call, Line, { atom, Line, Name }, TArgs }, NS }
|
||||
end;
|
||||
|
||||
%% Remote calls
|
||||
|
||||
translate_each({ { '.', _, [Left, Right] }, Meta, Args }, S)
|
||||
translate({ { '.', _, [Left, Right] }, Meta, Args }, S)
|
||||
when (is_tuple(Left) orelse is_atom(Left)), is_atom(Right), is_list(Meta), is_list(Args) ->
|
||||
{ TLeft, SL } = translate_each(Left, S),
|
||||
{ TLeft, SL } = translate(Left, S),
|
||||
{ TRight, SR } = translate(Right, mergec(S, SL)),
|
||||
|
||||
{ TRight, SR } = translate_each(Right, umergec(S, SL)),
|
||||
Callback = fun() -> translate_apply(Meta, TLeft, TRight, Args, S, SL, SR) end,
|
||||
|
||||
case TLeft of
|
||||
{ atom, _, Receiver } ->
|
||||
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) ->
|
||||
compile_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);
|
||||
case S#elixir_scope.context of
|
||||
Context when Left /= erlang, (Context == match) orelse (Context == guard) ->
|
||||
compile_error(Meta, S#elixir_scope.file, "cannot invoke remote function ~ts.~ts/~B inside ~ts",
|
||||
['Elixir.Macro':to_string(Left), Right, length(Args), Context]);
|
||||
_ ->
|
||||
case S#elixir_scope.context of
|
||||
Context when Context == match; Context == guard ->
|
||||
compile_error(Meta, S#elixir_scope.file, "cannot invoke remote function ~ts/~B inside ~ts",
|
||||
[Right, length(Args), Context]);
|
||||
_ ->
|
||||
Callback()
|
||||
end
|
||||
Line = ?line(Meta),
|
||||
{ TArgs, SA } = translate_args(Args, mergec(S, SR)),
|
||||
{ { call, Line, { remote, Line, TLeft, TRight }, TArgs }, mergev(SL, mergev(SR, SA)) }
|
||||
end;
|
||||
|
||||
%% Anonymous function calls
|
||||
|
||||
translate_each({ { '.', _, [Expr] }, Meta, Args }, S) when is_list(Args) ->
|
||||
{ TExpr, SE } = translate_each(Expr, S),
|
||||
case TExpr of
|
||||
{ atom, _, Atom } ->
|
||||
syntax_error(Meta, S#elixir_scope.file, "invalid function call :~ts.()", [Atom]);
|
||||
_ ->
|
||||
{ TArgs, SA } = translate_args(Args, umergec(S, SE)),
|
||||
{ {call, ?line(Meta), TExpr, TArgs}, umergev(SE, SA) }
|
||||
end;
|
||||
|
||||
%% Invalid calls
|
||||
|
||||
translate_each({ { '.', _, [Invalid, _] }, Meta, Args }, S) when is_list(Meta) and is_list(Args) ->
|
||||
syntax_error(Meta, S#elixir_scope.file, "invalid remote call on ~ts",
|
||||
['Elixir.Macro':to_string(Invalid)]);
|
||||
|
||||
translate_each({ _, Meta, Args } = Invalid, S) when is_list(Meta) and is_list(Args) ->
|
||||
syntax_error(Meta, S#elixir_scope.file, "invalid call ~ts",
|
||||
['Elixir.Macro':to_string(Invalid)]);
|
||||
|
||||
translate_each({ _, _, _ } = Tuple, S) ->
|
||||
syntax_error([{line,0}], S#elixir_scope.file, "expected a valid quoted expression, got: ~ts",
|
||||
['Elixir.Kernel':inspect(Tuple, [{raw,true}])]);
|
||||
translate({ { '.', _, [Expr] }, Meta, Args }, S) when is_list(Args) ->
|
||||
{ TExpr, SE } = translate(Expr, S),
|
||||
{ TArgs, SA } = translate_args(Args, mergec(S, SE)),
|
||||
{ { call, ?line(Meta), TExpr, TArgs }, mergev(SE, SA) };
|
||||
|
||||
%% Literals
|
||||
|
||||
translate_each(Literal, S) ->
|
||||
elixir_literal:translate(Literal, S).
|
||||
translate(List, S) when is_list(List) ->
|
||||
Fun = case S#elixir_scope.context of
|
||||
match -> fun translate/2;
|
||||
_ -> fun(X, Acc) -> translate_arg(X, Acc, S) end
|
||||
end,
|
||||
translate_list(List, Fun, S, []);
|
||||
|
||||
translate({ Left, Right }, S) ->
|
||||
{ TArgs, SE } = translate_args([Left, Right], S),
|
||||
{ { tuple, 0, TArgs }, SE };
|
||||
|
||||
translate(Other, S) ->
|
||||
{ elixir_utils:elixir_to_erl(Other), S }.
|
||||
|
||||
%% Helpers
|
||||
|
||||
translate_list([{ '|', _, [_, _]=Args}], Fun, Acc, List) ->
|
||||
{ [TLeft,TRight], TAcc } = lists:mapfoldl(Fun, Acc, Args),
|
||||
{ build_list([TLeft|List], TRight), TAcc };
|
||||
translate_list([H|T], Fun, Acc, List) ->
|
||||
{ TH, TAcc } = Fun(H, Acc),
|
||||
translate_list(T, Fun, TAcc, [TH|List]);
|
||||
translate_list([], _Fun, Acc, List) ->
|
||||
{ build_list(List, { nil, 0 }), Acc }.
|
||||
|
||||
build_list([H|T], Acc) ->
|
||||
build_list(T, { cons, 0, H, Acc });
|
||||
build_list([], Acc) ->
|
||||
Acc.
|
||||
|
||||
var_kind(Meta, Kind) ->
|
||||
case lists:keyfind(counter, 1, Meta) of
|
||||
{ counter, Counter } -> Counter;
|
||||
false -> Kind
|
||||
end.
|
||||
|
||||
%% Opts
|
||||
%% Case
|
||||
|
||||
translate_opts(Meta, Kind, Allowed, Opts, S) ->
|
||||
{ Expanded, TS } = case literal_opts(Opts) orelse skip_expansion(S#elixir_scope.module) of
|
||||
true -> { Opts, S };
|
||||
false -> 'Elixir.Macro':expand_all(Opts, elixir_scope:to_ex_env({ ?line(Meta), S }), S)
|
||||
end,
|
||||
validate_opts(Meta, Kind, Allowed, Expanded, TS),
|
||||
{ Expanded, TS }.
|
||||
|
||||
skip_expansion('Elixir.Kernel') -> true;
|
||||
skip_expansion('Elixir.Kernel.Typespec') -> true;
|
||||
skip_expansion(_) -> false.
|
||||
|
||||
literal_opts({ X, Y }) -> literal_opts(X) andalso literal_opts(Y);
|
||||
literal_opts(Opts) when is_list(Opts) -> lists:all(fun literal_opts/1, Opts);
|
||||
literal_opts(Opts) when is_atom(Opts); is_number(Opts); is_bitstring(Opts) -> true;
|
||||
literal_opts(_) -> false.
|
||||
|
||||
validate_opts(Meta, Kind, Allowed, Opts, S) when is_list(Opts) ->
|
||||
[begin
|
||||
compile_error(Meta, S#elixir_scope.file,
|
||||
"unsupported option ~ts given to ~s", ['Elixir.Kernel':inspect(Key), Kind])
|
||||
end || { Key, _ } <- Opts, not lists:member(Key, Allowed)];
|
||||
|
||||
validate_opts(Meta, Kind, _Allowed, _Opts, S) ->
|
||||
compile_error(Meta, S#elixir_scope.file, "invalid options for ~s, expected a keyword list", [Kind]).
|
||||
|
||||
%% 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;
|
||||
rewrite_case_clauses([
|
||||
{do,Meta1,[{'when',_,[{V,M,C},{'__op__',_,['orelse',_,_]}]}],False},
|
||||
{do,Meta2,[{'_',_,UC}],True}] = Clauses)
|
||||
when is_atom(V), is_list(M), is_atom(C), is_atom(UC) ->
|
||||
case lists:keyfind('cond', 1, M) of
|
||||
{'cond',true} ->
|
||||
[{do,Meta1,[false],False},{do,Meta2,[true],True}];
|
||||
_ ->
|
||||
compile_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;
|
||||
_ ->
|
||||
compile_error(Meta, S#elixir_scope.file,
|
||||
"invalid :as for alias, expected a compile time atom or alias")
|
||||
end,
|
||||
|
||||
case (New == Old) orelse (length(string:tokens(atom_to_list(New), ".")) == 2) of
|
||||
true -> ok;
|
||||
false -> compile_error(Meta, S#elixir_scope.file,
|
||||
"invalid :as for alias, nested alias ~s not allowed", [elixir_errors:inspect(New)])
|
||||
end,
|
||||
|
||||
{ { atom, ?line(Meta), nil }, elixir_aliases:store(Meta, New, Old, TKV, 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
|
||||
Clauses
|
||||
end;
|
||||
rewrite_case_clauses(Clauses) ->
|
||||
Clauses.
|
||||
|
||||
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.
|
||||
|
||||
is_atom_tuple({ atom, _, _ }) -> true;
|
||||
is_atom_tuple(_) -> false.
|
||||
|
||||
%% Locals
|
||||
|
||||
translate_local(Meta, Name, Args, #elixir_scope{local=nil,function=nil} = S) ->
|
||||
compile_error(Meta, S#elixir_scope.file, "function ~ts/~B undefined", [Name, length(Args)]);
|
||||
|
||||
translate_local(Meta, Name, Args, #elixir_scope{local=nil,module=Module,function=Function} = S) ->
|
||||
elixir_tracker:record_local({ Name, length(Args) }, Module, Function),
|
||||
Line = ?line(Meta),
|
||||
{ TArgs, NS } = translate_args(Args, S),
|
||||
{ { call, Line, { atom, Line, Name }, TArgs }, NS };
|
||||
|
||||
translate_local(Meta, Name, Args, S) ->
|
||||
translate_each({ { '.', Meta, [S#elixir_scope.local, Name] }, Meta, Args }, S).
|
||||
%% Pack a list of expressions from a block.
|
||||
unblock({ 'block', _, Exprs }) -> Exprs;
|
||||
unblock(Expr) -> [Expr].
|
||||
|
||||
%% Translate args
|
||||
|
||||
%% Variables in arguments are not propagated from one
|
||||
%% argument to the other. For instance:
|
||||
%%
|
||||
%% x = 1
|
||||
%% foo(x = x + 2, x)
|
||||
%% x
|
||||
%%
|
||||
%% Should be the same as:
|
||||
%%
|
||||
%% foo(3, 1)
|
||||
%% 3
|
||||
%%
|
||||
%% However, notice that if we are doing an assignment,
|
||||
%% it behaves the same as translate.
|
||||
|
||||
translate_arg(Arg, { Acc, S }) ->
|
||||
{ TArg, TAcc } = translate_each(Arg, Acc),
|
||||
{ TArg, { umergea(Acc, TAcc), umergev(S, TAcc) } }.
|
||||
translate_arg(Arg, Acc, S) when is_number(Arg); is_atom(Arg); is_binary(Arg); is_pid(Arg); is_function(Arg) ->
|
||||
{ TArg, _ } = translate(Arg, S),
|
||||
{ TArg, Acc };
|
||||
translate_arg(Arg, Acc, S) ->
|
||||
{ TArg, TAcc } = translate(Arg, mergec(S, Acc)),
|
||||
{ TArg, mergev(Acc, TAcc) }.
|
||||
|
||||
translate_args(Args, #elixir_scope{context=match} = S) ->
|
||||
translate(Args, S);
|
||||
translate_many(Args, S);
|
||||
|
||||
translate_args(Args, S) ->
|
||||
{ TArgs, { SC, SV } } = lists:mapfoldl(fun translate_arg/2, {S, S}, Args),
|
||||
{ TArgs, umergea(SV, SC) }.
|
||||
|
||||
%% Translate apply
|
||||
%% Used by both apply and external function invocation macros.
|
||||
|
||||
translate_apply(Meta, TLeft, TRight, Args, S, SL, SR) ->
|
||||
Line = ?line(Meta),
|
||||
|
||||
Optimize = case (Args == []) orelse lists:last(Args) of
|
||||
{ '|', _, _ } -> false;
|
||||
_ ->
|
||||
case TRight of
|
||||
{ atom, _, _ } -> true;
|
||||
_ -> false
|
||||
end
|
||||
end,
|
||||
|
||||
case Optimize of
|
||||
true ->
|
||||
%% Register the remote
|
||||
case TLeft of
|
||||
{ atom, _, Receiver } ->
|
||||
Tuple = { element(3, TRight), length(Args) },
|
||||
elixir_lexical:record_remote(Receiver, S#elixir_scope.lexical_tracker),
|
||||
elixir_tracker:record_remote(Tuple, Receiver, S#elixir_scope.module, S#elixir_scope.function);
|
||||
_ ->
|
||||
ok
|
||||
end,
|
||||
|
||||
{ TArgs, SA } = translate_args(Args, umergec(S, SR)),
|
||||
FS = umergev(SL, umergev(SR,SA)),
|
||||
{ { call, Line, { remote, Line, TLeft, TRight }, TArgs }, FS };
|
||||
false ->
|
||||
{ TArgs, SA } = translate_each(Args, umergec(S, SR)),
|
||||
FS = umergev(SL, umergev(SR,SA)),
|
||||
{ ?wrap_call(Line, erlang, apply, [TLeft, TRight, TArgs]), FS }
|
||||
end.
|
||||
|
||||
%% __op__ helpers
|
||||
|
||||
assert_no_match_scope_for_bin_op(Meta, Op, S)
|
||||
when Op == 'and'; Op == 'or' ->
|
||||
elixir_errors:assert_no_match_scope(Meta, Op, S);
|
||||
assert_no_match_scope_for_bin_op(_Meta, _Op, _S) -> ok.
|
||||
|
||||
convert_op('and') -> 'andalso';
|
||||
convert_op('or') -> 'orelse';
|
||||
convert_op('!==') -> '=/=';
|
||||
convert_op('===') -> '=:=';
|
||||
convert_op('!=') -> '/=';
|
||||
convert_op('<=') -> '=<';
|
||||
convert_op('<-') -> '!';
|
||||
convert_op(Else) -> Else.
|
||||
|
||||
assert_no_ambiguous_op(Name, Meta, [Arg], S) ->
|
||||
case lists:keyfind(ambiguous_op, 1, Meta) of
|
||||
{ ambiguous_op, Kind } ->
|
||||
case orddict:find({ Name, Kind }, S#elixir_scope.vars) of
|
||||
error -> ok;
|
||||
_ ->
|
||||
syntax_error(Meta, S#elixir_scope.file, "\"~ts ~ts\" looks like a function call but "
|
||||
"there is a variable named \"~ts\", please use explicit parenthesis or even spaces",
|
||||
[Name, 'Elixir.Macro':to_string(Arg), Name])
|
||||
end;
|
||||
_ -> ok
|
||||
end;
|
||||
|
||||
assert_no_ambiguous_op(_Atom, _Meta, _Args, _S) -> ok.
|
||||
lists:mapfoldl(fun(X, Acc) -> translate_arg(X, Acc, S) end, S, Args).
|
||||
|
||||
%% Comprehensions
|
||||
|
||||
translate_comprehension(Meta, Kind, Args, S) ->
|
||||
case elixir_utils:split_last(Args) of
|
||||
{ Cases, [{do,Expr}] } ->
|
||||
{ TCases, SC } = lists:mapfoldl(fun(C, Acc) -> translate_comprehension_clause(Meta, C, Acc) end, S, Cases),
|
||||
{ TExpr, SE } = translate_comprehension_do(Meta, Kind, Expr, SC),
|
||||
{ { Kind, ?line(Meta), TExpr, TCases }, umergec(S, SE) };
|
||||
_ ->
|
||||
syntax_error(Meta, S#elixir_scope.file, "missing do keyword in comprehension ~s", [Kind])
|
||||
end.
|
||||
{ Cases, [{do,Expr}] } = elixir_utils:split_last(Args),
|
||||
{ TCases, SC } = lists:mapfoldl(fun(C, Acc) -> translate_comprehension_clause(Meta, C, Acc) end, S, Cases),
|
||||
{ TExpr, SE } = translate_comprehension_do(Meta, Kind, Expr, SC),
|
||||
{ { Kind, ?line(Meta), TExpr, TCases }, mergef(S, SE) }.
|
||||
|
||||
translate_comprehension_do(_Meta, bc, { '<<>>', _, _ } = Expr, S) ->
|
||||
translate_each(Expr, S);
|
||||
translate(Expr, S);
|
||||
|
||||
translate_comprehension_do(Meta, bc, _Expr, S) ->
|
||||
syntax_error(Meta, S#elixir_scope.file, "a bit comprehension expects a bit string << >> to be returned");
|
||||
compile_error(Meta, S#elixir_scope.file, "a bit comprehension expects a bit string << >> to be returned");
|
||||
|
||||
translate_comprehension_do(_Meta, _Kind, Expr, S) ->
|
||||
translate_each(Expr, S).
|
||||
translate(Expr, S).
|
||||
|
||||
translate_comprehension_clause(_Meta, {inbits, Meta, [{ '<<>>', _, _} = Left, Right]}, S) ->
|
||||
{ TRight, SR } = translate_each(Right, S),
|
||||
{ TLeft, SL } = elixir_clauses:assigns(fun elixir_translator:translate_each/2, Left, SR),
|
||||
{ TRight, SR } = translate(Right, S),
|
||||
{ TLeft, SL } = elixir_clauses:match(fun elixir_translator:translate/2, Left, SR),
|
||||
{ { b_generate, ?line(Meta), TLeft, TRight }, SL };
|
||||
|
||||
translate_comprehension_clause(_Meta, {inbits, Meta, [_Left, _Right]}, S) ->
|
||||
syntax_error(Meta, S#elixir_scope.file, "a bit comprehension expects a bit string << >> to be used in inbits generators");
|
||||
compile_error(Meta, S#elixir_scope.file, "a bit comprehension expects a bit string << >> to be used in inbits generators");
|
||||
|
||||
translate_comprehension_clause(_Meta, {inlist, Meta, [Left, Right]}, S) ->
|
||||
{ TRight, SR } = translate_each(Right, S),
|
||||
{ TLeft, SL } = elixir_clauses:assigns(fun elixir_translator:translate_each/2, Left, SR),
|
||||
{ TRight, SR } = translate(Right, S),
|
||||
{ TLeft, SL } = elixir_clauses:match(fun elixir_translator:translate/2, Left, SR),
|
||||
{ { generate, ?line(Meta), TLeft, TRight }, SL };
|
||||
|
||||
translate_comprehension_clause(Meta, X, S) ->
|
||||
Line = ?line(Meta),
|
||||
{ TX, TS } = translate_each(X, S),
|
||||
{ BX, BS } = elixir_utils:convert_to_boolean(Line, TX, true, false, TS),
|
||||
{ TX, TS } = translate(X, S),
|
||||
{ BX, BS } = elixir_utils:convert_to_boolean(Line, TX, true, TS),
|
||||
{ { match, Line, { var, Line, '_' }, BX }, BS }.
|
||||
|
||||
%% Assertions
|
||||
|
||||
assert_module_scope(Meta, Kind, #elixir_scope{module=nil,file=File}) ->
|
||||
compile_error(Meta, File, "cannot invoke ~ts outside module", [Kind]);
|
||||
assert_module_scope(_Meta, _Kind, #elixir_scope{module=Module}) -> Module.
|
||||
|
||||
assert_function_scope(Meta, Kind, #elixir_scope{function=nil,file=File}) ->
|
||||
compile_error(Meta, File, "cannot invoke ~ts outside function", [Kind]);
|
||||
assert_function_scope(_Meta, _Kind, #elixir_scope{function=Function}) -> Function.
|
||||
|
||||
+89
-140
@@ -1,12 +1,14 @@
|
||||
-module(elixir_try).
|
||||
-export([clauses/3, format_error/1]).
|
||||
-import(elixir_scope, [umergec/2]).
|
||||
-include("elixir.hrl").
|
||||
|
||||
clauses(Meta, Clauses, S) ->
|
||||
Catch = elixir_clauses:get_pairs(Meta, 'catch', Clauses, S),
|
||||
Rescue = elixir_clauses:get_pairs(Meta, rescue, Clauses, S),
|
||||
Transformer = fun(X, Acc) -> each_clause(X, umergec(S, Acc)) end,
|
||||
clauses(_Meta, Clauses, S) ->
|
||||
Catch = elixir_clauses:get_pairs('catch', Clauses),
|
||||
Rescue = elixir_clauses:get_pairs(rescue, Clauses),
|
||||
Transformer = fun(X, SAcc) ->
|
||||
{ TX, TS } = each_clause(X, SAcc),
|
||||
{ TX, elixir_scope:mergec(S, TS) }
|
||||
end,
|
||||
lists:mapfoldl(Transformer, S, Rescue ++ Catch).
|
||||
|
||||
each_clause({ 'catch', Meta, Raw, Expr }, S) ->
|
||||
@@ -17,87 +19,46 @@ each_clause({ 'catch', Meta, Raw, Expr }, S) ->
|
||||
[X,Y] -> [X, Y, { '_', Meta, nil }];
|
||||
[_,_,_] -> Args;
|
||||
_ ->
|
||||
elixir_errors:syntax_error(Meta, S#elixir_scope.file, "too many arguments given for catch")
|
||||
elixir_errors:compile_error(Meta, S#elixir_scope.file, "too many arguments given for catch")
|
||||
end,
|
||||
|
||||
Condition = { '{}', Meta, Final },
|
||||
elixir_clauses:assigns_block(?line(Meta), fun elixir_translator:translate_each/2, Condition, [Expr], Guards, S);
|
||||
Condition = [{ '{}', Meta, Final }],
|
||||
elixir_clauses:clause(?line(Meta), fun elixir_translator:translate_many/2, Condition, Expr, Guards, S);
|
||||
|
||||
each_clause({ rescue, Meta, [Condition], Expr }, S) ->
|
||||
case normalize_rescue(Meta, Condition, S) of
|
||||
{ Left, Right } ->
|
||||
case Left of
|
||||
{ '_', _, _ } ->
|
||||
{ ClauseVar, CS } = elixir_scope:build_ex_var(?line(Meta), S),
|
||||
{ Clause, _ } = rescue_guards(Meta, ClauseVar, Right, S),
|
||||
each_clause({ 'catch', Meta, Clause, Expr }, CS);
|
||||
_ ->
|
||||
{ Clause, Safe } = rescue_guards(Meta, Left, Right, S),
|
||||
case Safe of
|
||||
true ->
|
||||
each_clause({ 'catch', Meta, Clause, Expr }, S);
|
||||
false ->
|
||||
{ ClauseVar, CS } = elixir_scope:build_ex_var(?line(Meta), S),
|
||||
{ FinalClause, _ } = rescue_guards(Meta, ClauseVar, Right, S),
|
||||
Match = { '=', Meta, [
|
||||
Left,
|
||||
{ { '.', Meta, ['Elixir.Exception', normalize] }, Meta, [ClauseVar] }
|
||||
] },
|
||||
FinalExpr = prepend_to_block(Meta, Match, Expr),
|
||||
each_clause({ 'catch', Meta, FinalClause, FinalExpr }, CS)
|
||||
end
|
||||
end;
|
||||
each_clause({ rescue, Meta, [{ in, _, [Left, Right]}], Expr }, S) ->
|
||||
case Left of
|
||||
{ '_', _, LAtom } when is_atom(LAtom) ->
|
||||
{ VarName, _, CS } = elixir_scope:build_var('_', S),
|
||||
{ Clause, _ } = rescue_guards(Meta, { VarName, Meta, nil }, Right, S),
|
||||
each_clause({ 'catch', Meta, Clause, Expr }, CS);
|
||||
_ ->
|
||||
each_clause({ 'catch', Meta, [error, Condition], Expr }, S)
|
||||
end;
|
||||
|
||||
each_clause({rescue,Meta,_,_}, S) ->
|
||||
elixir_errors:syntax_error(Meta, S#elixir_scope.file, "too many arguments given for rescue");
|
||||
|
||||
each_clause({Key,Meta,_,_}, S) ->
|
||||
elixir_errors:syntax_error(Meta, S#elixir_scope.file, "invalid key ~ts in try", [Key]).
|
||||
|
||||
%% Helpers
|
||||
|
||||
%% rescue [Error] -> _ in [Error]
|
||||
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) ->
|
||||
normalize_rescue(Meta, { in, Meta, [Rescue, { '_', Meta, nil }] }, S);
|
||||
|
||||
%% rescue var in [Exprs]
|
||||
normalize_rescue(_, { in, Meta, [Left, Right] }, S) ->
|
||||
case Right of
|
||||
{ '_', _, _ } ->
|
||||
{ Left, nil };
|
||||
_ when is_list(Right) ->
|
||||
is_valid_rescue_list(Right, S) andalso { Left, Right };
|
||||
_ ->
|
||||
{ Expanded, ES } = 'Elixir.Macro':expand_all(Right, elixir_scope:to_ex_env({ ?line(Meta), S }), S),
|
||||
case is_valid_rescue_list(Expanded, ES) of
|
||||
true -> { Left, Expanded };
|
||||
{ Clause, Safe } = rescue_guards(Meta, Left, Right, S),
|
||||
case Safe of
|
||||
true ->
|
||||
each_clause({ 'catch', Meta, Clause, Expr }, S);
|
||||
false ->
|
||||
elixir_errors:syntax_error(Meta, ES#elixir_scope.file, "invalid use of operator \"in\" in rescue inside try")
|
||||
{ VarName, _, CS } = elixir_scope:build_var('_', S),
|
||||
ClauseVar = { VarName, Meta, nil },
|
||||
{ FinalClause, _ } = rescue_guards(Meta, ClauseVar, Right, S),
|
||||
Match = { '=', Meta, [
|
||||
Left,
|
||||
{ { '.', Meta, ['Elixir.Exception', normalize] }, Meta, [ClauseVar] }
|
||||
] },
|
||||
FinalExpr = prepend_to_block(Meta, Match, Expr),
|
||||
each_clause({ 'catch', Meta, FinalClause, FinalExpr }, CS)
|
||||
end
|
||||
end;
|
||||
|
||||
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);
|
||||
_ ->
|
||||
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.
|
||||
each_clause({rescue,Meta,_,_}, S) ->
|
||||
elixir_errors:compile_error(Meta, S#elixir_scope.file, "invalid arguments for rescue in try");
|
||||
|
||||
each_clause({Key,Meta,_,_}, S) ->
|
||||
elixir_errors:compile_error(Meta, S#elixir_scope.file, "invalid key ~ts in try", [Key]).
|
||||
|
||||
%% Helpers
|
||||
|
||||
%% Convert rescue clauses into guards.
|
||||
rescue_guards(_, Var, nil, _) -> { [error, Var], false };
|
||||
rescue_guards(_, Var, { '_', _, _ }, _) -> { [error, Var], false };
|
||||
|
||||
rescue_guards(Meta, Var, Guards, S) ->
|
||||
{ RawElixir, RawErlang } = rescue_each_var(Meta, Var, Guards),
|
||||
@@ -106,15 +67,15 @@ rescue_guards(Meta, Var, Guards, S) ->
|
||||
Final = case Elixir == [] of
|
||||
true -> Erlang;
|
||||
false ->
|
||||
IsTuple = { is_tuple, Meta, [Var] },
|
||||
IsException = { '==', Meta, [
|
||||
{ { '.', Meta, [erlang, element] }, Meta, [2, Var] }, '__exception__'
|
||||
IsTuple = { erl(Meta, is_tuple), Meta, [Var] },
|
||||
IsException = { erl(Meta, '=='), Meta, [
|
||||
{ erl(Meta, element), Meta, [2, Var] }, '__exception__'
|
||||
] },
|
||||
OrElse = join(Meta, 'or', Elixir),
|
||||
[join(Meta, 'and', [IsTuple, IsException, OrElse])|Erlang]
|
||||
OrElse = join(Meta, fun do_or/3, Elixir),
|
||||
[join(Meta, fun do_and/3, [IsTuple, IsException, OrElse])|Erlang]
|
||||
end,
|
||||
{
|
||||
[{ 'when', Meta, [error, Var, reverse_join(Meta, 'when', Final)] }],
|
||||
[{ 'when', Meta, [error, Var, join_when(Meta, Final)] }],
|
||||
Safe
|
||||
}.
|
||||
|
||||
@@ -128,11 +89,10 @@ rescue_each_var(Meta, ClauseVar, Guards) ->
|
||||
false ->
|
||||
Elixir = [exception_compare(Meta, ClauseVar, Var) || Var <- Vars],
|
||||
Erlang = lists:map(fun(Rescue) ->
|
||||
Compares = [{ '==', Meta, [Rescue, Var] } || Var <- Vars],
|
||||
{ 'and', Meta, [
|
||||
erlang_rescue_guard_for(Meta, ClauseVar, Rescue),
|
||||
join(Meta, 'or', Compares)
|
||||
] }
|
||||
Compares = [{ erl(Meta, '=='), Meta, [Rescue, Var] } || Var <- Vars],
|
||||
do_and(Meta,
|
||||
erlang_rescue_guard_for(Meta, ClauseVar, Rescue),
|
||||
join(Meta, fun do_or/3, Compares))
|
||||
end, erlang_rescues()),
|
||||
{ Elixir, Erlang }
|
||||
end.
|
||||
@@ -152,17 +112,16 @@ rescue_each_ref(Meta, Var, [H|T], Elixir, Erlang, _Safe, S) when
|
||||
H == 'Elixir.MatchError'; H == 'Elixir.CaseClauseError';
|
||||
H == 'Elixir.TryClauseError'; H == 'Elixir.FunctionClauseError';
|
||||
H == 'Elixir.SystemLimitError' ->
|
||||
Expr = { 'or', Meta, [
|
||||
erlang_rescue_guard_for(Meta, Var, H),
|
||||
exception_compare(Meta, Var, H)
|
||||
] },
|
||||
Expr = do_or(Meta,
|
||||
erlang_rescue_guard_for(Meta, Var, H),
|
||||
exception_compare(Meta, Var, H)),
|
||||
rescue_each_ref(Meta, Var, T, Elixir, [Expr|Erlang], false, S);
|
||||
|
||||
rescue_each_ref(Meta, Var, [H|T], Elixir, Erlang, Safe, S) when is_atom(H) ->
|
||||
rescue_each_ref(Meta, Var, T, [exception_compare(Meta, Var, H)|Elixir], Erlang, Safe, S);
|
||||
|
||||
rescue_each_ref(Meta, Var, [H|T], Elixir, Erlang, Safe, S) ->
|
||||
case elixir_translator:translate_each(H, S) of
|
||||
case elixir_translator:translate(H, S) of
|
||||
{ { atom, _, Atom }, _ } ->
|
||||
rescue_each_ref(Meta, Var, [Atom|T], Elixir, Erlang, Safe, S);
|
||||
_ ->
|
||||
@@ -182,65 +141,58 @@ erlang_rescues() ->
|
||||
].
|
||||
|
||||
erlang_rescue_guard_for(Meta, Var, List) when is_list(List) ->
|
||||
join(Meta, 'or', [erlang_rescue_guard_for(Meta, Var, X) || X <- List]);
|
||||
join(Meta, fun do_or/3, [erlang_rescue_guard_for(Meta, Var, X) || X <- List]);
|
||||
|
||||
erlang_rescue_guard_for(Meta, Var, 'Elixir.UndefinedFunctionError') ->
|
||||
{ '==', Meta, [Var, undef] };
|
||||
{ erl(Meta, '=='), Meta, [Var, undef] };
|
||||
|
||||
erlang_rescue_guard_for(Meta, Var, 'Elixir.FunctionClauseError') ->
|
||||
{ '==', Meta, [Var, function_clause] };
|
||||
{ erl(Meta, '=='), Meta, [Var, function_clause] };
|
||||
|
||||
erlang_rescue_guard_for(Meta, Var, 'Elixir.SystemLimitError') ->
|
||||
{ '==', Meta, [Var, system_limit] };
|
||||
{ erl(Meta, '=='), Meta, [Var, system_limit] };
|
||||
|
||||
erlang_rescue_guard_for(Meta, Var, 'Elixir.ArithmeticError') ->
|
||||
{ '==', Meta, [Var, badarith] };
|
||||
{ erl(Meta, '=='), Meta, [Var, badarith] };
|
||||
|
||||
erlang_rescue_guard_for(Meta, Var, 'Elixir.BadArityError') ->
|
||||
{ 'and', Meta, [
|
||||
{ is_tuple, Meta, [Var] },
|
||||
exception_compare(Meta, Var, badarity)
|
||||
] };
|
||||
do_and(Meta,
|
||||
{ erl(Meta, is_tuple), Meta, [Var] },
|
||||
exception_compare(Meta, Var, badarity));
|
||||
|
||||
erlang_rescue_guard_for(Meta, Var, 'Elixir.BadFunctionError') ->
|
||||
{ 'and', Meta, [
|
||||
{ is_tuple, Meta, [Var] },
|
||||
exception_compare(Meta, Var, badfun)
|
||||
] };
|
||||
do_and(Meta,
|
||||
{ erl(Meta, is_tuple), Meta, [Var] },
|
||||
exception_compare(Meta, Var, badfun));
|
||||
|
||||
erlang_rescue_guard_for(Meta, Var, 'Elixir.MatchError') ->
|
||||
{ 'and', Meta, [
|
||||
{ is_tuple, Meta, [Var] },
|
||||
exception_compare(Meta, Var, badmatch)
|
||||
] };
|
||||
do_and(Meta,
|
||||
{ erl(Meta, is_tuple), Meta, [Var] },
|
||||
exception_compare(Meta, Var, badmatch));
|
||||
|
||||
erlang_rescue_guard_for(Meta, Var, 'Elixir.CaseClauseError') ->
|
||||
{ 'and', Meta, [
|
||||
{ is_tuple, Meta, [Var] },
|
||||
exception_compare(Meta, Var, case_clause)
|
||||
] };
|
||||
do_and(Meta,
|
||||
{ erl(Meta, is_tuple), Meta, [Var] },
|
||||
exception_compare(Meta, Var, case_clause));
|
||||
|
||||
erlang_rescue_guard_for(Meta, Var, 'Elixir.TryClauseError') ->
|
||||
{ 'and', Meta, [
|
||||
{ is_tuple, Meta, [Var] },
|
||||
exception_compare(Meta, Var, try_clause)
|
||||
] };
|
||||
do_and(Meta,
|
||||
{ erl(Meta, is_tuple), Meta, [Var] },
|
||||
exception_compare(Meta, Var, try_clause));
|
||||
|
||||
erlang_rescue_guard_for(Meta, Var, 'Elixir.ArgumentError') ->
|
||||
{ 'or', Meta, [
|
||||
{ '==', Meta, [Var, badarg] },
|
||||
{ 'and', Meta, [
|
||||
{ is_tuple, Meta, [Var] },
|
||||
exception_compare(Meta, Var, badarg)
|
||||
] }
|
||||
] };
|
||||
do_or(Meta,
|
||||
{ erl(Meta, '=='), Meta, [Var, badarg] },
|
||||
do_and(Meta,
|
||||
{ erl(Meta, is_tuple), Meta, [Var] },
|
||||
exception_compare(Meta, Var, badarg)));
|
||||
|
||||
erlang_rescue_guard_for(Meta, Var, 'Elixir.ErlangError') ->
|
||||
IsNotTuple = { 'not', Meta, [{ is_tuple, Meta, [Var] }] },
|
||||
IsException = { '!=', Meta, [
|
||||
{ { '.', Meta, [ erlang, element ] }, Meta, [2, Var] }, '__exception__'
|
||||
IsNotTuple = { erl(Meta, 'not'), Meta, [{ erl(Meta, is_tuple), Meta, [Var] }] },
|
||||
IsException = { erl(Meta, '/='), Meta, [
|
||||
{ erl(Meta, element), Meta, [2, Var] }, '__exception__'
|
||||
] },
|
||||
{ 'or', Meta, [IsNotTuple, IsException] }.
|
||||
do_or(Meta, IsNotTuple, IsException).
|
||||
|
||||
%% Helpers
|
||||
|
||||
@@ -253,27 +205,24 @@ format_error({ rescue_no_match, Var, Alias }) ->
|
||||
is_var({ Name, _, Atom }) when is_atom(Name), is_atom(Atom) -> true;
|
||||
is_var(_) -> false.
|
||||
|
||||
is_erl_var_or_atom({ Kind, Line, Name }) when Kind == var orelse Kind == atom, is_integer(Line), is_atom(Name) -> true;
|
||||
is_erl_var_or_atom(_) -> false.
|
||||
|
||||
is_valid_rescue_list(Right, S) when is_list(Right) ->
|
||||
{ TRight, _ } = elixir_translator:translate(Right, S),
|
||||
lists:all(fun is_erl_var_or_atom/1, TRight).
|
||||
|
||||
exception_compare(Meta, Var, Expr) ->
|
||||
{ '==', Meta, [
|
||||
{ { '.', Meta, [ erlang, element ] }, Meta, [1, Var] },
|
||||
{ erl(Meta, '=='), Meta, [
|
||||
{ erl(Meta, element), Meta, [1, Var] },
|
||||
Expr
|
||||
] }.
|
||||
|
||||
join(Meta, Kind, [H|T]) ->
|
||||
lists:foldl(fun(X, Acc) -> { Kind, Meta, [Acc, X] } end, H, T).
|
||||
lists:foldl(fun(X, Acc) -> Kind(Meta, Acc, X) end, H, T).
|
||||
|
||||
reverse_join(Meta, Kind, [H|T]) ->
|
||||
lists:foldl(fun(X, Acc) -> { Kind, Meta, [X, Acc] } end, H, T).
|
||||
join_when(Meta, [H|T]) ->
|
||||
lists:foldl(fun(X, Acc) -> { 'when', Meta, [X, Acc] } end, H, T).
|
||||
|
||||
prepend_to_block(_Meta, Expr, { '__block__', Meta, Args }) ->
|
||||
{ '__block__', Meta, [Expr|Args] };
|
||||
|
||||
prepend_to_block(Meta, Expr, Args) ->
|
||||
{ '__block__', Meta, [Expr, Args] }.
|
||||
|
||||
erl(Meta, Op) -> { '.', Meta, [erlang, Op] }.
|
||||
do_or(Meta, Left, Right) -> { '__op__', Meta, ['orelse', Left, Right] }.
|
||||
do_and(Meta, Left, Right) -> { '__op__', Meta, ['andalso', Left, Right] }.
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
%% Convenience functions used throughout elixir source code
|
||||
%% for ast manipulation and querying.
|
||||
-module(elixir_utils).
|
||||
-export([elixir_to_erl/1, elixir_to_erl/2,
|
||||
get_line/1, split_last/1, elixir_to_erl/3,
|
||||
-export([elixir_to_erl/1, get_line/1, split_last/1,
|
||||
characters_to_list/1, characters_to_binary/1,
|
||||
cons_to_list/1, list_to_cons/2, list_to_cons/3,
|
||||
convert_to_boolean/5, returns_boolean/1,
|
||||
convert_to_boolean/4, returns_boolean/1,
|
||||
file_type/1, file_type/2, relative_to_cwd/1]).
|
||||
-include("elixir.hrl").
|
||||
-include_lib("kernel/include/file.hrl").
|
||||
@@ -52,58 +50,34 @@ characters_to_binary(Data) ->
|
||||
false -> 'Elixir.String':'from_char_list!'(Data)
|
||||
end.
|
||||
|
||||
%% List conversion
|
||||
|
||||
cons_to_list({ nil, _ }) -> [];
|
||||
cons_to_list({ cons, _, Left, Right }) -> [Left|cons_to_list(Right)].
|
||||
|
||||
list_to_cons(Line, List) ->
|
||||
list_to_cons(Line, List, { nil, Line }).
|
||||
|
||||
list_to_cons(Line, List, Tail) ->
|
||||
lists:foldr(fun(X, Acc) ->
|
||||
{ cons, Line, X, Acc }
|
||||
end, Tail, List).
|
||||
|
||||
%% erl <-> elixir
|
||||
|
||||
elixir_to_erl(Tree) ->
|
||||
elixir_to_erl(Tree, fun(X) -> error({ badarg, X }) end).
|
||||
elixir_to_erl(Tree) when is_tuple(Tree) ->
|
||||
{ tuple, 0, [elixir_to_erl(X) || X <- tuple_to_list(Tree)] };
|
||||
|
||||
elixir_to_erl(Line, Tree, S) ->
|
||||
elixir_to_erl(Tree, fun
|
||||
(X) when is_function(X) ->
|
||||
elixir_errors:compile_error(Line, S#elixir_scope.file,
|
||||
"anonymous functions cannot be translated into a quoted expression, got: ~ts",
|
||||
['Elixir.Kernel':inspect(X)]);
|
||||
(X) ->
|
||||
elixir_errors:compile_error(Line, S#elixir_scope.file,
|
||||
"cannot translate ~ts into a quoted expression",
|
||||
['Elixir.Kernel':inspect(X)])
|
||||
end).
|
||||
|
||||
elixir_to_erl(Tree, Fun) when is_tuple(Tree) ->
|
||||
{ tuple, 0, [elixir_to_erl(X, Fun) || X <- tuple_to_list(Tree)] };
|
||||
|
||||
elixir_to_erl([], _Fun) ->
|
||||
elixir_to_erl([]) ->
|
||||
{ nil, 0 };
|
||||
|
||||
elixir_to_erl(Tree, Fun) when is_list(Tree) ->
|
||||
elixir_to_erl_cons_1(Tree, [], Fun);
|
||||
elixir_to_erl(Tree) when is_list(Tree) ->
|
||||
elixir_to_erl_cons_1(Tree, []);
|
||||
|
||||
elixir_to_erl(Tree, _Fun) when is_atom(Tree) ->
|
||||
elixir_to_erl(Tree) when is_atom(Tree) ->
|
||||
{ atom, 0, Tree };
|
||||
|
||||
elixir_to_erl(Tree, _Fun) when is_integer(Tree) ->
|
||||
elixir_to_erl(Tree) when is_integer(Tree) ->
|
||||
{ integer, 0, Tree };
|
||||
|
||||
elixir_to_erl(Tree, _Fun) when is_float(Tree) ->
|
||||
elixir_to_erl(Tree) when is_float(Tree) ->
|
||||
{ float, 0, Tree };
|
||||
|
||||
elixir_to_erl(Tree, _Fun) when is_binary(Tree) ->
|
||||
elixir_to_erl(Tree) when is_binary(Tree) ->
|
||||
%% Note that our binaries are utf-8 encoded and we are converting
|
||||
%% to a list using binary_to_list. The reason for this is that Erlang
|
||||
%% considers a string in a binary to be encoded in latin1, so the bytes
|
||||
%% are not changed in any fashion.
|
||||
{ bin, 0, [{ bin_element, 0, { string, 0, binary_to_list(Tree) }, default, default }] };
|
||||
|
||||
elixir_to_erl(Function, Fun) when is_function(Function) ->
|
||||
elixir_to_erl(Function) when is_function(Function) ->
|
||||
case (erlang:fun_info(Function, type) == { type, external }) andalso
|
||||
(erlang:fun_info(Function, env) == { env, [] }) of
|
||||
true ->
|
||||
@@ -116,18 +90,21 @@ elixir_to_erl(Function, Fun) when is_function(Function) ->
|
||||
{ atom, 0, Name },
|
||||
{ integer, 0, Arity } } };
|
||||
false ->
|
||||
Fun(Function)
|
||||
error(badarg)
|
||||
end;
|
||||
|
||||
elixir_to_erl(Other, Fun) ->
|
||||
Fun(Other).
|
||||
elixir_to_erl(Pid) when is_pid(Pid) ->
|
||||
?wrap_call(0, erlang, binary_to_term, [elixir_utils:elixir_to_erl(term_to_binary(Pid))]);
|
||||
|
||||
elixir_to_erl_cons_1([H|T], Acc, Fun) -> elixir_to_erl_cons_1(T, [H|Acc], Fun);
|
||||
elixir_to_erl_cons_1(Other, Acc, Fun) -> elixir_to_erl_cons_2(Acc, elixir_to_erl(Other, Fun), Fun).
|
||||
elixir_to_erl(_Other) ->
|
||||
error(badarg).
|
||||
|
||||
elixir_to_erl_cons_2([H|T], Acc, Fun) ->
|
||||
elixir_to_erl_cons_2(T, { cons, 0, elixir_to_erl(H, Fun), Acc }, Fun);
|
||||
elixir_to_erl_cons_2([], Acc, _Fun) ->
|
||||
elixir_to_erl_cons_1([H|T], Acc) -> elixir_to_erl_cons_1(T, [H|Acc]);
|
||||
elixir_to_erl_cons_1(Other, Acc) -> elixir_to_erl_cons_2(Acc, elixir_to_erl(Other)).
|
||||
|
||||
elixir_to_erl_cons_2([H|T], Acc) ->
|
||||
elixir_to_erl_cons_2(T, { cons, 0, elixir_to_erl(H), Acc });
|
||||
elixir_to_erl_cons_2([], Acc) ->
|
||||
Acc.
|
||||
|
||||
%% Boolean checks
|
||||
@@ -164,20 +141,21 @@ returns_boolean({ 'case', _, _, Clauses }) ->
|
||||
|
||||
returns_boolean(_) -> false.
|
||||
|
||||
convert_to_boolean(Line, Expr, Bool, InGuard, S) when is_integer(Line) ->
|
||||
convert_to_boolean(Line, Expr, Bool, S) when is_integer(Line) ->
|
||||
case { returns_boolean(Expr), Bool } of
|
||||
{ true, true } -> { Expr, S };
|
||||
{ true, false } -> { { op, Line, 'not', Expr }, S };
|
||||
_ -> do_convert_to_boolean(Line, Expr, Bool, InGuard, S)
|
||||
_ -> do_convert_to_boolean(Line, Expr, Bool, S)
|
||||
end.
|
||||
|
||||
%% Notice we use a temporary var and bundle nil
|
||||
%% and false checks in the same clause since
|
||||
%% it makes dialyzer happy.
|
||||
do_convert_to_boolean(Line, Expr, Bool, false, S) ->
|
||||
Any = { var, Line, '_' },
|
||||
{ Var, TS } = elixir_scope:build_erl_var(Line, S),
|
||||
OrElse = do_guarded_convert_to_boolean(Line, Var, 'orelse', '=='),
|
||||
do_convert_to_boolean(Line, Expr, Bool, S) ->
|
||||
{ Name, _, TS } = elixir_scope:build_var('_', S),
|
||||
Var = { var, Line, Name },
|
||||
Any = { var, Line, '_' },
|
||||
OrElse = do_guarded_convert_to_boolean(Line, Var, 'orelse', '=='),
|
||||
|
||||
FalseResult = { atom,Line,not Bool },
|
||||
TrueResult = { atom,Line,Bool },
|
||||
@@ -185,13 +163,7 @@ do_convert_to_boolean(Line, Expr, Bool, false, S) ->
|
||||
{ { 'case', Line, Expr, [
|
||||
{ clause, Line, [Var], [[OrElse]], [FalseResult] },
|
||||
{ clause, Line, [Any], [], [TrueResult] }
|
||||
] }, TS };
|
||||
|
||||
do_convert_to_boolean(Line, Expr, true, true, S) ->
|
||||
{ do_guarded_convert_to_boolean(Line, Expr, 'andalso', '/='), S };
|
||||
|
||||
do_convert_to_boolean(Line, Expr, false, true, S) ->
|
||||
{ do_guarded_convert_to_boolean(Line, Expr, 'orelse', '=='), S }.
|
||||
] }, TS }.
|
||||
|
||||
do_guarded_convert_to_boolean(Line, Expr, Op, Comp) ->
|
||||
Left = { op, Line, Comp, Expr, { atom, Line, false } },
|
||||
|
||||
@@ -27,5 +27,6 @@ defmodule KernelTest do
|
||||
doctest Stream
|
||||
doctest String
|
||||
doctest Tuple
|
||||
doctest URI
|
||||
doctest Version
|
||||
end
|
||||
|
||||
@@ -10,31 +10,48 @@ defmodule BehaviourTest do
|
||||
defcallback first(integer) :: integer
|
||||
|
||||
@doc "Foo"
|
||||
defcallback foo(atom, binary) :: binary
|
||||
defcallback foo(atom(), binary) :: binary
|
||||
|
||||
@doc "Bar"
|
||||
defcallback bar(External.hello, my_var :: binary) :: binary
|
||||
|
||||
defcallback guarded(my_var) :: my_var when my_var: binary
|
||||
|
||||
defcallback orr(atom | integer) :: atom
|
||||
|
||||
defcallback literal(123, { atom }, :atom, [integer], true) :: atom
|
||||
|
||||
@doc "I should be last."
|
||||
defmacrocallback last(integer) :: Macro.t
|
||||
end
|
||||
|
||||
test :docs do
|
||||
docs = Sample.__behaviour__(:docs)
|
||||
assert docs == [
|
||||
{{:first, 1}, 10, :def, "I should be first."},
|
||||
{{:foo, 2}, 13, :def, "Foo"},
|
||||
{{:bar, 2}, 16, :def, "Bar"},
|
||||
{{:last, 1}, 19, :defmacro, "I should be last."}
|
||||
]
|
||||
assert [
|
||||
{{:first, 1}, 10, :def, _, "I should be first."},
|
||||
{{:foo, 2}, 13, :def, _, "Foo"},
|
||||
{{:bar, 2}, 16, :def, _, "Bar"},
|
||||
{{:guarded, 1}, 18, :def, _, nil},
|
||||
{{:orr, 1}, 20, :def, _, nil},
|
||||
{{:literal, 5}, 22, :def, _, nil},
|
||||
{{:last, 1}, 25, :defmacro, _, "I should be last."}
|
||||
] = docs
|
||||
|
||||
assert [{ :integer, _, nil }] = List.keyfind(docs, { :first, 1 }, 0) |> elem(3)
|
||||
assert [{ :atom, _, nil }, { :binary, _, nil }] = List.keyfind(docs, { :foo, 2 }, 0) |> elem(3)
|
||||
assert [{ :hello, _, nil }, { :my_var, _, nil }] = List.keyfind(docs, { :bar, 2 }, 0) |> elem(3)
|
||||
assert [{ :my_var, _, nil }] = List.keyfind(docs, { :guarded, 1 }, 0) |> elem(3)
|
||||
assert [{ :atom, _, nil }] = List.keyfind(docs, { :orr, 1 }, 0) |> elem(3)
|
||||
assert [{ :int1, _, nil }, { :tuple2, _, nil }, { :atom3, _, nil }, { :list4, _, nil }, { :bool5, _, nil} ] = List.keyfind(docs, { :literal, 5 }, 0) |> elem(3)
|
||||
assert [{ :integer, _, nil }] = List.keyfind(docs, { :last, 1 }, 0) |> elem(3)
|
||||
end
|
||||
|
||||
test :callbacks do
|
||||
assert Sample.__behaviour__(:callbacks) == [first: 1, foo: 2, bar: 2, "MACRO-last": 2]
|
||||
assert Sample.__behaviour__(:callbacks) == [first: 1, foo: 2, bar: 2, guarded: 1, orr: 1, literal: 5, "MACRO-last": 2]
|
||||
end
|
||||
|
||||
test :specs do
|
||||
assert length(Keyword.get_values(Sample.module_info[:attributes], :callback)) == 4
|
||||
assert length(Keyword.get_values(Sample.module_info[:attributes], :callback)) == 7
|
||||
end
|
||||
|
||||
test :default_is_not_supported do
|
||||
|
||||
@@ -13,7 +13,7 @@ defmodule CodeTest do
|
||||
|
||||
contents = quote do
|
||||
defmodule CodeTest.Sample do
|
||||
def eval_quoted_info, do: { __MODULE__, __FILE__, __ENV__.line }
|
||||
def eval_quoted_info, do: { __MODULE__, __ENV__.file, __ENV__.line }
|
||||
end
|
||||
end
|
||||
|
||||
@@ -69,10 +69,6 @@ defmodule CodeTest do
|
||||
assert Code.require_file(fixture_path("code_sample.exs")) != nil
|
||||
end
|
||||
|
||||
test :file do
|
||||
assert :filename.absname(__FILE__) == __FILE__
|
||||
end
|
||||
|
||||
test :string_to_quoted do
|
||||
assert Code.string_to_quoted("1 + 2") == { :ok, { :+, [line: 1], [1, 2] } }
|
||||
assert { :error, _ } = Code.string_to_quoted("a.1")
|
||||
@@ -96,7 +92,7 @@ defmodule CodeTest do
|
||||
end
|
||||
|
||||
test :compile_source do
|
||||
assert __MODULE__.__info__(:compile)[:source] == String.to_char_list!(__FILE__)
|
||||
assert __MODULE__.__info__(:compile)[:source] == String.to_char_list!(__ENV__.file)
|
||||
end
|
||||
|
||||
test :compile_info_returned_with_source_accessible_through_keyword_module do
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
Code.require_file "test_helper.exs", __DIR__
|
||||
|
||||
defmodule DictTest.Common do
|
||||
defmacro __using__(module) do
|
||||
defmacro __using__(_) do
|
||||
quote location: :keep do
|
||||
# Most underlying Dict implementations have no key
|
||||
# order guarantees, sort them before we compare:
|
||||
@@ -12,18 +12,18 @@ defmodule DictTest.Common do
|
||||
end
|
||||
end
|
||||
|
||||
defp empty_dict, do: unquote(module).new
|
||||
defp empty_dict, do: dict_impl.new
|
||||
|
||||
defp new_dict(list // [{"first_key", 1}, {"second_key", 2}]) do
|
||||
unquote(module).new list
|
||||
dict_impl.new list
|
||||
end
|
||||
|
||||
defp new_dict(list, transform) do
|
||||
unquote(module).new list, transform
|
||||
dict_impl.new list, transform
|
||||
end
|
||||
|
||||
defp int_dict do
|
||||
unquote(module).new [{1,1}]
|
||||
dict_impl.new [{1,1}]
|
||||
end
|
||||
|
||||
test :access do
|
||||
@@ -333,19 +333,13 @@ defmodule DictTest.Common do
|
||||
refute Enum.member?(new_dict([{1,1}]), { 1, 1.0 })
|
||||
refute Enum.member?(new_dict([{1,1}]), { 1.0, 1.0 })
|
||||
end
|
||||
|
||||
test "unsupported dict" do
|
||||
assert_raise ArgumentError, "unsupported dict: :bad_dict", fn ->
|
||||
Dict.to_list :bad_dict
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defmodule Dict.HashDictTest do
|
||||
use ExUnit.Case, async: true
|
||||
use DictTest.Common, HashDict
|
||||
use DictTest.Common
|
||||
|
||||
doctest Dict
|
||||
defp dict_impl, do: HashDict
|
||||
@@ -353,7 +347,7 @@ end
|
||||
|
||||
defmodule Dict.ListDictTest do
|
||||
use ExUnit.Case, async: true
|
||||
use DictTest.Common, ListDict
|
||||
use DictTest.Common
|
||||
|
||||
doctest Dict
|
||||
defp dict_impl, do: ListDict
|
||||
|
||||
@@ -97,7 +97,9 @@ defmodule EnumTest.List do
|
||||
assert Enum.drop([1, 2, 3], 2) == [3]
|
||||
assert Enum.drop([1, 2, 3], 3) == []
|
||||
assert Enum.drop([1, 2, 3], 4) == []
|
||||
assert Enum.drop([1, 2, 3], -1) == [3]
|
||||
assert Enum.drop([1, 2, 3], -1) == [1, 2]
|
||||
assert Enum.drop([1, 2, 3], -2) == [1]
|
||||
assert Enum.drop([1, 2, 3], -4) == []
|
||||
assert Enum.drop([], 3) == []
|
||||
end
|
||||
|
||||
@@ -141,11 +143,6 @@ defmodule EnumTest.List do
|
||||
assert Enum.fetch([2, 4, 6], -4) == :error
|
||||
end
|
||||
|
||||
test :first do
|
||||
assert Enum.first([]) == nil
|
||||
assert Enum.first([1, 2, 3]) == 1
|
||||
end
|
||||
|
||||
test :filter do
|
||||
assert Enum.filter([1, 2, 3], fn(x) -> rem(x, 2) == 0 end) == [2]
|
||||
assert Enum.filter([2, 4, 6], fn(x) -> rem(x, 2) == 0 end) == [2, 4, 6]
|
||||
@@ -165,6 +162,16 @@ defmodule EnumTest.List do
|
||||
test :flat_map do
|
||||
assert Enum.flat_map([], fn(x) -> [x, x] end) == []
|
||||
assert Enum.flat_map([1, 2, 3], fn(x) -> [x, x] end) == [1, 1, 2, 2, 3, 3]
|
||||
assert Enum.flat_map([1, 2, 3], fn(x) -> x..x+1 end) == [1, 2, 2, 3, 3, 4]
|
||||
end
|
||||
|
||||
test :flat_map_reduce do
|
||||
assert Enum.flat_map_reduce([1, 2, 3], 0, &{ [&1, &2], &1 + &2 }) ==
|
||||
{ [1, 0, 2, 1, 3, 3], 6 }
|
||||
|
||||
assert Enum.flat_map_reduce(1..100, 0, fn i, acc ->
|
||||
if acc < 3, do: { [i], acc + 1 }, else: { :halt, acc }
|
||||
end) == { [1,2,3], 3 }
|
||||
end
|
||||
|
||||
test :reduce do
|
||||
@@ -173,10 +180,16 @@ defmodule EnumTest.List do
|
||||
|
||||
assert Enum.reduce([1, 2, 3], fn(x, acc) -> x + acc end) == 6
|
||||
assert_raise Enum.EmptyError, fn ->
|
||||
assert Enum.reduce([], fn(x, acc) -> x + acc end)
|
||||
Enum.reduce([], fn(x, acc) -> x + acc end)
|
||||
end
|
||||
end
|
||||
|
||||
test :intersperse do
|
||||
assert Enum.intersperse([], true) == []
|
||||
assert Enum.intersperse([1], true) == [1]
|
||||
assert Enum.intersperse([1,2,3], true) == [1, true, 2, true, 3]
|
||||
end
|
||||
|
||||
test :join do
|
||||
assert Enum.join([], " = ") == ""
|
||||
assert Enum.join([1, 2, 3], " = ") == "1 = 2 = 3"
|
||||
@@ -219,6 +232,15 @@ defmodule EnumTest.List do
|
||||
test :reverse do
|
||||
assert Enum.reverse([]) == []
|
||||
assert Enum.reverse([1, 2, 3]) == [3, 2, 1]
|
||||
assert Enum.reverse([1, 2, 3], [4, 5, 6]) == [3, 2, 1, 4, 5, 6]
|
||||
end
|
||||
|
||||
test :scan do
|
||||
assert Enum.scan([1,2,3,4,5], &(&1 + &2)) == [1,3,6,10,15]
|
||||
assert Enum.scan([], &(&1 + &2)) == []
|
||||
|
||||
assert Enum.scan([1,2,3,4,5], 0, &(&1 + &2)) == [1,3,6,10,15]
|
||||
assert Enum.scan([], 0, &(&1 + &2)) == []
|
||||
end
|
||||
|
||||
test :shuffle do
|
||||
@@ -260,7 +282,9 @@ defmodule EnumTest.List do
|
||||
assert Enum.take([1, 2, 3], 2) == [1, 2]
|
||||
assert Enum.take([1, 2, 3], 3) == [1, 2, 3]
|
||||
assert Enum.take([1, 2, 3], 4) == [1, 2, 3]
|
||||
assert Enum.take([1, 2, 3], -1) == [1, 2]
|
||||
assert Enum.take([1, 2, 3], -1) == [3]
|
||||
assert Enum.take([1, 2, 3], -2) == [2, 3]
|
||||
assert Enum.take([1, 2, 3], -4) == [1, 2, 3]
|
||||
assert Enum.take([], 3) == []
|
||||
end
|
||||
|
||||
@@ -291,10 +315,10 @@ defmodule EnumTest.List do
|
||||
test :zip do
|
||||
assert Enum.zip([:a, :b], [1, 2]) == [{:a, 1}, {:b, 2}]
|
||||
assert Enum.zip([:a, :b], [1, 2, 3, 4]) == [{:a, 1}, {:b, 2}]
|
||||
assert Enum.zip([:a, :b, :c, :d], [1, 2]) == [{:a, 1}, {:b, 2}, {:c, nil}, {:d, nil}]
|
||||
assert Enum.zip([:a, :b, :c, :d], [1, 2]) == [{:a, 1}, {:b, 2}]
|
||||
assert Enum.zip([], [1]) == []
|
||||
assert Enum.zip([1], []) == [{ 1, nil }]
|
||||
assert Enum.zip([], []) == []
|
||||
assert Enum.zip([1], []) == []
|
||||
assert Enum.zip([], []) == []
|
||||
end
|
||||
|
||||
test :with_index do
|
||||
@@ -307,7 +331,7 @@ defmodule EnumTest.List do
|
||||
assert Enum.max([1, 2, 3]) == 3
|
||||
assert Enum.max([1, [], :a, {}]) == []
|
||||
assert_raise Enum.EmptyError, fn ->
|
||||
assert Enum.max([])
|
||||
Enum.max([])
|
||||
end
|
||||
end
|
||||
|
||||
@@ -323,7 +347,7 @@ defmodule EnumTest.List do
|
||||
assert Enum.min([1, 2, 3]) == 1
|
||||
assert Enum.min([[], :a, {}]) == :a
|
||||
assert_raise Enum.EmptyError, fn ->
|
||||
assert Enum.min([])
|
||||
Enum.min([])
|
||||
end
|
||||
end
|
||||
|
||||
@@ -334,20 +358,21 @@ defmodule EnumTest.List do
|
||||
end
|
||||
end
|
||||
|
||||
test :chunks do
|
||||
assert Enum.chunks([1, 2, 3, 4, 5], 2) == [[1, 2], [3, 4]]
|
||||
assert Enum.chunks([1, 2, 3, 4, 5], 2, 2, [6]) == [[1, 2], [3, 4], [5, 6]]
|
||||
assert Enum.chunks([1, 2, 3, 4, 5, 6], 3, 2) == [[1, 2, 3], [3, 4, 5]]
|
||||
assert Enum.chunks([1, 2, 3, 4, 5, 6], 2, 3) == [[1, 2], [4, 5]]
|
||||
assert Enum.chunks([1, 2, 3, 4, 5, 6], 3, 2, []) == [[1, 2, 3], [3, 4, 5], [5, 6]]
|
||||
assert Enum.chunks([1, 2, 3, 4, 5], 4, 4, 6..10) == [[1, 2, 3, 4], [5, 6, 7, 8]]
|
||||
test :chunk do
|
||||
assert Enum.chunk([1, 2, 3, 4, 5], 2) == [[1, 2], [3, 4]]
|
||||
assert Enum.chunk([1, 2, 3, 4, 5], 2, 2, [6]) == [[1, 2], [3, 4], [5, 6]]
|
||||
assert Enum.chunk([1, 2, 3, 4, 5, 6], 3, 2) == [[1, 2, 3], [3, 4, 5]]
|
||||
assert Enum.chunk([1, 2, 3, 4, 5, 6], 2, 3) == [[1, 2], [4, 5]]
|
||||
assert Enum.chunk([1, 2, 3, 4, 5, 6], 3, 2, []) == [[1, 2, 3], [3, 4, 5], [5, 6]]
|
||||
assert Enum.chunk([1, 2, 3, 4, 5, 6], 3, 3, []) == [[1, 2, 3], [4, 5, 6]]
|
||||
assert Enum.chunk([1, 2, 3, 4, 5], 4, 4, 6..10) == [[1, 2, 3, 4], [5, 6, 7, 8]]
|
||||
end
|
||||
|
||||
test :chunks_by do
|
||||
assert Enum.chunks_by([1, 2, 2, 3, 4, 4, 6, 7, 7], &(rem(&1, 2) == 1)) == [[1], [2, 2], [3], [4, 4, 6], [7, 7]]
|
||||
assert Enum.chunks_by([1, 2, 3, 4], fn _ -> true end) == [[1, 2, 3, 4]]
|
||||
assert Enum.chunks_by([], fn _ -> true end) == []
|
||||
assert Enum.chunks_by([1], fn _ -> true end) == [[1]]
|
||||
test :chunk_by do
|
||||
assert Enum.chunk_by([1, 2, 2, 3, 4, 4, 6, 7, 7], &(rem(&1, 2) == 1)) == [[1], [2, 2], [3], [4, 4, 6], [7, 7]]
|
||||
assert Enum.chunk_by([1, 2, 3, 4], fn _ -> true end) == [[1, 2, 3, 4]]
|
||||
assert Enum.chunk_by([], fn _ -> true end) == []
|
||||
assert Enum.chunk_by([1], fn _ -> true end) == [[1]]
|
||||
end
|
||||
|
||||
test :slice do
|
||||
@@ -371,14 +396,14 @@ defmodule EnumTest.List do
|
||||
assert Enum.slice([1,2,3,4,5], 0..1) == [1, 2]
|
||||
assert Enum.slice([1,2,3,4,5], 0..2) == [1, 2, 3]
|
||||
assert Enum.slice([1,2,3,4,5], 1..2) == [2, 3]
|
||||
assert Enum.slice([1,2,3,4,5], 1..0) == nil
|
||||
assert Enum.slice([1,2,3,4,5], 1..0) == nil
|
||||
assert Enum.slice([1,2,3,4,5], 2..5) == [3, 4, 5]
|
||||
assert Enum.slice([1,2,3,4,5], 4..4) == [5]
|
||||
assert Enum.slice([1,2,3,4,5], 5..5) == []
|
||||
assert Enum.slice([1,2,3,4,5], 6..5) == nil
|
||||
assert Enum.slice([1,2,3,4,5], 6..0) == nil
|
||||
assert Enum.slice([1,2,3,4,5], -6..0) == nil
|
||||
assert Enum.slice([1,2,3,4,5], -6..5) == nil
|
||||
assert Enum.slice([1,2,3,4,5], 6..5) == nil
|
||||
assert Enum.slice([1,2,3,4,5], 6..0) == nil
|
||||
assert Enum.slice([1,2,3,4,5], -6..0) == nil
|
||||
assert Enum.slice([1,2,3,4,5], -6..5) == nil
|
||||
assert Enum.slice([1,2,3,4,5], -5..-1) == [1, 2, 3, 4, 5]
|
||||
assert Enum.slice([1,2,3,4,5], -5..-3) == [1, 2, 3]
|
||||
assert Enum.slice([1,2,3,4,5], -6..-1) == nil
|
||||
@@ -390,25 +415,25 @@ defmodule EnumTest.Range do
|
||||
use ExUnit.Case, async: true
|
||||
|
||||
test :all? do
|
||||
range = Range.new(first: 0, last: 5)
|
||||
range = 0..5
|
||||
refute Enum.all?(range, fn(x) -> rem(x, 2) == 0 end)
|
||||
|
||||
range = Range.new(first: 0, last: 1)
|
||||
range = 0..1
|
||||
assert Enum.all?(range, fn(x) -> x < 2 end)
|
||||
assert Enum.all?(range)
|
||||
|
||||
range = Range.new(first: 1, last: 0)
|
||||
range = 1..0
|
||||
assert Enum.all?(range)
|
||||
end
|
||||
|
||||
test :any? do
|
||||
range = Range.new(first: 0, last: 5)
|
||||
range = 0..5
|
||||
refute Enum.any?(range, &(&1 > 10))
|
||||
|
||||
range = Range.new(first: 0, last: 5)
|
||||
range = 0..5
|
||||
assert Enum.any?(range, &(&1 > 3))
|
||||
|
||||
range = Range.new(first: 1, last: 0)
|
||||
range = 1..0
|
||||
assert Enum.any?(range)
|
||||
end
|
||||
|
||||
@@ -421,58 +446,61 @@ defmodule EnumTest.Range do
|
||||
assert Enum.fetch!(-2..-6, 4) == -6
|
||||
|
||||
assert_raise Enum.OutOfBoundsError, fn ->
|
||||
assert Enum.fetch!(2..6, 8)
|
||||
Enum.fetch!(2..6, 8)
|
||||
end
|
||||
|
||||
assert_raise Enum.OutOfBoundsError, fn ->
|
||||
assert Enum.fetch!(-2..-6, 8)
|
||||
Enum.fetch!(-2..-6, 8)
|
||||
end
|
||||
|
||||
assert_raise Enum.OutOfBoundsError, fn ->
|
||||
assert Enum.fetch!(2..6, -8)
|
||||
Enum.fetch!(2..6, -8)
|
||||
end
|
||||
end
|
||||
|
||||
test :count do
|
||||
range = Range.new(first: 1, last: 5)
|
||||
range = 1..5
|
||||
assert Enum.count(range) == 5
|
||||
range = Range.new(first: 1, last: 1)
|
||||
range = 1..1
|
||||
assert Enum.count(range) == 1
|
||||
end
|
||||
|
||||
test :count_fun do
|
||||
range = Range.new(first: 1, last: 5)
|
||||
range = 1..5
|
||||
assert Enum.count(range, fn(x) -> rem(x, 2) == 0 end) == 2
|
||||
range = Range.new(first: 1, last: 1)
|
||||
range = 1..1
|
||||
assert Enum.count(range, fn(x) -> rem(x, 2) == 0 end) == 0
|
||||
end
|
||||
|
||||
test :drop do
|
||||
range = Range.new(first: 1, last: 3)
|
||||
range = 1..3
|
||||
assert Enum.drop(range, 0) == [1, 2, 3]
|
||||
assert Enum.drop(range, 1) == [2, 3]
|
||||
assert Enum.drop(range, 2) == [3]
|
||||
assert Enum.drop(range, 3) == []
|
||||
assert Enum.drop(range, 4) == []
|
||||
assert Enum.drop(range, -1) == [1, 2]
|
||||
assert Enum.drop(range, -2) == [1]
|
||||
assert Enum.drop(range, -4) == []
|
||||
|
||||
range = Range.new(first: 1, last: 0)
|
||||
range = 1..0
|
||||
assert Enum.drop(range, 3) == []
|
||||
end
|
||||
|
||||
test :drop_while do
|
||||
range = Range.new(first: 0, last: 6)
|
||||
range = 0..6
|
||||
assert Enum.drop_while(range, fn(x) -> x <= 3 end) == [4, 5, 6]
|
||||
assert Enum.drop_while(range, fn(_) -> false end) == [0, 1, 2, 3, 4, 5, 6]
|
||||
|
||||
range = Range.new(first: 0, last: 3)
|
||||
range = 0..3
|
||||
assert Enum.drop_while(range, fn(x) -> x <= 3 end) == []
|
||||
|
||||
range = Range.new(first: 1, last: 0)
|
||||
range = 1..0
|
||||
assert Enum.drop_while(range, fn(_) -> false end) == [1, 0]
|
||||
end
|
||||
|
||||
test :find do
|
||||
range = Range.new(first: 2, last: 6)
|
||||
range = 2..6
|
||||
assert Enum.find(range, fn(x) -> rem(x, 2) == 0 end) == 2
|
||||
assert Enum.find(range, fn(x) -> rem(x, 2) == 1 end) == 3
|
||||
assert Enum.find(range, fn _ -> false end) == nil
|
||||
@@ -480,37 +508,29 @@ defmodule EnumTest.Range do
|
||||
end
|
||||
|
||||
test :find_value do
|
||||
range = Range.new(first: 2, last: 6)
|
||||
range = 2..6
|
||||
assert Enum.find_value(range, fn(x) -> rem(x, 2) == 1 end)
|
||||
end
|
||||
|
||||
test :find_index do
|
||||
range = Range.new(first: 2, last: 6)
|
||||
range = 2..6
|
||||
assert Enum.find_index(range, fn(x) -> rem(x, 2) == 1 end) == 1
|
||||
end
|
||||
|
||||
test :empty? do
|
||||
range = Range.new(first: 1, last: 0)
|
||||
range = 1..0
|
||||
refute Enum.empty?(range)
|
||||
|
||||
range = Range.new(first: 1, last: 2)
|
||||
range = 1..2
|
||||
refute Enum.empty?(range)
|
||||
end
|
||||
|
||||
test :first do
|
||||
range = Range.new(first: 1, last: 0)
|
||||
assert Enum.first(range) == 1
|
||||
|
||||
range = Range.new(first: 1, last: 2)
|
||||
assert Enum.first(range) == 1
|
||||
end
|
||||
|
||||
test :each do
|
||||
try do
|
||||
range = Range.new(first: 1, last: 0)
|
||||
range = 1..0
|
||||
assert Enum.each(range, fn(x) -> x end) == :ok
|
||||
|
||||
range = Range.new(first: 1, last: 3)
|
||||
range = 1..3
|
||||
assert Enum.each(range, fn(x) -> Process.put(:enum_test_each, x * 2) end) == :ok
|
||||
assert Process.get(:enum_test_each) == 6
|
||||
after
|
||||
@@ -518,7 +538,7 @@ defmodule EnumTest.Range do
|
||||
end
|
||||
|
||||
try do
|
||||
range = Range.new(first: -1, last: -3)
|
||||
range = -1..-3
|
||||
assert Enum.each(range, fn(x) -> Process.put(:enum_test_each, x * 2) end) == :ok
|
||||
assert Process.get(:enum_test_each) == -6
|
||||
after
|
||||
@@ -527,109 +547,122 @@ defmodule EnumTest.Range do
|
||||
end
|
||||
|
||||
test :filter do
|
||||
range = Range.new(first: 1, last: 3)
|
||||
range = 1..3
|
||||
assert Enum.filter(range, fn(x) -> rem(x, 2) == 0 end) == [2]
|
||||
|
||||
range = Range.new(first: 1, last: 6)
|
||||
range = 1..6
|
||||
assert Enum.filter(range, fn(x) -> rem(x, 2) == 0 end) == [2, 4, 6]
|
||||
end
|
||||
|
||||
test :filter_with_match do
|
||||
range = Range.new(first: 1, last: 3)
|
||||
range = 1..3
|
||||
assert Enum.filter(range, &match?(1, &1)) == [1]
|
||||
assert Enum.filter(range, &match?(x when x < 3, &1)) == [1, 2]
|
||||
assert Enum.filter(range, &match?(_, &1)) == [1, 2, 3]
|
||||
end
|
||||
|
||||
test :filter_map do
|
||||
range = Range.new(first: 1, last: 3)
|
||||
range = 1..3
|
||||
assert Enum.filter_map(range, fn(x) -> rem(x, 2) == 0 end, &(&1 * 2)) == [4]
|
||||
|
||||
range = Range.new(first: 2, last: 6)
|
||||
range = 2..6
|
||||
assert Enum.filter_map(range, fn(x) -> rem(x, 2) == 0 end, &(&1 * 2)) == [4, 8, 12]
|
||||
end
|
||||
|
||||
test :flat_map do
|
||||
range = Range.new(first: 1, last: 3)
|
||||
range = 1..3
|
||||
assert Enum.flat_map(range, fn(x) -> [x, x] end) == [1, 1, 2, 2, 3, 3]
|
||||
end
|
||||
|
||||
test :reduce do
|
||||
range = Range.new(first: 1, last: 0)
|
||||
range = 1..0
|
||||
assert Enum.reduce(range, 1, fn(x, acc) -> x + acc end) == 2
|
||||
|
||||
range = Range.new(first: 1, last: 3)
|
||||
range = 1..3
|
||||
assert Enum.reduce(range, 1, fn(x, acc) -> x + acc end) == 7
|
||||
|
||||
range = Range.new(first: 1, last: 3)
|
||||
range = 1..3
|
||||
assert Enum.reduce(range, fn(x, acc) -> x + acc end) == 6
|
||||
end
|
||||
|
||||
test :reject do
|
||||
range = Range.new(first: 1, last: 3)
|
||||
range = 1..3
|
||||
assert Enum.reject(range, fn(x) -> rem(x, 2) == 0 end) == [1, 3]
|
||||
|
||||
range = Range.new(first: 1, last: 6)
|
||||
range = 1..6
|
||||
assert Enum.reject(range, fn(x) -> rem(x, 2) == 0 end) == [1, 3, 5]
|
||||
end
|
||||
|
||||
test :intersperse do
|
||||
range = 1..0
|
||||
assert Enum.intersperse(range, true) == [1, true, 0]
|
||||
|
||||
range = 1..3
|
||||
assert Enum.intersperse(range, false) == [1, false, 2, false, 3]
|
||||
end
|
||||
|
||||
test :join do
|
||||
range = Range.new(first: 1, last: 0)
|
||||
range = 1..0
|
||||
assert Enum.join(range, " = ") == "1 = 0"
|
||||
|
||||
range = Range.new(first: 1, last: 3)
|
||||
range = 1..3
|
||||
assert Enum.join(range, " = ") == "1 = 2 = 3"
|
||||
assert Enum.join(range) == "123"
|
||||
end
|
||||
|
||||
test :map_join do
|
||||
range = Range.new(first: 1, last: 0)
|
||||
range = 1..0
|
||||
assert Enum.map_join(range, " = ", &(&1 * 2)) == "2 = 0"
|
||||
|
||||
range = Range.new(first: 1, last: 3)
|
||||
range = 1..3
|
||||
assert Enum.map_join(range, " = ", &(&1 * 2)) == "2 = 4 = 6"
|
||||
assert Enum.map_join(range, &(&1 * 2)) == "246"
|
||||
end
|
||||
|
||||
test :map do
|
||||
range = Range.new(first: 1, last: 3)
|
||||
range = 1..3
|
||||
assert Enum.map(range, fn x -> x * 2 end) == [2, 4, 6]
|
||||
|
||||
range = Range.new(first: -1, last: -3)
|
||||
range = -1..-3
|
||||
assert Enum.map(range, fn x -> x * 2 end) == [-2, -4, -6]
|
||||
end
|
||||
|
||||
test :map_reduce do
|
||||
range = Range.new(first: 1, last: 0)
|
||||
range = 1..0
|
||||
assert Enum.map_reduce(range, 1, fn(x, acc) -> { x * 2, x + acc } end) == { [2, 0], 2 }
|
||||
|
||||
range = Range.new(first: 1, last: 3)
|
||||
range = 1..3
|
||||
assert Enum.map_reduce(range, 1, fn(x, acc) -> { x * 2, x + acc } end) == { [2, 4, 6], 7 }
|
||||
end
|
||||
|
||||
test :partition do
|
||||
range = Range.new(first: 1, last: 3)
|
||||
range = 1..3
|
||||
assert Enum.partition(range, fn(x) -> rem(x, 2) == 0 end) == { [2], [1, 3] }
|
||||
end
|
||||
|
||||
test :scan do
|
||||
assert Enum.scan(1..5, &(&1 + &2)) == [1,3,6,10,15]
|
||||
assert Enum.scan(1..5, 0, &(&1 + &2)) == [1,3,6,10,15]
|
||||
end
|
||||
|
||||
test :shuffle do
|
||||
# set a fixed seed so the test can be deterministic
|
||||
:random.seed(1374, 347975, 449264)
|
||||
assert Enum.shuffle(Range.new(first: 1, last: 5)) == [2, 4, 1, 5, 3]
|
||||
assert Enum.shuffle(1..5) == [2, 4, 1, 5, 3]
|
||||
end
|
||||
|
||||
test :sort do
|
||||
assert Enum.sort(Range.new(first: 3, last: 1)) == [1, 2, 3]
|
||||
assert Enum.sort(Range.new(first: 2, last: 1)) == [1, 2]
|
||||
assert Enum.sort(Range.new(first: 1, last: 1)) == [1]
|
||||
assert Enum.sort(3..1) == [1, 2, 3]
|
||||
assert Enum.sort(2..1) == [1, 2]
|
||||
assert Enum.sort(1..1) == [1]
|
||||
|
||||
assert Enum.sort(Range.new(first: 3, last: 1), &(&1 > &2)) == [3, 2, 1]
|
||||
assert Enum.sort(Range.new(first: 2, last: 1), &(&1 > &2)) == [2, 1]
|
||||
assert Enum.sort(Range.new(first: 1, last: 1), &(&1 > &2)) == [1]
|
||||
assert Enum.sort(3..1, &(&1 > &2)) == [3, 2, 1]
|
||||
assert Enum.sort(2..1, &(&1 > &2)) == [2, 1]
|
||||
assert Enum.sort(1..1, &(&1 > &2)) == [1]
|
||||
end
|
||||
|
||||
test :split do
|
||||
range = Range.new(first: 1, last: 3)
|
||||
range = 1..3
|
||||
assert Enum.split(range, 0) == { [], [1, 2, 3] }
|
||||
assert Enum.split(range, 1) == { [1], [2, 3] }
|
||||
assert Enum.split(range, 2) == { [1, 2], [3] }
|
||||
@@ -640,31 +673,34 @@ defmodule EnumTest.Range do
|
||||
assert Enum.split(range, -3) == { [], [1, 2, 3] }
|
||||
assert Enum.split(range, -10) == { [], [1, 2, 3] }
|
||||
|
||||
range = Range.new(first: 1, last: 0)
|
||||
range = 1..0
|
||||
assert Enum.split(range, 3) == { [1, 0], [] }
|
||||
end
|
||||
|
||||
test :split_while do
|
||||
range = Range.new(first: 1, last: 3)
|
||||
range = 1..3
|
||||
assert Enum.split_while(range, fn(_) -> false end) == { [], [1, 2, 3] }
|
||||
assert Enum.split_while(range, fn(_) -> true end) == { [1, 2, 3], [] }
|
||||
assert Enum.split_while(range, fn(x) -> x > 2 end) == { [], [1, 2, 3] }
|
||||
assert Enum.split_while(range, fn(x) -> x > 3 end) == { [], [1, 2, 3] }
|
||||
assert Enum.split_while(range, fn(x) -> x < 3 end) == { [1, 2], [3] }
|
||||
|
||||
range = Range.new(first: 1, last: 0)
|
||||
range = 1..0
|
||||
assert Enum.split_while(range, fn(_) -> true end) == { [1, 0], [] }
|
||||
end
|
||||
|
||||
test :take do
|
||||
range = Range.new(first: 1, last: 3)
|
||||
range = 1..3
|
||||
assert Enum.take(range, 0) == []
|
||||
assert Enum.take(range, 1) == [1]
|
||||
assert Enum.take(range, 2) == [1, 2]
|
||||
assert Enum.take(range, 3) == [1, 2, 3]
|
||||
assert Enum.take(range, 4) == [1, 2, 3]
|
||||
assert Enum.take(range, -1) == [3]
|
||||
assert Enum.take(range, -2) == [2, 3]
|
||||
assert Enum.take(range, -4) == [1, 2, 3]
|
||||
|
||||
range = Range.new(first: 1, last: 0)
|
||||
range = 1..0
|
||||
assert Enum.take(range, 3) == [1, 0]
|
||||
end
|
||||
|
||||
@@ -675,7 +711,7 @@ defmodule EnumTest.Range do
|
||||
end
|
||||
|
||||
test :take_while do
|
||||
range = Range.new(first: 1, last: 3)
|
||||
range = 1..3
|
||||
assert Enum.take_while(range, fn(x) -> x > 3 end) == []
|
||||
assert Enum.take_while(range, fn(x) -> x <= 1 end) == [1]
|
||||
assert Enum.take_while(range, fn(x) -> x <= 3 end) == [1, 2, 3]
|
||||
@@ -690,14 +726,14 @@ defmodule EnumTest.Range do
|
||||
test :zip do
|
||||
assert Enum.zip([:a, :b], 1..2) == [{:a, 1}, {:b, 2}]
|
||||
assert Enum.zip([:a, :b], 1..4) == [{:a, 1}, {:b, 2}]
|
||||
assert Enum.zip([:a, :b, :c, :d], 1..2) == [{:a, 1}, {:b, 2}, {:c, nil}, {:d, nil}]
|
||||
assert Enum.zip([:a, :b, :c, :d], 1..2) == [{:a, 1}, {:b, 2}]
|
||||
|
||||
assert Enum.zip(1..2, [:a, :b]) == [{1, :a}, {2, :b}]
|
||||
assert Enum.zip(1..4, [:a, :b]) == [{1, :a}, {2, :b}, {3, nil}, {4, nil}]
|
||||
assert Enum.zip(1..4, [:a, :b]) == [{1, :a}, {2, :b}]
|
||||
assert Enum.zip(1..2, [:a, :b, :c, :d]) == [{1, :a}, {2, :b}]
|
||||
|
||||
assert Enum.zip(1..2, 1..2) == [{1, 1}, {2, 2}]
|
||||
assert Enum.zip(1..4, 1..2) == [{1, 1}, {2, 2}, {3, nil}, {4, nil}]
|
||||
assert Enum.zip(1..4, 1..2) == [{1, 1}, {2, 2}]
|
||||
assert Enum.zip(1..2, 1..4) == [{1, 1}, {2, 2}]
|
||||
end
|
||||
|
||||
@@ -727,18 +763,18 @@ defmodule EnumTest.Range do
|
||||
assert Enum.min_by(1..3, fn(x) -> :math.pow(-2, x) end) == 3
|
||||
end
|
||||
|
||||
test :chunks do
|
||||
assert Enum.chunks(1..5, 2) == [[1, 2], [3, 4]]
|
||||
assert Enum.chunks(1..5, 2, 2, [6]) == [[1, 2], [3, 4], [5, 6]]
|
||||
assert Enum.chunks(1..6, 3, 2) == [[1, 2, 3], [3, 4, 5]]
|
||||
assert Enum.chunks(1..6, 2, 3) == [[1, 2], [4, 5]]
|
||||
assert Enum.chunks(1..6, 3, 2, []) == [[1, 2, 3], [3, 4, 5], [5, 6]]
|
||||
assert Enum.chunks(1..5, 4, 4, 6..10) == [[1, 2, 3, 4], [5, 6, 7, 8]]
|
||||
test :chunk do
|
||||
assert Enum.chunk(1..5, 2) == [[1, 2], [3, 4]]
|
||||
assert Enum.chunk(1..5, 2, 2, [6]) == [[1, 2], [3, 4], [5, 6]]
|
||||
assert Enum.chunk(1..6, 3, 2) == [[1, 2, 3], [3, 4, 5]]
|
||||
assert Enum.chunk(1..6, 2, 3) == [[1, 2], [4, 5]]
|
||||
assert Enum.chunk(1..6, 3, 2, []) == [[1, 2, 3], [3, 4, 5], [5, 6]]
|
||||
assert Enum.chunk(1..5, 4, 4, 6..10) == [[1, 2, 3, 4], [5, 6, 7, 8]]
|
||||
end
|
||||
|
||||
test :chunks_by do
|
||||
assert Enum.chunks_by(1..4, fn _ -> true end) == [[1, 2, 3, 4]]
|
||||
assert Enum.chunks_by(1..4, &(rem(&1, 2) == 1)) == [[1], [2], [3], [4]]
|
||||
test :chunk_by do
|
||||
assert Enum.chunk_by(1..4, fn _ -> true end) == [[1, 2, 3, 4]]
|
||||
assert Enum.chunk_by(1..4, &(rem(&1, 2) == 1)) == [[1], [2], [3], [4]]
|
||||
end
|
||||
|
||||
test :slice do
|
||||
@@ -756,25 +792,37 @@ defmodule EnumTest.Range do
|
||||
assert Enum.slice(1..5, -2, 5) == [4, 5]
|
||||
assert Enum.slice(1..5, -3, 1) == [3]
|
||||
end
|
||||
|
||||
|
||||
test :slice_range do
|
||||
assert Enum.slice(1..5, 0..0) == [1]
|
||||
assert Enum.slice(1..5, 0..1) == [1, 2]
|
||||
assert Enum.slice(1..5, 0..2) == [1, 2, 3]
|
||||
assert Enum.slice(1..5, 1..2) == [2, 3]
|
||||
assert Enum.slice(1..5, 1..0) == nil
|
||||
assert Enum.slice(1..5, 1..0) == nil
|
||||
assert Enum.slice(1..5, 2..5) == [3, 4, 5]
|
||||
assert Enum.slice(1..5, 4..4) == [5]
|
||||
assert Enum.slice(1..5, 5..5) == []
|
||||
assert Enum.slice(1..5, 6..5) == nil
|
||||
assert Enum.slice(1..5, 6..0) == nil
|
||||
assert Enum.slice(1..5, -6..0) == nil
|
||||
assert Enum.slice(1..5, -6..5) == nil
|
||||
assert Enum.slice(1..5, 6..5) == nil
|
||||
assert Enum.slice(1..5, 6..0) == nil
|
||||
assert Enum.slice(1..5, -6..0) == nil
|
||||
assert Enum.slice(1..5, -6..5) == nil
|
||||
assert Enum.slice(1..5, -5..-1) == [1, 2, 3, 4, 5]
|
||||
assert Enum.slice(1..5, -5..-3) == [1, 2, 3]
|
||||
assert Enum.slice(1..5, -6..-1) == nil
|
||||
assert Enum.slice(1..5, -6..-3) == nil
|
||||
end
|
||||
|
||||
test :reverse_range do
|
||||
assert Enum.reverse([]) == []
|
||||
assert Enum.reverse([1, 2, 3]) == [3, 2, 1]
|
||||
assert Enum.reverse([1, 2, 3], [4, 5, 6]) == [3, 2, 1, 4, 5, 6]
|
||||
|
||||
assert Enum.reverse(0..0) == [0]
|
||||
assert Enum.reverse(1..3) == [3, 2, 1]
|
||||
assert Enum.reverse(1..3, 4..6) == [3, 2, 1, 4, 5, 6]
|
||||
assert Enum.reverse([1, 2, 3], 4..6) == [3, 2, 1, 4, 5, 6]
|
||||
assert Enum.reverse(1..3, [4, 5, 6]) == [3, 2, 1, 4, 5, 6]
|
||||
end
|
||||
end
|
||||
|
||||
defmodule EnumTest.Others do
|
||||
@@ -800,15 +848,9 @@ defmodule EnumTest.Others do
|
||||
end
|
||||
|
||||
test :take_with_side_effects do
|
||||
reducible = fn(acc, fun) ->
|
||||
Enum.reduce([1, 2, 3], acc, fn(x, acc) ->
|
||||
IO.puts to_string(x)
|
||||
fun.(x, acc)
|
||||
end)
|
||||
end
|
||||
|
||||
stream = Stream.unfold(1, fn x -> IO.puts x; { x, x + 1 } end)
|
||||
assert capture_io(fn ->
|
||||
Enum.take(reducible, 1)
|
||||
Enum.take(stream, 1)
|
||||
end) == "1\n"
|
||||
end
|
||||
|
||||
|
||||
@@ -6,14 +6,14 @@ defmodule Kernel.ExceptionTest do
|
||||
# Ensure fields passed through an expression are valid
|
||||
defexception Custom, %w(message)a
|
||||
|
||||
test :is_exception do
|
||||
test "is_exception" do
|
||||
assert is_exception(RuntimeError.new)
|
||||
refute is_exception(empty_tuple)
|
||||
refute is_exception(a_tuple)
|
||||
refute is_exception(a_list)
|
||||
end
|
||||
|
||||
test :normalize do
|
||||
test "normalize" do
|
||||
assert is_record Exception.normalize(:badarg), ArgumentError
|
||||
assert is_record Exception.normalize(ArgumentError[]), ArgumentError
|
||||
|
||||
@@ -22,35 +22,36 @@ defmodule Kernel.ExceptionTest do
|
||||
assert is_record Exception.normalize(:error, ArgumentError[]), ArgumentError
|
||||
end
|
||||
|
||||
test :format_stacktrace_entry_with_no_file_or_line do
|
||||
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_stacktrace_entry_with_file_and_line do
|
||||
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_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_stacktrace_entry_with_file_no_line do
|
||||
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_stacktrace_entry_with_fun do
|
||||
test "format_stacktrace_entry with application" do
|
||||
assert Exception.format_stacktrace_entry({Exception, :bar, [], [file: 'file.ex']}) == "(elixir) file.ex: Exception.bar()"
|
||||
assert Exception.format_stacktrace_entry({Exception, :bar, [], [file: 'file.ex', line: 10]}) == "(elixir) file.ex:10: Exception.bar()"
|
||||
assert Exception.format_stacktrace_entry({:lists, :bar, [1, 2, 3], []}) == "(stdlib) :lists.bar(1, 2, 3)" end
|
||||
|
||||
test "format_stacktrace_entry with fun" do
|
||||
assert Exception.format_stacktrace_entry({fn(x) -> x end, [1], []}) =~ %r/#Function<.+>\(1\)/
|
||||
assert Exception.format_stacktrace_entry({fn(x, y) -> { x, y } end, 2, []}) =~ %r"#Function<.+>/2"
|
||||
end
|
||||
|
||||
test :format_mfa do
|
||||
test "format_mfa" do
|
||||
assert Exception.format_mfa(Foo, nil, 1) == "Foo.nil/1"
|
||||
assert Exception.format_mfa(Foo, :bar, 1) == "Foo.bar/1"
|
||||
assert Exception.format_mfa(Foo, :bar, []) == "Foo.bar()"
|
||||
@@ -59,53 +60,54 @@ defmodule Kernel.ExceptionTest do
|
||||
assert Exception.format_mfa(Foo, :"bar baz", 1) == "Foo.\"bar baz\"/1"
|
||||
end
|
||||
|
||||
test :format_fa do
|
||||
test "format_fa" do
|
||||
assert Exception.format_fa(fn -> end, 1) =~
|
||||
%r"#Function<\d\.\d+ in Kernel\.ExceptionTest\.test_format_fa/1>/1"
|
||||
%r"#Function<\d\.\d+ in Kernel\.ExceptionTest\.test format_fa/1>/1"
|
||||
end
|
||||
|
||||
test :runtime_error_message do
|
||||
test "runtime error message" do
|
||||
assert RuntimeError.new.message == "runtime error"
|
||||
assert RuntimeError.new(message: "exception").message == "exception"
|
||||
end
|
||||
|
||||
test :argument_error_message do
|
||||
test "argument error message" do
|
||||
assert ArgumentError.new.message == "argument error"
|
||||
assert ArgumentError.new(message: "exception").message == "exception"
|
||||
end
|
||||
|
||||
test :undefined_function_message 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: nil, function: :bar, arity: 0).message == "undefined function: nil.bar/0"
|
||||
end
|
||||
|
||||
test :function_clause_message do
|
||||
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 in Foo.bar/1"
|
||||
end
|
||||
|
||||
test :erlang_error_message do
|
||||
test "erlang error message" do
|
||||
assert ErlangError.new(original: :sample).message == "erlang error: :sample"
|
||||
end
|
||||
|
||||
test :raise_preserves_the_stacktrace do
|
||||
test "raise preserves the stacktrace" do
|
||||
stacktrace =
|
||||
try do
|
||||
raise "a"
|
||||
rescue _ ->
|
||||
[top|_] = System.stacktrace
|
||||
top
|
||||
end
|
||||
file = to_char_list(__FILE__)
|
||||
assert {Kernel.ExceptionTest, :test_raise_preserves_the_stacktrace, _,
|
||||
[file: ^file, line: 95]} = stacktrace # line is sensitive
|
||||
try do
|
||||
raise "a"
|
||||
rescue _ ->
|
||||
[top|_] = System.stacktrace
|
||||
top
|
||||
end
|
||||
|
||||
file = __ENV__.file |> Path.relative_to_cwd |> String.to_char_list!
|
||||
assert {Kernel.ExceptionTest, :"test raise preserves the stacktrace", _,
|
||||
[file: ^file, line: 96]} = stacktrace
|
||||
end
|
||||
|
||||
test :defexception do
|
||||
defexception SampleError, field: :ok do
|
||||
test "defexception" do
|
||||
defexception SampleError, message: nil do
|
||||
# Check do block is properly inline
|
||||
def message(_), do: "hello"
|
||||
def exception(_), do: SampleError[message: "hello"]
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user