Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b70fc122f8 | ||
|
|
0d5bf3a904 | ||
|
|
66019c36ca | ||
|
|
a73235a2de | ||
|
|
718b111797 | ||
|
|
fd57b5de4f | ||
|
|
bb9569db0c | ||
|
|
74c798585b | ||
|
|
cfdcf685cb | ||
|
|
572726cf65 | ||
|
|
1885841df6 | ||
|
|
80a466af35 | ||
|
|
83db533700 | ||
|
|
49fe9b05a0 | ||
|
|
c8425edb6d | ||
|
|
b9cb17ed39 | ||
|
|
5d48abb9ab | ||
|
|
1403350be0 | ||
|
|
df997f836b | ||
|
|
4544ab59cf | ||
|
|
7614b6c1d0 | ||
|
|
1da1f8c203 | ||
|
|
0addc221e0 | ||
|
|
f6a2046850 | ||
|
|
c345e51772 | ||
|
|
75e22706fc | ||
|
|
eac9929328 | ||
|
|
e1aead5ba3 | ||
|
|
6f5d4d04fd | ||
|
|
13eab25e6d | ||
|
|
2854fec36a | ||
|
|
48208066b7 | ||
|
|
4914c4982d | ||
|
|
14a562f260 | ||
|
|
dd91696184 | ||
|
|
5c55f0fe91 | ||
|
|
a6474aecd4 | ||
|
|
5844e29443 | ||
|
|
38f7d709d1 | ||
|
|
4931629b51 | ||
|
|
343d9ef042 | ||
|
|
703c7dfd21 | ||
|
|
082af1c417 | ||
|
|
dc9caf4260 | ||
|
|
f7fd253918 | ||
|
|
e6be0628ce | ||
|
|
7159da5780 | ||
|
|
5dab337685 | ||
|
|
12f113f6f8 | ||
|
|
c1367548dd | ||
|
|
e40375a65f | ||
|
|
a6bcb014ec | ||
|
|
d3a526ebf0 | ||
|
|
eb8fba694c | ||
|
|
0d3aa7fd66 | ||
|
|
ca7f1fee5a | ||
|
|
55ecdba77c | ||
|
|
53f5473b62 | ||
|
|
4eb9cd3f59 | ||
|
|
bf9e190561 | ||
|
|
0f6459e50d | ||
|
|
30b24b7e2d | ||
|
|
7aa5a89396 | ||
|
|
3ede5ff7f9 | ||
|
|
2f2f2e0a58 | ||
|
|
d20190c99a | ||
|
|
57b5319ae8 | ||
|
|
ecd95a8929 | ||
|
|
f65ef1dd67 | ||
|
|
356b74db01 | ||
|
|
94579848cc | ||
|
|
e0c312bdc7 | ||
|
|
523b7f1f4d | ||
|
|
d436c9728a | ||
|
|
a917c6c0f8 | ||
|
|
8c57450421 | ||
|
|
e999642a8e | ||
|
|
773d4b0a2e | ||
|
|
a79bdd5cdd | ||
|
|
e3a6316a6a | ||
|
|
3b6eafe833 | ||
|
|
1db68b4d71 | ||
|
|
8ee87b9eda | ||
|
|
a6410935c7 | ||
|
|
1ea2fe28e0 | ||
|
|
c7781e2e58 | ||
|
|
69792f36e0 | ||
|
|
62780dc54a | ||
|
|
f6e3b424c4 | ||
|
|
a3021f49e7 | ||
|
|
3dbf899000 | ||
|
|
8bbc42bfda | ||
|
|
dcf6ca1003 | ||
|
|
70f42e22bf | ||
|
|
ac91c25e21 | ||
|
|
427e5ed712 | ||
|
|
b84399fda0 | ||
|
|
400f1522c7 | ||
|
|
3d81da1951 | ||
|
|
b03f069861 | ||
|
|
19e853e895 | ||
|
|
350b01de82 | ||
|
|
69c25b7628 | ||
|
|
c67fba27f2 | ||
|
|
c822b920e0 | ||
|
|
bc55eeb054 | ||
|
|
d776493967 | ||
|
|
6b89a9c726 | ||
|
|
f41aac7b32 | ||
|
|
c5cf8a3470 | ||
|
|
83ceee54b3 | ||
|
|
b7a33832a5 | ||
|
|
ef76cadcb2 | ||
|
|
ebb989d3ed | ||
|
|
00eede696e | ||
|
|
3a94bab1bb | ||
|
|
2564635548 | ||
|
|
657054116b | ||
|
|
b7da298d02 | ||
|
|
a75e70b856 | ||
|
|
aba0d5ba3f | ||
|
|
d334c1d6e4 | ||
|
|
d856e667b9 | ||
|
|
5bd12f9cb9 | ||
|
|
3c151d5357 | ||
|
|
d92175ae41 | ||
|
|
2e9f4196cd | ||
|
|
3f8363c019 | ||
|
|
98d791acb4 | ||
|
|
0134a8f394 | ||
|
|
3d6071d640 | ||
|
|
821b7c6f76 | ||
|
|
9897b894be | ||
|
|
90af53c070 | ||
|
|
2566dd7e24 | ||
|
|
a7e63ae1f6 | ||
|
|
a91e1f2288 | ||
|
|
174f031efd | ||
|
|
f1e2fcf9f9 | ||
|
|
dd47d099c9 | ||
|
|
992ac67512 | ||
|
|
9ff328096b | ||
|
|
6fbc92b088 | ||
|
|
2d1facb17b | ||
|
|
ad4c89714c | ||
|
|
7120a5e050 | ||
|
|
d0fd5ed7f0 | ||
|
|
0b624fff39 | ||
|
|
5c4b8fb2cd | ||
|
|
64296f114d | ||
|
|
8e7b86964e | ||
|
|
ef475b241b | ||
|
|
1ca7c7a16e | ||
|
|
3f71c03547 | ||
|
|
15d2d9f249 | ||
|
|
a31f180dd9 | ||
|
|
e31a06d979 | ||
|
|
49fe6134ee | ||
|
|
9a684f0c0f | ||
|
|
7193753b82 | ||
|
|
e9af1ea988 | ||
|
|
97d4c56f3a | ||
|
|
759539ffe5 | ||
|
|
3bc24dfcf0 | ||
|
|
4ecc939a86 | ||
|
|
030816959c | ||
|
|
feab232bca | ||
|
|
eee4351311 | ||
|
|
6c3664c989 | ||
|
|
737b39850f | ||
|
|
67ac77fdb3 | ||
|
|
0171e201fb | ||
|
|
86b3f72c5d | ||
|
|
ed2c41837a | ||
|
|
5bdcc6643d | ||
|
|
2464cea20f | ||
|
|
4343b8a1e2 | ||
|
|
98a5748376 | ||
|
|
404f77421a | ||
|
|
1e0daad1b1 | ||
|
|
0b3c91bf6d | ||
|
|
446f5dc2c3 | ||
|
|
d421af678a | ||
|
|
e63e4d44a3 | ||
|
|
09414b0ade | ||
|
|
9e6f49a55e | ||
|
|
60455a9cdc | ||
|
|
d054f4f852 | ||
|
|
39b07ae8f7 | ||
|
|
b89309b05a | ||
|
|
2d8268cb8d | ||
|
|
80aac85caf | ||
|
|
874517efd1 | ||
|
|
1963026833 | ||
|
|
edcde3e801 | ||
|
|
088cfe0a4c | ||
|
|
fc42dc9bb7 | ||
|
|
5f9e0c1212 | ||
|
|
7131696621 | ||
|
|
a4234a4494 | ||
|
|
925f08c612 | ||
|
|
16a457f2f2 | ||
|
|
1c3f8a059b | ||
|
|
dca38e4377 | ||
|
|
db41ceaf38 | ||
|
|
b32b6a5108 | ||
|
|
4391247d30 | ||
|
|
e46e1b2907 | ||
|
|
2644060de4 | ||
|
|
295fe4605b | ||
|
|
8d2251f8f1 | ||
|
|
f0771bb392 | ||
|
|
d93258ec69 | ||
|
|
f751deea85 | ||
|
|
f8c894004b | ||
|
|
8b77b603b1 | ||
|
|
38948f7ed7 | ||
|
|
bb5b0b87c2 | ||
|
|
5b42cefb64 | ||
|
|
3ee311cd0b | ||
|
|
18a33427f5 | ||
|
|
d68b9340f0 | ||
|
|
50a2f8a8ea | ||
|
|
bc080f5d0b | ||
|
|
1a0e8378ea | ||
|
|
e2e975a28e | ||
|
|
3b141bb474 | ||
|
|
3b07eb3ceb | ||
|
|
a5f2a04ae3 | ||
|
|
046af30c1b | ||
|
|
71071b6a09 | ||
|
|
8785313413 | ||
|
|
fe2f04837e | ||
|
|
e3da91ad4f | ||
|
|
51d9856638 | ||
|
|
f4fc764789 | ||
|
|
79366ee7fc | ||
|
|
5765db5919 | ||
|
|
ca8a877d69 | ||
|
|
caa2eb1709 | ||
|
|
8be9ceac82 | ||
|
|
4fc3fc11de | ||
|
|
7d2669634b | ||
|
|
70275a5c8c | ||
|
|
0b92acf309 | ||
|
|
c228323228 | ||
|
|
b91ae66e04 | ||
|
|
7981f9df2d | ||
|
|
641b3f0766 | ||
|
|
9a9fc7318c | ||
|
|
95e3fec729 | ||
|
|
e884f84f03 | ||
|
|
3cc8e10525 | ||
|
|
60d11ed7cc | ||
|
|
64754ab7bf | ||
|
|
8099754898 | ||
|
|
4c2301b3a3 | ||
|
|
cd74bd5d54 | ||
|
|
a8debf06ad | ||
|
|
2ca8066c3a | ||
|
|
f0b36dcc4e | ||
|
|
be47b2aaf8 | ||
|
|
bf7662f9ec | ||
|
|
ea9b0e07da | ||
|
|
b343b6c459 | ||
|
|
429548ea75 | ||
|
|
997a0857c3 | ||
|
|
d7ab47cddb | ||
|
|
4e0b1b5150 | ||
|
|
811b60c115 | ||
|
|
203e37d698 | ||
|
|
0ab77cf550 | ||
|
|
a4e69dea6b | ||
|
|
fc762befcc | ||
|
|
2952512330 | ||
|
|
e93bd1e21b | ||
|
|
d50cd8a681 | ||
|
|
356d445f2e | ||
|
|
bf5982c239 | ||
|
|
056069071c | ||
|
|
a4deb46d9e | ||
|
|
7e8b9b6e52 | ||
|
|
1bd5003f9b | ||
|
|
cdd11e7bf1 | ||
|
|
10270da4cf | ||
|
|
9fbe5cc57c | ||
|
|
cbec463554 | ||
|
|
378f6ee0e2 | ||
|
|
2bb10d43da | ||
|
|
3eb89fb31c | ||
|
|
54353f39dc | ||
|
|
45b7debd35 | ||
|
|
79d2ddc46a | ||
|
|
0df9285db1 | ||
|
|
9cc440bfd7 | ||
|
|
404faaf484 | ||
|
|
e2f81270a6 | ||
|
|
b262884a19 | ||
|
|
6126261dea | ||
|
|
76b3a2af69 | ||
|
|
39c1fa91b4 | ||
|
|
bc10b6d848 | ||
|
|
0b51a53fe9 | ||
|
|
af2ce9cfc4 | ||
|
|
53c9dc33e0 | ||
|
|
0870003f1f | ||
|
|
bce28ab58a | ||
|
|
90de6c9a54 | ||
|
|
2eee8f77c7 | ||
|
|
6923e7bfc6 | ||
|
|
3f8d5b59ec | ||
|
|
1317164d11 | ||
|
|
63b2180553 | ||
|
|
e7c3bed4dd | ||
|
|
8683026d05 | ||
|
|
949c45d610 | ||
|
|
3dbdc77550 | ||
|
|
099879ef6d | ||
|
|
e217f7ec5b | ||
|
|
34b9cb67bd | ||
|
|
a3800b0ae5 | ||
|
|
b1b7c82f48 | ||
|
|
f2919e873d | ||
|
|
b90d2608b5 | ||
|
|
04077ec7c2 | ||
|
|
bae4c1c4c8 | ||
|
|
fad38e1bab | ||
|
|
e17df44ef3 | ||
|
|
5aa2340c7d | ||
|
|
65b0bde5e8 | ||
|
|
260b7387df | ||
|
|
d13fe163e3 | ||
|
|
3a834fcd2c | ||
|
|
f0af35d585 | ||
|
|
f510d4c3df | ||
|
|
4eee44fbae | ||
|
|
0c883e1aa2 | ||
|
|
2297f34179 | ||
|
|
a2c1f362c2 | ||
|
|
8575319c0e | ||
|
|
4c67f3daa0 | ||
|
|
3cf9774b92 | ||
|
|
e35ddb6781 | ||
|
|
a0b8067571 | ||
|
|
5f6fa9be11 | ||
|
|
81c6ac2548 | ||
|
|
47ab0c7a3a | ||
|
|
436cdde499 | ||
|
|
7c7c3eb3c2 | ||
|
|
5cb06e9640 | ||
|
|
a7142e2f6d | ||
|
|
36331f0f0b | ||
|
|
4997bc1e10 | ||
|
|
0f14e5001d | ||
|
|
d0f410ccd0 | ||
|
|
cb023c4934 | ||
|
|
6a77057910 | ||
|
|
e2be2579c5 | ||
|
|
f9ec6cfb5f | ||
|
|
cc1d7c5823 | ||
|
|
7649a5dba4 | ||
|
|
d221e9a161 | ||
|
|
4638831103 | ||
|
|
60b2928678 | ||
|
|
3f0c583045 | ||
|
|
0ce2fbb3e4 | ||
|
|
04c5c80353 | ||
|
|
1cb8f3a654 | ||
|
|
7b025dd982 | ||
|
|
eff51e4045 | ||
|
|
ea5df228ed | ||
|
|
769b69ad69 | ||
|
|
0e725fb906 | ||
|
|
4b317a75d8 | ||
|
|
2977a46865 | ||
|
|
d92b2e6831 | ||
|
|
0a7f6e7cd2 | ||
|
|
8bd43ed677 | ||
|
|
bcf8563d38 | ||
|
|
352379112a | ||
|
|
f8c66c8516 | ||
|
|
f090045e99 | ||
|
|
4256daf6a7 | ||
|
|
8c77ada8d2 | ||
|
|
63ed270c68 | ||
|
|
acdc55bd90 | ||
|
|
d2bfd10299 | ||
|
|
b2e32e4ccd | ||
|
|
0c80905f63 | ||
|
|
429fc2094b | ||
|
|
e8c3ed2881 | ||
|
|
3d583e7cdc | ||
|
|
7b1b0d93d8 | ||
|
|
9839bfa4ad | ||
|
|
0b640d985c | ||
|
|
b161d80b4c | ||
|
|
748b70d796 | ||
|
|
0267f312b1 | ||
|
|
8a45ca8905 | ||
|
|
05c3e98f64 | ||
|
|
212acf437a | ||
|
|
fc0af0542b | ||
|
|
14272c98b4 | ||
|
|
42ffd31e0e | ||
|
|
7aeb1b6930 | ||
|
|
f3f64cdba2 | ||
|
|
5b8a30be6b | ||
|
|
e90b877575 | ||
|
|
3d2ad9ac77 | ||
|
|
881bdd5534 | ||
|
|
c3bdfc9ba7 | ||
|
|
262e8b5f22 | ||
|
|
2f5afcaab9 | ||
|
|
4e42b75e17 | ||
|
|
c6f117d60f | ||
|
|
80c0ff84fb | ||
|
|
59b4b5b9bc | ||
|
|
2945939f3e | ||
|
|
3d9aa3e978 | ||
|
|
860fcc6eaa | ||
|
|
73f40cb10e | ||
|
|
8b56252e1b | ||
|
|
b8a0717873 | ||
|
|
8dbfcba237 | ||
|
|
5bb4f43e9d | ||
|
|
808f8981b3 | ||
|
|
73049ac355 | ||
|
|
d77ccf9415 | ||
|
|
9e644dfba1 | ||
|
|
e00eedec45 | ||
|
|
4016864db2 | ||
|
|
38c7edd203 | ||
|
|
fe3ab0cb77 | ||
|
|
9f81ac89e5 | ||
|
|
cd4c1976c9 | ||
|
|
87be134a5b | ||
|
|
a5ea8a63bf | ||
|
|
742258340c | ||
|
|
1be3335f0b | ||
|
|
307198c750 |
+162
-53
@@ -1,6 +1,115 @@
|
||||
# v0.11.2 (2013-11-14)
|
||||
|
||||
* Enhancements
|
||||
* [Mix] Add `mix iex` that redirects users to the proper `iex -S mix` command
|
||||
* [Mix] Support `build_per_environment: true` in project configuration that manages a separete build per environment, useful when you have per-environment behaviour/compilation
|
||||
|
||||
* Backwards incompatible changes
|
||||
* [Mix] Mix now compiles files to `_build`. Projects should update just fine, however documentation and books may want to update to the latest information
|
||||
|
||||
# v0.11.1 (2013-11-07)
|
||||
|
||||
* Enhancements
|
||||
* [Mix] Improve dependency convergence by explicitly checking each requirement instead of expecting all requirements to be equal
|
||||
* [Mix] Support optional dependencies with `optional: true`. Optional dependencies are downloaded for the current project but they are automatically skipped when such project is used as a dependency
|
||||
|
||||
* Bug fixes
|
||||
* [Kernel] Set compilation status per ParallelCompiler and not globally
|
||||
* [Mix] Ensure Mix does not load previous dependencies versions before `deps.get`/`deps.update`
|
||||
* [Mix] Ensure umbrella apps are sorted before running recursive commands
|
||||
* [Mix] Ensure umbrella apps run in the same environment as the parent project
|
||||
* [Mix] Ensure dependency tree is topsorted before compiling
|
||||
* [Mix] Raise error when duplicated projects are pushed into the stack
|
||||
* [URI] Allow lowercase escapes in URI
|
||||
|
||||
* Backwards incompatible changes
|
||||
* [Mix] Setting `:load_paths` in your project configuration is deprecated
|
||||
|
||||
# v0.11.0 (2013-11-02)
|
||||
|
||||
* Enhancements
|
||||
* [Code] Eval now returns variables from other contexts
|
||||
* [Dict] Document and enforce all dicts use the match operator (`===`) when checking for keys
|
||||
* [Enum] Add `Enum.slice/2` with a range
|
||||
* [Enum] Document and enforce `Enum.member?/2` to use the match operator (`===`)
|
||||
* [IEx] Split `IEx.Evaluator` from `IEx.Server` to allow custom evaluators
|
||||
* [IEx] Add support for `IEx.pry` which halts a given process for inspection
|
||||
* [IO] Add specs and allow some IO APIs to receive any data that implements `String.Chars`
|
||||
* [Kernel] Improve stacktraces on command line interfaces
|
||||
* [Kernel] Sigils can now handle balanced tokens as in `%s(f(o)o)`
|
||||
* [Kernel] Emit warnings when an alias is not used
|
||||
* [Macro] Add `Macro.pipe/3` and `Macro.unpipe/1` for building pipelines
|
||||
* [Mix] Allow umbrella children to share dependencies between them
|
||||
* [Mix] Allow mix to be escriptize'd
|
||||
* [Mix] Speed mix projects compilation by relying on more manifests information
|
||||
* [Protocol] Protocols now provide `impl_for/1` and `impl_for!/1` functions which receive a structure and returns its respective implementation, otherwise returns nil or an error
|
||||
* [Set] Document and enforce all sets use the match operator (`===`) when checking for keys
|
||||
* [String] Update to Unicode 6.3.0
|
||||
* [String] Add `String.slice/2` with a range
|
||||
|
||||
* Bug fixes
|
||||
* [Exception] Ensure `defexception` fields can be set dynamically
|
||||
* [Kernel] Guarantee aliases hygiene is respected when the current module name is not known upfront
|
||||
* [Kernel] `Kernel.access/2` no longer flattens lists
|
||||
* [Mix] Ensure cyclic dependencies are properly handled
|
||||
* [String] Implement the extended grapheme cluster algorithm for `String` operations
|
||||
|
||||
* Deprecations
|
||||
* [Kernel] `pid_to_list/1`, `list_to_pid/1`, `binary_to_atom/2`, `binary_to_existing_atom/2` and `atom_to_binary/2` are deprecated in favor of their counterparts in the `:erlang` module
|
||||
* [Kernel] `insert_elem/3` and `delete_elem/2` are deprecated in favor of `Tuple.insert_at/3` and `Tuple.delete_at/2`
|
||||
* [Kernel] Use of `in` inside matches (as in `x in [1,2,3] -> x`) is deprecated in favor of the guard syntax (`x when x in [1,2,3]`)
|
||||
* [Macro] `Macro.expand_all/2` is deprecated
|
||||
* [Protocol] `@only` and `@except` in protocols are now deprecated
|
||||
* [Protocol] Protocols no longer fallback to `Any` out of the box (this functionality needs to be explicitly enabled by setting `@fallback_to_any` to true)
|
||||
* [String] `String.to_integer/1` and `String.to_float/1` are deprecated in favor of `Integer.parse/1` and `Float.parse/1`
|
||||
|
||||
* Backwards incompatible changes
|
||||
* [CLI] Reading `.elixirrc` has been dropped in favor of setting env vars
|
||||
* [Kernel] `Kernel.access/2` now expects the second argument to be a compile time list
|
||||
* [Kernel] `fn -> end` quoted expression is no longer wrapped in a `do` keyword
|
||||
* [Kernel] Quoted variables from the same module must be explicitly shared. Previously, if a function returned `quote do: a = 1`, another function from the same module could access it as `quote do: a`. This has been fixed and the variables must be explicitly shared with `var!(a, __MODULE__)`
|
||||
* [Mix] Umbrella apps now treat children apps as dependencies. This means all dependencies will be checked out in the umbrela `deps` directory. On upgrade, child apps need to point to the umbrella project by setting `deps_path: "../../deps_path", lockfile: "../../mix.lock"` in their project config
|
||||
* [Process] `Process.group_leader/2` args have been reversed so the "subject" comes first
|
||||
* [Protocol] Protocol no longer dispatches to `Number`, but to `Integer` and `Float`
|
||||
|
||||
# v0.10.3 (2013-10-02)
|
||||
|
||||
* Enhancements
|
||||
* [Enum] Add `Enum.take_every/2`
|
||||
* [IEx] IEx now respects signals sent from the Ctrl+G menu
|
||||
* [Kernel] Allow documentation for types with `@typedoc`
|
||||
* [Mix] Allow apps to be selected in umbrella projects
|
||||
* [Record] Generated record functions `new` and `update` also take options with strings as keys
|
||||
* [Stream] Add `Stream.unfold/1`
|
||||
|
||||
* Bug fixes
|
||||
* [Dict] Fix a bug when a HashDict was marked as equal when one was actually a subset of the other
|
||||
* [EEx] Solve issue where `do` blocks inside templates were not properly aligned
|
||||
* [ExUnit] Improve checks and have better error reports on poorly aligned doctests
|
||||
* [Kernel] Fix handling of multiple heredocs on the same line
|
||||
* [Kernel] Provide better error messages for match, guard and quoting errors
|
||||
* [Kernel] Make `Kernel.raise/2` a macro to avoid messing up stacktraces
|
||||
* [Kernel] Ensure `&()` works on quoted blocks with only one expression
|
||||
* [Mix] Address an issue where a dependency was not compiled in the proper order when specified in different projects
|
||||
* [Mix] Ensure `compile: false` is a valid mechanism for disabling the compilation of dependencies
|
||||
* [Regex] Fix bug on `Regex.scan/3` when capturing groups and the regex has no groups
|
||||
* [String] Fix a bug with `String.split/2` when given an empty pattern
|
||||
* [Typespec] Guarantee typespecs error reports point to the proper line
|
||||
|
||||
* Deprecations
|
||||
* [Kernel] The previous partial application syntax (without the `&` operator) has now been deprecated
|
||||
* [Regex] `Regex.captures/3` is deprecated in favor of `Regex.named_captures/3`
|
||||
* [String] `String.valid_codepoint?/1` is deprecated in favor of pattern matching with `<<_ :: utf8 >>`
|
||||
|
||||
* Backwards incompatible changes
|
||||
* [IEx] The `r/0` helper has been removed as it caused surprising behaviour when many modules with dependencies were accumulated
|
||||
* [Mix] `Mix.Version` was renamed to `Version`
|
||||
* [Mix] `File.IteratorError` was renamed to `IO.StreamError`
|
||||
* [Mix] `mix new` now defaults to the `--sup` option, use `--bare` to get the previous behaviour
|
||||
|
||||
# v0.10.2 (2013-09-03)
|
||||
|
||||
* enhancements
|
||||
* Enhancements
|
||||
* [CLI] Add `--verbose` to elixirc, which now is non-verbose by default
|
||||
* [Dict] Add `Dict.Behaviour` as a convenience to create your own dictionaries
|
||||
* [Enum] Add `Enum.split/2`, `Enum.reduce/2`, `Enum.flat_map/2`, `Enum.chunks/2`, `Enum.chunks/4`, `Enum.chunks_by/2`, `Enum.concat/1` and `Enum.concat/2`
|
||||
@@ -24,7 +133,7 @@
|
||||
* [String] Add `String.ljust/2`, `String.rjust/2`, `String.ljust/3` and `String.rjust/3`
|
||||
* [URI] `URI.parse/1` supports IPv6 addresses
|
||||
|
||||
* bug fix
|
||||
* Bug fixes
|
||||
* [Behaviour] Do not compile behaviour docs if docs are disabled on compilation
|
||||
* [ExUnit] Doctests no longer eat too much space and provides detailed reports for poorly indented lines
|
||||
* [File] Fix a bug where `File.touch(file, datetime)` was not setting the proper datetime when the file did not exist
|
||||
@@ -42,7 +151,7 @@
|
||||
* [String] `String.slice/3` still returns the proper result when there is no length to be extracted
|
||||
* [System] `System.get_env/0` now returns a list of tuples as previously advertised
|
||||
|
||||
* deprecations
|
||||
* Deprecations
|
||||
* [Dict] `Dict.update/3` is deprecated in favor of `Dict.update!/3`
|
||||
* [Enum] `Enum.min/2` and `Enum.max/2` are deprecated in favor of `Enum.min_by/2` and `Enum.max_by/2`
|
||||
* [Enum] `Enum.join/2` and `Enum.map_join/3` with a char list are deprecated
|
||||
@@ -55,7 +164,7 @@
|
||||
* [Macro] `Macro.unescape_binary/1` and `Macro.unescape_binary/2` are deprecated in favor of `Macro.unescape_string/1` and `Macro.unescape_string/2`
|
||||
* [Mix] `:umbrella` option for umbrella paths has been deprecated in favor of `:in_umbrella`
|
||||
|
||||
* backwards incompatible changes
|
||||
* Backwards incompatible changes
|
||||
* [IO] IO functions now only accept iolists as arguments
|
||||
* [Kernel] `Binary.Chars` was renamed to `String.Chars`
|
||||
* [Kernel] The previous ambiguous import syntax `import :functions, Foo` was removed in favor of `import Foo, only: :functions`
|
||||
@@ -63,7 +172,7 @@
|
||||
|
||||
# v0.10.1 (2013-08-03)
|
||||
|
||||
* enhancements
|
||||
* Enhancements
|
||||
* [Behaviour] Add support for `defmacrocallback/1`
|
||||
* [Enum] Add `Enum.shuffle/1`
|
||||
* [ExUnit] The `:trace` option now also reports run time for each test
|
||||
@@ -86,7 +195,7 @@
|
||||
* [String] Add `Regex.scan/3` now supports capturing groups
|
||||
* [String] Add `String.reverse/1`
|
||||
|
||||
* bug fix
|
||||
* Bug fixes
|
||||
* [Behaviour] Ensure callbacks are stored in the definition order
|
||||
* [CLI] Speed up boot time on Elixir .bat files
|
||||
* [IEx] Reduce cases where IEx parser can get stuck
|
||||
@@ -101,19 +210,19 @@
|
||||
* [OptionParser] Ensure `:keep` and `:integer`|`:float` can be given together as options
|
||||
* [OptionParser] Ensure `--no-flag` sets `:flag` to false when `:flag` is a registered boolean switch
|
||||
|
||||
* deprecations
|
||||
* Deprecations
|
||||
* [Kernel] `function(Mod.fun/arity)` and `function(fun/arity)` are deprecated in favor of `&Mod.fun/arity` and `&fun/arity`
|
||||
* [Kernel] `function/3` is deprecated in favor of `Module.function/3`
|
||||
* [Kernel] `Kernel.ParallelCompiler` now receives a set of callbacks instead of a single one
|
||||
* [Mix] `:test_coverage` option now expect keywords arguments and the `--cover` flag is now treated as a boolean
|
||||
|
||||
* backwards incompatible changes
|
||||
* Backwards incompatible changes
|
||||
* [Regex] `Regex.scan/3` now always returns a list of lists, normalizing the result, instead of list with mixed lists and binaries
|
||||
* [System] `System.halt/2` was removed since the current Erlang implementation of such function is bugged
|
||||
|
||||
# v0.10.0 (2013-07-15)
|
||||
|
||||
* enhancements
|
||||
* Enhancements
|
||||
* [ExUnit] Support `trace: true` option which gives detailed reporting on test runs
|
||||
* [HashDict] Optimize `HashDict` to store pairs in a cons cell reducing storage per key by half
|
||||
* [Kernel] Add pretty printing support for inspect
|
||||
@@ -126,7 +235,7 @@
|
||||
* [Mix] Projects can now define an `:elixir` key to outline supported Elixir versions
|
||||
* [Typespec] Improve error messages to contain file, line and the typespec itself
|
||||
|
||||
* bug fix
|
||||
* Bug fixes
|
||||
* [CLI] Elixir can now run on Unix directories with `:` in its path
|
||||
* [Kernel] `match?/2` does not leak variables to outer scope
|
||||
* [Kernel] Keep `head|tail` format when splicing at the tail
|
||||
@@ -135,7 +244,7 @@
|
||||
* [Mix] Empty projects no longer leave empty ebin files on `mix compile`
|
||||
* [Module] Calling `Module.register_attribute/3` no longer automatically changes it to persisted or accumulated
|
||||
|
||||
* deprecations
|
||||
* Deprecations
|
||||
* [Enum] Receiving the index of iteration in `Enum.map/2` and `Enum.each/2` is deprecated in favor of `Stream.with_index/1`
|
||||
* [File] `File.iterator/1` and `File.biniterator/1` are deprecated in favor of `IO.stream/1` and `IO.binstream/1`
|
||||
* [File] `File.iterator!/2` and `File.biniterator!/2` are deprecated in favor of `File.stream!/2` and `File.binstream!/2`
|
||||
@@ -144,7 +253,7 @@
|
||||
* [Mix] Deprecate `mix run EXPR` in favor of `mix run -e EXPR`
|
||||
* [Record] `Record.__index__/2` deprecated in favor of `Record.__record__(:index, key)`
|
||||
|
||||
* backwards incompatible changes
|
||||
* Backwards incompatible changes
|
||||
* [Kernel] The `Binary.Inspect` protocol has been renamed to `Inspect`
|
||||
* [Kernel] Tighten up the grammar rules regarding parentheses omission, previously the examples below would compile but now they raise an error message:
|
||||
|
||||
@@ -157,7 +266,7 @@
|
||||
|
||||
# v0.9.3 (2013-06-23)
|
||||
|
||||
* enhancements
|
||||
* Enhancements
|
||||
* [File] Add `File.chgrp`, `File.chmod` and `File.chown`
|
||||
* [Kernel] Add `--warnings-as-errors` to Elixir's compiler options
|
||||
* [Kernel] Print warnings to stderr
|
||||
@@ -171,7 +280,7 @@
|
||||
* [Mix] `github: ...` shortcut now uses the faster `git` schema instead of `https`
|
||||
* [Record] Allow types to be given to `defrecordp`
|
||||
|
||||
* bug fix
|
||||
* Bug fixes
|
||||
* [Kernel] The elixir executable on Windows now supports the same options as the UNIX one
|
||||
* [Kernel] Improve error messages on default clauses clash
|
||||
* [Kernel] `__MODULE__.Foo` now returns `Foo` when outside of a Module
|
||||
@@ -180,12 +289,12 @@
|
||||
* [Kernel] Allow `(x, y) when z` in function clauses and try expressions
|
||||
* [Mix] Mix now properly evaluates rebar scripts
|
||||
|
||||
* deprecations
|
||||
* Deprecations
|
||||
* [Code] `Code.string_to_ast/1` has been deprecated in favor of `Code.string_to_quoted/1`
|
||||
* [Macro] `Macro.to_binary/1` has been deprecated in favor of `Macro.to_string/1`
|
||||
* [Typespec] Deprecate `(fun(...) -> ...)` in favor of `(... -> ...)`
|
||||
|
||||
* backwards incompatible changes
|
||||
* Backwards incompatible changes
|
||||
* [Bitwise] Precedence of operators used by the Bitwise module were changed, check `elixir_parser.yrl` for more information
|
||||
* [File] `rm_rf` and `cp_r` now returns a tuple with three elements on failures
|
||||
* [Kernel] The quoted representation for `->` clauses changed from a tuple with two elements to a tuple with three elements to support metadata
|
||||
@@ -195,7 +304,7 @@
|
||||
|
||||
# v0.9.2 (2013-06-13)
|
||||
|
||||
* enhancements
|
||||
* Enhancements
|
||||
* [ExUnit] `capture_io` now captures prompt by default
|
||||
* [Mix] Automatically import git dependencies from Rebar
|
||||
* [Mix] Support for dependencies directly from the umbrella application
|
||||
@@ -203,7 +312,7 @@
|
||||
* [String] Add `String.contains?`
|
||||
* [URI] Implement `Binary.Chars` (aka `to_binary`) for `URI.Info`
|
||||
|
||||
* bug fix
|
||||
* Bug fixes
|
||||
* [HashDict] Ensure HashDict uses exact match throughout its implementation
|
||||
* [IEx] Do not interpret ANSI codes in IEx results
|
||||
* [IEx] Ensure `--cookie` is set before accessing remote shell
|
||||
@@ -211,21 +320,21 @@
|
||||
* [Mix] Fix usage of shell expressions in `Mix.Shell.cmd`
|
||||
* [Mix] Start the application by default on escripts
|
||||
|
||||
* deprecations
|
||||
* Deprecations
|
||||
* [Regex] `Regex.index/2` is deprecated in favor `Regex.run/3`
|
||||
* [Kernel] `super` no longer supports implicit arguments
|
||||
|
||||
* backwards incompatible changes
|
||||
* Backwards incompatible changes
|
||||
* [Kernel] The `=~` operator now returns true or false instead of an index
|
||||
|
||||
# v0.9.1 (2013-05-30)
|
||||
|
||||
* enhancements
|
||||
* Enhancements
|
||||
* [IEx] Limit the number of entries kept in history and allow it to be configured
|
||||
* [Kernel] Add `String.start_with?` and `String.end_with?`
|
||||
* [Typespec] Allow keywords, e.g. `[foo: integer, bar: boolean | module]`, in typespecs
|
||||
|
||||
* bug fix
|
||||
* Bug fixes
|
||||
* [Dict] `Enum.to_list` and `Dict.to_list` now return the same results for dicts
|
||||
* [IEx] Enable shell customization via the `IEx.Options` module
|
||||
* [Kernel] Fix a bug where `unquote_splicing` did not work on the left side of a stab op
|
||||
@@ -236,12 +345,12 @@
|
||||
|
||||
# v0.9.0 (2013-05-23)
|
||||
|
||||
* enhancements
|
||||
* Enhancements
|
||||
* [ExUnit] `ExUnit.CaptureIO` now accepts an input to be used during capture
|
||||
* [IEx] Add support for .iex files that are loaded during shell's boot process
|
||||
* [IEx] Add `import_file/1` helper
|
||||
|
||||
* backwards incompatible changes
|
||||
* Backwards incompatible changes
|
||||
* [Enum] `Enum.Iterator` was replaced by the more composable and functional `Enumerable` protocol which supports reductions
|
||||
* [File] `File.iterator/1` and `File.biniterator/1` have been removed in favor of the safe `File.iterator!/1` and `File.biniterator!/1` ones
|
||||
* [Kernel] Erlang R15 is no longer supported
|
||||
@@ -249,7 +358,7 @@
|
||||
|
||||
# v0.8.3 (2013-05-22)
|
||||
|
||||
* enhancements
|
||||
* Enhancements
|
||||
* [CLI] Flags `-p` and `-pr` fails if pattern match no files
|
||||
* [CLI] Support `--hidden` and `--cookie` flags for distributed Erlang
|
||||
* [Enum] Add `Enum.to_list/1`, `Enum.member?/2`, `Enum.uniq/2`, `Enum.max/1`, `Enum.max/2`, `Enum.min/1` and `Enum.min/2`
|
||||
@@ -272,7 +381,7 @@
|
||||
* [Mix] Load beam files available at `MIX_PATH` on CLI usage
|
||||
* [String] Add `String.valid?` and `String.valid_character?`
|
||||
|
||||
* bug fix
|
||||
* Bug fixes
|
||||
* [ExUnit] Handle exit messages from in ExUnit
|
||||
* [ExUnit] Failures on ExUnit's setup_all now invalidates all tests
|
||||
* [Kernel] Ensure we don't splice keyword args unecessarily
|
||||
@@ -284,7 +393,7 @@
|
||||
* [Regex] Fix badmatch with `Regex.captures(%r/(.)/g, "cat")`
|
||||
* [URI] Downcase host and scheme and URIs
|
||||
|
||||
* deprecations
|
||||
* Deprecations
|
||||
* [Code] `Code.eval` is deprecated in favor of `Code.eval_string`
|
||||
* [Exception] `Exception.format_entry` is deprecated in favor of `Exception.format_stacktrace_entry`
|
||||
* [ExUnit] `assert left inlist right` is deprecated in favor of `assert left in right`
|
||||
@@ -293,7 +402,7 @@
|
||||
* [Kernel] `var_context` in quote was deprecated in favor of `context`
|
||||
* [Kernel] `Enum.at!` and `Dict.get!` is deprecated in favor of `Enum.fetch!` and `Dict.fetch!`
|
||||
|
||||
* backwards incompatible changes
|
||||
* Backwards incompatible changes
|
||||
* [Dict] `List.Dict` was moved to `ListDict`
|
||||
* [IO] `IO.gets`, `IO.getn` and friends now return binaries when reading from stdio
|
||||
* [Kernel] Precedence of `|>` has changed to lower to support constructs like `1..5 |> Enum.to_list`
|
||||
@@ -301,7 +410,7 @@
|
||||
|
||||
# v0.8.2 (2013-04-20)
|
||||
|
||||
* enhancements
|
||||
* Enhancements
|
||||
* [ExUnit] Use ANSI escape codes in CLI output
|
||||
* [ExUnit] Include suite run time on CLI results
|
||||
* [ExUnit] Add support to doctests, allowing test cases to be generated from code samples
|
||||
@@ -314,7 +423,7 @@
|
||||
* [Mix] Support `--cover` on mix test and `test_coverage` on Mixfiles
|
||||
* [Record] Each record now provides `Record.options` with the options supported by its `new` and `update` functions
|
||||
|
||||
* bug fix
|
||||
* Bug fixes
|
||||
* [Binary] inspect no longer escapes standalone hash `#`
|
||||
* [IEx] The `h` helper can now retrieve docs for special forms
|
||||
* [Kernel] Record optimizations were not being triggered in functions inside the record module
|
||||
@@ -322,11 +431,11 @@
|
||||
* [Kernel] Fix a bug where nested records could not use the Record[] syntax
|
||||
* [Path] Fix a bug on `Path.expand` when expanding paths starting with `~`
|
||||
|
||||
* deprecations
|
||||
* Deprecations
|
||||
* [Kernel] `setelem/3` is deprecated in favor of `set_elem/3`
|
||||
* [Kernel] `function(:is_atom, 1)` is deprecated in favor of `function(is_atom/1)`
|
||||
|
||||
* backwards incompatible changes
|
||||
* Backwards incompatible changes
|
||||
* [Kernel] `unquote` now only applies to the closest quote. If your code contains a quote that contains another quote that calls unquote, it will no longer work. Use `Macro.escape` instead and pass your quoted contents up in steps, for example:
|
||||
|
||||
quote do
|
||||
@@ -341,7 +450,7 @@
|
||||
|
||||
# v0.8.1 (2013-02-17)
|
||||
|
||||
* enhancements
|
||||
* Enhancements
|
||||
* [ExUnit] Tests can now receive metadata set on setup/teardown callbacks
|
||||
* [ExUnit] Add support to ExUnit.CaseTemplate to share callbacks in between test cases
|
||||
* [IO] Add `IO.ANSI` to make it easy to write ANSI escape codes
|
||||
@@ -354,13 +463,13 @@
|
||||
* [OptionParser] Switches are now overridden by default but can be kept in order if chosen
|
||||
* [Typespec] Better error reporting for invalid typespecs
|
||||
|
||||
* bug fix
|
||||
* Bug fixes
|
||||
* [Mix] Allow Mix projects to be generated with just one letter
|
||||
|
||||
* backwards incompatible changes
|
||||
* Backwards incompatible changes
|
||||
* [Kernel] `before_compile` and `after_compile` callbacks now receive the environment as first argument instead of the module
|
||||
|
||||
* deprecations
|
||||
* Deprecations
|
||||
* [ExUnit] Explicitly defined test/setup/teardown functions are deprecated
|
||||
* [Kernel] Tidy up and clean `quote` API
|
||||
* [Kernel] Old `:local.(args)` syntax is deprecated
|
||||
@@ -368,7 +477,7 @@
|
||||
|
||||
# v0.8.0 (2013-01-28)
|
||||
|
||||
* enhancements
|
||||
* Enhancements
|
||||
* [Binary] Support `<< "string" :: utf8 >>` as in Erlang
|
||||
* [Binary] Support `\a` escape character in binaries
|
||||
* [Binary] Support syntax shortcut for specifying size in bit syntax
|
||||
@@ -391,16 +500,16 @@
|
||||
* [String] Add `String.capitalize` and `String.slice`
|
||||
* [System] Add `System.tmp_dir`, `System.cwd` and `System.user_home`
|
||||
|
||||
* bug fix
|
||||
* Bug fixes
|
||||
* [Kernel] `import` with `only` accepts functions starting with underscore
|
||||
* [String] `String.first` and `String.last` return nil for empty binaries
|
||||
* [String] `String.rstrip` and `String.lstrip` now verify if argument is a binary
|
||||
* [Typespec] Support `...` inside typespec's lists
|
||||
|
||||
* backwards incompatible changes
|
||||
* Backwards incompatible changes
|
||||
* [Kernel] The AST now allows metadata to be attached to each node. This means the second item in the AST is no longer an integer (representing the line), but a keywords list. Code that relies on the line information from AST or that manually generate AST nodes need to be properly updated
|
||||
|
||||
* deprecations
|
||||
* Deprecations
|
||||
* [Dict] Deprecate `Binary.Dict` and `OrdDict` in favor of `HashDict` and `ListDict`
|
||||
* [File] Deprecate path related functions in favor of the module `Path`
|
||||
* [Kernel] The `/>` operator has been deprecated in favor of `|>`
|
||||
@@ -410,7 +519,7 @@
|
||||
|
||||
# v0.7.2 (2012-12-04)
|
||||
|
||||
* enhancements
|
||||
* Enhancements
|
||||
* [CLI] `--debug-info` is now true by default
|
||||
* [ExUnit] Make ExUnit exit happen in two steps allowing developers to add custom `at_exit` hooks
|
||||
* [IEx] Many improvements to helpers functions `h/1`, `s/1` and others
|
||||
@@ -423,12 +532,12 @@
|
||||
* [Record] Record accessors are now optimized and can be up to 6x faster in some cases
|
||||
* [String] Support `\xXX` and `\x{HEX}` escape sequences in strings, char lists and regexes
|
||||
|
||||
* bug fix
|
||||
* Bug fixes
|
||||
* [Bootstrap] Compiling Elixir source no longer fails if environment variables contain utf-8 entries
|
||||
* [IEx] IEx will now wait for all command line options to be processed before starting
|
||||
* [Kernel] Ensure proper stacktraces when showing deprecations
|
||||
|
||||
* deprecations
|
||||
* Deprecations
|
||||
* [Enum] `Enum.qsort` is deprecated in favor of `Enum.sort`
|
||||
* [List] `List.sort` and `List.uniq` have been deprecated in favor of their `Enum` counterparts
|
||||
* [Record] Default-based generated functions are deprecated
|
||||
@@ -436,7 +545,7 @@
|
||||
|
||||
# v0.7.1 (2012-11-18)
|
||||
|
||||
* enhancements
|
||||
* Enhancements
|
||||
* [IEx] Only show documented functions and also show docs for default generated functions
|
||||
* [IO] Add `IO.binread`, `IO.binwrite` and `IO.binreadline` to handle raw binary file operations
|
||||
* [ExUnit] Add support for user configuration at `HOME/.ex_unit.exs`
|
||||
@@ -454,20 +563,20 @@
|
||||
* [Typespec] Define a default type `t` for protocols and records
|
||||
* [Typespec] Add support for the access protocol in typespecs
|
||||
|
||||
* bug fix
|
||||
* Bug fixes
|
||||
* [Kernel] Fix an issue where variables inside clauses remained unassigned
|
||||
* [Kernel] Ensure `defoverridable` functions can be referred in many clauses
|
||||
* [Kernel] Allow keywords as function names when following a dot (useful when integrating with erlang libraries)
|
||||
* [File] File is opened by default on binary mode instead of utf-8
|
||||
|
||||
* deprecations
|
||||
* Deprecations
|
||||
* [Behaviour] `defcallback/1` is deprecated in favor of `defcallback/2` which matches erlang `@callbacks`
|
||||
* [Enum] `Enum.times` is deprecated in favor of using ranges
|
||||
* [System] `halt` moved to `System` module
|
||||
|
||||
# v0.7.0 (2012-10-20)
|
||||
|
||||
* enhancements
|
||||
* 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
|
||||
@@ -493,7 +602,7 @@
|
||||
* [Regex] Add `:return` option to `Regex.run` and `Regex.scan`
|
||||
* [String] Add a String module responsible for handling UTf-8 binaries
|
||||
|
||||
* bug fix
|
||||
* Bug fixes
|
||||
* [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
|
||||
@@ -501,12 +610,12 @@
|
||||
* [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
|
||||
* 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
|
||||
* 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`
|
||||
@@ -516,14 +625,14 @@
|
||||
|
||||
# v0.6.0 (2012-08-01)
|
||||
|
||||
* incompatible changes
|
||||
* Backwards incompatible changes
|
||||
* [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
|
||||
* Deprecations
|
||||
* [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`
|
||||
@@ -535,7 +644,7 @@
|
||||
* [Module] `Module.function_defined?` is deprecated in favor of `Module.defines?`
|
||||
* [Module] `Module.defined_functions` is deprecated in favor of `Module.definitions_in`
|
||||
|
||||
* enhancements
|
||||
* Enhancements
|
||||
* [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
|
||||
|
||||
@@ -19,11 +19,14 @@ INSTALL_PROGRAM = $(INSTALL) -m755
|
||||
define APP_TEMPLATE
|
||||
$(1): lib/$(1)/ebin/Elixir.$(2).beam lib/$(1)/ebin/$(1).app
|
||||
|
||||
lib/$(1)/ebin/$(1).app:
|
||||
$(Q) cd lib/$(1) && ../../bin/elixir -e "Mix.Server.start_link(:dev)" -r mix.exs -e "Mix.Task.run('compile.app')"
|
||||
lib/$(1)/ebin/$(1).app: lib/$(1)/mix.exs
|
||||
$(Q) cd lib/$(1) && ../../bin/elixir -e "Mix.Sup.start_link()" -r mix.exs -e "Mix.Task.run('compile.app')"
|
||||
$(Q) cp lib/$(1)/_build/shared/lib/$(1)/ebin/$(1).app lib/$(1)/ebin/$(1).app
|
||||
$(Q) rm -rf lib/$(1)/_build
|
||||
|
||||
lib/$(1)/ebin/Elixir.$(2).beam: $(wildcard lib/$(1)/lib/*.ex) $(wildcard lib/$(1)/lib/*/*.ex) $(wildcard lib/$(1)/lib/*/*/*.ex)
|
||||
@ echo "==> $(1) (compile)"
|
||||
@ rm -rf lib/$(1)/ebin
|
||||
$(Q) $$(ELIXIRC) "lib/$(1)/lib/**/*.ex" -o lib/$(1)/ebin
|
||||
|
||||
test_$(1): $(1)
|
||||
@@ -42,7 +45,9 @@ compile: lib/elixir/src/elixir.app.src erlang elixir
|
||||
|
||||
lib/elixir/src/elixir.app.src: src/elixir.app.src
|
||||
$(Q) rm -rf lib/elixir/src/elixir.app.src
|
||||
$(Q) cp src/elixir.app.src lib/elixir/src/elixir.app.src
|
||||
$(Q) echo "%% This file is automatically generated from <project_root>/src/elixir.app.src" \
|
||||
>lib/elixir/src/elixir.app.src
|
||||
$(Q) cat src/elixir.app.src >>lib/elixir/src/elixir.app.src
|
||||
|
||||
erlang:
|
||||
$(Q) cd lib/elixir && $(REBAR) compile
|
||||
@@ -65,10 +70,10 @@ $(KERNEL): lib/elixir/lib/*.ex lib/elixir/lib/*/*.ex
|
||||
$(Q) cd lib/elixir && $(REBAR) compile
|
||||
|
||||
unicode: $(UNICODE)
|
||||
$(UNICODE): lib/elixir/priv/unicode.ex lib/elixir/priv/UnicodeData.txt lib/elixir/priv/NamedSequences.txt
|
||||
$(UNICODE): lib/elixir/unicode/unicode.ex lib/elixir/unicode/UnicodeData.txt lib/elixir/unicode/GraphemeBreakProperty.txt
|
||||
@ echo "==> unicode (compile)";
|
||||
@ echo "This step can take up to a minute to compile in order to embed the Unicode database"
|
||||
$(Q) $(ELIXIRC) lib/elixir/priv/unicode.ex -o lib/elixir/ebin;
|
||||
$(Q) $(ELIXIRC) lib/elixir/unicode/unicode.ex -o lib/elixir/ebin;
|
||||
|
||||
$(eval $(call APP_TEMPLATE,ex_unit,ExUnit))
|
||||
$(eval $(call APP_TEMPLATE,eex,EEx))
|
||||
|
||||
@@ -10,11 +10,13 @@ If you want to contribute to Elixir or run it from source, clone this repository
|
||||
|
||||
$ git clone https://github.com/elixir-lang/elixir.git
|
||||
$ cd elixir
|
||||
$ make test
|
||||
$ make clean test
|
||||
|
||||
If Elixir fails to build (specifically when pulling in a new version via git), be sure to remove any previous build artifacts by running `make clean`, then `make test`.
|
||||
|
||||
If tests pass, you are ready to move on to the [Getting Started guide][1] or to try Interactive Elixir by running: `bin/iex` in your terminal.
|
||||
|
||||
However, if tests fail, it is likely you have an outdated Erlang version (Elixir requires Erlang R16B or later). You can check your Erlang version by calling `erl` in the command line. You will see some information as follow:
|
||||
However, if tests fail, it is likely you have an outdated Erlang version (Elixir requires Erlang R16B or later). You can check your Erlang version by calling `erl` in the command line. You will see some information as follows:
|
||||
|
||||
Erlang R16B (erts-5.10.1) [source] [64-bit] [smp:2:2] [rq:2] [async-threads:0] [hipe] [kernel-poll:false]
|
||||
|
||||
|
||||
+2
-2
@@ -10,7 +10,7 @@ This document simply outlines the release process:
|
||||
|
||||
4) Commit changes above with title "Release vVERSION"
|
||||
|
||||
5) Push master and create tag from master branch
|
||||
5) Push master and create tag vVERSION from master branch
|
||||
|
||||
6) Release new docs with `make release_docs`, move docs to `docs/stable`
|
||||
|
||||
@@ -20,7 +20,7 @@ This document simply outlines the release process:
|
||||
|
||||
9) Merge master into stable branch and push it
|
||||
|
||||
10) After release, bump versions and add `-dev` back
|
||||
10) After release, bump versions, add `-dev` back and commit
|
||||
|
||||
11) `make release_docs` once again and push `elixir-lang.github.com`
|
||||
|
||||
|
||||
@@ -73,8 +73,6 @@ done
|
||||
|
||||
SELF=$(readlink_f "$0")
|
||||
SCRIPT_PATH=$(dirname "$SELF")
|
||||
|
||||
if [ -f "$HOME/.elixirrc" ]; then . "$HOME/.elixirrc"; fi
|
||||
if [ "$ELIXIR_NO_CLI" != "1" ]; then ERL="$ERL -s elixir start_cli"; fi
|
||||
|
||||
if [ -f "$SCRIPT_PATH/../releases/RELEASES" ] && [ -f "$SCRIPT_PATH/erl" ]
|
||||
|
||||
@@ -38,4 +38,4 @@ readlink_f () {
|
||||
|
||||
SELF=$(readlink_f "$0")
|
||||
SCRIPT_PATH=$(dirname "$SELF")
|
||||
ELIXIR_NO_CLI=1 exec "$SCRIPT_PATH"/elixir --no-halt --erl "-nouser -s Elixir.IEx.CLI" +iex "$@"
|
||||
ELIXIR_NO_CLI=1 exec "$SCRIPT_PATH"/elixir --no-halt --erl "-user Elixir.IEx.CLI" +iex "$@"
|
||||
|
||||
@@ -31,7 +31,7 @@ defmodule EEx.Compiler do
|
||||
defp generate_buffer([{ :start_expr, line, mark, chars }|t], buffer, scope, state) do
|
||||
{ contents, t } = generate_buffer(t, "", [chars|scope], state.dict([]).line(line).start_line(line))
|
||||
buffer = state.engine.handle_expr(buffer, mark, contents)
|
||||
generate_buffer(t, buffer, scope, state.dict([]))
|
||||
generate_buffer(t, buffer, scope, state)
|
||||
end
|
||||
|
||||
defp generate_buffer([{ :middle_expr, line, _, chars }|t], buffer, [current|scope], state) do
|
||||
@@ -68,7 +68,7 @@ defmodule EEx.Compiler do
|
||||
else
|
||||
key = length(state.dict)
|
||||
placeholder = '__EEX__(' ++ integer_to_list(key) ++ ');'
|
||||
{ current ++ placeholder ++ new_lines ++ chars, state.update_dict([{key, buffer}|&1]) }
|
||||
{ current ++ placeholder ++ new_lines ++ chars, state.update_dict(&[{key, buffer}|&1]) }
|
||||
end
|
||||
end
|
||||
|
||||
@@ -108,7 +108,7 @@ defmodule EEx.Compiler do
|
||||
end
|
||||
|
||||
defp insert_quotes(list, dict) when is_list(list) do
|
||||
Enum.map list, insert_quotes(&1, dict)
|
||||
Enum.map list, &insert_quotes(&1, dict)
|
||||
end
|
||||
|
||||
defp insert_quotes(other, _dict) do
|
||||
|
||||
@@ -103,7 +103,7 @@ defmodule EEx.Tokenizer do
|
||||
# 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 } ->
|
||||
{ :ok, _line, tokens } ->
|
||||
tokens = Enum.reverse(tokens)
|
||||
fn_index = fn_index(tokens)
|
||||
|
||||
@@ -136,7 +136,7 @@ defmodule EEx.Tokenizer do
|
||||
end
|
||||
|
||||
defp end_index(tokens) do
|
||||
Enum.find_index(tokens, match?({ :end, _ }, &1)) || :infinity
|
||||
Enum.find_index(tokens, &match?({ :end, _ }, &1)) || :infinity
|
||||
end
|
||||
|
||||
defp check_spaces(string, token) do
|
||||
|
||||
@@ -264,6 +264,21 @@ foo
|
||||
assert_eval expected, string
|
||||
end
|
||||
|
||||
test "evaluates nested do expressions" do
|
||||
string = """
|
||||
<% y = ["a", "b", "c"] %>
|
||||
<%= cond do %>
|
||||
<% "a" in y -> %>
|
||||
Good
|
||||
<% true -> %>
|
||||
<% if true do %>true<% else %>false<% end %>
|
||||
Bad
|
||||
<% end %>
|
||||
"""
|
||||
|
||||
assert_eval "\n\n Good\n \n", string
|
||||
end
|
||||
|
||||
test "unicode" do
|
||||
template = """
|
||||
• <%= "•" %> •
|
||||
|
||||
@@ -17,7 +17,6 @@
|
||||
module=nil, %% the current module
|
||||
function=nil, %% the current function
|
||||
vars=[], %% a dict of defined variables and their alias
|
||||
list_vars=nil, %% a list of vars passed down to Macro.Env
|
||||
backup_vars=nil, %% a copy of vars to be used on ^var
|
||||
temp_vars=nil, %% a set of all variables defined in a particular assign
|
||||
clause_vars=nil, %% a dict of all variables defined in a particular clause
|
||||
@@ -26,6 +25,8 @@
|
||||
local=nil, %% the scope to evaluate local functions against
|
||||
context_modules=[], %% modules defined in the current context
|
||||
macro_aliases=[], %% keep aliases defined inside a macro
|
||||
macro_counter=0, %% macros expansions counter
|
||||
lexical_tracker=nil, %% holds the lexical tracker pid
|
||||
aliases, %% an orddict with aliases by new -> old names
|
||||
file, %% the current scope filename
|
||||
requires, %% a set with modules required
|
||||
@@ -36,7 +37,7 @@
|
||||
}).
|
||||
|
||||
-record(elixir_quote, {
|
||||
line=0,
|
||||
line=nil,
|
||||
context=nil,
|
||||
vars_hygiene=true,
|
||||
aliases_hygiene=true,
|
||||
@@ -46,6 +47,13 @@
|
||||
escape=false
|
||||
}).
|
||||
|
||||
-record(elixir_tokenizer, {
|
||||
file,
|
||||
terminators=[],
|
||||
check_terminators=true,
|
||||
existing_atoms_only=false
|
||||
}).
|
||||
|
||||
%% Introspection
|
||||
-define(defs(Kind), Kind == def; Kind == defp; Kind == defmacro; Kind == defmacrop).
|
||||
|
||||
|
||||
@@ -11,8 +11,6 @@ defprotocol Access do
|
||||
following built-in types: keywords, records and functions.
|
||||
"""
|
||||
|
||||
@only [List, Record, Atom]
|
||||
|
||||
@doc """
|
||||
Receives the element being accessed and the access item.
|
||||
"""
|
||||
@@ -22,6 +20,7 @@ end
|
||||
defimpl Access, for: List do
|
||||
@doc """
|
||||
Access the given key in a tuple list.
|
||||
The key is found via the `===` operator.
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -34,14 +33,10 @@ defimpl Access, for: List do
|
||||
"★☆"
|
||||
|
||||
"""
|
||||
def access(dict, key)
|
||||
def access([{ key, value }|_], key), do: value
|
||||
def access([{ _, _ }|t], key), do: access(t, key)
|
||||
def access([], _key), do: nil
|
||||
|
||||
def access(list, key) do
|
||||
case :lists.keyfind(key, 1, list) do
|
||||
{ ^key, value } -> value
|
||||
false -> nil
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defimpl Access, for: Atom do
|
||||
|
||||
@@ -1,40 +1,40 @@
|
||||
defmodule Application.Behaviour do
|
||||
@moduledoc """
|
||||
This module is a convenience to define application module callbacks.
|
||||
Default callbacks for applications.
|
||||
|
||||
In Erlang/OTP, an application is a component that can be started
|
||||
and stopped as a unit, and which can be re-used in other systems
|
||||
as well.
|
||||
and stopped as a unit, and which can be reused in other systems.
|
||||
|
||||
The first step to achieve this is to define an application specification.
|
||||
The first step in creating an application is to define an application specification.
|
||||
For example, if your application is named `:my_app`, an app specification
|
||||
should exist at `ebin/my_app.app`. This file is usually defined by
|
||||
build tools like Mix.
|
||||
|
||||
Then, with the app specification in hands, we must also define an
|
||||
application module callback that controls how to start and stop
|
||||
such applications. This module is about defining such callbacks.
|
||||
With the app specification in hand, we must define
|
||||
application module callbacks that control how to start and stop
|
||||
instances of the application. This module is about defining such callbacks.
|
||||
|
||||
There are two callbacks required to be implemented:
|
||||
There are two callbacks which must be implemented:
|
||||
|
||||
1. `start(type, args)` - It must return `{ :ok, pid }` or
|
||||
1. `start(type, args)` - must return `{ :ok, pid }` or
|
||||
`{ :ok, pid, state }`, where `pid` is the process identifier
|
||||
of the supervisor tree root;
|
||||
of the supervisor tree root and `state` is application defined
|
||||
state information;
|
||||
|
||||
2. `stop(state)` receives the state returned by `start` and should
|
||||
do any necessary cleaning up. Notice that shutting down the supervisor
|
||||
2. `stop(state)` - receives the `state` returned by `start` and should
|
||||
do any necessary clean up. Notice that shutting down the supervisor
|
||||
is automatically handled by the VM;
|
||||
|
||||
When using this module, it simply tags the module behaviour as
|
||||
`:application` and defines a default `stop/1` callback. The `start/2`
|
||||
still needs to be defined by the user.
|
||||
When using this module, it tags the module behaviour as
|
||||
`:application` and provides a default `stop/1` callback. The `start/2` callback
|
||||
still needs to be implemented by the user.
|
||||
|
||||
You can learn more about the `:application` module, the application
|
||||
specification and the application module callbacks below:
|
||||
specification and the application module callbacks from these sources:
|
||||
|
||||
http://www.erlang.org/doc/man/application.html
|
||||
http://www.erlang.org/doc/design_principles/applications.html
|
||||
http://learnyousomeerlang.com/building-otp-applications
|
||||
* http://www.erlang.org/doc/man/application.html
|
||||
* http://www.erlang.org/doc/design_principles/applications.html
|
||||
* http://learnyousomeerlang.com/building-otp-applications
|
||||
|
||||
## Example
|
||||
|
||||
@@ -102,4 +102,4 @@ defmodule Application.Behaviour do
|
||||
defoverridable [stop: 1]
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
+12
-11
@@ -1,12 +1,12 @@
|
||||
defmodule Behaviour do
|
||||
@moduledoc """
|
||||
A convenience module for defining behaviours.
|
||||
Utilities for defining behaviour intefaces.
|
||||
|
||||
Behaviours can be referenced by other modules
|
||||
in order to ensure they implement the proper
|
||||
callbacks.
|
||||
to ensure they implement required callbacks.
|
||||
|
||||
For example, you can specify the `URI.Parser`
|
||||
behaviour as follow:
|
||||
behaviour as follows:
|
||||
|
||||
defmodule URI.Parser do
|
||||
use Behaviour
|
||||
@@ -18,7 +18,7 @@ defmodule Behaviour do
|
||||
defcallback default_port() :: integer
|
||||
end
|
||||
|
||||
And then a specific module may use it as:
|
||||
And then a module may use it as:
|
||||
|
||||
defmodule URI.HTTP do
|
||||
@behaviour URI.Parser
|
||||
@@ -26,24 +26,25 @@ defmodule Behaviour do
|
||||
def parse(info), do: info
|
||||
end
|
||||
|
||||
In case the behaviour changes or URI.HTTP does
|
||||
If the behaviour changes or `URI.HTTP` does
|
||||
not implement one of the callbacks, a warning
|
||||
will be raised.
|
||||
|
||||
## Implementation
|
||||
|
||||
Behaviours since Erlang R15 must be defined via
|
||||
Since Erlang R15, behaviours must be defined via
|
||||
`@callback` attributes. `defcallback` is a simple
|
||||
mechanism that defines the `@callback` attribute
|
||||
according to the type specification and also allows
|
||||
docs and defines a custom function signature.
|
||||
according to the given type specification. `defcallback` allows
|
||||
documentsion to be created for the callback and defines
|
||||
a custom function signature.
|
||||
|
||||
The callbacks and their documentation can be retrieved
|
||||
via the `__behaviour__` callback function.
|
||||
"""
|
||||
|
||||
@doc """
|
||||
Defines a callback according to the given type specification.
|
||||
Define a function callback according to the given type specification.
|
||||
"""
|
||||
defmacro defcallback({ :::, _, [fun, return] }) do
|
||||
do_defcallback(fun, return, __CALLER__)
|
||||
@@ -54,7 +55,7 @@ defmodule Behaviour do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Defines a macro callback according to the given type specification.
|
||||
Define a macro callback according to the given type specification.
|
||||
"""
|
||||
defmacro defmacrocallback({ :::, _, [fun, return] }) do
|
||||
do_defmacrocallback(fun, return, __CALLER__)
|
||||
|
||||
+75
-43
@@ -6,24 +6,24 @@ defmodule Code do
|
||||
end
|
||||
|
||||
@moduledoc """
|
||||
The Code module is responsible for managing code compilation,
|
||||
code evaluation and code loading.
|
||||
Utilities for managing code compilation, code evaluation and code loading.
|
||||
|
||||
It complements [Erlang's code module](http://www.erlang.org/doc/man/code.html)
|
||||
This module complements [Erlang's code module](http://www.erlang.org/doc/man/code.html)
|
||||
to add behavior which is specific to Elixir.
|
||||
|
||||
"""
|
||||
|
||||
@doc """
|
||||
Returns all loaded files.
|
||||
List all loaded files.
|
||||
"""
|
||||
def loaded_files do
|
||||
:elixir_code_server.call :loaded
|
||||
end
|
||||
|
||||
@doc """
|
||||
Removes the given files from the loaded files list.
|
||||
The modules defined in the file are not removed,
|
||||
Remove files from the loaded files list.
|
||||
|
||||
The modules defined in the file are not removed;
|
||||
calling this function only removes them from the list,
|
||||
allowing them to be required again.
|
||||
"""
|
||||
@@ -32,7 +32,8 @@ defmodule Code do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Appends a path to the Erlang VM code path.
|
||||
Append a path to the Erlang VM code path.
|
||||
|
||||
The path is expanded with `Path.expand/1` before being appended.
|
||||
"""
|
||||
def append_path(path) do
|
||||
@@ -40,7 +41,8 @@ defmodule Code do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Prepends a path to the Erlang VM code path.
|
||||
Prepend a path to the Erlang VM code path.
|
||||
|
||||
The path is expanded with `Path.expand/1` before being prepended.
|
||||
"""
|
||||
def prepend_path(path) do
|
||||
@@ -48,7 +50,8 @@ defmodule Code do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Deletes a path from the Erlang VM code path.
|
||||
Delete a path from the Erlang VM code path.
|
||||
|
||||
The path is expanded with `Path.expand/1` before being deleted.
|
||||
"""
|
||||
def delete_path(path) do
|
||||
@@ -56,9 +59,12 @@ defmodule Code do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Evaluates the contents given by `string`. The second argument is
|
||||
a keyword list of variable bindings, followed by a keyword list of
|
||||
environment options. Those options can be:
|
||||
Evaluate the contents given by `string`.
|
||||
|
||||
The `binding` argument is a keyword list of variable bindings.
|
||||
The `opts` argument is a keyword list of environment options.
|
||||
|
||||
Those options can be:
|
||||
|
||||
* `:file` - the file to be considered in the evaluation
|
||||
* `:line` - the line on which the script starts
|
||||
@@ -83,9 +89,12 @@ defmodule Code do
|
||||
etc.
|
||||
|
||||
Returns a tuple of the form `{ value, binding }`,
|
||||
where `value` is the the value returned from evaluating `string`; `binding`
|
||||
is a keyword list with the value of all variable bindings after evaluating
|
||||
`string`. If an error occurs while evaluating `string` an exception will be raised.
|
||||
where `value` is the the value returned from evaluating `string`.
|
||||
If an error occurs while evaluating `string` an exception will be raised.
|
||||
|
||||
`binding` is a keyword list with the value of all variable bindings
|
||||
after evaluating `string`. The binding key is usually an atom, but it
|
||||
may be a tuple for variables defined in a different context.
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -95,7 +104,7 @@ defmodule Code do
|
||||
iex> Code.eval_string("c = a + b", [a: 1, b: 2], __ENV__)
|
||||
{3, [a: 1, b: 2, c: 3]}
|
||||
|
||||
iex> Code.eval_string("a = a + b", [a: 1, b: 2])
|
||||
iex> Code.eval_string("a = a + b", [a: 1, b: 2])
|
||||
{3, [a: 3, b: 2]}
|
||||
|
||||
For convenience, you can pass `__ENV__` as the `opts` argument and
|
||||
@@ -123,7 +132,7 @@ defmodule Code do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Evaluates the quoted contents.
|
||||
Evaluate the quoted contents.
|
||||
|
||||
See `eval_string/3` for a description of arguments and return values.
|
||||
|
||||
@@ -166,10 +175,10 @@ defmodule Code do
|
||||
end
|
||||
|
||||
defp validate_requires(kind, requires) do
|
||||
valid = is_list(requires) and Enum.all?(requires, is_atom(&1))
|
||||
valid = is_list(requires) and Enum.all?(requires, &is_atom(&1))
|
||||
|
||||
unless valid do
|
||||
raise ArgumentError, "expected :#{kind} option given to eval in the format: [module]"
|
||||
raise ArgumentError, message: "expected :#{kind} option given to eval in the format: [module]"
|
||||
end
|
||||
end
|
||||
|
||||
@@ -179,7 +188,7 @@ defmodule Code do
|
||||
end)
|
||||
|
||||
unless valid do
|
||||
raise ArgumentError, "expected :#{kind} option given to eval in the format: [{ module, module }]"
|
||||
raise ArgumentError, message: "expected :#{kind} option given to eval in the format: [{ module, module }]"
|
||||
end
|
||||
end
|
||||
|
||||
@@ -191,12 +200,14 @@ defmodule Code do
|
||||
end)
|
||||
|
||||
unless valid do
|
||||
raise ArgumentError, "expected :#{kind} option given to eval in the format: [{ module, [{ name, arity }] }]"
|
||||
raise ArgumentError, message: "expected :#{kind} option given to eval in the format: [{ module, [{ name, arity }] }]"
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Converts the given string to its quoted form. Returns `{ :ok, quoted_form }`
|
||||
Convert the given string to its quoted form.
|
||||
|
||||
Returns `{ :ok, quoted_form }`
|
||||
if it succeeds, `{ :error, { line, error, token } }` otherwise.
|
||||
|
||||
## Options
|
||||
@@ -209,10 +220,10 @@ defmodule Code do
|
||||
* `:existing_atoms_only` - When `true`, raises an error
|
||||
when non-existing atoms are found by the tokenizer.
|
||||
|
||||
## Macro.to_string/1
|
||||
## Macro.to_string/2
|
||||
|
||||
The opposite of converting a string to its quoted form is
|
||||
`Macro.to_string/1`, which converts a quoted form to a string/binary
|
||||
`Macro.to_string/2`, which converts a quoted form to a string/binary
|
||||
representation.
|
||||
"""
|
||||
def string_to_quoted(string, opts // []) when is_list(opts) do
|
||||
@@ -227,7 +238,9 @@ defmodule Code do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Converts the given string to its quoted form. It returns the ast if it succeeds,
|
||||
Convert the given string to its quoted form.
|
||||
|
||||
It returns the ast if it succeeds,
|
||||
raises an exception otherwise. The exception is a `TokenMissingError`
|
||||
in case a token is missing (usually because the expression is incomplete),
|
||||
`SyntaxError` otherwise.
|
||||
@@ -246,7 +259,9 @@ defmodule Code do
|
||||
defp unpack_quote(line, forms), do: { :__block__, [line: line], forms }
|
||||
|
||||
@doc """
|
||||
Loads the given `file`. Accepts `relative_to` as an argument to tell where
|
||||
Load the given file.
|
||||
|
||||
Accepts `relative_to` as an argument to tell where
|
||||
the file is located. If the file was already required/loaded, loads it again.
|
||||
It returns a list of tuples `{ ModuleName, <<byte_code>> }`, one tuple for each
|
||||
module defined in the file.
|
||||
@@ -266,9 +281,11 @@ defmodule Code do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Requires the given `file`. Accepts `relative_to` as an argument to tell where
|
||||
Require the given `file`.
|
||||
|
||||
Accepts `relative_to` as an argument to tell where
|
||||
the file is located. The return value is the same as that of `load_file/2`. If
|
||||
the file was already required/loaded, doesn't do anything and returns nil.
|
||||
the file was already required/loaded, doesn't do anything and returns `nil`.
|
||||
|
||||
Notice that if `require_file` is invoked by different processes concurrently,
|
||||
the first process to invoke `require_file` acquires a lock and the remaining
|
||||
@@ -294,7 +311,8 @@ defmodule Code do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Loads the compilation options from the code server.
|
||||
Load the compilation options from the code server.
|
||||
|
||||
Check `compiler_options/1` for more information.
|
||||
"""
|
||||
def compiler_options do
|
||||
@@ -302,8 +320,9 @@ defmodule Code do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Sets compilation options. These options are global
|
||||
since they are stored by Elixir's Code Server.
|
||||
Set compilation options.
|
||||
|
||||
These options are global since they are stored by Elixir's Code Server.
|
||||
|
||||
Available options are:
|
||||
|
||||
@@ -312,7 +331,7 @@ defmodule Code do
|
||||
|
||||
* `:debug_info` - when `true`, retain debug information in the compiled module.
|
||||
This allows a developer to reconstruct the original source
|
||||
code, for such reasons, `false` by default;
|
||||
code, `false` by default;
|
||||
|
||||
* `:ignore_module_conflict` - when `true`, override modules that were already defined
|
||||
without raising errors, `false` by default;
|
||||
@@ -325,7 +344,9 @@ defmodule Code do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Compiles the given string and returns a list of tuples where
|
||||
Compile the given string.
|
||||
|
||||
Returns a list of tuples where
|
||||
the first element is the module name and the second one is its
|
||||
binary.
|
||||
|
||||
@@ -336,7 +357,9 @@ defmodule Code do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Compiles the quoted expression and returns a list of tuples where
|
||||
Compile the quoted expression.
|
||||
|
||||
Returns a list of tuples where
|
||||
the first element is the module name and the second one is its
|
||||
binary.
|
||||
"""
|
||||
@@ -345,8 +368,10 @@ defmodule Code do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Ensures the given module is loaded. If the module is already
|
||||
loaded, it works as no-op. If the module was not yet loaded,
|
||||
Ensure the given module is loaded.
|
||||
|
||||
If the module is already
|
||||
loaded, this works as no-op. If the module was not yet loaded,
|
||||
it tries to load it.
|
||||
|
||||
If it succeeds loading the module, it returns
|
||||
@@ -362,7 +387,7 @@ defmodule Code do
|
||||
modules need to be loaded upfront or explicitly.
|
||||
|
||||
Therefore, this function is used to check if a module is loaded
|
||||
before using it and allows one to react accordingly. For example, the `URI`
|
||||
before using it and allows one to react accordingly. For example, the `URI`
|
||||
module uses this function to check if a specific parser exists for a given
|
||||
URI scheme.
|
||||
|
||||
@@ -387,17 +412,21 @@ defmodule Code do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Similar to `ensure_loaded/1`, but returns a boolean in case
|
||||
it could be ensured or not.
|
||||
Ensure the given module is loaded.
|
||||
|
||||
Similar to `ensure_loaded/1`, but returns `true` if the module
|
||||
is already loaded or was successfully loaded. Returns `false` otherwise.
|
||||
"""
|
||||
def ensure_loaded?(module) do
|
||||
match?({ :module, ^module }, ensure_loaded(module))
|
||||
end
|
||||
|
||||
@doc """
|
||||
Ensures the given module is compiled and loaded. If the module
|
||||
Ensure the given module is compiled and loaded.
|
||||
|
||||
If the module
|
||||
is already loaded, it works as no-op. If the module was not
|
||||
loaded yet, it checks if it needs to be compiled first and
|
||||
loaded yet, it checks if it needs to be compiled first and
|
||||
then tries to load it.
|
||||
|
||||
If it succeeds loading the module, it returns
|
||||
@@ -425,8 +454,11 @@ defmodule Code do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Similar to `ensure_compiled/1`, but returns a boolean in case
|
||||
it could be ensured or not.
|
||||
Ensure the given module is compiled and loaded.
|
||||
|
||||
Similar to `ensure_compiled/1`, but returns `true` if the module
|
||||
is already loaded or was successfully loaded and compiled.
|
||||
Returns `false` otherwise.
|
||||
"""
|
||||
def ensure_compiled?(module) do
|
||||
match?({ :module, ^module }, ensure_compiled(module))
|
||||
|
||||
@@ -31,6 +31,11 @@ defmodule Dict do
|
||||
IO.puts "#{k}: #{v}"
|
||||
end)
|
||||
|
||||
## Match
|
||||
|
||||
Dictionaries are required to implement all operations
|
||||
using the match (`===`) operator. Any deviation from
|
||||
this behaviour should be avoided and explicitly documented.
|
||||
"""
|
||||
|
||||
use Behaviour
|
||||
@@ -420,7 +425,7 @@ defmodule Dict do
|
||||
|
||||
@doc """
|
||||
Returns a new dict where only the keys in `keys` from `dict` are
|
||||
included.
|
||||
included.
|
||||
Any non-member keys are ignored.
|
||||
|
||||
## Examples
|
||||
@@ -458,8 +463,8 @@ defmodule Dict do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Check if two dicts are equal. If the dicts are of different types, they are
|
||||
first converted to lists.
|
||||
Check if two dicts are equal using `===`. If the dicts are
|
||||
of different types, they are first converted to lists.
|
||||
|
||||
## Examples
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ defmodule Dict.Behaviour do
|
||||
@moduledoc """
|
||||
This module makes it easier to create your own `Dict` compliant
|
||||
module, by providing default implementations for some required functions.
|
||||
|
||||
|
||||
Usage:
|
||||
|
||||
defmodule MyDict do
|
||||
@@ -13,16 +13,16 @@ defmodule Dict.Behaviour do
|
||||
# override default implementations if needed
|
||||
end
|
||||
|
||||
The client module must contain following functions: `size/1`, `fetch/2`,
|
||||
The client module must contain following functions: `size/1`, `fetch/2`,
|
||||
`put/3`, `update/4`, `delete/2` and `reduce/3`. All of them are part of
|
||||
the Dict behaviour, so no extra functions are actually required.
|
||||
|
||||
Based on these functions, `Dict.Behaviour` generates default implementations
|
||||
Based on these functions, `Dict.Behaviour` generates default implementations
|
||||
for other functions such as `drop`, `take`, etc. All of the functions are
|
||||
defined as overridable, so you can provide your own implementation if
|
||||
needed.
|
||||
|
||||
If you implement `new/0` and `new/1` functions, you can also test your custom
|
||||
If you implement `new/0` and `new/1` functions, you can also test your custom
|
||||
module via `Dict` doctests:
|
||||
|
||||
defmodule MyDict do
|
||||
@@ -38,11 +38,10 @@ defmodule Dict.Behaviour do
|
||||
end
|
||||
|
||||
"""
|
||||
|
||||
defmacro __using__(_) do
|
||||
quote do
|
||||
@behaviour Dict
|
||||
|
||||
|
||||
def get(dict, key, default // nil) do
|
||||
case fetch(dict, key) do
|
||||
{ :ok, value } -> value
|
||||
@@ -58,29 +57,29 @@ defmodule Dict.Behaviour do
|
||||
end
|
||||
end
|
||||
defoverridable fetch!: 2
|
||||
|
||||
|
||||
def has_key?(dict, key) do
|
||||
match? { :ok, _ }, fetch(dict, key)
|
||||
end
|
||||
defoverridable has_key?: 2
|
||||
|
||||
|
||||
def put_new(dict, key, value) do
|
||||
update(dict, key, value, fn(v) -> v end)
|
||||
end
|
||||
defoverridable put_new: 3
|
||||
|
||||
|
||||
def drop(dict, []), do: dict
|
||||
|
||||
def drop(dict, [key|keys]) do
|
||||
drop(delete(dict, key), keys)
|
||||
end
|
||||
defoverridable drop: 2
|
||||
|
||||
|
||||
def take(dict, keys) do
|
||||
take(dict, keys, new)
|
||||
end
|
||||
defoverridable take: 2
|
||||
|
||||
|
||||
defp take(_dict, [], acc), do: acc
|
||||
defp take(dict, [key|keys], acc) do
|
||||
case fetch(dict, key) do
|
||||
@@ -88,20 +87,21 @@ defmodule Dict.Behaviour do
|
||||
:error -> take(dict, keys, acc)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
def to_list(dict), do: reduce(dict, [], &[&1|&2]) |> Enum.reverse
|
||||
defoverridable to_list: 1
|
||||
|
||||
|
||||
def keys(dict), do: reduce(dict, [], fn({k, _}, acc) -> [k | acc] end) |> Enum.reverse
|
||||
defoverridable keys: 1
|
||||
|
||||
|
||||
def values(dict), do: reduce(dict, [], fn({_, v}, acc) -> [v | acc] end) |> Enum.reverse
|
||||
defoverridable values: 1
|
||||
|
||||
|
||||
def equal?(dict1, dict2) do
|
||||
import Kernel, except: [size: 1]
|
||||
case size(dict1) == size(dict2) do
|
||||
false -> false
|
||||
true ->
|
||||
true ->
|
||||
try do
|
||||
reduce(dict1, nil, fn({ k, v }, _acc) ->
|
||||
unless fetch(dict2, k) == { :ok, v }, do: throw(:error)
|
||||
@@ -113,7 +113,7 @@ defmodule Dict.Behaviour do
|
||||
end
|
||||
end
|
||||
defoverridable equal?: 2
|
||||
|
||||
|
||||
def merge(dict, enumerable, callback // fn(_k, _v1, v2) -> v2 end) do
|
||||
Enum.reduce(enumerable, dict, fn({key, value}, acc) ->
|
||||
update(acc, key, value, fn(v1) -> callback.(key, v1, value) end)
|
||||
@@ -122,4 +122,4 @@ defmodule Dict.Behaviour do
|
||||
defoverridable merge: 2, merge: 3
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
+128
-68
@@ -15,8 +15,6 @@ defprotocol Enumerable do
|
||||
element in the collection and cons'ing the element with the accumulated list.
|
||||
"""
|
||||
|
||||
@only [List, Record, Function]
|
||||
|
||||
@doc """
|
||||
This function performs the reducing operation on a given collection. It
|
||||
returns the accumulated value of applying the given function `fun` on every
|
||||
@@ -40,6 +38,7 @@ defprotocol Enumerable do
|
||||
|
||||
@doc """
|
||||
The function is used to check if a value exists within the collection.
|
||||
Membership should be tested with the match (`===`) operator.
|
||||
"""
|
||||
def member?(collection, value)
|
||||
|
||||
@@ -89,6 +88,10 @@ defmodule Enum do
|
||||
@doc """
|
||||
Checks if `value` exists within the `collection`.
|
||||
|
||||
Membership is tested with the match (`===`) operator although
|
||||
enumerables like ranges may include floats inside the given
|
||||
range.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Enum.member?(1..10, 5)
|
||||
@@ -212,8 +215,6 @@ defmodule Enum do
|
||||
Finds the element at the given index (zero-based).
|
||||
Returns `default` if index is out of bounds.
|
||||
|
||||
Expects an ordered collection.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Enum.at([2, 4, 6], 0)
|
||||
@@ -283,7 +284,11 @@ defmodule Enum do
|
||||
|
||||
@doc """
|
||||
Drops the first `count` items from `collection`.
|
||||
Expects an ordered collection.
|
||||
|
||||
A negative value `count` can be given, which means
|
||||
the collection is enumerated once to retrieve the
|
||||
proper index and the remaining of the calculation
|
||||
is done from the end.
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -309,13 +314,11 @@ defmodule Enum do
|
||||
end
|
||||
|
||||
def drop(collection, count) when count < 0 do
|
||||
{ list, count } = iterate_and_count(collection, count)
|
||||
drop(list, count)
|
||||
do_take_reverse(reverse(collection), abs(count), [])
|
||||
end
|
||||
|
||||
@doc """
|
||||
Drops items at the beginning of `collection` while `fun` returns `true`.
|
||||
Expects an ordered collection.
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -386,7 +389,9 @@ defmodule Enum do
|
||||
Finds the element at the given index (zero-based).
|
||||
Returns `{ :ok, element }` if found, otherwise `:error`.
|
||||
|
||||
Expects an ordered collection.
|
||||
A negative index can be passed, which means the collection is
|
||||
iterated once and the index is counted from the end (i.e.
|
||||
`-1` fetches the last element).
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -418,8 +423,7 @@ defmodule Enum do
|
||||
end
|
||||
|
||||
def fetch(collection, n) when n < 0 do
|
||||
{ list, count } = iterate_and_count_oob(collection, n)
|
||||
if count >= 0, do: fetch(list, count), else: :error
|
||||
fetch(reverse(collection), abs(n + 1))
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -473,7 +477,7 @@ defmodule Enum do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Enum.filter_map([1, 2, 3], fn(x) -> rem(x, 2) == 0 end, &1 * 2)
|
||||
iex> Enum.filter_map([1, 2, 3], fn(x) -> rem(x, 2) == 0 end, &(&1 * 2))
|
||||
[4]
|
||||
|
||||
"""
|
||||
@@ -556,8 +560,6 @@ defmodule Enum do
|
||||
Similar to `find/3`, but returns the index (zero-based)
|
||||
of the element instead of the element itself.
|
||||
|
||||
Expects an ordered collection.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Enum.find_index([2, 4, 6], fn(x) -> rem(x, 2) == 1 end)
|
||||
@@ -648,14 +650,9 @@ defmodule Enum do
|
||||
|
||||
"""
|
||||
@spec join(t) :: String.t
|
||||
@spec join(t, String.t | char_list) :: String.t | char_list
|
||||
@spec join(t, String.t) :: String.t
|
||||
def join(collection, joiner // "")
|
||||
|
||||
def join(collection, joiner) when is_list(joiner) do
|
||||
IO.write "Enum.join/2 with a char list is deprecated, please use do an explicit conversion instead\n#{Exception.format_stacktrace}"
|
||||
:unicode.characters_to_list join(collection, :unicode.characters_to_binary(joiner))
|
||||
end
|
||||
|
||||
def join(collection, joiner) when is_binary(joiner) do
|
||||
Enumerable.reduce(collection, "", fn
|
||||
entry, "" -> to_string(entry)
|
||||
@@ -708,14 +705,9 @@ defmodule Enum do
|
||||
|
||||
"""
|
||||
@spec map_join(t, (element -> any)) :: String.t
|
||||
@spec map_join(t, String.t | char_list, (element -> any)) :: String.t | char_list
|
||||
@spec map_join(t, String.t, (element -> any)) :: String.t
|
||||
def map_join(collection, joiner // "", mapper)
|
||||
|
||||
def map_join(collection, joiner, mapper) when is_list(joiner) do
|
||||
IO.write "Enum.map_join/3 with a char list is deprecated, please use do an explicit conversion instead\n#{Exception.format_stacktrace}"
|
||||
:unicode.characters_to_list map_join(collection, :unicode.characters_to_binary(joiner), mapper)
|
||||
end
|
||||
|
||||
def map_join(collection, joiner, mapper) when is_binary(joiner) do
|
||||
Enumerable.reduce(collection, "", fn
|
||||
entry, "" -> to_string(mapper, entry)
|
||||
@@ -902,8 +894,8 @@ defmodule Enum do
|
||||
end
|
||||
|
||||
def sort(collection) do
|
||||
fun = &1 <= &2
|
||||
Enumerable.reduce(collection, [], sort_reducer(&1, &2, fun)) |>
|
||||
fun = &(&1 <= &2)
|
||||
Enumerable.reduce(collection, [], &sort_reducer(&1, &2, fun)) |>
|
||||
sort_terminator(fun)
|
||||
end
|
||||
|
||||
@@ -922,7 +914,7 @@ defmodule Enum do
|
||||
end
|
||||
|
||||
def sort(collection, fun) do
|
||||
Enumerable.reduce(collection, [], sort_reducer(&1, &2, fun)) |>
|
||||
Enumerable.reduce(collection, [], &sort_reducer(&1, &2, fun)) |>
|
||||
sort_terminator(fun)
|
||||
end
|
||||
|
||||
@@ -969,8 +961,7 @@ defmodule Enum do
|
||||
end
|
||||
|
||||
def split(collection, count) when count < 0 do
|
||||
{ list, count } = iterate_and_count(collection, count)
|
||||
split(list, count)
|
||||
do_split_reverse(reverse(collection), abs(count), [])
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -998,8 +989,12 @@ defmodule Enum do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Takes the first `count` items from the collection. Expects an ordered
|
||||
collection.
|
||||
Takes the first `count` items from the collection.
|
||||
|
||||
A negative value `count` can be passed, which means
|
||||
the collection is enumerated once to retrieve the
|
||||
proper index and the remaining of the calculation
|
||||
is done from the end.
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -1037,13 +1032,36 @@ defmodule Enum do
|
||||
end
|
||||
|
||||
def take(collection, count) when count < 0 do
|
||||
{ list, count } = iterate_and_count(collection, count)
|
||||
take(list, count)
|
||||
do_drop(reverse(collection), abs(count)) |> :lists.reverse
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns a collection of every `nth` items in the collection,
|
||||
starting with the first.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Enum.take_every(1..10, 2)
|
||||
[1, 3, 5, 7, 9]
|
||||
|
||||
"""
|
||||
@spec take_every(t, integer) :: list
|
||||
def take_every(_collection, 0), do: []
|
||||
def take_every(collection, nth) do
|
||||
res = Enumerable.reduce(collection, nil, fn
|
||||
x, { acc, ^nth } -> { [x | acc], 1 }
|
||||
_, { acc, count } -> { acc, count + 1 }
|
||||
x, nil -> { [x], 1 }
|
||||
end)
|
||||
|
||||
case res do
|
||||
nil -> []
|
||||
{ list, _ } -> :lists.reverse(list)
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Takes the items at the beginning of `collection` while `fun` returns `true`.
|
||||
Expects an ordered collection.
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -1169,12 +1187,6 @@ defmodule Enum do
|
||||
reduce(collection, &Kernel.max(&1, &2))
|
||||
end
|
||||
|
||||
@doc false
|
||||
def max(coll, fun) do
|
||||
IO.write "Enum.max/2 is deprecated, please use Enum.max_by/2 instead\n#{Exception.format_stacktrace}"
|
||||
max_by(coll, fun)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns the maximum value as calculated by the given function.
|
||||
Raises `EmptyError` if the collection is empty.
|
||||
@@ -1229,12 +1241,6 @@ defmodule Enum do
|
||||
reduce(collection, &Kernel.min(&1, &2))
|
||||
end
|
||||
|
||||
@doc false
|
||||
def min(coll, fun) do
|
||||
IO.write "Enum.min/2 is deprecated, please use Enum.min_by/2 instead\n#{Exception.format_stacktrace}"
|
||||
min_by(coll, fun)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns the minimum value as calculated by the given function.
|
||||
Raises `EmptyError` if the collection is empty.
|
||||
@@ -1320,7 +1326,7 @@ defmodule Enum do
|
||||
|
||||
"""
|
||||
@spec chunks(t, non_neg_integer, non_neg_integer) :: [list]
|
||||
@spec chunks(t, non_neg_integer, non_neg_integer, list) :: [list]
|
||||
@spec chunks(t, non_neg_integer, non_neg_integer, list | nil) :: [list]
|
||||
def chunks(coll, n, step, pad // nil) when n > 0 and step > 0 do
|
||||
{ acc, buffer, i } =
|
||||
Enumerable.reduce(coll, { [], [], 0 }, fn
|
||||
@@ -1371,12 +1377,17 @@ defmodule Enum do
|
||||
@doc """
|
||||
Returns a subset list of the given collection. Dropping elements
|
||||
until element position `start`, then taking `count` elements.
|
||||
Expects an ordered collection.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Enum.slice(1..100, 5, 10)
|
||||
[6, 7, 8, 9, 10, 11, 12, 13, 14, 15]
|
||||
|
||||
"""
|
||||
@spec slice(t, integer, integer) :: list
|
||||
@spec slice(t, integer, non_neg_integer) :: list
|
||||
|
||||
def slice(coll, start, count) when start < 0 do
|
||||
{ list, new_start } = iterate_and_count_oob(coll, start)
|
||||
{ list, new_start } = iterate_and_count(coll, start)
|
||||
if new_start >= 0, do: slice(list, new_start, count)
|
||||
end
|
||||
|
||||
@@ -1408,6 +1419,41 @@ defmodule Enum do
|
||||
:enum_slice -> []
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns a subset list of the given collection. Dropping elements
|
||||
until element position `range.first`, then taking elements until element
|
||||
position `range.last` (inclusive).
|
||||
|
||||
Positions are calculated by adding the number of items in the collection to
|
||||
negative positions (so position -3 in a collection with count 5 becomes
|
||||
position 2).
|
||||
|
||||
The first position (after adding count to negative positions) must be smaller
|
||||
or equal to the last position.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Enum.slice(1..100, 5..10)
|
||||
[6, 7, 8, 9, 10, 11]
|
||||
|
||||
"""
|
||||
def slice(coll, first..last) when first >= 0 and last >= 0 do
|
||||
# Simple case, which works on infinite collections
|
||||
if last - first >= 0 do
|
||||
slice(coll, first, last - first + 1)
|
||||
end
|
||||
end
|
||||
|
||||
def slice(coll, first..last) do
|
||||
{ list, count } = iterate_and_count(coll, 0)
|
||||
corr_first = if first >= 0, do: first, else: first + count
|
||||
corr_last = if last >= 0, do: last, else: last + count
|
||||
length = corr_last - corr_first + 1
|
||||
if corr_first >= 0 and length > 0 do
|
||||
slice(list, corr_first, length)
|
||||
end
|
||||
end
|
||||
|
||||
## Helpers
|
||||
|
||||
@compile { :inline, chunks_n: 5, chunks_step: 4, to_string: 2 }
|
||||
@@ -1430,23 +1476,12 @@ defmodule Enum do
|
||||
{ acc, buffer, i }
|
||||
end
|
||||
|
||||
defp iterate_and_count_oob(collection, count) do
|
||||
{ list, total_items } = do_iterate_and_count(collection)
|
||||
{ list, total_items - abs(count) }
|
||||
defp iterate_and_count(collection, count) when is_list(collection) do
|
||||
{ collection, length(collection) - abs(count) }
|
||||
end
|
||||
|
||||
defp iterate_and_count(collection, count) do
|
||||
{ list, total_items } = do_iterate_and_count(collection)
|
||||
{ list, Kernel.max(0, total_items - abs(count)) }
|
||||
end
|
||||
|
||||
defp do_iterate_and_count(collection) when is_list(collection) do
|
||||
{ collection, length(collection) }
|
||||
end
|
||||
|
||||
defp do_iterate_and_count(collection) do
|
||||
reducer = fn(x, acc) -> { x, acc + 1 } end
|
||||
map_reduce(collection, 0, reducer)
|
||||
map_reduce(collection, -abs(count), fn(x, acc) -> { x, acc + 1 } end)
|
||||
end
|
||||
|
||||
defp to_string(mapper, entry) do
|
||||
@@ -1686,6 +1721,18 @@ defmodule Enum do
|
||||
{ :lists.reverse(acc), [] }
|
||||
end
|
||||
|
||||
defp do_split_reverse([h|t], counter, acc) when counter > 0 do
|
||||
do_split_reverse(t, counter - 1, [h|acc])
|
||||
end
|
||||
|
||||
defp do_split_reverse(list, 0, acc) do
|
||||
{ :lists.reverse(list), acc }
|
||||
end
|
||||
|
||||
defp do_split_reverse([], _, acc) do
|
||||
{ [], acc }
|
||||
end
|
||||
|
||||
## split_while
|
||||
|
||||
defp do_split_while([h|t], fun, acc) do
|
||||
@@ -1714,6 +1761,18 @@ defmodule Enum do
|
||||
[]
|
||||
end
|
||||
|
||||
defp do_take_reverse([h|t], counter, acc) when counter > 0 do
|
||||
do_take_reverse(t, counter - 1, [h|acc])
|
||||
end
|
||||
|
||||
defp do_take_reverse(_list, 0, acc) do
|
||||
acc
|
||||
end
|
||||
|
||||
defp do_take_reverse([], _, acc) do
|
||||
acc
|
||||
end
|
||||
|
||||
## take_while
|
||||
|
||||
defp do_take_while([h|t], fun) do
|
||||
@@ -1796,8 +1855,9 @@ defimpl Enumerable, for: Function do
|
||||
end
|
||||
|
||||
def member?(function, value) do
|
||||
function.(false, fn(entry, _) ->
|
||||
if entry === value, do: throw(:function_member?), else: false
|
||||
function.(false, fn
|
||||
^value, _ -> throw(:function_member?)
|
||||
_, _ -> false
|
||||
end)
|
||||
catch
|
||||
:function_member? -> true
|
||||
|
||||
@@ -39,6 +39,12 @@ defexception CaseClauseError, [actual: nil] do
|
||||
end
|
||||
end
|
||||
|
||||
defexception TryClauseError, [actual: nil] do
|
||||
def message(exception) do
|
||||
"no try clause matching: #{inspect(exception.actual)}"
|
||||
end
|
||||
end
|
||||
|
||||
defexception BadArityError, [function: nil, args: nil] do
|
||||
def message(exception) do
|
||||
"bad arity error: #{inspect(exception.function)} called with #{inspect(exception.args)}"
|
||||
@@ -146,6 +152,10 @@ defmodule Exception do
|
||||
CaseClauseError[actual: actual]
|
||||
end
|
||||
|
||||
def normalize({ :try_clause, actual }) do
|
||||
TryClauseError[actual: actual]
|
||||
end
|
||||
|
||||
def normalize(:undef) do
|
||||
{ mod, fun, arity } = from_stacktrace(:erlang.get_stacktrace)
|
||||
UndefinedFunctionError[module: mod, function: fun, arity: arity]
|
||||
@@ -266,6 +276,8 @@ defmodule Exception do
|
||||
"Foo.bar/1"
|
||||
iex> Exception.format_mfa Foo, :bar, []
|
||||
"Foo.bar()"
|
||||
iex> Exception.format_mfa nil, :bar, []
|
||||
"nil.bar()"
|
||||
|
||||
"""
|
||||
def format_mfa(module, fun, arity) do
|
||||
@@ -277,12 +289,14 @@ defmodule Exception do
|
||||
|
||||
if is_list(arity) do
|
||||
inspected = lc x inlist arity, do: inspect(x)
|
||||
"#{inspect module}.#{fun}(#{Enum.join(inspected, ", ")})"
|
||||
"#{format_module module}#{fun}(#{Enum.join(inspected, ", ")})"
|
||||
else
|
||||
"#{inspect module}.#{fun}/#{arity}"
|
||||
"#{format_module module}#{fun}/#{arity}"
|
||||
end
|
||||
end
|
||||
|
||||
defp format_module(mod), do: "#{inspect mod}."
|
||||
|
||||
@doc """
|
||||
Formats the given file and line as shown in stacktraces.
|
||||
If any of the values are nil, they are omitted.
|
||||
|
||||
+4
-11
@@ -47,13 +47,6 @@ defexception File.CopyError, [reason: nil, action: "", source: nil, destination:
|
||||
end
|
||||
end
|
||||
|
||||
defexception File.IteratorError, reason: nil do
|
||||
def message(exception) do
|
||||
formatted = iolist_to_binary(:file.format_error(reason exception))
|
||||
"error during file iteration: #{formatted}"
|
||||
end
|
||||
end
|
||||
|
||||
defmodule File do
|
||||
@moduledoc """
|
||||
This module contains functions to manipulate files.
|
||||
@@ -488,7 +481,7 @@ defmodule File do
|
||||
case F.list_dir(src) do
|
||||
{ :ok, files } ->
|
||||
case mkdir(dest) do
|
||||
success in [:ok, { :error, :eexist }] ->
|
||||
success when success in [:ok, { :error, :eexist }] ->
|
||||
Enum.reduce(files, [dest|acc], fn(x, acc) ->
|
||||
do_cp_r(FN.join(src, x), FN.join(dest, x), callback, acc)
|
||||
end)
|
||||
@@ -945,7 +938,7 @@ defmodule File do
|
||||
"""
|
||||
def ls(path // ".") do
|
||||
case F.list_dir(path) do
|
||||
{ :ok, file_list } -> { :ok, Enum.map(file_list, to_string(&1)) }
|
||||
{ :ok, file_list } -> { :ok, Enum.map(file_list, &to_string(&1)) }
|
||||
{ :error, _ } = error -> error
|
||||
end
|
||||
end
|
||||
@@ -980,7 +973,7 @@ defmodule File do
|
||||
of bytes given by `line_or_bytes`.
|
||||
|
||||
The returned stream will fail for the same reasons as `File.open!`.
|
||||
Note that the file is opened only when streaming begins.
|
||||
Note that the file is opened only and every time streaming begins.
|
||||
"""
|
||||
def stream!(file, mode // [], line_or_bytes // :line) do
|
||||
fn(acc, fun) ->
|
||||
@@ -999,7 +992,7 @@ defmodule File do
|
||||
of bytes given by `line_or_bytes`.
|
||||
|
||||
The returned stream will fail for the same reasons as `File.open!`.
|
||||
Note that the file is opened only when streaming begins.
|
||||
Note that the file is opened only and every time streaming begins.
|
||||
"""
|
||||
def binstream!(file, mode // [], line_or_bytes // :line) do
|
||||
fn(fun, acc) ->
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
defmodule Float do
|
||||
@moduledoc """
|
||||
Functions for working with floating point numbers.
|
||||
"""
|
||||
|
||||
@doc """
|
||||
Parses a binary into a float.
|
||||
|
||||
If successful, returns a tuple of the form `{ float, remainder_of_binary }`.
|
||||
Otherwise `:error`.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Float.parse("34")
|
||||
{34.0,""}
|
||||
iex> Float.parse("34.25")
|
||||
{34.25,""}
|
||||
iex> Float.parse("56.5xyz")
|
||||
{56.5,"xyz"}
|
||||
iex> Float.parse("pi")
|
||||
:error
|
||||
|
||||
"""
|
||||
@spec parse(binary) :: { float, binary } | :error
|
||||
def parse(binary) when is_binary(binary) do
|
||||
case Integer.parse binary do
|
||||
:error -> :error
|
||||
{ integer_part, after_integer } -> parse after_integer, integer_part
|
||||
end
|
||||
end
|
||||
|
||||
# Dot followed by digit is required afterwards or we are done
|
||||
defp parse(<< ?., char, rest :: binary >>, int) when char in ?0..?9 do
|
||||
parse(rest, char - ?0, 1, int)
|
||||
end
|
||||
|
||||
defp parse(rest, int) do
|
||||
{ :erlang.float(int), rest }
|
||||
end
|
||||
|
||||
# Handle decimal points
|
||||
defp parse(<< char, rest :: binary >>, float, decimal, int) when char in ?0..?9 do
|
||||
parse rest, 10 * float + (char - ?0), decimal + 1, int
|
||||
end
|
||||
|
||||
defp parse(<< ?e, after_e :: binary >>, float, decimal, int) do
|
||||
case Integer.parse after_e do
|
||||
:error ->
|
||||
# Note we rebuild the binary here instead of breaking it apart at
|
||||
# the function clause because the current approach copies a binary
|
||||
# just on this branch. If we broke it apart in the function clause,
|
||||
# the copy would happen when calling Integer.parse/1.
|
||||
{ floatify(int, float, decimal), << ?e, after_e :: binary >> }
|
||||
{ exponential, after_exponential } ->
|
||||
{ floatify(int, float, decimal, exponential), after_exponential }
|
||||
end
|
||||
end
|
||||
|
||||
defp parse(bitstring, float, decimal, int) do
|
||||
{ floatify(int, float, decimal), bitstring }
|
||||
end
|
||||
|
||||
defp floatify(int, float, decimal, exponential // 0) do
|
||||
multiplier = if int < 0, do: -1.0, else: 1.0
|
||||
|
||||
# Try to ensure the minimum amount of rounding errors
|
||||
result = multiplier * (abs(int) * :math.pow(10, decimal) + float) * :math.pow(10, exponential - decimal)
|
||||
|
||||
# Try avoiding stuff like this:
|
||||
# iex(1)> 0.0001 * 75
|
||||
# 0.007500000000000001
|
||||
# Due to IEEE 754 floating point standard
|
||||
# http://docs.oracle.com/cd/E19957-01/806-3568/ncg_goldberg.html
|
||||
|
||||
final_decimal_places = decimal - exponential
|
||||
if final_decimal_places > 0 do
|
||||
decimal_power_round = :math.pow(10, final_decimal_places)
|
||||
trunc(result * decimal_power_round) / decimal_power_round
|
||||
else
|
||||
result
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -60,7 +60,7 @@ defmodule GenFSM.Behaviour do
|
||||
end
|
||||
|
||||
def short_paid(:coin, state_data) do
|
||||
{ :next_state, :paid_in_full, state_data.update_coins(&1 + 1) }
|
||||
{ :next_state, :paid_in_full, &state_data.update_coins(&1 + 1) }
|
||||
end
|
||||
|
||||
def short_paid(:request_coffee, state_data) do
|
||||
@@ -84,7 +84,7 @@ defmodule GenFSM.Behaviour do
|
||||
|
||||
|
||||
def paid_in_full(:coin, state_data) do
|
||||
{ :next_state, :paid_in_full, state_data.update_coins(&1 + 1) }
|
||||
{ :next_state, :paid_in_full, &state_data.update_coins(&1 + 1) }
|
||||
end
|
||||
|
||||
def paid_in_full(:request_coffee, _state_data) do
|
||||
@@ -186,7 +186,7 @@ defmodule GenFSM.Behaviour do
|
||||
|
||||
@doc false
|
||||
def handle_sync_event(_event, from, state_name, state_data) do
|
||||
{ :reply, :default_implementation, state_name, state_data }
|
||||
{ :reply, :ok, state_name, state_data }
|
||||
end
|
||||
|
||||
@doc false
|
||||
|
||||
@@ -107,12 +107,6 @@ defmodule HashDict do
|
||||
dict
|
||||
end
|
||||
|
||||
@doc false
|
||||
def update(dict, key, fun) when is_function(fun, 1) do
|
||||
IO.write "HashDict.update/3 is deprecated, please use HashDict.update!/3 instead\n#{Exception.format_stacktrace}"
|
||||
update!(dict, key, fun)
|
||||
end
|
||||
|
||||
def update!(dict, key, fun) when is_function(fun, 1) do
|
||||
case dict_put(dict, key, { :update, nil, fun }) do
|
||||
{ dict, 0 } ->
|
||||
@@ -159,13 +153,13 @@ defmodule HashDict do
|
||||
|
||||
def merge(dict1, dict2, callback) when is_record(dict1, HashDict) and is_record(dict2, HashDict) and elem(dict1, 1) < elem(dict2, 1) do
|
||||
dict_fold dict1, dict2, fn [k|v1], acc ->
|
||||
update(acc, k, v1, callback.(k, v1, &1))
|
||||
update(acc, k, v1, &callback.(k, v1, &1))
|
||||
end
|
||||
end
|
||||
|
||||
def merge(dict1, dict2, callback) when is_record(dict1, HashDict) and is_record(dict2, HashDict) do
|
||||
dict_fold dict2, dict1, fn [k|v2], acc ->
|
||||
update(acc, k, v2, callback.(k, &1, v2))
|
||||
update(acc, k, v2, &callback.(k, &1, v2))
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -103,7 +103,7 @@ defmodule HashSet do
|
||||
end
|
||||
|
||||
def to_list(set) do
|
||||
set_fold(set, [], [&1|&2]) |> :lists.reverse
|
||||
set_fold(set, [], &[&1|&2]) |> :lists.reverse
|
||||
end
|
||||
|
||||
def put(set, member) do
|
||||
@@ -476,13 +476,6 @@ defmodule HashSet do
|
||||
defp each_contract(acc, [m|bucket]), do: [m|each_contract(acc, bucket)]
|
||||
defp each_contract([], bucket), do: bucket
|
||||
defp each_contract(acc, []), do: acc
|
||||
|
||||
@doc false
|
||||
def inspect_depth(trie(depth: d)), do: d
|
||||
@doc false
|
||||
def inspect_contract(trie(contract_on: c)), do: c
|
||||
@doc false
|
||||
def inspect_expand(trie(expand_on: e)), do: e
|
||||
end
|
||||
|
||||
defimpl Enumerable, for: HashSet do
|
||||
|
||||
+24
-15
@@ -267,9 +267,9 @@ defimpl Inspect, for: List do
|
||||
:io_lib.printable_list(thing) ->
|
||||
<< ?', Inspect.BitString.escape(String.from_char_list!(thing), ?') :: binary, ?' >>
|
||||
keyword?(thing) && not opts.raw ->
|
||||
surround_many("[", thing, "]", opts.limit, keyword(&1, opts))
|
||||
surround_many("[", thing, "]", opts.limit, &keyword(&1, opts))
|
||||
true ->
|
||||
surround_many("[", thing, "]", opts.limit, Kernel.inspect(&1, opts))
|
||||
surround_many("[", thing, "]", opts.limit, &Kernel.inspect(&1, opts))
|
||||
end
|
||||
end
|
||||
|
||||
@@ -317,7 +317,7 @@ defimpl Inspect, for: Tuple do
|
||||
def inspect(tuple, opts) do
|
||||
unless opts.raw do
|
||||
record_inspect(tuple, opts)
|
||||
end || surround_many("{", tuple_to_list(tuple), "}", opts.limit, Kernel.inspect(&1, opts))
|
||||
end || surround_many("{", tuple_to_list(tuple), "}", opts.limit, &Kernel.inspect(&1, opts))
|
||||
end
|
||||
|
||||
## Helpers
|
||||
@@ -331,7 +331,7 @@ defimpl Inspect, for: Tuple do
|
||||
else
|
||||
surround_record(name, fields, tail, opts)
|
||||
end
|
||||
end || surround_many("{", [name|tail], "}", opts.limit, Kernel.inspect(&1, opts))
|
||||
end || surround_many("{", [name|tail], "}", opts.limit, &Kernel.inspect(&1, opts))
|
||||
end
|
||||
|
||||
defp record_fields(name) do
|
||||
@@ -351,22 +351,35 @@ defimpl Inspect, for: Tuple do
|
||||
|
||||
concat(
|
||||
Inspect.Atom.inspect(name, opts),
|
||||
surround_many("[", Enum.zip(fields, tail), "]", opts.limit, keyword(&1, opts))
|
||||
surround_many("[", Enum.zip(fields, tail), "]", opts.limit, &keyword(&1, opts))
|
||||
)
|
||||
end
|
||||
|
||||
defp keyword({ k, v }, opts) do
|
||||
concat(
|
||||
atom_to_binary(k, :utf8) <> ": ",
|
||||
atom_to_binary(k) <> ": ",
|
||||
Kernel.inspect(v, opts)
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
defimpl Inspect, for: Number do
|
||||
defimpl Inspect, for: Integer do
|
||||
@doc """
|
||||
Represents the number as a string.
|
||||
Represents the integer as a string.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> inspect(1)
|
||||
"1"
|
||||
|
||||
"""
|
||||
def inspect(thing, _opts) do
|
||||
integer_to_binary(thing)
|
||||
end
|
||||
end
|
||||
|
||||
defimpl Inspect, for: Float do
|
||||
@doc """
|
||||
Floats are represented using the shortened, correctly rounded string
|
||||
that converts to float when read back with `binary_to_float/1`. This
|
||||
is done via the Erlang implementation of _Printing Floating-Point
|
||||
@@ -375,14 +388,10 @@ defimpl Inspect, for: Number do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> inspect(1)
|
||||
"1"
|
||||
iex> inspect(1.0)
|
||||
"1.0"
|
||||
|
||||
"""
|
||||
def inspect(thing, _opts) when is_integer(thing) do
|
||||
integer_to_binary(thing)
|
||||
end
|
||||
|
||||
def inspect(thing, _opts) do
|
||||
iolist_to_binary(:io_lib_format.fwrite_g(thing))
|
||||
end
|
||||
@@ -446,7 +455,7 @@ end
|
||||
|
||||
defimpl Inspect, for: PID do
|
||||
def inspect(pid, _opts) do
|
||||
"#PID" <> iolist_to_binary(pid_to_list(pid))
|
||||
"#PID" <> iolist_to_binary(:erlang.pid_to_list(pid))
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -82,6 +82,28 @@ defmodule Inspect.Algebra do
|
||||
defrecordp :doc_break, str: " " :: binary
|
||||
defrecordp :doc_group, doc: :doc_nil :: t
|
||||
|
||||
defmacrop is_doc(doc) do
|
||||
if __CALLER__.in_guard? do
|
||||
do_is_doc(doc)
|
||||
else
|
||||
var = quote do: doc
|
||||
quote do
|
||||
unquote(var) = unquote(doc)
|
||||
unquote(do_is_doc(var))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defp do_is_doc(doc) do
|
||||
quote do
|
||||
unquote(doc) |> is_binary or
|
||||
unquote(doc) |> is_integer or
|
||||
unquote(doc) == :doc_nil or
|
||||
(unquote(doc) |> is_tuple and
|
||||
elem(unquote(doc), 0) in [:doc_cons, :doc_nest, :doc_break, :doc_group])
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns `:doc_nil` which is a document entity used to represent
|
||||
nothingness. Takes no arguments.
|
||||
@@ -107,13 +129,17 @@ defmodule Inspect.Algebra do
|
||||
|
||||
"""
|
||||
@spec concat(t, t) :: doc_cons_t
|
||||
def concat(x, y), do: doc_cons(left: x, right: y)
|
||||
def concat(x, y) when is_doc(x) and is_doc(y) do
|
||||
doc_cons(left: x, right: y)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Concatenates a list of documents.
|
||||
"""
|
||||
@spec concat([t]) :: doc_cons_t
|
||||
def concat(docs), do: folddoc(docs, concat(&1, &2))
|
||||
def concat(docs) do
|
||||
folddoc(docs, &concat(&1, &2))
|
||||
end
|
||||
|
||||
@doc """
|
||||
Nests document entity `x` positions deep. Nesting will be
|
||||
@@ -127,8 +153,13 @@ defmodule Inspect.Algebra do
|
||||
|
||||
"""
|
||||
@spec nest(t, non_neg_integer) :: doc_nest_t
|
||||
def nest(x, 0), do: x
|
||||
def nest(x, i) when is_integer(i), do: doc_nest(indent: i, doc: x)
|
||||
def nest(x, 0) when is_doc(x) do
|
||||
x
|
||||
end
|
||||
|
||||
def nest(x, i) when is_doc(x) and is_integer(i) do
|
||||
doc_nest(indent: i, doc: x)
|
||||
end
|
||||
|
||||
@doc %S"""
|
||||
Document entity representing a break. This break can
|
||||
@@ -198,7 +229,9 @@ defmodule Inspect.Algebra do
|
||||
|
||||
"""
|
||||
@spec group(t) :: doc_group_t
|
||||
def group(d), do: doc_group(doc: d)
|
||||
def group(d) when is_doc(d) do
|
||||
doc_group(doc: d)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Inserts a mandatory single space between two document entities.
|
||||
@@ -240,7 +273,7 @@ defmodule Inspect.Algebra do
|
||||
"A!B"
|
||||
|
||||
"""
|
||||
@spec folddoc([any], ((any, [t]) -> t)) :: t
|
||||
@spec folddoc([t], ((t, t) -> t)) :: t
|
||||
def folddoc([], _), do: empty
|
||||
def folddoc([doc], _), do: doc
|
||||
def folddoc([d|ds], f), do: f.(d, folddoc(ds, f))
|
||||
@@ -273,11 +306,11 @@ defmodule Inspect.Algebra do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> doc = Inspect.Algebra.surround_many("[", Enum.to_list(1..5), "]", :infinity, integer_to_binary(&1))
|
||||
iex> doc = Inspect.Algebra.surround_many("[", Enum.to_list(1..5), "]", :infinity, &integer_to_binary(&1))
|
||||
iex> Inspect.Algebra.pretty(doc, 5)
|
||||
"[1,\n 2,\n 3,\n 4,\n 5]"
|
||||
|
||||
iex> doc = Inspect.Algebra.surround_many("[", Enum.to_list(1..5), "]", 3, integer_to_binary(&1))
|
||||
iex> doc = Inspect.Algebra.surround_many("[", Enum.to_list(1..5), "]", 3, &integer_to_binary(&1))
|
||||
iex> Inspect.Algebra.pretty(doc, 20)
|
||||
"[1, 2, 3, ...]"
|
||||
|
||||
@@ -357,7 +390,7 @@ defmodule Inspect.Algebra do
|
||||
def fits?(w, [{i, _, doc_group(doc: x)} | t]), do: fits?(w, [{i, :flat, x} | t])
|
||||
|
||||
@doc false
|
||||
@spec format(integer, integer, [{ integer, mode, t }]) :: atom | tuple
|
||||
@spec format(integer | :infinity, integer, [{ integer, mode, t }]) :: atom | tuple
|
||||
def format(_, _, []), do: :s_nil
|
||||
def format(w, k, [{_, _, :doc_nil} | t]), do: format(w, k, t)
|
||||
def format(w, k, [{i, m, doc_cons(left: x, right: y)} | t]), do: format(w, k, [{i, m, x} | [{i, m, y} | t]])
|
||||
@@ -367,7 +400,8 @@ defmodule Inspect.Algebra do
|
||||
def format(w, k, [{_, :flat, doc_break(str: s)} | t]), do: s_text(str: s, sdoc: format(w, (k + byte_size s), t))
|
||||
def format(w, k, [{i, :break, doc_break(str: s)} | t]) do
|
||||
k = k + byte_size(s)
|
||||
if fits?(w - k, t) do
|
||||
|
||||
if w == :infinity or fits?(w - k, t) do
|
||||
s_text(str: s, sdoc: format(w, k, t))
|
||||
else
|
||||
s_line(indent: i, sdoc: format(w, i, t))
|
||||
|
||||
@@ -3,17 +3,68 @@ defmodule Integer do
|
||||
Functions for working with integers.
|
||||
"""
|
||||
|
||||
import Bitwise
|
||||
|
||||
@doc """
|
||||
Returns true if `n` is an odd number, otherwise false.
|
||||
Determines if an integer is odd.
|
||||
|
||||
Returns `true` if `n` is an odd number, otherwise `false`.
|
||||
Implemented as a macro so it is allowed in guard clauses.
|
||||
"""
|
||||
defmacro odd?(n) do
|
||||
quote do: rem(unquote(n), 2) != 0
|
||||
quote do: (unquote(n) &&& 1) == 1
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns true if `n` is an even number, otherwise false.
|
||||
Determines if an integer is even.
|
||||
|
||||
Returns `true` if `n` is an even number, otherwise `false`.
|
||||
Implemented as a macro so it is allowed in guard clauses.
|
||||
"""
|
||||
defmacro even?(n) do
|
||||
quote do: rem(unquote(n), 2) == 0
|
||||
quote do: (unquote(n) &&& 1) == 0
|
||||
end
|
||||
|
||||
@doc """
|
||||
Converts a binary to an integer.
|
||||
|
||||
If successful, returns a tuple of the form `{ integer, remainder_of_binary }`.
|
||||
Otherwise `:error`.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Integer.parse("34")
|
||||
{34,""}
|
||||
iex> Integer.parse("34.5")
|
||||
{34,".5"}
|
||||
iex> Integer.parse("three")
|
||||
:error
|
||||
|
||||
"""
|
||||
@spec parse(binary) :: { integer, binary } | :error
|
||||
def parse(<< ?-, bin :: binary >>) do
|
||||
case do_parse(bin) do
|
||||
:error -> :error
|
||||
{ number, remainder } -> { -number, remainder }
|
||||
end
|
||||
end
|
||||
|
||||
def parse(<< ?+, bin :: binary >>) do
|
||||
do_parse(bin)
|
||||
end
|
||||
|
||||
def parse(bin) when is_binary(bin) do
|
||||
do_parse(bin)
|
||||
end
|
||||
|
||||
defp do_parse(<< char, bin :: binary >>) when char in ?0..?9, do: do_parse(bin, char - ?0)
|
||||
defp do_parse(_), do: :error
|
||||
|
||||
defp do_parse(<< char, rest :: binary >>, acc) when char in ?0..?9 do
|
||||
do_parse rest, 10 * acc + (char - ?0)
|
||||
end
|
||||
|
||||
defp do_parse(bitstring, acc) do
|
||||
{ acc, bitstring }
|
||||
end
|
||||
end
|
||||
|
||||
+56
-43
@@ -1,28 +1,32 @@
|
||||
defexception IO.StreamError, reason: nil do
|
||||
def message(exception) do
|
||||
formatted = iolist_to_binary(:file.format_error(reason exception))
|
||||
"error during streaming: #{formatted}"
|
||||
end
|
||||
end
|
||||
|
||||
defmodule IO do
|
||||
@moduledoc """
|
||||
Module responsible for doing IO. Many functions in this
|
||||
module expects an IO device and an io data encoded in UTF-8.
|
||||
Functions handling IO.
|
||||
|
||||
Many functions in this module expects an IO device as argument.
|
||||
An IO device must be a pid or an atom representing a process.
|
||||
For convenience, Elixir provides `:stdio` and `:stderr` as
|
||||
shortcuts to Erlang's `:standard_io` and `:standard_error`.
|
||||
|
||||
An io data can be:
|
||||
The majority of the functions expect data encoded in UTF-8
|
||||
and will do a conversion to string, via the `String.Chars`
|
||||
protocol (as shown in typespecs).
|
||||
|
||||
* 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_string` is invoked on the
|
||||
given argument;
|
||||
The functions starting with `bin*` expects iodata as arguments,
|
||||
i.e. iolists or binaries with no particular encoding.
|
||||
|
||||
"""
|
||||
|
||||
@type device :: atom | pid
|
||||
@type chardata :: char_list | String.Chars.t
|
||||
@type nodata :: { :error, term } | :eof
|
||||
|
||||
import :erlang, only: [group_leader: 0]
|
||||
|
||||
defmacrop is_iolist(data) do
|
||||
@@ -43,6 +47,7 @@ defmodule IO do
|
||||
for instance `{:error, :estale}` if reading from an
|
||||
NFS file system.
|
||||
"""
|
||||
@spec read(device, :line | non_neg_integer) :: chardata | nodata
|
||||
def read(device // group_leader, chars_or_line)
|
||||
|
||||
def read(device, :line) do
|
||||
@@ -65,6 +70,7 @@ defmodule IO do
|
||||
for instance `{:error, :estale}` if reading from an
|
||||
NFS file system.
|
||||
"""
|
||||
@spec binread(device, :line | non_neg_integer) :: iodata | nodata
|
||||
def binread(device // group_leader, chars_or_line)
|
||||
|
||||
def binread(device, :line) do
|
||||
@@ -98,8 +104,9 @@ defmodule IO do
|
||||
#=> "error"
|
||||
|
||||
"""
|
||||
def write(device // group_leader(), item) when is_iolist(item) do
|
||||
:io.put_chars map_dev(device), item
|
||||
@spec write(device, chardata) :: :ok
|
||||
def write(device // group_leader(), item) do
|
||||
:io.put_chars map_dev(device), to_chardata(item)
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -108,6 +115,7 @@ defmodule IO do
|
||||
|
||||
Check `write/2` for more information.
|
||||
"""
|
||||
@spec binwrite(device, iodata) :: :ok | { :error, term }
|
||||
def binwrite(device // group_leader(), item) when is_iolist(item) do
|
||||
:file.write map_dev(device), item
|
||||
end
|
||||
@@ -117,9 +125,10 @@ defmodule IO do
|
||||
but adds a newline at the end. The argument is expected
|
||||
to be a chardata.
|
||||
"""
|
||||
def puts(device // group_leader(), item) when is_iolist(item) do
|
||||
@spec puts(device, chardata) :: :ok
|
||||
def puts(device // group_leader(), item) do
|
||||
erl_dev = map_dev(device)
|
||||
:io.put_chars erl_dev, [item, ?\n]
|
||||
:io.put_chars erl_dev, [to_chardata(item), ?\n]
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -135,6 +144,7 @@ defmodule IO do
|
||||
IO.inspect Process.list
|
||||
|
||||
"""
|
||||
@spec inspect(term, Keyword.t) :: term
|
||||
def inspect(item, opts // []) do
|
||||
inspect group_leader(), item, opts
|
||||
end
|
||||
@@ -142,6 +152,7 @@ defmodule IO do
|
||||
@doc """
|
||||
Inspects the item with options using the given device.
|
||||
"""
|
||||
@spec inspect(device, term, Keyword.t) :: term
|
||||
def inspect(device, item, opts) when is_list(opts) do
|
||||
opts = Keyword.put_new(opts, :pretty, true)
|
||||
|
||||
@@ -160,7 +171,7 @@ defmodule IO do
|
||||
Gets a number of bytes from the io device. If the
|
||||
io device is a unicode device, `count` implies
|
||||
the number of unicode codepoints to be retrieved.
|
||||
Otherwise, `count` is the number of raw bytes to be retrieved.
|
||||
Otherwise, `count` is the number of raw bytes to be retrieved.
|
||||
It returns:
|
||||
|
||||
* `data` - The input characters.
|
||||
@@ -171,6 +182,8 @@ defmodule IO do
|
||||
for instance `{:error, :estale}` if reading from an
|
||||
NFS file system.
|
||||
"""
|
||||
@spec getn(chardata, pos_integer) :: chardata | nodata
|
||||
@spec getn(device, chardata) :: chardata | nodata
|
||||
def getn(prompt, count // 1)
|
||||
|
||||
def getn(prompt, count) when is_integer(count) do
|
||||
@@ -187,8 +200,9 @@ defmodule IO do
|
||||
the number of unicode codepoints to be retrieved.
|
||||
Otherwise, `count` is the number of raw bytes to be retrieved.
|
||||
"""
|
||||
@spec getn(device, chardata, pos_integer) :: chardata | nodata
|
||||
def getn(device, prompt, count) do
|
||||
:io.get_chars(map_dev(device), prompt, count)
|
||||
:io.get_chars(map_dev(device), to_chardata(prompt), count)
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -203,50 +217,46 @@ defmodule IO do
|
||||
for instance `{:error, :estale}` if reading from an
|
||||
NFS file system.
|
||||
"""
|
||||
@spec gets(device, chardata) :: chardata | nodata
|
||||
def gets(device // group_leader(), prompt) do
|
||||
:io.get_line(map_dev(device), prompt)
|
||||
:io.get_line(map_dev(device), to_chardata(prompt))
|
||||
end
|
||||
|
||||
@doc """
|
||||
Converts the io device into a Stream. The device is
|
||||
iterated line by line if :line is given or by a given
|
||||
iterated line by line if `:line` is given or by a given
|
||||
number of codepoints.
|
||||
|
||||
This reads the io as utf-8. Check out
|
||||
This reads the IO as utf-8. Check out
|
||||
`IO.binstream/2` to handle the IO as a raw binary.
|
||||
|
||||
Note that an IO stream has side effects and every time
|
||||
you go over the stream you may get different results.
|
||||
|
||||
## Examples
|
||||
|
||||
Here is an example on how we mimic an echo server
|
||||
from the command line:
|
||||
|
||||
Enum.each IO.stream(:stdio, :line), IO.write(&1)
|
||||
Enum.each IO.stream(:stdio, :line), &IO.write(&1)
|
||||
|
||||
"""
|
||||
def stream(device, line_or_bytes) do
|
||||
stream(map_dev(device), line_or_bytes, &1, &2)
|
||||
end
|
||||
|
||||
@doc false
|
||||
def stream(device) do
|
||||
IO.write "IO.stream(device) is deprecated, please use IO.stream(device, :line) instead\n#{Exception.format_stacktrace}"
|
||||
stream(device, :line)
|
||||
@spec stream(device, :line | pos_integer) :: Enumerable.t
|
||||
def stream(device, line_or_codepoints) do
|
||||
fn(acc, f) -> stream(map_dev(device), line_or_codepoints, acc, f) end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Converts the io device into a Stream. The device is
|
||||
iterated line by line.
|
||||
iterated line by line or by a number of bytes. This
|
||||
reads the IO as a raw binary.
|
||||
|
||||
This reads the io as a raw binary.
|
||||
Note that an IO stream has side effects and every time
|
||||
you go over the stream you may get different results.
|
||||
"""
|
||||
@spec binstream(device, :line | pos_integer) :: Enumerable.t
|
||||
def binstream(device, line_or_bytes) do
|
||||
binstream(map_dev(device), line_or_bytes, &1, &2)
|
||||
end
|
||||
|
||||
@doc false
|
||||
def binstream(device) do
|
||||
IO.write "IO.binstream(device) is deprecated, please use IO.binstream(device, :line) instead\n#{Exception.format_stacktrace}"
|
||||
binstream(device, :line)
|
||||
fn(acc, f) -> binstream(map_dev(device), line_or_bytes, acc, f) end
|
||||
end
|
||||
|
||||
@doc false
|
||||
@@ -255,7 +265,7 @@ defmodule IO do
|
||||
:eof ->
|
||||
acc
|
||||
{ :error, reason } ->
|
||||
raise File.IteratorError, reason: reason
|
||||
raise IO.StreamError, reason: reason
|
||||
data ->
|
||||
stream(device, what, fun.(data, acc), fun)
|
||||
end
|
||||
@@ -267,7 +277,7 @@ defmodule IO do
|
||||
:eof ->
|
||||
acc
|
||||
{ :error, reason } ->
|
||||
raise File.IteratorError, reason: reason
|
||||
raise IO.StreamError, reason: reason
|
||||
data ->
|
||||
binstream(device, what, fun.(data, acc), fun)
|
||||
end
|
||||
@@ -277,4 +287,7 @@ defmodule IO do
|
||||
defp map_dev(:stdio), do: :standard_io
|
||||
defp map_dev(:stderr), do: :standard_error
|
||||
defp map_dev(other), do: other
|
||||
|
||||
defp to_chardata(list) when is_list(list), do: list
|
||||
defp to_chardata(other), do: to_string(other)
|
||||
end
|
||||
|
||||
+18
-17
@@ -16,9 +16,9 @@ end
|
||||
|
||||
defmodule IO.ANSI do
|
||||
@moduledoc """
|
||||
This module provides functionality to render ANSI escape sequences
|
||||
Functionality to render ANSI escape sequences
|
||||
(http://en.wikipedia.org/wiki/ANSI_escape_code) — characters embedded
|
||||
in the text used to control formatting, color, and other output options
|
||||
in text used to control formatting, color, and other output options
|
||||
on video text terminals.
|
||||
"""
|
||||
|
||||
@@ -28,7 +28,7 @@ defmodule IO.ANSI do
|
||||
Checks whether the default I/O device is a terminal or a file.
|
||||
|
||||
Used to identify whether printing ANSI escape sequences will likely
|
||||
be printed as intended.
|
||||
be displayed as intended.
|
||||
"""
|
||||
@spec terminal? :: boolean
|
||||
@spec terminal?(:io.device) :: boolean
|
||||
@@ -144,18 +144,19 @@ defmodule IO.ANSI do
|
||||
The format for referring to sequences is `%{red}` and `%{red,bright}` (for
|
||||
multiple sequences).
|
||||
|
||||
It will also append a %{reset} to the string. If you don't want this
|
||||
behaviour, use `escape_fragment/1` and `escape_fragment/2`.
|
||||
It will also append a `%{reset}` to the string. If you don't want this
|
||||
behaviour, use `escape_fragment/2`.
|
||||
|
||||
An optional boolean parameter can be passed to enable or disable
|
||||
emitting actual ANSI codes. When false, no ANSI codes will emitted.
|
||||
emitting actual ANSI codes. When `false`, no ANSI codes will emitted.
|
||||
By default, standard output will be checked if it is a terminal capable
|
||||
of handling these sequences (using `terminal?/0` function)
|
||||
of handling these sequences (using `terminal?/1` function)
|
||||
|
||||
## Example
|
||||
## Examples
|
||||
|
||||
iex> IO.ANSI.escape("Hello %{red,bright,green}yes", true)
|
||||
"Hello \e[31m\e[1m\e[32myes\e[0m"
|
||||
|
||||
iex> IO.ANSI.escape("Hello %{red,bright,green}yes", true)
|
||||
"Hello \e[31m\e[1m\e[32myes\e[0m"
|
||||
"""
|
||||
@spec escape(String.t, emit :: boolean) :: String.t
|
||||
def escape(string, emit // terminal?) do
|
||||
@@ -174,16 +175,16 @@ defmodule IO.ANSI do
|
||||
multiple sequences).
|
||||
|
||||
An optional boolean parameter can be passed to enable or disable
|
||||
emitting actual ANSI codes. When false, no ANSI codes will emitted.
|
||||
emitting actual ANSI codes. When `false`, no ANSI codes will emitted.
|
||||
By default, standard output will be checked if it is a terminal capable
|
||||
of handling these sequences (using `terminal?/0` function)
|
||||
of handling these sequences (using `terminal?/1` function)
|
||||
|
||||
## Example
|
||||
## Examples
|
||||
|
||||
iex> IO.ANSI.escape_fragment("Hello %{red,bright,green}yes", true)
|
||||
"Hello \e[31m\e[1m\e[32myes"
|
||||
iex> IO.ANSI.escape_fragment("%{reset}bye", true)
|
||||
"\e[0mbye"
|
||||
iex> IO.ANSI.escape_fragment("Hello %{red,bright,green}yes", true)
|
||||
"Hello \e[31m\e[1m\e[32myes"
|
||||
iex> IO.ANSI.escape_fragment("%{reset}bye", true)
|
||||
"\e[0mbye"
|
||||
|
||||
"""
|
||||
@spec escape_fragment(String.t, emit :: boolean) :: String.t
|
||||
|
||||
+326
-321
File diff suppressed because it is too large
Load Diff
@@ -15,12 +15,12 @@ defmodule Kernel.CLI do
|
||||
System.argv(argv)
|
||||
|
||||
run fn ->
|
||||
command_results = Enum.map(Enum.reverse(config.commands), process_command(&1, config))
|
||||
command_results = Enum.map(Enum.reverse(config.commands), &process_command(&1, config))
|
||||
command_errors = lc { :error, msg } inlist command_results, do: msg
|
||||
errors = Enum.reverse(config.errors) ++ command_errors
|
||||
|
||||
if errors != [] do
|
||||
Enum.each(errors, IO.puts(:stderr, &1))
|
||||
Enum.each(errors, &IO.puts(:stderr, &1))
|
||||
System.halt(1)
|
||||
end
|
||||
end, config.halt
|
||||
@@ -77,7 +77,7 @@ defmodule Kernel.CLI do
|
||||
defp shared_option?(list, config, callback) do
|
||||
case process_shared(list, config) do
|
||||
{ [h|hs], _ } when h == hd(list) ->
|
||||
new_config = config.update_errors ["#{h} : Unknown option" | &1]
|
||||
new_config = config.update_errors &["#{h} : Unknown option" | &1]
|
||||
callback.(hs, new_config)
|
||||
{ new_list, new_config } ->
|
||||
callback.(new_list, new_config)
|
||||
@@ -104,6 +104,10 @@ defmodule Kernel.CLI do
|
||||
prune_stacktrace(t)
|
||||
end
|
||||
|
||||
defp prune_stacktrace([{ __MODULE__, :wrapper, 1, _ }|_]) do
|
||||
[]
|
||||
end
|
||||
|
||||
defp prune_stacktrace([h|t]) do
|
||||
[h|prune_stacktrace(t)]
|
||||
end
|
||||
@@ -120,17 +124,17 @@ defmodule Kernel.CLI do
|
||||
end
|
||||
|
||||
defp process_shared(["-pa", h|t], config) do
|
||||
Enum.each Path.wildcard(Path.expand(h)), Code.prepend_path(&1)
|
||||
Enum.each Path.wildcard(Path.expand(h)), &Code.prepend_path(&1)
|
||||
process_shared t, config
|
||||
end
|
||||
|
||||
defp process_shared(["-pz", h|t], config) do
|
||||
Enum.each Path.wildcard(Path.expand(h)), Code.append_path(&1)
|
||||
Enum.each Path.wildcard(Path.expand(h)), &Code.append_path(&1)
|
||||
process_shared t, config
|
||||
end
|
||||
|
||||
defp process_shared(["--app", h|t], config) do
|
||||
process_shared t, config.update_commands [{:app, h}|&1]
|
||||
process_shared t, config.update_commands &[{:app, h}|&1]
|
||||
end
|
||||
|
||||
defp process_shared(["--no-halt"|t], config) do
|
||||
@@ -138,15 +142,15 @@ defmodule Kernel.CLI do
|
||||
end
|
||||
|
||||
defp process_shared(["-e", h|t], config) do
|
||||
process_shared t, config.update_commands [{:eval, h}|&1]
|
||||
process_shared t, config.update_commands &[{:eval, h}|&1]
|
||||
end
|
||||
|
||||
defp process_shared(["-r", h|t], config) do
|
||||
process_shared t, config.update_commands [{:require, h}|&1]
|
||||
process_shared t, config.update_commands &[{:require, h}|&1]
|
||||
end
|
||||
|
||||
defp process_shared(["-pr", h|t], config) do
|
||||
process_shared t, config.update_commands [{:parallel_require, h}|&1]
|
||||
process_shared t, config.update_commands &[{:parallel_require, h}|&1]
|
||||
end
|
||||
|
||||
defp process_shared([erl, _|t], config) when erl in ["--erl", "--sname", "--name", "--cookie"] do
|
||||
@@ -176,15 +180,15 @@ defmodule Kernel.CLI do
|
||||
end
|
||||
|
||||
defp process_argv(["-S", h|t], config) do
|
||||
{ config.update_commands([{:script, h}|&1]), t }
|
||||
{ config.update_commands(&[{:script, h}|&1]), t }
|
||||
end
|
||||
|
||||
defp process_argv([h|t] = list, config) do
|
||||
case h do
|
||||
"-" <> _ ->
|
||||
shared_option? list, config, process_argv(&1, &2)
|
||||
shared_option? list, config, &process_argv(&1, &2)
|
||||
_ ->
|
||||
{ config.update_commands([{:file, h}|&1]), t }
|
||||
{ config.update_commands(&[{:file, h}|&1]), t }
|
||||
end
|
||||
end
|
||||
|
||||
@@ -203,19 +207,19 @@ defmodule Kernel.CLI do
|
||||
end
|
||||
|
||||
defp process_compiler(["--no-docs"|t], config) do
|
||||
process_compiler t, config.update_compiler_options([{:docs, false}|&1])
|
||||
process_compiler t, config.update_compiler_options(&[{:docs, false}|&1])
|
||||
end
|
||||
|
||||
defp process_compiler(["--no-debug-info"|t], config) do
|
||||
process_compiler t, config.update_compiler_options([{:debug_info, false}|&1])
|
||||
process_compiler t, config.update_compiler_options(&[{:debug_info, false}|&1])
|
||||
end
|
||||
|
||||
defp process_compiler(["--ignore-module-conflict"|t], config) do
|
||||
process_compiler t, config.update_compiler_options([{:ignore_module_conflict, true}|&1])
|
||||
process_compiler t, config.update_compiler_options(&[{:ignore_module_conflict, true}|&1])
|
||||
end
|
||||
|
||||
defp process_compiler(["--warnings-as-errors"|t], config) do
|
||||
process_compiler t, config.update_compiler_options([{:warnings_as_errors, true}|&1])
|
||||
process_compiler t, config.update_compiler_options(&[{:warnings_as_errors, true}|&1])
|
||||
end
|
||||
|
||||
defp process_compiler(["--verbose"|t], config) do
|
||||
@@ -225,15 +229,15 @@ defmodule Kernel.CLI do
|
||||
defp process_compiler([h|t] = list, config) do
|
||||
case h do
|
||||
"-" <> _ ->
|
||||
shared_option? list, config, process_compiler(&1, &2)
|
||||
shared_option? list, config, &process_compiler(&1, &2)
|
||||
_ ->
|
||||
pattern = if :filelib.is_dir(h), do: "#{h}/**/*.ex", else: h
|
||||
process_compiler t, config.update_compile [pattern|&1]
|
||||
process_compiler t, config.update_compile &[pattern|&1]
|
||||
end
|
||||
end
|
||||
|
||||
defp process_compiler([], config) do
|
||||
{ config.update_commands([{:compile, config.compile}|&1]), [] }
|
||||
{ config.update_commands(&[{:compile, config.compile}|&1]), [] }
|
||||
end
|
||||
|
||||
# Process iex options
|
||||
@@ -253,15 +257,15 @@ defmodule Kernel.CLI do
|
||||
end
|
||||
|
||||
defp process_iex(["-S", h|t], config) do
|
||||
{ config.update_commands([{:script, h}|&1]), t }
|
||||
{ config.update_commands(&[{:script, h}|&1]), t }
|
||||
end
|
||||
|
||||
defp process_iex([h|t] = list, config) do
|
||||
case h do
|
||||
"-" <> _ ->
|
||||
shared_option? list, config, process_iex(&1, &2)
|
||||
shared_option? list, config, &process_iex(&1, &2)
|
||||
_ ->
|
||||
{ config.update_commands([{:file, h}|&1]), t }
|
||||
{ config.update_commands(&[{:file, h}|&1]), t }
|
||||
end
|
||||
end
|
||||
|
||||
@@ -273,16 +277,14 @@ defmodule Kernel.CLI do
|
||||
|
||||
defp process_command({:cookie, h}, _config) do
|
||||
if Node.alive? do
|
||||
Node.set_cookie(binary_to_atom(h))
|
||||
:ok
|
||||
wrapper fn -> Node.set_cookie(binary_to_atom(h)) end
|
||||
else
|
||||
{ :error, "--cookie : Cannot set cookie if the node is not alive (set --name or --sname)" }
|
||||
end
|
||||
end
|
||||
|
||||
defp process_command({:eval, expr}, _config) when is_binary(expr) do
|
||||
Code.eval_string(expr, [])
|
||||
:ok
|
||||
wrapper fn -> Code.eval_string(expr, []) end
|
||||
end
|
||||
|
||||
defp process_command({:app, app}, _config) when is_binary(app) do
|
||||
@@ -296,8 +298,7 @@ defmodule Kernel.CLI do
|
||||
|
||||
defp process_command({:script, file}, _config) when is_binary(file) do
|
||||
if exec = find_elixir_executable(file) do
|
||||
Code.require_file(exec)
|
||||
:ok
|
||||
wrapper fn -> Code.require_file(exec) end
|
||||
else
|
||||
{ :error, "-S : Could not find executable #{file}" }
|
||||
end
|
||||
@@ -305,8 +306,7 @@ defmodule Kernel.CLI do
|
||||
|
||||
defp process_command({:file, file}, _config) when is_binary(file) do
|
||||
if :filelib.is_regular(file) do
|
||||
Code.require_file(file)
|
||||
:ok
|
||||
wrapper fn -> Code.require_file(file) end
|
||||
else
|
||||
{ :error, "No file named #{file}" }
|
||||
end
|
||||
@@ -315,11 +315,10 @@ defmodule Kernel.CLI do
|
||||
defp process_command({:require, pattern}, _config) when is_binary(pattern) do
|
||||
files = Path.wildcard(pattern)
|
||||
files = Enum.uniq(files)
|
||||
files = Enum.filter files, :filelib.is_regular(&1)
|
||||
files = Enum.filter files, &:filelib.is_regular(&1)
|
||||
|
||||
if files != [] do
|
||||
Enum.map files, Code.require_file(&1)
|
||||
:ok
|
||||
wrapper fn -> Enum.map files, &Code.require_file(&1) end
|
||||
else
|
||||
{ :error, "-r : No files matched pattern #{pattern}" }
|
||||
end
|
||||
@@ -328,11 +327,10 @@ defmodule Kernel.CLI do
|
||||
defp process_command({:parallel_require, pattern}, _config) when is_binary(pattern) do
|
||||
files = Path.wildcard(pattern)
|
||||
files = Enum.uniq(files)
|
||||
files = Enum.filter files, :filelib.is_regular(&1)
|
||||
files = Enum.filter files, &:filelib.is_regular(&1)
|
||||
|
||||
if files != [] do
|
||||
Kernel.ParallelRequire.files(files)
|
||||
:ok
|
||||
wrapper fn -> Kernel.ParallelRequire.files(files) end
|
||||
else
|
||||
{ :error, "-pr : No files matched pattern #{pattern}" }
|
||||
end
|
||||
@@ -341,20 +339,26 @@ defmodule Kernel.CLI do
|
||||
defp process_command({:compile, patterns}, config) do
|
||||
:filelib.ensure_dir(:filename.join(config.output, "."))
|
||||
|
||||
files = Enum.map patterns, Path.wildcard(&1)
|
||||
files = Enum.map patterns, &Path.wildcard(&1)
|
||||
files = Enum.uniq(Enum.concat(files))
|
||||
files = Enum.filter files, :filelib.is_regular(&1)
|
||||
files = Enum.filter files, &:filelib.is_regular(&1)
|
||||
|
||||
if files != [] do
|
||||
Code.compiler_options(config.compiler_options)
|
||||
Kernel.ParallelCompiler.files_to_path(files, config.output,
|
||||
each_file: fn file -> if config.verbose_compile do IO.puts "Compiled #{file}" end end)
|
||||
:ok
|
||||
wrapper fn ->
|
||||
Code.compiler_options(config.compiler_options)
|
||||
Kernel.ParallelCompiler.files_to_path(files, config.output,
|
||||
each_file: fn file -> if config.verbose_compile do IO.puts "Compiled #{file}" end end)
|
||||
end
|
||||
else
|
||||
{ :error, "--compile : No files matched patterns #{Enum.join(patterns, ",")}" }
|
||||
end
|
||||
end
|
||||
|
||||
defp wrapper(fun) do
|
||||
fun.()
|
||||
:ok
|
||||
end
|
||||
|
||||
defp find_elixir_executable(file) do
|
||||
if exec = System.find_executable(file) do
|
||||
# If we are on Windows, the executable is going to be
|
||||
|
||||
@@ -0,0 +1,175 @@
|
||||
# This is a module Elixir responsible for tracking
|
||||
# the usage of aliases, imports and requires in the Elixir scope.
|
||||
#
|
||||
# The implementation simply stores dispatch information in an
|
||||
# ETS table and then consults this table once compilation is done.
|
||||
#
|
||||
# Note that since this is required for bootstrap, we can't use
|
||||
# any of the `GenServer.Behaviour` conveniences.
|
||||
defmodule Kernel.LexicalTracker do
|
||||
@timeout 30_000
|
||||
@behavior :gen_server
|
||||
|
||||
@import 2
|
||||
@alias 3
|
||||
|
||||
@doc """
|
||||
Returns all remotes linked to in this lexical scope.
|
||||
"""
|
||||
def remotes(arg) do
|
||||
# If the module is compiled from a function, its lexical
|
||||
# scope may be long gone, so it has no associated PID.
|
||||
if pid = to_pid(arg) do
|
||||
ets = :gen_server.call(pid, :ets, @timeout)
|
||||
:ets.match(ets, { :"$1", :_, :_ }) |> List.flatten
|
||||
else
|
||||
[]
|
||||
end
|
||||
end
|
||||
|
||||
defp to_pid(pid) when is_pid(pid), do: pid
|
||||
defp to_pid(mod) when is_atom(mod) do
|
||||
table = :elixir_module.data_table(mod)
|
||||
[{ _, val }] = :ets.lookup(table, :__lexical_tracker)
|
||||
val
|
||||
end
|
||||
|
||||
# Internal API
|
||||
|
||||
# Starts the tracker and returns its pid.
|
||||
@doc false
|
||||
def start_link do
|
||||
{ :ok, pid } = :gen_server.start_link(__MODULE__, [], [])
|
||||
pid
|
||||
end
|
||||
|
||||
@doc false
|
||||
def stop(pid) do
|
||||
:gen_server.cast(pid, :stop)
|
||||
end
|
||||
|
||||
@doc false
|
||||
def add_import(pid, module, line, warn) do
|
||||
:gen_server.cast(pid, { :add_import, module, line, warn })
|
||||
end
|
||||
|
||||
@doc false
|
||||
def add_alias(pid, module, line, warn) do
|
||||
:gen_server.cast(pid, { :add_alias, module, line, warn })
|
||||
end
|
||||
|
||||
@doc false
|
||||
def remote_dispatch(pid, module) do
|
||||
:gen_server.cast(pid, { :remote_dispatch, module })
|
||||
end
|
||||
|
||||
@doc false
|
||||
def import_dispatch(pid, module) do
|
||||
:gen_server.cast(pid, { :import_dispatch, module })
|
||||
end
|
||||
|
||||
@doc false
|
||||
def alias_dispatch(pid, module) do
|
||||
:gen_server.cast(pid, { :alias_dispatch, module })
|
||||
end
|
||||
|
||||
@doc false
|
||||
def collect_unused_imports(pid) do
|
||||
unused(pid, @import)
|
||||
end
|
||||
|
||||
@doc false
|
||||
def collect_unused_aliases(pid) do
|
||||
unused(pid, @alias)
|
||||
end
|
||||
|
||||
defp unused(pid, pos) do
|
||||
ets = :gen_server.call(pid, :ets, @timeout)
|
||||
:ets.foldl(fn
|
||||
{ module, _, _ } = tuple, acc when is_integer(:erlang.element(pos, tuple)) ->
|
||||
[{ module, :erlang.element(pos, tuple) }|acc]
|
||||
_, acc ->
|
||||
acc
|
||||
end, [], ets) |> Enum.sort
|
||||
end
|
||||
|
||||
# Callbacks
|
||||
|
||||
|
||||
def init([]) do
|
||||
{ :ok, :ets.new(:lexical, [:protected]) }
|
||||
end
|
||||
|
||||
def handle_call(:ets, _from, d) do
|
||||
{ :reply, d, d }
|
||||
end
|
||||
|
||||
def handle_call(_request, _from, d) do
|
||||
{ :noreply, d }
|
||||
end
|
||||
|
||||
def handle_cast({ :remote_dispatch, module }, d) do
|
||||
add_module(d, module)
|
||||
{ :noreply, d }
|
||||
end
|
||||
|
||||
def handle_cast({ :import_dispatch, module }, d) do
|
||||
add_dispatch(d, module, @import)
|
||||
{ :noreply, d }
|
||||
end
|
||||
|
||||
def handle_cast({ :alias_dispatch, module }, d) do
|
||||
add_dispatch(d, module, @alias)
|
||||
{ :noreply, d }
|
||||
end
|
||||
|
||||
def handle_cast({ :add_import, module, line, warn }, d) do
|
||||
add_directive(d, module, line, warn, @import)
|
||||
{ :noreply, d }
|
||||
end
|
||||
|
||||
def handle_cast({ :add_alias, module, line, warn }, d) do
|
||||
add_directive(d, module, line, warn, @alias)
|
||||
{ :noreply, d }
|
||||
end
|
||||
|
||||
def handle_cast(:stop, d) do
|
||||
{ :stop, :normal, d }
|
||||
end
|
||||
|
||||
def handle_cast(_msg, d) do
|
||||
{ :noreply, d }
|
||||
end
|
||||
|
||||
def handle_info(_msg, d) do
|
||||
{ :noreply, d }
|
||||
end
|
||||
|
||||
def terminate(_reason, _d) do
|
||||
:ok
|
||||
end
|
||||
|
||||
def code_change(_old, d, _extra) do
|
||||
{ :ok, d }
|
||||
end
|
||||
|
||||
# Callbacks helpers
|
||||
|
||||
# In the table we keep imports and aliases.
|
||||
# If the value is false, it was not imported/aliased
|
||||
# If the value is true, it was imported/aliased
|
||||
# If the value is a line, it was imported/aliased and has a pending warning
|
||||
defp add_module(d, module) do
|
||||
:ets.insert_new(d, { module, false, false })
|
||||
end
|
||||
|
||||
defp add_dispatch(d, module, pos) do
|
||||
:ets.update_element(d, module, { pos, true })
|
||||
end
|
||||
|
||||
defp add_directive(d, module, line, warn, pos) do
|
||||
add_module(d, module)
|
||||
marker = if warn, do: line, else: true
|
||||
:ets.update_element(d, module, { pos, marker })
|
||||
end
|
||||
end
|
||||
@@ -20,13 +20,8 @@ defmodule Kernel.ParallelCompiler do
|
||||
* `:each_file` - for each file compiled, invokes the callback passing the file
|
||||
* `:each_module` - for each module compiled, invokes the callback
|
||||
passing the file, module and the module bytecode
|
||||
* `:each_waiting` - every time a module waits for another module to be compiled,
|
||||
this callback is invoked with the module we are waiting
|
||||
|
||||
The compiler doesn't care about the return values of the callbacks except
|
||||
by `each_waiting`, which must return nil or a file as a hint where the source
|
||||
could be found.
|
||||
|
||||
The compiler doesn't care about the return values of the callbacks.
|
||||
Returns the modules generated by each compiled file.
|
||||
"""
|
||||
def files(files, callbacks // [])
|
||||
@@ -47,14 +42,15 @@ defmodule Kernel.ParallelCompiler do
|
||||
|
||||
defp spawn_compilers(files, path, callbacks) do
|
||||
Code.ensure_loaded(Kernel.ErrorHandler)
|
||||
:elixir_code_server.cast(:reset_warnings)
|
||||
compiler_pid = self()
|
||||
:elixir_code_server.cast({ :reset_warnings, compiler_pid })
|
||||
schedulers = max(:erlang.system_info(:schedulers_online), 2)
|
||||
|
||||
result = spawn_compilers(files, files, path, callbacks, [], [], schedulers, [])
|
||||
|
||||
# In case --warning-as-errors is enabled and there was a warning,
|
||||
# compilation status will be set to error and we fail with Kernel.CompilationError
|
||||
case :elixir_code_server.call(:compilation_status) do
|
||||
# compilation status will be set to error and we fail with CompileError
|
||||
case :elixir_code_server.call({ :compilation_status, compiler_pid }) do
|
||||
:ok -> result
|
||||
:error -> raise CompileError, [], []
|
||||
end
|
||||
@@ -118,27 +114,18 @@ defmodule Kernel.ParallelCompiler do
|
||||
# list because someone invoked try/rescue UndefinedFunctionError
|
||||
new_waiting = List.keydelete(waiting, child, 0)
|
||||
spawn_compilers(files, original, output, callbacks, new_waiting, new_queued, schedulers, result)
|
||||
{ :module_available, child, file, module, binary } ->
|
||||
{ :module_available, child, ref, file, module, binary } ->
|
||||
if callback = Keyword.get(callbacks, :each_module) do
|
||||
callback.(file, module, binary)
|
||||
end
|
||||
|
||||
# Release the module loader which is waiting for an ack
|
||||
child <- { self, :ack }
|
||||
child <- { ref, :ack }
|
||||
|
||||
new_waiting = release_waiting_processes(module, waiting)
|
||||
new_result = [module|result]
|
||||
wait_for_messages(files, original, output, callbacks, new_waiting, queued, schedulers, new_result)
|
||||
{ :waiting, child, on } ->
|
||||
# If one of the callbacks is each_waiting and we haven't seen
|
||||
# the hinted file before, add it to the list to be processed.
|
||||
if (callback = Keyword.get(callbacks, :each_waiting)) &&
|
||||
(hint = callback.(on)) &&
|
||||
not(hint in original) do
|
||||
files = [hint|files]
|
||||
original = [hint|original]
|
||||
end
|
||||
|
||||
new_waiting = :orddict.store(child, on, waiting)
|
||||
spawn_compilers(files, original, output, callbacks, new_waiting, queued, schedulers, result)
|
||||
{ :failure, child, kind, reason, stacktrace } ->
|
||||
|
||||
@@ -75,7 +75,7 @@ defmodule Kernel.RecordRewriter do
|
||||
if :lists.member({ function, length(args) + 1 }, optimizable) do
|
||||
case record_field_info(function) do
|
||||
{ kind, field } ->
|
||||
if index = Enum.find_index(fields, field == &1) do
|
||||
if index = Enum.find_index(fields, &(field == &1)) do
|
||||
optimize_record_accessor_call(line, res, kind, field, index, left, args)
|
||||
end
|
||||
nil ->
|
||||
@@ -352,7 +352,7 @@ defmodule Kernel.RecordRewriter do
|
||||
end
|
||||
|
||||
defp extract_vars({ :tuple, _, args }, vars) do
|
||||
[Enum.map(args, extract_vars(&1, []))|vars]
|
||||
[Enum.map(args, &extract_vars(&1, []))|vars]
|
||||
end
|
||||
|
||||
defp extract_vars(_, vars) do
|
||||
|
||||
@@ -9,8 +9,8 @@ defmodule Kernel.SpecialForms do
|
||||
to define data structures, respectively tuples, lists and binaries.
|
||||
|
||||
This module also documents Elixir's pseudo variables (`__MODULE__`,
|
||||
`__FILE__`, `__ENV__` and `__CALLER__`). Pseudo variables return
|
||||
information about Elixir's compilation environment and can only
|
||||
`__FILE__`, `__ENV__`, `__DIR__` and `__CALLER__`). Pseudo variables
|
||||
return information about Elixir's compilation environment and can only
|
||||
be read, never assigned to.
|
||||
|
||||
Finally, it also documents 3 special forms (`__block__`,
|
||||
@@ -22,25 +22,20 @@ defmodule Kernel.SpecialForms do
|
||||
@doc """
|
||||
Defines a new tuple.
|
||||
|
||||
Only two item tuples are considered literals in Elixir.
|
||||
Therefore all other tuples are represented in the AST
|
||||
as a call to the special form `:{}`.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> { 1, 2, 3 }
|
||||
{ 1, 2, 3 }
|
||||
iex> quote do: { 1, 2, 3 }
|
||||
{ :{}, [], [1,2,3] }
|
||||
|
||||
"""
|
||||
defmacro :{}.(args)
|
||||
|
||||
@doc """
|
||||
Defines a new list.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> [ 1, 2, 3 ]
|
||||
[ 1, 2, 3 ]
|
||||
|
||||
"""
|
||||
defmacro :[].(args)
|
||||
|
||||
@doc """
|
||||
Defines a new bitstring.
|
||||
|
||||
@@ -80,12 +75,12 @@ defmodule Kernel.SpecialForms do
|
||||
|
||||
<<102, rest :: binary>>
|
||||
|
||||
The type can be integer, float, binary, bytes, bitstring,
|
||||
bits, utf8, utf16 or utf32, e.g.:
|
||||
The type can be integer, float, bitstring/bits, binary/bytes,
|
||||
utf8, utf16 or utf32, e.g.:
|
||||
|
||||
<<102 :: float, rest :: binary>>
|
||||
|
||||
Integer can be any arbitrary precision integer. A float is an
|
||||
An integer can be any arbitrary precision integer. A float is an
|
||||
IEEE 754 binary32 or binary64 floating point number. A bitstring
|
||||
is an arbitrary series of bits. A binary is a special case of
|
||||
bitstring that has a total size divisible by 8.
|
||||
@@ -179,7 +174,120 @@ defmodule Kernel.SpecialForms do
|
||||
defmacro :<<>>.(args)
|
||||
|
||||
@doc """
|
||||
`alias` is used to setup atom aliases, often useful with modules names.
|
||||
Defines a remote call or an alias.
|
||||
|
||||
The dot (`.`) in Elixir can be used for remote calls:
|
||||
|
||||
iex> String.downcase("FOO")
|
||||
"foo"
|
||||
|
||||
In this example above, we have used `.` to invoke `downcase` in the
|
||||
`String` alias, passing "FOO" as argument. We can also use the dot
|
||||
for creating aliases:
|
||||
|
||||
iex> Hello.World
|
||||
Hello.World
|
||||
|
||||
This time, we have joined two aliases, defining the final alias
|
||||
`Hello.World`.
|
||||
|
||||
## Syntax
|
||||
|
||||
The right side of `.` may be a word starting in upcase, which represents
|
||||
an alias, a word starting with lowercase or underscore, any valid language
|
||||
operator or any name wrapped in single- or double-quotes. Those are all valid
|
||||
examples:
|
||||
|
||||
iex> Kernel.Sample
|
||||
Kernel.Sample
|
||||
iex> Kernel.length([1,2,3])
|
||||
3
|
||||
iex> Kernel.+(1, 2)
|
||||
3
|
||||
iex> Kernel."length"([1,2,3])
|
||||
3
|
||||
iex> Kernel.'+'(1, 2)
|
||||
3
|
||||
|
||||
Note that `Kernel."HELLO"` will be treated as a remote call and not an alias.
|
||||
This choice was done so every time single- or double-quotes are used, we have
|
||||
a remote call irregardless of the quote contents. This decision is also reflected
|
||||
in the quoted expressions discussed below.
|
||||
|
||||
## Runtime (dynamic) behaviour
|
||||
|
||||
The result returned by `.` is always specified by the right-side:
|
||||
|
||||
iex> x = String
|
||||
iex> x.downcase("FOO")
|
||||
"foo"
|
||||
iex> x.Sample
|
||||
String.Sample
|
||||
|
||||
In case the right-side is also dynamic, `.`'s behaviour can be reproduced
|
||||
at runtime via `apply/3` and `Module.concat/2`:
|
||||
|
||||
iex> apply(Kernel, :+, [1,2])
|
||||
3
|
||||
iex> Module.concat(Kernel, Sample)
|
||||
Kernel.Sample
|
||||
|
||||
## Quoted expression
|
||||
|
||||
When `.` is used, the quoted expression may take two distinct
|
||||
forms. When the right side starts with a lowercase letter (or
|
||||
underscore):
|
||||
|
||||
iex> quote do: String.downcase("FOO")
|
||||
{{:., [], [{:__aliases__, [alias: false], [:String]}, :downcase]}, [], ["FOO"]}
|
||||
|
||||
Notice we have an inner tuple, containing the atom `:.` representing
|
||||
the dot as first element:
|
||||
|
||||
{:., [], [{:__aliases__, [alias: false], [:String]}, :downcase]}
|
||||
|
||||
This tuple follows the general quoted expression structure in Elixir,
|
||||
with the name as first argument, some keyword list as metadata as second,
|
||||
and the number of arguments as third. In this case, the arguments is the
|
||||
alias `String` and the atom `:downcase`. The second argument is **always**
|
||||
an atom:
|
||||
|
||||
iex> quote do: String."downcase"("FOO")
|
||||
{{:., [], [{:__aliases__, [alias: false], [:String]}, :downcase]}, [], ["FOO"]}
|
||||
|
||||
The tuple containing `:.` is wrapped in another tuple, which actually
|
||||
represents the function call, and has `"FOO"` as argument.
|
||||
|
||||
When the right side is an alias (i.e. starts with uppercase), we get instead:
|
||||
|
||||
iex> quote do: Hello.World
|
||||
{:__aliases__, [alias: false], [:Hello, :World]}
|
||||
|
||||
We got into more details about aliases in the `__aliases__` special form
|
||||
documentation.
|
||||
|
||||
## Unquoting
|
||||
|
||||
We can also use unquote to generate a remote call in a quoted expression:
|
||||
|
||||
iex> x = :downcase
|
||||
iex> quote do: String.unquote(x)("FOO")
|
||||
{{:., [], [{:__aliases__, [alias: false], [:String]}, :downcase]}, [], ["FOO"]}
|
||||
|
||||
Similar to `Kernel."HELLO"`, `unquote(x)` will always generate a remote call,
|
||||
independent of the value of `x`. To generate an alias via the quoted expression,
|
||||
one needs to rely on `Module.concat/2`:
|
||||
|
||||
iex> x = Sample
|
||||
iex> quote do: Module.concat(String, unquote(x))
|
||||
{{:., [], [{:__aliases__, [alias: false], [:Module]}, :concat]}, [],
|
||||
[{:__aliases__, [alias: false], [:String]}, Sample]}
|
||||
|
||||
"""
|
||||
defmacro (:.).(left, right)
|
||||
|
||||
@doc """
|
||||
`alias` is used to setup aliases, often useful with modules names.
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -213,12 +321,23 @@ defmodule Kernel.SpecialForms do
|
||||
`import`, `require` and `alias` are called directives and all
|
||||
have lexical scope. This means you can set up aliases inside
|
||||
specific functions and it won't affect the overall scope.
|
||||
|
||||
## Warnings
|
||||
|
||||
If you alias a module and you don't use the alias, Elixir is
|
||||
going to issue a warning implying the alias is not being used.
|
||||
|
||||
In case the alias is generated automatically by a macro,
|
||||
Elixir won't emit any warnings though, since the alias
|
||||
was not explicitly defined.
|
||||
|
||||
Both warning behaviors could be changed by explicitily
|
||||
setting the `:warn` option to true or false.
|
||||
"""
|
||||
defmacro alias(module, opts)
|
||||
|
||||
@doc """
|
||||
`require` is used to require the presence of external
|
||||
modules so macros can be invoked.
|
||||
Requires a given module to be compiled and loaded.
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -246,6 +365,8 @@ defmodule Kernel.SpecialForms do
|
||||
defmacro require(module, opts)
|
||||
|
||||
@doc """
|
||||
Imports function and macros from other modules.
|
||||
|
||||
`import` allows one to easily access functions or macros from
|
||||
others modules without using the qualified name.
|
||||
|
||||
@@ -335,14 +456,16 @@ defmodule Kernel.SpecialForms do
|
||||
defmacro import(module, opts)
|
||||
|
||||
@doc """
|
||||
Returns the current environment information as a `Macro.Env`
|
||||
record. In the environment you can access the current filename,
|
||||
Returns the current environment information as a `Macro.Env[]` record.
|
||||
|
||||
In the environment you can access the current filename,
|
||||
line numbers, set up aliases, the current function and others.
|
||||
"""
|
||||
defmacro __ENV__
|
||||
|
||||
@doc """
|
||||
Returns the current module name as an atom or `nil` otherwise.
|
||||
|
||||
Although the module can be accessed in the `__ENV__`, this macro
|
||||
is a convenient shortcut.
|
||||
"""
|
||||
@@ -350,6 +473,7 @@ defmodule Kernel.SpecialForms do
|
||||
|
||||
@doc """
|
||||
Returns the current file name as a binary.
|
||||
|
||||
Although the file can be accessed in the `__ENV__`, this macro
|
||||
is a convenient shortcut.
|
||||
"""
|
||||
@@ -360,8 +484,8 @@ defmodule Kernel.SpecialForms do
|
||||
"""
|
||||
defmacro __DIR__
|
||||
|
||||
@doc """
|
||||
Allows you to get the representation of any expression.
|
||||
@doc %S"""
|
||||
Gets the representation of any expression.
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -391,14 +515,14 @@ defmodule Kernel.SpecialForms do
|
||||
* `:unquote` - When false, disables unquoting. Useful when you have a quote
|
||||
inside another quote and want to control what quote is
|
||||
able to unquote;
|
||||
* `:location` - When set to `:keep`, keeps the current line and file on quotes.
|
||||
* `:location` - When set to `:keep`, keeps the current line and file from quote.
|
||||
Read the Stacktrace information section below for more information;
|
||||
* `:hygiene` - Allows a developer to disable hygiene selectively;
|
||||
* `:context` - Sets the resolution context;
|
||||
* `:binding` - Passes a binding to the macro. Whenever a binding is given,
|
||||
`unquote` is automatically disabled;
|
||||
* `:bind_quoted` - Passes a binding to the macro. Whenever a binding is given,
|
||||
`unquote` is automatically disabled;
|
||||
|
||||
## Macro literals
|
||||
## Quote literals
|
||||
|
||||
Besides the tuple described above, Elixir has a few literals that
|
||||
when quoted return themselves. They are:
|
||||
@@ -407,40 +531,111 @@ defmodule Kernel.SpecialForms do
|
||||
1 #=> Integers
|
||||
2.0 #=> Floats
|
||||
[1, 2] #=> Lists
|
||||
"binaries" #=> Binaries
|
||||
"strings" #=> Strings
|
||||
{key, value} #=> Tuple with two elements
|
||||
|
||||
## Hygiene and context
|
||||
## Quote and macros
|
||||
|
||||
Elixir macros are hygienic via means of deferred resolution.
|
||||
This means variables, aliases and imports defined inside the
|
||||
quoted refer to the context that defined the macro and not
|
||||
the context where the macro is expanded.
|
||||
`quote` is commonly used with macros for code generation. As an exercise,
|
||||
let's define a macro that multiplies a number by itself (squared). Note
|
||||
there is no reason to define such as a macro (and it would actually be
|
||||
seen as a bad practice), but it is simple enough that allows us to focus
|
||||
on the important aspects of quotes and macros:
|
||||
|
||||
For this mechanism to work, every quoted code is attached
|
||||
to a context. Consider the following example:
|
||||
|
||||
defmodule ContextSample do
|
||||
def hello do
|
||||
quote do: world
|
||||
defmodule Math do
|
||||
defmacro squared(x) do
|
||||
quote do
|
||||
unquote(x) * unquote(x)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
ContextSample.hello
|
||||
#=> {:world,[],ContextSample}
|
||||
We can invoke it as:
|
||||
|
||||
Notice how the third element of the returned tuple is the
|
||||
module name. This means that the variable is associated to the
|
||||
`ContextSample` module and only code generated by this module
|
||||
will be able to access that particular `world` variable.
|
||||
While this means macros from the same module could have
|
||||
conflicting variables, it also allows different quotes from
|
||||
the same module to access them.
|
||||
import Math
|
||||
IO.puts "Got #{squared(5)}"
|
||||
|
||||
The context can be disabled or changed by explicitly setting
|
||||
the `context` option. All hygiene mechanisms are based on such
|
||||
context and we are going to explore each of them in the following
|
||||
subsections.
|
||||
At first, there is nothing in this example that actually reveals it is a
|
||||
macro. But what is happening is that, at compilation time, `squared(5)`
|
||||
becomes `5 * 5`. We can see this behaviour in practice though
|
||||
because our macro actually has a bug:
|
||||
|
||||
import Math
|
||||
my_number = fn ->
|
||||
IO.puts "Returning 5"
|
||||
5
|
||||
end
|
||||
IO.puts "Got #{squared(my_number.())}"
|
||||
|
||||
The example above will print:
|
||||
|
||||
Returning 5
|
||||
Returning 5
|
||||
25
|
||||
|
||||
Notice how "Returning 5" was printed twice, instead of just once. This is
|
||||
because a macro receives an expression and not a value (which is what we
|
||||
would expect in a regular function). This means that:
|
||||
|
||||
squared(my_number.())
|
||||
|
||||
Actually expands to:
|
||||
|
||||
my_number.() * my_number.()
|
||||
|
||||
Which invokes the function twice, explaining why we get the printed value
|
||||
twice! In the majority of the cases, this is actually unexpected behaviour,
|
||||
and that's why one of the first things you need to keep in mind when it
|
||||
comes to macros is to **not unquote the same value more than once**.
|
||||
|
||||
Let's fix our macro:
|
||||
|
||||
defmodule Math do
|
||||
defmacro squared(x) do
|
||||
quote do
|
||||
x = unquote(x)
|
||||
x * x
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
Now invoking `square(my_number.())` as before will print the value just
|
||||
once.
|
||||
|
||||
In fact, this pattern is so common that most of the times you will want
|
||||
to use the `bind_quoted` option with `quote`:
|
||||
|
||||
defmodule Math do
|
||||
defmacro squared(x) do
|
||||
quote bind_quoted: [x: x] do
|
||||
x * x
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
`:bind_quoted` will translate to the same example as above. `:bind_quoted`
|
||||
can be used in many cases and is seen as good practice, not only because
|
||||
it helps us from running into common mistakes but also because it allows
|
||||
us to leverage other tools exposed by macros, as unquote fragments discussed
|
||||
in some sections below.
|
||||
|
||||
Before we finish this brief introduction, you will notice that, even though
|
||||
we defined a variable `x` inside our quote:
|
||||
|
||||
quote do
|
||||
x = unquote(x)
|
||||
x * x
|
||||
end
|
||||
|
||||
When we call:
|
||||
|
||||
import Math
|
||||
squared(5)
|
||||
x #=> ** (RuntimeError) undefined function or variable: x
|
||||
|
||||
We can see that `x` did not leak to the user context. This happens
|
||||
because Elixir macros are hygienic, a topic we will discuss at length
|
||||
in the next sections as well.
|
||||
|
||||
### Hygiene in variables
|
||||
|
||||
@@ -476,31 +671,47 @@ defmodule Kernel.SpecialForms do
|
||||
NoHygiene.interference
|
||||
a #=> 1
|
||||
|
||||
It is important to understand that quoted variables are scoped
|
||||
to the module they are defined. That said, even if two modules
|
||||
define the same quoted variable `a`, their values are going
|
||||
to be independent:
|
||||
Note that you cannot even access variables defined by the same
|
||||
module unless you explicitly give it a context:
|
||||
|
||||
defmodule Hygiene1 do
|
||||
defmacro var1 do
|
||||
quote do: a = 1
|
||||
defmodule Hygiene do
|
||||
defmacro write do
|
||||
quote do
|
||||
a = 1
|
||||
end
|
||||
end
|
||||
|
||||
defmacro read do
|
||||
quote do
|
||||
a
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defmodule Hygiene2 do
|
||||
defmacro var2 do
|
||||
quote do: a = 2
|
||||
Hygiene.write
|
||||
Hygiene.read
|
||||
#=> ** (RuntimeError) undefined function or variable: a
|
||||
|
||||
For such, you can explicitly pass the current module scope as
|
||||
argument:
|
||||
|
||||
defmodule ContextHygiene do
|
||||
defmacro write do
|
||||
quote do
|
||||
var!(a, ContextHygiene) = 1
|
||||
end
|
||||
end
|
||||
|
||||
defmacro read do
|
||||
quote do
|
||||
var!(a, ContextHygiene)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
Calling macros `var1` and `var2` are not going to change their
|
||||
each other values for `a`. This is useful because quoted
|
||||
variables from different modules cannot conflict. If you desire
|
||||
to explicitly access a variable from another module, we can once
|
||||
again use `var!` macro, but explicitly passing a second argument:
|
||||
|
||||
# Access the variable a from Hygiene1
|
||||
quote do: var!(a, Hygiene1) = 2
|
||||
ContextHygiene.write
|
||||
ContextHygiene.read
|
||||
#=> 1
|
||||
|
||||
Hygiene for variables can be disabled overall as:
|
||||
|
||||
@@ -557,7 +768,6 @@ defmodule Kernel.SpecialForms do
|
||||
#=> "world"
|
||||
end
|
||||
|
||||
|
||||
## Hygiene in imports
|
||||
|
||||
Similar to aliases, imports in Elixir are hygienic. Consider the
|
||||
@@ -609,35 +819,35 @@ defmodule Kernel.SpecialForms do
|
||||
|
||||
## Stacktrace information
|
||||
|
||||
One of Elixir's goals is to provide a proper stacktrace whenever there is an
|
||||
exception. In order to work properly with macros, the default behavior
|
||||
in quote is to not set a line. When a macro is invoked and the quoted
|
||||
expressions is expanded, the call site line is inserted.
|
||||
When defining functions via macros, developers have the option of
|
||||
choosing if runtime errors will be reported from the caller or from
|
||||
inside the quote. Let's see an example:
|
||||
|
||||
This is a good behavior for the majority of the cases, except if the macro
|
||||
is defining new functions. Consider this example:
|
||||
|
||||
defmodule MyServer do
|
||||
use GenServer.Behaviour
|
||||
end
|
||||
|
||||
`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.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 the original
|
||||
line and file lines instead of setting them to 0:
|
||||
|
||||
quote location: :keep do
|
||||
def handle_call(request, _from, state) do
|
||||
{ :reply, :undef, state }
|
||||
# adder.ex
|
||||
defmodule Adder do
|
||||
@doc "Defines a function that adds two numbers"
|
||||
defmacro defadd do
|
||||
quote location: :keep do
|
||||
def add(a, b), do: a + b
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
It is important to warn though that `location: :keep` evaluates the
|
||||
code as if it was defined inside `GenServer.Behaviour` file, in
|
||||
particular, the macro `__FILE__` and exceptions happening inside
|
||||
the quote will always point to `GenServer.Behaviour` file.
|
||||
# sample.ex
|
||||
defmodule Sample do
|
||||
import Adder
|
||||
defadd
|
||||
end
|
||||
|
||||
When using `location: :keep` and invalid arguments are given to
|
||||
`Sample.add/2`, the stacktrace information will point to the file
|
||||
and line inside the quote. Without `location: :keep`, the error is
|
||||
reported to where `defadd` was invoked.
|
||||
|
||||
Note that `location: :keep` evaluates the code as if it was defined
|
||||
inside the `Adder` file, in particular, the macro `__FILE__` and
|
||||
exceptions happening inside the quote will always point to the file
|
||||
where `Added` was defined.
|
||||
|
||||
## Binding and unquote fragments
|
||||
|
||||
@@ -697,8 +907,8 @@ defmodule Kernel.SpecialForms do
|
||||
One solution for this problem is to disable unquoting in the
|
||||
macro, however, doing that would make it impossible to inject
|
||||
`kv` representation into the tree. That's when the `:bind_quoted`
|
||||
option comes to the rescue. By using `:bind_quoted`, we can
|
||||
automatically disable unquoting while still injecting the
|
||||
option comes to the rescue (again!). By using `:bind_quoted`, we
|
||||
can automatically disable unquoting while still injecting the
|
||||
desired variables into the tree:
|
||||
|
||||
defmacro defkv(kv) do
|
||||
@@ -709,26 +919,21 @@ defmodule Kernel.SpecialForms do
|
||||
end
|
||||
end
|
||||
|
||||
In fact, the `:binding` option is recommended every time one
|
||||
desires to inject a value into the quote.
|
||||
In fact, the `:bind_quoted` option is recommended every time
|
||||
one desires to inject a value into the quote.
|
||||
"""
|
||||
defmacro quote(opts, block)
|
||||
|
||||
@doc """
|
||||
When used inside quoting, marks that the variable should
|
||||
not be hygienized. The argument can be either a variable
|
||||
node (i.e. a tuple with three elements where the last
|
||||
one is an atom) or an atom representing the variable name.
|
||||
unquoted or an atom representing the variable name.
|
||||
Check `quote/2` for more information.
|
||||
"""
|
||||
defmacro var!(var)
|
||||
|
||||
@doc """
|
||||
Defines a variable in the given context.
|
||||
|
||||
If the context is `false`, it is not stored in any particular
|
||||
context and the variable is not shared in between clauses.
|
||||
|
||||
Check `quote/2` for more information.
|
||||
"""
|
||||
defmacro var!(var, context)
|
||||
@@ -820,6 +1025,9 @@ defmodule Kernel.SpecialForms do
|
||||
iex> lc <<r::8, g::8, b::8>> inbits pixels, do: {r, g, b}
|
||||
[{213,45,132},{64,76,32},{76,0,0},{234,32,15}]
|
||||
|
||||
Variable assignments in the `do` block of the comprehension
|
||||
are not reflected outside of the block.
|
||||
|
||||
Note: Unlike Erlang, Elixir comprehension filters
|
||||
never behave as guards when it comes to errors. Errors in
|
||||
list comprehensions will always be raised. Consider this
|
||||
@@ -837,10 +1045,11 @@ defmodule Kernel.SpecialForms do
|
||||
defmacro lc(args)
|
||||
|
||||
@doc """
|
||||
Defines a bit comprehension. It follows the same syntax as
|
||||
a list comprehension but expects each element returned to
|
||||
be a bitstring. For example, here is how to remove all
|
||||
spaces from a string:
|
||||
Defines a bit comprehension.
|
||||
|
||||
It follows the same syntax and behaviour as a list comprehension
|
||||
but expects each element returned to be a bitstring. For example,
|
||||
here is how to remove all spaces from a string:
|
||||
|
||||
iex> bc <<c>> inbits " hello world ", c != ? , do: <<c>>
|
||||
"helloworld"
|
||||
@@ -849,6 +1058,21 @@ defmodule Kernel.SpecialForms do
|
||||
defmacro bc(args)
|
||||
|
||||
@doc """
|
||||
Defines an anonymous function.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> add = fn a, b -> a + b end
|
||||
iex> add.(1, 2)
|
||||
3
|
||||
|
||||
"""
|
||||
name = :fn
|
||||
defmacro unquote(name)(clauses)
|
||||
|
||||
@doc """
|
||||
Internal special form for block expressions.
|
||||
|
||||
This is the special form used whenever we have a block
|
||||
of expressions in Elixir. This special form is private
|
||||
and should not be invoked directly:
|
||||
@@ -860,7 +1084,7 @@ defmodule Kernel.SpecialForms do
|
||||
defmacro __block__(args)
|
||||
|
||||
@doc """
|
||||
Captures a call as an anonymous function.
|
||||
Captures an anonymous function.
|
||||
|
||||
The most common format to capture a function is via module,
|
||||
name and arity:
|
||||
@@ -879,17 +1103,17 @@ defmodule Kernel.SpecialForms do
|
||||
A capture also allows the captured functions to be partially
|
||||
applied, for example:
|
||||
|
||||
iex> fun = &atom_to_binary(&1, :utf8)
|
||||
iex> fun.(:hello)
|
||||
"hello"
|
||||
iex> fun = &is_record(&1, Range)
|
||||
iex> fun.(1..3)
|
||||
true
|
||||
|
||||
In the example above, we use &1 as a placeholder, generating
|
||||
a function with one argument. We could also use `&2` and `&3`
|
||||
to delimit more arguments:
|
||||
|
||||
iex> fun = &atom_to_binary(&1, &2)
|
||||
iex> fun.(:hello, :utf8)
|
||||
"hello"
|
||||
iex> fun = &is_record(&1, &2)
|
||||
iex> fun.(1..3, Range)
|
||||
true
|
||||
|
||||
Since operators are calls, they are also supported, although
|
||||
they require explicit parentheses around:
|
||||
@@ -938,30 +1162,31 @@ defmodule Kernel.SpecialForms do
|
||||
defmacro unquote(name)(expr)
|
||||
|
||||
@doc """
|
||||
This is the special form used whenever we have to temporarily
|
||||
change the scope information of a block. Used when `quote` is
|
||||
invoked with `location: :keep` to execute a given block as if
|
||||
it belonged to another file.
|
||||
Internal special form for modifying scope information.
|
||||
|
||||
Used when `quote` is invoked with `location: :keep` to execute
|
||||
a given block as if it belonged to another file.
|
||||
|
||||
quote location: :keep, do: 1
|
||||
#=> { :__scope__, [line: 1], [[file: "iex"],[do: 1]] }
|
||||
|
||||
Check `quote/1` for more information.
|
||||
Check `quote/2` for more information.
|
||||
"""
|
||||
defmacro __scope__(opts, args)
|
||||
|
||||
@doc """
|
||||
This is the special form used to hold aliases information.
|
||||
Internal special form to hold aliases information.
|
||||
|
||||
It is usually compiled to an atom:
|
||||
|
||||
quote do: Foo.Bar #=>
|
||||
{ :__aliases__, [], [:Foo,:Bar] }
|
||||
iex> quote do: Foo.Bar
|
||||
{:__aliases__, [alias: false], [:Foo, :Bar]}
|
||||
|
||||
Elixir represents `Foo.Bar` as `__aliases__` so calls can be
|
||||
unambiguously identified by the operator `:.`. For example:
|
||||
|
||||
quote do: Foo.bar #=>
|
||||
{{:.,[],[{:__aliases__,[],[:Foo]},:bar]},[],[]}
|
||||
iex> quote do: Foo.bar
|
||||
{{:., [], [{:__aliases__, [alias: false], [:Foo]}, :bar]}, [], []}
|
||||
|
||||
Whenever an expression iterator sees a `:.` as the tuple key,
|
||||
it can be sure that it represents a call and the second argument
|
||||
@@ -978,7 +1203,7 @@ defmodule Kernel.SpecialForms do
|
||||
4) When the head element of aliases is not an atom, it is expanded at runtime:
|
||||
|
||||
quote do: some_var.Foo
|
||||
{:__aliases__,[],[{:some_var,[],:quoted},:Bar]}
|
||||
{:__aliases__, [], [{:some_var, [], Elixir}, :Foo]}
|
||||
|
||||
Since `some_var` is not available at compilation time, the compiler
|
||||
expands such expression to:
|
||||
|
||||
@@ -159,7 +159,7 @@ defmodule Kernel.Typespec do
|
||||
@doc """
|
||||
Defines a `type`, `typep` or `opaque` by receiving Erlang's typespec.
|
||||
"""
|
||||
def define_type(module, kind, { name, _, vars } = type) when kind in [:type, :typep, :opaque] do
|
||||
def define_type(caller, kind, { name, _, vars } = type) when kind in [:type, :typep, :opaque] do
|
||||
{ kind, export } =
|
||||
case kind do
|
||||
:type -> { :type, true }
|
||||
@@ -167,12 +167,35 @@ defmodule Kernel.Typespec do
|
||||
:opaque -> { :opaque, true }
|
||||
end
|
||||
|
||||
module = caller.module
|
||||
arity = length(vars)
|
||||
|
||||
Module.compile_typespec module, kind, type
|
||||
if export, do:
|
||||
Module.compile_typespec(module, :export_type, [{ name, length(vars) }])
|
||||
|
||||
if export do
|
||||
Module.compile_typespec(module, :export_type, [{ name, arity }])
|
||||
end
|
||||
|
||||
define_doc(caller, kind, name, arity, export)
|
||||
type
|
||||
end
|
||||
|
||||
defp define_doc(caller, kind, name, arity, export) do
|
||||
module = caller.module
|
||||
doc = Module.get_attribute(module, :typedoc)
|
||||
|
||||
if doc do
|
||||
if export do
|
||||
Module.add_doc(module, caller.line, kind, { name, arity }, doc)
|
||||
else
|
||||
:elixir_errors.warn "#{caller.file}:#{caller.line}: type #{name}/#{arity} is private, " <>
|
||||
"@typedoc's are always discarded for private types\n"
|
||||
end
|
||||
end
|
||||
|
||||
Module.delete_attribute(module, :typedoc)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Defines a `spec` by receiving Erlang's typespec.
|
||||
"""
|
||||
@@ -193,7 +216,7 @@ defmodule Kernel.Typespec do
|
||||
for modules being compiled.
|
||||
"""
|
||||
def defines_type?(module, name, arity) do
|
||||
finder = match?({ ^name, _, vars } when length(vars) == arity, &1)
|
||||
finder = &match?({ ^name, _, vars } when length(vars) == arity, &1)
|
||||
:lists.any(finder, Module.get_attribute(module, :type)) or
|
||||
:lists.any(finder, Module.get_attribute(module, :opaque))
|
||||
end
|
||||
@@ -204,7 +227,7 @@ defmodule Kernel.Typespec do
|
||||
"""
|
||||
def defines_spec?(module, name, arity) do
|
||||
tuple = { name, arity }
|
||||
:lists.any(match?(^tuple, &1), Module.get_attribute(module, :spec))
|
||||
:lists.any(&match?(^tuple, &1), Module.get_attribute(module, :spec))
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -213,7 +236,7 @@ defmodule Kernel.Typespec do
|
||||
"""
|
||||
def defines_callback?(module, name, arity) do
|
||||
tuple = { name, arity }
|
||||
:lists.any(match?(^tuple, &1), Module.get_attribute(module, :callback))
|
||||
:lists.any(&match?(^tuple, &1), Module.get_attribute(module, :callback))
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -255,6 +278,26 @@ defmodule Kernel.Typespec do
|
||||
quote do: unquote(name)(unquote_splicing(args)) :: unquote(typespec_to_ast(type))
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns all type docs available from the module's beam code.
|
||||
|
||||
It is returned as a list of tuples where the first element is the pair of type
|
||||
name and arity and the second element is the documentation.
|
||||
|
||||
The module has to have a corresponding beam file on the disk which can be
|
||||
located by the runtime system.
|
||||
"""
|
||||
@spec beam_typedocs(module | binary) :: [tuple] | nil
|
||||
def beam_typedocs(module) when is_atom(module) or is_binary(module) do
|
||||
case abstract_code(module) do
|
||||
{ :ok, abstract_code } ->
|
||||
type_docs = lc { :attribute, _, :typedoc, tup } inlist abstract_code, do: tup
|
||||
List.flatten(type_docs)
|
||||
_ ->
|
||||
nil
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns all types available from the module's beam code.
|
||||
|
||||
@@ -264,7 +307,8 @@ defmodule Kernel.Typespec do
|
||||
The module has to have a corresponding beam file on the disk which can be
|
||||
located by the runtime system.
|
||||
"""
|
||||
def beam_types(module) do
|
||||
@spec beam_types(module | binary) :: [tuple] | nil
|
||||
def beam_types(module) when is_atom(module) or is_binary(module) do
|
||||
case abstract_code(module) do
|
||||
{ :ok, abstract_code } ->
|
||||
exported_types = lc { :attribute, _, :export_type, types } inlist abstract_code, do: types
|
||||
@@ -278,7 +322,7 @@ defmodule Kernel.Typespec do
|
||||
end
|
||||
end
|
||||
_ ->
|
||||
[]
|
||||
nil
|
||||
end
|
||||
end
|
||||
|
||||
@@ -291,7 +335,8 @@ defmodule Kernel.Typespec do
|
||||
The module has to have a corresponding beam file on the disk which can be
|
||||
located by the runtime system.
|
||||
"""
|
||||
def beam_specs(module) do
|
||||
@spec beam_specs(module | binary) :: [tuple] | nil
|
||||
def beam_specs(module) when is_atom(module) or is_binary(module) do
|
||||
from_abstract_code(module, :spec)
|
||||
end
|
||||
|
||||
@@ -301,10 +346,11 @@ defmodule Kernel.Typespec do
|
||||
It is returned as a list of tuples where the first
|
||||
element is spec name and arity and the second is the spec.
|
||||
|
||||
The module has to have a corresponding beam file on the disk which can be
|
||||
located by the runtime system.
|
||||
The module has to have a corresponding beam file on the disk
|
||||
which can be located by the runtime system.
|
||||
"""
|
||||
def beam_callbacks(module) do
|
||||
@spec beam_callbacks(module | binary) :: [tuple] | nil
|
||||
def beam_callbacks(module) when is_atom(module) or is_binary(module) do
|
||||
from_abstract_code(module, :callback)
|
||||
end
|
||||
|
||||
@@ -312,8 +358,8 @@ defmodule Kernel.Typespec do
|
||||
case abstract_code(module) do
|
||||
{ :ok, abstract_code } ->
|
||||
lc { :attribute, _, abs_kind, value } inlist abstract_code, kind == abs_kind, do: value
|
||||
_ ->
|
||||
[]
|
||||
:error ->
|
||||
nil
|
||||
end
|
||||
end
|
||||
|
||||
@@ -322,7 +368,7 @@ defmodule Kernel.Typespec do
|
||||
{:ok, { _, [{ :abstract_code, { _raw_abstract_v1, abstract_code } }] } } ->
|
||||
{ :ok, abstract_code }
|
||||
_ ->
|
||||
[]
|
||||
:error
|
||||
end
|
||||
end
|
||||
|
||||
@@ -368,7 +414,7 @@ defmodule Kernel.Typespec do
|
||||
vars = lc { :var, _, _ } = var inlist args, do: var
|
||||
type = { name, spec, vars }
|
||||
|
||||
define_type(caller.module, kind, type)
|
||||
define_type(caller, kind, type)
|
||||
end
|
||||
|
||||
@doc false
|
||||
|
||||
+146
-22
@@ -4,13 +4,23 @@ defmodule Keyword do
|
||||
of the tuple is an atom and the second element can be
|
||||
any value.
|
||||
|
||||
A keyword may have duplicated keys, so it is not strictly
|
||||
A keyword may have duplicated keys so it is not strictly
|
||||
a dictionary. However most of the functions in this module
|
||||
allows it to behave exactly as a dictionary. For example,
|
||||
`Keyword.get` will get the first entry matching the given
|
||||
key, regardless if duplicated entries exist. Similarly,
|
||||
`Keyword.put` and `Keyword.delete` ensure all duplicated
|
||||
entries for a given key are removed when invoked.
|
||||
behaves exactly as a dictionary and mimic the API defined
|
||||
by the `Dict` behaviour.
|
||||
|
||||
For example, `Keyword.get` will get the first entry matching
|
||||
the given key, regardless if duplicated entries exist.
|
||||
Similarly, `Keyword.put` and `Keyword.delete` ensure all
|
||||
duplicated entries for a given key are removed when invoked.
|
||||
A handful of functions exist to handle duplicated keys, in
|
||||
particular, `from_enum` allows creating a new keywords without
|
||||
removing duplicated keys, `get_values` returns all values for
|
||||
a given key and `delete_first` deletes just one of the existing
|
||||
entries.
|
||||
|
||||
Since a keyword list is simply a list, all the operations defined
|
||||
in `Enum` and `List` can also be applied.
|
||||
"""
|
||||
|
||||
@type key :: atom
|
||||
@@ -23,16 +33,12 @@ defmodule Keyword do
|
||||
duplicated entries.
|
||||
"""
|
||||
@spec from_enum(Enum.t) :: t
|
||||
def from_enum(enum) when is_list(enum) do
|
||||
enum
|
||||
end
|
||||
|
||||
def from_enum(enum) do
|
||||
Enum.map(enum, fn(x) -> x end)
|
||||
Enum.to_list(enum)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Checks if the given argument is a keywords list or not
|
||||
Checks if the given argument is a keywords list or not.
|
||||
"""
|
||||
@spec keyword?(term) :: boolean
|
||||
def keyword?([{ key, _value } | rest]) when is_atom(key) do
|
||||
@@ -356,22 +362,16 @@ defmodule Keyword do
|
||||
:lists.keymember(key, 1, keywords)
|
||||
end
|
||||
|
||||
@doc false
|
||||
def update(dict, key, fun) when is_function(fun, 1) do
|
||||
IO.write "Keyword.update/3 is deprecated, please use Keyword.update!/3 instead\n#{Exception.format_stacktrace}"
|
||||
update!(dict, key, fun)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Updates the `key` with the given function. If the `key` does
|
||||
not exist, raises `KeyError`.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Keyword.update!([a: 1], :a, &1 * 2)
|
||||
iex> Keyword.update!([a: 1], :a, &(&1 * 2))
|
||||
[a: 2]
|
||||
|
||||
iex> Keyword.update!([a: 1], :b, &1 * 2)
|
||||
iex> Keyword.update!([a: 1], :b, &(&1 * 2))
|
||||
** (KeyError) key not found: :b
|
||||
|
||||
"""
|
||||
@@ -394,10 +394,10 @@ defmodule Keyword do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Keyword.update([a: 1], :a, 13, &1 * 2)
|
||||
iex> Keyword.update([a: 1], :a, 13, &(&1 * 2))
|
||||
[a: 2]
|
||||
|
||||
iex> Keyword.update([a: 1], :b, 11, &1 * 2)
|
||||
iex> Keyword.update([a: 1], :b, 11, &(&1 * 2))
|
||||
[a: 1, b: 11]
|
||||
|
||||
"""
|
||||
@@ -413,4 +413,128 @@ defmodule Keyword do
|
||||
def update([], key, initial, _fun) when is_atom(key) do
|
||||
[{key, initial}]
|
||||
end
|
||||
|
||||
@doc """
|
||||
Splits the given keywords in two given the given keys.
|
||||
Duplicated keys are preserved in the split keyword list.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> d = [a: 1, b: 2, c: 3, d: 4]
|
||||
iex> Keyword.split(d, [:a, :c, :e])
|
||||
{ [a: 1, c: 3], [b: 2, d: 4] }
|
||||
|
||||
iex> d = [a: 1, b: 2, c: 3, d: 4, a: 5]
|
||||
iex> Keyword.split(d, [:a, :c, :e])
|
||||
{ [a: 1, c: 3, a: 5], [b: 2, d: 4] }
|
||||
|
||||
"""
|
||||
def split(dict, keys) do
|
||||
acc = { [], [] }
|
||||
|
||||
{ take, drop } = Enum.reduce dict, acc, fn({ k, v }, { take, drop }) ->
|
||||
case :lists.member(k, keys) do
|
||||
true -> { [{k, v}|take], drop }
|
||||
false -> { take, [{k, v}|drop] }
|
||||
end
|
||||
end
|
||||
|
||||
{ Enum.reverse(take), Enum.reverse(drop) }
|
||||
end
|
||||
|
||||
@doc """
|
||||
Takes the given keys from the dict.
|
||||
Duplicated keys are preserved in the new keyword list.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> d = [a: 1, b: 2, c: 3, d: 4]
|
||||
iex> Keyword.take(d, [:a, :c, :e])
|
||||
[a: 1, c: 3]
|
||||
|
||||
iex> d = [a: 1, b: 2, c: 3, d: 4, a: 5]
|
||||
iex> Keyword.take(d, [:a, :c, :e])
|
||||
[a: 1, c: 3, a: 5]
|
||||
|
||||
"""
|
||||
def take(dict, keys) do
|
||||
lc { k, _ } = tuple inlist dict, :lists.member(k, keys), do: tuple
|
||||
end
|
||||
|
||||
@doc """
|
||||
Drops the given keys from the dict.
|
||||
Duplicated keys are preserved in the new keyword list.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> d = [a: 1, b: 2, c: 3, d: 4]
|
||||
iex> Keyword.drop(d, [:b, :d])
|
||||
[a: 1, c: 3]
|
||||
|
||||
iex> d = [a: 1, b: 2, c: 3, d: 4, a: 5]
|
||||
iex> Keyword.drop(d, [:b, :d])
|
||||
[a: 1, c: 3, a: 5]
|
||||
|
||||
"""
|
||||
def drop(dict, keys) do
|
||||
lc { k, _ } = tuple inlist dict, not :lists.member(k, keys), do: tuple
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns the first value associated with `key` in the keyword
|
||||
list as well as the keyword list without `key`.
|
||||
|
||||
All duplicated entries are removed. See `pop_first/3` for
|
||||
removing only the first entry.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Keyword.pop [a: 1], :a
|
||||
{1,[]}
|
||||
|
||||
iex> Keyword.pop [a: 1], :b
|
||||
{nil,[a: 1]}
|
||||
|
||||
iex> Keyword.pop [a: 1], :b, 3
|
||||
{3,[a: 1]}
|
||||
|
||||
iex> Keyword.pop [a: 1], :b, 3
|
||||
{3,[a: 1]}
|
||||
|
||||
iex> Keyword.pop [a: 1, a: 2], :a
|
||||
{1,[]}
|
||||
|
||||
"""
|
||||
def pop(dict, key, default // nil) do
|
||||
{ get(dict, key, default), delete(dict, key) }
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns the first value associated with `key` in the keyword
|
||||
list as well as the keyword list without that particular ocurrence
|
||||
of `key`.
|
||||
|
||||
Duplicated entries are not removed.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Keyword.pop_first [a: 1], :a
|
||||
{1,[]}
|
||||
|
||||
iex> Keyword.pop_first [a: 1], :b
|
||||
{nil,[a: 1]}
|
||||
|
||||
iex> Keyword.pop_first [a: 1], :b, 3
|
||||
{3,[a: 1]}
|
||||
|
||||
iex> Keyword.pop_first [a: 1], :b, 3
|
||||
{3,[a: 1]}
|
||||
|
||||
iex> Keyword.pop_first [a: 1, a: 2], :a
|
||||
{1,[a: 2]}
|
||||
|
||||
"""
|
||||
def pop_first(dict, key, default // nil) do
|
||||
{ get(dict, key, default), delete_first(dict, key) }
|
||||
end
|
||||
end
|
||||
|
||||
+1
-13
@@ -12,18 +12,6 @@ defmodule List do
|
||||
|
||||
@compile :inline_list_funcs
|
||||
|
||||
@doc false
|
||||
def concat(list) when is_list(list) do
|
||||
IO.write "List.concat/1 is deprecated, please use Enum.concat/1 instead\n#{Exception.format_stacktrace}"
|
||||
:lists.append(list)
|
||||
end
|
||||
|
||||
@doc false
|
||||
def concat(list, elements) when is_list(list) and is_list(elements) do
|
||||
IO.write "List.concat/2 is deprecated, please use Enum.concat/2 instead\n#{Exception.format_stacktrace}"
|
||||
list ++ elements
|
||||
end
|
||||
|
||||
@doc """
|
||||
Deletes the given item from the list. Returns a list without the item.
|
||||
If the item occurs more than once in the list, just the first occurrence
|
||||
@@ -307,7 +295,7 @@ defmodule List do
|
||||
|
||||
"""
|
||||
def unzip(list) when is_list(list) do
|
||||
:lists.map tuple_to_list(&1), zip(list)
|
||||
:lists.map &tuple_to_list/1, zip(list)
|
||||
end
|
||||
|
||||
@doc """
|
||||
|
||||
@@ -9,8 +9,6 @@ defprotocol List.Chars do
|
||||
by Kernel invokes this protocol.
|
||||
"""
|
||||
|
||||
@only [BitString, List, Atom, Number, Record]
|
||||
|
||||
def to_char_list(thing)
|
||||
end
|
||||
|
||||
@@ -38,13 +36,15 @@ defimpl List.Chars, for: List do
|
||||
def to_char_list(list), do: list
|
||||
end
|
||||
|
||||
defimpl List.Chars, for: Number do
|
||||
@digits 20
|
||||
@limit :math.pow(10, @digits)
|
||||
|
||||
def to_char_list(thing) when is_integer(thing) do
|
||||
defimpl List.Chars, for: Integer do
|
||||
def to_char_list(thing) do
|
||||
integer_to_list(thing)
|
||||
end
|
||||
end
|
||||
|
||||
defimpl List.Chars, for: Float do
|
||||
@digits 20
|
||||
@limit :math.pow(10, @digits)
|
||||
|
||||
def to_char_list(thing) when thing > @limit do
|
||||
float_to_list(thing, scientific: @digits)
|
||||
|
||||
+32
-37
@@ -2,6 +2,10 @@ defmodule ListDict do
|
||||
@moduledoc """
|
||||
A Dict implementation that works on lists of two-items tuples.
|
||||
|
||||
This dictionary is only recommended for keeping a small amount
|
||||
of values. Other dict alternatives are more viable for keeping
|
||||
any other amount than a handful.
|
||||
|
||||
For more information about the functions and their APIs, please
|
||||
consult the `Dict` module.
|
||||
"""
|
||||
@@ -38,28 +42,25 @@ defmodule ListDict do
|
||||
length(dict)
|
||||
end
|
||||
|
||||
def has_key?(dict, key) do
|
||||
:lists.keymember(key, 1, dict)
|
||||
end
|
||||
def has_key?(dict, key)
|
||||
def has_key?([{ key, _ }|_], key), do: true
|
||||
def has_key?([{ _, _ }|t], key), do: has_key?(t, key)
|
||||
def has_key?([], _key), do: false
|
||||
|
||||
def get(dict, key, default // nil) do
|
||||
case :lists.keyfind(key, 1, dict) do
|
||||
{ ^key, value } -> value
|
||||
false -> default
|
||||
end
|
||||
end
|
||||
def get(dict, key, default // nil)
|
||||
def get([{ key, value }|_], key, _default), do: value
|
||||
def get([{ _, _ }|t], key, default), do: get(t, key, default)
|
||||
def get([], _key, default), do: default
|
||||
|
||||
def fetch(dict, key) do
|
||||
case :lists.keyfind(key, 1, dict) do
|
||||
{ ^key, value } -> { :ok, value }
|
||||
false -> :error
|
||||
end
|
||||
end
|
||||
def fetch(dict, key)
|
||||
def fetch([{ key, value }|_], key), do: { :ok, value }
|
||||
def fetch([{ _, _ }|t], key), do: fetch(t, key)
|
||||
def fetch([], _key), do: :error
|
||||
|
||||
def fetch!(dict, key) do
|
||||
case :lists.keyfind(key, 1, dict) do
|
||||
{ ^key, value } -> value
|
||||
false -> raise(KeyError, key: key)
|
||||
case fetch(dict, key) do
|
||||
{ :ok, value } -> value
|
||||
:error -> raise(KeyError, key: key)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -72,26 +73,26 @@ defmodule ListDict do
|
||||
end
|
||||
|
||||
def put_new(dict, key, val) do
|
||||
case :lists.keyfind(key, 1, dict) do
|
||||
{ ^key, _ } -> dict
|
||||
case has_key?(dict, key) do
|
||||
true -> dict
|
||||
false -> [{key, val}|dict]
|
||||
end
|
||||
end
|
||||
|
||||
def delete(dict, key) do
|
||||
lc { k, _ } = tuple inlist dict, key != k, do: tuple
|
||||
end
|
||||
def delete(dict, key)
|
||||
def delete([{ key, _ }|t], key), do: t
|
||||
def delete([{ _, _ } = h|t], key), do: [h|delete(t, key)]
|
||||
def delete([], _key), do: []
|
||||
|
||||
def merge(dict, enum, callback // fn(_k, _v1, v2) -> v2 end)
|
||||
|
||||
def merge(dict1, dict2, fun) do
|
||||
Enum.reduce dict2, dict1, fn { k, v2 }, acc ->
|
||||
update(acc, k, v2, fn(v1) -> fun.(k, v1, v2) end)
|
||||
def merge(dict, enum, callback // fn(_k, _v1, v2) -> v2 end) do
|
||||
Enum.reduce enum, dict, fn { k, v2 }, acc ->
|
||||
update(acc, k, v2, fn(v1) -> callback.(k, v1, v2) end)
|
||||
end
|
||||
end
|
||||
|
||||
def split(dict, keys) do
|
||||
acc = { new(), new() }
|
||||
acc = { [], [] }
|
||||
|
||||
{take, drop} = Enum.reduce dict, acc, fn({ k, v }, { take, drop }) ->
|
||||
if :lists.member(k, keys) do
|
||||
{ [{k, v}|take], drop }
|
||||
@@ -99,7 +100,7 @@ defmodule ListDict do
|
||||
{ take, [{k, v}|drop] }
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
{Enum.reverse(take), Enum.reverse(drop)}
|
||||
end
|
||||
|
||||
@@ -111,12 +112,6 @@ defmodule ListDict do
|
||||
lc { k, _ } = tuple inlist dict, not :lists.member(k, keys), do: tuple
|
||||
end
|
||||
|
||||
@doc false
|
||||
def update(dict, key, fun) when is_function(fun, 1) do
|
||||
IO.write "ListDict.update/3 is deprecated, please use ListDict.update!/3 instead\n#{Exception.format_stacktrace}"
|
||||
update!(dict, key, fun)
|
||||
end
|
||||
|
||||
def update!([{key, value}|dict], key, fun) do
|
||||
[{key, fun.(value)}|delete(dict, key)]
|
||||
end
|
||||
@@ -144,7 +139,7 @@ defmodule ListDict do
|
||||
def empty(_dict), do: []
|
||||
|
||||
def equal?(dict, other) do
|
||||
:lists.keysort(1, dict) == :lists.keysort(1, other)
|
||||
:lists.keysort(1, dict) === :lists.keysort(1, other)
|
||||
end
|
||||
|
||||
def to_list(dict), do: dict
|
||||
|
||||
+64
-83
@@ -5,6 +5,7 @@ defmodule Macro do
|
||||
This module provides conveniences for working with macros.
|
||||
"""
|
||||
|
||||
@typedoc "Abstract Syntax Tree (AST) node"
|
||||
@type t :: { t, t } | { t, Keyword.t, t } | atom | number | binary | list
|
||||
|
||||
@doc false
|
||||
@@ -23,8 +24,7 @@ defmodule Macro do
|
||||
[:!, :@, :^, :not, :+, :-, :~~~, :&]
|
||||
end
|
||||
|
||||
@typep precedence :: integer # Higher means binds stronger
|
||||
@spec binary_op_props(atom) :: { :left|:right, precedence }
|
||||
@spec binary_op_props(atom) :: { :left | :right, precedence :: integer }
|
||||
defp binary_op_props(o) do
|
||||
case o do
|
||||
::: -> {:right, 30}
|
||||
@@ -42,13 +42,47 @@ defmodule Macro do
|
||||
o when o in [:+, :-] -> {:left, 210}
|
||||
o when o in [:*, :/] -> {:left, 220}
|
||||
o when o in [:<>] -> {:right, 230}
|
||||
o when o in [:++, :--, :**] -> {:right, 240}
|
||||
:^^^ -> {:left, 250}
|
||||
:. -> {:left, 310}
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Receives an expresion representing a possible definition
|
||||
Breaks a pipeline expression into a list. Raises if
|
||||
the pipeline is ill-formed.
|
||||
"""
|
||||
@spec unpipe(Macro.t) :: [Macro.t]
|
||||
def unpipe({ :|> , _, [left, right] }) do
|
||||
[left|unpipe(right)]
|
||||
end
|
||||
|
||||
def unpipe(other) do
|
||||
[other]
|
||||
end
|
||||
|
||||
@doc """
|
||||
Pipes the given `expr` in to the `call_expr` as the
|
||||
argument in the given `position`.
|
||||
"""
|
||||
@spec pipe(Macro.t, Macro.t, integer) :: Macro.t | no_return
|
||||
def pipe(expr, call_args, integer // 0)
|
||||
|
||||
def pipe(expr, { call, line, atom }, integer) when is_atom(atom) do
|
||||
{ call, line, List.insert_at([], integer, expr) }
|
||||
end
|
||||
|
||||
def pipe(expr, { call, line, args }, integer) when is_list(args) do
|
||||
{ call, line, List.insert_at(args, integer, expr) }
|
||||
end
|
||||
|
||||
def pipe(expr, call_args, _integer) do
|
||||
raise ArgumentError,
|
||||
message: "cannot pipe #{to_string expr} into #{to_string call_args}"
|
||||
end
|
||||
|
||||
@doc """
|
||||
Receives an expression representing a possible definition
|
||||
and extracts its arguments. It returns a tuple with the
|
||||
function name and the arguments list or `:error` if not
|
||||
a valid call syntax.
|
||||
@@ -89,22 +123,10 @@ defmodule Macro do
|
||||
1
|
||||
|
||||
"""
|
||||
@spec escape(Macro.t) :: Macro.t
|
||||
@spec escape(Macro.t, Keyword.t) :: Macro.t
|
||||
@spec escape(term) :: Macro.t
|
||||
@spec escape(term, Keyword.t) :: Macro.t
|
||||
def escape(expr, opts // []) do
|
||||
:elixir_quote.escape(expr, Keyword.get(opts, :unquote, false)) |> elem(0)
|
||||
end
|
||||
|
||||
@doc false
|
||||
def unescape_binary(chars) do
|
||||
IO.write "Macro.unescape_binary/1 is deprecated, please use Macro.unescape_string/1 instead\n#{Exception.format_stacktrace}"
|
||||
unescape_string(chars)
|
||||
end
|
||||
|
||||
@doc false
|
||||
def unescape_binary(chars, map) do
|
||||
IO.write "Macro.unescape_binary/2 is deprecated, please use Macro.unescape_string/2 instead\n#{Exception.format_stacktrace}"
|
||||
unescape_string(chars, map)
|
||||
elem(:elixir_quote.escape(expr, Keyword.get(opts, :unquote, false)), 0)
|
||||
end
|
||||
|
||||
@doc %S"""
|
||||
@@ -174,7 +196,7 @@ defmodule Macro do
|
||||
|
||||
Using the unescape_map defined above is easy:
|
||||
|
||||
Macro.unescape_string "example\\n", unescape_map(&1)
|
||||
Macro.unescape_string "example\\n", &unescape_map(&1)
|
||||
|
||||
"""
|
||||
@spec unescape_string(String.t, (non_neg_integer -> non_neg_integer | false)) :: String.t
|
||||
@@ -221,7 +243,7 @@ defmodule Macro do
|
||||
|
||||
# Variables
|
||||
def to_string({ var, _, atom } = ast, fun) when is_atom(atom) do
|
||||
fun.(ast, atom_to_binary(var, :utf8))
|
||||
fun.(ast, atom_to_binary(var))
|
||||
end
|
||||
|
||||
# Aliases
|
||||
@@ -252,22 +274,17 @@ defmodule Macro do
|
||||
fun.(ast, "{" <> Enum.map_join(args, ", ", &to_string(&1, fun)) <> "}")
|
||||
end
|
||||
|
||||
# List containers
|
||||
def to_string({ :[], _, args } = ast, fun) do
|
||||
fun.(ast, "[" <> Enum.map_join(args, ", ", &to_string(&1, fun)) <> "]")
|
||||
end
|
||||
|
||||
# Fn keyword
|
||||
def to_string({ :fn, _, [[do: { :->, _, [{_, _, tuple}] } = arrow]] } = ast, fun)
|
||||
def to_string({ :fn, _, [{ :->, _, [{_, _, tuple}] } = arrow] } = ast, fun)
|
||||
when not is_tuple(tuple) or elem(tuple, 0) != :__block__ do
|
||||
fun.(ast, "fn " <> arrow_to_string(arrow, fun) <> " end")
|
||||
end
|
||||
|
||||
def to_string({ :fn, _, [[do: { :->, _, [_] } = block]] } = ast, fun) do
|
||||
def to_string({ :fn, _, [{ :->, _, [_] } = block] } = ast, fun) do
|
||||
fun.(ast, "fn " <> block_to_string(block, fun) <> "\nend")
|
||||
end
|
||||
|
||||
def to_string({ :fn, _, [[do: block]] } = ast, fun) do
|
||||
def to_string({ :fn, _, [block] } = ast, fun) do
|
||||
block = adjust_new_lines block_to_string(block, fun), "\n "
|
||||
fun.(ast, "fn\n " <> block <> "\nend")
|
||||
end
|
||||
@@ -294,16 +311,12 @@ defmodule Macro do
|
||||
end
|
||||
|
||||
def to_string({ op, _, [arg] } = ast, fun) when op in unary_ops do
|
||||
fun.(ast, atom_to_binary(op, :utf8) <> to_string(arg, fun))
|
||||
fun.(ast, atom_to_binary(op) <> to_string(arg, fun))
|
||||
end
|
||||
|
||||
# Access
|
||||
def to_string({ { :., _, [Kernel, :access] }, _, [left, right] } = ast, fun) do
|
||||
fun.(ast, if right != [] and Keyword.keyword?(right) do
|
||||
to_string(left, fun) <> to_string(right, fun)
|
||||
else
|
||||
to_string(left, fun) <> "[" <> to_string(right, fun) <> "]"
|
||||
end)
|
||||
fun.(ast, to_string(left, fun) <> to_string(right, fun))
|
||||
end
|
||||
|
||||
# All other calls
|
||||
@@ -321,11 +334,11 @@ defmodule Macro do
|
||||
end
|
||||
|
||||
# Lists
|
||||
def to_string(list = ast, fun) when is_list(list) do
|
||||
def to_string(list, fun) when is_list(list) do
|
||||
if Keyword.keyword?(list) do
|
||||
fun.(ast, "[" <> kw_list_to_string(list, fun) <> "]")
|
||||
fun.(list, "[" <> kw_list_to_string(list, fun) <> "]")
|
||||
else
|
||||
to_string({ :[], [], list }, fun)
|
||||
fun.(list, "[" <> Enum.map_join(list, ", ", &to_string(&1, fun)) <> "]")
|
||||
end
|
||||
end
|
||||
|
||||
@@ -336,14 +349,14 @@ defmodule Macro do
|
||||
defmacrop kw_keywords, do: [:do, :catch, :rescue, :after, :else]
|
||||
|
||||
defp is_kw_blocks?([_|_] = kw) do
|
||||
Enum.all?(kw, match?({x, _} when x in kw_keywords, &1))
|
||||
Enum.all?(kw, &match?({x, _} when x in kw_keywords, &1))
|
||||
end
|
||||
defp is_kw_blocks?(_), do: false
|
||||
|
||||
defp module_to_string(atom, _fun) when is_atom(atom), do: inspect(atom, raw: true)
|
||||
defp module_to_string(other, fun), do: call_to_string(other, fun)
|
||||
|
||||
defp call_to_string(atom, _fun) when is_atom(atom), do: atom_to_binary(atom, :utf8)
|
||||
defp call_to_string(atom, _fun) when is_atom(atom), do: atom_to_binary(atom)
|
||||
defp call_to_string({ :., _, [arg] }, fun), do: module_to_string(arg, fun) <> "."
|
||||
defp call_to_string({ :., _, [left, right] }, fun), do: module_to_string(left, fun) <> "." <> call_to_string(right, fun)
|
||||
defp call_to_string(other, fun), do: to_string(other, fun)
|
||||
@@ -375,7 +388,7 @@ defmodule Macro do
|
||||
|
||||
defp kw_block_to_string(key, value, fun) do
|
||||
block = adjust_new_lines block_to_string(value, fun), "\n "
|
||||
atom_to_binary(key, :utf8) <> "\n " <> block <> "\n"
|
||||
atom_to_binary(key) <> "\n " <> block <> "\n"
|
||||
end
|
||||
|
||||
defp block_to_string({ :->, _, exprs }, fun) do
|
||||
@@ -518,21 +531,21 @@ defmodule Macro do
|
||||
|
||||
"""
|
||||
def expand_once(ast, env) do
|
||||
expand_once(ast, env, nil) |> elem(0)
|
||||
elem(expand_once(ast, env, nil), 0)
|
||||
end
|
||||
|
||||
defp expand_once({ :__aliases__, _, _ } = original, env, cache) do
|
||||
case :elixir_aliases.expand(original, env.aliases, env.macro_aliases) do
|
||||
case :elixir_aliases.expand(original, env.aliases, env.macro_aliases, env.lexical_tracker) do
|
||||
receiver when is_atom(receiver) ->
|
||||
:elixir_tracker.record_alias(receiver, env.module)
|
||||
:elixir_lexical.record_remote(receiver, env.lexical_tracker)
|
||||
{ receiver, true, cache }
|
||||
aliases ->
|
||||
aliases = lc alias inlist aliases, do: (expand_once(alias, env, cache) |> elem(0))
|
||||
aliases = lc alias inlist aliases, do: elem(expand_once(alias, env, cache), 0)
|
||||
|
||||
case :lists.all(is_atom(&1), aliases) do
|
||||
case :lists.all(&is_atom/1, aliases) do
|
||||
true ->
|
||||
receiver = :elixir_aliases.concat(aliases)
|
||||
:elixir_tracker.record_alias(receiver, env.module)
|
||||
:elixir_lexical.record_remote(receiver, env.lexical_tracker)
|
||||
{ receiver, true, cache }
|
||||
false -> { original, false, cache }
|
||||
end
|
||||
@@ -611,7 +624,7 @@ defmodule Macro do
|
||||
defp to_erl_env(_env, cache), do: cache
|
||||
|
||||
defp is_partial?(args) do
|
||||
:lists.any(match?({ :&, _, [_] }, &1), args)
|
||||
:lists.any(&match?({ :&, _, [_] }, &1), args)
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -620,7 +633,7 @@ defmodule Macro do
|
||||
expansion works and `expand_all/2` for recursive expansion.
|
||||
"""
|
||||
def expand(tree, env) do
|
||||
expand(tree, env, nil) |> elem(0)
|
||||
elem(expand(tree, env, nil), 0)
|
||||
end
|
||||
|
||||
@doc false # Used internally by Elixir
|
||||
@@ -636,38 +649,6 @@ defmodule Macro do
|
||||
{ tree, cache }
|
||||
end
|
||||
|
||||
@doc %S"""
|
||||
Receives a AST node and expands it until it no longer represents
|
||||
a macro. Then it expands all of its children recursively.
|
||||
|
||||
The documentation for `expand_once/2` goes into more detail
|
||||
on how expansion works. Keep in mind that `expand_all/2` is
|
||||
naive as it doesn't mutate the environment. Take this example:
|
||||
|
||||
quoted = quote do: __MODULE__
|
||||
Macro.to_string Macro.expand_all(quoted, __ENV__)
|
||||
#=> "nil"
|
||||
|
||||
The example above, works as expected. Outside of a module,
|
||||
`__MODULE__` returns `nil`. Now consider this variation:
|
||||
|
||||
quoted = quote do
|
||||
defmodule Foo, do: __MODULE__
|
||||
end
|
||||
Macro.to_string Macro.expand_all(quoted, __ENV__)
|
||||
#=> "defmodule(Foo) do\n nil\nend"
|
||||
|
||||
One would expect `__MODULE__` to be expanded to `Foo`
|
||||
but that is not the case since all the expansion is done
|
||||
based on the environment `__ENV__`. This behaviour is on
|
||||
purpose, as the proper expansion would require passing through
|
||||
the whole Elixir compiler by actually executing the code, which
|
||||
is beyond the scope of this function.
|
||||
"""
|
||||
def expand_all(tree, env) do
|
||||
expand_all(tree, env, nil) |> elem(0)
|
||||
end
|
||||
|
||||
@doc false # Used internally by Elixir
|
||||
def expand_all(tree, env, cache) do
|
||||
expand_all_until(expand(tree, env, cache), env)
|
||||
@@ -686,7 +667,7 @@ defmodule Macro do
|
||||
end
|
||||
|
||||
defp expand_all_until({ list, cache }, env) when is_list(list) do
|
||||
:lists.mapfoldl(expand_all(&1, env, &2), cache, list)
|
||||
:lists.mapfoldl(&expand_all(&1, env, &2), cache, list)
|
||||
end
|
||||
|
||||
defp expand_all_until({ other, cache }, _env) do
|
||||
@@ -703,7 +684,7 @@ defmodule Macro do
|
||||
do_safe_term(terms) || :ok
|
||||
end
|
||||
|
||||
defp do_safe_term({ local, _, terms }) when local in [:{}, :[], :__aliases__] do
|
||||
defp do_safe_term({ local, _, terms }) when local in [:{}, :__aliases__] do
|
||||
do_safe_term(terms)
|
||||
end
|
||||
|
||||
@@ -712,7 +693,7 @@ defmodule Macro do
|
||||
end
|
||||
|
||||
defp do_safe_term({ left, right }), do: do_safe_term(left) || do_safe_term(right)
|
||||
defp do_safe_term(terms) when is_list(terms), do: Enum.find_value(terms, do_safe_term(&1))
|
||||
defp do_safe_term(terms) when is_list(terms), do: Enum.find_value(terms, &do_safe_term(&1))
|
||||
defp do_safe_term(terms) when is_tuple(terms), do: { :unsafe, terms }
|
||||
defp do_safe_term(_), do: nil
|
||||
end
|
||||
|
||||
@@ -34,14 +34,15 @@ defmodule Macro.Env do
|
||||
@type macros :: [{ module, [name_arity] }]
|
||||
@type context_modules :: [module]
|
||||
@type vars :: [{ atom, atom }]
|
||||
@type lexical_tracker :: pid
|
||||
|
||||
fields = [:module, :file, :line, :function, :aliases, :context, :requires,
|
||||
:functions, :macros, :context_modules, :macro_aliases, :vars]
|
||||
fields = [:module, :file, :line, :function, :aliases, :context, :requires, :functions,
|
||||
:macros, :context_modules, :macro_aliases, :vars, :lexical_tracker]
|
||||
|
||||
types = quote do: [module: module, file: file, line: line,
|
||||
function: name_arity, aliases: aliases, requires: requires,
|
||||
functions: functions, macros: macros, context_modules: context_modules,
|
||||
macro_aliases: aliases, vars: vars]
|
||||
macro_aliases: aliases, vars: vars, lexical_tracker: lexical_tracker]
|
||||
|
||||
Record.deffunctions(fields, __MODULE__)
|
||||
Record.deftypes(fields, types, __MODULE__)
|
||||
@@ -55,13 +56,13 @@ defmodule Macro.Env do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns wether the compilation environment is currently
|
||||
Returns whether the compilation environment is currently
|
||||
inside a guard.
|
||||
"""
|
||||
def in_guard?(record), do: context(record) == :guard
|
||||
|
||||
@doc """
|
||||
Returns wether the compilation environment is currently
|
||||
Returns whether the compilation environment is currently
|
||||
inside a match clause.
|
||||
"""
|
||||
def in_match?(record), do: context(record) == :match
|
||||
|
||||
+27
-18
@@ -29,7 +29,7 @@ defmodule Module do
|
||||
When just a module is provided, the function is assumed to be
|
||||
`__after_compile__/2`.
|
||||
|
||||
**Example**
|
||||
### Example
|
||||
|
||||
defmodule M do
|
||||
@after_compile __MODULE__
|
||||
@@ -44,18 +44,20 @@ defmodule Module do
|
||||
A hook that will be invoked before the module is compiled.
|
||||
|
||||
Accepts a module or a tuple `{ <module>, <function/macro atom> }`. The
|
||||
function/macro must take one argument: the module environment. If it\'s a
|
||||
function/macro must take one argument: the module environment. If it's a
|
||||
macro, its returned value will be injected at the end of the module definition
|
||||
before the compilation starts.
|
||||
|
||||
When just a module is provided, the function/macro is assumed to be
|
||||
`__before_compile__/1`.
|
||||
|
||||
**Example**
|
||||
Note: differently from `@after_compile`, the callback function/macro must
|
||||
be placed in a separate module (because when the callback is invoked,
|
||||
the current module does not yet exist).
|
||||
|
||||
defmodule M do
|
||||
@before_compile __MODULE__
|
||||
### Example
|
||||
|
||||
defmodule A do
|
||||
defmacro __before_compile__(_env) do
|
||||
quote do
|
||||
def hello, do: "world"
|
||||
@@ -63,11 +65,15 @@ defmodule Module do
|
||||
end
|
||||
end
|
||||
|
||||
defmodule B do
|
||||
@before_compile A
|
||||
end
|
||||
|
||||
* `@behaviour` (notice the british spelling)
|
||||
|
||||
Specify an OTP or user-defined behaviour.
|
||||
|
||||
**Example**
|
||||
### Example
|
||||
|
||||
defmodule M do
|
||||
@behaviour gen_event
|
||||
@@ -85,7 +91,7 @@ defmodule Module do
|
||||
See http://www.erlang.org/doc/man/compile.html for the list of supported
|
||||
options.
|
||||
|
||||
**Example**
|
||||
### Example
|
||||
|
||||
defmodule M do
|
||||
@compile { :inline, myfun: 1 }
|
||||
@@ -106,7 +112,7 @@ defmodule Module do
|
||||
|
||||
Can be invoked more than once.
|
||||
|
||||
**Example**
|
||||
### Example
|
||||
|
||||
defmodule M do
|
||||
@doc "Hello world"
|
||||
@@ -129,7 +135,7 @@ defmodule Module do
|
||||
|
||||
Accepts a string. Can be used more than once.
|
||||
|
||||
**Example**
|
||||
### Example
|
||||
|
||||
defmodule M do
|
||||
@doc "Hello world"
|
||||
@@ -147,7 +153,7 @@ defmodule Module do
|
||||
`@moduledoc false` will make the module invisible to the
|
||||
documentation extraction tools like ExDoc.
|
||||
|
||||
**Example**
|
||||
### Example
|
||||
|
||||
defmodule M do
|
||||
@moduledoc """
|
||||
@@ -183,7 +189,7 @@ defmodule Module do
|
||||
function (i.e. `env.function` returns the current function), the hook
|
||||
can only be a function, not a macro.
|
||||
|
||||
**Example**
|
||||
### Example
|
||||
|
||||
defmodule H do
|
||||
def on_def(_env, kind, name, args, guards, body) do
|
||||
@@ -216,7 +222,7 @@ defmodule Module do
|
||||
must have arity 0 (no arguments) and has to return `:ok`, otherwise the
|
||||
loading of the module will be aborted.
|
||||
|
||||
**Example**
|
||||
### Example
|
||||
|
||||
defmodule M do
|
||||
@on_load :load_check
|
||||
@@ -238,7 +244,7 @@ defmodule Module do
|
||||
|
||||
Specify the module version. Accepts any valid Elixir value.
|
||||
|
||||
**Example**
|
||||
### Example
|
||||
|
||||
defmodule M do
|
||||
@vsn "1.0"
|
||||
@@ -477,10 +483,11 @@ defmodule Module do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Attaches documentation to a given function. It expects
|
||||
the module the function belongs to, the line (a non negative
|
||||
Attaches documentation to a given function or type. It expects
|
||||
the module the function/type belongs to, the line (a non negative
|
||||
integer), the kind (def or defmacro), a tuple representing
|
||||
the function and its arity and the documentation, which should
|
||||
the function and its arity, the function signature (the signature
|
||||
should be omitted for types) and the documentation, which should
|
||||
be either a binary or a boolean.
|
||||
|
||||
## Examples
|
||||
@@ -491,12 +498,14 @@ defmodule Module do
|
||||
end
|
||||
|
||||
"""
|
||||
def add_doc(_module, _line, kind, _tuple, _signature, doc) when kind in [:defp, :defmacrop] do
|
||||
def add_doc(module, line, kind, tuple, signature // [], doc)
|
||||
|
||||
def add_doc(_module, _line, kind, _tuple, _signature, doc) when kind in [:defp, :defmacrop, :typep] do
|
||||
if doc, do: { :error, :private_doc }, else: :ok
|
||||
end
|
||||
|
||||
def add_doc(module, line, kind, tuple, signature, doc) when
|
||||
kind in [:def, :defmacro] and (is_binary(doc) or is_boolean(doc) or doc == nil) do
|
||||
kind in [:def, :defmacro, :type, :opaque] and (is_binary(doc) or is_boolean(doc) or doc == nil) do
|
||||
assert_not_compiled!(:add_doc, module)
|
||||
table = docs_table_for(module)
|
||||
|
||||
|
||||
@@ -5,15 +5,11 @@
|
||||
# ## Implementation
|
||||
#
|
||||
# The implementation uses the digraph module to track
|
||||
# all dependencies. The graph starts with three main
|
||||
# vertices:
|
||||
# all dependencies. The graph starts with one main vertice:
|
||||
#
|
||||
# * `:local` - points to local functions
|
||||
# * `:import` - points to imported modules
|
||||
# * `:warn` - points to imported modules that should be warned
|
||||
# * `:remote` - points to remote modules
|
||||
#
|
||||
# Besides those, we have can the following vertices:
|
||||
# We also have can the following vertices:
|
||||
#
|
||||
# * `Module` - a module that was invoked via an import or remotely
|
||||
# * `{ name, arity }` - a local function/arity pair
|
||||
@@ -24,9 +20,7 @@
|
||||
# as described below:
|
||||
#
|
||||
# * `Module`
|
||||
# * in neighbours: `:import`, `:remote`, `:warn`,
|
||||
# `{ :import, name, arity }` and `{ :remote, name arity }`
|
||||
# * out neighbours: `:warn`
|
||||
# * in neighbours: `{ :import, name, arity }` and `{ :remote, name arity }`
|
||||
#
|
||||
# * `{ name, arity }`
|
||||
# * in neighbours: `:local`, `{ name, arity }`
|
||||
@@ -92,15 +86,6 @@ defmodule Module.DispatchTracker do
|
||||
:digraph.out_neighbours(d, { name, arity }) |> only_tuples
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns all the modules which were imported.
|
||||
"""
|
||||
@spec imports(ref) :: [module]
|
||||
def imports(ref) do
|
||||
d = :gen_server.call(to_pid(ref), :digraph, @timeout)
|
||||
:digraph.out_neighbours(d, :import)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns all imported modules that had the given
|
||||
`{ name, arity }` invoked.
|
||||
@@ -111,24 +96,6 @@ defmodule Module.DispatchTracker do
|
||||
:digraph.out_neighbours(d, { :import, name, arity })
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns all the aliases defined in the given module.
|
||||
"""
|
||||
@spec aliases(ref) :: [module]
|
||||
def aliases(ref) do
|
||||
d = :gen_server.call(to_pid(ref), :digraph, @timeout)
|
||||
:digraph.out_neighbours(d, :alias)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns all the modules which were remotely dispatched to.
|
||||
"""
|
||||
@spec remotes(ref) :: [module]
|
||||
def remotes(ref) do
|
||||
d = :gen_server.call(to_pid(ref), :digraph, @timeout)
|
||||
:digraph.out_neighbours(d, :remote)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns all modules that had the given `{ name, arity }`
|
||||
invoked remotely.
|
||||
@@ -157,7 +124,7 @@ defmodule Module.DispatchTracker do
|
||||
neighbours = (lc { _, _ } = t inlist neighbours, do: t) |> :ordsets.from_list
|
||||
remaining = :ordsets.subtract(neighbours, vertices)
|
||||
vertices = :ordsets.union(neighbours, vertices)
|
||||
:lists.foldl(reduce_reachable(d, &1, &2), vertices, remaining)
|
||||
:lists.foldl(&reduce_reachable(d, &1, &2), vertices, remaining)
|
||||
end
|
||||
|
||||
defp to_pid(pid) when is_pid(pid), do: pid
|
||||
@@ -206,12 +173,6 @@ defmodule Module.DispatchTracker do
|
||||
:gen_server.cast(pid, { :add_local, from, to })
|
||||
end
|
||||
|
||||
# Adds an alias.
|
||||
@doc false
|
||||
def add_alias(pid, module) when is_atom(module) do
|
||||
:gen_server.cast(pid, { :add_alias, module })
|
||||
end
|
||||
|
||||
# Adds a remote dispatch to the given target.
|
||||
@doc false
|
||||
def add_remote(pid, function, module, target) when is_atom(module) and is_tuple(target) do
|
||||
@@ -224,40 +185,14 @@ defmodule Module.DispatchTracker do
|
||||
:gen_server.cast(pid, { :add_external, :import, function, module, target })
|
||||
end
|
||||
|
||||
# Associates a module with a warn. This adds the given
|
||||
# module and associates it with the `:import` vertex
|
||||
# permanently, even if warn is false.
|
||||
@doc false
|
||||
def add_warnable(pid, module, warn, line) when is_atom(module) and is_boolean(warn) do
|
||||
:gen_server.cast(pid, { :add_warnable, module, warn, line })
|
||||
end
|
||||
|
||||
# Collect all unused imports where warn has been set to true.
|
||||
def collect_unused_imports(pid) do
|
||||
d = :gen_server.call(pid, :digraph, @timeout)
|
||||
warnable = :digraph.out_neighbours(d, :warn)
|
||||
|
||||
lc mod inlist warnable, not has_imports?(d, mod), line = get_warn_line(d, mod) do
|
||||
{ mod, line }
|
||||
end
|
||||
end
|
||||
|
||||
defp get_warn_line(d, mod) do
|
||||
[edge] = :digraph.out_edges(d, mod)
|
||||
{ ^edge, ^mod, :warn, line } = :digraph.edge(d, edge)
|
||||
line
|
||||
end
|
||||
|
||||
defp has_imports?(d, mod) do
|
||||
Enum.any?(:digraph.in_neighbours(d, mod), match?({ :import, _, _ }, &1))
|
||||
end
|
||||
|
||||
# Yanks a local node. Returns its in and out vertices in a tuple.
|
||||
@doc false
|
||||
def yank(pid, local) do
|
||||
:gen_server.call(to_pid(pid), { :yank, local }, @timeout)
|
||||
end
|
||||
|
||||
# Reattach a previously yanked node
|
||||
@doc false
|
||||
def reattach(pid, kind, tuple, neighbours) do
|
||||
pid = to_pid(pid)
|
||||
add_definition(pid, kind, tuple)
|
||||
@@ -282,7 +217,7 @@ defmodule Module.DispatchTracker do
|
||||
@doc false
|
||||
def collect_unused_locals(pid, private) do
|
||||
reachable = reachable(pid)
|
||||
:lists.foldl(collect_unused_locals(&1, &2, reachable), [], private)
|
||||
:lists.foldl(&collect_unused_locals(&1, &2, reachable), [], private)
|
||||
end
|
||||
|
||||
defp collect_unused_locals({ tuple, kind, 0 }, acc, reachable) do
|
||||
@@ -322,10 +257,6 @@ defmodule Module.DispatchTracker do
|
||||
def init([]) do
|
||||
d = :digraph.new([:protected])
|
||||
:digraph.add_vertex(d, :local)
|
||||
:digraph.add_vertex(d, :alias)
|
||||
:digraph.add_vertex(d, :import)
|
||||
:digraph.add_vertex(d, :remote)
|
||||
:digraph.add_vertex(d, :warn)
|
||||
{ :ok, d }
|
||||
end
|
||||
|
||||
@@ -353,30 +284,11 @@ defmodule Module.DispatchTracker do
|
||||
{ :noreply, d }
|
||||
end
|
||||
|
||||
def handle_cast({ :add_alias, module }, d) do
|
||||
:digraph.add_vertex(d, module)
|
||||
replace_edge!(d, :alias, module)
|
||||
{ :noreply, d }
|
||||
end
|
||||
|
||||
def handle_cast({ :add_external, kind, function, module, { name, arity } }, d) do
|
||||
handle_import_or_remote(d, kind, function, module, name, arity)
|
||||
{ :noreply, d }
|
||||
end
|
||||
|
||||
def handle_cast({ :add_warnable, module, warn, line }, d) do
|
||||
:digraph.add_vertex(d, module)
|
||||
replace_edge!(d, :import, module)
|
||||
|
||||
if warn do
|
||||
:digraph.add_edge(d, :warn, module, line)
|
||||
:digraph.add_edge(d, module, :warn, line)
|
||||
else
|
||||
:digraph.del_path(d, :warn, module)
|
||||
end
|
||||
{ :noreply, d }
|
||||
end
|
||||
|
||||
def handle_cast({ :add_definition, kind, tuple }, d) do
|
||||
handle_add_definition(d, kind, tuple)
|
||||
{ :noreply, d }
|
||||
@@ -414,7 +326,6 @@ defmodule Module.DispatchTracker do
|
||||
|
||||
defp handle_import_or_remote(d, kind, function, module, name, arity) do
|
||||
:digraph.add_vertex(d, module)
|
||||
replace_edge!(d, kind, module)
|
||||
|
||||
tuple = { kind, name, arity }
|
||||
:digraph.add_vertex(d, tuple)
|
||||
|
||||
@@ -156,12 +156,12 @@ defmodule OptionParser do
|
||||
:boolean in kinds ->
|
||||
{ nil, value in [true, "true"] }
|
||||
:integer in kinds ->
|
||||
case String.to_integer(value) do
|
||||
case Integer.parse(value) do
|
||||
{ value, "" } -> { nil, value }
|
||||
_ -> { option, value }
|
||||
end
|
||||
:float in kinds ->
|
||||
case String.to_float(value) do
|
||||
case Float.parse(value) do
|
||||
{ value, "" } -> { nil, value }
|
||||
_ -> { option, value }
|
||||
end
|
||||
|
||||
@@ -304,11 +304,11 @@ defmodule Process do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Sets the group leader of Pid to GroupLeader. Typically, this is used when a processes
|
||||
Sets the group leader of `pid` to `leader`. Typically, this is used when a processes
|
||||
started from a certain shell should have another group leader than `:init`.
|
||||
"""
|
||||
@spec group_leader(leader :: pid, pid) :: true
|
||||
def group_leader(leader, pid) do
|
||||
@spec group_leader(pid, leader :: pid) :: true
|
||||
def group_leader(pid, leader) do
|
||||
:erlang.group_leader(leader, pid)
|
||||
end
|
||||
|
||||
|
||||
+161
-308
@@ -1,58 +1,93 @@
|
||||
defmodule Protocol do
|
||||
@moduledoc false
|
||||
|
||||
# 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 keyword list with the protocol functions
|
||||
when :functions is given;
|
||||
|
||||
* `__impl_for__/1` - receives one argument and returns a module
|
||||
that implements the protocol for the given
|
||||
data type. If no implementation matches, returns nil;
|
||||
|
||||
* `__impl_for__!/1` - same as above but raises an error if an implementation is not found
|
||||
"""
|
||||
# Callback for defprotocol.
|
||||
@doc false
|
||||
def defprotocol(name, [do: block]) do
|
||||
quote do
|
||||
defmodule unquote(name) do
|
||||
# We don't allow function definition inside protocols
|
||||
import Kernel, except: [
|
||||
defmacrop: 1, defmacrop: 2, defmacrop: 4,
|
||||
defmacro: 1, defmacro: 2, defmacro: 4,
|
||||
defp: 1, defp: 2, defp: 4,
|
||||
def: 1, def: 2, def: 4
|
||||
defmacrop: 1, defmacrop: 2, defmacro: 1, defmacro: 2,
|
||||
defp: 1, defp: 2, def: 1, def: 2
|
||||
]
|
||||
|
||||
# Import the new dsl that holds the new def
|
||||
import Protocol.DSL, only: :macros
|
||||
|
||||
# Compile with debug info for consolidation
|
||||
@compile :debug_info
|
||||
|
||||
# Set up a clear slate to store defined functions
|
||||
@functions []
|
||||
@fallback_to_any false
|
||||
|
||||
# Deprecated
|
||||
@only nil
|
||||
@except nil
|
||||
|
||||
# Invoke the user given block
|
||||
unquote(block)
|
||||
|
||||
# Define callbacks and meta information
|
||||
{ conversions, fallback, returns_nil } = Protocol.conversions_for(__MODULE__, @only, @except)
|
||||
Protocol.impl_for(conversions, fallback, returns_nil, __ENV__)
|
||||
Protocol.meta(@functions, conversions, fallback, returns_nil, __ENV__)
|
||||
# Finalize expansion
|
||||
unquote(after_defprotocol)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Implement the given protocol for the given module.
|
||||
It also defines a `__impl__` function which
|
||||
returns the protocol being implemented.
|
||||
"""
|
||||
defp after_defprotocol do
|
||||
quote unquote: false do
|
||||
{ arg, bodies, rec } = Protocol.impl_for(__MODULE__)
|
||||
|
||||
@spec impl_for(term) :: module | nil
|
||||
Kernel.def impl_for(data)
|
||||
|
||||
lc { guard, body } inlist bodies do
|
||||
Kernel.def impl_for(unquote(arg)) when unquote(guard), do: unquote(body)
|
||||
end
|
||||
|
||||
@spec impl_for!(term) :: module | no_return
|
||||
Kernel.def impl_for!(data) do
|
||||
impl_for(data) || raise(Protocol.UndefinedError, protocol: __MODULE__, value: data)
|
||||
end
|
||||
|
||||
# Handle special Record type
|
||||
Kernel.defp rec_impl_for(unquote(arg)), do: unquote(rec)
|
||||
|
||||
# Handle special Any type
|
||||
if @fallback_to_any do
|
||||
Kernel.defp any_impl_for do
|
||||
try do
|
||||
__MODULE__.Any.__impl__(:name)
|
||||
catch
|
||||
:error, :undef, [[{ __MODULE__.Any, :__impl__, [:name], _ }|_]|_] ->
|
||||
nil
|
||||
end
|
||||
end
|
||||
else
|
||||
Kernel.defp any_impl_for, do: nil
|
||||
end
|
||||
|
||||
# Inline both helpers
|
||||
@compile { :inline, any_impl_for: 0, rec_impl_for: 1 }
|
||||
|
||||
unless Kernel.Typespec.defines_type?(__MODULE__, :t, 0) do
|
||||
@type t :: term
|
||||
end
|
||||
|
||||
# Store information as an attribute so it
|
||||
# can be read without loading the module.
|
||||
Module.register_attribute(__MODULE__, :protocol, persist: true)
|
||||
@protocol [fallback_to_any: !!@fallback_to_any, consolidated: false]
|
||||
|
||||
@doc false
|
||||
Kernel.def __protocol__(:name), do: __MODULE__
|
||||
Kernel.def __protocol__(:functions), do: unquote(:lists.sort(@functions))
|
||||
end
|
||||
end
|
||||
|
||||
# Callback for defimpl.
|
||||
@doc false
|
||||
def defimpl(protocol, opts) do
|
||||
do_defimpl(protocol, :lists.keysort(1, opts))
|
||||
end
|
||||
@@ -76,6 +111,10 @@ defmodule Protocol do
|
||||
|
||||
unquote(block)
|
||||
|
||||
Module.register_attribute(__MODULE__, :impl, persist: true)
|
||||
@impl [protocol: @protocol, for: @for]
|
||||
|
||||
@doc false
|
||||
def __impl__(:name), do: __MODULE__
|
||||
def __impl__(:protocol), do: @protocol
|
||||
def __impl__(:for), do: @for
|
||||
@@ -100,203 +139,88 @@ defmodule Protocol do
|
||||
end
|
||||
end
|
||||
|
||||
# Implements the function that detects the protocol and returns
|
||||
# the module to dispatch to. Returns module.Record for records
|
||||
# which should be properly handled by the dispatching function.
|
||||
# Builtin types.
|
||||
@doc false
|
||||
def impl_for(conversions, fallback, returns_nil, env) do
|
||||
contents = lc kind inlist conversions do
|
||||
each_impl_for(kind, conversions, fallback)
|
||||
end
|
||||
|
||||
if returns_nil do
|
||||
contents = contents ++ [quote do
|
||||
defp __raw_impl__(_) do
|
||||
nil
|
||||
end
|
||||
end]
|
||||
end
|
||||
|
||||
Module.eval_quoted env.module, contents, [], env.location
|
||||
def builtin do
|
||||
[ Tuple, Atom, List, BitString, Integer, Float,
|
||||
Function, PID, Port, Reference, Any ]
|
||||
end
|
||||
|
||||
# Defines meta information about the protocol and internal callbacks.
|
||||
# Implements the function that detects the protocol and
|
||||
# returns the module to dispatch to.
|
||||
@doc false
|
||||
def meta(functions, conversions, fallback, returns_nil, env) do
|
||||
any = L.keyfind(Any, 1, conversions) != false
|
||||
records = L.keyfind(Record, 1, conversions) != false
|
||||
def impl_for(current) do
|
||||
arg = quote(do: arg)
|
||||
all = [Record|builtin]
|
||||
|
||||
meta = quote location: :keep do
|
||||
unless Kernel.Typespec.defines_type?(__MODULE__, :t, 0) do
|
||||
@type t :: unquote(generate_type(conversions, any))
|
||||
end
|
||||
|
||||
@doc false
|
||||
def __protocol__(:name), do: __MODULE__
|
||||
def __protocol__(:functions), do: unquote(:lists.sort(functions))
|
||||
end
|
||||
|
||||
impl_for = cond do
|
||||
records and fallback ->
|
||||
quote location: :keep do
|
||||
def __impl_for__(arg) do
|
||||
case __raw_impl__(arg) do
|
||||
__MODULE__.Record ->
|
||||
target = Module.concat(__MODULE__, :erlang.element(1, arg))
|
||||
try do
|
||||
target.__impl__
|
||||
target
|
||||
catch
|
||||
:error, :undef, [[{ ^target, :__impl__, [], _ }|_]|_] ->
|
||||
unquote(fallback)
|
||||
end
|
||||
other ->
|
||||
other
|
||||
end
|
||||
end
|
||||
end
|
||||
records ->
|
||||
quote location: :keep do
|
||||
def __impl_for__(arg) do
|
||||
case __raw_impl__(arg) do
|
||||
__MODULE__.Record ->
|
||||
Module.concat(__MODULE__, :erlang.element(1, arg))
|
||||
other ->
|
||||
other
|
||||
end
|
||||
end
|
||||
end
|
||||
true ->
|
||||
quote location: :keep do
|
||||
def __impl_for__(arg), do: __raw_impl__(arg)
|
||||
end
|
||||
end
|
||||
|
||||
impl_bang = if returns_nil do
|
||||
quote do
|
||||
def __impl_for__!(arg) do
|
||||
__impl_for__(arg) || raise(Protocol.UndefinedError, protocol: __MODULE__, value: arg)
|
||||
end
|
||||
end
|
||||
else
|
||||
quote do
|
||||
def __impl_for__!(arg), do: __impl_for__(arg)
|
||||
end
|
||||
end
|
||||
|
||||
Module.eval_quoted env.module, [meta, impl_for, impl_bang], [], env.location
|
||||
{ arg,
|
||||
lc(mod inlist all, do: impl_for(current, mod, arg)),
|
||||
rec_impl_for(current, arg) }
|
||||
end
|
||||
|
||||
# Returns the default conversions according to the given
|
||||
# only/except options.
|
||||
@doc false
|
||||
def conversions_for(module, only, except) do
|
||||
kinds = all_types
|
||||
defp rec_impl_for(current, arg) do
|
||||
fallback = impl_for(current, Tuple, arg) |> elem(1)
|
||||
|
||||
conversions =
|
||||
if only do
|
||||
L.map(fn i -> L.keyfind(i, 1, kinds) end, only)
|
||||
else
|
||||
except = except || [Any]
|
||||
L.foldl(fn i, list -> L.keydelete(i, 1, list) end, kinds, except)
|
||||
end
|
||||
|
||||
fallback = cond do
|
||||
L.keyfind(Tuple, 1, conversions) ->
|
||||
Module.concat module, Tuple
|
||||
L.keyfind(Any, 1, conversions) ->
|
||||
Module.concat module, Any
|
||||
true ->
|
||||
nil
|
||||
end
|
||||
|
||||
# If any is not in the list and we don't implement all
|
||||
# protocols, we need to handle nil cases.
|
||||
returns_nil = L.keyfind(Any, 1, conversions) == false and length(conversions) < 10
|
||||
{ conversions, fallback, returns_nil }
|
||||
end
|
||||
|
||||
## Helpers
|
||||
|
||||
defp generate_type(_conversions, true) do
|
||||
quote(do: any)
|
||||
end
|
||||
|
||||
defp generate_type(conversions, false) do
|
||||
or_function = fn({ _, _, x }, acc) -> { :|, [], [acc, x] } end
|
||||
{ _, _, first } = hd(conversions)
|
||||
:lists.foldl(or_function, first, tl(conversions))
|
||||
end
|
||||
|
||||
defp all_types do
|
||||
[ { Record, :is_record, quote do: tuple },
|
||||
{ Tuple, :is_tuple, quote do: tuple },
|
||||
{ Atom, :is_atom, quote do: atom },
|
||||
{ List, :is_list, quote do: list },
|
||||
{ BitString, :is_bitstring, quote do: <<>> },
|
||||
{ Number, :is_number, quote do: number },
|
||||
{ Function, :is_function, quote do: (... -> any) },
|
||||
{ PID, :is_pid, quote do: pid },
|
||||
{ Port, :is_port, quote do: port },
|
||||
{ Reference, :is_reference, quote do: reference },
|
||||
{ Any, :is_any, quote do: any } ]
|
||||
end
|
||||
|
||||
# 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)
|
||||
end
|
||||
end
|
||||
|
||||
defp is_builtin?([{h, _, _}|t]) do
|
||||
quote do
|
||||
first == unquote(h) or unquote(is_builtin?(t))
|
||||
end
|
||||
end
|
||||
|
||||
# We don't have a fallback, so we assume what was given
|
||||
# as a record is indeed a record
|
||||
defp each_impl_for({ _, :is_record, _ }, _conversions, nil) do
|
||||
quote do
|
||||
defp __raw_impl__(arg) when is_record(arg) do
|
||||
__MODULE__.Record
|
||||
target = Module.concat(unquote(current), unquote(arg))
|
||||
try do
|
||||
target.__impl__(:name)
|
||||
catch
|
||||
:error, :undef, [[{ ^target, :__impl__, [:name], _ }|_]|_] ->
|
||||
unquote(fallback)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Specially handle records in the case we have fallbacks.
|
||||
defp each_impl_for({ _, :is_record, _ }, conversions, fallback) do
|
||||
quote do
|
||||
defp __raw_impl__(arg) when is_record(arg) do
|
||||
first = :erlang.element(1, arg)
|
||||
case unquote(is_builtin?(conversions)) do
|
||||
true -> unquote(fallback)
|
||||
false ->
|
||||
case atom_to_list(first) do
|
||||
'Elixir.' ++ _ -> __MODULE__.Record
|
||||
_ -> unquote(fallback)
|
||||
end
|
||||
end
|
||||
defp impl_for(current, Record, arg) do
|
||||
fallback = impl_for(current, Tuple, arg) |> elem(1)
|
||||
|
||||
dispatch = quote do
|
||||
atom = :erlang.element(1, unquote(arg))
|
||||
|
||||
case not(atom in unquote(builtin)) and match?('Elixir.' ++ _, atom_to_list(atom)) do
|
||||
true -> rec_impl_for(atom)
|
||||
false -> unquote(fallback)
|
||||
end
|
||||
end
|
||||
|
||||
quote do
|
||||
{ is_record(unquote(arg)), unquote(dispatch) }
|
||||
end
|
||||
end
|
||||
|
||||
# Special case any as we don't need to generate a guard.
|
||||
defp each_impl_for({ _, :is_any, _ }, _, _) do
|
||||
defp impl_for(current, Tuple, arg), do: impl_with_fallback(Tuple, :is_tuple, current, arg)
|
||||
defp impl_for(current, Atom, arg), do: impl_with_fallback(Atom, :is_atom, current, arg)
|
||||
defp impl_for(current, List, arg), do: impl_with_fallback(List, :is_list, current, arg)
|
||||
defp impl_for(current, BitString, arg), do: impl_with_fallback(BitString, :is_bitstring, current, arg)
|
||||
defp impl_for(current, Integer, arg), do: impl_with_fallback(Integer, :is_integer, current, arg)
|
||||
defp impl_for(current, Float, arg), do: impl_with_fallback(Float, :is_float, current, arg)
|
||||
defp impl_for(current, Function, arg), do: impl_with_fallback(Function, :is_function, current, arg)
|
||||
defp impl_for(current, PID, arg), do: impl_with_fallback(PID, :is_pid, current, arg)
|
||||
defp impl_for(current, Port, arg), do: impl_with_fallback(Port, :is_port, current, arg)
|
||||
defp impl_for(current, Reference, arg), do: impl_with_fallback(Reference, :is_reference, current, arg)
|
||||
|
||||
defp impl_for(_current, Any, _arg) do
|
||||
{ true, quote(do: any_impl_for) }
|
||||
end
|
||||
|
||||
# Defines an implementation with fallback to the given module.
|
||||
defp impl_with_fallback(mod, guard, current, arg) do
|
||||
quote do
|
||||
defp __raw_impl__(_) do
|
||||
__MODULE__.Any
|
||||
end
|
||||
{ unquote(guard)(unquote(arg)),
|
||||
unquote(with_fallback(Module.concat(current, mod))) }
|
||||
end
|
||||
end
|
||||
|
||||
# Generate all others protocols.
|
||||
defp each_impl_for({ kind, fun, _ }, _, _) do
|
||||
# Tries to dispatch to a given target, fallbacks to the
|
||||
# given `fallback` implementation if the target does not exist.
|
||||
defp with_fallback(target) when is_atom(target) do
|
||||
quote do
|
||||
defp __raw_impl__(arg) when unquote(fun)(arg) do
|
||||
__MODULE__.unquote(kind)
|
||||
try do
|
||||
unquote(target).__impl__(:name)
|
||||
catch
|
||||
:error, :undef, [[{ unquote(target), :__impl__, [:name], _ }|_]|_] ->
|
||||
any_impl_for
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -305,36 +229,45 @@ end
|
||||
defmodule Protocol.DSL do
|
||||
@moduledoc false
|
||||
|
||||
# We need to use :lists because Enum is not available yet
|
||||
require :lists, as: L
|
||||
|
||||
@doc false
|
||||
def args_and_body(module, name, arity) do
|
||||
# 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
|
||||
{ binary_to_atom(<<?x, i + 64>>), [], __MODULE__ }
|
||||
end
|
||||
defmacro def({ _, _, args }) when args == [] or is_atom(args) do
|
||||
raise ArgumentError, message: "protocol functions expect at least one argument"
|
||||
end
|
||||
|
||||
{ conversions, fallback, returns_nil } = conversions_for(module)
|
||||
clauses = [default_clause(name, args)]
|
||||
defmacro def({ name, _, args }) when is_atom(name) and is_list(args) do
|
||||
arity = length(args)
|
||||
|
||||
if returns_nil do
|
||||
clauses = [nil_clause()|clauses]
|
||||
end
|
||||
type_args = lc _ inlist :lists.seq(2, arity), do: quote(do: term)
|
||||
type_args = [quote(do: t) | type_args]
|
||||
|
||||
if L.keyfind(Record, 1, conversions) do
|
||||
clauses = [record_clause(name, args, fallback)|clauses]
|
||||
end
|
||||
call_args = lc i inlist :lists.seq(2, arity),
|
||||
do: { binary_to_atom(<<?x, i + 64>>), [], __MODULE__ }
|
||||
call_args = [quote(do: t) | call_args]
|
||||
|
||||
body =
|
||||
quote do
|
||||
case __raw_impl__(xA), do: unquote({ :->, [], clauses })
|
||||
quote do
|
||||
name = unquote(name)
|
||||
arity = unquote(arity)
|
||||
|
||||
@functions [{name, arity}|@functions]
|
||||
|
||||
# Generate a fake definition with the user
|
||||
# signature that will be used by docs
|
||||
Kernel.def unquote(name)(unquote_splicing(args))
|
||||
|
||||
# Generate the actual implementation
|
||||
Kernel.def unquote(name)(unquote_splicing(call_args)) do
|
||||
impl_for!(t).unquote(name)(unquote_splicing(call_args))
|
||||
end
|
||||
|
||||
{ args, body }
|
||||
# Convert the spec to callback if possible,
|
||||
# otherwise generate a dummy callback
|
||||
Protocol.DSL.callback_from_spec(__MODULE__, name, arity) ||
|
||||
@callback unquote(name)(unquote_splicing(type_args)) :: term
|
||||
end
|
||||
end
|
||||
|
||||
defmacro def(_) do
|
||||
raise ArgumentError, message: "invalid args for def inside defprotocol"
|
||||
end
|
||||
|
||||
@doc false
|
||||
@@ -349,84 +282,4 @@ defmodule Protocol.DSL do
|
||||
|
||||
found != []
|
||||
end
|
||||
|
||||
defp conversions_for(module) do
|
||||
only = Module.get_attribute(module, :only)
|
||||
except = Module.get_attribute(module, :except)
|
||||
Protocol.conversions_for(module, only, except)
|
||||
end
|
||||
|
||||
defp default_clause(name, args) do
|
||||
{ [quote do: other],
|
||||
[],
|
||||
quote(do: apply(other, unquote(name), [unquote_splicing(args)])) }
|
||||
end
|
||||
|
||||
defp nil_clause() do
|
||||
{ [nil],
|
||||
[],
|
||||
quote(do: raise(Protocol.UndefinedError, protocol: __MODULE__, value: xA)) }
|
||||
end
|
||||
|
||||
defp record_clause(name, args, nil) do
|
||||
{ [quote(do: __MODULE__.Record)],
|
||||
[],
|
||||
quote(do: Module.concat(__MODULE__, :erlang.element(1, xA)).unquote(name)(unquote_splicing(args))) }
|
||||
end
|
||||
|
||||
defp record_clause(name, args, fallback) do
|
||||
arity = length(args)
|
||||
|
||||
{ [quote(do: __MODULE__.Record)],
|
||||
[],
|
||||
quote do
|
||||
target = Module.concat(__MODULE__, :erlang.element(1, xA))
|
||||
try do
|
||||
target.unquote(name)(unquote_splicing(args))
|
||||
catch
|
||||
:error, :undef, [[{ ^target, name, args, _ }|_]|_] when
|
||||
name == unquote(name) and length(args) == unquote(arity) ->
|
||||
apply unquote(fallback), name, [unquote_splicing(args)]
|
||||
end
|
||||
end }
|
||||
end
|
||||
|
||||
defmacro def(expression) do
|
||||
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)
|
||||
|
||||
type_args = lc _ inlist :lists.seq(2, arity), do: quote(do: term)
|
||||
type_args = [quote(do: t) | type_args]
|
||||
|
||||
meta = quote do
|
||||
name = unquote(name)
|
||||
arity = unquote(arity)
|
||||
|
||||
@functions [{name, arity}|@functions]
|
||||
|
||||
# Generate a fake definition with the user
|
||||
# signature that will be used by docs
|
||||
Kernel.def unquote(name)(unquote_splicing(args))
|
||||
|
||||
# Convert the spec to callback if possible,
|
||||
# otherwise generate a dummy callback
|
||||
Protocol.DSL.callback_from_spec(__MODULE__, name, arity) ||
|
||||
@callback unquote(name)(unquote_splicing(type_args)) :: term
|
||||
end
|
||||
|
||||
impl = quote bind_quoted: [name: name, arity: arity] do
|
||||
{ args, body } = Protocol.DSL.args_and_body(__MODULE__, name, arity)
|
||||
Kernel.def unquote(name)(unquote_splicing(args)), do: unquote(body)
|
||||
end
|
||||
|
||||
[meta, impl]
|
||||
end
|
||||
end
|
||||
|
||||
@@ -0,0 +1,266 @@
|
||||
defmodule Protocol.Consolidation do
|
||||
@moduledoc """
|
||||
Module responsible for consolidating protocols and helpers for
|
||||
extracting protocols and implementations from code paths for
|
||||
consolidation.
|
||||
"""
|
||||
|
||||
@doc """
|
||||
Extract all protocols from the given paths.
|
||||
|
||||
The paths can be either a char list or a string. Internally
|
||||
they are worked on as char lists, so passing them as lists
|
||||
avoid extra conversion.
|
||||
|
||||
## Examples
|
||||
|
||||
# Get Elixir's ebin and retrieve all protocols
|
||||
iex> path = :code.lib_dir(:elixir, :ebin)
|
||||
iex> mods = Protocol.Consolidation.extract_protocols([path])
|
||||
iex> Enumerable in mods
|
||||
true
|
||||
|
||||
"""
|
||||
@spec extract_protocols([char_list | String.t]) :: [atom]
|
||||
def extract_protocols(paths) do
|
||||
extract_matching_by_attribute paths, 'Elixir.',
|
||||
fn module, attributes ->
|
||||
case attributes[:protocol] do
|
||||
[fallback_to_any: _, consolidated: _] -> module
|
||||
_ -> nil
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Extract all types implemented for the given protocol from
|
||||
the given paths.
|
||||
|
||||
The paths can be either a char list or a string. Internally
|
||||
they are worked on as char lists, so passing them as lists
|
||||
avoid extra conversion.
|
||||
|
||||
## Examples
|
||||
|
||||
# Get Elixir's ebin and retrieve all protocols
|
||||
iex> path = :code.lib_dir(:elixir, :ebin)
|
||||
iex> mods = Protocol.Consolidation.extract_impls(Enumerable, [path])
|
||||
iex> List in mods
|
||||
true
|
||||
|
||||
"""
|
||||
@spec extract_impls(module, [char_list | String.t]) :: [atom]
|
||||
def extract_impls(protocol, paths) when is_atom(protocol) do
|
||||
prefix = atom_to_list(protocol) ++ '.'
|
||||
extract_matching_by_attribute paths, prefix, fn
|
||||
_mod, attributes ->
|
||||
case attributes[:impl] do
|
||||
[protocol: ^protocol, for: for] -> for
|
||||
_ -> nil
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defp extract_matching_by_attribute(paths, prefix, callback) do
|
||||
lc path inlist paths,
|
||||
file inlist list_dir(path),
|
||||
mod = extract_from_file(path, file, prefix, callback),
|
||||
do: mod
|
||||
end
|
||||
|
||||
defp list_dir(path) when is_list(path) do
|
||||
case :file.list_dir(path) do
|
||||
{ :ok, files } -> files
|
||||
_ -> []
|
||||
end
|
||||
end
|
||||
|
||||
defp list_dir(path), do: list_dir(to_char_list(path))
|
||||
|
||||
defp extract_from_file(path, file, prefix, callback) do
|
||||
if :lists.prefix(prefix, file) and Path.extname(file) == '.beam' do
|
||||
extract_from_beam(Path.join(path, file), callback)
|
||||
end
|
||||
end
|
||||
|
||||
defp extract_from_beam(file, callback) do
|
||||
case :beam_lib.chunks(file, [:attributes]) do
|
||||
{:ok, { module, [attributes: attributes] } } ->
|
||||
callback.(module, attributes)
|
||||
_ ->
|
||||
nil
|
||||
end
|
||||
end
|
||||
|
||||
defmacrop if_ok(expr, call) do
|
||||
quote do
|
||||
case unquote(expr) do
|
||||
{ :ok, var } -> unquote(Macro.pipe(quote(do: var), call))
|
||||
other -> other
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Receives a protocol and a list of implementations and
|
||||
consolidates the given protocol. Consolidation happens
|
||||
by changing the protocol `impl_for` in the abstract
|
||||
format to have fast lookup rules.
|
||||
|
||||
It returns the updated version of the protocol bytecode.
|
||||
A given bytecode or protocol implementation can be checked
|
||||
to be consolidated or not by analyzing the protocol
|
||||
attribute:
|
||||
|
||||
Enumerable.__info__(:attributes)[:protocol]
|
||||
|
||||
If the first element of the tuple is true, it means
|
||||
the protocol was consolidated.
|
||||
|
||||
This function does not load the protocol at any point
|
||||
nor loads the new bytecode for the compiled module.
|
||||
"""
|
||||
@spec apply_to(module, [module]) ::
|
||||
{ :ok, binary } |
|
||||
{ :error, :not_a_protocol } |
|
||||
{ :error, :no_beam_info }
|
||||
def apply_to(protocol, types) when is_atom(protocol) do
|
||||
ensure_protocol(protocol)
|
||||
|> if_ok(change_debug_info types)
|
||||
|> if_ok(compile)
|
||||
end
|
||||
|
||||
# Ensure the given module is loaded and is a protocol.
|
||||
defp ensure_protocol(protocol) do
|
||||
case :beam_lib.chunks(beam_file(protocol), [:abstract_code, :attributes]) do
|
||||
{ :ok, { ^protocol, [abstract_code: { _raw, abstract_code },
|
||||
attributes: attributes] } } ->
|
||||
case attributes[:protocol] do
|
||||
[fallback_to_any: any, consolidated: _] ->
|
||||
{ :ok, { protocol, any, abstract_code } }
|
||||
_ ->
|
||||
{ :error, :not_a_protocol }
|
||||
end
|
||||
_ ->
|
||||
{ :error, :no_beam_info }
|
||||
end
|
||||
end
|
||||
|
||||
defp beam_file(module) when is_atom(module) do
|
||||
case :code.which(module) do
|
||||
:non_existing -> module
|
||||
file -> file
|
||||
end
|
||||
end
|
||||
|
||||
# Change the debug information to the optimized
|
||||
# impl_for/1 dispatch version.
|
||||
defp change_debug_info({ protocol, any, code }, types) do
|
||||
types = if any, do: types, else: List.delete(types, Any)
|
||||
records = types -- Protocol.builtin
|
||||
builtin = Protocol.builtin -- (Protocol.builtin -- types)
|
||||
builtin = if records != [], do: [Record|builtin], else: builtin
|
||||
change_impl_for(code, protocol, builtin, records, false, [])
|
||||
end
|
||||
|
||||
defp change_impl_for([{ :attribute, line, :protocol, _ }|t], protocol, builtin, records, _, acc) do
|
||||
attr = [fallback_to_any: Any in builtin, consolidated: true]
|
||||
change_impl_for(t, protocol, builtin, records, true,
|
||||
[{ :attribute, line, :protocol, attr }|acc])
|
||||
end
|
||||
|
||||
defp change_impl_for([{ :function, line, :impl_for, 1, _ }|t], protocol, builtin, records, is_protocol, acc) do
|
||||
clauses = lc type inlist builtin, do: clause_for(type, protocol, line)
|
||||
|
||||
unless Any in builtin do
|
||||
clauses = clauses ++ [fallback_clause_for(nil, protocol, line)]
|
||||
end
|
||||
|
||||
change_impl_for(t, protocol, builtin, records, is_protocol,
|
||||
[{ :function, line, :impl_for, 1, clauses }|acc])
|
||||
end
|
||||
|
||||
defp change_impl_for([{ :function, line, :rec_impl_for, 1, _ }|t], protocol, builtin, records, is_protocol, acc) do
|
||||
fallback = if Tuple in builtin, do: Module.concat(protocol, Tuple)
|
||||
clauses = lc type inlist records, do: record_clause_for(type, protocol, line)
|
||||
clauses = clauses ++ [fallback_clause_for(fallback, protocol, line)]
|
||||
|
||||
change_impl_for(t, protocol, builtin, records, is_protocol,
|
||||
[{ :function, line, :rec_impl_for, 1, clauses }|acc])
|
||||
end
|
||||
|
||||
defp change_impl_for([h|t], protocol, info, types, is_protocol, acc) do
|
||||
change_impl_for(t, protocol, info, types, is_protocol, [h|acc])
|
||||
end
|
||||
|
||||
defp change_impl_for([], protocol, _info, _types, is_protocol, acc) do
|
||||
if is_protocol do
|
||||
{ :ok, { protocol, Enum.reverse(acc) } }
|
||||
else
|
||||
{ :error, :not_a_protocol }
|
||||
end
|
||||
end
|
||||
|
||||
defp clause_for(Tuple, protocol, line), do: builtin_clause_for(Tuple, :is_tuple, protocol, line)
|
||||
defp clause_for(Atom, protocol, line), do: builtin_clause_for(Atom, :is_atom, protocol, line)
|
||||
defp clause_for(List, protocol, line), do: builtin_clause_for(List, :is_list, protocol, line)
|
||||
defp clause_for(BitString, protocol, line), do: builtin_clause_for(BitString, :is_bitstring, protocol, line)
|
||||
defp clause_for(Integer, protocol, line), do: builtin_clause_for(Integer, :is_integer, protocol, line)
|
||||
defp clause_for(Float, protocol, line), do: builtin_clause_for(Float, :is_float, protocol, line)
|
||||
defp clause_for(Function, protocol, line), do: builtin_clause_for(Function, :is_function, protocol, line)
|
||||
defp clause_for(PID, protocol, line), do: builtin_clause_for(PID, :is_pid, protocol, line)
|
||||
defp clause_for(Port, protocol, line), do: builtin_clause_for(Port, :is_port, protocol, line)
|
||||
defp clause_for(Reference, protocol, line), do: builtin_clause_for(Reference, :is_reference, protocol, line)
|
||||
|
||||
defp clause_for(Any, protocol, line) do
|
||||
{:clause, line, [{:var, line, :_}], [],
|
||||
[{ :atom, line, Module.concat(protocol, Any) }]}
|
||||
end
|
||||
|
||||
defp clause_for(Record, _protocol, line) do
|
||||
{:clause, line, [{:var, line, :x}],
|
||||
[[{:op, line, :andalso,
|
||||
{:call, line,
|
||||
{:remote, line, {:atom, line, :erlang}, {:atom, line, :is_tuple}},
|
||||
[{:var, line, :x}]},
|
||||
{:call, line,
|
||||
{:remote, line, {:atom, line, :erlang}, {:atom, line, :is_atom}},
|
||||
[{:call, line,
|
||||
{:remote, line, {:atom, line, :erlang}, {:atom, line, :element}},
|
||||
[{:integer, line, 1}, {:var, line, :x}]
|
||||
}]},
|
||||
}]],
|
||||
[{:call, line,
|
||||
{:atom, line, :rec_impl_for},
|
||||
[{:call, line,
|
||||
{:remote, line, {:atom, line, :erlang}, {:atom, line, :element}},
|
||||
[{:integer, line, 1}, {:var, line, :x}]}]}]}
|
||||
end
|
||||
|
||||
defp builtin_clause_for(mod, guard, protocol, line) do
|
||||
{:clause, line,
|
||||
[{:var, line, :x}],
|
||||
[[{:call, line,
|
||||
{:remote, line, {:atom, line, :erlang}, {:atom, line, guard}},
|
||||
[{:var, line, :x}],
|
||||
}]],
|
||||
[{:atom, line, Module.concat(protocol, mod)}]}
|
||||
end
|
||||
|
||||
defp record_clause_for(other, protocol, line) do
|
||||
{:clause, line, [{:atom, line, other}], [],
|
||||
[{:atom, line, Module.concat(protocol, other)}]}
|
||||
end
|
||||
|
||||
defp fallback_clause_for(value, _protocol, line) do
|
||||
{:clause, line, [{:var, line, :_}], [],
|
||||
[{ :atom, line, value }]}
|
||||
end
|
||||
|
||||
# Finally compile the module and emit its bytecode.
|
||||
defp compile({ protocol, code }) do
|
||||
opts = if Code.compiler_options[:debug_info], do: [:debug_info], else: []
|
||||
{ :ok, ^protocol, binary, _warnings } = :compile.forms(code, [:return|opts])
|
||||
{ :ok, binary }
|
||||
end
|
||||
end
|
||||
@@ -17,21 +17,21 @@ defprotocol Range.Iterator do
|
||||
end
|
||||
|
||||
defimpl Enumerable, for: Range do
|
||||
def reduce(Range[first: first] = range, acc, fun) do
|
||||
def reduce(first .. _ = range, acc, fun) do
|
||||
Range.Iterator.reduce(first, range, acc, fun)
|
||||
end
|
||||
|
||||
def member?(Range[first: first, last: last], value) do
|
||||
value in first..last
|
||||
def member?(first .. last, value) do
|
||||
first <= value and value <= last
|
||||
end
|
||||
|
||||
def count(Range[first: first] = range) do
|
||||
def count(first .. _ = range) do
|
||||
Range.Iterator.count(first, range)
|
||||
end
|
||||
end
|
||||
|
||||
defimpl Range.Iterator, for: Number do
|
||||
def reduce(first, Range[last: last], acc, fun) when is_integer(first) and is_integer(last) do
|
||||
defimpl Range.Iterator, for: Integer do
|
||||
def reduce(first, Range[last: last], acc, fun) when is_integer(last) do
|
||||
reducer = if last >= first do
|
||||
fn(acc, fun) -> do_reducer_up(first, last, acc, fun) end
|
||||
else
|
||||
@@ -56,11 +56,11 @@ defimpl Range.Iterator, for: Number do
|
||||
do_reducer_down(counter - 1, last, fun.(counter, acc), fun)
|
||||
end
|
||||
|
||||
def count(first, Range[last: last]) when is_integer(first) and is_integer(last) and last >= first do
|
||||
def count(first, Range[last: last]) when is_integer(last) and last >= first do
|
||||
last - first + 1
|
||||
end
|
||||
|
||||
def count(first, Range[last: last]) when is_integer(first) and is_integer(last) do
|
||||
def count(first, Range[last: last]) when is_integer(last) do
|
||||
first - last + 1
|
||||
end
|
||||
end
|
||||
|
||||
+103
-56
@@ -1,19 +1,19 @@
|
||||
defmodule Record do
|
||||
@moduledoc %S"""
|
||||
Convenience functions for working with Records.
|
||||
Functions for working with Records.
|
||||
|
||||
A record is a tagged tuple which contains one or more elements
|
||||
where the first element is an atom. We can manually create a record
|
||||
by simply defining such a tuple:
|
||||
|
||||
iex> record = { User, "josé", 25 }
|
||||
iex> record = { User, "José", 25 }
|
||||
iex> is_record(record, User)
|
||||
true
|
||||
|
||||
However, manually constructing tuples can be quite error prone.
|
||||
In case we need to add a new field to our User, it would require
|
||||
If we need to add a new field to our User, it would require
|
||||
us to carefully change all places where the tuple is used.
|
||||
Furthermore, as more and more items are added to the tuple, they
|
||||
Furthermore, as more items are added to the tuple, they
|
||||
lose semantic value.
|
||||
|
||||
This module solves these problems by allowing us to name each
|
||||
@@ -38,7 +38,7 @@ defmodule Record do
|
||||
In the example above, `defrecordp` is going to generate a set of
|
||||
macros named `user` that allows us to create, update and match
|
||||
on a record. Our record is going to have two fields, a `name` with
|
||||
default value of "José" and `age` of 25.
|
||||
default value of "José" and `age` with default value 25.
|
||||
|
||||
Let's see some examples:
|
||||
|
||||
@@ -75,10 +75,7 @@ defmodule Record do
|
||||
As the name says, `defrecordp` is useful when you don't want to
|
||||
expose the record definition. The `user` macro used above, for
|
||||
example, is only available inside the `User` module and nowhere else.
|
||||
You can find more information in `Kernel.defrecordp/2` docs.
|
||||
|
||||
In general, though, records are used as part of a module's public
|
||||
interface. For such use cases, Elixir provides record modules.
|
||||
You can find more information in `Kernel.defrecordp/3` docs.
|
||||
|
||||
## defrecord
|
||||
|
||||
@@ -97,7 +94,7 @@ defmodule Record do
|
||||
user() #=> User[name: "José", age: 25]
|
||||
user(age: 26) #=> User[name: "José", age: 26]
|
||||
|
||||
Notice that now, since the record definition is accessible, Elixir
|
||||
Notice that now since the record definition is accessible, Elixir
|
||||
shows the record nicely formatted, no longer as a simple tuple. We
|
||||
can get the raw formatting by passing `raw: true` to `inspect`:
|
||||
|
||||
@@ -112,9 +109,9 @@ defmodule Record do
|
||||
User[] #=> User[name: "José", age: 25]
|
||||
User[age: 26] #=> User[name: "José", age: 26]
|
||||
|
||||
The macro name is replaced by the module name and the parenthesis
|
||||
are replaced by brackets. When the shortcut syntax is used, there
|
||||
is no need to import the record.
|
||||
The macro name is replaced by the module name and the parentheses
|
||||
are replaced by brackets. When this syntax is used, there is no
|
||||
need to import the record.
|
||||
|
||||
Before we sum up the differences between `defrecord` and
|
||||
`defrecordp`, there is one last functionality introduced by
|
||||
@@ -123,12 +120,12 @@ defmodule Record do
|
||||
## Runtime access
|
||||
|
||||
All the functionality discussed above happens at compilation time.
|
||||
This means that both `user(user, age: 26)` and `User[user, age: 26]`
|
||||
will be transformed into a simple tuple operation at compile time.
|
||||
This means that both `user(age: 26)` and `User[age: 26]` are expanded
|
||||
into a tuple at compile time.
|
||||
|
||||
However, there are some situations where we want to set or update
|
||||
fields dynamically. `defrecord` (and `defrecord` only) supports
|
||||
it out of the box:
|
||||
fields dynamically. `defrecord` (and not `defrecordp` ) supports
|
||||
this behaviour out of the box:
|
||||
|
||||
defrecord User, name: "José", age: 25
|
||||
|
||||
@@ -138,14 +135,23 @@ defmodule Record do
|
||||
user.update(age: 26)
|
||||
#=> User[name: "Hello", age: 26]
|
||||
|
||||
All the calls above happen at runtime. It gives Elixir
|
||||
records flexibility at the cost of performance since
|
||||
there is more work happening at runtime.
|
||||
All the calls above happen at runtime. It gives Elixir records
|
||||
flexibility at the cost of performance since there is more work
|
||||
happening at runtime.
|
||||
|
||||
To sum up, `defrecordp` should be used when you don't want
|
||||
to expose the record information while `defrecord` should be used
|
||||
whenever you want to share a record within your code or with other
|
||||
libraries or whenever you need to dynamically set or update fields.
|
||||
The above calls (`new` and `update`) can accept both atom and string
|
||||
keys for field names, however not both at the same time. This feature
|
||||
allows to "sanitize" untrusted dictionaries and initialize/update
|
||||
records without using `Kernel.binary_to_existing_atom/1`.
|
||||
|
||||
To sum up, `defrecordp` should be used when you don't want to expose
|
||||
the record information while `defrecord` should be used whenever you
|
||||
want to share a record within your code or with other libraries or
|
||||
whenever you need to dynamically set or update fields.
|
||||
|
||||
The standard library contains excellent examples of both use cases,
|
||||
with [`HashDict`](HashDict.html) being implemented with `defrecordp`
|
||||
and [`Range`](Range.html) with `defrecord`.
|
||||
|
||||
You can learn more about records in the `Kernel.defrecord/3` docs. Now
|
||||
let's discuss the usefulness of combining records with protocols.
|
||||
@@ -170,22 +176,30 @@ defmodule Record do
|
||||
end
|
||||
end
|
||||
|
||||
Now we can explicitly convert our WeekDate:
|
||||
Now we can explicitly convert our `WeekDate`:
|
||||
|
||||
to_string WeekDate[year: 2013, week: 26, week_day: 4]
|
||||
"2013-W26-4"
|
||||
|
||||
A protocol can be implemented for any record defined via `defrecord`.
|
||||
A protocol can be implemented for any record, whether
|
||||
generated with `defrecordp` or `defrecord`.
|
||||
"""
|
||||
|
||||
@type t :: tuple
|
||||
|
||||
@doc """
|
||||
Extract record information from an Erlang file and
|
||||
return the fields as a list of tuples.
|
||||
Extract record information from an Erlang file.
|
||||
|
||||
Returns the fields as a list of tuples.
|
||||
|
||||
## Examples
|
||||
|
||||
> Record.extract(:file_info, from_lib: "kernel/include/file.hrl")
|
||||
[size: :undefined, type: :undefined, access: :undefined, atime: :undefined,
|
||||
mtime: :undefined, ctime: :undefined, mode: :undefined, links: :undefined,
|
||||
major_device: :undefined, minor_device: :undefined, inode: :undefined,
|
||||
uid: :undefined, gid: :undefined]
|
||||
|
||||
defrecord FileInfo, Record.extract(:file_info, from_lib: "kernel/include/file.hrl")
|
||||
|
||||
"""
|
||||
@@ -194,8 +208,9 @@ defmodule Record do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Imports a public record definition as a set of private macros,
|
||||
as defined by `Kernel.defrecordp/2`. This is useful when one
|
||||
Import a public record definition as a set of private macros.
|
||||
|
||||
Macros defined as in `Kernel.defrecordp/3`. This is useful when one
|
||||
desires to manipulate a record via a set of macros instead
|
||||
of the regular access syntax.
|
||||
|
||||
@@ -231,14 +246,14 @@ defmodule Record do
|
||||
unquoted_fields = unquote(fields)
|
||||
|
||||
defmodule unquote(name) do
|
||||
import Elixir.Record.DSL
|
||||
import Record.DSL
|
||||
|
||||
@record_fields []
|
||||
@record_types []
|
||||
|
||||
Elixir.Record.deffunctions(unquoted_fields, __ENV__)
|
||||
Record.deffunctions(unquoted_fields, __ENV__)
|
||||
value = unquote(block)
|
||||
Elixir.Record.deftypes(@record_fields, @record_types, __ENV__)
|
||||
Record.deftypes(@record_fields, @record_types, __ENV__)
|
||||
value
|
||||
end
|
||||
end
|
||||
@@ -280,8 +295,9 @@ defmodule Record do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Defines record functions skipping the module definition.
|
||||
This is called directly by `defrecord`. It expects the record
|
||||
Define record functions skipping the module definition.
|
||||
|
||||
This is called directly by `Kernel.defrecord/3`. It expects the record
|
||||
values, a set of options and the module environment.
|
||||
|
||||
## Examples
|
||||
@@ -308,7 +324,7 @@ defmodule Record do
|
||||
|
||||
contents = [quote(do: @record_fields unquote(escaped))|contents]
|
||||
|
||||
# Special case for bootstraping purposes
|
||||
# Special case for bootstrapping purposes
|
||||
if env == Macro.Env do
|
||||
Module.eval_quoted(env, contents, [], [])
|
||||
else
|
||||
@@ -317,7 +333,7 @@ defmodule Record do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Defines types and specs for the record.
|
||||
Define types and specs for the record.
|
||||
"""
|
||||
def deftypes(values, types, env) do
|
||||
types = types || []
|
||||
@@ -339,8 +355,10 @@ defmodule Record do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Defines macros for manipulating records. This is called
|
||||
directly by `defrecordp`. It expects the macro name, the
|
||||
Define macros for manipulating records.
|
||||
|
||||
This is called
|
||||
directly by `Kernel.defrecordp/3`. It expects the macro name, the
|
||||
record values and the environment.
|
||||
|
||||
## Examples
|
||||
@@ -414,7 +432,7 @@ defmodule Record do
|
||||
@doc false
|
||||
def access(atom, fields, keyword, caller) do
|
||||
unless is_keyword(keyword) do
|
||||
raise ArgumentError, message: "expected contents inside brackets to be a Keyword"
|
||||
raise ArgumentError, message: "expected contents inside brackets to be a keyword list, got: #{inspect keyword}"
|
||||
end
|
||||
|
||||
in_match = caller.in_match?
|
||||
@@ -627,9 +645,8 @@ defmodule Record do
|
||||
# an ordered dict of options (opts) and it will try to fetch
|
||||
# the given key from the ordered dict, falling back to the
|
||||
# default value if one does not exist.
|
||||
selective = lc { k, v } inlist values do
|
||||
quote do: Keyword.get(opts, unquote(k), unquote(v))
|
||||
end
|
||||
atom_selective = lc { k, v } inlist values, do: initialize_lookup(k, v)
|
||||
string_selective = lc { k, v } inlist values, do: initialize_lookup(atom_to_binary(k), v)
|
||||
|
||||
quote do
|
||||
@doc false
|
||||
@@ -637,7 +654,17 @@ defmodule Record do
|
||||
|
||||
@doc false
|
||||
def new([]), do: { __MODULE__, unquote_splicing(defaults) }
|
||||
def new(opts) when is_list(opts), do: { __MODULE__, unquote_splicing(selective) }
|
||||
def new([{key, _}|_] = opts) when is_atom(key), do: { __MODULE__, unquote_splicing(atom_selective) }
|
||||
def new([{key, _}|_] = opts) when is_binary(key), do: { __MODULE__, unquote_splicing(string_selective) }
|
||||
end
|
||||
end
|
||||
|
||||
defp initialize_lookup(k, v) do
|
||||
quote do
|
||||
case :lists.keyfind(unquote(k), 1, opts) do
|
||||
false -> unquote(v)
|
||||
{_, v} -> v
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -728,15 +755,14 @@ defmodule Record do
|
||||
# Define an updater method that receives a
|
||||
# keyword list and updates the record.
|
||||
defp updater(values) do
|
||||
fields =
|
||||
lc {key, _default} inlist values do
|
||||
index = find_index(values, key, 1)
|
||||
quote do
|
||||
Keyword.get(keywords, unquote(key), elem(record, unquote(index)))
|
||||
end
|
||||
end
|
||||
atom_fields =
|
||||
lc {key, _default} inlist values, do: updater_lookup(key, key, values)
|
||||
|
||||
contents = quote do: { __MODULE__, unquote_splicing(fields) }
|
||||
string_fields =
|
||||
lc {key, _default} inlist values, do: updater_lookup(atom_to_binary(key), key, values)
|
||||
|
||||
atom_contents = quote do: { __MODULE__, unquote_splicing(atom_fields) }
|
||||
string_contents = quote do: { __MODULE__, unquote_splicing(string_fields) }
|
||||
|
||||
quote do
|
||||
@doc false
|
||||
@@ -744,8 +770,22 @@ defmodule Record do
|
||||
record
|
||||
end
|
||||
|
||||
def update(keywords, record) do
|
||||
unquote(contents)
|
||||
def update([{key, _}|_] = keywords, record) when is_atom(key) do
|
||||
unquote(atom_contents)
|
||||
end
|
||||
def update([{key, _}|_] = keywords, record) when is_binary(key) do
|
||||
unquote(string_contents)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defp updater_lookup(k, key, values) do
|
||||
v = find_index(values, key, 1)
|
||||
|
||||
quote do
|
||||
case :lists.keyfind(unquote(k), 1, keywords) do
|
||||
false -> elem(record, unquote(v))
|
||||
{_, value} -> value
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -768,6 +808,7 @@ defmodule Record do
|
||||
defp core_specs(values) do
|
||||
types = lc { _, _, spec } inlist values, do: spec
|
||||
options = if values == [], do: [], else: [options_specs(values)]
|
||||
values_specs = if values == [], do: [], else: values_specs(values)
|
||||
|
||||
quote do
|
||||
unless Kernel.Typespec.defines_type?(__MODULE__, :t, 0) do
|
||||
@@ -775,7 +816,7 @@ defmodule Record do
|
||||
end
|
||||
|
||||
unless Kernel.Typespec.defines_type?(__MODULE__, :options, 0) do
|
||||
@type options :: unquote(options)
|
||||
@type options :: unquote(options) | [{String.t, unquote(values_specs)}]
|
||||
end
|
||||
|
||||
@spec new :: t
|
||||
@@ -793,6 +834,11 @@ defmodule Record do
|
||||
{ :|, [], [{ k, v }, acc] }
|
||||
end, { k, v }, t
|
||||
end
|
||||
defp values_specs([{ _, _, v }|t]) do
|
||||
:lists.foldl fn { _, _, v }, acc ->
|
||||
{ :|, [], [v, acc] }
|
||||
end, v, t
|
||||
end
|
||||
|
||||
defp accessor_specs([{ :__exception__, _, _ }|t], 1, acc) do
|
||||
accessor_specs(t, 2, acc)
|
||||
@@ -814,7 +860,7 @@ defmodule Record do
|
||||
|
||||
## Helpers
|
||||
|
||||
defp is_keyword(list) when is_list(list), do: :lists.all(is_keyword_tuple(&1), list)
|
||||
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
|
||||
@@ -870,6 +916,7 @@ defmodule Record.DSL do
|
||||
|
||||
@doc """
|
||||
Defines the type for each field in the record.
|
||||
|
||||
Expects a keyword list.
|
||||
"""
|
||||
defmacro record_type(opts) when is_list(opts) do
|
||||
|
||||
@@ -19,7 +19,7 @@ defmodule Record.Extractor do
|
||||
def retrieve(name, from_lib: file) when is_binary(file) do
|
||||
[app|path] = :filename.split(String.to_char_list!(file))
|
||||
|
||||
case :code.lib_dir(app) do
|
||||
case :code.lib_dir(list_to_atom(app)) do
|
||||
{ :error, _ } ->
|
||||
raise ArgumentError, message: "lib file #{file} could not be found"
|
||||
libpath ->
|
||||
@@ -88,4 +88,4 @@ defmodule Record.Extractor do
|
||||
{ :value, record, _ } = :erl_eval.expr(record_ast, [])
|
||||
record
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
+36
-19
@@ -1,22 +1,21 @@
|
||||
defmodule Regex do
|
||||
@moduledoc %S"""
|
||||
Regular expressions for Elixir built on top of the re module
|
||||
Regular expressions for Elixir built on top of the `re` module
|
||||
in the Erlang Standard Library. More information can be found
|
||||
in the re documentation: http://www.erlang.org/doc/man/re.html
|
||||
in the [`re` documentation](http://www.erlang.org/doc/man/re.html).
|
||||
|
||||
Regular expressions in Elixir can be created using Regex.compile!
|
||||
Regular expressions in Elixir can be created using `Regex.compile!`
|
||||
or using the special form with `%r`:
|
||||
|
||||
# A simple regular expressions that matches foo anywhere in the string
|
||||
%r/foo/
|
||||
|
||||
# A regular expression with case insensitive options and handling for unicode chars
|
||||
%r/foo/iu
|
||||
# A regular expression with case insensitive options
|
||||
%r/foo/i
|
||||
|
||||
The re module provides several options, the ones available in Elixir, followed by
|
||||
The `re` module provides several options, the ones available in Elixir, followed by
|
||||
their shortcut in parenthesis, are:
|
||||
|
||||
* `unicode` (u) - enable unicode specific patterns like \p
|
||||
* `caseless` (i) - add case insensitivity
|
||||
* `dotall` (s) - causes dot to match newlines and also set newline to anycrlf.
|
||||
The new line setting can be overridden by setting `(*CR)` or `(*LF)` or
|
||||
@@ -27,8 +26,8 @@ defmodule Regex do
|
||||
allow `#` to delimit comments
|
||||
* `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
|
||||
* `ungreedy` (r) - inverts the "greediness" of the regexp
|
||||
* `groups` (g) - compiles with info about groups available
|
||||
|
||||
The options not available are:
|
||||
|
||||
@@ -44,7 +43,7 @@ defmodule Regex do
|
||||
"""
|
||||
|
||||
defrecordp :regex, Regex, [:re_pattern, :source, :options, :groups]
|
||||
@type t :: { Regex, term, binary, binary, [atom] }
|
||||
@type t :: { Regex, term, binary, binary, [atom] | nil }
|
||||
|
||||
defexception CompileError, message: "regex could not be compiled"
|
||||
|
||||
@@ -53,12 +52,20 @@ defmodule Regex do
|
||||
|
||||
The given options can either be a binary with the characters
|
||||
representing the same regex options given to the `%r` sigil,
|
||||
or a list of options, as expected by the Erlang `re` docs.
|
||||
or a list of options, as expected by the [Erlang `re` docs](http://www.erlang.org/doc/man/re.html).
|
||||
|
||||
It returns `{ :ok, regex }` in case of success,
|
||||
`{ :error, reason }` otherwise.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Regex.compile("foo")
|
||||
{:ok, %r"foo"}
|
||||
iex> Regex.compile("*foo")
|
||||
{:error, {'nothing to repeat', 0}}
|
||||
|
||||
"""
|
||||
@spec compile(binary, binary | [term]) :: t
|
||||
@spec compile(binary, binary | [term]) :: { :ok, t } | { :error, any }
|
||||
def compile(source, options // "")
|
||||
|
||||
def compile(source, options) when is_binary(options) do
|
||||
@@ -67,7 +74,9 @@ defmodule Regex do
|
||||
{ :error, { :invalid_option, rest } }
|
||||
|
||||
translated_options ->
|
||||
compile(source, translated_options, options)
|
||||
# Always use the unicode option, we don't have a latin1 legacy like
|
||||
# Erlang.
|
||||
compile(source, [:unicode|translated_options], options)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -154,15 +163,15 @@ defmodule Regex do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Regex.captures(%r/c(?<foo>d)/g, "abcd")
|
||||
iex> Regex.named_captures(%r/c(?<foo>d)/g, "abcd")
|
||||
[foo: "d"]
|
||||
iex> Regex.captures(%r/a(?<foo>b)c(?<bar>d)/g, "abcd")
|
||||
iex> Regex.named_captures(%r/a(?<foo>b)c(?<bar>d)/g, "abcd")
|
||||
[foo: "b", bar: "d"]
|
||||
iex> Regex.captures(%r/a(?<foo>b)c(?<bar>d)/g, "efgh")
|
||||
iex> Regex.named_captures(%r/a(?<foo>b)c(?<bar>d)/g, "efgh")
|
||||
nil
|
||||
|
||||
"""
|
||||
def captures(regex(groups: groups) = regex, string, options // []) do
|
||||
def named_captures(regex(groups: groups) = regex, string, options // []) do
|
||||
options = Keyword.put_new(options, :capture, :groups)
|
||||
results = run(regex, string, options)
|
||||
if results, do: Enum.zip(groups, results)
|
||||
@@ -249,13 +258,14 @@ defmodule Regex do
|
||||
|
||||
options = [{ :capture, captures, return }, :global]
|
||||
case :re.run(string, compiled, options) do
|
||||
:match -> []
|
||||
:nomatch -> []
|
||||
{ :match, results } -> results
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Split the given target into the number of parts specified.
|
||||
Splits the given target into the number of parts specified.
|
||||
If no number of parts is given, it defaults to `:infinity`.
|
||||
|
||||
## Examples
|
||||
@@ -265,6 +275,10 @@ defmodule Regex do
|
||||
["a","b-c"]
|
||||
iex> Regex.split(%r/-/, "abc")
|
||||
["abc"]
|
||||
iex> Regex.split(%r//, "abc")
|
||||
["a", "b", "c", ""]
|
||||
iex> Regex.split(%r//, "abc", trim: true)
|
||||
["a", "b", "c"]
|
||||
"""
|
||||
|
||||
def split(regex, string, options // [])
|
||||
@@ -354,7 +368,10 @@ 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(<<?u, t :: binary>>) do
|
||||
IO.write "The /u flag for regular expressions is no longer needed\n#{Exception.format_stacktrace}"
|
||||
translate_options(t)
|
||||
end
|
||||
defp translate_options(<<?i, t :: binary>>), do: [:caseless|translate_options(t)]
|
||||
defp translate_options(<<?x, t :: binary>>), do: [:extended|translate_options(t)]
|
||||
defp translate_options(<<?f, t :: binary>>), do: [:firstline|translate_options(t)]
|
||||
|
||||
@@ -15,6 +15,21 @@ defmodule Set do
|
||||
For simplicity's sake, in the examples below every time
|
||||
`new` is used, it implies one of the module-specific
|
||||
calls like above.
|
||||
|
||||
## Protocols
|
||||
|
||||
Sets are required to implement the `Enumerable` protocol,
|
||||
allowing one to write:
|
||||
|
||||
Enum.each(set, fn k ->
|
||||
IO.inspect k
|
||||
end)
|
||||
|
||||
## Match
|
||||
|
||||
Sets are required to implement all operations
|
||||
using the match (`===`) operator. Any deviation from
|
||||
this behaviour should be avoided and explicitly documented.
|
||||
"""
|
||||
|
||||
use Behaviour
|
||||
|
||||
+63
-21
@@ -8,7 +8,7 @@ defmodule Stream do
|
||||
|
||||
iex> range = 1..5
|
||||
1..5
|
||||
iex> Enum.map range, &1 * 2
|
||||
iex> Enum.map range, &(&1 * 2)
|
||||
[2,4,6,8,10]
|
||||
|
||||
In the example above, as we mapped over the range, the elements being
|
||||
@@ -16,8 +16,8 @@ defmodule Stream do
|
||||
module allows us to map the range, without triggering its enumeration:
|
||||
|
||||
iex> range = 1..3
|
||||
iex> stream = Stream.map(range, &1 * 2)
|
||||
iex> Enum.map(stream, &1 + 1)
|
||||
iex> stream = Stream.map(range, &(&1 * 2))
|
||||
iex> Enum.map(stream, &(&1 + 1))
|
||||
[3,5,7]
|
||||
|
||||
Notice we started with a range and then we created a stream that is
|
||||
@@ -34,9 +34,9 @@ defmodule Stream do
|
||||
example:
|
||||
|
||||
1..3 |>
|
||||
Enum.map(IO.inspect(&1)) |>
|
||||
Enum.map(&1 * 2) |>
|
||||
Enum.map(IO.inspect(&1))
|
||||
Enum.map(&IO.inspect(&1)) |>
|
||||
Enum.map(&(&1 * 2)) |>
|
||||
Enum.map(&IO.inspect(&1))
|
||||
1
|
||||
2
|
||||
3
|
||||
@@ -50,9 +50,9 @@ defmodule Stream do
|
||||
was iterated three times. Let's see an example with streams:
|
||||
|
||||
stream = 1..3 |>
|
||||
Stream.map(IO.inspect(&1)) |>
|
||||
Stream.map(&1 * 2) |>
|
||||
Stream.map(IO.inspect(&1))
|
||||
Stream.map(&IO.inspect(&1)) |>
|
||||
Stream.map(&(&1 * 2)) |>
|
||||
Stream.map(&IO.inspect(&1))
|
||||
Enum.to_list(stream)
|
||||
1
|
||||
2
|
||||
@@ -81,8 +81,8 @@ defmodule Stream do
|
||||
|
||||
This module also allows us to create streams from any enumerable:
|
||||
|
||||
iex> stream = Stream.map([1,2,3], &1 * 2)
|
||||
iex> Enum.map(stream, &1 + 1)
|
||||
iex> stream = Stream.map([1,2,3], &(&1 * 2))
|
||||
iex> Enum.map(stream, &(&1 + 1))
|
||||
[3,5,7]
|
||||
|
||||
By simply passing a list (which is an enumerable) as the first argument
|
||||
@@ -187,7 +187,7 @@ defmodule Stream do
|
||||
"""
|
||||
@spec cycle(Enumerable.t) :: t
|
||||
def cycle(enumerable) do
|
||||
do_cycle(enumerable, &1, &2)
|
||||
&do_cycle(enumerable, &1, &2)
|
||||
end
|
||||
|
||||
defp do_cycle(enumerable, acc, fun) do
|
||||
@@ -225,7 +225,7 @@ defmodule Stream do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> stream = Stream.drop_while(1..10, &1 <= 5)
|
||||
iex> stream = Stream.drop_while(1..10, &(&1 <= 5))
|
||||
iex> Enum.to_list(stream)
|
||||
[6,7,8,9,10]
|
||||
|
||||
@@ -271,7 +271,7 @@ defmodule Stream do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Stream.iterate(0, &1+1) |> Enum.take(5)
|
||||
iex> Stream.iterate(0, &(&1+1)) |> Enum.take(5)
|
||||
[0,1,2,3,4]
|
||||
|
||||
"""
|
||||
@@ -325,12 +325,30 @@ defmodule Stream do
|
||||
def flat_map(enumerable, f) do
|
||||
Lazy[enumerable: enumerable,
|
||||
fun: fn(f1) ->
|
||||
fn(entry, acc) ->
|
||||
Enumerable.reduce(f.(entry), acc, f1)
|
||||
end
|
||||
fn(entry, acc) -> do_flat_map(f.(entry), acc, f1) end
|
||||
end]
|
||||
end
|
||||
|
||||
defp do_flat_map(Lazy[] = lazy, acc, f1) do
|
||||
try do
|
||||
Enumerable.reduce(lazy, acc, fn x, y ->
|
||||
try do
|
||||
f1.(x, y)
|
||||
catch
|
||||
{ :stream_lazy, nesting, rest } ->
|
||||
throw({ :stream_flat_map, nesting, rest })
|
||||
end
|
||||
end)
|
||||
catch
|
||||
{ :stream_flat_map, nesting, rest } ->
|
||||
throw({ :stream_lazy, nesting, rest })
|
||||
end
|
||||
end
|
||||
|
||||
defp do_flat_map(enum, acc, f1) do
|
||||
Enumerable.reduce(enum, acc, f1)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Creates a stream that will reject elements according to
|
||||
the given function on enumeration.
|
||||
@@ -352,7 +370,6 @@ defmodule Stream do
|
||||
end]
|
||||
end
|
||||
|
||||
|
||||
@doc """
|
||||
Returns a stream generated by calling `generator_fun` repeatedly.
|
||||
|
||||
@@ -365,7 +382,7 @@ defmodule Stream do
|
||||
@spec repeatedly((() -> element)) :: t
|
||||
def repeatedly(generator_fun)
|
||||
when is_function(generator_fun, 0) do
|
||||
do_repeatedly(generator_fun, &1, &2)
|
||||
&do_repeatedly(generator_fun, &1, &2)
|
||||
end
|
||||
|
||||
defp do_repeatedly(generator_fun, acc, fun) do
|
||||
@@ -388,7 +405,7 @@ defmodule Stream do
|
||||
|
||||
"""
|
||||
@spec take(Enumerable.t, non_neg_integer) :: t
|
||||
def take(_enumerable, 0), do: Lazy[enumerable: [], fun: & &1]
|
||||
def take(_enumerable, 0), do: Lazy[enumerable: [], fun: &(&1)]
|
||||
|
||||
def take(enumerable, n) when n > 0 do
|
||||
Lazy[enumerable: enumerable,
|
||||
@@ -407,7 +424,7 @@ defmodule Stream do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> stream = Stream.take_while(1..100, &1 <= 5)
|
||||
iex> stream = Stream.take_while(1..100, &(&1 <= 5))
|
||||
iex> Enum.to_list(stream)
|
||||
[1,2,3,4,5]
|
||||
|
||||
@@ -427,6 +444,31 @@ defmodule Stream do
|
||||
acc: true]
|
||||
end
|
||||
|
||||
@doc """
|
||||
Emit a sequence of values and accumulators. Successive values are generated by
|
||||
calling `next_fun` with the previous accumulator.
|
||||
|
||||
If the return value is nil iteration ends.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Stream.unfold(5, fn 0 -> nil; n -> {n, n-1} end) |> Enum.to_list()
|
||||
[5, 4, 3, 2, 1]
|
||||
"""
|
||||
@spec unfold(acc, (acc -> { element, acc } | nil)) :: t
|
||||
def unfold(acc, f) do
|
||||
fn acc1, f1 ->
|
||||
do_unfold(acc, f, acc1, f1)
|
||||
end
|
||||
end
|
||||
|
||||
defp do_unfold(gen_acc, gen_fun, acc, fun) do
|
||||
case gen_fun.(gen_acc) do
|
||||
nil -> acc
|
||||
{ v, new_gen_acc } -> do_unfold(new_gen_acc, gen_fun, fun.(v, acc), fun)
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Creates a stream where each item in the enumerable will
|
||||
be accompanied by its index.
|
||||
|
||||
+116
-101
@@ -1,3 +1,5 @@
|
||||
import Kernel, except: [length: 1]
|
||||
|
||||
defmodule String do
|
||||
@moduledoc %S"""
|
||||
A String in Elixir is a UTF-8 encoded binary.
|
||||
@@ -5,7 +7,7 @@ defmodule String do
|
||||
## String and binary operations
|
||||
|
||||
The functions in this module act according to the
|
||||
Unicode Standard, version 6.2.0. For example,
|
||||
Unicode Standard, version 6.3.0. For example,
|
||||
`capitalize/1`, `downcase/1`, `strip/1` are provided by this
|
||||
module.
|
||||
|
||||
@@ -16,7 +18,7 @@ defmodule String do
|
||||
* `Kernel.binary_part/3` - retrieves part of the binary
|
||||
* `Kernel.bit_size/1` and `Kernel.byte_size/1` - size related functions
|
||||
* `Kernel.is_bitstring/1` and `Kernel.is_binary/1` - type checking function
|
||||
* Plus a number of conversion functions, like `Kernel.binary_to_atom/2`,
|
||||
* Plus a number of conversion functions, like `Kernel.binary_to_atom/1`,
|
||||
`Kernel.binary_to_integer/2`, `Kernel.binary_to_term/1` and their inverses,
|
||||
like `Kernel.integer_to_binary/2`
|
||||
|
||||
@@ -30,10 +32,14 @@ defmodule String do
|
||||
For example, the character "é" is represented with two
|
||||
bytes:
|
||||
|
||||
iex> string = "é"
|
||||
...> byte_size(string)
|
||||
iex> byte_size("é")
|
||||
2
|
||||
|
||||
However, this module returns the proper length:
|
||||
|
||||
iex> String.length("é")
|
||||
1
|
||||
|
||||
Furthermore, this module also presents the concept of
|
||||
graphemes, which are multiple characters that may be
|
||||
"perceived as a single character" by readers. For example,
|
||||
@@ -43,6 +49,8 @@ defmodule String do
|
||||
iex> string = "\x{0065}\x{0301}"
|
||||
...> byte_size(string)
|
||||
3
|
||||
iex> String.length(string)
|
||||
1
|
||||
|
||||
Although the example above is made of two characters, it is
|
||||
perceived by users as one.
|
||||
@@ -57,6 +65,11 @@ defmodule String do
|
||||
Standard, but does not contain any of the locale specific
|
||||
behaviour.
|
||||
|
||||
More information about graphemes can be found in the [Unicode
|
||||
Standard Annex #29](http://www.unicode.org/reports/tr29/).
|
||||
This current Elixir version implements Extended Grapheme Cluster
|
||||
algorithm.
|
||||
|
||||
## Integer codepoints
|
||||
|
||||
Although codepoints could be represented as integers, this
|
||||
@@ -139,7 +152,7 @@ defmodule String do
|
||||
def printable?(_), do: false
|
||||
|
||||
@doc """
|
||||
Splits a string on substrings at each Unicode whitespace
|
||||
Divides a string into substrings at each Unicode whitespace
|
||||
occurrence with leading and trailing whitespace ignored.
|
||||
|
||||
## Examples
|
||||
@@ -155,7 +168,7 @@ defmodule String do
|
||||
@spec split(t) :: [t]
|
||||
defdelegate split(binary), to: String.Unicode
|
||||
|
||||
@doc """
|
||||
@doc %S"""
|
||||
Divides a string into substrings based on a pattern,
|
||||
returning a list of these substrings. The pattern can
|
||||
be a string, a list of strings or a regular expression.
|
||||
@@ -168,6 +181,8 @@ defmodule String do
|
||||
|
||||
## Examples
|
||||
|
||||
Splitting with a string pattern:
|
||||
|
||||
iex> String.split("a,b,c", ",")
|
||||
["a", "b", "c"]
|
||||
iex> String.split("a,b,c", ",", global: false)
|
||||
@@ -175,15 +190,30 @@ defmodule String do
|
||||
iex> String.split(" a b c ", " ", trim: true)
|
||||
["a", "b", "c"]
|
||||
|
||||
A list of patterns:
|
||||
|
||||
iex> String.split("1,2 3,4", [" ", ","])
|
||||
["1", "2", "3", "4"]
|
||||
|
||||
A regular expression:
|
||||
|
||||
iex> String.split("a,b,c", %r{,})
|
||||
["a", "b", "c"]
|
||||
iex> String.split("a,b,c", %r{,}, global: false)
|
||||
["a", "b,c"]
|
||||
iex> String.split("a,b", %r{\\.})
|
||||
["a,b"]
|
||||
iex> String.split(" a b c ", %r{\s}, trim: true)
|
||||
["a", "b", "c"]
|
||||
|
||||
Splitting on empty patterns returns codepoints:
|
||||
|
||||
iex> String.split("abc", %r{})
|
||||
["a", "b", "c", ""]
|
||||
iex> String.split("abc", "")
|
||||
["a", "b", "c", ""]
|
||||
iex> String.split("abc", "", trim: true)
|
||||
["a", "b", "c"]
|
||||
iex> String.split("abc", "", global: false)
|
||||
["a", "bc"]
|
||||
|
||||
"""
|
||||
@spec split(t, t | [t] | Regex.t) :: [t]
|
||||
@@ -192,6 +222,8 @@ defmodule String do
|
||||
|
||||
def split("", _pattern, _options), do: [""]
|
||||
|
||||
def split(binary, "", options), do: split(binary, %r"", options)
|
||||
|
||||
def split(binary, pattern, options) when is_regex(pattern) do
|
||||
Regex.split(pattern, binary, options)
|
||||
end
|
||||
@@ -435,13 +467,13 @@ defmodule String do
|
||||
end
|
||||
|
||||
defp do_justify(subject, len, padding, type) when is_integer(padding) do
|
||||
subject_len = String.length(subject)
|
||||
subject_len = length(subject)
|
||||
|
||||
cond do
|
||||
subject_len >= len ->
|
||||
subject
|
||||
subject_len < len ->
|
||||
fill = String.duplicate(<<padding :: utf8>>, len - subject_len)
|
||||
fill = duplicate(<<padding :: utf8>>, len - subject_len)
|
||||
|
||||
case type do
|
||||
:left -> subject <> fill
|
||||
@@ -523,11 +555,11 @@ defmodule String do
|
||||
"""
|
||||
@spec reverse(t) :: t
|
||||
def reverse(string) do
|
||||
do_reverse(String.Unicode.next_grapheme(string), [])
|
||||
do_reverse(next_grapheme(string), [])
|
||||
end
|
||||
|
||||
defp do_reverse({grapheme, rest}, acc) do
|
||||
do_reverse(String.Unicode.next_grapheme(rest), [grapheme|acc])
|
||||
do_reverse(next_grapheme(rest), [grapheme|acc])
|
||||
end
|
||||
|
||||
defp do_reverse(:no_grapheme, acc), do: iolist_to_binary(acc)
|
||||
@@ -584,6 +616,7 @@ defmodule String do
|
||||
{ "j", "osé" }
|
||||
|
||||
"""
|
||||
@compile { :inline, next_codepoint: 1 }
|
||||
@spec next_codepoint(t) :: {codepoint, t} | :no_codepoint
|
||||
defdelegate next_codepoint(string), to: String.Unicode
|
||||
|
||||
@@ -642,30 +675,10 @@ defmodule String do
|
||||
def valid_character?(<<_ :: utf8>> = codepoint), do: valid?(codepoint)
|
||||
def valid_character?(_), do: false
|
||||
|
||||
@doc %S"""
|
||||
Checks whether `str` is a valid codepoint.
|
||||
|
||||
Note that the empty string is considered invalid, as are
|
||||
strings containing multiple codepoints.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> String.valid_codepoint?("a")
|
||||
true
|
||||
iex> String.valid_codepoint?("ø")
|
||||
true
|
||||
iex> String.valid_codepoint?(<<0xffff :: 16>>)
|
||||
false
|
||||
iex> String.valid_codepoint?("asdf")
|
||||
false
|
||||
|
||||
"""
|
||||
@spec valid_codepoint?(codepoint) :: boolean
|
||||
def valid_codepoint?(<<_ :: utf8>>), do: true
|
||||
def valid_codepoint?(_), do: false
|
||||
|
||||
@doc """
|
||||
Returns unicode graphemes in the string.
|
||||
Returns unicode graphemes in the string as per Extended Grapheme
|
||||
Cluster algorithm outlined in the [Unicode Standard Annex #29,
|
||||
Unicode Text Segmentation](http://www.unicode.org/reports/tr29/).
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -674,7 +687,7 @@ defmodule String do
|
||||
|
||||
"""
|
||||
@spec graphemes(t) :: [grapheme]
|
||||
defdelegate graphemes(string), to: String.Unicode
|
||||
defdelegate graphemes(string), to: String.Graphemes
|
||||
|
||||
@doc """
|
||||
Returns the next grapheme in a String.
|
||||
@@ -689,8 +702,9 @@ defmodule String do
|
||||
{ "j", "osé" }
|
||||
|
||||
"""
|
||||
@compile { :inline, next_grapheme: 1 }
|
||||
@spec next_grapheme(t) :: { grapheme, t } | :no_grapheme
|
||||
defdelegate next_grapheme(string), to: String.Unicode
|
||||
defdelegate next_grapheme(string), to: String.Graphemes
|
||||
|
||||
@doc """
|
||||
Returns the first grapheme from an utf8 string,
|
||||
@@ -782,7 +796,7 @@ defmodule String do
|
||||
end
|
||||
|
||||
def at(string, position) when position < 0 do
|
||||
real_pos = do_length(next_grapheme(string)) - abs(position)
|
||||
real_pos = length(string) - abs(position)
|
||||
case real_pos >= 0 do
|
||||
true -> do_at(next_grapheme(string), real_pos, 0)
|
||||
false -> nil
|
||||
@@ -827,7 +841,7 @@ defmodule String do
|
||||
@spec slice(t, integer, integer) :: grapheme | nil
|
||||
|
||||
def slice(string, start, 0) do
|
||||
case abs(start) <= String.length(string) do
|
||||
case abs(start) <= length(string) do
|
||||
true -> ""
|
||||
false -> nil
|
||||
end
|
||||
@@ -838,13 +852,72 @@ defmodule String do
|
||||
end
|
||||
|
||||
def slice(string, start, len) when start < 0 and len >= 0 do
|
||||
real_start_pos = do_length(next_grapheme(string)) - abs(start)
|
||||
real_start_pos = length(string) - abs(start)
|
||||
case real_start_pos >= 0 do
|
||||
true -> do_slice(next_grapheme(string), real_start_pos, real_start_pos + len - 1, 0, "")
|
||||
false -> nil
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns a substring from the offset given by the start of the
|
||||
range to the offset given by the end of the range.
|
||||
|
||||
If the start of the range is not a valid offset for the given
|
||||
string or if the range is in reverse order, returns `nil`.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> String.slice("elixir", 1..3)
|
||||
"lix"
|
||||
iex> String.slice("elixir", 1..10)
|
||||
"lixir"
|
||||
iex> String.slice("elixir", 10..3)
|
||||
nil
|
||||
|
||||
iex> String.slice("elixir", -4..-1)
|
||||
"ixir"
|
||||
iex> String.slice("elixir", 2..-1)
|
||||
"ixir"
|
||||
iex> String.slice("elixir", -4..6)
|
||||
"ixir"
|
||||
iex> String.slice("elixir", -1..-4)
|
||||
nil
|
||||
iex> String.slice("elixir", -10..-7)
|
||||
nil
|
||||
|
||||
iex> String.slice("a", 0..1500)
|
||||
"a"
|
||||
iex> String.slice("a", 1..1500)
|
||||
""
|
||||
iex> String.slice("a", 2..1500)
|
||||
nil
|
||||
|
||||
"""
|
||||
@spec slice(t, Range.t) :: t | nil
|
||||
|
||||
def slice(string, range)
|
||||
|
||||
def slice(string, first..last) when first >= 0 and last >= 0 do
|
||||
do_slice(next_grapheme(string), first, last, 0, "")
|
||||
end
|
||||
|
||||
def slice(string, first..last) do
|
||||
total = length(string)
|
||||
|
||||
if first < 0 do
|
||||
first = total + first
|
||||
end
|
||||
|
||||
if last < 0 do
|
||||
last = total + last
|
||||
end
|
||||
|
||||
if first > 0 do
|
||||
do_slice(next_grapheme(string), first, last, 0, "")
|
||||
end
|
||||
end
|
||||
|
||||
defp do_slice(_, start_pos, last_pos, _, _) when start_pos > last_pos do
|
||||
nil
|
||||
end
|
||||
@@ -872,64 +945,6 @@ defmodule String do
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Converts a string to an integer. If successful, returns a
|
||||
tuple of the form `{integer, remainder of string}`. If unsuccessful,
|
||||
returns `:error`.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> String.to_integer("34")
|
||||
{34,""}
|
||||
iex> String.to_integer("34.5")
|
||||
{34,".5"}
|
||||
iex> String.to_integer("three")
|
||||
:error
|
||||
|
||||
"""
|
||||
@spec to_integer(t) :: {integer, t} | :error
|
||||
|
||||
def to_integer(string) do
|
||||
{result, remainder} = :string.to_integer(:binary.bin_to_list(string))
|
||||
case result do
|
||||
:error -> :error
|
||||
_ -> {result, :binary.list_to_bin(remainder)}
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Converts a string to a float. If successful, returns a
|
||||
tuple of the form `{float, remainder of string}`.
|
||||
If unsuccessful, returns `:error`.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> String.to_float("34")
|
||||
{34.0,""}
|
||||
iex> String.to_float("34.25")
|
||||
{34.25,""}
|
||||
iex> String.to_float("56.5xyz")
|
||||
{56.5,"xyz"}
|
||||
iex> String.to_float("pi")
|
||||
:error
|
||||
|
||||
"""
|
||||
@spec to_float(t) :: {integer, t} | :error
|
||||
|
||||
def to_float(string) do
|
||||
charlist = :binary.bin_to_list(string)
|
||||
{result, remainder} = :string.to_float(charlist)
|
||||
case result do
|
||||
:error ->
|
||||
{int_result, int_remainder} = :string.to_integer(charlist)
|
||||
case int_result do
|
||||
:error -> :error
|
||||
_ -> {:erlang.float(int_result), :binary.list_to_bin(int_remainder)}
|
||||
end
|
||||
_ -> {result, :binary.list_to_bin(remainder)}
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns `true` if `string` starts with any of the prefixes given, otherwise
|
||||
`false`. `prefixes` can be either a single prefix or a list of prefixes.
|
||||
@@ -947,7 +962,7 @@ defmodule String do
|
||||
@spec starts_with?(t, t | [t]) :: boolean
|
||||
|
||||
def starts_with?(string, prefixes) when is_list(prefixes) do
|
||||
Enum.any?(prefixes, do_starts_with(string, &1))
|
||||
Enum.any?(prefixes, &do_starts_with(string, &1))
|
||||
end
|
||||
|
||||
def starts_with?(string, prefix) do
|
||||
@@ -983,7 +998,7 @@ defmodule String do
|
||||
@spec ends_with?(t, t | [t]) :: boolean
|
||||
|
||||
def ends_with?(string, suffixes) when is_list(suffixes) do
|
||||
Enum.any?(suffixes, do_ends_with(string, &1))
|
||||
Enum.any?(suffixes, &do_ends_with(string, &1))
|
||||
end
|
||||
|
||||
def ends_with?(string, suffix) do
|
||||
@@ -1022,7 +1037,7 @@ defmodule String do
|
||||
@spec contains?(t, t | [t]) :: boolean
|
||||
|
||||
def contains?(string, matches) when is_list(matches) do
|
||||
Enum.any?(matches, do_contains(string, &1))
|
||||
Enum.any?(matches, &do_contains(string, &1))
|
||||
end
|
||||
|
||||
def contains?(string, match) do
|
||||
|
||||
@@ -14,8 +14,6 @@ defprotocol String.Chars do
|
||||
as `"foo" <> to_string(bar)`.
|
||||
"""
|
||||
|
||||
@only [BitString, List, Number, Atom, Record]
|
||||
|
||||
def to_string(thing)
|
||||
end
|
||||
|
||||
@@ -29,7 +27,7 @@ defimpl String.Chars, for: Atom do
|
||||
end
|
||||
|
||||
def to_string(atom) do
|
||||
atom_to_binary(atom, :utf8)
|
||||
atom_to_binary(atom)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -67,14 +65,19 @@ defimpl String.Chars, for: List do
|
||||
def to_string(char_list), do: String.from_char_list!(char_list)
|
||||
end
|
||||
|
||||
defimpl String.Chars, for: Number do
|
||||
defimpl String.Chars, for: Integer do
|
||||
@doc """
|
||||
Simply converts the number (integer or a float) to a binary.
|
||||
Simply converts the integer to a string.
|
||||
"""
|
||||
def to_string(thing) when is_integer(thing) do
|
||||
def to_string(thing) do
|
||||
integer_to_binary(thing)
|
||||
end
|
||||
end
|
||||
|
||||
defimpl String.Chars, for: Float do
|
||||
@doc """
|
||||
Simply converts the float to a string.
|
||||
"""
|
||||
def to_string(thing) do
|
||||
iolist_to_binary(:io_lib_format.fwrite_g(thing))
|
||||
end
|
||||
|
||||
+58
-25
@@ -9,8 +9,8 @@ defmodule System do
|
||||
message: "could not get a current working directory, the current location is not accessible"
|
||||
|
||||
@moduledoc """
|
||||
The System module provides access to some variables used or
|
||||
maintained by the VM and to functions that interact strongly
|
||||
The System module provides access to variables used or
|
||||
maintained by the VM and to functions that interact directly
|
||||
with the VM or the host system.
|
||||
"""
|
||||
|
||||
@@ -57,13 +57,17 @@ defmodule System do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Elixir version information.
|
||||
|
||||
Returns Elixir's version as binary.
|
||||
"""
|
||||
@spec version() :: String.t
|
||||
def version, do: get_version
|
||||
|
||||
@doc """
|
||||
Returns a keywords list with version, git tag info and date.
|
||||
Elixir build information.
|
||||
|
||||
Returns a keyword list with Elixir version, git tag info and compilation date.
|
||||
"""
|
||||
@spec build_info() :: Keyword.t
|
||||
def build_info do
|
||||
@@ -71,7 +75,9 @@ defmodule System do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns the list of command-line arguments passed to the program.
|
||||
List command line arguments.
|
||||
|
||||
Returns the list of command line arguments passed to the program.
|
||||
"""
|
||||
@spec argv() :: [String.t]
|
||||
def argv do
|
||||
@@ -79,15 +85,19 @@ defmodule System do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Changes the list of command-line arguments. Use it with caution,
|
||||
as it destory any previous argv information.
|
||||
Modify command line arguments.
|
||||
|
||||
Changes the list of command line arguments. Use it with caution,
|
||||
as it destroys any previous argv information.
|
||||
"""
|
||||
@spec argv([String.t]) :: :ok
|
||||
def argv(argv) do
|
||||
:elixir_code_server.cast({ :argv, argv })
|
||||
def argv(args) do
|
||||
:elixir_code_server.cast({ :argv, args })
|
||||
end
|
||||
|
||||
@doc """
|
||||
Current working directory.
|
||||
|
||||
Returns the current working directory or `nil` if one
|
||||
is not available.
|
||||
"""
|
||||
@@ -99,6 +109,8 @@ defmodule System do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Current working directory, exception on error.
|
||||
|
||||
Returns the current working directory or raises `System.NoAccessCwdError`.
|
||||
"""
|
||||
def cwd! do
|
||||
@@ -106,8 +118,10 @@ defmodule System do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns the user home (platform independent).
|
||||
It returns `nil` if no user home is set.
|
||||
User home directory.
|
||||
|
||||
Returns the user home directory (platform independent).
|
||||
Returns `nil` if no user home is set.
|
||||
"""
|
||||
def user_home do
|
||||
case :os.type() do
|
||||
@@ -117,6 +131,8 @@ defmodule System do
|
||||
end
|
||||
|
||||
@doc """
|
||||
User home directory, exception on error.
|
||||
|
||||
Same as `user_home/0` but raises `System.NoHomeError`
|
||||
instead of returning `nil` if no user home is set.
|
||||
"""
|
||||
@@ -139,8 +155,10 @@ defmodule System do
|
||||
end
|
||||
|
||||
@doc %S"""
|
||||
Writable temporary directory.
|
||||
|
||||
Returns a writable temporary directory.
|
||||
It searches for directories in the following order:
|
||||
Searches for directories in the following order:
|
||||
|
||||
1. The directory named by the TMPDIR environment variable
|
||||
2. The directory named by the TEMP environment variable
|
||||
@@ -159,7 +177,9 @@ defmodule System do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Same as `tmp_dir` but raises `System.NoTmpDirError`
|
||||
Writable temporary directory, exception on error.
|
||||
|
||||
Same as `tmp_dir/0` but raises `System.NoTmpDirError`
|
||||
instead of returning `nil` if no temp dir is set.
|
||||
"""
|
||||
def tmp_dir! do
|
||||
@@ -189,18 +209,22 @@ defmodule System do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Register a program exit handler function.
|
||||
|
||||
Registers a function that will be invoked
|
||||
at the end of program execution. Useful for
|
||||
invoking a hook in a "script" mode.
|
||||
invoking a hook in "script" mode.
|
||||
|
||||
The function must expect the exit status code
|
||||
as argument.
|
||||
The function must receive the exit status code
|
||||
as an argument.
|
||||
"""
|
||||
def at_exit(fun) when is_function(fun, 1) do
|
||||
:elixir_code_server.cast { :at_exit, fun }
|
||||
end
|
||||
|
||||
@doc """
|
||||
Execute a system command.
|
||||
|
||||
Executes `command` in a command shell of the target OS,
|
||||
captures the standard output of the command and returns
|
||||
the result as a binary.
|
||||
@@ -222,6 +246,8 @@ defmodule System do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Locate an executable on the system.
|
||||
|
||||
This function looks up an executable program given
|
||||
its name using the environment variable PATH on Unix
|
||||
and Windows. It also considers the proper executable
|
||||
@@ -248,6 +274,8 @@ defmodule System do
|
||||
end
|
||||
|
||||
@doc """
|
||||
System environment variables.
|
||||
|
||||
Returns a list of all environment variables. Each variable is given as a
|
||||
`{name, value}` tuple where both `name` and `value` are strings.
|
||||
"""
|
||||
@@ -261,6 +289,8 @@ defmodule System do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Environment variable value.
|
||||
|
||||
Returns the value of the environment variable
|
||||
`varname` as a binary, or `nil` if the environment
|
||||
variable is undefined.
|
||||
@@ -274,6 +304,8 @@ defmodule System do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Erlang VM process identifier.
|
||||
|
||||
Returns the process identifier of the current Erlang emulator
|
||||
in the format most commonly used by the operating system environment.
|
||||
|
||||
@@ -283,6 +315,8 @@ defmodule System do
|
||||
def get_pid, do: iolist_to_binary(:os.getpid)
|
||||
|
||||
@doc """
|
||||
Set an environment variable value.
|
||||
|
||||
Sets a new `value` for the environment variable `varname`.
|
||||
"""
|
||||
@spec put_env(binary, binary) :: :ok
|
||||
@@ -292,6 +326,8 @@ defmodule System do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Set multiple environment variables.
|
||||
|
||||
Sets a new value for each environment variable corresponding
|
||||
to each key in `dict`.
|
||||
"""
|
||||
@@ -301,22 +337,24 @@ defmodule System do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Gets Elixir's stacktrace.
|
||||
Last exception stacktrace.
|
||||
|
||||
Notice the Erlang VM (and therefore this function) does not
|
||||
Note that the Erlang VM (and therefore this function) does not
|
||||
return the current stacktrace but rather the stacktrace of the
|
||||
latest exception.
|
||||
"""
|
||||
def stacktrace do
|
||||
filter_stacktrace :erlang.get_stacktrace
|
||||
:erlang.get_stacktrace
|
||||
end
|
||||
|
||||
@doc """
|
||||
Halts the Erlang runtime system where the first argument status must be a
|
||||
Halt the Erlang runtime system.
|
||||
|
||||
Halts the Erlang runtime system where the argument `status` must be a
|
||||
non-negative integer, the atom `:abort` or a binary.
|
||||
|
||||
* If an integer, the runtime system exits with the integer value which
|
||||
is returned to the Operating System;
|
||||
is returned to the operating system;
|
||||
|
||||
* If `:abort`, the runtime system aborts producing a core dump, if that is
|
||||
enabled in the operating system;
|
||||
@@ -347,9 +385,4 @@ defmodule System do
|
||||
def halt(status) when is_binary(status) do
|
||||
:erlang.halt(:binary.bin_to_list(status))
|
||||
end
|
||||
|
||||
## Helpers
|
||||
|
||||
defp filter_stacktrace([{ Kernel, :raise, _, _ }|t]), do: t
|
||||
defp filter_stacktrace(t), do: t
|
||||
end
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
defmodule Tuple do
|
||||
@moduledoc """
|
||||
Functions for working with tuples.
|
||||
"""
|
||||
|
||||
@doc """
|
||||
Create a new tuple.
|
||||
|
||||
Creates a tuple of size `size` containing the
|
||||
given `data` at every position.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Tuple.duplicate(:hello, 3)
|
||||
{ :hello, :hello, :hello }
|
||||
|
||||
"""
|
||||
@spec duplicate(term, non_neg_integer) :: tuple
|
||||
def duplicate(data, size) do
|
||||
:erlang.make_tuple(size, data)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Insert an element into a tuple.
|
||||
|
||||
Inserts `value` into `tuple` at the given zero-based `index`.
|
||||
Raises an `ArgumentError` if `index` is greater than the
|
||||
length of `tuple`.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> tuple = { :bar, :baz }
|
||||
...> Tuple.insert_at(tuple, 0, :foo)
|
||||
{ :foo, :bar, :baz }
|
||||
|
||||
"""
|
||||
@spec insert_at(tuple, non_neg_integer, term) :: tuple
|
||||
def insert_at(tuple, index, term) do
|
||||
:erlang.insert_element(index + 1, tuple, term)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Remove an element from a tuple.
|
||||
|
||||
Deletes the element at the zero-based `index` from `tuple`.
|
||||
Raises an `ArgumentError` if `index` is greater than
|
||||
or equal to the length of `tuple`.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> tuple = { :foo, :bar, :baz }
|
||||
...> Tuple.delete_at(tuple, 0)
|
||||
{ :bar, :baz }
|
||||
|
||||
"""
|
||||
@spec delete_at(tuple, non_neg_integer) :: tuple
|
||||
def delete_at(tuple, index) do
|
||||
:erlang.delete_element(index + 1, tuple)
|
||||
end
|
||||
end
|
||||
@@ -54,7 +54,7 @@ defmodule URI do
|
||||
that implements the `String.Chars` protocol (i.e. can be converted
|
||||
to a binary).
|
||||
"""
|
||||
def encode_query(l), do: Enum.map_join(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
|
||||
@@ -137,6 +137,7 @@ defmodule URI do
|
||||
def decode(<<>>), do: <<>>
|
||||
|
||||
defp hex2dec(n) when n in ?A..?F, do: n - ?A + 10
|
||||
defp hex2dec(n) when n in ?a..?f, do: n - ?a + 10
|
||||
defp hex2dec(n) when n in ?0..?9, do: n - ?0
|
||||
|
||||
defp check_plus(?+), do: 32
|
||||
|
||||
@@ -1,15 +1,13 @@
|
||||
defmodule Mix.Version do
|
||||
defmodule Version do
|
||||
@moduledoc %S"""
|
||||
This module provides functions for parsing and matching
|
||||
versions with requirements.
|
||||
Functions for parsing and matching versions against requirements.
|
||||
|
||||
A version is a string or a `Mix.Version.Schema` generated
|
||||
after parsing via `Mix.Version.parse/1`. A requirement is
|
||||
a string that follows a specific format.
|
||||
A version is a string in a specific format or a `Version.Schema`
|
||||
generated after parsing via `Version.parse/1`.
|
||||
|
||||
`Mix.Version` parsing and requirements follows
|
||||
`Version` parsing and requirements follow
|
||||
[SemVer 2.0 schema](http://semver.org/) and you will get
|
||||
the most of Mix' version system by following it. In order
|
||||
the most of Mix's version system by following it. In order
|
||||
to support integration with projects that may
|
||||
follow different versioning schemas, Elixir won't choke
|
||||
on unknown versions, however you won't be able to use
|
||||
@@ -33,7 +31,7 @@ defmodule Mix.Version do
|
||||
|
||||
Requirements allow you to specify which versions of a given
|
||||
dependency you are willing to work against. It supports common
|
||||
operators like `>=`, `<=`, `>`, `==` and friends that would
|
||||
operators like `>=`, `<=`, `>`, `==` and friends that
|
||||
work as one would expect:
|
||||
|
||||
# Only version 2.0.0
|
||||
@@ -54,8 +52,8 @@ defmodule Mix.Version do
|
||||
|
||||
"""
|
||||
|
||||
@type t :: String.t | Mix.Version.Schema.t
|
||||
@type requirement :: String.t | Mix.Version.Requirement.t
|
||||
@type t :: String.t | Version.Schema.t
|
||||
@type requirement :: String.t | Version.Requirement.t
|
||||
|
||||
@type matchable :: { major :: String.t | non_neg_integer,
|
||||
minor :: non_neg_integer | nil,
|
||||
@@ -79,11 +77,22 @@ defmodule Mix.Version do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Checks if the given version matches the specification.
|
||||
Check if the given version matches the specification.
|
||||
|
||||
Returns `true` if `version` satisfies `requirement`, `false` otherwise.
|
||||
Raises a `Version.InvalidRequirement` exception if `requirement` is not parseable.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Version.match?("2.0", ">1.0")
|
||||
true
|
||||
iex> Version.match?("2.0", "==1.0")
|
||||
false
|
||||
|
||||
"""
|
||||
@spec match?(t, requirement) :: boolean
|
||||
def match?(version, requirement) when is_binary(requirement) do
|
||||
case Mix.Version.Parser.parse_requirement(requirement) do
|
||||
case Version.Parser.parse_requirement(requirement) do
|
||||
{ :ok, req } ->
|
||||
match?(version, req)
|
||||
{ :error, reason } ->
|
||||
@@ -105,24 +114,38 @@ defmodule Mix.Version do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Checks if a version string is compatible with [semver](http://semver.org/).
|
||||
Check if a version string is compatible with [semver](http://semver.org/).
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Version.valid?("2.0")
|
||||
true
|
||||
iex> Version.valid?("invalid")
|
||||
false
|
||||
|
||||
"""
|
||||
@spec valid?(String.t | Schema.t) :: boolean
|
||||
def valid?(string) when is_binary(string) do
|
||||
Mix.Version.Parser.valid_version?(string)
|
||||
Version.Parser.valid_version?(string)
|
||||
end
|
||||
|
||||
def valid?(Mix.Version.Schema[major: nil]), do: false
|
||||
def valid?(Mix.Version.Schema[]), do: true
|
||||
def valid?(Version.Schema[major: nil]), do: false
|
||||
def valid?(Version.Schema[]), do: true
|
||||
|
||||
@doc """
|
||||
Parse a version into a matchable value.
|
||||
Parse a version string into a `Version.Schema`.
|
||||
|
||||
## Examples
|
||||
|
||||
> Version.parse("2.0.1-alpha1")
|
||||
#Version.Schema<2.0.1-alpha1>
|
||||
|
||||
"""
|
||||
@spec parse(String.t) :: { :ok, Schema.t } | { :error, term }
|
||||
@spec parse(String.t) :: Schema.t
|
||||
def parse(string) when is_binary(string) do
|
||||
case Mix.Version.Parser.parse_version(string) do
|
||||
case Version.Parser.parse_version(string) do
|
||||
{ :ok, matchable } -> from_matchable(matchable).source(string).build(get_build(string))
|
||||
{ :error, _ } -> Mix.Version.Schema[source: string]
|
||||
{ :error, _ } -> Version.Schema[source: string]
|
||||
end
|
||||
end
|
||||
|
||||
@@ -137,19 +160,25 @@ defmodule Mix.Version do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Get the matchable representation.
|
||||
Convert a version to a `Version.matchable`
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Version.to_matchable("2.0.1-alpha.1")
|
||||
{2, 0, 1, ["alpha", 1]}
|
||||
|
||||
"""
|
||||
@spec to_matchable(String.t | Schema.t) :: Mix.Version.matchable
|
||||
@spec to_matchable(String.t | Schema.t) :: Version.matchable
|
||||
def to_matchable(Schema[major: nil, source: source]) do
|
||||
{ source, nil, nil, [] }
|
||||
end
|
||||
|
||||
def to_matchable(Mix.Version.Schema[major: major, minor: minor, patch: patch, pre: nil]) do
|
||||
def to_matchable(Version.Schema[major: major, minor: minor, patch: patch, pre: nil]) do
|
||||
{ major, minor, patch, [] }
|
||||
end
|
||||
|
||||
def to_matchable(Mix.Version.Schema[major: major, minor: minor, patch: patch, pre: pre]) do
|
||||
{ major, minor, patch, Mix.Version.Parser.parse_pre(pre) }
|
||||
def to_matchable(Version.Schema[major: major, minor: minor, patch: patch, pre: pre]) do
|
||||
{ major, minor, patch, Version.Parser.parse_pre(pre) }
|
||||
end
|
||||
|
||||
def to_matchable(string) do
|
||||
@@ -157,11 +186,17 @@ defmodule Mix.Version do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Convert a matchable to a `Mix.Version`.
|
||||
Convert a matchable to a `Version.Schema`.
|
||||
|
||||
## Examples
|
||||
|
||||
> Version.from_matchable({2, 0, 1, ["alpha", 1]})
|
||||
#Version.Schema<2.0.1-alpha.1>
|
||||
|
||||
"""
|
||||
@spec from_matchable(Mix.Version.matchable) :: Schema.t
|
||||
@spec from_matchable(Version.matchable) :: Schema.t
|
||||
def from_matchable({ source, nil, nil, nil }) when is_binary(source) do
|
||||
Mix.Version.Schema[source: source]
|
||||
Version.Schema[source: source]
|
||||
end
|
||||
|
||||
def from_matchable({ major, minor, patch, pre }) do
|
||||
@@ -184,7 +219,7 @@ defmodule Mix.Version do
|
||||
end
|
||||
end
|
||||
|
||||
Mix.Version.Schema[major: major, minor: minor, patch: patch, pre: pre, source: source]
|
||||
Version.Schema[major: major, minor: minor, patch: patch, pre: pre, source: source]
|
||||
end
|
||||
|
||||
defmodule Parser.DSL do
|
||||
@@ -251,12 +286,12 @@ defmodule Mix.Version do
|
||||
end
|
||||
|
||||
deflexer acc do
|
||||
Enum.filter(Enum.reverse(acc), &1 != :' ')
|
||||
Enum.filter(Enum.reverse(acc), &(&1 != :' '))
|
||||
end
|
||||
|
||||
@version_regex %r/^(\d+)(?:\.(\d+)(?:\.(\d+))?)?(?:\-([^\s]+))?(?:\+[^\d]+)?$/
|
||||
|
||||
@spec parse_requirement(String.t) :: { :ok, Mix.Version.Requirement.t } | { :error, binary | atom }
|
||||
@spec parse_requirement(String.t) :: { :ok, Version.Requirement.t } | { :error, binary | atom }
|
||||
def parse_requirement(source) do
|
||||
lexed = lexer(source, [])
|
||||
|
||||
@@ -280,7 +315,7 @@ defmodule Mix.Version do
|
||||
defp nillify(""), do: nil
|
||||
defp nillify(o), do: o
|
||||
|
||||
@spec parse_version(String.t) :: { :ok, Mix.Version.matchable } | { :error, :invalid_version }
|
||||
@spec parse_version(String.t) :: { :ok, Version.matchable } | { :error, :invalid_version }
|
||||
def parse_version(string) when is_binary(string) do
|
||||
if valid_version?(string) do
|
||||
destructure [_, major, minor, patch, pre], Regex.run(@version_regex, string)
|
||||
@@ -364,7 +399,7 @@ defmodule Mix.Version do
|
||||
end
|
||||
|
||||
defp approximate(version) do
|
||||
Mix.Version.from_matchable(case Regex.run(@version_regex, version) do
|
||||
Version.from_matchable(case Regex.run(@version_regex, version) do
|
||||
[_, major] ->
|
||||
{ binary_to_integer(major) + 1, 0, 0, [] }
|
||||
|
||||
@@ -387,19 +422,19 @@ defmodule Mix.Version do
|
||||
end
|
||||
|
||||
defp to_condition([:'==', version | _]) do
|
||||
version = Mix.Version.to_matchable(version)
|
||||
version = Version.to_matchable(version)
|
||||
|
||||
{ :'==', :'$_', { :const, version } }
|
||||
end
|
||||
|
||||
defp to_condition([:'!=', version | _]) do
|
||||
version = Mix.Version.to_matchable(version)
|
||||
version = Version.to_matchable(version)
|
||||
|
||||
{ :'/=', :'$_', { :const, version } }
|
||||
end
|
||||
|
||||
defp to_condition([:'~>', version | _]) do
|
||||
from = Mix.Version.parse(version)
|
||||
from = Version.parse(version)
|
||||
to = approximate(version)
|
||||
|
||||
{ :andalso, to_condition([:'>=', to_string(from)]),
|
||||
@@ -407,7 +442,7 @@ defmodule Mix.Version do
|
||||
end
|
||||
|
||||
defp to_condition([:'>', version | _]) do
|
||||
{ major, minor, patch, pre } = Mix.Version.to_matchable(version)
|
||||
{ major, minor, patch, pre } = Version.to_matchable(version)
|
||||
|
||||
{ :andalso, { :not, { :is_binary, :'$1' } },
|
||||
{ :orelse, { :'>', {{ :'$1', :'$2', :'$3' }},
|
||||
@@ -423,7 +458,7 @@ defmodule Mix.Version do
|
||||
end
|
||||
|
||||
defp to_condition([:'>=', version | _]) do
|
||||
matchable = Mix.Version.to_matchable(version)
|
||||
matchable = Version.to_matchable(version)
|
||||
|
||||
{ :orelse, { :andalso, { :not, { :is_binary, :'$1' } },
|
||||
{ :'==', :'$_', { :const, matchable } } },
|
||||
@@ -431,7 +466,7 @@ defmodule Mix.Version do
|
||||
end
|
||||
|
||||
defp to_condition([:'<', version | _]) do
|
||||
{ major, minor, patch, pre } = Mix.Version.to_matchable(version)
|
||||
{ major, minor, patch, pre } = Version.to_matchable(version)
|
||||
|
||||
{ :andalso, { :not, { :is_binary, :'$1' } },
|
||||
{ :orelse, { :'<', {{ :'$1', :'$2', :'$3' }},
|
||||
@@ -447,7 +482,7 @@ defmodule Mix.Version do
|
||||
end
|
||||
|
||||
defp to_condition([:'<=', version | _]) do
|
||||
matchable = Mix.Version.to_matchable(version)
|
||||
matchable = Version.to_matchable(version)
|
||||
|
||||
{ :orelse, { :andalso, { :not, { :is_binary, :'$1' } },
|
||||
{ :'==', :'$_', { :const, matchable } } },
|
||||
@@ -468,26 +503,26 @@ defmodule Mix.Version do
|
||||
end
|
||||
end
|
||||
|
||||
defimpl String.Chars, for: Mix.Version.Schema do
|
||||
def to_string(Mix.Version.Schema[source: source]) do
|
||||
defimpl String.Chars, for: Version.Schema do
|
||||
def to_string(Version.Schema[source: source]) do
|
||||
source
|
||||
end
|
||||
end
|
||||
|
||||
defimpl Inspect, for: Mix.Version.Schema do
|
||||
defimpl Inspect, for: Version.Schema do
|
||||
def inspect(self, _opts) do
|
||||
"#Mix.Version.Schema<" <> to_string(self) <> ">"
|
||||
"#Version.Schema<" <> to_string(self) <> ">"
|
||||
end
|
||||
end
|
||||
|
||||
defimpl String.Chars, for: Mix.Version.Requirement do
|
||||
defimpl String.Chars, for: Version.Requirement do
|
||||
def to_string({ _, source, _ }) do
|
||||
source
|
||||
end
|
||||
end
|
||||
|
||||
defimpl Inspect, for: Mix.Version.Requirement do
|
||||
defimpl Inspect, for: Version.Requirement do
|
||||
def inspect({ _, source, _ }, _opts) do
|
||||
"#Mix.Version.Requirement<" <> source <> ">"
|
||||
"#Version.Requirement<" <> source <> ">"
|
||||
end
|
||||
end
|
||||
@@ -1,420 +0,0 @@
|
||||
LATIN CAPITAL LETTER A WITH MACRON AND GRAVE;0100 0300
|
||||
LATIN SMALL LETTER A WITH MACRON AND GRAVE;0101 0300
|
||||
LATIN CAPITAL LETTER E WITH VERTICAL LINE BELOW;0045 0329
|
||||
LATIN SMALL LETTER E WITH VERTICAL LINE BELOW;0065 0329
|
||||
LATIN CAPITAL LETTER E WITH VERTICAL LINE BELOW AND GRAVE;00C8 0329
|
||||
LATIN SMALL LETTER E WITH VERTICAL LINE BELOW AND GRAVE;00E8 0329
|
||||
LATIN CAPITAL LETTER E WITH VERTICAL LINE BELOW AND ACUTE;00C9 0329
|
||||
LATIN SMALL LETTER E WITH VERTICAL LINE BELOW AND ACUTE;00E9 0329
|
||||
LATIN CAPITAL LETTER E WITH CIRCUMFLEX AND MACRON;00CA 0304
|
||||
LATIN SMALL LETTER E WITH CIRCUMFLEX AND MACRON;00EA 0304
|
||||
LATIN CAPITAL LETTER E WITH CIRCUMFLEX AND CARON;00CA 030C
|
||||
LATIN SMALL LETTER E WITH CIRCUMFLEX AND CARON;00EA 030C
|
||||
LATIN CAPITAL LETTER I WITH MACRON AND GRAVE;012A 0300
|
||||
LATIN SMALL LETTER I WITH MACRON AND GRAVE;012B 0300
|
||||
LATIN SMALL LETTER I WITH DOT ABOVE AND ACUTE;0069 0307 0301
|
||||
LATIN SMALL LETTER NG WITH TILDE ABOVE;006E 0360 0067
|
||||
LATIN CAPITAL LETTER O WITH VERTICAL LINE BELOW;004F 0329
|
||||
LATIN SMALL LETTER O WITH VERTICAL LINE BELOW;006F 0329
|
||||
LATIN CAPITAL LETTER O WITH VERTICAL LINE BELOW AND GRAVE;00D2 0329
|
||||
LATIN SMALL LETTER O WITH VERTICAL LINE BELOW AND GRAVE;00F2 0329
|
||||
LATIN CAPITAL LETTER O WITH VERTICAL LINE BELOW AND ACUTE;00D3 0329
|
||||
LATIN SMALL LETTER O WITH VERTICAL LINE BELOW AND ACUTE;00F3 0329
|
||||
LATIN CAPITAL LETTER S WITH VERTICAL LINE BELOW;0053 0329
|
||||
LATIN SMALL LETTER S WITH VERTICAL LINE BELOW;0073 0329
|
||||
LATIN CAPITAL LETTER U WITH MACRON AND GRAVE;016A 0300
|
||||
LATIN SMALL LETTER U WITH MACRON AND GRAVE;016B 0300
|
||||
LATIN CAPITAL LETTER A WITH OGONEK AND ACUTE;0104 0301
|
||||
LATIN SMALL LETTER A WITH OGONEK AND ACUTE;0105 0301
|
||||
LATIN CAPITAL LETTER A WITH OGONEK AND TILDE;0104 0303
|
||||
LATIN SMALL LETTER A WITH OGONEK AND TILDE;0105 0303
|
||||
LATIN CAPITAL LETTER E WITH OGONEK AND ACUTE;0118 0301
|
||||
LATIN SMALL LETTER E WITH OGONEK AND ACUTE;0119 0301
|
||||
LATIN CAPITAL LETTER E WITH OGONEK AND TILDE;0118 0303
|
||||
LATIN SMALL LETTER E WITH OGONEK AND TILDE;0119 0303
|
||||
LATIN CAPITAL LETTER E WITH DOT ABOVE AND ACUTE;0116 0301
|
||||
LATIN SMALL LETTER E WITH DOT ABOVE AND ACUTE;0117 0301
|
||||
LATIN CAPITAL LETTER E WITH DOT ABOVE AND TILDE;0116 0303
|
||||
LATIN SMALL LETTER E WITH DOT ABOVE AND TILDE;0117 0303
|
||||
LATIN SMALL LETTER I WITH DOT ABOVE AND GRAVE;0069 0307 0300
|
||||
LATIN SMALL LETTER I WITH DOT ABOVE AND TILDE;0069 0307 0303
|
||||
LATIN CAPITAL LETTER I WITH OGONEK AND ACUTE;012E 0301
|
||||
LATIN SMALL LETTER I WITH OGONEK AND DOT ABOVE AND ACUTE;012F 0307 0301
|
||||
LATIN CAPITAL LETTER I WITH OGONEK AND TILDE;012E 0303
|
||||
LATIN SMALL LETTER I WITH OGONEK AND DOT ABOVE AND TILDE;012F 0307 0303
|
||||
LATIN CAPITAL LETTER J WITH TILDE;004A 0303
|
||||
LATIN SMALL LETTER J WITH DOT ABOVE AND TILDE;006A 0307 0303
|
||||
LATIN CAPITAL LETTER L WITH TILDE;004C 0303
|
||||
LATIN SMALL LETTER L WITH TILDE;006C 0303
|
||||
LATIN CAPITAL LETTER M WITH TILDE;004D 0303
|
||||
LATIN SMALL LETTER M WITH TILDE;006D 0303
|
||||
LATIN CAPITAL LETTER R WITH TILDE;0052 0303
|
||||
LATIN SMALL LETTER R WITH TILDE;0072 0303
|
||||
LATIN CAPITAL LETTER U WITH OGONEK AND ACUTE;0172 0301
|
||||
LATIN SMALL LETTER U WITH OGONEK AND ACUTE;0173 0301
|
||||
LATIN CAPITAL LETTER U WITH OGONEK AND TILDE;0172 0303
|
||||
LATIN SMALL LETTER U WITH OGONEK AND TILDE;0173 0303
|
||||
LATIN CAPITAL LETTER U WITH MACRON AND ACUTE;016A 0301
|
||||
LATIN SMALL LETTER U WITH MACRON AND ACUTE;016B 0301
|
||||
LATIN CAPITAL LETTER U WITH MACRON AND TILDE;016A 0303
|
||||
LATIN SMALL LETTER U WITH MACRON AND TILDE;016B 0303
|
||||
LATIN SMALL LETTER AE WITH GRAVE;00E6 0300
|
||||
LATIN SMALL LETTER OPEN O WITH GRAVE;0254 0300
|
||||
LATIN SMALL LETTER OPEN O WITH ACUTE;0254 0301
|
||||
LATIN SMALL LETTER TURNED V WITH GRAVE;028C 0300
|
||||
LATIN SMALL LETTER TURNED V WITH ACUTE;028C 0301
|
||||
LATIN SMALL LETTER SCHWA WITH GRAVE;0259 0300
|
||||
LATIN SMALL LETTER SCHWA WITH ACUTE;0259 0301
|
||||
LATIN SMALL LETTER HOOKED SCHWA WITH GRAVE;025A 0300
|
||||
LATIN SMALL LETTER HOOKED SCHWA WITH ACUTE;025A 0301
|
||||
BENGALI LETTER KHINYA;0995 09CD 09B7
|
||||
TAMIL CONSONANT K; 0B95 0BCD
|
||||
TAMIL CONSONANT NG; 0B99 0BCD
|
||||
TAMIL CONSONANT C; 0B9A 0BCD
|
||||
TAMIL CONSONANT NY; 0B9E 0BCD
|
||||
TAMIL CONSONANT TT; 0B9F 0BCD
|
||||
TAMIL CONSONANT NN; 0BA3 0BCD
|
||||
TAMIL CONSONANT T; 0BA4 0BCD
|
||||
TAMIL CONSONANT N; 0BA8 0BCD
|
||||
TAMIL CONSONANT P; 0BAA 0BCD
|
||||
TAMIL CONSONANT M; 0BAE 0BCD
|
||||
TAMIL CONSONANT Y; 0BAF 0BCD
|
||||
TAMIL CONSONANT R; 0BB0 0BCD
|
||||
TAMIL CONSONANT L; 0BB2 0BCD
|
||||
TAMIL CONSONANT V; 0BB5 0BCD
|
||||
TAMIL CONSONANT LLL;0BB4 0BCD
|
||||
TAMIL CONSONANT LL; 0BB3 0BCD
|
||||
TAMIL CONSONANT RR; 0BB1 0BCD
|
||||
TAMIL CONSONANT NNN;0BA9 0BCD
|
||||
TAMIL CONSONANT J; 0B9C 0BCD
|
||||
TAMIL CONSONANT SH; 0BB6 0BCD
|
||||
TAMIL CONSONANT SS; 0BB7 0BCD
|
||||
TAMIL CONSONANT S; 0BB8 0BCD
|
||||
TAMIL CONSONANT H; 0BB9 0BCD
|
||||
TAMIL CONSONANT KSS;0B95 0BCD 0BB7 0BCD
|
||||
TAMIL SYLLABLE KAA; 0B95 0BBE
|
||||
TAMIL SYLLABLE KI; 0B95 0BBF
|
||||
TAMIL SYLLABLE KII; 0B95 0BC0
|
||||
TAMIL SYLLABLE KU; 0B95 0BC1
|
||||
TAMIL SYLLABLE KUU; 0B95 0BC2
|
||||
TAMIL SYLLABLE KE; 0B95 0BC6
|
||||
TAMIL SYLLABLE KEE; 0B95 0BC7
|
||||
TAMIL SYLLABLE KAI; 0B95 0BC8
|
||||
TAMIL SYLLABLE KO; 0B95 0BCA
|
||||
TAMIL SYLLABLE KOO; 0B95 0BCB
|
||||
TAMIL SYLLABLE KAU; 0B95 0BCC
|
||||
TAMIL SYLLABLE NGAA; 0B99 0BBE
|
||||
TAMIL SYLLABLE NGI; 0B99 0BBF
|
||||
TAMIL SYLLABLE NGII; 0B99 0BC0
|
||||
TAMIL SYLLABLE NGU; 0B99 0BC1
|
||||
TAMIL SYLLABLE NGUU; 0B99 0BC2
|
||||
TAMIL SYLLABLE NGE; 0B99 0BC6
|
||||
TAMIL SYLLABLE NGEE; 0B99 0BC7
|
||||
TAMIL SYLLABLE NGAI; 0B99 0BC8
|
||||
TAMIL SYLLABLE NGO; 0B99 0BCA
|
||||
TAMIL SYLLABLE NGOO; 0B99 0BCB
|
||||
TAMIL SYLLABLE NGAU; 0B99 0BCC
|
||||
TAMIL SYLLABLE CI; 0B9A 0BBF
|
||||
TAMIL SYLLABLE CII; 0B9A 0BC0
|
||||
TAMIL SYLLABLE CU; 0B9A 0BC1
|
||||
TAMIL SYLLABLE CUU; 0B9A 0BC2
|
||||
TAMIL SYLLABLE CE; 0B9A 0BC6
|
||||
TAMIL SYLLABLE CEE; 0B9A 0BC7
|
||||
TAMIL SYLLABLE CAI; 0B9A 0BC8
|
||||
TAMIL SYLLABLE CO; 0B9A 0BCA
|
||||
TAMIL SYLLABLE COO; 0B9A 0BCB
|
||||
TAMIL SYLLABLE CAU; 0B9A 0BCC
|
||||
TAMIL SYLLABLE NYAA; 0B9E 0BBE
|
||||
TAMIL SYLLABLE NYI; 0B9E 0BBF
|
||||
TAMIL SYLLABLE NYII; 0B9E 0BC0
|
||||
TAMIL SYLLABLE NYU; 0B9E 0BC1
|
||||
TAMIL SYLLABLE NYUU; 0B9E 0BC2
|
||||
TAMIL SYLLABLE NYE; 0B9E 0BC6
|
||||
TAMIL SYLLABLE NYEE; 0B9E 0BC7
|
||||
TAMIL SYLLABLE NYAI; 0B9E 0BC8
|
||||
TAMIL SYLLABLE NYO; 0B9E 0BCA
|
||||
TAMIL SYLLABLE NYOO; 0B9E 0BCB
|
||||
TAMIL SYLLABLE NYAU; 0B9E 0BCC
|
||||
TAMIL SYLLABLE TTAA; 0B9F 0BBE
|
||||
TAMIL SYLLABLE TTI; 0B9F 0BBF
|
||||
TAMIL SYLLABLE TTII; 0B9F 0BC0
|
||||
TAMIL SYLLABLE TTU; 0B9F 0BC1
|
||||
TAMIL SYLLABLE TTUU; 0B9F 0BC2
|
||||
TAMIL SYLLABLE TTE; 0B9F 0BC6
|
||||
TAMIL SYLLABLE TTEE; 0B9F 0BC7
|
||||
TAMIL SYLLABLE TTAI; 0B9F 0BC8
|
||||
TAMIL SYLLABLE TTO; 0B9F 0BCA
|
||||
TAMIL SYLLABLE TTOO; 0B9F 0BCB
|
||||
TAMIL SYLLABLE TTAU; 0B9F 0BCC
|
||||
TAMIL SYLLABLE NNAA; 0BA3 0BBE
|
||||
TAMIL SYLLABLE NNI; 0BA3 0BBF
|
||||
TAMIL SYLLABLE NNII; 0BA3 0BC0
|
||||
TAMIL SYLLABLE NNU; 0BA3 0BC1
|
||||
TAMIL SYLLABLE NNUU; 0BA3 0BC2
|
||||
TAMIL SYLLABLE NNE; 0BA3 0BC6
|
||||
TAMIL SYLLABLE NNEE; 0BA3 0BC7
|
||||
TAMIL SYLLABLE NNAI; 0BA3 0BC8
|
||||
TAMIL SYLLABLE NNO; 0BA3 0BCA
|
||||
TAMIL SYLLABLE NNOO; 0BA3 0BCB
|
||||
TAMIL SYLLABLE NNAU; 0BA3 0BCC
|
||||
TAMIL SYLLABLE TAA; 0BA4 0BBE
|
||||
TAMIL SYLLABLE TI; 0BA4 0BBF
|
||||
TAMIL SYLLABLE TII; 0BA4 0BC0
|
||||
TAMIL SYLLABLE TU; 0BA4 0BC1
|
||||
TAMIL SYLLABLE TUU; 0BA4 0BC2
|
||||
TAMIL SYLLABLE TE; 0BA4 0BC6
|
||||
TAMIL SYLLABLE TEE; 0BA4 0BC7
|
||||
TAMIL SYLLABLE TAI; 0BA4 0BC8
|
||||
TAMIL SYLLABLE TO; 0BA4 0BCA
|
||||
TAMIL SYLLABLE TOO; 0BA4 0BCB
|
||||
TAMIL SYLLABLE TAU; 0BA4 0BCC
|
||||
TAMIL SYLLABLE NAA; 0BA8 0BBE
|
||||
TAMIL SYLLABLE NI; 0BA8 0BBF
|
||||
TAMIL SYLLABLE NII; 0BA8 0BC0
|
||||
TAMIL SYLLABLE NU; 0BA8 0BC1
|
||||
TAMIL SYLLABLE NUU; 0BA8 0BC2
|
||||
TAMIL SYLLABLE NE; 0BA8 0BC6
|
||||
TAMIL SYLLABLE NEE; 0BA8 0BC7
|
||||
TAMIL SYLLABLE NAI; 0BA8 0BC8
|
||||
TAMIL SYLLABLE NO; 0BA8 0BCA
|
||||
TAMIL SYLLABLE NOO; 0BA8 0BCB
|
||||
TAMIL SYLLABLE NAU; 0BA8 0BCC
|
||||
TAMIL SYLLABLE PAA; 0BAA 0BBE
|
||||
TAMIL SYLLABLE PI; 0BAA 0BBF
|
||||
TAMIL SYLLABLE PII; 0BAA 0BC0
|
||||
TAMIL SYLLABLE PU; 0BAA 0BC1
|
||||
TAMIL SYLLABLE PUU; 0BAA 0BC2
|
||||
TAMIL SYLLABLE PE; 0BAA 0BC6
|
||||
TAMIL SYLLABLE PEE; 0BAA 0BC7
|
||||
TAMIL SYLLABLE PAI; 0BAA 0BC8
|
||||
TAMIL SYLLABLE PO; 0BAA 0BCA
|
||||
TAMIL SYLLABLE POO; 0BAA 0BCB
|
||||
TAMIL SYLLABLE PAU; 0BAA 0BCC
|
||||
TAMIL SYLLABLE MAA; 0BAE 0BBE
|
||||
TAMIL SYLLABLE MI; 0BAE 0BBF
|
||||
TAMIL SYLLABLE MII; 0BAE 0BC0
|
||||
TAMIL SYLLABLE MU; 0BAE 0BC1
|
||||
TAMIL SYLLABLE MUU; 0BAE 0BC2
|
||||
TAMIL SYLLABLE ME; 0BAE 0BC6
|
||||
TAMIL SYLLABLE MEE; 0BAE 0BC7
|
||||
TAMIL SYLLABLE MAI; 0BAE 0BC8
|
||||
TAMIL SYLLABLE MO; 0BAE 0BCA
|
||||
TAMIL SYLLABLE MOO; 0BAE 0BCB
|
||||
TAMIL SYLLABLE MAU; 0BAE 0BCC
|
||||
TAMIL SYLLABLE YAA; 0BAF 0BBE
|
||||
TAMIL SYLLABLE YI; 0BAF 0BBF
|
||||
TAMIL SYLLABLE YII; 0BAF 0BC0
|
||||
TAMIL SYLLABLE YU; 0BAF 0BC1
|
||||
TAMIL SYLLABLE YUU; 0BAF 0BC2
|
||||
TAMIL SYLLABLE YE; 0BAF 0BC6
|
||||
TAMIL SYLLABLE YEE; 0BAF 0BC7
|
||||
TAMIL SYLLABLE YAI; 0BAF 0BC8
|
||||
TAMIL SYLLABLE YO; 0BAF 0BCA
|
||||
TAMIL SYLLABLE YOO; 0BAF 0BCB
|
||||
TAMIL SYLLABLE YAU; 0BAF 0BCC
|
||||
TAMIL SYLLABLE RAA; 0BB0 0BBE
|
||||
TAMIL SYLLABLE RI; 0BB0 0BBF
|
||||
TAMIL SYLLABLE RII; 0BB0 0BC0
|
||||
TAMIL SYLLABLE RU; 0BB0 0BC1
|
||||
TAMIL SYLLABLE RUU; 0BB0 0BC2
|
||||
TAMIL SYLLABLE RE; 0BB0 0BC6
|
||||
TAMIL SYLLABLE REE; 0BB0 0BC7
|
||||
TAMIL SYLLABLE RAI; 0BB0 0BC8
|
||||
TAMIL SYLLABLE RO; 0BB0 0BCA
|
||||
TAMIL SYLLABLE ROO; 0BB0 0BCB
|
||||
TAMIL SYLLABLE RAU; 0BB0 0BCC
|
||||
TAMIL SYLLABLE LAA; 0BB2 0BBE
|
||||
TAMIL SYLLABLE LI; 0BB2 0BBF
|
||||
TAMIL SYLLABLE LII; 0BB2 0BC0
|
||||
TAMIL SYLLABLE LU; 0BB2 0BC1
|
||||
TAMIL SYLLABLE LUU; 0BB2 0BC2
|
||||
TAMIL SYLLABLE LE; 0BB2 0BC6
|
||||
TAMIL SYLLABLE LEE; 0BB2 0BC7
|
||||
TAMIL SYLLABLE LAI; 0BB2 0BC8
|
||||
TAMIL SYLLABLE LO; 0BB2 0BCA
|
||||
TAMIL SYLLABLE LOO; 0BB2 0BCB
|
||||
TAMIL SYLLABLE LAU; 0BB2 0BCC
|
||||
TAMIL SYLLABLE VAA; 0BB5 0BBE
|
||||
TAMIL SYLLABLE VI; 0BB5 0BBF
|
||||
TAMIL SYLLABLE VII; 0BB5 0BC0
|
||||
TAMIL SYLLABLE VU; 0BB5 0BC1
|
||||
TAMIL SYLLABLE VUU; 0BB5 0BC2
|
||||
TAMIL SYLLABLE VE; 0BB5 0BC6
|
||||
TAMIL SYLLABLE VEE; 0BB5 0BC7
|
||||
TAMIL SYLLABLE VAI; 0BB5 0BC8
|
||||
TAMIL SYLLABLE VO; 0BB5 0BCA
|
||||
TAMIL SYLLABLE VOO; 0BB5 0BCB
|
||||
TAMIL SYLLABLE VAU; 0BB5 0BCC
|
||||
TAMIL SYLLABLE LLLAA; 0BB4 0BBE
|
||||
TAMIL SYLLABLE LLLI; 0BB4 0BBF
|
||||
TAMIL SYLLABLE LLLII; 0BB4 0BC0
|
||||
TAMIL SYLLABLE LLLU; 0BB4 0BC1
|
||||
TAMIL SYLLABLE LLLUU; 0BB4 0BC2
|
||||
TAMIL SYLLABLE LLLE; 0BB4 0BC6
|
||||
TAMIL SYLLABLE LLLEE; 0BB4 0BC7
|
||||
TAMIL SYLLABLE LLLAI; 0BB4 0BC8
|
||||
TAMIL SYLLABLE LLLO; 0BB4 0BCA
|
||||
TAMIL SYLLABLE LLLOO; 0BB4 0BCB
|
||||
TAMIL SYLLABLE LLLAU; 0BB4 0BCC
|
||||
TAMIL SYLLABLE LLAA; 0BB3 0BBE
|
||||
TAMIL SYLLABLE LLI; 0BB3 0BBF
|
||||
TAMIL SYLLABLE LLII; 0BB3 0BC0
|
||||
TAMIL SYLLABLE LLU; 0BB3 0BC1
|
||||
TAMIL SYLLABLE LLUU; 0BB3 0BC2
|
||||
TAMIL SYLLABLE LLE; 0BB3 0BC6
|
||||
TAMIL SYLLABLE LLEE; 0BB3 0BC7
|
||||
TAMIL SYLLABLE LLAI; 0BB3 0BC8
|
||||
TAMIL SYLLABLE LLO; 0BB3 0BCA
|
||||
TAMIL SYLLABLE LLOO; 0BB3 0BCB
|
||||
TAMIL SYLLABLE LLAU; 0BB3 0BCC
|
||||
TAMIL SYLLABLE RRAA; 0BB1 0BBE
|
||||
TAMIL SYLLABLE RRI; 0BB1 0BBF
|
||||
TAMIL SYLLABLE RRII; 0BB1 0BC0
|
||||
TAMIL SYLLABLE RRU; 0BB1 0BC1
|
||||
TAMIL SYLLABLE RRUU; 0BB1 0BC2
|
||||
TAMIL SYLLABLE RRE; 0BB1 0BC6
|
||||
TAMIL SYLLABLE RREE; 0BB1 0BC7
|
||||
TAMIL SYLLABLE RRAI; 0BB1 0BC8
|
||||
TAMIL SYLLABLE RRO; 0BB1 0BCA
|
||||
TAMIL SYLLABLE RROO; 0BB1 0BCB
|
||||
TAMIL SYLLABLE RRAU; 0BB1 0BCC
|
||||
TAMIL SYLLABLE NNNAA; 0BA9 0BBE
|
||||
TAMIL SYLLABLE NNNI; 0BA9 0BBF
|
||||
TAMIL SYLLABLE NNNII; 0BA9 0BC0
|
||||
TAMIL SYLLABLE NNNU; 0BA9 0BC1
|
||||
TAMIL SYLLABLE NNNUU; 0BA9 0BC2
|
||||
TAMIL SYLLABLE NNNE; 0BA9 0BC6
|
||||
TAMIL SYLLABLE NNNEE; 0BA9 0BC7
|
||||
TAMIL SYLLABLE NNNAI; 0BA9 0BC8
|
||||
TAMIL SYLLABLE NNNO; 0BA9 0BCA
|
||||
TAMIL SYLLABLE NNNOO; 0BA9 0BCB
|
||||
TAMIL SYLLABLE NNNAU; 0BA9 0BCC
|
||||
TAMIL SYLLABLE JAA; 0B9C 0BBE
|
||||
TAMIL SYLLABLE JI; 0B9C 0BBF
|
||||
TAMIL SYLLABLE JII; 0B9C 0BC0
|
||||
TAMIL SYLLABLE JU; 0B9C 0BC1
|
||||
TAMIL SYLLABLE JUU; 0B9C 0BC2
|
||||
TAMIL SYLLABLE JE; 0B9C 0BC6
|
||||
TAMIL SYLLABLE JEE; 0B9C 0BC7
|
||||
TAMIL SYLLABLE JAI; 0B9C 0BC8
|
||||
TAMIL SYLLABLE JO; 0B9C 0BCA
|
||||
TAMIL SYLLABLE JOO; 0B9C 0BCB
|
||||
TAMIL SYLLABLE JAU; 0B9C 0BCC
|
||||
TAMIL SYLLABLE SHAA; 0BB6 0BBE
|
||||
TAMIL SYLLABLE SHI; 0BB6 0BBF
|
||||
TAMIL SYLLABLE SHII; 0BB6 0BC0
|
||||
TAMIL SYLLABLE SHU; 0BB6 0BC1
|
||||
TAMIL SYLLABLE SHUU; 0BB6 0BC2
|
||||
TAMIL SYLLABLE SHE; 0BB6 0BC6
|
||||
TAMIL SYLLABLE SHEE; 0BB6 0BC7
|
||||
TAMIL SYLLABLE SHAI; 0BB6 0BC8
|
||||
TAMIL SYLLABLE SHO; 0BB6 0BCA
|
||||
TAMIL SYLLABLE SHOO; 0BB6 0BCB
|
||||
TAMIL SYLLABLE SHAU; 0BB6 0BCC
|
||||
TAMIL SYLLABLE SSAA; 0BB7 0BBE
|
||||
TAMIL SYLLABLE SSI; 0BB7 0BBF
|
||||
TAMIL SYLLABLE SSII; 0BB7 0BC0
|
||||
TAMIL SYLLABLE SSU; 0BB7 0BC1
|
||||
TAMIL SYLLABLE SSUU; 0BB7 0BC2
|
||||
TAMIL SYLLABLE SSE; 0BB7 0BC6
|
||||
TAMIL SYLLABLE SSEE; 0BB7 0BC7
|
||||
TAMIL SYLLABLE SSAI; 0BB7 0BC8
|
||||
TAMIL SYLLABLE SSO; 0BB7 0BCA
|
||||
TAMIL SYLLABLE SSOO; 0BB7 0BCB
|
||||
TAMIL SYLLABLE SSAU; 0BB7 0BCC
|
||||
TAMIL SYLLABLE SAA; 0BB8 0BBE
|
||||
TAMIL SYLLABLE SI; 0BB8 0BBF
|
||||
TAMIL SYLLABLE SII; 0BB8 0BC0
|
||||
TAMIL SYLLABLE SU; 0BB8 0BC1
|
||||
TAMIL SYLLABLE SUU; 0BB8 0BC2
|
||||
TAMIL SYLLABLE SE; 0BB8 0BC6
|
||||
TAMIL SYLLABLE SEE; 0BB8 0BC7
|
||||
TAMIL SYLLABLE SAI; 0BB8 0BC8
|
||||
TAMIL SYLLABLE SO; 0BB8 0BCA
|
||||
TAMIL SYLLABLE SOO; 0BB8 0BCB
|
||||
TAMIL SYLLABLE SAU; 0BB8 0BCC
|
||||
TAMIL SYLLABLE HAA; 0BB9 0BBE
|
||||
TAMIL SYLLABLE HI; 0BB9 0BBF
|
||||
TAMIL SYLLABLE HII; 0BB9 0BC0
|
||||
TAMIL SYLLABLE HU; 0BB9 0BC1
|
||||
TAMIL SYLLABLE HUU; 0BB9 0BC2
|
||||
TAMIL SYLLABLE HE; 0BB9 0BC6
|
||||
TAMIL SYLLABLE HEE; 0BB9 0BC7
|
||||
TAMIL SYLLABLE HAI; 0BB9 0BC8
|
||||
TAMIL SYLLABLE HO; 0BB9 0BCA
|
||||
TAMIL SYLLABLE HOO; 0BB9 0BCB
|
||||
TAMIL SYLLABLE HAU; 0BB9 0BCC
|
||||
TAMIL SYLLABLE KSSA; 0B95 0BCD 0BB7
|
||||
TAMIL SYLLABLE KSSAA; 0B95 0BCD 0BB7 0BBE
|
||||
TAMIL SYLLABLE KSSI; 0B95 0BCD 0BB7 0BBF
|
||||
TAMIL SYLLABLE KSSII; 0B95 0BCD 0BB7 0BC0
|
||||
TAMIL SYLLABLE KSSU; 0B95 0BCD 0BB7 0BC1
|
||||
TAMIL SYLLABLE KSSUU; 0B95 0BCD 0BB7 0BC2
|
||||
TAMIL SYLLABLE KSSE; 0B95 0BCD 0BB7 0BC6
|
||||
TAMIL SYLLABLE KSSEE; 0B95 0BCD 0BB7 0BC7
|
||||
TAMIL SYLLABLE KSSAI; 0B95 0BCD 0BB7 0BC8
|
||||
TAMIL SYLLABLE KSSO; 0B95 0BCD 0BB7 0BCA
|
||||
TAMIL SYLLABLE KSSOO; 0B95 0BCD 0BB7 0BCB
|
||||
TAMIL SYLLABLE KSSAU; 0B95 0BCD 0BB7 0BCC
|
||||
TAMIL SYLLABLE SHRII; 0BB6 0BCD 0BB0 0BC0
|
||||
SINHALA CONSONANT SIGN YANSAYA;0DCA 200D 0DBA
|
||||
SINHALA CONSONANT SIGN RAKAARAANSAYA;0DCA 200D 0DBB
|
||||
SINHALA CONSONANT SIGN REPAYA;0DBB 0DCA 200D
|
||||
GEORGIAN LETTER U-BRJGU;10E3 0302
|
||||
KHMER CONSONANT SIGN COENG KA;17D2 1780
|
||||
KHMER CONSONANT SIGN COENG KHA;17D2 1781
|
||||
KHMER CONSONANT SIGN COENG KO;17D2 1782
|
||||
KHMER CONSONANT SIGN COENG KHO;17D2 1783
|
||||
KHMER CONSONANT SIGN COENG NGO;17D2 1784
|
||||
KHMER CONSONANT SIGN COENG CA;17D2 1785
|
||||
KHMER CONSONANT SIGN COENG CHA;17D2 1786
|
||||
KHMER CONSONANT SIGN COENG CO;17D2 1787
|
||||
KHMER CONSONANT SIGN COENG CHO;17D2 1788
|
||||
KHMER CONSONANT SIGN COENG NYO;17D2 1789
|
||||
KHMER CONSONANT SIGN COENG DA;17D2 178A
|
||||
KHMER CONSONANT SIGN COENG TTHA;17D2 178B
|
||||
KHMER CONSONANT SIGN COENG DO;17D2 178C
|
||||
KHMER CONSONANT SIGN COENG TTHO;17D2 178D
|
||||
KHMER CONSONANT SIGN COENG NA;17D2 178E
|
||||
KHMER CONSONANT SIGN COENG TA;17D2 178F
|
||||
KHMER CONSONANT SIGN COENG THA;17D2 1790
|
||||
KHMER CONSONANT SIGN COENG TO;17D2 1791
|
||||
KHMER CONSONANT SIGN COENG THO;17D2 1792
|
||||
KHMER CONSONANT SIGN COENG NO;17D2 1793
|
||||
KHMER CONSONANT SIGN COENG BA;17D2 1794
|
||||
KHMER CONSONANT SIGN COENG PHA;17D2 1795
|
||||
KHMER CONSONANT SIGN COENG PO;17D2 1796
|
||||
KHMER CONSONANT SIGN COENG PHO;17D2 1797
|
||||
KHMER CONSONANT SIGN COENG MO;17D2 1798
|
||||
KHMER CONSONANT SIGN COENG YO;17D2 1799
|
||||
KHMER CONSONANT SIGN COENG RO;17D2 179A
|
||||
KHMER CONSONANT SIGN COENG LO;17D2 179B
|
||||
KHMER CONSONANT SIGN COENG VO;17D2 179C
|
||||
KHMER CONSONANT SIGN COENG SHA;17D2 179D
|
||||
KHMER CONSONANT SIGN COENG SSA;17D2 179E
|
||||
KHMER CONSONANT SIGN COENG SA;17D2 179F
|
||||
KHMER CONSONANT SIGN COENG HA;17D2 17A0
|
||||
KHMER CONSONANT SIGN COENG LA;17D2 17A1
|
||||
KHMER VOWEL SIGN COENG QA;17D2 17A2
|
||||
KHMER INDEPENDENT VOWEL SIGN COENG QU;17D2 17A7
|
||||
KHMER INDEPENDENT VOWEL SIGN COENG RY;17D2 17AB
|
||||
KHMER INDEPENDENT VOWEL SIGN COENG RYY;17D2 17AC
|
||||
KHMER INDEPENDENT VOWEL SIGN COENG QE;17D2 17AF
|
||||
KHMER VOWEL SIGN OM;17BB 17C6
|
||||
KHMER VOWEL SIGN AAM;17B6 17C6
|
||||
HIRAGANA LETTER BIDAKUON NGA;304B 309A
|
||||
HIRAGANA LETTER BIDAKUON NGI;304D 309A
|
||||
HIRAGANA LETTER BIDAKUON NGU;304F 309A
|
||||
HIRAGANA LETTER BIDAKUON NGE;3051 309A
|
||||
HIRAGANA LETTER BIDAKUON NGO;3053 309A
|
||||
KATAKANA LETTER BIDAKUON NGA;30AB 309A
|
||||
KATAKANA LETTER BIDAKUON NGI;30AD 309A
|
||||
KATAKANA LETTER BIDAKUON NGU;30AF 309A
|
||||
KATAKANA LETTER BIDAKUON NGE;30B1 309A
|
||||
KATAKANA LETTER BIDAKUON NGO;30B3 309A
|
||||
KATAKANA LETTER AINU CE;30BB 309A
|
||||
KATAKANA LETTER AINU TU;30C4 309A
|
||||
KATAKANA LETTER AINU TO;30C8 309A
|
||||
KATAKANA LETTER AINU P;31F7 309A
|
||||
MODIFIER LETTER EXTRA-HIGH EXTRA-LOW CONTOUR TONE BAR;02E5 02E9
|
||||
MODIFIER LETTER EXTRA-LOW EXTRA-HIGH CONTOUR TONE BAR;02E9 02E5
|
||||
@@ -24,4 +24,4 @@
|
||||
{verbose, false}
|
||||
]}.
|
||||
|
||||
{require_otp_vsn,"(R16B).*"}.
|
||||
{require_otp_vsn,"(R16B|R17A).*"}.
|
||||
|
||||
+11
-13
@@ -2,9 +2,8 @@
|
||||
-behaviour(application).
|
||||
-export([main/1, start_cli/0,
|
||||
scope_for_eval/1, scope_for_eval/2,
|
||||
eval/2, eval/3, eval/4,
|
||||
eval_quoted/2, eval_quoted/3, eval_quoted/4,
|
||||
eval_forms/3, translate_forms/3]).
|
||||
eval/2, eval/3, eval/4, eval_forms/3,
|
||||
eval_quoted/2, eval_quoted/3, eval_quoted/4]).
|
||||
-include("elixir.hrl").
|
||||
|
||||
%% Top level types
|
||||
@@ -124,17 +123,16 @@ eval_quoted(Tree, Binding, Line, #elixir_scope{} = S) when is_integer(Line) ->
|
||||
%% Handle forms evaluation internally, it is an
|
||||
%% internal API not meant for external usage.
|
||||
|
||||
translate_forms(Tree, Binding, Opts) when is_list(Opts) ->
|
||||
translate_forms(Tree, Binding, scope_for_eval(Opts));
|
||||
|
||||
translate_forms(Tree, Binding, #elixir_scope{} = Scope) ->
|
||||
elixir_translator:translate(Tree, elixir_scope:vars_from_binding(Scope, Binding)).
|
||||
eval_forms(Tree, Binding, Opts) when is_list(Opts) ->
|
||||
eval_forms(Tree, Binding, scope_for_eval(Opts));
|
||||
|
||||
eval_forms(Tree, Binding, Scope) ->
|
||||
{ ParseTree, NewScope } = translate_forms(Tree, Binding, Scope),
|
||||
case ParseTree of
|
||||
[] -> { nil, Binding, NewScope };
|
||||
{ ParsedBinding, ParsedScope } = elixir_scope:load_binding(Binding, Scope, nil),
|
||||
{ Exprs, NewScope } = elixir_translator:translate(Tree, ParsedScope),
|
||||
case Exprs of
|
||||
[] ->
|
||||
{ nil, Binding, NewScope };
|
||||
_ ->
|
||||
{value, Value, NewBinding} = erl_eval:exprs(ParseTree, elixir_scope:binding_for_eval(Binding, nil)),
|
||||
{Value, elixir_scope:binding_from_vars(NewScope, NewBinding), NewScope }
|
||||
{ value, Value, NewBinding } = erl_eval:exprs(Exprs, ParsedBinding),
|
||||
{ Value, elixir_scope:dump_binding(NewBinding, NewScope), NewScope }
|
||||
end.
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
-module(elixir_aliases).
|
||||
-export([nesting_alias/2, last/1, concat/1, safe_concat/1,
|
||||
format_error/1, ensure_loaded/3, ensure_loaded/4, expand/3, store/4]).
|
||||
format_error/1, ensure_loaded/3, ensure_loaded/4, expand/4, store/5]).
|
||||
-include("elixir.hrl").
|
||||
|
||||
%% Store an alias in the given scope
|
||||
store(_Meta, New, New, S) -> S;
|
||||
store(Meta, New, Old, S) ->
|
||||
store(_Meta, New, New, _TKV, S) -> S;
|
||||
store(Meta, New, Old, TKV, S) ->
|
||||
record_warn(Meta, New, TKV, S),
|
||||
|
||||
SA = S#elixir_scope{
|
||||
aliases=orddict:store(New, Old, S#elixir_scope.aliases)
|
||||
},
|
||||
@@ -19,47 +21,58 @@ store(Meta, New, Old, S) ->
|
||||
SA
|
||||
end.
|
||||
|
||||
record_warn(Meta, Ref, Opts, S) ->
|
||||
Warn =
|
||||
case lists:keyfind(warn, 1, Opts) of
|
||||
{ warn, false } -> false;
|
||||
{ warn, true } -> true;
|
||||
false -> not lists:keymember(context, 1, Meta)
|
||||
end,
|
||||
elixir_lexical:record_alias(Ref, ?line(Meta), Warn, S#elixir_scope.lexical_tracker).
|
||||
|
||||
%% Expand an alias. It returns an atom (meaning that there
|
||||
%% was an expansion) or a list of atoms.
|
||||
|
||||
expand({ '__aliases__', Meta, _ } = Alias, Aliases, MacroAliases) ->
|
||||
expand({ '__aliases__', Meta, _ } = Alias, Aliases, MacroAliases, LexicalTracker) ->
|
||||
case lists:keyfind(alias, 1, Meta) of
|
||||
{ alias, false } ->
|
||||
expand(Alias, MacroAliases);
|
||||
expand(Alias, MacroAliases, LexicalTracker);
|
||||
{ alias, Atom } when is_atom(Atom) ->
|
||||
case expand(Alias, MacroAliases) of
|
||||
case expand(Alias, MacroAliases, LexicalTracker) of
|
||||
OtherAtom when is_atom(OtherAtom) -> OtherAtom;
|
||||
OtherAliases when is_list(OtherAliases) -> Atom
|
||||
end;
|
||||
false ->
|
||||
expand(Alias, Aliases)
|
||||
expand(Alias, Aliases, LexicalTracker)
|
||||
end.
|
||||
|
||||
expand({ '__aliases__', _Meta, [H] }, Aliases) when H /= 'Elixir' ->
|
||||
case expand_one(H, Aliases) of
|
||||
expand({ '__aliases__', _Meta, [H] }, Aliases, LexicalTracker) when H /= 'Elixir' ->
|
||||
case expand_one(H, Aliases, LexicalTracker) of
|
||||
false -> [H];
|
||||
Atom -> Atom
|
||||
end;
|
||||
|
||||
expand({ '__aliases__', _Meta, [H|T] }, Aliases) when is_atom(H) ->
|
||||
expand({ '__aliases__', _Meta, [H|T] }, Aliases, LexicalTracker) when is_atom(H) ->
|
||||
case H of
|
||||
'Elixir' ->
|
||||
concat(T);
|
||||
_ ->
|
||||
case expand_one(H, Aliases) of
|
||||
case expand_one(H, Aliases, LexicalTracker) of
|
||||
false -> [H|T];
|
||||
Atom -> concat([Atom|T])
|
||||
end
|
||||
end;
|
||||
|
||||
expand({ '__aliases__', _Meta, List }, _Aliases) ->
|
||||
expand({ '__aliases__', _Meta, List }, _Aliases, _LexicalTracker) ->
|
||||
List.
|
||||
|
||||
expand_one(H, Aliases) ->
|
||||
expand_one(H, Aliases, LexicalTracker) ->
|
||||
Lookup = list_to_atom("Elixir." ++ atom_to_list(H)),
|
||||
case lookup(Lookup, Aliases) of
|
||||
Lookup -> false;
|
||||
Else -> Else
|
||||
Else ->
|
||||
elixir_lexical:record_alias(Lookup, LexicalTracker),
|
||||
Else
|
||||
end.
|
||||
|
||||
%% Ensure a module is loaded before its usage.
|
||||
|
||||
@@ -4,14 +4,13 @@
|
||||
handle_info/2, terminate/2, code_change/3]).
|
||||
-behavior(gen_server).
|
||||
-record(elixir_code_server, {
|
||||
compilation_status=[],
|
||||
argv=[],
|
||||
compilation_status=ok,
|
||||
loaded=[],
|
||||
at_exit=[],
|
||||
pool=[],
|
||||
counter=0,
|
||||
pool={[],0},
|
||||
compiler_options=[{docs,true},{debug_info,true},{warnings_as_errors,false}],
|
||||
waiting=[]
|
||||
erl_compiler_options=nil
|
||||
}).
|
||||
|
||||
call(Args) ->
|
||||
@@ -28,7 +27,9 @@ get_timeout() ->
|
||||
start_link() ->
|
||||
{ ok, _ } = gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).
|
||||
|
||||
init(_args) ->
|
||||
init(_Args) ->
|
||||
code:ensure_loaded('Elixir.Module.DispatchTracker'),
|
||||
code:ensure_loaded('Elixir.Kernel.LexicalTracker'),
|
||||
{ ok, #elixir_code_server{} }.
|
||||
|
||||
handle_call({ acquire, Path }, From, Config) ->
|
||||
@@ -59,16 +60,27 @@ handle_call(argv, _From, Config) ->
|
||||
handle_call(compiler_options, _From, Config) ->
|
||||
{ reply, Config#elixir_code_server.compiler_options, Config };
|
||||
|
||||
handle_call(compilation_status, _From, Config) ->
|
||||
{ reply, Config#elixir_code_server.compilation_status, Config };
|
||||
handle_call({ compilation_status, CompilerPid }, _From, Config) ->
|
||||
CompilationStatusList = Config#elixir_code_server.compilation_status,
|
||||
CompilationStatusListNew = orddict:erase(CompilerPid, CompilationStatusList),
|
||||
CompilationStatus = orddict:fetch(CompilerPid, CompilationStatusList),
|
||||
{ reply, CompilationStatus, Config#elixir_code_server{compilation_status=CompilationStatusListNew} };
|
||||
|
||||
handle_call(retrieve_module_name, _From, Config) ->
|
||||
case Config#elixir_code_server.pool of
|
||||
[H|T] ->
|
||||
{ reply, module_tuple(H), Config#elixir_code_server{pool=T} };
|
||||
[] ->
|
||||
Counter = Config#elixir_code_server.counter,
|
||||
{ reply, module_tuple(Counter), Config#elixir_code_server{counter=Counter+1} }
|
||||
{ [H|T], Counter } ->
|
||||
{ reply, module_tuple(H), Config#elixir_code_server{pool={T,Counter}} };
|
||||
{ [], Counter } ->
|
||||
{ reply, module_tuple(Counter), Config#elixir_code_server{pool={[],Counter+1}} }
|
||||
end;
|
||||
|
||||
handle_call(erl_compiler_options, _From, Config) ->
|
||||
case Config#elixir_code_server.erl_compiler_options of
|
||||
nil ->
|
||||
Opts = erl_compiler_options(),
|
||||
{ reply, Opts, Config#elixir_code_server{erl_compiler_options=Opts} };
|
||||
Opts ->
|
||||
{ reply, Opts, Config }
|
||||
end;
|
||||
|
||||
handle_call(_Request, _From, Config) ->
|
||||
@@ -84,14 +96,18 @@ handle_cast({ compiler_options, Options }, Config) ->
|
||||
Final = orddict:merge(fun(_,_,V) -> V end, Config#elixir_code_server.compiler_options, Options),
|
||||
{ noreply, Config#elixir_code_server{compiler_options=Final} };
|
||||
|
||||
handle_cast(register_warning, Config) ->
|
||||
handle_cast({ register_warning, CompilerPid }, Config) ->
|
||||
CompilationStatusCurrent = Config#elixir_code_server.compilation_status,
|
||||
CompilationStatusNew = orddict:store(CompilerPid, error, CompilationStatusCurrent),
|
||||
case orddict:find(warnings_as_errors, Config#elixir_code_server.compiler_options) of
|
||||
{ ok, true } -> { noreply, Config#elixir_code_server{compilation_status=error} };
|
||||
{ ok, true } -> { noreply, Config#elixir_code_server{compilation_status=CompilationStatusNew} };
|
||||
_ -> { noreply, Config }
|
||||
end;
|
||||
|
||||
handle_cast(reset_warnings, Config) ->
|
||||
{ noreply, Config#elixir_code_server{compilation_status=ok} };
|
||||
handle_cast({ reset_warnings, CompilerPid }, Config) ->
|
||||
CompilationStatusCurrent = Config#elixir_code_server.compilation_status,
|
||||
CompilationStatusNew = orddict:store(CompilerPid, ok, CompilationStatusCurrent),
|
||||
{ noreply, Config#elixir_code_server{compilation_status=CompilationStatusNew} };
|
||||
|
||||
handle_cast({ loaded, Path }, Config) ->
|
||||
Current = Config#elixir_code_server.loaded,
|
||||
@@ -112,8 +128,8 @@ handle_cast({ unload_files, Files }, Config) ->
|
||||
Unloaded = lists:foldl(fun(File, Acc) -> orddict:erase(File, Acc) end, Current, Files),
|
||||
{ noreply, Config#elixir_code_server{loaded=Unloaded} };
|
||||
|
||||
handle_cast({ return_module_name, I }, #elixir_code_server{pool=Pool} = Config) ->
|
||||
{ noreply, Config#elixir_code_server{pool=[I|Pool]} };
|
||||
handle_cast({ return_module_name, H }, #elixir_code_server{pool={T,Counter}} = Config) ->
|
||||
{ noreply, Config#elixir_code_server{pool={[H|T],Counter}} };
|
||||
|
||||
handle_cast(_Request, Config) ->
|
||||
{ noreply, Config }.
|
||||
@@ -127,4 +143,24 @@ terminate(_Reason, _Config) ->
|
||||
code_change(_Old, Config, _Extra) ->
|
||||
{ ok, Config }.
|
||||
|
||||
module_tuple(I) -> { list_to_atom("elixir_compiler_" ++ integer_to_list(I)), I }.
|
||||
module_tuple(I) -> { list_to_atom("elixir_compiler_" ++ integer_to_list(I)), I }.
|
||||
|
||||
erl_compiler_options() ->
|
||||
Key = "ERL_COMPILER_OPTIONS",
|
||||
case os:getenv(Key) of
|
||||
false -> [];
|
||||
Str when is_list(Str) ->
|
||||
case erl_scan:string(Str) of
|
||||
{ok,Tokens,_} ->
|
||||
case erl_parse:parse_term(Tokens ++ [{dot, 1}]) of
|
||||
{ok,List} when is_list(List) -> List;
|
||||
{ok,Term} -> [Term];
|
||||
{error,_Reason} ->
|
||||
io:format("Ignoring bad term in ~ts\n", [Key]),
|
||||
[]
|
||||
end;
|
||||
{error, {_,_,_Reason}, _} ->
|
||||
io:format("Ignoring bad term in ~ts\n", [Key]),
|
||||
[]
|
||||
end
|
||||
end.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
-module(elixir_compiler).
|
||||
-export([get_opts/0, get_opt/1, get_opt/2, string/2, quoted/2, file/1, file_to_path/2]).
|
||||
-export([core/0, module/3, eval_forms/4, format_error/1]).
|
||||
-export([core/0, module/4, eval_forms/4, format_error/1]).
|
||||
-include("elixir.hrl").
|
||||
|
||||
%% Public API
|
||||
@@ -18,7 +18,7 @@ get_opt(Key, Dict) ->
|
||||
get_opts() ->
|
||||
elixir_code_server:call(compiler_options).
|
||||
|
||||
%% Compiles the given string.
|
||||
%% Compilation entry points.
|
||||
|
||||
string(Contents, File) when is_list(Contents), is_binary(File) ->
|
||||
Forms = elixir_translator:'forms!'(Contents, 1, File, []),
|
||||
@@ -29,34 +29,34 @@ quoted(Forms, File) when is_binary(File) ->
|
||||
|
||||
try
|
||||
put(elixir_compiled, []),
|
||||
eval_forms(Forms, 1, [], elixir:scope_for_eval([{file,File}])),
|
||||
elixir_lexical:run(File, fun
|
||||
(Pid) ->
|
||||
Scope = elixir:scope_for_eval([{file,File}]),
|
||||
eval_forms(Forms, 1, [], Scope#elixir_scope{lexical_tracker=Pid})
|
||||
end),
|
||||
lists:reverse(get(elixir_compiled))
|
||||
after
|
||||
put(elixir_compiled, Previous)
|
||||
end.
|
||||
|
||||
%% Compile a file, return a tuple of module names and binaries.
|
||||
|
||||
file(Relative) when is_binary(Relative) ->
|
||||
File = filename:absname(Relative),
|
||||
{ ok, Bin } = file:read_file(File),
|
||||
string(elixir_utils:characters_to_list(Bin), File).
|
||||
|
||||
%% Compiles a file to the given path (directory).
|
||||
|
||||
file_to_path(File, Path) when is_binary(File), is_binary(Path) ->
|
||||
Lists = file(File),
|
||||
[binary_to_path(X, Path) || X <- Lists],
|
||||
Lists.
|
||||
|
||||
%% Evaluates the contents/forms by compiling them to an Erlang module.
|
||||
%% Evaluation
|
||||
|
||||
eval_forms(Forms, Line, Vars, S) ->
|
||||
{ Module, I } = retrieve_module_name(),
|
||||
{ Exprs, FS } = elixir_translator:translate(Forms, S),
|
||||
|
||||
Fun = module_form_fun(S#elixir_scope.module),
|
||||
Form = module_form(Fun, Exprs, Line, S#elixir_scope.file, Module, Vars),
|
||||
Fun = eval_fun(S#elixir_scope.module),
|
||||
Form = eval_mod(Fun, Exprs, Line, S#elixir_scope.file, Module, Vars),
|
||||
Args = [X || { _, _, _, X } <- Vars],
|
||||
|
||||
%% Pass { native, false } to speed up bootstrap
|
||||
@@ -79,88 +79,10 @@ eval_forms(Forms, Line, Vars, S) ->
|
||||
Res
|
||||
end), FS }.
|
||||
|
||||
%% Internal API
|
||||
eval_fun(nil) -> '__FILE__';
|
||||
eval_fun(_) -> '__MODULE__'.
|
||||
|
||||
%% Compile the module by forms based on the scope information
|
||||
%% executes the callback in case of success. This automatically
|
||||
%% handles errors and warnings. Used by this module and elixir_module.
|
||||
module(Forms, File, Callback) ->
|
||||
Options = case get_opt(debug_info) of
|
||||
true -> [debug_info];
|
||||
_ -> []
|
||||
end,
|
||||
module(Forms, File, Options, false, Callback).
|
||||
|
||||
module(Forms, File, RawOptions, Bootstrap, Callback) when
|
||||
is_binary(File), is_list(Forms), is_list(RawOptions), is_boolean(Bootstrap), is_function(Callback) ->
|
||||
{ Options, SkipNative } = compile_opts(Forms, RawOptions),
|
||||
Listname = elixir_utils:characters_to_list(File),
|
||||
|
||||
case compile:noenv_forms([no_auto_import()|Forms], [return,{source,Listname}|Options]) of
|
||||
{ok, ModuleName, Binary, RawWarnings} ->
|
||||
Warnings = case SkipNative of
|
||||
true -> [{?MODULE,[{0,?MODULE,{skip_native,ModuleName}}]}|RawWarnings];
|
||||
false -> RawWarnings
|
||||
end,
|
||||
format_warnings(Bootstrap, Warnings),
|
||||
code:load_binary(ModuleName, Listname, Binary),
|
||||
Callback(ModuleName, Binary);
|
||||
{error, Errors, Warnings} ->
|
||||
format_warnings(Bootstrap, Warnings),
|
||||
format_errors(Errors)
|
||||
end.
|
||||
|
||||
%% Compile core files for bootstrap.
|
||||
%% Invoked from the Makefile.
|
||||
|
||||
core() ->
|
||||
application:start(elixir),
|
||||
elixir_code_server:cast({ compiler_options, [{docs,false},{internal,true}] }),
|
||||
[core_file(File) || File <- core_main()].
|
||||
|
||||
%% HELPERS
|
||||
|
||||
compile_opts(Forms, Options) ->
|
||||
EnvOptions = env_default_opts(),
|
||||
SkipNative = lists:member(native, EnvOptions) and contains_on_load(Forms),
|
||||
case SkipNative or lists:member([{native,false}], Options) of
|
||||
true -> { Options ++ lists:delete(native, EnvOptions), SkipNative };
|
||||
false -> { Options ++ EnvOptions, SkipNative }
|
||||
end.
|
||||
|
||||
env_default_opts() ->
|
||||
Key = "ERL_COMPILER_OPTIONS",
|
||||
case os:getenv(Key) of
|
||||
false -> [];
|
||||
Str when is_list(Str) ->
|
||||
case erl_scan:string(Str) of
|
||||
{ok,Tokens,_} ->
|
||||
case erl_parse:parse_term(Tokens ++ [{dot, 1}]) of
|
||||
{ok,List} when is_list(List) -> List;
|
||||
{ok,Term} -> [Term];
|
||||
{error,_Reason} ->
|
||||
io:format("Ignoring bad term in ~ts\n", [Key]),
|
||||
[]
|
||||
end;
|
||||
{error, {_,_,_Reason}, _} ->
|
||||
io:format("Ignoring bad term in ~ts\n", [Key]),
|
||||
[]
|
||||
end
|
||||
end.
|
||||
|
||||
contains_on_load([{ attribute, _, on_load, _ }|_]) -> true;
|
||||
contains_on_load([_|T]) -> contains_on_load(T);
|
||||
contains_on_load([]) -> false.
|
||||
|
||||
no_auto_import() ->
|
||||
Bifs = [{ Name, Arity } || { Name, Arity } <- erlang:module_info(exports), erl_internal:bif(Name, Arity)],
|
||||
{ attribute, 0, compile, { no_auto_import, Bifs } }.
|
||||
|
||||
module_form_fun(nil) -> '__FILE__';
|
||||
module_form_fun(_) -> '__MODULE__'.
|
||||
|
||||
module_form(Fun, Exprs, Line, File, Module, Vars) when
|
||||
is_binary(File), is_integer(Line), is_list(Exprs), is_atom(Module) ->
|
||||
eval_mod(Fun, Exprs, Line, File, Module, Vars) when is_binary(File), is_integer(Line) ->
|
||||
Cons = lists:foldr(fun({ _, _, Var, _ }, Acc) ->
|
||||
{ cons, Line, { var, Line, Var }, Acc }
|
||||
end, { nil, Line }, Vars),
|
||||
@@ -180,22 +102,68 @@ module_form(Fun, Exprs, Line, File, Module, Vars) when
|
||||
] }
|
||||
].
|
||||
|
||||
%% Generate module names from code server.
|
||||
|
||||
retrieve_module_name() ->
|
||||
elixir_code_server:call(retrieve_module_name).
|
||||
|
||||
return_module_name(I) ->
|
||||
elixir_code_server:cast({ return_module_name, I }).
|
||||
|
||||
%% Receives a module Binary and outputs it in the given path.
|
||||
%% INTERNAL API
|
||||
|
||||
binary_to_path({ModuleName, Binary}, CompilePath) ->
|
||||
Path = filename:join(CompilePath, atom_to_list(ModuleName) ++ ".beam"),
|
||||
ok = file:write_file(Path, Binary),
|
||||
Path.
|
||||
%% Compile the module by forms based on the scope information
|
||||
%% executes the callback in case of success. This automatically
|
||||
%% handles errors and warnings. Used by this module and elixir_module.
|
||||
module(Forms, File, Opts, Callback) ->
|
||||
Final =
|
||||
case (get_opt(debug_info) == true) orelse
|
||||
lists:member(debug_info, Opts) of
|
||||
true -> [debug_info];
|
||||
false -> []
|
||||
end,
|
||||
module(Forms, File, Final, false, Callback).
|
||||
|
||||
%% CORE FILES COMPILATION
|
||||
module(Forms, File, RawOptions, Bootstrap, Callback) when
|
||||
is_binary(File), is_list(Forms), is_list(RawOptions), is_boolean(Bootstrap), is_function(Callback) ->
|
||||
{ Options, SkipNative } = compiler_options(Forms, RawOptions),
|
||||
Listname = elixir_utils:characters_to_list(File),
|
||||
|
||||
case compile:noenv_forms([no_auto_import()|Forms], [return,{source,Listname}|Options]) of
|
||||
{ok, ModuleName, Binary, RawWarnings} ->
|
||||
Warnings = case SkipNative of
|
||||
true -> [{?MODULE,[{0,?MODULE,{skip_native,ModuleName}}]}|RawWarnings];
|
||||
false -> RawWarnings
|
||||
end,
|
||||
format_warnings(Bootstrap, Warnings),
|
||||
code:load_binary(ModuleName, Listname, Binary),
|
||||
Callback(ModuleName, Binary);
|
||||
{error, Errors, Warnings} ->
|
||||
format_warnings(Bootstrap, Warnings),
|
||||
format_errors(Errors)
|
||||
end.
|
||||
|
||||
compiler_options(Forms, Options) ->
|
||||
EnvOptions = elixir_code_server:call(erl_compiler_options),
|
||||
SkipNative = lists:member(native, EnvOptions) and contains_on_load(Forms),
|
||||
case SkipNative or lists:member([{native,false}], Options) of
|
||||
true -> { Options ++ lists:delete(native, EnvOptions), SkipNative };
|
||||
false -> { Options ++ EnvOptions, SkipNative }
|
||||
end.
|
||||
|
||||
contains_on_load([{ attribute, _, on_load, _ }|_]) -> true;
|
||||
contains_on_load([_|T]) -> contains_on_load(T);
|
||||
contains_on_load([]) -> false.
|
||||
|
||||
no_auto_import() ->
|
||||
Bifs = [{ Name, Arity } || { Name, Arity } <- erlang:module_info(exports), erl_internal:bif(Name, Arity)],
|
||||
{ attribute, 0, compile, { no_auto_import, Bifs } }.
|
||||
|
||||
|
||||
%% CORE HANDLING
|
||||
|
||||
core() ->
|
||||
application:start(elixir),
|
||||
elixir_code_server:cast({ compiler_options, [{docs,false},{internal,true}] }),
|
||||
[core_file(File) || File <- core_main()].
|
||||
|
||||
core_file(File) ->
|
||||
try
|
||||
@@ -225,6 +193,7 @@ core_main() ->
|
||||
<<"lib/elixir/lib/inspect/algebra.ex">>,
|
||||
<<"lib/elixir/lib/inspect.ex">>,
|
||||
<<"lib/elixir/lib/range.ex">>,
|
||||
<<"lib/elixir/lib/regex.ex">>,
|
||||
<<"lib/elixir/lib/string.ex">>,
|
||||
<<"lib/elixir/lib/string/chars.ex">>,
|
||||
<<"lib/elixir/lib/io.ex">>,
|
||||
@@ -234,9 +203,15 @@ core_main() ->
|
||||
<<"lib/elixir/lib/kernel/error_handler.ex">>,
|
||||
<<"lib/elixir/lib/kernel/parallel_compiler.ex">>,
|
||||
<<"lib/elixir/lib/kernel/record_rewriter.ex">>,
|
||||
<<"lib/elixir/lib/kernel/lexical_tracker.ex">>,
|
||||
<<"lib/elixir/lib/module/dispatch_tracker.ex">>
|
||||
].
|
||||
|
||||
binary_to_path({ModuleName, Binary}, CompilePath) ->
|
||||
Path = filename:join(CompilePath, atom_to_list(ModuleName) ++ ".beam"),
|
||||
ok = file:write_file(Path, Binary),
|
||||
Path.
|
||||
|
||||
%% ERROR HANDLING
|
||||
|
||||
format_error({ skip_native, Module }) ->
|
||||
|
||||
@@ -8,9 +8,7 @@
|
||||
lookup_definition/2,
|
||||
delete_definition/2,
|
||||
wrap_definition/6,
|
||||
wrap_definition/8,
|
||||
store_definition/7,
|
||||
store_definition/9,
|
||||
store_each/8,
|
||||
unwrap_stored_definitions/2,
|
||||
format_error/1]).
|
||||
@@ -72,25 +70,17 @@ delete_definition(Module, Tuple) ->
|
||||
%% before evaluating the function body), we would see both definitions.
|
||||
|
||||
wrap_definition(Kind, Meta, Call, Expr, CheckClauses, S) ->
|
||||
do_wrap_definition(Kind, Meta, CheckClauses, [
|
||||
Call, Expr, elixir_scope:serialize(S)
|
||||
Line = ?line(Meta),
|
||||
?wrap_call(Line, ?MODULE, store_definition, [
|
||||
{atom, Line, Kind},
|
||||
{integer, Line, Line},
|
||||
{atom, Line, CheckClauses},
|
||||
{var, Line, '_@MODULE'},
|
||||
Call,
|
||||
Expr,
|
||||
elixir_scope:serialize(S)
|
||||
]).
|
||||
|
||||
wrap_definition(Kind, Meta, Name, Args, Guards, Expr, CheckClauses, S) ->
|
||||
do_wrap_definition(Kind, Meta, CheckClauses, [
|
||||
Name, Args, Guards, Expr, elixir_scope:serialize(S)
|
||||
]).
|
||||
|
||||
do_wrap_definition(Kind, Meta, CheckClauses, Extra) ->
|
||||
Line = ?line(Meta),
|
||||
Invoke =
|
||||
[{atom, Line, Kind},
|
||||
{integer, Line, Line},
|
||||
{atom, Line, CheckClauses},
|
||||
{var, Line, '_@MODULE'}] ++ Extra,
|
||||
|
||||
?wrap_call(Line, ?MODULE, store_definition, Invoke).
|
||||
|
||||
% Invoked by the wrap definition with the function abstract tree.
|
||||
% Each function is then added to the function table.
|
||||
|
||||
@@ -139,10 +129,7 @@ store_definition(Kind, Line, CheckClauses, Module, Name, Args, Guards, Body, #el
|
||||
[store_each(false, Kind, File, Location, Table, CTable, 0,
|
||||
default_function_for(Kind, Name, Default)) || Default <- Defaults],
|
||||
|
||||
{ Name, Arity };
|
||||
|
||||
store_definition(Kind, Line, CheckClauses, Module, Name, Args, Guards, Body, RawS) ->
|
||||
store_definition(Kind, Line, CheckClauses, Module, Name, Args, Guards, Body, elixir_scope:deserialize(RawS)).
|
||||
{ Name, Arity }.
|
||||
|
||||
%% @on_definition
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
% Holds the logic responsible for defining overridable functions and handling super.
|
||||
-module(elixir_def_overridable).
|
||||
-export([store_pending/1, is_defined/2, ensure_defined/4,
|
||||
assign_args/3, name/2, store/3, format_error/1]).
|
||||
name/2, store/3, format_error/1]).
|
||||
-include("elixir.hrl").
|
||||
|
||||
overridable(Module) ->
|
||||
@@ -25,19 +25,6 @@ ensure_defined(Meta, Module, Tuple, S) ->
|
||||
_ -> elixir_errors:form_error(Meta, S#elixir_scope.file, ?MODULE, { no_super, Module, Tuple })
|
||||
end.
|
||||
|
||||
%% Assign pseudo variables to the given vars.
|
||||
|
||||
assign_args(Line, Args, S) when is_integer(Line) ->
|
||||
{ FArgs, _ } = lists:mapfoldl(fun(X, Acc) -> assign_args(Line, X, Acc, S) end, 1, Args),
|
||||
FArgs.
|
||||
|
||||
assign_args(Line, X, Acc, _) ->
|
||||
Match = { match, Line, X, { var, Line, super_arg(Acc) } },
|
||||
{ Match, Acc + 1 }.
|
||||
|
||||
super_arg(Counter) ->
|
||||
?atom_concat(['_@S', Counter]).
|
||||
|
||||
%% Gets the name based on the function and stored overridables
|
||||
|
||||
name(Module, Function) ->
|
||||
|
||||
@@ -24,9 +24,11 @@ find_import(Meta, Name, Arity, S) ->
|
||||
|
||||
case find_dispatch(Meta, Tuple, S) of
|
||||
{ function, Receiver } ->
|
||||
elixir_lexical:record_import(Receiver, S#elixir_scope.lexical_tracker),
|
||||
elixir_tracker:record_import(Tuple, Receiver, S#elixir_scope.module, S#elixir_scope.function),
|
||||
Receiver;
|
||||
{ macro, Receiver } ->
|
||||
elixir_lexical:record_import(Receiver, S#elixir_scope.lexical_tracker),
|
||||
elixir_tracker:record_import(Tuple, Receiver, S#elixir_scope.module, S#elixir_scope.function),
|
||||
Receiver;
|
||||
_ ->
|
||||
@@ -39,6 +41,7 @@ import_function(Meta, Name, Arity, S) ->
|
||||
Tuple = { Name, Arity },
|
||||
case find_dispatch(Meta, Tuple, S) of
|
||||
{ function, Receiver } ->
|
||||
elixir_lexical:record_import(Receiver, S#elixir_scope.lexical_tracker),
|
||||
elixir_tracker:record_import(Tuple, Receiver, S#elixir_scope.module, S#elixir_scope.function),
|
||||
remote_function(Meta, Receiver, Name, Arity, S);
|
||||
{ macro, _Receiver } ->
|
||||
@@ -60,6 +63,7 @@ require_function(Meta, Receiver, Name, Arity, S) ->
|
||||
case is_element(Tuple, get_optional_macros(Receiver)) of
|
||||
true -> false;
|
||||
false ->
|
||||
elixir_lexical:record_remote(Receiver, S#elixir_scope.lexical_tracker),
|
||||
elixir_tracker:record_remote(Tuple, Receiver, S#elixir_scope.module, S#elixir_scope.function),
|
||||
remote_function(Meta, Receiver, Name, Arity, S)
|
||||
end.
|
||||
@@ -73,6 +77,7 @@ dispatch_import(Meta, Name, Args, S, Callback) ->
|
||||
|
||||
case find_dispatch(Meta, Tuple, S) of
|
||||
{ function, Receiver } ->
|
||||
elixir_lexical:record_import(Receiver, S#elixir_scope.lexical_tracker),
|
||||
elixir_tracker:record_import(Tuple, Receiver, Module, S#elixir_scope.function),
|
||||
Endpoint = case (Receiver == ?builtin) andalso is_element(Tuple, in_erlang_functions()) of
|
||||
true -> erlang;
|
||||
@@ -80,6 +85,7 @@ dispatch_import(Meta, Name, Args, S, Callback) ->
|
||||
end,
|
||||
elixir_translator:translate_each({ { '.', Meta, [Endpoint, Name] }, Meta, Args }, S);
|
||||
{ import, Receiver } ->
|
||||
elixir_lexical:record_remote(Receiver, S#elixir_scope.lexical_tracker),
|
||||
elixir_tracker:record_remote(Tuple, Receiver, S#elixir_scope.module, S#elixir_scope.function),
|
||||
elixir_translator:translate_each({ { '.', Meta, [Receiver, Name] }, [{require,false}|Meta], Args }, S);
|
||||
Result ->
|
||||
@@ -87,10 +93,11 @@ dispatch_import(Meta, Name, Args, S, Callback) ->
|
||||
{ error, noexpansion } ->
|
||||
Callback();
|
||||
{ error, internal } ->
|
||||
elixir_lexical:record_import(?builtin, S#elixir_scope.lexical_tracker),
|
||||
elixir_tracker:record_import(Tuple, ?builtin, Module, S#elixir_scope.function),
|
||||
elixir_macros:translate({ Name, Meta, Args }, S);
|
||||
{ ok, _Receiver, Tree } ->
|
||||
translate_expansion(Meta, Tree, S)
|
||||
{ ok, Receiver, Tree } ->
|
||||
translate_expansion(Meta, Receiver, Tree, S)
|
||||
end
|
||||
end.
|
||||
|
||||
@@ -101,6 +108,7 @@ dispatch_require(Meta, Receiver, Name, Args, S, Callback) ->
|
||||
|
||||
case (Receiver == ?builtin) andalso is_element(Tuple, in_erlang_functions()) of
|
||||
true ->
|
||||
elixir_lexical:record_remote(Receiver, S#elixir_scope.lexical_tracker),
|
||||
elixir_tracker:record_remote(Tuple, Receiver, S#elixir_scope.module, S#elixir_scope.function),
|
||||
{ TArgs, SA } = elixir_translator:translate_args(Args, S),
|
||||
{ ?wrap_call(?line(Meta), erlang, Name, TArgs), SA };
|
||||
@@ -109,10 +117,11 @@ dispatch_require(Meta, Receiver, Name, Args, S, Callback) ->
|
||||
{ error, noexpansion } ->
|
||||
Callback();
|
||||
{ error, internal } ->
|
||||
elixir_lexical:record_remote(?builtin, S#elixir_scope.lexical_tracker),
|
||||
elixir_tracker:record_remote(Tuple, ?builtin, S#elixir_scope.module, S#elixir_scope.function),
|
||||
elixir_macros:translate({ Name, Meta, Args }, S);
|
||||
{ ok, _Receiver, Tree } ->
|
||||
translate_expansion(Meta, Tree, S)
|
||||
{ ok, Receiver, Tree } ->
|
||||
translate_expansion(Meta, Receiver, Tree, S)
|
||||
end
|
||||
end.
|
||||
|
||||
@@ -150,10 +159,12 @@ do_expand_import_no_local(Meta, { Name, Arity } = Tuple, Args, Module, S, Result
|
||||
case is_element(Tuple, in_erlang_macros()) of
|
||||
true -> { error, internal };
|
||||
false ->
|
||||
elixir_lexical:record_import(?builtin, S#elixir_scope.lexical_tracker),
|
||||
elixir_tracker:record_import(Tuple, ?builtin, Module, S#elixir_scope.function),
|
||||
{ ok, ?builtin, expand_macro_named(Meta, ?builtin, Name, Arity, Args, Module, S) }
|
||||
end;
|
||||
{ macro, Receiver } ->
|
||||
elixir_lexical:record_import(Receiver, S#elixir_scope.lexical_tracker),
|
||||
elixir_tracker:record_import(Tuple, Receiver, Module, S#elixir_scope.function),
|
||||
{ ok, Receiver, expand_macro_named(Meta, Receiver, Name, Arity, Args, Module, S) };
|
||||
{ import, Receiver } ->
|
||||
@@ -168,6 +179,7 @@ expand_require(Meta, ?builtin, { Name, Arity } = Tuple, Args, Module, S) ->
|
||||
false ->
|
||||
case is_element(Tuple, in_elixir_macros()) of
|
||||
true ->
|
||||
elixir_lexical:record_remote(?builtin, S#elixir_scope.lexical_tracker),
|
||||
elixir_tracker:record_remote(Tuple, ?builtin, S#elixir_scope.module, S#elixir_scope.function),
|
||||
{ ok, ?builtin, expand_macro_named(Meta, ?builtin, Name, Arity, Args, Module, S) };
|
||||
false ->
|
||||
@@ -178,20 +190,12 @@ expand_require(Meta, ?builtin, { Name, Arity } = Tuple, Args, Module, S) ->
|
||||
expand_require(Meta, Receiver, { Name, Arity } = Tuple, Args, Module, S) ->
|
||||
Function = S#elixir_scope.function,
|
||||
|
||||
Fun = (Module == Receiver) andalso (Function /= nil) andalso
|
||||
(Function /= Tuple) andalso elixir_def_local:macro_for(Tuple, false, S),
|
||||
|
||||
case Fun of
|
||||
false ->
|
||||
case is_element(Tuple, get_optional_macros(Receiver)) of
|
||||
true ->
|
||||
elixir_tracker:record_remote(Tuple, Receiver, Module, Function),
|
||||
{ ok, Receiver, expand_macro_named(Meta, Receiver, Name, Arity, Args, Module, S) };
|
||||
false -> { error, noexpansion }
|
||||
end;
|
||||
_ ->
|
||||
elixir_tracker:record_local(Tuple, Module, Function),
|
||||
{ ok, Receiver, expand_macro_fun(Meta, Fun(), Receiver, Name, Args, Module, S) }
|
||||
case is_element(Tuple, get_optional_macros(Receiver)) of
|
||||
true ->
|
||||
elixir_lexical:record_remote(Receiver, S#elixir_scope.lexical_tracker),
|
||||
elixir_tracker:record_remote(Tuple, Receiver, Module, Function),
|
||||
{ ok, Receiver, expand_macro_named(Meta, Receiver, Name, Arity, Args, Module, S) };
|
||||
false -> { error, noexpansion }
|
||||
end.
|
||||
|
||||
%% Expansion helpers
|
||||
@@ -213,7 +217,7 @@ expand_macro_fun(Meta, Fun, Receiver, Name, Args, Module, S) ->
|
||||
catch
|
||||
Kind:Reason ->
|
||||
Info = { Receiver, Name, length(Args), location(Line, S) },
|
||||
erlang:raise(Kind, Reason, munge_stacktrace(Info, erlang:get_stacktrace(), SArg))
|
||||
erlang:raise(Kind, Reason, prune_stacktrace(Info, erlang:get_stacktrace(), SArg))
|
||||
end.
|
||||
|
||||
expand_macro_named(Meta, Receiver, Name, Arity, Args, Module, S) ->
|
||||
@@ -222,17 +226,18 @@ expand_macro_named(Meta, Receiver, Name, Arity, Args, Module, S) ->
|
||||
Fun = fun Receiver:ProperName/ProperArity,
|
||||
expand_macro_fun(Meta, Fun, Receiver, Name, Args, Module, S).
|
||||
|
||||
translate_expansion(Meta, Tree, S) ->
|
||||
translate_expansion(Meta, Receiver, Tree, S) ->
|
||||
Line = ?line(Meta),
|
||||
New = S#elixir_scope.macro_counter + 1,
|
||||
|
||||
try
|
||||
elixir_translator:translate_each(
|
||||
elixir_quote:linify(Line, Tree),
|
||||
S
|
||||
elixir_quote:linify(Line, { Receiver, New }, Tree),
|
||||
S#elixir_scope{macro_counter=New}
|
||||
)
|
||||
catch
|
||||
Kind:Reason ->
|
||||
erlang:raise(Kind, Reason, munge_stacktrace(mfa(Line, S), erlang:get_stacktrace(), nil))
|
||||
erlang:raise(Kind, Reason, prune_stacktrace(mfa(Line, S), erlang:get_stacktrace(), nil))
|
||||
end.
|
||||
|
||||
mfa(Line, #elixir_scope{module=nil} = S) ->
|
||||
@@ -305,17 +310,17 @@ not_an_import(Tuple, Context, Dict) ->
|
||||
end.
|
||||
|
||||
%% We've reached the invoked macro, skip it with the rest
|
||||
munge_stacktrace(Info, [{ _, _, [S|_], _ }|_], S) ->
|
||||
prune_stacktrace(Info, [{ _, _, [S|_], _ }|_], S) ->
|
||||
[Info];
|
||||
|
||||
%% We've reached the elixir_dispatch internals, skip it with the rest
|
||||
munge_stacktrace(Info, [{ elixir_dispatch, _, _, _ }|_], _) ->
|
||||
prune_stacktrace(Info, [{ elixir_dispatch, _, _, _ }|_], _) ->
|
||||
[Info];
|
||||
|
||||
munge_stacktrace(Info, [H|T], S) ->
|
||||
[H|munge_stacktrace(Info, T, S)];
|
||||
prune_stacktrace(Info, [H|T], S) ->
|
||||
[H|prune_stacktrace(Info, T, S)];
|
||||
|
||||
munge_stacktrace(Info, [], _) ->
|
||||
prune_stacktrace(Info, [], _) ->
|
||||
[Info].
|
||||
|
||||
remote_function(Meta, Receiver, Name, Arity, S) ->
|
||||
@@ -336,13 +341,14 @@ remote_function(Meta, Receiver, Name, Arity, S) ->
|
||||
|
||||
%% ERROR HANDLING
|
||||
|
||||
format_error({ unrequired_module, { Receiver, Name, Arity, Required }}) ->
|
||||
String = 'Elixir.Enum':join([elixir_errors:inspect(R) || R <- Required], <<", ">>),
|
||||
io_lib:format("tried to invoke macro ~ts.~ts/~B but module was not required. Required: ~ts",
|
||||
[elixir_errors:inspect(Receiver), Name, Arity, String]);
|
||||
format_error({ unrequired_module, { Receiver, Name, Arity, _Required }}) ->
|
||||
Module = elixir_errors:inspect(Receiver),
|
||||
io_lib:format("you must require ~ts before invoking the macro ~ts.~ts/~B",
|
||||
[Module, Module, Name, Arity]);
|
||||
|
||||
format_error({ macro_conflict, { Receiver, Name, Arity } }) ->
|
||||
io_lib:format("call to local macro ~ts/~B conflicts with imported ~ts.~ts/~B",
|
||||
io_lib:format("call to local macro ~ts/~B conflicts with imported ~ts.~ts/~B, "
|
||||
"please rename the local macro or remove the conflicting import",
|
||||
[Name, Arity, elixir_errors:inspect(Receiver), Name, Arity]);
|
||||
|
||||
format_error({ ambiguous_call, { Mod1, Mod2, Name, Arity }}) ->
|
||||
@@ -392,11 +398,10 @@ in_elixir_macros() ->
|
||||
in_erlang_functions() ->
|
||||
[
|
||||
{ abs, 1 },
|
||||
{ atom_to_binary, 2 },
|
||||
{ apply, 2 },
|
||||
{ apply, 3 },
|
||||
{ atom_to_list, 1 },
|
||||
{ binary_part, 3 },
|
||||
{ binary_to_atom, 2 },
|
||||
{ binary_to_existing_atom, 2 },
|
||||
{ binary_to_float, 1 },
|
||||
{ binary_to_float, 2 },
|
||||
{ binary_to_integer, 1 },
|
||||
@@ -440,7 +445,6 @@ in_erlang_functions() ->
|
||||
{ list_to_float, 1 },
|
||||
{ list_to_integer, 1 },
|
||||
{ list_to_integer, 2 },
|
||||
{ list_to_pid, 1 },
|
||||
{ list_to_tuple, 1 },
|
||||
{ make_ref, 0 },
|
||||
{ max, 2 },
|
||||
@@ -448,7 +452,6 @@ in_erlang_functions() ->
|
||||
{ node, 0 },
|
||||
{ node, 1 },
|
||||
% { now, 0 },
|
||||
{ pid_to_list, 1 },
|
||||
{ round, 1 },
|
||||
{ self, 0 },
|
||||
{ size, 1 },
|
||||
@@ -490,8 +493,6 @@ in_erlang_macros() ->
|
||||
{'>=',2},
|
||||
{'@',1},
|
||||
{'and',2},
|
||||
{apply,2},
|
||||
{apply,3},
|
||||
{'case',2},
|
||||
{def,1},
|
||||
{def,2},
|
||||
|
||||
@@ -12,18 +12,25 @@
|
||||
-include("elixir.hrl").
|
||||
|
||||
warn(Warning) ->
|
||||
elixir_code_server:cast(register_warning),
|
||||
CompilerPid = get(elixir_compiler_pid),
|
||||
|
||||
if
|
||||
CompilerPid =/= undefined ->
|
||||
elixir_code_server:cast({ register_warning, CompilerPid });
|
||||
true -> false
|
||||
end,
|
||||
|
||||
io:put_chars(standard_error, Warning).
|
||||
|
||||
%% Handle inspecting for exceptions
|
||||
%% Handle inspecting for exceptions modules
|
||||
|
||||
inspect(Atom) when is_atom(Atom) ->
|
||||
'Elixir.Inspect.Atom':inspect(Atom).
|
||||
|
||||
%% Raised during macros translation.
|
||||
|
||||
-spec syntax_error(non_neg_integer() | list(), file:filename(), binary() | string()) -> no_return().
|
||||
-spec syntax_error(non_neg_integer() | list(), file:filename(), binary() | string(), list()) -> no_return().
|
||||
-spec syntax_error(non_neg_integer() | list(), file:filename_all(), binary() | string()) -> no_return().
|
||||
-spec syntax_error(non_neg_integer() | list(), file:filename_all(), binary() | string(), list()) -> no_return().
|
||||
|
||||
syntax_error(Meta, File, Message) when is_list(Message) ->
|
||||
syntax_error(Meta, File, iolist_to_binary(Message));
|
||||
@@ -47,7 +54,7 @@ compile_error(Meta, File, Format, Args) ->
|
||||
|
||||
%% Raised on tokenizing/parsing
|
||||
|
||||
-spec parse_error(non_neg_integer() | list(), file:filename(), iolist() | atom(), [] | iolist()) -> no_return().
|
||||
-spec parse_error(non_neg_integer() | list(), file:filename_all(), iolist() | atom(), [] | iolist()) -> no_return().
|
||||
|
||||
parse_error(Meta, File, Error, []) ->
|
||||
Message = case Error of
|
||||
@@ -75,7 +82,7 @@ parse_error(Meta, File, Error, Token) ->
|
||||
|
||||
%% Raised during compilation
|
||||
|
||||
-spec form_error(non_neg_integer() | list(), file:filename(), module(), any()) -> no_return().
|
||||
-spec form_error(non_neg_integer() | list(), file:filename_all(), module(), any()) -> no_return().
|
||||
|
||||
form_error(Meta, File, Module, Desc) ->
|
||||
Message = iolist_to_binary(format_error(Module, Desc)),
|
||||
@@ -158,7 +165,7 @@ handle_file_warning(_, File, {Line,Module,Desc}) ->
|
||||
handle_file_warning(File, Desc) ->
|
||||
handle_file_warning(false, File, Desc).
|
||||
|
||||
-spec handle_file_error(file:filename(), {non_neg_integer(), module(), any()}) -> no_return().
|
||||
-spec handle_file_error(file:filename_all(), {non_neg_integer(), module(), any()}) -> no_return().
|
||||
|
||||
handle_file_error(File, {Line,erl_lint,{unsafe_var,Var,{In,_Where}}}) ->
|
||||
Translated = case In of
|
||||
@@ -169,6 +176,10 @@ handle_file_error(File, {Line,erl_lint,{unsafe_var,Var,{In,_Where}}}) ->
|
||||
Message = io_lib:format("cannot define variable ~ts inside ~ts", [format_var(Var), Translated]),
|
||||
raise(Line, File, 'Elixir.CompileError', iolist_to_binary(Message));
|
||||
|
||||
handle_file_error(File, {Line,erl_lint,{spec_fun_undefined,{M,F,A}}}) ->
|
||||
Message = io_lib:format("spec for undefined function ~ts.~ts/~B", [inspect(M), F, A]),
|
||||
raise(Line, File, 'Elixir.CompileError', iolist_to_binary(Message));
|
||||
|
||||
handle_file_error(File, {Line,Module,Desc}) ->
|
||||
form_error(Line, File, Module, Desc).
|
||||
|
||||
@@ -178,18 +189,6 @@ assert_no_function_scope(_Meta, _Kind, #elixir_scope{function=nil}) -> [];
|
||||
assert_no_function_scope(Meta, Kind, S) ->
|
||||
syntax_error(Meta, S#elixir_scope.file, "cannot invoke ~ts inside a function", [Kind]).
|
||||
|
||||
assert_no_match_or_guard_scope(Meta, Kind, S) ->
|
||||
assert_no_match_scope(Meta, Kind, S),
|
||||
assert_no_guard_scope(Meta, Kind, S).
|
||||
|
||||
assert_no_match_scope(Meta, Kind, #elixir_scope{context=match} = S) ->
|
||||
syntax_error(Meta, S#elixir_scope.file, "cannot invoke ~ts inside match clause", [Kind]);
|
||||
assert_no_match_scope(_Meta, _Kind, _S) -> [].
|
||||
|
||||
assert_no_guard_scope(Meta, Kind, #elixir_scope{context=guard} = S) ->
|
||||
syntax_error(Meta, S#elixir_scope.file, "cannot invoke ~ts inside guard", [Kind]);
|
||||
assert_no_guard_scope(_Meta, _Kind, _S) -> [].
|
||||
|
||||
assert_module_scope(Meta, Kind, #elixir_scope{module=nil,file=File}) ->
|
||||
syntax_error(Meta, File, "cannot invoke ~ts outside module", [Kind]);
|
||||
assert_module_scope(_Meta, _Kind, #elixir_scope{module=Module}) -> Module.
|
||||
@@ -198,6 +197,18 @@ assert_function_scope(Meta, Kind, #elixir_scope{function=nil,file=File}) ->
|
||||
syntax_error(Meta, File, "cannot invoke ~ts outside function", [Kind]);
|
||||
assert_function_scope(_Meta, _Kind, #elixir_scope{function=Function}) -> Function.
|
||||
|
||||
assert_no_match_or_guard_scope(Meta, Kind, S) ->
|
||||
assert_no_match_scope(Meta, Kind, S),
|
||||
assert_no_guard_scope(Meta, Kind, S).
|
||||
|
||||
assert_no_match_scope(Meta, _Kind, #elixir_scope{context=match} = S) ->
|
||||
compile_error(Meta, S#elixir_scope.file, "invalid pattern in match clause", []);
|
||||
assert_no_match_scope(_Meta, _Kind, _S) -> [].
|
||||
|
||||
assert_no_guard_scope(Meta, _Kind, #elixir_scope{context=guard} = S) ->
|
||||
compile_error(Meta, S#elixir_scope.file, "invalid pattern in guard", []);
|
||||
assert_no_guard_scope(_Meta, _Kind, _S) -> [].
|
||||
|
||||
%% Helpers
|
||||
|
||||
raise(Meta, File, Kind, Message) when is_list(Meta) ->
|
||||
|
||||
@@ -48,6 +48,9 @@ capture(Meta, { { '.', _, [_, Fun] }, _, Args } = Expr, S) when is_atom(Fun), is
|
||||
capture(Meta, { { '.', _, [_] }, _, Args } = Expr, S) when is_list(Args) ->
|
||||
do_capture(Meta, Expr, S, false);
|
||||
|
||||
capture(Meta, { '__block__', _, [Expr] }, S) ->
|
||||
capture(Meta, Expr, S);
|
||||
|
||||
capture(Meta, { '__block__', _, _ } = Expr, S) ->
|
||||
Message = "invalid args for &, block expressions are not allowed, got: ~ts",
|
||||
syntax_error(Meta, S#elixir_scope.file, Message, ['Elixir.Macro':to_string(Expr)]);
|
||||
@@ -59,7 +62,7 @@ capture(Meta, { Left, Right }, S) ->
|
||||
capture(Meta, { '{}', Meta, [Left, Right] }, S);
|
||||
|
||||
capture(Meta, List, S) when is_list(List) ->
|
||||
capture(Meta, { '[]', Meta, List }, S);
|
||||
do_capture(Meta, List, S, is_sequential_and_not_empty(List));
|
||||
|
||||
capture(Meta, Arg, S) when is_integer(Arg) ->
|
||||
compile_error(Meta, S#elixir_scope.file, "unhandled &~B outside of a capture", [Arg]);
|
||||
|
||||
@@ -38,15 +38,14 @@ import_macros(Force, Meta, Ref, Opts, S) ->
|
||||
S#elixir_scope.macro_macros, Existing, S),
|
||||
S#elixir_scope{macros=Macros, macro_macros=Temp}.
|
||||
|
||||
record_warn(_Meta, _Ref, _Opts, #elixir_scope{module=nil}) -> false;
|
||||
record_warn(Meta, Ref, Opts, #elixir_scope{module=Module}) ->
|
||||
record_warn(Meta, Ref, Opts, S) ->
|
||||
Warn =
|
||||
case keyfind(warn, Opts) of
|
||||
{ warn, false } -> false;
|
||||
{ warn, true } -> true;
|
||||
false -> not lists:keymember(context, 1, Meta)
|
||||
end,
|
||||
elixir_tracker:record_warn(Ref, Warn, ?line(Meta), Module).
|
||||
elixir_lexical:record_import(Ref, ?line(Meta), Warn, S#elixir_scope.lexical_tracker).
|
||||
|
||||
%% Calculates the imports based on only and except
|
||||
|
||||
@@ -202,7 +201,6 @@ special_form('__block__',_) -> true;
|
||||
special_form('->',_) -> true;
|
||||
special_form('<<>>',_) -> true;
|
||||
special_form('{}',_) -> true;
|
||||
special_form('[]',_) -> true;
|
||||
special_form('alias',1) -> true;
|
||||
special_form('alias',2) -> true;
|
||||
special_form('require',1) -> true;
|
||||
|
||||
@@ -6,62 +6,70 @@ unescape_tokens/1, unescape_tokens/2, unescape_map/1]).
|
||||
|
||||
%% Extract string interpolations
|
||||
|
||||
extract(Line, File, Interpol, String, Last) ->
|
||||
extract(Line, File, Interpol, String, [], [], [], Last).
|
||||
extract(Line, Raw, Interpol, String, Last) ->
|
||||
%% Ignore whatever is in the scope and enable terminator checking.
|
||||
Scope = Raw#elixir_tokenizer{terminators=[], check_terminators=true},
|
||||
extract(Line, Scope, Interpol, String, [], 0, [], Last).
|
||||
|
||||
extract(Line, File, _Interpol, [], Buffer, [], Output, []) ->
|
||||
finish_extraction(Line, File, Buffer, Output, []);
|
||||
%% Terminators
|
||||
|
||||
extract(Line, _File, _Interpol, [], _Buffer, [], _Output, Last) ->
|
||||
{ error, { Line, io_lib:format("missing terminator: ~ts", [[Last]]), [] } };
|
||||
extract(Line, _Scope, _Interpol, [], Buffer, 0, Output, []) ->
|
||||
finish_extraction(Line, Buffer, Output, []);
|
||||
|
||||
extract(Line, File, _Interpol, [Last|Remaining], Buffer, [], Output, Last) ->
|
||||
finish_extraction(Line, File, Buffer, Output, Remaining);
|
||||
extract(Line, _Scope, _Interpol, [], _Buffer, 0, _Output, Last) ->
|
||||
{ error, { string, Line, io_lib:format("missing terminator: ~ts", [[Last]]), [] } };
|
||||
|
||||
extract(Line, _File, _Interpol, End, _Buffer, _Search, _Output, Last) when End == [Last]; End == [] ->
|
||||
{ error, { Line, io_lib:format("missing terminator: ~ts", [[Last]]), [] } };
|
||||
extract(Line, _Scope, _Interpol, [Last|Remaining], Buffer, 0, Output, Last) ->
|
||||
finish_extraction(Line, Buffer, Output, Remaining);
|
||||
|
||||
extract(Line, File, Interpol, [$\n|Rest], Buffer, Search, Output, Last) ->
|
||||
extract(Line+1, File, Interpol, Rest, [$\n|Buffer], Search, Output, Last);
|
||||
extract(Line, _Scope, _Interpol, [], _Buffer, _Search, _Output, Last) ->
|
||||
{ error, { string, Line, io_lib:format("missing terminator: ~ts", [[Last]]), [] } };
|
||||
|
||||
extract(Line, File, Interpol, [$\\, $#, ${|Rest], Buffer, [], Output, Last) ->
|
||||
extract(Line, File, Interpol, Rest, [${,$#|Buffer], [], Output, Last);
|
||||
%% Going through the string
|
||||
|
||||
extract(Line, File, Interpol, [$\\,Char|Rest], Buffer, [], Output, Last) ->
|
||||
extract(Line, File, Interpol, Rest, [Char,$\\|Buffer], [], Output, Last);
|
||||
extract(Line, Scope, Interpol, [$\n|Rest], Buffer, Search, Output, Last) ->
|
||||
extract(Line+1, Scope, Interpol, Rest, [$\n|Buffer], Search, Output, Last);
|
||||
|
||||
extract(Line, File, true, [$#, ${|Rest], Buffer, [], Output, Last) ->
|
||||
NewOutput = build_interpol(s, Line, File, Buffer, Output),
|
||||
extract(Line, File, true, Rest, [], [$}], NewOutput, Last);
|
||||
extract(Line, Scope, Interpol, [$\\, $#, ${|Rest], Buffer, Search, Output, Last) ->
|
||||
extract(Line, Scope, Interpol, Rest, [${,$#|Buffer], Search, Output, Last);
|
||||
|
||||
extract(Line, File, true, [$}|Rest], Buffer, [$}], Output, Last) ->
|
||||
NewOutput = build_interpol(i, Line, File, Buffer, Output),
|
||||
extract(Line, File, true, Rest, [], [], NewOutput, Last);
|
||||
extract(Line, Scope, Interpol, [$\\,Char|Rest], Buffer, Search, Output, Last) ->
|
||||
extract(Line, Scope, Interpol, Rest, [Char,$\\|Buffer], Search, Output, Last);
|
||||
|
||||
%% Check for available separators inside interpolation
|
||||
extract(Line, Scope, true, [$#, ${|Rest], Buffer, Search, Output, Last) ->
|
||||
Output1 = build_string(Line, Buffer, Output),
|
||||
|
||||
extract(Line, File, Interpol, [C|Rest], Buffer, [C|Search], Output, Last) when C == $); C == $]; C == $}; C == $>; C == $"; C == $' ->
|
||||
extract(Line, File, Interpol, Rest, [C|Buffer], Search, Output, Last);
|
||||
case elixir_tokenizer:tokenize(Rest, Line, Scope) of
|
||||
{ error, { EndLine, _, "}" }, [$}|NewRest], Tokens } ->
|
||||
Output2 = build_interpol(Line, Tokens, Output1),
|
||||
extract(EndLine, Scope, true, NewRest, [], Search, Output2, Last);
|
||||
{ error, Reason, _, _ } ->
|
||||
{ error, Reason };
|
||||
{ ok, _EndLine, _ } ->
|
||||
{ error, { string, Line, "missing interpolation terminator: }", [] } }
|
||||
end;
|
||||
|
||||
extract(Line, File, Interpol, [C|Rest], Buffer, [_|_] = Search, Output, Last) when C == $"; C == $' ->
|
||||
extract(Line, File, Interpol, Rest, [C|Buffer], [C|Search], Output, Last);
|
||||
%% Matching () [] {} <> inside sigils
|
||||
|
||||
extract(Line, File, Interpol, [$<|Rest], Buffer, [_|_] = Search, Output, Last) ->
|
||||
extract(Line, File, Interpol, Rest, [$<|Buffer], [$>|Search], Output, Last);
|
||||
extract(Line, Scope, Interpol, [$(|Rest], Buffer, Search, Output, $)) ->
|
||||
extract(Line, Scope, Interpol, Rest, [$(|Buffer], Search + 1, Output, $));
|
||||
|
||||
extract(Line, File, Interpol, [${|Rest], Buffer, [_|_] = Search, Output, Last) ->
|
||||
extract(Line, File, Interpol, Rest, [${|Buffer], [$}|Search], Output, Last);
|
||||
extract(Line, Scope, Interpol, [$[|Rest], Buffer, Search, Output, $]) ->
|
||||
extract(Line, Scope, Interpol, Rest, [$[|Buffer], Search + 1, Output, $]);
|
||||
|
||||
extract(Line, File, Interpol, [$[|Rest], Buffer, [_|_] = Search, Output, Last) ->
|
||||
extract(Line, File, Interpol, Rest, [$[|Buffer], [$]|Search], Output, Last);
|
||||
extract(Line, Scope, Interpol, [${|Rest], Buffer, Search, Output, $}) ->
|
||||
extract(Line, Scope, Interpol, Rest, [${|Buffer], Search + 1, Output, $});
|
||||
|
||||
extract(Line, File, Interpol, [$(|Rest], Buffer, [_|_] = Search, Output, Last) ->
|
||||
extract(Line, File, Interpol, Rest, [$(|Buffer], [$)|Search], Output, Last);
|
||||
extract(Line, Scope, Interpol, [$<|Rest], Buffer, Search, Output, $>) ->
|
||||
extract(Line, Scope, Interpol, Rest, [$<|Buffer], Search + 1, Output, $>);
|
||||
|
||||
%% Else
|
||||
extract(Line, Scope, Interpol, [Last|Rest], Buffer, Search, Output, Last) ->
|
||||
extract(Line, Scope, Interpol, Rest, [Last|Buffer], Search - 1, Output, Last);
|
||||
|
||||
extract(Line, File, Interpol, [Char|Rest], Buffer, Search, Output, Last) ->
|
||||
extract(Line, File, Interpol, Rest, [Char|Buffer], Search, Output, Last).
|
||||
%% Catch all clause
|
||||
|
||||
extract(Line, Scope, Interpol, [Char|Rest], Buffer, Search, Output, Last) ->
|
||||
extract(Line, Scope, Interpol, Rest, [Char|Buffer], Search, Output, Last).
|
||||
|
||||
%% Unescape a series of tokens as returned by extract.
|
||||
|
||||
@@ -148,30 +156,16 @@ unescape_map(E) -> E.
|
||||
|
||||
% Extract Helpers
|
||||
|
||||
finish_extraction(Line, File, Buffer, Output, Remaining) ->
|
||||
case build_interpol(s, Line, File, Buffer, Output) of
|
||||
finish_extraction(Line, Buffer, Output, Remaining) ->
|
||||
case build_string(Line, Buffer, Output) of
|
||||
[] -> Final = [<<>>];
|
||||
Final -> []
|
||||
end,
|
||||
{ Line, lists:reverse(Final), Remaining }.
|
||||
|
||||
build_interpol(_Kind, _Line, _File, [], Output) ->
|
||||
Output;
|
||||
build_string(_Line, [], Output) -> Output;
|
||||
build_string(_Line, Buffer, Output) ->
|
||||
[elixir_utils:characters_to_binary(lists:reverse(Buffer))|Output].
|
||||
|
||||
build_interpol(s, _Line, _File, Buffer, Output) ->
|
||||
[elixir_utils:characters_to_binary(lists:reverse(Buffer))|Output];
|
||||
|
||||
build_interpol(i, Line, File, Buffer, Output) ->
|
||||
[wrap_interpol(Line, forms(lists:reverse(Buffer), Line, File))| Output].
|
||||
|
||||
wrap_interpol(Line, Form) ->
|
||||
Meta = [{line,Line}],
|
||||
{ '::', Meta, [{ { '.', Meta, ['Elixir.Kernel', to_string] }, Meta, [Form]}, { binary, Meta, nil }]}.
|
||||
|
||||
forms(String, StartLine, File) ->
|
||||
case elixir_translator:forms(String, StartLine, File, []) of
|
||||
{ ok, [] } -> nil;
|
||||
{ ok, [Forms] } when not is_list(Forms) -> Forms;
|
||||
{ ok, Forms } -> { '__block__', [{line,StartLine}], Forms };
|
||||
{ error, Tuple } -> throw({ interpolation_error, Tuple })
|
||||
end.
|
||||
build_interpol(Line, Buffer, Output) ->
|
||||
[{ Line, lists:reverse(Buffer) }|Output].
|
||||
|
||||
@@ -0,0 +1,80 @@
|
||||
%% Module responsible for tracking lexical information.
|
||||
-module(elixir_lexical).
|
||||
-export([run/2,
|
||||
record_alias/4, record_alias/2,
|
||||
record_import/4, record_import/2,
|
||||
record_remote/2, format_error/1
|
||||
]).
|
||||
-include("elixir.hrl").
|
||||
|
||||
-define(tracker, 'Elixir.Kernel.LexicalTracker').
|
||||
|
||||
run(File, Callback) ->
|
||||
case code:is_loaded(?tracker) of
|
||||
{ file, _ } ->
|
||||
Pid = ?tracker:start_link(),
|
||||
|
||||
try
|
||||
Callback(Pid)
|
||||
after
|
||||
warn_unused_aliases(File, Pid),
|
||||
warn_unused_imports(File, Pid),
|
||||
unlink(Pid), ?tracker:stop(Pid)
|
||||
end;
|
||||
false ->
|
||||
Callback(nil)
|
||||
end.
|
||||
|
||||
%% RECORD
|
||||
|
||||
record_alias(Module, Line, Warn, Ref) ->
|
||||
if_tracker(Ref, fun(Pid) ->
|
||||
?tracker:add_alias(Pid, Module, Line, Warn),
|
||||
true
|
||||
end).
|
||||
|
||||
record_import(Module, Line, Warn, Ref) ->
|
||||
if_tracker(Ref, fun(Pid) ->
|
||||
?tracker:add_import(Pid, Module, Line, Warn),
|
||||
true
|
||||
end).
|
||||
|
||||
record_alias(Module, Ref) ->
|
||||
if_tracker(Ref, fun(Pid) ->
|
||||
?tracker:alias_dispatch(Pid, Module),
|
||||
true
|
||||
end).
|
||||
|
||||
record_import(Module, Ref) ->
|
||||
if_tracker(Ref, fun(Pid) ->
|
||||
?tracker:import_dispatch(Pid, Module),
|
||||
true
|
||||
end).
|
||||
|
||||
record_remote(Module, Ref) ->
|
||||
if_tracker(Ref, fun(Pid) ->
|
||||
?tracker:remote_dispatch(Pid, Module),
|
||||
true
|
||||
end).
|
||||
|
||||
%% HELPERS
|
||||
|
||||
if_tracker(nil, _Callback) -> false;
|
||||
if_tracker(Pid, Callback) when is_pid(Pid) -> Callback(Pid).
|
||||
|
||||
%% ERROR HANDLING
|
||||
|
||||
warn_unused_imports(File, Pid) ->
|
||||
[ begin
|
||||
elixir_errors:handle_file_warning(File, { L, ?MODULE, { unused_import, M } })
|
||||
end || { M, L } <- ?tracker:collect_unused_imports(Pid)].
|
||||
|
||||
warn_unused_aliases(File, Pid) ->
|
||||
[ begin
|
||||
elixir_errors:handle_file_warning(File, { L, ?MODULE, { unused_alias, M } })
|
||||
end || { M, L } <- ?tracker:collect_unused_aliases(Pid)].
|
||||
|
||||
format_error({unused_alias, Module}) ->
|
||||
io_lib:format("unused alias ~ts", [elixir_errors:inspect(Module)]);
|
||||
format_error({unused_import, Module}) ->
|
||||
io_lib:format("unused import ~ts", [elixir_errors:inspect(Module)]).
|
||||
@@ -5,55 +5,40 @@
|
||||
-import(elixir_scope, [umergec/2]).
|
||||
-include("elixir.hrl").
|
||||
|
||||
translate({ '<<>>', Meta, Args } = Original, S) when is_list(Args) ->
|
||||
case elixir_partials:handle(Original, S, allow_tail) of
|
||||
error ->
|
||||
case S#elixir_scope.context of
|
||||
match ->
|
||||
build_bitstr(fun elixir_translator:translate_each/2, Args, Meta, S);
|
||||
_ ->
|
||||
{ TArgs, { SC, SV } } = build_bitstr(fun elixir_translator:translate_arg/2, Args, Meta, { S, S }),
|
||||
{ TArgs, umergec(SV, SC) }
|
||||
end;
|
||||
Else -> Else
|
||||
translate({ '<<>>', Meta, Args }, S) when is_list(Args) ->
|
||||
case S#elixir_scope.context of
|
||||
match ->
|
||||
build_bitstr(fun elixir_translator:translate_each/2, Args, Meta, S);
|
||||
_ ->
|
||||
{ TArgs, { SC, SV } } = build_bitstr(fun elixir_translator:translate_arg/2, Args, Meta, { S, S }),
|
||||
{ TArgs, umergec(SV, SC) }
|
||||
end;
|
||||
|
||||
translate({ '{}', Meta, Args } = Original, S) when is_list(Args) ->
|
||||
case elixir_partials:handle(Original, S) of
|
||||
error ->
|
||||
{ TArgs, SE } = translate_args(Args, S),
|
||||
{ { tuple, ?line(Meta), TArgs }, SE };
|
||||
Else -> Else
|
||||
end;
|
||||
|
||||
translate({ '[]', _Meta, [] }, S) ->
|
||||
{ { nil, 0 }, S };
|
||||
|
||||
translate({ '[]', Meta, 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]} ->
|
||||
RExprs = [Left|RArgs],
|
||||
TailFun = fun(ST) -> translate_each(Right, ST) end;
|
||||
_ ->
|
||||
RExprs = [RTail|RArgs],
|
||||
TailFun = fun(ST) -> { { nil, ?line(Meta) }, ST } end
|
||||
end,
|
||||
|
||||
{ Exprs, SE } = translate_args(lists:reverse(RExprs), S),
|
||||
{ Tail, ST } = TailFun(SE),
|
||||
{ elixir_utils:list_to_cons(?line(Meta), Exprs, Tail), ST };
|
||||
Else -> Else
|
||||
end;
|
||||
translate({ '{}', Meta, Args }, S) when is_list(Args) ->
|
||||
{ TArgs, SE } = translate_args(Args, S),
|
||||
{ { tuple, ?line(Meta), TArgs }, SE };
|
||||
|
||||
translate({ Left, Right }, S) ->
|
||||
translate({ '{}', [], [Left, Right]}, S);
|
||||
|
||||
translate(Args, S) when is_list(Args) ->
|
||||
translate({ '[]', [], Args }, S);
|
||||
translate([], S) ->
|
||||
{ { nil, 0 }, S };
|
||||
|
||||
translate([_|_] = Args, S) ->
|
||||
[RTail|RArgs] = lists:reverse(Args),
|
||||
|
||||
case RTail of
|
||||
{ '|', _, [Left,Right] } ->
|
||||
RExprs = [Left|RArgs],
|
||||
TailFun = fun(ST) -> translate_each(Right, ST) end;
|
||||
_ ->
|
||||
RExprs = [RTail|RArgs],
|
||||
TailFun = fun(ST) -> { { nil, 0 }, ST } end
|
||||
end,
|
||||
|
||||
{ Exprs, SE } = translate_args(lists:reverse(RExprs), S),
|
||||
{ Tail, ST } = TailFun(SE),
|
||||
{ elixir_utils:list_to_cons(0, Exprs, Tail), ST };
|
||||
|
||||
translate(Tuple, S) when is_tuple(Tuple) ->
|
||||
elixir_errors:compile_error(0, S#elixir_scope.file,
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
%% but do not since they need to be implemented in Erlang.
|
||||
-module(elixir_macros).
|
||||
-export([translate/2]).
|
||||
-import(elixir_translator, [translate_each/2, translate_args/2, translate_apply/7]).
|
||||
-import(elixir_translator, [translate_each/2]).
|
||||
-import(elixir_scope, [umergec/2, umergea/2]).
|
||||
-import(elixir_errors, [compile_error/3, syntax_error/3, syntax_error/4,
|
||||
assert_no_function_scope/3, assert_module_scope/3, assert_no_match_or_guard_scope/3]).
|
||||
@@ -40,12 +40,7 @@ translate({ '!', Meta, [Expr] }, S) ->
|
||||
elixir_utils:convert_to_boolean(?line(Meta), TExpr, false, S#elixir_scope.context == guard, SE);
|
||||
|
||||
translate({ in, Meta, [Left, Right] }, #elixir_scope{extra_guards=nil} = S) ->
|
||||
{ _, TExpr, TS } = translate_in(Meta, Left, Right, S),
|
||||
{ TExpr, TS };
|
||||
|
||||
translate({ in, Meta, [Left, Right] }, #elixir_scope{extra_guards=Extra} = S) ->
|
||||
{ TVar, TExpr, TS } = translate_in(Meta, Left, Right, S),
|
||||
{ TVar, TS#elixir_scope{extra_guards=[TExpr|Extra]} };
|
||||
translate_in(Meta, Left, Right, S);
|
||||
|
||||
%% @
|
||||
|
||||
@@ -162,7 +157,7 @@ translate({defmodule, Meta, [Ref, KV]}, S) when is_list(KV) ->
|
||||
FullModule = expand_module(Ref, Module, S),
|
||||
|
||||
RS = case elixir_aliases:nesting_alias(S#elixir_scope.module, FullModule) of
|
||||
{ New, Old } -> elixir_aliases:store(Meta, New, Old, S);
|
||||
{ New, Old } -> elixir_aliases:store(Meta, New, Old, [{warn,false}], S);
|
||||
false -> S
|
||||
end,
|
||||
|
||||
@@ -189,27 +184,6 @@ translate({Kind, Meta, [Call, Expr]}, S) when ?defs(Kind) ->
|
||||
(not QC#elixir_quote.unquoted) andalso (not QE#elixir_quote.unquoted),
|
||||
{ elixir_def:wrap_definition(Kind, Meta, TCall, TExpr, CheckClauses, SE), SE };
|
||||
|
||||
translate({Kind, Meta, [Name, Args, Guards, Expr]}, S) when ?defs(Kind) ->
|
||||
elixir_errors:deprecation(Meta, S#elixir_scope.file, "~ts/4 is deprecated, please use ~ts/2 instead", [Kind, Kind]),
|
||||
assert_module_scope(Meta, Kind, S),
|
||||
assert_no_function_scope(Meta, Kind, S),
|
||||
{ TName, SN } = translate_each(Name, S),
|
||||
{ TArgs, SA } = translate_each(Args, SN),
|
||||
{ TGuards, SG } = translate_each(Guards, SA),
|
||||
{ TExpr, SE } = translate_each(Expr, SG),
|
||||
{ elixir_def:wrap_definition(Kind, Meta, TName, TArgs, TGuards, TExpr, false, SE), SE };
|
||||
|
||||
%% Apply - Optimize apply by checking what doesn't need to be dispatched dynamically
|
||||
|
||||
translate({ apply, Meta, [Left, Right, Args] }, S) when is_list(Args) ->
|
||||
{ TLeft, SL } = translate_each(Left, S),
|
||||
{ TRight, SR } = translate_each(Right, umergec(S, SL)),
|
||||
translate_apply(Meta, TLeft, TRight, Args, S, SL, SR);
|
||||
|
||||
translate({ apply, Meta, Args }, S) ->
|
||||
{ TArgs, NS } = translate_args(Args, S),
|
||||
{ ?wrap_call(?line(Meta), erlang, apply, TArgs), NS };
|
||||
|
||||
translate({ Name, Meta, Args }, S) ->
|
||||
syntax_error(Meta, S#elixir_scope.file,
|
||||
"invalid arguments for macro ~ts/~B", [Name, length(Args)]).
|
||||
@@ -219,13 +193,7 @@ translate({ Name, Meta, Args }, S) ->
|
||||
translate_in(Meta, Left, Right, S) ->
|
||||
Line = ?line(Meta),
|
||||
|
||||
{ TLeft, SL } = case Left of
|
||||
{ '_', _, Atom } when is_atom(Atom) ->
|
||||
elixir_scope:build_erl_var(Line, S);
|
||||
_ ->
|
||||
translate_each(Left, S)
|
||||
end,
|
||||
|
||||
{ TLeft, SL } = translate_each(Left, S),
|
||||
{ TRight, SR } = translate_each(Right, SL),
|
||||
|
||||
Cache = (S#elixir_scope.context == nil),
|
||||
@@ -242,13 +210,13 @@ translate_in(Meta, Left, Right, S) ->
|
||||
{ cons, _, _, _ } ->
|
||||
[H|T] = elixir_utils:cons_to_list(TRight),
|
||||
Expr = lists:foldr(fun(X, Acc) ->
|
||||
{ op, Line, 'orelse', { op, Line, '==', Var, X }, Acc }
|
||||
end, { op, Line, '==', Var, H }, T),
|
||||
{ op, Line, 'orelse', { op, Line, '=:=', Var, X }, Acc }
|
||||
end, { op, Line, '=:=', Var, H }, T),
|
||||
{ Cache, Expr };
|
||||
{ string, _, [H|T] } ->
|
||||
Expr = lists:foldl(fun(X, Acc) ->
|
||||
{ op, Line, 'orelse', { op, Line, '==', Var, { integer, Line, X } }, Acc }
|
||||
end, { op, Line, '==', Var, { integer, Line, H } }, T),
|
||||
{ op, Line, 'orelse', { op, Line, '=:=', Var, { integer, Line, X } }, Acc }
|
||||
end, { op, Line, '=:=', Var, { integer, Line, H } }, T),
|
||||
{ Cache, Expr };
|
||||
{ tuple, _, [{ atom, _, 'Elixir.Range' }, Start, End] } ->
|
||||
Expr = case { Start, End } of
|
||||
@@ -277,8 +245,8 @@ translate_in(Meta, Left, Right, S) ->
|
||||
end,
|
||||
|
||||
case TCache of
|
||||
true -> { Var, { block, Line, [ { match, Line, Var, TLeft }, TExpr ] }, SV };
|
||||
false -> { Var, TExpr, SV }
|
||||
true -> { { block, Line, [ { match, Line, Var, TLeft }, TExpr ] }, SV };
|
||||
false -> { TExpr, SV }
|
||||
end.
|
||||
|
||||
increasing_compare(Line, Var, Start, End) ->
|
||||
@@ -291,8 +259,18 @@ decreasing_compare(Line, Var, Start, End) ->
|
||||
{ op, Line, '=<', Var, Start },
|
||||
{ op, Line, '>=', Var, End } }.
|
||||
|
||||
rewrite_case_clauses([{do,Meta1,[{in,_,[{'_',_,_},[false,nil]]}],False},{do,Meta2,[{'_',_,_}],True}]) ->
|
||||
[{do,Meta1,[false],False},{do,Meta2,[true],True}];
|
||||
%% TODO: Once we have elixir_exp, we can move this
|
||||
%% clause to Elixir code and out of case.
|
||||
rewrite_case_clauses([
|
||||
{do,Meta1,[{'when',_,[{V,M,C},{in,_,[{V,M,C},[false,nil]]}]}],False},
|
||||
{do,Meta2,[{'_',_,UC}],True}] = Clauses)
|
||||
when is_atom(V), is_list(M), is_atom(C), is_atom(UC) ->
|
||||
case lists:keyfind('cond', 1, M) of
|
||||
{ 'cond', true } ->
|
||||
[{do,Meta1,[false],False},{do,Meta2,[true],True}];
|
||||
_ ->
|
||||
Clauses
|
||||
end;
|
||||
|
||||
rewrite_case_clauses(Clauses) ->
|
||||
Clauses.
|
||||
@@ -307,7 +285,8 @@ expand_module({ '__aliases__', _, [H] }, _Module, S) ->
|
||||
|
||||
%% defmodule Hello.World
|
||||
expand_module({ '__aliases__', _, _ } = Alias, Module, S) ->
|
||||
case elixir_aliases:expand(Alias, S#elixir_scope.aliases, S#elixir_scope.macro_aliases) of
|
||||
case elixir_aliases:expand(Alias, S#elixir_scope.aliases, S#elixir_scope.macro_aliases,
|
||||
S#elixir_scope.lexical_tracker) of
|
||||
Atom when is_atom(Atom) ->
|
||||
Module;
|
||||
Aliases when is_list(Aliases) ->
|
||||
@@ -319,6 +298,7 @@ expand_module(_Raw, Module, S) ->
|
||||
elixir_aliases:concat([S#elixir_scope.module, Module]).
|
||||
|
||||
is_reserved_data(moduledoc) -> true;
|
||||
is_reserved_data(typedoc) -> true;
|
||||
is_reserved_data(doc) -> true;
|
||||
is_reserved_data(_) -> false.
|
||||
|
||||
|
||||
@@ -3,12 +3,14 @@
|
||||
eval_quoted/4, format_error/1, eval_callbacks/5]).
|
||||
-include("elixir.hrl").
|
||||
|
||||
-define(docs_attr, '__docs_table').
|
||||
-define(acc_attr, '__acc_attributes').
|
||||
-define(docs_attr, '__docs_table').
|
||||
-define(lexical_attr, '__lexical_tracker').
|
||||
-define(persisted_attr, '__persisted_attributes').
|
||||
-define(overridable_attr, '__overridable').
|
||||
|
||||
eval_quoted(Module, Quoted, RawBinding, Opts) ->
|
||||
Binding = elixir_scope:binding_for_eval(RawBinding, Module),
|
||||
Binding = orddict:store('_@MODULE', Module, RawBinding),
|
||||
Scope = scope_for_eval(Module, Opts),
|
||||
|
||||
elixir_def:reset_last(Module),
|
||||
@@ -23,7 +25,6 @@ eval_quoted(Module, Quoted, RawBinding, Opts) ->
|
||||
|
||||
scope_for_eval(Module, #elixir_scope{} = S) ->
|
||||
S#elixir_scope{module=Module};
|
||||
|
||||
scope_for_eval(Module, Opts) ->
|
||||
scope_for_eval(Module, elixir:scope_for_eval(Opts)).
|
||||
|
||||
@@ -42,9 +43,14 @@ docs_table(Module) ->
|
||||
%% will be passed to the invoked function.
|
||||
|
||||
translate(Meta, Ref, Block, S) ->
|
||||
Line = ?line(Meta),
|
||||
MetaBlock = elixir_utils:elixir_to_erl(Line, Block, S),
|
||||
{ MetaS, Vars } = elixir_scope:serialize_with_vars(Line, S),
|
||||
Line = ?line(Meta),
|
||||
LexS = case S#elixir_scope.function of
|
||||
nil -> S;
|
||||
_ -> S#elixir_scope{lexical_tracker=nil}
|
||||
end,
|
||||
|
||||
MetaBlock = elixir_utils:elixir_to_erl(Line, Block, LexS),
|
||||
{ MetaS, Vars } = elixir_scope:serialize_with_vars(Line, LexS),
|
||||
|
||||
Args = [{integer, Line, Line}, Ref, MetaBlock, Vars, MetaS],
|
||||
?wrap_call(Line, ?MODULE, compile, Args).
|
||||
@@ -62,15 +68,16 @@ compile(Line, Module, Block, Vars, #elixir_scope{context_modules=FileModules} =
|
||||
FileList = elixir_utils:characters_to_list(File),
|
||||
|
||||
check_module_availability(Line, File, Module, C),
|
||||
build(Line, File, Module),
|
||||
build(Line, File, Module, S#elixir_scope.lexical_tracker),
|
||||
|
||||
try
|
||||
Result = eval_form(Line, Module, Block, Vars, S),
|
||||
{ Base, Export, Private, Def, Defmacro, Functions } = elixir_def:unwrap_stored_definitions(FileList, Module),
|
||||
|
||||
{ All, Forms0 } = functions_form(Line, File, Module, Base, Export, Def, Defmacro, Functions, C),
|
||||
Forms1 = specs_form(Line, Module, Private, Defmacro, Forms0, C),
|
||||
Forms1 = specs_form(Module, Private, Defmacro, Forms0, C),
|
||||
Forms2 = attributes_form(Line, File, Module, Forms1),
|
||||
Forms3 = typedocs_form(Module, Forms2),
|
||||
|
||||
case ets:lookup(data_table(Module), 'on_load') of
|
||||
[] -> ok;
|
||||
@@ -80,16 +87,16 @@ compile(Line, Module, Block, Vars, #elixir_scope{context_modules=FileModules} =
|
||||
|
||||
AllFunctions = Def ++ [T || { T, defp, _, _, _ } <- Private],
|
||||
elixir_tracker:ensure_no_function_conflict(Line, File, Module, AllFunctions),
|
||||
elixir_tracker:ensure_all_imports_used(Line, File, Module),
|
||||
elixir_tracker:warn_unused_local(File, Module, Private),
|
||||
warn_unused_docs(Line, File, Module, All),
|
||||
warn_invalid_clauses(Line, File, Module, All),
|
||||
warn_unused_docs(Line, File, Module),
|
||||
|
||||
Final = [
|
||||
{ attribute, Line, file, { FileList, Line } },
|
||||
{ attribute, Line, module, Module } | Forms2
|
||||
{ attribute, Line, module, Module } | Forms3
|
||||
],
|
||||
|
||||
Binary = load_form(Line, Final, S),
|
||||
Binary = load_form(Line, Final, compile_opts(Module), S),
|
||||
{ module, Module, Binary, Result }
|
||||
after
|
||||
elixir_tracker:cleanup(Module),
|
||||
@@ -108,7 +115,7 @@ compile(Line, Module, Block, Vars, RawS) ->
|
||||
|
||||
%% Hook that builds both attribute and functions and set up common hooks.
|
||||
|
||||
build(Line, File, Module) ->
|
||||
build(Line, File, Module, Lexical) ->
|
||||
%% Table with meta information about the module.
|
||||
DataTable = data_table(Module),
|
||||
|
||||
@@ -118,7 +125,6 @@ build(Line, File, Module) ->
|
||||
end,
|
||||
|
||||
ets:new(DataTable, [set, named_table, public]),
|
||||
ets:insert(DataTable, { '__overridable', [] }),
|
||||
ets:insert(DataTable, { before_compile, [] }),
|
||||
ets:insert(DataTable, { after_compile, [] }),
|
||||
|
||||
@@ -128,9 +134,11 @@ build(Line, File, Module) ->
|
||||
end,
|
||||
|
||||
Attributes = [behavior, behaviour, on_load, spec, type, export_type, opaque, callback, compile],
|
||||
ets:insert(DataTable, { ?acc_attr, [before_compile,after_compile,on_definition|Attributes] }),
|
||||
ets:insert(DataTable, { ?acc_attr, [before_compile, after_compile, on_definition|Attributes] }),
|
||||
ets:insert(DataTable, { ?persisted_attr, [vsn|Attributes] }),
|
||||
ets:insert(DataTable, { ?docs_attr, ets:new(DataTable, [ordered_set, public]) }),
|
||||
ets:insert(DataTable, { ?lexical_attr, Lexical }),
|
||||
ets:insert(DataTable, { ?overridable_attr, [] }),
|
||||
|
||||
%% Setup other modules
|
||||
elixir_def:setup(Module),
|
||||
@@ -198,9 +206,21 @@ attributes_form(Line, _File, Module, Current) ->
|
||||
|
||||
ets:foldl(Transform, Current, Table).
|
||||
|
||||
%% Add typedocs to the form
|
||||
typedocs_form(Module, Current) ->
|
||||
Table = docs_table(Module),
|
||||
Transform = fun({ Tuple, Line, Kind, _Sig, Doc }, Acc) ->
|
||||
case Kind of
|
||||
type -> [{ attribute, Line, typedoc, { Tuple, Doc } } | Acc];
|
||||
opaque -> [{ attribute, Line, typedoc, { Tuple, Doc } } | Acc];
|
||||
_ -> Acc
|
||||
end
|
||||
end,
|
||||
ets:foldl(Transform, Current, Table).
|
||||
|
||||
%% Specs
|
||||
|
||||
specs_form(Line, Module, Private, Defmacro, Forms, C) ->
|
||||
specs_form(Module, Private, Defmacro, Forms, C) ->
|
||||
Defmacrop = [Tuple || { Tuple, defmacrop, _, _, _ } <- Private],
|
||||
case elixir_compiler:get_opt(internal, C) of
|
||||
true -> Forms;
|
||||
@@ -212,16 +232,17 @@ specs_form(Line, Module, Private, Defmacro, Forms, C) ->
|
||||
'Elixir.Module':delete_attribute(Module, spec),
|
||||
'Elixir.Module':delete_attribute(Module, callback),
|
||||
|
||||
Temp = specs_attributes(Line, spec, Forms, Specs),
|
||||
specs_attributes(Line, callback, Temp, Callbacks)
|
||||
Temp = specs_attributes(spec, Forms, Specs),
|
||||
specs_attributes(callback, Temp, Callbacks)
|
||||
end.
|
||||
|
||||
specs_attributes(Line, Type, Forms, Specs) ->
|
||||
specs_attributes(Type, Forms, Specs) ->
|
||||
Keys = lists:foldl(fun({ Tuple, Value }, Acc) ->
|
||||
lists:keystore(Tuple, 1, Acc, { Tuple, Value } )
|
||||
lists:keystore(Tuple, 1, Acc, { Tuple, Value })
|
||||
end, [], Specs),
|
||||
lists:foldl(fun({ Tuple, _ }, Acc) ->
|
||||
Values = [V || { K, V } <- Specs, K == Tuple],
|
||||
{ type, Line, _, _ } = hd(Values),
|
||||
[{ attribute, Line, Type, { Tuple, Values } }|Acc]
|
||||
end, Forms, Keys).
|
||||
|
||||
@@ -246,8 +267,14 @@ spec_for_macro(Else) -> Else.
|
||||
|
||||
%% Loads the form into the code server.
|
||||
|
||||
load_form(Line, Forms, #elixir_scope{file=File} = S) ->
|
||||
elixir_compiler:module(Forms, File, fun(Module, Binary) ->
|
||||
compile_opts(Module) ->
|
||||
case ets:lookup(data_table(Module), compile) of
|
||||
[{compile,Opts}] when is_list(Opts) -> Opts;
|
||||
[] -> []
|
||||
end.
|
||||
|
||||
load_form(Line, Forms, Opts, #elixir_scope{file=File} = S) ->
|
||||
elixir_compiler:module(Forms, File, Opts, fun(Module, Binary) ->
|
||||
EvalS = scope_for_eval(Module, S),
|
||||
Env = elixir_scope:to_ex_env({ Line, EvalS }),
|
||||
eval_callbacks(Line, Module, after_compile, [Env, Binary], EvalS),
|
||||
@@ -259,8 +286,9 @@ load_form(Line, Forms, #elixir_scope{file=File} = S) ->
|
||||
case get(elixir_compiler_pid) of
|
||||
undefined -> [];
|
||||
PID ->
|
||||
PID ! { module_available, self(), File, Module, Binary },
|
||||
receive { PID, ack } -> ok end
|
||||
Ref = make_ref(),
|
||||
PID ! { module_available, self(), Ref, File, Module, Binary },
|
||||
receive { Ref, ack } -> ok end
|
||||
end;
|
||||
_ ->
|
||||
[]
|
||||
@@ -282,18 +310,31 @@ check_module_availability(Line, File, Module, Compiler) ->
|
||||
[]
|
||||
end.
|
||||
|
||||
warn_unused_docs(_Line, _File, 'Elixir.Kernel', _All) -> ok;
|
||||
warn_unused_docs(_Line, _File, 'Elixir.Kernel.SpecialForms', _All) -> ok;
|
||||
warn_unused_docs(_Line, File, Module, All) ->
|
||||
warn_invalid_clauses(_Line, _File, 'Elixir.Kernel', _All) -> ok;
|
||||
warn_invalid_clauses(_Line, _File, 'Elixir.Kernel.SpecialForms', _All) -> ok;
|
||||
warn_invalid_clauses(_Line, File, Module, All) ->
|
||||
ets:foldl(fun
|
||||
({ Tuple, Line, _, _, _ }, Acc) ->
|
||||
({ _, _, Kind, _, _ }, _) when Kind == type; Kind == opaque ->
|
||||
ok;
|
||||
({ Tuple, Line, _, _, _ }, _) ->
|
||||
case lists:member(Tuple, All) of
|
||||
true -> Acc;
|
||||
false ->
|
||||
elixir_errors:handle_file_warning(File, { Line, ?MODULE, { invalid_doc, Tuple } })
|
||||
elixir_errors:handle_file_warning(File, { Line, ?MODULE, { invalid_clause, Tuple } });
|
||||
true ->
|
||||
ok
|
||||
end
|
||||
end, ok, docs_table(Module)).
|
||||
|
||||
warn_unused_docs(Line, File, Module) ->
|
||||
lists:foreach(fun(Attribute) ->
|
||||
case ets:member(data_table(Module), Attribute) of
|
||||
true ->
|
||||
elixir_errors:handle_file_warning(File, { Line, ?MODULE, { unused_doc, Attribute } });
|
||||
_ ->
|
||||
ok
|
||||
end
|
||||
end, [typedoc]).
|
||||
|
||||
% EXTRA FUNCTIONS
|
||||
|
||||
add_info_function(Line, File, Module, Export, Def, Defmacro, C) ->
|
||||
@@ -329,7 +370,10 @@ module_clause(Module) ->
|
||||
{ clause, 0, [{ atom, 0, module }], [], [{ atom, 0, Module }] }.
|
||||
|
||||
docs_clause(Module, true) ->
|
||||
Docs = ordsets:from_list(ets:tab2list(docs_table(Module))),
|
||||
Docs = ordsets:from_list(
|
||||
[{Tuple, Line, Kind, Sig, Doc} ||
|
||||
{Tuple, Line, Kind, Sig, Doc} <- ets:tab2list(docs_table(Module)),
|
||||
Kind =/= type, Kind =/= opaque]),
|
||||
{ clause, 0, [{ atom, 0, docs }], [], [elixir_utils:elixir_to_erl(Docs)] };
|
||||
|
||||
docs_clause(_Module, _) ->
|
||||
@@ -349,8 +393,7 @@ else_clause() ->
|
||||
% HELPERS
|
||||
|
||||
eval_callbacks(Line, Module, Name, Args, RawS) ->
|
||||
Binding = elixir_scope:binding_for_eval([], Module),
|
||||
S = elixir_scope:vars_from_binding(RawS, Binding),
|
||||
{ Binding, S } = elixir_scope:load_binding([], RawS, Module),
|
||||
Callbacks = lists:reverse(ets:lookup_element(data_table(Module), Name, 2)),
|
||||
Meta = [{line,Line},{require,false}],
|
||||
|
||||
@@ -369,25 +412,34 @@ eval_callbacks(Line, Module, Name, Args, RawS) ->
|
||||
catch
|
||||
Kind:Reason ->
|
||||
Info = { M, F, length(Args), [{ file, elixir_utils:characters_to_list(S#elixir_scope.file) }, { line, Line }] },
|
||||
erlang:raise(Kind, Reason, munge_stacktrace(Info, erlang:get_stacktrace()))
|
||||
erlang:raise(Kind, Reason, prune_stacktrace(Info, erlang:get_stacktrace()))
|
||||
end
|
||||
end
|
||||
end, Callbacks).
|
||||
|
||||
%% We've reached the elixir_dispatch internals, skip it with the rest
|
||||
munge_stacktrace(Info, [{ elixir_module, _, _, _ }|_]) ->
|
||||
%% We've reached the elixir_module or erl_eval internals, skip it with the rest
|
||||
prune_stacktrace(Info, [{ erl_eval, _, _, _ }|_]) ->
|
||||
[Info];
|
||||
|
||||
munge_stacktrace(Info, [H|T]) ->
|
||||
[H|munge_stacktrace(Info, T)];
|
||||
prune_stacktrace(Info, [{ elixir_module, _, _, _ }|_]) ->
|
||||
[Info];
|
||||
|
||||
munge_stacktrace(Info, []) ->
|
||||
prune_stacktrace(Info, [H|T]) ->
|
||||
[H|prune_stacktrace(Info, T)];
|
||||
|
||||
prune_stacktrace(Info, []) ->
|
||||
[Info].
|
||||
|
||||
% ERROR HANDLING
|
||||
|
||||
format_error({ invalid_doc, { Name, Arity } }) ->
|
||||
io_lib:format("docs provided for nonexistent function or macro ~ts/~B", [Name, Arity]);
|
||||
format_error({ invalid_clause, { Name, Arity } }) ->
|
||||
io_lib:format("empty clause provided for nonexistent function or macro ~ts/~B", [Name, Arity]);
|
||||
|
||||
format_error({ unused_doc, typedoc }) ->
|
||||
"@typedoc provided but no type follows it";
|
||||
|
||||
format_error({ unused_doc, doc }) ->
|
||||
"@doc provided but no definition follows it";
|
||||
|
||||
format_error({ internal_function_overridden, { Name, Arity } }) ->
|
||||
io_lib:format("function ~ts/~B is internal and should not be overridden", [Name, Arity]);
|
||||
|
||||
@@ -20,7 +20,7 @@ Nonterminals
|
||||
call_args_no_parens_kw_expr call_args_no_parens_kw_comma call_args_no_parens_kw
|
||||
dot_op dot_alias dot_identifier dot_op_identifier dot_do_identifier
|
||||
dot_paren_identifier dot_bracket_identifier
|
||||
var list bracket_access bit_string tuple
|
||||
var list bit_string tuple
|
||||
do_block fn_eol do_eol end_eol block_eol block_item block_list
|
||||
.
|
||||
|
||||
@@ -28,7 +28,7 @@ Terminals
|
||||
identifier kw_identifier bracket_identifier
|
||||
paren_identifier do_identifier block_identifier
|
||||
fn 'end' aliases
|
||||
number signed_number atom bin_string list_string sigil
|
||||
number signed_number atom atom_string bin_string list_string sigil
|
||||
dot_call_op op_identifier
|
||||
comp_op at_op unary_op and_op or_op arrow_op match_op
|
||||
range_op in_op inc_op when_op than_op default_op tail_op
|
||||
@@ -179,20 +179,20 @@ max_expr -> parens_call call_args_parens call_args_parens : build_nested_parens(
|
||||
max_expr -> dot_alias : '$1'.
|
||||
max_expr -> base_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 -> dot_bracket_identifier list : build_access(build_identifier('$1', nil), '$2').
|
||||
bracket_expr -> max_expr list : build_access('$1', '$2').
|
||||
|
||||
bracket_at_expr -> at_op_eol dot_bracket_identifier bracket_access :
|
||||
bracket_at_expr -> at_op_eol dot_bracket_identifier list :
|
||||
build_access(build_unary_op('$1', build_identifier('$2', nil)), '$3').
|
||||
bracket_at_expr -> at_op_eol max_expr bracket_access :
|
||||
bracket_at_expr -> at_op_eol max_expr list :
|
||||
build_access(build_unary_op('$1', '$2'), '$3').
|
||||
bracket_at_expr -> bracket_at_expr bracket_access :
|
||||
bracket_at_expr -> bracket_at_expr list :
|
||||
build_access('$1', '$2').
|
||||
|
||||
base_expr -> atom : ?exprs('$1').
|
||||
base_expr -> number : ?exprs('$1').
|
||||
base_expr -> signed_number : { element(4, '$1'), [{line,?line('$1')}], ?exprs('$1') }.
|
||||
base_expr -> atom : build_atom('$1').
|
||||
base_expr -> list : '$1'.
|
||||
base_expr -> list : element(1, '$1').
|
||||
base_expr -> tuple : '$1'.
|
||||
base_expr -> 'true' : ?id('$1').
|
||||
base_expr -> 'false' : ?id('$1').
|
||||
@@ -200,6 +200,7 @@ base_expr -> 'nil' : ?id('$1').
|
||||
base_expr -> aliases : { '__aliases__', [{line,?line('$1')}], ?exprs('$1') }.
|
||||
base_expr -> bin_string : build_bin_string('$1').
|
||||
base_expr -> list_string : build_list_string('$1').
|
||||
base_expr -> atom_string : build_atom_string('$1').
|
||||
base_expr -> bit_string : '$1'.
|
||||
base_expr -> sigil : build_sigil('$1').
|
||||
base_expr -> '&' number : { '&', [{line,?line('$1')}], [?exprs('$2')] }.
|
||||
@@ -434,21 +435,17 @@ kw -> kw_expr : ['$1'].
|
||||
kw -> kw_comma : lists:reverse('$1').
|
||||
kw -> kw_comma kw_expr : lists:reverse(['$2'|'$1']).
|
||||
|
||||
call_args_no_parens_kw_expr -> kw_eol call_args_no_parens_expr : {?exprs('$1'),'$2'}.
|
||||
call_args_no_parens_kw_expr -> kw_eol call_args_no_parens_expr : { ?exprs('$1'),'$2' }.
|
||||
call_args_no_parens_kw_comma -> call_args_no_parens_kw_expr : ['$1'].
|
||||
call_args_no_parens_kw_comma -> call_args_no_parens_kw_expr ',' call_args_no_parens_kw_comma : ['$1'|'$3'].
|
||||
call_args_no_parens_kw -> call_args_no_parens_kw_comma : '$1'.
|
||||
|
||||
% Lists
|
||||
|
||||
bracket_access -> open_bracket ']' : { [], ?line('$1') }.
|
||||
bracket_access -> open_bracket container_arg close_bracket : { '$2', ?line('$1') }.
|
||||
bracket_access -> open_bracket kw close_bracket : { '$2', ?line('$1') }.
|
||||
|
||||
list -> open_bracket ']' : [].
|
||||
list -> open_bracket kw close_bracket : '$2'.
|
||||
list -> open_bracket container_arg close_bracket : ['$2'].
|
||||
list -> open_bracket container_expr ',' container_args close_bracket : ['$2'|'$4'].
|
||||
list -> open_bracket ']' : { [], ?line('$1') }.
|
||||
list -> open_bracket kw close_bracket : { '$2', ?line('$1') }.
|
||||
list -> open_bracket container_arg close_bracket : { ['$2'], ?line('$1') }.
|
||||
list -> open_bracket container_expr ',' container_args close_bracket : { ['$2'|'$4'], ?line('$1') }.
|
||||
|
||||
% Tuple
|
||||
|
||||
@@ -548,32 +545,56 @@ extract_identifier(Other) -> Other.
|
||||
%% Fn
|
||||
|
||||
build_fn(Op, Stab) ->
|
||||
{ fn, [{line,?line(Op)}], [[{ do, Stab }]] }.
|
||||
{ fn, [{line,?line(Op)}], [Stab] }.
|
||||
|
||||
%% Access
|
||||
|
||||
build_access(Expr, Access) ->
|
||||
Meta = [{line,?line(Access)}],
|
||||
{ { '.', Meta, ['Elixir.Kernel', access] }, Meta, [ Expr, ?id(Access) ] }.
|
||||
build_access(Expr, { List, Line }) ->
|
||||
Meta = [{line,Line}],
|
||||
{ { '.', Meta, ['Elixir.Kernel', access] }, Meta, [Expr, List] }.
|
||||
|
||||
%% Interpolation aware
|
||||
|
||||
build_sigil({ sigil, Line, Sigil, Parts, Modifiers }) ->
|
||||
Meta = [{line,Line}],
|
||||
{ list_to_atom("sigil_" ++ [Sigil]), Meta, [ { '<<>>', Meta, Parts }, Modifiers ] }.
|
||||
{ list_to_atom("sigil_" ++ [Sigil]), Meta, [ { '<<>>', Meta, string_parts(Parts) }, Modifiers ] }.
|
||||
|
||||
build_bin_string({ bin_string, _Line, [H] }) when is_binary(H) -> H;
|
||||
build_bin_string({ bin_string, Line, Args }) -> { '<<>>', [{line,Line}], Args }.
|
||||
build_bin_string({ bin_string, _Line, [H] }) when is_binary(H) ->
|
||||
H;
|
||||
build_bin_string({ bin_string, Line, Args }) ->
|
||||
{ '<<>>', [{line,Line}], string_parts(Args) }.
|
||||
|
||||
build_list_string({ list_string, _Line, [H] }) when is_binary(H) -> elixir_utils:characters_to_list(H);
|
||||
build_list_string({ list_string, _Line, [H] }) when is_binary(H) ->
|
||||
elixir_utils:characters_to_list(H);
|
||||
build_list_string({ list_string, Line, Args }) ->
|
||||
Meta = [{line,Line}],
|
||||
{ { '.', Meta, ['Elixir.String', 'to_char_list!'] }, Meta, [{ '<<>>', Meta, Args}] }.
|
||||
{ { '.', Meta, ['Elixir.String', 'to_char_list!'] }, Meta, [{ '<<>>', Meta, string_parts(Args) }] }.
|
||||
|
||||
build_atom({ atom, _Line, Atom }) when is_atom(Atom) -> Atom;
|
||||
build_atom({ atom, Line, Args }) ->
|
||||
build_atom_string({ atom_string, _Line, Safe, [H] }) when is_binary(H) ->
|
||||
Op = binary_to_atom_op(Safe), erlang:Op(H, utf8);
|
||||
build_atom_string({ atom_string, Line, Safe, Args }) ->
|
||||
Meta = [{line,Line}],
|
||||
{ { '.', Meta, [erlang, binary_to_atom] }, Meta, [{ '<<>>', Meta, Args }, utf8] }.
|
||||
{ { '.', Meta, [erlang, binary_to_atom_op(Safe)] }, Meta, [{ '<<>>', Meta, string_parts(Args) }, utf8] }.
|
||||
|
||||
binary_to_atom_op(true) -> binary_to_existing_atom;
|
||||
binary_to_atom_op(false) -> binary_to_atom.
|
||||
|
||||
string_parts(Parts) ->
|
||||
[string_part(Part) || Part <- Parts].
|
||||
string_part(Binary) when is_binary(Binary) ->
|
||||
Binary;
|
||||
string_part({ Line, Tokens }) ->
|
||||
Form = string_tokens_parse(Line, Tokens),
|
||||
Meta = [{line,Line}],
|
||||
{ '::', Meta, [{ { '.', Meta, ['Elixir.Kernel', to_string] }, Meta, [Form]}, { binary, Meta, nil }]}.
|
||||
|
||||
string_tokens_parse(Line, Tokens) ->
|
||||
case parse(Tokens) of
|
||||
{ ok, [] } -> nil;
|
||||
{ ok, [Forms] } when not is_list(Forms) -> Forms;
|
||||
{ ok, Forms } -> { '__block__', [{line, Line}], Forms };
|
||||
{ error, _ } = Error -> throw(Error)
|
||||
end.
|
||||
|
||||
%% Keywords
|
||||
|
||||
|
||||
@@ -1,65 +0,0 @@
|
||||
%% Responsible handling Elixir partials with &1, &2 and so on.
|
||||
-module(elixir_partials).
|
||||
-export([handle/2, handle/3, is_sequential/1]).
|
||||
-include("elixir.hrl").
|
||||
|
||||
handle(Original, S) ->
|
||||
handle(Original, S, default).
|
||||
|
||||
handle({ _, Meta, Args } = Original, S, Opt) when is_list(Args), S#elixir_scope.context /= match ->
|
||||
case convert(Args, S, Opt) of
|
||||
{ Call, Def, SC } when Def /= [] ->
|
||||
Final = validate(Meta, Def, SC),
|
||||
Block = setelement(3, Original, Call),
|
||||
elixir_fn:fn(Meta, [{ Final, Meta, Block }], SC);
|
||||
_ -> error
|
||||
end;
|
||||
|
||||
handle(_Original, _S, _Opt) ->
|
||||
error.
|
||||
|
||||
validate(Meta, Def, S) ->
|
||||
validate(Meta, lists:sort(Def), 1, S).
|
||||
|
||||
validate(Meta, [{ Pos, Item }|T], Pos, S) ->
|
||||
[Item|validate(Meta, T, Pos + 1, S)];
|
||||
|
||||
validate(Meta, [{ Pos, _ }|_], Expected, S) ->
|
||||
elixir_errors:syntax_error(Meta, S#elixir_scope.file, "partial variable &~w cannot be defined without &~w", [Pos, Expected]);
|
||||
|
||||
validate(_Meta, [], _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([{'|', Meta, [_, _] = Args}|T], S, allow_tail, CallAcc, DefAcc) ->
|
||||
{ NewArgs, NewDef, NewS } = convert(Args, S, allow_tail, [], DefAcc),
|
||||
convert(T, NewS, allow_tail, [{ '|', Meta, NewArgs}|CallAcc], NewDef);
|
||||
|
||||
convert([{'&', Meta, [Pos]}|T], S, Opt, CallAcc, DefAcc) when is_integer(Pos) ->
|
||||
case lists:keyfind(Pos, 1, DefAcc) of
|
||||
false ->
|
||||
{ Var, SC } = elixir_scope:build_ex_var(?line(Meta), 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.
|
||||
@@ -1,33 +1,47 @@
|
||||
%% Implements Elixir quote.
|
||||
-module(elixir_quote).
|
||||
-export([escape/2, erl_escape/3, erl_quote/4, linify/2, unquote/6, join/5]).
|
||||
-export([escape/2, erl_escape/3, erl_quote/4,
|
||||
linify/2, linify/3, unquote/4, tail_join/3, join/2]).
|
||||
-include("elixir.hrl").
|
||||
|
||||
%% Apply the line from site call on quoted contents.
|
||||
linify(Line, Exprs) when is_integer(Line) ->
|
||||
do_linify(Line, Exprs).
|
||||
do_linify(Line, nil, Exprs).
|
||||
|
||||
do_linify(Line, { Left, Meta, Right }) ->
|
||||
linify(Line, Var, Exprs) when is_integer(Line) ->
|
||||
do_linify(Line, Var, Exprs).
|
||||
|
||||
do_linify(Line, { Receiver, Counter } = Var, { Left, Meta, Receiver }) when is_atom(Left), is_list(Meta) ->
|
||||
NewMeta = case keyfind(counter, Meta) of
|
||||
{ counter, _ } -> Meta;
|
||||
_ -> keystore(counter, Meta, Counter)
|
||||
end,
|
||||
do_tuple_linify(Line, Var, NewMeta, Left, Receiver);
|
||||
|
||||
do_linify(Line, Var, { Left, Meta, Right }) when is_list(Meta) ->
|
||||
do_tuple_linify(Line, Var, Meta, Left, Right);
|
||||
|
||||
do_linify(Line, Var, { Left, Right }) ->
|
||||
{ do_linify(Line, Var, Left), do_linify(Line, Var, Right) };
|
||||
|
||||
do_linify(Line, Var, List) when is_list(List) ->
|
||||
[do_linify(Line, Var, X) || X <- List];
|
||||
|
||||
do_linify(_, _, Else) -> Else.
|
||||
|
||||
do_tuple_linify(Line, Var, Meta, Left, Right) ->
|
||||
NewMeta = case ?line(Meta) of
|
||||
0 -> keystore(line, Meta, Line);
|
||||
_ -> Meta
|
||||
end,
|
||||
{ do_linify(Line, Left), NewMeta, do_linify(Line, Right) };
|
||||
|
||||
do_linify(Line, { Left, Right }) ->
|
||||
{ do_linify(Line, Left), do_linify(Line, Right) };
|
||||
|
||||
do_linify(Line, List) when is_list(List) ->
|
||||
[do_linify(Line, X) || X <- List];
|
||||
|
||||
do_linify(_, Else) -> Else.
|
||||
{ do_linify(Line, Var, Left), NewMeta, do_linify(Line, Var, Right) }.
|
||||
|
||||
%% Some expressions cannot be unquoted at compilation time.
|
||||
%% This function is responsible for doing runtime unquoting.
|
||||
unquote(_File, _Line, Meta, Left, { '__aliases__', _, Args }, nil) ->
|
||||
unquote(Meta, Left, { '__aliases__', _, Args }, nil) ->
|
||||
{ '__aliases__', Meta, [Left|Args] };
|
||||
|
||||
unquote(_File, _Line, Meta, Left, Right, nil) when is_atom(Right) ->
|
||||
unquote(Meta, Left, Right, nil) when is_atom(Right) ->
|
||||
case atom_to_list(Right) of
|
||||
"Elixir." ++ _ ->
|
||||
{ '__aliases__', Meta, [Left, Right] };
|
||||
@@ -35,33 +49,48 @@ unquote(_File, _Line, Meta, Left, Right, nil) when is_atom(Right) ->
|
||||
{ { '.', Meta, [Left, Right] }, Meta, [] }
|
||||
end;
|
||||
|
||||
unquote(_File, _Line, Meta, Left, { Right, _, Context }, nil) when is_atom(Right), is_atom(Context) ->
|
||||
unquote(Meta, Left, { Right, _, Context }, nil) when is_atom(Right), is_atom(Context) ->
|
||||
{ { '.', Meta, [Left, Right] }, Meta, [] };
|
||||
|
||||
unquote(_File, _Line, Meta, Left, { Right, _, Args }, nil) when is_atom(Right) ->
|
||||
unquote(Meta, Left, { Right, _, Args }, nil) when is_atom(Right) ->
|
||||
{ { '.', Meta, [Left, Right] }, Meta, Args };
|
||||
|
||||
unquote(File, Line, _Meta, _Left, Right, nil) ->
|
||||
elixir_errors:syntax_error(Line, File, "expected unquote after dot to return an atom, "
|
||||
"an alias or a quoted call, got: ~ts", ['Elixir.Macro':to_string(Right)]);
|
||||
unquote(_Meta, _Left, Right, nil) ->
|
||||
argument_error(<<"expected unquote after dot to return an atom, an alias or a quoted call, got: ",
|
||||
('Elixir.Macro':to_string(Right))/binary>>);
|
||||
|
||||
unquote(_File, _Line, Meta, Left, Right, Args) when is_atom(Right) ->
|
||||
unquote(Meta, Left, Right, Args) when is_atom(Right) ->
|
||||
{ { '.', Meta, [Left, Right] }, Meta, Args };
|
||||
|
||||
unquote(_File, _Line, Meta, Left, { Right, _, Context }, Args) when is_atom(Right), is_atom(Context) ->
|
||||
unquote(Meta, Left, { Right, _, Context }, Args) when is_atom(Right), is_atom(Context) ->
|
||||
{ { '.', Meta, [Left, Right] }, Meta, Args };
|
||||
|
||||
unquote(File, Line, _Meta, _Left, Right, _Args) ->
|
||||
elixir_errors:syntax_error(Line, File, "expected unquote after dot with args to return an atom "
|
||||
"or a quoted call, got: ~ts", ['Elixir.Macro':to_string(Right)]).
|
||||
unquote(_Meta, _Left, Right, _Args) ->
|
||||
argument_error(<<"expected unquote after dot with args to return an atom or a quoted call, got: ",
|
||||
('Elixir.Macro':to_string(Right))/binary>>).
|
||||
|
||||
join(_File, _Line, Left, Right, Rest) when is_list(Left), is_list(Right), is_list(Rest) ->
|
||||
Rest ++ Left ++ Right;
|
||||
join(Left, Right) when is_list(Right) ->
|
||||
validate_join(Left),
|
||||
Left ++ Right.
|
||||
|
||||
join(_File, _Line, Left, Right, Rest) ->
|
||||
[H|T] = lists:reverse(Rest ++ Left),
|
||||
tail_join(Left, Right, Tail) when is_list(Right), is_list(Tail) ->
|
||||
validate_join(Left),
|
||||
Tail ++ Left ++ Right;
|
||||
|
||||
tail_join(Left, Right, Tail) when is_list(Left) ->
|
||||
validate_join(Left),
|
||||
[H|T] = lists:reverse(Tail ++ Left),
|
||||
lists:reverse([{ '|', [], [H, Right] }|T]).
|
||||
|
||||
validate_join(List) when is_list(List) ->
|
||||
ok;
|
||||
validate_join(List) when not is_list(List) ->
|
||||
argument_error(<<"expected a list with quoted expressions in unquote_splicing/1, got: ",
|
||||
('Elixir.Kernel':inspect(List))/binary>>).
|
||||
|
||||
argument_error(Message) ->
|
||||
error('Elixir.ArgumentError':exception([{message,Message}])).
|
||||
|
||||
%% Escapes the given expression. It is similar to quote, but
|
||||
%% lines are kept and hygiene mechanisms are disabled.
|
||||
escape(Expr, Unquote) ->
|
||||
@@ -113,7 +142,24 @@ do_quote({ unquote, _Meta, [Expr] }, #elixir_quote{unquote=true} = Q, _) ->
|
||||
|
||||
%% Context mark
|
||||
|
||||
do_quote({ Def, Meta, Args }, #elixir_quote{escape=false} = Q, S) when ?defs(Def); Def == defmodule; Def == alias; Def == import ->
|
||||
do_quote({ defmodule, Meta, [_|_] = Args }, #elixir_quote{escape=false} = Q, S) ->
|
||||
%% Only store the context if we actually have a full alias as
|
||||
%% argument, otherwise the expression is being automatically
|
||||
%% generated and we don't want the alias to count.
|
||||
NewMeta =
|
||||
case hd(Args) of
|
||||
{ '__aliases__', _, Atoms } ->
|
||||
case lists:all(fun is_atom/1, Atoms) of
|
||||
true -> keystore(context, Meta, Q#elixir_quote.context);
|
||||
false -> Meta
|
||||
end;
|
||||
_ ->
|
||||
Meta
|
||||
end,
|
||||
|
||||
do_quote_tuple({ defmodule, NewMeta, Args }, Q, S);
|
||||
|
||||
do_quote({ Def, Meta, [_|_] = Args }, #elixir_quote{escape=false} = Q, S) when ?defs(Def); Def == alias; Def == import ->
|
||||
NewMeta = keystore(context, Meta, Q#elixir_quote.context),
|
||||
do_quote_tuple({ Def, NewMeta, Args }, Q, S);
|
||||
|
||||
@@ -128,7 +174,7 @@ do_quote({ 'alias!', _Meta, [Expr] }, #elixir_quote{aliases_hygiene=true} = Q, S
|
||||
{ TExpr, TQ#elixir_quote{aliases_hygiene=true} };
|
||||
|
||||
do_quote({ '__aliases__', Meta, [H|T] } = Alias, #elixir_quote{aliases_hygiene=true} = Q, S) when is_atom(H) and (H /= 'Elixir') ->
|
||||
Annotation = case elixir_aliases:expand(Alias, S#elixir_scope.aliases, S#elixir_scope.macro_aliases) of
|
||||
Annotation = case elixir_aliases:expand(Alias, S#elixir_scope.aliases, S#elixir_scope.macro_aliases, S#elixir_scope.lexical_tracker) of
|
||||
Atom when is_atom(Atom) -> Atom;
|
||||
Aliases when is_list(Aliases) -> false
|
||||
end,
|
||||
@@ -214,9 +260,9 @@ do_quote(Other, Q, _) ->
|
||||
%% Quote helpers
|
||||
|
||||
do_quote_call(Left, Meta, Expr, Args, Q, S) ->
|
||||
All = [?line(Meta), meta(Meta, Q), Left, { unquote, Meta, [Expr] }, Args],
|
||||
All = [meta(Meta, Q), Left, { unquote, Meta, [Expr] }, Args],
|
||||
{ TAll, TQ } = lists:mapfoldl(fun(X, Acc) -> do_quote(X, Acc, S) end, Q, All),
|
||||
{ { { '.', Meta, [elixir_quote, unquote] }, Meta, [{ '__FILE__', [], nil }|TAll] }, TQ }.
|
||||
{ { { '.', Meta, [elixir_quote, unquote] }, Meta, TAll }, TQ }.
|
||||
|
||||
do_quote_fa(Target, Meta, Args, F, A, Q, S) ->
|
||||
NewMeta =
|
||||
@@ -264,24 +310,24 @@ do_splice([{ '|', Meta, [{ unquote_splicing, _, [Left] }, Right] }|T], #elixir_q
|
||||
%% 1, 2 and 3, which could even be unquotes.
|
||||
{ TT, QT } = do_splice(T, Q, S, [], []),
|
||||
{ TR, QR } = do_quote(Right, QT, S),
|
||||
|
||||
%% Do the joining at runtime when we are aware of the values.
|
||||
Args = [{ '__FILE__', [], nil }, ?line(Meta), Left, TR, TT],
|
||||
{ { { '.', Meta, [elixir_quote, join] }, Meta, Args }, QR#elixir_quote{unquoted=true} };
|
||||
{ do_runtime_join(Meta, tail_join, [Left, TR, TT]), QR#elixir_quote{unquoted=true} };
|
||||
|
||||
do_splice(List, Q, S) ->
|
||||
do_splice(List, Q, S, [], []).
|
||||
|
||||
do_splice([{ unquote_splicing, _, [Expr] }|T], #elixir_quote{unquote=true} = Q, S, Buffer, Acc) ->
|
||||
do_splice(T, Q#elixir_quote{unquoted=true}, S, [], do_splice_join(do_splice_join(Expr, Buffer), Acc));
|
||||
do_splice([{ unquote_splicing, Meta, [Expr] }|T], #elixir_quote{unquote=true} = Q, S, Buffer, Acc) ->
|
||||
do_splice(T, Q#elixir_quote{unquoted=true}, S, [], do_runtime_join(Meta, join, [Expr, do_join(Buffer, Acc)]));
|
||||
|
||||
do_splice([H|T], Q, S, Buffer, Acc) ->
|
||||
{ TH, TQ } = do_quote(H, Q, S),
|
||||
do_splice(T, TQ, S, [TH|Buffer], Acc);
|
||||
|
||||
do_splice([], Q, _S, Buffer, Acc) ->
|
||||
{ do_splice_join(Buffer, Acc), Q }.
|
||||
{ do_join(Buffer, Acc), Q }.
|
||||
|
||||
do_splice_join(Left, []) -> Left;
|
||||
do_splice_join([], Right) -> Right;
|
||||
do_splice_join(Left, Right) -> { { '.', [], ['Elixir.Kernel', '++'] }, [], [Left, Right] }.
|
||||
do_join(Left, []) -> Left;
|
||||
do_join([], Right) -> Right;
|
||||
do_join(Left, Right) -> { { '.', [], ['Elixir.Kernel', '++'] }, [], [Left, Right] }.
|
||||
|
||||
do_runtime_join(Meta, Fun, Args) ->
|
||||
{ { '.', Meta, [elixir_quote, Fun] }, Meta, Args }.
|
||||
|
||||
@@ -2,17 +2,20 @@
|
||||
-module(elixir_scope).
|
||||
-export([translate_var/5,
|
||||
build_erl_var/2, build_ex_var/2,
|
||||
serialize/1, deserialize/1,
|
||||
serialize/1, deserialize/1, to_erl/1,
|
||||
serialize_with_vars/2, deserialize_with_vars/2,
|
||||
to_erl_env/1, to_ex_env/1,
|
||||
vars_from_binding/2, binding_for_eval/2, binding_from_vars/2,
|
||||
load_binding/3, dump_binding/2,
|
||||
umergev/2, umergec/2, umergea/2, merge_clause_vars/2
|
||||
]).
|
||||
-include("elixir.hrl").
|
||||
|
||||
translate_var(Meta, Name, Kind, S, Callback) ->
|
||||
%% VAR HANDLING
|
||||
|
||||
translate_var(Meta, Name, Kind, S, Callback) when is_atom(Kind); is_integer(Kind) ->
|
||||
Line = ?line(Meta),
|
||||
Vars = S#elixir_scope.vars,
|
||||
Temp = lists:keyfind(temp, 1, Meta) == { temp, true },
|
||||
Tuple = { Name, Kind },
|
||||
|
||||
case Name of
|
||||
@@ -41,7 +44,7 @@ translate_var(Meta, Name, Kind, S, Callback) ->
|
||||
true -> ordsets:add_element(Tuple, TempVars)
|
||||
end,
|
||||
clause_vars=if
|
||||
ClauseVars == nil; Kind == false -> ClauseVars;
|
||||
ClauseVars == nil; Temp == true -> ClauseVars;
|
||||
true -> orddict:store(Tuple, RealName, ClauseVars)
|
||||
end
|
||||
} }
|
||||
@@ -78,43 +81,54 @@ build_ex_var(Line, Key, Name, S) when is_integer(Line) ->
|
||||
Var = { ?atom_concat([Name, "@", Counter]), [{line,Line}], Context },
|
||||
{ Var, NS }.
|
||||
|
||||
% Handle Macro.Env conversion
|
||||
%% Macro.Env <-> #elixir_scope conversion
|
||||
|
||||
to_erl_env({ 'Elixir.Macro.Env', Module, File, _Line, Function, Aliases, Context,
|
||||
Requires, Functions, Macros, ContextModules, MacroAliases, Vars }) ->
|
||||
Requires, Functions, Macros, ContextModules, MacroAliases, _Vars, Lexical }) ->
|
||||
#elixir_scope{module=Module,file=File,
|
||||
function=Function,aliases=Aliases,context=Context,
|
||||
requires=Requires,macros=Macros,functions=Functions,
|
||||
context_modules=ContextModules,macro_aliases=MacroAliases,
|
||||
list_vars=Vars}.
|
||||
lexical_tracker=Lexical}.
|
||||
|
||||
to_ex_env({ Line, #elixir_scope{module=Module,file=File,
|
||||
function=Function,aliases=Aliases,context=Context,
|
||||
requires=Requires,macros=Macros,functions=Functions,
|
||||
context_modules=ContextModules,macro_aliases=MacroAliases,
|
||||
vars=Vars,list_vars=ListVars} }) when is_integer(Line) ->
|
||||
vars=Vars,lexical_tracker=Lexical} }) when is_integer(Line) ->
|
||||
{ 'Elixir.Macro.Env', Module, File, Line, Function, Aliases,
|
||||
Context, Requires, Functions, Macros, ContextModules, MacroAliases,
|
||||
list_vars(ListVars, Vars) }.
|
||||
list_vars(Vars), Lexical }.
|
||||
|
||||
list_vars(nil, Vars) -> [K || { K, _ } <- Vars];
|
||||
list_vars(Other, _) -> Other.
|
||||
list_vars(Vars) -> [Pair || { { _, K } = Pair, _ } <- Vars, is_atom(K)].
|
||||
|
||||
% Provides a tuple with only the scope information we want to serialize.
|
||||
%% SERIALIZATION
|
||||
|
||||
%% When serializing scopes, we support serialization of pids.
|
||||
to_erl(Structure) ->
|
||||
elixir_utils:elixir_to_erl(Structure, fun
|
||||
(X) when is_pid(X) ->
|
||||
?wrap_call(0, erlang, binary_to_term, [elixir_utils:elixir_to_erl(term_to_binary(X))]);
|
||||
(Other) ->
|
||||
error({ badarg, Other })
|
||||
end).
|
||||
|
||||
serialize(S) ->
|
||||
elixir_utils:elixir_to_erl(
|
||||
{ S#elixir_scope.file, S#elixir_scope.functions,
|
||||
S#elixir_scope.requires, S#elixir_scope.macros, S#elixir_scope.aliases,
|
||||
S#elixir_scope.macro_functions, S#elixir_scope.macro_macros, S#elixir_scope.macro_aliases,
|
||||
S#elixir_scope.context_modules }
|
||||
).
|
||||
to_erl({ S#elixir_scope.file, S#elixir_scope.functions,
|
||||
S#elixir_scope.requires, S#elixir_scope.macros, S#elixir_scope.aliases,
|
||||
S#elixir_scope.macro_functions, S#elixir_scope.macro_macros, S#elixir_scope.macro_aliases,
|
||||
S#elixir_scope.context_modules, S#elixir_scope.lexical_tracker }).
|
||||
|
||||
serialize_with_vars(Line, S) when is_integer(Line) ->
|
||||
{ Vars, _ } = orddict:fold(fun({ Key, Kind }, Value, { Acc, Counter }) ->
|
||||
KindKey = if
|
||||
is_atom(Kind) -> atom;
|
||||
is_integer(Kind) -> integer
|
||||
end,
|
||||
|
||||
{ { cons, Line, { tuple, Line, [
|
||||
{ atom, Line, Key },
|
||||
{ atom, Line, Kind },
|
||||
{ KindKey, Line, Kind },
|
||||
{ atom, Line, ?atom_concat(["_@", Counter]) },
|
||||
{ var, Line, Value }
|
||||
] }, Acc }, Counter + 1 }
|
||||
@@ -125,8 +139,8 @@ serialize_with_vars(Line, S) when is_integer(Line) ->
|
||||
|
||||
deserialize(Tuple) -> deserialize_with_vars(Tuple, []).
|
||||
|
||||
deserialize_with_vars({ File, Functions, Requires, Macros,
|
||||
Aliases, MacroFunctions, MacroMacros, MacroAliases, FileModules }, Vars) ->
|
||||
deserialize_with_vars({ File, Functions, Requires, Macros, Aliases, MacroFunctions,
|
||||
MacroMacros, MacroAliases, FileModules, LexicalTracker }, Vars) ->
|
||||
#elixir_scope{
|
||||
file=File,
|
||||
functions=Functions,
|
||||
@@ -138,9 +152,12 @@ deserialize_with_vars({ File, Functions, Requires, Macros,
|
||||
macro_aliases=MacroAliases,
|
||||
context_modules=FileModules,
|
||||
vars=orddict:from_list(Vars),
|
||||
lexical_tracker=LexicalTracker,
|
||||
counter=[{'',length(Vars)}]
|
||||
}.
|
||||
|
||||
%% SCOPE MERGING
|
||||
|
||||
%% Receives two scopes and return a new scope based on the second
|
||||
%% with their variables merged.
|
||||
|
||||
@@ -160,6 +177,7 @@ umergev(S1, S2) ->
|
||||
umergec(S1, S2) ->
|
||||
S1#elixir_scope{
|
||||
counter=S2#elixir_scope.counter,
|
||||
macro_counter=S2#elixir_scope.macro_counter,
|
||||
extra_guards=S2#elixir_scope.extra_guards,
|
||||
super=S2#elixir_scope.super,
|
||||
caller=S2#elixir_scope.caller
|
||||
@@ -202,39 +220,42 @@ var_number([$@|T], _Acc) -> var_number(T, []);
|
||||
var_number([H|T], Acc) -> var_number(T, [H|Acc]);
|
||||
var_number([], Acc) -> list_to_integer(lists:reverse(Acc)).
|
||||
|
||||
%% Setup the vars in scope from binding
|
||||
%% BINDINGS
|
||||
|
||||
vars_from_binding(Scope, Binding) ->
|
||||
Scope#elixir_scope{
|
||||
vars=binding_dict(Binding),
|
||||
load_binding(Binding, Scope, Module) ->
|
||||
{ NewBinding, NewVars, NewCounter } = load_binding(Binding, [], [], 0, Module),
|
||||
{ NewBinding, Scope#elixir_scope{
|
||||
vars=NewVars,
|
||||
temp_vars=[],
|
||||
clause_vars=nil,
|
||||
counter=[]
|
||||
}.
|
||||
counter=[{'',NewCounter}]
|
||||
} }.
|
||||
|
||||
binding_dict(List) -> binding_dict(List, orddict:new()).
|
||||
binding_dict([{{H,Kind},_}|T], Dict) -> binding_dict(T, orddict:store({ H, Kind }, H, Dict));
|
||||
binding_dict([{H,_}|T], Dict) -> binding_dict(T, orddict:store({ H, nil }, H, Dict));
|
||||
binding_dict([], Dict) -> Dict.
|
||||
load_binding([{'_@MODULE',Value}|T], Binding, Vars, Counter, _Module) ->
|
||||
load_binding(T, Binding, Vars, Counter, Value);
|
||||
load_binding([{Key,Value}|T], Binding, Vars, Counter, Module) ->
|
||||
Actual = case Key of
|
||||
{ _Name, _Kind } -> Key;
|
||||
Name when is_atom(Name) -> { Name, nil }
|
||||
end,
|
||||
InternalName = ?atom_concat(["_@", Counter]),
|
||||
load_binding(T,
|
||||
[{InternalName,Value}|Binding],
|
||||
orddict:store(Actual, InternalName, Vars),
|
||||
Counter + 1, Module);
|
||||
load_binding([], Binding, Vars, Counter, Module) ->
|
||||
{ lists:reverse([{'_@MODULE',Module}|Binding]), Vars, Counter }.
|
||||
|
||||
binding_for_eval(Binding, Module) ->
|
||||
Keyword = orddict:from_list(Binding),
|
||||
case orddict:find('_@MODULE', Keyword) of
|
||||
{ ok, _ } -> Keyword;
|
||||
_ -> orddict:store('_@MODULE', Module, Keyword)
|
||||
end.
|
||||
dump_binding(Binding, #elixir_scope{vars=Vars}) ->
|
||||
dump_binding(Vars, Binding, []).
|
||||
|
||||
binding_from_vars(#elixir_scope{vars=Vars}, Binding) ->
|
||||
binding_from_vars(Binding, [], Binding, Vars).
|
||||
|
||||
binding_from_vars([{Var,_}|T], Acc, Binding, Vars) ->
|
||||
case lists:member($@, atom_to_list(Var)) of
|
||||
true ->
|
||||
binding_from_vars(T, Acc, Binding, Vars);
|
||||
false ->
|
||||
RealName = orddict:fetch({ Var, nil }, Vars),
|
||||
RealValue = proplists:get_value(RealName, Binding, nil),
|
||||
binding_from_vars(T, [{Var, RealValue}|Acc], Binding, Vars)
|
||||
end;
|
||||
|
||||
binding_from_vars([], Acc, _Binding, _Vars) -> lists:reverse(Acc).
|
||||
dump_binding([{{Var,Kind}=Key,InternalName}|T], Binding, Acc) when is_atom(Kind) ->
|
||||
Actual = case Kind of
|
||||
nil -> Var;
|
||||
_ -> Key
|
||||
end,
|
||||
Value = proplists:get_value(InternalName, Binding, nil),
|
||||
dump_binding(T, Binding, orddict:store(Actual, Value, Acc));
|
||||
dump_binding([_|T], Binding, Acc) ->
|
||||
dump_binding(T, Binding, Acc);
|
||||
dump_binding([], _Binding, Acc) -> Acc.
|
||||
|
||||
+118
-113
@@ -3,13 +3,6 @@
|
||||
-export([tokenize/3]).
|
||||
-import(elixir_interpolation, [unescape_chars/1, unescape_tokens/1]).
|
||||
|
||||
-record(scope, {
|
||||
file,
|
||||
terminators=[],
|
||||
check_terminators=true,
|
||||
existing_atoms_only=false
|
||||
}).
|
||||
|
||||
-define(container(T1, T2),
|
||||
T1 == ${, T2 == $};
|
||||
T1 == $[, T2 == $]
|
||||
@@ -96,6 +89,9 @@
|
||||
-define(type_op(T1, T2),
|
||||
T1 == $:, T2 == $:).
|
||||
|
||||
tokenize(String, Line, #elixir_tokenizer{} = Scope) ->
|
||||
tokenize(String, Line, Scope, []);
|
||||
|
||||
tokenize(String, Line, Opts) ->
|
||||
File = case lists:keyfind(file, 1, Opts) of
|
||||
{ file, V1 } -> V1;
|
||||
@@ -112,21 +108,19 @@ tokenize(String, Line, Opts) ->
|
||||
false -> true
|
||||
end,
|
||||
|
||||
Scope = #scope{
|
||||
tokenize(String, Line, #elixir_tokenizer{
|
||||
file=File,
|
||||
existing_atoms_only=Existing,
|
||||
check_terminators=Check
|
||||
},
|
||||
}).
|
||||
|
||||
tokenize(String, Line, Scope, []).
|
||||
tokenize([], Line, #elixir_tokenizer{terminators=[]}, Tokens) ->
|
||||
{ ok, Line, lists:reverse(Tokens) };
|
||||
|
||||
tokenize([], _Line, #scope{terminators=[]}, Tokens) ->
|
||||
{ ok, lists:reverse(Tokens) };
|
||||
|
||||
tokenize([], EndLine, #scope{terminators=[{ Start, StartLine }|_]}, _Tokens) ->
|
||||
tokenize([], EndLine, #elixir_tokenizer{terminators=[{ Start, StartLine }|_]}, Tokens) ->
|
||||
End = terminator(Start),
|
||||
Message = io_lib:format("missing terminator: ~ts (for \"~ts\" starting at line ~B)", [End, Start, StartLine]),
|
||||
{ error, { EndLine, Message, [] } };
|
||||
{ error, { EndLine, Message, [] }, [], Tokens };
|
||||
|
||||
% Base integers
|
||||
|
||||
@@ -150,69 +144,69 @@ tokenize([$#|String], Line, Scope, Tokens) ->
|
||||
|
||||
% Sigils
|
||||
|
||||
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;
|
||||
tokenize([$%,S,H,H,H|T] = Original, Line, Scope, Tokens) when ?is_quote(H), ?is_upcase(S) orelse ?is_downcase(S) ->
|
||||
case extract_heredoc_with_interpolation(Line, Scope, ?is_downcase(S), T, H) of
|
||||
{ error, Reason } ->
|
||||
{ error, Reason, Original, Tokens };
|
||||
{ Parts, Rest } ->
|
||||
{ Final, Modifiers } = collect_modifiers(Rest, []),
|
||||
tokenize(Final, Line, Scope, [{ sigil, Line, S, Parts, Modifiers }|Tokens])
|
||||
end;
|
||||
|
||||
tokenize([$%,S,H|T], Line, #scope{file=File} = Scope, Tokens) when not(?is_identifier(H)), not(?is_terminator(H)), ?is_upcase(S) orelse ?is_downcase(S) ->
|
||||
case elixir_interpolation:extract(Line, File, ?is_downcase(S), T, sigil_terminator(H)) of
|
||||
tokenize([$%,S,H|T] = Original, Line, Scope, Tokens) when not(?is_identifier(H)), not(?is_terminator(H)), ?is_upcase(S) orelse ?is_downcase(S) ->
|
||||
case elixir_interpolation:extract(Line, Scope, ?is_downcase(S), T, sigil_terminator(H)) of
|
||||
{ NewLine, Parts, Rest } ->
|
||||
{ Final, Modifiers } = collect_modifiers(Rest, []),
|
||||
tokenize(Final, NewLine, Scope, [{ sigil, Line, S, Parts, Modifiers }|Tokens]);
|
||||
Error ->
|
||||
{ error, Reason } ->
|
||||
Sigil = [$%,S,H],
|
||||
interpolation_error(Error, " (for sigil ~ts starting at line ~B)", [Sigil, Line])
|
||||
interpolation_error(Reason, Original, Tokens, " (for sigil ~ts starting at line ~B)", [Sigil, Line])
|
||||
end;
|
||||
|
||||
% Char tokens
|
||||
|
||||
tokenize([$?,$\\,P,${,A,B,C,D,E,F,$}|T], Line, Scope, Tokens) when (P == $x orelse P == $X), ?is_hex(A), ?is_hex(B), ?is_hex(C), ?is_hex(D), ?is_hex(E), ?is_hex(F) ->
|
||||
[Char] = unicode:characters_to_list(elixir_interpolation:unescape_chars(list_to_binary([$\\,P,${,A,B,C,D,E,F,$}]))),
|
||||
Char = escape_char([$\\,P,${,A,B,C,D,E,F,$}]),
|
||||
tokenize(T, Line, Scope, [{ number, Line, Char }|Tokens]);
|
||||
|
||||
tokenize([$?,$\\,P,${,A,B,C,D,E,$}|T], Line, Scope, Tokens) when (P == $x orelse P == $X), ?is_hex(A), ?is_hex(B), ?is_hex(C), ?is_hex(D), ?is_hex(E) ->
|
||||
[Char] = unicode:characters_to_list(elixir_interpolation:unescape_chars(list_to_binary([$\\,P,${,A,B,C,D,E,$}]))),
|
||||
Char = escape_char([$\\,P,${,A,B,C,D,E,$}]),
|
||||
tokenize(T, Line, Scope, [{ number, Line, Char }|Tokens]);
|
||||
|
||||
tokenize([$?,$\\,P,${,A,B,C,D,$}|T], Line, Scope, Tokens) when (P == $x orelse P == $X), ?is_hex(A), ?is_hex(B), ?is_hex(C), ?is_hex(D) ->
|
||||
[Char] = unicode:characters_to_list(elixir_interpolation:unescape_chars(list_to_binary([$\\,P,${,A,B,C,D,$}]))),
|
||||
Char = escape_char([$\\,P,${,A,B,C,D,$}]),
|
||||
tokenize(T, Line, Scope, [{ number, Line, Char }|Tokens]);
|
||||
|
||||
tokenize([$?,$\\,P,${,A,B,C,$}|T], Line, Scope, Tokens) when (P == $x orelse P == $X), ?is_hex(A), ?is_hex(B), ?is_hex(C) ->
|
||||
[Char] = unicode:characters_to_list(elixir_interpolation:unescape_chars(list_to_binary([$\\,P,${,A,B,C,$}]))),
|
||||
Char = escape_char([$\\,P,${,A,B,C,$}]),
|
||||
tokenize(T, Line, Scope, [{ number, Line, Char }|Tokens]);
|
||||
|
||||
tokenize([$?,$\\,P,${,A,B,$}|T], Line, Scope, Tokens) when (P == $x orelse P == $X), ?is_hex(A), ?is_hex(B) ->
|
||||
[Char] = unicode:characters_to_list(elixir_interpolation:unescape_chars(list_to_binary([$\\,P,${,A,B,$}]))),
|
||||
Char = escape_char([$\\,P,${,A,B,$}]),
|
||||
tokenize(T, Line, Scope, [{ number, Line, Char }|Tokens]);
|
||||
|
||||
tokenize([$?,$\\,P,${,A,$}|T], Line, Scope, Tokens) when (P == $x orelse P == $X), ?is_hex(A) ->
|
||||
[Char] = unicode:characters_to_list(elixir_interpolation:unescape_chars(list_to_binary([$\\,P,${,A,$}]))),
|
||||
Char = escape_char([$\\,P,${,A,$}]),
|
||||
tokenize(T, Line, Scope, [{ number, Line, Char }|Tokens]);
|
||||
|
||||
tokenize([$?,$\\,P,A,B|T], Line, Scope, Tokens) when (P == $x orelse P == $X), ?is_hex(A), ?is_hex(B) ->
|
||||
[Char] = unicode:characters_to_list(elixir_interpolation:unescape_chars(list_to_binary([$\\,P,A,B]))),
|
||||
Char = escape_char([$\\,P,A,B]),
|
||||
tokenize(T, Line, Scope, [{ number, Line, Char }|Tokens]);
|
||||
|
||||
tokenize([$?,$\\,P,A|T], Line, Scope, Tokens) when (P == $x orelse P == $X), ?is_hex(A) ->
|
||||
[Char] = unicode:characters_to_list(elixir_interpolation:unescape_chars(list_to_binary([$\\,P,A]))),
|
||||
Char = escape_char([$\\,P,A]),
|
||||
tokenize(T, Line, Scope, [{ number, Line, Char }|Tokens]);
|
||||
|
||||
tokenize([$?,$\\,A,B,C|T], Line, Scope, Tokens) when ?is_octal(A), A =< $3,?is_octal(B), ?is_octal(C) ->
|
||||
[Char] = unicode:characters_to_list(elixir_interpolation:unescape_chars(list_to_binary([$\\,A,B,C]))),
|
||||
Char = escape_char([$\\,A,B,C]),
|
||||
tokenize(T, Line, Scope, [{ number, Line, Char }|Tokens]);
|
||||
|
||||
tokenize([$?,$\\,A,B|T], Line, Scope, Tokens) when ?is_octal(A), ?is_octal(B) ->
|
||||
[Char] = unicode:characters_to_list(elixir_interpolation:unescape_chars(list_to_binary([$\\,A,B]))),
|
||||
Char = escape_char([$\\,A,B]),
|
||||
tokenize(T, Line, Scope, [{ number, Line, Char }|Tokens]);
|
||||
|
||||
tokenize([$?,$\\,A|T], Line, Scope, Tokens) when ?is_octal(A) ->
|
||||
[Char] = unicode:characters_to_list(elixir_interpolation:unescape_chars(list_to_binary([$\\,A]))),
|
||||
Char = escape_char([$\\,A]),
|
||||
tokenize(T, Line, Scope, [{ number, Line, Char }|Tokens]);
|
||||
|
||||
tokenize([$?,$\\,H|T], Line, Scope, Tokens) ->
|
||||
@@ -266,18 +260,18 @@ tokenize([$.,T|Rest], Line, Scope, Tokens) when
|
||||
tokenize([$.,$(|Rest], Line, Scope, Tokens) ->
|
||||
tokenize([$(|Rest], Line, Scope, add_token_with_nl({ dot_call_op, Line, '.' }, Tokens));
|
||||
|
||||
tokenize([$.,H|T], Line, #scope{file=File} = Scope, Tokens) when ?is_quote(H) ->
|
||||
case elixir_interpolation:extract(Line, File, true, T, H) of
|
||||
tokenize([$.,H|T] = Original, Line, Scope, Tokens) when ?is_quote(H) ->
|
||||
case elixir_interpolation:extract(Line, Scope, true, T, H) of
|
||||
{ NewLine, [Part], Rest } when is_binary(Part) ->
|
||||
case unsafe_to_atom(Part, Line, Scope) of
|
||||
{ error, _ } = Error ->
|
||||
Error;
|
||||
Atom ->
|
||||
{ ok, Atom } ->
|
||||
Token = check_call_identifier(identifier, Line, Atom, Rest),
|
||||
tokenize(Rest, NewLine, Scope, [Token|add_token_with_nl({ '.', Line }, Tokens)])
|
||||
tokenize(Rest, NewLine, Scope, [Token|add_token_with_nl({ '.', Line }, Tokens)]);
|
||||
{ error, Reason } ->
|
||||
{ error, Reason, Original, Tokens }
|
||||
end;
|
||||
Error ->
|
||||
interpolation_error(Error, " (for function name starting at line ~B)", [Line])
|
||||
{ error, Reason } ->
|
||||
interpolation_error(Reason, Original, Tokens, " (for function name starting at line ~B)", [Line])
|
||||
end;
|
||||
|
||||
% Heredocs
|
||||
@@ -297,30 +291,23 @@ tokenize([$'|T], Line, Scope, Tokens) ->
|
||||
|
||||
% Atoms
|
||||
|
||||
tokenize([$:,H|T], Line, #scope{file=File} = Scope, Tokens) when ?is_quote(H) ->
|
||||
case elixir_interpolation:extract(Line, File, true, T, H) of
|
||||
tokenize([$:,H|T] = Original, Line, Scope, Tokens) when ?is_quote(H) ->
|
||||
case elixir_interpolation:extract(Line, Scope, true, T, H) of
|
||||
{ NewLine, Parts, Rest } ->
|
||||
SafeAtom = case unescape_tokens(Parts) of
|
||||
[Part] when is_list(Part) or is_binary(Part) -> unsafe_to_atom(Part, Line, Scope);
|
||||
Unescaped -> Unescaped
|
||||
end,
|
||||
case SafeAtom of
|
||||
{ error, _ } = Error ->
|
||||
Error;
|
||||
Atom ->
|
||||
tokenize(Rest, NewLine, Scope, [{ atom, Line, Atom }|Tokens])
|
||||
end;
|
||||
Error ->
|
||||
interpolation_error(Error, " (for atom starting at line ~B)", [Line])
|
||||
Unescaped = unescape_tokens(Parts),
|
||||
ExistingAtomsOnly = Scope#elixir_tokenizer.existing_atoms_only,
|
||||
tokenize(Rest, NewLine, Scope, [{ atom_string, Line, ExistingAtomsOnly, Unescaped }|Tokens]);
|
||||
{ error, Reason } ->
|
||||
interpolation_error(Reason, Original, Tokens, " (for atom starting at line ~B)", [Line])
|
||||
end;
|
||||
|
||||
tokenize([$:,T|String], Line, Scope, Tokens) when ?is_atom_start(T) ->
|
||||
tokenize([$:,T|String] = Original, Line, Scope, Tokens) when ?is_atom_start(T) ->
|
||||
{ Rest, Part } = tokenize_atom([T|String], []),
|
||||
case unsafe_to_atom(Part, Line, Scope) of
|
||||
{ error, _ } = Error ->
|
||||
Error;
|
||||
Atom ->
|
||||
tokenize(Rest, Line, Scope, [{ atom, Line, Atom }|Tokens])
|
||||
{ ok, Atom } ->
|
||||
tokenize(Rest, Line, Scope, [{ atom, Line, Atom }|Tokens]);
|
||||
{ error, Reason } ->
|
||||
{ error, Reason, Original, Tokens }
|
||||
end;
|
||||
|
||||
% Atom identifiers/operators
|
||||
@@ -474,18 +461,18 @@ tokenize([H|_] = String, Line, Scope, Tokens) when ?is_digit(H) ->
|
||||
|
||||
% Aliases
|
||||
|
||||
tokenize([H|_] = String, Line, Scope, Tokens) when ?is_upcase(H) ->
|
||||
{ Rest, Alias } = tokenize_identifier(String, []),
|
||||
tokenize([H|_] = Original, Line, Scope, Tokens) when ?is_upcase(H) ->
|
||||
{ Rest, Alias } = tokenize_identifier(Original, []),
|
||||
case unsafe_to_atom(Alias, Line, Scope) of
|
||||
{ error, _ } = Error ->
|
||||
Error;
|
||||
Atom ->
|
||||
{ ok, Atom } ->
|
||||
case Rest of
|
||||
[$:|T] when ?is_space(hd(T)) ->
|
||||
tokenize(T, Line, Scope, [{ kw_identifier, Line, Atom }|Tokens]);
|
||||
_ ->
|
||||
tokenize(Rest, Line, Scope, [{ aliases, Line, [Atom] }|Tokens])
|
||||
end
|
||||
end;
|
||||
{ error, Reason } ->
|
||||
{ error, Reason, Original, Tokens }
|
||||
end;
|
||||
|
||||
% Identifier
|
||||
@@ -496,7 +483,7 @@ tokenize([H|_] = String, Line, Scope, Tokens) when ?is_downcase(H); H == $_ ->
|
||||
handle_terminator(Rest, Line, Scope, Check, T);
|
||||
{ identifier, Rest, Token } ->
|
||||
tokenize(Rest, Line, Scope, [Token|Tokens]);
|
||||
{ error, _ } = Error ->
|
||||
{ error, _, _, _ } = Error ->
|
||||
Error
|
||||
end;
|
||||
|
||||
@@ -519,44 +506,48 @@ tokenize([T|Rest], Line, Scope, Tokens) when ?is_horizontal_space(T) ->
|
||||
tokenize([{line,Line}|Rest], _Line, Scope, Tokens) ->
|
||||
tokenize(Rest, Line, Scope, Tokens);
|
||||
|
||||
tokenize(T, Line, _Scope, _Tokens) ->
|
||||
{ error, { Line, "invalid token: ", until_eol(T) } }.
|
||||
tokenize(T, Line, _Scope, Tokens) ->
|
||||
{ error, { Line, "invalid token: ", until_eol(T) }, T, Tokens }.
|
||||
|
||||
until_eol("\r\n" ++ _) -> [];
|
||||
until_eol("\n" ++ _) -> [];
|
||||
until_eol([]) -> [];
|
||||
until_eol([H|T]) -> [H|until_eol(T)].
|
||||
|
||||
escape_char(List) ->
|
||||
<< Char/utf8 >> = elixir_interpolation:unescape_chars(list_to_binary(List)),
|
||||
Char.
|
||||
|
||||
%% Handlers
|
||||
|
||||
handle_heredocs(T, Line, H, #scope{file=File} = Scope, Tokens) ->
|
||||
case extract_heredoc_with_interpolation(Line, File, true, T, H) of
|
||||
{ error, _ } = Error ->
|
||||
Error;
|
||||
handle_heredocs(T, Line, H, Scope, Tokens) ->
|
||||
case extract_heredoc_with_interpolation(Line, Scope, true, T, H) of
|
||||
{ error, Reason } ->
|
||||
{ error, Reason, [H, H, H] ++ T, Tokens };
|
||||
{ Parts, Rest } ->
|
||||
Token = { string_type(H), Line, unescape_tokens(Parts) },
|
||||
tokenize(Rest, Line, Scope, [Token|Tokens])
|
||||
end.
|
||||
|
||||
handle_strings(T, Line, H, #scope{file=File} = Scope, Tokens) ->
|
||||
case elixir_interpolation:extract(Line, File, true, T, H) of
|
||||
handle_strings(T, Line, H, Scope, Tokens) ->
|
||||
case elixir_interpolation:extract(Line, Scope, true, T, H) of
|
||||
{ error, Reason } ->
|
||||
interpolation_error(Reason, [H|T], Tokens, " (for string starting at line ~B)", [Line]);
|
||||
{ NewLine, Parts, [$:|Rest] } when ?is_space(hd(Rest)) ->
|
||||
case Parts of
|
||||
[Bin] when is_binary(Bin) ->
|
||||
case unsafe_to_atom(unescape_chars(Bin), Line, Scope) of
|
||||
{ error, _ } = Error ->
|
||||
Error;
|
||||
Atom ->
|
||||
tokenize(Rest, NewLine, Scope, [{ kw_identifier, Line, Atom }|Tokens])
|
||||
{ ok, Atom } ->
|
||||
tokenize(Rest, NewLine, Scope, [{ kw_identifier, Line, Atom }|Tokens]);
|
||||
{ error, Reason } ->
|
||||
{ error, Reason, [H|T], Tokens }
|
||||
end;
|
||||
_ ->
|
||||
{ error, { Line, "invalid interpolation in key ", [$"|T] } }
|
||||
{ error, { Line, "invalid interpolation in key ", [$"|T] }, [H|T], Tokens }
|
||||
end;
|
||||
{ NewLine, Parts, Rest } ->
|
||||
Token = { string_type(H), Line, unescape_tokens(Parts) },
|
||||
tokenize(Rest, NewLine, Scope, [Token|Tokens]);
|
||||
Error ->
|
||||
interpolation_error(Error, " (for string starting at line ~B)", [Line])
|
||||
tokenize(Rest, NewLine, Scope, [Token|Tokens])
|
||||
end.
|
||||
|
||||
handle_nonl_op([$:|Rest], Line, _Kind, Op, Scope, Tokens) when ?is_space(hd(Rest)) ->
|
||||
@@ -581,18 +572,18 @@ eol(_Line, _Mod, [{',',_}|_] = Tokens) -> Tokens;
|
||||
eol(_Line, _Mod, [{eol,_,_}|_] = Tokens) -> Tokens;
|
||||
eol(Line, Mod, Tokens) -> [{eol,Line,Mod}|Tokens].
|
||||
|
||||
unsafe_to_atom(Part, Line, #scope{}) when
|
||||
unsafe_to_atom(Part, Line, #elixir_tokenizer{}) when
|
||||
is_binary(Part) andalso size(Part) > 255;
|
||||
is_list(Part) andalso length(Part) > 255 ->
|
||||
{ error, { Line, "atom length must be less than system limit", ":" } };
|
||||
unsafe_to_atom(Binary, _Line, #scope{existing_atoms_only=true}) when is_binary(Binary) ->
|
||||
binary_to_existing_atom(Binary, utf8);
|
||||
unsafe_to_atom(Binary, _Line, #scope{}) when is_binary(Binary) ->
|
||||
binary_to_atom(Binary, utf8);
|
||||
unsafe_to_atom(List, _Line, #scope{existing_atoms_only=true}) when is_list(List) ->
|
||||
list_to_existing_atom(List);
|
||||
unsafe_to_atom(List, _Line, #scope{}) when is_list(List) ->
|
||||
list_to_atom(List).
|
||||
unsafe_to_atom(Binary, _Line, #elixir_tokenizer{existing_atoms_only=true}) when is_binary(Binary) ->
|
||||
{ ok, binary_to_existing_atom(Binary, utf8) };
|
||||
unsafe_to_atom(Binary, _Line, #elixir_tokenizer{}) when is_binary(Binary) ->
|
||||
{ ok, binary_to_atom(Binary, utf8) };
|
||||
unsafe_to_atom(List, _Line, #elixir_tokenizer{existing_atoms_only=true}) when is_list(List) ->
|
||||
{ ok, list_to_existing_atom(List) };
|
||||
unsafe_to_atom(List, _Line, #elixir_tokenizer{}) when is_list(List) ->
|
||||
{ ok, list_to_atom(List) }.
|
||||
|
||||
collect_modifiers([H|T], Buffer) when ?is_downcase(H) ->
|
||||
collect_modifiers(T, [H|Buffer]);
|
||||
@@ -602,14 +593,16 @@ collect_modifiers(Rest, Buffer) ->
|
||||
|
||||
%% Heredocs
|
||||
|
||||
extract_heredoc_with_interpolation(Line, File, Interpol, T, H) ->
|
||||
extract_heredoc_with_interpolation(Line, Scope, Interpol, T, H) ->
|
||||
case extract_heredoc(Line, T, H) of
|
||||
{ error, _ } = Error ->
|
||||
Error;
|
||||
{ Body, Rest } ->
|
||||
case elixir_interpolation:extract(Line + 1, File, Interpol, Body, 0) of
|
||||
{ _, Parts, [] } -> { Parts, Rest };
|
||||
Error -> interpolation_error(Error, " (for heredoc starting at line ~B)", [Line])
|
||||
case elixir_interpolation:extract(Line + 1, Scope, Interpol, Body, 0) of
|
||||
{ error, Reason } ->
|
||||
{ error, interpolation_format(Reason, " (for heredoc starting at line ~B)", [Line]) };
|
||||
{ _, Parts, [] } ->
|
||||
{ Parts, Rest }
|
||||
end
|
||||
end.
|
||||
|
||||
@@ -658,6 +651,9 @@ remove_heredoc_spaces([], Buffer, _Spaces, _Original) ->
|
||||
%% the remaining of the document and the number of spaces the heredoc
|
||||
%% is aligned.
|
||||
|
||||
extract_heredoc_body(_Line, Marker, [{line,NewLine}|Rest], Buffer) ->
|
||||
extract_heredoc_body(NewLine, Marker, Rest, Buffer);
|
||||
|
||||
extract_heredoc_body(Line, Marker, Rest, Buffer) ->
|
||||
case extract_heredoc_line(Marker, Rest, Buffer, 0) of
|
||||
{ ok, NewBuffer, NewRest } ->
|
||||
@@ -784,8 +780,8 @@ tokenize_identifier(Rest, Acc) ->
|
||||
|
||||
%% Tokenize any identifier, handling kv, punctuated, paren, bracket and do identifiers.
|
||||
|
||||
tokenize_any_identifier(String, Line, Scope, Tokens) ->
|
||||
{ Rest, Identifier } = tokenize_identifier(String, []),
|
||||
tokenize_any_identifier(Original, Line, Scope, Tokens) ->
|
||||
{ Rest, Identifier } = tokenize_identifier(Original, []),
|
||||
|
||||
{ AllIdentifier, AllRest } =
|
||||
case Rest of
|
||||
@@ -794,17 +790,19 @@ tokenize_any_identifier(String, Line, Scope, Tokens) ->
|
||||
end,
|
||||
|
||||
case unsafe_to_atom(AllIdentifier, Line, Scope) of
|
||||
{ error, _ } = Error ->
|
||||
Error;
|
||||
Atom ->
|
||||
tokenize_kw_or_other(AllRest, identifier, Line, Atom, Tokens)
|
||||
{ ok, Atom } ->
|
||||
tokenize_kw_or_other(AllRest, identifier, Line, Atom, Tokens);
|
||||
{ error, Reason } ->
|
||||
{ error, Reason, Original, Tokens }
|
||||
end.
|
||||
|
||||
tokenize_kw_or_other([$:,H|T], _Kind, Line, Atom, _Tokens) when ?is_space(H) ->
|
||||
{ identifier, [H|T], { kw_identifier, Line, Atom } };
|
||||
|
||||
tokenize_kw_or_other([$:,H|_], _Kind, Line, Atom, _Tokens) when ?is_atom_start(H) ->
|
||||
{ error, { Line, "keyword argument must be followed by space after: ", atom_to_list(Atom) ++ [$:] } };
|
||||
tokenize_kw_or_other([$:,H|T], _Kind, Line, Atom, Tokens) when ?is_atom_start(H) ->
|
||||
Original = atom_to_list(Atom) ++ [$:],
|
||||
Reason = { Line, "keyword argument must be followed by space after: ", Original },
|
||||
{ error, Reason, Original ++ [H|T], Tokens };
|
||||
|
||||
tokenize_kw_or_other(Rest, Kind, Line, Atom, Tokens) ->
|
||||
case check_keyword(Line, Atom, Tokens) of
|
||||
@@ -813,7 +811,7 @@ tokenize_kw_or_other(Rest, Kind, Line, Atom, Tokens) ->
|
||||
{ ok, [Check|T] } ->
|
||||
{ keyword, Rest, Check, T };
|
||||
{ error, Token } ->
|
||||
{ error, { Line, "syntax error before: ", Token } }
|
||||
{ error, { Line, "syntax error before: ", Token }, atom_to_list(Atom) ++ Rest, Tokens }
|
||||
end.
|
||||
|
||||
%% Check if it is a call identifier (paren | bracket | do)
|
||||
@@ -827,23 +825,30 @@ add_token_with_nl(Left, T) -> [Left|T].
|
||||
|
||||
%% Error handling
|
||||
|
||||
interpolation_error({ error, { Line, Message, Token } }, Extension, Args) ->
|
||||
{ error, { Line, io_lib:format("~ts" ++ Extension, [Message|Args]), Token } }.
|
||||
interpolation_error(Reason, Rest, Tokens, Extension, Args) ->
|
||||
{ error, interpolation_format(Reason, Extension, Args), Rest, Tokens }.
|
||||
|
||||
interpolation_format({ string, Line, Message, Token }, Extension, Args) ->
|
||||
{ Line, io_lib:format("~ts" ++ Extension, [Message|Args]), Token };
|
||||
interpolation_format({ _, _, _ } = Reason, _Extension, _Args) ->
|
||||
Reason.
|
||||
|
||||
%% Terminators
|
||||
|
||||
handle_terminator(Rest, Line, Scope, Token, Tokens) ->
|
||||
case handle_terminator(Token, Scope) of
|
||||
{ error, _ } = Error -> Error;
|
||||
New -> tokenize(Rest, Line, New, [Token|Tokens])
|
||||
{ error, Reason } ->
|
||||
{ error, Reason, atom_to_list(element(1, Token)) ++ Rest, Tokens };
|
||||
New ->
|
||||
tokenize(Rest, Line, New, [Token|Tokens])
|
||||
end.
|
||||
|
||||
handle_terminator(_, #scope{check_terminators=false} = Scope) ->
|
||||
handle_terminator(_, #elixir_tokenizer{check_terminators=false} = Scope) ->
|
||||
Scope;
|
||||
handle_terminator(Token, #scope{terminators=Terminators} = Scope) ->
|
||||
handle_terminator(Token, #elixir_tokenizer{terminators=Terminators} = Scope) ->
|
||||
case check_terminator(Token, Terminators) of
|
||||
{ error, _ } = Error -> Error;
|
||||
New -> Scope#scope{terminators=New}
|
||||
New -> Scope#elixir_tokenizer{terminators=New}
|
||||
end.
|
||||
|
||||
check_terminator({ S, Line }, Terminators) when S == 'fn' ->
|
||||
|
||||
@@ -1,14 +1,12 @@
|
||||
%% Module responsible for tracking invocations of locals,
|
||||
%% imports and requires.
|
||||
%% Module responsible for tracking invocations of module calls.
|
||||
-module(elixir_tracker).
|
||||
-export([
|
||||
setup/1, cleanup/1,
|
||||
record_local/2, record_local/3,
|
||||
record_import/4, record_remote/4,
|
||||
record_warn/4, record_definition/3,
|
||||
record_defaults/4, record_alias/2,
|
||||
ensure_no_function_conflict/4, ensure_all_imports_used/3,
|
||||
warn_unused_local/3, format_error/1
|
||||
record_definition/3, record_defaults/4,
|
||||
ensure_no_function_conflict/4, warn_unused_local/3,
|
||||
format_error/1
|
||||
]).
|
||||
-include("elixir.hrl").
|
||||
|
||||
@@ -18,9 +16,9 @@
|
||||
%% RECORD
|
||||
|
||||
setup(Module) ->
|
||||
case code:ensure_loaded(?tracker) of
|
||||
{ module, _ } -> ets:insert(Module, { ?attr, ?tracker:start_link() });
|
||||
{ error, _ } -> ok
|
||||
case code:is_loaded(?tracker) of
|
||||
{ file, _ } -> ets:insert(Module, { ?attr, ?tracker:start_link() });
|
||||
false -> ok
|
||||
end.
|
||||
|
||||
cleanup(Module) ->
|
||||
@@ -39,40 +37,21 @@ record_local(Tuple, Module, Function) ->
|
||||
true
|
||||
end).
|
||||
|
||||
record_warn(Ref, Warn, Line, Module) ->
|
||||
if_tracker(Module, fun(Pid) ->
|
||||
?tracker:add_warnable(Pid, Ref, Warn, Line),
|
||||
true
|
||||
end).
|
||||
|
||||
record_import(_Tuple, Receiver, Module, _Function)
|
||||
when Module == nil; Module == Receiver -> false;
|
||||
record_import(Tuple, Receiver, Module, Function) ->
|
||||
try
|
||||
Pid = ets:lookup_element(Module, ?attr, 2),
|
||||
?tracker:add_import(Pid, Function, Receiver, Tuple)
|
||||
catch
|
||||
error:badarg -> false
|
||||
end.
|
||||
if_tracker(Module, fun(Pid) ->
|
||||
?tracker:add_import(Pid, Function, Receiver, Tuple),
|
||||
true
|
||||
end).
|
||||
|
||||
record_remote(_Tuple, Receiver, Module, _Function)
|
||||
when Module == nil; Module == Receiver -> false;
|
||||
record_remote(Tuple, Receiver, Module, Function) ->
|
||||
try
|
||||
Pid = ets:lookup_element(Module, ?attr, 2),
|
||||
?tracker:add_remote(Pid, Function, Receiver, Tuple)
|
||||
catch
|
||||
error:badarg -> false
|
||||
end.
|
||||
|
||||
record_alias(_Receiver, nil) -> false;
|
||||
record_alias(Receiver, Module) ->
|
||||
try
|
||||
Pid = ets:lookup_element(Module, ?attr, 2),
|
||||
?tracker:add_alias(Pid, Receiver)
|
||||
catch
|
||||
error:badarg -> false
|
||||
end.
|
||||
if_tracker(Module, fun(Pid) ->
|
||||
?tracker:add_remote(Pid, Function, Receiver, Tuple),
|
||||
true
|
||||
end).
|
||||
|
||||
record_definition(Tuple, Kind, Module) ->
|
||||
if_tracker(Module, fun(Pid) ->
|
||||
@@ -91,9 +70,10 @@ record_defaults(Tuple, Kind, Module, Defaults) ->
|
||||
%% HELPERS
|
||||
|
||||
if_tracker(Module, Callback) ->
|
||||
case ets:lookup(Module, ?attr) of
|
||||
[{ ?attr, Pid }] -> Callback(Pid);
|
||||
_ -> false
|
||||
try ets:lookup_element(Module, ?attr, 2) of
|
||||
Pid -> Callback(Pid)
|
||||
catch
|
||||
error:badarg -> false
|
||||
end.
|
||||
|
||||
%% ERROR HANDLING
|
||||
@@ -106,14 +86,6 @@ ensure_no_function_conflict(Meta, File, Module, AllDefined) ->
|
||||
end),
|
||||
ok.
|
||||
|
||||
ensure_all_imports_used(_Line, File, Module) ->
|
||||
if_tracker(Module, fun(Pid) ->
|
||||
[ begin
|
||||
elixir_errors:handle_file_warning(File, { L, ?MODULE, { unused_import, M } })
|
||||
end || { M, L } <- ?tracker:collect_unused_imports(Pid)]
|
||||
end),
|
||||
ok.
|
||||
|
||||
warn_unused_local(File, Module, Private) ->
|
||||
if_tracker(Module, fun(Pid) ->
|
||||
Args = [ { Fun, Kind, Defaults } ||
|
||||
@@ -131,9 +103,6 @@ format_error({function_conflict,{Receivers, Name, Arity}}) ->
|
||||
io_lib:format("imported ~ts.~ts/~B conflicts with local function",
|
||||
[elixir_errors:inspect(hd(Receivers)), Name, Arity]);
|
||||
|
||||
format_error({unused_import, Module}) ->
|
||||
io_lib:format("unused import ~ts", [elixir_errors:inspect(Module)]);
|
||||
|
||||
format_error({unused_args,{Name, Arity}}) ->
|
||||
io_lib:format("default arguments in ~ts/~B are never used", [Name, Arity]);
|
||||
|
||||
|
||||
@@ -2,8 +2,7 @@
|
||||
%% overriden are defined in this file.
|
||||
-module(elixir_translator).
|
||||
-export([forms/4, 'forms!'/4]).
|
||||
-export([translate/2, translate_each/2, translate_arg/2,
|
||||
translate_args/2, translate_apply/7]).
|
||||
-export([translate/2, translate_each/2, translate_arg/2, translate_args/2]).
|
||||
-import(elixir_scope, [umergev/2, umergec/2, umergea/2]).
|
||||
-import(elixir_errors, [syntax_error/3, syntax_error/4,
|
||||
compile_error/3, compile_error/4,
|
||||
@@ -12,17 +11,15 @@
|
||||
-include("elixir.hrl").
|
||||
|
||||
forms(String, StartLine, File, Opts) ->
|
||||
try elixir_tokenizer:tokenize(String, StartLine, [{ file, File }|Opts]) of
|
||||
{ ok, Tokens } ->
|
||||
case elixir_tokenizer:tokenize(String, StartLine, [{ file, File }|Opts]) of
|
||||
{ ok, _Line, Tokens } ->
|
||||
try elixir_parser:parse(Tokens) of
|
||||
{ ok, Forms } -> { ok, Forms };
|
||||
{ error, { Line, _, [Error, Token] } } -> { error, { Line, Error, Token } }
|
||||
catch
|
||||
{ error, { Line, _, [Error, Token] } } -> { error, { Line, Error, Token } }
|
||||
end;
|
||||
{ error, { _, _, _ } } = Else -> Else
|
||||
catch
|
||||
{ interpolation_error, { _, _, _ } = Tuple} -> { error, Tuple }
|
||||
{ error, Reason, _Rest, _SoFar } -> { error, Reason }
|
||||
end.
|
||||
|
||||
'forms!'(String, StartLine, File, Opts) ->
|
||||
@@ -36,10 +33,6 @@ forms(String, StartLine, File, Opts) ->
|
||||
translate(Forms, S) ->
|
||||
lists:mapfoldl(fun translate_each/2, S, Forms).
|
||||
|
||||
%% Those macros are "low-level". They are the basic mechanism
|
||||
%% that makes the language work and cannot be partially applied
|
||||
%% nor overwritten.
|
||||
|
||||
%% Assignment operator
|
||||
|
||||
translate_each({ '=', Meta, [Left, Right] }, S) ->
|
||||
@@ -50,7 +43,7 @@ translate_each({ '=', Meta, [Left, Right] }, S) ->
|
||||
|
||||
%% Containers
|
||||
|
||||
translate_each({ C, _, _ } = Original, S) when C == '[]'; C == '{}'; C == '<<>>' ->
|
||||
translate_each({ C, _, _ } = Original, S) when C == '{}'; C == '<<>>' ->
|
||||
elixir_literal:translate(Original, S);
|
||||
|
||||
%% Blocks and scope rewriters
|
||||
@@ -89,7 +82,7 @@ translate_each({ alias, Meta, [Ref] }, S) ->
|
||||
translate_each({ alias, Meta, [Ref, KV] }, S) ->
|
||||
assert_no_match_or_guard_scope(Meta, alias, S),
|
||||
{ TRef, SR } = translate_each(Ref, S),
|
||||
{ TKV, ST } = translate_opts(Meta, alias, [as], no_alias_opts(KV), SR),
|
||||
{ TKV, ST } = translate_opts(Meta, alias, [as, warn], no_alias_opts(KV), SR),
|
||||
|
||||
case TRef of
|
||||
{ atom, _, Old } ->
|
||||
@@ -105,7 +98,7 @@ translate_each({ require, Meta, [Ref, KV] }, S) ->
|
||||
assert_no_match_or_guard_scope(Meta, require, S),
|
||||
|
||||
{ TRef, SR } = translate_each(Ref, S),
|
||||
{ TKV, ST } = translate_opts(Meta, require, [as], no_alias_opts(KV), SR),
|
||||
{ TKV, ST } = translate_opts(Meta, require, [as, warn], no_alias_opts(KV), SR),
|
||||
|
||||
case TRef of
|
||||
{ atom, _, Old } ->
|
||||
@@ -145,7 +138,7 @@ translate_each({ '__DIR__', _Meta, Atom }, S) when is_atom(Atom) ->
|
||||
|
||||
translate_each({ '__ENV__', Meta, Atom }, S) when is_atom(Atom) ->
|
||||
Env = elixir_scope:to_ex_env({ ?line(Meta), S }),
|
||||
{ elixir_utils:elixir_to_erl(Env), S };
|
||||
{ elixir_scope:to_erl(Env), S };
|
||||
|
||||
translate_each({ '__CALLER__', Meta, Atom }, S) when is_atom(Atom) ->
|
||||
{ { var, ?line(Meta), '__CALLER__' }, S#elixir_scope{caller=true} };
|
||||
@@ -153,9 +146,9 @@ translate_each({ '__CALLER__', Meta, Atom }, S) when is_atom(Atom) ->
|
||||
%% Aliases
|
||||
|
||||
translate_each({ '__aliases__', Meta, _ } = Alias, S) ->
|
||||
case elixir_aliases:expand(Alias, S#elixir_scope.aliases, S#elixir_scope.macro_aliases) of
|
||||
case elixir_aliases:expand(Alias, S#elixir_scope.aliases, S#elixir_scope.macro_aliases, S#elixir_scope.lexical_tracker) of
|
||||
Receiver when is_atom(Receiver) ->
|
||||
elixir_tracker:record_alias(Receiver, S#elixir_scope.module),
|
||||
elixir_lexical:record_remote(Receiver, S#elixir_scope.lexical_tracker),
|
||||
{ { atom, ?line(Meta), Receiver }, S };
|
||||
Aliases ->
|
||||
{ TAliases, SA } = translate_args(Aliases, S),
|
||||
@@ -164,7 +157,7 @@ translate_each({ '__aliases__', Meta, _ } = Alias, S) ->
|
||||
true ->
|
||||
Atoms = [Atom || { atom, _, Atom } <- TAliases],
|
||||
Receiver = elixir_aliases:concat(Atoms),
|
||||
elixir_tracker:record_alias(Receiver, S#elixir_scope.module),
|
||||
elixir_lexical:record_remote(Receiver, S#elixir_scope.lexical_tracker),
|
||||
{ { atom, ?line(Meta), Receiver }, SA };
|
||||
false ->
|
||||
Args = [elixir_utils:list_to_cons(?line(Meta), TAliases)],
|
||||
@@ -287,7 +280,7 @@ translate_each({ '&', Meta, [Arg] }, S) ->
|
||||
assert_no_match_or_guard_scope(Meta, '&', S),
|
||||
elixir_fn:capture(Meta, Arg, S);
|
||||
|
||||
translate_each({ fn, Meta, [[{do, { '->', _, Pairs }}]] }, S) ->
|
||||
translate_each({ fn, Meta, [{ '->', _, Pairs }] }, S) ->
|
||||
assert_no_match_or_guard_scope(Meta, 'fn', S),
|
||||
elixir_fn:fn(Meta, Pairs, S);
|
||||
|
||||
@@ -296,30 +289,26 @@ translate_each({ fn, Meta, [[{do, { '->', _, Pairs }}]] }, S) ->
|
||||
translate_each({ Kind, Meta, Args }, S) when is_list(Args), (Kind == lc) orelse (Kind == bc) ->
|
||||
translate_comprehension(Meta, Kind, Args, S);
|
||||
|
||||
%% Super (exceptionally supports partial application)
|
||||
%% Super
|
||||
|
||||
translate_each({ super, Meta, Args } = Original, S) when is_list(Args) ->
|
||||
case elixir_partials:handle(Original, S) of
|
||||
error ->
|
||||
assert_no_match_or_guard_scope(Meta, super, S),
|
||||
Module = assert_module_scope(Meta, super, S),
|
||||
Function = assert_function_scope(Meta, super, S),
|
||||
elixir_def_overridable:ensure_defined(Meta, Module, Function, S),
|
||||
translate_each({ super, Meta, Args }, S) when is_list(Args) ->
|
||||
assert_no_match_or_guard_scope(Meta, super, S),
|
||||
Module = assert_module_scope(Meta, super, S),
|
||||
Function = assert_function_scope(Meta, super, S),
|
||||
elixir_def_overridable:ensure_defined(Meta, Module, Function, S),
|
||||
|
||||
{ _, Arity } = Function,
|
||||
{ _, Arity } = Function,
|
||||
|
||||
{ TArgs, TS } = if
|
||||
length(Args) == Arity ->
|
||||
translate_args(Args, S);
|
||||
true ->
|
||||
syntax_error(Meta, S#elixir_scope.file, "super must be called with the same number of "
|
||||
"arguments as the current function")
|
||||
end,
|
||||
{ TArgs, TS } = if
|
||||
length(Args) == Arity ->
|
||||
translate_args(Args, S);
|
||||
true ->
|
||||
syntax_error(Meta, S#elixir_scope.file, "super must be called with the same number of "
|
||||
"arguments as the current function")
|
||||
end,
|
||||
|
||||
Super = elixir_def_overridable:name(Module, Function),
|
||||
{ { call, ?line(Meta), { atom, ?line(Meta), Super }, TArgs }, TS#elixir_scope{super=true} };
|
||||
Else -> Else
|
||||
end;
|
||||
Super = elixir_def_overridable:name(Module, Function),
|
||||
{ { call, ?line(Meta), { atom, ?line(Meta), Super }, TArgs }, TS#elixir_scope{super=true} };
|
||||
|
||||
translate_each({ 'super?', Meta, [] }, S) ->
|
||||
Module = assert_module_scope(Meta, 'super?', S),
|
||||
@@ -329,9 +318,9 @@ translate_each({ 'super?', Meta, [] }, S) ->
|
||||
|
||||
%% Variables
|
||||
|
||||
translate_each({ '^', Meta, [ { Name, _, Kind } = Var ] },
|
||||
translate_each({ '^', Meta, [ { Name, VarMeta, Kind } = Var ] },
|
||||
#elixir_scope{extra=fn_match, extra_guards=Extra} = S) when is_atom(Name), is_atom(Kind) ->
|
||||
case orddict:find({ Name, Kind }, S#elixir_scope.backup_vars) of
|
||||
case orddict:find({ Name, var_kind(VarMeta, Kind) }, S#elixir_scope.backup_vars) of
|
||||
{ ok, Value } ->
|
||||
Line = ?line(Meta),
|
||||
{ TVar, TS } = translate_each(Var, S),
|
||||
@@ -341,8 +330,8 @@ translate_each({ '^', Meta, [ { Name, _, Kind } = Var ] },
|
||||
compile_error(Meta, S#elixir_scope.file, "unbound variable ^~ts", [Name])
|
||||
end;
|
||||
|
||||
translate_each({ '^', Meta, [ { Name, _, Kind } ] }, #elixir_scope{context=match} = S) when is_atom(Name), is_atom(Kind) ->
|
||||
case orddict:find({ Name, Kind }, S#elixir_scope.backup_vars) of
|
||||
translate_each({ '^', Meta, [ { Name, VarMeta, Kind } ] }, #elixir_scope{context=match} = S) when is_atom(Name), is_atom(Kind) ->
|
||||
case orddict:find({ Name, var_kind(VarMeta, Kind) }, S#elixir_scope.backup_vars) of
|
||||
{ ok, Value } ->
|
||||
{ { var, ?line(Meta), Value }, S };
|
||||
error ->
|
||||
@@ -358,7 +347,7 @@ translate_each({ '^', Meta, [ Expr ] }, S) ->
|
||||
"the unary operator ^ can only be used with variables, invalid expression ^~ts", ['Elixir.Macro':to_string(Expr)]);
|
||||
|
||||
translate_each({ Name, Meta, Kind }, S) when is_atom(Name), is_atom(Kind) ->
|
||||
elixir_scope:translate_var(Meta, Name, Kind, S, fun() ->
|
||||
elixir_scope:translate_var(Meta, Name, var_kind(Meta, Kind), S, fun() ->
|
||||
translate_each({ Name, Meta, [] }, S)
|
||||
end);
|
||||
|
||||
@@ -367,101 +356,99 @@ translate_each({ Name, Meta, Kind }, S) when is_atom(Name), is_atom(Kind) ->
|
||||
translate_each({ '->', Meta, _Args }, S) ->
|
||||
syntax_error(Meta, S#elixir_scope.file, "unhandled operator ->");
|
||||
|
||||
translate_each({ Atom, Meta, Args } = Original, S) when is_atom(Atom) ->
|
||||
translate_each({ Atom, Meta, Args }, S) when is_atom(Atom), is_list(Meta), is_list(Args) ->
|
||||
assert_no_ambiguous_op(Atom, Meta, Args, S),
|
||||
|
||||
case elixir_partials:is_sequential(Args) andalso
|
||||
elixir_dispatch:import_function(Meta, Atom, length(Args), S) of
|
||||
false ->
|
||||
case elixir_partials:handle(Original, S) of
|
||||
error ->
|
||||
Callback = fun() ->
|
||||
case S#elixir_scope.context of
|
||||
match ->
|
||||
compile_error(Meta, S#elixir_scope.file,
|
||||
"cannot invoke function ~ts/~B inside match", [Atom, length(Args)]);
|
||||
guard ->
|
||||
Arity = length(Args),
|
||||
File = S#elixir_scope.file,
|
||||
case Arity of
|
||||
0 -> compile_error(Meta, File, "unknown variable ~ts or cannot invoke "
|
||||
"function ~ts/~B inside guard", [Atom, Atom, Arity]);
|
||||
_ -> compile_error(Meta, File, "cannot invoke local ~ts/~B inside guard",
|
||||
[Atom, Arity])
|
||||
end;
|
||||
_ ->
|
||||
translate_local(Meta, Atom, Args, S)
|
||||
end
|
||||
end,
|
||||
Callback = fun() ->
|
||||
case S#elixir_scope.context of
|
||||
match ->
|
||||
compile_error(Meta, S#elixir_scope.file,
|
||||
"cannot invoke function ~ts/~B inside match", [Atom, length(Args)]);
|
||||
guard ->
|
||||
Arity = length(Args),
|
||||
File = S#elixir_scope.file,
|
||||
case Arity of
|
||||
0 -> compile_error(Meta, File, "unknown variable ~ts or cannot invoke "
|
||||
"function ~ts/~B inside guard", [Atom, Atom, Arity]);
|
||||
_ -> compile_error(Meta, File, "cannot invoke local ~ts/~B inside guard",
|
||||
[Atom, Arity])
|
||||
end;
|
||||
_ ->
|
||||
translate_local(Meta, Atom, Args, S)
|
||||
end
|
||||
end,
|
||||
|
||||
elixir_dispatch:dispatch_import(Meta, Atom, Args, S, Callback);
|
||||
Else -> Else
|
||||
end;
|
||||
Else -> Else
|
||||
end;
|
||||
elixir_dispatch:dispatch_import(Meta, Atom, Args, S, Callback);
|
||||
|
||||
%% Remote calls
|
||||
|
||||
translate_each({ { '.', _, [Left, Right] }, Meta, Args } = Original, S) when is_atom(Right) ->
|
||||
translate_each({ { '.', _, [Left, Right] }, Meta, Args }, S)
|
||||
when (is_tuple(Left) orelse is_atom(Left)), is_atom(Right), is_list(Meta), is_list(Args) ->
|
||||
{ TLeft, SL } = translate_each(Left, S),
|
||||
{ TRight, SR } = translate_each(Right, umergec(S, SL)),
|
||||
|
||||
Fun = (element(1, TLeft) == atom) andalso elixir_partials:is_sequential(Args) andalso
|
||||
elixir_dispatch:require_function(Meta, element(3, TLeft), Right, length(Args), SL),
|
||||
Callback = fun() ->
|
||||
case TLeft of
|
||||
{ atom, _, Receiver } ->
|
||||
Tuple = { element(3, TRight), length(Args) },
|
||||
elixir_lexical:record_remote(Receiver, S#elixir_scope.lexical_tracker),
|
||||
elixir_tracker:record_remote(Tuple, Receiver, S#elixir_scope.module, S#elixir_scope.function);
|
||||
_ ->
|
||||
ok
|
||||
end,
|
||||
|
||||
case Fun of
|
||||
false ->
|
||||
case elixir_partials:handle(Original, S) of
|
||||
error ->
|
||||
{ TRight, SR } = translate_each(Right, umergec(S, SL)),
|
||||
Callback = fun() -> translate_apply(Meta, TLeft, TRight, Args, S, SL, SR) end,
|
||||
Line = ?line(Meta),
|
||||
{ TArgs, SA } = translate_args(Args, umergec(S, SR)),
|
||||
{ { call, Line, { remote, Line, TLeft, TRight }, TArgs }, umergev(SL, umergev(SR, SA)) }
|
||||
end,
|
||||
|
||||
case TLeft of
|
||||
{ atom, _, Receiver } ->
|
||||
elixir_dispatch:dispatch_require(Meta, Receiver, Right, Args, umergev(SL, SR), fun() ->
|
||||
case S#elixir_scope.context of
|
||||
Context when Receiver /= erlang, (Context == match) orelse (Context == guard) ->
|
||||
compile_error(Meta, S#elixir_scope.file, "cannot invoke remote function ~ts.~ts/~B inside ~ts",
|
||||
[elixir_errors:inspect(Receiver), Right, length(Args), Context]);
|
||||
_ ->
|
||||
Callback()
|
||||
end
|
||||
end);
|
||||
_ ->
|
||||
case S#elixir_scope.context of
|
||||
Context when Context == match; Context == guard ->
|
||||
compile_error(Meta, S#elixir_scope.file, "cannot invoke remote function ~ts/~B inside ~ts",
|
||||
[Right, length(Args), Context]);
|
||||
_ ->
|
||||
Callback()
|
||||
end
|
||||
end;
|
||||
Else -> Else
|
||||
end;
|
||||
Else -> Else
|
||||
case TLeft of
|
||||
{ atom, _, Receiver } ->
|
||||
elixir_dispatch:dispatch_require(Meta, Receiver, Right, Args, umergev(SL, SR), fun() ->
|
||||
case S#elixir_scope.context of
|
||||
Context when Receiver /= erlang, (Context == match) orelse (Context == guard) ->
|
||||
compile_error(Meta, S#elixir_scope.file, "cannot invoke remote function ~ts.~ts/~B inside ~ts",
|
||||
[elixir_errors:inspect(Receiver), Right, length(Args), Context]);
|
||||
_ ->
|
||||
Callback()
|
||||
end
|
||||
end);
|
||||
_ ->
|
||||
case S#elixir_scope.context of
|
||||
Context when Context == match; Context == guard ->
|
||||
compile_error(Meta, S#elixir_scope.file, "cannot invoke remote function ~ts/~B inside ~ts",
|
||||
[Right, length(Args), Context]);
|
||||
_ ->
|
||||
Callback()
|
||||
end
|
||||
end;
|
||||
|
||||
%% Anonymous function calls
|
||||
|
||||
translate_each({ { '.', _, [Expr] }, Meta, Args } = Original, S) ->
|
||||
translate_each({ { '.', _, [Expr] }, Meta, Args }, S) when is_list(Args) ->
|
||||
{ TExpr, SE } = translate_each(Expr, S),
|
||||
case TExpr of
|
||||
{ atom, _, Atom } ->
|
||||
syntax_error(Meta, S#elixir_scope.file, "invalid function call :~ts.()", [Atom]);
|
||||
_ ->
|
||||
case elixir_partials:handle(Original, S) of
|
||||
error ->
|
||||
{ TArgs, SA } = translate_args(Args, umergec(S, SE)),
|
||||
{ {call, ?line(Meta), TExpr, TArgs}, umergev(SE, SA) };
|
||||
Else -> Else
|
||||
end
|
||||
{ TArgs, SA } = translate_args(Args, umergec(S, SE)),
|
||||
{ {call, ?line(Meta), TExpr, TArgs}, umergev(SE, SA) }
|
||||
end;
|
||||
|
||||
%% Invalid calls
|
||||
|
||||
translate_each({ Invalid, Meta, _Args }, S) ->
|
||||
syntax_error(Meta, S#elixir_scope.file, "unexpected parenthesis after ~ts",
|
||||
translate_each({ { '.', _, [Invalid, _] }, Meta, Args }, S) when is_list(Meta) and is_list(Args) ->
|
||||
syntax_error(Meta, S#elixir_scope.file, "invalid remote call on ~ts",
|
||||
['Elixir.Macro':to_string(Invalid)]);
|
||||
|
||||
translate_each({ _, Meta, Args } = Invalid, S) when is_list(Meta) and is_list(Args) ->
|
||||
syntax_error(Meta, S#elixir_scope.file, "invalid call ~ts",
|
||||
['Elixir.Macro':to_string(Invalid)]);
|
||||
|
||||
translate_each({ _, _, _ } = Tuple, S) ->
|
||||
syntax_error([{line,0}], S#elixir_scope.file, "expected a valid quoted expression, got: ~ts",
|
||||
['Elixir.Kernel':inspect(Tuple, [{raw,true}])]);
|
||||
|
||||
%% Literals
|
||||
|
||||
translate_each(Literal, S) ->
|
||||
@@ -469,6 +456,12 @@ translate_each(Literal, S) ->
|
||||
|
||||
%% Helpers
|
||||
|
||||
var_kind(Meta, Kind) ->
|
||||
case lists:keyfind(counter, 1, Meta) of
|
||||
{ counter, Counter } -> Counter;
|
||||
false -> Kind
|
||||
end.
|
||||
|
||||
%% Opts
|
||||
|
||||
translate_opts(Meta, Kind, Allowed, Opts, S) ->
|
||||
@@ -542,7 +535,7 @@ translate_alias(Meta, IncludeByDefault, Old, TKV, S) ->
|
||||
"invalid :as for alias, nested alias ~s not allowed", [elixir_errors:inspect(New)])
|
||||
end,
|
||||
|
||||
{ { atom, ?line(Meta), nil }, elixir_aliases:store(Meta, New, Old, S) }.
|
||||
{ { atom, ?line(Meta), nil }, elixir_aliases:store(Meta, New, Old, TKV, S) }.
|
||||
|
||||
no_alias_opts(KV) when is_list(KV) ->
|
||||
case lists:keyfind(as, 1, KV) of
|
||||
@@ -603,41 +596,6 @@ translate_args(Args, S) ->
|
||||
{ TArgs, { SC, SV } } = lists:mapfoldl(fun translate_arg/2, {S, S}, Args),
|
||||
{ TArgs, umergea(SV, SC) }.
|
||||
|
||||
%% Translate apply
|
||||
%% Used by both apply and external function invocation macros.
|
||||
|
||||
translate_apply(Meta, TLeft, TRight, Args, S, SL, SR) ->
|
||||
Line = ?line(Meta),
|
||||
|
||||
Optimize = case (Args == []) orelse lists:last(Args) of
|
||||
{ '|', _, _ } -> false;
|
||||
_ ->
|
||||
case TRight of
|
||||
{ atom, _, _ } -> true;
|
||||
_ -> false
|
||||
end
|
||||
end,
|
||||
|
||||
case Optimize of
|
||||
true ->
|
||||
%% Register the remote
|
||||
case TLeft of
|
||||
{ atom, _, Receiver } ->
|
||||
Tuple = { element(3, TRight), length(Args) },
|
||||
elixir_tracker:record_remote(Tuple, Receiver, S#elixir_scope.module, S#elixir_scope.function);
|
||||
_ ->
|
||||
ok
|
||||
end,
|
||||
|
||||
{ TArgs, SA } = translate_args(Args, umergec(S, SR)),
|
||||
FS = umergev(SL, umergev(SR,SA)),
|
||||
{ { call, Line, { remote, Line, TLeft, TRight }, TArgs }, FS };
|
||||
false ->
|
||||
{ TArgs, SA } = translate_each(Args, umergec(S, SR)),
|
||||
FS = umergev(SL, umergev(SR,SA)),
|
||||
{ ?wrap_call(Line, erlang, apply, [TLeft, TRight, TArgs]), FS }
|
||||
end.
|
||||
|
||||
%% __op__ helpers
|
||||
|
||||
assert_no_match_scope_for_bin_op(Meta, Op, S)
|
||||
|
||||
@@ -150,7 +150,8 @@ rescue_each_ref(Meta, Var, [H|T], Elixir, Erlang, _Safe, S) when
|
||||
H == 'Elixir.ArgumentError'; H == 'Elixir.ArithmeticError';
|
||||
H == 'Elixir.BadArityError'; H == 'Elixir.BadFunctionError';
|
||||
H == 'Elixir.MatchError'; H == 'Elixir.CaseClauseError';
|
||||
H == 'Elixir.FunctionClauseError'; H == 'Elixir.SystemLimitError' ->
|
||||
H == 'Elixir.TryClauseError'; H == 'Elixir.FunctionClauseError';
|
||||
H == 'Elixir.SystemLimitError' ->
|
||||
Expr = { 'or', Meta, [
|
||||
erlang_rescue_guard_for(Meta, Var, H),
|
||||
exception_compare(Meta, Var, H)
|
||||
@@ -176,8 +177,8 @@ rescue_each_ref(_, _, [], Elixir, Erlang, Safe, _) ->
|
||||
erlang_rescues() ->
|
||||
[
|
||||
'Elixir.UndefinedFunctionError', 'Elixir.ArgumentError', 'Elixir.ArithmeticError', 'Elixir.BadArityError',
|
||||
'Elixir.BadFunctionError', 'Elixir.MatchError', 'Elixir.CaseClauseError', 'Elixir.FunctionClauseError',
|
||||
'Elixir.SystemLimitError', 'Elixir.ErlangError'
|
||||
'Elixir.BadFunctionError', 'Elixir.MatchError', 'Elixir.CaseClauseError', 'Elixir.TryClauseError',
|
||||
'Elixir.FunctionClauseError', 'Elixir.SystemLimitError', 'Elixir.ErlangError'
|
||||
].
|
||||
|
||||
erlang_rescue_guard_for(Meta, Var, List) when is_list(List) ->
|
||||
@@ -219,6 +220,12 @@ erlang_rescue_guard_for(Meta, Var, 'Elixir.CaseClauseError') ->
|
||||
exception_compare(Meta, Var, case_clause)
|
||||
] };
|
||||
|
||||
erlang_rescue_guard_for(Meta, Var, 'Elixir.TryClauseError') ->
|
||||
{ 'and', Meta, [
|
||||
{ is_tuple, Meta, [Var] },
|
||||
exception_compare(Meta, Var, try_clause)
|
||||
] };
|
||||
|
||||
erlang_rescue_guard_for(Meta, Var, 'Elixir.ArgumentError') ->
|
||||
{ 'or', Meta, [
|
||||
{ '==', Meta, [Var, badarg] },
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
%% Convenience functions used throughout elixir source code
|
||||
%% for ast manipulation and querying.
|
||||
-module(elixir_utils).
|
||||
-export([elixir_to_erl/1, elixir_to_erl/3,
|
||||
get_line/1, split_last/1,
|
||||
-export([elixir_to_erl/1, elixir_to_erl/2,
|
||||
get_line/1, split_last/1, elixir_to_erl/3,
|
||||
characters_to_list/1, characters_to_binary/1,
|
||||
cons_to_list/1, list_to_cons/2, list_to_cons/3,
|
||||
convert_to_boolean/5, returns_boolean/1,
|
||||
@@ -10,7 +10,7 @@
|
||||
-include("elixir.hrl").
|
||||
-include_lib("kernel/include/file.hrl").
|
||||
|
||||
get_line(Opts) ->
|
||||
get_line(Opts) when is_list(Opts) ->
|
||||
case lists:keyfind(line, 1, Opts) of
|
||||
{ line, Line } when is_integer(Line) -> Line;
|
||||
false -> 0
|
||||
|
||||
@@ -5,17 +5,14 @@ defmodule KernelTest do
|
||||
|
||||
doctest Access.List
|
||||
doctest String.Chars.List
|
||||
doctest Inspect.Atom
|
||||
doctest Inspect.BitString
|
||||
doctest Inspect.List
|
||||
doctest Inspect.Number
|
||||
doctest Inspect.Regex
|
||||
doctest Inspect.Tuple
|
||||
doctest Bitwise
|
||||
doctest Code
|
||||
doctest Enum
|
||||
doctest Exception
|
||||
doctest Float
|
||||
doctest IO.ANSI
|
||||
doctest Inspect.Algebra
|
||||
doctest Integer
|
||||
doctest Keyword
|
||||
doctest Kernel
|
||||
doctest Kernel.SpecialForms
|
||||
@@ -24,9 +21,11 @@ defmodule KernelTest do
|
||||
doctest Module
|
||||
doctest OptionParser
|
||||
doctest Path
|
||||
doctest Protocol.Consolidation
|
||||
doctest Regex
|
||||
doctest Set
|
||||
doctest Stream
|
||||
doctest String
|
||||
doctest Inspect.Algebra
|
||||
doctest Tuple
|
||||
doctest Version
|
||||
end
|
||||
|
||||
@@ -5,6 +5,11 @@ defmodule CodeTest do
|
||||
import PathHelpers
|
||||
|
||||
def one, do: 1
|
||||
def genmodule(name) do
|
||||
defmodule name do
|
||||
Kernel.LexicalTracker.remotes(__MODULE__)
|
||||
end
|
||||
end
|
||||
|
||||
contents = quote do
|
||||
defmodule CodeTest.Sample do
|
||||
@@ -19,6 +24,10 @@ defmodule CodeTest do
|
||||
assert { 3, _ } = Code.eval_string("a + b", [a: 1, b: 2], __ENV__.location)
|
||||
end
|
||||
|
||||
test :eval_string_with_other_context do
|
||||
assert Code.eval_string("var!(a, Sample) = 1") == { 1, [{{:a,Sample},1}] }
|
||||
end
|
||||
|
||||
test :eval_with_unnamed_scopes do
|
||||
assert { RuntimeError[], [a: RuntimeError[]] } =
|
||||
Code.eval_string("a = (try do (raise \"hello\") rescue e -> e end)")
|
||||
@@ -60,15 +69,6 @@ defmodule CodeTest do
|
||||
assert Code.require_file(fixture_path("code_sample.exs")) != nil
|
||||
end
|
||||
|
||||
test :path_manipulation do
|
||||
path = __DIR__
|
||||
Code.prepend_path path
|
||||
assert to_char_list(path) in :code.get_path
|
||||
|
||||
Code.delete_path path
|
||||
refute to_char_list(path) in :code.get_path
|
||||
end
|
||||
|
||||
test :file do
|
||||
assert :filename.absname(__FILE__) == __FILE__
|
||||
end
|
||||
@@ -104,6 +104,13 @@ defmodule CodeTest do
|
||||
assert Keyword.get(compile, :source) != nil
|
||||
end
|
||||
|
||||
test :compile_string_works_accross_lexical_scopes do
|
||||
assert [{ CompileCrossSample, _ }] = Code.compile_string("CodeTest.genmodule CompileCrossSample")
|
||||
after
|
||||
:code.purge CompileCrossSample
|
||||
:code.delete CompileCrossSample
|
||||
end
|
||||
|
||||
test :compile_string do
|
||||
assert [{ CompileStringSample, _ }] = Code.compile_string("defmodule CompileStringSample, do: :ok")
|
||||
after
|
||||
@@ -128,3 +135,16 @@ defmodule CodeTest do
|
||||
refute Code.ensure_compiled?(Unknown.Module)
|
||||
end
|
||||
end
|
||||
|
||||
defmodule Code.SyncTest do
|
||||
use ExUnit.Case
|
||||
|
||||
test :path_manipulation do
|
||||
path = Path.join(__DIR__, "fixtures")
|
||||
Code.prepend_path path
|
||||
assert to_char_list(path) in :code.get_path
|
||||
|
||||
Code.delete_path path
|
||||
refute to_char_list(path) in :code.get_path
|
||||
end
|
||||
end
|
||||
|
||||
@@ -3,8 +3,8 @@ Code.require_file "test_helper.exs", __DIR__
|
||||
defmodule DictTest.Common do
|
||||
defmacro __using__(module) do
|
||||
quote location: :keep do
|
||||
# Most underlying Dict implementations have no key order guarantees,
|
||||
# sort them before we compare:
|
||||
# Most underlying Dict implementations have no key
|
||||
# order guarantees, sort them before we compare:
|
||||
defmacrop dicts_equal(actual, expected) do
|
||||
quote do
|
||||
cmp = fn { k1, _ }, { k2, _ } -> k1 < k2 end
|
||||
@@ -12,12 +12,31 @@ defmodule DictTest.Common do
|
||||
end
|
||||
end
|
||||
|
||||
defp empty_dict, do: unquote(module).new
|
||||
|
||||
defp new_dict(list // [{"first_key", 1}, {"second_key", 2}]) do
|
||||
unquote(module).new list
|
||||
end
|
||||
|
||||
defp new_dict(list, transform) do
|
||||
unquote(module).new list, transform
|
||||
end
|
||||
|
||||
defp int_dict do
|
||||
unquote(module).new [{1,1}]
|
||||
end
|
||||
|
||||
test :access do
|
||||
dict = new_dict [{"first_key", 1}, {"second_key", 2}]
|
||||
assert dict["first_key"] == 1
|
||||
assert dict["third_key"] == nil
|
||||
end
|
||||
|
||||
test :access_with_match do
|
||||
assert int_dict[1] == 1
|
||||
assert int_dict[1.0] == nil
|
||||
end
|
||||
|
||||
test :new_pairs do
|
||||
dict = new_dict [{"first_key", 1}, {"second_key", 2}]
|
||||
assert 2 == Dict.size dict
|
||||
@@ -41,12 +60,22 @@ defmodule DictTest.Common do
|
||||
assert "default" == Dict.get(empty_dict, "first_key", "default")
|
||||
end
|
||||
|
||||
test :get_with_match do
|
||||
assert Dict.get(int_dict, 1) == 1
|
||||
assert Dict.get(int_dict, 1.0) == nil
|
||||
end
|
||||
|
||||
test :fetch do
|
||||
assert { :ok, 1 } == Dict.fetch(new_dict, "first_key")
|
||||
assert { :ok, 2 } == Dict.fetch(new_dict, "second_key")
|
||||
assert :error == Dict.fetch(new_dict, "other_key")
|
||||
end
|
||||
|
||||
test :fetch_with_match do
|
||||
assert Dict.fetch(int_dict, 1) == { :ok, 1 }
|
||||
assert Dict.fetch(int_dict, 1.0) == :error
|
||||
end
|
||||
|
||||
test :fetch! do
|
||||
assert 1 == Dict.fetch!(new_dict, "first_key")
|
||||
assert 2 == Dict.fetch!(new_dict, "second_key")
|
||||
@@ -55,36 +84,62 @@ defmodule DictTest.Common do
|
||||
end
|
||||
end
|
||||
|
||||
test :fetch_with_match! do
|
||||
assert Dict.fetch!(int_dict, 1) == 1
|
||||
assert_raise KeyError, fn ->
|
||||
assert Dict.fetch!(int_dict, 1.0)
|
||||
end
|
||||
end
|
||||
|
||||
test :put do
|
||||
dict = Dict.put(new_dict, "first_key", {1})
|
||||
assert {1} == Dict.get dict, "first_key"
|
||||
assert 2 == Dict.get dict, "second_key"
|
||||
end
|
||||
|
||||
test :put_with_match do
|
||||
assert Dict.get(Dict.put(int_dict, 1, :other), 1) == :other
|
||||
assert Dict.get(Dict.put(int_dict, 1.0, :other), 1) == 1
|
||||
assert Dict.get(Dict.put(int_dict, 1, :other), 1.0) == nil
|
||||
assert Dict.get(Dict.put(int_dict, 1.0, :other), 1.0) == :other
|
||||
end
|
||||
|
||||
test :put_new do
|
||||
dict = Dict.put_new(new_dict, "first_key", {1})
|
||||
assert 1 == Dict.get dict, "first_key"
|
||||
end
|
||||
|
||||
test :put_new_with_match do
|
||||
assert Dict.get(Dict.put_new(int_dict, 1, :other), 1) == 1
|
||||
assert Dict.get(Dict.put_new(int_dict, 1.0, :other), 1) == 1
|
||||
assert Dict.get(Dict.put_new(int_dict, 1, :other), 1.0) == nil
|
||||
assert Dict.get(Dict.put_new(int_dict, 1.0, :other), 1.0) == :other
|
||||
end
|
||||
|
||||
test :keys do
|
||||
assert ["first_key", "second_key"] == Enum.sort Dict.keys new_dict
|
||||
assert [] == Dict.keys empty_dict
|
||||
assert Enum.sort(Dict.keys new_dict) == ["first_key", "second_key"]
|
||||
assert Dict.keys(empty_dict) == []
|
||||
end
|
||||
|
||||
test :values do
|
||||
assert [1, 2] ==Enum.sort Dict.values(new_dict)
|
||||
assert [] == Dict.values empty_dict
|
||||
assert Enum.sort(Dict.values(new_dict)) == [1, 2]
|
||||
assert Dict.values(empty_dict) == []
|
||||
end
|
||||
|
||||
test :delete do
|
||||
mdict = Dict.delete new_dict, "second_key"
|
||||
assert 1 == Dict.size mdict
|
||||
assert Dict.size(mdict) == 1
|
||||
assert Dict.has_key? mdict, "first_key"
|
||||
refute Dict.has_key? mdict, "second_key"
|
||||
|
||||
mdict = Dict.delete(new_dict, "other_key")
|
||||
assert mdict == new_dict
|
||||
assert 0 == Dict.size Dict.delete(empty_dict, "other_key")
|
||||
assert Dict.size(Dict.delete(empty_dict, "other_key")) == 0
|
||||
end
|
||||
|
||||
test :delete_with_match do
|
||||
assert Dict.get(Dict.delete(int_dict, 1), 1) == nil
|
||||
assert Dict.get(Dict.delete(int_dict, 1.0), 1) == 1
|
||||
end
|
||||
|
||||
test :merge do
|
||||
@@ -123,17 +178,28 @@ defmodule DictTest.Common do
|
||||
refute Dict.has_key?(dict, "b")
|
||||
end
|
||||
|
||||
test :has_key_with_match do
|
||||
assert Dict.has_key?(int_dict, 1)
|
||||
refute Dict.has_key?(int_dict, 1.0)
|
||||
end
|
||||
|
||||
test :size do
|
||||
assert 2 == Dict.size new_dict
|
||||
assert 0 == Dict.size empty_dict
|
||||
end
|
||||
|
||||
test :update do
|
||||
dict = Dict.update! new_dict, "first_key", fn val -> -val end
|
||||
assert -1 == Dict.get dict, "first_key"
|
||||
dict = Dict.update!(new_dict, "first_key", fn val -> -val end)
|
||||
assert Dict.get(dict, "first_key") == -1
|
||||
|
||||
dict = Dict.update dict, "non-existent", "...", fn val -> -val end
|
||||
assert "..." == Dict.get dict, "non-existent"
|
||||
dict = Dict.update(dict, "non-existent", "...", fn val -> -val end)
|
||||
assert Dict.get(dict, "non-existent") == "..."
|
||||
end
|
||||
|
||||
test :update_with_match do
|
||||
assert Dict.get(Dict.update!(int_dict, 1, &(&1 + 1)), 1) == 2
|
||||
assert Dict.get(Dict.update(int_dict, 1.0, 2, &(&1 + 1)), 1) == 1
|
||||
assert Dict.get(Dict.update(int_dict, 1.0, 2, &(&1 + 1)), 1.0) == 2
|
||||
end
|
||||
|
||||
test :pop do
|
||||
@@ -158,6 +224,16 @@ defmodule DictTest.Common do
|
||||
assert dicts_equal actual, new_dict
|
||||
end
|
||||
|
||||
test :pop_with_match do
|
||||
{v, actual} = Dict.pop(int_dict, 1)
|
||||
assert v == 1
|
||||
assert dicts_equal actual, []
|
||||
|
||||
{v, actual} = Dict.pop(int_dict, 1.0)
|
||||
assert v == nil
|
||||
assert dicts_equal actual, int_dict
|
||||
end
|
||||
|
||||
test :split do
|
||||
split_keys = []
|
||||
{take, drop} = Dict.split(new_dict, split_keys)
|
||||
@@ -185,6 +261,16 @@ defmodule DictTest.Common do
|
||||
assert dicts_equal drop, drop_expected
|
||||
end
|
||||
|
||||
test :split_with_match do
|
||||
{ take, drop } = Dict.split(int_dict, [1])
|
||||
assert dicts_equal take, int_dict
|
||||
assert dicts_equal drop, []
|
||||
|
||||
{ take, drop } = Dict.split(int_dict, [1.0])
|
||||
assert dicts_equal take, []
|
||||
assert dicts_equal drop, int_dict
|
||||
end
|
||||
|
||||
test :take do
|
||||
result = Dict.take(new_dict, ["unknown_key"])
|
||||
assert dicts_equal result, Dict.empty(new_dict)
|
||||
@@ -195,6 +281,11 @@ defmodule DictTest.Common do
|
||||
assert dicts_equal result, expected
|
||||
end
|
||||
|
||||
test :take_with_match do
|
||||
assert dicts_equal Dict.take(int_dict, [1]), int_dict
|
||||
assert dicts_equal Dict.take(int_dict, [1.0]), []
|
||||
end
|
||||
|
||||
test :drop do
|
||||
result = Dict.drop(new_dict, ["unknown_key"])
|
||||
assert dicts_equal result, new_dict
|
||||
@@ -203,17 +294,44 @@ defmodule DictTest.Common do
|
||||
assert dicts_equal result, Dict.delete(new_dict, "first_key")
|
||||
end
|
||||
|
||||
test :drop_with_match do
|
||||
assert dicts_equal Dict.drop(int_dict, [1]), []
|
||||
assert dicts_equal Dict.drop(int_dict, [1.0]), int_dict
|
||||
end
|
||||
|
||||
test :empty do
|
||||
assert empty_dict == Dict.empty new_dict
|
||||
end
|
||||
|
||||
test :equal? do
|
||||
dict1 = HashDict.new(a: 2, b: 3, f: 5, c: 123)
|
||||
dict2 = ListDict.new(a: 2, b: 3, f: 5, c: 123)
|
||||
dict1 = new_dict(a: 2, b: 3, f: 5, c: 123)
|
||||
dict2 = new_dict(a: 2, b: 3, f: 5, c: 123)
|
||||
assert dict_impl.equal?(dict1, dict2)
|
||||
assert Dict.equal?(dict1, dict2)
|
||||
|
||||
dict2 = Dict.put(dict2, :a, 3)
|
||||
refute dict_impl.equal?(dict1, dict2)
|
||||
refute Dict.equal?(dict1, dict2)
|
||||
|
||||
dict3 = [a: 2, b: 3, f: 5, c: 123, z: 666]
|
||||
refute Dict.equal?(dict1, dict3)
|
||||
refute Dict.equal?(dict3, dict1)
|
||||
end
|
||||
|
||||
test :equal_with_match? do
|
||||
dict1 = new_dict([{1,1}])
|
||||
dict2 = new_dict([{1.0,1}])
|
||||
assert dict_impl.equal?(dict1, dict1)
|
||||
refute dict_impl.equal?(dict1, dict2)
|
||||
assert Dict.equal?(dict1, dict1)
|
||||
refute Dict.equal?(dict1, dict2)
|
||||
end
|
||||
|
||||
test :enum_member do
|
||||
assert Enum.member?(new_dict([{1,1}]), { 1, 1 })
|
||||
refute Enum.member?(new_dict([{1,1}]), { 1.0, 1 })
|
||||
refute Enum.member?(new_dict([{1,1}]), { 1, 1.0 })
|
||||
refute Enum.member?(new_dict([{1,1}]), { 1.0, 1.0 })
|
||||
end
|
||||
|
||||
test "unsupported dict" do
|
||||
@@ -221,16 +339,6 @@ defmodule DictTest.Common do
|
||||
Dict.to_list :bad_dict
|
||||
end
|
||||
end
|
||||
|
||||
defp empty_dict, do: unquote(module).new
|
||||
|
||||
defp new_dict(list // [{"first_key", 1}, {"second_key", 2}]) do
|
||||
unquote(module).new list
|
||||
end
|
||||
|
||||
defp new_dict(list, transform) do
|
||||
unquote(module).new list, transform
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -152,14 +152,14 @@ defmodule EnumTest.List do
|
||||
end
|
||||
|
||||
test :filter_with_match do
|
||||
assert Enum.filter([1, 2, 3], match?(1, &1)) == [1]
|
||||
assert Enum.filter([1, 2, 3], match?(x when x < 3, &1)) == [1, 2]
|
||||
assert Enum.filter([1, 2, 3], match?(_, &1)) == [1, 2, 3]
|
||||
assert Enum.filter([1, 2, 3], &match?(1, &1)) == [1]
|
||||
assert Enum.filter([1, 2, 3], &match?(x when x < 3, &1)) == [1, 2]
|
||||
assert Enum.filter([1, 2, 3], &match?(_, &1)) == [1, 2, 3]
|
||||
end
|
||||
|
||||
test :filter_map do
|
||||
assert Enum.filter_map([1, 2, 3], fn(x) -> rem(x, 2) == 0 end, &1 * 2) == [4]
|
||||
assert Enum.filter_map([2, 4, 6], fn(x) -> rem(x, 2) == 0 end, &1 * 2) == [4, 8, 12]
|
||||
assert Enum.filter_map([1, 2, 3], fn(x) -> rem(x, 2) == 0 end, &(&1 * 2)) == [4]
|
||||
assert Enum.filter_map([2, 4, 6], fn(x) -> rem(x, 2) == 0 end, &(&1 * 2)) == [4, 8, 12]
|
||||
end
|
||||
|
||||
test :flat_map do
|
||||
@@ -185,15 +185,15 @@ defmodule EnumTest.List do
|
||||
end
|
||||
|
||||
test :map_join do
|
||||
assert Enum.map_join([], " = ", &1 * 2) == ""
|
||||
assert Enum.map_join([1, 2, 3], " = ", &1 * 2) == "2 = 4 = 6"
|
||||
assert Enum.map_join([1, 2, 3], &1 * 2) == "246"
|
||||
assert Enum.map_join([], " = ", &(&1 * 2)) == ""
|
||||
assert Enum.map_join([1, 2, 3], " = ", &(&1 * 2)) == "2 = 4 = 6"
|
||||
assert Enum.map_join([1, 2, 3], &(&1 * 2)) == "246"
|
||||
end
|
||||
|
||||
test :join_empty do
|
||||
fun = fn (acc, _) -> acc end
|
||||
assert Enum.join(fun, ".") == ""
|
||||
assert Enum.map_join(fun, ".", &1 + 0) == ""
|
||||
assert Enum.map_join(fun, ".", &(&1 + 0)) == ""
|
||||
end
|
||||
|
||||
test :map do
|
||||
@@ -229,7 +229,7 @@ defmodule EnumTest.List do
|
||||
|
||||
test :sort do
|
||||
assert Enum.sort([5, 3, 2, 4, 1]) == [1, 2, 3, 4, 5]
|
||||
assert Enum.sort([5, 3, 2, 4, 1], &1 > &2) == [5, 4, 3, 2, 1]
|
||||
assert Enum.sort([5, 3, 2, 4, 1], &(&1 > &2)) == [5, 4, 3, 2, 1]
|
||||
end
|
||||
|
||||
test :split do
|
||||
@@ -264,6 +264,13 @@ defmodule EnumTest.List do
|
||||
assert Enum.take([], 3) == []
|
||||
end
|
||||
|
||||
test :take_every do
|
||||
assert Enum.take_every([1, 2, 3, 4, 5, 6, 7, 8, 9, 10], 2) == [1, 3, 5, 7, 9]
|
||||
assert Enum.take_every([], 2) == []
|
||||
assert Enum.take_every([1, 2], 2) == [1]
|
||||
assert Enum.take_every([1, 2, 3], 0) == []
|
||||
end
|
||||
|
||||
test :take_while do
|
||||
assert Enum.take_while([1, 2, 3], fn(x) -> x > 3 end) == []
|
||||
assert Enum.take_while([1, 2, 3], fn(x) -> x <= 1 end) == [1]
|
||||
@@ -358,6 +365,25 @@ defmodule EnumTest.List do
|
||||
assert Enum.slice([1,2,3,4,5], -2, 5) == [4, 5]
|
||||
assert Enum.slice([1,2,3,4,5], -3, 1) == [3]
|
||||
end
|
||||
|
||||
test :slice_range do
|
||||
assert Enum.slice([1,2,3,4,5], 0..0) == [1]
|
||||
assert Enum.slice([1,2,3,4,5], 0..1) == [1, 2]
|
||||
assert Enum.slice([1,2,3,4,5], 0..2) == [1, 2, 3]
|
||||
assert Enum.slice([1,2,3,4,5], 1..2) == [2, 3]
|
||||
assert Enum.slice([1,2,3,4,5], 1..0) == nil
|
||||
assert Enum.slice([1,2,3,4,5], 2..5) == [3, 4, 5]
|
||||
assert Enum.slice([1,2,3,4,5], 4..4) == [5]
|
||||
assert Enum.slice([1,2,3,4,5], 5..5) == []
|
||||
assert Enum.slice([1,2,3,4,5], 6..5) == nil
|
||||
assert Enum.slice([1,2,3,4,5], 6..0) == nil
|
||||
assert Enum.slice([1,2,3,4,5], -6..0) == nil
|
||||
assert Enum.slice([1,2,3,4,5], -6..5) == nil
|
||||
assert Enum.slice([1,2,3,4,5], -5..-1) == [1, 2, 3, 4, 5]
|
||||
assert Enum.slice([1,2,3,4,5], -5..-3) == [1, 2, 3]
|
||||
assert Enum.slice([1,2,3,4,5], -6..-1) == nil
|
||||
assert Enum.slice([1,2,3,4,5], -6..-3) == nil
|
||||
end
|
||||
end
|
||||
|
||||
defmodule EnumTest.Range do
|
||||
@@ -377,10 +403,10 @@ defmodule EnumTest.Range do
|
||||
|
||||
test :any? do
|
||||
range = Range.new(first: 0, last: 5)
|
||||
refute Enum.any?(range, &1 > 10)
|
||||
refute Enum.any?(range, &(&1 > 10))
|
||||
|
||||
range = Range.new(first: 0, last: 5)
|
||||
assert Enum.any?(range, &1 > 3)
|
||||
assert Enum.any?(range, &(&1 > 3))
|
||||
|
||||
range = Range.new(first: 1, last: 0)
|
||||
assert Enum.any?(range)
|
||||
@@ -510,17 +536,17 @@ defmodule EnumTest.Range do
|
||||
|
||||
test :filter_with_match do
|
||||
range = Range.new(first: 1, last: 3)
|
||||
assert Enum.filter(range, match?(1, &1)) == [1]
|
||||
assert Enum.filter(range, match?(x when x < 3, &1)) == [1, 2]
|
||||
assert Enum.filter(range, match?(_, &1)) == [1, 2, 3]
|
||||
assert Enum.filter(range, &match?(1, &1)) == [1]
|
||||
assert Enum.filter(range, &match?(x when x < 3, &1)) == [1, 2]
|
||||
assert Enum.filter(range, &match?(_, &1)) == [1, 2, 3]
|
||||
end
|
||||
|
||||
test :filter_map do
|
||||
range = Range.new(first: 1, last: 3)
|
||||
assert Enum.filter_map(range, fn(x) -> rem(x, 2) == 0 end, &1 * 2) == [4]
|
||||
assert Enum.filter_map(range, fn(x) -> rem(x, 2) == 0 end, &(&1 * 2)) == [4]
|
||||
|
||||
range = Range.new(first: 2, last: 6)
|
||||
assert Enum.filter_map(range, fn(x) -> rem(x, 2) == 0 end, &1 * 2) == [4, 8, 12]
|
||||
assert Enum.filter_map(range, fn(x) -> rem(x, 2) == 0 end, &(&1 * 2)) == [4, 8, 12]
|
||||
end
|
||||
|
||||
test :flat_map do
|
||||
@@ -558,11 +584,11 @@ defmodule EnumTest.Range do
|
||||
|
||||
test :map_join do
|
||||
range = Range.new(first: 1, last: 0)
|
||||
assert Enum.map_join(range, " = ", &1 * 2) == "2 = 0"
|
||||
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"
|
||||
assert Enum.map_join(range, &1 * 2) == "246"
|
||||
assert Enum.map_join(range, " = ", &(&1 * 2)) == "2 = 4 = 6"
|
||||
assert Enum.map_join(range, &(&1 * 2)) == "246"
|
||||
end
|
||||
|
||||
test :map do
|
||||
@@ -597,9 +623,9 @@ defmodule EnumTest.Range do
|
||||
assert Enum.sort(Range.new(first: 2, last: 1)) == [1, 2]
|
||||
assert Enum.sort(Range.new(first: 1, last: 1)) == [1]
|
||||
|
||||
assert Enum.sort(Range.new(first: 3, last: 1), &1 > &2) == [3, 2, 1]
|
||||
assert Enum.sort(Range.new(first: 2, last: 1), &1 > &2) == [2, 1]
|
||||
assert Enum.sort(Range.new(first: 1, last: 1), &1 > &2) == [1]
|
||||
assert Enum.sort(Range.new(first: 3, last: 1), &(&1 > &2)) == [3, 2, 1]
|
||||
assert Enum.sort(Range.new(first: 2, last: 1), &(&1 > &2)) == [2, 1]
|
||||
assert Enum.sort(Range.new(first: 1, last: 1), &(&1 > &2)) == [1]
|
||||
end
|
||||
|
||||
test :split do
|
||||
@@ -642,6 +668,12 @@ defmodule EnumTest.Range do
|
||||
assert Enum.take(range, 3) == [1, 0]
|
||||
end
|
||||
|
||||
test :take_every do
|
||||
assert Enum.take_every(1..10, 2) == [1, 3, 5, 7, 9]
|
||||
assert Enum.take_every(1..2, 2) == [1]
|
||||
assert Enum.take_every(1..3, 0) == []
|
||||
end
|
||||
|
||||
test :take_while do
|
||||
range = Range.new(first: 1, last: 3)
|
||||
assert Enum.take_while(range, fn(x) -> x > 3 end) == []
|
||||
@@ -724,6 +756,25 @@ defmodule EnumTest.Range do
|
||||
assert Enum.slice(1..5, -2, 5) == [4, 5]
|
||||
assert Enum.slice(1..5, -3, 1) == [3]
|
||||
end
|
||||
|
||||
test :slice_range do
|
||||
assert Enum.slice(1..5, 0..0) == [1]
|
||||
assert Enum.slice(1..5, 0..1) == [1, 2]
|
||||
assert Enum.slice(1..5, 0..2) == [1, 2, 3]
|
||||
assert Enum.slice(1..5, 1..2) == [2, 3]
|
||||
assert Enum.slice(1..5, 1..0) == nil
|
||||
assert Enum.slice(1..5, 2..5) == [3, 4, 5]
|
||||
assert Enum.slice(1..5, 4..4) == [5]
|
||||
assert Enum.slice(1..5, 5..5) == []
|
||||
assert Enum.slice(1..5, 6..5) == nil
|
||||
assert Enum.slice(1..5, 6..0) == nil
|
||||
assert Enum.slice(1..5, -6..0) == nil
|
||||
assert Enum.slice(1..5, -6..5) == nil
|
||||
assert Enum.slice(1..5, -5..-1) == [1, 2, 3, 4, 5]
|
||||
assert Enum.slice(1..5, -5..-3) == [1, 2, 3]
|
||||
assert Enum.slice(1..5, -6..-1) == nil
|
||||
assert Enum.slice(1..5, -6..-3) == nil
|
||||
end
|
||||
end
|
||||
|
||||
defmodule EnumTest.Others do
|
||||
|
||||
@@ -3,6 +3,9 @@ Code.require_file "test_helper.exs", __DIR__
|
||||
defmodule Kernel.ExceptionTest do
|
||||
use ExUnit.Case, async: true
|
||||
|
||||
# Ensure fields passed through an expression are valid
|
||||
defexception Custom, %w(message)a
|
||||
|
||||
test :is_exception do
|
||||
assert is_exception(RuntimeError.new)
|
||||
refute is_exception(empty_tuple)
|
||||
@@ -51,6 +54,7 @@ defmodule Kernel.ExceptionTest do
|
||||
assert Exception.format_mfa(Foo, nil, 1) == "Foo.nil/1"
|
||||
assert Exception.format_mfa(Foo, :bar, 1) == "Foo.bar/1"
|
||||
assert Exception.format_mfa(Foo, :bar, []) == "Foo.bar()"
|
||||
assert Exception.format_mfa(nil, :bar, []) == "nil.bar()"
|
||||
assert Exception.format_mfa(:foo, :bar, [1, 2]) == ":foo.bar(1, 2)"
|
||||
assert Exception.format_mfa(Foo, :"bar baz", 1) == "Foo.\"bar baz\"/1"
|
||||
end
|
||||
@@ -73,6 +77,7 @@ defmodule Kernel.ExceptionTest do
|
||||
test :undefined_function_message do
|
||||
assert UndefinedFunctionError.new.message == "undefined function"
|
||||
assert UndefinedFunctionError.new(module: Foo, function: :bar, arity: 1).message == "undefined function: Foo.bar/1"
|
||||
assert UndefinedFunctionError.new(module: nil, function: :bar, arity: 0).message == "undefined function: nil.bar/0"
|
||||
end
|
||||
|
||||
test :function_clause_message do
|
||||
@@ -84,6 +89,26 @@ defmodule Kernel.ExceptionTest do
|
||||
assert ErlangError.new(original: :sample).message == "erlang error: :sample"
|
||||
end
|
||||
|
||||
test :raise_preserves_the_stacktrace do
|
||||
stacktrace =
|
||||
try do
|
||||
raise "a"
|
||||
rescue _ ->
|
||||
[top|_] = System.stacktrace
|
||||
top
|
||||
end
|
||||
file = to_char_list(__FILE__)
|
||||
assert {Kernel.ExceptionTest, :test_raise_preserves_the_stacktrace, _,
|
||||
[file: ^file, line: 95]} = stacktrace # line is sensitive
|
||||
end
|
||||
|
||||
test :defexception do
|
||||
defexception SampleError, field: :ok do
|
||||
# Check do block is properly inline
|
||||
def message(_), do: "hello"
|
||||
end
|
||||
end
|
||||
|
||||
defp empty_tuple, do: {}
|
||||
defp a_tuple, do: { :foo, :bar, :baz }
|
||||
defp a_list, do: [ :foo, :bar, :baz ]
|
||||
|
||||
@@ -550,7 +550,7 @@ defmodule FileTest do
|
||||
|
||||
test :open_a_file_with_function do
|
||||
file = fixture_path("file.txt")
|
||||
assert File.open(file, IO.read(&1, :line)) == { :ok, "FOO\n" }
|
||||
assert File.open(file, &IO.read(&1, :line)) == { :ok, "FOO\n" }
|
||||
end
|
||||
|
||||
test :open_a_missing_file! do
|
||||
@@ -562,7 +562,7 @@ defmodule FileTest do
|
||||
|
||||
test :open_a_file_with_function! do
|
||||
file = fixture_path("file.txt")
|
||||
assert File.open!(file, IO.read(&1, :line)) == "FOO\n"
|
||||
assert File.open!(file, &IO.read(&1, :line)) == "FOO\n"
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
Code.require_file "test_helper.exs", __DIR__
|
||||
|
||||
defmodule FloatTest do
|
||||
use ExUnit.Case, async: true
|
||||
|
||||
test :parse do
|
||||
assert Float.parse("12") === {12.0, ""}
|
||||
assert Float.parse("-12") === {-12.0, ""}
|
||||
assert Float.parse("123456789") === {123456789.0, ""}
|
||||
assert Float.parse("12.5") === {12.5, ""}
|
||||
assert Float.parse("12.524235") === {12.524235, ""}
|
||||
assert Float.parse("-12.5") === {-12.5, ""}
|
||||
assert Float.parse("-12.524235") === {-12.524235, ""}
|
||||
assert Float.parse("7.5e3") === {7.5e3, ""}
|
||||
assert Float.parse("7.5e-3") === {7.5e-3, ""}
|
||||
assert Float.parse("12x") === {12.0, "x"}
|
||||
assert Float.parse("12.5x") === {12.5, "x"}
|
||||
assert Float.parse("-12.32453e10") === {-1.232453e11, ""}
|
||||
assert Float.parse("-12.32453e-10") === {-1.232453e-9, ""}
|
||||
assert Float.parse("0.32453e-10") === {3.2453e-11, ""}
|
||||
assert Float.parse("1.32453e-10") === {1.32453e-10, ""}
|
||||
assert Float.parse("1.32.45") === {1.32, ".45"}
|
||||
assert Float.parse("1.o") === {1.0, ".o"}
|
||||
assert Float.parse("--1.2") === :error
|
||||
assert Float.parse("++1.2") === :error
|
||||
assert Float.parse("pi") === :error
|
||||
end
|
||||
end
|
||||
@@ -85,31 +85,31 @@ defmodule HashDictTest do
|
||||
test :update do
|
||||
dict = filled_dict(8)
|
||||
|
||||
dict = HashDict.update!(dict, 1, &1 * 2)
|
||||
dict = HashDict.update!(dict, 1, &(&1 * 2))
|
||||
assert HashDict.get(dict, 1) == 2
|
||||
assert HashDict.size(dict) == 8
|
||||
|
||||
assert_raise KeyError, fn ->
|
||||
HashDict.update!(dict, 11, &1 * 2)
|
||||
HashDict.update!(dict, 11, &(&1 * 2))
|
||||
end
|
||||
|
||||
dict = HashDict.update(dict, 3, 3, &1 * 2)
|
||||
dict = HashDict.update(dict, 3, 3, &(&1 * 2))
|
||||
assert HashDict.get(dict, 3) == 6
|
||||
assert HashDict.size(dict) == 8
|
||||
|
||||
dict = HashDict.update(dict, 11, 13, &1 * 2)
|
||||
dict = HashDict.update(dict, 11, 13, &(&1 * 2))
|
||||
assert HashDict.get(dict, 11) == 13
|
||||
assert HashDict.size(dict) == 9
|
||||
|
||||
assert_raise KeyError, fn->
|
||||
HashDict.update!(dict, 11.0, &1 * 2)
|
||||
HashDict.update!(dict, 11.0, &(&1 * 2))
|
||||
end
|
||||
|
||||
dict = HashDict.update(dict, 11.0, 15, &1 * 2)
|
||||
dict = HashDict.update(dict, 11.0, 15, &(&1 * 2))
|
||||
assert HashDict.get(dict, 11.0) == 15
|
||||
assert HashDict.size(dict) == 10
|
||||
|
||||
dict = HashDict.update(dict, 11.0, 15, &1 * 2)
|
||||
dict = HashDict.update(dict, 11.0, 15, &(&1 * 2))
|
||||
assert HashDict.get(dict, 11.0) == 30
|
||||
assert HashDict.get(dict, 11) == 13
|
||||
assert HashDict.size(dict) == 10
|
||||
@@ -302,6 +302,6 @@ defmodule HashDictTest do
|
||||
end
|
||||
|
||||
defp filled_dict(range) do
|
||||
Enum.reduce 1..range, HashDict.new, HashDict.put(&2, &1, &1)
|
||||
Enum.reduce 1..range, HashDict.new, &HashDict.put(&2, &1, &1)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -40,10 +40,8 @@ defmodule HashSetTest do
|
||||
diff = HashSet.difference(filled_set(9000), filled_set(9000))
|
||||
assert HashSet.equal?(diff, HashSet.new([]))
|
||||
|
||||
#Ensure the trie has correctly resized
|
||||
assert HashSet.inspect_depth(diff) == 0
|
||||
assert HashSet.inspect_contract(diff) == 2
|
||||
assert HashSet.inspect_expand(diff) == 80
|
||||
# Ensure the trie has correctly resized
|
||||
assert { HashSet, 0, 0, 80, 2, _ } = diff
|
||||
end
|
||||
|
||||
test "member? with ordered sets" do
|
||||
@@ -110,7 +108,7 @@ defmodule HashSetTest do
|
||||
end
|
||||
|
||||
defp filled_set(range) do
|
||||
Enum.reduce 1..range, HashSet.new, HashSet.put(&2, &1)
|
||||
Enum.reduce 1..range, HashSet.new, &HashSet.put(&2, &1)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -65,8 +65,6 @@ defmodule Inspect.BitStringTest do
|
||||
assert inspect("f\no") == "\"f\\no\""
|
||||
assert inspect("f\\o") == "\"f\\\\o\""
|
||||
assert inspect("f\ao") == "\"f\\ao\""
|
||||
assert inspect(%S{#{#a}) == "\"\\\#{#a\""
|
||||
assert inspect(%S{ #{#a}) == "\" \\\#{#a\""
|
||||
end
|
||||
|
||||
test :utf8 do
|
||||
|
||||
@@ -20,4 +20,17 @@ defmodule IntegerTest do
|
||||
assert Integer.even?(-2) == true
|
||||
assert Integer.even?(-3) == false
|
||||
end
|
||||
|
||||
test :parse do
|
||||
assert Integer.parse("12") === {12, ""}
|
||||
assert Integer.parse("-12") === {-12, ""}
|
||||
assert Integer.parse("123456789") === {123456789, ""}
|
||||
assert Integer.parse("12.5") === {12, ".5"}
|
||||
assert Integer.parse("7.5e-3") === {7, ".5e-3"}
|
||||
assert Integer.parse("12x") === {12, "x"}
|
||||
assert Integer.parse("++1") === :error
|
||||
assert Integer.parse("--1") === :error
|
||||
assert Integer.parse("+-1") === :error
|
||||
assert Integer.parse("three") === :error
|
||||
end
|
||||
end
|
||||
|
||||
@@ -2,6 +2,7 @@ Code.require_file "test_helper.exs", __DIR__
|
||||
|
||||
defmodule IOTest do
|
||||
use ExUnit.Case, async: true
|
||||
import ExUnit.CaptureIO
|
||||
|
||||
test :read_with_count do
|
||||
{ :ok, file } = File.open(Path.expand('../fixtures/file.txt', __FILE__), [:charlist])
|
||||
@@ -77,4 +78,32 @@ defmodule IOTest do
|
||||
assert "日\n" == IO.binread(file, :line)
|
||||
assert File.close(file) == :ok
|
||||
end
|
||||
|
||||
test :puts_with_chardata do
|
||||
assert capture_io(fn -> IO.puts("hello") end) == "hello\n"
|
||||
assert capture_io(fn -> IO.puts('hello') end) == "hello\n"
|
||||
assert capture_io(fn -> IO.puts(:hello) end) == "hello\n"
|
||||
assert capture_io(fn -> IO.puts(13) end) == "13\n"
|
||||
end
|
||||
|
||||
test :write_with_chardata do
|
||||
assert capture_io(fn -> IO.write("hello") end) == "hello"
|
||||
assert capture_io(fn -> IO.write('hello') end) == "hello"
|
||||
assert capture_io(fn -> IO.write(:hello) end) == "hello"
|
||||
assert capture_io(fn -> IO.write(13) end) == "13"
|
||||
end
|
||||
|
||||
test :gets_with_chardata do
|
||||
assert capture_io("foo\n", fn -> IO.gets("hello") end) == "hello"
|
||||
assert capture_io("foo\n", fn -> IO.gets('hello') end) == "hello"
|
||||
assert capture_io("foo\n", fn -> IO.gets(:hello) end) == "hello"
|
||||
assert capture_io("foo\n", fn -> IO.gets(13) end) == "13"
|
||||
end
|
||||
|
||||
test :getn_with_chardata do
|
||||
assert capture_io("foo\n", fn -> IO.getn("hello", 3) end) == "hello"
|
||||
assert capture_io("foo\n", fn -> IO.getn('hello', 3) end) == "hello"
|
||||
assert capture_io("foo\n", fn -> IO.getn(:hello, 3) end) == "hello"
|
||||
assert capture_io("foo\n", fn -> IO.getn(13, 3) end) == "13"
|
||||
end
|
||||
end
|
||||
|
||||
@@ -29,9 +29,9 @@ defmodule Kernel.AliasTest do
|
||||
|
||||
test :lexical do
|
||||
if true do
|
||||
alias OMG, as: List
|
||||
alias OMG, as: List, warn: false
|
||||
else
|
||||
alias ABC, as: List
|
||||
alias ABC, as: List, warn: false
|
||||
end
|
||||
|
||||
assert List.flatten([1, [2], 3]) == [1, 2, 3]
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user