Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
32d4739dad | ||
|
|
650cfc0448 | ||
|
|
d4d824022f | ||
|
|
c6284557e7 | ||
|
|
a8a2f86a23 | ||
|
|
fc219e7dca | ||
|
|
96a103d222 | ||
|
|
f525f5b8cb | ||
|
|
5c30a687fa | ||
|
|
9ffe8b0e97 | ||
|
|
12c8564a26 | ||
|
|
903aa60fec | ||
|
|
969bc968ee | ||
|
|
e941d78daf | ||
|
|
8d3475825b | ||
|
|
68c890e179 | ||
|
|
6f73d7dbb9 | ||
|
|
c2ac967123 | ||
|
|
dd573c9806 | ||
|
|
58ee415e63 | ||
|
|
561397da7a | ||
|
|
3d2438c625 | ||
|
|
a08710bfda | ||
|
|
151695b43c | ||
|
|
2d8259b432 | ||
|
|
ae03e10db5 | ||
|
|
208fbaf5ef | ||
|
|
820ca0a57e | ||
|
|
cf309ede14 | ||
|
|
da0a95695d | ||
|
|
b2541a1b93 | ||
|
|
5df40f7c42 | ||
|
|
5e24dbaddb | ||
|
|
3f8e3d4d38 | ||
|
|
391fee3ea2 | ||
|
|
6cee8572e0 | ||
|
|
f2d9e802cd | ||
|
|
5247961185 | ||
|
|
53a2618cba | ||
|
|
d360addc65 | ||
|
|
08e7da305c | ||
|
|
afab1f8d73 | ||
|
|
749266b649 | ||
|
|
c4dcaf540d | ||
|
|
561d6ea0d1 | ||
|
|
1574f61a8b | ||
|
|
7dc941227f | ||
|
|
ca026039a3 | ||
|
|
4b06a2d316 | ||
|
|
00299fe7cb | ||
|
|
9e6b650efd | ||
|
|
c56d0760f6 | ||
|
|
aedc78536f | ||
|
|
0c17fa0ab2 | ||
|
|
752a09c988 | ||
|
|
d5a8821ec1 | ||
|
|
aa7400aaad | ||
|
|
d849fc5dca | ||
|
|
3d73d1a359 | ||
|
|
18bcef3ef4 | ||
|
|
115fd99bfb | ||
|
|
879499b409 | ||
|
|
e265771c6e | ||
|
|
f9f728cb66 | ||
|
|
e321caee24 | ||
|
|
5dd3a23752 | ||
|
|
c572cb80fc | ||
|
|
b761f1a605 | ||
|
|
c1f907b412 | ||
|
|
c45d0f4f2f | ||
|
|
406f0dcffe | ||
|
|
9dbc216c11 | ||
|
|
22b455fad4 | ||
|
|
9807b506eb | ||
|
|
624423686d | ||
|
|
e5cba5e4b5 | ||
|
|
1f15bf72e7 | ||
|
|
e6bc5a273c | ||
|
|
8fa6c4f96f | ||
|
|
b7e8f97de1 | ||
|
|
5fc68fe71b | ||
|
|
808260cd1b | ||
|
|
d9152c08d8 | ||
|
|
4048e542d9 | ||
|
|
b08273700f | ||
|
|
0d5512931e | ||
|
|
7fc37de058 | ||
|
|
c4dac6fd7a | ||
|
|
d3122ddcc5 | ||
|
|
74cfcda902 | ||
|
|
6fc6dd4352 | ||
|
|
7014986f65 | ||
|
|
031f4e942f | ||
|
|
9904bdce9d | ||
|
|
754418493f | ||
|
|
27000ea1b4 | ||
|
|
620ee4298c | ||
|
|
1d09da7e53 | ||
|
|
5115256853 | ||
|
|
0d715343de | ||
|
|
441547d8e4 | ||
|
|
5e123e6519 | ||
|
|
5b45ee5c8d | ||
|
|
939942bdb3 | ||
|
|
e64be43090 | ||
|
|
da898e1ac9 | ||
|
|
8dba46a0ac | ||
|
|
633eb871ee | ||
|
|
91c0fa71fa | ||
|
|
87da4e91bb | ||
|
|
7768828d9c | ||
|
|
8ece000489 | ||
|
|
1297fa5953 | ||
|
|
c7a2442bc7 | ||
|
|
f88ee1c23a | ||
|
|
64a22fc15d | ||
|
|
07cd772962 | ||
|
|
160a8255ef | ||
|
|
cca430522d | ||
|
|
db417110e7 | ||
|
|
67896b56c5 | ||
|
|
d21d603617 | ||
|
|
fdd108fc43 | ||
|
|
19ff7a03f9 | ||
|
|
7f5744bde7 | ||
|
|
b87797f788 | ||
|
|
48b19ec580 | ||
|
|
f2e200f832 | ||
|
|
a541f53dd1 | ||
|
|
6ce77b1b49 | ||
|
|
5222c7737f | ||
|
|
b0cf65b5cd | ||
|
|
7cdc25538e | ||
|
|
b82cd8cbd0 | ||
|
|
9365556ea4 | ||
|
|
de376663f0 | ||
|
|
f24ec671c2 | ||
|
|
c0f7a268c7 | ||
|
|
7b4e0915b1 | ||
|
|
2ea7de7361 | ||
|
|
82cc435fd7 | ||
|
|
8eab556f55 | ||
|
|
5a37594853 | ||
|
|
82f3fa00d0 | ||
|
|
c97dc3bd15 | ||
|
|
d6e99e2f5a | ||
|
|
46b6f82473 | ||
|
|
c80e43ca42 | ||
|
|
80e41f8b4b | ||
|
|
9e16cf36ed | ||
|
|
1d8e39a2df | ||
|
|
21db352039 | ||
|
|
45f033d15a | ||
|
|
8eba2fcba0 | ||
|
|
e476c06798 | ||
|
|
0b6b7ea0e4 | ||
|
|
e62ef92a4b | ||
|
|
7be271876e | ||
|
|
7c639f9252 | ||
|
|
f03e707e8e | ||
|
|
38e63451d6 | ||
|
|
5e1366c946 | ||
|
|
0470ebf5af | ||
|
|
b71642045d | ||
|
|
582e814422 | ||
|
|
d15ad4da80 | ||
|
|
bc0089ff42 | ||
|
|
bff8a6ae75 | ||
|
|
6d4ad4ade6 | ||
|
|
a613445f78 | ||
|
|
e4ea0a6e36 | ||
|
|
a03639fe1a | ||
|
|
e7b1242e98 | ||
|
|
d3d83b17f4 | ||
|
|
78eb14edae | ||
|
|
a7d931ce21 | ||
|
|
b1cd1ee7e9 | ||
|
|
1513ca7c14 | ||
|
|
016480bdcc | ||
|
|
857af06aff | ||
|
|
961099746e | ||
|
|
8247382967 | ||
|
|
9b0d972571 | ||
|
|
b19c389cee | ||
|
|
2243235e08 | ||
|
|
a72457112f | ||
|
|
3f3e86d7f4 | ||
|
|
15bf0fb16e | ||
|
|
5184edd432 | ||
|
|
fc9506e28e | ||
|
|
5c01be91ea | ||
|
|
fed406a2e6 | ||
|
|
26904197d5 | ||
|
|
f552d7620f | ||
|
|
ce55936b45 | ||
|
|
3167d78711 | ||
|
|
05eb42612b | ||
|
|
6720522707 | ||
|
|
0520b138f8 | ||
|
|
057691722e | ||
|
|
0ddb31992b | ||
|
|
0e2752f7c2 | ||
|
|
76b2255d7b | ||
|
|
93d5128db3 | ||
|
|
6d98020923 | ||
|
|
77110f970b | ||
|
|
cf6ce9a3fd | ||
|
|
be0dea7c0a | ||
|
|
b0b444bbd3 | ||
|
|
3e45188e88 | ||
|
|
51171cb8e1 | ||
|
|
4a1dfccbec | ||
|
|
c62a7e1001 | ||
|
|
2b8854c63e | ||
|
|
b535f0746e | ||
|
|
4b795a042b | ||
|
|
c80e52ee39 | ||
|
|
520415b013 | ||
|
|
27682320d4 | ||
|
|
98ade3b8fb | ||
|
|
263c76f5e5 | ||
|
|
bd984a545d | ||
|
|
115e022025 | ||
|
|
dc5fe48639 | ||
|
|
155b937327 | ||
|
|
6ef10d4b0d | ||
|
|
4f2c2ceb95 | ||
|
|
078522d014 | ||
|
|
c765a95b91 | ||
|
|
8836405bc5 | ||
|
|
dd2e0f6064 | ||
|
|
23982f1744 | ||
|
|
a6f04a2224 | ||
|
|
7291abb26f | ||
|
|
55cb8ab436 | ||
|
|
32cddf2738 | ||
|
|
7893852f2f | ||
|
|
f1b625ccd2 | ||
|
|
caa7d41e96 | ||
|
|
b96a714240 | ||
|
|
d39a52ae79 | ||
|
|
3e66197318 | ||
|
|
065b7f3f8f | ||
|
|
6004a3614f | ||
|
|
22627c44df | ||
|
|
6e959733dd | ||
|
|
7d2d352236 | ||
|
|
9d61e5886c | ||
|
|
37c8867d9f | ||
|
|
da7e830bf7 | ||
|
|
c78d3b3717 | ||
|
|
cafbd2c2d4 | ||
|
|
ae8def380a | ||
|
|
37cb1801b5 | ||
|
|
dd43e799c8 | ||
|
|
e93d4197ef | ||
|
|
d55a1703bd | ||
|
|
87268e9c78 | ||
|
|
f0ecfbfac9 | ||
|
|
854cab51bc | ||
|
|
ffdf9e9a35 | ||
|
|
6d74511e92 | ||
|
|
75a1373bbb | ||
|
|
d26e4b3dbc | ||
|
|
d636dc0ff0 | ||
|
|
ed0b161992 | ||
|
|
5e640336e0 | ||
|
|
99c7d84f9c | ||
|
|
893a52a95a | ||
|
|
a8d2e45d2f | ||
|
|
ecd3c19c64 | ||
|
|
7f9a2ed17b | ||
|
|
7951f73925 | ||
|
|
a79d965248 | ||
|
|
958550c5cd | ||
|
|
997a798994 | ||
|
|
c0547a15ce | ||
|
|
3918b889f1 | ||
|
|
d1fd5a3c2a | ||
|
|
a3aa1e8de5 | ||
|
|
7c379aa50a | ||
|
|
1fa29be448 | ||
|
|
b9a60b2668 | ||
|
|
ab9735533e | ||
|
|
6b2b928867 | ||
|
|
4b1ea18625 | ||
|
|
7cd98e149c | ||
|
|
036d016053 | ||
|
|
be2aedff98 | ||
|
|
e19d666a14 | ||
|
|
9840ac266b | ||
|
|
bcfc927455 | ||
|
|
014a2ec7a8 | ||
|
|
4846d42101 | ||
|
|
636da42f2a | ||
|
|
7c4516f1ec | ||
|
|
65cc78034d | ||
|
|
f97a384b82 | ||
|
|
7e856e1af0 | ||
|
|
893e333204 | ||
|
|
5e94a69693 | ||
|
|
5775d49c08 | ||
|
|
6397cedec6 | ||
|
|
3dd445bcce | ||
|
|
5e803487ef | ||
|
|
caeea8973c | ||
|
|
6656f7c843 | ||
|
|
e7a872c975 | ||
|
|
ea797815a0 | ||
|
|
453d8835bd | ||
|
|
2272dd1c46 | ||
|
|
41b5f4c826 | ||
|
|
ac0ac7f024 | ||
|
|
5ff3b760d9 | ||
|
|
fe97c4886a | ||
|
|
0b45b61de8 | ||
|
|
406edcb07d | ||
|
|
24c6623a2b | ||
|
|
5ffec637b0 | ||
|
|
bd1633c778 | ||
|
|
bcf9d814dd | ||
|
|
69a4138e8c | ||
|
|
0fa06a2183 | ||
|
|
6c813a140d | ||
|
|
dfc40703f0 | ||
|
|
e208237669 | ||
|
|
3ec014ff77 | ||
|
|
c19e3d0446 | ||
|
|
a459e35537 | ||
|
|
bbaf6b4092 | ||
|
|
99cdb412e3 | ||
|
|
4ce444ad76 | ||
|
|
f06f15184c | ||
|
|
da4565ee88 | ||
|
|
636275c064 | ||
|
|
6137d55d10 | ||
|
|
2fbc8682ca | ||
|
|
2075a9b07a | ||
|
|
abb261443b | ||
|
|
41b0ae4b37 | ||
|
|
4d793f6db1 | ||
|
|
b2d0355ef9 | ||
|
|
c82a8ee0fa | ||
|
|
7044f9c9da | ||
|
|
ec8327967a | ||
|
|
5aa5268c5b | ||
|
|
619f345423 | ||
|
|
99913d7018 | ||
|
|
fce97507de | ||
|
|
3f5e6deb07 | ||
|
|
3c9f486567 | ||
|
|
846048855f | ||
|
|
fed8796d18 | ||
|
|
9a6259d333 | ||
|
|
8c8e8b1193 | ||
|
|
dad7f7bfb7 | ||
|
|
3aaaf5e916 | ||
|
|
a6b81bffd1 | ||
|
|
a9d4cdb7d7 | ||
|
|
64c58f2a3f | ||
|
|
9e56691274 | ||
|
|
3f86e6d555 | ||
|
|
339082053e | ||
|
|
0cbf50c53a | ||
|
|
1d87a778ef | ||
|
|
3b5e06763d | ||
|
|
a7eab9b426 | ||
|
|
1211ed7f0f | ||
|
|
2a08277ef1 | ||
|
|
df94927e09 | ||
|
|
3cd6e54fe9 | ||
|
|
336b9cf126 | ||
|
|
d19f6beca0 | ||
|
|
da84a797b0 | ||
|
|
69df6ea9f9 | ||
|
|
b1fa7d1834 | ||
|
|
6a0244bd88 | ||
|
|
f37e5c6246 | ||
|
|
96a431945d | ||
|
|
fefad03595 | ||
|
|
f76aeba141 | ||
|
|
fbb06f2086 | ||
|
|
ffc91e3004 | ||
|
|
32cc99767b | ||
|
|
c3e7543e48 | ||
|
|
c7e86b0554 | ||
|
|
99ce9250b5 | ||
|
|
adf4075f36 | ||
|
|
3c4b6a636e | ||
|
|
239c41bee2 | ||
|
|
478f9bbb52 | ||
|
|
c3dd4006b0 | ||
|
|
e05448afd7 | ||
|
|
55bc80b4e5 | ||
|
|
ce1b12422d | ||
|
|
bbcc640760 | ||
|
|
e08bcbeef6 | ||
|
|
d757f31ccd | ||
|
|
9a0f8107bd | ||
|
|
d4609c6f4e | ||
|
|
f29c6afe3d | ||
|
|
df51fbe74e | ||
|
|
e6ff35c180 | ||
|
|
c20fe6f396 | ||
|
|
86e7069a0a | ||
|
|
334f3ce78a | ||
|
|
0da2b590ba | ||
|
|
1133581d9e | ||
|
|
d9a10847a6 | ||
|
|
d159e4f592 | ||
|
|
8530694c76 | ||
|
|
a916cf47b4 | ||
|
|
3e9b61855c | ||
|
|
9f862be4eb | ||
|
|
d2c5fa3ad4 | ||
|
|
a8d9ad50d9 | ||
|
|
ffb1863d41 | ||
|
|
156ead2c57 | ||
|
|
7ea8faeada | ||
|
|
82655b61c7 | ||
|
|
088753d901 | ||
|
|
7632c0b17e | ||
|
|
7d594a1953 | ||
|
|
042f5e9c14 | ||
|
|
3864b04ac2 | ||
|
|
5fac52bf12 | ||
|
|
63890ef0b7 | ||
|
|
0dcd5501f0 | ||
|
|
08e9faabe8 | ||
|
|
e5f9dbde1a | ||
|
|
f5b0b7160c | ||
|
|
d0ca92ad4e | ||
|
|
ca25049feb | ||
|
|
76a0a64725 | ||
|
|
c6e64aba18 | ||
|
|
231d65e879 | ||
|
|
91e8e21242 | ||
|
|
d3013aa1d1 | ||
|
|
2c98c0999d | ||
|
|
94f9d4e3f6 | ||
|
|
66476b31d1 | ||
|
|
7f84211bb3 | ||
|
|
474250e6f4 | ||
|
|
65e67cdc27 | ||
|
|
6b91e46702 | ||
|
|
c83f136ad4 | ||
|
|
59a9ddb7d5 | ||
|
|
1ef67c570e | ||
|
|
2403d0c4b9 | ||
|
|
a1ec5f1552 | ||
|
|
45485fba59 | ||
|
|
97ea102a79 | ||
|
|
6e8fb6c630 | ||
|
|
854942d3ee | ||
|
|
d99ec5addf | ||
|
|
3f946d4af0 | ||
|
|
9318f747f4 | ||
|
|
e216f18010 | ||
|
|
ed14ce4bc8 | ||
|
|
0cb936a600 | ||
|
|
328541be87 | ||
|
|
85b76843b0 | ||
|
|
f688899b95 | ||
|
|
e3fb2a5acf | ||
|
|
5a99fbfdcd | ||
|
|
c634a5d232 | ||
|
|
9a6ac4f76c | ||
|
|
6af7e0cc6b | ||
|
|
7ff3e1f833 | ||
|
|
d6d630316e | ||
|
|
4714b54222 | ||
|
|
4d51bd64f0 | ||
|
|
fc1945acff | ||
|
|
5c0ddbb063 | ||
|
|
880ae1116d | ||
|
|
68923112cc | ||
|
|
865608b5de | ||
|
|
26640e3940 |
@@ -0,0 +1 @@
|
||||
lib/elixir/test/elixir/fixtures/*.txt text eol=lf
|
||||
@@ -14,3 +14,4 @@ erl_crash.dump
|
||||
.dialyzer_plt
|
||||
.dialyzer.base_plt
|
||||
.*.swp
|
||||
docs
|
||||
|
||||
@@ -7,7 +7,3 @@ notifications:
|
||||
- yrashk@gmail.com
|
||||
otp_release:
|
||||
- R16B
|
||||
- R15B03
|
||||
- R15B02
|
||||
- R15B01
|
||||
- R15B
|
||||
|
||||
@@ -1,3 +1,95 @@
|
||||
# v0.9.3 (2013-06-23)
|
||||
|
||||
* enhancements
|
||||
* [File] Add `File.chgrp`, `File.chmod` and `File.chown`
|
||||
* [Kernel] Add --warnings-as-errors to Elixir's compiler options
|
||||
* [Kernel] Print warnings to stderr
|
||||
* [Kernel] Warn on undefined module attributes
|
||||
* [Kernel] Emit warning for `x in []` in guards
|
||||
* [Kernel] Add `binding/0` and `binding/1` for retrieving bindings
|
||||
* [Kernel] `quote` now allows a binding as an option
|
||||
* [Macro] Add `Macro.expand_once/2` and `Macro.expand_all/2`
|
||||
* [Mix] Implement `Mix.Version` for basic versioniong semantics
|
||||
* [Mix] Support creation and installation of archives (.ez files)
|
||||
* [Mix] `github: ...` shortcut now uses the faster `git` schema instead of `https`
|
||||
* [Record] Allow types to be given to `defrecord` and `defrecordp`
|
||||
|
||||
* bug fix
|
||||
* [Kernel] The elixir executable on Windows now supports the same options as the UNIX one
|
||||
* [Kernel] Improve error messages on default clauses clash
|
||||
* [Kernel] `__MODULE__.Foo` now returns `Foo` when outside of a Module
|
||||
* [Kernel] Improve error messages when default clauses from different definitions collide
|
||||
* [Kernel] `^x` variables should always refer to the value before the expression
|
||||
* [Kernel] Allow `(x, y) when z` in function clauses and try expressions
|
||||
* [Mix] Mix now properly evaluates rebar scripts
|
||||
|
||||
* deprecations
|
||||
* [Code] `Code.string_to_ast/1` has been deprecated in favor of `Code.string_to_quoted/1`
|
||||
* [Macro] `Macro.to_binary/1` has been deprecated in favor of `Macro.to_string/1`
|
||||
* [Typespec] Deprecate `(fun(...) -> ...)` in favor of `(... -> ...)`
|
||||
|
||||
* backwards incompatible changes
|
||||
* [Bitwise] Precedence of operators used by the Bitwise module were changed. Check `elixir_parser.yrl` for more information.
|
||||
* [File] `rm_rf` and `cp_r` now returns a tuple with three elements on failures
|
||||
* [Kernel] The quoted representation for `->` clauses changed from a tuple with two elements to a tuple with three elements to support metadata
|
||||
* [Kernel] Sigils now dispatch to `sigil_$` instead of `__$__` where `$` is the sigil caracter
|
||||
* [Macro] `Macro.expand/2` now expands until final form. Although this is backwards incompatible, it is very likely you do not need to change your code, since expansion until its final form is recommended, particularly if you are expecting an atom out of it
|
||||
* [Mix] No longer support beam files on `mix local`
|
||||
|
||||
# v0.9.2 (2013-06-13)
|
||||
|
||||
* enhancements
|
||||
* [ExUnit] `capture_io` now captures prompt by default
|
||||
* [Mix] Automatically import git dependencies from Rebar
|
||||
* [Mix] Support for dependencies directly from the umbrella application
|
||||
* [Regex] Add `Regex.escape`
|
||||
* [String] Add `String.contains?`
|
||||
* [URI] Implement `Binary.Chars` (aka `to_binary`) for `URI.Info`
|
||||
|
||||
* bug fix
|
||||
* [HashDict] Ensure HashDict uses exact match throughout its implementation
|
||||
* [IEx] Do not interpret ANSI codes in IEx results
|
||||
* [IEx] Ensure --cookie is set before accessing remote shell
|
||||
* [Kernel] Do not ignore nil when dispatching protocols to avoid infinite loops
|
||||
* [Mix] Fix usage of shell expressions in `Mix.Shell.cmd`
|
||||
* [Mix] Start the application by default on escripts
|
||||
|
||||
* deprecations
|
||||
* [Regex] `Regex.index/2` is deprecated in favor `Regex.run/3`
|
||||
* [Kernel] `super` no longer supports implicit arguments
|
||||
|
||||
* backwards incompatible changes
|
||||
* [Kernel] The `=~` operator now returns true or false instead of an index
|
||||
|
||||
# v0.9.1 (2013-05-30)
|
||||
|
||||
* enhancements
|
||||
* [IEx] Limit the number of entries kept in history and allow it to be configured
|
||||
* [Kernel] Add `String.start_with?` and `String.end_with?`
|
||||
* [Typespec] Allow keywords, e.g. `[foo: integer, bar: boolean | module]`, in typespecs
|
||||
|
||||
* bug fix
|
||||
* [Dict] `Enum.to_list` and `Dict.to_list` now return the same results for dicts
|
||||
* [IEx] Enable shell customization via the `IEx.Options` module
|
||||
* [Kernel] Fix a bug where `unquote_splicing` did not work on the left side of a stab op
|
||||
* [Kernel] Unused functions with cyclic dependencies are now also warned as unused
|
||||
* [Mix] Fix a bug where `mix deps.get` was not retrieving nested dependencies
|
||||
* [Record] Fix a bug where nested records cannot be defined
|
||||
* [Record] Fix a bug where a record named Record cannot be defined
|
||||
|
||||
# v0.9.0 (2013-05-23)
|
||||
|
||||
* enhancements
|
||||
* [ExUnit] `ExUnit.CaptureIO` now accepts an input to be used during capture
|
||||
* [IEx] Add support for .iex files that are loaded during shell's boot process
|
||||
* [IEx] Add `import_file/1` helper
|
||||
|
||||
* backwards incompatible changes
|
||||
* [Enum] `Enum.Iterator` was replaced by the more composable and functional `Enumerable` protocol which supports reductions
|
||||
* [File] `File.iterator/1` and `File.biniterator/1` have been removed in favor of the safe `File.iterator!/1` and `File.biniterator!/1` ones
|
||||
* [Kernel] Erlang R15 is no longer supported
|
||||
* [Kernel] Elixir modules are now represented as `Elixir.ModuleName` (using `.` instead of `-` as separator)
|
||||
|
||||
# v0.8.3 (2013-05-22)
|
||||
|
||||
* enhancements
|
||||
|
||||
+29
-14
@@ -9,7 +9,7 @@ The issue tracker is the preferred channel for [bug reports](#bugs-reports),
|
||||
[features requests](#feature-requests) and [submitting pull
|
||||
requests](#pull-requests), but please **do not** use the issue tracker for
|
||||
personal support requests. Instead, use
|
||||
[the mailing list](http://groups.google.com/group/elixir-lang-core),
|
||||
[the mailing list](http://groups.google.com/group/elixir-lang-talk),
|
||||
[Stack Overflow](http://stackoverflow.com/questions/ask?tags=elixir), or
|
||||
[#elixir-lang](irc://chat.freenode.net/elixir-lang) on Freenode).
|
||||
|
||||
@@ -95,8 +95,8 @@ Good pull requests - patches, improvements, new features - are a fantastic
|
||||
help. They should remain focused in scope and avoid containing unrelated
|
||||
commits.
|
||||
|
||||
**IMPORTANT**: By submitting a patch, you agree to allow the project owner to
|
||||
license your work under the same license as that used by the project.
|
||||
**IMPORTANT**: By submitting a patch, you agree that your work will be
|
||||
licensed under the license used by the project.
|
||||
|
||||
If you have any significant pull request in mind (e.g. implementing features,
|
||||
refactoring code, porting to a different language), **please ask first**
|
||||
@@ -123,11 +123,16 @@ in order to craft an excellent pull request:
|
||||
2. If you cloned a while ago, get the latest changes from upstream:
|
||||
|
||||
```bash
|
||||
git checkout <dev-branch>
|
||||
git pull upstream <dev-branch>
|
||||
git checkout master
|
||||
git pull upstream master
|
||||
```
|
||||
|
||||
3. Create a new topic branch (off master) to contain your feature, change, or fix:
|
||||
3. Create a new topic branch (off of `master`) to contain your feature, change,
|
||||
or fix.
|
||||
|
||||
**IMPORTANT**: Making changes in `master` is discouraged. You should always
|
||||
keep your local `master` in sync with upstream `master` and make your
|
||||
changes in topic branches.
|
||||
|
||||
```bash
|
||||
git checkout -b <topic-branch-name>
|
||||
@@ -139,21 +144,29 @@ in order to craft an excellent pull request:
|
||||
[interactive rebase](https://help.github.com/articles/interactive-rebase)
|
||||
feature to tidy up your commits before making them public.
|
||||
|
||||
5. Locally merge (or rebase) the upstream development branch into your topic branch:
|
||||
|
||||
```bash
|
||||
git pull [--rebase] upstream <dev-branch>
|
||||
```
|
||||
|
||||
6. Push your topic branch up to your fork:
|
||||
5. Push your topic branch up to your fork:
|
||||
|
||||
```bash
|
||||
git push origin <topic-branch-name>
|
||||
```
|
||||
|
||||
7. [Open a Pull Request](https://help.github.com/articles/using-pull-requests/)
|
||||
6. [Open a Pull Request](https://help.github.com/articles/using-pull-requests/)
|
||||
with a clear title and description.
|
||||
|
||||
7. If you haven't updated your pull request for a while, you should consider
|
||||
rebasing on master and resolving any conflicts.
|
||||
|
||||
**IMPORTANT**: _Never ever_ merge upstream `master` into your branches. You
|
||||
should always `git rebase` on `master` to bring your changes up to date when
|
||||
necessary.
|
||||
|
||||
```bash
|
||||
git checkout master
|
||||
git pull upstream master
|
||||
git checkout <your-topic-branch>
|
||||
git rebase master
|
||||
```
|
||||
|
||||
We have saved some excellent pull requests we have received in the past in case
|
||||
you are looking for some examples:
|
||||
|
||||
@@ -161,3 +174,5 @@ you are looking for some examples:
|
||||
* https://github.com/elixir-lang/elixir/pull/1041
|
||||
* https://github.com/elixir-lang/elixir/pull/1058
|
||||
* https://github.com/elixir-lang/elixir/pull/1059
|
||||
|
||||
Thank you for your contributions!
|
||||
|
||||
@@ -11,12 +11,12 @@ INSTALL_PATH := /usr/local
|
||||
#==> Templates
|
||||
|
||||
define APP_TEMPLATE
|
||||
$(1): lib/$(1)/ebin/Elixir-$(2).beam lib/$(1)/ebin/$(1).app
|
||||
$(1): lib/$(1)/ebin/Elixir.$(2).beam lib/$(1)/ebin/$(1).app
|
||||
|
||||
lib/$(1)/ebin/$(1).app:
|
||||
@ cd lib/$(1) && ../../bin/elixir -e "Mix.Server.start_link(:dev)" -r mix.exs -e "Mix.Task.run('compile.app')"
|
||||
|
||||
lib/$(1)/ebin/Elixir-$(2).beam: $(wildcard lib/$(1)/lib/*.ex) $(wildcard lib/$(1)/lib/*/*.ex) $(wildcard lib/$(1)/lib/*/*/*.ex)
|
||||
lib/$(1)/ebin/Elixir.$(2).beam: $(wildcard lib/$(1)/lib/*.ex) $(wildcard lib/$(1)/lib/*/*.ex) $(wildcard lib/$(1)/lib/*/*/*.ex)
|
||||
@ echo "==> $(1) (compile)"
|
||||
@ $$(ELIXIRC) "lib/$(1)/lib/**/*.ex" -o lib/$(1)/ebin
|
||||
|
||||
@@ -27,8 +27,8 @@ endef
|
||||
|
||||
#==> Compilation tasks
|
||||
|
||||
KERNEL:=lib/elixir/ebin/Elixir-Kernel.beam
|
||||
UNICODE:=lib/elixir/ebin/Elixir-String-Unicode.beam
|
||||
KERNEL:=lib/elixir/ebin/Elixir.Kernel.beam
|
||||
UNICODE:=lib/elixir/ebin/Elixir.String.Unicode.beam
|
||||
|
||||
default: compile
|
||||
|
||||
@@ -44,7 +44,7 @@ erlang:
|
||||
# Since Mix depends on EEx and EEx depends on
|
||||
# Mix, we first compile EEx without the .app
|
||||
# file, then mix and then compile eex fully
|
||||
elixir: kernel lib/eex/ebin/Elixir-EEx.beam mix ex_unit eex iex
|
||||
elixir: kernel lib/eex/ebin/Elixir.EEx.beam mix ex_unit eex iex
|
||||
|
||||
kernel: $(KERNEL) VERSION
|
||||
$(KERNEL): lib/elixir/lib/*.ex lib/elixir/lib/*/*.ex
|
||||
@@ -92,17 +92,24 @@ clean:
|
||||
rm -rf lib/elixir/src/*_parser.erl
|
||||
rm -rf lib/elixir/test/ebin
|
||||
|
||||
clean_exbeam:
|
||||
@ rm -f lib/*/ebin/Elixir.*.beam
|
||||
|
||||
#==> Release tasks
|
||||
|
||||
SOURCE_REF = $(shell head="$$(git rev-parse HEAD)" tag="$$(git tag --points-at $$head | tail -1)" ; echo "$${tag:-$$head}\c")
|
||||
|
||||
docs: compile
|
||||
docs: compile ../ex_doc/bin/ex_doc
|
||||
mkdir -p ebin
|
||||
rm -rf docs
|
||||
cp -R -f lib/*/ebin/*.beam ./ebin
|
||||
bin/elixir ../ex_doc/bin/ex_doc "Elixir" "$(VERSION)" -m Kernel -u "https://github.com/elixir-lang/elixir" --source-ref "$(call SOURCE_REF)"
|
||||
rm -rf ebin
|
||||
|
||||
../ex_doc/bin/ex_doc:
|
||||
@ echo "ex_doc is not found in ../ex_doc as expected. See README for more information."
|
||||
@ false
|
||||
|
||||
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
|
||||
@@ -127,11 +134,11 @@ test_erlang: compile
|
||||
@ $(ERL) -pa lib/elixir/test/ebin -s test_helper test -s erlang halt;
|
||||
@ echo
|
||||
|
||||
test_elixir: test_kernel test_doctest test_mix test_ex_unit test_eex test_iex
|
||||
test_elixir: test_kernel test_ex_unit test_doc_test test_mix test_eex test_iex
|
||||
|
||||
test_doctest: compile
|
||||
test_doc_test: compile
|
||||
@ echo "==> doctest (exunit)"
|
||||
@ cd lib/elixir && ../../bin/elixir -r "test/doctest.exs";
|
||||
@ cd lib/elixir && ../../bin/elixir -r "test/doc_test.exs";
|
||||
|
||||
test_kernel: compile
|
||||
@ echo "==> kernel (exunit)"
|
||||
|
||||
@@ -14,12 +14,21 @@ If you want to contribute to Elixir or run it from source, clone this repository
|
||||
|
||||
If tests pass, you are ready to move on to the [Getting Started guide][1] or to try Interactive Elixir by running: `bin/iex` in your terminal.
|
||||
|
||||
However, if tests fail, it is likely you have an outdated Erlang version (Elixir requires Erlang R15B or later). You can check your Erlang version by calling `erl` in the command line. You will see some information as follow:
|
||||
However, if tests fail, it is likely you have an outdated Erlang version (Elixir requires Erlang R16B or later). You can check your Erlang version by calling `erl` in the command line. You will see some information as follow:
|
||||
|
||||
Erlang R15B (erts-5.8.4) [source] [64-bit] [smp:2:2] [rq:2] [async-threads:0] [hipe] [kernel-poll:false]
|
||||
Erlang R16B (erts-5.10.1) [source] [64-bit] [smp:2:2] [rq:2] [async-threads:0] [hipe] [kernel-poll:false]
|
||||
|
||||
If you have the correct version and tests still fail, feel free to [open an issue][2].
|
||||
|
||||
## Building documentation
|
||||
|
||||
Building the documentation requires [ex_doc](https://github.com/elixir-lang/ex_doc) to be installed and built in the same containing folder as elixir.
|
||||
|
||||
# After cloning and compiling Elixir
|
||||
$ git clone git://github.com/elixir-lang/ex_doc.git
|
||||
$ cd ex_doc && ../elixir/bin/mix compile
|
||||
$ cd ../elixir && make docs
|
||||
|
||||
## Contributing
|
||||
|
||||
We appreciate any contribution to Elixir, so check out our [CONTRIBUTING.md](CONTRIBUTING.md) guide for more information. We usually keep a list of features and bugs [in the issue tracker][2].
|
||||
@@ -29,11 +38,13 @@ We appreciate any contribution to Elixir, so check out our [CONTRIBUTING.md](CON
|
||||
* #elixir-lang on freenode IRC
|
||||
* [Website][1]
|
||||
* [Issue tracker][2]
|
||||
* [Mailing list][3]
|
||||
* [elixir-talk Mailing list (questions)][3]
|
||||
* [elixir-core Mailing list (development)][4]
|
||||
|
||||
[1]: http://elixir-lang.org
|
||||
[2]: https://github.com/elixir-lang/elixir/issues
|
||||
[3]: http://groups.google.com/group/elixir-lang-core
|
||||
[3]: http://groups.google.com/group/elixir-lang-talk
|
||||
[4]: http://groups.google.com/group/elixir-lang-core
|
||||
|
||||
## License
|
||||
|
||||
@@ -41,4 +52,4 @@ We appreciate any contribution to Elixir, so check out our [CONTRIBUTING.md](CON
|
||||
|
||||
Elixir source code is released under Apache 2 License with some parts under Erlang's license (EPL).
|
||||
|
||||
Check LEGAL and LICENSE files for more information.
|
||||
Check LEGAL and LICENSE files for more information.
|
||||
|
||||
+2
-2
@@ -2,7 +2,7 @@
|
||||
|
||||
This document simply outlines the release process:
|
||||
|
||||
1) Remove `.dev` extension from VERSION
|
||||
1) Remove `+dev` extension from VERSION
|
||||
|
||||
2) Run `make clean test` to ensure all tests pass from scratch and the CI is green
|
||||
|
||||
@@ -18,7 +18,7 @@ This document simply outlines the release process:
|
||||
|
||||
8) Push package to expm with `expm publish package.exs`
|
||||
|
||||
9) After release, bump versions and add `.dev` back
|
||||
9) After release, bump versions and add `+dev` back
|
||||
|
||||
## Places where version is mentioned
|
||||
|
||||
|
||||
+20
-6
@@ -2,7 +2,7 @@
|
||||
if [ $# -eq 0 ] || [ $1 = "--help" ] || [ $1 = "-h" ]; then
|
||||
echo "Usage: `basename $0` [options] [.exs file] [data]
|
||||
|
||||
-v Prints version
|
||||
-v Prints version and exit
|
||||
-e \"command\" Evaluates the given command (*)
|
||||
-r \"file\" Requires the given files/patterns (*)
|
||||
-S \"script\" Finds and executes the given script (*)
|
||||
@@ -26,7 +26,7 @@ fi
|
||||
|
||||
readlink_f () {
|
||||
cd "$(dirname "$1")" > /dev/null
|
||||
local filename="$(basename "$1")"
|
||||
filename="$(basename "$1")"
|
||||
if [ -h "$filename" ]; then
|
||||
readlink_f "$(readlink "$filename")"
|
||||
else
|
||||
@@ -41,14 +41,19 @@ while [ $I -le $# ]; do
|
||||
S=1
|
||||
eval "PEEK=\${$I}"
|
||||
case "$PEEK" in
|
||||
-v|--compile|--no-halt)
|
||||
-v|--compile|--no-halt|+iex|+compile)
|
||||
;;
|
||||
-e|-r|-pr|-pa|-pz|--remsh|--cookie|-S)
|
||||
-e|-r|-pr|-pa|-pz|--remsh)
|
||||
S=2
|
||||
;;
|
||||
--detached|--hidden)
|
||||
ERL="$ERL `echo $PEEK | cut -c 2-`"
|
||||
;;
|
||||
--cookie)
|
||||
I=$(expr $I + 1)
|
||||
eval "VAL=\${$I}"
|
||||
ERL="$ERL -setcookie "$VAL""
|
||||
;;
|
||||
--sname|--name)
|
||||
I=$(expr $I + 1)
|
||||
eval "VAL=\${$I}"
|
||||
@@ -69,12 +74,21 @@ done
|
||||
SELF=$(readlink_f "$0")
|
||||
SCRIPT_PATH=$(dirname "$SELF")
|
||||
|
||||
if [ "$ERL_LIBS" = "" ]
|
||||
then
|
||||
ERL_LIBS="$SCRIPT_PATH/../lib"
|
||||
else
|
||||
PATHSEP=":"
|
||||
if [ "$OSTYPE" = "msys" ]; then PATHSEP=";" ; fi
|
||||
ERL_LIBS="$ERL_LIBS$PATHSEP$SCRIPT_PATH/../lib"
|
||||
fi
|
||||
|
||||
if [ -f "$HOME/.elixirrc" ]; then . "$HOME/.elixirrc"; fi
|
||||
if [ "$ELIXIR_NO_CLI" != "1" ]; then ERL="$ERL -s elixir start_cli"; fi
|
||||
|
||||
if [ -f "$SCRIPT_PATH/../releases/RELEASES" ] && [ -f "$SCRIPT_PATH/erl" ]
|
||||
then
|
||||
exec "$SCRIPT_PATH"/erl -env ERL_LIBS $ERL_LIBS:"$SCRIPT_PATH/../lib" -boot elixir -noshell $ELIXIR_ERL_OPTS $ERL -extra "$@"
|
||||
exec "$SCRIPT_PATH"/erl -env ERL_LIBS "$ERL_LIBS" -boot elixir -noshell $ELIXIR_ERL_OPTS $ERL -extra "$@"
|
||||
else
|
||||
exec erl -env ERL_LIBS $ERL_LIBS:"$SCRIPT_PATH/../lib" -noshell $ELIXIR_ERL_OPTS $ERL -extra "$@"
|
||||
exec erl -env ERL_LIBS "$ERL_LIBS" -noshell $ELIXIR_ERL_OPTS $ERL -extra "$@"
|
||||
fi
|
||||
|
||||
+102
-12
@@ -1,25 +1,115 @@
|
||||
@echo off
|
||||
SETLOCAL enabledelayedexpansion
|
||||
set argc=0
|
||||
for %%x in (%*) do set /A argc+=1
|
||||
if %argc% == 0 (
|
||||
if %argc%== 0 (
|
||||
goto documentation
|
||||
) else (
|
||||
goto run
|
||||
goto parseopts
|
||||
)
|
||||
:documentation
|
||||
echo Usage: %~nx0 [options] [.exs file] [data]
|
||||
echo.
|
||||
echo -v Prints version and exit
|
||||
echo -e command Evaluates the given command (*)
|
||||
echo -r command Requires the given file/pattern (*)
|
||||
echo -pr command Requires the given file/pattern in parallel (*)
|
||||
echo -pa path Prepend the given path to Erlang code path (*)
|
||||
echo -pz path Append the given path to Erlang code path (*)
|
||||
echo --app app Start the given app and its dependencies (*)
|
||||
echo --no-halt Do not halt the Erlang VM after execution
|
||||
echo -v Prints version and exit
|
||||
echo -e command Evaluates the given command (*)
|
||||
echo -r file Requires the given files/patterns (*)
|
||||
echo -S script Finds and executes the given script (*)
|
||||
echo -pr file Requires the given files/patterns in parallel (*)
|
||||
echo -pa path Prepends the given path to Erlang code path (*)
|
||||
echo -pz path Appends the given path to Erlang code path (*)
|
||||
echo --app app Start the given app and its dependencies (*)
|
||||
echo --erl switches Switches to be passed down to erlang (*)
|
||||
echo --name name Makes and assigns a name to the distributed node
|
||||
echo --sname name Makes and assigns a short name to the distributed node
|
||||
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.
|
||||
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
|
||||
echo ** Options can be passed to the erlang runtime using ELIXIR_ERL_OPTS
|
||||
echo ** Options can be passed to the erlang runtime using ELIXIR_ERL_OPTS or --erl
|
||||
goto :EOF
|
||||
|
||||
:parseopts
|
||||
|
||||
rem Parameters for Erlang
|
||||
set parsErlang=
|
||||
|
||||
rem Make sure we keep a copy of all parameters
|
||||
set allPars=%*
|
||||
|
||||
rem Get the original path name from the batch file
|
||||
set originPath=%~dp0
|
||||
|
||||
rem Optional parameters before the "-extra" parameter
|
||||
set beforeExtra=
|
||||
|
||||
rem Recursive loop called for each parameter that parses the cmd line parameters
|
||||
:startloop
|
||||
set par=%1
|
||||
shift
|
||||
if "%par%"=="" (
|
||||
rem if no parameters defined
|
||||
goto :run
|
||||
)
|
||||
rem ******* ERLANG PARAMETERS **********************
|
||||
for /f "usebackq" %%m in (`echo %par%^|findstr \--detached`) do (
|
||||
set parsErlang=%parsErlang% -detached
|
||||
goto:startloop
|
||||
)
|
||||
for /f "usebackq" %%m in (`echo %par%^|findstr \--hidden`) do (
|
||||
set parsErlang=%parsErlang% -hidden
|
||||
goto:startloop
|
||||
)
|
||||
for /f "usebackq" %%m in (`echo %par%^|findstr \--cookie`) do (
|
||||
set parsErlang=%parsErlang% -setcookie %1
|
||||
shift
|
||||
goto:startloop
|
||||
)
|
||||
for /f "usebackq" %%m in (`echo %par%^|findstr \--sname`) do (
|
||||
set parsErlang=%parsErlang% -sname %1
|
||||
shift
|
||||
goto:startloop
|
||||
)
|
||||
for /f "usebackq" %%m in (`echo %par%^|findstr \--name`) do (
|
||||
set parsErlang=%parsErlang% -name %1
|
||||
shift
|
||||
goto:startloop
|
||||
)
|
||||
for /f "usebackq" %%m in (`echo %par%^|findstr \--erl`) do (
|
||||
set beforeExtra=%beforeExtra% %~1
|
||||
shift
|
||||
goto:startloop
|
||||
)
|
||||
rem ******* elixir parameters **********************
|
||||
for /f "usebackq" %%m in (`echo %par%^|findstr \--v`) do (
|
||||
goto:startloop
|
||||
)
|
||||
for /f "usebackq" %%m in (`echo %par%^|findstr \--compile`) do (
|
||||
goto:startloop
|
||||
)
|
||||
for /f "usebackq" %%m in (`echo %par%^|findstr \--no-halt`) do (
|
||||
goto:startloop
|
||||
)
|
||||
for /f "usebackq" %%m in (`echo %par%^|findstr \+iex`) do (
|
||||
goto:startloop
|
||||
)
|
||||
for /f "usebackq" %%m in (`echo %par%^|findstr \+compile`) do (
|
||||
goto:startloop
|
||||
)
|
||||
for /f "usebackq" %%m in (`echo %par%^|findstr \-[er]`) do (
|
||||
shift
|
||||
goto:startloop
|
||||
)
|
||||
for /f "usebackq" %%m in (`echo %par%^|findstr \-p[raz]`) do (
|
||||
shift
|
||||
goto:startloop
|
||||
)
|
||||
for /f "usebackq" %%m in (`echo %par%^|findstr \--remsh`) do (
|
||||
shift
|
||||
goto:startloop
|
||||
)
|
||||
rem ******* assume all pre-params are parsed ********************
|
||||
:run
|
||||
erl -env ERL_LIBS %ERL_LIBS%;"%~dp0\..\lib" -noshell %ELIXIR_ERL_OPTS% -s elixir start_cli -extra %*
|
||||
erl -env ERL_LIBS %ERL_LIBS%;"%originPath%\..\lib" -noshell %ELIXIR_ERL_OPTS% %parsErlang% -s elixir start_cli %beforeExtra% -extra %*
|
||||
|
||||
+2
-1
@@ -6,6 +6,7 @@ if [ $# -eq 0 ]; then
|
||||
--no-docs Do not attach documentation to compiled modules
|
||||
--no-debug-info Do not attach debug info to compiled modules
|
||||
--ignore-module-conflict
|
||||
--warnings-as-errors Treat warnings as errors and return non-zero exit code
|
||||
|
||||
** Options given after -- are passed down to the executed code
|
||||
** Options can be passed to the erlang runtime using ELIXIR_ERL_OPTS" >&2
|
||||
@@ -14,7 +15,7 @@ fi
|
||||
|
||||
readlink_f () {
|
||||
cd "$(dirname "$1")" > /dev/null
|
||||
local filename="$(basename "$1")"
|
||||
filename="$(basename "$1")"
|
||||
if [ -h "$filename" ]; then
|
||||
readlink_f "$(readlink "$filename")"
|
||||
else
|
||||
|
||||
@@ -13,6 +13,7 @@ echo -o The directory to output compiled files
|
||||
echo --no-docs Do not attach documentation to compiled modules
|
||||
echo --no-debug-info Do not attach debug info to compiled modules
|
||||
echo --ignore-module-conflict
|
||||
echo --warnings-as-errors Treat warnings as errors and return non-zero exit code
|
||||
echo.
|
||||
echo ** Options marked with (*) can be given more than once
|
||||
echo ** Options given after -- are passed down to the executed code
|
||||
|
||||
@@ -13,7 +13,12 @@ if [ $# -gt 0 ] && ([ $1 = "--help" ] || [ $1 = "-h" ]); then
|
||||
--erl \"switches\" Switches to be passed down to erlang (*)
|
||||
--name \"name\" Makes and assigns a name to the distributed node
|
||||
--sname \"name\" Makes and assigns a short name to the distributed node
|
||||
--cookie \"cookie\" Sets a cookie for this distributed node
|
||||
--hidden Makes a hidden node
|
||||
--detached Starts the Erlang VM detached from console
|
||||
--remsh \"name\" Connects to a node using a remote shell
|
||||
--dot-iex \"path\" Overrides default .iex file and uses path instead;
|
||||
path can be empty, then no file will be loaded
|
||||
|
||||
** Options marked with (*) can be given more than once
|
||||
** Options given after the .exs file or -- are passed down to the executed code
|
||||
@@ -23,7 +28,7 @@ fi
|
||||
|
||||
readlink_f () {
|
||||
cd "$(dirname "$1")" > /dev/null
|
||||
local filename="$(basename "$1")"
|
||||
filename="$(basename "$1")"
|
||||
if [ -h "$filename" ]; then
|
||||
readlink_f "$(readlink "$filename")"
|
||||
else
|
||||
@@ -33,4 +38,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 "$@"
|
||||
ELIXIR_NO_CLI=1 exec "$SCRIPT_PATH"/elixir --no-halt --erl "-user Elixir.IEx.CLI" +iex "$@"
|
||||
|
||||
@@ -75,7 +75,7 @@ defmodule EEx.Compiler do
|
||||
# Check if the syntax node represents an empty string
|
||||
|
||||
defp is_empty?(bin) when is_binary(bin) do
|
||||
bc(<<c>> inbits bin, not c in [?\s,?\t,?\r,?\n], do: <<c>>) == ""
|
||||
bc(<<c>> inbits bin, not c in [?\s, ?\t, ?\r, ?\n], do: <<c>>) == ""
|
||||
end
|
||||
|
||||
defp is_empty?({ :<>, _, [left, right] }) do
|
||||
|
||||
@@ -76,8 +76,8 @@ defmodule EEx.AssignsEngine do
|
||||
quote(do: Keyword.get var!(assigns), unquote(name))
|
||||
end
|
||||
|
||||
defp transform(_) do
|
||||
super
|
||||
defp transform(arg) do
|
||||
super(arg)
|
||||
end
|
||||
|
||||
defoverridable [transform: 1]
|
||||
@@ -94,4 +94,4 @@ defmodule EEx.SmartEngine do
|
||||
`AssignsEngine` and other conveniences. Read
|
||||
`EEx.AssignsEngine` for examples.
|
||||
"""
|
||||
end
|
||||
end
|
||||
|
||||
@@ -20,8 +20,8 @@ defmodule EEx.Tokenizer do
|
||||
end
|
||||
|
||||
defp tokenize('<%%' ++ t, current_line, line, buffer, acc) do
|
||||
{ buffer, new_line, rest } = tokenize_expr t, line, [?%,?<|buffer]
|
||||
tokenize rest, current_line, new_line, [?>,?%|buffer], acc
|
||||
{ buffer, new_line, rest } = tokenize_expr t, line, [?%, ?<|buffer]
|
||||
tokenize rest, current_line, new_line, [?>, ?%|buffer], acc
|
||||
end
|
||||
|
||||
defp tokenize('<%#' ++ t, current_line, line, buffer, acc) do
|
||||
@@ -63,7 +63,7 @@ defmodule EEx.Tokenizer do
|
||||
|
||||
# Tokenize an expression until we find %>
|
||||
|
||||
defp tokenize_expr([?%,?>|t], line, buffer) do
|
||||
defp tokenize_expr([?%, ?>|t], line, buffer) do
|
||||
{ buffer, line, t }
|
||||
end
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ defmodule EEx.SmartEngineTest do
|
||||
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", "<%= lc x inlist [1, 2, 3] do %><%= x %>\n<% end %>"
|
||||
end
|
||||
|
||||
defp assert_eval(expected, actual, binding // []) do
|
||||
|
||||
@@ -21,7 +21,7 @@ defmodule EEx.TokenizerTest do
|
||||
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, 1, "foo\n" }, { :expr, 2, '=', ' bar ' } ]
|
||||
end
|
||||
|
||||
test "strings with more than one line and expression with more than one line" do
|
||||
|
||||
@@ -101,7 +101,7 @@ defmodule EExTest do
|
||||
end
|
||||
|
||||
test "evaluates with require code" do
|
||||
assert_eval "foo 1,2,3", "foo <% require Enum, as: E %><%= E.join [1,2,3], \",\" %>"
|
||||
assert_eval "foo 1,2,3", "foo <% require Enum, as: E %><%= E.join [1, 2, 3], \",\" %>"
|
||||
end
|
||||
|
||||
test "evaluates with end of token" do
|
||||
@@ -242,7 +242,7 @@ Number 3
|
||||
"""
|
||||
|
||||
string = """
|
||||
<%= Enum.map [1,2,3], fn x -> %>
|
||||
<%= Enum.map [1, 2, 3], fn x -> %>
|
||||
Number <%= x %>
|
||||
<% end %>
|
||||
"""
|
||||
|
||||
@@ -13,11 +13,11 @@
|
||||
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
|
||||
name_args=false, %% when true, it means arguments should be named
|
||||
module=nil, %% the current module
|
||||
function=nil, %% the current function
|
||||
vars=[], %% a dict of defined variables and their alias
|
||||
temp_vars=[], %% a dict of all variables defined in a particular assign
|
||||
backup_vars=nil, %% a copy of vars to be used on ^var
|
||||
temp_vars=nil, %% a set of all variables defined in a particular assign
|
||||
clause_vars=nil, %% a dict of all variables defined in a particular clause
|
||||
extra_guards=nil, %% extra guards from args expansion
|
||||
counter=[], %% a counter for the variables defined
|
||||
|
||||
@@ -11,7 +11,7 @@ defprotocol Access do
|
||||
following built-in types: keywords, records and functions.
|
||||
"""
|
||||
|
||||
@only [List, Function, Record, Atom]
|
||||
@only [List, Record, Atom]
|
||||
|
||||
@doc """
|
||||
Receives the element being accessed and the access item.
|
||||
@@ -53,20 +53,9 @@ defimpl Access, for: Atom do
|
||||
def access(nil, _) do
|
||||
nil
|
||||
end
|
||||
|
||||
def access(atom, _) do
|
||||
raise "The access protocol can only be invoked for atoms at " <>
|
||||
"compilation time, tried to invoke it for #{inspect atom}"
|
||||
end
|
||||
end
|
||||
|
||||
defimpl Access, for: Function do
|
||||
@doc """
|
||||
The Access protocol for functions simply invokes
|
||||
the function passing the item as argument. This
|
||||
is useful because it allows a function to be
|
||||
passed as argument in places a dict would also fit.
|
||||
"""
|
||||
def access(function, item) do
|
||||
function.(item)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -48,7 +48,6 @@ defmodule Application.Behaviour do
|
||||
|
||||
"""
|
||||
|
||||
|
||||
# Starts the given application and all of its dependencies that
|
||||
# have not been started yet recursively.
|
||||
#
|
||||
@@ -95,6 +94,7 @@ defmodule Application.Behaviour do
|
||||
quote location: :keep do
|
||||
@behavior :application
|
||||
|
||||
@doc false
|
||||
def stop(_state) do
|
||||
:ok
|
||||
end
|
||||
|
||||
@@ -57,7 +57,7 @@ defmodule Behaviour do
|
||||
case Macro.extract_args(fun) do
|
||||
{ name, args } -> :ok
|
||||
:error ->
|
||||
raise ArgumentError, message: "invalid syntax in defcallback #{Macro.to_binary(fun)}"
|
||||
raise ArgumentError, message: "invalid syntax in defcallback #{Macro.to_string(fun)}"
|
||||
end
|
||||
|
||||
arity = length(args)
|
||||
@@ -113,4 +113,4 @@ defmodule Behaviour do
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -58,7 +58,15 @@ defimpl Binary.Chars, for: List do
|
||||
|
||||
"""
|
||||
def to_binary(thing) do
|
||||
iolist_to_binary(thing)
|
||||
try do
|
||||
iolist_to_binary(thing)
|
||||
rescue
|
||||
ArgumentError ->
|
||||
raise Protocol.UndefinedError,
|
||||
protocol: __MODULE__,
|
||||
structure: thing,
|
||||
extra: "Only iolists are supported"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -74,20 +82,11 @@ defimpl Binary.Chars, for: Number do
|
||||
integer_to_binary(thing)
|
||||
end
|
||||
|
||||
to_binary = :proplists.get_value(:float_to_binary,
|
||||
:proplists.get_value(:exports, :erlang.module_info, []))
|
||||
def to_binary(thing) when thing > @limit do
|
||||
float_to_binary(thing, scientific: @digits)
|
||||
end
|
||||
|
||||
if to_binary == 2 do
|
||||
def to_binary(thing) when thing > @limit do
|
||||
float_to_binary(thing, scientific: @digits)
|
||||
end
|
||||
|
||||
def to_binary(thing) do
|
||||
float_to_binary(thing, compact: true, decimals: @digits)
|
||||
end
|
||||
else
|
||||
def to_binary(thing) do
|
||||
float_to_binary(thing)
|
||||
end
|
||||
def to_binary(thing) do
|
||||
float_to_binary(thing, compact: true, decimals: @digits)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -144,7 +144,7 @@ defimpl Binary.Inspect, for: Atom do
|
||||
end
|
||||
end
|
||||
|
||||
# Detect if atom is an atom alias (Elixir-Foo-Bar-Baz)
|
||||
# Detect if atom is an atom alias (Elixir.Foo.Bar.Baz)
|
||||
|
||||
defp valid_ref_identifier?("Elixir" <> rest) do
|
||||
valid_ref_piece?(rest)
|
||||
@@ -152,7 +152,7 @@ defimpl Binary.Inspect, for: Atom do
|
||||
|
||||
defp valid_ref_identifier?(_), do: false
|
||||
|
||||
defp valid_ref_piece?(<<?-, h, t :: binary>>) when h in ?A..?Z do
|
||||
defp valid_ref_piece?(<<?., h, t :: binary>>) when h in ?A..?Z do
|
||||
valid_ref_piece? valid_identifier?(t)
|
||||
end
|
||||
|
||||
@@ -296,7 +296,7 @@ defimpl Binary.Inspect, for: List do
|
||||
|
||||
defp keyword?([{ key, _value } | rest]) when is_atom(key) do
|
||||
case atom_to_list(key) do
|
||||
'Elixir-' ++ _ -> false
|
||||
'Elixir.' ++ _ -> false
|
||||
_ -> keyword?(rest)
|
||||
end
|
||||
end
|
||||
@@ -314,7 +314,7 @@ defimpl Binary.Inspect, for: Tuple do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> inspect({1,2,3})
|
||||
iex> inspect({1, 2, 3})
|
||||
"{1,2,3}"
|
||||
iex> inspect(ArgumentError.new)
|
||||
"ArgumentError[message: \\\"argument error\\\"]"
|
||||
@@ -335,7 +335,7 @@ defimpl Binary.Inspect, for: Tuple do
|
||||
list = tuple_to_list(record)
|
||||
[name|tail] = list
|
||||
|
||||
if (fields = record_fields(name)) && (length(fields) == size(record) - 1) do
|
||||
if is_atom(name) && (fields = record_fields(name)) && (length(fields) == size(record) - 1) do
|
||||
if Enum.first(tail) == :__exception__ do
|
||||
record_join(name, tl(fields), tl(tail), opts)
|
||||
else
|
||||
@@ -391,21 +391,12 @@ defimpl Binary.Inspect, for: Number do
|
||||
integer_to_binary(thing)
|
||||
end
|
||||
|
||||
to_binary = :proplists.get_value(:float_to_binary,
|
||||
:proplists.get_value(:exports, :erlang.module_info, []))
|
||||
def inspect(thing, _) when thing > @limit do
|
||||
float_to_binary(thing, scientific: @digits)
|
||||
end
|
||||
|
||||
if to_binary == 2 do
|
||||
def inspect(thing, _) when thing > @limit do
|
||||
float_to_binary(thing, scientific: @digits)
|
||||
end
|
||||
|
||||
def inspect(thing, _) do
|
||||
float_to_binary(thing, compact: true, decimals: @digits)
|
||||
end
|
||||
else
|
||||
def inspect(thing, _) do
|
||||
float_to_binary(thing)
|
||||
end
|
||||
def inspect(thing, _) do
|
||||
float_to_binary(thing, compact: true, decimals: @digits)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -7,11 +7,9 @@ defmodule Bitwise do
|
||||
your module:
|
||||
|
||||
iex> use Bitwise
|
||||
...> bnot 1
|
||||
iex> bnot 1
|
||||
-2
|
||||
|
||||
iex> use Bitwise
|
||||
...> 1 &&& 1
|
||||
iex> 1 &&& 1
|
||||
1
|
||||
|
||||
You can select to include only or skip operators by passing options:
|
||||
@@ -127,4 +125,4 @@ defmodule Bitwise do
|
||||
defmacro left >>> right do
|
||||
quote do: __op__ :bsr, unquote(left), unquote(right)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
+101
-42
@@ -57,14 +57,8 @@ defmodule Code do
|
||||
:code.del_path(Path.expand to_char_list(path))
|
||||
end
|
||||
|
||||
@doc false
|
||||
def eval(string, binding // [], opts // []) do
|
||||
IO.write "[WARNING] Code.eval is deprecated, please use Code.eval_string instead\n#{Exception.format_stacktrace}"
|
||||
eval_string(string, binding, opts)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Evalutes the contents given by string. The second argument is the
|
||||
Evaluates the contents given by string. The second argument is the
|
||||
binding (which should be a keyword) followed by a keyword list of
|
||||
environment options. Those options can be:
|
||||
|
||||
@@ -78,11 +72,13 @@ defmodule Code do
|
||||
* `:aliases` - a list of tuples with the alias and its target
|
||||
* `:requires` - a list of modules required
|
||||
* `:functions` - a list of tuples where the first element is a module
|
||||
and the second a list of imported function names and arity
|
||||
and the second a list of imported function names and arity. The list
|
||||
of function names and arity must be sorted;
|
||||
* `:macros` - a list of tuples where the first element is a module
|
||||
and the second a list of imported macro names and arity
|
||||
and the second a list of imported macro names and arity. The list
|
||||
of function names and arity must be sorted;
|
||||
|
||||
Notice that setting any ov the values above overrides Elixir default
|
||||
Notice that setting any of the values above overrides Elixir default
|
||||
values. For example, setting `:requires` to `[]`, will no longer
|
||||
automatically required the `Kernel` module, in the same way setting
|
||||
`:macros` will no longer auto-import `Kernel` macros as `if`, `case`,
|
||||
@@ -104,17 +100,22 @@ defmodule Code do
|
||||
def eval_string(string, binding // [], opts // [])
|
||||
|
||||
def eval_string(string, binding, Macro.Env[] = env) do
|
||||
eval_string(string, binding, env.to_keywords)
|
||||
do_eval_string(string, binding, env.to_keywords)
|
||||
end
|
||||
|
||||
def eval_string(string, binding, opts) do
|
||||
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, _scope } =
|
||||
:elixir.eval :unicode.characters_to_list(string), binding, opts
|
||||
{ value, binding }
|
||||
end
|
||||
|
||||
@doc """
|
||||
Evalutes the quoted contents.
|
||||
Evaluates the quoted contents.
|
||||
|
||||
This function accepts a list of environment options.
|
||||
Check `Code.eval_string` for more information.
|
||||
@@ -136,17 +137,65 @@ defmodule Code do
|
||||
def eval_quoted(quoted, binding // [], opts // [])
|
||||
|
||||
def eval_quoted(quoted, binding, Macro.Env[] = env) do
|
||||
eval_quoted(quoted, binding, env.to_keywords)
|
||||
do_eval_quoted(quoted, binding, env.to_keywords)
|
||||
end
|
||||
|
||||
def eval_quoted(quoted, binding, opts) do
|
||||
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, _scope } =
|
||||
:elixir.eval_quoted [quoted], binding, opts
|
||||
{ value, binding }
|
||||
end
|
||||
|
||||
defp validate_eval_opts(opts) do
|
||||
if f = opts[:functions], do: validate_imports(:functions, f)
|
||||
if m = opts[:macros], do: validate_imports(:macros, m)
|
||||
if a = opts[:aliases], do: validate_aliases(:aliases, a)
|
||||
if r = opts[:requires], do: validate_requires(:requires, r)
|
||||
end
|
||||
|
||||
defp validate_requires(kind, requires) do
|
||||
valid = is_list(requires) and Enum.all?(requires, is_atom(&1))
|
||||
|
||||
unless valid do
|
||||
raise ArgumentError, "expected :#{kind} option given to eval in the format: [module]"
|
||||
end
|
||||
end
|
||||
|
||||
defp validate_aliases(kind, aliases) do
|
||||
valid = is_list(aliases) and Enum.all?(aliases, fn { k, v } ->
|
||||
is_atom(k) and is_atom(v)
|
||||
end)
|
||||
|
||||
unless valid do
|
||||
raise ArgumentError, "expected :#{kind} option given to eval in the format: [{ module, module }]"
|
||||
end
|
||||
end
|
||||
|
||||
defp validate_imports(kind, imports) do
|
||||
valid = is_list(imports) and Enum.all?(imports, fn { k, v } ->
|
||||
is_atom(k) and is_list(v) and Enum.all?(v, fn { name, arity } ->
|
||||
is_atom(name) and is_integer(arity)
|
||||
end)
|
||||
end)
|
||||
|
||||
unless valid do
|
||||
raise ArgumentError, "expected :#{kind} option given to eval in the format: [{ module, [{ name, arity }] }]"
|
||||
end
|
||||
end
|
||||
|
||||
@doc false
|
||||
def string_to_ast(string, opts // []) do
|
||||
IO.write "[WARNING] Code.string_to_ast is deprecated, please use Code.string_to_quoted instead\n#{Exception.format_stacktrace}"
|
||||
string_to_quoted(string, opts)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Converts the given string to AST. It returns `{ :ok, ast }`
|
||||
Converts the given string to quoted form. It returns `{ :ok, quoted_form }`
|
||||
if it succeeds, `{ :error, { line, error, token } }` otherwise.
|
||||
|
||||
## Options
|
||||
@@ -159,47 +208,53 @@ defmodule Code do
|
||||
* `:existing_atoms_only` - When true, raises an error
|
||||
when non-existing atoms are found by the tokenizer.
|
||||
|
||||
## Macro.to_binary/1
|
||||
## Macro.to_string/1
|
||||
|
||||
The opposite of converting a string to its AST is
|
||||
`Macro.to_binary`, which converts a AST to a binary
|
||||
The opposite of converting a string to its quoted form is
|
||||
`Macro.to_string`, which converts a quoted form to a string/binary
|
||||
representation.
|
||||
"""
|
||||
def string_to_ast(string, opts // []) do
|
||||
def string_to_quoted(string, opts // []) do
|
||||
file = Keyword.get opts, :file, "nofile"
|
||||
line = Keyword.get opts, :line, 1
|
||||
res = :elixir_translator.forms(:unicode.characters_to_list(string), line, file, opts)
|
||||
|
||||
case res do
|
||||
{ :ok, ast } -> { :ok, unpack_ast(line, ast) }
|
||||
{ :ok, forms } -> { :ok, unpack_quote(line, forms) }
|
||||
_ -> res
|
||||
end
|
||||
end
|
||||
|
||||
@doc false
|
||||
def string_to_ast!(string, opts // []) do
|
||||
IO.write "[WARNING] Code.string_to_ast! is deprecated, please use Code.string_to_quoted! instead\n#{Exception.format_stacktrace}"
|
||||
string_to_quoted!(string, opts)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Converts the given string to AST. It returns the ast if it succeeds,
|
||||
Converts the given string to 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),
|
||||
SyntaxError otherwise.
|
||||
|
||||
Check `Code.string_to_ast/2` for options information.
|
||||
Check `Code.string_to_quoted/2` for options information.
|
||||
"""
|
||||
def string_to_ast!(string, opts // []) do
|
||||
def string_to_quoted!(string, opts // []) do
|
||||
file = Keyword.get opts, :file, "nofile"
|
||||
line = Keyword.get opts, :line, 1
|
||||
res = :elixir_translator.forms!(:unicode.characters_to_list(string), line, file, opts)
|
||||
unpack_ast(line, res)
|
||||
unpack_quote(line, res)
|
||||
end
|
||||
|
||||
defp unpack_ast(_line, []), do: nil
|
||||
defp unpack_ast(_line, [forms]) when not is_list(forms), do: forms
|
||||
defp unpack_ast(line, forms), do: { :__block__, [line: line], forms }
|
||||
defp unpack_quote(_line, []), do: nil
|
||||
defp unpack_quote(_line, [forms]) when not is_list(forms), do: forms
|
||||
defp unpack_quote(line, forms), do: { :__block__, [line: line], forms }
|
||||
|
||||
@doc """
|
||||
Loads the given `file`. Accepts `relative_to` as an argument
|
||||
to tell where the file is located. If the file was already
|
||||
required/loaded, loads it again. It returns all the modules
|
||||
defined in the file.
|
||||
Loads 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 invoked concurrently
|
||||
@@ -217,8 +272,8 @@ defmodule Code do
|
||||
|
||||
@doc """
|
||||
Requires the given `file`. Accepts `relative_to` as an argument to tell where
|
||||
the file is located. It returns all the modules defined in the file. If the
|
||||
file was already required/loaded, doesn't do anything and returns nil.
|
||||
the file is located. The return value is the same as that of `load_file`. If
|
||||
the file was already required/loaded, doesn't do anything and returns nil.
|
||||
|
||||
Notice that if `require_file` is invoked by different processes concurrently,
|
||||
the first process to invoke `require_file` acquires a lock and the remaining
|
||||
@@ -245,7 +300,7 @@ defmodule Code do
|
||||
|
||||
@doc """
|
||||
Loads the compilation options from the code server.
|
||||
Check compiler_options/1 for more information.
|
||||
Check `compiler_options/1` for more information.
|
||||
"""
|
||||
def compiler_options do
|
||||
:elixir_code_server.call :compiler_options
|
||||
@@ -257,13 +312,17 @@ defmodule Code do
|
||||
|
||||
Available options are:
|
||||
|
||||
* docs - when true, retain documentation in the compiled module.
|
||||
True by default;
|
||||
* debug_info - when true, retain debug information in the compiled module.
|
||||
This allows a developer to reconstruct the original source
|
||||
code, for such reasons, false by default;
|
||||
* ignore_module_conflict - when true, override modules that were already defined
|
||||
without raising errors, false by default;
|
||||
* `:docs` - when true, retain documentation in the compiled module.
|
||||
True by default;
|
||||
|
||||
* `:debug_info` - when true, retain debug information in the compiled module.
|
||||
This allows a developer to reconstruct the original source
|
||||
code, for such reasons, false by default;
|
||||
|
||||
* `:ignore_module_conflict` - when true, override modules that were already defined
|
||||
without raising errors, false by default;
|
||||
|
||||
* `:warnings_as_errors` - cause compilation to fail when warnings are spewed;
|
||||
|
||||
"""
|
||||
def compiler_options(opts) do
|
||||
|
||||
+13
-23
@@ -29,7 +29,7 @@ defmodule Dict do
|
||||
...> dict[:hello]
|
||||
:world
|
||||
|
||||
And also the `Enum.Iterator` protocol, allowing one to write:
|
||||
And also the `Enumerable` protocol, allowing one to write:
|
||||
|
||||
Enum.each(dict, fn ({ k, v }) ->
|
||||
IO.puts "#{k}: #{v}"
|
||||
@@ -50,7 +50,6 @@ defmodule Dict do
|
||||
defcallback equal?(t, t) :: boolean
|
||||
defcallback get(t, key) :: value
|
||||
defcallback get(t, key, value) :: value
|
||||
defcallback get!(t, key) :: value | no_return
|
||||
defcallback has_key?(t, key) :: boolean
|
||||
defcallback keys(t) :: list(key)
|
||||
defcallback merge(t, t) :: t
|
||||
@@ -131,11 +130,9 @@ defmodule Dict do
|
||||
## Examples
|
||||
|
||||
iex> d = HashDict.new([a: 1])
|
||||
...> Dict.has_key?(d, :a)
|
||||
iex> Dict.has_key?(d, :a)
|
||||
true
|
||||
|
||||
iex> d = HashDict.new([a: 1])
|
||||
...> Dict.has_key?(d, :b)
|
||||
iex> Dict.has_key?(d, :b)
|
||||
false
|
||||
|
||||
"""
|
||||
@@ -151,15 +148,11 @@ defmodule Dict do
|
||||
## Examples
|
||||
|
||||
iex> d = HashDict.new([a: 1])
|
||||
...> Dict.get(d, :a)
|
||||
iex> Dict.get(d, :a)
|
||||
1
|
||||
|
||||
iex> d = HashDict.new([a: 1])
|
||||
...> Dict.get(d, :b)
|
||||
iex> Dict.get(d, :b)
|
||||
nil
|
||||
|
||||
iex> d = HashDict.new([a: 1])
|
||||
...> Dict.get(d, :b, 3)
|
||||
iex> Dict.get(d, :b, 3)
|
||||
3
|
||||
"""
|
||||
@spec get(t, key, value) :: value
|
||||
@@ -179,11 +172,9 @@ defmodule Dict do
|
||||
## Examples
|
||||
|
||||
iex> d = HashDict.new([a: 1])
|
||||
...> Dict.fetch(d, :a)
|
||||
iex> Dict.fetch(d, :a)
|
||||
{ :ok, 1 }
|
||||
|
||||
iex> d = HashDict.new([a: 1])
|
||||
...> Dict.fetch(d, :b)
|
||||
iex> Dict.fetch(d, :b)
|
||||
:error
|
||||
|
||||
"""
|
||||
@@ -199,10 +190,9 @@ defmodule Dict do
|
||||
## Examples
|
||||
|
||||
iex> d = HashDict.new([a: 1])
|
||||
...> Dict.fetch!(d, :a)
|
||||
iex> Dict.fetch!(d, :a)
|
||||
1
|
||||
iex> d = HashDict.new([a: 1])
|
||||
...> Dict.fetch!(d, :b)
|
||||
iex> Dict.fetch!(d, :b)
|
||||
** (KeyError) key not found: :b
|
||||
|
||||
"""
|
||||
@@ -397,7 +387,7 @@ defmodule Dict do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns a new dict where the the given `keys` a removed from `dict`.
|
||||
Returns a new dict where the given `keys` are removed from `dict`.
|
||||
Any non-member keys are ignored.
|
||||
|
||||
## Examples
|
||||
@@ -425,11 +415,11 @@ defmodule Dict do
|
||||
## Examples
|
||||
|
||||
iex> d = HashDict.new([a: 1, b: 2])
|
||||
...>
|
||||
...> d = Dict.take(d, [:a, :c, :d])
|
||||
...> Dict.to_list(d)
|
||||
[a: 1]
|
||||
|
||||
iex> d = HashDict.new([a: 1, b: 2])
|
||||
...>
|
||||
...> d = Dict.take(d, [:c, :d])
|
||||
...> Dict.to_list(d)
|
||||
[]
|
||||
|
||||
+398
-890
File diff suppressed because it is too large
Load Diff
+23
-18
@@ -67,9 +67,14 @@ defexception FunctionClauseError, [module: nil, function: nil, arity: nil] do
|
||||
end
|
||||
end
|
||||
|
||||
defexception Protocol.UndefinedError, [protocol: nil, structure: nil] do
|
||||
defexception Protocol.UndefinedError, [protocol: nil, structure: nil, extra: nil] do
|
||||
def message(exception) do
|
||||
"protocol #{inspect exception.protocol} not implemented for #{inspect exception.structure}"
|
||||
msg = "protocol #{inspect exception.protocol} not implemented for #{inspect exception.structure}"
|
||||
if exception.extra do
|
||||
msg <> ". " <> exception.extra
|
||||
else
|
||||
msg
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -150,12 +155,6 @@ defmodule Exception do
|
||||
ErlangError[original: other]
|
||||
end
|
||||
|
||||
@doc false
|
||||
def format_entry(entry, cwd // nil) do
|
||||
IO.write "[WARNING] Exception.format_stacktrace is deprecated, please use Exception.format_stacktrace_entry instead\n#{Exception.format_stacktrace}"
|
||||
format_stacktrace_entry(entry, cwd)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Receives a tuple representing a stacktrace entry and formats it.
|
||||
The current working directory may be given as argument, which
|
||||
@@ -193,9 +192,7 @@ defmodule Exception do
|
||||
calculates the current stacktrace and formats it. As consequence,
|
||||
the value of `System.stacktrace` is changed.
|
||||
"""
|
||||
def format_stacktrace(trace // nil)
|
||||
|
||||
def format_stacktrace(trace) do
|
||||
def format_stacktrace(trace // nil) do
|
||||
trace = trace || try do
|
||||
throw(:stacktrace)
|
||||
catch
|
||||
@@ -209,14 +206,22 @@ defmodule Exception do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Prints the current stacktrace to standard output.
|
||||
|
||||
A stacktrace must be given as argument. If not, this function
|
||||
calculates the current stacktrace and formats it. As consequence,
|
||||
the value of `System.stacktrace` is changed.
|
||||
Formats the caller, i.e. the first entry in the stacktrace.
|
||||
Notice that doing to tail call optimization, the stacktrace
|
||||
may not report the direct caller of the function.
|
||||
"""
|
||||
def print_stacktrace(trace // nil) do
|
||||
IO.write format_stacktrace(trace)
|
||||
def format_caller(trace // nil) do
|
||||
trace = trace || try do
|
||||
throw(:stacktrace)
|
||||
catch
|
||||
:stacktrace -> Enum.drop(:erlang.get_stacktrace, 1)
|
||||
end
|
||||
|
||||
if entry = Enum.at(trace, 1) do
|
||||
format_stacktrace_entry(entry)
|
||||
else
|
||||
"nofile:0: "
|
||||
end
|
||||
end
|
||||
|
||||
## Helpers
|
||||
|
||||
+213
-171
@@ -38,10 +38,12 @@ defexception File.Error, [reason: nil, action: "", path: nil] do
|
||||
end
|
||||
end
|
||||
|
||||
defexception File.CopyError, [reason: nil, action: "", source: nil, destination: nil] do
|
||||
defexception File.CopyError, [reason: nil, action: "", source: nil, destination: nil, on: nil] do
|
||||
def message(exception) do
|
||||
formatted = list_to_binary(:file.format_error(reason exception))
|
||||
"could not #{action exception} from #{source exception} to #{destination exception}: #{formatted}"
|
||||
location = if on = on(exception), do: ". #{on}", else: ""
|
||||
"could not #{action exception} from #{source exception} to " <>
|
||||
"#{destination exception}#{location}: #{formatted}"
|
||||
end
|
||||
end
|
||||
|
||||
@@ -54,28 +56,30 @@ end
|
||||
|
||||
defmodule File do
|
||||
@moduledoc """
|
||||
This module contains function to manipulate files.
|
||||
Many of the functions that interact with the filesystem
|
||||
have their naming based on its UNIX variants. For
|
||||
example, deleting a file is done with `File.rm`.
|
||||
Getting its stats with `File.stat`.
|
||||
This module contains functions to manipulate files.
|
||||
|
||||
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`, `IO.binwrite` and `IO.binreadline` to
|
||||
interact with the file. A developer may pass `:utf8`
|
||||
as an option when opening the file and then all other
|
||||
functions from IO are available, since they work directly
|
||||
Some of those functions are low-level, allowing the user
|
||||
to interact with the file or IO devices, like `File.open/2`,
|
||||
`File.copy/3` and others. This module also provides higher
|
||||
level functions that works with filenames and have their naming
|
||||
based on UNIX variants. For example, one can copy a file
|
||||
via `File.cp/3` and remove files and directories recursively
|
||||
via `File.rm_rf/2`
|
||||
|
||||
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`, `IO.binwrite` and
|
||||
`IO.binreadline` to interact with the file. A developer may
|
||||
pass `:utf8` as an option when opening the file and then all
|
||||
other functions from IO are available, since they work directly
|
||||
with Unicode data.
|
||||
|
||||
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 that ends with `!` which returns the
|
||||
result (without the `{ :ok, result }` tuple) in
|
||||
case of success or raises an exception in case it
|
||||
fails. For example:
|
||||
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
|
||||
that ends with `!` which returns the result (without the
|
||||
`{ :ok, result }` tuple) in case of success or raises an
|
||||
exception in case it fails. For example:
|
||||
|
||||
File.read("hello.txt")
|
||||
#=> { :ok, "World" }
|
||||
@@ -89,16 +93,10 @@ defmodule File do
|
||||
File.read!("invalid.txt")
|
||||
#=> raises File.Error
|
||||
|
||||
In general, a developer should use the former in case
|
||||
he wants to react in the fie 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).
|
||||
|
||||
Finally, the functions in this module accept either
|
||||
a char lists or a binary. When manipulating paths, a char
|
||||
list is returned if one is given as argument. However,
|
||||
when reading files, binaries are always returned.
|
||||
In general, a developer should use the former in case he wants
|
||||
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).
|
||||
"""
|
||||
|
||||
alias :file, as: F
|
||||
@@ -125,7 +123,7 @@ defmodule File do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns true if the given argument exists.
|
||||
Returns true if the given path exists.
|
||||
It can be regular file, directory, socket,
|
||||
symbolic link, named pipe or device file.
|
||||
|
||||
@@ -233,7 +231,7 @@ defmodule File do
|
||||
|
||||
@doc """
|
||||
Returns information about the `path`. If it exists, it
|
||||
returns a `{ :ok, info }` tuple, where info is as a
|
||||
returns a `{ :ok, info }` tuple, where info is a
|
||||
`File.Info` record. Retuns `{ :error, reason }` with
|
||||
the same reasons as `File.read` if a failure occurs.
|
||||
|
||||
@@ -310,17 +308,24 @@ defmodule File do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Copies the contents of `source` to `destination`. Both
|
||||
parameters can be a filename or an io device opened with `File.open`.
|
||||
`bytes_count` specifies the number of bytes to count, the default
|
||||
being `:infinity`.
|
||||
Copies the contents of `source` to `destination`.
|
||||
|
||||
If file `destination` already exists, it is overriden
|
||||
Both parameters can be a filename or an io device opened
|
||||
with `File.open/2`. `bytes_count` specifies the number of
|
||||
bytes to copy, the default being `:infinity`.
|
||||
|
||||
If file `destination` already exists, it is overwritten
|
||||
by the contents in `source`.
|
||||
|
||||
Returns `{ :ok, bytes_copied }` if successful,
|
||||
`{ :error, reason }` otherwise.
|
||||
|
||||
Compared to the `File.cp/3`, this function is more low-level,
|
||||
allowing a copy from device to device limited by a number of
|
||||
bytes. On the other hand, `File.cp/3` performs more extensive
|
||||
checks on both source and destination and it also preserves
|
||||
the file mode after copy.
|
||||
|
||||
Typical error reasons are the same as in `open/2`,
|
||||
`read/1` and `write/2`.
|
||||
"""
|
||||
@@ -329,7 +334,7 @@ defmodule File do
|
||||
end
|
||||
|
||||
@doc """
|
||||
The same as `copy/3` but raises an File.CopyError if it fails.
|
||||
The same as `copy/3` but raises an `File.CopyError` if it fails.
|
||||
Returns the `bytes_copied` otherwise.
|
||||
"""
|
||||
def copy!(source, destination, bytes_count // :infinity) do
|
||||
@@ -342,40 +347,36 @@ defmodule File do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Copies the contents in `source` to `destination`.
|
||||
Similar to the command `cp -r` in Unix systems,
|
||||
this function behaves differently depending
|
||||
if `source` and `destination` are a file or a directory.
|
||||
Copies the contents in `source` to `destination` preserving its mode.
|
||||
|
||||
If both are files, it simply copies `source` to
|
||||
`destination`. However, if `destination` is a directory,
|
||||
it copies the contents of `source` to `destination/source`
|
||||
recursively.
|
||||
Similar to the command `cp` in Unix systems, this function
|
||||
behaves differently depending if `destination` is a directory
|
||||
or not. In particular, if `destination` is a directory, it
|
||||
copies the contents of `source` to `destination/source`.
|
||||
|
||||
If a file already exists in the destination,
|
||||
it invokes a callback which should return
|
||||
true if the existing file should be overriden,
|
||||
false otherwise. It defaults to return true.
|
||||
If a file already exists in the destination, it invokes a
|
||||
callback which should return true if the existing file
|
||||
should be overwritten, false otherwise. It defaults to return true.
|
||||
|
||||
It returns `:ok` in case of success, returns
|
||||
`{ :error, reason }` otherwise.
|
||||
|
||||
If you want to copy contents from an io device to another device
|
||||
or do a straight copy from a source to a destination without
|
||||
preserving modes, check `File.copy/3` instead.
|
||||
"""
|
||||
def cp(source, destination, callback // fn(_, _) -> true end) do
|
||||
if dir?(source) do
|
||||
{ :error, :eisdir }
|
||||
else
|
||||
output =
|
||||
if dir?(destination) do
|
||||
mkdir(destination)
|
||||
FN.join(destination, FN.basename(source))
|
||||
else
|
||||
destination
|
||||
end
|
||||
|
||||
case do_cp_file(source, output, callback, []) do
|
||||
{ :error, _ } = error -> error
|
||||
_ -> :ok
|
||||
output =
|
||||
if dir?(destination) do
|
||||
mkdir(destination)
|
||||
FN.join(destination, FN.basename(source))
|
||||
else
|
||||
destination
|
||||
end
|
||||
|
||||
case do_cp_file(source, output, callback, []) do
|
||||
{ :error, reason, _ } -> { :error, reason }
|
||||
_ -> :ok
|
||||
end
|
||||
end
|
||||
|
||||
@@ -388,7 +389,8 @@ defmodule File do
|
||||
:ok -> :ok
|
||||
{ :error, reason } ->
|
||||
raise File.CopyError, reason: reason, action: "copy recursively",
|
||||
source: :unicode.characters_to_binary(source), destination: :unicode.characters_to_binary(destination)
|
||||
source: :unicode.characters_to_binary(source),
|
||||
destination: :unicode.characters_to_binary(destination)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -405,7 +407,7 @@ defmodule File do
|
||||
|
||||
If a file already exists in the destination,
|
||||
it invokes a callback which should return
|
||||
true if the existing file should be overriden,
|
||||
true if the existing file should be overwritten,
|
||||
false otherwise. It defaults to return true.
|
||||
|
||||
If a directory already exists in the destination
|
||||
@@ -434,7 +436,7 @@ defmodule File do
|
||||
|
||||
# Same as before, but asks the user how to proceed in case of conflicts
|
||||
File.cp_r "samples/.", "tmp", fn(source, destination) ->
|
||||
IO.gets("Overriding #{destination} by #{source}. Type y to confirm.") == "y"
|
||||
IO.gets("Overwriting #{destination} by #{source}. Type y to confirm.") == "y"
|
||||
end
|
||||
|
||||
"""
|
||||
@@ -448,7 +450,7 @@ defmodule File do
|
||||
end
|
||||
|
||||
case do_cp_r(source, output, callback, []) do
|
||||
{ :error, _ } = error -> error
|
||||
{ :error, _, _ } = error -> error
|
||||
res -> { :ok, res }
|
||||
end
|
||||
end
|
||||
@@ -460,19 +462,26 @@ defmodule File do
|
||||
def cp_r!(source, destination, callback // fn(_, _) -> true end) do
|
||||
case cp_r(source, destination, callback) do
|
||||
{ :ok, files } -> files
|
||||
{ :error, reason } ->
|
||||
{ :error, reason, file } ->
|
||||
raise File.CopyError, reason: reason, action: "copy recursively",
|
||||
source: :unicode.characters_to_binary(source), destination: :unicode.characters_to_binary(destination)
|
||||
source: :unicode.characters_to_binary(source),
|
||||
destination: :unicode.characters_to_binary(destination),
|
||||
on: file
|
||||
end
|
||||
end
|
||||
|
||||
# src may be a file or a directory, dest is definitely
|
||||
# a directory. Returns nil unless an error is found.
|
||||
defp do_cp_r(src, dest, callback, acc) when is_list(acc) do
|
||||
case F.read_link(src) do
|
||||
{ :ok, link } ->
|
||||
do_cp_link(link, src, dest, callback, acc)
|
||||
_ ->
|
||||
case :elixir.file_type(src) do
|
||||
{ :ok, :regular } ->
|
||||
do_cp_file(src, dest, callback, acc)
|
||||
{ :ok, :symlink } ->
|
||||
case F.read_link(src) do
|
||||
{ :ok, link } -> do_cp_link(link, src, dest, callback, acc)
|
||||
{ :error, reason } -> { :error, reason, :unicode.characters_to_binary(src) }
|
||||
end
|
||||
{ :ok, :directory } ->
|
||||
case F.list_dir(src) do
|
||||
{ :ok, files } ->
|
||||
case mkdir(dest) do
|
||||
@@ -480,12 +489,12 @@ defmodule File do
|
||||
Enum.reduce(files, [dest|acc], fn(x, acc) ->
|
||||
do_cp_r(FN.join(src, x), FN.join(dest, x), callback, acc)
|
||||
end)
|
||||
reason -> reason
|
||||
{ :error, reason } -> { :error, reason, :unicode.characters_to_binary(dest) }
|
||||
end
|
||||
{ :error, :enotdir } ->
|
||||
do_cp_file(src, dest, callback, acc)
|
||||
reason -> reason
|
||||
{ :error, reason } -> { :error, reason, :unicode.characters_to_binary(src) }
|
||||
end
|
||||
{ :ok, _ } -> { :error, :eio, src }
|
||||
{ :error, reason } -> { :error, reason, :unicode.characters_to_binary(src) }
|
||||
end
|
||||
end
|
||||
|
||||
@@ -514,12 +523,12 @@ defmodule File do
|
||||
{ :ok, _ } ->
|
||||
copy_file_mode!(src, dest)
|
||||
[dest|acc]
|
||||
reason -> reason
|
||||
{ :error, reason } -> { :error, reason, :unicode.characters_to_binary(src) }
|
||||
end
|
||||
else
|
||||
acc
|
||||
end
|
||||
reason -> reason
|
||||
{ :error, reason } -> { :error, reason, :unicode.characters_to_binary(src) }
|
||||
end
|
||||
end
|
||||
|
||||
@@ -533,12 +542,12 @@ defmodule File do
|
||||
rm(dest)
|
||||
case F.make_symlink(link, dest) do
|
||||
:ok -> [dest|acc]
|
||||
reason -> reason
|
||||
{ :error, reason } -> { :error, reason, :unicode.characters_to_binary(src) }
|
||||
end
|
||||
else
|
||||
acc
|
||||
end
|
||||
reason -> reason
|
||||
{ :error, reason } -> { :error, reason, :unicode.characters_to_binary(src) }
|
||||
end
|
||||
end
|
||||
|
||||
@@ -638,7 +647,7 @@ defmodule File do
|
||||
|
||||
@doc """
|
||||
Remove files and directories recursively at the given `path`.
|
||||
Symlinks are not followed but simply removed, non existing
|
||||
Symlinks are not followed but simply removed, non-existing
|
||||
files are simply ignored (i.e. doesn't make this function fail).
|
||||
|
||||
Returns `{ :ok, files_and_directories }` with all files and
|
||||
@@ -671,18 +680,18 @@ defmodule File do
|
||||
case rmdir(path) do
|
||||
:ok -> { :ok, [path|acc] }
|
||||
{ :error, :enoent } -> res
|
||||
reason -> reason
|
||||
reason -> { :error, reason, :unicode.characters_to_binary(path) }
|
||||
end
|
||||
reason -> reason
|
||||
reason -> { :error, reason, :unicode.characters_to_binary(path) }
|
||||
end
|
||||
{ :error, :enotdir } ->
|
||||
{ :error, reason } when reason in [:enotdir, :eio] ->
|
||||
case rm(path) do
|
||||
:ok -> { :ok, [path|acc] }
|
||||
{ :error, :enoent } -> entry
|
||||
reason -> reason
|
||||
{ :error, reason } when reason in [:enoent, :enotdir] -> entry
|
||||
reason -> { :error, reason, :unicode.characters_to_binary(path) }
|
||||
end
|
||||
{ :error, :enoent } -> entry
|
||||
reason -> reason
|
||||
{ :error, reason } -> { :error, reason, :unicode.characters_to_binary(path) }
|
||||
end
|
||||
end
|
||||
|
||||
@@ -704,8 +713,10 @@ defmodule File do
|
||||
def rm_rf!(path) do
|
||||
case rm_rf(path) do
|
||||
{ :ok, files } -> files
|
||||
{ :error, reason } ->
|
||||
raise File.Error, reason: reason, action: "remove files and directories recursively from", path: :unicode.characters_to_binary(path)
|
||||
{ :error, reason, _ } ->
|
||||
raise File.Error, reason: reason,
|
||||
action: "remove files and directories recursively from",
|
||||
path: :unicode.characters_to_binary(path)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -753,14 +764,14 @@ defmodule File do
|
||||
|
||||
This function returns:
|
||||
|
||||
* { :ok, io_device } - The file has been opened in the requested mode.
|
||||
* `{ :ok, io_device }` - The file has been opened in the requested mode.
|
||||
`io_device` is actually the pid of the process which handles the file.
|
||||
This process is linked to the process which originally opened the file.
|
||||
If any process to which the io_device is linked terminates, the file will
|
||||
be closed and the process itself will be terminated. An io_device returned
|
||||
If any process to which the `io_device` is linked terminates, the file will
|
||||
be closed and the process itself will be terminated. An `io_device` returned
|
||||
from this call can be used as an argument to the `IO` module functions.
|
||||
|
||||
* { :error, reason } - The file could not be opened.
|
||||
* `{ :error, reason }` - The file could not be opened.
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -930,11 +941,6 @@ defmodule File do
|
||||
`close/1` might return an old write error and not even try to close the file.
|
||||
See `open/2`.
|
||||
"""
|
||||
def close(io_device) when is_function(io_device) do
|
||||
{ iterator, _ } = io_device.()
|
||||
iterator.(:close)
|
||||
end
|
||||
|
||||
def close(io_device) do
|
||||
F.close(io_device)
|
||||
end
|
||||
@@ -944,13 +950,13 @@ defmodule File do
|
||||
passed into `Enum`. The device is iterated line
|
||||
by line, at the end of iteration the file is closed.
|
||||
|
||||
This reads the file as utf-8. CHeck out `File.biniterator`
|
||||
This reads the file as utf-8. Check out `File.biniterator`
|
||||
to handle the file as a raw binary.
|
||||
|
||||
## Examples
|
||||
|
||||
An example that lazily iterates a file replacing all double
|
||||
quotes per single quotes and write each line to a target file
|
||||
quotes per single quotes and writes each line to a target file
|
||||
is shown below:
|
||||
|
||||
{ :ok, device } = File.open("README.md")
|
||||
@@ -962,50 +968,27 @@ defmodule File do
|
||||
end
|
||||
|
||||
"""
|
||||
def iterator(device)
|
||||
|
||||
def iterator(file) when is_binary(file) or is_list(file) do
|
||||
iterator(file, [])
|
||||
end
|
||||
|
||||
def iterator(device) do
|
||||
fn ->
|
||||
function = fn
|
||||
:ok ->
|
||||
case :io.get_line(device, '') do
|
||||
:eof ->
|
||||
close(device)
|
||||
:stop
|
||||
{ :error, reason } ->
|
||||
raise File.IteratorError, reason: reason
|
||||
data ->
|
||||
{ data, :ok }
|
||||
end
|
||||
:close ->
|
||||
close(device)
|
||||
end
|
||||
{ function, :ok }
|
||||
fn(fun, acc) ->
|
||||
do_iterator(device, fun, acc)
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Opens the given `file` with the given `mode` and
|
||||
returns its iterator. Fails for the same reasons
|
||||
as `File.open`.
|
||||
"""
|
||||
def iterator(file, mode) do
|
||||
case open(file, mode) do
|
||||
{ :ok, device } -> { :ok, iterator(device) }
|
||||
error -> error
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Same as `iterator/2` but raises if the file
|
||||
cannot be opened.
|
||||
returns its iterator. The returned iterator will
|
||||
fail for the same reasons as `File.open!`. Note
|
||||
that the file is opened when the iteration begins.
|
||||
"""
|
||||
def iterator!(file, mode // []) do
|
||||
open!(file, mode) |> iterator
|
||||
fn(fun, acc) ->
|
||||
device = open!(file, mode)
|
||||
try do
|
||||
do_iterator(device, fun, acc)
|
||||
after
|
||||
F.close(device)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -1013,50 +996,87 @@ defmodule File do
|
||||
be passed into `Enum` to iterate line by line as a
|
||||
binary. Check `iterator/1` for more information.
|
||||
"""
|
||||
def biniterator(device)
|
||||
|
||||
def biniterator(file) when is_binary(file) or is_list(file) do
|
||||
biniterator(file, [])
|
||||
end
|
||||
|
||||
def biniterator(device) do
|
||||
fn ->
|
||||
function = fn
|
||||
:ok ->
|
||||
case :file.read_line(device) do
|
||||
:eof ->
|
||||
close(device)
|
||||
:stop
|
||||
{ :error, reason } ->
|
||||
raise File.IteratorError, reason: reason
|
||||
{ :ok, data } ->
|
||||
{ data, :ok }
|
||||
end
|
||||
:close ->
|
||||
close(device)
|
||||
end
|
||||
{ function, :ok }
|
||||
fn(fun, acc) ->
|
||||
do_biniterator(device, fun, acc)
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Opens the given `file` with the given `mode` and
|
||||
returns its biniterator. Fails for the same reasons
|
||||
as `File.open`.
|
||||
returns its biniterator. The returned iterator will
|
||||
fail for the same reasons as `File.open!`. Note
|
||||
that the file is opened when the iteration begins.
|
||||
"""
|
||||
def biniterator(file, mode) do
|
||||
case open(file, mode) do
|
||||
{ :ok, device } -> { :ok, biniterator(device) }
|
||||
error -> error
|
||||
def biniterator!(file, mode // []) do
|
||||
fn(fun, acc) ->
|
||||
device = open!(file, mode)
|
||||
try do
|
||||
do_biniterator(device, fun, acc)
|
||||
after
|
||||
F.close(device)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Same as `biniterator/2` but raises if the file
|
||||
cannot be opened.
|
||||
Changes the unix file `mode` for a given `file`.
|
||||
Returns `:ok` on success, or `{:error, reason}`
|
||||
on failure.
|
||||
"""
|
||||
def biniterator!(file, mode // []) do
|
||||
open!(file, mode) |> biniterator
|
||||
def chmod(file, mode) do
|
||||
F.change_mode(file, mode)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Same as `chmod/2`, but raises an exception in case of failure. Otherwise `:ok`.
|
||||
"""
|
||||
def chmod!(file, mode) do
|
||||
case chmod(file, mode) do
|
||||
:ok -> :ok
|
||||
{ :error, reason } ->
|
||||
raise File.Error, reason: reason, action: "change mode for", path: :unicode.characters_to_binary(file)
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Changes the user group given by the group id `gid`
|
||||
for a given `file`. Returns `:ok` on success, or
|
||||
`{:error, reason}` on failure.
|
||||
"""
|
||||
def chgrp(file, gid) do
|
||||
F.change_group(file, gid)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Same as `chgrp/2`, but raises an exception in case of failure. Otherwise `:ok`.
|
||||
"""
|
||||
def chgrp!(file, gid) do
|
||||
case chgrp(file, gid) do
|
||||
:ok -> :ok
|
||||
{ :error, reason } ->
|
||||
raise File.Error, reason: reason, action: "change group for", path: :unicode.characters_to_binary(file)
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Changes the owner given by the user id `gid`
|
||||
for a given `file`. Returns `:ok` on success,
|
||||
or `{:error, reason}` on failure.
|
||||
"""
|
||||
def chown(file, uid) do
|
||||
F.change_owner(file, uid)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Same as `chown/2`, but raises an exception in case of failure. Otherwise `:ok`.
|
||||
"""
|
||||
def chown!(file, gid) do
|
||||
case chown(file, gid) do
|
||||
:ok -> :ok
|
||||
{ :error, reason } ->
|
||||
raise File.Error, reason: reason, action: "change owner for", path: :unicode.characters_to_binary(file)
|
||||
end
|
||||
end
|
||||
|
||||
## Helpers
|
||||
@@ -1075,4 +1095,26 @@ defmodule File do
|
||||
|
||||
defp open_defaults([], true), do: [:binary]
|
||||
defp open_defaults([], false), do: []
|
||||
|
||||
defp do_iterator(device, acc, fun) do
|
||||
case :io.get_line(device, '') do
|
||||
:eof ->
|
||||
acc
|
||||
{ :error, reason } ->
|
||||
raise File.IteratorError, reason: reason
|
||||
data ->
|
||||
do_iterator(device, fun.(data, acc), fun)
|
||||
end
|
||||
end
|
||||
|
||||
defp do_biniterator(device, acc, fun) do
|
||||
case F.read_line(device) do
|
||||
:eof ->
|
||||
acc
|
||||
{ :error, reason } ->
|
||||
raise File.IteratorError, reason: reason
|
||||
{ :ok, data } ->
|
||||
do_iterator(device, fun.(data, acc), fun)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -55,28 +55,34 @@ defmodule GenEvent.Behaviour do
|
||||
@doc false
|
||||
defmacro __using__(_) do
|
||||
quote location: :keep do
|
||||
@behavior :gen_event
|
||||
@behaviour :gen_event
|
||||
|
||||
@doc false
|
||||
def init(args) do
|
||||
{ :ok, args }
|
||||
end
|
||||
|
||||
@doc false
|
||||
def handle_event(_event, state) do
|
||||
{ :ok, state }
|
||||
end
|
||||
|
||||
@doc false
|
||||
def handle_call(_request, state) do
|
||||
{ :ok, :ok, state }
|
||||
end
|
||||
|
||||
@doc false
|
||||
def handle_info(_msg, state) do
|
||||
{ :ok, state }
|
||||
end
|
||||
|
||||
@doc false
|
||||
def terminate(reason, state) do
|
||||
:ok
|
||||
end
|
||||
|
||||
@doc false
|
||||
def code_change(_old, state, _extra) do
|
||||
{ :ok, state }
|
||||
end
|
||||
|
||||
@@ -88,26 +88,32 @@ defmodule GenServer.Behaviour do
|
||||
quote location: :keep do
|
||||
@behavior :gen_server
|
||||
|
||||
@doc false
|
||||
def init(args) do
|
||||
{ :ok, args }
|
||||
end
|
||||
|
||||
@doc false
|
||||
def handle_call(_request, _from, state) do
|
||||
{ :noreply, state }
|
||||
end
|
||||
|
||||
@doc false
|
||||
def handle_info(_msg, state) do
|
||||
{ :noreply, state }
|
||||
end
|
||||
|
||||
@doc false
|
||||
def handle_cast(_msg, state) do
|
||||
{ :noreply, state }
|
||||
end
|
||||
|
||||
def terminate(reason, state) do
|
||||
@doc false
|
||||
def terminate(_reason, _state) do
|
||||
:ok
|
||||
end
|
||||
|
||||
@doc false
|
||||
def code_change(_old, state, _extra) do
|
||||
{ :ok, state }
|
||||
end
|
||||
|
||||
+95
-86
@@ -72,7 +72,7 @@ defmodule HashDict do
|
||||
|
||||
## Examples
|
||||
|
||||
HashDict.new [{:b,1},{:a,2}]
|
||||
HashDict.new [{:b, 1}, {:a, 2}]
|
||||
#=> #HashDict<[a: 2, b: 1]>
|
||||
|
||||
"""
|
||||
@@ -151,15 +151,6 @@ defmodule HashDict do
|
||||
end
|
||||
end
|
||||
|
||||
@doc false
|
||||
def get!(dict, key) when is_tuple(dict) do
|
||||
IO.write "[WARNING] Dict.get! and HashDict.get! are deprecated, please use Dict.fetch! and HashDict.fetch! instead\n#{Exception.format_stacktrace}"
|
||||
case dict_get(dict, key) do
|
||||
{ ^key, value } -> value
|
||||
false -> raise(KeyError, key: key)
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Fetches the value under key from the dict
|
||||
and return it in a tagged tuple.
|
||||
@@ -218,24 +209,17 @@ defmodule HashDict do
|
||||
ordered()
|
||||
end
|
||||
|
||||
def equal?(ordered(bucket: a, size: size), ordered(bucket: b, size: ^size)) do
|
||||
a == b
|
||||
end
|
||||
|
||||
def equal?(trie(size: size) = a, trie(size: ^size) = b) do
|
||||
a == b
|
||||
end
|
||||
|
||||
def equal?(ordered() = a, trie() = b) do
|
||||
equal?(b, a)
|
||||
end
|
||||
|
||||
def equal?(trie(size: size) = a, ordered(bucket: b, size: ^size)) do
|
||||
:lists.keysort(1, to_list(a)) == :lists.keysort(1, b)
|
||||
end
|
||||
|
||||
def equal?(_, _) do
|
||||
false
|
||||
@doc """
|
||||
Checks if two dicts are equal
|
||||
"""
|
||||
def equal?(dict1, dict2) do
|
||||
size = elem(dict1, 1)
|
||||
case elem(dict2, 1) do
|
||||
^size ->
|
||||
dict_equal?(dict1, dict2)
|
||||
_ ->
|
||||
false
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -246,7 +230,7 @@ defmodule HashDict do
|
||||
end
|
||||
|
||||
def to_list(dict) do
|
||||
dict_fold(dict, [], [&1|&2])
|
||||
dict_fold(dict, [], [&1|&2]) |> :lists.reverse
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -286,12 +270,6 @@ defmodule HashDict do
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Splits a dict into two dicts,
|
||||
one containing entries with key in the keys list,
|
||||
and another containing entries with key not in keys.
|
||||
Returns a 2-tuple of the new dicts.
|
||||
"""
|
||||
def split(dict, keys) do
|
||||
split(keys, new, dict)
|
||||
end
|
||||
@@ -307,10 +285,6 @@ defmodule HashDict do
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns a new dict with only the entries
|
||||
which key is in keys.
|
||||
"""
|
||||
def take(dict, keys) do
|
||||
take(dict, keys, new)
|
||||
end
|
||||
@@ -323,24 +297,28 @@ defmodule HashDict do
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns a new dict with only the entries
|
||||
which key is not in keys
|
||||
"""
|
||||
def drop(dict, []), do: dict
|
||||
|
||||
def drop(dict, [key|keys]) do
|
||||
drop(delete(dict, key), keys)
|
||||
end
|
||||
|
||||
def reduce(ordered(bucket: bucket), acc, fun) do
|
||||
:lists.foldl(fun, acc, bucket)
|
||||
end
|
||||
|
||||
def reduce(trie() = dict, acc, fun) do
|
||||
dict_fold(dict, acc, fun)
|
||||
end
|
||||
|
||||
## Dict-wide functions
|
||||
|
||||
defp dict_get(ordered(bucket: bucket), key) do
|
||||
:lists.keyfind(key, 1, bucket)
|
||||
bucket_get(bucket, key)
|
||||
end
|
||||
|
||||
defp dict_get(trie(root: root, depth: depth), key) do
|
||||
:lists.keyfind(key, 1, node_bucket(root, depth, bucket_hash(key)))
|
||||
bucket_get(node_bucket(root, depth, bucket_hash(key)), key)
|
||||
end
|
||||
|
||||
defp dict_fold(ordered(bucket: bucket), acc, fun) do
|
||||
@@ -390,7 +368,7 @@ defmodule HashDict do
|
||||
{ dict, value, 0 }
|
||||
{ root, value, -1 } ->
|
||||
{ if depth > 0 and trie(dict, :contract_on) == size do
|
||||
root = node_contract(root, depth, depth - 1)
|
||||
root = node_contract(root, depth)
|
||||
trie(dict,
|
||||
root: root,
|
||||
size: size - 1,
|
||||
@@ -403,57 +381,89 @@ defmodule HashDict do
|
||||
end
|
||||
end
|
||||
|
||||
defp dict_equal?(dict1, dict2) do
|
||||
try do
|
||||
reduce(dict1, true, fn({ key, value }, acc) ->
|
||||
case fetch(dict2, key) do
|
||||
{ _ok, ^value } ->
|
||||
acc
|
||||
_ ->
|
||||
throw(:error)
|
||||
end
|
||||
end)
|
||||
catch
|
||||
:error -> false
|
||||
end
|
||||
end
|
||||
|
||||
## Bucket helpers
|
||||
|
||||
# Get value from the bucket
|
||||
defp bucket_get([{k, _}|_bucket], key) when k > key do
|
||||
false
|
||||
end
|
||||
|
||||
defp bucket_get([{key, _}=e|_bucket], key) do
|
||||
e
|
||||
end
|
||||
|
||||
defp bucket_get([_e|bucket], key) do
|
||||
bucket_get(bucket, key)
|
||||
end
|
||||
|
||||
defp bucket_get([], _key) do
|
||||
false
|
||||
end
|
||||
|
||||
# Puts a value in the bucket
|
||||
defp bucket_put([{k,_}=e|bucket], key, { :put, value }) when key < k do
|
||||
{ [{key,value},e|bucket], 1 }
|
||||
defp bucket_put([{k, _}|_]=bucket, key, { :put, value }) when k > key do
|
||||
{ [{key, value}|bucket], 1 }
|
||||
end
|
||||
|
||||
defp bucket_put([{k,_}=e|bucket], key, { :update, initial, _fun }) when key < k do
|
||||
{ [{key,initial},e|bucket], 1 }
|
||||
defp bucket_put([{k, _}|_]=bucket, key, { :update, initial, _fun }) when k > key do
|
||||
{ [{key, initial}|bucket], 1 }
|
||||
end
|
||||
|
||||
defp bucket_put([{k,_}=e|bucket], key, value) when key > k do
|
||||
defp bucket_put([{key, _}|bucket], key, { :put, value }) do
|
||||
{ [{key, value}|bucket], 0 }
|
||||
end
|
||||
|
||||
defp bucket_put([{key, value}|bucket], key, { :update, _initial, fun }) do
|
||||
{ [{key, fun.(value)}|bucket], 0 }
|
||||
end
|
||||
|
||||
defp bucket_put([e|bucket], key, value) do
|
||||
{ rest, count } = bucket_put(bucket, key, value)
|
||||
{ [e|rest], count }
|
||||
end
|
||||
|
||||
defp bucket_put([{_,_}|bucket], key, { :put, value }) do
|
||||
{ [{key,value}|bucket], 0 }
|
||||
end
|
||||
|
||||
defp bucket_put([{_,value}|bucket], key, { :update, _initial, fun }) do
|
||||
{ [{key,fun.(value)}|bucket], 0 }
|
||||
end
|
||||
|
||||
defp bucket_put([], key, { :put, value }) do
|
||||
{ [{key,value}], 1 }
|
||||
{ [{key, value}], 1 }
|
||||
end
|
||||
|
||||
defp bucket_put([], key, { :update, initial, _fun }) do
|
||||
{ [{key,initial}], 1 }
|
||||
{ [{key, initial}], 1 }
|
||||
end
|
||||
|
||||
# Puts a value in the bucket without returning
|
||||
# the operation value
|
||||
defp bucket_put!([{k,_}=e|bucket], key, value) when key < k, do: [{key,value},e|bucket]
|
||||
defp bucket_put!([{k,_}=e|bucket], key, value) when key > k, do: [e|bucket_put!(bucket, key, value)]
|
||||
defp bucket_put!([{_,_}|bucket], key, value), do: [{key,value}|bucket]
|
||||
defp bucket_put!([], key, value), do: [{key,value}]
|
||||
defp bucket_put!([{k, _}|_]=bucket, key, value) when k > key, do: [{key, value}|bucket]
|
||||
defp bucket_put!([{key, _}|bucket], key, value), do: [{key, value}|bucket]
|
||||
defp bucket_put!([{_, _}=e|bucket], key, value), do: [e|bucket_put!(bucket, key, value)]
|
||||
defp bucket_put!([], key, value), do: [{key, value}]
|
||||
|
||||
# Deletes a key from the bucket
|
||||
defp bucket_delete([{k,_}|_] = bucket, key) when key < k do
|
||||
defp bucket_delete([{k, _}|_]=bucket, key) when k > key do
|
||||
{ bucket, nil, 0 }
|
||||
end
|
||||
|
||||
defp bucket_delete([{k,_}=e|bucket], key) when key > k do
|
||||
{ rest, value, count } = bucket_delete(bucket, key)
|
||||
{ [e|rest], value, count }
|
||||
defp bucket_delete([{key, value}|bucket], key) do
|
||||
{ bucket, value, -1 }
|
||||
end
|
||||
|
||||
defp bucket_delete([{_,value}|bucket], _key) do
|
||||
{ bucket, value, -1 }
|
||||
defp bucket_delete([e|bucket], key) do
|
||||
{ rest, value, count } = bucket_delete(bucket, key)
|
||||
{ [e|rest], value, count }
|
||||
end
|
||||
|
||||
defp bucket_delete([], _key) do
|
||||
@@ -551,24 +561,23 @@ defmodule HashDict do
|
||||
node_expand(b7, depth, n), node_expand(b8, depth, n) }
|
||||
end
|
||||
|
||||
defp node_contract({ b1, b2, b3, b4, b5, b6, b7, b8 }, depth, n) when depth > 1 do
|
||||
defp node_contract({ b1, b2, b3, b4, b5, b6, b7, b8 }, depth) when depth > 0 do
|
||||
depth = depth - 1
|
||||
{ node_contract(b1, depth, n), node_contract(b2, depth, n), node_contract(b3, depth, n),
|
||||
node_contract(b4, depth, n), node_contract(b5, depth, n), node_contract(b6, depth, n),
|
||||
node_contract(b7, depth, n), node_contract(b8, depth, n) }
|
||||
{ node_contract(b1, depth), node_contract(b2, depth), node_contract(b3, depth),
|
||||
node_contract(b4, depth), node_contract(b5, depth), node_contract(b6, depth),
|
||||
node_contract(b7, depth), node_contract(b8, depth) }
|
||||
end
|
||||
|
||||
defp node_contract({ b1, b2, b3, b4, b5, b6, b7, b8 }, 1, n) do
|
||||
@node_template |> each_contract(b1, n) |> each_contract(b2, n) |> each_contract(b3, n)
|
||||
|> each_contract(b4, n) |> each_contract(b5, n) |> each_contract(b6, n)
|
||||
|> each_contract(b7, n) |> each_contract(b8, n)
|
||||
defp node_contract({ b1, b2, b3, b4, b5, b6, b7, b8 }, 0) do
|
||||
b1 |> each_contract(b2) |> each_contract(b3) |> each_contract(b4)
|
||||
|> each_contract(b5) |> each_contract(b6) |> each_contract(b7)
|
||||
|> each_contract(b8)
|
||||
end
|
||||
|
||||
defp each_contract(acc, { b1, b2, b3, b4, b5, b6, b7, b8 }, n) do
|
||||
acc |> node_relocate(b1, n) |> node_relocate(b2, n) |> node_relocate(b3, n)
|
||||
|> node_relocate(b4, n) |> node_relocate(b5, n) |> node_relocate(b6, n)
|
||||
|> node_relocate(b7, n) |> node_relocate(b8, n)
|
||||
end
|
||||
defp each_contract([{k, _v}=e|acc], [{key, _value}|_]=bucket) when k < key, do: [e|each_contract(acc, bucket)]
|
||||
defp each_contract(acc, [e|bucket]), do: [e|each_contract(acc, bucket)]
|
||||
defp each_contract([], bucket), do: bucket
|
||||
defp each_contract(acc, []), do: acc
|
||||
|
||||
defp node_relocate(node // @node_template, bucket, n) do
|
||||
:lists.foldl fn { key, value }, acc ->
|
||||
@@ -578,8 +587,8 @@ defmodule HashDict do
|
||||
end
|
||||
end
|
||||
|
||||
defimpl Enum.Iterator, for: HashDict do
|
||||
def iterator(dict), do: HashDict.to_list(dict)
|
||||
defimpl Enumerable, for: HashDict do
|
||||
def reduce(dict, acc, fun), do: HashDict.reduce(dict, acc, fun)
|
||||
def member?(dict, { k, v }), do: match?({ :ok, ^v }, HashDict.fetch(dict, k))
|
||||
def member?(_dict, _), do: false
|
||||
def count(dict), do: HashDict.size(dict)
|
||||
|
||||
+11
-19
@@ -125,6 +125,17 @@ defmodule IO do
|
||||
:io.put_chars erl_dev, [to_iodata(item), ?\n]
|
||||
end
|
||||
|
||||
@doc """
|
||||
Writes the given arguments to stderr as a warning.
|
||||
|
||||
If `--warnings-as-errors` is true and this function is
|
||||
called during compilation, compilation will fail.
|
||||
"""
|
||||
def warn(item) do
|
||||
:elixir_code_server.cast(:register_warning)
|
||||
:io.put_chars :standard_error, [to_iodata(item), ?\n]
|
||||
end
|
||||
|
||||
@doc """
|
||||
Inspects and writes the given argument to the device
|
||||
followed by a new line. A set of options can be given.
|
||||
@@ -180,25 +191,6 @@ defmodule IO do
|
||||
:io.get_chars(map_dev(device), to_iodata(prompt), count)
|
||||
end
|
||||
|
||||
@doc false
|
||||
def getb(prompt, count // 1)
|
||||
|
||||
def getb(prompt, count) when is_integer(count) do
|
||||
IO.write "[WARNING] IO.getb is deprecated, please use IO.getn instead\n#{Exception.format_stacktrace}"
|
||||
getn(prompt, count)
|
||||
end
|
||||
|
||||
def getb(device, prompt) do
|
||||
IO.write "[WARNING] IO.getb is deprecated, please use IO.getn instead\n#{Exception.format_stacktrace}"
|
||||
getn(device, prompt)
|
||||
end
|
||||
|
||||
@doc false
|
||||
def getb(device, prompt, count) do
|
||||
IO.write "[WARNING] IO.getb is deprecated, please use IO.getn instead\n#{Exception.format_stacktrace}"
|
||||
getn(device, prompt, count)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Reads a line from the IO device. It returns:
|
||||
|
||||
|
||||
+22
-39
@@ -2,18 +2,14 @@ defmodule IO.ANSI.Sequence do
|
||||
@moduledoc false
|
||||
|
||||
defmacro defsequence(name, code) do
|
||||
quote do
|
||||
name = unquote(name)
|
||||
code = unquote(code)
|
||||
quote binding: [name: name, code: code] do
|
||||
def unquote(name)() do
|
||||
"\e[#{unquote(code)}m"
|
||||
end
|
||||
|
||||
def name, [], [], do:
|
||||
quote do: "\e[#{unquote(code)}m"
|
||||
|
||||
args =
|
||||
quote do: [<< unquote(atom_to_binary(name)), rest :: binary >> ]
|
||||
|
||||
defp :escape_sequence, args, [], do:
|
||||
quote do: { "\e[#{unquote(code)}m", rest }
|
||||
defp escape_sequence(<< unquote(atom_to_binary(name)), rest :: binary >>) do
|
||||
{ "\e[#{unquote(code)}m", rest }
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -24,9 +20,6 @@ defmodule IO.ANSI do
|
||||
(http://en.wikipedia.org/wiki/ANSI_escape_code) — characters embedded
|
||||
in the text used to control formatting, color, and other output options
|
||||
on video text terminals.
|
||||
|
||||
Please be aware that in Erlang/OTP versions prior to R16, you will not
|
||||
be able to render ANSI escape sequences in iex or erlang shell
|
||||
"""
|
||||
|
||||
import IO.ANSI.Sequence
|
||||
@@ -36,23 +29,11 @@ defmodule IO.ANSI do
|
||||
|
||||
Used to identify whether printing ANSI escape sequences will likely
|
||||
be printed as intended.
|
||||
|
||||
Please note that invoked while in shell (iex) in Erlang/OTP
|
||||
prior to R16, terminal?/0 will always return false because
|
||||
Erlang shell did not support ANSI escape sequences up until
|
||||
R16.
|
||||
"""
|
||||
@spec terminal? :: boolean
|
||||
@spec terminal?(:io.device) :: boolean
|
||||
def terminal?(device // :erlang.group_leader) do
|
||||
if :erlang.system_info(:otp_release) < 'R16' and
|
||||
Process.whereis(:user) != device do
|
||||
# Shell prior to R16 doesn't support ANSI escape
|
||||
# sequences
|
||||
false
|
||||
else
|
||||
match?({:ok, _}, :io.columns(device))
|
||||
end
|
||||
match?({:ok, _}, :io.columns(device))
|
||||
end
|
||||
|
||||
@doc "Resets all attributes"
|
||||
@@ -91,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
|
||||
lc font_n inlist [1, 2, 3, 4, 5, 6, 7, 8, 9] do
|
||||
@doc "Sets alternative font #{font_n}"
|
||||
defsequence :"font_#{font_n}", font_n + 10
|
||||
end
|
||||
@@ -151,13 +132,13 @@ defmodule IO.ANSI do
|
||||
end
|
||||
|
||||
@doc %B"""
|
||||
Escapes a string coverting named ANSI sequences into actual ANSI codes.
|
||||
Escapes a string by converting named ANSI sequences into actual ANSI codes.
|
||||
|
||||
The format for referring sequences is `%{red}` and `%{red,bright}` (for
|
||||
multiple sequences)
|
||||
The format for referring to sequences is `%{red}` and `%{red,bright}` (for
|
||||
multiple sequences).
|
||||
|
||||
It will also force a %{reset} to get appended to every string. If you don't
|
||||
want this behaviour, use `escape_fragment/1` and `escape_fragment/2`.
|
||||
It will also append a %{reset} to the string. If you don't want this
|
||||
behaviour, use `escape_fragment/1` and `escape_fragment/2`.
|
||||
|
||||
An optional boolean parameter can be passed to enable or disable
|
||||
emitting actual ANSI codes. When false, no ANSI codes will emitted.
|
||||
@@ -166,7 +147,7 @@ defmodule IO.ANSI do
|
||||
|
||||
## Example
|
||||
|
||||
iex> IO.ANSI.escape("Hello %{red,bright,green}yes")
|
||||
iex> IO.ANSI.escape("Hello %{red,bright,green}yes", true)
|
||||
"Hello \e[31m\e[1m\e[32myes\e[0m"
|
||||
"""
|
||||
@spec escape(String.t, emit :: boolean) :: String.t
|
||||
@@ -180,10 +161,10 @@ defmodule IO.ANSI do
|
||||
end
|
||||
|
||||
@doc %B"""
|
||||
Escapes a string coverting named ANSI sequences into actual ANSI codes.
|
||||
Escapes a string by converting named ANSI sequences into actual ANSI codes.
|
||||
|
||||
The format for referring sequences is `%{red}` and `%{red,bright}` (for
|
||||
multiple sequences)
|
||||
The format for referring to sequences is `%{red}` and `%{red,bright}` (for
|
||||
multiple sequences).
|
||||
|
||||
An optional boolean parameter can be passed to enable or disable
|
||||
emitting actual ANSI codes. When false, no ANSI codes will emitted.
|
||||
@@ -192,8 +173,10 @@ defmodule IO.ANSI do
|
||||
|
||||
## Example
|
||||
|
||||
iex> IO.ANSI.escape("Hello %{red,bright,green}yes")
|
||||
"Hello \e[31m\e[1m\e[32myes\e[0m"
|
||||
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"
|
||||
|
||||
"""
|
||||
@spec escape_fragment(String.t, emit :: boolean) :: String.t
|
||||
|
||||
+365
-388
File diff suppressed because it is too large
Load Diff
@@ -102,46 +102,42 @@ defmodule Kernel.CLI do
|
||||
|
||||
# Process shared options
|
||||
|
||||
defp process_shared(["-v"|t], config) do
|
||||
defp process_shared([opt|_t], _config) when opt in ["-v", "--version"] do
|
||||
IO.puts "Elixir #{System.version}"
|
||||
process_shared t, config
|
||||
System.halt 0
|
||||
end
|
||||
|
||||
defp process_shared(["-pa",h|t], config) do
|
||||
defp process_shared(["-pa", h|t], config) do
|
||||
Enum.each Path.wildcard(Path.expand(h)), Code.prepend_path(&1)
|
||||
process_shared t, config
|
||||
end
|
||||
|
||||
defp process_shared(["-pz",h|t], config) do
|
||||
defp process_shared(["-pz", h|t], config) do
|
||||
Enum.each Path.wildcard(Path.expand(h)), Code.append_path(&1)
|
||||
process_shared t, config
|
||||
end
|
||||
|
||||
defp process_shared(["--app",h|t], config) do
|
||||
process_shared t, config.update_commands [{:app,h}|&1]
|
||||
defp process_shared(["--app", h|t], config) do
|
||||
process_shared t, config.update_commands [{:app, h}|&1]
|
||||
end
|
||||
|
||||
defp process_shared(["--no-halt"|t], config) do
|
||||
process_shared t, config.halt(false)
|
||||
end
|
||||
|
||||
defp process_shared(["-e",h|t], config) do
|
||||
process_shared t, config.update_commands [{:eval,h}|&1]
|
||||
defp process_shared(["-e", h|t], config) do
|
||||
process_shared t, config.update_commands [{:eval, h}|&1]
|
||||
end
|
||||
|
||||
defp process_shared(["-r",h|t], config) do
|
||||
process_shared t, config.update_commands [{:require,h}|&1]
|
||||
defp process_shared(["-r", h|t], config) do
|
||||
process_shared t, config.update_commands [{:require, h}|&1]
|
||||
end
|
||||
|
||||
defp process_shared(["-pr",h|t], config) do
|
||||
process_shared t, config.update_commands [{:parallel_require,h}|&1]
|
||||
defp process_shared(["-pr", h|t], config) do
|
||||
process_shared t, config.update_commands [{:parallel_require, h}|&1]
|
||||
end
|
||||
|
||||
defp process_shared(["--cookie",h|t], config) do
|
||||
process_shared t, config.update_commands [{:cookie,h}|&1]
|
||||
end
|
||||
|
||||
defp process_shared([erl,_|t], config) when erl in ["--erl", "--sname", "--name"] do
|
||||
defp process_shared([erl, _|t], config) when erl in ["--erl", "--sname", "--name", "--cookie"] do
|
||||
process_shared t, config
|
||||
end
|
||||
|
||||
@@ -163,8 +159,8 @@ defmodule Kernel.CLI do
|
||||
process_iex t, config
|
||||
end
|
||||
|
||||
defp process_argv(["-S",h|t], config) do
|
||||
{ config.update_commands([{:script,h}|&1]), t }
|
||||
defp process_argv(["-S", h|t], config) do
|
||||
{ config.update_commands([{:script, h}|&1]), t }
|
||||
end
|
||||
|
||||
defp process_argv([h|t] = list, config) do
|
||||
@@ -172,7 +168,7 @@ defmodule Kernel.CLI do
|
||||
"-" <> _ ->
|
||||
shared_option? list, config, process_argv(&1, &2)
|
||||
_ ->
|
||||
{ config.update_commands([{:file,h}|&1]), t }
|
||||
{ config.update_commands([{:file, h}|&1]), t }
|
||||
end
|
||||
end
|
||||
|
||||
@@ -186,20 +182,24 @@ defmodule Kernel.CLI do
|
||||
{ config, t }
|
||||
end
|
||||
|
||||
defp process_compiler(["-o",h|t], config) do
|
||||
defp process_compiler(["-o", h|t], config) do
|
||||
process_compiler t, config.output(h)
|
||||
end
|
||||
|
||||
defp process_compiler(["--no-docs"|t], config) do
|
||||
process_compiler t, config.update_compiler_options([{:docs,false}|&1])
|
||||
process_compiler t, config.update_compiler_options([{:docs, false}|&1])
|
||||
end
|
||||
|
||||
defp process_compiler(["--no-debug-info"|t], config) do
|
||||
process_compiler t, config.update_compiler_options([{:debug_info,false}|&1])
|
||||
process_compiler t, config.update_compiler_options([{:debug_info, false}|&1])
|
||||
end
|
||||
|
||||
defp process_compiler(["--ignore-module-conflict"|t], config) do
|
||||
process_compiler t, config.update_compiler_options([{:ignore_module_conflict,true}|&1])
|
||||
process_compiler t, config.update_compiler_options([{:ignore_module_conflict, true}|&1])
|
||||
end
|
||||
|
||||
defp process_compiler(["--warnings-as-errors"|t], config) do
|
||||
process_compiler t, config.update_compiler_options([{:warnings_as_errors, true}|&1])
|
||||
end
|
||||
|
||||
defp process_compiler([h|t] = list, config) do
|
||||
@@ -207,13 +207,13 @@ defmodule Kernel.CLI do
|
||||
"-" <> _ ->
|
||||
shared_option? list, config, process_compiler(&1, &2)
|
||||
_ ->
|
||||
pattern = if File.dir?(h), do: "#{h}/**/*.ex", else: h
|
||||
pattern = if :filelib.is_dir(h), do: "#{h}/**/*.ex", else: h
|
||||
process_compiler t, config.update_compile [pattern|&1]
|
||||
end
|
||||
end
|
||||
|
||||
defp process_compiler([], config) do
|
||||
{ config.update_commands([{:compile,config.compile}|&1]), [] }
|
||||
{ config.update_commands([{:compile, config.compile}|&1]), [] }
|
||||
end
|
||||
|
||||
# Process iex options
|
||||
@@ -222,12 +222,18 @@ defmodule Kernel.CLI do
|
||||
{ config, t }
|
||||
end
|
||||
|
||||
defp process_iex([opt,_|t], config) when opt in ["--remsh"] do
|
||||
# This clause is here so that Kernel.CLI does not error out with "unknown
|
||||
# option"
|
||||
defp process_iex(["--dot-iex", _|t], config) do
|
||||
process_iex t, config
|
||||
end
|
||||
|
||||
defp process_iex(["-S",h|t], config) do
|
||||
{ config.update_commands([{:script,h}|&1]), t }
|
||||
defp process_iex([opt, _|t], config) when opt in ["--remsh"] do
|
||||
process_iex t, config
|
||||
end
|
||||
|
||||
defp process_iex(["-S", h|t], config) do
|
||||
{ config.update_commands([{:script, h}|&1]), t }
|
||||
end
|
||||
|
||||
defp process_iex([h|t] = list, config) do
|
||||
@@ -235,7 +241,7 @@ defmodule Kernel.CLI do
|
||||
"-" <> _ ->
|
||||
shared_option? list, config, process_iex(&1, &2)
|
||||
_ ->
|
||||
{ config.update_commands([{:file,h}|&1]), t }
|
||||
{ config.update_commands([{:file, h}|&1]), t }
|
||||
end
|
||||
end
|
||||
|
||||
@@ -245,7 +251,7 @@ defmodule Kernel.CLI do
|
||||
|
||||
# Process commands
|
||||
|
||||
defp process_command({:cookie,h}, _config) do
|
||||
defp process_command({:cookie, h}, _config) do
|
||||
if Node.alive? do
|
||||
Node.set_cookie(binary_to_atom(h))
|
||||
:ok
|
||||
@@ -269,7 +275,7 @@ defmodule Kernel.CLI do
|
||||
end
|
||||
|
||||
defp process_command({:script, file}, _config) when is_binary(file) do
|
||||
if exec = System.find_executable(file) do
|
||||
if exec = find_elixir_executable(file) do
|
||||
Code.require_file(exec)
|
||||
:ok
|
||||
else
|
||||
@@ -278,7 +284,7 @@ defmodule Kernel.CLI do
|
||||
end
|
||||
|
||||
defp process_command({:file, file}, _config) when is_binary(file) do
|
||||
if File.regular?(file) do
|
||||
if :filelib.is_regular(file) do
|
||||
Code.require_file(file)
|
||||
:ok
|
||||
else
|
||||
@@ -289,7 +295,7 @@ defmodule Kernel.CLI do
|
||||
defp process_command({:require, pattern}, _config) when is_binary(pattern) do
|
||||
files = Path.wildcard(pattern)
|
||||
files = Enum.uniq(files)
|
||||
files = Enum.filter files, File.regular?(&1)
|
||||
files = Enum.filter files, :filelib.is_regular(&1)
|
||||
|
||||
if files != [] do
|
||||
Enum.map files, Code.require_file(&1)
|
||||
@@ -302,7 +308,7 @@ defmodule Kernel.CLI do
|
||||
defp process_command({:parallel_require, pattern}, _config) when is_binary(pattern) do
|
||||
files = Path.wildcard(pattern)
|
||||
files = Enum.uniq(files)
|
||||
files = Enum.filter files, File.regular?(&1)
|
||||
files = Enum.filter files, :filelib.is_regular(&1)
|
||||
|
||||
if files != [] do
|
||||
Kernel.ParallelRequire.files(files)
|
||||
@@ -313,19 +319,33 @@ defmodule Kernel.CLI do
|
||||
end
|
||||
|
||||
defp process_command({:compile, patterns}, config) do
|
||||
File.mkdir_p(config.output)
|
||||
:filelib.ensure_dir(:filename.join(config.output, "."))
|
||||
|
||||
files = Enum.map patterns, Path.wildcard(&1)
|
||||
files = Enum.uniq(List.concat(files))
|
||||
files = Enum.filter files, File.regular?(&1)
|
||||
files = Enum.filter files, :filelib.is_regular(&1)
|
||||
|
||||
if files != [] do
|
||||
Code.compiler_options(config.compiler_options)
|
||||
Kernel.ParallelCompiler.files_to_path(files, config.output,
|
||||
fn file -> IO.puts "Compiled #{file}" end)
|
||||
:ok
|
||||
else
|
||||
{ :error, "--compile : No files matched patterns #{Enum.join(patterns, ",")}" }
|
||||
end
|
||||
end
|
||||
|
||||
defp find_elixir_executable(file) do
|
||||
if exec = System.find_executable(file) do
|
||||
# If we are on Windows, the executable is going to be
|
||||
# a .bat file that must be in the same directory as
|
||||
# the actual Elixir executable.
|
||||
case :os.type() do
|
||||
{ :win32, _ } ->
|
||||
exec = Path.rootname(exec)
|
||||
if File.regular?(exec), do: exec
|
||||
_ ->
|
||||
exec
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
defexception Kernel.CompilationError, message: "compilation failed"
|
||||
|
||||
defmodule Kernel.ParallelCompiler do
|
||||
alias :orddict, as: OrdDict
|
||||
|
||||
@@ -15,6 +17,10 @@ defmodule Kernel.ParallelCompiler do
|
||||
the current file stops being compiled until the dependency is
|
||||
resolved.
|
||||
|
||||
If there is any error during compilation or if warnings_as_errors
|
||||
is set to true and there is a warning, this function will fail
|
||||
with an exception.
|
||||
|
||||
A callback that is invoked every time a file is compiled
|
||||
with its name can be optionally given as argument.
|
||||
"""
|
||||
@@ -32,8 +38,14 @@ defmodule Kernel.ParallelCompiler do
|
||||
|
||||
defp spawn_compilers(files, path, callback) do
|
||||
Code.ensure_loaded(Kernel.ErrorHandler)
|
||||
:elixir_code_server.cast(:reset_warnings)
|
||||
schedulers = max(:erlang.system_info(:schedulers_online), 2)
|
||||
spawn_compilers(files, path, callback, [], [], schedulers, [])
|
||||
|
||||
result = spawn_compilers(files, path, callback, [], [], schedulers, [])
|
||||
case :elixir_code_server.call(:compilation_status) do
|
||||
:ok -> result
|
||||
:error -> raise Kernel.CompilationError, [], []
|
||||
end
|
||||
end
|
||||
|
||||
# We already have 4 currently running, don't spawn new ones
|
||||
@@ -64,7 +76,7 @@ defmodule Kernel.ParallelCompiler do
|
||||
end
|
||||
end
|
||||
|
||||
spawn_compilers(t, output, callback, waiting, [{child,h}|queued], schedulers, result)
|
||||
spawn_compilers(t, output, callback, waiting, [{child, h}|queued], schedulers, result)
|
||||
end
|
||||
|
||||
# No more files, nothing waiting, queue is empty, we are done
|
||||
@@ -138,4 +150,4 @@ defmodule Kernel.ParallelCompiler do
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -24,7 +24,7 @@ defmodule Kernel.SpecialForms do
|
||||
|
||||
## Examples
|
||||
|
||||
:{}.(1,2,3)
|
||||
iex> { 1, 2, 3 }
|
||||
{ 1, 2, 3 }
|
||||
|
||||
"""
|
||||
@@ -35,7 +35,7 @@ defmodule Kernel.SpecialForms do
|
||||
|
||||
## Examples
|
||||
|
||||
:[].(1,2,3)
|
||||
iex> [ 1, 2, 3 ]
|
||||
[ 1, 2, 3 ]
|
||||
|
||||
"""
|
||||
@@ -247,7 +247,7 @@ defmodule Kernel.SpecialForms do
|
||||
for example:
|
||||
|
||||
iex> import List
|
||||
...> flatten([1,[2],3])
|
||||
...> flatten([1, [2], 3])
|
||||
[1,2,3]
|
||||
|
||||
## Selector
|
||||
@@ -388,6 +388,8 @@ defmodule Kernel.SpecialForms do
|
||||
Read the Stacktrace information section below for more information;
|
||||
* `:hygiene` - Allows a developer to disable hygiene selectively;
|
||||
* `:context` - Sets the context resolution happens at;
|
||||
* `:binding` - Passes a binding to the macro. Whenever a binding is given,
|
||||
unquote is automatically disabled;
|
||||
|
||||
## Macro literals
|
||||
|
||||
@@ -397,7 +399,7 @@ defmodule Kernel.SpecialForms do
|
||||
:sum #=> Atoms
|
||||
1 #=> Integers
|
||||
2.0 #=> Floats
|
||||
[1,2] #=> Lists
|
||||
[1, 2] #=> Lists
|
||||
"binaries" #=> Binaries
|
||||
{key, value} #=> Tuple with two elements
|
||||
|
||||
@@ -424,6 +426,9 @@ defmodule Kernel.SpecialForms do
|
||||
module name. This means that the variable is associated to the
|
||||
ContextSample module and only code generated by this module
|
||||
will be able to access that particular `world` variable.
|
||||
While this means macros from the same module could have
|
||||
conflicting variables, it also allows different quotes from
|
||||
the same module to access them.
|
||||
|
||||
The context can be disabled or changed by explicitly setting
|
||||
the context option. All hygiene mechanisms are based on such
|
||||
@@ -626,6 +631,79 @@ defmodule Kernel.SpecialForms do
|
||||
code as if it was defined inside `GenServer.Behaviour` file, in
|
||||
particular, the macro `__FILE__` and exceptions happening inside
|
||||
the quote will always point to `GenServer.Behaviour` file.
|
||||
|
||||
## Binding and unquote fragments
|
||||
|
||||
Elixir quote/unquote mechanisms provides a functionality called
|
||||
unquote fragments. Unquote fragments provide an easy to generate
|
||||
functions on the fly. Consider this example:
|
||||
|
||||
kv = [foo: 1, bar: 2]
|
||||
Enum.each kv, fn { k, v } ->
|
||||
def unquote(k)(), do: unquote(v)
|
||||
end
|
||||
|
||||
In the example above, we have generated the function `foo/0` and
|
||||
`bar/0` dynamically. Now, imagine that, we want to convert this
|
||||
functionality into a macro:
|
||||
|
||||
defmacro defkv(kv) do
|
||||
Enum.each kv, fn { k, v } ->
|
||||
quote do
|
||||
def unquote(k)(), do: unquote(v)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
We can invoke this macro as:
|
||||
|
||||
defkv [foo: 1, bar: 2]
|
||||
|
||||
However, we can't invoke it as follows:
|
||||
|
||||
kv = [foo: 1, bar: 2]
|
||||
defkv kv
|
||||
|
||||
This is because the macro is expecting its arguments to be a
|
||||
key-value at **compilation** time. Since in the example above
|
||||
we are passing the representation of the variable `kv`, our
|
||||
code fails.
|
||||
|
||||
This is actually a common pitfall when developing macros. In
|
||||
practive, we want to avoid doing work at compilation time as
|
||||
much as we can. That said, let's attempt to improve our macro:
|
||||
|
||||
defmacro defkv(kv) do
|
||||
quote do
|
||||
Enum.each unquote(kv), fn { k, v } ->
|
||||
def unquote(k)(), do: unquote(v)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
If you try to run our new macro, you will notice it won't
|
||||
even compile, complaining that the variables `k` and `v`
|
||||
do not exist. This is because of the ambiguity: `unquote(k)`
|
||||
can either be an unquote fragment, as previously, or a regular
|
||||
unquote as in `unquote(kv)`.
|
||||
|
||||
One solution for this problem is to disable unquoting in the
|
||||
macro, however, doing that would make it impossible to inject
|
||||
`kv` representation into the tree. That's when the `:binding`
|
||||
option comes to the rescue. By using `:binding`, we can
|
||||
automatically disable unquoting while still injecting the
|
||||
desired variables into the tree:
|
||||
|
||||
defmacro defkv(kv) do
|
||||
quote binding: [kv: kv] do
|
||||
Enum.each kv, fn { k, v } ->
|
||||
def unquote(k)(), do: unquote(v)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
In fact, the `:binding` option is recommended every time one
|
||||
desires to inject a value into the quote.
|
||||
"""
|
||||
defmacro quote(opts, block)
|
||||
|
||||
@@ -640,6 +718,10 @@ defmodule Kernel.SpecialForms do
|
||||
|
||||
@doc """
|
||||
Defines a variable in the given context.
|
||||
|
||||
If the context is false, it is not stored in any particular
|
||||
context and the variable is not shared in between clauses.
|
||||
|
||||
Check `quote/2` for more information.
|
||||
"""
|
||||
defmacro var!(var, context)
|
||||
@@ -683,7 +765,7 @@ defmodule Kernel.SpecialForms do
|
||||
|
||||
## Examples
|
||||
|
||||
values = [2,3,4]
|
||||
values = [2, 3, 4]
|
||||
quote do: sum(1, unquote_splicing(values), 5)
|
||||
#=> { :sum, [], [1, 2, 3, 4, 5] }
|
||||
|
||||
@@ -694,7 +776,7 @@ defmodule Kernel.SpecialForms do
|
||||
@doc """
|
||||
List comprehensions allow you to quickly build a list from another list:
|
||||
|
||||
iex> lc n inlist [1,2,3,4], do: n * 2
|
||||
iex> lc n inlist [1, 2, 3, 4], do: n * 2
|
||||
[2,4,6,8]
|
||||
|
||||
A comprehension accepts many generators and also filters. Generators
|
||||
@@ -702,33 +784,33 @@ defmodule Kernel.SpecialForms do
|
||||
to loop lists and bitstrings:
|
||||
|
||||
# A list generator:
|
||||
iex> lc n inlist [1,2,3,4], do: n * 2
|
||||
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
|
||||
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]
|
||||
iex> list = [1, 2, 3, 4]
|
||||
...> lc n inlist list, 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
|
||||
iex> lc x inlist [1, 2], y inlist [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
|
||||
iex> lc n inlist [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:
|
||||
|
||||
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> 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}
|
||||
[{213,45,132},{64,76,32},{76,0,0},{234,32,15}]
|
||||
|
||||
"""
|
||||
@@ -752,7 +834,7 @@ defmodule Kernel.SpecialForms do
|
||||
and should not be invoked directly:
|
||||
|
||||
iex> quote do: (1; 2; 3)
|
||||
{ :__block__, [], [1,2,3] }
|
||||
{ :__block__, [], [1, 2, 3] }
|
||||
|
||||
"""
|
||||
defmacro __block__(args)
|
||||
@@ -807,4 +889,10 @@ defmodule Kernel.SpecialForms do
|
||||
|
||||
"""
|
||||
defmacro __aliases__(args)
|
||||
|
||||
@doc """
|
||||
Calls the overriden function when overriding it with `defoverridable`.
|
||||
See `Kernel.defoverridable` for more information and documentation.
|
||||
"""
|
||||
defmacro super(args)
|
||||
end
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
defmodule Kernel.Typespec do
|
||||
@moduledoc """
|
||||
Holds macros and functions for working with typespecs.
|
||||
Provides macros and functions for working with typespecs.
|
||||
|
||||
The attributes `@type`, `@opaque`, `@typep`, `@spec` and
|
||||
`@callback` available in modules are handled by the equivalent
|
||||
@@ -8,20 +8,19 @@ defmodule Kernel.Typespec do
|
||||
|
||||
## Defining a type
|
||||
|
||||
@type type_name :: type
|
||||
@typep type_name :: type
|
||||
@opaque type_name :: type
|
||||
@type type_name :: type
|
||||
@typep type_name :: type
|
||||
@opaque type_name :: type
|
||||
|
||||
For more details, see documentation for deftype, deftypep and defopaque in
|
||||
Kernel.Typespec
|
||||
For more details, see documentation for `deftype`, `deftypep` and `defopaque`
|
||||
below.
|
||||
|
||||
## Defining a specification
|
||||
|
||||
@spec function_name(type, type) :: type
|
||||
@callback function_name(type, type) :: type
|
||||
@spec function_name(type, type) :: type
|
||||
@callback function_name(type, type) :: type
|
||||
|
||||
For more details, see documentation for defspec and defcallback in
|
||||
Kernel.Typespec
|
||||
For more details, see documentation for `defspec` and `defcallback` below.
|
||||
|
||||
## Types
|
||||
|
||||
@@ -30,9 +29,10 @@ defmodule Kernel.Typespec do
|
||||
|
||||
Most of the built-in types provided in Erlang (for example, `pid()`)
|
||||
are expressed the same way: `pid()` or simply `pid`. Parametrized types
|
||||
are also supported: `list(integer())` and so are remote types: `Enum.t`.
|
||||
are also supported (`list(integer())`) and so are remote types (`Enum.t`).
|
||||
|
||||
Certain data type shortcuts ([...], <<>> and {...}) are supported as well.
|
||||
Certain data type shortcuts (`[...]`, `<<>>` and `{...}`) are supported as
|
||||
well.
|
||||
|
||||
Main differences lie in how bit strings and functions are defined:
|
||||
|
||||
@@ -50,22 +50,16 @@ defmodule Kernel.Typespec do
|
||||
|
||||
Any anonymous function:
|
||||
|
||||
(fun(...) -> any)
|
||||
or
|
||||
((...) -> any)
|
||||
or
|
||||
(... -> any)
|
||||
|
||||
Anonymous function with arity of zero:
|
||||
|
||||
(fun() -> type)
|
||||
or
|
||||
(() -> type)
|
||||
|
||||
Anonymous function with some arity:
|
||||
|
||||
(fun(type, type) -> type)
|
||||
or
|
||||
((type, type) -> type)
|
||||
or
|
||||
(type, type -> type)
|
||||
@@ -87,7 +81,7 @@ defmodule Kernel.Typespec do
|
||||
|
||||
@doc """
|
||||
Defines a type.
|
||||
This macro is the one responsible to handle the attribute @type.
|
||||
This macro is the one responsible for handling the attribute `@type`.
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -102,7 +96,7 @@ defmodule Kernel.Typespec do
|
||||
|
||||
@doc """
|
||||
Defines an opaque type.
|
||||
This macro is the one responsible to handle the attribute @opaque.
|
||||
This macro is the one responsible for handling the attribute `@opaque`.
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -117,7 +111,7 @@ defmodule Kernel.Typespec do
|
||||
|
||||
@doc """
|
||||
Defines a private type.
|
||||
This macro is the one responsible to handle the attribute @typep.
|
||||
This macro is the one responsible for handling the attribute `@typep`.
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -132,7 +126,7 @@ defmodule Kernel.Typespec do
|
||||
|
||||
@doc """
|
||||
Defines a spec.
|
||||
This macro is the one responsible to handle the attribute @spec.
|
||||
This macro is the one responsible for handling the attribute `@spec`.
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -147,7 +141,7 @@ defmodule Kernel.Typespec do
|
||||
|
||||
@doc """
|
||||
Defines a callback.
|
||||
This macro is the one responsible to handle the attribute @callback.
|
||||
This macro is the one responsible for handling the attribute `@callback`.
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -194,7 +188,7 @@ defmodule Kernel.Typespec do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns true if the current module defines a given type
|
||||
Returns `true` if the current module defines a given type
|
||||
(private, opaque or not). This function is only available
|
||||
for modules being compiled.
|
||||
"""
|
||||
@@ -205,7 +199,7 @@ defmodule Kernel.Typespec do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns true if the current module defines a given spec.
|
||||
Returns `true` if the current module defines a given spec.
|
||||
This function is only available for modules being compiled.
|
||||
"""
|
||||
def defines_spec?(module, name, arity) do
|
||||
@@ -214,7 +208,7 @@ defmodule Kernel.Typespec do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns true if the current module defines a callback.
|
||||
Returns `true` if the current module defines a callback.
|
||||
This function is only available for modules being compiled.
|
||||
"""
|
||||
def defines_callback?(module, name, arity) do
|
||||
@@ -225,7 +219,7 @@ defmodule Kernel.Typespec do
|
||||
@doc """
|
||||
Converts a spec clause back to Elixir AST.
|
||||
"""
|
||||
def spec_to_ast(name, { :type, line, :fun, [{:type, _, :product, args},result] }) do
|
||||
def spec_to_ast(name, { :type, line, :fun, [{:type, _, :product, args}, result] }) do
|
||||
args = lc arg inlist args, do: typespec_to_ast(arg)
|
||||
{ :::, [line: line], [{ name, [line: line], args }, typespec_to_ast(result)] }
|
||||
end
|
||||
@@ -262,13 +256,13 @@ defmodule Kernel.Typespec do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns all types available from the beam.
|
||||
Returns all types available from the module's beam code.
|
||||
|
||||
It is returned as a list of tuples where the first
|
||||
element is the type (`:typep`, `:type` and `:opaque`).
|
||||
|
||||
The module has to have a corresponding beam file
|
||||
on the file system.
|
||||
The module has to have a corresponding beam file on the disk which can be
|
||||
located by the runtime system.
|
||||
"""
|
||||
def beam_types(module) do
|
||||
case abstract_code(module) do
|
||||
@@ -289,26 +283,26 @@ defmodule Kernel.Typespec do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns all specs available from the beam.
|
||||
Returns all specs available from the module's beam code.
|
||||
|
||||
It is returned as a list of tuples where the first
|
||||
element is spec name and arity and the second is the spec.
|
||||
|
||||
The module has to have a corresponding beam file
|
||||
on the file system.
|
||||
The module has to have a corresponding beam file on the disk which can be
|
||||
located by the runtime system.
|
||||
"""
|
||||
def beam_specs(module) do
|
||||
from_abstract_code(module, :spec)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns all callbacks available from the beam.
|
||||
Returns all callbacks available from the module's beam code.
|
||||
|
||||
It is returned as a list of tuples where the first
|
||||
element is spec name and arity and the second is the spec.
|
||||
|
||||
The module has to have a corresponding beam file
|
||||
on the file system.
|
||||
The module has to have a corresponding beam file on the disk which can be
|
||||
located by the runtime system.
|
||||
"""
|
||||
def beam_callbacks(module) do
|
||||
from_abstract_code(module, :callback)
|
||||
@@ -356,7 +350,7 @@ defmodule Kernel.Typespec do
|
||||
end
|
||||
|
||||
def deftype(_kind, other, _caller) do
|
||||
type_spec = Macro.to_binary(other)
|
||||
type_spec = Macro.to_string(other)
|
||||
raise ArgumentError, message: "invalid type specification #{type_spec}"
|
||||
end
|
||||
|
||||
@@ -397,7 +391,7 @@ defmodule Kernel.Typespec do
|
||||
end
|
||||
|
||||
def defspec(_type, other, _caller) do
|
||||
spec = Macro.to_binary(other)
|
||||
spec = Macro.to_string(other)
|
||||
raise ArgumentError, message: "invalid function type specification #{spec}"
|
||||
end
|
||||
|
||||
@@ -422,6 +416,17 @@ defmodule Kernel.Typespec do
|
||||
{ :{}, [line: line], args }
|
||||
end
|
||||
|
||||
defp typespec_to_ast({ :type, _line, :list, [arg] }) do
|
||||
case unpack_typespec_kw(arg, []) do
|
||||
{ :ok, ast } -> ast
|
||||
:error -> [typespec_to_ast(arg)]
|
||||
end
|
||||
end
|
||||
|
||||
defp typespec_to_ast({ :type, _line, :list, args }) do
|
||||
lc arg inlist args, do: typespec_to_ast(arg)
|
||||
end
|
||||
|
||||
defp typespec_to_ast({ :type, line, :binary, [arg1, arg2] }) do
|
||||
[arg1, arg2] = lc arg inlist [arg1, arg2], do: typespec_to_ast(arg)
|
||||
cond do
|
||||
@@ -440,17 +445,17 @@ defmodule Kernel.Typespec do
|
||||
fn(arg, expr) -> { :|, [line: line], [expr, arg] } end
|
||||
end
|
||||
|
||||
defp typespec_to_ast({ :type, line, :fun, [{:type, _, :product, args},result] }) do
|
||||
defp typespec_to_ast({ :type, line, :fun, [{:type, _, :product, args}, result] }) do
|
||||
args = lc arg inlist args, do: typespec_to_ast(arg)
|
||||
{ :"->", [line: line], [{args, typespec_to_ast(result)}] }
|
||||
{ :->, [line: line], [{args, [line: line], typespec_to_ast(result)}] }
|
||||
end
|
||||
|
||||
defp typespec_to_ast({ :type, line, :fun, [args, result] }) do
|
||||
{ :"->", [line: line], [{[typespec_to_ast(args)], typespec_to_ast(result)}] }
|
||||
{ :->, [line: line], [{[typespec_to_ast(args)], [line: line], typespec_to_ast(result)}] }
|
||||
end
|
||||
|
||||
defp typespec_to_ast({ :type, line, :fun, [] }) do
|
||||
typespec_to_ast({ :type, line, :fun, [{:type, line, :any}, {:type,line,:any, []} ] })
|
||||
typespec_to_ast({ :type, line, :fun, [{:type, line, :any}, {:type, line, :any, []} ] })
|
||||
end
|
||||
|
||||
defp typespec_to_ast({ :type, line, :range, [left, right] }) do
|
||||
@@ -464,16 +469,16 @@ defmodule Kernel.Typespec do
|
||||
|
||||
defp typespec_to_ast({ :var, line, var }) do
|
||||
var =
|
||||
case atom_to_binary(var) do
|
||||
<<"_", c :: [binary, size(1)], rest :: binary>> ->
|
||||
binary_to_atom("_#{String.downcase(c)}#{rest}")
|
||||
<<c :: [binary, size(1)], rest :: binary>> ->
|
||||
binary_to_atom("#{String.downcase(c)}#{rest}")
|
||||
end
|
||||
case atom_to_binary(var) do
|
||||
<<"_", c :: [binary, size(1)], rest :: binary>> ->
|
||||
binary_to_atom("_#{String.downcase(c)}#{rest}")
|
||||
<<c :: [binary, size(1)], rest :: binary>> ->
|
||||
binary_to_atom("#{String.downcase(c)}#{rest}")
|
||||
end
|
||||
{ var, line, nil }
|
||||
end
|
||||
|
||||
# special shortcut(s)
|
||||
# Special shortcut(s)
|
||||
defp typespec_to_ast({ :remote_type, line, [{:atom, _, :elixir}, {:atom, _, :char_list}, []] }) do
|
||||
typespec_to_ast({:type, line, :char_list, []})
|
||||
end
|
||||
@@ -482,7 +487,6 @@ defmodule Kernel.Typespec do
|
||||
typespec_to_ast({:type, line, :as_boolean, [arg]})
|
||||
end
|
||||
|
||||
|
||||
defp typespec_to_ast({ :remote_type, line, [mod, name, args] }) do
|
||||
args = lc arg inlist args, do: typespec_to_ast(arg)
|
||||
dot = { :., [line: line], [typespec_to_ast(mod), typespec_to_ast(name)] }
|
||||
@@ -523,14 +527,14 @@ defmodule Kernel.Typespec do
|
||||
end
|
||||
|
||||
# Handle unions
|
||||
defp typespec({ :|, meta, [_,_] } = exprs, vars, caller) do
|
||||
exprs = :lists.reverse(collect_union(exprs))
|
||||
defp typespec({ :|, meta, [_, _] } = exprs, vars, caller) do
|
||||
exprs = Enum.reverse(collect_union(exprs))
|
||||
union = lc e inlist exprs, do: typespec(e, vars, caller)
|
||||
{ :type, line(meta), :union, union }
|
||||
end
|
||||
|
||||
# Handle binaries
|
||||
defp typespec({:<<>>, meta, []}, _,_) do
|
||||
defp typespec({:<<>>, meta, []}, _, _) do
|
||||
{:type, line(meta), :binary, [{:integer, line(meta), 0}, {:integer, line(meta), 0}]}
|
||||
end
|
||||
|
||||
@@ -558,12 +562,13 @@ defmodule Kernel.Typespec do
|
||||
end
|
||||
|
||||
# Handle funs
|
||||
|
||||
defp typespec({:->, meta, [{[{:fun, _, arguments}], return}]}, vars, caller) when is_list(arguments) do
|
||||
typespec({:->, meta, [{arguments, return}]}, vars, caller)
|
||||
defp typespec({:->, meta, [{[{:fun, _, arguments}], cmeta, return}]}, vars, caller) when is_list(arguments) do
|
||||
IO.warn "(fun(...) -> ...) in typespecs is deprecated, please drop fun() and " <>
|
||||
"write (... -> ...) instead. #{Exception.format_stacktrace(caller.stacktrace)}"
|
||||
typespec({:->, meta, [{arguments, cmeta, return}]}, vars, caller)
|
||||
end
|
||||
|
||||
defp typespec({:->, meta, [{arguments, return}]}, vars, caller) when is_list(arguments) do
|
||||
defp typespec({:->, meta, [{arguments, _, return}]}, vars, caller) when is_list(arguments) do
|
||||
args = fn_args(meta, arguments, return, vars, caller)
|
||||
{ :type, line(meta), :fun, args }
|
||||
end
|
||||
@@ -609,7 +614,6 @@ defmodule Kernel.Typespec do
|
||||
end
|
||||
|
||||
# Handle blocks
|
||||
|
||||
defp typespec({:__block__, _meta, [arg]}, vars, caller) do
|
||||
typespec(arg, vars, caller)
|
||||
end
|
||||
@@ -665,8 +669,11 @@ defmodule Kernel.Typespec do
|
||||
typespec({ :nonempty_list, [], [spec] }, vars, caller)
|
||||
end
|
||||
|
||||
defp typespec(l, _, _) when is_list(l) do
|
||||
raise ArgumentError, message: "Unexpected list #{inspect l}"
|
||||
defp typespec([h|t] = l, vars, caller) do
|
||||
union = Enum.reduce(t, validate_kw(h, l), fn(x, acc) ->
|
||||
{ :|, [], [acc, validate_kw(x, l)] }
|
||||
end)
|
||||
typespec({ :list, [], [union] }, vars, caller)
|
||||
end
|
||||
|
||||
defp typespec(t, vars, caller) when is_tuple(t) do
|
||||
@@ -684,9 +691,14 @@ defmodule Kernel.Typespec do
|
||||
defp collect_union({ :|, _, [a, b] }), do: [b|collect_union(a)]
|
||||
defp collect_union(v), do: [v]
|
||||
|
||||
defp validate_kw({ key, _ } = t, _) when is_atom(key), do: t
|
||||
defp validate_kw(_, original) do
|
||||
raise ArgumentError, message: "unexpected list #{inspect original} in typespec"
|
||||
end
|
||||
|
||||
defp fn_args(meta, args, return, vars, caller) do
|
||||
case [fn_args(meta, args, vars, caller), typespec(return, vars, caller)] do
|
||||
[{:type,_,:any},{:type,_,:any,[]}] -> []
|
||||
[{:type, _, :any}, {:type, _, :any, []}] -> []
|
||||
x -> x
|
||||
end
|
||||
end
|
||||
@@ -703,4 +715,19 @@ defmodule Kernel.Typespec do
|
||||
defp variable({name, meta, _}) do
|
||||
{:var, line(meta), name}
|
||||
end
|
||||
|
||||
defp unpack_typespec_kw({ :type, _, :union, [
|
||||
next,
|
||||
{ :type, _, :tuple, [{ :atom, _, atom }, type] }
|
||||
] }, acc) do
|
||||
unpack_typespec_kw(next, [{atom, typespec_to_ast(type)}|acc])
|
||||
end
|
||||
|
||||
defp unpack_typespec_kw({ :type, _, :tuple, [{ :atom, _, atom }, type] }, acc) do
|
||||
{ :ok, [{atom, typespec_to_ast(type)}|acc] }
|
||||
end
|
||||
|
||||
defp unpack_typespec_kw(_, _acc) do
|
||||
:error
|
||||
end
|
||||
end
|
||||
|
||||
@@ -56,7 +56,7 @@ defmodule Keyword do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Keyword.new([{:b,1},{:a,2}])
|
||||
iex> Keyword.new([{:b, 1}, {:a, 2}])
|
||||
[a: 2, b: 1]
|
||||
|
||||
"""
|
||||
@@ -74,7 +74,7 @@ defmodule Keyword do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Keyword.new([:a, :b], fn (x) -> {x,x} end) |> Enum.sort
|
||||
iex> Keyword.new([:a, :b], fn (x) -> {x, x} end) |> Enum.sort
|
||||
[a: :a, b: :b]
|
||||
|
||||
"""
|
||||
@@ -114,15 +114,6 @@ defmodule Keyword do
|
||||
end
|
||||
end
|
||||
|
||||
@doc false
|
||||
def get!(keywords, key) when is_atom(key) do
|
||||
IO.write "[WARNING] Keyword.get! is deprecated, please use Keyword.fetch! instead\n#{Exception.format_stacktrace}"
|
||||
case :lists.keyfind(key, 1, keywords) do
|
||||
{ ^key, value } -> value
|
||||
false -> raise(KeyError, key: key)
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Fetchs the value for specific key and return it in a tuple.
|
||||
If the key does not exist, returns `:error`.
|
||||
@@ -152,6 +143,7 @@ defmodule Keyword do
|
||||
|
||||
iex> Keyword.fetch!([a: 1], :a)
|
||||
1
|
||||
|
||||
iex> Keyword.fetch!([a: 1], :b)
|
||||
** (KeyError) key not found: :b
|
||||
|
||||
@@ -274,7 +266,7 @@ defmodule Keyword do
|
||||
def put_new(keywords, key, value) when is_atom(key) do
|
||||
case :lists.keyfind(key, 1, keywords) do
|
||||
{ ^key, _ } -> keywords
|
||||
false -> [{key,value}|keywords]
|
||||
false -> [{key, value}|keywords]
|
||||
end
|
||||
end
|
||||
|
||||
@@ -357,6 +349,7 @@ defmodule Keyword do
|
||||
|
||||
iex> Keyword.update([a: 1], :a, &1 * 2)
|
||||
[a: 2]
|
||||
|
||||
iex> Keyword.update([a: 1], :b, &1 * 2)
|
||||
** (KeyError) key not found: :b
|
||||
|
||||
|
||||
+14
-18
@@ -10,12 +10,14 @@ defmodule List do
|
||||
first argument.
|
||||
"""
|
||||
|
||||
@compile :inline_list_funcs
|
||||
|
||||
@doc """
|
||||
Given a list of lists, concatenates the sublists into a single list.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> List.concat([[1,[2],3], [4], [5,6]])
|
||||
iex> List.concat([[1, [2], 3], [4], [5, 6]])
|
||||
[1,[2],3,4,5,6]
|
||||
|
||||
"""
|
||||
@@ -32,7 +34,7 @@ defmodule List do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> List.concat([1,2,3], [4,5,6])
|
||||
iex> List.concat([1, 2, 3], [4, 5, 6])
|
||||
[1,2,3,4,5,6]
|
||||
|
||||
"""
|
||||
@@ -47,7 +49,7 @@ defmodule List do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> List.delete([1,2,3], 1)
|
||||
iex> List.delete([1, 2, 3], 1)
|
||||
[2,3]
|
||||
|
||||
"""
|
||||
@@ -63,7 +65,7 @@ defmodule List do
|
||||
iex> List.duplicate("hello", 3)
|
||||
["hello","hello","hello"]
|
||||
|
||||
iex> List.duplicate([1,2], 2)
|
||||
iex> List.duplicate([1, 2], 2)
|
||||
[[1,2],[1,2]]
|
||||
"""
|
||||
def duplicate(elem, n) do
|
||||
@@ -77,10 +79,10 @@ defmodule List do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> List.flatten([1,[[2],3]])
|
||||
iex> List.flatten([1, [[2], 3]])
|
||||
[1,2,3]
|
||||
|
||||
iex> List.flatten([1,[[2],3]], [4,5])
|
||||
iex> List.flatten([1, [[2], 3]], [4, 5])
|
||||
[1,2,3,4,5]
|
||||
|
||||
"""
|
||||
@@ -98,10 +100,10 @@ defmodule List do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> List.foldl([5,5], 10, fn (x, acc) -> x + acc end)
|
||||
iex> List.foldl([5, 5], 10, fn (x, acc) -> x + acc end)
|
||||
20
|
||||
|
||||
iex> List.foldl([1,2,3,4], 0, fn (x, acc) -> x - acc end)
|
||||
iex> List.foldl([1, 2, 3, 4], 0, fn (x, acc) -> x - acc end)
|
||||
2
|
||||
|
||||
"""
|
||||
@@ -115,7 +117,7 @@ defmodule List do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> List.foldr([1,2,3,4], 0, fn (x, acc) -> x - acc end)
|
||||
iex> List.foldr([1, 2, 3, 4], 0, fn (x, acc) -> x - acc end)
|
||||
-2
|
||||
|
||||
"""
|
||||
@@ -142,12 +144,6 @@ defmodule List do
|
||||
:lists.last(list)
|
||||
end
|
||||
|
||||
@doc false
|
||||
def member?(list, term) do
|
||||
IO.write "[WARNING] List.member? is deprecated, please use Enum.member? instead\n#{Exception.format_stacktrace}"
|
||||
:lists.member(term, list)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Receives a list of tuples and returns the first tuple
|
||||
where the item at position `position` matches with the
|
||||
@@ -171,7 +167,7 @@ defmodule List do
|
||||
|
||||
@doc """
|
||||
Receives a list of tuples and returns true if there is
|
||||
a tuple where the item at position `posistion` matches
|
||||
a tuple where the item at position `position` matches
|
||||
with the given `item`.
|
||||
|
||||
## Examples
|
||||
@@ -221,7 +217,7 @@ defmodule List do
|
||||
|
||||
@doc """
|
||||
Receives a list of tuples and deletes the first tuple
|
||||
where the item at position `posistion` matches with the
|
||||
where the item at position `position` matches with the
|
||||
given `item`. Returns the new tuple.
|
||||
|
||||
## Examples
|
||||
@@ -247,7 +243,7 @@ defmodule List do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> List.wrap([1,2,3])
|
||||
iex> List.wrap([1, 2, 3])
|
||||
[1,2,3]
|
||||
|
||||
"""
|
||||
|
||||
@@ -34,20 +34,11 @@ defimpl List.Chars, for: Number do
|
||||
integer_to_list(thing)
|
||||
end
|
||||
|
||||
to_list = :proplists.get_value(:float_to_list,
|
||||
:proplists.get_value(:exports, :erlang.module_info, []))
|
||||
|
||||
if to_list == 2 do
|
||||
def to_char_list(thing) when thing > @limit do
|
||||
float_to_list(thing, scientific: @digits)
|
||||
end
|
||||
|
||||
def to_char_list(thing) do
|
||||
float_to_list(thing, compact: true, decimals: @digits)
|
||||
end
|
||||
else
|
||||
def to_char_list(thing) do
|
||||
float_to_list(thing)
|
||||
end
|
||||
def to_char_list(thing) when thing > @limit do
|
||||
float_to_list(thing, scientific: @digits)
|
||||
end
|
||||
end
|
||||
|
||||
def to_char_list(thing) do
|
||||
float_to_list(thing, compact: true, decimals: @digits)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -65,15 +65,6 @@ defmodule ListDict do
|
||||
end
|
||||
end
|
||||
|
||||
@doc false
|
||||
def get!(dict, key) do
|
||||
IO.write "[WARNING] Dict.get! and ListDict.get! are deprecated, please use Dict.fetch! and ListDict.fetch! instead\n#{Exception.format_stacktrace}"
|
||||
case :lists.keyfind(key, 1, dict) do
|
||||
{ ^key, value } -> value
|
||||
false -> raise(KeyError, key: key)
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns the value under key from the given
|
||||
dict in a tagged tuple, otherwise `:error`.
|
||||
@@ -118,7 +109,7 @@ defmodule ListDict do
|
||||
def put_new(dict, key, val) do
|
||||
case :lists.keyfind(key, 1, dict) do
|
||||
{ ^key, _ } -> dict
|
||||
false -> [{key,val}|dict]
|
||||
false -> [{key, val}|dict]
|
||||
end
|
||||
end
|
||||
|
||||
@@ -140,35 +131,21 @@ defmodule ListDict do
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Splits a dict into two dicts,
|
||||
one containing entries with key in the keys list,
|
||||
and another containing entries with key not in keys.
|
||||
Returns a 2-tuple of the new dicts.
|
||||
"""
|
||||
def split(dict, keys) do
|
||||
acc = { new(), new() }
|
||||
Enum.reduce dict, acc, fn({ k, v }, { take, drop }) ->
|
||||
if :lists.member(k, keys) do
|
||||
{ [{k,v}|take], drop }
|
||||
{ [{k, v}|take], drop }
|
||||
else
|
||||
{ take, [{k,v}|drop] }
|
||||
{ take, [{k, v}|drop] }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns a new dict with only the entries
|
||||
which key is in keys
|
||||
"""
|
||||
def take(dict, keys) do
|
||||
lc { k, _ } = tuple inlist dict, :lists.member(k, keys), do: tuple
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns a new dict with only the entries
|
||||
which key is not in keys
|
||||
"""
|
||||
def drop(dict, keys) do
|
||||
lc { k, _ } = tuple inlist dict, not :lists.member(k, keys), do: tuple
|
||||
end
|
||||
|
||||
+191
-127
@@ -1,4 +1,4 @@
|
||||
import Kernel, except: [to_binary: 1]
|
||||
import Kernel, except: [to_string: 1]
|
||||
|
||||
defmodule Macro do
|
||||
@moduledoc """
|
||||
@@ -14,10 +14,10 @@ defmodule Macro do
|
||||
:===, :!==,
|
||||
:==, :!=, :<=, :>=,
|
||||
:&&, :||, :<>, :++, :--, :**, ://, :::, :<-, :.., :|>, :=~,
|
||||
:<, :>,
|
||||
:<, :>, :->,
|
||||
:+, :-, :*, :/, :=, :|, :.,
|
||||
:and, :or, :xor, :when, :in, :inlist, :inbits,
|
||||
:<<<, :>>>, :|||, :&&&, :^^^, :~~~
|
||||
:<<<, :>>>, :|||, :&&&, :^^^
|
||||
]
|
||||
end
|
||||
|
||||
@@ -26,7 +26,7 @@ defmodule Macro do
|
||||
as a macro so it can be used in guard clauses.
|
||||
"""
|
||||
defmacro unary_ops do
|
||||
[:!, :@, :^, :not, :+, :-]
|
||||
[:!, :@, :^, :not, :+, :-, :~~~, :&]
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -42,8 +42,8 @@ defmodule Macro do
|
||||
|
||||
extract_args(quote do: foo) == { :foo, [] }
|
||||
extract_args(quote do: foo()) == { :foo, [] }
|
||||
extract_args(quote do: foo(1,2,3)) == { :foo, [1,2,3] }
|
||||
extract_args(quote do: 1.(1,2,3)) == :error
|
||||
extract_args(quote do: foo(1, 2, 3)) == { :foo, [1, 2, 3] }
|
||||
extract_args(quote do: 1.(1, 2, 3)) == :error
|
||||
|
||||
"""
|
||||
def extract_args(expr) do
|
||||
@@ -54,6 +54,10 @@ defmodule Macro do
|
||||
Recursively escapes a value so it can be inserted
|
||||
into a syntax tree.
|
||||
|
||||
One may pass `unquote: true` to `Macro.escape/2`
|
||||
which leaves unquote statements unescaped, effectively
|
||||
unquoting the contents on escape.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Macro.escape(:foo)
|
||||
@@ -62,14 +66,12 @@ defmodule Macro do
|
||||
iex> Macro.escape({ :a, :b, :c })
|
||||
{ :{}, [], [:a, :b, :c] }
|
||||
|
||||
"""
|
||||
def escape(expr) do
|
||||
:elixir_quote.escape(expr, false) |> elem(0)
|
||||
end
|
||||
iex> Macro.escape({ :unquote, [], [1] }, unquote: true)
|
||||
1
|
||||
|
||||
@doc false
|
||||
def escape_quoted(expr) do
|
||||
:elixir_quote.escape(expr, true) |> elem(0)
|
||||
"""
|
||||
def escape(expr, opts // []) do
|
||||
:elixir_quote.escape(expr, Keyword.get(opts, :unquote, false)) |> elem(0)
|
||||
end
|
||||
|
||||
@doc %B"""
|
||||
@@ -151,7 +153,7 @@ defmodule Macro do
|
||||
|
||||
Only tokens that are binaries are unescaped, all others are
|
||||
ignored. This function is useful when implementing your own
|
||||
sigils. Check the implementation of `Kernel.__b__`
|
||||
sigils. Check the implementation of `Kernel.sigil_b`
|
||||
for examples.
|
||||
"""
|
||||
def unescape_tokens(tokens) do
|
||||
@@ -166,112 +168,124 @@ defmodule Macro do
|
||||
:elixir_interpolation.unescape_tokens(tokens, map)
|
||||
end
|
||||
|
||||
@doc false
|
||||
def to_binary(tree) do
|
||||
IO.write "[WARNING] Macro.to_binary is deprecated, please use Macro.to_string instead\n#{Exception.format_stacktrace}"
|
||||
to_string(tree)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Converts the given expression to a binary.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Macro.to_binary(quote do: foo.bar(1, 2, 3))
|
||||
iex> Macro.to_string(quote do: foo.bar(1, 2, 3))
|
||||
"foo.bar(1, 2, 3)"
|
||||
|
||||
"""
|
||||
def to_binary(tree)
|
||||
def to_string(tree)
|
||||
|
||||
# Variables
|
||||
def to_binary({ var, _, atom }) when is_atom(atom) do
|
||||
def to_string({ var, _, atom }) when is_atom(atom) do
|
||||
atom_to_binary(var, :utf8)
|
||||
end
|
||||
|
||||
# Aliases
|
||||
def to_binary({ :__aliases__, _, refs }) do
|
||||
Enum.map_join(refs, ".", call_to_binary(&1))
|
||||
def to_string({ :__aliases__, _, refs }) do
|
||||
Enum.map_join(refs, ".", call_to_string(&1))
|
||||
end
|
||||
|
||||
# Blocks
|
||||
def to_binary({ :__block__, _, [expr] }) do
|
||||
to_binary(expr)
|
||||
def to_string({ :__block__, _, [expr] }) do
|
||||
to_string(expr)
|
||||
end
|
||||
|
||||
def to_binary({ :__block__, _, _ } = expr) do
|
||||
block = adjust_new_lines block_to_binary(expr), "\n "
|
||||
def to_string({ :__block__, _, _ } = expr) do
|
||||
block = adjust_new_lines block_to_string(expr), "\n "
|
||||
"(\n " <> block <> "\n)"
|
||||
end
|
||||
|
||||
# Bits containers
|
||||
def to_binary({ :<<>>, _, args }) do
|
||||
"<<" <> Enum.map_join(args, ", ", to_binary(&1)) <> ">>"
|
||||
def to_string({ :<<>>, _, args }) do
|
||||
"<<" <> Enum.map_join(args, ", ", to_string(&1)) <> ">>"
|
||||
end
|
||||
|
||||
# Tuple containers
|
||||
def to_binary({ :{}, _, args }) do
|
||||
"{" <> Enum.map_join(args, ", ", to_binary(&1)) <> "}"
|
||||
def to_string({ :{}, _, args }) do
|
||||
"{" <> Enum.map_join(args, ", ", to_string(&1)) <> "}"
|
||||
end
|
||||
|
||||
# List containers
|
||||
def to_binary({ :[], _, args }) do
|
||||
"[" <> Enum.map_join(args, ", ", to_binary(&1)) <> "]"
|
||||
def to_string({ :[], _, args }) do
|
||||
"[" <> Enum.map_join(args, ", ", to_string(&1)) <> "]"
|
||||
end
|
||||
|
||||
# Fn keyword
|
||||
def to_binary({ :fn, _, [[do: { :->, _, [{_,tuple}] } = arrow]] })
|
||||
def to_string({ :fn, _, [[do: { :->, _, [{_, _, tuple}] } = arrow]] })
|
||||
when not is_tuple(tuple) or elem(tuple, 0) != :__block__ do
|
||||
"fn " <> arrow_to_binary(arrow) <> " end"
|
||||
"fn " <> arrow_to_string(arrow) <> " end"
|
||||
end
|
||||
|
||||
def to_binary({ :fn, _, [[do: { :->, _, [_] } = block]] }) do
|
||||
"fn " <> block_to_binary(block) <> "\nend"
|
||||
def to_string({ :fn, _, [[do: { :->, _, [_] } = block]] }) do
|
||||
"fn " <> block_to_string(block) <> "\nend"
|
||||
end
|
||||
|
||||
def to_binary({ :fn, _, [[do: block]] }) do
|
||||
block = adjust_new_lines block_to_binary(block), "\n "
|
||||
def to_string({ :fn, _, [[do: block]] }) do
|
||||
block = adjust_new_lines block_to_string(block), "\n "
|
||||
"fn\n " <> block <> "\nend"
|
||||
end
|
||||
|
||||
# Partial call
|
||||
def to_binary({ :&, _, [num] }) do
|
||||
def to_string({ :&, _, [num] }) do
|
||||
"&#{num}"
|
||||
end
|
||||
|
||||
# left -> right
|
||||
def to_binary({ :->, _, _ } = arrow) do
|
||||
"(" <> arrow_to_binary(arrow, true) <> ")"
|
||||
def to_string({ :->, _, _ } = arrow) do
|
||||
"(" <> arrow_to_string(arrow, true) <> ")"
|
||||
end
|
||||
|
||||
# Binary ops
|
||||
def to_binary({ op, _, [left, right] }) when op in binary_ops do
|
||||
op_to_binary(left) <> " #{op} " <> op_to_binary(right)
|
||||
def to_string({ op, _, [left, right] }) when op in binary_ops do
|
||||
op_to_string(left) <> " #{op} " <> op_to_string(right)
|
||||
end
|
||||
|
||||
# Splat when
|
||||
def to_string({ :when, _, args }) do
|
||||
{ left, right } = :elixir_tree_helpers.split_last(args)
|
||||
"(" <> Enum.map_join(left, ", ", to_string(&1)) <> ") when " <> to_string(right)
|
||||
end
|
||||
|
||||
# Unary ops
|
||||
def to_binary({ :not, _, [arg] }) do
|
||||
"not " <> to_binary(arg)
|
||||
def to_string({ :not, _, [arg] }) do
|
||||
"not " <> to_string(arg)
|
||||
end
|
||||
|
||||
def to_binary({ op, _, [arg] }) when op in unary_ops do
|
||||
atom_to_binary(op, :utf8) <> to_binary(arg)
|
||||
def to_string({ op, _, [arg] }) when op in unary_ops do
|
||||
atom_to_binary(op, :utf8) <> to_string(arg)
|
||||
end
|
||||
|
||||
# All other calls
|
||||
def to_binary({ target, _, args }) when is_list(args) do
|
||||
def to_string({ target, _, args }) when is_list(args) do
|
||||
{ list, last } = :elixir_tree_helpers.split_last(args)
|
||||
case is_kw_blocks?(last) do
|
||||
true -> call_to_binary_with_args(target, list) <> kw_blocks_to_binary(last)
|
||||
false -> call_to_binary_with_args(target, args)
|
||||
true -> call_to_string_with_args(target, list) <> kw_blocks_to_string(last)
|
||||
false -> call_to_string_with_args(target, args)
|
||||
end
|
||||
end
|
||||
|
||||
# Two-item tuples
|
||||
def to_binary({ left, right }) do
|
||||
to_binary({ :{}, [], [left, right] })
|
||||
def to_string({ left, right }) do
|
||||
to_string({ :{}, [], [left, right] })
|
||||
end
|
||||
|
||||
# Lists
|
||||
def to_binary(list) when is_list(list) do
|
||||
to_binary({ :[], [], list })
|
||||
def to_string(list) when is_list(list) do
|
||||
to_string({ :[], [], list })
|
||||
end
|
||||
|
||||
# All other structures
|
||||
def to_binary(other), do: inspect(other, raw: true)
|
||||
def to_string(other), do: inspect(other, raw: true)
|
||||
|
||||
# Block keywords
|
||||
defmacrop kw_keywords, do: [:do, :catch, :rescue, :after, :else]
|
||||
@@ -281,56 +295,56 @@ defmodule Macro do
|
||||
end
|
||||
defp is_kw_blocks?(_), do: false
|
||||
|
||||
defp module_to_binary(atom) when is_atom(atom), do: inspect(atom, raw: true)
|
||||
defp module_to_binary(other), do: call_to_binary(other)
|
||||
defp module_to_string(atom) when is_atom(atom), do: inspect(atom, raw: true)
|
||||
defp module_to_string(other), do: call_to_string(other)
|
||||
|
||||
defp call_to_binary(atom) when is_atom(atom), do: atom_to_binary(atom, :utf8)
|
||||
defp call_to_binary({ :., _, [arg] }), do: module_to_binary(arg) <> "."
|
||||
defp call_to_binary({ :., _, [left, right] }), do: module_to_binary(left) <> "." <> call_to_binary(right)
|
||||
defp call_to_binary(other), do: to_binary(other)
|
||||
defp call_to_string(atom) when is_atom(atom), do: atom_to_binary(atom, :utf8)
|
||||
defp call_to_string({ :., _, [arg] }), do: module_to_string(arg) <> "."
|
||||
defp call_to_string({ :., _, [left, right] }), do: module_to_string(left) <> "." <> call_to_string(right)
|
||||
defp call_to_string(other), do: to_string(other)
|
||||
|
||||
defp call_to_binary_with_args(target, args) do
|
||||
args = Enum.map_join(args, ", ", to_binary(&1))
|
||||
call_to_binary(target) <> "(" <> args <> ")"
|
||||
defp call_to_string_with_args(target, args) do
|
||||
args = Enum.map_join(args, ", ", to_string(&1))
|
||||
call_to_string(target) <> "(" <> args <> ")"
|
||||
end
|
||||
|
||||
defp kw_blocks_to_binary(kw) do
|
||||
defp kw_blocks_to_string(kw) do
|
||||
Enum.reduce(kw_keywords, " ", fn(x, acc) ->
|
||||
case Keyword.has_key?(kw, x) do
|
||||
true -> acc <> kw_block_to_binary(x, Keyword.get(kw, x))
|
||||
true -> acc <> kw_block_to_string(x, Keyword.get(kw, x))
|
||||
false -> acc
|
||||
end
|
||||
end) <> "end"
|
||||
end
|
||||
|
||||
defp kw_block_to_binary(key, value) do
|
||||
block = adjust_new_lines block_to_binary(value), "\n "
|
||||
defp kw_block_to_string(key, value) do
|
||||
block = adjust_new_lines block_to_string(value), "\n "
|
||||
atom_to_binary(key, :utf8) <> "\n " <> block <> "\n"
|
||||
end
|
||||
|
||||
defp block_to_binary({ :->, _, exprs }) do
|
||||
Enum.map_join(exprs, "\n", fn({ left, right }) ->
|
||||
defp block_to_string({ :->, _, exprs }) do
|
||||
Enum.map_join(exprs, "\n", fn({ left, _, right }) ->
|
||||
left = comma_join_or_empty_paren(left, false)
|
||||
left <> "->\n " <> adjust_new_lines block_to_binary(right), "\n "
|
||||
left <> "->\n " <> adjust_new_lines block_to_string(right), "\n "
|
||||
end)
|
||||
end
|
||||
|
||||
defp block_to_binary({ :__block__, _, exprs }) do
|
||||
Enum.map_join(exprs, "\n", to_binary(&1))
|
||||
defp block_to_string({ :__block__, _, exprs }) do
|
||||
Enum.map_join(exprs, "\n", to_string(&1))
|
||||
end
|
||||
|
||||
defp block_to_binary(other), do: to_binary(other)
|
||||
defp block_to_string(other), do: to_string(other)
|
||||
|
||||
defp op_to_binary({ op, _, [_, _] } = expr) when op in binary_ops do
|
||||
"(" <> to_binary(expr) <> ")"
|
||||
defp op_to_string({ op, _, [_, _] } = expr) when op in binary_ops do
|
||||
"(" <> to_string(expr) <> ")"
|
||||
end
|
||||
|
||||
defp op_to_binary(expr), do: to_binary(expr)
|
||||
defp op_to_string(expr), do: to_string(expr)
|
||||
|
||||
defp arrow_to_binary({ :->, _, pairs }, paren // false) do
|
||||
Enum.map_join(pairs, "; ", fn({ left, right }) ->
|
||||
defp arrow_to_string({ :->, _, pairs }, paren // false) do
|
||||
Enum.map_join(pairs, "; ", fn({ left, _, right }) ->
|
||||
left = comma_join_or_empty_paren(left, paren)
|
||||
left <> "-> " <> to_binary(right)
|
||||
left <> "-> " <> to_string(right)
|
||||
end)
|
||||
end
|
||||
|
||||
@@ -338,7 +352,7 @@ defmodule Macro do
|
||||
defp comma_join_or_empty_paren([], false), do: ""
|
||||
|
||||
defp comma_join_or_empty_paren(left, _) do
|
||||
Enum.map_join(left, ", ", to_binary(&1)) <> " "
|
||||
Enum.map_join(left, ", ", to_string(&1)) <> " "
|
||||
end
|
||||
|
||||
defp adjust_new_lines(block, replacement) do
|
||||
@@ -351,32 +365,18 @@ defmodule Macro do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Receives an expression representation and expands it. The following
|
||||
contents are expanded:
|
||||
Receives a AST node and expands it once. The following contents are expanded:
|
||||
|
||||
* Macros (local or remote);
|
||||
* Aliases are expanded (if possible) and return atoms;
|
||||
* All pseudo-variables (__FILE__, __MODULE__, etc);
|
||||
* Module attributes reader (@foo);
|
||||
|
||||
In case the expression cannot be expanded, it returns the expression itself.
|
||||
|
||||
Notice that `Macro.expand` is not recursive and it does not
|
||||
expand child expressions. In this example
|
||||
|
||||
Macro.expand(quote(do: !some_macro), __ENV__)
|
||||
|
||||
`!some_macro` will expand to something like:
|
||||
|
||||
case some_macro do
|
||||
false -> true
|
||||
nil -> true
|
||||
_ -> false
|
||||
end
|
||||
|
||||
Notice that the `!` operator is a macro that expands to a case.
|
||||
Even though `some_macro` is also a macro, it is not expanded
|
||||
because it is a child expression given to `!` as argument.
|
||||
In case the expression cannot be expanded, it returns the expression
|
||||
itself. Notice that `Macro.expand_once/2` performs the expansion just
|
||||
once and it is not recursive. Check `Macro.expand/2` for expansion
|
||||
until the node no longer represents a macro and `Macro.expand_all/2`
|
||||
for recursive expansion.
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -439,46 +439,50 @@ defmodule Macro do
|
||||
end
|
||||
|
||||
"""
|
||||
def expand(aliases, env) do
|
||||
expand(aliases, env, nil)
|
||||
def expand_once(aliases, env) do
|
||||
expand_once(aliases, env, nil) |> elem(0)
|
||||
end
|
||||
|
||||
defp expand({ :__aliases__, _, _ } = original, env, cache) do
|
||||
case :elixir_aliases.expand(original, env.aliases, []) do
|
||||
atom when is_atom(atom) -> atom
|
||||
defp expand_once({ :__aliases__, _, _ } = original, env, cache) do
|
||||
case :elixir_aliases.expand(original, env.aliases, env.macro_aliases) do
|
||||
atom when is_atom(atom) -> { atom, true, cache }
|
||||
aliases ->
|
||||
aliases = lc alias inlist aliases, do: expand(alias, env, cache)
|
||||
aliases = lc alias inlist aliases, do: (expand_once(alias, env, cache) |> elem(0))
|
||||
|
||||
case :lists.all(is_atom(&1), aliases) do
|
||||
true -> :elixir_aliases.concat(aliases)
|
||||
false -> original
|
||||
true -> { :elixir_aliases.concat(aliases), true, cache }
|
||||
false -> { original, false, cache }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Expand @ calls
|
||||
defp expand({ :@, _, [{ name, _, args }] } = original, env, _cache) when is_atom(args) or args == [] do
|
||||
defp expand_once({ :@, _, [{ name, _, args }] } = original, env, cache) when is_atom(args) or args == [] do
|
||||
case (module = env.module) && Module.open?(module) do
|
||||
true -> Module.get_attribute(module, name)
|
||||
false -> original
|
||||
true -> { Module.get_attribute(module, name), true, cache }
|
||||
false -> { original, false, cache }
|
||||
end
|
||||
end
|
||||
|
||||
# Expand pseudo-variables
|
||||
defp expand({ :__MODULE__, _, atom }, env, _cache) when is_atom(atom), do: env.module
|
||||
defp expand({ :__FILE__, _, atom }, env, _cache) when is_atom(atom), do: env.file
|
||||
defp expand({ :__DIR__, _, atom }, env, _cache) when is_atom(atom), do: :filename.dirname(env.file)
|
||||
defp expand({ :__ENV__, _, atom }, env, _cache) when is_atom(atom), do: env
|
||||
defp expand_once({ :__MODULE__, _, atom }, env, cache) when is_atom(atom),
|
||||
do: { env.module, true, cache }
|
||||
defp expand_once({ :__FILE__, _, atom }, env, cache) when is_atom(atom),
|
||||
do: { env.file, true, cache }
|
||||
defp expand_once({ :__DIR__, _, atom }, env, cache) when is_atom(atom),
|
||||
do: { :filename.dirname(env.file), true, cache }
|
||||
defp expand_once({ :__ENV__, _, atom }, env, cache) when is_atom(atom),
|
||||
do: { env, true, cache }
|
||||
|
||||
# Expand possible macro import invocation
|
||||
defp expand({ atom, line, args } = original, env, cache) when is_atom(atom) do
|
||||
defp expand_once({ atom, line, args } = original, env, cache) when is_atom(atom) do
|
||||
args = case is_atom(args) do
|
||||
true -> []
|
||||
false -> args
|
||||
end
|
||||
|
||||
case not is_partial?(args) do
|
||||
false -> original
|
||||
false -> { original, false, cache }
|
||||
true ->
|
||||
module = env.module
|
||||
|
||||
@@ -488,43 +492,103 @@ defmodule Macro do
|
||||
[]
|
||||
end
|
||||
|
||||
cache = to_erl_env(env, cache)
|
||||
expand = :elixir_dispatch.expand_import(line, { atom, length(args) }, args,
|
||||
env.module, extra, to_erl_env(env, cache))
|
||||
env.module, extra, cache)
|
||||
|
||||
case expand do
|
||||
{ :ok, _, expanded } -> expanded
|
||||
{ :error, _ } -> original
|
||||
{ :ok, _, expanded } -> { expanded, true, cache }
|
||||
{ :error, _ } -> { original, false, cache }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Expand possible macro require invocation
|
||||
defp expand({ { :., _, [left, right] }, line, args } = original, env, cache) when is_atom(right) do
|
||||
receiver = expand(left, env)
|
||||
defp expand_once({ { :., _, [left, right] }, line, args } = original, env, cache) when is_atom(right) do
|
||||
{ receiver, _, _ } = expand_once(left, env, cache)
|
||||
|
||||
case is_atom(receiver) and not is_partial?(args) do
|
||||
false -> original
|
||||
false -> { original, false, cache }
|
||||
true ->
|
||||
cache = to_erl_env(env, cache)
|
||||
expand = :elixir_dispatch.expand_require(line, receiver, { right, length(args) },
|
||||
args, env.module, to_erl_env(env, cache))
|
||||
args, env.module, cache)
|
||||
|
||||
case expand do
|
||||
{ :ok, _receiver, expanded } -> expanded
|
||||
{ :error, _ } -> original
|
||||
{ :ok, _receiver, expanded } -> { expanded, true, cache }
|
||||
{ :error, _ } -> { original, false, cache }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Anything else is just returned
|
||||
defp expand(other, _env, _cache), do: other
|
||||
defp expand_once(other, _env, cache), do: { other, false, cache }
|
||||
|
||||
defp to_erl_env(env, nil), do: :elixir_scope.to_erl_env(env)
|
||||
defp to_erl_env(_env, cache), do: cache
|
||||
|
||||
## Helpers
|
||||
|
||||
defp is_partial?(args) do
|
||||
:lists.any(match?({ :&, _, [_] }, &1), args)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Receives a AST node and expands it until it no longer represents
|
||||
a macro. Check `Macro.expand_once/2` for more information on how
|
||||
expansion works and `Macro.expand_all/2` for recursive expansion.
|
||||
"""
|
||||
def expand(tree, env) do
|
||||
expand(tree, env, nil) |> elem(0)
|
||||
end
|
||||
|
||||
@doc false # Used internally by Elixir
|
||||
def expand(tree, env, cache) do
|
||||
expand_until({ tree, true, cache }, env)
|
||||
end
|
||||
|
||||
defp expand_until({ tree, true, cache }, env) do
|
||||
expand_until(expand_once(tree, env, cache), env)
|
||||
end
|
||||
|
||||
defp expand_until({ tree, false, cache }, _env) do
|
||||
{ tree, cache }
|
||||
end
|
||||
|
||||
@doc """
|
||||
Receives a AST node and expands it until it no longer represents
|
||||
a macro. Then it expands all of its children recursively.
|
||||
|
||||
Check `Macro.expand_once/2` for more information on how expansion
|
||||
works.
|
||||
"""
|
||||
def expand_all(tree, env) do
|
||||
expand_all(tree, env, nil) |> elem(0)
|
||||
end
|
||||
|
||||
@doc false # Used internally by Elixir
|
||||
def expand_all(tree, env, cache) do
|
||||
expand_all_until(expand(tree, env, cache), env)
|
||||
end
|
||||
|
||||
defp expand_all_until({ { left, meta, right }, cache }, env) do
|
||||
{ left, cache } = expand_all(left, env, cache)
|
||||
{ right, cache } = expand_all(right, env, cache)
|
||||
{ { left, meta, right }, cache }
|
||||
end
|
||||
|
||||
defp expand_all_until({ { left, right }, cache }, env) do
|
||||
{ left, cache } = expand_all(left, env, cache)
|
||||
{ right, cache } = expand_all(right, env, cache)
|
||||
{ { left, right }, cache }
|
||||
end
|
||||
|
||||
defp expand_all_until({ list, cache }, env) when is_list(list) do
|
||||
:lists.mapfoldl(expand_all(&1, env, &2), cache, list)
|
||||
end
|
||||
|
||||
defp expand_all_until({ other, cache }, _env) do
|
||||
{ other, cache }
|
||||
end
|
||||
|
||||
@doc """
|
||||
Recurs the quoted expression checking if all sub terms are
|
||||
safe (i.e. they represented data structured and don't actually
|
||||
|
||||
@@ -20,6 +20,7 @@ defmodule Macro.Env do
|
||||
* `functions` - a list of functions imported from each module
|
||||
* `macros` - a list of macros imported from each module
|
||||
* `context_modules` - a list of modules defined in the current context
|
||||
* `macro_aliases` - a list of aliases defined inside the current macro
|
||||
"""
|
||||
|
||||
@type name_arity :: { atom, non_neg_integer }
|
||||
@@ -32,12 +33,13 @@ defmodule Macro.Env do
|
||||
@type macros :: [{ module, [name_arity] }]
|
||||
@type context_modules :: [module]
|
||||
|
||||
fields = [:module, :file, :line, :function, :aliases,
|
||||
:context, :requires, :functions, :macros, :context_modules]
|
||||
fields = [:module, :file, :line, :function, :aliases, :context, :requires,
|
||||
:functions, :macros, :context_modules, :macro_aliases]
|
||||
|
||||
types = quote do: [module: module, file: file, line: line,
|
||||
function: name_arity, aliases: aliases, requires: requires,
|
||||
functions: functions, macros: macros, context_modules: context_modules]
|
||||
functions: functions, macros: macros, context_modules: context_modules,
|
||||
macro_aliases: aliases]
|
||||
|
||||
Record.deffunctions(fields, __MODULE__)
|
||||
Record.deftypes(fields, types, __MODULE__)
|
||||
|
||||
+344
-50
@@ -1,22 +1,300 @@
|
||||
defmodule Module do
|
||||
require :ets, as: ETS
|
||||
|
||||
defmacrop is_env(env) do
|
||||
quote do
|
||||
is_tuple(unquote(env)) and size(unquote(env)) > 1 and elem(unquote(env), 0) == Macro.Env
|
||||
end
|
||||
end
|
||||
|
||||
@moduledoc """
|
||||
@moduledoc %B'''
|
||||
This module provides many functions to deal with modules during
|
||||
compilation time. It allows a developer to dynamically attach
|
||||
documentation, add, delete and register attributes and so forth.
|
||||
|
||||
After the module is compiled, using many of the functions in
|
||||
After a module is compiled, using many of the functions in
|
||||
this module will raise errors, since it is out of their purpose
|
||||
to inspect runtime data. Most of the runtime data can be inspected
|
||||
via the `__info__(attr)` function attached to each compiled module.
|
||||
|
||||
## Module attributes
|
||||
|
||||
Each module can be decorated with one or more attributes. The following ones
|
||||
are currently defined by Elixir:
|
||||
|
||||
* `@after_compile`
|
||||
|
||||
A hook that will be invoked right after the current module is compiled.
|
||||
|
||||
Accepts a module or a tuple `{ <module>, <function atom> }`. The function
|
||||
must take two arguments: the module environment and its bytecode.
|
||||
When just a module is provided, the function is assumed to be
|
||||
`__after_compile__/2`.
|
||||
|
||||
**Example**
|
||||
|
||||
defmodule M do
|
||||
@after_compile __MODULE__
|
||||
|
||||
def __after_compile__(env, _bytecode) do
|
||||
IO.inspect env
|
||||
end
|
||||
end
|
||||
|
||||
* `@before_compile`
|
||||
|
||||
A hook that will be invoked before the module is compiled.
|
||||
|
||||
Accepts a module or a tuple `{ <module>, <function/macro atom> }`. The
|
||||
function/macro must take one argument: the module environment. If it's a
|
||||
macro, its returned value will be injected at the end of the module definition
|
||||
before the compilation starts.
|
||||
|
||||
When just a module is provided, the function/macro is assumed to be
|
||||
`__before_compile__/1`.
|
||||
|
||||
**Example**
|
||||
|
||||
defmodule M do
|
||||
@before_compile __MODULE__
|
||||
|
||||
defmacro __before_compile__(_env) do
|
||||
quote do
|
||||
def hello, do: "world"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
* `@behaviour` (notice the british spelling)
|
||||
|
||||
Specify an OTP or user-defined behaviour.
|
||||
|
||||
**Example**
|
||||
|
||||
defmodule M do
|
||||
@behaviour gen_event
|
||||
|
||||
# ...
|
||||
end
|
||||
|
||||
* `@compile`
|
||||
|
||||
Define options for module compilation that are passed to the Erlang
|
||||
compiler.
|
||||
|
||||
Accepts an atom, a tuple, or a list of atoms and tuples.
|
||||
|
||||
See http://www.erlang.org/doc/man/compile.html for the list of supported
|
||||
options.
|
||||
|
||||
**Example**
|
||||
|
||||
defmodule M do
|
||||
@compile { :inline, myfun: 1 }
|
||||
|
||||
def myfun(arg) do
|
||||
to_binary(arg)
|
||||
end
|
||||
end
|
||||
|
||||
* `@doc`
|
||||
|
||||
Provide documentation for the function or macro that follows the
|
||||
attribute.
|
||||
|
||||
Accepts a string (often a heredoc) or `false` where `@doc false` will
|
||||
make the function/macro invisible to the documentation extraction tools
|
||||
like ExDoc.
|
||||
|
||||
Can be invoked more than once.
|
||||
|
||||
**Example**
|
||||
|
||||
defmodule M do
|
||||
@doc "Hello world"
|
||||
def hello do
|
||||
"world"
|
||||
end
|
||||
|
||||
@doc """
|
||||
Sum.
|
||||
"""
|
||||
def sum(a, b) do
|
||||
a + b
|
||||
end
|
||||
end
|
||||
|
||||
* `@file`
|
||||
|
||||
Change the filename used in stacktraces for the function or macro that
|
||||
follows the attribute.
|
||||
|
||||
Accepts a string. Can be used more than once.
|
||||
|
||||
**Example**
|
||||
|
||||
defmodule M do
|
||||
@doc "Hello world"
|
||||
@file "hello.ex"
|
||||
def hello do
|
||||
"world"
|
||||
end
|
||||
end
|
||||
|
||||
* `@moduledoc`
|
||||
|
||||
Provide documentation for the current module.
|
||||
|
||||
Accepts a string (which is often a heredoc) or `false` where
|
||||
`@moduledoc false` will make the module invisible to the
|
||||
documentation extraction tools like ExDoc.
|
||||
|
||||
**Example**
|
||||
|
||||
defmodule M do
|
||||
@moduledoc """
|
||||
A very useful module
|
||||
"""
|
||||
end
|
||||
|
||||
|
||||
* `@on_definition`
|
||||
|
||||
A hook that will be invoked after each function or macro in the current
|
||||
module is defined. This makes it easy to annotate and customize
|
||||
functions.
|
||||
|
||||
Accepts a module or a tuple `{ <module>, <function atom> }`. The function
|
||||
must take 6 arguments:
|
||||
|
||||
- the module environment
|
||||
- kind: `:def`, `:defp`, `:defmacro`, or `:defmacrop`
|
||||
- function/macro name
|
||||
- list of quoted arguments
|
||||
- list of quoted guards
|
||||
- quoted function body
|
||||
|
||||
If the function/macro being defined has multiple clauses, the hook will
|
||||
be called for each clause.
|
||||
|
||||
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.
|
||||
|
||||
**Example**
|
||||
|
||||
defmodule H do
|
||||
def on_def(_env, kind, name, args, guards, body) do
|
||||
IO.puts "Defining #{kind} named #{name} with args:"
|
||||
IO.inspect args
|
||||
IO.puts "and guards"
|
||||
IO.inspect guards
|
||||
IO.puts "and body"
|
||||
IO.puts Macro.to_string(body)
|
||||
end
|
||||
end
|
||||
|
||||
defmodule M do
|
||||
@on_definition { H, :on_def }
|
||||
|
||||
def hello(arg) when is_binary(arg) or is_list(arg) do
|
||||
"Hello" <> to_binary(arg)
|
||||
end
|
||||
|
||||
def hello(_) do
|
||||
:ok
|
||||
end
|
||||
end
|
||||
|
||||
* `@on_load`
|
||||
|
||||
A hook that will be invoked whenever the module is loaded.
|
||||
|
||||
Accepts a function atom of a function in the current module. The function
|
||||
must have arity 0 (no arguments) and has to return `:ok`, otherwise the
|
||||
loading of the module will be aborted.
|
||||
|
||||
**Example**
|
||||
|
||||
defmodule M do
|
||||
@on_load :load_check
|
||||
|
||||
def load_check do
|
||||
if some_condition() do
|
||||
:ok
|
||||
else
|
||||
nil
|
||||
end
|
||||
end
|
||||
|
||||
def some_condition do
|
||||
false
|
||||
end
|
||||
end
|
||||
|
||||
* `@vsn`
|
||||
|
||||
Specify the module version. Accepts any valid Elixir value.
|
||||
|
||||
**Example**
|
||||
|
||||
defmodule M do
|
||||
@vsn "1.0"
|
||||
end
|
||||
|
||||
The following attributes are part of typespecs and are also reserved by
|
||||
Elixir (see `Kernel.Typespec` for more information about typespecs):
|
||||
|
||||
* `@type` - defines a type to be used in `@spec`
|
||||
* `@typep` - defines a private type to be used in `@spec`
|
||||
* `@opaque` - defines an opaque type to be used in `@spec`
|
||||
* `@spec` - provides a specification for a function
|
||||
* `@callback` - provides a specification for the behavior callback
|
||||
|
||||
In addition to the built-in attributes outlined above, custom attributes may
|
||||
also be added. A custom attribute is any valid identifier prefixed with an
|
||||
`@` and followed by a valid Elixir value:
|
||||
|
||||
defmodule M do
|
||||
@custom_attr [some: "stuff"]
|
||||
end
|
||||
|
||||
For more advanced options available when defining custom attributes, see
|
||||
`register_attribute/3`.
|
||||
|
||||
## Runtime information about a module
|
||||
|
||||
It is possible to query a module at runtime to find out which functions and
|
||||
macros it defines, extract its docstrings, etc. See `__info__/1`.
|
||||
|
||||
'''
|
||||
|
||||
@doc """
|
||||
Provides runtime information about functions and macros defined by the
|
||||
module, enables docstring extraction, etc.
|
||||
|
||||
Each module gets an `__info__/1` function when it's compiled. The function
|
||||
takes one of the following atoms:
|
||||
|
||||
* `:functions` - keyword list of public functions along with their arities
|
||||
|
||||
* `:macros` - keyword list of public macros along with their arities
|
||||
|
||||
* `:docs` - list of all docstrings attached to functions and macros
|
||||
using the `@doc` attribute
|
||||
|
||||
* `:moduledoc` - tuple `{ <line>, <doc> }` where `line` is the line on
|
||||
which module definition starts and `doc` is the string
|
||||
attached to the module using the `@moduledoc` attribute
|
||||
|
||||
* `:module` - module name (`Module == Module.__info__(:module)`)
|
||||
|
||||
In addition to the above, you may also pass to `__info__/1` any atom supported
|
||||
by Erlang's `module_info` function which also gets defined for each compiled
|
||||
module. See http://erlang.org/doc/reference_manual/modules.html#id74571 for
|
||||
more information.
|
||||
"""
|
||||
def __info__(kind)
|
||||
|
||||
@doc """
|
||||
Check if a module is open, i.e. it is currently being defined
|
||||
@@ -24,14 +302,14 @@ defmodule Module do
|
||||
"""
|
||||
def open?(module) do
|
||||
table = data_table_for(module)
|
||||
table == ETS.info(table, :name)
|
||||
table == :ets.info(table, :name)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Evalutes the quotes contents in the given module context.
|
||||
Evaluates the quoted contents in the given module's context.
|
||||
|
||||
A list of environment options can also be given as argument.
|
||||
Check `Code.eval_string` for more information.
|
||||
See `Code.eval_string` for more information.
|
||||
|
||||
Raises an error if the module was already compiled.
|
||||
|
||||
@@ -212,21 +490,19 @@ defmodule Module do
|
||||
{ simplify_signature(x, line, acc), acc + 1 }
|
||||
end
|
||||
|
||||
case ETS.lookup(table, tuple) do
|
||||
case :ets.lookup(table, tuple) do
|
||||
[] ->
|
||||
ETS.insert(table, { tuple, line, kind, signature, doc })
|
||||
:ets.insert(table, { tuple, line, kind, signature, doc })
|
||||
:ok
|
||||
[{ tuple, line, _old_kind, old_sign, old_doc }] when old_doc == nil or doc == nil or old_doc == doc ->
|
||||
ETS.insert(table, {
|
||||
[{ tuple, line, _old_kind, old_sign, old_doc }] ->
|
||||
:ets.insert(table, {
|
||||
tuple,
|
||||
line,
|
||||
kind,
|
||||
merge_signatures(old_sign, signature, 1),
|
||||
doc || old_doc
|
||||
if(nil?(doc), do: old_doc, else: doc)
|
||||
})
|
||||
:ok
|
||||
_ ->
|
||||
{ :error, :existing_doc }
|
||||
end
|
||||
end
|
||||
|
||||
@@ -299,7 +575,7 @@ defmodule Module do
|
||||
def defines?(module, tuple) when is_tuple(tuple) do
|
||||
assert_not_compiled!(:defines?, module)
|
||||
table = function_table_for(module)
|
||||
ETS.lookup(table, tuple) != []
|
||||
:ets.lookup(table, tuple) != []
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -319,7 +595,7 @@ defmodule Module do
|
||||
def defines?(module, tuple, kind) do
|
||||
assert_not_compiled!(:defines?, module)
|
||||
table = function_table_for(module)
|
||||
case ETS.lookup(table, tuple) do
|
||||
case :ets.lookup(table, tuple) do
|
||||
[{ _, ^kind, _, _, _, _, _ }] -> true
|
||||
_ -> false
|
||||
end
|
||||
@@ -339,7 +615,7 @@ defmodule Module do
|
||||
def definitions_in(module) do
|
||||
assert_not_compiled!(:definitions_in, module)
|
||||
table = function_table_for(module)
|
||||
lc { tuple, _, _, _, _, _, _ } inlist ETS.tab2list(table), do: tuple
|
||||
lc { tuple, _, _, _, _, _, _ } inlist :ets.tab2list(table), do: tuple
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -358,13 +634,14 @@ defmodule Module do
|
||||
def definitions_in(module, kind) do
|
||||
assert_not_compiled!(:definitions_in, module)
|
||||
table = function_table_for(module)
|
||||
lc { tuple, stored_kind, _, _, _, _, _ } inlist ETS.tab2list(table), stored_kind == kind, do: tuple
|
||||
lc { tuple, stored_kind, _, _, _, _, _ } inlist :ets.tab2list(table), stored_kind == kind, do: tuple
|
||||
end
|
||||
|
||||
@doc """
|
||||
Makes the given functions in the given module overridable.
|
||||
An overridable function is lazily defined, allowing a
|
||||
developer to customize it.
|
||||
developer to customize it. See `Kernel.defoverridable` for
|
||||
more information and documentation.
|
||||
"""
|
||||
def make_overridable(module, tuples) do
|
||||
assert_not_compiled!(:make_overridable, module)
|
||||
@@ -376,10 +653,12 @@ defmodule Module do
|
||||
raise "Cannot make function #{name}/#{arity} overridable because it was not defined"
|
||||
clause ->
|
||||
:elixir_def.delete_definition(module, tuple)
|
||||
neighbours = Module.DispatchTracker.yank(module, tuple)
|
||||
|
||||
old = get_attribute(module, :__overridable)
|
||||
new = [ { tuple, { 1, clause, false } } ]
|
||||
merged = :orddict.merge(fn(_k, { count, _, _ }, _v2) -> { count + 1, clause, false } end, old, new)
|
||||
merged = :orddict.update(tuple, fn({ count, _, _, _ }) ->
|
||||
{ count + 1, clause, neighbours, false }
|
||||
end, { 1, clause, neighbours, false }, old)
|
||||
|
||||
put_attribute(module, :__overridable, merged)
|
||||
end
|
||||
@@ -409,26 +688,39 @@ defmodule Module do
|
||||
assert_not_compiled!(:put_attribute, module)
|
||||
table = data_table_for(module)
|
||||
value = normalize_attribute(key, value)
|
||||
acc = ETS.lookup_element(table, :__acc_attributes, 2)
|
||||
acc = :ets.lookup_element(table, :__acc_attributes, 2)
|
||||
|
||||
new =
|
||||
if :lists.member(key, acc) do
|
||||
case ETS.lookup(table, key) do
|
||||
[{^key,old}] -> [value|old]
|
||||
case :ets.lookup(table, key) do
|
||||
[{^key, old}] -> [value|old]
|
||||
[] -> [value]
|
||||
end
|
||||
else
|
||||
value
|
||||
end
|
||||
|
||||
ETS.insert(table, { key, new })
|
||||
:ets.insert(table, { key, new })
|
||||
end
|
||||
|
||||
@doc """
|
||||
Gets the given attribute from a module. If the attribute
|
||||
was marked as accumulate with `Module.register_attribute`,
|
||||
was marked with `accumulate` with `Module.register_attribute`,
|
||||
a list is always returned.
|
||||
|
||||
The `@` macro compiles to a call to this function. For example,
|
||||
the following code:
|
||||
|
||||
@foo
|
||||
|
||||
Expands to:
|
||||
|
||||
Module.get_attribute(__MODULE__, @foo, true)
|
||||
|
||||
Notice the third argument is used to indicate if a warning
|
||||
should be emitted when the attribute was not previously defined.
|
||||
This is true for `@foo` attributes but false for direct calls.
|
||||
|
||||
## Examples
|
||||
|
||||
defmodule Foo do
|
||||
@@ -441,15 +733,22 @@ defmodule Module do
|
||||
end
|
||||
|
||||
"""
|
||||
def get_attribute(module, key) when is_atom(key) do
|
||||
def get_attribute(module, key, warn // false) when is_atom(key) do
|
||||
assert_not_compiled!(:get_attribute, module)
|
||||
table = data_table_for(module)
|
||||
|
||||
case ETS.lookup(table, key) do
|
||||
[{^key,old}] -> old
|
||||
case :ets.lookup(table, key) do
|
||||
[{^key, val}] -> val
|
||||
[] ->
|
||||
acc = ETS.lookup_element(table, :__acc_attributes, 2)
|
||||
if :lists.member(key, acc), do: [], else: nil
|
||||
acc = :ets.lookup_element(table, :__acc_attributes, 2)
|
||||
|
||||
if :lists.member(key, acc) do
|
||||
[]
|
||||
else
|
||||
warn && IO.warn "#{Exception.format_caller} undefined module attribute @#{key}, " <>
|
||||
"please remove access to @#{key} or explicitly set it to nil before access"
|
||||
nil
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -467,7 +766,7 @@ defmodule Module do
|
||||
def delete_attribute(module, key) when is_atom(key) do
|
||||
assert_not_compiled!(:delete_attribute, module)
|
||||
table = data_table_for(module)
|
||||
ETS.delete(table, key)
|
||||
:ets.delete(table, key)
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -508,13 +807,13 @@ defmodule Module do
|
||||
table = data_table_for(module)
|
||||
|
||||
if Keyword.get(opts, :persist, true) do
|
||||
old = ETS.lookup_element(table, :__persisted_attributes, 2)
|
||||
ETS.insert(table, { :__persisted_attributes, [new|old] })
|
||||
old = :ets.lookup_element(table, :__persisted_attributes, 2)
|
||||
:ets.insert(table, { :__persisted_attributes, [new|old] })
|
||||
end
|
||||
|
||||
if Keyword.get(opts, :accumulate, true) do
|
||||
old = ETS.lookup_element(table, :__acc_attributes, 2)
|
||||
ETS.insert(table, { :__acc_attributes, [new|old] })
|
||||
old = :ets.lookup_element(table, :__acc_attributes, 2)
|
||||
:ets.insert(table, { :__acc_attributes, [new|old] })
|
||||
end
|
||||
end
|
||||
|
||||
@@ -528,7 +827,7 @@ defmodule Module do
|
||||
|
||||
"""
|
||||
def split(module) do
|
||||
tl(String.split(Binary.Chars.to_binary(module), "-"))
|
||||
tl(String.split(Binary.Chars.to_binary(module), "."))
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -537,13 +836,10 @@ defmodule Module do
|
||||
def to_binary(Elixir), do: "Elixir"
|
||||
|
||||
def to_binary(module) do
|
||||
"Elixir-" <> rest = Binary.Chars.to_binary(module)
|
||||
bc <<r>> inbits rest, do: <<to_dot(r)>>
|
||||
"Elixir." <> rest = Binary.Chars.to_binary(module)
|
||||
rest
|
||||
end
|
||||
|
||||
defp to_dot(?-), do: ?.
|
||||
defp to_dot(l), do: l
|
||||
|
||||
@doc false
|
||||
# Used internally to compile documentation. This function
|
||||
# is private and must be used only internally.
|
||||
@@ -558,9 +854,7 @@ defmodule Module do
|
||||
:ok ->
|
||||
:ok
|
||||
{ :error, :private_doc } ->
|
||||
IO.puts "#{env.file}:#{line} function #{name}/#{arity} is private, @doc's are always discarded for private functions"
|
||||
{ :error, :existing_doc } ->
|
||||
IO.puts "#{env.file}:#{line} @doc's for function #{name}/#{arity} have been given more than once, the first version is being kept"
|
||||
IO.warn "#{env.file}:#{line} function #{name}/#{arity} is private, @doc's are always discarded for private functions"
|
||||
end
|
||||
|
||||
delete_attribute(module, :doc)
|
||||
@@ -574,12 +868,12 @@ defmodule Module do
|
||||
table = data_table_for(module)
|
||||
|
||||
new =
|
||||
case ETS.lookup(table, key) do
|
||||
[{^key,old}] -> [value|old]
|
||||
case :ets.lookup(table, key) do
|
||||
[{^key, old}] -> [value|old]
|
||||
[] -> [value]
|
||||
end
|
||||
|
||||
ETS.insert(table, { key, new })
|
||||
:ets.insert(table, { key, new })
|
||||
end
|
||||
|
||||
## Helpers
|
||||
@@ -616,11 +910,11 @@ defmodule Module do
|
||||
end
|
||||
|
||||
defp function_table_for(module) do
|
||||
list_to_atom :lists.concat([:f, module])
|
||||
:elixir_def.table(module)
|
||||
end
|
||||
|
||||
defp docs_table_for(module) do
|
||||
list_to_atom :lists.concat([:o, module])
|
||||
:elixir_module.docs_table(module)
|
||||
end
|
||||
|
||||
defp assert_not_compiled!(fun, module) do
|
||||
|
||||
@@ -0,0 +1,436 @@
|
||||
# This is a module Elixir responsible for tracking
|
||||
# calls in order to extract Elixir modules' behaviour
|
||||
# during compilation time.
|
||||
#
|
||||
# ## Implementation
|
||||
#
|
||||
# The implementation uses the digraph module to track
|
||||
# all dependencies. The graph starts with three main
|
||||
# vertices:
|
||||
#
|
||||
# * `:local` - points to local functions
|
||||
# * `:import` - points to imported modules
|
||||
# * `:warn` - points to imported modules that should be warned
|
||||
# * `:remote` - points to remote modules
|
||||
#
|
||||
# Besides those, we have can the following vertices:
|
||||
#
|
||||
# * `Module` - a module that was invoked via an import or remotely
|
||||
# * `{ name, arity }` - a local function/arity pair
|
||||
# * `{ :import, name, arity }` - an invoked function/arity import
|
||||
# * `{ :remote, name, arity }` - an remotely invoked function/arity
|
||||
#
|
||||
# Each of those vertices can associate to other vertices
|
||||
# as described below:
|
||||
#
|
||||
# * `Module`
|
||||
# * in neighbours: `:import`, `:remote`, `:warn`,
|
||||
# `{ :import, name, arity }` and `{ :remote, name arity }`
|
||||
# * out neighbours: `:warn`
|
||||
#
|
||||
# * `{ name, arity }`
|
||||
# * in neighbours: `:local`, `{ name, arity }`
|
||||
# * out neighbours: `{ :import, name, arity }` and `{ :remote, name arity }`
|
||||
#
|
||||
# * `{ :import, name, arity }`
|
||||
# * in neighbours: `{ name, arity }`
|
||||
# * out neighbours: `Module`
|
||||
#
|
||||
# * `{ :remote, name, arity }`
|
||||
# * in neighbours: `{ name, arity }`
|
||||
# * out neighbours: `Module`
|
||||
#
|
||||
# Note that since this is required for bootstrap, we can't use
|
||||
# any of the `GenServer.Behaviour` conveniences.
|
||||
defmodule Module.DispatchTracker do
|
||||
@moduledoc false
|
||||
|
||||
@timeout 30_000
|
||||
@behavior :gen_server
|
||||
|
||||
@type ref :: pid | module
|
||||
@type name :: atom
|
||||
@type name_arity :: { name, arity }
|
||||
|
||||
@type local :: { name, arity }
|
||||
@type import :: { :import, name, arity }
|
||||
@type remote :: { :remote, name, arity }
|
||||
|
||||
# Public API
|
||||
|
||||
@doc """
|
||||
Receives a dispatch or a module and returns all dispatches
|
||||
that calls it.
|
||||
|
||||
In case the argument is a module, the response will be
|
||||
made by import and remote dispatches.
|
||||
|
||||
In case the argument is another dispatch, the response
|
||||
will be made by local dispatches.
|
||||
|
||||
This function is not recursive, so if A dispatches to
|
||||
B which dispatches to C, A does not appear in the result,
|
||||
only B.
|
||||
"""
|
||||
@spec dispatches_to(ref, module) :: [import | remote]
|
||||
@spec dispatches_to(ref, local | import | remote) :: [local]
|
||||
def dispatches_to(ref, dispatch) do
|
||||
d = :gen_server.call(to_pid(ref), :digraph, @timeout)
|
||||
:digraph.in_neighbours(d, dispatch) |> only_tuples
|
||||
end
|
||||
|
||||
@doc """
|
||||
Receives a local and returns all dispatches from that local.
|
||||
|
||||
This function is not recursive, so if A dispatches to
|
||||
B which dispatches to C, C does not appear in the result,
|
||||
only B.
|
||||
"""
|
||||
@spec dispatches_from(ref, local) :: [local | import | remote]
|
||||
def dispatches_from(ref, { name, arity }) do
|
||||
d = :gen_server.call(to_pid(ref), :digraph, @timeout)
|
||||
:digraph.out_neighbours(d, { name, arity }) |> only_tuples
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns all the modules which were imported.
|
||||
All external dependencies to a module is the sum
|
||||
of imports and remotes.
|
||||
"""
|
||||
@spec imports(ref) :: [module]
|
||||
def imports(ref) do
|
||||
d = :gen_server.call(to_pid(ref), :digraph, @timeout)
|
||||
:digraph.out_neighbours(d, :import)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns all imported modules that had the given
|
||||
`{ name, arity }` invoked.
|
||||
"""
|
||||
@spec imports_with_dispatch(ref, name_arity) :: [module]
|
||||
def imports_with_dispatch(ref, { name, arity }) do
|
||||
d = :gen_server.call(to_pid(ref), :digraph, @timeout)
|
||||
:digraph.out_neighbours(d, { :import, name, arity })
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns all the modules which were remotely dispatched
|
||||
to. All external dependencies to a module is the sum
|
||||
of imports and remotes.
|
||||
"""
|
||||
@spec remotes(ref) :: [module]
|
||||
def remotes(ref) do
|
||||
d = :gen_server.call(to_pid(ref), :digraph, @timeout)
|
||||
:digraph.out_neighbours(d, :remote)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns all modules that had the given `{ name, arity }`
|
||||
invoked remotely.
|
||||
"""
|
||||
@spec remotes_with_dispatch(ref, name_arity) :: [module]
|
||||
def remotes_with_dispatch(ref, { name, arity }) do
|
||||
d = :gen_server.call(to_pid(ref), :digraph, @timeout)
|
||||
:digraph.out_neighbours(d, { :remote, name, arity })
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns all locals that are reachable.
|
||||
|
||||
By default, all public functions are reachable.
|
||||
A private function is only reachable if it has
|
||||
a public function that it invokes directly.
|
||||
"""
|
||||
@spec reachable(ref) :: [local]
|
||||
def reachable(ref) do
|
||||
d = :gen_server.call(to_pid(ref), :digraph, @timeout)
|
||||
reduce_reachable(d, :local, [])
|
||||
end
|
||||
|
||||
defp reduce_reachable(d, vertex, vertices) do
|
||||
neighbours = :digraph.out_neighbours(d, vertex)
|
||||
neighbours = (lc { _, _ } = t inlist 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)
|
||||
end
|
||||
|
||||
defp to_pid(pid) when is_pid(pid), do: pid
|
||||
defp to_pid(mod) when is_atom(mod), do: Module.get_attribute(mod, :__dispatch_tracker)
|
||||
|
||||
defp only_tuples(list) do
|
||||
lc x inlist list, is_tuple(x), do: x
|
||||
end
|
||||
|
||||
# Internal API
|
||||
|
||||
# Starts the tracker and returns its pid.
|
||||
@doc false
|
||||
def start_link do
|
||||
{ :ok, pid } = :gen_server.start_link(__MODULE__, [], [])
|
||||
pid
|
||||
end
|
||||
|
||||
# Adds a definition into the tracker. A public
|
||||
# definition is connected with the :local node
|
||||
# while a private one is left unreachable until
|
||||
# a call is made to.
|
||||
@doc false
|
||||
def add_definition(pid, kind, tuple) when kind in [:def, :defp, :defmacro, :defmacrop] do
|
||||
:gen_server.cast(pid, { :add_definition, kind, tuple })
|
||||
end
|
||||
|
||||
# Adds and tracks defaults for a definition into the tracker.
|
||||
@doc false
|
||||
def add_defaults(pid, kind, tuple, defaults) when kind in [:def, :defp, :defmacro, :defmacrop] do
|
||||
:gen_server.cast(pid, { :add_defaults, kind, tuple, defaults })
|
||||
end
|
||||
|
||||
# Adds a local dispatch to the given target.
|
||||
def add_local(pid, to) when is_tuple(to) do
|
||||
:gen_server.cast(pid, { :add_local, :local, to })
|
||||
end
|
||||
|
||||
# Adds a local dispatch from-to the given target.
|
||||
@doc false
|
||||
def add_local(pid, from, to) when is_tuple(from) and is_tuple(to) do
|
||||
:gen_server.cast(pid, { :add_local, from, to })
|
||||
end
|
||||
|
||||
# Adds a remote dispatch to the given target.
|
||||
@doc false
|
||||
def add_remote(pid, function, module, target) when is_atom(module) and is_tuple(target) do
|
||||
:gen_server.cast(pid, { :add_remote, function, module, target })
|
||||
end
|
||||
|
||||
# Adds a import dispatch to the given target.
|
||||
@doc false
|
||||
def add_import(pid, function, module, target) when is_atom(module) and is_tuple(target) do
|
||||
:gen_server.cast(pid, { :add_import, function, module, target })
|
||||
end
|
||||
|
||||
# Associates a module with a warn. This adds the given
|
||||
# module and associates it with the `:import` vertex
|
||||
# permanently, even if warn is false.
|
||||
@doc false
|
||||
def add_warnable(pid, module, warn, line) when is_atom(module) and is_boolean(warn) do
|
||||
:gen_server.cast(pid, { :add_warnable, module, warn, line })
|
||||
end
|
||||
|
||||
# Collect all unused imports where warn has been set to true.
|
||||
def collect_unused_imports(pid) do
|
||||
d = :gen_server.call(pid, :digraph, @timeout)
|
||||
warnable = :digraph.out_neighbours(d, :warn)
|
||||
|
||||
lc mod inlist warnable, not has_imports?(d, mod), line = get_warn_line(d, mod) do
|
||||
{ mod, line }
|
||||
end
|
||||
end
|
||||
|
||||
defp get_warn_line(d, mod) do
|
||||
[edge] = :digraph.out_edges(d, mod)
|
||||
{ ^edge, ^mod, :warn, line } = :digraph.edge(d, edge)
|
||||
line
|
||||
end
|
||||
|
||||
defp has_imports?(d, mod) do
|
||||
Enum.any?(:digraph.in_neighbours(d, mod), match?({ :import, _, _ }, &1))
|
||||
end
|
||||
|
||||
# Yanks a local node. Returns its in and out vertices in a tuple.
|
||||
@doc false
|
||||
def yank(pid, local) do
|
||||
:gen_server.call(to_pid(pid), { :yank, local }, @timeout)
|
||||
end
|
||||
|
||||
def reattach(pid, kind, tuple, neighbours) do
|
||||
pid = to_pid(pid)
|
||||
add_definition(pid, kind, tuple)
|
||||
:gen_server.cast(pid, { :reattach, tuple, neighbours })
|
||||
end
|
||||
|
||||
# Collecting all conflicting imports with the given functions
|
||||
@doc false
|
||||
def collect_imports_conflicts(pid, all_defined) do
|
||||
d = :gen_server.call(pid, :digraph, @timeout)
|
||||
|
||||
lc { name, arity } inlist all_defined,
|
||||
n = :digraph.out_neighbours(d, { :import, name, arity }),
|
||||
n != [] do
|
||||
{ n, name, arity }
|
||||
end
|
||||
end
|
||||
|
||||
# Collect all unused definitions based on the private
|
||||
# given also accounting the expected amount of default
|
||||
# clauses a private function have.
|
||||
@doc false
|
||||
def collect_unused_locals(pid, private) do
|
||||
reachable = reachable(pid)
|
||||
:lists.foldl(collect_unused_locals(&1, &2, reachable), [], private)
|
||||
end
|
||||
|
||||
defp collect_unused_locals({ tuple, kind, 0 }, acc, reachable) do
|
||||
if :lists.member(tuple, reachable) do
|
||||
acc
|
||||
else
|
||||
[{ :unused_def, tuple, kind }|acc]
|
||||
end
|
||||
end
|
||||
|
||||
defp collect_unused_locals({ tuple, kind, default }, acc, reachable) when default > 0 do
|
||||
{ name, arity } = tuple
|
||||
min = arity - default
|
||||
max = arity
|
||||
|
||||
invoked = lc { n, a } inlist reachable, n == name, a in min..max, do: a
|
||||
|
||||
if invoked == [] do
|
||||
[{ :unused_def, tuple, kind }|acc]
|
||||
else
|
||||
case :lists.min(invoked) - min do
|
||||
0 -> acc
|
||||
^default -> [{ :unused_args, tuple }|acc]
|
||||
unused_args -> [{ :unused_args, tuple, unused_args }|acc]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Stops the gen server
|
||||
@doc false
|
||||
def stop(pid) do
|
||||
:gen_server.cast(pid, :stop)
|
||||
end
|
||||
|
||||
# Callbacks
|
||||
|
||||
def init([]) do
|
||||
d = :digraph.new([:protected])
|
||||
:digraph.add_vertex(d, :local)
|
||||
:digraph.add_vertex(d, :import)
|
||||
:digraph.add_vertex(d, :remote)
|
||||
:digraph.add_vertex(d, :warn)
|
||||
{ :ok, d }
|
||||
end
|
||||
|
||||
def handle_call({ :yank, local }, _from, d) do
|
||||
in_vertices = :digraph.in_neighbours(d, local)
|
||||
out_vertices = :digraph.out_neighbours(d, local)
|
||||
:digraph.del_vertex(d, local)
|
||||
{ :reply, { in_vertices, out_vertices }, d }
|
||||
end
|
||||
|
||||
def handle_call(:digraph, _from, d) do
|
||||
{ :reply, d, d }
|
||||
end
|
||||
|
||||
def handle_call(_request, _from, d) do
|
||||
{ :noreply, d }
|
||||
end
|
||||
|
||||
def handle_info(_msg, d) do
|
||||
{ :noreply, d }
|
||||
end
|
||||
|
||||
def handle_cast({ :add_local, from, to }, d) do
|
||||
handle_add_local(d, from, to)
|
||||
{ :noreply, d }
|
||||
end
|
||||
|
||||
def handle_cast({ :add_remote, function, module, { name, arity } }, d) do
|
||||
handle_import_or_remote(d, :remote, function, module, name, arity)
|
||||
{ :noreply, d }
|
||||
end
|
||||
|
||||
def handle_cast({ :add_import, function, module, { name, arity } }, d) do
|
||||
handle_import_or_remote(d, :import, function, module, name, arity)
|
||||
{ :noreply, d }
|
||||
end
|
||||
|
||||
def handle_cast({ :add_warnable, module, warn, line }, d) do
|
||||
:digraph.add_vertex(d, module)
|
||||
replace_edge!(d, :import, module)
|
||||
|
||||
if warn do
|
||||
:digraph.add_edge(d, :warn, module, line)
|
||||
:digraph.add_edge(d, module, :warn, line)
|
||||
else
|
||||
:digraph.del_path(d, :warn, module)
|
||||
end
|
||||
{ :noreply, d }
|
||||
end
|
||||
|
||||
def handle_cast({ :add_definition, kind, tuple }, d) do
|
||||
handle_add_definition(d, kind, tuple)
|
||||
{ :noreply, d }
|
||||
end
|
||||
|
||||
def handle_cast({ :add_defaults, kind, { name, arity }, defaults }, d) do
|
||||
lc i inlist :lists.seq(arity - defaults, arity - 1) do
|
||||
handle_add_definition(d, kind, { name, i })
|
||||
handle_add_local(d, { name, i }, { name, i + 1 })
|
||||
end
|
||||
{ :noreply, d }
|
||||
end
|
||||
|
||||
def handle_cast({ :reattach, tuple, { in_neigh, out_neigh } }, d) do
|
||||
lc from inlist in_neigh, do: replace_edge(d, from, tuple)
|
||||
lc to inlist out_neigh, do: replace_edge(d, tuple, to)
|
||||
{ :noreply, d }
|
||||
end
|
||||
|
||||
def handle_cast(:stop, d) do
|
||||
{ :stop, :normal, d }
|
||||
end
|
||||
|
||||
def handle_cast(_msg, d) do
|
||||
{ :noreply, d }
|
||||
end
|
||||
|
||||
def terminate(_reason, _d) do
|
||||
:ok
|
||||
end
|
||||
|
||||
def code_change(_old, d, _extra) do
|
||||
{ :ok, d }
|
||||
end
|
||||
|
||||
defp handle_import_or_remote(d, kind, function, module, name, arity) do
|
||||
:digraph.add_vertex(d, module)
|
||||
replace_edge!(d, kind, module)
|
||||
|
||||
tuple = { kind, name, arity }
|
||||
:digraph.add_vertex(d, tuple)
|
||||
replace_edge!(d, tuple, module)
|
||||
|
||||
if function != nil do
|
||||
replace_edge!(d, function, tuple)
|
||||
end
|
||||
end
|
||||
|
||||
defp handle_add_local(d, from, to) do
|
||||
:digraph.add_vertex(d, to)
|
||||
replace_edge!(d, from, to)
|
||||
end
|
||||
|
||||
defp handle_add_definition(d, public, tuple) when public in [:def, :defmacro] do
|
||||
:digraph.add_vertex(d, tuple)
|
||||
replace_edge!(d, :local, tuple)
|
||||
end
|
||||
|
||||
defp handle_add_definition(d, private, tuple) when private in [:defp, :defmacrop] do
|
||||
:digraph.add_vertex(d, tuple)
|
||||
end
|
||||
|
||||
defp replace_edge!(d, from, to) do
|
||||
unless :lists.member(to, :digraph.out_neighbours(d, from)) do
|
||||
[:"$e"|_] = :digraph.add_edge(d, from, to)
|
||||
end
|
||||
end
|
||||
|
||||
defp replace_edge(d, from, to) do
|
||||
unless :lists.member(to, :digraph.out_neighbours(d, from)) do
|
||||
:digraph.add_edge(d, from, to)
|
||||
end
|
||||
end
|
||||
end
|
||||
+12
-12
@@ -385,19 +385,16 @@ defmodule Path do
|
||||
join(left, atom_to_binary(right))
|
||||
|
||||
defp major_os_type do
|
||||
case :os.type do
|
||||
{ maj, _ } -> maj
|
||||
maj -> maj
|
||||
end
|
||||
:os.type |> elem(0)
|
||||
end
|
||||
|
||||
defp do_join(<<uc_letter, ?:, rest :: binary>>, relativename, [], :win32) when uc_letter in ?A..?Z, do:
|
||||
do_join(rest, relativename, [?:, uc_letter+?a-?A], :win32)
|
||||
defp do_join(<<?\\,rest :: binary>>, relativename, result, :win32), do:
|
||||
do_join(<<?/,rest :: binary>>, relativename, result, :win32)
|
||||
defp do_join(<<?/,rest :: binary>>, relativename, [?., ?/|result], os_type), do:
|
||||
defp do_join(<<?\\, rest :: binary>>, relativename, result, :win32), do:
|
||||
do_join(<<?/, rest :: binary>>, relativename, result, :win32)
|
||||
defp do_join(<<?/, rest :: binary>>, relativename, [?., ?/|result], os_type), do:
|
||||
do_join(rest, relativename, [?/|result], os_type)
|
||||
defp do_join(<<?/,rest :: binary>>, relativename, [?/|result], os_type), do:
|
||||
defp do_join(<<?/, rest :: binary>>, relativename, [?/|result], os_type), do:
|
||||
do_join(rest, relativename, [?/|result], os_type)
|
||||
defp do_join(<<>>, <<>>, result, os_type), do:
|
||||
list_to_binary(maybe_remove_dirsep(result, os_type))
|
||||
@@ -407,7 +404,7 @@ defmodule Path do
|
||||
do_join(relativename, <<>>, [?/|result], os_type)
|
||||
defp do_join(<<>>, relativename, result, os_type), do:
|
||||
do_join(relativename, <<>>, [?/|result], os_type)
|
||||
defp do_join(<<char,rest :: binary>>, relativename, result, os_type) when is_integer(char), do:
|
||||
defp do_join(<<char, rest :: binary>>, relativename, result, os_type) when is_integer(char), do:
|
||||
do_join(rest, relativename, [char|result], os_type)
|
||||
|
||||
defp maybe_remove_dirsep([?/, ?:, letter], :win32), do:
|
||||
@@ -426,13 +423,16 @@ defmodule Path do
|
||||
## Examples
|
||||
|
||||
iex> Path.split("")
|
||||
["/"]
|
||||
[]
|
||||
iex> Path.split("foo")
|
||||
["foo"]
|
||||
iex> Path.split("/foo/bar")
|
||||
["/", "foo", "bar"]
|
||||
|
||||
"""
|
||||
# Work around a bug in Erlang on UNIX
|
||||
def split(""), do: []
|
||||
|
||||
def split(path) do
|
||||
FN.split(path)
|
||||
end
|
||||
@@ -471,12 +471,12 @@ defmodule Path do
|
||||
|
||||
"""
|
||||
def wildcard(glob) when is_binary(glob) do
|
||||
paths = :elixir_glob.wildcard :unicode.characters_to_list(glob)
|
||||
paths = :filelib.wildcard :unicode.characters_to_list(glob)
|
||||
Enum.map paths, :unicode.characters_to_binary(&1)
|
||||
end
|
||||
|
||||
def wildcard(glob) when is_list(glob) do
|
||||
:elixir_glob.wildcard glob
|
||||
:filelib.wildcard glob
|
||||
end
|
||||
|
||||
## Helpers
|
||||
|
||||
@@ -76,9 +76,9 @@ defmodule Process do
|
||||
@doc """
|
||||
Sends an exit signal with the given reason to the pid.
|
||||
|
||||
The following behavior apply if reason is any term except `:normal` or `:kill`:
|
||||
The following behavior applies if reason is any term except `:normal` or `:kill`:
|
||||
|
||||
1) If pid is not trapping exits, pid itself will exist with the given reason;
|
||||
1) If pid is not trapping exits, pid will exit with the given reason;
|
||||
|
||||
2) If pid is trapping exits, the exit signal is transformed into a message
|
||||
{'EXIT', from, reason} and delivered to the message queue of pid;
|
||||
@@ -101,6 +101,14 @@ defmodule Process do
|
||||
:erlang.exit(pid, reason)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns the pid (process identifier) of the calling process.
|
||||
"""
|
||||
@spec self() :: pid
|
||||
def self() do
|
||||
:erlang.self()
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns the pid of a new process started by the application of `fun`.
|
||||
It behaves exactly the same as `Kernel.spawn/1`.
|
||||
@@ -360,6 +368,7 @@ defmodule Process do
|
||||
:erlang.process_info(pid, spec)
|
||||
end
|
||||
|
||||
@compile { :inline, nillify: 1 }
|
||||
defp nillify(:undefined), do: nil
|
||||
defp nillify(other), do: other
|
||||
end
|
||||
|
||||
+22
-18
@@ -34,6 +34,8 @@ defmodule Protocol do
|
||||
|
||||
# Set up a clear slate to store defined functions
|
||||
@functions []
|
||||
@only nil
|
||||
@except nil
|
||||
|
||||
# Invoke the user given block
|
||||
unquote(block)
|
||||
@@ -81,14 +83,14 @@ defmodule Protocol do
|
||||
def assert_protocol(module) do
|
||||
case Code.ensure_compiled(module) do
|
||||
{ :module, ^module } -> nil
|
||||
_ -> raise ArgumentError, message: "#{module} is not loaded"
|
||||
_ -> raise ArgumentError, message: "#{inspect module} is not loaded"
|
||||
end
|
||||
|
||||
try do
|
||||
module.__protocol__(:name)
|
||||
rescue
|
||||
UndefinedFunctionError ->
|
||||
raise ArgumentError, message: "#{module} is not a protocol"
|
||||
raise ArgumentError, message: "#{inspect module} is not a protocol"
|
||||
end
|
||||
end
|
||||
|
||||
@@ -236,13 +238,13 @@ defmodule Protocol do
|
||||
|
||||
# Returns a quoted expression that allows to check
|
||||
# if the first item in the tuple is a built-in or not.
|
||||
defp is_builtin?([{h,_,_}]) do
|
||||
defp is_builtin?([{h, _, _}]) do
|
||||
quote do
|
||||
first == unquote(h)
|
||||
end
|
||||
end
|
||||
|
||||
defp is_builtin?([{h,_,_}|t]) do
|
||||
defp is_builtin?([{h, _, _}|t]) do
|
||||
quote do
|
||||
first == unquote(h) or unquote(is_builtin?(t))
|
||||
end
|
||||
@@ -267,7 +269,7 @@ defmodule Protocol do
|
||||
true -> unquote(fallback)
|
||||
false ->
|
||||
case atom_to_list(first) do
|
||||
'Elixir-' ++ _ -> __MODULE__.Record
|
||||
'Elixir.' ++ _ -> __MODULE__.Record
|
||||
_ -> unquote(fallback)
|
||||
end
|
||||
end
|
||||
@@ -349,36 +351,38 @@ defmodule Protocol.DSL do
|
||||
end
|
||||
|
||||
defp default_clause(name, args) do
|
||||
quote do: { [other], apply(other, unquote(name), [unquote_splicing(args)]) }
|
||||
{ [quote do: other],
|
||||
[],
|
||||
quote(do: apply(other, unquote(name), [unquote_splicing(args)])) }
|
||||
end
|
||||
|
||||
defp nil_clause() do
|
||||
quote do
|
||||
{ [nil], raise(Protocol.UndefinedError, protocol: __MODULE__, structure: xA) }
|
||||
end
|
||||
{ [nil],
|
||||
[],
|
||||
quote(do: raise(Protocol.UndefinedError, protocol: __MODULE__, structure: xA)) }
|
||||
end
|
||||
|
||||
defp record_clause(name, args, nil) do
|
||||
quote do
|
||||
{ [__MODULE__.Record],
|
||||
Module.concat(__MODULE__, :erlang.element(1, xA)).unquote(name)(unquote_splicing(args)) }
|
||||
end
|
||||
{ [quote(do: __MODULE__.Record)],
|
||||
[],
|
||||
quote(do: Module.concat(__MODULE__, :erlang.element(1, xA)).unquote(name)(unquote_splicing(args))) }
|
||||
end
|
||||
|
||||
defp record_clause(name, args, fallback) do
|
||||
arity = length(args)
|
||||
|
||||
quote do
|
||||
{ [__MODULE__.Record],
|
||||
(target = Module.concat(__MODULE__, :erlang.element(1, xA))
|
||||
{ [quote(do: __MODULE__.Record)],
|
||||
[],
|
||||
quote do
|
||||
target = Module.concat(__MODULE__, :erlang.element(1, xA))
|
||||
try do
|
||||
target.unquote(name)(unquote_splicing(args))
|
||||
catch
|
||||
:error, :undef, [[{ ^target, name, args, _ }|_]|_] when
|
||||
name == unquote(name) and length(args) == unquote(arity) ->
|
||||
apply unquote(fallback), name, [unquote_splicing(args)]
|
||||
end) }
|
||||
end
|
||||
end
|
||||
end }
|
||||
end
|
||||
|
||||
defmacro def(expression) do
|
||||
|
||||
+24
-20
@@ -5,14 +5,7 @@ defrecord Range, [:first, :last] do
|
||||
end
|
||||
|
||||
defprotocol Range.Iterator do
|
||||
@doc """
|
||||
How to iterate the range, receives the first
|
||||
and range as arguments. It needs to return a
|
||||
function that receives an item and returns
|
||||
a tuple with two elements: the given item
|
||||
and the next item in the iteration.
|
||||
"""
|
||||
def iterator(first, range)
|
||||
def reduce(first, range, acc, fun)
|
||||
|
||||
@doc """
|
||||
Count how many items are in the range.
|
||||
@@ -20,12 +13,10 @@ defprotocol Range.Iterator do
|
||||
def count(first, range)
|
||||
end
|
||||
|
||||
defimpl Enum.Iterator, for: Range do
|
||||
def iterator(Range[first: first] = range) do
|
||||
iterator = Range.Iterator.iterator(first, range)
|
||||
{ iterator, iterator.(first) }
|
||||
defimpl Enumerable, for: Range do
|
||||
def reduce(Range[first: first] = range, acc, fun) do
|
||||
Range.Iterator.reduce(first, range, acc, fun)
|
||||
end
|
||||
|
||||
def member?(Range[first: first, last: last], value) do
|
||||
value in first..last
|
||||
end
|
||||
@@ -36,16 +27,29 @@ defimpl Enum.Iterator, for: Range do
|
||||
end
|
||||
|
||||
defimpl Range.Iterator, for: Number do
|
||||
def iterator(first, Range[last: last]) when is_integer(first) and is_integer(last) and last >= first do
|
||||
fn(current) ->
|
||||
if current > last, do: :stop, else: { current, current + 1 }
|
||||
def reduce(first, Range[last: last], acc, fun) when is_integer(first) and is_integer(last) do
|
||||
reducer = if last >= first do
|
||||
fn(acc, fun) -> do_reducer_up(first, last, acc, fun) end
|
||||
else
|
||||
fn(acc, fun) -> do_reducer_down(first, last, acc, fun) end
|
||||
end
|
||||
Enumerable.Function.reduce(reducer, acc, fun)
|
||||
end
|
||||
|
||||
def iterator(first, Range[last: last]) when is_integer(first) and is_integer(last) do
|
||||
fn(current) ->
|
||||
if current < last, do: :stop, else: { current, current - 1 }
|
||||
end
|
||||
defp do_reducer_up(counter, last, acc, _fun) when counter > last do
|
||||
acc
|
||||
end
|
||||
|
||||
defp do_reducer_up(counter, last, acc, fun) do
|
||||
do_reducer_up(counter + 1, last, fun.(counter, acc), fun)
|
||||
end
|
||||
|
||||
defp do_reducer_down(counter, last, acc, _fun) when counter < last do
|
||||
acc
|
||||
end
|
||||
|
||||
defp do_reducer_down(counter, last, acc, fun) do
|
||||
do_reducer_down(counter - 1, last, fun.(counter, acc), fun)
|
||||
end
|
||||
|
||||
def count(first, Range[last: last]) when is_integer(first) and is_integer(last) and last >= first do
|
||||
|
||||
+221
-21
@@ -1,6 +1,162 @@
|
||||
defmodule Record do
|
||||
@moduledoc """
|
||||
@moduledoc %B"""
|
||||
Functions to define Elixir records
|
||||
|
||||
A record is a tagged tuple which contains one or more elements and the first
|
||||
element is a module. One creates a record by calling `defrecord` or
|
||||
`defrecordp` which are documented in `Kernel`.
|
||||
|
||||
## Examples
|
||||
|
||||
defrecord FileInfo, atime: nil, accesses: 0
|
||||
|
||||
The line above will define a module named `FileInfo` which
|
||||
contains a function named `new` that returns a new record
|
||||
and other functions to read and set the values in the
|
||||
record:
|
||||
|
||||
file_info = FileInfo.new(atime: now())
|
||||
file_info.atime #=> Returns the value of atime
|
||||
file_info.atime(now()) #=> Updates the value of atime
|
||||
|
||||
# Update multiple attributes at once:
|
||||
file_info.update(atime: now(), accesses: 1)
|
||||
|
||||
# Obtain the keywords representation of a record:
|
||||
file_info.to_keywords #=> [accesses: 1, atime: {1370,7171,911705}]
|
||||
|
||||
|
||||
A record is simply a tuple where the first element is the record
|
||||
module name. We can get the record raw representation as follow:
|
||||
|
||||
inspect FileInfo.new, raw: true
|
||||
#=> { FileInfo, nil, nil }
|
||||
|
||||
Besides defining readers and writers for each attribute, Elixir also
|
||||
defines an `update_#{attribute}` function to update the value. Such
|
||||
functions expect a function as argument that receives the current
|
||||
value and must return the new one. For example, every time the file
|
||||
is accessed, the accesses counter can be incremented with:
|
||||
|
||||
file_info.update_accesses(fn(old) -> old + 1 end)
|
||||
|
||||
Which can be also written as:
|
||||
|
||||
file_info.update_accesses(&1 + 1)
|
||||
|
||||
## Access syntax
|
||||
|
||||
Records in Elixir can be expanded at compilation time to provide
|
||||
pattern matching and faster operations. For example, the clause
|
||||
below will only match if a `FileInfo` is given and the number of
|
||||
accesses is zero:
|
||||
|
||||
def enforce_no_access(FileInfo[accesses: 0]), do: :ok
|
||||
|
||||
The clause above will expand to:
|
||||
|
||||
def enforce_no_access({ FileInfo, _, 0 }), do: :ok
|
||||
|
||||
The downside of using such syntax is that, every time the record
|
||||
changes, your code now needs to be recompiled (which is usually
|
||||
not a concern since Elixir build tools by default recompiles the
|
||||
whole project whenever there is a change).
|
||||
|
||||
Finally, keep in mind that Elixir triggers some optimizations whenever
|
||||
the access syntax is used. For example:
|
||||
|
||||
def no_access?(FileInfo[] = file_info) do
|
||||
file_info.accesses == 0
|
||||
end
|
||||
|
||||
Is translated to:
|
||||
|
||||
def no_access?({ FileInfo, _, _ } = file_info) do
|
||||
elem(file_info, 1) == 0
|
||||
end
|
||||
|
||||
Which provides faster get and set times for record operations.
|
||||
|
||||
## Runtime introspection
|
||||
|
||||
At runtime, developers can use `__record__` to get information
|
||||
about the given record:
|
||||
|
||||
FileInfo.__record__(:name)
|
||||
#=> FileInfo
|
||||
|
||||
FileInfo.__record__(:fields)
|
||||
#=> [atime: nil, accesses: 0]
|
||||
|
||||
In order to quickly access the index of a field, one can use
|
||||
the `__index__` function:
|
||||
|
||||
FileInfo.__index__(:atime)
|
||||
#=> 0
|
||||
|
||||
FileInfo.__index__(:unknown)
|
||||
#=> nil
|
||||
|
||||
## Compile-time introspection
|
||||
|
||||
At the compile time, one can access following information about the record
|
||||
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
|
||||
|
||||
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]
|
||||
|
||||
## Documentation
|
||||
|
||||
By default records are not documented and have `@moduledoc` set to false.
|
||||
|
||||
## Types
|
||||
|
||||
Every record defines a type named `t` that can be accessed in typespecs.
|
||||
Those types can be passed at the moment the record is defined:
|
||||
|
||||
defrecord User,
|
||||
name: "" :: string,
|
||||
age: 0 :: integer
|
||||
|
||||
All the 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()
|
||||
|
||||
If the developer wants to define their own types to be used with the
|
||||
record, Elixir allows a more lengthy definition with the help of the
|
||||
`record_type` macro:
|
||||
|
||||
defrecord Config, counter: 0, failures: [] do
|
||||
@type kind :: term
|
||||
record_type counter: integer, failures: [kind]
|
||||
end
|
||||
|
||||
## Importing records
|
||||
|
||||
It is also possible to import a public record (a record, defined using
|
||||
`defrecord`) as a set of private macros (as if it was defined using `defrecordp`):
|
||||
|
||||
Record.import Config, as: :config
|
||||
|
||||
See `Record.import/2` and `defrecordp/2` documentation for more information
|
||||
"""
|
||||
|
||||
@doc """
|
||||
@@ -24,31 +180,55 @@ defmodule Record do
|
||||
"""
|
||||
def defrecord(name, values, opts) do
|
||||
block = Keyword.get(opts, :do, nil)
|
||||
{ fields, types } = record_split(values)
|
||||
|
||||
quote do
|
||||
values = unquote(values)
|
||||
unquoted_fields = unquote(fields)
|
||||
|
||||
defmodule unquote(name) do
|
||||
@moduledoc false
|
||||
import Record.DSL
|
||||
import Elixir.Record.DSL
|
||||
|
||||
@record_fields []
|
||||
@record_types []
|
||||
@record_types unquote(types)
|
||||
|
||||
Record.deffunctions(values, __ENV__)
|
||||
# Reassign fields to inner scope to
|
||||
# avoid conflicts in nested records
|
||||
fields = unquoted_fields
|
||||
|
||||
Elixir.Record.deffunctions(fields, __ENV__)
|
||||
value = unquote(block)
|
||||
Record.deftypes(values, @record_types, __ENV__)
|
||||
Elixir.Record.deftypes(fields, @record_types, __ENV__)
|
||||
value
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defp record_split(fields) when is_list(fields) do
|
||||
record_split(fields, [], [])
|
||||
end
|
||||
|
||||
defp record_split(other) do
|
||||
{ other, [] }
|
||||
end
|
||||
|
||||
defp record_split([{ field, { :::, _, [default, type] }}|t], defaults, types) do
|
||||
record_split t, [{ field, default }|defaults], [{ field, Macro.escape(type) }|types]
|
||||
end
|
||||
|
||||
defp record_split([other|t], defaults, types) do
|
||||
record_split t, [other|defaults], types
|
||||
end
|
||||
|
||||
defp record_split([], defaults, types) do
|
||||
{ :lists.reverse(defaults), types }
|
||||
end
|
||||
|
||||
@doc """
|
||||
Import public record definition as a set of private macros (as defined by defrecordp/2)
|
||||
|
||||
## Usage
|
||||
|
||||
Record.import Record.Module, as: macro_name
|
||||
Import public record definition as a set of private macros,
|
||||
as defined by `Kernel.defrecordp/2`. This is useful when one
|
||||
desires to manipulate a record via a set of macros instead
|
||||
of the regular access syntax.
|
||||
|
||||
## Example
|
||||
|
||||
@@ -61,7 +241,8 @@ defmodule Record do
|
||||
"""
|
||||
defmacro import(module, as: name) do
|
||||
quote do
|
||||
Record.defmacros(unquote(name), unquote(module).__record__(:fields), __ENV__, unquote(module))
|
||||
Record.defmacros(unquote(name),
|
||||
unquote(module).__record__(:fields), __ENV__, unquote(module))
|
||||
end
|
||||
end
|
||||
|
||||
@@ -71,12 +252,31 @@ defmodule Record do
|
||||
in `values`. This is invoked directly by `Kernel.defrecordp`,
|
||||
so check it for more information and documentation.
|
||||
"""
|
||||
def defrecordp(name, fields) do
|
||||
def defrecordp(name, fields) when is_atom(name) and is_list(fields) do
|
||||
{ fields, types, def_type } = recordp_split(fields, [], [], false)
|
||||
type = :"#{name}_t"
|
||||
|
||||
quote do
|
||||
Record.defmacros(unquote(name), unquote(fields), __ENV__)
|
||||
|
||||
if unquote(def_type) do
|
||||
@typep unquote(type)() :: { unquote(name), unquote_splicing(types) }
|
||||
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
|
||||
{ :lists.reverse(defaults), :lists.reverse(types), def_type }
|
||||
end
|
||||
|
||||
@doc """
|
||||
Defines record functions skipping the module definition.
|
||||
This is called directly by `defrecord`. It expects the record
|
||||
@@ -215,7 +415,7 @@ defmodule Record do
|
||||
@doc false
|
||||
def access(atom, fields, keyword, caller) do
|
||||
unless is_keyword(keyword) do
|
||||
raise "expected contents inside brackets to be a Keyword"
|
||||
raise ArgumentError, message: "expected contents inside brackets to be a Keyword"
|
||||
end
|
||||
|
||||
in_match = caller.in_match?
|
||||
@@ -245,7 +445,7 @@ defmodule Record do
|
||||
quote do: { unquote_splicing([atom|match]) }
|
||||
_ ->
|
||||
keys = lc { key, _ } inlist remaining, do: key
|
||||
raise "record #{inspect atom} does not have the keys: #{inspect keys}"
|
||||
raise ArgumentError, message: "record #{inspect atom} does not have the keys: #{inspect keys}"
|
||||
end
|
||||
end
|
||||
|
||||
@@ -265,11 +465,11 @@ defmodule Record do
|
||||
@doc false
|
||||
defp update(atom, fields, var, keyword, caller) do
|
||||
unless is_keyword(keyword) do
|
||||
raise "expected contents inside brackets to be a Keyword"
|
||||
raise ArgumentError, message: "expected contents inside brackets to be a Keyword"
|
||||
end
|
||||
|
||||
if caller.in_match? do
|
||||
raise "cannot invoke update style macro inside match context"
|
||||
raise ArgumentError, message: "cannot invoke update style macro inside match context"
|
||||
end
|
||||
|
||||
Enum.reduce keyword, var, fn({ key, value }, acc) ->
|
||||
@@ -279,7 +479,7 @@ defmodule Record do
|
||||
:erlang.setelement(unquote(index + 2), unquote(acc), unquote(value))
|
||||
end
|
||||
else
|
||||
raise "record #{inspect atom} does not have the key: #{inspect key}"
|
||||
raise ArgumentError, message: "record #{inspect atom} does not have the key: #{inspect key}"
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -295,7 +495,7 @@ defmodule Record do
|
||||
:erlang.element(unquote(index + 2), unquote(var))
|
||||
end
|
||||
else
|
||||
raise "record #{inspect atom} does not have the key: #{inspect key}"
|
||||
raise ArgumentError, message: "record #{inspect atom} does not have the key: #{inspect key}"
|
||||
end
|
||||
end
|
||||
|
||||
@@ -324,7 +524,7 @@ defmodule Record do
|
||||
if index do
|
||||
quote do: :erlang.element(unquote(index + 2), unquote(record))
|
||||
else
|
||||
raise "record #{inspect atom} does not have the key: #{inspect key}"
|
||||
raise ArgumentError, message: "record #{inspect atom} does not have the key: #{inspect key}"
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -573,7 +773,7 @@ defmodule Record do
|
||||
@spec to_keywords(t) :: options
|
||||
@spec update(options, t) :: t
|
||||
@spec __record__(:name) :: atom
|
||||
@spec __record__(:fields) :: [{atom,any}]
|
||||
@spec __record__(:fields) :: [{atom, any}]
|
||||
@spec __index__(atom) :: non_neg_integer | nil
|
||||
end
|
||||
end
|
||||
|
||||
+26
-23
@@ -79,26 +79,6 @@ defmodule Regex do
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Runs the regular expression against the given string
|
||||
and returns the index (zero indexes) where the first
|
||||
match occurs, nil otherwise.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Regex.index(%r/c(d)/, "abcd")
|
||||
2
|
||||
iex> Regex.index(%r/e/, "abcd")
|
||||
nil
|
||||
|
||||
"""
|
||||
def index(regex(re_pattern: compiled), string) do
|
||||
case :re.run(string, compiled, [{ :capture, :first, :index }]) do
|
||||
:nomatch -> nil
|
||||
{ :match, [{index,_}] } -> index
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns a boolean if there was a match or not.
|
||||
|
||||
@@ -125,6 +105,8 @@ defmodule Regex do
|
||||
["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 // [])
|
||||
@@ -291,6 +273,25 @@ defmodule Regex do
|
||||
:re.replace(string, compiled, replacement, opts)
|
||||
end
|
||||
|
||||
{ :ok, pattern } = :re.compile(%B"[.^$*+?()[{\\\|\s#]", [:unicode])
|
||||
@escape_pattern pattern
|
||||
|
||||
@doc %B"""
|
||||
Escapes a string to be literally matched in a regex.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Regex.escape(".")
|
||||
"\\."
|
||||
iex> Regex.escape("\\what if")
|
||||
"\\\\what\\ if"
|
||||
|
||||
"""
|
||||
@spec escape(String.t | char_list) :: String.t | char_list
|
||||
def escape(string) do
|
||||
:re.replace(string, @escape_pattern, "\\\\&", [:global, { :return, return_for(string) }])
|
||||
end
|
||||
|
||||
# Helpers
|
||||
|
||||
@doc false
|
||||
@@ -313,7 +314,7 @@ defmodule Regex do
|
||||
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(<<?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: []
|
||||
@@ -327,10 +328,12 @@ defmodule Regex do
|
||||
end
|
||||
end
|
||||
|
||||
{ :ok, pattern } = :re.compile(%B"\(\?<(?<G>[^>]*)>")
|
||||
@groups_pattern pattern
|
||||
|
||||
defp parse_groups(source) do
|
||||
options = [:global, {:capture, ['G'], :binary}]
|
||||
{:ok, pattern} = :re.compile(%B"\(\?<(?<G>[^>]*)>")
|
||||
case :re.run(source, pattern, options) do
|
||||
case :re.run(source, @groups_pattern, options) do
|
||||
:nomatch -> []
|
||||
{ :match, results } ->
|
||||
lc [group] inlist results, do: binary_to_atom(group)
|
||||
|
||||
+117
-7
@@ -146,7 +146,7 @@ defmodule String do
|
||||
|
||||
iex> String.split("foo bar")
|
||||
["foo", "bar"]
|
||||
iex> String.split("foo" <> <<194,133>> <> "bar")
|
||||
iex> String.split("foo" <> <<194, 133>> <> "bar")
|
||||
["foo", "bar"]
|
||||
iex> String.split(" foo bar ")
|
||||
["foo", "bar"]
|
||||
@@ -383,7 +383,7 @@ defmodule String do
|
||||
"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])
|
||||
iex> String.replace("a,b,c", ",", "[]", insert_replaced: [1, 1])
|
||||
"a[,,]b[,,]c"
|
||||
|
||||
"""
|
||||
@@ -399,7 +399,7 @@ defmodule String do
|
||||
opts = if options[:global] != false, do: [:global], else: []
|
||||
|
||||
if insert = options[:insert_replaced] do
|
||||
opts = [{:insert_replaced,insert}|opts]
|
||||
opts = [{:insert_replaced, insert}|opts]
|
||||
end
|
||||
|
||||
opts
|
||||
@@ -752,7 +752,7 @@ defmodule String do
|
||||
|
||||
"""
|
||||
@spec to_integer(t) :: {integer, t} | :error
|
||||
|
||||
|
||||
def to_integer(string) do
|
||||
{result, remainder} = :string.to_integer(binary_to_list(string))
|
||||
case result do
|
||||
@@ -773,8 +773,8 @@ defmodule String do
|
||||
{34.0,""}
|
||||
iex> String.to_float("34.25")
|
||||
{34.25,""}
|
||||
iex> String.to_float("56.5xyz")
|
||||
{56.5,"xyz"}
|
||||
iex> String.to_float("56.5xyz")
|
||||
{56.5,"xyz"}
|
||||
iex> String.to_float("pi")
|
||||
:error
|
||||
|
||||
@@ -785,7 +785,7 @@ defmodule String do
|
||||
charlist = binary_to_list(string)
|
||||
{result, remainder} = :string.to_float(charlist)
|
||||
case result do
|
||||
:error ->
|
||||
:error ->
|
||||
{int_result, int_remainder} = :string.to_integer(charlist)
|
||||
case int_result do
|
||||
:error -> :error
|
||||
@@ -795,4 +795,114 @@ defmodule String do
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns true if `string` starts with any of the prefixes given, otherwise
|
||||
false. `prefixes` can be either a single prefix or a list of prefixes.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> String.starts_with? "elixir", "eli"
|
||||
true
|
||||
iex> String.starts_with? "elixir", ["erlang", "elixir"]
|
||||
true
|
||||
iex> String.starts_with? "elixir", ["erlang", "ruby"]
|
||||
false
|
||||
|
||||
"""
|
||||
@spec starts_with?(t, t | [t]) :: boolean
|
||||
|
||||
def starts_with?(string, prefixes) when is_list(prefixes) do
|
||||
Enum.any?(prefixes, do_starts_with(string, &1))
|
||||
end
|
||||
|
||||
def starts_with?(string, prefix) do
|
||||
do_starts_with(string, prefix)
|
||||
end
|
||||
|
||||
defp do_starts_with(_, "") do
|
||||
true
|
||||
end
|
||||
|
||||
defp do_starts_with(string, prefix) when is_binary(prefix) do
|
||||
match?({0, _}, :binary.match(string, prefix))
|
||||
end
|
||||
|
||||
defp do_starts_with(_, _) do
|
||||
raise ArgumentError
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns true if `string` ends with any of the suffixes given, otherwise
|
||||
false. `suffixes` can be either a single suffix or a list of suffixes.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> String.ends_with? "language", "age"
|
||||
true
|
||||
iex> String.ends_with? "language", ["youth", "age"]
|
||||
true
|
||||
iex> String.ends_with? "language", ["youth", "elixir"]
|
||||
false
|
||||
|
||||
"""
|
||||
@spec ends_with?(t, t | [t]) :: boolean
|
||||
|
||||
def ends_with?(string, suffixes) when is_list(suffixes) do
|
||||
Enum.any?(suffixes, do_ends_with(string, &1))
|
||||
end
|
||||
|
||||
def ends_with?(string, suffix) do
|
||||
do_ends_with(string, suffix)
|
||||
end
|
||||
|
||||
defp do_ends_with(_, "") do
|
||||
true
|
||||
end
|
||||
|
||||
defp do_ends_with(string, suffix) when is_binary(suffix) do
|
||||
string_size = size(string)
|
||||
suffix_size = size(suffix)
|
||||
scope = {string_size - suffix_size, suffix_size}
|
||||
(suffix_size <= string_size) and (:nomatch != :binary.match(string, suffix, [scope: scope]))
|
||||
end
|
||||
|
||||
defp do_ends_with(_, _) do
|
||||
raise ArgumentError
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns true if `string` contains match, otherwise false.
|
||||
`matches` can be either a single string or a list of strings.
|
||||
|
||||
## Examples
|
||||
|
||||
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
|
||||
|
||||
"""
|
||||
@spec contains?(t, t | [t]) :: boolean
|
||||
|
||||
def contains?(string, matches) when is_list(matches) do
|
||||
Enum.any?(matches, do_contains(string, &1))
|
||||
end
|
||||
|
||||
def contains?(string, match) do
|
||||
do_contains(string, match)
|
||||
end
|
||||
|
||||
defp do_contains(_, "") do
|
||||
true
|
||||
end
|
||||
|
||||
defp do_contains(string, match) when is_binary(match) do
|
||||
:nomatch != :binary.match(string, match)
|
||||
end
|
||||
|
||||
defp do_contains(_, _) do
|
||||
raise ArgumentError
|
||||
end
|
||||
end
|
||||
|
||||
+52
-31
@@ -14,20 +14,40 @@ defmodule System do
|
||||
with the VM or the host system.
|
||||
"""
|
||||
|
||||
# Read and strip the version from the `VERSION` file.
|
||||
defmacrop get_version do
|
||||
Regex.replace %r/^\s+|\s+$/, File.read!("VERSION"), ""
|
||||
defp strip_re(iodata, pattern) do
|
||||
:re.replace(iodata, pattern, "", [return: :binary])
|
||||
end
|
||||
|
||||
# Tries to run `git describe --always --tags`. In case of success
|
||||
# returns the most recent tag, otherwise returns an empty string.
|
||||
defp read_stripped(path) do
|
||||
case :file.read_file(path) do
|
||||
{ :ok, binary } ->
|
||||
strip_re(binary, "^\s+|\s+$")
|
||||
_ -> ""
|
||||
end
|
||||
end
|
||||
|
||||
# Read and strip the version from the `VERSION` file.
|
||||
defmacrop get_version do
|
||||
case read_stripped("VERSION") do
|
||||
"" -> raise CompileError, message: "could not read the version number from VERSION"
|
||||
data -> data
|
||||
end
|
||||
end
|
||||
|
||||
# Tries to run `git describe --always --tags`. In the case of success returns
|
||||
# the most recent tag. If that is not available, tries to read the commit hash
|
||||
# from .git/HEAD. If that fails, returns an empty string.
|
||||
defmacrop get_describe do
|
||||
dotgit = Path.join(File.cwd!, ".git")
|
||||
if :os.find_executable('git') && File.exists?(dotgit) do
|
||||
data = :os.cmd('git describe --always --tags')
|
||||
Regex.replace %r/\n/, to_binary(data), ""
|
||||
else
|
||||
""
|
||||
dirpath = ".git"
|
||||
case :file.read_file_info(dirpath) do
|
||||
{ :ok, _ } ->
|
||||
if :os.find_executable('git') do
|
||||
data = :os.cmd('git describe --always --tags')
|
||||
strip_re(data, "\n")
|
||||
else
|
||||
read_stripped(:filename.join(".git", "HEAD"))
|
||||
end
|
||||
_ -> ""
|
||||
end
|
||||
end
|
||||
|
||||
@@ -133,15 +153,22 @@ defmodule System do
|
||||
end
|
||||
|
||||
defp write_env_tmp_dir(env) do
|
||||
case System.get_env(env) do
|
||||
case get_env(env) do
|
||||
nil -> nil
|
||||
tmp -> write_tmp_dir tmp
|
||||
end
|
||||
end
|
||||
|
||||
defp write_tmp_dir(dir) do
|
||||
case File.stat(dir) do
|
||||
{ :ok, File.Stat[type: :directory, access: access] } when access in [:read_write, :write] -> dir
|
||||
case :file.read_file_info(dir) do
|
||||
{:ok, info} ->
|
||||
type_index = File.Stat.__index__ :type
|
||||
access_index = File.Stat.__index__ :access
|
||||
case { elem(info, type_index), elem(info, access_index) } do
|
||||
{ :directory, access } when access in [:read_write, :write] ->
|
||||
dir
|
||||
_ -> nil
|
||||
end
|
||||
{ :error, _ } -> nil
|
||||
end
|
||||
end
|
||||
@@ -177,21 +204,23 @@ defmodule System do
|
||||
end
|
||||
|
||||
@doc """
|
||||
This functions looks up an executable program given
|
||||
This function looks up an executable program given
|
||||
its name using the environment variable PATH on Unix
|
||||
and Windows.
|
||||
and Windows. It also considers the proper executable
|
||||
extension for each OS, so for Windows it will try to
|
||||
lookup files with `.com`, `.cmd` or similar extensions.
|
||||
|
||||
If `command` is a char list, a char list is returned.
|
||||
If `program` is a char list, a char list is returned.
|
||||
Returns a binary otherwise.
|
||||
"""
|
||||
@spec find_executable(char_list) :: char_list | nil
|
||||
@spec find_executable(String.t) :: String.t | nil
|
||||
def find_executable(command) when is_list(command) do
|
||||
:os.find_executable(command) || nil
|
||||
def find_executable(program) when is_list(program) do
|
||||
:os.find_executable(program) || nil
|
||||
end
|
||||
|
||||
def find_executable(command) do
|
||||
case :os.find_executable(to_char_list(command)) do
|
||||
def find_executable(program) do
|
||||
case :os.find_executable(to_char_list(program)) do
|
||||
false -> nil
|
||||
other -> list_to_binary(other)
|
||||
end
|
||||
@@ -294,19 +323,11 @@ defmodule System do
|
||||
def halt(status // 0, options // [])
|
||||
|
||||
def halt(status, options) when is_integer(status) or status == :abort do
|
||||
do_halt(status, options)
|
||||
:erlang.halt(status, options)
|
||||
end
|
||||
|
||||
def halt(status, options) do
|
||||
do_halt(to_char_list(status), options)
|
||||
end
|
||||
|
||||
# Support R15B
|
||||
if { :halt, 2 } in :erlang.module_info(:exports) do
|
||||
defp do_halt(status, options), do: :erlang.halt(status, options)
|
||||
else
|
||||
IO.puts "Using limited halt support. Upgrade to R15B01 or later is recommended."
|
||||
defp do_halt(status, _options), do: :erlang.halt(status)
|
||||
:erlang.halt(to_char_list(status), options)
|
||||
end
|
||||
|
||||
## Helpers
|
||||
|
||||
+36
-9
@@ -37,14 +37,16 @@ defmodule URI do
|
||||
the query string in steps.
|
||||
"""
|
||||
def query_decoder(q) when is_binary(q) do
|
||||
fn -> { do_decoder(&1), q } end
|
||||
fn(acc, fun) ->
|
||||
do_decoder(q, acc, fun)
|
||||
end
|
||||
end
|
||||
|
||||
defp do_decoder("") do
|
||||
:stop
|
||||
defp do_decoder("", acc, _fun) do
|
||||
acc
|
||||
end
|
||||
|
||||
defp do_decoder(q) do
|
||||
defp do_decoder(q, acc, fun) do
|
||||
next =
|
||||
case :binary.split(q, "&") do
|
||||
[first, rest] -> rest
|
||||
@@ -57,7 +59,7 @@ defmodule URI do
|
||||
[ key ] -> { decode(key), nil }
|
||||
end
|
||||
|
||||
{ current, next }
|
||||
do_decoder(next, fun.(current, acc), fun)
|
||||
end
|
||||
|
||||
defp pair({k, v}) do
|
||||
@@ -148,7 +150,8 @@ defmodule URI do
|
||||
scheme_specific(scheme, info)
|
||||
end
|
||||
|
||||
defp scheme_specific(scheme, info) do
|
||||
@doc false
|
||||
def scheme_module(scheme) do
|
||||
if scheme do
|
||||
module =
|
||||
try do
|
||||
@@ -158,10 +161,14 @@ defmodule URI do
|
||||
end
|
||||
|
||||
if module && Code.ensure_loaded?(module) do
|
||||
module.parse(default_port(info, module))
|
||||
else
|
||||
info
|
||||
module
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defp scheme_specific(scheme, info) do
|
||||
if module = scheme_module(scheme) do
|
||||
module.parse(default_port(info, module))
|
||||
else
|
||||
info
|
||||
end
|
||||
@@ -200,3 +207,23 @@ defmodule URI do
|
||||
:ok
|
||||
end
|
||||
end
|
||||
|
||||
defimpl Binary.Chars, for: URI.Info do
|
||||
def to_binary(uri) do
|
||||
result = ""
|
||||
|
||||
if module = URI.scheme_module(uri.scheme) do
|
||||
if module.default_port == uri.port, do: uri = uri.port(nil)
|
||||
end
|
||||
|
||||
if uri.scheme, do: result = result <> uri.scheme <> "://"
|
||||
if uri.userinfo, do: result = result <> uri.userinfo <> "@"
|
||||
if uri.host, do: result = result <> uri.host
|
||||
if uri.port, do: result = result <> ":" <> integer_to_binary(uri.port)
|
||||
if uri.path, do: result = result <> uri.path
|
||||
if uri.query, do: result = result <> "?" <> uri.query
|
||||
if uri.fragment, do: result = result <> "#" <> uri.fragment
|
||||
|
||||
result
|
||||
end
|
||||
end
|
||||
|
||||
+26
-18
@@ -57,32 +57,36 @@ defmodule String.Unicode do
|
||||
|
||||
# Downcase
|
||||
|
||||
def downcase(""), do: ""
|
||||
def downcase(string), do: do_downcase(string) |> list_to_binary
|
||||
|
||||
lc { codepoint, _upper, lower, _title } inlist codes, lower && lower != codepoint do
|
||||
def downcase(unquote(codepoint) <> rest) do
|
||||
unquote(lower) <> downcase(rest)
|
||||
defp do_downcase(unquote(codepoint) <> rest) do
|
||||
unquote(binary_to_list(lower)) ++ downcase(rest)
|
||||
end
|
||||
end
|
||||
|
||||
def downcase(<< char, rest :: binary >>) do
|
||||
<< char >> <> downcase(rest)
|
||||
defp do_downcase(<< char, rest :: binary >>) do
|
||||
[char|do_downcase(rest)]
|
||||
end
|
||||
|
||||
defp do_downcase(""), do: []
|
||||
|
||||
# Upcase
|
||||
|
||||
def upcase(""), do: ""
|
||||
def upcase(string), do: do_upcase(string) |> list_to_binary
|
||||
|
||||
lc { codepoint, upper, _lower, _title } inlist codes, upper && upper != codepoint do
|
||||
def upcase(unquote(codepoint) <> rest) do
|
||||
unquote(upper) <> upcase(rest)
|
||||
defp do_upcase(unquote(codepoint) <> rest) do
|
||||
unquote(binary_to_list(upper)) ++ do_upcase(rest)
|
||||
end
|
||||
end
|
||||
|
||||
def upcase(<< char, rest :: binary >>) do
|
||||
<< char >> <> upcase(rest)
|
||||
defp do_upcase(<< char, rest :: binary >>) do
|
||||
[char|do_upcase(rest)]
|
||||
end
|
||||
|
||||
defp do_upcase(""), do: []
|
||||
|
||||
# Titlecase once
|
||||
|
||||
def titlecase_once(""), do: { "", "" }
|
||||
@@ -109,23 +113,27 @@ defmodule String.Unicode do
|
||||
|
||||
def lstrip(other) when is_binary(other), do: other
|
||||
|
||||
def rstrip(""), do: ""
|
||||
|
||||
def rstrip(string) when is_binary(string) do
|
||||
do_rstrip(string, "")
|
||||
do_rstrip(string, [], [])
|
||||
end
|
||||
|
||||
lc codepoint inlist whitespace do
|
||||
defp do_rstrip(unquote(codepoint) <> rest, buffer) do
|
||||
do_rstrip(rest, unquote(codepoint) <> buffer)
|
||||
c = binary_to_list(codepoint) |> :lists.reverse
|
||||
|
||||
defp do_rstrip(unquote(codepoint) <> rest, acc1, acc2) do
|
||||
do_rstrip(rest, unquote(c) ++ (acc1 || acc2), acc2)
|
||||
end
|
||||
end
|
||||
|
||||
defp do_rstrip(<< char, rest :: binary >>, buffer) do
|
||||
<< buffer :: binary, char, do_rstrip(rest, "") :: binary >>
|
||||
defp do_rstrip(<< char, rest :: binary >>, nil, acc2) do
|
||||
do_rstrip(rest, nil, [char|acc2])
|
||||
end
|
||||
|
||||
defp do_rstrip(<<>>, _), do: <<>>
|
||||
defp do_rstrip(<< char, rest :: binary >>, acc1, _acc2) do
|
||||
do_rstrip(rest, nil, [char|acc1])
|
||||
end
|
||||
|
||||
defp do_rstrip(<<>>, _acc1, acc2), do: acc2 |> :lists.reverse |> list_to_binary
|
||||
|
||||
# Split
|
||||
|
||||
|
||||
@@ -24,4 +24,4 @@
|
||||
{verbose, false}
|
||||
]}.
|
||||
|
||||
{require_otp_vsn,"(R15|R16).*"}.
|
||||
{require_otp_vsn,"(R16).*"}.
|
||||
|
||||
+40
-21
@@ -1,11 +1,12 @@
|
||||
-module(elixir).
|
||||
-behaviour(application).
|
||||
-export([main/1, start_cli/0,
|
||||
scope_for_eval/1, eval/2, eval/3, eval/4,
|
||||
scope_for_eval/1, scope_for_eval/2,
|
||||
eval/2, eval/3, eval/4, file_type/1,
|
||||
eval_quoted/2, eval_quoted/3, eval_quoted/4,
|
||||
eval_forms/3, translate_forms/3]).
|
||||
-include("elixir.hrl").
|
||||
-compile({parse_transform, elixir_transform}).
|
||||
-include_lib("kernel/include/file.hrl").
|
||||
|
||||
%% Top level types
|
||||
-export_type([char_list/0, as_boolean/1]).
|
||||
@@ -43,40 +44,58 @@ start_cli() ->
|
||||
application:start(?MODULE),
|
||||
'Elixir.Kernel.CLI':main(init:get_plain_arguments()).
|
||||
|
||||
%% Helpers
|
||||
|
||||
file_type(File) ->
|
||||
case file:read_file_info(File) of
|
||||
{ ok, #file_info{type=Type} } -> { ok, Type };
|
||||
{ error, _ } = Error -> Error
|
||||
end.
|
||||
|
||||
%% EVAL HOOKS
|
||||
|
||||
scope_for_eval(Opts) ->
|
||||
case lists:keyfind(file, 1, Opts) of
|
||||
{ file, RawFile } -> File = to_binary(RawFile);
|
||||
false -> File = <<"nofile">>
|
||||
scope_for_eval(#elixir_scope{
|
||||
file = <<"nofile">>,
|
||||
local = nil,
|
||||
aliases = [],
|
||||
requires = elixir_dispatch:default_requires(),
|
||||
functions = elixir_dispatch:default_functions(),
|
||||
macros = elixir_dispatch:default_macros()
|
||||
}, Opts).
|
||||
|
||||
scope_for_eval(Scope, Opts) ->
|
||||
File = case lists:keyfind(file, 1, Opts) of
|
||||
{ file, RawFile } -> to_binary(RawFile);
|
||||
false -> Scope#elixir_scope.file
|
||||
end,
|
||||
|
||||
case lists:keyfind(delegate_locals_to, 1, Opts) of
|
||||
{ delegate_locals_to, Local } -> Local;
|
||||
false -> Local = nil
|
||||
Local = case lists:keyfind(delegate_locals_to, 1, Opts) of
|
||||
{ delegate_locals_to, LocalOpt } -> LocalOpt;
|
||||
false -> Scope#elixir_scope.local
|
||||
end,
|
||||
|
||||
case lists:keyfind(aliases, 1, Opts) of
|
||||
{ aliases, Aliases } -> Aliases;
|
||||
false -> Aliases = []
|
||||
Aliases = case lists:keyfind(aliases, 1, Opts) of
|
||||
{ aliases, AliasesOpt } -> AliasesOpt;
|
||||
false -> Scope#elixir_scope.aliases
|
||||
end,
|
||||
|
||||
case lists:keyfind(requires, 1, Opts) of
|
||||
{ requires, List } -> Requires = ordsets:from_list(List);
|
||||
false -> Requires = elixir_dispatch:default_requires()
|
||||
Requires = case lists:keyfind(requires, 1, Opts) of
|
||||
{ requires, List } -> ordsets:from_list(List);
|
||||
false -> Scope#elixir_scope.requires
|
||||
end,
|
||||
|
||||
case lists:keyfind(functions, 1, Opts) of
|
||||
{ functions, Functions } -> Functions;
|
||||
false -> Functions = elixir_dispatch:default_functions()
|
||||
Functions = case lists:keyfind(functions, 1, Opts) of
|
||||
{ functions, FunctionsOpt } -> FunctionsOpt;
|
||||
false -> Scope#elixir_scope.functions
|
||||
end,
|
||||
|
||||
case lists:keyfind(macros, 1, Opts) of
|
||||
{ macros, Macros } -> Macros;
|
||||
false -> Macros = elixir_dispatch:default_macros()
|
||||
Macros = case lists:keyfind(macros, 1, Opts) of
|
||||
{ macros, MacrosOpt } -> MacrosOpt;
|
||||
false -> Scope#elixir_scope.macros
|
||||
end,
|
||||
|
||||
#elixir_scope{
|
||||
Scope#elixir_scope{
|
||||
file=File, local=Local,
|
||||
macros=Macros, functions=Functions,
|
||||
requires=Requires, aliases=Aliases }.
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
-export([nesting_alias/2, last/1, concat/1, safe_concat/1,
|
||||
format_error/1, ensure_loaded/3, ensure_loaded/4, expand/3, store/4]).
|
||||
-include("elixir.hrl").
|
||||
-compile({parse_transform, elixir_transform}).
|
||||
|
||||
%% Store an alias in the given scope
|
||||
store(_Meta, New, New, S) -> S;
|
||||
@@ -57,7 +56,7 @@ expand({ '__aliases__', _Meta, List }, _Aliases) ->
|
||||
List.
|
||||
|
||||
expand_one(H, Aliases) ->
|
||||
Lookup = list_to_atom("Elixir-" ++ atom_to_list(H)),
|
||||
Lookup = list_to_atom("Elixir." ++ atom_to_list(H)),
|
||||
case lookup(Lookup, Aliases) of
|
||||
Lookup -> false;
|
||||
Else -> Else
|
||||
@@ -87,9 +86,9 @@ ensure_loaded(Line, File, Ref, FileModules) ->
|
||||
|
||||
last(Atom) ->
|
||||
Last = last(lists:reverse(atom_to_list(Atom)), []),
|
||||
list_to_atom("Elixir-" ++ Last).
|
||||
list_to_atom("Elixir." ++ Last).
|
||||
|
||||
last([$-|_], Acc) -> Acc;
|
||||
last([$.|_], Acc) -> Acc;
|
||||
last([H|T], Acc) -> last(T, [H|Acc]);
|
||||
last([], Acc) -> Acc.
|
||||
|
||||
@@ -118,12 +117,12 @@ nesting_alias(Prefix, Full) ->
|
||||
do_nesting([X|PreTail], [X|Tail], Acc) ->
|
||||
do_nesting(PreTail, Tail, [X|Acc]);
|
||||
do_nesting([], [H|_], Acc) ->
|
||||
{ list_to_atom("Elixir-" ++ H), concat(lists:reverse([H|Acc])) };
|
||||
{ list_to_atom("Elixir." ++ H), concat(lists:reverse([H|Acc])) };
|
||||
do_nesting(_, _, _Acc) ->
|
||||
false.
|
||||
|
||||
list_nesting(Atom) ->
|
||||
case string:tokens(atom_to_list(Atom), "-") of
|
||||
case string:tokens(atom_to_list(Atom), ".") of
|
||||
["Elixir"|T] -> T;
|
||||
_ -> []
|
||||
end.
|
||||
@@ -135,23 +134,18 @@ concat(Args) -> list_to_atom(raw_concat(Args)).
|
||||
safe_concat(Args) -> list_to_existing_atom(raw_concat(Args)).
|
||||
|
||||
raw_concat(['Elixir'|Args]) -> do_concat(Args);
|
||||
raw_concat([nil|Args]) -> do_concat(Args);
|
||||
raw_concat(Args) -> do_concat(Args).
|
||||
|
||||
do_concat(Args) ->
|
||||
Aliases = [to_partial(Arg) || Arg <- Args, Arg /= nil],
|
||||
Aliases = [to_partial(Arg) || Arg <- Args],
|
||||
"Elixir" ++ lists:concat(Aliases).
|
||||
|
||||
to_partial(Arg) when is_binary(Arg) -> to_partial(binary_to_list(Arg));
|
||||
to_partial(Arg) when is_atom(Arg) -> to_partial(atom_to_list(Arg));
|
||||
to_partial("Elixir-" ++ Arg) -> dot_to_dash([$-|Arg]);
|
||||
to_partial([$-|_] = Arg) -> dot_to_dash(Arg);
|
||||
to_partial(Arg) when is_list(Arg) -> [$-|dot_to_dash(Arg)].
|
||||
|
||||
dot_to_dash(List) ->
|
||||
[case X of
|
||||
$. -> $-;
|
||||
_ -> X
|
||||
end || X <- List].
|
||||
to_partial("Elixir." ++ Arg) -> [$.|Arg];
|
||||
to_partial([$.|_] = Arg) -> Arg;
|
||||
to_partial(Arg) when is_list(Arg) -> [$.|Arg].
|
||||
|
||||
%% Lookup an alias in the current scope.
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
%% Handle code related to rocket args, guard and -> matching
|
||||
%% for case, fn, receive and friends. try is handled in elixir_try.
|
||||
%% Handle code related to args, guard and -> matching for case,
|
||||
%% fn, receive and friends. try is handled in elixir_try.
|
||||
-module(elixir_clauses).
|
||||
-export([
|
||||
assigns/3, assigns_block/5, assigns_block/6, extract_last_guards/1,
|
||||
assigns/3, assigns_block/5, assigns_block/6, extract_splat_guards/1,
|
||||
get_pairs/4, get_pairs/5, match/3, extract_args/1, extract_guards/1]).
|
||||
-include("elixir.hrl").
|
||||
|
||||
@@ -14,7 +14,7 @@ get_pairs(Meta, Key, Clauses, S) ->
|
||||
get_pairs(Meta, Key, Clauses, S, AllowNil) ->
|
||||
case lists:keyfind(Key, 1, Clauses) of
|
||||
{ Key, { '->', _, Pairs } } ->
|
||||
[{ Key, Left, Right } || { Left, Right } <- Pairs];
|
||||
[{ Key, PMeta, Left, Right } || { Left, PMeta, Right } <- Pairs];
|
||||
{ Key, nil } when AllowNil ->
|
||||
[];
|
||||
{ Key, _ } ->
|
||||
@@ -25,9 +25,12 @@ get_pairs(Meta, Key, Clauses, S, AllowNil) ->
|
||||
|
||||
% Function for translating assigns.
|
||||
|
||||
assigns(Fun, Args, #elixir_scope{context=Context} = S) when Context /= match ->
|
||||
{ Result, NewS } = assigns(Fun, Args, S#elixir_scope{context=match, temp_vars=orddict:new()}),
|
||||
{ Result, NewS#elixir_scope{context=Context} };
|
||||
assigns(Fun, Args, #elixir_scope{context=Context, temp_vars=TempVars,
|
||||
backup_vars=BackupVars, vars=Vars} = S) when Context /= match ->
|
||||
{ Result, NewS } = assigns(Fun, Args, S#elixir_scope{context=match,
|
||||
temp_vars=ordsets:new(), backup_vars=Vars}),
|
||||
{ Result, NewS#elixir_scope{context=Context,
|
||||
temp_vars=TempVars, backup_vars=BackupVars} };
|
||||
|
||||
assigns(Fun, Args, S) -> Fun(Args, S).
|
||||
|
||||
@@ -77,13 +80,13 @@ extract_args({ Name, _, Args }) when is_atom(Name), is_atom(Args) -> { Name, []
|
||||
extract_args({ Name, _, Args }) when is_atom(Name), is_list(Args) -> { Name, Args };
|
||||
extract_args(_) -> error.
|
||||
|
||||
% Extract guards when it is in the last element of the args
|
||||
% Extract guards when multiple left side args are allowed.
|
||||
|
||||
extract_last_guards([]) -> { [], [] };
|
||||
extract_last_guards(Args) ->
|
||||
{ Left, Right } = elixir_tree_helpers:split_last(Args),
|
||||
{ Bare, Guards } = extract_guards(Right),
|
||||
{ Left ++ [Bare], Guards }.
|
||||
extract_splat_guards([{ 'when', _, [_,_|_] = Args }]) ->
|
||||
{ Left, Right } = elixir_tree_helpers:split_last(Args),
|
||||
{ Left, extract_or_clauses(Right, []) };
|
||||
extract_splat_guards(Else) ->
|
||||
{ Else, [] }.
|
||||
|
||||
% Function for translating macros with match style like case and receive.
|
||||
|
||||
@@ -95,15 +98,15 @@ match(Meta, Clauses, #elixir_scope{clause_vars=C1} = S) ->
|
||||
do_match(_Meta, [], S) ->
|
||||
{ [], S };
|
||||
|
||||
do_match(Meta, [DecoupledClause], S) ->
|
||||
{ TDecoupledClause, TS } = each_clause(Meta, DecoupledClause, S),
|
||||
do_match(_Meta, [DecoupledClause], S) ->
|
||||
{ TDecoupledClause, TS } = each_clause(DecoupledClause, S),
|
||||
{ [TDecoupledClause], TS };
|
||||
|
||||
do_match(Meta, DecoupledClauses, S) ->
|
||||
% Transform tree just passing the variables counter forward
|
||||
% and storing variables defined inside each clause.
|
||||
Transformer = fun(X, {Acc, CV}) ->
|
||||
{ TX, TAcc } = each_clause(Meta, X, Acc),
|
||||
{ TX, TAcc } = each_clause(X, Acc),
|
||||
{ TX, { merge_clauses_scope(S, TAcc), [TAcc#elixir_scope.clause_vars|CV] } }
|
||||
end,
|
||||
|
||||
@@ -163,21 +166,21 @@ expand_clauses(_Line, [], [], _FinalVars, Acc, S) ->
|
||||
|
||||
% Handle each key/value clause pair and translate them accordingly.
|
||||
|
||||
each_clause(Meta, { do, [Condition], Expr }, S) ->
|
||||
each_clause({ do, Meta, [Condition], Expr }, S) ->
|
||||
assigns_block(?line(Meta), fun elixir_translator:translate_each/2, Condition, [Expr], S);
|
||||
|
||||
each_clause(Meta, { else, [Condition], Expr }, S) ->
|
||||
each_clause({ else, Meta, [Condition], Expr }, S) ->
|
||||
assigns_block(?line(Meta), fun elixir_translator:translate_each/2, Condition, [Expr], S);
|
||||
|
||||
each_clause(Meta, { 'after', [Condition], Expr }, S) ->
|
||||
each_clause({ 'after', Meta, [Condition], Expr }, S) ->
|
||||
{ TCondition, SC } = elixir_translator:translate_each(Condition, S),
|
||||
{ TBody, SB } = elixir_translator:translate([Expr], SC),
|
||||
{ { clause, ?line(Meta), [TCondition], [], TBody }, SB };
|
||||
|
||||
each_clause(Meta, { Key, [_|_], _ }, S) when Key == do; Key == 'after' ->
|
||||
each_clause({ Key, Meta, [_|_], _ }, S) when Key == do; Key == 'after' ->
|
||||
elixir_errors:syntax_error(Meta, S#elixir_scope.file, "too many arguments given for ~ts", [Key]);
|
||||
|
||||
each_clause(Meta, { Key, _, _ }, S) ->
|
||||
each_clause({ Key, Meta, _, _ }, S) ->
|
||||
elixir_errors:syntax_error(Meta, S#elixir_scope.file, "invalid key ~ts", [Key]).
|
||||
|
||||
% Check if the given expression is a match tuple.
|
||||
@@ -253,6 +256,5 @@ merge_clauses_scope(S1, S2) ->
|
||||
counter=S2#elixir_scope.counter,
|
||||
extra_guards=S2#elixir_scope.extra_guards,
|
||||
super=S1#elixir_scope.super orelse S2#elixir_scope.super,
|
||||
caller=S1#elixir_scope.caller orelse S2#elixir_scope.caller,
|
||||
name_args=S1#elixir_scope.name_args orelse S2#elixir_scope.name_args
|
||||
caller=S1#elixir_scope.caller orelse S2#elixir_scope.caller
|
||||
}.
|
||||
@@ -5,11 +5,12 @@
|
||||
-behavior(gen_server).
|
||||
-record(elixir_code_server, {
|
||||
argv=[],
|
||||
compilation_status=ok,
|
||||
loaded=[],
|
||||
at_exit=[],
|
||||
pool=[],
|
||||
counter=0,
|
||||
compiler_options=[{docs,true},{debug_info,true}],
|
||||
compiler_options=[{docs,true},{debug_info,true},{warnings_as_errors,false}],
|
||||
waiting=[]
|
||||
}).
|
||||
|
||||
@@ -58,6 +59,9 @@ handle_call(argv, _From, Config) ->
|
||||
handle_call(compiler_options, _From, Config) ->
|
||||
{ reply, Config#elixir_code_server.compiler_options, Config };
|
||||
|
||||
handle_call(compilation_status, _From, Config) ->
|
||||
{ reply, Config#elixir_code_server.compilation_status, Config };
|
||||
|
||||
handle_call(retrieve_module_name, _From, Config) ->
|
||||
case Config#elixir_code_server.pool of
|
||||
[H|T] ->
|
||||
@@ -80,6 +84,15 @@ handle_cast({ compiler_options, Options }, Config) ->
|
||||
Final = orddict:merge(fun(_,_,V) -> V end, Config#elixir_code_server.compiler_options, Options),
|
||||
{ noreply, Config#elixir_code_server{compiler_options=Final} };
|
||||
|
||||
handle_cast(register_warning, Config) ->
|
||||
case orddict:find(warnings_as_errors, Config#elixir_code_server.compiler_options) of
|
||||
{ ok, true } -> { noreply, Config#elixir_code_server{compilation_status=error} };
|
||||
_ -> { noreply, Config }
|
||||
end;
|
||||
|
||||
handle_cast(reset_warnings, Config) ->
|
||||
{ noreply, Config#elixir_code_server{compilation_status=ok} };
|
||||
|
||||
handle_cast({ loaded, Path }, Config) ->
|
||||
Current = Config#elixir_code_server.loaded,
|
||||
case orddict:find(Path, Current) of
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
-module(elixir_compiler).
|
||||
-export([get_opts/0, get_opt/1, get_opt/2, string/2, file/1, file_to_path/2]).
|
||||
-export([get_opts/0, get_opt/1, get_opt/2, string/2, quoted/2, file/1, file_to_path/2]).
|
||||
-export([core/0, module/3, eval_forms/4, format_error/1]).
|
||||
-include("elixir.hrl").
|
||||
-compile({parse_transform, elixir_transform}).
|
||||
|
||||
%% Public API
|
||||
|
||||
@@ -209,11 +208,10 @@ core_main() ->
|
||||
[
|
||||
"lib/elixir/lib/kernel.ex",
|
||||
"lib/elixir/lib/keyword.ex",
|
||||
"lib/elixir/lib/module.ex",
|
||||
"lib/elixir/lib/list.ex",
|
||||
"lib/elixir/lib/kernel/typespec.ex",
|
||||
"lib/elixir/lib/module.ex",
|
||||
"lib/elixir/lib/record.ex",
|
||||
"lib/elixir/lib/record/extractor.ex",
|
||||
"lib/elixir/lib/macro.ex",
|
||||
"lib/elixir/lib/macro/env.ex",
|
||||
"lib/elixir/lib/exception.ex",
|
||||
@@ -222,18 +220,14 @@ core_main() ->
|
||||
"lib/elixir/lib/enum.ex",
|
||||
"lib/elixir/lib/binary/inspect.ex",
|
||||
"lib/elixir/lib/binary/chars.ex",
|
||||
"lib/elixir/lib/list/chars.ex",
|
||||
"lib/elixir/lib/io.ex",
|
||||
"lib/elixir/lib/file.ex",
|
||||
"lib/elixir/lib/path.ex",
|
||||
"lib/elixir/lib/access.ex",
|
||||
"lib/elixir/lib/regex.ex",
|
||||
"lib/elixir/lib/system.ex",
|
||||
"lib/elixir/lib/process.ex",
|
||||
"lib/elixir/lib/kernel/cli.ex",
|
||||
"lib/elixir/lib/kernel/error_handler.ex",
|
||||
"lib/elixir/lib/kernel/parallel_compiler.ex",
|
||||
"lib/elixir/lib/kernel/record_rewriter.ex"
|
||||
"lib/elixir/lib/kernel/record_rewriter.ex",
|
||||
"lib/elixir/lib/module/dispatch_tracker.ex"
|
||||
].
|
||||
|
||||
%% ERROR HANDLING
|
||||
@@ -253,4 +247,4 @@ format_errors(File, Errors) ->
|
||||
format_warnings(Bootstrap, File, Warnings) ->
|
||||
lists:foreach(fun ({_, Each}) ->
|
||||
lists:foreach(fun (Warning) -> elixir_errors:handle_file_warning(Bootstrap, File, Warning) end, Each)
|
||||
end, Warnings).
|
||||
end, Warnings).
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
-module(elixir_def).
|
||||
-export([table/1,
|
||||
clauses_table/1,
|
||||
build_table/1,
|
||||
delete_table/1,
|
||||
setup/1,
|
||||
cleanup/1,
|
||||
reset_last/1,
|
||||
lookup_definition/2,
|
||||
delete_definition/2,
|
||||
@@ -15,22 +15,25 @@
|
||||
unwrap_stored_definitions/2,
|
||||
format_error/1]).
|
||||
-include("elixir.hrl").
|
||||
-compile({parse_transform, elixir_transform}).
|
||||
|
||||
-define(attr, '__def_table').
|
||||
-define(clauses_attr, '__clauses_table').
|
||||
|
||||
%% Table management functions. Called internally.
|
||||
|
||||
table(Module) -> ?atom_concat([f, Module]).
|
||||
clauses_table(Module) -> ?atom_concat([c, Module]).
|
||||
table(Module) ->
|
||||
ets:lookup_element(Module, ?attr, 2).
|
||||
|
||||
build_table(Module) ->
|
||||
FunctionsTable = table(Module),
|
||||
ClausesTable = clauses_table(Module),
|
||||
ets:new(FunctionsTable, [set, named_table, public]),
|
||||
ets:new(ClausesTable, [bag, named_table, public]),
|
||||
clauses_table(Module) ->
|
||||
ets:lookup_element(Module, ?clauses_attr, 2).
|
||||
|
||||
setup(Module) ->
|
||||
ets:insert(Module, { ?attr, ets:new(Module, [set, public]) }),
|
||||
ets:insert(Module, { ?clauses_attr, ets:new(Module, [bag, public]) }),
|
||||
reset_last(Module),
|
||||
{ FunctionsTable, ClausesTable }.
|
||||
ok.
|
||||
|
||||
delete_table(Module) ->
|
||||
cleanup(Module) ->
|
||||
ets:delete(table(Module)),
|
||||
ets:delete(clauses_table(Module)).
|
||||
|
||||
@@ -97,7 +100,7 @@ store_definition(Kind, Line, CheckClauses, Module, Call, Body, RawS) ->
|
||||
|
||||
{ Name, Args } = case elixir_clauses:extract_args(NameAndArgs) of
|
||||
error ->
|
||||
Format = [Kind, 'Elixir.Macro':to_binary(NameAndArgs)],
|
||||
Format = [Kind, 'Elixir.Macro':to_string(NameAndArgs)],
|
||||
elixir_errors:syntax_error(Line, S#elixir_scope.file, "invalid syntax in ~ts ~ts", Format);
|
||||
Tuple ->
|
||||
Tuple
|
||||
@@ -106,12 +109,14 @@ store_definition(Kind, Line, CheckClauses, Module, Call, Body, RawS) ->
|
||||
assert_no_aliases_name(Line, Name, Args, S),
|
||||
store_definition(Kind, Line, CheckClauses, Module, Name, Args, Guards, Body, S).
|
||||
|
||||
store_definition(Kind, Line, _CheckClauses, nil, _Name, _Args, _Guards, _Body, #elixir_scope{} = S) ->
|
||||
elixir_errors:syntax_error(Line, S#elixir_scope.file, "cannot define function outside module, invalid scope for ~ts", [Kind]);
|
||||
store_definition(Kind, Line, _CheckClauses, nil, _Name, _Args, _Guards, _Body, #elixir_scope{file=File}) ->
|
||||
elixir_errors:form_error(Line, File, ?MODULE, { no_module, Kind });
|
||||
|
||||
store_definition(Kind, Line, CheckClauses, Module, Name, Args, Guards, Body, #elixir_scope{} = DS) ->
|
||||
Arity = length(Args),
|
||||
S = DS#elixir_scope{function={Name,Arity}, module=Module},
|
||||
Tuple = { Name, Arity },
|
||||
S = DS#elixir_scope{module=Module, function=Tuple},
|
||||
elixir_tracker:record_definition(Tuple, Kind, Module),
|
||||
|
||||
CO = elixir_compiler:get_opts(),
|
||||
Location = retrieve_file(Line, Module, S, CO),
|
||||
@@ -119,14 +124,18 @@ store_definition(Kind, Line, CheckClauses, Module, Name, Args, Guards, Body, #el
|
||||
|
||||
{ Function, Defaults, TS } = translate_definition(Kind, Line, Name, Args, Guards, Body, S),
|
||||
|
||||
DefaultsLength = length(Defaults),
|
||||
elixir_tracker:record_defaults(Tuple, Kind, Module, DefaultsLength),
|
||||
|
||||
File = TS#elixir_scope.file,
|
||||
Table = table(Module),
|
||||
CTable = clauses_table(Module),
|
||||
|
||||
compile_super(Module, TS),
|
||||
store_each(CheckClauses, Kind, File, Location,
|
||||
Table, CTable, length(Defaults), Function),
|
||||
check_previous_defaults(Table, Line, Name, Arity, Kind, DefaultsLength, S),
|
||||
|
||||
store_each(CheckClauses, Kind, File, Location,
|
||||
Table, CTable, DefaultsLength, Function),
|
||||
[store_each(false, Kind, File, Location, Table, CTable, 0,
|
||||
default_function_for(Kind, Name, Default)) || Default <- Defaults],
|
||||
|
||||
@@ -191,23 +200,18 @@ translate_definition(Kind, Line, Name, RawArgs, RawGuards, RawBody, S) when is_i
|
||||
Function = { function, Line, Name, Arity, Clauses },
|
||||
{ Function, Defaults, TS }.
|
||||
|
||||
translate_clause(_Line, _Kind, _Unpacked, _Guards, nil, S) ->
|
||||
translate_clause(_Line, _Kind, _Unpacked, [], nil, S) ->
|
||||
{ [], S };
|
||||
|
||||
translate_clause(Line, Kind, _Unpacked, _Guards, nil, #elixir_scope{file=File}) ->
|
||||
elixir_errors:syntax_error(Line, File, "missing keyword do in ~ts", [Kind]);
|
||||
|
||||
translate_clause(Line, Kind, Unpacked, Guards, Body, S) ->
|
||||
Expr = expr_from_body(Line, Body),
|
||||
|
||||
{ TClause, TS } = elixir_clauses:assigns_block(Line,
|
||||
fun elixir_translator:translate/2, Unpacked, [Expr], Guards, S),
|
||||
|
||||
%% Add names to args
|
||||
NClause = case TS#elixir_scope.name_args of
|
||||
true ->
|
||||
NArgs = elixir_def_overridable:assign_args(Line, element(3, TClause), TS),
|
||||
setelement(3, TClause, NArgs);
|
||||
false -> TClause
|
||||
end,
|
||||
|
||||
%% Set __CALLER__ if used
|
||||
FClause = case is_macro(Kind) andalso TS#elixir_scope.caller of
|
||||
true ->
|
||||
@@ -215,8 +219,8 @@ translate_clause(Line, Kind, Unpacked, Guards, Body, S) ->
|
||||
{ 'var', Line, '__CALLER__' },
|
||||
?wrap_call(Line, elixir_scope, to_ex_env, [{ var, Line, '_@CALLER' }])
|
||||
},
|
||||
setelement(5, NClause, [FBody|element(5, NClause)]);
|
||||
false -> NClause
|
||||
setelement(5, TClause, [FBody|element(5, TClause)]);
|
||||
false -> TClause
|
||||
end,
|
||||
|
||||
{ [FClause], TS }.
|
||||
@@ -306,8 +310,8 @@ store_each(Check, Kind, File, Location, Table, CTable, Defaults, {function, Line
|
||||
FinalLocation = StoredLocation,
|
||||
FinalDefaults = max(Defaults, StoredDefaults),
|
||||
check_valid_kind(Line, File, Name, Arity, Kind, StoredKind),
|
||||
check_valid_defaults(Line, File, Name, Arity, Defaults, StoredDefaults),
|
||||
(Check and StoredCheck) andalso check_valid_clause(Line, File, Name, Arity, Table);
|
||||
(Check and StoredCheck) andalso check_valid_clause(Line, File, Name, Arity, Kind, Table),
|
||||
check_valid_defaults(Line, File, Name, Arity, Kind, Defaults, StoredDefaults);
|
||||
[] ->
|
||||
FinalLine = Line,
|
||||
FinalLocation = Location,
|
||||
@@ -324,38 +328,61 @@ check_valid_kind(Line, File, Name, Arity, Kind, StoredKind) ->
|
||||
elixir_errors:form_error(Line, File, ?MODULE,
|
||||
{ changed_kind, { Name, Arity, StoredKind, Kind } }).
|
||||
|
||||
check_valid_clause(Line, File, Name, Arity, Table) ->
|
||||
check_valid_clause(Line, File, Name, Arity, Kind, Table) ->
|
||||
case ets:lookup_element(Table, last, 2) of
|
||||
{Name,Arity} -> [];
|
||||
[] -> [];
|
||||
_ ->
|
||||
elixir_errors:handle_file_warning(File, { Line, ?MODULE,
|
||||
{ override_function, { Name, Arity } } })
|
||||
{ override_function, { Kind, Name, Arity } } })
|
||||
end.
|
||||
|
||||
check_valid_defaults(_Line, _File, _Name, _Arity, 0, _) -> [];
|
||||
check_valid_defaults(Line, File, Name, Arity, _, 0) ->
|
||||
elixir_errors:handle_file_warning(File, { Line, ?MODULE, { out_of_order_defaults, { Name, Arity } } });
|
||||
check_valid_defaults(Line, File, Name, Arity, _, _) ->
|
||||
elixir_errors:handle_file_warning(File, { Line, ?MODULE, { clauses_with_defaults, { Name, Arity } } }).
|
||||
check_valid_defaults(_Line, _File, _Name, _Arity, _Kind, 0, _) -> [];
|
||||
check_valid_defaults(Line, File, Name, Arity, Kind, _, 0) ->
|
||||
elixir_errors:handle_file_warning(File, { Line, ?MODULE, { out_of_order_defaults, { Kind, Name, Arity } } });
|
||||
check_valid_defaults(Line, File, Name, Arity, Kind, _, _) ->
|
||||
elixir_errors:form_error(Line, File, ?MODULE, { clauses_with_defaults, { Kind, Name, Arity } }).
|
||||
|
||||
check_previous_defaults(Table, Line, Name, Arity, Kind, Defaults, S) ->
|
||||
Matches = ets:match(Table, { { Name, '$2' }, '$1', '_', '_', '_', '_', '$3' }),
|
||||
[ begin
|
||||
elixir_errors:form_error(Line, S#elixir_scope.file, ?MODULE,
|
||||
{ defs_with_defaults, Name, { Kind, Arity }, { K, A } })
|
||||
end || [K, A, D] <- Matches, A /= Arity, D /= 0, defaults_conflict(A, D, Arity, Defaults)].
|
||||
|
||||
defaults_conflict(A, D, Arity, Defaults) ->
|
||||
((Arity >= (A - D)) andalso (Arity < A)) orelse
|
||||
((A >= (Arity - Defaults)) andalso (A < Arity)).
|
||||
|
||||
assert_no_aliases_name(Line, '__aliases__', [Atom], #elixir_scope{file=File}) when is_atom(Atom) ->
|
||||
Message = "function names should start with lowercase characters or underscore, invalid name ~ts",
|
||||
elixir_errors:syntax_error(Line, File, Message, [atom_to_binary(Atom, utf8)]);
|
||||
elixir_errors:syntax_error(Line, File, Message, [Atom]);
|
||||
|
||||
assert_no_aliases_name(_Meta, _Aliases, _Args, _S) ->
|
||||
ok.
|
||||
|
||||
%% Format errors
|
||||
|
||||
format_error({clauses_with_defaults,{Name,Arity}}) ->
|
||||
io_lib:format("function ~ts/~B has default values and multiple clauses, use a separate clause for declaring defaults", [Name, Arity]);
|
||||
format_error({no_module,{Kind,Name,Arity}}) ->
|
||||
io_lib:format("cannot define function outside module, invalid scope for ~ts ~ts/~B", [Kind, Name, Arity]);
|
||||
|
||||
format_error({out_of_order_defaults,{Name,Arity}}) ->
|
||||
io_lib:format("clause with defaults should be the first clause in function ~ts/~B", [Name, Arity]);
|
||||
format_error({defs_with_defaults, Name, { Kind, Arity }, { K, A } }) when Arity > A ->
|
||||
io_lib:format("~ts ~ts/~B defaults conflicts with ~ts ~ts/~B",
|
||||
[Kind, Name, Arity, K, Name, A]);
|
||||
|
||||
format_error({override_function,{Name,Arity}}) ->
|
||||
io_lib:format("trying to override previously defined function ~ts/~B", [Name, Arity]);
|
||||
format_error({defs_with_defaults, Name, { Kind, Arity }, { K, A } }) when Arity < A ->
|
||||
io_lib:format("~ts ~ts/~B conflicts with defaults from ~ts ~ts/~B",
|
||||
[Kind, Name, Arity, K, Name, A]);
|
||||
|
||||
format_error({clauses_with_defaults,{Kind,Name,Arity}}) ->
|
||||
io_lib:format("~ts ~ts/~B has default values and multiple clauses, "
|
||||
"use a separate clause for declaring defaults", [Kind, Name, Arity]);
|
||||
|
||||
format_error({out_of_order_defaults,{Kind,Name,Arity}}) ->
|
||||
io_lib:format("clause with defaults should be the first clause in ~ts ~ts/~B", [Kind, Name, Arity]);
|
||||
|
||||
format_error({override_function,{Kind,Name,Arity}}) ->
|
||||
io_lib:format("trying to override previously defined ~ts ~ts/~B", [Kind, Name, Arity]);
|
||||
|
||||
format_error({changed_kind,{Name,Arity,Previous,Current}}) ->
|
||||
io_lib:format("~ts ~ts/~B already defined as ~ts", [Current, Name, Arity, Previous]).
|
||||
io_lib:format("~ts ~ts/~B already defined as ~ts", [Current, Name, Arity, Previous]).
|
||||
|
||||
@@ -1,20 +1,14 @@
|
||||
%% Module responsible for local invocation of macros and functions.
|
||||
-module(elixir_def_local).
|
||||
-export([
|
||||
macro_for/3,
|
||||
function_for/3,
|
||||
format_error/1,
|
||||
check_unused_local/3
|
||||
]).
|
||||
-export([macro_for/3, function_for/3]).
|
||||
-include("elixir.hrl").
|
||||
|
||||
%% Used by elixir_dispatch, returns false if no macro is found
|
||||
macro_for(_Tuple, _All, nil) -> false;
|
||||
macro_for(_Tuple, _All, #elixir_scope{module=nil}) -> false;
|
||||
|
||||
macro_for(Tuple, All, Module) ->
|
||||
macro_for(Tuple, All, #elixir_scope{module=Module,function=Function}) ->
|
||||
try elixir_def:lookup_definition(Module, Tuple) of
|
||||
{ { Tuple, Kind, Line, _, _, _, _ }, Clauses } when Kind == defmacro; All, Kind == defmacrop ->
|
||||
elixir_import:record(Tuple, Module, Module),
|
||||
elixir_tracker:record_local(Tuple, Module, Function),
|
||||
get_function(Line, Module, Clauses);
|
||||
_ ->
|
||||
false
|
||||
@@ -22,20 +16,19 @@ macro_for(Tuple, All, Module) ->
|
||||
error:badarg -> false
|
||||
end.
|
||||
|
||||
%% Used on runtime by rewritten clauses, raises an error if function is not found
|
||||
function_for(Module, Name, Arity) ->
|
||||
Tuple = { Name, Arity },
|
||||
case elixir_def:lookup_definition(Module, Tuple) of
|
||||
{ { Tuple, _, Line, _, _, _, _ }, Clauses } ->
|
||||
elixir_import:record(Tuple, Module, Module),
|
||||
%% There is no need to record such calls
|
||||
%% since they come from funtions that were
|
||||
%% already analyzed at compilation time.
|
||||
get_function(Line, Module, Clauses);
|
||||
_ ->
|
||||
[_|T] = erlang:get_stacktrace(),
|
||||
erlang:raise(error, undef, [{Module,Name,Arity,[]}|T])
|
||||
end.
|
||||
|
||||
%% Helpers
|
||||
|
||||
get_function(Line, Module, Clauses) ->
|
||||
RewrittenClauses = [rewrite_clause(Clause, Module) || Clause <- Clauses],
|
||||
Fun = { 'fun', Line, {clauses, RewrittenClauses } },
|
||||
@@ -66,53 +59,4 @@ rewrite_clause(Tuple, Module) when is_tuple(Tuple) ->
|
||||
rewrite_clause(List, Module) when is_list(List) ->
|
||||
[rewrite_clause(Item, Module) || Item <- List];
|
||||
|
||||
rewrite_clause(Else, _) -> Else.
|
||||
|
||||
%% Error handling
|
||||
|
||||
check_unused_local(File, Recorded, Private) ->
|
||||
[check_unused_local(Fun, Kind, Line, File, Defaults, Recorded) ||
|
||||
{ Fun, Kind, Line, true, Defaults } <- Private].
|
||||
|
||||
check_unused_local(Fun, Kind, Line, File, 0, Recorded) ->
|
||||
not(lists:member(Fun, Recorded)) andalso
|
||||
elixir_errors:handle_file_warning(File, { Line, ?MODULE, { unused_def, Kind, Fun } });
|
||||
|
||||
check_unused_local({ _, Arity } = Fun, Kind, Line, File, Defaults, Recorded) when Defaults > 0 ->
|
||||
Min = Arity - Defaults,
|
||||
Max = Arity,
|
||||
|
||||
Invoked = [A || { _, A } <- Recorded, A >= Min, A =< Max],
|
||||
|
||||
case Invoked of
|
||||
[] ->
|
||||
elixir_errors:handle_file_warning(File, { Line, ?MODULE, { unused_def, Kind, Fun } });
|
||||
_ ->
|
||||
UnusedArgs = lists:min(Invoked) - Min,
|
||||
if
|
||||
UnusedArgs == 0 ->
|
||||
ok;
|
||||
UnusedArgs == Defaults ->
|
||||
elixir_errors:handle_file_warning(File, { Line, ?MODULE, { unused_args, Fun } });
|
||||
true ->
|
||||
elixir_errors:handle_file_warning(File, { Line, ?MODULE, { unused_args, Fun, UnusedArgs } })
|
||||
end
|
||||
end;
|
||||
|
||||
check_unused_local(_Fun, _Kind, _Line, _File, _Defaults, _Recorded) ->
|
||||
ok.
|
||||
|
||||
format_error({unused_def,defp,{Name, Arity}}) ->
|
||||
io_lib:format("function ~ts/~B is unused", [Name, Arity]);
|
||||
|
||||
format_error({unused_args,{Name, Arity}}) ->
|
||||
io_lib:format("default arguments in ~ts/~B are never used", [Name, Arity]);
|
||||
|
||||
format_error({unused_args,{Name, Arity},1}) ->
|
||||
io_lib:format("the first default argument in ~ts/~B is never used", [Name, Arity]);
|
||||
|
||||
format_error({unused_args,{Name, Arity},Count}) ->
|
||||
io_lib:format("the first ~B default arguments in ~ts/~B are never used", [Count, Name, Arity]);
|
||||
|
||||
format_error({unused_def,defmacrop,{Name, Arity}}) ->
|
||||
io_lib:format("macro ~ts/~B is unused", [Name, Arity]).
|
||||
rewrite_clause(Else, _) -> Else.
|
||||
@@ -1,9 +1,8 @@
|
||||
% Holds the logic responsible for defining overridable functions and handling super.
|
||||
-module(elixir_def_overridable).
|
||||
-export([store_pending/1, is_defined/2, ensure_defined/4,
|
||||
assign_args/3, retrieve_args/3, name/2, store/3, format_error/1]).
|
||||
assign_args/3, name/2, store/3, format_error/1]).
|
||||
-include("elixir.hrl").
|
||||
-compile({parse_transform, elixir_transform}).
|
||||
|
||||
overridable(Module) ->
|
||||
ets:lookup_element(elixir_module:data_table(Module), '__overridable', 2).
|
||||
@@ -16,25 +15,16 @@ overridable(Module, Value) ->
|
||||
is_defined(Module, Tuple) ->
|
||||
Overridable = overridable(Module),
|
||||
case orddict:find(Tuple, Overridable) of
|
||||
{ ok, { _, _, _ } } -> true;
|
||||
{ ok, { _, _, _, _ } } -> true;
|
||||
_ -> false
|
||||
end.
|
||||
|
||||
ensure_defined(Meta, Module, Tuple, S) ->
|
||||
case is_defined(Module, Tuple) of
|
||||
true -> [];
|
||||
true -> ok;
|
||||
_ -> elixir_errors:form_error(Meta, S#elixir_scope.file, ?MODULE, { no_super, Module, Tuple })
|
||||
end.
|
||||
|
||||
%% Retrieve args defined for the given arity.
|
||||
|
||||
retrieve_args(Meta, Arity, S) ->
|
||||
Line = ?line(Meta),
|
||||
{
|
||||
[ { var, Line, super_arg(X) } || X <- lists:seq(1, Arity) ],
|
||||
S#elixir_scope{name_args=true}
|
||||
}.
|
||||
|
||||
%% Assign pseudo variables to the given vars.
|
||||
|
||||
assign_args(Line, Args, S) when is_integer(Line) ->
|
||||
@@ -54,7 +44,7 @@ name(Module, Function) ->
|
||||
name(Module, Function, overridable(Module)).
|
||||
|
||||
name(_Module, { Name, _ } = Function, Overridable) ->
|
||||
{ Count, _, _ } = orddict:fetch(Function, Overridable),
|
||||
{ Count, _, _, _ } = orddict:fetch(Function, Overridable),
|
||||
?atom_concat([Name, " (overridable ", Count, ")"]).
|
||||
|
||||
%% Store
|
||||
@@ -62,9 +52,9 @@ name(_Module, { Name, _ } = Function, Overridable) ->
|
||||
store(Module, Function, GenerateName) ->
|
||||
Overridable = overridable(Module),
|
||||
case orddict:fetch(Function, Overridable) of
|
||||
{ _Count, _Clause, true } -> ok;
|
||||
{ Count, Clause, false } ->
|
||||
overridable(Module, orddict:store(Function, { Count, Clause, true }, Overridable)),
|
||||
{ _Count, _Clause, _Neighbours, true } -> ok;
|
||||
{ Count, Clause, Neighbours, false } ->
|
||||
overridable(Module, orddict:store(Function, { Count, Clause, Neighbours, true }, Overridable)),
|
||||
{ { { Name, Arity }, Kind, Line, File, _Check, Location, Defaults }, Clauses } = Clause,
|
||||
|
||||
{ FinalKind, FinalName } = case GenerateName of
|
||||
@@ -72,6 +62,8 @@ store(Module, Function, GenerateName) ->
|
||||
false -> { Kind, Name }
|
||||
end,
|
||||
|
||||
'Elixir.Module.DispatchTracker':reattach(Module, Kind, { Name, Arity }, Neighbours),
|
||||
|
||||
Def = { function, Line, FinalName, Arity, Clauses },
|
||||
elixir_def:store_each(false, FinalKind, File, Location,
|
||||
elixir_def:table(Module), elixir_def:clauses_table(Module), Defaults, Def)
|
||||
@@ -80,13 +72,13 @@ store(Module, Function, GenerateName) ->
|
||||
%% Store pending declarations that were not manually made concrete.
|
||||
|
||||
store_pending(Module) ->
|
||||
[store(Module, X, false) || { X, { _, _, false } } <- overridable(Module),
|
||||
[store(Module, X, false) || { X, { _, _, _, false } } <- overridable(Module),
|
||||
not 'Elixir.Module':'defines?'(Module, X)].
|
||||
|
||||
%% Error handling
|
||||
|
||||
format_error({ no_super, Module, { Name, Arity } }) ->
|
||||
Bins = [ format_fa(X) || { X, { _, _, _ } } <- overridable(Module)],
|
||||
Bins = [format_fa(X) || { X, { _, _, _, _ } } <- overridable(Module)],
|
||||
Joined = 'Elixir.Enum':join(Bins, <<", ">>),
|
||||
io_lib:format("no super defined for ~ts/~B in module ~ts. Overridable functions available are: ~ts",
|
||||
[Name, Arity, elixir_errors:inspect(Module), Joined]).
|
||||
|
||||
@@ -8,25 +8,29 @@
|
||||
expand_import/6, expand_require/6, find_import/4,
|
||||
format_error/1, in_erlang_functions/0, in_erlang_macros/0]).
|
||||
-include("elixir.hrl").
|
||||
-compile({parse_transform, elixir_transform}).
|
||||
|
||||
-import(ordsets, [is_element/2]).
|
||||
-define(BUILTIN, 'Elixir.Kernel').
|
||||
-define(builtin, 'Elixir.Kernel').
|
||||
|
||||
default_functions() ->
|
||||
[ { ?BUILTIN, ordsets:union(in_elixir_functions(), in_erlang_functions()) } ].
|
||||
[ { ?builtin, elixir_imported_functions() } ].
|
||||
default_macros() ->
|
||||
[ { ?BUILTIN, ordsets:union(in_elixir_macros(), in_erlang_macros()) } ].
|
||||
[ { ?builtin, elixir_imported_macros() } ].
|
||||
default_requires() ->
|
||||
[ ?BUILTIN, 'Elixir.Kernel.Typespec', 'Elixir.Record' ].
|
||||
[ ?builtin, 'Elixir.Kernel.Typespec', 'Elixir.Record' ].
|
||||
|
||||
find_import(Meta, Name, Arity, S) ->
|
||||
Tuple = { Name, Arity },
|
||||
|
||||
case find_dispatch(Meta, Tuple, S) of
|
||||
{ function, Receiver } -> Receiver;
|
||||
{ macro, Receiver } -> Receiver;
|
||||
_ -> false
|
||||
{ function, Receiver } ->
|
||||
elixir_tracker:record_import(Tuple, Receiver, S#elixir_scope.module, S#elixir_scope.function),
|
||||
Receiver;
|
||||
{ macro, Receiver } ->
|
||||
elixir_tracker:record_import(Tuple, Receiver, S#elixir_scope.module, S#elixir_scope.function),
|
||||
Receiver;
|
||||
_ ->
|
||||
false
|
||||
end.
|
||||
|
||||
%% Function retrieval
|
||||
@@ -35,15 +39,14 @@ import_function(Meta, Name, Arity, S) ->
|
||||
Tuple = { Name, Arity },
|
||||
case find_dispatch(Meta, Tuple, S) of
|
||||
{ function, Receiver } ->
|
||||
elixir_import:record(Tuple, Receiver, S#elixir_scope.module),
|
||||
elixir_tracker:record_import(Tuple, Receiver, S#elixir_scope.module, S#elixir_scope.function),
|
||||
remote_function(Meta, Receiver, Name, Arity, S);
|
||||
{ macro, _Receiver } ->
|
||||
false;
|
||||
{ import, Receiver } ->
|
||||
require_function(Meta, Receiver, Name, Arity, S);
|
||||
nomatch ->
|
||||
Module = S#elixir_scope.module,
|
||||
elixir_import:record({ Name, Arity }, Module, Module),
|
||||
elixir_tracker:record_local(Tuple, S#elixir_scope.module, S#elixir_scope.function),
|
||||
{ { 'fun', ?line(Meta), { function, Name, Arity } }, S }
|
||||
end.
|
||||
|
||||
@@ -52,7 +55,9 @@ require_function(Meta, Receiver, Name, Arity, S) ->
|
||||
|
||||
case is_element(Tuple, get_optional_macros(Receiver)) of
|
||||
true -> false;
|
||||
false -> remote_function(Meta, Receiver, Name, Arity, S)
|
||||
false ->
|
||||
elixir_tracker:record_remote(Tuple, Receiver, S#elixir_scope.module, S#elixir_scope.function),
|
||||
remote_function(Meta, Receiver, Name, Arity, S)
|
||||
end.
|
||||
|
||||
%% Function dispatch
|
||||
@@ -64,20 +69,21 @@ dispatch_import(Meta, Name, Args, S, Callback) ->
|
||||
|
||||
case find_dispatch(Meta, Tuple, S) of
|
||||
{ function, Receiver } ->
|
||||
elixir_import:record(Tuple, Receiver, Module),
|
||||
Endpoint = case (Receiver == ?BUILTIN) andalso is_element(Tuple, in_erlang_functions()) of
|
||||
elixir_tracker:record_import(Tuple, Receiver, Module, S#elixir_scope.function),
|
||||
Endpoint = case (Receiver == ?builtin) andalso is_element(Tuple, in_erlang_functions()) of
|
||||
true -> erlang;
|
||||
false -> Receiver
|
||||
end,
|
||||
elixir_translator:translate_each({ { '.', Meta, [Endpoint, Name] }, Meta, Args }, S);
|
||||
{ import, Receiver } ->
|
||||
elixir_tracker:record_remote(Tuple, Receiver, S#elixir_scope.module, S#elixir_scope.function),
|
||||
elixir_translator:translate_each({ { '.', Meta, [Receiver, Name] }, [{require,false}|Meta], Args }, S);
|
||||
Result ->
|
||||
case do_expand_import(Meta, Tuple, Args, Module, S, Result) of
|
||||
{ error, noexpansion } ->
|
||||
Callback();
|
||||
{ error, internal } ->
|
||||
elixir_import:record(Tuple, ?BUILTIN, Module),
|
||||
elixir_tracker:record_import(Tuple, ?builtin, Module, S#elixir_scope.function),
|
||||
elixir_macros:translate({ Name, Meta, Args }, S);
|
||||
{ ok, _Receiver, Tree } ->
|
||||
translate_expansion(Meta, Tree, S)
|
||||
@@ -89,8 +95,9 @@ dispatch_require(Meta, Receiver, Name, Args, S, Callback) ->
|
||||
Arity = length(Args),
|
||||
Tuple = { Name, Arity },
|
||||
|
||||
case (Receiver == ?BUILTIN) andalso is_element(Tuple, in_erlang_functions()) of
|
||||
case (Receiver == ?builtin) andalso is_element(Tuple, in_erlang_functions()) of
|
||||
true ->
|
||||
elixir_tracker:record_remote(Tuple, Receiver, S#elixir_scope.module, S#elixir_scope.function),
|
||||
{ TArgs, SA } = elixir_translator:translate_args(Args, S),
|
||||
{ ?wrap_call(?line(Meta), erlang, Name, TArgs), SA };
|
||||
false ->
|
||||
@@ -98,6 +105,7 @@ dispatch_require(Meta, Receiver, Name, Args, S, Callback) ->
|
||||
{ error, noexpansion } ->
|
||||
Callback();
|
||||
{ error, internal } ->
|
||||
elixir_tracker:record_remote(Tuple, ?builtin, S#elixir_scope.module, S#elixir_scope.function),
|
||||
elixir_macros:translate({ Name, Meta, Args }, S);
|
||||
{ ok, _Receiver, Tree } ->
|
||||
translate_expansion(Meta, Tree, S)
|
||||
@@ -112,21 +120,21 @@ expand_import(Meta, Tuple, Args, Module, Extra, S) ->
|
||||
|
||||
do_expand_import(Meta, { Name, Arity } = Tuple, Args, Module, S, Result) ->
|
||||
case Result of
|
||||
{ macro, ?BUILTIN } ->
|
||||
{ macro, ?builtin } ->
|
||||
case is_element(Tuple, in_erlang_macros()) of
|
||||
true -> { error, internal };
|
||||
false ->
|
||||
elixir_import:record(Tuple, ?BUILTIN, Module),
|
||||
{ ok, ?BUILTIN, expand_macro_named(Meta, ?BUILTIN, Name, Arity, Args, Module, S) }
|
||||
elixir_tracker:record_import(Tuple, ?builtin, Module, S#elixir_scope.function),
|
||||
{ ok, ?builtin, expand_macro_named(Meta, ?builtin, Name, Arity, Args, Module, S) }
|
||||
end;
|
||||
{ macro, Receiver } ->
|
||||
elixir_import:record(Tuple, Receiver, Module),
|
||||
elixir_tracker:record_import(Tuple, Receiver, Module, S#elixir_scope.function),
|
||||
{ ok, Receiver, expand_macro_named(Meta, Receiver, Name, Arity, Args, Module, S) };
|
||||
{ import, Receiver } ->
|
||||
expand_require(Meta, Receiver, Tuple, Args, Module, S);
|
||||
_ ->
|
||||
Fun = (S#elixir_scope.function /= Tuple) andalso
|
||||
elixir_def_local:macro_for(Tuple, true, Module),
|
||||
elixir_def_local:macro_for(Tuple, true, S),
|
||||
case Fun of
|
||||
false -> { error, noexpansion };
|
||||
_ ->
|
||||
@@ -134,24 +142,29 @@ do_expand_import(Meta, { Name, Arity } = Tuple, Args, Module, S, Result) ->
|
||||
end
|
||||
end.
|
||||
|
||||
expand_require(Meta, ?BUILTIN, { Name, Arity } = Tuple, Args, Module, S) ->
|
||||
expand_require(Meta, ?builtin, { Name, Arity } = Tuple, Args, Module, S) ->
|
||||
case is_element(Tuple, in_erlang_macros()) of
|
||||
true -> { error, internal };
|
||||
false ->
|
||||
case is_element(Tuple, in_elixir_macros()) of
|
||||
true -> { ok, ?BUILTIN, expand_macro_named(Meta, ?BUILTIN, Name, Arity, Args, Module, S) };
|
||||
false -> { error, noexpansion }
|
||||
true ->
|
||||
elixir_tracker:record_remote(Tuple, ?builtin, S#elixir_scope.module, S#elixir_scope.function),
|
||||
{ ok, ?builtin, expand_macro_named(Meta, ?builtin, Name, Arity, Args, Module, S) };
|
||||
false ->
|
||||
{ error, noexpansion }
|
||||
end
|
||||
end;
|
||||
|
||||
expand_require(Meta, Receiver, { Name, Arity } = Tuple, Args, Module, S) ->
|
||||
Fun = (Module == Receiver) andalso (S#elixir_scope.function /= Tuple) andalso
|
||||
elixir_def_local:macro_for(Tuple, false, Module),
|
||||
elixir_def_local:macro_for(Tuple, false, S),
|
||||
|
||||
case Fun of
|
||||
false ->
|
||||
case is_element(Tuple, get_optional_macros(Receiver)) of
|
||||
true -> { ok, Receiver, expand_macro_named(Meta, Receiver, Name, Arity, Args, Module, S) };
|
||||
true ->
|
||||
elixir_tracker:record_remote(Tuple, Receiver, S#elixir_scope.module, S#elixir_scope.function),
|
||||
{ ok, Receiver, expand_macro_named(Meta, Receiver, Name, Arity, Args, Module, S) };
|
||||
false -> { error, noexpansion }
|
||||
end;
|
||||
_ ->
|
||||
@@ -261,6 +274,22 @@ munge_stacktrace(Info, [H|T], S) ->
|
||||
munge_stacktrace(_, [], _) ->
|
||||
[].
|
||||
|
||||
remote_function(Meta, Receiver, Name, Arity, S) ->
|
||||
Line = ?line(Meta),
|
||||
|
||||
Final =
|
||||
case (Receiver == ?builtin) andalso is_element({ Name, Arity }, in_erlang_functions()) of
|
||||
true -> erlang;
|
||||
false -> Receiver
|
||||
end,
|
||||
|
||||
{ { 'fun', Line, { function,
|
||||
{ atom, Line, Final },
|
||||
{ atom, Line, Name },
|
||||
{ integer, Line, Arity}
|
||||
} }, S }.
|
||||
|
||||
|
||||
%% ERROR HANDLING
|
||||
|
||||
format_error({ unrequired_module, { Receiver, Name, Arity, Required }}) ->
|
||||
@@ -274,21 +303,6 @@ format_error({ ambiguous_call, { Mod1, Mod2, Name, Arity }}) ->
|
||||
|
||||
%% INTROSPECTION
|
||||
|
||||
remote_function(Meta, Receiver, Name, Arity, S) ->
|
||||
Line = ?line(Meta),
|
||||
|
||||
Final =
|
||||
case (Receiver == ?BUILTIN) andalso is_element({ Name, Arity }, in_erlang_functions()) of
|
||||
true -> erlang;
|
||||
false -> Receiver
|
||||
end,
|
||||
|
||||
{ { 'fun', Line, { function,
|
||||
{ atom, Line, Final },
|
||||
{ atom, Line, Name },
|
||||
{ integer, Line, Arity}
|
||||
} }, S }.
|
||||
|
||||
%% Do not try to get macros from Erlang. Speeds up compilation a bit.
|
||||
get_optional_macros(erlang) -> [];
|
||||
|
||||
@@ -303,20 +317,25 @@ get_optional_macros(Receiver) ->
|
||||
{ error, _ } -> []
|
||||
end.
|
||||
|
||||
%% Functions imported from Kernel module. Sorted on compilation.
|
||||
|
||||
in_elixir_functions() ->
|
||||
elixir_imported_functions() ->
|
||||
try
|
||||
?BUILTIN:'__info__'(functions)
|
||||
?builtin:'__info__'(functions)
|
||||
catch
|
||||
error:undef -> []
|
||||
error:undef -> in_erlang_functions()
|
||||
end.
|
||||
|
||||
elixir_imported_macros() ->
|
||||
try
|
||||
?builtin:'__info__'(macros)
|
||||
catch
|
||||
error:undef -> in_erlang_macros()
|
||||
end.
|
||||
|
||||
%% Macros imported from Kernel module. Sorted on compilation.
|
||||
|
||||
in_elixir_macros() ->
|
||||
try
|
||||
?BUILTIN:'__info__'(macros)
|
||||
?builtin:'__info__'(macros)
|
||||
catch
|
||||
error:undef -> []
|
||||
end.
|
||||
@@ -330,6 +349,10 @@ in_erlang_functions() ->
|
||||
{ binary_part, 3 },
|
||||
{ binary_to_atom, 2 },
|
||||
{ binary_to_existing_atom, 2 },
|
||||
{ binary_to_float, 1 },
|
||||
{ binary_to_float, 2 },
|
||||
{ binary_to_integer, 1 },
|
||||
{ binary_to_integer, 2 },
|
||||
{ binary_to_list, 1 },
|
||||
{ binary_to_list, 3 },
|
||||
{ binary_to_term, 1 },
|
||||
@@ -340,8 +363,11 @@ in_erlang_functions() ->
|
||||
% { date, 0 },
|
||||
{ exit, 1 },
|
||||
{ float, 1 },
|
||||
{ float_to_binary, 1 },
|
||||
{ float_to_list, 1 },
|
||||
{ hd, 1 },
|
||||
{ integer_to_binary, 1 },
|
||||
{ integer_to_binary, 2 },
|
||||
{ integer_to_list, 1 },
|
||||
{ integer_to_list, 2 },
|
||||
{ iolist_size, 1 },
|
||||
@@ -421,6 +447,9 @@ in_erlang_macros() ->
|
||||
{'and',2},
|
||||
{apply,2},
|
||||
{apply,3},
|
||||
{binding,0},
|
||||
{binding,1},
|
||||
{binding,2},
|
||||
{'case',2},
|
||||
{def,1},
|
||||
{def,2},
|
||||
|
||||
@@ -9,7 +9,6 @@
|
||||
handle_file_warning/2, handle_file_warning/3, handle_file_error/2,
|
||||
deprecation/3, deprecation/4, file_format/3]).
|
||||
-include("elixir.hrl").
|
||||
-compile({parse_transform, elixir_transform}).
|
||||
|
||||
%% Handle inspecting for exceptions
|
||||
|
||||
@@ -76,6 +75,11 @@ deprecation(Meta, File, Message, Args) ->
|
||||
|
||||
%% Handle warnings and errors (called during module compilation)
|
||||
|
||||
%% output warning based on warnings going to stdout or stderr.
|
||||
output_warning(Warning) ->
|
||||
elixir_code_server:cast(register_warning),
|
||||
io:format(standard_error, Warning, []).
|
||||
|
||||
%% Ignore on bootstrap
|
||||
handle_file_warning(true, _File, { _Line, sys_core_fold, nomatch_guard }) -> [];
|
||||
handle_file_warning(true, _File, { _Line, sys_core_fold, { nomatch_shadow, _ } }) -> [];
|
||||
@@ -94,14 +98,14 @@ handle_file_warning(_, File, {Line,erl_lint,{undefined_behaviour_func,{Fun,Arity
|
||||
Kind = protocol_or_behaviour(Module),
|
||||
Raw = "undefined ~ts function ~ts/~B (for ~ts ~ts)",
|
||||
Message = io_lib:format(Raw, [Kind, Fun, Arity, Kind, inspect(Module)]),
|
||||
io:format(file_format(Line, File, Message));
|
||||
output_warning(file_format(Line, File, Message));
|
||||
|
||||
handle_file_warning(_, File, {Line,erl_lint,{undefined_behaviour,Module}}) ->
|
||||
case elixir_compiler:get_opt(internal) of
|
||||
true -> [];
|
||||
false ->
|
||||
Message = io_lib:format("behaviour ~ts undefined", [inspect(Module)]),
|
||||
io:format(file_format(Line, File, Message))
|
||||
output_warning(file_format(Line, File, Message))
|
||||
end;
|
||||
|
||||
handle_file_warning(_, _File, {Line,erl_lint,{unused_var,_Var}}) when Line =< 0 ->
|
||||
@@ -109,16 +113,16 @@ handle_file_warning(_, _File, {Line,erl_lint,{unused_var,_Var}}) when Line =< 0
|
||||
|
||||
handle_file_warning(_, File, {Line,erl_lint,{unused_var,Var}}) ->
|
||||
Message = format_error(erl_lint, { unused_var, format_var(Var) }),
|
||||
io:format(file_format(Line, File, Message));
|
||||
output_warning(file_format(Line, File, Message));
|
||||
|
||||
handle_file_warning(_, File, {Line,erl_lint,{shadowed_var,Var,Where}}) ->
|
||||
Message = format_error(erl_lint, { shadowed_var, format_var(Var), Where }),
|
||||
io:format(file_format(Line, File, Message));
|
||||
output_warning(file_format(Line, File, Message));
|
||||
|
||||
%% Default behavior
|
||||
handle_file_warning(_, File, {Line,Module,Desc}) ->
|
||||
Message = format_error(Module, Desc),
|
||||
io:format(file_format(Line, File, Message)).
|
||||
output_warning(file_format(Line, File, Message)).
|
||||
|
||||
handle_file_warning(File, Desc) ->
|
||||
handle_file_warning(false, File, Desc).
|
||||
|
||||
@@ -1,284 +0,0 @@
|
||||
%% Modified version of filelib:wildcard that can handle "**"
|
||||
%% and automatically skips directories starting with "." by default.
|
||||
|
||||
%% %CopyrightBegin%
|
||||
%%
|
||||
%% Copyright Ericsson AB 1997-2010. All Rights Reserved.
|
||||
%%
|
||||
%% The contents of this file are subject to the Erlang Public License,
|
||||
%% Version 1.1, (the "License"); you may not use this file except in
|
||||
%% compliance with the License. You should have received a copy of the
|
||||
%% Erlang Public License along with this software. If not, it can be
|
||||
%% retrieved online at http://www.erlang.org/.
|
||||
%%
|
||||
%% Software distributed under the License is distributed on an "AS IS"
|
||||
%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
|
||||
%% the License for the specific language governing rights and limitations
|
||||
%% under the License.
|
||||
%%
|
||||
%% %CopyrightEnd%
|
||||
|
||||
-module(elixir_glob).
|
||||
-compile({no_auto_import,[error/1]}).
|
||||
-export([wildcard/1]).
|
||||
-include_lib("kernel/include/file.hrl").
|
||||
|
||||
-define(HANDLE_ERROR(Expr),
|
||||
try
|
||||
Expr
|
||||
catch
|
||||
error:{badpattern,_}=UnUsUalVaRiAbLeNaMe ->
|
||||
%% Get the stack backtrace correct.
|
||||
erlang:error(UnUsUalVaRiAbLeNaMe)
|
||||
end).
|
||||
|
||||
wildcard(Pattern) when is_list(Pattern) ->
|
||||
?HANDLE_ERROR(do_wildcard(Pattern, file)).
|
||||
|
||||
do_wildcard(Pattern, Mod) when is_list(Pattern) ->
|
||||
do_wildcard_comp(do_compile_wildcard(Pattern), Mod).
|
||||
|
||||
do_wildcard_comp({compiled_wildcard,{exists,File}}, Mod) ->
|
||||
case eval_read_file_info(File, Mod) of
|
||||
{ok,_} -> [File];
|
||||
_ -> []
|
||||
end;
|
||||
do_wildcard_comp({compiled_wildcard,[Base|Rest]}, Mod) ->
|
||||
do_wildcard_1([Base], Rest, Mod).
|
||||
|
||||
%%%
|
||||
%%% Pattern matching using a compiled wildcard.
|
||||
%%%
|
||||
|
||||
do_wildcard_1(Files, Pattern, Mod) ->
|
||||
do_wildcard_2(Files, Pattern, [], Mod).
|
||||
|
||||
do_wildcard_2([File|Rest], Pattern, Result, Mod) ->
|
||||
do_wildcard_2(Rest, Pattern, do_wildcard_3(File, Pattern, Result, Mod), Mod);
|
||||
do_wildcard_2([], _, Result, _Mod) ->
|
||||
Result.
|
||||
|
||||
do_wildcard_3(Base, [[double_star]|Rest], Result, Mod) ->
|
||||
lists:sort(do_double_star(current, [Base], Rest, Result, Mod, true));
|
||||
do_wildcard_3(Base, [Pattern|Rest], Result, Mod) ->
|
||||
case do_list_dir(Base, Mod) of
|
||||
{ok, Files0} ->
|
||||
Files = lists:sort(Files0),
|
||||
Matches = wildcard_4(Pattern, Files, Base, []),
|
||||
do_wildcard_2(Matches, Rest, Result, Mod);
|
||||
_ ->
|
||||
Result
|
||||
end;
|
||||
do_wildcard_3(Base, [], Result, _Mod) ->
|
||||
[Base|Result].
|
||||
|
||||
wildcard_4(Pattern, [File|Rest], Base, Result) when is_binary(File) ->
|
||||
case wildcard_5(Pattern, binary_to_list(File)) of
|
||||
true ->
|
||||
wildcard_4(Pattern, Rest, Base, [join(Base, File)|Result]);
|
||||
false ->
|
||||
wildcard_4(Pattern, Rest, Base, Result)
|
||||
end;
|
||||
wildcard_4(Pattern, [File|Rest], Base, Result) ->
|
||||
case wildcard_5(Pattern, File) of
|
||||
true ->
|
||||
wildcard_4(Pattern, Rest, Base, [join(Base, File)|Result]);
|
||||
false ->
|
||||
wildcard_4(Pattern, Rest, Base, Result)
|
||||
end;
|
||||
wildcard_4(_Patt, [], _Base, Result) ->
|
||||
Result.
|
||||
|
||||
wildcard_5([question|Rest1], [_|Rest2]) ->
|
||||
wildcard_5(Rest1, Rest2);
|
||||
wildcard_5([accept], _) ->
|
||||
true;
|
||||
wildcard_5([double_star], _) ->
|
||||
true;
|
||||
wildcard_5([star|Rest], File) ->
|
||||
do_star(Rest, File);
|
||||
wildcard_5([{one_of, Ordset}|Rest], [C|File]) ->
|
||||
case ordsets:is_element(C, Ordset) of
|
||||
true -> wildcard_5(Rest, File);
|
||||
false -> false
|
||||
end;
|
||||
wildcard_5([{alt, Alts}], File) ->
|
||||
do_alt(Alts, File);
|
||||
wildcard_5([C|Rest1], [C|Rest2]) when is_integer(C) ->
|
||||
wildcard_5(Rest1, Rest2);
|
||||
wildcard_5([X|_], [Y|_]) when is_integer(X), is_integer(Y) ->
|
||||
false;
|
||||
wildcard_5([], []) ->
|
||||
true;
|
||||
wildcard_5([], [_|_]) ->
|
||||
false;
|
||||
wildcard_5([_|_], []) ->
|
||||
false.
|
||||
|
||||
do_double_star(Base, [H|T], Rest, Result, Mod, Root) ->
|
||||
Full = join(Base, H),
|
||||
Result1 = case do_list_dir(Full, Mod) of
|
||||
{ok, Files} ->
|
||||
do_double_star(Full, Files, Rest, Result, Mod, false);
|
||||
_ -> Result
|
||||
end,
|
||||
Result2 = case Root andalso Rest == [] of
|
||||
true -> Result1;
|
||||
false -> do_wildcard_3(Full, Rest, Result1, Mod)
|
||||
end,
|
||||
do_double_star(Base, T, Rest, Result2, Mod, Root);
|
||||
do_double_star(_Base, [], _Rest, Result, _Mod, _Root) ->
|
||||
Result.
|
||||
|
||||
do_star(Pattern, [X|Rest]) ->
|
||||
case wildcard_5(Pattern, [X|Rest]) of
|
||||
true -> true;
|
||||
false -> do_star(Pattern, Rest)
|
||||
end;
|
||||
do_star(Pattern, []) ->
|
||||
wildcard_5(Pattern, []).
|
||||
|
||||
do_alt([Alt|Rest], File) ->
|
||||
case wildcard_5(Alt, File) of
|
||||
true -> true;
|
||||
false -> do_alt(Rest, File)
|
||||
end;
|
||||
do_alt([], _File) ->
|
||||
false.
|
||||
|
||||
do_list_dir(current, Mod) -> eval_list_dir(".", Mod);
|
||||
do_list_dir(Dir, Mod) -> eval_list_dir(Dir, Mod).
|
||||
|
||||
join(current, File) -> File;
|
||||
join(Base, File) -> filename:join(Base, File).
|
||||
|
||||
|
||||
%%% Compiling a wildcard.
|
||||
|
||||
do_compile_wildcard(Pattern) ->
|
||||
{compiled_wildcard,compile_wildcard_1(Pattern)}.
|
||||
|
||||
compile_wildcard_1(Pattern) ->
|
||||
[Root|Rest] = filename:split(Pattern),
|
||||
case filename:pathtype(Root) of
|
||||
relative ->
|
||||
compile_wildcard_2([Root|Rest], current);
|
||||
_ ->
|
||||
compile_wildcard_2(Rest, [Root])
|
||||
end.
|
||||
|
||||
compile_wildcard_2([Part|Rest], Root) ->
|
||||
case compile_part(Part) of
|
||||
Part ->
|
||||
compile_wildcard_2(Rest, join(Root, Part));
|
||||
Pattern ->
|
||||
compile_wildcard_3(Rest, [Pattern,Root])
|
||||
end;
|
||||
compile_wildcard_2([], Root) -> {exists,Root}.
|
||||
|
||||
compile_wildcard_3([Part|Rest], Result) ->
|
||||
compile_wildcard_3(Rest, [compile_part(Part)|Result]);
|
||||
compile_wildcard_3([], Result) ->
|
||||
lists:reverse(Result).
|
||||
|
||||
compile_part(Part) ->
|
||||
compile_part(Part, false, []).
|
||||
|
||||
compile_part_to_sep(Part) ->
|
||||
compile_part(Part, true, []).
|
||||
|
||||
compile_part([], true, _) ->
|
||||
error(missing_delimiter);
|
||||
compile_part([$,|Rest], true, Result) ->
|
||||
{ok, $,, lists:reverse(Result), Rest};
|
||||
compile_part([$}|Rest], true, Result) ->
|
||||
{ok, $}, lists:reverse(Result), Rest};
|
||||
compile_part([$?|Rest], Upto, Result) ->
|
||||
compile_part(Rest, Upto, [question|Result]);
|
||||
compile_part([$*,$*], Upto, Result) ->
|
||||
compile_part([], Upto, [double_star|Result]);
|
||||
compile_part([$*,$*|Rest], Upto, Result) ->
|
||||
compile_part(Rest, Upto, [star|Result]);
|
||||
compile_part([$*], Upto, Result) ->
|
||||
compile_part([], Upto, [accept|Result]);
|
||||
compile_part([$*|Rest], Upto, Result) ->
|
||||
compile_part(Rest, Upto, [star|Result]);
|
||||
compile_part([$[|Rest], Upto, Result) ->
|
||||
case compile_charset(Rest, ordsets:new()) of
|
||||
{ok, Charset, Rest1} ->
|
||||
compile_part(Rest1, Upto, [Charset|Result]);
|
||||
error ->
|
||||
compile_part(Rest, Upto, [$[|Result])
|
||||
end;
|
||||
compile_part([${|Rest], Upto, Result) ->
|
||||
case compile_alt(Rest) of
|
||||
{ok, Alt} ->
|
||||
lists:reverse(Result, [Alt]);
|
||||
error ->
|
||||
compile_part(Rest, Upto, [${|Result])
|
||||
end;
|
||||
compile_part([X|Rest], Upto, Result) ->
|
||||
compile_part(Rest, Upto, [X|Result]);
|
||||
compile_part([], _Upto, Result) ->
|
||||
lists:reverse(Result).
|
||||
|
||||
compile_charset([$]|Rest], Ordset) ->
|
||||
compile_charset1(Rest, ordsets:add_element($], Ordset));
|
||||
compile_charset([$-|Rest], Ordset) ->
|
||||
compile_charset1(Rest, ordsets:add_element($-, Ordset));
|
||||
compile_charset([], _Ordset) ->
|
||||
error;
|
||||
compile_charset(List, Ordset) ->
|
||||
compile_charset1(List, Ordset).
|
||||
|
||||
compile_charset1([Lower, $-, Upper|Rest], Ordset) when Lower =< Upper ->
|
||||
compile_charset1(Rest, compile_range(Lower, Upper, Ordset));
|
||||
compile_charset1([$]|Rest], Ordset) ->
|
||||
{ok, {one_of, Ordset}, Rest};
|
||||
compile_charset1([X|Rest], Ordset) ->
|
||||
compile_charset1(Rest, ordsets:add_element(X, Ordset));
|
||||
compile_charset1([], _Ordset) ->
|
||||
error.
|
||||
|
||||
compile_range(Lower, Current, Ordset) when Lower =< Current ->
|
||||
compile_range(Lower, Current-1, ordsets:add_element(Current, Ordset));
|
||||
compile_range(_, _, Ordset) ->
|
||||
Ordset.
|
||||
|
||||
compile_alt(Pattern) ->
|
||||
compile_alt(Pattern, []).
|
||||
|
||||
compile_alt(Pattern, Result) ->
|
||||
case compile_part_to_sep(Pattern) of
|
||||
{ok, $,, AltPattern, Rest} ->
|
||||
compile_alt(Rest, [AltPattern|Result]);
|
||||
{ok, $}, AltPattern, Rest} ->
|
||||
NewResult = [AltPattern|Result],
|
||||
RestPattern = compile_part(Rest),
|
||||
{ok, {alt, [Alt++RestPattern || Alt <- NewResult]}};
|
||||
Pattern ->
|
||||
error
|
||||
end.
|
||||
|
||||
error(Reason) ->
|
||||
erlang:error({badpattern,Reason}).
|
||||
|
||||
eval_read_file_info(File, file) ->
|
||||
file:read_file_info(File);
|
||||
eval_read_file_info(File, erl_prim_loader) ->
|
||||
case erl_prim_loader:read_file_info(File) of
|
||||
error -> {error, erl_prim_loader};
|
||||
Res-> Res
|
||||
end;
|
||||
eval_read_file_info(File, Mod) ->
|
||||
Mod:read_file_info(File).
|
||||
|
||||
eval_list_dir(Dir, file) ->
|
||||
file:list_dir(Dir);
|
||||
eval_list_dir(Dir, erl_prim_loader) ->
|
||||
case erl_prim_loader:list_dir(Dir) of
|
||||
error -> {error, erl_prim_loader};
|
||||
Res-> Res
|
||||
end;
|
||||
eval_list_dir(Dir, Mod) ->
|
||||
Mod:list_dir(Dir).
|
||||
@@ -2,51 +2,11 @@
|
||||
%% in between local functions and imports.
|
||||
%% For imports dispatch, please check elixir_dispatch.
|
||||
-module(elixir_import).
|
||||
-export([import/5, recorded_locals/1, format_error/1,
|
||||
ensure_no_import_conflict/4, ensure_no_local_conflict/4,
|
||||
ensure_all_imports_used/3,
|
||||
build_table/1, delete_table/1, record/3]).
|
||||
-export([import/5, format_error/1,
|
||||
ensure_no_local_conflict/4]).
|
||||
-include("elixir.hrl").
|
||||
|
||||
table(Module) -> ?atom_concat([i, Module]).
|
||||
|
||||
build_table(Module) ->
|
||||
ets:new(table(Module), [bag, named_table, public]).
|
||||
|
||||
delete_table(Module) ->
|
||||
ets:delete(table(Module)).
|
||||
|
||||
record(_Tuple, _Receiver, nil) ->
|
||||
false;
|
||||
|
||||
record(Tuple, Receiver, Module) ->
|
||||
try
|
||||
ets:insert(table(Module), { Tuple, Receiver })
|
||||
catch
|
||||
error:badarg -> false
|
||||
end.
|
||||
|
||||
record_warn(_Meta, _Ref, _Opts, #elixir_scope{module=nil}) -> false;
|
||||
|
||||
record_warn(Meta, Ref, Opts, S) ->
|
||||
Table = table(S#elixir_scope.module),
|
||||
ets:delete(Table, Ref),
|
||||
|
||||
Warn =
|
||||
case keyfind(warn, Opts) of
|
||||
{ warn, false } -> false;
|
||||
{ warn, true } -> true;
|
||||
false -> not lists:keymember(context, 1, Meta)
|
||||
end,
|
||||
|
||||
Warn andalso ets:insert(Table, { Ref, ?line(Meta) }).
|
||||
|
||||
recorded_locals(Module) ->
|
||||
Table = table(Module),
|
||||
Match = { '$1', Module },
|
||||
Result = ets:match(Table, Match),
|
||||
ets:match_delete(Table, Match),
|
||||
lists:append(Result).
|
||||
%% IMPORT HELPERS
|
||||
|
||||
%% Update the scope to consider the imports for aliases
|
||||
%% based on the given options and selector.
|
||||
@@ -80,7 +40,16 @@ import(Meta, Ref, Opts, Selector, S) ->
|
||||
record_warn(Meta, Ref, Opts, S),
|
||||
SM.
|
||||
|
||||
%% IMPORT FUNCTION RELATED HELPERS
|
||||
record_warn(_Meta, _Ref, _Opts, #elixir_scope{module=nil}) -> false;
|
||||
record_warn(Meta, Ref, Opts, #elixir_scope{module=Module}) ->
|
||||
Warn =
|
||||
case keyfind(warn, Opts) of
|
||||
{ warn, false } -> false;
|
||||
{ warn, true } -> true;
|
||||
false -> not lists:keymember(context, 1, Meta)
|
||||
end,
|
||||
|
||||
elixir_tracker:record_warn(Ref, Warn, ?line(Meta), Module).
|
||||
|
||||
%% Calculates the imports based on only and except
|
||||
|
||||
@@ -88,8 +57,7 @@ calculate(Meta, Key, Opts, Old, Temp, AvailableFun, S) ->
|
||||
File = S#elixir_scope.file,
|
||||
|
||||
New = case keyfind(only, Opts) of
|
||||
{ only, RawOnly } ->
|
||||
Only = expand_fun_arity(Meta, only, RawOnly, S),
|
||||
{ only, Only } ->
|
||||
case Only -- get_exports(Key) of
|
||||
[{Name,Arity}|_] ->
|
||||
Tuple = { invalid_import, { Key, Name, Arity } },
|
||||
@@ -101,8 +69,7 @@ calculate(Meta, Key, Opts, Old, Temp, AvailableFun, S) ->
|
||||
case keyfind(except, Opts) of
|
||||
false -> AvailableFun(true);
|
||||
{ except, [] } -> AvailableFun(true);
|
||||
{ except, RawExcept } ->
|
||||
Except = expand_fun_arity(Meta, except, RawExcept, S),
|
||||
{ except, Except } ->
|
||||
case keyfind(Key, Old) of
|
||||
false -> AvailableFun(true) -- Except;
|
||||
{Key,OldImports} -> OldImports -- Except
|
||||
@@ -134,29 +101,6 @@ if_quoted(Meta, Temp, Callback) ->
|
||||
Temp
|
||||
end.
|
||||
|
||||
%% Ensure we are expanding macros and stuff
|
||||
|
||||
expand_fun_arity(Meta, Kind, Value, S) ->
|
||||
{ TValue, _S } = elixir_translator:translate_each(Value, S),
|
||||
cons_to_keywords(Meta, Kind, TValue, S).
|
||||
|
||||
cons_to_keywords(Meta, Kind, { cons, _, Left, { nil, _ } }, S) ->
|
||||
[tuple_to_fun_arity(Meta, Kind, Left, S)];
|
||||
|
||||
cons_to_keywords(Meta, Kind, { cons, _, Left, Right }, S) ->
|
||||
[tuple_to_fun_arity(Meta, Kind, Left, S)|cons_to_keywords(Meta, Kind, Right, S)];
|
||||
|
||||
cons_to_keywords(Meta, Kind, _, S) ->
|
||||
elixir_errors:syntax_error(Meta, S#elixir_scope.file,
|
||||
"invalid value for :~ts, expected a list with functions and arities", [Kind]).
|
||||
|
||||
tuple_to_fun_arity(_Meta, _Kind, { tuple, _, [{ atom, _, Atom }, { integer, _, Integer }] }, _S) ->
|
||||
{ Atom, Integer };
|
||||
|
||||
tuple_to_fun_arity(Meta, Kind, _, S) ->
|
||||
elixir_errors:syntax_error(Meta, S#elixir_scope.file,
|
||||
"invalid value for :~ts, expected a list with functions and arities", [Kind]).
|
||||
|
||||
%% Retrieve functions and macros from modules
|
||||
|
||||
get_exports(Module) ->
|
||||
@@ -196,38 +140,11 @@ get_optional_macros(Module) ->
|
||||
%% VALIDATION HELPERS
|
||||
|
||||
%% Check if any of the locals defined conflicts with an invoked
|
||||
%% Elixir "implemented in Erlang" macro. Checking if a local
|
||||
%% conflicts with an import is automatically done by Erlang.
|
||||
%% Elixir "implemented in Erlang" macro.
|
||||
|
||||
ensure_no_local_conflict(Meta, File, Module, AllDefined) ->
|
||||
ensure_no_special_form_conflict(Meta, File, Module, AllDefined, local_conflict).
|
||||
|
||||
%% Find conlicts in the given list of functions with
|
||||
%% the recorded set of imports.
|
||||
|
||||
ensure_no_import_conflict(Meta, File, Module, AllDefined) ->
|
||||
Table = table(Module),
|
||||
Matches = [X || X <- AllDefined, ets:member(Table, X)],
|
||||
|
||||
case Matches of
|
||||
[{Name,Arity}|_] ->
|
||||
Key = ets:lookup_element(Table, { Name, Arity }, 2),
|
||||
Tuple = { import_conflict, { hd(Key), Name, Arity } },
|
||||
elixir_errors:form_error(Meta, File, ?MODULE, Tuple);
|
||||
[] ->
|
||||
ok
|
||||
end.
|
||||
|
||||
ensure_all_imports_used(_Line, File, Module) ->
|
||||
Table = table(Module),
|
||||
[begin
|
||||
elixir_errors:handle_file_warning(File, { L, ?MODULE, { unused_import, M } })
|
||||
end || [M, L] <- ets:select(Table, module_line_spec()),
|
||||
ets:match(Table, { '$1', M }) == []].
|
||||
|
||||
module_line_spec() ->
|
||||
[{ { '$1', '$2' }, [{ is_integer, '$2' }], ['$$'] }].
|
||||
|
||||
%% Ensure the given functions don't clash with any
|
||||
%% of Elixir non overridable macros.
|
||||
|
||||
@@ -247,17 +164,10 @@ ensure_no_special_form_conflict(_Meta, _File, _Key, [], _) -> ok.
|
||||
|
||||
%% ERROR HANDLING
|
||||
|
||||
format_error({already_imported,{Receiver, Name, Arity}}) ->
|
||||
io_lib:format("function ~ts/~B already imported from ~ts", [Name, Arity, elixir_errors:inspect(Receiver)]);
|
||||
|
||||
format_error({invalid_import,{Receiver, Name, Arity}}) ->
|
||||
io_lib:format("cannot import ~ts.~ts/~B because it doesn't exist",
|
||||
[elixir_errors:inspect(Receiver), Name, Arity]);
|
||||
|
||||
format_error({import_conflict,{Receiver, Name, Arity}}) ->
|
||||
io_lib:format("imported ~ts.~ts/~B conflicts with local function",
|
||||
[elixir_errors:inspect(Receiver), Name, Arity]);
|
||||
|
||||
format_error({local_conflict,{_, Name, Arity}}) ->
|
||||
io_lib:format("cannot define local ~ts/~B because it conflicts with Elixir special forms", [Name, Arity]);
|
||||
|
||||
@@ -265,9 +175,6 @@ format_error({internal_conflict,{Receiver, Name, Arity}}) ->
|
||||
io_lib:format("cannot import ~ts.~ts/~B because it conflicts with Elixir special forms",
|
||||
[elixir_errors:inspect(Receiver), Name, Arity]);
|
||||
|
||||
format_error({ unused_import, Module }) ->
|
||||
io_lib:format("unused import ~ts", [elixir_errors:inspect(Module)]);
|
||||
|
||||
format_error({ no_macros, Module }) ->
|
||||
io_lib:format("could not load macros from module ~ts", [elixir_errors:inspect(Module)]).
|
||||
|
||||
@@ -287,28 +194,23 @@ intersection([H|T], All) ->
|
||||
|
||||
intersection([], _All) -> [].
|
||||
|
||||
%% INTROSPECTION
|
||||
|
||||
%% Internal funs that are never imported etc.
|
||||
|
||||
remove_underscored(default, List) -> remove_underscored(List);
|
||||
remove_underscored(_, List) -> List.
|
||||
remove_underscored(default, List) ->
|
||||
lists:filter(fun({ Name, _ }) ->
|
||||
case atom_to_list(Name) of
|
||||
"_" ++ _ -> false;
|
||||
_ -> true
|
||||
end
|
||||
end, List);
|
||||
|
||||
remove_underscored([{ Name, _ } = H|T]) when Name < a ->
|
||||
case atom_to_list(Name) of
|
||||
[$_, $_, _, $_, $_] -> [H|remove_underscored(T)];
|
||||
"_" ++ _ -> remove_underscored(T);
|
||||
_ -> [H|remove_underscored(T)]
|
||||
end;
|
||||
|
||||
remove_underscored(T) ->
|
||||
T.
|
||||
remove_underscored(_, List) -> List.
|
||||
|
||||
remove_internals(Set) ->
|
||||
ordsets:del_element({ module_info, 1 },
|
||||
ordsets:del_element({ module_info, 0 }, Set)).
|
||||
|
||||
%% Macros implemented in Erlang that are not overridable.
|
||||
%% Macros implemented in Erlang that are not importable.
|
||||
|
||||
special_form() ->
|
||||
[
|
||||
@@ -319,7 +221,7 @@ special_form() ->
|
||||
{'__ambiguousop__','*'},
|
||||
{'__scope__',2},
|
||||
{'__block__','*'},
|
||||
{'->','2'},
|
||||
{'->','*'},
|
||||
{'<<>>','*'},
|
||||
{'{}','*'},
|
||||
{'[]','*'},
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
-export([extract/5, unescape_chars/1, unescape_chars/2,
|
||||
unescape_tokens/1, unescape_tokens/2, unescape_map/1]).
|
||||
-include("elixir.hrl").
|
||||
-compile({parse_transform, elixir_transform}).
|
||||
|
||||
%% Extract string interpolations
|
||||
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
%% Handle translation of Elixir literals to Erlang AST.
|
||||
-module(elixir_literal).
|
||||
-export([translate/2]).
|
||||
-import(elixir_translator, [translate_each/2, translate_args/2]).
|
||||
-import(elixir_scope, [umergec/2]).
|
||||
-include("elixir.hrl").
|
||||
-compile({parse_transform, elixir_transform}).
|
||||
|
||||
translate({ '<<>>', Meta, Args } = Original, S) when is_list(Args) ->
|
||||
case elixir_partials:handle(Original, S, allow_tail) of
|
||||
@@ -156,8 +156,8 @@ extract_bit_values(Meta, [Size|T], default, Types, S) when is_integer(Size) anda
|
||||
extract_bit_values(Meta, T, {integer, Meta, Size}, Types, S);
|
||||
|
||||
extract_bit_values(Meta, [{ '|', _, [Left, Right] }], Size, Types, S) ->
|
||||
Expanded = 'Elixir.Macro':expand(Right, elixir_scope:to_ex_env({ ?line(Meta), S })),
|
||||
extract_bit_values(Meta, [Left|join_expansion(Expanded,[])], Size, Types, S);
|
||||
{ Expanded, ES } = 'Elixir.Macro':expand_all(Right, elixir_scope:to_ex_env({ ?line(Meta), S }), S),
|
||||
extract_bit_values(Meta, [Left|join_expansion(Expanded,[])], Size, Types, ES);
|
||||
|
||||
extract_bit_values(Meta, [_|_] = All, Size, Types, S) ->
|
||||
handle_unknown_specifier(Meta, All, Size, Types, S);
|
||||
@@ -198,11 +198,11 @@ extract_bit_type_or_size(_Value, _Args, _Other, _Types) ->
|
||||
{ error, unknown }.
|
||||
|
||||
handle_unknown_specifier(Meta, [H|T], Size, Types, S) ->
|
||||
case 'Elixir.Macro':expand(H, elixir_scope:to_ex_env({ ?line(Meta), S })) of
|
||||
H ->
|
||||
elixir_errors:syntax_error(Meta, S#elixir_scope.file, "unknown bitstring specifier ~ts", ['Elixir.Macro':to_binary(H)]);
|
||||
E ->
|
||||
extract_bit_values(Meta, join_expansion(E,T), Size, Types, S)
|
||||
case 'Elixir.Macro':expand_all(H, elixir_scope:to_ex_env({ ?line(Meta), S }), S) of
|
||||
{ H, ES } ->
|
||||
elixir_errors:syntax_error(Meta, ES#elixir_scope.file, "unknown bitstring specifier ~ts", ['Elixir.Macro':to_string(H)]);
|
||||
{ E, ES } ->
|
||||
extract_bit_values(Meta, join_expansion(E,T), Size, Types, ES)
|
||||
end.
|
||||
|
||||
join_expansion({ '__block__', _, [Expanded] }, Tail) -> join_expansion(Expanded, Tail);
|
||||
|
||||
@@ -9,7 +9,6 @@
|
||||
|
||||
-include("elixir.hrl").
|
||||
-define(opt_in_types(Kind), Kind == atom orelse Kind == integer orelse Kind == float).
|
||||
-compile({parse_transform, elixir_transform}).
|
||||
|
||||
%% Operators
|
||||
|
||||
@@ -120,7 +119,7 @@ translate({'@', Meta, [{ Name, _, Args }]}, S) ->
|
||||
translate_each({
|
||||
{ '.', Meta, ['Elixir.Module', get_attribute] },
|
||||
Meta,
|
||||
[ { '__MODULE__', Meta, false }, Name ]
|
||||
[ { '__MODULE__', Meta, false }, Name, true ]
|
||||
}, S);
|
||||
_ ->
|
||||
Contents = 'Elixir.Module':get_attribute(S#elixir_scope.module, Name),
|
||||
@@ -131,6 +130,55 @@ translate({'@', Meta, [{ Name, _, Args }]}, S) ->
|
||||
end
|
||||
end;
|
||||
|
||||
%% Binding
|
||||
|
||||
translate({ 'binding', Meta, [] }, S) ->
|
||||
translate({ 'binding', Meta, [false] }, S);
|
||||
|
||||
translate({ 'binding', Meta, [false] }, S) ->
|
||||
Line = ?line(Meta),
|
||||
{ elixir_tree_helpers:list_to_cons(Line,
|
||||
[ to_var_value_tuple(Line, Name, Var) || { { Name, nil }, Var } <- S#elixir_scope.vars]), S };
|
||||
|
||||
translate({ 'binding', Meta, [true] }, S) ->
|
||||
Line = ?line(Meta),
|
||||
{ elixir_tree_helpers:list_to_cons(Line,
|
||||
[ to_var_value_tuple(Line, Name, Kind, Var) || { { Name, Kind }, Var } <- S#elixir_scope.vars]), S };
|
||||
|
||||
translate({ 'binding', Meta, [List] }, #elixir_scope{vars=Vars} = S) when is_list(List) ->
|
||||
Line = ?line(Meta),
|
||||
Dict = lists:foldl(fun
|
||||
(Name, Acc) when is_atom(Name) ->
|
||||
case orddict:find({ Name, nil }, Vars) of
|
||||
{ ok, Var } ->
|
||||
orddict:store(Name, Var, Acc);
|
||||
error ->
|
||||
Acc
|
||||
end;
|
||||
(Name, _Acc) ->
|
||||
elixir_errors:syntax_error(Line, S#elixir_scope.file, "binding/1 expects a list of atoms "
|
||||
"at compilation time, got: ~ts", ['Elixir.Macro':to_string(Name)])
|
||||
end, [], List),
|
||||
{ elixir_tree_helpers:list_to_cons(Line,
|
||||
[ to_var_value_tuple(Line, Name, Var) || { Name, Var } <- Dict]), S };
|
||||
|
||||
translate({ 'binding', Meta, [List, true] }, #elixir_scope{vars=Vars} = S) when is_list(List) ->
|
||||
Line = ?line(Meta),
|
||||
Dict = lists:foldl(fun
|
||||
(Tuple, Acc) when is_tuple(Tuple) ->
|
||||
case orddict:find(Tuple, Vars) of
|
||||
{ ok, Var } ->
|
||||
orddict:store(Tuple, Var, Acc);
|
||||
error ->
|
||||
Acc
|
||||
end;
|
||||
(Tuple, _Acc) ->
|
||||
elixir_errors:syntax_error(Line, S#elixir_scope.file, "binding/2 expects a list of tuples "
|
||||
"at compilation time, got: ~ts", ['Elixir.Macro':to_string(Tuple)])
|
||||
end, [], List),
|
||||
{ elixir_tree_helpers:list_to_cons(Line,
|
||||
[ to_var_value_tuple(Line, Name, Kind, Var) || { { Name, Kind }, Var } <- Dict]), S };
|
||||
|
||||
%% Case
|
||||
|
||||
translate({'case', Meta, [Expr, KV]}, S) ->
|
||||
@@ -148,12 +196,11 @@ translate({'case', Meta, [Expr, KV]}, S) ->
|
||||
|
||||
%% Try
|
||||
|
||||
translate({'try', Meta, [Clauses]}, RawS) ->
|
||||
S = RawS#elixir_scope{noname=true},
|
||||
translate({'try', Meta, [Clauses]}, S) ->
|
||||
assert_no_match_or_guard_scope(Meta, 'try', S),
|
||||
|
||||
Do = proplists:get_value('do', Clauses, nil),
|
||||
{ TDo, SB } = elixir_translator:translate_each(Do, S),
|
||||
{ TDo, SB } = elixir_translator:translate_each(Do, S#elixir_scope{noname=true}),
|
||||
|
||||
Catch = [Tuple || { X, _ } = Tuple <- Clauses, X == 'rescue' orelse X == 'catch'],
|
||||
{ TCatch, SC } = elixir_try:clauses(Meta, Catch, umergec(S, SB)),
|
||||
@@ -164,7 +211,8 @@ translate({'try', Meta, [Clauses]}, RawS) ->
|
||||
Else = elixir_clauses:get_pairs(Meta, else, Clauses, S),
|
||||
{ TElse, SE } = elixir_clauses:match(Meta, Else, umergec(S, SA)),
|
||||
|
||||
{ { 'try', ?line(Meta), pack(TDo), TElse, TCatch, pack(TAfter) }, umergec(RawS, SE) };
|
||||
SF = (umergec(S, SE))#elixir_scope{noname=S#elixir_scope.noname},
|
||||
{ { 'try', ?line(Meta), pack(TDo), TElse, TCatch, pack(TAfter) }, SF };
|
||||
|
||||
%% Receive
|
||||
|
||||
@@ -268,6 +316,9 @@ translate_in(Meta, Left, Right, S) ->
|
||||
end,
|
||||
|
||||
{ InGuard, TExpr } = case TRight of
|
||||
{ nil, _ } ->
|
||||
Expr = { atom, Line, false },
|
||||
{ Cache, Expr };
|
||||
{ cons, _, _, _ } ->
|
||||
[H|T] = elixir_tree_helpers:cons_to_list(TRight),
|
||||
Expr = lists:foldr(fun(X, Acc) ->
|
||||
@@ -298,7 +349,7 @@ translate_in(Meta, Left, Right, S) ->
|
||||
_ ->
|
||||
case Cache of
|
||||
true ->
|
||||
{ false, ?wrap_call(Line, 'Elixir-Enum', 'member?', [TRight, TLeft]) };
|
||||
{ false, ?wrap_call(Line, 'Elixir.Enum', 'member?', [TRight, TLeft]) };
|
||||
false ->
|
||||
syntax_error(Meta, S#elixir_scope.file, "invalid args for operator in, it expects an explicit array or an explicit range on the right side when used in guard expressions")
|
||||
end
|
||||
@@ -319,15 +370,15 @@ decreasing_compare(Line, Var, Start, End) ->
|
||||
{ op, Line, '=<', Var, Start },
|
||||
{ op, Line, '>=', Var, End } }.
|
||||
|
||||
rewrite_case_clauses([{do,[{in,_,[{'_',_,_},[false,nil]]}],False},{do,[{'_',_,_}],True}]) ->
|
||||
[{do,[false],False},{do,[true],True}];
|
||||
rewrite_case_clauses([{do,Meta1,[{in,_,[{'_',_,_},[false,nil]]}],False},{do,Meta2,[{'_',_,_}],True}]) ->
|
||||
[{do,Meta1,[false],False},{do,Meta2,[true],True}];
|
||||
|
||||
rewrite_case_clauses(Clauses) ->
|
||||
Clauses.
|
||||
|
||||
%% defmodule :foo
|
||||
expand_module(Raw, Module, #elixir_scope{module=Nesting}) when is_atom(Raw); Nesting == nil ->
|
||||
Module;
|
||||
expand_module(Raw, _Module, _S) when is_atom(Raw) ->
|
||||
Raw;
|
||||
|
||||
%% defmodule Hello
|
||||
expand_module({ '__aliases__', _, [H] }, _Module, S) ->
|
||||
@@ -350,6 +401,19 @@ is_reserved_data(moduledoc) -> true;
|
||||
is_reserved_data(doc) -> true;
|
||||
is_reserved_data(_) -> false.
|
||||
|
||||
to_var_value_tuple(Line, Name, Var) ->
|
||||
{ tuple, Line,
|
||||
[ { atom, Line, Name },
|
||||
{ var, Line, Var } ] }.
|
||||
|
||||
to_var_value_tuple(Line, Name, Kind, Var) ->
|
||||
{ tuple, Line,
|
||||
[ { tuple, Line, [
|
||||
{ atom, Line, Name },
|
||||
{ atom, Line, Kind }
|
||||
] },
|
||||
{ var, Line, Var } ] }.
|
||||
|
||||
spec_to_macro(type) -> deftype;
|
||||
spec_to_macro(typep) -> deftypep;
|
||||
spec_to_macro(opaque) -> defopaque;
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
-module(elixir_module).
|
||||
-export([translate/4, compile/5, data_table/1, eval_quoted/4,
|
||||
format_error/1, eval_callbacks/5]).
|
||||
-export([translate/4, compile/5, data_table/1, docs_table/1,
|
||||
eval_quoted/4, format_error/1, eval_callbacks/5]).
|
||||
-include("elixir.hrl").
|
||||
-compile({parse_transform, elixir_transform}).
|
||||
|
||||
-define(docs_attr, '__docs_table').
|
||||
-define(acc_attr, '__acc_attributes').
|
||||
-define(persisted_attr, '__persisted_attributes').
|
||||
|
||||
eval_quoted(Module, Quoted, RawBinding, Opts) ->
|
||||
Binding = binding_for_eval(Module, RawBinding),
|
||||
@@ -32,7 +35,7 @@ data_table(Module) ->
|
||||
Module.
|
||||
|
||||
docs_table(Module) ->
|
||||
?atom_concat([o, Module]).
|
||||
ets:lookup_element(Module, ?docs_attr, 2).
|
||||
|
||||
%% TRANSFORMATION FUNCTIONS
|
||||
|
||||
@@ -71,9 +74,17 @@ compile(Line, Module, Block, Vars, #elixir_scope{context_modules=FileModules} =
|
||||
Forms1 = specs_form(Line, Module, Private, Defmacro, Forms0, C),
|
||||
Forms2 = attributes_form(Line, File, Module, Forms1),
|
||||
|
||||
elixir_import:ensure_all_imports_used(Line, File, Module),
|
||||
case ets:lookup(data_table(Module), 'on_load') of
|
||||
[] -> ok;
|
||||
[{on_load,OnLoad}] ->
|
||||
[elixir_tracker:record_local(Tuple, Module) || Tuple <- OnLoad]
|
||||
end,
|
||||
|
||||
elixir_tracker:warn_unused_local(File, Module, Private),
|
||||
elixir_tracker:ensure_no_import_conflict(Line, File, Module, All),
|
||||
elixir_tracker:ensure_all_imports_used(Line, File, Module),
|
||||
|
||||
elixir_import:ensure_no_local_conflict(Line, File, Module, All),
|
||||
elixir_import:ensure_no_import_conflict(Line, File, Module, All),
|
||||
|
||||
Final = [
|
||||
{ attribute, Line, file, { FileList, Line } },
|
||||
@@ -83,10 +94,10 @@ compile(Line, Module, Block, Vars, #elixir_scope{context_modules=FileModules} =
|
||||
Binary = load_form(Line, Final, S),
|
||||
{ module, Module, Binary, Result }
|
||||
after
|
||||
ets:delete(data_table(Module)),
|
||||
elixir_tracker:cleanup(Module),
|
||||
elixir_def:cleanup(Module),
|
||||
ets:delete(docs_table(Module)),
|
||||
elixir_def:delete_table(Module),
|
||||
elixir_import:delete_table(Module)
|
||||
ets:delete(data_table(Module))
|
||||
end;
|
||||
|
||||
compile(Line, Other, _Block, _Vars, #elixir_scope{file=File}) ->
|
||||
@@ -119,20 +130,13 @@ build(Line, File, Module) ->
|
||||
end,
|
||||
|
||||
Attributes = [behavior, behaviour, on_load, spec, type, export_type, opaque, callback, compile],
|
||||
ets:insert(DataTable, { '__acc_attributes', [before_compile,after_compile,on_definition|Attributes] }),
|
||||
ets:insert(DataTable, { '__persisted_attributes', [vsn|Attributes] }),
|
||||
ets:insert(DataTable, { ?acc_attr, [before_compile,after_compile,on_definition|Attributes] }),
|
||||
ets:insert(DataTable, { ?persisted_attr, [vsn|Attributes] }),
|
||||
ets:insert(DataTable, { ?docs_attr, ets:new(DataTable, [ordered_set, public]) }),
|
||||
|
||||
%% Keep docs in another table since we don't want to pull out
|
||||
%% all the binaries every time a new documentation is stored.
|
||||
DocsTable = docs_table(Module),
|
||||
ets:new(DocsTable, [ordered_set, named_table, public]),
|
||||
|
||||
%% We keep a separated table for function definitions
|
||||
%% and another one for imports. We keep them in different
|
||||
%% tables for organization and speed purpose (since the
|
||||
%% imports table is frequently written to).
|
||||
elixir_def:build_table(Module),
|
||||
elixir_import:build_table(Module).
|
||||
%% Setup other modules
|
||||
elixir_def:setup(Module),
|
||||
elixir_tracker:setup(Module).
|
||||
|
||||
%% Receives the module representation and evaluates it.
|
||||
|
||||
@@ -156,13 +160,6 @@ functions_form(Line, File, Module, Export, Private, Def, Defmacro, RawFunctions,
|
||||
{ FinalExport, FinalFunctions } =
|
||||
add_info_function(Line, File, Module, Export, Functions, Def, Defmacro, C),
|
||||
|
||||
Recorded =
|
||||
case ets:lookup(data_table(Module), 'on_load') of
|
||||
[] -> elixir_import:recorded_locals(Module);
|
||||
[{on_load,OnLoad}] -> elixir_import:recorded_locals(Module) ++ OnLoad
|
||||
end,
|
||||
|
||||
elixir_def_local:check_unused_local(File, Recorded, Private),
|
||||
PrivateTuple = [Tuple || { Tuple, _, _, _, _ } <- Private],
|
||||
{ FinalExport ++ PrivateTuple, [
|
||||
{attribute, Line, export, lists:sort(FinalExport)} | FinalFunctions
|
||||
|
||||
+172
-195
@@ -2,9 +2,10 @@ Nonterminals
|
||||
grammar expr_list
|
||||
expr paren_expr block_expr fn_expr bracket_expr call_expr bracket_at_expr max_expr
|
||||
base_expr matched_expr matched_op_expr unmatched_expr op_expr
|
||||
add_op mult_op unary_op two_op right_op bin_concat_op
|
||||
match_op send_op default_op when_op pipe_op in_op inc_op range_op
|
||||
andand_op oror_op and_op or_op comp_expr_op colon_colon_op three_op at_op
|
||||
comp_op_eol at_op_eol unary_op_eol and_op_eol or_op_eol tail_op_eol
|
||||
add_op_eol mult_op_eol exp_op_eol two_op_eol type_op_eol stab_op_eol
|
||||
arrow_op_eol range_op_eol than_op_eol default_op_eol match_op_eol
|
||||
when_op_eol in_op_eol inc_op_eol
|
||||
open_paren close_paren empty_paren
|
||||
open_bracket close_bracket
|
||||
open_curly close_curly
|
||||
@@ -12,7 +13,7 @@ Nonterminals
|
||||
base_comma_expr comma_expr optional_comma_expr matched_comma_expr
|
||||
call_args call_args_parens parens_call
|
||||
call_args_no_parens call_args_no_parens_strict call_args_parens_not_one
|
||||
stab stab_eol stab_op stab_expr stab_maybe_expr
|
||||
stab stab_eol stab_expr stab_maybe_expr
|
||||
kw_eol kw_expr kw_comma kw_base
|
||||
matched_kw_expr matched_kw_comma matched_kw_base
|
||||
dot_op dot_ref dot_identifier dot_op_identifier dot_do_identifier
|
||||
@@ -26,45 +27,40 @@ Terminals
|
||||
bracket_identifier paren_identifier do_identifier block_identifier
|
||||
fn 'end' aliases
|
||||
number signed_number atom bin_string list_string sigil
|
||||
dot_call_op comp_op op_identifier
|
||||
'not' 'and' 'or' 'xor' 'when' 'in' 'inlist' 'inbits' 'do'
|
||||
'true' 'false' 'nil'
|
||||
'=' '+' '-' '*' '/' '++' '--' '**' '//'
|
||||
'(' ')' '[' ']' '{' '}' '<<' '>>' '::'
|
||||
eol ',' '&' '|' '.' '^' '@' '<-' '<>' '->' '|>' '=~'
|
||||
'&&' '||' '!' '...' '..'
|
||||
'<<<' '>>>' '&&&' '|||' '^^^' '~~~'
|
||||
dot_call_op op_identifier
|
||||
comp_op at_op unary_op and_op or_op arrow_op match_op
|
||||
range_op in_op inc_op when_op than_op default_op tail_op
|
||||
dual_op add_op mult_op exp_op two_op type_op stab_op
|
||||
'true' 'false' 'nil' 'do' eol ',' '.' '&' '...'
|
||||
'(' ')' '[' ']' '{' '}' '<<' '>>'
|
||||
.
|
||||
|
||||
Rootsymbol grammar.
|
||||
|
||||
Left 5 do.
|
||||
Right 10 stab_op.
|
||||
Left 20 ','. % Solve nested call_args conflicts
|
||||
Right 30 colon_colon_op.
|
||||
Right 40 when_op.
|
||||
Right 50 default_op.
|
||||
Left 60 pipe_op.
|
||||
Left 70 inc_op.
|
||||
Right 80 match_op.
|
||||
Right 110 send_op.
|
||||
Left 120 oror_op.
|
||||
Left 130 andand_op.
|
||||
Left 140 or_op.
|
||||
Left 150 and_op.
|
||||
Left 160 comp_expr_op.
|
||||
Left 170 in_op.
|
||||
Right 180 right_op.
|
||||
Left 190 range_op.
|
||||
Left 200 three_op.
|
||||
Left 210 add_op.
|
||||
Left 220 mult_op.
|
||||
Right 230 bin_concat_op.
|
||||
Right 240 two_op.
|
||||
Nonassoc 300 unary_op.
|
||||
Right 10 stab_op_eol. %% ->
|
||||
Left 20 ','.
|
||||
Right 30 type_op_eol. %% ::
|
||||
Right 40 when_op_eol. %% when
|
||||
Left 50 inc_op_eol. %% inlist, inbits
|
||||
Right 60 default_op_eol. %% //
|
||||
Left 70 tail_op_eol. %% |
|
||||
Right 80 match_op_eol. %% =
|
||||
Left 130 or_op_eol. %% ||, |||, or, xor
|
||||
Left 140 and_op_eol. %% &&, &&&, and
|
||||
Left 150 comp_op_eol. %% <, >, <=, >=, ==, !=, =~, ===, !==
|
||||
Right 160 arrow_op_eol. %% < (op), (op) > (e.g <-, |>, <<<, >>>)
|
||||
Left 170 in_op_eol. %% in
|
||||
Left 200 range_op_eol. %% ..
|
||||
Left 210 add_op_eol. %% + (op), - (op)
|
||||
Left 220 mult_op_eol. %% * (op), / (op)
|
||||
Right 230 than_op_eol. %% < (op) > (e.g <>)
|
||||
Right 240 two_op_eol. %% ++, --, **
|
||||
Left 250 exp_op_eol. %% ^ (op) (e.g ^^^)
|
||||
Nonassoc 300 unary_op_eol. %% +, -, !, ^, not, ~~~
|
||||
Left 310 dot_call_op.
|
||||
Left 310 dot_op.
|
||||
Nonassoc 320 at_op.
|
||||
Left 310 dot_op. %% .
|
||||
Nonassoc 320 at_op_eol. %% @ (op)
|
||||
Nonassoc 330 var.
|
||||
|
||||
%%% MAIN FLOW OF EXPRESSIONS
|
||||
@@ -88,58 +84,52 @@ expr -> matched_expr : '$1'.
|
||||
expr -> unmatched_expr : '$1'.
|
||||
|
||||
matched_expr -> matched_expr matched_op_expr : build_op(element(1, '$2'), '$1', element(2, '$2')).
|
||||
matched_expr -> unary_op matched_expr : build_unary_op('$1', '$2').
|
||||
matched_expr -> at_op matched_expr : build_unary_op('$1', '$2').
|
||||
matched_expr -> unary_op_eol matched_expr : build_unary_op('$1', '$2').
|
||||
matched_expr -> at_op_eol matched_expr : build_unary_op('$1', '$2').
|
||||
matched_expr -> bracket_at_expr : '$1'.
|
||||
matched_expr -> fn_expr : '$1'.
|
||||
|
||||
unmatched_expr -> matched_expr op_expr : build_op(element(1, '$2'), '$1', element(2, '$2')).
|
||||
unmatched_expr -> unmatched_expr op_expr : build_op(element(1, '$2'), '$1', element(2, '$2')).
|
||||
unmatched_expr -> unary_op expr : build_unary_op('$1', '$2').
|
||||
unmatched_expr -> at_op expr : build_unary_op('$1', '$2').
|
||||
unmatched_expr -> unary_op_eol expr : build_unary_op('$1', '$2').
|
||||
unmatched_expr -> at_op_eol expr : build_unary_op('$1', '$2').
|
||||
unmatched_expr -> block_expr : '$1'.
|
||||
|
||||
op_expr -> match_op expr : { '$1', '$2' }.
|
||||
op_expr -> add_op expr : { '$1', '$2' }.
|
||||
op_expr -> mult_op expr : { '$1', '$2' }.
|
||||
op_expr -> two_op expr : { '$1', '$2' }.
|
||||
op_expr -> right_op expr : { '$1', '$2' }.
|
||||
op_expr -> andand_op expr : { '$1', '$2' }.
|
||||
op_expr -> three_op expr : { '$1', '$2' }.
|
||||
op_expr -> oror_op expr : { '$1', '$2' }.
|
||||
op_expr -> and_op expr : { '$1', '$2' }.
|
||||
op_expr -> or_op expr : { '$1', '$2' }.
|
||||
op_expr -> pipe_op expr : { '$1', '$2' }.
|
||||
op_expr -> bin_concat_op expr : { '$1', '$2' }.
|
||||
op_expr -> in_op expr : { '$1', '$2' }.
|
||||
op_expr -> inc_op expr : { '$1', '$2' }.
|
||||
op_expr -> when_op expr : { '$1', '$2' }.
|
||||
op_expr -> send_op expr : { '$1', '$2' }.
|
||||
op_expr -> range_op expr : { '$1', '$2' }.
|
||||
op_expr -> default_op expr : { '$1', '$2' }.
|
||||
op_expr -> colon_colon_op expr : { '$1', '$2' }.
|
||||
op_expr -> comp_expr_op expr : { '$1', '$2' }.
|
||||
op_expr -> match_op_eol expr : { '$1', '$2' }.
|
||||
op_expr -> add_op_eol expr : { '$1', '$2' }.
|
||||
op_expr -> mult_op_eol expr : { '$1', '$2' }.
|
||||
op_expr -> exp_op_eol expr : { '$1', '$2' }.
|
||||
op_expr -> two_op_eol expr : { '$1', '$2' }.
|
||||
op_expr -> and_op_eol expr : { '$1', '$2' }.
|
||||
op_expr -> or_op_eol expr : { '$1', '$2' }.
|
||||
op_expr -> tail_op_eol expr : { '$1', '$2' }.
|
||||
op_expr -> than_op_eol expr : { '$1', '$2' }.
|
||||
op_expr -> in_op_eol expr : { '$1', '$2' }.
|
||||
op_expr -> inc_op_eol expr : { '$1', '$2' }.
|
||||
op_expr -> when_op_eol expr : { '$1', '$2' }.
|
||||
op_expr -> range_op_eol expr : { '$1', '$2' }.
|
||||
op_expr -> default_op_eol expr : { '$1', '$2' }.
|
||||
op_expr -> type_op_eol expr : { '$1', '$2' }.
|
||||
op_expr -> comp_op_eol expr : { '$1', '$2' }.
|
||||
op_expr -> arrow_op_eol expr : { '$1', '$2' }.
|
||||
|
||||
matched_op_expr -> match_op matched_expr : { '$1', '$2' }.
|
||||
matched_op_expr -> add_op matched_expr : { '$1', '$2' }.
|
||||
matched_op_expr -> mult_op matched_expr : { '$1', '$2' }.
|
||||
matched_op_expr -> two_op matched_expr : { '$1', '$2' }.
|
||||
matched_op_expr -> right_op matched_expr : { '$1', '$2' }.
|
||||
matched_op_expr -> andand_op matched_expr : { '$1', '$2' }.
|
||||
matched_op_expr -> three_op matched_expr : { '$1', '$2' }.
|
||||
matched_op_expr -> oror_op matched_expr : { '$1', '$2' }.
|
||||
matched_op_expr -> and_op matched_expr : { '$1', '$2' }.
|
||||
matched_op_expr -> or_op matched_expr : { '$1', '$2' }.
|
||||
matched_op_expr -> pipe_op matched_expr : { '$1', '$2' }.
|
||||
matched_op_expr -> bin_concat_op matched_expr : { '$1', '$2' }.
|
||||
matched_op_expr -> in_op matched_expr : { '$1', '$2' }.
|
||||
matched_op_expr -> inc_op matched_expr : { '$1', '$2' }.
|
||||
matched_op_expr -> when_op matched_expr : { '$1', '$2' }.
|
||||
matched_op_expr -> send_op matched_expr : { '$1', '$2' }.
|
||||
matched_op_expr -> range_op matched_expr : { '$1', '$2' }.
|
||||
matched_op_expr -> default_op matched_expr : { '$1', '$2' }.
|
||||
matched_op_expr -> colon_colon_op matched_expr : { '$1', '$2' }.
|
||||
matched_op_expr -> comp_expr_op matched_expr : { '$1', '$2' }.
|
||||
matched_op_expr -> match_op_eol matched_expr : { '$1', '$2' }.
|
||||
matched_op_expr -> add_op_eol matched_expr : { '$1', '$2' }.
|
||||
matched_op_expr -> mult_op_eol matched_expr : { '$1', '$2' }.
|
||||
matched_op_expr -> exp_op_eol matched_expr : { '$1', '$2' }.
|
||||
matched_op_expr -> two_op_eol matched_expr : { '$1', '$2' }.
|
||||
matched_op_expr -> and_op_eol matched_expr : { '$1', '$2' }.
|
||||
matched_op_expr -> or_op_eol matched_expr : { '$1', '$2' }.
|
||||
matched_op_expr -> tail_op_eol matched_expr : { '$1', '$2' }.
|
||||
matched_op_expr -> than_op_eol matched_expr : { '$1', '$2' }.
|
||||
matched_op_expr -> in_op_eol matched_expr : { '$1', '$2' }.
|
||||
matched_op_expr -> inc_op_eol matched_expr : { '$1', '$2' }.
|
||||
matched_op_expr -> when_op_eol matched_expr : { '$1', '$2' }.
|
||||
matched_op_expr -> range_op_eol matched_expr : { '$1', '$2' }.
|
||||
matched_op_expr -> default_op_eol matched_expr : { '$1', '$2' }.
|
||||
matched_op_expr -> type_op_eol matched_expr : { '$1', '$2' }.
|
||||
matched_op_expr -> comp_op_eol matched_expr : { '$1', '$2' }.
|
||||
matched_op_expr -> arrow_op_eol matched_expr : { '$1', '$2' }.
|
||||
|
||||
block_expr -> parens_call call_args_parens do_block : build_identifier('$1', '$2' ++ '$3').
|
||||
block_expr -> parens_call call_args_parens call_args_parens do_block : build_nested_parens('$1', '$2', '$3' ++ '$4').
|
||||
@@ -168,24 +158,27 @@ max_expr -> open_paren stab close_paren : build_stab(lists:reverse('$2')).
|
||||
bracket_expr -> dot_bracket_identifier bracket_access : build_access(build_identifier('$1', nil), '$2').
|
||||
bracket_expr -> max_expr bracket_access : build_access('$1', '$2').
|
||||
|
||||
bracket_at_expr -> at_op dot_bracket_identifier bracket_access : build_access(build_unary_op('$1', build_identifier('$2', nil)), '$3').
|
||||
bracket_at_expr -> at_op max_expr bracket_access : build_access(build_unary_op('$1', '$2'), '$3').
|
||||
bracket_at_expr -> bracket_at_expr bracket_access : build_access('$1', '$2').
|
||||
bracket_at_expr -> at_op_eol dot_bracket_identifier bracket_access :
|
||||
build_access(build_unary_op('$1', build_identifier('$2', nil)), '$3').
|
||||
bracket_at_expr -> at_op_eol max_expr bracket_access :
|
||||
build_access(build_unary_op('$1', '$2'), '$3').
|
||||
bracket_at_expr -> bracket_at_expr bracket_access :
|
||||
build_access('$1', '$2').
|
||||
|
||||
base_expr -> number : ?exprs('$1').
|
||||
base_expr -> signed_number : { element(4, '$1'), [{line,?line('$1')}], ?exprs('$1') }.
|
||||
base_expr -> atom : build_atom('$1').
|
||||
base_expr -> list : '$1'.
|
||||
base_expr -> tuple : '$1'.
|
||||
base_expr -> 'true' : ?op('$1').
|
||||
base_expr -> 'false' : ?op('$1').
|
||||
base_expr -> 'nil' : ?op('$1').
|
||||
base_expr -> 'true' : ?id('$1').
|
||||
base_expr -> 'false' : ?id('$1').
|
||||
base_expr -> 'nil' : ?id('$1').
|
||||
base_expr -> aliases : { '__aliases__', [{line,?line('$1')}], ?exprs('$1') }.
|
||||
base_expr -> bin_string : build_bin_string('$1').
|
||||
base_expr -> list_string : build_list_string('$1').
|
||||
base_expr -> bit_string : '$1'.
|
||||
base_expr -> '&' : { '&', [{line,?line('$1')}], ?exprs('$1') }.
|
||||
base_expr -> '...' : { ?op('$1'), [{line,?line('$1')}], [] }.
|
||||
base_expr -> '...' : { ?id('$1'), [{line,?line('$1')}], [] }.
|
||||
base_expr -> sigil : build_sigil('$1').
|
||||
|
||||
%% Blocks
|
||||
@@ -214,9 +207,13 @@ stab_eol -> stab : '$1'.
|
||||
stab_eol -> stab eol : '$1'.
|
||||
|
||||
stab_expr -> expr : '$1'.
|
||||
stab_expr -> stab_op stab_maybe_expr : build_op('$1', [], '$2').
|
||||
stab_expr -> call_args_no_parens stab_op stab_maybe_expr : build_op('$2', '$1', '$3').
|
||||
stab_expr -> call_args_parens_not_one stab_op stab_maybe_expr : build_op('$2', '$1', '$3').
|
||||
stab_expr -> stab_op_eol stab_maybe_expr : build_op('$1', [], '$2').
|
||||
stab_expr -> call_args_no_parens stab_op_eol stab_maybe_expr :
|
||||
build_op('$2', unwrap_when(unwrap_splice('$1')), '$3').
|
||||
stab_expr -> call_args_parens_not_one stab_op_eol stab_maybe_expr :
|
||||
build_op('$2', unwrap_splice('$1'), '$3').
|
||||
stab_expr -> call_args_parens_not_one when_op matched_expr stab_op_eol stab_maybe_expr :
|
||||
build_op('$4', [{ 'when', [{line,?line('$2')}], unwrap_splice('$1') ++ ['$3'] }], '$5').
|
||||
|
||||
stab_maybe_expr -> 'expr' : '$1'.
|
||||
stab_maybe_expr -> '$empty' : nil.
|
||||
@@ -236,7 +233,7 @@ open_paren -> '(' eol : '$1'.
|
||||
close_paren -> ')' : '$1'.
|
||||
close_paren -> eol ')' : '$2'.
|
||||
|
||||
empty_paren -> open_paren ')' : nil.
|
||||
empty_paren -> open_paren ')' : '$1'.
|
||||
|
||||
open_bracket -> '[' : '$1'.
|
||||
open_bracket -> '[' eol : '$1'.
|
||||
@@ -255,106 +252,69 @@ close_curly -> eol '}' : '$2'.
|
||||
|
||||
% Operators
|
||||
|
||||
add_op -> '+' : '$1'.
|
||||
add_op -> '-' : '$1'.
|
||||
add_op -> '+' eol : '$1'.
|
||||
add_op -> '-' eol : '$1'.
|
||||
add_op_eol -> add_op : '$1'.
|
||||
add_op_eol -> add_op eol : '$1'.
|
||||
add_op_eol -> dual_op : '$1'.
|
||||
add_op_eol -> dual_op eol : '$1'.
|
||||
|
||||
mult_op -> '*' : '$1'.
|
||||
mult_op -> '/' : '$1'.
|
||||
mult_op -> '*' eol : '$1'.
|
||||
mult_op -> '/' eol : '$1'.
|
||||
mult_op_eol -> mult_op : '$1'.
|
||||
mult_op_eol -> mult_op eol : '$1'.
|
||||
|
||||
two_op -> '++' : '$1'.
|
||||
two_op -> '--' : '$1'.
|
||||
two_op -> '++' eol : '$1'.
|
||||
two_op -> '--' eol : '$1'.
|
||||
two_op -> '**' : '$1'.
|
||||
two_op -> '**' eol : '$1'.
|
||||
exp_op_eol -> exp_op : '$1'.
|
||||
exp_op_eol -> exp_op eol : '$1'.
|
||||
|
||||
right_op -> '=~' : '$1'.
|
||||
right_op -> '=~' eol : '$1'.
|
||||
right_op -> '|>' : '$1'.
|
||||
right_op -> '|>' eol : '$1'.
|
||||
two_op_eol -> two_op : '$1'.
|
||||
two_op_eol -> two_op eol : '$1'.
|
||||
|
||||
three_op -> '&&&' : '$1'.
|
||||
three_op -> '&&&' eol : '$1'.
|
||||
three_op -> '|||' : '$1'.
|
||||
three_op -> '|||' eol : '$1'.
|
||||
three_op -> '<<<' : '$1'.
|
||||
three_op -> '<<<' eol : '$1'.
|
||||
three_op -> '>>>' : '$1'.
|
||||
three_op -> '>>>' eol : '$1'.
|
||||
three_op -> '^^^' : '$1'.
|
||||
three_op -> '^^^' eol : '$1'.
|
||||
default_op_eol -> default_op : '$1'.
|
||||
default_op_eol -> default_op eol : '$1'.
|
||||
|
||||
default_op -> '//' : '$1'.
|
||||
default_op -> '//' eol : '$1'.
|
||||
type_op_eol -> type_op : '$1'.
|
||||
type_op_eol -> type_op eol : '$1'.
|
||||
|
||||
colon_colon_op -> '::' : '$1'.
|
||||
colon_colon_op -> '::' eol : '$1'.
|
||||
unary_op_eol -> unary_op : '$1'.
|
||||
unary_op_eol -> unary_op eol : '$1'.
|
||||
unary_op_eol -> dual_op : '$1'.
|
||||
unary_op_eol -> dual_op eol : '$1'.
|
||||
|
||||
unary_op -> '+' : '$1'.
|
||||
unary_op -> '+' eol : '$1'.
|
||||
unary_op -> '-' : '$1'.
|
||||
unary_op -> '-' eol : '$1'.
|
||||
unary_op -> '!' : '$1'.
|
||||
unary_op -> '!' eol : '$1'.
|
||||
unary_op -> '^' : '$1'.
|
||||
unary_op -> '^' eol : '$1'.
|
||||
unary_op -> 'not' : '$1'.
|
||||
unary_op -> 'not' eol : '$1'.
|
||||
unary_op -> '~~~' : '$1'.
|
||||
unary_op -> '~~~' eol : '$1'.
|
||||
match_op_eol -> match_op : '$1'.
|
||||
match_op_eol -> match_op eol : '$1'.
|
||||
|
||||
at_op -> '@' : '$1'.
|
||||
at_op -> '@' eol : '$1'.
|
||||
and_op_eol -> and_op : '$1'.
|
||||
and_op_eol -> and_op eol : '$1'.
|
||||
|
||||
match_op -> '=' : '$1'.
|
||||
match_op -> '=' eol : '$1'.
|
||||
or_op_eol -> or_op : '$1'.
|
||||
or_op_eol -> or_op eol : '$1'.
|
||||
|
||||
andand_op -> '&&' : '$1'.
|
||||
andand_op -> '&&' eol : '$1'.
|
||||
tail_op_eol -> tail_op : '$1'.
|
||||
tail_op_eol -> tail_op eol : '$1'.
|
||||
|
||||
oror_op -> '||' : '$1'.
|
||||
oror_op -> '||' eol : '$1'.
|
||||
than_op_eol -> than_op : '$1'.
|
||||
than_op_eol -> than_op eol : '$1'.
|
||||
|
||||
and_op -> 'and' : '$1'.
|
||||
and_op -> 'and' eol : '$1'.
|
||||
in_op_eol -> in_op : '$1'.
|
||||
in_op_eol -> in_op eol : '$1'.
|
||||
|
||||
or_op -> 'or' : '$1'.
|
||||
or_op -> 'or' eol : '$1'.
|
||||
or_op -> 'xor' : '$1'.
|
||||
or_op -> 'xor' eol : '$1'.
|
||||
inc_op_eol -> inc_op : '$1'.
|
||||
inc_op_eol -> inc_op eol : '$1'.
|
||||
|
||||
pipe_op -> '|' : '$1'.
|
||||
pipe_op -> '|' eol : '$1'.
|
||||
when_op_eol -> when_op : '$1'.
|
||||
when_op_eol -> when_op eol : '$1'.
|
||||
|
||||
bin_concat_op -> '<>' : '$1'.
|
||||
bin_concat_op -> '<>' eol : '$1'.
|
||||
stab_op_eol -> stab_op : '$1'.
|
||||
stab_op_eol -> stab_op eol : '$1'.
|
||||
|
||||
in_op -> 'in' : '$1'.
|
||||
in_op -> 'in' eol : '$1'.
|
||||
range_op_eol -> range_op : '$1'.
|
||||
range_op_eol -> range_op eol : '$1'.
|
||||
|
||||
inc_op -> 'inlist' : '$1'.
|
||||
inc_op -> 'inlist' eol : '$1'.
|
||||
inc_op -> 'inbits' : '$1'.
|
||||
inc_op -> 'inbits' eol : '$1'.
|
||||
at_op_eol -> at_op : '$1'.
|
||||
at_op_eol -> at_op eol : '$1'.
|
||||
|
||||
when_op -> 'when' : '$1'.
|
||||
when_op -> 'when' eol : '$1'.
|
||||
comp_op_eol -> comp_op : '$1'.
|
||||
comp_op_eol -> comp_op eol : '$1'.
|
||||
|
||||
stab_op -> '->' : '$1'.
|
||||
stab_op -> '->' eol : '$1'.
|
||||
|
||||
send_op -> '<-' : '$1'.
|
||||
send_op -> '<-' eol : '$1'.
|
||||
|
||||
range_op -> '..' : '$1'.
|
||||
range_op -> '..' eol : '$1'.
|
||||
|
||||
comp_expr_op -> comp_op : '$1'.
|
||||
comp_expr_op -> comp_op eol : '$1'.
|
||||
arrow_op_eol -> arrow_op : '$1'.
|
||||
arrow_op_eol -> arrow_op eol : '$1'.
|
||||
|
||||
% Dot operator
|
||||
|
||||
@@ -390,7 +350,7 @@ matched_comma_expr -> matched_expr : ['$1'].
|
||||
matched_comma_expr -> matched_comma_expr ',' matched_expr : ['$3'|'$1'].
|
||||
|
||||
call_args_no_parens_strict -> call_args_no_parens : '$1'.
|
||||
call_args_no_parens_strict -> open_paren ')' : throw_no_parens_strict('$1').
|
||||
call_args_no_parens_strict -> empty_paren : throw_no_parens_strict('$1').
|
||||
call_args_no_parens_strict -> open_paren matched_kw_base close_paren : throw_no_parens_strict('$1').
|
||||
call_args_no_parens_strict -> open_paren matched_expr ',' call_args_no_parens close_paren : throw_no_parens_strict('$1').
|
||||
|
||||
@@ -398,7 +358,7 @@ call_args_no_parens -> matched_comma_expr : lists:reverse('$1').
|
||||
call_args_no_parens -> matched_kw_base : ['$1'].
|
||||
call_args_no_parens -> matched_comma_expr ',' matched_kw_base : lists:reverse(['$3'|'$1']).
|
||||
|
||||
call_args_parens_not_one -> open_paren ')' : [].
|
||||
call_args_parens_not_one -> empty_paren : [].
|
||||
call_args_parens_not_one -> open_paren matched_kw_base close_paren : ['$2'].
|
||||
call_args_parens_not_one -> open_paren matched_expr ',' call_args_no_parens close_paren : ['$2'|'$4'].
|
||||
|
||||
@@ -416,7 +376,7 @@ optional_comma_expr -> paren_expr ',' : '$1'.
|
||||
|
||||
call_args -> comma_expr : lists:reverse('$1').
|
||||
|
||||
call_args_parens -> open_paren ')' : [].
|
||||
call_args_parens -> empty_paren : [].
|
||||
call_args_parens -> open_paren call_args close_paren : '$2'.
|
||||
|
||||
% KV
|
||||
@@ -462,7 +422,7 @@ bit_string -> open_bit call_args close_bit : { '<<>>', [{line,?line('$1')}], '$2
|
||||
|
||||
Erlang code.
|
||||
|
||||
-define(op(Node), element(1, Node)).
|
||||
-define(id(Node), element(1, Node)).
|
||||
-define(line(Node), element(2, Node)).
|
||||
-define(exprs(Node), element(3, Node)).
|
||||
|
||||
@@ -475,17 +435,14 @@ Erlang code.
|
||||
|
||||
%% Operators
|
||||
|
||||
build_op({ _, _, _ } = Op, Left, Right) ->
|
||||
{ ?exprs(Op), [{line,?line(Op)}], [Left, Right] };
|
||||
|
||||
build_op({ BOp, Line }, { UOp, _, [Left] }, Right) when ?rearrange_bop(BOp), ?rearrange_uop(UOp) ->
|
||||
build_op({ _Kind, Line, BOp }, { UOp, _, [Left] }, Right) when ?rearrange_bop(BOp), ?rearrange_uop(UOp) ->
|
||||
{ UOp, [{line,Line}], [{ BOp, [{line,Line}], [Left, Right] }] };
|
||||
|
||||
build_op(Op, Left, Right) ->
|
||||
{ ?op(Op), [{line,?line(Op)}], [Left, Right] }.
|
||||
build_op({ _Kind, Line, Op }, Left, Right) ->
|
||||
{ Op, [{line,Line}], [Left, Right] }.
|
||||
|
||||
build_unary_op(Op, Expr) ->
|
||||
{ ?op(Op), [{line,?line(Op)}], [Expr] }.
|
||||
build_unary_op({ _Kind, Line, Op }, Expr) ->
|
||||
{ Op, [{line,Line}], [Expr] }.
|
||||
|
||||
build_tuple(_Marker, [Left, Right]) ->
|
||||
{ Left, Right };
|
||||
@@ -551,13 +508,13 @@ build_fn(Op, Stab) ->
|
||||
|
||||
build_access(Expr, Access) ->
|
||||
Meta = [{line,?line(Access)}],
|
||||
{ { '.', Meta, ['Elixir-Kernel', access] }, Meta, [ Expr, ?op(Access) ] }.
|
||||
{ { '.', Meta, ['Elixir.Kernel', access] }, Meta, [ Expr, ?id(Access) ] }.
|
||||
|
||||
%% Interpolation aware
|
||||
|
||||
build_sigil({ sigil, Line, Sigil, Parts, Modifiers }) ->
|
||||
Meta = [{line,Line}],
|
||||
{ list_to_atom([$_,$_,Sigil,$_,$_]), Meta, [ { '<<>>', Meta, Parts }, Modifiers ] }.
|
||||
{ list_to_atom("sigil_" ++ [Sigil]), Meta, [ { '<<>>', Meta, Parts }, Modifiers ] }.
|
||||
|
||||
build_bin_string({ bin_string, _Line, [H] }) when is_binary(H) -> H;
|
||||
build_bin_string({ bin_string, Line, Args }) -> { '<<>>', [{line,Line}], Args }.
|
||||
@@ -575,22 +532,42 @@ build_atom({ atom, Line, Args }) ->
|
||||
%% Keywords
|
||||
|
||||
build_stab([{ '->', Meta, [Left, Right] }|T]) ->
|
||||
{ '->', Meta, build_stab(T, Left, [Right], []) };
|
||||
{ '->', Meta, build_stab(Meta, T, Left, [Right], []) };
|
||||
|
||||
build_stab(Else) ->
|
||||
build_block(Else).
|
||||
|
||||
build_stab([{ '->', _, [Left, Right] }|T], Marker, Temp, Acc) ->
|
||||
H = { Marker, build_block(lists:reverse(Temp)) },
|
||||
build_stab(T, Left, [Right], [H|Acc]);
|
||||
build_stab(Old, [{ '->', New, [Left, Right] }|T], Marker, Temp, Acc) ->
|
||||
H = { Marker, Old, build_block(lists:reverse(Temp)) },
|
||||
build_stab(New, T, Left, [Right], [H|Acc]);
|
||||
|
||||
build_stab([H|T], Marker, Temp, Acc) ->
|
||||
build_stab(T, Marker, [H|Temp], Acc);
|
||||
build_stab(Meta, [H|T], Marker, Temp, Acc) ->
|
||||
build_stab(Meta, T, Marker, [H|Temp], Acc);
|
||||
|
||||
build_stab([], Marker, Temp, Acc) ->
|
||||
H = { Marker, build_block(lists:reverse(Temp)) },
|
||||
build_stab(Meta, [], Marker, Temp, Acc) ->
|
||||
H = { Marker, Meta, build_block(lists:reverse(Temp)) },
|
||||
lists:reverse([H|Acc]).
|
||||
|
||||
%% Every time the parser sees a (unquote_splicing())
|
||||
%% it assumes that a block is being spliced, wrapping
|
||||
%% the splicing in a __block__. But in the stab clause,
|
||||
%% we can have (unquote_splicing(1,2,3)) -> :ok, in such
|
||||
%% case, we don't actually want the block, since it is
|
||||
%% an arg style call. unwrap_splice unwraps the splice
|
||||
%% from such blocks.
|
||||
unwrap_splice([{ '__block__', [], [{ unquote_splicing, _, _ }] = Splice }]) ->
|
||||
Splice;
|
||||
|
||||
unwrap_splice(Other) -> Other.
|
||||
|
||||
unwrap_when(Args) ->
|
||||
case elixir_tree_helpers:split_last(Args) of
|
||||
{ Start, { 'when', Meta, [_, _] = End } } ->
|
||||
[{ 'when', Meta, Start ++ End }];
|
||||
{ _, _ } ->
|
||||
Args
|
||||
end.
|
||||
|
||||
%% Errors
|
||||
|
||||
throw(Line, Error, Token) ->
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
%% Responsible handling Elixir partials with &1, &2 and so on.
|
||||
-module(elixir_partials).
|
||||
-export([handle/2, handle/3, is_sequential/1]).
|
||||
-include("elixir.hrl").
|
||||
-compile({parse_transform, elixir_transform}).
|
||||
|
||||
handle(Original, S) ->
|
||||
handle(Original, S, default).
|
||||
@@ -11,7 +11,7 @@ handle({ _, Meta, Args } = Original, S, Opt) when is_list(Args), S#elixir_scope.
|
||||
{ Call, Def, SC } when Def /= [] ->
|
||||
Final = validate(Meta, Def, SC),
|
||||
Block = setelement(3, Original, Call),
|
||||
elixir_translator:translate_fn(Meta, [{ Final, Block }], SC);
|
||||
elixir_translator:translate_fn(Meta, [{ Final, Meta, Block }], SC);
|
||||
_ -> error
|
||||
end;
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
%% Implements Elixir quote.
|
||||
-module(elixir_quote).
|
||||
-export([escape/2, erl_escape/3, erl_quote/3, linify/2, unquote/5]).
|
||||
-compile({parse_transform, elixir_transform}).
|
||||
-export([escape/2, erl_escape/3, erl_quote/4, linify/2, unquote/5]).
|
||||
-include("elixir.hrl").
|
||||
|
||||
%% Apply the line from site call on quoted contents.
|
||||
@@ -29,7 +29,7 @@ unquote(_File, Meta, Left, { '__aliases__', _, Args }, nil) ->
|
||||
|
||||
unquote(_File, Meta, Left, Right, nil) when is_atom(Right) ->
|
||||
case atom_to_list(Right) of
|
||||
"Elixir-" ++ _ ->
|
||||
"Elixir." ++ _ ->
|
||||
{ '__aliases__', Meta, [Left, Right] };
|
||||
_ ->
|
||||
{ { '.', Meta, [Left, Right] }, Meta, [] }
|
||||
@@ -44,7 +44,7 @@ unquote(File, Meta, _Left, _Right, _Args) ->
|
||||
%% Escapes the given expression. It is similar to quote, but
|
||||
%% lines are kept and hygiene mechanisms are disabled.
|
||||
escape(Expr, Unquote) ->
|
||||
quote(Expr, #elixir_quote{
|
||||
quote(Expr, nil, #elixir_quote{
|
||||
line=keep,
|
||||
vars_hygiene=false,
|
||||
aliases_hygiene=false,
|
||||
@@ -60,15 +60,24 @@ erl_escape(Expr, Unquote, S) ->
|
||||
|
||||
%% Quotes an expression and return its quoted Elixir AST.
|
||||
|
||||
quote(Expr, Q, S) ->
|
||||
do_quote(Expr, Q, S).
|
||||
quote(Expr, nil, Q, S) ->
|
||||
do_quote(Expr, Q, S);
|
||||
|
||||
erl_quote({ 'unquote_splicing', Meta, [_] } = Expr, #elixir_quote{unquote=true} = Q, S) ->
|
||||
elixir_errors:deprecation(Meta, S#elixir_scope.file, "unquote_splicing in the quote body is deprecated, please use (unquote_splicing()) instead", []),
|
||||
erl_quote({ '__block__', Meta, [Expr] }, Q, S);
|
||||
quote(Expr, Binding, Q, S) ->
|
||||
Context = Q#elixir_quote.context,
|
||||
|
||||
erl_quote(Expr, Q, S) ->
|
||||
{ QExpr, TQ } = quote(Expr, Q, S),
|
||||
Vars = [ { '{}', [],
|
||||
[ '=', [], [
|
||||
{ '{}', [], [K, [], Context] },
|
||||
V
|
||||
] ]
|
||||
} || { K, V } <- Binding],
|
||||
|
||||
{ TExprs, TQ } = do_quote(Expr, Q, S),
|
||||
{ { '{}',[], ['__block__',[], Vars ++ [TExprs] ] }, TQ }.
|
||||
|
||||
erl_quote(Expr, Binding, Q, S) ->
|
||||
{ QExpr, TQ } = quote(Expr, Binding, Q, S),
|
||||
{ TExpr, TS } = elixir_translator:translate_each(QExpr, S),
|
||||
{ TExpr, TQ, TS }.
|
||||
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
%% Convenience functions used to manipulate scope
|
||||
%% and its variables.
|
||||
%% Convenience functions used to manipulate scope and its variables.
|
||||
-module(elixir_scope).
|
||||
-export([translate_var/5,
|
||||
build_erl_var/2, build_ex_var/2,
|
||||
@@ -9,11 +8,11 @@
|
||||
umergev/2, umergec/2, merge_clause_vars/2
|
||||
]).
|
||||
-include("elixir.hrl").
|
||||
-compile({parse_transform, elixir_transform}).
|
||||
|
||||
translate_var(Meta, Name, Kind, S, Callback) ->
|
||||
Line = ?line(Meta),
|
||||
Vars = S#elixir_scope.vars,
|
||||
Tuple = { Name, Kind },
|
||||
|
||||
case Name of
|
||||
'_' ->
|
||||
@@ -22,9 +21,9 @@ translate_var(Meta, Name, Kind, S, Callback) ->
|
||||
case S#elixir_scope.context of
|
||||
match ->
|
||||
TempVars = S#elixir_scope.temp_vars,
|
||||
case { orddict:is_key({ Name, Kind }, Vars), orddict:find(Name, TempVars) } of
|
||||
{ true, { ok, Kind } } ->
|
||||
{ { var, Line, orddict:fetch({ Name, Kind }, Vars) }, S };
|
||||
case { orddict:is_key(Tuple, Vars), is_list(TempVars) andalso ordsets:is_element(Tuple, TempVars) } of
|
||||
{ true, true } ->
|
||||
{ { var, Line, orddict:fetch(Tuple, Vars) }, S };
|
||||
{ Else, _ } ->
|
||||
{ NewVar, NS } = if
|
||||
Kind /= nil -> build_erl_var(Line, S);
|
||||
@@ -35,11 +34,14 @@ translate_var(Meta, Name, Kind, S, Callback) ->
|
||||
RealName = element(3, NewVar),
|
||||
ClauseVars = S#elixir_scope.clause_vars,
|
||||
{ NewVar, NS#elixir_scope{
|
||||
vars=orddict:store({ Name, Kind }, RealName, Vars),
|
||||
temp_vars=orddict:store(Name, Kind, TempVars),
|
||||
vars=orddict:store(Tuple, RealName, Vars),
|
||||
temp_vars=if
|
||||
TempVars == nil -> TempVars;
|
||||
true -> ordsets:add_element(Tuple, TempVars)
|
||||
end,
|
||||
clause_vars=if
|
||||
ClauseVars == nil -> nil;
|
||||
true -> orddict:store({ Name, Kind }, RealName, ClauseVars)
|
||||
ClauseVars == nil; Kind == false -> ClauseVars;
|
||||
true -> orddict:store(Tuple, RealName, ClauseVars)
|
||||
end
|
||||
} }
|
||||
end;
|
||||
@@ -77,18 +79,19 @@ build_ex_var(Line, Key, Name, S) when is_integer(Line) ->
|
||||
|
||||
% Handle Macro.Env conversion
|
||||
|
||||
to_erl_env({ 'Elixir.Macro.Env', Module, File, _Line, Function, Aliases, Context, Requires, Functions, Macros, FileModules }) ->
|
||||
to_erl_env({ 'Elixir.Macro.Env', Module, File, _Line, Function, Aliases, Context,
|
||||
Requires, Functions, Macros, ContextModules, MacroAliases }) ->
|
||||
#elixir_scope{module=Module,file=File,
|
||||
function=Function,aliases=Aliases,context=Context,
|
||||
requires=Requires,macros=Macros,functions=Functions,
|
||||
context_modules=FileModules}.
|
||||
context_modules=ContextModules,macro_aliases=MacroAliases}.
|
||||
|
||||
to_ex_env({ Line, #elixir_scope{module=Module,file=File,
|
||||
function=Function,aliases=Aliases,context=Context,
|
||||
requires=Requires,macros=Macros,functions=Functions,
|
||||
context_modules=FileModules} }) when is_integer(Line) ->
|
||||
context_modules=ContextModules,macro_aliases=MacroAliases} }) when is_integer(Line) ->
|
||||
{ 'Elixir.Macro.Env', Module, File, Line, Function, Aliases,
|
||||
Context, Requires, Functions, Macros, FileModules }.
|
||||
Context, Requires, Functions, Macros, ContextModules, MacroAliases }.
|
||||
|
||||
% Provides a tuple with only the scope information we want to serialize.
|
||||
|
||||
|
||||
+195
-117
@@ -10,73 +10,93 @@
|
||||
existing_atoms_only=false
|
||||
}).
|
||||
|
||||
-define(container2(T1, T2),
|
||||
-define(container(T1, T2),
|
||||
T1 == ${, T2 == $};
|
||||
T1 == $[, T2 == $]
|
||||
).
|
||||
|
||||
-define(comp3(T1, T2, T3),
|
||||
T1 == $=, T2 == $=, T3 == $=;
|
||||
T1 == $!, T2 == $=, T3 == $=
|
||||
).
|
||||
%% New ops table
|
||||
|
||||
-define(op3(T1, T2, T3),
|
||||
T1 == $<, T2 == $<, T3 == $<;
|
||||
T1 == $>, T2 == $>, T3 == $>;
|
||||
T1 == $^, T2 == $^, T3 == $^;
|
||||
T1 == $~, T2 == $~, T3 == $~;
|
||||
T1 == $&, T2 == $&, T3 == $&;
|
||||
T1 == $|, T2 == $|, T3 == $|
|
||||
).
|
||||
|
||||
-define(comp2(T1, T2),
|
||||
T1 == $=, T2 == $=;
|
||||
T1 == $!, T2 == $=;
|
||||
T1 == $<, T2 == $=;
|
||||
T1 == $>, T2 == $=
|
||||
).
|
||||
|
||||
-define(op2(T1, T2),
|
||||
T1 == $&, T2 == $&;
|
||||
T1 == $|, T2 == $|;
|
||||
T1 == $<, T2 == $>;
|
||||
T1 == $+, T2 == $+;
|
||||
T1 == $-, T2 == $-;
|
||||
T1 == $*, T2 == $*;
|
||||
T1 == $/, T2 == $/;
|
||||
T1 == $:, T2 == $:;
|
||||
T1 == $<, T2 == $-;
|
||||
T1 == $-, T2 == $>;
|
||||
T1 == $., T2 == $.;
|
||||
T1 == $|, T2 == $>;
|
||||
T1 == $/, T2 == $>;
|
||||
T1 == $=, T2 == $~
|
||||
).
|
||||
|
||||
-define(comp1(T),
|
||||
T == $<;
|
||||
T == $>
|
||||
).
|
||||
|
||||
-define(op1(T),
|
||||
T == $+;
|
||||
T == $-;
|
||||
T == $*;
|
||||
T == $/;
|
||||
T == $=;
|
||||
T == $|;
|
||||
T == $!;
|
||||
T == $^;
|
||||
T == $@
|
||||
).
|
||||
-define(at_op(T),
|
||||
T == $@).
|
||||
|
||||
-define(unary_op(T),
|
||||
T == '+';
|
||||
T == '-';
|
||||
T == '@';
|
||||
T == '!';
|
||||
T == '^'
|
||||
).
|
||||
% T == $&;
|
||||
T == $!;
|
||||
T == $^).
|
||||
|
||||
-define(unary_op3(T1, T2, T3),
|
||||
T1 == $~, T2 == $~, T3 == $~).
|
||||
|
||||
-define(exp_op3(T1, T2, T3),
|
||||
T1 == $^, T2 == $^, T3 == $^).
|
||||
|
||||
-define(two_op(T1, T2),
|
||||
T1 == $+, T2 == $+;
|
||||
T1 == $-, T2 == $-;
|
||||
T1 == $*, T2 == $*).
|
||||
|
||||
-define(than_op(T1, T2),
|
||||
T1 == $<, T2 == $>).
|
||||
|
||||
-define(mult_op(T),
|
||||
T == $* orelse T == $/).
|
||||
|
||||
-define(dual_op(T),
|
||||
T == $+ orelse T == $-).
|
||||
|
||||
-define(range_op(T1, T2),
|
||||
T1 == $., T2 == $.).
|
||||
|
||||
-define(arrow_op3(T1, T2, T3),
|
||||
T1 == $<, T2 == $<, T3 == $<;
|
||||
T1 == $>, T2 == $>, T3 == $>).
|
||||
|
||||
-define(arrow_op(T1, T2),
|
||||
T1 == $<, T2 == $-;
|
||||
T1 == $|, T2 == $>).
|
||||
|
||||
-define(comp_op(T),
|
||||
T == $<;
|
||||
T == $>).
|
||||
|
||||
-define(comp_op2(T1, T2),
|
||||
T1 == $=, T2 == $=;
|
||||
T1 == $=, T2 == $~;
|
||||
T1 == $!, T2 == $=;
|
||||
T1 == $<, T2 == $=;
|
||||
T1 == $>, T2 == $=).
|
||||
|
||||
-define(comp_op3(T1, T2, T3),
|
||||
T1 == $=, T2 == $=, T3 == $=;
|
||||
T1 == $!, T2 == $=, T3 == $=).
|
||||
|
||||
-define(and_op(T1, T2),
|
||||
T1 == $&, T2 == $&).
|
||||
|
||||
-define(or_op(T1, T2),
|
||||
T1 == $|, T2 == $|).
|
||||
|
||||
-define(and_op3(T1, T2, T3),
|
||||
T1 == $&, T2 == $&, T3 == $&).
|
||||
|
||||
-define(or_op3(T1, T2, T3),
|
||||
T1 == $|, T2 == $|, T3 == $|).
|
||||
|
||||
-define(match_op(T),
|
||||
T == $=).
|
||||
|
||||
-define(tail_op(T),
|
||||
T == $|).
|
||||
|
||||
-define(default_op(T1, T2),
|
||||
T1 == $/, T2 == $/).
|
||||
|
||||
-define(stab_op(T1, T2),
|
||||
T1 == $-, T2 == $>).
|
||||
|
||||
-define(type_op(T1, T2),
|
||||
T1 == $:, T2 == $:).
|
||||
|
||||
tokenize(String, Line, Opts) ->
|
||||
File = case lists:keyfind(file, 1, Opts) of
|
||||
@@ -205,13 +225,10 @@ tokenize([$?,Char|T], Line, Scope, Tokens) ->
|
||||
tokenize(T, Line, Scope, [{ number, Line, Char }|Tokens]);
|
||||
|
||||
% Dot identifier/operators
|
||||
|
||||
tokenize("..." ++ Rest, Line, Scope, Tokens) ->
|
||||
Token = check_call_identifier(identifier, Line, '...', Rest),
|
||||
tokenize(Rest, Line, Scope, [Token|Tokens]);
|
||||
|
||||
% ## Containers
|
||||
|
||||
tokenize([$.,T|Tail], Line, Scope, Tokens) when ?is_space(T) ->
|
||||
case [T|Tail] of
|
||||
[$\r,$\n|Rest] -> tokenize([$.|Rest], Line + 1, Scope, Tokens);
|
||||
@@ -219,22 +236,30 @@ tokenize([$.,T|Tail], Line, Scope, Tokens) when ?is_space(T) ->
|
||||
[_|Rest] -> tokenize([$.|Rest], Line, Scope, Tokens)
|
||||
end;
|
||||
|
||||
% ## Containers
|
||||
tokenize(".<<>>" ++ Rest, Line, Scope, Tokens) ->
|
||||
handle_call_identifier(Rest, Line, '<<>>', Scope, Tokens);
|
||||
|
||||
tokenize([$.,T1,T2|Rest], Line, Scope, Tokens) when ?container2(T1, T2) ->
|
||||
tokenize([$.,T1,T2|Rest], Line, Scope, Tokens) when ?container(T1, T2) ->
|
||||
handle_call_identifier(Rest, Line, list_to_atom([T1, T2]), Scope, Tokens);
|
||||
|
||||
% ## Three Token Operators
|
||||
tokenize([$.,T1,T2,T3|Rest], Line, Scope, Tokens) when ?comp3(T1, T2, T3); ?op3(T1, T2, T3) ->
|
||||
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);
|
||||
?arrow_op3(T1, T2, T3); ?exp_op3(T1, T2, T3) ->
|
||||
handle_call_identifier(Rest, Line, list_to_atom([T1, T2, T3]), Scope, Tokens);
|
||||
|
||||
% ## Two Token Operators
|
||||
tokenize([$.,T1,T2|Rest], Line, Scope, Tokens) when ?comp2(T1, T2); ?op2(T1, T2) ->
|
||||
tokenize([$.,T1,T2|Rest], Line, Scope, Tokens) when
|
||||
?comp_op2(T1, T2); ?and_op(T1, T2); ?or_op(T1, T2); ?arrow_op(T1, T2);
|
||||
?range_op(T1, T2); ?than_op(T1, T2); ?default_op(T1, T2); ?two_op(T1, T2);
|
||||
?stab_op(T1, T2); ?type_op(T1, T2) ->
|
||||
handle_call_identifier(Rest, Line, list_to_atom([T1, T2]), Scope, Tokens);
|
||||
|
||||
% ## Single Token Operators
|
||||
tokenize([$.,T|Rest], Line, Scope, Tokens) when ?comp1(T); ?op1(T); T == $& ->
|
||||
tokenize([$.,T|Rest], Line, Scope, Tokens) when
|
||||
?at_op(T); ?unary_op(T); ?dual_op(T); ?mult_op(T); ?comp_op(T);
|
||||
?match_op(T); ?tail_op(T); T == $& ->
|
||||
handle_call_identifier(Rest, Line, list_to_atom([T]), Scope, Tokens);
|
||||
|
||||
% Dot call
|
||||
@@ -295,19 +320,26 @@ tokenize(":..." ++ Rest, Line, Scope, Tokens) ->
|
||||
tokenize(":<<>>" ++ Rest, Line, Scope, Tokens) ->
|
||||
tokenize(Rest, Line, Scope, [{ atom, Line, '<<>>' }|Tokens]);
|
||||
|
||||
tokenize([$:,T1,T2|Rest], Line, Scope, Tokens) when ?container2(T1, T2) ->
|
||||
tokenize([$:,T1,T2|Rest], Line, Scope, Tokens) when ?container(T1, T2) ->
|
||||
tokenize(Rest, Line, Scope, [{ atom, Line, list_to_atom([T1,T2]) }|Tokens]);
|
||||
|
||||
% ## Three Token Operators
|
||||
tokenize([$:,T1,T2,T3|Rest], Line, Scope, Tokens) when ?comp3(T1, T2, T3); ?op3(T1, T2, T3) ->
|
||||
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);
|
||||
?arrow_op3(T1, T2, T3); ?exp_op3(T1, T2, T3) ->
|
||||
tokenize(Rest, Line, Scope, [{ atom, Line, list_to_atom([T1,T2,T3]) }|Tokens]);
|
||||
|
||||
% ## Two Token Operators
|
||||
tokenize([$:,T1,T2|Rest], Line, Scope, Tokens) when ?comp2(T1, T2); ?op2(T1, T2) ->
|
||||
tokenize([$:,T1,T2|Rest], Line, Scope, Tokens) when
|
||||
?comp_op2(T1, T2); ?and_op(T1, T2); ?or_op(T1, T2); ?arrow_op(T1, T2);
|
||||
?range_op(T1, T2); ?than_op(T1, T2); ?default_op(T1, T2); ?two_op(T1, T2);
|
||||
?stab_op(T1, T2); ?type_op(T1, T2) ->
|
||||
tokenize(Rest, Line, Scope, [{ atom, Line, list_to_atom([T1,T2]) }|Tokens]);
|
||||
|
||||
% ## Single Token Operators
|
||||
tokenize([$:,T|Rest], Line, Scope, Tokens) when ?comp1(T); ?op1(T); T == $&; T == $. ->
|
||||
tokenize([$:,T|Rest], Line, Scope, Tokens) when
|
||||
?at_op(T); ?unary_op(T); ?dual_op(T); ?mult_op(T); ?comp_op(T);
|
||||
?match_op(T); ?tail_op(T); T == $&; T == $. ->
|
||||
tokenize(Rest, Line, Scope, [{ atom, Line, list_to_atom([T]) }|Tokens]);
|
||||
|
||||
% End of line
|
||||
@@ -336,13 +368,24 @@ tokenize("\r\n" ++ Rest, Line, Scope, Tokens) ->
|
||||
tokenize([$&,H|Rest], Line, Scope, Tokens) when ?is_digit(H) ->
|
||||
tokenize(Rest, Line, Scope, [{ '&', Line, [list_to_integer([H])] }|Tokens]);
|
||||
|
||||
% ## Comparison three token operators
|
||||
tokenize([T1,T2,T3|Rest], Line, Scope, Tokens) when ?comp3(T1, T2, T3) ->
|
||||
handle_comp_op(Rest, Line, list_to_atom([T1,T2,T3]), Scope, Tokens);
|
||||
|
||||
% ## Three token operators
|
||||
tokenize([T1,T2,T3|Rest], Line, Scope, Tokens) when ?op3(T1, T2, T3) ->
|
||||
handle_op(Rest, Line, list_to_atom([T1,T2,T3]), Scope, Tokens);
|
||||
tokenize([T1,T2,T3|Rest], Line, Scope, Tokens) when ?unary_op3(T1, T2, T3) ->
|
||||
handle_nonl_op(Rest, Line, unary_op, list_to_atom([T1,T2,T3]), Scope, Tokens);
|
||||
|
||||
tokenize([T1,T2,T3|Rest], Line, Scope, Tokens) when ?comp_op3(T1, T2, T3) ->
|
||||
handle_op(Rest, Line, comp_op, list_to_atom([T1,T2,T3]), Scope, Tokens);
|
||||
|
||||
tokenize([T1,T2,T3|Rest], Line, Scope, Tokens) when ?and_op3(T1, T2, T3) ->
|
||||
handle_op(Rest, Line, and_op, list_to_atom([T1,T2,T3]), Scope, Tokens);
|
||||
|
||||
tokenize([T1,T2,T3|Rest], Line, Scope, Tokens) when ?or_op3(T1, T2, T3) ->
|
||||
handle_op(Rest, Line, or_op, list_to_atom([T1,T2,T3]), Scope, Tokens);
|
||||
|
||||
tokenize([T1,T2,T3|Rest], Line, Scope, Tokens) when ?arrow_op3(T1, T2, T3) ->
|
||||
handle_op(Rest, Line, arrow_op, list_to_atom([T1,T2,T3]), Scope, Tokens);
|
||||
|
||||
tokenize([T1,T2,T3|Rest], Line, Scope, Tokens) when ?exp_op3(T1, T2, T3) ->
|
||||
handle_op(Rest, Line, exp_op, list_to_atom([T1,T2,T3]), Scope, Tokens);
|
||||
|
||||
% ## Containers + punctuation tokens
|
||||
tokenize([T,T|Rest], Line, Scope, Tokens) when T == $<; T == $> ->
|
||||
@@ -354,21 +397,58 @@ tokenize([T|Rest], Line, Scope, Tokens) when T == $(;
|
||||
Token = { list_to_atom([T]), Line },
|
||||
handle_terminator(Rest, Line, Scope, Token, Tokens);
|
||||
|
||||
% ## Comparison two token operators
|
||||
tokenize([T1,T2|Rest], Line, Scope, Tokens) when ?comp2(T1, T2) ->
|
||||
handle_comp_op(Rest, Line, list_to_atom([T1, T2]), Scope, Tokens);
|
||||
|
||||
% ## Two Token Operators
|
||||
tokenize([T1,T2|Rest], Line, Scope, Tokens) when ?op2(T1, T2) ->
|
||||
handle_op(Rest, Line, list_to_atom([T1, T2]), Scope, Tokens);
|
||||
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);
|
||||
|
||||
% ## Comparison single token operators
|
||||
tokenize([T|Rest], Line, Scope, Tokens) when ?comp1(T) ->
|
||||
handle_comp_op(Rest, Line, list_to_atom([T]), Scope, Tokens);
|
||||
tokenize([T1,T2|Rest], Line, Scope, Tokens) when ?than_op(T1, T2) ->
|
||||
handle_op(Rest, Line, than_op, list_to_atom([T1, T2]), Scope, Tokens);
|
||||
|
||||
tokenize([T1,T2|Rest], Line, Scope, Tokens) when ?range_op(T1, T2) ->
|
||||
handle_op(Rest, Line, range_op, list_to_atom([T1, T2]), Scope, Tokens);
|
||||
|
||||
tokenize([T1,T2|Rest], Line, Scope, Tokens) when ?arrow_op(T1, T2) ->
|
||||
handle_op(Rest, Line, arrow_op, list_to_atom([T1, T2]), Scope, Tokens);
|
||||
|
||||
tokenize([T1,T2|Rest], Line, Scope, Tokens) when ?comp_op2(T1, T2) ->
|
||||
handle_op(Rest, Line, comp_op, list_to_atom([T1, T2]), Scope, Tokens);
|
||||
|
||||
tokenize([T1,T2|Rest], Line, Scope, Tokens) when ?and_op(T1, T2) ->
|
||||
handle_op(Rest, Line, and_op, list_to_atom([T1, T2]), Scope, Tokens);
|
||||
|
||||
tokenize([T1,T2|Rest], Line, Scope, Tokens) when ?or_op(T1, T2) ->
|
||||
handle_op(Rest, Line, or_op, list_to_atom([T1, T2]), Scope, Tokens);
|
||||
|
||||
tokenize([T1,T2|Rest], Line, Scope, Tokens) when ?default_op(T1, T2) ->
|
||||
handle_op(Rest, Line, default_op, list_to_atom([T1, T2]), Scope, Tokens);
|
||||
|
||||
tokenize([T1,T2|Rest], Line, Scope, Tokens) when ?stab_op(T1, T2) ->
|
||||
handle_op(Rest, Line, stab_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);
|
||||
|
||||
% ## Single Token Operators
|
||||
tokenize([T|Rest], Line, Scope, Tokens) when ?op1(T) ->
|
||||
handle_op(Rest, Line, list_to_atom([T]), Scope, Tokens);
|
||||
tokenize([T|Rest], Line, Scope, Tokens) when ?at_op(T) ->
|
||||
handle_nonl_op(Rest, Line, at_op, list_to_atom([T]), Scope, Tokens);
|
||||
|
||||
tokenize([T|Rest], Line, Scope, Tokens) when ?unary_op(T) ->
|
||||
handle_nonl_op(Rest, Line, unary_op, list_to_atom([T]), Scope, Tokens);
|
||||
|
||||
tokenize([T|Rest], Line, Scope, Tokens) when ?comp_op(T) ->
|
||||
handle_op(Rest, Line, comp_op, list_to_atom([T]), Scope, Tokens);
|
||||
|
||||
tokenize([T|Rest], Line, Scope, Tokens) when ?dual_op(T) ->
|
||||
handle_nonl_op(Rest, Line, dual_op, list_to_atom([T]), Scope, Tokens);
|
||||
|
||||
tokenize([T|Rest], Line, Scope, Tokens) when ?mult_op(T) ->
|
||||
handle_op(Rest, Line, mult_op, list_to_atom([T]), Scope, Tokens);
|
||||
|
||||
tokenize([T|Rest], Line, Scope, Tokens) when ?match_op(T) ->
|
||||
handle_op(Rest, Line, match_op, list_to_atom([T]), Scope, Tokens);
|
||||
|
||||
tokenize([T|Rest], Line, Scope, Tokens) when ?tail_op(T) ->
|
||||
handle_op(Rest, Line, tail_op, list_to_atom([T]), Scope, Tokens);
|
||||
|
||||
tokenize([$.|Rest], Line, Scope, Tokens) ->
|
||||
tokenize(Rest, Line, Scope, add_token_with_nl({ '.', Line }, Tokens));
|
||||
@@ -410,13 +490,13 @@ tokenize([H|_] = String, Line, Scope, Tokens) when ?is_downcase(H); H == $_ ->
|
||||
% Ambiguous unary/binary operators tokens
|
||||
|
||||
tokenize([Space, Sign, NotMarker|T], Line, Scope, [{ Identifier, _, _ } = H|Tokens]) when
|
||||
Sign == $+ orelse Sign == $-,
|
||||
?is_horizontal_space(Space),
|
||||
not(?is_space(NotMarker)),
|
||||
NotMarker /= $(, NotMarker /= $+, NotMarker /= $-, NotMarker /= $>,
|
||||
Identifier == identifier orelse Identifier == punctuated_identifier ->
|
||||
?dual_op(Sign),
|
||||
?is_horizontal_space(Space),
|
||||
not(?is_space(NotMarker)),
|
||||
NotMarker /= $(, NotMarker /= $+, NotMarker /= $-, NotMarker /= $>,
|
||||
Identifier == identifier orelse Identifier == punctuated_identifier ->
|
||||
Rest = [NotMarker|T],
|
||||
tokenize(Rest, Line, Scope, [{ list_to_atom([Sign]), Line }, setelement(1, H, op_identifier)|Tokens]);
|
||||
tokenize(Rest, Line, Scope, [{ dual_op, Line, list_to_atom([Sign]) }, setelement(1, H, op_identifier)|Tokens]);
|
||||
|
||||
% Spaces
|
||||
|
||||
@@ -462,20 +542,17 @@ handle_strings(T, Line, H, #scope{file=File} = Scope, Tokens) ->
|
||||
interpolation_error(Error, " (for string starting at line ~B)", [Line])
|
||||
end.
|
||||
|
||||
handle_comp_op([$:|Rest], Line, Op, Scope, Tokens) when ?is_space(hd(Rest)) ->
|
||||
handle_nonl_op([$:|Rest], Line, _Kind, Op, Scope, Tokens) when ?is_space(hd(Rest)) ->
|
||||
tokenize(Rest, Line, Scope, [{ kw_identifier, Line, Op }|Tokens]);
|
||||
|
||||
handle_comp_op(Rest, Line, Op, Scope, Tokens) ->
|
||||
tokenize(Rest, Line, Scope, add_token_with_nl({ comp_op, Line, Op }, Tokens)).
|
||||
handle_nonl_op(Rest, Line, Kind, Op, Scope, Tokens) ->
|
||||
tokenize(Rest, Line, Scope, [{ Kind, Line, Op }|Tokens]).
|
||||
|
||||
handle_op([$:|Rest], Line, Op, Scope, Tokens) when ?is_space(hd(Rest)) ->
|
||||
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, Op, Scope, Tokens) when ?unary_op(Op) ->
|
||||
tokenize(Rest, Line, Scope, [{ Op, Line }|Tokens]);
|
||||
|
||||
handle_op(Rest, Line, Op, Scope, Tokens) ->
|
||||
tokenize(Rest, Line, Scope, add_token_with_nl({ Op, Line }, Tokens)).
|
||||
handle_op(Rest, Line, Kind, Op, Scope, Tokens) ->
|
||||
tokenize(Rest, Line, Scope, add_token_with_nl({ Kind, Line, Op }, Tokens)).
|
||||
|
||||
handle_call_identifier(Rest, Line, Op, Scope, Tokens) ->
|
||||
Token = check_call_identifier(identifier, Line, Op, Rest),
|
||||
@@ -813,15 +890,16 @@ check_keyword(Line, Identifier, Atom, Tokens) when
|
||||
Identifier == identifier; Identifier == do_identifier;
|
||||
Identifier == bracket_identifier; Identifier == paren_identifier ->
|
||||
case keyword(Atom) of
|
||||
true -> { ok, [{ Atom, Line }|Tokens] };
|
||||
op -> { ok, add_token_with_nl({ Atom, Line }, Tokens) };
|
||||
block -> { ok, [{ block_identifier, Line, Atom }|Tokens] };
|
||||
do ->
|
||||
do ->
|
||||
case do_keyword_valid(Tokens) of
|
||||
true -> { ok, [{ Atom, Line }|Tokens] };
|
||||
false -> { error, "do" }
|
||||
end;
|
||||
false -> nomatch
|
||||
false -> nomatch;
|
||||
true -> { ok, [{ Atom, Line }|Tokens] };
|
||||
block -> { ok, [{ block_identifier, Line, Atom }|Tokens] };
|
||||
unary_op -> { ok, [{ unary_op, Line, Atom }|Tokens] };
|
||||
Kind -> { ok, add_token_with_nl({ Kind, Line, Atom }, Tokens) }
|
||||
end;
|
||||
|
||||
check_keyword(_, _, _, _) -> nomatch.
|
||||
@@ -836,19 +914,19 @@ keyword('end') -> true;
|
||||
keyword('true') -> true;
|
||||
keyword('false') -> true;
|
||||
keyword('nil') -> true;
|
||||
keyword('not') -> true;
|
||||
|
||||
% Special handling for do
|
||||
keyword('do') -> do;
|
||||
|
||||
% Bin operator keywords
|
||||
keyword('and') -> op;
|
||||
keyword('or') -> op;
|
||||
keyword('xor') -> op;
|
||||
keyword('when') -> op;
|
||||
keyword('in') -> op;
|
||||
keyword('inlist') -> op;
|
||||
keyword('inbits') -> op;
|
||||
% Operators keywords
|
||||
keyword('not') -> unary_op;
|
||||
keyword('and') -> and_op;
|
||||
keyword('or') -> or_op;
|
||||
keyword('xor') -> or_op;
|
||||
keyword('when') -> when_op;
|
||||
keyword('in') -> in_op;
|
||||
keyword('inlist') -> inc_op;
|
||||
keyword('inbits') -> inc_op;
|
||||
|
||||
% Block keywords
|
||||
keyword('after') -> block;
|
||||
|
||||
@@ -0,0 +1,141 @@
|
||||
%% Module responsible for tracking invocations of locals,
|
||||
%% imports and requires.
|
||||
-module(elixir_tracker).
|
||||
-export([
|
||||
setup/1, cleanup/1,
|
||||
record_local/2, record_local/3,
|
||||
record_import/4, record_remote/4,
|
||||
record_warn/4, record_definition/3,
|
||||
record_defaults/4,
|
||||
ensure_no_import_conflict/4, ensure_all_imports_used/3,
|
||||
warn_unused_local/3, format_error/1
|
||||
]).
|
||||
-include("elixir.hrl").
|
||||
|
||||
-define(attr, '__dispatch_tracker').
|
||||
-define(tracker, 'Elixir.Module.DispatchTracker').
|
||||
|
||||
%% RECORD
|
||||
|
||||
setup(Module) ->
|
||||
case code:ensure_loaded(?tracker) of
|
||||
{ module, _ } -> ets:insert(Module, { ?attr, ?tracker:start_link() });
|
||||
{ error, _ } -> ok
|
||||
end.
|
||||
|
||||
cleanup(Module) ->
|
||||
if_tracker(Module, fun(Pid) -> unlink(Pid), ?tracker:stop(Pid) end).
|
||||
|
||||
record_local(Tuple, Module) when is_atom(Module) ->
|
||||
if_tracker(Module, fun(Pid) ->
|
||||
?tracker:add_local(Pid, Tuple),
|
||||
true
|
||||
end).
|
||||
record_local(Tuple, _Module, Function)
|
||||
when Function == nil; Function == Tuple -> false;
|
||||
record_local(Tuple, Module, Function) ->
|
||||
if_tracker(Module, fun(Pid) ->
|
||||
?tracker:add_local(Pid, Function, Tuple),
|
||||
true
|
||||
end).
|
||||
|
||||
record_warn(Ref, Warn, Line, Module) ->
|
||||
if_tracker(Module, fun(Pid) ->
|
||||
?tracker:add_warnable(Pid, Ref, Warn, Line),
|
||||
true
|
||||
end).
|
||||
|
||||
record_import(_Tuple, Receiver, Module, _Function)
|
||||
when Module == nil; Module == Receiver -> false;
|
||||
record_import(Tuple, Receiver, Module, Function) ->
|
||||
try
|
||||
Pid = ets:lookup_element(Module, ?attr, 2),
|
||||
?tracker:add_import(Pid, Function, Receiver, Tuple)
|
||||
catch
|
||||
error:badarg -> false
|
||||
end.
|
||||
|
||||
record_remote(_Tuple, Receiver, Module, _Function)
|
||||
when Module == nil; Module == Receiver -> false;
|
||||
record_remote(Tuple, Receiver, Module, Function) ->
|
||||
try
|
||||
Pid = ets:lookup_element(Module, ?attr, 2),
|
||||
?tracker:add_remote(Pid, Function, Receiver, Tuple)
|
||||
catch
|
||||
error:badarg -> false
|
||||
end.
|
||||
|
||||
record_definition(Tuple, Kind, Module) ->
|
||||
if_tracker(Module, fun(Pid) ->
|
||||
?tracker:add_definition(Pid, Kind, Tuple),
|
||||
true
|
||||
end).
|
||||
|
||||
record_defaults(_Tuple, _Kind, _Module, 0) ->
|
||||
true;
|
||||
record_defaults(Tuple, Kind, Module, Defaults) ->
|
||||
if_tracker(Module, fun(Pid) ->
|
||||
?tracker:add_defaults(Pid, Kind, Tuple, Defaults),
|
||||
true
|
||||
end).
|
||||
|
||||
%% HELPERS
|
||||
|
||||
if_tracker(Module, Callback) ->
|
||||
case ets:lookup(Module, ?attr) of
|
||||
[{ ?attr, Pid }] -> Callback(Pid);
|
||||
_ -> false
|
||||
end.
|
||||
|
||||
%% ERROR HANDLING
|
||||
|
||||
ensure_no_import_conflict(Meta, File, Module, AllDefined) ->
|
||||
if_tracker(Module, fun(Pid) ->
|
||||
[ begin
|
||||
elixir_errors:form_error(Meta, File, ?MODULE, { import_conflict, Error })
|
||||
end || Error <- ?tracker:collect_imports_conflicts(Pid, AllDefined) ]
|
||||
end),
|
||||
ok.
|
||||
|
||||
ensure_all_imports_used(_Line, File, Module) ->
|
||||
if_tracker(Module, fun(Pid) ->
|
||||
[ begin
|
||||
elixir_errors:handle_file_warning(File, { L, ?MODULE, { unused_import, M } })
|
||||
end || { M, L } <- ?tracker:collect_unused_imports(Pid)]
|
||||
end),
|
||||
ok.
|
||||
|
||||
warn_unused_local(File, Module, Private) ->
|
||||
if_tracker(Module, fun(Pid) ->
|
||||
Args = [ { Fun, Kind, Defaults } ||
|
||||
{ Fun, Kind, _Line, true, Defaults } <- Private],
|
||||
|
||||
Unused = ?tracker:collect_unused_locals(Pid, Args),
|
||||
|
||||
[ begin
|
||||
{ _, _, Line, _, _ } = lists:keyfind(element(2, Error), 1, Private),
|
||||
elixir_errors:handle_file_warning(File, { Line, ?MODULE, Error })
|
||||
end || Error <- Unused ]
|
||||
end).
|
||||
|
||||
format_error({import_conflict,{Receivers, Name, Arity}}) ->
|
||||
io_lib:format("imported ~ts.~ts/~B conflicts with local function",
|
||||
[elixir_errors:inspect(hd(Receivers)), Name, Arity]);
|
||||
|
||||
format_error({ unused_import, Module }) ->
|
||||
io_lib:format("unused import ~ts", [elixir_errors:inspect(Module)]);
|
||||
|
||||
format_error({unused_args,{Name, Arity}}) ->
|
||||
io_lib:format("default arguments in ~ts/~B are never used", [Name, Arity]);
|
||||
|
||||
format_error({unused_args,{Name, Arity},1}) ->
|
||||
io_lib:format("the first default argument in ~ts/~B is never used", [Name, Arity]);
|
||||
|
||||
format_error({unused_args,{Name, Arity},Count}) ->
|
||||
io_lib:format("the first ~B default arguments in ~ts/~B are never used", [Count, Name, Arity]);
|
||||
|
||||
format_error({unused_def,{Name, Arity},defp}) ->
|
||||
io_lib:format("function ~ts/~B is unused", [Name, Arity]);
|
||||
|
||||
format_error({unused_def,{Name, Arity},defmacrop}) ->
|
||||
io_lib:format("macro ~ts/~B is unused", [Name, Arity]).
|
||||
@@ -1,38 +0,0 @@
|
||||
-module(elixir_transform).
|
||||
-export([parse_transform/2]).
|
||||
|
||||
parse_transform(Forms, _) ->
|
||||
do_transform(Forms).
|
||||
|
||||
do_transform({ atom, Line, Atom }) ->
|
||||
case atom_to_list(Atom) of
|
||||
"Elixir." ++ _ = List ->
|
||||
Module = [case T of
|
||||
$. -> $-;
|
||||
_ -> T
|
||||
end || T <- List],
|
||||
{ atom, Line, list_to_atom(Module) };
|
||||
_ ->
|
||||
{ atom, Line, Atom }
|
||||
end;
|
||||
|
||||
do_transform({ Name, Item1 }) ->
|
||||
{ Name, do_transform(Item1) };
|
||||
|
||||
do_transform({ Name, Item1, Item2 }) ->
|
||||
{ Name, do_transform(Item1), do_transform(Item2) };
|
||||
|
||||
do_transform({ Name, Item1, Item2, Item3 }) ->
|
||||
{ Name, do_transform(Item1), do_transform(Item2), do_transform(Item3) };
|
||||
|
||||
do_transform({ Name, Item1, Item2, Item3, Item4 }) ->
|
||||
{ Name, do_transform(Item1), do_transform(Item2), do_transform(Item3), do_transform(Item4) };
|
||||
|
||||
do_transform({ Name, Item1, Item2, Item3, Item4, Item5 }) ->
|
||||
{ Name, do_transform(Item1), do_transform(Item2), do_transform(Item3), do_transform(Item4), do_transform(Item5) };
|
||||
|
||||
do_transform(List) when is_list(List) ->
|
||||
[do_transform(X) || X <- List];
|
||||
|
||||
do_transform(Other) when not is_tuple(Other) ->
|
||||
Other.
|
||||
@@ -9,7 +9,6 @@
|
||||
assert_function_scope/3, assert_module_scope/3, assert_no_guard_scope/3,
|
||||
assert_no_match_or_guard_scope/3]).
|
||||
-include("elixir.hrl").
|
||||
-compile({parse_transform, elixir_transform}).
|
||||
|
||||
forms(String, StartLine, File, Opts) ->
|
||||
try elixir_tokenizer:tokenize(String, StartLine, [{ file, File }|Opts]) of
|
||||
@@ -63,9 +62,6 @@ translate_each({ '__block__', Meta, Args }, S) when is_list(Args) ->
|
||||
{ TArgs, NS } = translate(Args, S),
|
||||
{ { block, ?line(Meta), TArgs }, NS };
|
||||
|
||||
translate_each({ '__scope__', _Meta, [[{erl,true}],[{do,Expr}]] }, S) ->
|
||||
{ Expr, S };
|
||||
|
||||
translate_each({ '__scope__', _Meta, [[{file,File}],[{do,Expr}]] }, S) ->
|
||||
Old = S#elixir_scope.file,
|
||||
{ TExpr, TS } = translate_each(Expr, S#elixir_scope{file=File}),
|
||||
@@ -221,7 +217,7 @@ translate_each({ quote, Meta, [KV, Do] }, S) when is_list(Do) ->
|
||||
false -> syntax_error(Meta, S#elixir_scope.file, "invalid args for quote")
|
||||
end,
|
||||
|
||||
ValidOpts = [hygiene, context, var_context, location, line, file, unquote],
|
||||
ValidOpts = [hygiene, context, var_context, location, line, file, unquote, binding],
|
||||
{ TKV, ST } = translate_opts(Meta, quote, ValidOpts, KV, S),
|
||||
|
||||
Hygiene = case lists:keyfind(hygiene, 1, TKV) of
|
||||
@@ -231,21 +227,15 @@ translate_each({ quote, Meta, [KV, Do] }, S) when is_list(Do) ->
|
||||
[]
|
||||
end,
|
||||
|
||||
Context = case lists:keyfind(var_context, 1, TKV) of
|
||||
{ var_context, VarContext } ->
|
||||
elixir_errors:deprecation(Meta, S#elixir_scope.file, "var_context in quote is deprecated, please use context instead"),
|
||||
VarContext;
|
||||
Context = case lists:keyfind(context, 1, TKV) of
|
||||
{ context, Atom } when is_atom(Atom) ->
|
||||
Atom;
|
||||
{ context, _ } ->
|
||||
syntax_error(Meta, S#elixir_scope.file, "invalid :context for quote, expected an atom or an alias");
|
||||
false ->
|
||||
case lists:keyfind(context, 1, TKV) of
|
||||
{ context, Atom } when is_atom(Atom) ->
|
||||
Atom;
|
||||
{ context, _ } ->
|
||||
syntax_error(Meta, S#elixir_scope.file, "invalid :context for quote, expected an atom or an alias");
|
||||
false ->
|
||||
case S#elixir_scope.module of
|
||||
nil -> 'Elixir';
|
||||
Mod -> Mod
|
||||
end
|
||||
case S#elixir_scope.module of
|
||||
nil -> 'Elixir';
|
||||
Mod -> Mod
|
||||
end
|
||||
end,
|
||||
|
||||
@@ -268,29 +258,34 @@ translate_each({ quote, Meta, [KV, Do] }, S) when is_list(Do) ->
|
||||
false -> DefaultFile
|
||||
end,
|
||||
|
||||
TFile = case File of
|
||||
Scope = case File of
|
||||
keep -> S#elixir_scope.file;
|
||||
_ -> File
|
||||
end,
|
||||
|
||||
WExprs = if
|
||||
is_binary(TFile) ->
|
||||
{ '__scope__', Meta, [[{file,TFile}],[{do,Exprs}]] };
|
||||
QExprs = if
|
||||
is_binary(Scope) ->
|
||||
{ '__scope__', Meta, [[{file,Scope}],[{do,Exprs}]] };
|
||||
File == nil ->
|
||||
Exprs;
|
||||
true ->
|
||||
syntax_error(Meta, S#elixir_scope.file, "invalid args for quote, expected :file to be a binary")
|
||||
end,
|
||||
|
||||
{ Binding, DefaultUnquote } = case lists:keyfind(binding, 1, TKV) of
|
||||
{ binding, B } when is_list(B) -> { B, false };
|
||||
false -> { nil, true }
|
||||
end,
|
||||
|
||||
Unquote = case lists:keyfind(unquote, 1, TKV) of
|
||||
{ unquote, false } -> false;
|
||||
_ -> true
|
||||
{ unquote, Bool } when is_boolean(Bool) -> Bool;
|
||||
false -> DefaultUnquote
|
||||
end,
|
||||
|
||||
Q = #elixir_quote{vars_hygiene=Vars, line=Line, unquote=Unquote,
|
||||
aliases_hygiene=Aliases, imports_hygiene=Imports, context=Context},
|
||||
|
||||
{ TExprs, _TQ, TS } = elixir_quote:erl_quote(WExprs, Q, ST),
|
||||
{ TExprs, _TQ, TS } = elixir_quote:erl_quote(QExprs, Binding, Q, ST),
|
||||
{ TExprs, TS };
|
||||
|
||||
translate_each({ quote, Meta, [_, _] }, S) ->
|
||||
@@ -327,7 +322,7 @@ translate_each({ Kind, Meta, Args }, S) when is_list(Args), (Kind == lc) orelse
|
||||
|
||||
%% Super (exceptionally supports partial application)
|
||||
|
||||
translate_each({ super, Meta, Args } = Original, S) ->
|
||||
translate_each({ super, Meta, Args } = Original, S) when is_list(Args) ->
|
||||
case elixir_partials:handle(Original, S) of
|
||||
error ->
|
||||
assert_no_match_or_guard_scope(Meta, super, S),
|
||||
@@ -338,8 +333,6 @@ translate_each({ super, Meta, Args } = Original, S) ->
|
||||
{ _, Arity } = Function,
|
||||
|
||||
{ TArgs, TS } = if
|
||||
is_atom(Args) ->
|
||||
elixir_def_overridable:retrieve_args(Meta, Arity, S);
|
||||
length(Args) == Arity ->
|
||||
translate_args(Args, S);
|
||||
true ->
|
||||
@@ -360,7 +353,7 @@ translate_each({ 'super?', Meta, [] }, S) ->
|
||||
%% Variables
|
||||
|
||||
translate_each({ '^', Meta, [ { Name, _, Kind } ] }, #elixir_scope{context=match} = S) when is_atom(Name), is_atom(Kind) ->
|
||||
case orddict:find({ Name, Kind }, S#elixir_scope.vars) of
|
||||
case orddict:find({ Name, Kind }, S#elixir_scope.backup_vars) of
|
||||
{ ok, Value } ->
|
||||
{ { var, Meta, Value }, S };
|
||||
error ->
|
||||
@@ -373,7 +366,7 @@ translate_each({ '^', Meta, [ { Name, _, Kind } ] }, S) when is_atom(Name), is_a
|
||||
|
||||
translate_each({ '^', Meta, [ Expr ] }, S) ->
|
||||
syntax_error(Meta, S#elixir_scope.file,
|
||||
"the unary operator ^ can only be used with variables, invalid expression ^~ts", ['Elixir.Macro':to_binary(Expr)]);
|
||||
"the unary operator ^ can only be used with variables, invalid expression ^~ts", ['Elixir.Macro':to_string(Expr)]);
|
||||
|
||||
translate_each({ Name, Meta, Kind }, S) when is_atom(Name), is_atom(Kind) ->
|
||||
elixir_scope:translate_var(Meta, Name, Kind, S, fun() ->
|
||||
@@ -470,7 +463,7 @@ translate_each({ { '.', _, [Expr] }, Meta, Args } = Original, S) ->
|
||||
|
||||
translate_each({ Invalid, Meta, _Args }, S) ->
|
||||
syntax_error(Meta, S#elixir_scope.file, "unexpected parenthesis after ~ts",
|
||||
['Elixir.Macro':to_binary(Invalid)]);
|
||||
['Elixir.Macro':to_string(Invalid)]);
|
||||
|
||||
%% Literals
|
||||
|
||||
@@ -482,10 +475,21 @@ translate_each(Literal, S) ->
|
||||
%% Opts
|
||||
|
||||
translate_opts(Meta, Kind, Allowed, Opts, S) ->
|
||||
{ TOpts, TS } = translate_each(Opts, S),
|
||||
FOpts = elixir_tree_helpers:erl_to_elixir(TOpts),
|
||||
validate_opts(Meta, Kind, Allowed, FOpts, TS),
|
||||
{ FOpts, TS }.
|
||||
{ Expanded, TS } = case literal_opts(Opts) orelse skip_expansion(S#elixir_scope.module) of
|
||||
true -> { Opts, S };
|
||||
false -> 'Elixir.Macro':expand_all(Opts, elixir_scope:to_ex_env({ ?line(Meta), S }), S)
|
||||
end,
|
||||
validate_opts(Meta, Kind, Allowed, Expanded, TS),
|
||||
{ Expanded, TS }.
|
||||
|
||||
skip_expansion('Elixir.Kernel') -> true;
|
||||
skip_expansion('Elixir.Kernel.Typespec') -> true;
|
||||
skip_expansion(_) -> false.
|
||||
|
||||
literal_opts({ X, Y }) -> literal_opts(X) andalso literal_opts(Y);
|
||||
literal_opts(Opts) when is_list(Opts) -> lists:all(fun literal_opts/1, Opts);
|
||||
literal_opts(Opts) when is_atom(Opts); is_number(Opts); is_bitstring(Opts) -> true;
|
||||
literal_opts(_) -> false.
|
||||
|
||||
validate_opts(Meta, Kind, Allowed, Opts, S) when is_list(Opts) ->
|
||||
[begin
|
||||
@@ -534,7 +538,7 @@ translate_alias(Meta, IncludeByDefault, Old, TKV, S) ->
|
||||
"invalid args for alias, expected an atom or alias in option :as")
|
||||
end,
|
||||
|
||||
case (New == Old) orelse (length(string:tokens(atom_to_list(New), "-")) == 2) of
|
||||
case (New == Old) orelse (length(string:tokens(atom_to_list(New), ".")) == 2) of
|
||||
true -> ok;
|
||||
false -> syntax_error(Meta, S#elixir_scope.file,
|
||||
"invalid args for alias, cannot create nested alias ~s", [elixir_errors:inspect(New)])
|
||||
@@ -559,11 +563,9 @@ no_alias_expansion(Other) ->
|
||||
%% Function
|
||||
|
||||
translate_fn(Meta, Clauses, S) ->
|
||||
Line = ?line(Meta),
|
||||
|
||||
Transformer = fun({ ArgsWithGuards, Expr }, Acc) ->
|
||||
{ Args, Guards } = elixir_clauses:extract_last_guards(ArgsWithGuards),
|
||||
elixir_clauses:assigns_block(Line, fun elixir_translator:translate/2, Args, [Expr], Guards, umergec(S, Acc))
|
||||
Transformer = fun({ ArgsWithGuards, CMeta, Expr }, Acc) ->
|
||||
{ Args, Guards } = elixir_clauses:extract_splat_guards(ArgsWithGuards),
|
||||
elixir_clauses:assigns_block(?line(CMeta), fun elixir_translator:translate/2, Args, [Expr], Guards, umergec(S, Acc))
|
||||
end,
|
||||
|
||||
{ TClauses, NS } = lists:mapfoldl(Transformer, S, Clauses),
|
||||
@@ -571,15 +573,15 @@ translate_fn(Meta, Clauses, S) ->
|
||||
|
||||
case length(lists:usort(Arities)) of
|
||||
1 ->
|
||||
{ { 'fun', Line, { clauses, TClauses } }, umergec(S, NS) };
|
||||
{ { 'fun', ?line(Meta), { clauses, TClauses } }, umergec(S, NS) };
|
||||
_ ->
|
||||
syntax_error(Meta, S#elixir_scope.file, "cannot mix clauses with different arities in function definition")
|
||||
end.
|
||||
|
||||
%% Locals
|
||||
|
||||
translate_local(Meta, Name, Args, #elixir_scope{local=nil,module=Module} = S) ->
|
||||
elixir_import:record({ Name, length(Args) }, Module, Module),
|
||||
translate_local(Meta, Name, Args, #elixir_scope{local=nil,module=Module,function=Function} = S) ->
|
||||
elixir_tracker:record_local({ Name, length(Args) }, Module, Function),
|
||||
Line = ?line(Meta),
|
||||
{ TArgs, NS } = translate_args(Args, S),
|
||||
{ { call, Line, { atom, Line, Name }, TArgs }, NS };
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
%% Convenience functions used throughout elixir source code
|
||||
%% to generate erlang abstract format for basic structures
|
||||
%% as lists, condition clauses, etc.
|
||||
|
||||
-module(elixir_tree_helpers).
|
||||
-compile({parse_transform, elixir_transform}).
|
||||
-export([elixir_to_erl/1, erl_to_elixir/1, split_last/1,
|
||||
-export([elixir_to_erl/1, split_last/1,
|
||||
cons_to_list/1, list_to_cons/2, list_to_cons/3,
|
||||
convert_to_boolean/5, returns_boolean/1, get_line/1]).
|
||||
-include("elixir.hrl").
|
||||
@@ -56,22 +54,6 @@ elixir_to_erl_cons([H|T], Acc) ->
|
||||
|
||||
elixir_to_erl_cons([], Acc) -> Acc.
|
||||
|
||||
erl_to_elixir({ tuple, _, Args }) ->
|
||||
list_to_tuple([erl_to_elixir(X) || X <- Args]);
|
||||
|
||||
erl_to_elixir({ Kind, _, Value }) when Kind == atom; Kind == integer; Kind == float ->
|
||||
Value;
|
||||
|
||||
erl_to_elixir({ bin, _, [{ bin_element, 0, { string, 0, String }, default, default }] }) ->
|
||||
list_to_binary(String);
|
||||
|
||||
erl_to_elixir({ nil, _ }) -> [];
|
||||
|
||||
erl_to_elixir({ cons, _, Left, Right }) -> [erl_to_elixir(Left)|erl_to_elixir(Right)];
|
||||
|
||||
erl_to_elixir(Other) ->
|
||||
{ '__scope__', [], [[{erl,true}],[{do,Other}]] }.
|
||||
|
||||
%% Others
|
||||
|
||||
returns_boolean({ op, _, Op, _ }) when Op == 'not' -> true;
|
||||
@@ -96,7 +78,7 @@ returns_boolean({ call, _, { remote, _, { atom, _, erlang }, { atom, _, Fun } },
|
||||
returns_boolean({ call, _, { remote, _, { atom, _, erlang }, { atom, _, Fun } }, [_,_,_] }) when
|
||||
Fun == function_exported -> true;
|
||||
|
||||
returns_boolean({ atom, _, Bool }) when Bool == true; Bool == false -> true;
|
||||
returns_boolean({ atom, _, Bool }) when is_boolean(Bool) -> true;
|
||||
|
||||
returns_boolean({ 'case', _, _, Clauses }) ->
|
||||
lists:all(fun
|
||||
|
||||
@@ -2,16 +2,15 @@
|
||||
-export([clauses/3, format_error/1]).
|
||||
-import(elixir_scope, [umergec/2]).
|
||||
-include("elixir.hrl").
|
||||
-compile({parse_transform, elixir_transform}).
|
||||
|
||||
clauses(Meta, Clauses, S) ->
|
||||
Catch = elixir_clauses:get_pairs(Meta, 'catch', Clauses, S),
|
||||
Rescue = elixir_clauses:get_pairs(Meta, rescue, Clauses, S),
|
||||
Transformer = fun(X, Acc) -> each_clause(Meta, X, umergec(S, Acc)) end,
|
||||
Transformer = fun(X, Acc) -> each_clause(X, umergec(S, Acc)) end,
|
||||
lists:mapfoldl(Transformer, S, Rescue ++ Catch).
|
||||
|
||||
each_clause(Meta, { 'catch', Raw, Expr }, S) ->
|
||||
{ Args, Guards } = elixir_clauses:extract_last_guards(Raw),
|
||||
each_clause({ 'catch', Meta, Raw, Expr }, S) ->
|
||||
{ Args, Guards } = elixir_clauses:extract_splat_guards(Raw),
|
||||
|
||||
Final = case Args of
|
||||
[X] -> [throw, X, { '_', Meta, nil }];
|
||||
@@ -24,19 +23,19 @@ each_clause(Meta, { 'catch', Raw, Expr }, S) ->
|
||||
Condition = { '{}', Meta, Final },
|
||||
elixir_clauses:assigns_block(?line(Meta), fun elixir_translator:translate_each/2, Condition, [Expr], Guards, S);
|
||||
|
||||
each_clause(Meta, { rescue, [Condition|T], Expr }, S) ->
|
||||
each_clause({ rescue, Meta, [Condition], Expr }, S) ->
|
||||
case normalize_rescue(Meta, Condition, S) of
|
||||
{ Left, Right } ->
|
||||
case Left of
|
||||
{ '_', _, _ } ->
|
||||
{ ClauseVar, CS } = elixir_scope:build_ex_var(?line(Meta), S),
|
||||
{ Clause, _ } = rescue_guards(Meta, ClauseVar, Right, S),
|
||||
each_clause(Meta, { 'catch', [error, Clause|T], Expr }, CS);
|
||||
each_clause({ 'catch', Meta, Clause, Expr }, CS);
|
||||
_ ->
|
||||
{ Clause, Safe } = rescue_guards(Meta, Left, Right, S),
|
||||
case Safe of
|
||||
true ->
|
||||
each_clause(Meta, { 'catch', [error, Clause|T], Expr }, S);
|
||||
each_clause({ 'catch', Meta, Clause, Expr }, S);
|
||||
false ->
|
||||
{ ClauseVar, CS } = elixir_scope:build_ex_var(?line(Meta), S),
|
||||
{ FinalClause, _ } = rescue_guards(Meta, ClauseVar, Right, S),
|
||||
@@ -45,17 +44,17 @@ each_clause(Meta, { rescue, [Condition|T], Expr }, S) ->
|
||||
{ { '.', Meta, ['Elixir.Exception', normalize] }, Meta, [ClauseVar] }
|
||||
] },
|
||||
FinalExpr = prepend_to_block(Meta, Match, Expr),
|
||||
each_clause(Meta, { 'catch', [error, FinalClause|T], FinalExpr }, CS)
|
||||
each_clause({ 'catch', Meta, FinalClause, FinalExpr }, CS)
|
||||
end
|
||||
end;
|
||||
_ ->
|
||||
each_clause(Meta, { 'catch', [error, Condition|T], Expr }, S)
|
||||
each_clause({ 'catch', Meta, [error, Condition], Expr }, S)
|
||||
end;
|
||||
|
||||
each_clause(Meta, {rescue,_,_}, S) ->
|
||||
each_clause({rescue,Meta,_,_}, S) ->
|
||||
elixir_errors:syntax_error(Meta, S#elixir_scope.file, "too many arguments given for rescue");
|
||||
|
||||
each_clause(Meta, {Key,_,_}, S) ->
|
||||
each_clause({Key,Meta,_,_}, S) ->
|
||||
elixir_errors:syntax_error(Meta, S#elixir_scope.file, "invalid key ~ts in try", [Key]).
|
||||
|
||||
%% Helpers
|
||||
@@ -80,11 +79,11 @@ normalize_rescue(_, { in, Meta, [Left, Right] }, S) ->
|
||||
_ when is_list(Right) ->
|
||||
is_valid_rescue_list(Right, S) andalso { Left, Right };
|
||||
_ ->
|
||||
Expanded = 'Elixir.Macro':expand(Right, elixir_scope:to_ex_env({ ?line(Meta), S })),
|
||||
case is_valid_rescue_list(Expanded, S) of
|
||||
{ Expanded, ES } = 'Elixir.Macro':expand_all(Right, elixir_scope:to_ex_env({ ?line(Meta), S }), S),
|
||||
case is_valid_rescue_list(Expanded, ES) of
|
||||
true -> { Left, Expanded };
|
||||
false ->
|
||||
elixir_errors:syntax_error(Meta, S#elixir_scope.file, "invalid use of operator \"in\" in rescue inside try")
|
||||
elixir_errors:syntax_error(Meta, ES#elixir_scope.file, "invalid use of operator \"in\" in rescue inside try")
|
||||
end
|
||||
end;
|
||||
|
||||
@@ -98,7 +97,7 @@ normalize_rescue(Meta, Condition, S) ->
|
||||
end.
|
||||
|
||||
%% Convert rescue clauses into guards.
|
||||
rescue_guards(_, Var, nil, _) -> { Var, false };
|
||||
rescue_guards(_, Var, nil, _) -> { [error, Var], false };
|
||||
|
||||
rescue_guards(Meta, Var, Guards, S) ->
|
||||
{ RawElixir, RawErlang } = rescue_each_var(Meta, Var, Guards),
|
||||
@@ -115,7 +114,7 @@ rescue_guards(Meta, Var, Guards, S) ->
|
||||
[join(Meta, 'and', [IsTuple, IsException, OrElse])|Erlang]
|
||||
end,
|
||||
{
|
||||
{ 'when', Meta, [Var, reverse_join(Meta, 'when', Final)] },
|
||||
[{ 'when', Meta, [error, Var, reverse_join(Meta, 'when', Final)] }],
|
||||
Safe
|
||||
}.
|
||||
|
||||
@@ -239,8 +238,8 @@ erlang_rescue_guard_for(Meta, Var, 'Elixir.ErlangError') ->
|
||||
%% Helpers
|
||||
|
||||
format_error({ rescue_no_match, Var, Alias }) ->
|
||||
VarBinary = 'Elixir.Macro':to_binary(Var),
|
||||
AliasBinary = 'Elixir.Macro':to_binary(Alias),
|
||||
VarBinary = 'Elixir.Macro':to_string(Var),
|
||||
AliasBinary = 'Elixir.Macro':to_string(Alias),
|
||||
Message = "rescue clause (~ts = ~ts) can never match, maybe you meant to write: ~ts in [~ts] ?",
|
||||
io_lib:format(Message, [AliasBinary, VarBinary, VarBinary, AliasBinary]).
|
||||
|
||||
@@ -270,4 +269,4 @@ prepend_to_block(_Meta, Expr, { '__block__', Meta, Args }) ->
|
||||
{ '__block__', Meta, [Expr|Args] };
|
||||
|
||||
prepend_to_block(Meta, Expr, Args) ->
|
||||
{ '__block__', Meta, [Expr, Args] }.
|
||||
{ '__block__', Meta, [Expr, Args] }.
|
||||
|
||||
@@ -13,21 +13,16 @@ defmodule AccessTest do
|
||||
assert nil[:foo] == nil
|
||||
end
|
||||
|
||||
test :function do
|
||||
function = fn x -> x == :foo end
|
||||
assert function[:foo] == true
|
||||
assert function[:bar] == false
|
||||
end
|
||||
|
||||
# Test nil at compilation time does not fail
|
||||
# and that @config[:foo] has proper precedence.
|
||||
@config nil
|
||||
nil = @config[:foo]
|
||||
|
||||
@config [foo: :bar]
|
||||
:bar = @config[:foo]
|
||||
|
||||
@mod :lists
|
||||
[1,2,3] = @mod.flatten([1,[2],3])
|
||||
[1, 2, 3] = @mod.flatten([1, [2], 3])
|
||||
|
||||
test :atom do
|
||||
exception = assert_raise RuntimeError, fn ->
|
||||
|
||||
@@ -16,8 +16,8 @@ defmodule BehaviourTest do
|
||||
test :docs do
|
||||
docs = Enum.sort(Sample.__behaviour__(:docs))
|
||||
assert docs == [
|
||||
{{:bar,2},13,"Bar"},
|
||||
{{:foo,2},10,"Foo"}
|
||||
{{:bar, 2}, 13, "Bar"},
|
||||
{{:foo, 2}, 10, "Foo"}
|
||||
]
|
||||
end
|
||||
|
||||
|
||||
@@ -23,7 +23,7 @@ defmodule Binary.Chars.AtomTest do
|
||||
end
|
||||
|
||||
test :alias_atom do
|
||||
assert to_binary(Foo.Bar) == "Elixir-Foo-Bar"
|
||||
assert to_binary(Foo.Bar) == "Elixir.Foo.Bar"
|
||||
end
|
||||
end
|
||||
|
||||
@@ -50,11 +50,9 @@ defmodule Binary.Chars.NumberTest do
|
||||
assert to_binary(100) == "100"
|
||||
end
|
||||
|
||||
unless :erlang.system_info(:otp_release) < 'R16' do
|
||||
test :float do
|
||||
assert to_binary(1.0) == "1.0"
|
||||
assert to_binary(1.0e10) == "10000000000.0"
|
||||
end
|
||||
test :float do
|
||||
assert to_binary(1.0) == "1.0"
|
||||
assert to_binary(1.0e10) == "10000000000.0"
|
||||
end
|
||||
end
|
||||
|
||||
@@ -62,7 +60,7 @@ defmodule Binary.Chars.ListTest do
|
||||
use ExUnit.Case, async: true
|
||||
|
||||
test :basic do
|
||||
assert to_binary([ 1, "b", 3 ]) == <<1,98,3>>
|
||||
assert to_binary([ 1, "b", 3 ]) == <<1, 98, 3>>
|
||||
end
|
||||
|
||||
test :printable do
|
||||
@@ -73,3 +71,89 @@ defmodule Binary.Chars.ListTest do
|
||||
assert to_binary([]) == ""
|
||||
end
|
||||
end
|
||||
|
||||
defmodule Binary.Chars.ErrorsTest do
|
||||
use ExUnit.Case, async: true
|
||||
|
||||
test :list do
|
||||
assert to_binary([0, 1, 2, 3, 255]) == <<0, 1, 2, 3, 255>>
|
||||
assert to_binary([0, [1, "hello"], 2, [["bye"]]]) == <<0, 1, 104, 101, 108, 108, 111, 2, 98, 121, 101>>
|
||||
|
||||
assert_raise Protocol.UndefinedError, "protocol Binary.Chars.List not implemented for [256]. Only iolists are supported", fn ->
|
||||
to_binary([256])
|
||||
end
|
||||
assert_raise Protocol.UndefinedError, "protocol Binary.Chars.List not implemented for [1001,10001,100001]. Only iolists are supported", fn ->
|
||||
to_binary([1001, 10001, 100001])
|
||||
end
|
||||
assert_raise Protocol.UndefinedError, "protocol Binary.Chars.List not implemented for [:atom,13,\"hello\"]. Only iolists are supported", fn ->
|
||||
to_binary([:atom, 13, "hello"])
|
||||
end
|
||||
end
|
||||
|
||||
test :tuple do
|
||||
assert_raise Protocol.UndefinedError, "protocol Binary.Chars not implemented for {1,2,3}", fn ->
|
||||
to_binary({1, 2, 3})
|
||||
end
|
||||
end
|
||||
|
||||
test :nested_in_tuple do
|
||||
assert_raise Protocol.UndefinedError, "protocol Binary.Chars not implemented for {1,[2],:atom}", fn ->
|
||||
to_binary({1, [2], :atom})
|
||||
end
|
||||
end
|
||||
|
||||
test :nested_tuple do
|
||||
assert_raise Protocol.UndefinedError, "protocol Binary.Chars.List not implemented for [1,{[2],:atom}]. Only iolists are supported", fn ->
|
||||
to_binary([1, {[2], :atom}])
|
||||
end
|
||||
end
|
||||
|
||||
test :nested_pid do
|
||||
assert_raise Protocol.UndefinedError, %r"^protocol Binary\.Chars\.List not implemented for \[#PID<.+?>,:atom\]\. Only iolists are supported$", fn ->
|
||||
to_binary([self(), :atom])
|
||||
end
|
||||
end
|
||||
|
||||
test :nested_fun do
|
||||
assert_raise Protocol.UndefinedError, %r"^protocol Binary\.Chars\.List not implemented for \[:atom,#Function<.+?>\]\. Only iolists are supported$", fn ->
|
||||
to_binary([:atom, fn -> end])
|
||||
end
|
||||
end
|
||||
|
||||
test :record do
|
||||
# FIXME: is this expected error message?
|
||||
assert_raise UndefinedFunctionError, "undefined function: Binary.Chars.ArgumentError.to_binary/1", fn ->
|
||||
to_binary(ArgumentError[])
|
||||
end
|
||||
|
||||
# FIXME: is this expected error message?
|
||||
assert_raise UndefinedFunctionError, "undefined function: Binary.Chars.File.Stat.to_binary/1", fn ->
|
||||
to_binary(File.Stat[])
|
||||
end
|
||||
end
|
||||
|
||||
test :pid do
|
||||
assert_raise Protocol.UndefinedError, %r"^protocol Binary\.Chars not implemented for #PID<.+?>$", fn ->
|
||||
to_binary(self())
|
||||
end
|
||||
end
|
||||
|
||||
test :ref do
|
||||
assert_raise Protocol.UndefinedError, %r"^protocol Binary\.Chars not implemented for #Reference<.+?>$", fn ->
|
||||
to_binary(make_ref()) == ""
|
||||
end
|
||||
end
|
||||
|
||||
test :function do
|
||||
assert_raise Protocol.UndefinedError, %r"^protocol Binary\.Chars not implemented for #Function<.+?>$", fn ->
|
||||
to_binary(fn -> end)
|
||||
end
|
||||
end
|
||||
|
||||
test :port do
|
||||
[port|_] = Port.list
|
||||
assert_raise Protocol.UndefinedError, %r"^protocol Binary\.Chars not implemented for #Port<.+?>$", fn ->
|
||||
to_binary(port)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -86,12 +86,10 @@ defmodule Binary.Inspect.NumberTest do
|
||||
assert inspect(100) == "100"
|
||||
end
|
||||
|
||||
unless :erlang.system_info(:otp_release) < 'R16' do
|
||||
test :float do
|
||||
assert inspect(1.0) == "1.0"
|
||||
assert inspect(1.0e10) == "10000000000.0"
|
||||
assert inspect(1.0e-10) == "0.0000000001"
|
||||
end
|
||||
test :float do
|
||||
assert inspect(1.0) == "1.0"
|
||||
assert inspect(1.0e10) == "10000000000.0"
|
||||
assert inspect(1.0e-10) == "0.0000000001"
|
||||
end
|
||||
end
|
||||
|
||||
@@ -138,6 +136,12 @@ defmodule Binary.Inspect.TupleTest do
|
||||
assert inspect(:something.new) == ":something[a: nil, b: nil]"
|
||||
end
|
||||
|
||||
defrecord Rec, value: 1
|
||||
|
||||
test :two_items_record do
|
||||
assert inspect({ Rec[value: 1], 1 }) == "{Binary.Inspect.TupleTest.Rec[value: 1],1}"
|
||||
end
|
||||
|
||||
test :empty do
|
||||
assert inspect({}) == "{}"
|
||||
end
|
||||
@@ -174,7 +178,7 @@ defmodule Binary.Inspect.ListTest do
|
||||
end
|
||||
|
||||
test :non_printable do
|
||||
assert inspect([{:b, 1},{:a,1}]) == "[b: 1, a: 1]"
|
||||
assert inspect([{:b, 1}, {:a, 1}]) == "[b: 1, a: 1]"
|
||||
end
|
||||
|
||||
test :unproper do
|
||||
|
||||
@@ -53,7 +53,7 @@ bar
|
||||
assert "\123" == "S"
|
||||
assert "\377" == "ÿ"
|
||||
assert "\128" == "\n8"
|
||||
assert "\18" == <<1,?8>>
|
||||
assert "\18" == <<1, ?8>>
|
||||
end
|
||||
|
||||
test :hex do
|
||||
@@ -62,10 +62,10 @@ bar
|
||||
assert "\xFF" == "ÿ"
|
||||
assert "\x{A}"== "\n"
|
||||
assert "\x{E9}"== "é"
|
||||
assert "\x{10F}" == <<196,143>>
|
||||
assert "\x{10FF}" == <<225,131,191>>
|
||||
assert "\x{10FFF}" == <<240,144,191,191>>
|
||||
assert "\x{10FFFF}" == <<244,143,191,191>>
|
||||
assert "\x{10F}" == <<196, 143>>
|
||||
assert "\x{10FF}" == <<225, 131, 191>>
|
||||
assert "\x{10FFF}" == <<240, 144, 191, 191>>
|
||||
assert "\x{10FFFF}" == <<244, 143, 191, 191>>
|
||||
end
|
||||
|
||||
test :match do
|
||||
@@ -85,17 +85,17 @@ bar
|
||||
end
|
||||
|
||||
test :bitsyntax_with_utf do
|
||||
assert <<106,111,115,101>> == << "jose" :: utf8 >>
|
||||
assert <<106,111,115,101>> == << 'jose' :: utf8 >>
|
||||
assert <<106, 111, 115, 101>> == << "jose" :: utf8 >>
|
||||
assert <<106, 111, 115, 101>> == << 'jose' :: utf8 >>
|
||||
|
||||
assert <<0,106,0,111,0,115,0,101>> == << "jose" :: utf16 >>
|
||||
assert <<0,106,0,111,0,115,0,101>> == << 'jose' :: utf16 >>
|
||||
assert <<0, 106, 0, 111, 0, 115, 0, 101>> == << "jose" :: utf16 >>
|
||||
assert <<0, 106, 0, 111, 0, 115, 0, 101>> == << 'jose' :: utf16 >>
|
||||
|
||||
assert <<106,0,111,0,115,0,101,0>> == << "jose" :: [utf16, little] >>
|
||||
assert <<106,0,111,0,115,0,101,0>> == << 'jose' :: [utf16, little] >>
|
||||
assert <<106, 0, 111, 0, 115, 0, 101, 0>> == << "jose" :: [utf16, little] >>
|
||||
assert <<106, 0, 111, 0, 115, 0, 101, 0>> == << 'jose' :: [utf16, little] >>
|
||||
|
||||
assert <<0,0,0,106,0,0,0,111,0,0,0,115,0,0,0,101>> == << "jose" :: utf32 >>
|
||||
assert <<0,0,0,106,0,0,0,111,0,0,0,115,0,0,0,101>> == << 'jose' :: utf32 >>
|
||||
assert <<0, 0, 0, 106, 0, 0, 0, 111, 0, 0, 0, 115, 0, 0, 0, 101>> == << "jose" :: utf32 >>
|
||||
assert <<0, 0, 0, 106, 0, 0, 0, 111, 0, 0, 0, 115, 0, 0, 0, 101>> == << 'jose' :: utf32 >>
|
||||
end
|
||||
|
||||
@binary "new "
|
||||
@@ -148,4 +148,4 @@ bar
|
||||
defp is_match?(_, _) do
|
||||
false
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -38,11 +38,11 @@ defmodule Bitwise.OperatorsTest do
|
||||
end
|
||||
|
||||
test :band do
|
||||
assert 1 &&& 1 == 1
|
||||
assert (1 &&& 1) == 1
|
||||
end
|
||||
|
||||
test :bor do
|
||||
assert 0 ||| 1 == 1
|
||||
assert (0 ||| 1) == 1
|
||||
end
|
||||
|
||||
test :bxor do
|
||||
@@ -50,10 +50,10 @@ defmodule Bitwise.OperatorsTest do
|
||||
end
|
||||
|
||||
test :bsl do
|
||||
assert 1 <<< 1 == 2
|
||||
assert (1 <<< 1) == 2
|
||||
end
|
||||
|
||||
test :bsr do
|
||||
assert 1 >>> 1 == 0
|
||||
assert (1 >>> 1) == 0
|
||||
end
|
||||
end
|
||||
|
||||
@@ -19,10 +19,23 @@ defmodule CodeTest do
|
||||
assert { 3, _ } = Code.eval_string("a + b", [a: 1, b: 2], __ENV__.location)
|
||||
end
|
||||
|
||||
test :eval_with_unnamed_scopes do
|
||||
assert { RuntimeError[], [a: RuntimeError[]] } =
|
||||
Code.eval_string("a = (try do (raise \"hello\") rescue e -> e end)")
|
||||
end
|
||||
|
||||
test :eval_with_scope do
|
||||
assert Code.eval_string("one", [], delegate_locals_to: __MODULE__) == { 1, [] }
|
||||
end
|
||||
|
||||
test :eval_options do
|
||||
assert Code.eval_string("is_atom(:foo) and is_record(1..2, Range) and K.is_list([])", [],
|
||||
functions: [{ Kernel, [is_atom: 1] }],
|
||||
macros: [{ Kernel, [..: 2, and: 2, is_record: 2]}],
|
||||
aliases: [{K, Kernel}],
|
||||
requires: [Kernel]) == { true, [] }
|
||||
end
|
||||
|
||||
test :eval_with_requires do
|
||||
assert Code.eval_string("Kernel.if true, do: :ok", [], requires: [Z, Kernel]) == { :ok, [] }
|
||||
end
|
||||
@@ -34,7 +47,7 @@ defmodule CodeTest do
|
||||
|
||||
test :eval_quoted_with_env do
|
||||
alias :lists, as: MyList
|
||||
assert Code.eval_quoted(quote(do: MyList.flatten [[1,2,3]]), [], __ENV__) == { [1,2,3],[] }
|
||||
assert Code.eval_quoted(quote(do: MyList.flatten [[1, 2, 3]]), [], __ENV__) == { [1, 2, 3],[] }
|
||||
end
|
||||
|
||||
test :require do
|
||||
@@ -60,40 +73,30 @@ defmodule CodeTest do
|
||||
assert :filename.absname(__FILE__) == __FILE__
|
||||
end
|
||||
|
||||
test :string_to_ast do
|
||||
assert Code.string_to_ast("1 + 2") == { :ok, { :+, [line: 1], [1, 2] } }
|
||||
assert { :error, _ } = Code.string_to_ast("a.1")
|
||||
test :string_to_quoted do
|
||||
assert Code.string_to_quoted("1 + 2") == { :ok, { :+, [line: 1], [1, 2] } }
|
||||
assert { :error, _ } = Code.string_to_quoted("a.1")
|
||||
end
|
||||
|
||||
test :string_to_ast_existing_atoms_only do
|
||||
assert :badarg = catch_error(Code.string_to_ast(":thereisnosuchatom", existing_atoms_only: true))
|
||||
assert :badarg = catch_error(Code.string_to_ast!(":thereisnosuchatom", existing_atoms_only: true))
|
||||
test :string_to_quoted_existing_atoms_only do
|
||||
assert :badarg = catch_error(Code.string_to_quoted(":thereisnosuchatom", existing_atoms_only: true))
|
||||
assert :badarg = catch_error(Code.string_to_quoted!(":thereisnosuchatom", existing_atoms_only: true))
|
||||
end
|
||||
|
||||
test :string_to_ast! do
|
||||
assert Code.string_to_ast!("1 + 2") == { :+, [line: 1], [1, 2] }
|
||||
test :string_to_quoted! do
|
||||
assert Code.string_to_quoted!("1 + 2") == { :+, [line: 1], [1, 2] }
|
||||
|
||||
assert_raise SyntaxError, fn ->
|
||||
Code.string_to_ast!("a.1")
|
||||
Code.string_to_quoted!("a.1")
|
||||
end
|
||||
|
||||
assert_raise TokenMissingError, fn ->
|
||||
Code.string_to_ast!("1 +")
|
||||
Code.string_to_quoted!("1 +")
|
||||
end
|
||||
end
|
||||
|
||||
test :compile_source do
|
||||
compile = __MODULE__.__info__(:compile)
|
||||
|
||||
# Erlang has a bug that does not allow us to set the source
|
||||
# when compiling forms. In such cases, the source will be
|
||||
# under compile option. This is fixed and future Erlang
|
||||
# version will return the proper source always (source2).
|
||||
options = :proplists.get_value(:options, compile, [])
|
||||
source1 = :proplists.get_value(:source, options, nil)
|
||||
source2 = :proplists.get_value(:source, compile, nil)
|
||||
|
||||
assert (source1 || source2) == binary_to_list(__FILE__)
|
||||
assert __MODULE__.__info__(:compile)[:source] == binary_to_list(__FILE__)
|
||||
end
|
||||
|
||||
test :compile_info_returned_with_source_accessible_through_keyword_module do
|
||||
@@ -101,6 +104,20 @@ defmodule CodeTest do
|
||||
assert Keyword.get(compile, :source) != nil
|
||||
end
|
||||
|
||||
test :compile_string do
|
||||
assert [{ CompileStringSample, _ }] = Code.compile_string("defmodule CompileStringSample, do: :ok")
|
||||
after
|
||||
:code.purge CompileSimpleSample
|
||||
:code.delete CompileSimpleSample
|
||||
end
|
||||
|
||||
test :compile_quoted do
|
||||
assert [{ CompileQuotedSample, _ }] = Code.compile_string("defmodule CompileQuotedSample, do: :ok")
|
||||
after
|
||||
:code.purge CompileQuotedSample
|
||||
:code.delete CompileQuotedSample
|
||||
end
|
||||
|
||||
test :ensure_loaded? do
|
||||
assert Code.ensure_loaded?(__MODULE__)
|
||||
refute Code.ensure_loaded?(Unknown.Module)
|
||||
|
||||
@@ -5,7 +5,7 @@ defmodule EnumTest.List do
|
||||
|
||||
test :empty? do
|
||||
assert Enum.empty?([])
|
||||
refute Enum.empty?([1,2,3])
|
||||
refute Enum.empty?([1, 2, 3])
|
||||
refute Enum.empty?(1..3)
|
||||
end
|
||||
|
||||
@@ -18,89 +18,89 @@ defmodule EnumTest.List do
|
||||
end
|
||||
|
||||
test :count do
|
||||
assert Enum.count([1,2,3]) == 3
|
||||
assert Enum.count([1, 2, 3]) == 3
|
||||
assert Enum.count([]) == 0
|
||||
end
|
||||
|
||||
test :count_fun do
|
||||
assert Enum.count([1,2,3], fn(x) -> rem(x, 2) == 0 end) == 1
|
||||
assert Enum.count([1, 2, 3], fn(x) -> rem(x, 2) == 0 end) == 1
|
||||
assert Enum.count([], fn(x) -> rem(x, 2) == 0 end) == 0
|
||||
end
|
||||
|
||||
test :all? do
|
||||
assert Enum.all?([2,4,6], fn(x) -> rem(x, 2) == 0 end)
|
||||
refute Enum.all?([2,3,4], fn(x) -> rem(x, 2) == 0 end)
|
||||
assert Enum.all?([2, 4, 6], fn(x) -> rem(x, 2) == 0 end)
|
||||
refute Enum.all?([2, 3, 4], fn(x) -> rem(x, 2) == 0 end)
|
||||
|
||||
assert Enum.all?([2,4,6])
|
||||
refute Enum.all?([2,nil,4])
|
||||
assert Enum.all?([2, 4, 6])
|
||||
refute Enum.all?([2, nil, 4])
|
||||
|
||||
assert Enum.all?([])
|
||||
end
|
||||
|
||||
test :any? do
|
||||
refute Enum.any?([2,4,6], fn(x) -> rem(x, 2) == 1 end)
|
||||
assert Enum.any?([2,3,4], fn(x) -> rem(x, 2) == 1 end)
|
||||
refute Enum.any?([2, 4, 6], fn(x) -> rem(x, 2) == 1 end)
|
||||
assert Enum.any?([2, 3, 4], fn(x) -> rem(x, 2) == 1 end)
|
||||
|
||||
refute Enum.any?([false,false,false])
|
||||
assert Enum.any?([false,true,false])
|
||||
refute Enum.any?([false, false, false])
|
||||
assert Enum.any?([false, true, false])
|
||||
|
||||
refute Enum.any?([])
|
||||
end
|
||||
|
||||
test :at do
|
||||
assert Enum.at([2,4,6], 0) == 2
|
||||
assert Enum.at([2,4,6], 2) == 6
|
||||
assert Enum.at([2,4,6], 4) == nil
|
||||
assert Enum.at([2,4,6], 4, :none) == :none
|
||||
assert Enum.at([2, 4, 6], 0) == 2
|
||||
assert Enum.at([2, 4, 6], 2) == 6
|
||||
assert Enum.at([2, 4, 6], 4) == nil
|
||||
assert Enum.at([2, 4, 6], 4, :none) == :none
|
||||
end
|
||||
|
||||
test :fetch! do
|
||||
assert Enum.fetch!([2,4,6], 0) == 2
|
||||
assert Enum.fetch!([2,4,6], 2) == 6
|
||||
assert Enum.fetch!([2, 4, 6], 0) == 2
|
||||
assert Enum.fetch!([2, 4, 6], 2) == 6
|
||||
assert_raise Enum.OutOfBoundsError, fn ->
|
||||
Enum.fetch!([2,4,6], 4)
|
||||
Enum.fetch!([2, 4, 6], 4)
|
||||
end
|
||||
end
|
||||
|
||||
test :drop do
|
||||
assert Enum.drop([1,2,3], 0) == [1,2,3]
|
||||
assert Enum.drop([1,2,3], 1) == [2,3]
|
||||
assert Enum.drop([1,2,3], 2) == [3]
|
||||
assert Enum.drop([1,2,3], 3) == []
|
||||
assert Enum.drop([1,2,3], 4) == []
|
||||
assert Enum.drop([1,2,3], -1) == [3]
|
||||
assert Enum.drop([1, 2, 3], 0) == [1, 2, 3]
|
||||
assert Enum.drop([1, 2, 3], 1) == [2, 3]
|
||||
assert Enum.drop([1, 2, 3], 2) == [3]
|
||||
assert Enum.drop([1, 2, 3], 3) == []
|
||||
assert Enum.drop([1, 2, 3], 4) == []
|
||||
assert Enum.drop([1, 2, 3], -1) == [3]
|
||||
assert Enum.drop([], 3) == []
|
||||
end
|
||||
|
||||
test :drop_while do
|
||||
assert Enum.drop_while([1,2,3,4,3,2,1], fn(x) -> x <= 3 end) == [4,3,2,1]
|
||||
assert Enum.drop_while([1,2,3], fn(_) -> false end) == [1,2,3]
|
||||
assert Enum.drop_while([1,2,3], fn(x) -> x <= 3 end) == []
|
||||
assert Enum.drop_while([1, 2, 3, 4, 3, 2, 1], fn(x) -> x <= 3 end) == [4, 3, 2, 1]
|
||||
assert Enum.drop_while([1, 2, 3], fn(_) -> false end) == [1, 2, 3]
|
||||
assert Enum.drop_while([1, 2, 3], fn(x) -> x <= 3 end) == []
|
||||
assert Enum.drop_while([], fn(_) -> false end) == []
|
||||
end
|
||||
|
||||
test :find do
|
||||
assert Enum.find([2,4,6], fn(x) -> rem(x, 2) == 1 end) == nil
|
||||
assert Enum.find([2,4,6], 0, fn(x) -> rem(x, 2) == 1 end) == 0
|
||||
assert Enum.find([2,3,4], fn(x) -> rem(x, 2) == 1 end) == 3
|
||||
assert Enum.find([2, 4, 6], fn(x) -> rem(x, 2) == 1 end) == nil
|
||||
assert Enum.find([2, 4, 6], 0, fn(x) -> rem(x, 2) == 1 end) == 0
|
||||
assert Enum.find([2, 3, 4], fn(x) -> rem(x, 2) == 1 end) == 3
|
||||
end
|
||||
|
||||
test :find_value do
|
||||
assert Enum.find_value([2,4,6], fn(x) -> rem(x, 2) == 1 end) == nil
|
||||
assert Enum.find_value([2,4,6], 0, fn(x) -> rem(x, 2) == 1 end) == 0
|
||||
assert Enum.find_value([2,3,4], fn(x) -> rem(x, 2) == 1 end)
|
||||
assert Enum.find_value([2, 4, 6], fn(x) -> rem(x, 2) == 1 end) == nil
|
||||
assert Enum.find_value([2, 4, 6], 0, fn(x) -> rem(x, 2) == 1 end) == 0
|
||||
assert Enum.find_value([2, 3, 4], fn(x) -> rem(x, 2) == 1 end)
|
||||
end
|
||||
|
||||
test :find_index do
|
||||
assert Enum.find_index([2,4,6], fn(x) -> rem(x, 2) == 1 end) == nil
|
||||
assert Enum.find_index([2,3,4], fn(x) -> rem(x, 2) == 1 end) == 1
|
||||
assert Enum.find_index([2, 4, 6], fn(x) -> rem(x, 2) == 1 end) == nil
|
||||
assert Enum.find_index([2, 3, 4], fn(x) -> rem(x, 2) == 1 end) == 1
|
||||
end
|
||||
|
||||
test :each do
|
||||
try do
|
||||
assert Enum.each([], fn(x) -> x end) == :ok
|
||||
|
||||
assert Enum.each([1,2,3], fn(x) -> Process.put(:enum_test_each, x * 2) end) == :ok
|
||||
assert Enum.each([1, 2, 3], fn(x) -> Process.put(:enum_test_each, x * 2) end) == :ok
|
||||
assert Process.get(:enum_test_each) == 6
|
||||
|
||||
assert Enum.each([], fn(x, idx) -> {x, idx} end) == :ok
|
||||
@@ -114,64 +114,72 @@ defmodule EnumTest.List do
|
||||
end
|
||||
|
||||
test :fetch do
|
||||
assert Enum.fetch([2,4,6], 0) == { :ok, 2 }
|
||||
assert Enum.fetch([2,4,6], 2) == { :ok, 6 }
|
||||
assert Enum.fetch([2,4,6], 4) == :error
|
||||
assert Enum.fetch([2, 4, 6], 0) == { :ok, 2 }
|
||||
assert Enum.fetch([2, 4, 6], 2) == { :ok, 6 }
|
||||
assert Enum.fetch([2, 4, 6], 4) == :error
|
||||
end
|
||||
|
||||
test :first do
|
||||
assert Enum.first([]) == nil
|
||||
assert Enum.first([1,2,3]) == 1
|
||||
assert Enum.first([1, 2, 3]) == 1
|
||||
end
|
||||
|
||||
test :filter do
|
||||
assert Enum.filter([1,2,3], fn(x) -> rem(x, 2) == 0 end) == [2]
|
||||
assert Enum.filter([2,4,6], fn(x) -> rem(x, 2) == 0 end) == [2,4,6]
|
||||
assert Enum.filter([1, 2, 3], fn(x) -> rem(x, 2) == 0 end) == [2]
|
||||
assert Enum.filter([2, 4, 6], fn(x) -> rem(x, 2) == 0 end) == [2, 4, 6]
|
||||
end
|
||||
|
||||
test :filter_with_match do
|
||||
assert Enum.filter([1,2,3], match?(1, &1)) == [1]
|
||||
assert Enum.filter([1,2,3], match?(x when x < 3, &1)) == [1,2]
|
||||
assert Enum.filter([1,2,3], match?(_, &1)) == [1,2,3]
|
||||
assert Enum.filter([1, 2, 3], match?(1, &1)) == [1]
|
||||
assert Enum.filter([1, 2, 3], match?(x when x < 3, &1)) == [1, 2]
|
||||
assert Enum.filter([1, 2, 3], match?(_, &1)) == [1, 2, 3]
|
||||
end
|
||||
|
||||
test :filter_map do
|
||||
assert Enum.filter_map([1,2,3], fn(x) -> rem(x, 2) == 0 end, &1 * 2) == [4]
|
||||
assert Enum.filter_map([2,4,6], fn(x) -> rem(x, 2) == 0 end, &1 * 2) == [4,8,12]
|
||||
assert Enum.filter_map([1, 2, 3], fn(x) -> rem(x, 2) == 0 end, &1 * 2) == [4]
|
||||
assert Enum.filter_map([2, 4, 6], fn(x) -> rem(x, 2) == 0 end, &1 * 2) == [4, 8, 12]
|
||||
end
|
||||
|
||||
test :reduce do
|
||||
assert Enum.reduce([], 1, fn(x, acc) -> x + acc end) == 1
|
||||
assert Enum.reduce([1,2,3], 1, fn(x, acc) -> x + acc end) == 7
|
||||
assert Enum.reduce([1, 2, 3], 1, fn(x, acc) -> x + acc end) == 7
|
||||
end
|
||||
|
||||
test :join_with_bin do
|
||||
assert Enum.join([], " = ") == ""
|
||||
assert Enum.join([1,2,3], " = ") == "1 = 2 = 3"
|
||||
assert Enum.join([1,"2",3], " = ") == "1 = 2 = 3"
|
||||
assert Enum.join([1,2,3]) == "123"
|
||||
assert Enum.join([1, 2, 3], " = ") == "1 = 2 = 3"
|
||||
assert Enum.join([1, "2", 3], " = ") == "1 = 2 = 3"
|
||||
assert Enum.join([1, 2, 3]) == "123"
|
||||
end
|
||||
|
||||
test :join_with_list do
|
||||
assert Enum.join([], ' = ') == ''
|
||||
assert Enum.join([1,2,3], ' = ') == '1 = 2 = 3'
|
||||
assert Enum.join([1,"2",3], ' = ') == '1 = 2 = 3'
|
||||
assert Enum.join([1, 2, 3], ' = ') == '1 = 2 = 3'
|
||||
assert Enum.join([1, "2", 3], ' = ') == '1 = 2 = 3'
|
||||
end
|
||||
|
||||
test :map_join_with_bin do
|
||||
assert Enum.map_join([], " = ", &1 * 2) == ""
|
||||
assert Enum.map_join([1,2,3], " = ", &1 * 2) == "2 = 4 = 6"
|
||||
assert Enum.map_join([1,2,3], &1 * 2) == "246"
|
||||
assert Enum.map_join([1, 2, 3], " = ", &1 * 2) == "2 = 4 = 6"
|
||||
assert Enum.map_join([1, 2, 3], &1 * 2) == "246"
|
||||
end
|
||||
|
||||
test :map_join_with_list do
|
||||
assert Enum.map_join([], ' = ', &1 * 2) == ''
|
||||
assert Enum.map_join([1,2,3], ' = ', &1 * 2) == '2 = 4 = 6'
|
||||
assert Enum.map_join([1, 2, 3], ' = ', &1 * 2) == '2 = 4 = 6'
|
||||
end
|
||||
|
||||
test :join_empty do
|
||||
fun = fn (acc, _) -> acc end
|
||||
assert Enum.join(fun, ".") == ""
|
||||
assert Enum.map_join(fun, ".", &1 + 0) == ""
|
||||
assert Enum.join(fun, '.') == ''
|
||||
assert Enum.map_join(fun, '.', &1 + 0) == ''
|
||||
end
|
||||
|
||||
test :map do
|
||||
assert Enum.map([], fn x -> x * 2 end) == []
|
||||
assert Enum.map([1,2,3], fn x -> x * 2 end) == [2,4,6]
|
||||
assert Enum.map([1, 2, 3], fn x -> x * 2 end) == [2, 4, 6]
|
||||
|
||||
assert Enum.map([], fn x, idx -> x * idx end) == []
|
||||
assert Enum.map([1, 2, 3], fn _, idx -> idx end) == [0, 1, 2]
|
||||
@@ -179,86 +187,79 @@ defmodule EnumTest.List do
|
||||
|
||||
test :map_reduce do
|
||||
assert Enum.map_reduce([], 1, fn(x, acc) -> { x * 2, x + acc } end) == { [], 1 }
|
||||
assert Enum.map_reduce([1,2,3], 1, fn(x, acc) -> { x * 2, x + acc } end) == { [2,4,6], 7 }
|
||||
assert Enum.map_reduce([1, 2, 3], 1, fn(x, acc) -> { x * 2, x + acc } end) == { [2, 4, 6], 7 }
|
||||
end
|
||||
|
||||
test :partition do
|
||||
assert Enum.partition([1,2,3], fn(x) -> rem(x, 2) == 0 end) == { [2], [1,3] }
|
||||
assert Enum.partition([2,4,6], fn(x) -> rem(x, 2) == 0 end) == { [2,4,6], [] }
|
||||
assert Enum.partition([1, 2, 3], fn(x) -> rem(x, 2) == 0 end) == { [2], [1, 3] }
|
||||
assert Enum.partition([2, 4, 6], fn(x) -> rem(x, 2) == 0 end) == { [2, 4, 6], [] }
|
||||
end
|
||||
|
||||
test :reverse do
|
||||
assert Enum.reverse([]) == []
|
||||
assert Enum.reverse([1,2,3]) == [3,2,1]
|
||||
assert Enum.reverse([1, 2, 3]) == [3, 2, 1]
|
||||
end
|
||||
|
||||
test :sort do
|
||||
assert Enum.sort([5,3,2,4,1]) == [1,2,3,4,5]
|
||||
assert Enum.sort([5,3,2,4,1], &1 > &2) == [5,4,3,2,1]
|
||||
assert Enum.sort([5, 3, 2, 4, 1]) == [1, 2, 3, 4, 5]
|
||||
assert Enum.sort([5, 3, 2, 4, 1], &1 > &2) == [5, 4, 3, 2, 1]
|
||||
end
|
||||
|
||||
test :split do
|
||||
assert Enum.split([1,2,3], 0) == { [], [1,2,3] }
|
||||
assert Enum.split([1,2,3], 1) == { [1], [2,3] }
|
||||
assert Enum.split([1,2,3], 2) == { [1,2], [3] }
|
||||
assert Enum.split([1,2,3], 3) == { [1,2,3], [] }
|
||||
assert Enum.split([1,2,3], 4) == { [1,2,3], [] }
|
||||
assert Enum.split([1, 2, 3], 0) == { [], [1, 2, 3] }
|
||||
assert Enum.split([1, 2, 3], 1) == { [1], [2, 3] }
|
||||
assert Enum.split([1, 2, 3], 2) == { [1, 2], [3] }
|
||||
assert Enum.split([1, 2, 3], 3) == { [1, 2, 3], [] }
|
||||
assert Enum.split([1, 2, 3], 4) == { [1, 2, 3], [] }
|
||||
assert Enum.split([], 3) == { [], [] }
|
||||
assert Enum.split([1,2,3], -1) == { [1,2], [3] }
|
||||
assert Enum.split([1,2,3], -2) == { [1], [2,3] }
|
||||
assert Enum.split([1,2,3], -3) == { [], [1,2,3] }
|
||||
assert Enum.split([1,2,3], -10) == { [], [1,2,3] }
|
||||
assert Enum.split([1, 2, 3], -1) == { [1, 2], [3] }
|
||||
assert Enum.split([1, 2, 3], -2) == { [1], [2, 3] }
|
||||
assert Enum.split([1, 2, 3], -3) == { [], [1, 2, 3] }
|
||||
assert Enum.split([1, 2, 3], -10) == { [], [1, 2, 3] }
|
||||
end
|
||||
|
||||
test :split_while do
|
||||
assert Enum.split_while([1,2,3], fn(_) -> false end) == { [], [1,2,3] }
|
||||
assert Enum.split_while([1,2,3], fn(_) -> true end) == { [1,2,3], [] }
|
||||
assert Enum.split_while([1,2,3], fn(x) -> x > 2 end) == { [], [1,2,3] }
|
||||
assert Enum.split_while([1,2,3], fn(x) -> x > 3 end) == { [], [1,2,3] }
|
||||
assert Enum.split_while([1,2,3], fn(x) -> x < 3 end) == { [1,2], [3] }
|
||||
assert Enum.split_while([1, 2, 3], fn(_) -> false end) == { [], [1, 2, 3] }
|
||||
assert Enum.split_while([1, 2, 3], fn(_) -> true end) == { [1, 2, 3], [] }
|
||||
assert Enum.split_while([1, 2, 3], fn(x) -> x > 2 end) == { [], [1, 2, 3] }
|
||||
assert Enum.split_while([1, 2, 3], fn(x) -> x > 3 end) == { [], [1, 2, 3] }
|
||||
assert Enum.split_while([1, 2, 3], fn(x) -> x < 3 end) == { [1, 2], [3] }
|
||||
assert Enum.split_while([], fn(_) -> true end) == { [], [] }
|
||||
end
|
||||
|
||||
test :take do
|
||||
assert Enum.take([1,2,3], 0) == []
|
||||
assert Enum.take([1,2,3], 1) == [1]
|
||||
assert Enum.take([1,2,3], 2) == [1,2]
|
||||
assert Enum.take([1,2,3], 3) == [1,2,3]
|
||||
assert Enum.take([1,2,3], 4) == [1,2,3]
|
||||
assert Enum.take([1,2,3], -1) == [1,2]
|
||||
assert Enum.take([1, 2, 3], 0) == []
|
||||
assert Enum.take([1, 2, 3], 1) == [1]
|
||||
assert Enum.take([1, 2, 3], 2) == [1, 2]
|
||||
assert Enum.take([1, 2, 3], 3) == [1, 2, 3]
|
||||
assert Enum.take([1, 2, 3], 4) == [1, 2, 3]
|
||||
assert Enum.take([1, 2, 3], -1) == [1, 2]
|
||||
assert Enum.take([], 3) == []
|
||||
end
|
||||
|
||||
test :take_does_not_consume_next_without_a_need do
|
||||
import PathHelpers
|
||||
iterator = File.iterator!(fixture_path("one-liner.txt"))
|
||||
|
||||
try do
|
||||
File.open!(fixture_path("one-liner.txt"), [], fn file ->
|
||||
iterator = File.iterator(file)
|
||||
assert Enum.take(iterator, 1) == ["ONE"]
|
||||
assert Enum.take(iterator, 5) == []
|
||||
after
|
||||
File.close(iterator)
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
test :take_with_no_item_works_as_no_op do
|
||||
import PathHelpers
|
||||
iterator = File.iterator!(fixture_path("one-liner.txt"))
|
||||
|
||||
try do
|
||||
assert Enum.take(iterator, 0) == []
|
||||
assert Enum.take(iterator, 0) == []
|
||||
assert Enum.take(iterator, 0) == []
|
||||
assert Enum.take(iterator, 0) == []
|
||||
after
|
||||
File.close(iterator)
|
||||
end
|
||||
assert Enum.take(iterator, 0) == []
|
||||
assert Enum.take(iterator, 0) == []
|
||||
assert Enum.take(iterator, 0) == []
|
||||
assert Enum.take(iterator, 0) == []
|
||||
end
|
||||
|
||||
test :take_while do
|
||||
assert Enum.take_while([1,2,3], fn(x) -> x > 3 end) == []
|
||||
assert Enum.take_while([1,2,3], fn(x) -> x <= 1 end) == [1]
|
||||
assert Enum.take_while([1,2,3], fn(x) -> x <= 3 end) == [1,2,3]
|
||||
assert Enum.take_while([1, 2, 3], fn(x) -> x > 3 end) == []
|
||||
assert Enum.take_while([1, 2, 3], fn(x) -> x <= 1 end) == [1]
|
||||
assert Enum.take_while([1, 2, 3], fn(x) -> x <= 3 end) == [1, 2, 3]
|
||||
assert Enum.take_while([], fn(_) -> true end) == []
|
||||
end
|
||||
|
||||
@@ -268,8 +269,8 @@ defmodule EnumTest.List do
|
||||
end
|
||||
|
||||
test :uniq do
|
||||
assert Enum.uniq([1,2,3,2,1]) == [1,2,3]
|
||||
assert Enum.uniq([1,2,3,2,1], fn x -> x end) == [1,2,3]
|
||||
assert Enum.uniq([1, 2, 3, 2, 1]) == [1, 2, 3]
|
||||
assert Enum.uniq([1, 2, 3, 2, 1], fn x -> x end) == [1, 2, 3]
|
||||
end
|
||||
|
||||
test :zip do
|
||||
@@ -283,8 +284,8 @@ defmodule EnumTest.List do
|
||||
|
||||
test :max do
|
||||
assert Enum.max([1]) == 1
|
||||
assert Enum.max([1,2,3]) == 3
|
||||
assert Enum.max([1,[],:a,{}]) == []
|
||||
assert Enum.max([1, 2, 3]) == 3
|
||||
assert Enum.max([1, [], :a, {}]) == []
|
||||
assert_raise Enum.EmptyError, fn ->
|
||||
assert Enum.max([])
|
||||
end
|
||||
@@ -297,8 +298,8 @@ defmodule EnumTest.List do
|
||||
|
||||
test :min do
|
||||
assert Enum.min([1]) == 1
|
||||
assert Enum.min([1,2,3]) == 1
|
||||
assert Enum.min([[],:a,{}]) == :a
|
||||
assert Enum.min([1, 2, 3]) == 1
|
||||
assert Enum.min([[], :a, {}]) == :a
|
||||
assert_raise Enum.EmptyError, fn ->
|
||||
assert Enum.min([])
|
||||
end
|
||||
@@ -367,8 +368,8 @@ defmodule EnumTest.Range do
|
||||
|
||||
test :drop do
|
||||
range = Range.new(first: 1, last: 3)
|
||||
assert Enum.drop(range, 0) == [1,2,3]
|
||||
assert Enum.drop(range, 1) == [2,3]
|
||||
assert Enum.drop(range, 0) == [1, 2, 3]
|
||||
assert Enum.drop(range, 1) == [2, 3]
|
||||
assert Enum.drop(range, 2) == [3]
|
||||
assert Enum.drop(range, 3) == []
|
||||
assert Enum.drop(range, 4) == []
|
||||
@@ -379,14 +380,14 @@ defmodule EnumTest.Range do
|
||||
|
||||
test :drop_while do
|
||||
range = Range.new(first: 0, last: 6)
|
||||
assert Enum.drop_while(range, fn(x) -> x <= 3 end) == [4,5,6]
|
||||
assert Enum.drop_while(range, fn(_) -> false end) == [0,1,2,3,4,5,6]
|
||||
assert Enum.drop_while(range, fn(x) -> x <= 3 end) == [4, 5, 6]
|
||||
assert Enum.drop_while(range, fn(_) -> false end) == [0, 1, 2, 3, 4, 5, 6]
|
||||
|
||||
range = Range.new(first: 0, last: 3)
|
||||
assert Enum.drop_while(range, fn(x) -> x <= 3 end) == []
|
||||
|
||||
range = Range.new(first: 1, last: 0)
|
||||
assert Enum.drop_while(range, fn(_) -> false end) == [1,0]
|
||||
assert Enum.drop_while(range, fn(_) -> false end) == [1, 0]
|
||||
end
|
||||
|
||||
test :find do
|
||||
@@ -448,14 +449,14 @@ defmodule EnumTest.Range do
|
||||
assert Enum.filter(range, fn(x) -> rem(x, 2) == 0 end) == [2]
|
||||
|
||||
range = Range.new(first: 1, last: 6)
|
||||
assert Enum.filter(range, fn(x) -> rem(x, 2) == 0 end) == [2,4,6]
|
||||
assert Enum.filter(range, fn(x) -> rem(x, 2) == 0 end) == [2, 4, 6]
|
||||
end
|
||||
|
||||
test :filter_with_match do
|
||||
range = Range.new(first: 1, last: 3)
|
||||
assert Enum.filter(range, match?(1, &1)) == [1]
|
||||
assert Enum.filter(range, match?(x when x < 3, &1)) == [1,2]
|
||||
assert Enum.filter(range, match?(_, &1)) == [1,2,3]
|
||||
assert Enum.filter(range, match?(x when x < 3, &1)) == [1, 2]
|
||||
assert Enum.filter(range, match?(_, &1)) == [1, 2, 3]
|
||||
end
|
||||
|
||||
test :filter_map do
|
||||
@@ -463,7 +464,7 @@ defmodule EnumTest.Range do
|
||||
assert Enum.filter_map(range, fn(x) -> rem(x, 2) == 0 end, &1 * 2) == [4]
|
||||
|
||||
range = Range.new(first: 2, last: 6)
|
||||
assert Enum.filter_map(range, fn(x) -> rem(x, 2) == 0 end, &1 * 2) == [4,8,12]
|
||||
assert Enum.filter_map(range, fn(x) -> rem(x, 2) == 0 end, &1 * 2) == [4, 8, 12]
|
||||
end
|
||||
|
||||
test :reduce do
|
||||
@@ -510,10 +511,10 @@ defmodule EnumTest.Range do
|
||||
|
||||
test :map do
|
||||
range = Range.new(first: 1, last: 3)
|
||||
assert Enum.map(range, fn x -> x * 2 end) == [2,4,6]
|
||||
assert Enum.map(range, fn x -> x * 2 end) == [2, 4, 6]
|
||||
|
||||
range = Range.new(first: -1, last: -3)
|
||||
assert Enum.map(range, fn x -> x * 2 end) == [-2,-4,-6]
|
||||
assert Enum.map(range, fn x -> x * 2 end) == [-2, -4, -6]
|
||||
end
|
||||
|
||||
test :map_reduce do
|
||||
@@ -521,47 +522,47 @@ defmodule EnumTest.Range do
|
||||
assert Enum.map_reduce(range, 1, fn(x, acc) -> { x * 2, x + acc } end) == { [2, 0], 2 }
|
||||
|
||||
range = Range.new(first: 1, last: 3)
|
||||
assert Enum.map_reduce(range, 1, fn(x, acc) -> { x * 2, x + acc } end) == { [2,4,6], 7 }
|
||||
assert Enum.map_reduce(range, 1, fn(x, acc) -> { x * 2, x + acc } end) == { [2, 4, 6], 7 }
|
||||
end
|
||||
|
||||
test :partition do
|
||||
range = Range.new(first: 1, last: 3)
|
||||
assert Enum.partition(range, fn(x) -> rem(x, 2) == 0 end) == { [2], [1,3] }
|
||||
assert Enum.partition(range, fn(x) -> rem(x, 2) == 0 end) == { [2], [1, 3] }
|
||||
end
|
||||
|
||||
test :sort do
|
||||
assert Enum.sort(Range.new(first: 3, last: 1)) == [1,2,3]
|
||||
assert Enum.sort(Range.new(first: 2, last: 1)) == [1,2]
|
||||
assert Enum.sort(Range.new(first: 3, last: 1)) == [1, 2, 3]
|
||||
assert Enum.sort(Range.new(first: 2, last: 1)) == [1, 2]
|
||||
assert Enum.sort(Range.new(first: 1, last: 1)) == [1]
|
||||
|
||||
assert Enum.sort(Range.new(first: 3, last: 1), &1 > &2) == [3,2,1]
|
||||
assert Enum.sort(Range.new(first: 2, last: 1), &1 > &2) == [2,1]
|
||||
assert Enum.sort(Range.new(first: 3, last: 1), &1 > &2) == [3, 2, 1]
|
||||
assert Enum.sort(Range.new(first: 2, last: 1), &1 > &2) == [2, 1]
|
||||
assert Enum.sort(Range.new(first: 1, last: 1), &1 > &2) == [1]
|
||||
end
|
||||
|
||||
test :split do
|
||||
range = Range.new(first: 1, last: 3)
|
||||
assert Enum.split(range, 0) == { [], [1,2,3] }
|
||||
assert Enum.split(range, 1) == { [1], [2,3] }
|
||||
assert Enum.split(range, 2) == { [1,2], [3] }
|
||||
assert Enum.split(range, 3) == { [1,2,3], [] }
|
||||
assert Enum.split(range, 4) == { [1,2,3], [] }
|
||||
assert Enum.split(range, -1) == { [1,2], [3] }
|
||||
assert Enum.split(range, -2) == { [1], [2,3] }
|
||||
assert Enum.split(range, -3) == { [], [1,2,3] }
|
||||
assert Enum.split(range, -10) == { [], [1,2,3] }
|
||||
assert Enum.split(range, 0) == { [], [1, 2, 3] }
|
||||
assert Enum.split(range, 1) == { [1], [2, 3] }
|
||||
assert Enum.split(range, 2) == { [1, 2], [3] }
|
||||
assert Enum.split(range, 3) == { [1, 2, 3], [] }
|
||||
assert Enum.split(range, 4) == { [1, 2, 3], [] }
|
||||
assert Enum.split(range, -1) == { [1, 2], [3] }
|
||||
assert Enum.split(range, -2) == { [1], [2, 3] }
|
||||
assert Enum.split(range, -3) == { [], [1, 2, 3] }
|
||||
assert Enum.split(range, -10) == { [], [1, 2, 3] }
|
||||
|
||||
range = Range.new(first: 1, last: 0)
|
||||
assert Enum.split(range, 3) == { [1,0], [] }
|
||||
assert Enum.split(range, 3) == { [1, 0], [] }
|
||||
end
|
||||
|
||||
test :split_while do
|
||||
range = Range.new(first: 1, last: 3)
|
||||
assert Enum.split_while(range, fn(_) -> false end) == { [], [1,2,3] }
|
||||
assert Enum.split_while(range, fn(_) -> true end) == { [1,2,3], [] }
|
||||
assert Enum.split_while(range, fn(x) -> x > 2 end) == { [], [1,2,3] }
|
||||
assert Enum.split_while(range, fn(x) -> x > 3 end) == { [], [1,2,3] }
|
||||
assert Enum.split_while(range, fn(x) -> x < 3 end) == { [1,2], [3] }
|
||||
assert Enum.split_while(range, fn(_) -> false end) == { [], [1, 2, 3] }
|
||||
assert Enum.split_while(range, fn(_) -> true end) == { [1, 2, 3], [] }
|
||||
assert Enum.split_while(range, fn(x) -> x > 2 end) == { [], [1, 2, 3] }
|
||||
assert Enum.split_while(range, fn(x) -> x > 3 end) == { [], [1, 2, 3] }
|
||||
assert Enum.split_while(range, fn(x) -> x < 3 end) == { [1, 2], [3] }
|
||||
|
||||
range = Range.new(first: 1, last: 0)
|
||||
assert Enum.split_while(range, fn(_) -> true end) == { [1, 0], [] }
|
||||
@@ -571,25 +572,25 @@ defmodule EnumTest.Range do
|
||||
range = Range.new(first: 1, last: 3)
|
||||
assert Enum.take(range, 0) == []
|
||||
assert Enum.take(range, 1) == [1]
|
||||
assert Enum.take(range, 2) == [1,2]
|
||||
assert Enum.take(range, 3) == [1,2,3]
|
||||
assert Enum.take(range, 4) == [1,2,3]
|
||||
assert Enum.take(range, 2) == [1, 2]
|
||||
assert Enum.take(range, 3) == [1, 2, 3]
|
||||
assert Enum.take(range, 4) == [1, 2, 3]
|
||||
|
||||
range = Range.new(first: 1, last: 0)
|
||||
assert Enum.take(range, 3) == [1,0]
|
||||
assert Enum.take(range, 3) == [1, 0]
|
||||
end
|
||||
|
||||
test :take_while do
|
||||
range = Range.new(first: 1, last: 3)
|
||||
assert Enum.take_while(range, fn(x) -> x > 3 end) == []
|
||||
assert Enum.take_while(range, fn(x) -> x <= 1 end) == [1]
|
||||
assert Enum.take_while(range, fn(x) -> x <= 3 end) == [1,2,3]
|
||||
assert Enum.take_while(range, fn(x) -> x <= 3 end) == [1, 2, 3]
|
||||
assert Enum.take_while([], fn(_) -> true end) == []
|
||||
end
|
||||
|
||||
test :uniq do
|
||||
assert Enum.uniq(1..3) == [1,2,3]
|
||||
assert Enum.uniq(1..3, fn x -> x end) == [1,2,3]
|
||||
assert Enum.uniq(1..3) == [1, 2, 3]
|
||||
assert Enum.uniq(1..3, fn x -> x end) == [1, 2, 3]
|
||||
end
|
||||
|
||||
test :zip do
|
||||
@@ -617,8 +618,8 @@ defmodule EnumTest.Range do
|
||||
|
||||
test :min do
|
||||
assert Enum.min([1]) == 1
|
||||
assert Enum.min([1,2,3]) == 1
|
||||
assert Enum.min([[],:a,{}]) == :a
|
||||
assert Enum.min([1, 2, 3]) == 1
|
||||
assert Enum.min([[], :a, {}]) == :a
|
||||
|
||||
assert Enum.min(1..1, fn(x) -> :math.pow(-2, x) end) == 1
|
||||
assert Enum.min(1..3, fn(x) -> :math.pow(-2, x) end) == 3
|
||||
@@ -628,6 +629,8 @@ end
|
||||
defmodule EnumTest.Others do
|
||||
use ExUnit.Case, async: true
|
||||
|
||||
import ExUnit.CaptureIO
|
||||
|
||||
test :reverse do
|
||||
assert Enum.reverse(URI.query_decoder("foo=bar&baz=bat")) ==
|
||||
[{ "baz", "bat" }, { "foo", "bar" }]
|
||||
@@ -636,4 +639,18 @@ defmodule EnumTest.Others do
|
||||
test :count do
|
||||
assert Enum.count(URI.query_decoder("foo=bar&baz=bat")) == 2
|
||||
end
|
||||
|
||||
test :take do
|
||||
# Use IO to simulate side-effects
|
||||
reducible = fn(acc, fun) ->
|
||||
Enum.reduce([1, 2, 3], acc, fn(x, acc) ->
|
||||
IO.puts x
|
||||
fun.(x, acc)
|
||||
end)
|
||||
end
|
||||
|
||||
assert capture_io(fn ->
|
||||
Enum.take(reducible, 1)
|
||||
end) == "1\n"
|
||||
end
|
||||
end
|
||||
|
||||
@@ -34,15 +34,15 @@ defmodule Kernel.ExceptionTest do
|
||||
end
|
||||
|
||||
test :format_stacktrace_entry_with_fun do
|
||||
assert Exception.format_stacktrace_entry({fn(x) -> x end, [1], []}) =~ %r"\(1\)"
|
||||
assert Exception.format_stacktrace_entry({fn(x, y) -> { x, y } end, 2, []}) =~ %r"/2"
|
||||
assert Exception.format_stacktrace_entry({fn(x) -> x end, [1], []}) =~ %r/#Function<.+>\(1\)/
|
||||
assert Exception.format_stacktrace_entry({fn(x, y) -> { x, y } end, 2, []}) =~ %r"#Function<.+>/2"
|
||||
end
|
||||
|
||||
test :format_module_function_arity do
|
||||
assert Exception.format_module_fun_arity Foo, nil, 1 == "Foo.nil/1"
|
||||
assert Exception.format_module_fun_arity Foo, :bar, 1 == "Foo.bar/1"
|
||||
assert Exception.format_module_fun_arity Foo, :bar, [] == "Foo.bar()"
|
||||
assert Exception.format_module_fun_arity :foo, :bar, [1,2] == ":foo.bar(1, 2)"
|
||||
assert Exception.format_module_fun_arity :foo, :bar, [1, 2] == ":foo.bar(1, 2)"
|
||||
end
|
||||
|
||||
test :format_module_function_arity_with_special_function_name do
|
||||
|
||||
@@ -1,27 +1,14 @@
|
||||
Code.require_file "test_helper.exs", __DIR__
|
||||
|
||||
defmodule FileTest do
|
||||
import PathHelpers
|
||||
|
||||
use ExUnit.Case
|
||||
|
||||
import PathHelpers
|
||||
import Regex, only: [escape: 1]
|
||||
|
||||
defmodule Cp do
|
||||
use ExUnit.Case
|
||||
|
||||
# cp_r/c is managed in setup/teardown because if it is stored in
|
||||
# the repository, reltool can't build a release
|
||||
setup do
|
||||
src = fixture_path("cp_r")
|
||||
:file.make_symlink 'certainly/invalid', Path.join([src, "c"])
|
||||
:ok
|
||||
end
|
||||
|
||||
teardown do
|
||||
src = fixture_path("cp_r")
|
||||
File.rm Path.join([src, "c"])
|
||||
:ok
|
||||
end
|
||||
|
||||
test :cp_with_src_file_and_dest_file do
|
||||
src = fixture_path("file.txt")
|
||||
dest = tmp_path("sample.txt")
|
||||
@@ -186,13 +173,10 @@ defmodule FileTest do
|
||||
refute File.exists?(tmp_path("tmp/cp_r/b/3.txt"))
|
||||
|
||||
{ :ok, files } = File.cp_r(src, dest)
|
||||
assert length(files) == 8
|
||||
assert length(files) == 7
|
||||
assert tmp_path("tmp/cp_r/a") in files
|
||||
assert tmp_path("tmp/cp_r/c") in files
|
||||
assert tmp_path("tmp/cp_r/a/1.txt") in files
|
||||
|
||||
assert { :ok, 'certainly/invalid' } = :file.read_link(tmp_path("tmp/cp_r/c"))
|
||||
|
||||
assert File.exists?(tmp_path("tmp/cp_r/a/1.txt"))
|
||||
assert File.exists?(tmp_path("tmp/cp_r/a/a/2.txt"))
|
||||
assert File.exists?(tmp_path("tmp/cp_r/b/3.txt"))
|
||||
@@ -213,7 +197,7 @@ defmodule FileTest do
|
||||
refute File.exists?(tmp_path("tmp/b/3.txt"))
|
||||
|
||||
{ :ok, files } = File.cp_r(src, dest)
|
||||
assert length(files) == 8
|
||||
assert length(files) == 7
|
||||
|
||||
assert File.exists?(tmp_path("tmp/a/1.txt"))
|
||||
assert File.exists?(tmp_path("tmp/a/a/2.txt"))
|
||||
@@ -229,7 +213,7 @@ defmodule FileTest do
|
||||
|
||||
try do
|
||||
File.touch!(dest)
|
||||
assert File.cp_r(src, dest) == { :error, :enotdir }
|
||||
assert (File.cp_r(src, dest) |> is_io_error?)
|
||||
after
|
||||
File.rm_rf dest
|
||||
end
|
||||
@@ -245,7 +229,7 @@ defmodule FileTest do
|
||||
refute File.exists?(tmp_path("tmp/cp_r/b/3.txt"))
|
||||
|
||||
{ :ok, files } = File.cp_r(src, dest)
|
||||
assert length(files) == 8
|
||||
assert length(files) == 7
|
||||
|
||||
assert File.exists?(tmp_path("tmp/cp_r/a/1.txt"))
|
||||
assert File.exists?(tmp_path("tmp/cp_r/a/a/2.txt"))
|
||||
@@ -258,7 +242,7 @@ defmodule FileTest do
|
||||
test :cp_r_with_src_unknown do
|
||||
src = fixture_path("unknown")
|
||||
dest = tmp_path("tmp")
|
||||
assert File.cp_r(src, dest) == { :error, :enoent }
|
||||
assert File.cp_r(src, dest) == { :error, :enoent, src }
|
||||
end
|
||||
|
||||
test :cp_r_with_dir_and_file_conflict do
|
||||
@@ -268,7 +252,7 @@ defmodule FileTest do
|
||||
try do
|
||||
File.mkdir(dest)
|
||||
File.write!(Path.join(dest, "a"), "hello")
|
||||
assert File.cp_r(src, dest) == { :error, :enotdir }
|
||||
assert (File.cp_r(src, dest) |> is_io_error?)
|
||||
after
|
||||
File.rm_rf dest
|
||||
end
|
||||
@@ -286,7 +270,7 @@ defmodule FileTest do
|
||||
refute File.exists?(tmp_path("tmp/b/3.txt"))
|
||||
|
||||
{ :ok, files } = File.cp_r(src, dest)
|
||||
assert length(files) == 8
|
||||
assert length(files) == 7
|
||||
assert is_list(hd(files))
|
||||
|
||||
assert File.exists?(tmp_path("tmp/a/1.txt"))
|
||||
@@ -344,7 +328,7 @@ defmodule FileTest do
|
||||
refute File.exists?(tmp_path("tmp/a/a/2.txt"))
|
||||
refute File.exists?(tmp_path("tmp/b/3.txt"))
|
||||
|
||||
assert length(File.cp_r!(src, dest)) == 8
|
||||
assert length(File.cp_r!(src, dest)) == 7
|
||||
|
||||
assert File.exists?(tmp_path("tmp/a/1.txt"))
|
||||
assert File.exists?(tmp_path("tmp/a/a/2.txt"))
|
||||
@@ -357,7 +341,7 @@ defmodule FileTest do
|
||||
test :cp_r_with_src_unknown! do
|
||||
src = fixture_path("unknown")
|
||||
dest = tmp_path("tmp")
|
||||
assert_raise File.CopyError, "could not copy recursively from #{src} to #{dest}: no such file or directory", fn ->
|
||||
assert_raise File.CopyError, "could not copy recursively from #{src} to #{dest}. #{src}: no such file or directory", fn ->
|
||||
File.cp_r!(src, dest)
|
||||
end
|
||||
end
|
||||
@@ -369,13 +353,17 @@ defmodule FileTest do
|
||||
File.Stat[mode: src_mode] = File.stat! src
|
||||
File.Stat[mode: dest_mode] = File.stat! dest
|
||||
assert src_mode == dest_mode
|
||||
# on overwrite
|
||||
File.cp! src, dest, fn(_,_) -> true end
|
||||
|
||||
# On overwrite
|
||||
File.cp! src, dest, fn(_, _) -> true end
|
||||
File.Stat[mode: src_mode] = File.stat! src
|
||||
File.Stat[mode: dest_mode] = File.stat! dest
|
||||
assert src_mode == dest_mode
|
||||
end
|
||||
|
||||
defp is_io_error?(result) do
|
||||
elem(result, 1) in [:enotdir, :eio, :enoent, :eisdir]
|
||||
end
|
||||
end
|
||||
|
||||
defmodule Queries do
|
||||
@@ -525,13 +513,13 @@ defmodule FileTest do
|
||||
|
||||
test :open_utf8_and_charlist do
|
||||
{ :ok, file } = File.open(fixture_path("utf8.txt"), [:charlist, :utf8])
|
||||
assert IO.gets(file, "") == [1056,1091,1089,1089,1082,1080,1081,10]
|
||||
assert IO.gets(file, "") == [1056, 1091, 1089, 1089, 1082, 1080, 1081, 10]
|
||||
assert File.close(file) == :ok
|
||||
end
|
||||
|
||||
test :open_respects_encoding do
|
||||
{ :ok, file } = File.open(fixture_path("utf8.txt"), [{:encoding, :latin1}])
|
||||
assert IO.gets(file, "") == <<195,144,194,160,195,145,194,131,195,145,194,129,195,145,194,129,195,144,194,186,195,144,194,184,195,144,194,185,10>>
|
||||
assert IO.gets(file, "") == <<195, 144, 194, 160, 195, 145, 194, 131, 195, 145, 194, 129, 195, 145, 194, 129, 195, 144, 194, 186, 195, 144, 194, 184, 195, 144, 194, 185, 10>>
|
||||
assert File.close(file) == :ok
|
||||
end
|
||||
|
||||
@@ -586,7 +574,7 @@ defmodule FileTest do
|
||||
fixture = fixture_path("file.txt")
|
||||
invalid = Path.join fixture, "test"
|
||||
assert File.exists?(fixture)
|
||||
assert File.mkdir(invalid) == { :error, :enotdir }
|
||||
assert (File.mkdir(invalid) |> is_io_error?)
|
||||
refute File.exists?(invalid)
|
||||
end
|
||||
|
||||
@@ -605,7 +593,7 @@ defmodule FileTest do
|
||||
fixture = fixture_path("file.txt")
|
||||
invalid = Path.join fixture, "test"
|
||||
assert File.exists?(fixture)
|
||||
assert_raise File.Error, "could not make directory #{invalid}: not a directory", fn ->
|
||||
assert_raise File.Error, %r"^could not make directory #{escape invalid}: (not a directory|no such file or directory)", fn ->
|
||||
File.mkdir!(invalid)
|
||||
end
|
||||
end
|
||||
@@ -667,7 +655,7 @@ defmodule FileTest do
|
||||
test :mkdir_p_with_invalid_path do
|
||||
assert File.exists?(fixture_path("file.txt"))
|
||||
invalid = Path.join fixture_path("file.txt"), "test/foo"
|
||||
assert File.mkdir(invalid) == { :error, :enotdir }
|
||||
assert (File.mkdir(invalid) |> is_io_error?)
|
||||
refute File.exists?(invalid)
|
||||
end
|
||||
|
||||
@@ -686,29 +674,21 @@ defmodule FileTest do
|
||||
fixture = fixture_path("file.txt")
|
||||
invalid = Path.join fixture, "test"
|
||||
assert File.exists?(fixture)
|
||||
assert_raise File.Error, "could not make directory (with -p) #{invalid}: not a directory", fn ->
|
||||
assert_raise File.Error, %r"^could not make directory \(with -p\) #{escape invalid}: (not a directory|no such file or directory)", fn ->
|
||||
File.mkdir_p!(invalid)
|
||||
end
|
||||
end
|
||||
|
||||
defp is_io_error?(result) do
|
||||
{:error,errorcode} = result
|
||||
errorcode in [:enotdir, :eio, :enoent, :eisdir]
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
defmodule Rm do
|
||||
use ExUnit.Case
|
||||
|
||||
# cp_r/c is managed in setup/teardown because if it is stored in
|
||||
# the repository, reltool can't build a release
|
||||
setup do
|
||||
src = fixture_path("cp_r")
|
||||
:file.make_symlink 'certainly/invalid', Path.join([src, "c"])
|
||||
:ok
|
||||
end
|
||||
|
||||
teardown do
|
||||
src = fixture_path("cp_r")
|
||||
File.rm Path.join([src, "c"])
|
||||
:ok
|
||||
end
|
||||
|
||||
test :rm_file do
|
||||
fixture = tmp_path("tmp_test.txt")
|
||||
File.write(fixture, "test")
|
||||
@@ -748,7 +728,7 @@ defmodule FileTest do
|
||||
end
|
||||
|
||||
test :rmdir_with_file do
|
||||
assert File.rmdir(fixture_path("file.txt")) == { :error, :enotdir }
|
||||
assert (File.rmdir(fixture_path("file.txt")) |> is_io_error?)
|
||||
end
|
||||
|
||||
test :rmdir! do
|
||||
@@ -761,7 +741,7 @@ defmodule FileTest do
|
||||
|
||||
test :rmdir_with_file! do
|
||||
fixture = fixture_path("file.txt")
|
||||
assert_raise File.Error, "could not remove directory #{fixture}: not a directory", fn ->
|
||||
assert_raise File.Error, %r"^could not remove directory #{escape fixture}: (not a directory|I/O error)", fn ->
|
||||
File.rmdir!(fixture)
|
||||
end
|
||||
end
|
||||
@@ -776,7 +756,7 @@ defmodule FileTest do
|
||||
assert File.exists?(tmp_path("tmp/b/3.txt"))
|
||||
|
||||
{ :ok, files } = File.rm_rf(fixture)
|
||||
assert length(files) == 8
|
||||
assert length(files) == 7
|
||||
assert fixture in files
|
||||
assert tmp_path("tmp/a/1.txt") in files
|
||||
|
||||
@@ -810,7 +790,7 @@ defmodule FileTest do
|
||||
assert File.exists?(tmp_path("tmp/b/3.txt"))
|
||||
|
||||
{ :ok, files } = File.rm_rf(fixture)
|
||||
assert length(files) == 8
|
||||
assert length(files) == 7
|
||||
assert fixture in files
|
||||
assert (tmp_path("tmp/a/1.txt") |> to_char_list) in files
|
||||
|
||||
@@ -833,7 +813,7 @@ defmodule FileTest do
|
||||
|
||||
test :rm_rf_with_invalid do
|
||||
fixture = fixture_path "file.txt/path"
|
||||
assert File.rm_rf(fixture) == { :error, :enotdir }
|
||||
assert File.rm_rf(fixture) == { :ok, [] }
|
||||
end
|
||||
|
||||
test :rm_rf! do
|
||||
@@ -846,7 +826,7 @@ defmodule FileTest do
|
||||
assert File.exists?(tmp_path("tmp/b/3.txt"))
|
||||
|
||||
files = File.rm_rf!(fixture)
|
||||
assert length(files) == 8
|
||||
assert length(files) == 7
|
||||
assert fixture in files
|
||||
assert tmp_path("tmp/a/1.txt") in files
|
||||
|
||||
@@ -858,9 +838,11 @@ defmodule FileTest do
|
||||
|
||||
test :rm_rf_with_invalid! do
|
||||
fixture = fixture_path "file.txt/path"
|
||||
assert_raise File.Error, "could not remove files and directories recursively from #{fixture}: not a directory", fn ->
|
||||
File.rm_rf!(fixture)
|
||||
end
|
||||
assert File.rm_rf!(fixture) == []
|
||||
end
|
||||
|
||||
defp is_io_error?(result) do
|
||||
elem(result, 1) in [:enotdir, :eio, :enoent, :eisdir]
|
||||
end
|
||||
end
|
||||
|
||||
@@ -905,7 +887,7 @@ defmodule FileTest do
|
||||
dest = tmp_path("tmp_test.txt")
|
||||
|
||||
try do
|
||||
{ :ok, iterator } = File.iterator(src)
|
||||
iterator = File.iterator!(src)
|
||||
File.open dest, [:write], fn(target) ->
|
||||
Enum.each iterator, fn(line) ->
|
||||
IO.write target, Regex.replace(%r/"/, line, "'")
|
||||
@@ -934,12 +916,6 @@ defmodule FileTest do
|
||||
end
|
||||
end
|
||||
|
||||
test :iterator_can_be_closed do
|
||||
src = fixture_path("file.txt")
|
||||
iterator = File.iterator!(src)
|
||||
assert File.close(iterator) == :ok
|
||||
end
|
||||
|
||||
test :biniterator do
|
||||
src = File.open! fixture_path("file.txt")
|
||||
dest = tmp_path("tmp_test.txt")
|
||||
@@ -962,7 +938,7 @@ defmodule FileTest do
|
||||
dest = tmp_path("tmp_test.txt")
|
||||
|
||||
try do
|
||||
{ :ok, iterator } = File.biniterator(src)
|
||||
iterator = File.biniterator!(src)
|
||||
File.open dest, [:write], fn(target) ->
|
||||
Enum.each iterator, fn(line) ->
|
||||
IO.write target, Regex.replace(%r/"/, line, "'")
|
||||
@@ -991,12 +967,6 @@ defmodule FileTest do
|
||||
end
|
||||
end
|
||||
|
||||
test :biniterator_can_be_closed do
|
||||
src = fixture_path("file.txt")
|
||||
iterator = File.biniterator!(src)
|
||||
assert File.close(iterator) == :ok
|
||||
end
|
||||
|
||||
test :copy do
|
||||
src = fixture_path("file.txt")
|
||||
dest = tmp_path("tmp_test.txt")
|
||||
@@ -1069,20 +1039,24 @@ defmodule FileTest do
|
||||
end
|
||||
end
|
||||
|
||||
test :cwd_and_cd_with_utf8 do
|
||||
if :file.native_name_encoding == :utf8 do
|
||||
File.cd!(fixture_path("héllò"), fn ->
|
||||
if :file.native_name_encoding == :utf8 do
|
||||
test :cwd_and_cd_with_utf8 do
|
||||
File.mkdir_p(tmp_path("héllò"))
|
||||
|
||||
File.cd!(tmp_path("héllò"), fn ->
|
||||
assert Path.basename(File.cwd!) == "héllò"
|
||||
end)
|
||||
after
|
||||
File.rm_rf tmp_path("héllò")
|
||||
end
|
||||
end
|
||||
|
||||
test :invalid_cd do
|
||||
assert File.cd(fixture_path("file.txt")) == { :error, :enotdir }
|
||||
assert(File.cd(fixture_path("file.txt")) |> is_io_error?)
|
||||
end
|
||||
|
||||
test :invalid_cd! do
|
||||
message = "could not set current working directory to #{fixture_path("file.txt")}: not a directory"
|
||||
message = %r"^could not set current working directory to #{escape fixture_path("file.txt")}: (not a directory|no such file or directory)"
|
||||
assert_raise File.Error, message, fn ->
|
||||
File.cd!(fixture_path("file.txt"))
|
||||
end
|
||||
@@ -1127,7 +1101,7 @@ defmodule FileTest do
|
||||
|
||||
test :touch_with_failure do
|
||||
fixture = fixture_path("file.txt/bar")
|
||||
assert File.touch(fixture) == { :error, :enotdir }
|
||||
assert (File.touch(fixture) |> is_io_error?)
|
||||
end
|
||||
|
||||
test :touch_with_success! do
|
||||
@@ -1136,16 +1110,107 @@ defmodule FileTest do
|
||||
|
||||
test :touch_with_failure! do
|
||||
fixture = fixture_path("file.txt/bar")
|
||||
assert_raise File.Error, "could not touch #{fixture}: not a directory", fn ->
|
||||
assert_raise File.Error, %r"could not touch #{escape fixture}: (not a directory|no such file or directory)", fn ->
|
||||
File.touch!(fixture)
|
||||
end
|
||||
end
|
||||
|
||||
test :chmod_with_success do
|
||||
fixture = tmp_path("tmp_test.txt")
|
||||
|
||||
File.touch(fixture)
|
||||
try do
|
||||
assert File.chmod(fixture, 0100666) == :ok
|
||||
stat = File.stat!(fixture)
|
||||
assert stat.mode == 0100666
|
||||
|
||||
assert File.chmod(fixture, 0100777) == :ok
|
||||
stat = File.stat!(fixture)
|
||||
assert stat.mode == 0100777
|
||||
after
|
||||
File.rm(fixture)
|
||||
end
|
||||
end
|
||||
|
||||
test :chmod_with_success! do
|
||||
fixture = tmp_path("tmp_test.txt")
|
||||
|
||||
File.touch(fixture)
|
||||
try do
|
||||
assert File.chmod!(fixture, 0100666) == :ok
|
||||
stat = File.stat!(fixture)
|
||||
assert stat.mode == 0100666
|
||||
|
||||
assert File.chmod!(fixture, 0100777) == :ok
|
||||
stat = File.stat!(fixture)
|
||||
assert stat.mode == 0100777
|
||||
after
|
||||
File.rm(fixture)
|
||||
end
|
||||
end
|
||||
|
||||
test :chmod_with_failue do
|
||||
fixture = tmp_path("tmp_test.txt")
|
||||
File.rm(fixture)
|
||||
|
||||
assert File.chmod(fixture, 0100777) == {:error,:enoent}
|
||||
end
|
||||
|
||||
test :chmod_with_failue! do
|
||||
fixture = tmp_path("tmp_test.txt")
|
||||
File.rm(fixture)
|
||||
|
||||
message = %r"could not change mode for #{escape fixture}: no such file or directory"
|
||||
assert_raise File.Error, message, fn ->
|
||||
File.chmod!(fixture, 0100777)
|
||||
end
|
||||
end
|
||||
|
||||
test :chgrp_with_failue do
|
||||
fixture = tmp_path("tmp_test.txt")
|
||||
File.rm(fixture)
|
||||
|
||||
assert File.chgrp(fixture, 1) == {:error,:enoent}
|
||||
end
|
||||
|
||||
test :chgrp_with_failue! do
|
||||
fixture = tmp_path("tmp_test.txt")
|
||||
File.rm(fixture)
|
||||
|
||||
message = %r"could not change group for #{escape fixture}: no such file or directory"
|
||||
assert_raise File.Error, message, fn ->
|
||||
File.chgrp!(fixture, 1)
|
||||
end
|
||||
end
|
||||
|
||||
test :chown_with_failue do
|
||||
fixture = tmp_path("tmp_test.txt")
|
||||
File.rm(fixture)
|
||||
|
||||
assert File.chown(fixture, 1) == {:error,:enoent}
|
||||
end
|
||||
|
||||
test :chown_with_failue! do
|
||||
fixture = tmp_path("tmp_test.txt")
|
||||
File.rm(fixture)
|
||||
|
||||
message = %r"could not change owner for #{escape fixture}: no such file or directory"
|
||||
assert_raise File.Error, message, fn ->
|
||||
File.chown!(fixture, 1)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
defp last_year do
|
||||
last_year :calendar.local_time
|
||||
end
|
||||
|
||||
defp last_year({ { year, month, day },time }) do
|
||||
defp last_year({ { year, month, day }, time }) do
|
||||
{ { year - 1, month, day }, time }
|
||||
end
|
||||
|
||||
defp is_io_error?(result) do
|
||||
{:error,errorcode} = result
|
||||
errorcode in [:enotdir, :eio, :enoent, :eisdir]
|
||||
end
|
||||
end
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
defmodule WarningsSample do
|
||||
def hello(a), do: a
|
||||
def hello(b), do: b
|
||||
end
|
||||
@@ -28,8 +28,8 @@ defmodule GenEvent.BehaviourTest do
|
||||
|
||||
:gen_event.notify(pid, {:notification, 1})
|
||||
:gen_event.notify(pid, {:notification, 2})
|
||||
|
||||
assert :gen_event.call(pid, MyEventHandler, :notifications) == [1,2]
|
||||
|
||||
assert :gen_event.call(pid, MyEventHandler, :notifications) == [1, 2]
|
||||
assert :gen_event.call(pid, MyEventHandler, :notifications) == []
|
||||
end
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user