Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8d543cd122 | ||
|
|
01a575eb2c | ||
|
|
7040851a62 | ||
|
|
d857b7b3f5 | ||
|
|
e88ef6343e | ||
|
|
9ddf626d51 | ||
|
|
8dde5bee21 | ||
|
|
05e52f9437 | ||
|
|
30d9699153 | ||
|
|
ca3cbf7e17 | ||
|
|
c94f5fded6 | ||
|
|
7be4b95b5c | ||
|
|
f9fc540e2d | ||
|
|
f3bdcb49cb | ||
|
|
c229bd9239 | ||
|
|
ffc370e53d | ||
|
|
8d2feae582 | ||
|
|
6d62e473aa | ||
|
|
188a7b23cc | ||
|
|
0252a4f812 | ||
|
|
1fe5cad980 | ||
|
|
7fa6576f38 | ||
|
|
7685dbca06 | ||
|
|
7381c5152f | ||
|
|
8b601c2e54 | ||
|
|
c6d37b9b76 | ||
|
|
2841137382 | ||
|
|
a7a2178c6a | ||
|
|
f994bf46aa | ||
|
|
4be70753c3 | ||
|
|
6c319466f5 | ||
|
|
6c130d77ad | ||
|
|
25c24a7ec3 | ||
|
|
329b49e9ee | ||
|
|
b5331dd7c7 | ||
|
|
8f520e9735 | ||
|
|
c611b397c0 | ||
|
|
bbd8f5ad3e | ||
|
|
cd5dacc612 | ||
|
|
9aba2c1260 | ||
|
|
cfc5d012ba | ||
|
|
d61c930d5f | ||
|
|
042428098a | ||
|
|
8881d5c545 | ||
|
|
4a08bc8276 | ||
|
|
10eb8f4963 | ||
|
|
1539de9d9a | ||
|
|
104f64f8eb | ||
|
|
83a078da08 | ||
|
|
cf80551cc3 | ||
|
|
a36816f1aa | ||
|
|
0f1ac4ec54 | ||
|
|
9936d93c74 | ||
|
|
cd009eaf32 | ||
|
|
b731598d75 | ||
|
|
578761428e | ||
|
|
d9d642686d | ||
|
|
73eabd8b21 | ||
|
|
47a82fc71c | ||
|
|
c698c1ef11 | ||
|
|
dffea0f299 | ||
|
|
db32e550c4 | ||
|
|
c6c3703053 | ||
|
|
ec59659805 | ||
|
|
336ff3ba8d | ||
|
|
b481d14732 | ||
|
|
e344b6b44c | ||
|
|
120cbd4876 | ||
|
|
e730bd6d0a | ||
|
|
eadbf16fa9 | ||
|
|
1508222d85 | ||
|
|
fb255e4f82 | ||
|
|
63e954d32b | ||
|
|
5c357e5cff | ||
|
|
e5be7e0149 | ||
|
|
68fa9d818a | ||
|
|
2992669e17 | ||
|
|
85df13db77 | ||
|
|
814a2d525a | ||
|
|
be2bffb320 | ||
|
|
99965ab804 | ||
|
|
30e86ccf07 | ||
|
|
852e846a47 | ||
|
|
1cd5613ac6 | ||
|
|
5f888b71ca | ||
|
|
05fb269b5b | ||
|
|
f69c2e80fb | ||
|
|
e428227d7e | ||
|
|
512ab80f81 | ||
|
|
138dc1d42a | ||
|
|
c4c723f458 | ||
|
|
a9cf7a0215 | ||
|
|
b93adc78b8 | ||
|
|
72dd2507b0 | ||
|
|
895b50afb9 | ||
|
|
3a722d58aa | ||
|
|
e3c2368c7b | ||
|
|
754d8c4f27 | ||
|
|
1aa4f2b31a | ||
|
|
c3e5380a98 | ||
|
|
25aa76df5f | ||
|
|
523127cde8 | ||
|
|
152c784a07 | ||
|
|
10d021f1cb | ||
|
|
14bf6c84f7 | ||
|
|
5c6bf0856c | ||
|
|
fa5c3a2dea | ||
|
|
fa449e842a | ||
|
|
bdbc9f38e0 | ||
|
|
20d96a0d77 | ||
|
|
4775d60d00 | ||
|
|
15defaab04 | ||
|
|
bcee404edb | ||
|
|
576492ae48 | ||
|
|
4e83a15dee | ||
|
|
117d09509c | ||
|
|
7d1ef13f17 | ||
|
|
6a7602d974 | ||
|
|
1077e64126 | ||
|
|
61a27d1be6 | ||
|
|
95e1faae56 | ||
|
|
d35d0f41c8 | ||
|
|
661ef4377b | ||
|
|
4d715a3a41 | ||
|
|
175e90d6af | ||
|
|
af167fbd11 | ||
|
|
cc99da19dc | ||
|
|
94d5c92d34 | ||
|
|
efd161d145 | ||
|
|
9a6d15af53 | ||
|
|
42c1915ad3 | ||
|
|
49501ef59e | ||
|
|
f6d869cb96 | ||
|
|
90650b334e | ||
|
|
d984f4b545 | ||
|
|
7d98ae18f9 | ||
|
|
390f194b24 | ||
|
|
ec3c485f93 | ||
|
|
504dd24934 | ||
|
|
ea1b07e81e | ||
|
|
4880dfe424 | ||
|
|
db89ecfc7d | ||
|
|
b46f45b940 | ||
|
|
b74e67dd37 | ||
|
|
117f0b0ea8 | ||
|
|
c9756b55f2 | ||
|
|
42c39ed93c | ||
|
|
ea01976063 | ||
|
|
bc817b511b | ||
|
|
3e1bd2bfaa | ||
|
|
8736dc61eb | ||
|
|
845628df66 | ||
|
|
ba17e8ac78 | ||
|
|
f80ec44205 | ||
|
|
9c4c9e8bb0 | ||
|
|
f3f39927ac | ||
|
|
bfe81a1d9e | ||
|
|
e9c4ea2617 | ||
|
|
742486d112 | ||
|
|
a421a73abf | ||
|
|
f7bfdf9ac7 | ||
|
|
5a9e55f6e5 | ||
|
|
bfddbd726a | ||
|
|
c524f43860 | ||
|
|
4b960a2114 | ||
|
|
c3d5611cc2 | ||
|
|
fea9455eb7 | ||
|
|
806e7bdd4d | ||
|
|
fc3aa85d81 | ||
|
|
6c5b3134af | ||
|
|
3fed236607 | ||
|
|
29fe257442 | ||
|
|
b15394ce22 | ||
|
|
e3f13c6732 | ||
|
|
dca16fe966 | ||
|
|
7bef3ae021 | ||
|
|
c186bb19df | ||
|
|
ad50731395 | ||
|
|
cfe9b52485 | ||
|
|
0fdb57799c | ||
|
|
f8f6b8eb4c | ||
|
|
2c396b9cc1 | ||
|
|
cdbfeaf67e | ||
|
|
15fa33a6dc | ||
|
|
98631ada7d | ||
|
|
b8c2705ea1 | ||
|
|
35c8a17e8d | ||
|
|
23cd4ce28c | ||
|
|
bbd7960d78 | ||
|
|
7d5be8d093 | ||
|
|
d2f2d4ba20 | ||
|
|
7be9ba8585 | ||
|
|
28ecab3dde | ||
|
|
f756ce6077 | ||
|
|
f491ebb976 | ||
|
|
a5a2025daf | ||
|
|
92fa624892 | ||
|
|
b1be0d1ebe | ||
|
|
e4176725ab | ||
|
|
cea5a61ea2 | ||
|
|
a458e0e43c | ||
|
|
4c099d6fc7 | ||
|
|
72b3548c86 | ||
|
|
4f650b90df | ||
|
|
ab504ce9fe | ||
|
|
b5022828ff | ||
|
|
59c38454c7 | ||
|
|
74851be5f9 | ||
|
|
aa8bd9e021 | ||
|
|
af6c593898 | ||
|
|
09cd916ad4 | ||
|
|
bb6562391e | ||
|
|
139b203da2 | ||
|
|
770ac3249e | ||
|
|
e80286ce57 | ||
|
|
1b4603160c | ||
|
|
2208998f50 | ||
|
|
a75b572134 | ||
|
|
adcb8855d0 | ||
|
|
d216dbbc1d | ||
|
|
ae4bbed5d0 | ||
|
|
0252cd8b85 | ||
|
|
01009507ab | ||
|
|
df2254f8a3 | ||
|
|
a123ea29f7 | ||
|
|
87ea150b81 | ||
|
|
daf731dfe1 | ||
|
|
4507f61cbc | ||
|
|
697e7ea5ac | ||
|
|
439971e5b1 | ||
|
|
b432e19149 | ||
|
|
a4369dcbbb | ||
|
|
28de003aab | ||
|
|
40cdb6494d | ||
|
|
3642a2a799 | ||
|
|
978c512a3f | ||
|
|
b74615e559 | ||
|
|
7728f3d630 | ||
|
|
5ea95d3048 | ||
|
|
d59f2f9e16 | ||
|
|
a4ab7ee75c | ||
|
|
f20854e00e | ||
|
|
1e80f6d06e | ||
|
|
acc4b8e6fe | ||
|
|
95ff052517 | ||
|
|
cb51b92e2a | ||
|
|
b7bd24c30e | ||
|
|
0f96f406f4 | ||
|
|
7c6f78206e | ||
|
|
8e20f6b701 | ||
|
|
7250be7f1a | ||
|
|
7e068aca85 | ||
|
|
b2b3d3a97e | ||
|
|
062813c8f3 | ||
|
|
3f82624d0f | ||
|
|
b292d94739 | ||
|
|
8d973d08ed | ||
|
|
d5031b6347 | ||
|
|
ebe7f6d226 | ||
|
|
fe6bcad330 | ||
|
|
c2ebb620ff | ||
|
|
77d018758b | ||
|
|
6182602f12 | ||
|
|
e216b8a073 | ||
|
|
e21f9f144c | ||
|
|
a377687a90 | ||
|
|
4ed3f28285 | ||
|
|
8bca0ba86d | ||
|
|
fa6f13ce0c | ||
|
|
97c7e1282f | ||
|
|
f913e58080 | ||
|
|
b0a430e93c | ||
|
|
cf3ba5a669 | ||
|
|
b74447b58a | ||
|
|
ff1c2c0c46 | ||
|
|
ac3a697889 | ||
|
|
93cee4d85a | ||
|
|
a241169d81 | ||
|
|
4aefd13ad3 | ||
|
|
2305e65a63 | ||
|
|
503b0fe9da | ||
|
|
4a3efad86b | ||
|
|
868e44daca | ||
|
|
bcf8bbb37b | ||
|
|
b62ab9d541 | ||
|
|
ab61e6f95c | ||
|
|
3b15228414 | ||
|
|
119a87b2ab | ||
|
|
fb07d5e93c | ||
|
|
0872515373 | ||
|
|
c79caec547 | ||
|
|
e271da2d82 | ||
|
|
6ba4154d40 | ||
|
|
539f8252f8 | ||
|
|
86372c11c4 | ||
|
|
d20d282fae | ||
|
|
5fb2f0135e | ||
|
|
610d34c314 | ||
|
|
2244d2baf2 | ||
|
|
ae1bf939a6 | ||
|
|
363d8f3dc0 | ||
|
|
c919b3fa00 | ||
|
|
7fc69c1433 | ||
|
|
d8dd801ec8 | ||
|
|
272956067b | ||
|
|
27c0c489e3 | ||
|
|
0a1058515c | ||
|
|
764f6623f3 | ||
|
|
1f049dbac0 | ||
|
|
17b6edbe51 | ||
|
|
3b066b8563 | ||
|
|
037492b7a2 | ||
|
|
32622fd6a0 | ||
|
|
192098ce3c | ||
|
|
f7652b88e3 | ||
|
|
3a744f1377 | ||
|
|
b52a7ff37d | ||
|
|
d4b8156a11 | ||
|
|
c79b08b626 | ||
|
|
f3df54a913 | ||
|
|
11885babbe | ||
|
|
d67d6eec9c | ||
|
|
40c42d9569 | ||
|
|
fca3b47dc1 | ||
|
|
cd0fb66096 | ||
|
|
4a2685a165 | ||
|
|
6bcd52ded0 | ||
|
|
b3dea620b1 | ||
|
|
32c0c5dd8a | ||
|
|
8c40d27725 | ||
|
|
b4324ce9fd | ||
|
|
3c66c4597b | ||
|
|
bc4ad4e4da | ||
|
|
6917db44b6 | ||
|
|
3e5cccb74b | ||
|
|
d0960ae517 | ||
|
|
bc71395042 | ||
|
|
837a15363d | ||
|
|
058f0f6696 | ||
|
|
b19244ba28 | ||
|
|
a714b5ff1e | ||
|
|
56061b9d93 | ||
|
|
1e8aed1da1 | ||
|
|
6cf976be99 | ||
|
|
bafe7be1dc | ||
|
|
7fbe263ba0 | ||
|
|
9facc5a286 | ||
|
|
080beffc2c | ||
|
|
1781d5a141 | ||
|
|
925641c3f2 | ||
|
|
edfd64d853 | ||
|
|
bd9a72f748 | ||
|
|
6c7d9ba3af | ||
|
|
9866c0200d | ||
|
|
7260bc84c7 | ||
|
|
3e64b7ddeb | ||
|
|
e51bc9e5aa | ||
|
|
08ebd47582 | ||
|
|
893f012ffe | ||
|
|
67a906083e | ||
|
|
8602486c6d | ||
|
|
c01cedb822 | ||
|
|
9b3568aba9 | ||
|
|
6440c2dacf | ||
|
|
1c637792dc | ||
|
|
bb196c9c7c | ||
|
|
e4ec917d39 | ||
|
|
a7a6dd327b | ||
|
|
8f8fd13f3b | ||
|
|
e8bcb0ebea | ||
|
|
4d6920a470 | ||
|
|
225b24ea4f | ||
|
|
5b2751e094 | ||
|
|
daea0b44ca | ||
|
|
763771d023 | ||
|
|
9327689ebe | ||
|
|
e3996d2c43 | ||
|
|
4393c1a41c | ||
|
|
fdd469c6df | ||
|
|
c8d2a0b878 | ||
|
|
b959e1d117 | ||
|
|
c4cbc5a7f3 | ||
|
|
5156bfa47d | ||
|
|
28de080999 | ||
|
|
a74c6fe8cb | ||
|
|
4399e21ce3 | ||
|
|
68f1de4911 | ||
|
|
544e49d5a1 | ||
|
|
1b32a87431 | ||
|
|
9afc59d084 | ||
|
|
44fda96939 | ||
|
|
c78ce512be | ||
|
|
8ef0ef782c | ||
|
|
8deff9dac3 | ||
|
|
a9a5b4e086 | ||
|
|
91c7ab72d5 | ||
|
|
ec19e2b009 | ||
|
|
7428ccb9ca | ||
|
|
97d9db2a8f | ||
|
|
adf7df4f3b | ||
|
|
33e290d197 | ||
|
|
87c69433dc | ||
|
|
ca67bfe64c | ||
|
|
10adf310bc | ||
|
|
90929bd08e | ||
|
|
f6e139dc18 | ||
|
|
8cefa744b8 | ||
|
|
b15727dd42 | ||
|
|
37ea3965da | ||
|
|
9220637a3c | ||
|
|
9d596a1f39 | ||
|
|
646ee5f125 | ||
|
|
fc0e327625 | ||
|
|
b02f19dc81 | ||
|
|
1fb409fdd0 | ||
|
|
3352b07275 | ||
|
|
c926b7f943 | ||
|
|
9947f6a405 | ||
|
|
1acad9e5fc | ||
|
|
5710630e2f | ||
|
|
2973c34224 | ||
|
|
198a3f6291 | ||
|
|
9eb040d49b | ||
|
|
524720666e | ||
|
|
27ad9423f8 | ||
|
|
8ecf7b718d | ||
|
|
243a32f2d1 | ||
|
|
821e668361 | ||
|
|
fbaa46618c | ||
|
|
1b811d3b72 | ||
|
|
488066d4cd | ||
|
|
16843c7c39 | ||
|
|
a1cb84d2cc | ||
|
|
8b814ded29 | ||
|
|
fabfab8964 | ||
|
|
fb8b6f53f2 | ||
|
|
aaf6c91e0e | ||
|
|
088eff4c19 | ||
|
|
2714642b1e | ||
|
|
c0e804d37f | ||
|
|
b3e49a183d | ||
|
|
1fb81138be | ||
|
|
b08cdc8ec7 | ||
|
|
9af9a797f7 | ||
|
|
cb18ab0a5d | ||
|
|
b685ded72d | ||
|
|
40ca580d37 | ||
|
|
0048852114 | ||
|
|
e59199907a | ||
|
|
2d3386cedf | ||
|
|
9e675c85cd | ||
|
|
3006bae683 | ||
|
|
67f6e1c9b5 | ||
|
|
377006540f |
+12
-10
@@ -1,11 +1,13 @@
|
||||
/erl_crash.dump
|
||||
/src/*_lexer.erl
|
||||
/src/*_parser.erl
|
||||
/test/ebin
|
||||
/test/tmp
|
||||
/test/elixir/tmp
|
||||
/ebin/*
|
||||
/exbin/*
|
||||
/src/*rl.old
|
||||
/.eunit/*
|
||||
/deps/*
|
||||
/.full
|
||||
/lib/*/ebin/*
|
||||
/lib/*/tmp
|
||||
/lib/*/test/tmp
|
||||
/lib/elixir/src/elixir.app.src
|
||||
/lib/elixir/src/*_lexer.erl
|
||||
/lib/elixir/src/*_parser.erl
|
||||
/lib/elixir/test/ebin
|
||||
/deps/*
|
||||
/ebin
|
||||
/rel/elixir
|
||||
erl_crash.dump
|
||||
+2
-1
@@ -1,8 +1,9 @@
|
||||
language: erlang
|
||||
script: "make compile docs test"
|
||||
script: "make compile && make .full test"
|
||||
notifications:
|
||||
irc: "irc.freenode.org#elixir-lang"
|
||||
recipients:
|
||||
- jose.valim@plataformatec.com.br
|
||||
otp_release:
|
||||
- R15B01
|
||||
- R15B
|
||||
|
||||
@@ -1,3 +1,53 @@
|
||||
# v0.6.0 (2012-08-01)
|
||||
|
||||
* incompatible changes
|
||||
* [Kernel] Compiled files now follow `Elixir-ModuleName` convention to solve issues with Erlang embedded mode. This removes the `__MAIN__` pseudo-variable as modules are now located inside `Elixir` namespace;
|
||||
* [Kernel] `__using__` callback triggered by `use` now receives just one argument. Caller information can be accessed via macros using `__CALLER__`;
|
||||
* [Module] Removed data functions in favor of unifying the attributes API;
|
||||
* [Kernel] Comprehensions syntax changed to be more compatible with Erlang behavior;
|
||||
* [Kernel] loop and recur were removed in favor of recursion with named functions;
|
||||
|
||||
* deprecations
|
||||
* [Access] The semantics of the access protocol were reduced from a broad query API to simple data structure key-based access;
|
||||
* [Module] `Module.add_compile_callback(module, target, callback)` was deprecated in favor of `Module.add_attribute(module, :before_compile, { target, callback })`;
|
||||
* [Module] `Module.function_defined?` was deprecated in favor of `Module.defines?`;
|
||||
* [Module] `Module.defined_functions` was deprecated in favor of `Module.definitions_in`;
|
||||
* [File] `File.read_info` was deprecated in favor of `File.stat`;
|
||||
* [IO] `IO.print` was deprecated in favor of `IO.write`;
|
||||
* [Kernel] Deprecated `__LINE__` and `__FUNCTION__` in favor of `__ENV__.line` and `__ENV__.function`;
|
||||
* [Kernel] Deprecated `in_guard` in favor of `__CALLER__.in_guard?`;
|
||||
* [Kernel] `refer` is deprecated in favor of `alias`;
|
||||
* [ExUnit] Some assertions were deprecated in favor of simply using `assert()`;
|
||||
|
||||
* enhancements
|
||||
* [OptionParser] Make OptionParser public, add support to flags and improved switch parsing;
|
||||
* [Kernel] Operator `!` is now allowed in guard clauses;
|
||||
* [IEx] IEx now provides autocomplete if the OS supports tty;
|
||||
* [IEx] IEx now supports remsh;
|
||||
* [Mix] First Mix public release;
|
||||
* [Regex] Back references are now properly supported;
|
||||
* [IEx] Elixir now defaults to compile with documentation and `d` can be used in IEx to print modules and functions documentation;
|
||||
* [ExUnit] Support setup and teardown callbacks;
|
||||
* [Kernel] Introduced operator `=~` for regular expression matches;
|
||||
* [Kernel] Compiled docs now include the function signature;
|
||||
* [Kernel] `defmodule` do not start a new variable scope, this improves meta-programming capabilities;
|
||||
* [Range] Added a Range module with support to `in` operator (`x in 1..3`) and iterators;
|
||||
* [Enum] Enhanced Enum protocol to support `Enum.count`;
|
||||
* [Module] Added support to `@before_compile` and `@after_compile` callbacks. The first receives the module name while the latter receives the module name and its object code;
|
||||
* [Kernel] quote special form now supports line and unquote as options;
|
||||
* [Record] Allow `Record[_: value]` to set a default value to all records fields, as in Erlang;
|
||||
* [IEx] Functions `c` and `m` are available in IEx to compile and print available module information. Functions `h` and `v` are available to show history and print previous commands values;
|
||||
* [Enum] Optimized functions when a list is given as collection;
|
||||
* [System] Added `System.find_executable`
|
||||
* [Kernel] Document the macro `@` and allow attributes to be read inside functions;
|
||||
* [IO/File] Many improvements to `File` and `IO` modules;
|
||||
* [Macro] Added `Macro.expand`, useful for debugging what a macro expands to;
|
||||
* [Enum] Added `find_index`;
|
||||
* [Record] Records now provide a `to_keywords` function;
|
||||
* [Kernel] Added support to the `%R` sigil. The same as `%r`, but without interpolation or escaping. Both implementations were also optimized to generate the regex at compilation time;
|
||||
* [Kernel] Added `__ENV__` which returns a `Macro.Env` record with information about the compilation environment;
|
||||
* [Kernel] Added `__CALLER__` inside macros which returns a `Macro.Env` record with information about the calling site;
|
||||
|
||||
# v0.5.0 (2012-05-24)
|
||||
|
||||
* First official release
|
||||
@@ -1,10 +1,10 @@
|
||||
LEGAL NOTICE INFORMATION
|
||||
------------------------
|
||||
|
||||
All the files in this distribution are covered under either the Elixir's
|
||||
license (see the file LICENSE) except some files mentioned below that contains
|
||||
sections that are under Erlang's License (EPL):
|
||||
All the files in this distribution are covered under either Elixir's
|
||||
license (see the file LICENSE) except the files mentioned below that
|
||||
contains sections that are under Erlang's License (EPL):
|
||||
|
||||
lib/elixir/src/elixir_glob.erl
|
||||
lib/elixir/src/elixir_parser.erl (generated by build scripts)
|
||||
|
||||
src/elixir_glob.erl
|
||||
src/eex_lexer.erl (generated by build scripts)
|
||||
src/elixir_parser.erl (generated by build scripts)
|
||||
@@ -1,48 +1,103 @@
|
||||
REBAR:=$(shell which rebar || echo ./rebar)
|
||||
ERLC=erlc -I include
|
||||
ERL=erl -I include -noshell -pa ebin
|
||||
REBAR:=$(shell echo `pwd`/rebar)
|
||||
ELIXIRC:=bin/elixirc --ignore-module-conflict $(ELIXIRC_OPTS)
|
||||
ERLC:=erlc -I lib/elixir/include
|
||||
ERL:=erl -I lib/elixir/include -noshell -env ERL_LIBS $ERL_LIBS:lib
|
||||
FULLFLAG:=.full
|
||||
VERSION:=0.6.0
|
||||
|
||||
.PHONY: ebin docs
|
||||
.PHONY: 1
|
||||
.NOTPARALLEL: compile
|
||||
|
||||
compile: ebin ebin/__MAIN__ ebin/elixir.app
|
||||
#==> Templates
|
||||
define TASK_TEMPLATE
|
||||
$(1): lib/$(1)/ebin/Elixir-$(2).beam lib/$(1)/ebin/$(1).app
|
||||
|
||||
ebin/elixir.app:
|
||||
@ $(REBAR) compile
|
||||
lib/$(1)/ebin/$(1).app:
|
||||
@ cd lib/$(1) && ../../bin/elixir ../../bin/mix compile.app
|
||||
|
||||
ebin:
|
||||
@ $(REBAR) compile
|
||||
lib/$(1)/ebin/Elixir-$(2).beam: $(wildcard lib/$(1)/lib/*.ex) $(wildcard lib/$(1)/lib/*/*.ex) $(wildcard lib/$(1)/lib/*/*/*.ex) $$(FORCE)
|
||||
@ echo "==> $(1) (compile)"
|
||||
@ $$(ELIXIRC) "lib/$(1)/lib/**/*.ex" -o lib/$(1)/ebin
|
||||
|
||||
ebin/__MAIN__: lib/*.ex lib/*/*.ex
|
||||
@ rm -rf ebin/__MAIN__
|
||||
$(ERL) -s elixir_compiler core -s erlang halt
|
||||
@ rm -rf ebin/elixir.app
|
||||
test_$(1): $(1)
|
||||
@ echo "==> $(1) (exunit)"
|
||||
@ cd lib/$(1) && time ../../bin/elixir -r "test/test_helper.exs" -pr "test/**/*_test.exs"
|
||||
endef
|
||||
|
||||
#==> Compilation tasks
|
||||
KERNEL:=lib/elixir/ebin/Elixir-Kernel.beam
|
||||
|
||||
compile: lib/elixir/src/elixir.app.src erlang elixir
|
||||
|
||||
lib/elixir/src/elixir.app.src: src/elixir.app.src
|
||||
@ rm -rf lib/elixir/src/elixir.app.src
|
||||
@ cp src/elixir.app.src lib/elixir/src/elixir.app.src
|
||||
|
||||
erlang:
|
||||
@ cd lib/elixir && $(REBAR) compile
|
||||
|
||||
# We need to compile only EEx (without the app)
|
||||
# file so we can compile Mix
|
||||
elixir: kernel lib/eex/ebin/Elixir-EEx.beam mix ex_unit eex
|
||||
|
||||
kernel: $(KERNEL)
|
||||
$(KERNEL): lib/elixir/lib/*.ex lib/elixir/lib/*/*.ex $(FORCE)
|
||||
@ if [ -f $(KERNEL) ]; then \
|
||||
echo "==> kernel (compile)"; \
|
||||
$(ELIXIRC) "lib/elixir/lib/**/*.ex" -o lib/elixir/ebin; \
|
||||
else \
|
||||
echo "==> bootstrap (compile)"; \
|
||||
$(ERL) -s elixir_compiler core -s erlang halt; \
|
||||
fi
|
||||
@ rm -rf lib/elixir/ebin/elixir.app
|
||||
@ cd lib/elixir && $(REBAR) compile
|
||||
|
||||
$(eval $(call TASK_TEMPLATE,ex_unit,ExUnit))
|
||||
$(eval $(call TASK_TEMPLATE,eex,EEx))
|
||||
$(eval $(call TASK_TEMPLATE,mix,Mix))
|
||||
|
||||
clean:
|
||||
@ $(REBAR) clean
|
||||
@ rm -rf .full
|
||||
@ rm -rf lib/*/ebin
|
||||
@ cd lib/elixir && $(REBAR) clean
|
||||
|
||||
docs: compile
|
||||
@ bin/elixirc "lib/**/*.ex" --ignore-module-conflict --docs -o for_docs
|
||||
@ rm -rf ebin/__MAIN__
|
||||
@ mv for_docs/__MAIN__ ebin/__MAIN__
|
||||
@ rm -rf for_docs
|
||||
#==> Release tasks
|
||||
$(FULLFLAG): $(wildcard lib/*/ebin/*)
|
||||
make ELIXIRC_OPTS="--debug-info" FORCE=1
|
||||
touch $(FULLFLAG)
|
||||
|
||||
zip: $(FULLFLAG)
|
||||
rm -rf v$(VERSION).zip
|
||||
zip -9 -r v$(VERSION).zip bin CHANGELOG.md LEGAL lib/*/ebin LICENSE README.md rel
|
||||
|
||||
docs: $(FULLFLAG)
|
||||
mkdir -p ebin
|
||||
rm -rf docs
|
||||
cp -R -f lib/*/ebin/*.beam ./ebin
|
||||
bin/elixir ../exdoc/bin/exdoc
|
||||
rm -rf ebin
|
||||
|
||||
release_docs: docs
|
||||
bin/elixir ../exdoc/bin/exdoc
|
||||
rm -rf ../elixir-lang.github.com/docs
|
||||
mv output ../elixir-lang.github.com/docs
|
||||
cd ../elixir-lang.github.com && git checkout master
|
||||
rm -rf ../elixir-lang.github.com/docs/master
|
||||
mv output ../elixir-lang.github.com/docs/master
|
||||
|
||||
release_erl: $(FULLFLAG)
|
||||
@ rm -rf rel/elixir
|
||||
@ cd rel && ../rebar generate
|
||||
|
||||
#==> Tests tasks
|
||||
test: test_erlang test_elixir
|
||||
|
||||
test_erlang: compile
|
||||
@ echo "==> erlang (eunit)"
|
||||
@ mkdir -p test/ebin
|
||||
@ # Compile test files
|
||||
@ $(ERLC) -o test/ebin test/erlang/*.erl
|
||||
@ # Look and execute each file
|
||||
@ time $(ERL) -pa test/ebin -s test_helper test -s erlang halt
|
||||
@ echo "==> elixir (eunit)"
|
||||
@ mkdir -p lib/elixir/test/ebin
|
||||
@ $(ERLC) -pa lib/elixir/ebin -o lib/elixir/test/ebin lib/elixir/test/erlang/*.erl
|
||||
@ time $(ERL) -pa lib/elixir/test/ebin -s test_helper test -s erlang halt
|
||||
@ echo
|
||||
|
||||
test_elixir: compile
|
||||
@ echo "==> elixir (exunit)"
|
||||
@ time bin/elixir -r "test/elixir/test_helper.exs" -pr "test/elixir/**/*_test.exs"
|
||||
test_elixir: test_kernel test_mix test_ex_unit test_eex
|
||||
|
||||
test_kernel: compile
|
||||
@ echo "==> kernel (exunit)"
|
||||
@ cd lib/elixir && time ../../bin/elixir -r "test/elixir/test_helper.exs" -pr "test/elixir/**/*_test.exs"
|
||||
|
||||
@@ -1,18 +1,12 @@
|
||||

|
||||

|
||||
=========
|
||||
[](http://travis-ci.org/elixir-lang/elixir)
|
||||
|
||||
Elixir is a programming language built on top of the Erlang VM. As Erlang, it is a functional language built to support distributed, fault-tolerant, non-stop applications with hot code swapping.
|
||||
|
||||
Elixir is also dynamic typed but, differently from Erlang, it is also homoiconic, allowing meta-programming via macros. Elixir also supports polymorphism via protocols (similar to Clojure's), dynamic records and provides a reference mechanism.
|
||||
|
||||
Finally, Elixir and Erlang share the same bytecode and data types. This means you can invoke Erlang code from Elixir (and vice-versa) without any conversion or performance hit. This allows a developer to mix the expressiveness of Elixir with the robustness and performance of Erlang.
|
||||
For more about Elixir, installation and documentation, [check Elixir's website](http://elixir-lang.org/).
|
||||
|
||||
# Usage
|
||||
|
||||
You can run and learn about Elixir in the [Getting Started guide][1].
|
||||
|
||||
But if you just want to try it out, clone this repository to your machine, compile and test it:
|
||||
If you want to contribute to Elixir or run it from source, clone this repository to your machine, compile and test it:
|
||||
|
||||
$ git clone https://github.com/elixir-lang/elixir.git
|
||||
$ cd elixir
|
||||
@@ -26,32 +20,33 @@ However, if tests fail, it is likely you have an outdated Erlang version (Elixir
|
||||
|
||||
If you have the correct version and tests still fail, feel free to [open an issue][2].
|
||||
|
||||
# Contributing & Roadmap
|
||||
# Contributing
|
||||
|
||||
If you want to contribute, the code is organized as follows:
|
||||
If you want to contribute, Elixir code is divided in applications inside the `lib` folder:
|
||||
|
||||
* `include`, `src` - Both directories contain part of the source code written in Erlang. `yecc` is used as the parser;
|
||||
* `elixir` - Contains Elixir's kernel and stdlib;
|
||||
|
||||
* `lib` - Contains Elixir's STDLIB, written in Elixir;
|
||||
* `eex` - Template engine that allows you to embed Elixir;
|
||||
|
||||
* `test/elixir` - Tests for Elixir's STDLIB, written in Elixir. For this purpose, Elixir ships with a small unit test library called `ExUnit`;
|
||||
|
||||
* `test/erlang` - Contains tests for Elixir, written in Erlang. Usually, just internal stuff is tested here. The preferred way to test is in Elixir itself.
|
||||
* `ex_unit` - Simple test framework that ships with Elixir;
|
||||
|
||||
We usually keep a list of features and bugs [in the issue tracker][2].
|
||||
|
||||
# Important links
|
||||
|
||||
* #elixir-lang on freenode IRC
|
||||
* [Getting Started Guide][1]
|
||||
* [Mailing list](http://groups.google.com/group/elixir-lang-core)
|
||||
* [Website][1]
|
||||
* [Issue tracker][2]
|
||||
* [Textmate Bundle for Elixir](https://github.com/elixir-lang/elixir-tmbundle)
|
||||
* [Vim Elixir](https://github.com/elixir-lang/vim-elixir)
|
||||
* [Mailing list][3]
|
||||
|
||||
[1]: http://elixir-lang.org/getting_started/1.html
|
||||
[1]: http://elixir-lang.org
|
||||
[2]: https://github.com/elixir-lang/elixir/issues
|
||||
[3]: http://groups.google.com/group/elixir-lang-core
|
||||
|
||||
# License
|
||||
|
||||
Copyright (c) 2012 Plataformatec. See LICENSE file.
|
||||
"Elixir" and the Elixir logo are copyright (c) 2012 Plataformatec.
|
||||
|
||||
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.
|
||||
+10
-4
@@ -2,18 +2,24 @@
|
||||
|
||||
This document simply outlines the release process:
|
||||
|
||||
1) Run `make clean test` to ensure all tests pass from scratch
|
||||
1) Remove .dev extension from current versions
|
||||
|
||||
2) Remove .dev extension from current versions
|
||||
2) Run `make clean test` to ensure all tests pass from scratch
|
||||
|
||||
3) Ensure CHANGELOG is updated and tag release version with timestamp in it
|
||||
|
||||
4) Commit changes above and tag new version on Git
|
||||
|
||||
5) After release, bump versions and add .dev back
|
||||
5) Release new docs, update elixir-lang.org
|
||||
|
||||
6) Push new zip to Elixir's downloads page
|
||||
|
||||
7) After release, bump versions and add .dev back
|
||||
|
||||
## Places where version is mentioned
|
||||
|
||||
* src/elixir.app.src
|
||||
* lib/system.ex
|
||||
* lib/elixir/lib/system.ex
|
||||
* rel/reltool.config
|
||||
* Makefile
|
||||
* CHANGELOG
|
||||
+52
-14
@@ -5,29 +5,67 @@ if [ $# -eq 0 ]; then
|
||||
-v Prints version and exit
|
||||
-e \"command\" Evaluates the given command (*)
|
||||
-r \"file\" Requires the given files/patterns (*)
|
||||
-S \"script\" Finds and executes the given script (*)
|
||||
-pr \"file\" Requires the given files/patterns in parallel (*)
|
||||
-pa \"path\" Prepend the given path to Erlang code path (*)
|
||||
-pz \"path\" Append the given path to Erlang code path (*)
|
||||
--no-halt Do not halt the Erlang VM after execution
|
||||
-pa \"path\" Prepends the given path to Erlang code path (*)
|
||||
-pz \"path\" Appends the given path to Erlang code path (*)
|
||||
--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
|
||||
--remsh \"name\" Connects to a node using a remote shell (with iex)
|
||||
--no-halt Does not halt the Erlang VM after execution
|
||||
|
||||
** Options marked with (*) can be given more than once;
|
||||
|
||||
** Options given after the .exs file or -- are passed down to the executed code;
|
||||
|
||||
** Options can be passed to the erlang runtime using ELIXIR_ERL_OPTS." >&2
|
||||
** Options marked with (*) can be given more than once
|
||||
** Options given after the .exs file or -- are passed down to the executed code
|
||||
** Options can be passed to the erlang runtime using ELIXIR_ERL_OPTS or --erl" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
readlink_f () {
|
||||
cd `dirname $1` > /dev/null
|
||||
local filename=`basename $1`
|
||||
cd "$(dirname "$1")" > /dev/null
|
||||
local filename="$(basename "$1")"
|
||||
if [ -h "$filename" ]; then
|
||||
readlink_f `readlink $filename`
|
||||
readlink_f "$(readlink "$filename")"
|
||||
else
|
||||
echo "`pwd -P`/$filename"
|
||||
fi
|
||||
}
|
||||
|
||||
SELF=`readlink_f $0`
|
||||
SCRIPT_PATH=`dirname $SELF`
|
||||
erl -pa $SCRIPT_PATH/../ebin -noshell $ELIXIR_ERL_OPTS -s elixir start_cli -extra "$@"
|
||||
ERL=""
|
||||
I=1
|
||||
|
||||
while [ $I -le $# ]; do
|
||||
S=1
|
||||
eval "PEEK=\${$I}"
|
||||
case "$PEEK" in
|
||||
-v|--compile|--no-halt)
|
||||
;;
|
||||
-e|-r|-pr|-pa|-pz|--remsh|-S)
|
||||
S=2
|
||||
;;
|
||||
--sname|--name)
|
||||
I=$(expr $I + 1)
|
||||
eval "VAL=\${$I}"
|
||||
ERL="$ERL `echo $PEEK | cut -c 2-` "$VAL""
|
||||
;;
|
||||
--erl)
|
||||
I=$(expr $I + 1)
|
||||
eval "VAL=\${$I}"
|
||||
ERL="$ERL "$VAL""
|
||||
;;
|
||||
*)
|
||||
break
|
||||
;;
|
||||
esac
|
||||
I=$(expr $I + $S)
|
||||
done
|
||||
|
||||
SELF=$(readlink_f "$0")
|
||||
SCRIPT_PATH=$(dirname "$SELF")
|
||||
|
||||
if [ -f "$SCRIPT_PATH/../releases/RELEASES" ] && [ -f "$SCRIPT_PATH/erl" ]
|
||||
then
|
||||
"$SCRIPT_PATH"/erl -env ERL_LIBS $ERL_LIBS:"$SCRIPT_PATH/../lib" -boot elixir -noshell $ELIXIR_ERL_OPTS $ERL -s elixir start_cli -extra "$@"
|
||||
else
|
||||
erl -env ERL_LIBS $ERL_LIBS:"$SCRIPT_PATH/../lib" -noshell $ELIXIR_ERL_OPTS $ERL -s elixir start_cli -extra "$@"
|
||||
fi
|
||||
|
||||
+4
-6
@@ -15,10 +15,8 @@ echo -pa path Prepend the given path to Erlang code path (*)
|
||||
echo -pz path Append the given path to Erlang code path (*)
|
||||
echo --no-halt Do not halt the Erlang VM after execution
|
||||
echo.
|
||||
echo ** Options marked with (*) can be given more than once;
|
||||
echo.
|
||||
echo ** Options given after the .exs file or -- are passed down to the executed code;
|
||||
echo.
|
||||
echo ** Options can be passed to the erlang runtime using ELIXIR_ERL_OPTS.
|
||||
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
|
||||
:run
|
||||
erl -pa %~dp0\..\ebin -noshell %ELIXIR_ERL_OPTS% -s elixir start_cli -extra %*
|
||||
erl -env ERL_LIBS %ERL_LIBS%;"%~dp0\..\lib" -noshell %ELIXIR_ERL_OPTS% -s elixir start_cli -extra %*
|
||||
+10
-16
@@ -1,33 +1,27 @@
|
||||
#!/bin/sh
|
||||
if [ $# -eq 0 ]; then
|
||||
echo "Usage: `basename $0` [switches] [.ex files]
|
||||
echo "Usage: `basename $0` [elixir switches] [compiler switches] [.ex files]
|
||||
|
||||
-v Prints version and exit
|
||||
-o The directory to output compiled files
|
||||
-pa \"path\" Prepend the given path to Erlang code path (*)
|
||||
-pz \"path\" Append the given path to Erlang code path (*)
|
||||
--docs Attach documentation to compiled modules
|
||||
--no-docs Do not attach documentation with compiled code
|
||||
--debug-info Attach debug info to compiled modules
|
||||
--ignore-module-conflict
|
||||
|
||||
** Options marked with (*) can be given more than once;
|
||||
|
||||
** Options given after -- are passed down to the executed code;
|
||||
|
||||
** Options can be passed to the erlang runtime using ELIXIR_ERL_OPTS." >&2
|
||||
** Options given after -- are passed down to the executed code
|
||||
** Options can be passed to the erlang runtime using ELIXIR_ERL_OPTS" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
readlink_f () {
|
||||
cd `dirname $1` > /dev/null
|
||||
local filename=`basename $1`
|
||||
cd "$(dirname "$1")" > /dev/null
|
||||
local filename="$(basename "$1")"
|
||||
if [ -h "$filename" ]; then
|
||||
readlink_f `readlink $filename`
|
||||
readlink_f "$(readlink "$filename")"
|
||||
else
|
||||
echo "`pwd -P`/$filename"
|
||||
fi
|
||||
}
|
||||
|
||||
SELF=`readlink_f $0`
|
||||
SCRIPT_PATH=`dirname $SELF`
|
||||
$SCRIPT_PATH/elixir +compile "$@"
|
||||
SELF=$(readlink_f "$0")
|
||||
SCRIPT_PATH=$(dirname "$SELF")
|
||||
"$SCRIPT_PATH"/elixir --compile "$@"
|
||||
|
||||
+5
-7
@@ -11,14 +11,12 @@ echo -v Prints version and exit
|
||||
echo -o The directory to output compiled files
|
||||
echo -pa path Prepend the given path to Erlang code path (*)
|
||||
echo -pz path Append the given path to Erlang code path (*)
|
||||
echo --docs Attach documentation to compiled modules
|
||||
echo --no-docs Do not attach documentation with compiled code
|
||||
echo --debug-info Attach debug info to compiled modules
|
||||
echo --ignore-module-conflict
|
||||
echo.
|
||||
echo ** Options marked with (*) can be given more than once;
|
||||
echo.
|
||||
echo ** Options given after -- are passed down to the executed code;
|
||||
echo.
|
||||
echo ** Options can be passed to the erlang runtime using ELIXIR_ERL_OPTS." >&2
|
||||
echo ** Options marked with (*) can be given more than once
|
||||
echo ** Options given after -- are passed down to the executed code
|
||||
echo ** Options can be passed to the erlang runtime using ELIXIR_ERL_OPTS" >&2
|
||||
:run
|
||||
call %~dp0\elixir.bat +compile %*
|
||||
call "%~dp0\elixir.bat" --compile %*
|
||||
@@ -1,14 +1,14 @@
|
||||
#!/bin/sh
|
||||
readlink_f () {
|
||||
cd `dirname $1` > /dev/null
|
||||
local filename=`basename $1`
|
||||
cd "$(dirname "$1")" > /dev/null
|
||||
local filename="$(basename "$1")"
|
||||
if [ -h "$filename" ]; then
|
||||
readlink_f `readlink $filename`
|
||||
readlink_f "$(readlink "$filename")"
|
||||
else
|
||||
echo "`pwd -P`/$filename"
|
||||
fi
|
||||
}
|
||||
|
||||
SELF=`readlink_f $0`
|
||||
SCRIPT_PATH=`dirname $SELF`
|
||||
erl -pa $SCRIPT_PATH/../ebin -noinput $ELIXIR_ERL_OPTS -s elixir start_cli -extra --no-halt -e "Elixir.IEx.start" "$@"
|
||||
SELF=$(readlink_f "$0")
|
||||
SCRIPT_PATH=$(dirname "$SELF")
|
||||
"$SCRIPT_PATH"/elixir --no-halt -e "IEx.cli" "$@"
|
||||
+1
-1
@@ -1,2 +1,2 @@
|
||||
@echo off
|
||||
erl -pa %~dp0\..\ebin -noshell %ELIXIR_ERL_OPTS% -s elixir start_cli -extra --no-halt -e "Elixir.IEx.start" %*
|
||||
call "%~dp0\elixir.bat" --no-halt -e "IEx.cli" %*
|
||||
@@ -0,0 +1,2 @@
|
||||
@echo off
|
||||
call "%~dp0\elixir.bat" "%~dp0\mix" %*
|
||||
-178
@@ -1,178 +0,0 @@
|
||||
import Elixir.Builtin, except: [access: 2]
|
||||
|
||||
defprotocol Access do
|
||||
@moduledoc """
|
||||
The Access protocol is the underlying protocol invoked
|
||||
when the brackets syntax is used. For instance, `foo[bar]`
|
||||
is translated to `access foo, bar` which, by default,
|
||||
invokes `Access.access` protocol.
|
||||
|
||||
This protocol is implemented by default for most builtin
|
||||
types, like tuples, atoms, functions, etc.
|
||||
"""
|
||||
|
||||
@only [List, BitString, Record, Tuple, Atom, Function]
|
||||
|
||||
@doc """
|
||||
Receives the element being accessed and the access item.
|
||||
"""
|
||||
def access(element, qualifier)
|
||||
end
|
||||
|
||||
defimpl Access, for: Tuple do
|
||||
@doc """
|
||||
Access the tuple via an integer. Negative indexes
|
||||
performs an inverted lookup, for example, -1 can be
|
||||
used to retrieve the last item in the tuple. Returns
|
||||
nil if an out of bounds access occurs.
|
||||
|
||||
## Examples
|
||||
|
||||
tuple = { :a, :b, :c }
|
||||
tuple[-1] #=> :c
|
||||
|
||||
"""
|
||||
def access(tuple, integer) when is_integer(integer) and integer > 0 and integer <= tuple_size(tuple) do
|
||||
:erlang.element(integer, tuple)
|
||||
end
|
||||
|
||||
def access(tuple, integer) when is_integer(integer) and integer < 0 do
|
||||
size = tuple_size(tuple)
|
||||
position = integer + size + 1
|
||||
if position > size or position < 1,
|
||||
do: nil, else: :erlang.element(position, tuple)
|
||||
end
|
||||
|
||||
def access(_tuple, integer) when is_integer(integer) do
|
||||
nil
|
||||
end
|
||||
end
|
||||
|
||||
defimpl Access, for: List do
|
||||
@doc """
|
||||
Access the list via a predicate.
|
||||
|
||||
If a regular expression, it returns a list with the
|
||||
matched contents.
|
||||
|
||||
If an atom, assumes the list is a keywords list and
|
||||
access the key in the keywords equals to the given
|
||||
atom.
|
||||
|
||||
Notice this protocol does not implement an integer
|
||||
lookup. This is intentional since doing an index
|
||||
based access on lists is usually undesired.
|
||||
|
||||
## Examples
|
||||
|
||||
list = 'sample'
|
||||
list[%r/a/] #=> 'a'
|
||||
|
||||
keywords = [a: 1, b: 2]
|
||||
keywords[:a] #=> 1
|
||||
|
||||
"""
|
||||
|
||||
## Atom
|
||||
|
||||
def access(list, atom) when is_atom(atom) do
|
||||
atom_access(list, atom)
|
||||
end
|
||||
|
||||
## Regex
|
||||
|
||||
def access(list, re) when is_regex(re) do
|
||||
case Erlang.re.run(list, Regex.re_pattern(re), [{ :capture, :first, :list }]) do
|
||||
:nomatch -> nil
|
||||
{ :match, [result] } -> result
|
||||
end
|
||||
end
|
||||
|
||||
## Helpers
|
||||
|
||||
defp atom_access([{k, _}|_], key) when key < k, do: nil
|
||||
defp atom_access([{k, _}|d], key) when key > k, do: atom_access(d, key)
|
||||
defp atom_access([{_k, value}|_], _key), do: value
|
||||
defp atom_access([], _), do: nil
|
||||
end
|
||||
|
||||
defimpl Access, for: BitString do
|
||||
@doc """
|
||||
Access the binary via a predicate.
|
||||
|
||||
If a regular expression, it returns a binary with the
|
||||
matched contents.
|
||||
|
||||
## Examples
|
||||
|
||||
binary = "abc"
|
||||
Binary.access binary, %r(a) #=> "a"
|
||||
|
||||
"""
|
||||
|
||||
## Regex
|
||||
|
||||
def access(binary, re) when is_binary(binary) and is_regex(re) do
|
||||
case Erlang.re.run(binary, Regex.re_pattern(re), [{ :capture, :first, :binary }]) do
|
||||
:nomatch -> nil
|
||||
{ :match, [result] } -> result
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defimpl Access, for: Atom do
|
||||
@doc """
|
||||
Access the atom via keywords. Different from other
|
||||
implementations, the Access protocol for atoms is
|
||||
special cased during compilation time to provide
|
||||
faster read and write access for records.
|
||||
|
||||
This pattern can also be used in guards, which is
|
||||
a very useful way to extract information from a
|
||||
record.
|
||||
|
||||
## Examples
|
||||
|
||||
def increment(State[counter: counter] = state) do
|
||||
state.counter(counter + 1)
|
||||
end
|
||||
|
||||
In the example above, we use the Access protocol on atoms
|
||||
to match the counter field in the record State. Considering
|
||||
the record definition is as follows:
|
||||
|
||||
defrecord State, counter: 0, other: nil
|
||||
|
||||
The clause above is translated to:
|
||||
|
||||
def increment({ State, counter, _ } = state) do
|
||||
state.counter(counter + 1)
|
||||
end
|
||||
|
||||
Which is a very convenient way to pattern match and have
|
||||
faster read times. The same pattern can be used to create
|
||||
a new record:
|
||||
|
||||
def new_state(counter) do
|
||||
State[counter: counter]
|
||||
end
|
||||
|
||||
All fields not specified on creation defaults to their
|
||||
default value.
|
||||
"""
|
||||
def access(atom, keywords) when is_list(keywords) do
|
||||
atom.new(keywords)
|
||||
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
|
||||
@@ -1,48 +0,0 @@
|
||||
defmodule Bitwise do
|
||||
@moduledoc """
|
||||
This module provide macros for bitwise operators
|
||||
provided by Erlang. These macros can be used in guards.
|
||||
"""
|
||||
|
||||
@doc """
|
||||
Bitwise not.
|
||||
"""
|
||||
defmacro bnot(expr) do
|
||||
quote do: __op__ :bnot, unquote(expr)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Bitwise and.
|
||||
"""
|
||||
defmacro band(left, right) do
|
||||
quote do: __op__ :band, unquote(left), unquote(right)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Bitwise or.
|
||||
"""
|
||||
defmacro bor(left, right) do
|
||||
quote do: __op__ :bor, unquote(left), unquote(right)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Bitwise xor.
|
||||
"""
|
||||
defmacro bxor(left, right) do
|
||||
quote do: __op__ :bxor, unquote(left), unquote(right)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Arithmetic bitshift left.
|
||||
"""
|
||||
defmacro bsl(left, right) do
|
||||
quote do: __op__ :bsl, unquote(left), unquote(right)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Arithmetic bitshift right.
|
||||
"""
|
||||
defmacro bsr(left, right) do
|
||||
quote do: __op__ :bsr, unquote(left), unquote(right)
|
||||
end
|
||||
end
|
||||
-185
@@ -1,185 +0,0 @@
|
||||
defmodule Code do
|
||||
@moduledoc """
|
||||
The Code module is responsible to manage code compilation,
|
||||
code evaluation and code loading.
|
||||
"""
|
||||
|
||||
@doc """
|
||||
Returns all the loaded files.
|
||||
"""
|
||||
def loaded_files do
|
||||
server_call :loaded
|
||||
end
|
||||
|
||||
@doc """
|
||||
Appends a path to Erlang VM code path.
|
||||
"""
|
||||
def append_path(path) do
|
||||
Erlang.code.add_pathz(to_char_list(path))
|
||||
end
|
||||
|
||||
@doc """
|
||||
Prepends a path to Erlang VM code path.
|
||||
"""
|
||||
def prepend_path(path) do
|
||||
Erlang.code.add_patha(to_char_list(path))
|
||||
end
|
||||
|
||||
@doc """
|
||||
Evalutes the contents given by string. The second argument is the binding
|
||||
(which should be a Keyword) followed by a keyword list of options. The
|
||||
options can be:
|
||||
|
||||
* `:file` - the file to be considered in the evaluation
|
||||
* `:line` - the line the script starts
|
||||
* `:delegate_locals_to` - delegate local calls to the given module,
|
||||
otherwise functions are evaluated inside Erlang's default scope.
|
||||
|
||||
## Examples
|
||||
|
||||
Code.eval "a + b", [a: 1, b: 2], file: __FILE__, line: __LINE__
|
||||
#=> { 3, [ {:a, 1}, {:b, 2} ] }
|
||||
|
||||
"""
|
||||
def eval(string, binding // [], opts // []) do
|
||||
{ value, binding, _scope } =
|
||||
Erlang.elixir.eval :unicode.characters_to_list(string), binding, opts
|
||||
{ value, binding }
|
||||
end
|
||||
|
||||
@doc """
|
||||
Evalutes the quoted contents.
|
||||
|
||||
## Options
|
||||
|
||||
This function accepts a list of options. The supported
|
||||
options are:
|
||||
|
||||
* `:file` - The filename to be used in stacktraces
|
||||
or by the __FILE__ macro in case there is an error.
|
||||
|
||||
* `:line` - The line to be used when expanding __LINE__
|
||||
macros and expressions inside the quote.
|
||||
|
||||
## Examples
|
||||
|
||||
contents = quote hygiene: false, do: a + b
|
||||
Code.eval_quoted contents, [a: 1, b: 2], file: __FILE__, line: __LINE__
|
||||
#=> { 3, [ {:a, 1}, {:b, 2} ] }
|
||||
|
||||
"""
|
||||
def eval_quoted(quoted, binding // [], opts // []) do
|
||||
{ value, binding, _scope } =
|
||||
Erlang.elixir.eval_quoted [quoted], binding, opts
|
||||
{ value, binding }
|
||||
end
|
||||
|
||||
@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 the full path of the loaded file.
|
||||
|
||||
When loading a file, you may skip passing .exs as extension as Elixir
|
||||
automatically adds it for you.
|
||||
"""
|
||||
def load_file(file, relative_to // nil) do
|
||||
file = find_file(file, relative_to)
|
||||
server_call { :loaded, file }
|
||||
Erlang.elixir_compiler.file to_char_list(file)
|
||||
file
|
||||
end
|
||||
|
||||
@doc """
|
||||
Requires the given `file`. Accepts `relative_to` as an argument to tell
|
||||
where the file is located. If the file was already required/loaded,
|
||||
returns nil, otherwise the full path of the loaded file.
|
||||
|
||||
When requiring a file, you may skip passing .exs as extension as
|
||||
Elixir automatically adds it for you.
|
||||
"""
|
||||
def require_file(file, relative_to // nil) do
|
||||
file = find_file(file, relative_to)
|
||||
|
||||
case server_call({ :loaded, file }) do
|
||||
:ok ->
|
||||
Erlang.elixir_compiler.file to_char_list(file)
|
||||
file
|
||||
:duplicated -> nil
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Loads the compilation options from the code server.
|
||||
Check compiler_options/1 for more information.
|
||||
"""
|
||||
def compiler_options do
|
||||
server_call :compiler_options
|
||||
end
|
||||
|
||||
@doc """
|
||||
Sets compilation options. Those options are global
|
||||
since they are stored by Elixir's Code Server.
|
||||
|
||||
Available options are:
|
||||
|
||||
* docs - when true, retain documentation in the compiled module;
|
||||
* debug_info - when true, retain debug information in the compiled module.
|
||||
Notice debug information can be used to reconstruct the source code;
|
||||
* ignore_module_conflict - when true, override modules that were already defined;
|
||||
|
||||
"""
|
||||
def compiler_options(opts) do
|
||||
server_call { :compiler_options, opts }
|
||||
end
|
||||
|
||||
@doc """
|
||||
Compiles the given string and returns a list of tuples where
|
||||
the first element is the module name and the second one is its
|
||||
binary.
|
||||
|
||||
For compiling many files at once, check `Elixir.ParallelCompiler`.
|
||||
"""
|
||||
def compile_string(string, file // 'nofile') do
|
||||
Erlang.elixir_compiler.string :unicode.characters_to_list(string), to_char_list(file)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Ensure if the given module is loaded. If the module is already loaded,
|
||||
it works as no-op. If the module was not loaded yet, it tries to load it.
|
||||
|
||||
If it succeeds loading the module anyhow, it returns `{ :module, module }`.
|
||||
If not, returns `{ :error, reason }` with the error reason.
|
||||
"""
|
||||
def ensure_loaded(module) when is_atom(module) do
|
||||
Erlang.code.ensure_loaded(module)
|
||||
end
|
||||
|
||||
## Helpers
|
||||
|
||||
# Finds the file given the relative_to path.
|
||||
# If the file is found, returns its path in binary, fails otherwise.
|
||||
defp find_file(file, relative_to) do
|
||||
file = to_binary(file)
|
||||
|
||||
file = if relative_to do
|
||||
File.expand_path(file, relative_to)
|
||||
else
|
||||
File.expand_path(file)
|
||||
end
|
||||
|
||||
if File.regular?(file) do
|
||||
file
|
||||
else
|
||||
prefix = "#{file}.exs"
|
||||
if File.regular?(prefix) do
|
||||
prefix
|
||||
else
|
||||
raise ArgumentError, message: "could not load #{file}"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defp server_call(args) do
|
||||
Erlang.gen_server.call(:elixir_code_server, args)
|
||||
end
|
||||
end
|
||||
@@ -40,15 +40,14 @@ defmodule EEx do
|
||||
`EEx.SmartEngine` supports the following tags:
|
||||
|
||||
<% Elixir expression - inline with output %>
|
||||
<& Elxir matching expression - not printed &>
|
||||
<%= Elixir expression - replace with result %>
|
||||
|
||||
All expressions that output something to the template
|
||||
*must* use the equals sign (`=`). Since everything in
|
||||
**must** use the equals sign (`=`). Since everything in
|
||||
Elixir is a macro, there are no exceptions for this rule.
|
||||
For example, while some template languages would special-
|
||||
case `if` clauses, they are treated the same in EEx and
|
||||
also requires `=` in order to have their result printed:
|
||||
also require `=` in order to have their result printed:
|
||||
|
||||
<%= if true do %>
|
||||
It is obviously true
|
||||
@@ -56,27 +55,14 @@ defmodule EEx do
|
||||
This will never appear
|
||||
<% end %>
|
||||
|
||||
The `<& ... &>` expression is only used in matching clauses.
|
||||
For example, the `cond` macro would be written as:
|
||||
|
||||
<%= cond do %>
|
||||
<& false -> &> Never printed
|
||||
<& true -> &> always printed
|
||||
<% end %>
|
||||
|
||||
Notice that different engines may have different rules
|
||||
for each tag. Other tags may be added in future versions.
|
||||
|
||||
### Macros
|
||||
|
||||
`EEx.SmartEngine` also adds two macros to your template.
|
||||
The first one is the `for` macro, which allows you to easily
|
||||
loop a variable:
|
||||
|
||||
EEx.eval_string "<%= for x in [1,2,3] do %><%= x %>\n<% end %>", []
|
||||
#=> "1\n2\n3\n"
|
||||
|
||||
It also adds defines a macro named `@` that allows easy access:
|
||||
`EEx.SmartEngine` also adds some macros to your template.
|
||||
An example is the `@` macro which allows easy data access
|
||||
in a template:
|
||||
|
||||
EEx.eval_string "<%= @foo %>", assigns: [foo: 1]
|
||||
#=> 1
|
||||
@@ -105,8 +91,9 @@ defmodule EEx do
|
||||
|
||||
"""
|
||||
defmacro function_from_string(kind, name, source, args // [], options // []) do
|
||||
info = [file: __CALLER__.file, line: __CALLER__.line + 1]
|
||||
quote do
|
||||
info = Keyword.merge unquote(options), [file: __FILE__, line: __LINE__]
|
||||
info = Keyword.merge unquote(info), unquote(options)
|
||||
EEx.function_from_quoted(__MODULE__, unquote(kind), unquote(name),
|
||||
unquote(args), EEx.compile_string(unquote(source), info), info)
|
||||
end
|
||||
@@ -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>> in bin when 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
|
||||
@@ -6,12 +6,12 @@ defmodule EEx.TransformerEngine do
|
||||
overridable directive that allows a developer to
|
||||
customize the expression returned by the engine.
|
||||
|
||||
Check `EEx.AssignsEngine`, `EEx.ForEngine` and
|
||||
`EEx.SmartEngine` for examples of using this module.
|
||||
Check `EEx.AssignsEngine` and `EEx.SmartEngine` for
|
||||
examples of using this module.
|
||||
"""
|
||||
|
||||
@doc false
|
||||
defmacro __using__(_, _) do
|
||||
defmacro __using__(_) do
|
||||
quote do
|
||||
@behavior EEx.Engine
|
||||
|
||||
@@ -32,7 +32,7 @@ defmodule EEx.TransformerEngine do
|
||||
end
|
||||
|
||||
defp transform(list) when is_list(list) do
|
||||
lc i in list, do: transform(i)
|
||||
lc i inlist list, do: transform(i)
|
||||
end
|
||||
|
||||
defp transform(other) do
|
||||
@@ -70,48 +70,16 @@ defmodule EEx.AssignsEngine do
|
||||
"""
|
||||
|
||||
@doc false
|
||||
defmacro __using__(_, _) do
|
||||
quote [unquote: false] do
|
||||
defmacro __using__(_) do
|
||||
quote unquote: false do
|
||||
defp transform({ :@, line, [{ name, _, atom }] }) when is_atom(name) and is_atom(atom) do
|
||||
quote(do: Keyword.get var!(assigns), unquote(name))
|
||||
end
|
||||
|
||||
defp transform(_), do: super
|
||||
defoverridable [transform: 1]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defmodule EEx.ForEngine do
|
||||
@moduledoc %B"""
|
||||
An abstract engine that, when used with the
|
||||
`TransformerEngine`, allows a developer to easily loop
|
||||
using `for`.
|
||||
|
||||
This engine is included by default on the SmartEngine.
|
||||
|
||||
## Examples
|
||||
|
||||
defmodule MyEngine do
|
||||
use EEx.TransformerEngine
|
||||
use EEx.ForEngine
|
||||
defp transform(_) do
|
||||
super
|
||||
end
|
||||
|
||||
EEx.eval_string("<%= for x in [1,2,3] do %><%= x %>\n<% end %>", assigns: [foo: 1])
|
||||
#=> "1\n2\n3\n"
|
||||
|
||||
"""
|
||||
|
||||
@doc false
|
||||
defmacro __using__(_, _) do
|
||||
quote [unquote: false] do
|
||||
defp transform({ :for, line, [{ :in, _, [var, collection] }, opts] }) do
|
||||
quote do
|
||||
Enum.map_join(unquote(collection), "", fn(unquote(var), unquote(opts)))
|
||||
end
|
||||
end
|
||||
|
||||
defp transform(_), do: super
|
||||
defoverridable [transform: 1]
|
||||
end
|
||||
end
|
||||
@@ -120,13 +88,10 @@ end
|
||||
defmodule EEx.SmartEngine do
|
||||
use EEx.TransformerEngine
|
||||
use EEx.AssignsEngine
|
||||
use EEx.ForEngine
|
||||
|
||||
@moduledoc """
|
||||
An engine meant for end-user usage that includes both
|
||||
`AssignsEngine` and `ForEngine` conveniences. Therefore,
|
||||
a developer can easily access assigns via `@` and loop
|
||||
using `for`. Read `EEx.AssignsEngine` and `EEx.ForEngine`
|
||||
for examples.
|
||||
An engine meant for end-user usage that includes
|
||||
`AssignsEngine` and other conveniences. Read
|
||||
`EEx.AssignsEngine` for examples.
|
||||
"""
|
||||
end
|
||||
@@ -0,0 +1,151 @@
|
||||
defmodule EEx.Tokenizer do
|
||||
@moduledoc false
|
||||
|
||||
@doc """
|
||||
Tokenizes the given char list or binary.
|
||||
It returns 4 different types of tokens as result:
|
||||
|
||||
* { :text, line, contents }
|
||||
* { :expr, line, marker, contents }
|
||||
* { :start_expr, line, marker, contents }
|
||||
* { :end_expr, line, marker, contents }
|
||||
|
||||
"""
|
||||
def tokenize(bin, line) when is_binary(bin) do
|
||||
tokenize(binary_to_list(bin), line)
|
||||
end
|
||||
|
||||
def tokenize(list, line) do
|
||||
List.reverse(tokenize(list, line, line, [], []))
|
||||
end
|
||||
|
||||
defp tokenize([?<,?%|t], current_line, line, buffer, acc) do
|
||||
{ marker, t } = retrieve_marker(t)
|
||||
{ expr, new_line, rest } = tokenize_expr t, line, []
|
||||
|
||||
token = token_name(expr)
|
||||
acc = tokenize_text(current_line, buffer, acc)
|
||||
final = { token, line, marker, List.reverse(expr) }
|
||||
tokenize rest, new_line, new_line, [], [final | acc]
|
||||
end
|
||||
|
||||
defp tokenize('\n' ++ t, current_line, line, buffer, acc) do
|
||||
tokenize t, current_line, line + 1, [?\n|buffer], acc
|
||||
end
|
||||
|
||||
defp tokenize([h|t], current_line, line, buffer, acc) do
|
||||
tokenize t, current_line, line, [h|buffer], acc
|
||||
end
|
||||
|
||||
defp tokenize([], current_line, _line, buffer, acc) do
|
||||
tokenize_text(current_line, buffer, acc)
|
||||
end
|
||||
|
||||
# Retrieve marker for <%
|
||||
|
||||
defp retrieve_marker('=' ++ t) do
|
||||
{ '=', t }
|
||||
end
|
||||
|
||||
defp retrieve_marker(t) do
|
||||
{ '', t }
|
||||
end
|
||||
|
||||
# Tokenize an expression until we find %>
|
||||
|
||||
defp tokenize_expr([?%,?>|t], line, buffer) do
|
||||
{ buffer, line, t }
|
||||
end
|
||||
|
||||
defp tokenize_expr('\n' ++ t, line, buffer) do
|
||||
tokenize_expr t, line + 1, [?\n|buffer]
|
||||
end
|
||||
|
||||
defp tokenize_expr([h|t], line, buffer) do
|
||||
tokenize_expr t, line, [h|buffer]
|
||||
end
|
||||
|
||||
# Raise an error if the expected token is not found
|
||||
|
||||
defp tokenize_expr([], _line, _buffer) do
|
||||
raise EEx.SyntaxError, message: "missing token: %>"
|
||||
end
|
||||
|
||||
# Receive an expression content and check
|
||||
# if it is a start, middle or an end token.
|
||||
#
|
||||
# Start tokens finish with `do` and `fn ->`
|
||||
# Middle tokens are marked with `->` or keywords
|
||||
# End tokens contain only the end word
|
||||
|
||||
defp token_name([h|t]) when h in [?\s, ?\t] do
|
||||
token_name(t)
|
||||
end
|
||||
|
||||
defp token_name('od' ++ [h|_]) when h in [?\s, ?\t, ?)] do
|
||||
:start_expr
|
||||
end
|
||||
|
||||
defp token_name('>-' ++ rest) do
|
||||
rest = List.reverse(rest)
|
||||
|
||||
# Tokenize the remaining passing "__internal__" as file,
|
||||
# which relax the tokenizer to not error on unmatched
|
||||
# pairs. Then, we check if there is a "fn" token and,
|
||||
# if so, it is not followed by an "end" token. If this
|
||||
# is the case, we are on a start expr.
|
||||
case :elixir_tokenizer.tokenize(rest, 1, "__internal__") do
|
||||
{ :ok, tokens } ->
|
||||
tokens = List.reverse(tokens)
|
||||
fn_index = fn_index(tokens)
|
||||
|
||||
if fn_index && end_index(tokens) > fn_index do
|
||||
:start_expr
|
||||
else
|
||||
:middle_expr
|
||||
end
|
||||
error ->
|
||||
:middle_expr
|
||||
end
|
||||
end
|
||||
|
||||
defp token_name('esle' ++ t), do: check_spaces(t, :middle_expr)
|
||||
defp token_name('retfa' ++ t), do: check_spaces(t, :middle_expr)
|
||||
defp token_name('hctac' ++ t), do: check_spaces(t, :middle_expr)
|
||||
defp token_name('eucser' ++ t), do: check_spaces(t, :middle_expr)
|
||||
defp token_name('dne' ++ t), do: check_spaces(t, :end_expr)
|
||||
|
||||
defp token_name(_) do
|
||||
:expr
|
||||
end
|
||||
|
||||
defp fn_index(tokens) do
|
||||
Enum.find_index(tokens, function do
|
||||
{ :fn_paren, _ } -> true
|
||||
{ :fn, _ } -> true
|
||||
_ -> false
|
||||
end)
|
||||
end
|
||||
|
||||
defp end_index(tokens) do
|
||||
Enum.find_index(tokens, match?({ :end, _ }, &1)) || :infinity
|
||||
end
|
||||
|
||||
defp check_spaces(string, token) do
|
||||
if only_spaces?(string), do: token, else: :expr
|
||||
end
|
||||
|
||||
defp only_spaces?([h|t]) when h in [?\s, ?\t], do: only_spaces?(t)
|
||||
defp only_spaces?(other), do: other == []
|
||||
|
||||
# Tokenize the buffered text by appending
|
||||
# it to the given accumulator.
|
||||
|
||||
defp tokenize_text(_line, [], acc) do
|
||||
acc
|
||||
end
|
||||
|
||||
defp tokenize_text(line, buffer, acc) do
|
||||
[{ :text, line, list_to_binary(List.reverse(buffer)) } | acc]
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,7 @@
|
||||
defmodule EEx.Mixfile do
|
||||
use Mix.Project
|
||||
|
||||
def project do
|
||||
[app: :eex, version: System.version]
|
||||
end
|
||||
end
|
||||
@@ -1,7 +1,7 @@
|
||||
Code.require_file "../../test_helper", __FILE__
|
||||
|
||||
defmodule EEx.SmartEngineTest do
|
||||
use ExUnit.Case
|
||||
use ExUnit.Case, async: true
|
||||
|
||||
test "evaluates simple string" do
|
||||
assert_eval "foo bar", "foo bar"
|
||||
@@ -12,7 +12,7 @@ defmodule EEx.SmartEngineTest do
|
||||
end
|
||||
|
||||
test "evaluates with loops" do
|
||||
assert_eval "1\n2\n3\n", "<%= for x in [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
|
||||
@@ -1,7 +1,7 @@
|
||||
Code.require_file "../../test_helper", __FILE__
|
||||
|
||||
defmodule EEx.TokenizerTest do
|
||||
use ExUnit.Case
|
||||
use ExUnit.Case, async: true
|
||||
require EEx.Tokenizer, as: T
|
||||
|
||||
test "simple chars lists" do
|
||||
@@ -51,7 +51,7 @@ baz %>
|
||||
end
|
||||
|
||||
test "strings with embedded -> end" do
|
||||
assert T.tokenize('foo <% cond do %><& false -> &>bar<& true -> &>baz<% end %>', 1) == [
|
||||
assert T.tokenize('foo <% cond do %><% false -> %>bar<% true -> %>baz<% end %>', 1) == [
|
||||
{ :text, 1, "foo " },
|
||||
{ :start_expr, 1, '', ' cond do ' },
|
||||
{ :middle_expr, 1, '', ' false -> ' },
|
||||
@@ -5,9 +5,9 @@ require EEx
|
||||
defmodule EExText.Compiled do
|
||||
def before_compile do
|
||||
fill_in_stacktrace
|
||||
{ __LINE__, hd(tl(System.stacktrace)) }
|
||||
{ __ENV__.line, hd(tl(System.stacktrace)) }
|
||||
end
|
||||
|
||||
{ :erlang, 1, 2 }.tuple_to_list
|
||||
EEx.function_from_string :def, :string_sample, "<%= a + b %>", [:a, :b]
|
||||
|
||||
filename = File.expand_path("../fixtures/eex_template_with_bindings.eex", __FILE__)
|
||||
@@ -16,13 +16,19 @@ defmodule EExText.Compiled do
|
||||
|
||||
def after_compile do
|
||||
fill_in_stacktrace
|
||||
{ __LINE__, hd(tl(System.stacktrace)) }
|
||||
{ __ENV__.line, hd(tl(System.stacktrace)) }
|
||||
end
|
||||
|
||||
@file "unknown"
|
||||
def unknown do
|
||||
fill_in_stacktrace
|
||||
{ __ENV__.line, hd(tl(System.stacktrace)) }
|
||||
end
|
||||
|
||||
@file __ENV__
|
||||
def other do
|
||||
fill_in_stacktrace
|
||||
{ __LINE__, hd(tl(System.stacktrace)) }
|
||||
{ __ENV__.line, hd(tl(System.stacktrace)) }
|
||||
end
|
||||
|
||||
defp fill_in_stacktrace do
|
||||
@@ -43,7 +49,7 @@ defmodule Clause do
|
||||
end
|
||||
|
||||
defmodule EExTest do
|
||||
use ExUnit.Case
|
||||
use ExUnit.Case, async: true
|
||||
|
||||
test "evaluates simple string" do
|
||||
assert_eval "foo bar", "foo bar"
|
||||
@@ -134,7 +140,7 @@ foo
|
||||
|
||||
string = """
|
||||
foo
|
||||
<%= __LINE__ %>
|
||||
<%= __ENV__.line %>
|
||||
"""
|
||||
|
||||
assert_eval expected, string
|
||||
@@ -152,9 +158,9 @@ foo
|
||||
string = """
|
||||
foo
|
||||
<%= if true do %>
|
||||
<%= __LINE__ %>
|
||||
<%= __ENV__.line %>
|
||||
<% end %>
|
||||
<%= __LINE__ %>
|
||||
<%= __ENV__.line %>
|
||||
"""
|
||||
|
||||
assert_eval expected, string
|
||||
@@ -171,10 +177,10 @@ true
|
||||
|
||||
string = """
|
||||
foo
|
||||
<%= if __LINE__ == 2 do %>
|
||||
<%= if __ENV__.line == 2 do %>
|
||||
<%= true %>
|
||||
<% end %>
|
||||
<%= __LINE__ %>
|
||||
<%= __ENV__.line %>
|
||||
"""
|
||||
|
||||
assert_eval expected, string
|
||||
@@ -192,11 +198,11 @@ true
|
||||
string = """
|
||||
foo
|
||||
<%= cond do %>
|
||||
<& false -> &> false
|
||||
<& __LINE__ == 4 -> &>
|
||||
<% false -> %> false
|
||||
<% __ENV__.line == 4 -> %>
|
||||
<%= true %>
|
||||
<% end %>
|
||||
<%= __LINE__ %>
|
||||
<%= __ENV__.line %>
|
||||
"""
|
||||
|
||||
assert_eval expected, string
|
||||
@@ -214,11 +220,51 @@ foo
|
||||
string = """
|
||||
foo
|
||||
<%= if false do %>
|
||||
<%= __LINE__ %>
|
||||
<%= __ENV__.line %>
|
||||
<% else %>
|
||||
<%= __LINE__ %>
|
||||
<%= __ENV__.line %>
|
||||
<% end %>
|
||||
<%= __ENV__.line %>
|
||||
"""
|
||||
|
||||
assert_eval expected, string
|
||||
end
|
||||
|
||||
test "properly handle functions" do
|
||||
expected = """
|
||||
|
||||
Number 1
|
||||
|
||||
Number 2
|
||||
|
||||
Number 3
|
||||
|
||||
"""
|
||||
|
||||
string = """
|
||||
<%= Enum.map [1,2,3], fn x -> %>
|
||||
Number <%= x %>
|
||||
<% end %>
|
||||
"""
|
||||
|
||||
assert_eval expected, string
|
||||
end
|
||||
|
||||
test "do not consider already finished functions" do
|
||||
expected = """
|
||||
foo
|
||||
|
||||
true
|
||||
|
||||
"""
|
||||
|
||||
string = """
|
||||
foo
|
||||
<%= cond do %>
|
||||
<% false -> %> false
|
||||
<% fn -> 1 end -> %>
|
||||
<%= true %>
|
||||
<% end %>
|
||||
<%= __LINE__ %>
|
||||
"""
|
||||
|
||||
assert_eval expected, string
|
||||
@@ -270,12 +316,21 @@ foo
|
||||
}
|
||||
}
|
||||
|
||||
assert EExText.Compiled.other ==
|
||||
assert EExText.Compiled.unknown ==
|
||||
{ 25,
|
||||
{ EExText.Compiled,
|
||||
:unknown,
|
||||
0,
|
||||
[file: 'unknown', line: 24]
|
||||
}
|
||||
}
|
||||
|
||||
assert EExText.Compiled.other ==
|
||||
{ 31,
|
||||
{ EExText.Compiled,
|
||||
:other,
|
||||
0,
|
||||
[file: 'unknown', line: 24]
|
||||
[file: binary_to_list(__FILE__), line: 30]
|
||||
}
|
||||
}
|
||||
end
|
||||
@@ -1,144 +0,0 @@
|
||||
defmodule EEx.Tokenizer do
|
||||
@moduledoc false
|
||||
|
||||
@doc """
|
||||
Tokenizes the given char list or binary.
|
||||
It returns 4 different types of tokens as result:
|
||||
|
||||
* { :text, line, contents }
|
||||
* { :expr, line, marker, contents }
|
||||
* { :start_expr, line, marker, contents }
|
||||
* { :end_expr, line, marker, contents }
|
||||
|
||||
"""
|
||||
def tokenize(bin, line) when is_binary(bin) do
|
||||
tokenize(binary_to_list(bin), line)
|
||||
end
|
||||
|
||||
def tokenize(list, line) do
|
||||
List.reverse(tokenize(list, line, line, [], []))
|
||||
end
|
||||
|
||||
defp tokenize([?<,h|t], current_line, line, buffer, acc) when h in [?&, ?%] do
|
||||
{ marker, t } = retrieve_marker(h, t)
|
||||
{ expr, new_line, rest } = tokenize_expr h, t, line, []
|
||||
|
||||
token = token_name(h, expr)
|
||||
expr = List.reverse(expr)
|
||||
|
||||
# If it isn't a start or end token, it may be a middle token.
|
||||
if token == :expr do
|
||||
token = middle_expr_token_name(expr)
|
||||
end
|
||||
|
||||
acc = tokenize_text(current_line, buffer, acc)
|
||||
tokenize rest, new_line, new_line, [], [ { token, line, marker, expr } | acc]
|
||||
end
|
||||
|
||||
defp tokenize('\n' ++ t, current_line, line, buffer, acc) do
|
||||
tokenize t, current_line, line + 1, [?\n|buffer], acc
|
||||
end
|
||||
|
||||
defp tokenize([h|t], current_line, line, buffer, acc) do
|
||||
tokenize t, current_line, line, [h|buffer], acc
|
||||
end
|
||||
|
||||
defp tokenize([], current_line, _line, buffer, acc) do
|
||||
tokenize_text(current_line, buffer, acc)
|
||||
end
|
||||
|
||||
# Retrieve marker for <%, <& is ignored
|
||||
|
||||
defp retrieve_marker(?%, '=' ++ t) do
|
||||
{ '=', t }
|
||||
end
|
||||
|
||||
defp retrieve_marker(_, t) do
|
||||
{ '', t }
|
||||
end
|
||||
|
||||
# Tokenize an expression until we find %> or &>
|
||||
|
||||
defp tokenize_expr(char, [char,?>|t], line, buffer) do
|
||||
{ buffer, line, t }
|
||||
end
|
||||
|
||||
defp tokenize_expr(char, '\n' ++ t, line, buffer) do
|
||||
tokenize_expr char, t, line + 1, [?\n|buffer]
|
||||
end
|
||||
|
||||
defp tokenize_expr(char, [h|t], line, buffer) do
|
||||
tokenize_expr char, t, line, [h|buffer]
|
||||
end
|
||||
|
||||
# Raise an error if the expected token is not found
|
||||
|
||||
defp tokenize_expr(char, [], _line, _buffer) do
|
||||
raise EEx.SyntaxError, message: "missing token: " <> <<char, ?>>>
|
||||
end
|
||||
|
||||
# Receive an expression content and check
|
||||
# if it is a start, middle or an end token.
|
||||
#
|
||||
# Start tokens finish with `do` and '->'
|
||||
# Middle tokens are marked as <& or keywords
|
||||
# End tokens contain only the end word
|
||||
|
||||
defp token_name(?&, _), do: :middle_expr
|
||||
defp token_name(?%, rest), do: token_name(rest)
|
||||
|
||||
defp token_name([h|t]) when h in [?\s, ?\t] do
|
||||
token_name(t)
|
||||
end
|
||||
|
||||
defp token_name('od' ++ [h|_]) when h in [?\s, ?\t, ?)] do
|
||||
:start_expr
|
||||
end
|
||||
|
||||
defp token_name('>-' ++ _) do
|
||||
:start_expr
|
||||
end
|
||||
|
||||
defp token_name('dne' ++ t) do
|
||||
if only_spaces?(t), do: :end_expr, else: :expr
|
||||
end
|
||||
|
||||
defp token_name(_) do
|
||||
:expr
|
||||
end
|
||||
|
||||
# Receive an expression contents and see if it matches
|
||||
# a keyword block syntax, like else.
|
||||
|
||||
defp middle_expr_token_name([h|t]) when h in [?\s, ?\t] do
|
||||
middle_expr_token_name(t)
|
||||
end
|
||||
|
||||
defp middle_expr_token_name([h|_] = list) when h >= ?a and h <= ?z do
|
||||
if valid_middle_identifier?(list), do: :middle_expr, else: :expr
|
||||
end
|
||||
|
||||
defp middle_expr_token_name(_) do
|
||||
:expr
|
||||
end
|
||||
|
||||
defp valid_middle_identifier?('else' ++ rest), do: only_spaces?(rest)
|
||||
defp valid_middle_identifier?('after' ++ rest), do: only_spaces?(rest)
|
||||
defp valid_middle_identifier?('catch' ++ rest), do: only_spaces?(rest)
|
||||
defp valid_middle_identifier?('rescue' ++ rest), do: only_spaces?(rest)
|
||||
defp valid_middle_identifier?(_), do: false
|
||||
|
||||
defp only_spaces?([h|t]) when h in [?\s, ?\t], do: only_spaces?(t)
|
||||
defp only_spaces?(other), do: other == []
|
||||
|
||||
# Tokenize the buffered text by appending
|
||||
# it to the given accumulator.
|
||||
|
||||
defp tokenize_text(_line, [], acc) do
|
||||
acc
|
||||
end
|
||||
|
||||
defp tokenize_text(line, buffer, acc) do
|
||||
[{ :text, line, list_to_binary(List.reverse(buffer)) } | acc]
|
||||
end
|
||||
end
|
||||
@@ -1,106 +0,0 @@
|
||||
defmodule Elixir.IEx.UnicodeIO do
|
||||
@moduledoc false
|
||||
|
||||
@doc """
|
||||
Implements the get IO API used by IEx. It receives the
|
||||
code cache, the instructions counter and needs to
|
||||
return a list with the new characters inserted.
|
||||
"""
|
||||
def get(cache, _count) do
|
||||
prompt = case cache do
|
||||
[] -> "iex> "
|
||||
_ -> "...> "
|
||||
end
|
||||
:unicode.characters_to_list(Erlang.io.get_line(prompt))
|
||||
end
|
||||
|
||||
@doc """
|
||||
Implements the put IO API used by IEx. It receives the
|
||||
result and prints it.
|
||||
"""
|
||||
def put(result) do
|
||||
IO.inspect result
|
||||
end
|
||||
|
||||
@doc """
|
||||
Implements the error IO API used by IEx. It prints error
|
||||
messages.
|
||||
"""
|
||||
def error(result) do
|
||||
IO.puts :standard_error, result
|
||||
end
|
||||
end
|
||||
|
||||
defrecord Elixir.IEx.Config, io: nil, binding: nil, cache: '', counter: 0, scope: nil
|
||||
|
||||
defmodule Elixir.IEx do
|
||||
@moduledoc false
|
||||
|
||||
import Exception, only: [format_stacktrace: 1]
|
||||
|
||||
def start(binding // [], io // Elixir.IEx.UnicodeIO) do
|
||||
config = boot_config(binding, io)
|
||||
function = fn -> do_loop(config) end
|
||||
Erlang.user_drv.start([:"tty_sl -c -e", {:erlang, :spawn, [function]}])
|
||||
end
|
||||
|
||||
def simple_start(binding // [], io // Elixir.IEx.UnicodeIO) do
|
||||
config = boot_config(binding, io)
|
||||
do_loop(config)
|
||||
end
|
||||
|
||||
def c(files, path // ".") do
|
||||
tuples = Elixir.ParallelCompiler.files_to_path List.wrap(files), path
|
||||
Enum.map tuples, elem(&1, 1)
|
||||
end
|
||||
|
||||
## Helpers
|
||||
|
||||
defp boot_config(binding, io) do
|
||||
IO.puts "Interactive Elixir (#{System.version}) - press Ctrl+C to exit"
|
||||
|
||||
scope = Erlang.elixir.scope_for_eval(
|
||||
file: 'iex',
|
||||
delegate_locals_to: __MODULE__
|
||||
)
|
||||
|
||||
Elixir.IEx.Config.new(io: io, binding: binding, scope: scope)
|
||||
end
|
||||
|
||||
defp do_loop(config) do
|
||||
io = config.io
|
||||
|
||||
config = config.increment_counter
|
||||
counter = config.counter
|
||||
cache = config.cache
|
||||
code = cache ++ io.get(cache, counter)
|
||||
|
||||
new_config =
|
||||
try do
|
||||
{ result, new_binding, scope } =
|
||||
Erlang.elixir.eval(code, config.binding, counter, config.scope)
|
||||
io.put result
|
||||
config.binding(new_binding).cache('').scope(scope)
|
||||
rescue
|
||||
TokenMissingError ->
|
||||
config.cache(code)
|
||||
exception ->
|
||||
stacktrace = System.stacktrace
|
||||
io.error "** (#{inspect exception.__record__(:name)}) #{exception.message}"
|
||||
print_stacktrace io, stacktrace
|
||||
config.cache('')
|
||||
catch
|
||||
kind, error ->
|
||||
stacktrace = System.stacktrace
|
||||
io.error "** (#{kind}) #{inspect(error)}"
|
||||
print_stacktrace io, stacktrace
|
||||
config.cache('')
|
||||
end
|
||||
|
||||
do_loop(new_config)
|
||||
end
|
||||
|
||||
defp print_stacktrace(io, stacktrace) do
|
||||
Enum.each stacktrace, fn s -> io.error " #{format_stacktrace(s)}" end
|
||||
end
|
||||
end
|
||||
@@ -1,8 +1,6 @@
|
||||
-define(ELIXIR_WRAP_CALL(Line, Module, Function, Args),
|
||||
{ call, Line,
|
||||
{ remote, Line,
|
||||
{ record_field, 1, { atom, 1, '' }, { atom, Line, Module } },
|
||||
{ atom, Line, Function } },
|
||||
{ remote, Line, { atom, Line, Module }, { atom, Line, Function } },
|
||||
Args
|
||||
}).
|
||||
|
||||
@@ -10,25 +8,25 @@
|
||||
-define(ELIXIR_MACRO(Macro), list_to_atom(lists:concat(['MACRO-',Macro]))).
|
||||
|
||||
-record(elixir_scope, {
|
||||
assign=false, %% when true, new variables can be defined in that subtree
|
||||
guard=false, %% when true, we are inside a guard
|
||||
noref=false, %% when true, don't resolve references
|
||||
context=nil, %% can be assign, guards or nil
|
||||
noname=false, %% when true, don't add new names (used by try)
|
||||
check_clauses=true, %% when true, check def clauses ordering
|
||||
super=false, %% when true, it means super was invoked
|
||||
caller=false, %% when true, it means caller was invoked
|
||||
name_args=false, %% when true, it means arguments should be named
|
||||
macro=[], %% a stack with macros nesting
|
||||
function=[], %% the current function
|
||||
recur=[], %% the current loop function to be recurred
|
||||
module=[], %% the current module
|
||||
module=nil, %% the current module
|
||||
function=nil, %% the current function
|
||||
recur=nil, %% the current loop function to be recurred
|
||||
vars=dict:new(), %% a dict of defined variables and their alias
|
||||
temp_vars=dict:new(), %% a dict of all variables defined in a particular assign
|
||||
clause_vars=dict:new(), %% a dict of all variables defined in a particular clause
|
||||
quote_vars=dict:new(), %% a dict of all quoted variables
|
||||
extra_guards=nil, %% extra guards from args expansion
|
||||
counter=0, %% a counter for the variables defined
|
||||
filename="nofile", %% the current scope filename
|
||||
local=[], %% the scope to evaluate local functions against
|
||||
refer=[], %% an orddict with references by new -> old names
|
||||
file=(<<"nofile">>), %% the current scope filename
|
||||
local=nil, %% the scope to evaluate local functions against
|
||||
aliases=[], %% an orddict with aliases by new -> old names
|
||||
requires=elixir_dispatch:default_requires(), %% a set with modules required
|
||||
macros=elixir_dispatch:default_macros(), %% a list with macros imported by module
|
||||
functions=elixir_dispatch:default_functions(), %% a list with functions imported by module
|
||||
@@ -0,0 +1,66 @@
|
||||
import Kernel, except: [access: 2]
|
||||
|
||||
defprotocol Access do
|
||||
@moduledoc """
|
||||
The Access protocol is the underlying protocol invoked
|
||||
when the brackets syntax is used. For instance, `foo[bar]`
|
||||
is translated to `access foo, bar` which, by default,
|
||||
invokes `Access.access` protocol.
|
||||
|
||||
This protocol is limited and is implemented only for the
|
||||
following built-in types: keywords, tuples, atoms and
|
||||
functions.
|
||||
"""
|
||||
|
||||
@only [List, Function, Record, Atom]
|
||||
|
||||
@doc """
|
||||
Receives the element being accessed and the access item.
|
||||
"""
|
||||
def access(element, qualifier)
|
||||
end
|
||||
|
||||
defimpl Access, for: List do
|
||||
@doc """
|
||||
Access the given key in a keywords list.
|
||||
|
||||
## Examples
|
||||
|
||||
keywords = [a: 1, b: 2]
|
||||
keywords[:a] #=> 1
|
||||
|
||||
"""
|
||||
|
||||
def access(list, atom) when is_atom(atom) do
|
||||
atom_access(list, atom)
|
||||
end
|
||||
|
||||
defp atom_access([{k, _}|_], key) when key < k, do: nil
|
||||
defp atom_access([{k, _}|d], key) when key > k, do: atom_access(d, key)
|
||||
defp atom_access([{_k, value}|_], _key), do: value
|
||||
defp atom_access([], _), do: nil
|
||||
end
|
||||
|
||||
defimpl Access, for: Atom do
|
||||
@doc """
|
||||
The access protocol can only be accessed by atoms
|
||||
at compilation time. If we reach this, we should raise
|
||||
an exception.
|
||||
"""
|
||||
def access(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
|
||||
@@ -3,14 +3,6 @@ defmodule Binary do
|
||||
Functions for working with binaries.
|
||||
"""
|
||||
|
||||
@doc """
|
||||
Simply invokes the Access protocol for the given binary.
|
||||
Check `Access.BitString` for more information.
|
||||
"""
|
||||
def access(binary, access) when is_binary(binary) do
|
||||
Access.BitString.access(binary, access)
|
||||
end
|
||||
|
||||
@doc %B"""
|
||||
Receives a char list and escapes all special chars (like \n)
|
||||
and interpolation markers. A last argument is given and wraps
|
||||
@@ -37,22 +29,22 @@ defmodule Binary do
|
||||
"""
|
||||
|
||||
# Allow basic ascii chars
|
||||
def printable?(<<c, t|:binary>>) when c >= ?\s and c <= ?~ do
|
||||
def printable?(<<c, t|:binary>>) when c in ?\s..?~ do
|
||||
printable?(t)
|
||||
end
|
||||
|
||||
# From 16#A0 to 16#BF
|
||||
def printable?(<<194, c, t|:binary>>) when c >= 160 and c <= 191 do
|
||||
def printable?(<<194, c, t|:binary>>) when c in 160..191 do
|
||||
printable?(t)
|
||||
end
|
||||
|
||||
# From 16#C0 to 16#7FF
|
||||
def printable?(<<m, o1, t|:binary>>) when m >= 195 and m <= 223 and o1 >= 128 and o1 < 192 do
|
||||
def printable?(<<m, o1, t|:binary>>) when m in 195..223 and o1 in 128..191 do
|
||||
printable?(t)
|
||||
end
|
||||
|
||||
# From 16#800 to 16#CFFF
|
||||
def printable?(<<m, o1, o2, t|:binary>>) when m >= 224 and m <= 236 and
|
||||
def printable?(<<m, o1, o2, t|:binary>>) when m in 224..236 and
|
||||
o1 >= 128 and o1 < 192 and o2 >= 128 and o2 < 192 do
|
||||
printable?(t)
|
||||
end
|
||||
@@ -70,19 +62,19 @@ defmodule Binary do
|
||||
|
||||
# From 16#E000 to 16#EFFF
|
||||
def printable?(<<m, o1, o2, t|:binary>>) when (m == 238 or m == 239) and
|
||||
o1 >= 128 and o1 < 192 and o2 >= 128 and o2 < 192 do
|
||||
o1 in 128..191 and o2 in 128..191 do
|
||||
printable?(t)
|
||||
end
|
||||
|
||||
# From 16#F000 to 16#FFFD
|
||||
def printable?(<<239, o1, o2, t|:binary>>) when
|
||||
o1 >= 128 and o1 < 192 and o2 >= 128 and o2 < 192 do
|
||||
o1 in 128..191 and o2 in 128..191 do
|
||||
printable?(t)
|
||||
end
|
||||
|
||||
# From 16#10000 to 16#3FFFF
|
||||
def printable?(<<240, o1, o2, o3, t|:binary>>) when
|
||||
o1 >= 144 and o1 < 192 and o2 >= 128 and o2 < 192 and o3 >= 128 and o3 < 192 do
|
||||
o1 in 144..191 and o2 in 128..191 and o3 in 128..191 do
|
||||
printable?(t)
|
||||
end
|
||||
|
||||
@@ -92,8 +84,8 @@ defmodule Binary do
|
||||
end
|
||||
|
||||
# From 16#4000 to 16#10FFFF
|
||||
def printable?(<<m, o1, o2, o3, t|:binary>>) when m >= 241 and m <= 244 and
|
||||
o1 >= 128 and o1 < 192 and o2 >= 128 and o2 < 192 and o3 >= 128 and o3 < 192 do
|
||||
def printable?(<<m, o1, o2, o3, t|:binary>>) when m in 241..244 and
|
||||
o1 in 128..191 and o2 in 128..191 and o3 in 128..191 do
|
||||
printable?(t)
|
||||
end
|
||||
|
||||
@@ -154,6 +146,11 @@ defmodule Binary do
|
||||
If the `unescape_map` function returns false. The char is
|
||||
not escaped and `\` is kept in the char list.
|
||||
|
||||
## Octals
|
||||
|
||||
Octals will by default be escaped unless the map function
|
||||
returns false for ?0.
|
||||
|
||||
## Examples
|
||||
|
||||
Using the unescape_map defined above is easy:
|
||||
@@ -171,7 +168,7 @@ defmodule Binary do
|
||||
about unescaping. Only tokens that are char lists are
|
||||
unescaped, all others are ignored. This method is useful
|
||||
when implementing your own sigils. Check the implementation
|
||||
of `Elixir::Builtin.__b__` for examples.
|
||||
of `Kernel.__b__` for examples.
|
||||
"""
|
||||
def unescape_tokens(tokens) do
|
||||
Erlang.elixir_interpolation.unescape_tokens(tokens)
|
||||
@@ -1,4 +1,4 @@
|
||||
import Elixir.Builtin, except: [to_binary: 1]
|
||||
import Kernel, except: [to_binary: 1]
|
||||
|
||||
defprotocol Binary.Chars do
|
||||
@moduledoc %B"""
|
||||
@@ -8,7 +8,7 @@ defprotocol Binary.Chars do
|
||||
`to_binary` which does the conversion.
|
||||
|
||||
The `to_binary` function automatically imported
|
||||
by Elixir.Builtin invokes this protocol. String
|
||||
by Kernel invokes this protocol. String
|
||||
interpolation also invokes to_binary in its
|
||||
arguments. For example, `"foo#{bar}"` is the same
|
||||
as `"foo" <> to_binary(bar)`.
|
||||
@@ -1,4 +1,4 @@
|
||||
import Elixir.Builtin, except: [inspect: 1]
|
||||
import Kernel, except: [inspect: 1]
|
||||
|
||||
defprotocol Binary.Inspect do
|
||||
@moduledoc """
|
||||
@@ -16,12 +16,16 @@ defprotocol Binary.Inspect do
|
||||
end
|
||||
|
||||
defimpl Binary.Inspect, for: Atom do
|
||||
require Macro
|
||||
|
||||
@doc """
|
||||
Represents the atom as an Elixir term.
|
||||
The atoms false, true and nil are simply
|
||||
quoted. Modules are properly represented
|
||||
Represents the atom as an Elixir term. The atoms false, true
|
||||
and nil are simply quoted. Modules are properly represented
|
||||
as modules using the dot notation.
|
||||
|
||||
Notice that in Elixir, all operators can be represented using
|
||||
literal atoms (`:+`, `:-`, etc).
|
||||
|
||||
## Examples
|
||||
|
||||
inspect(:foo) #=> ":foo"
|
||||
@@ -29,47 +33,64 @@ defimpl Binary.Inspect, for: Atom do
|
||||
inspect(Foo.Bar) #=> "Foo.Bar"
|
||||
|
||||
"""
|
||||
def inspect(false), do: "false"
|
||||
def inspect(true), do: "true"
|
||||
def inspect(nil), do: "nil"
|
||||
def inspect(:""), do: ":\"\""
|
||||
def inspect(false), do: "false"
|
||||
def inspect(true), do: "true"
|
||||
def inspect(nil), do: "nil"
|
||||
def inspect(:""), do: ":\"\""
|
||||
def inspect(Elixir), do: "Elixir"
|
||||
|
||||
def inspect(atom) do
|
||||
binary = atom_to_binary(atom)
|
||||
|
||||
cond do
|
||||
valid_identifier?(binary) == <<>> ->
|
||||
":" <> binary
|
||||
valid_ref_identifier?(binary) == <<>> ->
|
||||
"__MAIN__." <> rest = binary
|
||||
rest
|
||||
valid_atom_identifier?(binary) ->
|
||||
":" <> binary
|
||||
valid_ref_identifier?(binary) ->
|
||||
"Elixir-" <> rest = binary
|
||||
bc <<r>> inbits rest, do: <<to_dot(r)>>
|
||||
atom in Macro.binary_ops or atom in Macro.unary_ops ->
|
||||
":" <> binary
|
||||
true ->
|
||||
":" <> Binary.escape(binary, ?")
|
||||
end
|
||||
end
|
||||
|
||||
# Detect if atom is a module reference (__MAIN__.Foo.Bar.Baz)
|
||||
# Detect if atom is an atom alias (Elixir-Foo-Bar-Baz)
|
||||
|
||||
defp valid_ref_identifier?("__MAIN__" <> rest) do
|
||||
defp to_dot(?-), do: ?.
|
||||
defp to_dot(l), do: l
|
||||
|
||||
defp valid_ref_identifier?("Elixir" <> rest) do
|
||||
valid_ref_piece?(rest)
|
||||
end
|
||||
|
||||
defp valid_ref_identifier?(rest) do
|
||||
rest
|
||||
end
|
||||
defp valid_ref_identifier?(_), do: false
|
||||
|
||||
defp valid_ref_piece?(<<?., h, t|:binary>>) when h >= ?A and h <= ?Z do
|
||||
defp valid_ref_piece?(<<?-, h, t|:binary>>) when h in ?A..?Z do
|
||||
valid_ref_piece? valid_identifier?(t)
|
||||
end
|
||||
|
||||
defp valid_ref_piece?(other), do: other
|
||||
defp valid_ref_piece?(<<>>), do: true
|
||||
defp valid_ref_piece?(_), do: false
|
||||
|
||||
# Detect if atom is :letter_or_underscore
|
||||
# Detect if atom
|
||||
|
||||
defp valid_identifier?(<<h, t|:binary>>) \
|
||||
when h >= ?a and h <= ?z \
|
||||
when h >= ?A and h <= ?Z \
|
||||
when h == ?_ do
|
||||
defp valid_atom_identifier?(<<h, t|:binary>>) when h in ?a..?z or h in ?A..?Z or h == ?_ do
|
||||
case valid_identifier?(t) do
|
||||
<<>> -> true
|
||||
<<??>> -> true
|
||||
<<?!>> -> true
|
||||
_ -> false
|
||||
end
|
||||
end
|
||||
|
||||
defp valid_atom_identifier?(_), do: false
|
||||
|
||||
defp valid_identifier?(<<h, t|:binary>>)
|
||||
when h in ?a..?z
|
||||
when h in ?A..?Z
|
||||
when h in ?0..?9
|
||||
when h == ?_ do
|
||||
valid_identifier? t
|
||||
end
|
||||
|
||||
@@ -119,6 +140,12 @@ defimpl Binary.Inspect, for: List do
|
||||
If so, a single-quoted representation is returned,
|
||||
otherwise the brackets syntax is used.
|
||||
|
||||
Inspecting a list is conservative as it does not try
|
||||
to guess how the list is encoded. That said, `'josé'`
|
||||
will likely be inspected as `[106,111,115,195,169]`
|
||||
because we can't know if it is encoded in utf-8
|
||||
or iso-5569-1, which is common in Erlang libraries.
|
||||
|
||||
## Examples
|
||||
|
||||
inspect('bar') #=> 'bar'
|
||||
@@ -130,7 +157,7 @@ defimpl Binary.Inspect, for: List do
|
||||
def inspect([]), do: "[]"
|
||||
|
||||
def inspect(thing) do
|
||||
if Erlang.io_lib.printable_list(thing) do
|
||||
if printable?(thing) do
|
||||
Binary.escape(list_to_binary(thing), ?')
|
||||
else
|
||||
container_join(thing, "[", "]")
|
||||
@@ -155,6 +182,19 @@ defimpl Binary.Inspect, for: List do
|
||||
def container_join([], acc, last) do
|
||||
acc <> last
|
||||
end
|
||||
|
||||
## printable?
|
||||
|
||||
defp printable?([c|cs]) when is_integer(c) and c in 32..126 do
|
||||
printable?(cs)
|
||||
end
|
||||
|
||||
defp printable?([c|cs]) when c in [?\n, ?\r, ?\t, ?\v, ?\b, ?\f, ?\e] do
|
||||
printable?(cs)
|
||||
end
|
||||
|
||||
defp printable?([]), do: true
|
||||
defp printable?(_), do: false
|
||||
end
|
||||
|
||||
defimpl Binary.Inspect, for: Tuple do
|
||||
@@ -172,7 +212,7 @@ defimpl Binary.Inspect, for: Tuple do
|
||||
|
||||
def inspect(exception) when is_exception(exception) do
|
||||
[name,_|tail] = tuple_to_list(exception)
|
||||
[_|fields] = lc { field, _ } in name.__record__(:fields), do: field
|
||||
[_|fields] = lc { field, _ } inlist name.__record__(:fields), do: field
|
||||
Binary.Inspect.Atom.inspect(name) <> records_join(fields, tail, "[", "]")
|
||||
end
|
||||
|
||||
@@ -181,8 +221,12 @@ defimpl Binary.Inspect, for: Tuple do
|
||||
[name|tail] = list
|
||||
|
||||
if is_record?(name) do
|
||||
fields = lc { field, _ } in name.__record__(:fields), do: field
|
||||
Binary.Inspect.Atom.inspect(name) <> records_join(fields, tail, "[", "]")
|
||||
fields = lc { field, _ } inlist name.__record__(:fields), do: field
|
||||
if length(fields) != size(thing) - 1 do
|
||||
Binary.Inspect.List.container_join(list, "{", "}")
|
||||
else
|
||||
Binary.Inspect.Atom.inspect(name) <> records_join(fields, tail, "[", "]")
|
||||
end
|
||||
else
|
||||
Binary.Inspect.List.container_join(list, "{", "}")
|
||||
end
|
||||
@@ -191,8 +235,8 @@ defimpl Binary.Inspect, for: Tuple do
|
||||
## Helpers
|
||||
|
||||
defp is_record?(name) do
|
||||
is_atom(name) and match?("__MAIN__." <> _, atom_to_binary(name, :utf8)) and
|
||||
:erlang.function_exported(name, :__record__, 1)
|
||||
is_atom(name) and match?("Elixir-" <> _, atom_to_binary(name, :utf8)) and
|
||||
function_exported?(name, :__record__, 1)
|
||||
end
|
||||
|
||||
defp records_join([f], [v], acc, last) do
|
||||
@@ -0,0 +1,126 @@
|
||||
defmodule Bitwise do
|
||||
@moduledoc """
|
||||
This module provide macros and operators for bitwise operators.
|
||||
These macros can be used in guards.
|
||||
|
||||
The easiest way to use is to simply import them into
|
||||
your module:
|
||||
|
||||
use Bitwise
|
||||
|
||||
bnot 1 #=> -2
|
||||
1 &&& 1 #=> 1
|
||||
|
||||
You can select to include only or skip operators by passing options:
|
||||
|
||||
use Bitwise, only_operators: true
|
||||
1 &&& 1 #=> 1
|
||||
|
||||
"""
|
||||
|
||||
@doc """
|
||||
Allow a developer to use this module in their programs with
|
||||
the following options:
|
||||
|
||||
* `:only_operators` - Include only operators;
|
||||
* `:skip_operators` - Skip operators;
|
||||
|
||||
"""
|
||||
defmacro __using__(options) do
|
||||
except = cond do
|
||||
Keyword.get(options, :only_operators) ->
|
||||
[:bnot, :band, :bor, :bxor, :bsl, :bsr]
|
||||
Keyword.get(options, :skip_operators) ->
|
||||
[:~~~, :&&&, :|||, :^^^, :<<<, :>>>]
|
||||
true -> []
|
||||
end
|
||||
|
||||
quote do
|
||||
import Bitwise, except: unquote(except)
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Bitwise not.
|
||||
"""
|
||||
defmacro bnot(expr) do
|
||||
quote do: __op__ :bnot, unquote(expr)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Bitwise not as operator.
|
||||
"""
|
||||
defmacro ~~~expr do
|
||||
quote do: __op__ :bnot, unquote(expr)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Bitwise and.
|
||||
"""
|
||||
defmacro band(left, right) do
|
||||
quote do: __op__ :band, unquote(left), unquote(right)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Bitwise and as operator.
|
||||
"""
|
||||
defmacro left &&& right do
|
||||
quote do: __op__ :band, unquote(left), unquote(right)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Bitwise or.
|
||||
"""
|
||||
defmacro bor(left, right) do
|
||||
quote do: __op__ :bor, unquote(left), unquote(right)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Bitwise or as operator.
|
||||
"""
|
||||
defmacro left ||| right do
|
||||
quote do: __op__ :bor, unquote(left), unquote(right)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Bitwise xor.
|
||||
"""
|
||||
defmacro bxor(left, right) do
|
||||
quote do: __op__ :bxor, unquote(left), unquote(right)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Bitwise xor as operator.
|
||||
"""
|
||||
defmacro left ^^^ right do
|
||||
quote do: __op__ :bxor, unquote(left), unquote(right)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Arithmetic bitshift left.
|
||||
"""
|
||||
defmacro bsl(left, right) do
|
||||
quote do: __op__ :bsl, unquote(left), unquote(right)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Arithmetic bitshift left as operator.
|
||||
"""
|
||||
defmacro left <<< right do
|
||||
quote do: __op__ :bsl, unquote(left), unquote(right)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Arithmetic bitshift right.
|
||||
"""
|
||||
defmacro bsr(left, right) do
|
||||
quote do: __op__ :bsr, unquote(left), unquote(right)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Arithmetic bitshift right as operator.
|
||||
"""
|
||||
defmacro left >>> right do
|
||||
quote do: __op__ :bsr, unquote(left), unquote(right)
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,314 @@
|
||||
defmodule Code do
|
||||
@moduledoc """
|
||||
The Code module is responsible to manage code compilation,
|
||||
code evaluation and code loading.
|
||||
|
||||
It complements (Erlang's code module)[1] to add behavior
|
||||
which is specific to Elixir.
|
||||
|
||||
[1]: (www.erlang.org/doc/man/code.html)
|
||||
|
||||
"""
|
||||
|
||||
@doc """
|
||||
Returns all the loaded files.
|
||||
"""
|
||||
def loaded_files do
|
||||
server_call :loaded
|
||||
end
|
||||
|
||||
@doc """
|
||||
Appends a path to Erlang VM code path.
|
||||
The path is expanded with `File.expand_path` before added.
|
||||
"""
|
||||
def append_path(path) do
|
||||
Erlang.code.add_pathz(File.expand_path to_char_list(path))
|
||||
end
|
||||
|
||||
@doc """
|
||||
Prepends a path to Erlang VM code path.
|
||||
The path is expanded with `File.expand_path` before added.
|
||||
"""
|
||||
def prepend_path(path) do
|
||||
Erlang.code.add_patha(File.expand_path to_char_list(path))
|
||||
end
|
||||
|
||||
@doc """
|
||||
Evalutes the contents given by string. The second argument is the binding
|
||||
(which should be a Keyword) followed by a keyword list of options. The
|
||||
options can be:
|
||||
|
||||
* `:file` - the file to be considered in the evaluation
|
||||
* `:line` - the line the script starts
|
||||
* `:delegate_locals_to` - delegate local calls to the given module,
|
||||
otherwise functions are evaluated inside Erlang's default scope.
|
||||
|
||||
## Examples
|
||||
|
||||
Code.eval "a + b", [a: 1, b: 2], file: __ENV__.file, line: __ENV__.line
|
||||
#=> { 3, [ {:a, 1}, {:b, 2} ] }
|
||||
|
||||
When passing the __ENV__'s file and line, we could simply get
|
||||
the location which already returns both fields as a keywords lists:
|
||||
|
||||
Code.eval "a + b", [a: 1, b: 2], __ENV__.location
|
||||
#=> { 3, [ {:a, 1}, {:b, 2} ] }
|
||||
|
||||
"""
|
||||
def eval(string, binding // [], opts // []) do
|
||||
{ value, binding, _scope } =
|
||||
Erlang.elixir.eval :unicode.characters_to_list(string), binding, opts
|
||||
{ value, binding }
|
||||
end
|
||||
|
||||
@doc """
|
||||
Evalutes the quoted contents.
|
||||
|
||||
## Options
|
||||
|
||||
This function accepts a list of options. The supported
|
||||
options are:
|
||||
|
||||
* `:file` - The filename to be used in stacktraces
|
||||
and the file reported in the __ENV__ variable.
|
||||
|
||||
* `:line` - The line reported in the __ENV__ variable.
|
||||
|
||||
## Examples
|
||||
|
||||
contents = quote hygiene: false, do: a + b
|
||||
|
||||
Code.eval_quoted contents, [a: 1, b: 2], file: __ENV__.file, line: __ENV__.line
|
||||
#=> { 3, [ {:a, 1}, {:b, 2} ] }
|
||||
|
||||
When passing the __ENV__'s file and line, we could simply get
|
||||
the location which already returns both fields as a keywords lists:
|
||||
|
||||
Code.eval_quoted contents, [a: 1, b: 2], __ENV__.location
|
||||
#=> { 3, [ {:a, 1}, {:b, 2} ] }
|
||||
|
||||
"""
|
||||
def eval_quoted(quoted, binding // [], opts // []) do
|
||||
{ value, binding, _scope } =
|
||||
Erlang.elixir.eval_quoted [quoted], binding, opts
|
||||
{ value, binding }
|
||||
end
|
||||
|
||||
@doc """
|
||||
Converts the given string to AST. It returns { :ok, ast }
|
||||
if it succeeds, { :error, { line, error, token } } otherwise.
|
||||
|
||||
## Options
|
||||
|
||||
* `:file` - The filename to be used in stacktraces
|
||||
and the file reported in the __ENV__ variable.
|
||||
|
||||
* `:line` - The line reported in the __ENV__ variable.
|
||||
|
||||
"""
|
||||
def string_to_ast(string, opts // []) do
|
||||
file = Keyword.get opts, :file, "nofile"
|
||||
line = Keyword.get opts, :line, 1
|
||||
res = :elixir_translator.raw_forms(:unicode.characters_to_list(string), line, file)
|
||||
case res do
|
||||
{ :ok, ast } -> { :ok, unpack_ast(line, ast) }
|
||||
_ -> res
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Converts the given string to AST. 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.
|
||||
|
||||
## Options
|
||||
|
||||
* `:file` - The filename to be used in stacktraces
|
||||
and the file reported in the __ENV__ variable.
|
||||
|
||||
* `:line` - The line reported in the __ENV__ variable.
|
||||
|
||||
"""
|
||||
def string_to_ast!(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)
|
||||
unpack_ast(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, 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 the full path of the loaded file.
|
||||
|
||||
When loading a file, you may skip passing .exs as extension as Elixir
|
||||
automatically adds it for you.
|
||||
"""
|
||||
def load_file(file, relative_to // nil) when is_binary(file) do
|
||||
file = find_file(file, relative_to)
|
||||
server_call { :loaded, file }
|
||||
Erlang.elixir_compiler.file file
|
||||
file
|
||||
end
|
||||
|
||||
@doc """
|
||||
Requires the given `file`. Accepts `relative_to` as an argument to tell
|
||||
where the file is located. If the file was already required/loaded,
|
||||
returns nil, otherwise the full path of the loaded file.
|
||||
|
||||
When requiring a file, you may skip passing .exs as extension as
|
||||
Elixir automatically adds it for you.
|
||||
"""
|
||||
def require_file(file, relative_to // nil) when is_binary(file) do
|
||||
file = find_file(file, relative_to)
|
||||
|
||||
case server_call({ :loaded, file }) do
|
||||
:ok -> Erlang.elixir_compiler.file file
|
||||
:duplicated -> []
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Loads the compilation options from the code server.
|
||||
Check compiler_options/1 for more information.
|
||||
"""
|
||||
def compiler_options do
|
||||
server_call :compiler_options
|
||||
end
|
||||
|
||||
@doc """
|
||||
Sets compilation options. Those options are global
|
||||
since they are stored by Elixir's Code Server.
|
||||
|
||||
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;
|
||||
|
||||
"""
|
||||
def compiler_options(opts) do
|
||||
server_call { :compiler_options, opts }
|
||||
end
|
||||
|
||||
@doc """
|
||||
Compiles the given string and returns a list of tuples where
|
||||
the first element is the module name and the second one is its
|
||||
binary.
|
||||
|
||||
For compiling many files at once, check `Kernel.ParallelCompiler`.
|
||||
"""
|
||||
def compile_string(string, file // "nofile") when is_binary(file) do
|
||||
Erlang.elixir_compiler.string :unicode.characters_to_list(string), to_binary(file)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Ensures the given module is loaded. If the module is already
|
||||
loaded, it works as no-op. If the module was not loaded yet,
|
||||
it tries to load it.
|
||||
|
||||
If it succeeds loading the module anyhow, it returns
|
||||
`{ :module, module }`. If not, returns `{ :error, reason }` with
|
||||
the error reason.
|
||||
|
||||
## Code loading on the Erlang VM
|
||||
|
||||
Erlang has two modes to load code: interactive and embedded.
|
||||
|
||||
By default, the Erlang VM runs on interactive mode, where modules
|
||||
are loaded as needed. In embedded mode the opposite happens, as all
|
||||
modules need to be loaded upfront or explicitly.
|
||||
|
||||
Therefore, this function is useful to check if a module is loaded
|
||||
before using it and react accordingly. For example, the `URI` module
|
||||
uses this function to check if a specific parser exists and is for the
|
||||
given URI scheme.
|
||||
|
||||
## Code.ensure_compiled
|
||||
|
||||
Elixir also contains an `ensure_compiled/1` function that is a
|
||||
superset of `ensure_loaded/1`.
|
||||
|
||||
Since Elixir's compilation happens in parallel, in some situations
|
||||
you may need to use a module but it was not compiled yet, therefore
|
||||
it can't even be loaded.
|
||||
|
||||
`ensure_compiled/1` puts a halt in the current process until the
|
||||
module we are depending on is available.
|
||||
|
||||
In most of the cases, `ensure_loaded` is enough. `ensure_compiled`
|
||||
must be used just in same rare conditions, usually involving macros
|
||||
that needs to invoke a module for callback information.
|
||||
"""
|
||||
def ensure_loaded(module) when is_atom(module) do
|
||||
Erlang.code.ensure_loaded(module)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Ensures the given module is compiled and loaded. If the module
|
||||
is already loaded, it works as no-op. If the module was not
|
||||
loaded yet, it checks if it needs to be compiled first and just
|
||||
then tries to load it.
|
||||
|
||||
If it succeeds loading the module anyhow, it returns
|
||||
`{ :module, module }`. If not, returns `{ :error, reason }` with
|
||||
the error reason.
|
||||
|
||||
Check `ensure_loaded/1` for more information on module loading
|
||||
and when to use `ensure_loaded/1` or `ensure_compiled/1`.
|
||||
"""
|
||||
def ensure_compiled(module) when is_atom(module) do
|
||||
case Erlang.code.ensure_loaded(module) do
|
||||
{ :error, :nofile } = error ->
|
||||
case :erlang.get(:elixir_compiler_pid) do
|
||||
:undefined -> error
|
||||
_ ->
|
||||
try do
|
||||
module.__info__(:self)
|
||||
{ :module, module }
|
||||
rescue
|
||||
UndefinedFunctionError -> error
|
||||
end
|
||||
end
|
||||
other -> other
|
||||
end
|
||||
end
|
||||
|
||||
## Helpers
|
||||
|
||||
# Finds the file given the relative_to path.
|
||||
# If the file is found, returns its path in binary, fails otherwise.
|
||||
defp find_file(file, relative_to) do
|
||||
file = to_binary(file)
|
||||
|
||||
file = if relative_to do
|
||||
File.expand_path(file, relative_to)
|
||||
else
|
||||
File.expand_path(file)
|
||||
end
|
||||
|
||||
if File.regular?(file) do
|
||||
file
|
||||
else
|
||||
prefix = "#{file}.exs"
|
||||
if File.regular?(prefix) do
|
||||
prefix
|
||||
else
|
||||
raise ArgumentError, message: "could not load #{file}"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defp server_call(args) do
|
||||
Erlang.gen_server.call(:elixir_code_server, args)
|
||||
end
|
||||
end
|
||||
@@ -44,8 +44,8 @@ defprotocol Dict do
|
||||
|
||||
## Examples
|
||||
|
||||
Dict.has_key?([a:, 1], :a) #=> true
|
||||
Dict.has_key?([a:, 1], :b) #=> false
|
||||
Dict.has_key?([a: 1], :a) #=> true
|
||||
Dict.has_key?([a: 1], :b) #=> false
|
||||
|
||||
"""
|
||||
def has_key?(dict, key)
|
||||
@@ -1,7 +1,7 @@
|
||||
defmodule Dict.Common do
|
||||
@moduledoc false
|
||||
|
||||
defmacro __using__(_module, ref) do
|
||||
defmacro __using__(ref) do
|
||||
quote do
|
||||
@doc """
|
||||
Creates a new empty dict.
|
||||
@@ -15,7 +15,7 @@ defmodule Dict.Common do
|
||||
|
||||
## Examples
|
||||
|
||||
#{unquote(inspect(__MODULE__))}.new [{:b,1},{:a,2}]
|
||||
#{inspect(__MODULE__)}.new [{:b,1},{:a,2}]
|
||||
#=> [a: 1, b: 2]
|
||||
|
||||
"""
|
||||
@@ -31,7 +31,7 @@ defmodule Dict.Common do
|
||||
|
||||
## Examples
|
||||
|
||||
#{unquote(inspect(__MODULE__))}.new ["a", "b"], fn x -> {x, x} end
|
||||
#{inspect(__MODULE__)}.new ["a", "b"], fn x -> {x, x} end
|
||||
#=> ["a": "a", "b": "b"]
|
||||
"""
|
||||
def new(list, transform) when is_function(transform) do
|
||||
File diff suppressed because it is too large
Load Diff
@@ -56,6 +56,14 @@ defmodule Exception do
|
||||
ErlangError.new original: other
|
||||
end
|
||||
|
||||
# Check the given module is a valid record.
|
||||
@doc false
|
||||
def check!(module) do
|
||||
unless :erlang.function_exported(module, :message, 1) do
|
||||
raise "Expected #{inspect module} to implement message/1"
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Receives a module, fun and arity and returns a string
|
||||
representing such invocation. Arity may also be a list
|
||||
@@ -65,7 +73,7 @@ defmodule Exception do
|
||||
<< ?:, fun | :binary >> = inspect(fun)
|
||||
|
||||
if is_list(arity) do
|
||||
inspected = lc x in arity, do: inspect(x)
|
||||
inspected = lc x inlist arity, do: inspect(x)
|
||||
"#{inspect module}.#{fun}(#{Enum.join(inspected, ", ")})"
|
||||
else
|
||||
"#{inspect module}.#{fun}/#{arity}"
|
||||
@@ -194,4 +202,12 @@ defexception ErlangError, [original: nil] do
|
||||
def message(exception) do
|
||||
"erlang error: #{inspect(exception.original)}"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defexception Keyword.KeyError, key: nil do
|
||||
def message(exception) do
|
||||
"key not found: #{inspect exception.key}"
|
||||
end
|
||||
end
|
||||
|
||||
defexception Enum.OutOfBoundsError, message: "out of bounds error"
|
||||
File diff suppressed because it is too large
Load Diff
@@ -16,8 +16,8 @@ defmodule GenServer.Behavior do
|
||||
http://www.erlang.org/doc/design_principles/gen_server_concepts.html
|
||||
|
||||
"""
|
||||
defmacro __using__(_, _) do
|
||||
quote do
|
||||
defmacro __using__(_) do
|
||||
quote location: :keep do
|
||||
@behavior :gen_server
|
||||
|
||||
def handle_call(_request, _from, state) do
|
||||
@@ -0,0 +1,81 @@
|
||||
defimpl Dict, for: HashDict do
|
||||
defmacrop dict(data) do
|
||||
quote do
|
||||
{ HashDict, unquote(data) }
|
||||
end
|
||||
end
|
||||
|
||||
def keys(dict(data)) do
|
||||
:dict.fetch_keys data
|
||||
end
|
||||
|
||||
def values(dict(data)) do
|
||||
:dict.fold fn _key, value, acc ->
|
||||
[value|acc]
|
||||
end, [], data
|
||||
end
|
||||
|
||||
def size(dict(data)) do
|
||||
:dict.size data
|
||||
end
|
||||
|
||||
def has_key?(dict(data), key) do
|
||||
:dict.is_key key, data
|
||||
end
|
||||
|
||||
def get(dict(data), key, default // nil) do
|
||||
case :dict.find(key, data) do
|
||||
{:ok, value} ->
|
||||
value
|
||||
:error ->
|
||||
default
|
||||
end
|
||||
end
|
||||
|
||||
def put(dict(data), key, value) do
|
||||
dict(:dict.store key, value, data)
|
||||
end
|
||||
|
||||
def delete(dict(data), key) do
|
||||
dict(:dict.erase key, data)
|
||||
end
|
||||
|
||||
def merge(dict(d1), dict(d2)) do
|
||||
dict(:dict.merge fn _k, _v1, v2 -> v2 end, d1, d2)
|
||||
end
|
||||
|
||||
def merge(dict(d1), dict(d2), fun) do
|
||||
dict(:dict.merge fun, d1, d2)
|
||||
end
|
||||
|
||||
def update(dict(data), key, fun) do
|
||||
dict(:dict.update key, fun, data)
|
||||
end
|
||||
|
||||
def update(dict(data), key, initial, fun) do
|
||||
dict(:dict.update key, fun, initial, data)
|
||||
end
|
||||
|
||||
def empty(_) do
|
||||
dict(:dict.new)
|
||||
end
|
||||
|
||||
def to_list(dict(data)) do
|
||||
:dict.to_list data
|
||||
end
|
||||
end
|
||||
|
||||
defimpl Enum.Iterator, for: HashDict do
|
||||
def iterator({ HashDict, data }), do: :dict.to_list(data)
|
||||
def count({ HashDict, data }), do: :dict.size(data)
|
||||
end
|
||||
|
||||
defmodule HashDict do
|
||||
@moduledoc """
|
||||
This module implements a dictionary based on hashing of the keys.
|
||||
It is a simple wrapper around [Erlang's dict module](http://www.erlang.org/doc/man/dict.html)
|
||||
and exposed via the `Dict` protocol.
|
||||
"""
|
||||
|
||||
use Dict.Common, Dict.HashDict
|
||||
end
|
||||
@@ -0,0 +1,212 @@
|
||||
defrecord IEx.Config, io: nil, binding: nil, cache: '', counter: 1, scope: nil, result: nil
|
||||
|
||||
defmodule IEx do
|
||||
@moduledoc """
|
||||
This module implements interactive Elixir. It provides a main
|
||||
function, `start` which will either delegate to `tty` or `simple`.
|
||||
The former is meant for systems where tty is available and relies
|
||||
on it in order to work properly. This makes all control commands
|
||||
available in tty available to the developer.
|
||||
|
||||
In case `tty` is not available (for example, Windows), a
|
||||
developer may invoke `simple` which starts a stripped
|
||||
down version.
|
||||
"""
|
||||
|
||||
import Exception, only: [format_stacktrace: 1]
|
||||
|
||||
@doc """
|
||||
Interface to start IEx from CLI.
|
||||
"""
|
||||
def cli do
|
||||
run([remsh: get_remsh(:init.get_plain_arguments)])
|
||||
end
|
||||
|
||||
defp get_remsh(['--remsh',h|_]), do: list_to_binary(h)
|
||||
defp get_remsh([_|t]), do: get_remsh(t)
|
||||
defp get_remsh([]), do: nil
|
||||
|
||||
@doc """
|
||||
Runs IEx checking if tty is available or not.
|
||||
If so, invoke tty, otherwise go with the simple iex.
|
||||
"""
|
||||
def run(opts // []) when is_list(opts) do
|
||||
case :os.type do
|
||||
{ :unix, _ } -> tty(opts)
|
||||
_ -> simple(opts)
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Starts IEx using a tty server.
|
||||
"""
|
||||
def tty(opts // []) when is_list(opts) do
|
||||
config = boot_config(opts)
|
||||
|
||||
remote =
|
||||
if remsh = opts[:remsh] do
|
||||
if node() == :nonode@nohost do
|
||||
raise ArgumentError, message: "In order to use --remsh, you need to name the node"
|
||||
end
|
||||
if is_atom(remsh), do: remsh, else: binary_to_atom(remsh)
|
||||
end
|
||||
|
||||
function = fn ->
|
||||
# We are inside the new tty and in a new process,
|
||||
# reattach it the error logger.
|
||||
attach_error_logger
|
||||
|
||||
start config
|
||||
end
|
||||
|
||||
# Dettach the error logger because we are going to unregister
|
||||
# the user process and start a new tty which will get control
|
||||
# over the standardio. Dettaching it here allows us to get rid
|
||||
# of warnings. We reattach it again when we get the new tty.
|
||||
dettach_error_logger
|
||||
|
||||
# Unregister the user process, user_drv command below
|
||||
# will register the new one.
|
||||
unregister_user_process
|
||||
|
||||
# Close the default io port, user_drv start command below
|
||||
# will take control over the io.
|
||||
close_io_port
|
||||
|
||||
args =
|
||||
if remote do
|
||||
{ remote, :erlang, :apply, [function, []] }
|
||||
else
|
||||
{ :erlang, :apply, [function, []] }
|
||||
end
|
||||
|
||||
Erlang.user_drv.start([:"tty_sl -c -e", args])
|
||||
end
|
||||
|
||||
@doc """
|
||||
Starts IEx simply using the current stdio.
|
||||
"""
|
||||
def simple(opts // []) when is_list(opts) do
|
||||
start boot_config(opts)
|
||||
end
|
||||
|
||||
# This is a callback invoked by Erlang shell utilities.
|
||||
@doc false
|
||||
def start(config // nil) do
|
||||
spawn fn ->
|
||||
config = config || boot_config([])
|
||||
gl = :erlang.group_leader
|
||||
glnode = node gl
|
||||
if glnode != node do
|
||||
ensure_module_exists glnode, IEx.Remsh
|
||||
expand_fun = IEx.Remsh.expand node
|
||||
else
|
||||
expand_fun = IEx.Autocomplete.expand &1
|
||||
end
|
||||
:io.setopts gl, [expand_fun: expand_fun]
|
||||
start_loop(config)
|
||||
end
|
||||
end
|
||||
|
||||
## Boot Helpers
|
||||
|
||||
defp boot_config(opts) do
|
||||
IO.puts "Interactive Elixir (#{System.version}) - press Ctrl+C to exit"
|
||||
|
||||
scope = Erlang.elixir.scope_for_eval(
|
||||
file: 'iex',
|
||||
delegate_locals_to: IEx.Helpers
|
||||
)
|
||||
|
||||
IEx.Config[
|
||||
io: opts[:io] || IEx.UnicodeIO,
|
||||
binding: opts[:binding] || [],
|
||||
scope: scope
|
||||
]
|
||||
end
|
||||
|
||||
defp dettach_error_logger do
|
||||
:error_logger.delete_report_handler(:error_logger_tty_h)
|
||||
end
|
||||
|
||||
defp attach_error_logger do
|
||||
:error_logger.add_report_handler(:error_logger_tty_h)
|
||||
end
|
||||
|
||||
defp unregister_user_process do
|
||||
if is_pid(Process.whereis(:user)), do: Process.unregister :user
|
||||
end
|
||||
|
||||
defp close_io_port do
|
||||
if port = Enum.find(Port.list, io_port?(&1)) do
|
||||
Port.close(port)
|
||||
end
|
||||
end
|
||||
|
||||
defp io_port?(port) do
|
||||
Port.info(port, :name) == {:name,'0/1'} && port
|
||||
end
|
||||
|
||||
## Loop helpers
|
||||
|
||||
defp start_loop(config) do
|
||||
Process.put :iex_history, []
|
||||
{ _, _, scope } = Erlang.elixir.eval('import IEx.Helpers', [], 0, config.scope)
|
||||
do_loop(config.scope(scope))
|
||||
end
|
||||
|
||||
defp do_loop(config) do
|
||||
io = config.io
|
||||
|
||||
counter = config.counter
|
||||
cache = config.cache
|
||||
code = cache ++ io.get(config)
|
||||
|
||||
new_config =
|
||||
try do
|
||||
{ result, new_binding, scope } =
|
||||
Erlang.elixir.eval(code, config.binding, counter, config.scope)
|
||||
|
||||
io.put result
|
||||
|
||||
config = config.result(result)
|
||||
update_history(config.cache(code).scope(nil))
|
||||
config.increment_counter.cache('').binding(new_binding).scope(scope)
|
||||
rescue
|
||||
TokenMissingError ->
|
||||
config.cache(code)
|
||||
exception ->
|
||||
stacktrace = System.stacktrace
|
||||
io.error "** (#{inspect exception.__record__(:name)}) #{exception.message}"
|
||||
print_stacktrace io, stacktrace
|
||||
config.cache('')
|
||||
catch
|
||||
kind, error ->
|
||||
stacktrace = System.stacktrace
|
||||
io.error "** (#{kind}) #{inspect(error)}"
|
||||
print_stacktrace io, stacktrace
|
||||
config.cache('')
|
||||
end
|
||||
|
||||
do_loop(new_config)
|
||||
end
|
||||
|
||||
defp update_history(config) do
|
||||
current = Process.get :iex_history
|
||||
Process.put :iex_history, [config|current]
|
||||
end
|
||||
|
||||
defp print_stacktrace(io, stacktrace) do
|
||||
Enum.each stacktrace, fn s -> io.error " #{format_stacktrace(s)}" end
|
||||
end
|
||||
|
||||
## Code injection helper
|
||||
|
||||
defp ensure_module_exists(node, mod) do
|
||||
unless :rpc.call node, :code, :is_loaded, [mod] do
|
||||
{m,b,f} = :code.get_object_code mod
|
||||
{:module, mod} = :rpc.call node, :code, :load_binary, [m,f,b]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -0,0 +1,266 @@
|
||||
defmodule IEx.Autocomplete do
|
||||
@moduledoc """
|
||||
Autocompletion for the Elixir shell.
|
||||
"""
|
||||
|
||||
defrecord Mod, name: nil, type: nil
|
||||
defrecord Fun, name: nil, arities: []
|
||||
|
||||
defprotocol Entry do
|
||||
@moduledoc false
|
||||
def to_entries(entry)
|
||||
def to_hint(entry, hint)
|
||||
end
|
||||
|
||||
defimpl Entry, for: Mod do
|
||||
def to_entries(mod) do
|
||||
[mod.name]
|
||||
end
|
||||
|
||||
def to_hint(Mod[name: name], hint) do
|
||||
:lists.nthtail(length(hint), name) ++ '.'
|
||||
end
|
||||
end
|
||||
|
||||
defimpl Entry, for: Fun do
|
||||
def to_entries(fun) do
|
||||
lc a inlist fun.arities, do: '#{fun.name}/#{a}'
|
||||
end
|
||||
|
||||
def to_hint(Fun[name: name], hint) do
|
||||
:lists.nthtail(length(hint), name)
|
||||
end
|
||||
end
|
||||
|
||||
def expand([]) do
|
||||
funs = module_funs(IEx.Helpers) ++ module_funs(Kernel)
|
||||
mods = [Mod[name: 'Elixir', type: :elixir], Mod[name: 'Erlang', type: :elixir]]
|
||||
format_expansion mods ++ funs
|
||||
end
|
||||
|
||||
def expand([h|t]=expr) do
|
||||
cond do
|
||||
h === ?. ->
|
||||
expand_dot reduce(t)
|
||||
h === ?: ->
|
||||
expand_erlang_modules
|
||||
(h in ?a..?z) or (h in ?A..?Z) or h === ?_ ->
|
||||
expand_expr reduce(expr)
|
||||
h in '(+[' ->
|
||||
expand ''
|
||||
true ->
|
||||
no_match
|
||||
end
|
||||
end
|
||||
|
||||
defp expand_dot(expr) do
|
||||
case Code.string_to_ast expr do
|
||||
{:ok, atom} when is_atom(atom) ->
|
||||
expand_module_funs atom
|
||||
{:ok, {:__aliases__,_,[:Erlang]}} ->
|
||||
expand_erlang_modules
|
||||
{:ok, {:__aliases__,_,list}} ->
|
||||
expand_elixir_modules list
|
||||
{:ok, {{:.,_,[{:__aliases__,_,[:Erlang]},mod]},_,[]}} when is_atom(mod) ->
|
||||
expand_module_funs mod
|
||||
_ ->
|
||||
no_match
|
||||
end
|
||||
end
|
||||
|
||||
defp expand_expr(expr) do
|
||||
case Code.string_to_ast expr do
|
||||
{:ok, atom} when is_atom(atom) ->
|
||||
expand_erlang_modules atom_to_list(atom)
|
||||
{:ok, { atom, _, nil }} when is_atom(atom) ->
|
||||
expand_module_funs Kernel, atom_to_list(atom)
|
||||
{:ok, {:__aliases__,_,[root]}} ->
|
||||
expand_elixir_modules [], atom_to_list(root)
|
||||
{:ok, {:__aliases__,_,list}} ->
|
||||
hint = atom_to_list(List.last(list))
|
||||
list = :lists.sublist(list, length(list)-1)
|
||||
expand_elixir_modules list, hint
|
||||
{:ok, {{:., _, [mod,fun]},_,[]}} when is_atom(fun) ->
|
||||
expand_call mod, atom_to_list(fun)
|
||||
_ -> no_match
|
||||
end
|
||||
end
|
||||
|
||||
defp reduce(expr) do
|
||||
last_token(List.reverse(expr), [' ', '(', '[', '+', '-'])
|
||||
end
|
||||
|
||||
defp last_token(s, []) do
|
||||
s
|
||||
end
|
||||
|
||||
defp last_token(s, [h|t]) do
|
||||
last_token(List.last(:string.tokens(s, h)), t)
|
||||
end
|
||||
|
||||
defp no_match, do: { :no, '', [] }
|
||||
|
||||
## Formatting
|
||||
|
||||
defp format_expansion(list, hint // '')
|
||||
|
||||
defp format_expansion([], _) do
|
||||
no_match
|
||||
end
|
||||
|
||||
defp format_expansion([uniq], hint) do
|
||||
{ :yes, Entry.to_hint(uniq, hint), [] }
|
||||
end
|
||||
|
||||
defp format_expansion([first|_]=entries, hint) do
|
||||
binary = Enum.map entries, fn e -> list_to_binary(e.name) end
|
||||
length = length hint
|
||||
prefix = :binary.longest_common_prefix(binary)
|
||||
if prefix == 0 or (prefix == length) do
|
||||
{:yes, '',
|
||||
Enum.reduce entries, [], fn e, acc -> Entry.to_entries(e) ++ acc end }
|
||||
else
|
||||
{:yes, :lists.sublist(first.name, 1 + length, prefix-length), [] }
|
||||
end
|
||||
end
|
||||
|
||||
## Root Modules
|
||||
|
||||
defp root_modules do
|
||||
Enum.reduce :code.all_loaded, [], fn {m,_}, acc ->
|
||||
mod = atom_to_list(m)
|
||||
case mod do
|
||||
'Elixir' ++ _ ->
|
||||
tokens = :string.tokens(mod, '-')
|
||||
if length(tokens) === 2 do
|
||||
[Mod.new(name: List.last(tokens), type: :elixir)|acc]
|
||||
else
|
||||
acc
|
||||
end
|
||||
_ ->
|
||||
[Mod.new(name: mod, type: :erlang)|acc]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
## Expand calls
|
||||
|
||||
# :atom.fun
|
||||
defp expand_call(mod, hint) when is_atom(mod) do
|
||||
expand_module_funs mod, hint
|
||||
end
|
||||
|
||||
# Erlang.mod.fun
|
||||
defp expand_call({ { :., _, [{ :__aliases__, _, [:Erlang] }, mod] }, _, [] }, hint) when is_atom(mod) do
|
||||
expand_module_funs mod, hint
|
||||
end
|
||||
|
||||
# Erlang.mod
|
||||
defp expand_call({ :__aliases__, _, [:Erlang] }, hint) do
|
||||
expand_erlang_modules hint
|
||||
end
|
||||
|
||||
# Elixir.fun
|
||||
defp expand_call({ :__aliases__, _, list }, hint) do
|
||||
expand_module_funs Module.concat(list), hint
|
||||
end
|
||||
|
||||
defp expand_call(_, _) do
|
||||
no_match
|
||||
end
|
||||
|
||||
## Erlang modules
|
||||
|
||||
defp expand_erlang_modules(hint // '') do
|
||||
format_expansion match_erlang_modules(hint), hint
|
||||
end
|
||||
|
||||
defp match_erlang_modules('') do
|
||||
Enum.filter root_modules, fn m -> m.type === :erlang end
|
||||
end
|
||||
|
||||
defp match_erlang_modules(hint) do
|
||||
Enum.filter root_modules, fn m -> :lists.prefix(hint, m.name) end
|
||||
end
|
||||
|
||||
## Elixir modules
|
||||
|
||||
defp expand_elixir_modules(list, hint // '') do
|
||||
mod = Module.concat(list)
|
||||
format_expansion elixir_submodules(mod, hint, list == []) ++ module_funs(mod, hint), hint
|
||||
end
|
||||
|
||||
defp elixir_submodules(mod, hint, root) do
|
||||
modname = atom_to_list(mod)
|
||||
depth = length(:string.tokens(modname, '-')) + 1
|
||||
base = modname ++ [?-|hint]
|
||||
|
||||
Enum.reduce modules_as_lists(root), [], fn(m, acc) ->
|
||||
if :lists.prefix(base, m) do
|
||||
tokens = :string.tokens(m, '-')
|
||||
if length(tokens) == depth do
|
||||
name = List.last(tokens)
|
||||
[Mod.new(type: :elixir, name: name)|acc]
|
||||
else
|
||||
acc
|
||||
end
|
||||
else
|
||||
acc
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defp modules_as_lists(true) do
|
||||
['Elixir-Elixir', 'Elixir-Erlang'] ++ modules_as_lists(false)
|
||||
end
|
||||
|
||||
defp modules_as_lists(false) do
|
||||
Enum.map(:code.all_loaded, fn({ m, _ }) -> atom_to_list(m) end)
|
||||
end
|
||||
|
||||
## Functions
|
||||
|
||||
defp expand_module_funs(mod, hint // '') do
|
||||
format_expansion module_funs(mod, hint), hint
|
||||
end
|
||||
|
||||
defp module_funs(mod, hint // '') do
|
||||
case ensure_loaded(mod) do
|
||||
{ :module, _ } ->
|
||||
falist = get_funs(mod)
|
||||
|
||||
list = Enum.reduce falist, [], fn {f,a}, acc ->
|
||||
case :lists.keyfind(f, 1, acc) do
|
||||
{f,aa} -> :lists.keyreplace(f, 1, acc, {f, [a|aa]})
|
||||
false -> [{f, [a]}|acc]
|
||||
end
|
||||
end
|
||||
|
||||
lc {fun, arities} inlist list, name = atom_to_list(fun), is_prefix?(hint, name) do
|
||||
Fun[name: name, arities: arities]
|
||||
end
|
||||
_ ->
|
||||
[]
|
||||
end
|
||||
end
|
||||
|
||||
## Generic Helpers
|
||||
|
||||
defp get_funs(mod) do
|
||||
if function_exported?(mod, :__info__, 1) do
|
||||
if docs = mod.__info__(:docs) do
|
||||
lc { pair, _line, _kind, _sign, doc } inlist docs, doc != false, do: pair
|
||||
else
|
||||
(mod.__info__(:functions) -- [__info__: 1]) ++ mod.__info__(:macros)
|
||||
end
|
||||
else
|
||||
mod.module_info(:exports)
|
||||
end
|
||||
end
|
||||
|
||||
defp is_prefix?('', _), do: true
|
||||
defp is_prefix?(hint, name), do: :lists.prefix(hint, name)
|
||||
|
||||
defp ensure_loaded(Elixir), do: { :error, :nofile }
|
||||
defp ensure_loaded(mod), do: Code.ensure_loaded(mod)
|
||||
end
|
||||
@@ -0,0 +1,244 @@
|
||||
defmodule IEx.Helpers do
|
||||
@moduledoc """
|
||||
A bunch of helpers available in IEx.
|
||||
|
||||
* `c` - compiles a file in the given path
|
||||
* `d` - prints documentation
|
||||
* `h` - prints history
|
||||
* `m` - prints loaded modules
|
||||
* `r` - recompiles and reloads the given module's source file
|
||||
* `v` - retrieves nth value from console
|
||||
|
||||
Documentation for functions in this module can be consulted
|
||||
directly from the command line, as an example, try:
|
||||
|
||||
d(:c, 1)
|
||||
|
||||
"""
|
||||
|
||||
@doc """
|
||||
Expects a list of files to compile and a path
|
||||
to write their object code to. It returns the name
|
||||
of the compiled modules.
|
||||
|
||||
## Examples
|
||||
|
||||
c ["foo.ex"], "ebin"
|
||||
#=> Foo
|
||||
|
||||
"""
|
||||
def c(files, path // ".") do
|
||||
tuples = Kernel.ParallelCompiler.files_to_path List.wrap(files), path
|
||||
Enum.map tuples, elem(&1, 1)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns the name and module of all modules loaded.
|
||||
"""
|
||||
def m do
|
||||
all = Enum.map :code.all_loaded, fn { mod, file } -> { inspect(mod), file } end
|
||||
sorted = List.sort(all)
|
||||
size = Enum.reduce sorted, 0, fn({ mod, _ }, acc) -> max(byte_size(mod), acc) end
|
||||
format = "~-#{size}s ~s~n"
|
||||
|
||||
Enum.each sorted, fn({ mod, file }) ->
|
||||
:io.format(format, [mod, file])
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Prints commands history and their result.
|
||||
"""
|
||||
def h do
|
||||
history = List.reverse(Process.get(:iex_history))
|
||||
Enum.each(history, print_history(&1))
|
||||
end
|
||||
|
||||
defp print_history(config) do
|
||||
IO.puts "#{config.counter}: #{config.cache}#=> #{inspect config.result}\n"
|
||||
end
|
||||
|
||||
@doc """
|
||||
Shows the documentation for IEx.Helpers.
|
||||
"""
|
||||
def d() do
|
||||
d(IEx.Helpers, :all)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Shows the documentation for the given module
|
||||
or for the given function/arity pair.
|
||||
|
||||
## Examples
|
||||
|
||||
d(Enum)
|
||||
#=> Prints documentation for Enum
|
||||
|
||||
It also accepts functions in the format `fun/arity`
|
||||
and `module.fun/arity`, for example:
|
||||
|
||||
d receive/1
|
||||
d Enum.all?/2
|
||||
|
||||
"""
|
||||
defmacro d({ :/, _, [{ fun, _, nil }, arity] }) do
|
||||
quote do
|
||||
d(unquote(fun), unquote(arity))
|
||||
end
|
||||
end
|
||||
|
||||
defmacro d({ :/, _, [{ { :., _, [mod, fun] }, _, [] }, arity] }) do
|
||||
quote do
|
||||
d(unquote(mod), unquote(fun), unquote(arity))
|
||||
end
|
||||
end
|
||||
|
||||
defmacro d(other) do
|
||||
quote do
|
||||
d(unquote(other), :all)
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Prints the documentation for the given function and arity.
|
||||
|
||||
The function may either be a function defined inside `IEx.Helpers`
|
||||
or in `Kernel`. To see functions from other module, use
|
||||
`d/3` instead.
|
||||
|
||||
## Examples
|
||||
|
||||
d(:d, 2)
|
||||
#=> Prints documentation for this function
|
||||
|
||||
"""
|
||||
def d(:d, 1) do
|
||||
d(__MODULE__, :d, 1)
|
||||
end
|
||||
|
||||
def d(function, arity) when is_atom(function) and is_integer(arity) do
|
||||
if function_exported?(__MODULE__, function, arity) do
|
||||
d(__MODULE__, function, arity)
|
||||
else
|
||||
d(Kernel, function, arity)
|
||||
end
|
||||
end
|
||||
|
||||
def d(module, :all) when is_atom(module) do
|
||||
case Code.ensure_loaded(module) do
|
||||
{ :module, _ } ->
|
||||
case module.__info__(:moduledoc) do
|
||||
{ _, binary } when is_binary(binary) ->
|
||||
IO.puts "# #{inspect module}\n"
|
||||
IO.write binary
|
||||
{ _, _ } ->
|
||||
IO.puts "No docs for #{inspect module}"
|
||||
_ ->
|
||||
IO.puts "#{inspect module} was not compiled with docs"
|
||||
end
|
||||
{ :error, reason } ->
|
||||
IO.puts "Could not load module #{inspect module}: #{reason}"
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Shows the documentation for the `function/arity` in `module`.
|
||||
"""
|
||||
def d(module, function, arity) when is_atom(module) and is_atom(function) and is_integer(arity) do
|
||||
if docs = module.__info__(:docs) do
|
||||
doc =
|
||||
if tuple = List.keyfind(docs, { function, arity }, 1) do
|
||||
print_signature(tuple)
|
||||
end
|
||||
|
||||
if doc do
|
||||
IO.write "\n" <> doc
|
||||
else
|
||||
IO.puts "No docs for #{function}/#{arity}"
|
||||
end
|
||||
else
|
||||
IO.puts "#{inspect module} was not compiled with docs"
|
||||
end
|
||||
end
|
||||
|
||||
# Get the full signature from a function.
|
||||
defp print_signature({ _info, _line, _kind, _args, false }) do
|
||||
false
|
||||
end
|
||||
|
||||
defp print_signature({ { name, _arity }, _line, kind, args, docs }) do
|
||||
args = Enum.map_join(args, ", ", signature_arg(&1))
|
||||
IO.puts "* #{kind} #{name}(#{args})"
|
||||
docs
|
||||
end
|
||||
|
||||
defp signature_arg({ ://, _, [left, right] }) do
|
||||
signature_arg(left) <> " // " <> Macro.to_binary(right)
|
||||
end
|
||||
|
||||
defp signature_arg({ var, _, _ }) do
|
||||
atom_to_binary(var)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Retrieves nth query's value from the history. Use negative
|
||||
values to lookup query's value from latest to earliest.
|
||||
For instance, v(-1) returns the latest result.
|
||||
"""
|
||||
def v(n) when n < 0 do
|
||||
history = Process.get(:iex_history)
|
||||
Enum.nth!(history, abs(n)).result
|
||||
end
|
||||
|
||||
def v(n) do
|
||||
history = Process.get(:iex_history) /> List.reverse
|
||||
Enum.nth!(history, n).result
|
||||
end
|
||||
|
||||
@doc """
|
||||
Reloads all modules that were already reloaded
|
||||
at some point with `r/1`.
|
||||
"""
|
||||
def r do
|
||||
Enum.map iex_reloaded, r(&1)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Recompiles and reloads the specified module's source file.
|
||||
|
||||
Please note that all the modules defined in the specified
|
||||
files are recompiled and reloaded.
|
||||
"""
|
||||
def r(module) do
|
||||
if source = source(module) do
|
||||
Process.put(:iex_reloaded, :ordsets.add_element(module, iex_reloaded))
|
||||
{ module, Code.load_file source }
|
||||
else
|
||||
:nosource
|
||||
end
|
||||
end
|
||||
|
||||
defp iex_reloaded do
|
||||
Process.get(:iex_reloaded) || :ordsets.new
|
||||
end
|
||||
|
||||
defp source(module) do
|
||||
compile = module.module_info(:compile)
|
||||
|
||||
# Get the source of the compiled module. Due to a bug in Erlang
|
||||
# R15 and before, we need to look for the source first in the
|
||||
# options and then into the real source.
|
||||
options =
|
||||
case List.keyfind(compile, :options, 1) do
|
||||
{ :options, opts } -> opts
|
||||
_ -> []
|
||||
end
|
||||
|
||||
source = List.keyfind(options, :source, 1) || List.keyfind(compile, :source, 1)
|
||||
|
||||
case source do
|
||||
{ :source, source } -> list_to_binary(source)
|
||||
_ -> nil
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,24 @@
|
||||
defmodule IEx.Remsh do
|
||||
@moduledoc """
|
||||
Helper function injected into connecting remote nodes
|
||||
to properly handle autocompletion. Elixir supports:
|
||||
|
||||
* remsh from an elixir node to an elixir node
|
||||
* remsh from a plain erlang node to an elixir node (through the ^G menu)
|
||||
* remsh from an elixir node to a plain erlang node (and get an erl shell there)
|
||||
|
||||
In order to get an Elixir shell from the ^G menu,
|
||||
you need to use 'Elixir-IEx' as the shell name.
|
||||
|
||||
Connecting an Elixir shell to a remote node without
|
||||
Elixir is **not** supported.
|
||||
"""
|
||||
def expand(node) do
|
||||
fn e ->
|
||||
case :rpc.call node, Elixir.IEx.Autocomplete, :expand, [e] do
|
||||
{:badrpc, _} -> {:no, '', []}
|
||||
r -> r
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,45 @@
|
||||
defmodule IEx.UnicodeIO do
|
||||
@moduledoc """
|
||||
This module implements the API used by IEX to
|
||||
interact with the console. This API may change
|
||||
in the future without warnings.
|
||||
"""
|
||||
|
||||
@doc """
|
||||
Implements the get IO API used by IEx. It receives the
|
||||
code cache, the instructions counter and needs to
|
||||
return a list with the new characters inserted.
|
||||
"""
|
||||
def get(config) do
|
||||
prefix = case config.cache do
|
||||
[] -> "iex"
|
||||
_ -> "..."
|
||||
end
|
||||
prompt = case node do
|
||||
:nonode@nohost ->
|
||||
"#{prefix}(#{config.counter})> "
|
||||
n ->
|
||||
"#{prefix}(#{n})#{config.counter}> "
|
||||
end
|
||||
case IO.gets(prompt) do
|
||||
{ :error, _ } -> ''
|
||||
data -> :unicode.characters_to_list(data)
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Implements the put IO API used by IEx. It receives the
|
||||
result and prints it.
|
||||
"""
|
||||
def put(result) do
|
||||
IO.inspect result
|
||||
end
|
||||
|
||||
@doc """
|
||||
Implements the error IO API used by IEx. It prints error
|
||||
messages.
|
||||
"""
|
||||
def error(result) do
|
||||
IO.puts result
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,135 @@
|
||||
defmodule IO do
|
||||
@moduledoc """
|
||||
Module responsible for doing IO. The function in this
|
||||
module expects an iodata as argument encoded in UTF-8.
|
||||
An iodata can be:
|
||||
|
||||
* A list of integers representing a string. Any unicode
|
||||
character must be represented with one entry in the list,
|
||||
this entry being an integer with the codepoint value;
|
||||
* A binary in which unicode characters are represented
|
||||
with many bytes (Elixir's default representation);
|
||||
* A list of binaries or a list of char lists (as described above);
|
||||
* If none of the above, `to_binary` is invoked in the
|
||||
given argument;
|
||||
|
||||
"""
|
||||
|
||||
@doc """
|
||||
Reads `count` bytes from the IO device. It returns:
|
||||
|
||||
* `data` - The input characters.
|
||||
|
||||
* :eof - End of file was encountered.
|
||||
|
||||
* {:error, reason} - Other (rare) error condition,
|
||||
for instance {:error, :estale} if reading from an
|
||||
NFS file system.
|
||||
"""
|
||||
def read(device // :stdio, count) do
|
||||
Erlang.io.get_chars(map_dev(device), "", count)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Read a line from the IO device. It returns:
|
||||
|
||||
* `data` - The input characters.
|
||||
|
||||
* :eof - End of file was encountered.
|
||||
|
||||
* {:error, reason} - Other (rare) error condition,
|
||||
for instance {:error, :estale} if reading from an
|
||||
NFS file system.
|
||||
|
||||
This function does the same as `gets/2`,
|
||||
except the prompt is not required as argument.
|
||||
"""
|
||||
def readline(device // :stdio) do
|
||||
Erlang.io.get_line(map_dev(device), "")
|
||||
end
|
||||
|
||||
@doc """
|
||||
Writes the given argument to the given device.
|
||||
By default the device is the standard output.
|
||||
The argument is expected to be a chardata (i.e.
|
||||
a char list or an unicode binary).
|
||||
|
||||
It returns `:ok` if it succeeds.
|
||||
|
||||
## Examples
|
||||
|
||||
IO.write "sample"
|
||||
#=> "sample"
|
||||
|
||||
IO.write :stderr, "error"
|
||||
#=> "error"
|
||||
|
||||
"""
|
||||
def write(device // :stdio, item) do
|
||||
Erlang.io.put_chars map_dev(device), to_iodata(item)
|
||||
end
|
||||
|
||||
def print(device // :stdio, item) do
|
||||
IO.puts "IO.print is deprecated in favor of IO.write"
|
||||
Erlang.io.put_chars map_dev(device), to_iodata(item)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Writes the argument to the device, similarly to write
|
||||
but adds a new line at the end. The argument is expected
|
||||
to be a chardata.
|
||||
"""
|
||||
def puts(device // :stdio, item) do
|
||||
erl_dev = map_dev(device)
|
||||
Erlang.io.put_chars erl_dev, to_iodata(item)
|
||||
Erlang.io.nl(erl_dev)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Inspects and writes the given argument to the device
|
||||
followed by a new line. Returns the item given.
|
||||
"""
|
||||
def inspect(device // :stdio, item) do
|
||||
puts device, Kernel.inspect(item)
|
||||
item
|
||||
end
|
||||
|
||||
@doc """
|
||||
Gets `count` bytes from the IO device. It returns:
|
||||
|
||||
* `data` - The input characters.
|
||||
|
||||
* :eof - End of file was encountered.
|
||||
|
||||
* {:error, reason} - Other (rare) error condition,
|
||||
for instance {:error, :estale} if reading from an
|
||||
NFS file system.
|
||||
"""
|
||||
def getb(device // :stdio, prompt, count // 1) do
|
||||
Erlang.io.get_chars(map_dev(device), to_iodata(prompt), count)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Reads a line from the IO device. It returns:
|
||||
|
||||
* `data` - The characters in the line terminated
|
||||
by a LF (or end of file).
|
||||
|
||||
* :eof - End of file was encountered.
|
||||
|
||||
* {:error, reason} - Other (rare) error condition,
|
||||
for instance {:error, :estale} if reading from an
|
||||
NFS file system.
|
||||
"""
|
||||
def gets(device // :stdio, prompt) do
|
||||
Erlang.io.get_line(map_dev(device), to_iodata(prompt))
|
||||
end
|
||||
|
||||
# Map the Elixir names for standard io and error to Erlang names
|
||||
defp map_dev(:stdio), do: :standard_io
|
||||
defp map_dev(:stderr), do: :standard_error
|
||||
defp map_dev(other), do: other
|
||||
|
||||
defp to_iodata(io) when is_list(io) or is_binary(io), do: io
|
||||
defp to_iodata(other), do: to_binary(other)
|
||||
end
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,7 +1,7 @@
|
||||
defrecord Elixir.CLI.Config, commands: [], close: [],
|
||||
output: '.', compile: [], halt: true, compiler_options: []
|
||||
defrecord Kernel.CLI.Config, commands: [], output: ".",
|
||||
compile: [], halt: true, compiler_options: []
|
||||
|
||||
defmodule Elixir.CLI do
|
||||
defmodule Kernel.CLI do
|
||||
@moduledoc false
|
||||
|
||||
import Exception, only: [format_stacktrace: 1]
|
||||
@@ -9,12 +9,12 @@ defmodule Elixir.CLI do
|
||||
# Invoked directly from erlang boot process. It parses all argv
|
||||
# options and execute them in the order they are specified.
|
||||
def process_argv(options) do
|
||||
{ config, argv } = process_options(options, Elixir.CLI.Config.new)
|
||||
{ config, argv } = process_options(options, Kernel.CLI.Config.new)
|
||||
|
||||
argv = lc arg in argv, do: list_to_binary(arg)
|
||||
argv = lc arg inlist argv, do: list_to_binary(arg)
|
||||
Erlang.gen_server.call(:elixir_code_server, { :argv, argv })
|
||||
|
||||
all_commands = List.reverse(config.commands) ++ List.reverse(config.close)
|
||||
all_commands = List.reverse(config.commands)
|
||||
|
||||
try do
|
||||
Enum.map all_commands, process_command(&1, config)
|
||||
@@ -26,7 +26,7 @@ defmodule Elixir.CLI do
|
||||
exception ->
|
||||
at_exit(1)
|
||||
stacktrace = System.stacktrace
|
||||
IO.puts :standard_error, "** (#{inspect exception.__record__(:name)}) #{exception.message}"
|
||||
IO.puts :stderr, "** (#{inspect exception.__record__(:name)}) #{exception.message}"
|
||||
print_stacktrace(stacktrace)
|
||||
halt(1)
|
||||
catch
|
||||
@@ -39,7 +39,7 @@ defmodule Elixir.CLI do
|
||||
kind, reason ->
|
||||
at_exit(1)
|
||||
stacktrace = System.stacktrace
|
||||
IO.puts :standard_error, "** (#{kind}) #{inspect(reason)}"
|
||||
IO.puts :stderr, "** (#{kind}) #{inspect(reason)}"
|
||||
print_stacktrace(stacktrace)
|
||||
halt(1)
|
||||
end
|
||||
@@ -49,23 +49,23 @@ defmodule Elixir.CLI do
|
||||
|
||||
defp at_exit(status) do
|
||||
hooks = Erlang.gen_server.call(:elixir_code_server, :at_exit)
|
||||
lc hook in hooks do
|
||||
lc hook inlist hooks do
|
||||
try do
|
||||
hook.(status)
|
||||
rescue
|
||||
exception ->
|
||||
IO.puts :standard_error, "** (#{inspect exception.__record__(:name)}) #{exception.message}"
|
||||
IO.puts :stderr, "** (#{inspect exception.__record__(:name)}) #{exception.message}"
|
||||
print_stacktrace(System.stacktrace)
|
||||
catch
|
||||
kind, reason ->
|
||||
IO.puts :standard_error, "** #{kind} #{inspect(reason)}"
|
||||
IO.puts :stderr, "** #{kind} #{inspect(reason)}"
|
||||
print_stacktrace(System.stacktrace)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defp invalid_option(option) do
|
||||
IO.puts(:standard_error, "Unknown option #{list_to_binary(option)}")
|
||||
IO.puts(:stderr, "Unknown option #{list_to_binary(option)}")
|
||||
halt(1)
|
||||
end
|
||||
|
||||
@@ -79,7 +79,7 @@ defmodule Elixir.CLI do
|
||||
end
|
||||
|
||||
defp print_stacktrace(stacktrace) do
|
||||
Enum.each stacktrace, fn s -> IO.puts :standard_error, " #{format_stacktrace(s)}" end
|
||||
Enum.each stacktrace, fn s -> IO.puts :stderr, " #{format_stacktrace(s)}" end
|
||||
end
|
||||
|
||||
# Process shared options
|
||||
@@ -89,8 +89,12 @@ defmodule Elixir.CLI do
|
||||
process_shared t, config
|
||||
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.prepend_commands [{:eval,h}]
|
||||
process_shared t, config.prepend_commands [eval: h]
|
||||
end
|
||||
|
||||
defp process_shared(['-pa',h|t], config) do
|
||||
@@ -104,14 +108,20 @@ defmodule Elixir.CLI do
|
||||
end
|
||||
|
||||
defp process_shared(['-r',h|t], config) do
|
||||
h = list_to_binary(h)
|
||||
config = Enum.reduce File.wildcard(h), config, fn path, config ->
|
||||
config.prepend_commands [{:require, path}]
|
||||
config.prepend_commands [require: path]
|
||||
end
|
||||
process_shared t, config
|
||||
end
|
||||
|
||||
defp process_shared(['-pr',h|t], config) do
|
||||
process_shared t, config.prepend_commands [{:parallel_require, h}]
|
||||
h = list_to_binary(h)
|
||||
process_shared t, config.prepend_commands [parallel_require: h]
|
||||
end
|
||||
|
||||
defp process_shared([erl,_|t], config) when erl in ['--erl', '--sname', '--remsh', '--name'] do
|
||||
process_shared t, config
|
||||
end
|
||||
|
||||
defp process_shared(list, config) do
|
||||
@@ -124,12 +134,18 @@ defmodule Elixir.CLI do
|
||||
{ config, t }
|
||||
end
|
||||
|
||||
def process_options(['--no-halt'|t], config) do
|
||||
process_options t, config.halt(false)
|
||||
def process_options(['--compile'|t], config) do
|
||||
process_compiler t, config
|
||||
end
|
||||
|
||||
def process_options(['+compile'|t], config) do
|
||||
process_compiler t, config
|
||||
def process_options(['-S',h|t], config) do
|
||||
exec = System.find_executable(h)
|
||||
if exec do
|
||||
{ config.prepend_commands([require: list_to_binary(exec)]), t }
|
||||
else
|
||||
IO.puts(:stderr, "Could not find executable #{h}")
|
||||
halt(1)
|
||||
end
|
||||
end
|
||||
|
||||
def process_options([h|t] = list, config) do
|
||||
@@ -137,7 +153,8 @@ defmodule Elixir.CLI do
|
||||
'-' ++ _ ->
|
||||
shared_option? list, config, process_options(&1, &2)
|
||||
_ ->
|
||||
{ config.prepend_commands([{:require, h}]), t }
|
||||
h = list_to_binary(h)
|
||||
{ config.prepend_commands([require: h]), t }
|
||||
end
|
||||
end
|
||||
|
||||
@@ -152,19 +169,17 @@ defmodule Elixir.CLI do
|
||||
end
|
||||
|
||||
defp process_compiler(['-o',h|t], config) do
|
||||
process_compiler t, config.output(h)
|
||||
process_compiler t, config.output(list_to_binary(h))
|
||||
end
|
||||
|
||||
defp process_compiler(['--docs'|t], config) do
|
||||
process_compiler t, config.merge_compiler_options(docs: true)
|
||||
defp process_compiler(['--no-docs'|t], config) do
|
||||
process_compiler t, config.merge_compiler_options(docs: false)
|
||||
end
|
||||
|
||||
defp process_compiler(['--debug-info'|t], config) do
|
||||
process_compiler t, config.merge_compiler_options(debug_info: true)
|
||||
end
|
||||
|
||||
# This option is used internally so we can compile
|
||||
# Elixir with Elixir without raising module conflicts
|
||||
defp process_compiler(['--ignore-module-conflict'|t], config) do
|
||||
process_compiler t, config.merge_compiler_options(ignore_module_conflict: true)
|
||||
end
|
||||
@@ -174,76 +189,42 @@ defmodule Elixir.CLI do
|
||||
'-' ++ _ ->
|
||||
shared_option? list, config, process_compiler(&1, &2)
|
||||
_ ->
|
||||
pattern = if File.dir?(h), do: '#{h}/**/*.ex', else: h
|
||||
h = list_to_binary(h)
|
||||
pattern = if File.dir?(h), do: "#{h}/**/*.ex", else: h
|
||||
process_compiler t, config.prepend_compile [pattern]
|
||||
end
|
||||
end
|
||||
|
||||
defp process_compiler([], config) do
|
||||
{ config.prepend_commands([{:compile, config.compile}]), [] }
|
||||
{ config.prepend_commands([compile: config.compile]), [] }
|
||||
end
|
||||
|
||||
# Process commands
|
||||
|
||||
defp process_command({:eval, expr}, _config) do
|
||||
defp process_command({:eval, expr}, _config) when is_list(expr) do
|
||||
Erlang.elixir.eval(expr, [])
|
||||
end
|
||||
|
||||
defp process_command({:require, file}, _config) do
|
||||
defp process_command({:require, file}, _config) when is_binary(file) do
|
||||
Code.require_file(file)
|
||||
end
|
||||
|
||||
defp process_command({:parallel_require, pattern}, _config) do
|
||||
defp process_command({:parallel_require, pattern}, _config) when is_binary(pattern) do
|
||||
files = File.wildcard(pattern)
|
||||
files = List.uniq(files)
|
||||
files = Enum.filter files, File.regular?(&1)
|
||||
spawn_requires(files, [])
|
||||
Kernel.ParallelRequire.files(files)
|
||||
end
|
||||
|
||||
defp process_command({:compile, patterns}, config) do
|
||||
Erlang.file.make_dir(config.output)
|
||||
File.mkdir_p(config.output)
|
||||
|
||||
files = Enum.map patterns, File.wildcard(&1)
|
||||
files = List.uniq(List.concat(files))
|
||||
files = Enum.filter files, File.regular?(&1)
|
||||
|
||||
Code.compiler_options(config.compiler_options)
|
||||
Elixir.ParallelCompiler.files_to_path(files, config.output,
|
||||
Kernel.ParallelCompiler.files_to_path(files, config.output,
|
||||
fn file -> IO.puts "Compiled #{file}" end)
|
||||
end
|
||||
|
||||
# Responsible for spawning requires in parallel
|
||||
# For now, we spawn at maximum four process at the same time
|
||||
|
||||
defp spawn_requires([], []), do: :done
|
||||
defp spawn_requires([], waiting), do: wait_for_messages([], waiting)
|
||||
|
||||
defp spawn_requires(files, waiting) when length(waiting) >= 4 do
|
||||
wait_for_messages(files, waiting)
|
||||
end
|
||||
|
||||
defp spawn_requires([h|t], waiting) do
|
||||
parent = Process.self
|
||||
|
||||
child = spawn_link fn ->
|
||||
try do
|
||||
Code.require_file(h)
|
||||
parent <- { :required, Process.self }
|
||||
catch
|
||||
kind, reason ->
|
||||
parent <- { :failure, Process.self, kind, reason, System.stacktrace }
|
||||
end
|
||||
end
|
||||
|
||||
spawn_requires(t, [child|waiting])
|
||||
end
|
||||
|
||||
defp wait_for_messages(files, waiting) do
|
||||
receive do
|
||||
{ :required, child } ->
|
||||
spawn_requires(files, List.delete(waiting, child))
|
||||
{ :failure, _child, kind, reason, stacktrace } ->
|
||||
Erlang.erlang.raise(kind, reason, stacktrace)
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -1,6 +1,6 @@
|
||||
# Implement error_handler pattern for Erlang
|
||||
# which is integrated with Elixir.ParallelCompiler
|
||||
defmodule Elixir.ErrorHandler do
|
||||
# which is integrated with Kernel.ParallelCompiler
|
||||
defmodule Kernel.ErrorHandler do
|
||||
@moduledoc false
|
||||
|
||||
def undefined_function(module, fun, args) do
|
||||
@@ -17,8 +17,8 @@ defmodule Elixir.ErrorHandler do
|
||||
case Code.ensure_loaded(module) do
|
||||
{ :module, _ } -> []
|
||||
{ :error, _ } ->
|
||||
parent = Process.get(:elixir_parent_compiler)
|
||||
parent <- { :waiting, Process.self, module }
|
||||
parent = Process.get(:elixir_compiler_pid)
|
||||
parent <- { :waiting, self(), module }
|
||||
receive do
|
||||
{ :release, ^parent } -> :ok
|
||||
end
|
||||
@@ -1,5 +1,5 @@
|
||||
defmodule Elixir.ParallelCompiler do
|
||||
refer Erlang.orddict, as: Orddict
|
||||
defmodule Kernel.ParallelCompiler do
|
||||
alias Erlang.orddict, as: Orddict
|
||||
|
||||
@moduledoc """
|
||||
A module responsible for compiling files in parallel.
|
||||
@@ -15,9 +15,8 @@ defmodule Elixir.ParallelCompiler do
|
||||
the current file stops being compiled until the dependency is
|
||||
resolved.
|
||||
|
||||
A callback that receives every time a file is compiled
|
||||
with the module names and binaries defined inside it can
|
||||
be optionally given as argument.
|
||||
A callback that is invoked every time a file is compiled
|
||||
with its name can be optionally given as argument.
|
||||
"""
|
||||
def files(files, callback // default_callback) do
|
||||
files_to_path(files, nil, callback)
|
||||
@@ -27,10 +26,8 @@ defmodule Elixir.ParallelCompiler do
|
||||
Compiles the given files to the given path.
|
||||
Read files/2 for more information.
|
||||
"""
|
||||
def files_to_path(files, path, callback // default_callback) do
|
||||
Code.ensure_loaded(Elixir.ErrorHandler)
|
||||
files = Enum.map(files, to_char_list(&1))
|
||||
path = if path, do: to_char_list(path)
|
||||
def files_to_path(files, path, callback // default_callback) when is_binary(path) do
|
||||
Code.ensure_loaded(Kernel.ErrorHandler)
|
||||
spawn_compilers(files, path, callback, [], [], [])
|
||||
end
|
||||
|
||||
@@ -42,11 +39,11 @@ defmodule Elixir.ParallelCompiler do
|
||||
|
||||
# Spawn a compiler for each file in the list until we reach the limit
|
||||
defp spawn_compilers([h|t], output, callback, waiting, queued, result) do
|
||||
parent = Process.self()
|
||||
parent = self()
|
||||
|
||||
child = spawn_link fn ->
|
||||
Process.put(:elixir_parent_compiler, parent)
|
||||
Process.flag(:error_handler, Elixir.ErrorHandler)
|
||||
Process.put(:elixir_compiler_pid, parent)
|
||||
Process.flag(:error_handler, Kernel.ErrorHandler)
|
||||
|
||||
try do
|
||||
if output do
|
||||
@@ -54,10 +51,10 @@ defmodule Elixir.ParallelCompiler do
|
||||
else
|
||||
Erlang.elixir_compiler.file(h)
|
||||
end
|
||||
parent <- { :compiled, Process.self(), h }
|
||||
parent <- { :compiled, self(), h }
|
||||
catch
|
||||
kind, reason ->
|
||||
parent <- { :failure, Process.self(), kind, reason, System.stacktrace }
|
||||
parent <- { :failure, self(), kind, reason, System.stacktrace }
|
||||
end
|
||||
end
|
||||
|
||||
@@ -69,7 +66,7 @@ defmodule Elixir.ParallelCompiler do
|
||||
|
||||
# Queued x, waiting for x: POSSIBLE ERROR! Release processes so we get the failures
|
||||
defp spawn_compilers([], output, callback, waiting, queued, result) when length(waiting) == length(queued) do
|
||||
Enum.each queued, fn { child, _ } -> child <- { :release, Process.self() } end
|
||||
Enum.each queued, fn { child, _ } -> child <- { :release, self() } end
|
||||
wait_for_messages([], output, callback, waiting, queued, result)
|
||||
end
|
||||
|
||||
@@ -82,7 +79,7 @@ defmodule Elixir.ParallelCompiler do
|
||||
defp wait_for_messages(files, output, callback, waiting, queued, result) do
|
||||
receive do
|
||||
{ :compiled, child, file } ->
|
||||
callback.(list_to_binary(file))
|
||||
callback.(file)
|
||||
new_queued = List.keydelete(queued, child, 1)
|
||||
# Sometimes we may have spurious entries in the waiting
|
||||
# list because someone invoked try/rescue UndefinedFunctionError
|
||||
@@ -101,7 +98,7 @@ defmodule Elixir.ParallelCompiler do
|
||||
end
|
||||
|
||||
{^child, file} = List.keyfind(queued, child, 1)
|
||||
IO.puts "== Compilation error on file #{list_to_binary(file)}#{extra} =="
|
||||
IO.puts "== Compilation error on file #{file}#{extra} =="
|
||||
Erlang.erlang.raise(kind, reason, stacktrace)
|
||||
end
|
||||
end
|
||||
@@ -110,7 +107,7 @@ defmodule Elixir.ParallelCompiler do
|
||||
defp release_waiting_processes(module, waiting) do
|
||||
Enum.filter waiting, fn { child, waiting_module } ->
|
||||
if waiting_module == module do
|
||||
child <- { :release, Process.self() }
|
||||
child <- { :release, self() }
|
||||
false
|
||||
else
|
||||
true
|
||||
@@ -0,0 +1,51 @@
|
||||
defmodule Kernel.ParallelRequire do
|
||||
@moduledoc """
|
||||
A module responsible for requiring files in parallel.
|
||||
"""
|
||||
|
||||
defmacrop default_callback, do: quote(do: fn x -> x end)
|
||||
|
||||
@doc """
|
||||
Requires the given files.
|
||||
|
||||
A callback that is invoked every time a file is required
|
||||
can be optionally given as argument.
|
||||
"""
|
||||
def files(files, callback // default_callback) do
|
||||
spawn_requires(files, [], callback, [])
|
||||
end
|
||||
|
||||
defp spawn_requires([], [], _callback, result), do: result
|
||||
defp spawn_requires([], waiting, callback, result), do: wait_for_messages([], waiting, callback, result)
|
||||
|
||||
defp spawn_requires(files, waiting, callback, result) when length(waiting) >= 4 do
|
||||
wait_for_messages(files, waiting, callback, result)
|
||||
end
|
||||
|
||||
defp spawn_requires([h|t], waiting, callback, result) do
|
||||
parent = self
|
||||
|
||||
child = spawn_link fn ->
|
||||
try do
|
||||
new = Code.require_file(h)
|
||||
result = new ++ result
|
||||
callback.(h)
|
||||
parent <- { :required, self }
|
||||
catch
|
||||
kind, reason ->
|
||||
parent <- { :failure, self, kind, reason, System.stacktrace }
|
||||
end
|
||||
end
|
||||
|
||||
spawn_requires(t, [child|waiting], callback, result)
|
||||
end
|
||||
|
||||
defp wait_for_messages(files, waiting, callback, result) do
|
||||
receive do
|
||||
{ :required, child } ->
|
||||
spawn_requires(files, List.delete(waiting, child), callback, result)
|
||||
{ :failure, _child, kind, reason, stacktrace } ->
|
||||
Erlang.erlang.raise(kind, reason, stacktrace)
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -1,8 +1,18 @@
|
||||
defmodule Elixir.SpecialForms do
|
||||
defmodule Kernel.SpecialForms do
|
||||
@moduledoc """
|
||||
In this module we define Elixir special forms. Those are called
|
||||
special forms because they cannot be overridden by the developer
|
||||
and sometimes have lexical scope (like refer, require, import, etc).
|
||||
and sometimes have lexical scope (like `alias`, `import`, etc).
|
||||
|
||||
This module also documents Elixir's pseudo variables (`__MODULE__`,
|
||||
`__FILE__`, `__ENV__` and `__CALLER__`). Pseudo variables return
|
||||
information about Elixir's compilation environment and can only
|
||||
be read, never assigned to.
|
||||
|
||||
Finally, it also documents 3 special forms (`__block__`,
|
||||
`__scope__` and `__aliases__`), which are not intended to be
|
||||
called directly by the developer but they appear in quoted
|
||||
contents since they are essential in Elixir's constructions.
|
||||
"""
|
||||
|
||||
@doc """
|
||||
@@ -36,42 +46,42 @@ defmodule Elixir.SpecialForms do
|
||||
defmacro :<<>>.(args)
|
||||
|
||||
@doc """
|
||||
`refer` is used to setup aliases between modules.
|
||||
`alias` is used to setup atom aliases, often useful with modules names.
|
||||
|
||||
## Examples
|
||||
|
||||
`refer` can be used to setup an alias for any module:
|
||||
`alias` can be used to setup an alias for any module:
|
||||
|
||||
defmodule Math do
|
||||
refer MyKeyword, as: Keyword
|
||||
alias MyKeyword, as: Keyword
|
||||
end
|
||||
|
||||
In the example above, we have set up `MyOrdict` to be referenced
|
||||
In the example above, we have set up `MyOrdict` to be alias
|
||||
as `Keyword`. So now, any reference to `Keyword` will be
|
||||
automatically replaced by `MyKeyword`.
|
||||
|
||||
In case one wants to access the original `Keyword`, it can be done
|
||||
by accessing __MAIN__:
|
||||
by accessing Elixir:
|
||||
|
||||
Keyword.values #=> uses MyKeyword.values
|
||||
__MAIN__.Keyword.values #=> uses Keyword.values
|
||||
Elixir.Keyword.values #=> uses Keyword.values
|
||||
|
||||
Notice that calling `refer` without the `as:` option automatically
|
||||
Notice that calling `alias` without the `as:` option automatically
|
||||
sets an alias based on the last part of the module. For example:
|
||||
|
||||
refer Foo.Bar.Baz
|
||||
alias Foo.Bar.Baz
|
||||
|
||||
Is the same as:
|
||||
|
||||
refer Foo.Bar.Baz, as: Baz
|
||||
alias Foo.Bar.Baz, as: Baz
|
||||
|
||||
## Lexical scope
|
||||
|
||||
`import`, `require` and `refer` are called directives and all
|
||||
`import`, `require` and `alias` are called directives and all
|
||||
have lexical scope. This means you can set up aliases inside
|
||||
specific functions and it won't affect the overall scope.
|
||||
"""
|
||||
defmacro refer(module, opts)
|
||||
defmacro alias(module, opts)
|
||||
|
||||
@doc """
|
||||
`require` is used to require the presence of external
|
||||
@@ -94,10 +104,10 @@ defmodule Elixir.SpecialForms do
|
||||
|
||||
An attempt to call a macro that was not loaded will raise an error.
|
||||
|
||||
## Refer shortcut
|
||||
## Alias shortcut
|
||||
|
||||
`require` also accepts `as:` as an option so it automatically sets
|
||||
up an alias. Please check `refer` for more information.
|
||||
up an alias. Please check `alias` for more information.
|
||||
|
||||
"""
|
||||
defmacro require(module, opts)
|
||||
@@ -140,8 +150,8 @@ defmodule Elixir.SpecialForms do
|
||||
|
||||
defmodule Math do
|
||||
def some_function do
|
||||
# 1) Disable `if/2` from Elixir.Builtin
|
||||
import Elixir.Builtin, except: [if: 2]
|
||||
# 1) Disable `if/2` from Kernel
|
||||
import Kernel, except: [if: 2]
|
||||
|
||||
# 2) Require the new `if` macro from MyMacros
|
||||
import MyMacros
|
||||
@@ -156,36 +166,35 @@ defmodule Elixir.SpecialForms do
|
||||
specific function. All other functions in that module will still
|
||||
be able to use the original one.
|
||||
|
||||
## Refer/Require shortcut
|
||||
## Alias/Require shortcut
|
||||
|
||||
All imported modules are also required by default. `import`
|
||||
also accepts `as:` as an option so it automatically sets up
|
||||
an alias. Please check `refer` for more information.
|
||||
an alias. Please check `alias` for more information.
|
||||
"""
|
||||
defmacro import(module, opts)
|
||||
|
||||
@doc """
|
||||
Returns the current environment information as a `Macro.Env`
|
||||
record. In the environment you can access the current filename,
|
||||
line numbers, set up aliases, the current function and others.
|
||||
"""
|
||||
defmacro __ENV__
|
||||
|
||||
@doc """
|
||||
Returns the current module name as an atom or nil otherwise.
|
||||
Although the module can be accessed in the __ENV__, this macro
|
||||
is a convenient shortcut.
|
||||
"""
|
||||
defmacro __MODULE__
|
||||
|
||||
@doc """
|
||||
Returns the current file name as a binary.
|
||||
Although the file can be accessed in the __ENV__, this macro
|
||||
is a convenient shortcut.
|
||||
"""
|
||||
defmacro __FILE__
|
||||
|
||||
@doc """
|
||||
Returns the current line number as an integer.
|
||||
"""
|
||||
defmacro __LINE__
|
||||
|
||||
@doc """
|
||||
Returns the current function as a tuple,
|
||||
where the first element is the name as an atom
|
||||
and the second is the arity as an integer.
|
||||
"""
|
||||
defmacro __FUNCTION__
|
||||
|
||||
@doc """
|
||||
Allows you to get the representation of any expression.
|
||||
|
||||
@@ -261,21 +270,54 @@ defmodule Elixir.SpecialForms do
|
||||
NoHygiene.interference
|
||||
a #=> 11
|
||||
|
||||
Notice that references are not hygienic in Elixir unless
|
||||
you explicitly access it via __MAIN__ to the reference name.
|
||||
Notice that aliases are not hygienic in Elixir, ambiguity
|
||||
must be solved by prepending Elixir:
|
||||
|
||||
quote do
|
||||
__MAIN__.Foo #=> Access the root Foo
|
||||
Foo #=> Access the Foo reference in the current
|
||||
module (if any is set), then fallback to root
|
||||
Elixir.Foo #=> Access the root Foo
|
||||
Foo #=> Access the Foo alias in the current module
|
||||
(if any is set), then fallback to Elixir.Foo
|
||||
end
|
||||
|
||||
## Options
|
||||
|
||||
`quote` also accepts some options as arguments. For example,
|
||||
hygiene can be turned off via `hygiene: false` which is useful
|
||||
when one is generating a code that should be inserted into
|
||||
some function.
|
||||
* `:hygiene` - When false, disables hygiene;
|
||||
* `:unquote` - When false, disables unquoting. Useful when you have a quote
|
||||
inside another quote and want to control which quote is able to unquote;
|
||||
* `:location` - When set to `:keep`, keeps the current line and file on quotes.
|
||||
Read the Stacktrace information section below for more information;
|
||||
|
||||
## Stacktrace information
|
||||
|
||||
One of Elixir goals is to provide proper stacktrace whenever there is an
|
||||
exception. In order to work properly with macros, the default behavior
|
||||
in quote is to set the line to 0. When a macro is invoked and the quoted
|
||||
expressions is expanded, 0 is replaced by the line of the call site.
|
||||
|
||||
This is a good behavior for the majority of the cases, except if the macro
|
||||
is defining new functions. Consider this example:
|
||||
|
||||
defmodule MyServer do
|
||||
use GenServer.Behavior
|
||||
end
|
||||
|
||||
`GenServer.Behavior` defines new functions in our `MyServer` module.
|
||||
However, if there is an exception in any of these functions, we want
|
||||
the stacktrace to point to the `GenServer.Behavior` and not the line
|
||||
that calls `use GenServer.Behavior`. For this reason, there is an
|
||||
option called `:location` that when set to `:keep` keeps these proper
|
||||
semantics:
|
||||
|
||||
quote location: :keep do
|
||||
def handle_call(request, _from, state) do
|
||||
{ :reply, :undef, state }
|
||||
end
|
||||
end
|
||||
|
||||
It is important to warn though that `location: :keep` evaluates the
|
||||
code as if it was defined inside `GenServer.Behavior` file, in
|
||||
particular, the macro `__FILE__` will always point to
|
||||
`GenServer.Behavior` file.
|
||||
"""
|
||||
defmacro quote(opts, do: contents)
|
||||
|
||||
@@ -317,135 +359,46 @@ defmodule Elixir.SpecialForms do
|
||||
"""
|
||||
defmacro unquote_splicing(expr)
|
||||
|
||||
@doc """
|
||||
Returns an anonymous function based on the given arguments.
|
||||
|
||||
## Examples
|
||||
|
||||
sum = fn(x, y) -> x + y end
|
||||
sum.(1, 2) #=> 3
|
||||
|
||||
Notice that a function needs to be invoked using the dot between
|
||||
the function and the arguments.
|
||||
|
||||
A function could also be defined using the `end` syntax, although
|
||||
it is recommend to use it only with the stab operator in order to
|
||||
avoid ambiguity. For example, consider this case:
|
||||
|
||||
Enum.map [1,2,3], fn x ->
|
||||
x * 2
|
||||
end
|
||||
|
||||
The example works fine because `->` binds to the closest function call,
|
||||
which is `fn`, but if we replace it by `do/end`, it will fail:
|
||||
|
||||
Enum.map [1,2,3], fn(x) do
|
||||
x * 2
|
||||
end
|
||||
|
||||
The reason it fails is because do/end always bind to the farthest
|
||||
function call.
|
||||
|
||||
## Function with multiple clauses
|
||||
|
||||
One may define a function which expects different clauses as long
|
||||
as all clauses expects the same number of arguments:
|
||||
|
||||
fun = fn do
|
||||
x, y when y < 0 ->
|
||||
x - y
|
||||
x, y ->
|
||||
x + y
|
||||
end
|
||||
|
||||
fun.(10, -10) #=> 20
|
||||
fun.(10, 10) #=> 20
|
||||
|
||||
"""
|
||||
defmacro fn(args)
|
||||
|
||||
@doc """
|
||||
Handle annonymous recursive loops.
|
||||
|
||||
## Examples
|
||||
|
||||
list = [1,2,3]
|
||||
|
||||
loop list, [] do
|
||||
[h|t], acc ->
|
||||
recur t, [h*2|acc]
|
||||
[], acc ->
|
||||
acc
|
||||
end
|
||||
#=> [6,4,2]
|
||||
|
||||
Notice that all match clauses expects the same ammount
|
||||
of arguments. Guards can also be given.
|
||||
|
||||
Recursion happens by calling recur with the same number
|
||||
of arguments of each match clause. `recur` does not guarantee
|
||||
that it will be tail recursive.
|
||||
"""
|
||||
defmacro loop(args)
|
||||
|
||||
@doc """
|
||||
A function that forces the current loop to recur. See `loop/1`
|
||||
for more information.
|
||||
"""
|
||||
defmacro recur(args)
|
||||
|
||||
@doc """
|
||||
List comprehensions allow you to quickly build a list from another list:
|
||||
|
||||
lc n in [1,2,3,4], do: n * 2
|
||||
lc n inlist [1,2,3,4], do: n * 2
|
||||
#=> [2,4,6,8]
|
||||
|
||||
A comprehension accepts many generators and also filters. Filters must be given after the when clause:
|
||||
|
||||
# A comprehension with a generator and a filter
|
||||
lc n in [1,2,3,4,5,6] when rem(n, 2) == 0, do: n
|
||||
#=> [2,4,6]
|
||||
|
||||
# A comprehension with two generators
|
||||
lc x in [1,2], y in [2,3], do: x*y
|
||||
#=> [2,3,4,6]
|
||||
|
||||
Elixir provides generators for both lists and bitstrings:
|
||||
A comprehension accepts many generators and also filters. Generators
|
||||
are defined using both `inlist` and `inbits` operators, allowing you
|
||||
to loop lists and bitstrings:
|
||||
|
||||
# A list generator:
|
||||
lc n in [1,2,3,4], do: n * 2
|
||||
lc n inlist [1,2,3,4], do: n * 2
|
||||
#=> [2,4,6,8]
|
||||
|
||||
# A bit string generator:
|
||||
lc <<n>> in <<1,2,3,4>>, do: n * 2
|
||||
lc <<n>> inbits <<1,2,3,4>>, do: n * 2
|
||||
#=> [2,4,6,8]
|
||||
|
||||
Bit string generators are quite useful when you need to organize bit string streams:
|
||||
# A generator from a variable:
|
||||
list = [1,2,3,4]
|
||||
lc n inlist list, do: n * 2
|
||||
#=> [2,4,6,8]
|
||||
|
||||
# A comprehension with two generators
|
||||
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
|
||||
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>> in pixels, do: {r,g,b}
|
||||
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}]
|
||||
|
||||
Elixir does its best to hide the differences between list and bit string generators.
|
||||
However, there is a special case due to Erlang limitation where we need to explicitly
|
||||
tell Erlang that a list is being given as argument:
|
||||
|
||||
# This will fail because when Elixir sees that the left side
|
||||
# of the in expression is a bit string, it expects the right side
|
||||
# to be a bit string as well:
|
||||
lc <<n>> in [<<1>>,<<2>>,<<3>>], do: n*2
|
||||
#=> ** (ErlangError) erlang error {:bad_generator,[<<1>>,<<2>>,<<3>>]}
|
||||
|
||||
# You need to be explicit and use inlist:
|
||||
lc inlist(<<n>>, [<<1>>,<<2>>,<<3>>]), do: n*2
|
||||
#=> [2,4,6]
|
||||
|
||||
# For consistency, inbin is also available:
|
||||
lc inbin(<<n>>, <<1,2,3>>), do: n*2
|
||||
#=> [2,4,6]
|
||||
|
||||
Notice that although comprehensions uses `when` to specify filters, filters are not
|
||||
guards and therefore accept any expression (they are not limited as guards).
|
||||
"""
|
||||
defmacro lc(args)
|
||||
|
||||
@@ -455,42 +408,43 @@ defmodule Elixir.SpecialForms do
|
||||
be a bitstring. For example, here is how to remove all
|
||||
spaces from a string:
|
||||
|
||||
bc <<c>> in " hello world " when c != ?\s, do: <<c>>
|
||||
bc <<c>> inbits " hello world ", c != ? , do: <<c>>
|
||||
"helloworld"
|
||||
|
||||
"""
|
||||
defmacro bc(args)
|
||||
|
||||
@doc """
|
||||
Keeps one of the given expressions depending in the context
|
||||
of evaluation is a guard or not. This is useful when creating
|
||||
macro that should work both inside and outside guards but
|
||||
still hold some characteristics.
|
||||
This is the special form used whenever we have a block
|
||||
of expressions in Elixir. This special form is private
|
||||
and should not be invoked directly:
|
||||
|
||||
## Example
|
||||
quote do: (1; 2; 3)
|
||||
#=> { :__block__, 0, [1,2,3] }
|
||||
|
||||
A good example is the `is_exception/1` macro defined in Elixir:
|
||||
|
||||
defmacro is_exception(thing) do
|
||||
quote do
|
||||
quote do
|
||||
is_tuple(unquote(thing)) and elem(unquote(thing), 2) == :__exception__
|
||||
else
|
||||
result = unquote(thing)
|
||||
is_tuple(result) and elem(result, 2) == :__exception__
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
Notice that if inside a guard, we unquote the same element twice.
|
||||
This will cause the same element to be evaluted twice, but this is
|
||||
fine for guards since we cannot assign variables in guards and
|
||||
we cannot call expressions inside guards. However, when outside
|
||||
of a guard, evaluating the arguments twice can be harmful and
|
||||
unexpected, for this reason, we save the result in a variable.
|
||||
|
||||
In the example above, `in_guard` is allowing us to customize
|
||||
the same macro to work inside and outside guards.
|
||||
"""
|
||||
defmacro in_guard(do: do_block, else: else_block)
|
||||
defmacro __block__(args)
|
||||
|
||||
@doc """
|
||||
This is the special form used whenever we have to temporarily
|
||||
change the scope information of a block. Used when `quote` is
|
||||
invoked with `location: :keep` to execute a given block as if
|
||||
it belonged to another file.
|
||||
|
||||
quote location: :keep, do: 1
|
||||
#=> { :__scope__, 1,[[file: "iex"],[do: 1]] }
|
||||
|
||||
Check `quote/1` for more information.
|
||||
"""
|
||||
defmacro __scope__(opts, args)
|
||||
|
||||
@doc """
|
||||
This is the special form used to hold aliases information.
|
||||
At compilation time, it is usually compiled to an atom:
|
||||
|
||||
quote do: Foo.Bar
|
||||
{ :__aliases__, 0, [:Foo,:Bar] }
|
||||
|
||||
"""
|
||||
defmacro __aliases__(args)
|
||||
end
|
||||
@@ -0,0 +1,244 @@
|
||||
defmodule Kernel.Typespec do
|
||||
@moduledoc """
|
||||
This is the module that converts Elixir typespecs
|
||||
to Erlang typespecs syntax. Everytime @spec, @type
|
||||
and @typep are used they proxy to the functions
|
||||
in this module.
|
||||
"""
|
||||
|
||||
defmacro deftype(name, options // []) do
|
||||
_deftype(name, true, __CALLER__, options)
|
||||
end
|
||||
|
||||
defmacro deftypep(name) do
|
||||
_deftype(name, false, __CALLER__, [])
|
||||
end
|
||||
|
||||
defmacro defspec(spec, block) do
|
||||
_defspec(:spec, __CALLER__, spec, block)
|
||||
end
|
||||
|
||||
defmacro defcallback(spec, block) do
|
||||
_defspec(:callback, __CALLER__, spec, block)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Get the types defined for the given module. This function
|
||||
is only available for modules being compiled. If the module
|
||||
was already compiled, you need to loop its attributes
|
||||
to get such information.
|
||||
"""
|
||||
def get_types(module) do
|
||||
Module.read_attribute(module, :type) ++ Module.read_attribute(module, :opaque)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Get the specs defined for the given module. This function
|
||||
is only available for modules being compiled. If the module
|
||||
was already compiled, you need to loop its attributes
|
||||
to get such information.
|
||||
"""
|
||||
def get_specs(module) do
|
||||
specs = :ets.tab2list(spec_table_for(module))
|
||||
keys = :lists.ukeysort(1, specs)
|
||||
lc { k, _ } inlist keys, do: { k, :proplists.append_values(k, specs) }
|
||||
end
|
||||
|
||||
## Typespec conversion
|
||||
|
||||
# Handle unions
|
||||
defp typespec({ :|, line, [_,_] } = exprs, vars, caller) do
|
||||
exprs = List.reverse(collect_union(exprs))
|
||||
union = lc e inlist exprs, do: typespec(e, vars, caller)
|
||||
{ :type, line, :union, union }
|
||||
end
|
||||
|
||||
# Handle binaries
|
||||
defp typespec({:<<>>, line, []}, _,_) do
|
||||
{:type, line, :binary, [{:integer, line, 0}, {:integer, line, 0}]}
|
||||
end
|
||||
|
||||
defp typespec({:<<>>, line, [{:|, _, [{:_, line1, atom}, {:*, _, [{:_, line2, atom}, unit]}]}]}, _, _) when is_atom(atom) do
|
||||
{:type, line, :binary, [{:integer, line1, 0}, {:integer, line2, unit}]}
|
||||
end
|
||||
|
||||
defp typespec({:<<>>, line, [{:|, line1, [{:_, line2, atom}, base]}]}, _, _) when is_atom(atom) do
|
||||
{:type, line, :binary, [{:integer, line1, base}, {:integer, line2, 0}]}
|
||||
end
|
||||
|
||||
# Handle ranges
|
||||
defp typespec({:"..", line, args}, vars, caller) do
|
||||
typespec({:range, line, args}, vars, caller)
|
||||
end
|
||||
|
||||
# Handle aliases
|
||||
defp typespec({:__aliases__, _, _} = alias, vars, caller) do
|
||||
atom = Macro.expand alias, caller
|
||||
typespec(atom, vars, caller)
|
||||
end
|
||||
|
||||
# Handle type operator
|
||||
defp typespec({:"::", line, [var, expr] }, vars, caller) do
|
||||
left = typespec(var, [elem(var,1)|vars], caller)
|
||||
right = typespec(expr, vars, caller)
|
||||
{ :ann_type, line, [left, right] }
|
||||
end
|
||||
|
||||
# Handle unary ops
|
||||
defp typespec({op, line, [integer]}, _, _) when op in [:+, :-] and is_integer(integer) do
|
||||
{ :op, line, op, {:integer, line, integer} }
|
||||
end
|
||||
|
||||
# Handle remote calls
|
||||
defp typespec({{:., line, [remote, name]}, _, args}, vars, caller) do
|
||||
remote = Macro.expand remote, caller
|
||||
unless is_atom(remote), do: raise(ArgumentError, message: "Invalid remote in typespec")
|
||||
remote_type({typespec(remote, vars, caller), line, typespec(name, vars, caller), args}, vars, caller)
|
||||
end
|
||||
|
||||
# Handle tuples
|
||||
defp typespec({:tuple, line, atom}, vars, caller) when is_atom(atom) do
|
||||
typespec({:{}, line, []}, vars, caller)
|
||||
end
|
||||
|
||||
defp typespec({:{}, line, []}, _, _) do
|
||||
{ :type, line, :tuple, :any }
|
||||
end
|
||||
|
||||
defp typespec({:{}, line, t}, vars, caller) when is_list(t) do
|
||||
args = lc e inlist t, do: typespec(e, vars, caller)
|
||||
{ :type, line, :tuple, args }
|
||||
end
|
||||
|
||||
# Handle funs
|
||||
defp typespec({:fun, line, arguments}, vars, caller) when is_list(arguments) do
|
||||
args =
|
||||
case List.reverse(arguments) do
|
||||
[[{:do,h}]|t] -> fn_args(line, List.reverse(t), h, vars, caller)
|
||||
[] -> []
|
||||
_ -> [fn_args(line, arguments, vars, caller)]
|
||||
end
|
||||
|
||||
{ :type, line, :fun, args }
|
||||
end
|
||||
|
||||
# Handle variables or local calls
|
||||
defp typespec({name, line, atom}, vars, caller) when is_atom(atom) do
|
||||
if List.member?(vars, name) do
|
||||
{ :var, line, name }
|
||||
else
|
||||
typespec({name, line, []}, vars, caller)
|
||||
end
|
||||
end
|
||||
|
||||
# Handle local calls
|
||||
defp typespec({name, line, arguments}, vars, caller) do
|
||||
arguments = lc arg inlist arguments, do: typespec(arg, vars, caller)
|
||||
{ :type, line, name, arguments }
|
||||
end
|
||||
|
||||
# Handle literals
|
||||
defp typespec(atom, _, _) when is_atom(atom) do
|
||||
{ :atom, 0, atom }
|
||||
end
|
||||
|
||||
defp typespec(integer, _, _) when is_integer(integer) do
|
||||
{ :integer, 0, integer }
|
||||
end
|
||||
|
||||
defp typespec([], vars, caller) do
|
||||
typespec({ nil, 0, [] }, vars, caller)
|
||||
end
|
||||
|
||||
defp typespec([spec], vars, caller) do
|
||||
typespec({ :list, 0, [spec] }, vars, caller)
|
||||
end
|
||||
|
||||
defp typespec(l, _, _) when is_list(l) do
|
||||
raise(ArgumentError, message: "Unexpected list #{inspect l}")
|
||||
end
|
||||
|
||||
defp typespec(t, vars, caller) when is_tuple(t) do
|
||||
args = lc e inlist tuple_to_list(t), do: typespec(e, vars, caller)
|
||||
{ :type, 0, :tuple, args }
|
||||
end
|
||||
|
||||
## Helpers
|
||||
|
||||
defp remote_type({remote, line, name, arguments}, vars, caller) do
|
||||
arguments = lc arg inlist arguments, do: typespec(arg, vars, caller)
|
||||
{ :remote_type, line, [ remote, name, arguments ] }
|
||||
end
|
||||
|
||||
defp collect_union({ :|, _, [a, b] }), do: [b|collect_union(a)]
|
||||
defp collect_union(v), do: [v]
|
||||
|
||||
defp fn_args(line, args, return, vars, caller) do
|
||||
[fn_args(line, args, vars, caller), typespec(return, vars, caller)]
|
||||
end
|
||||
|
||||
defp fn_args(line, [{:"...", _, _}], _vars, _caller) do
|
||||
{ :type, line, :any }
|
||||
end
|
||||
|
||||
defp fn_args(line, args, vars, caller) do
|
||||
args = lc arg inlist args, do: typespec(arg, vars, caller)
|
||||
{ :type, line, :product, args }
|
||||
end
|
||||
|
||||
def _deftype({:"::", _, [name, definition]}, export, caller, opts) do
|
||||
_deftype(name, definition, export, caller, opts)
|
||||
end
|
||||
|
||||
def _deftype(name, export, caller, opts) do
|
||||
_deftype(name, { :term, caller.line, nil }, export, caller, opts)
|
||||
end
|
||||
|
||||
defp _deftype({name, _, args}, definition, export, caller, options) do
|
||||
args = if is_atom(args), do: [], else: lc(arg inlist args, do: variable(arg))
|
||||
vars = lc {:var, _, var} inlist args, do: var
|
||||
spec = typespec(definition, vars, caller)
|
||||
|
||||
vars = lc ({:var, _, _} = var) inlist args, do: var
|
||||
attr = if options[:opaque], do: :opaque, else: :type
|
||||
|
||||
export = if export do
|
||||
quote do: Module.compile_type(__MODULE__, :export_type, [{name, length(vars)}])
|
||||
else
|
||||
nil
|
||||
end
|
||||
|
||||
quote do
|
||||
name = unquote(name)
|
||||
spec = unquote(Macro.escape(spec))
|
||||
vars = unquote(Macro.escape(vars))
|
||||
type = { name, spec, vars }
|
||||
Module.compile_type __MODULE__, unquote(attr), type
|
||||
unquote(export)
|
||||
{ unquote(attr), type }
|
||||
end
|
||||
end
|
||||
|
||||
defp _defspec(type, caller, {name, line, args},[{:do,return}]) do
|
||||
spec = { :type, line, :fun, fn_args(line, args, return, [], caller) }
|
||||
code = Macro.escape { {type, { name, length(args) }}, [spec] }
|
||||
table = spec_table_for(caller.module)
|
||||
|
||||
quote do
|
||||
code = unquote(code)
|
||||
:ets.insert(unquote(table), code)
|
||||
code
|
||||
end
|
||||
end
|
||||
|
||||
defp spec_table_for(module) do
|
||||
table = list_to_atom Erlang.lists.concat([:s, module])
|
||||
unless table == :ets.info(table, :name), do:
|
||||
raise(ArgumentError, message: "cannot manage specs for #{inspect module} because it was already compiled")
|
||||
table
|
||||
end
|
||||
|
||||
defp variable({name, line, _}) do
|
||||
{:var, line, name}
|
||||
end
|
||||
end
|
||||
@@ -87,6 +87,20 @@ defmodule Keyword do
|
||||
def get([{_, value}|_], _key, _default), do: value
|
||||
def get([], _, default), do: default
|
||||
|
||||
@doc """
|
||||
Gets the value for specific key. If key does not exist,
|
||||
an error is raised.
|
||||
|
||||
## Examples
|
||||
|
||||
Keyword.get! [a: 1], :a #=> 1
|
||||
Keyword.get! [a: 1], :b #=> raises KeyError[key: :b]
|
||||
"""
|
||||
def get!([{k, _}|_], key) when key < k, do: raise(Keyword.KeyError, key: key)
|
||||
def get!([{k, _}|d], key) when key > k, do: get!(d, key)
|
||||
def get!([{_, value}|_], _key), do: value
|
||||
def get!([], key), do: raise(Keyword.KeyError, key: key)
|
||||
|
||||
@doc """
|
||||
Gets all values for a specific key.
|
||||
|
||||
@@ -111,7 +125,7 @@ defmodule Keyword do
|
||||
|
||||
"""
|
||||
def keys(keywords) do
|
||||
lc { key, _ } in keywords, do: key
|
||||
lc { key, _ } inlist keywords, do: key
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -122,7 +136,7 @@ defmodule Keyword do
|
||||
Keyword.values [a: 1, b: 2] #=> [1,2]
|
||||
"""
|
||||
def values(keywords) do
|
||||
lc { _, value } in keywords, do: value
|
||||
lc { _, value } inlist keywords, do: value
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -10,14 +10,6 @@ defmodule List do
|
||||
first argument.
|
||||
"""
|
||||
|
||||
@doc """
|
||||
Simply invokes the Access protocol for the given list.
|
||||
Check `Access.List` for more information.
|
||||
"""
|
||||
def access(list, access) when is_list(list) do
|
||||
Access.List.access(list, access)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Given a list of lists, concatenates the sublists into a single list.
|
||||
|
||||
@@ -44,10 +36,6 @@ defmodule List do
|
||||
#=> [1,2,3,4,5,6]
|
||||
|
||||
"""
|
||||
def concat([h], elements) when is_list(elements) do
|
||||
[h|elements]
|
||||
end
|
||||
|
||||
def concat(list, elements) when is_list(list) and is_list(elements) do
|
||||
list ++ elements
|
||||
end
|
||||
@@ -130,7 +118,9 @@ defmodule List do
|
||||
#=> [3,2,1]
|
||||
|
||||
"""
|
||||
defdelegate [reverse: 1], to: Erlang.lists
|
||||
def reverse(list) do
|
||||
:lists.reverse(list)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns the last element in `list` or nil if the `list` is empty.
|
||||
@@ -147,7 +137,9 @@ defmodule List do
|
||||
"""
|
||||
def last([]), do: nil
|
||||
|
||||
defdelegate [last: 1], to: Erlang.lists
|
||||
def last(list) do
|
||||
:lists.last(list)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Checks if the given `term` is included in the list.
|
||||
@@ -284,7 +276,6 @@ defmodule List do
|
||||
:lists.sort list
|
||||
end
|
||||
|
||||
|
||||
@doc """
|
||||
Sorts the list according to an ordering function. fun(a, b) should
|
||||
return true if `a` compares less than or equal to `b`, `false` otherwise.
|
||||
@@ -327,26 +318,6 @@ defmodule List do
|
||||
Erlang.lists.duplicate(n, elem)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Looks for a term in a list and returns its position.
|
||||
If term is found in the first position, return 1.
|
||||
If no terms not found in list, the return value is nil.
|
||||
|
||||
### Examples
|
||||
|
||||
List.find_index ['a'], 'b'
|
||||
#=> nil
|
||||
List.find_index ['a'], 'a'
|
||||
#=> 1
|
||||
"""
|
||||
def find_index(list, term) do
|
||||
index = Erlang.string.str(list, [term])
|
||||
case index == 0 do
|
||||
true -> nil
|
||||
false -> index
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Wraps the argument in a list.
|
||||
If the argument is already a list, returns the list.
|
||||
@@ -6,7 +6,7 @@ defprotocol List.Chars do
|
||||
`to_char_list` which does the conversion.
|
||||
|
||||
The `to_char_list` function automatically imported
|
||||
by Elixir.Builtin invokes this protocol.
|
||||
by Kernel invokes this protocol.
|
||||
"""
|
||||
|
||||
@only [BitString, List, Atom, Number, Record]
|
||||
@@ -0,0 +1,370 @@
|
||||
import Kernel, except: [to_binary: 1]
|
||||
|
||||
defmodule Macro do
|
||||
@moduledoc """
|
||||
This module provides conveniences for working with macros.
|
||||
"""
|
||||
|
||||
@doc """
|
||||
Returns a list of binary operators. This is available
|
||||
as a macro so it can be used in guard clauses.
|
||||
"""
|
||||
defmacro binary_ops do
|
||||
[
|
||||
:===, :!==,
|
||||
:==, :!=, :<=, :>=,
|
||||
:&&, :||, :<>, :++, :--, :**, ://, :::, :<-, :.., :/>, :=~,
|
||||
:<, :>,
|
||||
:+, :-, :*, :/, :=, :|, :.,
|
||||
:and, :or, :xor, :when, :in, :inlist, :inbits,
|
||||
:<<<, :>>>, :|||, :&&&, :^^^, :~~~
|
||||
]
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns a list of unary operators. This is available
|
||||
as a macro so it can be used in guard clauses.
|
||||
"""
|
||||
defmacro unary_ops do
|
||||
[:!, :@, :^, :not, :+, :-]
|
||||
end
|
||||
|
||||
@doc """
|
||||
Recursively escapes the given value so it can be inserted
|
||||
into a syntax tree. Structures that are valid syntax nodes
|
||||
(like atoms, integers, binaries) are represented by themselves.
|
||||
|
||||
## Examples
|
||||
|
||||
Macro.escape(:foo)
|
||||
#=> :foo
|
||||
|
||||
Macro.escape({ :a, :b, :c })
|
||||
#=> { :{}, 0, [:a, :b, :c] }
|
||||
"""
|
||||
def escape({ left, right }) do
|
||||
{ escape(left), escape(right) }
|
||||
end
|
||||
|
||||
def escape(tuple) when is_tuple(tuple) do
|
||||
{ :{}, 0, escape(tuple_to_list(tuple)) }
|
||||
end
|
||||
|
||||
def escape(list) when is_list(list) do
|
||||
lc item inlist list, do: escape(item)
|
||||
end
|
||||
|
||||
def escape(other), do: other
|
||||
|
||||
@doc """
|
||||
Converts the given expression to a binary.
|
||||
|
||||
## Examples
|
||||
|
||||
Macro.to_binary(quote do: foo.bar(1, 2, 3))
|
||||
#=> "foo.bar(1, 2, 3)"
|
||||
|
||||
"""
|
||||
def to_binary(tree)
|
||||
|
||||
# Variables
|
||||
def to_binary({ 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))
|
||||
end
|
||||
|
||||
# Blocks
|
||||
def to_binary({ :__block__, _, [expr] }) do
|
||||
to_binary(expr)
|
||||
end
|
||||
|
||||
def to_binary({ :__block__, _, _ } = expr) do
|
||||
block = adjust_new_lines block_to_binary(expr), "\n "
|
||||
"(\n " <> block <> "\n)"
|
||||
end
|
||||
|
||||
# Bits containers
|
||||
def to_binary({ :<<>>, _, args }) do
|
||||
"<<" <> Enum.map_join(args, ", ", to_binary(&1)) <> ">>"
|
||||
end
|
||||
|
||||
# Tuple containers
|
||||
def to_binary({ :{}, _, args }) do
|
||||
"{" <> Enum.map_join(args, ", ", to_binary(&1)) <> "}"
|
||||
end
|
||||
|
||||
# List containers
|
||||
def to_binary({ :[], _, args }) do
|
||||
"[" <> Enum.map_join(args, ", ", to_binary(&1)) <> "]"
|
||||
end
|
||||
|
||||
# Fn keyword
|
||||
def to_binary({ :fn, _, [[do: block]] }) do
|
||||
"fn " <> block_to_binary(block) <> "\nend"
|
||||
end
|
||||
|
||||
# Partial call
|
||||
def to_binary({ :&, _, [num] }) do
|
||||
"&#{num}"
|
||||
end
|
||||
|
||||
# Binary ops
|
||||
def to_binary({ op, _, [left, right] }) when op in binary_ops do
|
||||
op_to_binary(left) <> " #{op} " <> op_to_binary(right)
|
||||
end
|
||||
|
||||
# Unary ops
|
||||
def to_binary({ op, _, [arg] }) when op in unary_ops do
|
||||
atom_to_binary(op, :utf8) <> to_binary(arg)
|
||||
end
|
||||
|
||||
# All other calls
|
||||
def to_binary({ target, _, args }) when is_list(args) do
|
||||
{ list, last } = Erlang.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)
|
||||
end
|
||||
end
|
||||
|
||||
# Two-item tuples
|
||||
def to_binary({ left, right }) do
|
||||
to_binary({ :{}, 0, [left, right] })
|
||||
end
|
||||
|
||||
# Lists
|
||||
def to_binary(list) when is_list(list) do
|
||||
to_binary({ :[], 0, list })
|
||||
end
|
||||
|
||||
# All other structures
|
||||
def to_binary(other), do: Binary.Inspect.inspect(other)
|
||||
|
||||
# Block keywords
|
||||
defmacrop kw_keywords, do: [:do, :catch, :rescue, :after, :else]
|
||||
|
||||
defp is_kw_blocks?([_|_] = kw), do: Enum.all?(kw, fn({x,_}) -> x in kw_keywords end)
|
||||
defp is_kw_blocks?(_), do: false
|
||||
|
||||
defp call_to_binary(atom) when is_atom(atom), do: atom_to_binary(atom, :utf8)
|
||||
defp call_to_binary({ :., _, [arg] }), do: call_to_binary(arg) <> "."
|
||||
defp call_to_binary({ :., _, [left, right] }), do: call_to_binary(left) <> "." <> call_to_binary(right)
|
||||
defp call_to_binary(other), do: to_binary(other)
|
||||
|
||||
defp call_to_binary_with_args(target, args) do
|
||||
args = Enum.map_join(args, ", ", to_binary(&1))
|
||||
call_to_binary(target) <> "(" <> args <> ")"
|
||||
end
|
||||
|
||||
defp kw_blocks_to_binary(kw) do
|
||||
Enum.reduce(kw_keywords, " ", fn(x, acc) ->
|
||||
case Keyword.key?(kw, x) do
|
||||
true -> acc <> kw_block_to_binary(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 "
|
||||
atom_to_binary(key, :utf8) <> "\n " <> block <> "\n"
|
||||
end
|
||||
|
||||
defp block_to_binary({ :->, _, exprs }) do
|
||||
Enum.map_join(exprs, "\n", fn({ left, right }) ->
|
||||
left = Enum.map_join(left, ", ", to_binary(&1))
|
||||
left <> " ->\n " <> adjust_new_lines block_to_binary(right), "\n "
|
||||
end)
|
||||
end
|
||||
|
||||
defp block_to_binary({ :__block__, _, exprs }) do
|
||||
Enum.map_join(exprs, "\n", to_binary(&1))
|
||||
end
|
||||
|
||||
defp block_to_binary(other), do: to_binary(other)
|
||||
|
||||
defp op_to_binary({ op, _, [_, _] } = expr) when op in binary_ops do
|
||||
"(" <> to_binary(expr) <> ")"
|
||||
end
|
||||
|
||||
defp op_to_binary(expr), do: to_binary(expr)
|
||||
|
||||
defp adjust_new_lines(block, replacement) do
|
||||
bc <<x>> inbits block do
|
||||
<< case x == ?\n do
|
||||
true -> replacement
|
||||
false -> <<x>>
|
||||
end | :binary >>
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Receives an expression representation and expands it. The following
|
||||
contents are expanded:
|
||||
|
||||
* Macros (local or remote);
|
||||
* Aliases are expanded (if possible) and return atoms;
|
||||
* All pseudo-variables (__FILE__, __MODULE__, etc);
|
||||
|
||||
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. For example, `!some_macro` will expand as:
|
||||
|
||||
iex> IO.puts Macro.to_binary Macro.expand(quote(do: !some_macro), __ENV__)
|
||||
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.
|
||||
|
||||
## Examples
|
||||
|
||||
In the example below, we have a macro that generates a module
|
||||
with a function named `name_length` that returns the length
|
||||
of the module name. The value of this function will be calculated
|
||||
at compilation time and not at runtime.
|
||||
|
||||
Consider the implementation below:
|
||||
|
||||
defmacro defmodule_with_length(name, do: block) do
|
||||
length = length(atom_to_list(name))
|
||||
|
||||
quote do
|
||||
defmodule unquote(name) do
|
||||
def name_length, do: unquote(length)
|
||||
unquote(block)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
When invoked like this:
|
||||
|
||||
defmodule_with_length My.Module do
|
||||
def other_function, do: ...
|
||||
end
|
||||
|
||||
The compilation will fail because `My.Module` when quoted
|
||||
is not an atom, but a syntax tree as follow:
|
||||
|
||||
{:__aliases__, 0, [:My, :Module] }
|
||||
|
||||
That said, we need to expand the aliases node above to an
|
||||
atom, so we can retrieve its length. Expanding the node is
|
||||
not straight-forward because we also need to expand the
|
||||
caller aliases. For example:
|
||||
|
||||
alias MyHelpers, as: My
|
||||
|
||||
defmodule_with_length My.Module do
|
||||
def other_function, do: ...
|
||||
end
|
||||
|
||||
The final module name will be `MyHelpers.Module` and not
|
||||
`My.Module`. With `Macro.expand`, such aliases are taken
|
||||
into consideration. Local and remote macros are also
|
||||
expanded. We could rewrite our macro above to use this
|
||||
function as:
|
||||
|
||||
defmacro defmodule_with_length(name, do: block) do
|
||||
expanded = Macro.expand(name, __CALLER__)
|
||||
length = length(atom_to_list(expanded))
|
||||
|
||||
quote do
|
||||
defmodule unquote(name) do
|
||||
def name_length, do: unquote(length)
|
||||
unquote(block)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
"""
|
||||
def expand(aliases, env)
|
||||
|
||||
# The first case we handle is __aliases__. In case
|
||||
# aliases just contain one item, we are sure it is
|
||||
# an atom, so we just expand it based on the aliases
|
||||
# dict.
|
||||
def expand({ :__aliases__, _, [h] }, env) when h != Elixir do
|
||||
expand_alias(h, env)
|
||||
end
|
||||
|
||||
# In case aliases contains more than one item, we need
|
||||
# to loop them checking if they are all atoms or not.
|
||||
# Macros and pseudo-variables are then expanded.
|
||||
def expand({ :__aliases__, _, [h|t] }, env) do
|
||||
aliases = case h do
|
||||
x when is_atom(x) and x != Elixir -> [expand_alias(x, env)|t]
|
||||
_ -> [h|t]
|
||||
end
|
||||
|
||||
aliases = lc alias inlist aliases, do: expand(alias, env)
|
||||
:lists.all(is_atom(&1), aliases) && Erlang.elixir_aliases.concat(aliases)
|
||||
end
|
||||
|
||||
# Expand Erlang.foo calls
|
||||
def expand({ { :., _, [{ :__aliases__, _, [:Erlang] }, atom] }, _, args }, _env) when
|
||||
is_atom(atom) and (is_atom(args) or args == []), do: atom
|
||||
|
||||
# Expand pseudo-variables
|
||||
def expand({ :__MODULE__, _, atom }, env) when is_atom(atom), do: env.module
|
||||
def expand({ :__FILE__, _, atom }, env) when is_atom(atom), do: env.file
|
||||
def expand({ :__ENV__, _, atom }, env) when is_atom(atom), do: env
|
||||
|
||||
# Expand possible macro import invocation
|
||||
def expand({ atom, line, args } = original, env) when is_atom(atom) do
|
||||
args = case is_atom(args) do
|
||||
true -> []
|
||||
false -> args
|
||||
end
|
||||
|
||||
case not is_partial?(args) do
|
||||
false -> original
|
||||
true ->
|
||||
expand = Erlang.elixir_dispatch.expand_import(line, { atom, length(args) }, args,
|
||||
env.module, env.function, env.requires, env.macros, env)
|
||||
case expand do
|
||||
{ :ok, _, expanded } -> expanded
|
||||
{ :error, _ } -> original
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Expand possible macro require invocation
|
||||
def expand({ { :., _, [left, right] }, line, args } = original, env) when is_atom(right) do
|
||||
receiver = expand(left, env)
|
||||
|
||||
case is_atom(receiver) and not is_partial?(args) do
|
||||
false -> original
|
||||
true ->
|
||||
expand = Erlang.elixir_dispatch.expand_require(line, receiver, { right, length(args) },
|
||||
args, env.module, env.function, env.requires, env)
|
||||
case expand do
|
||||
{ :ok, expanded } -> expanded
|
||||
{ :error, _ } -> original
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Anything else is just returned
|
||||
def expand(other, _env), do: other
|
||||
|
||||
## Helpers
|
||||
|
||||
defp is_partial?(args) do
|
||||
:lists.any(match?({ :&, _, [_] }, &1), args)
|
||||
end
|
||||
|
||||
defp expand_alias(h, env) do
|
||||
atom = list_to_atom('Elixir-' ++ atom_to_list(h))
|
||||
Erlang.elixir_aliases.lookup(atom, env.aliases)
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,90 @@
|
||||
# We generate this record using a raw module due to
|
||||
# bootstrap constraints. Notice the fields are not
|
||||
# represented by a keywords list because we want
|
||||
# to keep control over the order.
|
||||
defmodule Macro.Env do
|
||||
@doc """
|
||||
A record that contains compile time environment information,
|
||||
It can be accessed at any time by calling __ENV__.
|
||||
"""
|
||||
|
||||
def __access__(caller, args) do
|
||||
Record.access(caller, __MODULE__, __record__(:fields), args)
|
||||
end
|
||||
|
||||
def __record__(kind, _), do: __record__(kind)
|
||||
def __record__(:name), do: Macro.Env
|
||||
|
||||
# When adding removing new fields,
|
||||
# src/elixir_tree_helpers.erl needs to be changed as well.
|
||||
def __record__(:fields) do
|
||||
[
|
||||
{:module,nil},
|
||||
{:file,nil},
|
||||
{:line,nil},
|
||||
{:function,nil},
|
||||
{:aliases,nil},
|
||||
{:context,nil},
|
||||
{:requires,nil},
|
||||
{:macros,nil}
|
||||
]
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns the current module name.
|
||||
"""
|
||||
def module(record), do: elem(record, 2)
|
||||
|
||||
@doc """
|
||||
Returns the current file name as a binary.
|
||||
"""
|
||||
def file(record), do: elem(record, 3)
|
||||
|
||||
@doc """
|
||||
Returns the current line as an integer.
|
||||
"""
|
||||
def line(record), do: elem(record, 4)
|
||||
|
||||
@doc """
|
||||
Returns a tuple as { Atom, Integer }, where the first element
|
||||
is the function name and the seconds its arity. Returns `nil`
|
||||
if not inside a function.
|
||||
"""
|
||||
def function(record), do: elem(record, 5)
|
||||
|
||||
@doc """
|
||||
Returns a list of two item tuples, where the first
|
||||
item is the aliased name and the second the actual name.
|
||||
"""
|
||||
def aliases(record), do: elem(record, 6)
|
||||
|
||||
@doc """
|
||||
Returns wether the compilation environment is currently
|
||||
inside a guard.
|
||||
"""
|
||||
def in_guard?(record), do: elem(record, 7) == :guard
|
||||
|
||||
@doc """
|
||||
Returns wether the compilation environment is currently
|
||||
inside a match clause.
|
||||
"""
|
||||
def in_match?(record), do: elem(record, 7) == :assign
|
||||
|
||||
@doc """
|
||||
Returns the list of required modules.
|
||||
"""
|
||||
def requires(record), do: elem(record, 8)
|
||||
|
||||
@doc """
|
||||
Returns a list of macros imported from each module.
|
||||
"""
|
||||
def macros(record), do: elem(record, 9)
|
||||
|
||||
@doc """
|
||||
Returns a keywords list containing the file and line
|
||||
information as keys.
|
||||
"""
|
||||
def location(record) do
|
||||
[file: file(record), line: line(record)]
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,563 @@
|
||||
defmodule Module do
|
||||
require Erlang.ets, as: ETS
|
||||
|
||||
@moduledoc """
|
||||
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
|
||||
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.
|
||||
"""
|
||||
|
||||
@doc """
|
||||
Evalutes the quotes contents in the given module context.
|
||||
Raises an error if the module was already compiled.
|
||||
|
||||
## Options
|
||||
|
||||
This function accepts a list of options. The supported
|
||||
options are:
|
||||
|
||||
* `:file` - The filename to be used in stacktraces
|
||||
and the file reported in the __ENV__ variable.
|
||||
|
||||
* `:line` - The line reported in the __ENV__ variable.
|
||||
|
||||
## Examples
|
||||
|
||||
defmodule Foo do
|
||||
contents = quote do: (def sum(a, b), do: a + b)
|
||||
Module.eval_quoted __MODULE__, contents, []
|
||||
end
|
||||
|
||||
Foo.sum(1, 2) #=> 3
|
||||
|
||||
This function also accepts a `Macro.Env` as first argument. This
|
||||
is useful to evalute the quoted contents inside an existing environment:
|
||||
|
||||
defmodule Foo do
|
||||
contents = quote do: (def sum(a, b), do: a + b)
|
||||
Module.eval_quoted __ENV__, contents, []
|
||||
end
|
||||
|
||||
Foo.sum(1, 2) #=> 3
|
||||
|
||||
"""
|
||||
def eval_quoted(env, quoted, binding // [], opts // [])
|
||||
|
||||
def eval_quoted(Macro.Env[module: module] = env, quoted, binding, opts) do
|
||||
eval_quoted(module, quoted, binding, Keyword.merge(env.location, opts))
|
||||
end
|
||||
|
||||
def eval_quoted(module, quoted, binding, opts) do
|
||||
assert_not_compiled!(:eval_quoted, module)
|
||||
|
||||
binding = Erlang.elixir_module.binding_for_eval(module, binding)
|
||||
scope = Erlang.elixir_module.scope_for_eval(module, opts)
|
||||
|
||||
Erlang.elixir_def.reset_last(module)
|
||||
|
||||
line = Keyword.get opts, :line, 1
|
||||
{ value, binding, _scope } = Erlang.elixir.eval_quoted([quoted], binding, line, scope)
|
||||
{ value, binding }
|
||||
end
|
||||
|
||||
@doc """
|
||||
Concatenates the list of aliases and returns a new alias.
|
||||
It handles char lists, binaries and atoms.
|
||||
|
||||
## Examples
|
||||
|
||||
Module.concat [Foo, Bar] #=> Foo.Bar
|
||||
Module.concat [Foo, "Bar"] #=> Foo.Bar
|
||||
Module.concat [Foo, 'Bar'] #=> Foo.Bar
|
||||
|
||||
"""
|
||||
def concat(list) when is_list(list) do
|
||||
Erlang.elixir_aliases.concat(list)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Concatenates the two given aliases and returns a new alias.
|
||||
It handles char lists, binaries and atoms.
|
||||
|
||||
## Examples
|
||||
|
||||
Module.concat Foo, Bar #=> Foo.Bar
|
||||
Module.concat Foo, "Bar" #=> Foo.Bar
|
||||
Module.concat Foo, 'Bar' #=> Foo.Bar
|
||||
|
||||
"""
|
||||
def concat(left, right) do
|
||||
Erlang.elixir_aliases.concat([left, right])
|
||||
end
|
||||
|
||||
@doc """
|
||||
Concatenates the list aliases and returns a new alias only
|
||||
if the alias was already referenced. If the alias was not
|
||||
referenced yet, fails with ArgumentError.
|
||||
It handles char lists, binaries and atoms.
|
||||
|
||||
## Examples
|
||||
|
||||
Module.safe_concat [Unknown, Module]
|
||||
#=> ArgumentError
|
||||
|
||||
Module.safe_concat [List, Chars]
|
||||
#=> List.Chars
|
||||
|
||||
"""
|
||||
def safe_concat(list) when is_list(list) do
|
||||
Erlang.elixir_aliases.safe_concat(list)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Concatenates the two aliases and returns a new alias only
|
||||
if the alias was already referenced. If the alias was not
|
||||
referenced yet, fails with ArgumentError.
|
||||
It handles char lists, binaries and atoms.
|
||||
|
||||
## Examples
|
||||
|
||||
Module.safe_concat Unknown, Module
|
||||
#=> ArgumentError
|
||||
|
||||
Module.safe_concat List, Chars
|
||||
#=> List.Chars
|
||||
|
||||
"""
|
||||
def safe_concat(left, right) do
|
||||
Erlang.elixir_aliases.safe_concat([left, right])
|
||||
end
|
||||
|
||||
@doc """
|
||||
Attaches documentation to a given function. It expects
|
||||
the module the function belongs to, the line (a non negative
|
||||
integer), the kind (def or defmacro), a tuple representing
|
||||
the function and its arity and the documentation, which should
|
||||
be either a binary or a boolean.
|
||||
|
||||
## Examples
|
||||
|
||||
defmodule MyModule do
|
||||
Module.add_doc(__MODULE__, __ENV__.line + 1, :def, { :version, 0 }, [], "Manually added docs")
|
||||
def version, do: 1
|
||||
end
|
||||
|
||||
"""
|
||||
def add_doc(_module, _line, kind, _tuple, _signature, doc) when kind in [:defp, :defmacrop] do
|
||||
if doc, do: { :error, :private_doc }, else: :ok
|
||||
end
|
||||
|
||||
def add_doc(module, line, kind, tuple, signature, doc) when
|
||||
is_binary(doc) or is_boolean(doc) or doc == nil do
|
||||
assert_not_compiled!(:add_doc, module)
|
||||
table = docs_table_for(module)
|
||||
|
||||
{ signature, _ } = Enum.map_reduce signature, 1, fn(x, acc) ->
|
||||
{ simplify_signature(x, line, acc), acc + 1 }
|
||||
end
|
||||
|
||||
case { ETS.lookup(table, tuple), doc } do
|
||||
{ [], _ } ->
|
||||
ETS.insert(table, { tuple, line, kind, signature, doc })
|
||||
:ok
|
||||
{ [{ tuple, line, kind, old, doc }], nil } ->
|
||||
ETS.insert(table, { tuple, line, kind, merge_signatures(old, signature, 1), doc })
|
||||
:ok
|
||||
_ ->
|
||||
{ :error, :existing_doc }
|
||||
end
|
||||
end
|
||||
|
||||
# Simplify signatures to be stored in docs
|
||||
|
||||
defp simplify_signature({ ://, defline, [left, right ] }, line, i) do
|
||||
{ ://, defline, [simplify_signature(left, line, i), right] }
|
||||
end
|
||||
|
||||
defp simplify_signature({ var, line, atom }, _, _i) when is_atom(atom) do
|
||||
case atom_to_list(var) do
|
||||
[?_|_] -> { var, line, :guess }
|
||||
_ -> { var, line, nil }
|
||||
end
|
||||
end
|
||||
|
||||
defp simplify_signature({ :=, _, [_, right] }, line, i) do
|
||||
simplify_signature(right, line, i)
|
||||
end
|
||||
|
||||
defp simplify_signature(other, line, i) when is_integer(other), do: { :"int#{i}", line, :guess }
|
||||
defp simplify_signature(other, line, i) when is_boolean(other), do: { :"bool#{i}", line, :guess }
|
||||
defp simplify_signature(other, line, i) when is_atom(other), do: { :"atom#{i}", line, :guess }
|
||||
defp simplify_signature(other, line, i) when is_list(other), do: { :"list#{i}", line, :guess }
|
||||
defp simplify_signature(other, line, i) when is_float(other), do: { :"float#{i}", line, :guess }
|
||||
defp simplify_signature(other, line, i) when is_binary(other), do: { :"binary#{i}", line, :guess }
|
||||
defp simplify_signature(_, line, i), do: { :"arg#{i}", line, :guess }
|
||||
|
||||
# Merge signatures
|
||||
|
||||
defp merge_signatures([h1|t1], [h2|t2], i) do
|
||||
[merge_signature(h1, h2, i)|merge_signatures(t1, t2, i + 1)]
|
||||
end
|
||||
|
||||
defp merge_signatures([], [], _) do
|
||||
[]
|
||||
end
|
||||
|
||||
defp merge_signature({ ://, line, [left, right] }, newer, i) do
|
||||
{ ://, line, [merge_signature(left, newer, i), right] }
|
||||
end
|
||||
|
||||
defp merge_signature(older, { ://, _, [left, _] }, i) do
|
||||
merge_signature(older, left, i)
|
||||
end
|
||||
|
||||
# The older signature, when given, always have higher precedence
|
||||
defp merge_signature({ _, _, nil } = older, _newer, _), do: older
|
||||
defp merge_signature(_older, { _, _, nil } = newer, _), do: newer
|
||||
|
||||
# Both are a guess, so check if they are the same guess
|
||||
defp merge_signature({ var, _, _ } = older, { var, _, _ }, _), do: older
|
||||
|
||||
# Otherwise, returns a generic guess
|
||||
defp merge_signature({ _, line, _ }, _newer, i), do: { :"arg#{i}", line, :guess }
|
||||
|
||||
@doc """
|
||||
Checks if the module defines the given function or macro.
|
||||
Use `defines?/3` to assert for an specific type.
|
||||
|
||||
## Examples
|
||||
|
||||
defmodule Example do
|
||||
Module.defines? __MODULE__, { :version, 0 } #=> false
|
||||
def version, do: 1
|
||||
Module.defines? __MODULE__, { :version, 0 } #=> true
|
||||
end
|
||||
|
||||
"""
|
||||
def defines?(module, tuple) when is_tuple(tuple) do
|
||||
assert_not_compiled!(:defines?, module)
|
||||
table = function_table_for(module)
|
||||
ETS.lookup(table, tuple) != []
|
||||
end
|
||||
|
||||
@doc """
|
||||
Checks if the module defines a function or macro with the
|
||||
given `kind`. `kind` can be either `:def`, `:defp`,
|
||||
`:defmacro` or `:defmacrop`.
|
||||
|
||||
## Examples
|
||||
|
||||
defmodule Example do
|
||||
Module.defines? __MODULE__, { :version, 0 }, :defp #=> false
|
||||
def version, do: 1
|
||||
Module.defines? __MODULE__, { :version, 0 }, :defp #=> false
|
||||
end
|
||||
|
||||
"""
|
||||
def defines?(module, tuple, kind) do
|
||||
assert_not_compiled!(:defines?, module)
|
||||
table = function_table_for(module)
|
||||
case ETS.lookup(table, tuple) do
|
||||
[{ _, ^kind, _, _, _, _, _, _ }] -> true
|
||||
_ -> false
|
||||
end
|
||||
end
|
||||
|
||||
@doc false
|
||||
def function_defined?(module, tuple) do
|
||||
IO.puts "Module.function_defined? is deprecated in favor of Module.defines?"
|
||||
defines?(module, tuple)
|
||||
end
|
||||
|
||||
@doc false
|
||||
def function_defined?(module, tuple, kind) do
|
||||
IO.puts "Module.function_defined? is deprecated in favor of Module.defines?"
|
||||
defines?(module, tuple, kind)
|
||||
end
|
||||
|
||||
@doc false
|
||||
def defined_functions(module) do
|
||||
IO.puts "Module.defined_functions is deprecated in favor of Module.definitions_in"
|
||||
definitions_in(module)
|
||||
end
|
||||
|
||||
@doc false
|
||||
def defined_functions(module, kind) do
|
||||
IO.puts "Module.defined_functions is deprecated in favor of Module.definitions_in"
|
||||
definitions_in(module, kind)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Return all functions defined in the given module.
|
||||
|
||||
## Examples
|
||||
|
||||
defmodule Example do
|
||||
def version, do: 1
|
||||
Module.definitions_in __MODULE__ #=> [{:version,1}]
|
||||
end
|
||||
|
||||
"""
|
||||
def definitions_in(module) do
|
||||
assert_not_compiled!(:definitions_in, module)
|
||||
table = function_table_for(module)
|
||||
lc { tuple, _, _, _, _, _, _, _ } inlist ETS.tab2list(table), do: tuple
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns all functions defined in te given module according
|
||||
to its kind.
|
||||
|
||||
## Examples
|
||||
|
||||
defmodule Example do
|
||||
def version, do: 1
|
||||
Module.definitions_in __MODULE__, :def #=> [{:version,1}]
|
||||
Module.definitions_in __MODULE__, :defp #=> []
|
||||
end
|
||||
|
||||
"""
|
||||
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
|
||||
end
|
||||
|
||||
@doc """
|
||||
Makes the given functions in the given module overridable.
|
||||
An overridable function is lazily defined, allowing a
|
||||
developer to customize it.
|
||||
"""
|
||||
def make_overridable(module, tuples) do
|
||||
assert_not_compiled!(:make_overridable, module)
|
||||
table = function_table_for(module)
|
||||
lc tuple inlist tuples do
|
||||
case ETS.lookup(table, tuple) do
|
||||
[clause] ->
|
||||
ETS.delete(table, tuple)
|
||||
|
||||
old = Module.read_attribute(module, :__overridable)
|
||||
new = [ { tuple, { 1, [clause] } } ]
|
||||
merged = :orddict.merge(fn(_k, { count, v1 }, _v2) -> { count + 1, [clause|v1] } end, old, new)
|
||||
|
||||
Module.add_attribute(module, :__overridable, merged)
|
||||
_ ->
|
||||
{ name, arity } = tuple
|
||||
raise "Cannot make function #{name}/#{arity} overridable because it was not defined"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def add_compile_callback(module, target, fun // :__compiling__) do
|
||||
IO.puts "Module.add_compile_callback(module, target, fun) is deprecated in favor of " <>
|
||||
"Module.add_attribute(module, :before_compile, { target, fun })"
|
||||
assert_not_compiled!(:add_compile_callback, module)
|
||||
add_attribute(module, :before_compile, { target, fun })
|
||||
end
|
||||
|
||||
@doc """
|
||||
Adds an Erlang attribute to the given module with the given
|
||||
key and value. The semantics of adding the attribute depends
|
||||
if the attribute was registered or not via `register_attribute/2`.
|
||||
|
||||
## Examples
|
||||
|
||||
defmodule MyModule do
|
||||
Module.add_attribute __MODULE__, :custom_threshold_for_lib, 10
|
||||
end
|
||||
|
||||
"""
|
||||
def add_attribute(module, key, value) when is_atom(key) do
|
||||
assert_not_compiled!(:add_attribute, module)
|
||||
table = data_table_for(module)
|
||||
value = normalize_attribute(key, value)
|
||||
acc = ETS.lookup_element(table, :__acc_attributes, 2)
|
||||
|
||||
new =
|
||||
if List.member?(acc, key) do
|
||||
case ETS.lookup(table, key) do
|
||||
[{^key,old}] -> [value|old]
|
||||
[] -> [value]
|
||||
end
|
||||
else
|
||||
value
|
||||
end
|
||||
|
||||
ETS.insert(table, { key, new })
|
||||
end
|
||||
|
||||
@doc """
|
||||
Reads the given attribute from a module. If the attribute
|
||||
was marked as accumulate with `Module.register_attribute`,
|
||||
a list is always returned.
|
||||
|
||||
## Examples
|
||||
|
||||
defmodule Foo do
|
||||
Module.add_attribute __MODULE__, :value, 1
|
||||
Module.read_attribute __MODULE__, :value #=> 1
|
||||
|
||||
Module.register_attribute __MODULE__, :value, accumulate: true
|
||||
Module.add_attribute __MODULE__, :value, 1
|
||||
Module.read_attribute __MODULE__, :value #=> [1]
|
||||
end
|
||||
|
||||
"""
|
||||
def read_attribute(module, key) when is_atom(key) do
|
||||
assert_not_compiled!(:read_attribute, module)
|
||||
table = data_table_for(module)
|
||||
|
||||
case ETS.lookup(table, key) do
|
||||
[{^key,old}] -> old
|
||||
[] ->
|
||||
acc = ETS.lookup_element(table, :__acc_attributes, 2)
|
||||
if List.member?(acc, key), do: [], else: nil
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Deletes all attributes that matches the given key.
|
||||
|
||||
## Examples
|
||||
|
||||
defmodule MyModule do
|
||||
Module.add_attribute __MODULE__, :custom_threshold_for_lib, 10
|
||||
Module.delete_attribute __MODULE__, :custom_threshold_for_lib
|
||||
end
|
||||
|
||||
"""
|
||||
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)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Registers an attribute. By registering an attribute, a developer
|
||||
is able to customize how Elixir will store and accumulate the
|
||||
attribute values.
|
||||
|
||||
## Options
|
||||
|
||||
When registering an attribute, two options can be given:
|
||||
|
||||
* `:accumulate` - Several calls to the same attribute will
|
||||
accumulate instead of override the previous one;
|
||||
|
||||
* `:persist` - The attribute will be persisted in the Erlang
|
||||
Abstract Format. Useful when interfacing with Erlang libraries.
|
||||
|
||||
By default, both options are true. Which means that registering
|
||||
an attribute without passing any options will revert the attribute
|
||||
behavior to exactly the same expected in Erlang.
|
||||
|
||||
## Examples
|
||||
|
||||
defmodule MyModule do
|
||||
Module.register_attribute __MODULE__,
|
||||
:custom_threshold_for_lib,
|
||||
accumulate: true, persist: false
|
||||
|
||||
@custom_threshold_for_lib 10
|
||||
@custom_threshold_for_lib 20
|
||||
@custom_threshold_for_lib #=> [20, 10]
|
||||
end
|
||||
|
||||
"""
|
||||
def register_attribute(module, new, opts // []) do
|
||||
assert_not_compiled!(:register_attribute, module)
|
||||
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] })
|
||||
end
|
||||
|
||||
if Keyword.get(opts, :accumulate, true) do
|
||||
old = ETS.lookup_element(table, :__acc_attributes, 2)
|
||||
ETS.insert(table, { :__acc_attributes, [new|old] })
|
||||
end
|
||||
end
|
||||
|
||||
@doc false
|
||||
# Used internally to compile documentation. This function
|
||||
# is private and must be used only internally.
|
||||
def compile_doc(module, line, kind, pair, signature) do
|
||||
doc = read_attribute(module, :doc)
|
||||
result = add_doc(module, line, kind, pair, signature, doc)
|
||||
delete_attribute(module, :doc)
|
||||
result
|
||||
end
|
||||
|
||||
@doc false
|
||||
# Used internally to compile types. This function
|
||||
# is private and must be used only internally.
|
||||
def compile_type(module, key, value) when is_atom(key) do
|
||||
assert_not_compiled!(:add_attribute, module)
|
||||
table = data_table_for(module)
|
||||
|
||||
new =
|
||||
case ETS.lookup(table, key) do
|
||||
[{^key,old}] -> [value|old]
|
||||
[] -> [value]
|
||||
end
|
||||
|
||||
ETS.insert(table, { key, new })
|
||||
end
|
||||
|
||||
## Helpers
|
||||
|
||||
defp normalize_attribute(:on_load, atom) when is_atom(atom) do
|
||||
{ atom, 0 }
|
||||
end
|
||||
|
||||
defp normalize_attribute(kind, atom) when kind in [:behavior, :behaviour] and is_atom(atom) do
|
||||
Code.ensure_compiled(atom)
|
||||
atom
|
||||
end
|
||||
|
||||
defp normalize_attribute(:file, Macro.Env[file: file, line: line]), do: { binary_to_list(file), line}
|
||||
defp normalize_attribute(:file, { binary, line }) when is_binary(binary), do: { binary_to_list(binary), line }
|
||||
defp normalize_attribute(:file, other) when not is_tuple(other), do: normalize_attribute(:file, { other, 1 })
|
||||
|
||||
defp normalize_attribute(key, atom) when key in [:before_compile, :after_compile] and is_atom(atom) do
|
||||
{ atom, key }
|
||||
end
|
||||
|
||||
defp normalize_attribute(key, _value) when key in [:type, :typep, :export_type, :opaque, :callback] do
|
||||
raise ArgumentError, message: "Attributes type, typep, export_type, opaque and callback " <>
|
||||
"must be set via Kernel.Typespec"
|
||||
end
|
||||
|
||||
defp normalize_attribute(_key, value) do
|
||||
value
|
||||
end
|
||||
|
||||
defp compiled?(module) do
|
||||
table = data_table_for(module)
|
||||
table == ETS.info(table, :name)
|
||||
end
|
||||
|
||||
defp data_table_for(module) do
|
||||
list_to_atom Erlang.lists.concat([:d, module])
|
||||
end
|
||||
|
||||
defp function_table_for(module) do
|
||||
list_to_atom Erlang.lists.concat([:f, module])
|
||||
end
|
||||
|
||||
defp docs_table_for(module) do
|
||||
list_to_atom Erlang.lists.concat([:o, module])
|
||||
end
|
||||
|
||||
defp assert_not_compiled!(fun, module) do
|
||||
compiled?(module) ||
|
||||
raise ArgumentError,
|
||||
message: "could not call #{fun} on module #{inspect module} because it was already compiled"
|
||||
end
|
||||
end
|
||||
@@ -3,6 +3,13 @@ defmodule Node do
|
||||
Functions related to Erlang nodes.
|
||||
"""
|
||||
|
||||
@doc """
|
||||
Returns the current node. It returns the same as the built-in node().
|
||||
"""
|
||||
def self do
|
||||
:erlang.node()
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns true if the local node is alive; that is, if the node can be
|
||||
part of a distributed system. Otherwise, it returns false.
|
||||
@@ -61,4 +68,49 @@ defmodule Node do
|
||||
def disconnect(node) do
|
||||
:erlang.disconnect_node(node)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns the pid of a new process started by the application of `fun`
|
||||
on `node`. If `node` does not exist, a useless pid is returned.
|
||||
|
||||
Check http://www.erlang.org/doc/man/erlang.html#spawn_opt-4 for
|
||||
the list of available options.
|
||||
"""
|
||||
def spawn(node, fun, opts // []) do
|
||||
:erlang.spawn_opt(node, fun, opts)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns the pid of a new process started by the application of
|
||||
`module.function(args)` on `node`. If `node` does not exists, a useless
|
||||
pid is returned.
|
||||
|
||||
Check http://www.erlang.org/doc/man/erlang.html#spawn_opt-4 for
|
||||
the list of available options.
|
||||
"""
|
||||
def spawn(node, module, fun, args, opts // []) do
|
||||
:erlang.spawn(node, module, fun, args, opts)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns the pid of a new process started by the application of `fun`
|
||||
on `node`. A link is created between the calling process and the
|
||||
new process, atomically. If `node` does not exist, a useless pid is returned
|
||||
(and due to the link, an exit signal with exit reason :noconnection will be
|
||||
received).
|
||||
"""
|
||||
def spawn_link(node, fun) do
|
||||
:erlang.spawn_link(node, fun)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns the pid of a new process started by the application of
|
||||
`module.function(args)` on `node`. A link is created between the calling
|
||||
process and the new process, atomically. If `node` does not exist, a useless
|
||||
pid is returned (and due to the link, an exit signal with exit reason
|
||||
:noconnection will be received).
|
||||
"""
|
||||
def spawn_link(node, module, fun, args) do
|
||||
:erlang.spawn_link(node, module, fun, args)
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,140 @@
|
||||
defmodule OptionParser do
|
||||
@doc """
|
||||
Parses the argv and returns one tuple with parsed options
|
||||
and the arguments.
|
||||
|
||||
## Examples
|
||||
|
||||
OptionParser.parse(["--debug"])
|
||||
#=> { [debug: true], [] }
|
||||
|
||||
OptionParser.parse(["--source", "lib"])
|
||||
#=> { [source: "lib"], [] }
|
||||
|
||||
OptionParser.parse(["--source", "lib", "test/enum_test.exs", "--verbose"])
|
||||
#=> { [source: "lib", verbose: true], ["test/enum_test.exs"] }
|
||||
|
||||
## Aliases
|
||||
|
||||
A set of aliases can be given as second argument:
|
||||
|
||||
OptionParser.parse(["-d"], aliases: [d: :debug])
|
||||
#=> { [debug: true], [] }
|
||||
|
||||
## Flags
|
||||
|
||||
A set of flags can be given as argument too. Those are considered
|
||||
boolean and never consume the next value unless it is a boolean:
|
||||
|
||||
OptionParser.parse(["--unlock path/to/file"], flags: [:unlock])
|
||||
#=> { [unlock: true], ["path/to/file"] }
|
||||
|
||||
OptionParser.parse(["--unlock false path/to/file"], flags: [:unlock])
|
||||
#=> { [unlock: false], ["path/to/file"] }
|
||||
|
||||
## Negation switches
|
||||
|
||||
Any switches starting with `--no-` are always considered to be
|
||||
booleans and never parse the next value.
|
||||
|
||||
OptionParser.parse(["--no-op path/to/file"])
|
||||
#=> { [no_op: true], ["path/to/file"] }
|
||||
|
||||
"""
|
||||
def parse(argv, opts // []) when is_list(argv) and is_list(opts) do
|
||||
aliases = opts[:aliases] || []
|
||||
flags = opts[:flags] || []
|
||||
dict = Keyword.new(flags, fn(k) -> { k, false } end)
|
||||
parse(argv, aliases, flags, dict, [], true)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Similar to parse but only parses the head of the argv.
|
||||
I.e. as soon as it finds a non switch, it stops parsing.
|
||||
|
||||
Check `parse/2` for more info.
|
||||
|
||||
## Example
|
||||
|
||||
OptionParser.parse_head(["--source", "lib", "test/enum_test.exs", "--verbose"])
|
||||
#=> { [source: "lib"], ["test/enum_test.exs", "--verbose"] }
|
||||
|
||||
"""
|
||||
def parse_head(argv, opts // []) when is_list(argv) and is_list(opts) do
|
||||
aliases = opts[:aliases] || []
|
||||
flags = opts[:flags] || []
|
||||
dict = Keyword.new(flags, fn(k) -> { k, false } end)
|
||||
parse(argv, aliases, flags, dict, [], false)
|
||||
end
|
||||
|
||||
## Helpers
|
||||
|
||||
defp parse(["-" <> option|t], aliases, flags, dict, args, all) do
|
||||
{ option, value } = normalize_option(option, aliases)
|
||||
|
||||
if value == nil do
|
||||
{ value, t } = if is_flag?(flags, option) do
|
||||
flag_from_tail(t)
|
||||
else
|
||||
value_from_tail(t)
|
||||
end
|
||||
end
|
||||
|
||||
dict = store_option dict, option, value
|
||||
parse(t, aliases, flags, dict, args, all)
|
||||
end
|
||||
|
||||
defp parse([], _, _, dict, args, true) do
|
||||
{ dict, List.reverse(args) }
|
||||
end
|
||||
|
||||
defp parse([h|t], aliases, flags, dict, args, true) do
|
||||
parse(t, aliases, flags, dict, [h|args], true)
|
||||
end
|
||||
|
||||
defp parse(value, _, _, dict, _args, false) do
|
||||
{ dict, value }
|
||||
end
|
||||
|
||||
defp flag_from_tail([h|t]) when h in ["false", "true"], do: { h, t }
|
||||
defp flag_from_tail(t) , do: { true, t }
|
||||
|
||||
defp value_from_tail(["-" <> _|_] = t), do: { true, t }
|
||||
defp value_from_tail([h|t]), do: { h, t }
|
||||
defp value_from_tail([]), do: { true, [] }
|
||||
|
||||
defp store_option(dict, option, value) when value in ["false", "true"] do
|
||||
store_option(dict, option, binary_to_atom(value))
|
||||
end
|
||||
|
||||
defp store_option(dict, option, value) do
|
||||
Keyword.put dict, option, value
|
||||
end
|
||||
|
||||
defp normalize_option(<<?-, option|:binary>>, aliases) do
|
||||
normalize_option(option, aliases)
|
||||
end
|
||||
|
||||
defp normalize_option(option, aliases) do
|
||||
{ option, value } = split_option(option)
|
||||
if is_no?(option), do: value = true
|
||||
atom = option /> to_underscore /> binary_to_atom
|
||||
{ aliases[atom] || atom, value }
|
||||
end
|
||||
|
||||
defp split_option(option) do
|
||||
case :binary.split(option, "=") do
|
||||
[h] -> { h, nil }
|
||||
[h|t] -> { h, Enum.join(t, "=") }
|
||||
end
|
||||
end
|
||||
|
||||
defp to_underscore(option) do
|
||||
bc <<c>> inbits option, do: << if c == ?-, do: ?_, else: c >>
|
||||
end
|
||||
|
||||
defp is_no?("no-" <> _), do: true
|
||||
defp is_no?(_), do: false
|
||||
|
||||
defp is_flag?(flags, option), do: List.member?(flags, option)
|
||||
end
|
||||
@@ -0,0 +1,84 @@
|
||||
defimpl Dict, for: Orddict do
|
||||
defmacrop dict(data) do
|
||||
quote do
|
||||
{ Orddict, unquote(data) }
|
||||
end
|
||||
end
|
||||
|
||||
def keys(dict(data)) do
|
||||
lc { k, _ } inlist data, do: k
|
||||
end
|
||||
|
||||
def values(dict(data)) do
|
||||
lc { _, v } inlist data, do: v
|
||||
end
|
||||
|
||||
def size(dict(data)) do
|
||||
length(data)
|
||||
end
|
||||
|
||||
def has_key?(dict(data), key) do
|
||||
:orddict.is_key key, data
|
||||
end
|
||||
|
||||
def get(dict(data), key, default // nil) do
|
||||
case :orddict.find(key, data) do
|
||||
{:ok, value} ->
|
||||
value
|
||||
:error ->
|
||||
default
|
||||
end
|
||||
end
|
||||
|
||||
def put(dict(data), key, value) do
|
||||
dict(:orddict.store key, value, data)
|
||||
end
|
||||
|
||||
def delete(dict(data), key) do
|
||||
dict(:orddict.erase key, data)
|
||||
end
|
||||
|
||||
def merge(dict(d1), dict(d2)) do
|
||||
dict(:orddict.merge fn _k, _v1, v2 -> v2 end, d1, d2)
|
||||
end
|
||||
|
||||
def merge(dict(d1), dict(d2), fun) do
|
||||
dict(:orddict.merge fun, d1, d2)
|
||||
end
|
||||
|
||||
def update(dict(data), key, fun) do
|
||||
dict(:orddict.update key, fun, data)
|
||||
end
|
||||
|
||||
def update(dict(data), key, initial, fun) do
|
||||
dict(:orddict.update key, fun, initial, data)
|
||||
end
|
||||
|
||||
def empty(_) do
|
||||
dict([])
|
||||
end
|
||||
|
||||
def to_list(dict(data)) do
|
||||
data
|
||||
end
|
||||
end
|
||||
|
||||
defimpl Enum.Iterator, for: Orddict do
|
||||
def iterator({ Orddict, data }), do: data
|
||||
def count({ Orddict, data }), do: length(data)
|
||||
end
|
||||
|
||||
defimpl Enum.OrdIterator, for: Orddict do
|
||||
def iterator({ Orddict, data }), do: data
|
||||
def to_list({ h, next }, _), do: [h|next]
|
||||
end
|
||||
|
||||
defmodule Orddict do
|
||||
@moduledoc """
|
||||
This module implements a dictionary based that stores items
|
||||
as a list of tuples. It is a simple wrapper around
|
||||
[Erlang's orddict module](http://www.erlang.org/doc/man/orddict.html)
|
||||
and exposed via the `Dict` protocol.
|
||||
"""
|
||||
use Dict.Common, Dict.Orddict
|
||||
end
|
||||
@@ -49,7 +49,9 @@ defmodule Process do
|
||||
@doc """
|
||||
Returns all keys that have the given `value`.
|
||||
"""
|
||||
defdelegate [get_keys: 1], to: :erlang
|
||||
def get_keys(value) do
|
||||
:erlang.get_keys(value)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Stores the given key-value in the process dictionary.
|
||||
@@ -92,113 +94,72 @@ defmodule Process do
|
||||
|
||||
## Examples
|
||||
|
||||
Process.exit(other, :kil)
|
||||
Process.exit(pid, :kill)
|
||||
|
||||
"""
|
||||
defdelegate [exit: 2], to: :erlang
|
||||
def exit(pid, status) do
|
||||
:erlang.exit(pid, status)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns the pid of a new process started by the application of `fun`.
|
||||
Otherwise works like spawn/3.
|
||||
"""
|
||||
defdelegate [spawn: 1], to: :erlang
|
||||
|
||||
@doc """
|
||||
Returns the pid of a new process started by the application of `fun`
|
||||
on `node`. If `node` does not exist, a useless pid is returned.
|
||||
Otherwise works like spawn/3.
|
||||
It behaves exactly the same as the `Kernel.spawn/1` function except
|
||||
it also accepts extra options, for the list of available options
|
||||
check http://www.erlang.org/doc/man/erlang.html#spawn_opt-4
|
||||
"""
|
||||
defdelegate [spawn: 2], to: :erlang
|
||||
def spawn(fun, opts // []) do
|
||||
:erlang.spawn_opt(fun, opts)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns the pid of a new process started by the application of
|
||||
`module.function(args)`. The new process created will be placed in the system
|
||||
scheduler queue and be run some time later.
|
||||
|
||||
See http://www.erlang.org/doc/man/erlang.html#spawn-3 for more info.
|
||||
"""
|
||||
defdelegate [spawn: 3], to: :erlang
|
||||
It behaves exactly the same as the `Kernel.spawn/3` function except
|
||||
it also accepts extra options, for the list of available options
|
||||
check http://www.erlang.org/doc/man/erlang.html#spawn_opt-4
|
||||
|
||||
@doc """
|
||||
Returns the pid of a new process started by the application of
|
||||
`module.function(args)` on `node`. If `node` does not exists, a useless
|
||||
pid is returned. Otherwise works like spawn/3.
|
||||
"""
|
||||
defdelegate [spawn: 4], to: :erlang
|
||||
def spawn(mod, fun, args, opts // []) do
|
||||
:erlang.spawn_opt(mod, fun, args, opts)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns the pid of a new process started by the application of `fun`.
|
||||
A link is created between the calling process and the new
|
||||
process, atomically. Otherwise works like spawn/3.
|
||||
process, atomically.
|
||||
"""
|
||||
defdelegate [spawn_link: 1], to: :erlang
|
||||
|
||||
@doc """
|
||||
Returns the pid of a new process started by the application of `fun`
|
||||
on `node`. A link is created between the calling process and the
|
||||
new process, atomically. If `node` does not exist, a useless pid is returned
|
||||
(and due to the link, an exit signal with exit reason :noconnection will be
|
||||
received). Otherwise works like spawn/3.
|
||||
"""
|
||||
defdelegate [spawn_link: 2], to: :erlang
|
||||
def spawn_link(fun) do
|
||||
:erlang.spawn_link(fun)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns the pid of a new process started by the application of
|
||||
`module.function(args)`. A link is created between the calling process
|
||||
and the new process, atomically. Otherwise works like spawn/3.
|
||||
"""
|
||||
defdelegate [spawn_link: 3], to: :erlang
|
||||
|
||||
@doc """
|
||||
Returns the pid of a new process started by the application of
|
||||
`module.function(args)` on `node`. A link is created between the calling
|
||||
process and the new process, atomically. If `node` does not exist, a useless
|
||||
pid is returned (and due to the link, an exit signal with exit reason
|
||||
:noconnection will be received). Otherwise works like spawn/3.
|
||||
"""
|
||||
defdelegate [spawn_link: 4], to: :erlang
|
||||
def spawn_link(mod, fun, args) do
|
||||
:erlang.spawn_link(mod, fun, args)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns the pid of a new process started by the application of `fun`
|
||||
and reference for a monitor created to the new process.
|
||||
Otherwise works like spawn/3.
|
||||
"""
|
||||
defdelegate [spawn_monitor: 1], to: :erlang
|
||||
def spawn_monitor(fun) do
|
||||
:erlang.spawn_monitor(fun)
|
||||
end
|
||||
|
||||
@doc """
|
||||
A new process is started by the application of `module.function(args)`
|
||||
and the process is monitored at the same time. Returns the pid and a
|
||||
reference for the monitor. Otherwise works like spawn/3.
|
||||
"""
|
||||
defdelegate [spawn_monitor: 3], to: :erlang
|
||||
|
||||
@doc """
|
||||
The same as `spawn/1` but accepts extra options as arguments.
|
||||
Please read http://www.erlang.org/doc/man/erlang.html#spawn_opt-4 for
|
||||
documentation of the options.
|
||||
"""
|
||||
defdelegate [spawn_opt: 2], to: :erlang
|
||||
|
||||
@doc """
|
||||
The same as `spawn/2` but accepts extra options as arguments.
|
||||
Please read http://www.erlang.org/doc/man/erlang.html#spawn_opt-4 for
|
||||
documentation of the options.
|
||||
"""
|
||||
defdelegate [spawn_opt: 3], to: :erlang
|
||||
|
||||
@doc """
|
||||
The same as `spawn/3` but accepts extra options as arguments.
|
||||
Please read http://www.erlang.org/doc/man/erlang.html#spawn_opt-4 for
|
||||
documentation of the options.
|
||||
"""
|
||||
defdelegate [spawn_opt: 4], to: :erlang
|
||||
|
||||
@doc """
|
||||
The same as `spawn/4` but accepts extra options as arguments.
|
||||
Please read http://www.erlang.org/doc/man/erlang.html#spawn_opt-4 for
|
||||
documentation of the options.
|
||||
"""
|
||||
defdelegate [spawn_opt: 5], to: :erlang
|
||||
def spawn_monitor(mod, fun, args) do
|
||||
:erlang.spawn_monitor(mod, fun, args)
|
||||
end
|
||||
|
||||
@doc """
|
||||
The calling process starts monitoring the item given.
|
||||
@@ -241,7 +202,9 @@ defmodule Process do
|
||||
|
||||
See http://www.erlang.org/doc/man/erlang.html#link-1 for more info.
|
||||
"""
|
||||
defdelegate [link: 1], to: :erlang
|
||||
def link(pid) do
|
||||
:erlang.link(pid)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Removes the link, if there is one, between the calling process and
|
||||
@@ -250,7 +213,9 @@ defmodule Process do
|
||||
|
||||
See http://www.erlang.org/doc/man/erlang.html#unlink-1 for more info.
|
||||
"""
|
||||
defdelegate [unlink: 1], to: :erlang
|
||||
def unlink(pid) do
|
||||
:erlang.unlink(pid)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Associates the name with a pid or a port identifier. name, which must
|
||||
@@ -259,14 +224,18 @@ defmodule Process do
|
||||
|
||||
See http://www.erlang.org/doc/man/erlang.html#register-2 for more info.
|
||||
"""
|
||||
defdelegate [register: 2], to: :erlang
|
||||
def register(name, pid) do
|
||||
:erlang.register(name, pid)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Removes the registered name, associated with a pid or a port identifier.
|
||||
|
||||
See http://www.erlang.org/doc/man/erlang.html#unregister-1 for more info.
|
||||
"""
|
||||
defdelegate [unregister: 1], to: :erlang
|
||||
def unregister(name) do
|
||||
:erlang.unregister(name)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns the pid or port identifier with the registered name.
|
||||
@@ -274,7 +243,9 @@ defmodule Process do
|
||||
|
||||
See http://www.erlang.org/doc/man/erlang.html#whereis-1 for more info.
|
||||
"""
|
||||
defdelegate [whereis: 1], to: :erlang
|
||||
def whereis(name) do
|
||||
:erlang.whereis(name)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns a list of names which have been registered using register/2.
|
||||
@@ -23,7 +23,7 @@ defmodule Protocol do
|
||||
defmodule unquote(name) do
|
||||
# Remove "harmful" macros
|
||||
# We don't want to allow function definition inside protocols
|
||||
import Elixir.Builtin, except: [
|
||||
import Kernel, except: [
|
||||
defmacro: 1, defmacro: 2, defmacro: 4,
|
||||
defp: 1, defp: 2, defp: 4,
|
||||
def: 1, def: 2, def: 4
|
||||
@@ -40,8 +40,8 @@ defmodule Protocol do
|
||||
|
||||
# Define callbacks and meta information
|
||||
{ conversions, fallback } = Protocol.conversions_for(__MODULE__, @only, @except)
|
||||
Protocol.impl_for(__MODULE__, conversions)
|
||||
Protocol.meta(__MODULE__, @functions, fallback)
|
||||
Protocol.impl_for(__ENV__, conversions)
|
||||
Protocol.meta(__ENV__, @functions, fallback)
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -73,11 +73,9 @@ defmodule Protocol do
|
||||
if not loaded or not a protocol.
|
||||
"""
|
||||
def assert_protocol(module) do
|
||||
try do
|
||||
module.__info__(:data)
|
||||
rescue
|
||||
UndefinedFunctionError ->
|
||||
raise ArgumentError, message: "#{module} is not loaded"
|
||||
case :code.ensure_loaded(module) do
|
||||
{ :module, ^module } -> nil
|
||||
_ -> raise ArgumentError, message: "#{module} is not loaded"
|
||||
end
|
||||
|
||||
try do
|
||||
@@ -106,7 +104,7 @@ defmodule Protocol do
|
||||
@doc """
|
||||
Defines meta information about the protocol and internal callbacks.
|
||||
"""
|
||||
def meta(module, functions, fallback) do
|
||||
def meta(env, functions, fallback) do
|
||||
contents = quote do
|
||||
def __protocol__(:name), do: __MODULE__
|
||||
def __protocol__(:functions), do: unquote(:lists.sort(functions))
|
||||
@@ -137,7 +135,7 @@ defmodule Protocol do
|
||||
defp __fallback__, do: unquote(fallback)
|
||||
end
|
||||
|
||||
Module.eval_quoted module, contents, [], file: __FILE__, line: __LINE__
|
||||
Module.eval_quoted env, contents
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -145,8 +143,8 @@ defmodule Protocol do
|
||||
the module to dispatch to. Returns module.Record for records
|
||||
which should be properly handled by the dispatching function.
|
||||
"""
|
||||
def impl_for(module, conversions) do
|
||||
contents = lc kind in conversions, do: each_impl_for(kind, conversions)
|
||||
def impl_for(env, conversions) do
|
||||
contents = lc kind inlist conversions, do: each_impl_for(kind, conversions)
|
||||
|
||||
# If we don't implement all protocols and any is not in the
|
||||
# list, we need to add a final clause that returns nil.
|
||||
@@ -158,7 +156,7 @@ defmodule Protocol do
|
||||
end]
|
||||
end
|
||||
|
||||
Module.eval_quoted module, contents, [], file: __FILE__, line: __LINE__
|
||||
Module.eval_quoted env, contents
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -230,7 +228,7 @@ defmodule Protocol do
|
||||
true -> __MODULE__.Tuple
|
||||
false ->
|
||||
case atom_to_list(first) do
|
||||
'__MAIN__' ++ _ -> __MODULE__.Record
|
||||
'Elixir-' ++ _ -> __MODULE__.Record
|
||||
_ -> __MODULE__.Tuple
|
||||
end
|
||||
end
|
||||
@@ -275,7 +273,7 @@ defmodule Protocol.DSL do
|
||||
# are named xa, xb and so forth. We cannot use string
|
||||
# interpolation to generate the arguments because of compile
|
||||
# dependencies, so we use the <<>> instead.
|
||||
args = lc i in :lists.seq(1, arity) do
|
||||
args = lc i inlist :lists.seq(1, arity) do
|
||||
{ binary_to_atom(<<?x, i + 64>>), 0, :quoted }
|
||||
end
|
||||
|
||||
@@ -283,7 +281,7 @@ defmodule Protocol.DSL do
|
||||
# Append new function to the list
|
||||
@functions [unquote({name, arity})|@functions]
|
||||
|
||||
Elixir.Builtin.def unquote(name).(unquote_splicing(args)) do
|
||||
Kernel.def unquote(name).(unquote_splicing(args)) do
|
||||
args = [unquote_splicing(args)]
|
||||
case __raw_impl__(xA) do
|
||||
__MODULE__.Record ->
|
||||
@@ -0,0 +1,84 @@
|
||||
defmodule Range do
|
||||
def __access__(caller, arg), do: Record.access(caller, __MODULE__, __record__(:fields), arg)
|
||||
def __record__(kind, _), do: __record__(kind)
|
||||
def __record__(:name), do: __MODULE__
|
||||
def __record__(:fields), do: [{:first,nil},{:last,nil}]
|
||||
|
||||
@doc """
|
||||
Returns a new range based on the given options.
|
||||
|
||||
## Examples
|
||||
|
||||
Range.new first: 1, last: 10
|
||||
|
||||
"""
|
||||
def new(options) do
|
||||
{
|
||||
__MODULE__,
|
||||
Keyword.get!(options, :first),
|
||||
Keyword.get!(options, :last)
|
||||
}
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns the first item of the range.
|
||||
"""
|
||||
def first(range) do
|
||||
elem(range, 2)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns the last item of the range.
|
||||
"""
|
||||
def last(range) do
|
||||
elem(range, 3)
|
||||
end
|
||||
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)
|
||||
|
||||
@doc """
|
||||
Count how many items are in the range.
|
||||
"""
|
||||
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) }
|
||||
end
|
||||
|
||||
def count(Range[first: first] = range) do
|
||||
Range.Iterator.count(first, range)
|
||||
end
|
||||
end
|
||||
|
||||
defimpl Enum.OrdIterator, for: Range do
|
||||
def iterator(range) do
|
||||
Enum.Iterator.Range.iterator(range)
|
||||
end
|
||||
|
||||
def to_list({ h, next }, iterator), do: [h|to_list(iterator.(next), iterator)]
|
||||
def to_list(:stop, _), do: []
|
||||
end
|
||||
|
||||
defimpl Range.Iterator, for: Number do
|
||||
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
|
||||
end
|
||||
|
||||
def count(first, Range[last: last]) when is_integer(first) and is_integer(last) do
|
||||
last - first + 1
|
||||
end
|
||||
end
|
||||
@@ -16,9 +16,56 @@ defmodule Record do
|
||||
Record.Extractor.retrieve(name, opts)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Implements the access macro used by records.
|
||||
It returns a quoted expression that represents
|
||||
the access given by the keywords.
|
||||
"""
|
||||
def access(caller, atom, fields, keyword) do
|
||||
unless is_orddict(keyword) do
|
||||
raise "expected contents inside brackets to be a Keyword"
|
||||
end
|
||||
|
||||
in_match = caller.in_match?
|
||||
|
||||
has_underscore_value = Keyword.key?(keyword, :_)
|
||||
underscore_value = Keyword.get(keyword, :_, { :_, 0, nil })
|
||||
keyword = Keyword.delete keyword, :_
|
||||
|
||||
iterator = fn({field, default}, each_keyword) ->
|
||||
new_fields =
|
||||
case Keyword.key?(each_keyword, field) do
|
||||
true -> Keyword.get(each_keyword, field)
|
||||
false ->
|
||||
case in_match or has_underscore_value do
|
||||
true -> underscore_value
|
||||
false -> Macro.escape(default)
|
||||
end
|
||||
end
|
||||
|
||||
{ new_fields, Keyword.delete(each_keyword, field) }
|
||||
end
|
||||
|
||||
{ match, remaining } = :lists.mapfoldl(iterator, keyword, fields)
|
||||
|
||||
case remaining do
|
||||
[] -> { :{}, caller.line, [atom|match] }
|
||||
_ ->
|
||||
keys = lc { key, _ } inlist remaining, do: key
|
||||
raise "record #{inspect atom} does not have the keys: #{inspect keys}"
|
||||
end
|
||||
end
|
||||
|
||||
defp is_orddict(list) when is_list(list), do: :lists.all(is_orddict_tuple(&1), list)
|
||||
defp is_orddict(_), do: false
|
||||
|
||||
defp is_orddict_tuple({ x, _ }) when is_atom(x), do: true
|
||||
defp is_orddict_tuple(_), do: false
|
||||
|
||||
|
||||
@doc """
|
||||
Main entry point for records definition.
|
||||
This is invoked directly by `Elixir.Builtin.defrecord`.
|
||||
This is invoked directly by `Kernel.defrecord`.
|
||||
Returns the quoted expression of a module given by name.
|
||||
"""
|
||||
def defrecord(name, values, opts) do
|
||||
@@ -29,7 +76,7 @@ defmodule Record do
|
||||
quote do
|
||||
defmodule unquote(name) do
|
||||
@moduledoc unquote(moduledoc)
|
||||
Record.define_functions(__MODULE__, unquote(values), unquote(definition))
|
||||
Record.define_functions(__ENV__, unquote(values), unquote(definition))
|
||||
unquote(block)
|
||||
end
|
||||
end
|
||||
@@ -37,17 +84,18 @@ defmodule Record do
|
||||
|
||||
@doc false
|
||||
# Private endpoint that defines the functions for the Record.
|
||||
def define_functions(module, values, definition) do
|
||||
def define_functions(env, values, definition) do
|
||||
# Escape the values so they are valid syntax nodes
|
||||
values = Macro.escape(values)
|
||||
|
||||
contents = [
|
||||
reflection(module, values),
|
||||
reflection(values),
|
||||
getters_and_setters(values, 1, [], definition),
|
||||
initializers(values)
|
||||
initializers(values),
|
||||
converters(values)
|
||||
]
|
||||
|
||||
Module.eval_quoted module, contents, [], file: __FILE__, line: __LINE__
|
||||
Module.eval_quoted env, contents
|
||||
end
|
||||
|
||||
# Define __record__/1 and __record__/2 as reflection functions
|
||||
@@ -64,11 +112,12 @@ defmodule Record do
|
||||
# FileInfo.__record__(:name) #=> FileInfo
|
||||
# FileInfo.__record__(:fields) #=> [atime: nil, mtime: nil]
|
||||
#
|
||||
defp reflection(name, values) do
|
||||
defp reflection(values) do
|
||||
quote do
|
||||
def __record__(kind), do: __record__(kind, nil)
|
||||
def __record__(:name, _), do: unquote(name)
|
||||
def __record__(:fields, _), do: unquote(values)
|
||||
def __access__(caller, args), do: Record.access(caller, __MODULE__, __record__(:fields), args)
|
||||
def __record__(kind, _), do: __record__(kind)
|
||||
def __record__(:name), do: __MODULE__
|
||||
def __record__(:fields), do: unquote(values)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -91,13 +140,13 @@ defmodule Record do
|
||||
# end
|
||||
#
|
||||
defp initializers(values) do
|
||||
defaults = Enum.map values, elem(&1, 2)
|
||||
defaults = lc value inlist values, do: elem(value, 2)
|
||||
|
||||
# For each value, define a piece of code that will receive
|
||||
# an ordered dict of options (opts) and it will try to fetch
|
||||
# the given key from the ordered dict, falling back to the
|
||||
# default value if one does not exist.
|
||||
selective = Enum.map values, fn {k,v} ->
|
||||
selective = lc { k, v } inlist values do
|
||||
quote do: Keyword.get(opts, unquote(k), unquote(v))
|
||||
end
|
||||
|
||||
@@ -109,6 +158,30 @@ defmodule Record do
|
||||
end
|
||||
end
|
||||
|
||||
# Define converters method(s). For a declaration like:
|
||||
#
|
||||
# defrecord FileInfo, atime: nil, mtime: nil
|
||||
#
|
||||
# It will define one method, to_keywords, which will return a Keyword
|
||||
#
|
||||
# [atime: nil, mtime: nil]
|
||||
#
|
||||
defp converters(values) do
|
||||
sorted = lc { k, _ } inlist values do
|
||||
index = find_index(values, k, 1)
|
||||
{ k, quote(do: :erlang.element(unquote(index + 1), record)) }
|
||||
end
|
||||
|
||||
quote do
|
||||
def to_keywords(record) do
|
||||
unquote(:orddict.from_list(sorted))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defp find_index([{ k, _ }|_], k, i), do: i
|
||||
defp find_index([{ _, _ }|t], k, i), do: find_index(t, k, i + 1)
|
||||
|
||||
# Implement getters and setters for each attribute.
|
||||
# For a declaration like:
|
||||
#
|
||||
@@ -150,7 +223,6 @@ end
|
||||
defmodule Record.Extractor do
|
||||
@moduledoc false
|
||||
|
||||
|
||||
# Retrieve a record definition from an Erlang file using
|
||||
# the same lookup as the *include* attribute from Erlang modules.
|
||||
def retrieve(name, from: string) do
|
||||
@@ -188,7 +260,7 @@ defmodule Record.Extractor do
|
||||
|
||||
# Parse the given file and retrieve all existent records.
|
||||
defp retrieve_from_file(file) do
|
||||
lc { :attribute, _, :record, record } in read_file(file), do: record
|
||||
lc { :attribute, _, :record, record } inlist read_file(file), do: record
|
||||
end
|
||||
|
||||
# Read a file and return its abstract syntax form that also
|
||||
@@ -296,5 +368,17 @@ defmodule Record.Definition do
|
||||
end
|
||||
end
|
||||
|
||||
def extension_for(key, default, i) when is_boolean(default) do
|
||||
bin_key = atom_to_binary(key)
|
||||
toggle = :"toggle_#{bin_key}"
|
||||
|
||||
quote do
|
||||
def unquote(toggle).(value // false, record) do
|
||||
current = :erlang.element(unquote(i), record)
|
||||
:erlang.setelement(unquote(i), record, not current)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def extension_for(_, _, _), do: nil
|
||||
end
|
||||
end
|
||||
@@ -4,7 +4,7 @@ defmodule Regex do
|
||||
in the Erlang Standard Library. More information can be found
|
||||
on re documentation: http://www.erlang.org/doc/man/re.html
|
||||
|
||||
Regular expressions in Elixir can be created using Regex.compile
|
||||
Regular expressions in Elixir can be created using Regex.compile!
|
||||
or using the special form with `%r`:
|
||||
|
||||
# A simple regular expressions that matches foo anywhere in the string
|
||||
@@ -13,21 +13,16 @@ defmodule Regex do
|
||||
# A regular expression with case insensitive options and handle unicode chars
|
||||
%r/foo/iu
|
||||
|
||||
The re module provides several options, some of them are not
|
||||
available in Elixir while others are enabled by default. The
|
||||
ones enabled by default are:
|
||||
|
||||
* multiline - the given string is always considered to be multiline, so
|
||||
`^` and `$` marks the beginning and end of each line. You need to use
|
||||
`\A` and `\z` to match the end or beginning of the string
|
||||
|
||||
The available options, followed by their shortcut in parenthesis, are:
|
||||
The re module provides several options, the one available in Elixir, followed by
|
||||
their shortcut in parenthesis, are:
|
||||
|
||||
* unicode (u) - used when you want to match against specific unicode characters
|
||||
* caseless (i) - add case insensitivity
|
||||
* dotall (m) - causes dot to match newlines and also set newline to anycrlf.
|
||||
* dotall (s) - causes dot to match newlines and also set newline to anycrlf.
|
||||
The new line setting can be overwritten by setting `(*CR)` or `(*LF)` or
|
||||
`(*CRLF)` or `(*ANY)` according to re documentation
|
||||
* multiline (m) - causes `^` and `$` to mark the beginning and end of each line.
|
||||
You need to use `\A` and `\z` to match the end or beginning of the string
|
||||
* extended (x) - whitespace characters are ignored except when escaped and
|
||||
allow `#` to delimit comments
|
||||
* firstline (f) - forces the unanchored pattern to match before or at the first
|
||||
@@ -43,24 +38,57 @@ defmodule Regex do
|
||||
or `(*ANY)` at the beginning of the regexp according to the re documentation
|
||||
|
||||
Most of the functions in this module accept either a binary or a char list
|
||||
as string subject. The result is based on the argument (a binary will return
|
||||
as subject. The result is based on the argument (a binary will return
|
||||
a binary, a char list will return a char list).
|
||||
"""
|
||||
|
||||
@doc """
|
||||
Compile the regular expression according to the given options.
|
||||
The result returned is a record named :re_pattern and its
|
||||
length can be modified in future releases.
|
||||
defexception CompileError, message: "regex could not be compiled"
|
||||
|
||||
Check the module documentation for more information
|
||||
about the options supported by compile.
|
||||
@doc """
|
||||
Compiles the regular expression according to the given options.
|
||||
|
||||
It returns `{ :ok, regex }` in case of success,
|
||||
`{ :error, reason }` otherwise.
|
||||
"""
|
||||
def compile(source, options // "") do
|
||||
source = to_binary(source)
|
||||
options = to_binary(options)
|
||||
re_opts = [:multiline|translate_options(options)]
|
||||
{ :ok, compiled } = Erlang.re.compile(source, re_opts)
|
||||
{ Regex, compiled, source, options }
|
||||
re_opts = translate_options(options)
|
||||
case Erlang.re.compile(source, re_opts) do
|
||||
{ :ok, compiled } ->
|
||||
{ :ok, { Regex, compiled, source, options } }
|
||||
error ->
|
||||
error
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Compiles the regular expression according to the given options.
|
||||
Fails with `Regex.CompileError` if the regex cannot be compiled.
|
||||
"""
|
||||
def compile!(source, options // "") do
|
||||
case compile(source, options) do
|
||||
{ :ok, regex } -> regex
|
||||
{ :error, { reason, at } } -> raise Regex.CompileError, message: "#{reason} at position #{at}"
|
||||
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
|
||||
|
||||
Regex.index %r/c(d)/, "abcd" #=> 3
|
||||
Regex.index %r/e/, "abcd" #=> nil
|
||||
|
||||
"""
|
||||
def index({ Regex, compiled, _, _ }, string) do
|
||||
case Erlang.re.run(string, compiled, [{ :capture, :first, :index }]) do
|
||||
:nomatch -> nil
|
||||
{ :match, [{index,_}] } -> index
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -103,12 +131,12 @@ defmodule Regex do
|
||||
|
||||
## Examples
|
||||
|
||||
Regex.run %r/c(d)/, "abcd" #=> [{2,2},{3,1}]
|
||||
Regex.run %r/e/, "abcd" #=> nil
|
||||
Regex.indexes %r/c(d)/, "abcd" #=> [{2,2},{3,1}]
|
||||
Regex.indexes %r/e/, "abcd" #=> nil
|
||||
|
||||
"""
|
||||
def indexes({ Regex, compiled, _, _ }, string) do
|
||||
case Erlang.re.run(string, compiled, [{ :capture, :all, :index }, { :offset, 0 }]) do
|
||||
case Erlang.re.run(string, compiled, [{ :capture, :all, :index }]) do
|
||||
:nomatch ->
|
||||
nil
|
||||
{ :match, results } ->
|
||||
@@ -165,7 +193,7 @@ defmodule Regex do
|
||||
case Erlang.re.run(string, compiled, options) do
|
||||
:nomatch -> []
|
||||
{ :match, results } ->
|
||||
lc result in results do
|
||||
lc result inlist results do
|
||||
case result do
|
||||
[t] -> t
|
||||
[h|t] -> t
|
||||
@@ -233,6 +261,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(<<?m, 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(<<>>), do: []
|
||||
end
|
||||
@@ -1,65 +1,3 @@
|
||||
# Since Elixir does not (yet) support local macros,
|
||||
# we need to wrap the step that extracts git information
|
||||
# in this module.
|
||||
defmodule System.GitCompiler do
|
||||
@moduledoc false
|
||||
|
||||
defmacro generate do
|
||||
quote do
|
||||
@doc """
|
||||
Returns a tuple { Elixir version, commit sha-1, build date }.
|
||||
|
||||
The format of the return value may change in a future release. Please
|
||||
make sure your code doesn't depend on it.
|
||||
"""
|
||||
def build_info do
|
||||
{ System.version,
|
||||
unquote(get_head_sha),
|
||||
unquote(get_date) }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Tries to run `git rev-parse HEAD`. In case of success returns the
|
||||
# commit sha, otherwise returns an empty string.
|
||||
defp get_head_sha do
|
||||
# The following failures are possible:
|
||||
#
|
||||
# 1) there is no `git` command
|
||||
# 2) pwd is not a git repository
|
||||
#
|
||||
command = 'git rev-parse HEAD'
|
||||
opts = [:stream, :exit_status, :use_stdio,
|
||||
:stderr_to_stdout, :in, :eof]
|
||||
port = :erlang.open_port {:spawn, command}, opts
|
||||
|
||||
output = read_port port
|
||||
case output do
|
||||
{ 0, data } ->
|
||||
Regex.replace_all %r/\n/, to_binary(data), ""
|
||||
_ ->
|
||||
""
|
||||
end
|
||||
end
|
||||
|
||||
defp read_port(port, data // []) do
|
||||
receive do
|
||||
{^port, {:data, new_data}} ->
|
||||
read_port port, [new_data|data]
|
||||
{^port, :eof} ->
|
||||
:erlang.port_close port
|
||||
receive do
|
||||
{^port, {:exit_status, exit_status}} ->
|
||||
{exit_status, List.reverse data}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defp get_date do
|
||||
list_to_binary :httpd_util.rfc1123_date
|
||||
end
|
||||
end
|
||||
|
||||
defmodule System do
|
||||
@moduledoc """
|
||||
The System module provides access to some variables used or
|
||||
@@ -67,13 +5,36 @@ defmodule System do
|
||||
with the VM or the host system.
|
||||
"""
|
||||
|
||||
require System.GitCompiler
|
||||
System.GitCompiler.generate
|
||||
# Tries to run `git rev-parse HEAD`. In case of success
|
||||
# returns the commit sha, otherwise returns an empty string.
|
||||
defmacrop get_head_sha do
|
||||
if :os.find_executable('git') do
|
||||
data = :os.cmd('git rev-parse HEAD')
|
||||
Regex.replace_all %r/\n/, to_binary(data), ""
|
||||
else
|
||||
""
|
||||
end
|
||||
end
|
||||
|
||||
# Get the date at compilation time.
|
||||
defmacrop get_date do
|
||||
list_to_binary :httpd_util.rfc1123_date
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns Elixir's version as binary.
|
||||
"""
|
||||
def version, do: "0.5.0"
|
||||
def version, do: "0.6.0"
|
||||
|
||||
@doc """
|
||||
Returns a tuple { Elixir version, commit sha-1, build date }.
|
||||
|
||||
The format of the return value may change in a future release. Please
|
||||
make sure your code doesn't depend on it.
|
||||
"""
|
||||
def build_info do
|
||||
{ version, get_head_sha, get_date }
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns the list of command-line arguments passed to the program.
|
||||
@@ -98,11 +59,37 @@ defmodule System do
|
||||
Executes `command` in a command shell of the target OS,
|
||||
captures the standard output of the command and returns
|
||||
the result as a binary.
|
||||
|
||||
If `command` is a char list, a char list is returned.
|
||||
Returns a binary otherwise.
|
||||
"""
|
||||
def cmd(command) when is_list(command) do
|
||||
:os.cmd(command)
|
||||
end
|
||||
|
||||
def cmd(command) do
|
||||
list_to_binary :os.cmd(to_char_list(command))
|
||||
end
|
||||
|
||||
@doc """
|
||||
This functions looks up an executable program given
|
||||
its name using the environment variable PATH on Unix
|
||||
and Windows.
|
||||
|
||||
If `command` is a char list, a char list is returned.
|
||||
Returns a binary otherwise.
|
||||
"""
|
||||
def find_executable(command) when is_list(command) do
|
||||
:os.find_executable(command) || nil
|
||||
end
|
||||
|
||||
def find_executable(command) do
|
||||
case :os.find_executable(to_char_list(command)) do
|
||||
false -> nil
|
||||
other -> list_to_binary(other)
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns a list of all environment variables. Each environment variable is
|
||||
given as a single string of the format "VarName=Value", where VarName is the
|
||||
@@ -157,9 +144,9 @@ defmodule System do
|
||||
## Helpers
|
||||
|
||||
# Filter stacktrace by removing internal BOOTSTRAP calls.
|
||||
defp filter_stacktrace([{ Elixir.Builtin, :raise, _, _ }|t]), do: filter_stacktrace(t)
|
||||
defp filter_stacktrace([{ Kernel, :raise, _, _ }|t]), do: filter_stacktrace(t)
|
||||
defp filter_stacktrace([{ _mod, :BOOTSTRAP, _, info }|t]),
|
||||
do: filter_stacktrace([{ Elixir.Builtin, :defmodule, 2, info }|t])
|
||||
do: filter_stacktrace([{ Kernel, :defmodule, 2, info }|t])
|
||||
defp filter_stacktrace([h|t]), do: [h|filter_stacktrace(t)]
|
||||
defp filter_stacktrace([]), do: []
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
defmodule Tuple do
|
||||
end
|
||||
@@ -57,7 +57,7 @@ defmodule URI do
|
||||
@doc """
|
||||
Percent (URL) encodes a URI.
|
||||
"""
|
||||
def encode(s), do: bc <<c>> in s, do: <<percent(c)|:binary>>
|
||||
def encode(s), do: bc <<c>> inbits s, do: <<percent(c)|:binary>>
|
||||
|
||||
defp percent(32), do: <<?+>>
|
||||
defp percent(?-), do: <<?->>
|
||||
@@ -94,8 +94,8 @@ defmodule URI do
|
||||
|
||||
def decode(<<>>), do: <<>>
|
||||
|
||||
defp hex2dec(n) when n >= ?A and n <= ?F, do: n - ?A + 10
|
||||
defp hex2dec(n) when n >= ?0 and n <= ?9, do: n - ?0
|
||||
defp hex2dec(n) when n in ?A..?F, do: n - ?A + 10
|
||||
defp hex2dec(n) when n in ?0..?9, do: n - ?0
|
||||
|
||||
defp check_plus(?+), do: 32
|
||||
defp check_plus(c), do: c
|
||||
@@ -170,7 +170,7 @@ defmodule URI do
|
||||
# Regex.run returns empty strings sometimes. We want
|
||||
# to replace those with nil for consistency.
|
||||
defp nillify(l) do
|
||||
lc s in l do
|
||||
lc s inlist l do
|
||||
if size(s) > 0 do
|
||||
s
|
||||
else
|
||||
@@ -0,0 +1,27 @@
|
||||
{erl_first_files, ["elixir_transform"]}.
|
||||
|
||||
{erl_opts, [
|
||||
warn_unused_vars,
|
||||
warn_export_all,
|
||||
warn_shadow_vars,
|
||||
warn_unused_import,
|
||||
warn_unused_function,
|
||||
warn_bif_clash,
|
||||
warn_unused_record,
|
||||
warn_deprecated_function,
|
||||
warn_obsolete_guard,
|
||||
strict_validation,
|
||||
warn_exported_vars,
|
||||
%% warn_export_vars,
|
||||
%% warn_missing_spec,
|
||||
%% warn_untyped_record,
|
||||
%% warnings_as_errors,
|
||||
no_debug_info
|
||||
]}.
|
||||
|
||||
{yrl_opts, [
|
||||
{report, true},
|
||||
{verbose, false}
|
||||
]}.
|
||||
|
||||
{require_otp_vsn,"R15"}.
|
||||
@@ -5,6 +5,7 @@
|
||||
eval_quoted/2, eval_quoted/3, eval_quoted/4,
|
||||
eval_forms/3]).
|
||||
-include("elixir.hrl").
|
||||
-compile({parse_transform, elixir_transform}).
|
||||
|
||||
% OTP APPLICATION API
|
||||
|
||||
@@ -12,7 +13,8 @@
|
||||
|
||||
start(_Type, _Args) ->
|
||||
%% Set the shell to unicode so printing inside files work
|
||||
io:setopts([{encoding,unicode}]),
|
||||
io:setopts(standard_io, [{encoding,unicode}]),
|
||||
io:setopts(standard_error, [{encoding,unicode}]),
|
||||
elixir_sup:start_link([]).
|
||||
|
||||
stop(_S) ->
|
||||
@@ -36,22 +38,22 @@ start_app() ->
|
||||
|
||||
start_cli() ->
|
||||
start_app(),
|
||||
'__MAIN__.Elixir.CLI':process_argv(init:get_plain_arguments()).
|
||||
'Elixir.Kernel.CLI':process_argv(init:get_plain_arguments()).
|
||||
|
||||
%% EVAL HOOKS
|
||||
|
||||
scope_for_eval(Opts) ->
|
||||
Filename = case orddict:find(file, Opts) of
|
||||
{ ok, F } -> to_char_list(F);
|
||||
error -> "nofile"
|
||||
File = case orddict:find(file, Opts) of
|
||||
{ ok, F } -> to_binary(F);
|
||||
error -> <<"nofile">>
|
||||
end,
|
||||
|
||||
Local = case orddict:find(delegate_locals_to, Opts) of
|
||||
{ ok, L } -> L;
|
||||
error -> []
|
||||
error -> nil
|
||||
end,
|
||||
|
||||
#elixir_scope{filename=Filename,local=Local}.
|
||||
#elixir_scope{file=File,local=Local}.
|
||||
|
||||
%% String evaluation
|
||||
|
||||
@@ -64,8 +66,9 @@ eval(String, Binding, Opts) ->
|
||||
end,
|
||||
eval(String, Binding, Line, scope_for_eval(Opts)).
|
||||
|
||||
eval(String, Binding, Line, #elixir_scope{filename=Filename} = S) ->
|
||||
Forms = elixir_translator:forms(String, Line, Filename),
|
||||
eval(String, Binding, Line, #elixir_scope{file=File} = S) when
|
||||
is_list(String), is_list(Binding), is_integer(Line), is_binary(File) ->
|
||||
Forms = elixir_translator:forms(String, Line, File),
|
||||
eval_forms(Forms, Binding, S).
|
||||
|
||||
%% Quoted evaluation
|
||||
@@ -97,8 +100,8 @@ eval_forms(Tree, Binding, RawScope) ->
|
||||
|
||||
%% INTERNAL HELPERS
|
||||
|
||||
to_char_list(Bin) when is_binary(Bin) -> binary_to_list(Bin);
|
||||
to_char_list(List) when is_list(List) -> List.
|
||||
to_binary(Bin) when is_binary(Bin) -> Bin;
|
||||
to_binary(List) when is_list(List) -> list_to_binary(List).
|
||||
|
||||
binding_dict(List) -> binding_dict(List, dict:new()).
|
||||
binding_dict([{H,_}|T], Dict) -> binding_dict(T, dict:store(H, H, Dict));
|
||||
@@ -107,7 +110,7 @@ binding_dict([], Dict) -> Dict.
|
||||
final_binding(Binding, Vars) -> final_binding(Binding, [], Binding, Vars).
|
||||
final_binding([{Var,_}|T], Acc, Binding, Vars) ->
|
||||
case atom_to_list(Var) of
|
||||
"_EX" ++ _ -> final_binding(T, Acc, Binding, Vars);
|
||||
"_@" ++ _ -> final_binding(T, Acc, Binding, Vars);
|
||||
_ ->
|
||||
RealName = dict:fetch(Var, Vars),
|
||||
RealValue = proplists:get_value(RealName, Binding, nil),
|
||||
@@ -118,7 +121,7 @@ final_binding([], Acc, _Binding, _Vars) -> lists:reverse(Acc).
|
||||
|
||||
normalize_binding(Binding) ->
|
||||
Keyword = orddict:from_list(Binding),
|
||||
case orddict:find('_EXMODULE', Keyword) of
|
||||
case orddict:find('_@MODULE', Keyword) of
|
||||
{ ok, _ } -> Keyword;
|
||||
_ -> orddict:store('_EXMODULE', nil, Keyword)
|
||||
_ -> orddict:store('_@MODULE', nil, Keyword)
|
||||
end.
|
||||
@@ -0,0 +1,84 @@
|
||||
-module(elixir_aliases).
|
||||
-export([first/1, last/1, concat/1, safe_concat/1, lookup/2,
|
||||
format_error/1, ensure_loaded/3]).
|
||||
-include("elixir.hrl").
|
||||
-compile({parse_transform, elixir_transform}).
|
||||
|
||||
%% Ensure a module is loaded before its usage.
|
||||
ensure_loaded(_Line, 'Elixir.Kernel', _S) ->
|
||||
ok;
|
||||
|
||||
ensure_loaded(Line, Ref, S) ->
|
||||
try
|
||||
Ref:module_info(compile)
|
||||
catch
|
||||
error:undef ->
|
||||
Kind = case lists:member(Ref, S#elixir_scope.scheduled) of
|
||||
true -> scheduled_module;
|
||||
false -> unloaded_module
|
||||
end,
|
||||
elixir_errors:form_error(Line, S#elixir_scope.file, ?MODULE, { Kind, Ref })
|
||||
end.
|
||||
|
||||
%% Receives an atom and returns the first alias.
|
||||
|
||||
first(Atom) ->
|
||||
First = first(atom_to_list(Atom), []),
|
||||
list_to_atom("Elixir-" ++ First).
|
||||
|
||||
first("Elixir-" ++ Rest, []) -> first(Rest, []);
|
||||
first([$-|_], Acc) -> lists:reverse(Acc);
|
||||
first([H|T], Acc) -> first(T, [H|Acc]);
|
||||
first([], Acc) -> lists:reverse(Acc).
|
||||
|
||||
%% Receives an atom and returns the last alias.
|
||||
|
||||
last(Atom) ->
|
||||
Last = last(lists:reverse(atom_to_list(Atom)), []),
|
||||
list_to_atom("Elixir-" ++ Last).
|
||||
|
||||
last([$-|_], Acc) -> Acc;
|
||||
last([H|T], Acc) -> last(T, [H|Acc]);
|
||||
last([], Acc) -> Acc.
|
||||
|
||||
%% Receives a list of atoms representing modules
|
||||
%% and concatenate them.
|
||||
|
||||
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(Args) -> do_concat(Args).
|
||||
|
||||
do_concat(Args) ->
|
||||
Aliases = [to_partial(Arg) || Arg <- Args, Arg /= nil],
|
||||
"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].
|
||||
|
||||
%% Lookup an alias in the current scope
|
||||
|
||||
lookup(Else, Dict) ->
|
||||
case orddict:find(Else, Dict) of
|
||||
{ ok, Value } when Value /= Else -> lookup(Value, Dict);
|
||||
_ -> Else
|
||||
end.
|
||||
|
||||
%% Errors
|
||||
|
||||
format_error({unloaded_module, Module}) ->
|
||||
io_lib:format("module ~s is not loaded and could not be found", [elixir_errors:inspect(Module)]);
|
||||
|
||||
format_error({scheduled_module, Module}) ->
|
||||
io_lib:format("module ~s is not loaded but was defined. This happens because you are trying to use a module in the same context it is defined. Try defining the module outside the context that requires it.",
|
||||
[elixir_errors:inspect(Module)]).
|
||||
@@ -4,7 +4,7 @@
|
||||
-export([
|
||||
assigns/3, assigns_block/5, assigns_block/6, extract_last_guards/1,
|
||||
get_pairs/4, get_pairs/5, match/3, extract_args/1, extract_guards/1]).
|
||||
-import(elixir_variables, [umergec/2]).
|
||||
-import(elixir_scope, [umergec/2]).
|
||||
-include("elixir.hrl").
|
||||
|
||||
%% Get pairs from a clause.
|
||||
@@ -19,16 +19,16 @@ get_pairs(Line, Key, Clauses, S, AllowNil) ->
|
||||
{ ok, nil } when AllowNil ->
|
||||
[];
|
||||
{ ok, _ } ->
|
||||
elixir_errors:syntax_error(Line, S#elixir_scope.filename, "expected pairs with -> for key ~s", [Key]);
|
||||
elixir_errors:syntax_error(Line, S#elixir_scope.file, "expected pairs with -> for key ~s", [Key]);
|
||||
_ ->
|
||||
[]
|
||||
end.
|
||||
|
||||
% Function for translating assigns.
|
||||
|
||||
assigns(Fun, Args, #elixir_scope{assign=false} = S) ->
|
||||
{ Result, NewS } = assigns(Fun, Args, S#elixir_scope{assign=true, temp_vars=dict:new()}),
|
||||
{ Result, NewS#elixir_scope{assign=false} };
|
||||
assigns(Fun, Args, #elixir_scope{context=Context} = S) when Context /= assign ->
|
||||
{ Result, NewS } = assigns(Fun, Args, S#elixir_scope{context=assign, temp_vars=dict:new()}),
|
||||
{ Result, NewS#elixir_scope{context=Context} };
|
||||
|
||||
assigns(Fun, Args, S) -> Fun(Args, S).
|
||||
|
||||
@@ -40,12 +40,17 @@ assigns_block(Line, Fun, BareArgs, Exprs, S) ->
|
||||
assigns_block(Line, Fun, Args, Exprs, Guards, S).
|
||||
|
||||
assigns_block(Line, Fun, Args, Exprs, Guards, S) ->
|
||||
{ TArgs, SA } = elixir_clauses:assigns(Fun, Args, S),
|
||||
{ TExprs, SE } = elixir_translator:translate(Exprs, SA),
|
||||
{ TArgs, SA } = assigns(Fun, Args, S#elixir_scope{extra_guards=[]}),
|
||||
{ TExprs, SE } = elixir_translator:translate(Exprs, SA#elixir_scope{extra_guards=nil}),
|
||||
|
||||
FArgs = listify(TArgs),
|
||||
SG = SA#elixir_scope{guard=true},
|
||||
FGuards = [translate_guard(Line, Guard, SG) || Guard <- Guards],
|
||||
SG = SA#elixir_scope{context=guard, extra_guards=nil},
|
||||
Extra = SA#elixir_scope.extra_guards,
|
||||
|
||||
FGuards = case Guards of
|
||||
[] -> case Extra of [] -> []; _ -> [Extra] end;
|
||||
_ -> [translate_guard(Line, Guard, Extra, SG) || Guard <- Guards]
|
||||
end,
|
||||
|
||||
% Uncompact expressions from the block.
|
||||
case TExprs of
|
||||
@@ -57,19 +62,10 @@ assigns_block(Line, Fun, Args, Exprs, Guards, S) ->
|
||||
|
||||
% Translate/Extract guards from the given expression.
|
||||
|
||||
translate_guard(Line, Guard, S) ->
|
||||
[element(1, elixir_translator:translate_each(elixir_quote:linify(Line, Guard), S))].
|
||||
translate_guard(Line, Guard, Extra, S) ->
|
||||
[element(1, elixir_translator:translate_each(elixir_quote:linify(Line, Guard), S))|Extra].
|
||||
|
||||
extract_guards({ 'when', _, [Left, Right] }) ->
|
||||
Clauses = extract_or_clauses(Right, []),
|
||||
case Left of
|
||||
{ 'in', Line, [Var, _] } ->
|
||||
{ Var, [{ 'and', Line, [Left, Clause] } || Clause <- Clauses] };
|
||||
_ ->
|
||||
{ Left, Clauses }
|
||||
end;
|
||||
|
||||
extract_guards({ 'in', _, [Var, _] } = Expr) -> { Var, [Expr] };
|
||||
extract_guards({ 'when', _, [Left, Right] }) -> { Left, extract_or_clauses(Right, []) };
|
||||
extract_guards(Else) -> { Else, [] }.
|
||||
|
||||
extract_or_clauses({ 'when', _, [Left, Right] }, Acc) -> extract_or_clauses(Right, [Left|Acc]);
|
||||
@@ -124,7 +120,7 @@ match(Line, DecoupledClauses, RawS) ->
|
||||
|
||||
% Defines a tuple that will be used as left side of the match operator
|
||||
LeftVars = [{var, Line, NewValue} || {_, NewValue,_} <- FinalVars],
|
||||
{ StorageVar, SS } = elixir_variables:build_erl(Line, FS),
|
||||
{ StorageVar, SS } = elixir_scope:build_erl_var(Line, FS),
|
||||
|
||||
% Expand all clauses by adding a match operation at the end that assigns
|
||||
% variables missing in one clause to the others.
|
||||
@@ -172,10 +168,10 @@ each_clause(Line, { 'after', [Condition], Expr }, S) ->
|
||||
{ { clause, Line, [TCondition], [], TBody }, SB };
|
||||
|
||||
each_clause(Line, { Key, [_|_], _ }, S) when Key == do; Key == 'after' ->
|
||||
elixir_errors:syntax_error(Line, S#elixir_scope.filename, "too many arguments given for ~s", [Key]);
|
||||
elixir_errors:syntax_error(Line, S#elixir_scope.file, "too many arguments given for ~s", [Key]);
|
||||
|
||||
each_clause(Line, { Key, _, _ }, S) ->
|
||||
elixir_errors:syntax_error(Line, S#elixir_scope.filename, "invalid key ~s", [Key]).
|
||||
elixir_errors:syntax_error(Line, S#elixir_scope.file, "invalid key ~s", [Key]).
|
||||
|
||||
% Check if the given expression is a match tuple.
|
||||
% This is a small optimization to allow us to change
|
||||
@@ -204,7 +200,7 @@ has_match_tuple(_) -> false.
|
||||
% Normalize the given var checking its existence in the scope var dictionary.
|
||||
|
||||
normalize_vars(Var, #elixir_scope{vars=Vars, clause_vars=ClauseVars} = S) ->
|
||||
{ { _, _, NewValue }, NS } = elixir_variables:build_erl(0, S),
|
||||
{ { _, _, NewValue }, NS } = elixir_scope:build_erl_var(0, S),
|
||||
|
||||
FS = NS#elixir_scope{
|
||||
vars=dict:store(Var, NewValue, Vars),
|
||||
@@ -6,7 +6,7 @@
|
||||
argv=[],
|
||||
loaded=[],
|
||||
at_exit=[],
|
||||
compiler_options=[]
|
||||
compiler_options=[{docs,true}]
|
||||
}).
|
||||
|
||||
start_link() ->
|
||||
@@ -32,7 +32,8 @@ handle_call({argv, Argv}, _From, Config) ->
|
||||
{ reply, ok, Config#elixir_code_server{argv=Argv} };
|
||||
|
||||
handle_call({compiler_options, Options}, _From, Config) ->
|
||||
{ reply, ok, Config#elixir_code_server{compiler_options=Options} };
|
||||
Final = orddict:merge(fun(_,_,V) -> V end, Config#elixir_code_server.compiler_options, Options),
|
||||
{ reply, ok, Config#elixir_code_server{compiler_options=Final} };
|
||||
|
||||
handle_call(loaded, _From, Config) ->
|
||||
{ reply, Config#elixir_code_server.loaded, Config };
|
||||
@@ -0,0 +1,234 @@
|
||||
-module(elixir_compiler).
|
||||
-export([get_opts/0, get_opt/1, get_opt/2, string/2, file/1, file_to_path/2]).
|
||||
-export([core/0, module/3, eval_forms/5]).
|
||||
-include("elixir.hrl").
|
||||
-compile({parse_transform, elixir_transform}).
|
||||
|
||||
%% Public API
|
||||
|
||||
%% Get compilation options.
|
||||
|
||||
get_opt(Key) -> get_opt(Key, get_opts()).
|
||||
|
||||
get_opt(Key, Dict) ->
|
||||
case orddict:find(Key, Dict) of
|
||||
{ ok, Value } -> Value;
|
||||
error -> false
|
||||
end.
|
||||
|
||||
get_opts() ->
|
||||
gen_server:call(elixir_code_server, compiler_options).
|
||||
|
||||
%% Compiles the given string.
|
||||
|
||||
string(Contents, File) when is_list(Contents), is_binary(File) ->
|
||||
Previous = get(elixir_compiled),
|
||||
|
||||
try
|
||||
put(elixir_compiled, []),
|
||||
Forms = elixir_translator:forms(Contents, 1, File),
|
||||
eval_forms(Forms, 1, File, [], #elixir_scope{file=File}),
|
||||
lists:reverse(get(elixir_compiled))
|
||||
after
|
||||
put(elixir_compiled, Previous)
|
||||
end.
|
||||
|
||||
%% Compile a file, return a tuple of module names and binaries.
|
||||
|
||||
file(Relative) when is_binary(Relative) ->
|
||||
File = filename:absname(Relative),
|
||||
case file:read_file(File) of
|
||||
{ok, Bin} ->
|
||||
string(unicode:characters_to_list(Bin), File);
|
||||
Error ->
|
||||
erlang:error(Error)
|
||||
end.
|
||||
|
||||
%% Compiles a file to the given path (directory).
|
||||
|
||||
file_to_path(File, Path) when is_binary(File), is_binary(Path) ->
|
||||
Lists = file(File),
|
||||
[binary_to_path(X, Path) || X <- Lists],
|
||||
Lists.
|
||||
|
||||
%% Evaluates the contents/forms by compiling them to an Erlang module.
|
||||
|
||||
eval_forms(Forms, Line, Module, Vars, #elixir_scope{module=nil} = S) ->
|
||||
eval_forms(Forms, Line, Module, nil, Vars, S);
|
||||
|
||||
eval_forms(Forms, Line, Module, Vars, #elixir_scope{module=Value} = S) ->
|
||||
eval_forms(Forms, Line, Module, Value, Vars, S).
|
||||
|
||||
eval_forms(Forms, Line, RawModule, Value, Vars, S) ->
|
||||
case (Value == nil) andalso allows_fast_compilation(Forms) of
|
||||
true -> eval_compilation(Forms, Vars, S);
|
||||
false -> code_loading_compilation(Forms, Line, RawModule, Value, Vars, S)
|
||||
end.
|
||||
|
||||
eval_compilation(Forms, Vars, S) ->
|
||||
Binding = [{ Var, Value } || { _, Var, Value } <- Vars],
|
||||
{ Result, _Binding, FS } = elixir:eval_forms(Forms, [{'_@MODULE',nil}|Binding], S),
|
||||
{ Result, FS }.
|
||||
|
||||
code_loading_compilation(Forms, Line, RawModule, Value, Vars, S) ->
|
||||
Module = escape_module(RawModule),
|
||||
{ Exprs, FS } = elixir_translator:translate(Forms, S),
|
||||
ModuleForm = module_form(Exprs, Line, S#elixir_scope.file, Module, Vars),
|
||||
|
||||
Args = [X || { _, _, X } <- Vars],
|
||||
|
||||
{ module(ModuleForm, S#elixir_scope.file, [], true, fun(Mod, _) ->
|
||||
Res = Mod:'BOOTSTRAP'(Value, Args),
|
||||
code:purge(Module),
|
||||
code:delete(Module),
|
||||
Res
|
||||
end), FS }.
|
||||
|
||||
%% Internal API
|
||||
|
||||
%% Compile the module by forms based on the scope information
|
||||
%% executes the callback in case of success. This automatically
|
||||
%% handles errors and warnings. Used by this module and elixir_module.
|
||||
module(Forms, S, Callback) ->
|
||||
Options = case get_opt(debug_info) of
|
||||
true -> [debug_info];
|
||||
_ -> []
|
||||
end,
|
||||
module(Forms, S#elixir_scope.file, Options, false, Callback).
|
||||
|
||||
module(Forms, File, Options, Bootstrap, Callback) when
|
||||
is_binary(File), is_list(Forms), is_list(Options), is_boolean(Bootstrap), is_function(Callback) ->
|
||||
Listname = binary_to_list(File),
|
||||
case compile:forms([no_auto_import()|Forms], [return,{source,Listname}|Options]) of
|
||||
{ok, ModuleName, Binary, Warnings} ->
|
||||
format_warnings(Bootstrap, File, Warnings),
|
||||
code:load_binary(ModuleName, Listname, Binary),
|
||||
Callback(ModuleName, Binary);
|
||||
{error, Errors, Warnings} ->
|
||||
format_warnings(Bootstrap, File, Warnings),
|
||||
format_errors(File, Errors)
|
||||
end.
|
||||
|
||||
%% Compile core files for bootstrap.
|
||||
%% Invoked from the Makefile.
|
||||
|
||||
core() ->
|
||||
elixir:start_app(),
|
||||
gen_server:call(elixir_code_server, { compiler_options, [{docs,false},{internal,true}] }),
|
||||
[core_file(File) || File <- core_main()],
|
||||
AllLists = [filelib:wildcard(Wildcard) || Wildcard <- core_list()],
|
||||
Files = lists:append(AllLists) -- core_main(),
|
||||
[core_file(File) || File <- 'Elixir.List':uniq(Files)].
|
||||
|
||||
%% HELPERS
|
||||
|
||||
no_auto_import() ->
|
||||
{ attribute, 0, compile, {
|
||||
no_auto_import, erlang:module_info(exports) } }.
|
||||
|
||||
module_form(Exprs, Line, File, Module, Vars) when
|
||||
is_binary(File), is_list(Exprs), is_integer(Line), is_atom(Module) ->
|
||||
|
||||
Cons = lists:foldr(fun({ _, Var, _ }, Acc) ->
|
||||
{ cons, Line, { var, Line, Var }, Acc }
|
||||
end, { nil, Line }, Vars),
|
||||
|
||||
Args = [{ var, Line, '_@MODULE'}, Cons],
|
||||
|
||||
[
|
||||
{ attribute, Line, file, { binary_to_list(File), 1 } },
|
||||
{ attribute, Line, module, Module },
|
||||
{ attribute, Line, export, [{ 'BOOTSTRAP', 2 }] },
|
||||
{ function, Line, 'BOOTSTRAP', length(Args), [
|
||||
{ clause, Line, Args, [], Exprs }
|
||||
] }
|
||||
].
|
||||
|
||||
%% Fast compilation is available?
|
||||
|
||||
allows_fast_compilation([{defmodule,_,_}|T]) -> allows_fast_compilation(T);
|
||||
allows_fast_compilation([]) -> true;
|
||||
allows_fast_compilation(_) -> false.
|
||||
|
||||
%% Escape the module name, removing slashes, dots,
|
||||
%% so it can be loaded by Erlang.
|
||||
|
||||
escape_module(Module) when is_atom(Module) ->
|
||||
escape_module(atom_to_list(Module));
|
||||
|
||||
escape_module(Module) when is_binary(Module) ->
|
||||
escape_module(binary_to_list(Module));
|
||||
|
||||
escape_module(Module) when is_list(Module) ->
|
||||
list_to_atom(escape_each(Module)).
|
||||
|
||||
escape_each([H|T]) when H >= $A, H =< $Z; H >= $a, H =< $z; H >= $0, H =< $9; H == $- ->
|
||||
[H|escape_each(T)];
|
||||
|
||||
escape_each([_|T]) ->
|
||||
[$_|escape_each(T)];
|
||||
|
||||
escape_each([]) -> [].
|
||||
|
||||
%% Receives a module Binary and outputs it in the given path.
|
||||
|
||||
binary_to_path({ModuleName, Binary}, CompilePath) ->
|
||||
Path = filename:join(CompilePath, atom_to_list(ModuleName) ++ ".beam"),
|
||||
ok = file:write_file(Path, Binary),
|
||||
Path.
|
||||
|
||||
%% CORE FILES COMPILATION
|
||||
|
||||
core_file(File) ->
|
||||
try
|
||||
Lists = file(list_to_binary(File)),
|
||||
[binary_to_path(X, "lib/elixir/ebin") || X <- Lists],
|
||||
io:format("Compiled ~s~n", [File])
|
||||
catch
|
||||
Kind:Reason ->
|
||||
io:format("~p: ~p~nstacktrace: ~p~n", [Kind, Reason, erlang:get_stacktrace()]),
|
||||
exit(1)
|
||||
end.
|
||||
|
||||
core_list() ->
|
||||
[
|
||||
"lib/elixir/lib/uri/parser.ex",
|
||||
"lib/elixir/lib/elixir/formatter.ex",
|
||||
"lib/elixir/lib/dict.ex",
|
||||
"lib/elixir/lib/*/*.ex",
|
||||
"lib/elixir/lib/*.ex"
|
||||
].
|
||||
|
||||
core_main() ->
|
||||
[
|
||||
"lib/elixir/lib/kernel.ex",
|
||||
"lib/elixir/lib/macro/env.ex",
|
||||
"lib/elixir/lib/macro.ex",
|
||||
"lib/elixir/lib/keyword.ex",
|
||||
"lib/elixir/lib/record.ex",
|
||||
"lib/elixir/lib/module.ex",
|
||||
"lib/elixir/lib/list.ex",
|
||||
"lib/elixir/lib/protocol.ex",
|
||||
"lib/elixir/lib/enum.ex",
|
||||
"lib/elixir/lib/exception.ex",
|
||||
"lib/elixir/lib/binary/inspect.ex",
|
||||
"lib/elixir/lib/binary/chars.ex",
|
||||
"lib/elixir/lib/list/chars.ex",
|
||||
"lib/elixir/lib/gen_server/behavior.ex",
|
||||
"lib/elixir/lib/code.ex"
|
||||
].
|
||||
|
||||
%% ERROR HANDLING
|
||||
|
||||
format_errors(_File, []) ->
|
||||
exit({nocompile, "compilation failed but no error was raised"});
|
||||
|
||||
format_errors(File, Errors) ->
|
||||
lists:foreach(fun ({_, Each}) ->
|
||||
lists:foreach(fun (Error) -> elixir_errors:handle_file_error(File, Error) end, Each)
|
||||
end, 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).
|
||||
@@ -10,6 +10,7 @@
|
||||
unwrap_stored_definitions/1,
|
||||
format_error/1]).
|
||||
-include("elixir.hrl").
|
||||
-compile({parse_transform, elixir_transform}).
|
||||
|
||||
%% Table management functions. Called internally.
|
||||
|
||||
@@ -44,12 +45,12 @@ reset_last(Module) ->
|
||||
%% If we just analyzed the compiled structure (i.e. the function availables
|
||||
%% before evaluating the function body), we would see both definitions.
|
||||
wrap_definition(Kind, Line, Name, Args, Guards, Expr, S) ->
|
||||
MetaS = elixir_variables:serialize_scope(S),
|
||||
MetaS = elixir_scope:serialize(S),
|
||||
|
||||
Invoke = [
|
||||
{atom, Line, Kind},
|
||||
{integer, Line, Line},
|
||||
{var, Line, '_EXMODULE'},
|
||||
{var, Line, '_@MODULE'},
|
||||
Name,
|
||||
Args,
|
||||
Guards,
|
||||
@@ -63,31 +64,24 @@ wrap_definition(Kind, Line, Name, Args, Guards, Expr, S) ->
|
||||
% Each function is then added to the function table.
|
||||
|
||||
store_definition(Kind, Line, nil, _Name, _Args, _Guards, _Expr, RawS) ->
|
||||
S = elixir_variables:deserialize_scope(RawS),
|
||||
elixir_errors:syntax_error(Line, S#elixir_scope.filename, "cannot define function outside module, invalid scope for ~s", [Kind]);
|
||||
S = elixir_scope:deserialize(RawS),
|
||||
elixir_errors:syntax_error(Line, S#elixir_scope.file, "cannot define function outside module, invalid scope for ~s", [Kind]);
|
||||
|
||||
store_definition(Kind, Line, Module, Name, Args, Guards, RawExpr, RawS) ->
|
||||
Arity = length(Args),
|
||||
|
||||
DS = elixir_variables:deserialize_scope(RawS),
|
||||
S = DS#elixir_scope{function={Name,Arity}, module=Module},
|
||||
|
||||
case RawExpr of
|
||||
skip_definition -> Expr = nil;
|
||||
[{ do, Expr }] -> [];
|
||||
_ -> Expr = { 'try', Line, [RawExpr] }
|
||||
end,
|
||||
DS = elixir_scope:deserialize(RawS),
|
||||
S = DS#elixir_scope{function={Name,Arity}, module=Module},
|
||||
Expr = def_body(Line, RawExpr),
|
||||
|
||||
{ Function, Defaults, TS } = translate_definition(Kind, Line, Name, Args, Guards, Expr, S),
|
||||
|
||||
Filename = TS#elixir_scope.filename,
|
||||
FunctionTable = table(Module),
|
||||
|
||||
CO = elixir_compiler:get_opts(),
|
||||
compile_docs(Kind, Line, Module, Name, Arity, TS, CO),
|
||||
compile_docs(Kind, Line, Module, Name, Arity, Args, TS, CO),
|
||||
|
||||
File = TS#elixir_scope.file,
|
||||
Table = table(Module),
|
||||
Stack = S#elixir_scope.macro,
|
||||
Location = retrieve_file(Module, CO),
|
||||
Stack = S#elixir_scope.macro,
|
||||
|
||||
%% Store function
|
||||
case RawExpr of
|
||||
@@ -95,29 +89,35 @@ store_definition(Kind, Line, Module, Name, Args, Guards, RawExpr, RawS) ->
|
||||
_ ->
|
||||
compile_super(Module, TS),
|
||||
CheckClauses = S#elixir_scope.check_clauses,
|
||||
store_each(CheckClauses, Kind, Filename, Location,
|
||||
Stack, FunctionTable, length(Defaults), Function)
|
||||
store_each(CheckClauses, Kind, File, Location,
|
||||
Stack, Table, length(Defaults), Function)
|
||||
end,
|
||||
|
||||
%% Store defaults
|
||||
[store_each(false, Kind, Filename, Location, Stack, FunctionTable, 0,
|
||||
function_for_clause(Name, Default)) || Default <- Defaults],
|
||||
[store_each(false, Kind, File, Location, Stack, Table, 0,
|
||||
function_for_default(Kind, Name, Default)) || Default <- Defaults],
|
||||
|
||||
{ Name, Arity }.
|
||||
|
||||
%% Compile the documentation related to the module.
|
||||
def_body(_Line, skip_definition) -> nil;
|
||||
def_body(_Line, [{ do, Expr }]) -> Expr;
|
||||
def_body(Line, Else) -> { 'try', Line, [Else] }.
|
||||
|
||||
%% Compile super clause
|
||||
|
||||
compile_super(Module, #elixir_scope{function=Function, super=true}) ->
|
||||
elixir_def_overridable:store(Module, Function, true);
|
||||
|
||||
compile_super(_Module, _S) -> [].
|
||||
|
||||
compile_docs(Kind, Line, Module, Name, Arity, S, CO) ->
|
||||
case elixir_compiler:get_opt(internal, CO) of
|
||||
true -> [];
|
||||
_ ->
|
||||
case '__MAIN__.Module':compile_doc(Module, Line, Kind, { Name, Arity }) of
|
||||
{ error, Message } -> elixir_errors:handle_file_warning(S#elixir_scope.filename,
|
||||
%% Compile docs
|
||||
|
||||
compile_docs(Kind, Line, Module, Name, Arity, Args, S, CO) ->
|
||||
case elixir_compiler:get_opt(docs, CO) of
|
||||
false -> [];
|
||||
true ->
|
||||
case 'Elixir.Module':compile_doc(Module, Line, Kind, { Name, Arity }, Args) of
|
||||
{ error, Message } -> elixir_errors:handle_file_warning(S#elixir_scope.file,
|
||||
{ Line, ?MODULE, { Message, { Name, Arity } } });
|
||||
_ -> []
|
||||
end
|
||||
@@ -127,44 +127,60 @@ retrieve_file(Module, CO) ->
|
||||
case elixir_compiler:get_opt(internal, CO) of
|
||||
true -> [];
|
||||
_ ->
|
||||
case '__MAIN__.Module':read_data(Module, file) of
|
||||
case 'Elixir.Module':read_attribute(Module, file) of
|
||||
nil -> [];
|
||||
Else ->
|
||||
'__MAIN__.Module':merge_data(Module, [{file,nil}]),
|
||||
if
|
||||
is_binary(Else) -> { Else, 1 };
|
||||
is_tuple(Else) -> Else
|
||||
end
|
||||
'Elixir.Module':delete_attribute(Module, file),
|
||||
Else
|
||||
end
|
||||
end.
|
||||
|
||||
%% Translate the given call and expression given
|
||||
%% and then store it in memory.
|
||||
|
||||
translate_definition(Kind, Line, Name, RawArgs, RawGuards, Expr, S) ->
|
||||
{ Args, Guards } = lists:mapfoldl(fun
|
||||
({ 'in', _, [Left, _] } = X, Acc) ->
|
||||
{ Left, add_to_guards(Line, X, Acc) };
|
||||
(X, Acc) ->
|
||||
{ X, Acc }
|
||||
end, RawGuards, RawArgs),
|
||||
translate_definition(Kind, Line, Name, Args, Guards, Expr, S) ->
|
||||
Arity = length(Args),
|
||||
IsMacro = is_macro(Kind),
|
||||
|
||||
Arity = length(Args),
|
||||
{ Unpacked, Defaults } = elixir_def_defaults:unpack(Kind, Name, Args, S),
|
||||
%% Macros receive a special argument on invocation. Notice it does
|
||||
%% not affect the arity of the stored function, but the clause
|
||||
%% already contains it.
|
||||
ExtendedArgs = case IsMacro of
|
||||
true -> [{ '_@CALLER', Line, nil }|Args];
|
||||
false -> Args
|
||||
end,
|
||||
|
||||
{ Unpacked, Defaults } = elixir_def_defaults:unpack(Kind, Name, ExtendedArgs, S),
|
||||
|
||||
{ TClause, TS } = elixir_clauses:assigns_block(Line,
|
||||
fun elixir_translator:translate/2, Unpacked, [Expr], Guards, S),
|
||||
|
||||
FClause = case TS#elixir_scope.name_args of
|
||||
%% Add names to args
|
||||
NClause = case TS#elixir_scope.name_args of
|
||||
true ->
|
||||
FArgs = elixir_def_overridable:assign_args(Line, element(3, TClause), TS),
|
||||
setelement(3, TClause, FArgs);
|
||||
NArgs = elixir_def_overridable:assign_args(Line, element(3, TClause), TS),
|
||||
setelement(3, TClause, NArgs);
|
||||
false -> TClause
|
||||
end,
|
||||
|
||||
%% Set __CALLER__ if used
|
||||
FClause = case IsMacro andalso TS#elixir_scope.caller of
|
||||
true ->
|
||||
FBody = { 'match', Line,
|
||||
{ 'var', Line, '__CALLER__' },
|
||||
?ELIXIR_WRAP_CALL(Line, elixir_scope, to_ex_env, [{ var, Line, '_@CALLER' }])
|
||||
},
|
||||
setelement(5, NClause, [FBody|element(5, NClause)]);
|
||||
false -> NClause
|
||||
end,
|
||||
|
||||
Function = { function, Line, Name, Arity, [FClause] },
|
||||
{ Function, Defaults, TS }.
|
||||
|
||||
is_macro(defmacro) -> true;
|
||||
is_macro(defmacrop) -> true;
|
||||
is_macro(_) -> false.
|
||||
|
||||
% Unwrap the functions stored in the functions table.
|
||||
% It returns a list of all functions to be exported, plus the macros,
|
||||
% and the body of all functions.
|
||||
@@ -182,7 +198,7 @@ unwrap_stored_definition([Fun|T], Exports, Private, Def, Defmacro, Defmacrop, Fu
|
||||
|
||||
unwrap_stored_definition([Fun|T], Exports, Private, Def, Defmacro, Defmacrop, Functions) when element(2, Fun) == defmacro ->
|
||||
{ Name, Arity } = Tuple = element(1, Fun),
|
||||
Macro = { ?ELIXIR_MACRO(Name), Arity },
|
||||
Macro = { ?ELIXIR_MACRO(Name), Arity + 1 },
|
||||
|
||||
unwrap_stored_definition(
|
||||
T, [Macro|Exports], Private, Def, [Tuple|Defmacro], Defmacrop,
|
||||
@@ -202,7 +218,8 @@ unwrap_stored_definition([Fun|T], Exports, Private, Def, Defmacro, Defmacrop, Fu
|
||||
);
|
||||
|
||||
unwrap_stored_definition([], Exports, Private, Def, Defmacro, Defmacrop, {Functions,Tail}) ->
|
||||
{ Exports, Private, Def, Defmacro, Defmacrop, lists:reverse(Tail ++ Functions) }.
|
||||
{ Exports, Private, ordsets:from_list(Def), ordsets:from_list(Defmacro),
|
||||
ordsets:from_list(Defmacrop), lists:reverse(Tail ++ Functions) }.
|
||||
|
||||
%% Helpers
|
||||
|
||||
@@ -221,61 +238,58 @@ function_for_stored_definition({{Name, Arity}, _, Line, _, Location, _, _, Claus
|
||||
]
|
||||
}.
|
||||
|
||||
function_for_clause(Name, { clause, Line, Args, _Guards, _Exprs } = Clause) ->
|
||||
function_for_default(Kind, Name, { clause, Line, Args, _Guards, _Exprs } = Clause)
|
||||
when Kind == defmacro; Kind == defmacrop ->
|
||||
{ function, Line, Name, length(Args) - 1, [Clause] };
|
||||
|
||||
function_for_default(_, Name, { clause, Line, Args, _Guards, _Exprs } = Clause) ->
|
||||
{ function, Line, Name, length(Args), [Clause] }.
|
||||
|
||||
%% Store each definition in the table.
|
||||
%% This function also checks and emit warnings in case
|
||||
%% the kind, of the visibility of the function changes.
|
||||
|
||||
store_each(Check, Kind, Filename, Location, Stack, Table, Defaults, {function, Line, Name, Arity, Clauses}) ->
|
||||
store_each(Check, Kind, File, Location, Stack, Table, Defaults, {function, Line, Name, Arity, Clauses}) ->
|
||||
case ets:lookup(Table, {Name, Arity}) of
|
||||
[{{Name, Arity}, StoredKind, _, _, StoredLocation, StoredStack, StoredDefaults, StoredClauses}] ->
|
||||
FinalLocation = StoredLocation,
|
||||
FinalDefaults = Defaults + StoredDefaults,
|
||||
FinalClauses = Clauses ++ StoredClauses,
|
||||
check_valid_kind(Line, Filename, Name, Arity, Kind, StoredKind),
|
||||
check_valid_defaults(Line, Filename, Name, Arity, Defaults),
|
||||
Check andalso (Stack == StoredStack) andalso check_valid_clause(Line, Filename, Name, Arity, Table);
|
||||
check_valid_kind(Line, File, Name, Arity, Kind, StoredKind),
|
||||
check_valid_defaults(Line, File, Name, Arity, FinalDefaults),
|
||||
Check andalso (Stack == StoredStack) andalso check_valid_clause(Line, File, Name, Arity, Table);
|
||||
[] ->
|
||||
FinalLocation = Location,
|
||||
FinalDefaults = Defaults,
|
||||
FinalClauses = Clauses,
|
||||
Check andalso ets:insert(Table, { last, { Name, Arity } })
|
||||
end,
|
||||
ets:insert(Table, {{Name, Arity}, Kind, Line, Filename, FinalLocation, Stack, FinalDefaults, FinalClauses}).
|
||||
ets:insert(Table, {{Name, Arity}, Kind, Line, File, FinalLocation, Stack, FinalDefaults, FinalClauses}).
|
||||
|
||||
%% Validations
|
||||
|
||||
check_valid_kind(_Line, _Filename, _Name, _Arity, Kind, Kind) -> [];
|
||||
check_valid_kind(Line, Filename, Name, Arity, Kind, StoredKind) ->
|
||||
elixir_errors:form_error(Line, Filename, ?MODULE,
|
||||
check_valid_kind(_Line, _File, _Name, _Arity, Kind, Kind) -> [];
|
||||
check_valid_kind(Line, File, Name, Arity, Kind, StoredKind) ->
|
||||
elixir_errors:form_error(Line, File, ?MODULE,
|
||||
{ changed_kind, { Name, Arity, StoredKind, Kind } }).
|
||||
|
||||
check_valid_clause(Line, Filename, Name, Arity, Table) ->
|
||||
check_valid_clause(Line, File, Name, Arity, Table) ->
|
||||
case ets:lookup_element(Table, last, 2) of
|
||||
{Name,Arity} -> [];
|
||||
[] -> [];
|
||||
{ElseName, ElseArity} ->
|
||||
elixir_errors:handle_file_warning(Filename, { Line, ?MODULE,
|
||||
elixir_errors:handle_file_warning(File, { Line, ?MODULE,
|
||||
{ changed_clause, { { Name, Arity }, { ElseName, ElseArity } } } })
|
||||
end.
|
||||
|
||||
check_valid_defaults(_Line, _Filename, _Name, _Arity, 0) -> [];
|
||||
check_valid_defaults(Line, Filename, Name, Arity, _) ->
|
||||
elixir_errors:handle_file_warning(Filename, { Line, ?MODULE, { clauses_with_docs, { Name, Arity } } }).
|
||||
|
||||
%% Helpers
|
||||
|
||||
add_to_guards(_Line, Expr, []) ->
|
||||
[Expr];
|
||||
add_to_guards(Line, Expr, Clauses) ->
|
||||
[{ 'and', Line, [Expr, Clause] } || Clause <- Clauses].
|
||||
check_valid_defaults(_Line, _File, _Name, _Arity, 0) -> [];
|
||||
check_valid_defaults(Line, File, Name, Arity, _) ->
|
||||
elixir_errors:handle_file_warning(File, { Line, ?MODULE, { clauses_with_docs, { Name, Arity } } }).
|
||||
|
||||
%% Format errors
|
||||
|
||||
format_error({clauses_with_docs,{Name,Arity}}) ->
|
||||
io_lib:format("function ~s/~B has default values and multiple clauses, it is recommended to use a separate clause for declaring defalts", [Name, Arity]);
|
||||
io_lib:format("function ~s/~B has default values and multiple clauses, use a separate clause for declaring defaults", [Name, Arity]);
|
||||
|
||||
format_error({private_doc,{Name,Arity}}) ->
|
||||
io_lib:format("function ~s/~B is private, @doc's are always discarded for private functions", [Name, Arity]);
|
||||
@@ -3,7 +3,6 @@
|
||||
-export([unpack/4]).
|
||||
-include("elixir.hrl").
|
||||
|
||||
% Unpack default args from the given clause. Invoked by elixir_translate.
|
||||
unpack(Kind, Name, Args, S) ->
|
||||
unpack_each(Kind, Name, Args, [], [], S).
|
||||
|
||||
@@ -50,7 +49,7 @@ extract_defaults([], NewArgs, NewInvoke) ->
|
||||
build_match([], _Line, Acc) -> Acc;
|
||||
|
||||
build_match([_|T], Line, Acc) ->
|
||||
Var = { ?ELIXIR_ATOM_CONCAT(["_EX", length(T)]), Line, nil },
|
||||
Var = { ?ELIXIR_ATOM_CONCAT(["_@", length(T)]), Line, nil },
|
||||
build_match(T, Line, [Var|Acc]).
|
||||
|
||||
% Given the invoked function name based on the kind
|
||||
@@ -8,45 +8,55 @@
|
||||
]).
|
||||
-include("elixir.hrl").
|
||||
|
||||
%% Reading
|
||||
%% Used by elixir_dispatch, returns false if no macro is found
|
||||
macro_for(_Tuple, _All, nil) -> false;
|
||||
|
||||
macro_for(_Tuple, _All, #elixir_scope{module=[]}) -> false;
|
||||
|
||||
macro_for(Tuple, All, #elixir_scope{module=Module}) ->
|
||||
case ets:lookup(elixir_def:table(Module), Tuple) of
|
||||
macro_for(Tuple, All, Module) ->
|
||||
try ets:lookup(elixir_def:table(Module), Tuple) of
|
||||
[{Tuple, Kind, Line, _, _, _, _, Clauses}] when Kind == defmacro; All, Kind == defmacrop ->
|
||||
RewrittenClauses = [rewrite_clause(Clause, Module) || Clause <- Clauses],
|
||||
Fun = { 'fun', Line, {clauses, lists:reverse(RewrittenClauses)} },
|
||||
{ value, Result, _Binding } = erl_eval:exprs([Fun], []),
|
||||
Result;
|
||||
_ -> false
|
||||
get_function(Line, Module, Clauses);
|
||||
_ ->
|
||||
false
|
||||
catch
|
||||
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 ets:lookup(elixir_def:table(Module), Tuple) of
|
||||
[{Tuple, _, Line, _, _, _, _, Clauses}] ->
|
||||
% elixir_def_local:record(Line, Tuple, false, Module),
|
||||
RewrittenClauses = [rewrite_clause(Clause, Module) || Clause <- Clauses],
|
||||
Fun = { 'fun', Line, {clauses, lists:reverse(RewrittenClauses)} },
|
||||
{ value, Result, _Binding } = erl_eval:exprs([Fun], []),
|
||||
Result;
|
||||
get_function(Line, Module, Clauses);
|
||||
_ ->
|
||||
[_|T] = erlang:get_stacktrace(),
|
||||
erlang:raise(error, undef, [{Module,Name,Arity,[]}|T])
|
||||
end.
|
||||
|
||||
%% Helpers
|
||||
%% TODO: Consider caching functions in a table for performance.
|
||||
|
||||
get_function(Line, Module, Clauses) ->
|
||||
RewrittenClauses = [rewrite_clause(Clause, Module) || Clause <- Clauses],
|
||||
Fun = { 'fun', Line, {clauses, lists:reverse(RewrittenClauses)} },
|
||||
{ value, Result, _Binding } = erl_eval:exprs([Fun], []),
|
||||
Result.
|
||||
|
||||
%% TODO: Consider caching functions in a table for performance.
|
||||
rewrite_clause({ call, Line, { atom, Line, RawName }, Args }, Module) ->
|
||||
Remote = { remote, Line,
|
||||
{ atom, Line, ?MODULE },
|
||||
{ atom, Line, function_for }
|
||||
},
|
||||
Arity = { integer, Line, length(Args) },
|
||||
Name = { atom, Line, rewrite_name(atom_to_list(RawName), RawName) },
|
||||
FunCall = { call, Line, Remote, [{ atom, Line, Module }, Name, Arity] },
|
||||
|
||||
%% If we have a macro, its arity in the table is
|
||||
%% actually one less than in the function call
|
||||
{ Name, Arity } = case atom_to_list(RawName) of
|
||||
"MACRO-" ++ Rest -> { list_to_atom(Rest), length(Args) - 1 };
|
||||
_ -> { RawName, length(Args) }
|
||||
end,
|
||||
|
||||
FunCall = { call, Line, Remote, [
|
||||
{ atom, Line, Module }, { atom, Line, Name }, { integer, Line, Arity }
|
||||
] },
|
||||
{ call, Line, FunCall, Args };
|
||||
|
||||
rewrite_clause(Tuple, Module) when is_tuple(Tuple) ->
|
||||
@@ -57,13 +67,10 @@ rewrite_clause(List, Module) when is_list(List) ->
|
||||
|
||||
rewrite_clause(Else, _) -> Else.
|
||||
|
||||
rewrite_name("MACRO-" ++ Rest, _) -> list_to_atom(Rest);
|
||||
rewrite_name(_, Name) -> Name.
|
||||
|
||||
%% Error handling
|
||||
|
||||
check_unused_local_macros(Filename, Recorded, PMacros) ->
|
||||
[elixir_errors:handle_file_warning(Filename,
|
||||
check_unused_local_macros(File, Recorded, PMacros) ->
|
||||
[elixir_errors:handle_file_warning(File,
|
||||
{ Line, ?MODULE, { unused_macro, Fun } }) || { Fun, Line } <- PMacros, not lists:member(Fun, Recorded)].
|
||||
|
||||
format_error({unused_macro,{Name, Arity}}) ->
|
||||
@@ -1,22 +1,15 @@
|
||||
% Holds the logic responsible for defining overridable functions and handling super.
|
||||
-module(elixir_def_overridable).
|
||||
-export([define/3, store_pending/1, is_defined/2, ensure_defined/4,
|
||||
-export([store_pending/1, is_defined/2, ensure_defined/4,
|
||||
assign_args/3, retrieve_args/3, name/2, store/3, format_error/1]).
|
||||
-include("elixir.hrl").
|
||||
-compile({parse_transform, elixir_transform}).
|
||||
|
||||
overridable(Module) ->
|
||||
ets:lookup_element(elixir_module:data_table(Module), overridable, 2).
|
||||
ets:lookup_element(elixir_module:data_table(Module), '__overridable', 2).
|
||||
|
||||
overridable(Module, Value) ->
|
||||
ets:insert(elixir_module:data_table(Module), { overridable, Value }).
|
||||
|
||||
%% Add new function to the overridable dictionary.
|
||||
|
||||
define(Module, Tuple, Args) ->
|
||||
Old = overridable(Module),
|
||||
New = [{ Tuple, { 1, [Args] } }],
|
||||
Abstract = orddict:merge(fun(_K, { Count, V1 }, _V2) -> { Count + 1, [Args|V1] } end, Old, New),
|
||||
overridable(Module, Abstract).
|
||||
ets:insert(elixir_module:data_table(Module), { '__overridable', Value }).
|
||||
|
||||
%% Check if an overridable function is defined.
|
||||
|
||||
@@ -28,9 +21,9 @@ is_defined(Module, Tuple) ->
|
||||
end.
|
||||
|
||||
ensure_defined(Line, Module, Tuple, S) ->
|
||||
case elixir_def_overridable:is_defined(Module, Tuple) of
|
||||
case is_defined(Module, Tuple) of
|
||||
true -> [];
|
||||
_ -> elixir_errors:form_error(Line, S#elixir_scope.filename, ?MODULE, { no_super, Module, Tuple })
|
||||
_ -> elixir_errors:form_error(Line, S#elixir_scope.file, ?MODULE, { no_super, Module, Tuple })
|
||||
end.
|
||||
|
||||
%% Retrieve args defined for the given arity.
|
||||
@@ -52,7 +45,7 @@ assign_args(Line, X, Acc, _) ->
|
||||
{ Match, Acc + 1 }.
|
||||
|
||||
super_arg(Counter) ->
|
||||
?ELIXIR_ATOM_CONCAT(['_EXS', Counter]).
|
||||
?ELIXIR_ATOM_CONCAT(['_@S', Counter]).
|
||||
|
||||
%% Gets the name based on the function and stored overridables
|
||||
|
||||
@@ -70,7 +63,7 @@ store(Module, Function, GenerateName) ->
|
||||
{ Count, [H|T] } = orddict:fetch(Function, Overridable),
|
||||
overridable(Module, orddict:store(Function, { Count, T }, Overridable)),
|
||||
|
||||
{ { Name, Arity }, Kind, Line, Filename, Location, Stack, Defaults, Clauses } = H,
|
||||
{ { Name, Arity }, Kind, Line, File, Location, Stack, Defaults, Clauses } = H,
|
||||
|
||||
{ FinalKind, FinalName } = case GenerateName of
|
||||
true -> { defp, name(Module, Function, Overridable) };
|
||||
@@ -78,21 +71,21 @@ store(Module, Function, GenerateName) ->
|
||||
end,
|
||||
|
||||
Def = { function, Line, FinalName, Arity, Clauses },
|
||||
elixir_def:store_each(false, FinalKind, Filename, Location,
|
||||
elixir_def:store_each(false, FinalKind, File, Location,
|
||||
Stack, elixir_def:table(Module), Defaults, Def).
|
||||
|
||||
%% Store pending declarations that were not manually made concrete.
|
||||
|
||||
store_pending(Module) ->
|
||||
[store(Module, X, false) || { X, { _, [_|_] } } <- overridable(Module),
|
||||
not '__MAIN__.Module':'function_defined?'(Module, X)].
|
||||
not 'Elixir.Module':'defines?'(Module, X)].
|
||||
|
||||
%% Error handling
|
||||
|
||||
format_error({ no_super, Module, { Name, Arity } }) ->
|
||||
Bins = [ format_fa(X) || { X, { _, [_|_] } } <- overridable(Module)],
|
||||
Joined = '__MAIN__.Enum':join(Bins, <<", ">>),
|
||||
io_lib:format("no super defined for ~s/~B in module ~p. Overridable functions available are: ~s",
|
||||
Joined = 'Elixir.Enum':join(Bins, <<", ">>),
|
||||
io_lib:format("no super defined for ~s/~B in module ~s. Overridable functions available are: ~s",
|
||||
[Name, Arity, elixir_errors:inspect(Module), Joined]).
|
||||
|
||||
format_fa({ Name, Arity }) ->
|
||||
@@ -3,19 +3,22 @@
|
||||
%% by elixir_import and therefore assumes it is normalized (ordsets)
|
||||
-module(elixir_dispatch).
|
||||
-export([default_macros/0, default_functions/0, default_requires/0,
|
||||
dispatch_require/6, dispatch_imports/5,
|
||||
dispatch_require/6, dispatch_import/5,
|
||||
require_function/5, import_function/4,
|
||||
expand_import/8, expand_require/8,
|
||||
format_error/1]).
|
||||
-include("elixir.hrl").
|
||||
-compile({parse_transform, elixir_transform}).
|
||||
|
||||
-import(ordsets, [is_element/2]).
|
||||
-define(BUILTIN, '__MAIN__.Elixir.Builtin').
|
||||
-define(BUILTIN, 'Elixir.Kernel').
|
||||
|
||||
default_functions() ->
|
||||
[ { ?BUILTIN, ordsets:union(in_elixir_functions(), in_erlang_functions()) } ].
|
||||
default_macros() ->
|
||||
[ { ?BUILTIN, ordsets:union(in_elixir_macros(), in_erlang_macros()) } ].
|
||||
default_requires() ->
|
||||
[ ?BUILTIN ].
|
||||
[ ?BUILTIN, 'Elixir.Kernel.Typespec' ].
|
||||
|
||||
%% Function retrieval
|
||||
|
||||
@@ -28,7 +31,7 @@ import_function(Line, Name, Arity, S) ->
|
||||
_ -> false
|
||||
end;
|
||||
Receiver ->
|
||||
elixir_import:record(import, Tuple, Receiver, S),
|
||||
elixir_import:record(import, Tuple, Receiver, S#elixir_scope.module),
|
||||
remote_function(Line, Receiver, Name, Arity, S)
|
||||
end.
|
||||
|
||||
@@ -40,32 +43,27 @@ require_function(Line, Receiver, Name, Arity, S) ->
|
||||
false -> remote_function(Line, Receiver, Name, Arity, S)
|
||||
end.
|
||||
|
||||
%% Dispatch based on scope's imports
|
||||
%% Function dispatch
|
||||
|
||||
dispatch_import(Line, Name, Args, S, Callback) ->
|
||||
Module = S#elixir_scope.module,
|
||||
Arity = length(Args),
|
||||
Tuple = { Name, Arity },
|
||||
|
||||
dispatch_imports(Line, Name, Args, S, Callback) ->
|
||||
Arity = length(Args),
|
||||
Tuple = { Name, Arity },
|
||||
case find_dispatch(Tuple, S#elixir_scope.functions) of
|
||||
false ->
|
||||
case find_dispatch(Tuple, S#elixir_scope.macros) of
|
||||
false ->
|
||||
Fun = (S#elixir_scope.function /= Tuple) andalso elixir_def_local:macro_for(Tuple, true, S),
|
||||
case Fun of
|
||||
false -> Callback();
|
||||
_ ->
|
||||
Receiver = S#elixir_scope.module,
|
||||
elixir_import:record(import, Tuple, Receiver, S),
|
||||
dispatch_macro_fun(Line, Fun, Receiver, Name, Arity, Args, S)
|
||||
end;
|
||||
?BUILTIN ->
|
||||
elixir_import:record(import, Tuple, ?BUILTIN, S),
|
||||
dispatch_builtin_macro(Line, Tuple, Args, S);
|
||||
Receiver ->
|
||||
elixir_import:record(import, Tuple, Receiver, S),
|
||||
dispatch_macro(Line, Receiver, Name, Arity, Args, S)
|
||||
case expand_import(Line, Tuple, Args, Module, S#elixir_scope.function,
|
||||
S#elixir_scope.requires, S#elixir_scope.macros, S) of
|
||||
{ error, noexpansion } ->
|
||||
Callback();
|
||||
{ error, internal } ->
|
||||
elixir_import:record(import, Tuple, ?BUILTIN, Module),
|
||||
elixir_macros:translate_macro({ Name, Line, Args }, S);
|
||||
{ ok, Receiver, Tree } ->
|
||||
translate_expansion(Line, Tree, Receiver, Name, Arity, S)
|
||||
end;
|
||||
Receiver ->
|
||||
elixir_import:record(import, Tuple, Receiver, S),
|
||||
elixir_import:record(import, Tuple, Receiver, Module),
|
||||
Endpoint = case (Receiver == ?BUILTIN) andalso is_element(Tuple, in_erlang_functions()) of
|
||||
true -> erlang;
|
||||
false -> Receiver
|
||||
@@ -73,69 +71,109 @@ dispatch_imports(Line, Name, Args, S, Callback) ->
|
||||
elixir_translator:translate_each({ { '.', Line, [Endpoint, Name] }, Line, Args }, S)
|
||||
end.
|
||||
|
||||
%% Dispatch based on scope's require
|
||||
dispatch_require(Line, Receiver, Name, Args, S, Callback) ->
|
||||
Module = S#elixir_scope.module,
|
||||
Arity = length(Args),
|
||||
Tuple = { Name, Arity },
|
||||
|
||||
dispatch_require(Line, ?BUILTIN, Name, Args, S, Callback) ->
|
||||
Arity = length(Args),
|
||||
Tuple = {Name, Arity},
|
||||
|
||||
case is_element(Tuple, in_erlang_functions()) of
|
||||
case (Receiver == Module) andalso is_element(Tuple, in_erlang_functions()) of
|
||||
true ->
|
||||
elixir_translator:translate_each({ { '.', Line, [erlang, Name] }, Line, Args }, S);
|
||||
false ->
|
||||
case dispatch_builtin_macro(Line, Tuple, Args, S) of
|
||||
false -> Callback();
|
||||
Else -> Else
|
||||
case expand_require(Line, Receiver, Tuple, Args, Module,
|
||||
S#elixir_scope.function, S#elixir_scope.requires, S) of
|
||||
{ error, noexpansion } ->
|
||||
Callback();
|
||||
{ error, internal } ->
|
||||
elixir_macros:translate_macro({ Name, Line, Args }, S);
|
||||
{ ok, Tree } ->
|
||||
translate_expansion(Line, Tree, Receiver, Name, Arity, S)
|
||||
end
|
||||
end.
|
||||
|
||||
%% Macros expansion
|
||||
|
||||
expand_import(Line, { Name, Arity } = Tuple, Args, Module, Function, Requires, Macros, SEnv) ->
|
||||
case find_dispatch(Tuple, Macros) of
|
||||
false ->
|
||||
Fun = (Function /= Tuple) andalso
|
||||
elixir_def_local:macro_for(Tuple, true, Module),
|
||||
case Fun of
|
||||
false -> { error, noexpansion };
|
||||
_ ->
|
||||
elixir_import:record(import, Tuple, Module, Module),
|
||||
{ ok, Module, expand_macro_fun(Line, Fun, Module, Name, Args, Module, Requires, SEnv) }
|
||||
end;
|
||||
?BUILTIN ->
|
||||
case is_element(Tuple, in_elixir_macros()) of
|
||||
false -> { error, internal };
|
||||
true ->
|
||||
elixir_import:record(import, Tuple, ?BUILTIN, Module),
|
||||
{ ok, ?BUILTIN, expand_macro_named(Line, ?BUILTIN, Name, Arity, Args, Module, Requires, SEnv) }
|
||||
end;
|
||||
Receiver ->
|
||||
elixir_import:record(import, Tuple, Receiver, Module),
|
||||
{ ok, Receiver, expand_macro_named(Line, Receiver, Name, Arity, Args, Module, Requires, SEnv) }
|
||||
end.
|
||||
|
||||
expand_require(Line, ?BUILTIN, { Name, Arity } = Tuple, Args, Module, _Function, Requires, SEnv) ->
|
||||
case is_element(Tuple, in_erlang_macros()) of
|
||||
true -> { error, internal };
|
||||
false ->
|
||||
case is_element(Tuple, in_elixir_macros()) of
|
||||
true -> { ok, expand_macro_named(Line, ?BUILTIN, Name, Arity, Args, Module, Requires, SEnv) };
|
||||
false -> { error, noexpansion }
|
||||
end
|
||||
end;
|
||||
|
||||
dispatch_require(Line, Receiver, Name, Args, S, Callback) ->
|
||||
Arity = length(Args),
|
||||
Tuple = {Name, Arity},
|
||||
|
||||
Fun = (S#elixir_scope.module == Receiver) andalso (S#elixir_scope.function /= Tuple) andalso
|
||||
elixir_def_local:macro_for(Tuple, false, S),
|
||||
expand_require(Line, Receiver, { Name, Arity } = Tuple, Args, Module, Function, Requires, SEnv) ->
|
||||
Fun = (Module == Receiver) andalso (Function /= Tuple) andalso
|
||||
elixir_def_local:macro_for(Tuple, false, Module),
|
||||
|
||||
case Fun of
|
||||
false ->
|
||||
case is_element(Tuple, get_optional_macros(Receiver)) of
|
||||
true -> dispatch_macro(Line, Receiver, Name, Arity, Args, S);
|
||||
false -> Callback()
|
||||
true -> { ok, expand_macro_named(Line, Receiver, Name, Arity, Args, Module, Requires, SEnv) };
|
||||
false -> { error, noexpansion }
|
||||
end;
|
||||
_ ->
|
||||
elixir_import:record(import, Tuple, Receiver, S),
|
||||
dispatch_macro_fun(Line, Fun, Receiver, Name, Arity, Args, S)
|
||||
elixir_import:record(import, Tuple, Receiver, Module),
|
||||
{ ok, expand_macro_fun(Line, Fun, Receiver, Name, Args, Module, Requires, SEnv) }
|
||||
end.
|
||||
|
||||
%% HELPERS
|
||||
%% Expansion helpers
|
||||
|
||||
dispatch_builtin_macro(Line, { Name, Arity } = Tuple, Args, S) ->
|
||||
case is_element(Tuple, in_erlang_macros()) of
|
||||
true -> elixir_macros:translate_macro({ Name, Line, Args }, S);
|
||||
expand_macro_fun(Line, Fun, Receiver, Name, Args, Module, Requires, SEnv) ->
|
||||
case (Receiver == Module) or is_element(Receiver, Requires) of
|
||||
true -> ok;
|
||||
false ->
|
||||
case is_element(Tuple, in_elixir_macros()) of
|
||||
true -> dispatch_macro(Line, ?BUILTIN, Name, Arity, Args, S);
|
||||
false -> false
|
||||
end
|
||||
end.
|
||||
Tuple = { unrequired_module, { Receiver, Name, length(Args), Requires } },
|
||||
elixir_errors:form_error(Line, elixir_scope:filename(SEnv), ?MODULE, Tuple)
|
||||
end,
|
||||
|
||||
dispatch_macro(Line, Receiver, Name, Arity, Args, S) ->
|
||||
Macro = ?ELIXIR_MACRO(Name),
|
||||
dispatch_macro_fun(Line, fun Receiver:Macro/Arity, Receiver, Name, Arity, Args, S).
|
||||
SArg = {Line,SEnv},
|
||||
|
||||
dispatch_macro_fun(Line, Fun, Receiver, Name, Arity, Args, S) ->
|
||||
ensure_required(Line, Receiver, Name, Arity, S),
|
||||
Tree = try
|
||||
apply(Fun, Args)
|
||||
try
|
||||
apply(Fun, [SArg|Args])
|
||||
catch
|
||||
Kind:Reason ->
|
||||
Info = { Receiver, Name, length(Args), [{ file, S#elixir_scope.filename }, { line, Line }] },
|
||||
erlang:raise(Kind, Reason, insert_before_dispatch_macro(Info, erlang:get_stacktrace()))
|
||||
end,
|
||||
Info = { Receiver, Name, length(Args), [{ file, elixir_scope:filename(SEnv) }, { line, Line }] },
|
||||
erlang:raise(Kind, Reason, munge_stacktrace(Info, erlang:get_stacktrace(), SArg))
|
||||
end.
|
||||
|
||||
expand_macro_named(Line, Receiver, Name, Arity, Args, Module, Requires, SEnv) ->
|
||||
ProperName = ?ELIXIR_MACRO(Name),
|
||||
ProperArity = Arity + 1,
|
||||
Fun = fun Receiver:ProperName/ProperArity,
|
||||
expand_macro_fun(Line, Fun, Receiver, Name, Args, Module, Requires, SEnv).
|
||||
|
||||
translate_expansion(Line, Tree, Receiver, Name, Arity, S) ->
|
||||
NewS = S#elixir_scope{macro=[{Line,Receiver,Name,Arity}|S#elixir_scope.macro]},
|
||||
{ TTree, TS } = elixir_translator:translate_each(elixir_quote:linify(Line, Tree), NewS),
|
||||
{ TTree, TS#elixir_scope{macro=S#elixir_scope.macro} }.
|
||||
|
||||
%% Helpers
|
||||
|
||||
find_dispatch(Tuple, [{ Name, Values }|T]) ->
|
||||
case is_element(Tuple, Values) of
|
||||
true -> Name;
|
||||
@@ -144,32 +182,24 @@ find_dispatch(Tuple, [{ Name, Values }|T]) ->
|
||||
|
||||
find_dispatch(_Tuple, []) -> false.
|
||||
|
||||
%% Insert call site into backtrace right after dispatch macro
|
||||
munge_stacktrace(Info, [{ _, _, [S|_], _ }|_], S) ->
|
||||
[Info];
|
||||
|
||||
insert_before_dispatch_macro(Info, [{ elixir_dispatch, dispatch_macro_fun, _, _ }|_] = T) ->
|
||||
[Info|T];
|
||||
munge_stacktrace(Info, [{ elixir_dispatch, expand_macro_fun, _, _ }|_], _) ->
|
||||
[Info];
|
||||
|
||||
insert_before_dispatch_macro(Info, [H|T]) ->
|
||||
[H|insert_before_dispatch_macro(Info, T)];
|
||||
munge_stacktrace(Info, [H|T], S) ->
|
||||
[H|munge_stacktrace(Info, T, S)];
|
||||
|
||||
insert_before_dispatch_macro(_, []) ->
|
||||
munge_stacktrace(_, [], _) ->
|
||||
[].
|
||||
|
||||
%% ERROR HANDLING
|
||||
|
||||
ensure_required(_Line, Receiver, _Name, _Arity, #elixir_scope{module=Receiver}) -> ok;
|
||||
ensure_required(Line, Receiver, Name, Arity, S) ->
|
||||
Requires = S#elixir_scope.requires,
|
||||
case is_element(Receiver, Requires) of
|
||||
true -> ok;
|
||||
false ->
|
||||
Tuple = { unrequired_module, { Receiver, Name, Arity, Requires } },
|
||||
elixir_errors:form_error(Line, S#elixir_scope.filename, ?MODULE, Tuple)
|
||||
end.
|
||||
|
||||
format_error({ unrequired_module,{Receiver, Name, Arity, Required }}) ->
|
||||
io_lib:format("tried to invoke macro ~s.~s/~B but module was not required. Required: ~p",
|
||||
[elixir_errors:inspect(Receiver), Name, Arity, [elixir_errors:inspect(R) || R <- Required]]).
|
||||
String = string:join([elixir_errors:inspect(R) || R <- Required], ", "),
|
||||
io_lib:format("tried to invoke macro ~s.~s/~B but module was not required. Required: ~s",
|
||||
[elixir_errors:inspect(Receiver), Name, Arity, String]).
|
||||
|
||||
%% INTROSPECTION
|
||||
|
||||
@@ -200,7 +230,7 @@ get_optional_macros(Receiver) ->
|
||||
{ error, _ } -> []
|
||||
end.
|
||||
|
||||
%% Functions imported from Elixit.Builtin module. Sorted on compilation.
|
||||
%% Functions imported from Kernel module. Sorted on compilation.
|
||||
|
||||
in_elixir_functions() ->
|
||||
try
|
||||
@@ -209,7 +239,7 @@ in_elixir_functions() ->
|
||||
error:undef -> []
|
||||
end.
|
||||
|
||||
%% Macros imported from Elixit.Builtin module. Sorted on compilation.
|
||||
%% Macros imported from Kernel module. Sorted on compilation.
|
||||
|
||||
in_elixir_macros() ->
|
||||
try
|
||||
@@ -224,9 +254,7 @@ in_erlang_functions() ->
|
||||
{ abs, 1 },
|
||||
{ atom_to_binary, 2 },
|
||||
{ atom_to_list, 1 },
|
||||
% Those are allowed in guard clauses, so we need to bring them back.
|
||||
% { binary_part, 2 },
|
||||
% { binary_part, 3 },
|
||||
{ binary_part, 3 },
|
||||
{ binary_to_atom, 2 },
|
||||
{ binary_to_existing_atom, 2 },
|
||||
{ binary_to_list, 1 },
|
||||
@@ -280,6 +308,7 @@ in_erlang_functions() ->
|
||||
% { now, 0 },
|
||||
{ pid_to_list, 1 },
|
||||
{ round, 1 },
|
||||
{ self, 0 },
|
||||
{ size, 1 },
|
||||
{ spawn, 1 },
|
||||
{ spawn, 3 },
|
||||
@@ -299,6 +328,7 @@ in_erlang_functions() ->
|
||||
%% Macros implemented in Erlang. MUST BE SORTED.
|
||||
in_erlang_macros() ->
|
||||
[
|
||||
{'!',1},
|
||||
{'!=',2},
|
||||
{'!==',2},
|
||||
{'*',2},
|
||||
@@ -317,7 +347,6 @@ in_erlang_macros() ->
|
||||
{'>',2},
|
||||
{'>=',2},
|
||||
{'@',1},
|
||||
{access,2},
|
||||
{'and',2},
|
||||
{apply,2},
|
||||
{apply,3},
|
||||
@@ -335,12 +364,14 @@ in_erlang_macros() ->
|
||||
{defp,1},
|
||||
{defp,2},
|
||||
{defp,4},
|
||||
{function,1},
|
||||
{function,2},
|
||||
{function,3},
|
||||
{in,2},
|
||||
{'not',1},
|
||||
{'or',2},
|
||||
{'receive',1},
|
||||
{'try',1},
|
||||
{use,1},
|
||||
{use,2},
|
||||
{'var!',1},
|
||||
{'xor',2}
|
||||
].
|
||||
@@ -0,0 +1,156 @@
|
||||
% A bunch of helpers to help to deal with errors in Elixir source code.
|
||||
% This is not exposed in the Elixir language.
|
||||
-module(elixir_errors).
|
||||
-export([syntax_error/3, syntax_error/4, inspect/1,
|
||||
form_error/4, parse_error/4, assert_module_scope/3,
|
||||
assert_no_function_scope/3, assert_function_scope/3,
|
||||
assert_no_assign_scope/3, assert_no_guard_scope/3,
|
||||
assert_no_assign_or_guard_scope/3,
|
||||
handle_file_warning/2, handle_file_warning/3, handle_file_error/2,
|
||||
deprecation/3, deprecation/4, file_format/3]).
|
||||
-include("elixir.hrl").
|
||||
-compile({parse_transform, elixir_transform}).
|
||||
|
||||
%% Handle inspecting for exceptions
|
||||
|
||||
inspect(Atom) when is_atom(Atom) ->
|
||||
case atom_to_list(Atom) of
|
||||
"Elixir-" ++ Rest -> [to_dot(R) || R <- Rest];
|
||||
Else -> Else
|
||||
end;
|
||||
|
||||
inspect(Other) -> Other.
|
||||
|
||||
to_dot($-) -> $.;
|
||||
to_dot(L) -> L.
|
||||
|
||||
%% Raised during macros translation.
|
||||
|
||||
syntax_error(Line, File, Message) when is_list(Message) ->
|
||||
syntax_error(Line, File, iolist_to_binary(Message));
|
||||
|
||||
syntax_error(Line, File, Message) when is_binary(Message) ->
|
||||
raise(Line, File, 'Elixir.SyntaxError', Message).
|
||||
|
||||
syntax_error(Line, File, Format, Args) ->
|
||||
Message = io_lib:format(Format, Args),
|
||||
raise(Line, File, 'Elixir.SyntaxError', iolist_to_binary(Message)).
|
||||
|
||||
%% Raised on tokenizing/parsing
|
||||
|
||||
parse_error(Line, File, Error, []) ->
|
||||
Message = case Error of
|
||||
"syntax error before: " -> <<"syntax error: expression is incomplete">>;
|
||||
_ -> iolist_to_binary(Error)
|
||||
end,
|
||||
raise(Line, File, 'Elixir.TokenMissingError', Message);
|
||||
|
||||
parse_error(Line, File, "syntax error before: ", "'end'") ->
|
||||
raise(Line, File, 'Elixir.SyntaxError', <<"unexpected token: end">>);
|
||||
|
||||
parse_error(Line, File, Error, Token) ->
|
||||
BinError = if
|
||||
is_atom(Error) -> atom_to_binary(Error, utf8);
|
||||
true -> iolist_to_binary(Error)
|
||||
end,
|
||||
|
||||
BinToken = if
|
||||
Token == [] -> <<>>;
|
||||
true -> iolist_to_binary(Token)
|
||||
end,
|
||||
|
||||
Message = <<BinError / binary, BinToken / binary >>,
|
||||
raise(Line, File, 'Elixir.SyntaxError', Message).
|
||||
|
||||
%% Raised during compilation
|
||||
|
||||
form_error(Line, File, Module, Desc) ->
|
||||
Message = iolist_to_binary(format_error(Module, Desc)),
|
||||
raise(Line, File, 'Elixir.CompileError', Message).
|
||||
|
||||
%% Shows a deprecation message
|
||||
|
||||
deprecation(Line, File, Message) -> deprecation(Line, File, Message, []).
|
||||
|
||||
deprecation(Line, File, Message, Args) ->
|
||||
io:format(file_format(Line, File, io_lib:format(Message, Args))).
|
||||
|
||||
%% Handle warnings and errors (called during module compilation)
|
||||
|
||||
%% Ignore on bootstrap
|
||||
handle_file_warning(true, _File, { _Line, sys_core_fold, nomatch_guard }) -> [];
|
||||
handle_file_warning(true, _File, { _Line, sys_core_fold, { nomatch_shadow, _ } }) -> [];
|
||||
|
||||
%% Ignore always
|
||||
handle_file_warning(_, _File, { _Line, sys_core_fold, useless_building }) -> [];
|
||||
|
||||
%% This is an Erlang bug, it considers { tuple, _ }.call to always fail
|
||||
handle_file_warning(_, _File, { _Line, v3_kernel, bad_call }) -> [];
|
||||
|
||||
%% Rewrite
|
||||
handle_file_warning(_, File, {Line,erl_lint,{undefined_behaviour_func,{Fun,Arity},Module}}) ->
|
||||
Raw = "undefined callback function ~s/~B (behaviour ~s)",
|
||||
Message = io_lib:format(Raw, [Fun,Arity,inspect(Module)]),
|
||||
io:format(file_format(Line, File, Message));
|
||||
|
||||
handle_file_warning(_, File, {Line,erl_lint,{undefined_behaviour,Module}}) ->
|
||||
Raw = io_lib:format("behaviour ~s undefined", [inspect(Module)]),
|
||||
|
||||
Message = case erlang:function_exported(Module, behavior_info, 1) of
|
||||
true -> Raw ++ " (maybe you meant behaviour_info instead of behavior_info?)";
|
||||
false -> Raw
|
||||
end,
|
||||
|
||||
io:format(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)).
|
||||
|
||||
handle_file_warning(File, Desc) ->
|
||||
handle_file_warning(false, File, Desc).
|
||||
|
||||
handle_file_error(File, {Line,Module,Desc}) ->
|
||||
form_error(Line, File, Module, Desc).
|
||||
|
||||
%% Assertions
|
||||
|
||||
assert_no_function_scope(_Line, _Kind, #elixir_scope{function=nil}) -> [];
|
||||
assert_no_function_scope(Line, Kind, S) ->
|
||||
syntax_error(Line, S#elixir_scope.file, "cannot invoke ~s inside a function", [Kind]).
|
||||
|
||||
assert_no_assign_or_guard_scope(Line, Kind, S) ->
|
||||
assert_no_assign_scope(Line, Kind, S),
|
||||
assert_no_guard_scope(Line, Kind, S).
|
||||
|
||||
assert_no_assign_scope(Line, Kind, #elixir_scope{context=assign} = S) ->
|
||||
syntax_error(Line, S#elixir_scope.file, "cannot invoke ~s inside assign", [Kind]);
|
||||
assert_no_assign_scope(_Line, _Kind, _S) -> [].
|
||||
|
||||
assert_no_guard_scope(Line, Kind, #elixir_scope{context=guard} = S) ->
|
||||
syntax_error(Line, S#elixir_scope.file, "cannot invoke ~s inside guard", [Kind]);
|
||||
assert_no_guard_scope(_Line, _Kind, _S) -> [].
|
||||
|
||||
assert_module_scope(Line, Kind, #elixir_scope{module=nil,file=File}) ->
|
||||
syntax_error(Line, File, "cannot invoke ~s outside module", [Kind]);
|
||||
assert_module_scope(_Line, _Kind, #elixir_scope{module=Module}) -> Module.
|
||||
|
||||
assert_function_scope(Line, Kind, #elixir_scope{function=nil,file=File}) ->
|
||||
syntax_error(Line, File, "cannot invoke ~s outside function", [Kind]);
|
||||
assert_function_scope(_Line, _Kind, #elixir_scope{function=Function}) -> Function.
|
||||
|
||||
%% Helpers
|
||||
|
||||
raise(Line, File, Kind, Message) ->
|
||||
Stacktrace = erlang:get_stacktrace(),
|
||||
erlang:raise(error, { Kind, '__exception__', Message, iolist_to_binary(File), Line }, Stacktrace).
|
||||
|
||||
file_format(Line, File, Message) ->
|
||||
io_lib:format("~ts:~w: ~ts~n", [File, Line, Message]).
|
||||
|
||||
format_error([], Desc) ->
|
||||
io_lib:format("~p", [Desc]);
|
||||
|
||||
format_error(Module, Desc) ->
|
||||
Module:format_error(Desc).
|
||||
@@ -14,11 +14,15 @@ build_table(Module) ->
|
||||
delete_table(Module) ->
|
||||
ets:delete(table(Module)).
|
||||
|
||||
record(_Kind, _Tuple, _Receiver, #elixir_scope{module=[]}) ->
|
||||
[];
|
||||
record(_Kind, _Tuple, _Receiver, nil) ->
|
||||
false;
|
||||
|
||||
record(import, Tuple, Receiver, #elixir_scope{module=Module}) ->
|
||||
ets:insert(table(Module), { Tuple, Receiver }).
|
||||
record(import, Tuple, Receiver, Module) ->
|
||||
try
|
||||
ets:insert(table(Module), { Tuple, Receiver })
|
||||
catch
|
||||
error:badarg -> false
|
||||
end.
|
||||
|
||||
recorded_locals(Module) ->
|
||||
Table = table(Module),
|
||||
@@ -27,7 +31,7 @@ recorded_locals(Module) ->
|
||||
ets:match_delete(Table, Match),
|
||||
lists:append(Result).
|
||||
|
||||
%% Update the scope to consider the imports for reference
|
||||
%% Update the scope to consider the imports for aliases
|
||||
%% based on the given options and selector.
|
||||
|
||||
import(Line, Ref, Opts, Selector, S) ->
|
||||
@@ -61,7 +65,7 @@ import(Line, Ref, Opts, Selector, S) ->
|
||||
%% Calculates the imports based on only and except
|
||||
|
||||
calculate(Line, Key, Opts, Old, AvailableFun, S) ->
|
||||
Filename = S#elixir_scope.filename,
|
||||
File = S#elixir_scope.file,
|
||||
All = keydelete(Key, Old),
|
||||
|
||||
New = case orddict:find(only, Opts) of
|
||||
@@ -69,7 +73,7 @@ calculate(Line, Key, Opts, Old, AvailableFun, S) ->
|
||||
case Only -- get_exports(Key) of
|
||||
[{Name,Arity}|_] ->
|
||||
Tuple = { invalid_import, { Key, Name, Arity } },
|
||||
elixir_errors:form_error(Line, Filename, ?MODULE, Tuple);
|
||||
elixir_errors:form_error(Line, File, ?MODULE, Tuple);
|
||||
_ -> intersection(Only, AvailableFun())
|
||||
end;
|
||||
error ->
|
||||
@@ -89,9 +93,9 @@ calculate(Line, Key, Opts, Old, AvailableFun, S) ->
|
||||
case Final of
|
||||
[] -> All;
|
||||
_ ->
|
||||
ensure_no_conflicts(Line, Filename, Final, keydelete(Key, S#elixir_scope.macros)),
|
||||
ensure_no_conflicts(Line, Filename, Final, keydelete(Key, S#elixir_scope.functions)),
|
||||
ensure_no_in_erlang_macro_conflict(Line, Filename, Key, Final, internal_conflict),
|
||||
ensure_no_conflicts(Line, File, Final, keydelete(Key, S#elixir_scope.macros)),
|
||||
ensure_no_conflicts(Line, File, Final, keydelete(Key, S#elixir_scope.functions)),
|
||||
ensure_no_in_erlang_macro_conflict(Line, File, Key, Final, internal_conflict),
|
||||
[{ Key, Final }|All]
|
||||
end.
|
||||
|
||||
@@ -117,7 +121,7 @@ get_macros(Line, Module, S) ->
|
||||
catch
|
||||
error:undef ->
|
||||
Tuple = { no_macros, Module },
|
||||
elixir_errors:form_error(Line, S#elixir_scope.filename, ?MODULE, Tuple)
|
||||
elixir_errors:form_error(Line, S#elixir_scope.file, ?MODULE, Tuple)
|
||||
end.
|
||||
|
||||
get_optional_macros(Module) ->
|
||||
@@ -137,13 +141,13 @@ get_optional_macros(Module) ->
|
||||
%% Elixir "implemented in Erlang" macro. Checking if a local
|
||||
%% conflicts with an import is automatically done by Erlang.
|
||||
|
||||
ensure_no_local_conflict(Line, Filename, Module, AllDefined) ->
|
||||
ensure_no_in_erlang_macro_conflict(Line, Filename, Module, AllDefined, local_conflict).
|
||||
ensure_no_local_conflict(Line, File, Module, AllDefined) ->
|
||||
ensure_no_in_erlang_macro_conflict(Line, File, Module, AllDefined, local_conflict).
|
||||
|
||||
%% Find conlicts in the given list of functions with
|
||||
%% the recorded set of imports.
|
||||
|
||||
ensure_no_import_conflict(Line, Filename, Module, AllDefined) ->
|
||||
ensure_no_import_conflict(Line, File, Module, AllDefined) ->
|
||||
Table = table(Module),
|
||||
Matches = [X || X <- AllDefined, ets:member(Table, X)],
|
||||
|
||||
@@ -151,7 +155,7 @@ ensure_no_import_conflict(Line, Filename, Module, AllDefined) ->
|
||||
[{Name,Arity}|_] ->
|
||||
Key = ets:lookup_element(Table, {Name, Arity }, 2),
|
||||
Tuple = { import_conflict, { Key, Name, Arity } },
|
||||
elixir_errors:form_error(Line, Filename, ?MODULE, Tuple);
|
||||
elixir_errors:form_error(Line, File, ?MODULE, Tuple);
|
||||
[] ->
|
||||
ok
|
||||
end.
|
||||
@@ -159,7 +163,7 @@ ensure_no_import_conflict(Line, Filename, Module, AllDefined) ->
|
||||
%% Ensure the given functions don't clash with any
|
||||
%% of Elixir non overridable macros.
|
||||
|
||||
ensure_no_in_erlang_macro_conflict(Line, Filename, Key, [{Name,Arity}|T], Reason) ->
|
||||
ensure_no_in_erlang_macro_conflict(Line, File, Key, [{Name,Arity}|T], Reason) ->
|
||||
Values = lists:filter(fun({X,Y}) ->
|
||||
(Name == X) andalso ((Y == '*') orelse (Y == Arity))
|
||||
end, non_overridable_macros()),
|
||||
@@ -167,27 +171,27 @@ ensure_no_in_erlang_macro_conflict(Line, Filename, Key, [{Name,Arity}|T], Reason
|
||||
case Values /= [] of
|
||||
true ->
|
||||
Tuple = { Reason, { Key, Name, Arity } },
|
||||
elixir_errors:form_error(Line, Filename, ?MODULE, Tuple);
|
||||
false -> ensure_no_in_erlang_macro_conflict(Line, Filename, Key, T, Reason)
|
||||
elixir_errors:form_error(Line, File, ?MODULE, Tuple);
|
||||
false -> ensure_no_in_erlang_macro_conflict(Line, File, Key, T, Reason)
|
||||
end;
|
||||
|
||||
ensure_no_in_erlang_macro_conflict(_Line, _Filename, _Key, [], _) -> ok.
|
||||
ensure_no_in_erlang_macro_conflict(_Line, _File, _Key, [], _) -> ok.
|
||||
|
||||
%% Find conlicts in the given list of functions with the set of imports.
|
||||
%% Used internally to ensure a newly imported fun or macro does not
|
||||
%% conflict with an already imported set.
|
||||
|
||||
ensure_no_conflicts(Line, Filename, Functions, [{Key,Value}|T]) ->
|
||||
ensure_no_conflicts(Line, File, Functions, [{Key,Value}|T]) ->
|
||||
Filtered = lists:filter(fun(X) -> lists:member(X, Functions) end, Value),
|
||||
case Filtered of
|
||||
[{Name,Arity}|_] ->
|
||||
Tuple = { already_imported, { Key, Name, Arity } },
|
||||
elixir_errors:form_error(Line, Filename, ?MODULE, Tuple);
|
||||
elixir_errors:form_error(Line, File, ?MODULE, Tuple);
|
||||
[] ->
|
||||
ensure_no_conflicts(Line, Filename, Functions, T)
|
||||
ensure_no_conflicts(Line, File, Functions, T)
|
||||
end;
|
||||
|
||||
ensure_no_conflicts(_Line, _Filename, _Functions, _S) -> ok.
|
||||
ensure_no_conflicts(_Line, _File, _Functions, _S) -> ok.
|
||||
|
||||
%% ERROR HANDLING
|
||||
|
||||
@@ -237,7 +241,7 @@ internal_funs() ->
|
||||
{ module_info, 0 },
|
||||
{ module_info, 1 },
|
||||
{ '__info__', 1 },
|
||||
{ '__using__', 2 }
|
||||
{ '__using__', 1 }
|
||||
].
|
||||
|
||||
%% Macros implemented in Erlang that are not overridable.
|
||||
@@ -248,29 +252,32 @@ non_overridable_macros() ->
|
||||
{'=',2},
|
||||
{'__op__',2},
|
||||
{'__op__',3},
|
||||
{'__ambiguousop__','*'},
|
||||
{'__scope__',2},
|
||||
{'__block__','*'},
|
||||
{'->','2'},
|
||||
{'<<>>','*'},
|
||||
{'{}','*'},
|
||||
{'[]','*'},
|
||||
{'alias',1},
|
||||
{'alias',2},
|
||||
{'require',1},
|
||||
{'require',2},
|
||||
{'import',1},
|
||||
{'import',2},
|
||||
{'import',3},
|
||||
{'__ENV__',0},
|
||||
{'__CALLER__',0},
|
||||
{'__MODULE__',0},
|
||||
{'__FILE__',0},
|
||||
{'__LINE__',0},
|
||||
{'__FUNCTION__',0},
|
||||
{'__ref__',1},
|
||||
{'__aliases__','*'},
|
||||
{'quote',1},
|
||||
{'quote',2},
|
||||
{'unquote',1},
|
||||
{'unquote_splicing',1},
|
||||
{'fn','*'},
|
||||
{'loop','*'},
|
||||
{'recur','*'},
|
||||
{'super','*'},
|
||||
{'super?',0},
|
||||
{'bc','*'},
|
||||
{'lc','*'}
|
||||
].
|
||||
@@ -0,0 +1,158 @@
|
||||
% Handle string and string-like interpolations.
|
||||
-module(elixir_interpolation).
|
||||
-export([extract/5, unescape_chars/1, unescape_chars/2,
|
||||
unescape_tokens/1, unescape_tokens/2, unescape_map/1]).
|
||||
-define(is_octal(S), S >= $0 andalso S =< $7).
|
||||
-include("elixir.hrl").
|
||||
-compile({parse_transform, elixir_transform}).
|
||||
|
||||
%% Extract string interpolations
|
||||
|
||||
extract(Line, File, Interpol, String, Last) ->
|
||||
extract(Line, File, Interpol, String, [], [], [], Last).
|
||||
|
||||
extract(Line, File, _Interpol, [], Buffer, [], Output, []) ->
|
||||
finish_extraction(Line, File, Buffer, Output, []);
|
||||
|
||||
extract(Line, _File, _Interpol, [], _Buffer, [], _Output, Last) ->
|
||||
{ error, { Line, io_lib:format("missing terminator: ~ts", [[Last]]), [] } };
|
||||
|
||||
extract(Line, File, _Interpol, [Last|Remaining], Buffer, [], Output, Last) ->
|
||||
finish_extraction(Line, File, Buffer, Output, Remaining);
|
||||
|
||||
extract(Line, _File, _Interpol, [Last], _Buffer, Search, _Output, Last) ->
|
||||
{ error, { Line, io_lib:format("unexpected end of string: ~ts", [[hd(Search)]]), [Last] } };
|
||||
|
||||
extract(Line, File, Interpol, [$\n|Rest], Buffer, Search, Output, Last) ->
|
||||
extract(Line+1, File, Interpol, Rest, [$\n|Buffer], Search, Output, Last);
|
||||
|
||||
extract(Line, File, Interpol, [$\\, $#, ${|Rest], Buffer, [], Output, Last) ->
|
||||
extract(Line, File, Interpol, Rest, [${,$#|Buffer], [], Output, Last);
|
||||
|
||||
extract(Line, File, Interpol, [$\\,Char|Rest], Buffer, [], Output, Last) ->
|
||||
extract(Line, File, Interpol, Rest, [Char,$\\|Buffer], [], Output, Last);
|
||||
|
||||
extract(Line, File, true, [$#, ${|Rest], Buffer, [], Output, Last) ->
|
||||
NewOutput = build_interpol(s, Line, File, Buffer, Output),
|
||||
extract(Line, File, true, Rest, [], [$}], NewOutput, Last);
|
||||
|
||||
extract(Line, File, true, [$}|Rest], Buffer, [$}], Output, Last) ->
|
||||
NewOutput = build_interpol(i, Line, File, Buffer, Output),
|
||||
extract(Line, File, true, Rest, [], [], NewOutput, Last);
|
||||
|
||||
%% Check for available separators "", {}, [] and () inside interpolation
|
||||
|
||||
extract(Line, File, Interpol, [C|Rest], Buffer, [C|Search], Output, Last) when C == $); C == $]; C == $}; C == $"; C == $' ->
|
||||
extract(Line, File, Interpol, Rest, [C|Buffer], Search, Output, Last);
|
||||
|
||||
extract(Line, File, Interpol, [C|Rest], Buffer, [_|_] = Search, Output, Last) when C == $"; C == $' ->
|
||||
extract(Line, File, Interpol, Rest, [C|Buffer], [C|Search], Output, Last);
|
||||
|
||||
extract(Line, File, Interpol, [${|Rest], Buffer, [_|_] = Search, Output, Last) ->
|
||||
extract(Line, File, Interpol, Rest, [${|Buffer], [$}|Search], Output, Last);
|
||||
|
||||
extract(Line, File, Interpol, [$[|Rest], Buffer, [_|_] = Search, Output, Last) ->
|
||||
extract(Line, File, Interpol, Rest, [$[|Buffer], [$]|Search], Output, Last);
|
||||
|
||||
extract(Line, File, Interpol, [$(|Rest], Buffer, [_|_] = Search, Output, Last) ->
|
||||
extract(Line, File, Interpol, Rest, [$(|Buffer], [$)|Search], Output, Last);
|
||||
|
||||
%% Else
|
||||
|
||||
extract(Line, File, Interpol, [Char|Rest], Buffer, Search, Output, Last) ->
|
||||
extract(Line, File, Interpol, Rest, [Char|Buffer], Search, Output, Last).
|
||||
|
||||
%% Unescape a series of tokens as returned by extract.
|
||||
|
||||
unescape_tokens(Tokens) ->
|
||||
unescape_tokens(Tokens, fun unescape_map/1).
|
||||
|
||||
unescape_tokens(Tokens, Map) ->
|
||||
[unescape_token(Token, Map) || Token <- Tokens].
|
||||
|
||||
unescape_token(Token, Map) when is_binary(Token) -> unescape_chars(Token, Map);
|
||||
unescape_token(Other, _Map) -> Other.
|
||||
|
||||
% Unescape chars. For instance, "\" "n" (two chars) needs to be converted to "\n" (one char).
|
||||
|
||||
-define(to_octal(List),
|
||||
|
||||
).
|
||||
|
||||
unescape_chars(String) ->
|
||||
unescape_chars(String, fun unescape_map/1).
|
||||
|
||||
unescape_chars(String, Map) ->
|
||||
Octals = case Map($0) of
|
||||
false -> false;
|
||||
_ -> true
|
||||
end,
|
||||
unescape_chars(String, Map, Octals, <<>>).
|
||||
|
||||
unescape_chars(<<$\\,A,B,C,Rest/binary>>, Map, true, Acc) when ?is_octal(A), ?is_octal(B), ?is_octal(C) ->
|
||||
to_octal(Rest, Map, [A,B,C], Acc);
|
||||
|
||||
unescape_chars(<<$\\,A,B,Rest/binary>>, Map, true, Acc) when ?is_octal(A), ?is_octal(B) ->
|
||||
to_octal(Rest, Map, [A,B], Acc);
|
||||
|
||||
unescape_chars(<<$\\,A,Rest/binary>>, Map, true, Acc) when ?is_octal(A) ->
|
||||
to_octal(Rest, Map, [A], Acc);
|
||||
|
||||
unescape_chars(<<$\\,Escaped,Rest/binary>>, Map, Octals, Acc) ->
|
||||
case Map(Escaped) of
|
||||
false -> unescape_chars(Rest, Map, Octals, <<Acc/binary, $\\, Escaped>>);
|
||||
Other -> unescape_chars(Rest, Map, Octals, <<Acc/binary, Other>>)
|
||||
end;
|
||||
|
||||
unescape_chars(<<Char, Rest/binary>>, Map, Octals, Acc) ->
|
||||
unescape_chars(Rest, Map, Octals, <<Acc/binary, Char>>);
|
||||
|
||||
unescape_chars(<<>>, _Map, _Octals, Acc) -> Acc.
|
||||
|
||||
to_octal(Rest, Map, Octal, Acc) ->
|
||||
unescape_chars(Rest, Map, true, <<Acc/binary, (list_to_integer(Octal, 8))/integer>>).
|
||||
|
||||
% Unescape Helpers
|
||||
|
||||
unescape_map($b) -> $\b;
|
||||
unescape_map($d) -> $\d;
|
||||
unescape_map($e) -> $\e;
|
||||
unescape_map($f) -> $\f;
|
||||
unescape_map($n) -> $\n;
|
||||
unescape_map($r) -> $\r;
|
||||
unescape_map($s) -> $\s;
|
||||
unescape_map($t) -> $\t;
|
||||
unescape_map($v) -> $\v;
|
||||
unescape_map(E) -> E.
|
||||
|
||||
% Extract Helpers
|
||||
|
||||
finish_extraction(Line, File, Buffer, Output, Remaining) ->
|
||||
case build_interpol(s, Line, File, Buffer, Output) of
|
||||
[] -> Final = [<<>>];
|
||||
Final -> []
|
||||
end,
|
||||
{ Line, lists:reverse(Final), Remaining }.
|
||||
|
||||
build_interpol(_Kind, _Line, _File, [], Output) ->
|
||||
Output;
|
||||
|
||||
build_interpol(s, _Line, _File, Buffer, Output) ->
|
||||
[unicode:characters_to_binary(lists:reverse(Buffer))|Output];
|
||||
|
||||
build_interpol(i, Line, File, Buffer, Output) ->
|
||||
[wrap_interpol(Line, forms(lists:reverse(Buffer), Line, File))| Output].
|
||||
|
||||
wrap_interpol(_Line, Form) when is_binary(Form) ->
|
||||
Form;
|
||||
|
||||
wrap_interpol(Line, Form) ->
|
||||
{ '|', Line, [{ { '.', Line, ['Elixir.Binary.Chars', to_binary] }, Line, [Form]}, binary]}.
|
||||
|
||||
forms(String, StartLine, File) ->
|
||||
case elixir_translator:raw_forms(String, StartLine, File) of
|
||||
{ ok, [] } -> nil;
|
||||
{ ok, [Forms] } when not is_list(Forms) -> Forms;
|
||||
{ ok, Forms } -> { '__block__', StartLine, Forms };
|
||||
{ error, Tuple } -> throw({ interpolation_error, Tuple })
|
||||
end.
|
||||
@@ -0,0 +1,317 @@
|
||||
%% Those macros behave like they belong to Kernel,
|
||||
%% but do not since they need to be implemented in Erlang.
|
||||
-module(elixir_macros).
|
||||
-export([translate_macro/2]).
|
||||
-import(elixir_translator, [translate_each/2, translate/2, translate_args/2, translate_apply/7]).
|
||||
-import(elixir_scope, [umergec/2]).
|
||||
-import(elixir_errors, [syntax_error/3, syntax_error/4,
|
||||
assert_no_function_scope/3, assert_module_scope/3, assert_no_assign_or_guard_scope/3]).
|
||||
-include("elixir.hrl").
|
||||
|
||||
-define(FUNS(), Kind == def; Kind == defp; Kind == defmacro; Kind == defmacrop).
|
||||
-compile({parse_transform, elixir_transform}).
|
||||
|
||||
%% Operators
|
||||
|
||||
translate_macro({ '+', _Line, [Expr] }, S) when is_number(Expr) ->
|
||||
translate_each(Expr, S);
|
||||
|
||||
translate_macro({ '-', _Line, [Expr] }, S) when is_number(Expr) ->
|
||||
translate_each(-1 * Expr, S);
|
||||
|
||||
translate_macro({ Op, Line, Exprs }, S) when is_list(Exprs),
|
||||
Op == '<-' orelse Op == '--' ->
|
||||
assert_no_assign_or_guard_scope(Line, Op, S),
|
||||
translate_each({ '__op__', Line, [Op|Exprs] }, S);
|
||||
|
||||
translate_macro({ Op, Line, Exprs }, S) when is_list(Exprs),
|
||||
Op == '+' orelse Op == '-' orelse Op == '*' orelse Op == '/' orelse
|
||||
Op == '++' orelse Op == 'not' orelse Op == 'and' orelse Op == 'or' orelse
|
||||
Op == 'xor' orelse Op == '<' orelse Op == '>' orelse Op == '<=' orelse
|
||||
Op == '>=' orelse Op == '==' orelse Op == '!=' orelse Op == '===' orelse
|
||||
Op == '!==' ->
|
||||
translate_each({ '__op__', Line, [Op|Exprs] }, S);
|
||||
|
||||
translate_macro({ '!', Line, [{ '!', _, [Expr] }] }, S) ->
|
||||
{ TExpr, SE } = translate_each(Expr, S),
|
||||
{ elixir_tree_helpers:convert_to_boolean(Line, TExpr, true, S#elixir_scope.context == guard), SE };
|
||||
|
||||
translate_macro({ '!', Line, [Expr] }, S) ->
|
||||
{ TExpr, SE } = translate_each(Expr, S),
|
||||
{ elixir_tree_helpers:convert_to_boolean(Line, TExpr, false, S#elixir_scope.context == guard), SE };
|
||||
|
||||
translate_macro({ in, Line, [Left, Right] }, #elixir_scope{extra_guards=nil} = S) ->
|
||||
{ _, TExpr, TS } = translate_in(Line, Left, Right, S),
|
||||
{ TExpr, TS };
|
||||
|
||||
translate_macro({ in, Line, [Left, Right] }, #elixir_scope{extra_guards=Extra} = S) ->
|
||||
{ TVar, TExpr, TS } = translate_in(Line, Left, Right, S),
|
||||
{ TVar, TS#elixir_scope{extra_guards=[TExpr|Extra]} };
|
||||
|
||||
%% Functions
|
||||
|
||||
translate_macro({ function, Line, [[{do,{ '->',_,Pairs}}]] }, S) ->
|
||||
assert_no_assign_or_guard_scope(Line, 'function', S),
|
||||
elixir_translator:translate_fn(Line, Pairs, S);
|
||||
|
||||
translate_macro({ function, Line, [_] }, S) ->
|
||||
assert_no_assign_or_guard_scope(Line, 'function', S),
|
||||
syntax_error(Line, S#elixir_scope.file, "invalid args for function");
|
||||
|
||||
translate_macro({ function, Line, [_, _] = Args }, S) ->
|
||||
assert_no_assign_or_guard_scope(Line, 'function', S),
|
||||
|
||||
case translate_args(Args, S) of
|
||||
{ [{atom,_,Name}, {integer,_,Arity}], SA } ->
|
||||
case elixir_dispatch:import_function(Line, Name, Arity, SA) of
|
||||
false -> syntax_error(Line, S#elixir_scope.file, "cannot convert a macro to a function");
|
||||
Else -> Else
|
||||
end;
|
||||
_ ->
|
||||
syntax_error(Line, S#elixir_scope.file, "cannot dynamically retrieve local function. use function(module, fun, arity) instead")
|
||||
end;
|
||||
|
||||
translate_macro({ function, Line, [_,_,_] = Args }, S) when is_list(Args) ->
|
||||
assert_no_assign_or_guard_scope(Line, 'function', S),
|
||||
{ [A,B,C], SA } = translate_args(Args, S),
|
||||
{ { 'fun', Line, { function, A, B, C } }, SA };
|
||||
|
||||
%% @
|
||||
|
||||
translate_macro({'@', Line, [{ Name, _, Args }]}, S) when Name == typep; Name == type; Name == spec; Name == callback ->
|
||||
case elixir_compiler:get_opt(internal) of
|
||||
true -> { { nil, Line }, S };
|
||||
false ->
|
||||
Call = { { '.', Line, ['Elixir.Kernel.Typespec', spec_to_macro(Name)] }, Line, Args },
|
||||
translate_each(Call, S)
|
||||
end;
|
||||
|
||||
translate_macro({'@', Line, [{ Name, _, Args }]}, S) ->
|
||||
assert_module_scope(Line, '@', S),
|
||||
|
||||
case is_reserved_data(Name) andalso elixir_compiler:get_opt(internal) of
|
||||
true ->
|
||||
{ { nil, Line }, S };
|
||||
_ ->
|
||||
case Args of
|
||||
[Arg] ->
|
||||
case S#elixir_scope.function of
|
||||
nil ->
|
||||
translate_each({
|
||||
{ '.', Line, ['Elixir.Module', add_attribute] },
|
||||
Line,
|
||||
[ { '__MODULE__', Line, false }, Name, Arg ]
|
||||
}, S);
|
||||
_ ->
|
||||
syntax_error(Line, S#elixir_scope.file,
|
||||
"cannot dynamically set attribute @~s inside a function", [Name])
|
||||
end;
|
||||
_ when is_atom(Args) or (Args == []) ->
|
||||
case S#elixir_scope.function of
|
||||
nil ->
|
||||
translate_each({
|
||||
{ '.', Line, ['Elixir.Module', read_attribute] },
|
||||
Line,
|
||||
[ { '__MODULE__', Line, false }, Name ]
|
||||
}, S);
|
||||
_ ->
|
||||
Contents = 'Elixir.Module':read_attribute(S#elixir_scope.module, Name),
|
||||
{ elixir_tree_helpers:abstract_syntax(Contents), S }
|
||||
end;
|
||||
_ ->
|
||||
syntax_error(Line, S#elixir_scope.file, "expected 0 or 1 argument for @~s, got: ~p", [Name, length(Args)])
|
||||
end
|
||||
end;
|
||||
|
||||
%% Case
|
||||
|
||||
translate_macro({'case', Line, [Expr, KV]}, S) ->
|
||||
assert_no_assign_or_guard_scope(Line, 'case', S),
|
||||
Clauses = elixir_clauses:get_pairs(Line, do, KV, S),
|
||||
{ TExpr, NS } = translate_each(Expr, S),
|
||||
|
||||
RClauses = case elixir_tree_helpers:returns_boolean(TExpr) of
|
||||
true -> rewrite_case_clauses(Clauses);
|
||||
false -> Clauses
|
||||
end,
|
||||
|
||||
{ TClauses, TS } = elixir_clauses:match(Line, RClauses, NS),
|
||||
{ { 'case', Line, TExpr, TClauses }, TS };
|
||||
|
||||
%% Try
|
||||
|
||||
translate_macro({'try', Line, [Clauses]}, RawS) ->
|
||||
S = RawS#elixir_scope{noname=true},
|
||||
assert_no_assign_or_guard_scope(Line, 'try', S),
|
||||
|
||||
Do = proplists:get_value('do', Clauses, []),
|
||||
{ TDo, SB } = translate([Do], S),
|
||||
|
||||
Catch = [Tuple || { X, _ } = Tuple <- Clauses, X == 'rescue' orelse X == 'catch'],
|
||||
{ TCatch, SC } = elixir_try:clauses(Line, Catch, umergec(S, SB)),
|
||||
|
||||
{ TAfter, SA } = case orddict:find('after', Clauses) of
|
||||
{ ok, After } -> translate([After], umergec(S, SC));
|
||||
error -> { [], SC }
|
||||
end,
|
||||
|
||||
{ { 'try', Line, unpack(TDo), [], TCatch, unpack(TAfter) }, umergec(RawS, SA) };
|
||||
|
||||
%% Receive
|
||||
|
||||
translate_macro({'receive', Line, [KV] }, S) ->
|
||||
assert_no_assign_or_guard_scope(Line, 'receive', S),
|
||||
Do = elixir_clauses:get_pairs(Line, do, KV, S, true),
|
||||
|
||||
case orddict:is_key('after', KV) of
|
||||
true ->
|
||||
After = elixir_clauses:get_pairs(Line, 'after', KV, S),
|
||||
{ TClauses, SC } = elixir_clauses:match(Line, Do ++ After, S),
|
||||
{ FClauses, TAfter } = elixir_tree_helpers:split_last(TClauses),
|
||||
{ _, _, [FExpr], _, FAfter } = TAfter,
|
||||
{ { 'receive', Line, FClauses, FExpr, FAfter }, SC };
|
||||
false ->
|
||||
{ TClauses, SC } = elixir_clauses:match(Line, Do, S),
|
||||
{ { 'receive', Line, TClauses }, SC }
|
||||
end;
|
||||
|
||||
%% Definitions
|
||||
|
||||
translate_macro({defmodule, Line, [Ref, KV]}, S) ->
|
||||
{ TRef, _ } = translate_each(Ref, S),
|
||||
|
||||
Block = case orddict:find(do, KV) of
|
||||
{ ok, DoValue } -> DoValue;
|
||||
error -> syntax_error(Line, S#elixir_scope.file, "expected do: argument in defmodule")
|
||||
end,
|
||||
|
||||
{ FRef, FS } = case TRef of
|
||||
{ atom, _, Module } ->
|
||||
NewModule = module_ref(Ref, Module, S#elixir_scope.module),
|
||||
|
||||
RS = case Module == NewModule of
|
||||
true -> S;
|
||||
false ->
|
||||
element(2, translate_each({
|
||||
alias, Line, [NewModule, [{as, elixir_aliases:first(Module)}]]
|
||||
}, S))
|
||||
end,
|
||||
|
||||
{
|
||||
{ atom, Line, NewModule },
|
||||
RS#elixir_scope{scheduled=[NewModule|S#elixir_scope.scheduled]}
|
||||
};
|
||||
_ ->
|
||||
{ TRef, S }
|
||||
end,
|
||||
|
||||
{ elixir_module:translate(Line, FRef, Block, S), FS };
|
||||
|
||||
translate_macro({Kind, Line, [Call]}, S) when ?FUNS() ->
|
||||
translate_macro({Kind, Line, [Call, skip_definition]}, S);
|
||||
|
||||
translate_macro({Kind, Line, [Call, Expr]}, S) when ?FUNS() ->
|
||||
assert_module_scope(Line, Kind, S),
|
||||
assert_no_function_scope(Line, Kind, S),
|
||||
{ TCall, Guards } = elixir_clauses:extract_guards(Call),
|
||||
{ Name, Args } = elixir_clauses:extract_args(TCall),
|
||||
TName = elixir_tree_helpers:abstract_syntax(Name),
|
||||
TArgs = elixir_tree_helpers:abstract_syntax(Args),
|
||||
TGuards = elixir_tree_helpers:abstract_syntax(Guards),
|
||||
TExpr = elixir_tree_helpers:abstract_syntax(Expr),
|
||||
{ elixir_def:wrap_definition(Kind, Line, TName, TArgs, TGuards, TExpr, S), S };
|
||||
|
||||
translate_macro({Kind, Line, [Name, Args, Guards, Expr]}, S) when ?FUNS() ->
|
||||
assert_module_scope(Line, Kind, S),
|
||||
assert_no_function_scope(Line, Kind, S),
|
||||
{ TName, NS } = translate_each(Name, S),
|
||||
{ TArgs, AS } = translate_each(Args, NS),
|
||||
{ TGuards, TS } = translate_each(Guards, AS),
|
||||
TExpr = elixir_tree_helpers:abstract_syntax(Expr),
|
||||
{ elixir_def:wrap_definition(Kind, Line, TName, TArgs, TGuards, TExpr, TS), TS };
|
||||
|
||||
%% Apply - Optimize apply by checking what doesn't need to be dispatched dynamically
|
||||
|
||||
translate_macro({ apply, Line, [Left, Right, Args] }, S) when is_list(Args) ->
|
||||
{ TLeft, SL } = translate_each(Left, S),
|
||||
{ TRight, SR } = translate_each(Right, umergec(S, SL)),
|
||||
translate_apply(Line, TLeft, TRight, Args, S, SL, SR);
|
||||
|
||||
translate_macro({ apply, Line, Args }, S) ->
|
||||
{ TArgs, NS } = translate_args(Args, S),
|
||||
{ ?ELIXIR_WRAP_CALL(Line, erlang, apply, TArgs), NS };
|
||||
|
||||
%% Handle forced variables
|
||||
|
||||
translate_macro({ 'var!', _, [{Name, Line, Atom}] }, S) when is_atom(Name), is_atom(Atom) ->
|
||||
elixir_scope:translate_var(Line, Name, S);
|
||||
|
||||
translate_macro({ 'var!', Line, [_] }, S) ->
|
||||
syntax_error(Line, S#elixir_scope.file, "invalid args for var!").
|
||||
|
||||
%% HELPERS
|
||||
|
||||
translate_in(Line, Left, Right, S) ->
|
||||
{ TLeft, SL } = case Left of
|
||||
{ '_', _, Atom } when is_atom(Atom) ->
|
||||
elixir_scope:build_erl_var(Line, S);
|
||||
_ ->
|
||||
translate_each(Left, S)
|
||||
end,
|
||||
|
||||
{ TRight, SR } = translate_each(Right, SL),
|
||||
|
||||
Cache = (S#elixir_scope.context == nil),
|
||||
|
||||
{ Var, SV } = case Cache of
|
||||
true -> elixir_scope:build_erl_var(Line, SR);
|
||||
false -> { TLeft, SR }
|
||||
end,
|
||||
|
||||
Expr = case TRight of
|
||||
{ cons, _, _, _ } ->
|
||||
[H|T] = elixir_tree_helpers:cons_to_list(TRight),
|
||||
lists:foldl(fun(X, Acc) ->
|
||||
{ op, Line, 'orelse', Acc, { op, Line, '==', Var, X } }
|
||||
end, { op, Line, '==', Var, H }, T);
|
||||
{ tuple, _, [{ atom, _, 'Elixir.Range' }, Start, End] } ->
|
||||
{ op, Line, 'andalso',
|
||||
{ op, Line, '>=', Var, Start },
|
||||
{ op, Line, '=<', Var, End }
|
||||
};
|
||||
_ ->
|
||||
syntax_error(Line, S#elixir_scope.file, "invalid args for operator in")
|
||||
end,
|
||||
|
||||
case Cache of
|
||||
true -> { Var, { block, Line, [ { match, Line, Var, TLeft }, Expr ] }, SV };
|
||||
false -> { Var, Expr, SV }
|
||||
end.
|
||||
|
||||
rewrite_case_clauses([{do,[{in,_,[{'_',_,_},[false,nil]]}],False},{do,[{'_',_,_}],True}]) ->
|
||||
[{do,[false],False},{do,[true],True}];
|
||||
|
||||
rewrite_case_clauses(Clauses) ->
|
||||
Clauses.
|
||||
|
||||
module_ref(_Raw, Module, nil) ->
|
||||
Module;
|
||||
|
||||
module_ref({ '__aliases__', _, ['Elixir'|_]}, Module, _Nesting) ->
|
||||
Module;
|
||||
|
||||
module_ref(_F, Module, Nesting) ->
|
||||
elixir_aliases:concat([Nesting, Module]).
|
||||
|
||||
is_reserved_data(moduledoc) -> true;
|
||||
is_reserved_data(doc) -> true;
|
||||
is_reserved_data(_) -> false.
|
||||
|
||||
spec_to_macro(type) -> deftype;
|
||||
spec_to_macro(typep) -> deftypep;
|
||||
spec_to_macro(spec) -> defspec;
|
||||
spec_to_macro(callback) -> defcallback.
|
||||
|
||||
% Unpack a list of expressions from a block.
|
||||
unpack([{ '__block__', _, Exprs }]) -> Exprs;
|
||||
unpack(Exprs) -> Exprs.
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user