Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
833e9e9783 | ||
|
|
411f1ce55d | ||
|
|
5e6c761b73 | ||
|
|
bd3eb2a10f | ||
|
|
ab25aa72b1 | ||
|
|
8304ec0e91 | ||
|
|
ca0dbe8f46 | ||
|
|
03f30b985b | ||
|
|
152911a03d | ||
|
|
923eb5d63d | ||
|
|
dc4029d577 | ||
|
|
90ab4e3160 | ||
|
|
b757cfcb59 | ||
|
|
3a1d97550c | ||
|
|
9ddfa0bf41 | ||
|
|
673f27d1a8 | ||
|
|
01f6d4b1d9 | ||
|
|
5592b7b264 | ||
|
|
43d3e3eba6 | ||
|
|
4f3e66cdfd | ||
|
|
435c4615d1 | ||
|
|
5e886ffd97 | ||
|
|
6cece2c074 | ||
|
|
7219324f47 | ||
|
|
8ea024e06b | ||
|
|
3f8a0b91e6 | ||
|
|
eb7b9146be | ||
|
|
17b5ede88f | ||
|
|
baa28932f9 | ||
|
|
347a9dee7b | ||
|
|
ab4c9322cf | ||
|
|
044380871e | ||
|
|
15c7d8b2af | ||
|
|
ac188369ac | ||
|
|
6ca846fb4d | ||
|
|
8477357c33 | ||
|
|
f19b9d7cfd | ||
|
|
9fc710abf6 | ||
|
|
436f634f86 | ||
|
|
d55591fbbc | ||
|
|
4502af2f85 | ||
|
|
a8349424ba | ||
|
|
42b3157bcd | ||
|
|
64bdb7a086 | ||
|
|
aa7f7e4fdb | ||
|
|
3c1b93f514 | ||
|
|
052d23c90f | ||
|
|
a95b1737ad | ||
|
|
c5e19a32bd | ||
|
|
c5876b25da | ||
|
|
be3c2f5cde | ||
|
|
4063af6ed9 | ||
|
|
9b4a086f15 | ||
|
|
601cbdfb3a | ||
|
|
89d31e9306 | ||
|
|
0fe7a46d47 | ||
|
|
4a349927be | ||
|
|
444cec2d24 | ||
|
|
d888fbe340 | ||
|
|
c977170331 | ||
|
|
cfbdd835fe | ||
|
|
2a98788ca3 | ||
|
|
83ca39b6aa | ||
|
|
6a45bcfc37 | ||
|
|
76197dfec6 | ||
|
|
c13f5a4a7e | ||
|
|
a04ed73cba | ||
|
|
48112e349f | ||
|
|
26f9a60256 | ||
|
|
c9ac4176a0 | ||
|
|
9d1fdae915 | ||
|
|
0aa0a404f9 | ||
|
|
b4ccac21f4 | ||
|
|
09f9f1faa3 | ||
|
|
69e4f6c24c | ||
|
|
4cd14e1334 | ||
|
|
01678161c4 | ||
|
|
ed89fdb732 | ||
|
|
2053aba138 | ||
|
|
98efb89125 | ||
|
|
563ca3fb58 | ||
|
|
b2e96d3901 | ||
|
|
54224c0a61 | ||
|
|
901d00b398 | ||
|
|
c8aab86122 | ||
|
|
dc8c2059e6 | ||
|
|
11f934b74a | ||
|
|
77677d5f90 | ||
|
|
ee69cb4f74 | ||
|
|
1256388109 | ||
|
|
c8161be644 | ||
|
|
3f2a2d0cb6 | ||
|
|
37b4073435 | ||
|
|
95286df0c4 | ||
|
|
69e36c731c | ||
|
|
626ed9532b | ||
|
|
441bf29f16 | ||
|
|
fac57e78b2 | ||
|
|
30365b5d4f | ||
|
|
ecf4ed56f2 | ||
|
|
4dcff67b47 | ||
|
|
4863d054c3 | ||
|
|
e6dba433eb | ||
|
|
adbe6fc3b6 | ||
|
|
a192f7778d | ||
|
|
0632e01f44 | ||
|
|
124342552f | ||
|
|
001c242e74 | ||
|
|
8a83a7c5d9 | ||
|
|
bdb14b3d2d | ||
|
|
84a5404468 | ||
|
|
10f8d5ddc8 | ||
|
|
9367ec709e | ||
|
|
2df7a3cd82 | ||
|
|
a22530baf9 | ||
|
|
412e5ea9d1 | ||
|
|
4859264fe3 | ||
|
|
010faad0ce | ||
|
|
e42840bf81 | ||
|
|
e7e5e5d72a | ||
|
|
80bc2c9825 | ||
|
|
19065ac43e | ||
|
|
05eefdfc5c | ||
|
|
7438d4b9f7 | ||
|
|
78d80334c2 | ||
|
|
418d590668 | ||
|
|
76eef91ff3 | ||
|
|
e9ff6a57da | ||
|
|
fe788964bd | ||
|
|
cc624348f3 | ||
|
|
7ef9edd523 | ||
|
|
8788e82d1c | ||
|
|
8a318256e0 | ||
|
|
909a4eb33a | ||
|
|
984dc056ee | ||
|
|
5778d25ead | ||
|
|
52d12a35fb | ||
|
|
602ee253b8 | ||
|
|
503ccae3ab | ||
|
|
8566cdb7b8 | ||
|
|
6d2c2867c4 | ||
|
|
acd12e637e | ||
|
|
fed42dc885 | ||
|
|
b2037b0fcc | ||
|
|
a0255b6ef8 | ||
|
|
b8019e64af | ||
|
|
e5d000e672 | ||
|
|
802d5ebc00 | ||
|
|
cd8837752f | ||
|
|
5932ee6013 | ||
|
|
ecb6163e82 | ||
|
|
7e51b4c1e6 | ||
|
|
20f1115f50 | ||
|
|
1fd9ab6a9a | ||
|
|
d6edca01c5 | ||
|
|
d472ea98a1 | ||
|
|
d06313a750 | ||
|
|
e386312c95 | ||
|
|
211f325631 | ||
|
|
804b0f9445 | ||
|
|
be9675491b | ||
|
|
e86a650d33 | ||
|
|
65e911e5bc | ||
|
|
8a80ead20c | ||
|
|
01182a288a | ||
|
|
2779501ae9 | ||
|
|
fc4d4645c3 | ||
|
|
9e835246d6 | ||
|
|
670f2a66ad | ||
|
|
a2cb5e9a6c | ||
|
|
d025e9f149 | ||
|
|
58eea96fc2 | ||
|
|
5c4fddefbb | ||
|
|
69c198630c | ||
|
|
9fceea3284 | ||
|
|
20ec00e2fb | ||
|
|
b8b7d5764e | ||
|
|
27ba6c644b | ||
|
|
e32380ea6c | ||
|
|
85755a12e5 | ||
|
|
b232455377 | ||
|
|
e4ad1c14e4 | ||
|
|
718eae73f8 | ||
|
|
2431aa5183 | ||
|
|
93885ec48b | ||
|
|
c7a7022bf3 | ||
|
|
4855daf011 | ||
|
|
49ea60ca8f | ||
|
|
1638cf49d8 | ||
|
|
f82d45fc02 | ||
|
|
46bfc3f4dc | ||
|
|
5bb8cca386 | ||
|
|
a5cebfbd3a | ||
|
|
41e550aadb | ||
|
|
40652bd879 | ||
|
|
03c12b2ede | ||
|
|
284093e3a6 | ||
|
|
ede6e604c0 | ||
|
|
e7c1473d7a | ||
|
|
f5560da26d | ||
|
|
6c96c3473a | ||
|
|
6ffcd51f1a | ||
|
|
26204441bb | ||
|
|
c63a07f928 | ||
|
|
76cc6599a9 | ||
|
|
51ab06f115 | ||
|
|
5ba2f24400 | ||
|
|
1fa253086d | ||
|
|
064d449a0a | ||
|
|
0ad79506fe | ||
|
|
3f162d7b95 | ||
|
|
a4bf18d024 | ||
|
|
06d991df71 | ||
|
|
2ea812f7da | ||
|
|
2a14b6e44e | ||
|
|
35a6e24020 | ||
|
|
aebbf68d73 | ||
|
|
8f962fd739 | ||
|
|
dcae67ee1b | ||
|
|
bac9ceae4f | ||
|
|
0cbf8dc95f | ||
|
|
ce37416cc6 | ||
|
|
52d406dac2 | ||
|
|
d14687b3bc | ||
|
|
32febf9b49 | ||
|
|
fe381f970e | ||
|
|
5df46d54ef | ||
|
|
6f8bfbaed8 | ||
|
|
88670c9ea8 | ||
|
|
0245de8e85 | ||
|
|
6887ac7309 | ||
|
|
ef4415339e | ||
|
|
f4ade44414 | ||
|
|
e518c5263e | ||
|
|
b586273baf | ||
|
|
a4f7c89938 | ||
|
|
55d2c137b4 | ||
|
|
0dcf8c23d4 | ||
|
|
7e48450296 | ||
|
|
0f07aef3b2 | ||
|
|
2d1de31278 | ||
|
|
be1b4a12af | ||
|
|
8ad9bcc04a | ||
|
|
32bcc1165e | ||
|
|
fa9bf99a13 | ||
|
|
8cd66cf336 | ||
|
|
83c3f59330 | ||
|
|
567dfd21d7 | ||
|
|
f7aa17a2fd | ||
|
|
98479a61d8 | ||
|
|
719deda1a4 | ||
|
|
cfad689b63 | ||
|
|
fec12a6fd1 | ||
|
|
29a66c7393 | ||
|
|
33331799a2 | ||
|
|
6a516efc1b | ||
|
|
e4f018226b | ||
|
|
c229d34c03 | ||
|
|
cb78831b4e | ||
|
|
7d92b1b8e6 | ||
|
|
3505417989 | ||
|
|
fd963b16e0 | ||
|
|
89b5e340e9 | ||
|
|
202072321c | ||
|
|
1f8562b96d | ||
|
|
e0d585b1d2 | ||
|
|
b39fd27636 | ||
|
|
c6629af897 | ||
|
|
36896c939b | ||
|
|
996e107073 | ||
|
|
f345d8ddc0 | ||
|
|
92e7535972 | ||
|
|
68828d2b49 | ||
|
|
2a94699d79 | ||
|
|
19ca5b6020 | ||
|
|
8d0ac17d57 | ||
|
|
e4d680448f | ||
|
|
2d1fe4f8a5 | ||
|
|
cd6aedc8f2 | ||
|
|
b43cf970ff | ||
|
|
74a8cc8c90 | ||
|
|
4df5019b7e | ||
|
|
24bc3668a6 | ||
|
|
9aa32423b0 | ||
|
|
6d4f54c4ec | ||
|
|
bc4918ee0d | ||
|
|
bd09818898 | ||
|
|
6abdea44db | ||
|
|
4dc1431179 | ||
|
|
705aa84c5a | ||
|
|
6a0e633353 | ||
|
|
2a26eb4e52 | ||
|
|
7e039f7fcf | ||
|
|
317819319c | ||
|
|
9b0f00f075 | ||
|
|
2297a06a14 | ||
|
|
fdc08d15a9 | ||
|
|
66955b18cd | ||
|
|
9259cfc56e | ||
|
|
4ae3eed03e | ||
|
|
965c5197fa | ||
|
|
b0454837d2 | ||
|
|
b5cf14949d | ||
|
|
1c5e43370c | ||
|
|
d1f5682baa | ||
|
|
dc51521927 | ||
|
|
6c87df4c7a | ||
|
|
10cf2266fa | ||
|
|
a00c64681d | ||
|
|
3cedb8fc25 | ||
|
|
a4e3ec5f45 | ||
|
|
dade8f4495 | ||
|
|
cc68813523 | ||
|
|
fe5adccc29 | ||
|
|
7c4e7fe077 | ||
|
|
4112f45c48 | ||
|
|
41f08de173 | ||
|
|
e28d19d031 | ||
|
|
5203b940d0 | ||
|
|
015034c6e9 | ||
|
|
49475ade58 | ||
|
|
5ce4be4d5c | ||
|
|
7e9facdc80 | ||
|
|
ef113e15a0 | ||
|
|
189970a841 | ||
|
|
8d0b15050e | ||
|
|
0dd1e52429 | ||
|
|
1f2b9d3c14 | ||
|
|
cc58c24d76 | ||
|
|
9f77dd5b59 | ||
|
|
a82a1baf23 | ||
|
|
c1d0bca70f | ||
|
|
d6d1691559 | ||
|
|
258d0c0884 | ||
|
|
2e1136eef3 | ||
|
|
c5a35af3fb | ||
|
|
3658693588 | ||
|
|
c3ba5aaca8 | ||
|
|
c2746101ab | ||
|
|
280a1e7b0f | ||
|
|
b8e79dc360 | ||
|
|
62e375bc71 | ||
|
|
92e8ffb658 | ||
|
|
3ad0abfc67 | ||
|
|
70b977a897 | ||
|
|
c9273fa383 | ||
|
|
9e9274d9b2 | ||
|
|
55027e3ac3 | ||
|
|
2e86b8b5e4 | ||
|
|
8972a4c9f1 | ||
|
|
48b07eaa3d | ||
|
|
947edcba9a |
@@ -5,5 +5,6 @@ notifications:
|
||||
recipients:
|
||||
- jose.valim@plataformatec.com.br
|
||||
otp_release:
|
||||
- R15B02
|
||||
- R15B01
|
||||
- R15B
|
||||
|
||||
+91
-42
@@ -1,52 +1,101 @@
|
||||
# v0.7.0 (2012-10-20)
|
||||
|
||||
* enhancements
|
||||
* [Behaviour] Add Behaviour with a simple callback DSL to define callbacks
|
||||
* [Binary] Add a Dict binary that converts its keys to binaries on insertion
|
||||
* [Binary] Optimize `Binary.Inspect` and improve inspect for floats
|
||||
* [CLI] Support `--detached` option
|
||||
* [Code] `Code.string_to_ast` supports `:existing_atoms_only` as an option in order to guarantee no new atoms is generated when parsing the code
|
||||
* [EEx] Support `<%%` and `<%#` tags
|
||||
* [ExUnit] Support `after_spawn` callbacks which are invoked after each process is spawned
|
||||
* [ExUnit] Support context data in `setup_all`, `setup`, `teardown` and `teardown_all` callbacks
|
||||
* [IEx] Support `after_spawn` callbacks which are invoked after each process is spawned
|
||||
* [Kernel] Better error messages when invalid options are given to `import`, `alias` or `require`
|
||||
* [Kernel] Allow partial application on literals, for example: `{ &1, &2 }` to build tuples or `[&1|&2]` to build cons cells
|
||||
* [Kernel] Added `integer_to_binary` and `binary_to_integer`
|
||||
* [Kernel] Added `float_to_binary` and `binary_to_float`
|
||||
* [Kernel] Many improvements to `unquote` and `unquote_splicing`. For example, `unquote(foo).unquote(bar)(args)` is supported and no longer need to be written via `apply`
|
||||
* [Keyword] Keyword list is no longer ordered according to Erlang terms but the order in which they are specified
|
||||
* [List] Add `List.keyreplace` and `List.keystore`
|
||||
* [Macro] Support `Macro.safe_term` which returns `:ok` if an expression does not execute code and is made only of raw data types
|
||||
* [Mix] Add support for environments - the current environment can be set via `MIX_ENV`
|
||||
* [Mix] Add support for handling and fetching dependencies' dependencies
|
||||
* [Module] Support module creation via `Module.create`
|
||||
* [Range] Support decreasing ranges
|
||||
* [Record] Improvements to the Record API, added `Record.defmacros`
|
||||
* [Regex] Add `:return` option to `Regex.run` and `Regex.scan`
|
||||
* [String] Add a String module responsible for handling UTf-8 binaries
|
||||
|
||||
* bug fix
|
||||
* [File] `File.cp` and `File.cp_r` now preserves the file's mode
|
||||
* [IEx] Fix a bug where printing to `:stdio` on `IEx` was causing it to hang
|
||||
* [Macro] Fix a bug where quoted expressions were not behaving the same as their non-quoted counterparts
|
||||
* [Mix] `mix deps.get [DEPS]` now only gets the specified dependencies
|
||||
* [Mix] Mix now exits with status 1 in case of failures
|
||||
* [Protocol] Avoid false positives on protocol dispatch (a bug caused the dispatch to be triggered to an invalid protocol)
|
||||
|
||||
* backwards incompatible changes
|
||||
* [ExUnit] `setup` and `teardown` callbacks now receives the test name as second argument
|
||||
* [Kernel] Raw function definition with `def/4`, `defp/4`, `defmacro/4`, `defmacrop/4` now evaluates all arguments. The previous behaviour was accidental and did not properly evaluate all arguments
|
||||
* [Kernel] Change tuple-related (`elem` and `setelem`), Enum functions (`find_index`, `nth!` and `times`) and List functions (List.key*) to zero-index
|
||||
|
||||
* deprecations
|
||||
* [Code] `Code.require_file` and `Code.load_file` now expect the full name as argument
|
||||
* [Enum] `List.reverse/1` and `List.zip/2` were moved to `Enum`
|
||||
* [GenServer] Rename `GenServer.Behavior` to `GenServer.Behaviour`
|
||||
* [Kernel] Bitstring syntax now uses `::` instead of `|`
|
||||
* [Kernel] `Erlang.` syntax is deprecated in favor of simply using atoms
|
||||
* [Module] `Module.read_attribute` and `Module.add_attribute` deprecated in favor of `Module.get_attribute` and `Module.put_attribute` which mimics Dict API
|
||||
|
||||
# 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;
|
||||
* [Kernel] Compile 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__`
|
||||
* [Kernel] Comprehensions syntax changed to be more compatible with Erlang behavior
|
||||
* [Kernel] loop and recur are removed in favor of recursion with named functions
|
||||
* [Module] Removed data functions in favor of unifying the attributes API
|
||||
|
||||
* 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()`;
|
||||
* [Access] The semantics of the access protocol were reduced from a broad query API to simple data structure key-based access
|
||||
* [ExUnit] Some assertions are deprecated in favor of simply using `assert()`
|
||||
* [File] `File.read_info` is deprecated in favor of `File.stat`
|
||||
* [IO] `IO.print` is deprecated in favor of `IO.write`
|
||||
* [Kernel] Deprecate `__LINE__` and `__FUNCTION__` in favor of `__ENV__.line` and `__ENV__.function`
|
||||
* [Kernel] Deprecate `in_guard` in favor of `__CALLER__.in_guard?`
|
||||
* [Kernel] `refer` is deprecated in favor of `alias`
|
||||
* [Module] `Module.add_compile_callback(module, target, callback)` is deprecated in favor of `Module.put_attribute(module, :before_compile, { target, callback })`
|
||||
* [Module] `Module.function_defined?` is deprecated in favor of `Module.defines?`
|
||||
* [Module] `Module.defined_functions` is deprecated in favor of `Module.definitions_in`
|
||||
|
||||
* 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;
|
||||
* [Enum] Enhance Enum protocol to support `Enum.count`
|
||||
* [Enum] Optimize functions when a list is given as collection
|
||||
* [Enum] Add `find_index`, `nth!` and others
|
||||
* [ExUnit] Support setup and teardown callbacks
|
||||
* [IEx] IEx now provides autocomplete if the OS supports tty
|
||||
* [IEx] IEx now supports remsh
|
||||
* [IEx] Elixir now defaults to compile with documentation and `d` can be used in IEx to print modules and functions documentation
|
||||
* [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
|
||||
* [IO/File] Many improvements to `File` and `IO` modules
|
||||
* [Kernel] Operator `!` is now allowed in guard clauses
|
||||
* [Kernel] Introduce 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
|
||||
* [Kernel] quote special form now supports line and unquote as options
|
||||
* [Kernel] Document the macro `@` and allow attributes to be read inside functions
|
||||
* [Kernel] Add 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] Add `__ENV__` which returns a `Macro.Env` record with information about the compilation environment
|
||||
* [Kernel] Add `__CALLER__` inside macros which returns a `Macro.Env` record with information about the calling site
|
||||
* [Macro] Add `Macro.expand`, useful for debugging what a macro expands to
|
||||
* [Mix] First Mix public release
|
||||
* [Module] Add 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
|
||||
* [OptionParser] Make OptionParser public, add support to flags and improved switch parsing
|
||||
* [Range] Add a Range module with support to `in` operator (`x in 1..3`) and iterators
|
||||
* [Record] Allow `Record[_: value]` to set a default value to all records fields, as in Erlang
|
||||
* [Record] Records now provide a `to_keywords` function
|
||||
* [Regex] Back references are now properly supported
|
||||
* [System] Add `System.find_executable`
|
||||
|
||||
# v0.5.0 (2012-05-24)
|
||||
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
REBAR:=$(shell which rebar || echo `pwd`/rebar)
|
||||
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
|
||||
VERSION:=0.7.0
|
||||
|
||||
.PHONY: 1
|
||||
.NOTPARALLEL: compile
|
||||
@@ -21,7 +21,7 @@ lib/$(1)/ebin/Elixir-$(2).beam: $(wildcard lib/$(1)/lib/*.ex) $(wildcard lib/$(1
|
||||
|
||||
test_$(1): $(1)
|
||||
@ echo "==> $(1) (exunit)"
|
||||
@ cd lib/$(1) && time ../../bin/elixir -r "test/test_helper.exs" -pr "test/**/*_test.exs"
|
||||
@ cd lib/$(1) && time ../../bin/elixir -r "test/test_helper.exs" -pr "test/**/*_test.exs";
|
||||
endef
|
||||
|
||||
#==> Compilation tasks
|
||||
@@ -38,7 +38,7 @@ erlang:
|
||||
|
||||
# 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
|
||||
elixir: kernel lib/eex/ebin/Elixir-EEx.beam mix ex_unit eex iex
|
||||
|
||||
kernel: $(KERNEL)
|
||||
$(KERNEL): lib/elixir/lib/*.ex lib/elixir/lib/*/*.ex $(FORCE)
|
||||
@@ -55,11 +55,16 @@ $(KERNEL): lib/elixir/lib/*.ex lib/elixir/lib/*/*.ex $(FORCE)
|
||||
$(eval $(call TASK_TEMPLATE,ex_unit,ExUnit))
|
||||
$(eval $(call TASK_TEMPLATE,eex,EEx))
|
||||
$(eval $(call TASK_TEMPLATE,mix,Mix))
|
||||
$(eval $(call TASK_TEMPLATE,iex,IEx))
|
||||
|
||||
clean:
|
||||
@ rm -rf .full
|
||||
@ rm -rf lib/*/ebin
|
||||
@ cd lib/elixir && $(REBAR) clean
|
||||
rm -rf .full
|
||||
rm -rf ebin
|
||||
rm -rf lib/*/ebin
|
||||
rm -rf lib/*/test/tmp
|
||||
rm -rf lib/mix/test/fixtures/git_repo
|
||||
rm -rf lib/mix/tmp
|
||||
|
||||
#==> Release tasks
|
||||
$(FULLFLAG): $(wildcard lib/*/ebin/*)
|
||||
@@ -93,11 +98,11 @@ test_erlang: compile
|
||||
@ 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
|
||||
@ time $(ERL) -pa lib/elixir/test/ebin -s test_helper test -s erlang halt;
|
||||
@ echo
|
||||
|
||||
test_elixir: test_kernel test_mix test_ex_unit test_eex
|
||||
test_elixir: test_kernel test_mix test_ex_unit test_eex test_iex
|
||||
|
||||
test_kernel: compile
|
||||
@ echo "==> kernel (exunit)"
|
||||
@ cd lib/elixir && time ../../bin/elixir -r "test/elixir/test_helper.exs" -pr "test/elixir/**/*_test.exs"
|
||||
@ cd lib/elixir && time ../../bin/elixir -r "test/elixir/test_helper.exs" -pr "test/elixir/**/*_test.exs";
|
||||
|
||||
+6
-4
@@ -8,13 +8,15 @@ This document simply outlines the release process:
|
||||
|
||||
3) Ensure CHANGELOG is updated and tag release version with timestamp in it
|
||||
|
||||
4) Commit changes above and tag new version on Git
|
||||
4) Commit changes above and update stable branch
|
||||
|
||||
5) Release new docs, update elixir-lang.org
|
||||
5) Create tag from stable branch
|
||||
|
||||
6) Push new zip to Elixir's downloads page
|
||||
6) Release new docs, update elixir-lang.org
|
||||
|
||||
7) After release, bump versions and add .dev back
|
||||
7) Push new zip to Elixir's downloads page
|
||||
|
||||
8) After release, bump versions and add .dev back
|
||||
|
||||
## Places where version is mentioned
|
||||
|
||||
|
||||
+8
-2
@@ -13,6 +13,7 @@ if [ $# -eq 0 ]; then
|
||||
--name \"name\" Makes and assigns a name to the distributed node
|
||||
--sname \"name\" Makes and assigns a short name to the distributed node
|
||||
--remsh \"name\" Connects to a node using a remote shell (with iex)
|
||||
--detached Starts the Erlang VM detached from console
|
||||
--no-halt Does not halt the Erlang VM after execution
|
||||
|
||||
** Options marked with (*) can be given more than once
|
||||
@@ -43,6 +44,9 @@ while [ $I -le $# ]; do
|
||||
-e|-r|-pr|-pa|-pz|--remsh|-S)
|
||||
S=2
|
||||
;;
|
||||
--detached)
|
||||
ERL="$ERL `echo $PEEK | cut -c 2-`"
|
||||
;;
|
||||
--sname|--name)
|
||||
I=$(expr $I + 1)
|
||||
eval "VAL=\${$I}"
|
||||
@@ -63,9 +67,11 @@ done
|
||||
SELF=$(readlink_f "$0")
|
||||
SCRIPT_PATH=$(dirname "$SELF")
|
||||
|
||||
if [ -f "$HOME/.elixirrc" ]; then . "$HOME/.elixirrc"; fi
|
||||
|
||||
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 "$@"
|
||||
exec "$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 "$@"
|
||||
exec erl -env ERL_LIBS $ERL_LIBS:"$SCRIPT_PATH/../lib" -noshell $ELIXIR_ERL_OPTS $ERL -s elixir start_cli -extra "$@"
|
||||
fi
|
||||
|
||||
+1
-1
@@ -24,4 +24,4 @@ readlink_f () {
|
||||
|
||||
SELF=$(readlink_f "$0")
|
||||
SCRIPT_PATH=$(dirname "$SELF")
|
||||
"$SCRIPT_PATH"/elixir --compile "$@"
|
||||
exec "$SCRIPT_PATH"/elixir --compile "$@"
|
||||
|
||||
@@ -11,4 +11,4 @@ readlink_f () {
|
||||
|
||||
SELF=$(readlink_f "$0")
|
||||
SCRIPT_PATH=$(dirname "$SELF")
|
||||
"$SCRIPT_PATH"/elixir --no-halt -e "IEx.cli" "$@"
|
||||
exec "$SCRIPT_PATH"/elixir --no-halt -e "IEx.cli" "$@"
|
||||
@@ -41,6 +41,8 @@ defmodule EEx do
|
||||
|
||||
<% Elixir expression - inline with output %>
|
||||
<%= Elixir expression - replace with result %>
|
||||
<%% EEx quotation - returns the contents inside %>
|
||||
<%# Comments - they are discarded from source %>
|
||||
|
||||
All expressions that output something to the template
|
||||
**must** use the equals sign (`=`). Since everything in
|
||||
|
||||
@@ -23,7 +23,7 @@ defmodule EEx.Compiler do
|
||||
end
|
||||
|
||||
defp generate_buffer([{ :expr, line, mark, chars }|t], buffer, scope, state) do
|
||||
expr = maybe_block Erlang.elixir_translator.forms(chars, line, state.file)
|
||||
expr = maybe_block :elixir_translator.forms!(chars, line, state.file, [])
|
||||
buffer = state.engine.handle_expr(buffer, mark, expr)
|
||||
generate_buffer(t, buffer, scope, state)
|
||||
end
|
||||
@@ -41,7 +41,7 @@ defmodule EEx.Compiler do
|
||||
|
||||
defp generate_buffer([{ :end_expr, line, _, chars }|t], buffer, [current|_], state) do
|
||||
{ wrapped, state } = wrap_expr(current, line, buffer, chars, state)
|
||||
tuples = maybe_block Erlang.elixir_translator.forms(wrapped, state.start_line, state.file)
|
||||
tuples = maybe_block :elixir_translator.forms!(wrapped, state.start_line, state.file, [])
|
||||
buffer = insert_quotes(tuples, state.dict)
|
||||
{ buffer, t }
|
||||
end
|
||||
@@ -95,7 +95,7 @@ defmodule EEx.Compiler do
|
||||
# Changes placeholder to real expression
|
||||
|
||||
defp insert_quotes({ :__EEX__, _, [key] }, dict) do
|
||||
{ ^key, value } = List.keyfind dict, key, 1
|
||||
{ ^key, value } = List.keyfind dict, key, 0
|
||||
value
|
||||
end
|
||||
|
||||
|
||||
@@ -37,9 +37,8 @@ defmodule EEx.Engine do
|
||||
"""
|
||||
def handle_expr(buffer, '=', expr) do
|
||||
quote do
|
||||
tmp_1 = unquote(buffer)
|
||||
tmp_2 = to_binary(unquote(expr))
|
||||
tmp_1 <> tmp_2
|
||||
tmp = unquote(buffer)
|
||||
tmp <> to_binary(unquote(expr))
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -16,16 +16,26 @@ defmodule EEx.Tokenizer do
|
||||
end
|
||||
|
||||
def tokenize(list, line) do
|
||||
List.reverse(tokenize(list, line, line, [], []))
|
||||
Enum.reverse(tokenize(list, line, line, [], []))
|
||||
end
|
||||
|
||||
defp tokenize([?<,?%|t], current_line, line, buffer, acc) do
|
||||
defp tokenize('<%%' ++ t, current_line, line, buffer, acc) do
|
||||
{ buffer, new_line, rest } = tokenize_expr t, line, [?%,?<|buffer]
|
||||
tokenize rest, current_line, new_line, [?>,?%|buffer], acc
|
||||
end
|
||||
|
||||
defp tokenize('<%#' ++ t, current_line, line, buffer, acc) do
|
||||
{ _, new_line, rest } = tokenize_expr t, line, []
|
||||
tokenize rest, current_line, new_line, buffer, acc
|
||||
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) }
|
||||
final = { token, line, marker, Enum.reverse(expr) }
|
||||
tokenize rest, new_line, new_line, [], [final | acc]
|
||||
end
|
||||
|
||||
@@ -65,8 +75,6 @@ defmodule EEx.Tokenizer 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
|
||||
@@ -87,16 +95,16 @@ defmodule EEx.Tokenizer do
|
||||
end
|
||||
|
||||
defp token_name('>-' ++ rest) do
|
||||
rest = List.reverse(rest)
|
||||
rest = Enum.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
|
||||
# Tokenize the remaining passing check_terminators as
|
||||
# false, 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, file: "eex", check_terminators: false) do
|
||||
{ :ok, tokens } ->
|
||||
tokens = List.reverse(tokens)
|
||||
tokens = Enum.reverse(tokens)
|
||||
fn_index = fn_index(tokens)
|
||||
|
||||
if fn_index && end_index(tokens) > fn_index do
|
||||
@@ -146,6 +154,6 @@ defmodule EEx.Tokenizer do
|
||||
end
|
||||
|
||||
defp tokenize_text(line, buffer, acc) do
|
||||
[{ :text, line, list_to_binary(List.reverse(buffer)) } | acc]
|
||||
[{ :text, line, list_to_binary(Enum.reverse(buffer)) } | acc]
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
Code.require_file "../../test_helper", __FILE__
|
||||
Code.require_file "../../test_helper.exs", __FILE__
|
||||
|
||||
defmodule EEx.SmartEngineTest do
|
||||
use ExUnit.Case, async: true
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
Code.require_file "../../test_helper", __FILE__
|
||||
Code.require_file "../../test_helper.exs", __FILE__
|
||||
|
||||
defmodule EEx.TokenizerTest do
|
||||
use ExUnit.Case, async: true
|
||||
@@ -41,6 +41,30 @@ baz %>
|
||||
]
|
||||
end
|
||||
|
||||
test "quotation" do
|
||||
assert T.tokenize('foo <%% true %>', 1) == [
|
||||
{ :text, 1, "foo <% true %>" }
|
||||
]
|
||||
end
|
||||
|
||||
test "quotation with do/end" do
|
||||
assert T.tokenize('foo <%% true do %>bar<%% end %>', 1) == [
|
||||
{ :text, 1, "foo <% true do %>bar<% end %>" }
|
||||
]
|
||||
end
|
||||
|
||||
test "comments" do
|
||||
assert T.tokenize('foo <%# true %>', 1) == [
|
||||
{ :text, 1, "foo " }
|
||||
]
|
||||
end
|
||||
|
||||
test "comments with do/end" do
|
||||
assert T.tokenize('foo <%# true do %>bar<%# end %>', 1) == [
|
||||
{ :text, 1, "foo bar" }
|
||||
]
|
||||
end
|
||||
|
||||
test "strings with embedded do end" do
|
||||
assert T.tokenize('foo <% if true do %>bar<% end %>', 1) == [
|
||||
{ :text, 1, "foo " },
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
Code.require_file "../test_helper", __FILE__
|
||||
Code.require_file "../test_helper.exs", __FILE__
|
||||
|
||||
require EEx
|
||||
|
||||
@@ -33,7 +33,7 @@ defmodule EExText.Compiled do
|
||||
|
||||
defp fill_in_stacktrace do
|
||||
try do
|
||||
Erlang.erlang.error "failed"
|
||||
:erlang.error "failed"
|
||||
catch
|
||||
:error, _, stack -> stack
|
||||
end
|
||||
|
||||
@@ -1,2 +1 @@
|
||||
# Configure ExUnit, no options supported yet.
|
||||
ExUnit.start []
|
||||
@@ -18,10 +18,10 @@
|
||||
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
|
||||
vars=[], %% a dict of defined variables and their alias
|
||||
temp_vars=[], %% a dict of all variables defined in a particular assign
|
||||
clause_vars=[], %% a dict of all variables defined in a particular clause
|
||||
quote_vars=[], %% a dict of all quoted variables
|
||||
extra_guards=nil, %% extra guards from args expansion
|
||||
counter=0, %% a counter for the variables defined
|
||||
file=(<<"nofile">>), %% the current scope filename
|
||||
|
||||
@@ -8,7 +8,7 @@ defprotocol Access do
|
||||
invokes `Access.access` protocol.
|
||||
|
||||
This protocol is limited and is implemented only for the
|
||||
following built-in types: keywords, tuples, atoms and
|
||||
following built-in types: keywords, records, atoms and
|
||||
functions.
|
||||
"""
|
||||
|
||||
@@ -22,7 +22,7 @@ end
|
||||
|
||||
defimpl Access, for: List do
|
||||
@doc """
|
||||
Access the given key in a keywords list.
|
||||
Access the given key in a keyword list.
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -32,13 +32,9 @@ defimpl Access, for: List do
|
||||
"""
|
||||
|
||||
def access(list, atom) when is_atom(atom) do
|
||||
atom_access(list, atom)
|
||||
Keyword.get(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
|
||||
@@ -47,6 +43,9 @@ defimpl Access, for: Atom do
|
||||
at compilation time. If we reach this, we should raise
|
||||
an exception.
|
||||
"""
|
||||
def access(nil, _) do
|
||||
nil
|
||||
end
|
||||
def access(atom, _) do
|
||||
raise "The access protocol can only be invoked for atoms at " <>
|
||||
"compilation time, tried to invoke it for #{inspect atom}"
|
||||
|
||||
@@ -0,0 +1,89 @@
|
||||
defmodule Behaviour do
|
||||
@moduledoc """
|
||||
A convenience module for defining behaviours.
|
||||
Behaviours can be referenced by other modules
|
||||
in order to ensure they implement the proper
|
||||
callbacks.
|
||||
|
||||
For example, you can specify the `URI.Parser`
|
||||
behaviour as follow:
|
||||
|
||||
defmodule URI.Parser do
|
||||
use Behaviour
|
||||
|
||||
@doc "Parses the given URL"
|
||||
defcallback parse(arg)
|
||||
|
||||
@doc "Defines a default port"
|
||||
defcallback default_port()
|
||||
end
|
||||
|
||||
And then a specific protocol may use it as:
|
||||
|
||||
defmodule URI.HTTP do
|
||||
@behaviour URI.Parser
|
||||
def default_port(), do: 80
|
||||
def parse(info), do: info
|
||||
end
|
||||
|
||||
In case the behaviour changes or URI.HTTP does
|
||||
not implement one of the callbacks, a warning
|
||||
will be raised.
|
||||
|
||||
## Implementation
|
||||
|
||||
Internally, Erlang call `behaviour_info(:callbacks)`
|
||||
to obtain all functions that a behaviour should
|
||||
implemented. Therefore, all this module does is
|
||||
to define `behaviour_info(:callbacks)` with the
|
||||
`defcallback` definitions.
|
||||
"""
|
||||
|
||||
@doc """
|
||||
Annotates the given function is a callback. `defcallback` is
|
||||
slightly different than simple using `def` because, even if
|
||||
`defcallback` contains default values, a default function
|
||||
won't be generated, which would happen with `def`.
|
||||
"""
|
||||
defmacro defcallback(fun) do
|
||||
{ name, args } = :elixir_clauses.extract_args(fun)
|
||||
length = length(args)
|
||||
|
||||
{ args, defaults } = Enum.map_reduce(args, 0, function do
|
||||
{ ://, _, [expr, _] }, acc ->
|
||||
{ expr, acc + 1 }
|
||||
expr, acc ->
|
||||
{ expr, acc }
|
||||
end)
|
||||
|
||||
attributes = Enum.map (length - defaults)..length, fn(i) ->
|
||||
quote do
|
||||
@__behaviour_callbacks { unquote(name), unquote(i) }
|
||||
end
|
||||
end
|
||||
|
||||
quote do
|
||||
__block__ unquote(attributes)
|
||||
def unquote(name)(unquote_splicing(args))
|
||||
end
|
||||
end
|
||||
|
||||
@doc false
|
||||
defmacro __using__(_) do
|
||||
quote do
|
||||
Module.register_attribute(__MODULE__, :__behaviour_callbacks, accumulate: true)
|
||||
@before_compile unquote(__MODULE__)
|
||||
import unquote(__MODULE__)
|
||||
end
|
||||
end
|
||||
|
||||
@doc false
|
||||
defmacro __before_compile__(_) do
|
||||
quote do
|
||||
@doc false
|
||||
def behaviour_info(:callbacks) do
|
||||
@__behaviour_callbacks
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -1,218 +0,0 @@
|
||||
defmodule Binary do
|
||||
@moduledoc """
|
||||
Functions for working with binaries.
|
||||
"""
|
||||
|
||||
@doc %B"""
|
||||
Receives a char list and escapes all special chars (like \n)
|
||||
and interpolation markers. A last argument is given and wraps
|
||||
the whole char list given.
|
||||
|
||||
## Examples
|
||||
|
||||
Binary.escape "foo", ?'
|
||||
#=> "'foo'"
|
||||
|
||||
"""
|
||||
def escape(other, char) do
|
||||
<<char>> <> do_escape(other, char)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Check if a binary is printable considering it is encoded
|
||||
as UTF-8. Returns true if so, false otherwise.
|
||||
|
||||
## Examples
|
||||
|
||||
Binary.printable?("abc") #=> true
|
||||
|
||||
"""
|
||||
|
||||
# Allow basic ascii chars
|
||||
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 in 160..191 do
|
||||
printable?(t)
|
||||
end
|
||||
|
||||
# From 16#C0 to 16#7FF
|
||||
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 in 224..236 and
|
||||
o1 >= 128 and o1 < 192 and o2 >= 128 and o2 < 192 do
|
||||
printable?(t)
|
||||
end
|
||||
|
||||
# From 16#D000 to 16#D7FF
|
||||
def printable?(<<237, o1, o2, t|:binary>>) when
|
||||
o1 >= 128 and o1 < 160 and o2 >= 128 and o2 < 192 do
|
||||
printable?(t)
|
||||
end
|
||||
|
||||
# Reject 16#FFFF and 16#FFFE
|
||||
def printable?(<<239, 191, o>>) when o == 190 or o == 191 do
|
||||
false
|
||||
end
|
||||
|
||||
# From 16#E000 to 16#EFFF
|
||||
def printable?(<<m, o1, o2, t|:binary>>) when (m == 238 or m == 239) and
|
||||
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 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 in 144..191 and o2 in 128..191 and o3 in 128..191 do
|
||||
printable?(t)
|
||||
end
|
||||
|
||||
# Reject 16#110000 onwards
|
||||
def printable?(<<244, o1, _, _, _|:binary>>) when o1 >= 144 do
|
||||
false
|
||||
end
|
||||
|
||||
# From 16#4000 to 16#10FFFF
|
||||
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
|
||||
|
||||
def printable?(<<?\n, t|:binary>>), do: printable?(t)
|
||||
def printable?(<<?\r, t|:binary>>), do: printable?(t)
|
||||
def printable?(<<?\t, t|:binary>>), do: printable?(t)
|
||||
def printable?(<<?\v, t|:binary>>), do: printable?(t)
|
||||
def printable?(<<?\b, t|:binary>>), do: printable?(t)
|
||||
def printable?(<<?\f, t|:binary>>), do: printable?(t)
|
||||
def printable?(<<?\e, t|:binary>>), do: printable?(t)
|
||||
|
||||
def printable?(<<>>), do: true
|
||||
def printable?(_), do: false
|
||||
|
||||
@doc %B"""
|
||||
Unescape the given chars. The unescaping is driven by the same
|
||||
rules as single- and double-quoted strings. Check `unescape/2`
|
||||
for information on how to customize the escaping map.
|
||||
|
||||
In this setup, Elixir will escape the following: `\b`, `\d`,
|
||||
`\e`, `\f`, `\n`, `\r`, `\s`, `\t` and `\v`. Octals are also
|
||||
escaped according to the latin1 set they represent.
|
||||
|
||||
## Examples
|
||||
|
||||
Binary.unescape "example\\n"
|
||||
#=> "example\n"
|
||||
|
||||
In the example above, we pass a string with `\n` escaped
|
||||
and we return a version with it unescaped.
|
||||
"""
|
||||
def unescape(chars) do
|
||||
Erlang.elixir_interpolation.unescape_chars(chars)
|
||||
end
|
||||
|
||||
@doc %B"""
|
||||
Unescape the given chars according to the map given.
|
||||
Check `unescape/1` if you want to use the same map as Elixir
|
||||
single- and double-quoted strings.
|
||||
|
||||
## Map
|
||||
|
||||
The map must be a function. The function receives an integer
|
||||
representing the number of the characters it wants to unescape.
|
||||
Here is the default mapping function implemented by Elixir:
|
||||
|
||||
def unescape_map(?b), do: ?\b
|
||||
def unescape_map(?d), do: ?\d
|
||||
def unescape_map(?e), do: ?\e
|
||||
def unescape_map(?f), do: ?\f
|
||||
def unescape_map(?n), do: ?\n
|
||||
def unescape_map(?r), do: ?\r
|
||||
def unescape_map(?s), do: ?\s
|
||||
def unescape_map(?t), do: ?\t
|
||||
def unescape_map(?v), do: ?\v
|
||||
def unescape_map(e), do: e
|
||||
|
||||
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:
|
||||
|
||||
Binary.unescape "example\\n", unescape_map(&1)
|
||||
|
||||
"""
|
||||
def unescape(chars, map) do
|
||||
Erlang.elixir_interpolation.unescape_chars(chars, map)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Unescape the given tokens according to the default map.
|
||||
Check `unescape/1` and `unescape/2` for more information
|
||||
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 `Kernel.__b__` for examples.
|
||||
"""
|
||||
def unescape_tokens(tokens) do
|
||||
Erlang.elixir_interpolation.unescape_tokens(tokens)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Unescape the given tokens according to the given map.
|
||||
Check `unescape_tokens/1` and `unescaped/2` for more information.
|
||||
"""
|
||||
def unescape_tokens(tokens, map) do
|
||||
Erlang.elixir_interpolation.unescape_tokens(tokens, map)
|
||||
end
|
||||
|
||||
## Helpers
|
||||
|
||||
defp do_escape(<<char, t|:binary>>, char) do
|
||||
<<?\\, char, do_escape(t, char)|:binary>>
|
||||
end
|
||||
|
||||
defp do_escape(<<h, t|:binary>>, char) when
|
||||
h == ?# or h == ?\b or
|
||||
h == ?\d or h == ?\e or
|
||||
h == ?\f or h == ?\n or
|
||||
h == ?\r or h == ?\\ or
|
||||
h == ?\t or h == ?\v do
|
||||
<<?\\, escape_map(h), do_escape(t, char)|:binary>>
|
||||
end
|
||||
|
||||
defp do_escape(<<h, t|:binary>>, char) do
|
||||
<<h, do_escape(t,char)|:binary>>
|
||||
end
|
||||
|
||||
defp do_escape(<<>>, char) do
|
||||
<<char>>
|
||||
end
|
||||
|
||||
defp escape_map(?#), do: ?#
|
||||
defp escape_map(?\b), do: ?b
|
||||
defp escape_map(?\d), do: ?d
|
||||
defp escape_map(?\e), do: ?e
|
||||
defp escape_map(?\f), do: ?f
|
||||
defp escape_map(?\n), do: ?n
|
||||
defp escape_map(?\r), do: ?r
|
||||
defp escape_map(?\\), do: ?\\
|
||||
defp escape_map(?\t), do: ?t
|
||||
defp escape_map(?\v), do: ?v
|
||||
end
|
||||
@@ -0,0 +1,81 @@
|
||||
defmodule Binary.Dict do
|
||||
@moduledoc """
|
||||
This module implements a dictionary that forces the keys to be
|
||||
converted to binaries on insertion. Currently it is implemented
|
||||
using an `OrdDict`, but this may change in the future.
|
||||
|
||||
Check the `Dict` module for examples and documentation.
|
||||
"""
|
||||
|
||||
use Dict.Common
|
||||
|
||||
defmacrop dict(data) do
|
||||
quote do
|
||||
{ Binary.Dict, 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 to_binary(key), data
|
||||
end
|
||||
|
||||
def get(dict(data), key, default) do
|
||||
case :orddict.find(to_binary(key), data) do
|
||||
{:ok, value} -> value
|
||||
:error -> default
|
||||
end
|
||||
end
|
||||
|
||||
def put(dict(data), key, value) do
|
||||
dict(:orddict.store to_binary(key), value, data)
|
||||
end
|
||||
|
||||
def delete(dict(data), key) do
|
||||
dict(:orddict.erase to_binary(key), data)
|
||||
end
|
||||
|
||||
def merge(dict(d1), dict(d2), fun) do
|
||||
dict(:orddict.merge fun, d1, d2)
|
||||
end
|
||||
|
||||
def merge(dict(_) = d1, d2, fun) do
|
||||
merge(d1, new(d2), fun)
|
||||
end
|
||||
|
||||
def update(dict(data), key, fun) do
|
||||
dict(:orddict.update to_binary(key), fun, data)
|
||||
end
|
||||
|
||||
def update(dict(data), key, initial, fun) do
|
||||
dict(:orddict.update to_binary(key), fun, initial, data)
|
||||
end
|
||||
|
||||
def empty(_) do
|
||||
dict([])
|
||||
end
|
||||
|
||||
def to_list(dict(data)) do
|
||||
data
|
||||
end
|
||||
end
|
||||
|
||||
defimpl Enum.Iterator, for: Binary.Dict do
|
||||
def iterator({ Binary.Dict, data }), do: data
|
||||
def count({ Binary.Dict, data }), do: length(data)
|
||||
end
|
||||
|
||||
defimpl Access, for: Binary.Dict do
|
||||
def access(dict, key), do: Binary.Dict.get(dict, key, nil)
|
||||
end
|
||||
@@ -3,22 +3,103 @@ import Kernel, except: [inspect: 1]
|
||||
defprotocol Binary.Inspect do
|
||||
@moduledoc """
|
||||
The `Binary.Inspect` protocol is responsible for
|
||||
converting any structure to a Binary for textual
|
||||
representation. All basic data structures (tuple,
|
||||
list, function, pid, etc) implement the inspect
|
||||
protocol. Other structures are adviced to implement
|
||||
the protocol in order to provide pretty printing.
|
||||
converting any structure to a binary for textual
|
||||
representation. All basic data structures
|
||||
(tuple, list, function, pid, etc) implement the
|
||||
inspect protocol. Other structures are advised to
|
||||
implement the protocol in order to provide pretty
|
||||
printing.
|
||||
"""
|
||||
|
||||
@only [BitString, List, Record, Tuple, Atom, Number, Any]
|
||||
@only [BitString, List, Tuple, Atom, Number, Any]
|
||||
|
||||
def inspect(thing)
|
||||
def inspect(thing, opts)
|
||||
end
|
||||
|
||||
defmodule Binary.Inspect.Utils do
|
||||
@moduledoc false
|
||||
|
||||
## container_join
|
||||
|
||||
def container_join(tuple, first, last, opts) when is_tuple(tuple) do
|
||||
container_join(tuple_to_list(tuple), first, last, opts)
|
||||
end
|
||||
|
||||
def container_join(list, first, last, opts) do
|
||||
first <> do_container_join(list, opts, Keyword.get(opts, :limit, :infinity)) <> last
|
||||
end
|
||||
|
||||
defp do_container_join(_, _opts, 0) do
|
||||
"..."
|
||||
end
|
||||
|
||||
defp do_container_join([h], opts, _counter) do
|
||||
Binary.Inspect.inspect(h, opts)
|
||||
end
|
||||
|
||||
defp do_container_join([h|t], opts, counter) when is_list(t) do
|
||||
Binary.Inspect.inspect(h, opts) <> "," <> do_container_join(t, opts, decrement(counter))
|
||||
end
|
||||
|
||||
defp do_container_join([h|t], opts, _counter) do
|
||||
Binary.Inspect.inspect(h, opts) <> "|" <> Binary.Inspect.inspect(t, opts)
|
||||
end
|
||||
|
||||
defp do_container_join([], _opts, _counter) do
|
||||
""
|
||||
end
|
||||
|
||||
defp decrement(:infinity), do: :infinity
|
||||
defp decrement(counter), do: counter - 1
|
||||
|
||||
## escape
|
||||
|
||||
# It is considerably faster to loop the binary
|
||||
# and convert it to a list as we go compared
|
||||
# to looping the binary and creating a binary
|
||||
# as we go.
|
||||
def escape(other, char) do
|
||||
list_to_binary [char|do_escape(other, char)]
|
||||
end
|
||||
|
||||
defp do_escape(<<char, t :: binary>>, char) do
|
||||
[?\\, char | do_escape(t, char)]
|
||||
end
|
||||
|
||||
defp do_escape(<<h, t :: binary>>, char) when
|
||||
h == ?# or h == ?\b or
|
||||
h == ?\d or h == ?\e or
|
||||
h == ?\f or h == ?\n or
|
||||
h == ?\r or h == ?\\ or
|
||||
h == ?\t or h == ?\v do
|
||||
[?\\, escape_map(h) | do_escape(t, char)]
|
||||
end
|
||||
|
||||
defp do_escape(<<h, t :: binary>>, char) do
|
||||
[h | do_escape(t,char)]
|
||||
end
|
||||
|
||||
defp do_escape(<<>>, char) do
|
||||
[char]
|
||||
end
|
||||
|
||||
defp escape_map(?#), do: ?#
|
||||
defp escape_map(?\b), do: ?b
|
||||
defp escape_map(?\d), do: ?d
|
||||
defp escape_map(?\e), do: ?e
|
||||
defp escape_map(?\f), do: ?f
|
||||
defp escape_map(?\n), do: ?n
|
||||
defp escape_map(?\r), do: ?r
|
||||
defp escape_map(?\\), do: ?\\
|
||||
defp escape_map(?\t), do: ?t
|
||||
defp escape_map(?\v), do: ?v
|
||||
end
|
||||
|
||||
defimpl Binary.Inspect, for: Atom do
|
||||
require Macro
|
||||
import Binary.Inspect.Utils
|
||||
|
||||
@doc """
|
||||
@moduledoc """
|
||||
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.
|
||||
@@ -33,13 +114,14 @@ 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(Elixir), do: "Elixir"
|
||||
|
||||
def inspect(atom) 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
|
||||
@@ -51,7 +133,7 @@ defimpl Binary.Inspect, for: Atom do
|
||||
atom in Macro.binary_ops or atom in Macro.unary_ops ->
|
||||
":" <> binary
|
||||
true ->
|
||||
":" <> Binary.escape(binary, ?")
|
||||
":" <> escape(binary, ?")
|
||||
end
|
||||
end
|
||||
|
||||
@@ -66,7 +148,7 @@ defimpl Binary.Inspect, for: Atom do
|
||||
|
||||
defp valid_ref_identifier?(_), do: false
|
||||
|
||||
defp valid_ref_piece?(<<?-, h, t|:binary>>) when h in ?A..?Z do
|
||||
defp valid_ref_piece?(<<?-, h, t :: binary>>) when h in ?A..?Z do
|
||||
valid_ref_piece? valid_identifier?(t)
|
||||
end
|
||||
|
||||
@@ -75,7 +157,7 @@ defimpl Binary.Inspect, for: Atom do
|
||||
|
||||
# Detect if atom
|
||||
|
||||
defp valid_atom_identifier?(<<h, t|:binary>>) when h in ?a..?z or h in ?A..?Z or 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
|
||||
@@ -86,7 +168,7 @@ defimpl Binary.Inspect, for: Atom do
|
||||
|
||||
defp valid_atom_identifier?(_), do: false
|
||||
|
||||
defp valid_identifier?(<<h, t|:binary>>)
|
||||
defp valid_identifier?(<<h, t :: binary>>)
|
||||
when h in ?a..?z
|
||||
when h in ?A..?Z
|
||||
when h in ?0..?9
|
||||
@@ -98,7 +180,9 @@ defimpl Binary.Inspect, for: Atom do
|
||||
end
|
||||
|
||||
defimpl Binary.Inspect, for: BitString do
|
||||
@doc %B"""
|
||||
import Binary.Inspect.Utils
|
||||
|
||||
@moduledoc %B"""
|
||||
Represents the string as itself escaping
|
||||
all necessary characters.
|
||||
|
||||
@@ -108,34 +192,55 @@ defimpl Binary.Inspect, for: BitString do
|
||||
inspect("f\"oo") #=> "f\"oo"
|
||||
|
||||
"""
|
||||
def inspect(thing) when is_binary(thing) do
|
||||
if Binary.printable?(thing) do
|
||||
Binary.escape(thing, ?")
|
||||
|
||||
def inspect(thing, opts) when is_binary(thing) do
|
||||
if String.printable?(thing) do
|
||||
escape(thing, ?")
|
||||
else
|
||||
as_bitstring(thing)
|
||||
as_bitstring(thing, opts)
|
||||
end
|
||||
end
|
||||
|
||||
def inspect(thing) do
|
||||
as_bitstring(thing)
|
||||
def inspect(thing, opts) do
|
||||
as_bitstring(thing, opts)
|
||||
end
|
||||
|
||||
## Helpers
|
||||
## Bitstrings
|
||||
|
||||
defp as_bitstring(thing) do
|
||||
erlang = Erlang.io_lib.format('~p', [thing])
|
||||
list_to_binary List.reverse(replace(erlang, []))
|
||||
defp as_bitstring(bitstring, opts) do
|
||||
"<<" <> each_bit(bitstring, Keyword.get(opts, :limit, :infinity)) <> ">>"
|
||||
end
|
||||
|
||||
defp replace([?:|t], acc), do: replace(t, [?||acc])
|
||||
defp replace([h|t], acc) when is_list(h), do: replace(t, replace(h, acc))
|
||||
defp replace([h|t], acc), do: replace(t, [h|acc])
|
||||
defp replace([], acc), do: acc
|
||||
defp each_bit(_, 0) do
|
||||
"..."
|
||||
end
|
||||
|
||||
defp each_bit(<<h, t :: bitstring>>, counter) when t != <<>> do
|
||||
integer_to_binary(h) <> "," <> each_bit(t, decrement(counter))
|
||||
end
|
||||
|
||||
defp each_bit(<<h :: size(8)>>, _counter) do
|
||||
integer_to_binary(h)
|
||||
end
|
||||
|
||||
defp each_bit(<<>>, _counter) do
|
||||
<<>>
|
||||
end
|
||||
|
||||
defp each_bit(bitstring, _counter) do
|
||||
size = bit_size(bitstring)
|
||||
<<h :: size(size)>> = bitstring
|
||||
integer_to_binary(h) <> "::size(" <> integer_to_binary(size) <> ")"
|
||||
end
|
||||
|
||||
defp decrement(:infinity), do: :infinity
|
||||
defp decrement(counter), do: counter - 1
|
||||
end
|
||||
|
||||
defimpl Binary.Inspect, for: List do
|
||||
import Binary.Inspect.Utils
|
||||
|
||||
@doc %B"""
|
||||
@moduledoc %B"""
|
||||
Represents a list checking if it can be printed or not.
|
||||
If so, a single-quoted representation is returned,
|
||||
otherwise the brackets syntax is used.
|
||||
@@ -154,33 +259,37 @@ defimpl Binary.Inspect, for: List do
|
||||
|
||||
"""
|
||||
|
||||
def inspect([]), do: "[]"
|
||||
def inspect([], _), do: "[]"
|
||||
|
||||
def inspect(thing) do
|
||||
if printable?(thing) do
|
||||
Binary.escape(list_to_binary(thing), ?')
|
||||
else
|
||||
container_join(thing, "[", "]")
|
||||
def inspect(thing, opts) do
|
||||
cond do
|
||||
printable?(thing) ->
|
||||
escape(list_to_binary(thing), ?')
|
||||
keywords?(thing) ->
|
||||
"[" <> join_keywords(thing, opts) <> "]"
|
||||
true ->
|
||||
container_join(thing, "[", "]", opts)
|
||||
end
|
||||
end
|
||||
|
||||
## Helpers
|
||||
## keywords?
|
||||
defp keywords?([]), do: true
|
||||
defp keywords?([{ key, _value } | rest]) when is_atom(key) do
|
||||
keywords?(rest)
|
||||
end
|
||||
defp keywords?(_other), do: false
|
||||
|
||||
def container_join([h], acc, last) do
|
||||
acc <> Binary.Inspect.inspect(h) <> last
|
||||
defp join_keywords(thing, opts) do
|
||||
Enum.join(lc {key, value} inlist thing do
|
||||
key_to_binary(key, opts) <> ": " <> Binary.Inspect.inspect(value, opts)
|
||||
end, ", ")
|
||||
end
|
||||
|
||||
def container_join([h|t], acc, last) when is_list(t) do
|
||||
acc = acc <> Binary.Inspect.inspect(h) <> ","
|
||||
container_join(t, acc, last)
|
||||
end
|
||||
|
||||
def container_join([h|t], acc, last) do
|
||||
acc <> Binary.Inspect.inspect(h) <> "|" <> Binary.Inspect.inspect(t) <> last
|
||||
end
|
||||
|
||||
def container_join([], acc, last) do
|
||||
acc <> last
|
||||
defp key_to_binary(key, opts) do
|
||||
case Binary.Inspect.Atom.inspect(key, opts) do
|
||||
":" <> right -> right
|
||||
other -> other
|
||||
end
|
||||
end
|
||||
|
||||
## printable?
|
||||
@@ -198,7 +307,9 @@ defimpl Binary.Inspect, for: List do
|
||||
end
|
||||
|
||||
defimpl Binary.Inspect, for: Tuple do
|
||||
@doc """
|
||||
import Binary.Inspect.Utils
|
||||
|
||||
@moduledoc """
|
||||
Inspect tuples. If the tuple represents a record,
|
||||
it shows it nicely formatted using the access syntax.
|
||||
|
||||
@@ -208,53 +319,82 @@ defimpl Binary.Inspect, for: Tuple do
|
||||
inspect(ArgumentError.new) #=> ArgumentError[message: "argument error"]
|
||||
|
||||
"""
|
||||
def inspect({}), do: "{}"
|
||||
|
||||
def inspect(exception) when is_exception(exception) do
|
||||
[name,_|tail] = tuple_to_list(exception)
|
||||
[_|fields] = lc { field, _ } inlist name.__record__(:fields), do: field
|
||||
Binary.Inspect.Atom.inspect(name) <> records_join(fields, tail, "[", "]")
|
||||
end
|
||||
def inspect({}, _), do: "{}"
|
||||
|
||||
def inspect(thing) do
|
||||
list = tuple_to_list(thing)
|
||||
[name|tail] = list
|
||||
|
||||
if is_record?(name) do
|
||||
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
|
||||
def inspect(tuple, opts) do
|
||||
unless opts[:raw] do
|
||||
record_protocol(tuple, opts)
|
||||
end || container_join(tuple, "{", "}", opts)
|
||||
end
|
||||
|
||||
## Helpers
|
||||
|
||||
defp is_record?(name) do
|
||||
is_atom(name) and match?("Elixir-" <> _, atom_to_binary(name, :utf8)) and
|
||||
function_exported?(name, :__record__, 1)
|
||||
defp record_protocol(tuple, opts) do
|
||||
name = elem(tuple, 0)
|
||||
|
||||
if is_atom(name) and match?("Elixir-" <> _, atom_to_binary(name)) do
|
||||
if name in [BitString, List, Tuple, Atom, Number, Any] do
|
||||
record_inspect(tuple, opts)
|
||||
else
|
||||
try do
|
||||
target = Module.concat(Binary.Inspect, name)
|
||||
target.inspect(tuple, opts)
|
||||
rescue
|
||||
UndefinedFunctionError ->
|
||||
record_inspect(tuple, opts)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defp records_join([f], [v], acc, last) do
|
||||
acc <> atom_to_binary(f, :utf8) <> ": " <> Binary.Inspect.inspect(v) <> last
|
||||
defp record_inspect(exception, opts) when is_exception(exception) do
|
||||
list = tuple_to_list(exception)
|
||||
[name,_|tail] = list
|
||||
[_|fields] = name.__record__(:fields)
|
||||
record_join(name, fields, tail, opts)
|
||||
end
|
||||
|
||||
defp records_join([fh|ft], [vh|vt], acc, last) do
|
||||
acc = acc <> atom_to_binary(fh, :utf8) <> ": " <> Binary.Inspect.inspect(vh) <> ", "
|
||||
records_join(ft, vt, acc, last)
|
||||
defp record_inspect(record, opts) do
|
||||
list = tuple_to_list(record)
|
||||
[name|tail] = list
|
||||
|
||||
if (fields = record_fields(name)) && (length(fields) == size(record) - 1) do
|
||||
record_join(name, fields, tail, opts)
|
||||
end
|
||||
end
|
||||
|
||||
defp records_join([], [], acc, last) do
|
||||
acc <> last
|
||||
defp record_fields(name) do
|
||||
try do
|
||||
name.__record__(:fields)
|
||||
rescue
|
||||
_ -> nil
|
||||
end
|
||||
end
|
||||
|
||||
defp record_join(name, fields, tail, opts) do
|
||||
fields = lc { field, _ } inlist fields, do: field
|
||||
Binary.Inspect.Atom.inspect(name, opts) <> "[" <>
|
||||
record_join(fields, tail, opts) <> "]"
|
||||
end
|
||||
|
||||
defp record_join([f], [v], opts) do
|
||||
atom_to_binary(f, :utf8) <> ": " <> Binary.Inspect.inspect(v, opts)
|
||||
end
|
||||
|
||||
defp record_join([fh|ft], [vh|vt], opts) do
|
||||
atom_to_binary(fh, :utf8) <> ": " <>
|
||||
Binary.Inspect.inspect(vh, opts) <> ", " <>
|
||||
record_join(ft, vt, opts)
|
||||
end
|
||||
|
||||
defp record_join([], [], _opts) do
|
||||
""
|
||||
end
|
||||
end
|
||||
|
||||
defimpl Binary.Inspect, for: Number do
|
||||
@doc """
|
||||
@moduledoc """
|
||||
Represents the number as a binary.
|
||||
|
||||
## Examples
|
||||
@@ -262,17 +402,18 @@ defimpl Binary.Inspect, for: Number do
|
||||
inspect(1) #=> "1"
|
||||
|
||||
"""
|
||||
def inspect(thing) when is_integer(thing) do
|
||||
|
||||
def inspect(thing, _) when is_integer(thing) do
|
||||
list_to_binary integer_to_list(thing)
|
||||
end
|
||||
|
||||
def inspect(thing) do
|
||||
list_to_binary float_to_list(thing)
|
||||
def inspect(thing, _) do
|
||||
list_to_binary :io_lib.format("~p", [thing])
|
||||
end
|
||||
end
|
||||
|
||||
defimpl Binary.Inspect, for: Regex do
|
||||
@doc %B"""
|
||||
@moduledoc %B"""
|
||||
Represents the Regex using the `%r""` syntax.
|
||||
|
||||
## Examples
|
||||
@@ -280,13 +421,14 @@ defimpl Binary.Inspect, for: Regex do
|
||||
inspect(%r/foo/m) #=> "%r\"foo\"m"
|
||||
|
||||
"""
|
||||
def inspect(thing) do
|
||||
"%r" <> Binary.Inspect.inspect(Regex.source(thing)) <> Regex.opts(thing)
|
||||
|
||||
def inspect(thing, _) do
|
||||
"%r" <> Binary.Inspect.inspect(Regex.source(thing), []) <> Regex.opts(thing)
|
||||
end
|
||||
end
|
||||
|
||||
defimpl Binary.Inspect, for: Any do
|
||||
@doc """
|
||||
@moduledoc """
|
||||
For all other terms not implemented, we use the default
|
||||
Erlang representation.
|
||||
|
||||
@@ -295,7 +437,8 @@ defimpl Binary.Inspect, for: Any do
|
||||
inspect Process.self #=> "<0.35.0>"
|
||||
|
||||
"""
|
||||
def inspect(thing) do
|
||||
iolist_to_binary Erlang.io_lib.format('~p', [thing])
|
||||
|
||||
def inspect(thing, _) do
|
||||
iolist_to_binary :io_lib.format('~p', [thing])
|
||||
end
|
||||
end
|
||||
@@ -29,9 +29,9 @@ defmodule Bitwise do
|
||||
defmacro __using__(options) do
|
||||
except = cond do
|
||||
Keyword.get(options, :only_operators) ->
|
||||
[:bnot, :band, :bor, :bxor, :bsl, :bsr]
|
||||
[bnot: 1, band: 2, bor: 2, bxor: 2, bsl: 2, bsr: 2]
|
||||
Keyword.get(options, :skip_operators) ->
|
||||
[:~~~, :&&&, :|||, :^^^, :<<<, :>>>]
|
||||
[~~~: 1, &&&: 2, |||: 2, ^^^: 2, <<<: 2, >>>: 2]
|
||||
true -> []
|
||||
end
|
||||
|
||||
|
||||
+100
-41
@@ -17,12 +17,22 @@ defmodule Code do
|
||||
server_call :loaded
|
||||
end
|
||||
|
||||
@doc """
|
||||
Removes the given files from the loaded files list.
|
||||
The modules defined in the file are not removed,
|
||||
calling this function only removes it from the list,
|
||||
allowing it to be required again.
|
||||
"""
|
||||
def unload_files(files) do
|
||||
server_cast { :unload_files, files }
|
||||
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))
|
||||
:code.add_pathz(File.expand_path to_char_list(path))
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -30,7 +40,15 @@ defmodule Code do
|
||||
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))
|
||||
:code.add_patha(File.expand_path to_char_list(path))
|
||||
end
|
||||
|
||||
@doc """
|
||||
Deletes a path from Erlang VM code path.
|
||||
The path is expanded with `File.expand_path` before deleted.
|
||||
"""
|
||||
def delete_path(path) do
|
||||
:code.del_path(File.expand_path to_char_list(path))
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -49,7 +67,7 @@ defmodule Code do
|
||||
#=> { 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:
|
||||
the location which already returns both fields as a keyword list:
|
||||
|
||||
Code.eval "a + b", [a: 1, b: 2], __ENV__.location
|
||||
#=> { 3, [ {:a, 1}, {:b, 2} ] }
|
||||
@@ -57,7 +75,7 @@ defmodule Code do
|
||||
"""
|
||||
def eval(string, binding // [], opts // []) do
|
||||
{ value, binding, _scope } =
|
||||
Erlang.elixir.eval :unicode.characters_to_list(string), binding, opts
|
||||
:elixir.eval :unicode.characters_to_list(string), binding, opts
|
||||
{ value, binding }
|
||||
end
|
||||
|
||||
@@ -81,16 +99,22 @@ defmodule Code do
|
||||
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:
|
||||
For convenience, you can also pass the current __ENV__ and
|
||||
the proper information will be extracted:
|
||||
|
||||
Code.eval_quoted contents, [a: 1, b: 2], __ENV__.location
|
||||
Code.eval_quoted contents, [a: 1, b: 2], __ENV__
|
||||
#=> { 3, [ {:a, 1}, {:b, 2} ] }
|
||||
|
||||
"""
|
||||
def eval_quoted(quoted, binding // [], opts // []) do
|
||||
def eval_quoted(quoted, binding // [], opts // [])
|
||||
|
||||
def eval_quoted(quoted, binding, Macro.Env[] = env) do
|
||||
eval_quoted(quoted, binding, env.location)
|
||||
end
|
||||
|
||||
def eval_quoted(quoted, binding, opts) do
|
||||
{ value, binding, _scope } =
|
||||
Erlang.elixir.eval_quoted [quoted], binding, opts
|
||||
:elixir.eval_quoted [quoted], binding, opts
|
||||
{ value, binding }
|
||||
end
|
||||
|
||||
@@ -105,11 +129,15 @@ defmodule Code do
|
||||
|
||||
* `:line` - The line reported in the __ENV__ variable.
|
||||
|
||||
* `:existing_atoms_only` - When true, raises an error
|
||||
when non-existing atoms are found by the tokenizer.
|
||||
|
||||
"""
|
||||
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)
|
||||
res = :elixir_translator.forms(:unicode.characters_to_list(string), line, file, opts)
|
||||
|
||||
case res do
|
||||
{ :ok, ast } -> { :ok, unpack_ast(line, ast) }
|
||||
_ -> res
|
||||
@@ -122,18 +150,12 @@ defmodule Code do
|
||||
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.
|
||||
|
||||
Check `Code.string_to_ast/2` for options information.
|
||||
"""
|
||||
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)
|
||||
res = :elixir_translator.forms!(:unicode.characters_to_list(string), line, file, opts)
|
||||
unpack_ast(line, res)
|
||||
end
|
||||
|
||||
@@ -142,34 +164,50 @@ defmodule Code do
|
||||
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.
|
||||
Loads the given `file`. Accepts `relative_to` as an argument
|
||||
to tell where the file is located. If the file was already
|
||||
required/loaded, loads it again. It returns all the modules
|
||||
defined in the file.
|
||||
|
||||
When loading a file, you may skip passing .exs as extension as Elixir
|
||||
automatically adds it for you.
|
||||
Notice that if `load_file` is invoked by different processes
|
||||
concurrently, the target file will be invoked concurrently
|
||||
in many times. I.e. if `load_file` is called N times with
|
||||
a given file, the given file will be loaded N times. Check
|
||||
`require_file` if you don't want a file to be loaded concurrently.
|
||||
"""
|
||||
def load_file(file, relative_to // nil) when is_binary(file) do
|
||||
file = find_file(file, relative_to)
|
||||
server_call { :loaded, file }
|
||||
Erlang.elixir_compiler.file file
|
||||
file
|
||||
server_call { :acquire, file }
|
||||
loaded = :elixir_compiler.file file
|
||||
server_cast { :loaded, file }
|
||||
loaded
|
||||
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.
|
||||
Requires the given `file`. Accepts `relative_to` as an argument
|
||||
to tell where the file is located. If the file was already
|
||||
required/loaded, loads it again. It returns all the modules
|
||||
defined in the file.
|
||||
|
||||
When requiring a file, you may skip passing .exs as extension as
|
||||
Elixir automatically adds it for you.
|
||||
Notice that if `require_file` is invoked by different processes
|
||||
concurrently, the first process to invoke `require_file` acquires
|
||||
a lock and the remaining ones will block until the file is
|
||||
available. I.e. if `require_file` is called N times with a given
|
||||
file, the given file will be loaded only once. Check `load_file`
|
||||
if you want a file to be loaded concurrently.
|
||||
"""
|
||||
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 -> []
|
||||
case server_call({ :acquire, file }) do
|
||||
:loaded ->
|
||||
nil
|
||||
{ :queued, ref } ->
|
||||
receive do { :elixir_code_server, ^ref, :loaded } -> nil end
|
||||
:proceed ->
|
||||
loaded = :elixir_compiler.file file
|
||||
server_cast { :loaded, file }
|
||||
loaded
|
||||
end
|
||||
end
|
||||
|
||||
@@ -208,7 +246,7 @@ defmodule Code do
|
||||
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)
|
||||
:elixir_compiler.string :unicode.characters_to_list(string), to_binary(file)
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -230,8 +268,8 @@ defmodule Code do
|
||||
|
||||
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.
|
||||
uses this function to check if a specific parser exists for a given
|
||||
URI scheme.
|
||||
|
||||
## Code.ensure_compiled
|
||||
|
||||
@@ -250,7 +288,15 @@ defmodule Code do
|
||||
that needs to invoke a module for callback information.
|
||||
"""
|
||||
def ensure_loaded(module) when is_atom(module) do
|
||||
Erlang.code.ensure_loaded(module)
|
||||
:code.ensure_loaded(module)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Similar to `ensure_loaded/1`, but returns a boolean in case
|
||||
it could be ensured or not.
|
||||
"""
|
||||
def ensure_loaded?(module) do
|
||||
match?({ :module, ^module }, ensure_loaded(module))
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -267,9 +313,9 @@ defmodule Code do
|
||||
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
|
||||
case :code.ensure_loaded(module) do
|
||||
{ :error, :nofile } = error ->
|
||||
case :erlang.get(:elixir_compiler_pid) do
|
||||
case :erlang.get(:elixir_ensure_compiled) do
|
||||
:undefined -> error
|
||||
_ ->
|
||||
try do
|
||||
@@ -283,6 +329,14 @@ defmodule Code do
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Similar to `ensure_compiled/1`, but returns a boolean in case
|
||||
it could be ensured or not.
|
||||
"""
|
||||
def ensure_compiled?(module) do
|
||||
match?({ :module, ^module }, ensure_compiled(module))
|
||||
end
|
||||
|
||||
## Helpers
|
||||
|
||||
# Finds the file given the relative_to path.
|
||||
@@ -301,6 +355,7 @@ defmodule Code do
|
||||
else
|
||||
prefix = "#{file}.exs"
|
||||
if File.regular?(prefix) do
|
||||
IO.write "[WARNING] Passing a file without .exs extension to Code.load_file or Code.require_file is deprecated, please pass the full name instead\n#{Exception.formatted_stacktrace}"
|
||||
prefix
|
||||
else
|
||||
raise ArgumentError, message: "could not load #{file}"
|
||||
@@ -309,6 +364,10 @@ defmodule Code do
|
||||
end
|
||||
|
||||
defp server_call(args) do
|
||||
Erlang.gen_server.call(:elixir_code_server, args)
|
||||
:gen_server.call(:elixir_code_server, args)
|
||||
end
|
||||
|
||||
defp server_cast(args) do
|
||||
:gen_server.cast(:elixir_code_server, args)
|
||||
end
|
||||
end
|
||||
|
||||
+107
-39
@@ -1,12 +1,38 @@
|
||||
defprotocol Dict do
|
||||
@only [Record]
|
||||
|
||||
defmodule Dict do
|
||||
@moduledoc """
|
||||
This module provides the Dict protocol
|
||||
with the goal of being a common API
|
||||
to work with dictionaries.
|
||||
This module specifies the Dict API expected to be
|
||||
implemented by different dictionaries. It also provides
|
||||
functions that redirect to the underlying Dict based on
|
||||
the tuple signature.
|
||||
|
||||
The keyword list used throughout Elixir cannot be
|
||||
manipulated via the Dict module, you must use the
|
||||
Keyword module instead. This distinction is intentional:
|
||||
the Dict module is meant to work on structures that work
|
||||
as storage.
|
||||
|
||||
To create a new dict, use the `new` functions defined
|
||||
by each dict type:
|
||||
|
||||
OrdDict.new [{:a, 1}, {:b, 2}]
|
||||
HashDict.new #=> creates an empty HashDict
|
||||
|
||||
For simplicity's sake, in the examples below everytime
|
||||
`new` is used, it implies one of the module-specific
|
||||
calls like the two above. Likewise, when the result of
|
||||
a function invocation is shown in the form `[a: 1, b: 2]`,
|
||||
it implies that the returned value is actually of the
|
||||
same dict type as the input one.
|
||||
|
||||
"""
|
||||
|
||||
@doc false
|
||||
def behaviour_info(:callbacks) do
|
||||
[ delete: 2, empty: 1, get: 3, has_key?: 2,
|
||||
keys: 1, merge: 3, put: 3, size: 1, to_list: 1,
|
||||
update: 3, update: 4, values: 1 ]
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns a list containing all dict's keys.
|
||||
The keys are not guaranteed to be sorted, unless
|
||||
@@ -14,41 +40,53 @@ defprotocol Dict do
|
||||
|
||||
## Examples
|
||||
|
||||
Dict.keys [a: 1, b: 2] #=> [:a,:b]
|
||||
d = new [a: 1, b: 2]
|
||||
Dict.keys d #=> [:a,:b]
|
||||
|
||||
"""
|
||||
def keys(dict)
|
||||
def keys(dict) do
|
||||
elem(dict, 0).keys(dict)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns a list containing all dict's values.
|
||||
|
||||
## Examples
|
||||
|
||||
Dict.values [a: 1, b: 2] #=> [1,2]
|
||||
d = new [a: 1, b: 2]
|
||||
Dict.values d #=> [1,2]
|
||||
|
||||
"""
|
||||
def values(dict)
|
||||
def values(dict) do
|
||||
elem(dict, 0).values(dict)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns the number of elements in `dict`.
|
||||
|
||||
## Examples
|
||||
|
||||
Dict.size [a: 1, b: 2] #=> 2
|
||||
d = new [a: 1, b: 2]
|
||||
Dict.size d #=> 2
|
||||
|
||||
"""
|
||||
def size(dict)
|
||||
def size(dict) do
|
||||
elem(dict, 0).size(dict)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns whether the given key exists in the given dict.
|
||||
|
||||
## Examples
|
||||
|
||||
Dict.has_key?([a: 1], :a) #=> true
|
||||
Dict.has_key?([a: 1], :b) #=> false
|
||||
d = new [a: 1]
|
||||
Dict.has_key?(d, :a) #=> true
|
||||
Dict.has_key?(d, :b) #=> false
|
||||
|
||||
"""
|
||||
def has_key?(dict, key)
|
||||
def has_key?(dict, key) do
|
||||
elem(dict, 0).has_key?(dict, key)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns the value associated with `key` in `dict`. If `dict` does not
|
||||
@@ -56,13 +94,15 @@ defprotocol Dict do
|
||||
|
||||
## Examples
|
||||
|
||||
Dict.get [a: 1], :a #=> 1
|
||||
Dict.get [a: 1], :b #=> nil
|
||||
Dict.get [a: 1], :b, 3 #=> 3
|
||||
d = new [a: 1]
|
||||
Dict.get d, :a #=> 1
|
||||
Dict.get d, :b #=> nil
|
||||
Dict.get d, :b, 3 #=> 3
|
||||
|
||||
"""
|
||||
def get(dict, key)
|
||||
def get(dict, key, default)
|
||||
def get(dict, key, default // nil) do
|
||||
elem(dict, 0).get(dict, key, default)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Stores the given `value` under `key` in `dict`.
|
||||
@@ -70,11 +110,14 @@ defprotocol Dict do
|
||||
|
||||
## Examples
|
||||
|
||||
Dict.put [a: 1, b: 2], :a, 3
|
||||
d = new [a: 1, b: 2]
|
||||
Dict.put d, :a, 3
|
||||
#=> [a: 3, b: 2]
|
||||
|
||||
"""
|
||||
def put(dict, key, val)
|
||||
def put(dict, key, val) do
|
||||
elem(dict, 0).put(dict, key, val)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Removes the entry stored under the given key from `dict`.
|
||||
@@ -82,23 +125,34 @@ defprotocol Dict do
|
||||
|
||||
## Examples
|
||||
|
||||
Dict.delete [a: 1, b: 2], :a #=> [b: 2]
|
||||
Dict.delete [b: 2], :a #=> [b: 2]
|
||||
d = new [a: 1, b: 2]
|
||||
Dict.delete d, :a #=> [b: 2]
|
||||
|
||||
d = new [b: 2]
|
||||
Dict.delete d, :a #=> [b: 2]
|
||||
|
||||
"""
|
||||
def delete(dict, key)
|
||||
def delete(dict, key) do
|
||||
elem(dict, 0).delete(dict, key)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Merges two dicts into one. If the dicts have duplicated entries, the one
|
||||
given as second argument wins.
|
||||
Merges two dicts into one. If the dicts have duplicated entries,
|
||||
the one given as second argument wins. In case the second argument
|
||||
is not of the same kind as the first one, it is converted to the
|
||||
same kind before merging as long as it implements the `Enum` protocol.
|
||||
|
||||
## Examples
|
||||
|
||||
Dict.merge [a: 1, b: 2], [a: 3, d: 4]
|
||||
#=> [a:3, b:2, d: 4]
|
||||
d1 = new [a: 1, b: 2]
|
||||
d2 = new [a: 3, d: 4]
|
||||
Dict.merge d1, d2
|
||||
#=> [a: 3, b: 2, d: 4]
|
||||
|
||||
"""
|
||||
def merge(dict1, dict2)
|
||||
def merge(dict1, dict2) do
|
||||
merge(dict1, dict2, fn(_k, _v1, v2) -> v2 end)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Merges two dicts into one. If the dicts have duplicated entries, the given
|
||||
@@ -106,13 +160,17 @@ defprotocol Dict do
|
||||
|
||||
## Examples
|
||||
|
||||
Dict.merge [a: 1, b: 2], [a: 3, d: 4], fn _k, v1, v2 ->
|
||||
d1 = new [a: 1, b: 2]
|
||||
d2 = new [a: 3, d: 4]
|
||||
Dict.merge d1, d2, fn _k, v1, v2 ->
|
||||
v1 + v2
|
||||
end
|
||||
#=> [a: 4, b: 2, d: 4]
|
||||
|
||||
"""
|
||||
def merge(dict1, dict2, fun)
|
||||
def merge(dict1, dict2, fun) do
|
||||
elem(dict1, 0).merge(dict1, dict2, fun)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Update a value in `dict` by calling `fun` on the value to get a new
|
||||
@@ -120,11 +178,14 @@ defprotocol Dict do
|
||||
|
||||
## Examples
|
||||
|
||||
Dict.update [a: 1, b: 2], :a, fn val -> -val end
|
||||
d = new [a: 1, b: 2]
|
||||
Dict.update d, :a, fn val -> -val end
|
||||
#=> [a: -1, b: 2]
|
||||
|
||||
"""
|
||||
def update(dict, key, fun)
|
||||
def update(dict, key, fun) do
|
||||
elem(dict, 0).update(dict, key, fun)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Update a value in `dict` by calling `fun` on the value to get a new value. If
|
||||
@@ -133,20 +194,27 @@ defprotocol Dict do
|
||||
|
||||
## Examples
|
||||
|
||||
Dict.update [a: 1, b: 2], :c, 3, fn val -> -val end
|
||||
d = new [a: 1, b: 2]
|
||||
Dict.update d, :c, 3, fn val -> -val end
|
||||
#=> [a: 1, b: 2, c: 3]
|
||||
|
||||
"""
|
||||
def update(dict, key, initial, fun)
|
||||
def update(dict, key, initial, fun) do
|
||||
elem(dict, 0).update(dict, key, initial, fun)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns an empty dict of the same type as `dict`.
|
||||
"""
|
||||
def empty(dict)
|
||||
def empty(dict) do
|
||||
elem(dict, 0).empty(dict)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns a list of key-value pairs stored in `dict`.
|
||||
No particular order is enforced.
|
||||
"""
|
||||
def to_list(dict)
|
||||
end
|
||||
def to_list(dict) do
|
||||
elem(dict, 0).to_list(dict)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,13 +1,15 @@
|
||||
defmodule Dict.Common do
|
||||
@moduledoc false
|
||||
|
||||
defmacro __using__(ref) do
|
||||
defmacro __using__(_) do
|
||||
quote do
|
||||
@behavior Dict
|
||||
|
||||
@doc """
|
||||
Creates a new empty dict.
|
||||
"""
|
||||
def new do
|
||||
unquote(ref).empty(nil)
|
||||
empty(nil)
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -21,7 +23,7 @@ defmodule Dict.Common do
|
||||
"""
|
||||
def new(pairs) do
|
||||
Enum.reduce pairs, new, fn { k, v }, dict ->
|
||||
unquote(ref).put(dict, k, v)
|
||||
put(dict, k, v)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -37,7 +39,7 @@ defmodule Dict.Common do
|
||||
def new(list, transform) when is_function(transform) do
|
||||
Enum.reduce list, new(), fn i, dict ->
|
||||
{ k, v } = transform.(i)
|
||||
unquote(ref).put(dict, k, v)
|
||||
put(dict, k, v)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
+268
-170
@@ -52,45 +52,8 @@ defprotocol Enum.Iterator do
|
||||
def count(collection)
|
||||
end
|
||||
|
||||
defprotocol Enum.OrdIterator do
|
||||
@moduledoc """
|
||||
This protocol is invoked by some functions in Enum which
|
||||
requires an ordered collection to function correctly. For
|
||||
instance, `Enum.split_with/2`, `Enum.take_while` all rely
|
||||
on this protocol.
|
||||
|
||||
An ordered collection does not mean the items are ordered
|
||||
according to the Elixir ordering but simply that any two
|
||||
distinct instances of the same collection with exactly
|
||||
the same items always yield the same order when iterated.
|
||||
"""
|
||||
|
||||
@only [List, Record]
|
||||
|
||||
@doc """
|
||||
Must return a tuple under the same conditions as
|
||||
`Enum.Iterator.iterator`.
|
||||
"""
|
||||
def iterator(collection)
|
||||
|
||||
@doc """
|
||||
On each step, the iterator function returned by `iterator/1`
|
||||
returns a tuple with two elements. This function receives
|
||||
those two elements as a tuple and must return a list back.
|
||||
|
||||
This is used in order to quicky return a list from any point
|
||||
during iteration. For example, consider the function `Enum.drop`.
|
||||
`Enum.drop collection, 3` should drop 3 items and return a list
|
||||
back. While we could loop over the remaining items to get a list
|
||||
back, this function is invoked allowing us to get a result
|
||||
back without a need to loop the remaining items.
|
||||
"""
|
||||
def to_list({ current, next }, iterator)
|
||||
end
|
||||
|
||||
defmodule Enum do
|
||||
require Enum.Iterator, as: I
|
||||
require Enum.OrdIterator, as: O
|
||||
alias Enum.Iterator, as: I
|
||||
|
||||
@moduledoc """
|
||||
Provides a set of algorithms that enumerate over collections according to the
|
||||
@@ -174,6 +137,33 @@ defmodule Enum do
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Finds the element at the given index (zero-based).
|
||||
Raises out of bounds error in case the given position
|
||||
is outside the range of the collection.
|
||||
|
||||
Expects an ordered collection.
|
||||
|
||||
## Examples
|
||||
|
||||
Enum.at! [2,4,6], 0 #=> 2
|
||||
Enum.at! [2,4,6], 2 #=> 6
|
||||
Enum.at! [2,4,6], 4 #=> raises Enum.OutOfBoundsError
|
||||
|
||||
"""
|
||||
def at!(collection, n) when is_list(collection) and n >= 0 do
|
||||
do_at!(collection, n)
|
||||
end
|
||||
|
||||
def at!(collection, n) when n >= 0 do
|
||||
case I.iterator(collection) do
|
||||
{ iterator, pointer } ->
|
||||
do_at!(pointer, iterator, n)
|
||||
list when is_list(list) ->
|
||||
do_at!(list, n)
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns the collection size.
|
||||
|
||||
@@ -186,6 +176,22 @@ defmodule Enum do
|
||||
I.count(collection)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Counts for how many items the function returns true.
|
||||
"""
|
||||
def count(collection, fun) when is_list(collection) do
|
||||
do_count(collection, fun)
|
||||
end
|
||||
|
||||
def count(collection, fun) do
|
||||
case I.iterator(collection) do
|
||||
{ iterator, pointer } ->
|
||||
do_count(pointer, iterator, fun)
|
||||
list when is_list(list) ->
|
||||
do_count(list, fun)
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Drops the first `count` items from the collection. Expects an ordered
|
||||
collection.
|
||||
@@ -198,7 +204,7 @@ defmodule Enum do
|
||||
|
||||
"""
|
||||
def drop(collection, count) do
|
||||
elem split(collection, count), 2
|
||||
elem split(collection, count), 1
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -215,10 +221,9 @@ defmodule Enum do
|
||||
end
|
||||
|
||||
def drop_while(collection, fun) do
|
||||
case O.iterator(collection) do
|
||||
case I.iterator(collection) do
|
||||
{ iterator, pointer } ->
|
||||
module = O.__impl_for__!(collection)
|
||||
do_drop_while(pointer, iterator, fun, module)
|
||||
do_drop_while(pointer, iterator, fun)
|
||||
list when is_list(list) ->
|
||||
do_drop_while(list, fun)
|
||||
end
|
||||
@@ -263,7 +268,7 @@ defmodule Enum do
|
||||
|
||||
def empty?(collection) do
|
||||
case I.iterator(collection) do
|
||||
{ _iterator, pointer } -> pointer === :stop
|
||||
{ _iterator, pointer } -> pointer == :stop
|
||||
list when is_list(list) -> list == []
|
||||
end
|
||||
end
|
||||
@@ -348,13 +353,13 @@ defmodule Enum do
|
||||
Similar to find, but returns the value of the function
|
||||
invocation instead of the element itself.
|
||||
|
||||
## Examples
|
||||
## Examples
|
||||
|
||||
Enum.find_value [2,4,6], fn(x) -> rem(x, 2) == 1 end
|
||||
#=> nil
|
||||
Enum.find_value [2,4,6], fn(x) -> rem(x, 2) == 1 end
|
||||
#=> nil
|
||||
|
||||
Enum.find_value [2,3,4], fn(x) -> rem(x, 2) == 1 end
|
||||
#=> true
|
||||
Enum.find_value [2,3,4], fn(x) -> rem(x, 2) == 1 end
|
||||
#=> true
|
||||
|
||||
"""
|
||||
def find_value(collection, ifnone // nil, fun)
|
||||
@@ -373,30 +378,30 @@ defmodule Enum do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Similar to find, but returns the index (count starts with 1)
|
||||
Similar to find, but returns the index (count starts with 0)
|
||||
of the item instead of the element itself.
|
||||
|
||||
Expects an ordered collection.
|
||||
|
||||
## Examples
|
||||
## Examples
|
||||
|
||||
Enum.find_index [2,4,6], fn(x) -> rem(x, 2) == 1 end
|
||||
#=> nil
|
||||
Enum.find_index [2,4,6], fn(x) -> rem(x, 2) == 1 end
|
||||
#=> nil
|
||||
|
||||
Enum.find_index [2,3,4], fn(x) -> rem(x, 2) == 1 end
|
||||
#=> 2
|
||||
Enum.find_index [2,3,4], fn(x) -> rem(x, 2) == 1 end
|
||||
#=> 2
|
||||
|
||||
"""
|
||||
def find_index(collection, fun) when is_list(collection) do
|
||||
do_find_index(collection, 1, fun)
|
||||
do_find_index(collection, 0, fun)
|
||||
end
|
||||
|
||||
def find_index(collection, fun) do
|
||||
case O.iterator(collection) do
|
||||
case I.iterator(collection) do
|
||||
{ iterator, pointer } ->
|
||||
do_find_index(pointer, iterator, 1, fun)
|
||||
do_find_index(pointer, iterator, 0, fun)
|
||||
list when is_list(list) ->
|
||||
do_find_index(list, 1, fun)
|
||||
do_find_index(list, 0, fun)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -413,7 +418,7 @@ defmodule Enum do
|
||||
def first([h|_]), do: h
|
||||
|
||||
def first(collection) do
|
||||
case O.iterator(collection) do
|
||||
case I.iterator(collection) do
|
||||
{ _iterator, { h, _ } } -> h
|
||||
{ _iterator, :stop } -> nil
|
||||
list when is_list(list) -> first(list)
|
||||
@@ -547,32 +552,6 @@ defmodule Enum do
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Finds the element at the nth index. Returns nil in case
|
||||
the given index is outside the range of the collection.
|
||||
|
||||
Expects an ordered collection.
|
||||
|
||||
## Examples
|
||||
|
||||
Enum.nth! [2,4,6], 1 #=> 2
|
||||
Enum.nth! [2,4,6], 3 #=> 6
|
||||
Enum.nth! [2,4,6], 5 #=> raises Enum.OutOfBoundsError
|
||||
|
||||
"""
|
||||
def nth!(collection, n) when is_list(collection) and n > 0 do
|
||||
do_nth!(collection, n)
|
||||
end
|
||||
|
||||
def nth!(collection, n) when n > 0 do
|
||||
case O.iterator(collection) do
|
||||
{ iterator, pointer } ->
|
||||
do_nth!(pointer, iterator, n)
|
||||
list when is_list(list) ->
|
||||
do_nth!(list, n)
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Partitions `collection` into two where the first one contains elements
|
||||
for which `fun` returns a truthy value, and the second one -- for which `fun`
|
||||
@@ -598,8 +577,8 @@ defmodule Enum do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Invokes `fun` for each element in the collection passing the accumulator
|
||||
`acc` and the element as arguments. The return value is stored in `acc`.
|
||||
Invokes `fun` for each element in the collection passing that element and the
|
||||
accumulator `acc` as arguments. `fun`'s return value is stored in `acc`.
|
||||
Returns the accumulator.
|
||||
|
||||
## Examples
|
||||
@@ -621,6 +600,28 @@ defmodule Enum do
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Reverses the collection.
|
||||
|
||||
Expects an ordered collection.
|
||||
|
||||
## Examples
|
||||
|
||||
Enum.reverse [1, 2, 3]
|
||||
#=> [3, 2, 1]
|
||||
|
||||
"""
|
||||
def reverse(collection) when is_list(collection) do
|
||||
:lists.reverse(collection)
|
||||
end
|
||||
|
||||
def reverse(collection) do
|
||||
case I.iterator(collection) do
|
||||
{ iterator, pointer } -> do_reverse(pointer, iterator, [])
|
||||
list when is_list(list) -> reverse(list)
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Sorts the collection according to the quick sort algorithm.
|
||||
|
||||
@@ -644,13 +645,18 @@ defmodule Enum do
|
||||
|
||||
@doc """
|
||||
Splits the enumerable into two collections, leaving `count` elements in the
|
||||
first one. Expects an ordered collection.
|
||||
first one. If `count` is a negative number, it starts couting from the back
|
||||
to the beginning of the collection. Be aware that a negative `count`
|
||||
implies in an iteration through the whole collection.
|
||||
Expects an ordered collection.
|
||||
|
||||
## Examples
|
||||
|
||||
Enum.split [1,2,3], 2 #=> { [1,2], [3] }
|
||||
Enum.split [1,2,3], 10 #=> { [1,2,3], [] }
|
||||
Enum.split [1,2,3], 0 #=> { [], [1,2,3] }
|
||||
Enum.split [1,2,3], -1 #=> { [1,2], [3] }
|
||||
Enum.split [1,2,3], -5 #=> { [], [1,2,3] }
|
||||
|
||||
"""
|
||||
def split(collection, count) when is_list(collection) and count >= 0 do
|
||||
@@ -658,35 +664,46 @@ defmodule Enum do
|
||||
end
|
||||
|
||||
def split(collection, count) when count >= 0 do
|
||||
case O.iterator(collection) do
|
||||
case I.iterator(collection) do
|
||||
{ iterator, pointer } ->
|
||||
module = O.__impl_for__!(collection)
|
||||
do_split(pointer, iterator, count, [], module)
|
||||
do_split(pointer, iterator, count, [])
|
||||
list when is_list(list) ->
|
||||
do_split(list, count, [])
|
||||
end
|
||||
end
|
||||
|
||||
def split(collection, count) when count < 0 do
|
||||
reducer = fn(x, acc) -> {x, acc + 1} end
|
||||
{ list , total_items } = Enum.map_reduce(collection, 0, reducer)
|
||||
real_count = max(0, total_items - abs(count))
|
||||
split(list, real_count)
|
||||
end
|
||||
|
||||
@doc false
|
||||
def split_with(collection, fun) do
|
||||
IO.write "[WARNING] Enum.split_with is deprecated, please use split_while instead\n#{Exception.formatted_stacktrace}"
|
||||
split_while(collection, fun)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Splits `collection` at the first element, for which `fun` returns true.
|
||||
Expects an ordered collection.
|
||||
|
||||
## Examples
|
||||
|
||||
Enum.split_with [1,2,3,4], fn x -> x == 2 end
|
||||
Enum.split_while [1,2,3,4], fn x -> x == 2 end
|
||||
#=> { [1], [2, 3, 4] }
|
||||
"""
|
||||
def split_with(collection, fun) when is_list(collection) do
|
||||
do_split_with(collection, fun, [])
|
||||
def split_while(collection, fun) when is_list(collection) do
|
||||
do_split_while(collection, fun, [])
|
||||
end
|
||||
|
||||
def split_with(collection, fun) do
|
||||
case O.iterator(collection) do
|
||||
def split_while(collection, fun) do
|
||||
case I.iterator(collection) do
|
||||
{ iterator, pointer } ->
|
||||
module = O.__impl_for__!(collection)
|
||||
do_split_with(pointer, iterator, fun, [], module)
|
||||
do_split_while(pointer, iterator, fun, [])
|
||||
list when is_list(list) ->
|
||||
do_split_with(list, fun, [])
|
||||
do_split_while(list, fun, [])
|
||||
end
|
||||
end
|
||||
|
||||
@@ -702,7 +719,7 @@ defmodule Enum do
|
||||
|
||||
"""
|
||||
def take(collection, count) do
|
||||
elem split(collection, count), 1
|
||||
elem split(collection, count), 0
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -720,7 +737,7 @@ defmodule Enum do
|
||||
end
|
||||
|
||||
def take_while(collection, fun) do
|
||||
case O.iterator(collection) do
|
||||
case I.iterator(collection) do
|
||||
{ iterator, pointer } ->
|
||||
do_take_while(pointer, iterator, fun)
|
||||
list when is_list(list) ->
|
||||
@@ -729,15 +746,15 @@ defmodule Enum do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Iterates the given function n times, passing values from 1
|
||||
to n.
|
||||
Iterates the given function n times, passing values
|
||||
from 0 to n - 1.
|
||||
|
||||
## Examples
|
||||
|
||||
Enum.times 3, fn(x) -> IO.inspect x end
|
||||
0
|
||||
1
|
||||
2
|
||||
3
|
||||
|
||||
"""
|
||||
def times(times, function) when times >= 0 do
|
||||
@@ -765,6 +782,38 @@ defmodule Enum do
|
||||
do_times_2(times, 1, function, acc)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Zips corresponding elements from two collections into one list
|
||||
of tuples. The number of elements in the resulting list is
|
||||
dictated by the first enum. In case the second list is shorter,
|
||||
values are filled with nil.
|
||||
"""
|
||||
def zip(coll1, coll2) when is_list(coll1) do
|
||||
do_zip(coll1, iterator(coll2))
|
||||
end
|
||||
|
||||
def zip(coll1, coll2) do
|
||||
case I.iterator(coll1) do
|
||||
{ iterator, pointer } ->
|
||||
do_zip(pointer, iterator, iterator(coll2))
|
||||
list when is_list(list) ->
|
||||
do_zip(list, iterator(coll2))
|
||||
end
|
||||
end
|
||||
|
||||
## Helpers
|
||||
|
||||
defp iterator(collection) when is_list(collection), do: collection
|
||||
defp iterator(collection), do: I.iterator(collection)
|
||||
|
||||
defp to_list({ h, next }, iterator) do
|
||||
[h|to_list(iterator.(next), iterator)]
|
||||
end
|
||||
|
||||
defp to_list(:stop, _) do
|
||||
[]
|
||||
end
|
||||
|
||||
## Implementations
|
||||
|
||||
## all?
|
||||
@@ -819,6 +868,42 @@ defmodule Enum do
|
||||
false
|
||||
end
|
||||
|
||||
## at!
|
||||
|
||||
defp do_at!([h|_], 0), do: h
|
||||
defp do_at!([_|t], n), do: do_at!(t, n - 1)
|
||||
defp do_at!([], _), do: raise Enum.OutOfBoundsError
|
||||
|
||||
defp do_at!({ h, _next }, _iterator, 0), do: h
|
||||
defp do_at!({ _, next }, iterator, n), do: do_at!(iterator.(next), iterator, n - 1)
|
||||
defp do_at!(:stop, _iterator, _), do: raise Enum.OutOfBoundsError
|
||||
|
||||
## count
|
||||
|
||||
defp do_count([h|t], fun) do
|
||||
if fun.(h) do
|
||||
1 + do_count(t, fun)
|
||||
else
|
||||
do_count(t, fun)
|
||||
end
|
||||
end
|
||||
|
||||
defp do_count([], _) do
|
||||
0
|
||||
end
|
||||
|
||||
defp do_count({ h, next }, iterator, fun) do
|
||||
if fun.(h) do
|
||||
1 + do_count(iterator.(next), iterator, fun)
|
||||
else
|
||||
do_count(iterator.(next), iterator, fun)
|
||||
end
|
||||
end
|
||||
|
||||
defp do_count(:stop, _, _) do
|
||||
0
|
||||
end
|
||||
|
||||
## drop_while
|
||||
|
||||
defp do_drop_while([h|t], fun) do
|
||||
@@ -833,15 +918,15 @@ defmodule Enum do
|
||||
[]
|
||||
end
|
||||
|
||||
defp do_drop_while({ h, next } = extra, iterator, fun, module) do
|
||||
defp do_drop_while({ h, next } = extra, iterator, fun) do
|
||||
if fun.(h) do
|
||||
do_drop_while(iterator.(next), iterator, fun, module)
|
||||
do_drop_while(iterator.(next), iterator, fun)
|
||||
else
|
||||
module.to_list(extra, iterator)
|
||||
to_list(extra, iterator)
|
||||
end
|
||||
end
|
||||
|
||||
defp do_drop_while(:stop, _, _, _) do
|
||||
defp do_drop_while(:stop, _, _) do
|
||||
[]
|
||||
end
|
||||
|
||||
@@ -954,16 +1039,6 @@ defmodule Enum do
|
||||
[]
|
||||
end
|
||||
|
||||
## nth
|
||||
|
||||
defp do_nth!([h|_], 1), do: h
|
||||
defp do_nth!([_|t], n), do: do_nth!(t, n - 1)
|
||||
defp do_nth!([], _), do: raise Enum.OutOfBoundsError
|
||||
|
||||
defp do_nth!({ h, _next }, _iterator, 1), do: h
|
||||
defp do_nth!({ _, next }, iterator, n), do: do_nth!(iterator.(next), iterator, n - 1)
|
||||
defp do_nth!(:stop, _iterator, _), do: raise Enum.OutOfBoundsError
|
||||
|
||||
## reduce
|
||||
|
||||
defp do_reduce({ h, next }, iterator, acc, fun) do
|
||||
@@ -974,30 +1049,30 @@ defmodule Enum do
|
||||
acc
|
||||
end
|
||||
|
||||
## split_with
|
||||
## split_while
|
||||
|
||||
defp do_split_with([h|t], fun, acc) do
|
||||
defp do_split_while([h|t], fun, acc) do
|
||||
if fun.(h) do
|
||||
do_split_with(t, fun, [h|acc])
|
||||
do_split_while(t, fun, [h|acc])
|
||||
else
|
||||
{ List.reverse(acc), [h|t] }
|
||||
{ :lists.reverse(acc), [h|t] }
|
||||
end
|
||||
end
|
||||
|
||||
defp do_split_with([], _, acc) do
|
||||
{ List.reverse(acc), [] }
|
||||
defp do_split_while([], _, acc) do
|
||||
{ :lists.reverse(acc), [] }
|
||||
end
|
||||
|
||||
defp do_split_with({ h, next } = extra, iterator, fun, acc, module) do
|
||||
defp do_split_while({ h, next } = extra, iterator, fun, acc) do
|
||||
if fun.(h) do
|
||||
do_split_with(iterator.(next), iterator, fun, [h|acc], module)
|
||||
do_split_while(iterator.(next), iterator, fun, [h|acc])
|
||||
else
|
||||
{ List.reverse(acc), module.to_list(extra, iterator) }
|
||||
{ :lists.reverse(acc), to_list(extra, iterator) }
|
||||
end
|
||||
end
|
||||
|
||||
defp do_split_with(:stop, _, _, acc, _module) do
|
||||
{ List.reverse(acc), [] }
|
||||
defp do_split_while(:stop, _, _, acc) do
|
||||
{ :lists.reverse(acc), [] }
|
||||
end
|
||||
|
||||
## join
|
||||
@@ -1007,7 +1082,7 @@ defmodule Enum do
|
||||
end
|
||||
|
||||
defp do_join([h|t], joiner, acc) do
|
||||
acc = << acc | :binary, joiner | :binary, to_binary(h) | :binary >>
|
||||
acc = << acc :: binary, joiner :: binary, to_binary(h) :: binary >>
|
||||
do_join(t, joiner, acc)
|
||||
end
|
||||
|
||||
@@ -1020,7 +1095,7 @@ defmodule Enum do
|
||||
end
|
||||
|
||||
defp do_join({ h, next }, iterator, joiner, acc) do
|
||||
acc = << acc | :binary, joiner | :binary, to_binary(h) | :binary >>
|
||||
acc = << acc :: binary, joiner :: binary, to_binary(h) :: binary >>
|
||||
do_join(iterator.(next), iterator, joiner, acc)
|
||||
end
|
||||
|
||||
@@ -1035,7 +1110,7 @@ defmodule Enum do
|
||||
end
|
||||
|
||||
defp do_map_join([h|t], mapper, joiner, acc) do
|
||||
acc = << acc | :binary, joiner | :binary, to_binary(mapper.(h)) | :binary >>
|
||||
acc = << acc :: binary, joiner :: binary, to_binary(mapper.(h)) :: binary >>
|
||||
do_map_join(t, mapper, joiner, acc)
|
||||
end
|
||||
|
||||
@@ -1048,7 +1123,7 @@ defmodule Enum do
|
||||
end
|
||||
|
||||
defp do_map_join({ h, next }, iterator, mapper, joiner, acc) do
|
||||
acc = << acc | :binary, joiner | :binary, to_binary(mapper.(h)) | :binary >>
|
||||
acc = << acc :: binary, joiner :: binary, to_binary(mapper.(h)) :: binary >>
|
||||
do_map_join(iterator.(next), iterator, mapper, joiner, acc)
|
||||
end
|
||||
|
||||
@@ -1074,7 +1149,7 @@ defmodule Enum do
|
||||
end
|
||||
|
||||
defp do_map_reduce(:stop, _, list_acc, acc, _f) do
|
||||
{ List.reverse(list_acc), acc }
|
||||
{ :lists.reverse(list_acc), acc }
|
||||
end
|
||||
|
||||
## partition
|
||||
@@ -1088,7 +1163,7 @@ defmodule Enum do
|
||||
end
|
||||
|
||||
defp do_partition([], _, acc1, acc2) do
|
||||
{ List.reverse(acc1), List.reverse(acc2) }
|
||||
{ :lists.reverse(acc1), :lists.reverse(acc2) }
|
||||
end
|
||||
|
||||
defp do_partition({ h, next }, iterator, fun, acc1, acc2) do
|
||||
@@ -1100,7 +1175,17 @@ defmodule Enum do
|
||||
end
|
||||
|
||||
defp do_partition(:stop, _, _, acc1, acc2) do
|
||||
{ List.reverse(acc1), List.reverse(acc2) }
|
||||
{ :lists.reverse(acc1), :lists.reverse(acc2) }
|
||||
end
|
||||
|
||||
## reverse
|
||||
|
||||
defp do_reverse({ h, next }, iterator, acc) do
|
||||
do_reverse(iterator.(next), iterator, [h|acc])
|
||||
end
|
||||
|
||||
defp do_reverse(:stop, _, acc) do
|
||||
acc
|
||||
end
|
||||
|
||||
## qsort (lists)
|
||||
@@ -1160,23 +1245,23 @@ defmodule Enum do
|
||||
end
|
||||
|
||||
defp do_split(list, 0, acc) do
|
||||
{ List.reverse(acc), list }
|
||||
{ :lists.reverse(acc), list }
|
||||
end
|
||||
|
||||
defp do_split([], _, acc) do
|
||||
{ List.reverse(acc), [] }
|
||||
{ :lists.reverse(acc), [] }
|
||||
end
|
||||
|
||||
defp do_split({ h, next }, iterator, counter, acc, module) when counter > 0 do
|
||||
do_split(iterator.(next), iterator, counter - 1, [h|acc], module)
|
||||
defp do_split({ h, next }, iterator, counter, acc) when counter > 0 do
|
||||
do_split(iterator.(next), iterator, counter - 1, [h|acc])
|
||||
end
|
||||
|
||||
defp do_split(extra, iterator, 0, acc, module) do
|
||||
{ List.reverse(acc), module.to_list(extra, iterator) }
|
||||
defp do_split(extra, iterator, 0, acc) do
|
||||
{ :lists.reverse(acc), to_list(extra, iterator) }
|
||||
end
|
||||
|
||||
defp do_split(:stop, _, _, acc, _module) do
|
||||
{ List.reverse(acc), [] }
|
||||
defp do_split(:stop, _, _, acc) do
|
||||
{ :lists.reverse(acc), [] }
|
||||
end
|
||||
|
||||
## take_while
|
||||
@@ -1207,7 +1292,7 @@ defmodule Enum do
|
||||
|
||||
## times
|
||||
|
||||
defp do_times_0(limit, counter, _function) when counter > limit do
|
||||
defp do_times_0(limit, counter, _function) when counter >= limit do
|
||||
end
|
||||
|
||||
defp do_times_0(limit, counter, function) do
|
||||
@@ -1215,7 +1300,7 @@ defmodule Enum do
|
||||
do_times_0(limit, 1 + counter, function)
|
||||
end
|
||||
|
||||
defp do_times_1(limit, counter, _function) when counter > limit do
|
||||
defp do_times_1(limit, counter, _function) when counter >= limit do
|
||||
end
|
||||
|
||||
defp do_times_1(limit, counter, function) do
|
||||
@@ -1223,7 +1308,7 @@ defmodule Enum do
|
||||
do_times_1(limit, 1 + counter, function)
|
||||
end
|
||||
|
||||
defp do_times_2(limit, counter, _function, acc) when counter > limit do
|
||||
defp do_times_2(limit, counter, _function, acc) when counter >= limit do
|
||||
acc
|
||||
end
|
||||
|
||||
@@ -1231,25 +1316,52 @@ defmodule Enum do
|
||||
new_acc = function.(counter, acc)
|
||||
do_times_2(limit, 1 + counter, function, new_acc)
|
||||
end
|
||||
|
||||
## zip
|
||||
|
||||
defp do_zip([h1|next1], other) do
|
||||
{ h2, next2 } = do_zip_next(other)
|
||||
[{ h1, h2 }|do_zip(next1, next2)]
|
||||
end
|
||||
|
||||
defp do_zip([], _) do
|
||||
[]
|
||||
end
|
||||
|
||||
defp do_zip({ h1, next1 }, iterator, other) do
|
||||
{ h2, next2 } = do_zip_next(other)
|
||||
[{ h1, h2 }|do_zip(iterator.(next1), iterator, next2)]
|
||||
end
|
||||
|
||||
defp do_zip(:stop, _, _) do
|
||||
[]
|
||||
end
|
||||
|
||||
defp do_zip_next([h|t]), do: { h, t }
|
||||
defp do_zip_next([]), do: { nil, [] }
|
||||
|
||||
defp do_zip_next({ iterator, { h, next } }) do
|
||||
{ h, { iterator, iterator.(next) } }
|
||||
end
|
||||
|
||||
defp do_zip_next({ _iterator, :stop } = i) do
|
||||
{ nil, i }
|
||||
end
|
||||
end
|
||||
|
||||
defimpl Enum.Iterator, for: List do
|
||||
def iterator(list), do: list
|
||||
def count(list), do: length(list)
|
||||
end
|
||||
|
||||
defimpl Enum.OrdIterator, for: List do
|
||||
def iterator(list), do: list
|
||||
def to_list({ h, next }, _), do: [h|next]
|
||||
def iterator(list), do: list
|
||||
def count(list), do: length(list)
|
||||
end
|
||||
|
||||
defimpl Enum.Iterator, for: Function do
|
||||
def iterator(function) do
|
||||
{ function, function.(:start) }
|
||||
function.()
|
||||
end
|
||||
|
||||
def count(function) do
|
||||
do_count(function.(:start), function, 0)
|
||||
{ function, first } = function.()
|
||||
do_count(first, function, 0)
|
||||
end
|
||||
|
||||
defp do_count({ _, next }, function, acc) do
|
||||
@@ -1260,17 +1372,3 @@ defimpl Enum.Iterator, for: Function do
|
||||
acc
|
||||
end
|
||||
end
|
||||
|
||||
defimpl Enum.OrdIterator, for: Function do
|
||||
def iterator(function) do
|
||||
{ function, function.(:start) }
|
||||
end
|
||||
|
||||
def to_list({ h, next }, function) do
|
||||
[h|to_list(function.(next), function)]
|
||||
end
|
||||
|
||||
def to_list(:stop, _function) do
|
||||
[]
|
||||
end
|
||||
end
|
||||
@@ -60,7 +60,7 @@ defmodule Exception do
|
||||
@doc false
|
||||
def check!(module) do
|
||||
unless :erlang.function_exported(module, :message, 1) do
|
||||
raise "Expected #{inspect module} to implement message/1"
|
||||
raise "Expected #{inspect module} to implement message/1"
|
||||
end
|
||||
end
|
||||
|
||||
@@ -70,7 +70,10 @@ defmodule Exception do
|
||||
of arguments. It follows the same syntax as in stacktraces.
|
||||
"""
|
||||
def format_module_fun_arity(module, fun, arity) do
|
||||
<< ?:, fun | :binary >> = inspect(fun)
|
||||
case inspect(fun) do
|
||||
<< ?:, fun :: binary >> -> :ok
|
||||
fun -> :ok
|
||||
end
|
||||
|
||||
if is_list(arity) do
|
||||
inspected = lc x inlist arity, do: inspect(x)
|
||||
@@ -80,6 +83,16 @@ defmodule Exception do
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns the stacktrace as a binary formatted as per `format_stacktrace/1`.
|
||||
"""
|
||||
def formatted_stacktrace(trace // System.stacktrace) do
|
||||
case trace do
|
||||
[] -> ""
|
||||
s -> " " <> Enum.map_join(s, "\n ", format_stacktrace(&1)) <> "\n"
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Formats each line in the stacktrace.
|
||||
"""
|
||||
@@ -89,7 +102,7 @@ defmodule Exception do
|
||||
|
||||
@doc """
|
||||
Formats file and line information present in stacktraces.
|
||||
Expect them to be given in a keywords list.
|
||||
Expect them to be given in a keyword list.
|
||||
"""
|
||||
def format_file_line(file_line) do
|
||||
format_file_line(Keyword.get(file_line, :file), Keyword.get(file_line, :line))
|
||||
@@ -210,4 +223,4 @@ defexception Keyword.KeyError, key: nil do
|
||||
end
|
||||
end
|
||||
|
||||
defexception Enum.OutOfBoundsError, message: "out of bounds error"
|
||||
defexception Enum.OutOfBoundsError, message: "out of bounds error"
|
||||
|
||||
+77
-60
@@ -1,33 +1,35 @@
|
||||
defrecord File.Stat, Record.extract(:file_info, from_lib: "kernel/include/file.hrl"), moduledoc: """
|
||||
A record responsible to hold file information. Its fields are:
|
||||
defrecord File.Stat, Record.extract(:file_info, from_lib: "kernel/include/file.hrl") do
|
||||
@moduledoc """
|
||||
A record responsible to hold file information. Its fields are:
|
||||
|
||||
* `size` - Size of file in bytes.
|
||||
* `type` - `:device`, `:directory`, `:regular`, `:other`. The type of the file.
|
||||
* `access` - `:read`, `:write`, `:read_write`, `:none`. The current system access to
|
||||
the file.
|
||||
* `atime` - The last time the file was read.
|
||||
* `mtime` - The last time the file was written.
|
||||
* `ctime` - The interpretation of this time field depends on the operating
|
||||
system. On Unix, it is the last time the file or the inode was
|
||||
changed. In Windows, it is the create time.
|
||||
* `mode` - The file permissions.
|
||||
* `links` - The number of links to this file. This is always 1 for file
|
||||
systems which have no concept of links.
|
||||
* `major_device` - Identifies the file system where the file is located.
|
||||
In windows, the number indicates a drive as follows:
|
||||
0 means A:, 1 means B:, and so on.
|
||||
* `minor_device` - Only valid for character devices on Unix. In all other
|
||||
cases, this field is zero.
|
||||
* `inode` - Gives the inode number. On non-Unix file systems, this field
|
||||
will be zero.
|
||||
* `uid` - Indicates the owner of the file.
|
||||
* `gid` - Gives the group that the owner of the file belongs to. Will be
|
||||
zero for non-Unix file systems.
|
||||
* `size` - Size of file in bytes.
|
||||
* `type` - `:device`, `:directory`, `:regular`, `:other`. The type of the file.
|
||||
* `access` - `:read`, `:write`, `:read_write`, `:none`. The current system access to
|
||||
the file.
|
||||
* `atime` - The last time the file was read.
|
||||
* `mtime` - The last time the file was written.
|
||||
* `ctime` - The interpretation of this time field depends on the operating
|
||||
system. On Unix, it is the last time the file or the inode was
|
||||
changed. In Windows, it is the create time.
|
||||
* `mode` - The file permissions.
|
||||
* `links` - The number of links to this file. This is always 1 for file
|
||||
systems which have no concept of links.
|
||||
* `major_device` - Identifies the file system where the file is located.
|
||||
In windows, the number indicates a drive as follows:
|
||||
0 means A:, 1 means B:, and so on.
|
||||
* `minor_device` - Only valid for character devices on Unix. In all other
|
||||
cases, this field is zero.
|
||||
* `inode` - Gives the inode number. On non-Unix file systems, this field
|
||||
will be zero.
|
||||
* `uid` - Indicates the owner of the file.
|
||||
* `gid` - Gives the group that the owner of the file belongs to. Will be
|
||||
zero for non-Unix file systems.
|
||||
|
||||
The time type returned in `atime`, `mtime`, and `ctime` is dependent on the
|
||||
time type set in options. `{:time, type}` where type can be `:local`,
|
||||
`:universal`, or `:posix`. Default is `:local`.
|
||||
"""
|
||||
The time type returned in `atime`, `mtime`, and `ctime` is dependent on the
|
||||
time type set in options. `{:time, type}` where type can be `:local`,
|
||||
`:universal`, or `:posix`. Default is `:local`.
|
||||
"""
|
||||
end
|
||||
|
||||
defexception File.Error, [reason: nil, action: "", path: nil] do
|
||||
def message(exception) do
|
||||
@@ -85,11 +87,16 @@ defmodule File do
|
||||
should be used when the developer expects his software
|
||||
to fail in case the file cannot be read (i.e. it is
|
||||
literally an exception).
|
||||
|
||||
Finally, the functions in this module accept either
|
||||
a char lists or a binary. When manipulating paths, a char
|
||||
list is returned if one is given as argument. However,
|
||||
when reading files, binaries are always returned.
|
||||
"""
|
||||
|
||||
alias Erlang.file, as: F
|
||||
alias Erlang.filename, as: FN
|
||||
alias Erlang.filelib, as: FL
|
||||
alias :file, as: F
|
||||
alias :filename, as: FN
|
||||
alias :filelib, as: FL
|
||||
|
||||
@doc """
|
||||
Expands the path by returning its absolute name and expanding
|
||||
@@ -385,7 +392,7 @@ defmodule File do
|
||||
On some platforms, `:enoent` is returned instead.
|
||||
* :enomem - There is not enough memory for the contents of the file.
|
||||
|
||||
You can use `Erlang.file.format_error(reason)` to get a descriptive string of the error.
|
||||
You can use `:file.format_error(reason)` to get a descriptive string of the error.
|
||||
"""
|
||||
def read(path) do
|
||||
F.read_file(path)
|
||||
@@ -456,12 +463,12 @@ defmodule File do
|
||||
|
||||
"""
|
||||
def wildcard(glob) when is_binary(glob) do
|
||||
paths = Erlang.elixir_glob.wildcard binary_to_list(glob)
|
||||
paths = :elixir_glob.wildcard binary_to_list(glob)
|
||||
Enum.map paths, list_to_binary(&1)
|
||||
end
|
||||
|
||||
def wildcard(glob) when is_list(glob) do
|
||||
Erlang.elixir_glob.wildcard(glob)
|
||||
:elixir_glob.wildcard(glob)
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -487,16 +494,6 @@ defmodule File do
|
||||
end
|
||||
end
|
||||
|
||||
def read_info(path, opts // []) do
|
||||
IO.puts "File.read_info is deprecated in favor of File.stat"
|
||||
stat(path, opts)
|
||||
end
|
||||
|
||||
def read_info!(path, opts // []) do
|
||||
IO.puts "File.read_info! is deprecated in favor of File.stat"
|
||||
stat!(path, opts)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Same as `stat` but returns the `File.Stat` directly and
|
||||
throws `File.Error` if an error is returned.
|
||||
@@ -514,7 +511,7 @@ defmodule File do
|
||||
path. Returns `:ok` or `{ :error, reason }`.
|
||||
"""
|
||||
def write_stat(path, File.Stat[] = stat, opts // []) do
|
||||
F.write_file_info(path, setelem(stat, 1, :file_info), opts)
|
||||
F.write_file_info(path, setelem(stat, 0, :file_info), opts)
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -738,16 +735,25 @@ defmodule File do
|
||||
acc
|
||||
end
|
||||
|
||||
defp copy_file_mode!(src, dest) do
|
||||
src_stat = File.stat!(src)
|
||||
dest_stat = File.stat!(dest)
|
||||
File.write_stat!(dest, File.Stat.mode(File.Stat.mode(src_stat), dest_stat))
|
||||
end
|
||||
|
||||
# Both src and dest are files.
|
||||
defp do_cp_file(src, dest, callback, acc) do
|
||||
case copy(src, { dest, [:exclusive] }) do
|
||||
{ :ok, _ } ->
|
||||
copy_file_mode!(src, dest)
|
||||
[dest|acc]
|
||||
{ :error, :eexist } ->
|
||||
if callback.(src, dest) do
|
||||
rm(dest)
|
||||
case copy(src, dest) do
|
||||
{ :ok, _ } -> [dest|acc]
|
||||
{ :ok, _ } ->
|
||||
copy_file_mode!(src, dest)
|
||||
[dest|acc]
|
||||
reason -> reason
|
||||
end
|
||||
else
|
||||
@@ -1017,9 +1023,9 @@ defmodule File do
|
||||
|
||||
## Examples
|
||||
|
||||
File.open!("foo.txt", [:read, :write], fn(file) ->
|
||||
IO.readline(file)
|
||||
end)
|
||||
File.open!("foo.txt", [:read, :write], fn(file) ->
|
||||
IO.readline(file)
|
||||
end)
|
||||
|
||||
"""
|
||||
def open(path, modes, function) do
|
||||
@@ -1146,7 +1152,7 @@ defmodule File do
|
||||
source = File.iterator("README.md")
|
||||
File.open "NEWREADME.md", [:write], fn(target) ->
|
||||
Enum.each source, fn(line) ->
|
||||
IO.write target, Regex.replace_all(%r/"/, line, "'")
|
||||
IO.write target, Regex.replace(%r/"/, line, "'")
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1158,19 +1164,30 @@ defmodule File do
|
||||
end
|
||||
|
||||
def iterator(device) do
|
||||
fn(_) ->
|
||||
case :io.get_line(device, "") do
|
||||
:eof ->
|
||||
close(device)
|
||||
:stop
|
||||
{ :error, reason } ->
|
||||
raise File.IteratorError, reason: reason
|
||||
data ->
|
||||
{ data, :ok }
|
||||
fn ->
|
||||
function = fn(_) ->
|
||||
case :io.get_line(device, "") do
|
||||
:eof ->
|
||||
close(device)
|
||||
:stop
|
||||
{ :error, reason } ->
|
||||
raise File.IteratorError, reason: reason
|
||||
data ->
|
||||
{ strip_ending(data), :ok }
|
||||
end
|
||||
end
|
||||
{ function, function.(:start) }
|
||||
end
|
||||
end
|
||||
|
||||
defp strip_ending("\r\n"), do: <<>>
|
||||
defp strip_ending("\n"), do: <<>>
|
||||
defp strip_ending(""), do: <<>>
|
||||
|
||||
defp strip_ending(<< h, t :: binary >>) do
|
||||
<< h, strip_ending(t) :: binary >>
|
||||
end
|
||||
|
||||
@doc """
|
||||
Opens the given `file` with the given `mode` and
|
||||
returns its iterator. Fails for the same reasons
|
||||
@@ -1209,7 +1226,7 @@ defmodule File do
|
||||
end
|
||||
|
||||
defp normalize([], acc) do
|
||||
join List.reverse(acc)
|
||||
join Enum.reverse(acc)
|
||||
end
|
||||
|
||||
defp open_defaults([:charlist|t], add_encoding, _add_binary) do
|
||||
|
||||
@@ -1,48 +0,0 @@
|
||||
defmodule GenServer.Behavior do
|
||||
@doc """
|
||||
By using this module, you get default GenServer callbacks
|
||||
for `handle_call`, `handle_info`, `handle_cast`, `terminate`
|
||||
and `code_change`. `init` still needs to be implemented by the
|
||||
developer. Since these functions are defined as overridable,
|
||||
they can be partially customized and have a global clause
|
||||
that simply invokes `super`. See `ExUnit.Server` for some
|
||||
code examples.
|
||||
|
||||
This module also tags the behavior as :gen_server. For more
|
||||
information on gen_server, please refer to the Erlang
|
||||
documentation:
|
||||
|
||||
http://www.erlang.org/doc/man/gen_server.html
|
||||
http://www.erlang.org/doc/design_principles/gen_server_concepts.html
|
||||
|
||||
"""
|
||||
defmacro __using__(_) do
|
||||
quote location: :keep do
|
||||
@behavior :gen_server
|
||||
|
||||
def handle_call(_request, _from, state) do
|
||||
{ :reply, :undef, state }
|
||||
end
|
||||
|
||||
def handle_info(_msg, state) do
|
||||
{ :noreply, state }
|
||||
end
|
||||
|
||||
def handle_cast(_msg, state) do
|
||||
{ :noreply, state }
|
||||
end
|
||||
|
||||
def terminate(reason, state) do
|
||||
:error_logger.error_report('#{inspect __MODULE__} crashed:\n#{inspect reason}')
|
||||
:error_logger.error_report('#{inspect __MODULE__} snapshot:\n#{inspect state}')
|
||||
:ok
|
||||
end
|
||||
|
||||
def code_change(_old, state, _extra) do
|
||||
{ :ok, state }
|
||||
end
|
||||
|
||||
defoverridable [handle_call: 3, handle_info: 2, handle_cast: 2, terminate: 2, code_change: 3]
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,88 @@
|
||||
defmodule GenServer.Behavior do
|
||||
@moduledoc false
|
||||
|
||||
defmacro __using__(_) do
|
||||
IO.puts "Using GenServer.Behavior is deprecated, use GenServer.Behaviour instead"
|
||||
quote do
|
||||
use GenServer.Behaviour
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defmodule GenServer.Behaviour do
|
||||
@moduledoc """
|
||||
By using this module, you get default GenServer callbacks
|
||||
for `init`, `handle_call`, `handle_info`, `handle_cast`,
|
||||
`terminate` and `code_change`. Since these functions are
|
||||
defined as overridable, they can be customized and fallback
|
||||
to the default behaviour by calling `super`.
|
||||
|
||||
This module also tags the behavior as :gen_server. For more
|
||||
information on gen_server, please refer to the Erlang
|
||||
documentation:
|
||||
|
||||
http://www.erlang.org/doc/man/gen_server.html
|
||||
http://www.erlang.org/doc/design_principles/gen_server_concepts.html
|
||||
|
||||
## Example
|
||||
|
||||
defmodule MyServer do
|
||||
use GenServer.Behaviour
|
||||
|
||||
# Callbacks
|
||||
|
||||
def handle_call(:peek, _from, [h|_] = state) do
|
||||
{ :reply, h, state }
|
||||
end
|
||||
|
||||
# Default behaviour
|
||||
def handle_call(request, from, config) do
|
||||
super(request, from, config)
|
||||
end
|
||||
|
||||
def handle_cast({ :push, item }, state) do
|
||||
{ :noreply, [item|state] }
|
||||
end
|
||||
|
||||
# Default cast behaviour
|
||||
def handle_cast(request, config) do
|
||||
super(request, config)
|
||||
end
|
||||
end
|
||||
|
||||
"""
|
||||
|
||||
@doc false
|
||||
defmacro __using__(_) do
|
||||
quote location: :keep do
|
||||
@behavior :gen_server
|
||||
|
||||
def init(args) do
|
||||
{ :ok, args }
|
||||
end
|
||||
|
||||
def handle_call(_request, _from, state) do
|
||||
{ :reply, :undef, state }
|
||||
end
|
||||
|
||||
def handle_info(_msg, state) do
|
||||
{ :noreply, state }
|
||||
end
|
||||
|
||||
def handle_cast(_msg, state) do
|
||||
{ :noreply, state }
|
||||
end
|
||||
|
||||
def terminate(reason, state) do
|
||||
:ok
|
||||
end
|
||||
|
||||
def code_change(_old, state, _extra) do
|
||||
{ :ok, state }
|
||||
end
|
||||
|
||||
defoverridable [init: 1, handle_call: 3, handle_info: 2,
|
||||
handle_cast: 2, terminate: 2, code_change: 3]
|
||||
end
|
||||
end
|
||||
end
|
||||
+37
-21
@@ -1,81 +1,97 @@
|
||||
defimpl Dict, for: HashDict do
|
||||
defmodule HashDict do
|
||||
@moduledoc """
|
||||
This module implements a dictionary type 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` module.
|
||||
|
||||
Check the `Dict` module for examples and documentation.
|
||||
"""
|
||||
|
||||
use Dict.Common
|
||||
|
||||
defmacrop dict(data) do
|
||||
quote do
|
||||
{ HashDict, unquote(data) }
|
||||
end
|
||||
end
|
||||
|
||||
@doc false
|
||||
def keys(dict(data)) do
|
||||
:dict.fetch_keys data
|
||||
end
|
||||
|
||||
@doc false
|
||||
def values(dict(data)) do
|
||||
:dict.fold fn _key, value, acc ->
|
||||
[value|acc]
|
||||
end, [], data
|
||||
end
|
||||
|
||||
@doc false
|
||||
def size(dict(data)) do
|
||||
:dict.size data
|
||||
end
|
||||
|
||||
@doc false
|
||||
def has_key?(dict(data), key) do
|
||||
:dict.is_key key, data
|
||||
end
|
||||
|
||||
def get(dict(data), key, default // nil) do
|
||||
@doc false
|
||||
def get(dict(data), key, default) do
|
||||
case :dict.find(key, data) do
|
||||
{:ok, value} ->
|
||||
value
|
||||
:error ->
|
||||
default
|
||||
{:ok, value} -> value
|
||||
:error -> default
|
||||
end
|
||||
end
|
||||
|
||||
@doc false
|
||||
def put(dict(data), key, value) do
|
||||
dict(:dict.store key, value, data)
|
||||
end
|
||||
|
||||
@doc false
|
||||
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
|
||||
|
||||
@doc false
|
||||
def merge(dict(d1), dict(d2), fun) do
|
||||
dict(:dict.merge fun, d1, d2)
|
||||
end
|
||||
|
||||
@doc false
|
||||
def merge(dict(_) = d1, d2, fun) do
|
||||
merge(d1, new(d2), fun)
|
||||
end
|
||||
|
||||
@doc false
|
||||
def update(dict(data), key, fun) do
|
||||
dict(:dict.update key, fun, data)
|
||||
end
|
||||
|
||||
@doc false
|
||||
def update(dict(data), key, initial, fun) do
|
||||
dict(:dict.update key, fun, initial, data)
|
||||
end
|
||||
|
||||
@doc false
|
||||
def empty(_) do
|
||||
dict(:dict.new)
|
||||
end
|
||||
|
||||
@doc false
|
||||
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)
|
||||
def iterator(dict), do: HashDict.to_list(dict)
|
||||
def count(dict), do: HashDict.size(dict)
|
||||
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
|
||||
defimpl Access, for: HashDict do
|
||||
def access(dict, key), do: HashDict.get(dict, key, nil)
|
||||
end
|
||||
@@ -1,45 +0,0 @@
|
||||
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
|
||||
+20
-17
@@ -1,15 +1,23 @@
|
||||
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:
|
||||
Module responsible for doing IO. Many functions in this
|
||||
module expects an IO device and an io data encoded in UTF-8.
|
||||
|
||||
An IO device must be a pid is an atom representing a process.
|
||||
For convenience, Elixir provides `:stdio` and `:stderr` as
|
||||
shortcut to Erlang's `:standard_io` and `:standard_error`.
|
||||
|
||||
An io data 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;
|
||||
|
||||
@@ -27,7 +35,7 @@ defmodule IO do
|
||||
NFS file system.
|
||||
"""
|
||||
def read(device // :stdio, count) do
|
||||
Erlang.io.get_chars(map_dev(device), "", count)
|
||||
:io.get_chars(map_dev(device), "", count)
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -45,7 +53,7 @@ defmodule IO do
|
||||
except the prompt is not required as argument.
|
||||
"""
|
||||
def readline(device // :stdio) do
|
||||
Erlang.io.get_line(map_dev(device), "")
|
||||
:io.get_line(map_dev(device), "")
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -66,12 +74,7 @@ defmodule IO do
|
||||
|
||||
"""
|
||||
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)
|
||||
:io.put_chars map_dev(device), to_iodata(item)
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -81,16 +84,16 @@ defmodule IO do
|
||||
"""
|
||||
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)
|
||||
:io.put_chars erl_dev, to_iodata(item)
|
||||
: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)
|
||||
def inspect(device // :stdio, item, opts // []) do
|
||||
puts device, Binary.Inspect.inspect(item, opts)
|
||||
item
|
||||
end
|
||||
|
||||
@@ -106,7 +109,7 @@ defmodule IO do
|
||||
NFS file system.
|
||||
"""
|
||||
def getb(device // :stdio, prompt, count // 1) do
|
||||
Erlang.io.get_chars(map_dev(device), to_iodata(prompt), count)
|
||||
:io.get_chars(map_dev(device), to_iodata(prompt), count)
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -122,7 +125,7 @@ defmodule IO do
|
||||
NFS file system.
|
||||
"""
|
||||
def gets(device // :stdio, prompt) do
|
||||
Erlang.io.get_line(map_dev(device), to_iodata(prompt))
|
||||
:io.get_line(map_dev(device), to_iodata(prompt))
|
||||
end
|
||||
|
||||
# Map the Elixir names for standard io and error to Erlang names
|
||||
|
||||
+507
-212
File diff suppressed because it is too large
Load Diff
@@ -4,17 +4,15 @@ defrecord Kernel.CLI.Config, commands: [], output: ".",
|
||||
defmodule Kernel.CLI do
|
||||
@moduledoc false
|
||||
|
||||
import Exception, only: [format_stacktrace: 1]
|
||||
|
||||
# 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, Kernel.CLI.Config.new)
|
||||
|
||||
argv = lc arg inlist argv, do: list_to_binary(arg)
|
||||
Erlang.gen_server.call(:elixir_code_server, { :argv, argv })
|
||||
:gen_server.call(:elixir_code_server, { :argv, argv })
|
||||
|
||||
all_commands = List.reverse(config.commands)
|
||||
all_commands = Enum.reverse(config.commands)
|
||||
|
||||
try do
|
||||
Enum.map all_commands, process_command(&1, config)
|
||||
@@ -25,9 +23,9 @@ defmodule Kernel.CLI do
|
||||
rescue
|
||||
exception ->
|
||||
at_exit(1)
|
||||
stacktrace = System.stacktrace
|
||||
trace = System.stacktrace
|
||||
IO.puts :stderr, "** (#{inspect exception.__record__(:name)}) #{exception.message}"
|
||||
print_stacktrace(stacktrace)
|
||||
IO.puts Exception.formatted_stacktrace(trace)
|
||||
halt(1)
|
||||
catch
|
||||
:exit, reason when is_integer(reason) ->
|
||||
@@ -38,9 +36,9 @@ defmodule Kernel.CLI do
|
||||
halt(0)
|
||||
kind, reason ->
|
||||
at_exit(1)
|
||||
stacktrace = System.stacktrace
|
||||
trace = System.stacktrace
|
||||
IO.puts :stderr, "** (#{kind}) #{inspect(reason)}"
|
||||
print_stacktrace(stacktrace)
|
||||
IO.puts Exception.formatted_stacktrace(trace)
|
||||
halt(1)
|
||||
end
|
||||
end
|
||||
@@ -48,18 +46,20 @@ defmodule Kernel.CLI do
|
||||
## Private
|
||||
|
||||
defp at_exit(status) do
|
||||
hooks = Erlang.gen_server.call(:elixir_code_server, :at_exit)
|
||||
hooks = :gen_server.call(:elixir_code_server, :at_exit)
|
||||
lc hook inlist hooks do
|
||||
try do
|
||||
hook.(status)
|
||||
rescue
|
||||
exception ->
|
||||
trace = System.stacktrace
|
||||
IO.puts :stderr, "** (#{inspect exception.__record__(:name)}) #{exception.message}"
|
||||
print_stacktrace(System.stacktrace)
|
||||
IO.puts Exception.formatted_stacktrace(trace)
|
||||
catch
|
||||
kind, reason ->
|
||||
trace = System.stacktrace
|
||||
IO.puts :stderr, "** #{kind} #{inspect(reason)}"
|
||||
print_stacktrace(System.stacktrace)
|
||||
IO.puts Exception.formatted_stacktrace(trace)
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -78,10 +78,6 @@ defmodule Kernel.CLI do
|
||||
end
|
||||
end
|
||||
|
||||
defp print_stacktrace(stacktrace) do
|
||||
Enum.each stacktrace, fn s -> IO.puts :stderr, " #{format_stacktrace(s)}" end
|
||||
end
|
||||
|
||||
# Process shared options
|
||||
|
||||
defp process_shared(['-v'|t], config) do
|
||||
@@ -202,7 +198,7 @@ defmodule Kernel.CLI do
|
||||
# Process commands
|
||||
|
||||
defp process_command({:eval, expr}, _config) when is_list(expr) do
|
||||
Erlang.elixir.eval(expr, [])
|
||||
:elixir.eval(expr, [])
|
||||
end
|
||||
|
||||
defp process_command({:require, file}, _config) when is_binary(file) do
|
||||
|
||||
@@ -5,12 +5,12 @@ defmodule Kernel.ErrorHandler do
|
||||
|
||||
def undefined_function(module, fun, args) do
|
||||
ensure_loaded(module)
|
||||
Erlang.error_handler.undefined_function(module, fun, args)
|
||||
:error_handler.undefined_function(module, fun, args)
|
||||
end
|
||||
|
||||
def undefined_lambda(module, fun, args) do
|
||||
ensure_loaded(module)
|
||||
Erlang.error_handler.undefined_lambda(module, fun, args)
|
||||
:error_handler.undefined_lambda(module, fun, args)
|
||||
end
|
||||
|
||||
defp ensure_loaded(module) do
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
defmodule Kernel.ParallelCompiler do
|
||||
alias Erlang.orddict, as: Orddict
|
||||
alias :orddict, as: OrdDict
|
||||
|
||||
@moduledoc """
|
||||
A module responsible for compiling files in parallel.
|
||||
@@ -43,13 +43,14 @@ defmodule Kernel.ParallelCompiler do
|
||||
|
||||
child = spawn_link fn ->
|
||||
Process.put(:elixir_compiler_pid, parent)
|
||||
Process.put(:elixir_ensure_compiled, true)
|
||||
Process.flag(:error_handler, Kernel.ErrorHandler)
|
||||
|
||||
try do
|
||||
if output do
|
||||
Erlang.elixir_compiler.file_to_path(h, output)
|
||||
:elixir_compiler.file_to_path(h, output)
|
||||
else
|
||||
Erlang.elixir_compiler.file(h)
|
||||
:elixir_compiler.file(h)
|
||||
end
|
||||
parent <- { :compiled, self(), h }
|
||||
catch
|
||||
@@ -80,26 +81,26 @@ defmodule Kernel.ParallelCompiler do
|
||||
receive do
|
||||
{ :compiled, child, file } ->
|
||||
callback.(file)
|
||||
new_queued = List.keydelete(queued, child, 1)
|
||||
new_queued = List.keydelete(queued, child, 0)
|
||||
# Sometimes we may have spurious entries in the waiting
|
||||
# list because someone invoked try/rescue UndefinedFunctionError
|
||||
new_waiting = List.keydelete(waiting, child, 1)
|
||||
new_waiting = List.keydelete(waiting, child, 0)
|
||||
spawn_compilers(files, output, callback, new_waiting, new_queued, result)
|
||||
{ :module_available, child, module, binary } ->
|
||||
new_waiting = release_waiting_processes(module, waiting)
|
||||
new_result = [{module, binary}|result]
|
||||
wait_for_messages(files, output, callback, new_waiting, queued, new_result)
|
||||
{ :waiting, child, on } ->
|
||||
new_waiting = Orddict.store(child, on, waiting)
|
||||
new_waiting = OrdDict.store(child, on, waiting)
|
||||
spawn_compilers(files, output, callback, new_waiting, queued, result)
|
||||
{ :failure, child, kind, reason, stacktrace } ->
|
||||
extra = if match?({^child, module}, List.keyfind(waiting, child, 1)) do
|
||||
extra = if match?({^child, module}, List.keyfind(waiting, child, 0)) do
|
||||
" (undefined module #{inspect module})"
|
||||
end
|
||||
|
||||
{^child, file} = List.keyfind(queued, child, 1)
|
||||
{^child, file} = List.keyfind(queued, child, 0)
|
||||
IO.puts "== Compilation error on file #{file}#{extra} =="
|
||||
Erlang.erlang.raise(kind, reason, stacktrace)
|
||||
:erlang.raise(kind, reason, stacktrace)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -28,7 +28,7 @@ defmodule Kernel.ParallelRequire do
|
||||
child = spawn_link fn ->
|
||||
try do
|
||||
new = Code.require_file(h)
|
||||
result = new ++ result
|
||||
result = (new || []) ++ result
|
||||
callback.(h)
|
||||
parent <- { :required, self }
|
||||
catch
|
||||
@@ -45,7 +45,7 @@ defmodule Kernel.ParallelRequire do
|
||||
{ :required, child } ->
|
||||
spawn_requires(files, List.delete(waiting, child), callback, result)
|
||||
{ :failure, _child, kind, reason, stacktrace } ->
|
||||
Erlang.erlang.raise(kind, reason, stacktrace)
|
||||
:erlang.raise(kind, reason, stacktrace)
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -118,30 +118,38 @@ defmodule Kernel.SpecialForms do
|
||||
|
||||
## Examples
|
||||
|
||||
If you want to use the `values` function from `Keyword` several times
|
||||
in your module and you don't want to always type `Keyword.values`,
|
||||
you can simply import it:
|
||||
If you are using several functions from a given module, you can
|
||||
import those functions and reference them as local functions,
|
||||
for example:
|
||||
|
||||
defmodule Math do
|
||||
import Keyword, only: [values: 1]
|
||||
import List
|
||||
flatten([1,[2],3]) #=> [1,2,3]
|
||||
|
||||
def some_function do
|
||||
# call values(orddict)
|
||||
end
|
||||
end
|
||||
## Selector
|
||||
|
||||
In this case, we are importing only the function `values` (with arity 1)
|
||||
from `Keyword`. Although `only` is optional, its usage is recommended.
|
||||
`except` could also be given as an option. If no option is given, all
|
||||
functions and macros are imported.
|
||||
By default, Elixir imports functions and macros from the given
|
||||
module, except the ones starting with underscore (which are
|
||||
usually callbacks):
|
||||
|
||||
In case you want to import only functions or macros, you can pass a
|
||||
first argument selecting the scope:
|
||||
import List
|
||||
|
||||
import :macros, MyMacros
|
||||
A developer can change this behavior to include all macros and
|
||||
functions, regardless if it starts with underscore, by passing
|
||||
`:all` as first argument:
|
||||
|
||||
And you can then use `only` or `except` to filter the macros being
|
||||
included.
|
||||
import :all, List
|
||||
|
||||
It can also be customized to import only functions or only
|
||||
macros:
|
||||
|
||||
import :functions, List
|
||||
import :macros, List
|
||||
|
||||
Alternatively, Elixir allows a developer to specify `:only`
|
||||
or `:except` as a fine grained control on what to import (or
|
||||
not):
|
||||
|
||||
import List, only: [flatten: 1]
|
||||
|
||||
## Lexical scope
|
||||
|
||||
@@ -161,10 +169,10 @@ defmodule Kernel.SpecialForms do
|
||||
end
|
||||
end
|
||||
|
||||
In the example above, we imported macros from `MyMacros`, replacing
|
||||
the original `if/2` implementation by our own during that
|
||||
specific function. All other functions in that module will still
|
||||
be able to use the original one.
|
||||
In the example above, we imported macros from `MyMacros`,
|
||||
replacing the original `if/2` implementation by our own
|
||||
during that specific function. All other functions in that
|
||||
module will still be able to use the original one.
|
||||
|
||||
## Alias/Require shortcut
|
||||
|
||||
@@ -298,13 +306,13 @@ defmodule Kernel.SpecialForms do
|
||||
is defining new functions. Consider this example:
|
||||
|
||||
defmodule MyServer do
|
||||
use GenServer.Behavior
|
||||
use GenServer.Behaviour
|
||||
end
|
||||
|
||||
`GenServer.Behavior` defines new functions in our `MyServer` module.
|
||||
`GenServer.Behaviour` 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
|
||||
the stacktrace to point to the `GenServer.Behaviour` and not the line
|
||||
that calls `use GenServer.Behaviour`. For this reason, there is an
|
||||
option called `:location` that when set to `:keep` keeps these proper
|
||||
semantics:
|
||||
|
||||
@@ -315,9 +323,9 @@ defmodule Kernel.SpecialForms do
|
||||
end
|
||||
|
||||
It is important to warn though that `location: :keep` evaluates the
|
||||
code as if it was defined inside `GenServer.Behavior` file, in
|
||||
code as if it was defined inside `GenServer.Behaviour` file, in
|
||||
particular, the macro `__FILE__` will always point to
|
||||
`GenServer.Behavior` file.
|
||||
`GenServer.Behaviour` file.
|
||||
"""
|
||||
defmacro quote(opts, do: contents)
|
||||
|
||||
@@ -432,7 +440,7 @@ defmodule Kernel.SpecialForms do
|
||||
it belonged to another file.
|
||||
|
||||
quote location: :keep, do: 1
|
||||
#=> { :__scope__, 1,[[file: "iex"],[do: 1]] }
|
||||
#=> { :__scope__, 1,[[file: "iex"], 1] }
|
||||
|
||||
Check `quote/1` for more information.
|
||||
"""
|
||||
|
||||
@@ -29,7 +29,7 @@ defmodule Kernel.Typespec do
|
||||
to get such information.
|
||||
"""
|
||||
def get_types(module) do
|
||||
Module.read_attribute(module, :type) ++ Module.read_attribute(module, :opaque)
|
||||
Module.get_attribute(module, :type) ++ Module.get_attribute(module, :opaque)
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -48,7 +48,7 @@ defmodule Kernel.Typespec do
|
||||
|
||||
# Handle unions
|
||||
defp typespec({ :|, line, [_,_] } = exprs, vars, caller) do
|
||||
exprs = List.reverse(collect_union(exprs))
|
||||
exprs = Enum.reverse(collect_union(exprs))
|
||||
union = lc e inlist exprs, do: typespec(e, vars, caller)
|
||||
{ :type, line, :union, union }
|
||||
end
|
||||
@@ -79,7 +79,7 @@ defmodule Kernel.Typespec do
|
||||
|
||||
# Handle type operator
|
||||
defp typespec({:"::", line, [var, expr] }, vars, caller) do
|
||||
left = typespec(var, [elem(var,1)|vars], caller)
|
||||
left = typespec(var, [elem(var, 0)|vars], caller)
|
||||
right = typespec(expr, vars, caller)
|
||||
{ :ann_type, line, [left, right] }
|
||||
end
|
||||
@@ -113,8 +113,8 @@ defmodule Kernel.Typespec do
|
||||
# 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)
|
||||
case Enum.reverse(arguments) do
|
||||
[[{:do,h}]|t] -> fn_args(line, Enum.reverse(t), h, vars, caller)
|
||||
[] -> []
|
||||
_ -> [fn_args(line, arguments, vars, caller)]
|
||||
end
|
||||
@@ -220,6 +220,7 @@ defmodule Kernel.Typespec do
|
||||
end
|
||||
|
||||
defp _defspec(type, caller, {name, line, args},[{:do,return}]) do
|
||||
if is_atom(args), do: args = []
|
||||
spec = { :type, line, :fun, fn_args(line, args, return, [], caller) }
|
||||
code = Macro.escape { {type, { name, length(args) }}, [spec] }
|
||||
table = spec_table_for(caller.module)
|
||||
@@ -232,7 +233,7 @@ defmodule Kernel.Typespec do
|
||||
end
|
||||
|
||||
defp spec_table_for(module) do
|
||||
table = list_to_atom Erlang.lists.concat([:s, module])
|
||||
table = list_to_atom :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
|
||||
|
||||
+124
-64
@@ -2,8 +2,7 @@ defmodule Keyword do
|
||||
@moduledoc """
|
||||
A keyword is a list of tuples where the first element
|
||||
of the tuple is an atom and the second element can be
|
||||
any value. The list is sorted by the first element of
|
||||
each tuple.
|
||||
any value.
|
||||
|
||||
A keyword may have duplicated keys, so it is not strictly
|
||||
a dictionary. However most of the functions in this module
|
||||
@@ -12,22 +11,23 @@ defmodule Keyword do
|
||||
key, regardless if duplicated entries exist. Similarly,
|
||||
`Keyword.put` and `Keyword.delete` ensure all duplicated
|
||||
entries for a given key are removed when invoked.
|
||||
|
||||
This module uses `==` as operator to check if two keys
|
||||
are equal or not.
|
||||
"""
|
||||
|
||||
@doc """
|
||||
Creates a Keyword from enum. Differently from `Keyword.new`
|
||||
that behaves as a dict, `Keyword.from_enum` do not remove
|
||||
which behaves as a dict, `Keyword.from_enum` do not remove
|
||||
duplicated entries.
|
||||
"""
|
||||
def from_enum(enum) when is_list(enum) do
|
||||
enum
|
||||
end
|
||||
|
||||
def from_enum(enum) do
|
||||
Enum.qsort(enum)
|
||||
Enum.map(enum, fn(x) -> x end)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns an empty keywords list, i.e. an empty list.
|
||||
Returns an empty keyword list, i.e. an empty list.
|
||||
"""
|
||||
def new do
|
||||
[]
|
||||
@@ -44,7 +44,7 @@ defmodule Keyword do
|
||||
|
||||
"""
|
||||
def new(pairs) do
|
||||
Enum.reduce pairs, [], fn {k, v}, keywords ->
|
||||
Enum.reduce pairs, [], fn { k, v }, keywords ->
|
||||
put(keywords, k, v)
|
||||
end
|
||||
end
|
||||
@@ -58,6 +58,7 @@ defmodule Keyword do
|
||||
|
||||
Keyword.new [:a, :b], fn x -> {x,x} end
|
||||
#=> [a: :a, b: :b]
|
||||
|
||||
"""
|
||||
def new(pairs, transform) do
|
||||
Enum.reduce pairs, [], fn i, keywords ->
|
||||
@@ -80,12 +81,14 @@ defmodule Keyword do
|
||||
Keyword.get [a: 1], :a #=> 1
|
||||
Keyword.get [a: 1], :b #=> nil
|
||||
Keyword.get [a: 1], :b, 3 #=> 3
|
||||
|
||||
"""
|
||||
def get(keywords, key, default // nil)
|
||||
def get([{k, _}|_], key, default) when key < k, do: default
|
||||
def get([{k, _}|d], key, default) when key > k, do: get(d, key, default)
|
||||
def get([{_, value}|_], _key, _default), do: value
|
||||
def get([], _, default), do: default
|
||||
def get(keywords, key, default // nil) do
|
||||
case :lists.keyfind(key, 1, keywords) do
|
||||
{ ^key, value } -> value
|
||||
false -> default
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Gets the value for specific key. If key does not exist,
|
||||
@@ -95,11 +98,14 @@ defmodule Keyword do
|
||||
|
||||
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)
|
||||
def get!(keywords, key) do
|
||||
case :lists.keyfind(key, 1, keywords) do
|
||||
{ ^key, value } -> value
|
||||
false -> raise(Keyword.KeyError, key: key)
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Gets all values for a specific key.
|
||||
@@ -110,13 +116,12 @@ defmodule Keyword do
|
||||
#=> [1,2]
|
||||
|
||||
"""
|
||||
def get_values([{k, _}|_], key) when key < k, do: []
|
||||
def get_values([{k, _}|d], key) when key > k, do: get_values(d, key)
|
||||
def get_values([{_, value}|d], key), do: [value|get_values(d, key)]
|
||||
def get_values([], _), do: []
|
||||
def get_values(keywords, key) do
|
||||
lc { k, v } inlist keywords, key == k, do: v
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns all keys from the keywords list. Duplicated
|
||||
Returns all keys from the keyword list. Duplicated
|
||||
keys appear duplicated in the final list of keys.
|
||||
|
||||
## Examples
|
||||
@@ -134,14 +139,15 @@ defmodule Keyword do
|
||||
## Examples
|
||||
|
||||
Keyword.values [a: 1, b: 2] #=> [1,2]
|
||||
|
||||
"""
|
||||
def values(keywords) do
|
||||
lc { _, value } inlist keywords, do: value
|
||||
end
|
||||
|
||||
@doc """
|
||||
Deletes all entries in the keywords list for a specific key.
|
||||
If the key does not exist, returns the keywords list unchanged.
|
||||
Deletes all entries in the keyword list for a specific key.
|
||||
If the key does not exist, returns the keyword list unchanged.
|
||||
Use `delete_first` to delete just the first entry in case of
|
||||
duplicated keys.
|
||||
|
||||
@@ -149,11 +155,11 @@ defmodule Keyword do
|
||||
|
||||
Keyword.delete [a: 1, b: 2], :a #=> [b: 2]
|
||||
Keyword.delete [b: 2], :a #=> [b: 2]
|
||||
|
||||
"""
|
||||
def delete([{k, _}|_] = keywords, key) when key < k, do: keywords
|
||||
def delete([{k, _} = e|tail], key) when key > k, do: [e|delete(tail, key)]
|
||||
def delete([{_, _}|tail], key), do: delete(tail, key)
|
||||
def delete([], _), do: []
|
||||
def delete(keywords, key) do
|
||||
lc { k, _ } = tuple inlist keywords, key != k, do: tuple
|
||||
end
|
||||
|
||||
@doc """
|
||||
Sets the given `value` under `key`.
|
||||
@@ -161,45 +167,46 @@ defmodule Keyword do
|
||||
If a previous value is already stored, all entries are
|
||||
removed and the value is overriden.
|
||||
|
||||
Use `put_other/3` to add a new value for an existing key
|
||||
without removing previous ones.
|
||||
|
||||
## Examples
|
||||
|
||||
Keyword.put [a: 1, b: 2], :a, 3
|
||||
#=> [a: 3, b: 2]
|
||||
|
||||
"""
|
||||
def put([{k, _} = e|keywords], key, value) when key < k and is_atom(key) do
|
||||
[{key, value},e|keywords]
|
||||
end
|
||||
|
||||
def put([{k, _} = e|keywords], key, value) when key > k do
|
||||
[e|put(keywords, key, value)]
|
||||
end
|
||||
|
||||
def put([{key, _}|keywords], key, value) when is_atom(key) do
|
||||
[{key, value}|delete(keywords, key)]
|
||||
end
|
||||
|
||||
def put([], key, value) when is_atom(key) do
|
||||
[{key, value}]
|
||||
def put(list, key, value) do
|
||||
[{key, value}|delete(list, key)]
|
||||
end
|
||||
|
||||
@doc """
|
||||
Merges two keywords lists into one. If they have duplicated
|
||||
Checks if two keywords are equal. I.e. they contain
|
||||
the same keys and those keys contain the same values.
|
||||
|
||||
## Examples
|
||||
|
||||
Keyword.equal? [a: 1, b: 2], [b: 2, a: 1]
|
||||
#=> true
|
||||
|
||||
"""
|
||||
def equal?(left, right) do
|
||||
:lists.sort(left) == :lists.sort(right)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Merges two keyword lists into one. If they have duplicated
|
||||
entries, the one given as second argument wins.
|
||||
|
||||
## Examples
|
||||
|
||||
Keyword.merge [a: 1, b: 2], [a: 3, d: 4]
|
||||
#=> [a:3, b:2, d: 4]
|
||||
|
||||
"""
|
||||
def merge(d1, d2) do
|
||||
merge(d1, d2, fn _k, _v1, v2 -> v2 end)
|
||||
d2 ++ lc({ k, _ } = tuple inlist d1, not has_key?(d2, k), do: tuple)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Merges two keywords lists into one. If they have duplicated
|
||||
Merges two keyword lists into one. If they have duplicated
|
||||
entries, the given function is invoked to solve conflicts.
|
||||
|
||||
## Examples
|
||||
@@ -208,33 +215,86 @@ defmodule Keyword do
|
||||
v1 + v2
|
||||
end
|
||||
#=> [a:4, b:2, d: 4]
|
||||
|
||||
"""
|
||||
def merge([{k1, _} = e1|d1], [{k2, _} = e2|d2], fun) when k1 < k2 and is_atom(k1) do
|
||||
[e1|merge(d1, [e2|d2], fun)]
|
||||
def merge(d1, d2, fun) do
|
||||
do_merge(d2, d1, fun)
|
||||
end
|
||||
|
||||
def merge([{k1, _} = e1|d1], [{k2, _} = e2|d2], fun) when k1 > k2 and is_atom(k2) do
|
||||
[e2|merge([e1|d1], d2, fun)]
|
||||
defp do_merge([{ k, v2 }|t], acc, fun) do
|
||||
do_merge t, update(acc, k, v2, fn(v1) -> fun.(k, v1, v2) end), fun
|
||||
end
|
||||
|
||||
def merge([{k1, v1}|d1], [{k1, v2}|d2], fun) do
|
||||
[{k1, fun.(k1, v1, v2)}|merge(d1, d2, fun)]
|
||||
defp do_merge([], acc, _fun) do
|
||||
acc
|
||||
end
|
||||
|
||||
def merge([], d2, _fun), do: d2
|
||||
def merge(d1, [], _fun), do: d1
|
||||
@doc false
|
||||
def key?(list, key) do
|
||||
IO.write "[WARNING] Keyword.key? is deprecated, please use Keyword.has_key? instead\n#{Exception.formatted_stacktrace}"
|
||||
has_key?(list, key)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns whether a given key exists in the given keywords.
|
||||
|
||||
### Examples
|
||||
Keyword.key?([a: 1], :a)
|
||||
## Examples
|
||||
|
||||
Keyword.has_key?([a: 1], :a)
|
||||
#=> true
|
||||
Keyword.key?([a: 1], :b)
|
||||
Keyword.has_key?([a: 1], :b)
|
||||
#=> false
|
||||
|
||||
"""
|
||||
def key?([{k, _}|_], key) when key < k, do: false
|
||||
def key?([{k, _}|d], key) when key > k, do: key?(d, key)
|
||||
def key?([{_, _}|_], _key), do: true
|
||||
def key?([], _), do: false
|
||||
def has_key?(keywords, key) do
|
||||
:lists.keymember(key, 1, keywords)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Updates the key with the given function. If the key does
|
||||
not exist, raises `Keyword.KeyError`.
|
||||
|
||||
## Examples
|
||||
|
||||
Keyword.update([a: 1], :a, &1 * 2)
|
||||
#=> [a: 2]
|
||||
Keyword.update([a: 1], :b, &1 * 2)
|
||||
#=> Keyword.KeyError
|
||||
|
||||
"""
|
||||
def update([{key, value}|keywords], key, fun) do
|
||||
[{key, fun.(value)}|delete(keywords, key)]
|
||||
end
|
||||
|
||||
def update([{_, _} = e|keywords], key, fun) do
|
||||
[e|update(keywords, key, fun)]
|
||||
end
|
||||
|
||||
def update([], key, _fun) when is_atom(key) do
|
||||
raise(Keyword.KeyError, key: key)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Updates the key with the given function. If the key does
|
||||
not exist, inserts the given `initial` value.
|
||||
|
||||
## Examples
|
||||
|
||||
Keyword.update([a: 1], :a, 13, &1 * 2)
|
||||
#=> [a: 2]
|
||||
Keyword.update([a: 1], :b, 11, &1 * 2)
|
||||
#=> [a: 1, b: 11]
|
||||
|
||||
"""
|
||||
def update([{key, value}|keywords], key, _initial, fun) do
|
||||
[{key, fun.(value)}|delete(keywords, key)]
|
||||
end
|
||||
|
||||
def update([{_, _} = e|keywords], key, initial, fun) do
|
||||
[e|update(keywords, key, initial, fun)]
|
||||
end
|
||||
|
||||
def update([], key, initial, _fun) when is_atom(key) do
|
||||
[{key, initial}]
|
||||
end
|
||||
end
|
||||
|
||||
+77
-67
@@ -20,7 +20,7 @@ defmodule List do
|
||||
|
||||
"""
|
||||
def concat(list) when is_list(list) do
|
||||
Erlang.lists.append(list)
|
||||
:lists.append(list)
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -52,7 +52,22 @@ defmodule List do
|
||||
|
||||
"""
|
||||
def delete(list, item) do
|
||||
Erlang.lists.delete(item, list)
|
||||
:lists.delete(item, list)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Duplicates the given element n times in a list.
|
||||
|
||||
## Examples
|
||||
|
||||
List.duplicate "hello", 3
|
||||
#=> ["hello","hello","hello"]
|
||||
|
||||
List.duplicate [1,2], 2
|
||||
#=> [[1,2],[1,2]]
|
||||
"""
|
||||
def duplicate(elem, n) do
|
||||
:lists.duplicate(n, elem)
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -70,11 +85,11 @@ defmodule List do
|
||||
|
||||
"""
|
||||
def flatten(list) do
|
||||
Erlang.lists.flatten(list)
|
||||
:lists.flatten(list)
|
||||
end
|
||||
|
||||
def flatten(list, tail) do
|
||||
Erlang.lists.flatten(list, tail)
|
||||
:lists.flatten(list, tail)
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -91,7 +106,7 @@ defmodule List do
|
||||
|
||||
"""
|
||||
def foldl(list, acc, function) when is_list(list) and is_function(function) do
|
||||
Erlang.lists.foldl(function, acc, list)
|
||||
:lists.foldl(function, acc, list)
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -105,20 +120,12 @@ defmodule List do
|
||||
|
||||
"""
|
||||
def foldr(list, acc, function) when is_list(list) and is_function(function) do
|
||||
Erlang.lists.foldr(function, acc, list)
|
||||
:lists.foldr(function, acc, list)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Reverses the given list. This function simply delegates
|
||||
to `lists:reverse` which is implemented in C for performance.
|
||||
|
||||
## Examples
|
||||
|
||||
List.reverse [1,2,3]
|
||||
#=> [3,2,1]
|
||||
|
||||
"""
|
||||
@doc false
|
||||
def reverse(list) do
|
||||
IO.write "[WARNING] List.reverse/1 is deprecated, please use Enum.reverse/1 instead\n#{Exception.formatted_stacktrace}"
|
||||
:lists.reverse(list)
|
||||
end
|
||||
|
||||
@@ -156,7 +163,7 @@ defmodule List do
|
||||
|
||||
"""
|
||||
def member?(list, term) do
|
||||
Erlang.lists.member(term, list)
|
||||
:lists.member(term, list)
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -166,18 +173,18 @@ defmodule List do
|
||||
|
||||
## Examples
|
||||
|
||||
List.keyfind([a: 1, b: 2], :a, 1)
|
||||
List.keyfind([a: 1, b: 2], :a, 0)
|
||||
#=> { :a, 1 }
|
||||
|
||||
List.keyfind([a: 1, b: 2], 2, 2)
|
||||
List.keyfind([a: 1, b: 2], 2, 1)
|
||||
#=> { :b, 2 }
|
||||
|
||||
List.keyfind([a: 1, b: 2], :c, 1)
|
||||
List.keyfind([a: 1, b: 2], :c, 0)
|
||||
#=> nil
|
||||
|
||||
"""
|
||||
def keyfind(list, item, position, default // nil) do
|
||||
Erlang.lists.keyfind(item, position, list) || default
|
||||
def keyfind(list, key, position, default // nil) do
|
||||
:lists.keyfind(key, position + 1, list) || default
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -187,18 +194,47 @@ defmodule List do
|
||||
|
||||
## Examples
|
||||
|
||||
List.keymember?([a: 1, b: 2], :a, 1)
|
||||
List.keymember?([a: 1, b: 2], :a, 0)
|
||||
#=> true
|
||||
|
||||
List.keymember?([a: 1, b: 2], 2, 2)
|
||||
List.keymember?([a: 1, b: 2], 2, 1)
|
||||
#=> true
|
||||
|
||||
List.keymember?([a: 1, b: 2], :c, 1)
|
||||
List.keymember?([a: 1, b: 2], :c, 0)
|
||||
#=> false
|
||||
|
||||
"""
|
||||
def keymember?(list, item, position) do
|
||||
Erlang.lists.keymember(item, position, list)
|
||||
def keymember?(list, key, position) do
|
||||
:lists.keymember(key, position + 1, list)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Receives a list of tuples and replaces the item
|
||||
identified by `key` at position `pos` if it exists.
|
||||
|
||||
## Examples
|
||||
|
||||
List.keyreplace([a: 1, b: 2], :a, 0, { :a, 3 })
|
||||
#=> [a: 3, b: 2]
|
||||
|
||||
"""
|
||||
def keyreplace(list, key, position, new_tuple) do
|
||||
:lists.keyreplace(key, position + 1, list, new_tuple)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Receives a list of tuples and replaces the item
|
||||
identified by `key` at position `pos`. If the item
|
||||
does not exist, it is added to the end of the list.
|
||||
|
||||
## Examples
|
||||
|
||||
List.keystore([a: 1, b: 2], :a, 0, { :a, 3 })
|
||||
#=> [a: 3, b: 2]
|
||||
|
||||
"""
|
||||
def keystore(list, key, position, new_tuple) do
|
||||
:lists.keystore(key, position + 1, list, new_tuple)
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -208,18 +244,18 @@ defmodule List do
|
||||
|
||||
## Examples
|
||||
|
||||
List.keydelete([a: 1, b: 2], :a, 1)
|
||||
List.keydelete([a: 1, b: 2], :a, 0)
|
||||
#=> [{ :b, 2 }]
|
||||
|
||||
List.keydelete([a: 1, b: 2], 2, 2)
|
||||
List.keydelete([a: 1, b: 2], 2, 1)
|
||||
#=> [{ :a, 1 }]
|
||||
|
||||
List.keydelete([a: 1, b: 2], :c, 1)
|
||||
List.keydelete([a: 1, b: 2], :c, 0)
|
||||
#=> [{ :a, 1 }, { :b, 2 }]
|
||||
|
||||
"""
|
||||
def keydelete(list, item, position) do
|
||||
Erlang.lists.keydelete(item, position, list)
|
||||
def keydelete(list, key, position) do
|
||||
:lists.keydelete(key, position + 1, list)
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -243,22 +279,22 @@ defmodule List do
|
||||
def range(first, last, step) when is_integer(first) and is_integer(last) and first <= last do
|
||||
step = case step do
|
||||
nil ->
|
||||
Erlang.lists.seq(first, last, 1)
|
||||
:lists.seq(first, last, 1)
|
||||
x when x < 0 ->
|
||||
[]
|
||||
_ ->
|
||||
Erlang.lists.seq(first, last, step)
|
||||
:lists.seq(first, last, step)
|
||||
end
|
||||
end
|
||||
|
||||
def range(first, last, step) when is_integer(first) and is_integer(last) and first > last do
|
||||
step = case step do
|
||||
nil ->
|
||||
Erlang.lists.seq(first, last, -1)
|
||||
:lists.seq(first, last, -1)
|
||||
x when x > 0 ->
|
||||
[]
|
||||
_ ->
|
||||
Erlang.lists.seq(first, last, step)
|
||||
:lists.seq(first, last, step)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -303,21 +339,6 @@ defmodule List do
|
||||
do_uniq(list, [])
|
||||
end
|
||||
|
||||
@doc """
|
||||
Duplicates the given element n times in a list.
|
||||
|
||||
## Examples
|
||||
|
||||
List.duplicate "hello", 3
|
||||
#=> ["hello","hello","hello"]
|
||||
|
||||
List.duplicate [1,2], 2
|
||||
#=> [[1,2],[1,2]]
|
||||
"""
|
||||
def duplicate(elem, n) do
|
||||
Erlang.lists.duplicate(n, elem)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Wraps the argument in a list.
|
||||
If the argument is already a list, returns the list.
|
||||
@@ -340,21 +361,9 @@ defmodule List do
|
||||
[other]
|
||||
end
|
||||
|
||||
@doc """
|
||||
Zips corresponding elements from two lists (or tuples) into one list of tuples. The
|
||||
number of elements in the resulting list is equal to the length of the
|
||||
shortest list among the given ones.
|
||||
|
||||
## Examples
|
||||
|
||||
List.zip [1, 2, 3], [4, 5, 6]
|
||||
#=> [{1, 4}, {2, 5}, {3, 6}]
|
||||
|
||||
List.zip [1, 2], [4, 5, 6]
|
||||
#=> [{1, 4}, {2, 5}]
|
||||
|
||||
"""
|
||||
@doc false
|
||||
def zip(item1, item2) do
|
||||
IO.write "[WARNING] List.zip/2 is deprecated, please use Enum.zip/2 instead\n#{Exception.formatted_stacktrace}"
|
||||
do_zip(to_list(item1), to_list(item2), [])
|
||||
end
|
||||
|
||||
@@ -370,6 +379,7 @@ defmodule List do
|
||||
#=> [{1, 3, 5}]
|
||||
|
||||
"""
|
||||
def zip([]), do: []
|
||||
def zip(list_of_lists) when is_list(list_of_lists) do
|
||||
do_zip(list_of_lists, [])
|
||||
end
|
||||
@@ -396,7 +406,7 @@ defmodule List do
|
||||
# uniq
|
||||
|
||||
defp do_uniq([h|t], acc) do
|
||||
case Erlang.lists.member(h, acc) do
|
||||
case :lists.member(h, acc) do
|
||||
true ->
|
||||
do_uniq(t, acc)
|
||||
false ->
|
||||
@@ -415,7 +425,7 @@ defmodule List do
|
||||
end
|
||||
|
||||
defp do_zip(_, _, acc) do
|
||||
reverse acc
|
||||
:lists.reverse acc
|
||||
end
|
||||
|
||||
defp do_zip(list, acc) do
|
||||
|
||||
+132
-13
@@ -56,6 +56,92 @@ defmodule Macro do
|
||||
|
||||
def escape(other), do: other
|
||||
|
||||
@doc %B"""
|
||||
Unescape the given chars. This is the unescaping behavior
|
||||
used by default in Elixir single- and double-quoted strings.
|
||||
Check `unescape_binary/2` for information on how to customize
|
||||
the escaping map.
|
||||
|
||||
In this setup, Elixir will escape the following: `\b`, `\d`,
|
||||
`\e`, `\f`, `\n`, `\r`, `\s`, `\t` and `\v`. Octals are also
|
||||
escaped according to the latin1 set they represent.
|
||||
|
||||
This function is commonly used on sigil implementations
|
||||
(like `%r`, `%b` and others).
|
||||
|
||||
## Examples
|
||||
|
||||
Macro.unescape_binary "example\\n"
|
||||
#=> "example\n"
|
||||
|
||||
In the example above, we pass a string with `\n` escaped
|
||||
and we return a version with it unescaped.
|
||||
"""
|
||||
def unescape_binary(chars) do
|
||||
:elixir_interpolation.unescape_chars(chars)
|
||||
end
|
||||
|
||||
@doc %B"""
|
||||
Unescape the given chars according to the map given.
|
||||
Check `unescape/1` if you want to use the same map as
|
||||
Elixir single- and double-quoted strings.
|
||||
|
||||
## Map
|
||||
|
||||
The map must be a function. The function receives an integer
|
||||
representing the number of the characters it wants to unescape.
|
||||
Here is the default mapping function implemented by Elixir:
|
||||
|
||||
def unescape_map(?b), do: ?\b
|
||||
def unescape_map(?d), do: ?\d
|
||||
def unescape_map(?e), do: ?\e
|
||||
def unescape_map(?f), do: ?\f
|
||||
def unescape_map(?n), do: ?\n
|
||||
def unescape_map(?r), do: ?\r
|
||||
def unescape_map(?s), do: ?\s
|
||||
def unescape_map(?t), do: ?\t
|
||||
def unescape_map(?v), do: ?\v
|
||||
def unescape_map(e), do: e
|
||||
|
||||
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:
|
||||
|
||||
Macro.unescape_binary "example\\n", unescape_map(&1)
|
||||
|
||||
"""
|
||||
def unescape_binary(chars, map) do
|
||||
:elixir_interpolation.unescape_chars(chars, map)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Unescape the given tokens according to the default map.
|
||||
Check `unescape/1` and `unescape/2` for more information
|
||||
about unescaping. Only tokens that are binaries are
|
||||
unescaped, all others are ignored. This method is useful
|
||||
when implementing your own sigils. Check the implementation
|
||||
of `Kernel.__b__` for examples.
|
||||
"""
|
||||
def unescape_tokens(tokens) do
|
||||
:elixir_interpolation.unescape_tokens(tokens)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Unescape the given tokens according to the given map.
|
||||
Check `unescape_tokens/1` and `unescaped/2` for more information.
|
||||
"""
|
||||
def unescape_tokens(tokens, map) do
|
||||
:elixir_interpolation.unescape_tokens(tokens, map)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Converts the given expression to a binary.
|
||||
|
||||
@@ -124,7 +210,7 @@ defmodule Macro do
|
||||
|
||||
# All other calls
|
||||
def to_binary({ target, _, args }) when is_list(args) do
|
||||
{ list, last } = Erlang.elixir_tree_helpers.split_last(args)
|
||||
{ list, last } = :elixir_tree_helpers.split_last(args)
|
||||
case is_kw_blocks?(last) do
|
||||
true -> call_to_binary_with_args(target, list) <> kw_blocks_to_binary(last)
|
||||
false -> call_to_binary_with_args(target, args)
|
||||
@@ -142,12 +228,14 @@ defmodule Macro do
|
||||
end
|
||||
|
||||
# All other structures
|
||||
def to_binary(other), do: Binary.Inspect.inspect(other)
|
||||
def to_binary(other), do: Binary.Inspect.inspect(other, raw: true)
|
||||
|
||||
# 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?([_|_] = kw) do
|
||||
Enum.all?(kw, match?({x, _} when x in kw_keywords, &1))
|
||||
end
|
||||
defp is_kw_blocks?(_), do: false
|
||||
|
||||
defp call_to_binary(atom) when is_atom(atom), do: atom_to_binary(atom, :utf8)
|
||||
@@ -162,7 +250,7 @@ defmodule Macro do
|
||||
|
||||
defp kw_blocks_to_binary(kw) do
|
||||
Enum.reduce(kw_keywords, " ", fn(x, acc) ->
|
||||
case Keyword.key?(kw, x) do
|
||||
case Keyword.has_key?(kw, x) do
|
||||
true -> acc <> kw_block_to_binary(x, Keyword.get(kw, x))
|
||||
false -> acc
|
||||
end
|
||||
@@ -198,7 +286,7 @@ defmodule Macro do
|
||||
<< case x == ?\n do
|
||||
true -> replacement
|
||||
false -> <<x>>
|
||||
end | :binary >>
|
||||
end :: binary >>
|
||||
end
|
||||
end
|
||||
|
||||
@@ -300,19 +388,27 @@ defmodule Macro do
|
||||
# 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
|
||||
def expand({ :__aliases__, _, [h|t] } = original, 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)
|
||||
|
||||
case :lists.all(is_atom(&1), aliases) do
|
||||
true -> :elixir_aliases.concat(aliases)
|
||||
false -> original
|
||||
end
|
||||
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 @ calls
|
||||
def expand({ :@, _, [{ name, _, args }] } = original, env) when is_atom(args) or args == [] do
|
||||
case (module = env.module) && Module.open?(module) do
|
||||
true -> Module.get_attribute(module, name)
|
||||
false -> original
|
||||
end
|
||||
end
|
||||
|
||||
# Expand pseudo-variables
|
||||
def expand({ :__MODULE__, _, atom }, env) when is_atom(atom), do: env.module
|
||||
@@ -329,7 +425,7 @@ defmodule Macro do
|
||||
case not is_partial?(args) do
|
||||
false -> original
|
||||
true ->
|
||||
expand = Erlang.elixir_dispatch.expand_import(line, { atom, length(args) }, args,
|
||||
expand = :elixir_dispatch.expand_import(line, { atom, length(args) }, args,
|
||||
env.module, env.function, env.requires, env.macros, env)
|
||||
case expand do
|
||||
{ :ok, _, expanded } -> expanded
|
||||
@@ -345,7 +441,7 @@ defmodule Macro do
|
||||
case is_atom(receiver) and not is_partial?(args) do
|
||||
false -> original
|
||||
true ->
|
||||
expand = Erlang.elixir_dispatch.expand_require(line, receiver, { right, length(args) },
|
||||
expand = :elixir_dispatch.expand_require(line, receiver, { right, length(args) },
|
||||
args, env.module, env.function, env.requires, env)
|
||||
case expand do
|
||||
{ :ok, expanded } -> expanded
|
||||
@@ -365,6 +461,29 @@ defmodule Macro do
|
||||
|
||||
defp expand_alias(h, env) do
|
||||
atom = list_to_atom('Elixir-' ++ atom_to_list(h))
|
||||
Erlang.elixir_aliases.lookup(atom, env.aliases)
|
||||
:elixir_aliases.lookup(atom, env.aliases)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Recurs the quoted expression checking if all sub terms are
|
||||
safe (i.e. they represented data structured and don't actually
|
||||
evaluate code) and returns `:ok` unless a given term is unsafe,
|
||||
which is returned as `{ :unsafe, term }`.
|
||||
"""
|
||||
def safe_term(terms) do
|
||||
do_safe_term(terms) || :ok
|
||||
end
|
||||
|
||||
def do_safe_term({ local, _, terms }) when local in [:{}, :[], :__aliases__] do
|
||||
do_safe_term(terms)
|
||||
end
|
||||
|
||||
def do_safe_term({ unary, _, [term] }) when unary in [:+, :-] do
|
||||
do_safe_term(term)
|
||||
end
|
||||
|
||||
def do_safe_term({ left, right }), do: do_safe_term(left) || do_safe_term(right)
|
||||
def do_safe_term(terms) when is_list(terms), do: Enum.find_value(terms, do_safe_term(&1))
|
||||
def do_safe_term(terms) when is_tuple(terms), do: { :unsafe, terms }
|
||||
def do_safe_term(_), do: nil
|
||||
end
|
||||
+28
-45
@@ -1,90 +1,73 @@
|
||||
# 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 """
|
||||
Record.deffunctions([:module, :file, :line, :function,
|
||||
:aliases, :context, :requires, :macros], __MODULE__)
|
||||
|
||||
@moduledoc """
|
||||
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)
|
||||
def module(record)
|
||||
|
||||
@doc """
|
||||
Returns the current file name as a binary.
|
||||
"""
|
||||
def file(record), do: elem(record, 3)
|
||||
def file(record)
|
||||
|
||||
@doc """
|
||||
Returns the current line as an integer.
|
||||
"""
|
||||
def line(record), do: elem(record, 4)
|
||||
def line(record)
|
||||
|
||||
@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)
|
||||
def function(record)
|
||||
|
||||
@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)
|
||||
def aliases(record)
|
||||
|
||||
@doc """
|
||||
Returns wether the compilation environment is currently
|
||||
inside a guard.
|
||||
Returns the context of the environment. It can be nil
|
||||
(default context), inside a guard or inside an assign.
|
||||
"""
|
||||
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
|
||||
def context(record)
|
||||
|
||||
@doc """
|
||||
Returns the list of required modules.
|
||||
"""
|
||||
def requires(record), do: elem(record, 8)
|
||||
def requires(record)
|
||||
|
||||
@doc """
|
||||
Returns a list of macros imported from each module.
|
||||
"""
|
||||
def macros(record), do: elem(record, 9)
|
||||
def macros(record)
|
||||
|
||||
@doc """
|
||||
Returns a keywords list containing the file and line
|
||||
Returns a keyword list containing the file and line
|
||||
information as keys.
|
||||
"""
|
||||
def location(record) do
|
||||
[file: file(record), line: line(record)]
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns wether the compilation environment is currently
|
||||
inside a guard.
|
||||
"""
|
||||
def in_guard?(record), do: context(record) == :guard
|
||||
|
||||
@doc """
|
||||
Returns wether the compilation environment is currently
|
||||
inside a match clause.
|
||||
"""
|
||||
def in_match?(record), do: context(record) == :assign
|
||||
end
|
||||
|
||||
+143
-79
@@ -1,5 +1,5 @@
|
||||
defmodule Module do
|
||||
require Erlang.ets, as: ETS
|
||||
require :ets, as: ETS
|
||||
|
||||
@moduledoc """
|
||||
This module provides many functions to deal with modules during
|
||||
@@ -12,6 +12,15 @@ defmodule Module do
|
||||
via the `__info__(attr)` function attached to each compiled module.
|
||||
"""
|
||||
|
||||
@doc """
|
||||
Check if a module is open, i.e. it is currently being defined
|
||||
and its attributes and functions can be modified.
|
||||
"""
|
||||
def open?(module) do
|
||||
table = data_table_for(module)
|
||||
table == ETS.info(table, :name)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Evalutes the quotes contents in the given module context.
|
||||
Raises an error if the module was already compiled.
|
||||
@@ -36,7 +45,8 @@ defmodule Module do
|
||||
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:
|
||||
is useful to evalute the quoted contents inside an existing
|
||||
environment (considering the environemnt module, line and file):
|
||||
|
||||
defmodule Foo do
|
||||
contents = quote do: (def sum(a, b), do: a + b)
|
||||
@@ -54,15 +64,48 @@ defmodule Module do
|
||||
|
||||
def eval_quoted(module, quoted, binding, opts) do
|
||||
assert_not_compiled!(:eval_quoted, module)
|
||||
:elixir_module.eval_quoted(module, quoted, binding, opts)
|
||||
end
|
||||
|
||||
binding = Erlang.elixir_module.binding_for_eval(module, binding)
|
||||
scope = Erlang.elixir_module.scope_for_eval(module, opts)
|
||||
@doc """
|
||||
Creates a module with the given name and given by
|
||||
the given quoted expressions. The line where the module
|
||||
is defined and its file can be given as options.
|
||||
|
||||
Erlang.elixir_def.reset_last(module)
|
||||
## Examples
|
||||
|
||||
line = Keyword.get opts, :line, 1
|
||||
{ value, binding, _scope } = Erlang.elixir.eval_quoted([quoted], binding, line, scope)
|
||||
{ value, binding }
|
||||
contents =
|
||||
quote do
|
||||
def world, do: true
|
||||
end
|
||||
|
||||
Module.create(Hello, contents, __ENV__)
|
||||
|
||||
Hello.world #=> true
|
||||
|
||||
## Differences with `defmodule`
|
||||
|
||||
`Module.create` works similarly to `defmodule` and
|
||||
return the same results. While one could also use
|
||||
`defmodule` to define modules dynamically, this
|
||||
function is preferred when the module body is given
|
||||
by a quoted expression.
|
||||
|
||||
Another important distinction is that `defmodule`
|
||||
blends into the scope it is invoked, allowing you
|
||||
to access all variables, imports and requires from
|
||||
the module. `Module.create`, on the other hand, creates
|
||||
a new scope so imports, requires, etc are not inherited.
|
||||
"""
|
||||
def create(module, quoted, opts // [])
|
||||
|
||||
def create(module, quoted, Macro.Env[] = env) do
|
||||
create(module, quoted, env.location)
|
||||
end
|
||||
|
||||
def create(module, quoted, opts) when is_atom(module) do
|
||||
line = opts[:line] || 1
|
||||
:elixir_module.compile(line, module, quoted, [], :elixir.scope_for_eval(opts))
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -77,7 +120,7 @@ defmodule Module do
|
||||
|
||||
"""
|
||||
def concat(list) when is_list(list) do
|
||||
Erlang.elixir_aliases.concat(list)
|
||||
:elixir_aliases.concat(list)
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -92,7 +135,7 @@ defmodule Module do
|
||||
|
||||
"""
|
||||
def concat(left, right) do
|
||||
Erlang.elixir_aliases.concat([left, right])
|
||||
:elixir_aliases.concat([left, right])
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -111,7 +154,7 @@ defmodule Module do
|
||||
|
||||
"""
|
||||
def safe_concat(list) when is_list(list) do
|
||||
Erlang.elixir_aliases.safe_concat(list)
|
||||
:elixir_aliases.safe_concat(list)
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -130,7 +173,7 @@ defmodule Module do
|
||||
|
||||
"""
|
||||
def safe_concat(left, right) do
|
||||
Erlang.elixir_aliases.safe_concat([left, right])
|
||||
:elixir_aliases.safe_concat([left, right])
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -153,7 +196,7 @@ defmodule Module do
|
||||
end
|
||||
|
||||
def add_doc(module, line, kind, tuple, signature, doc) when
|
||||
is_binary(doc) or is_boolean(doc) or doc == nil do
|
||||
kind in [:def, :defmacro, :defcallback] and (is_binary(doc) or is_boolean(doc) or doc == nil) do
|
||||
assert_not_compiled!(:add_doc, module)
|
||||
table = docs_table_for(module)
|
||||
|
||||
@@ -161,12 +204,18 @@ defmodule Module do
|
||||
{ simplify_signature(x, line, acc), acc + 1 }
|
||||
end
|
||||
|
||||
case { ETS.lookup(table, tuple), doc } do
|
||||
{ [], _ } ->
|
||||
case ETS.lookup(table, tuple) 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 })
|
||||
[{ tuple, line, old_kind, old_sign, old_doc }] when old_doc == nil or doc == nil or old_doc == doc ->
|
||||
ETS.insert(table, {
|
||||
tuple,
|
||||
line,
|
||||
merge_kind(old_kind, kind),
|
||||
merge_signatures(old_sign, signature, 1),
|
||||
doc || old_doc
|
||||
})
|
||||
:ok
|
||||
_ ->
|
||||
{ :error, :existing_doc }
|
||||
@@ -198,7 +247,11 @@ defmodule Module do
|
||||
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
|
||||
# Merge
|
||||
|
||||
defp merge_kind(:defcallback, new), do: new
|
||||
defp merge_kind(old, :defcallback), do: old
|
||||
defp merge_kind(_old, new), do: new
|
||||
|
||||
defp merge_signatures([h1|t1], [h2|t2], i) do
|
||||
[merge_signature(h1, h2, i)|merge_signatures(t1, t2, i + 1)]
|
||||
@@ -268,30 +321,6 @@ defmodule Module do
|
||||
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.
|
||||
|
||||
@@ -341,11 +370,11 @@ defmodule Module do
|
||||
[clause] ->
|
||||
ETS.delete(table, tuple)
|
||||
|
||||
old = Module.read_attribute(module, :__overridable)
|
||||
old = get_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)
|
||||
put_attribute(module, :__overridable, merged)
|
||||
_ ->
|
||||
{ name, arity } = tuple
|
||||
raise "Cannot make function #{name}/#{arity} overridable because it was not defined"
|
||||
@@ -353,27 +382,28 @@ defmodule Module do
|
||||
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 })
|
||||
@doc """
|
||||
Returns true if the given tuple in module is marked as overridable.
|
||||
"""
|
||||
def overridable?(module, tuple) do
|
||||
key = List.keyfind(get_attribute(module, :__overridable), tuple, 0)
|
||||
match? { _, { _, [_|_] } }, key
|
||||
end
|
||||
|
||||
@doc """
|
||||
Adds an Erlang attribute to the given module with the given
|
||||
key and value. The semantics of adding the attribute depends
|
||||
Puts an Erlang attribute to the given module with the given
|
||||
key and value. The semantics of putting the attribute depends
|
||||
if the attribute was registered or not via `register_attribute/2`.
|
||||
|
||||
## Examples
|
||||
|
||||
defmodule MyModule do
|
||||
Module.add_attribute __MODULE__, :custom_threshold_for_lib, 10
|
||||
Module.put_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)
|
||||
def put_attribute(module, key, value) when is_atom(key) do
|
||||
assert_not_compiled!(:put_attribute, module)
|
||||
table = data_table_for(module)
|
||||
value = normalize_attribute(key, value)
|
||||
acc = ETS.lookup_element(table, :__acc_attributes, 2)
|
||||
@@ -391,25 +421,31 @@ defmodule Module do
|
||||
ETS.insert(table, { key, new })
|
||||
end
|
||||
|
||||
@doc false
|
||||
def add_attribute(module, key, value) when is_atom(key) do
|
||||
IO.write "[WARNING] Module.add_attribute is deprecated, please use Module.put_attribute instead\n#{Exception.formatted_stacktrace}"
|
||||
put_attribute(module, key, value)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Reads the given attribute from a module. If the attribute
|
||||
Gets 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.put_attribute __MODULE__, :value, 1
|
||||
Module.get_attribute __MODULE__, :value #=> 1
|
||||
|
||||
Module.register_attribute __MODULE__, :value, accumulate: true
|
||||
Module.add_attribute __MODULE__, :value, 1
|
||||
Module.read_attribute __MODULE__, :value #=> [1]
|
||||
Module.put_attribute __MODULE__, :value, 1
|
||||
Module.get_attribute __MODULE__, :value #=> [1]
|
||||
end
|
||||
|
||||
"""
|
||||
def read_attribute(module, key) when is_atom(key) do
|
||||
assert_not_compiled!(:read_attribute, module)
|
||||
def get_attribute(module, key) when is_atom(key) do
|
||||
assert_not_compiled!(:get_attribute, module)
|
||||
table = data_table_for(module)
|
||||
|
||||
case ETS.lookup(table, key) do
|
||||
@@ -420,13 +456,19 @@ defmodule Module do
|
||||
end
|
||||
end
|
||||
|
||||
@doc false
|
||||
def read_attribute(module, key) when is_atom(key) do
|
||||
IO.write "[WARNING] Module.read_attribute is deprecated, please use Module.get_attribute instead\n#{Exception.formatted_stacktrace}"
|
||||
get_attribute(module, key)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Deletes all attributes that matches the given key.
|
||||
|
||||
## Examples
|
||||
|
||||
defmodule MyModule do
|
||||
Module.add_attribute __MODULE__, :custom_threshold_for_lib, 10
|
||||
Module.put_attribute __MODULE__, :custom_threshold_for_lib, 10
|
||||
Module.delete_attribute __MODULE__, :custom_threshold_for_lib
|
||||
end
|
||||
|
||||
@@ -454,7 +496,7 @@ defmodule Module do
|
||||
|
||||
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.
|
||||
behavior to exactly the same expected in :
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -484,21 +526,47 @@ defmodule Module do
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Split the given module name into binary parts.
|
||||
|
||||
## Examples
|
||||
|
||||
Module.split Very.Long.Module.Name.And.Even.Longer
|
||||
#=> ["Very", "Long", "Module", "Name", "And", "Even", "Longer"]
|
||||
|
||||
"""
|
||||
def split(module) do
|
||||
tl(String.split(to_binary(module), "-"))
|
||||
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)
|
||||
def compile_doc(env, kind, name, args, _guards, body) do
|
||||
module = env.module
|
||||
line = env.line
|
||||
arity = length(args)
|
||||
pair = { name, arity }
|
||||
doc = get_attribute(module, :doc)
|
||||
kind = if kind == :def and body == nil and module != Kernel, do: :defcallback, else: kind
|
||||
|
||||
case add_doc(module, line, kind, pair, args, doc) do
|
||||
:ok ->
|
||||
:ok
|
||||
{ :error, :private_doc } ->
|
||||
IO.puts "#{env.file}:#{line} function #{name}/#{arity} is private, @doc's are always discarded for private functions"
|
||||
{ :error, :existing_doc } ->
|
||||
IO.puts "#{env.file}:#{line} @doc's for function #{name}/#{arity} have been given more than once, the first version is being kept"
|
||||
end
|
||||
|
||||
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)
|
||||
assert_not_compiled!(:put_attribute, module)
|
||||
table = data_table_for(module)
|
||||
|
||||
new =
|
||||
@@ -525,8 +593,9 @@ defmodule Module do
|
||||
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 }
|
||||
defp normalize_attribute(key, atom) when is_atom(atom) and
|
||||
key in [:before_compile, :after_compile, :on_definition] do
|
||||
{ atom, :"__#{key}__" }
|
||||
end
|
||||
|
||||
defp normalize_attribute(key, _value) when key in [:type, :typep, :export_type, :opaque, :callback] do
|
||||
@@ -538,25 +607,20 @@ defmodule Module 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])
|
||||
module
|
||||
end
|
||||
|
||||
defp function_table_for(module) do
|
||||
list_to_atom Erlang.lists.concat([:f, module])
|
||||
list_to_atom :lists.concat([:f, module])
|
||||
end
|
||||
|
||||
defp docs_table_for(module) do
|
||||
list_to_atom Erlang.lists.concat([:o, module])
|
||||
list_to_atom :lists.concat([:o, module])
|
||||
end
|
||||
|
||||
defp assert_not_compiled!(fun, module) do
|
||||
compiled?(module) ||
|
||||
open?(module) ||
|
||||
raise ArgumentError,
|
||||
message: "could not call #{fun} on module #{inspect module} because it was already compiled"
|
||||
end
|
||||
|
||||
@@ -85,7 +85,7 @@ defmodule OptionParser do
|
||||
end
|
||||
|
||||
defp parse([], _, _, dict, args, true) do
|
||||
{ dict, List.reverse(args) }
|
||||
{ dict, Enum.reverse(args) }
|
||||
end
|
||||
|
||||
defp parse([h|t], aliases, flags, dict, args, true) do
|
||||
@@ -111,7 +111,7 @@ defmodule OptionParser do
|
||||
Keyword.put dict, option, value
|
||||
end
|
||||
|
||||
defp normalize_option(<<?-, option|:binary>>, aliases) do
|
||||
defp normalize_option(<<?-, option :: binary>>, aliases) do
|
||||
normalize_option(option, aliases)
|
||||
end
|
||||
|
||||
|
||||
@@ -1,84 +1,95 @@
|
||||
defimpl Dict, for: Orddict do
|
||||
defmodule OrdDict do
|
||||
@moduledoc """
|
||||
This module implements a dictionary type 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` module.
|
||||
|
||||
Check the `Dict` module for examples and documentation.
|
||||
"""
|
||||
|
||||
use Dict.Common
|
||||
|
||||
defmacrop dict(data) do
|
||||
quote do
|
||||
{ Orddict, unquote(data) }
|
||||
{ OrdDict, unquote(data) }
|
||||
end
|
||||
end
|
||||
|
||||
@doc false
|
||||
def keys(dict(data)) do
|
||||
lc { k, _ } inlist data, do: k
|
||||
end
|
||||
|
||||
@doc false
|
||||
def values(dict(data)) do
|
||||
lc { _, v } inlist data, do: v
|
||||
end
|
||||
|
||||
@doc false
|
||||
def size(dict(data)) do
|
||||
length(data)
|
||||
end
|
||||
|
||||
@doc false
|
||||
def has_key?(dict(data), key) do
|
||||
:orddict.is_key key, data
|
||||
end
|
||||
|
||||
def get(dict(data), key, default // nil) do
|
||||
@doc false
|
||||
def get(dict(data), key, default) do
|
||||
case :orddict.find(key, data) do
|
||||
{:ok, value} ->
|
||||
value
|
||||
:error ->
|
||||
default
|
||||
{:ok, value} -> value
|
||||
:error -> default
|
||||
end
|
||||
end
|
||||
|
||||
@doc false
|
||||
def put(dict(data), key, value) do
|
||||
dict(:orddict.store key, value, data)
|
||||
end
|
||||
|
||||
@doc false
|
||||
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
|
||||
|
||||
@doc false
|
||||
def merge(dict(d1), dict(d2), fun) do
|
||||
dict(:orddict.merge fun, d1, d2)
|
||||
end
|
||||
|
||||
@doc false
|
||||
def merge(dict(_) = d1, d2, fun) do
|
||||
merge(d1, new(d2), fun)
|
||||
end
|
||||
|
||||
@doc false
|
||||
def update(dict(data), key, fun) do
|
||||
dict(:orddict.update key, fun, data)
|
||||
end
|
||||
|
||||
@doc false
|
||||
def update(dict(data), key, initial, fun) do
|
||||
dict(:orddict.update key, fun, initial, data)
|
||||
end
|
||||
|
||||
@doc false
|
||||
def empty(_) do
|
||||
dict([])
|
||||
end
|
||||
|
||||
@doc false
|
||||
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)
|
||||
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]
|
||||
defimpl Access, for: OrdDict do
|
||||
def access(dict, key), do: OrdDict.get(dict, key, nil)
|
||||
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
|
||||
@@ -28,7 +28,7 @@ defmodule Process do
|
||||
@doc """
|
||||
Returns all key-values in the dictionary
|
||||
with no specific ordering (i.e. they are
|
||||
not a keywords list).
|
||||
not a keyword list).
|
||||
"""
|
||||
def get do
|
||||
:erlang.get()
|
||||
@@ -224,7 +224,7 @@ defmodule Process do
|
||||
|
||||
See http://www.erlang.org/doc/man/erlang.html#register-2 for more info.
|
||||
"""
|
||||
def register(name, pid) do
|
||||
def register(pid, name) do
|
||||
:erlang.register(name, pid)
|
||||
end
|
||||
|
||||
@@ -244,7 +244,22 @@ defmodule Process do
|
||||
See http://www.erlang.org/doc/man/erlang.html#whereis-1 for more info.
|
||||
"""
|
||||
def whereis(name) do
|
||||
:erlang.whereis(name)
|
||||
nillify :erlang.whereis(name)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns the pid of the group leader for the process which evaluates the function.
|
||||
"""
|
||||
def group_leader do
|
||||
:erlang.group_leader
|
||||
end
|
||||
|
||||
@doc """
|
||||
Sets the group leader of Pid to GroupLeader. Typically, this is used when a processes
|
||||
started from a certain shell should have another group leader than `:init`.
|
||||
"""
|
||||
def group_leader(leader, pid) do
|
||||
:erlang.group_leader(leader, pid)
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -275,6 +290,16 @@ defmodule Process do
|
||||
:erlang.process_flag(pid, flag, value)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns information about the process identified by pid.
|
||||
Use this only for debugging information.
|
||||
|
||||
See http://www.erlang.org/doc/man/erlang.html#process_info-1 for more info.
|
||||
"""
|
||||
def info(pid) do
|
||||
:erlang.process_info(pid)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns information about the process identified by pid
|
||||
or undefined if the process is not alive.
|
||||
|
||||
+71
-58
@@ -1,15 +1,15 @@
|
||||
defmodule Protocol do
|
||||
@moduledoc false
|
||||
|
||||
# We need to use Erlang.lists because Enum is not available yet
|
||||
require Erlang.lists, as: L
|
||||
# We need to use :lists because Enum is not available yet
|
||||
require :lists, as: L
|
||||
|
||||
@doc """
|
||||
Handle `defprotocol`. It will define a function for each
|
||||
protocol plus two extra functions:
|
||||
|
||||
* `__protocol__/1` - returns the protocol name when :name is given,
|
||||
and a keywords list with the protocol functions
|
||||
and a keyword list with the protocol functions
|
||||
when :functions is given;
|
||||
|
||||
* `__impl_for__/1` - receives one argument and returns a module
|
||||
@@ -21,8 +21,7 @@ defmodule Protocol do
|
||||
def defprotocol(name, [do: block]) do
|
||||
quote do
|
||||
defmodule unquote(name) do
|
||||
# Remove "harmful" macros
|
||||
# We don't want to allow function definition inside protocols
|
||||
# We don't allow function definition inside protocols
|
||||
import Kernel, except: [
|
||||
defmacro: 1, defmacro: 2, defmacro: 4,
|
||||
defp: 1, defp: 2, defp: 4,
|
||||
@@ -40,8 +39,8 @@ defmodule Protocol do
|
||||
|
||||
# Define callbacks and meta information
|
||||
{ conversions, fallback } = Protocol.conversions_for(__MODULE__, @only, @except)
|
||||
Protocol.impl_for(__ENV__, conversions)
|
||||
Protocol.meta(__ENV__, @functions, fallback)
|
||||
Protocol.impl_for(conversions, fallback, __ENV__)
|
||||
Protocol.meta(@functions, fallback, __ENV__)
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -51,7 +50,15 @@ defmodule Protocol do
|
||||
It also defines a `__impl__` function which
|
||||
returns the protocol being implemented.
|
||||
"""
|
||||
def defimpl(protocol, [do: block, for: for]) do
|
||||
def defimpl(protocol, opts) do
|
||||
do_defimpl(protocol, :lists.keysort(1, opts))
|
||||
end
|
||||
|
||||
defp do_defimpl(protocol, [do: block, for: for]) when is_list(for) do
|
||||
lc f inlist for, do: do_defimpl(protocol, [do: block, for: f])
|
||||
end
|
||||
|
||||
defp do_defimpl(protocol, [do: block, for: for]) do
|
||||
quote do
|
||||
protocol = unquote(protocol)
|
||||
for = unquote(for)
|
||||
@@ -60,11 +67,10 @@ defmodule Protocol do
|
||||
Protocol.assert_protocol(protocol)
|
||||
|
||||
defmodule name do
|
||||
def __impl__, do: unquote(protocol)
|
||||
@behaviour unquote(protocol)
|
||||
unquote(block)
|
||||
def __impl__, do: unquote(protocol)
|
||||
end
|
||||
|
||||
Protocol.assert_impl(name, protocol)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -73,7 +79,7 @@ defmodule Protocol do
|
||||
if not loaded or not a protocol.
|
||||
"""
|
||||
def assert_protocol(module) do
|
||||
case :code.ensure_loaded(module) do
|
||||
case Code.ensure_compiled(module) do
|
||||
{ :module, ^module } -> nil
|
||||
_ -> raise ArgumentError, message: "#{module} is not loaded"
|
||||
end
|
||||
@@ -86,29 +92,17 @@ defmodule Protocol do
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Check if the given `impl` is a valid impl for `protocol`.
|
||||
Raises an error if not.
|
||||
|
||||
"""
|
||||
def assert_impl(impl, protocol) do
|
||||
remaining = protocol.__protocol__(:functions) -- impl.__info__(:functions)
|
||||
|
||||
if remaining != [] do
|
||||
pp = Enum.map_join remaining, ", ", fn {x,y} -> "#{x}/#{y}" end
|
||||
raise ArgumentError,
|
||||
message: "#{inspect impl} did not implement #{inspect protocol}, missing: #{pp}"
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Defines meta information about the protocol and internal callbacks.
|
||||
"""
|
||||
def meta(env, functions, fallback) do
|
||||
def meta(functions, fallback, env) do
|
||||
contents = quote do
|
||||
def __protocol__(:name), do: __MODULE__
|
||||
def __protocol__(:functions), do: unquote(:lists.sort(functions))
|
||||
|
||||
@doc false
|
||||
def behaviour_info(:callbacks), do: [{ :__impl__, 0 }|__protocol__(:functions)]
|
||||
|
||||
def __impl_for__(arg) do
|
||||
case __raw_impl__(arg) do
|
||||
__MODULE__.Record ->
|
||||
@@ -116,8 +110,9 @@ defmodule Protocol do
|
||||
try do
|
||||
target.__impl__
|
||||
target
|
||||
rescue
|
||||
UndefinedFunctionError -> __fallback__
|
||||
catch
|
||||
:error, :undef, [[{ ^target, :__impl__, [], _ }|_]|_] ->
|
||||
__fallback__
|
||||
end
|
||||
other ->
|
||||
other
|
||||
@@ -143,12 +138,14 @@ 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(env, conversions) do
|
||||
contents = lc kind inlist conversions, do: each_impl_for(kind, conversions)
|
||||
def impl_for(conversions, fallback, env) do
|
||||
contents = lc kind inlist conversions do
|
||||
each_impl_for(kind, if fallback, do: conversions)
|
||||
end
|
||||
|
||||
# If we don't implement all protocols and any is not in the
|
||||
# list, we need to add a final clause that returns nil.
|
||||
if !L.member({ Any, :is_any }, conversions) && length(conversions) < 10 do
|
||||
if !L.keyfind(Any, 1, conversions) && length(conversions) < 10 do
|
||||
contents = contents ++ [quote do
|
||||
defp __raw_impl__(_) do
|
||||
nil
|
||||
@@ -204,8 +201,8 @@ defmodule Protocol do
|
||||
]
|
||||
end
|
||||
|
||||
# Returns a quoted expression that allow to checks
|
||||
# if a variable named first is built in or not.
|
||||
# Returns a quoted expression that allows to check
|
||||
# if the first item in the tuple is a built-in or not.
|
||||
defp is_builtin?([{h,_}]) do
|
||||
quote do
|
||||
first == unquote(h)
|
||||
@@ -218,18 +215,30 @@ defmodule Protocol do
|
||||
end
|
||||
end
|
||||
|
||||
# Specially handle tuples as they can also be record.
|
||||
# If this is the case, module.Record will be returned.
|
||||
# Handle records when we don't have fallbacks.
|
||||
# It simply gets the first element of the tuple.
|
||||
# This case assumes that, whenever a tuple is given
|
||||
# it is meant to be a record, so we don't need extra
|
||||
# checks.
|
||||
defp each_impl_for({ _, :is_record }, nil) do
|
||||
quote do
|
||||
defp __raw_impl__(arg) when is_tuple(arg) and is_atom(:erlang.element(1, arg)) do
|
||||
__MODULE__.Record
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Specially handle records in the case we have fallbacks.
|
||||
defp each_impl_for({ _, :is_record }, conversions) do
|
||||
quote do
|
||||
defp __raw_impl__(arg) when is_tuple(arg) and is_atom(:erlang.element(1, arg)) do
|
||||
first = :erlang.element(1, arg)
|
||||
case unquote(is_builtin?(conversions)) do
|
||||
true -> __MODULE__.Tuple
|
||||
true -> __fallback__
|
||||
false ->
|
||||
case atom_to_list(first) do
|
||||
'Elixir-' ++ _ -> __MODULE__.Record
|
||||
_ -> __MODULE__.Tuple
|
||||
_ -> __fallback__
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -259,21 +268,22 @@ defmodule Protocol.DSL do
|
||||
@moduledoc false
|
||||
|
||||
defmacro def(expression) do
|
||||
{ name, arity } =
|
||||
case expression do
|
||||
{ _, _, args } when args == [] or is_atom(args) ->
|
||||
raise ArgumentError, message: "protocol functions expect at least one argument"
|
||||
{ name, _, args } when is_atom(name) and is_list(args) ->
|
||||
{ name, length(args) }
|
||||
_ ->
|
||||
raise ArgumentError, message: "invalid args for defprotocol"
|
||||
end
|
||||
case expression do
|
||||
{ _, _, args } when args == [] or is_atom(args) ->
|
||||
raise ArgumentError, message: "protocol functions expect at least one argument"
|
||||
{ name, _, args } when is_atom(name) and is_list(args) ->
|
||||
:ok
|
||||
_ ->
|
||||
raise ArgumentError, message: "invalid args for defprotocol"
|
||||
end
|
||||
|
||||
arity = length(args)
|
||||
|
||||
# Generate arguments according the arity. The arguments
|
||||
# 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 inlist :lists.seq(1, arity) do
|
||||
generated = lc i inlist :lists.seq(1, arity) do
|
||||
{ binary_to_atom(<<?x, i + 64>>), 0, :quoted }
|
||||
end
|
||||
|
||||
@@ -281,28 +291,31 @@ defmodule Protocol.DSL do
|
||||
# Append new function to the list
|
||||
@functions [unquote({name, arity})|@functions]
|
||||
|
||||
Kernel.def unquote(name).(unquote_splicing(args)) do
|
||||
args = [unquote_splicing(args)]
|
||||
# Generate a fake definition with the user
|
||||
# signature that will be used by docs
|
||||
Kernel.def unquote(name).(unquote_splicing(args))
|
||||
|
||||
Kernel.def unquote(name).(unquote_splicing(generated)) do
|
||||
case __raw_impl__(xA) do
|
||||
__MODULE__.Record ->
|
||||
target = Module.concat(__MODULE__, :erlang.element(1, xA))
|
||||
try do
|
||||
target = Module.concat(__MODULE__, :erlang.element(1, xA))
|
||||
apply target, unquote(name), args
|
||||
rescue
|
||||
UndefinedFunctionError ->
|
||||
target.unquote(name)(unquote_splicing(generated))
|
||||
catch
|
||||
:error, :undef, [[{ ^target, unquote(name), args, _ }|_]|_] when length(args) == unquote(arity) ->
|
||||
case __fallback__ do
|
||||
nil ->
|
||||
raise Protocol.UndefinedError, protocol: __MODULE__, structure: xA
|
||||
other ->
|
||||
apply other, unquote(name), args
|
||||
apply other, unquote(name), [unquote_splicing(generated)]
|
||||
end
|
||||
end
|
||||
nil ->
|
||||
raise Protocol.UndefinedError, protocol: __MODULE__, structure: xA
|
||||
other ->
|
||||
apply other, unquote(name), args
|
||||
apply other, unquote(name), [unquote_splicing(generated)]
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
+8
-42
@@ -1,38 +1,7 @@
|
||||
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
|
||||
|
||||
defrecord Range, [:first, :last] do
|
||||
@moduledoc """
|
||||
Defines a Range.
|
||||
"""
|
||||
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
|
||||
@@ -62,16 +31,13 @@ defimpl Enum.Iterator, for: Range do
|
||||
end
|
||||
end
|
||||
|
||||
defimpl Enum.OrdIterator, for: Range do
|
||||
def iterator(range) do
|
||||
Enum.Iterator.Range.iterator(range)
|
||||
defimpl Range.Iterator, for: Number do
|
||||
def iterator(first, Range[first: f, last: last]) when is_integer(first) and is_integer(last) and last < f do
|
||||
fn(current) ->
|
||||
if current < last, do: :stop, else: { current, current - 1 }
|
||||
end
|
||||
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 }
|
||||
|
||||
+332
-101
@@ -1,6 +1,6 @@
|
||||
defmodule Record do
|
||||
@moduledoc """
|
||||
Functions to define and interact with Erlang records
|
||||
Functions to define Elixir records
|
||||
"""
|
||||
|
||||
@doc """
|
||||
@@ -17,24 +17,150 @@ defmodule Record do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Implements the access macro used by records.
|
||||
It returns a quoted expression that represents
|
||||
the access given by the keywords.
|
||||
Main entry point for records definition. It defines a module
|
||||
with the given `name` and the fields specified in `modules`.
|
||||
This is invoked directly by `Kernel.defrecord`, so check it
|
||||
for more information and documentation.
|
||||
"""
|
||||
def defrecord(name, values, opts) do
|
||||
block = Keyword.get(opts, :do, nil)
|
||||
opts = Keyword.delete(opts, :do)
|
||||
|
||||
quote do
|
||||
defmodule unquote(name) do
|
||||
@moduledoc false
|
||||
Record.deffunctions(unquote(values), unquote(opts), __ENV__)
|
||||
unquote(block)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Defines record functions skipping the module definition.
|
||||
This is called directly by `defrecord`. It expects the
|
||||
module environment, the module values and a keyword list
|
||||
of options.
|
||||
|
||||
## Examples
|
||||
|
||||
defmodule CustomRecord do
|
||||
Record.deffunctions [:name, :age], __ENV__
|
||||
end
|
||||
|
||||
"""
|
||||
def deffunctions(values, opts // [], env) do
|
||||
values = lc value inlist values, do: convert_value(value)
|
||||
escaped = Macro.escape(values)
|
||||
extensions = Keyword.get(opts, :extensions, Record.Extensions)
|
||||
|
||||
contents = [
|
||||
reflection(escaped),
|
||||
initializer(escaped),
|
||||
indexes(escaped),
|
||||
readers(values, 1, []),
|
||||
conversions(values),
|
||||
writers(values, 1, []),
|
||||
updater(values),
|
||||
extensions(values, 1, [], extensions)
|
||||
]
|
||||
|
||||
# Special case for bootstraping purposes
|
||||
if env == Macro.Env do
|
||||
:elixir_module.eval_quoted(env, contents, [], [])
|
||||
else
|
||||
contents = [quote(do: @__record__ unquote(escaped))|contents]
|
||||
Module.eval_quoted(env, contents)
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Defines three macros for reading and writing records values.
|
||||
These macros are private to the current module and are
|
||||
basically a simple mechanism for manipulating tuples when
|
||||
there isn't an interest in exposing the record as a whole.
|
||||
In some ways, it is similar to Erlang records, since it is
|
||||
only available at compilation time.
|
||||
|
||||
## Examples
|
||||
|
||||
defmodule CustomModule do
|
||||
Record.defmacros :_user, [:name, :age], __ENV__
|
||||
|
||||
def new(name, age) do
|
||||
_user(name: name, age: age)
|
||||
end
|
||||
|
||||
def name(user, name) do
|
||||
_user(user, name: name)
|
||||
end
|
||||
|
||||
def age(user) do
|
||||
_user(user, :age)
|
||||
end
|
||||
|
||||
def to_keywords(user) do
|
||||
_user(user)
|
||||
end
|
||||
|
||||
def name_and_age(user) do
|
||||
_user(user, [:name, :age])
|
||||
end
|
||||
|
||||
def age_and_name(user) do
|
||||
_user(user, [:age, :name])
|
||||
end
|
||||
end
|
||||
|
||||
"""
|
||||
def defmacros(name, values, env) do
|
||||
escaped = lc value inlist values, do: Macro.escape(convert_value(value))
|
||||
|
||||
contents = quote do
|
||||
defmacrop unquote(name).(record) when is_tuple(record) do
|
||||
Record.to_keywords(__CALLER__, __MODULE__, unquote(escaped), record)
|
||||
end
|
||||
|
||||
defmacrop unquote(name).(args) do
|
||||
Record.access(__CALLER__, __MODULE__, unquote(escaped), args)
|
||||
end
|
||||
|
||||
defmacrop unquote(name).(record, key) when is_atom(key) do
|
||||
Record.get(__CALLER__, __MODULE__, unquote(escaped), record, key)
|
||||
end
|
||||
|
||||
defmacrop unquote(name).(record, args) do
|
||||
Record.dispatch(__CALLER__, __MODULE__, unquote(escaped), record, args)
|
||||
end
|
||||
end
|
||||
|
||||
Module.eval_quoted(env, contents)
|
||||
end
|
||||
|
||||
defp convert_value(atom) when is_atom(atom) do
|
||||
{ atom, nil }
|
||||
end
|
||||
|
||||
defp convert_value(other), do: other
|
||||
|
||||
# Implements the access macro used by records.
|
||||
# It returns a quoted expression that defines
|
||||
# a record or a match in case the record is
|
||||
# inside a match.
|
||||
@doc false
|
||||
def access(caller, atom, fields, keyword) do
|
||||
unless is_orddict(keyword) do
|
||||
unless is_keyword(keyword) do
|
||||
raise "expected contents inside brackets to be a Keyword"
|
||||
end
|
||||
|
||||
in_match = caller.in_match?
|
||||
|
||||
has_underscore_value = Keyword.key?(keyword, :_)
|
||||
has_underscore_value = Keyword.has_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
|
||||
case Keyword.has_key?(each_keyword, field) do
|
||||
true -> Keyword.get(each_keyword, field)
|
||||
false ->
|
||||
case in_match or has_underscore_value do
|
||||
@@ -56,48 +182,92 @@ defmodule Record do
|
||||
end
|
||||
end
|
||||
|
||||
defp is_orddict(list) when is_list(list), do: :lists.all(is_orddict_tuple(&1), list)
|
||||
defp is_orddict(_), do: false
|
||||
# Dispatch the call to either update or to_list depending on the args given.
|
||||
@doc false
|
||||
def dispatch(caller, atom, fields, record, args) do
|
||||
if is_keyword(args) do
|
||||
update(caller, atom, fields, record, args)
|
||||
else
|
||||
to_list(caller, atom, fields, record, args)
|
||||
end
|
||||
end
|
||||
|
||||
defp is_orddict_tuple({ x, _ }) when is_atom(x), do: true
|
||||
defp is_orddict_tuple(_), do: false
|
||||
# Implements the update macro defined by defmacros.
|
||||
# It returns a quoted expression that represents
|
||||
# the access given by the keywords.
|
||||
@doc false
|
||||
defp update(caller, atom, fields, var, keyword) do
|
||||
unless is_keyword(keyword) do
|
||||
raise "expected contents inside brackets to be a Keyword"
|
||||
end
|
||||
|
||||
if caller.in_match? do
|
||||
raise "cannot invoke update style macro inside match context"
|
||||
end
|
||||
|
||||
@doc """
|
||||
Main entry point for records definition.
|
||||
This is invoked directly by `Kernel.defrecord`.
|
||||
Returns the quoted expression of a module given by name.
|
||||
"""
|
||||
def defrecord(name, values, opts) do
|
||||
moduledoc = Keyword.get(opts, :moduledoc, false)
|
||||
block = Keyword.get(opts, :do)
|
||||
definition = Keyword.get(opts, :definition, Record.Definition)
|
||||
|
||||
quote do
|
||||
defmodule unquote(name) do
|
||||
@moduledoc unquote(moduledoc)
|
||||
Record.define_functions(__ENV__, unquote(values), unquote(definition))
|
||||
unquote(block)
|
||||
Enum.reduce keyword, var, fn({ key, value }, acc) ->
|
||||
index = find_index(fields, key, 0)
|
||||
if index do
|
||||
quote do
|
||||
:erlang.setelement(unquote(index + 2), unquote(acc), unquote(value))
|
||||
end
|
||||
else
|
||||
raise "record #{inspect atom} does not have the key: #{inspect key}"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Implements the get macro defined by defmacros.
|
||||
# It returns a quoted expression that represents
|
||||
# getting the value of a given field.
|
||||
@doc false
|
||||
# Private endpoint that defines the functions for the Record.
|
||||
def define_functions(env, values, definition) do
|
||||
# Escape the values so they are valid syntax nodes
|
||||
values = Macro.escape(values)
|
||||
|
||||
contents = [
|
||||
reflection(values),
|
||||
getters_and_setters(values, 1, [], definition),
|
||||
initializers(values),
|
||||
converters(values)
|
||||
]
|
||||
|
||||
Module.eval_quoted env, contents
|
||||
def get(_caller, atom, fields, var, key) do
|
||||
index = find_index(fields, key, 0)
|
||||
if index do
|
||||
quote do
|
||||
:erlang.element(unquote(index + 2), unquote(var))
|
||||
end
|
||||
else
|
||||
raise "record #{inspect atom} does not have the key: #{inspect key}"
|
||||
end
|
||||
end
|
||||
|
||||
# Implements to_keywords macro defined by defmacros.
|
||||
# It returns a quoted expression that represents
|
||||
# converting record to keywords list.
|
||||
@doc false
|
||||
def to_keywords(_caller, _atom, fields, record) do
|
||||
Enum.map Keyword.from_enum(fields),
|
||||
fn({key, _default}) ->
|
||||
index = find_index(fields, key, 0)
|
||||
quote do
|
||||
{unquote(key), :erlang.element(unquote(index + 2), unquote(record))}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Implements to_list macro defined by defmacros.
|
||||
# It returns a quoted expression that represents
|
||||
# extracting given fields from record.
|
||||
@doc false
|
||||
defp to_list(_caller, atom, fields, record, keys) do
|
||||
Enum.map keys,
|
||||
fn(key) ->
|
||||
index = find_index(fields, key, 0)
|
||||
if index do
|
||||
quote do: :erlang.element(unquote(index + 2), unquote(record))
|
||||
else
|
||||
raise "record #{inspect atom} does not have the key: #{inspect key}"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defp is_keyword(list) when is_list(list), do: :lists.all(is_keyword_tuple(&1), list)
|
||||
defp is_keyword(_), do: false
|
||||
|
||||
defp is_keyword_tuple({ x, _ }) when is_atom(x), do: true
|
||||
defp is_keyword_tuple(_), do: false
|
||||
|
||||
# Define __record__/1 and __record__/2 as reflection functions
|
||||
# that returns the record names and fields.
|
||||
#
|
||||
@@ -114,7 +284,6 @@ defmodule Record do
|
||||
#
|
||||
defp reflection(values) do
|
||||
quote do
|
||||
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)
|
||||
@@ -139,8 +308,8 @@ defmodule Record do
|
||||
# { FileInfo, Keyword.get(opts, :atime), Keyword.get(opts, :mtime) }
|
||||
# end
|
||||
#
|
||||
defp initializers(values) do
|
||||
defaults = lc value inlist values, do: elem(value, 2)
|
||||
defp initializer(values) do
|
||||
defaults = lc { _, value } inlist values, do: value
|
||||
|
||||
# For each value, define a piece of code that will receive
|
||||
# an ordered dict of options (opts) and it will try to fetch
|
||||
@@ -154,7 +323,37 @@ defmodule Record do
|
||||
def new(), do: new([])
|
||||
def new([]), do: { __MODULE__, unquote_splicing(defaults) }
|
||||
def new(opts) when is_list(opts), do: { __MODULE__, unquote_splicing(selective) }
|
||||
def new(tuple) when is_tuple(tuple), do: setelem(tuple, 1, __MODULE__)
|
||||
def new(tuple) when is_tuple(tuple), do: :erlang.setelement(1, tuple, __MODULE__)
|
||||
end
|
||||
end
|
||||
|
||||
# Define method to get index of a given key.
|
||||
#
|
||||
# Useful if you need to know position of the key for such applications as:
|
||||
# - ets
|
||||
# - mnesia
|
||||
#
|
||||
# For a declaration like:
|
||||
#
|
||||
# defrecord FileInfo, atime: nil, mtime: nil
|
||||
#
|
||||
# It will define following method:
|
||||
#
|
||||
# def __index__(:atime), do: 2
|
||||
# def __index__(:mtime), do: 3
|
||||
# def __index__(_), do: nil
|
||||
#
|
||||
defp indexes(values) do
|
||||
quoted = lc { k, _ } inlist values do
|
||||
index = find_index(values, k, 0)
|
||||
quote do
|
||||
def __index__(unquote(k)), do: unquote(index + 1)
|
||||
end
|
||||
end
|
||||
quote do
|
||||
unquote(quoted)
|
||||
def __index__(_), do: nil
|
||||
def __index__(key, _), do: __index__(key)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -163,13 +362,13 @@ defmodule Record do
|
||||
# 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
|
||||
defp conversions(values) do
|
||||
sorted = lc { k, _ } inlist values do
|
||||
index = find_index(values, k, 1)
|
||||
{ k, quote(do: :erlang.element(unquote(index + 1), record)) }
|
||||
index = find_index(values, k, 0)
|
||||
{ k, quote(do: :erlang.element(unquote(index + 2), record)) }
|
||||
end
|
||||
|
||||
quote do
|
||||
@@ -181,43 +380,85 @@ defmodule Record do
|
||||
|
||||
defp find_index([{ k, _ }|_], k, i), do: i
|
||||
defp find_index([{ _, _ }|t], k, i), do: find_index(t, k, i + 1)
|
||||
defp find_index([], _k, _i), do: nil
|
||||
|
||||
# Implement getters and setters for each attribute.
|
||||
# For a declaration like:
|
||||
# Implement readers. For a declaration like:
|
||||
#
|
||||
# defrecord FileInfo, atime: nil, mtime: nil
|
||||
#
|
||||
# It will define four methods:
|
||||
#
|
||||
# def :atime.(record) do
|
||||
# elem(record, 2)
|
||||
# end
|
||||
#
|
||||
# def :atime.(record, value) do
|
||||
# setelem(record, 2, value)
|
||||
# elem(record, 1)
|
||||
# end
|
||||
#
|
||||
# def :mtime.(record) do
|
||||
# elem(record, 3)
|
||||
# elem(record, 2)
|
||||
# end
|
||||
#
|
||||
# def :mtime.(record, value) do
|
||||
# setelem(record, value, 3)
|
||||
# end
|
||||
#
|
||||
# `element` and `setelement` will simply get and set values
|
||||
# from the record tuple. Notice that `:atime.(record)` is just
|
||||
# a dynamic way to say `atime(record)`. We need to use this
|
||||
# syntax as `unquote(key)(record)` wouldn't be valid (as Elixir
|
||||
# allows you to parenthesis just on specific cases as `foo()`
|
||||
# and `foo.bar()`)
|
||||
defp getters_and_setters([{ key, default }|t], i, acc, definition) do
|
||||
i = i + 1
|
||||
functions = definition.functions_for(key, default, i)
|
||||
getters_and_setters(t, i, [functions | acc], definition)
|
||||
defp readers([{ key, _default }|t], i, acc) do
|
||||
contents = quote do
|
||||
def unquote(key).(record) do
|
||||
:erlang.element(unquote(i + 1), record)
|
||||
end
|
||||
end
|
||||
|
||||
readers(t, i + 1, [contents | acc])
|
||||
end
|
||||
|
||||
defp getters_and_setters([], _i, acc, _), do: acc
|
||||
defp readers([], _i, acc), do: acc
|
||||
|
||||
# Implement writers. For a declaration like:
|
||||
#
|
||||
# defrecord FileInfo, atime: nil, mtime: nil
|
||||
#
|
||||
# It will define four methods:
|
||||
#
|
||||
# def :atime.(value, record) do
|
||||
# setelem(record, 1, value)
|
||||
# end
|
||||
#
|
||||
# def :mtime.(record) do
|
||||
# setelem(record, 2, value)
|
||||
# end
|
||||
#
|
||||
defp writers([{ key, _default }|t], i, acc) do
|
||||
contents = quote do
|
||||
def unquote(key).(value, record) do
|
||||
:erlang.setelement(unquote(i + 1), record, value)
|
||||
end
|
||||
end
|
||||
|
||||
writers(t, i + 1, [contents | acc])
|
||||
end
|
||||
|
||||
defp writers([], _i, acc), do: acc
|
||||
|
||||
# Defines update/2
|
||||
defp updater(values) do
|
||||
fields =
|
||||
lc {key, _default} inlist values do
|
||||
quote do
|
||||
Keyword.get(keywords,
|
||||
unquote(key),
|
||||
elem(record, unquote(find_index(values, key, 1))))
|
||||
end
|
||||
end
|
||||
contents = {:{}, 0, [(quote do: __MODULE__)|fields]}
|
||||
quote do
|
||||
def update(keywords, record) do
|
||||
unquote(contents)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Defines extra functions from the definition.
|
||||
defp extensions([{ key, default }|t], i, acc, extensions) do
|
||||
functions = extensions.functions_for(key, default, i)
|
||||
extensions(t, i + 1, [functions | acc], extensions)
|
||||
end
|
||||
|
||||
defp extensions([], _i, acc, _), do: acc
|
||||
end
|
||||
|
||||
defmodule Record.Extractor do
|
||||
@@ -228,7 +469,7 @@ defmodule Record.Extractor do
|
||||
def retrieve(name, from: string) do
|
||||
file = to_char_list(string)
|
||||
|
||||
case Erlang.code.where_is_file(file) do
|
||||
case :code.where_is_file(file) do
|
||||
:non_existing -> realfile = file
|
||||
realfile -> nil
|
||||
end
|
||||
@@ -239,19 +480,19 @@ defmodule Record.Extractor do
|
||||
# Retrieve a record definition from an Erlang file using
|
||||
# the same lookup as the *include_lib* attribute from Erlang modules.
|
||||
def retrieve(name, from_lib: file) do
|
||||
[app|path] = Erlang.filename.split(to_char_list(file))
|
||||
case Erlang.code.lib_dir(to_char_list(app)) do
|
||||
[app|path] = :filename.split(to_char_list(file))
|
||||
case :code.lib_dir(to_char_list(app)) do
|
||||
{ :error, _ } ->
|
||||
raise ArgumentError, "Lib file #{to_binary(file)} could not be found"
|
||||
libpath ->
|
||||
retrieve_record name, Erlang.filename.join([libpath|path])
|
||||
retrieve_record name, :filename.join([libpath|path])
|
||||
end
|
||||
end
|
||||
|
||||
# Retrieve the record with the given name from the given file
|
||||
defp retrieve_record(name, file) do
|
||||
records = retrieve_from_file(file)
|
||||
if record = List.keyfind(records, name, 1) do
|
||||
if record = List.keyfind(records, name, 0) do
|
||||
parse_record(record)
|
||||
else
|
||||
raise ArgumentError, "No record #{name} found at #{to_binary(file)}"
|
||||
@@ -267,7 +508,7 @@ defmodule Record.Extractor do
|
||||
# includes record and other preprocessor modules. This is done
|
||||
# by using Erlang's epp_dodger.
|
||||
defp read_file(file) do
|
||||
case Erlang.epp_dodger.quick_parse_file(file) do
|
||||
case :epp_dodger.quick_parse_file(file) do
|
||||
{ :ok, form } ->
|
||||
form
|
||||
other ->
|
||||
@@ -282,7 +523,7 @@ defmodule Record.Extractor do
|
||||
cons = List.foldr fields, { nil, 0 }, fn f, acc ->
|
||||
{ :cons, 0, parse_field(f), acc }
|
||||
end
|
||||
{ :value, list, _ } = Erlang.erl_eval.expr(cons, [])
|
||||
{ :value, list, _ } = :erl_eval.expr(cons, [])
|
||||
list
|
||||
end
|
||||
|
||||
@@ -299,16 +540,14 @@ defmodule Record.Extractor do
|
||||
end
|
||||
end
|
||||
|
||||
defmodule Record.Definition do
|
||||
defmodule Record.Extensions do
|
||||
@moduledoc false
|
||||
|
||||
# Main entry point. It defines both default functions
|
||||
# via `default_for` and extensions via `extension_for`.
|
||||
def functions_for(key, default, i) do
|
||||
[
|
||||
default_for(key, default, i),
|
||||
extension_for(key, default, i)
|
||||
]
|
||||
[ default_for(key, default, i),
|
||||
extension_for(key, default, i) ]
|
||||
end
|
||||
|
||||
# Skip the __exception__ for defexception.
|
||||
@@ -322,17 +561,9 @@ defmodule Record.Definition do
|
||||
update = binary_to_atom(bin_update)
|
||||
|
||||
quote do
|
||||
def unquote(key).(record) do
|
||||
:erlang.element(unquote(i), record)
|
||||
end
|
||||
|
||||
def unquote(key).(value, record) do
|
||||
:erlang.setelement(unquote(i), record, value)
|
||||
end
|
||||
|
||||
def unquote(update).(function, record) do
|
||||
current = :erlang.element(unquote(i), record)
|
||||
:erlang.setelement(unquote(i), record, function.(current))
|
||||
current = :erlang.element(unquote(i + 1), record)
|
||||
:erlang.setelement(unquote(i + 1), record, function.(current))
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -345,13 +576,13 @@ defmodule Record.Definition do
|
||||
|
||||
quote do
|
||||
def unquote(prepend).(value, record) do
|
||||
current = :erlang.element(unquote(i), record)
|
||||
:erlang.setelement(unquote(i), record, value ++ current)
|
||||
current = :erlang.element(unquote(i + 1), record)
|
||||
:erlang.setelement(unquote(i + 1), record, value ++ current)
|
||||
end
|
||||
|
||||
def unquote(merge).(value, record) do
|
||||
current = :erlang.element(unquote(i), record)
|
||||
:erlang.setelement(unquote(i), record, Keyword.merge(current, value))
|
||||
current = :erlang.element(unquote(i + 1), record)
|
||||
:erlang.setelement(unquote(i + 1), record, Keyword.merge(current, value))
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -362,8 +593,8 @@ defmodule Record.Definition do
|
||||
|
||||
quote do
|
||||
def unquote(increment).(value // 1, record) do
|
||||
current = :erlang.element(unquote(i), record)
|
||||
:erlang.setelement(unquote(i), record, current + value)
|
||||
current = :erlang.element(unquote(i + 1), record)
|
||||
:erlang.setelement(unquote(i + 1), record, current + value)
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -373,9 +604,9 @@ defmodule Record.Definition do
|
||||
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)
|
||||
def unquote(toggle).(record) do
|
||||
current = :erlang.element(unquote(i + 1), record)
|
||||
:erlang.setelement(unquote(i + 1), record, not current)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
+135
-59
@@ -28,6 +28,7 @@ defmodule Regex do
|
||||
* firstline (f) - forces the unanchored pattern to match before or at the first
|
||||
newline, though the matched text may continue over the newline
|
||||
* ungreedy (r) - invert the "greediness" of the regexp
|
||||
* groups (g) - compile with info about groups available
|
||||
|
||||
The options not available are:
|
||||
|
||||
@@ -53,10 +54,12 @@ defmodule Regex do
|
||||
def compile(source, options // "") do
|
||||
source = to_binary(source)
|
||||
options = to_binary(options)
|
||||
re_opts = translate_options(options)
|
||||
case Erlang.re.compile(source, re_opts) do
|
||||
opts = translate_options(options)
|
||||
re_opts = opts -- [:groups]
|
||||
groups = if opts != re_opts, do: parse_groups(source)
|
||||
case :re.compile(source, re_opts) do
|
||||
{ :ok, compiled } ->
|
||||
{ :ok, { Regex, compiled, source, options } }
|
||||
{ :ok, { Regex, compiled, source, options, groups } }
|
||||
error ->
|
||||
error
|
||||
end
|
||||
@@ -84,8 +87,8 @@ defmodule Regex do
|
||||
Regex.index %r/e/, "abcd" #=> nil
|
||||
|
||||
"""
|
||||
def index({ Regex, compiled, _, _ }, string) do
|
||||
case Erlang.re.run(string, compiled, [{ :capture, :first, :index }]) do
|
||||
def index({ Regex, compiled, _, _, _ }, string) do
|
||||
case :re.run(string, compiled, [{ :capture, :first, :index }]) do
|
||||
:nomatch -> nil
|
||||
{ :match, [{index,_}] } -> index
|
||||
end
|
||||
@@ -100,8 +103,8 @@ defmodule Regex do
|
||||
Regex.match? %r/foo/, "bar" #=> false
|
||||
|
||||
"""
|
||||
def match?({ Regex, compiled, _, _ }, string) do
|
||||
:nomatch != Erlang.re.run(string, compiled)
|
||||
def match?({ Regex, compiled, _, _, _ }, string) do
|
||||
:re.run(string, compiled, [{ :capture, :none }]) == :match
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -114,15 +117,43 @@ defmodule Regex do
|
||||
Regex.run %r/e/, "abcd" #=> nil
|
||||
|
||||
"""
|
||||
def run({ Regex, compiled, _, _ }, string) do
|
||||
case Erlang.re.run(string, compiled, [{ :capture, :all, return_for(string) }]) do
|
||||
:nomatch ->
|
||||
nil
|
||||
{ :match, results } ->
|
||||
results
|
||||
def run(regex, string, options // [])
|
||||
def run({ Regex, compiled, _, _, groups }, string, options) do
|
||||
return = Keyword.get(options, :return, return_for(string))
|
||||
|
||||
captures =
|
||||
case Keyword.get(options, :capture, :all) do
|
||||
:groups -> groups || raise "Regex was not compiled with g"
|
||||
others -> others
|
||||
end
|
||||
|
||||
case :re.run(string, compiled, [{ :capture, captures, return }]) do
|
||||
:nomatch -> nil
|
||||
{ :match, results } -> results
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns the given captures as a list of tuples.
|
||||
|
||||
## Examples
|
||||
|
||||
Regex.captures %r/c(?<foo>d)/g, "abcd" #=> [{:foo, ["d"]}]
|
||||
|
||||
"""
|
||||
def captures({ Regex, _, _, _, groups } = regex, string, options // []) do
|
||||
unless captures = Keyword.get(options, :capture) do
|
||||
captures = if groups do
|
||||
List.sort(groups)
|
||||
else
|
||||
raise "Regex was not compiled with g"
|
||||
end
|
||||
options = Keyword.put(options, :capture, captures)
|
||||
end
|
||||
results = run(regex, string, options)
|
||||
if results, do: Enum.zip captures, results
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns a list with the match indexes in the given string.
|
||||
The matches are tuples where the first element is the index
|
||||
@@ -135,19 +166,18 @@ defmodule Regex do
|
||||
Regex.indexes %r/e/, "abcd" #=> nil
|
||||
|
||||
"""
|
||||
def indexes({ Regex, compiled, _, _ }, string) do
|
||||
case Erlang.re.run(string, compiled, [{ :capture, :all, :index }]) do
|
||||
:nomatch ->
|
||||
nil
|
||||
{ :match, results } ->
|
||||
results
|
||||
def indexes({ Regex, compiled, _, _, _ }, string) do
|
||||
IO.write "[WARNING] Regex.indexes is deprecated, please use Regex.run with return: :index as option instead\n#{Exception.formatted_stacktrace}"
|
||||
case :re.run(string, compiled, [{ :capture, :all, :index }]) do
|
||||
:nomatch -> nil
|
||||
{ :match, results } -> results
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns the underlying re_pattern in the regular expression.
|
||||
"""
|
||||
def re_pattern({ Regex, compiled, _, _ }) do
|
||||
def re_pattern({ Regex, compiled, _, _, _ }) do
|
||||
compiled
|
||||
end
|
||||
|
||||
@@ -159,7 +189,7 @@ defmodule Regex do
|
||||
Regex.source %r(foo) #=> "foo"
|
||||
|
||||
"""
|
||||
def source({ Regex, _, source, _ }) do
|
||||
def source({ Regex, _, source, _, _ }) do
|
||||
source
|
||||
end
|
||||
|
||||
@@ -171,10 +201,22 @@ defmodule Regex do
|
||||
Regex.opts %r(foo)m #=> 'm'
|
||||
|
||||
"""
|
||||
def opts({ Regex, _, _, opts }) do
|
||||
def opts({ Regex, _, _, opts, _ }) do
|
||||
opts
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns list of named groups in regex.
|
||||
|
||||
## Examples
|
||||
|
||||
Regex.groups %r/(?<foo>foo)/g #=> ["foo"]
|
||||
|
||||
"""
|
||||
def groups({ Regex, _, _, _, groups }) do
|
||||
groups
|
||||
end
|
||||
|
||||
@doc """
|
||||
Same as run, but scans the target several times collecting all matches of
|
||||
the regular expression. A list is returned with each match. If the item in
|
||||
@@ -188,35 +230,49 @@ defmodule Regex do
|
||||
Regex.scan %r/e/, "abcd" #=> []
|
||||
|
||||
"""
|
||||
def scan({ Regex, compiled, _, _ }, string) do
|
||||
options = [{ :capture, :all, return_for(string) }, :global, { :offset, 0 }]
|
||||
case Erlang.re.run(string, compiled, options) do
|
||||
def scan(regex, string, options // [])
|
||||
def scan({ Regex, compiled, _, _, _ }, string, options) do
|
||||
return = options[:return] || return_for(string)
|
||||
options = [{ :capture, :all, return }, :global]
|
||||
case :re.run(string, compiled, options) do
|
||||
:nomatch -> []
|
||||
{ :match, results } ->
|
||||
lc result inlist results do
|
||||
case result do
|
||||
[t] -> t
|
||||
[h|t] -> t
|
||||
end
|
||||
end
|
||||
{ :match, results } -> flatten_result(results)
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Split the given target in the number of parts specified. If no ammount
|
||||
of parts is given, it defaults to :infinity.
|
||||
Split the given target in the number of parts specified.
|
||||
If no ammount of parts is given, it defaults to :infinity.
|
||||
"""
|
||||
def split({ Regex, compiled, _, _ }, string, parts // :infinity) do
|
||||
options = [{ :return, return_for(string) }, :trim, { :parts, parts }]
|
||||
Erlang.re.split(string, compiled, options)
|
||||
|
||||
def split(regex, string, options // [])
|
||||
|
||||
def split(regex, string, options) when is_integer(options) or is_atom(options) do
|
||||
IO.write "[WARNING] Passing an integer or atom to Regex.split/3 is deprecated, pass a :parts option instead\n#{Exception.formatted_stacktrace}"
|
||||
split(regex, string, parts: options)
|
||||
end
|
||||
|
||||
def split({ Regex, compiled, _, _, _ }, string, options) do
|
||||
parts =
|
||||
cond do
|
||||
options[:global] == false -> 2
|
||||
p = options[:parts] -> p
|
||||
true -> :infinity
|
||||
end
|
||||
|
||||
return = options[:return] || return_for(string)
|
||||
opts = [{ :return, return }, { :parts, parts }]
|
||||
:re.split(string, compiled, opts)
|
||||
end
|
||||
|
||||
@doc %B"""
|
||||
Receives a string and a replacement and returns a string where the
|
||||
first match of the regular expressions is replaced by replacement.
|
||||
Inside the replacement, you can either give "&" to access the whole
|
||||
regular expression or \N, where N is in integer to access an specific
|
||||
matching parens.
|
||||
Receives a regex, a binary and a replacement and returns a new
|
||||
binary where the all matches are replaced by replacement.
|
||||
|
||||
Inside the replacement, you can either give "&" to access the
|
||||
whole regular expression or \N, where N is in integer to access
|
||||
a specific matching parens. You can also set global to false
|
||||
if you want to replace just the first occurrence.
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -227,23 +283,23 @@ defmodule Regex do
|
||||
Regex.replace(%r/(b)/, "abc", "[\\1]") #=> "a[b]c"
|
||||
|
||||
"""
|
||||
def replace({ Regex, compiled, _, _ }, string, replacement) do
|
||||
Erlang.re.replace(string, compiled, replacement, [{ :return, return_for(string) }])
|
||||
def replace({ Regex, compiled, _, _, _ }, string, replacement, options // []) do
|
||||
opts = if options[:global] != false, do: [:global], else: []
|
||||
return = options[:return] || return_for(string)
|
||||
opts = [{ :return, return }|opts]
|
||||
:re.replace(string, compiled, replacement, opts)
|
||||
end
|
||||
|
||||
@doc """
|
||||
The same as replace, but replaces all parts where the regular
|
||||
expressions matches in the string. Please read `replace/3` for
|
||||
documentation and examples.
|
||||
"""
|
||||
def replace_all({ Regex, compiled, _, _ }, string, replacement) do
|
||||
Erlang.re.replace(string, compiled, replacement, [{ :return, return_for(string) }, :global])
|
||||
@doc false
|
||||
def replace_all({ Regex, compiled, _, _, _ }, string, replacement) do
|
||||
IO.write "[WARNING] Regex.replace_all is deprecated, simply use Regex.replace instead\n#{Exception.formatted_stacktrace}"
|
||||
:re.replace(string, compiled, replacement, [{ :return, return_for(string) }, :global])
|
||||
end
|
||||
|
||||
# Helpers
|
||||
|
||||
@doc false
|
||||
# Unescape map function used by Binary.unescape.
|
||||
# Unescape map function used by Macro.unescape_binary.
|
||||
def unescape_map(?f), do: ?\f
|
||||
def unescape_map(?n), do: ?\n
|
||||
def unescape_map(?r), do: ?\r
|
||||
@@ -256,12 +312,32 @@ defmodule Regex do
|
||||
defp return_for(element) when is_binary(element), do: :binary
|
||||
defp return_for(element) when is_list(element), do: :list
|
||||
|
||||
defp translate_options(<<?u, t|:binary>>), do: [:unicode|translate_options(t)]
|
||||
defp translate_options(<<?i, t|:binary>>), do: [:caseless|translate_options(t)]
|
||||
defp translate_options(<<?x, t|:binary>>), do: [:extended|translate_options(t)]
|
||||
defp translate_options(<<?f, t|:binary>>), do: [:firstline|translate_options(t)]
|
||||
defp translate_options(<<?r, t|:binary>>), do: [:ungreedy|translate_options(t)]
|
||||
defp translate_options(<<?s, t|:binary>>), do: [:dotall,{:newline,:anycrlf}|translate_options(t)]
|
||||
defp translate_options(<<?m, t|:binary>>), do: [:multiline|translate_options(t)]
|
||||
defp translate_options(<<?u, t :: binary>>), do: [:unicode|translate_options(t)]
|
||||
defp translate_options(<<?i, t :: binary>>), do: [:caseless|translate_options(t)]
|
||||
defp translate_options(<<?x, t :: binary>>), do: [:extended|translate_options(t)]
|
||||
defp translate_options(<<?f, t :: binary>>), do: [:firstline|translate_options(t)]
|
||||
defp translate_options(<<?r, t :: binary>>), do: [:ungreedy|translate_options(t)]
|
||||
defp translate_options(<<?s, t :: binary>>), do: [:dotall,{:newline,:anycrlf}|translate_options(t)]
|
||||
defp translate_options(<<?m, t :: binary>>), do: [:multiline|translate_options(t)]
|
||||
defp translate_options(<<?g, t :: binary>>), do: [:groups|translate_options(t)]
|
||||
defp translate_options(<<>>), do: []
|
||||
|
||||
defp flatten_result(results) do
|
||||
lc result inlist results do
|
||||
case result do
|
||||
[t] -> t
|
||||
[h|t] -> t
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defp parse_groups(source) do
|
||||
options = [:global, {:capture, ['G'], :binary}]
|
||||
{:ok, pattern} = :re.compile(%B"\(\?<(?<G>[^>]*)>")
|
||||
case :re.run(source, pattern, options) do
|
||||
:nomatch -> []
|
||||
{ :match, results } ->
|
||||
lc [group] inlist results, do: binary_to_atom(group)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -0,0 +1,425 @@
|
||||
defmodule String do
|
||||
@moduledoc """
|
||||
A string in Elixir is a utf-8 binary. This module
|
||||
contains function to work with utf-8 data and its
|
||||
codepoints.
|
||||
|
||||
For working with raw binaries, use Erlang's :binary
|
||||
module.
|
||||
"""
|
||||
|
||||
@doc """
|
||||
Checks if a string is printable considering it is encoded
|
||||
as UTF-8. Returns true if so, false otherwise.
|
||||
|
||||
## Examples
|
||||
|
||||
String.printable?("abc") #=> true
|
||||
|
||||
"""
|
||||
|
||||
# Allow basic ascii chars
|
||||
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 in 160..191 do
|
||||
printable?(t)
|
||||
end
|
||||
|
||||
# From 16#C0 to 16#7FF
|
||||
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 in 224..236 and
|
||||
o1 >= 128 and o1 < 192 and o2 >= 128 and o2 < 192 do
|
||||
printable?(t)
|
||||
end
|
||||
|
||||
# From 16#D000 to 16#D7FF
|
||||
def printable?(<<237, o1, o2, t :: binary>>) when
|
||||
o1 >= 128 and o1 < 160 and o2 >= 128 and o2 < 192 do
|
||||
printable?(t)
|
||||
end
|
||||
|
||||
# Reject 16#FFFF and 16#FFFE
|
||||
def printable?(<<239, 191, o>>) when o == 190 or o == 191 do
|
||||
false
|
||||
end
|
||||
|
||||
# From 16#E000 to 16#EFFF
|
||||
def printable?(<<m, o1, o2, t :: binary>>) when (m == 238 or m == 239) and
|
||||
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 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 in 144..191 and o2 in 128..191 and o3 in 128..191 do
|
||||
printable?(t)
|
||||
end
|
||||
|
||||
# Reject 16#110000 onwards
|
||||
def printable?(<<244, o1, _, _, _ :: binary>>) when o1 >= 144 do
|
||||
false
|
||||
end
|
||||
|
||||
# From 16#4000 to 16#10FFFF
|
||||
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
|
||||
|
||||
def printable?(<<?\n, t :: binary>>), do: printable?(t)
|
||||
def printable?(<<?\r, t :: binary>>), do: printable?(t)
|
||||
def printable?(<<?\t, t :: binary>>), do: printable?(t)
|
||||
def printable?(<<?\v, t :: binary>>), do: printable?(t)
|
||||
def printable?(<<?\b, t :: binary>>), do: printable?(t)
|
||||
def printable?(<<?\f, t :: binary>>), do: printable?(t)
|
||||
def printable?(<<?\e, t :: binary>>), do: printable?(t)
|
||||
|
||||
def printable?(<<>>), do: true
|
||||
def printable?(_), do: false
|
||||
|
||||
|
||||
@doc """
|
||||
Divides a string into sub string based on a pattern,
|
||||
returning a list of these sub string. The pattern can
|
||||
be a string, a list of strings or a regular expression.
|
||||
|
||||
The string is split into two parts by default, unless
|
||||
`global` option is true. If a pattern is not specified,
|
||||
the string is split on whitespace occurrences.
|
||||
|
||||
It returns a list with the original string if the pattern
|
||||
can't be matched.
|
||||
|
||||
## Examples
|
||||
|
||||
String.split("a,b,c", ",") #=> ["a", "b", "c"]
|
||||
String.split("a,b,c", ",", global: false) #=> ["a", "b,c"]
|
||||
|
||||
String.split("foo bar") #=> ["foo", "bar"]
|
||||
String.split("1,2 3,4", [" ", ","]) #=> ["1", "2", "3", "4"]
|
||||
|
||||
String.split("a,b,c", %r{,}) #=> ["a", "b", "c"]
|
||||
String.split("a,b,c", %r{,}, global: false) #=> ["a", "b,c"]
|
||||
String.split("a,b", %r{\.}) #=> ["a,b"]
|
||||
|
||||
"""
|
||||
def split(binary, pattern // " ", options // [])
|
||||
|
||||
def split(binary, pattern, options) when is_regex(pattern) do
|
||||
Regex.split(pattern, binary, global: options[:global])
|
||||
end
|
||||
|
||||
def split(binary, pattern, options) do
|
||||
opts = if options[:global] != false, do: [:global], else: []
|
||||
:binary.split(binary, pattern, opts)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Convert all characters on the given string to upper case.
|
||||
|
||||
## Examples
|
||||
|
||||
String.upcase("abcd") #=> "ABCD"
|
||||
String.upcase("ab 123 xpto") #=> "AB 123 XPTO"
|
||||
String.upcase("josé") #=> "JOSÉ"
|
||||
|
||||
"""
|
||||
def upcase(<<>>), do: <<>>
|
||||
|
||||
def upcase(<<195, c, t :: binary>>) when c in 160..191 do
|
||||
<<195, c - 32, upcase(t) :: binary>>
|
||||
end
|
||||
|
||||
def upcase(<<c, t :: binary>>) when c in ?a..?z do
|
||||
<<c - 32, upcase(t) :: binary>>
|
||||
end
|
||||
|
||||
def upcase(<<c, t :: binary>>) do
|
||||
<<c , upcase(t) :: binary>>
|
||||
end
|
||||
|
||||
@doc """
|
||||
Convert all characters on the given string to down case.
|
||||
|
||||
## Examples
|
||||
|
||||
String.downcase("ABCD") #=> "abcd"
|
||||
String.downcase("AB 123 XPTO") #=> "ab 123 xpto"
|
||||
String.downcase("JOSÉ") #=> "josé"
|
||||
|
||||
"""
|
||||
def downcase(<<>>), do: <<>>
|
||||
|
||||
def downcase(<<195, c, t :: binary>>) when c in 128..159 do
|
||||
<<195, c + 32, downcase(t) :: binary>>
|
||||
end
|
||||
|
||||
def downcase(<<c, t :: binary>>) when c in ?A..?Z do
|
||||
<<c + 32, downcase(t) :: binary>>
|
||||
end
|
||||
|
||||
def downcase(<<c, t :: binary>>) do
|
||||
<<c , downcase(t) :: binary>>
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns a string where trailing char have been
|
||||
removed. If no `char` is passed `space`is used.
|
||||
|
||||
## Examples
|
||||
|
||||
String.rstrip(" abc ") #=> " abc"
|
||||
String.rstrip(" abc _", ?_) #=> " abc "
|
||||
|
||||
"""
|
||||
def rstrip(string, char // ?\s)
|
||||
|
||||
# Do a quick check before we traverse the whole
|
||||
# binary. :binary.last is a fast operation (it
|
||||
# does not traverse the whole binary).
|
||||
def rstrip(string, char) do
|
||||
if :binary.last(string) == char do
|
||||
rstrip(string, "", char)
|
||||
else
|
||||
string
|
||||
end
|
||||
end
|
||||
|
||||
defp rstrip(<<char, string :: binary>>, buffer, char) do
|
||||
rstrip(string, <<char, buffer :: binary>>, char)
|
||||
end
|
||||
|
||||
defp rstrip(<<char, string :: binary>>, buffer, another_char) do
|
||||
<<buffer :: binary, char, rstrip(string, "", another_char) :: binary>>
|
||||
end
|
||||
|
||||
defp rstrip(<<>>, _, _) do
|
||||
<<>>
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns a string where leading char have been
|
||||
removed. If no `char` is passed `space`is used.
|
||||
|
||||
## Examples
|
||||
|
||||
String.lstrip(" abc ") #=> "abc "
|
||||
String.lstrip("_ abc _", ?_) #=> " abc _"
|
||||
|
||||
"""
|
||||
def lstrip(string, char // ?\s)
|
||||
|
||||
def lstrip(<<char, rest :: binary>>, char) do
|
||||
<<lstrip(rest, char) :: binary>>
|
||||
end
|
||||
|
||||
def lstrip(other, _char) do
|
||||
other
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns a string where leading/trailing char have been
|
||||
removed. If no `char` is passed `space`is used.
|
||||
|
||||
## Examples
|
||||
|
||||
String.strip(" abc ") #=> "abc"
|
||||
String.strip("a abc a", ?a) #=> " abc "
|
||||
|
||||
"""
|
||||
def strip(string, char // ?\s) do
|
||||
rstrip(lstrip(string, char), char)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns a new binary based on `subject` by replacing the parts
|
||||
matching `pattern` for `replacement`. If `options` is specified
|
||||
with `[global: true]`, then it will replace all matches, otherwise
|
||||
it will replace just the first one.
|
||||
|
||||
For the replaced part must be used in `replacement`, then the
|
||||
position or the positions where it is to be inserted must be specified by using
|
||||
the option `insert_replaced`.
|
||||
|
||||
## Examples
|
||||
|
||||
String.replace("a,b,c", ",", "-") #=> "a-b-c"
|
||||
String.replace("a,b,c", ",", "-", global: false) #=> "a-b,c"
|
||||
String.replace("a,b,c", "b", "[]", insert_replaced: 1) #=> "a,[b],c"
|
||||
String.replace("a,b,c", ",", "[]", insert_replaced: 2) #=> "a[],b[],c"
|
||||
String.replace("a,b,c", ",", "[]", insert_replaced: [1,1]) #=> "a[,,]b[,,]c"
|
||||
|
||||
"""
|
||||
def replace(subject, pattern, replacement, options // []) do
|
||||
opts = translate_replace_options(options)
|
||||
:binary.replace(subject, pattern, replacement, opts)
|
||||
end
|
||||
|
||||
defp translate_replace_options(options) do
|
||||
opts = if options[:global] != false, do: [:global], else: []
|
||||
|
||||
if insert = options[:insert_replaced] do
|
||||
opts = [{:insert_replaced,insert}|opts]
|
||||
end
|
||||
|
||||
opts
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns a binary `subject` duplicated `n` times.
|
||||
|
||||
## Examples
|
||||
|
||||
String.duplicate("abc", 1) #=> "abc"
|
||||
String.duplicate("abc", 2) #=> "abcabc"
|
||||
|
||||
"""
|
||||
def duplicate(subject, n) when is_integer(n) and n > 0 do
|
||||
:binary.copy(subject, n)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns a list with codepoints from an utf8 string.
|
||||
|
||||
## Examples
|
||||
|
||||
String.codepoints("josé") #=> ["j", "o", "s", "é"]
|
||||
String.codepoints("оптими зации") #=> ["о","п","т","и","м","и"," ","з","а","ц","и","и"]
|
||||
String.codepoints("ἅἪῼ") #=> ["ἅ","Ἢ","ῼ"]
|
||||
|
||||
"""
|
||||
def codepoints(string) do
|
||||
do_codepoints(codepoint(string))
|
||||
end
|
||||
|
||||
defp do_codepoints({char, rest}) do
|
||||
[char|do_codepoints(codepoint(rest))]
|
||||
end
|
||||
|
||||
defp do_codepoints(:no_codepoint), do: []
|
||||
|
||||
@doc """
|
||||
Returns the first codepoint from an utf8 string.
|
||||
|
||||
## Examples
|
||||
|
||||
String.first("elixir") #=> "e"
|
||||
String.first("եոգլի") #=> "ե"
|
||||
|
||||
"""
|
||||
def first(string) do
|
||||
case codepoint(string) do
|
||||
{ char, _ } -> char
|
||||
:no_codepoint -> ""
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns the last codepoint from an utf8 string.
|
||||
|
||||
## Examples
|
||||
|
||||
String.last("elixir") #=> "r"
|
||||
String.last("եոգլի") #=> "ի"
|
||||
|
||||
"""
|
||||
def last(string) do
|
||||
do_last(codepoint(string), "")
|
||||
end
|
||||
|
||||
defp do_last({char, rest}, _) do
|
||||
do_last(codepoint(rest), char)
|
||||
end
|
||||
|
||||
defp do_last(:no_codepoint, last_char), do: last_char
|
||||
|
||||
@doc """
|
||||
Returns the number of codepoint in an utf8 string.
|
||||
|
||||
## Examples
|
||||
|
||||
String.length("elixir") #=> 6
|
||||
String.length("եոգլի") #=> 5
|
||||
|
||||
"""
|
||||
def length(string) do
|
||||
do_length(codepoint(string))
|
||||
end
|
||||
|
||||
defp do_length({_, rest}) do
|
||||
1 + do_length(codepoint(rest))
|
||||
end
|
||||
|
||||
defp do_length(:no_codepoint), do: 0
|
||||
|
||||
@doc """
|
||||
Returns the codepoint in the `position` of the given utf8 `string`.
|
||||
If `position` is greater than `string` length, than it returns `nil`.
|
||||
|
||||
## Examples
|
||||
|
||||
String.at("elixir", 0) #=> "1"
|
||||
String.at("elixir", 1) #=> "l"
|
||||
String.at("elixir", 10) #=> nil
|
||||
String.at("elixir", -1) #=> "r"
|
||||
String.at("elixir", -10) #=> "nil"
|
||||
|
||||
"""
|
||||
def at(string, position) when position >= 0 do
|
||||
do_at(codepoint(string), position, 0)
|
||||
end
|
||||
|
||||
def at(string, position) when position < 0 do
|
||||
real_pos = do_length(codepoint(string)) - abs(position)
|
||||
case real_pos >= 0 do
|
||||
true -> do_at(codepoint(string), real_pos, 0)
|
||||
false -> ""
|
||||
end
|
||||
end
|
||||
|
||||
defp do_at({_ , rest}, desired_pos, current_pos) when desired_pos > current_pos do
|
||||
do_at(codepoint(rest), desired_pos, current_pos + 1)
|
||||
end
|
||||
|
||||
defp do_at({char, _}, desired_pos, current_pos) when desired_pos == current_pos do
|
||||
char
|
||||
end
|
||||
|
||||
defp do_at(:no_codepoint, _, _), do: ""
|
||||
|
||||
# Private implementation which returns the first codepoint
|
||||
# of any given utf8 string and the rest of it
|
||||
# If an empty string is given, :no_codepoint is returned.
|
||||
defp codepoint(<<194, char, rest :: binary>>)
|
||||
when char in 161..191,
|
||||
do: { <<194, char>>, rest }
|
||||
|
||||
defp codepoint(<<first, char, rest :: binary>>)
|
||||
when first in 195..223 and char in 128..191,
|
||||
do: { <<first, char>>, rest }
|
||||
|
||||
defp codepoint(<<first, second, char, rest :: binary>>)
|
||||
when first == 224 and second in 160..191 and char in 128..191,
|
||||
do: { <<first, second, char>>, rest }
|
||||
|
||||
defp codepoint(<<first, second, char, rest :: binary>>)
|
||||
when first in 225..239 and second in 128..191 and char in 128..191,
|
||||
do: { <<first, second, char>>, rest }
|
||||
|
||||
defp codepoint(<<other, rest :: binary>>), do: { <<other>>, rest }
|
||||
|
||||
defp codepoint(<<>>), do: :no_codepoint
|
||||
end
|
||||
+11
-14
@@ -5,12 +5,12 @@ defmodule System do
|
||||
with the VM or the host system.
|
||||
"""
|
||||
|
||||
# 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
|
||||
# Tries to run `git describe --always --tags`. In case of success
|
||||
# returns the most recent tag, otherwise returns an empty string.
|
||||
defmacrop get_describe do
|
||||
if :os.find_executable('git') do
|
||||
data = :os.cmd('git rev-parse HEAD')
|
||||
Regex.replace_all %r/\n/, to_binary(data), ""
|
||||
data = :os.cmd('git describe --always --tags')
|
||||
Regex.replace %r/\n/, to_binary(data), ""
|
||||
else
|
||||
""
|
||||
end
|
||||
@@ -24,23 +24,20 @@ defmodule System do
|
||||
@doc """
|
||||
Returns Elixir's version as binary.
|
||||
"""
|
||||
def version, do: "0.6.0"
|
||||
def version, do: "0.7.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.
|
||||
Returns a keywords list with version, git tag info and date.
|
||||
"""
|
||||
def build_info do
|
||||
{ version, get_head_sha, get_date }
|
||||
[version: version, tag: get_describe, date: get_date]
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns the list of command-line arguments passed to the program.
|
||||
"""
|
||||
def argv do
|
||||
Erlang.gen_server.call(:elixir_code_server, :argv)
|
||||
:gen_server.call(:elixir_code_server, :argv)
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -138,7 +135,7 @@ defmodule System do
|
||||
Get the stacktrace.
|
||||
"""
|
||||
def stacktrace do
|
||||
filter_stacktrace Erlang.erlang.get_stacktrace
|
||||
filter_stacktrace :erlang.get_stacktrace
|
||||
end
|
||||
|
||||
## Helpers
|
||||
@@ -151,6 +148,6 @@ defmodule System do
|
||||
defp filter_stacktrace([]), do: []
|
||||
|
||||
defp server_call(args) do
|
||||
Erlang.gen_server.call(:elixir_code_server, args)
|
||||
:gen_server.call(:elixir_code_server, args)
|
||||
end
|
||||
end
|
||||
|
||||
+39
-29
@@ -19,35 +19,45 @@ defmodule URI do
|
||||
that implements the Binary.Chars protocol (i.e. can be converted
|
||||
to binary).
|
||||
"""
|
||||
def encode_query(l), do: Enum.join(Enum.map(l, pair(&1)), "&")
|
||||
def encode_query(l), do: Enum.map_join(l, "&", pair(&1))
|
||||
|
||||
@doc """
|
||||
Given a query string of the form "key1=value1&key=value2...", produces an
|
||||
orddict with one entry for each key-value pair. Each key and value will be a
|
||||
binary. It also does percent-unescaping of both keys and values.
|
||||
|
||||
Returns nil if the query string is malformed.
|
||||
Use decoder/1 if you want to customize or iterate each value manually.
|
||||
"""
|
||||
def decode_query(q, dict // Orddict.new) do
|
||||
if Regex.match?(%r/^\s*$/, q) do
|
||||
dict
|
||||
else
|
||||
parts = Regex.split %r/&/, to_binary(q)
|
||||
impl = Dict.__impl_for__!(dict)
|
||||
def decode_query(q, dict // OrdDict.new) do
|
||||
Enum.reduce query_decoder(q), dict, fn({ k, v }, acc) -> Dict.put(acc, k, v) end
|
||||
end
|
||||
|
||||
try do
|
||||
List.foldl parts, dict, fn kvstr, acc ->
|
||||
case Regex.split(%r/=/, kvstr) do
|
||||
[ key, value ] when key != "" ->
|
||||
impl.put acc, decode(key), decode(value)
|
||||
_ ->
|
||||
throw :malformed_query_string
|
||||
end
|
||||
end
|
||||
catch
|
||||
:malformed_query_string -> nil
|
||||
@doc """
|
||||
Returns an iterator function over the query string that decodes
|
||||
the query string in steps.
|
||||
"""
|
||||
def query_decoder(q) do
|
||||
fn -> { do_decoder(&1), do_decoder(to_binary(q)) } end
|
||||
end
|
||||
|
||||
defp do_decoder("") do
|
||||
:stop
|
||||
end
|
||||
|
||||
defp do_decoder(q) do
|
||||
next =
|
||||
case :binary.split(q, "&") do
|
||||
[first, rest] -> rest
|
||||
[first] -> ""
|
||||
end
|
||||
end
|
||||
|
||||
current =
|
||||
case :binary.split(first, "=") do
|
||||
[ key, value ] -> { decode(key), decode(value) }
|
||||
[ key ] -> { decode(key), nil }
|
||||
end
|
||||
|
||||
{ current, next }
|
||||
end
|
||||
|
||||
defp pair({k, v}) do
|
||||
@@ -57,17 +67,17 @@ defmodule URI do
|
||||
@doc """
|
||||
Percent (URL) encodes a URI.
|
||||
"""
|
||||
def encode(s), do: bc <<c>> inbits s, do: <<percent(c)|:binary>>
|
||||
def encode(s), do: bc <<c>> inbits s, do: <<percent(c) :: binary>>
|
||||
|
||||
defp percent(32), do: <<?+>>
|
||||
defp percent(?-), do: <<?->>
|
||||
defp percent(?_), do: <<?_>>
|
||||
defp percent(?.), do: <<?.>>
|
||||
|
||||
defp percent(c) when
|
||||
c >= ?0 and c <= ?9 when
|
||||
c >= ?a and c <= ?z when
|
||||
c >= ?A and c <= ?Z do
|
||||
defp percent(c)
|
||||
when c >= ?0 and c <= ?9
|
||||
when c >= ?a and c <= ?z
|
||||
when c >= ?A and c <= ?Z do
|
||||
<<c>>
|
||||
end
|
||||
|
||||
@@ -84,11 +94,11 @@ defmodule URI do
|
||||
@doc """
|
||||
Unpercent (URL) decodes a URI.
|
||||
"""
|
||||
def decode(<<?%, hex1, hex2, tail |:binary >>) do
|
||||
def decode(<<?%, hex1, hex2, tail :: binary >>) do
|
||||
<< bsl(hex2dec(hex1), 4) + hex2dec(hex2) >> <> decode(tail)
|
||||
end
|
||||
|
||||
def decode(<<head, tail |:binary >>) do
|
||||
def decode(<<head, tail :: binary >>) do
|
||||
<<check_plus(head)>> <> decode(tail)
|
||||
end
|
||||
|
||||
@@ -98,7 +108,7 @@ defmodule URI do
|
||||
defp hex2dec(n) when n in ?0..?9, do: n - ?0
|
||||
|
||||
defp check_plus(?+), do: 32
|
||||
defp check_plus(c), do: c
|
||||
defp check_plus(c), do: c
|
||||
|
||||
@doc """
|
||||
Parses a URI into components.
|
||||
@@ -121,7 +131,7 @@ defmodule URI do
|
||||
def parse(s) do
|
||||
# From http://tools.ietf.org/html/rfc3986#appendix-B
|
||||
regex = %r/^(([^:\/?#]+):)?(\/\/([^\/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?/
|
||||
parts = nillify(Regex.run(regex, s))
|
||||
parts = nillify(Regex.run(regex, to_binary(s)))
|
||||
|
||||
destructure [_, _, scheme, _, authority, path, _, query, _, fragment], parts
|
||||
{ userinfo, host, port } = split_authority(authority)
|
||||
|
||||
@@ -4,8 +4,15 @@ defmodule URI.Parser do
|
||||
Check URI.HTTP for a possible implementation.
|
||||
"""
|
||||
|
||||
def behaviour_info(:callbacks) do
|
||||
[parse: 1,
|
||||
default_port: 0]
|
||||
end
|
||||
use Behaviour
|
||||
|
||||
@doc """
|
||||
Responsible for parsing extra URL information.
|
||||
"""
|
||||
defcallback parse(uri_info)
|
||||
|
||||
@doc """
|
||||
Responsible for returning the default port.
|
||||
"""
|
||||
defcallback default_port()
|
||||
end
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
defmodule Elixir.Mixfile do
|
||||
use Mix.Project
|
||||
|
||||
def project do
|
||||
[ app: :elixir,
|
||||
version: System.version,
|
||||
escript_embed_elixir: false,
|
||||
escript_main_module: :elixir,
|
||||
escript_emu_args: "%%! -noshell\n" ]
|
||||
end
|
||||
end
|
||||
+32
-19
@@ -1,12 +1,13 @@
|
||||
-module(elixir).
|
||||
-behaviour(application).
|
||||
-export([start_cli/0, start_app/0,
|
||||
-export([main/1, start_cli/0, start_app/0,
|
||||
scope_for_eval/1, eval/2, eval/3, eval/4,
|
||||
eval_quoted/2, eval_quoted/3, eval_quoted/4,
|
||||
eval_forms/3]).
|
||||
-include("elixir.hrl").
|
||||
-compile({parse_transform, elixir_transform}).
|
||||
|
||||
|
||||
% OTP APPLICATION API
|
||||
|
||||
-export([start/2, stop/1, config_change/3]).
|
||||
@@ -23,6 +24,12 @@ stop(_S) ->
|
||||
config_change(_Changed, _New, _Remove) ->
|
||||
ok.
|
||||
|
||||
%% escript entry point
|
||||
|
||||
main(Args) ->
|
||||
start_app(),
|
||||
'Elixir.Kernel.CLI':process_argv(Args).
|
||||
|
||||
%% ELIXIR ENTRY POINTS
|
||||
|
||||
% Start the Elixir app. This is the proper way to boot Elixir from
|
||||
@@ -43,14 +50,14 @@ start_cli() ->
|
||||
%% EVAL HOOKS
|
||||
|
||||
scope_for_eval(Opts) ->
|
||||
File = case orddict:find(file, Opts) of
|
||||
{ ok, F } -> to_binary(F);
|
||||
error -> <<"nofile">>
|
||||
File = case lists:keyfind(file, 1, Opts) of
|
||||
{ file, F } -> to_binary(F);
|
||||
false -> <<"nofile">>
|
||||
end,
|
||||
|
||||
Local = case orddict:find(delegate_locals_to, Opts) of
|
||||
{ ok, L } -> L;
|
||||
error -> nil
|
||||
Local = case lists:keyfind(delegate_locals_to, 1, Opts) of
|
||||
{ delegate_locals_to, L } -> L;
|
||||
false -> nil
|
||||
end,
|
||||
|
||||
#elixir_scope{file=File,local=Local}.
|
||||
@@ -60,15 +67,15 @@ scope_for_eval(Opts) ->
|
||||
eval(String, Binding) -> eval(String, Binding, []).
|
||||
|
||||
eval(String, Binding, Opts) ->
|
||||
case orddict:find(line, Opts) of
|
||||
{ ok, Line } -> [];
|
||||
error -> Line = 1
|
||||
case lists:keyfind(line, 1, Opts) of
|
||||
false -> Line = 1;
|
||||
{ line, Line } -> []
|
||||
end,
|
||||
eval(String, Binding, Line, scope_for_eval(Opts)).
|
||||
|
||||
eval(String, Binding, Line, #elixir_scope{file=File} = S) when
|
||||
is_list(String), is_list(Binding), is_integer(Line), is_binary(File) ->
|
||||
Forms = elixir_translator:forms(String, Line, File),
|
||||
Forms = elixir_translator:'forms!'(String, Line, File, []),
|
||||
eval_forms(Forms, Binding, S).
|
||||
|
||||
%% Quoted evaluation
|
||||
@@ -76,9 +83,9 @@ eval(String, Binding, Line, #elixir_scope{file=File} = S) when
|
||||
eval_quoted(Tree, Binding) -> eval_quoted(Tree, Binding, []).
|
||||
|
||||
eval_quoted(Tree, Binding, Opts) ->
|
||||
case orddict:find(line, Opts) of
|
||||
{ ok, Line } -> [];
|
||||
error -> Line = 1
|
||||
case lists:keyfind(line, 1, Opts) of
|
||||
{ line, Line } -> [];
|
||||
false -> Line = 1
|
||||
end,
|
||||
eval_quoted(Tree, Binding, Line, scope_for_eval(Opts)).
|
||||
|
||||
@@ -89,7 +96,13 @@ eval_quoted(Tree, Binding, Line, #elixir_scope{} = S) ->
|
||||
%% internal API not meant for external usage.
|
||||
|
||||
eval_forms(Tree, Binding, RawScope) ->
|
||||
Scope = RawScope#elixir_scope{vars=binding_dict(Binding)},
|
||||
Scope = RawScope#elixir_scope{
|
||||
vars=binding_dict(Binding),
|
||||
temp_vars=[],
|
||||
quote_vars=[],
|
||||
clause_vars=[],
|
||||
counter=0
|
||||
},
|
||||
{ ParseTree, NewScope } = elixir_translator:translate(Tree, Scope),
|
||||
case ParseTree of
|
||||
[] -> { nil, Binding, NewScope };
|
||||
@@ -103,8 +116,8 @@ eval_forms(Tree, Binding, RawScope) ->
|
||||
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));
|
||||
binding_dict(List) -> binding_dict(List, orddict:new()).
|
||||
binding_dict([{H,_}|T], Dict) -> binding_dict(T, orddict:store(H, H, Dict));
|
||||
binding_dict([], Dict) -> Dict.
|
||||
|
||||
final_binding(Binding, Vars) -> final_binding(Binding, [], Binding, Vars).
|
||||
@@ -112,7 +125,7 @@ final_binding([{Var,_}|T], Acc, Binding, Vars) ->
|
||||
case atom_to_list(Var) of
|
||||
"_@" ++ _ -> final_binding(T, Acc, Binding, Vars);
|
||||
_ ->
|
||||
RealName = dict:fetch(Var, Vars),
|
||||
RealName = orddict:fetch(Var, Vars),
|
||||
RealValue = proplists:get_value(RealName, Binding, nil),
|
||||
final_binding(T, [{Var, RealValue}|Acc], Binding, Vars)
|
||||
end;
|
||||
@@ -124,4 +137,4 @@ normalize_binding(Binding) ->
|
||||
case orddict:find('_@MODULE', Keyword) of
|
||||
{ ok, _ } -> Keyword;
|
||||
_ -> orddict:store('_@MODULE', nil, Keyword)
|
||||
end.
|
||||
end.
|
||||
|
||||
@@ -13,12 +13,12 @@ get_pairs(Line, Key, Clauses, S) ->
|
||||
get_pairs(Line, Key, Clauses, S, false).
|
||||
|
||||
get_pairs(Line, Key, Clauses, S, AllowNil) ->
|
||||
case orddict:find(Key, Clauses) of
|
||||
{ ok, { '->', _, Pairs } } ->
|
||||
case lists:keyfind(Key, 1, Clauses) of
|
||||
{ Key, { '->', _, Pairs } } ->
|
||||
[{ Key, Left, Right } || { Left, Right } <- Pairs];
|
||||
{ ok, nil } when AllowNil ->
|
||||
{ Key, nil } when AllowNil ->
|
||||
[];
|
||||
{ ok, _ } ->
|
||||
{ Key, _ } ->
|
||||
elixir_errors:syntax_error(Line, S#elixir_scope.file, "expected pairs with -> for key ~s", [Key]);
|
||||
_ ->
|
||||
[]
|
||||
@@ -27,7 +27,7 @@ get_pairs(Line, Key, Clauses, S, AllowNil) ->
|
||||
% Function for translating assigns.
|
||||
|
||||
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 } = assigns(Fun, Args, S#elixir_scope{context=assign, temp_vars=orddict:new()}),
|
||||
{ Result, NewS#elixir_scope{context=Context} };
|
||||
|
||||
assigns(Fun, Args, S) -> Fun(Args, S).
|
||||
@@ -88,7 +88,7 @@ extract_last_guards(Args) ->
|
||||
% Function for translating macros with match style like case and receive.
|
||||
|
||||
match(Line, DecoupledClauses, RawS) ->
|
||||
S = RawS#elixir_scope{clause_vars=dict:new()},
|
||||
S = RawS#elixir_scope{clause_vars=orddict:new()},
|
||||
|
||||
case DecoupledClauses of
|
||||
[DecoupledClause] ->
|
||||
@@ -106,7 +106,7 @@ match(Line, DecoupledClauses, RawS) ->
|
||||
|
||||
% Now get all the variables defined inside each clause
|
||||
CV = lists:reverse(RawCV),
|
||||
NewVars = lists:umerge([lists:sort(dict:fetch_keys(X)) || X <- CV]),
|
||||
NewVars = lists:umerge([lists:sort(orddict:fetch_keys(X)) || X <- CV]),
|
||||
|
||||
case NewVars of
|
||||
[] -> { TClauses, TS };
|
||||
@@ -119,44 +119,44 @@ match(Line, DecoupledClauses, RawS) ->
|
||||
{ FinalVars, FS } = lists:mapfoldl(fun normalize_vars/2, TS, NewVars),
|
||||
|
||||
% Defines a tuple that will be used as left side of the match operator
|
||||
LeftVars = [{var, Line, NewValue} || {_, NewValue,_} <- FinalVars],
|
||||
{ StorageVar, SS } = elixir_scope:build_erl_var(Line, FS),
|
||||
LeftVars = [{var, Line, NewValue} || { _, _, NewValue, _ } <- FinalVars],
|
||||
|
||||
% Expand all clauses by adding a match operation at the end that assigns
|
||||
% variables missing in one clause to the others.
|
||||
Expander = fun(Clause, Counter) ->
|
||||
ClauseVars = lists:nth(Counter, CV),
|
||||
RightVars = [normalize_clause_var(Var, OldValue, ClauseVars) || {Var, _, OldValue} <- FinalVars],
|
||||
|
||||
AssignExpr = generate_match(Line, LeftVars, RightVars),
|
||||
ClauseExprs = element(5, Clause),
|
||||
[Final|RawClauseExprs] = lists:reverse(ClauseExprs),
|
||||
|
||||
% If the last sentence has a match clause, we need to assign its value
|
||||
% in the variable list. If not, we insert the variable list before the
|
||||
% final clause in order to keep it tail call optimized.
|
||||
FinalClauseExprs = case has_match_tuple(Final) of
|
||||
true ->
|
||||
case Final of
|
||||
{ match, _, { var, _, UserVarName } = UserVar, _ } when UserVarName /= '_' ->
|
||||
[UserVar,AssignExpr,Final|RawClauseExprs];
|
||||
_ ->
|
||||
StorageExpr = { match, Line, StorageVar, Final },
|
||||
[StorageVar,AssignExpr,StorageExpr|RawClauseExprs]
|
||||
end;
|
||||
false ->
|
||||
[Final,AssignExpr|RawClauseExprs]
|
||||
end,
|
||||
|
||||
FinalClause = setelement(5, Clause, lists:reverse(FinalClauseExprs)),
|
||||
{ FinalClause, Counter + 1 }
|
||||
end,
|
||||
|
||||
{ FClauses, _ } = lists:mapfoldl(Expander, 1, TClauses),
|
||||
{ FClauses, SS }
|
||||
% Expand all clauses by adding a match operation at the end
|
||||
% that assigns variables missing in one clause to the others.
|
||||
expand_clauses(Line, TClauses, CV, LeftVars, FinalVars, [], FS)
|
||||
end
|
||||
end.
|
||||
|
||||
expand_clauses(Line, [Clause|T], [ClauseVars|V], LeftVars, FinalVars, Acc, S) ->
|
||||
RightVars = [normalize_clause_var(Var, Kind, OldValue, ClauseVars) || { Var, Kind, _, OldValue } <- FinalVars],
|
||||
|
||||
AssignExpr = generate_match(Line, LeftVars, RightVars),
|
||||
ClauseExprs = element(5, Clause),
|
||||
[Final|RawClauseExprs] = lists:reverse(ClauseExprs),
|
||||
|
||||
% If the last sentence has a match clause, we need to assign its value
|
||||
% in the variable list. If not, we insert the variable list before the
|
||||
% final clause in order to keep it tail call optimized.
|
||||
{ FinalClauseExprs, FS } = case has_match_tuple(Final) of
|
||||
true ->
|
||||
case Final of
|
||||
{ match, _, { var, _, UserVarName } = UserVar, _ } when UserVarName /= '_' ->
|
||||
{ [UserVar,AssignExpr,Final|RawClauseExprs], S };
|
||||
_ ->
|
||||
{ StorageVar, SS } = elixir_scope:build_erl_var(Line, S),
|
||||
StorageExpr = { match, Line, StorageVar, Final },
|
||||
{ [StorageVar,AssignExpr,StorageExpr|RawClauseExprs], SS }
|
||||
end;
|
||||
false ->
|
||||
{ [Final,AssignExpr|RawClauseExprs], S }
|
||||
end,
|
||||
|
||||
FinalClause = setelement(5, Clause, lists:reverse(FinalClauseExprs)),
|
||||
expand_clauses(Line, T, V, LeftVars, FinalVars, [FinalClause|Acc], FS);
|
||||
|
||||
expand_clauses(_Line, [], [], _LeftVars, _FinalVars, Acc, S) ->
|
||||
{ lists:reverse(Acc), S }.
|
||||
|
||||
% Handle each key/value clause pair and translate them accordingly.
|
||||
|
||||
each_clause(Line, { do, [Condition], Expr }, S) ->
|
||||
@@ -199,26 +199,31 @@ 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_scope:build_erl_var(0, S),
|
||||
normalize_vars({ Var, quoted }, S) ->
|
||||
normalize_vars(Var, quoted, #elixir_scope.quote_vars, S);
|
||||
|
||||
FS = NS#elixir_scope{
|
||||
vars=dict:store(Var, NewValue, Vars),
|
||||
clause_vars=dict:store(Var, NewValue, ClauseVars)
|
||||
},
|
||||
normalize_vars({ Var, Kind }, S) ->
|
||||
normalize_vars(Var, Kind, #elixir_scope.vars, S).
|
||||
|
||||
Expr = case dict:find(Var, Vars) of
|
||||
normalize_vars(Var, Kind, Index, #elixir_scope{clause_vars=ClauseVars} = S) ->
|
||||
Vars = element(Index, S),
|
||||
{ { _, _, NewValue }, S1 } = elixir_scope:build_erl_var(0, S),
|
||||
|
||||
S2 = setelement(Index, S1, orddict:store(Var, NewValue, Vars)),
|
||||
S3 = S2#elixir_scope{clause_vars=orddict:store({ Var, Kind }, NewValue, ClauseVars)},
|
||||
|
||||
Expr = case orddict:find(Var, Vars) of
|
||||
{ ok, OldValue } -> { var, 0, OldValue };
|
||||
error -> { atom, 0, nil }
|
||||
end,
|
||||
|
||||
{ { Var, NewValue, Expr }, FS }.
|
||||
{ { Var, Kind, NewValue, Expr }, S3 }.
|
||||
|
||||
% Normalize a var by checking if it was defined in the clause.
|
||||
% If so, use it, otherwise use from main scope.
|
||||
|
||||
normalize_clause_var(Var, OldValue, ClauseVars) ->
|
||||
case dict:find(Var, ClauseVars) of
|
||||
normalize_clause_var(Var, Kind, OldValue, ClauseVars) ->
|
||||
case orddict:find({ Var, Kind }, ClauseVars) of
|
||||
{ ok, ClauseValue } -> { var, 0, ClauseValue };
|
||||
error -> OldValue
|
||||
end.
|
||||
|
||||
@@ -16,27 +16,31 @@ init(_args) ->
|
||||
process_flag(trap_exit, true),
|
||||
{ ok, #elixir_code_server{} }.
|
||||
|
||||
handle_call({loaded, Path}, _From, Config) ->
|
||||
handle_call({ acquire, Path }, From, Config) ->
|
||||
Current = Config#elixir_code_server.loaded,
|
||||
case lists:member(Path, Current) of
|
||||
true ->
|
||||
{ reply, duplicated, Config#elixir_code_server{loaded=Current} };
|
||||
false ->
|
||||
{ reply, ok, Config#elixir_code_server{loaded=[Path|Current]} }
|
||||
case orddict:find(Path, Current) of
|
||||
{ ok, true } ->
|
||||
{ reply, loaded, Config };
|
||||
{ ok, { Ref, List } } when is_list(List), is_reference(Ref) ->
|
||||
Queued = orddict:store(Path, { Ref, [From|List] }, Current),
|
||||
{ reply, { queued, Ref }, Config#elixir_code_server{loaded=Queued} };
|
||||
error ->
|
||||
Queued = orddict:store(Path, { make_ref(), [] }, Current),
|
||||
{ reply, proceed, Config#elixir_code_server{loaded=Queued} }
|
||||
end;
|
||||
|
||||
handle_call({at_exit, AtExit}, _From, Config) ->
|
||||
handle_call({ at_exit, AtExit }, _From, Config) ->
|
||||
{ reply, ok, Config#elixir_code_server{at_exit=[AtExit|Config#elixir_code_server.at_exit]} };
|
||||
|
||||
handle_call({argv, Argv}, _From, Config) ->
|
||||
handle_call({ argv, Argv }, _From, Config) ->
|
||||
{ reply, ok, Config#elixir_code_server{argv=Argv} };
|
||||
|
||||
handle_call({compiler_options, Options}, _From, Config) ->
|
||||
handle_call({ compiler_options, Options }, _From, Config) ->
|
||||
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 };
|
||||
{ reply, [F || { F, true } <- Config#elixir_code_server.loaded], Config };
|
||||
|
||||
handle_call(at_exit, _From, Config) ->
|
||||
{ reply, Config#elixir_code_server.at_exit, Config };
|
||||
@@ -50,6 +54,25 @@ handle_call(compiler_options, _From, Config) ->
|
||||
handle_call(_Request, _From, Config) ->
|
||||
{ reply, undef, Config }.
|
||||
|
||||
handle_cast({ loaded, Path }, Config) ->
|
||||
Current = Config#elixir_code_server.loaded,
|
||||
case orddict:find(Path, Current) of
|
||||
{ ok, true } ->
|
||||
{ noreply, Config };
|
||||
{ ok, { Ref, List } } when is_list(List), is_reference(Ref) ->
|
||||
[Pid ! { elixir_code_server, Ref, loaded } || { Pid, _Tag } <- lists:reverse(List)],
|
||||
Done = orddict:store(Path, true, Current),
|
||||
{ noreply, Config#elixir_code_server{loaded=Done} };
|
||||
error ->
|
||||
Done = orddict:store(Path, true, Current),
|
||||
{ noreply, Config#elixir_code_server{loaded=Done} }
|
||||
end;
|
||||
|
||||
handle_cast({ unload_files, Files }, Config) ->
|
||||
Current = Config#elixir_code_server.loaded,
|
||||
Unloaded = lists:foldl(fun(File, Acc) -> orddict:erase(File, Acc) end, Current, Files),
|
||||
{ noreply, Config#elixir_code_server{loaded=Unloaded} };
|
||||
|
||||
handle_cast(_Request, Config) ->
|
||||
{ noreply, Config }.
|
||||
|
||||
|
||||
@@ -11,9 +11,9 @@
|
||||
get_opt(Key) -> get_opt(Key, get_opts()).
|
||||
|
||||
get_opt(Key, Dict) ->
|
||||
case orddict:find(Key, Dict) of
|
||||
{ ok, Value } -> Value;
|
||||
error -> false
|
||||
case lists:keyfind(Key, 1, Dict) of
|
||||
false -> false;
|
||||
{ Key, Value } -> Value
|
||||
end.
|
||||
|
||||
get_opts() ->
|
||||
@@ -26,7 +26,7 @@ string(Contents, File) when is_list(Contents), is_binary(File) ->
|
||||
|
||||
try
|
||||
put(elixir_compiled, []),
|
||||
Forms = elixir_translator:forms(Contents, 1, File),
|
||||
Forms = elixir_translator:'forms!'(Contents, 1, File, []),
|
||||
eval_forms(Forms, 1, File, [], #elixir_scope{file=File}),
|
||||
lists:reverse(get(elixir_compiled))
|
||||
after
|
||||
@@ -37,12 +37,8 @@ string(Contents, File) when is_list(Contents), is_binary(File) ->
|
||||
|
||||
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.
|
||||
{ ok, Bin } = file:read_file(File),
|
||||
string(unicode:characters_to_list(Bin), File).
|
||||
|
||||
%% Compiles a file to the given path (directory).
|
||||
|
||||
@@ -192,9 +188,14 @@ core_file(File) ->
|
||||
|
||||
core_list() ->
|
||||
[
|
||||
"lib/elixir/lib/range.ex",
|
||||
"lib/elixir/lib/behaviour.ex",
|
||||
"lib/elixir/lib/uri/parser.ex",
|
||||
"lib/elixir/lib/elixir/formatter.ex",
|
||||
"lib/elixir/lib/dict.ex",
|
||||
"lib/elixir/lib/dict/common.ex",
|
||||
"lib/elixir/lib/access.ex",
|
||||
"lib/elixir/lib/range.ex",
|
||||
"lib/elixir/lib/*/*.ex",
|
||||
"lib/elixir/lib/*.ex"
|
||||
].
|
||||
@@ -202,20 +203,20 @@ core_list() ->
|
||||
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/macro.ex",
|
||||
"lib/elixir/lib/macro/env.ex",
|
||||
"lib/elixir/lib/module.ex",
|
||||
"lib/elixir/lib/list.ex",
|
||||
"lib/elixir/lib/code.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"
|
||||
"lib/elixir/lib/gen_server/behaviour.ex"
|
||||
].
|
||||
|
||||
%% ERROR HANDLING
|
||||
|
||||
@@ -63,71 +63,64 @@ wrap_definition(Kind, Line, Name, Args, Guards, Expr, S) ->
|
||||
% Invoked by the wrap definition with the function abstract tree.
|
||||
% Each function is then added to the function table.
|
||||
|
||||
store_definition(Kind, Line, nil, _Name, _Args, _Guards, _Expr, RawS) ->
|
||||
store_definition(Kind, Line, nil, _Name, _Args, _Guards, _Body, RawS) ->
|
||||
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) ->
|
||||
store_definition(Kind, Line, Module, Name, Args, Guards, Body, RawS) ->
|
||||
Arity = length(Args),
|
||||
DS = elixir_scope:deserialize(RawS),
|
||||
S = DS#elixir_scope{function={Name,Arity}, module=Module},
|
||||
Expr = def_body(Line, RawExpr),
|
||||
Expr = def_body(Line, Body),
|
||||
|
||||
CO = elixir_compiler:get_opts(),
|
||||
Location = retrieve_file(Module, CO),
|
||||
run_on_definition_callbacks(Kind, Line, Module, Name, Args, Guards, Body, S, CO),
|
||||
|
||||
{ Function, Defaults, TS } = translate_definition(Kind, Line, Name, Args, Guards, Expr, S),
|
||||
|
||||
CO = elixir_compiler:get_opts(),
|
||||
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),
|
||||
File = TS#elixir_scope.file,
|
||||
Table = table(Module),
|
||||
Stack = TS#elixir_scope.macro,
|
||||
|
||||
%% Store function
|
||||
case RawExpr of
|
||||
skip_definition -> [];
|
||||
_ ->
|
||||
if
|
||||
(Body == nil) -> [];
|
||||
true ->
|
||||
compile_super(Module, TS),
|
||||
CheckClauses = S#elixir_scope.check_clauses,
|
||||
store_each(CheckClauses, Kind, File, Location,
|
||||
Stack, Table, length(Defaults), Function)
|
||||
end,
|
||||
|
||||
%% Store defaults
|
||||
[store_each(false, Kind, File, Location, Stack, Table, 0,
|
||||
function_for_default(Kind, Name, Default)) || Default <- Defaults],
|
||||
|
||||
{ Name, Arity }.
|
||||
|
||||
def_body(_Line, skip_definition) -> nil;
|
||||
def_body(_Line, [{ do, Expr }]) -> Expr;
|
||||
def_body(Line, Else) -> { 'try', Line, [Else] }.
|
||||
def_body(_Line, nil) -> nil;
|
||||
def_body(_Line, [{ do, Expr }]) -> Expr;
|
||||
def_body(Line, Else) -> { 'try', Line, [Else] }.
|
||||
|
||||
%% Compile super clause
|
||||
%% @on_definition
|
||||
|
||||
compile_super(Module, #elixir_scope{function=Function, super=true}) ->
|
||||
elixir_def_overridable:store(Module, Function, true);
|
||||
|
||||
compile_super(_Module, _S) -> [].
|
||||
|
||||
%% 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
|
||||
run_on_definition_callbacks(Kind, Line, Module, Name, Args, Guards, Expr, S, CO) ->
|
||||
case elixir_compiler:get_opt(internal, CO) of
|
||||
true ->
|
||||
ok;
|
||||
_ ->
|
||||
Env = elixir_scope:to_ex_env({ Line, S }),
|
||||
Callbacks = 'Elixir.Module':get_attribute(Module, on_definition),
|
||||
[Mod:Fun(Env, Kind, Name, Args, Guards, Expr) || { Mod, Fun } <- Callbacks]
|
||||
end.
|
||||
|
||||
%% Retrieve @file
|
||||
|
||||
retrieve_file(Module, CO) ->
|
||||
case elixir_compiler:get_opt(internal, CO) of
|
||||
true -> [];
|
||||
_ ->
|
||||
case 'Elixir.Module':read_attribute(Module, file) of
|
||||
case 'Elixir.Module':get_attribute(Module, file) of
|
||||
nil -> [];
|
||||
Else ->
|
||||
'Elixir.Module':delete_attribute(Module, file),
|
||||
@@ -135,10 +128,20 @@ retrieve_file(Module, CO) ->
|
||||
end
|
||||
end.
|
||||
|
||||
%% Compile super
|
||||
|
||||
compile_super(Module, #elixir_scope{function=Function, super=true}) ->
|
||||
elixir_def_overridable:store(Module, Function, true);
|
||||
|
||||
compile_super(_Module, _S) -> ok.
|
||||
|
||||
%% Translate the given call and expression given
|
||||
%% and then store it in memory.
|
||||
|
||||
translate_definition(Kind, Line, Name, Args, Guards, Expr, S) ->
|
||||
translate_definition(Kind, Line, Name, RawArgs, RawGuards, RawExpr, S) ->
|
||||
Args = elixir_quote:linify(Line, RawArgs),
|
||||
Guards = elixir_quote:linify(Line, RawGuards),
|
||||
Expr = elixir_quote:linify(Line, RawExpr),
|
||||
Arity = length(Args),
|
||||
IsMacro = is_macro(Kind),
|
||||
|
||||
@@ -291,12 +294,6 @@ check_valid_defaults(Line, File, Name, Arity, _) ->
|
||||
format_error({clauses_with_docs,{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]);
|
||||
|
||||
format_error({existing_doc,{Name,Arity}}) ->
|
||||
io_lib:format("@doc's for function ~s/~B have been given more than once, the first version is being kept", [Name, Arity]);
|
||||
|
||||
format_error({changed_clause,{{Name,Arity},{ElseName,ElseArity}}}) ->
|
||||
io_lib:format("function ~s/~B does not match previous clause ~s/~B", [Name, Arity, ElseName, ElseArity]);
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@ unpack(Kind, Name, Args, S) ->
|
||||
%% clauses and a list of clauses for the default calls.
|
||||
unpack_each(Kind, Name, [{'//', Line, [Expr, _]}|T] = List, Acc, Clauses, S) ->
|
||||
Base = build_match(Acc, Line, []),
|
||||
{ Args, Invoke } = extract_defaults(List, [], []),
|
||||
{ Args, Invoke } = extract_defaults(List, Line, length(Base), [], []),
|
||||
|
||||
SM = S#elixir_scope{counter=length(Base)},
|
||||
{ DefArgs, SA } = elixir_clauses:assigns(fun elixir_translator:translate/2, Base ++ Args, SM),
|
||||
@@ -35,13 +35,14 @@ unpack_each(_Kind, _Name, [], Acc, Clauses, _S) ->
|
||||
|
||||
% Extract default values from args following the current default clause.
|
||||
|
||||
extract_defaults([{'//', _, [_Expr, Default]}|T], NewArgs, NewInvoke) ->
|
||||
extract_defaults(T, NewArgs, [Default|NewInvoke]);
|
||||
extract_defaults([{'//', _, [_Expr, Default]}|T], Line, Counter, NewArgs, NewInvoke) ->
|
||||
extract_defaults(T, Line, Counter, NewArgs, [Default|NewInvoke]);
|
||||
|
||||
extract_defaults([H|T], NewArgs, NewInvoke) ->
|
||||
extract_defaults(T, [H|NewArgs], [H|NewInvoke]);
|
||||
extract_defaults([_|T], Line, Counter, NewArgs, NewInvoke) ->
|
||||
H = { ?ELIXIR_ATOM_CONCAT(["_@", Counter]), Line, nil },
|
||||
extract_defaults(T, Line, Counter + 1, [H|NewArgs], [H|NewInvoke]);
|
||||
|
||||
extract_defaults([], NewArgs, NewInvoke) ->
|
||||
extract_defaults([], _Line, _Counter, NewArgs, NewInvoke) ->
|
||||
{ lists:reverse(NewArgs), lists:reverse(NewInvoke) }.
|
||||
|
||||
% Build matches for all the previous argument until the current default clause.
|
||||
|
||||
@@ -54,7 +54,7 @@ name(Module, Function) ->
|
||||
|
||||
name(_Module, { Name, _ } = Function, Overridable) ->
|
||||
{ Count, _ } = orddict:fetch(Function, Overridable),
|
||||
?ELIXIR_ATOM_CONCAT(["OVERRIDABLE-", Count, "-", Name]).
|
||||
?ELIXIR_ATOM_CONCAT([Name, " (overridable ", Count, ")"]).
|
||||
|
||||
%% Store
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
dispatch_require/6, dispatch_import/5,
|
||||
require_function/5, import_function/4,
|
||||
expand_import/8, expand_require/8,
|
||||
format_error/1]).
|
||||
format_error/1, in_erlang_functions/0, in_erlang_macros/0]).
|
||||
-include("elixir.hrl").
|
||||
-compile({parse_transform, elixir_transform}).
|
||||
|
||||
@@ -58,7 +58,7 @@ dispatch_import(Line, Name, Args, S, Callback) ->
|
||||
Callback();
|
||||
{ error, internal } ->
|
||||
elixir_import:record(import, Tuple, ?BUILTIN, Module),
|
||||
elixir_macros:translate_macro({ Name, Line, Args }, S);
|
||||
elixir_macros:translate({ Name, Line, Args }, S);
|
||||
{ ok, Receiver, Tree } ->
|
||||
translate_expansion(Line, Tree, Receiver, Name, Arity, S)
|
||||
end;
|
||||
@@ -85,7 +85,7 @@ dispatch_require(Line, Receiver, Name, Args, S, Callback) ->
|
||||
{ error, noexpansion } ->
|
||||
Callback();
|
||||
{ error, internal } ->
|
||||
elixir_macros:translate_macro({ Name, Line, Args }, S);
|
||||
elixir_macros:translate({ Name, Line, Args }, S);
|
||||
{ ok, Tree } ->
|
||||
translate_expansion(Line, Tree, Receiver, Name, Arity, S)
|
||||
end
|
||||
@@ -105,9 +105,9 @@ expand_import(Line, { Name, Arity } = Tuple, Args, Module, Function, Requires, M
|
||||
{ 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 ->
|
||||
case is_element(Tuple, in_erlang_macros()) of
|
||||
true -> { error, internal };
|
||||
false ->
|
||||
elixir_import:record(import, Tuple, ?BUILTIN, Module),
|
||||
{ ok, ?BUILTIN, expand_macro_named(Line, ?BUILTIN, Name, Arity, Args, Module, Requires, SEnv) }
|
||||
end;
|
||||
@@ -276,6 +276,7 @@ in_erlang_functions() ->
|
||||
{ integer_to_list, 2 },
|
||||
{ iolist_size, 1 },
|
||||
{ iolist_to_binary, 1 },
|
||||
{ is_alive, 0 },
|
||||
{ is_atom, 1 },
|
||||
{ is_binary, 1 },
|
||||
{ is_bitstring, 1 },
|
||||
|
||||
@@ -89,8 +89,9 @@ 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)]),
|
||||
Kind = protocol_or_behaviour(Module),
|
||||
Raw = "undefined ~s function ~s/~B (for ~s ~s)",
|
||||
Message = io_lib:format(Raw, [Kind, Fun, Arity, Kind, inspect(Module)]),
|
||||
io:format(file_format(Line, File, Message));
|
||||
|
||||
handle_file_warning(_, File, {Line,erl_lint,{undefined_behaviour,Module}}) ->
|
||||
@@ -144,7 +145,8 @@ assert_function_scope(_Line, _Kind, #elixir_scope{function=Function}) -> Functio
|
||||
|
||||
raise(Line, File, Kind, Message) ->
|
||||
Stacktrace = erlang:get_stacktrace(),
|
||||
erlang:raise(error, { Kind, '__exception__', Message, iolist_to_binary(File), Line }, Stacktrace).
|
||||
Exception = Kind:new([{description, Message}, {file, iolist_to_binary(File)}, {line, Line}]),
|
||||
erlang:raise(error, Exception, Stacktrace).
|
||||
|
||||
file_format(Line, File, Message) ->
|
||||
io_lib:format("~ts:~w: ~ts~n", [File, Line, Message]).
|
||||
@@ -153,4 +155,21 @@ format_error([], Desc) ->
|
||||
io_lib:format("~p", [Desc]);
|
||||
|
||||
format_error(Module, Desc) ->
|
||||
Module:format_error(Desc).
|
||||
Module:format_error(Desc).
|
||||
|
||||
protocol_or_behaviour(Module) ->
|
||||
case is_protocol(Module) of
|
||||
true -> protocol;
|
||||
false -> behaviour
|
||||
end.
|
||||
|
||||
is_protocol(Module) ->
|
||||
case code:ensure_loaded(Module) of
|
||||
{ module, _ } ->
|
||||
case erlang:function_exported(Module, '__protocol__', 1) of
|
||||
true -> Module:'__protocol__'(name) == Module;
|
||||
false -> false
|
||||
end;
|
||||
{ error, _ } ->
|
||||
false
|
||||
end.
|
||||
@@ -35,22 +35,24 @@ recorded_locals(Module) ->
|
||||
%% based on the given options and selector.
|
||||
|
||||
import(Line, Ref, Opts, Selector, S) ->
|
||||
SF = case (Selector == all) or (Selector == functions) of
|
||||
IncludeAll = (Selector == all) or (Selector == default),
|
||||
|
||||
SF = case IncludeAll or (Selector == functions) of
|
||||
false -> S;
|
||||
true ->
|
||||
FunctionsFun = fun() -> get_functions(Ref) end,
|
||||
FunctionsFun = fun() -> remove_underscored(Selector, get_functions(Ref)) end,
|
||||
Functions = calculate(Line, Ref, Opts,
|
||||
S#elixir_scope.functions, FunctionsFun, S),
|
||||
S#elixir_scope{functions=Functions}
|
||||
end,
|
||||
|
||||
SM = case (Selector == all) or (Selector == macros) of
|
||||
SM = case IncludeAll or (Selector == macros) of
|
||||
false -> SF;
|
||||
true ->
|
||||
MacrosFun = fun() ->
|
||||
case Selector of
|
||||
all -> get_optional_macros(Ref);
|
||||
_ -> get_macros(Line, Ref, SF)
|
||||
case IncludeAll of
|
||||
true -> remove_underscored(Selector, get_optional_macros(Ref));
|
||||
false -> get_macros(Line, Ref, SF)
|
||||
end
|
||||
end,
|
||||
Macros = calculate(Line, Ref, Opts,
|
||||
@@ -68,27 +70,31 @@ calculate(Line, Key, Opts, Old, AvailableFun, S) ->
|
||||
File = S#elixir_scope.file,
|
||||
All = keydelete(Key, Old),
|
||||
|
||||
New = case orddict:find(only, Opts) of
|
||||
{ ok, Only } ->
|
||||
New = case keyfind(only, Opts) of
|
||||
{ only, RawOnly } ->
|
||||
Only = expand_fun_arity(Line, only, RawOnly, S),
|
||||
case Only -- get_exports(Key) of
|
||||
[{Name,Arity}|_] ->
|
||||
Tuple = { invalid_import, { Key, Name, Arity } },
|
||||
elixir_errors:form_error(Line, File, ?MODULE, Tuple);
|
||||
_ -> intersection(Only, AvailableFun())
|
||||
end;
|
||||
error ->
|
||||
case orddict:find(except, Opts) of
|
||||
{ ok, Except } ->
|
||||
false ->
|
||||
case keyfind(except, Opts) of
|
||||
false -> AvailableFun() ;
|
||||
{ except, [] } -> AvailableFun();
|
||||
{ except, RawExcept } ->
|
||||
Except = expand_fun_arity(Line, except, RawExcept, S),
|
||||
case keyfind(Key, Old) of
|
||||
false -> AvailableFun() -- Except;
|
||||
{Key,ToRemove} -> ToRemove -- Except
|
||||
end;
|
||||
error -> AvailableFun()
|
||||
end
|
||||
end
|
||||
end,
|
||||
|
||||
%% Normalize the data before storing it
|
||||
Final = ordsets:from_list(New -- internal_funs()),
|
||||
Set = ordsets:from_list(New),
|
||||
Final = remove_internals(Set),
|
||||
|
||||
case Final of
|
||||
[] -> All;
|
||||
@@ -99,6 +105,29 @@ calculate(Line, Key, Opts, Old, AvailableFun, S) ->
|
||||
[{ Key, Final }|All]
|
||||
end.
|
||||
|
||||
%% Ensure we are expanding macros and stuff
|
||||
|
||||
expand_fun_arity(Line, Kind, Value, S) ->
|
||||
{ TValue, _S } = elixir_translator:translate_each(Value, S),
|
||||
cons_to_keywords(Line, Kind, TValue, S).
|
||||
|
||||
cons_to_keywords(Line, Kind, { cons, _, Left, { nil, _ } }, S) ->
|
||||
[tuple_to_fun_arity(Line, Kind, Left, S)];
|
||||
|
||||
cons_to_keywords(Line, Kind, { cons, _, Left, Right }, S) ->
|
||||
[tuple_to_fun_arity(Line, Kind, Left, S)|cons_to_keywords(Line, Kind, Right, S)];
|
||||
|
||||
cons_to_keywords(Line, Kind, _, S) ->
|
||||
elixir_errors:syntax_error(Line, S#elixir_scope.file,
|
||||
"invalid value for :~s, expected a list with functions and arities", [Kind]).
|
||||
|
||||
tuple_to_fun_arity(_Line, _Kind, { tuple, _, [{ atom, _, Atom }, { integer, _, Integer }] }, _S) ->
|
||||
{ Atom, Integer };
|
||||
|
||||
tuple_to_fun_arity(Line, Kind, _, S) ->
|
||||
elixir_errors:syntax_error(Line, S#elixir_scope.file,
|
||||
"invalid value for :~s, expected a list with functions and arities", [Kind]).
|
||||
|
||||
%% Retrieve functions and macros from modules
|
||||
|
||||
get_exports(Module) ->
|
||||
@@ -236,13 +265,22 @@ intersection([], _All) -> [].
|
||||
|
||||
%% Internal funs that are never imported etc.
|
||||
|
||||
internal_funs() ->
|
||||
[
|
||||
{ module_info, 0 },
|
||||
{ module_info, 1 },
|
||||
{ '__info__', 1 },
|
||||
{ '__using__', 1 }
|
||||
].
|
||||
remove_underscored(default, List) -> remove_underscored(List);
|
||||
remove_underscored(_, List) -> List.
|
||||
|
||||
remove_underscored([{ Name, _ } = H|T]) when Name < a ->
|
||||
case atom_to_list(Name) of
|
||||
[$_, $_, _, $_, $_] -> [H|remove_underscored(T)];
|
||||
"_" ++ _ -> remove_underscored(T);
|
||||
_ -> [H|remove_underscored(T)]
|
||||
end;
|
||||
|
||||
remove_underscored(T) ->
|
||||
T.
|
||||
|
||||
remove_internals(Set) ->
|
||||
ordsets:del_element({ module_info, 1 },
|
||||
ordsets:del_element({ module_info, 0 }, Set)).
|
||||
|
||||
%% Macros implemented in Erlang that are not overridable.
|
||||
|
||||
@@ -280,4 +318,4 @@ non_overridable_macros() ->
|
||||
{'super?',0},
|
||||
{'bc','*'},
|
||||
{'lc','*'}
|
||||
].
|
||||
].
|
||||
|
||||
@@ -147,10 +147,10 @@ wrap_interpol(_Line, Form) when is_binary(Form) ->
|
||||
Form;
|
||||
|
||||
wrap_interpol(Line, Form) ->
|
||||
{ '|', Line, [{ { '.', Line, ['Elixir.Binary.Chars', to_binary] }, Line, [Form]}, binary]}.
|
||||
{ '::', Line, [{ { '.', Line, ['Elixir.Binary.Chars', to_binary] }, Line, [Form]}, { binary, Line, nil }]}.
|
||||
|
||||
forms(String, StartLine, File) ->
|
||||
case elixir_translator:raw_forms(String, StartLine, File) of
|
||||
case elixir_translator:forms(String, StartLine, File, []) of
|
||||
{ ok, [] } -> nil;
|
||||
{ ok, [Forms] } when not is_list(Forms) -> Forms;
|
||||
{ ok, Forms } -> { '__block__', StartLine, Forms };
|
||||
|
||||
@@ -0,0 +1,245 @@
|
||||
-module(elixir_literal).
|
||||
-export([translate/2]).
|
||||
-import(elixir_translator, [translate_each/2, translate_args/2]).
|
||||
-import(elixir_scope, [umergev/2, umergec/2]).
|
||||
-include("elixir.hrl").
|
||||
-compile({parse_transform, elixir_transform}).
|
||||
|
||||
translate({ '<<>>', Line, Args } = Original, S) when is_list(Args) ->
|
||||
case elixir_partials:handle(Original, S, allow_tail) of
|
||||
error ->
|
||||
case S#elixir_scope.context of
|
||||
assign ->
|
||||
build_bitstr(fun elixir_translator:translate_each/2, Args, Line, S);
|
||||
_ ->
|
||||
{ TArgs, { SC, SV } } = build_bitstr(fun elixir_translator:translate_arg/2, Args, Line, { S, S }),
|
||||
{ TArgs, umergec(SV, SC) }
|
||||
end;
|
||||
Else -> Else
|
||||
end;
|
||||
|
||||
translate({ '{}', Line, Args } = Original, S) when is_list(Args) ->
|
||||
case elixir_partials:handle(Original, S) of
|
||||
error ->
|
||||
{ TArgs, SE } = translate_args(Args, S),
|
||||
{ {tuple, Line, TArgs}, SE };
|
||||
Else -> Else
|
||||
end;
|
||||
|
||||
translate({ '[]', _Line, [] }, S) ->
|
||||
{ { nil, 0 }, S };
|
||||
|
||||
translate({ '[]', _Line, Args } = Original, S) when is_list(Args) ->
|
||||
case elixir_partials:handle(Original, S, allow_tail) of
|
||||
error ->
|
||||
[RTail|RArgs] = lists:reverse(Args),
|
||||
|
||||
case RTail of
|
||||
{'|',_,[Left,Right]} ->
|
||||
Exprs = [Left|RArgs],
|
||||
{ Tail, ST } = translate_each(Right, S),
|
||||
ListS = umergec(S, ST);
|
||||
_ ->
|
||||
Exprs = [RTail|RArgs],
|
||||
Tail = { nil, 0 },
|
||||
ST = S,
|
||||
ListS = S
|
||||
end,
|
||||
|
||||
{ FExprs, FS } = case S#elixir_scope.context of
|
||||
assign ->
|
||||
elixir_tree_helpers:build_reverse_list(fun elixir_translator:translate_each/2, Exprs, 0, ListS, Tail);
|
||||
_ ->
|
||||
{ TArgs, { SC, SV } } =
|
||||
elixir_tree_helpers:build_reverse_list(fun elixir_translator:translate_arg/2, Exprs, 0, { ListS, ListS }, Tail),
|
||||
{ TArgs, umergec(SV, SC) }
|
||||
end,
|
||||
|
||||
{ FExprs, umergev(ST, FS) };
|
||||
Else -> Else
|
||||
end;
|
||||
|
||||
translate({ Left, Right }, S) ->
|
||||
translate({ '{}', 0, [Left, Right]}, S);
|
||||
|
||||
translate(Args, S) when is_list(Args) ->
|
||||
translate({ '[]', 0, Args }, S);
|
||||
|
||||
translate(Number, S) when is_integer(Number) ->
|
||||
{ { integer, 0, Number }, S };
|
||||
|
||||
translate(Number, S) when is_float(Number) ->
|
||||
{ { float, 0, Number }, S };
|
||||
|
||||
translate(Atom, S) when is_atom(Atom) ->
|
||||
{ { atom, 0, Atom }, S };
|
||||
|
||||
translate(Bitstring, S) when is_bitstring(Bitstring) ->
|
||||
{ elixir_tree_helpers:abstract_syntax(Bitstring), S }.
|
||||
|
||||
%% Helpers
|
||||
|
||||
% Build a bitstring taking into accounts the following types:
|
||||
%
|
||||
% * If a bitstring or a list is given, we just append its items
|
||||
% * If '|' is given, extract the bitstring information
|
||||
% * All the other types are simply translated and handled with Erlang's default
|
||||
%
|
||||
build_bitstr(Fun, Exprs, Line, S) ->
|
||||
{ Final, FinalS } = build_bitstr_each(Fun, Exprs, Line, S, []),
|
||||
{ { bin, Line, lists:reverse(Final) }, FinalS }.
|
||||
|
||||
build_bitstr_each(_Fun, [], _Line, S, Acc) ->
|
||||
{ Acc, S };
|
||||
|
||||
build_bitstr_each(Fun, [H|T], Line, S, Acc) when is_list(H) ->
|
||||
{ NewAcc, NewS } = build_bitstr_each(Fun, H, Line, S, Acc),
|
||||
build_bitstr_each(Fun, T, Line, NewS, NewAcc);
|
||||
|
||||
build_bitstr_each(Fun, [H|T], Line, S, Acc) when is_bitstring(H) ->
|
||||
{ bin, _, Elements } = elixir_tree_helpers:abstract_syntax(H),
|
||||
NewAcc = lists:foldl(fun(Element, FinalAcc) -> [Element|FinalAcc] end, Acc, Elements),
|
||||
build_bitstr_each(Fun, T, Line, S, NewAcc);
|
||||
|
||||
build_bitstr_each(Fun, [{'::',_,[H,V]}|T], Line, S, Acc) ->
|
||||
{ Expr, NS } = Fun(H, S),
|
||||
|
||||
%% Variables defined outside the binary can be accounted
|
||||
%% on subparts, however we can't assign new variables.
|
||||
case NS of
|
||||
{ ES, _ } -> []; %% translate_arg, no assigns
|
||||
_ -> ES = NS#elixir_scope{context=nil} %% translate_each, revert assigns
|
||||
end,
|
||||
|
||||
{ Size, Types } = extract_bit_values(Line, V, ES),
|
||||
build_bitstr_each(Fun, T, Line, NS, [{ bin_element, Line, Expr, Size, Types }|Acc]);
|
||||
|
||||
%% Deprecated syntax
|
||||
build_bitstr_each(Fun, [{'|',_,[H,V]}|T], Line, S, Acc) ->
|
||||
{ Expr, NS } = Fun(H, S),
|
||||
|
||||
%% Just variables defined outside the binary can be accounted on subparts
|
||||
case NS of
|
||||
{ ES, _ } -> [];
|
||||
ES -> []
|
||||
end,
|
||||
|
||||
elixir_errors:deprecation(Line, ES#elixir_scope.file, "Old bitstring syntax with | is deprecated, use the new one with :: instead"),
|
||||
|
||||
%% Assigns can be made in subparts
|
||||
{ Int, Types } = extract_bin_values(Line, V, default, [], ES#elixir_scope{context=nil}),
|
||||
|
||||
Final = case Types of
|
||||
[] -> default;
|
||||
_ -> lists:reverse(Types)
|
||||
end,
|
||||
|
||||
build_bitstr_each(Fun, T, Line, NS, [{ bin_element, Line, Expr, Int, Final }|Acc]);
|
||||
|
||||
build_bitstr_each(Fun, [H|T], Line, S, Acc) ->
|
||||
{ Expr, NS } = Fun(H, S),
|
||||
build_bitstr_each(Fun, T, Line, NS, [{ bin_element, Line, Expr, default, default }|Acc]).
|
||||
|
||||
%% Extra bitstring specifiers
|
||||
|
||||
extract_bit_values(Line, V, S) when is_list(V) ->
|
||||
extract_bit_values(Line, V, default, [], S);
|
||||
|
||||
extract_bit_values(Line, V, S) ->
|
||||
extract_bit_values(Line, [V], S).
|
||||
|
||||
extract_bit_values(Line, [{ Value, _CallLine, Atom }|T] = All, Size, Types, S)
|
||||
when is_atom(Value) andalso (is_atom(Atom) orelse Atom == []) ->
|
||||
case extract_bit_type(Value, Types) of
|
||||
{ error, unknown } ->
|
||||
handle_unknown_specifier(Line, All, Size, Types, S);
|
||||
NewTypes when is_list(NewTypes) ->
|
||||
extract_bit_values(Line, T, Size, NewTypes, S)
|
||||
end;
|
||||
|
||||
extract_bit_values(Line, [{ Value, CallLine, [_] = Args }|T] = All, Size, Types, S) when is_atom(Value) ->
|
||||
{ TArgs, _ } = elixir_translator:translate_args(Args, S),
|
||||
case extract_bit_type_or_size(Value, TArgs, Size, Types) of
|
||||
{ error, unknown } ->
|
||||
handle_unknown_specifier(Line, All, Size, Types, S);
|
||||
{ error, Msg } ->
|
||||
elixir_errors:syntax_error(CallLine, S#elixir_scope.file, Msg);
|
||||
{ NewSize, NewTypes } ->
|
||||
extract_bit_values(Line, T, NewSize, NewTypes, S)
|
||||
end;
|
||||
|
||||
extract_bit_values(Line, [{ '|', _, [Left, Right] }], Size, Types, S) ->
|
||||
Expanded = 'Elixir.Macro':expand(Right, elixir_scope:to_ex_env({ Line, S })),
|
||||
extract_bit_values(Line, [Left|join_expansion(Expanded,[])], Size, Types, S);
|
||||
|
||||
extract_bit_values(Line, [_|_] = All, Size, Types, S) ->
|
||||
handle_unknown_specifier(Line, All, Size, Types, S);
|
||||
|
||||
extract_bit_values(_Line, [], Size, [], _S) ->
|
||||
{ Size, default };
|
||||
|
||||
extract_bit_values(_Line, [], Size, Types, _S) ->
|
||||
{ Size, lists:reverse(Types) }.
|
||||
|
||||
extract_bit_type(Value, Types) when
|
||||
Value == binary; Value == integer; Value == float; Value == bitstring;
|
||||
Value == bytes; Value == bits;
|
||||
Value == utf8; Value == utf16; Value == utf32;
|
||||
Value == signed; Value == unsigned;
|
||||
Value == big; Value == little; Value == native ->
|
||||
[Value|Types];
|
||||
|
||||
extract_bit_type(_Value, _Types) ->
|
||||
{ error, unknown }.
|
||||
|
||||
extract_bit_type_or_size(size, [{ Kind, _, _ } = Arg], default, Types) when Kind == var; Kind == integer ->
|
||||
{ Arg, Types };
|
||||
|
||||
extract_bit_type_or_size(size, _Args, default, _Types) ->
|
||||
{ error, "size in bitstring expects an integer or a variable as argument" };
|
||||
|
||||
extract_bit_type_or_size(size, _Args, _Other, _Types) ->
|
||||
{ error, "duplicated size definition for bitstring" };
|
||||
|
||||
extract_bit_type_or_size(unit, [{ integer, _, Arg }], Size, Types) ->
|
||||
{ Size, [{ unit, Arg }|Types] };
|
||||
|
||||
extract_bit_type_or_size(unit, _Args, _Other, _Types) ->
|
||||
{ error, "unit in bitstring expects an integer as argument" };
|
||||
|
||||
extract_bit_type_or_size(_Value, _Args, _Other, _Types) ->
|
||||
{ error, unknown }.
|
||||
|
||||
handle_unknown_specifier(Line, [H|T], Size, Types, S) ->
|
||||
case 'Elixir.Macro':expand(H, elixir_scope:to_ex_env({ Line, S })) of
|
||||
H ->
|
||||
elixir_errors:syntax_error(Line, S#elixir_scope.file, "unknown bitstring specifier ~s", ['Elixir.Macro':to_binary(H)]);
|
||||
E ->
|
||||
extract_bit_values(Line, join_expansion(E,T), Size, Types, S)
|
||||
end.
|
||||
|
||||
join_expansion({ '__block__', _, [Expanded] }, Tail) -> join_expansion(Expanded, Tail);
|
||||
join_expansion(Expanded, Tail) when is_list(Expanded) -> Expanded ++ Tail;
|
||||
join_expansion(Expanded, Tail) -> [Expanded|Tail].
|
||||
|
||||
%% Deprecated specifiers
|
||||
|
||||
extract_bin_values(Line, { '-', _Line, [Left, Right] }, Int, Types, S) ->
|
||||
{ LInt, LTypes } = extract_bin_values(Line, Left, Int, Types, S),
|
||||
extract_bin_values(Line, Right, LInt, LTypes, S);
|
||||
|
||||
extract_bin_values(Line, Value, default, Types, _S) when is_integer(Value) ->
|
||||
{ { integer, Line, Value }, Types };
|
||||
|
||||
extract_bin_values(_Line, { Value, _, Atom } = Expr, default, Types, S) when is_atom(Value), is_atom(Atom) ->
|
||||
Translated = element(1, elixir_translator:translate_each(Expr, S)),
|
||||
{ Translated, Types };
|
||||
|
||||
extract_bin_values(Line, Value, _Int, _Types, S) when is_integer(Value) ->
|
||||
elixir_errors:syntax_error(Line, S#elixir_scope.file, "duplicated size specifier ~p in <<>>", [Value]);
|
||||
|
||||
extract_bin_values(_Line, Value, Int, Types, _S) when is_atom(Value); is_tuple(Value), tuple_size(Value) == 2 ->
|
||||
{ Int, [Value|Types] };
|
||||
|
||||
extract_bin_values(Line, _Value, _Int, _Types, S) ->
|
||||
elixir_errors:syntax_error(Line, S#elixir_scope.file, "invalid specifier for <<>>").
|
||||
@@ -1,8 +1,8 @@
|
||||
%% 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]).
|
||||
-export([translate/2]).
|
||||
-import(elixir_translator, [translate_each/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]).
|
||||
@@ -13,18 +13,18 @@
|
||||
|
||||
%% Operators
|
||||
|
||||
translate_macro({ '+', _Line, [Expr] }, S) when is_number(Expr) ->
|
||||
translate({ '+', _Line, [Expr] }, S) when is_number(Expr) ->
|
||||
translate_each(Expr, S);
|
||||
|
||||
translate_macro({ '-', _Line, [Expr] }, S) when is_number(Expr) ->
|
||||
translate({ '-', _Line, [Expr] }, S) when is_number(Expr) ->
|
||||
translate_each(-1 * Expr, S);
|
||||
|
||||
translate_macro({ Op, Line, Exprs }, S) when is_list(Exprs),
|
||||
translate({ 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),
|
||||
translate({ 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
|
||||
@@ -32,33 +32,33 @@ translate_macro({ Op, Line, Exprs }, S) when is_list(Exprs),
|
||||
Op == '!==' ->
|
||||
translate_each({ '__op__', Line, [Op|Exprs] }, S);
|
||||
|
||||
translate_macro({ '!', Line, [{ '!', _, [Expr] }] }, S) ->
|
||||
translate({ '!', 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) ->
|
||||
translate({ '!', 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) ->
|
||||
translate({ 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) ->
|
||||
translate({ 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) ->
|
||||
translate({ 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) ->
|
||||
translate({ 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) ->
|
||||
translate({ function, Line, [_, _] = Args }, S) ->
|
||||
assert_no_assign_or_guard_scope(Line, 'function', S),
|
||||
|
||||
case translate_args(Args, S) of
|
||||
@@ -71,14 +71,14 @@ translate_macro({ function, Line, [_, _] = Args }, S) ->
|
||||
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) ->
|
||||
translate({ 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 ->
|
||||
translate({'@', 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 ->
|
||||
@@ -86,7 +86,7 @@ translate_macro({'@', Line, [{ Name, _, Args }]}, S) when Name == typep; Name ==
|
||||
translate_each(Call, S)
|
||||
end;
|
||||
|
||||
translate_macro({'@', Line, [{ Name, _, Args }]}, S) ->
|
||||
translate({'@', Line, [{ Name, _, Args }]}, S) ->
|
||||
assert_module_scope(Line, '@', S),
|
||||
|
||||
case is_reserved_data(Name) andalso elixir_compiler:get_opt(internal) of
|
||||
@@ -98,7 +98,7 @@ translate_macro({'@', Line, [{ Name, _, Args }]}, S) ->
|
||||
case S#elixir_scope.function of
|
||||
nil ->
|
||||
translate_each({
|
||||
{ '.', Line, ['Elixir.Module', add_attribute] },
|
||||
{ '.', Line, ['Elixir.Module', put_attribute] },
|
||||
Line,
|
||||
[ { '__MODULE__', Line, false }, Name, Arg ]
|
||||
}, S);
|
||||
@@ -110,12 +110,12 @@ translate_macro({'@', Line, [{ Name, _, Args }]}, S) ->
|
||||
case S#elixir_scope.function of
|
||||
nil ->
|
||||
translate_each({
|
||||
{ '.', Line, ['Elixir.Module', read_attribute] },
|
||||
{ '.', Line, ['Elixir.Module', get_attribute] },
|
||||
Line,
|
||||
[ { '__MODULE__', Line, false }, Name ]
|
||||
}, S);
|
||||
_ ->
|
||||
Contents = 'Elixir.Module':read_attribute(S#elixir_scope.module, Name),
|
||||
Contents = 'Elixir.Module':get_attribute(S#elixir_scope.module, Name),
|
||||
{ elixir_tree_helpers:abstract_syntax(Contents), S }
|
||||
end;
|
||||
_ ->
|
||||
@@ -125,7 +125,7 @@ translate_macro({'@', Line, [{ Name, _, Args }]}, S) ->
|
||||
|
||||
%% Case
|
||||
|
||||
translate_macro({'case', Line, [Expr, KV]}, S) ->
|
||||
translate({'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),
|
||||
@@ -140,49 +140,49 @@ translate_macro({'case', Line, [Expr, KV]}, S) ->
|
||||
|
||||
%% Try
|
||||
|
||||
translate_macro({'try', Line, [Clauses]}, RawS) ->
|
||||
translate({'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),
|
||||
{ TDo, SB } = elixir_translator: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 }
|
||||
{ TAfter, SA } = case lists:keyfind('after', 1, Clauses) of
|
||||
{ 'after', After } -> elixir_translator:translate([After], umergec(S, SC));
|
||||
false -> { [], SC }
|
||||
end,
|
||||
|
||||
{ { 'try', Line, unpack(TDo), [], TCatch, unpack(TAfter) }, umergec(RawS, SA) };
|
||||
|
||||
%% Receive
|
||||
|
||||
translate_macro({'receive', Line, [KV] }, S) ->
|
||||
translate({'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 ->
|
||||
case lists:keyfind('after', 1, KV) of
|
||||
false ->
|
||||
{ TClauses, SC } = elixir_clauses:match(Line, Do, S),
|
||||
{ { 'receive', Line, TClauses }, SC };
|
||||
_ ->
|
||||
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 }
|
||||
{ { 'receive', Line, FClauses, FExpr, FAfter }, SC }
|
||||
end;
|
||||
|
||||
%% Definitions
|
||||
|
||||
translate_macro({defmodule, Line, [Ref, KV]}, S) ->
|
||||
translate({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")
|
||||
Block = case lists:keyfind(do, 1, KV) of
|
||||
{ do, DoValue } -> DoValue;
|
||||
false -> syntax_error(Line, S#elixir_scope.file, "expected do: argument in defmodule")
|
||||
end,
|
||||
|
||||
{ FRef, FS } = case TRef of
|
||||
@@ -207,10 +207,10 @@ translate_macro({defmodule, Line, [Ref, KV]}, S) ->
|
||||
|
||||
{ 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({Kind, Line, [Call]}, S) when ?FUNS() ->
|
||||
translate({Kind, Line, [Call, nil]}, S);
|
||||
|
||||
translate_macro({Kind, Line, [Call, Expr]}, S) when ?FUNS() ->
|
||||
translate({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),
|
||||
@@ -221,35 +221,35 @@ translate_macro({Kind, Line, [Call, Expr]}, S) when ?FUNS() ->
|
||||
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() ->
|
||||
translate({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 };
|
||||
{ TGuards, GS } = translate_each(Guards, AS),
|
||||
{ TExpr, ES } = translate_each(Expr, GS),
|
||||
{ elixir_def:wrap_definition(Kind, Line, TName, TArgs, TGuards, TExpr, ES), ES };
|
||||
|
||||
%% 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) ->
|
||||
translate({ 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) ->
|
||||
translate({ 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({ 'var!', _, [{Name, Line, Atom}] }, S) when is_atom(Name), is_atom(Atom) ->
|
||||
elixir_scope:translate_var(Line, Name, nil, S);
|
||||
|
||||
translate_macro({ 'var!', Line, [_] }, S) ->
|
||||
translate({ 'var!', Line, [_] }, S) ->
|
||||
syntax_error(Line, S#elixir_scope.file, "invalid args for var!").
|
||||
|
||||
%% HELPERS
|
||||
%% Helpers
|
||||
|
||||
translate_in(Line, Left, Right, S) ->
|
||||
{ TLeft, SL } = case Left of
|
||||
@@ -275,9 +275,17 @@ translate_in(Line, Left, Right, S) ->
|
||||
{ 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 }
|
||||
{ op, Line, 'orelse',
|
||||
{ op, Line, 'andalso',
|
||||
{ op, Line, '=<', Start, End},
|
||||
{ op, Line, 'andalso',
|
||||
{ op, Line, '>=', Var, Start },
|
||||
{ op, Line, '=<', Var, End } } },
|
||||
{ op, Line, 'andalso',
|
||||
{ op, Line, '<', End, Start},
|
||||
{ op, Line, 'andalso',
|
||||
{ op, Line, '=<', Var, Start },
|
||||
{ op, Line, '>=', Var, End } } }
|
||||
};
|
||||
_ ->
|
||||
syntax_error(Line, S#elixir_scope.file, "invalid args for operator in")
|
||||
|
||||
@@ -1,9 +1,22 @@
|
||||
-module(elixir_module).
|
||||
-export([translate/4, compile/5, data_table/1,
|
||||
format_error/1, scope_for_eval/2, binding_for_eval/2]).
|
||||
-export([translate/4, compile/5, data_table/1, eval_quoted/4, format_error/1]).
|
||||
-include("elixir.hrl").
|
||||
-compile({parse_transform, elixir_transform}).
|
||||
|
||||
eval_quoted(Module, Quoted, RawBinding, Opts) ->
|
||||
Binding = binding_for_eval(Module, RawBinding),
|
||||
Scope = scope_for_eval(Module, Opts),
|
||||
|
||||
elixir_def:reset_last(Module),
|
||||
|
||||
case lists:keyfind(line, 1, Opts) of
|
||||
{ line, Line } -> Line;
|
||||
false -> Line = 1
|
||||
end,
|
||||
|
||||
{ Value, FinalBinding, _Scope } = elixir:eval_quoted([Quoted], Binding, Line, Scope),
|
||||
{ Value, FinalBinding }.
|
||||
|
||||
scope_for_eval(Module, #elixir_scope{} = S) ->
|
||||
S#elixir_scope{module=Module};
|
||||
|
||||
@@ -12,10 +25,11 @@ scope_for_eval(Module, Opts) ->
|
||||
|
||||
binding_for_eval(Module, Binding) -> [{'_@MODULE',Module}|Binding].
|
||||
|
||||
|
||||
%% TABLE METHODS
|
||||
|
||||
data_table(Module) ->
|
||||
?ELIXIR_ATOM_CONCAT([d, Module]).
|
||||
Module.
|
||||
|
||||
docs_table(Module) ->
|
||||
?ELIXIR_ATOM_CONCAT([o, Module]).
|
||||
@@ -23,7 +37,7 @@ docs_table(Module) ->
|
||||
spec_table(Module) ->
|
||||
?ELIXIR_ATOM_CONCAT([s, Module]).
|
||||
|
||||
%% TRANSFORMATION METHODS
|
||||
%% TRANSFORMATION FUNCTIONS
|
||||
|
||||
%% Transformation of args and scope into a compiled erlang call.
|
||||
%% The abstract form for extra arguments may be given and they
|
||||
@@ -33,7 +47,7 @@ translate(Line, Ref, Block, S) ->
|
||||
MetaBlock = elixir_tree_helpers:abstract_syntax(Block),
|
||||
MetaS = elixir_scope:serialize(S),
|
||||
|
||||
Vars = dict:fold(fun(Key, Value, { Acc, Counter }) ->
|
||||
Vars = orddict:fold(fun(Key, Value, { Acc, Counter }) ->
|
||||
{ { cons, Line, { tuple, Line, [
|
||||
{ atom, Line, Key },
|
||||
{ atom, Line, ?ELIXIR_ATOM_CONCAT(["_@", Counter]) },
|
||||
@@ -46,9 +60,7 @@ translate(Line, Ref, Block, S) ->
|
||||
|
||||
%% The compilation hook.
|
||||
|
||||
compile(Line, Module, Block, Vars, RawS) when is_atom(Module) ->
|
||||
Dict = [{ X, Y } || { X, Y, _ } <- Vars],
|
||||
S = elixir_scope:deserialize(RawS, Dict),
|
||||
compile(Line, Module, Block, Vars, #elixir_scope{} = S) when is_atom(Module) ->
|
||||
C = elixir_compiler:get_opts(),
|
||||
File = S#elixir_scope.file,
|
||||
|
||||
@@ -61,7 +73,7 @@ compile(Line, Module, Block, Vars, RawS) when is_atom(Module) ->
|
||||
|
||||
{ All, Forms0 } = functions_form(Line, File, Module, Export, Private, Def, Defmacro, Defmacrop, Functions, C),
|
||||
Forms1 = attributes_form(Line, File, Module, Forms0),
|
||||
Forms2 = specs_form(Line, Module, Def, Defmacro, Defmacrop, Forms1),
|
||||
Forms2 = specs_form(Line, Module, Defmacro, Defmacrop, Forms1),
|
||||
|
||||
elixir_import:ensure_no_local_conflict(Line, File, Module, All),
|
||||
elixir_import:ensure_no_import_conflict(Line, File, Module, All),
|
||||
@@ -71,8 +83,8 @@ compile(Line, Module, Block, Vars, RawS) when is_atom(Module) ->
|
||||
{attribute, Line, module, Module} | Forms2
|
||||
],
|
||||
|
||||
load_form(Line, Final, S),
|
||||
Result
|
||||
Binary = load_form(Line, Final, S),
|
||||
{ module, Module, Binary, Result }
|
||||
after
|
||||
ets:delete(data_table(Module)),
|
||||
ets:delete(docs_table(Module)),
|
||||
@@ -81,9 +93,13 @@ compile(Line, Module, Block, Vars, RawS) when is_atom(Module) ->
|
||||
elixir_import:delete_table(Module)
|
||||
end;
|
||||
|
||||
compile(Line, Other, _Block, _Vars, RawS) ->
|
||||
S = elixir_scope:deserialize(RawS),
|
||||
elixir_errors:form_error(Line, S#elixir_scope.file, ?MODULE, { invalid_module, Other }).
|
||||
compile(Line, Other, _Block, _Vars, #elixir_scope{file=File}) ->
|
||||
elixir_errors:form_error(Line, File, ?MODULE, { invalid_module, Other });
|
||||
|
||||
compile(Line, Module, Block, Vars, RawS) ->
|
||||
Dict = [{ X, Y } || { X, Y, _ } <- Vars],
|
||||
S = elixir_scope:deserialize(RawS, Dict),
|
||||
compile(Line, Module, Block, Vars, S).
|
||||
|
||||
%% Hook that builds both attribute and functions and set up common hooks.
|
||||
|
||||
@@ -98,11 +114,16 @@ build(Line, File, Module) ->
|
||||
|
||||
ets:new(DataTable, [set, named_table, public]),
|
||||
ets:insert(DataTable, { '__overridable', [] }),
|
||||
ets:insert(DataTable, { 'before_compile', [] }),
|
||||
ets:insert(DataTable, { 'after_compile', [] }),
|
||||
ets:insert(DataTable, { before_compile, [] }),
|
||||
ets:insert(DataTable, { after_compile, [] }),
|
||||
|
||||
case elixir_compiler:get_opt(docs) of
|
||||
true -> ets:insert(DataTable, { on_definition, [{ 'Elixir.Module', compile_doc }] });
|
||||
_ -> ets:insert(DataTable, { on_definition, [] })
|
||||
end,
|
||||
|
||||
Attributes = [behavior, behaviour, on_load, spec, type, export_type, opaque, callback, compile],
|
||||
ets:insert(DataTable, { '__acc_attributes', [before_compile,after_compile|Attributes] }),
|
||||
ets:insert(DataTable, { '__acc_attributes', [before_compile,after_compile,on_definition|Attributes] }),
|
||||
ets:insert(DataTable, { '__persisted_attributes', [vsn|Attributes] }),
|
||||
|
||||
%% Keep docs in another table since we don't want to pull out
|
||||
@@ -128,7 +149,7 @@ eval_form(Line, Module, Block, Vars, RawS) ->
|
||||
S = scope_for_eval(Module, RawS),
|
||||
{ Value, NewS } = elixir_compiler:eval_forms([Block], Line, Temp, Vars, S),
|
||||
elixir_def_overridable:store_pending(Module),
|
||||
eval_callbacks(Line, Module, 'before_compile', [Module], NewS),
|
||||
eval_callbacks(Line, Module, before_compile, [Module], NewS),
|
||||
Value.
|
||||
|
||||
%% Return the form with exports and function declarations.
|
||||
@@ -167,9 +188,9 @@ attributes_form(Line, _File, Module, Current) ->
|
||||
|
||||
%% Specs
|
||||
|
||||
specs_form(Line, Module, Def, Defmacro, Defmacrop, Forms) ->
|
||||
specs_form(Line, Module, Defmacro, Defmacrop, Forms) ->
|
||||
{ Specs, Callbacks } = ets:foldl(fun(X, Acc) ->
|
||||
translate_spec(X, Module, Def, Defmacro, Defmacrop, Acc)
|
||||
translate_spec(X, Defmacro, Defmacrop, Acc)
|
||||
end, { [], [] }, spec_table(Module)),
|
||||
|
||||
Temp = specs_attributes(Line, spec, Forms, Specs),
|
||||
@@ -181,7 +202,7 @@ specs_attributes(Line, Type, Forms, Specs) ->
|
||||
[{ attribute, Line, Type, { Tuple, proplists:append_values(Tuple, Specs) } }|Acc]
|
||||
end, Forms, Keys).
|
||||
|
||||
translate_spec({ { spec, Spec }, Rest }, Module, Def, Defmacro, Defmacrop, { Specs, Callbacks } = Acc) ->
|
||||
translate_spec({ { spec, Spec }, Rest }, Defmacro, Defmacrop, { Specs, Callbacks } = Acc) ->
|
||||
case ordsets:is_element(Spec, Defmacrop) of
|
||||
true -> Acc;
|
||||
false ->
|
||||
@@ -191,14 +212,11 @@ translate_spec({ { spec, Spec }, Rest }, Module, Def, Defmacro, Defmacrop, { Spe
|
||||
New = { { ?ELIXIR_MACRO(Name), Arity + 1 }, [spec_for_macro(X) || X <- Rest] },
|
||||
{ [New|Specs], Callbacks };
|
||||
false ->
|
||||
case (Module == 'Elixir.Kernel') andalso (not ordsets:is_element(Spec, Def)) of
|
||||
true -> Acc;
|
||||
false -> { [{ Spec, Rest }|Specs], Callbacks }
|
||||
end
|
||||
{ [{ Spec, Rest }|Specs], Callbacks }
|
||||
end
|
||||
end;
|
||||
|
||||
translate_spec({ { callback, Spec }, Rest }, _, _, _, _, { Specs, Callbacks }) ->
|
||||
translate_spec({ { callback, Spec }, Rest }, _, _, { Specs, Callbacks }) ->
|
||||
{ Specs, [{ Spec, Rest }|Callbacks] }.
|
||||
|
||||
spec_for_macro({ type, Line, 'fun', [{ type, _, product, Args }|T] }) ->
|
||||
@@ -212,7 +230,7 @@ spec_for_macro(Else) -> Else.
|
||||
load_form(Line, Forms, S) ->
|
||||
elixir_compiler:module(Forms, S, fun(Module, Binary) ->
|
||||
EvalS = scope_for_eval(Module, S),
|
||||
eval_callbacks(Line, Module, 'after_compile', [Module, Binary], EvalS),
|
||||
eval_callbacks(Line, Module, after_compile, [Module, Binary], EvalS),
|
||||
|
||||
case get(elixir_compiled) of
|
||||
Current when is_list(Current) ->
|
||||
@@ -221,9 +239,10 @@ load_form(Line, Forms, S) ->
|
||||
undefined -> [];
|
||||
PID -> PID ! { module_available, self(), Module, Binary }
|
||||
end;
|
||||
_ ->
|
||||
[]
|
||||
end
|
||||
_ -> []
|
||||
end,
|
||||
|
||||
Binary
|
||||
end).
|
||||
|
||||
check_module_availability(Line, File, Module, Compiler) ->
|
||||
@@ -249,7 +268,7 @@ add_info_function(Line, File, Module, Export, Functions, Def, Defmacro, C) ->
|
||||
Docs = elixir_compiler:get_opt(docs, C),
|
||||
Contents = { function, Line, '__info__', 1, [
|
||||
functions_clause(Line, Def),
|
||||
macros_clause(Line, Defmacro),
|
||||
macros_clause(Line, Module, Def, Defmacro),
|
||||
docs_clause(Line, Module, Docs),
|
||||
moduledoc_clause(Line, Module, Docs),
|
||||
compile_clause(Line),
|
||||
@@ -259,16 +278,22 @@ add_info_function(Line, File, Module, Export, Functions, Def, Defmacro, C) ->
|
||||
{ [Pair|Export], [Contents|Functions] }
|
||||
end.
|
||||
|
||||
functions_clause(Line, RawDef) ->
|
||||
Def = ordsets:add_element({'__info__',1}, RawDef),
|
||||
{ clause, Line, [{ atom, Line, functions }], [], [elixir_tree_helpers:abstract_syntax(Def)] }.
|
||||
functions_clause(Line, Def) ->
|
||||
All = ordsets:add_element({'__info__',1}, Def),
|
||||
{ clause, Line, [{ atom, Line, functions }], [], [elixir_tree_helpers:abstract_syntax(All)] }.
|
||||
|
||||
macros_clause(Line, Module, Def, Defmacro) ->
|
||||
All = handle_builtin_macros(Module, Def, Defmacro),
|
||||
{ clause, Line, [{ atom, Line, macros }], [], [elixir_tree_helpers:abstract_syntax(All)] }.
|
||||
|
||||
handle_builtin_macros('Elixir.Kernel', Def, Defmacro) ->
|
||||
ordsets:subtract(ordsets:union(Defmacro,
|
||||
elixir_dispatch:in_erlang_macros()), Def);
|
||||
handle_builtin_macros(_, _Def, Defmacro) -> Defmacro.
|
||||
|
||||
self_clause(Line, Module) ->
|
||||
{ clause, Line, [{ atom, Line, self }], [], [{ atom, Line, Module }] }.
|
||||
|
||||
macros_clause(Line, Defmacro) ->
|
||||
{ clause, Line, [{ atom, Line, macros }], [], [elixir_tree_helpers:abstract_syntax(Defmacro)] }.
|
||||
|
||||
docs_clause(Line, Module, true) ->
|
||||
Docs = ordsets:from_list(ets:tab2list(docs_table(Module))),
|
||||
{ clause, Line, [{ atom, Line, docs }], [], [elixir_tree_helpers:abstract_syntax(Docs)] };
|
||||
@@ -277,7 +302,7 @@ docs_clause(Line, _Module, _) ->
|
||||
{ clause, Line, [{ atom, Line, docs }], [], [{ atom, Line, nil }] }.
|
||||
|
||||
moduledoc_clause(Line, Module, true) ->
|
||||
Docs = 'Elixir.Module':read_attribute(Module, moduledoc),
|
||||
Docs = 'Elixir.Module':get_attribute(Module, moduledoc),
|
||||
{ clause, Line, [{ atom, Line, moduledoc }], [], [elixir_tree_helpers:abstract_syntax({ Line, Docs })] };
|
||||
|
||||
moduledoc_clause(Line, _Module, _) ->
|
||||
@@ -297,7 +322,7 @@ else_clause(Line) ->
|
||||
eval_callbacks(Line, Module, Name, Args, RawS) ->
|
||||
S = RawS#elixir_scope{check_clauses=false},
|
||||
Binding = binding_for_eval(Module, []),
|
||||
Callbacks = ets:lookup_element(data_table(Module), Name, 2),
|
||||
Callbacks = lists:reverse(ets:lookup_element(data_table(Module), Name, 2)),
|
||||
Requires = S#elixir_scope.requires,
|
||||
|
||||
lists:foreach(fun({M,F}) ->
|
||||
|
||||
@@ -3,22 +3,21 @@
|
||||
|
||||
Nonterminals
|
||||
grammar expr_list
|
||||
expr block_expr fn_expr bracket_expr call_expr max_expr
|
||||
expr block_expr fn_expr bracket_expr call_expr bracket_at_expr max_expr
|
||||
base_expr matched_expr matched_op_expr unmatched_expr op_expr
|
||||
comma_separator
|
||||
add_op mult_op unary_op two_op pipeline_op bin_concat_op
|
||||
match_op send_op default_op when_op pipe_op in_op inc_op stab_op range_op
|
||||
andand_op oror_op and_op or_op comp_expr_op colon_colon_op three_op
|
||||
andand_op oror_op and_op or_op comp_expr_op colon_colon_op three_op at_op
|
||||
open_paren close_paren
|
||||
open_bracket close_bracket
|
||||
open_curly close_curly
|
||||
open_bit close_bit
|
||||
comma_expr matched_comma_expr call_args_comma_expr
|
||||
call_args call_args_parens call_args_no_parens
|
||||
base_comma_expr comma_expr optional_comma_expr matched_comma_expr
|
||||
call_args call_args_parens call_args_no_parens parens_call
|
||||
stab_expr stab_expr_list
|
||||
kw_eol kw_expr kw_comma kw_base
|
||||
matched_kw_expr matched_kw_comma matched_kw_base
|
||||
parens_call dot_op dot_ref dot_identifier dot_op_identifier dot_do_identifier
|
||||
dot_op dot_ref dot_identifier dot_op_identifier dot_do_identifier
|
||||
dot_paren_identifier dot_punctuated_identifier dot_bracket_identifier
|
||||
var list bracket_access bit_string tuple
|
||||
fn_block do_block do_eol end_eol block_eol block_item block_list
|
||||
@@ -67,7 +66,8 @@ Right 290 pipeline_op.
|
||||
Nonassoc 300 unary_op.
|
||||
Left 310 dot_call_op.
|
||||
Left 310 dot_op.
|
||||
Nonassoc 320 var.
|
||||
Nonassoc 320 at_op.
|
||||
Nonassoc 330 var.
|
||||
|
||||
%%% MAIN FLOW OF EXPRESSIONS
|
||||
|
||||
@@ -87,11 +87,14 @@ expr -> unmatched_expr : '$1'.
|
||||
|
||||
matched_expr -> matched_expr matched_op_expr : build_op(element(1, '$2'), '$1', element(2, '$2')).
|
||||
matched_expr -> unary_op matched_expr : build_unary_op('$1', '$2').
|
||||
matched_expr -> at_op matched_expr : build_unary_op('$1', '$2').
|
||||
matched_expr -> bracket_at_expr : '$1'.
|
||||
matched_expr -> fn_expr : '$1'.
|
||||
|
||||
unmatched_expr -> matched_expr op_expr : build_op(element(1, '$2'), '$1', element(2, '$2')).
|
||||
unmatched_expr -> unmatched_expr op_expr : build_op(element(1, '$2'), '$1', element(2, '$2')).
|
||||
unmatched_expr -> unary_op expr : build_unary_op('$1', '$2').
|
||||
unmatched_expr -> at_op expr : build_unary_op('$1', '$2').
|
||||
unmatched_expr -> block_expr : '$1'.
|
||||
|
||||
op_expr -> match_op expr : { '$1', '$2' }.
|
||||
@@ -137,6 +140,7 @@ matched_op_expr -> colon_colon_op matched_expr : { '$1', '$2' }.
|
||||
matched_op_expr -> comp_expr_op matched_expr : { '$1', '$2' }.
|
||||
|
||||
block_expr -> parens_call call_args_parens do_block : build_identifier('$1', '$2' ++ '$3').
|
||||
block_expr -> parens_call call_args_parens call_args_parens do_block : { build_identifier('$1', '$2'), ?line('$1'), '$3' ++ '$4' }.
|
||||
block_expr -> dot_punctuated_identifier call_args_no_parens do_block : build_identifier('$1', '$2' ++ '$3').
|
||||
block_expr -> dot_do_identifier do_block : build_identifier('$1', '$2').
|
||||
block_expr -> dot_identifier call_args_no_parens do_block : build_identifier('$1', '$2' ++ '$3').
|
||||
@@ -152,18 +156,23 @@ call_expr -> dot_identifier call_args_no_parens : build_identifier('$1', '$2').
|
||||
call_expr -> dot_punctuated_identifier : build_identifier('$1', []).
|
||||
call_expr -> dot_do_identifier : build_identifier('$1', nil).
|
||||
call_expr -> var : build_identifier('$1', nil).
|
||||
call_expr -> bracket_expr : '$1'.
|
||||
|
||||
bracket_expr -> dot_bracket_identifier bracket_access : build_access(build_identifier('$1', nil), '$2').
|
||||
bracket_expr -> max_expr bracket_access : build_access('$1', '$2').
|
||||
bracket_expr -> max_expr : '$1'.
|
||||
call_expr -> max_expr : '$1'.
|
||||
|
||||
max_expr -> bracket_expr : '$1'.
|
||||
max_expr -> parens_call call_args_parens : build_identifier('$1', '$2').
|
||||
max_expr -> parens_call call_args_parens call_args_parens : { build_identifier('$1', '$2'), ?line('$1'), '$3' }.
|
||||
max_expr -> dot_ref : '$1'.
|
||||
max_expr -> base_expr : '$1'.
|
||||
max_expr -> open_paren ')' : build_block([]).
|
||||
max_expr -> open_paren expr_list close_paren : build_block('$2').
|
||||
|
||||
bracket_expr -> dot_bracket_identifier bracket_access : build_access(build_identifier('$1', nil), '$2').
|
||||
bracket_expr -> max_expr bracket_access : build_access('$1', '$2').
|
||||
|
||||
bracket_at_expr -> at_op dot_bracket_identifier bracket_access : build_access(build_unary_op('$1', build_identifier('$2', nil)), '$3').
|
||||
bracket_at_expr -> at_op max_expr bracket_access : build_access(build_unary_op('$1', '$2'), '$3').
|
||||
bracket_at_expr -> bracket_at_expr bracket_access : build_access('$1', '$2').
|
||||
|
||||
base_expr -> number : ?exprs('$1').
|
||||
base_expr -> signed_number : { element(4, '$1'), ?line('$1'), ?exprs('$1') }.
|
||||
base_expr -> atom : build_atom('$1').
|
||||
@@ -186,8 +195,8 @@ fn_block -> '->' grammar 'end' : build_block('$2', false).
|
||||
|
||||
do_block -> do_eol 'end' : [[{do,nil}]].
|
||||
do_block -> do_eol stab_expr_list end_eol : [[{ do, build_stab(lists:reverse('$2')) }]].
|
||||
do_block -> do_eol block_list 'end' : [sort_kw([{ do, nil }|'$2'])].
|
||||
do_block -> do_eol stab_expr_list eol block_list 'end' : [sort_kw([{ do, build_stab(lists:reverse('$2')) }|'$4'])].
|
||||
do_block -> do_eol block_list 'end' : [[{ do, nil }|'$2']].
|
||||
do_block -> do_eol stab_expr_list eol block_list 'end' : [[{ do, build_stab(lists:reverse('$2')) }|'$4']].
|
||||
|
||||
do_eol -> 'do' : '$1'.
|
||||
do_eol -> 'do' eol : '$1'.
|
||||
@@ -214,9 +223,6 @@ block_list -> block_item block_list : ['$1'|'$2'].
|
||||
|
||||
var -> dot_identifier : '$1'.
|
||||
|
||||
comma_separator -> ',' : '$1'.
|
||||
comma_separator -> ',' eol : '$1'.
|
||||
|
||||
open_paren -> '(' : '$1'.
|
||||
open_paren -> '(' eol : '$1'.
|
||||
close_paren -> ')' : '$1'.
|
||||
@@ -288,11 +294,12 @@ unary_op -> '^' : '$1'.
|
||||
unary_op -> '^' eol : '$1'.
|
||||
unary_op -> 'not' : '$1'.
|
||||
unary_op -> 'not' eol : '$1'.
|
||||
unary_op -> '@' : '$1'.
|
||||
unary_op -> '@' eol : '$1'.
|
||||
unary_op -> '~~~' : '$1'.
|
||||
unary_op -> '~~~' eol : '$1'.
|
||||
|
||||
at_op -> '@' : '$1'.
|
||||
at_op -> '@' eol : '$1'.
|
||||
|
||||
match_op -> '=' : '$1'.
|
||||
match_op -> '=' eol : '$1'.
|
||||
|
||||
@@ -370,55 +377,63 @@ parens_call -> matched_expr dot_call_op : { '.', ?line('$2'), ['$1'] }. % Fun/lo
|
||||
% Function calls
|
||||
|
||||
matched_comma_expr -> matched_expr : ['$1'].
|
||||
matched_comma_expr -> matched_comma_expr comma_separator matched_expr : ['$3'|'$1'].
|
||||
matched_comma_expr -> matched_comma_expr ',' matched_expr : ['$3'|'$1'].
|
||||
|
||||
call_args_no_parens -> matched_comma_expr : lists:reverse('$1').
|
||||
call_args_no_parens -> matched_kw_base : ['$1'].
|
||||
call_args_no_parens -> matched_comma_expr comma_separator matched_kw_base : lists:reverse(['$3'|'$1']).
|
||||
call_args_no_parens -> matched_comma_expr ',' matched_kw_base : lists:reverse(['$3'|'$1']).
|
||||
|
||||
base_comma_expr -> expr ',' : ['$1'].
|
||||
base_comma_expr -> base_comma_expr expr ',' : ['$2'|'$1'].
|
||||
|
||||
comma_expr -> expr : ['$1'].
|
||||
comma_expr -> comma_expr comma_separator expr : ['$3'|'$1'].
|
||||
comma_expr -> kw_base : ['$1'].
|
||||
comma_expr -> base_comma_expr : '$1'.
|
||||
comma_expr -> base_comma_expr expr : ['$2'|'$1'].
|
||||
comma_expr -> base_comma_expr kw_base : ['$2'|'$1'].
|
||||
|
||||
call_args_comma_expr -> comma_expr : lists:reverse('$1').
|
||||
call_args_comma_expr -> kw_base : ['$1'].
|
||||
call_args_comma_expr -> comma_expr comma_separator kw_base : lists:reverse(['$3'|'$1']).
|
||||
optional_comma_expr -> expr : '$1'.
|
||||
optional_comma_expr -> expr ',' : '$1'.
|
||||
|
||||
call_args -> comma_expr : lists:reverse('$1').
|
||||
|
||||
call_args_parens -> open_paren ')' : [].
|
||||
call_args_parens -> open_paren call_args_comma_expr close_paren : '$2'.
|
||||
|
||||
call_args -> call_args_comma_expr : '$1'.
|
||||
call_args_parens -> open_paren call_args close_paren : '$2'.
|
||||
|
||||
% KV
|
||||
|
||||
kw_eol -> kw_identifier : '$1'.
|
||||
kw_eol -> kw_identifier eol : '$1'.
|
||||
kw_eol -> kw_identifier : '$1'.
|
||||
kw_eol -> kw_identifier eol : '$1'.
|
||||
kw_expr -> kw_eol expr : { ?exprs('$1'),'$2' }.
|
||||
|
||||
kw_expr -> kw_eol expr : {?exprs('$1'),'$2'}.
|
||||
kw_comma -> kw_expr : ['$1'].
|
||||
kw_comma -> kw_expr comma_separator kw_comma : ['$1'|'$3'].
|
||||
kw_base -> kw_comma : sort_kw('$1').
|
||||
kw_comma -> kw_expr ',' : ['$1'].
|
||||
kw_comma -> kw_comma kw_expr ',' : ['$2'|'$1'].
|
||||
|
||||
kw_base -> kw_expr : ['$1'].
|
||||
kw_base -> kw_comma : lists:reverse('$1').
|
||||
kw_base -> kw_comma kw_expr : lists:reverse(['$2'|'$1']).
|
||||
|
||||
matched_kw_expr -> kw_eol matched_expr : {?exprs('$1'),'$2'}.
|
||||
matched_kw_comma -> matched_kw_expr : ['$1'].
|
||||
matched_kw_comma -> matched_kw_expr comma_separator matched_kw_comma : ['$1'|'$3'].
|
||||
matched_kw_base -> matched_kw_comma : sort_kw('$1').
|
||||
matched_kw_comma -> matched_kw_expr ',' matched_kw_comma : ['$1'|'$3'].
|
||||
matched_kw_base -> matched_kw_comma : '$1'.
|
||||
|
||||
% Lists
|
||||
|
||||
bracket_access -> open_bracket ']' : { [], ?line('$1') }.
|
||||
bracket_access -> open_bracket expr close_bracket : { '$2', ?line('$1') }.
|
||||
bracket_access -> open_bracket optional_comma_expr close_bracket : { '$2', ?line('$1') }.
|
||||
bracket_access -> open_bracket kw_base close_bracket : { '$2', ?line('$1') }.
|
||||
|
||||
list -> open_bracket ']' : [].
|
||||
list -> open_bracket kw_base close_bracket : '$2'.
|
||||
list -> open_bracket expr close_bracket : ['$2'].
|
||||
list -> open_bracket expr comma_separator call_args close_bracket : ['$2'|'$4'].
|
||||
list -> open_bracket optional_comma_expr close_bracket : ['$2'].
|
||||
list -> open_bracket expr ',' call_args close_bracket : ['$2'|'$4'].
|
||||
|
||||
% Tuple
|
||||
|
||||
tuple -> open_curly '}' : build_tuple('$1', []).
|
||||
tuple -> open_curly expr close_curly : build_tuple('$1', ['$2']).
|
||||
tuple -> open_curly expr comma_separator call_args close_curly : build_tuple('$1', ['$2'|'$4']).
|
||||
tuple -> open_curly optional_comma_expr close_curly : build_tuple('$1', ['$2']).
|
||||
tuple -> open_curly expr ',' call_args close_curly : build_tuple('$1', ['$2'|'$4']).
|
||||
|
||||
% Bitstrings
|
||||
|
||||
@@ -528,9 +543,8 @@ build_bin_string({ bin_string, Line, Args }) -> { '<<>>', Line, Args }.
|
||||
build_list_string({ list_string, _Line, [H] }) when is_binary(H) -> binary_to_list(H);
|
||||
build_list_string({ list_string, Line, Args }) -> { binary_to_list, Line, [{ '<<>>', Line, Args}] }.
|
||||
|
||||
build_atom({ atom, _Line, [H] }) when is_atom(H) -> H;
|
||||
build_atom({ atom, _Line, [H] }) when is_binary(H) -> binary_to_atom(H, utf8);
|
||||
build_atom({ atom, Line, Args }) -> { binary_to_atom, Line, [{ '<<>>', Line, Args}, utf8] }.
|
||||
build_atom({ atom, _Line, Atom }) when is_atom(Atom) -> Atom;
|
||||
build_atom({ atom, Line, Args }) -> { binary_to_atom, Line, [{ '<<>>', Line, Args }, utf8] }.
|
||||
|
||||
%% Keywords
|
||||
|
||||
@@ -550,6 +564,3 @@ build_stab([H|T], Marker, Temp, Acc) ->
|
||||
build_stab([], Marker, Temp, Acc) ->
|
||||
H = { Marker, build_block(Temp) },
|
||||
lists:reverse([H|Acc]).
|
||||
|
||||
sort_kw(List) -> lists:sort(fun sort_kw/2, List).
|
||||
sort_kw({ A, _ }, { B, _ }) -> A =< B.
|
||||
@@ -0,0 +1,65 @@
|
||||
-module(elixir_partials).
|
||||
-export([handle/2, handle/3, is_sequential/1]).
|
||||
-include("elixir.hrl").
|
||||
-compile({parse_transform, elixir_transform}).
|
||||
|
||||
handle(Original, S) ->
|
||||
handle(Original, S, default).
|
||||
|
||||
handle({ _, Line, Args } = Original, S, Opt) when is_list(Args), S#elixir_scope.context /= assign ->
|
||||
case convert(Args, S, Opt) of
|
||||
{ Call, Def, SC } when Def /= [] ->
|
||||
Final = validate(Line, Def, SC),
|
||||
Block = setelement(3, Original, Call),
|
||||
elixir_translator:translate_fn(Line, [{ Final, Block }], SC);
|
||||
_ -> error
|
||||
end;
|
||||
|
||||
handle(_Original, _S, _Opt) ->
|
||||
error.
|
||||
|
||||
validate(Line, Def, S) ->
|
||||
validate(Line, lists:sort(Def), 1, S).
|
||||
|
||||
validate(Line, [{ Pos, Item }|T], Pos, S) ->
|
||||
[Item|validate(Line, T, Pos + 1, S)];
|
||||
|
||||
validate(Line, [{ Pos, _ }|_], Expected, S) ->
|
||||
elixir_errors:syntax_error(Line, S#elixir_scope.file, "partial variable &~w cannot be defined without &~w", [Pos, Expected]);
|
||||
|
||||
validate(_Line, [], _Pos, _S) ->
|
||||
[].
|
||||
|
||||
%% This function receives arguments and then checks
|
||||
%% the args for partial application. It returns a tuple
|
||||
%% with three elements where the first element is the list
|
||||
%% of call args, the second the list of def args for the
|
||||
%% function definition and the third one is the new scope.
|
||||
convert(List, S, Opt) -> convert(List, S, Opt, [], []).
|
||||
|
||||
convert([{'|', Line, [_, _] = Args}|T], S, allow_tail, CallAcc, DefAcc) ->
|
||||
{ NewArgs, NewDef, NewS } = convert(Args, S, allow_tail, [], DefAcc),
|
||||
convert(T, NewS, allow_tail, [{ '|', Line, NewArgs}|CallAcc], NewDef);
|
||||
|
||||
convert([{'&', Line, [Pos]}|T], S, Opt, CallAcc, DefAcc) ->
|
||||
case lists:keyfind(Pos, 1, DefAcc) of
|
||||
false ->
|
||||
{ Var, SC } = elixir_scope:build_ex_var(Line, S),
|
||||
convert(T, SC, Opt, [Var|CallAcc], [{Pos,Var}|DefAcc]);
|
||||
{Pos,Var} ->
|
||||
convert(T, S, Opt, [Var|CallAcc], DefAcc)
|
||||
end;
|
||||
|
||||
convert([H|T], S, Opt, CallAcc, DefAcc) ->
|
||||
convert(T, S, Opt, [H|CallAcc], DefAcc);
|
||||
|
||||
convert([], S, _Opt, CallAcc, DefAcc) ->
|
||||
{ lists:reverse(CallAcc), lists:reverse(DefAcc), S }.
|
||||
|
||||
is_sequential(List) -> is_sequential(List, 1).
|
||||
|
||||
is_sequential([{ '&', _, [Int] }|T], Int) ->
|
||||
is_sequential(T, Int + 1);
|
||||
|
||||
is_sequential([], Int) when Int > 1 -> true;
|
||||
is_sequential(_, _Int) -> false.
|
||||
@@ -17,51 +17,65 @@ linify(_, Else) -> Else.
|
||||
|
||||
%% Translation
|
||||
|
||||
quote({ unquote, _Line, [Expr] }, #elixir_quote{unquote=true}, S) ->
|
||||
quote({ 'unquote_splicing', Line, _ } = Expr, Q, S) ->
|
||||
do_quote({ '__block__', Line, [Expr] }, Q, S);
|
||||
|
||||
quote(Else, Q, S) ->
|
||||
do_quote(Else, Q, S).
|
||||
|
||||
do_quote({ unquote, _Line, [Expr] }, #elixir_quote{unquote=true}, S) ->
|
||||
elixir_translator:translate_each(Expr, S);
|
||||
|
||||
quote({ Left, Line, nil }, Q, S) when is_atom(Left) ->
|
||||
do_quote({ { { '.', Line, [Left, unquote] }, _Line, [Expr] }, CallLine, Args }, Q, S) ->
|
||||
Rewritten = { { '.', Line, [Left, { unquote, Line, [Expr] }] }, CallLine, Args },
|
||||
do_quote(Rewritten, Q, S);
|
||||
|
||||
do_quote({ { '.', Line, [Left, unquote] }, _Line, [Expr] }, Q, S) ->
|
||||
Rewritten = { { '.', Line, [Left, { unquote, Line, [Expr] }] }, Line, [] },
|
||||
do_quote(Rewritten, Q, S);
|
||||
|
||||
do_quote({ Left, Line, nil }, Q, S) when is_atom(Left) ->
|
||||
Tuple = { tuple, Line, [
|
||||
{ atom, Line, Left },
|
||||
{ integer, Line, line(Line, Q) },
|
||||
line(Line, Q),
|
||||
{ atom, Line, Q#elixir_quote.marker }
|
||||
] },
|
||||
{ Tuple, S };
|
||||
|
||||
quote({ Left, Line, Right }, Q, S) ->
|
||||
{ TLeft, LS } = quote(Left, Q, S),
|
||||
{ TRight, RS } = quote(Right, Q, LS),
|
||||
do_quote({ Left, Line, Right }, Q, S) ->
|
||||
{ TLeft, LS } = do_quote(Left, Q, S),
|
||||
{ TRight, RS } = do_quote(Right, Q, LS),
|
||||
|
||||
%% We need to remove line numbers from quoted exprs
|
||||
%% otherwise the line number quotes in the macro will
|
||||
%% get mixed with the unquoted contents
|
||||
Tuple = { tuple, Line, [TLeft, { integer, Line, line(Line, Q) }, TRight] },
|
||||
Tuple = { tuple, Line, [TLeft, line(Line, Q), TRight] },
|
||||
{ Tuple, RS };
|
||||
|
||||
% Handle two item tuples but still allow them to be spliced.
|
||||
quote({ Left, Right }, Q, S) when
|
||||
do_quote({ Left, Right }, Q, S) when
|
||||
not is_tuple(Left) orelse (element(1, Left) /= unquote_splicing),
|
||||
not is_tuple(Right) orelse (element(1, Right) /= unquote_splicing) ->
|
||||
{ TLeft, LS } = quote(Left, Q, S),
|
||||
{ TRight, RS } = quote(Right, Q, LS),
|
||||
{ { tuple, line(0, Q), [TLeft, TRight] }, RS };
|
||||
{ TLeft, LS } = do_quote(Left, Q, S),
|
||||
{ TRight, RS } = do_quote(Right, Q, LS),
|
||||
{ { tuple, int_line(Q), [TLeft, TRight] }, RS };
|
||||
|
||||
quote({ Left, Right }, Q, S) ->
|
||||
quote({ '{}', 0, [Left, Right] }, Q, S);
|
||||
do_quote({ Left, Right }, Q, S) ->
|
||||
do_quote({ '{}', int_line(Q), [Left, Right] }, Q, S);
|
||||
|
||||
quote(List, Q, S) when is_list(List) ->
|
||||
do_quote(List, Q, S) when is_list(List) ->
|
||||
splice(List, Q, [], [], S);
|
||||
|
||||
quote(Number, Q, S) when is_integer(Number) ->
|
||||
{ { integer, line(0, Q), Number }, S };
|
||||
do_quote(Number, Q, S) when is_integer(Number) ->
|
||||
{ { integer, int_line(Q), Number }, S };
|
||||
|
||||
quote(Number, Q, S) when is_float(Number) ->
|
||||
{ { float, line(0, Q), Number }, S };
|
||||
do_quote(Number, Q, S) when is_float(Number) ->
|
||||
{ { float, int_line(Q), Number }, S };
|
||||
|
||||
quote(Atom, Q, S) when is_atom(Atom) ->
|
||||
{ { atom, line(0, Q), Atom }, S };
|
||||
do_quote(Atom, Q, S) when is_atom(Atom) ->
|
||||
{ { atom, int_line(Q), Atom }, S };
|
||||
|
||||
quote(Bitstring, _Q, S) when is_bitstring(Bitstring) ->
|
||||
do_quote(Bitstring, _Q, S) when is_bitstring(Bitstring) ->
|
||||
{ elixir_tree_helpers:abstract_syntax(Bitstring), S }.
|
||||
|
||||
% Loop through the list finding each unquote_splicing entry.
|
||||
@@ -71,6 +85,11 @@ splice([{ unquote_splicing, _, [Args] }|T], #elixir_quote{unquote=true} = Q, Buf
|
||||
{ TArgs, TS } = elixir_translator:translate_each(Args, NewS),
|
||||
splice(T, Q, [], [TArgs|NewAcc], TS);
|
||||
|
||||
splice([{ '|', Line, [{ unquote_splicing, _, [_] } = Left, Right] }], #elixir_quote{unquote=true} = Q, Buffer, Acc, S) ->
|
||||
{ TLeft, SL } = splice([Left], Q, Buffer, Acc, S),
|
||||
{ TRight, SR } = do_quote(Right, Q, SL),
|
||||
{ { op, Line, '++', TLeft, TRight }, SR };
|
||||
|
||||
splice([H|T], Q, Buffer, Acc, S) ->
|
||||
splice(T, Q, [H|Buffer], Acc, S);
|
||||
|
||||
@@ -78,12 +97,12 @@ splice([], Q, Buffer, Acc, S) ->
|
||||
{ NewAcc, NewS } = from_buffer_to_acc(Buffer, Q, Acc, S),
|
||||
case NewAcc of
|
||||
[] ->
|
||||
{ { nil, line(0, Q) }, NewS };
|
||||
{ { nil, int_line(Q) }, NewS };
|
||||
[List] ->
|
||||
{ List, NewS };
|
||||
_ ->
|
||||
List = elixir_tree_helpers:build_simple_reverse_list(0, NewAcc),
|
||||
{ ?ELIXIR_WRAP_CALL(0, lists, append, [List]), NewS }
|
||||
List = elixir_tree_helpers:build_simple_reverse_list(int_line(Q), NewAcc),
|
||||
{ ?ELIXIR_WRAP_CALL(int_line(Q), lists, append, [List]), NewS }
|
||||
end.
|
||||
|
||||
from_buffer_to_acc([], _Q, Acc, S) ->
|
||||
@@ -91,8 +110,14 @@ from_buffer_to_acc([], _Q, Acc, S) ->
|
||||
|
||||
from_buffer_to_acc(Buffer, Q, Acc, S) ->
|
||||
{ New, NewS } = elixir_tree_helpers:build_reverse_list(
|
||||
fun(X, AccS) -> quote(X, Q, AccS) end, Buffer, 0, S),
|
||||
fun(X, AccS) -> do_quote(X, Q, AccS) end, Buffer, int_line(Q), S),
|
||||
{ [New|Acc], NewS }.
|
||||
|
||||
line(Line, #elixir_quote{line=keep}) -> Line;
|
||||
line(_Line, #elixir_quote{line=Line}) -> Line.
|
||||
line(Line, #elixir_quote{line=keep}) -> { integer, Line, Line };
|
||||
line(_Line, #elixir_quote{line=Line}) -> Line.
|
||||
|
||||
int_line(Q) ->
|
||||
case line(0, Q) of
|
||||
{ integer, _, Line } -> Line;
|
||||
_ -> 0
|
||||
end.
|
||||
@@ -1,7 +1,7 @@
|
||||
%% Convenience functions used to manipulate scope
|
||||
%% and its variables.
|
||||
-module(elixir_scope).
|
||||
-export([translate_var/3,
|
||||
-export([translate_var/4,
|
||||
build_erl_var/2, build_ex_var/2,
|
||||
serialize/1, deserialize/1, deserialize/2,
|
||||
to_erl_env/1, to_ex_env/1, filename/1,
|
||||
@@ -10,30 +10,36 @@
|
||||
-include("elixir.hrl").
|
||||
-compile({parse_transform, elixir_transform}).
|
||||
|
||||
translate_var(Line, Name, S) ->
|
||||
Match = S#elixir_scope.context == assign,
|
||||
translate_var(Line, Name, Kind, S) ->
|
||||
Vars = S#elixir_scope.vars,
|
||||
TempVars = S#elixir_scope.temp_vars,
|
||||
ClauseVars = S#elixir_scope.clause_vars,
|
||||
|
||||
case Name of
|
||||
'_' -> { {var, Line, Name}, S };
|
||||
_ ->
|
||||
case { Match, dict:is_key(Name, Vars), dict:is_key(Name, TempVars) } of
|
||||
{ true, true, true } -> { {var, Line, dict:fetch(Name, Vars) }, S };
|
||||
{ true, Else, _ } ->
|
||||
{ NewVar, NS } = case Else or S#elixir_scope.noname of
|
||||
true -> build_erl_var(Line, S);
|
||||
false -> { {var, Line, Name}, S }
|
||||
end,
|
||||
RealName = element(3, NewVar),
|
||||
{ NewVar, NS#elixir_scope{
|
||||
vars=dict:store(Name, RealName, Vars),
|
||||
temp_vars=dict:store(Name, RealName, TempVars),
|
||||
clause_vars=dict:store(Name, RealName, ClauseVars)
|
||||
} };
|
||||
{ false, false, _ } -> elixir_translator:translate_each({Name, Line, []}, S);
|
||||
{ false, true, _ } -> { {var, Line, dict:fetch(Name, Vars) }, S }
|
||||
case S#elixir_scope.context of
|
||||
assign ->
|
||||
TempVars = S#elixir_scope.temp_vars,
|
||||
case { orddict:is_key(Name, Vars), orddict:find(Name, TempVars) } of
|
||||
{ true, { ok, Kind } } ->
|
||||
{ {var, Line, orddict:fetch(Name, Vars) }, S };
|
||||
{ Else, _ } ->
|
||||
{ NewVar, NS } = case Else or S#elixir_scope.noname of
|
||||
true -> build_erl_var(Line, S);
|
||||
false -> { {var, Line, Name}, S }
|
||||
end,
|
||||
RealName = element(3, NewVar),
|
||||
ClauseVars = S#elixir_scope.clause_vars,
|
||||
{ NewVar, NS#elixir_scope{
|
||||
vars=orddict:store(Name, RealName, Vars),
|
||||
temp_vars=orddict:store(Name, Kind, TempVars),
|
||||
clause_vars=orddict:store({ Name, Kind }, RealName, ClauseVars)
|
||||
} }
|
||||
end;
|
||||
_ ->
|
||||
case orddict:is_key(Name, Vars) of
|
||||
false -> elixir_translator:translate_each({Name, Line, []}, S);
|
||||
true -> { {var, Line, orddict:fetch(Name, Vars) }, S }
|
||||
end
|
||||
end
|
||||
end.
|
||||
|
||||
@@ -87,7 +93,7 @@ deserialize({ File, Functions, CheckClauses, Macro, Requires, Macros, Aliases, S
|
||||
macros=Macros,
|
||||
aliases=Aliases,
|
||||
scheduled=Scheduled,
|
||||
vars=dict:from_list(Vars),
|
||||
vars=orddict:from_list(Vars),
|
||||
counter=length(Vars)
|
||||
}.
|
||||
|
||||
@@ -102,9 +108,9 @@ umergev(S1, S2) ->
|
||||
C1 = S1#elixir_scope.clause_vars,
|
||||
C2 = S2#elixir_scope.clause_vars,
|
||||
S2#elixir_scope{
|
||||
vars=dict:merge(fun var_merger/3, V1, V2),
|
||||
quote_vars=dict:merge(fun var_merger/3, Q1, Q2),
|
||||
clause_vars=dict:merge(fun var_merger/3, C1, C2)
|
||||
vars=orddict:merge(fun var_merger/3, V1, V2),
|
||||
quote_vars=orddict:merge(fun var_merger/3, Q1, Q2),
|
||||
clause_vars=orddict:merge(fun clause_var_merger/3, C1, C2)
|
||||
}.
|
||||
|
||||
% Receives two scopes and return a new scope based on the first
|
||||
@@ -121,13 +127,16 @@ umergec(S1, S2) ->
|
||||
|
||||
% Merge variables trying to find the most recently created.
|
||||
|
||||
clause_var_merger({ Var, _ }, K1, K2) ->
|
||||
var_merger(Var, K1, K2).
|
||||
|
||||
var_merger(Var, Var, K2) -> K2;
|
||||
var_merger(Var, K1, Var) -> K1;
|
||||
var_merger(_Var, K1, K2) ->
|
||||
V1 = list_to_integer(var_number(atom_to_list(K1))),
|
||||
V2 = list_to_integer(var_number(atom_to_list(K2))),
|
||||
V1 = var_number(atom_to_list(K1)),
|
||||
V2 = var_number(atom_to_list(K2)),
|
||||
if V1 > V2 -> K1;
|
||||
true -> K2
|
||||
end.
|
||||
|
||||
var_number([_,_|T]) -> T.
|
||||
var_number([_,_|T]) -> list_to_integer(T).
|
||||
@@ -14,7 +14,7 @@ init(Args) ->
|
||||
permanent, % Restart = permanent | transient | temporary
|
||||
2000, % Shutdown = brutal_kill | int() >= 0 | infinity
|
||||
worker, % Type = worker | supervisor
|
||||
[elixir] % Modules = [Module] | dynamic
|
||||
[elixir_code_server] % Modules = [Module] | dynamic
|
||||
}
|
||||
],
|
||||
|
||||
|
||||
+235
-165
@@ -3,6 +3,13 @@
|
||||
-export([tokenize/3]).
|
||||
-import(elixir_interpolation, [unescape_chars/1, unescape_tokens/1]).
|
||||
|
||||
-record(scope, {
|
||||
file,
|
||||
terminators=[],
|
||||
check_terminators=true,
|
||||
existing_atoms_only=false
|
||||
}).
|
||||
|
||||
-define(is_digit(S), S >= $0 andalso S =< $9).
|
||||
-define(is_hex(S), ?is_digit(S) orelse (S >= $A andalso S =< $F) orelse (S >= $a andalso S =< $f)).
|
||||
-define(is_bin(S), S >= $0 andalso S =< $1).
|
||||
@@ -72,53 +79,82 @@
|
||||
T == $@
|
||||
).
|
||||
|
||||
tokenize(String, Line, File) ->
|
||||
tokenize(String, Line, File, [], []).
|
||||
-define(unary_op(T),
|
||||
T == '+';
|
||||
T == '-';
|
||||
T == '@';
|
||||
T == '!';
|
||||
T == '^'
|
||||
).
|
||||
|
||||
tokenize([], _Line, _File, [], Tokens) ->
|
||||
tokenize(String, Line, Opts) ->
|
||||
File = case lists:keyfind(file, 1, Opts) of
|
||||
{ file, V1 } -> V1;
|
||||
false -> <<"nofile">>
|
||||
end,
|
||||
|
||||
Existing = case lists:keyfind(existing_atoms_only, 1, Opts) of
|
||||
{ existing_atoms_only, true } -> true;
|
||||
false -> false
|
||||
end,
|
||||
|
||||
Check = case lists:keyfind(check_terminators, 1, Opts) of
|
||||
{ check_terminators, false } -> false;
|
||||
false -> true
|
||||
end,
|
||||
|
||||
Scope = #scope{
|
||||
file=File,
|
||||
existing_atoms_only=Existing,
|
||||
check_terminators=Check
|
||||
},
|
||||
|
||||
tokenize(String, Line, Scope, []).
|
||||
|
||||
tokenize([], _Line, #scope{terminators=[]}, Tokens) ->
|
||||
{ ok, lists:reverse(Tokens) };
|
||||
|
||||
tokenize([], EndLine, _File, [{ Start, StartLine }|_], _Tokens) ->
|
||||
tokenize([], EndLine, #scope{terminators=[{ Start, StartLine }|_]}, _Tokens) ->
|
||||
End = terminator(Start),
|
||||
Message = io_lib:format("missing terminator: ~s (for \"~s\" starting at line ~B)", [End, Start, StartLine]),
|
||||
{ error, { EndLine, Message, [] } };
|
||||
|
||||
% Base integers
|
||||
|
||||
tokenize([$0,X,H|T], Line, File, Terminators, Tokens) when (X == $x orelse X == $X), ?is_hex(H) ->
|
||||
tokenize([$0,X,H|T], Line, Scope, Tokens) when (X == $x orelse X == $X), ?is_hex(H) ->
|
||||
{ Rest, Number } = tokenize_hex([H|T], []),
|
||||
tokenize(Rest, Line, File, Terminators, [{number,Line,Number}|Tokens]);
|
||||
tokenize(Rest, Line, Scope, [{ number, Line, Number }|Tokens]);
|
||||
|
||||
tokenize([$0,O,H|T], Line, File, Terminators, Tokens) when (O == $o orelse O == $O), ?is_octal(H) ->
|
||||
tokenize([$0,O,H|T], Line, Scope, Tokens) when (O == $o orelse O == $O), ?is_octal(H) ->
|
||||
{ Rest, Number } = tokenize_octal([H|T], []),
|
||||
tokenize(Rest, Line, File, Terminators, [{number,Line,Number}|Tokens]);
|
||||
tokenize(Rest, Line, Scope, [{ number, Line, Number }|Tokens]);
|
||||
|
||||
tokenize([$0,B,H|T], Line, File, Terminators, Tokens) when (B == $b orelse B == $B), ?is_bin(H) ->
|
||||
tokenize([$0,B,H|T], Line, Scope, Tokens) when (B == $b orelse B == $B), ?is_bin(H) ->
|
||||
{ Rest, Number } = tokenize_bin([H|T], []),
|
||||
tokenize(Rest, Line, File, Terminators, [{number,Line,Number}|Tokens]);
|
||||
tokenize(Rest, Line, Scope, [{ number, Line, Number }|Tokens]);
|
||||
|
||||
% Comments
|
||||
|
||||
tokenize([$#|String], Line, File, Terminators, Tokens) ->
|
||||
tokenize([$#|String], Line, Scope, Tokens) ->
|
||||
Rest = tokenize_comment(String),
|
||||
tokenize(Rest, Line, File, Terminators, Tokens);
|
||||
tokenize(Rest, Line, Scope, Tokens);
|
||||
|
||||
% Sigils
|
||||
|
||||
tokenize([$%,S,H,H,H|T], Line, File, Terminators, Tokens) when ?is_quote(H), ?is_upcase(S) orelse ?is_downcase(S) ->
|
||||
tokenize([$%,S,H,H,H|T], Line, #scope{file=File} = Scope, Tokens) when ?is_quote(H), ?is_upcase(S) orelse ?is_downcase(S) ->
|
||||
case extract_heredoc_with_interpolation(Line, File, ?is_downcase(S), T, H) of
|
||||
{ error, _ } = Error ->
|
||||
Error;
|
||||
{ Parts, Rest } ->
|
||||
{ Final, Modifiers } = collect_modifiers(Rest, []),
|
||||
tokenize(Final, Line, File, Terminators, [{sigil,Line,S,Parts,Modifiers}|Tokens])
|
||||
tokenize(Final, Line, Scope, [{ sigil, Line, S, Parts, Modifiers }|Tokens])
|
||||
end;
|
||||
|
||||
tokenize([$%,S,H|T], Line, File, Terminators, Tokens) when not(?is_word(H)), ?is_upcase(S) orelse ?is_downcase(S) ->
|
||||
tokenize([$%,S,H|T], Line, #scope{file=File} = Scope, Tokens) when not(?is_word(H)), ?is_upcase(S) orelse ?is_downcase(S) ->
|
||||
case elixir_interpolation:extract(Line, File, ?is_downcase(S), T, sigil_terminator(H)) of
|
||||
{ NewLine, Parts, Rest } ->
|
||||
{ Final, Modifiers } = collect_modifiers(Rest, []),
|
||||
tokenize(Final, NewLine, File, Terminators, [{sigil,Line,S,Parts,Modifiers}|Tokens]);
|
||||
tokenize(Final, NewLine, Scope, [{ sigil, Line, S, Parts, Modifiers }|Tokens]);
|
||||
Error ->
|
||||
Sigil = [$%,S,H],
|
||||
interpolation_error(Error, " (for sigil ~s starting at line ~B)", [Sigil, Line])
|
||||
@@ -126,74 +162,85 @@ tokenize([$%,S,H|T], Line, File, Terminators, Tokens) when not(?is_word(H)), ?is
|
||||
|
||||
% Char tokens
|
||||
|
||||
tokenize([$?,$\\,H|T], Line, File, Terminators, Tokens) ->
|
||||
tokenize([$?,$\\,H|T], Line, Scope, Tokens) ->
|
||||
Char = elixir_interpolation:unescape_map(H),
|
||||
tokenize(T, Line, File, Terminators, [{number,Line,Char}|Tokens]);
|
||||
tokenize(T, Line, Scope, [{ number, Line, Char }|Tokens]);
|
||||
|
||||
tokenize([$?,Char|T], Line, File, Terminators, Tokens) ->
|
||||
tokenize(T, Line, File, Terminators, [{number,Line,Char}|Tokens]);
|
||||
tokenize([$?,Char|T], Line, Scope, Tokens) ->
|
||||
tokenize(T, Line, Scope, [{ number, Line, Char }|Tokens]);
|
||||
|
||||
% Dot identifier
|
||||
|
||||
tokenize("..." ++ Rest, Line, File, Terminators, Tokens) ->
|
||||
tokenize(Rest, Line, File, Terminators, [tokenize_call_identifier(identifier, Line, '...', Rest)|Tokens]);
|
||||
tokenize("..." ++ Rest, Line, Scope, Tokens) ->
|
||||
Token = tokenize_call_identifier(identifier, Line, '...', Rest),
|
||||
tokenize(Rest, Line, Scope, [Token|Tokens]);
|
||||
|
||||
% Dot operators
|
||||
|
||||
% ## Containers
|
||||
tokenize(".<<>>" ++ Rest, Line, File, Terminators, Tokens) ->
|
||||
handle_call_identifier(Line, File, Terminators, '<<>>', Rest, Tokens);
|
||||
tokenize(".<<>>" ++ Rest, Line, Scope, Tokens) ->
|
||||
handle_call_identifier(Rest, Line, '<<>>', Scope, Tokens);
|
||||
|
||||
tokenize([$.,T1,T2|Rest], Line, File, Terminators, Tokens) when ?container2(T1, T2) ->
|
||||
handle_call_identifier(Line, File, Terminators, list_to_atom([T1, T2]), Rest, Tokens);
|
||||
tokenize([$.,T1,T2|Rest], Line, Scope, Tokens) when ?container2(T1, T2) ->
|
||||
handle_call_identifier(Rest, Line, list_to_atom([T1, T2]), Scope, Tokens);
|
||||
|
||||
% ## Three Token Operators
|
||||
tokenize([$.,T1,T2,T3|Rest], Line, File, Terminators, Tokens) when ?comp3(T1, T2, T3); ?op3(T1, T2, T3) ->
|
||||
handle_call_identifier(Line, File, Terminators, list_to_atom([T1, T2, T3]), Rest, Tokens);
|
||||
tokenize([$.,T1,T2,T3|Rest], Line, Scope, Tokens) when ?comp3(T1, T2, T3); ?op3(T1, T2, T3) ->
|
||||
handle_call_identifier(Rest, Line, list_to_atom([T1, T2, T3]), Scope, Tokens);
|
||||
|
||||
% ## Two Token Operators
|
||||
tokenize([$.,T1,T2|Rest], Line, File, Terminators, Tokens) when ?comp2(T1, T2); ?op2(T1, T2) ->
|
||||
handle_call_identifier(Line, File, Terminators, list_to_atom([T1, T2]), Rest, Tokens);
|
||||
tokenize([$.,T1,T2|Rest], Line, Scope, Tokens) when ?comp2(T1, T2); ?op2(T1, T2) ->
|
||||
handle_call_identifier(Rest, Line, list_to_atom([T1, T2]), Scope, Tokens);
|
||||
|
||||
% ## Single Token Operators
|
||||
tokenize([$.,T|Rest], Line, File, Terminators, Tokens) when ?comp1(T); ?op1(T); T == $& ->
|
||||
handle_call_identifier(Line, File, Terminators, list_to_atom([T]), Rest, Tokens);
|
||||
tokenize([$.,T|Rest], Line, Scope, Tokens) when ?comp1(T); ?op1(T); T == $& ->
|
||||
handle_call_identifier(Rest, Line, list_to_atom([T]), Scope, Tokens);
|
||||
|
||||
% Dot call
|
||||
|
||||
% ## Exception for .( as it needs to be treated specially in the parser
|
||||
tokenize([$.,$(|Rest], Line, File, Terminators, Tokens) ->
|
||||
tokenize([$(|Rest], Line, File, Terminators, add_token_with_nl({dot_call_op,Line,'.'}, Tokens));
|
||||
tokenize([$.,$(|Rest], Line, Scope, Tokens) ->
|
||||
tokenize([$(|Rest], Line, Scope, add_token_with_nl({ dot_call_op, Line, '.' }, Tokens));
|
||||
|
||||
tokenize([$.,H|T], Line, File, Terminators, Tokens) when ?is_quote(H) ->
|
||||
tokenize([$.,H|T], Line, #scope{file=File} = Scope, Tokens) when ?is_quote(H) ->
|
||||
case elixir_interpolation:extract(Line, File, true, T, H) of
|
||||
{ NewLine, [Part], Rest } when is_binary(Part) ->
|
||||
Atom = binary_to_atom(Part, utf8),
|
||||
tokenize(Rest, NewLine, File, Terminators,
|
||||
[tokenize_call_identifier(identifier, Line, Atom, Rest)|add_token_with_nl({'.',Line}, Tokens)]);
|
||||
Atom = unsafe_to_atom(Part, Scope),
|
||||
Token = tokenize_call_identifier(identifier, Line, Atom, Rest),
|
||||
tokenize(Rest, NewLine, Scope, [Token|add_token_with_nl({ '.', Line }, Tokens)]);
|
||||
Error ->
|
||||
interpolation_error(Error, " (for function name starting at line ~B)", [Line])
|
||||
end;
|
||||
|
||||
% Heredocs
|
||||
|
||||
tokenize("\"\"\"" ++ T, Line, File, Terminators, Tokens) -> handle_heredocs(Line, File, Terminators, $", T, Tokens);
|
||||
tokenize("'''" ++ T, Line, File, Terminators, Tokens) -> handle_heredocs(Line, File, Terminators, $', T, Tokens);
|
||||
tokenize("\"\"\"" ++ T, Line, Scope, Tokens) ->
|
||||
handle_heredocs(T, Line, $", Scope, Tokens);
|
||||
|
||||
tokenize("'''" ++ T, Line, Scope, Tokens) ->
|
||||
handle_heredocs(T, Line, $', Scope, Tokens);
|
||||
|
||||
% Strings
|
||||
|
||||
tokenize([$"|T], Line, File, Terminators, Tokens) -> handle_strings(Line, File, Terminators, $", T, Tokens);
|
||||
tokenize([$'|T], Line, File, Terminators, Tokens) -> handle_strings(Line, File, Terminators, $', T, Tokens);
|
||||
tokenize([$"|T], Line, Scope, Tokens) ->
|
||||
handle_strings(T, Line, $", Scope, Tokens);
|
||||
tokenize([$'|T], Line, Scope, Tokens) ->
|
||||
handle_strings(T, Line, $', Scope, Tokens);
|
||||
|
||||
% Atoms
|
||||
|
||||
tokenize([$:,T|String], Line, File, Terminators, Tokens) when ?is_upcase(T); ?is_downcase(T); T == $_ ->
|
||||
{ Rest, Atom } = tokenize_atom([T|String], []),
|
||||
tokenize(Rest, Line, File, Terminators, [{atom,Line,[Atom]}|Tokens]);
|
||||
tokenize([$:,T|String], Line, Scope, Tokens) when ?is_upcase(T); ?is_downcase(T); T == $_ ->
|
||||
{ Rest, Atom } = tokenize_atom([T|String], [], Scope),
|
||||
tokenize(Rest, Line, Scope, [{ atom, Line, Atom }|Tokens]);
|
||||
|
||||
tokenize([$:,H|T], Line, File, Terminators, Tokens) when ?is_quote(H) ->
|
||||
tokenize([$:,H|T], Line, #scope{file=File} = Scope, Tokens) when ?is_quote(H) ->
|
||||
case elixir_interpolation:extract(Line, File, true, T, H) of
|
||||
{ NewLine, Parts, Rest } -> tokenize(Rest, NewLine, File, Terminators, [{atom,Line,unescape_tokens(Parts)}|Tokens]);
|
||||
{ NewLine, Parts, Rest } ->
|
||||
Token = case unescape_tokens(Parts) of
|
||||
[Part] -> { atom, Line, unsafe_to_atom(Part, Scope) };
|
||||
Unescaped -> { atom, Line, Unescaped }
|
||||
end,
|
||||
tokenize(Rest, NewLine, Scope, [Token|Tokens]);
|
||||
Error ->
|
||||
interpolation_error(Error, " (for atom starting at line ~B)", [Line])
|
||||
end;
|
||||
@@ -201,140 +248,146 @@ tokenize([$:,H|T], Line, File, Terminators, Tokens) when ?is_quote(H) ->
|
||||
% Atom operators
|
||||
|
||||
% ## Containers
|
||||
tokenize(":<<>>" ++ Rest, Line, File, Terminators, Tokens) ->
|
||||
tokenize(Rest, Line, File, Terminators, [{atom,Line,['<<>>']}|Tokens]);
|
||||
tokenize(":<<>>" ++ Rest, Line, Scope, Tokens) ->
|
||||
tokenize(Rest, Line, Scope, [{ atom, Line, '<<>>' }|Tokens]);
|
||||
|
||||
tokenize([$:,T1,T2|Rest], Line, File, Terminators, Tokens) when ?container2(T1, T2) ->
|
||||
tokenize(Rest, Line, File, Terminators, [{atom,Line,[list_to_atom([T1,T2])]}|Tokens]);
|
||||
tokenize([$:,T1,T2|Rest], Line, Scope, Tokens) when ?container2(T1, T2) ->
|
||||
tokenize(Rest, Line, Scope, [{ atom, Line, list_to_atom([T1,T2]) }|Tokens]);
|
||||
|
||||
% ## Three Token Operators
|
||||
tokenize([$:,T1,T2,T3|Rest], Line, File, Terminators, Tokens) when ?comp3(T1, T2, T3); ?op3(T1, T2, T3) ->
|
||||
tokenize(Rest, Line, File, Terminators, [{atom,Line,[list_to_atom([T1,T2,T3])]}|Tokens]);
|
||||
tokenize([$:,T1,T2,T3|Rest], Line, Scope, Tokens) when ?comp3(T1, T2, T3); ?op3(T1, T2, T3) ->
|
||||
tokenize(Rest, Line, Scope, [{ atom, Line, list_to_atom([T1,T2,T3]) }|Tokens]);
|
||||
|
||||
% ## Two Token Operators
|
||||
tokenize([$:,T1,T2|Rest], Line, File, Terminators, Tokens) when ?comp2(T1, T2); ?op2(T1, T2) ->
|
||||
tokenize(Rest, Line, File, Terminators, [{atom,Line,[list_to_atom([T1,T2])]}|Tokens]);
|
||||
tokenize([$:,T1,T2|Rest], Line, Scope, Tokens) when ?comp2(T1, T2); ?op2(T1, T2) ->
|
||||
tokenize(Rest, Line, Scope, [{ atom, Line, list_to_atom([T1,T2]) }|Tokens]);
|
||||
|
||||
% ## Single Token Operators
|
||||
tokenize([$:,T|Rest], Line, File, Terminators, Tokens) when ?comp1(T); ?op1(T); T == $&; T == $. ->
|
||||
tokenize(Rest, Line, File, Terminators, [{atom,Line,[list_to_atom([T])]}|Tokens]);
|
||||
tokenize([$:,T|Rest], Line, Scope, Tokens) when ?comp1(T); ?op1(T); T == $&; T == $. ->
|
||||
tokenize(Rest, Line, Scope, [{ atom, Line, list_to_atom([T]) }|Tokens]);
|
||||
|
||||
% End of line
|
||||
|
||||
tokenize(";" ++ Rest, Line, File, Terminators, []) ->
|
||||
tokenize(Rest, Line, File, Terminators, eol(Line, $;, []));
|
||||
tokenize(";" ++ Rest, Line, Scope, []) ->
|
||||
tokenize(Rest, Line, Scope, eol(Line, $;, []));
|
||||
|
||||
tokenize(";" ++ Rest, Line, File, Terminators, [Top|Tokens]) when element(1, Top) /= eol ->
|
||||
tokenize(Rest, Line, File, Terminators, eol(Line, $;, [Top|Tokens]));
|
||||
tokenize(";" ++ Rest, Line, Scope, [Top|Tokens]) when element(1, Top) /= eol ->
|
||||
tokenize(Rest, Line, Scope, eol(Line, $;, [Top|Tokens]));
|
||||
|
||||
tokenize("\\\n" ++ Rest, Line, File, Terminators, Tokens) ->
|
||||
tokenize(Rest, Line + 1, File, Terminators, Tokens);
|
||||
tokenize("\\\n" ++ Rest, Line, Scope, Tokens) ->
|
||||
tokenize(Rest, Line + 1, Scope, Tokens);
|
||||
|
||||
tokenize("\\\r\n" ++ Rest, Line, File, Terminators, Tokens) ->
|
||||
tokenize(Rest, Line + 1, File, Terminators, Tokens);
|
||||
tokenize("\\\r\n" ++ Rest, Line, Scope, Tokens) ->
|
||||
tokenize(Rest, Line + 1, Scope, Tokens);
|
||||
|
||||
tokenize("\n" ++ Rest, Line, File, Terminators, Tokens) ->
|
||||
tokenize(Rest, Line + 1, File, Terminators, eol(Line, $\n, Tokens));
|
||||
tokenize("\n" ++ Rest, Line, Scope, Tokens) ->
|
||||
tokenize(Rest, Line + 1, Scope, eol(Line, $\n, Tokens));
|
||||
|
||||
tokenize("\r\n" ++ Rest, Line, File, Terminators, Tokens) ->
|
||||
tokenize(Rest, Line + 1, File, Terminators, eol(Line, $\n, Tokens));
|
||||
tokenize("\r\n" ++ Rest, Line, Scope, Tokens) ->
|
||||
tokenize(Rest, Line + 1, Scope, eol(Line, $\n, Tokens));
|
||||
|
||||
% Stand-alone tokens
|
||||
|
||||
% ## &
|
||||
tokenize([$&,H|Rest], Line, File, Terminators, Tokens) when ?is_digit(H) ->
|
||||
tokenize(Rest, Line, File, Terminators, [{'&', Line, [list_to_integer([H])]}|Tokens]);
|
||||
tokenize([$&,H|Rest], Line, Scope, Tokens) when ?is_digit(H) ->
|
||||
tokenize(Rest, Line, Scope, [{ '&', Line, [list_to_integer([H])] }|Tokens]);
|
||||
|
||||
% ## Comparison three token operators
|
||||
tokenize([T1,T2,T3|Rest], Line, File, Terminators, Tokens) when ?comp3(T1, T2, T3) ->
|
||||
handle_comp_op(Line, File, Terminators, list_to_atom([T1,T2,T3]), Rest, Tokens);
|
||||
tokenize([T1,T2,T3|Rest], Line, Scope, Tokens) when ?comp3(T1, T2, T3) ->
|
||||
handle_comp_op(Rest, Line, list_to_atom([T1,T2,T3]), Scope, Tokens);
|
||||
|
||||
% ## Three token operators
|
||||
tokenize([T1,T2,T3|Rest], Line, File, Terminators, Tokens) when ?op3(T1, T2, T3) ->
|
||||
handle_op(Line, File, Terminators, list_to_atom([T1,T2,T3]), Rest, Tokens, 3);
|
||||
tokenize([T1,T2,T3|Rest], Line, Scope, Tokens) when ?op3(T1, T2, T3) ->
|
||||
handle_op(Rest, Line, list_to_atom([T1,T2,T3]), Scope, Tokens);
|
||||
|
||||
% ## Containers + punctuation tokens
|
||||
|
||||
tokenize([T,T|Rest], Line, File, Terminators, Tokens) when T == $<; T == $> ->
|
||||
tokenize([T,T|Rest], Line, Scope, Tokens) when T == $<; T == $> ->
|
||||
Token = { list_to_atom([T,T]), Line },
|
||||
case handle_terminator(File, Token, Terminators) of
|
||||
case handle_terminator(Token, Scope) of
|
||||
{ error, _ } = Error -> Error;
|
||||
New -> tokenize(Rest, Line, File, New, [Token|Tokens])
|
||||
New -> tokenize(Rest, Line, New, [Token|Tokens])
|
||||
end;
|
||||
|
||||
tokenize([T|Rest], Line, File, Terminators, Tokens) when T == $(;
|
||||
tokenize([T|Rest], Line, Scope, Tokens) when T == $(;
|
||||
T == ${; T == $}; T == $[; T == $]; T == $); T == $, ->
|
||||
Token = { list_to_atom([T]), Line },
|
||||
case handle_terminator(File, Token, Terminators) of
|
||||
case handle_terminator(Token, Scope) of
|
||||
{ error, _ } = Error -> Error;
|
||||
New -> tokenize(Rest, Line, File, New, [Token|Tokens])
|
||||
New -> tokenize(Rest, Line, New, [Token|Tokens])
|
||||
end;
|
||||
|
||||
% ## Comparison two token operators
|
||||
tokenize([T1,T2|Rest], Line, File, Terminators, Tokens) when ?comp2(T1, T2) ->
|
||||
handle_comp_op(Line, File, Terminators, list_to_atom([T1,T2]), Rest, Tokens);
|
||||
tokenize([T1,T2|Rest], Line, Scope, Tokens) when ?comp2(T1, T2) ->
|
||||
handle_comp_op(Rest, Line, list_to_atom([T1, T2]), Scope, Tokens);
|
||||
|
||||
% ## Two Token Operators
|
||||
tokenize([T1,T2|Rest], Line, File, Terminators, Tokens) when ?op2(T1, T2) ->
|
||||
handle_op(Line, File, Terminators, list_to_atom([T1,T2]), Rest, Tokens, 2);
|
||||
tokenize([T1,T2|Rest], Line, Scope, Tokens) when ?op2(T1, T2) ->
|
||||
handle_op(Rest, Line, list_to_atom([T1, T2]), Scope, Tokens);
|
||||
|
||||
% ## Comparison single token operators
|
||||
tokenize([T|Rest], Line, File, Terminators, Tokens) when ?comp1(T) ->
|
||||
handle_comp_op(Line, File, Terminators, list_to_atom([T]), Rest, Tokens);
|
||||
tokenize([T|Rest], Line, Scope, Tokens) when ?comp1(T) ->
|
||||
handle_comp_op(Rest, Line, list_to_atom([T]), Scope, Tokens);
|
||||
|
||||
% ## Single Token Operators
|
||||
tokenize([T|Rest], Line, File, Terminators, Tokens) when ?op1(T) ->
|
||||
handle_op(Line, File, Terminators, list_to_atom([T]), Rest, Tokens, 1);
|
||||
tokenize([T|Rest], Line, Scope, Tokens) when ?op1(T) ->
|
||||
handle_op(Rest, Line, list_to_atom([T]), Scope, Tokens);
|
||||
|
||||
tokenize([$.|Rest], Line, File, Terminators, Tokens) ->
|
||||
tokenize(Rest, Line, File, Terminators, add_token_with_nl({'.', Line}, Tokens));
|
||||
tokenize([$.|Rest], Line, Scope, Tokens) ->
|
||||
tokenize(Rest, Line, Scope, add_token_with_nl({ '.', Line }, Tokens));
|
||||
|
||||
% Integers and floats
|
||||
|
||||
tokenize([H|_] = String, Line, File, Terminators, Tokens) when ?is_digit(H) ->
|
||||
tokenize([H|_] = String, Line, Scope, Tokens) when ?is_digit(H) ->
|
||||
{ Rest, Number } = tokenize_number(String, [], false),
|
||||
tokenize(Rest, Line, File, Terminators, [{number,Line,Number}|Tokens]);
|
||||
tokenize(Rest, Line, Scope, [{ number, Line, Number }|Tokens]);
|
||||
|
||||
% Aliases
|
||||
|
||||
tokenize([H|_] = String, Line, File, Terminators, Tokens) when ?is_upcase(H) ->
|
||||
tokenize([H|_] = String, Line, Scope, Tokens) when ?is_upcase(H) ->
|
||||
{ Rest, Alias } = tokenize_identifier(String, [], false),
|
||||
tokenize(Rest, Line, File, Terminators, [{'__aliases__',Line,[list_to_atom(Alias)]}|Tokens]);
|
||||
Atom = unsafe_to_atom(Alias, Scope),
|
||||
case Rest of
|
||||
[$:|T] when hd(T) /= $: ->
|
||||
tokenize(T, Line, Scope, [{ kw_identifier, Line, Atom }|Tokens]);
|
||||
_ ->
|
||||
tokenize(Rest, Line, Scope, [{ '__aliases__', Line, [Atom] }|Tokens])
|
||||
end;
|
||||
|
||||
% Identifier
|
||||
|
||||
tokenize([H|_] = String, Line, File, Terminators, Tokens) when ?is_downcase(H); H == $_ ->
|
||||
{ Rest, { Kind, _, Identifier } } = tokenize_any_identifier(Line, File, String, []),
|
||||
case keyword(Line, Kind, Identifier, Tokens) of
|
||||
tokenize([H|_] = String, Line, Scope, Tokens) when ?is_downcase(H); H == $_ ->
|
||||
{ Rest, { Kind, _, Identifier } } = tokenize_any_identifier(String, Line, [], Scope),
|
||||
case handle_keyword(Line, Kind, Identifier, Tokens) of
|
||||
false ->
|
||||
tokenize(Rest, Line, File, Terminators, [{Kind,Line,Identifier}|Tokens]);
|
||||
tokenize(Rest, Line, Scope, [{ Kind, Line, Identifier }|Tokens]);
|
||||
[Check|T] ->
|
||||
case handle_terminator(File, Check, Terminators) of
|
||||
case handle_terminator(Check, Scope) of
|
||||
{ error, _ } = Error -> Error;
|
||||
New -> tokenize(Rest, Line, File, New, [Check|T])
|
||||
New -> tokenize(Rest, Line, New, [Check|T])
|
||||
end
|
||||
end;
|
||||
|
||||
% Ambiguous unary/binary operators tokens
|
||||
|
||||
tokenize([Space,Sign,NotMarker|T], Line, File, Terminators, [{Identifier,_,_} = H|Tokens]) when Sign == $+ orelse Sign == $-,
|
||||
tokenize([Space, Sign, NotMarker|T], Line, Scope, [{ Identifier, _, _ } = H|Tokens]) when
|
||||
Sign == $+ orelse Sign == $-,
|
||||
Space == $\s orelse Space == $\t,
|
||||
NotMarker /= $\s, NotMarker /= $\t, NotMarker /= $\r,
|
||||
NotMarker /= $\n, NotMarker /= $:, NotMarker /= $(,
|
||||
NotMarker /= $+, NotMarker /= $-, NotMarker /= $>,
|
||||
Identifier == identifier orelse Identifier == punctuated_identifier ->
|
||||
Rest = [NotMarker|T],
|
||||
tokenize(Rest, Line, File, Terminators, [{list_to_atom([Sign]),Line},setelement(1, H, op_identifier)|Tokens]);
|
||||
tokenize(Rest, Line, Scope, [{ list_to_atom([Sign]), Line }, setelement(1, H, op_identifier)|Tokens]);
|
||||
|
||||
% Spaces
|
||||
|
||||
tokenize([T|Rest], Line, File, Terminators, Tokens) when T == $\s; T == $\r; T == $\t ->
|
||||
tokenize(Rest, Line, File, Terminators, Tokens);
|
||||
tokenize([T|Rest], Line, Scope, Tokens) when T == $\s; T == $\r; T == $\t ->
|
||||
tokenize(Rest, Line, Scope, Tokens);
|
||||
|
||||
tokenize([{line,Line}|Rest], _, File, Terminators, Tokens) ->
|
||||
tokenize(Rest, Line, File, Terminators, Tokens);
|
||||
tokenize([{line,Line}|Rest], _Line, Scope, Tokens) ->
|
||||
tokenize(Rest, Line, Scope, Tokens);
|
||||
|
||||
tokenize(T, Line, _File, _Termiantors, _Tokens) ->
|
||||
tokenize(T, Line, _Scope, _Tokens) ->
|
||||
{ error, { Line, "invalid token: ", until_eol(T) } }.
|
||||
|
||||
until_eol("\r\n" ++ _) -> [];
|
||||
@@ -344,58 +397,67 @@ until_eol([H|T]) -> [H|until_eol(T)].
|
||||
|
||||
%% Handlers
|
||||
|
||||
handle_heredocs(Line, File, Terminators, H, T, Tokens) ->
|
||||
handle_heredocs(T, Line, H, #scope{file=File} = Scope, Tokens) ->
|
||||
case extract_heredoc_with_interpolation(Line, File, true, T, H) of
|
||||
{ error, _ } = Error ->
|
||||
Error;
|
||||
{ Parts, Rest } ->
|
||||
Token = { string_type(H), Line, unescape_tokens(Parts) },
|
||||
tokenize(Rest, Line, File, Terminators, [Token|Tokens])
|
||||
tokenize(Rest, Line, Scope, [Token|Tokens])
|
||||
end.
|
||||
|
||||
handle_strings(Line, File, Terminators, H, T, Tokens) ->
|
||||
handle_strings(T, Line, H, #scope{file=File} = Scope, Tokens) ->
|
||||
case elixir_interpolation:extract(Line, File, true, T, H) of
|
||||
{ NewLine, Parts, [$:|Rest] } ->
|
||||
{ NewLine, Parts, [$:|Rest] } when hd(Rest) /= $: ->
|
||||
case Parts of
|
||||
[Bin] when is_binary(Bin) ->
|
||||
Atom = binary_to_atom(unescape_chars(Bin), utf8),
|
||||
tokenize(Rest, NewLine, File, Terminators, [{kw_identifier,Line,Atom}|Tokens]);
|
||||
Atom = unsafe_to_atom(unescape_chars(Bin), Scope),
|
||||
tokenize(Rest, NewLine, Scope, [{ kw_identifier, Line, Atom }|Tokens]);
|
||||
_ ->
|
||||
{ error, { Line, "invalid interpolation in key", [$"|T] } }
|
||||
end;
|
||||
{ NewLine, Parts, Rest } ->
|
||||
Token = { string_type(H),Line,unescape_tokens(Parts) },
|
||||
tokenize(Rest, NewLine, File, Terminators, [Token|Tokens]);
|
||||
tokenize(Rest, NewLine, Scope, [Token|Tokens]);
|
||||
Error ->
|
||||
interpolation_error(Error, " (for string starting at line ~B)", [Line])
|
||||
end.
|
||||
|
||||
handle_comp_op(Line, File, Terminators, Op, [$:,S|Rest], Tokens) when ?is_space(S) ->
|
||||
tokenize(Rest, Line, File, Terminators, [{kw_identifier, Line, Op}|Tokens]);
|
||||
handle_comp_op([$:|Rest], Line, Op, Scope, Tokens) when hd(Rest) /= $: ->
|
||||
verify_kw_and_space(Line, Op, Rest, Scope),
|
||||
tokenize(Rest, Line, Scope, [{ kw_identifier, Line, Op }|Tokens]);
|
||||
|
||||
handle_comp_op(Line, File, Terminators, Op, Rest, Tokens) ->
|
||||
tokenize(Rest, Line, File, Terminators, add_token_with_nl({comp_op, Line, Op}, Tokens)).
|
||||
handle_comp_op(Rest, Line, Op, Scope, Tokens) ->
|
||||
tokenize(Rest, Line, Scope, add_token_with_nl({ comp_op, Line, Op }, Tokens)).
|
||||
|
||||
handle_op(Line, File, Terminators, Op, [$:,S|Rest], Tokens, _) when ?is_space(S) ->
|
||||
tokenize(Rest, Line, File, Terminators, [{kw_identifier, Line, Op}|Tokens]);
|
||||
handle_op([$:|Rest], Line, Op, Scope, Tokens) when hd(Rest) /= $: ->
|
||||
verify_kw_and_space(Line, Op, Rest, Scope),
|
||||
tokenize(Rest, Line, Scope, [{ kw_identifier, Line, Op }|Tokens]);
|
||||
|
||||
handle_op(Line, File, Terminators, Op, Rest, Tokens, 1) ->
|
||||
tokenize(Rest, Line, File, Terminators, [{Op, Line}|Tokens]);
|
||||
handle_op(Rest, Line, Op, Scope, Tokens) when ?unary_op(Op) ->
|
||||
tokenize(Rest, Line, Scope, [{ Op, Line }|Tokens]);
|
||||
|
||||
handle_op(Line, File, Terminators, Op, Rest, Tokens, _) ->
|
||||
tokenize(Rest, Line, File, Terminators, add_token_with_nl({Op, Line}, Tokens)).
|
||||
handle_op(Rest, Line, Op, Scope, Tokens) ->
|
||||
tokenize(Rest, Line, Scope, add_token_with_nl({ Op, Line }, Tokens)).
|
||||
|
||||
handle_call_identifier(Line, File, Terminators, Op, Rest, Tokens) ->
|
||||
handle_call_identifier(Rest, Line, Op, Scope, Tokens) ->
|
||||
Token = tokenize_call_identifier(identifier, Line, Op, Rest),
|
||||
tokenize(Rest, Line, File, Terminators, [Token|add_token_with_nl({'.',Line}, Tokens)]).
|
||||
tokenize(Rest, Line, Scope, [Token|add_token_with_nl({ '.', Line }, Tokens)]).
|
||||
|
||||
%% Helpers
|
||||
|
||||
eol(Line, Mod, Tokens) ->
|
||||
case Tokens of
|
||||
[{eol,_,_}|_] -> Tokens;
|
||||
_ -> [{eol,Line,Mod}|Tokens]
|
||||
end.
|
||||
eol(_Line, _Mod, [{',',_}|_] = Tokens) -> Tokens;
|
||||
eol(_Line, _Mod, [{eol,_,_}|_] = Tokens) -> Tokens;
|
||||
eol(Line, Mod, Tokens) -> [{eol,Line,Mod}|Tokens].
|
||||
|
||||
unsafe_to_atom(Binary, #scope{existing_atoms_only=true}) when is_binary(Binary) ->
|
||||
binary_to_existing_atom(Binary, utf8);
|
||||
unsafe_to_atom(Binary, #scope{}) when is_binary(Binary) ->
|
||||
binary_to_atom(Binary, utf8);
|
||||
unsafe_to_atom(List, #scope{existing_atoms_only=true}) when is_list(List) ->
|
||||
list_to_existing_atom(List);
|
||||
unsafe_to_atom(List, #scope{}) when is_list(List) ->
|
||||
list_to_atom(List).
|
||||
|
||||
collect_modifiers([H|T], Buffer) when ?is_downcase(H) ->
|
||||
collect_modifiers(T, [H|Buffer]);
|
||||
@@ -583,32 +645,33 @@ tokenize_identifier(Rest, Acc, _Marker) ->
|
||||
{ Rest, lists:reverse(Acc) }.
|
||||
|
||||
% Tokenize atom identifier, which also accepts punctuated identifiers
|
||||
tokenize_atom(String, Acc) ->
|
||||
tokenize_atom(String, Acc, Scope) ->
|
||||
{ Rest, Identifier } = tokenize_identifier(String, Acc, atom),
|
||||
case Rest of
|
||||
[H|T] when H == $?; H == $! ->
|
||||
{ T, ?ELIXIR_ATOM_CONCAT([Identifier, [H]]) };
|
||||
{ T, unsafe_to_atom(Identifier ++ [H], Scope) };
|
||||
_ ->
|
||||
{ Rest, list_to_atom(Identifier) }
|
||||
{ Rest, unsafe_to_atom(Identifier, Scope) }
|
||||
end.
|
||||
|
||||
% Tokenize any identifier, handling kv, punctuated, paren, bracket and do identifiers.
|
||||
tokenize_any_identifier(Line, File, String, Acc) ->
|
||||
tokenize_any_identifier(String, Line, Acc, Scope) ->
|
||||
{ Rest, Identifier } = tokenize_identifier(String, Acc, false),
|
||||
|
||||
case Rest of
|
||||
[H,$:|T] when H == $?; H == $! ->
|
||||
Atom = ?ELIXIR_ATOM_CONCAT([Identifier, [H]]),
|
||||
verify_kw_and_space(Line, File, Atom, T),
|
||||
[H,$:|T] when H == $? orelse H == $!, hd(T) /= $: ->
|
||||
Atom = unsafe_to_atom(Identifier ++ [H], Scope),
|
||||
verify_kw_and_space(Line, Atom, T, Scope),
|
||||
{ T, { kw_identifier, Line, Atom } };
|
||||
[H|T] when H == $?; H == $! ->
|
||||
Atom = ?ELIXIR_ATOM_CONCAT([Identifier, [H]]),
|
||||
Atom = unsafe_to_atom(Identifier ++ [H], Scope),
|
||||
{ T, tokenize_call_identifier(punctuated_identifier, Line, Atom, T) };
|
||||
[$:|T] ->
|
||||
Atom = list_to_atom(Identifier),
|
||||
verify_kw_and_space(Line, File, Atom, T),
|
||||
[$:|T] when hd(T) /= $: ->
|
||||
Atom = unsafe_to_atom(Identifier, Scope),
|
||||
verify_kw_and_space(Line, Atom, T, Scope),
|
||||
{ T, { kw_identifier, Line, Atom } };
|
||||
_ ->
|
||||
{ Rest, tokenize_call_identifier(identifier, Line, list_to_atom(Identifier), Rest) }
|
||||
{ Rest, tokenize_call_identifier(identifier, Line, unsafe_to_atom(Identifier, Scope), Rest) }
|
||||
end.
|
||||
|
||||
tokenize_call_identifier(Kind, Line, Atom, Rest) ->
|
||||
@@ -617,13 +680,13 @@ tokenize_call_identifier(Kind, Line, Atom, Rest) ->
|
||||
[$[|_] -> { bracket_identifier, Line, Atom };
|
||||
_ ->
|
||||
case next_is_block(Rest) of
|
||||
[] -> { Kind, Line, Atom };
|
||||
false -> { Kind, Line, Atom };
|
||||
BlockIdentifier -> { BlockIdentifier, Line, Atom }
|
||||
end
|
||||
end.
|
||||
|
||||
verify_kw_and_space(_Line, _File, _Atom, [H|_]) when ?is_space(H) -> ok;
|
||||
verify_kw_and_space(Line, File, Atom, _) ->
|
||||
verify_kw_and_space(_Line, _Atom, [H|_], #scope{}) when ?is_space(H) -> ok;
|
||||
verify_kw_and_space(Line, Atom, _, #scope{file=File}) ->
|
||||
io:format("~ts:~w: keyword argument ~s: must be followed by space~n", [File, Line, Atom]).
|
||||
|
||||
next_is_block([Space|Tokens]) when Space == $\t; Space == $\s ->
|
||||
@@ -631,13 +694,13 @@ next_is_block([Space|Tokens]) when Space == $\t; Space == $\s ->
|
||||
|
||||
next_is_block([$d,$o,H|_]) when
|
||||
?is_digit(H); ?is_upcase(H); ?is_downcase(H); H == $_; H == $: ->
|
||||
[];
|
||||
false;
|
||||
|
||||
next_is_block([$d,$o|_]) ->
|
||||
do_identifier;
|
||||
|
||||
next_is_block(_) ->
|
||||
[].
|
||||
false.
|
||||
|
||||
add_token_with_nl(Left, [{eol,_,$\n}|T]) -> [Left|T];
|
||||
add_token_with_nl(Left, T) -> [Left|T].
|
||||
@@ -658,17 +721,18 @@ sigil_terminator(${) -> $};
|
||||
sigil_terminator($<) -> $>;
|
||||
sigil_terminator(O) -> O.
|
||||
|
||||
%% In case File is "__internal__", we don't
|
||||
%% do any of the terminator checks
|
||||
handle_terminator(_, #scope{check_terminators=false} = Scope) ->
|
||||
Scope;
|
||||
handle_terminator(Token, #scope{terminators=Terminators} = Scope) ->
|
||||
case check_terminator(Token, Terminators) of
|
||||
{ error, _ } = Error -> Error;
|
||||
New -> Scope#scope{terminators=New}
|
||||
end.
|
||||
|
||||
handle_terminator(<<"__internal__">>, _, []) -> [];
|
||||
handle_terminator(_File, Token, Terminator) ->
|
||||
handle_terminator(Token, Terminator).
|
||||
|
||||
handle_terminator({ S, Line }, Terminators) when S == 'fn'; S == 'fn_paren' ->
|
||||
check_terminator({ S, Line }, Terminators) when S == 'fn'; S == 'fn_paren' ->
|
||||
[{ fn, Line }|Terminators];
|
||||
|
||||
handle_terminator({ S, _ } = New, Terminators) when
|
||||
check_terminator({ S, _ } = New, Terminators) when
|
||||
S == 'do';
|
||||
S == '(';
|
||||
S == '[';
|
||||
@@ -676,7 +740,7 @@ handle_terminator({ S, _ } = New, Terminators) when
|
||||
S == '<<' ->
|
||||
[New|Terminators];
|
||||
|
||||
handle_terminator({ E, _ }, [{ S, _ }|Terminators]) when
|
||||
check_terminator({ E, _ }, [{ S, _ }|Terminators]) when
|
||||
S == 'do', E == 'end';
|
||||
S == 'fn', E == 'end';
|
||||
S == '(', E == ')';
|
||||
@@ -685,17 +749,17 @@ handle_terminator({ E, _ }, [{ S, _ }|Terminators]) when
|
||||
S == '<<', E == '>>' ->
|
||||
Terminators;
|
||||
|
||||
handle_terminator({ E, Line }, [{ Start, StartLine }|_]) when
|
||||
check_terminator({ E, Line }, [{ Start, StartLine }|_]) when
|
||||
E == 'end'; E == ')'; E == ']'; E == '}'; E == '>>' ->
|
||||
End = terminator(Start),
|
||||
Message = io_lib:format("missing terminator: ~s (for \"~s\" starting at line ~B)", [End, Start, StartLine]),
|
||||
{ error, { Line, Message, [] } };
|
||||
|
||||
handle_terminator({ E, Line }, []) when
|
||||
check_terminator({ E, Line }, []) when
|
||||
E == 'end'; E == ')'; E == ']'; E == '}'; E == '>>' ->
|
||||
{ error, { Line, "unexpected token: ", atom_to_list(E) } };
|
||||
|
||||
handle_terminator(_, Terminators) ->
|
||||
check_terminator(_, Terminators) ->
|
||||
Terminators.
|
||||
|
||||
terminator('fn') -> 'end';
|
||||
@@ -706,10 +770,16 @@ terminator('{') -> '}';
|
||||
terminator('<<') -> '>>'.
|
||||
|
||||
% Keywords check
|
||||
keyword(Line, paren_identifier, fn, Tokens) ->
|
||||
handle_keyword(Line, paren_identifier, fn, Tokens) ->
|
||||
[{ 'fn_paren', Line }|Tokens];
|
||||
|
||||
keyword(Line, Identifier, Atom, Tokens) when Identifier == identifier; Identifier == do_identifier ->
|
||||
handle_keyword(Line, Identifier, Atom, Tokens) when Identifier == bracket_identifier; Identifier == paren_identifier ->
|
||||
case keyword(Atom) of
|
||||
true -> [{ Atom, Line }|Tokens];
|
||||
_ -> [{ Identifier, Line, Atom }|Tokens]
|
||||
end;
|
||||
|
||||
handle_keyword(Line, Identifier, Atom, Tokens) when Identifier == identifier; Identifier == do_identifier ->
|
||||
case keyword(Atom) of
|
||||
true -> [{ Atom, Line }|Tokens];
|
||||
op -> add_token_with_nl({ Atom, Line }, Tokens);
|
||||
@@ -717,7 +787,7 @@ keyword(Line, Identifier, Atom, Tokens) when Identifier == identifier; Identifi
|
||||
false -> false
|
||||
end;
|
||||
|
||||
keyword(_, _, _, _) -> false.
|
||||
handle_keyword(_, _, _, _) -> false.
|
||||
|
||||
% Keywords
|
||||
keyword('fn') -> true;
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
%% Main entry point for translations. Are macros that cannot be
|
||||
%% overriden are defined in this file.
|
||||
-module(elixir_translator).
|
||||
-export([translate/2, translate_each/2, translate_args/2, translate_apply/7, translate_fn/3, raw_forms/3, forms/3]).
|
||||
-export([forms/4, 'forms!'/4]).
|
||||
-export([translate/2, translate_each/2, translate_arg/2,
|
||||
translate_args/2, translate_apply/7, translate_fn/3]).
|
||||
-import(elixir_scope, [umergev/2, umergec/2]).
|
||||
-import(elixir_errors, [syntax_error/3, syntax_error/4, parse_error/4,
|
||||
assert_function_scope/3, assert_module_scope/3, assert_no_guard_scope/3,
|
||||
@@ -9,8 +11,8 @@
|
||||
-include("elixir.hrl").
|
||||
-compile({parse_transform, elixir_transform}).
|
||||
|
||||
raw_forms(String, StartLine, File) ->
|
||||
try elixir_tokenizer:tokenize(String, StartLine, File) of
|
||||
forms(String, StartLine, File, Opts) ->
|
||||
try elixir_tokenizer:tokenize(String, StartLine, [{ file, File }|Opts]) of
|
||||
{ ok, Tokens } ->
|
||||
case elixir_parser:parse(Tokens) of
|
||||
{ ok, Forms } -> { ok, Forms };
|
||||
@@ -21,8 +23,8 @@ raw_forms(String, StartLine, File) ->
|
||||
{ interpolation_error, { _, _, _ } = Tuple} -> { error, Tuple }
|
||||
end.
|
||||
|
||||
forms(String, StartLine, File) ->
|
||||
case raw_forms(String, StartLine, File) of
|
||||
'forms!'(String, StartLine, File, Opts) ->
|
||||
case forms(String, StartLine, File, Opts) of
|
||||
{ ok, Forms } -> Forms;
|
||||
{ error, { Line, Error, Token } } -> parse_error(Line, File, Error, Token)
|
||||
end.
|
||||
@@ -42,6 +44,11 @@ translate_each({'=', Line, [Left, Right]}, S) ->
|
||||
{ TLeft, SL } = elixir_clauses:assigns(fun translate_each/2, Left, SR),
|
||||
{ { match, Line, TLeft, TRight }, SL };
|
||||
|
||||
%% Containers
|
||||
|
||||
translate_each({ C, _, _ } = Original, S) when C == '[]'; C == '{}'; C == '<<>>' ->
|
||||
elixir_literal:translate(Original, S);
|
||||
|
||||
%% Blocks and scope rewriters
|
||||
|
||||
translate_each({ '__block__', Line, [] }, S) ->
|
||||
@@ -54,7 +61,7 @@ translate_each({ '__block__', Line, Args }, S) when is_list(Args) ->
|
||||
{ TArgs, NS } = translate(Args, S),
|
||||
{ { block, Line, TArgs }, NS };
|
||||
|
||||
translate_each({ '__scope__', _Line, [[{file,File}],[{do,Expr}]] }, S) ->
|
||||
translate_each({ '__scope__', _Line, [[{file,File}],Expr] }, S) ->
|
||||
Old = S#elixir_scope.file,
|
||||
{ TExpr, TS } = translate_each(Expr, S#elixir_scope{file=File}),
|
||||
{ TExpr, TS#elixir_scope{file=Old} };
|
||||
@@ -72,7 +79,7 @@ translate_each({ '__op__', Line, [Op, Left, Right] }, S) when is_atom(Op) ->
|
||||
translate_each({ '__ambiguousop__', Line, [Var, H|T] }, S) ->
|
||||
{ Name, _, _ } = Var,
|
||||
|
||||
case dict:find(Name, S#elixir_scope.vars) of
|
||||
case orddict:find(Name, S#elixir_scope.vars) of
|
||||
error -> translate_each({ Name, Line, [H|T] }, S);
|
||||
_ ->
|
||||
case T of
|
||||
@@ -81,45 +88,25 @@ translate_each({ '__ambiguousop__', Line, [Var, H|T] }, S) ->
|
||||
end
|
||||
end;
|
||||
|
||||
%% Containers
|
||||
|
||||
translate_each({ '<<>>', Line, Args }, S) when is_list(Args) ->
|
||||
case S#elixir_scope.context of
|
||||
assign ->
|
||||
elixir_tree_helpers:build_bitstr(fun translate_each/2, Args, Line, S);
|
||||
_ ->
|
||||
{ TArgs, { SC, SV } } = elixir_tree_helpers:build_bitstr(fun translate_arg/2, Args, Line, { S, S }),
|
||||
{ TArgs, umergec(SV, SC) }
|
||||
end;
|
||||
|
||||
translate_each({ '{}', Line, Args }, S) when is_list(Args) ->
|
||||
{ TArgs, SE } = translate_args(Args, S),
|
||||
{ {tuple, Line, TArgs}, SE };
|
||||
|
||||
translate_each({ '[]', _Line, Args }, S) when is_list(Args) ->
|
||||
translate_each(Args, S);
|
||||
|
||||
%% Lexical
|
||||
|
||||
translate_each({ refer, Line, Args }, S) ->
|
||||
elixir_errors:deprecation(Line, S#elixir_scope.file, "refer is deprecated, please use alias instead"),
|
||||
translate_each({ alias, Line, Args }, S);
|
||||
|
||||
translate_each({ alias, Line, [Ref] }, S) ->
|
||||
translate_each({ alias, Line, [Ref,[]] }, S);
|
||||
|
||||
translate_each({ alias, Line, [Ref, KV] }, S) ->
|
||||
assert_no_assign_or_guard_scope(Line, alias, S),
|
||||
validate_opts(Line, alias, [as], KV, S),
|
||||
|
||||
{ TRef, SR } = translate_each(Ref, S),
|
||||
|
||||
case TRef of
|
||||
{ atom, _, Old } ->
|
||||
{ New, SF } = case orddict:find(as, KV) of
|
||||
{ ok, false } ->
|
||||
{ New, SF } = case lists:keyfind(as, 1, KV) of
|
||||
Opt when Opt == { as, true }; Opt == false ->
|
||||
{ elixir_aliases:last(Old), SR };
|
||||
{ as, false } ->
|
||||
{ Old, SR };
|
||||
Opt when Opt == { ok, true }; Opt == error ->
|
||||
{ elixir_aliases:last(Old), SR };
|
||||
{ ok, Other } ->
|
||||
{ as, Other } ->
|
||||
{ TOther, SA } = translate_each(Other, SR#elixir_scope{aliases=[]}),
|
||||
case TOther of
|
||||
{ atom, _, Atom } -> { Atom, SA };
|
||||
@@ -152,11 +139,13 @@ translate_each({ require, Line, [Ref] }, S) ->
|
||||
|
||||
translate_each({ require, Line, [Ref, KV] }, S) ->
|
||||
assert_no_assign_or_guard_scope(Line, 'require', S),
|
||||
validate_opts(Line, require, [as], KV, S),
|
||||
|
||||
{ TRef, SR } = translate_each(Ref, S),
|
||||
|
||||
As = case orddict:find(as, KV) of
|
||||
{ ok, Value } -> Value;
|
||||
error -> false
|
||||
As = case lists:keyfind(as, 1, KV) of
|
||||
false -> false;
|
||||
{ as, Value } -> Value
|
||||
end,
|
||||
|
||||
case TRef of
|
||||
@@ -176,7 +165,7 @@ translate_each({ import, Line, [Left] }, S) ->
|
||||
translate_each({ import, Line, [Left, []]}, S);
|
||||
|
||||
translate_each({ import, Line, [Left,Opts] }, S) when is_list(Opts) ->
|
||||
translate_each({ import, Line, [all, Left, Opts]}, S);
|
||||
translate_each({ import, Line, [default, Left, Opts]}, S);
|
||||
|
||||
translate_each({ import, Line, [Selector, Left] }, S) ->
|
||||
translate_each({ import, Line, [Selector, Left, []]}, S);
|
||||
@@ -188,22 +177,19 @@ translate_each({ import, Line, [Left, Right, Opts] }, S) ->
|
||||
|
||||
Selector = case TSelector of
|
||||
{ atom, _, SelectorAtom } -> SelectorAtom;
|
||||
_ -> syntax_error(Line, S#elixir_scope.file, "invalid selector for import")
|
||||
_ -> syntax_error(Line, S#elixir_scope.file, "invalid selector for import, expected an atom")
|
||||
end,
|
||||
|
||||
Ref = case TRef of
|
||||
{ atom, _, RefAtom } -> RefAtom;
|
||||
_ -> syntax_error(Line, S#elixir_scope.file, "invalid name for import")
|
||||
_ -> syntax_error(Line, S#elixir_scope.file, "invalid name for import, expected an atom or alias")
|
||||
end,
|
||||
|
||||
case is_list(Opts) of
|
||||
true -> [];
|
||||
_ -> syntax_error(Line, S#elixir_scope.file, "invalid options for import")
|
||||
end,
|
||||
validate_opts(Line, import, [as, only, except], Opts, S),
|
||||
|
||||
As = case orddict:find(as, Opts) of
|
||||
{ ok, Value } -> Value;
|
||||
error -> false
|
||||
As = case lists:keyfind(as, 1, Opts) of
|
||||
false -> false;
|
||||
{ as, Value } -> Value
|
||||
end,
|
||||
|
||||
elixir_aliases:ensure_loaded(Line, Ref, SR),
|
||||
@@ -224,21 +210,6 @@ translate_each({ '__ENV__', Line, Atom }, S) when is_atom(Atom) ->
|
||||
translate_each({ '__CALLER__', Line, Atom }, S) when is_atom(Atom) ->
|
||||
{ { var, Line, '__CALLER__' }, S#elixir_scope{caller=true} };
|
||||
|
||||
%% Arg-less deprecated macros
|
||||
|
||||
translate_each({'__FUNCTION__', Line, Atom}, S) when is_atom(Atom) ->
|
||||
elixir_errors:deprecation(Line, S#elixir_scope.file, "__FUNCTION__ is deprecated, use __ENV__.function instead"),
|
||||
case S#elixir_scope.function of
|
||||
nil ->
|
||||
{ { atom, Line, nil }, S };
|
||||
{ Name, Arity } ->
|
||||
{ { tuple, Line, [ { atom, Line, Name }, { integer, Line, Arity } ] }, S }
|
||||
end;
|
||||
|
||||
translate_each({'__LINE__', Line, Atom}, S) when is_atom(Atom) ->
|
||||
elixir_errors:deprecation(Line, S#elixir_scope.file, "__LINE__ is deprecated, use __ENV__.line instead"),
|
||||
{ { integer, Line, Line }, S };
|
||||
|
||||
%% Aliases
|
||||
|
||||
translate_each({ '__aliases__', Line, [H] }, S) when H /= 'Elixir' ->
|
||||
@@ -267,75 +238,89 @@ translate_each({ '__aliases__', Line, [H|T] }, S) ->
|
||||
|
||||
%% Quoting
|
||||
|
||||
translate_each({ Unquote, Line, _Args }, S) when Unquote == unquote; Unquote == unquote_splicing ->
|
||||
syntax_error(Line, S#elixir_scope.file, "~p called outside quote", [Unquote]);
|
||||
|
||||
translate_each({ quote, Line, [Left, Right] }, S) ->
|
||||
translate_each({ quote, Line, [orddict:from_list(Left ++ Right)] }, S);
|
||||
|
||||
translate_each({ quote, GivenLine, [[{do,Exprs}|T]] }, S) ->
|
||||
Marker = case orddict:find(hygiene, T) of
|
||||
{ ok, false } -> nil;
|
||||
translate_each({ quote, GivenLine, [T] }, S) when is_list(T) ->
|
||||
Exprs =
|
||||
case lists:keyfind(do, 1, T) of
|
||||
{do, E} -> E;
|
||||
false ->
|
||||
syntax_error(GivenLine, S#elixir_scope.file, "invalid args for quote")
|
||||
end,
|
||||
|
||||
Marker = case lists:keyfind(hygiene, 1, T) of
|
||||
{ hygiene, false } -> nil;
|
||||
_ -> quoted
|
||||
end,
|
||||
|
||||
{ DefaultLine, WrappedExprs } = case orddict:find(location, T) of
|
||||
{ ok, keep } ->
|
||||
Scoped = { '__scope__', GivenLine, [[{file,S#elixir_scope.file}],[{do,Exprs}]] },
|
||||
{ DefaultLine, WrappedExprs } = case lists:keyfind(location, 1, T) of
|
||||
{ location, keep } ->
|
||||
Scoped = { '__scope__', GivenLine, [[{file,S#elixir_scope.file}],Exprs] },
|
||||
{ keep, Scoped };
|
||||
_ ->
|
||||
{ 0, Exprs}
|
||||
end,
|
||||
|
||||
Line = case orddict:find(line, T) of
|
||||
{ ok, keep } -> keep;
|
||||
{ ok, Value } when is_integer(Value) -> Value;
|
||||
{ ok, _ } -> syntax_error(GivenLine, S#elixir_scope.file,
|
||||
"invalid args for quote. expected line to be the atom :keep or an integer");
|
||||
Line = case lists:keyfind(line, 1, T) of
|
||||
{ line, Value } -> Value;
|
||||
_ -> DefaultLine
|
||||
end,
|
||||
|
||||
Unquote = case orddict:find(unquote, T) of
|
||||
{ ok, false } -> false;
|
||||
{ TLine, SL } = case Line of
|
||||
keep -> { keep, S };
|
||||
_ -> translate_each(Line, S)
|
||||
end,
|
||||
|
||||
Unquote = case lists:keyfind(unquote, 1, T) of
|
||||
{ unquote, false } -> false;
|
||||
_ -> true
|
||||
end,
|
||||
|
||||
elixir_quote:quote(WrappedExprs, #elixir_quote{marker=Marker, line=Line, unquote=Unquote}, S);
|
||||
|
||||
translate_each({ quote, Line, [_] }, S) ->
|
||||
syntax_error(Line, S#elixir_scope.file, "invalid args for quote");
|
||||
|
||||
translate_each({ in_guard, Line, [[{do,Guard},{else,Else}]] }, S) ->
|
||||
elixir_errors:deprecation(Line, S#elixir_scope.file, "in_guard is deprecated, check __CALLER__.in_guard? instead"),
|
||||
case S#elixir_scope.context of
|
||||
guard -> translate_each(Guard, S);
|
||||
_ -> translate_each(Else, S)
|
||||
end;
|
||||
elixir_quote:quote(WrappedExprs, #elixir_quote{marker=Marker, line=TLine, unquote=Unquote}, SL);
|
||||
|
||||
translate_each({ quote, GivenLine, [_] }, S) ->
|
||||
syntax_error(GivenLine, S#elixir_scope.file, "invalid args for quote");
|
||||
|
||||
%% Functions
|
||||
|
||||
translate_each({ fn, Line, [[{do, { '->', _, Pairs }}]] }, S) ->
|
||||
assert_no_assign_or_guard_scope(Line, 'fn', S),
|
||||
translate_fn(Line, Pairs, S);
|
||||
|
||||
%% Super
|
||||
%% Comprehensions
|
||||
|
||||
translate_each({ super, Line, Args }, #elixir_scope{file=File} = S) ->
|
||||
assert_no_assign_or_guard_scope(Line, 'super', S),
|
||||
Module = assert_module_scope(Line, super, S),
|
||||
Function = assert_function_scope(Line, super, S),
|
||||
elixir_def_overridable:ensure_defined(Line, Module, Function, S),
|
||||
translate_each({ Kind, Line, Args }, S) when is_list(Args), (Kind == lc) orelse (Kind == bc) ->
|
||||
translate_comprehension(Line, Kind, Args, S);
|
||||
|
||||
{ _, Arity } = Function,
|
||||
%% Super (exceptionally supports partial application)
|
||||
|
||||
{ TArgs, TS } = if
|
||||
is_atom(Args) ->
|
||||
elixir_def_overridable:retrieve_args(Line, Arity, S);
|
||||
length(Args) == Arity ->
|
||||
translate_args(Args, S);
|
||||
true ->
|
||||
syntax_error(Line, File, "super must be called with the same number of arguments as the current function")
|
||||
end,
|
||||
translate_each({ super, Line, Args } = Original, S) ->
|
||||
case elixir_partials:handle(Original, S) of
|
||||
error ->
|
||||
assert_no_assign_or_guard_scope(Line, super, S),
|
||||
Module = assert_module_scope(Line, super, S),
|
||||
Function = assert_function_scope(Line, super, S),
|
||||
elixir_def_overridable:ensure_defined(Line, Module, Function, S),
|
||||
|
||||
Super = elixir_def_overridable:name(Module, Function),
|
||||
{ { call, Line, { atom, Line, Super }, TArgs }, TS#elixir_scope{super=true} };
|
||||
{ _, Arity } = Function,
|
||||
|
||||
{ TArgs, TS } = if
|
||||
is_atom(Args) ->
|
||||
elixir_def_overridable:retrieve_args(Line, Arity, S);
|
||||
length(Args) == Arity ->
|
||||
translate_args(Args, S);
|
||||
true ->
|
||||
syntax_error(Line, S#elixir_scope.file, "super must be called with the same number of arguments as the current function")
|
||||
end,
|
||||
|
||||
Super = elixir_def_overridable:name(Module, Function),
|
||||
{ { call, Line, { atom, Line, Super }, TArgs }, TS#elixir_scope{super=true} };
|
||||
Else -> Else
|
||||
end;
|
||||
|
||||
translate_each({ 'super?', Line, [] }, S) ->
|
||||
Module = assert_module_scope(Line, 'super?', S),
|
||||
@@ -343,25 +328,23 @@ translate_each({ 'super?', Line, [] }, S) ->
|
||||
Bool = elixir_def_overridable:is_defined(Module, Function),
|
||||
{ { atom, Line, Bool }, S };
|
||||
|
||||
%% Comprehensions
|
||||
|
||||
translate_each({ Kind, Line, Args }, S) when is_list(Args), (Kind == lc) orelse (Kind == bc) ->
|
||||
translate_comprehension(Line, Kind, Args, S);
|
||||
|
||||
%% Variables
|
||||
|
||||
translate_each({ '^', Line, [ { Name, _, Args } ] }, S) ->
|
||||
Result = case is_atom(Args) of
|
||||
true ->
|
||||
case S#elixir_scope.context of
|
||||
assign ->
|
||||
case dict:find(Name, S#elixir_scope.vars) of
|
||||
error -> "unbound variable ^~s";
|
||||
{ ok, Value } -> { {var, Line, Value}, S }
|
||||
end;
|
||||
_ -> "cannot access variable ^~s outside of assignment"
|
||||
Dict = case Args of
|
||||
nil -> S#elixir_scope.vars;
|
||||
quoted -> S#elixir_scope.quote_vars;
|
||||
_ ->
|
||||
syntax_error(Line, S#elixir_scope.file, "cannot use ^ with expression at ^~s, ^ must be used only with variables", [Name])
|
||||
end,
|
||||
|
||||
Result = case S#elixir_scope.context of
|
||||
assign ->
|
||||
case orddict:find(Name, Dict) of
|
||||
error -> "unbound variable ^~s";
|
||||
{ ok, Value } -> { {var, Line, Value}, S }
|
||||
end;
|
||||
false -> "cannot use ^ with expression at ^~s, ^ must be used only with variables"
|
||||
_ -> "cannot access variable ^~s outside of assignment"
|
||||
end,
|
||||
|
||||
case is_list(Result) of
|
||||
@@ -373,7 +356,7 @@ translate_each({ '^', Line, [ { Name, _, Args } ] }, S) ->
|
||||
|
||||
translate_each({ Name, Line, quoted }, S) when is_atom(Name) ->
|
||||
NewS = S#elixir_scope{vars=S#elixir_scope.quote_vars,noname=true},
|
||||
{ TVar, VS } = elixir_scope:translate_var(Line, Name, NewS),
|
||||
{ TVar, VS } = elixir_scope:translate_var(Line, Name, quoted, NewS),
|
||||
{ TVar, VS#elixir_scope{
|
||||
quote_vars=VS#elixir_scope.vars,
|
||||
noname=S#elixir_scope.noname,
|
||||
@@ -381,20 +364,25 @@ translate_each({ Name, Line, quoted }, S) when is_atom(Name) ->
|
||||
} };
|
||||
|
||||
translate_each({ Name, Line, nil }, S) when is_atom(Name) ->
|
||||
elixir_scope:translate_var(Line, Name, S);
|
||||
elixir_scope:translate_var(Line, Name, nil, S);
|
||||
|
||||
%% Local calls
|
||||
|
||||
translate_each({ Atom, Line, Args } = Original, S) when is_atom(Atom) ->
|
||||
case sequential_partials(Args, 1) andalso
|
||||
case elixir_partials:is_sequential(Args) andalso
|
||||
elixir_dispatch:import_function(Line, Atom, length(Args), S) of
|
||||
false ->
|
||||
case handle_partials(Line, Original, S) of
|
||||
case elixir_partials:handle(Original, S) of
|
||||
error ->
|
||||
Callback = fun() ->
|
||||
case S#elixir_scope.context of
|
||||
guard -> syntax_error(Line, S#elixir_scope.file,
|
||||
"cannot invoke local ~s/~B inside guard", [Atom, length(Args)]);
|
||||
guard ->
|
||||
Arity = length(Args),
|
||||
File = S#elixir_scope.file,
|
||||
case Arity of
|
||||
0 -> syntax_error(Line, File, "unknown variable ~s or cannot invoke local ~s/~B inside guard", [Atom, Atom, Arity]);
|
||||
_ -> syntax_error(Line, File, "cannot invoke local ~s/~B inside guard", [Atom, Arity])
|
||||
end;
|
||||
_ -> translate_local(Line, Atom, Args, S)
|
||||
end
|
||||
end,
|
||||
@@ -404,23 +392,24 @@ translate_each({ Atom, Line, Args } = Original, S) when is_atom(Atom) ->
|
||||
Else -> Else
|
||||
end;
|
||||
|
||||
%% Dot calls
|
||||
%% Remote calls
|
||||
|
||||
translate_each({ { '.', _, [Left, Right] }, Line, Args } = Original, S) when is_atom(Right) ->
|
||||
{ TLeft, SL } = translate_each(Left, S),
|
||||
|
||||
Fun = (element(1, TLeft) == atom) andalso sequential_partials(Args, 1) andalso
|
||||
Fun = (element(1, TLeft) == atom) andalso elixir_partials:is_sequential(Args) andalso
|
||||
elixir_dispatch:require_function(Line, element(3, TLeft), Right, length(Args), SL),
|
||||
|
||||
case Fun of
|
||||
false ->
|
||||
case handle_partials(Line, Original, S) of
|
||||
case elixir_partials:handle(Original, S) of
|
||||
error ->
|
||||
{ TRight, SR } = translate_each(Right, umergec(S, SL)),
|
||||
Callback = fun() -> translate_apply(Line, TLeft, TRight, Args, S, SL, SR) end,
|
||||
|
||||
case TLeft of
|
||||
{ atom, _, 'Elixir.Erlang' } ->
|
||||
elixir_errors:deprecation(Line, S#elixir_scope.file, "Erlang. syntax is deprecated"),
|
||||
case Args of
|
||||
[] -> { { atom, Line, Right }, S };
|
||||
_ ->
|
||||
@@ -445,7 +434,7 @@ translate_each({ { '.', _, [Expr] }, Line, Args } = Original, S) ->
|
||||
{ atom, _, Atom } ->
|
||||
translate_each({ Atom, Line, Args }, S);
|
||||
_ ->
|
||||
case handle_partials(Line, Original, S) of
|
||||
case elixir_partials:handle(Original, S) of
|
||||
error ->
|
||||
{ TArgs, SA } = translate_args(Args, umergec(S, SE)),
|
||||
{ {call, Line, TExpr, TArgs}, umergev(SE, SA) };
|
||||
@@ -453,54 +442,27 @@ translate_each({ { '.', _, [Expr] }, Line, Args } = Original, S) ->
|
||||
end
|
||||
end;
|
||||
|
||||
%% Invalid calls
|
||||
|
||||
translate_each({ Invalid, Line, _Args }, S) ->
|
||||
syntax_error(Line, S#elixir_scope.file, "unexpected parenthesis after ~ts",
|
||||
['Elixir.Macro':to_binary(Invalid)]);
|
||||
|
||||
%% Literals
|
||||
|
||||
translate_each({ Left, Right }, S) ->
|
||||
translate_each({ '{}', 0, [Left, Right]}, S);
|
||||
|
||||
translate_each([], S) ->
|
||||
{ { nil, 0 }, S };
|
||||
|
||||
translate_each(Args, S) when is_list(Args) ->
|
||||
[RTail|RArgs] = lists:reverse(Args),
|
||||
|
||||
case RTail of
|
||||
{'|',_,[Left,Right]} ->
|
||||
Exprs = [Left|RArgs],
|
||||
{ Tail, ST } = translate_each(Right, S),
|
||||
ListS = umergec(S, ST);
|
||||
_ ->
|
||||
Exprs = [RTail|RArgs],
|
||||
Tail = { nil, 0 },
|
||||
ST = S,
|
||||
ListS = S
|
||||
end,
|
||||
|
||||
{ FExprs, FS } = case S#elixir_scope.context of
|
||||
assign ->
|
||||
elixir_tree_helpers:build_reverse_list(fun translate_each/2, Exprs, 0, ListS, Tail);
|
||||
_ ->
|
||||
{ TArgs, { SC, SV } } =
|
||||
elixir_tree_helpers:build_reverse_list(fun translate_arg/2, Exprs, 0, { ListS, ListS }, Tail),
|
||||
{ TArgs, umergec(SV, SC) }
|
||||
end,
|
||||
|
||||
{ FExprs, umergev(ST, FS) };
|
||||
|
||||
translate_each(Number, S) when is_integer(Number) ->
|
||||
{ { integer, 0, Number }, S };
|
||||
|
||||
translate_each(Number, S) when is_float(Number) ->
|
||||
{ { float, 0, Number }, S };
|
||||
|
||||
translate_each(Atom, S) when is_atom(Atom) ->
|
||||
{ { atom, 0, Atom }, S };
|
||||
|
||||
translate_each(Bitstring, S) when is_bitstring(Bitstring) ->
|
||||
{ elixir_tree_helpers:abstract_syntax(Bitstring), S }.
|
||||
translate_each(Literal, S) ->
|
||||
elixir_literal:translate(Literal, S).
|
||||
|
||||
%% Helpers
|
||||
|
||||
validate_opts(Line, Kind, Allowed, Opts, S) when is_list(Opts) ->
|
||||
[begin
|
||||
syntax_error(Line, S#elixir_scope.file, "unsupported option ~s given to ~s", [Key, Kind])
|
||||
end || { Key, _ } <- Opts, not lists:member(Key, Allowed)];
|
||||
|
||||
validate_opts(Line, Kind, _Allowed, _Opts, S) ->
|
||||
syntax_error(Line, S#elixir_scope.file, "invalid options for ~s, expected a keyword list", [Kind]).
|
||||
|
||||
translate_fn(Line, Clauses, S) ->
|
||||
Transformer = fun({ ArgsWithGuards, Expr }, Acc) ->
|
||||
{ Args, Guards } = elixir_clauses:extract_last_guards(ArgsWithGuards),
|
||||
@@ -522,20 +484,23 @@ translate_local(Line, Name, Args, S) ->
|
||||
},
|
||||
{ { call, Line, Remote, TArgs }, NS }.
|
||||
|
||||
% Variables in arguments are not propagated from one
|
||||
% argument to the other. For instance:
|
||||
%
|
||||
% x = 1
|
||||
% foo(x = x + 2, x)
|
||||
% x
|
||||
%
|
||||
% Should be the same as:
|
||||
%
|
||||
% foo(3, 1)
|
||||
% 3
|
||||
%
|
||||
% However, notice that if we are doing an assignment,
|
||||
% it behaves the same as translate.
|
||||
%% Translate args
|
||||
|
||||
%% Variables in arguments are not propagated from one
|
||||
%% argument to the other. For instance:
|
||||
%%
|
||||
%% x = 1
|
||||
%% foo(x = x + 2, x)
|
||||
%% x
|
||||
%%
|
||||
%% Should be the same as:
|
||||
%%
|
||||
%% foo(3, 1)
|
||||
%% 3
|
||||
%%
|
||||
%% However, notice that if we are doing an assignment,
|
||||
%% it behaves the same as translate.
|
||||
|
||||
translate_arg(Arg, { Acc, S }) ->
|
||||
{ TArg, TAcc } = translate_each(Arg, Acc),
|
||||
{ TArg, { umergec(S, TAcc), umergev(S, TAcc) } }.
|
||||
@@ -547,8 +512,9 @@ translate_args(Args, S) ->
|
||||
{ TArgs, { SC, SV } } = lists:mapfoldl(fun translate_arg/2, {S, S}, Args),
|
||||
{ TArgs, umergec(SV, SC) }.
|
||||
|
||||
% Translate apply. Used by both apply and
|
||||
% external function invocation macros.
|
||||
%% Translate apply
|
||||
%% Used by both apply and external function invocation macros.
|
||||
|
||||
translate_apply(Line, TLeft, TRight, Args, S, SL, SR) ->
|
||||
Optimize = case (Args == []) orelse lists:last(Args) of
|
||||
{ '|', _, _ } -> false;
|
||||
@@ -572,62 +538,7 @@ translate_apply(Line, TLeft, TRight, Args, S, SL, SR) ->
|
||||
{ ?ELIXIR_WRAP_CALL(Line, erlang, apply, [TLeft, TRight, TArgs]), FS }
|
||||
end.
|
||||
|
||||
%% Handle partials by automatically wrapping them in a function.
|
||||
%% It also checks if we are in an assignment scope and does not
|
||||
%% apply the function if this is the case.
|
||||
handle_partials(_Line, _Original, #elixir_scope{context=assign}) ->
|
||||
error;
|
||||
|
||||
handle_partials(Line, Original, S) ->
|
||||
case convert_partials(Line, element(3, Original), S) of
|
||||
{ Call, Def, SC } when Def /= [] ->
|
||||
Final = validate_partials(Line, Def, SC),
|
||||
Block = setelement(3, Original, Call),
|
||||
translate_fn(Line, [{ Final, Block }], SC);
|
||||
_ -> error
|
||||
end.
|
||||
|
||||
validate_partials(Line, Def, S) ->
|
||||
validate_partials(Line, lists:sort(Def), 1, S).
|
||||
|
||||
validate_partials(Line, [{ Pos, Item }|T], Pos, S) ->
|
||||
[Item|validate_partials(Line, T, Pos + 1, S)];
|
||||
|
||||
validate_partials(Line, [{ Pos, _ }|_], Expected, S) ->
|
||||
syntax_error(Line, S#elixir_scope.file, "partial variable &~w cannot be defined without &~w", [Pos, Expected]);
|
||||
|
||||
validate_partials(_Line, [], _Pos, _S) ->
|
||||
[].
|
||||
|
||||
%% This function receives arguments and then checks
|
||||
%% the args for partial application. It returns a tuple
|
||||
%% with three elements where the first element is the list
|
||||
%% of call args, the second the list of def args for the
|
||||
%% function definition and the third one is the new scope.
|
||||
convert_partials(Line, List, S) -> convert_partials(Line, List, S, [], []).
|
||||
|
||||
convert_partials(Line, [{'&', _, [Pos]}|T], S, CallAcc, DefAcc) ->
|
||||
case lists:keyfind(Pos, 1, DefAcc) of
|
||||
false ->
|
||||
{ Var, SC } = elixir_scope:build_ex_var(Line, S),
|
||||
convert_partials(Line, T, SC, [Var|CallAcc], [{Pos,Var}|DefAcc]);
|
||||
{Pos,Var} ->
|
||||
convert_partials(Line, T, S, [Var|CallAcc], DefAcc)
|
||||
end;
|
||||
|
||||
convert_partials(Line, [H|T], S, CallAcc, DefAcc) ->
|
||||
convert_partials(Line, T, S, [H|CallAcc], DefAcc);
|
||||
|
||||
convert_partials(_Line, [], S, CallAcc, DefAcc) ->
|
||||
{ lists:reverse(CallAcc), lists:reverse(DefAcc), S }.
|
||||
|
||||
sequential_partials([{ '&', _, [Int] }|T], Int) ->
|
||||
sequential_partials(T, Int + 1);
|
||||
|
||||
sequential_partials([], Int) when Int > 1 -> true;
|
||||
sequential_partials(_, _Int) -> false.
|
||||
|
||||
%% Convert operators
|
||||
%% __op__ helpers
|
||||
|
||||
convert_op('and') -> 'andalso';
|
||||
convert_op('or') -> 'orelse';
|
||||
@@ -686,4 +597,4 @@ translate_comprehension_clause(X, S) ->
|
||||
{ _, L, _ } -> L;
|
||||
_ -> 0
|
||||
end,
|
||||
{ elixir_tree_helpers:convert_to_boolean(Line, TX, true, false), TS }.
|
||||
{ elixir_tree_helpers:convert_to_boolean(Line, TX, true, false), TS }.
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
%% Convenience functions used around elixir source code
|
||||
%% Convenience functions used throughout elixir source code
|
||||
%% to generate erlang abstract format for basic structures
|
||||
%% as binaries, lists, condition clauses, etc.
|
||||
%% as lists, condition clauses, etc.
|
||||
|
||||
-module(elixir_tree_helpers).
|
||||
-compile({parse_transform, elixir_transform}).
|
||||
-export([abstract_syntax/1, split_last/1, cons_to_list/1,
|
||||
convert_to_boolean/4, returns_boolean/1,
|
||||
build_bitstr/4, build_list/4, build_list/5, build_simple_list/2,
|
||||
build_list/4, build_list/5, build_simple_list/2,
|
||||
build_reverse_list/4, build_reverse_list/5, build_simple_reverse_list/2]).
|
||||
-include("elixir.hrl").
|
||||
|
||||
@@ -62,73 +63,6 @@ build_list_each(Fun, [H|T], Line, S, Acc) ->
|
||||
{ Expr, NS } = Fun(H, S),
|
||||
build_list_each(Fun, T, Line, NS, { cons, Line, Expr, Acc }).
|
||||
|
||||
% Build a bitstring taking into accounts the following types:
|
||||
%
|
||||
% * If a bitstring or a list is given, we just append its items
|
||||
% * If '|' is given, extract the bitstring information
|
||||
% * All the other types are simply translated and handled with Erlang's default
|
||||
%
|
||||
build_bitstr(Fun, Exprs, Line, S) ->
|
||||
{ Final, FinalS } = build_bitstr_each(Fun, Exprs, Line, S, []),
|
||||
{ { bin, Line, lists:reverse(Final) }, FinalS }.
|
||||
|
||||
build_bitstr_each(_Fun, [], _Line, S, Acc) ->
|
||||
{ Acc, S };
|
||||
|
||||
build_bitstr_each(Fun, [H|T], Line, S, Acc) when is_list(H) ->
|
||||
{ NewAcc, NewS } = build_bitstr_each(Fun, H, Line, S, Acc),
|
||||
build_bitstr_each(Fun, T, Line, NewS, NewAcc);
|
||||
|
||||
build_bitstr_each(Fun, [H|T], Line, S, Acc) when is_bitstring(H) ->
|
||||
{ bin, _, Elements } = abstract_syntax(H),
|
||||
NewAcc = lists:foldl(fun(Element, FinalAcc) -> [Element|FinalAcc] end, Acc, Elements),
|
||||
build_bitstr_each(Fun, T, Line, S, NewAcc);
|
||||
|
||||
build_bitstr_each(Fun, [{'|',_,[H,V]}|T], Line, S, Acc) ->
|
||||
{ Expr, NS } = Fun(H, S),
|
||||
|
||||
%% Just variables defined outside the binary can be accounted on subparts
|
||||
case NS of
|
||||
{ ES, _ } -> [];
|
||||
ES -> []
|
||||
end,
|
||||
|
||||
%% Assigns can be made in subparts
|
||||
{ Int, Types } = extract_bin_values(Line, V, default, [], ES#elixir_scope{context=nil}),
|
||||
|
||||
Final = case Types of
|
||||
[] -> default;
|
||||
_ -> lists:reverse(Types)
|
||||
end,
|
||||
|
||||
build_bitstr_each(Fun, T, Line, NS, [{ bin_element, Line, Expr, Int, Final }|Acc]);
|
||||
|
||||
build_bitstr_each(Fun, [H|T], Line, S, Acc) ->
|
||||
{ Expr, NS } = Fun(H, S),
|
||||
build_bitstr_each(Fun, T, Line, NS, [{ bin_element, Line, Expr, default, default }|Acc]).
|
||||
|
||||
%% Extra binary specifiers
|
||||
|
||||
extract_bin_values(Line, { '-', _Line, [Left, Right] }, Int, Types, S) ->
|
||||
{ LInt, LTypes } = extract_bin_values(Line, Left, Int, Types, S),
|
||||
extract_bin_values(Line, Right, LInt, LTypes, S);
|
||||
|
||||
extract_bin_values(Line, Value, default, Types, _S) when is_integer(Value) ->
|
||||
{ { integer, Line, Value }, Types };
|
||||
|
||||
extract_bin_values(_Line, { Value, _, Atom } = Expr, default, Types, S) when is_atom(Value), is_atom(Atom) ->
|
||||
Translated = element(1, elixir_translator:translate_each(Expr, S)),
|
||||
{ Translated, Types };
|
||||
|
||||
extract_bin_values(Line, Value, _Int, _Types, S) when is_integer(Value) ->
|
||||
elixir_errors:syntax_error(Line, S#elixir_scope.file, "duplicated size specifier ~p in <<>>", [Value]);
|
||||
|
||||
extract_bin_values(_Line, Value, Int, Types, _S) when is_atom(Value); is_tuple(Value), tuple_size(Value) == 2 ->
|
||||
{ Int, [Value|Types] };
|
||||
|
||||
extract_bin_values(Line, _Value, _Int, _Types, S) ->
|
||||
elixir_errors:syntax_error(Line, S#elixir_scope.file, "invalid specifier for <<>>").
|
||||
|
||||
%% Others
|
||||
|
||||
returns_boolean({ op, _, Op, _ }) when Op == 'not' -> true;
|
||||
|
||||
@@ -1,10 +1,16 @@
|
||||
Code.require_file "../test_helper", __FILE__
|
||||
Code.require_file "../test_helper.exs", __FILE__
|
||||
|
||||
defmodule Access.TupleTest do
|
||||
defmodule AccessTest do
|
||||
use ExUnit.Case, async: true
|
||||
|
||||
test :list do
|
||||
assert [foo: :bar][:foo] == :bar
|
||||
assert [foo: [bar: :baz]][:foo][:bar] == :baz
|
||||
assert [foo: [bar: :baz]][:fuu][:bar] == nil
|
||||
end
|
||||
|
||||
test :nil do
|
||||
assert nil[:foo] == nil
|
||||
end
|
||||
|
||||
test :function do
|
||||
@@ -13,6 +19,16 @@ defmodule Access.TupleTest do
|
||||
assert function[:bar] == false
|
||||
end
|
||||
|
||||
# Test nil at compilation time does not fail
|
||||
# and that @config[:foo] has proper precedence.
|
||||
nil = @config[:foo]
|
||||
|
||||
@config [foo: :bar]
|
||||
:bar = @config[:foo]
|
||||
|
||||
@mod :lists
|
||||
[1,2,3] = @mod.flatten([1,[2],3])
|
||||
|
||||
test :atom do
|
||||
exception = assert_raise RuntimeError, fn ->
|
||||
foo = :foo
|
||||
@@ -62,4 +78,4 @@ defmodule Access.RecordTest do
|
||||
|
||||
defp is_config(Config[]), do: true
|
||||
defp is_config(_), do: false
|
||||
end
|
||||
end
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
Code.require_file "../test_helper.exs", __FILE__
|
||||
|
||||
defmodule BehaviourTest do
|
||||
use ExUnit.Case, async: true
|
||||
|
||||
defmodule Sample do
|
||||
use Behaviour
|
||||
|
||||
@doc "Doc"
|
||||
defcallback foo(foo, bar // [], bat // [])
|
||||
end
|
||||
|
||||
test :docs_are_defined do
|
||||
assert [_, _] = Sample.__info__(:docs)
|
||||
end
|
||||
|
||||
test :callbacks do
|
||||
assert [{:foo,3},{:foo,2},{:foo,1}] = Sample.behaviour_info(:callbacks)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
Code.require_file "../../test_helper", __FILE__
|
||||
Code.require_file "../../test_helper.exs", __FILE__
|
||||
|
||||
defmodule Binary.Chars.AtomTest do
|
||||
use ExUnit.Case, async: true
|
||||
@@ -32,7 +32,7 @@ defmodule Binary.Chars.BitStringTest do
|
||||
|
||||
test :bitstring do
|
||||
assert_raise FunctionClauseError, fn ->
|
||||
to_binary(<<1|12-:integer-:signed>>)
|
||||
to_binary(<<1 :: [size(12), integer, signed]>>)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
Code.require_file "../../test_helper", __FILE__
|
||||
Code.require_file "../../test_helper.exs", __FILE__
|
||||
|
||||
defmodule Binary.Inspect.AtomTest do
|
||||
use ExUnit.Case, async: true
|
||||
@@ -50,7 +50,7 @@ defmodule Binary.Inspect.BitStringTest do
|
||||
use ExUnit.Case, async: true
|
||||
|
||||
test :bitstring do
|
||||
assert inspect(<<1|12-:integer-:signed>>) == "<<0,1|4>>"
|
||||
assert inspect(<<1 :: [size(12), integer, signed]>>) == "<<0,1::size(4)>>"
|
||||
end
|
||||
|
||||
test :binary do
|
||||
@@ -68,7 +68,11 @@ defmodule Binary.Inspect.BitStringTest do
|
||||
end
|
||||
|
||||
test :unprintable do
|
||||
assert inspect(<<1>>) == "<<1>>"
|
||||
assert inspect(<<193>>) == "<<193>>"
|
||||
end
|
||||
|
||||
test :unprintable_with_opts do
|
||||
assert inspect(<<193, 193, 193, 193>>, limit: 3) == "<<193,193,193,...>>"
|
||||
end
|
||||
end
|
||||
|
||||
@@ -80,9 +84,10 @@ defmodule Binary.Inspect.NumberTest do
|
||||
end
|
||||
|
||||
test :float do
|
||||
assert inspect(1.0) == "1.00000000000000000000e+00"
|
||||
assert inspect(1.0e10) == "1.00000000000000000000e+10"
|
||||
assert inspect(1.0e+10) == "1.00000000000000000000e+10"
|
||||
assert inspect(1.0) == "1.0"
|
||||
assert inspect(1.0e10) == "1.0e10"
|
||||
assert inspect(1.0e+10) == "1.0e10"
|
||||
assert inspect(1.0e-10) == "1.0e-10"
|
||||
end
|
||||
end
|
||||
|
||||
@@ -105,8 +110,10 @@ defmodule Binary.Inspect.TupleTest do
|
||||
assert inspect({ List, 1 }) == "{List,1}"
|
||||
end
|
||||
|
||||
defrecord Config, a: 1, b: []
|
||||
|
||||
test :with_record do
|
||||
assert inspect(ExUnit.Server.Config.new) == "ExUnit.Server.Config[async_cases: [], options: [], sync_cases: []]"
|
||||
assert inspect(Config.new) == "Binary.Inspect.TupleTest.Config[a: 1, b: []]"
|
||||
end
|
||||
|
||||
test :with_tuple_matching_record_name_but_not_length do
|
||||
@@ -120,6 +127,14 @@ defmodule Binary.Inspect.TupleTest do
|
||||
test :empty do
|
||||
assert inspect({}) == "{}"
|
||||
end
|
||||
|
||||
test :with_limit do
|
||||
assert inspect({ 1, 2, 3, 4 }, limit: 3) == "{1,2,3,...}"
|
||||
end
|
||||
|
||||
test :with_raw do
|
||||
assert inspect(Config.new, raw: true) == "{Binary.Inspect.TupleTest.Config,1,[]}"
|
||||
end
|
||||
end
|
||||
|
||||
defmodule Binary.Inspect.ListTest do
|
||||
@@ -133,8 +148,15 @@ defmodule Binary.Inspect.ListTest do
|
||||
assert inspect('abc') == "'abc'"
|
||||
end
|
||||
|
||||
test :keyword do
|
||||
assert inspect([a: 1]) == "[a: 1]"
|
||||
assert inspect([a: 1, b: 2]) == "[a: 1, b: 2]"
|
||||
assert inspect([a: 1, a: 2, b: 2]) == "[a: 1, a: 2, b: 2]"
|
||||
assert inspect(["123": 1]) == %b(["123": 1])
|
||||
end
|
||||
|
||||
test :non_printable do
|
||||
assert inspect([{:a,1}]) == "[{:a,1}]"
|
||||
assert inspect([{:b, 1},{:a,1}]) == "[b: 1, a: 1]"
|
||||
end
|
||||
|
||||
test :unproper do
|
||||
@@ -148,6 +170,10 @@ defmodule Binary.Inspect.ListTest do
|
||||
test :empty do
|
||||
assert inspect([]) == "[]"
|
||||
end
|
||||
|
||||
test :with_limit do
|
||||
assert inspect([ 1, 2, 3, 4 ], limit: 3) == "[1,2,3,...]"
|
||||
end
|
||||
end
|
||||
|
||||
defmodule Binary.Inspect.AnyTest do
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
Code.require_file "../test_helper", __FILE__
|
||||
Code.require_file "../test_helper.exs", __FILE__
|
||||
|
||||
defmodule BinaryTest do
|
||||
defmodule Binary.LiteralTest do
|
||||
use ExUnit.Case, async: true
|
||||
|
||||
test :heredoc do
|
||||
@@ -46,35 +46,6 @@ bar
|
||||
assert x == "bar"
|
||||
end
|
||||
|
||||
test :__B__ do
|
||||
assert %B(foo) == "foo"
|
||||
assert %B[foo] == "foo"
|
||||
assert %B{foo} == "foo"
|
||||
assert %B'foo' == "foo"
|
||||
assert %B"foo" == "foo"
|
||||
assert %B|foo| == "foo"
|
||||
assert %B(f#{o}o) == "f\#{o}o"
|
||||
assert %B(f\no) == "f\\no"
|
||||
end
|
||||
|
||||
test :__b__ do
|
||||
assert %b(foo) == "foo"
|
||||
assert %b(f#{:o}o) == "foo"
|
||||
assert %b(f\no) == "f\no"
|
||||
end
|
||||
|
||||
test :__B__with_heredoc do
|
||||
assert " f\#{o}o\\n\n" == %B"""
|
||||
f#{o}o\n
|
||||
"""
|
||||
end
|
||||
|
||||
test :__b__with_heredoc do
|
||||
assert " foo\n\n" == %b"""
|
||||
f#{:o}o\n
|
||||
"""
|
||||
end
|
||||
|
||||
test :octals do
|
||||
assert "\123" == "S"
|
||||
assert "\128" == "\n8"
|
||||
@@ -88,19 +59,46 @@ bar
|
||||
|
||||
test :pattern_match do
|
||||
s = 16
|
||||
assert <<a, b|s>> = "foo"
|
||||
assert <<a, b :: size(s)>> = "foo"
|
||||
end
|
||||
|
||||
test :partial_application do
|
||||
assert (<< &1, 2 >>).(1) == << 1, 2 >>
|
||||
assert (<< &1, &2 >>).(1, 2) == << 1, 2 >>
|
||||
assert (<< &2, &1 >>).(2, 1) == << 1, 2 >>
|
||||
end
|
||||
|
||||
test :bitsyntax_translation do
|
||||
refb = "sample"
|
||||
sec_data = "another"
|
||||
<< size(refb) | 1 - :big - :signed - :integer - {:unit, 8},
|
||||
refb | :binary,
|
||||
size(sec_data) | 1 - :big - :signed - :integer - {:unit, 16},
|
||||
sec_data|:binary >>
|
||||
<< size(refb) :: [size(1), big, signed, integer, unit(8)],
|
||||
refb :: binary,
|
||||
size(sec_data) :: [size(1), big, signed, integer, unit(16)],
|
||||
sec_data :: binary >>
|
||||
end
|
||||
|
||||
defp is_match?(<<char, _|:binary>>, char) do
|
||||
defmacrop signed_16 do
|
||||
quote do
|
||||
[big, signed, integer, unit(16)]
|
||||
end
|
||||
end
|
||||
|
||||
defmacrop refb_spec do
|
||||
quote do
|
||||
[size(1), big, signed, integer, unit(8)]
|
||||
end
|
||||
end
|
||||
|
||||
test :bitsyntax_macro do
|
||||
refb = "sample"
|
||||
sec_data = "another"
|
||||
<< size(refb) :: refb_spec,
|
||||
refb :: binary,
|
||||
size(sec_data) :: [size(1) | signed_16],
|
||||
sec_data :: binary >>
|
||||
end
|
||||
|
||||
defp is_match?(<<char, _ :: binary>>, char) do
|
||||
true
|
||||
end
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
Code.require_file "../test_helper", __FILE__
|
||||
Code.require_file "../test_helper.exs", __FILE__
|
||||
|
||||
defmodule Bitwise.FunctionsTest do
|
||||
use ExUnit.Case, async: true
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
Code.require_file "../test_helper", __FILE__
|
||||
Code.require_file "../test_helper.exs", __FILE__
|
||||
|
||||
defmodule CharListTest do
|
||||
use ExUnit.Case, async: true
|
||||
@@ -26,21 +26,4 @@ bar '''
|
||||
assert '\128' == '\n8'
|
||||
assert '\18' == [1, ?8]
|
||||
end
|
||||
|
||||
test :__C__ do
|
||||
assert %C(foo) == 'foo'
|
||||
assert %C[foo] == 'foo'
|
||||
assert %C{foo} == 'foo'
|
||||
assert %C'foo' == 'foo'
|
||||
assert %C"foo" == 'foo'
|
||||
assert %C|foo| == 'foo'
|
||||
assert %C(f#{o}o) == 'f\#{o}o'
|
||||
assert %C(f\no) == 'f\\no'
|
||||
end
|
||||
|
||||
test :__c__ do
|
||||
assert %c(foo) == 'foo'
|
||||
assert %c(f#{:o}o) == 'foo'
|
||||
assert %c(f\no) == 'f\no'
|
||||
end
|
||||
end
|
||||
@@ -1,4 +1,4 @@
|
||||
Code.require_file "../test_helper", __FILE__
|
||||
Code.require_file "../test_helper.exs", __FILE__
|
||||
|
||||
defmodule CodeTest do
|
||||
use ExUnit.Case, async: true
|
||||
@@ -29,8 +29,22 @@ defmodule CodeTest do
|
||||
end
|
||||
|
||||
test :require do
|
||||
Code.require_file fixture_path("code_sample"), __FILE__
|
||||
Code.require_file fixture_path("code_sample.exs")
|
||||
assert fixture_path("code_sample.exs") in Code.loaded_files
|
||||
assert Code.require_file(fixture_path("code_sample.exs")) == nil
|
||||
|
||||
Code.unload_files [fixture_path("code_sample.exs")]
|
||||
refute fixture_path("code_sample.exs") in Code.loaded_files
|
||||
assert Code.require_file(fixture_path("code_sample.exs")) != nil
|
||||
end
|
||||
|
||||
test :path_manipulation do
|
||||
path = File.expand_path("../binary", __FILE__)
|
||||
Code.prepend_path path
|
||||
assert binary_to_list(path) in :code.get_path
|
||||
|
||||
Code.delete_path path
|
||||
refute binary_to_list(path) in :code.get_path
|
||||
end
|
||||
|
||||
test :file do
|
||||
@@ -42,6 +56,11 @@ defmodule CodeTest do
|
||||
assert { :error, _ } = Code.string_to_ast("a.1")
|
||||
end
|
||||
|
||||
test :string_to_ast_existing_atoms_only do
|
||||
assert :badarg = catch_error(Code.string_to_ast(":thereisnosuchatom", existing_atoms_only: true))
|
||||
assert :badarg = catch_error(Code.string_to_ast!(":thereisnosuchatom", existing_atoms_only: true))
|
||||
end
|
||||
|
||||
test :string_to_ast! do
|
||||
assert Code.string_to_ast!("1 + 2") == quote line: 1, do: 1 + 2
|
||||
|
||||
@@ -72,4 +91,14 @@ defmodule CodeTest do
|
||||
compile = __MODULE__.__info__(:compile)
|
||||
assert Keyword.get(compile, :source) != nil
|
||||
end
|
||||
|
||||
test :ensure_loaded? do
|
||||
assert Code.ensure_loaded?(__MODULE__)
|
||||
refute Code.ensure_loaded?(Unknown.Module)
|
||||
end
|
||||
|
||||
test :ensure_compiled? do
|
||||
assert Code.ensure_compiled?(__MODULE__)
|
||||
refute Code.ensure_compiled?(Unknown.Module)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,23 +1,29 @@
|
||||
Code.require_file "../test_helper", __FILE__
|
||||
Code.require_file "../test_helper.exs", __FILE__
|
||||
|
||||
defmodule DictTest.Common do
|
||||
defmacro __using__(module) do
|
||||
quote do
|
||||
quote location: :keep do
|
||||
use ExUnit.Case, async: true
|
||||
|
||||
test :access do
|
||||
dict = new_dict [{"first_key", 1}, {"second_key", 2}]
|
||||
assert dict["first_key"] == 1
|
||||
assert dict["third_key"] == nil
|
||||
end
|
||||
|
||||
test :new_pairs do
|
||||
dict = new_dict [{"first key", 1}, {"second key", 2}]
|
||||
dict = new_dict [{"first_key", 1}, {"second_key", 2}]
|
||||
assert 2 == Dict.size dict
|
||||
|
||||
assert ["first key", "second key"] == List.sort Dict.keys dict
|
||||
assert ["first_key", "second_key"] == List.sort Dict.keys dict
|
||||
assert [1, 2] == List.sort Dict.values dict
|
||||
end
|
||||
|
||||
test :new_pairs_with_transform do
|
||||
dict = new_dict [{1}, {2}, {3}], fn {x} -> { {x}, x } end
|
||||
dict = new_dict [{1}, {2}, {3}], fn {x} -> { <<x + 64>>, x } end
|
||||
assert 3 == Dict.size dict
|
||||
|
||||
assert [{1}, {2}, {3}] == List.sort Dict.keys dict
|
||||
assert ["A", "B", "C"] == List.sort Dict.keys dict
|
||||
assert [1, 2, 3] == List.sort Dict.values dict
|
||||
end
|
||||
|
||||
@@ -62,18 +68,26 @@ defmodule DictTest.Common do
|
||||
assert dict == Dict.merge dict, dict
|
||||
assert empty_dict == Dict.merge empty_dict, empty_dict
|
||||
|
||||
dict1 = new_dict List.zip ["a", "b", "c"], [1, 2, 3]
|
||||
dict2 = new_dict List.zip ["a", "c", "d"], [3, :a, 0]
|
||||
assert new_dict(List.zip ["a", "b", "c", "d"], [3, 2, :a, 0]) == Dict.merge(dict1, dict2)
|
||||
dict1 = new_dict Enum.zip ["a", "b", "c"], [1, 2, 3]
|
||||
dict2 = new_dict Enum.zip ["a", "c", "d"], [3, :a, 0]
|
||||
final = new_dict Enum.zip ["a", "b", "c", "d"], [3, 2, :a, 0]
|
||||
assert Dict.merge(dict1, dict2) == final
|
||||
end
|
||||
|
||||
test :merge_with_enum do
|
||||
dict1 = new_dict Enum.zip ["a", "b", "c"], [1, 2, 3]
|
||||
dict2 = Enum.zip ["a", "c", "d"], [3, :a, 0]
|
||||
final = new_dict(Enum.zip ["a", "b", "c", "d"], [3, 2, :a, 0])
|
||||
assert Dict.merge(dict1, dict2) == final
|
||||
end
|
||||
|
||||
test :merge_with_function do
|
||||
dict1 = new_dict List.zip ["a", "b"], [1, 2]
|
||||
dict2 = new_dict List.zip ["a", "d"], [3, 4]
|
||||
dict1 = new_dict Enum.zip ["a", "b"], [1, 2]
|
||||
dict2 = new_dict Enum.zip ["a", "d"], [3, 4]
|
||||
result = Dict.merge dict1, dict2, fn _k, v1, v2 ->
|
||||
v1 + v2
|
||||
end
|
||||
assert new_dict(List.zip ["a", "b", "d"], [4, 2, 4]) == result
|
||||
assert new_dict(Enum.zip ["a", "b", "d"], [4, 2, 4]) == result
|
||||
end
|
||||
|
||||
test :has_key do
|
||||
@@ -100,11 +114,10 @@ defmodule DictTest.Common do
|
||||
end
|
||||
|
||||
defp empty_dict, do: unquote(module).new
|
||||
defp new_dict({k, v}), do: unquote(module).new {k, v}
|
||||
|
||||
defp new_dict(list // [{"first_key", 1}, {"second_key", 2}])
|
||||
defp new_dict(list), do: unquote(module).new list
|
||||
defp new_dict(list, transform) when is_function(transform), do: unquote(module).new list, transform
|
||||
defp new_dict(keys, values), do: unquote(module).new keys, values
|
||||
defp new_dict(list, transform), do: unquote(module).new list, transform
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -113,14 +126,28 @@ defmodule DictTest do
|
||||
use DictTest.Common, HashDict
|
||||
|
||||
test :new do
|
||||
assert :dict.new == elem(HashDict.new, 2)
|
||||
assert :dict.new == elem(new_dict([]), 1)
|
||||
end
|
||||
end
|
||||
|
||||
defmodule OrddictTest do
|
||||
use DictTest.Common, Orddict
|
||||
defmodule OrdDictTest do
|
||||
use DictTest.Common, OrdDict
|
||||
|
||||
test :new do
|
||||
assert [] == elem(Orddict.new, 2)
|
||||
assert [] == elem(new_dict([]), 1)
|
||||
end
|
||||
end
|
||||
|
||||
defmodule Binary.DictTest do
|
||||
use DictTest.Common, Binary.Dict
|
||||
|
||||
test :new do
|
||||
assert [] == elem(new_dict([]), 1)
|
||||
end
|
||||
|
||||
test :merge_mixed do
|
||||
merged = Dict.merge(new_dict, new_dict([first_key: 13]))
|
||||
assert merged[:first_key] == 13
|
||||
assert merged["first_key"] == 13
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
Code.require_file "../test_helper", __FILE__
|
||||
Code.require_file "../test_helper.exs", __FILE__
|
||||
|
||||
defmodule EnumTest.Common do
|
||||
defmodule EnumTest do
|
||||
use ExUnit.Case, async: true
|
||||
|
||||
test :times_with_arity_0 do
|
||||
test :times do
|
||||
try do
|
||||
Process.put(:times_with_arity, nil)
|
||||
assert Enum.times(0, fn -> Process.put(:times_with_arity, :ok) end) == 0
|
||||
@@ -15,17 +15,26 @@ defmodule EnumTest.Common do
|
||||
end
|
||||
end
|
||||
|
||||
test :times_with_arity_1 do
|
||||
test :times_with_iterator do
|
||||
try do
|
||||
assert Enum.times(5, fn x -> Process.put(:times_with_arity, x) end) == 5
|
||||
assert Process.get(:times_with_arity) == 5
|
||||
assert Process.get(:times_with_arity) == 4
|
||||
after
|
||||
Process.delete(:times_with_arity)
|
||||
end
|
||||
end
|
||||
|
||||
test :times_with_arity_2 do
|
||||
assert Enum.times(5, 0, fn acc, x -> acc + x end) == 15
|
||||
test :times_with_iterator_and_acumulator do
|
||||
assert Enum.times(5, 0, fn acc, x -> acc + x end) == 10
|
||||
end
|
||||
|
||||
test :reverse do
|
||||
assert Enum.reverse(URI.query_decoder("foo=bar&baz=bat")) ==
|
||||
[{ "baz", "bat" }, { "foo", "bar" }]
|
||||
end
|
||||
|
||||
test :count do
|
||||
assert Enum.count(URI.query_decoder("foo=bar&baz=bat")) == 2
|
||||
end
|
||||
end
|
||||
|
||||
@@ -52,11 +61,24 @@ defmodule EnumTest.List do
|
||||
refute Enum.any?([])
|
||||
end
|
||||
|
||||
test :at! do
|
||||
assert Enum.at!([2,4,6], 0) == 2
|
||||
assert Enum.at!([2,4,6], 2) == 6
|
||||
assert_raise Enum.OutOfBoundsError, fn ->
|
||||
Enum.at!([2,4,6], 4)
|
||||
end
|
||||
end
|
||||
|
||||
test :count do
|
||||
assert Enum.count([1,2,3]) == 3
|
||||
assert Enum.count([]) == 0
|
||||
end
|
||||
|
||||
test :count_fun do
|
||||
assert Enum.count([1,2,3], fn(x) -> rem(x, 2) == 0 end) == 1
|
||||
assert Enum.count([], fn(x) -> rem(x, 2) == 0 end) == 0
|
||||
end
|
||||
|
||||
test :drop do
|
||||
assert Enum.drop([1,2,3], 0) == [1,2,3]
|
||||
assert Enum.drop([1,2,3], 1) == [2,3]
|
||||
@@ -87,7 +109,7 @@ defmodule EnumTest.List do
|
||||
|
||||
test :find_index do
|
||||
assert Enum.find_index([2,4,6], fn(x) -> rem(x, 2) == 1 end) == nil
|
||||
assert Enum.find_index([2,3,4], fn(x) -> rem(x, 2) == 1 end) == 2
|
||||
assert Enum.find_index([2,3,4], fn(x) -> rem(x, 2) == 1 end) == 1
|
||||
end
|
||||
|
||||
test :empty? do
|
||||
@@ -166,19 +188,16 @@ defmodule EnumTest.List do
|
||||
assert Enum.map_reduce([1,2,3], 1, fn(x, acc) -> { x * 2, x + acc } end) == { [2,4,6], 7 }
|
||||
end
|
||||
|
||||
test :nth! do
|
||||
assert Enum.nth!([2,4,6], 1) == 2
|
||||
assert Enum.nth!([2,4,6], 3) == 6
|
||||
assert_raise Enum.OutOfBoundsError, fn ->
|
||||
Enum.nth!([2,4,6], 5)
|
||||
end
|
||||
end
|
||||
|
||||
test :partition do
|
||||
assert Enum.partition([1,2,3], fn(x) -> rem(x, 2) == 0 end) == { [2], [1,3] }
|
||||
assert Enum.partition([2,4,6], fn(x) -> rem(x, 2) == 0 end) == { [2,4,6], [] }
|
||||
end
|
||||
|
||||
test :reverse do
|
||||
assert Enum.reverse([]) == []
|
||||
assert Enum.reverse([1,2,3]) == [3,2,1]
|
||||
end
|
||||
|
||||
test :qsort do
|
||||
assert Enum.qsort([5,3,2,4,1]) == [1,2,3,4,5]
|
||||
end
|
||||
@@ -190,15 +209,19 @@ defmodule EnumTest.List do
|
||||
assert Enum.split([1,2,3], 3) == { [1,2,3], [] }
|
||||
assert Enum.split([1,2,3], 4) == { [1,2,3], [] }
|
||||
assert Enum.split([], 3) == { [], [] }
|
||||
assert Enum.split([1,2,3], -1) == { [1,2], [3] }
|
||||
assert Enum.split([1,2,3], -2) == { [1], [2,3] }
|
||||
assert Enum.split([1,2,3], -3) == { [], [1,2,3] }
|
||||
assert Enum.split([1,2,3], -10) == { [], [1,2,3] }
|
||||
end
|
||||
|
||||
test :split_with do
|
||||
assert Enum.split_with([1,2,3], fn(_) -> false end) == { [], [1,2,3] }
|
||||
assert Enum.split_with([1,2,3], fn(_) -> true end) == { [1,2,3], [] }
|
||||
assert Enum.split_with([1,2,3], fn(x) -> x > 2 end) == { [], [1,2,3] }
|
||||
assert Enum.split_with([1,2,3], fn(x) -> x > 3 end) == { [], [1,2,3] }
|
||||
assert Enum.split_with([1,2,3], fn(x) -> x < 3 end) == { [1,2], [3] }
|
||||
assert Enum.split_with([], fn(_) -> true end) == { [], [] }
|
||||
test :split_while do
|
||||
assert Enum.split_while([1,2,3], fn(_) -> false end) == { [], [1,2,3] }
|
||||
assert Enum.split_while([1,2,3], fn(_) -> true end) == { [1,2,3], [] }
|
||||
assert Enum.split_while([1,2,3], fn(x) -> x > 2 end) == { [], [1,2,3] }
|
||||
assert Enum.split_while([1,2,3], fn(x) -> x > 3 end) == { [], [1,2,3] }
|
||||
assert Enum.split_while([1,2,3], fn(x) -> x < 3 end) == { [1,2], [3] }
|
||||
assert Enum.split_while([], fn(_) -> true end) == { [], [] }
|
||||
end
|
||||
|
||||
test :take do
|
||||
@@ -216,6 +239,15 @@ defmodule EnumTest.List do
|
||||
assert Enum.take_while([1,2,3], fn(x) -> x <= 3 end) == [1,2,3]
|
||||
assert Enum.take_while([], fn(_) -> true end) == []
|
||||
end
|
||||
|
||||
test :zip do
|
||||
assert Enum.zip([:a, :b], [1, 2]) == [{:a, 1}, {:b, 2}]
|
||||
assert Enum.zip([:a, :b], [1, 2, 3, 4]) == [{:a, 1}, {:b, 2}]
|
||||
assert Enum.zip([:a, :b, :c, :d], [1, 2]) == [{:a, 1}, {:b, 2}, {:c, nil}, {:d, nil}]
|
||||
assert Enum.zip([], [1]) == []
|
||||
assert Enum.zip([1], []) == [{ 1, nil }]
|
||||
assert Enum.zip([], []) == []
|
||||
end
|
||||
end
|
||||
|
||||
defmodule EnumTest.Dict.Common do
|
||||
@@ -245,6 +277,12 @@ defmodule EnumTest.Dict.Common do
|
||||
assert Enum.count(unquote(module).new) == 0
|
||||
end
|
||||
|
||||
test :count_fun do
|
||||
dict = unquote(module).new [{2,2}, {3,4}, {4,6}]
|
||||
assert Enum.count(dict, fn({x,_}) -> rem(x, 2) == 0 end) == 2
|
||||
assert Enum.count(unquote(module).new, fn(x) -> rem(x, 2) == 0 end) == 0
|
||||
end
|
||||
|
||||
test :find do
|
||||
dict = unquote(module).new [a: 1, b: 2, c: 3]
|
||||
assert nil == Enum.find(dict, fn({_, v}) -> v == 0 end)
|
||||
@@ -309,10 +347,10 @@ defmodule EnumTest.Dict.Common do
|
||||
test :join do
|
||||
dict = unquote(module).new [a: 1, b: 2, c: 3]
|
||||
|
||||
assert_raise UndefinedFunctionError, fn ->
|
||||
assert_raise Protocol.UndefinedError, fn ->
|
||||
Enum.join dict, ","
|
||||
end
|
||||
assert_raise UndefinedFunctionError, fn ->
|
||||
assert_raise Protocol.UndefinedError, fn ->
|
||||
Enum.join dict, ','
|
||||
end
|
||||
end
|
||||
@@ -354,90 +392,48 @@ defmodule EnumTest.Dict.Common do
|
||||
end
|
||||
end
|
||||
|
||||
defmodule EnumTest.HashDict do
|
||||
use EnumTest.Dict.Common, HashDict
|
||||
defmodule EnumTest.OrdDict do
|
||||
use EnumTest.Dict.Common, OrdDict
|
||||
|
||||
test :drop do
|
||||
assert_raise Protocol.UndefinedError, fn ->
|
||||
Enum.drop HashDict.new, 5
|
||||
end
|
||||
end
|
||||
|
||||
test :drop_while do
|
||||
assert_raise Protocol.UndefinedError, fn ->
|
||||
Enum.drop_while HashDict.new, fn(x) -> x end
|
||||
end
|
||||
end
|
||||
|
||||
test :first do
|
||||
assert_raise Protocol.UndefinedError, fn ->
|
||||
Enum.first HashDict.new
|
||||
end
|
||||
end
|
||||
|
||||
test :split do
|
||||
assert_raise Protocol.UndefinedError, fn ->
|
||||
Enum.split HashDict.new, 5
|
||||
end
|
||||
end
|
||||
|
||||
test :split_with do
|
||||
assert_raise Protocol.UndefinedError, fn ->
|
||||
Enum.split_with HashDict.new, fn(x) -> x end
|
||||
end
|
||||
end
|
||||
|
||||
test :take do
|
||||
assert_raise Protocol.UndefinedError, fn ->
|
||||
Enum.take HashDict.new, 5
|
||||
end
|
||||
end
|
||||
|
||||
test :take_while do
|
||||
assert_raise Protocol.UndefinedError, fn ->
|
||||
Enum.take_while HashDict.new, fn(x) -> x end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defmodule EnumTest.Orddict do
|
||||
use EnumTest.Dict.Common, Orddict
|
||||
|
||||
test :drop do
|
||||
dict = Orddict.new [a: 1, b: 2, c: 3]
|
||||
dict = OrdDict.new [a: 1, b: 2, c: 3]
|
||||
assert Enum.drop(dict, 2) == [c: 3]
|
||||
assert Enum.drop(dict, 3) == []
|
||||
assert Enum.drop(dict, 4) == []
|
||||
end
|
||||
|
||||
test :drop_while do
|
||||
dict = Orddict.new [a: 1, b: 2, c: 3]
|
||||
dict = OrdDict.new [a: 1, b: 2, c: 3]
|
||||
assert Enum.drop_while(dict, fn({_k, v}) -> v < 3 end) == [c: 3]
|
||||
end
|
||||
|
||||
test :first do
|
||||
dict = Orddict.new []
|
||||
dict = OrdDict.new []
|
||||
assert Enum.first(dict) == nil
|
||||
|
||||
dict = Orddict.new [a: 1, b: 2, c: 3]
|
||||
dict = OrdDict.new [a: 1, b: 2, c: 3]
|
||||
assert Enum.first(dict) == {:a, 1}
|
||||
end
|
||||
|
||||
test :split do
|
||||
dict = Orddict.new [a: 1, b: 2, c: 3]
|
||||
dict = OrdDict.new [a: 1, b: 2, c: 3]
|
||||
assert Enum.split(dict, 1) == { [a: 1], [b: 2, c: 3] }
|
||||
assert Enum.split(dict, 0) == { [], [a: 1, b: 2, c: 3] }
|
||||
assert Enum.split(dict, 3) == { [a: 1, b: 2, c: 3], [] }
|
||||
assert Enum.split(dict, 4) == { [a: 1, b: 2, c: 3], [] }
|
||||
assert Enum.split(dict, -1) == { [a: 1, b: 2], [c: 3] }
|
||||
assert Enum.split(dict, -2) == { [a: 1], [b: 2, c: 3] }
|
||||
assert Enum.split(dict, -3) == { [], [a: 1, b: 2, c: 3] }
|
||||
assert Enum.split(dict, -10) == { [], [a: 1, b: 2, c: 3] }
|
||||
end
|
||||
|
||||
test :split_with do
|
||||
dict = Orddict.new [a: 1, b: 3, c: 2, d: 4]
|
||||
assert Enum.split_with(dict, fn({_k, v}) -> rem(v, 2) == 1 end) == { [a: 1, b: 3], [c: 2, d: 4] }
|
||||
test :split_while do
|
||||
dict = OrdDict.new [a: 1, b: 3, c: 2, d: 4]
|
||||
assert Enum.split_while(dict, fn({_k, v}) -> rem(v, 2) == 1 end) == { [a: 1, b: 3], [c: 2, d: 4] }
|
||||
end
|
||||
|
||||
test :take do
|
||||
dict = Orddict.new [a: 1, b: 2, c: 3, d: 4]
|
||||
dict = OrdDict.new [a: 1, b: 2, c: 3, d: 4]
|
||||
assert Enum.take(dict, 2) == [a: 1, b: 2]
|
||||
assert Enum.take(dict, 0) == []
|
||||
assert Enum.take(dict, 4) == [a: 1, b: 2, c: 3, d: 4]
|
||||
@@ -445,7 +441,7 @@ defmodule EnumTest.Orddict do
|
||||
end
|
||||
|
||||
test :take_while do
|
||||
dict = Orddict.new [a: 1, b: 3, c: 2, d: 4]
|
||||
dict = OrdDict.new [a: 1, b: 3, c: 2, d: 4]
|
||||
assert Enum.take_while(dict, fn({_k, v}) -> rem(v, 2) == 1 end) == [a: 1, b: 3]
|
||||
assert Enum.take_while(dict, fn({_k, v}) -> v < 10 end) == [a: 1, b: 3, c: 2, d: 4]
|
||||
assert Enum.take_while(dict, fn({_k, v}) -> v < 1 end) == []
|
||||
@@ -475,7 +471,22 @@ defmodule EnumTest.Range do
|
||||
assert Enum.any?(range, &1 > 3)
|
||||
|
||||
range = Range.new(first: 1, last: 0)
|
||||
refute Enum.any?(range)
|
||||
assert Enum.any?(range)
|
||||
end
|
||||
|
||||
test :at! do
|
||||
assert Enum.at!(2..6, 0) == 2
|
||||
assert Enum.at!(2..6, 4) == 6
|
||||
assert Enum.at!(-2..-6, 0) == -2
|
||||
assert Enum.at!(-2..-6, 4) == -6
|
||||
|
||||
assert_raise Enum.OutOfBoundsError, fn ->
|
||||
assert Enum.at!(2..6, 8)
|
||||
end
|
||||
assert_raise Enum.OutOfBoundsError, fn ->
|
||||
assert Enum.at!(-2..-6, 8)
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
test :count do
|
||||
@@ -485,6 +496,13 @@ defmodule EnumTest.Range do
|
||||
assert Enum.count(range) == 1
|
||||
end
|
||||
|
||||
test :count_fun do
|
||||
range = Range.new(first: 1, last: 5)
|
||||
assert Enum.count(range, fn(x) -> rem(x, 2) == 0 end) == 2
|
||||
range = Range.new(first: 1, last: 1)
|
||||
assert Enum.count(range, fn(x) -> rem(x, 2) == 0 end) == 0
|
||||
end
|
||||
|
||||
test :drop do
|
||||
range = Range.new(first: 1, last: 3)
|
||||
assert Enum.drop(range, 0) == [1,2,3]
|
||||
@@ -506,7 +524,7 @@ defmodule EnumTest.Range do
|
||||
assert Enum.drop_while(range, fn(x) -> x <= 3 end) == []
|
||||
|
||||
range = Range.new(first: 1, last: 0)
|
||||
assert Enum.drop_while(range, fn(_) -> false end) == []
|
||||
assert Enum.drop_while(range, fn(_) -> false end) == [1,0]
|
||||
end
|
||||
|
||||
test :find do
|
||||
@@ -522,12 +540,12 @@ defmodule EnumTest.Range do
|
||||
|
||||
test :find_index do
|
||||
range = Range.new(first: 2, last: 6)
|
||||
assert Enum.find_index(range, fn(x) -> rem(x, 2) == 1 end) == 2
|
||||
assert Enum.find_index(range, fn(x) -> rem(x, 2) == 1 end) == 1
|
||||
end
|
||||
|
||||
test :empty? do
|
||||
range = Range.new(first: 1, last: 0)
|
||||
assert Enum.empty?(range)
|
||||
refute Enum.empty?(range)
|
||||
|
||||
range = Range.new(first: 1, last: 2)
|
||||
refute Enum.empty?(range)
|
||||
@@ -535,7 +553,7 @@ defmodule EnumTest.Range do
|
||||
|
||||
test :first do
|
||||
range = Range.new(first: 1, last: 0)
|
||||
assert Enum.first(range) == nil
|
||||
assert Enum.first(range) == 1
|
||||
|
||||
range = Range.new(first: 1, last: 2)
|
||||
assert Enum.first(range) == 1
|
||||
@@ -552,6 +570,15 @@ defmodule EnumTest.Range do
|
||||
after
|
||||
Process.delete(:enum_test_each)
|
||||
end
|
||||
|
||||
try do
|
||||
range = Range.new(first: -1, last: -3)
|
||||
assert Enum.each(range, fn(x) -> Process.put(:enum_test_each, x * 2) end) == :ok
|
||||
assert Process.get(:enum_test_each) == -6
|
||||
after
|
||||
Process.delete(:enum_test_each)
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
test :filter do
|
||||
@@ -579,7 +606,7 @@ defmodule EnumTest.Range do
|
||||
|
||||
test :reduce do
|
||||
range = Range.new(first: 1, last: 0)
|
||||
assert Enum.reduce(range, 1, fn(x, acc) -> x + acc end) == 1
|
||||
assert Enum.reduce(range, 1, fn(x, acc) -> x + acc end) == 2
|
||||
|
||||
range = Range.new(first: 1, last: 3)
|
||||
assert Enum.reduce(range, 1, fn(x, acc) -> x + acc end) == 7
|
||||
@@ -587,7 +614,7 @@ defmodule EnumTest.Range do
|
||||
|
||||
test :join_with_bin do
|
||||
range = Range.new(first: 1, last: 0)
|
||||
assert Enum.join(range, " = ") == ""
|
||||
assert Enum.join(range, " = ") == "1 = 0"
|
||||
|
||||
range = Range.new(first: 1, last: 3)
|
||||
assert Enum.join(range, " = ") == "1 = 2 = 3"
|
||||
@@ -596,7 +623,7 @@ defmodule EnumTest.Range do
|
||||
|
||||
test :join_with_list do
|
||||
range = Range.new(first: 1, last: 0)
|
||||
assert Enum.join(range, ' = ') == ''
|
||||
assert Enum.join(range, ' = ') == '1 = 0'
|
||||
|
||||
range = Range.new(first: 1, last: 3)
|
||||
assert Enum.join(range, ' = ') == '1 = 2 = 3'
|
||||
@@ -604,7 +631,7 @@ defmodule EnumTest.Range do
|
||||
|
||||
test :map_join_with_bin do
|
||||
range = Range.new(first: 1, last: 0)
|
||||
assert Enum.map_join(range, " = ", &1 * 2) == ""
|
||||
assert Enum.map_join(range, " = ", &1 * 2) == "2 = 0"
|
||||
|
||||
range = Range.new(first: 1, last: 3)
|
||||
assert Enum.map_join(range, " = ", &1 * 2) == "2 = 4 = 6"
|
||||
@@ -613,37 +640,28 @@ defmodule EnumTest.Range do
|
||||
|
||||
test :map_join_with_list do
|
||||
range = Range.new(first: 1, last: 0)
|
||||
assert Enum.map_join(range, ' = ', &1 * 2) == ''
|
||||
assert Enum.map_join(range, ' = ', &1 * 2) == '2 = 0'
|
||||
|
||||
range = Range.new(first: 1, last: 3)
|
||||
assert Enum.map_join(range, ' = ', &1 * 2) == '2 = 4 = 6'
|
||||
end
|
||||
|
||||
test :map do
|
||||
range = Range.new(first: 1, last: 0)
|
||||
assert Enum.map(range, fn x -> x * 2 end) == []
|
||||
|
||||
range = Range.new(first: 1, last: 3)
|
||||
assert Enum.map(range, fn x -> x * 2 end) == [2,4,6]
|
||||
|
||||
range = Range.new(first: -1, last: -3)
|
||||
assert Enum.map(range, fn x -> x * 2 end) == [-2,-4,-6]
|
||||
end
|
||||
|
||||
test :map_reduce do
|
||||
range = Range.new(first: 1, last: 0)
|
||||
assert Enum.map_reduce(range, 1, fn(x, acc) -> { x * 2, x + acc } end) == { [], 1 }
|
||||
assert Enum.map_reduce(range, 1, fn(x, acc) -> { x * 2, x + acc } end) == { [2, 0], 2 }
|
||||
|
||||
range = Range.new(first: 1, last: 3)
|
||||
assert Enum.map_reduce(range, 1, fn(x, acc) -> { x * 2, x + acc } end) == { [2,4,6], 7 }
|
||||
end
|
||||
|
||||
test :nth! do
|
||||
assert Enum.nth!(2..6, 1) == 2
|
||||
assert Enum.nth!(2..6, 5) == 6
|
||||
|
||||
assert_raise Enum.OutOfBoundsError, fn ->
|
||||
assert Enum.nth!(2..6, 9)
|
||||
end
|
||||
end
|
||||
|
||||
test :partition do
|
||||
range = Range.new(first: 1, last: 3)
|
||||
assert Enum.partition(range, fn(x) -> rem(x, 2) == 0 end) == { [2], [1,3] }
|
||||
@@ -656,21 +674,25 @@ defmodule EnumTest.Range do
|
||||
assert Enum.split(range, 2) == { [1,2], [3] }
|
||||
assert Enum.split(range, 3) == { [1,2,3], [] }
|
||||
assert Enum.split(range, 4) == { [1,2,3], [] }
|
||||
assert Enum.split(range, -1) == { [1,2], [3] }
|
||||
assert Enum.split(range, -2) == { [1], [2,3] }
|
||||
assert Enum.split(range, -3) == { [], [1,2,3] }
|
||||
assert Enum.split(range, -10) == { [], [1,2,3] }
|
||||
|
||||
range = Range.new(first: 1, last: 0)
|
||||
assert Enum.split(range, 3) == { [], [] }
|
||||
assert Enum.split(range, 3) == { [1,0], [] }
|
||||
end
|
||||
|
||||
test :split_with do
|
||||
test :split_while do
|
||||
range = Range.new(first: 1, last: 3)
|
||||
assert Enum.split_with(range, fn(_) -> false end) == { [], [1,2,3] }
|
||||
assert Enum.split_with(range, fn(_) -> true end) == { [1,2,3], [] }
|
||||
assert Enum.split_with(range, fn(x) -> x > 2 end) == { [], [1,2,3] }
|
||||
assert Enum.split_with(range, fn(x) -> x > 3 end) == { [], [1,2,3] }
|
||||
assert Enum.split_with(range, fn(x) -> x < 3 end) == { [1,2], [3] }
|
||||
assert Enum.split_while(range, fn(_) -> false end) == { [], [1,2,3] }
|
||||
assert Enum.split_while(range, fn(_) -> true end) == { [1,2,3], [] }
|
||||
assert Enum.split_while(range, fn(x) -> x > 2 end) == { [], [1,2,3] }
|
||||
assert Enum.split_while(range, fn(x) -> x > 3 end) == { [], [1,2,3] }
|
||||
assert Enum.split_while(range, fn(x) -> x < 3 end) == { [1,2], [3] }
|
||||
|
||||
range = Range.new(first: 1, last: 0)
|
||||
assert Enum.split_with(range, fn(_) -> true end) == { [], [] }
|
||||
assert Enum.split_while(range, fn(_) -> true end) == { [1, 0], [] }
|
||||
end
|
||||
|
||||
test :take do
|
||||
@@ -682,7 +704,7 @@ defmodule EnumTest.Range do
|
||||
assert Enum.take(range, 4) == [1,2,3]
|
||||
|
||||
range = Range.new(first: 1, last: 0)
|
||||
assert Enum.take(range, 3) == []
|
||||
assert Enum.take(range, 3) == [1,0]
|
||||
end
|
||||
|
||||
test :take_while do
|
||||
@@ -694,4 +716,18 @@ defmodule EnumTest.Range do
|
||||
range = Range.new(first: 1, last: 0)
|
||||
assert Enum.take_while([], fn(_) -> true end) == []
|
||||
end
|
||||
end
|
||||
|
||||
test :zip do
|
||||
assert Enum.zip([:a, :b], 1..2) == [{:a, 1}, {:b, 2}]
|
||||
assert Enum.zip([:a, :b], 1..4) == [{:a, 1}, {:b, 2}]
|
||||
assert Enum.zip([:a, :b, :c, :d], 1..2) == [{:a, 1}, {:b, 2}, {:c, nil}, {:d, nil}]
|
||||
|
||||
assert Enum.zip(1..2, [:a, :b]) == [{1, :a}, {2, :b}]
|
||||
assert Enum.zip(1..4, [:a, :b]) == [{1, :a}, {2, :b}, {3, nil}, {4, nil}]
|
||||
assert Enum.zip(1..2, [:a, :b, :c, :d]) == [{1, :a}, {2, :b}]
|
||||
|
||||
assert Enum.zip(1..2, 1..2) == [{1, 1}, {2, 2}]
|
||||
assert Enum.zip(1..4, 1..2) == [{1, 1}, {2, 2}, {3, nil}, {4, nil}]
|
||||
assert Enum.zip(1..2, 1..4) == [{1, 1}, {2, 2}]
|
||||
end
|
||||
end
|
||||
@@ -1,4 +1,4 @@
|
||||
Code.require_file "../test_helper", __FILE__
|
||||
Code.require_file "../test_helper.exs", __FILE__
|
||||
|
||||
defmodule Kernel.ExceptionTest do
|
||||
use ExUnit.Case, async: true
|
||||
@@ -28,6 +28,7 @@ defmodule Kernel.ExceptionTest do
|
||||
end
|
||||
|
||||
test :format_module_function_arity do
|
||||
assert Exception.format_module_fun_arity Foo, nil, 1 == "Foo.nil/1"
|
||||
assert Exception.format_module_fun_arity Foo, :bar, 1 == "Foo.bar/1"
|
||||
assert Exception.format_module_fun_arity Foo, :bar, [] == "Foo.bar()"
|
||||
assert Exception.format_module_fun_arity :foo, :bar, [1,2] == ":foo.bar(1, 2)"
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
Code.require_file "../test_helper", __FILE__
|
||||
Code.require_file "../test_helper.exs", __FILE__
|
||||
|
||||
defmodule FileTest do
|
||||
import PathHelpers
|
||||
@@ -8,6 +8,20 @@ defmodule FileTest do
|
||||
defmodule Cp do
|
||||
use ExUnit.Case
|
||||
|
||||
##
|
||||
## cp_r/c is managed in setup/teardown because if it is stored in
|
||||
## the repository, reltool can't build a release
|
||||
##
|
||||
def setup do
|
||||
src = fixture_path("cp_r")
|
||||
:file.make_symlink 'certainly/invalid', File.join([src, "c"])
|
||||
end
|
||||
|
||||
def teardown do
|
||||
src = fixture_path("cp_r")
|
||||
File.rm File.join([src, "c"])
|
||||
end
|
||||
|
||||
test :cp_with_src_file_and_dest_file do
|
||||
src = fixture_path("foo.txt")
|
||||
dest = tmp_path("sample.txt")
|
||||
@@ -350,6 +364,20 @@ defmodule FileTest do
|
||||
end
|
||||
end
|
||||
|
||||
test :cp_preserves_mode do
|
||||
src = fixture_path("cp_mode")
|
||||
dest = tmp_path("tmp/cp_mode")
|
||||
File.cp! src, dest
|
||||
File.Stat[mode: src_mode] = File.stat! src
|
||||
File.Stat[mode: dest_mode] = File.stat! dest
|
||||
assert src_mode == dest_mode
|
||||
# on overwrite
|
||||
File.cp! src, dest, fn(_,_) -> true end
|
||||
File.Stat[mode: src_mode] = File.stat! src
|
||||
File.Stat[mode: dest_mode] = File.stat! dest
|
||||
assert src_mode == dest_mode
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
defmodule Paths do
|
||||
@@ -756,6 +784,20 @@ defmodule FileTest do
|
||||
defmodule Rm do
|
||||
use ExUnit.Case
|
||||
|
||||
##
|
||||
## cp_r/c is managed in setup/teardown because if it is stored in
|
||||
## the repository, reltool can't build a release
|
||||
##
|
||||
def setup do
|
||||
src = fixture_path("cp_r")
|
||||
:file.make_symlink 'certainly/invalid', File.join([src, "c"])
|
||||
end
|
||||
|
||||
def teardown do
|
||||
src = fixture_path("cp_r")
|
||||
File.rm File.join([src, "c"])
|
||||
end
|
||||
|
||||
test :rm_file do
|
||||
fixture = tmp_path("tmp_test.txt")
|
||||
File.write(fixture, "test")
|
||||
@@ -938,7 +980,7 @@ defmodule FileTest do
|
||||
iterator = File.iterator(src)
|
||||
File.open dest, [:write], fn(target) ->
|
||||
Enum.each iterator, fn(line) ->
|
||||
IO.write target, Regex.replace_all(%r/"/, line, "'")
|
||||
IO.puts target, Regex.replace(%r/"/, line, "'")
|
||||
end
|
||||
end
|
||||
assert File.read(dest) == { :ok, "FOO\n" }
|
||||
@@ -955,7 +997,7 @@ defmodule FileTest do
|
||||
{ :ok, iterator } = File.iterator(src)
|
||||
File.open dest, [:write], fn(target) ->
|
||||
Enum.each iterator, fn(line) ->
|
||||
IO.write target, Regex.replace_all(%r/"/, line, "'")
|
||||
IO.puts target, Regex.replace(%r/"/, line, "'")
|
||||
end
|
||||
end
|
||||
assert File.read(dest) == { :ok, "FOO\n" }
|
||||
@@ -972,7 +1014,7 @@ defmodule FileTest do
|
||||
iterator = File.iterator!(src)
|
||||
File.open dest, [:write], fn(target) ->
|
||||
Enum.each iterator, fn(line) ->
|
||||
IO.write target, Regex.replace_all(%r/"/, line, "'")
|
||||
IO.puts target, Regex.replace(%r/"/, line, "'")
|
||||
end
|
||||
end
|
||||
assert File.read(dest) == { :ok, "FOO\n" }
|
||||
|
||||
Executable
@@ -1 +0,0 @@
|
||||
certainly/invalid
|
||||
@@ -1,4 +1,4 @@
|
||||
Code.require_file "../test_helper", __FILE__
|
||||
Code.require_file "../test_helper.exs", __FILE__
|
||||
|
||||
defmodule IOTest do
|
||||
use ExUnit.Case, async: true
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
Code.require_file "../../test_helper", __FILE__
|
||||
Code.require_file "../../test_helper.exs", __FILE__
|
||||
|
||||
defmodule Kernel.AliasTest.Nested do
|
||||
def value, do: 1
|
||||
@@ -10,7 +10,7 @@ defmodule Kernel.AliasTest do
|
||||
use ExUnit.Case, async: true
|
||||
|
||||
test :alias_erlang do
|
||||
alias Erlang.lists, as: MyList
|
||||
alias :lists, as: MyList
|
||||
assert MyList.flatten([1,[2],3]) == [1,2,3]
|
||||
assert Elixir.MyList.Bar == :"Elixir-MyList-Bar"
|
||||
assert MyList.Bar == :"Elixir-lists-Bar"
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
Code.require_file "../../test_helper", __FILE__
|
||||
Code.require_file "../../test_helper.exs", __FILE__
|
||||
|
||||
defmodule Kernel.CaseTest do
|
||||
use ExUnit.Case, async: true
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
Code.require_file "../../test_helper", __FILE__
|
||||
Code.require_file "../../test_helper.exs", __FILE__
|
||||
|
||||
defmodule Kernel.CLI.InitTest do
|
||||
use ExUnit.Case, async: true
|
||||
@@ -18,7 +18,7 @@ defmodule Kernel.CLI.OptionParsingTest do
|
||||
|
||||
test :path do
|
||||
root = fixture_path("../../..") /> to_char_list
|
||||
list = elixir('-e "IO.inspect Erlang.code.get_path" -pa "#{root}/*" -pz "#{root}/lib/*"')
|
||||
list = elixir('-e "IO.inspect :code.get_path" -pa "#{root}/*" -pz "#{root}/lib/*"')
|
||||
{ path, _ } = Code.eval list, []
|
||||
|
||||
# pa
|
||||
@@ -52,8 +52,8 @@ defmodule Kernel.CLI.ErrorTest do
|
||||
import PathHelpers
|
||||
|
||||
test :code_error do
|
||||
assert Erlang.string.str('** (throw) 1', elixir('-e "throw 1"')) == 0
|
||||
assert Erlang.string.str('** (ErlangError) erlang error: 1', elixir('-e "error 1"')) == 0
|
||||
assert :string.str('** (throw) 1', elixir('-e "throw 1"')) == 0
|
||||
assert :string.str('** (ErlangError) erlang error: 1', elixir('-e "error 1"')) == 0
|
||||
|
||||
# It does not catch exits with integers nor strings...
|
||||
assert elixir('-e "exit 1"') == ''
|
||||
@@ -66,9 +66,9 @@ defmodule Kernel.CLI.SyntaxErrorTest do
|
||||
|
||||
test :syntax_code_error do
|
||||
message = '** (TokenMissingError) nofile:1: syntax error: expression is incomplete'
|
||||
assert Erlang.string.str(message, elixir('-e "[1,2"')) == 0
|
||||
assert :string.str(message, elixir('-e "[1,2"')) == 0
|
||||
message = '** (SyntaxError) nofile:1: syntax error before: \'end\''
|
||||
assert Erlang.string.str(message, elixir('-e "case 1 end"')) == 0
|
||||
assert :string.str(message, elixir('-e "case 1 end"')) == 0
|
||||
end
|
||||
end
|
||||
|
||||
@@ -90,7 +90,7 @@ defmodule Kernel.CLI.ParallelCompilerTest do
|
||||
|
||||
test :compile_code do
|
||||
output = elixirc('#{fixture_path("parallel_compiler")} -o #{tmp_path}')
|
||||
assert Erlang.string.str(output, 'message_from_foo') > 0,
|
||||
assert :string.str(output, 'message_from_foo') > 0,
|
||||
"Expected #{inspect output} to contain 'message_from_foo'"
|
||||
assert File.regular?(tmp_path "Elixir-Foo.beam")
|
||||
assert File.regular?(tmp_path "Elixir-Bar.beam")
|
||||
@@ -100,7 +100,7 @@ defmodule Kernel.CLI.ParallelCompilerTest do
|
||||
output = elixirc('#{fixture_path("parallel_deadlock")} -o #{tmp_path}')
|
||||
foo = '== Compilation error on file #{fixture_path "parallel_deadlock/foo.ex"} (undefined module Bar) =='
|
||||
bar = '== Compilation error on file #{fixture_path "parallel_deadlock/bar.ex"} (undefined module Foo) =='
|
||||
assert Erlang.string.str(output, foo) > 0 or Erlang.string.str(output, bar) > 0,
|
||||
assert :string.str(output, foo) > 0 or :string.str(output, bar) > 0,
|
||||
"Expected #{inspect output} to contain #{inspect foo} or #{inspect bar}"
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
Code.require_file "../../test_helper", __FILE__
|
||||
Code.require_file "../../test_helper.exs", __FILE__
|
||||
|
||||
defmodule Kernel.ComprehensionTest do
|
||||
use ExUnit.Case, async: true
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
Code.require_file "../../test_helper", __FILE__
|
||||
Code.require_file "../../test_helper.exs", __FILE__
|
||||
|
||||
defmodule Kernel.DefClause do
|
||||
defmacro defclause(expr, block) do
|
||||
@@ -11,6 +11,12 @@ defmodule Kernel.ClausesTest do
|
||||
|
||||
import Kernel.DefClause
|
||||
|
||||
# This is just to ensure we won't have warnings
|
||||
# for clauses with match after default.
|
||||
def clause_with_match_after_default(x, y // nil, { z, w }) do
|
||||
x + y + z + w
|
||||
end
|
||||
|
||||
# This is simply testing the warning about
|
||||
# function overriden consider the current stack
|
||||
def foo(0), do: 0
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
Code.require_file "../../test_helper", __FILE__
|
||||
Code.require_file "../../test_helper.exs", __FILE__
|
||||
|
||||
defmodule Kernel.DocTest do
|
||||
use ExUnit.Case
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user