Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ada53524ca | ||
|
|
e6e3570961 | ||
|
|
db5adc1b35 | ||
|
|
5dd7a2ba3a | ||
|
|
edfb650c94 | ||
|
|
c012456573 | ||
|
|
860c056cbe | ||
|
|
897424b185 | ||
|
|
f4c61dae09 | ||
|
|
73a76c6f7c | ||
|
|
002bf16e2e | ||
|
|
47c922e571 | ||
|
|
60edb8489c | ||
|
|
d539d606de | ||
|
|
bb02b25e49 | ||
|
|
6ce51612e0 | ||
|
|
6243bc8dc8 | ||
|
|
e39b32f235 | ||
|
|
0843ea0fbb | ||
|
|
bbd6abb168 | ||
|
|
a29d2913f7 | ||
|
|
d1edad39c9 | ||
|
|
74584ad921 | ||
|
|
47f57d65e0 | ||
|
|
ed71b57c00 | ||
|
|
0246226487 | ||
|
|
7da2a3ccc6 | ||
|
|
d41f39a53a | ||
|
|
44c81ae0ed | ||
|
|
c6c6a27baf | ||
|
|
97e70e6de1 | ||
|
|
980dbc96d7 | ||
|
|
0883c75335 | ||
|
|
5761460bd6 | ||
|
|
d881d899a4 | ||
|
|
166685caad | ||
|
|
f60bdb8d5f | ||
|
|
c3ef484d14 | ||
|
|
3cb586a672 | ||
|
|
68828523a0 | ||
|
|
3139168007 | ||
|
|
03189d05b3 | ||
|
|
aa09359def | ||
|
|
9595fb18dd | ||
|
|
d2530e44eb | ||
|
|
21b1e51909 | ||
|
|
9e96966622 | ||
|
|
845a3d1fea | ||
|
|
8b70e07b2e | ||
|
|
f6ca23c1a5 | ||
|
|
cc03382831 | ||
|
|
09e7dbf3d5 | ||
|
|
238eda25e3 | ||
|
|
a7c6b8da5c | ||
|
|
1a26ea2c19 | ||
|
|
6d6e7d7137 | ||
|
|
f7d0c8c58e | ||
|
|
4e93dab9a8 | ||
|
|
4847214766 | ||
|
|
e56df8ad70 | ||
|
|
325c98ccbd | ||
|
|
2daf136a53 | ||
|
|
70b4c060e6 | ||
|
|
620e452f9f | ||
|
|
c373edff9b | ||
|
|
1bd50f4d36 | ||
|
|
be79bbf55a | ||
|
|
89f651f628 | ||
|
|
f3f2481e71 | ||
|
|
d33463dcd5 | ||
|
|
2de2322402 | ||
|
|
f13ae1839c | ||
|
|
29ebae3739 | ||
|
|
59dc733dfc | ||
|
|
0c9d5f16e9 | ||
|
|
b18ddd440f | ||
|
|
f4d852d292 | ||
|
|
c37a244d66 | ||
|
|
bdab8045b9 | ||
|
|
e8ea1969e4 | ||
|
|
f669e2ba96 | ||
|
|
17ddb2539d | ||
|
|
9cbe498a02 | ||
|
|
edf55a8424 | ||
|
|
41bce3e068 | ||
|
|
914a8cdcd9 | ||
|
|
a333f646d4 | ||
|
|
d73c108b89 | ||
|
|
580b9292d6 | ||
|
|
07648bd075 | ||
|
|
149091a1a1 | ||
|
|
34a3b41543 | ||
|
|
ad87292fa0 | ||
|
|
2d9dcf860a | ||
|
|
e2357d4b95 | ||
|
|
8fac8cc25f | ||
|
|
ada913c7ef | ||
|
|
df04a3788e | ||
|
|
65543f0766 | ||
|
|
a73d70176a | ||
|
|
14d2ce460e | ||
|
|
2ce02084ee | ||
|
|
6ab07f6f5b | ||
|
|
7d2babe272 | ||
|
|
913327749a | ||
|
|
9be2f7f3d3 | ||
|
|
09e9917fc5 | ||
|
|
4af5f2b12f | ||
|
|
14fb6d7337 | ||
|
|
ecd5b3d8ef | ||
|
|
a52bbfce8e | ||
|
|
6696184fc9 | ||
|
|
eda13d1e41 | ||
|
|
f69c2182f7 | ||
|
|
b5d48ada53 | ||
|
|
27c9cc5bbb | ||
|
|
fb211b0c44 | ||
|
|
c7c5d3af24 | ||
|
|
6e9a3fd24d | ||
|
|
34c2fb773f | ||
|
|
9bdfa41c4d | ||
|
|
85576f8fba | ||
|
|
453c731f8a | ||
|
|
d4dd48d2fb | ||
|
|
0f9f6152cf | ||
|
|
eb998862d7 | ||
|
|
187e1b4a49 | ||
|
|
318e068587 | ||
|
|
3d6dd50866 | ||
|
|
b3ebf924a9 | ||
|
|
cd3e6d9238 | ||
|
|
56db9fb043 | ||
|
|
897396447f | ||
|
|
d79f3ddffd | ||
|
|
9ba58c50ab | ||
|
|
deedadb528 | ||
|
|
96e5d8bb41 | ||
|
|
8c5eaa973b | ||
|
|
42ab7a5370 | ||
|
|
e73fce96d1 | ||
|
|
d8e414032f | ||
|
|
7362ca8fab | ||
|
|
705bf0cd90 | ||
|
|
71ca5672cc | ||
|
|
7494106933 | ||
|
|
cbc03036cb | ||
|
|
eaa52f1db3 | ||
|
|
a05d0d3b26 | ||
|
|
9fc7a1c43f | ||
|
|
04da85d19e | ||
|
|
866acc4f82 | ||
|
|
cf730f2dbf | ||
|
|
012256791d | ||
|
|
5c410d9c34 | ||
|
|
efb67a8416 | ||
|
|
26f5f640c9 | ||
|
|
7f3a9ed9c3 | ||
|
|
04e774a4ba | ||
|
|
38d1badb64 | ||
|
|
bb3cc518cf | ||
|
|
eaa2db1809 | ||
|
|
a38636ac08 | ||
|
|
6268dc3d22 | ||
|
|
d50405135a | ||
|
|
e5fed25efd | ||
|
|
ae32bf1fe0 | ||
|
|
433a46a0ad | ||
|
|
b2fb6c9bb1 | ||
|
|
75de306c46 | ||
|
|
598648da1a | ||
|
|
accf75b118 | ||
|
|
0cbbefe11b | ||
|
|
17b4dbbdbb | ||
|
|
9e068fb0a2 | ||
|
|
59ca4728d1 | ||
|
|
7af4364121 | ||
|
|
e38173602a | ||
|
|
9d149a3070 | ||
|
|
9158cad331 | ||
|
|
00b1aadd76 | ||
|
|
c1fedcc643 | ||
|
|
2f7972c57a | ||
|
|
ef1b2ea49c | ||
|
|
69f2d5083f | ||
|
|
10b1f56ad9 | ||
|
|
a6f171d183 | ||
|
|
21a231a914 | ||
|
|
9e9583c29c | ||
|
|
f6a513c71c | ||
|
|
e1f9b3f291 | ||
|
|
3f2458f7e3 | ||
|
|
fb916430f5 | ||
|
|
c9402713c9 | ||
|
|
85b8fcf2d9 | ||
|
|
13d0a4d3cf | ||
|
|
9c1c424ed2 | ||
|
|
26480d5755 | ||
|
|
ab4f1013d3 | ||
|
|
5913097c17 | ||
|
|
af26e9fd69 | ||
|
|
c6e8a905f5 | ||
|
|
a67411324b | ||
|
|
ccf5182e73 | ||
|
|
97e044cff5 | ||
|
|
e47417912d | ||
|
|
128d82fde6 | ||
|
|
61170bda23 | ||
|
|
89cc6feea0 | ||
|
|
409fec08d3 | ||
|
|
d94e7b595e | ||
|
|
1339462c82 | ||
|
|
7f8265f85c | ||
|
|
2e8c53c85b | ||
|
|
933834aedd | ||
|
|
013303a44e | ||
|
|
bab252f1e8 | ||
|
|
655e54b551 | ||
|
|
24bf9d3f9a | ||
|
|
dab33cbddc | ||
|
|
ce73493cc5 | ||
|
|
3984ed9da3 | ||
|
|
a42a0188a1 | ||
|
|
f5660dad92 | ||
|
|
440bfdc3b4 | ||
|
|
d66a3aea39 | ||
|
|
db76866309 | ||
|
|
d0dca93fb2 | ||
|
|
0d66824965 | ||
|
|
fd8859abdd | ||
|
|
bf3852b7bd | ||
|
|
3512c1a860 | ||
|
|
8e38540470 | ||
|
|
33985326b1 | ||
|
|
cbe041bdda | ||
|
|
20b14b3221 | ||
|
|
dd4ede4c1d | ||
|
|
9f89ac920c | ||
|
|
d7f8f28ed9 | ||
|
|
ac61ddb09a | ||
|
|
141f0a3ebf | ||
|
|
cb48e7670e | ||
|
|
a5889cffc0 | ||
|
|
933b3af54c | ||
|
|
8fcb832edc | ||
|
|
16c90ef97d | ||
|
|
8084f0a6bf | ||
|
|
335d82e607 | ||
|
|
c51a2a5462 | ||
|
|
d0b28f4828 | ||
|
|
8e5fec95fa | ||
|
|
7715a09374 | ||
|
|
24203f8d9d | ||
|
|
2b04f540e2 | ||
|
|
45c99aee82 | ||
|
|
2d25a6fb25 | ||
|
|
aaf63a73b1 | ||
|
|
bacb1a530a | ||
|
|
c151739415 | ||
|
|
d44e8bd1f6 | ||
|
|
2a742e69e6 | ||
|
|
5bda5d503b | ||
|
|
e871f7a9ca | ||
|
|
0aa4be5086 | ||
|
|
6fed5068ce | ||
|
|
548f24bbff | ||
|
|
b478462fe2 | ||
|
|
ea34b7a302 | ||
|
|
66ff417183 | ||
|
|
c28b9b0ab4 | ||
|
|
8058afe9c6 | ||
|
|
b6f80fc05f | ||
|
|
920e056465 | ||
|
|
6346029cf9 | ||
|
|
822dcd0557 | ||
|
|
5ab85c7621 | ||
|
|
dbb6f358cc | ||
|
|
7e021d9bbb | ||
|
|
4a4a407644 | ||
|
|
1844aec63e | ||
|
|
baabbdec28 | ||
|
|
2137c28e4e | ||
|
|
7a96e8131c | ||
|
|
675a2a60c8 | ||
|
|
b8c25bc258 | ||
|
|
f76a867c75 | ||
|
|
0776cc5a64 | ||
|
|
371e96e2a8 | ||
|
|
9aa41361a6 | ||
|
|
f17dec2c74 | ||
|
|
7641a5eded | ||
|
|
f3aad51922 | ||
|
|
848218cdac | ||
|
|
6a1cef052f | ||
|
|
455076446b | ||
|
|
6e83cb8d82 | ||
|
|
1f0861771a | ||
|
|
4917bfca29 | ||
|
|
b46481e12f | ||
|
|
e4c2fc2ac1 | ||
|
|
99e69bd6c5 | ||
|
|
4d366f4688 | ||
|
|
daa0049b32 | ||
|
|
bc06c5e43f | ||
|
|
da1541c791 | ||
|
|
1ce2433212 | ||
|
|
8cecbf5e9d | ||
|
|
a1b53d35ea | ||
|
|
de71ecf195 | ||
|
|
5a618258a3 | ||
|
|
ba54afbf45 | ||
|
|
1ea44670ce | ||
|
|
ff0df96cfc | ||
|
|
3476f224b7 | ||
|
|
e612592e21 | ||
|
|
cfbd5bbb7f | ||
|
|
46e000394b | ||
|
|
875b95f609 | ||
|
|
ffdf3d32b2 | ||
|
|
1fa74df6c7 | ||
|
|
cb9c17c025 | ||
|
|
923eeb9602 | ||
|
|
c5ff2d1dcf | ||
|
|
c65dcc9b19 |
+1
-6
@@ -6,9 +6,4 @@ notifications:
|
||||
- jose.valim@plataformatec.com.br
|
||||
- eric.meadows.jonsson@gmail.com
|
||||
otp_release:
|
||||
- 17.0-rc1
|
||||
- R16B03-1
|
||||
- R16B03
|
||||
- R16B02
|
||||
- R16B01
|
||||
- R16B
|
||||
- 17.0
|
||||
|
||||
@@ -1,3 +1,52 @@
|
||||
# v0.13.0 (2014-04-20)
|
||||
|
||||
* Enhancements
|
||||
* [Base] Add `Base` module which does conversions to bases 16, 32, hex32, 64 and url64
|
||||
* [Code] Add `Code.eval_file/2`
|
||||
* [Collectable] Add the `Collectable` protocol that empowers `Enum.into/2` and `Stream.into/2` and the `:into` option in comprehensions
|
||||
* [Collectable] Implement `Collectable` for lists, dicts, bitstrings, functions and provide both `File.Stream` and `IO.Stream`
|
||||
* [EEx] Add `handle_body/1` callback to `EEx.Engine`
|
||||
* [Enum] Add `Enum.group_by/2`, `Enum.into/2`, `Enum.into/3`, `Enum.traverse/2` and `Enum.sum/2`
|
||||
* [ExUnit] Randomize cases and tests suite runs, allow seed configuration and the `--seed` flag via `mix test`
|
||||
* [ExUnit] Support `--only` for filtering when running tests with `mix test`
|
||||
* [ExUnit] Raise an error if another `capture_io` process already captured the device
|
||||
* [ExUnit] Improve formatter to show source code and rely on lhs and rhs (instead of expected and actual)
|
||||
* [IEx] Allow prompt configuration with the `:prompt` option
|
||||
* [IEx] Use werl on Windows
|
||||
* [Kernel] Support `ERL_PATH` in `bin/elixir`
|
||||
* [Kernel] Support interpolation in keyword syntax
|
||||
* [Map] Add a Map module and support 17.0 maps and structs
|
||||
* [Mix] Add dependency option `:only` to specify the dependency environment. `mix deps.get` and `mix deps.update` works accross all environment unless `--only` is specified
|
||||
* [Mix] Add `Mix.Shell.prompt/1`
|
||||
* [Mix] Ensure the project is compiled in case Mix' CLI cannot find a task
|
||||
* [Node] Add `Node.ping/1`
|
||||
* [Process] Include `Process.send/3` and support the `--gen-debug` option
|
||||
* [Regex] Regexes no longer need the "g" option when there is a need to use named captures
|
||||
* [Stream] Add `Stream.into/2` and `Stream.into/3`
|
||||
* [StringIO] Add a `StringIO` module that allows a String to be used as IO device
|
||||
* [System] Add `System.delete_env/1` to remove a variable from the environment
|
||||
|
||||
* Bug fixes
|
||||
* [CLI] Ensure `--app` is handled as an atom before processing
|
||||
* [ExUnit] Ensure `ExUnit.Assertions` does not emit compiler warnings for `assert_receive`
|
||||
* [Kernel] Ensure the same pid is not queued twice in the parallel compiler
|
||||
* [Macro] `Macro.to_string/2` considers proper precedence when translating `!(foo > bar)` into a string
|
||||
* [Mix] Automatically recompile on outdated Elixir version and show proper error messages
|
||||
* [Mix] Ensure generated `.app` file includes core dependencies
|
||||
* [Mix] Allow a dependency with no SCM to be overridden
|
||||
* [Mix] Allow queries in `mix local.install` URL
|
||||
* [OptionParser] Do not recognize undefined aliases as switches
|
||||
|
||||
* Deprecations
|
||||
* [Dict] `Dict.empty/1`, `Dict.new/1` and `Dict.new/2` are deprecated
|
||||
* [Exception] `Exception.normalize/1` is deprecated in favor of `Exception.normalize/2`
|
||||
* [Kernel] `lc` and `bc` comprehensions are deprecated in favor of `for` (this is a soft deprecation, no warning will be emitted)
|
||||
* [ListDict] `ListDict` is deprecated in favor of `Map` (this is a soft deprecation, no warning will be emitted)
|
||||
* [Record] `defrecord/2`, `defrecordp/3`, `is_record/1` and `is_record/2` macros in Kernel are deprecated. Instead, use the new macros and API defined in the `Record` module (this is a soft deprecation, no warnings will be emitted)
|
||||
|
||||
* Backwards incompatible changes
|
||||
* [ExUnit] Formatters are now required to be a GenEvent and `ExUnit.run/2` returns a map with results
|
||||
|
||||
# v0.12.5 (2014-03-09)
|
||||
|
||||
* Bug fixes
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
REBAR := rebar
|
||||
ELIXIRC := bin/elixirc --verbose --ignore-module-conflict $(ELIXIRC_OPTS)
|
||||
ELIXIRC := bin/elixirc --verbose --ignore-module-conflict
|
||||
ERLC := erlc -I lib/elixir/include
|
||||
ERL := erl -I lib/elixir/include -noshell -pa lib/elixir/ebin
|
||||
VERSION := $(strip $(shell cat VERSION))
|
||||
@@ -11,7 +11,7 @@ INSTALL_DIR = $(INSTALL) -m755 -d
|
||||
INSTALL_DATA = $(INSTALL) -m644
|
||||
INSTALL_PROGRAM = $(INSTALL) -m755
|
||||
|
||||
.PHONY: install compile erlang elixir dialyze test clean docs release_docs release_zip release_erl
|
||||
.PHONY: install compile erlang elixir dialyze test clean docs release_docs release_zip
|
||||
.NOTPARALLEL: compile
|
||||
|
||||
#==> Templates
|
||||
@@ -127,17 +127,13 @@ docs: compile ../ex_doc/bin/ex_doc
|
||||
|
||||
release_zip: compile
|
||||
rm -rf v$(VERSION).zip
|
||||
zip -9 -r v$(VERSION).zip bin CHANGELOG.md LEGAL lib/*/ebin LICENSE README.md rel VERSION
|
||||
zip -9 -r v$(VERSION).zip bin CHANGELOG.md LEGAL lib/*/ebin LICENSE README.md VERSION
|
||||
|
||||
release_docs: docs
|
||||
cd ../docs
|
||||
rm -rf ../docs/master
|
||||
mv docs ../docs/master
|
||||
|
||||
release_erl: compile
|
||||
$(Q) rm -rf rel/elixir
|
||||
$(Q) cd rel && ../rebar generate
|
||||
|
||||
#==> Tests tasks
|
||||
|
||||
test: test_erlang test_elixir
|
||||
|
||||
@@ -16,9 +16,9 @@ If Elixir fails to build (specifically when pulling in a new version via git), b
|
||||
|
||||
If tests pass, you are ready to move on to the [Getting Started guide][1] or to try Interactive Elixir by running: `bin/iex` in your terminal.
|
||||
|
||||
However, if tests fail, it is likely you have an outdated Erlang version (Elixir requires Erlang R16B or later). You can check your Erlang version by calling `erl` in the command line. You will see some information as follows:
|
||||
However, if tests fail, it is likely you have an outdated Erlang version (Elixir requires Erlang 17.0 or later). You can check your Erlang version by calling `erl` in the command line. You will see some information as follows:
|
||||
|
||||
Erlang R16B (erts-5.10.1) [source] [64-bit] [smp:2:2] [rq:2] [async-threads:0] [hipe] [kernel-poll:false]
|
||||
Erlang/OTP 17 [RELEASE CANDIDATE 1] [erts-6.0] [source] [64-bit] [smp:4:4] [async-threads:10] [hipe] [kernel-poll:false]
|
||||
|
||||
If you have the correct version and tests still fail, feel free to [open an issue][2].
|
||||
|
||||
|
||||
+7
-9
@@ -4,25 +4,23 @@ This document simply outlines the release process:
|
||||
|
||||
1) Remove `-dev` extension from VERSION
|
||||
|
||||
2) Run `make clean test` to ensure all tests pass from scratch and the CI is green
|
||||
2) Ensure CHANGELOG is updated and timestamp
|
||||
|
||||
3) Ensure CHANGELOG is updated and tag release version with timestamp in it
|
||||
3) Commit changes above with title "Release vVERSION" and generate new tag
|
||||
|
||||
4) Commit changes above with title "Release vVERSION"
|
||||
4) Run `make clean test` to ensure all tests pass from scratch and the CI is green
|
||||
|
||||
5) Push master and create tag vVERSION from master branch
|
||||
5) Push master and tags
|
||||
|
||||
6) Release new docs with `make release_docs`, move docs to `docs/stable`
|
||||
|
||||
7) Release new zip with `make release_zip`, push new zip to GitHub Releases
|
||||
|
||||
8) Push package to expm with `expm publish package.exs`
|
||||
8) Merge master into stable branch and push it
|
||||
|
||||
9) Merge master into stable branch and push it
|
||||
9) After release, bump versions, add `-dev` back and commit
|
||||
|
||||
10) After release, bump versions, add `-dev` back and commit
|
||||
|
||||
11) `make release_docs` once again and push `elixir-lang.github.com`
|
||||
10) `make release_docs` once again and push it to `elixir-lang.github.com`
|
||||
|
||||
## Places where version is mentioned
|
||||
|
||||
|
||||
+21
-7
@@ -17,6 +17,7 @@ if [ $# -eq 0 ] || [ "$1" = "--help" ] || [ "$1" = "-h" ]; then
|
||||
--hidden Makes a hidden node
|
||||
--detached Starts the Erlang VM detached from console
|
||||
--no-halt Does not halt the Erlang VM after execution
|
||||
--gen-debug Turns on default debugging for all GenServers
|
||||
|
||||
** Options marked with (*) can be given more than once
|
||||
** Options given after the .exs file or -- are passed down to the executed code
|
||||
@@ -34,6 +35,7 @@ readlink_f () {
|
||||
fi
|
||||
}
|
||||
|
||||
MODE="elixir"
|
||||
ERL=""
|
||||
I=1
|
||||
|
||||
@@ -41,7 +43,13 @@ while [ $I -le $# ]; do
|
||||
S=1
|
||||
eval "PEEK=\${$I}"
|
||||
case "$PEEK" in
|
||||
-v|--compile|--no-halt|+iex|+compile)
|
||||
+iex)
|
||||
MODE="iex"
|
||||
;;
|
||||
+elixirc)
|
||||
MODE="elixirc"
|
||||
;;
|
||||
-v|--compile|--no-halt)
|
||||
;;
|
||||
-e|-r|-pr|-pa|-pz|--remsh|--app)
|
||||
S=2
|
||||
@@ -64,6 +72,9 @@ while [ $I -le $# ]; do
|
||||
eval "VAL=\${$I}"
|
||||
ERL="$ERL "$VAL""
|
||||
;;
|
||||
--gen-debug)
|
||||
ERL="$ERL -generic_debug"
|
||||
;;
|
||||
*)
|
||||
break
|
||||
;;
|
||||
@@ -73,11 +84,14 @@ done
|
||||
|
||||
SELF=$(readlink_f "$0")
|
||||
SCRIPT_PATH=$(dirname "$SELF")
|
||||
if [ "$ELIXIR_NO_CLI" != "1" ]; then ERL="$ERL -s elixir start_cli"; fi
|
||||
if [ "$MODE" != "iex" ]; then ERL="$ERL -s elixir start_cli"; fi
|
||||
|
||||
if [ -f "$SCRIPT_PATH/../releases/RELEASES" ] && [ -f "$SCRIPT_PATH/erl" ]
|
||||
then
|
||||
exec "$SCRIPT_PATH"/erl -pa "$SCRIPT_PATH"/../lib/*/ebin -noshell $ELIXIR_ERL_OPTS $ERL -boot start -extra "$@"
|
||||
else
|
||||
exec erl -pa "$SCRIPT_PATH"/../lib/*/ebin -noshell $ELIXIR_ERL_OPTS $ERL -extra "$@"
|
||||
if [ -z "$ERL_PATH" ]; then
|
||||
if [ -f "$SCRIPT_PATH/../releases/RELEASES" ] && [ -f "$SCRIPT_PATH/erl" ]; then
|
||||
ERL_PATH="$SCRIPT_PATH"/erl
|
||||
else
|
||||
ERL_PATH=erl
|
||||
fi
|
||||
fi
|
||||
|
||||
exec "$ERL_PATH" -pa "$SCRIPT_PATH"/../lib/*/ebin -noshell $ELIXIR_ERL_OPTS $ERL -extra "$@"
|
||||
|
||||
+12
-2
@@ -24,6 +24,7 @@ echo --cookie cookie Sets a cookie for this distributed node
|
||||
echo --hidden Makes a hidden node
|
||||
echo --detached Starts the Erlang VM detached from console
|
||||
echo --no-halt Does not halt the Erlang VM after execution
|
||||
echo --gen-debug Turns on default debugging for all GenServers
|
||||
echo.
|
||||
echo ** Options marked with (*) can be given more than once
|
||||
echo ** Options given after the .exs file or -- are passed down to the executed code
|
||||
@@ -44,6 +45,9 @@ set originPath=%~dp0
|
||||
rem Optional parameters before the "-extra" parameter
|
||||
set beforeExtra=
|
||||
|
||||
rem Flag which determines whether or not to use werl vs erl
|
||||
set useWerl=0
|
||||
|
||||
rem Recursive loop called for each parameter that parses the cmd line parameters
|
||||
:startloop
|
||||
set par="%1"
|
||||
@@ -56,6 +60,8 @@ if "%par%"=="""" (
|
||||
rem if no parameters defined - special case for parameter that is already quoted
|
||||
goto :expand_erl_libs
|
||||
)
|
||||
rem ******* EXECUTION OPTIONS **********************
|
||||
IF "%par%"==""+iex"" (Set useWerl=1)
|
||||
rem ******* ERLANG PARAMETERS **********************
|
||||
IF NOT "%par%"=="%par:--detached=%" (Set parsErlang=%parsErlang% -detached)
|
||||
IF NOT "%par%"=="%par:--hidden=%" (Set parsErlang=%parsErlang% -hidden)
|
||||
@@ -63,6 +69,7 @@ IF NOT "%par%"=="%par:--cookie=%" (Set parsErlang=%parsErlang% -setcookie %1 &
|
||||
IF NOT "%par%"=="%par:--sname=%" (Set parsErlang=%parsErlang% -sname %1 && shift)
|
||||
IF NOT "%par%"=="%par:--name=%" (Set parsErlang=%parsErlang% -name %1 && shift)
|
||||
IF NOT "%par%"=="%par:--erl=%" (Set beforeExtra=%beforeExtra% %~1 && shift)
|
||||
IF NOT "%par%"=="%par:--gen-debug=%" (Set parsErlang=%parsErlang% -generic_debug)
|
||||
rem ******* elixir parameters **********************
|
||||
rem Note: we don't have to do anything with options that don't take an argument
|
||||
IF NOT "%par%"=="%par:-e=%" (shift)
|
||||
@@ -84,5 +91,8 @@ for /d %%d in ("%originPath%..\lib\*.") do (
|
||||
)
|
||||
SETLOCAL disabledelayedexpansion
|
||||
:run
|
||||
erl %ext_libs% -noshell %ELIXIR_ERL_OPTS% %parsErlang% -s elixir start_cli %beforeExtra% -extra %*
|
||||
|
||||
IF %useWerl% EQU 1 (
|
||||
werl %ext_libs% -noshell %ELIXIR_ERL_OPTS% %parsErlang% -s elixir start_cli %beforeExtra% -extra %*
|
||||
) ELSE (
|
||||
erl %ext_libs% -noshell %ELIXIR_ERL_OPTS% %parsErlang% -s elixir start_cli %beforeExtra% -extra %*
|
||||
)
|
||||
+1
-1
@@ -26,4 +26,4 @@ readlink_f () {
|
||||
|
||||
SELF=$(readlink_f "$0")
|
||||
SCRIPT_PATH=$(dirname "$SELF")
|
||||
exec "$SCRIPT_PATH"/elixir +compile "$@"
|
||||
exec "$SCRIPT_PATH"/elixir +elixirc "$@"
|
||||
|
||||
+1
-1
@@ -20,4 +20,4 @@ echo ** Options marked with (*) can be given more than once
|
||||
echo ** Options given after -- are passed down to the executed code
|
||||
echo ** Options can be passed to the erlang runtime using ELIXIR_ERL_OPTS" >&2
|
||||
:run
|
||||
call "%~dp0\elixir.bat" +compile %*
|
||||
call "%~dp0\elixir.bat" +elixirc %*
|
||||
|
||||
@@ -16,13 +16,14 @@ if [ $# -gt 0 ] && ([ "$1" = "--help" ] || [ "$1" = "-h" ]); then
|
||||
--cookie \"cookie\" Sets a cookie for this distributed node
|
||||
--hidden Makes a hidden node
|
||||
--detached Starts the Erlang VM detached from console
|
||||
--gen-debug Turns on default debugging for all GenServers
|
||||
--remsh \"name\" Connects to a node using a remote shell
|
||||
--dot-iex \"path\" Overrides default .iex.exs file and uses path instead;
|
||||
path can be empty, then no file will be loaded
|
||||
|
||||
** Options marked with (*) can be given more than once
|
||||
** Options given after the .exs file or -- are passed down to the executed code
|
||||
** Options can be passed to the erlang runtime using ELIXIR_ERL_OPTS or --erl" >&2
|
||||
** Options can be passed to the VM using ELIXIR_ERL_OPTS or --erl" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
@@ -38,4 +39,4 @@ readlink_f () {
|
||||
|
||||
SELF=$(readlink_f "$0")
|
||||
SCRIPT_PATH=$(dirname "$SELF")
|
||||
ELIXIR_NO_CLI=1 exec "$SCRIPT_PATH"/elixir --no-halt --erl "-user Elixir.IEx.CLI" +iex "$@"
|
||||
exec "$SCRIPT_PATH"/elixir --no-halt --erl "-user Elixir.IEx.CLI" +iex "$@"
|
||||
|
||||
+1
-1
@@ -1,2 +1,2 @@
|
||||
@echo off
|
||||
call "%~dp0\elixir.bat" +iex --no-halt -e "IEx.start" %*
|
||||
call "%~dp0\elixir.bat" +iex --erl "-user Elixir.IEx.CLI" --no-halt %
|
||||
+15
-16
@@ -5,8 +5,8 @@ defmodule EEx do
|
||||
EEx stands for Embedded Elixir. It allows you to embed
|
||||
Elixir code inside a string in a robust way:
|
||||
|
||||
EEx.eval_string "foo <%= bar %>", [bar: "baz"]
|
||||
#=> "foo baz"
|
||||
iex> EEx.eval_string "foo <%= bar %>", [bar: "baz"]
|
||||
"foo baz"
|
||||
|
||||
## API
|
||||
|
||||
@@ -31,7 +31,7 @@ defmodule EEx do
|
||||
|
||||
All functions in this module accepts EEx-related options.
|
||||
They are:
|
||||
|
||||
|
||||
* `:line` - the line to be used as the template start.
|
||||
Defaults to 1;
|
||||
* `:file` - the file to be used in the template.
|
||||
@@ -79,12 +79,12 @@ defmodule EEx do
|
||||
An example is the `@` macro which allows easy data access
|
||||
in a template:
|
||||
|
||||
EEx.eval_string "<%= @foo %>", assigns: [foo: 1]
|
||||
#=> 1
|
||||
iex> EEx.eval_string "<%= @foo %>", assigns: [foo: 1]
|
||||
"1"
|
||||
|
||||
In other words, <%= @foo %> is simply translated to:
|
||||
|
||||
<%= Keyword.get assigns, :foo %>
|
||||
<%= Dict.get assigns, :foo %>
|
||||
|
||||
The assigns extension is useful when the number of variables
|
||||
required by the template is not specified at compilation time.
|
||||
@@ -97,12 +97,12 @@ defmodule EEx do
|
||||
|
||||
## Examples
|
||||
|
||||
defmodule Sample do
|
||||
require EEx
|
||||
EEx.function_from_string :def, :sample, "<%= a + b %>", [:a, :b]
|
||||
end
|
||||
|
||||
Sample.sample(1, 2) #=> "3"
|
||||
iex> defmodule Sample do
|
||||
...> require EEx
|
||||
...> EEx.function_from_string :def, :sample, "<%= a + b %>", [:a, :b]
|
||||
...> end
|
||||
iex> Sample.sample(1, 2)
|
||||
"3"
|
||||
|
||||
"""
|
||||
defmacro function_from_string(kind, name, source, args \\ [], options \\ []) do
|
||||
@@ -177,8 +177,8 @@ defmodule EEx do
|
||||
|
||||
## Examples
|
||||
|
||||
EEx.eval_string "foo <%= bar %>", [bar: "baz"]
|
||||
#=> "foo baz"
|
||||
iex> EEx.eval_string "foo <%= bar %>", [bar: "baz"]
|
||||
"foo baz"
|
||||
|
||||
"""
|
||||
def eval_string(source, bindings \\ [], options \\ []) do
|
||||
@@ -195,8 +195,7 @@ defmodule EEx do
|
||||
foo <%= bar %>
|
||||
|
||||
# iex
|
||||
EEx.eval_file "sample.ex", [bar: "baz"]
|
||||
#=> "foo baz"
|
||||
EEx.eval_file "sample.ex", [bar: "baz"] #=> "foo baz"
|
||||
|
||||
"""
|
||||
def eval_file(filename, bindings \\ [], options \\ []) do
|
||||
|
||||
+21
-21
@@ -17,8 +17,8 @@ defmodule EEx.Compiler do
|
||||
|
||||
# Generates the buffers by handling each expression from the tokenizer
|
||||
|
||||
defp generate_buffer([{ :text, _line, chars }|t], buffer, scope, state) do
|
||||
buffer = state.engine.handle_text(buffer, chars)
|
||||
defp generate_buffer([{ :text, chars }|t], buffer, scope, state) do
|
||||
buffer = state.engine.handle_text(buffer, String.from_char_list!(chars))
|
||||
generate_buffer(t, buffer, scope, state)
|
||||
end
|
||||
|
||||
@@ -28,8 +28,9 @@ defmodule EEx.Compiler do
|
||||
generate_buffer(t, buffer, scope, state)
|
||||
end
|
||||
|
||||
defp generate_buffer([{ :start_expr, line, mark, chars }|t], buffer, scope, state) do
|
||||
{ contents, t } = generate_buffer(t, "", [chars|scope], state.dict([]).line(line).start_line(line))
|
||||
defp generate_buffer([{ :start_expr, start_line, mark, chars }|t], buffer, scope, state) do
|
||||
{ contents, line, t } = look_ahead_text(t, start_line, chars)
|
||||
{ contents, t } = generate_buffer(t, "", [contents|scope], state.dict([]).line(line).start_line(start_line))
|
||||
buffer = state.engine.handle_expr(buffer, mark, contents)
|
||||
generate_buffer(t, buffer, scope, state)
|
||||
end
|
||||
@@ -50,8 +51,8 @@ defmodule EEx.Compiler do
|
||||
raise EEx.SyntaxError, message: "unexpected token: #{inspect chars} at line #{inspect line}"
|
||||
end
|
||||
|
||||
defp generate_buffer([], buffer, [], _state) do
|
||||
buffer
|
||||
defp generate_buffer([], buffer, [], state) do
|
||||
state.engine.handle_body(buffer)
|
||||
end
|
||||
|
||||
defp generate_buffer([], _buffer, _scope, _state) do
|
||||
@@ -62,28 +63,27 @@ defmodule EEx.Compiler do
|
||||
|
||||
defp wrap_expr(current, line, buffer, chars, state) do
|
||||
new_lines = List.duplicate(?\n, line - state.line)
|
||||
key = length(state.dict)
|
||||
placeholder = '__EEX__(' ++ integer_to_list(key) ++ ');'
|
||||
{ current ++ placeholder ++ new_lines ++ chars, state.update_dict(&[{key, buffer}|&1]) }
|
||||
end
|
||||
|
||||
if state.dict == [] and empty?(buffer) do
|
||||
{ current ++ new_lines ++ chars, state }
|
||||
# Look text ahead on expressions
|
||||
|
||||
defp look_ahead_text([{ :text, text }, { :middle_expr, line, _, chars }|t]=list, start, contents) do
|
||||
if only_spaces?(text) do
|
||||
{ contents ++ text ++ chars, line, t }
|
||||
else
|
||||
key = length(state.dict)
|
||||
placeholder = '__EEX__(' ++ integer_to_list(key) ++ ');'
|
||||
{ current ++ placeholder ++ new_lines ++ chars, state.update_dict(&[{key, buffer}|&1]) }
|
||||
{ contents, start, list }
|
||||
end
|
||||
end
|
||||
|
||||
# Check if the syntax node represents an empty string
|
||||
|
||||
defp empty?(bin) when is_binary(bin) do
|
||||
bc(<<c>> inbits bin, not c in [?\s, ?\t, ?\r, ?\n], do: <<c>>) == ""
|
||||
defp look_ahead_text(t, start, contents) do
|
||||
{ contents, start, t }
|
||||
end
|
||||
|
||||
defp empty?({ :<>, _, [left, right] }) do
|
||||
empty?(left) and empty?(right)
|
||||
end
|
||||
|
||||
defp empty?(_) do
|
||||
false
|
||||
defp only_spaces?(chars) do
|
||||
Enum.all?(chars, &(&1 in [?\s, ?\t, ?\r, ?\n]))
|
||||
end
|
||||
|
||||
# Changes placeholder to real expression
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
defmodule EEx.Engine do
|
||||
@moduledoc ~S"""
|
||||
This is the basic EEx engine that ships with Elixir.
|
||||
An engine needs to implement two functions:
|
||||
An engine needs to implement three functions:
|
||||
|
||||
* `handle_body(quoted)` - receives the final built quoted
|
||||
expression, should do final post-processing and return a
|
||||
quoted expression;
|
||||
|
||||
* `handle_text(buffer, text)` - it receives the buffer,
|
||||
the text and must return a new quoted expression;
|
||||
@@ -10,11 +14,11 @@ defmodule EEx.Engine do
|
||||
the marker, the expr and must return a new quoted expression;
|
||||
|
||||
The marker is what follows exactly after `<%`. For example,
|
||||
`<% foo %>` has an empty marker, but `<%= foo %>` has `'='`
|
||||
`<% foo %>` has an empty marker, but `<%= foo %>` has `"="`
|
||||
as marker. The allowed markers so far are:
|
||||
|
||||
* `''`
|
||||
* `'='`
|
||||
* `""`
|
||||
* `"="`
|
||||
|
||||
Read `handle_expr/3` below for more information about the markers
|
||||
implemented by default by this engine.
|
||||
@@ -22,9 +26,18 @@ defmodule EEx.Engine do
|
||||
|
||||
use Behaviour
|
||||
|
||||
defcallback handle_body(Macro.t) :: Macro.t
|
||||
defcallback handle_text(Macro.t, binary) :: Macro.t
|
||||
defcallback handle_expr(Macro.t, binary, Macro.t) :: Macro.t
|
||||
|
||||
@doc """
|
||||
The default implementation implementation simply returns the
|
||||
given expression.
|
||||
"""
|
||||
def handle_body(quoted) do
|
||||
quoted
|
||||
end
|
||||
|
||||
@doc """
|
||||
The default implementation simply concatenates text to the buffer.
|
||||
"""
|
||||
@@ -54,4 +67,4 @@ defmodule EEx.Engine do
|
||||
tmp
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -15,6 +15,10 @@ defmodule EEx.TransformerEngine do
|
||||
quote do
|
||||
@behaviour EEx.Engine
|
||||
|
||||
def handle_body(body) do
|
||||
EEx.Engine.handle_body(body)
|
||||
end
|
||||
|
||||
def handle_text(buffer, text) do
|
||||
EEx.Engine.handle_text(buffer, text)
|
||||
end
|
||||
@@ -32,14 +36,14 @@ defmodule EEx.TransformerEngine do
|
||||
end
|
||||
|
||||
defp transform(list) when is_list(list) do
|
||||
lc i inlist list, do: transform(i)
|
||||
for i <- list, do: transform(i)
|
||||
end
|
||||
|
||||
defp transform(other) do
|
||||
other
|
||||
end
|
||||
|
||||
defoverridable [transform: 1, handle_expr: 3, handle_text: 2]
|
||||
defoverridable [transform: 1, handle_body: 1, handle_expr: 3, handle_text: 2]
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -59,8 +63,8 @@ defmodule EEx.AssignsEngine do
|
||||
use EEx.AssignsEngine
|
||||
end
|
||||
|
||||
EEx.eval_string("<%= @foo %>", assigns: [foo: 1])
|
||||
#=> 1
|
||||
iex> EEx.eval_string("<%= @foo %>", assigns: [foo: 1])
|
||||
"1"
|
||||
|
||||
In the example above, we can access the value `foo` under
|
||||
the binding `assigns` using `@foo`. This is useful when
|
||||
|
||||
@@ -5,9 +5,10 @@ defmodule EEx.Tokenizer do
|
||||
Tokenizes the given char list or binary.
|
||||
It returns 4 different types of tokens as result:
|
||||
|
||||
* { :text, line, contents }
|
||||
* { :text, contents }
|
||||
* { :expr, line, marker, contents }
|
||||
* { :start_expr, line, marker, contents }
|
||||
* { :middle_expr, line, marker, contents }
|
||||
* { :end_expr, line, marker, contents }
|
||||
|
||||
"""
|
||||
@@ -16,39 +17,39 @@ defmodule EEx.Tokenizer do
|
||||
end
|
||||
|
||||
def tokenize(list, line) do
|
||||
Enum.reverse(tokenize(list, line, line, [], []))
|
||||
Enum.reverse(tokenize(list, line, [], []))
|
||||
end
|
||||
|
||||
defp tokenize('<%%' ++ t, current_line, line, buffer, acc) do
|
||||
defp tokenize('<%%' ++ t, line, buffer, acc) do
|
||||
{ buffer, new_line, rest } = tokenize_expr t, line, [?%, ?<|buffer]
|
||||
tokenize rest, current_line, new_line, [?>, ?%|buffer], acc
|
||||
tokenize rest, new_line, [?>, ?%|buffer], acc
|
||||
end
|
||||
|
||||
defp tokenize('<%#' ++ t, current_line, line, buffer, acc) do
|
||||
defp tokenize('<%#' ++ t, line, buffer, acc) do
|
||||
{ _, new_line, rest } = tokenize_expr t, line, []
|
||||
tokenize rest, current_line, new_line, buffer, acc
|
||||
tokenize rest, new_line, buffer, acc
|
||||
end
|
||||
|
||||
defp tokenize('<%' ++ t, current_line, line, buffer, acc) do
|
||||
defp tokenize('<%' ++ t, line, buffer, acc) do
|
||||
{ marker, t } = retrieve_marker(t)
|
||||
{ expr, new_line, rest } = tokenize_expr t, line, []
|
||||
|
||||
token = token_name(expr)
|
||||
acc = tokenize_text(current_line, buffer, acc)
|
||||
acc = tokenize_text(buffer, acc)
|
||||
final = { token, line, marker, Enum.reverse(expr) }
|
||||
tokenize rest, new_line, new_line, [], [final | acc]
|
||||
tokenize rest, new_line, [], [final | acc]
|
||||
end
|
||||
|
||||
defp tokenize('\n' ++ t, current_line, line, buffer, acc) do
|
||||
tokenize t, current_line, line + 1, [?\n|buffer], acc
|
||||
defp tokenize('\n' ++ t, line, buffer, acc) do
|
||||
tokenize t, line + 1, [?\n|buffer], acc
|
||||
end
|
||||
|
||||
defp tokenize([h|t], current_line, line, buffer, acc) do
|
||||
tokenize t, current_line, line, [h|buffer], acc
|
||||
defp tokenize([h|t], line, buffer, acc) do
|
||||
tokenize t, line, [h|buffer], acc
|
||||
end
|
||||
|
||||
defp tokenize([], current_line, _line, buffer, acc) do
|
||||
tokenize_text(current_line, buffer, acc)
|
||||
defp tokenize([], _line, buffer, acc) do
|
||||
tokenize_text(buffer, acc)
|
||||
end
|
||||
|
||||
# Retrieve marker for <%
|
||||
@@ -140,20 +141,21 @@ defmodule EEx.Tokenizer do
|
||||
end
|
||||
|
||||
defp check_spaces(string, token) do
|
||||
if only_spaces?(string), do: token, else: :expr
|
||||
if Enum.all?(string, &(&1 in [?\s, ?\t])) do
|
||||
token
|
||||
else
|
||||
:expr
|
||||
end
|
||||
end
|
||||
|
||||
defp only_spaces?([h|t]) when h in [?\s, ?\t], do: only_spaces?(t)
|
||||
defp only_spaces?(other), do: other == []
|
||||
|
||||
# Tokenize the buffered text by appending
|
||||
# it to the given accumulator.
|
||||
|
||||
defp tokenize_text(_line, [], acc) do
|
||||
defp tokenize_text([], acc) do
|
||||
acc
|
||||
end
|
||||
|
||||
defp tokenize_text(line, buffer, acc) do
|
||||
[{ :text, line, String.from_char_list!(Enum.reverse(buffer)) } | acc]
|
||||
defp tokenize_text(buffer, acc) do
|
||||
[{ :text, Enum.reverse(buffer) } | acc]
|
||||
end
|
||||
end
|
||||
|
||||
@@ -7,17 +7,16 @@ defmodule EEx.SmartEngineTest do
|
||||
assert_eval "foo bar", "foo bar"
|
||||
end
|
||||
|
||||
test "evaluates with assigns as a ListDict" do
|
||||
test "evaluates with assigns as keywords" do
|
||||
assert_eval "1", "<%= @foo %>", assigns: [foo: 1]
|
||||
end
|
||||
|
||||
test "evaluates with assigns as a HashDict" do
|
||||
assigns = HashDict.new [foo: 1]
|
||||
assert_eval "1", "<%= @foo %>", assigns: assigns
|
||||
test "evaluates with assigns as a map" do
|
||||
assert_eval "1", "<%= @foo %>", assigns: %{foo: 1}
|
||||
end
|
||||
|
||||
test "evaluates with loops" do
|
||||
assert_eval "1\n2\n3\n", "<%= lc x inlist [1, 2, 3] do %><%= x %>\n<% end %>"
|
||||
assert_eval "1\n2\n3\n", "<%= for x <- [1, 2, 3] do %><%= x %>\n<% end %>"
|
||||
end
|
||||
|
||||
defp assert_eval(expected, actual, binding \\ []) do
|
||||
|
||||
@@ -5,23 +5,23 @@ defmodule EEx.TokenizerTest do
|
||||
require EEx.Tokenizer, as: T
|
||||
|
||||
test "simple chars lists" do
|
||||
assert T.tokenize('foo', 1) == [ { :text, 1, "foo" } ]
|
||||
assert T.tokenize('foo', 1) == [ { :text, 'foo' } ]
|
||||
end
|
||||
|
||||
test "simple strings" do
|
||||
assert T.tokenize("foo", 1) == [ { :text, 1, "foo" } ]
|
||||
assert T.tokenize("foo", 1) == [ { :text, 'foo' } ]
|
||||
end
|
||||
|
||||
test "strings with embedded code" do
|
||||
assert T.tokenize('foo <% bar %>', 1) == [ { :text, 1, "foo " }, { :expr, 1, "", ' bar ' } ]
|
||||
assert T.tokenize('foo <% bar %>', 1) == [ { :text, 'foo ' }, { :expr, 1, "", ' bar ' } ]
|
||||
end
|
||||
|
||||
test "strings with embedded equals code" do
|
||||
assert T.tokenize('foo <%= bar %>', 1) == [ { :text, 1, "foo " }, { :expr, 1, "=", ' bar ' } ]
|
||||
assert T.tokenize('foo <%= bar %>', 1) == [ { :text, 'foo ' }, { :expr, 1, "=", ' bar ' } ]
|
||||
end
|
||||
|
||||
test "strings with more than one line" do
|
||||
assert T.tokenize('foo\n<%= bar %>', 1) == [ { :text, 1, "foo\n" }, { :expr, 2, "=", ' bar ' } ]
|
||||
assert T.tokenize('foo\n<%= bar %>', 1) == [ { :text, 'foo\n' }, { :expr, 2, "=", ' bar ' } ]
|
||||
end
|
||||
|
||||
test "strings with more than one line and expression with more than one line" do
|
||||
@@ -33,66 +33,66 @@ baz %>
|
||||
'''
|
||||
|
||||
assert T.tokenize(string, 1) == [
|
||||
{:text, 1, "foo "},
|
||||
{:text, 'foo '},
|
||||
{:expr, 1, "=", ' bar\n\nbaz '},
|
||||
{:text, 3, "\n"},
|
||||
{:text, '\n'},
|
||||
{:expr, 4, "", ' foo '},
|
||||
{:text, 4, "\n"}
|
||||
{:text, '\n'}
|
||||
]
|
||||
end
|
||||
|
||||
test "quotation" do
|
||||
assert T.tokenize('foo <%% true %>', 1) == [
|
||||
{ :text, 1, "foo <% true %>" }
|
||||
{ :text, 'foo <% true %>' }
|
||||
]
|
||||
end
|
||||
|
||||
test "quotation with do/end" do
|
||||
assert T.tokenize('foo <%% true do %>bar<%% end %>', 1) == [
|
||||
{ :text, 1, "foo <% true do %>bar<% end %>" }
|
||||
{ :text, 'foo <% true do %>bar<% end %>' }
|
||||
]
|
||||
end
|
||||
|
||||
test "comments" do
|
||||
assert T.tokenize('foo <%# true %>', 1) == [
|
||||
{ :text, 1, "foo " }
|
||||
{ :text, 'foo ' }
|
||||
]
|
||||
end
|
||||
|
||||
test "comments with do/end" do
|
||||
assert T.tokenize('foo <%# true do %>bar<%# end %>', 1) == [
|
||||
{ :text, 1, "foo bar" }
|
||||
{ :text, 'foo bar' }
|
||||
]
|
||||
end
|
||||
|
||||
test "strings with embedded do end" do
|
||||
assert T.tokenize('foo <% if true do %>bar<% end %>', 1) == [
|
||||
{ :text, 1, "foo " },
|
||||
{ :text, 'foo ' },
|
||||
{ :start_expr, 1, "", ' if true do ' },
|
||||
{ :text, 1, "bar" },
|
||||
{ :text, 'bar' },
|
||||
{ :end_expr, 1, "", ' end ' }
|
||||
]
|
||||
end
|
||||
|
||||
test "strings with embedded -> end" do
|
||||
assert T.tokenize('foo <% cond do %><% false -> %>bar<% true -> %>baz<% end %>', 1) == [
|
||||
{ :text, 1, "foo " },
|
||||
{ :text, 'foo ' },
|
||||
{ :start_expr, 1, "", ' cond do ' },
|
||||
{ :middle_expr, 1, "", ' false -> ' },
|
||||
{ :text, 1, "bar" },
|
||||
{ :text, 'bar' },
|
||||
{ :middle_expr, 1, "", ' true -> ' },
|
||||
{ :text, 1, "baz" },
|
||||
{ :text, 'baz' },
|
||||
{ :end_expr, 1, "", ' end ' }
|
||||
]
|
||||
end
|
||||
|
||||
test "strings with embedded keywords blocks" do
|
||||
assert T.tokenize('foo <% if true do %>bar<% else %>baz<% end %>', 1) == [
|
||||
{ :text, 1, "foo " },
|
||||
{ :text, 'foo ' },
|
||||
{ :start_expr, 1, "", ' if true do ' },
|
||||
{ :text, 1, "bar" },
|
||||
{ :text, 'bar' },
|
||||
{ :middle_expr, 1, "", ' else ' },
|
||||
{ :text, 1, "baz" },
|
||||
{ :text, 'baz' },
|
||||
{ :end_expr, 1, "", ' end ' }
|
||||
]
|
||||
end
|
||||
|
||||
@@ -29,7 +29,7 @@ defmodule EExText.Compiled do
|
||||
try do
|
||||
:erlang.error "failed"
|
||||
catch
|
||||
:error, _, stack -> stack
|
||||
:error, _ -> System.stacktrace
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -45,6 +45,9 @@ end
|
||||
defmodule EExTest do
|
||||
use ExUnit.Case, async: true
|
||||
|
||||
doctest EEx
|
||||
doctest EEx.AssignsEngine
|
||||
|
||||
test "evaluates simple string" do
|
||||
assert_eval "foo bar", "foo bar"
|
||||
end
|
||||
@@ -344,6 +347,26 @@ foo
|
||||
}
|
||||
end
|
||||
|
||||
defmodule TestEngine do
|
||||
@behaviour EEx.Engine
|
||||
|
||||
def handle_body(body) do
|
||||
{ :wrapped, body }
|
||||
end
|
||||
|
||||
def handle_text(buffer, text) do
|
||||
EEx.Engine.handle_text(buffer, text)
|
||||
end
|
||||
|
||||
def handle_expr(buffer, mark, expr) do
|
||||
EEx.Engine.handle_expr(buffer, mark, expr)
|
||||
end
|
||||
end
|
||||
|
||||
test "calls handle_body" do
|
||||
assert { :wrapped, "foo" } = EEx.eval_string("foo", [], engine: TestEngine)
|
||||
end
|
||||
|
||||
defp assert_eval(expected, actual) do
|
||||
result = EEx.eval_string(actual, [], file: __ENV__.file, engine: EEx.Engine)
|
||||
assert result == expected
|
||||
|
||||
@@ -10,10 +10,11 @@
|
||||
|
||||
-record(elixir_scope, {
|
||||
context=nil, %% can be match, guards or nil
|
||||
extra=nil, %% extra information about the context, like fn_match and do_match
|
||||
extra=nil, %% extra information about the context, like fn_match and map_key
|
||||
noname=false, %% when true, don't add new names (used by try)
|
||||
super=false, %% when true, it means super was invoked
|
||||
caller=false, %% when true, it means caller was invoked
|
||||
return=true, %% when true, the return value is used
|
||||
module=nil, %% the current module
|
||||
function=nil, %% the current function
|
||||
vars=[], %% a dict of defined variables and their alias
|
||||
|
||||
+25
-20
@@ -7,6 +7,22 @@ defprotocol Access do
|
||||
is translated to `access foo, bar` which, by default,
|
||||
invokes the `Access.access` protocol.
|
||||
|
||||
This protocol is implemented by default for Lists, Maps
|
||||
and dictionary like types:
|
||||
|
||||
iex> keywords = [a: 1, b: 2]
|
||||
iex> keywords[:a]
|
||||
1
|
||||
|
||||
iex> map = %{ a: 1, b: 2 }
|
||||
iex> map[:a]
|
||||
1
|
||||
|
||||
iex> star_ratings = %{ 1.0 => "★", 1.5 => "★☆", 2.0 => "★★" }
|
||||
iex> star_ratings[1.5]
|
||||
"★☆"
|
||||
|
||||
The key access must be implemented using the `===` operator.
|
||||
This protocol is limited and is implemented only for the
|
||||
following built-in types: keywords, records and functions.
|
||||
"""
|
||||
@@ -18,33 +34,22 @@ defprotocol Access do
|
||||
end
|
||||
|
||||
defimpl Access, for: List do
|
||||
@doc """
|
||||
Access the given key in a tuple list.
|
||||
The key is found via the `===` operator.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> keywords = [a: 1, b: 2]
|
||||
...> keywords[:a]
|
||||
1
|
||||
|
||||
iex> star_ratings = [{1.0, "★"}, {1.5, "★☆"}, {2.0, "★★"}]
|
||||
...> star_ratings[1.5]
|
||||
"★☆"
|
||||
|
||||
"""
|
||||
def access(dict, key)
|
||||
def access([{ key, value }|_], key), do: value
|
||||
def access([{ _, _ }|t], key), do: access(t, key)
|
||||
def access([], _key), do: nil
|
||||
end
|
||||
|
||||
defimpl Access, for: Map do
|
||||
def access(map, key) do
|
||||
case :maps.find(key, map) do
|
||||
{ :ok, value } -> value
|
||||
:error -> nil
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defimpl Access, for: Atom do
|
||||
@doc """
|
||||
The access protocol can only be accessed by atoms
|
||||
at compilation time. If we reach this, we should raise
|
||||
an exception.
|
||||
"""
|
||||
def access(nil, _) do
|
||||
nil
|
||||
end
|
||||
|
||||
@@ -48,47 +48,6 @@ defmodule Application.Behaviour do
|
||||
|
||||
"""
|
||||
|
||||
# Starts the given application and all of its dependencies that
|
||||
# have not been started yet recursively.
|
||||
#
|
||||
# ## Supported types
|
||||
#
|
||||
# When starting an application, a type can be given:
|
||||
#
|
||||
# * `:permanent` - If a permanent application terminates, all other
|
||||
# applications and the runtime system are also terminated;
|
||||
# * `:transient` - If a transient application terminates with reason
|
||||
# `:normal`, this is reported but no other applications are terminated.
|
||||
# If a transient application terminates abnormally, all other
|
||||
# applications and the runtime system are also terminated;
|
||||
# * `:temporary` - If a temporary application terminates, this is reported
|
||||
# but no other applications are terminated.
|
||||
#
|
||||
# The type only applies to the application being started. Its dependencies
|
||||
# are all started with default type (which is :temporary).
|
||||
#
|
||||
# Note that transient mode is of little practical use, since when a
|
||||
# supervision tree terminates, the reason is set to shutdown, not normal.
|
||||
#
|
||||
# ## Examples
|
||||
#
|
||||
# Application.Behaviour.start(:my_app)
|
||||
#
|
||||
@doc false
|
||||
def start(app, type \\ :temporary) do
|
||||
case :application.start(app, type) do
|
||||
{ :error, { :not_started, dep } } ->
|
||||
case start(dep) do
|
||||
:ok -> start(app, type)
|
||||
other -> other
|
||||
end
|
||||
{ :error, { :already_started, _ } } ->
|
||||
:ok
|
||||
other ->
|
||||
other
|
||||
end
|
||||
end
|
||||
|
||||
@doc false
|
||||
defmacro __using__(_) do
|
||||
quote location: :keep do
|
||||
|
||||
@@ -0,0 +1,464 @@
|
||||
defmodule Base do
|
||||
import Bitwise
|
||||
|
||||
@moduledoc """
|
||||
This module provides data encoding and decoding functions
|
||||
according to [RFC 4648](http://tools.ietf.org/html/rfc4648).
|
||||
|
||||
This document defines the commonly used base 64, base 32, and base
|
||||
16 encoding schemes.
|
||||
"""
|
||||
|
||||
b16_alphabet = Enum.with_index '0123456789ABCDEF'
|
||||
b64_alphabet = Enum.with_index 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'
|
||||
b64url_alphabet = Enum.with_index 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_'
|
||||
b32_alphabet = Enum.with_index 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567'
|
||||
b32hex_alphabet = Enum.with_index '0123456789ABCDEFGHIJKLMNOPQRSTUV'
|
||||
|
||||
Enum.each [ { :enc16, :dec16, b16_alphabet },
|
||||
{ :enc64, :dec64, b64_alphabet },
|
||||
{ :enc32, :dec32, b32_alphabet },
|
||||
{ :enc64url, :dec64url, b64url_alphabet },
|
||||
{ :enc32hex, :dec32hex, b32hex_alphabet } ], fn({enc, dec, alphabet}) ->
|
||||
for {encoding, value} <- alphabet do
|
||||
defp unquote(enc)(unquote(value)), do: unquote(encoding)
|
||||
defp unquote(dec)(unquote(encoding)), do: unquote(value)
|
||||
end
|
||||
defp unquote(dec)(c) do
|
||||
raise ArgumentError, message: "non-alphabet digit found: #{<<c>>}"
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Encodes a binary string into a base 16 encoded string.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Base.encode16("foobar")
|
||||
"666F6F626172"
|
||||
|
||||
"""
|
||||
@spec encode16(binary) :: binary
|
||||
def encode16(data) when is_binary(data) do
|
||||
do_encode16(data, &enc16/1)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Decodes a base 16 encoded string into a binary string.
|
||||
|
||||
The following alphabet is used both for encoding and decoding:
|
||||
|
||||
| Value | Encoding | Value | Encoding | Value | Encoding | Value | Encoding |
|
||||
|------:|---------:|------:|---------:|------:|---------:|------:|---------:|
|
||||
| 0| 0| 4| 4| 8| 8| 12| C|
|
||||
| 1| 1| 5| 5| 9| 9| 13| D|
|
||||
| 2| 2| 6| 6| 10| A| 14| E|
|
||||
| 3| 3| 7| 7| 11| B| 15| F|
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Base.decode16("666F6F626172")
|
||||
{:ok, "foobar"}
|
||||
|
||||
"""
|
||||
@spec decode16(binary) :: { :ok, binary } | :error
|
||||
def decode16(string) when is_binary(string) do
|
||||
{ :ok, decode16!(string) }
|
||||
rescue
|
||||
ArgumentError -> :error
|
||||
end
|
||||
|
||||
@doc """
|
||||
Decodes a base 16 encoded string into a binary string.
|
||||
|
||||
An `ArgumentError` exception is raised if the padding is incorrect or
|
||||
a non-alphabet character is present in the string.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Base.decode16!("666F6F626172")
|
||||
"foobar"
|
||||
|
||||
"""
|
||||
@spec decode16!(binary) :: binary
|
||||
def decode16!(string) when is_binary(string) do
|
||||
do_decode16(string, &dec16/1)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Encodes a binary string into a base 64 encoded string.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Base.encode64("foobar")
|
||||
"Zm9vYmFy"
|
||||
|
||||
"""
|
||||
@spec encode64(binary) :: binary
|
||||
def encode64(data) when is_binary(data) do
|
||||
do_encode64(data, &enc64/1)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Decodes a base 64 encoded string into a binary string.
|
||||
|
||||
The following alphabet is used both for encoding and decoding:
|
||||
|
||||
| Value | Encoding | Value | Encoding | Value | Encoding | Value | Encoding |
|
||||
|------:|---------:|------:|---------:|------:|---------:|------:|---------:|
|
||||
| 0| A| 17| R| 34| i| 51| z|
|
||||
| 1| B| 18| S| 35| j| 52| 0|
|
||||
| 2| C| 19| T| 36| k| 53| 1|
|
||||
| 3| D| 20| U| 37| l| 54| 2|
|
||||
| 4| E| 21| V| 38| m| 55| 3|
|
||||
| 5| F| 22| W| 39| n| 56| 4|
|
||||
| 6| G| 23| X| 40| o| 57| 5|
|
||||
| 7| H| 24| Y| 41| p| 58| 6|
|
||||
| 8| I| 25| Z| 42| q| 59| 7|
|
||||
| 9| J| 26| a| 43| r| 60| 8|
|
||||
| 10| K| 27| b| 44| s| 61| 9|
|
||||
| 11| L| 28| c| 45| t| 62| +|
|
||||
| 12| M| 29| d| 46| u| 63| /|
|
||||
| 13| N| 30| e| 47| v| | |
|
||||
| 14| O| 31| f| 48| w| (pad)| =|
|
||||
| 15| P| 32| g| 49| x| | |
|
||||
| 16| Q| 33| h| 50| y| | |
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Base.decode64("Zm9vYmFy")
|
||||
{:ok, "foobar"}
|
||||
|
||||
"""
|
||||
@spec decode64(binary) :: { :ok, binary } | :error
|
||||
def decode64(string) when is_binary(string) do
|
||||
{ :ok, do_decode64(string, &dec64/1) }
|
||||
rescue
|
||||
ArgumentError -> :error
|
||||
end
|
||||
|
||||
@doc """
|
||||
Decodes a base 64 encoded string into a binary string.
|
||||
|
||||
The following alphabet is used both for encoding and decoding:
|
||||
|
||||
An `ArgumentError` exception is raised if the padding is incorrect or
|
||||
a non-alphabet character is present in the string.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Base.decode64!("Zm9vYmFy")
|
||||
"foobar"
|
||||
|
||||
"""
|
||||
@spec decode64!(binary) :: binary
|
||||
def decode64!(string) when is_binary(string) do
|
||||
do_decode64(string, &dec64/1)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Encodes a binary string into a base 64 encoded string with URL and filename
|
||||
safe alphabet.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Base.url_encode64(<<255,127,254,252>>)
|
||||
"_3_-_A=="
|
||||
|
||||
"""
|
||||
@spec url_encode64(binary) :: binary
|
||||
def url_encode64(data) when is_binary(data) do
|
||||
do_encode64(data, &enc64url/1)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Decodes a base 64 encoded string with URL and filename safe alphabet
|
||||
into a binary string.
|
||||
|
||||
The following alphabet is used both for encoding and decoding:
|
||||
|
||||
| Value | Encoding | Value | Encoding | Value | Encoding | Value | Encoding |
|
||||
|------:|---------:|------:|---------:|------:|---------:|------:|---------:|
|
||||
| 0| A| 17| R| 34| i| 51| z|
|
||||
| 1| B| 18| S| 35| j| 52| 0|
|
||||
| 2| C| 19| T| 36| k| 53| 1|
|
||||
| 3| D| 20| U| 37| l| 54| 2|
|
||||
| 4| E| 21| V| 38| m| 55| 3|
|
||||
| 5| F| 22| W| 39| n| 56| 4|
|
||||
| 6| G| 23| X| 40| o| 57| 5|
|
||||
| 7| H| 24| Y| 41| p| 58| 6|
|
||||
| 8| I| 25| Z| 42| q| 59| 7|
|
||||
| 9| J| 26| a| 43| r| 60| 8|
|
||||
| 10| K| 27| b| 44| s| 61| 9|
|
||||
| 11| L| 28| c| 45| t| 62| -|
|
||||
| 12| M| 29| d| 46| u| 63| _|
|
||||
| 13| N| 30| e| 47| v| | |
|
||||
| 14| O| 31| f| 48| w| (pad)| =|
|
||||
| 15| P| 32| g| 49| x| | |
|
||||
| 16| Q| 33| h| 50| y| | |
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Base.url_decode64("_3_-_A==")
|
||||
{:ok, <<255,127,254,252>>}
|
||||
|
||||
"""
|
||||
@spec url_decode64(binary) :: { :ok, binary } | :error
|
||||
def url_decode64(string) when is_binary(string) do
|
||||
{ :ok, do_decode64(string, &dec64url/1) }
|
||||
rescue
|
||||
ArgumentError -> :error
|
||||
end
|
||||
|
||||
@doc """
|
||||
Decodes a base 64 encoded string with URL and filename safe alphabet
|
||||
into a binary string.
|
||||
|
||||
An `ArgumentError` exception is raised if the padding is incorrect or
|
||||
a non-alphabet character is present in the string.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Base.url_decode64!("_3_-_A==")
|
||||
<<255,127,254,252>>
|
||||
|
||||
"""
|
||||
@spec url_decode64!(binary) :: binary
|
||||
def url_decode64!(string) when is_binary(string) do
|
||||
do_decode64(string, &dec64url/1)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Encodes a binary string into a base 32 encoded string.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Base.encode32("foobar")
|
||||
"MZXW6YTBOI======"
|
||||
|
||||
"""
|
||||
@spec encode32(binary) :: binary
|
||||
def encode32(data) when is_binary(data) do
|
||||
do_encode32(data, &enc32/1)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Decodes a base 32 encoded string into a binary string.
|
||||
|
||||
The following alphabet is used both for encoding and decoding:
|
||||
|
||||
| Value | Encoding | Value | Encoding | Value | Encoding | Value | Encoding |
|
||||
|------:|---------:|------:|---------:|------:|---------:|------:|---------:|
|
||||
| 0| A| 9| J| 18| S| 27| 3|
|
||||
| 1| B| 10| K| 19| T| 28| 4|
|
||||
| 2| C| 11| L| 20| U| 29| 5|
|
||||
| 3| D| 12| M| 21| V| 30| 6|
|
||||
| 4| E| 13| N| 22| W| 31| 7|
|
||||
| 5| F| 14| O| 23| X| | |
|
||||
| 6| G| 15| P| 24| Y| (pad)| =|
|
||||
| 7| H| 16| Q| 25| Z| | |
|
||||
| 8| I| 17| R| 26| 2| | |
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Base.decode32("MZXW6YTBOI======")
|
||||
{:ok, "foobar"}
|
||||
|
||||
"""
|
||||
@spec decode32(binary) :: { :ok, binary } | :error
|
||||
def decode32(string) do
|
||||
{ :ok, do_decode32(string, &dec32/1) }
|
||||
rescue
|
||||
ArgumentError -> :error
|
||||
end
|
||||
|
||||
@doc """
|
||||
Decodes a base 32 encoded string into a binary string.
|
||||
|
||||
An `ArgumentError` exception is raised if the padding is incorrect or
|
||||
a non-alphabet character is present in the string.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Base.decode32!("MZXW6YTBOI======")
|
||||
"foobar"
|
||||
|
||||
"""
|
||||
@spec decode32!(binary) :: binary
|
||||
def decode32!(string) do
|
||||
do_decode32(string, &dec32/1)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Encodes a binary string into a base 32 encoded string with an
|
||||
extended hexadecimal alphabet.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Base.hex_encode32("foobar")
|
||||
"CPNMUOJ1E8======"
|
||||
|
||||
"""
|
||||
@spec hex_encode32(binary) :: binary
|
||||
def hex_encode32(data) when is_binary(data) do
|
||||
do_encode32(data, &enc32hex/1)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Decodes a base 32 encoded string with extended hexadecimal alphabet
|
||||
into a binary string.
|
||||
|
||||
The following alphabet is used both for encoding and decoding:
|
||||
|
||||
| Value | Encoding | Value | Encoding | Value | Encoding | Value | Encoding |
|
||||
|------:|---------:|------:|---------:|------:|---------:|------:|---------:|
|
||||
| 0| 0| 9| 9| 18| I| 27| R|
|
||||
| 1| 1| 10| A| 19| J| 28| S|
|
||||
| 2| 2| 11| B| 20| K| 29| T|
|
||||
| 3| 3| 12| C| 21| L| 30| U|
|
||||
| 4| 4| 13| D| 22| M| 31| V|
|
||||
| 5| 5| 14| E| 23| N| | |
|
||||
| 6| 6| 15| F| 24| O| (pad)| =|
|
||||
| 7| 7| 16| G| 25| P| | |
|
||||
| 8| 8| 17| H| 26| Q| | |
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Base.hex_decode32("CPNMUOJ1E8======")
|
||||
{:ok, "foobar"}
|
||||
|
||||
"""
|
||||
@spec hex_decode32(binary) :: { :ok, binary } | :error
|
||||
def hex_decode32(string) when is_binary(string) do
|
||||
{ :ok, do_decode32(string, &dec32hex/1) }
|
||||
rescue
|
||||
ArgumentError -> :error
|
||||
end
|
||||
|
||||
@doc """
|
||||
Decodes a base 32 encoded string with extended hexadecimal alphabet
|
||||
into a binary string.
|
||||
|
||||
An `ArgumentError` exception is raised if the padding is incorrect or
|
||||
a non-alphabet character is present in the string.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Base.hex_decode32!("CPNMUOJ1E8======")
|
||||
"foobar"
|
||||
|
||||
"""
|
||||
@spec hex_decode32!(binary) :: binary
|
||||
def hex_decode32!(string) when is_binary(string) do
|
||||
do_decode32(string, &dec32hex/1)
|
||||
end
|
||||
|
||||
defp do_encode16(<<>>, _), do: <<>>
|
||||
defp do_encode16(data, enc) do
|
||||
for <<c::4 <- data>>, into: <<>>, do: <<enc.(c)::8>>
|
||||
end
|
||||
|
||||
defp do_decode16(<<>>, _), do: <<>>
|
||||
defp do_decode16(string, dec) when rem(byte_size(string), 2) == 0 do
|
||||
for <<c1::8, c2::8 <- string>>, into: <<>> do
|
||||
<<dec.(c1)::4, dec.(c2)::4>>
|
||||
end
|
||||
end
|
||||
defp do_decode16(_, _) do
|
||||
raise ArgumentError, message: "odd-length string"
|
||||
end
|
||||
|
||||
defp do_encode64(<<>>, _), do: <<>>
|
||||
defp do_encode64(data, enc) do
|
||||
split = 3 * div(byte_size(data), 3)
|
||||
<<main::[size(split), binary], rest::binary>> = data
|
||||
main = for <<c::6 <- main>>, into: <<>>, do: <<enc.(c)::8>>
|
||||
case rest do
|
||||
<<c1::6, c2::6, c3::4>> ->
|
||||
<<main::binary, enc.(c1)::8, enc.(c2)::8, enc.(bsl(c3, 2))::8, ?=>>
|
||||
<<c1::6, c2::2>> ->
|
||||
<<main::binary, enc.(c1)::8, enc.(bsl(c2, 4))::8, ?=, ?=>>
|
||||
<<>> ->
|
||||
main
|
||||
end
|
||||
end
|
||||
|
||||
defp do_decode64(<<>>, _), do: <<>>
|
||||
defp do_decode64(string, dec) when rem(byte_size(string), 4) == 0 do
|
||||
split = byte_size(string) - 4
|
||||
<<main::[size(split), binary], rest::binary>> = string
|
||||
main = for <<c::8 <- main>>, into: <<>>, do: <<dec.(c)::6>>
|
||||
case rest do
|
||||
<<c1::8, c2::8, ?=, ?=>> ->
|
||||
<<main::binary, dec.(c1)::6, bsr(dec.(c2), 4)::2>>
|
||||
<<c1::8, c2::8, c3::8, ?=>> ->
|
||||
<<main::binary, dec.(c1)::6, dec.(c2)::6, bsr(dec.(c3), 2)::4>>
|
||||
<<c1::8, c2::8, c3::8, c4::8>> ->
|
||||
<<main::binary, dec.(c1)::6, dec.(c2)::6, dec.(c3)::6, dec.(c4)::6>>
|
||||
<<>> ->
|
||||
main
|
||||
end
|
||||
end
|
||||
defp do_decode64(_, _) do
|
||||
raise ArgumentError, message: "incorrect padding"
|
||||
end
|
||||
|
||||
defp do_encode32(<<>>, _), do: <<>>
|
||||
defp do_encode32(data, enc) do
|
||||
split = 5 * div(byte_size(data), 5)
|
||||
<<main::[size(split), binary], rest::binary>> = data
|
||||
main = for <<c::5 <- main>>, into: <<>>, do: <<enc.(c)::8>>
|
||||
case rest do
|
||||
<<c1::5, c2::5, c3::5, c4::5, c5::5, c6::5, c7::2>> ->
|
||||
<<main::binary,
|
||||
enc.(c1)::8, enc.(c2)::8, enc.(c3)::8, enc.(c4)::8,
|
||||
enc.(c5)::8, enc.(c6)::8, enc.(bsl(c7, 3))::8, ?=>>
|
||||
<<c1::5, c2::5, c3::5, c4::5, c5::4>> ->
|
||||
<<main::binary,
|
||||
enc.(c1)::8, enc.(c2)::8, enc.(c3)::8, enc.(c4)::8,
|
||||
enc.(bsl(c5, 1))::8, ?=, ?=, ?=>>
|
||||
<<c1::5, c2::5, c3::5, c4::1>> ->
|
||||
<<main::binary,
|
||||
enc.(c1)::8, enc.(c2)::8, enc.(c3)::8, enc.(bsl(c4, 4))::8,
|
||||
?=, ?=, ?=, ?=>>
|
||||
<<c1::5, c2::3>> ->
|
||||
<<main::binary,
|
||||
enc.(c1)::8, enc.(bsl(c2, 2))::8, ?=, ?=,
|
||||
?=, ?=, ?=, ?=>>
|
||||
<<>> ->
|
||||
main
|
||||
end
|
||||
end
|
||||
|
||||
defp do_decode32(<<>>, _), do: <<>>
|
||||
defp do_decode32(string, dec) when rem(byte_size(string), 8) == 0 do
|
||||
split = byte_size(string) - 8
|
||||
<<main::[size(split), binary], rest::binary>> = string
|
||||
main = for <<c::8 <- main>>, into: <<>>, do: <<dec.(c)::5>>
|
||||
case rest do
|
||||
<<c1::8, c2::8, ?=, ?=, ?=, ?=, ?=, ?=>> ->
|
||||
<<main::binary, dec.(c1)::5, bsr(dec.(c2), 2)::3>>
|
||||
<<c1::8, c2::8, c3::8, c4::8, ?=, ?=, ?=, ?=>> ->
|
||||
<<main::binary,
|
||||
dec.(c1)::5, dec.(c2)::5, dec.(c3)::5, bsr(dec.(c4), 4)::1>>
|
||||
<<c1::8, c2::8, c3::8, c4::8, c5::8, ?=, ?=, ?=>> ->
|
||||
<<main::binary,
|
||||
dec.(c1)::5, dec.(c2)::5, dec.(c3)::5, dec.(c4)::5,
|
||||
bsr(dec.(c5), 1)::4>>
|
||||
<<c1::8, c2::8, c3::8, c4::8, c5::8, c6::8, c7::8, ?=>> ->
|
||||
<<main::binary,
|
||||
dec.(c1)::5, dec.(c2)::5, dec.(c3)::5, dec.(c4)::5,
|
||||
dec.(c5)::5, dec.(c6)::5, bsr(dec.(c7), 3)::2>>
|
||||
<<c1::8, c2::8, c3::8, c4::8, c5::8, c6::8, c7::8, c8::8>> ->
|
||||
<<main::binary,
|
||||
dec.(c1)::5, dec.(c2)::5, dec.(c3)::5, dec.(c4)::5,
|
||||
dec.(c5)::5, dec.(c6)::5, dec.(c7)::5, dec.(c8)::5>>
|
||||
<<>> ->
|
||||
main
|
||||
end
|
||||
end
|
||||
defp do_decode32(_, _) do
|
||||
raise ArgumentError, message: "incorrect padding"
|
||||
end
|
||||
|
||||
end
|
||||
+23
-84
@@ -1,6 +1,6 @@
|
||||
defmodule Behaviour do
|
||||
@moduledoc """
|
||||
Utilities for defining behaviour intefaces.
|
||||
Utilities for defining behaviour interfaces.
|
||||
|
||||
Behaviours can be referenced by other modules
|
||||
to ensure they implement required callbacks.
|
||||
@@ -46,60 +46,53 @@ defmodule Behaviour do
|
||||
@doc """
|
||||
Define a function callback according to the given type specification.
|
||||
"""
|
||||
defmacro defcallback({ :::, _, [fun, return_and_guard] }) do
|
||||
{ return, guard } = split_return_and_guard(return_and_guard)
|
||||
do_defcallback(fun, return, guard, __CALLER__)
|
||||
end
|
||||
|
||||
defmacro defcallback(fun) do
|
||||
do_defcallback(fun, quote(do: term), [], __CALLER__)
|
||||
defmacro defcallback(spec) do
|
||||
do_defcallback(split_spec(spec, quote(do: term)), __CALLER__)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Define a macro callback according to the given type specification.
|
||||
"""
|
||||
defmacro defmacrocallback({ :::, _, [fun, return_and_guard] }) do
|
||||
{ return, guard } = split_return_and_guard(return_and_guard)
|
||||
do_defmacrocallback(fun, return, guard, __CALLER__)
|
||||
defmacro defmacrocallback(spec) do
|
||||
do_defmacrocallback(split_spec(spec, quote(do: Macro.t)), __CALLER__)
|
||||
end
|
||||
|
||||
defmacro defmacrocallback(fun) do
|
||||
do_defmacrocallback(fun, quote(do: Macro.t), [], __CALLER__)
|
||||
defp split_spec({ :when, _, [{ :::, _, [spec, return] }, guard] }, _default) do
|
||||
{ spec, return, guard }
|
||||
end
|
||||
|
||||
defp split_return_and_guard({ :when, _, [return, guard] }) do
|
||||
{ return, guard }
|
||||
defp split_spec({ :when, _, [spec, guard] }, default) do
|
||||
{ spec, default, guard }
|
||||
end
|
||||
|
||||
defp split_return_and_guard({ :|, meta, [left, right] }) do
|
||||
{ return, guard } = split_return_and_guard(right)
|
||||
{ { :|, meta, [left, return] }, guard }
|
||||
defp split_spec({ :::, _, [spec, return] }, _default) do
|
||||
{ spec, return, [] }
|
||||
end
|
||||
|
||||
defp split_return_and_guard(other) do
|
||||
{ other, [] }
|
||||
defp split_spec(spec, default) do
|
||||
{ spec, default, [] }
|
||||
end
|
||||
|
||||
defp do_defcallback(fun, return, guards, caller) do
|
||||
case Macro.decompose_call(fun) do
|
||||
defp do_defcallback({ spec, return, guards }, caller) do
|
||||
case Macro.decompose_call(spec) do
|
||||
{ name, args } ->
|
||||
do_callback(:def, name, args, name, length(args), args, return, guards, caller)
|
||||
_ ->
|
||||
raise ArgumentError, message: "invalid syntax in defcallback #{Macro.to_string(fun)}"
|
||||
raise ArgumentError, message: "invalid syntax in defcallback #{Macro.to_string(spec)}"
|
||||
end
|
||||
end
|
||||
|
||||
defp do_defmacrocallback(fun, return, guards, caller) do
|
||||
case Macro.decompose_call(fun) do
|
||||
defp do_defmacrocallback({ spec, return, guards }, caller) do
|
||||
case Macro.decompose_call(spec) do
|
||||
{ name, args } ->
|
||||
do_callback(:defmacro, :"MACRO-#{name}", [quote(do: env :: Macro.Env.t)|args],
|
||||
name, length(args), args, return, guards, caller)
|
||||
_ ->
|
||||
raise ArgumentError, message: "invalid syntax in defmacrocallback #{Macro.to_string(fun)}"
|
||||
raise ArgumentError, message: "invalid syntax in defmacrocallback #{Macro.to_string(spec)}"
|
||||
end
|
||||
end
|
||||
|
||||
defp do_callback(kind, name, args, docs_name, docs_arity, docs_args, return, guards, caller) do
|
||||
defp do_callback(kind, name, args, docs_name, docs_arity, _docs_args, return, guards, caller) do
|
||||
Enum.each args, fn
|
||||
{ :::, _, [left, right] } ->
|
||||
ensure_not_default(left)
|
||||
@@ -111,17 +104,12 @@ defmodule Behaviour do
|
||||
end
|
||||
|
||||
quote do
|
||||
docs_args = unquote(Macro.escape(docs_args))
|
||||
@callback unquote(name)(unquote_splicing(args)) :: unquote(return) when unquote(guards)
|
||||
Behaviour.store_docs(__MODULE__, unquote(caller.line), unquote(kind),
|
||||
unquote(docs_name), docs_args, unquote(docs_arity))
|
||||
unquote(docs_name), unquote(docs_arity))
|
||||
end
|
||||
end
|
||||
|
||||
defp ensure_not_default({ ://, _, [_, _] }) do
|
||||
raise ArgumentError, message: "default arguments // not supported in defcallback/defmacrocallback"
|
||||
end
|
||||
|
||||
defp ensure_not_default({ :\\, _, [_, _] }) do
|
||||
raise ArgumentError, message: "default arguments \\\\ not supported in defcallback/defmacrocallback"
|
||||
end
|
||||
@@ -129,61 +117,12 @@ defmodule Behaviour do
|
||||
defp ensure_not_default(_), do: :ok
|
||||
|
||||
@doc false
|
||||
def store_docs(module, line, kind, name, args, arity) do
|
||||
def store_docs(module, line, kind, name, arity) do
|
||||
doc = Module.get_attribute module, :doc
|
||||
Module.delete_attribute module, :doc
|
||||
|
||||
signature = Enum.map(Stream.with_index(args), fn { arg, ix } ->
|
||||
{ simplify_signature(arg, ix + 1), [line: line], nil }
|
||||
end)
|
||||
|
||||
Module.put_attribute module, :behaviour_docs, { { name, arity }, line, kind, signature, doc }
|
||||
Module.put_attribute module, :behaviour_docs, { { name, arity }, line, kind, doc }
|
||||
end
|
||||
|
||||
defp simplify_signature({ :::, _, [var, _] }, i) do
|
||||
simplify_signature(var, i)
|
||||
end
|
||||
|
||||
defp simplify_signature({ :|, _, [first, _] }, i) do
|
||||
simplify_signature(first, i)
|
||||
end
|
||||
|
||||
defp simplify_signature({ :., _, [_, name] }, _i) do
|
||||
name
|
||||
end
|
||||
|
||||
defp simplify_signature({ :->, _, list }, i) when is_list(list) do
|
||||
:"fun#{i}"
|
||||
end
|
||||
|
||||
defp simplify_signature({ :.., _, list }, i) when is_list(list) do
|
||||
:"range#{i}"
|
||||
end
|
||||
|
||||
defp simplify_signature({ :<<>>, _, list }, i) when is_list(list) do
|
||||
:"bitstring#{i}"
|
||||
end
|
||||
|
||||
defp simplify_signature({ :{}, _, list }, i) when is_list(list) do
|
||||
:"tuple#{i}"
|
||||
end
|
||||
|
||||
defp simplify_signature({ name, _, args }, _i)
|
||||
when is_atom(name) and (is_atom(args) or is_list(args)) do
|
||||
name
|
||||
end
|
||||
|
||||
defp simplify_signature({ ast, _, list }, i) when is_list(list) do
|
||||
simplify_signature(ast, i)
|
||||
end
|
||||
|
||||
defp simplify_signature(other, i) when is_integer(other), do: :"int#{i}"
|
||||
defp simplify_signature(other, i) when is_boolean(other), do: :"bool#{i}"
|
||||
defp simplify_signature(other, i) when is_atom(other), do: :"atom#{i}"
|
||||
defp simplify_signature(other, i) when is_list(other), do: :"list#{i}"
|
||||
defp simplify_signature(other, i) when is_tuple(other), do: :"tuple#{i}"
|
||||
defp simplify_signature(_, i), do: :"arg#{i}"
|
||||
|
||||
@doc false
|
||||
defmacro __using__(_) do
|
||||
quote do
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
defmodule Bitwise do
|
||||
@moduledoc """
|
||||
This module provide macros and operators for bitwise operators.
|
||||
This module provides macros and operators for bitwise operators.
|
||||
These macros can be used in guards.
|
||||
|
||||
The easiest way to use is to simply import them into
|
||||
@@ -15,7 +15,7 @@ defmodule Bitwise do
|
||||
You can select to include only or skip operators by passing options:
|
||||
|
||||
iex> use Bitwise, only_operators: true
|
||||
...> 1 &&& 1
|
||||
iex> 1 &&& 1
|
||||
1
|
||||
|
||||
"""
|
||||
|
||||
+78
-80
@@ -59,11 +59,11 @@ defmodule Code do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Evaluate the contents given by `string`.
|
||||
Evaluate the contents given by `string`.
|
||||
|
||||
The `binding` argument is a keyword list of variable bindings.
|
||||
The `opts` argument is a keyword list of environment options.
|
||||
|
||||
The `binding` argument is a keyword list of variable bindings.
|
||||
The `opts` argument is a keyword list of environment options.
|
||||
|
||||
Those options can be:
|
||||
|
||||
* `:file` - the file to be considered in the evaluation
|
||||
@@ -118,15 +118,12 @@ defmodule Code do
|
||||
def eval_string(string, binding \\ [], opts \\ [])
|
||||
|
||||
def eval_string(string, binding, Macro.Env[] = env) do
|
||||
do_eval_string(string, binding, env.to_keywords)
|
||||
{ value, binding, _env, _scope } = :elixir.eval to_char_list(string), binding, env.to_keywords
|
||||
{ value, binding }
|
||||
end
|
||||
|
||||
def eval_string(string, binding, opts) when is_list(opts) do
|
||||
validate_eval_opts(opts)
|
||||
do_eval_string(string, binding, opts)
|
||||
end
|
||||
|
||||
defp do_eval_string(string, binding, opts) when is_list(binding) do
|
||||
{ value, binding, _env, _scope } = :elixir.eval to_char_list(string), binding, opts
|
||||
{ value, binding }
|
||||
end
|
||||
@@ -139,29 +136,26 @@ defmodule Code do
|
||||
## Examples
|
||||
|
||||
iex> contents = quote(hygiene: [vars: false], do: a + b)
|
||||
...> Code.eval_quoted(contents, [a: 1, b: 2], file: __ENV__.file, line: __ENV__.line)
|
||||
iex> Code.eval_quoted(contents, [a: 1, b: 2], file: __ENV__.file, line: __ENV__.line)
|
||||
{3, [a: 1, b: 2]}
|
||||
|
||||
For convenience, you can pass `__ENV__` as the `opts` argument and
|
||||
all options will be automatically extracted from the current environment:
|
||||
|
||||
iex> contents = quote(hygiene: [vars: false], do: a + b)
|
||||
...> Code.eval_quoted(contents, [a: 1, b: 2], __ENV__)
|
||||
iex> Code.eval_quoted(contents, [a: 1, b: 2], __ENV__)
|
||||
{3, [a: 1, b: 2]}
|
||||
|
||||
"""
|
||||
def eval_quoted(quoted, binding \\ [], opts \\ [])
|
||||
|
||||
def eval_quoted(quoted, binding, Macro.Env[] = env) do
|
||||
do_eval_quoted(quoted, binding, env.to_keywords)
|
||||
{ value, binding, _env, _scope } = :elixir.eval_quoted quoted, binding, env.to_keywords
|
||||
{ value, binding }
|
||||
end
|
||||
|
||||
def eval_quoted(quoted, binding, opts) when is_list(opts) do
|
||||
validate_eval_opts(opts)
|
||||
do_eval_quoted(quoted, binding, opts)
|
||||
end
|
||||
|
||||
defp do_eval_quoted(quoted, binding, opts) when is_list(binding) do
|
||||
{ value, binding, _env, _scope } = :elixir.eval_quoted quoted, binding, opts
|
||||
{ value, binding }
|
||||
end
|
||||
@@ -204,8 +198,8 @@ defmodule Code do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Convert the given string to its quoted form.
|
||||
|
||||
Convert the given string to its quoted form.
|
||||
|
||||
Returns `{ :ok, quoted_form }`
|
||||
if it succeeds, `{ :error, { line, error, token } }` otherwise.
|
||||
|
||||
@@ -228,17 +222,12 @@ 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.string_to_quoted(to_char_list(string), line, file, opts)
|
||||
|
||||
case res do
|
||||
{ :ok, forms } -> { :ok, forms }
|
||||
_ -> res
|
||||
end
|
||||
:elixir.string_to_quoted(to_char_list(string), line, file, opts)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Convert the given string to its quoted form.
|
||||
|
||||
Convert the given string to its quoted form.
|
||||
|
||||
It returns the ast if it succeeds,
|
||||
raises an exception otherwise. The exception is a `TokenMissingError`
|
||||
in case a token is missing (usually because the expression is incomplete),
|
||||
@@ -253,18 +242,31 @@ defmodule Code do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Load the given file.
|
||||
|
||||
Accepts `relative_to` as an argument to tell where
|
||||
the file is located. If the file was already required/loaded, loads it again.
|
||||
It returns a list of tuples `{ ModuleName, <<byte_code>> }`, one tuple for each
|
||||
module defined in the file.
|
||||
Evals the given file.
|
||||
|
||||
Notice that if `load_file` is invoked by different processes
|
||||
concurrently, the target file will be invoked concurrently
|
||||
many times. I.e. if `load_file` is called N times with
|
||||
a given file, the given file will be loaded N times. Check
|
||||
`require_file/2` if you don't want a file to be loaded concurrently.
|
||||
Accepts `relative_to` as an argument to tell where the file is located.
|
||||
|
||||
While `load_file` loads a file and returns the loaded modules and their
|
||||
byte code, `eval_file` simply evalutes the file contents and returns the
|
||||
evaluation result and its bindings.
|
||||
"""
|
||||
def eval_file(file, relative_to \\ nil) do
|
||||
file = find_file(file, relative_to)
|
||||
eval_string File.read!(file), [], []
|
||||
end
|
||||
|
||||
@doc """
|
||||
Load the given file.
|
||||
|
||||
Accepts `relative_to` as an argument to tell where the file is located.
|
||||
If the file was already required/loaded, loads it again.
|
||||
|
||||
It returns a list of tuples `{ ModuleName, <<byte_code>> }`, one tuple for
|
||||
each module defined in the file.
|
||||
|
||||
Notice that if `load_file` is invoked by different processes concurrently,
|
||||
the target file will be loaded concurrently many times. Check `require_file/2`
|
||||
if you don't want a file to be loaded concurrently.
|
||||
"""
|
||||
def load_file(file, relative_to \\ nil) when is_binary(file) do
|
||||
file = find_file(file, relative_to)
|
||||
@@ -275,11 +277,11 @@ defmodule Code do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Require the given `file`.
|
||||
|
||||
Accepts `relative_to` as an argument to tell where
|
||||
the file is located. The return value is the same as that of `load_file/2`. If
|
||||
the file was already required/loaded, doesn't do anything and returns `nil`.
|
||||
Requires the given `file`.
|
||||
|
||||
Accepts `relative_to` as an argument to tell where the file is located.
|
||||
The return value is the same as that of `load_file/2`. If the file was already
|
||||
required/loaded, doesn't do anything and returns `nil`.
|
||||
|
||||
Notice that if `require_file` is invoked by different processes concurrently,
|
||||
the first process to invoke `require_file` acquires a lock and the remaining
|
||||
@@ -287,7 +289,7 @@ defmodule Code do
|
||||
N times with a given file, it will be loaded only once. The first process to
|
||||
call `require_file` will get the list of loaded modules, others will get `nil`.
|
||||
|
||||
Check `load_file/2` if you want a file to be loaded concurrently.
|
||||
Check `load_file/2` if you want a file to be loaded multiple times.
|
||||
"""
|
||||
def require_file(file, relative_to \\ nil) when is_binary(file) do
|
||||
file = find_file(file, relative_to)
|
||||
@@ -305,7 +307,7 @@ defmodule Code do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Load the compilation options from the code server.
|
||||
Gets the compilation options from the code server.
|
||||
|
||||
Check `compiler_options/1` for more information.
|
||||
"""
|
||||
@@ -314,7 +316,7 @@ defmodule Code do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Set compilation options.
|
||||
Sets compilation options.
|
||||
|
||||
These options are global since they are stored by Elixir's Code Server.
|
||||
|
||||
@@ -338,11 +340,10 @@ defmodule Code do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Compile the given string.
|
||||
Compiles the given string.
|
||||
|
||||
Returns a list of tuples where
|
||||
the first element is the module name and the second one is its
|
||||
binary.
|
||||
Returns a list of tuples where the first element is the module name
|
||||
and the second one is its byte code (as a binary).
|
||||
|
||||
For compiling many files at once, check `Kernel.ParallelCompiler.files/2`.
|
||||
"""
|
||||
@@ -351,26 +352,23 @@ defmodule Code do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Compile the quoted expression.
|
||||
|
||||
Returns a list of tuples where
|
||||
the first element is the module name and the second one is its
|
||||
binary.
|
||||
Compiles the quoted expression.
|
||||
|
||||
Returns a list of tuples where the first element is the module name and
|
||||
the second one is its byte code (as a binary).
|
||||
"""
|
||||
def compile_quoted(quoted, file \\ "nofile") when is_binary(file) do
|
||||
:elixir_compiler.quoted quoted, file
|
||||
end
|
||||
|
||||
@doc """
|
||||
Ensure the given module is loaded.
|
||||
|
||||
If the module is already
|
||||
loaded, this works as no-op. If the module was not yet loaded,
|
||||
it tries to load it.
|
||||
Ensures the given module is loaded.
|
||||
|
||||
If it succeeds loading the module, it returns
|
||||
`{ :module, module }`. If not, returns `{ :error, reason }` with
|
||||
the error reason.
|
||||
If the module is already loaded, this works as no-op. If the module
|
||||
was not yet loaded, it tries to load it.
|
||||
|
||||
If it succeeds loading the module, it returns `{ :module, module }`.
|
||||
If not, returns `{ :error, reason }` with the error reason.
|
||||
|
||||
## Code loading on the Erlang VM
|
||||
|
||||
@@ -385,47 +383,46 @@ defmodule Code do
|
||||
module uses this function to check if a specific parser exists for a given
|
||||
URI scheme.
|
||||
|
||||
## Code.ensure_compiled
|
||||
## `Code.ensure_compiled/1`
|
||||
|
||||
Elixir also contains an `ensure_compiled/1` function that is a
|
||||
superset of `ensure_loaded/1`.
|
||||
|
||||
Since Elixir's compilation happens in parallel, in some situations
|
||||
you may need to use a module but that was not yet compiled, therefore
|
||||
you may need to use a module that was not yet compiled, therefore
|
||||
it can't even be loaded.
|
||||
|
||||
`ensure_compiled/1` halts the current process until the
|
||||
module we are depending on is available.
|
||||
|
||||
In most cases, `ensure_loaded` is enough. `ensure_compiled`
|
||||
must be used in some rare cases, usually involving macros
|
||||
that need to invoke a module for callback information.
|
||||
In most cases, `ensure_loaded/1` is enough. `ensure_compiled/1`
|
||||
must be used in rare cases, usually involving macros that need to
|
||||
invoke a module for callback information.
|
||||
"""
|
||||
def ensure_loaded(module) when is_atom(module) do
|
||||
:code.ensure_loaded(module)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Ensure the given module is loaded.
|
||||
Ensures the given module is loaded.
|
||||
|
||||
Similar to `ensure_loaded/1`, but returns `true` if the module
|
||||
is already loaded or was successfully loaded. Returns `false` otherwise.
|
||||
is already loaded or was successfully loaded. Returns `false`
|
||||
otherwise.
|
||||
"""
|
||||
def ensure_loaded?(module) do
|
||||
match?({ :module, ^module }, ensure_loaded(module))
|
||||
end
|
||||
|
||||
@doc """
|
||||
Ensure the given module is compiled and loaded.
|
||||
|
||||
If the module
|
||||
is already loaded, it works as no-op. If the module was not
|
||||
loaded yet, it checks if it needs to be compiled first and
|
||||
then tries to load it.
|
||||
Ensures the given module is compiled and loaded.
|
||||
|
||||
If it succeeds loading the module, it returns
|
||||
`{ :module, module }`. If not, returns `{ :error, reason }` with
|
||||
the error reason.
|
||||
If the module is already loaded, it works as no-op. If the module was
|
||||
not loaded yet, it checks if it needs to be compiled first and then
|
||||
tries to load it.
|
||||
|
||||
If it succeeds loading the module, it returns `{ :module, module }`.
|
||||
If not, returns `{ :error, reason }` with the error reason.
|
||||
|
||||
Check `ensure_loaded/1` for more information on module loading
|
||||
and when to use `ensure_loaded/1` or `ensure_compiled/1`.
|
||||
@@ -448,10 +445,10 @@ defmodule Code do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Ensure the given module is compiled and loaded.
|
||||
Ensures the given module is compiled and loaded.
|
||||
|
||||
Similar to `ensure_compiled/1`, but returns `true` if the module
|
||||
is already loaded or was successfully loaded and compiled.
|
||||
is already loaded or was successfully loaded and compiled.
|
||||
Returns `false` otherwise.
|
||||
"""
|
||||
def ensure_compiled?(module) do
|
||||
@@ -461,6 +458,7 @@ defmodule Code do
|
||||
## Helpers
|
||||
|
||||
# Finds the file given the relative_to path.
|
||||
#
|
||||
# If the file is found, returns its path in binary, fails otherwise.
|
||||
defp find_file(file, relative_to) do
|
||||
file = if relative_to do
|
||||
|
||||
@@ -0,0 +1,117 @@
|
||||
defprotocol Collectable do
|
||||
@moduledoc """
|
||||
A protocol to traverse data structures.
|
||||
|
||||
The `Enum.into/2` function uses this protocol to insert an
|
||||
enumerable into a collection:
|
||||
|
||||
iex> Enum.into([a: 1, b: 2], %{})
|
||||
%{a: 1, b: 2}
|
||||
|
||||
If a collection implements both `Enumerable` and `Collectable`, both
|
||||
operations can be combined with `Enum.traverse/2`:
|
||||
|
||||
iex> Enum.traverse(%{ a: 1, b: 2 }, fn { k, v } -> { k, v * 2 } end)
|
||||
%{a: 2, b: 4}
|
||||
|
||||
## Why Collectable?
|
||||
|
||||
The `Enumerable` protocol is useful to take values out of a collection.
|
||||
In order to support a wide range of values, the functions provided by
|
||||
the `Enumerable` protocol do not keep shape. For example, passing a
|
||||
dictionary to `Enum.map/2` always returns a list.
|
||||
|
||||
This design is intentional. `Enumerable` was designed to support infinite
|
||||
collections, resources and other structures with fixed shape. For example,
|
||||
it doesn't make sense to insert values into a range, as it has a fixed
|
||||
shape where just the range limits are stored.
|
||||
|
||||
The `Collectable` module was designed to fill the gap left by the
|
||||
`Enumerable` protocol. It provides two functions: `into/1` and `empty/1`.
|
||||
|
||||
`into/1` can be seen as the opposite of `Enumerable.reduce/3`. If
|
||||
`Enumerable` is about taking values out, `Collectable.into/1` is about
|
||||
collecting those values into a structure.
|
||||
|
||||
`empty/1` receives a collectable and returns an empty version of the
|
||||
same collectable. By combining the enumerable functionality with `into/1`
|
||||
and `empty/1`, one can, for example, implement a traversal mechanism.
|
||||
"""
|
||||
|
||||
@type command :: { :cont, term } | :done | :halt
|
||||
|
||||
@doc """
|
||||
Receives a collectable structure and returns an empty one.
|
||||
"""
|
||||
@spec empty(t) :: t
|
||||
def empty(collectable)
|
||||
|
||||
@doc """
|
||||
Returns a function that collects values alongside
|
||||
the initial accumulation value.
|
||||
|
||||
The returned function receives a collectable and injects a given
|
||||
value into it for every `{ :cont, term }` instruction.
|
||||
|
||||
`:done` is passed when no further values will be injected, useful
|
||||
for closing resources and normalizing values. A collectable must
|
||||
be returned on `:done`.
|
||||
|
||||
If injection is suddenly interrupted, `:halt` is passed and it can
|
||||
return any value, as it won't be used.
|
||||
"""
|
||||
@spec into(t) :: { term, (term, command -> t | term) }
|
||||
def into(collectable)
|
||||
end
|
||||
|
||||
defimpl Collectable, for: List do
|
||||
def empty(_list) do
|
||||
[]
|
||||
end
|
||||
|
||||
def into(original) do
|
||||
{ [], fn
|
||||
list, { :cont, x } -> [x|list]
|
||||
list, :done -> original ++ :lists.reverse(list)
|
||||
_, :halt -> :ok
|
||||
end }
|
||||
end
|
||||
end
|
||||
|
||||
defimpl Collectable, for: BitString do
|
||||
def empty(_bitstring) do
|
||||
""
|
||||
end
|
||||
|
||||
def into(original) do
|
||||
{ original, fn
|
||||
bitstring, { :cont, x } -> <<bitstring :: bits, x :: bits>>
|
||||
bitstring, :done -> bitstring
|
||||
_, :halt -> :ok
|
||||
end }
|
||||
end
|
||||
end
|
||||
|
||||
defimpl Collectable, for: Function do
|
||||
def empty(function) do
|
||||
function
|
||||
end
|
||||
|
||||
def into(function) do
|
||||
{ function, function }
|
||||
end
|
||||
end
|
||||
|
||||
defimpl Collectable, for: Map do
|
||||
def empty(_map) do
|
||||
%{}
|
||||
end
|
||||
|
||||
def into(original) do
|
||||
{ original, fn
|
||||
map, { :cont, { k, v } } -> :maps.put(k, v, map)
|
||||
map, :done -> map
|
||||
_, :halt -> :ok
|
||||
end }
|
||||
end
|
||||
end
|
||||
+88
-105
@@ -12,7 +12,7 @@ defmodule Dict do
|
||||
HashDict.new #=> creates an empty HashDict
|
||||
|
||||
In the examples below, `dict_impl` means a specific
|
||||
`Dict` implementation, for example `HashDict` or `ListDict`.
|
||||
`Dict` implementation, for example `HashDict` or `Map`.
|
||||
|
||||
## Protocols
|
||||
|
||||
@@ -21,15 +21,11 @@ defmodule Dict do
|
||||
protocol:
|
||||
|
||||
iex> dict = dict_impl.new
|
||||
...> dict = Dict.put(dict, :hello, :world)
|
||||
...> dict[:hello]
|
||||
iex> dict = Dict.put(dict, :hello, :world)
|
||||
iex> dict[:hello]
|
||||
:world
|
||||
|
||||
And the `Enumerable` protocol, allowing one to write:
|
||||
|
||||
Enum.each(dict, fn ({ k, v }) ->
|
||||
IO.puts "#{k}: #{v}"
|
||||
end)
|
||||
As well as the `Enumerable` and `Collectable` protocols.
|
||||
|
||||
## Match
|
||||
|
||||
@@ -41,11 +37,9 @@ defmodule Dict do
|
||||
|
||||
@type key :: any
|
||||
@type value :: any
|
||||
@type t :: tuple | list
|
||||
@type t :: tuple | list | map
|
||||
|
||||
defcallback new :: t
|
||||
defcallback new(Enum.t) :: t
|
||||
defcallback new(Enum.t, (any -> { key, value })) :: t
|
||||
defcallback delete(t, key) :: t
|
||||
defcallback drop(t, Enum.t) :: t
|
||||
defcallback empty(t) :: t
|
||||
@@ -62,7 +56,6 @@ defmodule Dict do
|
||||
defcallback pop(t, key, value) :: {value, t}
|
||||
defcallback put(t, key, value) :: t
|
||||
defcallback put_new(t, key, value) :: t
|
||||
defcallback reduce(t, Enumerable.acc, Enumerable.reducer) :: Enumerable.result
|
||||
defcallback size(t) :: non_neg_integer()
|
||||
defcallback split(t, Enum.t) :: {t, t}
|
||||
defcallback take(t, Enum.t) :: t
|
||||
@@ -73,13 +66,17 @@ defmodule Dict do
|
||||
|
||||
defmacrop target(dict) do
|
||||
quote do
|
||||
cond do
|
||||
is_tuple(unquote(dict)) ->
|
||||
elem(unquote(dict), 0)
|
||||
is_list(unquote(dict)) ->
|
||||
case unquote(dict) do
|
||||
%{__struct__: x} when is_atom(x) ->
|
||||
x
|
||||
%{} ->
|
||||
Map
|
||||
x when is_tuple(x) ->
|
||||
elem(x, 0)
|
||||
x when is_list(x) ->
|
||||
ListDict
|
||||
true ->
|
||||
unsupported_dict(unquote(dict))
|
||||
x ->
|
||||
unsupported_dict(x)
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -90,8 +87,8 @@ defmodule Dict do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> d = dict_impl.new([a: 1, b: 2])
|
||||
...> Enum.sort(Dict.keys(d))
|
||||
iex> d = Enum.into([a: 1, b: 2], dict_impl.new)
|
||||
iex> Enum.sort(Dict.keys(d))
|
||||
[:a,:b]
|
||||
|
||||
"""
|
||||
@@ -106,8 +103,8 @@ defmodule Dict do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> d = dict_impl.new([a: 1, b: 2])
|
||||
...> Enum.sort(Dict.values(d))
|
||||
iex> d = Enum.into([a: 1, b: 2], dict_impl.new)
|
||||
iex> Enum.sort(Dict.values(d))
|
||||
[1,2]
|
||||
|
||||
"""
|
||||
@@ -121,8 +118,8 @@ defmodule Dict do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> d = dict_impl.new([a: 1, b: 2])
|
||||
...> Dict.size(d)
|
||||
iex> d = Enum.into([a: 1, b: 2], dict_impl.new)
|
||||
iex> Dict.size(d)
|
||||
2
|
||||
|
||||
"""
|
||||
@@ -136,7 +133,7 @@ defmodule Dict do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> d = dict_impl.new([a: 1])
|
||||
iex> d = Enum.into([a: 1], dict_impl.new)
|
||||
iex> Dict.has_key?(d, :a)
|
||||
true
|
||||
iex> Dict.has_key?(d, :b)
|
||||
@@ -154,7 +151,7 @@ defmodule Dict do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> d = dict_impl.new([a: 1])
|
||||
iex> d = Enum.into([a: 1], dict_impl.new)
|
||||
iex> Dict.get(d, :a)
|
||||
1
|
||||
iex> Dict.get(d, :b)
|
||||
@@ -173,7 +170,7 @@ defmodule Dict do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> d = dict_impl.new([a: 1])
|
||||
iex> d = Enum.into([a: 1], dict_impl.new)
|
||||
iex> Dict.fetch(d, :a)
|
||||
{ :ok, 1 }
|
||||
iex> Dict.fetch(d, :b)
|
||||
@@ -191,11 +188,9 @@ defmodule Dict do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> d = dict_impl.new([a: 1])
|
||||
iex> d = Enum.into([a: 1], dict_impl.new)
|
||||
iex> Dict.fetch!(d, :a)
|
||||
1
|
||||
iex> Dict.fetch!(d, :b)
|
||||
** (KeyError) key not found: :b
|
||||
|
||||
"""
|
||||
@spec fetch!(t, key) :: value | no_return
|
||||
@@ -209,9 +204,9 @@ defmodule Dict do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> d = dict_impl.new([a: 1, b: 2])
|
||||
...> d = Dict.put(d, :a, 3)
|
||||
...> Dict.get(d, :a)
|
||||
iex> d = Enum.into([a: 1, b: 2], dict_impl.new)
|
||||
iex> d = Dict.put(d, :a, 3)
|
||||
iex> Dict.get(d, :a)
|
||||
3
|
||||
|
||||
"""
|
||||
@@ -225,9 +220,9 @@ defmodule Dict do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> d = dict_impl.new([a: 1, b: 2])
|
||||
...> d = Dict.put_new(d, :a, 3)
|
||||
...> Dict.get(d, :a)
|
||||
iex> d = Enum.into([a: 1, b: 2], dict_impl.new)
|
||||
iex> d = Dict.put_new(d, :a, 3)
|
||||
iex> Dict.get(d, :a)
|
||||
1
|
||||
|
||||
"""
|
||||
@@ -242,13 +237,13 @@ defmodule Dict do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> d = dict_impl.new([a: 1, b: 2])
|
||||
...> d = Dict.delete(d, :a)
|
||||
...> Dict.get(d, :a)
|
||||
iex> d = Enum.into([a: 1, b: 2], dict_impl.new)
|
||||
iex> d = Dict.delete(d, :a)
|
||||
iex> Dict.get(d, :a)
|
||||
nil
|
||||
|
||||
iex> d = dict_impl.new([b: 2])
|
||||
...> Dict.delete(d, :a) == d
|
||||
iex> d = Enum.into([b: 2], dict_impl.new)
|
||||
iex> Dict.delete(d, :a) == d
|
||||
true
|
||||
|
||||
"""
|
||||
@@ -270,18 +265,18 @@ defmodule Dict do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> d1 = dict_impl.new([a: 1, b: 2])
|
||||
...> d2 = dict_impl.new([a: 3, d: 4])
|
||||
...> d = Dict.merge(d1, d2)
|
||||
...> [a: Dict.get(d, :a), b: Dict.get(d, :b), d: Dict.get(d, :d)]
|
||||
iex> d1 = Enum.into([a: 1, b: 2], dict_impl.new)
|
||||
iex> d2 = Enum.into([a: 3, d: 4], dict_impl.new)
|
||||
iex> d = Dict.merge(d1, d2)
|
||||
iex> [a: Dict.get(d, :a), b: Dict.get(d, :b), d: Dict.get(d, :d)]
|
||||
[a: 3, b: 2, d: 4]
|
||||
|
||||
iex> d1 = dict_impl.new([a: 1, b: 2])
|
||||
...> d2 = dict_impl.new([a: 3, d: 4])
|
||||
...> d = Dict.merge(d1, d2, fn(_k, v1, v2) ->
|
||||
iex> d1 = Enum.into([a: 1, b: 2], dict_impl.new)
|
||||
iex> d2 = Enum.into([a: 3, d: 4], dict_impl.new)
|
||||
iex> d = Dict.merge(d1, d2, fn(_k, v1, v2) ->
|
||||
...> v1 + v2
|
||||
...> end)
|
||||
...> [a: Dict.get(d, :a), b: Dict.get(d, :b), d: Dict.get(d, :d)]
|
||||
iex> [a: Dict.get(d, :a), b: Dict.get(d, :b), d: Dict.get(d, :d)]
|
||||
[a: 4, b: 2, d: 4]
|
||||
|
||||
"""
|
||||
@@ -293,7 +288,7 @@ defmodule Dict do
|
||||
if target1 == target2 do
|
||||
target1.merge(dict1, dict2, fun)
|
||||
else
|
||||
target2.reduce(dict2, { :cont, dict1 }, fn({ k, v }, acc) ->
|
||||
Enumerable.reduce(dict2, { :cont, dict1 }, fn({ k, v }, acc) ->
|
||||
{ :cont, target1.update(acc, k, v, fn(other) -> fun.(k, other, v) end) }
|
||||
end) |> elem(1)
|
||||
end
|
||||
@@ -305,19 +300,19 @@ defmodule Dict do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> dict = dict_impl.new [a: 1]
|
||||
...> {v, d} = Dict.pop dict, :a
|
||||
...> {v, Enum.sort(d)}
|
||||
iex> dict = Enum.into([a: 1], dict_impl.new)
|
||||
iex> {v, d} = Dict.pop dict, :a
|
||||
iex> {v, Enum.sort(d)}
|
||||
{1,[]}
|
||||
|
||||
iex> dict = dict_impl.new [a: 1]
|
||||
...> {v, d} = Dict.pop dict, :b
|
||||
...> {v, Enum.sort(d)}
|
||||
iex> dict = Enum.into([a: 1], dict_impl.new)
|
||||
iex> {v, d} = Dict.pop dict, :b
|
||||
iex> {v, Enum.sort(d)}
|
||||
{nil,[a: 1]}
|
||||
|
||||
iex> dict = dict_impl.new [a: 1]
|
||||
...> {v, d} = Dict.pop dict, :b, 3
|
||||
...> {v, Enum.sort(d)}
|
||||
iex> dict = Enum.into([a: 1], dict_impl.new)
|
||||
iex> {v, d} = Dict.pop dict, :b, 3
|
||||
iex> {v, Enum.sort(d)}
|
||||
{3,[a: 1]}
|
||||
|
||||
"""
|
||||
@@ -332,9 +327,9 @@ defmodule Dict do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> d = dict_impl.new([a: 1, b: 2])
|
||||
...> d = Dict.update!(d, :a, fn(val) -> -val end)
|
||||
...> Dict.get(d, :a)
|
||||
iex> d = Enum.into([a: 1, b: 2], dict_impl.new)
|
||||
iex> d = Dict.update!(d, :a, fn(val) -> -val end)
|
||||
iex> Dict.get(d, :a)
|
||||
-1
|
||||
|
||||
"""
|
||||
@@ -350,9 +345,9 @@ defmodule Dict do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> d = dict_impl.new([a: 1, b: 2])
|
||||
...> d = Dict.update(d, :c, 3, fn(val) -> -val end)
|
||||
...> Dict.get(d, :c)
|
||||
iex> d = Enum.into([a: 1, b: 2], dict_impl.new)
|
||||
iex> d = Dict.update(d, :c, 3, fn(val) -> -val end)
|
||||
iex> Dict.get(d, :c)
|
||||
3
|
||||
|
||||
"""
|
||||
@@ -370,19 +365,19 @@ defmodule Dict do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> d = dict_impl.new([a: 1, b: 2, c: 3, d: 4])
|
||||
...> { d1, d2 } = Dict.split(d, [:a, :c, :e])
|
||||
...> { Dict.to_list(d1) |> Enum.sort, Dict.to_list(d2) |> Enum.sort }
|
||||
iex> d = Enum.into([a: 1, b: 2, c: 3, d: 4], dict_impl.new)
|
||||
iex> { d1, d2 } = Dict.split(d, [:a, :c, :e])
|
||||
iex> { Dict.to_list(d1) |> Enum.sort, Dict.to_list(d2) |> Enum.sort }
|
||||
{ [a: 1, c: 3], [b: 2, d: 4] }
|
||||
|
||||
iex> d = dict_impl.new([])
|
||||
...> { d1, d2 } = Dict.split(d, [:a, :c])
|
||||
...> { Dict.to_list(d1), Dict.to_list(d2) }
|
||||
iex> d = Enum.into([], dict_impl.new)
|
||||
iex> { d1, d2 } = Dict.split(d, [:a, :c])
|
||||
iex> { Dict.to_list(d1), Dict.to_list(d2) }
|
||||
{ [], [] }
|
||||
|
||||
iex> d = dict_impl.new([a: 1, b: 2])
|
||||
...> { d1, d2 } = Dict.split(d, [:a, :b, :c])
|
||||
...> { Dict.to_list(d1) |> Enum.sort, Dict.to_list(d2) }
|
||||
iex> d = Enum.into([a: 1, b: 2], dict_impl.new)
|
||||
iex> { d1, d2 } = Dict.split(d, [:a, :b, :c])
|
||||
iex> { Dict.to_list(d1) |> Enum.sort, Dict.to_list(d2) }
|
||||
{ [a: 1, b: 2], [] }
|
||||
|
||||
"""
|
||||
@@ -397,14 +392,14 @@ defmodule Dict do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> d = dict_impl.new([a: 1, b: 2])
|
||||
...> d = Dict.drop(d, [:a, :c, :d])
|
||||
...> Dict.to_list(d)
|
||||
iex> d = Enum.into([a: 1, b: 2], dict_impl.new)
|
||||
iex> d = Dict.drop(d, [:a, :c, :d])
|
||||
iex> Dict.to_list(d)
|
||||
[b: 2]
|
||||
|
||||
iex> d = dict_impl.new([a: 1, b: 2])
|
||||
...> d = Dict.drop(d, [:c, :d])
|
||||
...> Dict.to_list(d) |> Enum.sort
|
||||
iex> d = Enum.into([a: 1, b: 2], dict_impl.new)
|
||||
iex> d = Dict.drop(d, [:c, :d])
|
||||
iex> Dict.to_list(d) |> Enum.sort
|
||||
[a: 1, b: 2]
|
||||
|
||||
"""
|
||||
@@ -420,14 +415,12 @@ defmodule Dict do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> d = dict_impl.new([a: 1, b: 2])
|
||||
...>
|
||||
...> d = Dict.take(d, [:a, :c, :d])
|
||||
...> Dict.to_list(d)
|
||||
iex> d = Enum.into([a: 1, b: 2], dict_impl.new)
|
||||
iex> d = Dict.take(d, [:a, :c, :d])
|
||||
iex> Dict.to_list(d)
|
||||
[a: 1]
|
||||
...>
|
||||
...> d = Dict.take(d, [:c, :d])
|
||||
...> Dict.to_list(d)
|
||||
iex> d = Dict.take(d, [:c, :d])
|
||||
iex> Dict.to_list(d)
|
||||
[]
|
||||
|
||||
"""
|
||||
@@ -436,17 +429,7 @@ defmodule Dict do
|
||||
target(dict).take(dict, keys)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns an empty dict of the same type as `dict`.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> d = dict_impl.new([a: 1, b: 2])
|
||||
...> e = Dict.empty(d)
|
||||
...> Dict.to_list(e)
|
||||
[]
|
||||
|
||||
"""
|
||||
@doc false
|
||||
@spec empty(t) :: t
|
||||
def empty(dict) do
|
||||
target(dict).empty(dict)
|
||||
@@ -461,14 +444,14 @@ defmodule Dict do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> a = dict_impl.new(a: 2, b: 3, f: 5, c: 123)
|
||||
...> b = [a: 2, b: 3, f: 5, c: 123]
|
||||
...> Dict.equal?(a, b)
|
||||
iex> a = Enum.into([a: 2, b: 3, f: 5, c: 123], dict_impl.new)
|
||||
iex> b = [a: 2, b: 3, f: 5, c: 123]
|
||||
iex> Dict.equal?(a, b)
|
||||
true
|
||||
|
||||
iex> a = dict_impl.new(a: 2, b: 3, f: 5, c: 123)
|
||||
...> b = []
|
||||
...> Dict.equal?(a, b)
|
||||
iex> a = Enum.into([a: 2, b: 3, f: 5, c: 123], dict_impl.new)
|
||||
iex> b = []
|
||||
iex> Dict.equal?(a, b)
|
||||
false
|
||||
|
||||
"""
|
||||
@@ -482,7 +465,7 @@ defmodule Dict do
|
||||
target1.equal?(dict1, dict2)
|
||||
|
||||
target1.size(dict1) == target2.size(dict2) ->
|
||||
target2.reduce(dict2, { :cont, true }, fn({ k, v }, _acc) ->
|
||||
Enumerable.reduce(dict2, { :cont, true }, fn({ k, v }, _acc) ->
|
||||
case target1.fetch(dict1, k) do
|
||||
{ :ok, ^v } -> { :cont, true }
|
||||
_ -> { :halt, false }
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
defmodule Dict.Behaviour do
|
||||
@moduledoc """
|
||||
@moduledoc ~S"""
|
||||
Default implementations for some required functions in the `Dict` module.
|
||||
|
||||
This module makes it easier to create your own `Dict` compliant
|
||||
@@ -24,8 +24,8 @@ defmodule Dict.Behaviour do
|
||||
* `size/1`
|
||||
* `update/4`
|
||||
|
||||
All of which are part of the `Dict` behaviour, so no extra functions are
|
||||
actually required.
|
||||
All functions, except `reduce/3`, are required by the Dict behaviour.
|
||||
`reduce/3` must be implemtented as per the Enumerable protocol.
|
||||
|
||||
Based on these functions, `Dict.Behaviour` generates default implementations
|
||||
for the following functions:
|
||||
@@ -79,7 +79,7 @@ defmodule Dict.Behaviour do
|
||||
def fetch!(dict, key) do
|
||||
case fetch(dict, key) do
|
||||
{ :ok, value } -> value
|
||||
:error -> raise KeyError, key: key
|
||||
:error -> raise KeyError, key: key, term: dict
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
+204
-25
@@ -144,8 +144,8 @@ defmodule Enum do
|
||||
Some particular types, like dictionaries, yield a specific format on
|
||||
enumeration. For dicts, the argument is always a `{ key, value }` tuple:
|
||||
|
||||
iex> dict = HashDict.new [a: 1, b: 2]
|
||||
iex> Enum.map(dict, fn { k, v } -> { k, v * 2 } end) |> Enum.sort
|
||||
iex> dict = %{a: 1, b: 2}
|
||||
iex> Enum.map(dict, fn { k, v } -> { k, v * 2 } end)
|
||||
[a: 2, b: 4]
|
||||
|
||||
Note that the functions in the `Enum` module are eager: they always start
|
||||
@@ -202,6 +202,7 @@ defmodule Enum do
|
||||
|
||||
iex> Enum.all?([1, 2, 3])
|
||||
true
|
||||
|
||||
iex> Enum.all?([1, nil, 3])
|
||||
false
|
||||
|
||||
@@ -238,6 +239,7 @@ defmodule Enum do
|
||||
|
||||
iex> Enum.any?([false, false, false])
|
||||
false
|
||||
|
||||
iex> Enum.any?([false, true, false])
|
||||
true
|
||||
|
||||
@@ -265,10 +267,13 @@ defmodule Enum do
|
||||
|
||||
iex> Enum.at([2, 4, 6], 0)
|
||||
2
|
||||
|
||||
iex> Enum.at([2, 4, 6], 2)
|
||||
6
|
||||
|
||||
iex> Enum.at([2, 4, 6], 4)
|
||||
nil
|
||||
|
||||
iex> Enum.at([2, 4, 6], 4, :none)
|
||||
:none
|
||||
|
||||
@@ -305,10 +310,13 @@ defmodule Enum do
|
||||
|
||||
iex> Enum.chunk([1, 2, 3, 4, 5, 6], 2)
|
||||
[[1, 2], [3, 4], [5, 6]]
|
||||
|
||||
iex> Enum.chunk([1, 2, 3, 4, 5, 6], 3, 2)
|
||||
[[1, 2, 3], [3, 4, 5]]
|
||||
|
||||
iex> Enum.chunk([1, 2, 3, 4, 5, 6], 3, 2, [7])
|
||||
[[1, 2, 3], [3, 4, 5], [5, 6, 7]]
|
||||
|
||||
iex> Enum.chunk([1, 2, 3, 4, 5, 6], 3, 3, [])
|
||||
[[1, 2, 3], [4, 5, 6]]
|
||||
|
||||
@@ -412,11 +420,6 @@ defmodule Enum do
|
||||
|
||||
def count(collection) do
|
||||
case Enumerable.count(collection) do
|
||||
value when is_integer(value) ->
|
||||
IO.write "Expected #{inspect Enumerable.impl_for(collection)}.count/1 to return " <>
|
||||
"{ :ok, boolean } if pre-calculated, otherwise { :error, module }, got " <>
|
||||
"an integer\n#{Exception.format_stacktrace}"
|
||||
value
|
||||
{ :ok, value } when is_integer(value) ->
|
||||
value
|
||||
{ :error, module } ->
|
||||
@@ -431,6 +434,7 @@ defmodule Enum do
|
||||
`fun` returns `true`.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Enum.count([1, 2, 3, 4, 5], fn(x) -> rem(x, 2) == 0 end)
|
||||
2
|
||||
|
||||
@@ -454,10 +458,13 @@ defmodule Enum do
|
||||
|
||||
iex> Enum.drop([1, 2, 3], 2)
|
||||
[3]
|
||||
|
||||
iex> Enum.drop([1, 2, 3], 10)
|
||||
[]
|
||||
|
||||
iex> Enum.drop([1, 2, 3], 0)
|
||||
[1,2,3]
|
||||
|
||||
iex> Enum.drop([1, 2, 3], -1)
|
||||
[1,2]
|
||||
|
||||
@@ -488,6 +495,7 @@ defmodule Enum do
|
||||
|
||||
iex> Enum.drop_while([1, 2, 3, 4, 5], fn(x) -> x < 3 end)
|
||||
[3,4,5]
|
||||
|
||||
"""
|
||||
@spec drop_while(t, (element -> as_boolean(term))) :: list
|
||||
def drop_while(collection, fun) when is_list(collection) do
|
||||
@@ -533,6 +541,7 @@ defmodule Enum do
|
||||
|
||||
iex> Enum.empty?([])
|
||||
true
|
||||
|
||||
iex> Enum.empty?([1, 2, 3])
|
||||
false
|
||||
|
||||
@@ -558,8 +567,10 @@ defmodule Enum do
|
||||
|
||||
iex> Enum.fetch([2, 4, 6], 0)
|
||||
{ :ok, 2 }
|
||||
|
||||
iex> Enum.fetch([2, 4, 6], 2)
|
||||
{ :ok, 6 }
|
||||
|
||||
iex> Enum.fetch([2, 4, 6], 4)
|
||||
:error
|
||||
|
||||
@@ -626,7 +637,7 @@ defmodule Enum do
|
||||
"""
|
||||
@spec filter(t, (element -> as_boolean(term))) :: list
|
||||
def filter(collection, fun) when is_list(collection) do
|
||||
lc item inlist collection, fun.(item), do: item
|
||||
for item <- collection, fun.(item), do: item
|
||||
end
|
||||
|
||||
def filter(collection, fun) do
|
||||
@@ -645,7 +656,7 @@ defmodule Enum do
|
||||
"""
|
||||
@spec filter_map(t, (element -> as_boolean(term)), (element -> element)) :: list
|
||||
def filter_map(collection, filter, mapper) when is_list(collection) do
|
||||
lc item inlist collection, filter.(item), do: mapper.(item)
|
||||
for item <- collection, filter.(item), do: mapper.(item)
|
||||
end
|
||||
|
||||
def filter_map(collection, filter, mapper) do
|
||||
@@ -807,8 +818,10 @@ defmodule Enum do
|
||||
|
||||
iex> Enum.intersperse([1, 2, 3], 0)
|
||||
[1, 0, 2, 0, 3]
|
||||
|
||||
iex> Enum.intersperse([1], 0)
|
||||
[1]
|
||||
|
||||
iex> Enum.intersperse([], 0)
|
||||
[]
|
||||
|
||||
@@ -826,6 +839,70 @@ defmodule Enum do
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Inserts the given enumerable into a collectable.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Enum.into([1, 2], [0])
|
||||
[0, 1, 2]
|
||||
|
||||
iex> Enum.into([a: 1, b: 2], %{})
|
||||
%{a: 1, b: 2}
|
||||
|
||||
"""
|
||||
@spec into(Enumerable.t, Collectable.t) :: Collectable.t
|
||||
def into(collection, list) when is_list(list) do
|
||||
list ++ to_list(collection)
|
||||
end
|
||||
|
||||
def into(collection, %{} = map) when is_list(collection) and (map_size(map) == 0) do
|
||||
:maps.from_list(collection)
|
||||
end
|
||||
|
||||
def into(collection, collectable) do
|
||||
{ initial, fun } = Collectable.into(collectable)
|
||||
into(collection, initial, fun, fn x, acc ->
|
||||
fun.(acc, { :cont, x })
|
||||
end)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Inserts the given enumerable into a collectable
|
||||
according to the transformation function.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Enum.into([2, 3], [3], fn x -> x * 3 end)
|
||||
[3, 6, 9]
|
||||
|
||||
"""
|
||||
@spec into(Enumerable.t, Collectable.t, (term -> term)) :: Collectable.t
|
||||
|
||||
def into(collection, list, transform) when is_list(list) and is_function(transform, 1) do
|
||||
list ++ map(collection, transform)
|
||||
end
|
||||
|
||||
def into(collection, collectable, transform) when is_function(transform, 1) do
|
||||
{ initial, fun } = Collectable.into(collectable)
|
||||
into(collection, initial, fun, fn x, acc ->
|
||||
fun.(acc, { :cont, transform.(x) })
|
||||
end)
|
||||
end
|
||||
|
||||
defp into(collection, initial, fun, callback) do
|
||||
try do
|
||||
reduce(collection, initial, callback)
|
||||
catch
|
||||
kind, reason ->
|
||||
stacktrace = System.stacktrace
|
||||
fun.(initial, :halt)
|
||||
:erlang.raise(kind, reason, stacktrace)
|
||||
else
|
||||
acc -> fun.(acc, :done)
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Joins the given `collection` according to `joiner`.
|
||||
`joiner` can be either a binary or a list and the
|
||||
@@ -840,6 +917,7 @@ defmodule Enum do
|
||||
|
||||
iex> Enum.join([1, 2, 3])
|
||||
"123"
|
||||
|
||||
iex> Enum.join([1, 2, 3], " = ")
|
||||
"1 = 2 = 3"
|
||||
|
||||
@@ -872,7 +950,7 @@ defmodule Enum do
|
||||
"""
|
||||
@spec map(t, (element -> any)) :: list
|
||||
def map(collection, fun) when is_list(collection) do
|
||||
lc item inlist collection, do: fun.(item)
|
||||
for item <- collection, do: fun.(item)
|
||||
end
|
||||
|
||||
def map(collection, fun) do
|
||||
@@ -893,6 +971,7 @@ defmodule Enum do
|
||||
|
||||
iex> Enum.map_join([1, 2, 3], &(&1 * 2))
|
||||
"246"
|
||||
|
||||
iex> Enum.map_join([1, 2, 3], " = ", &(&1 * 2))
|
||||
"2 = 4 = 6"
|
||||
|
||||
@@ -1000,6 +1079,7 @@ defmodule Enum do
|
||||
|
||||
iex> Enum.member?(1..10, 5)
|
||||
true
|
||||
|
||||
iex> Enum.member?([:a, :b, :c], :d)
|
||||
false
|
||||
|
||||
@@ -1011,17 +1091,12 @@ defmodule Enum do
|
||||
|
||||
def member?(collection, value) do
|
||||
case Enumerable.member?(collection, value) do
|
||||
value when is_boolean(value) ->
|
||||
IO.write "Expected #{inspect Enumerable.impl_for(collection)}.member?/2 to return " <>
|
||||
"{ :ok, boolean } if faster than linear, otherwise { :error, __MODULE__ }, " <>
|
||||
"got a boolean\n#{Exception.format_stacktrace}"
|
||||
value
|
||||
{ :ok, value } when is_boolean(value) ->
|
||||
value
|
||||
{ :error, module } ->
|
||||
module.reduce(collection, { :cont, false }, fn
|
||||
v, _ when v === value -> { :halt, true }
|
||||
_, _ -> { :cont, false }
|
||||
_, _ -> { :cont, false }
|
||||
end) |> elem(1)
|
||||
end
|
||||
end
|
||||
@@ -1079,6 +1154,22 @@ defmodule Enum do
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns the sum of all values.
|
||||
|
||||
Raises `ArithmeticError` if collection contains a non-numeric value.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Enum.sum([1, 2, 3])
|
||||
6
|
||||
|
||||
"""
|
||||
@spec sum(t) :: number
|
||||
def sum(collection) do
|
||||
reduce(collection, 0, &+/2)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Partitions `collection` into two collections, where the first one contains elements
|
||||
for which `fun` returns a truthy value, and the second one -- for which `fun`
|
||||
@@ -1104,6 +1195,27 @@ defmodule Enum do
|
||||
{ :lists.reverse(acc1), :lists.reverse(acc2) }
|
||||
end
|
||||
|
||||
@doc """
|
||||
Splits `collection` into groups based on `fun`.
|
||||
|
||||
The result is a dict (by default a map) where each key is
|
||||
a group and each value is a list of elements from `collection`
|
||||
for which `fun` returned that group. Ordering is not necessarily
|
||||
preserved.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Enum.group_by(~w{ant buffalo cat dingo}, &String.length/1)
|
||||
%{ 3 => ["cat", "ant"], 7 => ["buffalo"], 5 => ["dingo"] }
|
||||
|
||||
"""
|
||||
@spec group_by(t, (element -> any)) :: HashDict
|
||||
def group_by(collection, dict \\ %{}, fun) do
|
||||
reduce(collection, dict, fn(entry, categories) ->
|
||||
Dict.update(categories, fun.(entry), [entry], &[entry|&1])
|
||||
end)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Invokes `fun` for each element in the collection passing that element and the
|
||||
accumulator `acc` as arguments. `fun`'s return value is stored in `acc`.
|
||||
@@ -1172,7 +1284,7 @@ defmodule Enum do
|
||||
"""
|
||||
@spec reject(t, (element -> as_boolean(term))) :: list
|
||||
def reject(collection, fun) when is_list(collection) do
|
||||
lc item inlist collection, !fun.(item), do: item
|
||||
for item <- collection, !fun.(item), do: item
|
||||
end
|
||||
|
||||
def reject(collection, fun) do
|
||||
@@ -1268,9 +1380,9 @@ defmodule Enum do
|
||||
|
||||
## Examples
|
||||
|
||||
iex(1)> Enum.shuffle([1, 2, 3])
|
||||
iex> Enum.shuffle([1, 2, 3])
|
||||
[3, 2, 1]
|
||||
iex(2)> Enum.shuffle([1, 2, 3])
|
||||
iex> Enum.shuffle([1, 2, 3])
|
||||
[3, 1, 2]
|
||||
|
||||
"""
|
||||
@@ -1361,12 +1473,14 @@ defmodule Enum do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns a sorted list of collection elements. Uses the merge sort algorithm.
|
||||
Sorts the collection according to Elixir's term ordering.
|
||||
|
||||
Uses the merge sort algorithm.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Enum.sort([3, 2, 1])
|
||||
[1,2,3]
|
||||
[1, 2, 3]
|
||||
|
||||
"""
|
||||
@spec sort(t) :: list
|
||||
@@ -1379,14 +1493,27 @@ defmodule Enum do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns a list of collection elements sorted by the given function.
|
||||
Sorts the collection by the given function.
|
||||
|
||||
Uses the merge sort algorithm.
|
||||
This function uses the merge sort algorithm. The given function
|
||||
must return false if the first argument is less than right one.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Enum.sort([1, 2, 3], &(&1 > &2))
|
||||
[3,2,1]
|
||||
[3, 2, 1]
|
||||
|
||||
The sorting algorithm will be stable as long as the given function
|
||||
returns true for values considered equal:
|
||||
|
||||
iex> Enum.sort ["some", "kind", "of", "monster"], &(byte_size(&1) <= byte_size(&2))
|
||||
["of", "some", "kind", "monster"]
|
||||
|
||||
If the function does not return true, the sorting is not stable and
|
||||
the order of equal terms may be shuffled:
|
||||
|
||||
iex> Enum.sort ["some", "kind", "of", "monster"], &(byte_size(&1) < byte_size(&2))
|
||||
["of", "kind", "some", "monster"]
|
||||
|
||||
"""
|
||||
@spec sort(t, (element, element -> boolean)) :: list
|
||||
@@ -1412,12 +1539,16 @@ defmodule Enum do
|
||||
|
||||
iex> Enum.split([1, 2, 3], 2)
|
||||
{ [1,2], [3] }
|
||||
|
||||
iex> Enum.split([1, 2, 3], 10)
|
||||
{ [1,2,3], [] }
|
||||
|
||||
iex> Enum.split([1, 2, 3], 0)
|
||||
{ [], [1,2,3] }
|
||||
|
||||
iex> Enum.split([1, 2, 3], -1)
|
||||
{ [1,2], [3] }
|
||||
|
||||
iex> Enum.split([1, 2, 3], -5)
|
||||
{ [], [1,2,3] }
|
||||
|
||||
@@ -1451,6 +1582,7 @@ defmodule Enum do
|
||||
|
||||
iex> Enum.split_while([1, 2, 3, 4], fn(x) -> x < 3 end)
|
||||
{ [1, 2], [3, 4] }
|
||||
|
||||
"""
|
||||
@spec split_while(t, (element -> as_boolean(term))) :: {list, list}
|
||||
def split_while(collection, fun) when is_list(collection) do
|
||||
@@ -1481,10 +1613,13 @@ defmodule Enum do
|
||||
|
||||
iex> Enum.take([1, 2, 3], 2)
|
||||
[1,2]
|
||||
|
||||
iex> Enum.take([1, 2, 3], 10)
|
||||
[1,2,3]
|
||||
|
||||
iex> Enum.take([1, 2, 3], 0)
|
||||
[]
|
||||
|
||||
iex> Enum.take([1, 2, 3], -1)
|
||||
[3]
|
||||
|
||||
@@ -1563,7 +1698,7 @@ defmodule Enum do
|
||||
|
||||
"""
|
||||
@spec to_list(t) :: [term]
|
||||
def to_list(collection) when is_list collection do
|
||||
def to_list(collection) when is_list(collection) do
|
||||
collection
|
||||
end
|
||||
|
||||
@@ -1571,6 +1706,27 @@ defmodule Enum do
|
||||
reverse(collection) |> :lists.reverse
|
||||
end
|
||||
|
||||
|
||||
@doc """
|
||||
Traverses the given enumerable keeping its shape.
|
||||
|
||||
It also expects the enumerable to implement the `Collectable` protocol.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Enum.traverse(%{a: 1, b: 2}, fn { k, v } -> { k, v * 2 } end)
|
||||
%{a: 2, b: 4}
|
||||
|
||||
"""
|
||||
@spec traverse(Enumerable.t, (term -> term)) :: Collectable.t
|
||||
def traverse(collection, transform) when is_list(collection) do
|
||||
:lists.map(transform, collection)
|
||||
end
|
||||
|
||||
def traverse(collection, transform) do
|
||||
into(collection, Collectable.empty(collection), transform)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Enumerates the collection, removing all duplicated items.
|
||||
|
||||
@@ -1995,6 +2151,29 @@ defimpl Enumerable, for: List do
|
||||
do: { :error, __MODULE__ }
|
||||
end
|
||||
|
||||
defimpl Enumerable, for: Map do
|
||||
def reduce(map, acc, fun) do
|
||||
do_reduce(:maps.to_list(map), acc, fun)
|
||||
end
|
||||
|
||||
defp do_reduce(_, { :halt, acc }, _fun), do: { :halted, acc }
|
||||
defp do_reduce(list, { :suspend, acc }, fun), do: { :suspended, acc, &do_reduce(list, &1, fun) }
|
||||
defp do_reduce([], { :cont, acc }, _fun), do: { :done, acc }
|
||||
defp do_reduce([h|t], { :cont, acc }, fun), do: do_reduce(t, fun.(h, acc), fun)
|
||||
|
||||
def member?(map, { key, value }) do
|
||||
{ :ok, match?({ :ok, ^value }, :maps.find(key, map)) }
|
||||
end
|
||||
|
||||
def member?(_map, _other) do
|
||||
{ :ok, false }
|
||||
end
|
||||
|
||||
def count(map) do
|
||||
{ :ok, map_size(map) }
|
||||
end
|
||||
end
|
||||
|
||||
defimpl Enumerable, for: Function do
|
||||
def reduce(function, acc, fun),
|
||||
do: function.(acc, fun)
|
||||
|
||||
+61
-50
@@ -28,27 +28,33 @@ defexception CompileError, [file: nil, line: nil, description: "compile error"]
|
||||
end
|
||||
end
|
||||
|
||||
defexception BadFunctionError, [actual: nil] do
|
||||
defexception BadFunctionError, [term: nil] do
|
||||
def message(exception) do
|
||||
"expected a function, got: #{inspect(exception.actual)}"
|
||||
"expected a function, got: #{inspect(exception.term)}"
|
||||
end
|
||||
end
|
||||
|
||||
defexception MatchError, [actual: nil] do
|
||||
defexception BadStructError, [struct: nil, term: nil] do
|
||||
def message(exception) do
|
||||
"no match of right hand side value: #{inspect(exception.actual)}"
|
||||
"expected a struct named #{inspect(exception.struct)}, got: #{inspect(exception.term)}"
|
||||
end
|
||||
end
|
||||
|
||||
defexception CaseClauseError, [actual: nil] do
|
||||
defexception MatchError, [term: nil] do
|
||||
def message(exception) do
|
||||
"no case clause matching: #{inspect(exception.actual)}"
|
||||
"no match of right hand side value: #{inspect(exception.term)}"
|
||||
end
|
||||
end
|
||||
|
||||
defexception TryClauseError, [actual: nil] do
|
||||
defexception CaseClauseError, [term: nil] do
|
||||
def message(exception) do
|
||||
"no try clause matching: #{inspect(exception.actual)}"
|
||||
"no case clause matching: #{inspect(exception.term)}"
|
||||
end
|
||||
end
|
||||
|
||||
defexception TryClauseError, [term: nil] do
|
||||
def message(exception) do
|
||||
"no try clause matching: #{inspect(exception.term)}"
|
||||
end
|
||||
end
|
||||
|
||||
@@ -105,9 +111,9 @@ defexception ErlangError, [original: nil] do
|
||||
end
|
||||
end
|
||||
|
||||
defexception KeyError, key: nil do
|
||||
defexception KeyError, key: nil, term: nil do
|
||||
def message(exception) do
|
||||
"key not found: #{inspect exception.key}"
|
||||
"key #{inspect exception.key} not found in: #{inspect exception.term}"
|
||||
end
|
||||
end
|
||||
|
||||
@@ -133,20 +139,19 @@ end
|
||||
|
||||
defmodule Exception do
|
||||
@moduledoc """
|
||||
Convenience functions to work with and pretty print
|
||||
exceptions and stacktraces.
|
||||
Functions to work with and pretty print exceptions.
|
||||
|
||||
Notice that stacktraces in Elixir are updated on errors.
|
||||
For example, at any given moement, `System.stacktrace`
|
||||
will return the stacktrace for the last error that ocurred
|
||||
in the current process.
|
||||
Note that stacktraces in Elixir are updated on throw,
|
||||
errors and exits. For example, at any given moment,
|
||||
`System.stacktrace` will return the stacktrace for the
|
||||
last throw/error/exit that ocurred in the current process.
|
||||
|
||||
That said, many of the functions in this module will
|
||||
automatically calculate the stacktrace based on the caller,
|
||||
when invoked without arguments, changing the value of
|
||||
`System.stacktrace`. If instead you want to format the
|
||||
stacktrace of the latest error, you should instead explicitly
|
||||
pass the `System.stacktrace` as argument.
|
||||
Finally note developers should not rely on the particular
|
||||
format of the `format` functions provided by this module.
|
||||
They may be changed in future releases in order to better
|
||||
suit Elixir's tool chain. In other words, by using the
|
||||
functions in this module it is guarantee you will format
|
||||
exceptions as in the current Elixir version being used.
|
||||
"""
|
||||
|
||||
@type stacktrace :: [stacktrace_entry]
|
||||
@@ -166,71 +171,75 @@ defmodule Exception do
|
||||
normalizes only `:error`, returning the untouched payload
|
||||
for others.
|
||||
"""
|
||||
def normalize(:error, exception), do: normalize(exception)
|
||||
def normalize(_kind, other), do: other
|
||||
def normalize(:error, exception), do: normalize_error(exception)
|
||||
def normalize(_kind, payload), do: payload
|
||||
|
||||
@doc """
|
||||
Normalizes an exception, converting Erlang exceptions
|
||||
to Elixir exceptions.
|
||||
@doc false
|
||||
def normalize(error) do
|
||||
IO.write :stderr, "Exception.normalize/1 is deprecated, please use Exception.normalize/2 instead\n#{Exception.format_stacktrace}"
|
||||
normalize_error(error)
|
||||
end
|
||||
|
||||
Useful when interfacing Erlang code with Elixir code.
|
||||
"""
|
||||
def normalize(exception) when is_exception(exception) do
|
||||
defp normalize_error(exception) when is_exception(exception) do
|
||||
exception
|
||||
end
|
||||
|
||||
def normalize(:badarg) do
|
||||
defp normalize_error(:badarg) do
|
||||
ArgumentError[]
|
||||
end
|
||||
|
||||
def normalize(:badarith) do
|
||||
defp normalize_error(:badarith) do
|
||||
ArithmeticError[]
|
||||
end
|
||||
|
||||
def normalize(:system_limit) do
|
||||
defp normalize_error(:system_limit) do
|
||||
SystemLimitError[]
|
||||
end
|
||||
|
||||
def normalize({ :badarity, { fun, args } }) do
|
||||
defp normalize_error({ :badarity, { fun, args } }) do
|
||||
BadArityError[function: fun, args: args]
|
||||
end
|
||||
|
||||
def normalize({ :badfun, actual }) do
|
||||
BadFunctionError[actual: actual]
|
||||
defp normalize_error({ :badfun, term }) do
|
||||
BadFunctionError[term: term]
|
||||
end
|
||||
|
||||
def normalize({ :badmatch, actual }) do
|
||||
MatchError[actual: actual]
|
||||
defp normalize_error({ :badstruct, struct, term }) do
|
||||
BadStructError[struct: struct, term: term]
|
||||
end
|
||||
|
||||
def normalize({ :case_clause, actual }) do
|
||||
CaseClauseError[actual: actual]
|
||||
defp normalize_error({ :badmatch, term }) do
|
||||
MatchError[term: term]
|
||||
end
|
||||
|
||||
def normalize({ :try_clause, actual }) do
|
||||
TryClauseError[actual: actual]
|
||||
defp normalize_error({ :case_clause, term }) do
|
||||
CaseClauseError[term: term]
|
||||
end
|
||||
|
||||
def normalize(:undef) do
|
||||
defp normalize_error({ :try_clause, term }) do
|
||||
TryClauseError[term: term]
|
||||
end
|
||||
|
||||
defp normalize_error(:undef) do
|
||||
{ mod, fun, arity } = from_stacktrace(:erlang.get_stacktrace)
|
||||
UndefinedFunctionError[module: mod, function: fun, arity: arity]
|
||||
end
|
||||
|
||||
def normalize(:function_clause) do
|
||||
defp normalize_error(:function_clause) do
|
||||
{ mod, fun, arity } = from_stacktrace(:erlang.get_stacktrace)
|
||||
FunctionClauseError[module: mod, function: fun, arity: arity]
|
||||
end
|
||||
|
||||
def normalize({ :badarg, payload }) do
|
||||
defp normalize_error({ :badarg, payload }) do
|
||||
ArgumentError[message: "argument error: #{inspect(payload)}"]
|
||||
end
|
||||
|
||||
def normalize(other) do
|
||||
defp normalize_error(other) do
|
||||
ErlangError[original: other]
|
||||
end
|
||||
|
||||
@doc """
|
||||
Receives an stacktrace entry and formats it into a string.
|
||||
Receives a stacktrace entry and formats it into a string.
|
||||
"""
|
||||
@spec format_stacktrace_entry(stacktrace_entry) :: String.t
|
||||
def format_stacktrace_entry(entry)
|
||||
@@ -250,11 +259,11 @@ defmodule Exception do
|
||||
format_location(location) <> "(file)"
|
||||
end
|
||||
|
||||
def format_stacktrace_entry({module, fun, arity, location}) do
|
||||
def format_stacktrace_entry({ module, fun, arity, location }) do
|
||||
format_application(module) <> format_location(location) <> format_mfa(module, fun, arity)
|
||||
end
|
||||
|
||||
def format_stacktrace_entry({fun, arity, location}) do
|
||||
def format_stacktrace_entry({ fun, arity, location }) do
|
||||
format_location(location) <> format_fa(fun, arity)
|
||||
end
|
||||
|
||||
@@ -307,8 +316,10 @@ defmodule Exception do
|
||||
|
||||
iex> Exception.format_mfa Foo, :bar, 1
|
||||
"Foo.bar/1"
|
||||
|
||||
iex> Exception.format_mfa Foo, :bar, []
|
||||
"Foo.bar()"
|
||||
|
||||
iex> Exception.format_mfa nil, :bar, []
|
||||
"nil.bar()"
|
||||
|
||||
@@ -332,7 +343,7 @@ defmodule Exception do
|
||||
end
|
||||
|
||||
defp format_arity(arity) when is_list(arity) do
|
||||
inspected = lc x inlist arity, do: inspect(x)
|
||||
inspected = for x <- arity, do: inspect(x)
|
||||
"(#{Enum.join(inspected, ", ")})"
|
||||
end
|
||||
|
||||
|
||||
+162
-64
@@ -1,3 +1,5 @@
|
||||
require Record
|
||||
|
||||
defrecord File.Stat, Record.extract(:file_info, from_lib: "kernel/include/file.hrl") do
|
||||
@moduledoc """
|
||||
A record responsible to hold file information. Its fields are:
|
||||
@@ -31,18 +33,99 @@ defrecord File.Stat, Record.extract(:file_info, from_lib: "kernel/include/file.h
|
||||
"""
|
||||
end
|
||||
|
||||
defmodule File do
|
||||
defmodule File.Stream do
|
||||
@moduledoc """
|
||||
Defines a `File.Stream` struct returned by `File.stream!/2`.
|
||||
|
||||
The following fields are public:
|
||||
|
||||
* `path` - the file path
|
||||
* `modes` - the file modes
|
||||
* `raw` - a boolean indicating if bin functions should be used
|
||||
* `line_or_bytes` - if reading should read lines or a given amount of bytes
|
||||
|
||||
"""
|
||||
|
||||
defstruct path: nil, modes: [], line_or_bytes: :line, raw: true
|
||||
|
||||
defimpl Collectable do
|
||||
def empty(stream) do
|
||||
stream
|
||||
end
|
||||
|
||||
def into(%{ path: path, modes: modes, raw: raw } = stream) do
|
||||
modes = for mode <- modes, not mode in [:read], do: mode
|
||||
|
||||
case :file.open(path, [:write|modes]) do
|
||||
{ :ok, device } ->
|
||||
{ :ok, into(device, stream, raw) }
|
||||
{ :error, reason } ->
|
||||
raise File.Error, reason: reason, action: "stream", path: path
|
||||
end
|
||||
end
|
||||
|
||||
defp into(device, stream, raw) do
|
||||
fn
|
||||
:ok, { :cont, x } ->
|
||||
case raw do
|
||||
true -> IO.binwrite(device, x)
|
||||
false -> IO.write(device, x)
|
||||
end
|
||||
:ok, :done ->
|
||||
:file.close(device)
|
||||
stream
|
||||
:ok, :halt ->
|
||||
:file.close(device)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defimpl Enumerable do
|
||||
def reduce(%{ path: path, modes: modes, line_or_bytes: line_or_bytes, raw: raw }, acc, fun) do
|
||||
modes = for mode <- modes, not mode in [:write, :append], do: mode
|
||||
|
||||
start_fun =
|
||||
fn ->
|
||||
case :file.open(path, modes) do
|
||||
{ :ok, device } -> device
|
||||
{ :error, reason } ->
|
||||
raise File.Error, reason: reason, action: "stream", path: path
|
||||
end
|
||||
end
|
||||
|
||||
next_fun =
|
||||
case raw do
|
||||
true -> &IO.each_binstream(&1, line_or_bytes)
|
||||
false -> &IO.each_stream(&1, line_or_bytes)
|
||||
end
|
||||
|
||||
Stream.resource(start_fun, next_fun, &:file.close/1).(acc, fun)
|
||||
end
|
||||
|
||||
def count(_stream) do
|
||||
{ :error, __MODULE__ }
|
||||
end
|
||||
|
||||
def member?(_stream, _term) do
|
||||
{ :error, __MODULE__ }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defmodule File do
|
||||
@moduledoc ~S"""
|
||||
This module contains functions to manipulate files.
|
||||
|
||||
Some of those functions are low-level, allowing the user
|
||||
to interact with the file or IO devices, like `open/2`,
|
||||
`copy/3` and others. This module also provides higher
|
||||
level functions that works with filenames and have their naming
|
||||
level functions that work with filenames and have their naming
|
||||
based on UNIX variants. For example, one can copy a file
|
||||
via `cp/3` and remove files and directories recursively
|
||||
via `rm_rf/1`
|
||||
|
||||
## Encoding
|
||||
|
||||
In order to write and read files, one must use the functions
|
||||
in the `IO` module. By default, a file is opened in binary mode
|
||||
which requires the functions `IO.binread/2` and `IO.binwrite/2`
|
||||
@@ -51,6 +134,8 @@ defmodule File do
|
||||
`IO.write/2` functions must be used as they are responsible for
|
||||
doing the proper conversions and data guarantees.
|
||||
|
||||
## API
|
||||
|
||||
Most of the functions in this module return `:ok` or
|
||||
`{ :ok, result }` in case of success, `{ :error, reason }`
|
||||
otherwise. Those function are also followed by a variant
|
||||
@@ -74,6 +159,23 @@ defmodule File do
|
||||
to react if the file does not exist. The latter should be used
|
||||
when the developer expects his software to fail in case the
|
||||
file cannot be read (i.e. it is literally an exception).
|
||||
|
||||
## Processes and raw files
|
||||
|
||||
Every time a file is opened, Elixir spawns a new process. Writing
|
||||
to a file is equivalent to sending messages to that process that
|
||||
writes to the file descriptor.
|
||||
|
||||
This means files can be passed between nodes and message passing
|
||||
guarantees they can write to the same file in a network.
|
||||
|
||||
However, you may not always want to pay the price for this abstraction.
|
||||
In such cases, a file can be opened in `:raw` mode. The options `:read_ahead`
|
||||
and `:delayed_write` are also useful when operating large files or
|
||||
working with files in tight loops.
|
||||
|
||||
Check http://www.erlang.org/doc/man/file.html#open-2 for more information
|
||||
about such options and other performance considerations.
|
||||
"""
|
||||
|
||||
alias :file, as: F
|
||||
@@ -209,7 +311,7 @@ defmodule File do
|
||||
@doc """
|
||||
Returns information about the `path`. If it exists, it
|
||||
returns a `{ :ok, info }` tuple, where info is a
|
||||
`File.Info` record. Retuns `{ :error, reason }` with
|
||||
`File.Stat` record. Returns `{ :error, reason }` with
|
||||
the same reasons as `read/1` if a failure occurs.
|
||||
|
||||
## Options
|
||||
@@ -513,9 +615,17 @@ defmodule File do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Writes `content` to the file `path`. The file is created if it
|
||||
does not exist. If it exists, the previous contents are overwritten.
|
||||
Returns `:ok` if successful, or `{:error, reason}` if an error occurs.
|
||||
Writes `content` to the file `path`.
|
||||
|
||||
The file is created if it does not exist. If it exists, the previous
|
||||
contents are overwritten. Returns `:ok` if successful, or `{:error, reason}`
|
||||
if an error occurs.
|
||||
|
||||
**Warning:** Every time this function is invoked, a file descriptor is opened
|
||||
and a new process is spawned to write to the file. For this reason, if you are
|
||||
doing multiple writes in a loop, opening the file via `File.open/2` and using
|
||||
the functions in `IO` to write to the file will yield much better performance
|
||||
then calling this function multiple times.
|
||||
|
||||
Typical error reasons are:
|
||||
|
||||
@@ -526,10 +636,11 @@ defmodule File do
|
||||
* :eacces - Missing permission for writing the file or searching one of the parent directories.
|
||||
* :eisdir - The named file is a directory.
|
||||
|
||||
Check `File.open/2` for existing modes.
|
||||
The writing is automatically done in `:raw` mode. Check
|
||||
`File.open/2` for other available options.
|
||||
"""
|
||||
def write(path, content, modes \\ []) do
|
||||
F.write_file(path, content, modes)
|
||||
F.write_file(path, content, [:raw|modes])
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -710,7 +821,7 @@ defmodule File do
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
@doc ~S"""
|
||||
Opens the given `path` according to the given list of modes.
|
||||
|
||||
In order to write and read files, one must use the functions
|
||||
@@ -733,7 +844,7 @@ defmodule File do
|
||||
* `:exclusive` - The file, when opened for writing, is created if it does not exist.
|
||||
If the file exists, open will return { :error, :eexist }.
|
||||
|
||||
* `:charlist` - When this term is given, read operations on the file will return char lists rather than binaries;
|
||||
* `:char_list` - When this term is given, read operations on the file will return char lists rather than binaries;
|
||||
|
||||
* `:compressed` - Makes it possible to read or write gzip compressed files.
|
||||
The compressed option must be combined with either read or write, but not both.
|
||||
@@ -746,10 +857,8 @@ defmodule File do
|
||||
or if data is read by a function that returns data in a format that cannot cope
|
||||
with the character range of the data, an error occurs and the file will be closed.
|
||||
|
||||
If a function is given two modes (instead of a list), it dispatches to `open/3`.
|
||||
|
||||
Check http:\\www.erlang.org/doc/man/file.html#open-2 for more information about
|
||||
other options like `read_ahead` and `delayed_write`.
|
||||
Check http://www.erlang.org/doc/man/file.html#open-2 for more information about
|
||||
other options like `:read_ahead` and `:delayed_write`.
|
||||
|
||||
This function returns:
|
||||
|
||||
@@ -789,7 +898,7 @@ defmodule File do
|
||||
It returns `{ :ok, function_result }` in case of success,
|
||||
`{ :error, reason }` otherwise.
|
||||
|
||||
Do not use this function with :delayed_write option
|
||||
Do not use this function with `:delayed_write` option
|
||||
since automatically closing the file may fail
|
||||
(as writes are delayed).
|
||||
|
||||
@@ -935,63 +1044,46 @@ defmodule File do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Opens the file at the given `path` with the given `modes` and
|
||||
returns a stream for each `:line` (default) or for a given number
|
||||
of bytes given by `line_or_bytes`.
|
||||
Returns a `File.Stream` for the given `path` with the given `modes`.
|
||||
|
||||
The returned stream will fail for the same reasons as
|
||||
`File.open!/2`. Note that the file is opened only and every time
|
||||
streaming begins.
|
||||
The stream implements both `Enumerable` and `Collectable` protocols,
|
||||
which means it can be used both for read and write.
|
||||
|
||||
Note that stream by default uses `IO.binread/2` unless
|
||||
the file is opened with an encoding, then the slower `IO.read/2`
|
||||
is used to do the proper data conversion and guarantees.
|
||||
The `line_or_byte` argument configures how the file is read when
|
||||
streaming, by `:line` (default) or by a given number of bytes.
|
||||
|
||||
Operating the stream can fail on open for the same reasons as
|
||||
`File.open!/2`. Note that the file is automatically opened only and
|
||||
every time streaming begins. There is no need to pass `:read` and
|
||||
`:write` modes, as those are automatically set by Elixir.
|
||||
|
||||
## Raw files
|
||||
|
||||
Since Elixir controls when the streamed file is opened, the underlying
|
||||
device cannot be shared and as such it is convenient to open the file
|
||||
in raw mode for performance reasons. Therefore, Elixir **will** open
|
||||
streams in `:raw` mode with the `:read_ahead` option unless an encoding
|
||||
is specified.
|
||||
|
||||
One may also consider passing the `:delayed_write` option if the stream
|
||||
is meant to be written to under a tight loop.
|
||||
"""
|
||||
def stream!(path, modes \\ [], line_or_bytes \\ :line) do
|
||||
modes = open_defaults(modes, true)
|
||||
bin = nil? List.keyfind(modes, :encoding, 0)
|
||||
raw = :lists.keyfind(:encoding, 1, modes) == false
|
||||
|
||||
start_fun =
|
||||
fn ->
|
||||
case F.open(path, modes) do
|
||||
{ :ok, device } -> device
|
||||
{ :error, reason } ->
|
||||
raise File.Error, reason: reason, action: "stream", path: to_string(path)
|
||||
modes =
|
||||
if raw do
|
||||
if :lists.keyfind(:read_ahead, 1, modes) do
|
||||
[:raw|modes]
|
||||
else
|
||||
[:raw, :read_ahead|modes]
|
||||
end
|
||||
else
|
||||
modes
|
||||
end
|
||||
|
||||
next_fun =
|
||||
case bin do
|
||||
true -> &IO.do_binstream(&1, line_or_bytes)
|
||||
false -> &IO.do_stream(&1, line_or_bytes)
|
||||
end
|
||||
|
||||
Stream.resource(start_fun, next_fun, &F.close/1)
|
||||
end
|
||||
|
||||
@doc false
|
||||
def stream_to!(stream, path, modes \\ []) do
|
||||
IO.write :stderr, "File.stream_to!/3 is deprecated and will be removed/1\n#{Exception.format_stacktrace}"
|
||||
modes = open_defaults([:write|List.delete(modes, :write)], true)
|
||||
bin = nil? List.keyfind(modes, :encoding, 0)
|
||||
|
||||
fn acc, f ->
|
||||
case F.open(path, modes) do
|
||||
{ :ok, device } ->
|
||||
each =
|
||||
case bin do
|
||||
true -> &IO.binwrite(device, &1)
|
||||
false -> &IO.write(device, &1)
|
||||
end
|
||||
|
||||
stream
|
||||
|> Stream.each(each)
|
||||
|> Stream.after(fn -> F.close(device) end)
|
||||
|> Enumerable.Stream.Lazy.reduce(acc, f)
|
||||
{ :error, reason } ->
|
||||
raise File.Error, reason: reason, action: "stream_to", path: to_string(path)
|
||||
end
|
||||
end
|
||||
%File.Stream{ path: to_string(path), modes: modes, raw: raw, line_or_bytes: line_or_bytes }
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -1056,7 +1148,9 @@ defmodule File do
|
||||
|
||||
## Helpers
|
||||
|
||||
defp open_defaults([:charlist|t], _add_binary) do
|
||||
@read_ahead 64*1024
|
||||
|
||||
defp open_defaults([:char_list|t], _add_binary) do
|
||||
open_defaults(t, false)
|
||||
end
|
||||
|
||||
@@ -1064,6 +1158,10 @@ defmodule File do
|
||||
open_defaults([{ :encoding, :utf8 }|t], add_binary)
|
||||
end
|
||||
|
||||
defp open_defaults([:read_ahead|t], add_binary) do
|
||||
open_defaults([{ :read_ahead, @read_ahead }|t], add_binary)
|
||||
end
|
||||
|
||||
defp open_defaults([h|t], add_binary) do
|
||||
[h|open_defaults(t, add_binary)]
|
||||
end
|
||||
|
||||
+14
-4
@@ -13,10 +13,13 @@ defmodule Float do
|
||||
|
||||
iex> Float.parse("34")
|
||||
{34.0,""}
|
||||
|
||||
iex> Float.parse("34.25")
|
||||
{34.25,""}
|
||||
|
||||
iex> Float.parse("56.5xyz")
|
||||
{56.5,"xyz"}
|
||||
|
||||
iex> Float.parse("pi")
|
||||
:error
|
||||
|
||||
@@ -82,7 +85,7 @@ defmodule Float do
|
||||
# iex(1)> 0.0001 * 75
|
||||
# 0.007500000000000001
|
||||
# Due to IEEE 754 floating point standard
|
||||
# http:\\docs.oracle.com/cd/E19957-01/806-3568/ncg_goldberg.html
|
||||
# http://docs.oracle.com/cd/E19957-01/806-3568/ncg_goldberg.html
|
||||
|
||||
final_decimal_places = decimal - exponential
|
||||
if final_decimal_places > 0 do
|
||||
@@ -94,14 +97,16 @@ defmodule Float do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Round a float to the largest integer less than or equal to `num`
|
||||
Rounds a float to the largest integer less than or equal to `num`.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Float.floor(34)
|
||||
34
|
||||
|
||||
iex> Float.floor(34.25)
|
||||
34
|
||||
|
||||
iex> Float.floor(-56.5)
|
||||
-57
|
||||
|
||||
@@ -117,14 +122,16 @@ defmodule Float do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Round a float to the largest integer greater than or equal to `num`
|
||||
Rounds a float to the largest integer greater than or equal to `num`.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Float.ceil(34)
|
||||
34
|
||||
|
||||
iex> Float.ceil(34.25)
|
||||
35
|
||||
|
||||
iex> Float.ceil(-56.5)
|
||||
-56
|
||||
|
||||
@@ -147,16 +154,19 @@ defmodule Float do
|
||||
|
||||
iex> Float.round(5.5674, 3)
|
||||
5.567
|
||||
|
||||
iex> Float.round(5.5675, 3)
|
||||
5.568
|
||||
|
||||
iex> Float.round(-5.5674, 3)
|
||||
-5.567
|
||||
|
||||
iex> Float.round(-5.5675, 3)
|
||||
-5.568
|
||||
|
||||
"""
|
||||
@spec round(float, integer) :: float
|
||||
def round(number, precision) when is_float(number) and is_integer(precision) and precision in 0..15 do
|
||||
Kernel.round(number * :math.pow(10, precision)) / :math.pow(10, precision)
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
@@ -1,202 +0,0 @@
|
||||
defmodule GenFSM.Behaviour do
|
||||
@moduledoc """
|
||||
This module is a convenience for defining GenFSM callbacks in Elixir.
|
||||
|
||||
A finite state machine (FSM) is responsible for reacting to events received;
|
||||
GenFSM is an OTP behaviour that encapsulates common FSM
|
||||
functionalities.
|
||||
|
||||
## Example
|
||||
|
||||
Below is an example of a GenFSM that runs a very simple minded
|
||||
coffee vending machine (CVM). The CVM treats all coins the same. If
|
||||
you press the request button then the CVM will brew coffee, if you
|
||||
have paid enough coins; if not, it will wait until you have inserted
|
||||
enough coins and then it will instantly brew the coffee, since you
|
||||
had already pressed the request button. As we told you - a very
|
||||
simple minded CVM! And greedy too. If you insert more coins than you
|
||||
need it will gladly eat them until you press the request button.
|
||||
|
||||
We will leave it to the service-minded reader to improve the way the CVM
|
||||
works - we hereby declare a full disclaimer for any lawsuits that the
|
||||
behaviour of the CVM in its original state might encur.
|
||||
|
||||
defmodule MyFsm do
|
||||
use GenFSM.Behaviour
|
||||
|
||||
# keeping track of what is going on inside the CVM.
|
||||
# 3 is the target price for a cup of coffee
|
||||
defrecord StateData, coins: 0, price: 3
|
||||
|
||||
# API functions
|
||||
|
||||
def start_link() do
|
||||
:gen_fsm.start_link(__MODULE__, [], [])
|
||||
end
|
||||
|
||||
def insert_coin(pid) do
|
||||
:gen_fsm.send_event(pid, :coin)
|
||||
end
|
||||
|
||||
def request_coffee(pid) do
|
||||
:gen_fsm.send_event(pid, :request_coffee)
|
||||
end
|
||||
|
||||
# Callbacks
|
||||
|
||||
def init(_args) do
|
||||
{ :ok, :short_paid, StateData.new }
|
||||
end
|
||||
|
||||
def short_paid(:coin, state_data = StateData[coins: c, price: p]) when c + 1 < p do
|
||||
{ :next_state, :short_paid, state_data.coins(c + 1) }
|
||||
end
|
||||
|
||||
def short_paid(:coin, state_data) do
|
||||
{ :next_state, :paid_in_full, &state_data.update_coins(&1 + 1) }
|
||||
end
|
||||
|
||||
def short_paid(:request_coffee, state_data) do
|
||||
{ :next_state, :requested_short_paid, state_data }
|
||||
end
|
||||
|
||||
def requested_short_paid(:request_coffee, state_data) do
|
||||
{ :next_state, :requested_short_paid, state_data }
|
||||
end
|
||||
|
||||
def requested_short_paid(:coin, state_data=StateData[coins: c, price: p]) when c+1 < p do
|
||||
{ :next_state, :requested_short_paid, state_data.coins(c + 1) }
|
||||
end
|
||||
|
||||
def requested_short_paid(:coin, _state_data) do
|
||||
IO.puts "Here's your coffee!"
|
||||
{ :next_state, :short_paid, StateData.new }
|
||||
end
|
||||
|
||||
def paid_in_full(:coin, state_data) do
|
||||
{ :next_state, :paid_in_full, &state_data.update_coins(&1 + 1) }
|
||||
end
|
||||
|
||||
def paid_in_full(:request_coffee, _state_data) do
|
||||
IO.puts "Here's your coffee!"
|
||||
{ :next_state, :short_paid, StateData.new }
|
||||
end
|
||||
end
|
||||
|
||||
{ :ok, pid } = MyFsm.start_link()
|
||||
|
||||
MyFsm.insert_coin(pid)
|
||||
#=> :ok
|
||||
MyFsm.insert_coin(pid)
|
||||
#=> :ok
|
||||
|
||||
MyFsm.request_coffee(pid)
|
||||
#=> :ok
|
||||
|
||||
MyFsm.insert_coin(pid)
|
||||
#=> :ok
|
||||
#=> Here's your coffee!
|
||||
|
||||
Notice we never call the GenFSM callbacks directly; they are called by
|
||||
OTP whenever we interact with the server throught the API. `send_event` is
|
||||
asynchronous, whereas `sync_send_event` is synchronous. For
|
||||
a GenFSM, the different values a callback can return depend
|
||||
on the type of callback.
|
||||
|
||||
State handling returns for `send_event` callbacks:
|
||||
|
||||
{ :next_state, next_state_name, new_state_data }
|
||||
{ :next_state, next_state_name, new_state_data, timeout }
|
||||
{ :next_state, next_state_name, new_state_data, :hibernate }
|
||||
{ :stop, reason, new_state_data }
|
||||
|
||||
State handling returns for `sync_send_event` callbacks:
|
||||
|
||||
{ :reply, reply, next_state_name, new_state_data }
|
||||
{ :reply, reply, next_state_name, new_state_data, timeout }
|
||||
{ :reply, reply, next_state_name, new_state_data, :hibernate }
|
||||
{ :next_state, next_state_name, new_state_data }
|
||||
{ :next_state, next_state_name, new_state_data, timeout }
|
||||
{ :next_state, next_state_name, new_state_data, :hibernate }
|
||||
{ :stop, reason, reply, new_state_data }
|
||||
{ :stop, reason, new_state_date }
|
||||
|
||||
There are 6 callbacks required to be implemented in a GenFsm plus 1
|
||||
or 2 for each state. The `GenFSM.Behaviour` module defines
|
||||
`handle_sync_event`, `handle_info`, `terminate` and `code_change`
|
||||
for you. The list of callbacks are:
|
||||
|
||||
* `init(args)` - invoked when the FSM is started;
|
||||
* `handle_sync_event(event, from, state_name, state_data)` - invoked to
|
||||
handle `sync_send_all_state_event` messages;
|
||||
* `handle_event(event, state_name, state_data)` - invoked to handle
|
||||
`send_all_state_event` messages;
|
||||
* `handle_info(msg, state_name, state_data)` - handle all other
|
||||
messages which are normally received by processes;
|
||||
* `terminate(reason, state_name, state_data)` - called when the FSM
|
||||
is about to terminate, useful for cleaning up;
|
||||
* `code_change(old_vsn, state, extra)` - called when the application
|
||||
code is being upgraded live (hot code swap);
|
||||
|
||||
Unlike `GenServer` and `GenEvent`, the callback `init/1` is not
|
||||
implemented by default, as it requires the next state to be returned.
|
||||
|
||||
For each state you need to define either or both of these:
|
||||
|
||||
* `state_name(event, state_data)` - invoked to handle
|
||||
`send_event` messages;
|
||||
* `state_name(event, from, state_data)`- invoked to handle
|
||||
`sync_send_event` messages;
|
||||
|
||||
If you send asynchronous events you only need to implement the
|
||||
`state_name/2` variant and vice-versa for synchronous events and
|
||||
`state_name/3`. Keep in mind that if you mix `send_event` and
|
||||
`sync_send_event` the best thing to do is to implement both
|
||||
callbacks for all states.
|
||||
|
||||
Starting and sending messages to the GenFSM is done via Erlang's
|
||||
`:gen_fsm` module. For more information, please refer to the
|
||||
following:
|
||||
|
||||
* http://www.erlang.org/doc/man/gen_fsm.html
|
||||
* http://www.erlang.org/doc/design_principles/fsm.html
|
||||
* http://learnyousomeerlang.com/finite-state-machines
|
||||
"""
|
||||
|
||||
@doc false
|
||||
defmacro __using__(_) do
|
||||
quote location: :keep do
|
||||
IO.puts :stderr, "warning: GenFSM is deprecated and it will be removed in upcoming releases\n#{Exception.format_stacktrace}"
|
||||
|
||||
@behaviour :gen_fsm
|
||||
|
||||
@doc false
|
||||
def handle_event(event, state_name, state_data) do
|
||||
{ :stop, {:bad_event, state_name, event}, state_data }
|
||||
end
|
||||
|
||||
@doc false
|
||||
def handle_sync_event(event, from, state_name, state_data) do
|
||||
{ :stop, {:bad_sync_event, state_name, event}, state_data }
|
||||
end
|
||||
|
||||
@doc false
|
||||
def handle_info(_msg, state_name, state_data) do
|
||||
{ :next_state, state_name, state_data }
|
||||
end
|
||||
|
||||
@doc false
|
||||
def terminate(_reason, _state_name, _state_data) do
|
||||
:ok
|
||||
end
|
||||
|
||||
@doc false
|
||||
def code_change(_old, state_name, state_data, _extra) do
|
||||
{ :ok, state_name, state_data }
|
||||
end
|
||||
|
||||
defoverridable [handle_event: 3, handle_sync_event: 4,
|
||||
handle_info: 3, terminate: 3, code_change: 4]
|
||||
end
|
||||
end
|
||||
end
|
||||
+23
-21
@@ -31,34 +31,19 @@ defmodule HashDict do
|
||||
trie()
|
||||
end
|
||||
|
||||
@doc """
|
||||
Creates a new dict from the given enumerable.
|
||||
|
||||
## Examples
|
||||
|
||||
HashDict.new [{:b, 1}, {:a, 2}]
|
||||
#=> #HashDict<[a: 2, b: 1]>
|
||||
|
||||
"""
|
||||
@doc false
|
||||
@spec new(Enum.t) :: Dict.t
|
||||
def new(enum) do
|
||||
IO.write :stderr, "HashDict.new/1 is deprecated, please use Enum.into/2 instead\n#{Exception.format_stacktrace}"
|
||||
Enum.reduce enum, trie(), fn { k, v }, dict ->
|
||||
put(dict, k, v)
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Creates a new dict from the enumerable with the
|
||||
help of the transformation function.
|
||||
|
||||
## Examples
|
||||
|
||||
HashDict.new ["a", "b"], fn x -> {x, x} end
|
||||
#=> #HashDict<[{"a","a"},{"b","b"}]>
|
||||
|
||||
"""
|
||||
@doc false
|
||||
@spec new(Enum.t, (term -> {key :: term, value ::term})) :: Dict.t
|
||||
def new(enum, transform) when is_function(transform) do
|
||||
IO.write :stderr, "HashDict.new/2 is deprecated, please use Enum.into/3 instead\n#{Exception.format_stacktrace}"
|
||||
Enum.reduce enum, trie(), fn i, dict ->
|
||||
{ k, v } = transform.(i)
|
||||
put(dict, k, v)
|
||||
@@ -66,6 +51,7 @@ defmodule HashDict do
|
||||
end
|
||||
|
||||
def empty(trie()) do
|
||||
IO.write :stderr, "HashDict.empty/1 is deprecated, please use Collectable.empty/1 instead\n#{Exception.format_stacktrace}"
|
||||
trie()
|
||||
end
|
||||
|
||||
@@ -74,8 +60,9 @@ defmodule HashDict do
|
||||
trie(root: root, size: size + counter)
|
||||
end
|
||||
|
||||
def update!(trie(root: root, size: size), key, fun) when is_function(fun, 1) do
|
||||
{ root, counter } = do_update(root, key, fn -> raise KeyError, key: key end, fun, key_hash(key))
|
||||
def update!(trie(root: root, size: size) = dict, key, fun) when is_function(fun, 1) do
|
||||
{ root, counter } = do_update(root, key, fn -> raise KeyError, key: key, term: dict end,
|
||||
fun, key_hash(key))
|
||||
trie(root: root, size: size + counter)
|
||||
end
|
||||
|
||||
@@ -106,6 +93,7 @@ defmodule HashDict do
|
||||
size
|
||||
end
|
||||
|
||||
@doc false
|
||||
def reduce(trie(root: root), acc, fun) do
|
||||
do_reduce(root, acc, fun, @node_size, fn
|
||||
{:suspend, acc} -> {:suspended, acc, &{ :done, elem(&1, 1) }}
|
||||
@@ -286,3 +274,17 @@ end
|
||||
defimpl Access, for: HashDict do
|
||||
def access(dict, key), do: HashDict.get(dict, key, nil)
|
||||
end
|
||||
|
||||
defimpl Collectable, for: HashDict do
|
||||
def empty(_dict) do
|
||||
HashDict.new
|
||||
end
|
||||
|
||||
def into(original) do
|
||||
{ original, fn
|
||||
dict, { :cont, { k, v } } -> Dict.put(dict, k, v)
|
||||
dict, :done -> dict
|
||||
_, :halt -> :ok
|
||||
end }
|
||||
end
|
||||
end
|
||||
|
||||
+20
-19
@@ -31,34 +31,19 @@ defmodule HashSet do
|
||||
trie()
|
||||
end
|
||||
|
||||
@doc """
|
||||
Creates a new set from the given enumerable.
|
||||
|
||||
## Examples
|
||||
|
||||
HashSet.new [1, 2]
|
||||
#=> #HashSet<[1, 2]>
|
||||
|
||||
"""
|
||||
@doc false
|
||||
@spec new(Enum.t) :: Set.t
|
||||
def new(enum) do
|
||||
IO.write :stderr, "HashSet.new/1 is deprecated, please use Enum.into/2 instead\n#{Exception.format_stacktrace}"
|
||||
Enum.reduce enum, trie(), fn i, set ->
|
||||
put(set, i)
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Creates a new set from the enumerable with the
|
||||
help of the transformation function.
|
||||
|
||||
## Examples
|
||||
|
||||
HashSet.new [1, 2], &integer_to_binary/1
|
||||
#=> #HashSet<[1, 2]>
|
||||
|
||||
"""
|
||||
@doc false
|
||||
@spec new(Enum.t, (term -> term)) :: Set.t
|
||||
def new(enum, transform) when is_function(transform) do
|
||||
IO.write :stderr, "HashSet.new/2 is deprecated, please use Enum.into/3 instead\n#{Exception.format_stacktrace}"
|
||||
Enum.reduce enum, trie(), fn i, set ->
|
||||
put(set, transform.(i))
|
||||
end
|
||||
@@ -112,6 +97,7 @@ defmodule HashSet do
|
||||
end
|
||||
|
||||
def empty(trie()) do
|
||||
IO.write :stderr, "HashSet.empty/1 is deprecated, please use Collectable.empty/1 instead\n#{Exception.format_stacktrace}"
|
||||
trie()
|
||||
end
|
||||
|
||||
@@ -131,6 +117,7 @@ defmodule HashSet do
|
||||
end
|
||||
end
|
||||
|
||||
@doc false
|
||||
def reduce(trie(root: root), acc, fun) do
|
||||
do_reduce(root, acc, fun, @node_size, fn
|
||||
{:suspend, acc} -> {:suspended, acc, &{ :done, elem(&1, 1) }}
|
||||
@@ -281,3 +268,17 @@ defimpl Enumerable, for: HashSet do
|
||||
def member?(set, v), do: { :ok, HashSet.member?(set, v) }
|
||||
def count(set), do: { :ok, HashSet.size(set) }
|
||||
end
|
||||
|
||||
defimpl Collectable, for: HashSet do
|
||||
def empty(_dict) do
|
||||
HashSet.new
|
||||
end
|
||||
|
||||
def into(original) do
|
||||
{ original, fn
|
||||
set, { :cont, x } -> HashSet.put(set, x)
|
||||
set, :done -> set
|
||||
_, :halt -> :ok
|
||||
end }
|
||||
end
|
||||
end
|
||||
|
||||
+72
-14
@@ -3,6 +3,7 @@ import Inspect.Algebra
|
||||
|
||||
defrecord Inspect.Opts,
|
||||
records: true,
|
||||
structs: true,
|
||||
binaries: :infer,
|
||||
char_lists: :infer,
|
||||
limit: 50,
|
||||
@@ -57,6 +58,10 @@ defprotocol Inspect do
|
||||
Inspect.HashSet.inspect(HashSet.new, Inspect.Opts.new)
|
||||
|
||||
"""
|
||||
|
||||
# Handle structs in Any
|
||||
@fallback_to_any true
|
||||
|
||||
def inspect(thing, opts)
|
||||
end
|
||||
|
||||
@@ -75,8 +80,10 @@ defimpl Inspect, for: Atom do
|
||||
|
||||
iex> inspect(:foo)
|
||||
":foo"
|
||||
|
||||
iex> inspect(nil)
|
||||
"nil"
|
||||
|
||||
iex> inspect(Foo.Bar)
|
||||
"Foo.Bar"
|
||||
|
||||
@@ -100,7 +107,7 @@ defimpl Inspect, for: Atom do
|
||||
valid_ref_identifier?(binary) ->
|
||||
"Elixir." <> rest = binary
|
||||
rest
|
||||
atom in [:{}, :<<>>, :...] ->
|
||||
atom in [:%{}, :{}, :<<>>, :..., :[]] ->
|
||||
":" <> binary
|
||||
atom in Macro.binary_ops or atom in Macro.unary_ops ->
|
||||
":" <> binary
|
||||
@@ -163,8 +170,10 @@ defimpl Inspect, for: BitString do
|
||||
|
||||
iex> inspect("bar")
|
||||
"\"bar\""
|
||||
|
||||
iex> inspect("f\"oo")
|
||||
"\"f\\\"oo\""
|
||||
|
||||
iex> inspect(<<0,1,2>>)
|
||||
"<<0, 1, 2>>"
|
||||
|
||||
@@ -290,8 +299,10 @@ defimpl Inspect, for: List do
|
||||
|
||||
iex> inspect('bar')
|
||||
"'bar'"
|
||||
|
||||
iex> inspect([0|'bar'])
|
||||
"[0, 98, 97, 114]"
|
||||
|
||||
iex> inspect([:foo,:bar])
|
||||
"[:foo, :bar]"
|
||||
|
||||
@@ -310,7 +321,7 @@ defimpl Inspect, for: List do
|
||||
end
|
||||
end
|
||||
|
||||
defp keyword({key, value}, opts) do
|
||||
def keyword({key, value}, opts) do
|
||||
concat(
|
||||
key_to_binary(key) <> ": ",
|
||||
to_doc(value, opts)
|
||||
@@ -324,15 +335,15 @@ defimpl Inspect, for: List do
|
||||
end
|
||||
end
|
||||
|
||||
defp keyword?([{ key, _value } | rest]) when is_atom(key) do
|
||||
def keyword?([{ key, _value } | rest]) when is_atom(key) do
|
||||
case atom_to_list(key) do
|
||||
'Elixir.' ++ _ -> false
|
||||
_ -> keyword?(rest)
|
||||
end
|
||||
end
|
||||
|
||||
defp keyword?([]), do: true
|
||||
defp keyword?(_other), do: false
|
||||
def keyword?([]), do: true
|
||||
def keyword?(_other), do: false
|
||||
end
|
||||
|
||||
defimpl Inspect, for: Tuple do
|
||||
@@ -344,6 +355,7 @@ defimpl Inspect, for: Tuple do
|
||||
|
||||
iex> inspect({1, 2, 3})
|
||||
"{1, 2, 3}"
|
||||
|
||||
iex> inspect(ArgumentError.new)
|
||||
"ArgumentError[message: \\\"argument error\\\"]"
|
||||
|
||||
@@ -406,6 +418,32 @@ defimpl Inspect, for: Tuple do
|
||||
end
|
||||
end
|
||||
|
||||
defimpl Inspect, for: Map do
|
||||
def inspect(map, opts) do
|
||||
inspect(map, "", opts)
|
||||
end
|
||||
|
||||
def inspect(map, name, opts) do
|
||||
map = :maps.to_list(map)
|
||||
surround_many("%" <> name <> "{", map, "}", opts.limit, traverse_fun(map, opts))
|
||||
end
|
||||
|
||||
defp traverse_fun(list, opts) do
|
||||
if Inspect.List.keyword?(list) do
|
||||
&Inspect.List.keyword(&1, opts)
|
||||
else
|
||||
&to_map(&1, opts)
|
||||
end
|
||||
end
|
||||
|
||||
defp to_map({key, value}, opts) do
|
||||
concat(
|
||||
concat(to_doc(key, opts), " => "),
|
||||
to_doc(value, opts)
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
defimpl Inspect, for: Integer do
|
||||
@doc """
|
||||
Represents the integer as a string.
|
||||
@@ -450,13 +488,9 @@ defimpl Inspect, for: Regex do
|
||||
"~r\"foo\"m"
|
||||
|
||||
"""
|
||||
def inspect(regex, opts) when size(regex) == 5 do
|
||||
def inspect(regex, opts) when size(regex) == 4 do
|
||||
concat ["~r", to_doc(Regex.source(regex), opts), Regex.opts(regex)]
|
||||
end
|
||||
|
||||
def inspect(other, Inspect.Opts[] = opts) do
|
||||
to_doc(other, opts.records(false))
|
||||
end
|
||||
end
|
||||
|
||||
defimpl Inspect, for: Function do
|
||||
@@ -481,18 +515,24 @@ defimpl Inspect, for: Function do
|
||||
end
|
||||
|
||||
defp default_inspect(mod, fun_info) do
|
||||
"#Function<#{uniq(fun_info)}/#{fun_info[:arity]} in #{Inspect.Atom.inspect(mod)}.#{extract_name(fun_info[:name])}>"
|
||||
"#Function<#{uniq(fun_info)}/#{fun_info[:arity]} in " <>
|
||||
"#{Inspect.Atom.inspect(mod)}#{extract_name(fun_info[:name])}>"
|
||||
end
|
||||
|
||||
defp extract_name([]) do
|
||||
""
|
||||
end
|
||||
|
||||
defp extract_name(name) do
|
||||
case :binary.split(atom_to_binary(name), "-", [:global]) do
|
||||
["", name | _] -> name
|
||||
_ -> name
|
||||
["", name | _] -> "." <> name
|
||||
_ -> "." <> name
|
||||
end
|
||||
end
|
||||
|
||||
defp uniq(fun_info) do
|
||||
integer_to_binary(fun_info[:new_index]) <> "." <> integer_to_binary(fun_info[:uniq])
|
||||
integer_to_binary(fun_info[:new_index]) <> "." <>
|
||||
integer_to_binary(fun_info[:uniq])
|
||||
end
|
||||
end
|
||||
|
||||
@@ -515,6 +555,24 @@ defimpl Inspect, for: Reference do
|
||||
end
|
||||
end
|
||||
|
||||
defimpl Inspect, for: Any do
|
||||
def inspect(%{__struct__: struct} = map, opts) do
|
||||
try do
|
||||
struct.__struct__
|
||||
rescue
|
||||
_ -> Inspect.Map.inspect(map, opts)
|
||||
else
|
||||
dunder ->
|
||||
if :maps.keys(dunder) == :maps.keys(map) do
|
||||
Inspect.Map.inspect(:maps.remove(:__struct__, map),
|
||||
Inspect.Atom.inspect(struct, opts), opts)
|
||||
else
|
||||
Inspect.Map.inspect(map, opts)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defimpl Inspect, for: HashDict do
|
||||
def inspect(dict, opts) do
|
||||
concat ["#HashDict<", Inspect.List.inspect(HashDict.to_list(dict), opts), ">"]
|
||||
|
||||
@@ -8,6 +8,7 @@ defmodule Inspect.Algebra do
|
||||
|
||||
iex> Inspect.Algebra.empty
|
||||
:doc_nil
|
||||
|
||||
iex> "foo"
|
||||
"foo"
|
||||
|
||||
@@ -105,26 +106,42 @@ defmodule Inspect.Algebra do
|
||||
according to the inspect protocol.
|
||||
"""
|
||||
@spec to_doc(any, Inspect.Opts.t) :: t
|
||||
def to_doc(arg, opts) when is_record(opts, Inspect.Opts) do
|
||||
if is_tuple(arg) do
|
||||
if elem(opts, Inspect.Opts.__record__(:index, :records)) do
|
||||
try do
|
||||
Inspect.inspect(arg, opts)
|
||||
rescue
|
||||
e ->
|
||||
res = Inspect.Tuple.inspect(arg, opts)
|
||||
IO.puts :stderr, "** (Inspect.Error) Got #{inspect e.__record__(:name)} with message " <>
|
||||
"#{e.message} while inspecting #{pretty(res, opts.width)}"
|
||||
res
|
||||
end
|
||||
else
|
||||
Inspect.Tuple.inspect(arg, opts)
|
||||
def to_doc(arg, opts) when is_record(arg) and is_record(opts, Inspect.Opts) do
|
||||
if opts.records do
|
||||
try do
|
||||
Inspect.inspect(arg, opts)
|
||||
rescue
|
||||
e ->
|
||||
res = Inspect.Tuple.inspect(arg, opts)
|
||||
IO.puts :stderr, "** (Inspect.Error) Got #{inspect e.__record__(:name)} with message " <>
|
||||
"#{e.message} while inspecting #{pretty(res, opts.width)}"
|
||||
res
|
||||
end
|
||||
else
|
||||
Inspect.inspect(arg, opts)
|
||||
Inspect.Tuple.inspect(arg, opts)
|
||||
end
|
||||
end
|
||||
|
||||
def to_doc(%{ __struct__: struct } = map, opts) when is_atom(struct) and is_record(opts, Inspect.Opts) do
|
||||
if opts.structs do
|
||||
try do
|
||||
Inspect.inspect(map, opts)
|
||||
rescue
|
||||
e ->
|
||||
res = Inspect.Map.inspect(map, opts)
|
||||
IO.puts :stderr, "** (Inspect.Error) Got #{inspect e.__record__(:name)} with message " <>
|
||||
"#{e.message} while inspecting #{pretty(res, opts.width)}"
|
||||
res
|
||||
end
|
||||
else
|
||||
Inspect.Map.inspect(map, opts)
|
||||
end
|
||||
end
|
||||
|
||||
def to_doc(arg, opts) when is_record(opts, Inspect.Opts) do
|
||||
Inspect.inspect(arg, opts)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns `:doc_nil` which is a document entity used to represent
|
||||
nothingness. Takes no arguments.
|
||||
|
||||
@@ -26,8 +26,8 @@ defmodule Integer do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Converts a binary to an integer.
|
||||
|
||||
Converts a binary to an integer.
|
||||
|
||||
If successful, returns a tuple of the form `{ integer, remainder_of_binary }`.
|
||||
Otherwise `:error`.
|
||||
|
||||
@@ -35,8 +35,10 @@ defmodule Integer do
|
||||
|
||||
iex> Integer.parse("34")
|
||||
{34,""}
|
||||
|
||||
iex> Integer.parse("34.5")
|
||||
{34,".5"}
|
||||
|
||||
iex> Integer.parse("three")
|
||||
:error
|
||||
|
||||
|
||||
+77
-12
@@ -6,6 +6,61 @@ defexception IO.StreamError, [:reason, :message] do
|
||||
end
|
||||
end
|
||||
|
||||
defmodule IO.Stream do
|
||||
@moduledoc """
|
||||
Defines a `IO.Stream` struct returned by `IO.stream/2` and `IO.binstream/2`.
|
||||
|
||||
The following fields are public:
|
||||
|
||||
* `device` - the IO device
|
||||
* `raw` - a boolean indicating if bin functions should be used
|
||||
* `line_or_bytes` - if reading should read lines or a given amount of bytes
|
||||
|
||||
"""
|
||||
|
||||
defstruct device: nil, raw: true, line_or_bytes: :line
|
||||
|
||||
defimpl Collectable do
|
||||
def empty(stream) do
|
||||
stream
|
||||
end
|
||||
|
||||
def into(%{ device: device, raw: raw } = stream) do
|
||||
{ :ok, into(stream, device, raw) }
|
||||
end
|
||||
|
||||
defp into(stream, device, raw) do
|
||||
fn
|
||||
:ok, { :cont, x } ->
|
||||
case raw do
|
||||
true -> IO.binwrite(device, x)
|
||||
false -> IO.write(device, x)
|
||||
end
|
||||
:ok, _ -> stream
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defimpl Enumerable do
|
||||
def reduce(%{ device: device, raw: raw, line_or_bytes: line_or_bytes }, acc, fun) do
|
||||
next_fun =
|
||||
case raw do
|
||||
true -> &IO.each_binstream(&1, line_or_bytes)
|
||||
false -> &IO.each_stream(&1, line_or_bytes)
|
||||
end
|
||||
Stream.unfold(device, next_fun).(acc, fun)
|
||||
end
|
||||
|
||||
def count(_stream) do
|
||||
{ :error, __MODULE__ }
|
||||
end
|
||||
|
||||
def member?(_stream, _term) do
|
||||
{ :error, __MODULE__ }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defmodule IO do
|
||||
@moduledoc """
|
||||
Functions handling IO.
|
||||
@@ -159,7 +214,7 @@ defmodule IO do
|
||||
|
||||
unless Keyword.get(opts, :width) do
|
||||
opts = case :io.columns(device) do
|
||||
{ :ok, width } -> Keyword.put(opts, :width, width)
|
||||
{ :ok, width } -> [width: width] ++ opts
|
||||
{ :error, _ } -> opts
|
||||
end
|
||||
end
|
||||
@@ -224,9 +279,14 @@ defmodule IO do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Converts the io device into a Stream. The device is
|
||||
iterated line by line if `:line` is given or by a given
|
||||
number of codepoints.
|
||||
Converts the io device into a `IO.Stream`.
|
||||
|
||||
An `IO.Stream` implements both `Enumerable` and
|
||||
`Collectable`, allowing it to be used for both read
|
||||
and write.
|
||||
|
||||
The device is iterated line by line if `:line` is given or
|
||||
by a given number of codepoints.
|
||||
|
||||
This reads the IO as utf-8. Check out
|
||||
`IO.binstream/2` to handle the IO as a raw binary.
|
||||
@@ -244,24 +304,29 @@ defmodule IO do
|
||||
"""
|
||||
@spec stream(device, :line | pos_integer) :: Enumerable.t
|
||||
def stream(device, line_or_codepoints) do
|
||||
Stream.unfold(map_dev(device), &do_stream(&1, line_or_codepoints))
|
||||
%IO.Stream{ device: map_dev(device), raw: false, line_or_bytes: line_or_codepoints }
|
||||
end
|
||||
|
||||
@doc """
|
||||
Converts the io device into a Stream. The device is
|
||||
iterated line by line or by a number of bytes. This
|
||||
reads the IO as a raw binary.
|
||||
Converts the IO device into a `IO.Stream`.
|
||||
|
||||
An `IO.Stream` implements both `Enumerable` and
|
||||
`Collectable`, allowing it to be used for both read
|
||||
and write.
|
||||
|
||||
The device is iterated line by line or by a number of bytes.
|
||||
This reads the IO device as a raw binary.
|
||||
|
||||
Note that an IO stream has side effects and every time
|
||||
you go over the stream you may get different results.
|
||||
"""
|
||||
@spec binstream(device, :line | pos_integer) :: Enumerable.t
|
||||
def binstream(device, line_or_bytes) do
|
||||
Stream.unfold(map_dev(device), &do_binstream(&1, line_or_bytes))
|
||||
%IO.Stream{ device: map_dev(device), raw: true, line_or_bytes: line_or_bytes }
|
||||
end
|
||||
|
||||
@doc false
|
||||
def do_stream(device, what) do
|
||||
def each_stream(device, what) do
|
||||
case read(device, what) do
|
||||
:eof ->
|
||||
nil
|
||||
@@ -273,7 +338,7 @@ defmodule IO do
|
||||
end
|
||||
|
||||
@doc false
|
||||
def do_binstream(device, what) do
|
||||
def each_binstream(device, what) do
|
||||
case binread(device, what) do
|
||||
:eof ->
|
||||
nil
|
||||
@@ -287,7 +352,7 @@ defmodule IO do
|
||||
# Map the Elixir names for standard io and error to Erlang names
|
||||
defp map_dev(:stdio), do: :standard_io
|
||||
defp map_dev(:stderr), do: :standard_error
|
||||
defp map_dev(other), do: other
|
||||
defp map_dev(other) when is_atom(other) or is_pid(other) or is_tuple(other), do: other
|
||||
|
||||
defp to_chardata(list) when is_list(list), do: list
|
||||
defp to_chardata(other), do: to_string(other)
|
||||
|
||||
@@ -17,7 +17,7 @@ end
|
||||
defmodule IO.ANSI do
|
||||
@moduledoc """
|
||||
Functionality to render ANSI escape sequences
|
||||
(http:\\en.wikipedia.org/wiki/ANSI_escape_code) — characters embedded
|
||||
(http://en.wikipedia.org/wiki/ANSI_escape_code) — characters embedded
|
||||
in text used to control formatting, color, and other output options
|
||||
on video text terminals.
|
||||
"""
|
||||
@@ -72,7 +72,7 @@ defmodule IO.ANSI do
|
||||
@doc "Sets primary (default) font"
|
||||
defsequence :primary_font, 10
|
||||
|
||||
lc font_n inlist [1, 2, 3, 4, 5, 6, 7, 8, 9] do
|
||||
for font_n <- [1, 2, 3, 4, 5, 6, 7, 8, 9] do
|
||||
@doc "Sets alternative font #{font_n}"
|
||||
defsequence :"font_#{font_n}", font_n + 10
|
||||
end
|
||||
@@ -92,7 +92,7 @@ defmodule IO.ANSI do
|
||||
colors = [:black, :red, :green, :yellow, :blue, :magenta, :cyan, :white]
|
||||
colors = Enum.zip(0..(length(colors)-1), colors)
|
||||
|
||||
lc { code, color } inlist colors do
|
||||
for { code, color } <- colors do
|
||||
@doc "Sets foreground color to #{color}"
|
||||
defsequence color, code + 30
|
||||
|
||||
@@ -183,6 +183,7 @@ defmodule IO.ANSI do
|
||||
|
||||
iex> IO.ANSI.escape_fragment("Hello %{red,bright,green}yes", true)
|
||||
"Hello \e[31m\e[1m\e[32myes"
|
||||
|
||||
iex> IO.ANSI.escape_fragment("%{reset}bye", true)
|
||||
"\e[0mbye"
|
||||
|
||||
|
||||
+282
-201
@@ -46,8 +46,10 @@ defmodule Kernel do
|
||||
|
||||
iex> abs(-3.33)
|
||||
3.33
|
||||
|
||||
iex> abs(-3)
|
||||
3
|
||||
|
||||
"""
|
||||
@spec abs(number) :: number
|
||||
def abs(number) do
|
||||
@@ -175,7 +177,7 @@ defmodule Kernel do
|
||||
## Examples
|
||||
|
||||
iex> :my_atom
|
||||
...> binary_to_existing_atom("my_atom")
|
||||
iex> binary_to_existing_atom("my_atom")
|
||||
:my_atom
|
||||
|
||||
iex> binary_to_existing_atom("this_atom_will_never_exist")
|
||||
@@ -245,6 +247,7 @@ defmodule Kernel do
|
||||
|
||||
iex> bit_size(<<433::16, 3::3>>)
|
||||
19
|
||||
|
||||
iex> bit_size(<<1, 2, 3>>)
|
||||
24
|
||||
|
||||
@@ -268,9 +271,11 @@ defmodule Kernel do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns an integer which is the number of bytes needed to contain `bitstring`.
|
||||
(That is, if the number of bits in `bitstring` is not divisible by 8, the resulting
|
||||
number of bytes will be rounded up.)
|
||||
Returns the number of bytes needed to contain `bitstring`.
|
||||
|
||||
That is, if the number of bits in `bitstring` is not divisible by 8,
|
||||
the resulting number of bytes will be rounded up. This operation
|
||||
happens in constant time.
|
||||
|
||||
Allowed in guard tests. Inlined by the compiler.
|
||||
|
||||
@@ -278,6 +283,7 @@ defmodule Kernel do
|
||||
|
||||
iex> byte_size(<<433::16, 3::3>>)
|
||||
3
|
||||
|
||||
iex> byte_size(<<1, 2, 3>>)
|
||||
3
|
||||
|
||||
@@ -369,7 +375,8 @@ defmodule Kernel do
|
||||
|
||||
## Examples
|
||||
|
||||
float_to_binary 7.1, [decimals: 2, compact: true] #=> "7.1"
|
||||
iex> float_to_binary 7.1, [decimals: 2, compact: true]
|
||||
"7.1"
|
||||
|
||||
"""
|
||||
@spec float_to_binary(float, list) :: binary
|
||||
@@ -389,7 +396,8 @@ defmodule Kernel do
|
||||
|
||||
## Examples
|
||||
|
||||
float_to_list 7.1, [decimals: 2, compact: true] #=> '7.1'
|
||||
iex> float_to_list 7.1, [decimals: 2, compact: true]
|
||||
'7.1'
|
||||
|
||||
"""
|
||||
@spec float_to_list(float, list) :: char_list
|
||||
@@ -505,13 +513,13 @@ defmodule Kernel do
|
||||
## Examples
|
||||
|
||||
iex> bin1 = <<1, 2, 3>>
|
||||
...> bin2 = <<4, 5>>
|
||||
...> bin3 = <<6>>
|
||||
...> iolist_to_binary([bin1, 1, [2, 3, bin2], 4|bin3])
|
||||
iex> bin2 = <<4, 5>>
|
||||
iex> bin3 = <<6>>
|
||||
iex> iolist_to_binary([bin1, 1, [2, 3, bin2], 4|bin3])
|
||||
<<1,2,3,1,2,3,4,5,4,6>>
|
||||
|
||||
iex> bin = <<1, 2, 3>>
|
||||
...> iolist_to_binary(bin)
|
||||
iex> iolist_to_binary(bin)
|
||||
<<1,2,3>>
|
||||
|
||||
"""
|
||||
@@ -665,6 +673,16 @@ defmodule Kernel do
|
||||
:erlang.is_tuple(term)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns `true` if `term` is a map; otherwise returns `false`.
|
||||
|
||||
Allowed in guard tests. Inlined by the compiler.
|
||||
"""
|
||||
@spec is_map(term) :: boolean
|
||||
def is_map(term) do
|
||||
:erlang.is_map(term)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns the length of `list`.
|
||||
|
||||
@@ -674,6 +692,7 @@ defmodule Kernel do
|
||||
|
||||
iex> length([1, 2, 3, 4, 5, 6, 7, 8, 9])
|
||||
9
|
||||
|
||||
"""
|
||||
@spec length(list) :: non_neg_integer
|
||||
def length(list) do
|
||||
@@ -689,6 +708,7 @@ defmodule Kernel do
|
||||
|
||||
iex> list_to_atom('elixir')
|
||||
:elixir
|
||||
|
||||
"""
|
||||
@spec list_to_atom(char_list) :: atom
|
||||
def list_to_atom(list) do
|
||||
@@ -704,9 +724,9 @@ defmodule Kernel do
|
||||
## Examples
|
||||
|
||||
iex> bin1 = <<1, 2, 3>>
|
||||
...> bin2 = <<4, 5>>
|
||||
...> bin3 = <<6, 7::4>>
|
||||
...> list_to_bitstring([bin1, 1, [2, 3, bin2], 4|bin3])
|
||||
iex> bin2 = <<4, 5>>
|
||||
iex> bin3 = <<6, 7::4>>
|
||||
iex> list_to_bitstring([bin1, 1, [2, 3, bin2], 4|bin3])
|
||||
<<1,2,3,1,2,3,4,5,4,6,7::size(4)>>
|
||||
|
||||
"""
|
||||
@@ -735,6 +755,7 @@ defmodule Kernel do
|
||||
|
||||
iex> list_to_float('2.2017764e+0')
|
||||
2.2017764
|
||||
|
||||
"""
|
||||
@spec list_to_float(list) :: float
|
||||
def list_to_float(list) do
|
||||
@@ -750,6 +771,7 @@ defmodule Kernel do
|
||||
|
||||
iex> list_to_integer('123')
|
||||
123
|
||||
|
||||
"""
|
||||
@spec list_to_integer(list) :: integer
|
||||
def list_to_integer(list) do
|
||||
@@ -765,6 +787,7 @@ defmodule Kernel do
|
||||
|
||||
iex> list_to_integer('3FF', 16)
|
||||
1023
|
||||
|
||||
"""
|
||||
@spec list_to_integer(list, non_neg_integer) :: integer
|
||||
def list_to_integer(list, base) do
|
||||
@@ -780,6 +803,7 @@ defmodule Kernel do
|
||||
|
||||
iex> list_to_tuple([:share, [:elixir, 163]])
|
||||
{:share, [:elixir, 163]}
|
||||
|
||||
"""
|
||||
@spec list_to_tuple(list) :: tuple
|
||||
def list_to_tuple(list) do
|
||||
@@ -804,6 +828,18 @@ defmodule Kernel do
|
||||
:erlang.make_ref()
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns the size of a map.
|
||||
|
||||
This operation happens in constant time.
|
||||
|
||||
Allowed in guard tests. Inlined by the compiler.
|
||||
"""
|
||||
@spec map_size(map) :: non_neg_integer
|
||||
def map_size(map) do
|
||||
:erlang.map_size(map)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Return the biggest of the two given terms according to
|
||||
Erlang's term ordering. If the terms compare equal, the
|
||||
@@ -930,7 +966,7 @@ defmodule Kernel do
|
||||
@doc """
|
||||
Returns the size of the given argument, which must be a tuple or a binary.
|
||||
|
||||
Prefer using `tuple_size` or `byte_size` insted.
|
||||
Prefer using `tuple_size/1` or `byte_size/1` instead.
|
||||
|
||||
Allowed in guard tests. Inlined by the compiler.
|
||||
"""
|
||||
@@ -1024,7 +1060,7 @@ defmodule Kernel do
|
||||
end
|
||||
|
||||
@doc """
|
||||
A non-local return from a function. Check `try/2` for more information.
|
||||
A non-local return from a function. Check `Kernel.SpecialForms.try/1` for more information.
|
||||
|
||||
Inlined by the compiler.
|
||||
"""
|
||||
@@ -1062,6 +1098,8 @@ defmodule Kernel do
|
||||
@doc """
|
||||
Returns the size of a tuple.
|
||||
|
||||
This operation happens in constant time.
|
||||
|
||||
Allowed in guard tests. Inlined by the compiler.
|
||||
"""
|
||||
@spec tuple_size(tuple) :: non_neg_integer
|
||||
@@ -1171,6 +1209,7 @@ defmodule Kernel do
|
||||
|
||||
iex> 1 / 2
|
||||
0.5
|
||||
|
||||
iex> 2 / 1
|
||||
2.0
|
||||
|
||||
@@ -1229,6 +1268,7 @@ defmodule Kernel do
|
||||
|
||||
iex> true xor false
|
||||
true
|
||||
|
||||
iex> true xor true
|
||||
false
|
||||
|
||||
@@ -1364,6 +1404,7 @@ defmodule Kernel do
|
||||
|
||||
iex> 1 != 2
|
||||
true
|
||||
|
||||
iex> 1 != 1.0
|
||||
false
|
||||
|
||||
@@ -1427,7 +1468,7 @@ defmodule Kernel do
|
||||
## Example
|
||||
|
||||
iex> tuple = { :foo, :bar, 3 }
|
||||
...> elem(tuple, 1)
|
||||
iex> elem(tuple, 1)
|
||||
:bar
|
||||
|
||||
"""
|
||||
@@ -1444,7 +1485,7 @@ defmodule Kernel do
|
||||
## Example
|
||||
|
||||
iex> tuple = { :foo, :bar, 3 }
|
||||
...> set_elem(tuple, 0, :baz)
|
||||
iex> set_elem(tuple, 0, :baz)
|
||||
{ :baz, :bar, 3 }
|
||||
|
||||
"""
|
||||
@@ -1496,6 +1537,7 @@ defmodule Kernel do
|
||||
|
||||
iex> !Enum.empty?([])
|
||||
false
|
||||
|
||||
iex> !List.first([])
|
||||
true
|
||||
|
||||
@@ -1532,7 +1574,7 @@ defmodule Kernel do
|
||||
long as the first part is a literal binary:
|
||||
|
||||
iex> "foo" <> x = "foobar"
|
||||
...> x
|
||||
iex> x
|
||||
"bar"
|
||||
|
||||
"""
|
||||
@@ -1671,6 +1713,7 @@ defmodule Kernel do
|
||||
|
||||
iex> is_exception((fn -> ArithmeticError.new end).())
|
||||
true
|
||||
|
||||
iex> is_exception((fn -> 1 end).())
|
||||
false
|
||||
|
||||
@@ -1698,10 +1741,11 @@ defmodule Kernel do
|
||||
|
||||
## Examples
|
||||
|
||||
defrecord Config, sample: nil
|
||||
|
||||
is_record(Config.new, Config) #=> true
|
||||
is_record(Config.new, List) #=> false
|
||||
iex> defrecord Config, sample: nil
|
||||
iex> is_record(Config.new, Config)
|
||||
true
|
||||
iex> is_record(Config.new, List)
|
||||
false
|
||||
|
||||
"""
|
||||
defmacro is_record(thing, kind) do
|
||||
@@ -1739,24 +1783,6 @@ defmodule Kernel do
|
||||
end
|
||||
end
|
||||
|
||||
@doc false
|
||||
defmacro is_regex(thing) do
|
||||
IO.puts :stderr, "is_regex/1 is deprecated in favor of Regex.regex?/1\n" <>
|
||||
Exception.format_stacktrace(__CALLER__.stacktrace)
|
||||
quote do
|
||||
Kernel.is_record(unquote(thing), Regex)
|
||||
end
|
||||
end
|
||||
|
||||
@doc false
|
||||
defmacro is_range(thing) do
|
||||
IO.puts :stderr, "is_range/1 is deprecated in favor of Range.range?/1\n" <>
|
||||
Exception.format_stacktrace(__CALLER__.stacktrace)
|
||||
quote do
|
||||
Kernel.is_record(unquote(thing), Range)
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Matches the term on the left against the regular expression or string on the
|
||||
right. Returns true if `left` matches `right` (if it's a regular expression)
|
||||
@@ -1795,8 +1821,8 @@ defmodule Kernel do
|
||||
|
||||
The following options are supported:
|
||||
|
||||
* `:records` - when false, records are not formatted by the inspect protocol,
|
||||
they are instead printed as just tuples, defaults to true;
|
||||
* `:structs` - when false, structs are not formatted by the inspect protocol,
|
||||
they are instead printed as maps, defaults to true;
|
||||
|
||||
* `:binaries` - when `:as_strings` all binaries will be printed as strings,
|
||||
non-printable bytes will be escaped; when `:as_binaries` all
|
||||
@@ -1815,8 +1841,7 @@ defmodule Kernel do
|
||||
|
||||
* `:pretty` - if set to true enables pretty printing, defaults to false;
|
||||
|
||||
* `:width` - the width available for inspect to layout the data structure
|
||||
representation. Defaults to the smaller of 80 or the terminal width;
|
||||
* `:width` - defaults to the 80 characters;
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -1826,12 +1851,6 @@ defmodule Kernel do
|
||||
iex> inspect [1, 2, 3, 4, 5], limit: 3
|
||||
"[1, 2, 3, ...]"
|
||||
|
||||
iex> inspect(ArgumentError[])
|
||||
"ArgumentError[message: \"argument error\"]"
|
||||
|
||||
iex> inspect(ArgumentError[], records: false)
|
||||
"{ArgumentError, :__exception__, \"argument error\"}"
|
||||
|
||||
iex> inspect("josé" <> <<0>>)
|
||||
"<<106, 111, 115, 195, 169, 0>>"
|
||||
|
||||
@@ -1845,7 +1864,7 @@ defmodule Kernel do
|
||||
representation of an Elixir term. In such cases, the inspected result
|
||||
must start with `#`. For example, inspecting a function will return:
|
||||
|
||||
inspect &(&1 + &2)
|
||||
inspect fn a, b -> a + b end
|
||||
#=> #Function<...>
|
||||
|
||||
"""
|
||||
@@ -1859,6 +1878,55 @@ defmodule Kernel do
|
||||
Inspect.Algebra.pretty(Inspect.Algebra.to_doc(arg, opts), limit)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Creates and updates structs.
|
||||
|
||||
The struct argument may be an atom (which defines `defstruct`)
|
||||
or a struct itself. The second argument is any Enumerable that
|
||||
emits two-item tuples (key-value) during enumeration.
|
||||
|
||||
If one of the keys in the Enumerable does not exist in the struct,
|
||||
they are automatically discarded.
|
||||
|
||||
This function is useful for dynamically creating and updating
|
||||
structs.
|
||||
|
||||
## Example
|
||||
|
||||
defmodule User do
|
||||
defstruct name: "jose"
|
||||
end
|
||||
|
||||
struct(User)
|
||||
#=> %User{name: "jose"}
|
||||
|
||||
opts = [name: "eric"]
|
||||
user = struct(User, opts)
|
||||
#=> %User{name: "eric"}
|
||||
|
||||
struct(user, unknown: "value")
|
||||
#=> %User{name: "eric"}
|
||||
|
||||
"""
|
||||
def struct(struct, kv \\ [])
|
||||
|
||||
def struct(struct, []) when is_atom(struct) do
|
||||
apply(struct, :__struct__, [])
|
||||
end
|
||||
|
||||
def struct(struct, kv) when is_atom(struct) do
|
||||
struct(apply(struct, :__struct__, []), kv)
|
||||
end
|
||||
|
||||
def struct(%{ __struct__: _ } = struct, kv) do
|
||||
Enum.reduce(kv, struct, fn { k, v }, acc ->
|
||||
case :maps.is_key(k, acc) and k != :__struct__ do
|
||||
true -> :maps.put(k, v, acc)
|
||||
false -> acc
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Converts the argument to a string according to the String.Chars protocol.
|
||||
This is the function invoked when there is string interpolation.
|
||||
@@ -1897,6 +1965,7 @@ defmodule Kernel do
|
||||
|
||||
iex> nil?(1)
|
||||
false
|
||||
|
||||
iex> nil?(nil)
|
||||
true
|
||||
|
||||
@@ -1913,8 +1982,10 @@ defmodule Kernel do
|
||||
|
||||
iex> match?(1, 1)
|
||||
true
|
||||
|
||||
iex> match?(1, 2)
|
||||
false
|
||||
|
||||
iex> match?({1, _}, {1, 2})
|
||||
true
|
||||
|
||||
@@ -1933,6 +2004,7 @@ defmodule Kernel do
|
||||
|
||||
iex> match?(x, 1)
|
||||
true
|
||||
|
||||
iex> binding([:x]) == []
|
||||
true
|
||||
|
||||
@@ -2143,13 +2215,13 @@ defmodule Kernel do
|
||||
end
|
||||
|
||||
defp do_binding(context, vars, in_match) do
|
||||
lc { v, c } inlist vars, c == context, v != :_@CALLER do
|
||||
for { v, c } <- vars, c == context, v != :_@CALLER do
|
||||
{ v, wrap_binding(in_match, { v, [], c }) }
|
||||
end
|
||||
end
|
||||
|
||||
defp do_binding(list, context, vars, in_match) do
|
||||
lc { v, c } inlist vars, c == context, :lists.member(v, list) do
|
||||
for { v, c } <- vars, c == context, :lists.member(v, list) do
|
||||
{ v, wrap_binding(in_match, { v, [], c }) }
|
||||
end
|
||||
end
|
||||
@@ -2299,6 +2371,7 @@ defmodule Kernel do
|
||||
|
||||
iex> unless(Enum.empty?([]), do: "Hello")
|
||||
nil
|
||||
|
||||
iex> unless(Enum.empty?([1,2,3]), do: "Hello")
|
||||
"Hello"
|
||||
|
||||
@@ -2320,7 +2393,7 @@ defmodule Kernel do
|
||||
## Examples
|
||||
|
||||
iex> destructure([x, y, z], [1, 2, 3, 4, 5])
|
||||
...> {x, y, z}
|
||||
iex> {x, y, z}
|
||||
{1, 2, 3}
|
||||
|
||||
Notice in the example above, even though the right
|
||||
@@ -2329,7 +2402,7 @@ defmodule Kernel do
|
||||
are simply assigned to nil:
|
||||
|
||||
iex> destructure([x, y, z], [1])
|
||||
...> {x, y, z}
|
||||
iex> {x, y, z}
|
||||
{1, nil, nil}
|
||||
|
||||
The left side supports any expression you would use
|
||||
@@ -2353,7 +2426,7 @@ defmodule Kernel do
|
||||
unquote(item) = nil
|
||||
end
|
||||
end
|
||||
{ :case, [{:export_all,true}|meta], args }
|
||||
{ :case, [{:export_head,true}|meta], args }
|
||||
end
|
||||
end
|
||||
|
||||
@@ -2365,10 +2438,13 @@ defmodule Kernel do
|
||||
|
||||
iex> 0 in 1..3
|
||||
false
|
||||
|
||||
iex> 1 in 1..3
|
||||
true
|
||||
|
||||
iex> 2 in 1..3
|
||||
true
|
||||
|
||||
iex> 3 in 1..3
|
||||
true
|
||||
|
||||
@@ -2447,35 +2523,52 @@ defmodule Kernel do
|
||||
end
|
||||
|
||||
@doc """
|
||||
`|>` is called the pipeline operator as it is useful
|
||||
to write pipeline style expressions. This operator
|
||||
introduces the expression on the left as the first
|
||||
argument to the function call on the right.
|
||||
`|>` is the pipe operator.
|
||||
|
||||
This operator introduces the expression on the left as
|
||||
the first argument to the function call on the right.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> [1, [2], 3] |> List.flatten |> Enum.map(&(&1 * 2))
|
||||
[2,4,6]
|
||||
iex> [1, [2], 3] |> List.flatten()
|
||||
[1, 2, 3]
|
||||
|
||||
The expression above is simply translated to:
|
||||
The example above is the same as calling `List.flatten([1, [2], 3])`,
|
||||
i.e. the argument on the left side of `|>` is introduced as the first
|
||||
argument of the function call on the right side.
|
||||
|
||||
Enum.map(List.flatten([1, [2], 3]), &(&1 * 2))
|
||||
This pattern is mostly useful when there is a desire to execute
|
||||
a bunch of operations, resembling a pipeline:
|
||||
|
||||
Be aware of operator precedence when using this operator.
|
||||
iex> [1, [2], 3] |> List.flatten |> Enum.map(fn x -> x * 2 end)
|
||||
[2, 4, 6]
|
||||
|
||||
The example above will pass the list to `List.flatten/1`, then get
|
||||
the flattened list and pass to `Enum.map/2`, which will multiply
|
||||
each entry in the list per two.
|
||||
|
||||
In other words, the expression above simply translates to:
|
||||
|
||||
Enum.map(List.flatten([1, [2], 3]), fn x -> x * 2 end)
|
||||
|
||||
Beware of operator precedence when using the pipe operator.
|
||||
For example, the following expression:
|
||||
|
||||
String.graphemes "Hello" |> Enum.reverse
|
||||
|
||||
Is translated to:
|
||||
Translates to:
|
||||
|
||||
String.graphemes("Hello" |> Enum.reverse)
|
||||
|
||||
Which will result in an error as Enumerable protocol
|
||||
is not defined for binaries. Adding explicit parenthesis
|
||||
resolves the ambiguity:
|
||||
Which will result in an error as Enumerable protocol is not defined
|
||||
for binaries. Adding explicit parenthesis resolves the ambiguity:
|
||||
|
||||
String.graphemes("Hello") |> Enum.reverse
|
||||
|
||||
Or, even better:
|
||||
|
||||
"Hello" |> String.graphemes |> Enum.reverse
|
||||
|
||||
"""
|
||||
defmacro left |> right do
|
||||
:lists.foldl fn x, acc -> Macro.pipe(acc, x) end, left, Macro.unpipe(right)
|
||||
@@ -2523,53 +2616,10 @@ defmodule Kernel do
|
||||
The usage of this protocol is to access a raw value in a
|
||||
keyword list.
|
||||
|
||||
sample = [a: 1, b: 2, c: 3]
|
||||
sample[:b] #=> 2
|
||||
iex> sample = [a: 1, b: 2, c: 3]
|
||||
iex> sample[:b]
|
||||
2
|
||||
|
||||
## Aliases
|
||||
|
||||
Whenever invoked on an alias or an atom, the access protocol is
|
||||
expanded at compilation time rather than on runtime. This feature
|
||||
is used by records to allow a developer to match against an specific
|
||||
part of a record:
|
||||
|
||||
def increment(State[counter: counter, other: 13] = state) do
|
||||
state.counter(counter + 1)
|
||||
end
|
||||
|
||||
In the example above, we use the Access protocol to match the
|
||||
counter field in the record `State`. Considering the record
|
||||
definition is as follows:
|
||||
|
||||
defrecord State, counter: 0, other: nil
|
||||
|
||||
The clause above is translated to:
|
||||
|
||||
def increment({ State, counter, 13 } = state) do
|
||||
state.counter(counter + 1)
|
||||
end
|
||||
|
||||
The same pattern can be used to create a new record:
|
||||
|
||||
def new_state(counter) do
|
||||
State[counter: counter]
|
||||
end
|
||||
|
||||
The example above is faster than `State.new(counter: :counter)` because
|
||||
the record is expanded at compilation time and not at runtime. If a field
|
||||
is not specified on creation, it will have its default value.
|
||||
|
||||
Finally, as in Erlang, Elixir also allows the following syntax:
|
||||
|
||||
new_uri = State[_: 1]
|
||||
|
||||
In this case **all** fields will be set to `1`. Notice that,
|
||||
as in Erlang, in case an expression is given, it will be
|
||||
evaluated multiple times:
|
||||
|
||||
new_uri = State[_: IO.puts "Hello"]
|
||||
|
||||
In this case, `"Hello"` will be printed twice (one per each field).
|
||||
"""
|
||||
defmacro access(element, args) when is_list(args) do
|
||||
caller = __CALLER__
|
||||
@@ -2596,7 +2646,7 @@ defmodule Kernel do
|
||||
end
|
||||
end
|
||||
|
||||
Record.access(atom, fields, args, caller)
|
||||
Record.Deprecated.access(atom, fields, args, caller)
|
||||
false ->
|
||||
case caller.in_match? or caller.in_guard? do
|
||||
true -> raise ArgumentError, message: "dynamic access cannot be invoked inside match and guard clauses"
|
||||
@@ -2617,7 +2667,7 @@ defmodule Kernel do
|
||||
## Examples
|
||||
|
||||
iex> x = 1
|
||||
...> x in [1, 2, 3]
|
||||
iex> x in [1, 2, 3]
|
||||
true
|
||||
|
||||
This macro simply translates the expression above to:
|
||||
@@ -2626,7 +2676,7 @@ defmodule Kernel do
|
||||
|
||||
## Guards
|
||||
|
||||
The in operator can be used on guard clauses as long as the
|
||||
The `in` operator can be used on guard clauses as long as the
|
||||
right side is a range or a list. Elixir will then expand the
|
||||
operator to a valid guard expression. For example:
|
||||
|
||||
@@ -2779,11 +2829,11 @@ defmodule Kernel do
|
||||
|
||||
## Examples
|
||||
|
||||
defmodule Foo do
|
||||
def bar, do: :baz
|
||||
end
|
||||
|
||||
Foo.bar #=> :baz
|
||||
iex> defmodule Foo do
|
||||
...> def bar, do: :baz
|
||||
...> end
|
||||
iex> Foo.bar
|
||||
:baz
|
||||
|
||||
## Nesting
|
||||
|
||||
@@ -3035,6 +3085,10 @@ defmodule Kernel do
|
||||
end
|
||||
|
||||
@doc ~S"""
|
||||
Note: Records are mostly deprecated and being slowly removed from
|
||||
the language. The set of supported record operations will be
|
||||
maintained in the Record module.
|
||||
|
||||
Exports a module with a record definition and runtime operations.
|
||||
|
||||
Please see the `Record` module's documentation for an introduction
|
||||
@@ -3084,22 +3138,6 @@ defmodule Kernel do
|
||||
|
||||
user.update_age(fn(old) -> old + 1 end)
|
||||
|
||||
## Types
|
||||
|
||||
Every record defines a type named `t` that can be accessed in typespecs.
|
||||
Those types can be specified inside the record definition:
|
||||
|
||||
defrecord User do
|
||||
record_type name: string, age: integer
|
||||
end
|
||||
|
||||
All fields without a specified type are assumed to have type `term`.
|
||||
|
||||
Assuming the `User` record defined above, it could be used in typespecs
|
||||
as follow:
|
||||
|
||||
@spec handle_user(User.t) :: boolean()
|
||||
|
||||
## Runtime introspection
|
||||
|
||||
At runtime, developers can use `__record__` to get information
|
||||
@@ -3120,42 +3158,19 @@ defmodule Kernel do
|
||||
User.__record__(:index, :unknown)
|
||||
#=> nil
|
||||
|
||||
## Compile-time introspection
|
||||
|
||||
At compile time, one can access the following information about the record
|
||||
from within the record module:
|
||||
|
||||
* `@record_fields` — a keyword list of record fields with defaults
|
||||
* `@record_types` — a keyword list of record fields with types
|
||||
|
||||
For example:
|
||||
|
||||
defrecord Foo, bar: nil do
|
||||
record_type bar: nil | integer
|
||||
IO.inspect @record_fields
|
||||
IO.inspect @record_types
|
||||
end
|
||||
|
||||
Prints out:
|
||||
|
||||
[bar: nil]
|
||||
[bar: {:|,[line: ...],[nil,{:integer,[line: ...],nil}]}]
|
||||
|
||||
Where the last line is a quoted representation of
|
||||
|
||||
[bar: nil | integer]
|
||||
|
||||
"""
|
||||
defmacro defrecord(name, fields, do_block \\ [])
|
||||
|
||||
defmacro defrecord(name, fields, do_block) do
|
||||
defmacro defrecord(name, fields, do_block \\ []) do
|
||||
case is_list(fields) and Keyword.get(fields, :do, false) do
|
||||
false -> Record.defrecord(name, fields, do_block)
|
||||
other -> Record.defrecord(name, Keyword.delete(fields, :do), do: other)
|
||||
false -> Record.Deprecated.defrecord(name, fields, do_block)
|
||||
other -> Record.Deprecated.defrecord(name, Keyword.delete(fields, :do), do: other)
|
||||
end
|
||||
end
|
||||
|
||||
@doc ~S"""
|
||||
Note: Records are mostly deprecated and being slowly removed from
|
||||
the language. The set of supported record operations will be
|
||||
maintained in the Record module.
|
||||
|
||||
Defines a set of private macros to manipulate a record definition.
|
||||
|
||||
This macro defines a set of macros private to the current module to
|
||||
@@ -3196,23 +3211,80 @@ defmodule Kernel do
|
||||
|
||||
state() #=> { MyServer, nil }
|
||||
|
||||
## Types
|
||||
|
||||
`defrecordp` allows a developer to generate a type
|
||||
automatically by simply providing a type to its fields.
|
||||
The following definition:
|
||||
|
||||
defrecordp :user,
|
||||
name: "José" :: binary,
|
||||
age: 25 :: integer
|
||||
|
||||
Will generate the following type:
|
||||
|
||||
@typep user_t :: { :user, binary, integer }
|
||||
|
||||
"""
|
||||
defmacro defrecordp(name, tag \\ nil, fields) do
|
||||
Record.defrecordp(name, Macro.expand(tag, __CALLER__), fields)
|
||||
Record.Deprecated.defrecordp(name, Macro.expand(tag, __CALLER__), fields)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Defines a struct for the current module.
|
||||
|
||||
A struct is a tagged map that allows developers to provide
|
||||
default values for keys, tags to be used in polymorphic
|
||||
dispatches and compile time assertions.
|
||||
|
||||
To define a struct, a developer needs to only define
|
||||
a function named `__struct__/0` that returns a map with the
|
||||
structs field. This macro is a convenience for defining such
|
||||
function, with the addition of a type `t`.
|
||||
|
||||
For more information about structs, please check
|
||||
`Kernel.SpecialForms.%/2`.
|
||||
|
||||
## Examples
|
||||
|
||||
defmodule MyRange do
|
||||
defstruct first: nil, last: nil
|
||||
end
|
||||
|
||||
Notice `defstruct` requires a keyword list of fields and values
|
||||
at expansion time. In other words, `defstruct` works with quoted
|
||||
expressions. In other words, a struct defined like:
|
||||
|
||||
defmodule MyRange do
|
||||
defstruct first: nil, last: 1 + 1
|
||||
end
|
||||
|
||||
Will execute `1 + 1` every time the struct is built. This also
|
||||
implies the following leads to an error:
|
||||
|
||||
defmodule MyRange do
|
||||
my_fields = [first: nil, last: nil]
|
||||
defstruct my_fields
|
||||
end
|
||||
|
||||
## Types
|
||||
|
||||
`defstruct` automatically generates a type `t` unless one exists.
|
||||
The following definition:
|
||||
|
||||
defmodule User do
|
||||
defstruct name: "José" :: String.t,
|
||||
age: 25 :: integer
|
||||
end
|
||||
|
||||
Generates a type as follows:
|
||||
|
||||
@type t :: %User{name: String.t, age: integer}
|
||||
|
||||
In case a struct does not declare a field type, it defaults to `term`.
|
||||
"""
|
||||
defmacro defstruct(kv) do
|
||||
kv = Macro.escape(kv, unquote: true)
|
||||
quote bind_quoted: [kv: kv] do
|
||||
# Expand possible macros that return KVs.
|
||||
kv = Macro.expand(kv, __ENV__)
|
||||
{ fields, types } = Record.Backend.split_fields_and_types(:defstruct, kv)
|
||||
|
||||
if :code.ensure_loaded(Kernel.Typespec) == { :module, Kernel.Typespec } and
|
||||
not Kernel.Typespec.defines_type?(__MODULE__, :t, 0) do
|
||||
@type t :: %{ unquote_splicing(types), __struct__: __MODULE__ }
|
||||
end
|
||||
|
||||
def __struct__() do
|
||||
%{ unquote_splicing(fields), __struct__: __MODULE__ }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@doc ~S"""
|
||||
@@ -3279,7 +3351,7 @@ defmodule Kernel do
|
||||
end)
|
||||
|
||||
fields = quote do: [__exception__: :__exception__] ++ unquote(fields)
|
||||
record = Record.defrecord(name, fields, do_block)
|
||||
record = Record.Deprecated.defrecord(name, fields, do_block)
|
||||
|
||||
quote do
|
||||
{ :module, name, _, _ } = unquote(record)
|
||||
@@ -3291,8 +3363,10 @@ defmodule Kernel do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Defines a module as a protocol and specifies the API that
|
||||
should be defined by its implementations.
|
||||
Defines a protocol.
|
||||
|
||||
A protocol specifies an API that should be defined by its
|
||||
implementations.
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -3334,7 +3408,7 @@ defmodule Kernel do
|
||||
And we would have to define the implementation for all types.
|
||||
The supported types available are:
|
||||
|
||||
* Record (see below)
|
||||
* Structs (see below)
|
||||
* Tuple
|
||||
* Atom
|
||||
* List
|
||||
@@ -3343,23 +3417,24 @@ defmodule Kernel do
|
||||
* Float
|
||||
* Function
|
||||
* PID
|
||||
* Map
|
||||
* Port
|
||||
* Reference
|
||||
* Any (see below)
|
||||
|
||||
## Protocols + Records
|
||||
## Protocols + Structs
|
||||
|
||||
The real benefit of protocols comes when mixed with records.
|
||||
The real benefit of protocols comes when mixed with structs.
|
||||
For instance, Elixir ships with many data types implemented as
|
||||
records, like `HashDict` and `HashSet`. We can implement the
|
||||
structs, like `HashDict` and `HashSet`. We can implement the
|
||||
`Blank` protocol for those types as well:
|
||||
|
||||
defimpl Blank, for: HashDict do
|
||||
defimpl Blank, for: [HashDict, HashSet] do
|
||||
def blank?(dict), do: Dict.empty?(dict)
|
||||
end
|
||||
|
||||
Since records are tuples, if a protocol is not found a given
|
||||
type, it will fallback to `Tuple`.
|
||||
If a protocol is not found for a given type, it will fallback to
|
||||
`Any`.
|
||||
|
||||
## Fallback to any
|
||||
|
||||
@@ -3393,8 +3468,8 @@ defmodule Kernel do
|
||||
|
||||
## Types
|
||||
|
||||
As in records, defining a protocol automatically defines a type
|
||||
named `t`, which can be used as:
|
||||
Defining a protocol automatically defines a type named `t`, which
|
||||
can be used as:
|
||||
|
||||
@spec present?(Blank.t) :: boolean
|
||||
def present?(blank) do
|
||||
@@ -3580,7 +3655,7 @@ defmodule Kernel do
|
||||
|
||||
append_first = Keyword.get(opts, :append_first, false)
|
||||
|
||||
lc fun inlist List.wrap(funs) do
|
||||
for fun <- List.wrap(funs) do
|
||||
{ name, args } =
|
||||
case Macro.decompose_call(fun) do
|
||||
{ _, _ } = pair -> pair
|
||||
@@ -3612,6 +3687,7 @@ defmodule Kernel do
|
||||
|
||||
iex> ~S(foo)
|
||||
"foo"
|
||||
|
||||
iex> ~S(f\#{o}o)
|
||||
"f\\\#{o}o"
|
||||
|
||||
@@ -3628,6 +3704,7 @@ defmodule Kernel do
|
||||
|
||||
iex> ~s(foo)
|
||||
"foo"
|
||||
|
||||
iex> ~s(f\#{:o}o)
|
||||
"foo"
|
||||
|
||||
@@ -3644,6 +3721,7 @@ defmodule Kernel do
|
||||
|
||||
iex> ~C(foo)
|
||||
'foo'
|
||||
|
||||
iex> ~C(f\#{o}o)
|
||||
'f\\\#{o}o'
|
||||
|
||||
@@ -3660,6 +3738,7 @@ defmodule Kernel do
|
||||
|
||||
iex> ~c(foo)
|
||||
'foo'
|
||||
|
||||
iex> ~c(f\#{:o}o)
|
||||
'foo'
|
||||
|
||||
@@ -3724,8 +3803,10 @@ defmodule Kernel do
|
||||
|
||||
iex> ~w(foo \#{:bar} baz)
|
||||
["foo", "bar", "baz"]
|
||||
|
||||
iex> ~w(--source test/enum_test.exs)
|
||||
["--source", "test/enum_test.exs"]
|
||||
|
||||
iex> ~w(foo bar baz)a
|
||||
[:foo, :bar, :baz]
|
||||
|
||||
@@ -3774,14 +3855,14 @@ defmodule Kernel do
|
||||
true ->
|
||||
case mod do
|
||||
?s -> String.split(string)
|
||||
?a -> lc p inlist String.split(string), do: binary_to_atom(p)
|
||||
?c -> lc p inlist String.split(string), do: String.to_char_list!(p)
|
||||
?a -> for p <- String.split(string), do: binary_to_atom(p)
|
||||
?c -> for p <- String.split(string), do: String.to_char_list!(p)
|
||||
end
|
||||
false ->
|
||||
case mod do
|
||||
?s -> quote do: String.split(unquote(string))
|
||||
?a -> quote do: lc(p inlist String.split(unquote(string)), do: binary_to_atom(p))
|
||||
?c -> quote do: lc(p inlist String.split(unquote(string)), do: String.to_char_list!(p))
|
||||
?a -> quote do: for(p <- String.split(unquote(string)), do: binary_to_atom(p))
|
||||
?c -> quote do: for(p <- String.split(unquote(string)), do: String.to_char_list!(p))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -9,14 +9,14 @@ defmodule Kernel.CLI do
|
||||
This is the API invoked by Elixir boot process.
|
||||
"""
|
||||
def main(argv) do
|
||||
argv = lc arg inlist argv, do: String.from_char_list!(arg)
|
||||
argv = for arg <- argv, do: String.from_char_list!(arg)
|
||||
|
||||
{ config, argv } = process_argv(argv, Kernel.CLI.Config.new)
|
||||
System.argv(argv)
|
||||
|
||||
run fn ->
|
||||
command_results = Enum.map(Enum.reverse(config.commands), &process_command(&1, config))
|
||||
command_errors = lc { :error, msg } inlist command_results, do: msg
|
||||
command_errors = for { :error, msg } <- command_results, do: msg
|
||||
errors = Enum.reverse(config.errors) ++ command_errors
|
||||
|
||||
if errors != [] do
|
||||
@@ -60,7 +60,7 @@ defmodule Kernel.CLI do
|
||||
defp at_exit(status) do
|
||||
hooks = :elixir_code_server.call(:flush_at_exit)
|
||||
|
||||
lc hook inlist hooks do
|
||||
for hook <- hooks do
|
||||
try do
|
||||
hook.(status)
|
||||
catch
|
||||
@@ -157,7 +157,7 @@ defmodule Kernel.CLI do
|
||||
process_shared t, config
|
||||
end
|
||||
|
||||
defp process_shared([erl|t], config) when erl in ["--detached", "--hidden"] do
|
||||
defp process_shared([erl|t], config) when erl in ["--detached", "--hidden", "--gen-debug"] do
|
||||
process_shared t, config
|
||||
end
|
||||
|
||||
@@ -171,7 +171,7 @@ defmodule Kernel.CLI do
|
||||
{ config, t }
|
||||
end
|
||||
|
||||
defp process_argv(["+compile"|t], config) do
|
||||
defp process_argv(["+elixirc"|t], config) do
|
||||
process_compiler t, config
|
||||
end
|
||||
|
||||
@@ -288,10 +288,10 @@ defmodule Kernel.CLI do
|
||||
end
|
||||
|
||||
defp process_command({:app, app}, _config) when is_binary(app) do
|
||||
case Application.Behaviour.start(binary_to_atom(app)) do
|
||||
case :application.ensure_all_started(binary_to_atom(app)) do
|
||||
{ :error, reason } ->
|
||||
{ :error, "--app : Could not start application #{app}: #{inspect reason}" }
|
||||
:ok ->
|
||||
{ :ok, _ } ->
|
||||
:ok
|
||||
end
|
||||
end
|
||||
|
||||
@@ -13,24 +13,27 @@ defmodule Kernel.ErrorHandler do
|
||||
:error_handler.undefined_lambda(module, fun, args)
|
||||
end
|
||||
|
||||
def release() do
|
||||
# On release, no further allow elixir_ensure_compiled
|
||||
# directives and revert to the original error handler.
|
||||
# Note we should not delete the elixir_compiler_pid though,
|
||||
# as we still want to send notifications to the compiler.
|
||||
:erlang.erase(:elixir_ensure_compiled)
|
||||
:erlang.process_flag(:error_handler, :error_handler)
|
||||
:ok
|
||||
end
|
||||
|
||||
defp ensure_loaded(module) do
|
||||
case Code.ensure_loaded(module) do
|
||||
{ :module, _ } -> []
|
||||
{ :error, _ } ->
|
||||
parent = :erlang.get(:elixir_compiler_pid)
|
||||
ref = :erlang.make_ref
|
||||
send parent, { :waiting, self(), ref, module }
|
||||
send parent, { :waiting, module, self(), ref, module }
|
||||
:erlang.garbage_collect(self)
|
||||
receive do
|
||||
{ ^ref, :ready } ->
|
||||
:ok
|
||||
{ ^ref, :release } ->
|
||||
# On release, no further allow elixir_ensure_compiled
|
||||
# directives and revert to the original error handler.
|
||||
# Note we should not delete the elixir_compiler_pid though,
|
||||
# as we still want to send notifications to the compiler.
|
||||
:erlang.erase(:elixir_ensure_compiled)
|
||||
:erlang.process_flag(:error_handler, :error_handler)
|
||||
{ ^ref, :ready } -> :ok
|
||||
{ ^ref, :release } -> release()
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -57,16 +57,16 @@ defmodule Kernel.ParallelCompiler do
|
||||
end
|
||||
|
||||
# We already have 4 currently running, don't spawn new ones
|
||||
defp spawn_compilers(files, original, output, callbacks, waiting, queued, schedulers, result) when
|
||||
defp spawn_compilers(entries, original, output, callbacks, waiting, queued, schedulers, result) when
|
||||
length(queued) - length(waiting) >= schedulers do
|
||||
wait_for_messages(files, original, output, callbacks, waiting, queued, schedulers, result)
|
||||
wait_for_messages(entries, original, output, callbacks, waiting, queued, schedulers, result)
|
||||
end
|
||||
|
||||
# Release waiting processes
|
||||
defp spawn_compilers([h|t], original, output, callbacks, waiting, queued, schedulers, result) when is_pid(h) do
|
||||
{ ^h, ref, _ } = List.keyfind(waiting, h, 0)
|
||||
{ _kind, ^h, ref, _module } = List.keyfind(waiting, h, 1)
|
||||
send h, { ref, :ready }
|
||||
waiting = List.keydelete(waiting, h, 0)
|
||||
waiting = List.keydelete(waiting, h, 1)
|
||||
spawn_compilers(t, original, output, callbacks, waiting, queued, schedulers, result)
|
||||
end
|
||||
|
||||
@@ -97,16 +97,19 @@ defmodule Kernel.ParallelCompiler do
|
||||
end)
|
||||
end
|
||||
|
||||
spawn_compilers(t, original, output, callbacks, waiting, [{pid, ref, h}|queued], schedulers, result)
|
||||
spawn_compilers(t, original, output, callbacks, waiting,
|
||||
[{ pid, ref, h }|queued], schedulers, result)
|
||||
end
|
||||
|
||||
# No more files, nothing waiting, queue is empty, we are done
|
||||
defp spawn_compilers([], _original, _output, _callbacks, [], [], _schedulers, result), do: result
|
||||
defp spawn_compilers([], _original, _output, _callbacks, [], [], _schedulers, result) do
|
||||
for { :module, mod } <- result, do: mod
|
||||
end
|
||||
|
||||
# Queued x, waiting for x: POSSIBLE ERROR! Release processes so we get the failures
|
||||
defp spawn_compilers([], original, output, callbacks, waiting, queued, schedulers, result) when length(waiting) == length(queued) do
|
||||
Enum.each queued, fn { child, _, _ } ->
|
||||
{ ^child, ref, _ } = List.keyfind(waiting, child, 0)
|
||||
{ _kind, ^child, ref, _module } = List.keyfind(waiting, child, 1)
|
||||
send child, { ref, :release }
|
||||
end
|
||||
wait_for_messages([], original, output, callbacks, waiting, queued, schedulers, result)
|
||||
@@ -118,8 +121,17 @@ defmodule Kernel.ParallelCompiler do
|
||||
end
|
||||
|
||||
# Wait for messages from child processes
|
||||
defp wait_for_messages(files, original, output, callbacks, waiting, queued, schedulers, result) do
|
||||
defp wait_for_messages(entries, original, output, callbacks, waiting, queued, schedulers, result) do
|
||||
receive do
|
||||
{ :struct_available, module } ->
|
||||
available = for { :struct, pid, _, waiting_module } <- waiting,
|
||||
module == waiting_module,
|
||||
not pid in entries,
|
||||
do: pid
|
||||
|
||||
spawn_compilers(available ++ entries, original, output, callbacks,
|
||||
waiting, queued, schedulers, [{ :struct, module }|result])
|
||||
|
||||
{ :module_available, child, ref, file, module, binary } ->
|
||||
if callback = Keyword.get(callbacks, :each_module) do
|
||||
callback.(file, module, binary)
|
||||
@@ -128,21 +140,25 @@ defmodule Kernel.ParallelCompiler do
|
||||
# Release the module loader which is waiting for an ack
|
||||
send child, { ref, :ack }
|
||||
|
||||
available = lc { pid, _, waiting_module } inlist waiting,
|
||||
waiting_module == module,
|
||||
available = for { _kind, pid, _, waiting_module } <- waiting,
|
||||
module == waiting_module,
|
||||
not pid in entries,
|
||||
do: pid
|
||||
|
||||
spawn_compilers(available ++ files, original, output, callbacks,
|
||||
waiting, queued, schedulers, [module|result])
|
||||
spawn_compilers(available ++ entries, original, output, callbacks,
|
||||
waiting, queued, schedulers, [{ :module, module }|result])
|
||||
|
||||
{ :waiting, child, ref, on } ->
|
||||
# Oops, we already got this module. Do not put it on waiting.
|
||||
if :lists.member(on, result) do
|
||||
send child, { :release, ref }
|
||||
{ :waiting, kind, child, ref, on } ->
|
||||
defined = fn { k, m } -> on == m and k in [kind, :module] end
|
||||
|
||||
# Oops, we already got it, do not put it on waiting.
|
||||
if :lists.any(defined, result) do
|
||||
send child, { ref, :ready }
|
||||
else
|
||||
waiting = [{ child, ref, on }|waiting]
|
||||
waiting = [{ kind, child, ref, on }|waiting]
|
||||
end
|
||||
spawn_compilers(files, original, output, callbacks, waiting, queued, schedulers, result)
|
||||
|
||||
spawn_compilers(entries, original, output, callbacks, waiting, queued, schedulers, result)
|
||||
|
||||
{ :DOWN, _down_ref, :process, down_pid, { :compiled, file } } ->
|
||||
if callback = Keyword.get(callbacks, :each_file) do
|
||||
@@ -151,36 +167,36 @@ defmodule Kernel.ParallelCompiler do
|
||||
|
||||
# Sometimes we may have spurious entries in the waiting
|
||||
# list because someone invoked try/rescue UndefinedFunctionError
|
||||
new_files = List.delete(files, down_pid)
|
||||
new_waiting = List.keydelete(waiting, down_pid, 0)
|
||||
new_entries = List.delete(entries, down_pid)
|
||||
new_queued = List.keydelete(queued, down_pid, 0)
|
||||
spawn_compilers(new_files, original, output, callbacks, new_waiting, new_queued, schedulers, result)
|
||||
new_waiting = List.keydelete(waiting, down_pid, 1)
|
||||
spawn_compilers(new_entries, original, output, callbacks, new_waiting, new_queued, schedulers, result)
|
||||
|
||||
{ :DOWN, down_ref, :process, _down_pid, { :failure, kind, reason, stacktrace } } ->
|
||||
handle_failure(down_ref, kind, reason, stacktrace, files, waiting, queued)
|
||||
wait_for_messages(files, original, output, callbacks, waiting, queued, schedulers, result)
|
||||
handle_failure(down_ref, kind, reason, stacktrace, entries, waiting, queued)
|
||||
wait_for_messages(entries, original, output, callbacks, waiting, queued, schedulers, result)
|
||||
|
||||
{ :DOWN, down_ref, :process, _down_pid, other } ->
|
||||
handle_failure(down_ref, :exit, other, [], files, waiting, queued)
|
||||
wait_for_messages(files, original, output, callbacks, waiting, queued, schedulers, result)
|
||||
handle_failure(down_ref, :exit, other, [], entries, waiting, queued)
|
||||
wait_for_messages(entries, original, output, callbacks, waiting, queued, schedulers, result)
|
||||
end
|
||||
end
|
||||
|
||||
defp handle_failure(ref, kind, reason, stacktrace, files, waiting, queued) do
|
||||
defp handle_failure(ref, kind, reason, stacktrace, entries, waiting, queued) do
|
||||
case List.keyfind(queued, ref, 1) do
|
||||
{ child, ^ref, file } ->
|
||||
if many_missing?(child, files, waiting, queued) do
|
||||
if many_missing?(child, entries, waiting, queued) do
|
||||
IO.puts "== Compilation failed =="
|
||||
IO.puts "Compilation failed on the following files:\n"
|
||||
|
||||
Enum.each Enum.reverse(queued), fn { pid, _ref, file } ->
|
||||
case List.keyfind(waiting, pid, 0) do
|
||||
{ ^pid, _, mod } -> IO.puts "* #{file} is missing module #{inspect mod}"
|
||||
case List.keyfind(waiting, pid, 1) do
|
||||
{ _, ^pid, _, mod } -> IO.puts "* #{file} is missing module #{inspect mod}"
|
||||
_ -> :ok
|
||||
end
|
||||
end
|
||||
|
||||
IO.puts "\nThe first failure is shown below..."
|
||||
IO.puts "\nOne of the failures is shown below..."
|
||||
end
|
||||
|
||||
IO.puts "== Compilation error on file #{file} =="
|
||||
@@ -190,11 +206,11 @@ defmodule Kernel.ParallelCompiler do
|
||||
end
|
||||
end
|
||||
|
||||
defp many_missing?(child, files, waiting, queued) do
|
||||
defp many_missing?(child, entries, waiting, queued) do
|
||||
waiting_length = length(waiting)
|
||||
|
||||
match?({ ^child, _, _ }, List.keyfind(waiting, child, 0)) and
|
||||
waiting_length > 1 and files == [] and
|
||||
match?({ _, ^child, _, _ }, List.keyfind(waiting, child, 1)) and
|
||||
waiting_length > 1 and entries == [] and
|
||||
waiting_length == length(queued)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,413 +0,0 @@
|
||||
# This is an optimization Elixir runs on function clauses.
|
||||
# Whenever a variables matches against a record (a tagged
|
||||
# tuple), this information is stored in order to optimize
|
||||
# record calls.
|
||||
defmodule Kernel.RecordRewriter do
|
||||
@moduledoc false
|
||||
|
||||
def optimize_clause(module, clause) do
|
||||
optimize_clause(clause, module, :orddict.new)
|
||||
end
|
||||
|
||||
## Clause
|
||||
|
||||
defp optimize_clause({ :clause, line, args, guards, body }, module, dict) do
|
||||
{ args, dict } = optimize_args(args, module, dict)
|
||||
{ body, dict, res } = optimize_body(body, module, dict, [])
|
||||
{ { :clause, line, args, guards, body }, dict, res }
|
||||
end
|
||||
|
||||
defp optimize_args(args, module, dict) do
|
||||
Enum.map_reduce args, dict, fn(arg, acc) ->
|
||||
{ new_arg, new_acc, _res } = optimize_expr(arg, module, acc)
|
||||
{ new_arg, new_acc }
|
||||
end
|
||||
end
|
||||
|
||||
defp optimize_body([], _module, dict, _acc) do
|
||||
{ [], dict, nil }
|
||||
end
|
||||
|
||||
defp optimize_body([h], module, dict, acc) do
|
||||
{ new_expr, new_dict, new_res } = optimize_expr(h, module, dict)
|
||||
{ Enum.reverse([new_expr|acc]), new_dict, new_res }
|
||||
end
|
||||
|
||||
defp optimize_body([h|t], module, dict, acc) do
|
||||
{ new_expr, new_dict, _ } = optimize_expr(h, module, dict)
|
||||
optimize_body(t, module, new_dict, [new_expr|acc])
|
||||
end
|
||||
|
||||
## Record helpers
|
||||
|
||||
defp record_fields(record, record) do
|
||||
fields = Module.get_attribute(record, :record_fields)
|
||||
optimizable = Module.get_attribute(record, :record_optimizable)
|
||||
unless nil?(fields) or nil?(optimizable) do
|
||||
{ (lc { k, _ } inlist fields, do: k), optimizable }
|
||||
end
|
||||
end
|
||||
|
||||
defp record_fields(_module, record) do
|
||||
if Code.ensure_loaded?(record) && function_exported?(record, :__record__, 1) do
|
||||
try do
|
||||
fields = lc { k, _ } inlist record.__record__(:fields), do: k
|
||||
optimizable = record.__record__(:optimizable)
|
||||
{ fields, optimizable }
|
||||
rescue
|
||||
[UndefinedFunctionError, FunctionClauseError] -> nil
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defp record_field_info(function) do
|
||||
case atom_to_list(function) do
|
||||
'update' -> nil
|
||||
'update_' ++ _field -> nil
|
||||
_ -> { :accessor, function }
|
||||
end
|
||||
end
|
||||
|
||||
defp optimize_call(line, module, { record, _ } = res, left, { :atom, _, function }, args) do
|
||||
case record_fields(module, record) do
|
||||
{ fields, optimizable } ->
|
||||
opt_call =
|
||||
if { function, length(args) + 1 } in optimizable do
|
||||
case record_field_info(function) do
|
||||
{ kind, field } ->
|
||||
if index = Enum.find_index(fields, &(field == &1)) do
|
||||
optimize_record_accessor_call(line, res, kind, field, index, left, args)
|
||||
end
|
||||
nil ->
|
||||
optimize_record_other_call(line, record, res, function, left, args)
|
||||
end
|
||||
end
|
||||
|
||||
opt_call || optimize_record_other_call(line, record, nil, function, left, args)
|
||||
nil ->
|
||||
nil
|
||||
end
|
||||
end
|
||||
|
||||
defp optimize_call(_line, _module, _res, _left, _right, _args) do
|
||||
nil
|
||||
end
|
||||
|
||||
defp optimize_record_accessor_call(line, _res, :accessor, _field, index, left, []) do
|
||||
call = { :call, line,
|
||||
{ :remote, line, { :atom, 0, :erlang }, { :atom, 0, :element } },
|
||||
[{ :integer, 0, index + 2 }, left]
|
||||
}
|
||||
{ call, nil }
|
||||
end
|
||||
|
||||
defp optimize_record_accessor_call(line, res, :accessor, _field, index, left, [arg]) do
|
||||
call = { :call, line,
|
||||
{ :remote, line, { :atom, 0, :erlang }, { :atom, 0, :setelement } },
|
||||
[{ :integer, 0, index + 2 }, left, arg]
|
||||
}
|
||||
{ call, res }
|
||||
end
|
||||
|
||||
defp optimize_record_other_call(line, record, res, function, left, args) do
|
||||
call = { :call, line,
|
||||
{ :remote, line, { :atom, 0, record }, { :atom, 0, function } },
|
||||
args ++ [left]
|
||||
}
|
||||
{ call, res }
|
||||
end
|
||||
|
||||
## Expr
|
||||
|
||||
defp optimize_expr({ :call, call_line,
|
||||
{ :remote, _, { :atom, _, :erlang }, { :atom, _, :setelement } } = remote,
|
||||
[{ :integer, _, pos } = integer, tuple, value] }, module, dict) when pos > 1 do
|
||||
|
||||
{ tuple, dict, res } = optimize_expr(tuple, module, dict)
|
||||
{ value, dict, _ } = optimize_expr(value, module, dict)
|
||||
{ { :call, call_line, remote, [integer, tuple, value] }, dict, res }
|
||||
end
|
||||
|
||||
defp optimize_expr({ :call, call_line, { :remote, line, left, right }, args }, module, dict) do
|
||||
{ left, dict, res } = optimize_expr(left, module, dict)
|
||||
{ right, dict, _ } = optimize_expr(right, module, dict)
|
||||
{ args, dict } = optimize_args(args, module, dict)
|
||||
|
||||
case optimize_call(call_line, module, res, left, right, args) do
|
||||
{ call, call_res } ->
|
||||
{ call, dict, call_res }
|
||||
nil ->
|
||||
{ { :call, call_line, { :remote, line, left, right }, args }, dict, nil }
|
||||
end
|
||||
end
|
||||
|
||||
defp optimize_expr({ :call, line, expr, args }, module, dict) do
|
||||
{ expr, dict, _ } = optimize_expr(expr, module, dict)
|
||||
{ args, dict } = optimize_args(args, module, dict)
|
||||
{ { :call, line, expr, args }, dict, nil }
|
||||
end
|
||||
|
||||
defp optimize_expr({ :match, line, left, right }, module, dict) do
|
||||
{ left, dict, left_res } = optimize_expr(left, module, dict)
|
||||
{ right, dict, right_res } = optimize_expr(right, module, dict)
|
||||
|
||||
match = { :match, line, left, right }
|
||||
|
||||
if left_res do
|
||||
dict = assign_vars(extract_vars(right, []), dict, left_res)
|
||||
end
|
||||
|
||||
if right_res do
|
||||
dict = assign_vars(extract_vars(left, []), dict, right_res)
|
||||
end
|
||||
|
||||
{ match, dict, right_res || left_res }
|
||||
end
|
||||
|
||||
defp optimize_expr({ :op, line, op, left, right }, module, dict) do
|
||||
{ left, dict, _ } = optimize_expr(left, module, dict)
|
||||
{ right, dict, _ } = optimize_expr(right, module, dict)
|
||||
{ { :op, line, op, left, right }, dict, nil }
|
||||
end
|
||||
|
||||
defp optimize_expr({ :op, line, op, expr }, module, dict) do
|
||||
{ expr, dict, _ } = optimize_expr(expr, module, dict)
|
||||
{ { :op, line, op, expr }, dict, nil }
|
||||
end
|
||||
|
||||
defp optimize_expr({ :bin, line, elements }, module, dict) do
|
||||
{ elements, dict } = optimize_args(elements, module, dict)
|
||||
{ { :bin, line, elements }, dict, nil }
|
||||
end
|
||||
|
||||
defp optimize_expr({ :bin_element, line, expr, type1, type2 }, module, dict) do
|
||||
{ expr, dict, _ } = optimize_expr(expr, module, dict)
|
||||
{ { :bin_element, line, expr, type1, type2 }, dict, nil }
|
||||
end
|
||||
|
||||
defp optimize_expr({ :cons, line, left, right }, module, dict) do
|
||||
{ left, dict, _ } = optimize_expr(left, module, dict)
|
||||
{ right, dict, _ } = optimize_expr(right, module, dict)
|
||||
{ { :cons, line, left, right }, dict, nil }
|
||||
end
|
||||
|
||||
defp optimize_expr({ :block, line, args }, module, dict) do
|
||||
{ args, dict, res } = optimize_body(args, module, dict, [])
|
||||
{ { :block, line, args }, dict, res }
|
||||
end
|
||||
|
||||
defp optimize_expr({ :tuple, line, args }, module, dict) do
|
||||
{ args, dict, args_res } = optimize_tuple_args(args, module, dict)
|
||||
args_res = if Enum.any?(args_res), do: args_res, else: nil
|
||||
|
||||
res =
|
||||
case args do
|
||||
[{ :atom, _, atom }|_] -> atom
|
||||
_ -> nil
|
||||
end
|
||||
|
||||
{ { :tuple, line, args }, dict, { res, args_res } }
|
||||
end
|
||||
|
||||
defp optimize_expr({ :var, _, name } = var, _module, dict) do
|
||||
case :orddict.find(name, dict) do
|
||||
{ :ok, res } -> { var, dict, res }
|
||||
:error -> { var, dict, nil }
|
||||
end
|
||||
end
|
||||
|
||||
defp optimize_expr({ :case, line, expr, clauses }, module, dict) do
|
||||
{ expr, dict, _ } = optimize_expr(expr, module, dict)
|
||||
tuples = lc clause inlist clauses, do: optimize_clause(clause, module, dict)
|
||||
clauses = lc { clause, _, _ } inlist tuples, do: clause
|
||||
dict = join_dict(tuples)
|
||||
res = join_result(tuples)
|
||||
{ { :case, line, expr, clauses }, dict, res }
|
||||
end
|
||||
|
||||
defp optimize_expr({ :receive, line, clauses }, module, dict) do
|
||||
tuples = lc clause inlist clauses, do: optimize_clause(clause, module, dict)
|
||||
clauses = lc { clause, _, _ } inlist tuples, do: clause
|
||||
dict = join_dict(tuples)
|
||||
res = join_result(tuples)
|
||||
{ { :receive, line, clauses }, dict, res }
|
||||
end
|
||||
|
||||
defp optimize_expr({ :receive, line, clauses, after_key, after_value }, module, dict) do
|
||||
tuples = lc clause inlist clauses, do: optimize_clause(clause, module, dict)
|
||||
clauses = lc { clause, _, _ } inlist tuples, do: clause
|
||||
|
||||
{ after_key, dict, _ } = optimize_expr(after_key, module, dict)
|
||||
{ after_value, dict, res } = optimize_body(after_value, module, dict, [])
|
||||
|
||||
dict = join_dict(tuples, dict)
|
||||
res = join_result(tuples, res)
|
||||
|
||||
{ { :receive, line, clauses, after_key, after_value }, dict, res }
|
||||
end
|
||||
|
||||
defp optimize_expr({ :try, line, body, elses, catches, try_after }, module, dict) do
|
||||
tuples = lc clause inlist catches, do: optimize_clause(clause, module, dict)
|
||||
catches = lc { clause, _, _ } inlist tuples, do: clause
|
||||
|
||||
tuples = lc clause inlist elses, do: optimize_clause(clause, module, dict)
|
||||
elses = lc { clause, _, _ } inlist tuples, do: clause
|
||||
|
||||
{ body, _, res } = optimize_body(body, module, dict, [])
|
||||
res = join_result(tuples, res)
|
||||
|
||||
{ try_after, _, _ } = optimize_body(try_after, module, dict, [])
|
||||
{ { :try, line, body, elses, catches, try_after }, dict, res }
|
||||
end
|
||||
|
||||
defp optimize_expr({ :fun, line, { :function, receiver, name, arity } }, module, dict) do
|
||||
{ receiver, dict, _ } = optimize_expr(receiver, module, dict)
|
||||
{ name, dict, _ } = optimize_expr(name, module, dict)
|
||||
{ arity, dict, _ } = optimize_expr(arity, module, dict)
|
||||
{ { :fun, line, { :function, receiver, name, arity } }, dict, nil }
|
||||
end
|
||||
|
||||
defp optimize_expr({ :fun, line, { :clauses, clauses } }, module, dict) do
|
||||
clauses = lc clause inlist clauses do
|
||||
{ clause, _, _ } = optimize_clause(clause, module, dict)
|
||||
clause
|
||||
end
|
||||
|
||||
{ { :fun, line, { :clauses, clauses } }, dict, nil }
|
||||
end
|
||||
|
||||
defp optimize_expr({ comprehension, line, expr, args }, module, dict) when comprehension in [:lc, :bc] do
|
||||
{ args, new_dict } = optimize_args(args, module, dict)
|
||||
{ expr, _, _ } = optimize_expr(expr, module, new_dict)
|
||||
{ { comprehension, line, expr, args }, dict, nil }
|
||||
end
|
||||
|
||||
defp optimize_expr({ generate, line, left, right }, module, dict) when generate in [:generate, :b_generate] do
|
||||
{ left, dict, _ } = optimize_expr(left, module, dict)
|
||||
{ right, dict, _ } = optimize_expr(right, module, dict)
|
||||
{ { generate, line, left, right }, dict, nil }
|
||||
end
|
||||
|
||||
defp optimize_expr(other, _module, dict) when elem(other, 0) in [:string, :atom, :integer, :float, :nil, :fun] do
|
||||
{ other, dict, nil }
|
||||
end
|
||||
|
||||
## Helpers
|
||||
|
||||
defp optimize_tuple_args(args, module, dict) do
|
||||
{ final_args, { final_dict, final_acc } } =
|
||||
Enum.map_reduce args, { dict, [] }, fn(arg, { acc_dict, acc_res }) ->
|
||||
{ new_arg, new_acc, res } = optimize_expr(arg, module, acc_dict)
|
||||
{ new_arg, { new_acc, [res|acc_res] } }
|
||||
end
|
||||
|
||||
{ final_args, final_dict, Enum.reverse(final_acc) }
|
||||
end
|
||||
|
||||
defp assign_vars([key|t], dict, { _, value } = res) when is_list(key) and is_list(value) and length(key) == length(value) do
|
||||
assign_vars t, assign_nested_vars(key, dict, value), res
|
||||
end
|
||||
|
||||
defp assign_vars([key|t], dict, { value, _ } = res) when is_atom(key) and value != nil do
|
||||
dict =
|
||||
case :orddict.find(key, dict) do
|
||||
{ :ok, ^res } ->
|
||||
dict
|
||||
{ :ok, { ^value, _ } } ->
|
||||
:orddict.store(key, { value, nil }, dict)
|
||||
{ :ok, _ } ->
|
||||
# We are overriding a type of an existing variable,
|
||||
# which means the source code is invalid.
|
||||
:orddict.store(key, nil, dict)
|
||||
:error ->
|
||||
:orddict.store(key, res, dict)
|
||||
end
|
||||
|
||||
assign_vars t, dict, res
|
||||
end
|
||||
|
||||
defp assign_vars([_|t], dict, res) do
|
||||
assign_vars t, dict, res
|
||||
end
|
||||
|
||||
defp assign_vars([], dict, _res) do
|
||||
dict
|
||||
end
|
||||
|
||||
defp assign_nested_vars([vars|vt], dict, [res|rt]) do
|
||||
assign_nested_vars(vt, assign_vars(vars, dict, res), rt)
|
||||
end
|
||||
|
||||
defp assign_nested_vars([], dict, []) do
|
||||
dict
|
||||
end
|
||||
|
||||
defp extract_vars({ :match, _, left, right }, vars) do
|
||||
vars = extract_vars(right, vars)
|
||||
extract_vars(left, vars)
|
||||
end
|
||||
|
||||
defp extract_vars({ :var, _, name }, vars) do
|
||||
[name|vars]
|
||||
end
|
||||
|
||||
defp extract_vars({ :tuple, _, args }, vars) do
|
||||
[Enum.map(args, &extract_vars(&1, []))|vars]
|
||||
end
|
||||
|
||||
defp extract_vars(_, vars) do
|
||||
vars
|
||||
end
|
||||
|
||||
defp join_dict([]) do
|
||||
[]
|
||||
end
|
||||
|
||||
defp join_dict([{ _, dict, _ }|t]) do
|
||||
join_dict(t, dict)
|
||||
end
|
||||
|
||||
defp join_dict([{ _, dict, _ }|t], other) do
|
||||
other = Enum.reduce other, other, fn
|
||||
{ key, { value, _ } = res }, acc ->
|
||||
case :orddict.find(key, dict) do
|
||||
{ :ok, ^res } -> acc
|
||||
{ :ok, { ^value, _ } } -> :orddict.store(key, { value, nil }, acc)
|
||||
{ :ok, _ } -> :orddict.store(key, nil, acc)
|
||||
:error -> :orddict.erase(key, acc)
|
||||
end
|
||||
{ key, nil }, acc ->
|
||||
:orddict.store(key, nil, acc)
|
||||
end
|
||||
|
||||
join_dict(t, other)
|
||||
end
|
||||
|
||||
defp join_dict([], other) do
|
||||
other
|
||||
end
|
||||
|
||||
defp join_result([]) do
|
||||
[]
|
||||
end
|
||||
|
||||
defp join_result([{ _, _, res }|t]) do
|
||||
join_result(t, res)
|
||||
end
|
||||
|
||||
defp join_result([{ _, _, res }|t], res) do
|
||||
join_result(t, res)
|
||||
end
|
||||
|
||||
defp join_result([{ _, _, { res, _ } }|t], { res, _ }) do
|
||||
join_result(t, { res, nil })
|
||||
end
|
||||
|
||||
defp join_result([{ _, _, _ }|_], _res) do
|
||||
nil
|
||||
end
|
||||
|
||||
defp join_result([], res) do
|
||||
res
|
||||
end
|
||||
end
|
||||
@@ -20,22 +20,141 @@ defmodule Kernel.SpecialForms do
|
||||
"""
|
||||
|
||||
@doc """
|
||||
Defines a new tuple.
|
||||
Creates a tuple.
|
||||
|
||||
Only two item tuples are considered literals in Elixir.
|
||||
Therefore all other tuples are represented in the AST
|
||||
as a call to the special form `:{}`.
|
||||
|
||||
Conveniences for manipulating tuples can be found in the
|
||||
`Tuple` module. Some functions for working with tuples are
|
||||
also available in `Kernel`, namely `Kernel.elem/2`,
|
||||
`Kernel.set_elem/3` and `Kernel.tuple_size/1`.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> { 1, 2, 3 }
|
||||
{ 1, 2, 3 }
|
||||
|
||||
iex> quote do: { 1, 2, 3 }
|
||||
{ :{}, [], [1,2,3] }
|
||||
|
||||
"""
|
||||
defmacro unquote(:{})(args)
|
||||
|
||||
@doc """
|
||||
Creates a map.
|
||||
|
||||
Maps are key-value stores where keys are compared
|
||||
using the match operator (`===`). Maps can be created with
|
||||
the `%{}` special form where keys are associated via `=>`:
|
||||
|
||||
%{ 1 => 2 }
|
||||
|
||||
Maps also support the keyword notation, as other special forms,
|
||||
as long as they are at the end of the argument list:
|
||||
|
||||
%{ hello: :world, with: :keywords }
|
||||
%{ :hello => :world, with: :keywords }
|
||||
|
||||
If a map has duplicated keys, the last key will always have
|
||||
higher precedence:
|
||||
|
||||
iex> %{ a: :b, a: :c }
|
||||
%{ a: :c }
|
||||
|
||||
Conveniences for manipulating maps can be found in the
|
||||
`Map` module.
|
||||
|
||||
## Access syntax
|
||||
|
||||
Besides the access functions available in the `Map` module,
|
||||
like `Map.get/3` and `Map.fetch/2`, a map can be accessed using the
|
||||
`.` operator:
|
||||
|
||||
iex> map = %{ a: :b }
|
||||
iex> map.a
|
||||
:b
|
||||
|
||||
Note that the `.` operator expects the field to exist in the map.
|
||||
If not, an `ArgumentError` is raised.
|
||||
|
||||
## Update syntax
|
||||
|
||||
Maps also support an update syntax:
|
||||
|
||||
iex> map = %{ :a => :b }
|
||||
iex> %{ map | :a => :c }
|
||||
%{ :a => :c }
|
||||
|
||||
Notice the update syntax requires the given keys to exist.
|
||||
Trying to update a key that does not exist will raise an `ArgumentError`.
|
||||
|
||||
## AST representation
|
||||
|
||||
Regardless if `=>` or the keywords syntax is used, Maps are
|
||||
always represented internally as a list of two-items tuples
|
||||
for simplicity:
|
||||
|
||||
iex> quote do: %{ :a => :b, c: :d }
|
||||
{ :%{}, [], [{:a, :b}, {:c, :d}] }
|
||||
|
||||
"""
|
||||
defmacro unquote(:%{})(args)
|
||||
|
||||
@doc """
|
||||
Creates a struct.
|
||||
|
||||
A struct is a tagged map that allows developers to provide
|
||||
default values for keys, tags to be used in polymorphic
|
||||
dispatches and compile time assertions.
|
||||
|
||||
To define a struct, you just need to implement the `__struct__/0`
|
||||
function in a module:
|
||||
|
||||
defmodule User do
|
||||
def __struct__ do
|
||||
%{ name: "josé", age: 27 }
|
||||
end
|
||||
end
|
||||
|
||||
Now a struct can be created as follow:
|
||||
|
||||
%User{}
|
||||
|
||||
Underneath, a struct is just a map with a `__struct__` field
|
||||
pointing to the User module:
|
||||
|
||||
%User{} == %{ __struct__: User, name: "josé", age: 27 }
|
||||
|
||||
A struct also validates the given keys are part of the defined
|
||||
struct. The example below will fail because there is no key
|
||||
`:full_name` in the user struct:
|
||||
|
||||
%User{ full_name: "José Valim" }
|
||||
|
||||
Note that a struct specifies a minimum set of keys required
|
||||
for operations. Other keys can be added to structs via the
|
||||
regular map operations:
|
||||
|
||||
user = %User{}
|
||||
Map.put(user, :a_non_struct_key, :value)
|
||||
|
||||
An update operation specific for structs is also available:
|
||||
|
||||
%User{ user | age: 28 }
|
||||
|
||||
The syntax above will guarantee the given keys are valid at
|
||||
compilation time and it will guarantee at runtime the given
|
||||
argument is a struct, failing with `BadStructError` otherwise.
|
||||
|
||||
Check `Kernel.defprotocol/2` for more information on how structs
|
||||
can be used with protocols for polymorphic dispatch. Also,
|
||||
see `Kernel.struct/2` for examples on how to create and update
|
||||
structs dynamically.
|
||||
"""
|
||||
defmacro unquote(:%)(struct, map)
|
||||
|
||||
@doc """
|
||||
Defines a new bitstring.
|
||||
|
||||
@@ -108,7 +227,7 @@ defmodule Kernel.SpecialForms do
|
||||
signedness, e.g.:
|
||||
|
||||
iex> <<val, _rest :: binary>> = <<-100, "foo">>
|
||||
...> val
|
||||
iex> val
|
||||
156
|
||||
|
||||
Here, `val` is interpreted as unsigned.
|
||||
@@ -200,12 +319,16 @@ defmodule Kernel.SpecialForms do
|
||||
|
||||
iex> Kernel.Sample
|
||||
Kernel.Sample
|
||||
|
||||
iex> Kernel.length([1,2,3])
|
||||
3
|
||||
|
||||
iex> Kernel.+(1, 2)
|
||||
3
|
||||
|
||||
iex> Kernel."length"([1,2,3])
|
||||
3
|
||||
|
||||
iex> Kernel.'+'(1, 2)
|
||||
3
|
||||
|
||||
@@ -229,6 +352,7 @@ defmodule Kernel.SpecialForms do
|
||||
|
||||
iex> apply(:erlang, :+, [1,2])
|
||||
3
|
||||
|
||||
iex> Module.concat(Kernel, Sample)
|
||||
Kernel.Sample
|
||||
|
||||
@@ -331,7 +455,7 @@ defmodule Kernel.SpecialForms do
|
||||
Elixir won't emit any warnings though, since the alias
|
||||
was not explicitly defined.
|
||||
|
||||
Both warning behaviours could be changed by explicitily
|
||||
Both warning behaviours could be changed by explicitly
|
||||
setting the `:warn` option to true or false.
|
||||
"""
|
||||
defmacro alias(module, opts)
|
||||
@@ -377,7 +501,7 @@ defmodule Kernel.SpecialForms do
|
||||
for example:
|
||||
|
||||
iex> import List
|
||||
...> flatten([1, [2], 3])
|
||||
iex> flatten([1, [2], 3])
|
||||
[1,2,3]
|
||||
|
||||
## Selector
|
||||
@@ -443,7 +567,7 @@ defmodule Kernel.SpecialForms do
|
||||
Elixir won't emit any warnings though, since the import
|
||||
was not explicitly defined.
|
||||
|
||||
Both warning behaviours could be changed by explicitily
|
||||
Both warning behaviours could be changed by explicitly
|
||||
setting the `:warn` option to true or false.
|
||||
|
||||
## Ambiguous function/macro names
|
||||
@@ -1007,77 +1131,65 @@ defmodule Kernel.SpecialForms do
|
||||
"""
|
||||
defmacro unquote(:unquote_splicing)(expr)
|
||||
|
||||
@doc """
|
||||
List comprehensions allow you to quickly build a list from another list:
|
||||
@doc ~S"""
|
||||
Comprehensions allow you to quickly build a data structure from
|
||||
an enumerable or a bitstring.
|
||||
|
||||
iex> lc n inlist [1, 2, 3, 4], do: n * 2
|
||||
[2,4,6,8]
|
||||
Let's start with an example:
|
||||
|
||||
A comprehension accepts many generators and also filters. Generators
|
||||
are defined using both `inlist` and `inbits` operators, allowing you
|
||||
to loop lists and bitstrings:
|
||||
iex> for n <- [1, 2, 3, 4], do: n * 2
|
||||
[2, 4, 6, 8]
|
||||
|
||||
A comprehension accepts many generators and filters. Enumerable
|
||||
generators are defined using `<-`:
|
||||
|
||||
# A list generator:
|
||||
iex> lc n inlist [1, 2, 3, 4], do: n * 2
|
||||
[2,4,6,8]
|
||||
|
||||
# A bit string generator:
|
||||
iex> lc <<n>> inbits <<1, 2, 3, 4>>, do: n * 2
|
||||
[2,4,6,8]
|
||||
|
||||
# A generator from a variable:
|
||||
iex> list = [1, 2, 3, 4]
|
||||
...> lc n inlist list, do: n * 2
|
||||
[2,4,6,8]
|
||||
iex> for n <- [1, 2, 3, 4], do: n * 2
|
||||
[2, 4, 6, 8]
|
||||
|
||||
# A comprehension with two generators
|
||||
iex> lc x inlist [1, 2], y inlist [2, 3], do: x*y
|
||||
[2,3,4,6]
|
||||
iex> for x <- [1, 2], y <- [2, 3], do: x*y
|
||||
[2, 3, 4, 6]
|
||||
|
||||
Filters can also be given:
|
||||
|
||||
# A comprehension with a generator and a filter
|
||||
iex> lc n inlist [1, 2, 3, 4, 5, 6], rem(n, 2) == 0, do: n
|
||||
[2,4,6]
|
||||
iex> for n <- [1, 2, 3, 4, 5, 6], rem(n, 2) == 0, do: n
|
||||
[2, 4, 6]
|
||||
|
||||
Bit string generators are quite useful when you need to
|
||||
organize bit string streams:
|
||||
Bitstring generators are also supported and are very useful when you
|
||||
need to organize bitstring streams:
|
||||
|
||||
iex> pixels = <<213, 45, 132, 64, 76, 32, 76, 0, 0, 234, 32, 15>>
|
||||
iex> lc <<r::8, g::8, b::8>> inbits pixels, do: {r, g, b}
|
||||
iex> for <<r::8, g::8, b::8 <- pixels >>, do: {r, g, b}
|
||||
[{213,45,132},{64,76,32},{76,0,0},{234,32,15}]
|
||||
|
||||
Variable assignments in the `do` block of the comprehension
|
||||
are not reflected outside of the block.
|
||||
Variable assignments inside the comprehension, be it in generators,
|
||||
filters or inside the block, are not reflected outside of the
|
||||
comprehension.
|
||||
|
||||
Note: Unlike Erlang, Elixir comprehension filters
|
||||
never behave as guards when it comes to errors. Errors in
|
||||
list comprehensions will always be raised. Consider this
|
||||
Erlang example:
|
||||
## Into
|
||||
|
||||
erl> [I || I <- [1,2,3], hd(I)].
|
||||
[]
|
||||
In the examples above, the result returned by the comprehension was
|
||||
always a list. The returned result can be configured by passing an
|
||||
`:into` option, that accepts any structure as long as it implements
|
||||
the `Collectable` protocol.
|
||||
|
||||
In Elixir, it will raise:
|
||||
For example, we can use bitstring generators with the `:into` option
|
||||
to easily remove all spaces in a string:
|
||||
|
||||
iex> lc i inlist [1,2,3], hd(i), do: i
|
||||
** (ArgumentError) argument error
|
||||
|
||||
"""
|
||||
defmacro lc(args)
|
||||
|
||||
@doc """
|
||||
Defines a bit comprehension.
|
||||
|
||||
It follows the same syntax and behaviour as a list comprehension
|
||||
but expects each element returned to be a bitstring. For example,
|
||||
here is how to remove all spaces from a string:
|
||||
|
||||
iex> bc <<c>> inbits " hello world ", c != ? , do: <<c>>
|
||||
iex> for <<c <- " hello world ">>, c != ?\s, into: "", do: <<c>>
|
||||
"helloworld"
|
||||
|
||||
The `IO` module provides streams, that are both `Enumerable` and
|
||||
`Collectable`, here is an upcase echo server using comprehensions:
|
||||
|
||||
for line <- IO.stream(:stdio, :line), into: IO.stream(:stdio, :line) do
|
||||
String.upcase(line)
|
||||
end
|
||||
|
||||
"""
|
||||
defmacro bc(args)
|
||||
defmacro for(args)
|
||||
|
||||
@doc """
|
||||
Defines an anonymous function.
|
||||
@@ -1105,10 +1217,12 @@ defmodule Kernel.SpecialForms do
|
||||
defmacro __block__(args)
|
||||
|
||||
@doc """
|
||||
Captures an anonymous function.
|
||||
Captures or creates an anonymous function.
|
||||
|
||||
The most common format to capture a function is via module,
|
||||
name and arity:
|
||||
## Capture
|
||||
|
||||
The capture operator is most commonly used to capture a
|
||||
function with given name and arity from a module:
|
||||
|
||||
iex> fun = &Kernel.is_atom/1
|
||||
iex> fun.(:atom)
|
||||
@@ -1116,47 +1230,39 @@ defmodule Kernel.SpecialForms do
|
||||
iex> fun.("string")
|
||||
false
|
||||
|
||||
Local functions, including private ones, and imported ones
|
||||
can also be captured by omitting the module name:
|
||||
In the example above, we captured `Kernel.is_atom/1` as an
|
||||
anonymous function and then invoked it.
|
||||
|
||||
The capture operator can also be used to capture local functions,
|
||||
including private ones, and imported functions by omitting the
|
||||
module name:
|
||||
|
||||
&local_function/1
|
||||
|
||||
A capture also allows the captured functions to be partially
|
||||
applied, for example:
|
||||
## Anonymous functions
|
||||
|
||||
iex> fun = &is_record(&1, Range)
|
||||
iex> fun.(1..3)
|
||||
The capture operator can be also be used to partially apply
|
||||
functions, where `&1`, `&2` and so on can be used as value
|
||||
placeholders. For example:
|
||||
|
||||
iex> double = &(&1 * 2)
|
||||
iex> double.(2)
|
||||
4
|
||||
|
||||
In other words, `&(&1 * 2)` is equivalent to `fn x -> x * 2 end`.
|
||||
Another example using a local function:
|
||||
|
||||
iex> fun = &is_atom(&1)
|
||||
iex> fun.(:atom)
|
||||
true
|
||||
|
||||
In the example above, we use &1 as a placeholder, generating
|
||||
a function with one argument. We could also use `&2` and `&3`
|
||||
to delimit more arguments:
|
||||
|
||||
iex> fun = &is_record(&1, &2)
|
||||
iex> fun.(1..3, Range)
|
||||
true
|
||||
|
||||
Since operators are calls, they are also supported, although
|
||||
they require explicit parentheses around:
|
||||
|
||||
iex> fun = &(&1 + &2)
|
||||
iex> fun.(1, 2)
|
||||
3
|
||||
|
||||
And even more complex call expressions:
|
||||
The `&` operator can be used with more complex expressions:
|
||||
|
||||
iex> fun = &(&1 + &2 + &3)
|
||||
iex> fun.(1, 2, 3)
|
||||
6
|
||||
|
||||
Record-like calls are also allowed:
|
||||
|
||||
iex> fun = &(&1.first)
|
||||
iex> fun.(1..3)
|
||||
1
|
||||
|
||||
Remember tuple and lists are represented as calls in the AST and
|
||||
therefore are also allowed:
|
||||
As well as with lists and tuples:
|
||||
|
||||
iex> fun = &{&1, &2}
|
||||
iex> fun.(1, 2)
|
||||
@@ -1166,16 +1272,14 @@ defmodule Kernel.SpecialForms do
|
||||
iex> fun.(1, 2)
|
||||
[1|2]
|
||||
|
||||
Anything that is not a call is not allowed though, examples:
|
||||
The only restrictions when creating anonymous functions is that at
|
||||
least one placeholder must be present, i.e. it must contain at least
|
||||
`&1`:
|
||||
|
||||
# An atom is not a call
|
||||
&:foo
|
||||
|
||||
# A var is not a call
|
||||
var = 1
|
||||
# No placeholder fails to compile
|
||||
&var
|
||||
|
||||
# A block expression is not a call
|
||||
# Block expressions are also not supported
|
||||
&(foo(&1, &2); &3 + &4)
|
||||
|
||||
"""
|
||||
@@ -1273,7 +1377,7 @@ defmodule Kernel.SpecialForms do
|
||||
"""
|
||||
defmacro case(condition, blocks)
|
||||
|
||||
@doc """
|
||||
@doc ~S"""
|
||||
Evaluate the given expressions and handle any error, exit
|
||||
or throw that may have happened.
|
||||
|
||||
@@ -1286,10 +1390,10 @@ defmodule Kernel.SpecialForms do
|
||||
IO.puts "Invalid argument given"
|
||||
catch
|
||||
value ->
|
||||
IO.puts "caught \#{value}"
|
||||
IO.puts "caught #{value}"
|
||||
else
|
||||
value ->
|
||||
IO.puts "Success! The result was \#{value}"
|
||||
IO.puts "Success! The result was #{value}"
|
||||
after
|
||||
IO.puts "This is printed regardless if it failed or succeed"
|
||||
end
|
||||
@@ -1335,10 +1439,39 @@ defmodule Kernel.SpecialForms do
|
||||
x -> nil
|
||||
end
|
||||
|
||||
## Catching exits and Erlang errors
|
||||
## Erlang errors
|
||||
|
||||
The catch clause works exactly the same as in erlang. Therefore,
|
||||
one can also handle exits/errors coming from Erlang as below:
|
||||
Erlang errors are transformed into Elixir ones during rescue:
|
||||
|
||||
try do
|
||||
:erlang.error(:badarg)
|
||||
rescue
|
||||
ArgumentError -> :ok
|
||||
end
|
||||
|
||||
The most common Erlang errors will be transformed into their
|
||||
Elixir counter-part. Those which are not will be transformed
|
||||
into `ErlangError`:
|
||||
|
||||
try do
|
||||
:erlang.error(:unknown)
|
||||
rescue
|
||||
ErlangError -> :ok
|
||||
end
|
||||
|
||||
In fact, ErlangError can be used to rescue any error that is
|
||||
not an Elixir error proper. For example, it can be used to rescue
|
||||
the earlier `:badarg` error too, prior to transformation:
|
||||
|
||||
try do
|
||||
:erlang.error(:badarg)
|
||||
rescue
|
||||
ErlangError -> :ok
|
||||
end
|
||||
|
||||
## Catching throws and exits
|
||||
|
||||
The catch clause can be used to catch throws values and exits.
|
||||
|
||||
try do
|
||||
exit(1)
|
||||
@@ -1347,14 +1480,14 @@ defmodule Kernel.SpecialForms do
|
||||
end
|
||||
|
||||
try do
|
||||
error(:sample)
|
||||
throw(:sample)
|
||||
catch
|
||||
:error, :sample ->
|
||||
IO.puts "sample error"
|
||||
:throw, :sample ->
|
||||
IO.puts "sample thrown"
|
||||
end
|
||||
|
||||
Although the second form should be avoided in favor of raise/rescue
|
||||
control mechanisms.
|
||||
catch values also support `:error`, as in Erlang, although it is
|
||||
commonly avoided in favor of raise/rescue control mechanisms.
|
||||
|
||||
## Else clauses
|
||||
|
||||
@@ -1428,7 +1561,7 @@ defmodule Kernel.SpecialForms do
|
||||
:exit_b
|
||||
end
|
||||
|
||||
This means the VM nolonger needs to keep the stacktrace once inside
|
||||
This means the VM no longer needs to keep the stacktrace once inside
|
||||
an else clause and so tail recursion is possible when using a `try`
|
||||
with a tail call as the final call inside an else clause. The same
|
||||
is true for rescue and catch clauses.
|
||||
@@ -1437,7 +1570,7 @@ defmodule Kernel.SpecialForms do
|
||||
|
||||
Since an expression inside `try` may not have been evaluated
|
||||
due to an exception, any variable created inside `try` cannot
|
||||
be accessed externaly. For instance:
|
||||
be accessed externally. For instance:
|
||||
|
||||
try do
|
||||
x = 1
|
||||
@@ -1499,7 +1632,7 @@ defmodule Kernel.SpecialForms do
|
||||
end
|
||||
|
||||
The `after` clause can be specified even if there are no match clauses.
|
||||
There are two special cases for the timout value given to after
|
||||
There are two special cases for the timeout value given to `after`
|
||||
|
||||
* `:infinity` - The process should wait indefinitely for a matching
|
||||
message, this is the same as not using a timeout.
|
||||
|
||||
@@ -361,13 +361,13 @@ defmodule Kernel.Typespec do
|
||||
|> Enum.uniq
|
||||
|> Enum.map(&{ &1, { :var, meta, nil } })
|
||||
|
||||
result = if vars == [] do
|
||||
typespec_to_ast(result)
|
||||
else
|
||||
{ :when, meta, [typespec_to_ast(result), vars] }
|
||||
end
|
||||
spec = { :::, meta, [body, typespec_to_ast(result)] }
|
||||
|
||||
{ :::, meta, [body, result] }
|
||||
if vars == [] do
|
||||
spec
|
||||
else
|
||||
{ :when, meta, [spec, vars] }
|
||||
end
|
||||
end
|
||||
|
||||
def spec_to_ast(name, { :type, line, :fun, [] }) do
|
||||
@@ -376,23 +376,23 @@ defmodule Kernel.Typespec do
|
||||
|
||||
def spec_to_ast(name, { :type, line, :bounded_fun, [{ :type, _, :fun, [{ :type, _, :product, args }, result] }, constraints] }) do
|
||||
guards =
|
||||
lc {:type, _, :constraint, [{:atom, _, :is_subtype}, [{ :var, _, var }, type]]} inlist constraints do
|
||||
for { :type, _, :constraint, [{ :atom, _, :is_subtype }, [{ :var, _, var }, type]] } <- constraints do
|
||||
{ var, typespec_to_ast(type) }
|
||||
end
|
||||
|
||||
meta = [line: line]
|
||||
|
||||
vars = args ++ [result]
|
||||
|> Enum.flat_map(&collect_vars/1)
|
||||
|> Enum.uniq
|
||||
|> Kernel.--(Keyword.keys(guards))
|
||||
|> Enum.map(&{ &1, { :var, meta, nil } })
|
||||
|> Enum.flat_map(&collect_vars/1)
|
||||
|> Enum.uniq
|
||||
|> Kernel.--(Keyword.keys(guards))
|
||||
|> Enum.map(&{ &1, { :var, meta, nil } })
|
||||
|
||||
args = lc arg inlist args, do: typespec_to_ast(arg)
|
||||
args = for arg <- args, do: typespec_to_ast(arg)
|
||||
|
||||
{ :::, meta, [
|
||||
{ name, [line: line], args },
|
||||
{ :when, meta, [typespec_to_ast(result), guards ++ vars] }
|
||||
{ :when, meta, [
|
||||
{ :::, meta, [{ name, [line: line], args }, typespec_to_ast(result)] },
|
||||
guards ++ vars
|
||||
] }
|
||||
end
|
||||
|
||||
@@ -400,14 +400,14 @@ defmodule Kernel.Typespec do
|
||||
Converts a type clause back to Elixir AST.
|
||||
"""
|
||||
def type_to_ast({ { :record, record }, fields, args }) when is_atom(record) do
|
||||
fields = lc field inlist fields, do: typespec_to_ast(field)
|
||||
args = lc arg inlist args, do: typespec_to_ast(arg)
|
||||
fields = for field <- fields, do: typespec_to_ast(field)
|
||||
args = for arg <- args, do: typespec_to_ast(arg)
|
||||
type = { :{}, [], [record|fields] }
|
||||
quote do: unquote(record)(unquote_splicing(args)) :: unquote(type)
|
||||
end
|
||||
|
||||
def type_to_ast({ name, type, args }) do
|
||||
args = lc arg inlist args, do: typespec_to_ast(arg)
|
||||
args = for arg <- args, do: typespec_to_ast(arg)
|
||||
quote do: unquote(name)(unquote_splicing(args)) :: unquote(typespec_to_ast(type))
|
||||
end
|
||||
|
||||
@@ -424,7 +424,7 @@ defmodule Kernel.Typespec do
|
||||
def beam_typedocs(module) when is_atom(module) or is_binary(module) do
|
||||
case abstract_code(module) do
|
||||
{ :ok, abstract_code } ->
|
||||
type_docs = lc { :attribute, _, :typedoc, tup } inlist abstract_code, do: tup
|
||||
type_docs = for { :attribute, _, :typedoc, tup } <- abstract_code, do: tup
|
||||
List.flatten(type_docs)
|
||||
_ ->
|
||||
nil
|
||||
@@ -444,10 +444,10 @@ defmodule Kernel.Typespec do
|
||||
def beam_types(module) when is_atom(module) or is_binary(module) do
|
||||
case abstract_code(module) do
|
||||
{ :ok, abstract_code } ->
|
||||
exported_types = lc { :attribute, _, :export_type, types } inlist abstract_code, do: types
|
||||
exported_types = for { :attribute, _, :export_type, types } <- abstract_code, do: types
|
||||
exported_types = List.flatten(exported_types)
|
||||
|
||||
lc { :attribute, _, kind, { name, _, args } = type } inlist abstract_code, kind in [:opaque, :type] do
|
||||
for { :attribute, _, kind, { name, _, args } = type } <- abstract_code, kind in [:opaque, :type] do
|
||||
cond do
|
||||
kind == :opaque -> { :opaque, type }
|
||||
{ name, length(args) } in exported_types -> { :type, type }
|
||||
@@ -490,7 +490,7 @@ defmodule Kernel.Typespec do
|
||||
defp from_abstract_code(module, kind) do
|
||||
case abstract_code(module) do
|
||||
{ :ok, abstract_code } ->
|
||||
lc { :attribute, _, abs_kind, value } inlist abstract_code, kind == abs_kind, do: value
|
||||
for { :attribute, _, abs_kind, value } <- abstract_code, kind == abs_kind, do: value
|
||||
:error ->
|
||||
nil
|
||||
end
|
||||
@@ -498,7 +498,7 @@ defmodule Kernel.Typespec do
|
||||
|
||||
defp abstract_code(module) do
|
||||
case :beam_lib.chunks(abstract_code_beam(module), [:abstract_code]) do
|
||||
{:ok, { _, [{ :abstract_code, { _raw_abstract_v1, abstract_code } }] } } ->
|
||||
{ :ok, { _, [{ :abstract_code, { _raw_abstract_v1, abstract_code } }] } } ->
|
||||
{ :ok, abstract_code }
|
||||
_ ->
|
||||
:error
|
||||
@@ -524,13 +524,13 @@ defmodule Kernel.Typespec do
|
||||
if is_atom(args) do
|
||||
[]
|
||||
else
|
||||
lc(arg inlist args, do: variable(arg))
|
||||
for(arg <- args, do: variable(arg))
|
||||
end
|
||||
|
||||
vars = lc { :var, _, var } inlist args, do: var
|
||||
vars = for { :var, _, var } <- args, do: var
|
||||
spec = typespec(definition, vars, caller)
|
||||
|
||||
vars = lc { :var, _, _ } = var inlist args, do: var
|
||||
vars = for { :var, _, _ } = var <- args, do: var
|
||||
type = { name, spec, vars }
|
||||
|
||||
define_type(caller, kind, type)
|
||||
@@ -542,9 +542,17 @@ defmodule Kernel.Typespec do
|
||||
end
|
||||
|
||||
@doc false
|
||||
def defspec(type, { :::, meta, [{ name, _, args }, return_and_guard] }, caller) when is_atom(name) and name != ::: do
|
||||
|
||||
def defspec(type, { :when, _meta, [spec, guard] }, caller) do
|
||||
defspec(type, spec, guard, caller)
|
||||
end
|
||||
|
||||
def defspec(type, spec, caller) do
|
||||
defspec(type, spec, [], caller)
|
||||
end
|
||||
|
||||
defp defspec(type, { :::, meta, [{ name, _, args }, return] }, guard, caller) when is_atom(name) and name != ::: do
|
||||
if is_atom(args), do: args = []
|
||||
{ return, guard } = split_return_and_guard(return_and_guard)
|
||||
|
||||
unless Keyword.keyword?(guard) do
|
||||
guard = Macro.to_string(guard)
|
||||
@@ -564,24 +572,11 @@ defmodule Kernel.Typespec do
|
||||
code
|
||||
end
|
||||
|
||||
def defspec(_type, other, caller) do
|
||||
spec = Macro.to_string(other)
|
||||
defp defspec(_type, spec, _guard, caller) do
|
||||
spec = Macro.to_string(spec)
|
||||
compile_error caller, "invalid function type specification: #{spec}"
|
||||
end
|
||||
|
||||
defp split_return_and_guard({ :when, _, [return, guard] }) do
|
||||
{ return, guard }
|
||||
end
|
||||
|
||||
defp split_return_and_guard({ :|, meta, [left, right] }) do
|
||||
{ return, guard } = split_return_and_guard(right)
|
||||
{ { :|, meta, [left, return] }, guard }
|
||||
end
|
||||
|
||||
defp split_return_and_guard(other) do
|
||||
{ other, [] }
|
||||
end
|
||||
|
||||
defp guard_to_constraints(guard, vars, meta, caller) do
|
||||
line = line(meta)
|
||||
|
||||
@@ -630,7 +625,7 @@ defmodule Kernel.Typespec do
|
||||
end
|
||||
|
||||
defp typespec_to_ast({ :type, line, :tuple, args }) do
|
||||
args = lc arg inlist args, do: typespec_to_ast(arg)
|
||||
args = for arg <- args, do: typespec_to_ast(arg)
|
||||
{ :{}, [line: line], args }
|
||||
end
|
||||
|
||||
@@ -642,11 +637,26 @@ defmodule Kernel.Typespec do
|
||||
end
|
||||
|
||||
defp typespec_to_ast({ :type, _line, :list, args }) do
|
||||
lc arg inlist args, do: typespec_to_ast(arg)
|
||||
for arg <- args, do: typespec_to_ast(arg)
|
||||
end
|
||||
|
||||
defp typespec_to_ast({ :type, line, :map, fields }) do
|
||||
fields = Enum.map fields, fn { :type, _, :map_field_assoc, k, v } ->
|
||||
{ typespec_to_ast(k), typespec_to_ast(v) }
|
||||
end
|
||||
|
||||
{ struct, fields } = Keyword.pop(fields, :__struct__)
|
||||
map = { :%{}, [line: line], fields }
|
||||
|
||||
if struct do
|
||||
{ :%, [line: line], [struct, map] }
|
||||
else
|
||||
map
|
||||
end
|
||||
end
|
||||
|
||||
defp typespec_to_ast({ :type, line, :binary, [arg1, arg2] }) do
|
||||
[arg1, arg2] = lc arg inlist [arg1, arg2], do: typespec_to_ast(arg)
|
||||
[arg1, arg2] = for arg <- [arg1, arg2], do: typespec_to_ast(arg)
|
||||
cond do
|
||||
arg2 == 0 ->
|
||||
quote line: line, do: <<_ :: unquote(arg1)>>
|
||||
@@ -658,12 +668,12 @@ defmodule Kernel.Typespec do
|
||||
end
|
||||
|
||||
defp typespec_to_ast({ :type, line, :union, args }) do
|
||||
args = lc arg inlist args, do: typespec_to_ast(arg)
|
||||
args = for arg <- args, do: typespec_to_ast(arg)
|
||||
Enum.reduce Enum.reverse(args), fn(arg, expr) -> { :|, [line: line], [arg, expr] } end
|
||||
end
|
||||
|
||||
defp typespec_to_ast({ :type, line, :fun, [{:type, _, :product, args}, result] }) do
|
||||
args = lc arg inlist args, do: typespec_to_ast(arg)
|
||||
args = for arg <- args, do: typespec_to_ast(arg)
|
||||
[{ :->, [line: line], [args, typespec_to_ast(result)] }]
|
||||
end
|
||||
|
||||
@@ -680,7 +690,7 @@ defmodule Kernel.Typespec do
|
||||
end
|
||||
|
||||
defp typespec_to_ast({ :type, line, name, args }) do
|
||||
args = lc arg inlist args, do: typespec_to_ast(arg)
|
||||
args = for arg <- args, do: typespec_to_ast(arg)
|
||||
{ name, [line: line], args }
|
||||
end
|
||||
|
||||
@@ -702,7 +712,7 @@ defmodule Kernel.Typespec do
|
||||
end
|
||||
|
||||
defp typespec_to_ast({ :remote_type, line, [mod, name, args] }) do
|
||||
args = lc arg inlist args, do: typespec_to_ast(arg)
|
||||
args = for arg <- args, do: typespec_to_ast(arg)
|
||||
dot = { :., [line: line], [typespec_to_ast(mod), typespec_to_ast(name)] }
|
||||
{ dot, [line: line], args }
|
||||
end
|
||||
@@ -752,7 +762,7 @@ defmodule Kernel.Typespec do
|
||||
# Handle unions
|
||||
defp typespec({ :|, meta, [_, _] } = exprs, vars, caller) do
|
||||
exprs = collect_union(exprs)
|
||||
union = lc e inlist exprs, do: typespec(e, vars, caller)
|
||||
union = for e <- exprs, do: typespec(e, vars, caller)
|
||||
{ :type, line(meta), :union, union }
|
||||
end
|
||||
|
||||
@@ -769,6 +779,18 @@ defmodule Kernel.Typespec do
|
||||
{:type, line(meta), :binary, [{:integer, line(meta1), base}, {:integer, line(meta2), 0}]}
|
||||
end
|
||||
|
||||
## Handle maps and structs
|
||||
defp typespec({:%{}, meta, fields}, vars, caller) do
|
||||
fields = Enum.map(fields, fn { k, v } ->
|
||||
{:type, line(meta), :map_field_assoc, typespec(k, vars, caller), typespec(v, vars, caller)}
|
||||
end)
|
||||
{:type, line(meta), :map, fields}
|
||||
end
|
||||
|
||||
defp typespec({:%, _, [name, {:%{}, meta, fields}]}, vars, caller) do
|
||||
typespec({:%{}, meta, [{:__struct__, name}|fields]}, vars, caller)
|
||||
end
|
||||
|
||||
# Handle ranges
|
||||
defp typespec({:.., meta, args}, vars, caller) do
|
||||
typespec({:range, meta, args}, vars, caller)
|
||||
@@ -824,7 +846,7 @@ defmodule Kernel.Typespec do
|
||||
end
|
||||
|
||||
defp typespec({:{}, meta, t}, vars, caller) when is_list(t) do
|
||||
args = lc e inlist t, do: typespec(e, vars, caller)
|
||||
args = for e <- t, do: typespec(e, vars, caller)
|
||||
{ :type, line(meta), :tuple, args }
|
||||
end
|
||||
|
||||
@@ -850,7 +872,7 @@ defmodule Kernel.Typespec do
|
||||
defp typespec({:string, meta, arguments}, vars, caller) do
|
||||
:elixir_errors.warn caller.line, caller.file, "string() type use is discouraged. For character lists, use " <>
|
||||
"char_list() type, for strings, String.t()\n#{Exception.format_stacktrace(caller.stacktrace)}"
|
||||
arguments = lc arg inlist arguments, do: typespec(arg, vars, caller)
|
||||
arguments = for arg <- arguments, do: typespec(arg, vars, caller)
|
||||
{ :type, line(meta), :string, arguments }
|
||||
end
|
||||
|
||||
@@ -863,7 +885,7 @@ defmodule Kernel.Typespec do
|
||||
end
|
||||
|
||||
defp typespec({name, meta, arguments}, vars, caller) do
|
||||
arguments = lc arg inlist arguments, do: typespec(arg, vars, caller)
|
||||
arguments = for arg <- arguments, do: typespec(arg, vars, caller)
|
||||
{ :type, line(meta), name, arguments }
|
||||
end
|
||||
|
||||
@@ -903,7 +925,7 @@ defmodule Kernel.Typespec do
|
||||
end
|
||||
|
||||
defp remote_type({remote, meta, name, arguments}, vars, caller) do
|
||||
arguments = lc arg inlist arguments, do: typespec(arg, vars, caller)
|
||||
arguments = for arg <- arguments, do: typespec(arg, vars, caller)
|
||||
{ :remote_type, line(meta), [ remote, name, arguments ] }
|
||||
end
|
||||
|
||||
@@ -927,7 +949,7 @@ defmodule Kernel.Typespec do
|
||||
end
|
||||
|
||||
defp fn_args(meta, args, vars, caller) do
|
||||
args = lc arg inlist args, do: typespec(arg, vars, caller)
|
||||
args = for arg <- args, do: typespec(arg, vars, caller)
|
||||
{ :type, line(meta), :product, args }
|
||||
end
|
||||
|
||||
|
||||
+53
-42
@@ -36,6 +36,7 @@ defmodule Keyword do
|
||||
"""
|
||||
@spec from_enum(Enum.t) :: t
|
||||
def from_enum(enum) do
|
||||
IO.write :stderr, "Keyword.from_enum/1 is deprecated, please use Enum.into/2 instead\n#{Exception.format_stacktrace}"
|
||||
Enum.to_list(enum)
|
||||
end
|
||||
|
||||
@@ -59,8 +60,11 @@ defmodule Keyword do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Creates a Keyword from an enumerable. Similar to dicts,
|
||||
duplicated entries are removed, the latest one prevails.
|
||||
Creates a keyword from an enumerable.
|
||||
|
||||
Duplicated entries are removed, the latest one prevails.
|
||||
I.e. differently from `Enum.into(enumerable, [])`,
|
||||
`Keyword.new(enumerable)` guarantees the keys are unique.
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -76,9 +80,11 @@ defmodule Keyword do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Creates a Keyword from an enumerable with the
|
||||
help of the transformation function. Duplicated
|
||||
entries are removed, the latest one prevails.
|
||||
Creates a keyword from an enumerable via the transformation function.
|
||||
|
||||
Duplicated entries are removed, the latest one prevails.
|
||||
I.e. differently from `Enum.into(enumerable, [], fun)`,
|
||||
`Keyword.new(enumerable, fun)` guarantees the keys are unique.
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -116,7 +122,7 @@ defmodule Keyword do
|
||||
"""
|
||||
@spec get(t, key) :: value
|
||||
@spec get(t, key, value) :: value
|
||||
def get(keywords, key, default \\ nil) when is_atom(key) do
|
||||
def get(keywords, key, default \\ nil) when is_list(keywords) and is_atom(key) do
|
||||
case :lists.keyfind(key, 1, keywords) do
|
||||
{ ^key, value } -> value
|
||||
false -> default
|
||||
@@ -137,7 +143,7 @@ defmodule Keyword do
|
||||
|
||||
"""
|
||||
@spec fetch(t, key) :: value
|
||||
def fetch(keywords, key) when is_atom(key) do
|
||||
def fetch(keywords, key) when is_list(keywords) and is_atom(key) do
|
||||
case :lists.keyfind(key, 1, keywords) do
|
||||
{ ^key, value } -> { :ok, value }
|
||||
false -> :error
|
||||
@@ -154,14 +160,14 @@ defmodule Keyword do
|
||||
1
|
||||
|
||||
iex> Keyword.fetch!([a: 1], :b)
|
||||
** (KeyError) key not found: :b
|
||||
** (KeyError) key :b not found in: [a: 1]
|
||||
|
||||
"""
|
||||
@spec fetch!(t, key) :: value | no_return
|
||||
def fetch!(keywords, key) when is_atom(key) do
|
||||
def fetch!(keywords, key) when is_list(keywords) and is_atom(key) do
|
||||
case :lists.keyfind(key, 1, keywords) do
|
||||
{ ^key, value } -> value
|
||||
false -> raise(KeyError, key: key)
|
||||
false -> raise(KeyError, key: key, term: keywords)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -175,8 +181,8 @@ defmodule Keyword do
|
||||
|
||||
"""
|
||||
@spec get_values(t, key) :: [value]
|
||||
def get_values(keywords, key) when is_atom(key) do
|
||||
lc { k, v } inlist keywords, key == k, do: v
|
||||
def get_values(keywords, key) when is_list(keywords) and is_atom(key) do
|
||||
for { k, v } <- keywords, key == k, do: v
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -193,8 +199,8 @@ defmodule Keyword do
|
||||
|
||||
"""
|
||||
@spec keys(t) :: [key]
|
||||
def keys(keywords) do
|
||||
lc { key, _ } inlist keywords, do: key
|
||||
def keys(keywords) when is_list(keywords) do
|
||||
for { key, _ } <- keywords, do: key
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -207,8 +213,8 @@ defmodule Keyword do
|
||||
|
||||
"""
|
||||
@spec values(t) :: [value]
|
||||
def values(keywords) do
|
||||
lc { _, value } inlist keywords, do: value
|
||||
def values(keywords) when is_list(keywords) do
|
||||
for { _, value } <- keywords, do: value
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -228,8 +234,8 @@ defmodule Keyword do
|
||||
|
||||
"""
|
||||
@spec delete(t, key, value) :: t
|
||||
def delete(keywords, key, value) when is_atom(key) do
|
||||
lc { k, v } = tuple inlist keywords, key != k or value != v, do: tuple
|
||||
def delete(keywords, key, value) when is_list(keywords) and is_atom(key) do
|
||||
for { k, v } = tuple <- keywords, key != k or value != v, do: tuple
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -251,8 +257,8 @@ defmodule Keyword do
|
||||
|
||||
"""
|
||||
@spec delete(t, key) :: t
|
||||
def delete(keywords, key) when is_atom(key) do
|
||||
lc { k, _ } = tuple inlist keywords, key != k, do: tuple
|
||||
def delete(keywords, key) when is_list(keywords) and is_atom(key) do
|
||||
for { k, _ } = tuple <- keywords, key != k, do: tuple
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -269,7 +275,7 @@ defmodule Keyword do
|
||||
|
||||
"""
|
||||
@spec delete_first(t, key) :: t
|
||||
def delete_first(keywords, key) when is_atom(key) do
|
||||
def delete_first(keywords, key) when is_list(keywords) and is_atom(key) do
|
||||
:lists.keydelete(key, 1, keywords)
|
||||
end
|
||||
|
||||
@@ -289,7 +295,7 @@ defmodule Keyword do
|
||||
|
||||
"""
|
||||
@spec put(t, key, value) :: t
|
||||
def put(keywords, key, value) when is_atom(key) do
|
||||
def put(keywords, key, value) when is_list(keywords) and is_atom(key) do
|
||||
[{key, value}|delete(keywords, key)]
|
||||
end
|
||||
|
||||
@@ -298,6 +304,7 @@ defmodule Keyword do
|
||||
already exists.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Keyword.put_new([a: 1], :b, 2)
|
||||
[b: 2, a: 1]
|
||||
|
||||
@@ -306,7 +313,7 @@ defmodule Keyword do
|
||||
|
||||
"""
|
||||
@spec put_new(t, key, value) :: t
|
||||
def put_new(keywords, key, value) when is_atom(key) do
|
||||
def put_new(keywords, key, value) when is_list(keywords) and is_atom(key) do
|
||||
case :lists.keyfind(key, 1, keywords) do
|
||||
{ ^key, _ } -> keywords
|
||||
false -> [{key, value}|keywords]
|
||||
@@ -340,7 +347,7 @@ defmodule Keyword do
|
||||
"""
|
||||
@spec merge(t, t) :: t
|
||||
def merge(d1, d2) when is_list(d1) and is_list(d2) do
|
||||
d2 ++ lc({ k, _ } = tuple inlist d1, not has_key?(d2, k), do: tuple)
|
||||
d2 ++ for({ k, _ } = tuple <- d1, not has_key?(d2, k), do: tuple)
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -351,7 +358,7 @@ defmodule Keyword do
|
||||
|
||||
iex> Keyword.merge([a: 1, b: 2], [a: 3, d: 4], fn (_k, v1, v2) ->
|
||||
...> v1 + v2
|
||||
iex> end)
|
||||
...> end)
|
||||
[a: 4, b: 2, d: 4]
|
||||
|
||||
"""
|
||||
@@ -381,7 +388,7 @@ defmodule Keyword do
|
||||
|
||||
"""
|
||||
@spec has_key?(t, key) :: boolean
|
||||
def has_key?(keywords, key) when is_atom(key) do
|
||||
def has_key?(keywords, key) when is_list(keywords) and is_atom(key) do
|
||||
:lists.keymember(key, 1, keywords)
|
||||
end
|
||||
|
||||
@@ -395,20 +402,24 @@ defmodule Keyword do
|
||||
[a: 2]
|
||||
|
||||
iex> Keyword.update!([a: 1], :b, &(&1 * 2))
|
||||
** (KeyError) key not found: :b
|
||||
** (KeyError) key :b not found in: [a: 1]
|
||||
|
||||
"""
|
||||
@spec update!(t, key, (value -> value)) :: t | no_return
|
||||
def update!([{key, value}|keywords], key, fun) do
|
||||
def update!(keywords, key, fun) do
|
||||
update!(keywords, key, fun, keywords)
|
||||
end
|
||||
|
||||
defp update!([{key, value}|keywords], key, fun, _dict) do
|
||||
[{key, fun.(value)}|delete(keywords, key)]
|
||||
end
|
||||
|
||||
def update!([{_, _} = e|keywords], key, fun) do
|
||||
[e|update!(keywords, key, fun)]
|
||||
defp update!([{_, _} = e|keywords], key, fun, dict) do
|
||||
[e|update!(keywords, key, fun, dict)]
|
||||
end
|
||||
|
||||
def update!([], key, _fun) when is_atom(key) do
|
||||
raise(KeyError, key: key)
|
||||
defp update!([], key, _fun, dict) when is_atom(key) do
|
||||
raise(KeyError, key: key, term: dict)
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -452,10 +463,10 @@ defmodule Keyword do
|
||||
{ [a: 1, c: 3, a: 5], [b: 2, d: 4] }
|
||||
|
||||
"""
|
||||
def split(dict, keys) do
|
||||
def split(keywords, keys) when is_list(keywords) do
|
||||
acc = { [], [] }
|
||||
|
||||
{ take, drop } = Enum.reduce dict, acc, fn({ k, v }, { take, drop }) ->
|
||||
{ take, drop } = Enum.reduce keywords, acc, fn({ k, v }, { take, drop }) ->
|
||||
case k in keys do
|
||||
true -> { [{k, v}|take], drop }
|
||||
false -> { take, [{k, v}|drop] }
|
||||
@@ -480,8 +491,8 @@ defmodule Keyword do
|
||||
[a: 1, c: 3, a: 5]
|
||||
|
||||
"""
|
||||
def take(dict, keys) do
|
||||
lc { k, _ } = tuple inlist dict, k in keys, do: tuple
|
||||
def take(keywords, keys) when is_list(keywords) do
|
||||
for { k, _ } = tuple <- keywords, k in keys, do: tuple
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -499,8 +510,8 @@ defmodule Keyword do
|
||||
[a: 1, c: 3, a: 5]
|
||||
|
||||
"""
|
||||
def drop(dict, keys) do
|
||||
lc { k, _ } = tuple inlist dict, not k in keys, do: tuple
|
||||
def drop(keywords, keys) when is_list(keywords) do
|
||||
for { k, _ } = tuple <- keywords, not k in keys, do: tuple
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -528,8 +539,8 @@ defmodule Keyword do
|
||||
{1,[]}
|
||||
|
||||
"""
|
||||
def pop(dict, key, default \\ nil) do
|
||||
{ get(dict, key, default), delete(dict, key) }
|
||||
def pop(keywords, key, default \\ nil) when is_list(keywords) do
|
||||
{ get(keywords, key, default), delete(keywords, key) }
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -557,7 +568,7 @@ defmodule Keyword do
|
||||
{1,[a: 2]}
|
||||
|
||||
"""
|
||||
def pop_first(dict, key, default \\ nil) do
|
||||
{ get(dict, key, default), delete_first(dict, key) }
|
||||
def pop_first(keywords, key, default \\ nil) when is_list(keywords) do
|
||||
{ get(keywords, key, default), delete_first(keywords, key) }
|
||||
end
|
||||
end
|
||||
|
||||
@@ -47,6 +47,7 @@ defmodule List do
|
||||
|
||||
iex> List.duplicate([1, 2], 2)
|
||||
[[1,2],[1,2]]
|
||||
|
||||
"""
|
||||
@spec duplicate(elem, non_neg_integer) :: [elem] when elem: var
|
||||
def duplicate(elem, n) do
|
||||
@@ -123,8 +124,10 @@ defmodule List do
|
||||
|
||||
iex> List.first([])
|
||||
nil
|
||||
|
||||
iex> List.first([1])
|
||||
1
|
||||
|
||||
iex> List.first([1, 2, 3])
|
||||
1
|
||||
|
||||
@@ -140,8 +143,10 @@ defmodule List do
|
||||
|
||||
iex> List.last([])
|
||||
nil
|
||||
|
||||
iex> List.last([1])
|
||||
1
|
||||
|
||||
iex> List.last([1, 2, 3])
|
||||
3
|
||||
|
||||
|
||||
+24
-19
@@ -15,27 +15,24 @@ defmodule ListDict do
|
||||
"""
|
||||
def new, do: []
|
||||
|
||||
@doc """
|
||||
Creates a new `ListDict` from the given pairs.
|
||||
"""
|
||||
@doc false
|
||||
def new(pairs) do
|
||||
IO.write :stderr, "ListDict.new/1 is deprecated, please use Enum.into/2 instead\n#{Exception.format_stacktrace}"
|
||||
Enum.to_list pairs
|
||||
end
|
||||
|
||||
@doc """
|
||||
Creates a new `ListDict` from the given pairs
|
||||
via the given transformation function.
|
||||
"""
|
||||
@doc false
|
||||
def new(list, transform) when is_function(transform) do
|
||||
IO.write :stderr, "ListDict.new/2 is deprecated, please use Enum.into/3 instead\n#{Exception.format_stacktrace}"
|
||||
Enum.map list, transform
|
||||
end
|
||||
|
||||
def keys(dict) do
|
||||
lc { key, _ } inlist dict, do: key
|
||||
for { key, _ } <- dict, do: key
|
||||
end
|
||||
|
||||
def values(dict) do
|
||||
lc { _, value } inlist dict, do: value
|
||||
for { _, value } <- dict, do: value
|
||||
end
|
||||
|
||||
def size(dict) do
|
||||
@@ -60,7 +57,7 @@ defmodule ListDict do
|
||||
def fetch!(dict, key) do
|
||||
case fetch(dict, key) do
|
||||
{ :ok, value } -> value
|
||||
:error -> raise(KeyError, key: key)
|
||||
:error -> raise(KeyError, key: key, term: dict)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -105,23 +102,27 @@ defmodule ListDict do
|
||||
end
|
||||
|
||||
def take(dict, keys) do
|
||||
lc { k, _ } = tuple inlist dict, k in keys, do: tuple
|
||||
for { k, _ } = tuple <- dict, k in keys, do: tuple
|
||||
end
|
||||
|
||||
def drop(dict, keys) do
|
||||
lc { k, _ } = tuple inlist dict, not k in keys, do: tuple
|
||||
for { k, _ } = tuple <- dict, not k in keys, do: tuple
|
||||
end
|
||||
|
||||
def update!([{key, value}|dict], key, fun) do
|
||||
[{key, fun.(value)}|delete(dict, key)]
|
||||
def update!(list, key, fun) do
|
||||
update!(list, key, fun, list)
|
||||
end
|
||||
|
||||
def update!([{_, _} = e|dict], key, fun) do
|
||||
[e|update!(dict, key, fun)]
|
||||
defp update!([{key, value}|list], key, fun, _dict) do
|
||||
[{key, fun.(value)}|delete(list, key)]
|
||||
end
|
||||
|
||||
def update!([], key, _fun) do
|
||||
raise(KeyError, key: key)
|
||||
defp update!([{_, _} = e|list], key, fun, dict) do
|
||||
[e|update!(list, key, fun, dict)]
|
||||
end
|
||||
|
||||
defp update!([], key, _fun, dict) do
|
||||
raise(KeyError, key: key, term: dict)
|
||||
end
|
||||
|
||||
def update([{key, value}|dict], key, _initial, fun) do
|
||||
@@ -136,12 +137,16 @@ defmodule ListDict do
|
||||
[{key, initial}]
|
||||
end
|
||||
|
||||
def empty(_dict), do: []
|
||||
def empty(_dict) do
|
||||
IO.write :stderr, "ListDict.empty/1 is deprecated, please use Collectable.empty/1 instead\n#{Exception.format_stacktrace}"
|
||||
[]
|
||||
end
|
||||
|
||||
def equal?(dict, other) do
|
||||
:lists.keysort(1, dict) === :lists.keysort(1, other)
|
||||
end
|
||||
|
||||
@doc false
|
||||
def reduce(_, { :halt, acc }, _fun), do: { :halted, acc }
|
||||
def reduce(list, { :suspend, acc }, fun), do: { :suspended, acc, &reduce(list, &1, fun) }
|
||||
def reduce([], { :cont, acc }, _fun), do: { :done, acc }
|
||||
|
||||
+46
-12
@@ -13,7 +13,7 @@ defmodule Macro do
|
||||
|
||||
@binary_ops [:===, :!==,
|
||||
:==, :!=, :<=, :>=,
|
||||
:&&, :||, :<>, :++, :--, ://, :\\, :::, :<-, :.., :|>, :=~,
|
||||
:&&, :||, :<>, :++, :--, :\\, :::, :<-, :.., :|>, :=~,
|
||||
:<, :>, :->,
|
||||
:+, :-, :*, :/, :=, :|, :.,
|
||||
:and, :or, :xor, :when, :in, :inlist, :inbits,
|
||||
@@ -30,7 +30,7 @@ defmodule Macro do
|
||||
@spec binary_op_props(atom) :: { :left | :right, precedence :: integer }
|
||||
defp binary_op_props(o) do
|
||||
case o do
|
||||
o when o in [:<-, :inlist, :inbits, ://, :\\, :::] -> {:left, 40}
|
||||
o when o in [:<-, :inlist, :inbits, :\\, :::] -> {:left, 40}
|
||||
:| -> {:right, 50}
|
||||
:when -> {:right, 70}
|
||||
:= -> {:right, 80}
|
||||
@@ -113,7 +113,7 @@ defmodule Macro do
|
||||
end
|
||||
|
||||
def update_meta(list, fun) when is_list(list) do
|
||||
lc x inlist list, do: update_meta(x, fun)
|
||||
for x <- list, do: update_meta(x, fun)
|
||||
end
|
||||
|
||||
def update_meta(other, _fun) do
|
||||
@@ -333,6 +333,18 @@ defmodule Macro do
|
||||
fun.(ast, tuple)
|
||||
end
|
||||
|
||||
# Map containers
|
||||
def to_string({ :%{}, _, args } = ast, fun) do
|
||||
map = "%{" <> map_to_string(args, fun) <> "}"
|
||||
fun.(ast, map)
|
||||
end
|
||||
|
||||
def to_string({ :%, _, [structname, map] } = ast, fun) do
|
||||
{ :%{}, _, args } = map
|
||||
struct = "%" <> to_string(structname, fun) <> "{" <> map_to_string(args, fun) <> "}"
|
||||
fun.(ast, struct)
|
||||
end
|
||||
|
||||
# Fn keyword
|
||||
def to_string({ :fn, _, [{ :->, _, [_, tuple] }] = arrow } = ast, fun)
|
||||
when not is_tuple(tuple) or elem(tuple, 0) != :__block__ do
|
||||
@@ -376,6 +388,11 @@ defmodule Macro do
|
||||
end
|
||||
|
||||
# Unary ops
|
||||
def to_string({ unary, _, [{ binary, _, [_, _] } = arg] } = ast, fun)
|
||||
when unary in unquote(@unary_ops) and binary in unquote(@binary_ops) do
|
||||
fun.(ast, atom_to_binary(unary) <> "(" <> to_string(arg, fun) <> ")")
|
||||
end
|
||||
|
||||
def to_string({ :not, _, [arg] } = ast, fun) do
|
||||
fun.(ast, "not " <> to_string(arg, fun))
|
||||
end
|
||||
@@ -481,12 +498,29 @@ defmodule Macro do
|
||||
|
||||
defp block_to_string(other, fun), do: to_string(other, fun)
|
||||
|
||||
defp map_to_string([{:|, _, [update_map, update_args]}], fun) do
|
||||
to_string(update_map, fun) <> " | " <> map_to_string(update_args, fun)
|
||||
end
|
||||
|
||||
defp map_to_string(list, fun) do
|
||||
cond do
|
||||
Keyword.keyword?(list) -> kw_list_to_string(list, fun)
|
||||
true -> map_list_to_string(list, fun)
|
||||
end
|
||||
end
|
||||
|
||||
defp kw_list_to_string(list, fun) do
|
||||
Enum.map_join(list, ", ", fn { key, value } ->
|
||||
atom_to_binary(key) <> ": " <> to_string(value, fun)
|
||||
end)
|
||||
end
|
||||
|
||||
defp map_list_to_string(list, fun) do
|
||||
Enum.map_join(list, ", ", fn { key, value } ->
|
||||
to_string(key, fun) <> " => " <> to_string(value, fun)
|
||||
end)
|
||||
end
|
||||
|
||||
defp parenthise(expr, fun) do
|
||||
"(" <> to_string(expr, fun) <> ")"
|
||||
end
|
||||
@@ -524,11 +558,11 @@ defmodule Macro do
|
||||
end
|
||||
|
||||
defp adjust_new_lines(block, replacement) do
|
||||
bc <<x>> inbits block do
|
||||
<< case x == ?\n do
|
||||
for <<x <- block>>, into: "" do
|
||||
case x == ?\n do
|
||||
true -> replacement
|
||||
false -> <<x>>
|
||||
end :: binary >>
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -618,7 +652,7 @@ defmodule Macro do
|
||||
:elixir_lexical.record_remote(receiver, env.lexical_tracker)
|
||||
{ receiver, true }
|
||||
aliases ->
|
||||
aliases = lc alias inlist aliases, do: elem(do_expand_once(alias, env), 0)
|
||||
aliases = for alias <- aliases, do: elem(do_expand_once(alias, env), 0)
|
||||
|
||||
case :lists.all(&is_atom/1, aliases) do
|
||||
true ->
|
||||
@@ -720,11 +754,11 @@ defmodule Macro do
|
||||
defp do_expand_once(other, _env), do: { other, false }
|
||||
|
||||
@doc """
|
||||
Receives an AST node and expands it until it no longer represents
|
||||
a macro.
|
||||
Receives an AST node and expands it until it can no longer
|
||||
be expanded.
|
||||
|
||||
Check `expand_once/2` for more information on how
|
||||
expansion works.
|
||||
This function uses `expand_once/2` under the hood. Check
|
||||
`expand_once/2` for more information and exmaples.
|
||||
"""
|
||||
def expand(tree, env) do
|
||||
expand_until({ tree, true }, env)
|
||||
@@ -750,7 +784,7 @@ defmodule Macro do
|
||||
do_safe_term(terms) || :ok
|
||||
end
|
||||
|
||||
defp do_safe_term({ local, _, terms }) when local in [:{}, :__aliases__] do
|
||||
defp do_safe_term({ local, _, terms }) when local in [:{}, :%{}, :__aliases__] do
|
||||
do_safe_term(terms)
|
||||
end
|
||||
|
||||
|
||||
@@ -0,0 +1,116 @@
|
||||
defmodule Map do
|
||||
@moduledoc """
|
||||
A Dict implementation that works on maps.
|
||||
|
||||
Maps are key-value stores where keys are compared using
|
||||
the match operator (`===`). Maps can be created with
|
||||
the `%{}` special form defined in the `Kernel.SpecialForms`
|
||||
module.
|
||||
|
||||
For more information about the functions in this module and
|
||||
their APIs, please consult the `Dict` module.
|
||||
"""
|
||||
|
||||
use Dict.Behaviour
|
||||
|
||||
defdelegate [keys(map), values(map), size(map), merge(map1, map2)], to: :maps
|
||||
|
||||
@doc """
|
||||
Returns a new empty map.
|
||||
"""
|
||||
def new, do: %{}
|
||||
|
||||
@doc """
|
||||
Creates a new map from the given pairs.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Map.new [{:b, 1}, {:a, 2}]
|
||||
%{a: 2, b: 1}
|
||||
|
||||
"""
|
||||
def new(pairs) do
|
||||
:maps.from_list pairs
|
||||
end
|
||||
|
||||
@doc """
|
||||
Creates a new map from the given pairs
|
||||
via the given transformation function.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Map.new ["a", "b"], fn x -> {x, x} end
|
||||
%{"a" => "a", "b" => "b"}
|
||||
|
||||
"""
|
||||
def new(list, transform) when is_function(transform) do
|
||||
Enum.map(list, transform) |> :maps.from_list
|
||||
end
|
||||
|
||||
def has_key?(map, key), do: :maps.is_key(key, map)
|
||||
|
||||
def fetch(map, key), do: :maps.find(key, map)
|
||||
|
||||
def pop(map, key, default \\ nil) do
|
||||
{ get(map, key, default), delete(map, key) }
|
||||
end
|
||||
|
||||
def put(map, key, val) do
|
||||
:maps.put(key, val, map)
|
||||
end
|
||||
|
||||
def put_new(map, key, val) do
|
||||
case has_key?(map, key) do
|
||||
true -> map
|
||||
false -> :maps.put(key, val, map)
|
||||
end
|
||||
end
|
||||
|
||||
def delete(map, key), do: :maps.remove(key, map)
|
||||
|
||||
def merge(map1, map2, callback) do
|
||||
:maps.fold fn k, v2, acc ->
|
||||
update(acc, k, v2, fn(v1) -> callback.(k, v1, v2) end)
|
||||
end, map1, map2
|
||||
end
|
||||
|
||||
def split(map, keys) do
|
||||
acc = { %{}, %{} }
|
||||
|
||||
:maps.fold fn k, v, { take, drop } ->
|
||||
if k in keys do
|
||||
{ put(take, k, v), drop }
|
||||
else
|
||||
{ take, put(drop, k, v) }
|
||||
end
|
||||
end, acc, map
|
||||
end
|
||||
|
||||
def update!(map, key, fun) do
|
||||
case :maps.find(key, map) do
|
||||
:error ->
|
||||
raise(KeyError, key: key, term: map)
|
||||
{ :ok, val } ->
|
||||
:maps.put(key, fun.(val), map)
|
||||
end
|
||||
end
|
||||
|
||||
def update(map, key, initial, fun) do
|
||||
case :maps.find(key, map) do
|
||||
:error ->
|
||||
:maps.put(key, initial, map)
|
||||
{ :ok, val } ->
|
||||
:maps.put(key, fun.(val), map)
|
||||
end
|
||||
end
|
||||
|
||||
def empty(_) do
|
||||
IO.write :stderr, "Map.empty/1 is deprecated, please use Collectable.empty/1 instead\n#{Exception.format_stacktrace}"
|
||||
%{}
|
||||
end
|
||||
|
||||
def equal?(map, map), do: true
|
||||
def equal?(_, _), do: false
|
||||
|
||||
def to_list(map), do: :maps.to_list map
|
||||
end
|
||||
+50
-40
@@ -11,7 +11,7 @@ defmodule Module do
|
||||
documentation, add, delete and register attributes and so forth.
|
||||
|
||||
After a module is compiled, using many of the functions in
|
||||
this module will raise errors, since it is out of their purpose
|
||||
this module will raise errors, since it is out of their scope
|
||||
to inspect runtime data. Most of the runtime data can be inspected
|
||||
via the `__info__(attr)` function attached to each compiled module.
|
||||
|
||||
@@ -51,7 +51,7 @@ defmodule Module do
|
||||
When just a module is provided, the function/macro is assumed to be
|
||||
`__before_compile__/1`.
|
||||
|
||||
Note: differently from `@after_compile`, the callback function/macro must
|
||||
Note: unlike `@after_compile`, the callback function/macro must
|
||||
be placed in a separate module (because when the callback is invoked,
|
||||
the current module does not yet exist).
|
||||
|
||||
@@ -164,7 +164,7 @@ defmodule Module do
|
||||
|
||||
* `@on_definition`
|
||||
|
||||
A hook that will be invoked after each function or macro in the current
|
||||
A hook that will be invoked when each function or macro in the current
|
||||
module is defined. Useful when annotating functions.
|
||||
|
||||
Accepts a module or a tuple `{ <module>, <function atom> }`. The function
|
||||
@@ -173,22 +173,25 @@ defmodule Module do
|
||||
- the module environment
|
||||
- kind: `:def`, `:defp`, `:defmacro`, or `:defmacrop`
|
||||
- function/macro name
|
||||
- list of quoted arguments
|
||||
- list of quoted guards
|
||||
- quoted function body
|
||||
- list of expanded arguments
|
||||
- list of expanded guards
|
||||
- expanded function body
|
||||
|
||||
Note the hook receives the expanded arguments and it is invoked before
|
||||
the function is stored in the module. So `Module.defines?/2` will return
|
||||
false for the first clause of every function.
|
||||
|
||||
If the function/macro being defined has multiple clauses, the hook will
|
||||
be called for each clause.
|
||||
|
||||
Unlike other hooks, `@on_definition` will only invoke functions
|
||||
and never macros. This is because the hook is invoked inside the context
|
||||
of the function (and nested function definitions are not allowed in
|
||||
Elixir).
|
||||
|
||||
When just a module is provided, the function is assumed to be
|
||||
`__on_definition__/6`.
|
||||
|
||||
Note that you can't provide the current module to `@on_definition`
|
||||
because the hook function will not be defined in time. Finally, since
|
||||
the `on_definition` hook is executed inside the context of the defined
|
||||
function (i.e. `env.function` returns the current function), the hook
|
||||
can only be a function, not a macro.
|
||||
|
||||
### Example
|
||||
|
||||
defmodule H do
|
||||
@@ -308,15 +311,14 @@ defmodule Module do
|
||||
and its attributes and functions can be modified.
|
||||
"""
|
||||
def open?(module) do
|
||||
table = data_table_for(module)
|
||||
table == :ets.info(table, :name)
|
||||
:elixir_module.is_open(module)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Evaluates the quoted contents in the given module's context.
|
||||
|
||||
A list of environment options can also be given as argument.
|
||||
See `Code.eval_string` for more information.
|
||||
See `Code.eval_string/3` for more information.
|
||||
|
||||
Raises an error if the module was already compiled.
|
||||
|
||||
@@ -404,6 +406,7 @@ defmodule Module do
|
||||
|
||||
iex> Module.concat([Foo, Bar])
|
||||
Foo.Bar
|
||||
|
||||
iex> Module.concat([Foo, "Bar"])
|
||||
Foo.Bar
|
||||
|
||||
@@ -420,6 +423,7 @@ defmodule Module do
|
||||
|
||||
iex> Module.concat(Foo, Bar)
|
||||
Foo.Bar
|
||||
|
||||
iex> Module.concat(Foo, "Bar")
|
||||
Foo.Bar
|
||||
|
||||
@@ -510,7 +514,7 @@ defmodule Module do
|
||||
table = docs_table_for(module)
|
||||
|
||||
{ signature, _ } = Enum.map_reduce signature, 1, fn(x, acc) ->
|
||||
{ simplify_signature(x, line, acc), acc + 1 }
|
||||
{ simplify_signature(x, acc), acc + 1 }
|
||||
end
|
||||
|
||||
case :ets.lookup(table, tuple) do
|
||||
@@ -531,28 +535,34 @@ defmodule Module do
|
||||
|
||||
# Simplify signatures to be stored in docs
|
||||
|
||||
defp simplify_signature({ :\\, defline, [left, right ] }, line, i) do
|
||||
{ :\\, defline, [simplify_signature(left, line, i), right] }
|
||||
defp simplify_signature({ :\\, _, [left, right ] }, i) do
|
||||
{ :\\, [], [simplify_signature(left, i), right] }
|
||||
end
|
||||
|
||||
defp simplify_signature({ var, line, atom }, _, _i) when is_atom(atom) do
|
||||
case atom_to_list(var) do
|
||||
[?_|_] -> { var, line, :guess }
|
||||
_ -> { var, line, nil }
|
||||
defp simplify_signature({ :%, _, [left, _] }, _i) when is_atom(left) do
|
||||
last = List.last(String.split(atom_to_binary(left), "."))
|
||||
atom = binary_to_atom(String.downcase(last))
|
||||
{ atom, [], nil }
|
||||
end
|
||||
|
||||
defp simplify_signature({ :=, _, [_, right] }, i) do
|
||||
simplify_signature(right, i)
|
||||
end
|
||||
|
||||
defp simplify_signature({ var, _, atom }, _i) when is_atom(atom) do
|
||||
case atom_to_binary(var) do
|
||||
"_" <> rest -> { binary_to_atom(rest), [], Elixir }
|
||||
_ -> { var, [], nil }
|
||||
end
|
||||
end
|
||||
|
||||
defp simplify_signature({ :=, _, [_, right] }, line, i) do
|
||||
simplify_signature(right, line, i)
|
||||
end
|
||||
|
||||
defp simplify_signature(other, line, i) when is_integer(other), do: { :"int#{i}", line, :guess }
|
||||
defp simplify_signature(other, line, i) when is_boolean(other), do: { :"bool#{i}", line, :guess }
|
||||
defp simplify_signature(other, line, i) when is_atom(other), do: { :"atom#{i}", line, :guess }
|
||||
defp simplify_signature(other, line, i) when is_list(other), do: { :"list#{i}", line, :guess }
|
||||
defp simplify_signature(other, line, i) when is_float(other), do: { :"float#{i}", line, :guess }
|
||||
defp simplify_signature(other, line, i) when is_binary(other), do: { :"binary#{i}", line, :guess }
|
||||
defp simplify_signature(_, line, i), do: { :"arg#{i}", line, :guess }
|
||||
defp simplify_signature(other, i) when is_integer(other), do: { :"int#{i}", [], Elixir }
|
||||
defp simplify_signature(other, i) when is_boolean(other), do: { :"bool#{i}", [], Elixir }
|
||||
defp simplify_signature(other, i) when is_atom(other), do: { :"atom#{i}", [], Elixir }
|
||||
defp simplify_signature(other, i) when is_list(other), do: { :"list#{i}", [], Elixir }
|
||||
defp simplify_signature(other, i) when is_float(other), do: { :"float#{i}", [], Elixir }
|
||||
defp simplify_signature(other, i) when is_binary(other), do: { :"binary#{i}", [], Elixir }
|
||||
defp simplify_signature(_, i), do: { :"arg#{i}", [], Elixir }
|
||||
|
||||
# Merge
|
||||
|
||||
@@ -580,7 +590,7 @@ defmodule Module do
|
||||
defp merge_signature({ var, _, _ } = older, { var, _, _ }, _), do: older
|
||||
|
||||
# Otherwise, returns a generic guess
|
||||
defp merge_signature({ _, line, _ }, _newer, i), do: { :"arg#{i}", line, :guess }
|
||||
defp merge_signature({ _, line, _ }, _newer, i), do: { :"arg#{i}", line, Elixir }
|
||||
|
||||
@doc """
|
||||
Checks if the module defines the given function or macro.
|
||||
@@ -638,7 +648,7 @@ defmodule Module do
|
||||
def definitions_in(module) do
|
||||
assert_not_compiled!(:definitions_in, module)
|
||||
table = function_table_for(module)
|
||||
lc { tuple, _, _, _, _, _, _ } inlist :ets.tab2list(table), do: tuple
|
||||
for { tuple, _, _, _, _, _, _ } <- :ets.tab2list(table), do: tuple
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -657,19 +667,19 @@ defmodule Module do
|
||||
def definitions_in(module, kind) do
|
||||
assert_not_compiled!(:definitions_in, module)
|
||||
table = function_table_for(module)
|
||||
lc { tuple, stored_kind, _, _, _, _, _ } inlist :ets.tab2list(table), stored_kind == kind, do: tuple
|
||||
for { tuple, stored_kind, _, _, _, _, _ } <- :ets.tab2list(table), stored_kind == kind, do: tuple
|
||||
end
|
||||
|
||||
@doc """
|
||||
Makes the given functions in `module` overridable.
|
||||
An overridable function is lazily defined, allowing a
|
||||
developer to customize it. See `Kernel.defoverridable` for
|
||||
developer to customize it. See `Kernel.defoverridable/1` for
|
||||
more information and documentation.
|
||||
"""
|
||||
def make_overridable(module, tuples) do
|
||||
assert_not_compiled!(:make_overridable, module)
|
||||
|
||||
lc tuple inlist tuples do
|
||||
for tuple <- tuples do
|
||||
case :elixir_def.lookup_definition(module, tuple) do
|
||||
false ->
|
||||
{ name, arity } = tuple
|
||||
@@ -703,7 +713,7 @@ defmodule Module do
|
||||
@doc """
|
||||
Puts an Erlang attribute to the given module with the given
|
||||
key and value. The semantics of putting the attribute depends
|
||||
if the attribute was registered or not via `register_attribute/2`.
|
||||
if the attribute was registered or not via `register_attribute/3`.
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -733,7 +743,7 @@ defmodule Module do
|
||||
|
||||
@doc """
|
||||
Gets the given attribute from a module. If the attribute
|
||||
was marked with `accumulate` with `Module.register_attribute`,
|
||||
was marked with `accumulate` with `Module.register_attribute/3`,
|
||||
a list is always returned.
|
||||
|
||||
The `@` macro compiles to a call to this function. For example,
|
||||
|
||||
@@ -71,7 +71,7 @@ defmodule Module.LocalsTracker do
|
||||
|
||||
defp reduce_reachable(d, vertex, vertices) do
|
||||
neighbours = :digraph.out_neighbours(d, vertex)
|
||||
neighbours = (lc { _, _ } = t inlist neighbours, do: t) |> :ordsets.from_list
|
||||
neighbours = (for { _, _ } = t <- neighbours, do: t) |> :ordsets.from_list
|
||||
remaining = :ordsets.subtract(neighbours, vertices)
|
||||
vertices = :ordsets.union(neighbours, vertices)
|
||||
:lists.foldl(&reduce_reachable(d, &1, &2), vertices, remaining)
|
||||
@@ -144,10 +144,10 @@ defmodule Module.LocalsTracker do
|
||||
def collect_imports_conflicts(pid, all_defined) do
|
||||
d = :gen_server.call(pid, :digraph, @timeout)
|
||||
|
||||
lc { name, arity } inlist all_defined,
|
||||
:digraph.in_neighbours(d, { :import, name, arity }) != [],
|
||||
n = :digraph.out_neighbours(d, { :import, name, arity }),
|
||||
n != [] do
|
||||
for { name, arity } <- all_defined,
|
||||
:digraph.in_neighbours(d, { :import, name, arity }) != [],
|
||||
n = :digraph.out_neighbours(d, { :import, name, arity }),
|
||||
n != [] do
|
||||
{ n, name, arity }
|
||||
end
|
||||
end
|
||||
@@ -174,7 +174,7 @@ defmodule Module.LocalsTracker do
|
||||
min = arity - default
|
||||
max = arity
|
||||
|
||||
invoked = lc { n, a } inlist reachable, n == name, a in min..max, do: a
|
||||
invoked = for { n, a } <- reachable, n == name, a in min..max, do: a
|
||||
|
||||
if invoked == [] do
|
||||
[{ :unused_def, tuple, kind }|acc]
|
||||
@@ -261,7 +261,7 @@ defmodule Module.LocalsTracker do
|
||||
end
|
||||
|
||||
def handle_cast({ :add_defaults, kind, { name, arity }, defaults }, { d, _ } = state) do
|
||||
lc i inlist :lists.seq(arity - defaults, arity - 1) do
|
||||
for i <- :lists.seq(arity - defaults, arity - 1) do
|
||||
handle_add_definition(d, kind, { name, i })
|
||||
handle_add_local(d, { name, i }, { name, i + 1 })
|
||||
end
|
||||
@@ -269,8 +269,8 @@ defmodule Module.LocalsTracker do
|
||||
end
|
||||
|
||||
def handle_cast({ :reattach, tuple, { in_neigh, out_neigh } }, { d, _ } = state) do
|
||||
lc from inlist in_neigh, do: replace_edge(d, from, tuple)
|
||||
lc to inlist out_neigh, do: replace_edge(d, tuple, to)
|
||||
for from <- in_neigh, do: replace_edge(d, from, tuple)
|
||||
for to <- out_neigh, do: replace_edge(d, tuple, to)
|
||||
{ :noreply, state }
|
||||
end
|
||||
|
||||
|
||||
+73
-42
@@ -8,10 +8,12 @@ defmodule Node do
|
||||
about inlined functions, check out the `Kernel` module.
|
||||
"""
|
||||
|
||||
@type t :: atom
|
||||
@type t :: node
|
||||
|
||||
@doc """
|
||||
Returns the current node. It returns the same as the built-in `node()`.
|
||||
Returns the current node.
|
||||
|
||||
It returns the same as the built-in `node()`.
|
||||
"""
|
||||
@spec self :: t
|
||||
def self do
|
||||
@@ -19,8 +21,9 @@ defmodule Node do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns `true` if the local node is alive; that is, if the node can be
|
||||
part of a distributed system. Otherwise, it returns `false`.
|
||||
Returns `true` if the local node is alive.
|
||||
|
||||
That is, if the node can be part of a distributed system.
|
||||
"""
|
||||
@spec alive? :: boolean
|
||||
def alive? do
|
||||
@@ -29,7 +32,9 @@ defmodule Node do
|
||||
|
||||
@doc """
|
||||
Returns a list of all visible nodes in the system, excluding
|
||||
the local node. Same as `list(:visible)`.
|
||||
the local node.
|
||||
|
||||
Same as `list(:visible)`.
|
||||
"""
|
||||
@spec list :: [t]
|
||||
def list do
|
||||
@@ -37,8 +42,9 @@ defmodule Node do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns a list of nodes according to argument given. The result
|
||||
returned when the argument is a list, is the list of nodes
|
||||
Returns a list of nodes according to argument given.
|
||||
|
||||
The result returned when the argument is a list, is the list of nodes
|
||||
satisfying the disjunction(s) of the list elements.
|
||||
|
||||
See http://www.erlang.org/doc/man/erlang.html#nodes-1 for more info.
|
||||
@@ -50,8 +56,10 @@ defmodule Node do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Monitors the status of the node. If `flag` is `true`, monitoring is
|
||||
turned on. If `flag` is `false`, monitoring is turned off.
|
||||
Monitors the status of the node.
|
||||
|
||||
If `flag` is `true`, monitoring is turned on.
|
||||
If `flag` is `false`, monitoring is turned off.
|
||||
|
||||
See http://www.erlang.org/doc/man/erlang.html#monitor_node-2 for more info.
|
||||
"""
|
||||
@@ -72,10 +80,28 @@ defmodule Node do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Forces the disconnection of a node. This will appear to the `node` as if
|
||||
the local node has crashed. This BIF is mainly used in the Erlang network
|
||||
authentication protocols. Returns `true` if disconnection succeeds, otherwise
|
||||
`false`. If the local node is not alive, the function returns `:ignored`.
|
||||
Tries to set up a connection to node.
|
||||
|
||||
Returns `:pang` if it fails, or `:pong` if it is successful.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Node.ping(:unknown_node)
|
||||
:pang
|
||||
|
||||
"""
|
||||
@spec ping(t) :: :pong | :pang
|
||||
def ping(node) do
|
||||
:net_adm.ping(node)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Forces the disconnection of a node.
|
||||
|
||||
This will appear to the `node` as if the local node has crashed.
|
||||
This function is mainly used in the Erlang network authentication
|
||||
protocols. Returns `true` if disconnection succeeds, otherwise `false`.
|
||||
If the local node is not alive, the function returns `:ignored`.
|
||||
|
||||
See http://www.erlang.org/doc/man/erlang.html#disconnect_node-1 for more info.
|
||||
"""
|
||||
@@ -85,9 +111,10 @@ defmodule Node do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Establishes a connection to `node`. Returns `true` if successful,
|
||||
`false` if not, and the atom `:ignored` if the local node is not
|
||||
alive.
|
||||
Establishes a connection to `node`.
|
||||
|
||||
Returns `true` if successful, `false` if not, and the atom
|
||||
`:ignored` if the local node is not alive.
|
||||
|
||||
See http://erlang.org/doc/man/net_kernel.html#connect_node-1 for more info.
|
||||
"""
|
||||
@@ -112,10 +139,11 @@ defmodule Node do
|
||||
|
||||
@doc """
|
||||
Returns the pid of a new process started by the application of `fun`
|
||||
on `node`. If `node` does not exist, a useless pid is returned.
|
||||
on `node`.
|
||||
|
||||
Check http://www.erlang.org/doc/man/erlang.html#spawn_opt-3 for
|
||||
the list of available options.
|
||||
If `node` does not exist, a useless pid is returned. Check
|
||||
http://www.erlang.org/doc/man/erlang.html#spawn_opt-3 for the list of
|
||||
available options.
|
||||
|
||||
Inlined by the compiler.
|
||||
"""
|
||||
@@ -126,11 +154,11 @@ defmodule Node do
|
||||
|
||||
@doc """
|
||||
Returns the pid of a new process started by the application of
|
||||
`module.function(args)` on `node`. If `node` does not exists,
|
||||
a useless pid is returned.
|
||||
`module.function(args)` on `node`.
|
||||
|
||||
Check http://www.erlang.org/doc/man/erlang.html#spawn-4 for
|
||||
the list of available options.
|
||||
If `node` does not exist, a useless pid is returned. Check
|
||||
http://www.erlang.org/doc/man/erlang.html#spawn-4 for the list of
|
||||
available options.
|
||||
|
||||
Inlined by the compiler.
|
||||
"""
|
||||
@@ -141,11 +169,11 @@ defmodule Node do
|
||||
|
||||
@doc """
|
||||
Returns the pid of a new process started by the application of
|
||||
`module.function(args)` on `node`. If `node` does not exists,
|
||||
a useless pid is returned.
|
||||
`module.function(args)` on `node`.
|
||||
|
||||
Check http://www.erlang.org/doc/man/erlang.html#spawn_opt-5 for
|
||||
the list of available options.
|
||||
If `node` does not exist, a useless pid is returned. Check
|
||||
http://www.erlang.org/doc/man/erlang.html#spawn_opt-5 for the list of
|
||||
available options.
|
||||
|
||||
Inlined by the compiler.
|
||||
"""
|
||||
@@ -155,11 +183,11 @@ defmodule Node do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns the pid of a new process started by the application of `fun`
|
||||
on `node`. A link is created between the calling process and the
|
||||
new process, atomically. If `node` does not exist, a useless pid is returned
|
||||
(and due to the link, an exit signal with exit reason `:noconnection` will be
|
||||
received).
|
||||
Returns the pid of a new linked process started by the application of `fun` on `node`.
|
||||
|
||||
A link is created between the calling process and the new process, atomically.
|
||||
If `node` does not exist, a useless pid is returned (and due to the link, an exit
|
||||
signal with exit reason `:noconnection` will be received).
|
||||
|
||||
Inlined by the compiler.
|
||||
"""
|
||||
@@ -169,11 +197,12 @@ defmodule Node do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns the pid of a new process started by the application of
|
||||
`module.function(args)` on `node`. A link is created between the calling
|
||||
process and the new process, atomically. If `node` does not exist, a useless
|
||||
pid is returned (and due to the link, an exit signal with exit reason
|
||||
`:noconnection` will be received).
|
||||
Returns the pid of a new linked process started by the application of
|
||||
`module.function(args)` on `node`.
|
||||
|
||||
A link is created between the calling process and the new process, atomically.
|
||||
If `node` does not exist, a useless pid is returned (and due to the link, an exit
|
||||
signal with exit reason `:noconnection` will be received).
|
||||
|
||||
Inlined by the compiler.
|
||||
"""
|
||||
@@ -183,9 +212,10 @@ defmodule Node do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Sets the magic cookie of `node` to the atom `cookie`. The default node
|
||||
is `Node.self`, the local node. If `node` is the local node, the function also
|
||||
sets the cookie of all other unknown nodes to `cookie`.
|
||||
Sets the magic cookie of `node` to the atom `cookie`.
|
||||
|
||||
The default node is `Node.self`, the local node. If `node` is the local node,
|
||||
the function also sets the cookie of all other unknown nodes to `cookie`.
|
||||
|
||||
This function will raise `FunctionClauseError` if the given `node` is not alive.
|
||||
"""
|
||||
@@ -194,8 +224,9 @@ defmodule Node do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns the magic cookie of the local node, if the node is alive;
|
||||
otherwise `:nocookie`.
|
||||
Returns the magic cookie of the local node.
|
||||
|
||||
Returns the cookie if the node is alive, otherwise `:nocookie`.
|
||||
"""
|
||||
def get_cookie() do
|
||||
:erlang.get_cookie()
|
||||
|
||||
@@ -5,7 +5,7 @@ defmodule OptionParser do
|
||||
|
||||
@doc """
|
||||
Parses `argv` and returns a tuple with the parsed options, its
|
||||
arguments, and a list options that couldn't be parsed.
|
||||
arguments, and a list of options that couldn't be parsed.
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -42,6 +42,7 @@ defmodule OptionParser do
|
||||
`true` or `false`;
|
||||
* `:integer` - Parses the switch as an integer;
|
||||
* `:float` - Parses the switch as a float;
|
||||
* `:string` - Returns the switch as is (the default);
|
||||
|
||||
If a switch can't be parsed, the option is returned in the invalid
|
||||
options list (third element of the returned tuple).
|
||||
@@ -59,10 +60,10 @@ defmodule OptionParser do
|
||||
...> switches: [unlock: :boolean, limit: :integer])
|
||||
{ [unlock: true, limit: 0], ["path/to/file"], [] }
|
||||
|
||||
iex> OptionParser.parse(["-limit", "3"], switches: [limit: :integer])
|
||||
iex> OptionParser.parse(["--limit", "3"], switches: [limit: :integer])
|
||||
{ [limit: 3], [], [] }
|
||||
|
||||
iex> OptionParser.parse(["-limit", "yyz"], switches: [limit: :integer])
|
||||
iex> OptionParser.parse(["--limit", "yyz"], switches: [limit: :integer])
|
||||
{ [], [], [limit: "yyz"] }
|
||||
|
||||
## Negation switches
|
||||
@@ -96,6 +97,7 @@ defmodule OptionParser do
|
||||
|
||||
iex> OptionParser.parse_head(["--verbose", "--source", "lib", "test/enum_test.exs", "--unlock"])
|
||||
{ [verbose: true, source: "lib"], ["test/enum_test.exs", "--unlock"], [] }
|
||||
|
||||
"""
|
||||
def parse_head(argv, opts \\ []) when is_list(argv) and is_list(opts) do
|
||||
parse(argv, opts, false)
|
||||
@@ -118,7 +120,8 @@ defmodule OptionParser do
|
||||
end
|
||||
|
||||
defp parse(["-" <> option|t], aliases, switches, dict, args, invalid, all) do
|
||||
{ option, kinds, value } = normalize_option(option, switches, aliases)
|
||||
{ option, value } = split_option(option)
|
||||
{ option, kinds, value } = normalize_option(option, value, switches, aliases)
|
||||
|
||||
if nil?(value) do
|
||||
{ value, t } =
|
||||
@@ -153,6 +156,8 @@ defmodule OptionParser do
|
||||
defp store_option(dict, invalid, option, value, kinds) do
|
||||
{ invalid_option, value } =
|
||||
cond do
|
||||
:invalid in kinds ->
|
||||
{ option, value }
|
||||
:boolean in kinds ->
|
||||
{ nil, value in [true, "true"] }
|
||||
:integer in kinds ->
|
||||
@@ -185,27 +190,33 @@ defmodule OptionParser do
|
||||
end
|
||||
end
|
||||
|
||||
defp normalize_option(<<?-, option :: binary>>, switches, aliases) do
|
||||
normalize_option(option, switches, aliases)
|
||||
defp normalize_option(<<?-, option :: binary>>, value, switches, _aliases) do
|
||||
normalize_option(get_negated(option, switches), value, switches)
|
||||
end
|
||||
|
||||
defp normalize_option(option, switches, aliases) do
|
||||
{ option, value } = split_option(option)
|
||||
|
||||
if non_neg = get_non_negated(option, aliases) do
|
||||
kinds = List.wrap(switches[non_neg])
|
||||
|
||||
if :boolean in kinds do
|
||||
{ non_neg, kinds, false }
|
||||
else
|
||||
{ get_aliased(option, aliases), [:boolean], true }
|
||||
end
|
||||
defp normalize_option(option, value, switches, aliases) do
|
||||
option = get_option(option)
|
||||
if alias = aliases[option] do
|
||||
normalize_option({ :default, alias }, value, switches)
|
||||
else
|
||||
atom = get_aliased(option, aliases)
|
||||
{ atom, List.wrap(switches[atom]), value }
|
||||
{ option, [:invalid], value }
|
||||
end
|
||||
end
|
||||
|
||||
defp normalize_option({ :negated, option }, _value, switches) do
|
||||
kinds = List.wrap(switches[option])
|
||||
|
||||
if :boolean in kinds do
|
||||
{ option, kinds, false }
|
||||
else
|
||||
{ option, [:boolean], true }
|
||||
end
|
||||
end
|
||||
|
||||
defp normalize_option({ :default, option }, value, switches) do
|
||||
{ option, List.wrap(switches[option]), value }
|
||||
end
|
||||
|
||||
defp split_option(option) do
|
||||
case :binary.split(option, "=") do
|
||||
[h] -> { h, nil }
|
||||
@@ -214,14 +225,20 @@ defmodule OptionParser do
|
||||
end
|
||||
|
||||
defp to_underscore(option) do
|
||||
bc <<c>> inbits option, do: << if(c == ?-, do: ?_, else: c) >>
|
||||
for <<c <- option>>, into: "", do: << if(c == ?-, do: ?_, else: c) >>
|
||||
end
|
||||
|
||||
defp get_aliased(option, aliases) do
|
||||
atom = option |> to_underscore |> binary_to_atom
|
||||
aliases[atom] || atom
|
||||
defp get_option(option) do
|
||||
option |> to_underscore |> binary_to_atom
|
||||
end
|
||||
|
||||
defp get_non_negated("no-" <> rest, aliases), do: get_aliased(rest, aliases)
|
||||
defp get_non_negated(_, _), do: nil
|
||||
defp get_negated("no-" <> rest = option, switches) do
|
||||
negated = get_option(rest)
|
||||
option = if Keyword.has_key?(switches, negated), do: negated, else: get_option(option)
|
||||
{ :negated, option }
|
||||
end
|
||||
|
||||
defp get_negated(rest, _switches) do
|
||||
{ :default, get_option(rest) }
|
||||
end
|
||||
end
|
||||
|
||||
@@ -205,8 +205,10 @@ defmodule Path do
|
||||
|
||||
iex> Path.relative_to("/usr/local/foo", "/usr/local")
|
||||
"foo"
|
||||
|
||||
iex> Path.relative_to("/usr/local/foo", "/")
|
||||
"usr/local/foo"
|
||||
|
||||
iex> Path.relative_to("/usr/local/foo", "/etc")
|
||||
"/usr/local/foo"
|
||||
|
||||
@@ -277,8 +279,10 @@ defmodule Path do
|
||||
|
||||
iex> Path.basename("~/foo/bar.ex", ".ex")
|
||||
"bar"
|
||||
|
||||
iex> Path.basename("~/foo/bar.exs", ".ex")
|
||||
"bar.exs"
|
||||
|
||||
iex> Path.basename("~/foo/bar.old.ex", ".ex")
|
||||
"bar.old"
|
||||
|
||||
@@ -309,6 +313,7 @@ defmodule Path do
|
||||
|
||||
iex> Path.extname("foo.erl")
|
||||
".erl"
|
||||
|
||||
iex> Path.extname("~/foo/bar")
|
||||
""
|
||||
|
||||
@@ -324,6 +329,7 @@ defmodule Path do
|
||||
|
||||
iex> Path.rootname("/foo/bar")
|
||||
"/foo/bar"
|
||||
|
||||
iex> Path.rootname("/foo/bar.ex")
|
||||
"/foo/bar"
|
||||
|
||||
@@ -340,6 +346,7 @@ defmodule Path do
|
||||
|
||||
iex> Path.rootname("/foo/bar.erl", ".erl")
|
||||
"/foo/bar"
|
||||
|
||||
iex> Path.rootname("/foo/bar.erl", ".ex")
|
||||
"/foo/bar.erl"
|
||||
|
||||
@@ -356,8 +363,10 @@ defmodule Path do
|
||||
|
||||
iex> Path.join(["~", "foo"])
|
||||
"~/foo"
|
||||
|
||||
iex> Path.join(["foo"])
|
||||
"foo"
|
||||
|
||||
iex> Path.join(["/", "foo", "bar"])
|
||||
"/foo/bar"
|
||||
|
||||
@@ -427,8 +436,10 @@ defmodule Path do
|
||||
|
||||
iex> Path.split("")
|
||||
[]
|
||||
|
||||
iex> Path.split("foo")
|
||||
["foo"]
|
||||
|
||||
iex> Path.split("/foo/bar")
|
||||
["/", "foo", "bar"]
|
||||
|
||||
|
||||
@@ -123,6 +123,33 @@ defmodule Process do
|
||||
:erlang.send(dest, msg)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Sends a message to the given process.
|
||||
|
||||
If the option `:noconnect` is used and sending the message would require an
|
||||
auto-connection to another node the message is not sent and `:noconnect` is
|
||||
returned.
|
||||
|
||||
If the option `:nosuspend` is used and sending the message would cause the
|
||||
sender to be suspended the message is not sent and `:nosuspend` is returned.
|
||||
|
||||
Otherwise the message is sent and `:ok` is returned.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Process.send({ :name, :node_does_not_exist }, :hi, [:noconnect])
|
||||
:noconnect
|
||||
|
||||
"""
|
||||
@spec send(dest, msg, [option]) :: result when
|
||||
dest: pid | port | atom | { atom, node },
|
||||
msg: any,
|
||||
option: :noconnect | :nosuspend,
|
||||
result: :ok | :noconnect | :nosuspend
|
||||
def send(dest, msg, options) do
|
||||
:erlang.send(dest, msg, options)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Sends `msg` to `dest` after `time` millisecons.
|
||||
|
||||
@@ -326,7 +353,9 @@ defmodule Process do
|
||||
be an atom, can be used instead of the pid / port identifier with the
|
||||
`Kernel.send/2` function.
|
||||
|
||||
See http://www.erlang.org/doc/man/erlang.html#register-2 for more info.
|
||||
`Process.register/2` will fail with `ArgumentError` if the pid supplied
|
||||
is no longer alive, (check with `alive?/1`) or if the name is
|
||||
already registered (check with `registered?/1`).
|
||||
"""
|
||||
@spec register(pid | port, atom) :: true
|
||||
def register(pid, name) do
|
||||
|
||||
+79
-87
@@ -33,13 +33,32 @@ defmodule Protocol do
|
||||
|
||||
defp after_defprotocol do
|
||||
quote unquote: false do
|
||||
{ arg, bodies, rec } = Protocol.impl_for(__MODULE__)
|
||||
# == Deprecated records handling ==
|
||||
{ arg, impl } = Protocol.rec_impl_for(__MODULE__)
|
||||
Kernel.def impl_for(unquote(arg)) when Kernel.is_record(unquote(arg)), do: unquote(impl)
|
||||
# == Deprecated records handling ==
|
||||
|
||||
@spec impl_for(term) :: module | nil
|
||||
Kernel.def impl_for(data)
|
||||
|
||||
lc { guard, body } inlist bodies do
|
||||
Kernel.def impl_for(unquote(arg)) when unquote(guard), do: unquote(body)
|
||||
# Define the implementation for structs.
|
||||
#
|
||||
# It simply delegates to struct_impl_for which is then
|
||||
# optimized during protocol consolidation.
|
||||
Kernel.def impl_for(%{ __struct__: struct }) when :erlang.is_atom(struct) do
|
||||
struct_impl_for(struct)
|
||||
end
|
||||
|
||||
# Define the implementation for builtins.
|
||||
for { guard, mod } <- Protocol.builtin do
|
||||
target = Module.concat(__MODULE__, mod)
|
||||
|
||||
Kernel.def impl_for(data) when :erlang.unquote(guard)(data) do
|
||||
case impl_for?(unquote(target)) do
|
||||
true -> unquote(target).__impl__(:name)
|
||||
false -> any_impl_for
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@spec impl_for!(term) :: module | no_return
|
||||
@@ -47,25 +66,35 @@ defmodule Protocol do
|
||||
impl_for(data) || raise(Protocol.UndefinedError, protocol: __MODULE__, value: data)
|
||||
end
|
||||
|
||||
# Handle special Record type
|
||||
Kernel.defp rec_impl_for(unquote(arg)), do: unquote(rec)
|
||||
|
||||
# Handle special Any type
|
||||
# Internal handler for Any
|
||||
if @fallback_to_any do
|
||||
Kernel.defp any_impl_for do
|
||||
try do
|
||||
__MODULE__.Any.__impl__(:name)
|
||||
catch
|
||||
:error, :undef, [[{ __MODULE__.Any, :__impl__, [:name], _ }|_]|_] ->
|
||||
nil
|
||||
case impl_for?(__MODULE__.Any) do
|
||||
true -> __MODULE__.Any.__impl__(:name)
|
||||
false -> nil
|
||||
end
|
||||
end
|
||||
else
|
||||
Kernel.defp any_impl_for, do: nil
|
||||
end
|
||||
|
||||
# Inline both helpers
|
||||
@compile { :inline, any_impl_for: 0, rec_impl_for: 1 }
|
||||
# Internal handler for Structs
|
||||
Kernel.defp struct_impl_for(struct) do
|
||||
target = Module.concat(__MODULE__, struct)
|
||||
case impl_for?(target) do
|
||||
true -> target.__impl__(:name)
|
||||
false -> any_impl_for
|
||||
end
|
||||
end
|
||||
|
||||
# Check if compilation is available internally
|
||||
Kernel.defp impl_for?(target) do
|
||||
Code.ensure_compiled?(target) and
|
||||
function_exported?(target, :__impl__, 1)
|
||||
end
|
||||
|
||||
# Inline any and struct implementations
|
||||
@compile { :inline, any_impl_for: 0, struct_impl_for: 1, impl_for?: 1 }
|
||||
|
||||
if :code.ensure_loaded(Kernel.Typespec) == { :module, Kernel.Typespec } and
|
||||
not Kernel.Typespec.defines_type?(__MODULE__, :t, 0) do
|
||||
@@ -90,7 +119,7 @@ defmodule Protocol do
|
||||
end
|
||||
|
||||
defp do_defimpl(protocol, [do: block, for: for]) when is_list(for) do
|
||||
lc f inlist for, do: do_defimpl(protocol, [do: block, for: f])
|
||||
for f <- for, do: do_defimpl(protocol, [do: block, for: f])
|
||||
end
|
||||
|
||||
defp do_defimpl(protocol, [do: block, for: for]) do
|
||||
@@ -139,87 +168,50 @@ defmodule Protocol do
|
||||
# Builtin types.
|
||||
@doc false
|
||||
def builtin do
|
||||
[ Tuple, Atom, List, BitString, Integer, Float,
|
||||
Function, PID, Port, Reference, Any ]
|
||||
[is_tuple: Tuple,
|
||||
is_atom: Atom,
|
||||
is_list: List,
|
||||
is_map: Map,
|
||||
is_bitstring: BitString,
|
||||
is_integer: Integer,
|
||||
is_float: Float,
|
||||
is_function: Function,
|
||||
is_pid: PID,
|
||||
is_port: Port,
|
||||
is_reference: Reference]
|
||||
end
|
||||
|
||||
# Implements the function that detects the protocol and
|
||||
# returns the module to dispatch to.
|
||||
@doc false
|
||||
def impl_for(current) do
|
||||
arg = quote(do: arg)
|
||||
all = [Record|builtin]
|
||||
def rec_impl_for(current) do
|
||||
all = [Any] ++ for { _guard, mod } <- builtin, do: mod
|
||||
arg = quote do: arg
|
||||
target = Module.concat(current, Tuple)
|
||||
|
||||
{ arg,
|
||||
lc(mod inlist all, do: impl_for(current, mod, arg)),
|
||||
rec_impl_for(current, arg) }
|
||||
end
|
||||
fallback = quote do
|
||||
case impl_for?(unquote(target)) do
|
||||
true -> unquote(target).__impl__(:name)
|
||||
false -> any_impl_for
|
||||
end
|
||||
end
|
||||
|
||||
defp rec_impl_for(current, arg) do
|
||||
fallback = impl_for(current, Tuple, arg) |> elem(1)
|
||||
impl_for = quote do
|
||||
atom = :erlang.element(1, unquote(arg))
|
||||
|
||||
quote do
|
||||
target = Module.concat(unquote(current), unquote(arg))
|
||||
try do
|
||||
target.__impl__(:name)
|
||||
catch
|
||||
:error, :undef, [[{ ^target, :__impl__, [:name], _ }|_]|_] ->
|
||||
case not(atom in unquote(all)) and match?('Elixir.' ++ _, atom_to_list(atom)) do
|
||||
true ->
|
||||
target = Module.concat(unquote(current), atom)
|
||||
case impl_for?(target) do
|
||||
true -> target.__impl__(:name)
|
||||
false -> unquote(fallback)
|
||||
end
|
||||
false ->
|
||||
unquote(fallback)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defp impl_for(current, Record, arg) do
|
||||
fallback = impl_for(current, Tuple, arg) |> elem(1)
|
||||
|
||||
dispatch = quote do
|
||||
atom = :erlang.element(1, unquote(arg))
|
||||
|
||||
case not(atom in unquote(builtin)) and match?('Elixir.' ++ _, atom_to_list(atom)) do
|
||||
true -> rec_impl_for(atom)
|
||||
false -> unquote(fallback)
|
||||
end
|
||||
end
|
||||
|
||||
quote do
|
||||
{ is_record(unquote(arg)), unquote(dispatch) }
|
||||
end
|
||||
end
|
||||
|
||||
defp impl_for(current, Tuple, arg), do: impl_with_fallback(Tuple, :is_tuple, current, arg)
|
||||
defp impl_for(current, Atom, arg), do: impl_with_fallback(Atom, :is_atom, current, arg)
|
||||
defp impl_for(current, List, arg), do: impl_with_fallback(List, :is_list, current, arg)
|
||||
defp impl_for(current, BitString, arg), do: impl_with_fallback(BitString, :is_bitstring, current, arg)
|
||||
defp impl_for(current, Integer, arg), do: impl_with_fallback(Integer, :is_integer, current, arg)
|
||||
defp impl_for(current, Float, arg), do: impl_with_fallback(Float, :is_float, current, arg)
|
||||
defp impl_for(current, Function, arg), do: impl_with_fallback(Function, :is_function, current, arg)
|
||||
defp impl_for(current, PID, arg), do: impl_with_fallback(PID, :is_pid, current, arg)
|
||||
defp impl_for(current, Port, arg), do: impl_with_fallback(Port, :is_port, current, arg)
|
||||
defp impl_for(current, Reference, arg), do: impl_with_fallback(Reference, :is_reference, current, arg)
|
||||
|
||||
defp impl_for(_current, Any, _arg) do
|
||||
{ true, quote(do: any_impl_for) }
|
||||
end
|
||||
|
||||
# Defines an implementation with fallback to the given module.
|
||||
defp impl_with_fallback(mod, guard, current, arg) do
|
||||
quote do
|
||||
{ :erlang.unquote(guard)(unquote(arg)),
|
||||
unquote(with_fallback(Module.concat(current, mod))) }
|
||||
end
|
||||
end
|
||||
|
||||
# Tries to dispatch to a given target, fallbacks to the
|
||||
# given `fallback` implementation if the target does not exist.
|
||||
defp with_fallback(target) when is_atom(target) do
|
||||
quote do
|
||||
try do
|
||||
unquote(target).__impl__(:name)
|
||||
catch
|
||||
:error, :undef, [[{ unquote(target), :__impl__, [:name], _ }|_]|_] ->
|
||||
any_impl_for
|
||||
end
|
||||
end
|
||||
{ arg, impl_for }
|
||||
end
|
||||
end
|
||||
|
||||
@@ -234,10 +226,10 @@ defmodule Protocol.DSL do
|
||||
defmacro def({ name, _, args }) when is_atom(name) and is_list(args) do
|
||||
arity = length(args)
|
||||
|
||||
type_args = lc _ inlist :lists.seq(2, arity), do: quote(do: term)
|
||||
type_args = for _ <- :lists.seq(2, arity), do: quote(do: term)
|
||||
type_args = [quote(do: t) | type_args]
|
||||
|
||||
call_args = lc i inlist :lists.seq(2, arity),
|
||||
call_args = for i <- :lists.seq(2, arity),
|
||||
do: { binary_to_atom(<<?x, i + 64>>), [], __MODULE__ }
|
||||
call_args = [quote(do: t) | call_args]
|
||||
|
||||
@@ -274,7 +266,7 @@ defmodule Protocol.DSL do
|
||||
tuple = { name, arity }
|
||||
specs = Module.get_attribute(module, :spec)
|
||||
|
||||
found = lc { k, v } inlist specs, k == tuple do
|
||||
found = for { k, v } <- specs, k == tuple do
|
||||
Kernel.Typespec.define_callback(module, tuple, v)
|
||||
true
|
||||
end
|
||||
|
||||
@@ -62,8 +62,8 @@ defmodule Protocol.Consolidation do
|
||||
end
|
||||
|
||||
defp extract_matching_by_attribute(paths, prefix, callback) do
|
||||
lc path inlist paths,
|
||||
file inlist list_dir(path),
|
||||
for path <- paths,
|
||||
file <- list_dir(path),
|
||||
mod = extract_from_file(path, file, prefix, callback),
|
||||
do: mod
|
||||
end
|
||||
@@ -125,6 +125,9 @@ defmodule Protocol.Consolidation do
|
||||
{ :error, :not_a_protocol } |
|
||||
{ :error, :no_beam_info }
|
||||
def apply_to(protocol, types) when is_atom(protocol) do
|
||||
raise ArgumentError, "consolidation is disabled as we can't consolidate records " <>
|
||||
"and structs at once. Consolidation will be added back once " <>
|
||||
"polymorphic records are removed"
|
||||
ensure_protocol(protocol)
|
||||
|> if_ok(change_debug_info types)
|
||||
|> if_ok(compile)
|
||||
@@ -157,36 +160,38 @@ defmodule Protocol.Consolidation do
|
||||
# impl_for/1 dispatch version.
|
||||
defp change_debug_info({ protocol, any, code }, types) do
|
||||
types = if any, do: types, else: List.delete(types, Any)
|
||||
records = types -- Protocol.builtin
|
||||
builtin = Protocol.builtin -- (Protocol.builtin -- types)
|
||||
builtin = if records != [], do: [Record|builtin], else: builtin
|
||||
change_impl_for(code, protocol, builtin, records, false, [])
|
||||
all = [Any] ++ for { _guard, mod } <- Protocol.builtin, do: mod
|
||||
structs = types -- all
|
||||
change_impl_for(code, protocol, types, structs, false, [])
|
||||
end
|
||||
|
||||
defp change_impl_for([{ :attribute, line, :protocol, _ }|t], protocol, builtin, records, _, acc) do
|
||||
attr = [fallback_to_any: Any in builtin, consolidated: true]
|
||||
change_impl_for(t, protocol, builtin, records, true,
|
||||
[{ :attribute, line, :protocol, attr }|acc])
|
||||
defp change_impl_for([{ :attribute, line, :protocol, opts }|t], protocol, types, structs, _, acc) do
|
||||
opts = [fallback_to_any: opts[:fallback_to_any], consolidated: true]
|
||||
change_impl_for(t, protocol, types, structs, true,
|
||||
[{ :attribute, line, :protocol, opts }|acc])
|
||||
end
|
||||
|
||||
defp change_impl_for([{ :function, line, :impl_for, 1, _ }|t], protocol, builtin, records, is_protocol, acc) do
|
||||
clauses = lc type inlist builtin, do: clause_for(type, protocol, line)
|
||||
defp change_impl_for([{ :function, line, :impl_for, 1, _ }|t], protocol, types, structs, is_protocol, acc) do
|
||||
fallback = if Any in types, do: Module.concat(protocol, Any), else: nil
|
||||
|
||||
unless Any in builtin do
|
||||
clauses = clauses ++ [fallback_clause_for(nil, protocol, line)]
|
||||
end
|
||||
clauses = for { guard, mod } <- Protocol.builtin,
|
||||
mod in types,
|
||||
do: builtin_clause_for(mod, guard, protocol, line)
|
||||
|
||||
change_impl_for(t, protocol, builtin, records, is_protocol,
|
||||
clauses = [struct_clause_for(line)|clauses] ++
|
||||
[fallback_clause_for(fallback, protocol, line)]
|
||||
|
||||
change_impl_for(t, protocol, types, structs, is_protocol,
|
||||
[{ :function, line, :impl_for, 1, clauses }|acc])
|
||||
end
|
||||
|
||||
defp change_impl_for([{ :function, line, :rec_impl_for, 1, _ }|t], protocol, builtin, records, is_protocol, acc) do
|
||||
fallback = if Tuple in builtin, do: Module.concat(protocol, Tuple)
|
||||
clauses = lc type inlist records, do: record_clause_for(type, protocol, line)
|
||||
clauses = clauses ++ [fallback_clause_for(fallback, protocol, line)]
|
||||
defp change_impl_for([{ :function, line, :struct_impl_for, 1, _ }|t], protocol, types, structs, is_protocol, acc) do
|
||||
fallback = if Any in types, do: Module.concat(protocol, Any), else: nil
|
||||
clauses = for struct <- structs, do: each_struct_clause_for(struct, protocol, line)
|
||||
clauses = clauses ++ [fallback_clause_for(fallback, protocol, line)]
|
||||
|
||||
change_impl_for(t, protocol, builtin, records, is_protocol,
|
||||
[{ :function, line, :rec_impl_for, 1, clauses }|acc])
|
||||
change_impl_for(t, protocol, types, structs, is_protocol,
|
||||
[{ :function, line, :struct_impl_for, 1, clauses }|acc])
|
||||
end
|
||||
|
||||
defp change_impl_for([h|t], protocol, info, types, is_protocol, acc) do
|
||||
@@ -201,42 +206,6 @@ defmodule Protocol.Consolidation do
|
||||
end
|
||||
end
|
||||
|
||||
defp clause_for(Tuple, protocol, line), do: builtin_clause_for(Tuple, :is_tuple, protocol, line)
|
||||
defp clause_for(Atom, protocol, line), do: builtin_clause_for(Atom, :is_atom, protocol, line)
|
||||
defp clause_for(List, protocol, line), do: builtin_clause_for(List, :is_list, protocol, line)
|
||||
defp clause_for(BitString, protocol, line), do: builtin_clause_for(BitString, :is_bitstring, protocol, line)
|
||||
defp clause_for(Integer, protocol, line), do: builtin_clause_for(Integer, :is_integer, protocol, line)
|
||||
defp clause_for(Float, protocol, line), do: builtin_clause_for(Float, :is_float, protocol, line)
|
||||
defp clause_for(Function, protocol, line), do: builtin_clause_for(Function, :is_function, protocol, line)
|
||||
defp clause_for(PID, protocol, line), do: builtin_clause_for(PID, :is_pid, protocol, line)
|
||||
defp clause_for(Port, protocol, line), do: builtin_clause_for(Port, :is_port, protocol, line)
|
||||
defp clause_for(Reference, protocol, line), do: builtin_clause_for(Reference, :is_reference, protocol, line)
|
||||
|
||||
defp clause_for(Any, protocol, line) do
|
||||
{:clause, line, [{:var, line, :_}], [],
|
||||
[{ :atom, line, Module.concat(protocol, Any) }]}
|
||||
end
|
||||
|
||||
defp clause_for(Record, _protocol, line) do
|
||||
{:clause, line, [{:var, line, :x}],
|
||||
[[{:op, line, :andalso,
|
||||
{:call, line,
|
||||
{:remote, line, {:atom, line, :erlang}, {:atom, line, :is_tuple}},
|
||||
[{:var, line, :x}]},
|
||||
{:call, line,
|
||||
{:remote, line, {:atom, line, :erlang}, {:atom, line, :is_atom}},
|
||||
[{:call, line,
|
||||
{:remote, line, {:atom, line, :erlang}, {:atom, line, :element}},
|
||||
[{:integer, line, 1}, {:var, line, :x}]
|
||||
}]},
|
||||
}]],
|
||||
[{:call, line,
|
||||
{:atom, line, :rec_impl_for},
|
||||
[{:call, line,
|
||||
{:remote, line, {:atom, line, :erlang}, {:atom, line, :element}},
|
||||
[{:integer, line, 1}, {:var, line, :x}]}]}]}
|
||||
end
|
||||
|
||||
defp builtin_clause_for(mod, guard, protocol, line) do
|
||||
{:clause, line,
|
||||
[{:var, line, :x}],
|
||||
@@ -247,7 +216,21 @@ defmodule Protocol.Consolidation do
|
||||
[{:atom, line, Module.concat(protocol, mod)}]}
|
||||
end
|
||||
|
||||
defp record_clause_for(other, protocol, line) do
|
||||
defp struct_clause_for(line) do
|
||||
{:clause, line,
|
||||
[{:map, line, [
|
||||
{:map_field_exact, line, {:atom, line, :__struct__}, {:var, line, :x}}
|
||||
]}],
|
||||
[[{:call, line,
|
||||
{:remote, line, {:atom, line, :erlang}, {:atom, line, :is_atom}},
|
||||
[{:var, line, :x}],
|
||||
}]],
|
||||
[{:call, line,
|
||||
{:atom, line, :struct_impl_for},
|
||||
[{:var, line, :x}]}]}
|
||||
end
|
||||
|
||||
defp each_struct_clause_for(other, protocol, line) do
|
||||
{:clause, line, [{:atom, line, other}], [],
|
||||
[{:atom, line, Module.concat(protocol, other)}]}
|
||||
end
|
||||
|
||||
@@ -34,6 +34,7 @@ defmodule Range do
|
||||
|
||||
iex> Range.range?(1..3)
|
||||
true
|
||||
|
||||
iex> Range.range?(0)
|
||||
false
|
||||
|
||||
|
||||
+155
-789
@@ -1,833 +1,199 @@
|
||||
defmodule Record do
|
||||
@moduledoc ~S"""
|
||||
Functions for working with Records.
|
||||
@moduledoc """
|
||||
Module to work, define and import records.
|
||||
|
||||
A record is a tagged tuple which contains one or more elements
|
||||
where the first element is an atom. We can manually create a record
|
||||
by simply defining such a tuple:
|
||||
Records are simply tuples where the first element is an atom:
|
||||
|
||||
iex> record = { User, "José", 25 }
|
||||
iex> is_record(record, User)
|
||||
iex> Record.record? { User, "jose", 27 }
|
||||
true
|
||||
|
||||
However, manually constructing tuples can be quite error prone.
|
||||
If we need to add a new field to our User, it would require
|
||||
us to carefully change all places where the tuple is used.
|
||||
Furthermore, as more items are added to the tuple, they
|
||||
lose semantic value.
|
||||
This module provides conveniences for working with records at
|
||||
compilation time, where compile-time field names are used to
|
||||
manipulate the tuples, providing fast operations on top of
|
||||
the tuples compact structure.
|
||||
|
||||
This module solves these problems by allowing us to name each
|
||||
element and encapsulate the generation and manipulation of
|
||||
such tuples. Much of the functionality provided by this module happens
|
||||
at compilation time, meaning they don't add any runtime overhead while
|
||||
considerably improving the quality of our code.
|
||||
In Elixir, records are used in two situations:
|
||||
|
||||
For these reasons, Records are frequently used in Elixir and
|
||||
are also very useful when combined with Protocols. This module
|
||||
provides different mechanisms for working with records and we
|
||||
are going to explore them in the following sections.
|
||||
1. To work with short, internal data. See `Inspect.Algebra`
|
||||
implementation for a good example;
|
||||
|
||||
## defrecordp
|
||||
2. To interface with Erlang records;
|
||||
|
||||
The simplest way of working with records is via `defrecordp`:
|
||||
The macros `defrecord/3` and `defrecordp/3` can be used to create
|
||||
records while `extract/2` can be used to extract records from Erlang
|
||||
files.
|
||||
"""
|
||||
|
||||
@doc """
|
||||
Extracts record information from an Erlang file.
|
||||
|
||||
Returns a quoted expression containing the fields as a list
|
||||
of tuples. It expects the record name to be an atom and the
|
||||
library path to be a string at expansion time.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> 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]
|
||||
|
||||
"""
|
||||
defmacro extract(name, opts) when is_atom(name) and is_list(opts) do
|
||||
Macro.escape Record.Extractor.extract(name, opts)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Checks if the given `data` is a record of `kind`.
|
||||
|
||||
This is implemented as a macro so it can be used in guard clauses.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> record = { User, "jose", 27 }
|
||||
iex> Record.record?(record, User)
|
||||
true
|
||||
|
||||
"""
|
||||
defmacro record?(data, kind) do
|
||||
case __CALLER__.in_guard? do
|
||||
true ->
|
||||
quote do
|
||||
is_tuple(unquote(data)) and tuple_size(unquote(data)) > 0
|
||||
and :erlang.element(1, unquote(data)) == unquote(kind)
|
||||
end
|
||||
false ->
|
||||
quote do
|
||||
result = unquote(data)
|
||||
is_tuple(result) and tuple_size(result) > 0
|
||||
and :erlang.element(1, result) == unquote(kind)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Checks if the given `data` is a record.
|
||||
|
||||
This is implemented as a macro so it can be used in guard clauses.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> record = { User, "jose", 27 }
|
||||
iex> Record.record?(record)
|
||||
true
|
||||
iex> tuple = {}
|
||||
iex> Record.record?(tuple)
|
||||
false
|
||||
|
||||
"""
|
||||
defmacro record?(data) do
|
||||
case __CALLER__.in_guard? do
|
||||
true ->
|
||||
quote do
|
||||
is_tuple(unquote(data)) and tuple_size(unquote(data)) > 0
|
||||
and is_atom(:erlang.element(1, unquote(data)))
|
||||
end
|
||||
false ->
|
||||
quote do
|
||||
result = unquote(data)
|
||||
is_tuple(result) and tuple_size(result) > 0
|
||||
and is_atom(:erlang.element(1, result))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@doc false
|
||||
def defmacros(name, values, env, tag \\ nil) do
|
||||
Record.Deprecated.defmacros(name, values, env, tag)
|
||||
end
|
||||
|
||||
@doc false
|
||||
def deftypes(values, types, env) do
|
||||
Record.Deprecated.deftypes(values, types, env)
|
||||
end
|
||||
|
||||
@doc false
|
||||
def deffunctions(values, env) do
|
||||
Record.Deprecated.deffunctions(values, env)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Defines a set of macros to create and access a record.
|
||||
|
||||
The macros are going to have `name`, a tag (which defaults)
|
||||
to the name if none is given, and a set of fields given by
|
||||
`kv`.
|
||||
|
||||
## Examples
|
||||
|
||||
defmodule User do
|
||||
defrecordp :user, name: "José", age: 25
|
||||
Record.defrecord :user, [name: "José", age: "25"]
|
||||
end
|
||||
|
||||
In the example above, `defrecordp` is going to generate a set of
|
||||
macros named `user` that allows us to create, update and match
|
||||
on a record. Our record is going to have two fields, a `name` with
|
||||
default value of "José" and `age` with default value 25.
|
||||
|
||||
Let's see some examples:
|
||||
In the example above, a set of macros named `user` but with different
|
||||
arities will be defined to manipulate the underlying record:
|
||||
|
||||
# To create records
|
||||
user() #=> { :user, "José", 25 }
|
||||
user(age: 26) #=> { :user, "José", 26 }
|
||||
|
||||
By using the `user` macro, we no longer need to explicitly create
|
||||
a tuple with all elements. It also allows us to create and modify
|
||||
values by name:
|
||||
# To get a field from the record
|
||||
user(record, :name) #=> "José"
|
||||
|
||||
# Create a new record
|
||||
sample_user = user()
|
||||
# To update the record
|
||||
user(record, age: 26) #=> { :user, "José", 26 }
|
||||
|
||||
# And now change its age to 26
|
||||
user(sample_user, age: 26)
|
||||
By default, Elixir uses the record name as the first element of
|
||||
the tuple (the tag). But it can be changed to something else:
|
||||
|
||||
Since `user` is a macro, all the work happens at compilation time.
|
||||
This means all operations, like changing the `age` above, work
|
||||
as a simple tuple operation at runtime:
|
||||
|
||||
# This update operation...
|
||||
user(sample_user, age: 26)
|
||||
|
||||
# Literally translates to this one:
|
||||
set_elem(sample_user, 2, 26)
|
||||
|
||||
For this reason, the following operation is not allowed as all
|
||||
values need to be explicit:
|
||||
|
||||
new_values = [age: 26]
|
||||
user(sample_user, new_values)
|
||||
|
||||
As the name says, `defrecordp` is useful when you don't want to
|
||||
expose the record definition. The `user` macro used above, for
|
||||
example, is only available inside the `User` module and nowhere else.
|
||||
You can find more information in `Kernel.defrecordp/3` docs.
|
||||
|
||||
## defrecord
|
||||
|
||||
By using `defrecord`, a developer can make a Record definition
|
||||
available everywhere within Elixir code. Let's see an example:
|
||||
|
||||
defrecord User, name: "José", age: 25
|
||||
|
||||
User[] #=> User[name: "José", age: 25]
|
||||
User[age: 26] #=> User[name: "José", age: 26]
|
||||
|
||||
All the functionality discussed above happens at compilation time.
|
||||
This means that both `user(age: 26)` and `User[age: 26]` are expanded
|
||||
into a tuple at compile time.
|
||||
|
||||
However, there are some situations where we want to set or update
|
||||
fields dynamically. `defrecord` (and not `defrecordp` ) supports
|
||||
this behaviour out of the box:
|
||||
|
||||
defrecord User, name: "José", age: 25
|
||||
|
||||
opts = [name: "Hello"]
|
||||
user = User.new(opts)
|
||||
#=> User[name: "Hello", age: 25]
|
||||
user.update(age: 26)
|
||||
#=> User[name: "Hello", age: 26]
|
||||
|
||||
All the calls above happen at runtime. It gives Elixir records
|
||||
flexibility at the cost of performance since there is more work
|
||||
happening at runtime.
|
||||
|
||||
The above calls (`new` and `update`) can accept both atom and string
|
||||
keys for field names, however not both at the same time. This feature
|
||||
allows to "sanitize" untrusted dictionaries and initialize/update
|
||||
records without using `Kernel.binary_to_existing_atom/1`.
|
||||
|
||||
To sum up, `defrecordp` should be used when you don't want to expose
|
||||
the record information while `defrecord` should be used whenever you
|
||||
want to share a record within your code or with other libraries or
|
||||
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`
|
||||
and [`Range`](Range.html) with `defrecord`.
|
||||
|
||||
You can learn more about records in the `Kernel.defrecord/3` docs. Now
|
||||
let's discuss the usefulness of combining records with protocols.
|
||||
|
||||
## Protocols
|
||||
|
||||
Developers can extend existing protocols by creating their own
|
||||
records and implementing the desired protocols. For instance,
|
||||
imagine that you have created a new representation for storing
|
||||
date and time, represented by the year, the week of the year
|
||||
and the week day:
|
||||
|
||||
defrecord WeekDate, year: nil, week: nil, week_day: nil
|
||||
|
||||
Now we want this date to be represented as a string and this
|
||||
can be done by implementing the `String.Chars` protocol for
|
||||
our record:
|
||||
|
||||
defimpl String.Chars, for: WeekDate do
|
||||
def to_string(WeekDate[year: year, week: week, week_day: day]) do
|
||||
"#{year}-W#{week}-#{day}"
|
||||
end
|
||||
defmodule User do
|
||||
Record.defrecord :user, User, name: nil
|
||||
end
|
||||
|
||||
Now we can explicitly convert our `WeekDate`:
|
||||
|
||||
to_string WeekDate[year: 2013, week: 26, week_day: 4]
|
||||
"2013-W26-4"
|
||||
|
||||
A protocol can be implemented for any record, whether
|
||||
generated with `defrecordp` or `defrecord`.
|
||||
"""
|
||||
|
||||
@type t :: tuple
|
||||
|
||||
@doc """
|
||||
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,
|
||||
uid: :undefined, gid: :undefined]
|
||||
|
||||
defrecord FileInfo, Record.extract(:file_info, from_lib: "kernel/include/file.hrl")
|
||||
user() #=> { User, nil }
|
||||
|
||||
"""
|
||||
def extract(name, opts) do
|
||||
Record.Extractor.retrieve(name, opts)
|
||||
end
|
||||
defmacro defrecord(name, tag \\ nil, kv) do
|
||||
kv = Macro.escape(kv, unquote: true)
|
||||
|
||||
@doc false
|
||||
def defrecord(name, fields, opts) do
|
||||
block = Keyword.get(opts, :do, nil)
|
||||
record_check!(fields)
|
||||
quote bind_quoted: [name: name, tag: tag, kv: kv] do
|
||||
tag = tag || name
|
||||
kv = Macro.expand(kv, __ENV__)
|
||||
|
||||
quote do
|
||||
unquoted_fields = unquote(fields)
|
||||
{ fields, _types } = Record.Backend.split_fields_and_types(:defrecord, kv)
|
||||
fields = Macro.escape(fields)
|
||||
|
||||
defmodule unquote(name) do
|
||||
import Record.DSL
|
||||
|
||||
@record_fields []
|
||||
@record_types []
|
||||
|
||||
Record.deffunctions(unquoted_fields, __ENV__)
|
||||
value = unquote(block)
|
||||
Record.deftypes(@record_fields, @record_types, __ENV__)
|
||||
value
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defp record_check!([{ field, { :::, _, [_, _] }}|_]) when is_atom(field) do
|
||||
raise ArgumentError, message: "typespecs are not supported inlined with defrecord, " <>
|
||||
"please use record_type instead"
|
||||
end
|
||||
|
||||
defp record_check!([_|t]), do: record_check!(t)
|
||||
defp record_check!(_), do: :ok
|
||||
|
||||
@doc false
|
||||
def defrecordp(name, tag, fields) do
|
||||
case recordp_split(fields, [], [], false) do
|
||||
{ :ok, fields, types, def_type } ->
|
||||
types = Macro.escape(types)
|
||||
|
||||
# bind_quoted isn't available when bootstrapping record
|
||||
quoted = quote [unquote: false] do
|
||||
Record.defmacros(name, fields, __ENV__, tag)
|
||||
|
||||
if def_type do
|
||||
type = binary_to_atom(atom_to_binary(name) <> "_t")
|
||||
@typep unquote(type)() :: { unquote(tag || name), unquote_splicing(types) }
|
||||
end
|
||||
end
|
||||
|
||||
quote do
|
||||
def_type = unquote(def_type)
|
||||
fields = unquote(fields)
|
||||
types = unquote(types)
|
||||
tag = unquote(tag)
|
||||
name = unquote(name)
|
||||
unquote(quoted)
|
||||
end
|
||||
|
||||
:error ->
|
||||
quote do
|
||||
name = unquote(name)
|
||||
Record.defmacros(name, unquote(fields), __ENV__, unquote(tag))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defp recordp_split([{ field, { :::, _, [default, type] }}|t], defaults, types, _) do
|
||||
recordp_split(t, [{ field, default }|defaults], [type|types], true)
|
||||
end
|
||||
|
||||
defp recordp_split([other|t], defaults, types, def_type) do
|
||||
recordp_split(t, [other|defaults], [quote(do: term)|types], def_type)
|
||||
end
|
||||
|
||||
defp recordp_split([], defaults, types, def_type) do
|
||||
{ :ok, :lists.reverse(defaults), :lists.reverse(types), def_type }
|
||||
end
|
||||
|
||||
defp recordp_split(_, _, _, _) do
|
||||
:error
|
||||
end
|
||||
|
||||
@doc """
|
||||
Define record functions skipping the module definition.
|
||||
|
||||
This is called directly by `Kernel.defrecord/3`. It expects the record
|
||||
values, a set of options and the module environment.
|
||||
|
||||
## Examples
|
||||
|
||||
defmodule CustomRecord do
|
||||
Record.deffunctions [:name, :age], __ENV__
|
||||
Record.deftypes [:name, :age], [name: :binary, age: :integer], __ENV__
|
||||
defmacro(unquote(name)(args \\ [])) do
|
||||
Record.Backend.access(unquote(tag), unquote(fields), args, __CALLER__)
|
||||
end
|
||||
|
||||
"""
|
||||
def deffunctions(values, env) do
|
||||
values = lc value inlist values, do: convert_value(value)
|
||||
escaped = Macro.escape(values)
|
||||
|
||||
contents = [
|
||||
reflection(escaped),
|
||||
initializer(escaped),
|
||||
conversions(values),
|
||||
record_optimizable(),
|
||||
updater(values),
|
||||
accessors(values, 1),
|
||||
switch_recorder()
|
||||
]
|
||||
|
||||
contents = [quote(do: @record_fields unquote(escaped))|contents]
|
||||
|
||||
# Special case for bootstrapping purposes
|
||||
if env == Macro.Env do
|
||||
Module.eval_quoted(env, contents, [], [])
|
||||
else
|
||||
Module.eval_quoted(env.module, contents, [], env.location)
|
||||
defmacro(unquote(name)(record, args)) do
|
||||
Record.Backend.access(unquote(tag), unquote(fields), record, args, __CALLER__)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Define types and specs for the record.
|
||||
Same as `defrecord/3` but generates private macros.
|
||||
"""
|
||||
def deftypes(values, types, env) do
|
||||
types = types || []
|
||||
values = lc value inlist values do
|
||||
{ name, default } = convert_value(value)
|
||||
{ name, default, find_spec(types, name) }
|
||||
end
|
||||
defmacro defrecordp(name, tag \\ nil, kv) do
|
||||
kv = Macro.escape(kv, unquote: true)
|
||||
|
||||
contents = [
|
||||
core_specs(values),
|
||||
accessor_specs(values, 1, [])
|
||||
]
|
||||
quote bind_quoted: [name: name, tag: tag, kv: kv] do
|
||||
tag = tag || name
|
||||
kv = Macro.expand(kv, __ENV__)
|
||||
|
||||
# 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
|
||||
{ fields, _types } = Record.Backend.split_fields_and_types(:defrecordp, kv)
|
||||
fields = Macro.escape(fields)
|
||||
|
||||
@doc """
|
||||
Define macros for manipulating records.
|
||||
|
||||
This is called
|
||||
directly by `Kernel.defrecordp/3`. It expects the macro name, the
|
||||
record values and the environment.
|
||||
|
||||
## Examples
|
||||
|
||||
defmodule CustomRecord do
|
||||
Record.defmacros :user, [:name, :age], __ENV__
|
||||
defmacrop(unquote(name)(args \\ [])) do
|
||||
Record.Backend.access(unquote(tag), unquote(fields), args, __CALLER__)
|
||||
end
|
||||
|
||||
"""
|
||||
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) }
|
||||
end
|
||||
|
||||
tag = tag || name
|
||||
|
||||
contents = quote do
|
||||
defmacrop unquote(name)() do
|
||||
Record.access(unquote(tag), unquote(escaped), [], __CALLER__)
|
||||
defmacrop(unquote(name)(record, args)) do
|
||||
Record.Backend.access(unquote(tag), unquote(fields), record, args, __CALLER__)
|
||||
end
|
||||
|
||||
defmacrop unquote(name)(args) do
|
||||
Record.access(unquote(tag), unquote(escaped), args, __CALLER__)
|
||||
end
|
||||
|
||||
defmacrop unquote(name)(record, args) do
|
||||
Record.dispatch(unquote(tag), unquote(escaped), record, args, __CALLER__)
|
||||
end
|
||||
end
|
||||
|
||||
Module.eval_quoted(env.module, contents, [], env.location)
|
||||
end
|
||||
|
||||
## Callbacks
|
||||
|
||||
# Store all optimizable fields in the record as well
|
||||
@doc false
|
||||
defmacro __before_compile__(_) do
|
||||
quote do
|
||||
def __record__(:optimizable), do: @record_optimizable
|
||||
end
|
||||
end
|
||||
|
||||
# Store fields that can be optimized and that cannot be
|
||||
# optimized as they are overridden
|
||||
@doc false
|
||||
def __on_definition__(env, kind, name, args, _guards, _body) do
|
||||
tuple = { name, length(args) }
|
||||
module = env.module
|
||||
functions = Module.get_attribute(module, :record_optimizable)
|
||||
|
||||
functions =
|
||||
if kind in [:def] and Module.get_attribute(module, :record_optimized) do
|
||||
[tuple|functions]
|
||||
else
|
||||
List.delete(functions, tuple)
|
||||
end
|
||||
|
||||
Module.put_attribute(module, :record_optimizable, functions)
|
||||
end
|
||||
|
||||
@doc false
|
||||
# Implements the access macro used by records.
|
||||
# It returns the index of the given field.
|
||||
def access(atom, fields, arg, _caller) when is_atom(arg) do
|
||||
if index = find_index(fields, arg, 0) do
|
||||
index + 1
|
||||
else
|
||||
raise ArgumentError, message: "record #{inspect atom} does not have the key: #{inspect arg}"
|
||||
end
|
||||
end
|
||||
|
||||
# Implements the access macro used by records.
|
||||
# It returns a quoted expression that defines
|
||||
# a record or a match in case the record is
|
||||
# inside a match.
|
||||
def access(atom, fields, keyword, caller) do
|
||||
unless is_keyword(keyword) do
|
||||
raise ArgumentError, message: "expected contents inside brackets to be a keyword list or an atom, got: #{inspect keyword}"
|
||||
end
|
||||
|
||||
in_match = caller.in_match?
|
||||
|
||||
has_underscore_value = Keyword.has_key?(keyword, :_)
|
||||
underscore_value = Keyword.get(keyword, :_, { :_, [], nil })
|
||||
keyword = Keyword.delete keyword, :_
|
||||
|
||||
iterator = fn({field, default}, each_keyword) ->
|
||||
new_fields =
|
||||
case Keyword.has_key?(each_keyword, field) do
|
||||
true -> Keyword.get(each_keyword, field)
|
||||
false ->
|
||||
case in_match or has_underscore_value do
|
||||
true -> underscore_value
|
||||
false -> Macro.escape(default)
|
||||
end
|
||||
end
|
||||
|
||||
{ new_fields, Keyword.delete(each_keyword, field) }
|
||||
end
|
||||
|
||||
{ match, remaining } = :lists.mapfoldl(iterator, keyword, fields)
|
||||
|
||||
case remaining do
|
||||
[] ->
|
||||
{ :{}, [], [atom|match] }
|
||||
_ ->
|
||||
keys = lc { key, _ } inlist remaining, do: key
|
||||
raise ArgumentError, message: "record #{inspect atom} does not have the key: #{inspect hd(keys)}"
|
||||
end
|
||||
end
|
||||
|
||||
# Dispatch the call to either get, update or to_list depending on the args given.
|
||||
@doc false
|
||||
def dispatch(atom, fields, record, args, caller) do
|
||||
cond do
|
||||
is_atom(args) ->
|
||||
get(atom, fields, record, args)
|
||||
is_keyword(args) ->
|
||||
update(atom, fields, record, args, caller)
|
||||
true ->
|
||||
raise ArgumentError, message: "expected arguments to be a compile time atom, list or keywords"
|
||||
end
|
||||
end
|
||||
|
||||
# Implements the update macro defined by defmacros.
|
||||
# It returns a quoted expression that represents
|
||||
# the access given by the keywords.
|
||||
defp update(atom, fields, var, keyword, caller) do
|
||||
unless is_keyword(keyword) do
|
||||
raise ArgumentError, message: "expected arguments to be compile time keywords"
|
||||
end
|
||||
|
||||
if caller.in_match? do
|
||||
raise ArgumentError, message: "cannot invoke update style macro inside match context"
|
||||
end
|
||||
|
||||
Enum.reduce keyword, var, fn({ key, value }, acc) ->
|
||||
index = find_index(fields, key, 0)
|
||||
if index do
|
||||
quote do
|
||||
:erlang.setelement(unquote(index + 2), unquote(acc), unquote(value))
|
||||
end
|
||||
else
|
||||
raise ArgumentError, message: "record #{inspect atom} does not have the key: #{inspect key}"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Implements the get macro defined by defmacros.
|
||||
# It returns a quoted expression that represents
|
||||
# getting the value of a given field.
|
||||
defp get(atom, fields, var, key) do
|
||||
index = find_index(fields, key, 0)
|
||||
if index do
|
||||
quote do
|
||||
:erlang.element(unquote(index + 2), unquote(var))
|
||||
end
|
||||
else
|
||||
raise ArgumentError, message: "record #{inspect atom} does not have the key: #{inspect key}"
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
|
||||
|
||||
## Function generation
|
||||
|
||||
# Define __record__/1, __record__/2, __record__/3 as reflection
|
||||
# functions that return the record names and fields.
|
||||
#
|
||||
# Note that fields are *not* keywords. They are in the same
|
||||
# order as given as parameter and reflects the order of the
|
||||
# fields in the tuple.
|
||||
#
|
||||
# ## Examples
|
||||
#
|
||||
# defrecord FileInfo, atime: nil, mtime: nil
|
||||
#
|
||||
# FileInfo.__record__(:name) #=> FileInfo
|
||||
# FileInfo.__record__(:fields) #=> [atime: nil, mtime: nil]
|
||||
# FileInfo.__record__(:index, :atime) #=> 1
|
||||
# FileInfo.__record__(:index, :mtime) #=> 2
|
||||
#
|
||||
defp reflection(values) do
|
||||
quoted = lc { k, _ } inlist values do
|
||||
index = find_index(values, k, 0)
|
||||
quote do
|
||||
def __record__(:index, unquote(k)), do: unquote(index + 1)
|
||||
end
|
||||
end
|
||||
|
||||
quote do
|
||||
unquote(quoted)
|
||||
|
||||
@doc false
|
||||
def __record__(:index, _), do: nil
|
||||
@doc false
|
||||
def __record__(:index, arg, _), do: __record__(:index, arg)
|
||||
|
||||
@doc false
|
||||
def __record__(kind, _), do: __record__(kind)
|
||||
|
||||
@doc false
|
||||
def __record__(:name), do: __MODULE__
|
||||
def __record__(:fields), do: unquote(values)
|
||||
end
|
||||
end
|
||||
|
||||
# Define initializers methods. For a declaration like:
|
||||
#
|
||||
# defrecord FileInfo, atime: nil, mtime: nil
|
||||
#
|
||||
# It will define three methods:
|
||||
#
|
||||
# def new() do
|
||||
# new([])
|
||||
# end
|
||||
#
|
||||
# def new([]) do
|
||||
# { FileInfo, nil, nil }
|
||||
# end
|
||||
#
|
||||
# def new(opts) do
|
||||
# { FileInfo, Keyword.get(opts, :atime), Keyword.get(opts, :mtime) }
|
||||
# end
|
||||
#
|
||||
defp initializer(values) do
|
||||
defaults = lc { _, value } inlist values, do: value
|
||||
|
||||
# For each value, define a piece of code that will receive
|
||||
# an ordered dict of options (opts) and it will try to fetch
|
||||
# the given key from the ordered dict, falling back to the
|
||||
# default value if one does not exist.
|
||||
atom_selective = lc { k, v } inlist values, do: initialize_lookup(k, v)
|
||||
string_selective = lc { k, v } inlist values, do: initialize_lookup(atom_to_binary(k), v)
|
||||
|
||||
quote do
|
||||
@doc false
|
||||
def new(), do: new([])
|
||||
|
||||
@doc false
|
||||
def new([]), do: { __MODULE__, unquote_splicing(defaults) }
|
||||
def new([{key, _}|_] = opts) when is_atom(key), do: { __MODULE__, unquote_splicing(atom_selective) }
|
||||
def new([{key, _}|_] = opts) when is_binary(key), do: { __MODULE__, unquote_splicing(string_selective) }
|
||||
end
|
||||
end
|
||||
|
||||
defp initialize_lookup(k, v) do
|
||||
quote do
|
||||
case :lists.keyfind(unquote(k), 1, opts) do
|
||||
false -> unquote(v)
|
||||
{_, v} -> v
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Define converters method(s). For a declaration like:
|
||||
#
|
||||
# defrecord FileInfo, atime: nil, mtime: nil
|
||||
#
|
||||
# It will define one method, to_keywords, which will return a Keyword
|
||||
#
|
||||
# [atime: nil, mtime: nil]
|
||||
#
|
||||
defp conversions(values) do
|
||||
sorted = lc { k, _ } inlist values do
|
||||
index = find_index(values, k, 0)
|
||||
{ k, quote(do: :erlang.element(unquote(index + 2), record)) }
|
||||
end
|
||||
|
||||
quote do
|
||||
@doc false
|
||||
def to_keywords(record) do
|
||||
unquote(sorted)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Implement accessors. For a declaration like:
|
||||
#
|
||||
# defrecord FileInfo, atime: nil, mtime: nil
|
||||
#
|
||||
# It will define four methods:
|
||||
#
|
||||
# def atime(record) do
|
||||
# elem(record, 1)
|
||||
# end
|
||||
#
|
||||
# def mtime(record) do
|
||||
# elem(record, 2)
|
||||
# end
|
||||
#
|
||||
# def atime(value, record) do
|
||||
# set_elem(record, 1, value)
|
||||
# end
|
||||
#
|
||||
# def mtime(record) do
|
||||
# set_elem(record, 2, value)
|
||||
# end
|
||||
#
|
||||
# def atime(callback, record) do
|
||||
# set_elem(record, 1, callback.(elem(record, 1)))
|
||||
# end
|
||||
#
|
||||
# def mtime(callback, record) do
|
||||
# set_elem(record, 2, callback.(elem(record, 2)))
|
||||
# end
|
||||
#
|
||||
defp accessors([{ :__exception__, _ }|t], 1) do
|
||||
accessors(t, 2)
|
||||
end
|
||||
|
||||
defp accessors([{ key, _default }|t], i) do
|
||||
update = binary_to_atom "update_" <> atom_to_binary(key)
|
||||
|
||||
contents = quote do
|
||||
@doc false
|
||||
def unquote(key)(record) do
|
||||
:erlang.element(unquote(i + 1), record)
|
||||
end
|
||||
|
||||
@doc false
|
||||
def unquote(key)(value, record) do
|
||||
:erlang.setelement(unquote(i + 1), record, value)
|
||||
end
|
||||
|
||||
@doc false
|
||||
def unquote(update)(function, record) do
|
||||
:erlang.setelement(unquote(i + 1), record,
|
||||
function.(:erlang.element(unquote(i + 1), record)))
|
||||
end
|
||||
end
|
||||
|
||||
[contents|accessors(t, i + 1)]
|
||||
end
|
||||
|
||||
defp accessors([], _i) do
|
||||
[]
|
||||
end
|
||||
|
||||
# Define an updater method that receives a
|
||||
# keyword list and updates the record.
|
||||
defp updater(values) do
|
||||
atom_fields =
|
||||
lc {key, _default} inlist values, do: updater_lookup(key, key, values)
|
||||
|
||||
string_fields =
|
||||
lc {key, _default} inlist values, do: updater_lookup(atom_to_binary(key), key, values)
|
||||
|
||||
atom_contents = quote do: { __MODULE__, unquote_splicing(atom_fields) }
|
||||
string_contents = quote do: { __MODULE__, unquote_splicing(string_fields) }
|
||||
|
||||
quote do
|
||||
@doc false
|
||||
def update([], record) do
|
||||
record
|
||||
end
|
||||
|
||||
def update([{key, _}|_] = keywords, record) when is_atom(key) do
|
||||
unquote(atom_contents)
|
||||
end
|
||||
def update([{key, _}|_] = keywords, record) when is_binary(key) do
|
||||
unquote(string_contents)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defp updater_lookup(k, key, values) do
|
||||
index = find_index(values, key, 0)
|
||||
|
||||
quote do
|
||||
case :lists.keyfind(unquote(k), 1, keywords) do
|
||||
false -> :erlang.element(unquote(index + 2), record)
|
||||
{_, value} -> value
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defp record_optimizable do
|
||||
quote do
|
||||
@record_optimized true
|
||||
@record_optimizable []
|
||||
@before_compile { unquote(__MODULE__), :__before_compile__ }
|
||||
@on_definition { unquote(__MODULE__), :__on_definition__ }
|
||||
end
|
||||
end
|
||||
|
||||
defp switch_recorder do
|
||||
quote do: @record_optimized false
|
||||
end
|
||||
|
||||
## Types/specs generation
|
||||
|
||||
defp core_specs(values) do
|
||||
types = lc { _, _, spec } inlist values, do: spec
|
||||
options = lc { k, _, v } inlist values, do: { k, v }
|
||||
|
||||
quote do
|
||||
unless Kernel.Typespec.defines_type?(__MODULE__, :t, 0) do
|
||||
@type t :: { __MODULE__, unquote_splicing(types) }
|
||||
end
|
||||
|
||||
unless Kernel.Typespec.defines_type?(__MODULE__, :options, 0) do
|
||||
@type options :: unquote(options) | [{String.t, term}]
|
||||
end
|
||||
|
||||
@spec new :: t
|
||||
@spec new(options) :: t
|
||||
@spec to_keywords(t) :: options
|
||||
@spec update(options, t) :: t
|
||||
@spec __record__(:name) :: atom
|
||||
@spec __record__(:fields) :: [{atom, any}]
|
||||
@spec __record__(:index, atom) :: non_neg_integer | nil
|
||||
end
|
||||
end
|
||||
|
||||
defp accessor_specs([{ :__exception__, _, _ }|t], 1, acc) do
|
||||
accessor_specs(t, 2, acc)
|
||||
end
|
||||
|
||||
defp accessor_specs([{ key, _default, spec }|t], i, acc) do
|
||||
update = binary_to_atom "update_" <> atom_to_binary(key)
|
||||
|
||||
contents = quote do
|
||||
@spec unquote(key)(t) :: unquote(spec)
|
||||
@spec unquote(key)(unquote(spec), t) :: t
|
||||
@spec unquote(update)((unquote(spec) -> unquote(spec)), t) :: t
|
||||
end
|
||||
|
||||
accessor_specs(t, i + 1, [contents | acc])
|
||||
end
|
||||
|
||||
defp accessor_specs([], _i, acc), do: acc
|
||||
|
||||
## Helpers
|
||||
|
||||
defp is_keyword(list) when is_list(list), do: :lists.all(&is_keyword_tuple/1, list)
|
||||
defp is_keyword(_), do: false
|
||||
|
||||
defp is_keyword_tuple({ x, _ }) when is_atom(x), do: true
|
||||
defp is_keyword_tuple(_), do: false
|
||||
|
||||
defp convert_value(atom) when is_atom(atom), do: { atom, nil }
|
||||
|
||||
defp convert_value({ atom, other }) when is_atom(atom), do:
|
||||
{ atom, check_value(atom, other) }
|
||||
|
||||
defp convert_value({ field, _ }), do:
|
||||
raise(ArgumentError, message: "record field name has to be an atom, got #{inspect field}")
|
||||
|
||||
defp check_value(atom, other) when is_list(other) do
|
||||
lc(i inlist other, do: check_value(atom, i))
|
||||
other
|
||||
end
|
||||
|
||||
defp check_value(atom, other) when is_tuple(other) do
|
||||
lc(i inlist tuple_to_list(other), do: check_value(atom, i))
|
||||
other
|
||||
end
|
||||
|
||||
defp check_value(atom, other) when is_function(other) do
|
||||
unless :erlang.fun_info(other, :env) == { :env, [] } and
|
||||
:erlang.fun_info(other, :type) == { :type, :external } do
|
||||
raise ArgumentError, message: "record field default value #{inspect atom} can only contain " <>
|
||||
"functions that point to an existing &Mod.fun/arity"
|
||||
end
|
||||
other
|
||||
end
|
||||
|
||||
defp check_value(atom, other) when is_reference(other) or is_pid(other) or is_port(other) do
|
||||
raise(ArgumentError, message: "record field default value #{inspect atom} cannot contain a reference, pid or port")
|
||||
end
|
||||
|
||||
defp check_value(_atom, other), do: other
|
||||
|
||||
defp find_index([{ k, _ }|_], k, i), do: i
|
||||
defp find_index([{ _, _ }|t], k, i), do: find_index(t, k, i + 1)
|
||||
defp find_index([], _k, _i), do: nil
|
||||
|
||||
defp find_spec(types, name) do
|
||||
matches = lc { k, v } inlist types, name == k, do: v
|
||||
case matches do
|
||||
[h|_] -> h
|
||||
_ -> quote do: term
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defmodule Record.DSL do
|
||||
@moduledoc false
|
||||
|
||||
@doc """
|
||||
Defines the type for each field in the record.
|
||||
|
||||
Expects a keyword list.
|
||||
"""
|
||||
defmacro record_type(opts) when is_list(opts) do
|
||||
escaped = lc { k, v } inlist opts, do: { k, Macro.escape(v) }
|
||||
|
||||
quote do
|
||||
@record_types Keyword.merge(@record_types || [], unquote(escaped))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -0,0 +1,146 @@
|
||||
defmodule Record.Backend do
|
||||
# Callback functions invoked by defrecord, defrecordp and friends.
|
||||
@moduledoc false
|
||||
|
||||
@doc """
|
||||
Splits a keywords list into fields and types.
|
||||
|
||||
This logic is shared by records and structs.
|
||||
"""
|
||||
def split_fields_and_types(tag, kv) when is_list(kv) do
|
||||
split_fields_and_types(tag, kv, [], [])
|
||||
end
|
||||
|
||||
def split_fields_and_types(tag, other) do
|
||||
raise ArgumentError, message: "#{tag} fields must be a keyword list, got: #{Macro.to_string other}"
|
||||
end
|
||||
|
||||
defp split_fields_and_types(tag, [{ field, { :::, _, [default, type] }}|t], fields, types) do
|
||||
split_fields_and_types(tag, t, [{ field, default }|fields], [{ field, type }|types])
|
||||
end
|
||||
|
||||
defp split_fields_and_types(tag, [{ field, default }|t], fields, types) when is_atom(field) do
|
||||
split_fields_and_types(tag, t, [{ field, default }|fields], [{ field, quote(do: term) }|types])
|
||||
end
|
||||
|
||||
defp split_fields_and_types(tag, [field|t], fields, types) when is_atom(field) do
|
||||
split_fields_and_types(tag, t, [{ field, nil }|fields], [{ field, quote(do: term) }|types])
|
||||
end
|
||||
|
||||
defp split_fields_and_types(tag, [other|_], _fields, _types) do
|
||||
raise ArgumentError, message: "#{tag} fields must be atoms, got: #{Macro.to_string other}"
|
||||
end
|
||||
|
||||
defp split_fields_and_types(_tag, [], fields, types) do
|
||||
{ :lists.reverse(fields), :lists.reverse(types) }
|
||||
end
|
||||
|
||||
@doc """
|
||||
Callback invoked from record/0 and record/1 macros.
|
||||
"""
|
||||
def access(atom, fields, args, caller) do
|
||||
cond do
|
||||
is_atom(args) ->
|
||||
index(atom, fields, args)
|
||||
Keyword.keyword?(args) ->
|
||||
create(atom, fields, args, caller)
|
||||
true ->
|
||||
raise ArgumentError,
|
||||
message: "expected arguments to be a compile time atom or keywords, got: #{Macro.to_string args}"
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Callback invoked from the record/2 macro.
|
||||
"""
|
||||
def access(atom, fields, record, args, caller) do
|
||||
cond do
|
||||
is_atom(args) ->
|
||||
get(atom, fields, record, args)
|
||||
Keyword.keyword?(args) ->
|
||||
update(atom, fields, record, args, caller)
|
||||
true ->
|
||||
raise ArgumentError,
|
||||
message: "expected arguments to be a compile time atom or keywords, got: #{Macro.to_string args}"
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Gets the index of field.
|
||||
"""
|
||||
def index(atom, fields, field) do
|
||||
if index = find_index(fields, field, 0) do
|
||||
index - 1 # Convert to Elixir index
|
||||
else
|
||||
raise ArgumentError, message: "record #{inspect atom} does not have the key: #{inspect field}"
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Creates a new record with the given default fields and keyword values.
|
||||
"""
|
||||
def create(atom, fields, keyword, caller) do
|
||||
in_match = caller.in_match?
|
||||
|
||||
{ match, remaining } =
|
||||
Enum.map_reduce(fields, keyword, fn({ field, default }, each_keyword) ->
|
||||
new_fields =
|
||||
case Keyword.has_key?(each_keyword, field) do
|
||||
true -> Keyword.get(each_keyword, field)
|
||||
false ->
|
||||
case in_match do
|
||||
true -> { :_, [], nil }
|
||||
false -> Macro.escape(default)
|
||||
end
|
||||
end
|
||||
|
||||
{ new_fields, Keyword.delete(each_keyword, field) }
|
||||
end)
|
||||
|
||||
case remaining do
|
||||
[] ->
|
||||
{ :{}, [], [atom|match] }
|
||||
_ ->
|
||||
keys = for { key, _ } <- remaining, do: key
|
||||
raise ArgumentError, message: "record #{inspect atom} does not have the key: #{inspect hd(keys)}"
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Updates a record given by var with the given keyword.
|
||||
"""
|
||||
def update(atom, fields, var, keyword, caller) do
|
||||
if caller.in_match? do
|
||||
raise ArgumentError, message: "cannot invoke update style macro inside match"
|
||||
end
|
||||
|
||||
Enum.reduce keyword, var, fn({ key, value }, acc) ->
|
||||
index = find_index(fields, key, 0)
|
||||
if index do
|
||||
quote do
|
||||
:erlang.setelement(unquote(index), unquote(acc), unquote(value))
|
||||
end
|
||||
else
|
||||
raise ArgumentError, message: "record #{inspect atom} does not have the key: #{inspect key}"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Gets a record key from the given var.
|
||||
"""
|
||||
def get(atom, fields, var, key) do
|
||||
index = find_index(fields, key, 0)
|
||||
if index do
|
||||
quote do
|
||||
:erlang.element(unquote(index), unquote(var))
|
||||
end
|
||||
else
|
||||
raise ArgumentError, message: "record #{inspect atom} does not have the key: #{inspect key}"
|
||||
end
|
||||
end
|
||||
|
||||
defp find_index([{ k, _ }|_], k, i), do: i + 2
|
||||
defp find_index([{ _, _ }|t], k, i), do: find_index(t, k, i + 1)
|
||||
defp find_index([], _k, _i), do: nil
|
||||
end
|
||||
@@ -0,0 +1,526 @@
|
||||
defmodule Record.Deprecated do
|
||||
@moduledoc false
|
||||
|
||||
def defrecord(name, fields, opts) do
|
||||
block = Keyword.get(opts, :do, nil)
|
||||
record_check!(fields)
|
||||
|
||||
quote do
|
||||
unquoted_fields = unquote(fields)
|
||||
|
||||
defmodule unquote(name) do
|
||||
import Record.DSL
|
||||
|
||||
@record_fields []
|
||||
@record_types []
|
||||
|
||||
Record.Deprecated.deffunctions(unquoted_fields, __ENV__)
|
||||
value = unquote(block)
|
||||
Record.Deprecated.deftypes(@record_fields, @record_types, __ENV__)
|
||||
value
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defp record_check!([{ field, { :::, _, [_, _] }}|_]) when is_atom(field) do
|
||||
raise ArgumentError, message: "typespecs are not supported inlined with defrecord, " <>
|
||||
"please use record_type instead"
|
||||
end
|
||||
|
||||
defp record_check!([_|t]), do: record_check!(t)
|
||||
defp record_check!(_), do: :ok
|
||||
|
||||
def defrecordp(name, tag, fields) do
|
||||
case recordp_split(fields, [], [], false) do
|
||||
{ :ok, fields, types, def_type } ->
|
||||
types = Macro.escape(types)
|
||||
|
||||
# bind_quoted isn't available when bootstrapping record
|
||||
quoted = quote [unquote: false] do
|
||||
Record.Deprecated.defmacros(name, fields, __ENV__, tag)
|
||||
|
||||
if def_type do
|
||||
type = binary_to_atom(atom_to_binary(name) <> "_t")
|
||||
@typep unquote(type)() :: { unquote(tag || name), unquote_splicing(types) }
|
||||
end
|
||||
end
|
||||
|
||||
quote do
|
||||
def_type = unquote(def_type)
|
||||
fields = unquote(fields)
|
||||
types = unquote(types)
|
||||
tag = unquote(tag)
|
||||
name = unquote(name)
|
||||
unquote(quoted)
|
||||
end
|
||||
|
||||
:error ->
|
||||
quote do
|
||||
name = unquote(name)
|
||||
Record.Deprecated.defmacros(name, unquote(fields), __ENV__, unquote(tag))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defp recordp_split([{ field, { :::, _, [default, type] }}|t], defaults, types, _) do
|
||||
recordp_split(t, [{ field, default }|defaults], [type|types], true)
|
||||
end
|
||||
|
||||
defp recordp_split([other|t], defaults, types, def_type) do
|
||||
recordp_split(t, [other|defaults], [quote(do: term)|types], def_type)
|
||||
end
|
||||
|
||||
defp recordp_split([], defaults, types, def_type) do
|
||||
{ :ok, :lists.reverse(defaults), :lists.reverse(types), def_type }
|
||||
end
|
||||
|
||||
defp recordp_split(_, _, _, _) do
|
||||
:error
|
||||
end
|
||||
|
||||
def deffunctions(values, env) do
|
||||
values = for value <- values, do: convert_value(value)
|
||||
escaped = Macro.escape(values)
|
||||
|
||||
contents = [
|
||||
reflection(escaped),
|
||||
initializer(escaped),
|
||||
conversions(values),
|
||||
record_optimizable(),
|
||||
updater(values),
|
||||
accessors(values, 1),
|
||||
switch_recorder()
|
||||
]
|
||||
|
||||
contents = [quote(do: @record_fields unquote(escaped))|contents]
|
||||
|
||||
# Special case for bootstrapping purposes
|
||||
if env == Macro.Env do
|
||||
Module.eval_quoted(env, contents, [], [])
|
||||
else
|
||||
Module.eval_quoted(env.module, contents, [], env.location)
|
||||
end
|
||||
end
|
||||
|
||||
def deftypes(values, types, env) do
|
||||
types = types || []
|
||||
values = for value <- values do
|
||||
{ name, default } = convert_value(value)
|
||||
{ name, default, find_spec(types, name) }
|
||||
end
|
||||
|
||||
contents = [
|
||||
core_specs(values),
|
||||
accessor_specs(values, 1, [])
|
||||
]
|
||||
|
||||
# We need to handle bootstraping
|
||||
cond do
|
||||
:code.ensure_loaded(Kernel.Typespec) != { :module, Kernel.Typespec } ->
|
||||
nil
|
||||
env == Macro.Env ->
|
||||
Module.eval_quoted(env, contents, [], [])
|
||||
true ->
|
||||
Module.eval_quoted(env.module, contents, [], env.location)
|
||||
end
|
||||
end
|
||||
|
||||
def defmacros(name, values, env, tag \\ nil)
|
||||
when is_atom(name) and is_list(values) and is_atom(tag) do
|
||||
|
||||
escaped = for value <- values do
|
||||
{ key, value } = convert_value(value)
|
||||
{ key, Macro.escape(value) }
|
||||
end
|
||||
|
||||
tag = tag || name
|
||||
|
||||
contents = quote do
|
||||
defmacrop unquote(name)() do
|
||||
Record.Deprecated.access(unquote(tag), unquote(escaped), [], __CALLER__)
|
||||
end
|
||||
|
||||
defmacrop unquote(name)(args) do
|
||||
Record.Deprecated.access(unquote(tag), unquote(escaped), args, __CALLER__)
|
||||
end
|
||||
|
||||
defmacrop unquote(name)(record, args) do
|
||||
Record.Deprecated.dispatch(unquote(tag), unquote(escaped), record, args, __CALLER__)
|
||||
end
|
||||
end
|
||||
|
||||
Module.eval_quoted(env.module, contents, [], env.location)
|
||||
end
|
||||
|
||||
## Callbacks
|
||||
|
||||
defmacro __before_compile__(_) do
|
||||
quote do
|
||||
def __record__(:optimizable), do: @record_optimizable
|
||||
end
|
||||
end
|
||||
|
||||
def __on_definition__(env, kind, name, args, _guards, _body) do
|
||||
tuple = { name, length(args) }
|
||||
module = env.module
|
||||
functions = Module.get_attribute(module, :record_optimizable)
|
||||
|
||||
functions =
|
||||
if kind in [:def] and Module.get_attribute(module, :record_optimized) do
|
||||
[tuple|functions]
|
||||
else
|
||||
List.delete(functions, tuple)
|
||||
end
|
||||
|
||||
Module.put_attribute(module, :record_optimizable, functions)
|
||||
end
|
||||
|
||||
def access(atom, fields, arg, _caller) when is_atom(arg) do
|
||||
if index = find_index(fields, arg, 0) do
|
||||
index + 1
|
||||
else
|
||||
raise ArgumentError, message: "record #{inspect atom} does not have the key: #{inspect arg}"
|
||||
end
|
||||
end
|
||||
|
||||
def access(atom, fields, keyword, caller) do
|
||||
unless is_keyword(keyword) do
|
||||
raise ArgumentError, message: "expected contents inside brackets to be a keyword list or an atom, got: #{inspect keyword}"
|
||||
end
|
||||
|
||||
in_match = caller.in_match?
|
||||
|
||||
has_underscore_value = Keyword.has_key?(keyword, :_)
|
||||
underscore_value = Keyword.get(keyword, :_, { :_, [], nil })
|
||||
keyword = Keyword.delete keyword, :_
|
||||
|
||||
iterator = fn({field, default}, each_keyword) ->
|
||||
new_fields =
|
||||
case Keyword.has_key?(each_keyword, field) do
|
||||
true -> Keyword.get(each_keyword, field)
|
||||
false ->
|
||||
case in_match or has_underscore_value do
|
||||
true -> underscore_value
|
||||
false -> Macro.escape(default)
|
||||
end
|
||||
end
|
||||
|
||||
{ new_fields, Keyword.delete(each_keyword, field) }
|
||||
end
|
||||
|
||||
{ match, remaining } = :lists.mapfoldl(iterator, keyword, fields)
|
||||
|
||||
case remaining do
|
||||
[] ->
|
||||
{ :{}, [], [atom|match] }
|
||||
_ ->
|
||||
keys = for { key, _ } <- remaining, do: key
|
||||
raise ArgumentError, message: "record #{inspect atom} does not have the key: #{inspect hd(keys)}"
|
||||
end
|
||||
end
|
||||
|
||||
def dispatch(atom, fields, record, args, caller) do
|
||||
cond do
|
||||
is_atom(args) ->
|
||||
get(atom, fields, record, args)
|
||||
is_keyword(args) ->
|
||||
update(atom, fields, record, args, caller)
|
||||
true ->
|
||||
raise ArgumentError, message: "expected arguments to be a compile time atom or keywords"
|
||||
end
|
||||
end
|
||||
|
||||
defp update(atom, fields, var, keyword, caller) do
|
||||
unless is_keyword(keyword) do
|
||||
raise ArgumentError, message: "expected arguments to be compile time keywords"
|
||||
end
|
||||
|
||||
if caller.in_match? do
|
||||
raise ArgumentError, message: "cannot invoke update style macro inside match context"
|
||||
end
|
||||
|
||||
Enum.reduce keyword, var, fn({ key, value }, acc) ->
|
||||
index = find_index(fields, key, 0)
|
||||
if index do
|
||||
quote do
|
||||
:erlang.setelement(unquote(index + 2), unquote(acc), unquote(value))
|
||||
end
|
||||
else
|
||||
raise ArgumentError, message: "record #{inspect atom} does not have the key: #{inspect key}"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defp get(atom, fields, var, key) do
|
||||
index = find_index(fields, key, 0)
|
||||
if index do
|
||||
quote do
|
||||
:erlang.element(unquote(index + 2), unquote(var))
|
||||
end
|
||||
else
|
||||
raise ArgumentError, message: "record #{inspect atom} does not have the key: #{inspect key}"
|
||||
end
|
||||
end
|
||||
|
||||
## Function generation
|
||||
|
||||
defp reflection(values) do
|
||||
quoted = for { k, _ } <- values do
|
||||
index = find_index(values, k, 0)
|
||||
quote do
|
||||
def __record__(:index, unquote(k)), do: unquote(index + 1)
|
||||
end
|
||||
end
|
||||
|
||||
quote do
|
||||
unquote(quoted)
|
||||
|
||||
@doc false
|
||||
def __record__(:index, _), do: nil
|
||||
@doc false
|
||||
def __record__(:index, arg, _), do: __record__(:index, arg)
|
||||
|
||||
@doc false
|
||||
def __record__(kind, _), do: __record__(kind)
|
||||
|
||||
@doc false
|
||||
def __record__(:name), do: __MODULE__
|
||||
def __record__(:fields), do: unquote(values)
|
||||
end
|
||||
end
|
||||
|
||||
defp initializer(values) do
|
||||
defaults = for { _, value } <- values, do: value
|
||||
|
||||
# For each value, define a piece of code that will receive
|
||||
# an ordered dict of options (opts) and it will try to fetch
|
||||
# the given key from the ordered dict, falling back to the
|
||||
# default value if one does not exist.
|
||||
atom_selective = for { k, v } <- values, do: initialize_lookup(k, v)
|
||||
string_selective = for { k, v } <- values, do: initialize_lookup(atom_to_binary(k), v)
|
||||
|
||||
quote do
|
||||
@doc false
|
||||
def new(), do: new([])
|
||||
|
||||
@doc false
|
||||
def new([]), do: { __MODULE__, unquote_splicing(defaults) }
|
||||
def new([{key, _}|_] = opts) when is_atom(key), do: { __MODULE__, unquote_splicing(atom_selective) }
|
||||
def new([{key, _}|_] = opts) when is_binary(key), do: { __MODULE__, unquote_splicing(string_selective) }
|
||||
end
|
||||
end
|
||||
|
||||
defp initialize_lookup(k, v) do
|
||||
quote do
|
||||
case :lists.keyfind(unquote(k), 1, opts) do
|
||||
false -> unquote(v)
|
||||
{_, v} -> v
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defp conversions(values) do
|
||||
sorted = for { k, _ } <- values do
|
||||
index = find_index(values, k, 0)
|
||||
{ k, quote(do: :erlang.element(unquote(index + 2), record)) }
|
||||
end
|
||||
|
||||
quote do
|
||||
@doc false
|
||||
def to_keywords(record) do
|
||||
unquote(sorted)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defp accessors([{ :__exception__, _ }|t], 1) do
|
||||
accessors(t, 2)
|
||||
end
|
||||
|
||||
defp accessors([{ key, _default }|t], i) do
|
||||
update = binary_to_atom "update_" <> atom_to_binary(key)
|
||||
|
||||
contents = quote do
|
||||
@doc false
|
||||
def unquote(key)(record) do
|
||||
:erlang.element(unquote(i + 1), record)
|
||||
end
|
||||
|
||||
@doc false
|
||||
def unquote(key)(value, record) do
|
||||
:erlang.setelement(unquote(i + 1), record, value)
|
||||
end
|
||||
|
||||
@doc false
|
||||
def unquote(update)(function, record) do
|
||||
:erlang.setelement(unquote(i + 1), record,
|
||||
function.(:erlang.element(unquote(i + 1), record)))
|
||||
end
|
||||
end
|
||||
|
||||
[contents|accessors(t, i + 1)]
|
||||
end
|
||||
|
||||
defp accessors([], _i) do
|
||||
[]
|
||||
end
|
||||
|
||||
# Define an updater method that receives a
|
||||
# keyword list and updates the record.
|
||||
defp updater(values) do
|
||||
atom_fields =
|
||||
for {key, _default} <- values, do: updater_lookup(key, key, values)
|
||||
|
||||
string_fields =
|
||||
for {key, _default} <- values, do: updater_lookup(atom_to_binary(key), key, values)
|
||||
|
||||
atom_contents = quote do: { __MODULE__, unquote_splicing(atom_fields) }
|
||||
string_contents = quote do: { __MODULE__, unquote_splicing(string_fields) }
|
||||
|
||||
quote do
|
||||
@doc false
|
||||
def update([], record) do
|
||||
record
|
||||
end
|
||||
|
||||
def update([{key, _}|_] = keywords, record) when is_atom(key) do
|
||||
unquote(atom_contents)
|
||||
end
|
||||
def update([{key, _}|_] = keywords, record) when is_binary(key) do
|
||||
unquote(string_contents)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defp updater_lookup(k, key, values) do
|
||||
index = find_index(values, key, 0)
|
||||
|
||||
quote do
|
||||
case :lists.keyfind(unquote(k), 1, keywords) do
|
||||
false -> :erlang.element(unquote(index + 2), record)
|
||||
{_, value} -> value
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defp record_optimizable do
|
||||
quote do
|
||||
@record_optimized true
|
||||
@record_optimizable []
|
||||
@before_compile { unquote(__MODULE__), :__before_compile__ }
|
||||
@on_definition { unquote(__MODULE__), :__on_definition__ }
|
||||
end
|
||||
end
|
||||
|
||||
defp switch_recorder do
|
||||
quote do: @record_optimized false
|
||||
end
|
||||
|
||||
## Types/specs generation
|
||||
|
||||
defp core_specs(values) do
|
||||
types = for { _, _, spec } <- values, do: spec
|
||||
options = for { k, _, v } <- values, do: { k, v }
|
||||
|
||||
quote do
|
||||
unless Kernel.Typespec.defines_type?(__MODULE__, :t, 0) do
|
||||
@type t :: { __MODULE__, unquote_splicing(types) }
|
||||
end
|
||||
|
||||
unless Kernel.Typespec.defines_type?(__MODULE__, :options, 0) do
|
||||
@type options :: unquote(options) | [{String.t, term}]
|
||||
end
|
||||
|
||||
@spec new :: t
|
||||
@spec new(options) :: t
|
||||
@spec to_keywords(t) :: options
|
||||
@spec update(options, t) :: t
|
||||
@spec __record__(:name) :: atom
|
||||
@spec __record__(:fields) :: [{atom, any}]
|
||||
@spec __record__(:index, atom) :: non_neg_integer | nil
|
||||
end
|
||||
end
|
||||
|
||||
defp accessor_specs([{ :__exception__, _, _ }|t], 1, acc) do
|
||||
accessor_specs(t, 2, acc)
|
||||
end
|
||||
|
||||
defp accessor_specs([{ key, _default, spec }|t], i, acc) do
|
||||
update = binary_to_atom "update_" <> atom_to_binary(key)
|
||||
|
||||
contents = quote do
|
||||
@spec unquote(key)(t) :: unquote(spec)
|
||||
@spec unquote(key)(unquote(spec), t) :: t
|
||||
@spec unquote(update)((unquote(spec) -> unquote(spec)), t) :: t
|
||||
end
|
||||
|
||||
accessor_specs(t, i + 1, [contents | acc])
|
||||
end
|
||||
|
||||
defp accessor_specs([], _i, acc), do: acc
|
||||
|
||||
## Helpers
|
||||
|
||||
defp is_keyword(list) when is_list(list), do: :lists.all(&is_keyword_tuple/1, list)
|
||||
defp is_keyword(_), do: false
|
||||
|
||||
defp is_keyword_tuple({ x, _ }) when is_atom(x), do: true
|
||||
defp is_keyword_tuple(_), do: false
|
||||
|
||||
defp convert_value(atom) when is_atom(atom), do: { atom, nil }
|
||||
|
||||
defp convert_value({ atom, other }) when is_atom(atom), do:
|
||||
{ atom, check_value(atom, other) }
|
||||
|
||||
defp convert_value({ field, _ }), do:
|
||||
raise(ArgumentError, message: "record field name has to be an atom, got #{inspect field}")
|
||||
|
||||
defp check_value(atom, other) when is_list(other) do
|
||||
for(i <- other, do: check_value(atom, i))
|
||||
other
|
||||
end
|
||||
|
||||
defp check_value(atom, other) when is_tuple(other) do
|
||||
for(i <- tuple_to_list(other), do: check_value(atom, i))
|
||||
other
|
||||
end
|
||||
|
||||
defp check_value(atom, other) when is_function(other) do
|
||||
unless :erlang.fun_info(other, :env) == { :env, [] } and
|
||||
:erlang.fun_info(other, :type) == { :type, :external } do
|
||||
raise ArgumentError, message: "record field default value #{inspect atom} can only contain " <>
|
||||
"functions that point to an existing &Mod.fun/arity"
|
||||
end
|
||||
other
|
||||
end
|
||||
|
||||
defp check_value(atom, other) when is_reference(other) or is_pid(other) or is_port(other) do
|
||||
raise(ArgumentError, message: "record field default value #{inspect atom} cannot contain a reference, pid or port")
|
||||
end
|
||||
|
||||
defp check_value(_atom, other), do: other
|
||||
|
||||
defp find_index([{ k, _ }|_], k, i), do: i
|
||||
defp find_index([{ _, _ }|t], k, i), do: find_index(t, k, i + 1)
|
||||
defp find_index([], _k, _i), do: nil
|
||||
|
||||
defp find_spec(types, name) do
|
||||
matches = for { k, v } <- types, name == k, do: v
|
||||
case matches do
|
||||
[h|_] -> h
|
||||
_ -> quote do: term
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defmodule Record.DSL do
|
||||
@moduledoc false
|
||||
|
||||
defmacro record_type(opts) when is_list(opts) do
|
||||
escaped = for { k, v } <- opts, do: { k, Macro.escape(v) }
|
||||
|
||||
quote do
|
||||
@record_types Keyword.merge(@record_types || [], unquote(escaped))
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -3,7 +3,7 @@ defmodule Record.Extractor do
|
||||
|
||||
# Retrieve a record definition from an Erlang file using
|
||||
# the same lookup as the *include* attribute from Erlang modules.
|
||||
def retrieve(name, from: file) when is_binary(file) do
|
||||
def extract(name, from: file) when is_binary(file) do
|
||||
file = String.to_char_list!(file)
|
||||
|
||||
realfile =
|
||||
@@ -12,26 +12,26 @@ defmodule Record.Extractor do
|
||||
realfile -> realfile
|
||||
end
|
||||
|
||||
retrieve_record(name, realfile)
|
||||
extract_record(name, realfile)
|
||||
end
|
||||
|
||||
# Retrieve a record definition from an Erlang file using
|
||||
# the same lookup as the *include_lib* attribute from Erlang modules.
|
||||
def retrieve(name, from_lib: file) when is_binary(file) do
|
||||
def extract(name, from_lib: file) when is_binary(file) do
|
||||
[app|path] = :filename.split(String.to_char_list!(file))
|
||||
|
||||
case :code.lib_dir(list_to_atom(app)) do
|
||||
{ :error, _ } ->
|
||||
raise ArgumentError, message: "lib file #{file} could not be found"
|
||||
libpath ->
|
||||
retrieve_record name, :filename.join([libpath|path])
|
||||
extract_record name, :filename.join([libpath|path])
|
||||
end
|
||||
end
|
||||
|
||||
# Retrieve the record with the given name from the given file
|
||||
defp retrieve_record(name, file) do
|
||||
defp extract_record(name, file) do
|
||||
form = read_file(file)
|
||||
records = retrieve_records(form)
|
||||
records = extract_records(form)
|
||||
if record = List.keyfind(records, name, 0) do
|
||||
parse_record(record, form)
|
||||
else
|
||||
@@ -39,9 +39,9 @@ defmodule Record.Extractor do
|
||||
end
|
||||
end
|
||||
|
||||
# Parse the given file and retrieve all existent records.
|
||||
defp retrieve_records(form) do
|
||||
lc { :attribute, _, :record, record } inlist form, do: record
|
||||
# Parse the given file and extract all existent records.
|
||||
defp extract_records(form) do
|
||||
for { :attribute, _, :record, record } <- form, do: record
|
||||
end
|
||||
|
||||
# Read a file and return its abstract syntax form that also
|
||||
|
||||
+97
-64
@@ -27,7 +27,6 @@ defmodule Regex do
|
||||
* `firstline` (f) - forces the unanchored pattern to match before or at the first
|
||||
newline, though the matched text may continue over the newline
|
||||
* `ungreedy` (r) - inverts the "greediness" of the regexp
|
||||
* `groups` (g) - compiles with info about groups available
|
||||
|
||||
The options not available are:
|
||||
|
||||
@@ -37,9 +36,31 @@ defmodule Regex do
|
||||
* `newline` - not available, use `(*CR)` or `(*LF)` or `(*CRLF)` or `(*ANYCRLF)`
|
||||
or `(*ANY)` at the beginning of the regexp according to the re documentation
|
||||
|
||||
## Captures
|
||||
|
||||
Many functions in this module allows what to capture in a regex
|
||||
match via the `:capture` option. The supported values are:
|
||||
|
||||
* `:all` - all captured subpatterns including the complete matching string.
|
||||
This is the default;
|
||||
|
||||
* `:first` - only the first captured subpattern, which is always the complete
|
||||
matching part of the string. All explicitly captured subpatterns are
|
||||
discarded;
|
||||
|
||||
* `:all_but_first`- all but the first matching subpattern, i.e. all explicitly
|
||||
captured subpatterns, but not the complete matching part of
|
||||
the string;
|
||||
|
||||
* `:none` - do not return matching subpatterns at all;
|
||||
|
||||
* `:groups` - captures only named captures in the Regex;
|
||||
|
||||
* `list(binary)` - a list of named captures to capture;
|
||||
|
||||
"""
|
||||
|
||||
defrecordp :regex, Regex, [:re_pattern, :source, :options, :groups]
|
||||
defrecordp :regex, Regex, [:re_pattern, :source, :options]
|
||||
@type t :: { Regex, term, binary, binary, [atom] | nil }
|
||||
|
||||
defexception CompileError, message: "regex could not be compiled"
|
||||
@@ -58,6 +79,7 @@ defmodule Regex do
|
||||
|
||||
iex> Regex.compile("foo")
|
||||
{:ok, ~r"foo"}
|
||||
|
||||
iex> Regex.compile("*foo")
|
||||
{:error, {'nothing to repeat', 0}}
|
||||
|
||||
@@ -82,12 +104,9 @@ defmodule Regex do
|
||||
end
|
||||
|
||||
defp compile(source, opts, doc_opts) when is_binary(source) do
|
||||
re_opts = opts -- [:groups]
|
||||
groups = if opts != re_opts, do: parse_groups(source)
|
||||
|
||||
case :re.compile(source, re_opts) do
|
||||
case :re.compile(source, opts) do
|
||||
{ :ok, re_pattern } ->
|
||||
{ :ok, regex(re_pattern: re_pattern, source: source, options: doc_opts, groups: groups) }
|
||||
{ :ok, regex(re_pattern: re_pattern, source: source, options: doc_opts) }
|
||||
error ->
|
||||
error
|
||||
end
|
||||
@@ -111,12 +130,12 @@ defmodule Regex do
|
||||
|
||||
iex> Regex.match?(~r/foo/, "foo")
|
||||
true
|
||||
|
||||
iex> Regex.match?(~r/foo/, "bar")
|
||||
false
|
||||
|
||||
"""
|
||||
def match?(regex(re_pattern: compiled), string) do
|
||||
_ = return_for(string)
|
||||
def match?(regex(re_pattern: compiled), string) when is_binary(string) do
|
||||
:re.run(string, compiled, [{ :capture, :none }]) == :match
|
||||
end
|
||||
|
||||
@@ -127,6 +146,7 @@ defmodule Regex do
|
||||
|
||||
iex> Regex.regex?(~r/foo/)
|
||||
true
|
||||
|
||||
iex> Regex.regex?(0)
|
||||
false
|
||||
|
||||
@@ -138,27 +158,32 @@ defmodule Regex do
|
||||
Runs the regular expression against the given string until the first match.
|
||||
It returns a list with all captures or `nil` if no match occurred.
|
||||
|
||||
When the option `:capture` is set to `:groups`, it will capture all
|
||||
the groups in the regex.
|
||||
## Options
|
||||
|
||||
* `:return` - Set to `:index` to return indexes. Defaults to `:binary`;
|
||||
* `:capture` - What to capture in the result. Check the moduledoc for Regex
|
||||
to see the possible capture values;
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Regex.run(~r/c(d)/, "abcd")
|
||||
["cd", "d"]
|
||||
|
||||
iex> Regex.run(~r/e/, "abcd")
|
||||
nil
|
||||
|
||||
iex> Regex.run(~r/c(d)/, "abcd", return: :index)
|
||||
[{2,2},{3,1}]
|
||||
|
||||
"""
|
||||
def run(regex, string, options \\ [])
|
||||
|
||||
def run(regex(re_pattern: compiled, groups: groups), string, options) do
|
||||
return = Keyword.get(options, :return, return_for(string))
|
||||
def run(regex(re_pattern: compiled) = regex, string, options) when is_binary(string) do
|
||||
return = Keyword.get(options, :return, :binary)
|
||||
|
||||
captures =
|
||||
case Keyword.get(options, :capture, :all) do
|
||||
:groups -> groups || raise ArgumentError, message: "regex was not compiled with g"
|
||||
:groups -> groups(regex)
|
||||
others -> others
|
||||
end
|
||||
|
||||
@@ -171,22 +196,25 @@ defmodule Regex do
|
||||
|
||||
@doc """
|
||||
Returns the given captures as a keyword list or `nil` if no captures
|
||||
are found. Requires the regex to be compiled with the groups option.
|
||||
are found. The option `:return` can be set to `:index` to get indexes
|
||||
back.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Regex.named_captures(~r/c(?<foo>d)/g, "abcd")
|
||||
iex> Regex.named_captures(~r/c(?<foo>d)/, "abcd")
|
||||
[foo: "d"]
|
||||
iex> Regex.named_captures(~r/a(?<foo>b)c(?<bar>d)/g, "abcd")
|
||||
[foo: "b", bar: "d"]
|
||||
iex> Regex.named_captures(~r/a(?<foo>b)c(?<bar>d)/g, "efgh")
|
||||
|
||||
iex> Regex.named_captures(~r/a(?<foo>b)c(?<bar>d)/, "abcd")
|
||||
[bar: "d", foo: "b"]
|
||||
|
||||
iex> Regex.named_captures(~r/a(?<foo>b)c(?<bar>d)/, "efgh")
|
||||
nil
|
||||
|
||||
"""
|
||||
def named_captures(regex(groups: groups) = regex, string, options \\ []) do
|
||||
options = Keyword.put_new(options, :capture, :groups)
|
||||
def named_captures(regex, string, options \\ []) when is_binary(string) do
|
||||
options = [capture: :groups] ++ options
|
||||
results = run(regex, string, options)
|
||||
if results, do: Enum.zip(groups, results)
|
||||
if results, do: Enum.zip(groups(regex), results)
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -227,12 +255,13 @@ defmodule Regex do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Regex.groups(~r/(?<foo>bar)/g)
|
||||
iex> Regex.groups(~r/(?<foo>bar)/)
|
||||
[:foo]
|
||||
|
||||
"""
|
||||
def groups(regex(groups: groups)) do
|
||||
groups
|
||||
def groups(regex(re_pattern: re_pattern)) do
|
||||
{ :namelist, groups } = :re.inspect(re_pattern, :namelist)
|
||||
for group <- groups, do: binary_to_atom(group)
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -241,30 +270,32 @@ defmodule Regex do
|
||||
where each entry in the primary list represents a match and each
|
||||
entry in the secondary list represents the captured contents.
|
||||
|
||||
The captured contents defaults to `:all`, which includes the whole
|
||||
regex match and each capture.
|
||||
## Options
|
||||
|
||||
When the option `:capture` is set to `:groups`, it will capture all
|
||||
the groups in the regex.
|
||||
* `:return` - Set to `:index` to return indexes. Defaults to `:binary`;
|
||||
* `:capture` - What to capture in the result. Check the moduledoc for Regex
|
||||
to see the possible capture values;
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Regex.scan(~r/c(d|e)/, "abcd abce")
|
||||
[["cd", "d"], ["ce", "e"]]
|
||||
|
||||
iex> Regex.scan(~r/c(?:d|e)/, "abcd abce")
|
||||
[["cd"], ["ce"]]
|
||||
|
||||
iex> Regex.scan(~r/e/, "abcd")
|
||||
[]
|
||||
|
||||
"""
|
||||
def scan(regex, string, options \\ [])
|
||||
|
||||
def scan(regex(re_pattern: compiled, groups: groups), string, options) do
|
||||
return = Keyword.get(options, :return, return_for(string))
|
||||
def scan(regex(re_pattern: compiled) = regex, string, options) when is_binary(string) do
|
||||
return = Keyword.get(options, :return, :binary)
|
||||
|
||||
captures =
|
||||
case Keyword.get(options, :capture, :all) do
|
||||
:groups -> groups || raise ArgumentError, message: "regex was not compiled with g"
|
||||
:groups -> groups(regex)
|
||||
others -> others
|
||||
end
|
||||
|
||||
@@ -278,24 +309,39 @@ defmodule Regex do
|
||||
|
||||
@doc """
|
||||
Splits the given target into the number of parts specified.
|
||||
If no number of parts is given, it defaults to `:infinity`.
|
||||
|
||||
## Options
|
||||
|
||||
* `:global` - splits the string in all parts unless set to `false`,
|
||||
which leads the string to be split in 2. Defaults to true;
|
||||
|
||||
* `:parts` - when specified, splits the string into maximum the number
|
||||
of given parts;
|
||||
|
||||
* `:trim` - when true, remove blank strings from the result;
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Regex.split(~r/-/, "a-b-c")
|
||||
["a","b","c"]
|
||||
|
||||
iex> Regex.split(~r/-/, "a-b-c", [parts: 2])
|
||||
["a","b-c"]
|
||||
|
||||
iex> Regex.split(~r/-/, "abc")
|
||||
["abc"]
|
||||
|
||||
iex> Regex.split(~r//, "abc")
|
||||
["a", "b", "c", ""]
|
||||
|
||||
iex> Regex.split(~r//, "abc", trim: true)
|
||||
["a", "b", "c"]
|
||||
|
||||
"""
|
||||
|
||||
def split(regex, string, options \\ [])
|
||||
|
||||
def split(regex(re_pattern: compiled), string, options) do
|
||||
def split(regex(re_pattern: compiled), string, options) when is_binary(string) do
|
||||
parts =
|
||||
cond do
|
||||
Keyword.get(options, :global) == false -> 2
|
||||
@@ -303,12 +349,11 @@ defmodule Regex do
|
||||
true -> :infinity
|
||||
end
|
||||
|
||||
return = Keyword.get(options, :return, return_for(string))
|
||||
opts = [return: return, parts: parts]
|
||||
opts = [return: :binary, parts: parts]
|
||||
splits = :re.split(string, compiled, opts)
|
||||
|
||||
if Keyword.get(options, :trim, false) do
|
||||
lc split inlist splits, split != "", do: split
|
||||
for split <- splits, split != "", do: split
|
||||
else
|
||||
splits
|
||||
end
|
||||
@@ -327,20 +372,25 @@ defmodule Regex do
|
||||
|
||||
iex> Regex.replace(~r/d/, "abc", "d")
|
||||
"abc"
|
||||
|
||||
iex> Regex.replace(~r/b/, "abc", "d")
|
||||
"adc"
|
||||
|
||||
iex> Regex.replace(~r/b/, "abc", "[&]")
|
||||
"a[b]c"
|
||||
|
||||
iex> Regex.replace(~r/b/, "abc", "[\\&]")
|
||||
"a[&]c"
|
||||
|
||||
iex> Regex.replace(~r/(b)/, "abc", "[\\1]")
|
||||
"a[b]c"
|
||||
|
||||
"""
|
||||
def replace(regex(re_pattern: compiled), string, replacement, options \\ []) do
|
||||
opts = if Keyword.get(options, :global) != false, do: [:global], else: []
|
||||
return = Keyword.get(options, :return, return_for(string))
|
||||
opts = [{ :return, return }|opts]
|
||||
def replace(regex, string, replacement, options \\ [])
|
||||
|
||||
def replace(regex(re_pattern: compiled), string, replacement, options) when is_binary(string) do
|
||||
opts = if Keyword.get(options, :global) != false, do: [:global], else: []
|
||||
opts = [{ :return, :binary }|opts]
|
||||
:re.replace(string, compiled, replacement, opts)
|
||||
end
|
||||
|
||||
@@ -354,13 +404,14 @@ defmodule Regex do
|
||||
|
||||
iex> Regex.escape(".")
|
||||
"\\."
|
||||
|
||||
iex> Regex.escape("\\what if")
|
||||
"\\\\what\\ if"
|
||||
|
||||
"""
|
||||
@spec escape(String.t) :: String.t
|
||||
def escape(string) do
|
||||
:re.replace(string, @escape_pattern, "\\\\&", [:global, { :return, return_for(string) }])
|
||||
def escape(string) when is_binary(string) do
|
||||
:re.replace(string, @escape_pattern, "\\\\&", [:global, { :return, :binary }])
|
||||
end
|
||||
|
||||
# Helpers
|
||||
@@ -377,35 +428,17 @@ defmodule Regex do
|
||||
|
||||
# Private Helpers
|
||||
|
||||
defp return_for(element) when is_binary(element), do: :binary
|
||||
defp return_for(element) when is_list(element) do
|
||||
IO.write :stderr, "Passing char lists to Regex is deprecated, please use binaries instead\n#{Exception.format_stacktrace}"
|
||||
:list
|
||||
end
|
||||
|
||||
defp translate_options(<<?u, t :: binary>>) do
|
||||
IO.write "The /u flag for regular expressions is no longer needed\n#{Exception.format_stacktrace}"
|
||||
defp translate_options(<<?g, t :: binary>>) do
|
||||
IO.write :stderr, "The /g flag for regular expressions is no longer needed\n#{Exception.format_stacktrace}"
|
||||
translate_options(t)
|
||||
end
|
||||
|
||||
defp translate_options(<<?i, t :: binary>>), do: [:caseless|translate_options(t)]
|
||||
defp translate_options(<<?x, t :: binary>>), do: [:extended|translate_options(t)]
|
||||
defp translate_options(<<?f, t :: binary>>), do: [:firstline|translate_options(t)]
|
||||
defp translate_options(<<?r, t :: binary>>), do: [:ungreedy|translate_options(t)]
|
||||
defp translate_options(<<?s, t :: binary>>), do: [:dotall, {:newline, :anycrlf}|translate_options(t)]
|
||||
defp translate_options(<<?m, t :: binary>>), do: [:multiline|translate_options(t)]
|
||||
defp translate_options(<<?g, t :: binary>>), do: [:groups|translate_options(t)]
|
||||
defp translate_options(<<>>), do: []
|
||||
defp translate_options(rest), do: { :error, rest }
|
||||
|
||||
{ :ok, pattern } = :re.compile(~S"\(\?<(?<G>[^>]*)>")
|
||||
@groups_pattern pattern
|
||||
|
||||
defp parse_groups(source) do
|
||||
options = [:global, {:capture, ['G'], :binary}]
|
||||
case :re.run(source, @groups_pattern, options) do
|
||||
:nomatch -> []
|
||||
{ :match, results } ->
|
||||
lc [group] inlist results, do: binary_to_atom(group)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
+41
-45
@@ -12,35 +12,27 @@ defmodule Set do
|
||||
|
||||
HashSet.new #=> creates an empty HashSet
|
||||
|
||||
For simplicity's sake, in the examples below every time
|
||||
`new` is used, it implies one of the module-specific
|
||||
calls like above.
|
||||
In the examples below, `set_impl` means a specific
|
||||
`Set` implementation, for example `HashSet`.
|
||||
|
||||
## Protocols
|
||||
|
||||
Sets are required to implement the `Enumerable` protocol,
|
||||
allowing one to write:
|
||||
|
||||
Enum.each(set, fn k ->
|
||||
IO.inspect k
|
||||
end)
|
||||
Sets are required to implement both `Enumerable` and `Collectable`
|
||||
protocols.
|
||||
|
||||
## Match
|
||||
|
||||
Sets are required to implement all operations
|
||||
using the match (`===`) operator. Any deviation from
|
||||
this behaviour should be avoided and explicitly documented.
|
||||
Sets are required to implement all operations using the match (`===`)
|
||||
operator.
|
||||
"""
|
||||
|
||||
use Behaviour
|
||||
|
||||
@type value :: any
|
||||
@type values :: [ value ]
|
||||
@type t :: tuple
|
||||
@type t :: map | tuple
|
||||
|
||||
defcallback new :: t
|
||||
defcallback new(Enum.t) :: t
|
||||
defcallback new(Enum.t, (any -> any)) :: t
|
||||
defcallback delete(t, value) :: t
|
||||
defcallback difference(t, t) :: t
|
||||
defcallback disjoint?(t, t) :: boolean
|
||||
@@ -49,7 +41,6 @@ defmodule Set do
|
||||
defcallback intersection(t, t) :: t
|
||||
defcallback member?(t, value) :: boolean
|
||||
defcallback put(t, value) :: t
|
||||
defcallback reduce(t, Enumerable.acc, Enumerable.reducer) :: Enumerable.result
|
||||
defcallback size(t) :: non_neg_integer
|
||||
defcallback subset?(t, t) :: boolean
|
||||
defcallback to_list(t) :: list()
|
||||
@@ -57,10 +48,13 @@ defmodule Set do
|
||||
|
||||
defmacrop target(set) do
|
||||
quote do
|
||||
if is_tuple(unquote(set)) do
|
||||
elem(unquote(set), 0)
|
||||
else
|
||||
unsupported_set(unquote(set))
|
||||
case unquote(set) do
|
||||
%{__struct__: x} when is_atom(x) ->
|
||||
x
|
||||
x when is_tuple(x) ->
|
||||
elem(x, 0)
|
||||
x ->
|
||||
unsupported_set(x)
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -70,13 +64,14 @@ defmodule Set do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> s = HashSet.new([1, 2, 3])
|
||||
...> Set.delete(s, 4) |> Enum.sort
|
||||
iex> s = Enum.into([1, 2, 3], set_impl.new)
|
||||
iex> Set.delete(s, 4) |> Enum.sort
|
||||
[1, 2, 3]
|
||||
|
||||
iex> s = HashSet.new([1, 2, 3])
|
||||
...> Set.delete(s, 2) |> Enum.sort
|
||||
iex> s = Enum.into([1, 2, 3], set_impl.new)
|
||||
iex> Set.delete(s, 2) |> Enum.sort
|
||||
[1, 3]
|
||||
|
||||
"""
|
||||
@spec delete(t, value) :: t
|
||||
def delete(set, value) do
|
||||
@@ -92,7 +87,7 @@ defmodule Set do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Set.difference(HashSet.new([1,2]), HashSet.new([2,3,4])) |> Enum.sort
|
||||
iex> Set.difference(Enum.into([1,2], set_impl.new), Enum.into([2,3,4], set_impl.new)) |> Enum.sort
|
||||
[1]
|
||||
|
||||
"""
|
||||
@@ -119,9 +114,10 @@ defmodule Set do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Set.disjoint?(HashSet.new([1, 2]), HashSet.new([3, 4]))
|
||||
iex> Set.disjoint?(Enum.into([1, 2], set_impl.new), Enum.into([3, 4], set_impl.new))
|
||||
true
|
||||
iex> Set.disjoint?(HashSet.new([1, 2]), HashSet.new([2, 3]))
|
||||
|
||||
iex> Set.disjoint?(Enum.into([1, 2], set_impl.new), Enum.into([2, 3], set_impl.new))
|
||||
false
|
||||
|
||||
"""
|
||||
@@ -142,9 +138,7 @@ defmodule Set do
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns an empty set of the same type as `set`.
|
||||
"""
|
||||
@doc false
|
||||
@spec empty(t) :: t
|
||||
def empty(set) do
|
||||
target(set).empty(set)
|
||||
@@ -159,10 +153,10 @@ defmodule Set do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Set.equal?(HashSet.new([1, 2]), HashSet.new([2, 1, 1]))
|
||||
iex> Set.equal?(Enum.into([1, 2], set_impl.new), Enum.into([2, 1, 1], set_impl.new))
|
||||
true
|
||||
|
||||
iex> Set.equal?(HashSet.new([1, 2]), HashSet.new([3, 4]))
|
||||
|
||||
iex> Set.equal?(Enum.into([1, 2], set_impl.new), Enum.into([3, 4], set_impl.new))
|
||||
false
|
||||
|
||||
"""
|
||||
@@ -192,10 +186,10 @@ defmodule Set do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Set.intersection(HashSet.new([1,2]), HashSet.new([2,3,4])) |> Enum.sort
|
||||
iex> Set.intersection(Enum.into([1,2], set_impl.new), Enum.into([2,3,4], set_impl.new)) |> Enum.sort
|
||||
[2]
|
||||
|
||||
iex> Set.intersection(HashSet.new([1,2]), HashSet.new([3,4])) |> Enum.sort
|
||||
iex> Set.intersection(Enum.into([1,2], set_impl.new), Enum.into([3,4], set_impl.new)) |> Enum.sort
|
||||
[]
|
||||
|
||||
"""
|
||||
@@ -207,7 +201,7 @@ defmodule Set do
|
||||
if target1 == target2 do
|
||||
target1.intersection(set1, set2)
|
||||
else
|
||||
target1.reduce(set1, { :cont, target1.empty(set1) }, fn v, acc ->
|
||||
target1.reduce(set1, { :cont, Collectable.empty(set1) }, fn v, acc ->
|
||||
{ :cont, if(target2.member?(set2, v), do: target1.put(acc, v), else: acc) }
|
||||
end) |> elem(1)
|
||||
end
|
||||
@@ -218,10 +212,10 @@ defmodule Set do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Set.member?(HashSet.new([1, 2, 3]), 2)
|
||||
iex> Set.member?(Enum.into([1, 2, 3], set_impl.new), 2)
|
||||
true
|
||||
|
||||
iex> Set.member?(HashSet.new([1, 2, 3]), 4)
|
||||
iex> Set.member?(Enum.into([1, 2, 3], set_impl.new), 4)
|
||||
false
|
||||
|
||||
"""
|
||||
@@ -235,10 +229,10 @@ defmodule Set do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Set.put(HashSet.new([1, 2, 3]), 3) |> Enum.sort
|
||||
iex> Set.put(Enum.into([1, 2, 3], set_impl.new), 3) |> Enum.sort
|
||||
[1, 2, 3]
|
||||
|
||||
iex> Set.put(HashSet.new([1, 2, 3]), 4) |> Enum.sort
|
||||
iex> Set.put(Enum.into([1, 2, 3], set_impl.new), 4) |> Enum.sort
|
||||
[1, 2, 3, 4]
|
||||
|
||||
"""
|
||||
@@ -252,7 +246,7 @@ defmodule Set do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Set.size(HashSet.new([1, 2, 3]))
|
||||
iex> Set.size(Enum.into([1, 2, 3], set_impl.new))
|
||||
3
|
||||
|
||||
"""
|
||||
@@ -270,10 +264,12 @@ defmodule Set do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Set.subset?(HashSet.new([1, 2]), HashSet.new([1, 2, 3]))
|
||||
iex> Set.subset?(Enum.into([1, 2], set_impl.new), Enum.into([1, 2, 3], set_impl.new))
|
||||
true
|
||||
iex> Set.subset?(HashSet.new([1, 2, 3]), HashSet.new([1, 2]))
|
||||
|
||||
iex> Set.subset?(Enum.into([1, 2, 3], set_impl.new), Enum.into([1, 2], set_impl.new))
|
||||
false
|
||||
|
||||
"""
|
||||
@spec subset?(t, t) :: boolean
|
||||
def subset?(set1, set2) do
|
||||
@@ -292,7 +288,7 @@ defmodule Set do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> HashSet.to_list(HashSet.new([1, 2, 3])) |> Enum.sort
|
||||
iex> set_impl.to_list(Enum.into([1, 2, 3], set_impl.new)) |> Enum.sort
|
||||
[1,2,3]
|
||||
|
||||
"""
|
||||
@@ -310,7 +306,7 @@ defmodule Set do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Set.union(HashSet.new([1,2]), HashSet.new([2,3,4])) |> Enum.sort
|
||||
iex> Set.union(Enum.into([1,2], set_impl.new), Enum.into([2,3,4], set_impl.new)) |> Enum.sort
|
||||
[1,2,3,4]
|
||||
|
||||
"""
|
||||
|
||||
+75
-46
@@ -81,9 +81,15 @@ defmodule Stream do
|
||||
|
||||
This module also provides many convenience functions for creating streams,
|
||||
like `Stream.cycle/1`, `Stream.unfold/2`, `Stream.resource/3` and more.
|
||||
|
||||
Note the functions in this module are guaranteed to return enumerables.
|
||||
Since enumerables can have different shapes (structs, anonymous functions,
|
||||
and so on), the functions in this module may return any of those shapes
|
||||
and that it may change at any time. For example, a function that today
|
||||
returns an anonymous function may return a struct in future releases.
|
||||
"""
|
||||
|
||||
defrecord Lazy, enum: nil, funs: [], accs: [], after: [], last: nil
|
||||
defrecord Lazy, enum: nil, funs: [], accs: [], done: nil
|
||||
|
||||
defimpl Enumerable, for: Lazy do
|
||||
@compile :inline_list_funs
|
||||
@@ -103,46 +109,28 @@ defmodule Stream do
|
||||
{ :error, __MODULE__ }
|
||||
end
|
||||
|
||||
defp do_reduce(Lazy[enum: enum, funs: funs, accs: accs, last: last, after: after_funs], acc, fun) do
|
||||
defp do_reduce(Lazy[enum: enum, funs: funs, accs: accs, done: done], acc, fun) do
|
||||
composed = :lists.foldl(fn fun, acc -> fun.(acc) end, fun, funs)
|
||||
do_each(&Enumerable.reduce(enum, &1, composed), after_funs,
|
||||
last && { last, fun }, :lists.reverse(accs), acc)
|
||||
do_each(&Enumerable.reduce(enum, &1, composed),
|
||||
done && { done, fun }, :lists.reverse(accs), acc)
|
||||
end
|
||||
|
||||
defp do_each(reduce, [], last, accs, { command, acc }) do
|
||||
handle_reduce reduce.({ command, [acc|accs] }), [], last
|
||||
end
|
||||
|
||||
defp do_each(reduce, after_funs, last, accs, { command, acc }) do
|
||||
try do
|
||||
handle_reduce reduce.({ command, [acc|accs] }), after_funs, last
|
||||
catch
|
||||
kind, reason ->
|
||||
lc fun inlist after_funs, do: fun.()
|
||||
:erlang.raise(kind, reason, :erlang.get_stacktrace)
|
||||
end
|
||||
end
|
||||
|
||||
defp handle_reduce(res, after_funs, last) do
|
||||
case res do
|
||||
defp do_each(reduce, done, accs, { command, acc }) do
|
||||
case reduce.({ command, [acc|accs] }) do
|
||||
{ :suspended, [acc|accs], continuation } ->
|
||||
{ :suspended, acc, &do_each(continuation, after_funs, last, accs, &1) }
|
||||
{ :suspended, acc, &do_each(continuation, done, accs, &1) }
|
||||
{ :halted, [acc|_] } ->
|
||||
lc fun inlist after_funs, do: fun.()
|
||||
{ :halted, acc }
|
||||
{ :done, [acc|_] = accs } ->
|
||||
case last do
|
||||
case done do
|
||||
nil ->
|
||||
lc fun inlist after_funs, do: fun.()
|
||||
{ :done, acc }
|
||||
{ last, fun } ->
|
||||
res = case last.(fun).(accs) do
|
||||
{ done, fun } ->
|
||||
case done.(fun).(accs) do
|
||||
{ :cont, [acc|_] } -> { :done, acc }
|
||||
{ :halt, [acc|_] } -> { :halted, acc }
|
||||
{ :suspend, [acc|_] } -> { :suspended, acc, &({ :done, &1 |> elem(1) }) }
|
||||
{ :suspend, [acc|_] } -> { :suspended, acc, &({ :done, elem(&1, 1) }) }
|
||||
end
|
||||
lc fun inlist after_funs, do: fun.()
|
||||
res
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -173,18 +161,6 @@ defmodule Stream do
|
||||
|
||||
## Transformers
|
||||
|
||||
@doc false
|
||||
@spec unquote(:after)(Enumerable.t, (() -> term)) :: Enumerable.t
|
||||
def unquote(:after)(Lazy[after: funs] = lazy, fun) do
|
||||
IO.write :stderr, "Stream.after/1 is deprecated and will be removed/1\n#{Exception.format_stacktrace}"
|
||||
lazy.after([fun|funs])
|
||||
end
|
||||
|
||||
def unquote(:after)(enum, fun) do
|
||||
IO.write :stderr, "Stream.after/1 is deprecated and will be removed/1\n#{Exception.format_stacktrace}"
|
||||
Lazy[enum: enum, after: [fun]]
|
||||
end
|
||||
|
||||
@doc """
|
||||
Shortcut to `chunk(enum, n, n)`.
|
||||
"""
|
||||
@@ -208,10 +184,13 @@ defmodule Stream do
|
||||
|
||||
iex> Stream.chunk([1, 2, 3, 4, 5, 6], 2) |> Enum.to_list
|
||||
[[1, 2], [3, 4], [5, 6]]
|
||||
|
||||
iex> Stream.chunk([1, 2, 3, 4, 5, 6], 3, 2) |> Enum.to_list
|
||||
[[1, 2, 3], [3, 4, 5]]
|
||||
|
||||
iex> Stream.chunk([1, 2, 3, 4, 5, 6], 3, 2, [7]) |> Enum.to_list
|
||||
[[1, 2, 3], [3, 4, 5], [5, 6, 7]]
|
||||
|
||||
iex> Stream.chunk([1, 2, 3, 4, 5, 6], 3, 3, []) |> Enum.to_list
|
||||
[[1, 2, 3], [4, 5, 6]]
|
||||
|
||||
@@ -402,6 +381,44 @@ defmodule Stream do
|
||||
lazy enum, fn(f1) -> R.filter_map(filter, mapper, f1) end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Injects the stream values into the given collectable as a side-effect.
|
||||
|
||||
This function is often used with `run/1` since any evaluation
|
||||
is delayed until the stream is executed. See `run/1` for an example.
|
||||
"""
|
||||
@spec into(Enumerable.t, Collectable.t) :: Enumerable.t
|
||||
def into(enum, collectable, transform \\ fn x -> x end) do
|
||||
&do_into(enum, collectable, transform, &1, &2)
|
||||
end
|
||||
|
||||
defp do_into(enum, collectable, transform, acc, fun) do
|
||||
{ initial, into } = Collectable.into(collectable)
|
||||
composed = fn x, [acc|collectable] ->
|
||||
collectable = into.(collectable, { :cont, transform.(x) })
|
||||
{ reason, acc } = fun.(x, acc)
|
||||
{ reason, [acc|collectable] }
|
||||
end
|
||||
do_into(&Enumerable.reduce(enum, &1, composed), initial, into, acc)
|
||||
end
|
||||
|
||||
defp do_into(reduce, collectable, into, { command, acc }) do
|
||||
try do
|
||||
reduce.({ command, [acc|collectable] })
|
||||
catch
|
||||
kind, reason ->
|
||||
stacktrace = System.stacktrace
|
||||
into.(collectable, :halt)
|
||||
:erlang.raise(kind, reason, stacktrace)
|
||||
else
|
||||
{ :suspended, [acc|collectable], continuation } ->
|
||||
{ :suspended, acc, &do_into(continuation, collectable, into, &1) }
|
||||
{ reason, [acc|collectable] } ->
|
||||
into.(collectable, :done)
|
||||
{ reason, acc }
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Creates a stream that will apply the given function on
|
||||
enumeration.
|
||||
@@ -439,6 +456,18 @@ defmodule Stream do
|
||||
|
||||
This is useful when a stream needs to be run, for side effects,
|
||||
and there is no interest in its return result.
|
||||
|
||||
## Examples
|
||||
|
||||
Open up a file, replace all `#` by `%` and stream to another file
|
||||
without loading the whole file in memory:
|
||||
|
||||
stream = File.stream!("code")
|
||||
|> Stream.map(&String.replace(&1, "#", "%"))
|
||||
|> Stream.into(File.stream!("new"))
|
||||
|
||||
No computation will be done until we call one of the Enum functions
|
||||
or `Stream.run/1`.
|
||||
"""
|
||||
@spec run(Enumerable.t) :: :ok
|
||||
def run(stream) do
|
||||
@@ -576,7 +605,6 @@ defmodule Stream do
|
||||
lazy enum, fn(f1) -> R.take_while(fun, f1) end
|
||||
end
|
||||
|
||||
|
||||
@doc """
|
||||
Transforms an existing stream.
|
||||
|
||||
@@ -997,6 +1025,7 @@ defmodule Stream do
|
||||
|
||||
iex> Stream.unfold(5, fn 0 -> nil; n -> {n, n-1} end) |> Enum.to_list()
|
||||
[5, 4, 3, 2, 1]
|
||||
|
||||
"""
|
||||
@spec unfold(acc, (acc -> { element, acc } | nil)) :: Enumerable.t
|
||||
def unfold(next_acc, next_fun) do
|
||||
@@ -1032,8 +1061,8 @@ defmodule Stream do
|
||||
defp lazy(enum, acc, fun),
|
||||
do: Lazy[enum: enum, funs: [fun], accs: [acc]]
|
||||
|
||||
defp lazy(Lazy[last: nil, funs: funs, accs: accs] = lazy, acc, fun, last),
|
||||
do: lazy.funs([fun|funs]).accs([acc|accs]).last(last)
|
||||
defp lazy(enum, acc, fun, last),
|
||||
do: Lazy[enum: enum, funs: [fun], accs: [acc], last: last]
|
||||
defp lazy(Lazy[done: nil, funs: funs, accs: accs] = lazy, acc, fun, done),
|
||||
do: lazy.funs([fun|funs]).accs([acc|accs]).done(done)
|
||||
defp lazy(enum, acc, fun, done),
|
||||
do: Lazy[enum: enum, funs: [fun], accs: [acc], done: done]
|
||||
end
|
||||
|
||||
@@ -47,7 +47,7 @@ defmodule String do
|
||||
by the letter "e" followed by the accent ́:
|
||||
|
||||
iex> string = "\x{0065}\x{0301}"
|
||||
...> byte_size(string)
|
||||
iex> byte_size(string)
|
||||
3
|
||||
iex> String.length(string)
|
||||
1
|
||||
@@ -83,13 +83,14 @@ defmodule String do
|
||||
|
||||
iex> ?j
|
||||
106
|
||||
|
||||
iex> ?é
|
||||
233
|
||||
|
||||
Or also via pattern matching:
|
||||
|
||||
iex> << eacute :: utf8 >> = "é"
|
||||
...> eacute
|
||||
iex> eacute
|
||||
233
|
||||
|
||||
As we have seen above, codepoints can be inserted into
|
||||
@@ -159,8 +160,10 @@ defmodule String do
|
||||
|
||||
iex> String.split("foo bar")
|
||||
["foo", "bar"]
|
||||
|
||||
iex> String.split("foo" <> <<194, 133>> <> "bar")
|
||||
["foo", "bar"]
|
||||
|
||||
iex> String.split(" foo bar ")
|
||||
["foo", "bar"]
|
||||
|
||||
@@ -185,8 +188,10 @@ defmodule String do
|
||||
|
||||
iex> String.split("a,b,c", ",")
|
||||
["a", "b", "c"]
|
||||
|
||||
iex> String.split("a,b,c", ",", global: false)
|
||||
["a", "b,c"]
|
||||
|
||||
iex> String.split(" a b c ", " ", trim: true)
|
||||
["a", "b", "c"]
|
||||
|
||||
@@ -199,8 +204,10 @@ defmodule String do
|
||||
|
||||
iex> String.split("a,b,c", ~r{,})
|
||||
["a", "b", "c"]
|
||||
|
||||
iex> String.split("a,b,c", ~r{,}, global: false)
|
||||
["a", "b,c"]
|
||||
|
||||
iex> String.split(" a b c ", ~r{\s}, trim: true)
|
||||
["a", "b", "c"]
|
||||
|
||||
@@ -208,10 +215,13 @@ defmodule String do
|
||||
|
||||
iex> String.split("abc", ~r{})
|
||||
["a", "b", "c", ""]
|
||||
|
||||
iex> String.split("abc", "")
|
||||
["a", "b", "c", ""]
|
||||
|
||||
iex> String.split("abc", "", trim: true)
|
||||
["a", "b", "c"]
|
||||
|
||||
iex> String.split("abc", "", global: false)
|
||||
["a", "bc"]
|
||||
|
||||
@@ -232,7 +242,7 @@ defmodule String do
|
||||
splits = :binary.split(binary, pattern, opts)
|
||||
|
||||
if Keyword.get(options, :trim, false) do
|
||||
lc split inlist splits, split != "", do: split
|
||||
for split <- splits, split != "", do: split
|
||||
else
|
||||
splits
|
||||
end
|
||||
@@ -246,8 +256,10 @@ defmodule String do
|
||||
|
||||
iex> String.upcase("abcd")
|
||||
"ABCD"
|
||||
|
||||
iex> String.upcase("ab 123 xpto")
|
||||
"AB 123 XPTO"
|
||||
|
||||
iex> String.upcase("josé")
|
||||
"JOSÉ"
|
||||
|
||||
@@ -262,8 +274,10 @@ defmodule String do
|
||||
|
||||
iex> String.downcase("ABCD")
|
||||
"abcd"
|
||||
|
||||
iex> String.downcase("AB 123 XPTO")
|
||||
"ab 123 xpto"
|
||||
|
||||
iex> String.downcase("JOSÉ")
|
||||
"josé"
|
||||
|
||||
@@ -284,8 +298,10 @@ defmodule String do
|
||||
|
||||
iex> String.capitalize("abcd")
|
||||
"Abcd"
|
||||
|
||||
iex> String.capitalize("fin")
|
||||
"Fin"
|
||||
|
||||
iex> String.capitalize("josé")
|
||||
"José"
|
||||
|
||||
@@ -423,6 +439,7 @@ defmodule String do
|
||||
|
||||
iex> String.rjust("abc", 5)
|
||||
" abc"
|
||||
|
||||
iex> String.rjust("abc", 5, ?-)
|
||||
"--abc"
|
||||
|
||||
@@ -447,6 +464,7 @@ defmodule String do
|
||||
|
||||
iex> String.ljust("abc", 5)
|
||||
"abc "
|
||||
|
||||
iex> String.ljust("abc", 5, ?-)
|
||||
"abc--"
|
||||
|
||||
@@ -493,6 +511,7 @@ defmodule String do
|
||||
|
||||
iex> String.replace("a,b,c", ",", "-")
|
||||
"a-b-c"
|
||||
|
||||
iex> String.replace("a,b,c", ",", "-", global: false)
|
||||
"a-b,c"
|
||||
|
||||
@@ -510,8 +529,10 @@ defmodule String do
|
||||
|
||||
iex> String.replace("a,b,c", "b", "[]", insert_replaced: 1)
|
||||
"a,[b],c"
|
||||
|
||||
iex> String.replace("a,b,c", ",", "[]", insert_replaced: 2)
|
||||
"a[],b[],c"
|
||||
|
||||
iex> String.replace("a,b,c", ",", "[]", insert_replaced: [1, 1])
|
||||
"a[,,]b[,,]c"
|
||||
|
||||
@@ -547,8 +568,10 @@ defmodule String do
|
||||
|
||||
iex> String.reverse("abcd")
|
||||
"dcba"
|
||||
|
||||
iex> String.reverse("hello world")
|
||||
"dlrow olleh"
|
||||
|
||||
iex> String.reverse("hello ∂og")
|
||||
"go∂ olleh"
|
||||
|
||||
@@ -571,8 +594,10 @@ defmodule String do
|
||||
|
||||
iex> String.duplicate("abc", 0)
|
||||
""
|
||||
|
||||
iex> String.duplicate("abc", 1)
|
||||
"abc"
|
||||
|
||||
iex> String.duplicate("abc", 2)
|
||||
"abcabc"
|
||||
|
||||
@@ -589,8 +614,10 @@ defmodule String do
|
||||
|
||||
iex> String.codepoints("josé")
|
||||
["j", "o", "s", "é"]
|
||||
|
||||
iex> String.codepoints("оптими зации")
|
||||
["о","п","т","и","м","и"," ","з","а","ц","и","и"]
|
||||
|
||||
iex> String.codepoints("ἅἪῼ")
|
||||
["ἅ","Ἢ","ῼ"]
|
||||
|
||||
@@ -627,10 +654,13 @@ defmodule String do
|
||||
|
||||
iex> String.valid?("a")
|
||||
true
|
||||
|
||||
iex> String.valid?("ø")
|
||||
true
|
||||
|
||||
iex> String.valid?(<<0xffff :: 16>>)
|
||||
false
|
||||
|
||||
iex> String.valid?("asd" <> <<0xffff :: 16>>)
|
||||
false
|
||||
|
||||
@@ -643,7 +673,7 @@ defmodule String do
|
||||
?\x{6FFFE}, ?\x{6FFFF}, ?\x{7FFFE}, ?\x{7FFFF}, ?\x{8FFFE}, ?\x{8FFFF},
|
||||
?\x{9FFFE}, ?\x{9FFFF}, ?\x{10FFFE}, ?\x{10FFFF} ]
|
||||
|
||||
lc noncharacter inlist noncharacters do
|
||||
for noncharacter <- noncharacters do
|
||||
def valid?(<< unquote(noncharacter) :: utf8, _ :: binary >>), do: false
|
||||
end
|
||||
|
||||
@@ -664,8 +694,10 @@ defmodule String do
|
||||
|
||||
iex> String.valid_character?("a")
|
||||
true
|
||||
|
||||
iex> String.valid_character?("ø")
|
||||
true
|
||||
|
||||
iex> String.valid_character?("\x{ffff}")
|
||||
false
|
||||
|
||||
@@ -714,6 +746,7 @@ defmodule String do
|
||||
|
||||
iex> String.first("elixir")
|
||||
"e"
|
||||
|
||||
iex> String.first("եոգլի")
|
||||
"ե"
|
||||
|
||||
@@ -734,6 +767,7 @@ defmodule String do
|
||||
|
||||
iex> String.last("elixir")
|
||||
"r"
|
||||
|
||||
iex> String.last("եոգլի")
|
||||
"ի"
|
||||
|
||||
@@ -756,6 +790,7 @@ defmodule String do
|
||||
|
||||
iex> String.length("elixir")
|
||||
6
|
||||
|
||||
iex> String.length("եոգլի")
|
||||
5
|
||||
|
||||
@@ -779,12 +814,16 @@ defmodule String do
|
||||
|
||||
iex> String.at("elixir", 0)
|
||||
"e"
|
||||
|
||||
iex> String.at("elixir", 1)
|
||||
"l"
|
||||
|
||||
iex> String.at("elixir", 10)
|
||||
nil
|
||||
|
||||
iex> String.at("elixir", -1)
|
||||
"r"
|
||||
|
||||
iex> String.at("elixir", -10)
|
||||
nil
|
||||
|
||||
@@ -822,18 +861,25 @@ defmodule String do
|
||||
|
||||
iex> String.slice("elixir", 1, 3)
|
||||
"lix"
|
||||
|
||||
iex> String.slice("elixir", 1, 10)
|
||||
"lixir"
|
||||
|
||||
iex> String.slice("elixir", 10, 3)
|
||||
nil
|
||||
|
||||
iex> String.slice("elixir", -4, 4)
|
||||
"ixir"
|
||||
|
||||
iex> String.slice("elixir", -10, 3)
|
||||
nil
|
||||
|
||||
iex> String.slice("a", 0, 1500)
|
||||
"a"
|
||||
|
||||
iex> String.slice("a", 1, 1500)
|
||||
""
|
||||
|
||||
iex> String.slice("a", 2, 1500)
|
||||
nil
|
||||
|
||||
@@ -870,26 +916,34 @@ defmodule String do
|
||||
|
||||
iex> String.slice("elixir", 1..3)
|
||||
"lix"
|
||||
|
||||
iex> String.slice("elixir", 1..10)
|
||||
"lixir"
|
||||
|
||||
iex> String.slice("elixir", 10..3)
|
||||
nil
|
||||
|
||||
iex> String.slice("elixir", -4..-1)
|
||||
"ixir"
|
||||
|
||||
iex> String.slice("elixir", 2..-1)
|
||||
"ixir"
|
||||
|
||||
iex> String.slice("elixir", -4..6)
|
||||
"ixir"
|
||||
|
||||
iex> String.slice("elixir", -1..-4)
|
||||
nil
|
||||
|
||||
iex> String.slice("elixir", -10..-7)
|
||||
nil
|
||||
|
||||
iex> String.slice("a", 0..1500)
|
||||
"a"
|
||||
|
||||
iex> String.slice("a", 1..1500)
|
||||
""
|
||||
|
||||
iex> String.slice("a", 2..1500)
|
||||
nil
|
||||
|
||||
@@ -953,8 +1007,10 @@ defmodule String do
|
||||
|
||||
iex> String.starts_with? "elixir", "eli"
|
||||
true
|
||||
|
||||
iex> String.starts_with? "elixir", ["erlang", "elixir"]
|
||||
true
|
||||
|
||||
iex> String.starts_with? "elixir", ["erlang", "ruby"]
|
||||
false
|
||||
|
||||
@@ -989,8 +1045,10 @@ defmodule String do
|
||||
|
||||
iex> String.ends_with? "language", "age"
|
||||
true
|
||||
|
||||
iex> String.ends_with? "language", ["youth", "age"]
|
||||
true
|
||||
|
||||
iex> String.ends_with? "language", ["youth", "elixir"]
|
||||
false
|
||||
|
||||
@@ -1027,6 +1085,7 @@ defmodule String do
|
||||
|
||||
iex> String.match?("foo", ~r/foo/)
|
||||
true
|
||||
|
||||
iex> String.match?("bar", ~r/foo/)
|
||||
false
|
||||
|
||||
@@ -1045,8 +1104,10 @@ defmodule String do
|
||||
|
||||
iex> String.contains? "elixir of life", "of"
|
||||
true
|
||||
|
||||
iex> String.contains? "elixir of life", ["life", "death"]
|
||||
true
|
||||
|
||||
iex> String.contains? "elixir of life", ["death", "mercury"]
|
||||
false
|
||||
|
||||
@@ -1098,6 +1159,7 @@ defmodule String do
|
||||
|
||||
iex> String.to_char_list("æß")
|
||||
{ :ok, 'æß' }
|
||||
|
||||
iex> String.to_char_list("abc")
|
||||
{ :ok, 'abc' }
|
||||
|
||||
@@ -1127,6 +1189,7 @@ defmodule String do
|
||||
|
||||
iex> String.to_char_list!("æß")
|
||||
'æß'
|
||||
|
||||
iex> String.to_char_list!("abc")
|
||||
'abc'
|
||||
|
||||
@@ -1152,6 +1215,7 @@ defmodule String do
|
||||
|
||||
iex> String.from_char_list([0x00E6, 0x00DF])
|
||||
{ :ok, "æß" }
|
||||
|
||||
iex> String.from_char_list([0x0061, 0x0062, 0x0063])
|
||||
{ :ok, "abc" }
|
||||
|
||||
@@ -1179,6 +1243,7 @@ defmodule String do
|
||||
|
||||
iex> String.from_char_list!([0x00E6, 0x00DF])
|
||||
"æß"
|
||||
|
||||
iex> String.from_char_list!([0x0061, 0x0062, 0x0063])
|
||||
"abc"
|
||||
|
||||
|
||||
@@ -58,6 +58,7 @@ defimpl String.Chars, for: List do
|
||||
|
||||
iex> to_string('foo')
|
||||
"foo"
|
||||
|
||||
iex> to_string(["foo", 'bar'])
|
||||
"foobar"
|
||||
|
||||
|
||||
@@ -0,0 +1,345 @@
|
||||
defmodule StringIO do
|
||||
@moduledoc """
|
||||
This module provides an IO device that wraps a string.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> { :ok, pid } = StringIO.open("foo")
|
||||
iex> IO.read(pid, 2)
|
||||
"fo"
|
||||
|
||||
"""
|
||||
|
||||
use GenServer.Behaviour
|
||||
|
||||
defrecordp :state, input: "", output: "", capture_prompt: false
|
||||
|
||||
@doc """
|
||||
Creates an IO device.
|
||||
|
||||
If the `:capture_prompt` option is set to `true`,
|
||||
prompts (specified as arguments to `IO.get*` functions)
|
||||
are captured.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> { :ok, pid } = StringIO.open("foo")
|
||||
iex> IO.gets(pid, ">")
|
||||
"foo"
|
||||
iex> StringIO.contents(pid)
|
||||
{ "", "" }
|
||||
|
||||
iex> { :ok, pid } = StringIO.open("foo", capture_prompt: true)
|
||||
iex> IO.gets(pid, ">")
|
||||
"foo"
|
||||
iex> StringIO.contents(pid)
|
||||
{ "", ">" }
|
||||
|
||||
"""
|
||||
@spec open(binary, Keyword.t) :: { :ok, pid }
|
||||
def open(string, options \\ []) when is_binary(string) do
|
||||
:gen_server.start_link(__MODULE__, { string, options }, [])
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns current buffers.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> { :ok, pid } = StringIO.open("in")
|
||||
iex> IO.write(pid, "out")
|
||||
iex> StringIO.contents(pid)
|
||||
{ "in", "out" }
|
||||
|
||||
"""
|
||||
@spec contents(pid) :: { binary, binary }
|
||||
def contents(pid) when is_pid(pid) do
|
||||
:gen_server.call(pid, :contents)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Stops the IO device and returns remaining buffers.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> { :ok, pid } = StringIO.open("in")
|
||||
iex> IO.write(pid, "out")
|
||||
iex> StringIO.close(pid)
|
||||
{ :ok, { "in", "out" } }
|
||||
|
||||
"""
|
||||
@spec close(pid) :: { :ok, { binary, binary } }
|
||||
def close(pid) when is_pid(pid) do
|
||||
:gen_server.call(pid, :close)
|
||||
end
|
||||
|
||||
## callbacks
|
||||
|
||||
def init({ string, options }) do
|
||||
capture_prompt = options[:capture_prompt] || false
|
||||
{ :ok, state(input: string, capture_prompt: capture_prompt) }
|
||||
end
|
||||
|
||||
def handle_info({ :io_request, from, reply_as, req }, s) do
|
||||
s = io_request(from, reply_as, req, s)
|
||||
{ :noreply, s }
|
||||
end
|
||||
|
||||
def handle_info(msg, s) do
|
||||
super(msg, s)
|
||||
end
|
||||
|
||||
def handle_call(:contents, _from, state(input: input, output: output) = s) do
|
||||
{ :reply, { input, output }, s }
|
||||
end
|
||||
|
||||
def handle_call(:close, _from, state(input: input, output: output) = s) do
|
||||
{ :stop, :normal, { :ok, { input, output } }, s }
|
||||
end
|
||||
|
||||
def handle_call(request, from, s) do
|
||||
super(request, from, s)
|
||||
end
|
||||
|
||||
defp io_request(from, reply_as, req, s) do
|
||||
{ reply, s } = io_request(req, s)
|
||||
io_reply(from, reply_as, to_reply(reply))
|
||||
s
|
||||
end
|
||||
|
||||
defp io_request({ :put_chars, chars }, state(output: output) = s) do
|
||||
{ :ok, state(s, output: << output :: binary, to_binary(chars) :: binary >>) }
|
||||
end
|
||||
|
||||
defp io_request({ :put_chars, m, f, as }, state(output: output) = s) do
|
||||
chars = apply(m, f, as)
|
||||
{ :ok, state(s, output: << output :: binary, to_binary(chars) :: binary >>) }
|
||||
end
|
||||
|
||||
defp io_request({ :put_chars, _encoding, chars }, s) do
|
||||
io_request({ :put_chars, chars }, s)
|
||||
end
|
||||
|
||||
defp io_request({ :put_chars, _encoding, mod, func, args }, s) do
|
||||
io_request({ :put_chars, mod, func, args }, s)
|
||||
end
|
||||
|
||||
defp io_request({ :get_chars, prompt, n }, s) when n >= 0 do
|
||||
io_request({ :get_chars, :latin1, prompt, n }, s)
|
||||
end
|
||||
|
||||
defp io_request({ :get_chars, encoding, prompt, n }, s) when n >= 0 do
|
||||
get_chars(encoding, prompt, n, s)
|
||||
end
|
||||
|
||||
defp io_request({ :get_line, prompt }, s) do
|
||||
io_request({ :get_line, :latin1, prompt }, s)
|
||||
end
|
||||
|
||||
defp io_request({ :get_line, encoding, prompt }, s) do
|
||||
get_line(encoding, prompt, s)
|
||||
end
|
||||
|
||||
defp io_request({ :get_until, prompt, mod, fun, args }, s) do
|
||||
io_request({ :get_until, :latin1, prompt, mod, fun, args }, s)
|
||||
end
|
||||
|
||||
defp io_request({ :get_until, encoding, prompt, mod, fun, args }, s) do
|
||||
get_until(encoding, prompt, mod, fun, args, s)
|
||||
end
|
||||
|
||||
defp io_request({ :get_password, encoding }, s) do
|
||||
get_line(encoding, "", s)
|
||||
end
|
||||
|
||||
defp io_request({ :setopts, _opts }, s) do
|
||||
{ { :error, :enotsup }, s }
|
||||
end
|
||||
|
||||
defp io_request(:getopts, s) do
|
||||
{ { :ok, [binary: true, encoding: :unicode] }, s }
|
||||
end
|
||||
|
||||
defp io_request({ :get_geometry, :columns }, s) do
|
||||
{ { :error, :enotsup }, s }
|
||||
end
|
||||
|
||||
defp io_request({ :get_geometry, :rows }, s) do
|
||||
{ { :error, :enotsup }, s }
|
||||
end
|
||||
|
||||
defp io_request({ :requests, reqs }, s) do
|
||||
io_requests(reqs, { :ok, s })
|
||||
end
|
||||
|
||||
defp io_request(_, s) do
|
||||
{ { :error, :request }, s }
|
||||
end
|
||||
|
||||
## get_chars
|
||||
|
||||
defp get_chars(encoding, prompt, n,
|
||||
state(input: input, output: output, capture_prompt: capture_prompt) = s) do
|
||||
case do_get_chars(input, encoding, n) do
|
||||
{ :error, _ } = error ->
|
||||
{ error, s }
|
||||
{ result, input } ->
|
||||
if capture_prompt do
|
||||
output = << output :: binary, to_binary(prompt) :: binary >>
|
||||
end
|
||||
|
||||
{ result, state(s, input: input, output: output) }
|
||||
end
|
||||
end
|
||||
|
||||
defp do_get_chars("", _encoding, _n) do
|
||||
{ :eof, "" }
|
||||
end
|
||||
|
||||
defp do_get_chars(input, :latin1, n) when byte_size(input) < n do
|
||||
{ input, "" }
|
||||
end
|
||||
|
||||
defp do_get_chars(input, :latin1, n) do
|
||||
<< chars :: [ binary, size(n) ], rest :: binary >> = input
|
||||
{ chars, rest }
|
||||
end
|
||||
|
||||
defp do_get_chars(input, encoding, n) do
|
||||
try do
|
||||
case :file_io_server.count_and_find(input, n, encoding) do
|
||||
{ buf_count, split_pos } when buf_count < n or split_pos == :none ->
|
||||
{ input, "" }
|
||||
{ _buf_count, split_pos } ->
|
||||
<< chars :: [ binary, size(split_pos) ], rest :: binary >> = input
|
||||
{ chars, rest }
|
||||
end
|
||||
catch
|
||||
:exit, :invalid_unicode ->
|
||||
{ :error, :invalid_unicode }
|
||||
end
|
||||
end
|
||||
|
||||
## get_line
|
||||
|
||||
defp get_line(encoding, prompt,
|
||||
state(input: input, output: output, capture_prompt: capture_prompt) = s) do
|
||||
case :unicode.characters_to_list(input, encoding) do
|
||||
{ :error, _, _ } ->
|
||||
{ { :error, :collect_line }, s }
|
||||
{ :incomplete, _, _ } ->
|
||||
{ { :error, :collect_line }, s }
|
||||
chars ->
|
||||
{ result, input } = do_get_line(chars, encoding)
|
||||
|
||||
if capture_prompt do
|
||||
output = << output :: binary, to_binary(prompt) :: binary >>
|
||||
end
|
||||
|
||||
{ result, state(s, input: input, output: output) }
|
||||
end
|
||||
end
|
||||
|
||||
defp do_get_line('', _encoding) do
|
||||
{ :eof, "" }
|
||||
end
|
||||
|
||||
defp do_get_line(chars, encoding) do
|
||||
{ line, rest } = collect_line(chars)
|
||||
{ :unicode.characters_to_binary(line, encoding),
|
||||
:unicode.characters_to_binary(rest, encoding) }
|
||||
end
|
||||
|
||||
## get_until
|
||||
|
||||
defp get_until(encoding, prompt, mod, fun, args,
|
||||
state(input: input, output: output, capture_prompt: capture_prompt) = s) do
|
||||
case :unicode.characters_to_list(input, encoding) do
|
||||
{ :error, _, _ } ->
|
||||
{ :error, s }
|
||||
{ :incomplete, _, _ } ->
|
||||
{ :error, s }
|
||||
chars ->
|
||||
{ result, input, count } = do_get_until(chars, encoding, mod, fun, args)
|
||||
|
||||
if capture_prompt do
|
||||
output = << output :: binary, :binary.copy(to_binary(prompt), count) :: binary >>
|
||||
end
|
||||
|
||||
input =
|
||||
case input do
|
||||
:eof -> ""
|
||||
_ -> :unicode.characters_to_binary(input, encoding)
|
||||
end
|
||||
|
||||
{ result, state(s, input: input, output: output) }
|
||||
end
|
||||
end
|
||||
|
||||
defp do_get_until(chars, encoding, mod, fun, args, continuation \\ [], count \\ 0)
|
||||
|
||||
defp do_get_until('', encoding, mod, fun, args, continuation, count) do
|
||||
case apply(mod, fun, [continuation, :eof | args]) do
|
||||
{ :done, result, rest } ->
|
||||
{ result, rest, count + 1 }
|
||||
{ :more, next_continuation } ->
|
||||
do_get_until('', encoding, mod, fun, args, next_continuation, count + 1)
|
||||
end
|
||||
end
|
||||
|
||||
defp do_get_until(chars, encoding, mod, fun, args, continuation, count) do
|
||||
{ line, rest } = collect_line(chars)
|
||||
|
||||
case apply(mod, fun, [continuation, line | args]) do
|
||||
{ :done, result, rest1 } ->
|
||||
unless rest1 == :eof do
|
||||
rest = rest1 ++ rest
|
||||
end
|
||||
{ result, rest, count + 1 }
|
||||
{ :more, next_continuation } ->
|
||||
do_get_until(rest, encoding, mod, fun, args, next_continuation, count + 1)
|
||||
end
|
||||
end
|
||||
|
||||
## io_requests
|
||||
|
||||
defp io_requests([r|rs], { :ok, s }) do
|
||||
io_requests(rs, io_request(r, s))
|
||||
end
|
||||
|
||||
defp io_requests(_, result) do
|
||||
result
|
||||
end
|
||||
|
||||
## helpers
|
||||
|
||||
defp collect_line(chars) do
|
||||
collect_line(chars, [])
|
||||
end
|
||||
|
||||
defp collect_line([], stack) do
|
||||
{ :lists.reverse(stack), [] }
|
||||
end
|
||||
|
||||
defp collect_line([?\r, ?\n | rest], stack) do
|
||||
{ :lists.reverse([?\n|stack]), rest }
|
||||
end
|
||||
|
||||
defp collect_line([?\n | rest], stack) do
|
||||
{ :lists.reverse([?\n|stack]), rest }
|
||||
end
|
||||
|
||||
defp collect_line([h|t], stack) do
|
||||
collect_line(t, [h|stack])
|
||||
end
|
||||
|
||||
defp io_reply(from, reply_as, reply) do
|
||||
send from, { :io_reply, reply_as, reply }
|
||||
end
|
||||
|
||||
defp to_binary(list) when is_list(list), do: String.from_char_list!(list)
|
||||
defp to_binary(binary) when is_binary(binary), do: binary
|
||||
|
||||
defp to_reply(list) when is_list(list), do: String.from_char_list!(list)
|
||||
defp to_reply(other), do: other
|
||||
end
|
||||
@@ -331,12 +331,25 @@ defmodule System do
|
||||
Enum.each dict, fn {key, val} -> put_env key, val end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Deletes an environment variable.
|
||||
|
||||
Removes the variable `varname` from the environment.
|
||||
"""
|
||||
@spec delete_env(String.t) :: :ok
|
||||
def delete_env(varname) do
|
||||
:os.unsetenv(String.to_char_list!(varname))
|
||||
:ok
|
||||
end
|
||||
|
||||
@doc """
|
||||
Last exception stacktrace.
|
||||
|
||||
Note that the Erlang VM (and therefore this function) does not
|
||||
return the current stacktrace but rather the stacktrace of the
|
||||
latest exception.
|
||||
|
||||
Inlined by the compiler into `:erlang.get_stacktrace/0`.
|
||||
"""
|
||||
def stacktrace do
|
||||
:erlang.get_stacktrace
|
||||
|
||||
@@ -30,7 +30,7 @@ defmodule Tuple do
|
||||
## Examples
|
||||
|
||||
iex> tuple = { :bar, :baz }
|
||||
...> Tuple.insert_at(tuple, 0, :foo)
|
||||
iex> Tuple.insert_at(tuple, 0, :foo)
|
||||
{ :foo, :bar, :baz }
|
||||
|
||||
"""
|
||||
@@ -43,13 +43,13 @@ defmodule Tuple do
|
||||
Remove an element from a tuple.
|
||||
|
||||
Deletes the element at the zero-based `index` from `tuple`.
|
||||
Raises an `ArgumentError` if `index` is greater than
|
||||
Raises an `ArgumentError` if `index` is greater than
|
||||
or equal to the length of `tuple`.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> tuple = { :foo, :bar, :baz }
|
||||
...> Tuple.delete_at(tuple, 0)
|
||||
iex> Tuple.delete_at(tuple, 0)
|
||||
{ :bar, :baz }
|
||||
|
||||
"""
|
||||
|
||||
+19
-29
@@ -62,27 +62,22 @@ defmodule URI do
|
||||
|
||||
Takes an enumerable (containing a sequence of two-item tuples)
|
||||
and returns a string of the form "key1=value1&key2=value2..." where
|
||||
keys and values are URL encoded as per `encode/1`. Keys and values can
|
||||
be any term that implements the `String.Chars` protocol (i.e. can be converted
|
||||
to a binary).
|
||||
keys and values are URL encoded as per `encode/1`.
|
||||
|
||||
Keys and values can be any term that implements the `String.Chars`
|
||||
protocol, except lists which are explicitly forbidden.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> hd = HashDict.new([{"foo", 1}, {"bar", "2"}])
|
||||
iex> hd = %{"foo" => 1, "bar" => 2}
|
||||
iex> URI.encode_query(hd)
|
||||
"bar=2&foo=1"
|
||||
|
||||
"""
|
||||
def encode_query(l), do: Enum.map_join(l, "&", &pair/1)
|
||||
|
||||
@doc false
|
||||
def decode_query(q) do
|
||||
IO.write :stderr, "URI.decode_query/1 is deprecated, please use URI.decode_query/2 with an explicit argument\n#{Exception.format_stacktrace}"
|
||||
decode_query(q, HashDict.new)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Decodes a query string into a dictionary.
|
||||
Decodes a query string into a dictionary (by default uses a map).
|
||||
|
||||
Given a query string of the form "key1=value1&key2=value2...", produces a
|
||||
`HashDict` with one entry for each key-value pair. Each key and value will be a
|
||||
@@ -92,14 +87,11 @@ defmodule URI do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> hd = HashDict.new()
|
||||
iex> URI.decode_query("foo=1&bar=2", hd) |> HashDict.keys
|
||||
["bar", "foo"]
|
||||
iex> URI.decode_query("foo=1&bar=2", hd) |> HashDict.values
|
||||
["2", "1"]
|
||||
iex> URI.decode_query("foo=1&bar=2")
|
||||
%{"bar" => "2", "foo" => "1"}
|
||||
|
||||
"""
|
||||
def decode_query(q, dict) when is_binary(q) do
|
||||
def decode_query(q, dict \\ %{}) when is_binary(q) do
|
||||
Enum.reduce query_decoder(q), dict, fn({ k, v }, acc) -> Dict.put(acc, k, v) end
|
||||
end
|
||||
|
||||
@@ -137,14 +129,12 @@ defmodule URI do
|
||||
{ current, next }
|
||||
end
|
||||
|
||||
defp pair({k, v}) when is_list(k) do
|
||||
IO.write :stderr, "Passing list keys to URI.encode_query/1 is deprecated\n#{Exception.format_stacktrace}"
|
||||
encode(to_string(k)) <> "=" <> encode(to_string(v))
|
||||
defp pair({k, _}) when is_list(k) do
|
||||
raise ArgumentError, message: "encode_query/1 keys cannot be lists, got: #{inspect k}"
|
||||
end
|
||||
|
||||
defp pair({k, v}) when is_list(v) do
|
||||
IO.write :stderr, "Passing list values to URI.encode_query/1 is deprecated\n#{Exception.format_stacktrace}"
|
||||
encode(to_string(k)) <> "=" <> encode(to_string(v))
|
||||
defp pair({_, v}) when is_list(v) do
|
||||
raise ArgumentError, message: "encode_query/1 values cannot be lists, got: #{inspect v}"
|
||||
end
|
||||
|
||||
defp pair({k, v}) do
|
||||
@@ -156,11 +146,11 @@ defmodule URI do
|
||||
|
||||
## Example
|
||||
|
||||
iex> URI.encode("http://elixir-lang.com/getting_started/2.html")
|
||||
"http%3A%2F%2Felixir-lang.com%2Fgetting_started%2F2.html"
|
||||
iex> URI.encode("http://elixir-lang.org/getting_started/2.html")
|
||||
"http%3A%2F%2Felixir-lang.org%2Fgetting_started%2F2.html"
|
||||
|
||||
"""
|
||||
def encode(s), do: bc(<<c>> inbits s, do: <<percent(c) :: binary>>)
|
||||
def encode(s), do: for(<<c <- s>>, into: "", do: percent(c))
|
||||
|
||||
defp percent(32), do: <<?+>>
|
||||
defp percent(?-), do: <<?->>
|
||||
@@ -184,8 +174,8 @@ defmodule URI do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> URI.decode("http%3A%2F%2Felixir-lang.com")
|
||||
"http://elixir-lang.com"
|
||||
iex> URI.decode("http%3A%2F%2Felixir-lang.org")
|
||||
"http://elixir-lang.org"
|
||||
|
||||
"""
|
||||
def decode(uri) do
|
||||
@@ -272,7 +262,7 @@ defmodule URI do
|
||||
# Regex.run returns empty strings sometimes. We want
|
||||
# to replace those with nil for consistency.
|
||||
defp nillify(l) do
|
||||
lc s inlist l do
|
||||
for s <- l do
|
||||
if size(s) > 0, do: s, else: nil
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
{erl_first_files, ["elixir_transform"]}.
|
||||
|
||||
{erl_opts, [
|
||||
warn_unused_vars,
|
||||
warn_export_all,
|
||||
@@ -24,4 +22,4 @@
|
||||
{verbose, false}
|
||||
]}.
|
||||
|
||||
{require_otp_vsn,"(R16B|17).*"}.
|
||||
{require_otp_vsn,"(17).*"}.
|
||||
|
||||
@@ -21,8 +21,8 @@ start(_Type, _Args) ->
|
||||
%% Set the shell to unicode so printing inside scripts work
|
||||
%% Those can take a while, so let's do it in a new process
|
||||
spawn(fun() ->
|
||||
io:setopts(standard_io, [binary,{encoding,unicode}]),
|
||||
io:setopts(standard_error, [binary,{encoding,unicode}])
|
||||
io:setopts(standard_io, [binary,{encoding,utf8}]),
|
||||
io:setopts(standard_error, [{unicode,true}])
|
||||
end),
|
||||
elixir_sup:start_link().
|
||||
|
||||
@@ -158,11 +158,11 @@ string_to_quoted(String, StartLine, File, Opts) when is_integer(StartLine), is_b
|
||||
{ ok, _Line, Tokens } ->
|
||||
try elixir_parser:parse(Tokens) of
|
||||
{ ok, Forms } -> { ok, Forms };
|
||||
{ error, { Line, _, [Error, Token] } } -> { error, { Line, Error, Token } }
|
||||
{ error, { Line, _, [Error, Token] } } -> { error, { Line, to_binary(Error), to_binary(Token) } }
|
||||
catch
|
||||
{ error, { Line, _, [Error, Token] } } -> { error, { Line, Error, Token } }
|
||||
{ error, { Line, _, [Error, Token] } } -> { error, { Line, to_binary(Error), to_binary(Token) } }
|
||||
end;
|
||||
{ error, Reason, _Rest, _SoFar } -> { error, Reason }
|
||||
{ error, { Line, Error, Token }, _Rest, _SoFar } -> { error, { Line, to_binary(Error), to_binary(Token) } }
|
||||
end.
|
||||
|
||||
'string_to_quoted!'(String, StartLine, File, Opts) ->
|
||||
@@ -172,3 +172,6 @@ string_to_quoted(String, StartLine, File, Opts) when is_integer(StartLine), is_b
|
||||
{ error, { Line, Error, Token } } ->
|
||||
elixir_errors:parse_error(Line, File, Error, Token)
|
||||
end.
|
||||
|
||||
to_binary(List) when is_list(List) -> elixir_utils:characters_to_binary(List);
|
||||
to_binary(Atom) when is_atom(Atom) -> atom_to_binary(Atom, utf8).
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
%% Handle translation of Elixir literals to Erlang AST.
|
||||
-module(elixir_bitstring).
|
||||
-export([translate/3, expand/3]).
|
||||
-export([translate/3, expand/3, has_size/1]).
|
||||
-include("elixir.hrl").
|
||||
|
||||
%% Expansion
|
||||
@@ -102,6 +101,13 @@ handle_unknown_bit_info(Meta, { _, ExprMeta, _ } = Expr, T, Size, Types, E) ->
|
||||
|
||||
%% Translation
|
||||
|
||||
has_size({ bin, _, Elements }) ->
|
||||
not lists:any(fun({ bin_element, _Line, _Expr, Size, Types }) ->
|
||||
(Types /= default) andalso (Size == default) andalso
|
||||
lists:any(fun(X) -> lists:member(X, Types) end,
|
||||
[bits, bytes, bitstring, binary])
|
||||
end, Elements).
|
||||
|
||||
translate(Meta, Args, S) ->
|
||||
case S#elixir_scope.context of
|
||||
match ->
|
||||
@@ -168,15 +174,30 @@ types_require_conversion([UTF|T]) when UTF == utf8; UTF == utf16; UTF == utf32 -
|
||||
types_require_conversion([]) -> true;
|
||||
types_require_conversion(_) -> false.
|
||||
|
||||
types_allow_splice([bytes], Elements) -> lists:all(fun has_default_size/1, Elements);
|
||||
types_allow_splice([binary], Elements) -> lists:all(fun has_default_size/1, Elements);
|
||||
types_allow_splice([bytes], Elements) -> is_byte_size(Elements, 0);
|
||||
types_allow_splice([binary], Elements) -> is_byte_size(Elements, 0);
|
||||
types_allow_splice([bits], _) -> true;
|
||||
types_allow_splice([bitstring], _) -> true;
|
||||
types_allow_splice(default, _) -> true;
|
||||
types_allow_splice(_, _) -> false.
|
||||
|
||||
has_default_size({ bin_element, _, _, default, _ }) -> true;
|
||||
has_default_size({ bin_element, _, _, _, _ }) -> false.
|
||||
is_byte_size([Element|T], Acc) ->
|
||||
case elem_size(Element) of
|
||||
{unknown, Unit} when Unit rem 8 == 0 -> is_byte_size(T, Acc);
|
||||
{unknown, _Unit} -> false;
|
||||
{Size, Unit} -> is_byte_size(T, Size*Unit + Acc)
|
||||
end;
|
||||
is_byte_size([], Size) ->
|
||||
Size rem 8 == 0.
|
||||
|
||||
elem_size({bin_element, _, _, default, _}) -> {0, 0};
|
||||
elem_size({bin_element, _, _, {integer,_,Size}, Types}) -> {Size, unit_size(Types, 1)};
|
||||
elem_size({bin_element, _, _, _Size, Types}) -> {unknown, unit_size(Types, 1)}.
|
||||
|
||||
unit_size([binary|T], _) -> unit_size(T, 8);
|
||||
unit_size([{unit, Size}|_], _) -> Size;
|
||||
unit_size([_|T], Guess) -> unit_size(T, Guess);
|
||||
unit_size([], Guess) -> Guess.
|
||||
|
||||
%% Extra bitstring specifiers
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
%% Handle code related to args, guard and -> matching for case,
|
||||
%% fn, receive and friends. try is handled in elixir_try.
|
||||
-module(elixir_clauses).
|
||||
-export([match/3, clause/6, clauses/3, get_pairs/2, get_pairs/3,
|
||||
-export([match/3, clause/7, clauses/4, get_pairs/2, get_pairs/3,
|
||||
extract_splat_guards/1, extract_guards/1]).
|
||||
-include("elixir.hrl").
|
||||
|
||||
@@ -31,9 +31,9 @@ match(Fun, Args, S) -> Fun(Args, S).
|
||||
|
||||
%% Translate clauses with args, guards and expressions
|
||||
|
||||
clause(Line, Fun, Args, Expr, Guards, S) when is_integer(Line) ->
|
||||
clause(Line, Fun, Args, Expr, Guards, Return, S) when is_integer(Line) ->
|
||||
{ TArgs, SA } = match(Fun, Args, S#elixir_scope{extra_guards=[]}),
|
||||
{ TExpr, SE } = elixir_translator:translate(Expr, SA#elixir_scope{extra_guards=nil}),
|
||||
{ TExpr, SE } = elixir_translator:translate_block(Expr, Return, SA#elixir_scope{extra_guards=nil}),
|
||||
|
||||
SG = SA#elixir_scope{context=guard, extra_guards=nil},
|
||||
Extra = SA#elixir_scope.extra_guards,
|
||||
@@ -66,22 +66,18 @@ extract_splat_guards(Else) ->
|
||||
|
||||
% Function for translating macros with match style like case and receive.
|
||||
|
||||
clauses(Meta, Clauses, #elixir_scope{export_vars=CV} = S) ->
|
||||
{ TC, TS } = do_clauses(Meta, Clauses, S#elixir_scope{export_vars=[]}),
|
||||
clauses(Meta, Clauses, Return, #elixir_scope{export_vars=CV} = S) ->
|
||||
{ TC, TS } = do_clauses(Meta, Clauses, Return, S#elixir_scope{export_vars=[]}),
|
||||
{ TC, TS#elixir_scope{export_vars=elixir_scope:merge_opt_vars(CV, TS#elixir_scope.export_vars)} }.
|
||||
|
||||
do_clauses(_Meta, [], S) ->
|
||||
do_clauses(_Meta, [], _Return, S) ->
|
||||
{ [], S };
|
||||
|
||||
% do_clauses(_Meta, [DecoupledClause], S) ->
|
||||
% { TDecoupledClause, TS } = each_clause(DecoupledClause, S),
|
||||
% { [TDecoupledClause], TS };
|
||||
|
||||
do_clauses(Meta, DecoupledClauses, S) ->
|
||||
do_clauses(Meta, DecoupledClauses, Return, S) ->
|
||||
% Transform tree just passing the variables counter forward
|
||||
% and storing variables defined inside each clause.
|
||||
Transformer = fun(X, {SAcc, VAcc}) ->
|
||||
{ TX, TS } = each_clause(X, SAcc),
|
||||
{ TX, TS } = each_clause(X, Return, SAcc),
|
||||
{ TX,
|
||||
{ elixir_scope:mergec(S, TS), [TS#elixir_scope.export_vars|VAcc] } }
|
||||
end,
|
||||
@@ -142,17 +138,17 @@ expand_clauses(_Line, [], [], _FinalVars, Acc, S) ->
|
||||
|
||||
% Handle each key/value clause pair and translate them accordingly.
|
||||
|
||||
each_clause({ do, Meta, [Condition], Expr }, S) ->
|
||||
each_clause({ do, Meta, [Condition], Expr }, Return, S) ->
|
||||
{ Arg, Guards } = extract_guards(Condition),
|
||||
clause(?line(Meta), fun elixir_translator:translate_many/2, [Arg], Expr, Guards, S);
|
||||
clause(?line(Meta), fun elixir_translator:translate_args/2, [Arg], Expr, Guards, Return, S);
|
||||
|
||||
each_clause({ else, Meta, [Condition], Expr }, S) ->
|
||||
each_clause({ else, Meta, [Condition], Expr }, Return, S) ->
|
||||
{ Arg, Guards } = extract_guards(Condition),
|
||||
clause(?line(Meta), fun elixir_translator:translate_many/2, [Arg], Expr, Guards, S);
|
||||
clause(?line(Meta), fun elixir_translator:translate_args/2, [Arg], Expr, Guards, Return, S);
|
||||
|
||||
each_clause({ 'after', Meta, [Condition], Expr }, S) ->
|
||||
each_clause({ 'after', Meta, [Condition], Expr }, Return, S) ->
|
||||
{ TCondition, SC } = elixir_translator:translate(Condition, S),
|
||||
{ TExpr, SB } = elixir_translator:translate(Expr, SC),
|
||||
{ TExpr, SB } = elixir_translator:translate_block(Expr, Return, SC),
|
||||
{ { clause, ?line(Meta), [TCondition], [], unblock(TExpr) }, SB }.
|
||||
|
||||
% Check if the given expression is a match tuple.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
-module(elixir_compiler).
|
||||
-export([get_opt/1, string/2, quoted/2, file/1, file_to_path/2]).
|
||||
-export([core/0, module/4, eval_forms/3, format_error/1]).
|
||||
-export([core/0, module/4, eval_forms/3]).
|
||||
-include("elixir.hrl").
|
||||
|
||||
%% Public API
|
||||
@@ -137,15 +137,11 @@ module(Forms, File, Opts, Callback) ->
|
||||
|
||||
module(Forms, File, RawOptions, Bootstrap, Callback) when
|
||||
is_binary(File), is_list(Forms), is_list(RawOptions), is_boolean(Bootstrap), is_function(Callback) ->
|
||||
{ Options, SkipNative } = compiler_options(Forms, RawOptions),
|
||||
Options = RawOptions ++ elixir_code_server:call(erl_compiler_options),
|
||||
Listname = elixir_utils:characters_to_list(File),
|
||||
|
||||
case compile:noenv_forms([no_auto_import()|Forms], [return,{source,Listname}|Options]) of
|
||||
{ok, ModuleName, Binary, RawWarnings} ->
|
||||
Warnings = case SkipNative of
|
||||
true -> [{?MODULE,[{0,?MODULE,{skip_native,ModuleName}}]}|RawWarnings];
|
||||
false -> RawWarnings
|
||||
end,
|
||||
case compile:noenv_forms(Forms, [no_auto_import,return,{source,Listname}|Options]) of
|
||||
{ok, ModuleName, Binary, Warnings} ->
|
||||
format_warnings(Bootstrap, Warnings),
|
||||
code:load_binary(ModuleName, Listname, Binary),
|
||||
Callback(ModuleName, Binary);
|
||||
@@ -154,23 +150,6 @@ module(Forms, File, RawOptions, Bootstrap, Callback) when
|
||||
format_errors(Errors)
|
||||
end.
|
||||
|
||||
compiler_options(Forms, Options) ->
|
||||
EnvOptions = elixir_code_server:call(erl_compiler_options),
|
||||
SkipNative = lists:member(native, EnvOptions) and contains_on_load(Forms),
|
||||
case SkipNative or lists:member([{native,false}], Options) of
|
||||
true -> { Options ++ lists:delete(native, EnvOptions), SkipNative };
|
||||
false -> { Options ++ EnvOptions, SkipNative }
|
||||
end.
|
||||
|
||||
contains_on_load([{ attribute, _, on_load, _ }|_]) -> true;
|
||||
contains_on_load([_|T]) -> contains_on_load(T);
|
||||
contains_on_load([]) -> false.
|
||||
|
||||
no_auto_import() ->
|
||||
Bifs = [{ Name, Arity } || { Name, Arity } <- erlang:module_info(exports), erl_internal:bif(Name, Arity)],
|
||||
{ attribute, 0, compile, { no_auto_import, Bifs } }.
|
||||
|
||||
|
||||
%% CORE HANDLING
|
||||
|
||||
core() ->
|
||||
@@ -196,6 +175,8 @@ core_main() ->
|
||||
<<"lib/elixir/lib/module.ex">>,
|
||||
<<"lib/elixir/lib/list.ex">>,
|
||||
<<"lib/elixir/lib/record.ex">>,
|
||||
<<"lib/elixir/lib/record/backend.ex">>,
|
||||
<<"lib/elixir/lib/record/deprecated.ex">>,
|
||||
<<"lib/elixir/lib/macro.ex">>,
|
||||
<<"lib/elixir/lib/macro/env.ex">>,
|
||||
<<"lib/elixir/lib/exception.ex">>,
|
||||
@@ -210,6 +191,7 @@ core_main() ->
|
||||
<<"lib/elixir/lib/regex.ex">>,
|
||||
<<"lib/elixir/lib/string.ex">>,
|
||||
<<"lib/elixir/lib/string/chars.ex">>,
|
||||
<<"lib/elixir/lib/collectable.ex">>,
|
||||
<<"lib/elixir/lib/io.ex">>,
|
||||
<<"lib/elixir/lib/path.ex">>,
|
||||
<<"lib/elixir/lib/system.ex">>,
|
||||
@@ -217,7 +199,6 @@ core_main() ->
|
||||
<<"lib/elixir/lib/kernel/cli.ex">>,
|
||||
<<"lib/elixir/lib/kernel/error_handler.ex">>,
|
||||
<<"lib/elixir/lib/kernel/parallel_compiler.ex">>,
|
||||
<<"lib/elixir/lib/kernel/record_rewriter.ex">>,
|
||||
<<"lib/elixir/lib/kernel/lexical_tracker.ex">>
|
||||
].
|
||||
|
||||
@@ -228,10 +209,6 @@ binary_to_path({ModuleName, Binary}, CompilePath) ->
|
||||
|
||||
%% ERROR HANDLING
|
||||
|
||||
format_error({ skip_native, Module }) ->
|
||||
io_lib:format("skipping native compilation for ~ts because it contains on_load attribute",
|
||||
[elixir_aliases:inspect(Module)]).
|
||||
|
||||
format_errors([]) ->
|
||||
exit({ nocompile, "compilation failed but no error was raised" });
|
||||
|
||||
|
||||
@@ -86,8 +86,7 @@ store_definition(Line, Kind, CheckClauses, Name, Args, Guards, Body, MetaFile, #
|
||||
elixir_locals:record_definition(Tuple, Kind, Module),
|
||||
|
||||
Location = retrieve_location(Line, MetaFile, Module),
|
||||
run_on_definition_callbacks(Kind, Line, Module, Name, Args, Guards, Body, E),
|
||||
{ Function, Defaults, Super } = translate_definition(Kind, Line, Name, Args, Guards, Body, E),
|
||||
{ Function, Defaults, Super } = translate_definition(Kind, Line, Module, Name, Args, Guards, Body, E),
|
||||
|
||||
DefaultsLength = length(Defaults),
|
||||
elixir_locals:record_defaults(Tuple, Kind, Module, DefaultsLength),
|
||||
@@ -104,6 +103,7 @@ store_definition(Line, Kind, CheckClauses, Name, Args, Guards, Body, MetaFile, #
|
||||
[store_each(false, Kind, File, Location, Table, CTable, 0,
|
||||
default_function_for(Kind, Name, Default)) || Default <- Defaults],
|
||||
|
||||
make_struct_available(Kind, Module, Name, Args),
|
||||
{ Name, Arity }.
|
||||
|
||||
%% @on_definition
|
||||
@@ -118,6 +118,14 @@ run_on_definition_callbacks(Kind, Line, Module, Name, Args, Guards, Expr, E) ->
|
||||
[Mod:Fun(Env, Kind, Name, Args, Guards, Expr) || { Mod, Fun } <- Callbacks]
|
||||
end.
|
||||
|
||||
make_struct_available(def, Module, '__struct__', []) ->
|
||||
case erlang:get(elixir_compiler_pid) of
|
||||
undefined -> ok;
|
||||
Pid -> Pid ! { struct_available, Module }
|
||||
end;
|
||||
make_struct_available(_, _, _, _) ->
|
||||
ok.
|
||||
|
||||
%% Retrieve location from meta file or @file, otherwise nil
|
||||
|
||||
retrieve_location(Line, File, Module) ->
|
||||
@@ -144,24 +152,25 @@ compile_super(_Module, _, _E) -> ok.
|
||||
%% Translate the given call and expression given
|
||||
%% and then store it in memory.
|
||||
|
||||
translate_definition(Kind, Line, Name, Args, Guards, Body, E) when is_integer(Line) ->
|
||||
translate_definition(Kind, Line, Module, Name, Args, Guards, Body, E) when is_integer(Line) ->
|
||||
Arity = length(Args),
|
||||
|
||||
{ EArgs, EGuards, EBody, _ } = elixir_exp_clauses:def(fun elixir_def_defaults:expand/2,
|
||||
Args, Guards, expr_from_body(Line, Body), E),
|
||||
|
||||
%% Macros receive a special argument on invocation. Notice it does
|
||||
%% not affect the arity of the stored function, but the clause
|
||||
%% already contains it.
|
||||
AllArgs = case is_macro(Kind) of
|
||||
true -> [{ '_@CALLER', [{line,Line}], nil }|Args];
|
||||
false -> Args
|
||||
EAllArgs = case is_macro(Kind) of
|
||||
true -> [{ '_@CALLER', [{line,Line}], nil }|EArgs];
|
||||
false -> EArgs
|
||||
end,
|
||||
|
||||
{ EArgs, EGuards, EBody, _ } = elixir_exp_clauses:def(fun elixir_def_defaults:expand/2,
|
||||
AllArgs, Guards, expr_from_body(Line, Body), E),
|
||||
|
||||
S = elixir_env:env_to_scope(E),
|
||||
{ Unpacked, Defaults } = elixir_def_defaults:unpack(Kind, Name, EArgs, S),
|
||||
{ Unpacked, Defaults } = elixir_def_defaults:unpack(Kind, Name, EAllArgs, S),
|
||||
{ Clauses, Super } = translate_clause(Body, Line, Kind, Unpacked, EGuards, EBody, S),
|
||||
|
||||
run_on_definition_callbacks(Kind, Line, Module, Name, EArgs, EGuards, EBody, E),
|
||||
Function = { function, Line, Name, Arity, Clauses },
|
||||
{ Function, Defaults, Super }.
|
||||
|
||||
@@ -171,7 +180,7 @@ translate_clause(nil, Line, Kind, _Args, _Guards, _Body, #elixir_scope{file=File
|
||||
elixir_errors:form_error(Line, File, ?MODULE, { missing_do, Kind });
|
||||
translate_clause(_, Line, Kind, Args, Guards, Body, S) ->
|
||||
{ TClause, TS } = elixir_clauses:clause(Line,
|
||||
fun elixir_translator:translate_many/2, Args, Body, Guards, S),
|
||||
fun elixir_translator:translate_args/2, Args, Body, Guards, true, S),
|
||||
|
||||
%% Set __CALLER__ if used
|
||||
FClause = case is_macro(Kind) andalso TS#elixir_scope.caller of
|
||||
|
||||
@@ -5,12 +5,6 @@
|
||||
|
||||
expand(Args, E) ->
|
||||
lists:mapfoldl(fun
|
||||
({ '//', Meta, [Left, Right] }, Acc) ->
|
||||
elixir_errors:deprecation(Meta, E#elixir_env.file, "using // for default arguments is deprecated, "
|
||||
"please use \\\\ instead"),
|
||||
{ ELeft, EL } = elixir_exp:expand(Left, Acc),
|
||||
{ ERight, _ } = elixir_exp:expand(Right, Acc#elixir_env{context=nil}),
|
||||
{ { '\\\\', Meta, [ELeft, ERight] }, EL };
|
||||
({ '\\\\', Meta, [Left, Right] }, Acc) ->
|
||||
{ ELeft, EL } = elixir_exp:expand(Left, Acc),
|
||||
{ ERight, _ } = elixir_exp:expand(Right, Acc#elixir_env{context=nil}),
|
||||
@@ -31,7 +25,7 @@ unpack_each(Kind, Name, [{'\\\\', Line, [Expr, _]}|T] = List, Acc, Clauses, S) -
|
||||
Base = build_match(Acc, Line, []),
|
||||
{ Args, Invoke } = extract_defaults(List, Line, length(Base), [], []),
|
||||
|
||||
{ DefArgs, SA } = elixir_clauses:match(fun elixir_translator:translate_many/2, Base ++ Args, S),
|
||||
{ DefArgs, SA } = elixir_clauses:match(fun elixir_translator:translate_args/2, Base ++ Args, S),
|
||||
{ DefInvoke, _ } = elixir_translator:translate_args(Base ++ Invoke, SA),
|
||||
|
||||
Call = { call, Line,
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
-define(kernel, 'Elixir.Kernel').
|
||||
-define(node, 'Elixir.Node').
|
||||
-define(process, 'Elixir.Process').
|
||||
-define(system, 'Elixir.System').
|
||||
|
||||
default_functions() ->
|
||||
[ { ?kernel, elixir_imported_functions() } ].
|
||||
@@ -113,7 +114,7 @@ expand_import(Meta, { Name, Arity } = Tuple, Args, E, Extra) ->
|
||||
|
||||
case Dispatch of
|
||||
%% In case it is an import, we dispatch the import.
|
||||
{ Kind, _ } when Kind == import ->
|
||||
{ import, _ } ->
|
||||
do_expand_import(Meta, Tuple, Args, Module, E, Dispatch);
|
||||
|
||||
%% There is a local and an import. This is a conflict unless
|
||||
@@ -395,6 +396,7 @@ inline(?kernel, is_function, 1) -> { erlang, is_function };
|
||||
inline(?kernel, is_function, 2) -> { erlang, is_function };
|
||||
inline(?kernel, is_integer, 1) -> { erlang, is_integer };
|
||||
inline(?kernel, is_list, 1) -> { erlang, is_list };
|
||||
inline(?kernel, is_map, 1) -> { erlang, is_map };
|
||||
inline(?kernel, is_number, 1) -> { erlang, is_number };
|
||||
inline(?kernel, is_pid, 1) -> { erlang, is_pid };
|
||||
inline(?kernel, is_port, 1) -> { erlang, is_port };
|
||||
@@ -409,6 +411,7 @@ inline(?kernel, list_to_integer, 1) -> { erlang, list_to_integer };
|
||||
inline(?kernel, list_to_integer, 2) -> { erlang, list_to_integer };
|
||||
inline(?kernel, list_to_tuple, 1) -> { erlang, list_to_tuple };
|
||||
inline(?kernel, make_ref, 0) -> { erlang, make_ref };
|
||||
inline(?kernel, map_size, 1) -> { erlang, map_size };
|
||||
inline(?kernel, max, 2) -> { erlang, max };
|
||||
inline(?kernel, min, 2) -> { erlang, min };
|
||||
inline(?kernel, node, 0) -> { erlang, node };
|
||||
@@ -451,4 +454,6 @@ inline(?node, spawn_link, 4) -> { erlang, spawn_link };
|
||||
inline(?node, spawn_monitor, 2) -> { erlang, spawn_monitor };
|
||||
inline(?node, spawn_monitor, 4) -> { erlang, spawn_monitor };
|
||||
|
||||
inline(?system, stacktrace, 0) -> { erlang, get_stacktrace };
|
||||
|
||||
inline(_, _, _) -> false.
|
||||
|
||||
@@ -36,8 +36,8 @@ mergev(E1, E2) ->
|
||||
}.
|
||||
|
||||
%% Receives two scopes and return the later scope
|
||||
%% keeping the variables from the first (counters,
|
||||
%% imports and everything else are passed forward).
|
||||
%% keeping the variables from the first (imports
|
||||
%% and everything else are passed forward).
|
||||
|
||||
mergea(E1, E2) ->
|
||||
E2#elixir_env{vars=E1#elixir_env.vars}.
|
||||
|
||||
@@ -7,6 +7,8 @@
|
||||
deprecation/3, deprecation/4]).
|
||||
-include("elixir.hrl").
|
||||
|
||||
-type line_or_meta() :: integer() | list().
|
||||
|
||||
warn(Warning) ->
|
||||
CompilerPid = get(elixir_compiler_pid),
|
||||
if
|
||||
@@ -24,13 +26,13 @@ warn(Line, File, Warning) ->
|
||||
|
||||
%% Raised during expansion/translation/compilation.
|
||||
|
||||
-spec form_error(list(), binary(), module(), any()) -> no_return().
|
||||
-spec form_error(line_or_meta(), binary(), module(), any()) -> no_return().
|
||||
|
||||
form_error(Meta, File, Module, Desc) ->
|
||||
compile_error(Meta, File, format_error(Module, Desc)).
|
||||
|
||||
-spec compile_error(list(), binary(), iolist()) -> no_return().
|
||||
-spec compile_error(list(), binary(), iolist(), list()) -> no_return().
|
||||
-spec compile_error(line_or_meta(), binary(), iolist()) -> no_return().
|
||||
-spec compile_error(line_or_meta(), binary(), iolist(), list()) -> no_return().
|
||||
|
||||
compile_error(Meta, File, Message) when is_list(Message) ->
|
||||
raise(Meta, File, 'Elixir.CompileError', elixir_utils:characters_to_binary(Message)).
|
||||
@@ -40,30 +42,20 @@ compile_error(Meta, File, Format, Args) when is_list(Format) ->
|
||||
|
||||
%% Raised on tokenizing/parsing
|
||||
|
||||
-spec parse_error(non_neg_integer() | list(), file:filename_all(), iolist() | atom(), [] | iolist()) -> no_return().
|
||||
-spec parse_error(line_or_meta(), binary(), iolist() | atom(), [] | iolist()) -> no_return().
|
||||
|
||||
parse_error(Meta, File, Error, []) ->
|
||||
parse_error(Meta, File, Error, <<>>) ->
|
||||
Message = case Error of
|
||||
"syntax error before: " -> <<"syntax error: expression is incomplete">>;
|
||||
_ -> iolist_to_binary(Error)
|
||||
<<"syntax error before: ">> -> <<"syntax error: expression is incomplete">>;
|
||||
_ -> Error
|
||||
end,
|
||||
raise(Meta, File, 'Elixir.TokenMissingError', Message);
|
||||
|
||||
parse_error(Meta, File, "syntax error before: ", "'end'") ->
|
||||
parse_error(Meta, File, <<"syntax error before: ">>, <<"'end'">>) ->
|
||||
raise(Meta, File, 'Elixir.SyntaxError', <<"unexpected token: end">>);
|
||||
|
||||
parse_error(Meta, File, Error, Token) ->
|
||||
BinError = if
|
||||
is_atom(Error) -> atom_to_binary(Error, utf8);
|
||||
true -> iolist_to_binary(Error)
|
||||
end,
|
||||
|
||||
BinToken = if
|
||||
Token == [] -> <<>>;
|
||||
true -> elixir_utils:characters_to_binary(Token)
|
||||
end,
|
||||
|
||||
Message = <<BinError / binary, BinToken / binary >>,
|
||||
parse_error(Meta, File, Error, Token) when is_binary(Error), is_binary(Token) ->
|
||||
Message = <<Error / binary, Token / binary >>,
|
||||
raise(Meta, File, 'Elixir.SyntaxError', Message).
|
||||
|
||||
%% Shows a deprecation message
|
||||
@@ -204,10 +196,8 @@ protocol_or_behaviour(Module) ->
|
||||
is_protocol(Module) ->
|
||||
case code:ensure_loaded(Module) of
|
||||
{ module, _ } ->
|
||||
case erlang:function_exported(Module, '__protocol__', 1) of
|
||||
true -> Module:'__protocol__'(name) == Module;
|
||||
false -> false
|
||||
end;
|
||||
erlang:function_exported(Module, '__protocol__', 1) andalso
|
||||
Module:'__protocol__'(name) == Module;
|
||||
{ error, _ } ->
|
||||
false
|
||||
end.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
-module(elixir_exp).
|
||||
-export([expand/2, expand_many/2, expand_args/2, expand_arg/2]).
|
||||
-export([expand/2, expand_args/2, expand_arg/2]).
|
||||
-import(elixir_errors, [compile_error/3, compile_error/4]).
|
||||
-include("elixir.hrl").
|
||||
|
||||
@@ -17,6 +17,12 @@ expand({ '{}', Meta, Args }, E) ->
|
||||
{ EArgs, EA } = expand_args(Args, E),
|
||||
{ { '{}', Meta, EArgs }, EA };
|
||||
|
||||
expand({ '%{}', Meta, Args }, E) ->
|
||||
elixir_map:expand_map(Meta, Args, E);
|
||||
|
||||
expand({ '%', Meta, [Left, Right] }, E) ->
|
||||
elixir_map:expand_struct(Meta, Left, Right, E);
|
||||
|
||||
expand({ '<<>>', Meta, Args }, E) ->
|
||||
elixir_bitstring:expand(Meta, Args, E);
|
||||
|
||||
@@ -79,7 +85,8 @@ expand({ alias, Meta, [Ref, KV] }, E) ->
|
||||
expand_alias(Meta, true, ERef, EKV, ET) };
|
||||
true ->
|
||||
compile_error(Meta, E#elixir_env.file,
|
||||
"invalid arguments for alias, expected a compile time atom or alias as argument")
|
||||
"invalid argument for alias, expected a compile time atom or alias, got: ~ts",
|
||||
['Elixir.Kernel':inspect(ERef)])
|
||||
end;
|
||||
|
||||
expand({ require, Meta, [Ref] }, E) ->
|
||||
@@ -97,7 +104,8 @@ expand({ require, Meta, [Ref, KV] }, E) ->
|
||||
expand_require(Meta, ERef, EKV, ET) };
|
||||
true ->
|
||||
compile_error(Meta, E#elixir_env.file,
|
||||
"invalid arguments for require, expected a compile time atom or alias as argument")
|
||||
"invalid argument for require, expected a compile time atom or alias, got: ~ts",
|
||||
['Elixir.Kernel':inspect(ERef)])
|
||||
end;
|
||||
|
||||
expand({ import, Meta, [Left] }, E) ->
|
||||
@@ -115,7 +123,9 @@ expand({ import, Meta, [Ref, KV] }, E) ->
|
||||
{ { import, Meta, [ERef, EKV] },
|
||||
expand_require(Meta, ERef, EKV, ET#elixir_env{functions=Functions, macros=Macros}) };
|
||||
true ->
|
||||
compile_error(Meta, E#elixir_env.file, "invalid name for import, expected a compile time atom or alias")
|
||||
compile_error(Meta, E#elixir_env.file,
|
||||
"invalid argument for import, expected a compile time atom or alias, got: ~ts",
|
||||
['Elixir.Kernel':inspect(ERef)])
|
||||
end;
|
||||
|
||||
%% Pseudo vars
|
||||
@@ -172,8 +182,9 @@ expand({ quote, Meta, [KV, Do] }, E) when is_list(Do) ->
|
||||
Context = case lists:keyfind(context, 1, EKV) of
|
||||
{ context, Atom } when is_atom(Atom) ->
|
||||
Atom;
|
||||
{ context, _ } ->
|
||||
compile_error(Meta, E#elixir_env.file, "invalid :context for quote, expected a compile time atom or an alias");
|
||||
{ context, Ctx } ->
|
||||
compile_error(Meta, E#elixir_env.file, "invalid :context for quote, "
|
||||
"expected a compile time atom or alias, got: ~ts", ['Elixir.Kernel':inspect(Ctx)]);
|
||||
false ->
|
||||
case E#elixir_env.module of
|
||||
nil -> 'Elixir';
|
||||
@@ -247,6 +258,9 @@ expand({'try', Meta, [KV]}, E) ->
|
||||
expand({ Kind, Meta, Args }, E) when is_list(Args), (Kind == lc) orelse (Kind == bc) ->
|
||||
expand_comprehension(Meta, Kind, Args, E);
|
||||
|
||||
expand({ for, Meta, Args }, E) when is_list(Args) ->
|
||||
elixir_for:expand(Meta, Args, E);
|
||||
|
||||
%% Super
|
||||
|
||||
expand({ super, Meta, Args }, E) when is_list(Args) ->
|
||||
@@ -403,7 +417,7 @@ expand_arg(Arg, { Acc1, Acc2 }) ->
|
||||
{ EArg, { elixir_env:mergea(Acc1, EAcc), elixir_env:mergev(Acc2, EAcc) } }.
|
||||
|
||||
expand_args(Args, #elixir_env{context=match} = E) ->
|
||||
lists:mapfoldl(fun expand/2, E, Args);
|
||||
expand_many(Args, E);
|
||||
expand_args(Args, E) ->
|
||||
{ EArgs, { EC, EV } } = lists:mapfoldl(fun expand_arg/2, {E, E}, Args),
|
||||
{ EArgs, elixir_env:mergea(EV, EC) }.
|
||||
|
||||
@@ -98,7 +98,7 @@ do_try(_Meta, { 'after', Expr }, E) ->
|
||||
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);
|
||||
expand_without_export(Meta, 'try', fun elixir_exp:expand_args/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) ->
|
||||
@@ -176,8 +176,8 @@ expand_arg(Meta, Kind, Key) ->
|
||||
%% 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;
|
||||
case lists:keyfind(export_head, 1, Meta) of
|
||||
{ export_head, true } -> Fun;
|
||||
_ -> fun(ExportArgs, ExportE) -> Fun(export_vars(ExportArgs), ExportE) end
|
||||
end,
|
||||
Transformer = fun(Clause, Vars) ->
|
||||
|
||||
@@ -6,15 +6,16 @@
|
||||
translate(Meta, Clauses, S) ->
|
||||
Transformer = fun({ '->', CMeta, [ArgsWithGuards, Expr] }, Acc) ->
|
||||
{ Args, Guards } = elixir_clauses:extract_splat_guards(ArgsWithGuards),
|
||||
{ TClause, TS } = elixir_clauses:clause(?line(CMeta), fun translate_fn_match/2, Args, Expr, Guards, Acc),
|
||||
{ TClause, TS } = elixir_clauses:clause(?line(CMeta), fun translate_fn_match/2,
|
||||
Args, Expr, Guards, true, Acc),
|
||||
{ TClause, elixir_scope:mergef(S, TS) }
|
||||
end,
|
||||
|
||||
{ TClauses, NS } = lists:mapfoldl(Transformer, S, Clauses),
|
||||
Arities = [length(Args) || { clause, _Line, Args, _Guards, _Exprs } <- TClauses],
|
||||
|
||||
case length(lists:usort(Arities)) of
|
||||
1 ->
|
||||
case lists:usort(Arities) of
|
||||
[_] ->
|
||||
{ { 'fun', ?line(Meta), { clauses, TClauses } }, NS };
|
||||
_ ->
|
||||
compile_error(Meta, S#elixir_scope.file,
|
||||
@@ -22,14 +23,14 @@ translate(Meta, Clauses, S) ->
|
||||
end.
|
||||
|
||||
translate_fn_match(Arg, S) ->
|
||||
{ TArg, TS } = elixir_translator:translate_many(Arg, S#elixir_scope{extra=fn_match}),
|
||||
{ TArg, TS#elixir_scope{extra=S#elixir_scope.extra} }.
|
||||
{ TArg, TS } = elixir_translator:translate_args(Arg, S#elixir_scope{backup_vars=orddict:new()}),
|
||||
{ TArg, TS#elixir_scope{backup_vars=S#elixir_scope.backup_vars} }.
|
||||
|
||||
%% Expansion
|
||||
|
||||
expand(Meta, Clauses, E) when is_list(Clauses) ->
|
||||
Transformer = fun(Clause) ->
|
||||
{ EClause, _ } = elixir_exp_clauses:clause(Meta, fn, fun elixir_exp:expand_many/2, Clause, E),
|
||||
{ EClause, _ } = elixir_exp_clauses:clause(Meta, fn, fun elixir_exp:expand_args/2, Clause, E),
|
||||
EClause
|
||||
end,
|
||||
{ { fn, Meta, lists:map(Transformer, Clauses) }, E }.
|
||||
|
||||
@@ -0,0 +1,338 @@
|
||||
-module(elixir_for).
|
||||
-export([expand/3, translate/3]).
|
||||
-include("elixir.hrl").
|
||||
|
||||
%% Expansion
|
||||
|
||||
expand(Meta, Args, E) ->
|
||||
{ Cases, Block } =
|
||||
case elixir_utils:split_last(Args) of
|
||||
{ OuterCases, OuterOpts } when is_list(OuterOpts) ->
|
||||
case elixir_utils:split_last(OuterCases) of
|
||||
{ InnerCases, InnerOpts } when is_list(InnerOpts) ->
|
||||
{ InnerCases, InnerOpts ++ OuterOpts };
|
||||
_ ->
|
||||
{ OuterCases, OuterOpts }
|
||||
end;
|
||||
_ ->
|
||||
{ Args, [] }
|
||||
end,
|
||||
|
||||
{ Expr, Opts } =
|
||||
case lists:keyfind(do, 1, Block) of
|
||||
{ do, Do } -> { Do, lists:keydelete(do, 1, Block) };
|
||||
_ -> elixir_errors:compile_error(Meta, E#elixir_env.file,
|
||||
"missing do keyword in for comprehension")
|
||||
end,
|
||||
|
||||
{ EOpts, EO } = elixir_exp:expand(Opts, E),
|
||||
{ ECases, EC } = lists:mapfoldl(fun expand/2, EO, Cases),
|
||||
{ EExpr, _ } = elixir_exp:expand(Expr, EC),
|
||||
{ { for, Meta, ECases ++ [[{do,EExpr}|EOpts]] }, E }.
|
||||
|
||||
expand({'<-', Meta, [Left, Right]}, E) ->
|
||||
{ ERight, ER } = elixir_exp:expand(Right, E),
|
||||
{ ELeft, EL } = elixir_exp_clauses:match(fun elixir_exp:expand/2, Left, E),
|
||||
{ { '<-', Meta, [ELeft, ERight] }, elixir_env:mergev(EL, ER) };
|
||||
expand({ '<<>>', Meta, Args } = X, E) when is_list(Args) ->
|
||||
case elixir_utils:split_last(Args) of
|
||||
{ LeftStart, {'<-', OpMeta, [LeftEnd, Right] } } ->
|
||||
{ ERight, ER } = elixir_exp:expand(Right, E),
|
||||
Left = { '<<>>', Meta, LeftStart ++ [LeftEnd] },
|
||||
{ ELeft, EL } = elixir_exp_clauses:match(fun elixir_exp:expand/2, Left, E),
|
||||
{ { '<<>>', [], [ { '<-', OpMeta, [ELeft, ERight] }] }, elixir_env:mergev(EL, ER) };
|
||||
_ ->
|
||||
elixir_exp:expand(X, E)
|
||||
end;
|
||||
expand(X, E) ->
|
||||
elixir_exp:expand(X, E).
|
||||
|
||||
%% Translation
|
||||
|
||||
translate(Meta, Args, #elixir_scope{return=Return} = RS) ->
|
||||
S = RS#elixir_scope{return=true},
|
||||
{ AccName, _, SA } = elixir_scope:build_var('_', S),
|
||||
{ VarName, _, SV } = elixir_scope:build_var('_', SA),
|
||||
|
||||
Line = ?line(Meta),
|
||||
Acc = { var, Line, AccName },
|
||||
Var = { var, Line, VarName },
|
||||
|
||||
{ Cases, [{do,Expr}|Opts] } = elixir_utils:split_last(Args),
|
||||
|
||||
{ TInto, SI } =
|
||||
case lists:keyfind(into, 1, Opts) of
|
||||
{ into, Into } -> elixir_translator:translate(Into, SV);
|
||||
false when Return -> { { nil, Line }, SV };
|
||||
false -> { false, SV }
|
||||
end,
|
||||
|
||||
{ TCases, SC } = translate_gen(Meta, Cases, [], SI),
|
||||
{ TExpr, SE } = elixir_translator:translate_block(Expr, Return, SC),
|
||||
SF = elixir_scope:mergef(SI, SE),
|
||||
|
||||
case comprehension_expr(TInto, TExpr) of
|
||||
{ inline, TIntoExpr } ->
|
||||
{ build_inline(Line, TCases, TIntoExpr, TInto, Var, Acc, SE), SF };
|
||||
{ into, TIntoExpr } ->
|
||||
build_into(Line, TCases, TIntoExpr, TInto, Var, Acc, SF)
|
||||
end.
|
||||
|
||||
translate_gen(ForMeta, [{ '<-', Meta, [Left, Right] }|T], Acc, S) ->
|
||||
{ TLeft, TRight, TFilters, TT, TS } = translate_gen(Meta, Left, Right, T, S),
|
||||
TAcc = [{ enum, Meta, TLeft, TRight, TFilters }|Acc],
|
||||
translate_gen(ForMeta, TT, TAcc, TS);
|
||||
translate_gen(ForMeta, [{ '<<>>', _, [ { '<-', Meta, [Left, Right] } ] }|T], Acc, S) ->
|
||||
{ TLeft, TRight, TFilters, TT, TS } = translate_gen(Meta, Left, Right, T, S),
|
||||
TAcc = [{ bin, Meta, TLeft, TRight, TFilters }|Acc],
|
||||
case elixir_bitstring:has_size(TLeft) of
|
||||
true -> translate_gen(ForMeta, TT, TAcc, TS);
|
||||
false ->
|
||||
elixir_errors:compile_error(Meta, S#elixir_scope.file,
|
||||
"bitstring fields without size are not allowed in bitstring generators")
|
||||
end;
|
||||
translate_gen(_ForMeta, [], Acc, S) ->
|
||||
{ lists:reverse(Acc), S };
|
||||
translate_gen(ForMeta, _, _, S) ->
|
||||
elixir_errors:compile_error(ForMeta, S#elixir_scope.file,
|
||||
"for comprehensions must start with a generator").
|
||||
|
||||
translate_gen(_Meta, Left, Right, T, S) ->
|
||||
{ TRight, SR } = elixir_translator:translate(Right, S),
|
||||
{ TLeft, SL } = elixir_clauses:match(fun elixir_translator:translate/2, Left, SR),
|
||||
{ TT, { TFilters, TS } } = translate_filters(T, SL),
|
||||
{ TLeft, TRight, TFilters, TT, TS }.
|
||||
|
||||
translate_filters(T, S) ->
|
||||
{ Filters, Rest } = collect_filters(T, []),
|
||||
{ Rest, lists:mapfoldr(fun translate_filter/2, S, Filters) }.
|
||||
|
||||
translate_filter(Filter, S) ->
|
||||
{ TFilter, TS } = elixir_translator:translate(Filter, S),
|
||||
case elixir_utils:returns_boolean(TFilter) of
|
||||
true ->
|
||||
{ { nil, TFilter }, TS };
|
||||
false ->
|
||||
{ Name, _, VS } = elixir_scope:build_var('_', TS),
|
||||
{ { { var, 0, Name }, TFilter }, VS }
|
||||
end.
|
||||
|
||||
collect_filters([{ '<-', _, [_, _] }|_] = T, Acc) ->
|
||||
{ Acc, T };
|
||||
collect_filters([{ '<<>>', _, [{ '<-', _, [_, _] }] }|_] = T, Acc) ->
|
||||
{ Acc, T };
|
||||
collect_filters([H|T], Acc) ->
|
||||
collect_filters(T, [H|Acc]);
|
||||
collect_filters([], Acc) ->
|
||||
{ Acc, [] }.
|
||||
|
||||
%% If all we have is one enum generator, we check if it is a list
|
||||
%% for optimization otherwise fallback to the reduce generator.
|
||||
build_inline(Line, [{ enum, Meta, Left, Right, Filters }] = Orig, Expr, Into, Var, Acc, S) ->
|
||||
case Right of
|
||||
{ cons, _, _, _ } ->
|
||||
build_comprehension(Line, Orig, Expr, Into);
|
||||
{ Other, _, _ } when Other == tuple; Other == map; Other == integer; Other == atom ->
|
||||
build_reduce(Orig, Expr, Into, Acc, S);
|
||||
_ ->
|
||||
Clauses = [{ enum, Meta, Left, Var, Filters }],
|
||||
|
||||
{'case', -1, Right, [
|
||||
{clause, -1,
|
||||
[Var],
|
||||
[[?wrap_call(Line, erlang, is_list, [Var])]],
|
||||
[build_comprehension(Line, Clauses, Expr, Into)]},
|
||||
{clause, -1,
|
||||
[Var],
|
||||
[],
|
||||
[build_reduce(Clauses, Expr, Into, Acc, S)]}
|
||||
]}
|
||||
end;
|
||||
|
||||
build_inline(Line, Clauses, Expr, Into, _Var, Acc, S) ->
|
||||
case lists:all(fun(Clause) -> element(1, Clause) == bin end, Clauses) of
|
||||
true -> build_comprehension(Line, Clauses, Expr, Into);
|
||||
false -> build_reduce(Clauses, Expr, Into, Acc, S)
|
||||
end.
|
||||
|
||||
build_into(Line, Clauses, Expr, Into, Fun, Acc, S) ->
|
||||
{ Kind, SK } = build_var(Line, S),
|
||||
{ Reason, SR } = build_var(Line, SK),
|
||||
{ Stack, ST } = build_var(Line, SR),
|
||||
{ Done, SD } = build_var(Line, ST),
|
||||
|
||||
IntoExpr = {call, Line, Fun, [Acc, pair(Line, cont, Expr)]},
|
||||
MatchExpr = {match, Line,
|
||||
{tuple, Line, [Acc, Fun]},
|
||||
?wrap_call(Line, 'Elixir.Collectable', into, [Into])
|
||||
},
|
||||
|
||||
TryExpr =
|
||||
{'try', Line,
|
||||
[build_reduce_clause(Clauses, IntoExpr, Acc, Acc, SD)],
|
||||
[{clause, Line,
|
||||
[Done],
|
||||
[],
|
||||
[{call, Line, Fun, [Done, {atom, Line, done}]}]}],
|
||||
[{clause, Line,
|
||||
[{tuple, Line, [Kind, Reason, {var, Line, '_'}]}],
|
||||
[],
|
||||
[{match, Line, Stack, ?wrap_call(Line, erlang, get_stacktrace, [])},
|
||||
{call, Line, Fun, [Acc, {atom, Line, halt}]},
|
||||
?wrap_call(Line, erlang, raise, [Kind, Reason, Stack])]}],
|
||||
[]},
|
||||
|
||||
{ {block, Line, [MatchExpr, TryExpr]}, SD }.
|
||||
|
||||
%% Helpers
|
||||
|
||||
build_reduce(Clauses, Expr, false, Acc, S) ->
|
||||
build_reduce_clause(Clauses, Expr, {nil, 0}, Acc, S);
|
||||
build_reduce(Clauses, Expr, {nil, Line} = Into, Acc, S) ->
|
||||
ListExpr = {cons, Line, Expr, Acc},
|
||||
?wrap_call(Line, lists, reverse,
|
||||
[build_reduce_clause(Clauses, ListExpr, Into, Acc, S)]);
|
||||
build_reduce(Clauses, Expr, {bin, _, _} = Into, Acc, S) ->
|
||||
{bin, Line, Elements} = Expr,
|
||||
BinExpr = {bin, Line, [{bin_element, Line, Acc, default, [bitstring]}|Elements]},
|
||||
build_reduce_clause(Clauses, BinExpr, Into, Acc, S).
|
||||
|
||||
build_reduce_clause([{ enum, Meta, Left, Right, Filters }|T], Expr, Arg, Acc, S) ->
|
||||
Line = ?line(Meta),
|
||||
Inner = build_reduce_clause(T, Expr, Acc, Acc, S),
|
||||
|
||||
True = pair(Line, cont, Inner),
|
||||
False = pair(Line, cont, Acc),
|
||||
|
||||
Clauses0 =
|
||||
case is_var(Left) of
|
||||
true -> [];
|
||||
false ->
|
||||
[{clause, -1,
|
||||
[{var, Line, '_'}, Acc], [],
|
||||
[False]}]
|
||||
end,
|
||||
|
||||
Clauses1 =
|
||||
[{clause, Line,
|
||||
[Left, Acc], [],
|
||||
[join_filters(Line, Filters, True, False)]}|Clauses0],
|
||||
|
||||
Args = [Right, pair(Line, cont, Arg), {'fun', Line, {clauses, Clauses1}}],
|
||||
Tuple = ?wrap_call(Line, 'Elixir.Enumerable', reduce, Args),
|
||||
|
||||
%% Use -1 because in case of no returns we don't care about the result
|
||||
?wrap_call(-1, erlang, element, [{integer, Line, 2}, Tuple]);
|
||||
|
||||
build_reduce_clause([{ bin, Meta, Left, Right, Filters }|T], Expr, Arg, Acc, S) ->
|
||||
Line = ?line(Meta),
|
||||
{ Tail, ST } = build_var(Line, S),
|
||||
{ Fun, SF } = build_var(Line, ST),
|
||||
|
||||
True = build_reduce_clause(T, Expr, Acc, Acc, SF),
|
||||
False = Acc,
|
||||
|
||||
{bin, _, Elements} = Left,
|
||||
|
||||
BinMatch =
|
||||
{bin, Line, Elements ++ [{ bin_element, Line, Tail, default, [bitstring] }]},
|
||||
NoVarMatch =
|
||||
{bin, Line, no_var(Elements) ++ [{ bin_element, Line, Tail, default, [bitstring] }]},
|
||||
|
||||
Clauses =
|
||||
[{clause, Line,
|
||||
[BinMatch, Acc], [],
|
||||
[{call, Line, Fun, [Tail, join_filters(Line, Filters, True, False)]}]},
|
||||
{clause, -1,
|
||||
[NoVarMatch, Acc], [],
|
||||
[{call, Line, Fun, [Tail, False]}]},
|
||||
{clause, -1,
|
||||
[{bin, Line, []}, Acc], [],
|
||||
[Acc]},
|
||||
{clause, -1,
|
||||
[Tail, {var, Line, '_'}], [],
|
||||
[?wrap_call(Line, erlang, error, [pair(Line, badarg, Tail)])]}],
|
||||
|
||||
{call, Line,
|
||||
{named_fun, Line, element(3, Fun), Clauses},
|
||||
[Right, Arg]};
|
||||
|
||||
build_reduce_clause([], Expr, _Arg, _Acc, _S) ->
|
||||
Expr.
|
||||
|
||||
is_var({var, _, _}) -> true;
|
||||
is_var(_) -> false.
|
||||
|
||||
pair(Line, Atom, Arg) ->
|
||||
{tuple, Line, [{atom, Line, Atom}, Arg]}.
|
||||
|
||||
build_var(Line, S) ->
|
||||
{ Name, _, ST } = elixir_scope:build_var('_', S),
|
||||
{ { var, Line, Name }, ST }.
|
||||
|
||||
no_var(Elements) ->
|
||||
[{bin_element, Line, no_var_expr(Expr), Size, Types} ||
|
||||
{bin_element, Line, Expr, Size, Types} <- Elements].
|
||||
no_var_expr({ var, Line, _ }) ->
|
||||
{var, Line, '_'}.
|
||||
|
||||
build_comprehension(Line, Clauses, Expr, false) ->
|
||||
{block, Line, [
|
||||
build_comprehension(Line, Clauses, Expr, {nil, Line}),
|
||||
{nil, Line}
|
||||
]};
|
||||
build_comprehension(Line, Clauses, Expr, Into) ->
|
||||
{comprehension_kind(Into), Line, Expr, comprehension_clause(Clauses)}.
|
||||
|
||||
comprehension_clause([{ Kind, Meta, Left, Right, Filters }|T]) ->
|
||||
Line = ?line(Meta),
|
||||
[{comprehension_generator(Kind), Line, Left, Right}] ++
|
||||
comprehension_filter(Line, Filters) ++
|
||||
comprehension_clause(T);
|
||||
comprehension_clause([]) ->
|
||||
[].
|
||||
|
||||
comprehension_kind({ nil, _ }) -> lc;
|
||||
comprehension_kind({ bin, _, [] }) -> bc.
|
||||
|
||||
comprehension_generator(enum) -> generate;
|
||||
comprehension_generator(bin) -> b_generate.
|
||||
|
||||
comprehension_expr({ bin, _, [] }, { bin, _, _ } = Expr) ->
|
||||
{ inline, Expr };
|
||||
comprehension_expr({ bin, Line, [] }, Expr) ->
|
||||
BinExpr = { bin, Line, [{ bin_element, Line, Expr, default, [bitstring] }] },
|
||||
{ inline, BinExpr };
|
||||
comprehension_expr({ nil, _ }, Expr) ->
|
||||
{ inline, Expr };
|
||||
comprehension_expr(false, Expr) ->
|
||||
{ inline, Expr };
|
||||
comprehension_expr(_, Expr) ->
|
||||
{ into, Expr }.
|
||||
|
||||
comprehension_filter(Line, Filters) ->
|
||||
[join_filter(Line, Filter, { atom, Line, true }, { atom, Line, false }) ||
|
||||
Filter <- lists:reverse(Filters)].
|
||||
|
||||
join_filters(_Line, [], True, _False) ->
|
||||
True;
|
||||
join_filters(Line, [H|T], True, False) ->
|
||||
lists:foldl(fun(Filter, Acc) ->
|
||||
join_filter(Line, Filter, Acc, False)
|
||||
end, join_filter(Line, H, True, False), T).
|
||||
|
||||
join_filter(Line, { nil, Filter }, True, False) ->
|
||||
{'case', Line, Filter, [
|
||||
{clause, Line, [{atom, Line, true}], [], [True] },
|
||||
{clause, Line, [{atom, Line, false}], [], [False] }
|
||||
] };
|
||||
join_filter(Line, { Var, Filter }, True, False) ->
|
||||
Guard =
|
||||
{op, Line, 'orelse',
|
||||
{op, Line, '==', Var, {atom, Line, false}},
|
||||
{op, Line, '==', Var, {atom, Line, nil}}},
|
||||
|
||||
{'case', Line, Filter, [
|
||||
{clause, Line, [Var], [[Guard]], [False] },
|
||||
{clause, Line, [{var, Line, '_'}], [], [True] }
|
||||
] }.
|
||||
@@ -176,35 +176,38 @@ remove_internals(Set) ->
|
||||
|
||||
%% Special forms
|
||||
|
||||
special_form('&',1) -> true;
|
||||
special_form('^',1) -> true;
|
||||
special_form('=',2) -> true;
|
||||
special_form('__op__',2) -> true;
|
||||
special_form('__op__',3) -> true;
|
||||
special_form('__block__',_) -> true;
|
||||
special_form('->',_) -> true;
|
||||
special_form('<<>>',_) -> true;
|
||||
special_form('{}',_) -> true;
|
||||
special_form('alias',1) -> true;
|
||||
special_form('alias',2) -> true;
|
||||
special_form('require',1) -> true;
|
||||
special_form('require',2) -> true;
|
||||
special_form('import',1) -> true;
|
||||
special_form('import',2) -> true;
|
||||
special_form('__ENV__',0) -> true;
|
||||
special_form('__CALLER__',0) -> true;
|
||||
special_form('__MODULE__',0) -> true;
|
||||
special_form('__DIR__',0) -> true;
|
||||
special_form('__aliases__',_) -> true;
|
||||
special_form('quote',1) -> true;
|
||||
special_form('quote',2) -> true;
|
||||
special_form('unquote',1) -> true;
|
||||
special_form('unquote_splicing',1) -> true;
|
||||
special_form('fn',_) -> true;
|
||||
special_form('super',_) -> true;
|
||||
special_form('bc',_) -> true;
|
||||
special_form('lc',_) -> true;
|
||||
special_form('case',2) -> true;
|
||||
special_form('try',2) -> true;
|
||||
special_form('receive',1) -> true;
|
||||
special_form('&', 1) -> true;
|
||||
special_form('^', 1) -> true;
|
||||
special_form('=', 2) -> true;
|
||||
special_form('%', 2) -> true;
|
||||
special_form('__op__', 2) -> true;
|
||||
special_form('__op__', 3) -> true;
|
||||
special_form('__block__', _) -> true;
|
||||
special_form('->', _) -> true;
|
||||
special_form('<<>>', _) -> true;
|
||||
special_form('{}', _) -> true;
|
||||
special_form('%{}', _) -> true;
|
||||
special_form('alias', 1) -> true;
|
||||
special_form('alias', 2) -> true;
|
||||
special_form('require', 1) -> true;
|
||||
special_form('require', 2) -> true;
|
||||
special_form('import', 1) -> true;
|
||||
special_form('import', 2) -> true;
|
||||
special_form('__ENV__', 0) -> true;
|
||||
special_form('__CALLER__', 0) -> true;
|
||||
special_form('__MODULE__', 0) -> true;
|
||||
special_form('__DIR__', 0) -> true;
|
||||
special_form('__aliases__', _) -> true;
|
||||
special_form('quote', 1) -> true;
|
||||
special_form('quote', 2) -> true;
|
||||
special_form('unquote', 1) -> true;
|
||||
special_form('unquote_splicing', 1) -> true;
|
||||
special_form('fn', _) -> true;
|
||||
special_form('super', _) -> true;
|
||||
special_form('for', _) -> true;
|
||||
special_form('bc', _) -> true;
|
||||
special_form('lc', _) -> true;
|
||||
special_form('case', 2) -> true;
|
||||
special_form('try', 2) -> true;
|
||||
special_form('receive', 1) -> true;
|
||||
special_form(_, _) -> false.
|
||||
|
||||
@@ -27,6 +27,12 @@ extract(Line, _Scope, _Interpol, [], _Buffer, _Search, _Output, Last) ->
|
||||
|
||||
%% Going through the string
|
||||
|
||||
extract(Line, Scope, Interpol, [$\\, $\n|Rest], Buffer, Search, Output, Last) ->
|
||||
extract(Line+1, Scope, Interpol, Rest, Buffer, Search, Output, Last);
|
||||
|
||||
extract(Line, Scope, Interpol, [$\\, $\r, $\n|Rest], Buffer, Search, Output, Last) ->
|
||||
extract(Line+1, Scope, Interpol, Rest, Buffer, Search, Output, Last);
|
||||
|
||||
extract(Line, Scope, Interpol, [$\n|Rest], Buffer, Search, Output, Last) ->
|
||||
extract(Line+1, Scope, Interpol, Rest, [$\n|Buffer], Search, Output, Last);
|
||||
|
||||
|
||||
@@ -6,13 +6,12 @@
|
||||
record_definition/3, record_defaults/4,
|
||||
ensure_no_function_conflict/4, warn_unused_local/3, format_error/1
|
||||
]).
|
||||
-export([macro_for/3, function_for/3]).
|
||||
-export([macro_for/3, local_for/3, local_for/4]).
|
||||
|
||||
-include("elixir.hrl").
|
||||
-define(attr, '__locals_tracker').
|
||||
-define(tracker, 'Elixir.Module.LocalsTracker').
|
||||
|
||||
macro_for(nil, _Name, _Arity) -> false;
|
||||
macro_for(Module, Name, Arity) ->
|
||||
Tuple = { Name, Arity },
|
||||
try elixir_def:lookup_definition(Module, Tuple) of
|
||||
@@ -25,13 +24,13 @@ macro_for(Module, Name, Arity) ->
|
||||
error:badarg -> false
|
||||
end.
|
||||
|
||||
function_for(Module, Name, Arity) ->
|
||||
local_for(Module, Name, Arity) ->
|
||||
local_for(Module, Name, Arity, nil).
|
||||
local_for(Module, Name, Arity, Given) ->
|
||||
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.
|
||||
{ { Tuple, Kind, Line, _, _, _, _ }, [_|_] = Clauses }
|
||||
when Given == nil; Kind == Given ->
|
||||
get_function(Line, Module, Clauses);
|
||||
_ ->
|
||||
[_|T] = erlang:get_stacktrace(),
|
||||
@@ -40,14 +39,14 @@ function_for(Module, Name, Arity) ->
|
||||
|
||||
get_function(Line, Module, Clauses) ->
|
||||
RewrittenClauses = [rewrite_clause(Clause, Module) || Clause <- Clauses],
|
||||
Fun = { 'fun', Line, {clauses, RewrittenClauses } },
|
||||
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 }
|
||||
{ atom, Line, local_for }
|
||||
},
|
||||
|
||||
%% If we have a macro, its arity in the table is
|
||||
|
||||
@@ -0,0 +1,175 @@
|
||||
-module(elixir_map).
|
||||
-export([expand_map/3, translate_map/3, expand_struct/4, translate_struct/4]).
|
||||
-import(elixir_errors, [compile_error/4]).
|
||||
-include("elixir.hrl").
|
||||
|
||||
expand_map(Meta, [{ '|', UpdateMeta, [Left, Right]}], E) ->
|
||||
{ [ELeft|ERight], EA } = elixir_exp:expand_args([Left|Right], E),
|
||||
{ { '%{}', Meta, [{ '|', UpdateMeta, [ELeft, ERight] }] }, EA };
|
||||
expand_map(Meta, Args, E) ->
|
||||
{ EArgs, EA } = elixir_exp:expand_args(Args, E),
|
||||
{ { '%{}', Meta, EArgs }, EA }.
|
||||
|
||||
expand_struct(Meta, Left, Right, E) ->
|
||||
{ [ELeft, ERight], EE } = elixir_exp:expand_args([Left, Right], E),
|
||||
|
||||
case is_atom(ELeft) of
|
||||
true -> ok;
|
||||
false ->
|
||||
compile_error(Meta, E#elixir_env.file, "expected struct name to be a compile "
|
||||
"time atom or alias, got: ~ts", ['Elixir.Macro':to_string(ELeft)])
|
||||
end,
|
||||
|
||||
EMeta =
|
||||
case lists:member(ELeft, E#elixir_env.context_modules) of
|
||||
true ->
|
||||
case (ELeft == E#elixir_env.module) and
|
||||
(E#elixir_env.function == nil) of
|
||||
true ->
|
||||
compile_error(Meta, E#elixir_env.file,
|
||||
"cannot access struct ~ts in body of the module that defines it as "
|
||||
"the struct fields are not yet accessible",
|
||||
[elixir_aliases:inspect(ELeft)]);
|
||||
false ->
|
||||
[{ struct, context }|Meta]
|
||||
end;
|
||||
false ->
|
||||
Meta
|
||||
end,
|
||||
|
||||
case ERight of
|
||||
{ '%{}', _, _ } -> ok;
|
||||
_ -> compile_error(Meta, E#elixir_env.file,
|
||||
"expected struct to be followed by a map, got: ~ts",
|
||||
['Elixir.Macro':to_string(ERight)])
|
||||
end,
|
||||
|
||||
{ { '%', EMeta, [ELeft, ERight] }, EE }.
|
||||
|
||||
translate_map(Meta, Args, S) ->
|
||||
{ Assocs, TUpdate, US } = extract_assoc_update(Args, S),
|
||||
translate_map(Meta, Assocs, TUpdate, US).
|
||||
|
||||
translate_struct(Meta, Name, { '%{}', MapMeta, Args }, S) ->
|
||||
{ Assocs, TUpdate, US } = extract_assoc_update(Args, S),
|
||||
Struct = load_struct(Meta, Name, S),
|
||||
|
||||
case is_map(Struct) of
|
||||
true ->
|
||||
assert_struct_keys(Meta, Name, Struct, Assocs, S);
|
||||
false ->
|
||||
compile_error(Meta, S#elixir_scope.file, "expected ~ts.__struct__/0 to "
|
||||
"return a map, got: ~ts", [elixir_aliases:inspect(Name), 'Elixir.Kernel':inspect(Struct)])
|
||||
end,
|
||||
|
||||
if
|
||||
TUpdate /= nil ->
|
||||
Line = ?line(Meta),
|
||||
{ VarName, _, VS } = elixir_scope:build_var('_', US),
|
||||
|
||||
Var = { var, Line, VarName },
|
||||
Map = { map, Line, [{ map_field_exact, Line, { atom, Line, '__struct__' }, { atom, Line, Name }}] },
|
||||
|
||||
Match = { match, Line, Var, Map },
|
||||
Error = { tuple, Line, [{ atom, Line, badstruct }, { atom, Line, Name }, Var] },
|
||||
|
||||
{ TMap, TS } = translate_map(MapMeta, Assocs, Var, VS),
|
||||
|
||||
{ { 'case', Line, TUpdate, [
|
||||
{ clause, Line, [Match], [], [TMap] },
|
||||
{ clause, Line, [Var], [], [?wrap_call(Line, erlang, error, [Error])] }
|
||||
] }, TS };
|
||||
S#elixir_scope.context == match ->
|
||||
translate_map(MapMeta, Assocs ++ [{'__struct__', Name}], nil, US);
|
||||
true ->
|
||||
Keys = [K || {K,_} <- Assocs],
|
||||
{ StructAssocs, _ } = elixir_quote:escape(maps:to_list(maps:without(Keys, Struct)), false),
|
||||
translate_map(MapMeta, StructAssocs ++ Assocs ++ [{'__struct__', Name}], nil, US)
|
||||
end.
|
||||
|
||||
%% Helpers
|
||||
|
||||
load_struct(Meta, Name, S) ->
|
||||
Local =
|
||||
elixir_module:is_open(Name) andalso
|
||||
(case lists:keyfind(struct, 1, Meta) of
|
||||
{ struct, context } -> true;
|
||||
_ -> wait_for_struct(Name)
|
||||
end),
|
||||
|
||||
try
|
||||
case Local of
|
||||
true ->
|
||||
try
|
||||
(elixir_locals:local_for(Name, '__struct__', 0, def))()
|
||||
catch
|
||||
error:undef -> Name:'__struct__'();
|
||||
error:badarg -> Name:'__struct__'()
|
||||
end;
|
||||
false ->
|
||||
Name:'__struct__'()
|
||||
end
|
||||
catch
|
||||
error:undef ->
|
||||
Inspected = elixir_aliases:inspect(Name),
|
||||
compile_error(Meta, S#elixir_scope.file, "~ts.__struct__/0 is undefined, "
|
||||
"cannot expand struct ~ts", [Inspected, Inspected])
|
||||
end.
|
||||
|
||||
wait_for_struct(Module) ->
|
||||
case erlang:get(elixir_compiler_pid) of
|
||||
undefined ->
|
||||
false;
|
||||
Pid ->
|
||||
Ref = erlang:make_ref(),
|
||||
Pid ! { waiting, struct, self(), Ref, Module },
|
||||
receive
|
||||
{ Ref, ready } ->
|
||||
true;
|
||||
{ Ref, release } ->
|
||||
'Elixir.Kernel.ErrorHandler':release(),
|
||||
false
|
||||
end
|
||||
end.
|
||||
|
||||
translate_map(Meta, Assocs, TUpdate, #elixir_scope{extra=Extra} = S) ->
|
||||
{ Op, KeyFun, ValFun } = extract_key_val_op(TUpdate, S),
|
||||
|
||||
Line = ?line(Meta),
|
||||
|
||||
{ TArgs, SA } = lists:mapfoldl(fun
|
||||
({ Key, Value }, Acc) ->
|
||||
{ TKey, Acc1 } = KeyFun(Key, Acc),
|
||||
{ TValue, Acc2 } = ValFun(Value, Acc1#elixir_scope{extra=Extra}),
|
||||
{ { Op, ?line(Meta), TKey, TValue }, Acc2 };
|
||||
(Other, _Acc) ->
|
||||
compile_error(Meta, S#elixir_scope.file, "expected key-value pairs in map, got: ~ts",
|
||||
['Elixir.Macro':to_string(Other)])
|
||||
end, S, Assocs),
|
||||
|
||||
build_map(Line, TUpdate, TArgs, SA).
|
||||
|
||||
extract_assoc_update([{ '|', _Meta, [Update, Args] }], S) ->
|
||||
{ TArg, SA } = elixir_translator:translate_arg(Update, S, S),
|
||||
{ Args, TArg, SA };
|
||||
extract_assoc_update(Args, SA) -> { Args, nil, SA }.
|
||||
|
||||
extract_key_val_op(_TUpdate, #elixir_scope{context=match}) ->
|
||||
{ map_field_exact,
|
||||
fun(X, Acc) -> elixir_translator:translate(X, Acc#elixir_scope{extra=map_key}) end,
|
||||
fun elixir_translator:translate/2 };
|
||||
extract_key_val_op(TUpdate, S) ->
|
||||
KS = #elixir_scope{extra=map_key},
|
||||
Op = if TUpdate == nil -> map_field_assoc; true -> map_field_exact end,
|
||||
{ Op,
|
||||
fun(X, Acc) -> elixir_translator:translate_arg(X, Acc, KS) end,
|
||||
fun(X, Acc) -> elixir_translator:translate_arg(X, Acc, S) end }.
|
||||
|
||||
build_map(Line, nil, TArgs, SA) -> { { map, Line, TArgs }, SA };
|
||||
build_map(Line, TUpdate, TArgs, SA) -> { { map, Line, TUpdate, TArgs }, SA }.
|
||||
|
||||
assert_struct_keys(Meta, Name, Struct, Assocs, S) ->
|
||||
[begin
|
||||
compile_error(Meta, S#elixir_scope.file, "unknown key ~ts for struct ~ts",
|
||||
['Elixir.Kernel':inspect(Key), elixir_aliases:inspect(Name)])
|
||||
end || { Key, _ } <- Assocs, not maps:is_key(Key, Struct)].
|
||||
@@ -1,5 +1,5 @@
|
||||
-module(elixir_module).
|
||||
-export([compile/4, data_table/1, docs_table/1,
|
||||
-export([compile/4, data_table/1, docs_table/1, is_open/1,
|
||||
format_error/1, eval_callbacks/5]).
|
||||
-include("elixir.hrl").
|
||||
|
||||
@@ -17,6 +17,9 @@ data_table(Module) ->
|
||||
docs_table(Module) ->
|
||||
ets:lookup_element(Module, ?docs_attr, 2).
|
||||
|
||||
is_open(Module) ->
|
||||
Module == ets:info(Module, name).
|
||||
|
||||
%% Compilation hook
|
||||
|
||||
compile(Module, Block, Vars, #elixir_env{line=Line} = Env) when is_atom(Module) ->
|
||||
@@ -127,12 +130,7 @@ eval_form(Line, Module, Block, Vars, E) ->
|
||||
|
||||
%% Return the form with exports and function declarations.
|
||||
|
||||
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,
|
||||
|
||||
functions_form(Line, File, Module, BaseAll, BaseExport, Def, Defmacro, BaseFunctions) ->
|
||||
Info = add_info_function(Line, File, Module, BaseExport, Def, Defmacro),
|
||||
|
||||
All = [{ '__info__', 1 }|BaseAll],
|
||||
@@ -143,17 +141,6 @@ functions_form(Line, File, Module, BaseAll, BaseExport, Def, Defmacro, RawFuncti
|
||||
{ attribute, Line, export, lists:sort(Export) } | Functions
|
||||
] }.
|
||||
|
||||
record_rewrite_functions(Module, Functions) ->
|
||||
lists:map(fun
|
||||
({ function, Line, Name, Arity, Clauses }) ->
|
||||
Rewriten = [begin
|
||||
{ C, _, _ } = 'Elixir.Kernel.RecordRewriter':optimize_clause(Module, Clause),
|
||||
C
|
||||
end || Clause <- Clauses],
|
||||
{ function, Line, Name, Arity, Rewriten };
|
||||
(Other) -> Other
|
||||
end, Functions).
|
||||
|
||||
%% Add attributes handling to the form
|
||||
|
||||
attributes_form(Line, _File, Module, Current) ->
|
||||
@@ -268,16 +255,24 @@ load_form(Line, Forms, Opts, #elixir_env{file=File} = E) ->
|
||||
end).
|
||||
|
||||
check_module_availability(Line, File, Module) ->
|
||||
Reserved = ['Elixir.Atom', 'Elixir.BitString', 'Elixir.Function',
|
||||
'Elixir.PID', 'Elixir.Reference', 'Elixir.Any'],
|
||||
|
||||
case lists:member(Module, Reserved) of
|
||||
true -> elixir_errors:handle_file_error(File, { Line, ?MODULE, { module_reserved, Module } });
|
||||
false -> ok
|
||||
end,
|
||||
|
||||
case elixir_compiler:get_opt(ignore_module_conflict) of
|
||||
false ->
|
||||
case code:ensure_loaded(Module) of
|
||||
{ module, _ } ->
|
||||
elixir_errors:handle_file_warning(File, { Line, ?MODULE, { module_defined, Module } });
|
||||
{ error, _ } ->
|
||||
[]
|
||||
{ error, _ } ->
|
||||
ok
|
||||
end;
|
||||
true ->
|
||||
[]
|
||||
ok
|
||||
end.
|
||||
|
||||
warn_invalid_clauses(_Line, _File, 'Elixir.Kernel.SpecialForms', _All) -> ok;
|
||||
@@ -407,6 +402,8 @@ 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_aliases:inspect(Module)]);
|
||||
format_error({ module_reserved, Module }) ->
|
||||
io_lib:format("module ~ts is reserved and cannot be defined", [elixir_aliases:inspect(Module)]);
|
||||
format_error({ module_in_definition, Module }) ->
|
||||
io_lib:format("cannot define module ~ts because it is currently being defined",
|
||||
[elixir_aliases:inspect(Module)]).
|
||||
|
||||
@@ -1,38 +1,37 @@
|
||||
Nonterminals
|
||||
grammar expr_list
|
||||
expr container_expr block_expr no_parens_expr identifier_expr max_expr
|
||||
bracket_expr bracket_at_expr base_expr matched_expr unmatched_expr
|
||||
expr container_expr block_expr no_parens_expr no_parens_one_expr access_expr
|
||||
bracket_expr bracket_at_expr matched_expr unmatched_expr max_expr
|
||||
op_expr matched_op_expr no_parens_op_expr
|
||||
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 pipe_op_eol stab_op_eol
|
||||
arrow_op_eol match_op_eol when_op_eol in_op_eol in_match_op_eol
|
||||
arrow_op_eol match_op_eol when_op_eol in_op_eol in_match_op_eol type_op_eol
|
||||
open_paren close_paren empty_paren
|
||||
open_bracket close_bracket
|
||||
open_curly close_curly
|
||||
open_bit close_bit
|
||||
container_args_base container_args
|
||||
call_args_parens_base call_args_parens parens_call
|
||||
list list_args open_bracket close_bracket
|
||||
tuple open_curly close_curly
|
||||
bit_string open_bit close_bit
|
||||
map map_op map_close map_args map_expr struct_op
|
||||
assoc_op_eol assoc_expr assoc_base assoc_update assoc_update_kw assoc
|
||||
container_args_base container_args call_args_parens_base call_args_parens parens_call
|
||||
call_args_no_parens_one call_args_no_parens_expr call_args_no_parens_comma_expr
|
||||
call_args_no_parens_all call_args_no_parens_many call_args_no_parens_many_strict
|
||||
stab stab_eol stab_expr stab_maybe_expr stab_parens_many
|
||||
kw_eol kw_expr kw_comma kw
|
||||
call_args_no_parens_kw_expr call_args_no_parens_kw_comma call_args_no_parens_kw
|
||||
kw_eol kw_base kw call_args_no_parens_kw_expr call_args_no_parens_kw
|
||||
dot_op dot_alias dot_identifier dot_op_identifier dot_do_identifier
|
||||
dot_paren_identifier dot_bracket_identifier
|
||||
var list list_args bit_string tuple
|
||||
do_block fn_eol do_eol end_eol block_eol block_item block_list
|
||||
.
|
||||
|
||||
Terminals
|
||||
identifier kw_identifier bracket_identifier
|
||||
identifier kw_identifier kw_identifier_string bracket_identifier
|
||||
paren_identifier do_identifier block_identifier
|
||||
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 in_op in_match_op
|
||||
dual_op add_op mult_op exp_op two_op pipe_op stab_op when_op
|
||||
comp_op at_op unary_op and_op or_op arrow_op match_op in_op in_match_op type_op
|
||||
dual_op add_op mult_op exp_op two_op pipe_op stab_op when_op assoc_op
|
||||
'true' 'false' 'nil' 'do' eol ',' '.'
|
||||
'(' ')' '[' ']' '{' '}' '<<' '>>'
|
||||
'(' ')' '[' ']' '{' '}' '<<' '>>' '%{}' '%'
|
||||
.
|
||||
|
||||
Rootsymbol grammar.
|
||||
@@ -41,13 +40,17 @@ Rootsymbol grammar.
|
||||
Expect 2.
|
||||
|
||||
%% Changes in ops and precedence should be reflected on lib/elixir/lib/macro.ex
|
||||
%% Note though the operator => in practice has lower precedence than all others.
|
||||
%% Its entry in the table is only to support the %{ user | foo => bar } syntax.
|
||||
Left 5 do.
|
||||
Right 10 stab_op_eol. %% ->
|
||||
Left 20 ','.
|
||||
Left 40 in_match_op_eol. %% <-, inlist, inbits, //, \\, :: (allowed in matches along =)
|
||||
Right 50 pipe_op_eol. %% |
|
||||
Right 70 when_op_eol. %% when
|
||||
Right 80 match_op_eol. %% =
|
||||
Left 40 in_match_op_eol. %% <-, inlist, inbits, \\ (allowed in matches along =)
|
||||
Right 50 when_op_eol. %% when
|
||||
Right 60 type_op_eol. %% ::
|
||||
Right 70 pipe_op_eol. %% |
|
||||
Right 80 assoc_op_eol. %% =>
|
||||
Right 90 match_op_eol. %% =
|
||||
Left 130 or_op_eol. %% ||, |||, or, xor
|
||||
Left 140 and_op_eol. %% &&, &&&, and
|
||||
Left 150 comp_op_eol. %% <, >, <=, >=, ==, !=, =~, ===, !==
|
||||
@@ -60,8 +63,8 @@ Left 250 exp_op_eol. %% ^ (op) (e.g ^^^)
|
||||
Nonassoc 300 unary_op_eol. %% +, -, !, ^, not, &, ~~~
|
||||
Left 310 dot_call_op.
|
||||
Left 310 dot_op. %% .
|
||||
Nonassoc 320 at_op_eol. %% @ (op)
|
||||
Nonassoc 330 var.
|
||||
Nonassoc 320 at_op_eol. %% @
|
||||
Nonassoc 330 dot_identifier.
|
||||
|
||||
%%% MAIN FLOW OF EXPRESSIONS
|
||||
|
||||
@@ -105,13 +108,12 @@ expr -> unmatched_expr : '$1'.
|
||||
%% 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 number : build_unary_op('$1', ?exprs('$2')).
|
||||
matched_expr -> unary_op_eol matched_expr : build_unary_op('$1', '$2').
|
||||
matched_expr -> unary_op_eol no_parens_expr : build_unary_op('$1', '$2').
|
||||
matched_expr -> at_op_eol matched_expr : build_unary_op('$1', '$2').
|
||||
matched_expr -> at_op_eol no_parens_expr : build_unary_op('$1', '$2').
|
||||
matched_expr -> bracket_at_expr : '$1'.
|
||||
matched_expr -> identifier_expr : '$1'.
|
||||
matched_expr -> no_parens_one_expr : '$1'.
|
||||
matched_expr -> access_expr : '$1'.
|
||||
|
||||
no_parens_expr -> dot_op_identifier call_args_no_parens_many_strict : build_identifier('$1', '$2').
|
||||
no_parens_expr -> dot_identifier call_args_no_parens_many_strict : build_identifier('$1', '$2').
|
||||
@@ -137,6 +139,7 @@ op_expr -> and_op_eol expr : { '$1', '$2' }.
|
||||
op_expr -> or_op_eol expr : { '$1', '$2' }.
|
||||
op_expr -> in_op_eol expr : { '$1', '$2' }.
|
||||
op_expr -> in_match_op_eol expr : { '$1', '$2' }.
|
||||
op_expr -> type_op_eol expr : { '$1', '$2' }.
|
||||
op_expr -> when_op_eol expr : { '$1', '$2' }.
|
||||
op_expr -> pipe_op_eol expr : { '$1', '$2' }.
|
||||
op_expr -> comp_op_eol expr : { '$1', '$2' }.
|
||||
@@ -151,6 +154,7 @@ 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 -> in_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 -> pipe_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' }.
|
||||
@@ -168,56 +172,61 @@ matched_op_expr -> and_op_eol matched_expr : { '$1', '$2' }.
|
||||
matched_op_expr -> or_op_eol matched_expr : { '$1', '$2' }.
|
||||
matched_op_expr -> in_op_eol matched_expr : { '$1', '$2' }.
|
||||
matched_op_expr -> in_match_op_eol matched_expr : { '$1', '$2' }.
|
||||
matched_op_expr -> type_op_eol matched_expr : { '$1', '$2' }.
|
||||
matched_op_expr -> when_op_eol matched_expr : { '$1', '$2' }.
|
||||
matched_op_expr -> pipe_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' }.
|
||||
|
||||
identifier_expr -> dot_op_identifier call_args_no_parens_one : build_identifier('$1', '$2').
|
||||
identifier_expr -> dot_identifier call_args_no_parens_one : build_identifier('$1', '$2').
|
||||
identifier_expr -> dot_do_identifier : build_identifier('$1', nil).
|
||||
identifier_expr -> var : build_identifier('$1', nil).
|
||||
identifier_expr -> max_expr : '$1'.
|
||||
no_parens_one_expr -> dot_op_identifier call_args_no_parens_one : build_identifier('$1', '$2').
|
||||
no_parens_one_expr -> dot_identifier call_args_no_parens_one : build_identifier('$1', '$2').
|
||||
no_parens_one_expr -> dot_do_identifier : build_identifier('$1', nil).
|
||||
no_parens_one_expr -> dot_identifier : build_identifier('$1', nil).
|
||||
|
||||
max_expr -> fn_eol stab end_eol : build_fn('$1', build_stab(lists:reverse('$2'))).
|
||||
max_expr -> open_paren stab close_paren : build_stab(lists:reverse('$2')).
|
||||
max_expr -> bracket_expr : '$1'.
|
||||
%% From this point on, we just have constructs that can be
|
||||
%% used with the access syntax. Notice that (dot_)identifier
|
||||
%% is not included in this list simply because the tokenizer
|
||||
%% marks identifiers followed by brackets as bracket_identifier.
|
||||
access_expr -> bracket_at_expr : '$1'.
|
||||
access_expr -> bracket_expr : '$1'.
|
||||
access_expr -> unary_op_eol number : build_unary_op('$1', ?exprs('$2')).
|
||||
access_expr -> fn_eol stab end_eol : build_fn('$1', build_stab(reverse('$2'))).
|
||||
access_expr -> open_paren stab close_paren : build_stab(reverse('$2')).
|
||||
access_expr -> number : ?exprs('$1').
|
||||
access_expr -> signed_number : { element(4, '$1'), meta('$1'), ?exprs('$1') }.
|
||||
access_expr -> list : element(1, '$1').
|
||||
access_expr -> map : '$1'.
|
||||
access_expr -> tuple : '$1'.
|
||||
access_expr -> 'true' : ?id('$1').
|
||||
access_expr -> 'false' : ?id('$1').
|
||||
access_expr -> 'nil' : ?id('$1').
|
||||
access_expr -> bin_string : build_bin_string('$1').
|
||||
access_expr -> list_string : build_list_string('$1').
|
||||
access_expr -> bit_string : '$1'.
|
||||
access_expr -> sigil : build_sigil('$1').
|
||||
access_expr -> max_expr : '$1'.
|
||||
|
||||
%% Aliases and properly formed calls. Used by map_expr.
|
||||
max_expr -> atom : ?exprs('$1').
|
||||
max_expr -> atom_string : build_atom_string('$1').
|
||||
max_expr -> parens_call call_args_parens : build_identifier('$1', '$2').
|
||||
max_expr -> parens_call call_args_parens call_args_parens : build_nested_parens('$1', '$2', '$3').
|
||||
max_expr -> dot_alias : '$1'.
|
||||
max_expr -> base_expr : '$1'.
|
||||
|
||||
bracket_expr -> dot_bracket_identifier list : build_access(build_identifier('$1', nil), '$2').
|
||||
bracket_expr -> max_expr list : build_access('$1', '$2').
|
||||
bracket_expr -> access_expr list : build_access('$1', '$2').
|
||||
|
||||
bracket_at_expr -> at_op_eol dot_bracket_identifier list :
|
||||
build_access(build_unary_op('$1', build_identifier('$2', nil)), '$3').
|
||||
bracket_at_expr -> at_op_eol max_expr list :
|
||||
bracket_at_expr -> at_op_eol access_expr list :
|
||||
build_access(build_unary_op('$1', '$2'), '$3').
|
||||
bracket_at_expr -> bracket_at_expr list :
|
||||
build_access('$1', '$2').
|
||||
|
||||
base_expr -> atom : ?exprs('$1').
|
||||
base_expr -> number : ?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__', 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').
|
||||
|
||||
%% Blocks
|
||||
|
||||
do_block -> do_eol 'end' : [[{do,nil}]].
|
||||
do_block -> do_eol stab end_eol : [[{ do, build_stab(lists:reverse('$2')) }]].
|
||||
do_block -> do_eol stab end_eol : [[{ do, build_stab(reverse('$2')) }]].
|
||||
do_block -> do_eol block_list 'end' : [[{ do, nil }|'$2']].
|
||||
do_block -> do_eol stab_eol block_list 'end' : [[{ do, build_stab(lists:reverse('$2')) }|'$3']].
|
||||
do_block -> do_eol stab_eol block_list 'end' : [[{ do, build_stab(reverse('$2')) }|'$3']].
|
||||
|
||||
fn_eol -> 'fn' : '$1'.
|
||||
fn_eol -> 'fn' eol : '$1'.
|
||||
@@ -249,7 +258,7 @@ stab_expr -> stab_parens_many when_op expr stab_op_eol stab_maybe_expr :
|
||||
stab_maybe_expr -> 'expr' : '$1'.
|
||||
stab_maybe_expr -> '$empty' : nil.
|
||||
|
||||
block_item -> block_eol stab_eol : { ?exprs('$1'), build_stab(lists:reverse('$2')) }.
|
||||
block_item -> block_eol stab_eol : { ?exprs('$1'), build_stab(reverse('$2')) }.
|
||||
block_item -> block_eol : { ?exprs('$1'), nil }.
|
||||
|
||||
block_list -> block_item : ['$1'].
|
||||
@@ -257,8 +266,6 @@ block_list -> block_item block_list : ['$1'|'$2'].
|
||||
|
||||
%% Helpers
|
||||
|
||||
var -> dot_identifier : '$1'.
|
||||
|
||||
open_paren -> '(' : '$1'.
|
||||
open_paren -> '(' eol : '$1'.
|
||||
close_paren -> ')' : '$1'.
|
||||
@@ -266,20 +273,20 @@ close_paren -> eol ')' : '$2'.
|
||||
|
||||
empty_paren -> open_paren ')' : '$1'.
|
||||
|
||||
open_bracket -> '[' : '$1'.
|
||||
open_bracket -> '[' eol : '$1'.
|
||||
close_bracket -> ']' : '$1'.
|
||||
close_bracket -> eol ']' : '$2'.
|
||||
open_bracket -> '[' : '$1'.
|
||||
open_bracket -> '[' eol : '$1'.
|
||||
close_bracket -> ']' : '$1'.
|
||||
close_bracket -> eol ']' : '$2'.
|
||||
|
||||
open_bit -> '<<' : '$1'.
|
||||
open_bit -> '<<' eol : '$1'.
|
||||
close_bit -> '>>' : '$1'.
|
||||
close_bit -> eol '>>' : '$2'.
|
||||
open_bit -> '<<' : '$1'.
|
||||
open_bit -> '<<' eol : '$1'.
|
||||
close_bit -> '>>' : '$1'.
|
||||
close_bit -> eol '>>' : '$2'.
|
||||
|
||||
open_curly -> '{' : '$1'.
|
||||
open_curly -> '{' eol : '$1'.
|
||||
close_curly -> '}' : '$1'.
|
||||
close_curly -> eol '}' : '$2'.
|
||||
close_curly -> '}' : '$1'.
|
||||
close_curly -> eol '}' : '$2'.
|
||||
|
||||
% Operators
|
||||
|
||||
@@ -320,6 +327,9 @@ in_op_eol -> in_op eol : '$1'.
|
||||
in_match_op_eol -> in_match_op : '$1'.
|
||||
in_match_op_eol -> in_match_op eol : '$1'.
|
||||
|
||||
type_op_eol -> type_op : '$1'.
|
||||
type_op_eol -> type_op eol : '$1'.
|
||||
|
||||
when_op_eol -> when_op : '$1'.
|
||||
when_op_eol -> when_op eol : '$1'.
|
||||
|
||||
@@ -343,6 +353,7 @@ dot_op -> '.' eol : '$1'.
|
||||
dot_identifier -> identifier : '$1'.
|
||||
dot_identifier -> matched_expr dot_op identifier : build_dot('$2', '$1', '$3').
|
||||
|
||||
dot_alias -> aliases : { '__aliases__', meta('$1', 0), ?exprs('$1') }.
|
||||
dot_alias -> matched_expr dot_op aliases : build_dot_alias('$2', '$1', '$3').
|
||||
|
||||
dot_op_identifier -> op_identifier : '$1'.
|
||||
@@ -377,8 +388,8 @@ call_args_no_parens_one -> matched_expr : ['$1'].
|
||||
call_args_no_parens_one -> no_parens_expr : ['$1'].
|
||||
|
||||
call_args_no_parens_many -> matched_expr ',' call_args_no_parens_kw : ['$1', '$3'].
|
||||
call_args_no_parens_many -> call_args_no_parens_comma_expr : lists:reverse('$1').
|
||||
call_args_no_parens_many -> call_args_no_parens_comma_expr ',' call_args_no_parens_kw : lists:reverse(['$3'|'$1']).
|
||||
call_args_no_parens_many -> call_args_no_parens_comma_expr : reverse('$1').
|
||||
call_args_no_parens_many -> call_args_no_parens_comma_expr ',' call_args_no_parens_kw : reverse(['$3'|'$1']).
|
||||
|
||||
call_args_no_parens_many_strict -> call_args_no_parens_many : '$1'.
|
||||
call_args_no_parens_many_strict -> empty_paren : throw_no_parens_strict('$1').
|
||||
@@ -410,36 +421,35 @@ call_args_parens_base -> call_args_parens_base ',' container_expr : ['$3'|'$1'].
|
||||
call_args_parens -> empty_paren : [].
|
||||
call_args_parens -> open_paren no_parens_expr close_paren : ['$2'].
|
||||
call_args_parens -> open_paren kw close_paren : ['$2'].
|
||||
call_args_parens -> open_paren call_args_parens_base close_paren : lists:reverse('$2').
|
||||
call_args_parens -> open_paren call_args_parens_base ',' kw close_paren : lists:reverse(['$4'|'$2']).
|
||||
call_args_parens -> open_paren call_args_parens_base close_paren : reverse('$2').
|
||||
call_args_parens -> open_paren call_args_parens_base ',' kw close_paren : reverse(['$4'|'$2']).
|
||||
|
||||
% KV
|
||||
|
||||
kw_eol -> kw_identifier : '$1'.
|
||||
kw_eol -> kw_identifier eol : '$1'.
|
||||
kw_eol -> kw_identifier : ?exprs('$1').
|
||||
kw_eol -> kw_identifier eol : ?exprs('$1').
|
||||
kw_eol -> kw_identifier_string : build_atom_string('$1').
|
||||
kw_eol -> kw_identifier_string eol : build_atom_string('$1').
|
||||
|
||||
kw_expr -> kw_eol container_expr : { ?exprs('$1'),'$2' }.
|
||||
kw_comma -> kw_expr ',' : ['$1'].
|
||||
kw_comma -> kw_comma kw_expr ',' : ['$2'|'$1'].
|
||||
kw_base -> kw_eol container_expr : [{ '$1', '$2' }].
|
||||
kw_base -> kw_base ',' kw_eol container_expr : [{ '$3', '$4' }|'$1'].
|
||||
|
||||
kw -> kw_expr : ['$1'].
|
||||
kw -> kw_comma : lists:reverse('$1').
|
||||
kw -> kw_comma kw_expr : lists:reverse(['$2'|'$1']).
|
||||
kw -> kw_base : reverse('$1').
|
||||
kw -> kw_base ',' : reverse('$1').
|
||||
|
||||
call_args_no_parens_kw_expr -> kw_eol call_args_no_parens_expr : { ?exprs('$1'),'$2' }.
|
||||
call_args_no_parens_kw_comma -> call_args_no_parens_kw_expr : ['$1'].
|
||||
call_args_no_parens_kw_comma -> call_args_no_parens_kw_expr ',' call_args_no_parens_kw_comma : ['$1'|'$3'].
|
||||
call_args_no_parens_kw -> call_args_no_parens_kw_comma : '$1'.
|
||||
call_args_no_parens_kw_expr -> kw_eol call_args_no_parens_expr : { '$1','$2' }.
|
||||
call_args_no_parens_kw -> call_args_no_parens_kw_expr : ['$1'].
|
||||
call_args_no_parens_kw -> call_args_no_parens_kw_expr ',' call_args_no_parens_kw : ['$1'|'$3'].
|
||||
|
||||
% Lists
|
||||
|
||||
list_args -> kw : '$1'.
|
||||
list_args -> container_args_base : lists:reverse('$1').
|
||||
list_args -> container_args_base ',' : lists:reverse('$1').
|
||||
list_args -> container_args_base ',' kw : lists:reverse('$1') ++ '$3'.
|
||||
list_args -> container_args_base : reverse('$1').
|
||||
list_args -> container_args_base ',' : reverse('$1').
|
||||
list_args -> container_args_base ',' kw : reverse('$1', '$3').
|
||||
|
||||
list -> open_bracket ']' : { [], ?line('$1') }.
|
||||
list -> open_bracket list_args close_bracket : { '$2', ?line('$1') }.
|
||||
list -> open_bracket ']' : build_list('$1', []).
|
||||
list -> open_bracket list_args close_bracket : build_list('$1', '$2').
|
||||
|
||||
% Tuple
|
||||
|
||||
@@ -448,19 +458,67 @@ tuple -> open_curly container_args close_curly : build_tuple('$1', '$2').
|
||||
|
||||
% Bitstrings
|
||||
|
||||
bit_string -> open_bit '>>' : { '<<>>', meta('$1'), [] }.
|
||||
bit_string -> open_bit container_args close_bit : { '<<>>', meta('$1'), '$2' }.
|
||||
bit_string -> open_bit '>>' : build_bit('$1', []).
|
||||
bit_string -> open_bit container_args close_bit : build_bit('$1', '$2').
|
||||
|
||||
% Map and structs
|
||||
|
||||
map_expr -> max_expr : '$1'.
|
||||
map_expr -> dot_identifier : build_identifier('$1', nil).
|
||||
map_expr -> at_op_eol map_expr : build_unary_op('$1', '$2').
|
||||
|
||||
assoc_op_eol -> assoc_op : '$1'.
|
||||
assoc_op_eol -> assoc_op eol : '$1'.
|
||||
|
||||
assoc_expr -> container_expr assoc_op_eol container_expr : { '$1', '$3' }.
|
||||
assoc_expr -> map_expr : '$1'.
|
||||
|
||||
assoc_update -> matched_expr pipe_op_eol matched_expr assoc_op_eol matched_expr : { '$2', '$1', [{ '$3', '$5' }] }.
|
||||
assoc_update -> unmatched_expr pipe_op_eol expr assoc_op_eol expr : { '$2', '$1', [{ '$3', '$5' }] }.
|
||||
assoc_update -> matched_expr pipe_op_eol map_expr : { '$2', '$1', ['$3'] }.
|
||||
|
||||
assoc_update_kw -> matched_expr pipe_op_eol kw : { '$2', '$1', '$3' }.
|
||||
assoc_update_kw -> unmatched_expr pipe_op_eol kw : { '$2', '$1', '$3' }.
|
||||
|
||||
assoc_base -> assoc_expr : ['$1'].
|
||||
assoc_base -> assoc_base ',' assoc_expr : ['$3'|'$1'].
|
||||
|
||||
assoc -> assoc_base : reverse('$1').
|
||||
assoc -> assoc_base ',' : reverse('$1').
|
||||
|
||||
map_op -> '%{}' : '$1'.
|
||||
map_op -> '%{}' eol : '$1'.
|
||||
|
||||
map_close -> kw close_curly : '$1'.
|
||||
map_close -> assoc close_curly : '$1'.
|
||||
map_close -> assoc_base ',' kw close_curly : reverse('$1', '$3').
|
||||
|
||||
map_args -> open_curly '}' : build_map('$1', []).
|
||||
map_args -> open_curly map_close : build_map('$1', '$2').
|
||||
map_args -> open_curly assoc_update close_curly : build_map_update('$1', '$2', []).
|
||||
map_args -> open_curly assoc_update ',' close_curly : build_map_update('$1', '$2', []).
|
||||
map_args -> open_curly assoc_update ',' map_close : build_map_update('$1', '$2', '$4').
|
||||
map_args -> open_curly assoc_update_kw close_curly : build_map_update('$1', '$2', []).
|
||||
|
||||
struct_op -> '%' : '$1'.
|
||||
struct_op -> '%' eol : '$1'.
|
||||
|
||||
map -> map_op map_args : '$2'.
|
||||
map -> struct_op map_expr map_args : { '%', meta('$1'), ['$2', '$3'] }.
|
||||
map -> struct_op map_expr eol map_args : { '%', meta('$1'), ['$2', '$4'] }.
|
||||
|
||||
Erlang code.
|
||||
|
||||
-define(id(Node), element(1, Node)).
|
||||
-define(line(Node), element(2, Node)).
|
||||
-define(exprs(Node), element(3, Node)).
|
||||
-define(rearrange_uop(Op), Op == 'not' orelse Op == '!').
|
||||
-define(rearrange_uop(Op), (Op == 'not' orelse Op == '!')).
|
||||
-define(is_atom_string(Atom), (Atom == atom_string orelse Atom == kw_identifier_string)).
|
||||
|
||||
%% The following directive is needed for (significantly) faster
|
||||
%% compilation of the generated .erl file by the HiPE compiler
|
||||
-compile([{hipe,[{regalloc,linear_scan}]}]).
|
||||
-import(lists, [reverse/1, reverse/2]).
|
||||
|
||||
meta(Line, Counter) -> [{counter,Counter}|meta(Line)].
|
||||
meta(Line) when is_integer(Line) -> [{line,Line}];
|
||||
@@ -483,12 +541,23 @@ build_op({ _Kind, Line, Op }, Left, Right) ->
|
||||
build_unary_op({ _Kind, Line, Op }, Expr) ->
|
||||
{ Op, meta(Line), [Expr] }.
|
||||
|
||||
build_list(Marker, Args) ->
|
||||
{ Args, ?line(Marker) }.
|
||||
|
||||
build_tuple(_Marker, [Left, Right]) ->
|
||||
{ Left, Right };
|
||||
|
||||
build_tuple(Marker, Args) ->
|
||||
{ '{}', meta(Marker), Args }.
|
||||
|
||||
build_bit(Marker, Args) ->
|
||||
{ '<<>>', meta(Marker), Args }.
|
||||
|
||||
build_map(Marker, Args) ->
|
||||
{ '%{}', meta(Marker), Args }.
|
||||
|
||||
build_map_update(Marker, { Pipe, Left, Right }, Extra) ->
|
||||
{ '%{}', meta(Marker), [build_op(Pipe, Left, Right ++ Extra)] }.
|
||||
|
||||
%% Blocks
|
||||
|
||||
build_block([{Op,_,[_]}]=Exprs) when ?rearrange_uop(Op) -> { '__block__', [], Exprs };
|
||||
@@ -564,9 +633,9 @@ build_list_string({ list_string, Line, Args }) ->
|
||||
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) ->
|
||||
build_atom_string({ Atom, _Line, Safe, [H] }) when ?is_atom_string(Atom) andalso is_binary(H) ->
|
||||
Op = binary_to_atom_op(Safe), erlang:Op(H, utf8);
|
||||
build_atom_string({ atom_string, Line, Safe, Args }) ->
|
||||
build_atom_string({ Atom, Line, Safe, Args }) when ?is_atom_string(Atom) ->
|
||||
Meta = meta(Line),
|
||||
{ { '.', Meta, [erlang, binary_to_atom_op(Safe)] }, Meta, [{ '<<>>', Meta, string_parts(Args) }, utf8] }.
|
||||
|
||||
@@ -597,15 +666,15 @@ build_stab(Else) ->
|
||||
build_block(Else).
|
||||
|
||||
build_stab(Old, [{ '->', New, [Left, Right] }|T], Marker, Temp, Acc) ->
|
||||
H = { '->', Old, [Marker, build_block(lists:reverse(Temp))] },
|
||||
H = { '->', Old, [Marker, build_block(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 = { '->', Meta, [Marker, build_block(lists:reverse(Temp))] },
|
||||
lists:reverse([H|Acc]).
|
||||
H = { '->', Meta, [Marker, build_block(reverse(Temp))] },
|
||||
reverse([H|Acc]).
|
||||
|
||||
%% Every time the parser sees a (unquote_splicing())
|
||||
%% it assumes that a block is being spliced, wrapping
|
||||
@@ -628,7 +697,7 @@ unwrap_when(Args) ->
|
||||
end.
|
||||
|
||||
to_block([One]) when not is_list(One) -> One;
|
||||
to_block(Other) -> { '__block__', [], lists:reverse(Other) }.
|
||||
to_block(Other) -> { '__block__', [], reverse(Other) }.
|
||||
|
||||
%% Errors
|
||||
|
||||
|
||||
@@ -240,13 +240,16 @@ do_quote({ Left, Right }, Q, E) ->
|
||||
{ TRight, RQ } = do_quote(Right, LQ, E),
|
||||
{ { TLeft, TRight }, RQ };
|
||||
|
||||
do_quote(Map, #elixir_quote{escape=true} = Q, E) when is_map(Map) ->
|
||||
{ TT, TQ } = do_quote(maps:to_list(Map), Q, E),
|
||||
{ { '%{}', [], TT }, TQ };
|
||||
|
||||
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, E) when is_list(List) ->
|
||||
% The improper case is pretty inefficient, but improper lists are hopefully
|
||||
% rare.
|
||||
% The improper case is pretty inefficient, but improper lists are are.
|
||||
case reverse_improper(List) of
|
||||
{ L } -> do_splice(L, Q, E);
|
||||
{ L, R } ->
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
-module(elixir_tokenizer).
|
||||
-include("elixir.hrl").
|
||||
-export([tokenize/3]).
|
||||
-import(elixir_interpolation, [unescape_chars/1, unescape_tokens/1]).
|
||||
-import(elixir_interpolation, [unescape_tokens/1]).
|
||||
|
||||
-define(container(T1, T2),
|
||||
T1 == ${, T2 == $}
|
||||
T1 == ${, T2 == $};
|
||||
T1 == $[, T2 == $]
|
||||
).
|
||||
|
||||
-define(at_op(T),
|
||||
@@ -72,13 +73,14 @@
|
||||
|
||||
-define(in_match_op(T1, T2),
|
||||
T1 == $<, T2 == $-;
|
||||
T1 == $/, T2 == $/;
|
||||
T1 == $\\, T2 == $\\;
|
||||
T1 == $:, T2 == $:).
|
||||
T1 == $\\, T2 == $\\).
|
||||
|
||||
-define(stab_op(T1, T2),
|
||||
T1 == $-, T2 == $>).
|
||||
|
||||
-define(type_op(T1, T2),
|
||||
T1 == $:, T2 == $:).
|
||||
|
||||
-define(pipe_op(T1),
|
||||
T == $|).
|
||||
|
||||
@@ -137,31 +139,6 @@ tokenize([$#|String], Line, Scope, Tokens) ->
|
||||
|
||||
% Sigils
|
||||
|
||||
tokenize([$%,S,H,H,H|T] = Original, Line, Scope, Tokens) when ?is_quote(H), ?is_upcase(S) orelse ?is_downcase(S) ->
|
||||
io:format(standard_error, "~ts:~p: warning: using % for sigils is deprecated, "
|
||||
"please use ~~ instead~n", [Scope#elixir_tokenizer.file, Line]),
|
||||
|
||||
case extract_heredoc_with_interpolation(Line, Scope, ?is_downcase(S), T, H) of
|
||||
{ ok, NewLine, Parts, Rest } ->
|
||||
{ Final, Modifiers } = collect_modifiers(Rest, []),
|
||||
tokenize(Final, NewLine, Scope, [{ sigil, Line, S, Parts, Modifiers }|Tokens]);
|
||||
{ error, Reason } ->
|
||||
{ error, Reason, Original, Tokens }
|
||||
end;
|
||||
|
||||
tokenize([$%,S,H|T] = Original, Line, Scope, Tokens) when ?is_sigil(H), ?is_upcase(S) orelse ?is_downcase(S) ->
|
||||
io:format(standard_error, "~ts:~p: warning: using % for sigils is deprecated, "
|
||||
"please use ~~ instead~n", [Scope#elixir_tokenizer.file, Line]),
|
||||
|
||||
case elixir_interpolation:extract(Line, Scope, ?is_downcase(S), T, sigil_terminator(H)) of
|
||||
{ NewLine, Parts, Rest } ->
|
||||
{ Final, Modifiers } = collect_modifiers(Rest, []),
|
||||
tokenize(Final, NewLine, Scope, [{ sigil, Line, S, Parts, Modifiers }|Tokens]);
|
||||
{ error, Reason } ->
|
||||
Sigil = [$%,S,H],
|
||||
interpolation_error(Reason, Original, Tokens, " (for sigil ~ts starting at line ~B)", [Sigil, Line])
|
||||
end;
|
||||
|
||||
tokenize([$~,S,H,H,H|T] = Original, Line, Scope, Tokens) when ?is_quote(H), ?is_upcase(S) orelse ?is_downcase(S) ->
|
||||
case extract_heredoc_with_interpolation(Line, Scope, ?is_downcase(S), T, H) of
|
||||
{ ok, NewLine, Parts, Rest } ->
|
||||
@@ -270,18 +247,30 @@ tokenize([$:,T|String] = Original, Line, Scope, Tokens) when ?is_atom_start(T) -
|
||||
{ error, Reason, Original, Tokens }
|
||||
end;
|
||||
|
||||
% Atom identifiers/operators
|
||||
% %% Special atom identifiers / operators
|
||||
|
||||
tokenize(":..." ++ Rest, Line, Scope, Tokens) ->
|
||||
tokenize(Rest, Line, Scope, [{ atom, Line, '...' }|Tokens]);
|
||||
|
||||
% ## Containers
|
||||
tokenize(":<<>>" ++ Rest, Line, Scope, Tokens) ->
|
||||
tokenize(Rest, Line, Scope, [{ atom, Line, '<<>>' }|Tokens]);
|
||||
|
||||
tokenize(":%{}" ++ Rest, Line, Scope, Tokens) ->
|
||||
tokenize(Rest, Line, Scope, [{ atom, Line, '%{}' }|Tokens]);
|
||||
tokenize(":%" ++ Rest, Line, Scope, Tokens) ->
|
||||
tokenize(Rest, Line, Scope, [{ atom, Line, '%' }|Tokens]);
|
||||
tokenize([$:,T1,T2|Rest], Line, Scope, Tokens) when ?container(T1, T2) ->
|
||||
tokenize(Rest, Line, Scope, [{ atom, Line, list_to_atom([T1,T2]) }|Tokens]);
|
||||
|
||||
tokenize("...:" ++ Rest, Line, Scope, Tokens) when ?is_space(hd(Rest)) ->
|
||||
tokenize(Rest, Line, Scope, [{ kw_identifier, Line, '...' }|Tokens]);
|
||||
tokenize("<<>>:" ++ Rest, Line, Scope, Tokens) when ?is_space(hd(Rest)) ->
|
||||
tokenize(Rest, Line, Scope, [{ kw_identifier, Line, '<<>>' }|Tokens]);
|
||||
tokenize("%{}:" ++ Rest, Line, Scope, Tokens) when ?is_space(hd(Rest)) ->
|
||||
tokenize(Rest, Line, Scope, [{ kw_identifier, Line, '%{}' }|Tokens]);
|
||||
tokenize("%:" ++ Rest, Line, Scope, Tokens) when ?is_space(hd(Rest)) ->
|
||||
tokenize(Rest, Line, Scope, [{ kw_identifier, Line, '%' }|Tokens]);
|
||||
tokenize([T1,T2,$:|Rest], Line, Scope, Tokens) when ?container(T1, T2), ?is_space(hd(Rest)) ->
|
||||
tokenize(Rest, Line, Scope, [{ kw_identifier, Line, list_to_atom([T1,T2]) }|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);
|
||||
@@ -291,7 +280,7 @@ 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);
|
||||
?in_match_op(T1, T2); ?two_op(T1, T2); ?stab_op(T1, T2) ->
|
||||
?in_match_op(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
|
||||
@@ -355,6 +344,9 @@ tokenize([T|Rest], Line, Scope, Tokens) when T == $(;
|
||||
Token = { list_to_atom([T]), Line },
|
||||
handle_terminator(Rest, Line, Scope, Token, Tokens);
|
||||
|
||||
tokenize("=>" ++ Rest, Line, Scope, Tokens) ->
|
||||
tokenize(Rest, Line, Scope, add_token_with_nl({ assoc_op, Line, '=>' }, Tokens));
|
||||
|
||||
% ## Two Token Operators
|
||||
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);
|
||||
@@ -374,6 +366,9 @@ tokenize([T1,T2|Rest], Line, Scope, Tokens) when ?or_op(T1, T2) ->
|
||||
tokenize([T1,T2|Rest], Line, Scope, Tokens) when ?in_match_op(T1, T2) ->
|
||||
handle_op(Rest, Line, in_match_op, list_to_atom([T1, T2]), Scope, Tokens);
|
||||
|
||||
tokenize([T1,T2|Rest], Line, Scope, Tokens) when ?type_op(T1, T2) ->
|
||||
handle_op(Rest, Line, type_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);
|
||||
|
||||
@@ -400,7 +395,13 @@ tokenize([T|Rest], Line, Scope, Tokens) when ?match_op(T) ->
|
||||
tokenize([T|Rest], Line, Scope, Tokens) when ?pipe_op(T) ->
|
||||
handle_op(Rest, Line, pipe_op, list_to_atom([T]), Scope, Tokens);
|
||||
|
||||
% Dot
|
||||
% Others
|
||||
|
||||
tokenize([$%|T], Line, Scope, Tokens) ->
|
||||
case strip_space(T, 0) of
|
||||
{ [${|_] = Rest, Counter } -> tokenize(Rest, Line + Counter, Scope, [{ '%{}', Line }|Tokens]);
|
||||
{ Rest, Counter } -> tokenize(Rest, Line + Counter, Scope, [{ '%', Line }|Tokens])
|
||||
end;
|
||||
|
||||
tokenize([$.|T], Line, Scope, Tokens) ->
|
||||
{ Rest, Counter } = strip_space(T, 0),
|
||||
@@ -495,17 +496,9 @@ handle_strings(T, Line, H, Scope, Tokens) ->
|
||||
{ error, Reason } ->
|
||||
interpolation_error(Reason, [H|T], Tokens, " (for string starting at line ~B)", [Line]);
|
||||
{ NewLine, Parts, [$:|Rest] } when ?is_space(hd(Rest)) ->
|
||||
case Parts of
|
||||
[Bin] when is_binary(Bin) ->
|
||||
case unsafe_to_atom(unescape_chars(Bin), Line, Scope) of
|
||||
{ ok, Atom } ->
|
||||
tokenize(Rest, NewLine, Scope, [{ kw_identifier, Line, Atom }|Tokens]);
|
||||
{ error, Reason } ->
|
||||
{ error, Reason, [H|T], Tokens }
|
||||
end;
|
||||
_ ->
|
||||
{ error, { Line, "invalid interpolation in key ", [$"|T] }, [H|T], Tokens }
|
||||
end;
|
||||
Unescaped = unescape_tokens(Parts),
|
||||
ExistingAtomsOnly = Scope#elixir_tokenizer.existing_atoms_only,
|
||||
tokenize(Rest, NewLine, Scope, [{ kw_identifier_string, Line, ExistingAtomsOnly, Unescaped }|Tokens]);
|
||||
{ NewLine, Parts, Rest } ->
|
||||
Token = { string_type(H), Line, unescape_tokens(Parts) },
|
||||
tokenize(Rest, NewLine, Scope, [Token|Tokens])
|
||||
@@ -515,8 +508,8 @@ handle_unary_op([$:|Rest], Line, _Kind, Op, Scope, Tokens) when ?is_space(hd(Res
|
||||
tokenize(Rest, Line, Scope, [{ kw_identifier, Line, Op }|Tokens]);
|
||||
|
||||
handle_unary_op(Rest, Line, Kind, Op, Scope, Tokens) ->
|
||||
case strip_space(Rest, 0) of
|
||||
{ [$/|_], _ } -> tokenize(Rest, Line, Scope, [{ identifier, Line, Op }|Tokens]);
|
||||
case strip_horizontal_space(Rest) of
|
||||
[$/|_] -> tokenize(Rest, Line, Scope, [{ identifier, Line, Op }|Tokens]);
|
||||
_ -> tokenize(Rest, Line, Scope, [{ Kind, Line, Op }|Tokens])
|
||||
end.
|
||||
|
||||
@@ -524,8 +517,8 @@ handle_op([$:|Rest], Line, _Kind, Op, Scope, Tokens) when ?is_space(hd(Rest)) ->
|
||||
tokenize(Rest, Line, Scope, [{ kw_identifier, Line, Op }|Tokens]);
|
||||
|
||||
handle_op(Rest, Line, Kind, Op, Scope, Tokens) ->
|
||||
case strip_space(Rest, 0) of
|
||||
{ [$/|_], _ } -> tokenize(Rest, Line, Scope, [{ identifier, Line, Op }|Tokens]);
|
||||
case strip_horizontal_space(Rest) of
|
||||
[$/|_] -> tokenize(Rest, Line, Scope, [{ identifier, Line, Op }|Tokens]);
|
||||
_ -> tokenize(Rest, Line, Scope, add_token_with_nl({ Kind, Line, Op }, Tokens))
|
||||
end.
|
||||
|
||||
@@ -538,7 +531,7 @@ handle_dot([$.,T1,T2,T3|Rest], Line, Scope, Tokens) when
|
||||
% ## Two Token Operators
|
||||
handle_dot([$.,T1,T2|Rest], Line, Scope, Tokens) when
|
||||
?comp_op2(T1, T2); ?and_op(T1, T2); ?or_op(T1, T2); ?arrow_op(T1, T2);
|
||||
?in_match_op(T1, T2); ?two_op(T1, T2); ?stab_op(T1, T2) ->
|
||||
?in_match_op(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
|
||||
@@ -781,7 +774,7 @@ tokenize_any_identifier(Original, Line, Scope, Tokens) ->
|
||||
tokenize_kw_or_other([$:,H|T], _Kind, Line, Atom, _Tokens) when ?is_space(H) ->
|
||||
{ identifier, [H|T], { kw_identifier, Line, Atom } };
|
||||
|
||||
tokenize_kw_or_other([$:,H|T], _Kind, Line, Atom, Tokens) when ?is_atom_start(H) ->
|
||||
tokenize_kw_or_other([$:,H|T], _Kind, Line, Atom, Tokens) when ?is_atom_start(H); ?is_digit(H) ->
|
||||
Original = atom_to_list(Atom) ++ [$:],
|
||||
Reason = { Line, "keyword argument must be followed by space after: ", Original },
|
||||
{ error, Reason, Original ++ [H|T], Tokens };
|
||||
@@ -887,16 +880,13 @@ terminator('<<') -> '>>'.
|
||||
|
||||
check_keyword(_Line, _Atom, [{ '.', _ }|_]) ->
|
||||
nomatch;
|
||||
|
||||
check_keyword(Line, do, [{ Identifier, Line, Atom }|T]) when Identifier == identifier ->
|
||||
{ ok, [{ do, Line }, { do_identifier, Line, Atom }|T] };
|
||||
|
||||
check_keyword(DoLine, do, [{ Identifier, Line, Atom }|T]) when Identifier == identifier ->
|
||||
{ ok, add_token_with_nl({ do, DoLine }, [{ do_identifier, Line, Atom }|T]) };
|
||||
check_keyword(Line, do, Tokens) ->
|
||||
case do_keyword_valid(Tokens) of
|
||||
true -> { ok, [{ do, Line }|Tokens] };
|
||||
true -> { ok, add_token_with_nl({ do, Line }, Tokens) };
|
||||
false -> { error, "do" }
|
||||
end;
|
||||
|
||||
check_keyword(Line, Atom, Tokens) ->
|
||||
case keyword(Atom) of
|
||||
false -> nomatch;
|
||||
@@ -915,13 +905,11 @@ do_keyword_valid([{ Atom, _ }|_]) ->
|
||||
false -> true;
|
||||
_ -> keyword(Atom) == false
|
||||
end;
|
||||
|
||||
do_keyword_valid(_) ->
|
||||
true.
|
||||
|
||||
% Regular keywords
|
||||
keyword('fn') -> token;
|
||||
keyword('do') -> token;
|
||||
keyword('end') -> token;
|
||||
keyword('true') -> token;
|
||||
keyword('false') -> token;
|
||||
|
||||
@@ -1,18 +1,20 @@
|
||||
%% Translate Elixir quoted expressions to Erlang Abstract Format.
|
||||
%% Expects the tree to be expanded.
|
||||
-module(elixir_translator).
|
||||
-export([translate_many/2, translate/2, translate_arg/3, translate_args/2]).
|
||||
-export([translate/2, translate_arg/3, translate_args/2, translate_block/3]).
|
||||
-import(elixir_scope, [mergev/2, mergec/2, mergef/2]).
|
||||
-import(elixir_errors, [compile_error/3, compile_error/4]).
|
||||
-include("elixir.hrl").
|
||||
|
||||
translate_many(Forms, S) ->
|
||||
lists:mapfoldl(fun translate/2, S, Forms).
|
||||
|
||||
%% =
|
||||
|
||||
translate({ '=', Meta, [Left, Right] }, S) ->
|
||||
{ TRight, SR } = translate(Right, S),
|
||||
Return = case Left of
|
||||
{ '_', _, Atom } when is_atom(Atom) -> false;
|
||||
_ -> true
|
||||
end,
|
||||
|
||||
{ TRight, SR } = translate_block(Right, Return, S),
|
||||
{ TLeft, SL } = elixir_clauses:match(fun translate/2, Left, SR),
|
||||
{ { match, ?line(Meta), TLeft, TRight }, SL };
|
||||
|
||||
@@ -22,14 +24,20 @@ 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_map:translate_map(Meta, Args, S);
|
||||
|
||||
translate({ '%', Meta, [Left, Right] }, S) ->
|
||||
elixir_map:translate_struct(Meta, Left, Right, S);
|
||||
|
||||
translate({ '<<>>', Meta, Args }, S) when is_list(Args) ->
|
||||
elixir_bitstring:translate(Meta, Args, S);
|
||||
|
||||
%% Blocks and scope rewriters
|
||||
%% Blocks
|
||||
|
||||
translate({ '__block__', Meta, Args }, S) when is_list(Args) ->
|
||||
{ TArgs, NS } = translate_many(Args, S),
|
||||
{ { block, ?line(Meta), TArgs }, NS };
|
||||
translate({ '__block__', Meta, Args }, #elixir_scope{return=Return} = S) when is_list(Args) ->
|
||||
{ TArgs, SA } = translate_block(Args, [], Return, S#elixir_scope{return=true}),
|
||||
{ { block, ?line(Meta), TArgs }, SA };
|
||||
|
||||
%% Erlang op
|
||||
|
||||
@@ -64,7 +72,8 @@ translate({ fn, Meta, Clauses }, S) ->
|
||||
|
||||
%% Case
|
||||
|
||||
translate({'case', Meta, [Expr, KV]}, S) when is_list(KV) ->
|
||||
translate({'case', Meta, [Expr, KV]}, #elixir_scope{return=Return} = RS) when is_list(KV) ->
|
||||
S = RS#elixir_scope{noname=true, return=true},
|
||||
Clauses = elixir_clauses:get_pairs(do, KV),
|
||||
{ TExpr, NS } = translate(Expr, S),
|
||||
|
||||
@@ -73,18 +82,18 @@ translate({'case', Meta, [Expr, KV]}, S) when is_list(KV) ->
|
||||
false -> Clauses
|
||||
end,
|
||||
|
||||
{ TClauses, TS } = elixir_clauses:clauses(Meta, RClauses, NS),
|
||||
{ TClauses, TS } = elixir_clauses:clauses(Meta, RClauses, Return, NS),
|
||||
{ { 'case', ?line(Meta), TExpr, TClauses }, TS };
|
||||
|
||||
%% Try
|
||||
|
||||
translate({'try', Meta, [Clauses]}, RS) when is_list(Clauses) ->
|
||||
S = RS#elixir_scope{noname=true},
|
||||
translate({'try', Meta, [Clauses]}, #elixir_scope{return=Return} = RS) when is_list(Clauses) ->
|
||||
S = RS#elixir_scope{noname=true, return=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)),
|
||||
{ TCatch, SC } = elixir_try:clauses(Meta, Catch, Return, mergec(S, SB)),
|
||||
|
||||
case lists:keyfind('after', 1, Clauses) of
|
||||
{ 'after', After } ->
|
||||
@@ -95,23 +104,24 @@ translate({'try', Meta, [Clauses]}, RS) when is_list(Clauses) ->
|
||||
end,
|
||||
|
||||
Else = elixir_clauses:get_pairs(else, Clauses),
|
||||
{ TElse, SE } = elixir_clauses:clauses(Meta, Else, mergec(S, SA)),
|
||||
{ TElse, SE } = elixir_clauses:clauses(Meta, Else, Return, mergec(S, SA)),
|
||||
|
||||
SF = (mergec(S, SE))#elixir_scope{noname=RS#elixir_scope.noname},
|
||||
{ { 'try', ?line(Meta), unblock(TDo), TElse, TCatch, TAfter }, SF };
|
||||
|
||||
%% Receive
|
||||
|
||||
translate({'receive', Meta, [KV] }, S) when is_list(KV) ->
|
||||
translate({'receive', Meta, [KV] }, #elixir_scope{return=Return} = RS) when is_list(KV) ->
|
||||
S = RS#elixir_scope{return=true},
|
||||
Do = elixir_clauses:get_pairs(do, KV, true),
|
||||
|
||||
case lists:keyfind('after', 1, KV) of
|
||||
false ->
|
||||
{ TClauses, SC } = elixir_clauses:clauses(Meta, Do, S),
|
||||
{ TClauses, SC } = elixir_clauses:clauses(Meta, Do, Return, S),
|
||||
{ { 'receive', ?line(Meta), TClauses }, SC };
|
||||
_ ->
|
||||
After = elixir_clauses:get_pairs('after', KV),
|
||||
{ TClauses, SC } = elixir_clauses:clauses(Meta, Do ++ After, S),
|
||||
{ TClauses, SC } = elixir_clauses:clauses(Meta, Do ++ After, Return, S),
|
||||
{ FClauses, TAfter } = elixir_utils:split_last(TClauses),
|
||||
{ _, _, [FExpr], _, FAfter } = TAfter,
|
||||
{ { 'receive', ?line(Meta), FClauses, FExpr, FAfter }, SC }
|
||||
@@ -122,6 +132,9 @@ translate({'receive', Meta, [KV] }, S) when is_list(KV) ->
|
||||
translate({ Kind, Meta, Args }, S) when is_list(Args), (Kind == lc) orelse (Kind == bc) ->
|
||||
translate_comprehension(Meta, Kind, Args, S);
|
||||
|
||||
translate({ for, Meta, Args }, S) when is_list(Args) ->
|
||||
elixir_for:translate(Meta, Args, S);
|
||||
|
||||
%% Super
|
||||
|
||||
translate({ super, Meta, Args }, S) when is_list(Args) ->
|
||||
@@ -144,21 +157,6 @@ translate({ super, Meta, Args }, S) when is_list(Args) ->
|
||||
|
||||
%% Variables
|
||||
|
||||
translate({ '^', Meta, [ { Name, VarMeta, Kind } = Var ] },
|
||||
#elixir_scope{extra=fn_match, extra_guards=Extra} = 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 } } ->
|
||||
elixir_errors:deprecation(Meta, S#elixir_scope.file, "using ^ inside function clause heads is "
|
||||
"deprecated, please use a guard instead"),
|
||||
Line = ?line(Meta),
|
||||
{ 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({ '^', 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
|
||||
@@ -174,6 +172,9 @@ translate({ '_', Meta, Kind }, #elixir_scope{context=match} = S) when is_atom(Ki
|
||||
translate({ '_', Meta, Kind }, S) when is_atom(Kind) ->
|
||||
compile_error(Meta, S#elixir_scope.file, "unbound variable _");
|
||||
|
||||
translate({ Name, Meta, Kind }, #elixir_scope{extra=map_key} = S) when is_atom(Name), is_atom(Kind) ->
|
||||
compile_error(Meta, S#elixir_scope.file, "illegal use of variable ~ts in map key", [Name]);
|
||||
|
||||
translate({ Name, Meta, Kind }, S) when is_atom(Name), is_atom(Kind) ->
|
||||
elixir_scope:translate_var(Meta, Name, var_kind(Meta, Kind), S);
|
||||
|
||||
@@ -208,8 +209,9 @@ translate({ { '.', _, [Left, Right] }, Meta, Args }, S)
|
||||
{ TLeft, SL } = translate(Left, S),
|
||||
{ TArgs, SA } = translate_args(Args, mergec(S, SL)),
|
||||
|
||||
Line = ?line(Meta),
|
||||
Arity = length(Args),
|
||||
Line = ?line(Meta),
|
||||
Arity = length(Args),
|
||||
TRight = { atom, Line, Right },
|
||||
|
||||
%% We need to rewrite erlang function calls as operators
|
||||
%% because erl_eval chokes on them. We can remove this
|
||||
@@ -221,7 +223,35 @@ translate({ { '.', _, [Left, Right] }, Meta, Args }, S)
|
||||
{ list_to_tuple([op, Line, Right] ++ TArgs), mergev(SL, SA) };
|
||||
false ->
|
||||
assert_allowed_in_context(Meta, Left, Right, Arity, S),
|
||||
{ { call, Line, { remote, Line, TLeft, { atom, 0, Right } }, TArgs }, mergev(SL, SA) }
|
||||
SC = mergev(SL, SA),
|
||||
|
||||
case not is_atom(Left) andalso (Arity == 0) of
|
||||
true ->
|
||||
{ Var, _, SV } = elixir_scope:build_var('_', SC),
|
||||
TVar = { var, Line, Var },
|
||||
TMap = { tuple, Line, [
|
||||
{ atom, Line, 'Elixir.KeyError' },
|
||||
{ atom, Line, '__exception__' },
|
||||
TRight,
|
||||
TVar] },
|
||||
|
||||
{ { 'case', -1, TLeft, [
|
||||
{ clause, -1,
|
||||
[{ map, Line, [{ map_field_exact, Line, TRight, TVar }] }],
|
||||
[],
|
||||
[TVar] },
|
||||
{ clause, -1,
|
||||
[{ match, Line, { map, Line, [] }, TVar }],
|
||||
[],
|
||||
[?wrap_call(Line, erlang, error, [TMap])] },
|
||||
{ clause, -1,
|
||||
[TVar],
|
||||
[],
|
||||
[{ call, Line, { remote, Line, TVar, TRight }, [] }] }
|
||||
] }, SV };
|
||||
false ->
|
||||
{ { call, Line, { remote, Line, TLeft, TRight }, TArgs }, SC }
|
||||
end
|
||||
end;
|
||||
|
||||
%% Anonymous function calls
|
||||
@@ -295,6 +325,7 @@ unblock({ 'block', _, Exprs }) -> Exprs;
|
||||
unblock(Expr) -> [Expr].
|
||||
|
||||
%% Translate args
|
||||
|
||||
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 };
|
||||
@@ -303,11 +334,37 @@ translate_arg(Arg, Acc, S) ->
|
||||
{ TArg, mergev(Acc, TAcc) }.
|
||||
|
||||
translate_args(Args, #elixir_scope{context=match} = S) ->
|
||||
translate_many(Args, S);
|
||||
lists:mapfoldl(fun translate/2, S, Args);
|
||||
|
||||
translate_args(Args, S) ->
|
||||
lists:mapfoldl(fun(X, Acc) -> translate_arg(X, Acc, S) end, S, Args).
|
||||
|
||||
%% Translate blocks
|
||||
|
||||
translate_block([], Acc, _Return, S) ->
|
||||
{ lists:reverse(Acc), S };
|
||||
translate_block([H], Acc, Return, S) ->
|
||||
{ TH, TS } = translate_block(H, Return, S),
|
||||
translate_block([], [TH|Acc], Return, TS);
|
||||
translate_block([H|T], Acc, Return, S) ->
|
||||
{ TH, TS } = translate_block(H, false, S),
|
||||
translate_block(T, [TH|Acc], Return, TS).
|
||||
|
||||
translate_block(Expr, Return, S) ->
|
||||
case (Return == false) andalso handles_no_return(Expr) of
|
||||
true -> translate(Expr, S#elixir_scope{return=Return});
|
||||
false -> translate(Expr, S)
|
||||
end.
|
||||
|
||||
%% Expressions that can handle no return may receive
|
||||
%% return=false but must always return return=true.
|
||||
handles_no_return({ 'try', _, [_] }) -> true;
|
||||
handles_no_return({ 'for', _, [_|_] }) -> true;
|
||||
handles_no_return({ 'case', _, [_, _] }) -> true;
|
||||
handles_no_return({ 'receive', _, [_] }) -> true;
|
||||
handles_no_return({ '__block__', _, [_|_] }) -> true;
|
||||
handles_no_return(_) -> false.
|
||||
|
||||
%% Comprehensions
|
||||
|
||||
translate_comprehension(Meta, Kind, Args, S) ->
|
||||
|
||||
@@ -1,58 +1,58 @@
|
||||
-module(elixir_try).
|
||||
-export([clauses/3, format_error/1]).
|
||||
-export([clauses/4, format_error/1]).
|
||||
-include("elixir.hrl").
|
||||
|
||||
clauses(_Meta, Clauses, S) ->
|
||||
clauses(_Meta, Clauses, Return, 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, TS } = each_clause(X, Return, SAcc),
|
||||
{ TX, elixir_scope:mergec(S, TS) }
|
||||
end,
|
||||
lists:mapfoldl(Transformer, S, Rescue ++ Catch).
|
||||
|
||||
each_clause({ 'catch', Meta, Raw, Expr }, S) ->
|
||||
each_clause({ 'catch', Meta, Raw, Expr }, Return, S) ->
|
||||
{ Args, Guards } = elixir_clauses:extract_splat_guards(Raw),
|
||||
|
||||
Final = case Args of
|
||||
[X] -> [throw, X, { '_', Meta, nil }];
|
||||
[X,Y] -> [X, Y, { '_', Meta, nil }];
|
||||
[_,_,_] -> Args;
|
||||
_ ->
|
||||
[X] -> [throw, X, { '_', Meta, nil }];
|
||||
[X,Y] -> [X, Y, { '_', Meta, nil }];
|
||||
_ ->
|
||||
elixir_errors:compile_error(Meta, S#elixir_scope.file, "too many arguments given for catch")
|
||||
end,
|
||||
|
||||
Condition = [{ '{}', Meta, Final }],
|
||||
elixir_clauses:clause(?line(Meta), fun elixir_translator:translate_many/2, Condition, Expr, Guards, S);
|
||||
elixir_clauses:clause(?line(Meta), fun elixir_translator:translate_args/2,
|
||||
Condition, Expr, Guards, Return, S);
|
||||
|
||||
each_clause({ rescue, Meta, [{ in, _, [Left, Right]}], Expr }, S) ->
|
||||
each_clause({ rescue, Meta, [{ in, _, [Left, Right]}], Expr }, Return, 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, Clause, Expr }, Return, CS);
|
||||
_ ->
|
||||
{ Clause, Safe } = rescue_guards(Meta, Left, Right, S),
|
||||
case Safe of
|
||||
true ->
|
||||
each_clause({ 'catch', Meta, Clause, Expr }, S);
|
||||
each_clause({ 'catch', Meta, Clause, Expr }, Return, S);
|
||||
false ->
|
||||
{ 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] }
|
||||
{ { '.', Meta, ['Elixir.Exception', normalize] }, Meta, [error, ClauseVar] }
|
||||
] },
|
||||
FinalExpr = prepend_to_block(Meta, Match, Expr),
|
||||
each_clause({ 'catch', Meta, FinalClause, FinalExpr }, CS)
|
||||
each_clause({ 'catch', Meta, FinalClause, FinalExpr }, Return, CS)
|
||||
end
|
||||
end;
|
||||
|
||||
each_clause({rescue,Meta,_,_}, S) ->
|
||||
each_clause({ rescue, Meta, _, _ }, _Return, S) ->
|
||||
elixir_errors:compile_error(Meta, S#elixir_scope.file, "invalid arguments for rescue in try");
|
||||
|
||||
each_clause({Key,Meta,_,_}, S) ->
|
||||
each_clause({ Key, Meta, _, _ }, _Return, S) ->
|
||||
elixir_errors:compile_error(Meta, S#elixir_scope.file, "invalid key ~ts in try", [Key]).
|
||||
|
||||
%% Helpers
|
||||
@@ -71,8 +71,8 @@ rescue_guards(Meta, Var, Guards, S) ->
|
||||
IsException = { erl(Meta, '=='), Meta, [
|
||||
{ erl(Meta, element), Meta, [2, Var] }, '__exception__'
|
||||
] },
|
||||
OrElse = join(Meta, fun do_or/3, Elixir),
|
||||
[join(Meta, fun do_and/3, [IsTuple, IsException, OrElse])|Erlang]
|
||||
OrElse = join(Meta, fun erl_or/3, Elixir),
|
||||
[join(Meta, fun erl_and/3, [IsTuple, IsException, OrElse])|Erlang]
|
||||
end,
|
||||
{
|
||||
[{ 'when', Meta, [error, Var, join_when(Meta, Final)] }],
|
||||
@@ -87,12 +87,12 @@ rescue_each_var(Meta, ClauseVar, Guards) ->
|
||||
case Vars == [] of
|
||||
true -> { [], [] };
|
||||
false ->
|
||||
Elixir = [exception_compare(Meta, ClauseVar, Var) || Var <- Vars],
|
||||
Elixir = [erl_exception_compare(Meta, ClauseVar, Var) || Var <- Vars],
|
||||
Erlang = lists:map(fun(Rescue) ->
|
||||
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))
|
||||
erl_and(Meta,
|
||||
erl_rescue_guard_for(Meta, ClauseVar, Rescue),
|
||||
join(Meta, fun erl_or/3, Compares))
|
||||
end, erlang_rescues()),
|
||||
{ Elixir, Erlang }
|
||||
end.
|
||||
@@ -111,21 +111,21 @@ rescue_each_ref(Meta, Var, [H|T], Elixir, Erlang, _Safe, S) when
|
||||
H == 'Elixir.BadArityError'; H == 'Elixir.BadFunctionError';
|
||||
H == 'Elixir.MatchError'; H == 'Elixir.CaseClauseError';
|
||||
H == 'Elixir.TryClauseError'; H == 'Elixir.FunctionClauseError';
|
||||
H == 'Elixir.SystemLimitError' ->
|
||||
Expr = do_or(Meta,
|
||||
erlang_rescue_guard_for(Meta, Var, H),
|
||||
exception_compare(Meta, Var, H)),
|
||||
H == 'Elixir.SystemLimitError'; H == 'Elixir.BadStructError' ->
|
||||
Expr = erl_or(Meta,
|
||||
erl_rescue_guard_for(Meta, Var, H),
|
||||
erl_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, T, [erl_exception_compare(Meta, Var, H)|Elixir], Erlang, Safe, S);
|
||||
|
||||
rescue_each_ref(Meta, Var, [H|T], Elixir, Erlang, Safe, S) ->
|
||||
case elixir_translator:translate(H, S) of
|
||||
{ { atom, _, Atom }, _ } ->
|
||||
rescue_each_ref(Meta, Var, [Atom|T], Elixir, Erlang, Safe, S);
|
||||
_ ->
|
||||
rescue_each_ref(Meta, Var, T, [exception_compare(Meta, Var, H)|Elixir], Erlang, Safe, S)
|
||||
rescue_each_ref(Meta, Var, T, [erl_exception_compare(Meta, Var, H)|Elixir], Erlang, Safe, S)
|
||||
end;
|
||||
|
||||
rescue_each_ref(_, _, [], Elixir, Erlang, Safe, _) ->
|
||||
@@ -135,64 +135,70 @@ rescue_each_ref(_, _, [], Elixir, Erlang, Safe, _) ->
|
||||
|
||||
erlang_rescues() ->
|
||||
[
|
||||
'Elixir.UndefinedFunctionError', 'Elixir.ArgumentError', 'Elixir.ArithmeticError', 'Elixir.BadArityError',
|
||||
'Elixir.BadFunctionError', 'Elixir.MatchError', 'Elixir.CaseClauseError', 'Elixir.TryClauseError',
|
||||
'Elixir.FunctionClauseError', 'Elixir.SystemLimitError', 'Elixir.ErlangError'
|
||||
'Elixir.UndefinedFunctionError', 'Elixir.ArgumentError', 'Elixir.ArithmeticError',
|
||||
'Elixir.BadArityError', 'Elixir.BadFunctionError', 'Elixir.MatchError',
|
||||
'Elixir.CaseClauseError', 'Elixir.TryClauseError', 'Elixir.FunctionClauseError',
|
||||
'Elixir.SystemLimitError', 'Elixir.ErlangError', 'Elixir.BadStructError'
|
||||
].
|
||||
|
||||
erlang_rescue_guard_for(Meta, Var, List) when is_list(List) ->
|
||||
join(Meta, fun do_or/3, [erlang_rescue_guard_for(Meta, Var, X) || X <- List]);
|
||||
erl_rescue_guard_for(Meta, Var, List) when is_list(List) ->
|
||||
join(Meta, fun erl_or/3, [erl_rescue_guard_for(Meta, Var, X) || X <- List]);
|
||||
|
||||
erlang_rescue_guard_for(Meta, Var, 'Elixir.UndefinedFunctionError') ->
|
||||
erl_rescue_guard_for(Meta, Var, 'Elixir.UndefinedFunctionError') ->
|
||||
{ erl(Meta, '=='), Meta, [Var, undef] };
|
||||
|
||||
erlang_rescue_guard_for(Meta, Var, 'Elixir.FunctionClauseError') ->
|
||||
erl_rescue_guard_for(Meta, Var, 'Elixir.FunctionClauseError') ->
|
||||
{ erl(Meta, '=='), Meta, [Var, function_clause] };
|
||||
|
||||
erlang_rescue_guard_for(Meta, Var, 'Elixir.SystemLimitError') ->
|
||||
erl_rescue_guard_for(Meta, Var, 'Elixir.SystemLimitError') ->
|
||||
{ erl(Meta, '=='), Meta, [Var, system_limit] };
|
||||
|
||||
erlang_rescue_guard_for(Meta, Var, 'Elixir.ArithmeticError') ->
|
||||
erl_rescue_guard_for(Meta, Var, 'Elixir.ArithmeticError') ->
|
||||
{ erl(Meta, '=='), Meta, [Var, badarith] };
|
||||
|
||||
erlang_rescue_guard_for(Meta, Var, 'Elixir.BadArityError') ->
|
||||
do_and(Meta,
|
||||
{ erl(Meta, is_tuple), Meta, [Var] },
|
||||
exception_compare(Meta, Var, badarity));
|
||||
erl_rescue_guard_for(Meta, Var, 'Elixir.BadArityError') ->
|
||||
erl_and(Meta,
|
||||
erl_tuple_size(Meta, Var, 2),
|
||||
erl_exception_compare(Meta, Var, badarity));
|
||||
|
||||
erlang_rescue_guard_for(Meta, Var, 'Elixir.BadFunctionError') ->
|
||||
do_and(Meta,
|
||||
{ erl(Meta, is_tuple), Meta, [Var] },
|
||||
exception_compare(Meta, Var, badfun));
|
||||
erl_rescue_guard_for(Meta, Var, 'Elixir.BadFunctionError') ->
|
||||
erl_and(Meta,
|
||||
erl_tuple_size(Meta, Var, 2),
|
||||
erl_exception_compare(Meta, Var, badfun));
|
||||
|
||||
erlang_rescue_guard_for(Meta, Var, 'Elixir.MatchError') ->
|
||||
do_and(Meta,
|
||||
{ erl(Meta, is_tuple), Meta, [Var] },
|
||||
exception_compare(Meta, Var, badmatch));
|
||||
erl_rescue_guard_for(Meta, Var, 'Elixir.MatchError') ->
|
||||
erl_and(Meta,
|
||||
erl_tuple_size(Meta, Var, 2),
|
||||
erl_exception_compare(Meta, Var, badmatch));
|
||||
|
||||
erlang_rescue_guard_for(Meta, Var, 'Elixir.CaseClauseError') ->
|
||||
do_and(Meta,
|
||||
{ erl(Meta, is_tuple), Meta, [Var] },
|
||||
exception_compare(Meta, Var, case_clause));
|
||||
erl_rescue_guard_for(Meta, Var, 'Elixir.CaseClauseError') ->
|
||||
erl_and(Meta,
|
||||
erl_tuple_size(Meta, Var, 2),
|
||||
erl_exception_compare(Meta, Var, case_clause));
|
||||
|
||||
erlang_rescue_guard_for(Meta, Var, 'Elixir.TryClauseError') ->
|
||||
do_and(Meta,
|
||||
{ erl(Meta, is_tuple), Meta, [Var] },
|
||||
exception_compare(Meta, Var, try_clause));
|
||||
erl_rescue_guard_for(Meta, Var, 'Elixir.TryClauseError') ->
|
||||
erl_and(Meta,
|
||||
erl_tuple_size(Meta, Var, 2),
|
||||
erl_exception_compare(Meta, Var, try_clause));
|
||||
|
||||
erlang_rescue_guard_for(Meta, Var, 'Elixir.ArgumentError') ->
|
||||
do_or(Meta,
|
||||
{ erl(Meta, '=='), Meta, [Var, badarg] },
|
||||
do_and(Meta,
|
||||
{ erl(Meta, is_tuple), Meta, [Var] },
|
||||
exception_compare(Meta, Var, badarg)));
|
||||
erl_rescue_guard_for(Meta, Var, 'Elixir.BadStructError') ->
|
||||
erl_and(Meta,
|
||||
erl_tuple_size(Meta, Var, 3),
|
||||
erl_exception_compare(Meta, Var, badstruct));
|
||||
|
||||
erlang_rescue_guard_for(Meta, Var, 'Elixir.ErlangError') ->
|
||||
erl_rescue_guard_for(Meta, Var, 'Elixir.ArgumentError') ->
|
||||
erl_or(Meta,
|
||||
{ erl(Meta, '=='), Meta, [Var, badarg] },
|
||||
erl_and(Meta,
|
||||
erl_tuple_size(Meta, Var, 2),
|
||||
erl_exception_compare(Meta, Var, badarg)));
|
||||
|
||||
erl_rescue_guard_for(Meta, Var, 'Elixir.ErlangError') ->
|
||||
IsNotTuple = { erl(Meta, 'not'), Meta, [{ erl(Meta, is_tuple), Meta, [Var] }] },
|
||||
IsException = { erl(Meta, '/='), Meta, [
|
||||
{ erl(Meta, element), Meta, [2, Var] }, '__exception__'
|
||||
] },
|
||||
do_or(Meta, IsNotTuple, IsException).
|
||||
erl_or(Meta, IsNotTuple, IsException).
|
||||
|
||||
%% Helpers
|
||||
|
||||
@@ -205,7 +211,10 @@ format_error({ rescue_no_match, Var, Alias }) ->
|
||||
is_var({ Name, _, Atom }) when is_atom(Name), is_atom(Atom) -> true;
|
||||
is_var(_) -> false.
|
||||
|
||||
exception_compare(Meta, Var, Expr) ->
|
||||
erl_tuple_size(Meta, Var, Size) ->
|
||||
{ erl(Meta, '=='), Meta, [{ erl(Meta, tuple_size), Meta, [Var] }, Size] }.
|
||||
|
||||
erl_exception_compare(Meta, Var, Expr) ->
|
||||
{ erl(Meta, '=='), Meta, [
|
||||
{ erl(Meta, element), Meta, [1, Var] },
|
||||
Expr
|
||||
@@ -224,5 +233,5 @@ 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] }.
|
||||
erl_or(Meta, Left, Right) -> { '__op__', Meta, ['orelse', Left, Right] }.
|
||||
erl_and(Meta, Left, Right) -> { '__op__', Meta, ['andalso', Left, Right] }.
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user