Compare commits

...
605 Commits
Author SHA1 Message Date
José Valim a3d88dc406 Release v1.2.1 2016-01-14 19:25:11 +01:00
Daniel Azuma 58a6291f17 Fix a crash in Macro.to_string if a tree looks like a sigil but the function is not an atom
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2016-01-13 11:19:58 +01:00
Aleksei Magusev 81884247d7 Warn when defimp is called for consolidated protocol
Conflicts:
	lib/elixir/lib/protocol.ex
2016-01-12 10:50:29 +01:00
Adrien Moreau c07a34f9a8 Correct the type definition of ExUnit.state for the failed state
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2016-01-12 09:20:45 +01:00
José Valim e05bfc78cb Improve docs and error handling for Access
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2016-01-10 22:52:08 +01:00
Aleksei Magusev cb430702c9 Preserve variable metadata during collection from assertion pattern
Closes #4174.
2016-01-09 20:47:46 +01:00
Aleksei Magusev 6aaa4bb06d Do not warn in match assertion if variable is reused in pattern
Having the folowing assertion `assert {var, var} = {1, 1}`,
the code produced previously:

  [var] = case(right) do
    {var, var} ->
      #...
      [var]
    _ ->
      #...
  end

And with the patch:

  [var, var] = case(right) do
    {var, var} ->
      #...
      [var, var]
    _ ->
      #...
  end
2016-01-06 23:56:44 +01:00
Thomas Fisher a6b80a19c5 Fixes bug in IEx.Config.configuration
IEx.Config.configuration was raising a FunctionClauseError since
it did not have a default value for width

Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2016-01-06 18:58:42 +01:00
Aleksei Magusev 8be0ad0499 Correct Macro.to_string/1 formatting for capture operator
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2016-01-06 18:58:07 +01:00
José Valim 8da4936ac3 Ensure dependencies are properly skipped when running in another environment
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2016-01-06 18:57:54 +01:00
James Fish bcc92ccc40 Support remote pids/ports with IEx helper i/1
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2016-01-02 18:54:07 +01:00
José Valim d64d4b009c v1.2 branch 2016-01-01 11:51:36 +01:00
José Valim b1344724b6 Release v1.2.0 2016-01-01 11:40:35 +01:00
José Valim 4a7e541130 Fix race condition in task_test.exs, closes #4130 2015-12-31 08:03:17 +01:00
José Valim 403c85cebe Merge pull request #4129 from alco/alco/iex-info-improvements
Pedantic improvements to the output of the i/1 helper in IEx.
2015-12-30 13:55:20 +01:00
Alexei Sholik f81489a19e Pedantic improvements to the output of the i/1 helper in IEx. 2015-12-30 14:53:04 +02:00
Aleksei Magusev 65ec108399 Use private context for generated variables inside with 2015-12-29 13:57:49 +01:00
Aleksei Magusev 0f6c9d1594 Make sure variables are not leaking from with
Closes #4123.
2015-12-29 13:51:48 +01:00
José Valim 0751f4b873 Remove unused noname field 2015-12-29 13:42:25 +01:00
José Valim 2cee5950e7 Merge pull request #4121 from jisaacstone/master
documenting the default return value for `Module.get_attribute/2`
2015-12-29 08:59:25 +01:00
Aleksei Magusev 08a19d0768 Keep GitRepo.Mixfile loaded between runs 2015-12-29 02:47:17 +01:00
Aleksei Magusev d5e6296a72 Add missing purging of GitRepo.Mixfile and extra renaming 2015-12-29 02:34:25 +01:00
Aleksei Magusev c5cb550bfd Make Mix projects definitions consistent across tests 2015-12-29 02:12:21 +01:00
jisaacstone b7813259c2 documenting the default return value for Module.get_attribute/2
refactor the docs so the first line is short

Went ahead and did the whole module.
2015-12-28 15:35:09 -08:00
Aleksei Magusev e0fd6dee99 Add missing purging of GitRepo module 2015-12-28 01:58:52 +01:00
José Valim 7354974ef2 Add a note on how to reference a callback 2015-12-26 23:18:51 +01:00
Aleksei Magusev e1d5263560 Use :binary instead of String in Macro.to_string for bitstrings
No need for grapheme handling and `str` is never of zero size.
2015-12-26 14:31:19 +01:00
José Valim 4855d08c44 Merge pull request #4118 from tuvistavie/fix-macro-to_string
Fix bug in Macro.to_string. Fix #4116.
2015-12-26 11:31:46 +01:00
Daniel Perez 1ae3e344f8 Fix bug in Macro.to_string. Fix #4116. 2015-12-26 11:07:50 +01:00
José Valim 94c9f39f3d Merge pull request #4117 from tony612/fix-doc-indent-in-module
fix the indent of moduledoc in Module
2015-12-26 10:06:21 +01:00
Tony Han 4e190e5358 fix the indent of moduledoc in Module 2015-12-26 16:46:07 +08:00
José Valim 14610cb09b Merge pull request #4115 from alco/alco/__info__-spec
Update the spec for the generated __info__/1 function to please Dialyzer
2015-12-25 21:56:26 +01:00
Alexei Sholik 1703066f60 Update the spec for the generated __info__/1 function to please Dialyzer
Verify the spec's correctness with `mix dialyze --specdiffs` in a dummy
mix project.
2015-12-25 22:39:37 +02:00
José Valim 5968df9649 Mention IEX_WITH_WERL in docs 2015-12-23 23:16:40 +01:00
José Valim a05a1a4692 Merge pull request #4108 from tuvistavie/add-mix-debug
Add mix debug mode. Close #4106.
2015-12-23 23:16:10 +01:00
José Valim 278374202b Merge pull request #4102 from orderthruchaos/master
Add environment variable to iex.bat so user can default to --werl
2015-12-23 23:12:26 +01:00
Daniel Perez 4f2f92c8f1 Add documenation about env variables to Mix module. 2015-12-24 06:55:50 +09:00
Daniel Perez 08f1c2b629 Add mix debug mode. Close #4106. 2015-12-24 06:55:49 +09:00
Brett N. DiFrischia 8310c9b9ac Use IEX_WITH_WERL environment variable to enable --werl by default. 2015-12-23 15:30:40 -06:00
José Valim f7a1d273bc Amend writing docs 2015-12-23 16:45:48 +01:00
José Valim f51b1116e9 Merge pull request #4109 from bordeltabernacle/add/func_args
Add section about function arguments to 'Writing Documentation'
2015-12-23 16:43:53 +01:00
Rob Phoenix ac3bbad648 Add section about function arguments to 'Writing Documentation' 2015-12-23 14:19:14 +00:00
José Valim 88ffb468b5 Fix CHANGELOG 2015-12-23 10:46:22 +01:00
José Valim 62a36ed8db Improve docs for each compiler, closes #4099 2015-12-23 10:37:20 +01:00
José Valim be0e9837e2 Document --werl for autocomplete on windows, closes #4103 2015-12-23 10:34:58 +01:00
José Valim 6e528cd7c9 Fetch umbrella child deps across environments without conflicts
Closes #4069 #4105
2015-12-23 10:33:12 +01:00
José Valim c4d5ee5e68 Revert "Load all umbrella children from the fetcher, closes #4069"
This reverts commit 57af576192.
2015-12-23 10:33:12 +01:00
José Valim e4685dc81c Merge pull request #4104 from KronicDeth/enum-sort-by-docs
Fix link to Kernel.<=/2 in Enum.sort_by/3 @doc
2015-12-23 10:23:12 +01:00
Luke Imhoff e471e063f8 Fix link to Kernel.<=/2 in Enum.sort_by/3 @doc
When I first wrote the docs, I didn't know I had to include the
`Kernel.` to make the automatic linking work.  I double checked after
reading the documentation guide for Elixir 1.2 and noticed that the
`<=/2` wasn't linking.
2015-12-22 20:38:37 -06:00
Brett N. DiFrischia 1330de1eda Add environment variable to iex.bat so user can default to --werl if desired. 2015-12-22 11:16:40 -06:00
Aleksei Magusev 076c8dccfd Merge pull request #4100 from rrrene/patch-3
Fix small typo in Writing Documentation.md
2015-12-22 13:05:58 +01:00
René Föhring 359a17a761 Fix small typo in Writing Documentation.md
[ci skip]
2015-12-22 12:51:43 +01:00
Eric Meadows-Jönsson 924222188a Merge pull request #4098 from sup/patch-1
update Kernel.SpecialForms.import summary
2015-12-22 00:58:02 +01:00
jona 747d47c792 update Kernel.SpecialForms.import summary 2015-12-21 20:50:59 +01:00
José Valim 211a1299cc Release v1.2.0-rc.1 2015-12-21 19:49:25 +01:00
José Valim 74d73acaae Improve error message on invalid map definition, closes #4068 2015-12-21 19:01:00 +01:00
José Valim 4c67b83ceb Check for :name or :id in Logger translator, closes #4073 2015-12-21 18:38:57 +01:00
José Valim 57af576192 Load all umbrella children from the fetcher, closes #4069 2015-12-21 18:29:19 +01:00
Eric Meadows-Jönsson 2cf5bbe0fe Merge pull request #4097 from scrogson/typo_fix
read vs. erad
2015-12-21 16:56:12 +01:00
Sonny Scroggin 913fa65faa read vs. erad 2015-12-21 09:36:04 -06:00
José Valim 5d5d7dadb4 is -> may be 2015-12-21 16:30:59 +01:00
José Valim f1d4d5b034 Add pages about typespecs and docs, closes #4079 2015-12-21 16:05:25 +01:00
José Valim e6a31aff0b Merge pull request #4094 from tuvistavie/update_deps_clean_doc
Add deps.clean --build documentation.
2015-12-21 08:33:22 +01:00
Daniel Perez e87fc8ff67 Add deps.clean --build documentation. 2015-12-21 12:41:54 +09:00
José Valim 0ee77de4f2 Merge pull request #4093 from jisaacstone/master
added return values to the Logger macro's documentation
2015-12-20 20:11:42 +01:00
jisaacstone 5613c2edd2 added return values to the Logger macro's documentation 2015-12-20 10:46:29 -08:00
James Fish 6cba4f33a9 Merge pull request #4091 from jisaacstone/I4081
Fix issue #4081 UnicodeConversionError would generate invalid strings
2015-12-19 17:59:27 +00:00
jisaacstone e9aa70a0c2 Fix issue #4081 UnicodeConversionError would generate invalid strings
previously assumed the head of the returned iolist would be an integer. This has been fixed with more recursion
fishcakez was right
2015-12-19 08:20:19 -08:00
Aleksei Magusev 75378611c6 Use question mark at the end of boolean variable 2015-12-19 16:13:16 +01:00
José Valim e6a8bbdd84 Merge pull request #4089 from tuvistavie/mix_deps_clean_build_flag
Add --build flag to deps.clean mix task. Close #4088.
2015-12-19 13:29:52 +01:00
Daniel Perez 91d6856b60 Add --build flag to deps.clean mix task. Close #4088. 2015-12-19 21:22:25 +09:00
José Valim 80e6942ca9 Clarify the use of --deps option 2015-12-19 13:06:36 +01:00
José Valim 25485ac20d Bring back umbrella specific configuration 2015-12-18 17:46:59 +01:00
José Valim 519ccc8290 Merge pull request #4084 from asymmetric/quote-doc
Fix example in `quote`
2015-12-18 17:38:59 +01:00
Lorenzo Manacorda 778f9b6295 Fix example in quote
The example prints "Got 25", not just "25"
2015-12-18 15:46:08 +01:00
José Valim 737bf1b369 Emit proper error if parsetools is not available 2015-12-17 09:14:54 +01:00
José Valim 12e052ee55 Revert "Warn if no status after eof"
This reverts commit fca45d8713.
2015-12-15 18:22:28 +01:00
José Valim ee56234edf Ensure halt on system cmd collectable 2015-12-15 18:03:11 +01:00
José Valim fca45d8713 Warn if no status after eof 2015-12-15 17:50:16 +01:00
Aleksei Magusev a4ffd55431 Merge pull request #4076 from lexmag/access-error
Make Access syntax fail to compile for unsupported values
2015-12-15 01:03:02 +01:00
Aleksei Magusev 16eac65502 The Access is not a protocol 2015-12-15 00:56:53 +01:00
Aleksei Magusev 7113ccf0bc Provide better error message for keywords access with non-atom keys 2015-12-15 00:56:49 +01:00
Aleksei Magusev 6660e7e409 Make Access syntax fail to compile for unsupported values 2015-12-15 00:55:27 +01:00
José Valim 860db84865 Do not run converger if dependencies have diverged
The remote converger should not worry about the dep
status code in case they are invalid. We have also
changed mix deps.get/deps.update to raise at the end
in case any of the dependencies diverged.

Closes https://github.com/hexpm/hex/issues/169
2015-12-15 00:34:29 +01:00
José Valim 77e142d124 Merge pull request #4077 from f-lombardo/win-mix-bat
mix.bat changed to emulate mix script on Unix-like systems. Solves #4075
2015-12-14 22:38:58 +01:00
Franco Lombardo 6f80bf1bff mix.bat changed to emulate mix script on Unix-like systems. This solves #4075
Using mix file as input to elixir. Note that #!/usr/bin/env elixir will be
intepreted as an Elixir comment
2015-12-14 21:49:53 +01:00
Aleksei Magusev 5e15f2eb88 Merge pull request #4072 from lexmag/non-keywords-access
Raise when Access syntax used on non-keywords
2015-12-12 14:34:25 +01:00
Aleksei Magusev f4403742eb Raise when Access syntax used on non-keywords 2015-12-12 14:25:09 +01:00
Aleksei Magusev 66964cbd9b Bring Access.fetch/2 tests back 2015-12-12 02:16:41 +01:00
José Valim d9356c3dc5 More consolidation effects on test clearer 2015-12-10 17:41:43 +01:00
José Valim 12e3836448 Update CHANGELOG 2015-12-10 11:37:32 +01:00
Eric Meadows-Jönsson c9d4eedbbc Update CHANGELOG.md 2015-12-09 20:03:10 +01:00
José Valim a2860b32a3 Release v1.2.0-rc.0 2015-12-09 16:27:55 +01:00
José Valim 7a1758e6c0 Avoid generating accumulator for nested comprehensions
Closes #4013
2015-12-09 16:01:37 +01:00
José Valim afe22a2920 Use nil for resetting env vars instead of false #4067 2015-12-09 11:38:07 +01:00
José Valim adaceb36eb Merge pull request #4067 from vanstee/allow-false-value-in-env
Allow `false` value in env passed to `System.cmd/3`
2015-12-09 02:15:15 +01:00
vanstee 966c8a27ae Allow false value in env passed to System.cmd/3
Previously only strings were accepted as valid env keys and values.
However, because the `env` option is eventually passed to
`:erlang.open_port`, `false` values, which remove existing environment
variables, should be allowed.

From the docs for [`erlang:open_port/2`](http://www.erlang.org/doc/man/erlang.html#open_port-2):

    {env, Env}
      This is only valid for {spawn, Command} and {spawn_executable,
      FileName}. The environment of the started process is extended using the
      environment specifications in Env.

      Env should be a list of tuples {Name, Val}, where Name is the name of an
      environment variable, and Val is the value it is to have in the spawned
      port process. Both Name and Val must be strings. The one exception is
      Val being the atom false (in analogy with os:getenv/1), which removes
      the environment variable.
2015-12-08 18:43:07 -05:00
Eric Meadows-Jönsson 037340c2c8 Improve rebar config script error message 2015-12-08 23:15:41 +01:00
Eric Meadows-Jönsson 62ba9a9e4c Converge dependency managers 2015-12-08 23:15:22 +01:00
Eric Meadows-Jönsson 2266d0ec3f Update rebar3 config merge 2015-12-08 21:09:28 +01:00
José Valim 3e13166ee0 Do not disable convergency checks for optional deps 2015-12-08 21:02:58 +01:00
José Valim 5449e7f5aa Merge pull request #4064 from henrik/patch-8
String docs: link codepoints/graphemes to details
2015-12-08 20:20:02 +01:00
Henrik Nyh 932d303733 Example of String.codepoints/.graphemes difference
So it's even clearer, when you compare these two functions.
2015-12-08 19:08:39 +01:00
Henrik Nyh 70cfb853d4 String docs: link codepoints/graphemes to details
Since the distinction between these two functions isn't very clear otherwise.
2015-12-08 18:43:39 +01:00
Aleksei Magusev 677e9ae225 Don't use deprecated Dict reference 2015-12-08 15:56:08 +01:00
Aleksei Magusev 3ee44d4a5c Merge pull request #4066 from tuvistavie/fix-macro-to_string
Fix bug in Macro.to_string. Fix #4065.
2015-12-08 15:52:32 +01:00
Daniel Perez 9737481dc2 Fix bug in Macro.to_string. Fix #4065. 2015-12-08 23:48:38 +09:00
José Valim 29340f4d06 Ensure we treat deps as a git dependencies 2015-12-08 13:40:24 +01:00
José Valim 3520bb24e5 Make non semver versions a dependency status instead of raising
This allows the check_lock information to override the error
in case the dependency is out of date.

Closes #4026
2015-12-08 13:26:05 +01:00
José Valim 45619f1a6c Remove io ansi macro 2015-12-08 12:27:48 +01:00
José Valim de0e9bf8e0 Merge pull request #4052 from Calamitous/ansi-cursor-sequences
Added support for RGB colors in IO.ANSI
2015-12-08 11:57:52 +01:00
Eric Budd 0131d0281c Add helper methods and macro to IO.ANSI for rgb216 color
- valid_rgb216/3
- color/3
- color_background/3
2015-12-07 20:29:43 -05:00
José Valim b39f2f69f7 Merge pull request #4061 from henrik/patch-5
Clarify codepoint/grapheme in String docs
2015-12-07 23:40:35 +01:00
Henrik Nyh 6a5a9f8d47 Clarify codepoint vs. grapheme in String docs 2015-12-07 22:39:12 +01:00
José Valim fb9d3c3106 Merge pull request #4063 from henrik/patch-7
String.split docs: codepoints -> graphemes
2015-12-07 22:22:22 +01:00
José Valim b58f7db73a Merge pull request #4062 from henrik/patch-6
String.slice docs: codepoints -> graphemes
2015-12-07 22:21:11 +01:00
Henrik Nyh e1bd1cca65 String.split docs: codepoints -> graphemes 2015-12-07 22:19:46 +01:00
Henrik Nyh c932a7fac2 String.slice docs: codepoints -> graphemes 2015-12-07 22:00:28 +01:00
Aleksei Magusev 857ea3ca19 Merge pull request #4049 from lexmag/conflicting-import
Make import with conflicting options to raise
2015-12-07 21:16:12 +01:00
Aleksei Magusev 9ac3c2e00d Make import with conflicting options to raise 2015-12-07 21:01:28 +01:00
José Valim 71c42e8ebb Merge pull request #4058 from whatyouhide/mix-generator-docs
Add specs and enhance docs for Mix.Generator
2015-12-07 16:41:55 +01:00
Andrea Leopardi a8e66fd3ae Add specs and enhance docs for Mix.Generator 2015-12-07 15:06:52 +01:00
José Valim a1b3dc8b0e Merge pull request #4057 from alco/master
Clarify the new default behaviour for protocol consolidation
2015-12-07 14:51:32 +01:00
Alexei Sholik 20333ebbc8 Clarify the new default behaviour for protocol consolidation 2015-12-07 15:32:35 +02:00
José Valim fa991c1940 Merge pull request #4056 from whatyouhide/mix-project-specs
Specs for functions in Mix.Project and a couple of docs polishings
2015-12-07 14:03:20 +01:00
Andrea Leopardi bf019b8a84 Update a test for Mix.Project.in_project/3-4 2015-12-07 12:20:20 +01:00
Andrea Leopardi 450b8b37de Enhance the docs for Mix.Project.in_project/3-4 2015-12-07 12:20:18 +01:00
Andrea Leopardi a303f38f71 Add @specs to the functions in Mix.Project 2015-12-07 12:05:37 +01:00
José Valim 1e40dae6e5 Do not run remove converger multiple times 2015-12-07 11:57:28 +01:00
José Valim 69ba432b9c Clean up expand dot implementation 2015-12-07 10:17:42 +01:00
José Valim 0f608c6a48 Merge pull request #4053 from tuvistavie/fix-path-expand
Fix edge case in Path.expand. Fix #4050.
2015-12-07 10:09:52 +01:00
José Valim d094160ae9 Merge pull request #4054 from backspace/fix-arguement-typo
Fix typo: ArguementError -> ArgumentError
2015-12-06 19:15:56 +01:00
Buck Doyle ab7566afb6 Fix typo: ArguementError -> ArgumentError 2015-12-06 13:02:45 -05:00
Daniel Perez f23e0856b1 Fix edge case in Path.expand. Fix #4050. 2015-12-06 15:29:40 +09:00
Aleksei Magusev ecd86761da Cover bittype_mismatch error missing in :erl_lint.format_error/1
For example, `<<0::little-big>>` expression.
2015-12-05 12:53:18 +01:00
José Valim b18e41020b Merge pull request #4047 from antipax/patch-1
Map.take/2, Map.drop/2, Map.split/2 missing spec
2015-12-05 10:43:13 +01:00
Eric Entin 6ed496a119 Add spec to Map.drop/2 and Map.split/2 2015-12-04 23:32:13 -05:00
Eric Entin 7422c1a750 Map.take/2 missing spec 2015-12-04 22:55:53 -05:00
Eric Meadows-Jönsson 01b4f2fdab Remove improper checking from update and get
It was incorrect because we were doing the check before loading deps
and writing lock. It needs to happen after.

Related #3795.
2015-12-04 16:23:34 +01:00
Eric Meadows-Jönsson df15733fc0 Fix invalid UTF-8 in Base.decode error message 2015-12-04 13:07:18 +01:00
José Valim 7a9a69f08b Merge pull request #4046 from lexmag/import-with-invalid-options
Properly handle import with invalid options
2015-12-04 08:58:44 +01:00
Aleksei Magusev ddf0262a3d Correct "unrequired macro" assertion 2015-12-04 02:46:33 +01:00
Aleksei Magusev aa297109bf Properly handle import with invalid options 2015-12-04 02:45:59 +01:00
José Valim aece570083 Merge pull request #4045 from joeyates/feature/correct-bin-and-home
Documentation fixes
2015-12-03 23:42:55 +01:00
Joe Yates c78755a0fb Documentation fixes 2015-12-03 23:28:20 +01:00
José Valim c37ea4e874 Ignore only requirements from optional dependencies
Closes #4025
2015-12-03 18:30:23 +01:00
José Valim 609e042d61 Merge pull request #4040 from GeorgeTaveras1231/feature/Enum.with_index/2
Implement Enum.with_index/2
2015-12-03 16:06:41 +01:00
George Taveras 3491434bea Implement Stream.with_index/2
This removes the previous implementation `Stream.with_index/1` but
maintains backwards compatibility by providing a default
offset of 0
2015-12-03 09:59:12 -05:00
George Taveras 2d582087c6 Implement Enum.with_index/2
This removes the previous implementation `Enum.with_index/1` but
maintains backwards compatibility by providing a default offset of 0

Proposal at #4039
2015-12-03 09:40:30 -05:00
José Valim 5a755948fe Update CHANGELOG 2015-12-03 13:35:58 +01:00
José Valim 2c5c53834f Make Rebar 3 support opt-in 2015-12-03 13:21:22 +01:00
José Valim 253ede29da Merge pull request #3518 from elixir-lang/emj-rebar3
Add rebar3 manager
2015-12-03 11:48:07 +01:00
José Valim 3715521c10 Use proper grapheme on String.reverse example 2015-12-03 10:43:16 +01:00
José Valim 4a80285540 Document that reverse x reverse != identity
Closes #4038
2015-12-03 10:10:43 +01:00
José Valim d6f0e64164 Merge pull request #4036 from joeyates/feature/optionparser-raises-with-both-strict-and-switches
Raise error for incompatible OptionParser options
2015-12-03 09:28:50 +01:00
Joe Yates 9b9b324241 Raise error for incompatible OptionParser options
* supplying :switch together with :strict raises an error.
* closes #4035.
2015-12-03 00:32:45 +01:00
José Valim ae12b80e71 Merge pull request #4034 from joeyates/feature/reword-optionparser-moduledoc
Reword OptionParser moduledoc
2015-12-02 23:36:34 +01:00
Joe Yates 2891f5734c Reword OptionParser moduledoc
* make the return value clearer by presenting it as a list,
* introduce `:switches` before `:strict`,
* remove the documented distinction between `:undefined` and `:invalid`
  as it seems to be an implementation detail,
* indicate how to use `:keep` with non-string types.
2015-12-02 23:30:00 +01:00
Eric Meadows-Jönsson 5c1ae1cd1c Rebar3 dependency support 2015-12-02 22:51:43 +01:00
Eric Meadows-Jönsson a60d79a0e3 Merge pull request #4032 from uDude/master
fix grammar:  does -> do.
2015-12-01 23:18:27 +01:00
Michael Kyle e10e000eed fix grammar: does -> do. 2015-12-01 14:53:29 -07:00
José Valim 6d87f137e5 Merge pull request #4030 from whatyouhide/system-time-offset
Add System.time_offset/0-1
2015-12-01 21:42:44 +01:00
José Valim 5d76724a1f Merge pull request #4029 from OnorioCatenacci/win-support
Adding Code For Diagnostics To Windows Batch Files
2015-12-01 21:18:54 +01:00
Eric Meadows-Jönsson ef9c88f8a0 Merge pull request #4022 from milmazz/issue_4020
Add --mirror option to mix local.{rebar,hex} tasks
2015-12-01 20:59:16 +01:00
Milton Mazzarri 14282b03df Add support for HEX_CDN var in mix local.{hex,rebar} tasks 2015-12-01 12:47:39 -06:00
Andrea Leopardi f62c76497d Add System.time_offset/0-1 2015-12-01 18:52:15 +01:00
Onorio Catenacci 5ffb2c570d Adding code to the Windows batch files to make it easier to diagnose issues on Windows with Elixir 2015-12-01 12:24:54 -05:00
José Valim d314230faa Merge pull request #4024 from whatyouhide/time-functions-in-system
Add monotonic_time, system_time and unique_integer to System
2015-12-01 16:19:08 +01:00
Andrea Leopardi 75b78ba5c6 Add System.convert_time_unit/3 2015-12-01 14:54:22 +01:00
Andrea Leopardi 2093e823cd Add monotonic|system_time and unique_integer to System 2015-12-01 14:54:15 +01:00
José Valim 944990381f Merge pull request #4023 from joeyates/feature/moduledoc-fixes
Fix typos in Elixir moduledocs
2015-12-01 14:16:56 +01:00
Joe Yates 05e426248c Fix typos in Elixir moduledocs 2015-12-01 13:45:33 +01:00
Aleksei Magusev 225620f71f Ensure Macro.pipe/3 in stacktrace on bad call 2015-11-30 23:53:45 +01:00
Aleksei Magusev 26dfd5ebb0 Do not bypass warnings_as_errors on Dict and defdelegate warnings 2015-11-30 22:21:40 +01:00
José Valim f35084b7f0 Merge pull request #4017 from joeyates/feature/file-stat-doc-fixes
Improve the docs for File.Stat
2015-11-30 18:58:05 +01:00
Joe Yates 5461693dc5 Improve the docs for File.Stat
`gid` - The entry was misleading as it incorrectly implied that the
`gid` indicates the user's group, whereas the `gid` can potentially be
for any group, even one that the user-owner doesn't belong to.
2015-11-30 18:53:38 +01:00
José Valim 06d8e4fec9 Improve guards in Regex module 2015-11-30 17:54:03 +01:00
José Valim d2e864d366 Merge pull request #4012 from jfis/master
quick changelog 1.2 edits
2015-11-29 20:18:43 +01:00
jason fistner 2cc62d8c38 quick changelog 1.2 edits 2015-11-29 12:52:41 -05:00
José Valim 7c1b6a657c Merge pull request #4011 from seomoz/improve-build-path-docs
Reword confusing `build_path` documentation.
2015-11-29 09:32:54 +01:00
Myron Marston 17f55ac5e1 Reword confusing build_path documentation.
See 7963398aaf for background.
2015-11-28 20:47:19 -08:00
Aleksei Magusev 805ee01b5f Don't print the result of the 'ls' and 'cd' IEx helpers 2015-11-29 00:30:34 +01:00
Aleksei Magusev 5a7c7d272b Merge pull request #4010 from lexmag/bit-syntax-to-string
Make Macro.to_string/1 consistent with Kernel.inspect/1 for bitstrings
2015-11-29 00:01:24 +01:00
Aleksei Magusev 40096e0ff1 Eliminate spaces in bitstring definitions 2015-11-28 23:48:48 +01:00
Aleksei Magusev 644c29aca8 Improve Macro.to_string/1 formatting for nested bitstrings 2015-11-28 23:47:35 +01:00
Aleksei Magusev d9a0a7f319 Make Macro.to_string/1 consistent with Kernel.inspect/1 for bitstrings 2015-11-28 23:45:38 +01:00
José Valim edb4e4b85b Merge pull request #4007 from jparise/pwd-suppress-result
Don't print the result of the 'pwd' helper.
2015-11-28 20:09:30 +01:00
José Valim 64f8c21ba6 Merge pull request #4008 from jparise/expand-erlang
Only expand Erlang modules when ':' is a prefix.
2015-11-28 20:09:14 +01:00
Jon Parise 346e0d1df7 Don't print the result of the 'pwd' helper.
This would previous print the 'pwd' string followed by the command's `:ok`
result. This change suppresses the `:ok:` in the same way as similar helpers.
2015-11-28 10:55:40 -08:00
Jon Parise 5836a70a78 Only expand Erlang modules when ':' is a prefix.
Previously, inputs like 'Enum:' would trigger Erlang module expansion.
2015-11-28 10:49:26 -08:00
José Valim 1d3781c00f Merge pull request #4006 from michalmuskala/iodocs
Explain IO devices maintain position
2015-11-28 19:41:22 +01:00
Michal Muskala 15b239748e Explain IO devices maintain position 2015-11-28 18:45:30 +01:00
José Valim 319939338b Merge pull request #4005 from thestonefox/chore/typo-in-changelog
Typo in CHANGELOG.md
2015-11-28 15:37:26 +01:00
Harvey Ball eef1fa3938 Typo in CHANGELOG.md 2015-11-28 14:44:06 +00:00
José Valim 01898c62e3 Make new replace implementations tail recursive 2015-11-28 14:27:50 +01:00
José Valim ef5ba3af05 Update CHANGELOG 2015-11-28 14:20:56 +01:00
José Valim 5ebe251f9c Rename String.trim_* into the more general purpose String.replace_* 2015-11-28 14:20:47 +01:00
José Valim aa6cfb772d Remove dependency between loadconfig tests 2015-11-28 13:15:49 +01:00
José Valim 7963398aaf Do not check for configured apps when build path is set
If the build path is set, we assume it is being shared
and therefore we cannot check for applications as we
may have both false positives and false negatives.

Therefore we let the application that owns the build path
to ultimately perform the check.
2015-11-28 13:06:21 +01:00
José Valim 00042b8578 Ensure config_path is respected 2015-11-28 11:52:37 +01:00
Eric Meadows-Jönsson 5d8bbbe8d9 Merge pull request #4003 from gustf/master
Typo in CHANGELOG.md
2015-11-27 23:50:38 +01:00
Gustaf Andersson 39c33b6105 Typo in CHANGELOG.md 2015-11-27 23:46:37 +01:00
José Valim 7acb551132 Update CHANGELOG 2015-11-27 22:24:40 +01:00
José Valim 65d4a26a94 Use kind and stacktrace from each error in multi error 2015-11-27 22:10:09 +01:00
José Valim 5ff37dc1b9 Support tag reporting 2015-11-27 22:04:29 +01:00
José Valim b9037980bb Only show not available deps after deps.get/update 2015-11-27 21:42:32 +01:00
José Valim 3ba93e780f Support raising and formatting of errors in ExUnit 2015-11-27 18:03:54 +01:00
Eric Meadows-Jönsson 715bdccf2c Merge pull request #4000 from tokafish/patch-2
Fixed typo in supervisor docs
2015-11-27 07:43:56 +01:00
Thomas Fisher dc741ea4fa Fixed typo in supervisor docs 2015-11-26 21:03:53 -05:00
Aleksei Magusev 26fe2fe71d Improve code style 2015-11-26 21:21:50 +01:00
José Valim 5e9a8a90cd Warn on non-variables for defdelegate, closes #3994 2015-11-26 14:40:41 +01:00
José Valim 5b6f257826 Include file and line in all doctest errors
This commit also cleans up the string_to_quoted implementation
by relying on a single location information used throughout
errors and translations.
2015-11-26 12:23:52 +01:00
José Valim 104d677f4a Merge pull request #3997 from milmazz/issue_3917
Print line number when indentation level mismatch in doctest
2015-11-26 12:01:31 +01:00
José Valim a48686c303 Check for link label without using regular expressions 2015-11-26 12:00:24 +01:00
José Valim d0d4361def Merge pull request #3996 from milmazz/issue_3960
One reference link label per line
2015-11-26 11:55:56 +01:00
José Valim 3605c3c178 Do not prune deps if build_path is set 2015-11-26 10:45:08 +01:00
José Valim c06fde60ce Do not show duplicate info on iex -v 2015-11-26 09:58:38 +01:00
Eric Meadows-Jönsson 1dd5c03998 Dependency requirement can be missing 2015-11-26 06:52:11 +01:00
Milton Mazzarri 9245522fd1 Print line number when indentation level mismatch 2015-11-25 23:03:03 -06:00
Milton Mazzarri c1b2a9048d One reference link label per line 2015-11-25 22:12:47 -06:00
Aleksei Magusev 30308917fc Improve Info helper test for Ports
It becomes platform independent.
2015-11-26 01:10:45 +01:00
Aleksei Magusev 1333bf31ad Add missing comments about inlining in Process 2015-11-26 01:10:45 +01:00
Aleksei Magusev f656633f0d Improve Info helper test for PIDs
It uses explicit exit for sample process.
2015-11-26 01:10:45 +01:00
Aleksei Magusev 99e0d8ea80 Use :lists.map instead of hand-written recursion 2015-11-26 01:10:45 +01:00
Eric Meadows-Jönsson 7231c45422 Merge pull request #3945 from lbarratt/feature/proxy-auth
Support for proxy auth with mix calls to httpc
2015-11-26 00:13:46 +01:00
Luke Barratt 5b903871a3 Support for proxy auth with mix calls to httpc 2015-11-25 23:04:29 +00:00
José Valim dda58028e8 Remove location keep as we rely on the user's using block 2015-11-25 20:01:35 +01:00
José Valim b4d5c17d0a Share build and config paths in umbrella apps 2015-11-25 19:49:39 +01:00
José Valim b6ac08ace0 Use a module name other than Foo to avoid conflicts 2015-11-25 14:25:44 +01:00
José Valim 73351fecfd Rename get_key to spec 2015-11-25 14:19:55 +01:00
Roman Smirnov 87396c6189 Implementation of get_key and get_all_key from :application in Elixir
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2015-11-25 14:06:43 +01:00
José Valim bc2c7ffd72 Improve docs for List 2015-11-25 13:47:19 +01:00
José Valim 445d5ee074 Show more process information 2015-11-25 13:40:17 +01:00
José Valim 8dba22d8af Merge pull request #3991 from whatyouhide/iex-info-tests
IEx.Info tests
2015-11-25 13:02:35 +01:00
Aleksei Magusev f8a82f604a Move doctests from central place to the modules they describe 2015-11-25 00:26:07 +01:00
Aleksei Magusev 499983e2c5 Suppress warning in set file 2015-11-24 23:04:14 +01:00
Andrea Leopardi b59de832fd Add a test for the i/1 IEx helper 2015-11-24 22:57:20 +01:00
Andrea Leopardi aee2d9800f Add tests for the IEx.Info protocol 2015-11-24 22:57:20 +01:00
Andrea Leopardi aa01677c9f Polish IEx.Info for BitStrings 2015-11-24 22:57:20 +01:00
José Valim 8703e9655e Unify undefined function messages 2015-11-24 22:07:16 +01:00
José Valim f7edee834f Merge pull request #3990 from whatyouhide/process-put-docs
Enhance the docs for Process.put/2
2015-11-24 21:10:22 +01:00
José Valim 9ddb3eda00 Merge pull request #3989 from maxim/keyword_2_elem_clarification
Specify that keyword list is made of 2-elem tuples
2015-11-24 21:07:26 +01:00
Andrea Leopardi c3cc02b8b4 Enhance the docs for Process.put/2 2015-11-24 21:06:04 +01:00
Maxim Chernyak 36ff2fa703 Specify that keyword list is made of 2-elem tuples 2015-11-24 15:02:08 -05:00
José Valim 75a77be9a0 Use the name of the app for :path dependencies, closes #3979 2015-11-24 13:55:05 +01:00
José Valim 1b89b6f69f Improve styling of how location is shown on pry 2015-11-24 13:07:22 +01:00
José Valim f7be7351bb Merge pull request #3972 from Troush/verbose-pry
Added verbose pry functionality
2015-11-24 12:55:49 +01:00
José Valim 940d8a5ec8 Expand sigil s to binaries when possible 2015-11-24 12:55:31 +01:00
Troush 0ba7dfcd31 Added verbose pry functionality
Showing place in code where IEx.pry was invoked
2015-11-24 10:38:55 +02:00
José Valim 2246d51a89 Merge pull request #3984 from rodrigues/master
Use List.foldr in Console metadata handling
2015-11-24 00:28:14 +01:00
José Valim 84f3a7320a Merge pull request #3981 from whatyouhide/default-args-in-defdelegate
Default args in defdelegate
2015-11-24 00:01:52 +01:00
Victor Rodrigues 966838f1c5 Use List.foldr in Console metadata handling 2015-11-23 23:54:58 +01:00
Andrea Leopardi a394a85aff Tests for raising with default args in defdelegate 2015-11-23 23:43:53 +01:00
Andrea Leopardi 68b4259d75 Raise when default args are used with defdelegate
Right now, using default args (`\\`) with `defdelegate` results in an
error that looks like this:

    ** (CompileError) iex:2: function '\\\\'/2 undefined
        (stdlib) lists.erl:1337: :lists.foreach/2
        (stdlib) erl_eval.erl:669: :erl_eval.do_apply/6
           (iex) lib/iex/evaluator.ex:117: IEx.Evaluator.handle_eval/5

The error is not very clear. I added some code that raises clearly when
default arguments are used with `defdelegate`.
2015-11-23 23:40:41 +01:00
José Valim 6a1ca846df Better check for functions, closes #3983 2015-11-23 23:35:14 +01:00
José Valim 9db6348add Update CHANGELOG 2015-11-23 22:48:16 +01:00
José Valim 3a559432d4 Add raw representation and Object -> Module in module info 2015-11-23 17:06:51 +01:00
José Valim 6bf20163b4 Remove outdated set_test.exs 2015-11-23 13:58:06 +01:00
José Valim 6eb746a10c Check for docs instead of :__info__ 2015-11-23 13:57:52 +01:00
José Valim 60c7d532b0 Improvements to IEx.Info 2015-11-23 13:48:32 +01:00
José Valim c7d65c3c6f Optimize functions in MapSet 2015-11-23 19:28:14 +08:00
José Valim 79907972e6 Document functions in MapSet 2015-11-23 19:16:44 +08:00
José Valim e6abf5ffb1 Merge pull request #3934 from whatyouhide/iex-helper-i
First incomplete version of the i/1 IEx helper
2015-11-23 19:16:35 +08:00
José Valim 778f639fec Merge pull request #3978 from elixirdrops/master
Task module documentation improvements
2015-11-23 18:46:19 +08:00
allyraza fdaefb0d03 improve docs for task module
fix a typo in task module

more typos in task module
2015-11-23 11:13:53 +02:00
Aleksei Magusev 517b0f1138 Merge pull request #3973 from martinsvalin/dont-escape-underlines-outside-url-in-docs
Don't escape underlines outside url in docs.
2015-11-23 00:29:24 +01:00
Andrea Leopardi 94149b1e24 Make IEx.Info fallback to any 2015-11-22 20:07:33 +01:00
Andrea Leopardi 6b4a65e7ff Polish the implementation of IEx.Info for modules 2015-11-22 20:07:33 +01:00
Andrea Leopardi d9ac304b97 Implement the IEx.Info protocol for all data types 2015-11-22 20:07:30 +01:00
José Valim 936b243517 Soft deprecate Dict, HashDict, Set and HashSet 2015-11-22 20:15:42 +08:00
José Valim e61921eebe Document and remove Map dependency from Dict 2015-11-22 20:08:25 +08:00
José Valim 0c2f3a4e5c Document and remove Keyword dependency from Dict 2015-11-22 19:18:25 +08:00
José Valim 44bcf394e4 Rename IEx.Evaluator functions
* Rename start to init because it does not effectively start a process
* Rename run_state to loop_state due thanks to the main loop function
2015-11-22 16:35:33 +08:00
José Valim d1cf766a00 Merge pull request #3977 from antipax/fix-3914-split-iex-server-evaluator-state
Fix #3914: split IEx server/evaluator state to prevent unnecessary copying
2015-11-22 16:32:12 +08:00
Eric Entin 7ab488479b Fix #3914: split IEx server/evaluator state to prevent unnecessary copying 2015-11-21 17:36:27 -05:00
José Valim 0ce5b62582 Do not skip variables from bitstrings when collecting them 2015-11-21 05:02:38 +08:00
José Valim 4fa892208e Properly change __ENV__.file when @file is set
Note that @file may be set from the :file meta as well.
2015-11-21 05:02:38 +08:00
Aleksei Magusev 18dd2b140a Make ParallelRequire aware of warning_as_errors option 2015-11-20 20:20:07 +01:00
José Valim 7b5f4a5842 Improve error message for invalid do/do: 2015-11-20 17:30:40 +08:00
José Valim f79056e168 Merge pull request #3976 from elixirdrops/master
fix tuple module docs
2015-11-20 17:27:54 +08:00
allyraza ec84afbb22 fix tuple module docs 2015-11-20 10:34:52 +02:00
Aleksei Magusev 91d5ba5725 Fix indentation in elixir_exp 2015-11-20 00:30:14 +01:00
Aleksei Magusev 8d6a20f054 No need for default_callback private macro in ParallelRequire 2015-11-20 00:30:14 +01:00
Eric Meadows-Jönsson 3fbf764e05 Merge pull request #3974 from elixirdrops/master
fix typos and spells in Version module
2015-11-19 14:49:24 +01:00
allyraza dac0f2fe27 fix typos and spells in Version module 2015-11-19 09:35:17 +02:00
Martin Svalin 424998828b Don't escape underlines outside url in docs. 2015-11-18 23:18:29 +01:00
Eric Meadows-Jönsson 8391b23b8e Merge pull request #3969 from elixirdrops/master
remove additional pipes from Stream docs
2015-11-17 12:10:56 +01:00
allyraza 024e8eb71a remove additional pipes from Stream docs 2015-11-17 13:07:05 +02:00
Aleksei Magusev ff04de1592 Remove unreachable function "branch" in parser 2015-11-17 12:06:35 +01:00
Aleksei Magusev 58fcce1bfc Remove undesirable output in parser 2015-11-17 11:43:09 +01:00
José Valim a70a5fb442 Merge pull request #3968 from elixirdrops/master
fix spellings and grammar typo in string module
2015-11-17 11:31:31 +01:00
allyraza 92fecf106b fix spellings and grammar typo in string module 2015-11-17 12:24:19 +02:00
José Valim c855561e96 Expand assertions before extracting vars and pins 2015-11-16 23:56:33 +01:00
José Valim ba79d29aa5 Warn on conditional vars
Note a field named safe_by_default has been added so we don't
warn on variables defined inside EXPR in "case EXPR, do: ...".

The proper solution would be to convert && and || into special
forms and they would be free of the warning. However,
safe_by_default is a welcome temporary solution until we see
how the warning will fare on actual applications.
2015-11-16 23:56:33 +01:00
José Valim 93153d4c4f Merge pull request #3967 from schaary/ms_fix_option_parser_docs
Docs fix: the OptionParser parses options, not arguments
2015-11-16 23:31:14 +01:00
Aleksei Magusev 56274111b7 Simplify some assertions 2015-11-16 22:43:21 +01:00
Michael Schaarschmidt c9105569a0 The OptionParser parses options, not argumemnts 2015-11-16 22:31:30 +01:00
José Valim 3d960c5b65 Tell how to solve access field names in uppercase 2015-11-16 22:25:03 +01:00
José Valim ce419c1e66 Merge pull request #3965 from Troush/elixir-info-option
Added ./bin/elixir -v option #3947
2015-11-16 22:11:29 +01:00
Troush 600899d48a Changed ./bin/elixir -v/--version to be more verbose with Erlang system_version and short revision hash.
Also removed tag from System.build_info/0 and added shot revision hash as revision key
2015-11-16 22:22:32 +02:00
José Valim b2bf633ab5 Clean up and optimize cond implementation 2015-11-16 19:25:08 +01:00
José Valim 599e7bb89c Remove wrapper export_vars function 2015-11-16 17:59:22 +01:00
José Valim 3fa9a642d5 Use new module location 2015-11-16 17:47:00 +01:00
José Valim a14b2afe4b Clean up destructure implementation 2015-11-16 17:29:56 +01:00
José Valim a34d81c253 often undesired -> ambiguous 2015-11-16 14:04:40 +01:00
José Valim 269d897a1b Merge pull request #3964 from Gazler/test/pipe-parens
tests(Kernel.WarningTest): test for pipeline without parentheses
2015-11-16 13:45:39 +01:00
Gary Rennie 76178a6293 tests(Kernel.WarningTest): test for pipeline without parentheses 2015-11-16 12:43:44 +00:00
José Valim 3487d00ddb Warn when piping into functions without parens, closes #3642 2015-11-16 13:26:05 +01:00
José Valim 05e4e26a87 Wrap values read from ex_unit app environment, closes #3940 2015-11-15 21:40:07 +01:00
José Valim d8699068a8 Introduce Task.yield_many/2 2015-11-15 21:33:21 +01:00
José Valim 52253aa234 Add cancel and read timer functions to Process 2015-11-15 20:45:11 +01:00
José Valim 8180dfe904 Return {:exit, reason} in yield/shutdown 2015-11-15 20:45:11 +01:00
José Valim 7adec1548d Set stop timeout to infinity, same as OTP 2015-11-15 20:45:11 +01:00
Aleksei Magusev 482f33e6ea Unit test ANSI color/1 and color_background/1 2015-11-15 19:03:43 +01:00
José Valim 1891a3244f Merge pull request #3957 from rodrigues/master
Add ANSI escape sequences for extended colors
2015-11-15 13:50:26 +01:00
José Valim ad647a8b20 Remove the need for export_head 2015-11-15 13:11:41 +01:00
José Valim bf4c7cb211 Update CHANGELOG 2015-11-15 11:44:11 +01:00
José Valim cb440f650e Remove m/0 and m/1 helpers 2015-11-15 11:35:02 +01:00
José Valim 15a613dd08 before -> after 2015-11-14 22:03:56 +01:00
José Valim c803afe90c Add tests for endianness 2015-11-14 16:30:17 +01:00
José Valim 1587efc787 Warn if compiled endianness is different than current one, closes #2785 2015-11-13 19:50:59 +01:00
José Valim 2b5dc7f79b Ensure .compile.lock is per project, closes #3849 2015-11-13 19:34:48 +01:00
Aleksei Magusev 8428ae1a99 Merge pull request #3958 from whatyouhide/empty-stab-take-2
Throw an error for -> with an empty right-hand side
2015-11-13 11:42:45 +01:00
José Valim 412f7cea00 Drop if_ok in favor of new with special form 2015-11-13 11:27:09 +01:00
José Valim 16f4975755 No need to use https for local hex/rebar as we sign and verify packages 2015-11-13 11:21:06 +01:00
José Valim f68db17d58 Merge pull request #3943 from lexmag/introduce-with
Introduce with
2015-11-13 11:20:58 +01:00
Andrea Leopardi e4db37c1b0 Throw an error for -> with an empty right-hand side 2015-11-13 01:40:53 +01:00
Aleksei Magusev 5202b13b9b Make with a special form 2015-11-13 00:29:00 +01:00
Victor Rodrigues dc17f37292 Add ANSI escape sequences for extended colors 2015-11-12 11:55:36 +01:00
José Valim ba7a92e18a Merge pull request #3953 from eksperimental/integers_docs
Integer docs
2015-11-12 06:39:53 -02:00
José Valim b485d80bec Merge pull request #3948 from xbinxu/master
implement Enum.empty? as Enum.count == 0
2015-11-12 06:28:15 -02:00
eksperimental 511a175495 Test Integer: rename tests + test in guards 2015-11-12 10:10:04 +07:00
Alex Xu a1325e1caf minor fix 2015-11-11 11:03:03 +08:00
eksperimental 8951ef5d0d Add examples to Integer.is_odd/1 and Integer.is_even/1 2015-11-11 00:11:37 +07:00
Alex Xu 7ba0d4d97d only use count == 0 for enumerable containers implement count interface 2015-11-10 14:39:55 +08:00
Alex Xu 8488e29346 implement Enum.empty? as Enum.count == 0 2015-11-10 11:35:03 +08:00
Aleksei Magusev fd81b308fe Add with expansion and translation 2015-11-09 15:28:43 +01:00
James Fish 361d2f065d Fix remsh'ing to node with different beam files 2015-11-07 14:39:23 +00:00
Aleksei Magusev cdb163e634 Merge pull request #3944 from henrik/patch-3
logger.ex: doc typo
2015-11-07 11:58:07 +01:00
Henrik Nyh 3810185cc4 logger.ex: doc typo 2015-11-07 01:52:43 +01:00
James Fish 760e5015c8 Merge pull request #3942 from padde/add-file-to-logger-metadata
Add file to logger metadata
2015-11-06 20:25:39 +00:00
Patrick Oscity a47b730f3c Improve documentation for logger metadata 2015-11-06 15:51:18 +01:00
Patrick Oscity b75d47189b Add file to logger default metadata 2015-11-06 15:44:43 +01:00
James Fish 662c5fb3cb Merge pull request #3941 from ronanh/fix_genserver_doc_typo
Fix a typo in GenServer Documentation
2015-11-06 11:46:18 +00:00
Ronan Harmegnies 038ee4fde2 Fix a typo in GenServer Documentation 2015-11-06 07:19:17 +01:00
José Valim 43b489649f Merge pull request #3938 from alco/supervisor-moduledoc
Use ~S in Supervisor's moduledoc, fixes #3937
2015-11-04 18:13:26 -02:00
Alexei Sholik d9232bd40c Use ~S in Supervisor's moduledoc, fixes #3937 2015-11-04 11:50:25 +02:00
Andrea Leopardi 806c2da15c First incomplete version of the i/1 IEx helper 2015-11-01 23:33:44 +01:00
José Valim 8fd3eb5a2c Use String.to_char_list when converting shell commands
We have found this bug as rebar failed to compile projects
with non-latin characters on the path. Using String.to_char_list
performs the proper conversion, passing the proper arguments
to rebar.
2015-10-30 11:30:05 -02:00
José Valim 4a0b64886d Merge pull request #3928 from oguizol/enum_all_any_enhancement
Updated documentation of Enum.all? and Enum.any? to reflect the actua…
2015-10-29 14:58:34 -02:00
Olivier ab80ec5606 Updated documentation of Enum.all? and Enum.any? to reflect the actual implementation 2015-10-29 15:55:38 +01:00
José Valim c721afa04e Merge pull request #3922 from scrogson/update_encode_decode_www_form_docs
Correct mime type as 'x-www-form-urlencoded'
2015-10-28 11:31:44 -02:00
José Valim 38371fa167 Merge pull request #3923 from alco/special-forms-doc-typo
Fix erroneous formatting in the docs for 'try'
2015-10-28 10:42:28 -02:00
Alexei Sholik e8d1f7a6ec Fix erroneous formatting in the docs for 'try' 2015-10-28 14:36:40 +02:00
Sonny Scroggin d54436a456 Correct mime type as 'x-www-form-urlencoded' 2015-10-28 07:13:59 -05:00
Aleksei Magusev 6c8855f1a7 Merge pull request #3921 from smoynes/patch-1
Fix formatting of example
2015-10-28 10:43:16 +01:00
Scott Moynes 2c25042306 Fix formatting of example 2015-10-27 19:54:36 -07:00
Aleksei Magusev 7bb02bbbbb Merge pull request #3919 from vadim-moz/add-map-and-mapset-new-with-params
Add `new/1` and `new/2` to Map and MapSet (mirroring the Keyword variant)
2015-10-27 18:44:42 +01:00
vadim suvorov aebd7e5143 Add new/1 and new/2 to Map and MapSet (mirroring the Keyword variant)
See discussion at https://github.com/elixir-lang/elixir/issues/3855
2015-10-27 10:27:15 -07:00
José Valim 977226d8be Merge pull request #3920 from eksperimental/doctest_space_message
Correct message for invalid indentation in doc test when is "1 space"
2015-10-27 14:35:31 -02:00
eksperimental 7b1a7b29ce Correct message for invalid indentation in doc test when is "1 space" 2015-10-27 23:30:40 +07:00
José Valim 39f1bdb7ff Remove --quiet, document MIX_QUIET 2015-10-25 22:46:11 -02:00
José Valim 62641dfa03 Merge pull request #3910 from druzn3k/issue_3167
Implemented Mix.Shell.Quiet
2015-10-25 21:44:17 -02:00
Aleksei Magusev 37e4f20bd6 Correct String.jaro_distance/2 spec definition 2015-10-25 23:44:19 +01:00
Eric Meadows-Jönsson 62ca86965f Merge pull request #3912 from eksperimental/pipe_operator_simplication
Pipe operator simplication
2015-10-25 11:18:43 +01:00
druznek 24a7e6c690 Implemented Mix.Shell.Quiet
Mix.Shell.Quiet is like Mix.Shell.IO, but it prints much less
information out. Useful when the output is not important.
2015-10-25 10:38:55 +01:00
eksperimental 01c8e1cd72 Remove |> from end of line 2015-10-25 12:59:17 +07:00
eksperimental ecff4ad729 Simplify code by using pipe operator 2015-10-25 12:59:11 +07:00
Aleksei Magusev 28a279d88d Suppress warning in Task test 2015-10-24 13:23:42 +02:00
José Valim a56ba1e377 Merge pull request #3909 from druzn3k/issue_3760
Documentation improvement to GenEvent
2015-10-24 10:20:10 +02:00
druznek b6dcbdb69a Documentation improvement to GenEvent
Properly documented the possibility of adding an handler with an
unique id attached when multiple instances of the same module must
be attached.
2015-10-24 10:16:42 +02:00
José Valim 62f0f2a528 Merge pull request #3908 from lexmag/remove-stop-match-leftover
Remove needless :stop clause, tidy up bad owner error message in Task
2015-10-24 09:54:55 +02:00
Aleksei Magusev 8096300c87 Tidy up bad owner error message in Task calls 2015-10-24 00:51:36 +02:00
Aleksei Magusev 19c40ec584 Favor local calls inside modules 2015-10-24 00:49:45 +02:00
Aleksei Magusev 911b9de753 Remove needless :stop clause in GenEvent receive 2015-10-24 00:47:47 +02:00
José Valim 49e7b8a8b8 Merge pull request #3907 from Gazler/fix/umbrella-empty-dir
fix(Mix.Dep.Umbrella): prevent error when app is missing mix.exs
2015-10-23 23:38:39 +02:00
Gary Rennie 82d3da0774 fix(Mix.Dep.Umbrella): prevent error when app is missing mix.exs
Previously, when using an umbrella app, if a directory was empty when
running a mix task the following error was displayed:

 > ** (Mix) Could not find a Mix.Project, please ensure a mix.exs file
 > is available"

Since git does not keep directories, it is common to see this issue when
switching branches on an umbrella application where some branches have a
particular app inside `apps` and other branches don't.

This commit checks for the presence of `mix.exs` and uses directories
containing the file as an app in the umbrella application
2015-10-23 22:34:33 +01:00
José Valim d757d6f821 Merge pull request #3906 from henrik/patch-2
Clarify Float.ceil/floor handling of decimals
2015-10-23 22:30:30 +02:00
Henrik Nyh 0c4fcb807a Clarify Float.ceil/floor handling of decimals 2015-10-23 22:27:06 +02:00
José Valim ef3c45f994 Ensure keys can also be used on update 2015-10-23 15:33:09 +02:00
José Valim 94a07df8d3 Remove uses of soon to be deprecated Dict 2015-10-23 15:04:24 +02:00
José Valim a0bb2c3563 Add Supervisor.stop 2015-10-23 14:53:40 +02:00
José Valim a2d11ab784 Fix filesystem race condition on mix test suite 2015-10-23 14:46:42 +02:00
José Valim 949fc7a885 Move eex error to warning 2015-10-23 13:28:42 +02:00
José Valim 63c1f07360 Merge pull request #3349 from tony612/eex-raises-on-missing-assigns
eex engine raises on missing assigns
2015-10-23 13:23:05 +02:00
José Valim c33e41295e Remove warnings from test suite 2015-10-23 11:12:23 +02:00
José Valim 9672ae363d Streamline docs and warnings in Task, Agent, GenServer and GenEvent 2015-10-23 10:46:00 +02:00
José Valim 20868b9838 Merge pull request #3901 from TattdCodeMonkey/use-gen-stop
Support new gen:stop in GenEvent and GenServer
2015-10-23 10:32:31 +02:00
Rodney Norris 63295ef204 added stop/3 to GenServer and GenEvent using :gen.stop 2015-10-22 21:41:08 -05:00
José Valim 2b7146c802 Merge pull request #3900 from lexmag/recompile-no-start
Do not start apps on "recompile" in an IEx session with --no-start
2015-10-23 01:37:49 +02:00
Aleksei Magusev 65b39eb582 Do not start apps on "recompile" in an IEx session with --no-start 2015-10-23 01:35:16 +02:00
José Valim 836f6fcfc2 Emit status reports at the end of deps.get/deps.update
Closes #3795
2015-10-23 01:14:54 +02:00
James Fish 4c42568dde Ensure Task function in stacktrace when invalid owner 2015-10-22 23:25:48 +01:00
José Valim bd3332c848 Ensure we cannot pipe into tuples or maps (literals) 2015-10-22 23:41:50 +02:00
José Valim 95082f1d4f Merge pull request #3895 from seomoz/task-owner
Provide helpful error from `Task` functions when called from wrong process
2015-10-22 22:32:43 +02:00
Katie 3b55fda690 Raise an error when a task is queried from a non-owning process.
Fixes #3894.
2015-10-22 12:14:11 -07:00
José Valim fc3f273401 Merge pull request #3893 from hubertlepicki/validate_git_options
Validate git options and warn on conflicting ref, branch or tags
2015-10-22 13:38:42 +02:00
Hubert Łępicki 7c0f21a9c4 Validate git options and warn on conflicting ref, branch or tags 2015-10-22 13:21:50 +02:00
José Valim e87896ec11 Update CHANGELOG 2015-10-22 13:15:11 +02:00
José Valim 8aa8fdf93d Make it clear about top-level cases 2015-10-22 01:52:42 +02:00
José Valim f821e29525 Optimize base encode/decode about 10x 2015-10-21 19:05:30 +02:00
José Valim ff4da655d9 Allow pin in fns and comprehensions 2015-10-21 13:50:41 +02:00
José Valim ccb78d65e7 Allow size and map key variable matching in for and fn
We are treating map keys as patterns, so the pin
operator is required when matching on those.

We are treating bitstring specifiers as values,
so the pin operator is not required when matching
on those.

It is debatable if bitstring specifiers should
also be considered matches and require the pin
operator in such occasions, the reason why we have
decided to keep it as value is two fold:

1. Elixir 1.0 used them as values, so it is
   backwards compatible;

2. Supporting them as patterns introduces
   ambiguity since they are never matched,
   for example, we would need to write:

     x = 8
     <<a, b::size(^x)>> # valid
     <<x, y::size(x)>>  # valid
     <<a, b::size(x)>>  # invalid

   The last example contradicts regular matching
   where it would be valid with or without the pin
   operator. Notice this problem does not occur with
   maps because maps can never bind on a variable
   introduced in the pattern itself.
2015-10-21 13:50:41 +02:00
José Valim 8060b793b2 Merge pull request #3891 from tony612/improve-supervisor-spec-doc
improve doc for restart of supervisor spec
2015-10-20 19:55:48 +02:00
Tony Han 1872064062 improve doc for restart of supervisor spec 2015-10-21 01:41:26 +08:00
Eric Meadows-Jönsson 1aa0912493 Merge pull request #3886 from tokafish/patch-1
Fixing misspelling of "suppress" in docs
2015-10-20 00:12:50 -05:00
Thomas Fisher 7a2edcbaee Fixing minor misspelling 2015-10-19 22:02:34 -07:00
José Valim 9697074346 Merge pull request #3884 from gjaldon/format_error_grammar_fix
Grammar fix for error message returned by :elixir_aliases.format_error/2
2015-10-19 23:30:27 +02:00
Gabriel Jaldon 1818287700 Grammar fix for error message returned by :elixir_aliases.format_error/2 2015-10-20 04:05:55 +08:00
José Valim 3ef6d24a7e Support variables as map keys 2015-10-19 21:37:59 +02:00
José Valim 6a8b212376 Error with tuples in map access syntax 2015-10-19 15:31:51 +02:00
José Valim 95b03739dd Add Process.get_keys/0 2015-10-19 12:33:48 +02:00
José Valim 35fbf0716a Move code server and compiler away from ord* 2015-10-19 12:19:15 +02:00
José Valim 6128444f4b Move more items away from ord* when possible 2015-10-19 12:19:15 +02:00
José Valim a90bf7fc50 Merge pull request #3883 from djm/patch-1
Mix.Task moduledoc update: correction
2015-10-19 00:20:08 +02:00
José Valim 89d2b916c1 Provide better error messages for context modules 2015-10-19 00:19:34 +02:00
Darian Moody 0f1b5e5e23 Mix.Task moduledoc update: correction
Corrected:

* showed => shown
* Multiline backtick rendering issues.
2015-10-18 23:18:42 +01:00
José Valim d416772ace Merge pull request #3882 from hubertlepicki/master
improve error message on requiring module from the context it was defined in
2015-10-19 00:01:47 +02:00
José Valim 6de232cab0 Use empty bindings 2015-10-18 23:59:57 +02:00
José Valim 5b88ef22d2 Move internal vars and backup vars from orddict to maps 2015-10-18 23:32:28 +02:00
José Valim 2f7c2da482 Migrate to :rand 2015-10-18 22:31:04 +02:00
Hubert Łępicki d9a8a2a93d cleaner (?) error messages when attempting to use the module from the same module 2015-10-18 21:58:07 +02:00
José Valim 1f7ee6788c Remove unused functions in utils 2015-10-18 21:09:46 +02:00
José Valim 07d375d6c2 Remove more dependencies on HashSet and HashDict 2015-10-18 21:09:34 +02:00
José Valim 4b01bccfbf Include line for previously defined doc attribute warnings 2015-10-18 20:56:05 +02:00
José Valim ecf90268eb Introduce String.trim_{prefix,suffix,leading,trailing}/2
The first two will remove only the first occurrence of
the given match in string. The last two will remove all
occurrences of the given match.

We have chosen the leading/trailing naming instead of
left/right because left/right is used in String as
lstrip/rstrip and ljust/rjust, i.e. with a different
naming convention. Furthermore, the strip/just variants
receives codepoints while the ones in trim receives
strings themselves.
2015-10-18 20:50:50 +02:00
José Valim 266fb8459d Merge pull request #3880 from philosodad/documentation-fix-for-map-special-form
Fix innacurate iex output
2015-10-17 23:57:59 +02:00
José Valim 1a00eb67e7 Only load consolidated protocols if consolidation is enabled 2015-10-17 23:57:14 +02:00
Paul Daigle 295a366f72 Fix innacurate iex output
Uses a string key instead of an atom key in order to force the iex
output to show the list of tuples rather than a keyword list.
2015-10-17 17:39:26 -04:00
José Valim 5ec2a43650 Merge pull request #3878 from elixir-lang/jf-task-sup-no-block
Don't block Task.Supervisor when starting tasks
2015-10-17 13:40:56 +02:00
James Fish c87eafab0c Don't block Task.Supervisor when starting tasks 2015-10-16 22:33:19 +01:00
José Valim a52e8538f8 Merge pull request #3877 from lexmag/doc-false-redefining
Properly check for atrribute value on redefining
2015-10-16 22:35:27 +02:00
Aleksei Magusev 7602bda91e Properly check for docs atrribute value on redefining 2015-10-16 22:29:30 +02:00
Aleksei Magusev 4ac8d8a87e Warn on attribute change even if value is the same 2015-10-16 20:48:39 +02:00
José Valim 8c79b21112 Add Task.Supervisor.async_nolink/1/3 2015-10-16 19:05:19 +02:00
José Valim 7c7b88d888 Merge pull request #3876 from MattNguyen/patch-1
Update enum.ex
2015-10-16 18:27:54 +02:00
Matthew Nguyen 6de06887cb Update enum.ex 2015-10-16 09:25:31 -07:00
José Valim 754fdf87c3 Do not warn on attribute change if value is the same 2015-10-16 18:11:34 +02:00
José Valim 1224e93ab8 Update CHANGELOG 2015-10-16 18:11:14 +02:00
José Valim b131b17f78 Fix bootstrap issues on macro code 2015-10-16 17:40:56 +02:00
José Valim 31187012fa Merge pull request #3874 from nurugger07/underscore_camelize_strings
Include underscore/1 & camelize/1 in the Macro module
2015-10-16 17:33:49 +02:00
José Valim 1359c909b5 Merge pull request #3875 from wiserfirst/master
Fix code example in the document for quote macro
2015-10-16 13:31:32 +02:00
Qing Wu 507faf5a7e Fix code example in the document for quote macro 2015-10-16 21:24:04 +11:00
José Valim 2547506865 Do not mark erlang:error as generated as it prunes stacktraces 2015-10-16 02:02:16 +02:00
Johnny Winn b3595595a7 Include underscore/1 & camelize/1 in the Macro module
* also adding deprecation note in the Mix.Utils module
2015-10-15 19:39:45 -04:00
José Valim 8d216f8162 Merge pull request #3870 from kennyballou/special_forms_doc_change
Add clarification to comprehension match example
2015-10-15 13:42:26 +02:00
José Valim 613760104e Merge pull request #3871 from Havvy/patch-2
Add link to monitor/2 example
2015-10-15 13:37:23 +02:00
Ryan Scheel 7e5ea0da6d Add link to monitor/2 example
Because copying the entire example would be silly.
2015-10-14 20:19:25 -07:00
kballou e273f6930e Add clarification to comprehension match example
The current example is fine, especially with an understanding of the
matching that is happening. However, to make the example more explicit, the
name of the unmatched pair should be different.
2015-10-14 17:35:37 -06:00
José Valim e12ca353a9 Do not generate install instructions for umbrellas 2015-10-15 01:32:49 +02:00
José Valim 7b5905a2dd Remove unecessary warning, make type opaque 2015-10-14 23:45:56 +02:00
José Valim 66582563e7 Merge pull request #3867 from lexmag/redefinition-warn
Tidy up implementation for warning on @attribute redefinition
2015-10-14 23:07:19 +02:00
Aleksei Magusev 022c007b2c Tidy up implementation for warning on @attribute redefinition 2015-10-14 21:27:16 +02:00
José Valim be940abece Test duplicate @doc warn 2015-10-14 19:33:19 +02:00
José Valim bb29ced8b3 Merge pull request #3865 from henrik/keyword-list-to-string
More helpful error for keyword list to_string
2015-10-14 19:09:23 +02:00
Henrik Nyh 2de804eb86 More helpful List.to_string argument error
Replaces the confusing "argument error" with ":unicode.characters_to_binary([a: 1])" in the backtrace that you would get when interpolating e.g. a keyword list. Developers used to Ruby may expect this to just work.

Kudos to @lexmag for the idea to implement it this way.
2015-10-14 19:01:47 +02:00
José Valim 68ab38736b Tidy up code from merged PRs 2015-10-14 18:43:23 +02:00
José Valim 80c94907dd Merge pull request #3752 from gustf/feature-warn-test-pattern
New config: warn_test_pattern
2015-10-14 18:38:46 +02:00
José Valim 9fcaed147c Merge pull request #3651 from bbense/iex_m_command
Iex m command
2015-10-14 18:36:43 +02:00
José Valim d9a93c47ca Solve bootstrap issues with @doc 2015-10-14 18:25:20 +02:00
José Valim 53c85df69a Merge pull request #3793 from danielberkompas/3707-warn-on-redefined-docs
[#3707] Warn when @attributes are redefined
2015-10-14 17:56:22 +02:00
José Valim e301923d68 Fix race condition on umbrella path dependency test 2015-10-14 15:52:55 +02:00
José Valim b54a8a0cbb Merge pull request #3866 from lexmag/generated-raise
Make workaround for dialyzer warnings suppression on raise
2015-10-14 15:43:53 +02:00
Aleksei Magusev f7069342c0 Make workaround for dialyzer warnings suppression on raise 2015-10-14 15:40:20 +02:00
José Valim 247761b0c7 Silence dialyzer warnings on raise 2015-10-14 15:17:15 +02:00
José Valim d1b785821c Add note to deprecate size functions in Map / Keyword 2015-10-13 23:20:50 +02:00
José Valim d5a0db2940 Merge pull request #3863 from joshuap/float_parse_documentation
Document limits of `Float.parse/1`.
2015-10-13 17:51:41 +02:00
Joshua Wood c4fe36275b Document limits of Float.parse/1.
This commit clarifies the documentation for `Float.parse/1` as well as
adds test assertions to demonstrate the limits enforced by erlang/IEEE
754-1985 double precision floats. See #3862 for more context.
2015-10-13 08:37:05 -07:00
Booker C. Bense b23ec517d6 A first pass at implementing an m helper for modules.
I like the current output as it's simple and short and
to me conveys the information, however that may not work
for everybody. The code is setup to easily allow changes in
both label names and formatting the output.

I am avoiding any information that might be erlang 18 only
(ie. MD5 , native, etc.. )

Expanded documentation

Added label and formatting suggestions from comments
on Pull Request.

Added addition testing to ensure date formating

Added code to ensure that m module works on Erlang 17.

Improved documentation of m commands slightly, rebased with current master
and implemented code changes suggested. (Sorry, I thought I had addressed
those already... )
2015-10-12 09:07:31 -07:00
Booker C. Bense 29cad499f7 m/1 version of :c.m is working
Added formatting suggestions from Jose
2015-10-12 09:07:31 -07:00
José Valim 71e5acc0b7 Ensure no warnings on rewrites 2015-10-11 12:55:59 -05:00
José Valim 2aad447cbf Remove old dialyzer test file 2015-10-11 12:40:02 -05:00
José Valim 8ac8a4c2e8 Clean up generated code 2015-10-11 12:32:29 -05:00
James Fish a637caffc8 Add dialyzer tests for generated AST 2015-10-11 12:06:12 -05:00
James Fish a7f89b091f Fix dialyzer warnings for remote calls
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2015-10-11 12:03:36 -05:00
José Valim 3ccd5137ef Add generated annotations 2015-10-11 12:02:47 -05:00
José Valim 94de6615d7 Consolidation is now on by default 2015-10-10 16:00:13 -05:00
José Valim c859e49199 Update CHANGELOG and remove deprecated code 2015-10-07 22:27:31 -05:00
José Valim fd72cb37cc Remove unecessary System.put_env 2015-10-07 17:37:12 -05:00
José Valim accf687a0d Do not recur on non recursive tasks 2015-10-07 15:36:10 -05:00
José Valim c983b3db69 Merge pull request #3844 from Maikon/show-type-docs-with-t1
Display docs for types with t/1
2015-10-07 14:36:22 -05:00
Makis Otman f29e29b3a4 Display type docs with t(Module.type) and t(Module.type/arity) 2015-10-07 19:52:49 +01:00
José Valim 8298041cbc Make tests that assert consolidation more robust 2015-10-06 22:59:19 -05:00
José Valim 2c1a39eebb Merge pull request #3845 from mgwidmann/docs_for_callbacks2
IEx callback documentation
2015-10-06 19:25:20 -05:00
José Valim 245094f665 Use fixtures only when necessary 2015-10-06 19:24:32 -05:00
José Valim 3f9602bf82 Add tests for always on consolidation 2015-10-06 18:33:35 -05:00
José Valim b2d459aa29 Merge pull request #3846 from lexmag/bitstring-splicing
Fix bitstring splicing for `bytes` type modifier
2015-10-06 17:52:11 -05:00
Aleksei Magusev 17b3df271d Fix bitstring splicing for bytes type modifier 2015-10-07 00:30:41 +02:00
Matt Widmann 49e41ec098 Make IEx.Helpers.h handle automatically looking up callbacks when the implementation is requested. 2015-10-06 15:45:50 -04:00
José Valim 9c86f5b167 Solve bootstrap issues on defdelegate 2015-10-06 11:57:00 -05:00
José Valim 56fbcfc79f Merge pull request #3841 from danmarcab/delegate_docs
Add default docs for delegate methods
2015-10-06 11:49:36 -05:00
Daniel Marin fb8ac4d85c Add default docs for delegate methods 2015-10-06 15:45:41 +01:00
José Valim 578800734f Cache and always consolidate protocols 2015-10-05 23:03:08 -05:00
José Valim 3f1bc7c9bb Split ElixirSCM and Lock manifests apart 2015-10-05 23:03:08 -05:00
José Valim 2e1ce93210 Add protocol information to manifests 2015-10-05 23:03:08 -05:00
José Valim 572eddd9fe Merge pull request #3840 from Havvy/behaviour-docs
Transplant Behaviour module docs to @behaviour
2015-10-05 22:50:19 -05:00
Ryan Scheel (Havvy) 14ac3c5e97 Transplant Behaviour module docs to @behaviour
Most of these docs are from the 1.0 Behaviour module docs, but
those were seemingly lost at some point during the switch to
@callback. I'm not sure whether it is better to put them on
@behaviour or @callback, but I placed them on @behaviour. I'd
rather see a "Behaviour" page that has this information, not
just something randomly stuck in Module or Kernel.Typespec.
2015-10-06 02:20:52 +00:00
Eric Meadows-Jönsson 4bbd3b0bbb inspect invalid ANSI sequences 2015-10-05 17:34:15 -05:00
José Valim a06a873392 Merge pull request #3833 from barisbalic/add-as_boolean-to-typespec-docs
List `as_boolean/1` under `Kernel.Typespec` built-ins.
2015-10-05 14:37:36 -05:00
Baris Balic 70e0b24536 List as_boolean() under Kernel.Typespec built-ins. 2015-10-05 14:35:27 -05:00
José Valim cb78e0b334 Merge pull request #3836 from tejasmanohar/link_freenode_webchat
change irc:// URI to freenode's webchat
2015-10-05 11:51:50 -05:00
Tejas Manohar 1932b5243e change irc:// URI to freenode's webchat
most IRC clients don't seem to support irc://, obviously not the command-line ones. additionally, GitHub doesn't consider irc:// a hyperlink so that's less useful but it does hyperlink this (and we're on freenode)

update webchat URL to have the correct channel
2015-10-05 11:44:06 -05:00
José Valim 9bcd8439e7 Tidy up GenServer docs 2015-10-04 15:58:34 -05:00
José Valim 40fff43ea0 Merge pull request #3832 from ma2gedev/macro-traverse
Add `Macro.traverse/4` pre/post-order traversal at the same time
2015-10-04 15:28:26 -05:00
Takayuki Matsubara 7e1073ec49 Add Macro.traverse/4 pre/post-order traversal at the same time
Almost is like the `Macro.prewalk/3` and `Macro.postwalk/3` but
pre/post-order traversal at the same time.
2015-10-05 00:06:21 +09:00
José Valim f6b2ddd81b Merge pull request #3805 from endersstocker/add-string-normalize-nfc
Add NFC support to String.normalize/2
2015-10-04 08:55:34 -05:00
José Valim c37a90ea4a Merge pull request #3823 from mgwidmann/configurable_iex_width
Add IEx width option for larger terminal screens.
2015-10-04 08:47:30 -05:00
José Valim 19a8e766a7 Merge pull request #3827 from lexmag/omit-else
Omit `else` option in `if` and `unless` clauses if it returns `nil`
2015-10-04 08:40:41 -05:00
José Valim e2b13ddba4 Merge pull request #3829 from javimolla/add-genserver-via-info
Add more info about :via registry to GenServer
2015-10-04 08:39:06 -05:00
javimolla 3eeb903534 fix3761: Add more info about :via registry to GenServer 2015-10-04 11:25:24 +02:00
Aleksei Magusev d682c47bf9 Omit else option in if and unless clauses if it returns nil 2015-10-03 21:07:19 +02:00
Matt Widmann 340717b979 Add IEx width option for larger terminal screens. 2015-10-03 12:14:45 -04:00
Bryan Endersstocker 07fedf66d0 Add NFC support to String.normalize/2 2015-10-03 11:08:55 -04:00
José Valim fa865ce5c1 Merge pull request #3825 from tyre/patch-1
Update special_forms.ex
2015-10-03 06:25:00 -05:00
Chris Maddox ddfcc60505 Update special_forms.ex
I a word
2015-10-03 00:13:47 -05:00
José Valim b933d84b05 Merge pull request #3817 from uDude/master
Fixed typo/grammar in Supervisor @doc
2015-10-02 16:53:18 -05:00
uDude 6239bf32db Fixed a typo in @doc. 2015-10-02 15:19:22 -06:00
José Valim 3915e6c127 Merge pull request #3819 from rrrene/patch-2
Fix small typo in docs for File.rename/2
2015-10-02 16:00:44 -05:00
René Föhring 02b2bd2fc0 Fix small typo in docs for File.rename/2 2015-10-02 21:49:20 +02:00
José Valim 40c70ffcfa Merge pull request #3807 from CrowdHailer/informative-use-message
Informative error message when passing a non atom value to use
2015-10-01 17:06:09 +02:00
eksperimental 471de4b8a4 run compile before regular tests
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2015-10-01 17:05:05 +02:00
CrowdHailer f2cd34f1a8 use macro to show code sample in error message 2015-10-01 16:04:35 +01:00
José Valim ca5a9060ef Merge pull request #3810 from lexmag/optimize-keyword-new
Optimize `Keyword.new/2` function
2015-10-01 16:38:39 +02:00
José Valim f8e2332db9 Merge pull request #3811 from michalmuskala/optimize_map
Optimize Enum.map/2 for lists
2015-10-01 16:28:32 +02:00
José Valim 4061a9573d Merge pull request #3812 from radar/enum-shuffle
Add demonstration for Enum.shuffle's randomness
2015-10-01 16:25:31 +02:00
Ryan Bigg 0cc63df80d Add demonstration for Enum.shuffle's randomness 2015-10-01 19:16:59 +10:00
Michal Muskala ed66646187 Optimize Enum.map/2 for lists
Out of 3 options:
* using for comprehensions
* using explicit recursion with accumulator
* using simple consing

The third option seems to be the fastest. That's also the one that
is used by :lists.map/2
2015-10-01 00:56:07 +02:00
Aleksei Magusev 3f7f074494 Optimize Keyword.new/2 function 2015-10-01 00:17:24 +02:00
José Valim f70192bb6e Merge pull request #3806 from flupke/patch-1
Fix default value of :time option in File.Stat docs
2015-09-30 08:45:14 +02:00
Luper Rouch dabcb4c71f Fix default value of :time option in File.Stat docs 2015-09-30 08:30:12 +02:00
José Valim c7637bc362 Merge pull request #3801 from eksperimental/enum_2
Enum: corrections in  private functions
2015-09-29 23:48:19 +02:00
eksperimental 0d3d410beb Enum: rename variable in priv. func.
rename variable according to changes introduced in commit ced1095e17
2015-09-30 02:21:22 +07:00
Daniel Berkompas 50e37d75e0 [#3707] Warn when @attributes are redefined
Attributes such as @doc and @moduledoc should emit warnings when they
are redefined. For example:

```
@doc “first”
@doc “second”
```

This should emit a warning to the user, because the doc tag will be
overwritten with the value “second”.

See #3707 for more details.
2015-09-29 11:48:05 -07:00
José Valim c8172c8341 Do not delete only information from deps
If we delete this information, we lose the environments the
dependency has been originally restricted to, raising improper
error messages.
2015-09-29 17:25:17 +02:00
José Valim c0c6983363 Print byte information on base decoding errors 2015-09-29 15:21:50 +02:00
José Valim f7654bf66a Ensure we print unicode codepoints on errors, closes #3804 2015-09-29 15:03:55 +02:00
José Valim 5f36e0b33e Merge pull request #3796 from radar/master
Add an example for IO.gets
2015-09-29 13:58:59 +02:00
Ryan Bigg 4d5a6bed63 Add an example for IO.gets 2015-09-29 07:58:17 -04:00
José Valim efa505b38a Ensure we keep ordering and last properties in keyword 2015-09-29 13:27:55 +02:00
José Valim b138c6f472 Merge pull request #3802 from Havvy/comment-genevent-example
Add comments to the first GenEvent example.
2015-09-29 12:21:59 +02:00
Ryan Scheel (Havvy) b268291c8c Add comments to the first GenEvent example. 2015-09-29 11:42:42 +02:00
José Valim d5fe8eb068 Add a test for umbrellas with build embedded 2015-09-28 23:52:55 +02:00
José Valim 30281d149a Fix failing tests 2015-09-28 22:57:32 +02:00
José Valim 81503c0a91 Merge pull request #3792 from eksperimental/enum_rephrase_terminology
Enum: several fixes
2015-09-28 22:35:19 +02:00
eksperimental ced1095e17 Enum - Several fixes
- Updates the terminology: what it was refereed as collection is now
  Enumerable and collectable
- Text wrap to 72 chars
- Backticks added to enumerable and other variables.
- Specs fixed
- Summaries improved
  - Try to avoid the use of what it returns in the summary.
- Documentation for several functions corrected and improved, as well as examples.
- Some variables in functions renamed.
- I have changed the use of value when it actually refered to elements in the
  Enumerable to element, but i had kept it when it was used for tuples,
  refering to key - value, even though value could be an enumerable.
2015-09-29 03:33:25 +07:00
José Valim 293933b813 Ensure stacktraces are shown on app_path failures 2015-09-28 20:38:17 +02:00
José Valim af1ef7f47a Merge pull request #3790 from CrowdHailer/genevent-handler-type
removed obsolete type option from the GenEvent handler type
2015-09-28 14:30:48 +02:00
CrowdHailer 74cbda6d8e removed obsolete type option from the GenEvent handler type 2015-09-28 13:20:14 +01:00
José Valim 06c4786ee0 Merge pull request #3789 from CrowdHailer/config-docs
Configuration file now supported
2015-09-28 14:14:58 +02:00
Peter Saxton 266a51510c Configuration file now supported
I was confused by that section as mix does have a config.exs file and wondered if it was outdated documentation.
2015-09-28 13:09:50 +01:00
José Valim 323a51d868 Merge pull request #3787 from eksperimental/range_periods
Remove surrounding spaces in range example
2015-09-28 09:51:26 +02:00
José Valim fdbaeb0b5f Wrap docs 2015-09-28 09:51:06 +02:00
José Valim ca89db448f Check Range creation at runtime 2015-09-28 09:49:31 +02:00
José Valim 9f2b778ddc Merge pull request #3779 from deansc/external_links_in_summary
Remove external links in summary and update docs for Port.
2015-09-28 09:47:09 +02:00
eksperimental f782eced38 Remove surrounding spaces in range example 2015-09-28 09:39:48 +07:00
Deans Charbal cd36df6e3d Remove external links in summary and update docs for Port. 2015-09-27 17:15:47 -04:00
José Valim 77f2558cf5 Merge pull request #3786 from lexmag/no-spaces-range
Use same form for ranges in error messages what we use for `inspect/2`
2015-09-27 22:57:48 +02:00
José Valim 8f869ad471 Merge pull request #3784 from eksperimental/fix_macro_to_string_test
Macro.to_string/2 : add test
2015-09-27 22:57:04 +02:00
José Valim e586fd89cc Merge pull request #3785 from lexmag/key-error-always
Сurrently `Kernel.struct!/2` raises only `KeyError`
2015-09-27 22:30:02 +02:00
Aleksei Magusev 262edd2c25 Use same form for ranges in error messages what we use for inspect/2 2015-09-27 22:25:53 +02:00
Aleksei Magusev 99f0ed1915 Сurrently Kernel.struct!/2 raises only KeyError 2015-09-27 22:13:37 +02:00
José Valim 31d6fbf3b7 Merge pull request #3783 from eksperimental/fix_range_integer2
Fix Range validation
2015-09-27 18:04:56 +02:00
José Valim 835f8b974d Merge pull request #3782 from wsmoak/patch-1
Clarify instructions for building the docs
2015-09-27 18:03:17 +02:00
eksperimental 84c2d76c05 Macro.to_string/2 : add test 2015-09-27 22:58:14 +07:00
eksperimental 31a30afafd Fix Range validation
- checks for lists when defining range
- replace (left .. right) with (first .. last) in error messages
  to use the same terminology used in the function definition
- add guards to Range.new/2
- add specs to Range.range?/1
- test added to check for lists in range.
2015-09-27 22:46:18 +07:00
José Valim 1512b21e60 Merge pull request #3780 from eksperimental/fix_macro_to_string
Fix Macro.to_string/2 to print ranges
2015-09-27 17:42:08 +02:00
Wendy Smoak 03a7e43372 Clarify instructions for building the docs 2015-09-27 09:50:36 -04:00
eksperimental 0ce1c441d5 Fix Macro.to_string/2 to print ranges
this removes the spaces between the ranges and the periods
2015-09-27 18:53:05 +07:00
José Valim 7ba752e038 Merge pull request #3772 from antipax/fix-assertion-errors-with-more-than-1-pinned-var
Fix assertion errors with more than 1 pinned var
2015-09-26 18:23:26 +02:00
José Valim 6f5ca9b4f5 Update docs and CHANGELOG 2015-09-26 18:22:40 +02:00
José Valim b8c664c739 Merge pull request #3767 from endersstocker/add-string-is-equivalent
Add String.equivalent?/2 to test canonical Unicode equivalence
2015-09-26 17:55:19 +02:00
Bryan Endersstocker 945f8b6466 Optimize Unicode normalization
Also extracted String.Normalizer module from String.Unicode.
2015-09-26 10:59:04 -04:00
Eric Entin b206ce368f Fix assertion errors with more than 1 pinned var 2015-09-26 10:50:55 -04:00
José Valim 0eaa86c230 Merge pull request #3777 from eksperimental/update_readme
Update README.md
2015-09-26 16:43:13 +02:00
José Valim 1065858043 Merge pull request #3776 from eksperimental/docs_license
Add text Files to Docs.zip
2015-09-26 16:32:28 +02:00
eksperimental 0f7da65f87 Update README.md
* update license new license (EPL removed)
* format lines to be 72 char long
* update erlang requirements
2015-09-26 21:25:52 +07:00
eksperimental b8d57b4bb9 Add text Files to Docs.zip 2015-09-26 21:12:23 +07:00
José Valim 6a6ac511a1 Merge pull request #3774 from eksperimental/readme
License: Use NOTICE (remove LEGAL)
2015-09-26 14:35:30 +02:00
eksperimental 6967a41472 License: Use NOTICE (remove LEGAL) 2015-09-26 18:54:32 +07:00
José Valim 77651cdd30 Merge pull request #3773 from eksperimental/synopsis_remove_semicolon
Remove trailing semicolong from summaries
2015-09-26 13:01:35 +02:00
eksperimental 369c252b1a Remove trailing semicolong from summaries 2015-09-26 17:36:11 +07:00
José Valim 2f4d225a76 Merge pull request #3770 from eksperimental/summaries
Improve Summaries
2015-09-26 11:26:36 +02:00
eksperimental 1ca942b29a Improve Summaries
It splits the long summaries
and other fixes
2015-09-26 13:25:53 +07:00
Bryan Endersstocker bf02047280 Add String.normalize/2 2015-09-26 00:12:44 -04:00
Bryan Enders dab2a632b6 Rename String.is_equivalent/2 to String.equivalent?/2 2015-09-25 15:01:29 -04:00
Gustaf Andersson 7bec2e1d08 New config, :warn_test_pattern
Display warning for files matching :warn_test_pattern that are not
matching :test_pattern. Default config,"*_test.ex".
2015-09-25 20:43:41 +02:00
José Valim e3560a8763 Bring unused warnings into tests 2015-09-25 19:42:56 +02:00
Bryan Enders f83cc7de5f Add String.is_equivalent/2 to test canonical Unicode equivalence 2015-09-25 13:35:16 -04:00
José Valim deba783731 Bring Access.Map temporarily back 2015-09-25 14:38:24 +02:00
José Valim 511cb5d3a5 Streamline more code specific to Erlang 18 2015-09-25 12:59:47 +02:00
José Valim 4254b1f901 Add missing NOTICE file 2015-09-25 12:46:22 +02:00
José Valim ff51926fe8 Move away from HashDict and HashSet 2015-09-25 12:46:22 +02:00
José Valim 8b9d270817 Rely on Erlang's Apache 2 license 2015-09-25 12:46:22 +02:00
José Valim b115f817e6 Merge pull request #3655 from larrylv/fix_update_map_non_exisitent_key_doc
Update a map for non existent key will raise a KeyError, not ArgumentError
2015-09-25 12:02:43 +02:00
José Valim 0e5804fdbf Merge pull request #3666 from lexmag/import-many-friends
Introduce support for Mod.{A, B, C} on imports, alias, require and use
2015-09-25 12:02:30 +02:00
José Valim 3bd0528ba5 Merge pull request #3738 from lexmag/raising-struct
Add raising on unknown keys version of `Kernel.struct/2`
2015-09-25 12:02:01 +02:00
José Valim f524f25c58 Require Erlang 18.0 forward 2015-09-25 12:01:10 +02:00
Aleksei Magusev 8a657eb4b2 Add raising on unknown keys version of Kernel.struct/2 2015-09-25 11:57:30 +02:00
Aleksei Magusev 8c57c91103 Consider multi-alias call on use 2015-09-25 11:53:54 +02:00
Aleksei Magusev e57db36501 Consider multi-alias call on import, alias, require 2015-09-25 11:53:54 +02:00
Aleksei Magusev a5a29dc47b Add support for x.{y, z, w} expression 2015-09-25 11:53:54 +02:00
Aleksei Magusev a54d9034f1 Add test for use macro 2015-09-25 11:53:54 +02:00
José Valim 5edc9e16f0 Start v1.2.0-dev 2015-09-25 11:03:30 +02:00
Larry Lv 5a06315429 Update a map for non existent key will raise a KeyError, not ArgumentError
```
iex(7)> map = %{:a => 1, 2 => :b}
%{2 => :b, :a => 1}
iex(8)> %{map | :c => 3}
** (KeyError) key :c not found in: %{2 => :b, :a => 1}
```
2015-09-02 21:33:52 +08:00
Tony Han bddb8ae9f7 eex engine raises on missing assigns 2015-06-11 10:30:36 +08:00
313 changed files with 14666 additions and 6704 deletions
-4
View File
@@ -1,10 +1,6 @@
language: erlang
otp_release:
- 17.0
- 17.3
- 17.4
- 17.5
- 18.0
sudo: false
+168 -157
View File
@@ -1,201 +1,212 @@
# Changelog for Elixir v1.1
# Changelog for Elixir v1.2
v1.1 brings enhancements, bug fixes, performance improvements and more
into Elixir.
v1.2 brings enhancements, bug fixes, performance improvements and more
into Elixir. Elixir v1.2 relies on many features in Erlang 18, requiring
at least Erlang 18+. Upgrading to Erlang 18 is therefore necessary before
upgrading Elixir.
Elixir v1.1 supports both Erlang 17 and Erlang 18. This is, however, the
last release supporting Erlang 17, so upgrading to Erlang 18 is advised.
Elixir v1.2 will introduce features that are Erlang 18 only.
## Erlang 18 support
On the enhancements side, the most notable changes are the new functions
added to `Enum`, `Dict` and `Task` modules, and a new datatype called `MapSet`.
`MapSet` implements the `Set` API on top of a map and, for Elixir v1.1,
it is useful for holding only dozens of entries. Future Elixir versions,
however, will be able to rely on `MapSet` from dozens of keys up to
millions, with better performance than `HashSet`.
We have brought many features specific to Erlang 18. Here are the highlights:
On the deprecation side, this release includes one major deprecation and
some soft deprecations.
* Maps can now scale from dozens to millions of keys. Therefore, usage of
the modules `Dict` and `HashDict` is now discouraged and will be
deprecated in future releases, instead use `Map`. Similarly, `Set` and
`HashSet` will be deprecated in favor of `MapSet`
* Compilation times are faster due to improvements in both the Elixir and
Erlang compilers
* Dialyzer now emits less false negative warnings thanks to new annotations
available in the Erlang compiler
The major deprecation relates to the Access protocol. Due to performance
issues, the access syntax `opts[key]` will no longer be powered by the
`Access` protocol, instead, it will use a subset of the `Dict` module.
Therefore this release will emit warnings if you attempt to implement
the `Access` protocol. Note the `Access` module and the `opts[key]`
syntax are not affected and they are not deprecated, only the underlying
protocol dispatch.
## Language improvements
The soft deprecations are minor and they won't emit warnings. It simply
means the documentation has been updated to mention the new best
practices. Warnings will be emitted in the future though (when they are
promoted to deprecations).
This release includes four notable language improvements:
On the tooling side, ExUnit got the ability to skip tests and a couple
new configuration options. Mix got improved warnings and error messages,
faster compilation times and the brand new `mix profile.fprof` task.
* The addition of multi aliases/imports/require:
Mix now also ships with `local.public_keys` to safely manage the
installation of Hex and Rebar alongside the ability to checksum
archive installs. By default, Elixir will always ship with a valid
public key and this feature should work transparently for users.
alias MyApp.{Foo, Bar, Baz}
Finally, we have added a `CODE_OF_CONDUCT.md` file to our repository.
If you haven't read it yet, please do it. We are here to help!
* Support for variables in map keys:
Note: Erlang 17.1 contains a regression in its wildcard implementation that
causes tools like rebar to fail. If you have a project with rebar dependencies
and is using Erlang 17.1, remember to update to at least Erlang 17.3.
%{key => value}
## v1.1.0 (2015-09-25)
* Support for the pin operator in map keys and function clauses:
%{^key => value} = %{key => value}
fn ^key -> :ok end
* Addition of the `with` special form to match on multiple expressions:
with {:ok, contents} <- File.read("my_file.ex"),
{res, binding} <- Code.eval_string(contents),
do: {:ok, res}
These improvements aim to make the language more consistent and expressive.
## Getting started experience
While we were improving the language, we also improved both the parser and
compiler to be even more aware of language constructs, emitting warnings
on common pitfalls like when piping to expressions without parentheses or
when defining unsafe variables.
We have also introduced the `i/1` helper in IEx, which allows developers
to retrieve information about any data type. This will help newcomers
explore the language values while providing experienced developers with
crucial information about the value they are introspecting.
## Workflow improvements
Umbrella applications are now able to share both build and configuration files.
This aims to drastically reduce compilation times in umbrella projects by
adding the following configuration to each umbrella app's `mix.exs` file:
build_path: "../../_build",
config_path: "../../config/config.exs",
Finally, Mix will now consolidate protocols by default as we are now able to
consolidate in parallel and cache the consolidation results, providing the
best performance across all environments without affecting compilation times.
The only downside of this change is that, if you have been implementing
protocols exclusively as part of your test suite, inside the `test` directory,
those won't be picked up as it happens after compilation. For such cases,
consolidation can be disabled by setting `consolidate_protocols: false` in
the project config.
These are great additions on top of the faster compilation times we have
achieved when migrating to Erlang 18.
## Rebar 3 support
With Rebar 3 gaining more adoption in the Erlang community, Mix is
now able to fetch and compile Rebar 3 dependencies. This feature is currently
experimental and therefore opt-in: if you have a Rebar 3 dependency, you can
ask Mix to use Rebar 3 to compile it by passing the `manager: :rebar3` option.
Once configured, Mix will prompt you to install Rebar 3 if it is not yet
available.
## v1.2.1 (2016-01-14)
### 1. Enhancements
* [IEx] Support remote pids/ports with IEx helper `i/1`
* [Protocol] Warn when `defimpl` is called for a consolidated protocol
### 2. Bug fixes
* [ExUnit] Ensure `assert` macros can be used from quoted code
* [ExUnit] Do not warn in match assertion if variable is reused in pattern
* [Macro] Fix a bug in `Macro.to_string/1` where a remote function could be accidentally interpreted as a sigil
* [Mix] Ensure dependencies are properly skipped when `--only` option is given to `mix deps.get`
## v1.2.0 (2016-01-01)
### 1. Enhancements
#### Elixir
* [Application] Add `Application.fetch_env!/2`, `Application.loaded_applications/0` and `Application.started_applications/0`
* [CLI] Add support for `--werl` in Windows bash-like shells
* [Dict] Add `Dict.get_and_update/3` which behaves similar to the now deprecated Access protocol
* [Dict] Add `Dict.get_lazy/3`, `Dict.pop_lazy/3` and `Dict.put_new_lazy/3`
* [Enum] Add `Enum.random/1`, `Enum.take_random/2`, `Enum.min_max/1`, `Enum.min_max_by/2`, `Enum.reverse_slice/3`, `Enum.reduce_while/3`, `Enum.dedup/1` and `Enum.dedup_by/2`
* [Enum] Inline common map usage in `Enum` functions for performance
* [File] Add `File.lstat/1` and `File.lstat/1` that works like `File.stat/1` but is able to return symlink information (i.e. it does not traverse symlinks)
* [File] Add `File.rename/2`
* [Integer] Add `Integer.digits/2` and `Integer.undigits/2`
* [Inspect] Add the `:safe` option to `inspect/2` and make it safe by default, meaning failures while inspecting won't trigger other failures. Instead, it will be wrapped in an exception which is properly formatted
* [IO] Support fenced code blocks on `IO.ANSI.Docs`
* [GenServer] Add `GenServer.whereis/1` that expands `GenServer` dispatches into a proper pid
* [Kernel] No longer include `:crypto` and `:syntax_tools` as dependencies. The former is only needed if you have encrypted debug info (therefore you can add `:crypto` as a dependency manually) and the latter is no longer used
* [Kernel] Raise when `var.Alias` syntax is used and it does not expand to an atom at compile time (previously it emitted warnings)
* [Kernel] Improve generation of argument names for function signatures
* [Kernel] `::/2` is now a special form
* [Kernel] Warn when a variable with underscore is used
* [Kernel] Allow underscores in binary, octal and hex literals
* [Kernel] Warn when module attributes, variables, strings and numbers are used in code but the expression has no effect
* [Kernel] Support `\uXXXX` and `\u{X*}` in strings and char lists to map to Unicode codepoints
* [List] Add `List.keytake/3`
* [Module] Improve name inference for function signatures in documentation metadata
* [Process] Add `Process.hibernate/3`
* [Process] Allow a list of specs in `Process.info/2`
* [Set] Introduce `MapSet` data type. This new data type uses maps behind the scenes and is useful for storing a dozens of items in Erlang 17. In future versions when maps efficiently support large collections, it is meant to be the main Set abstraction in Elixir
* [Stream] Add `Stream.dedup/1`, `Stream.dedup_by/2` and `Stream.transform/4`
* [String] Support calculation of the jaro distance between strings (usually names) via `String.jaro_distance/2`. This is used by Mix to support "Did you mean?" feature when a task does not exist
* [String] Add `String.splitter/3` that splits strings as a stream
* [StringIO] `StringIO.flush/1` was added to flush the output of a StringIO device
* [Task] Introduce `Task.yield/2` and `Task.shutdown/2` to check if a task is still executing and shutdown otherwise
* [Tuple] Add `Tuple.append/2`
* [URI] Default ports were added for "ws" and "wss" schemas
* [URI] Add `URI.to_string/1`
#### EEx
* [EEx] Add `:trim` option to EEx that automatically trims the left side of `<%` and right side `%>` if only spaces and new lines preceed/follow them
* [Application] Add `spec/1` and `spec/2` to retrieve application specification
* [Application] Add `get_application/1` to retrieve the application a given module belongs to
* [Base] Optimize encode and decode operations about 10 times
* [Enum] Use the faster and auto-seeding `:rand` instead of `:random` in `Enum.shuffle/1` and `Enum.random/1` and `Enum.take_random/2`
* [Enum] Add `Enum.with_index/2`
* [GenServer] Add `GenServer.stop/1` for shutting down servers reliably
* [IO] Add `color` related functions to `IO.ANSI`
* [Kernel] Support multiple aliases in `alias`, `import`, `require` and `use`. For example, `alias MyApp.{Foo, Bar, Baz}`
* [Kernel] Add `struct!/2`. Similar to `struct/2` but raises on invalid keys
* [Kernel] Warn if `@doc/@typedoc/@moduledoc` attributes are redefined
* [Kernel] Warn if non-variables are used in `defdelegate/2` (as they have no effect)
* [Kernel] Mark quoted expressions as generated, avoiding false positives on dialyzer
* [Kernel] Allow variables as map keys on creation `%{key => value}` and on matches `%{^key => value}`
* [Kernel] Allow the pin operator `^` in `fn` clauses and on the left side of `<-` in `for` comprehensions
* [Kernel] Introduce `with` as a special form that allows matching on right side parameters
* [Kernel] Warn when right hand side of `->` does not provide any expression
* [Kernel] Warn if the Elixir was compiled with a different endianness than the one currently available at runtime
* [Kernel] Warn if a variable is used after being defined exclusively in a nested context
* [Kernel] Warn if piping into an expression without parentheses
* [Macro] Add `Macro.traverse/4` that performs pre and post-walk at once
* [Macro] Add `Macro.camelize/1` and `Macro.underscore/1`
* [Process] Add `Process.get_keys/0`
* [Stream] Add `Stream.with_index/2`
* [String] Introduce `String.replace_{prefix,suffix,leading,trailing}/2`. The first two will replace only the first occurrence of the given match in string. The last two will replace all occurrences of the given match
* [String] Support `String.normalize/2` and `String.equivalent?/2` that perform NFD and NFC normalization
* [System] Add `System.time_offset`, `System.monotonic_time`, `System.system_time`, `System.convert_time_unit` and `System.unique_integer`
* [System] Allow `System.cmd/3` to remove variables by specifying nil values
* [Task] Add `Task.Supervisor.async_nolink/1/3` that spawns a supervised task without linking to the caller process
* [Task] Introduce `Task.yield_many/2`
* [Task] Raise an error when a task is queried from a non-owning process (instead of waiting forever)
#### ExUnit
* [ExUnit] Add number of skipped tests to `ExUnit` output
* [ExUnit] Make timeout configurable for the whole test suite via the `:timeout` configuration
* [ExUnit] Allow moduledoc to be filtered/skipped in doctests
* [ExUnit] Provide built-in log capturing functionality
* [ExUnit] Allow `assert_receive_timeout` and `refute_receive_timeout` to be configured in the ExUnit application
* [ExUnit] Allow tests to be skipped with `@tag :skip` or `@tag skip: "reason"`
* [ExUnit] Add tests without implementation (missing the do block) which automatically fail. Such tests are also automatically tagged as `:not_implemented`, allowing them to be skipped
* [ExUnit] Increase by default stacktrace depth to 20 (this value is also configurable)
* [ExUnit] Improve formatting on `assert_raise` errors for message mismatch
* [ExUnit] Improve formatting on `assert_receive` when using pinned variables
* [ExUnit] Allow one test to raise multiple errors. The goal is to enable tools in the ecosystem to emit multiple failure reports from the same test
* [ExUnit] Support `@tag report: [:foo, :bar]` which will include the values for tags `:foo` and `:bar` whenever a test fails
#### IEx
* [IEx] Support `IEx.pry` with `--remsh` for remote debugging
* [IEx] Add `b/1` helper that shows documentation for behaviour modules and its callback functions
* [IEx] Provide tab completion for aliases and allow aliases like `Foo.Bar.Baz` to autocomplete even if `Foo.Bar` is not defined
* [IEx] Provide a `pid/3` helper for buildings pids from numbers
* [IEx] Allow `IEX_WITH_WERL` to be set on Windows to always run on WERL mode
* [IEx] Display type docs for `t(Module.type)` and `t(Module.type/arity)`
* [IEx] Add `i/1` helper that prints information about any data type
* [IEx] Show source code snippet whenever there is a request to pry a given process
#### Logger
* [Logger] Support printing pids and refs in Logger metadata
* [Logger] Allow Logger metadata to be removed from pdict by setting it to `nil`
* [Logger] Add application configuration `translator_inspect_opts` for logger to customize how state and message are formatted when translating OTP errors and reports
* [Logger] Automatically include the current application in metadata when compiled via Mix
* [Logger] Add file to logger metadata
#### Mix
* [Mix] Check Elixir version right after archive installation and provide feedback if there is a mismatch
* [Mix] Allow rebar dependencies with `mix.exs` to be compiled with Mix
* [Mix] Allow rebar dependencies to be specified via `:path`
* [Mix] Also consider subdirectories in `config` directory for `Mix.Project.config_files/0`
* [Mix] Allow dynamic configuration in Mix projects by storing config in an agent
* [Mix] Support rebar3 style Git refs in `rebar.config` files
* [Mix] Only recompile compile time dependencies in mix projects. This should considerably speed up recompilation times in Elixir projects
* [Mix] Warn when configuring an application that is not available
* [Mix] Add `mix profile.fprof` for easy code profiling
* [Mix] Abort when dependencies have conflicting `:only` definitions
* [Mix] Fully recompile projects if Elixir or SCM changes
* [Mix] Allow checksum to be checked on archive install via `--sha512` option
* [Mix] Add `mix local.public_keys` to safely manage installation of Hex and Rebar dependencies
* [Mix] Cache and always consolidate protocols
* [Mix] Add `warn_test_pattern` to `mix test` that will warn on potentially misconfigured test files
* [Mix] Introduce `MIX_QUIET` environment variable that configures the underlying Mix task to output only error messages
* [Mix] Introduce `MIX_DEBUG` environment variable that prints information about the task being run
* [Mix] Validate git options and warn on conflicting ref, branch or tags
* [Mix] New umbrella applications will now share configuration and build files
* [Mix] Add experimental support for Rebar 3
* [Mix] Do not warn when an optional dependency has a conflicting `:only` option with another dependency
* [Mix] Raise readable error message when parsertools is not available
* [Mix] Add `--build` flag to `mix deps.clean DEP` to only remove artifacts from `_build`
### 2. Bug fixes
#### Elixir
#### Kernel
* [CLI] Ensure Logger messages are flushed when executing commands
* [Code] `:delegate_locals_to` failed to delegate to the chosen module in many situations and messed up stacktraces. This option has therefore been replaced by imports
* [Code] Store the documentation line in the metadata returned by `Code.get_docs/2`
* [Exception] Do not fail when calculating an exception message, even if the message is invalid
* [File] Ensure `File.touch/2` and `File.stat/2` receive and return universal times. Previously they would work with local times which are not monotonically increasing, which could present issues on scripts. If the times are being shown to the user, `time: :local` can be given as argument
* [Float] Support complete scientific notation in `Float.parse/1`
* [Kernel] Do not expand `in/2` argument in module body
* [Kernel] Throw syntax error for undefind atom/alias syntax `:foo.Bar`
* [Kernel] Improve error message when we can't compile because the target directory is not writeable
* [Kernel] Allow capture of non-symbolic operators like `&and/2`, `&not/1` and others
* [Kernel] Raise if heredoc terminal is accidentally found in the middle of a line without escaping
* [Kernel] Don't warn on missing imports if nothing was imported
* [Kernel] Raise if arity bigger than 255 is given to capture operator
* [Macro] Properly convert captures in `Macro.to_string/1`
* [Module] Do not accept non-Elixir module names in `Module.split/1`
* [Protocol] Guarantee that derived protocols go through `Any` instead of `Map`
* [Range] Restrict ranges to integers to fix diverse bugs of values being included in the range when they should not (false positives)
* [Regex] Fix splitting of empty strings with regexes when trim is set to `true`. Now both `String.split/3` and `Regex.split/3` return an empty list when called with an empty string and trim is enabled
* [Regex] Fix `Regex.replace/4` so it doesn't discard escape characters
* [Stream] Ensure suspending `Stream.flat_map/2` and `Stream.transform/3` does not consume unecessary items from the given enumerable
* [Access] Improve error messages when using Access on non-valid key-value structures
* [Kernel] Raise when conflicting `:only` and `:except` are given to import
* [Kernel] Change `__ENV__.file` if `@file` is set for the given function
* [Kernel] Make `Kernel.ParallelRequire` aware of `:warning_as_errors`
* [Kernel] Improve error message for invalid `do`/`do:`
* [Macro] Ensure `Macro.to_string/2` respects operator precedence when using the access operator
* [Path] Do not crash when expanding paths that go beyond the root, for example, `Path.expand("/../..")`
* [String] Ensure `UnicodeConversionError` does not contain invalid string in its error message
#### EEx
#### IEx
* [EEx] Allow EEx interpolation to also apply inside quotations `<%%= ... %>`
#### ExUnit
* [ExUnit] Skipped tests now correctly count towards the total of tests in the result returned by `ExUnit.run/0`
* [ExUnit] Fix a bug where failures when inspecting data structure or retrieving error messages could bring the whole ExUnit runner down
* [ExUnit] Do not change the semantics of evaluated code with `assert`/`refute`. For example, from now on, `assert nil = some_expr()` will now raise as expected, as the expression still evaluates to a falsy value
* [ExUnit] Report proper line number for doctest failures
#### Logger
* [Logger] Include metadata in `Logger.log/3`, use `Logger.bare_log/3` for runtime-only, with no metadata behaviour
* [IEx] Do not start apps on `recompile` helper if `--no-start` was given
* [IEx] Avoid copying of data when evaluating every expression in IEx
#### Mix
* [Mix] Use the safer `https` protocol instead of `git` for `:github` dependencies
* [Mix] Ensure automatic protocol consolidation via `:consolidate_protocols` is triggered in umbrella apps
* [Mix] Do not raise if wildcard given to `import_config` does not match any file
* [Mix] Applications with `:build_embedded` set to true require explicit compilation step
* [Mix] Also remove consolidated protocols on `mix clean`
* [Mix] Ensure `--exclude` in `mix test` concatenates with test helper excludes
* [Mix] Always run non-recursive tasks at the umbrella root
* [Mix] Ensure rebar projects work on directory names that contain non-latin characters
* [Mix] Ignore directories inside `apps` in umbrellas that do not have a `mix.exs` file
* [Mix] Ensure Mix can be used with path dependencies where the app name is different than the path basename
* [Mix] Ensure dependencies won't crash when updating from a git repository to a hex repository and the git version did not respect SemVer
* [Mix] Do not run remote converger if dependencies have diverged
* [Mix] Ensure umbrella dependencies across all environments are loaded on parent deps.get/deps.update
#### ExUnit
* [ExUnit] Include file and line in all compilation errors for doctests
### 3. Soft deprecations (no warnings emitted)
#### Elixir
#### Kernel
* [Behaviour] The module `Behaviour` is deprecated. Instead of `defcallback`, one can simply use `@callback`. Instead of `defmacrocallback`, one can simply use `@macrocallback`
* [Enum] `Enum.uniq/2` is deprecated in favor of `Enum.uniq_by/2`
* [Kernel] `\x` inside strings and charlists is deprecated in favor of `\uXXXX` and `\u{X*}`. The values emitted by `\x` are unfortunately wrong (they should be bytes but currently it emits codepoints). `\u` is meant to correctly map to codepoints and `\x` will be fixed in the future to map to bytes
* [Regex] Ungreedy option `r` is deprecated in favor of `U` (which is standard in regular expressions in other languages)
* [Dict] `Dict` and `HashDict` are soft deprecated in favor of `Map`
* [Keyword] `Keyword.size/1` is deprecated in favor of `length/1`
* [Map] `Map.size/1` is deprecated in favor of `map_size/1`
* [Set] `Set` and `HashSet` are soft deprecated in favor of `MapSet`
### 4. Deprecations
#### Mix
#### Elixir
* [Mix] `Mix.Utils.camelize/1` and `Mix.Utils.underscore/1` are soft deprecated in favor of `Macro.camelize/1` and `Macro.underscore/1`
* [Access] Implementing the Access protocol is deprecated. The Access protocol relies on the code server in development and test mode (when protocol consolidation is not applied) and it generated a bottleneck when working with multiple processes and the Access protocol was invoked hundreds of times (which is not uncommon). Note the `Access` module and the `opts[key]` syntax are not affected and they are not deprecated, only the underlying protocol dispatch
* [Kernel] `?\xHEX` is deprecated in favor of `0xHEX`. There is no situation where the former should be used in favor of the latter and the latter is always cleaner
* [Kernel] Giving `as: true | false` to `alias/2` and `require/2` have been deprecated (it was undocumented behaviour)
* [String] Passing an empty string to `starts_with?`, `contains?` and `ends_with?` had dubious behaviour and have been deprecated to help developers identify possible bugs in their source code
-9
View File
@@ -1,9 +0,0 @@
LEGAL NOTICE INFORMATION
------------------------
All the files in this distribution are copyright (c) 2012 Plataformatec
covered under Elixir's license (see the file LICENSE) except the file
mentioned below that contains sections that are copyright
(c) 1996 Ericsson AB under Erlang's License (EPL):
lib/elixir/src/elixir_parser.erl (generated by build scripts)
+11 -27
View File
@@ -1,6 +1,6 @@
REBAR ?= "$(CURDIR)/rebar"
PREFIX ?= /usr/local
DOCS := master
DOCS := v1.2
ELIXIRC := bin/elixirc --verbose --ignore-module-conflict
ERLC := erlc -I lib/elixir/include
ERL := erl -I lib/elixir/include -noshell -pa lib/elixir/ebin
@@ -17,13 +17,11 @@ INSTALL_PROGRAM = $(INSTALL) -m755
#==> Functions
# This check should work for older versions like R16B
# as well as new verions like 17.1 and 18
define CHECK_ERLANG_RELEASE
$(Q) erl -noshell -eval 'io:fwrite("~s", [erlang:system_info(otp_release)])' -s erlang halt | grep -q '^1[789]'; \
if [ $$? != 0 ]; then \
echo "At least Erlang 17.0 is required to build Elixir"; \
exit 1; \
$(Q) erl -noshell -eval 'io:fwrite("~s", [erlang:system_info(otp_release) >= "18"])' -s erlang halt | grep -q '^true'; \
if [ $$? != 0 ]; then \
echo "At least Erlang 18.0 is required to build Elixir"; \
exit 1; \
fi;
endef
@@ -42,7 +40,7 @@ lib/$(1)/ebin/Elixir.$(2).beam: $(wildcard lib/$(1)/lib/*.ex) $(wildcard lib/$(1
@ rm -rf lib/$(1)/ebin
$(Q) cd lib/$(1) && ../../$$(ELIXIRC) "lib/**/*.ex" -o ebin
test_$(1): $(1)
test_$(1): compile $(1)
@ echo "==> $(1) (exunit)"
$(Q) cd lib/$(1) && ../../bin/elixir -r "test/test_helper.exs" -pr "test/**/*_test.exs";
endef
@@ -130,14 +128,14 @@ clean_exbeam:
LOGO_PATH = $(shell test -f ../docs/logo.png && echo "--logo ../docs/logo.png")
SOURCE_REF = $(shell head="$$(git rev-parse HEAD)" tag="$$(git tag --points-at $$head | tail -1)" ; echo "$${tag:-$$head}\c")
COMPILE_DOCS = bin/elixir ../ex_doc/bin/ex_doc "$(1)" "$(VERSION)" "lib/$(2)/ebin" -m "$(3)" -u "https://github.com/elixir-lang/elixir" --source-ref "$(call SOURCE_REF)" $(call LOGO_PATH) -o doc/$(2) -p http://elixir-lang.org/docs.html
COMPILE_DOCS = bin/elixir ../ex_doc/bin/ex_doc "$(1)" "$(VERSION)" "lib/$(2)/ebin" -m "$(3)" -u "https://github.com/elixir-lang/elixir" --source-ref "$(call SOURCE_REF)" $(call LOGO_PATH) -o doc/$(2) -p http://elixir-lang.org/docs.html $(4)
docs: compile ../ex_doc/bin/ex_doc docs_elixir docs_eex docs_mix docs_iex docs_ex_unit docs_logger
docs_elixir: compile ../ex_doc/bin/ex_doc
@ echo "==> ex_doc (elixir)"
$(Q) rm -rf doc/elixir
$(call COMPILE_DOCS,Elixir,elixir,Kernel)
$(call COMPILE_DOCS,Elixir,elixir,Kernel,-e "lib/elixir/pages/Typespecs.md" -e "lib/elixir/pages/Writing Documentation.md")
docs_eex: compile ../ex_doc/bin/ex_doc
@ echo "==> ex_doc (eex)"
@@ -172,12 +170,12 @@ docs_logger: compile ../ex_doc/bin/ex_doc
Docs.zip: docs
rm -rf Docs-v$(VERSION).zip
zip -9 -r Docs-v$(VERSION).zip doc
zip -9 -r Docs-v$(VERSION).zip CHANGELOG.md doc NOTICE LICENSE README.md
@ echo "Docs file created $(CURDIR)/Docs-v$(VERSION).zip"
Precompiled.zip: build_man compile
rm -rf Precompiled-v$(VERSION).zip
zip -9 -r Precompiled-v$(VERSION).zip bin CHANGELOG.md LEGAL lib/*/ebin LICENSE man README.md VERSION
zip -9 -r Precompiled-v$(VERSION).zip bin CHANGELOG.md lib/*/ebin LICENSE man NOTICE README.md VERSION
@ echo "Precompiled file created $(CURDIR)/Precompiled-v$(VERSION).zip"
#==> Publish
@@ -188,16 +186,6 @@ publish_docs: docs
rm -rf ../docs/$(DOCS)/*/
cp -R doc/* ../docs/$(DOCS)
# This task requires aws-cli to be installed and set up for access to s3.hex.pm
# See: http://docs.aws.amazon.com/cli/latest/userguide/cli-chap-getting-set-up.html
publish_mix: compile
cd lib/mix && MIX_ENV=prod mix escript.build
aws s3 cp lib/mix/mix s3://s3.hex.pm/builds/mix/v$(VERSION)/mix --acl public-read
aws s3 cp lib/mix/mix s3://s3.hex.pm/builds/mix/mix --acl public-read
rm lib/mix/mix
rm -rf lib/mix/_build
#==> Tests tasks
test: test_erlang test_elixir
@@ -215,11 +203,7 @@ $(TEST_EBIN)/%.beam: $(TEST_ERL)/%.erl
$(Q) mkdir -p $(TEST_EBIN)
$(Q) $(ERLC) -o $(TEST_EBIN) $<
test_elixir: test_stdlib test_ex_unit test_logger test_doc_test test_mix test_eex test_iex
test_doc_test: compile
@ echo "==> doctest (exunit)"
$(Q) cd lib/elixir && ../../bin/elixir -r "test/doc_test.exs";
test_elixir: test_stdlib test_ex_unit test_logger test_mix test_eex test_iex
test_stdlib: compile
@ echo "==> elixir (exunit)"
+22
View File
@@ -0,0 +1,22 @@
LEGAL NOTICE INFORMATION
------------------------
All the files in this distribution are copyright (c) 2012 Plataformatec
covered under Elixir's license (see the file LICENSE) except the cases
below.
== lib/elixir/src/elixir_parser.erl (generated by build scripts)
Copyright Ericsson AB 1996-2015
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
+38 -16
View File
@@ -1,12 +1,15 @@
![Elixir](https://github.com/elixir-lang/elixir-lang.github.com/raw/master/images/logo/logo.png)
=========
[![Build Status](https://secure.travis-ci.org/elixir-lang/elixir.svg?branch=master "Build Status")](https://travis-ci.org/elixir-lang/elixir)
[![Build Status](https://secure.travis-ci.org/elixir-lang/elixir.svg?branch=master
"Build Status")](https://travis-ci.org/elixir-lang/elixir)
For more about Elixir, installation and documentation, [check Elixir's website](http://elixir-lang.org/).
For more about Elixir, installation and documentation,
[check Elixir's website](http://elixir-lang.org/).
## Usage
If you want to contribute to Elixir or run it from source, clone this repository to your machine, compile and test it:
If you want to contribute to Elixir or run it from source, clone this
repository to your machine, compile and test it:
```sh
git clone https://github.com/elixir-lang/elixir.git
@@ -14,32 +17,50 @@ cd elixir
make clean test
```
> Note: if you are running on Windows, [this article includes important notes for compiling Elixir from source on Windows](https://github.com/elixir-lang/elixir/wiki/Windows).
> Note: if you are running on Windows,
[this article includes important notes for compiling Elixir from source
on Windows](https://github.com/elixir-lang/elixir/wiki/Windows).
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 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.
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 17.0 or later). You can check your Erlang version by calling `erl` in the command line. You will see some information as follows:
However, if tests fail, it is likely you have an outdated Erlang version
(Elixir requires Erlang 18.0 or later).
You can check your Erlang version by calling `erl` in the command line.
You will see some information as follows:
`Erlang/OTP 17 [erts-6.0] [source-07b8f44] [64-bit] [smp:4:4] [async-threads:10] [hipe] [kernel-poll:false]`
`Erlang/OTP 18 [erts-7.0] [source] [smp:2:2] [async-threads:10] [hipe]
[kernel-poll:false]`
If you have the correct version and tests still fail, feel free to [open an issue][2].
If you have the correct version and tests still fail, feel free to
[open an issue][2].
## Building documentation
Building the documentation requires [ExDoc](https://github.com/elixir-lang/ex_doc) to be installed and built in the same containing folder as Elixir.
Building the documentation requires
[ExDoc](https://github.com/elixir-lang/ex_doc) to be installed and built
alongside Elixir.
```sh
# After cloning and compiling Elixir
# After cloning and compiling Elixir, in its parent directory:
git clone git://github.com/elixir-lang/ex_doc.git
cd ex_doc && ../elixir/bin/mix do deps.get, compile
cd ../elixir && make docs
```
This will produce documentation sets for `elixir`, `mix`, etc., under the `doc` directory.
## Contributing
We appreciate any contribution to Elixir. Check our [CODE_OF_CONDUCT.md](CODE_OF_CONDUCT.md) and [CONTRIBUTING.md](CONTRIBUTING.md) guides for more information. We usually keep a list of features and bugs [in the issue tracker][2].
We appreciate any contribution to Elixir.
Check our [CODE_OF_CONDUCT.md](CODE_OF_CONDUCT.md) and
[CONTRIBUTING.md](CONTRIBUTING.md) guides for more information.
We usually keep a list of features and bugs [in the issue tracker][2].
## Important links
@@ -54,14 +75,15 @@ We appreciate any contribution to Elixir. Check our [CODE_OF_CONDUCT.md](CODE_OF
[2]: https://github.com/elixir-lang/elixir/issues
[3]: https://groups.google.com/group/elixir-lang-talk
[4]: https://groups.google.com/group/elixir-lang-core
[5]: irc://chat.freenode.net/elixir-lang
[6]: http://www.freenode.net/
[5]: https://webchat.freenode.net/?channels=#elixir-lang
[6]: http://www.freenode.net
[7]: http://elixir-lang.org/docs.html
## License
"Elixir" and the Elixir logo are copyright (c) 2012 Plataformatec.
Elixir source code is released under Apache 2 License with some parts under Erlang's license (EPL).
Elixir source code is released under Apache 2 License.
Check [LEGAL](LEGAL) and [LICENSE](LICENSE) files for more information.
Check [NOTICE](NOTICE) and [LICENSE](LICENSE) files for more
information.
+1 -3
View File
@@ -30,12 +30,10 @@ This document simply outlines the release process:
12. Create a new branch "vMAJOR.MINOR"
13. Move docs generation to `docs/vMAJOR.MINOR` and copy them from `docs/stable`
13. Move docs generation to `docs/vMAJOR.MINOR` in Makefile and copy them from `docs/stable` (change index.html accordingly)
14. In master, bump versions, start new CHANGELOG, add `-dev` back and commit "Start vVERSION+1"
15. `make release_docs` and push it to `elixir-lang/docs`
## Places where version is mentioned
* VERSION (make sure there is no newline in this file)
+1 -1
View File
@@ -1 +1 @@
1.1.0
1.2.1
+1 -1
View File
@@ -2,7 +2,7 @@
if [ $# -eq 0 ] || [ "$1" = "--help" ] || [ "$1" = "-h" ]; then
echo "Usage: `basename $0` [options] [.exs file] [data]
-v Prints version and exit
-v Prints version and exits
-e \"command\" Evaluates the given command (*)
-r \"file\" Requires the given files/patterns (*)
-S \"script\"   Finds and executes the given script
+2 -2
View File
@@ -1,4 +1,4 @@
@echo off
@if defined ELIXIR_CLI_ECHO (@echo on) else (@echo off)
setlocal
if ""%1""=="""" goto :documentation
if ""%1""==""--help"" goto :documentation
@@ -9,7 +9,7 @@ goto parseopts
:documentation
echo Usage: %~nx0 [options] [.exs file] [data]
echo.
echo -v Prints version and exit
echo -v Prints version and exits
echo -e command Evaluates the given command (*)
echo -r file Requires the given files/patterns (*)
echo -S script Finds and executes the given script
+1 -1
View File
@@ -1,4 +1,4 @@
@echo off
@if defined ELIXIR_CLI_ECHO (@echo on) else (@echo off)
set argc=0
for %%A in (%*) do (
if "%%A"=="--help" goto documentation
+4 -2
View File
@@ -1,2 +1,4 @@
@echo off
call "%~dp0\elixir.bat" +iex --erl "-user Elixir.IEx.CLI" --no-halt %*
@if defined ELIXIR_CLI_ECHO (@echo on) else (@echo off)
@if defined IEX_WITH_WERL (@set __ELIXIR_IEX_FLAGS=--werl) else (set __ELIXIR_IEX_FLAGS=)
call "%~dp0\elixir.bat" +iex --erl "-user Elixir.IEx.CLI" --no-halt %__ELIXIR_IEX_FLAGS% %*
@set __ELIXIR_IEX_FLAGS=
+2 -2
View File
@@ -1,2 +1,2 @@
@echo off
call "%~dp0\elixir.bat" -e Mix.start -e Mix.CLI.main %*
@if defined ELIXIR_CLI_ECHO (@echo on) else (@echo off)
call "%~dp0\elixir.bat" "%~dp0\mix" %*
+3 -3
View File
@@ -9,7 +9,7 @@ end
defmodule EEx do
@moduledoc ~S"""
EEx stands for Embedded Elixir. It allows you to embed
Elixir code inside a string in a robust way:
Elixir code inside a string in a robust way.
iex> EEx.eval_string "foo <%= bar %>", [bar: "baz"]
"foo baz"
@@ -85,9 +85,9 @@ defmodule EEx do
iex> EEx.eval_string "<%= @foo %>", assigns: [foo: 1]
"1"
In other words, `<%= @foo %>` is simply translated to:
In other words, `<%= @foo %>` translates to:
<%= Dict.get assigns, :foo %>
<%= {:ok, v} = Access.fetch(assigns, :foo); v %>
The assigns extension is useful when the number of variables
required by the template is not specified at compilation time.
+20 -2
View File
@@ -53,6 +53,9 @@ defmodule EEx.Engine do
@doc """
Handles assigns in quoted expressions.
A warning will be printed on missing assigns.
Future versions will raise.
This can be added to any custom engine by invoking
`handle_assign/1` with `Macro.prewalk/2`:
@@ -64,13 +67,28 @@ defmodule EEx.Engine do
"""
def handle_assign({:@, meta, [{name, _, atom}]}) when is_atom(name) and is_atom(atom) do
line = meta[:line] || 0
quote line: line, do: Dict.get(var!(assigns), unquote(name))
quote line: line, do: EEx.Engine.fetch_assign!(var!(assigns), unquote(name))
end
def handle_assign(arg) do
arg
end
@doc false
# TODO: raise on 1.3 or 1.4
def fetch_assign!(assigns, key) do
case Access.fetch(assigns, key) do
{:ok, val} ->
val
:error ->
keys = Enum.map(assigns, &elem(&1, 0))
IO.write :stderr, "warning: assign @#{key} not available in eex template. " <>
"Please ensure all assigns are given as options. " <>
"Available assigns: #{inspect keys}\n" <>
Exception.format_stacktrace
nil
end
end
@doc """
The default implementation simply returns the given expression.
"""
+4
View File
@@ -236,6 +236,10 @@ foo
assert_eval expected, string
end
test "respects files" do
assert_eval "sample.ex", "<%= __ENV__.file %>", [], file: "sample.ex"
end
test "properly handle functions" do
expected = """
+89 -44
View File
@@ -1,15 +1,16 @@
defmodule Access do
@moduledoc """
Dictionary-like access to data structures via the `foo[bar]` syntax.
Key-based access to data structures via the `foo[bar]` syntax.
This module also empowers `Kernel`s nested update functions
`Kernel.get_in/2`, `Kernel.put_in/3`, `Kernel.update_in/3` and
`Kernel.get_and_update_in/3`.
Elixir provides two syntaxes for accessing values. `user[:name]`
is used by dynamic structures, like maps and keywords, while
`user.name` is used by structs. The main difference is that
`user[:name]` won't raise if the key `:name` is missing but
`user.name` will raise if there is no `:name` key.
## Examples
## Key-based lookups
Out of the box, Access works with built-in dictionaries: `Keyword`
and `Map`:
Out of the box, Access works with `Keyword` and `Map`:
iex> keywords = [a: 1, b: 2]
iex> keywords[:a]
@@ -23,13 +24,67 @@ defmodule Access do
iex> star_ratings[1.5]
"★☆"
Access can be combined with `Kernel.put_in/3` to put a value
in a given key:
iex> map = %{a: 1, b: 2}
iex> put_in map[:a], 3
%{a: 3, b: 2}
This syntax is very convenient as it can be nested arbitrarily:
iex> users = %{"john" => %{age: 27}, "meg" => %{age: 23}}
iex> put_in users["john"][:age], 28
%{"john" => %{age: 28}, "meg" => %{age: 23}}
Furthermore, Access transparently ignores `nil` values:
iex> keywords = [a: 1, b: 2]
iex> keywords[:c][:unknown]
nil
The key comparison must be implemented using the `===` operator.
Since Access is a behaviour, it can be implemented to key-value
data structures. Access requires the key comparison to be
implemented using the `===` operator.
## Field-based lookups
The Access syntax (`foo[bar]`) cannot be used to access fields in
structs. That's by design, as Access is meant to be used for
dynamic key-value structures, like maps and keywords, and not
by static ones like structs.
However Elixir already provides a field-based lookup for structs.
Imagine a struct named `User` with name and age fields. The
following would raise:
user = %User{name: "john"}
user[:name]
** (UndefinedFunctionError) undefined function User.fetch/2
(User does not implement the Access behaviour)
Structs instead use the `user.name` syntax:
user.name
#=> "john"
The same `user.name` syntax can also be used by `Kernel.put_in/2`
to for updating structs fields:
put_in user.name, "mary"
%User{name: "mary"}
Differently from `user[:name]`, `user.name` cannot be extended by
the developers, and will be always restricted to only maps and
structs.
Summing up:
* `user[:name]` is used by dynamic structures, is extensible and
does not raise on missing keys
* `user.name` is used by static structures, it is not extensible
and it will raise on missing keys
"""
@type t :: list | map | nil
@@ -39,6 +94,20 @@ defmodule Access do
@callback fetch(t, key) :: {:ok, value} | :error
@callback get_and_update(t, key, (value -> {value, value})) :: {value, t}
defmacrop raise_undefined_behaviour(e, struct, top) do
quote do
stacktrace = System.stacktrace
e =
case stacktrace do
[unquote(top)|_] ->
%{unquote(e) | reason: "#{inspect unquote(struct)} does not implement the Access behaviour"}
_ ->
unquote(e)
end
reraise e, stacktrace
end
end
@doc """
Fetches the container's value for the given key.
"""
@@ -47,19 +116,27 @@ defmodule Access do
def fetch(%{__struct__: struct} = container, key) do
struct.fetch(container, key)
rescue
e in UndefinedFunctionError ->
raise_undefined_behaviour e, struct, {^struct, :fetch, [^container, ^key], _}
end
def fetch(%{} = map, key) do
:maps.find(key, map)
end
def fetch(list, key) when is_list(list) do
def fetch(list, key) when is_list(list) and is_atom(key) do
case :lists.keyfind(key, 1, list) do
{^key, value} -> {:ok, value}
false -> :error
end
end
def fetch(list, key) when is_list(list) do
raise ArgumentError,
"the Access calls for keywords expect the key to be an atom, got: " <> inspect(key)
end
def fetch(nil, _key) do
:error
end
@@ -91,6 +168,9 @@ defmodule Access do
def get_and_update(%{__struct__: struct} = container, key, fun) do
struct.get_and_update(container, key, fun)
rescue
e in UndefinedFunctionError ->
raise_undefined_behaviour e, struct, {^struct, :get_and_update, [^container, ^key, ^fun], _}
end
def get_and_update(%{} = map, key, fun) do
@@ -112,38 +192,3 @@ defmodule Access do
"could not put/update key #{inspect key} on a nil value"
end
end
# Callbacks invoked when inlining code for *_in in Kernel.
# TODO: Remove me on 1.2
defmodule Access.Map do
@moduledoc false
def update!(%{} = map, key, fun) do
case :maps.find(key, map) do
{:ok, value} ->
:maps.put(key, fun.(value), map)
:error ->
raise KeyError, key: key, term: map
end
end
def update!(other, key, _fun) do
raise ArgumentError,
"could not put/update key #{inspect key}. Expected map/struct, got: #{inspect other}"
end
def get_and_update!(%{} = map, key, fun) do
case :maps.find(key, map) do
{:ok, value} ->
{get, update} = fun.(value)
{get, :maps.put(key, update, map)}
:error ->
raise KeyError, key: key, term: map
end
end
def get_and_update!(other, key, _fun) do
raise ArgumentError,
"could not put/update key #{inspect key}. Expected map/struct, got: #{inspect other}"
end
end
+22 -9
View File
@@ -17,7 +17,7 @@ defmodule Agent do
defmodule Mix.TasksServer do
def start_link do
Agent.start_link(fn -> HashSet.new end, name: __MODULE__)
Agent.start_link(fn -> MapSet.new end, name: __MODULE__)
end
@doc "Checks if the task has already executed"
@@ -31,13 +31,13 @@ defmodule Agent do
@doc "Marks a task as executed"
def put_task(task, project) do
item = {task, project}
Agent.update(__MODULE__, &Set.put(&1, item))
Agent.update(__MODULE__, &MapSet.put(&1, item))
end
@doc "Resets the executed tasks and return the previous list of tasks"
@doc "Resets the executed tasks and returns the previous list of tasks"
def take_all() do
Agent.get_and_update(__MODULE__, fn set ->
{Enum.into(set, []), HashSet.new}
{Enum.into(set, []), MapSet.new}
end)
end
end
@@ -295,12 +295,25 @@ defmodule Agent do
end
@doc """
Stops the agent.
Stops the agent with the given `reason`.
Returns `:ok` if the agent is stopped within the given `timeout`.
It returns `:ok` if the server terminates with the given
reason, if it terminates with another reason, the call will
exit.
This function keeps OTP semantics regarding error reporting.
If the reason is any other than `:normal`, `:shutdown` or
`{:shutdown, _}`, an error report will be logged.
"""
@spec stop(agent, timeout) :: :ok
def stop(agent, timeout \\ 5000) do
GenServer.call(agent, :stop, timeout)
@spec stop(agent, reason :: term, timeout) :: :ok
def stop(agent, reason \\ :normal, timeout \\ :infinity) do
if is_integer(reason) or reason == :infinity do
IO.write :stderr, "warning: Agent.stop(agent, timeout) is deprecated, " <>
"please use Agent.stop(agent, :normal, timeout) instead\n" <>
Exception.format_stacktrace
:gen.stop(agent, :normal, reason)
else
:gen.stop(agent, reason, timeout)
end
end
end
-15
View File
@@ -23,10 +23,6 @@ defmodule Agent.Server do
{:reply, :ok, run(fun, [state])}
end
def handle_call(:stop, _from, state) do
{:stop, :normal, :ok, state}
end
def handle_call(msg, from, state) do
super(msg, from, state)
end
@@ -43,17 +39,6 @@ defmodule Agent.Server do
{:ok, run(fun, [state])}
end
def terminate(_reason, _state) do
# There is a race condition if the agent is
# restarted too fast and it is registered.
try do
self |> :erlang.process_info(:registered_name) |> elem(1) |> Process.unregister
rescue
_ -> :ok
end
:ok
end
defp initial_call(mfa) do
_ = Process.put(:"$initial_call", get_initial_call(mfa))
:ok
+49 -3
View File
@@ -44,9 +44,6 @@ defmodule Application do
including new values that are not defined in the environment file (although
this should be avoided).
In the future, we plan to support configuration files which allow
developers to configure the environment of their dependencies.
Keep in mind that each application is responsible for its environment.
Do not use the functions in this module for directly accessing or modifying
the environment of other applications (as it may lead to inconsistent
@@ -112,6 +109,55 @@ defmodule Application do
@type value :: term
@type start_type :: :permanent | :transient | :temporary
@application_keys [:description, :id, :vsn, :modules, :maxP, :maxT, :registered,
:included_applications, :applications, :mod, :start_phases]
@doc """
Returns the spec for `app`.
The following keys are returned:
* #{Enum.map_join @application_keys, "\n * ", &inspect/1}
Note the environment is not returned as it can be accessed via
`fetch_env/2`. Returns `nil` if the application is not loaded.
"""
@spec spec(app) :: [{key, value}] | nil
def spec(app) do
case :application.get_all_key(app) do
{:ok, info} -> :lists.keydelete(:env, 1, info)
:undefined -> nil
end
end
@doc """
Returns the value for `key` in `app`'s specification.
See `spec/1` for the supporte keys. If the given
specification parameter does not exist, this function
will raise.
"""
@spec spec(app, key) :: value
def spec(app, key) when key in @application_keys do
{:ok, value} = :application.get_key(app, key)
value
end
@doc """
Get the application for the given module.
The application is located by analyzing the spec
of all loaded applications. Returns `nil` if
the module is not listed in any application spec.
"""
@spec get_application(atom) :: atom | nil
def get_application(module) when is_atom(module) do
case :application.get_application(module) do
{:ok, app} -> app
:undefined -> nil
end
end
@doc """
Returns all key-value pairs for `app`.
"""
+253 -108
View File
@@ -98,43 +98,40 @@ defmodule Base do
b32_alphabet = Enum.with_index 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567'
b32hex_alphabet = Enum.with_index '0123456789ABCDEFGHIJKLMNOPQRSTUV'
Enum.each [ {:enc16, :dec16, b16_alphabet},
{:enc64, :dec64, b64_alphabet},
{:enc32, :dec32, b32_alphabet},
{:enc64url, :dec64url, b64url_alphabet},
{:enc32hex, :dec32hex, b32hex_alphabet} ], fn({enc, dec, alphabet}) ->
Enum.each [{:enc16, :dec16, b16_alphabet},
{:enc32, :dec32, b32_alphabet},
{:enc64, :dec64, b64_alphabet},
{:enc64url, :dec64url, b64url_alphabet},
{:enc32hex, :dec32hex, b32hex_alphabet}], fn({enc, dec, alphabet}) ->
for {encoding, value} <- alphabet do
defp unquote(enc)(unquote(value)), do: unquote(encoding)
defp unquote(dec)(unquote(encoding)), do: unquote(value)
end
defp unquote(dec)(c) do
raise ArgumentError, "non-alphabet digit found: #{<<c>>}"
raise ArgumentError, "non-alphabet digit found: #{inspect <<c>>, binaries: :as_strings} (byte #{c})"
end
end
defp encode_case(:upper, func),
do: func
defp encode_case(:lower, func),
do: &to_lower(func.(&1))
defp decode_case(:upper, func),
do: func
defp decode_case(:lower, func),
do: &func.(from_lower(&1))
defp decode_case(:mixed, func),
do: &func.(from_mixed(&1))
@compile {:inline, from_upper: 1, from_lower: 1, from_mixed: 1,
to_lower: 1, to_upper: 1, enc16: 1, dec16: 1,
enc32: 1, dec32: 1, enc32hex: 1, dec32hex: 1,
enc64: 1, dec64: 1, enc64url: 1, dec64url: 1}
defp to_lower(char) when char in ?A..?Z,
do: char + (?a - ?A)
defp to_lower(char),
do: char
defp to_upper(char), do: char
defp from_upper(char), do: char
defp from_lower(char) when char in ?a..?z,
do: char - (?a - ?A)
defp from_lower(char) when not char in ?A..?Z,
do: char
defp from_lower(char),
do: raise(ArgumentError, "non-alphabet digit found: #{<<char>>}")
do: raise(ArgumentError, "non-alphabet digit found: \"#{<<char>>}\" (byte #{char})")
defp from_mixed(char) when char in ?a..?z,
do: char - (?a - ?A)
@@ -160,10 +157,9 @@ defmodule Base do
@spec encode16(binary, Keyword.t) :: binary
def encode16(data, opts \\ []) when is_binary(data) do
case = Keyword.get(opts, :case, :upper)
do_encode16(data, encode_case(case, &enc16/1))
do_encode16(case, data)
end
@doc """
Decodes a base 16 encoded string into a binary string.
@@ -185,9 +181,8 @@ defmodule Base do
"""
@spec decode16(binary) :: {:ok, binary} | :error
@spec decode16(binary, Keyword.t) :: {:ok, binary} | :error
def decode16(string, opts \\ []) when is_binary(string) do
case = Keyword.get(opts, :case, :upper)
{:ok, do_decode16(string, decode_case(case, &dec16/1))}
def decode16(string, opts \\ []) do
{:ok, decode16!(string, opts)}
rescue
ArgumentError -> :error
end
@@ -216,9 +211,15 @@ defmodule Base do
"""
@spec decode16!(binary) :: binary
@spec decode16!(binary, Keyword.t) :: binary
def decode16!(string, opts \\ []) when is_binary(string) do
def decode16!(string, opts \\ [])
def decode16!(string, opts) when is_binary(string) and rem(byte_size(string), 2) == 0 do
case = Keyword.get(opts, :case, :upper)
do_decode16(string, decode_case(case, &dec16/1))
do_decode16(case, string)
end
def decode16!(string, _opts) when is_binary(string) do
raise ArgumentError, "odd-length string"
end
@doc """
@@ -232,7 +233,7 @@ defmodule Base do
"""
@spec encode64(binary) :: binary
def encode64(data) when is_binary(data) do
do_encode64(data, &enc64/1)
do_encode64(data)
end
@doc """
@@ -246,7 +247,7 @@ defmodule Base do
"""
@spec decode64(binary) :: {:ok, binary} | :error
def decode64(string) when is_binary(string) do
{:ok, do_decode64(string, &dec64/1)}
{:ok, decode64!(string)}
rescue
ArgumentError -> :error
end
@@ -266,8 +267,12 @@ defmodule Base do
"""
@spec decode64!(binary) :: binary
def decode64!(string) when is_binary(string) and rem(byte_size(string), 4) == 0 do
do_decode64(string)
end
def decode64!(string) when is_binary(string) do
do_decode64(string, &dec64/1)
raise ArgumentError, "incorrect padding"
end
@doc """
@@ -282,7 +287,7 @@ defmodule Base do
"""
@spec url_encode64(binary) :: binary
def url_encode64(data) when is_binary(data) do
do_encode64(data, &enc64url/1)
do_encode64url(data)
end
@doc """
@@ -297,7 +302,7 @@ defmodule Base do
"""
@spec url_decode64(binary) :: {:ok, binary} | :error
def url_decode64(string) when is_binary(string) do
{:ok, do_decode64(string, &dec64url/1)}
{:ok, url_decode64!(string)}
rescue
ArgumentError -> :error
end
@@ -316,8 +321,12 @@ defmodule Base do
"""
@spec url_decode64!(binary) :: binary
def url_decode64!(string) when is_binary(string) and rem(byte_size(string), 4) == 0 do
do_decode64url(string)
end
def url_decode64!(string) when is_binary(string) do
do_decode64(string, &dec64url/1)
raise ArgumentError, "incorrect padding"
end
@doc """
@@ -339,7 +348,7 @@ defmodule Base do
@spec encode32(binary, Keyword.t) :: binary
def encode32(data, opts \\ []) when is_binary(data) do
case = Keyword.get(opts, :case, :upper)
do_encode32(data, encode_case(case, &enc32/1))
do_encode32(case, data)
end
@doc """
@@ -364,8 +373,7 @@ defmodule Base do
@spec decode32(binary) :: {:ok, binary} | :error
@spec decode32(binary, Keyword.t) :: {:ok, binary} | :error
def decode32(string, opts \\ []) do
case = Keyword.get(opts, :case, :upper)
{:ok, do_decode32(string, decode_case(case, &dec32/1))}
{:ok, decode32!(string, opts)}
rescue
ArgumentError -> :error
end
@@ -394,9 +402,15 @@ defmodule Base do
"""
@spec decode32!(binary) :: binary
@spec decode32!(binary, Keyword.t) :: binary
def decode32!(string, opts \\ []) do
def decode32!(string, opts \\ [])
def decode32!(string, opts) when is_binary(string) and rem(byte_size(string), 8) == 0 do
case = Keyword.get(opts, :case, :upper)
do_decode32(string, decode_case(case, &dec32/1))
do_decode32(case, string)
end
def decode32!(string, _opts) when is_binary(string) do
raise ArgumentError, "incorrect padding"
end
@doc """
@@ -419,7 +433,7 @@ defmodule Base do
@spec hex_encode32(binary, Keyword.t) :: binary
def hex_encode32(data, opts \\ []) when is_binary(data) do
case = Keyword.get(opts, :case, :upper)
do_encode32(data, encode_case(case, &enc32hex/1))
do_hex_encode32(case, data)
end
@doc """
@@ -444,9 +458,8 @@ defmodule Base do
"""
@spec hex_decode32(binary) :: {:ok, binary} | :error
@spec hex_decode32(binary, Keyword.t) :: {:ok, binary} | :error
def hex_decode32(string, opts \\ []) when is_binary(string) do
case = Keyword.get(opts, :case, :upper)
{:ok, do_decode32(string, decode_case(case, &dec32hex/1))}
def hex_decode32(string, opts \\ []) do
{:ok, hex_decode32!(string, opts)}
rescue
ArgumentError -> :error
end
@@ -476,117 +489,249 @@ defmodule Base do
"""
@spec hex_decode32!(binary) :: binary
@spec hex_decode32!(binary, Keyword.t) :: binary
def hex_decode32!(string, opts \\ []) when is_binary(string) do
def hex_decode32!(string, opts \\ [])
def hex_decode32!(string, opts) when is_binary(string) and rem(byte_size(string), 8) == 0 do
case = Keyword.get(opts, :case, :upper)
do_decode32(string, decode_case(case, &dec32hex/1))
do_hex_decode32(case, string)
end
defp do_encode16(<<>>, _), do: <<>>
defp do_encode16(data, enc) do
for <<c::4 <- data>>, into: <<>>, do: <<enc.(c)::8>>
def hex_decode32!(string, _opts) when is_binary(string) do
raise ArgumentError, "incorrect padding"
end
defp do_decode16(<<>>, _), do: <<>>
defp do_decode16(string, dec) when rem(byte_size(string), 2) == 0 do
for <<c1::8, c2::8 <- string>>, into: <<>> do
<<dec.(c1)::4, dec.(c2)::4>>
defp do_encode16(_, <<>>), do: <<>>
defp do_encode16(:upper, data) do
for <<c::4 <- data>>, into: <<>>, do: <<enc16(c)::8>>
end
defp do_encode16(:lower, data) do
for <<c::4 <- data>>, into: <<>>, do: <<to_lower(enc16(c))::8>>
end
defp do_decode16(_, <<>>), do: <<>>
defp do_decode16(:upper, string) when rem(byte_size(string), 2) == 0 do
for <<c1::8, c2::8 <- string>>, into: <<>> do
<<dec16(c1)::4, dec16(c2)::4>>
end
end
defp do_decode16(_, _) do
raise ArgumentError, "odd-length string"
defp do_decode16(:lower, string) when rem(byte_size(string), 2) == 0 do
for <<c1::8, c2::8 <- string>>, into: <<>> do
<<dec16(from_lower(c1))::4, dec16(from_lower(c2))::4>>
end
end
defp do_decode16(:mixed, string) when rem(byte_size(string), 2) == 0 do
for <<c1::8, c2::8 <- string>>, into: <<>> do
<<dec16(from_mixed(c1))::4, dec16(from_mixed(c2))::4>>
end
end
defp do_encode64(<<>>, _), do: <<>>
defp do_encode64(data, enc) do
defp do_encode64(<<>>), do: <<>>
defp do_encode64(data) do
split = 3 * div(byte_size(data), 3)
<<main::size(split)-binary, rest::binary>> = data
main = for <<c::6 <- main>>, into: <<>>, do: <<enc.(c)::8>>
main = for <<c::6 <- main>>, into: <<>>, do: <<enc64(c)::8>>
case rest do
<<c1::6, c2::6, c3::4>> ->
<<main::binary, enc.(c1)::8, enc.(c2)::8, enc.(bsl(c3, 2))::8, ?=>>
<<main::binary, enc64(c1)::8, enc64(c2)::8, enc64(bsl(c3, 2))::8, ?=>>
<<c1::6, c2::2>> ->
<<main::binary, enc.(c1)::8, enc.(bsl(c2, 4))::8, ?=, ?=>>
<<main::binary, enc64(c1)::8, enc64(bsl(c2, 4))::8, ?=, ?=>>
<<>> ->
main
end
end
defp do_decode64(<<>>, _), do: <<>>
defp do_decode64(string, dec) when rem(byte_size(string), 4) == 0 do
defp do_decode64(<<>>), do: <<>>
defp do_decode64(string) do
split = byte_size(string) - 4
<<main::size(split)-binary, rest::binary>> = string
main = for <<c::8 <- main>>, into: <<>>, do: <<dec.(c)::6>>
main = for <<c::8 <- main>>, into: <<>>, do: <<dec64(c)::6>>
case rest do
<<c1::8, c2::8, ?=, ?=>> ->
<<main::binary, dec.(c1)::6, bsr(dec.(c2), 4)::2>>
<<main::binary, dec64(c1)::6, bsr(dec64(c2), 4)::2>>
<<c1::8, c2::8, c3::8, ?=>> ->
<<main::binary, dec.(c1)::6, dec.(c2)::6, bsr(dec.(c3), 2)::4>>
<<main::binary, dec64(c1)::6, dec64(c2)::6, bsr(dec64(c3), 2)::4>>
<<c1::8, c2::8, c3::8, c4::8>> ->
<<main::binary, dec.(c1)::6, dec.(c2)::6, dec.(c3)::6, dec.(c4)::6>>
<<main::binary, dec64(c1)::6, dec64(c2)::6, dec64(c3)::6, dec64(c4)::6>>
<<>> ->
main
end
end
defp do_decode64(_, _) do
raise ArgumentError, "incorrect padding"
end
defp do_encode32(<<>>, _), do: <<>>
defp do_encode32(data, enc) do
split = 5 * div(byte_size(data), 5)
defp do_encode64url(<<>>), do: <<>>
defp do_encode64url(data) do
split = 3 * div(byte_size(data), 3)
<<main::size(split)-binary, rest::binary>> = data
main = for <<c::5 <- main>>, into: <<>>, do: <<enc.(c)::8>>
main = for <<c::6 <- main>>, into: <<>>, do: <<enc64url(c)::8>>
case rest do
<<c1::5, c2::5, c3::5, c4::5, c5::5, c6::5, c7::2>> ->
<<main::binary,
enc.(c1)::8, enc.(c2)::8, enc.(c3)::8, enc.(c4)::8,
enc.(c5)::8, enc.(c6)::8, enc.(bsl(c7, 3))::8, ?=>>
<<c1::5, c2::5, c3::5, c4::5, c5::4>> ->
<<main::binary,
enc.(c1)::8, enc.(c2)::8, enc.(c3)::8, enc.(c4)::8,
enc.(bsl(c5, 1))::8, ?=, ?=, ?=>>
<<c1::5, c2::5, c3::5, c4::1>> ->
<<main::binary,
enc.(c1)::8, enc.(c2)::8, enc.(c3)::8, enc.(bsl(c4, 4))::8,
?=, ?=, ?=, ?=>>
<<c1::5, c2::3>> ->
<<main::binary,
enc.(c1)::8, enc.(bsl(c2, 2))::8, ?=, ?=,
?=, ?=, ?=, ?=>>
<<c1::6, c2::6, c3::4>> ->
<<main::binary, enc64url(c1)::8, enc64url(c2)::8, enc64url(bsl(c3, 2))::8, ?=>>
<<c1::6, c2::2>> ->
<<main::binary, enc64url(c1)::8, enc64url(bsl(c2, 4))::8, ?=, ?=>>
<<>> ->
main
end
end
defp do_decode32(<<>>, _), do: <<>>
defp do_decode32(string, dec) when rem(byte_size(string), 8) == 0 do
split = byte_size(string) - 8
defp do_decode64url(<<>>), do: <<>>
defp do_decode64url(string) do
split = byte_size(string) - 4
<<main::size(split)-binary, rest::binary>> = string
main = for <<c::8 <- main>>, into: <<>>, do: <<dec.(c)::5>>
main = for <<c::8 <- main>>, into: <<>>, do: <<dec64url(c)::6>>
case rest do
<<c1::8, c2::8, ?=, ?=, ?=, ?=, ?=, ?=>> ->
<<main::binary, dec.(c1)::5, bsr(dec.(c2), 2)::3>>
<<c1::8, c2::8, c3::8, c4::8, ?=, ?=, ?=, ?=>> ->
<<main::binary,
dec.(c1)::5, dec.(c2)::5, dec.(c3)::5, bsr(dec.(c4), 4)::1>>
<<c1::8, c2::8, c3::8, c4::8, c5::8, ?=, ?=, ?=>> ->
<<main::binary,
dec.(c1)::5, dec.(c2)::5, dec.(c3)::5, dec.(c4)::5,
bsr(dec.(c5), 1)::4>>
<<c1::8, c2::8, c3::8, c4::8, c5::8, c6::8, c7::8, ?=>> ->
<<main::binary,
dec.(c1)::5, dec.(c2)::5, dec.(c3)::5, dec.(c4)::5,
dec.(c5)::5, dec.(c6)::5, bsr(dec.(c7), 3)::2>>
<<c1::8, c2::8, c3::8, c4::8, c5::8, c6::8, c7::8, c8::8>> ->
<<main::binary,
dec.(c1)::5, dec.(c2)::5, dec.(c3)::5, dec.(c4)::5,
dec.(c5)::5, dec.(c6)::5, dec.(c7)::5, dec.(c8)::5>>
<<c1::8, c2::8, ?=, ?=>> ->
<<main::binary, dec64url(c1)::6, bsr(dec64url(c2), 4)::2>>
<<c1::8, c2::8, c3::8, ?=>> ->
<<main::binary, dec64url(c1)::6, dec64url(c2)::6, bsr(dec64url(c3), 2)::4>>
<<c1::8, c2::8, c3::8, c4::8>> ->
<<main::binary, dec64url(c1)::6, dec64url(c2)::6, dec64url(c3)::6, dec64url(c4)::6>>
<<>> ->
main
end
end
defp do_decode32(_, _) do
raise ArgumentError, "incorrect padding"
defp do_encode32(_, <<>>), do: <<>>
for {case, fun} <- [upper: :to_upper, lower: :to_lower] do
defp do_encode32(unquote(case), data) do
split = 5 * div(byte_size(data), 5)
<<main::size(split)-binary, rest::binary>> = data
main = for <<c::5 <- main>>, into: <<>>, do: <<unquote(fun)(enc32(c))::8>>
case rest do
<<c1::5, c2::5, c3::5, c4::5, c5::5, c6::5, c7::2>> ->
<<main::binary,
unquote(fun)(enc32(c1))::8, unquote(fun)(enc32(c2))::8,
unquote(fun)(enc32(c3))::8, unquote(fun)(enc32(c4))::8,
unquote(fun)(enc32(c5))::8, unquote(fun)(enc32(c6))::8,
unquote(fun)(enc32(bsl(c7, 3)))::8, ?=>>
<<c1::5, c2::5, c3::5, c4::5, c5::4>> ->
<<main::binary,
unquote(fun)(enc32(c1))::8, unquote(fun)(enc32(c2))::8,
unquote(fun)(enc32(c3))::8, unquote(fun)(enc32(c4))::8,
unquote(fun)(enc32(bsl(c5, 1)))::8, ?=, ?=, ?=>>
<<c1::5, c2::5, c3::5, c4::1>> ->
<<main::binary,
unquote(fun)(enc32(c1))::8, unquote(fun)(enc32(c2))::8,
unquote(fun)(enc32(c3))::8, unquote(fun)(enc32(bsl(c4, 4)))::8,
?=, ?=, ?=, ?=>>
<<c1::5, c2::3>> ->
<<main::binary,
unquote(fun)(enc32(c1))::8, unquote(fun)(enc32(bsl(c2, 2)))::8, ?=, ?=,
?=, ?=, ?=, ?=>>
<<>> ->
main
end
end
end
defp do_decode32(_, <<>>), do: <<>>
for {case, fun} <- [upper: :from_upper, lower: :from_lower, mixed: :from_mixed] do
defp do_decode32(unquote(case), string) do
split = byte_size(string) - 8
<<main::size(split)-binary, rest::binary>> = string
main = for <<c::8 <- main>>, into: <<>>, do: <<dec32(unquote(fun)(c))::5>>
case rest do
<<c1::8, c2::8, ?=, ?=, ?=, ?=, ?=, ?=>> ->
<<main::binary, dec32(unquote(fun)(c1))::5,
bsr(dec32(unquote(fun)(c2)), 2)::3>>
<<c1::8, c2::8, c3::8, c4::8, ?=, ?=, ?=, ?=>> ->
<<main::binary,
dec32(unquote(fun)(c1))::5, dec32(unquote(fun)(c2))::5,
dec32(unquote(fun)(c3))::5, bsr(dec32(unquote(fun)(c4)), 4)::1>>
<<c1::8, c2::8, c3::8, c4::8, c5::8, ?=, ?=, ?=>> ->
<<main::binary,
dec32(unquote(fun)(c1))::5, dec32(unquote(fun)(c2))::5,
dec32(unquote(fun)(c3))::5, dec32(unquote(fun)(c4))::5,
bsr(dec32(unquote(fun)(c5)), 1)::4>>
<<c1::8, c2::8, c3::8, c4::8, c5::8, c6::8, c7::8, ?=>> ->
<<main::binary,
dec32(unquote(fun)(c1))::5, dec32(unquote(fun)(c2))::5,
dec32(unquote(fun)(c3))::5, dec32(unquote(fun)(c4))::5,
dec32(unquote(fun)(c5))::5, dec32(unquote(fun)(c6))::5,
bsr(dec32(unquote(fun)(c7)), 3)::2>>
<<c1::8, c2::8, c3::8, c4::8, c5::8, c6::8, c7::8, c8::8>> ->
<<main::binary,
dec32(unquote(fun)(c1))::5, dec32(unquote(fun)(c2))::5,
dec32(unquote(fun)(c3))::5, dec32(unquote(fun)(c4))::5,
dec32(unquote(fun)(c5))::5, dec32(unquote(fun)(c6))::5,
dec32(unquote(fun)(c7))::5, dec32(unquote(fun)(c8))::5>>
<<>> ->
main
end
end
end
defp do_hex_encode32(_, <<>>), do: <<>>
for {case, fun} <- [upper: :to_upper, lower: :to_lower] do
defp do_hex_encode32(unquote(case), data) do
split = 5 * div(byte_size(data), 5)
<<main::size(split)-binary, rest::binary>> = data
main = for <<c::5 <- main>>, into: <<>>, do: <<unquote(fun)(enc32hex(c))::8>>
case rest do
<<c1::5, c2::5, c3::5, c4::5, c5::5, c6::5, c7::2>> ->
<<main::binary,
unquote(fun)(enc32hex(c1))::8, unquote(fun)(enc32hex(c2))::8,
unquote(fun)(enc32hex(c3))::8, unquote(fun)(enc32hex(c4))::8,
unquote(fun)(enc32hex(c5))::8, unquote(fun)(enc32hex(c6))::8,
unquote(fun)(enc32hex(bsl(c7, 3)))::8, ?=>>
<<c1::5, c2::5, c3::5, c4::5, c5::4>> ->
<<main::binary,
unquote(fun)(enc32hex(c1))::8, unquote(fun)(enc32hex(c2))::8,
unquote(fun)(enc32hex(c3))::8, unquote(fun)(enc32hex(c4))::8,
unquote(fun)(enc32hex(bsl(c5, 1)))::8, ?=, ?=, ?=>>
<<c1::5, c2::5, c3::5, c4::1>> ->
<<main::binary,
unquote(fun)(enc32hex(c1))::8, unquote(fun)(enc32hex(c2))::8,
unquote(fun)(enc32hex(c3))::8, unquote(fun)(enc32hex(bsl(c4, 4)))::8,
?=, ?=, ?=, ?=>>
<<c1::5, c2::3>> ->
<<main::binary,
unquote(fun)(enc32hex(c1))::8, unquote(fun)(enc32hex(bsl(c2, 2)))::8, ?=, ?=,
?=, ?=, ?=, ?=>>
<<>> ->
main
end
end
end
defp do_hex_decode32(_, <<>>), do: <<>>
for {case, fun} <- [upper: :from_upper, lower: :from_lower, mixed: :from_mixed] do
defp do_hex_decode32(unquote(case), string) do
split = byte_size(string) - 8
<<main::size(split)-binary, rest::binary>> = string
main = for <<c::8 <- main>>, into: <<>>, do: <<dec32hex(unquote(fun)(c))::5>>
case rest do
<<c1::8, c2::8, ?=, ?=, ?=, ?=, ?=, ?=>> ->
<<main::binary, dec32hex(unquote(fun)(c1))::5,
bsr(dec32hex(unquote(fun)(c2)), 2)::3>>
<<c1::8, c2::8, c3::8, c4::8, ?=, ?=, ?=, ?=>> ->
<<main::binary,
dec32hex(unquote(fun)(c1))::5, dec32hex(unquote(fun)(c2))::5,
dec32hex(unquote(fun)(c3))::5, bsr(dec32hex(unquote(fun)(c4)), 4)::1>>
<<c1::8, c2::8, c3::8, c4::8, c5::8, ?=, ?=, ?=>> ->
<<main::binary,
dec32hex(unquote(fun)(c1))::5, dec32hex(unquote(fun)(c2))::5,
dec32hex(unquote(fun)(c3))::5, dec32hex(unquote(fun)(c4))::5,
bsr(dec32hex(unquote(fun)(c5)), 1)::4>>
<<c1::8, c2::8, c3::8, c4::8, c5::8, c6::8, c7::8, ?=>> ->
<<main::binary,
dec32hex(unquote(fun)(c1))::5, dec32hex(unquote(fun)(c2))::5,
dec32hex(unquote(fun)(c3))::5, dec32hex(unquote(fun)(c4))::5,
dec32hex(unquote(fun)(c5))::5, dec32hex(unquote(fun)(c6))::5,
bsr(dec32hex(unquote(fun)(c7)), 3)::2>>
<<c1::8, c2::8, c3::8, c4::8, c5::8, c6::8, c7::8, c8::8>> ->
<<main::binary,
dec32hex(unquote(fun)(c1))::5, dec32hex(unquote(fun)(c2))::5,
dec32hex(unquote(fun)(c3))::5, dec32hex(unquote(fun)(c4))::5,
dec32hex(unquote(fun)(c5))::5, dec32hex(unquote(fun)(c6))::5,
dec32hex(unquote(fun)(c7))::5, dec32hex(unquote(fun)(c8))::5>>
<<>> ->
main
end
end
end
end
+3 -2
View File
@@ -1,8 +1,9 @@
defmodule Bitwise do
@moduledoc """
This module provides macro-based operators that perform calculations
on (sets of) bits. In general, you should `use` the Bitwise module
as a whole:
on (sets of) bits.
In general, you should `use` the Bitwise module as a whole:
iex> use Bitwise
iex> bnot 1
+12 -10
View File
@@ -374,7 +374,8 @@ defmodule Code do
## Examples
Code.compiler_options
#=> [debug_info: true, docs: true, warnings_as_errors: false]
#=> %{debug_info: true, docs: true,
warnings_as_errors: false, ignore_module_conflict: false}
"""
def compiler_options do
@@ -421,17 +422,18 @@ defmodule Code do
## Examples
Code.compiler_options(debug_info: true)
#=> [debug_info: true, docs: true, warnings_as_errors: false]
#=> %{debug_info: true, docs: true,
warnings_as_errors: false, ignore_module_conflict: false}
"""
def compiler_options(opts) do
{opts, bad} = Keyword.split(opts, available_compiler_options)
if bad != [] do
bad = bad |> Keyword.keys |> Enum.join(", ")
raise ArgumentError, message: "unknown compiler options: #{bad}"
end
update = &:orddict.merge(fn(_, _, value) -> value end, &1, opts)
:elixir_config.update :compiler_options, update
available = available_compiler_options()
for {k, _} <- opts,
not k in available,
do: raise "unknown compiler options: #{k}"
:elixir_config.update :compiler_options, &Enum.into(opts, &1)
end
@doc """
@@ -602,7 +604,7 @@ defmodule Code do
"Converts an atom to a char list."
# Module doesn't exist
iex> docs = Code.get_docs(ModuleNotGood, :all)
iex> Code.get_docs(ModuleNotGood, :all)
nil
"""
+11 -504
View File
@@ -1,118 +1,20 @@
defmodule Dict do
@moduledoc ~S"""
This module specifies the Dict API expected to be
implemented by different dictionaries.
WARNING: this module is deprecated.
It also provides functions that redirect to the underlying
Dict, allowing a developer to work with different Dict
implementations using one API.
To create a new dict, use the `new` functions defined
by each dict type:
HashDict.new #=> creates an empty HashDict
In the examples below, `dict_impl` means a specific
`Dict` implementation, for example `HashDict` or `Map`.
## Warning
Do not use this module if you expect a certain `Dict`
implementation. For example, if you are working with
maps and you don't need polymorphism, it is preferrable
to use the `Map` module instead of the `Dict` one.
## Protocols
Besides implementing the functions in this module, all
dictionaries are required to implement the `Access`
protocol:
iex> dict = dict_impl.new
iex> dict = Dict.put(dict, :hello, :world)
iex> dict[:hello]
:world
As well as the `Enumerable` and `Collectable` protocols.
## Match
Dictionaries are required to implement all operations
using the match (`===`) operator.
## Default implementation
Default implementations for some functions in the `Dict` module
are provided via `use Dict`.
For example:
defmodule MyDict do
use Dict
# implement required functions (see below)
# override default implementations if optimization
# is needed
end
The client module must contain the following functions:
* `delete/2`
* `fetch/2`
* `put/3`
* `reduce/3`
* `size/1`
All functions, except `reduce/3`, are required by the Dict behaviour.
`reduce/3` must be implemented as per the Enumerable protocol.
Based on these functions, `Dict` generates default implementations
for the following functions:
* `drop/2`
* `equal?/2`
* `fetch!/2`
* `get/2`
* `get/3`
* `get_lazy/3`
* `get_and_update/3`
* `has_key?/2`
* `keys/1`
* `merge/2`
* `merge/3`
* `pop/2`
* `pop/3`
* `pop_lazy/3`
* `put_new/3`
* `put_new_lazy/3`
* `split/2`
* `take/2`
* `to_list/1`
* `update/4`
* `update!/3`
* `values/1`
All of these functions are defined as overridable, so you can provide
your own implementation if needed.
Note you can also test your custom module via `Dict`'s doctests:
defmodule MyDict do
# ...
end
defmodule MyTests do
use ExUnit.Case
doctest Dict
defp dict_impl, do: MyDict
end
If you need a general dictionary, use the `Map` module.
If you need to manipulate keyword lists, use `Keyword`.
To convert maps into keywords and vice-versa, use the
`new` function in the respective modules.
"""
@type key :: any
@type value :: any
@type t :: list | map
# TODO: Remove callbacks on 1.3
# TODO: Deprecate every function on 1.3
@callback new :: t
@callback delete(t, key) :: t
@callback drop(t, Enum.t) :: t
@@ -145,6 +47,9 @@ defmodule Dict do
# Use this import to guarantee proper code expansion
import Kernel, except: [size: 1]
%{file: file, line: line} = __CALLER__
:elixir_errors.warn(line, file, "the Dict module is deprecated")
quote do
@behaviour Dict
@@ -325,276 +230,71 @@ defmodule Dict do
end
end
@doc """
Returns a list of all keys in `dict`.
The keys are not guaranteed to be in any order.
## Examples
iex> dict = Enum.into([a: 1, b: 2], dict_impl.new)
iex> Enum.sort(Dict.keys(dict))
[:a, :b]
"""
@spec keys(t) :: [key]
def keys(dict) do
target(dict).keys(dict)
end
@doc """
Returns a list of all values in `dict`.
The values are not guaranteed to be in any order.
## Examples
iex> dict = Enum.into([a: 1, b: 2], dict_impl.new)
iex> Enum.sort(Dict.values(dict))
[1, 2]
"""
@spec values(t) :: [value]
def values(dict) do
target(dict).values(dict)
end
@doc """
Returns the number of elements in `dict`.
## Examples
iex> dict = Enum.into([a: 1, b: 2], dict_impl.new)
iex> Dict.size(dict)
2
"""
@spec size(t) :: non_neg_integer
def size(dict) do
target(dict).size(dict)
end
@doc """
Returns whether the given `key` exists in the given `dict`.
## Examples
iex> dict = Enum.into([a: 1], dict_impl.new)
iex> Dict.has_key?(dict, :a)
true
iex> Dict.has_key?(dict, :b)
false
"""
@spec has_key?(t, key) :: boolean
def has_key?(dict, key) do
target(dict).has_key?(dict, key)
end
@doc """
Returns the value associated with `key` in `dict`. If `dict` does not
contain `key`, returns `default` (or `nil` if not provided).
## Examples
iex> dict = Enum.into([a: 1], dict_impl.new)
iex> Dict.get(dict, :a)
1
iex> Dict.get(dict, :b)
nil
iex> Dict.get(dict, :b, 3)
3
"""
@spec get(t, key, value) :: value
def get(dict, key, default \\ nil) do
target(dict).get(dict, key, default)
end
@doc """
Returns the value associated with `key` in `dict`. If `dict` does not
contain `key`, it lazily evaluates `fun` and returns its result.
This is useful if the default value is very expensive to calculate or
generally difficult to set-up and tear-down again.
## Examples
iex> dict = Enum.into([a: 1], dict_impl.new)
iex> fun = fn ->
...> # some expensive operation here
...> :result
...> end
iex> Dict.get_lazy(dict, :a, fun)
1
iex> Dict.get_lazy(dict, :b, fun)
:result
"""
@spec get_lazy(t, key, (() -> value)) :: value
def get_lazy(dict, key, fun) do
target(dict).get_lazy(dict, key, fun)
end
@doc """
Gets a value from `dict` and updates the value at `key` in one pass.
This `fun` argument receives the value of `key` in `dict` (or `nil` if `key`
is not present) and must return a two-elements tuple: the "get" value (the
value retrieved from the dict which can be operated on before being returned)
and the new value to be stored under `key` in `dict`.
The returned value is a tuple with the "get" value returned by `fun` and a new
dict with the updated value under `key`.
## Examples
iex> dict = Enum.into([a: 1], dict_impl.new)
iex> {get, new_dict} = Dict.get_and_update dict, :a, fn(current_value) ->
...> {current_value + 1, "foo"}
...> end
iex> get
2
iex> Dict.get(new_dict, :a)
"foo"
"""
@spec get_and_update(t, key, (value -> {value, value})) :: {value, t}
def get_and_update(dict, key, fun) do
target(dict).get_and_update(dict, key, fun)
end
@doc """
Returns `{:ok, value}` associated with `key` in `dict`.
If `dict` does not contain `key`, returns `:error`.
## Examples
iex> dict = Enum.into([a: 1], dict_impl.new)
iex> Dict.fetch(dict, :a)
{:ok, 1}
iex> Dict.fetch(dict, :b)
:error
"""
@spec fetch(t, key) :: value
def fetch(dict, key) do
target(dict).fetch(dict, key)
end
@doc """
Returns the value associated with `key` in `dict`. If `dict` does not
contain `key`, it raises `KeyError`.
## Examples
iex> dict = Enum.into([a: 1], dict_impl.new)
iex> Dict.fetch!(dict, :a)
1
"""
@spec fetch!(t, key) :: value | no_return
def fetch!(dict, key) do
target(dict).fetch!(dict, key)
end
@doc """
Stores the given `value` under `key` in `dict`.
If `dict` already has `key`, the stored value is replaced by the new one.
## Examples
iex> dict = Enum.into([a: 1, b: 2], dict_impl.new)
iex> dict = Dict.put(dict, :a, 3)
iex> Dict.get(dict, :a)
3
"""
@spec put(t, key, value) :: t
def put(dict, key, val) do
target(dict).put(dict, key, val)
end
@doc """
Puts the given `value` under `key` in `dict` unless `key` is already present.
## Examples
iex> dict = Enum.into([a: 1, b: 2], dict_impl.new)
iex> dict = Dict.put_new(dict, :a, 3)
iex> Dict.get(dict, :a)
1
"""
@spec put_new(t, key, value) :: t
def put_new(dict, key, val) do
target(dict).put_new(dict, key, val)
end
@doc """
Evaluates `fun` and puts the result under `key` in `dict` unless `key`
is already present.
This is useful if the value is very expensive to calculate or generally
difficult to set-up and tear-down again.
## Examples
iex> dict = Enum.into([a: 1, b: 2], dict_impl.new)
iex> fun = fn ->
...> # some expensive operation here
...> 3
...> end
iex> dict = Dict.put_new_lazy(dict, :a, fun)
iex> Dict.get(dict, :a)
1
iex> dict = Dict.put_new_lazy(dict, :c, fun)
iex> Dict.get(dict, :c)
3
"""
@spec put_new_lazy(t, key, (() -> value)) :: t
def put_new_lazy(dict, key, fun) do
target(dict).put_new_lazy(dict, key, fun)
end
@doc """
Removes the entry stored under the given `key` from `dict`.
If `dict` does not contain `key`, returns the dictionary unchanged.
## Examples
iex> dict = Enum.into([a: 1, b: 2], dict_impl.new)
iex> dict = Dict.delete(dict, :a)
iex> Dict.get(dict, :a)
nil
iex> dict = Enum.into([b: 2], dict_impl.new)
iex> Dict.delete(dict, :a) == dict
true
"""
@spec delete(t, key) :: t
def delete(dict, key) do
target(dict).delete(dict, key)
end
@doc """
Merges the dict `dict2` into dict `dict1`.
If one of the `dict2` entries is found in `dict1`, the
conflicting entries in `dict2` have higher precedence.
Notice this function is polymorphic as it merges dicts of any
type. Each dict implementation also provides a `merge` function,
but they can only merge dicts of the same type.
## Examples
iex> dict1 = Enum.into([a: 1, b: 2], dict_impl.new)
iex> dict2 = Enum.into([a: 3, d: 4], dict_impl.new)
iex> dict = Dict.merge(dict1, dict2)
iex> [a: Dict.get(dict, :a), b: Dict.get(dict, :b), d: Dict.get(dict, :d)]
[a: 3, b: 2, d: 4]
"""
@spec merge(t, t) :: t
def merge(dict1, dict2) do
target1 = target(dict1)
@@ -607,27 +307,6 @@ defmodule Dict do
end
end
@doc """
Merges the dict `dict2` into dict `dict1`.
If one of the `dict2` entries is found in `dict1`, the function
will be invoked to resolve the conflict.
Notice this function is polymorphic as it merges dicts of any
type. Each dict implementation also provides a `merge` function,
but they can only merge dicts of the same type.
## Examples
iex> dict1 = Enum.into([a: 1, b: 2], dict_impl.new)
iex> dict2 = Enum.into([a: 3, d: 4], dict_impl.new)
iex> dict = Dict.merge(dict1, dict2, fn(_k, v1, v2) ->
...> v1 + v2
...> end)
iex> [a: Dict.get(dict, :a), b: Dict.get(dict, :b), d: Dict.get(dict, :d)]
[a: 4, b: 2, d: 4]
"""
@spec merge(t, t, (key, value, value -> value)) :: t
def merge(dict1, dict2, fun) do
target1 = target(dict1)
@@ -646,207 +325,46 @@ defmodule Dict do
end) |> elem(1)
end
@doc """
Returns the value associated with `key` in `dict` as
well as the `dict` without `key`.
If `key` is not present in `dict`, then the `dict` will
be returned unmodified.
## Examples
iex> dict = Enum.into([a: 1], dict_impl.new)
iex> {v, dict} = Dict.pop dict, :a
iex> {v, Enum.sort(dict)}
{1, []}
iex> dict = Enum.into([a: 1], dict_impl.new)
iex> {v, dict} = Dict.pop dict, :b
iex> {v, Enum.sort(dict)}
{nil, [a: 1]}
iex> dict = Enum.into([a: 1], dict_impl.new)
iex> {v, dict} = Dict.pop dict, :b, 3
iex> {v, Enum.sort(dict)}
{3, [a: 1]}
"""
@spec pop(t, key, value) :: {value, t}
def pop(dict, key, default \\ nil) do
target(dict).pop(dict, key, default)
end
@doc """
Returns the value associated with `key` in `dict` as
well as the `dict` without `key`.
If `key` is not present in `dict`, then the `dict` will
be returned unmodified, and it will lazily evaluate `fun`
and return its result instead of the missing value.
This is useful if the default value is very expensive to calculate or
generally difficult to set-up and tear-down again.
## Examples
iex> dict = Enum.into([a: 1], dict_impl.new)
iex> fun = fn ->
...> # some expensive operation here
...> :result
...> end
iex> {v, dict} = Dict.pop_lazy dict, :a, fun
iex> {v, Enum.sort(dict)}
{1, []}
iex> dict = Enum.into([a: 1], dict_impl.new)
iex> fun = fn ->
...> # some expensive operation here
...> :result
...> end
iex> {v, dict} = Dict.pop_lazy dict, :b, fun
iex> {v, Enum.sort(dict)}
{:result, [a: 1]}
"""
@spec pop_lazy(t, key, (() -> value)) :: {value, t}
def pop_lazy(dict, key, fun) do
target(dict).pop_lazy(dict, key, fun)
end
@doc """
Updates a value in `dict` by calling `fun` on the value to get a new
value. An exception is generated if `key` is not present in the dict.
## Examples
iex> dict = Enum.into([a: 1, b: 2], dict_impl.new)
iex> dict = Dict.update!(dict, :a, fn(val) -> -val end)
iex> Dict.get(dict, :a)
-1
"""
@spec update!(t, key, (value -> value)) :: t
def update!(dict, key, fun) do
target(dict).update!(dict, key, fun)
end
@doc """
Updates a value in `dict` by calling `fun` on the value to get a new value. If
`key` is not present in `dict` then `initial` will be stored as the first
value.
## Examples
iex> dict = Enum.into([a: 1, b: 2], dict_impl.new)
iex> dict = Dict.update(dict, :c, 3, fn(val) -> -val end)
iex> Dict.get(dict, :c)
3
"""
@spec update(t, key, value, (value -> value)) :: t
def update(dict, key, initial, fun) do
target(dict).update(dict, key, initial, fun)
end
@doc """
Returns a tuple of two dicts, where the first dict contains only
entries from `dict` with keys in `keys`, and the second dict
contains only entries from `dict` with keys not in `keys`.
All non-member keys are ignored.
## Examples
iex> dict = Enum.into([a: 1, b: 2, c: 3, d: 4], dict_impl.new)
iex> {dict1, dict2} = Dict.split(dict, [:a, :c, :e])
iex> {Dict.to_list(dict1) |> Enum.sort, Dict.to_list(dict2) |> Enum.sort}
{[a: 1, c: 3], [b: 2, d: 4]}
iex> dict = Enum.into([], dict_impl.new)
iex> {dict1, dict2} = Dict.split(dict, [:a, :c])
iex> {Dict.to_list(dict1), Dict.to_list(dict2)}
{[], []}
iex> dict = Enum.into([a: 1, b: 2], dict_impl.new)
iex> {dict1, dict2} = Dict.split(dict, [:a, :b, :c])
iex> {Dict.to_list(dict1) |> Enum.sort, Dict.to_list(dict2)}
{[a: 1, b: 2], []}
"""
@spec split(t, [key]) :: {t, t}
def split(dict, keys) do
target(dict).split(dict, keys)
end
@doc """
Returns a new dict where the given `keys` are removed from `dict`.
All non-member keys are ignored.
## Examples
iex> dict = Enum.into([a: 1, b: 2], dict_impl.new)
iex> dict = Dict.drop(dict, [:a, :c, :d])
iex> Dict.to_list(dict)
[b: 2]
iex> dict = Enum.into([a: 1, b: 2], dict_impl.new)
iex> dict = Dict.drop(dict, [:c, :d])
iex> Dict.to_list(dict) |> Enum.sort
[a: 1, b: 2]
"""
@spec drop(t, [key]) :: t
def drop(dict, keys) do
target(dict).drop(dict, keys)
end
@doc """
Returns a new dict where only the keys in `keys` from `dict` are included.
All non-member keys are ignored.
## Examples
iex> dict = Enum.into([a: 1, b: 2], dict_impl.new)
iex> dict = Dict.take(dict, [:a, :c, :d])
iex> Dict.to_list(dict)
[a: 1]
iex> dict = Dict.take(dict, [:c, :d])
iex> Dict.to_list(dict)
[]
"""
@spec take(t, [key]) :: t
def take(dict, keys) do
target(dict).take(dict, keys)
end
@doc false
@spec empty(t) :: t
def empty(dict) do
target(dict).empty(dict)
end
@doc """
Checks if two dicts are equal using `===`.
Notice this function is polymorphic as it compares dicts of any
type. Each dict implementation also provides an `equal?` function,
but they can only compare dicts of the same type.
## Examples
iex> dict1 = Enum.into([a: 2, b: 3, f: 5, c: 123], dict_impl.new)
iex> dict2 = [a: 2, b: 3, f: 5, c: 123]
iex> Dict.equal?(dict1, dict2)
true
iex> dict1 = Enum.into([a: 2, b: 3, f: 5, c: 123], dict_impl.new)
iex> dict2 = []
iex> Dict.equal?(dict1, dict2)
false
"""
@spec equal?(t, t) :: boolean
def equal?(dict1, dict2) do
target1 = target(dict1)
@@ -860,7 +378,7 @@ defmodule Dict do
Enumerable.reduce(dict2, {:cont, true}, fn({k, v}, _acc) ->
case target1.fetch(dict1, k) do
{:ok, ^v} -> {:cont, true}
_ -> {:halt, false}
_ -> {:halt, false}
end
end) |> elem(1)
@@ -869,17 +387,6 @@ defmodule Dict do
end
end
@doc """
Returns a list of key-value pairs stored in `dict`.
No particular order is enforced.
## Examples
iex> dict = dict_impl.new
iex> dict = Dict.put(dict, :a, 1)
iex> Dict.to_list(dict)
[a: 1]
"""
@spec to_list(t) :: list
def to_list(dict) do
target(dict).to_list(dict)
+718 -581
View File
File diff suppressed because it is too large Load Diff
+28 -13
View File
@@ -42,7 +42,7 @@ defmodule Exception do
def exception?(_), do: false
@doc """
Gets the message for an exception.
Gets the message for an `exception`.
"""
def message(%{__struct__: module, __exception__: true} = exception) when is_atom(module) do
try do
@@ -95,7 +95,7 @@ defmodule Exception do
end
@doc """
Normalizes and formats any throw, error and exit.
Normalizes and formats any throw/error/exit.
The message is formatted and displayed in the same
format as used by Elixir's CLI.
@@ -128,7 +128,7 @@ defmodule Exception do
end
@doc """
Normalizes and formats throw/errors/exits and stacktrace.
Normalizes and formats throw/errors/exits and stacktraces.
It relies on `format_banner/3` and `format_stacktrace/1`
to generate the final format.
@@ -155,7 +155,7 @@ defmodule Exception do
end
@doc """
Formats an exit, returns a string.
Formats an exit. It returns a string.
Often there are errors/exceptions inside exits. Exits are often
wrapped by the caller and provide stacktraces too. This function
@@ -365,9 +365,10 @@ defmodule Exception do
end
defp format_application(module) do
case :application.get_application(module) do
{:ok, app} -> "(" <> Atom.to_string(app) <> ") "
:undefined -> ""
if app = Application.get_application(module) do
"(" <> Atom.to_string(app) <> ") "
else
""
end
end
@@ -394,7 +395,7 @@ defmodule Exception do
## Examples
Exception.format_fa(fn -> end, 1)
Exception.format_fa(fn -> nil end, 1)
#=> "#Function<...>/1"
"""
@@ -447,7 +448,7 @@ defmodule Exception do
end
@doc """
Formats the given file and line as shown in stacktraces.
Formats the given `file` and `line` as shown in stacktraces.
If any of the values are `nil`, they are omitted.
## Examples
@@ -626,19 +627,23 @@ defmodule UndefinedFunctionError do
end
def message(%{reason: :"module could not be loaded", module: module, function: function, arity: arity}) do
"undefined function: " <> Exception.format_mfa(module, function, arity) <>
"undefined function " <> Exception.format_mfa(module, function, arity) <>
" (module #{inspect module} is not available)"
end
def message(%{reason: :"function not exported", module: module, function: function, arity: arity}) do
"undefined function: " <> Exception.format_mfa(module, function, arity)
"undefined function " <> Exception.format_mfa(module, function, arity)
end
def message(%{reason: :"function not available", module: module, function: function, arity: arity}) do
"nil." <> fa = Exception.format_mfa(nil, function, arity)
"undefined function: " <> Exception.format_mfa(module, function, arity) <>
"undefined function " <> Exception.format_mfa(module, function, arity) <>
" (function #{fa} is not available)"
end
def message(%{reason: reason, module: module, function: function, arity: arity}) do
"undefined function " <> Exception.format_mfa(module, function, arity) <> " (#{reason})"
end
end
defmodule FunctionClauseError do
@@ -703,9 +708,13 @@ defmodule UnicodeConversionError do
"encoding starting at #{inspect rest}"
end
defp detail([h|_]) do
defp detail([h|_]) when is_integer(h) do
"code point #{h}"
end
defp detail([h|_]) do
detail(h)
end
end
defmodule Enum.OutOfBoundsError do
@@ -791,6 +800,8 @@ defmodule ErlangError do
def normalize({:badkey, key}, stacktrace) do
term =
case stacktrace || :erlang.get_stacktrace do
[{Map, :get_and_update!, [map, _, _], _}|_] -> map
[{Map, :update!, [map, _, _], _}|_] -> map
[{:maps, :update, [_, _, map], _}|_] -> map
[{:maps, :get, [_, map], _}|_] -> map
_ -> nil
@@ -798,6 +809,10 @@ defmodule ErlangError do
%KeyError{key: key, term: term}
end
def normalize({:badkey, key, map}, _stacktrace) do
%KeyError{key: key, term: map}
end
def normalize({:case_clause, term}, _stacktrace) do
%CaseClauseError{term: term}
end
+45 -44
View File
@@ -3,35 +3,35 @@ defmodule File do
This module contains functions to manipulate files.
Some of those functions are low-level, allowing the user
to interact with the file or IO devices, like `open/2`,
to interact with files or IO devices, like `open/2`,
`copy/3` and others. This module also provides higher
level functions that work with filenames and have their naming
based on UNIX variants. For example, one can copy a file
via `cp/3` and remove files and directories recursively
via `rm_rf/1`
via `rm_rf/1`.
## Encoding
In order to write and read files, one must use the functions
in the `IO` module. By default, a file is opened in binary mode
in the `IO` module. By default, a file is opened in binary mode,
which requires the functions `IO.binread/2` and `IO.binwrite/2`
to interact with the file. A developer may pass `:utf8` as an
option when opening the file, then the slower `IO.read/2` and
`IO.write/2` functions must be used as they are responsible for
doing the proper conversions and data guarantees.
doing the proper conversions and providing the proper data guarantees.
Note that filenames when given as char lists in Elixir are
always treated as UTF-8. In particular, we expect that the
shell and the operating system are configured to use UTF8
encoding. Binary filenames are considering raw and passed
shell and the operating system are configured to use UTF-8
encoding. Binary filenames are considered raw and passed
to the OS as is.
## API
Most of the functions in this module return `:ok` or
`{:ok, result}` in case of success, `{:error, reason}`
otherwise. Those function are also followed by a variant
that ends with `!` which returns the result (without the
otherwise. Those functions also have a variant
that ends with `!` which returns the result (instead of the
`{:ok, result}` tuple) in case of success or raises an
exception in case it fails. For example:
@@ -55,7 +55,7 @@ defmodule File do
## Processes and raw files
Every time a file is opened, Elixir spawns a new process. Writing
to a file is equivalent to sending messages to that process that
to a file is equivalent to sending messages to the process that
writes to the file descriptor.
This means files can be passed between nodes and message passing
@@ -63,7 +63,7 @@ defmodule File do
However, you may not always want to pay the price for this abstraction.
In such cases, a file can be opened in `:raw` mode. The options `:read_ahead`
and `:delayed_write` are also useful when operating large files or
and `:delayed_write` are also useful when operating on large files or
working with files in tight loops.
Check [`:file.open/2`](http://www.erlang.org/doc/man/file.html#open-2) for more information
@@ -232,7 +232,7 @@ defmodule File do
end
@doc """
Returns binary with the contents of the given filename or raises
Returns a binary with the contents of the given filename or raises
`File.Error` if an error occurs.
"""
@spec read!(Path.t) :: binary | no_return
@@ -261,7 +261,8 @@ defmodule File do
The values for `:time` can be:
* `:universal` - returns a `{date, time}` tuple in UTC (default)
* `:local` - returns a `{date, time}` tuple using the machine time
* `:local` - returns a `{date, time}` tuple using the same time zone as the
machine
* `:posix` - returns the time as integer seconds since epoch
"""
@@ -291,8 +292,8 @@ defmodule File do
end
@doc """
Returns information about the `path`. If the file is a symlink sets
the `type` to `:symlink` and returns `File.Stat` for the link. For any
Returns information about the `path`. If the file is a symlink, sets
the `type` to `:symlink` and returns a `File.Stat` struct for the link. For any
other file, returns exactly the same values as `stat/2`.
For more details, see [`:file.read_link_info/2`](http://www.erlang.org/doc/man/file.html#read_link_info-2).
@@ -322,7 +323,7 @@ defmodule File do
end
@doc """
Same as `lstat/2` but returns the `File.Stat` directly and
Same as `lstat/2` but returns the `File.Stat` struct directly and
throws `File.Error` if an error is returned.
"""
@spec lstat!(Path.t, stat_options) :: File.Stat.t | no_return
@@ -363,7 +364,7 @@ defmodule File do
Updates modification time (mtime) and access time (atime) of
the given file.
File is created if it doesn’t exist. Requires datetime in UTC.
The file is created if it doesn’t exist. Requires datetime in UTC.
"""
@spec touch(Path.t, :calendar.datetime) :: :ok | {:error, posix}
def touch(path, time \\ :calendar.universal_time) do
@@ -449,12 +450,12 @@ defmodule File do
end
@doc """
Renames the `source` file to `destination` file. If can be used to move files
Renames the `source` file to `destination` file. It can be used to move files
(and directories) between directories. If moving a file, you must fully
specify the `destination` filename, it is not sufficient to simply specify
it's directory.
its directory.
It returns `:ok` in case of success, returns `{:error, reason}` otherwise
It returns `:ok` in case of success, returns `{:error, reason}` otherwise.
Note: The command `mv` in Unix systems behaves differently depending
if `source` is a file and the `destination` is an existing directory.
@@ -478,9 +479,9 @@ defmodule File do
If a file already exists in the destination, it invokes a
callback which should return `true` if the existing file
should be overwritten, `false` otherwise. It defaults to return `true`.
should be overwritten, `false` otherwise. The callback defaults to return `true`.
It returns `:ok` in case of success, returns
The function returns `:ok` in case of success, returns
`{:error, reason}` otherwise.
If you want to copy contents from an io device to another device
@@ -534,20 +535,20 @@ defmodule File do
If a file already exists in the destination,
it invokes a callback which should return
`true` if the existing file should be overwritten,
`false` otherwise. It defaults to return `true`.
`false` otherwise. The callback defaults to return `true`.
If a directory already exists in the destination
where a file is meant to be (or otherwise), this
where a file is meant to be (or vice versa), this
function will fail.
This function may fail while copying files,
in such cases, it will leave the destination
directory in a dirty state, where already
copied files won't be removed.
directory in a dirty state, where file which have already been copied
won't be removed.
It returns `{:ok, files_and_directories}` in case of
success with all files and directories copied in no
specific order, `{:error, reason, file}` otherwise.
The function returns `{:ok, files_and_directories}` in case of
success, `files_and_directories` lists all files and directories copied in no
specific order. It returns `{:error, reason, file}` otherwise.
Note: The command `cp` in Unix systems behaves differently
depending if `destination` is an existing directory or not.
@@ -682,13 +683,13 @@ defmodule File do
and a new process is spawned to write to the file. For this reason, if you are
doing multiple writes in a loop, opening the file via `File.open/2` and using
the functions in `IO` to write to the file will yield much better performance
then calling this function multiple times.
than calling this function multiple times.
Typical error reasons are:
* `:enoent` - a component of the file name does not exist
* `:enotdir` - a component of the file name is not a directory;
on some platforms, enoent is returned instead
on some platforms, `:enoent` is returned instead
* `:enospc` - there is a no space left on the device
* `:eacces` - missing permission for writing the file or searching one of
the parent directories
@@ -727,7 +728,7 @@ defmodule File do
* `:eacces` - missing permission for the file or one of its parents
* `:eperm` - the file is a directory and user is not super-user
* `:enotdir` - a component of the file name is not a directory;
on some platforms, enoent is returned instead
on some platforms, `:enoent` is returned instead
* `:einval` - filename had an improper type, such as tuple
## Examples
@@ -921,7 +922,7 @@ defmodule File do
Opens the given `path` according to the given list of modes.
In order to write and read files, one must use the functions
in the `IO` module. By default, a file is opened in binary mode
in the `IO` module. By default, a file is opened in binary mode,
which requires the functions `IO.binread/2` and `IO.binwrite/2`
to interact with the file. A developer may pass `:utf8` as an
option when opening the file and then all other functions from
@@ -955,10 +956,10 @@ defmodule File do
* `:utf8` - this option denotes how data is actually stored in the disk
file and makes the file perform automatic translation of characters to
and from utf-8.
and from UTF-8.
If data is sent to a file in a format that cannot be converted to the
utf-8 or if data is read by a function that returns data in a format that
UTF-8 or if data is read by a function that returns data in a format that
cannot cope with the character range of the data, an error occurs and the
file will be closed.
@@ -999,9 +1000,9 @@ defmodule File do
end
@doc """
Similar to `open/2` but expects a function as last argument.
Similar to `open/2` but expects a function as its last argument.
The file is opened, given to the function as argument and
The file is opened, given to the function as an argument and
automatically closed after the function returns, regardless
if there was an error when executing the function.
@@ -1065,7 +1066,7 @@ defmodule File do
Gets the current working directory.
In rare circumstances, this function can fail on Unix. It may happen
if read permission does not exist for the parent directories of the
if read permissions do not exist for the parent directories of the
current directory. For this reason, returns `{:ok, cwd}` in case
of success, `{:error, reason}` otherwise.
"""
@@ -1123,8 +1124,8 @@ defmodule File do
@doc """
Changes the current directory to the given `path`,
executes the given function and then revert back
to the previous path regardless if there is an exception.
executes the given function and then reverts back
to the previous path regardless of whether there is an exception.
Raises an error if retrieving or changing the current
directory fails.
@@ -1141,7 +1142,7 @@ defmodule File do
end
@doc """
Returns list of files in the given directory.
Returns the list of files in the given directory.
It returns `{:ok, [files]}` in case of success,
`{:error, reason}` otherwise.
@@ -1191,9 +1192,9 @@ defmodule File do
streaming, by `:line` (default) or by a given number of bytes.
Operating the stream can fail on open for the same reasons as
`File.open!/2`. Note that the file is automatically opened only and
every time streaming begins. There is no need to pass `:read` and
`:write` modes, as those are automatically set by Elixir.
`File.open!/2`. Note that the file is automatically opened each time streaming
begins. There is no need to pass `:read` and `:write` modes, as those are
automatically set by Elixir.
## Raw files
@@ -1267,7 +1268,7 @@ defmodule File do
end
@doc """
Changes the user group given by the group id `gid`
Changes the group given by the group id `gid`
for a given `file`. Returns `:ok` on success, or
`{:error, reason}` on failure.
"""
+6 -5
View File
@@ -2,7 +2,7 @@ require Record
defmodule File.Stat do
@moduledoc """
A struct responsible to hold file information.
A struct that holds file information.
In Erlang, this struct is represented by a `:file_info` record.
Therefore this module also provides functions for converting
@@ -41,14 +41,15 @@ defmodule File.Stat do
* `inode` - gives the inode number. On non-Unix file systems, this field
will be zero.
* `uid` - indicates the owner of the file.
* `uid` - indicates the owner of the file. Will be zero for non-Unix file
systems.
* `gid` - gives the group that the owner of the file belongs to. Will be
zero for non-Unix file systems.
* `gid` - indicates the group that owns the file. Will be zero for
non-Unix file systems.
The time type returned in `atime`, `mtime`, and `ctime` is dependent on the
time type set in options. `{:time, type}` where type can be `:local`,
`:universal`, or `:posix`. Default is `:local`.
`:universal`, or `:posix`. Default is `:universal`.
"""
record = Record.extract(:file_info, from_lib: "kernel/include/file.hrl")
+12 -8
View File
@@ -9,7 +9,11 @@ defmodule Float do
Parses a binary into a float.
If successful, returns a tuple of the form `{float, remainder_of_binary}`;
otherwise, `:error`.
when the binary cannot be coerced into a valid float, the atom `:error` is
returned.
If the size of float exceeds the maximum size of `1.7976931348623157e+308`,
the `ArgumentError` exception is raised.
If a float formatted string wants to be directly converted to a float,
`String.to_float/1` can be used instead.
@@ -51,16 +55,16 @@ defmodule Float do
defp parse_unsigned(binary) when is_binary(binary), do:
:error
defp parse_unsigned(<<digit, rest :: binary>>, dot?, e?, acc) when digit in ?0..?9, do:
defp parse_unsigned(<<digit, rest::binary>>, dot?, e?, acc) when digit in ?0..?9, do:
parse_unsigned(rest, dot?, e?, <<acc::binary, digit>>)
defp parse_unsigned(<<?., digit, rest :: binary>>, false, false, acc) when digit in ?0..?9, do:
defp parse_unsigned(<<?., digit, rest::binary>>, false, false, acc) when digit in ?0..?9, do:
parse_unsigned(rest, true, false, <<acc::binary, ?., digit>>)
defp parse_unsigned(<<exp_marker, digit, rest :: binary>>, dot?, false, acc) when exp_marker in 'eE' and digit in ?0..?9, do:
defp parse_unsigned(<<exp_marker, digit, rest::binary>>, dot?, false, acc) when exp_marker in 'eE' and digit in ?0..?9, do:
parse_unsigned(rest, true, true, <<add_dot(acc, dot?)::binary, ?e, digit>>)
defp parse_unsigned(<<exp_marker, sign, digit, rest :: binary>>, dot?, false, acc) when exp_marker in 'eE' and sign in '-+' and digit in ?0..?9, do:
defp parse_unsigned(<<exp_marker, sign, digit, rest::binary>>, dot?, false, acc) when exp_marker in 'eE' and sign in '-+' and digit in ?0..?9, do:
parse_unsigned(rest, true, true, <<add_dot(acc, dot?)::binary, ?e, sign, digit>>)
defp parse_unsigned(rest, dot?, _e?, acc), do:
@@ -86,7 +90,7 @@ defmodule Float do
iex> Float.floor(-56.5)
-57.0
iex> Float.floor(34.253, 2)
iex> Float.floor(34.259, 2)
34.25
"""
@@ -100,7 +104,7 @@ defmodule Float do
end
@doc """
Rounds a float to the largest integer greater than or equal to `num`.
Rounds a float to the smallest integer greater than or equal to `num`.
`ceil/2` also accepts a precision to round a floating point value down
to an arbitrary number of fractional digits (between 0 and 15).
@@ -116,7 +120,7 @@ defmodule Float do
iex> Float.ceil(-56.5)
-56.0
iex> Float.ceil(34.253, 2)
iex> Float.ceil(34.251, 2)
34.26
"""
+25 -17
View File
@@ -22,6 +22,7 @@ defmodule GenEvent do
As an example, let's have a GenEvent that accumulates messages until
they are collected by an explicit call.
# Define a Event Handler
defmodule LoggerHandler do
use GenEvent
@@ -36,17 +37,21 @@ defmodule GenEvent do
end
end
# Start a new event manager.
{:ok, pid} = GenEvent.start_link([])
# Attach an event handler to the event manager.
GenEvent.add_handler(pid, LoggerHandler, [])
#=> :ok
# Send some events to the event manager.
GenEvent.notify(pid, {:log, 1})
#=> :ok
GenEvent.notify(pid, {:log, 2})
#=> :ok
# Call functions on specific handlers in the manager.
GenEvent.call(pid, LoggerHandler, :messages)
#=> [1, 2]
@@ -81,7 +86,7 @@ defmodule GenEvent do
asynchronously.
On `GenEvent.sync_notify/2`, the manager acknowledges an event
just after it was processed by all event handlers.
just after it is processed by all event handlers.
On `GenEvent.notify/2`, all events are processed asynchronously and
there is no ack (which means there is no backpressure).
@@ -125,7 +130,7 @@ defmodule GenEvent do
too much kool aid" section of the "Learn you some Erlang" link above. Due
to those changes, Elixir's GenEvent does not trap exits by default.
Furthermore, Elixir's also normalizes the `{:error, _}` tuples returned
Furthermore, Elixir also normalizes the `{:error, _}` tuples returned
by many functions, in order to be more consistent with themselves and
the `GenServer` module.
"""
@@ -290,7 +295,7 @@ defmodule GenEvent do
@type manager :: pid | name | {atom, node}
@typedoc "Supported values for new handlers"
@type handler :: atom | {atom, term} | {pid, reference}
@type handler :: atom | {atom, term}
@doc false
defmacro __using__(_) do
@@ -417,6 +422,10 @@ defmodule GenEvent do
If the given handler was previously installed at the manager, this
function returns `{:error, :already_present}`.
For installing multiple instances of the same handler, `{Module, id}` instead
of `Module` must be used. The handler could be then referenced with
`{Module, id}` instead of just `Module`.
"""
@spec add_handler(manager, handler, term) :: :ok | {:error, term}
def add_handler(manager, handler, args) do
@@ -607,18 +616,24 @@ defmodule GenEvent do
end
@doc """
Terminates the event `manager`.
Stops the manager with the given `reason`.
Before terminating, the event manager will call `terminate(:stop, ...)`
for each installed event handler.
Before terminating, the event manager will call
`terminate(:stop, ...)` for each installed event handler.
It returns `:ok` if the manager terminates with the given
reason, if it terminates with another reason, the call will
exit.
This function keeps OTP semantics regarding error reporting.
If the reason is any other than `:normal`, `:shutdown` or
`{:shutdown, _}`, an error report will be logged.
"""
@spec stop(manager) :: :ok
def stop(manager) do
rpc(manager, :stop)
@spec stop(manager, reason :: term, timeout) :: :ok
def stop(manager, reason \\ :normal, timeout \\ :infinity) do
:gen.stop(manager, reason, timeout)
end
defp rpc(module, cmd) do
# TODO: Change the tag on OTP 18
{:ok, reply} = :gen.call(module, self(), cmd, :infinity)
reply
end
@@ -727,13 +742,6 @@ defmodule GenEvent do
{hib, reply, handlers} = server_swap_handler(handler1, args1, handler2, args2, handlers, mon, name)
reply(tag, reply)
loop(parent, name, handlers, debug, hib)
{_from, tag, :stop} ->
try do
server_terminate(:normal, parent, handlers, name)
catch
:exit, :normal -> :ok
end
reply(tag, :ok)
{_from, tag, :which_handlers} ->
reply(tag, server_which_handlers(handlers))
loop(parent, name, handlers, debug, false)
+1 -1
View File
@@ -2,7 +2,7 @@ defmodule GenEvent.Stream do
@moduledoc """
Defines a `GenEvent` stream.
This is a struct returned by `stream/2`. The struct is public and
This is a struct returned by `GenEvent.stream/2`. The struct is public and
contains the following fields:
* `:manager` - the manager reference given to `GenEvent.stream/2`
+25 -5
View File
@@ -2,7 +2,7 @@ defmodule GenServer do
@moduledoc """
A behaviour module for implementing the server of a client-server relation.
A GenServer is a process as any other Elixir process and it can be used
A GenServer is a process like any other Elixir process and it can be used
to keep state, execute code asynchronously and so on. The advantage of using
a generic server process (GenServer) implemented using this module is that it
will have a standard set of interface functions and include functionality for
@@ -76,8 +76,11 @@ defmodule GenServer do
term using the functions in the `:global` module.
* `{:via, module, term}` - the GenServer is registered with the given
mechanism and name. The `:via` option expects a module name to control
the registration mechanism alongside a name which can be any term.
mechanism and name. The `:via` option expects a module that exports
`register_name/2`, `unregister_name/1`, `whereis_name/1` and `send/2`.
One such example is the `:global` module which uses these functions
for keeping the list of names of processes and their associated pid's
that are available globally for a network of Erlang nodes.
For example, we could start and register our Stack server locally as follows:
@@ -222,7 +225,7 @@ defmodule GenServer do
Returning `{:reply, reply, new_state, timeout}` is similar to
`{:reply, reply, new_state}` except `handle_info(:timeout, new_state)` will be
called after `timeout` milliseconds if no messages are receved.
called after `timeout` milliseconds if no messages are received.
Returning `{:reply, reply, new_state, :hibernate}` is similar to
`{:reply, reply, new_state}` except the process is hibernated and will
@@ -514,6 +517,23 @@ defmodule GenServer do
end
end
@doc """
Stops the server with the given `reason`.
The `terminate/2` callback will be invoked before exiting.
It returns `:ok` if the server terminates with the given
reason, if it terminates with another reason, the call will
exit.
This function keeps OTP semantics regarding error reporting.
If the reason is any other than `:normal`, `:shutdown` or
`{:shutdown, _}`, an error report will be logged.
"""
@spec stop(server, reason :: term, timeout) :: :ok
def stop(server, reason \\ :normal, timeout \\ :infinity) do
:gen.stop(server, reason, timeout)
end
@doc """
Makes a synchronous call to the `server` and waits for its reply.
@@ -533,7 +553,7 @@ defmodule GenServer do
failure and continues running, and the server is just late with the reply,
it may arrive at any time later into the caller's message queue. The caller
must in this case be prepared for this and discard any such garbage messages
that are two element tuples with a reference as the first element.
that are two-element tuples with a reference as the first element.
"""
@spec call(server, term, timeout) :: term
def call(server, request, timeout \\ 5000) do
+3 -12
View File
@@ -1,17 +1,8 @@
defmodule HashDict do
@moduledoc """
A key-value store.
WARNING: this module is deprecated.
The `HashDict` is represented internally as a struct, therefore
`%HashDict{}` can be used whenever there is a need to match
on any `HashDict`. Note though the struct fields are private and
must not be accessed directly. Instead, use the functions on this
or in the `Dict` module.
Implementation-wise, `HashDict` is implemented using tries, which
grows in space as the number of keys grows, working well with both
small and large set of keys. For more information about the
functions and their APIs, please consult the `Dict` module.
Use the `Map` module instead.
"""
use Dict
@@ -236,7 +227,7 @@ end
defimpl Collectable, for: HashDict do
def into(original) do
{original, fn
dict, {:cont, {k, v}} -> Dict.put(dict, k, v)
dict, {:cont, {k, v}} -> HashDict.put(dict, k, v)
dict, :done -> dict
_, :halt -> :ok
end}
+2 -14
View File
@@ -1,17 +1,8 @@
defmodule HashSet do
@moduledoc """
A set store.
WARNING: this module is deprecated.
The `HashSet` is represented internally as a struct, therefore
`%HashSet{}` can be used whenever there is a need to match
on any `HashSet`. Note though the struct fields are private and
must not be accessed directly. Instead, use the functions on this
or in the `Set` module.
The `HashSet` is implemented using tries, which grows in
space as the number of keys grows, working well with both
small and large set of keys. For more information about the
functions and their APIs, please consult the `Set` module.
Use the `MapSet` module instead.
"""
@behaviour Set
@@ -29,9 +20,6 @@ defmodule HashSet do
@compile :inline_list_funcs
@compile {:inline, key_hash: 1, key_mask: 1, key_shift: 1}
@doc """
Creates a new empty set.
"""
@spec new :: Set.t
def new do
%HashSet{}
+65 -62
View File
@@ -16,20 +16,20 @@ defprotocol Inspect do
## Examples
Many times, inspecting a structure can be implemented in function
of existing entities. For example, here is `HashSet`'s `inspect`
of existing entities. For example, here is `MapSet`'s `inspect`
implementation:
defimpl Inspect, for: HashSet do
defimpl Inspect, for: MapSet do
import Inspect.Algebra
def inspect(dict, opts) do
concat ["#HashSet<", to_doc(HashSet.to_list(dict), opts), ">"]
concat ["#MapSet<", to_doc(MapSet.to_list(dict), opts), ">"]
end
end
The `concat` function comes from `Inspect.Algebra` and it
concatenates algebra documents together. In the example above,
it is concatenating the string `"HashSet<"` (all strings are
it is concatenating the string `"MapSet<"` (all strings are
valid algebra documents that keep their formatting when pretty
printed), the document returned by `Inspect.Algebra.to_doc/2` and the
other string `">"`.
@@ -45,10 +45,10 @@ defprotocol Inspect do
to a raw representation for printing the structure.
You can however access the underlying error by invoking the Inspect
implementation directly. For example, to test Inspect.HashSet above,
implementation directly. For example, to test Inspect.MapSet above,
you can invoke it as:
Inspect.HashSet.inspect(HashSet.new, Inspect.Opts.new)
Inspect.MapSet.inspect(MapSet.new, Inspect.Opts.new)
"""
@@ -88,7 +88,7 @@ defimpl Inspect, for: Atom do
atom in Macro.binary_ops or atom in Macro.unary_ops ->
":" <> binary
true ->
<< ?:, ?", Inspect.BitString.escape(binary, ?") :: binary, ?" >>
<<?:, ?", Inspect.BitString.escape(binary, ?")::binary, ?">>
end
end
@@ -104,7 +104,7 @@ defimpl Inspect, for: Atom do
defp valid_ref_identifier?(_), do: false
defp valid_ref_piece?(<<?., h, t :: binary>>) when h in ?A..?Z do
defp valid_ref_piece?(<<?., h, t::binary>>) when h in ?A..?Z do
valid_ref_piece? valid_identifier?(t)
end
@@ -113,7 +113,7 @@ defimpl Inspect, for: Atom do
# Detect if atom
defp valid_atom_identifier?(<<h, t :: binary>>) when h in ?a..?z or h in ?A..?Z or h == ?_ do
defp valid_atom_identifier?(<<h, t::binary>>) when h in ?a..?z or h in ?A..?Z or h == ?_ do
valid_atom_piece?(t)
end
@@ -129,7 +129,7 @@ defimpl Inspect, for: Atom do
end
end
defp valid_identifier?(<<h, t :: binary>>)
defp valid_identifier?(<<h, t::binary>>)
when h in ?a..?z
when h in ?A..?Z
when h in ?0..?9
@@ -143,7 +143,7 @@ end
defimpl Inspect, for: BitString do
def inspect(thing, %Inspect.Opts{binaries: bins} = opts) when is_binary(thing) do
if bins == :as_strings or (bins == :infer and String.printable?(thing)) do
<<?", escape(thing, ?") :: binary, ?">>
<<?", escape(thing, ?")::binary, ?">>
else
inspect_bitstring(thing, opts)
end
@@ -160,54 +160,54 @@ defimpl Inspect, for: BitString do
escape(other, char, <<>>)
end
defp escape(<< char, t :: binary >>, char, binary) do
escape(t, char, << binary :: binary, ?\\, char >>)
defp escape(<<char, t::binary >>, char, binary) do
escape(t, char, <<binary::binary, ?\\, char>>)
end
defp escape(<<?#, ?{, t :: binary>>, char, binary) do
escape(t, char, << binary :: binary, ?\\, ?#, ?{>>)
defp escape(<<?#, ?{, t::binary>>, char, binary) do
escape(t, char, <<binary::binary, ?\\, ?#, ?{>>)
end
defp escape(<<?\a, t :: binary>>, char, binary) do
escape(t, char, << binary :: binary, ?\\, ?a >>)
defp escape(<<?\a, t::binary>>, char, binary) do
escape(t, char, <<binary::binary, ?\\, ?a>>)
end
defp escape(<<?\b, t :: binary>>, char, binary) do
escape(t, char, << binary :: binary, ?\\, ?b >>)
defp escape(<<?\b, t::binary>>, char, binary) do
escape(t, char, <<binary::binary, ?\\, ?b>>)
end
defp escape(<<?\d, t :: binary>>, char, binary) do
escape(t, char, << binary :: binary, ?\\, ?d >>)
defp escape(<<?\d, t::binary>>, char, binary) do
escape(t, char, <<binary::binary, ?\\, ?d>>)
end
defp escape(<<?\e, t :: binary>>, char, binary) do
escape(t, char, << binary :: binary, ?\\, ?e >>)
defp escape(<<?\e, t::binary>>, char, binary) do
escape(t, char, <<binary::binary, ?\\, ?e>>)
end
defp escape(<<?\f, t :: binary>>, char, binary) do
escape(t, char, << binary :: binary, ?\\, ?f >>)
defp escape(<<?\f, t::binary>>, char, binary) do
escape(t, char, <<binary::binary, ?\\, ?f>>)
end
defp escape(<<?\n, t :: binary>>, char, binary) do
escape(t, char, << binary :: binary, ?\\, ?n >>)
defp escape(<<?\n, t::binary>>, char, binary) do
escape(t, char, <<binary::binary, ?\\, ?n>>)
end
defp escape(<<?\r, t :: binary>>, char, binary) do
escape(t, char, << binary :: binary, ?\\, ?r >>)
defp escape(<<?\r, t::binary>>, char, binary) do
escape(t, char, <<binary::binary, ?\\, ?r>>)
end
defp escape(<<?\\, t :: binary>>, char, binary) do
escape(t, char, << binary :: binary, ?\\, ?\\ >>)
defp escape(<<?\\, t::binary>>, char, binary) do
escape(t, char, <<binary::binary, ?\\, ?\\>>)
end
defp escape(<<?\t, t :: binary>>, char, binary) do
escape(t, char, << binary :: binary, ?\\, ?t >>)
defp escape(<<?\t, t::binary>>, char, binary) do
escape(t, char, <<binary::binary, ?\\, ?t>>)
end
defp escape(<<?\v, t :: binary>>, char, binary) do
escape(t, char, << binary :: binary, ?\\, ?v >>)
defp escape(<<?\v, t::binary>>, char, binary) do
escape(t, char, <<binary::binary, ?\\, ?v>>)
end
defp escape(<<h :: utf8, t :: binary>>, char, binary) do
head = << h :: utf8 >>
defp escape(<<h::utf8, t::binary>>, char, binary) do
head = <<h::utf8 >>
if String.printable?(head) do
escape(t, char, append(head, binary))
else
<< byte :: size(8), h :: binary >> = head
t = << h :: binary, t :: binary >>
escape(t, char, << binary :: binary, escape_char(byte) :: binary >>)
<<byte::8, h::binary >> = head
t = <<h::binary, t::binary>>
escape(t, char, <<binary::binary, escape_char(byte)::binary>>)
end
end
defp escape(<<h, t :: binary>>, char, binary) do
escape(t, char, << binary :: binary, escape_char(h) :: binary >>)
defp escape(<<h, t::binary>>, char, binary) do
escape(t, char, <<binary::binary, escape_char(h)::binary>>)
end
defp escape(<<>>, _char, binary), do: binary
@@ -218,17 +218,17 @@ defimpl Inspect, for: BitString do
end
def escape_char(char) when char < 0x100 do
<<a::4, b::4>> = <<char::size(8)>>
<<a::4, b::4>> = <<char::8>>
<<?\\, ?x, to_hex(a), to_hex(b)>>
end
def escape_char(char) when char < 0x10000 do
<<a::4, b::4, c::4, d::4>> = <<char::size(16)>>
<<a::4, b::4, c::4, d::4>> = <<char::16>>
<<?\\, ?x, ?{, to_hex(a), to_hex(b), to_hex(c), to_hex(d), ?}>>
end
def escape_char(char) when char < 0x1000000 do
<<a::4, b::4, c::4, d::4, e::4, f::4>> = <<char::size(24)>>
<<a::4, b::4, c::4, d::4, e::4, f::4>> = <<char::24>>
<<?\\, ?x, ?{, to_hex(a), to_hex(b), to_hex(c),
to_hex(d), to_hex(e), to_hex(f), ?}>>
end
@@ -236,7 +236,7 @@ defimpl Inspect, for: BitString do
defp to_hex(c) when c in 0..9, do: ?0+c
defp to_hex(c) when c in 10..15, do: ?A+c-10
defp append(<<h, t :: binary>>, binary), do: append(t, << binary :: binary, h >>)
defp append(<<h, t::binary>>, binary), do: append(t, <<binary::binary, h>>)
defp append(<<>>, binary), do: binary
## Bitstrings
@@ -249,11 +249,11 @@ defimpl Inspect, for: BitString do
acc <> "..."
end
defp each_bit(<<h, t :: bitstring>>, counter, acc) when t != <<>> do
defp each_bit(<<h, t::bitstring>>, counter, acc) when t != <<>> do
each_bit(t, decrement(counter), acc <> Integer.to_string(h) <> ", ")
end
defp each_bit(<<h :: size(8)>>, _counter, acc) do
defp each_bit(<<h::8>>, _counter, acc) do
acc <> Integer.to_string(h)
end
@@ -263,7 +263,7 @@ defimpl Inspect, for: BitString do
defp each_bit(bitstring, _counter, acc) do
size = bit_size(bitstring)
<<h :: size(size)>> = bitstring
<<h::size(size)>> = bitstring
acc <> Integer.to_string(h) <> "::size(" <> Integer.to_string(size) <> ")"
end
@@ -277,7 +277,7 @@ defimpl Inspect, for: List do
def inspect(thing, %Inspect.Opts{char_lists: lists} = opts) do
cond do
lists == :as_char_lists or (lists == :infer and printable?(thing)) ->
<< ?', Inspect.BitString.escape(IO.chardata_to_string(thing), ?') :: binary, ?' >>
<<?', Inspect.BitString.escape(IO.chardata_to_string(thing), ?')::binary, ?'>>
keyword?(thing) ->
surround_many("[", thing, "]", opts, &keyword/2)
true ->
@@ -285,6 +285,7 @@ defimpl Inspect, for: List do
end
end
@doc false
def keyword({key, value}, opts) do
concat(
key_to_binary(key) <> ": ",
@@ -292,6 +293,7 @@ defimpl Inspect, for: List do
)
end
@doc false
def keyword?([{key, _value} | rest]) when is_atom(key) do
case Atom.to_char_list(key) do
'Elixir.' ++ _ -> false
@@ -302,6 +304,19 @@ defimpl Inspect, for: List do
def keyword?([]), do: true
def keyword?(_other), do: false
@doc false
def printable?([c|cs]) when is_integer(c) and c in 32..126, do: printable?(cs)
def printable?([?\n|cs]), do: printable?(cs)
def printable?([?\r|cs]), do: printable?(cs)
def printable?([?\t|cs]), do: printable?(cs)
def printable?([?\v|cs]), do: printable?(cs)
def printable?([?\b|cs]), do: printable?(cs)
def printable?([?\f|cs]), do: printable?(cs)
def printable?([?\e|cs]), do: printable?(cs)
def printable?([?\a|cs]), do: printable?(cs)
def printable?([]), do: true
def printable?(_), do: false
## Private
defp key_to_binary(key) do
@@ -310,18 +325,6 @@ defimpl Inspect, for: List do
other -> other
end
end
defp printable?([c|cs]) when is_integer(c) and c in 32..126, do: printable?(cs)
defp printable?([?\n|cs]), do: printable?(cs)
defp printable?([?\r|cs]), do: printable?(cs)
defp printable?([?\t|cs]), do: printable?(cs)
defp printable?([?\v|cs]), do: printable?(cs)
defp printable?([?\b|cs]), do: printable?(cs)
defp printable?([?\f|cs]), do: printable?(cs)
defp printable?([?\e|cs]), do: printable?(cs)
defp printable?([?\a|cs]), do: printable?(cs)
defp printable?([]), do: true
defp printable?(_), do: false
end
defimpl Inspect, for: Tuple do
+22 -6
View File
@@ -11,6 +11,14 @@ defmodule Integer do
Returns `true` if `n` is an odd number, otherwise `false`.
Allowed in guard clauses.
## Examples
iex> Integer.is_odd(3)
true
iex> Integer.is_odd(4)
false
"""
defmacro is_odd(n) do
quote do: (unquote(n) &&& 1) == 1
@@ -22,6 +30,14 @@ defmodule Integer do
Returns `true` if `n` is an even number, otherwise `false`.
Allowed in guard clauses.
## Examples
iex> Integer.is_even(10)
true
iex> Integer.is_even(5)
false
"""
defmacro is_even(n) do
quote do: (unquote(n) &&& 1) == 0
@@ -126,14 +142,14 @@ defmodule Integer do
raise ArgumentError, "invalid base #{base}"
end
defp parse_in_base(<< ?-, bin :: binary >>, base) do
defp parse_in_base("-" <> bin, base) do
case do_parse(bin, base) do
:error -> :error
{number, remainder} -> {-number, remainder}
end
end
defp parse_in_base(<< ?+, bin :: binary >>, base) do
defp parse_in_base("+" <> bin, base) do
do_parse(bin, base)
end
@@ -141,9 +157,9 @@ defmodule Integer do
do_parse(bin, base)
end
defp do_parse(<< char, bin :: binary >>, base) do
defp do_parse(<<char, rest::binary>>, base) do
if valid_digit_in_base?(char, base) do
do_parse(bin, base, parse_digit(char, base))
do_parse(rest, base, parse_digit(char, base))
else
:error
end
@@ -151,11 +167,11 @@ defmodule Integer do
defp do_parse(_, _), do: :error
defp do_parse(<< char, rest :: binary >>, base, acc) do
defp do_parse(<<char, rest::binary>>, base, acc) do
if valid_digit_in_base?(char, base) do
do_parse(rest, base, base * acc + parse_digit(char, base))
else
{acc, << char, rest :: binary >>}
{acc, <<char, rest::binary>>}
end
end
+15 -2
View File
@@ -9,7 +9,7 @@ defmodule IO do
The majority of the functions expect char data, i.e. strings or
lists of characters and strings. In case another type is given,
it will do a conversion to string via the `String.Chars` protocol
functions will convert to string via the `String.Chars` protocol
(as shown in typespecs).
The functions starting with `bin*` expect iodata as an argument,
@@ -27,6 +27,11 @@ defmodule IO do
* `:stderr` - a shortcut for the named process `:standard_error`
provided in Erlang
IO devices maintain their position, that means subsequent calls to any
reading or writing functions will start from the place when the device
was last accessed. Position of files can be changed using the
`:file.position/2` function.
"""
@type device :: atom | pid
@@ -247,7 +252,9 @@ defmodule IO do
end
@doc """
Reads a line from the IO device. It returns:
Reads a line from the IO device.
It returns:
* `data` - the characters in the line terminated
by a line-feed (LF) or end of file (EOF)
@@ -257,6 +264,12 @@ defmodule IO do
* `{:error, reason}` - other (rare) error condition;
for instance, `{:error, :estale}` if reading from an
NFS volume
## Examples
To display "What is your name?" as a prompt and await user input:
IO.gets "What is your name?"
"""
@spec gets(device, chardata | String.Chars.t) :: chardata | nodata
def gets(device \\ group_leader(), prompt) do
+30 -2
View File
@@ -42,6 +42,34 @@ defmodule IO.ANSI do
Application.get_env(:elixir, :ansi_enabled, false)
end
@doc "Sets foreground color"
@spec color(0..255) :: String.t
def color(code) when code in 0..255, do: "\e[38;5;#{code}m"
@doc ~S"""
Sets the foreground color from individual RGB values.
Valid values for each color are in the range 0 to 5.
"""
@spec color(0..5, 0..5, 0..5) :: String.t
def color(r, g, b) when r in 0..5 and g in 0..5 and b in 0..5 do
color(16 + (36 * r) + (6 * g) + b)
end
@doc "Sets background color"
@spec color_background(0..255) :: String.t
def color_background(code) when code in 0..255, do: "\e[48;5;#{code}m"
@doc ~S"""
Sets the background color from individual RGB values.
Valid values for each color are in the range 0 to 5.
"""
@spec color_background(0..5, 0..5, 0..5) :: String.t
def color_background(r, g, b) when r in 0..5 and g in 0..5 and b in 0..5 do
color_background(16 + (36 * r) + (6 * g) + b)
end
@doc "Resets all attributes"
defsequence :reset, 0
@@ -131,12 +159,12 @@ defmodule IO.ANSI do
@doc "Clears screen"
defsequence :clear, "2", "J"
@doc "Clears line"
defsequence :clear_line, "2", "K"
defp format_sequence(other) do
raise ArgumentError, "invalid ANSI sequence specification: #{other}"
raise ArgumentError, "invalid ANSI sequence specification: #{inspect other}"
end
@doc ~S"""
+58 -45
View File
@@ -104,23 +104,15 @@ defmodule IO.ANSI.Docs do
defp process(all=[line | rest], text, indent, options) do
{stripped, count} = strip_spaces(line, 0, :infinity)
if is_table_line?(stripped) and rest != [] and is_table_line?(hd(rest)) do
write_text(text, indent, options)
process_table(all, indent, options)
else
case stripped do
<<bullet, ?\s, item :: binary>> when bullet in @bullets ->
write_text(text, indent, options)
process_list("• ", item, rest, count, indent, options)
<<d1, ?., ?\s, item :: binary>> when d1 in ?0..?9 ->
write_text(text, indent, options)
process_list(<<d1, ?., ?\s>>, item, rest, count, indent, options)
<<d1, d2, ?., ?\s, item :: binary>> when d1 in ?0..?9 and d2 in ?0..?9 ->
write_text(text, indent, options)
process_list(<<d1, d2, ?., ?\s>>, item, rest, count, indent, options)
_ ->
process(rest, [stripped | text], indent, options)
end
cond do
link_label?(stripped, count) ->
write_text([line], indent, options, true)
process(rest, text, indent, options)
table_line?(stripped) and rest != [] and table_line?(hd(rest)) ->
write_text(text, indent, options)
process_table(all, indent, options)
true ->
process_rest(stripped, rest, count, text, indent, options)
end
end
@@ -143,6 +135,22 @@ defmodule IO.ANSI.Docs do
## Lists
defp process_rest(stripped, rest, count, text, indent, options) do
case stripped do
<<bullet, ?\s, item::binary>> when bullet in @bullets ->
write_text(text, indent, options)
process_list("• ", item, rest, count, indent, options)
<<d1, ?., ?\s, item::binary>> when d1 in ?0..?9 ->
write_text(text, indent, options)
process_list(<<d1, ?., ?\s>>, item, rest, count, indent, options)
<<d1, d2, ?., ?\s, item::binary>> when d1 in ?0..?9 and d2 in ?0..?9 ->
write_text(text, indent, options)
process_list(<<d1, d2, ?., ?\s>>, item, rest, count, indent, options)
_ ->
process(rest, [stripped | text], indent, options)
end
end
defp process_list(entry, line, rest, count, indent, options) do
# The first list always win some extra padding
if indent == "", do: entry = " " <> entry
@@ -170,11 +178,11 @@ defmodule IO.ANSI.Docs do
defp process_list_next_kind(stripped, rest, count, next_count) do
case {stripped, rest} do
{<<bullet, ?\s, _ :: binary>>, _} when bullet in @bullets and next_count <= count ->
{<<bullet, ?\s, _::binary>>, _} when bullet in @bullets and next_count <= count ->
:list
{<<d1, ?., ?\s, _ :: binary>>, _} when d1 in ?0..?9 and next_count <= count ->
{<<d1, ?., ?\s, _::binary>>, _} when d1 in ?0..?9 and next_count <= count ->
:list
{<<d1, d2, ?., ?\s, _ :: binary>>, _} when d1 in ?0..?9 and d2 in ?0..?9 and next_count <= count ->
{<<d1, d2, ?., ?\s, _::binary>>, _} when d1 in ?0..?9 and d2 in ?0..?9 and next_count <= count ->
:list
{"", [" " <> _ | _]} ->
:next
@@ -254,7 +262,7 @@ defmodule IO.ANSI.Docs do
## Tables
defp process_table(lines, indent, options) do
{table, rest} = Enum.split_while(lines, &is_table_line?/1)
{table, rest} = Enum.split_while(lines, &table_line?/1)
table_lines(table, options)
newline_after_block
process(rest, [], indent, options)
@@ -334,7 +342,7 @@ defmodule IO.ANSI.Docs do
end
end
defp is_table_line?(line) do
defp table_line?(line) do
Regex.match?(~r'''
( ^ \s{0,3} \| (?: [^|]+ \|)+ \s* $ )
|
@@ -344,6 +352,14 @@ defmodule IO.ANSI.Docs do
## Helpers
defp link_label?("[" <> rest, count) when count <= 3, do: link_label?(rest)
defp link_label?(_, _), do: false
defp link_label?("]: " <> _), do: true
defp link_label?("]" <> _), do: false
defp link_label?(""), do: false
defp link_label?(<<_>> <> rest), do: link_label?(rest)
defp strip_spaces(" " <> line, acc, max) when acc < max,
do: strip_spaces(line, acc + 1, max)
defp strip_spaces(rest, acc, _max),
@@ -385,11 +401,11 @@ defmodule IO.ANSI.Docs do
{Enum.reverse(acc), []}
end
defp length_without_escape(<< ?\e, ?[, _, _, ?m, rest :: binary >>, count) do
defp length_without_escape(<<?\e, ?[, _, _, ?m>> <> rest, count) do
length_without_escape(rest, count)
end
defp length_without_escape(<< ?\e, ?[, _, ?m, rest :: binary >>, count) do
defp length_without_escape(<<?\e, ?[, _, ?m>> <> rest, count) do
length_without_escape(rest, count)
end
@@ -407,10 +423,7 @@ defmodule IO.ANSI.Docs do
end
defp escape_underlines_in_link(text) do
case Regex.match?(~r{.*(https?\S*)}, text) do
true -> Regex.replace(~r{_}, text, "\\\\_")
_ -> text
end
Regex.replace(~r{https?\S*}, text, &String.replace(&1, "_", "\\_"))
end
defp remove_square_brackets_in_link(text) do
@@ -437,11 +450,11 @@ defmodule IO.ANSI.Docs do
# Inline start
defp handle_inline(<<?*, ?*, rest :: binary>>, options) do
defp handle_inline(<<?*, ?*, rest::binary>>, options) do
handle_inline(rest, ?d, ["**"], [], options)
end
defp handle_inline(<<mark, rest :: binary>>, options) when mark in @single do
defp handle_inline(<<mark, rest::binary>>, options) when mark in @single do
handle_inline(rest, mark, [<<mark>>], [], options)
end
@@ -451,72 +464,72 @@ defmodule IO.ANSI.Docs do
# Inline delimiters
defp handle_inline(<<delimiter, ?*, ?*, rest :: binary>>, nil, buffer, acc, options)
defp handle_inline(<<delimiter, ?*, ?*, rest::binary>>, nil, buffer, acc, options)
when rest != "" and delimiter in @delimiters do
handle_inline(rest, ?d, ["**"], [delimiter, Enum.reverse(buffer)|acc], options)
end
defp handle_inline(<<delimiter, mark, rest :: binary>>, nil, buffer, acc, options)
defp handle_inline(<<delimiter, mark, rest::binary>>, nil, buffer, acc, options)
when rest != "" and delimiter in @delimiters and mark in @single do
handle_inline(rest, mark, [<<mark>>], [delimiter, Enum.reverse(buffer)|acc], options)
end
defp handle_inline(<<?`, rest :: binary>>, nil, buffer, acc, options)
defp handle_inline(<<?`, rest::binary>>, nil, buffer, acc, options)
when rest != "" do
handle_inline(rest, ?`, ["`"], [Enum.reverse(buffer)|acc], options)
end
# Clauses for handling escape
defp handle_inline(<<?\\, ?\\, ?*, ?*, rest :: binary>>, nil, buffer, acc, options)
defp handle_inline(<<?\\, ?\\, ?*, ?*, rest::binary>>, nil, buffer, acc, options)
when rest != "" do
handle_inline(rest, ?d, ["**"], [?\\, Enum.reverse(buffer)|acc], options)
end
defp handle_inline(<<?\\, ?\\, mark, rest :: binary>>, nil, buffer, acc, options)
defp handle_inline(<<?\\, ?\\, mark, rest::binary>>, nil, buffer, acc, options)
when rest != "" and mark in @single do
handle_inline(rest, mark, [<<mark>>], [?\\, Enum.reverse(buffer)|acc], options)
end
defp handle_inline(<<?\\, ?\\, rest :: binary>>, limit, buffer, acc, options) do
defp handle_inline(<<?\\, ?\\, rest::binary>>, limit, buffer, acc, options) do
handle_inline(rest, limit, [?\\|buffer], acc, options)
end
# An escape is not valid inside `
defp handle_inline(<<?\\, mark, rest :: binary>>, limit, buffer, acc, options)
defp handle_inline(<<?\\, mark, rest::binary>>, limit, buffer, acc, options)
when not(mark == limit and mark == ?`) do
handle_inline(rest, limit, [mark|buffer], acc, options)
end
# Inline end
defp handle_inline(<<?*, ?*, delimiter, rest :: binary>>, ?d, buffer, acc, options)
defp handle_inline(<<?*, ?*, delimiter, rest::binary>>, ?d, buffer, acc, options)
when delimiter in @delimiters do
handle_inline(<<delimiter, rest :: binary>>, nil, [], [inline_buffer(buffer, options)|acc], options)
handle_inline(<<delimiter, rest::binary>>, nil, [], [inline_buffer(buffer, options)|acc], options)
end
defp handle_inline(<<mark, delimiter, rest :: binary>>, mark, buffer, acc, options)
defp handle_inline(<<mark, delimiter, rest::binary>>, mark, buffer, acc, options)
when delimiter in @delimiters and mark in @single do
handle_inline(<<delimiter, rest :: binary>>, nil, [], [inline_buffer(buffer, options)|acc], options)
handle_inline(<<delimiter, rest::binary>>, nil, [], [inline_buffer(buffer, options)|acc], options)
end
defp handle_inline(<<?*, ?*, rest:: binary>>, ?d, buffer, acc, options)
defp handle_inline(<<?*, ?*, rest::binary>>, ?d, buffer, acc, options)
when rest == "" do
handle_inline(<<>>, nil, [], [inline_buffer(buffer, options)|acc], options)
end
defp handle_inline(<<mark, rest :: binary>>, mark, buffer, acc, options)
defp handle_inline(<<mark, rest::binary>>, mark, buffer, acc, options)
when rest == "" and mark in @single do
handle_inline(<<>>, nil, [], [inline_buffer(buffer, options)|acc], options)
end
defp handle_inline(<<?`, rest :: binary>>, ?`, buffer, acc, options) do
defp handle_inline(<<?`, rest::binary>>, ?`, buffer, acc, options) do
handle_inline(rest, nil, [], [inline_buffer(buffer, options)|acc], options)
end
# Catch all
defp handle_inline(<<char, rest :: binary>>, mark, buffer, acc, options) do
defp handle_inline(<<char, rest::binary>>, mark, buffer, acc, options) do
handle_inline(rest, mark, [char|buffer], acc, options)
end
+1 -1
View File
@@ -10,7 +10,7 @@ end
defmodule IO.Stream do
@moduledoc """
Defines a `IO.Stream` struct returned by `IO.stream/2` and `IO.binstream/2`.
Defines an `IO.Stream` struct returned by `IO.stream/2` and `IO.binstream/2`.
The following fields are public:
+129 -88
View File
@@ -7,11 +7,12 @@ import :elixir_bootstrap
defmodule Kernel do
@moduledoc """
`Kernel` provides the default macros and functions
Elixir imports into your environment. These macros and functions
can be skipped or cherry-picked via the `import` macro. For
instance, if you want to tell Elixir not to import the `if`
macro, you can do:
Provides the default macros and functions Elixir imports into your
environment.
These macros and functions can be skipped or cherry-picked via the
`import` macro. For instance, if you want to tell Elixir not to
import the `if` macro, you can do:
import Kernel, except: [if: 2]
@@ -1339,19 +1340,31 @@ defmodule Kernel do
:erlang.error unquote(alias).exception([])
end
_ ->
quote do
case unquote(msg) do
msg when is_binary(msg) ->
:erlang.error RuntimeError.exception(msg)
atom when is_atom(atom) ->
:erlang.error atom.exception([])
%{__struct__: struct, __exception__: true} = other when is_atom(struct) ->
:erlang.error other
other ->
message = "raise/1 expects an alias, string or exception as the first argument, got: #{inspect other}"
:erlang.error ArgumentError.exception(message)
end
generated = fn fun, var ->
{fun, [generated: true, line: -1], [{var, [], __MODULE__}]}
end
{fun, meta, [arg, [do: clauses]]} =
quote do
case unquote(msg) do
msg when unquote(generated.(:is_binary, :msg)) ->
:erlang.error RuntimeError.exception(msg)
atom when unquote(generated.(:is_atom, :atom)) ->
:erlang.error atom.exception([])
%{__struct__: struct, __exception__: true} = other when is_atom(struct) ->
:erlang.error other
other ->
message = "raise/1 expects an alias, string or exception as the first argument, got: #{inspect other}"
:erlang.error ArgumentError.exception(message)
end
end
clauses =
:lists.map(fn {:->, meta, args} ->
{:->, [generated: true] ++ Keyword.put(meta, :line, -1), args}
end, clauses)
{fun, meta, [arg, [do: clauses]]}
end
end
@@ -1590,33 +1603,47 @@ defmodule Kernel do
"""
@spec struct(module | map, Enum.t) :: map
def struct(struct, kv \\ [])
def struct(struct, []) when is_atom(struct) do
apply(struct, :__struct__, [])
end
def struct(struct, kv) when is_atom(struct) do
struct(apply(struct, :__struct__, []), kv)
end
def struct(%{__struct__: _} = struct, []) do
struct
end
def struct(%{__struct__: _} = struct, kv) do
Enum.reduce(kv, struct, fn {k, v}, acc ->
case :maps.is_key(k, acc) and k != :__struct__ do
true -> :maps.put(k, v, acc)
def struct(struct, kv \\ []) do
struct(struct, kv, fn({key, val}, acc) ->
case :maps.is_key(key, acc) and key != :__struct__ do
true -> :maps.put(key, val, acc)
false -> acc
end
end)
end
@doc """
Same as `struct/2` but raises if any of provided keys doesn't exist in the struct.
"""
@spec struct!(module | map, Enum.t) :: map | no_return
def struct!(struct, kv \\ []) do
struct(struct, kv, fn
{:__struct__, _}, acc -> acc
{key, val}, acc ->
:maps.update(key, val, acc)
end)
end
defp struct(struct, [], _fun) when is_atom(struct) do
apply(struct, :__struct__, [])
end
defp struct(struct, kv, fun) when is_atom(struct) do
struct(apply(struct, :__struct__, []), kv, fun)
end
defp struct(%{__struct__: _} = struct, [], _fun) do
struct
end
defp struct(%{__struct__: _} = struct, kv, fun) do
Enum.reduce(kv, struct, fun)
end
@doc """
Gets a value from a nested structure.
Uses the `Access` protocol to traverse the structures
Uses the `Access` module to traverse the structures
according to the given `keys`, unless the `key` is a
function.
@@ -1635,7 +1662,7 @@ defmodule Kernel do
27
In case any of entries in the middle returns `nil`, `nil` will be returned
as per the Access protocol:
as per the Access module:
iex> users = %{"john" => %{age: 27}, "meg" => %{age: 23}}
iex> get_in(users, ["unknown", :age])
@@ -1675,7 +1702,7 @@ defmodule Kernel do
@doc """
Puts a value in a nested structure.
Uses the `Access` protocol to traverse the structures
Uses the `Access` module to traverse the structures
according to the given `keys`, unless the `key` is a
function. If the key is a function, it will be invoked
as specified in `get_and_update_in/3`.
@@ -1697,7 +1724,7 @@ defmodule Kernel do
@doc """
Updates a key in a nested structure.
Uses the `Access` protocol to traverse the structures
Uses the `Access` module to traverse the structures
according to the given `keys`, unless the `key` is a
function. If the key is a function, it will be invoked
as specified in `get_and_update_in/3`.
@@ -1722,7 +1749,7 @@ defmodule Kernel do
It expects a tuple to be returned, containing the value
retrieved and the update one.
Uses the `Access` protocol to traverse the structures
Uses the `Access` module to traverse the structures
according to the given `keys`, unless the `key` is a
function.
@@ -2161,17 +2188,17 @@ defmodule Kernel do
true ->
raise ArgumentError, "cannot set attribute @#{name} inside function/macro"
false ->
arg = case name do
:behavior ->
cond do
name == :behavior ->
:elixir_errors.warn env.line, env.file,
"@behavior attribute is not supported, please use @behaviour instead"
:doc -> {env.line, arg}
:typedoc -> {env.line, arg}
:moduledoc -> {env.line, arg}
_ -> arg
:lists.member(name, [:moduledoc, :typedoc, :doc]) ->
{stack, _} = :elixir_quote.escape(env_stacktrace(env), false)
arg = {env.line, arg}
quote do: Module.put_attribute(__MODULE__, unquote(name), unquote(arg), unquote(stack))
true ->
quote do: Module.put_attribute(__MODULE__, unquote(name), unquote(arg))
end
quote do: Module.put_attribute(__MODULE__, unquote(name), unquote(arg))
end
end
@@ -2241,7 +2268,7 @@ defmodule Kernel do
defmacro binding(context \\ nil) do
in_match? = Macro.Env.in_match?(__CALLER__)
for {v, c} <- __CALLER__.vars, c == context do
{v, wrap_binding(in_match?, {v, [warn: false], c})}
{v, wrap_binding(in_match?, {v, [generated: true], c})}
end
end
@@ -2390,19 +2417,9 @@ defmodule Kernel do
do).
"""
defmacro destructure(left, right) when is_list(left) do
Enum.reduce left, right, fn item, acc ->
{:case, meta, args} =
quote do
case unquote(acc) do
[h|t] ->
unquote(item) = h
t
other when other == [] or other == nil ->
unquote(item) = nil
[]
end
end
{:case, meta, args}
quote do
unquote(left) =
Kernel.Utils.destructure(unquote(right), unquote(length(left)))
end
end
@@ -2429,16 +2446,21 @@ defmodule Kernel do
defmacro first .. last do
case is_float(first) or is_float(last) or
is_atom(first) or is_atom(last) or
is_binary(first) or is_binary(last) do
is_binary(first) or is_binary(last) or
is_list(first) or is_list(last) do
true ->
raise ArgumentError,
"ranges (left .. right) expect both sides to be integers, " <>
"ranges (first..last) expect both sides to be integers, " <>
"got: #{Macro.to_string({:.., [], [first, last]})}"
false ->
{:%{}, [], [__struct__: Elixir.Range, first: first, last: last]}
case __CALLER__.context do
nil -> quote do: Elixir.Range.new(unquote(first), unquote(last))
_ -> {:%{}, [], [__struct__: Elixir.Range, first: first, last: last]}
end
end
end
@doc """
Provides a short-circuit operator that evaluates and returns
the second expression only if the first one evaluates to `true`
@@ -2462,9 +2484,8 @@ defmodule Kernel do
false
Note that, unlike Erlang's `and` operator,
this operator accepts any expression as the first argument,
not only booleans.
Note that, unlike `and/2`, this operator accepts any expression
as the first argument, not only booleans.
"""
defmacro left && right do
quote do
@@ -2498,9 +2519,8 @@ defmodule Kernel do
iex> Enum.empty?([]) || throw(:bad)
true
Note that, unlike Erlang's `or` operator,
this operator accepts any expression as the first argument,
not only booleans.
Note that, unlike `or/2`, this operator accepts any expression
as the first argument, not only booleans.
"""
defmacro left || right do
quote do
@@ -2662,7 +2682,7 @@ defmodule Kernel do
{:%{}, [], [__struct__: Elixir.Range, first: first, last: last]} ->
in_range(left, Macro.expand(first, __CALLER__), Macro.expand(last, __CALLER__))
_ ->
raise ArgumentError, <<"invalid args for operator in, it expects a compile time list ",
raise ArgumentError, <<"invalid args for operator \"in\", it expects a compile time list ",
"or range on the right side when used in guard expressions, got: ",
Macro.to_string(right) :: binary>>
end
@@ -3036,7 +3056,7 @@ defmodule Kernel do
end
Foo.bar #=> 3
Foo.sum(1, 2) #=> ** (UndefinedFunctionError) undefined function: Foo.sum/2
Foo.sum(1, 2) #=> ** (UndefinedFunctionError) undefined function Foo.sum/2
"""
defmacro defp(call, expr \\ nil) do
@@ -3190,7 +3210,7 @@ defmodule Kernel do
"""
defmacro defstruct(fields) do
quote bind_quoted: [fields: fields] do
fields = Kernel.Def.struct(__MODULE__, fields)
fields = Kernel.Utils.defstruct(__MODULE__, fields)
@struct fields
case Module.get_attribute(__MODULE__, :derive) do
@@ -3355,15 +3375,16 @@ defmodule Kernel do
The real benefit of protocols comes when mixed with structs.
For instance, Elixir ships with many data types implemented as
structs, like `HashDict` and `HashSet`. We can implement the
`Blank` protocol for those types as well:
structs, like `MapSet`. We can implement the `Blank` protocol
for those types as well:
defimpl Blank, for: [HashDict, HashSet] do
defimpl Blank, for: MapSet do
def blank?(enum_like), do: Enum.empty?(enum_like)
end
When implementing a protocol for a struct, the `:for` option can be omitted if
the `defimpl` call is inside the module that defines the struct:
When implementing a protocol for a struct, the `:for` option can
be omitted if the `defimpl` call is inside the module that defines
the struct:
defmodule User do
defstruct [:email, :name]
@@ -3599,17 +3620,31 @@ defmodule Kernel do
please define it a module which will be imported accordingly.
"""
defmacro use(module, opts \\ []) do
expanded = Macro.expand(module, __CALLER__)
case is_atom(expanded) do
false ->
raise ArgumentError, "invalid arguments for use, expected an atom or alias as argument"
true ->
calls = Enum.map(expand_aliases(module, __CALLER__), fn
expanded when is_atom(expanded) ->
quote do
require unquote(expanded)
unquote(expanded).__using__(unquote(opts))
end
end
_otherwise ->
raise ArgumentError, "invalid arguments for use, expected a compile time atom or alias, got: #{Macro.to_string(module)}"
end)
quote(do: (unquote_splicing calls))
end
defp expand_aliases({{:., _, [base, :{}]}, _, refs}, env) do
base = Macro.expand(base, env)
Enum.map(refs, fn
{:__aliases__, _, ref} ->
Module.concat([base | ref])
ref when is_atom(ref) ->
Module.concat(base, ref)
other -> other
end)
end
defp expand_aliases(module, env) do
[Macro.expand(module, env)]
end
@doc """
@@ -3665,7 +3700,10 @@ defmodule Kernel do
raise ArgumentError, "expected to: to be given as argument"
for fun <- List.wrap(funs) do
{name, args, as, as_args} = Kernel.Def.delegate(fun, opts)
{name, args, as, as_args} = Kernel.Utils.defdelegate(fun, opts, __ENV__)
unless Module.get_attribute(__MODULE__, :doc) do
@doc "See `#{inspect target}.#{as}/#{:erlang.length as_args}`."
end
def unquote(name)(unquote_splicing(args)) do
unquote(target).unquote(as)(unquote_splicing(as_args))
end
@@ -3691,7 +3729,7 @@ defmodule Kernel do
"""
defmacro sigil_S(term, modifiers)
defmacro sigil_S(string, []), do: string
defmacro sigil_S({:<<>>, _, [binary]}, []) when is_binary(binary), do: binary
@doc ~S"""
Handles the sigil `~s`.
@@ -3712,6 +3750,9 @@ defmodule Kernel do
"""
defmacro sigil_s(term, modifiers)
defmacro sigil_s({:<<>>, _, [piece]}, []) when is_binary(piece) do
Macro.unescape_string(piece)
end
defmacro sigil_s({:<<>>, line, pieces}, []) do
{:<<>>, line, Macro.unescape_tokens(pieces)}
end
+12 -1
View File
@@ -146,7 +146,18 @@ defmodule Kernel.CLI do
# Parse shared options
defp parse_shared([opt|_t], _config) when opt in ["-v", "--version"] do
IO.puts "Elixir #{System.version}"
if function_exported?(IEx, :started?, 0) and IEx.started? do
IO.puts "IEx #{System.version}"
else
IO.puts :erlang.system_info(:system_version)
{:ok, v} = Version.parse(System.version)
case v.pre do
[] -> IO.puts "Elixir #{System.version}"
_ -> IO.puts "Elixir #{System.version} (#{System.build_info().revision})"
end
end
System.halt 0
end
+1 -1
View File
@@ -14,7 +14,7 @@ defmodule Kernel.ErrorHandler do
end
def release() do
# On release, no further allow elixir_ensure_compiled
# On release, no longer allow elixir_ensure_compiled
# directives and revert to the original error handler.
# Note we should not delete the elixir_compiler_pid though,
# as we still want to send notifications to the compiler.
+1 -1
View File
@@ -1,4 +1,4 @@
# This is a module Elixir responsible for tracking
# This is an Elixir module responsible for tracking
# the usage of aliases, imports and requires in the Elixir scope.
#
# The implementation simply stores dispatch information in an
+2 -2
View File
@@ -55,9 +55,9 @@ defmodule Kernel.ParallelCompiler do
result = spawn_compilers(files, files, path, options, [], [], schedulers, [])
# In case --warning-as-errors is enabled and there was a warning,
# compilation status will be set to error and we fail with CompileError
# compilation status will be set to error.
case :elixir_code_server.call({:compilation_status, compiler_pid}) do
:ok ->
:ok ->
result
:error ->
IO.puts :stderr, "Compilation failed due to warnings while using the --warnings-as-errors option"
+24 -19
View File
@@ -3,8 +3,6 @@ defmodule Kernel.ParallelRequire do
A module responsible for requiring files in parallel.
"""
defmacrop default_callback, do: quote(do: fn x -> x end)
@doc """
Requires the given files.
@@ -13,9 +11,21 @@ defmodule Kernel.ParallelRequire do
Returns the modules generated by each required file.
"""
def files(files, callback \\ default_callback) do
def files(files, callback \\ fn x -> x end) do
compiler_pid = self()
:elixir_code_server.cast({:reset_warnings, compiler_pid})
schedulers = max(:erlang.system_info(:schedulers_online), 2)
spawn_requires(files, [], callback, schedulers, [])
result = spawn_requires(files, [], callback, schedulers, [])
# In case --warning-as-errors is enabled and there was a warning,
# compilation status will be set to error.
case :elixir_code_server.call({:compilation_status, compiler_pid}) do
:ok ->
result
:error ->
IO.puts :stderr, "Compilation failed due to warnings while using the --warnings-as-errors option"
exit({:shutdown, 1})
end
end
defp spawn_requires([], [], _callback, _schedulers, result), do: result
@@ -29,22 +39,9 @@ defmodule Kernel.ParallelRequire do
end
defp spawn_requires([h|t], waiting, callback, schedulers, result) do
parent = self
compiler_pid = :erlang.get(:elixir_compiler_pid)
ensure_compiled = :erlang.get(:elixir_ensure_compiled)
{:error_handler, handler} = :erlang.process_info(parent, :error_handler)
parent = self()
{pid, ref} = :erlang.spawn_monitor fn ->
if compiler_pid != :undefined do
:erlang.put(:elixir_compiler_pid, compiler_pid)
end
if ensure_compiled != :undefined do
:erlang.put(:elixir_ensure_compiled, ensure_compiled)
end
:erlang.process_flag(:error_handler, handler)
:erlang.put(:elixir_compiler_pid, parent)
exit(try do
new = Code.require_file(h) || []
@@ -75,6 +72,14 @@ defmodule Kernel.ParallelRequire do
end
end
spawn_requires(files, waiting, callback, schedulers, result)
{:module_available, child, ref, _, _, _} ->
send(child, {ref, :ack})
spawn_requires(files, waiting, callback, schedulers, result)
{:struct_available, _} ->
spawn_requires(files, waiting, callback, schedulers, result)
{:waiting, :struct, child, ref, _} ->
send(child, {ref, :release})
spawn_requires(files, waiting, callback, schedulers, result)
end
end
end
+101 -66
View File
@@ -1,12 +1,11 @@
defmodule Kernel.SpecialForms do
@moduledoc """
In this module we define Elixir special forms. Special forms
cannot be overridden by the developer and are the basic
building blocks of Elixir code.
Special forms are the basic building blocks of Elixir, and therefore
they cannot be overridden by the developer.
Some of those forms are lexical (like `alias`, `case`, etc).
The macros `{}` and `<<>>` are also special forms used to define
tuple and binary data structures respectively.
We define them in this module. Some of these forms are lexical (like
`alias`, `case`, etc). The macros `{}` and `<<>>` are also special
forms used to define tuple and binary data structures respectively.
This module also documents Elixir's pseudo variables (`__ENV__`,
`__MODULE__`, `__DIR__` and `__CALLER__`). Pseudo variables return
@@ -88,7 +87,7 @@ defmodule Kernel.SpecialForms do
%{:a => :c}
Notice the update syntax requires the given keys to exist.
Trying to update a key that does not exist will raise an `ArgumentError`.
Trying to update a key that does not exist will raise an `KeyError`.
## AST representation
@@ -96,8 +95,8 @@ defmodule Kernel.SpecialForms do
always represented internally as a list of two-items tuples
for simplicity:
iex> quote do: %{:a => :b, c: :d}
{:%{}, [], [{:a, :b}, {:c, :d}]}
iex> quote do: %{"a" => :b, c: :d}
{:%{}, [], [{"a", :b}, {:c, :d}]}
"""
defmacro unquote(:%{})(args)
@@ -169,8 +168,8 @@ defmodule Kernel.SpecialForms do
## Examples
iex> << 1, 2, 3 >>
<< 1, 2, 3 >>
iex> <<1, 2, 3>>
<<1, 2, 3>>
## Types
@@ -193,7 +192,7 @@ defmodule Kernel.SpecialForms do
<<1, 2, 3>>
Elixir also accepts by default the segment to be a literal
string or a literal char list, which are by expanded to integers:
string or a literal char list, which are by default expanded to integers:
iex> <<0, "foo">>
<<0, 102, 111, 111>>
@@ -207,15 +206,15 @@ defmodule Kernel.SpecialForms do
We can solve this by explicitly tagging it as a binary:
iex> rest = "oo"
iex> <<102, rest :: binary>>
iex> <<102, rest::binary>>
"foo"
The utf8, utf16, and utf32 types are for unicode codepoints. They
can also be applied to literal strings and char lists:
iex> <<"foo" :: utf16>>
iex> <<"foo"::utf16>>
<<0, 102, 0, 111, 0, 111>>
iex> <<"foo" :: utf32>>
iex> <<"foo"::utf32>>
<<0, 0, 0, 102, 0, 0, 0, 111, 0, 0, 0, 111>>
## Options
@@ -223,13 +222,13 @@ defmodule Kernel.SpecialForms do
Many options can be given by using `-` as separator. Order is
arbitrary, so the following are all equivalent:
<<102 :: integer-native, rest :: binary>>
<<102 :: native-integer, rest :: binary>>
<<102 :: unsigned-big-integer, rest :: binary>>
<<102 :: unsigned-big-integer-size(8), rest :: binary>>
<<102 :: unsigned-big-integer-8, rest :: binary>>
<<102 :: 8-integer-big-unsigned, rest :: binary>>
<<102, rest :: binary>>
<<102::integer-native, rest::binary>>
<<102::native-integer, rest::binary>>
<<102::unsigned-big-integer, rest::binary>>
<<102::unsigned-big-integer-size(8), rest::binary>>
<<102::unsigned-big-integer-8, rest::binary>>
<<102::8-integer-big-unsigned, rest::binary>>
<<102, rest::binary>>
### Unit and Size
@@ -268,9 +267,9 @@ defmodule Kernel.SpecialForms do
when passing integer values:
iex> x = 1
iex> << x :: 8 >> == << x :: size(8) >>
iex> <<x::8>> == <<x::size(8)>>
true
iex> << x :: 8 * 4 >> == << x :: size(8)-unit(4) >>
iex> <<x::8 * 4>> == <<x::size(8)-unit(4)>>
true
This syntax reflects the fact the effective size is given by
@@ -293,11 +292,11 @@ defmodule Kernel.SpecialForms do
Integers can be `signed` or `unsigned`, defaulting to `unsigned`.
iex> <<int::integer>> = <<-100>>
iex> <<int::integer>> = <<-100>>
<<156>>
iex> int
156
iex> <<int::integer-signed>> = <<-100>>
iex> <<int::integer-signed>> = <<-100>>
<<156>>
iex> int
-100
@@ -305,7 +304,7 @@ defmodule Kernel.SpecialForms do
`signed` and `unsigned` are only used for matching binaries (see below) and
are only used for integers.
iex> <<-100 :: signed, _rest :: binary>> = <<-100, "foo">>
iex> <<-100::signed, _rest::binary>> = <<-100, "foo">>
<<156, 102, 111, 111>>
### Endianness
@@ -541,7 +540,7 @@ defmodule Kernel.SpecialForms do
defmacro require(module, opts)
@doc """
Imports function and macros from other modules.
Imports functions and macros from other modules.
`import` allows one to easily access functions or macros from
others modules without using the qualified name.
@@ -821,7 +820,7 @@ defmodule Kernel.SpecialForms do
Returning 5
Returning 5
25
Got 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
@@ -881,7 +880,7 @@ defmodule Kernel.SpecialForms do
import Math
squared(5)
x #=> ** (RuntimeError) undefined function or variable: x
x #=> ** (CompileError) undefined variable x or undefined function x/0
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
@@ -940,7 +939,7 @@ defmodule Kernel.SpecialForms do
Hygiene.write
Hygiene.read
#=> ** (RuntimeError) undefined function or variable: a
#=> ** (RuntimeError) undefined variable a or undefined function a/0
For such, you can explicitly pass the current module scope as
argument:
@@ -969,34 +968,34 @@ defmodule Kernel.SpecialForms do
Consider the following example:
defmodule Hygiene do
alias HashDict, as: D
alias Map, as: M
defmacro no_interference do
quote do: D.new
quote do: M.new
end
end
require Hygiene
Hygiene.no_interference #=> #HashDict<[]>
Hygiene.no_interference #=> %{}
Notice that, even though the alias `D` is not available
Notice that, even though the alias `M` is not available
in the context the macro is expanded, the code above works
because `D` still expands to `HashDict`.
because `M` still expands to `Map`.
Similarly, even if we defined an alias with the same name
before invoking a macro, it won't affect the macro's result:
defmodule Hygiene do
alias HashDict, as: D
alias Map, as: M
defmacro no_interference do
quote do: D.new
quote do: M.new
end
end
require Hygiene
alias SomethingElse, as: D
Hygiene.no_interference #=> #HashDict<[]>
alias SomethingElse, as: M
Hygiene.no_interference #=> %{}
In some cases, you want to access an alias or a module defined
in the caller. For such, you can use the `alias!` macro:
@@ -1033,54 +1032,54 @@ defmodule Kernel.SpecialForms do
following code:
defmodule Hygiene do
defmacrop get_size do
defmacrop get_length do
quote do
size("hello")
length([1,2,3])
end
end
def return_size do
import Kernel, except: [size: 1]
get_size
def return_length do
import Kernel, except: [length: 1]
get_length
end
end
Hygiene.return_size #=> 5
Hygiene.return_length #=> 3
Notice how `return_size` returns 5 even though the `size/1`
function is not imported. In fact, even if `return_size` imported
a function from another module, it wouldn't affect the function
result:
Notice how `return_length` returns 5 even though the `length/1`
function is not imported. In fact, even if `return_length`
imported a function with the same name and arity from another
module, it wouldn't affect the function result:
def return_size do
import Dict, only: [size: 1]
get_size
def return_length do
import String, only: [length: 1]
get_length
end
Calling this new `return_size` will still return 5 as result.
Calling this new `return_length` will still return 3 as result.
Elixir is smart enough to delay the resolution to the latest
moment possible. So, if you call `size("hello")` inside quote,
but no `size/1` function is available, it is then expanded in
moment possible. So, if you call `length([1, 2, 3])` inside quote,
but no `length/1` function is available, it is then expanded in
the caller:
defmodule Lazy do
defmacrop get_size do
import Kernel, except: [size: 1]
defmacrop get_length do
import Kernel, except: [length: 1]
quote do
size([a: 1, b: 2])
length("hello")
end
end
def return_size do
import Kernel, except: [size: 1]
import Dict, only: [size: 1]
get_size
def return_length do
import Kernel, except: [length: 1]
import String, only: [length: 1]
get_length
end
end
Lazy.return_size #=> 2
Lazy.return_length #=> 5
## Stacktrace information
@@ -1252,7 +1251,7 @@ defmodule Kernel.SpecialForms do
Note generators can also be used to filter as it removes any value
that doesn't match the left side of `<-`:
iex> for {:user, name} <- [user: "john", admin: "john", user: "meg"] do
iex> for {:user, name} <- [user: "john", admin: "james", user: "meg"] do
...> String.upcase(name)
...> end
["JOHN", "MEG"]
@@ -1291,6 +1290,42 @@ defmodule Kernel.SpecialForms do
"""
defmacro for(args)
@doc """
Used to combine matching clauses.
Let's start with an example:
iex> opts = %{width: 10, height: 15}
iex> with {:ok, width} <- Map.fetch(opts, :width),
...> {:ok, height} <- Map.fetch(opts, :height),
...> do: {:ok, width * height}
{:ok, 150}
If all clauses match, the `do` block is executed, returning its result.
Otherwise the chain is aborted and a non-matched value is returned:
iex> opts = %{width: 10}
iex> with {:ok, width} <- Map.fetch(opts, :width),
...> {:ok, height} <- Map.fetch(opts, :height),
...> do: {:ok, width * height}
:error
Similarly to `for`/1, variables bound inside `with/1` won't leak,
and also it allows "bare expressions":
iex> width = nil
iex> opts = %{width: 10, height: 15}
iex> with {:ok, width} <- Map.fetch(opts, :width),
...> double_width = width * 2,
...> {:ok, height} <- Map.fetch(opts, :height),
...> do: {:ok, double_width * height}
{:ok, 300}
iex> width
nil
"""
defmacro with(args)
@doc """
Defines an anonymous function.
@@ -1676,7 +1711,7 @@ defmodule Kernel.SpecialForms do
This means the VM no longer needs to keep the stacktrace once inside
an else clause and so tail recursion is possible when using a `try`
with a tail call as the final call inside an else clause. The same
is `true` for `rescue` and `catch` clauses.
is true for `rescue` and `catch` clauses.
## Variable handling
+26 -206
View File
@@ -1,173 +1,5 @@
defmodule Kernel.Typespec do
@moduledoc ~S"""
Provides macros and functions for working with typespecs.
Elixir comes with a notation for declaring types and specifications. Elixir is
dynamically typed, as such typespecs are never used by the compiler to
optimize or modify code. Still, using typespecs is useful as documentation and
tools such as [Dialyzer](http://www.erlang.org/doc/man/dialyzer.html) can
analyze the code with typespecs to find bugs.
The attributes `@type`, `@opaque`, `@typep`, `@spec`, `@callback` and
`@macrocallback` available in modules are handled by the equivalent macros
defined by this module. See sub-sections "Defining a type" and "Defining a
specification" below.
## Types and their syntax
The type syntax provided by Elixir is fairly similar to [the one in
Erlang](http://www.erlang.org/doc/reference_manual/typespec.html).
Most of the built-in types provided in Erlang (for example, `pid()`) are
expressed the same way: `pid()` or simply `pid`. Parameterized types are also
supported (`list(integer)`) and so are remote types (`Enum.t`).
Integers and atom literals are allowed as types (ex. `1`, `:atom` or
`false`). All other types are built of unions of predefined types. Certain
shorthands are allowed, such as `[...]`, `<<>>` and `{...}`.
### Basic types
type :: any() # the top type, the set of all terms
| none() # the bottom type, contains no terms
| pid()
| port()
| reference()
| tuple()
| atom()
| integer()
| non_neg_integer() # 0, 1, 2, 3, ...
| pos_integer() # 1, 2, 3, ...
| neg_integer() # ..., -3, -2, -1
| float()
| map()
| struct()
| list(type)
| nonempty_list(type)
| improper_list(type1, type2)
| maybe_improper_list(type1, type2)
| Literals # Described in section "Literals"
| Builtin # Described in section "Builtin-types"
| Remotes # Described in section "Remotes"
### Literals
The following literals are also supported in typespecs:
type :: :atom ## Atoms
| 1 ## Integers
| 1..10 ## Integers from 1 to 10
| 1.0 ## Floats
| <<>> ## Bitstrings
| <<_ :: size>> # size is 0 or a positive integer
| <<_ :: _ * unit>> # unit is an integer from 1 to 256
| <<_ :: size * unit>>
| [type] ## Lists
| [] # empty list
| [...] # shorthand for nonempty_list(any())
| [type, ...] # shorthand for nonempty_list(type)
| [key: type] # keyword lists
| (... -> type) ## Functions
| (... -> type) # any arity, returns type
| (() -> type) # 0-arity, returns type
| (type1, type2 -> type) # 2-arity, returns type
| %{} ## Maps
| %{key: type} # map with key :key with value of type
| %{type1 => type2} # map with keys of type1 with values of type2
| %SomeStruct{}
| %SomeStruct{key: type}
| {} ## Tuples
| {:ok, type} # two element tuple with an atom and any type
### Built-in types
Those types are also provided by Elixir as shortcuts on top of the
basic and literal types.
Built-in type | Defined as
:---------------------- | :---------
`term()` | `any()`
`binary()` | `<< _ :: _ * 8 >>`
`bitstring()` | `<< _ :: _ * 1 >>`
`boolean()` | `false` \| `true`
`byte()` | `0..255`
`char()` | `0..0x10ffff`
`number()` | `integer()` \| `float()`
`char_list()` | `[char()]`
`list()` | `[any()]`
`maybe_improper_list()` | `maybe_improper_list(any(), any())`
`nonempty_list()` | `nonempty_list(any())`
`iodata()` | `iolist()` \| `binary()`
`iolist()` | `maybe_improper_list(byte()` \| `binary()` \| `iolist(), binary()` \| `[])`
`module()` | `atom()` \| `tuple()`
`arity()` | `0..255`
`mfa()` | `{atom(), atom(), arity()}`
`node()` | `atom()`
`timeout()` | `:infinity` \| `non_neg_integer()`
`no_return()` | `none()`
`fun()` | `(... -> any)`
`struct()` | `%{__struct__: atom()}`
### Remote types
Any module is also able to define their own type and the modules in
Elixir are no exception. For example, a string is `String.t`, a
range is `Range.t`, any enumerable can be `Enum.t` and so on.
## Defining a type
@type type_name :: type
@typep type_name :: type
@opaque type_name :: type
A type defined with `@typep` is private. An opaque type, defined with
`@opaque` is a type where the internal structure of the type will not be
visible, but the type is still public.
Types can be parameterized by defining variables as parameters, these variables
can then be used to define the type.
@type dict(key, value) :: [{key, value}]
## Defining a specification
@spec function_name(type1, type2) :: return_type
@callback function_name(type1, type2) :: return_type
@macrocallback macro_name(type1, type2) :: Macro.t
Callbacks are used to define the callbacks functions of behaviours (see
`Behaviour`).
Guards can be used to restrict type variables given as arguments to the
function.
@spec function(arg) :: [arg] when arg: atom
Type variables with no restriction can also be defined.
@spec function(arg) :: [arg] when arg: var
Specifications can be overloaded just like ordinary functions.
@spec function(integer) :: atom
@spec function(atom) :: integer
## Notes
Elixir discourages the use of type `string` as it might be confused with
binaries which are referred to as "strings" in Elixir (as opposed to character
lists). In order to use the type that is called `string` in Erlang, one has to
use the `char_list` type which is a synonym for `string`. If you use `string`,
you'll get a warning from the compiler.
If you want to refer to the "string" type (the one operated on by functions in
the `String` module), use `String.t` type instead.
"""
@moduledoc false
@doc """
Defines a type.
@@ -508,13 +340,9 @@ defmodule Kernel.Typespec do
end
defp get_doc_info(table, attr, caller) do
# TODO: Use :ets.take/2 with Erlang 18
case :ets.lookup(table, attr) do
[{^attr, {line, doc}}] ->
:ets.delete(table, attr)
{line, doc}
[] ->
{caller.line, nil}
case :ets.take(table, attr) do
[{^attr, {line, doc}}] -> {line, doc}
[] -> {caller.line, nil}
end
end
@@ -846,9 +674,9 @@ defmodule Kernel.Typespec do
defp erl_to_ex_var(var) do
case Atom.to_string(var) do
<<"_", c :: binary-size(1), rest :: binary>> ->
<<"_", c::binary-1, rest::binary>> ->
String.to_atom("_#{String.downcase(c)}#{rest}")
<<c :: binary-size(1), rest :: binary>> ->
<<c::binary-1, rest::binary>> ->
String.to_atom("#{String.downcase(c)}#{rest}")
end
end
@@ -894,18 +722,17 @@ defmodule Kernel.Typespec do
{:type, line(meta), :map, :any}
end
defp typespec({:%{}, meta, fields}, vars, caller) do
defp typespec({:%{}, meta, fields} = map, vars, caller) do
fields =
# TODO: Remove else once we support only OTP >18
if :erlang.system_info(:otp_release) >= '18' do
:lists.map(fn {k, v} ->
:lists.map(fn
{k, v} ->
{:type, line(meta), :map_field_assoc, [typespec(k, vars, caller), typespec(v, vars, caller)]}
end, fields)
else
:lists.map(fn {k, v} ->
{:type, line(meta), :map_field_assoc, typespec(k, vars, caller), typespec(v, vars, caller)}
end, fields)
end
{:|, _, [_, _]} ->
compile_error(caller, "invalid map specification. When using the | operator in the map key, " <>
"make sure to wrap the key type in parentheses: #{Macro.to_string(map)}")
_ ->
compile_error(caller, "invalid map specification: #{Macro.to_string(map)}")
end, fields)
{:type, line(meta), :map, fields}
end
@@ -921,14 +748,16 @@ defmodule Kernel.Typespec do
module.__struct__
end
struct = struct |> Map.from_struct |> Map.to_list
unless Keyword.keyword?(fields) do
compile_error(caller, "expected key-value pairs in struct #{Macro.to_string(name)}")
end
struct =
types =
:lists.map(fn {field, _} ->
{field, quote do: term()}
end, Map.to_list(struct))
{field, Keyword.get(fields, field, quote(do: term()))}
end, struct)
:lists.foreach(fn {field, _} ->
unless Keyword.has_key?(struct, field) do
@@ -936,8 +765,7 @@ defmodule Kernel.Typespec do
end
end, fields)
fields = Keyword.merge(struct, [__struct__: module] ++ fields)
typespec({:%{}, meta, fields}, vars, caller)
typespec({:%{}, meta, [__struct__: module] ++ types}, vars, caller)
end
# Handle records
@@ -948,9 +776,9 @@ defmodule Kernel.Typespec do
defp typespec({:record, meta, [atom, fields]}, vars, caller) do
case Macro.expand({atom, [], [{atom, [], []}]}, caller) do
keyword when is_list(keyword) ->
keyword =
types =
:lists.map(fn {field, _} ->
{field, quote do: term()}
Keyword.get(fields, field, quote(do: term()))
end, keyword)
:lists.foreach(fn {field, _} ->
@@ -959,9 +787,6 @@ defmodule Kernel.Typespec do
end
end, fields)
fields = Keyword.merge(keyword, fields)
types = Keyword.values(fields)
typespec({:{}, meta, [atom|types]}, vars, caller)
_ ->
compile_error(caller, "unknown record #{inspect atom}")
@@ -1068,14 +893,9 @@ defmodule Kernel.Typespec do
defp typespec({name, meta, arguments}, vars, caller) do
arguments = for arg <- arguments, do: typespec(arg, vars, caller)
if :erlang.system_info(:otp_release) >= '18' do
arity = length(arguments)
type = if :erl_internal.is_type(name, arity), do: :type, else: :user_type
{type, line(meta), name, arguments}
else
{:type, line(meta), name, arguments}
end
arity = length(arguments)
type = if :erl_internal.is_type(name, arity), do: :type, else: :user_type
{type, line(meta), name, arguments}
end
# Handle literals
@@ -1,10 +1,19 @@
defmodule Kernel.Def do
import Kernel, except: [destructure: 2, defdelegate: 2, defstruct: 2]
defmodule Kernel.Utils do
@moduledoc false
@doc """
Callback invoked at compile time for `defdelegate`.
"""
def delegate(fun, opts) do
def destructure(list, count) when is_list(list), do: destructure_list(list, count)
def destructure(nil, count), do: destructure_nil(count)
defp destructure_list(_, 0), do: []
defp destructure_list([], count), do: destructure_nil(count)
defp destructure_list([h|t], count), do: [h|destructure_list(t, count - 1)]
defp destructure_nil(0), do: []
defp destructure_nil(count), do: [nil|destructure_nil(count - 1)]
def defdelegate(fun, opts, env) do
append_first = Keyword.get(opts, :append_first, false)
{name, args} =
@@ -13,6 +22,8 @@ defmodule Kernel.Def do
_ -> raise ArgumentError, "invalid syntax in defdelegate #{Macro.to_string(fun)}"
end
:ok = check_defdelegate_args(args, env)
as_args =
case append_first and args != [] do
true -> tl(args) ++ [hd(args)]
@@ -23,10 +34,18 @@ defmodule Kernel.Def do
{name, args, as, as_args}
end
@doc """
Callback invoked at compile time for `defstruct`.
"""
def struct(module, fields) do
# TODO: Convert this to an error on 1.3
defp check_defdelegate_args([], _env),
do: :ok
defp check_defdelegate_args([{var, _, mod}|rest], env) when is_atom(var) and is_atom(mod),
do: check_defdelegate_args(rest, env)
defp check_defdelegate_args([code|_], env) do
:elixir_errors.warn(env.line, env.file,
"defdelegate/2 will only accept variable names in upcoming versions, " <>
"got: #{Macro.to_string(code)}")
end
def defstruct(module, fields) do
case fields do
fs when is_list(fs) -> :ok
other ->
+198 -95
View File
@@ -1,13 +1,15 @@
defmodule Keyword do
@moduledoc """
A keyword is a list of tuples where the first element
of the tuple is an atom and the second element can be
any value.
A set of functions for working with keywords.
A keyword is a list of 2-element tuples where the first
element 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 dictionary. However most of the functions in this module
behave exactly as a dictionary and mimic the API defined
by the `Dict` behaviour.
behave exactly as a dictionary so they work similarly to
the functions you would find in the `Map` module.
For example, `Keyword.get/3` will get the first entry matching
the given key, regardless if duplicated entries exist.
@@ -27,7 +29,6 @@ defmodule Keyword do
"""
@compile :inline_list_funcs
@behaviour Dict
@type key :: atom
@type value :: any
@@ -37,18 +38,38 @@ defmodule Keyword do
@doc """
Returns `true` if `term` is a keyword list; otherwise returns `false`.
## Examples
iex> Keyword.keyword?([])
true
iex> Keyword.keyword?([a: 1])
true
iex> Keyword.keyword?([{Foo, 1}])
true
iex> Keyword.keyword?([{}])
false
iex> Keyword.keyword?([:key])
false
iex> Keyword.keyword?(%{})
false
"""
@spec keyword?(term) :: boolean
def keyword?(term)
def keyword?([{key, _value} | rest]) when is_atom(key),
do: keyword?(rest)
def keyword?([{key, _value} | rest]) when is_atom(key), do: keyword?(rest)
def keyword?([]), do: true
def keyword?(_other), do: false
@doc """
Returns an empty keyword list, i.e. an empty list.
## Examples
iex> Keyword.new()
[]
"""
@spec new :: t
def new, do: []
@@ -57,13 +78,16 @@ defmodule Keyword do
Creates a keyword from an enumerable.
Duplicated entries are removed, the latest one prevails.
Unlike `Enum.into(enumerable, [])`,
`Keyword.new(enumerable)` guarantees the keys are unique.
Unlike `Enum.into(enumerable, [])`, `Keyword.new(enumerable)`
guarantees the keys are unique.
## Examples
iex> Keyword.new([{:b, 1}, {:a, 2}])
[a: 2, b: 1]
[b: 1, a: 2]
iex> Keyword.new([{:a, 1}, {:a, 2}, {:a, 3}])
[a: 3]
"""
@spec new(Enum.t) :: t
@@ -81,37 +105,45 @@ defmodule Keyword do
## Examples
iex> Keyword.new([:a, :b], fn (x) -> {x, x} end)
[b: :b, a: :a]
[a: :a, b: :b]
"""
@spec new(Enum.t, (term -> {key, value})) :: t
def new(pairs, transform) when is_function(transform, 1) do
def new(pairs, transform) do
fun = fn el, acc ->
{k, v} = transform.(el)
put_new(acc, k, v)
end
:lists.foldr(fun, [], Enum.reverse(pairs))
:lists.foldl(fun, [], Enum.reverse(pairs))
end
@doc """
Gets the value for a specific `key`.
If `key` does not exist, return the default value (`nil` if no default value).
If `key` does not exist, return the default value
(`nil` if no default value).
If duplicated entries exist, the first one is returned.
Use `get_values/2` to retrieve all entries.
## Examples
iex> Keyword.get([], :a)
nil
iex> Keyword.get([a: 1], :a)
1
iex> Keyword.get([a: 1], :b)
nil
iex> Keyword.get([a: 1], :b, 3)
3
With duplicated keys:
iex> Keyword.get([a: 1, a: 2], :a, 3)
1
iex> Keyword.get([a: 1, a: 2], :b, 3)
3
"""
@spec get(t, key) :: value
@spec get(t, key, value) :: value
@@ -128,7 +160,7 @@ defmodule Keyword do
If `key` does not exist, lazily evaluates `fun` and returns its result.
This is useful if the default value is very expensive to calculate or
generally difficult to set-up and tear-down again.
generally difficult to setup and teardown again.
If duplicated entries exist, the first one is returned.
Use `get_values/2` to retrieve all entries.
@@ -138,12 +170,12 @@ defmodule Keyword do
iex> keyword = [a: 1]
iex> fun = fn ->
...> # some expensive operation here
...> :result
...> 13
...> end
iex> Keyword.get_lazy(keyword, :a, fun)
1
iex> Keyword.get_lazy(keyword, :b, fun)
:result
13
"""
@spec get_lazy(t, key, (() -> value)) :: value
@@ -168,13 +200,18 @@ defmodule Keyword do
## Examples
iex> Keyword.get_and_update [a: 1], :a, fn(current_value) ->
iex> Keyword.get_and_update([a: 1], :a, fn current_value ->
...> {current_value, "new value!"}
...> end
...> end)
{1, [a: "new value!"]}
iex> Keyword.get_and_update([a: 1], :b, fn current_value ->
...> {current_value, "new value!"}
...> end)
{nil, [b: "new value!", a: 1]}
"""
@spec get_and_update(t, key, (value -> {value, value})) :: {value, t}
@spec get_and_update(t, key, (value -> {get, value})) :: {get, t} when get: term
def get_and_update(keywords, key, fun)
when is_list(keywords) and is_atom(key),
do: get_and_update(keywords, [], key, fun)
@@ -192,6 +229,48 @@ defmodule Keyword do
{get, [{key, update}|:lists.reverse(acc)]}
end
@doc """
Gets the value from `key` and updates it. Raises if there is no `key`.
This `fun` argument receives the value of `key` and must return a
two-elements tuple: the "get" value (the retrieved value, which can be
operated on before being returned) and the new value to be stored under
`key`.
The returned value is a tuple with the "get" value returned by `fun` and a new
keyword list with the updated value under `key`.
## Examples
iex> Keyword.get_and_update!([a: 1], :a, fn(current_value) ->
...> {current_value, "new value!"}
...> end)
{1, [a: "new value!"]}
iex> Keyword.get_and_update!([a: 1], :b, fn current_value ->
...> {current_value, "new value!"}
...> end)
** (KeyError) key :b not found in: [a: 1]
"""
@spec get_and_update!(t, key, (value -> {get, value})) :: {get, t} | no_return when get: term
def get_and_update!(keywords, key, fun) do
get_and_update!(keywords, key, fun, [])
end
defp get_and_update!([{key, value}|keywords], key, fun, acc) do
{get, value} = fun.(value)
{get, :lists.reverse(acc, [{key, value}|delete(keywords, key)])}
end
defp get_and_update!([{_, _} = e|keywords], key, fun, acc) do
get_and_update!(keywords, key, fun, [e|acc])
end
defp get_and_update!([], key, _fun, acc) when is_atom(key) do
raise(KeyError, key: key, term: acc)
end
@doc """
Fetches the value for a specific `key` and returns it in a tuple.
@@ -201,7 +280,6 @@ defmodule Keyword do
iex> Keyword.fetch([a: 1], :a)
{:ok, 1}
iex> Keyword.fetch([a: 1], :b)
:error
@@ -223,7 +301,6 @@ defmodule Keyword do
iex> Keyword.fetch!([a: 1], :a)
1
iex> Keyword.fetch!([a: 1], :b)
** (KeyError) key :b not found in: [a: 1]
@@ -241,6 +318,10 @@ defmodule Keyword do
## Examples
iex> Keyword.get_values([], :a)
[]
iex> Keyword.get_values([a: 1], :a)
[1]
iex> Keyword.get_values([a: 1, a: 2], :a)
[1, 2]
@@ -251,7 +332,6 @@ defmodule Keyword do
{k, v} when k === key -> {true, v}
{_, _} -> false
end
:lists.filtermap(fun, keywords)
end
@@ -264,7 +344,6 @@ defmodule Keyword do
iex> Keyword.keys([a: 1, b: 2])
[:a, :b]
iex> Keyword.keys([a: 1, b: 2, a: 3])
[:a, :b, :a]
@@ -277,10 +356,14 @@ defmodule Keyword do
@doc """
Returns all values from the keyword list.
Values from duplicated keys will be kept in the final list of values.
## Examples
iex> Keyword.values([a: 1, b: 2])
[1, 2]
iex> Keyword.values([a: 1, b: 2, a: 3])
[1, 2, 3]
"""
@spec values(t) :: [value]
@@ -297,12 +380,12 @@ defmodule Keyword do
iex> Keyword.delete([a: 1, b: 2], :a, 1)
[b: 2]
iex> Keyword.delete([a: 1, b: 2, a: 3], :a, 3)
[a: 1, b: 2]
iex> Keyword.delete([b: 2], :a, 5)
[b: 2]
iex> Keyword.delete([a: 1], :a, 5)
[a: 1]
iex> Keyword.delete([a: 1], :b, 5)
[a: 1]
"""
@spec delete(t, key, value) :: t
@@ -321,10 +404,8 @@ defmodule Keyword do
iex> Keyword.delete([a: 1, b: 2], :a)
[b: 2]
iex> Keyword.delete([a: 1, b: 2, a: 3], :a)
[b: 2]
iex> Keyword.delete([b: 2], :a)
[b: 2]
@@ -343,7 +424,6 @@ defmodule Keyword do
iex> Keyword.delete_first([a: 1, b: 2, a: 3], :a)
[b: 2, a: 3]
iex> Keyword.delete_first([b: 2], :a)
[b: 2]
@@ -361,9 +441,10 @@ defmodule Keyword do
## Examples
iex> Keyword.put([a: 1], :b, 2)
[b: 2, a: 1]
iex> Keyword.put([a: 1, b: 2], :a, 3)
[a: 3, b: 2]
iex> Keyword.put([a: 1, b: 2, a: 4], :a, 3)
[a: 3, b: 2]
@@ -377,8 +458,8 @@ defmodule Keyword do
Evaluates `fun` and puts the result under `key`
in keyword list unless `key` is already present.
This is useful if the value is very expensive to calculate or generally
difficult to set-up and tear-down again.
This is useful if the value is very expensive to calculate or
generally difficult to setup and teardown again.
## Examples
@@ -410,7 +491,6 @@ defmodule Keyword do
iex> Keyword.put_new([a: 1], :b, 2)
[b: 2, a: 1]
iex> Keyword.put_new([a: 1, b: 2], :a, 3)
[a: 1, b: 2]
@@ -433,6 +513,10 @@ defmodule Keyword do
iex> Keyword.equal?([a: 1, b: 2], [b: 2, a: 1])
true
iex> Keyword.equal?([a: 1, b: 2], [b: 1, a: 2])
false
iex> Keyword.equal?([a: 1, b: 2, a: 3], [b: 2, a: 3, a: 1])
true
"""
@spec equal?(t, t) :: boolean
@@ -443,44 +527,72 @@ defmodule Keyword do
@doc """
Merges two keyword lists into one.
If they have duplicated keys, the one given in the second argument wins.
All keys, including duplicated keys, given in `keywords2` will be added
to `keywords1`, overriding any existing one.
There are no guarantees about the order of keys in the returned keyword.
## Examples
iex> Keyword.merge([a: 1, b: 2], [a: 3, d: 4])
[a: 3, d: 4, b: 2]
[b: 2, a: 3, d: 4]
iex> Keyword.merge([a: 1, b: 2], [a: 3, d: 4, a: 5])
[b: 2, a: 3, d: 4, a: 5]
"""
@spec merge(t, t) :: t
def merge(keywords1, keywords2) when is_list(keywords1) and is_list(keywords2) do
fun = fn {k, _v} -> not has_key?(keywords2, k) end
keywords2 ++ :lists.filter(fun, keywords1)
:lists.filter(fun, keywords1) ++ keywords2
end
@doc """
Merges two keyword lists into one.
If they have duplicated keys, the given function is invoked to solve conflicts.
All keys, including duplicated keys, given in `keywords2` will be added
to `keywords1`. The given function will be invoked to solve conflicts.
If `keywords2` has duplicate keys, the given function will be invoked
for each matching pair in `keywords1`.
There are no guarantees about the order of keys in the returned keyword.
## Examples
iex> Keyword.merge([a: 1, b: 2], [a: 3, d: 4], fn (_k, v1, v2) ->
iex> Keyword.merge([a: 1, b: 2], [a: 3, d: 4], fn _k, v1, v2 ->
...> v1 + v2
...> end)
[b: 2, a: 4, d: 4]
iex> Keyword.merge([a: 1, b: 2], [a: 3, d: 4, a: 5], fn :a, v1, v2 ->
...> v1 + v2
...> end)
[a: 4, b: 2, d: 4]
[b: 2, a: 4, d: 4, a: 5]
iex> Keyword.merge([a: 1, b: 2, a: 3], [a: 3, d: 4, a: 5], fn :a, v1, v2 ->
...> v1 + v2
...> end)
[b: 2, a: 4, d: 4, a: 8]
"""
@spec merge(t, t, (key, value, value -> value)) :: t
def merge(keywords1, keywords2, fun) when is_list(keywords1) and is_list(keywords2) do
do_merge(keywords2, keywords1, fun)
do_merge(keywords2, [], keywords1, keywords1, fun)
end
defp do_merge([{k, v2}|t], acc, fun) do
do_merge t, update(acc, k, v2, fn(v1) -> fun.(k, v1, v2) end), fun
defp do_merge([{k, v2}|t], acc, rest, original, fun) do
case :lists.keyfind(k, 1, original) do
{^k, v1} ->
do_merge(t, [{k, fun.(k, v1, v2)}|acc],
delete(rest, k), :lists.keydelete(k, 1, original), fun)
false ->
do_merge(t, [{k, v2}|acc], rest, original, fun)
end
end
defp do_merge([], acc, _fun) do
acc
defp do_merge([], acc, rest, _original, _fun) do
rest ++ :lists.reverse(acc)
end
@doc """
@@ -490,7 +602,6 @@ defmodule Keyword do
iex> Keyword.has_key?([a: 1], :a)
true
iex> Keyword.has_key?([a: 1], :b)
false
@@ -512,6 +623,8 @@ defmodule Keyword do
iex> Keyword.update!([a: 1], :a, &(&1 * 2))
[a: 2]
iex> Keyword.update!([a: 1, a: 2], :a, &(&1 * 2))
[a: 2]
iex> Keyword.update!([a: 1], :b, &(&1 * 2))
** (KeyError) key :b not found in: [a: 1]
@@ -546,7 +659,8 @@ defmodule Keyword do
iex> Keyword.update([a: 1], :a, 13, &(&1 * 2))
[a: 2]
iex> Keyword.update([a: 1, a: 2], :a, 13, &(&1 * 2))
[a: 2]
iex> Keyword.update([a: 1], :b, 11, &(&1 * 2))
[a: 1, b: 11]
@@ -578,13 +692,10 @@ defmodule Keyword do
## 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]}
iex> Keyword.split([a: 1, b: 2, c: 3], [:a, :c, :e])
{[a: 1, c: 3], [b: 2]}
iex> Keyword.split([a: 1, b: 2, c: 3, a: 4], [:a, :c, :e])
{[a: 1, c: 3, a: 4], [b: 2]}
"""
def split(keywords, keys) when is_list(keywords) do
@@ -608,12 +719,9 @@ defmodule Keyword do
## Examples
iex> d = [a: 1, b: 2, c: 3, d: 4]
iex> Keyword.take(d, [:a, :c, :e])
iex> Keyword.take([a: 1, b: 2, c: 3], [: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])
iex> Keyword.take([a: 1, b: 2, c: 3, a: 5], [:a, :c, :e])
[a: 1, c: 3, a: 5]
"""
@@ -628,12 +736,9 @@ defmodule Keyword do
## Examples
iex> d = [a: 1, b: 2, c: 3, d: 4]
iex> Keyword.drop(d, [:b, :d])
iex> Keyword.drop([a: 1, b: 2, c: 3], [:b, :d])
[a: 1, c: 3]
iex> d = [a: 1, b: 2, b: 3, c: 3, d: 4, a: 5]
iex> Keyword.drop(d, [:b, :d])
iex> Keyword.drop([a: 1, b: 2, b: 3, c: 3, a: 5], [:b, :d])
[a: 1, c: 3, a: 5]
"""
@@ -642,24 +747,20 @@ defmodule Keyword do
end
@doc """
Returns the first value associated with `key` in the keyword
list as well as the keyword list without `key`.
Returns and removes all values associated with `key` in the keyword list.
All duplicated keys are removed. See `pop_first/3` for
removing only the first entry.
## Examples
iex> Keyword.pop [a: 1], :a
iex> Keyword.pop([a: 1], :a)
{1, []}
iex> Keyword.pop [a: 1], :b
iex> Keyword.pop([a: 1], :b)
{nil, [a: 1]}
iex> Keyword.pop [a: 1], :b, 3
iex> Keyword.pop([a: 1], :b, 3)
{3, [a: 1]}
iex> Keyword.pop [a: 1, a: 2], :a
iex> Keyword.pop([a: 1, a: 2], :a)
{1, []}
"""
@@ -674,11 +775,10 @@ defmodule Keyword do
end
@doc """
Returns the first value associated with `key` in the keyword
list as well as the keyword list without `key`.
Lazily returns and removes all values associated with `key` in the keyword list.
This is useful if the default value is very expensive to calculate or
generally difficult to set-up and tear-down again.
generally difficult to setup and teardown again.
All duplicated keys are removed. See `pop_first/3` for
removing only the first entry.
@@ -688,12 +788,12 @@ defmodule Keyword do
iex> keyword = [a: 1]
iex> fun = fn ->
...> # some expensive operation here
...> :result
...> 13
...> end
iex> Keyword.pop_lazy(keyword, :a, fun)
{1, []}
iex> Keyword.pop_lazy(keyword, :b, fun)
{:result, [a: 1]}
{13, [a: 1]}
"""
@spec pop_lazy(t, key, (() -> value)) :: {value, t}
@@ -708,9 +808,7 @@ defmodule Keyword do
end
@doc """
Returns the first value associated with `key` in the keyword
list as well as the keyword list without that particular occurrence
of `key`.
Returns and removes the first value associated with `key` in the keyword list.
Duplicated keys are not removed.
@@ -718,13 +816,10 @@ defmodule Keyword do
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, a: 2], :a
{1, [a: 2]}
@@ -737,15 +832,23 @@ defmodule Keyword do
end
end
# Dict callbacks
@doc """
Returns the keyword list itself.
## Examples
iex> Keyword.to_list([a: 1])
[a: 1]
"""
def to_list(keyword) when is_list(keyword) do
keyword
end
# TODO: Deprecate by 1.3
# TODO: Remove by 1.4
@doc false
def size(keyword) do
length(keyword)
end
@doc false
def to_list(keyword) do
keyword
end
end
+40 -9
View File
@@ -1,19 +1,45 @@
defmodule List do
@moduledoc """
Implements functions that only make sense for lists
and cannot be part of the Enum protocol. In general,
favor using the Enum API instead of List.
Specialized functions that only work on lists.
Some functions in this module expect an index. Index
access for list is linear. Negative indexes are also
In general, favor using the `Enum` API instead of `List`.
Index access for list is linear. Negative indexes are also
supported but they imply the list will be iterated twice,
one to calculate the proper index and another to the
one to calculate the proper index and another to perform the
operation.
A decision was taken to delegate most functions to
Erlang's standard library but follow Elixir's convention
of receiving the target (in this case, a list) as the
of receiving the subject (in this case, a list) as the
first argument.
## Char lists
If a list is made of non-negative integers, it can also
be called as a char list. Elixir uses single quotes to
define char lists:
iex> 'héllo'
[104, 233, 108, 108, 111]
In particular, char lists may be printed back in single
quotes if they contain only ASCII-printable codepoints:
iex> 'abc'
'abc'
The rationale behind this behaviour is to better support
Erlang libraries which may return text as char lists
instead of Elixir strings. One example of such functions
is `Application.loaded_applications`:
Application.loaded_applications
#=> [{:stdlib, 'ERTS CXC 138 10', '2.6'},
{:compiler, 'ERTS CXC 138 10', '6.0.1'},
{:elixir, 'elixir', '1.0.0'},
{:kernel, 'ERTS CXC 138 10', '4.1'},
{:logger, 'logger', '1.0.0'}]
"""
@compile :inline_list_funcs
@@ -485,7 +511,7 @@ defmodule List do
end
@doc """
Converts a char list to an existing atom. Raises an `ArguementError`
Converts a char list to an existing atom. Raises an `ArgumentError`
if the atom does not exist.
Currently Elixir does not support conversions from char lists
@@ -591,7 +617,12 @@ defmodule List do
"""
@spec to_string(:unicode.charlist) :: String.t
def to_string(list) when is_list(list) do
case :unicode.characters_to_binary(list) do
try do
:unicode.characters_to_binary(list)
rescue
ArgumentError ->
raise ArgumentError, "cannot convert list to string. The list must contain only integers, strings or nested such lists; got: #{inspect list}"
else
result when is_binary(result) ->
result
+211 -77
View File
@@ -20,8 +20,6 @@ defmodule Macro do
@typedoc "Abstract Syntax Tree (AST)"
@type t :: expr | {t, t} | atom | number | binary | pid | fun | [t]
@typedoc "Expr node (remaining ones are literals)"
@type expr :: {expr | atom, Keyword.t, atom | [t]}
@binary_ops [:===, :!==,
@@ -88,13 +86,17 @@ defmodule Macro do
def pipe(expr, call_args, position)
def pipe(expr, {:&, _, _} = call_args, _integer) do
bad_pipe(expr, call_args)
raise ArgumentError, bad_pipe(expr, call_args)
end
def pipe(expr, {tuple_or_map, _, _} = call_args, _integer) when tuple_or_map in [:{}, :%{}] do
raise ArgumentError, bad_pipe(expr, call_args)
end
def pipe(expr, {call, _, [_, _]} = call_args, _integer)
when call in unquote(@binary_ops) do
raise ArgumentError, "cannot pipe #{to_string expr} into #{to_string call_args}, " <>
"the #{to_string call} operator can only take two arguments"
"the #{to_string call} operator can only take two arguments"
end
def pipe(expr, {call, line, atom}, integer) when is_atom(atom) do
@@ -106,12 +108,12 @@ defmodule Macro do
end
def pipe(expr, call_args, _integer) do
bad_pipe(expr, call_args)
raise ArgumentError, bad_pipe(expr, call_args)
end
defp bad_pipe(expr, call_args) do
raise ArgumentError, "cannot pipe #{to_string expr} into #{to_string call_args}, " <>
"can only pipe into local calls foo(), remote calls Foo.bar() or anonymous functions calls foo.()"
"cannot pipe #{to_string expr} into #{to_string call_args}, " <>
"can only pipe into local calls foo(), remote calls Foo.bar() or anonymous functions calls foo.()"
end
@doc """
@@ -165,6 +167,52 @@ defmodule Macro do
{var, [], context}
end
@doc """
Performs a depth-first, traversal of quoted expressions
using an accumulator.
"""
@spec traverse(t, any, (t, any -> {t, any}), (t, any -> {t, any})) :: {t, any}
def traverse(ast, acc, pre, post) when is_function(pre, 2) and is_function(post, 2) do
{ast, acc} = pre.(ast, acc)
do_traverse(ast, acc, pre, post)
end
defp do_traverse({form, meta, args}, acc, pre, post) do
unless is_atom(form) do
{form, acc} = pre.(form, acc)
{form, acc} = do_traverse(form, acc, pre, post)
end
unless is_atom(args) do
{args, acc} = Enum.map_reduce(args, acc, fn x, acc ->
{x, acc} = pre.(x, acc)
do_traverse(x, acc, pre, post)
end)
end
post.({form, meta, args}, acc)
end
defp do_traverse({left, right}, acc, pre, post) do
{left, acc} = pre.(left, acc)
{left, acc} = do_traverse(left, acc, pre, post)
{right, acc} = pre.(right, acc)
{right, acc} = do_traverse(right, acc, pre, post)
post.({left, right}, acc)
end
defp do_traverse(list, acc, pre, post) when is_list(list) do
{list, acc} = Enum.map_reduce(list, acc, fn x, acc ->
{x, acc} = pre.(x, acc)
do_traverse(x, acc, pre, post)
end)
post.(list, acc)
end
defp do_traverse(x, acc, _pre, post) do
post.(x, acc)
end
@doc """
Performs a depth-first, pre-order traversal of quoted expressions.
"""
@@ -179,43 +227,7 @@ defmodule Macro do
"""
@spec prewalk(t, any, (t, any -> {t, any})) :: {t, any}
def prewalk(ast, acc, fun) when is_function(fun, 2) do
{ast, acc} = fun.(ast, acc)
do_prewalk(ast, acc, fun)
end
defp do_prewalk({form, meta, args}, acc, fun) do
unless is_atom(form) do
{form, acc} = fun.(form, acc)
{form, acc} = do_prewalk(form, acc, fun)
end
unless is_atom(args) do
{args, acc} = Enum.map_reduce(args, acc, fn x, acc ->
{x, acc} = fun.(x, acc)
do_prewalk(x, acc, fun)
end)
end
{{form, meta, args}, acc}
end
defp do_prewalk({left, right}, acc, fun) do
{left, acc} = fun.(left, acc)
{left, acc} = do_prewalk(left, acc, fun)
{right, acc} = fun.(right, acc)
{right, acc} = do_prewalk(right, acc, fun)
{{left, right}, acc}
end
defp do_prewalk(list, acc, fun) when is_list(list) do
Enum.map_reduce(list, acc, fn x, acc ->
{x, acc} = fun.(x, acc)
do_prewalk(x, acc, fun)
end)
end
defp do_prewalk(x, acc, _fun) do
{x, acc}
traverse(ast, acc, fun, fn x, a -> {x, a} end)
end
@doc """
@@ -232,34 +244,7 @@ defmodule Macro do
"""
@spec postwalk(t, any, (t, any -> {t, any})) :: {t, any}
def postwalk(ast, acc, fun) when is_function(fun, 2) do
do_postwalk(ast, acc, fun)
end
defp do_postwalk({form, meta, args}, acc, fun) do
unless is_atom(form) do
{form, acc} = do_postwalk(form, acc, fun)
end
unless is_atom(args) do
{args, acc} = Enum.map_reduce(args, acc, &do_postwalk(&1, &2, fun))
end
fun.({form, meta, args}, acc)
end
defp do_postwalk({left, right}, acc, fun) do
{left, acc} = do_postwalk(left, acc, fun)
{right, acc} = do_postwalk(right, acc, fun)
fun.({left, right}, acc)
end
defp do_postwalk(list, acc, fun) when is_list(list) do
{list, acc} = Enum.map_reduce(list, acc, &do_postwalk(&1, &2, fun))
fun.(list, acc)
end
defp do_postwalk(x, acc, fun) do
fun.(x, acc)
traverse(ast, acc, fn x, a -> {x, a} end, fun)
end
@doc """
@@ -493,14 +478,19 @@ defmodule Macro do
end
# Bits containers
def to_string({:<<>>, _, args} = ast, fun) do
def to_string({:<<>>, _, parts} = ast, fun) do
if interpolated?(ast) do
fun.(ast, interpolate(ast, fun))
else
fun.(ast, case Enum.map_join(args, ", ", &to_string(&1, fun)) do
"<" <> rest -> "<< <" <> rest <> " >>"
rest -> "<<" <> rest <> ">>"
result = Enum.map_join(parts, ", ", fn(part) ->
str = bitpart_to_string(part, fun)
if :binary.first(str) == ?< or :binary.last(str) == ?> do
"(" <> str <> ")"
else
str
end
end)
fun.(ast, "<<" <> result <> ">>")
end
end
@@ -537,6 +527,12 @@ defmodule Macro do
fun.(ast, "fn\n " <> block <> "\nend")
end
# Ranges
def to_string({:.., _, args} = ast, fun) do
range = Enum.map_join(args, "..", &to_string(&1, fun))
fun.(ast, range)
end
# left -> right
def to_string([{:->, _, _}|_] = ast, fun) do
fun.(ast, "(" <> arrow_to_string(ast, fun, true) <> ")")
@@ -575,6 +571,10 @@ defmodule Macro do
fun.(ast, "&" <> to_string(mod, fun) <> "." <> Atom.to_string(name) <> "/" <> to_string(arity, fun))
end
def to_string({:&, _, [arg]} = ast, fun) when not is_integer(arg) do
fun.(ast, "&(" <> to_string(arg, fun) <> ")")
end
# Unary ops
def to_string({unary, _, [{binary, _, [_, _]} = arg]} = ast, fun)
when unary in unquote(@unary_ops) and binary in unquote(@binary_ops) do
@@ -590,6 +590,11 @@ defmodule Macro do
end
# Access
def to_string({{:., _, [Access, :get]}, _, [{op, _, _} = left, right]} = ast, fun)
when op in unquote(@binary_ops) do
fun.(ast, "(" <> to_string(left, fun) <> ")" <> to_string([right], fun))
end
def to_string({{:., _, [Access, :get]}, _, [left, right]} = ast, fun) do
fun.(ast, to_string(left, fun) <> to_string([right], fun))
end
@@ -629,6 +634,30 @@ defmodule Macro do
# All other structures
def to_string(other, fun), do: fun.(other, inspect(other, []))
defp bitpart_to_string({:::, _, [left, right]} = ast, fun) do
result =
op_to_string(left, fun, :::, :left) <>
"::" <>
bitmods_to_string(right, fun, :::, :right)
fun.(ast, result)
end
defp bitpart_to_string(ast, fun) do
to_string(ast, fun)
end
defp bitmods_to_string({:-, _, [left, right]} = ast, fun, _, _) do
result =
bitmods_to_string(left, fun, :-, :left) <>
"-" <>
bitmods_to_string(right, fun, :-, :right)
fun.(ast, result)
end
defp bitmods_to_string(other, fun, parent_op, side) do
op_to_string(other, fun, parent_op, side)
end
# Block keywords
@kw_keywords [:do, :catch, :rescue, :after, :else]
@@ -666,7 +695,7 @@ defmodule Macro do
defp module_to_string(atom, _fun) when is_atom(atom), do: inspect(atom, [])
defp module_to_string(other, fun), do: call_to_string(other, fun)
defp sigil_call({func, _, [{:<<>>, _, _} = bin, args]} = ast, fun) when is_list(args) do
defp sigil_call({func, _, [{:<<>>, _, _} = bin, args]} = ast, fun) when is_atom(func) and is_list(args) do
sigil =
case Atom.to_string(func) do
<<"sigil_", name>> ->
@@ -1017,4 +1046,109 @@ defmodule Macro do
defp expand_until({tree, false}, _env) do
tree
end
@doc """
Converts the given atom or binary to underscore format.
If an atom is given, it is assumed to be an Elixir module,
so it is converted to a binary and then processed.
## Examples
iex> Macro.underscore "FooBar"
"foo_bar"
iex> Macro.underscore "Foo.Bar"
"foo/bar"
iex> Macro.underscore Foo.Bar
"foo/bar"
In general, `underscore` can be thought of as the reverse of
`camelize`, however, in some cases formatting may be lost:
iex> Macro.underscore "SAPExample"
"sap_example"
iex> Macro.camelize "sap_example"
"SapExample"
"""
def underscore(atom) when is_atom(atom) do
"Elixir." <> rest = Atom.to_string(atom)
underscore(rest)
end
def underscore(""), do: ""
def underscore(<<h, t::binary>>) do
<<to_lower_char(h)>> <> do_underscore(t, h)
end
defp do_underscore(<<h, t, rest::binary>>, _)
when (h >= ?A and h <= ?Z) and not (t >= ?A and t <= ?Z) and t != ?. do
<<?_, to_lower_char(h), t>> <> do_underscore(rest, t)
end
defp do_underscore(<<h, t::binary>>, prev)
when (h >= ?A and h <= ?Z) and not (prev >= ?A and prev <= ?Z) do
<<?_, to_lower_char(h)>> <> do_underscore(t, h)
end
defp do_underscore(<<?., t::binary>>, _) do
<<?/>> <> underscore(t)
end
defp do_underscore(<<h, t::binary>>, _) do
<<to_lower_char(h)>> <> do_underscore(t, h)
end
defp do_underscore(<<>>, _) do
<<>>
end
@doc """
Converts the given string to CamelCase format.
## Examples
iex> Macro.camelize "foo_bar"
"FooBar"
"""
@spec camelize(String.t) :: String.t
def camelize(string)
def camelize(""),
do: ""
def camelize(<<?_, t::binary>>),
do: camelize(t)
def camelize(<<h, t::binary>>),
do: <<to_upper_char(h)>> <> do_camelize(t)
defp do_camelize(<<?_, ?_, t::binary>>),
do: do_camelize(<<?_, t::binary >>)
defp do_camelize(<<?_, h, t::binary>>) when h >= ?a and h <= ?z,
do: <<to_upper_char(h)>> <> do_camelize(t)
defp do_camelize(<<?_>>),
do: <<>>
defp do_camelize(<<?/, t::binary>>),
do: <<?.>> <> camelize(t)
defp do_camelize(<<h, t::binary>>),
do: <<h>> <> do_camelize(t)
defp do_camelize(<<>>),
do: <<>>
defp to_upper_char(char) when char >= ?a and char <= ?z, do: char - 32
defp to_upper_char(char), do: char
defp to_lower_char(char) when char >= ?A and char <= ?Z, do: char + 32
defp to_lower_char(char), do: char
end
+4 -4
View File
@@ -24,12 +24,12 @@ defmodule Macro.Env do
* `module` - the current module name
* `file` - the current file name as a binary
* `line` - the current line as an integer
* `function` - a tuple as `{atom, integer`}, where the first
element is the function name and the seconds its arity; returns
* `function` - a tuple as `{atom, integer}`, where the first
element is the function name and the second its arity; returns
`nil` if not inside a function
* `context` - the context of the environment; it can be `nil`
(default context), inside a guard or inside an assign
* `aliases` - a list of two item tuples, where the first
(default context), inside a guard or inside a match
* `aliases` - a list of two-item tuples, where the first
item is the aliased name and the second the actual name
* `requires` - the list of required modules
* `functions` - a list of functions imported from each module
+510 -17
View File
@@ -1,37 +1,341 @@
defmodule Map do
@moduledoc """
A Dict implementation that works on maps.
A set of functions for working with maps.
Maps are key-value stores where keys are compared using
the match operator (`===`). Maps can be created with
the `%{}` special form defined in the `Kernel.SpecialForms`
module.
For more information about the functions in this module and
their APIs, please consult the `Dict` module.
Maps are key-value stores where keys can be any value and
are compared using the match operator (`===`). Maps can be
created with the `%{}` special form defined in the
`Kernel.SpecialForms` module.
"""
use Dict
defdelegate [keys(map), values(map), size(map), merge(map1, map2), to_list(map)], to: :maps
@type key :: any
@type value :: any
@compile {:inline, fetch: 2, put: 3, delete: 2, has_key?: 2}
@doc """
Returns a new empty map.
Returns all keys from the map.
## Examples
iex> Map.keys(%{a: 1, b: 2})
[:a, :b]
"""
@spec keys(map) :: [key]
defdelegate keys(map), to: :maps
@doc """
Returns all values from the map.
## Examples
iex> Map.values(%{a: 1, b: 2})
[1, 2]
"""
@spec values(map) :: [value]
defdelegate values(map), to: :maps
@doc """
Converts the map to a list.
## Examples
iex> Map.to_list(%{a: 1})
[a: 1]
iex> Map.to_list(%{1 => 2})
[{1, 2}]
"""
@spec to_list(map) :: [{term, term}]
defdelegate to_list(map), to: :maps
@doc """
Returns a new empty map.
## Examples
iex> Map.new
%{}
"""
@spec new :: map
def new, do: %{}
@doc """
Creates a map from an enumerable.
Duplicated keys are removed; the latest one prevails.
## Examples
iex> Map.new([{:b, 1}, {:a, 2}])
%{a: 2, b: 1}
iex> Map.new([a: 1, a: 2, a: 3])
%{a: 3}
"""
@spec new(Enum.t) :: map
def new(enumerable) do
Enum.reduce(enumerable, %{}, fn {k, v}, acc -> put(acc, k, v) end)
end
@doc """
Creates a map from an enumerable via the transformation function.
Duplicated entries are removed; the latest one prevails.
## Examples
iex> Map.new([:a, :b], fn x -> {x, x} end)
%{a: :a, b: :b}
"""
@spec new(Enum.t, (term -> {key, value})) :: map
def new(enumerable, transform) do
fun = fn el, acc ->
{k, v} = transform.(el)
put(acc, k, v)
end
Enum.reduce(enumerable, %{}, fun)
end
@doc """
Returns whether a given `key` exists in the given `map`.
## Examples
iex> Map.has_key?(%{a: 1}, :a)
true
iex> Map.has_key?(%{a: 1}, :b)
false
"""
@spec has_key?(map, key) :: boolean
def has_key?(map, key), do: :maps.is_key(key, map)
@doc """
Fetches the value for a specific `key` and returns it in a tuple.
If the `key` does not exist, returns `:error`.
## Examples
iex> Map.fetch(%{a: 1}, :a)
{:ok, 1}
iex> Map.fetch(%{a: 1}, :b)
:error
"""
@spec fetch(map, key) :: {:ok, value} | :error
def fetch(map, key), do: :maps.find(key, map)
@doc """
Fetches the value for specific `key`.
If `key` does not exist, a `KeyError` is raised.
## Examples
iex> Map.fetch!(%{a: 1}, :a)
1
iex> Map.fetch!(%{a: 1}, :b)
** (KeyError) key :b not found in: %{a: 1}
"""
@spec fetch!(map, key) :: value | no_return
def fetch!(map, key) do
case fetch(map, key) do
{:ok, value} -> value
:error -> raise KeyError, key: key, term: map
end
end
@doc """
Puts the given `value` under `key` unless the entry `key`
already exists.
## Examples
iex> Map.put_new(%{a: 1}, :b, 2)
%{b: 2, a: 1}
iex> Map.put_new(%{a: 1, b: 2}, :a, 3)
%{a: 1, b: 2}
"""
@spec put_new(map, key, value) :: map
def put_new(map, key, value) do
case has_key?(map, key) do
true -> map
false -> put(map, key, value)
end
end
@doc """
Evaluates `fun` and puts the result under `key`
in map unless `key` is already present.
This is useful if the value is very expensive to calculate or
generally difficult to setup and teardown again.
## Examples
iex> map = %{a: 1}
iex> fun = fn ->
...> # some expensive operation here
...> 3
...> end
iex> Map.put_new_lazy(map, :a, fun)
%{a: 1}
iex> Map.put_new_lazy(map, :b, fun)
%{a: 1, b: 3}
"""
@spec put_new_lazy(map, key, (() -> value)) :: map
def put_new_lazy(map, key, fun) when is_function(fun, 0) do
case has_key?(map, key) do
true -> map
false -> put(map, key, fun.())
end
end
@doc """
Takes all entries corresponding to the given keys and
returns them in a new map.
## Examples
iex> Map.take(%{a: 1, b: 2, c: 3}, [:a, :c, :e])
%{a: 1, c: 3}
"""
@spec take(map, [key]) :: map
def take(map, keys) do
Enum.reduce(keys, new, fn key, acc ->
case fetch(map, key) do
{:ok, value} -> put(acc, key, value)
:error -> acc
end
end)
end
@doc """
Gets the value for a specific `key`.
If `key` does not exist, return the default value
(`nil` if no default value).
## Examples
iex> Map.get(%{}, :a)
nil
iex> Map.get(%{a: 1}, :a)
1
iex> Map.get(%{a: 1}, :b)
nil
iex> Map.get(%{a: 1}, :b, 3)
3
"""
@spec get(map, key) :: value
@spec get(map, key, value) :: value
def get(map, key, default \\ nil) do
case fetch(map, key) do
{:ok, value} -> value
:error -> default
end
end
@doc """
Gets the value for a specific `key`.
If `key` does not exist, lazily evaluates `fun` and returns its result.
This is useful if the default value is very expensive to calculate or
generally difficult to setup and teardown again.
## Examples
iex> map = %{a: 1}
iex> fun = fn ->
...> # some expensive operation here
...> 13
...> end
iex> Map.get_lazy(map, :a, fun)
1
iex> Map.get_lazy(map, :b, fun)
13
"""
@spec get_lazy(map, key, (() -> value)) :: value
def get_lazy(map, key, fun) when is_function(fun, 0) do
case fetch(map, key) do
{:ok, value} -> value
:error -> fun.()
end
end
@doc """
Puts the given `value` under `key`.
## Examples
iex> Map.put(%{a: 1}, :b, 2)
%{a: 1, b: 2}
iex> Map.put(%{a: 1, b: 2}, :a, 3)
%{a: 3, b: 2}
"""
@spec put(map, key, value) :: map
def put(map, key, val) do
:maps.put(key, val, map)
end
@doc """
Deletes the entries in the map for a specific `key`.
If the `key` does not exist, returns the map unchanged.
## Examples
iex> Map.delete(%{a: 1, b: 2}, :a)
%{b: 2}
iex> Map.delete(%{b: 2}, :a)
%{b: 2}
"""
@spec delete(map, key) :: map
def delete(map, key), do: :maps.remove(key, map)
@doc """
Merges two maps into one.
All keys in `map2` will be added to `map1`, overriding any existing one.
## Examples
iex> Map.merge(%{a: 1, b: 2}, %{a: 3, d: 4})
%{a: 3, b: 2, d: 4}
"""
@spec merge(map, map) :: map
defdelegate merge(map1, map2), to: :maps
@doc """
Merges two maps into one.
All keys in `map2` will be added to `map1`. The given function will
be invoked with the key, value1 and value2 to solve conflicts.
## Examples
iex> Map.merge(%{a: 1, b: 2}, %{a: 3, d: 4}, fn _k, v1, v2 ->
...> v1 + v2
...> end)
%{a: 4, b: 2, d: 4}
"""
@spec merge(map, map, (key, value, value -> value)) :: map
def merge(map1, map2, callback) do
:maps.fold fn k, v2, acc ->
update(acc, k, v2, fn(v1) -> callback.(k, v1, v2) end)
@@ -39,8 +343,131 @@ defmodule Map do
end
@doc """
Updates the value in the map with the given function.
Updates the `key` in `map` with the given function.
If the `key` does not exist, inserts the given `initial` value.
## Examples
iex> Map.update(%{a: 1}, :a, 13, &(&1 * 2))
%{a: 2}
iex> Map.update(%{a: 1}, :b, 11, &(&1 * 2))
%{a: 1, b: 11}
"""
@spec update(map, key, value, (value -> value)) :: map
def update(map, key, initial, fun) do
case fetch(map, key) do
{:ok, value} ->
put(map, key, fun.(value))
:error ->
put(map, key, initial)
end
end
@doc """
Returns and removes all values associated with `key` in the `map`.
## Examples
iex> Map.pop(%{a: 1}, :a)
{1, %{}}
iex> Map.pop(%{a: 1}, :b)
{nil, %{a: 1}}
iex> Map.pop(%{a: 1}, :b, 3)
{3, %{a: 1}}
"""
@spec pop(map, key, value) :: {value, map}
def pop(map, key, default \\ nil) do
case fetch(map, key) do
{:ok, value} -> {value, delete(map, key)}
:error -> {default, map}
end
end
@doc """
Lazily returns and removes all values associated with `key` in the `map`.
This is useful if the default value is very expensive to calculate or
generally difficult to setup and teardown again.
## Examples
iex> map = %{a: 1}
iex> fun = fn ->
...> # some expensive operation here
...> 13
...> end
iex> Map.pop_lazy(map, :a, fun)
{1, %{}}
iex> Map.pop_lazy(map, :b, fun)
{13, %{a: 1}}
"""
@spec pop_lazy(map, key, (() -> value)) :: {value, map}
def pop_lazy(map, key, fun) when is_function(fun, 0) do
case fetch(map, key) do
{:ok, value} -> {value, delete(map, key)}
:error -> {fun.(), map}
end
end
@doc """
Drops the given keys from the map.
## Examples
iex> Map.drop(%{a: 1, b: 2, c: 3}, [:b, :d])
%{a: 1, c: 3}
"""
@spec drop(map, [key]) :: map
def drop(map, keys) do
Enum.reduce(keys, map, &delete(&2, &1))
end
@doc """
Takes all entries corresponding to the given keys and extracts them into a
separate map.
Returns a tuple with the new map and the old map with removed keys.
Keys for which there are no entires in the map are ignored.
## Examples
iex> Map.split(%{a: 1, b: 2, c: 3}, [:a, :c, :e])
{%{a: 1, c: 3}, %{b: 2}}
"""
@spec split(map, [key]) :: {map, map}
def split(map, keys) do
Enum.reduce(keys, {new, map}, fn key, {inc, exc} = acc ->
case fetch(exc, key) do
{:ok, value} ->
{put(inc, key, value), delete(exc, key)}
:error ->
acc
end
end)
end
@doc """
Updates the `key` with the given function.
If the `key` does not exist, raises `KeyError`.
## Examples
iex> Map.update!(%{a: 1}, :a, &(&1 * 2))
%{a: 2}
iex> Map.update!(%{a: 1}, :b, &(&1 * 2))
** (KeyError) key :b not found
"""
@spec update!(map, key, (value -> value)) :: map | no_return
def update!(%{} = map, key, fun) do
case fetch(map, key) do
{:ok, value} ->
@@ -53,8 +480,30 @@ defmodule Map do
def update!(map, _key, _fun), do: :erlang.error({:badmap, map})
@doc """
Gets a value and updates a map in one operation.
Gets the value from `key` and updates it, all in one pass.
This `fun` argument receives the value of `key` (or `nil` if `key`
is not present) and must return a two-elements tuple: the "get" value (the
retrieved value, which can be operated on before being returned) and the new
value to be stored under `key`.
The returned value is a tuple with the "get" value returned by `fun` and a
new map with the updated value under `key`.
## Examples
iex> Map.get_and_update(%{a: 1}, :a, fn current_value ->
...> {current_value, "new value!"}
...> end)
{1, %{a: "new value!"}}
iex> Map.get_and_update(%{a: 1}, :b, fn current_value ->
...> {current_value, "new value!"}
...> end)
{nil, %{b: "new value!", a: 1}}
"""
@spec get_and_update(map, key, (value -> {get, value})) :: {get, map} when get: term
def get_and_update(%{} = map, key, fun) do
current_value = case :maps.find(key, map) do
{:ok, value} -> value
@@ -68,8 +517,30 @@ defmodule Map do
def get_and_update(map, _key, _fun), do: :erlang.error({:badmap, map})
@doc """
Gets a value and updates a map only if the key exists in one operation.
Gets the value from `key` and updates it. Raises if there is no `key`.
This `fun` argument receives the value of `key` and must return a
two-elements tuple: the "get" value (the retrieved value, which can be
operated on before being returned) and the new value to be stored under
`key`.
The returned value is a tuple with the "get" value returned by `fun` and a
new map with the updated value under `key`.
## Examples
iex> Map.get_and_update!(%{a: 1}, :a, fn(current_value) ->
...> {current_value, "new value!"}
...> end)
{1, %{a: "new value!"}}
iex> Map.get_and_update!(%{a: 1}, :b, fn current_value ->
...> {current_value, "new value!"}
...> end)
** (KeyError) key :b not found
"""
@spec get_and_update!(map, key, (value -> {get, value})) :: {get, map} | no_return when get: term
def get_and_update!(%{} = map, key, fun) do
case :maps.find(key, map) do
{:ok, value} ->
@@ -101,6 +572,7 @@ defmodule Map do
#=> %{name: "john"}
"""
@spec from_struct(atom | struct) :: map
def from_struct(struct) when is_atom(struct) do
:maps.remove(:__struct__, struct.__struct__)
end
@@ -109,6 +581,27 @@ defmodule Map do
:maps.remove(:__struct__, struct)
end
def equal?(map1, map2)
@doc """
Checks if two maps are equal.
Two maps are considered to be equal if they contain
the same keys and those keys contain the same values.
## Examples
iex> Map.equal?(%{a: 1, b: 2}, %{b: 2, a: 1})
true
iex> Map.equal?(%{a: 1, b: 2}, %{b: 1, a: 2})
false
"""
@spec equal?(map, map) :: boolean
def equal?(%{} = map1, %{} = map2), do: map1 === map2
# TODO: Deprecate by 1.3
# TODO: Remove by 1.4
@doc false
def size(map) do
map_size(map)
end
end
+214 -41
View File
@@ -1,97 +1,270 @@
defmodule MapSet do
@moduledoc """
A set store.
A set of functions for working with sets.
The `MapSet` is represented internally as a struct, therefore
`%MapSet{}` can be used whenever there is a need to match
on any `MapSet`. Note though the struct fields are private and
must not be accessed directly. Instead, use the functions on this
or in the `Set` module.
The `MapSet` is implemented using `Map` data type.
For more information about the functions
and their APIs, please consult the `Set` module.
The `MapSet` is represented internally as a struct,
therefore `%MapSet{}` can be used whenever there is a
need to match on any `MapSet`. Note though the struct
fields are private and must not be accessed directly.
Instead, use the functions in this module.
"""
@behaviour Set
@opaque t :: %__MODULE__{map: map}
@type value :: term
defstruct map: %{}
@doc """
Returns a new set.
## Examples
iex> MapSet.new
#MapSet<[]>
"""
@spec new :: t
def new(), do: %MapSet{}
@doc """
Creates a set from an enumerable.
## Examples
iex> MapSet.new([:b, :a, 3])
#MapSet<[3, :a, :b]>
iex> MapSet.new([3, 3, 3, 2, 2, 1])
#MapSet<[1, 2, 3]>
"""
@spec new(Enum.t) :: t
def new(enumerable) do
Enum.reduce(enumerable, %MapSet{}, &put(&2, &1))
end
@doc """
Creates a mapset from an enumerable via the transformation function.
## Examples
iex> MapSet.new([1, 2, 1], fn x -> 2 * x end)
#MapSet<[2, 4]>
"""
@spec new(Enum.t, (term -> term)) :: t
def new(enumerable, transform) do
Enum.reduce(enumerable, %MapSet{}, &put(&2, transform.(&1)))
end
@doc """
Deletes `value` from `set`.
Returns a new set which is a copy of `set` but without `value`.
## Examples
iex> set = MapSet.new([1, 2, 3])
iex> MapSet.delete(set, 4)
#MapSet<[1, 2, 3]>
iex> MapSet.delete(set, 2)
#MapSet<[1, 3]>
"""
@spec delete(t, value) :: t
def delete(%MapSet{map: map} = set, term) do
%{set | map: Map.delete(map, term)}
end
def difference(%MapSet{} = set1, %MapSet{} = set2) do
reduce(set2, {:cont, set1}, fn value, acc ->
{:cont, delete(acc, value)}
end) |> elem(1)
@doc """
Returns a set that is `set1` without the members of `set2`.
## Examples
iex> MapSet.difference(MapSet.new([1, 2]), MapSet.new([2, 3, 4]))
#MapSet<[1]>
"""
@spec difference(t, t) :: t
def difference(%MapSet{map: map1}, %MapSet{map: map2}) do
map = :maps.fold(fn value, _, acc ->
Map.delete(acc, value)
end, map1, map2)
%MapSet{map: map}
end
def disjoint?(%MapSet{} = set1, %MapSet{} = set2) do
if size(set1) > size(set2), do: {set1, set2} = {set2, set1}
reduce(set1, {:cont, true}, fn value, _ ->
if member?(set2, value) do
{:halt, false}
@doc """
Checks if `set1` and `set2` have no members in common.
## Examples
iex> MapSet.disjoint?(MapSet.new([1, 2]), MapSet.new([3, 4]))
true
iex> MapSet.disjoint?(MapSet.new([1, 2]), MapSet.new([2, 3]))
false
"""
@spec disjoint?(t, t) :: boolean
def disjoint?(%MapSet{map: map1}, %MapSet{map: map2}) do
if map_size(map1) > map_size(map2), do: {map1, map2} = {map2, map1}
:maps.fold(fn value, _, _ ->
if Map.has_key?(map2, value) do
throw({:halt, false})
else
{:cont, true}
true
end
end) |> elem(1)
end, true, map1)
catch
{:halt, false} -> false
end
@doc """
Checks if two sets are equal.
The comparison between elements must be done using `===`.
## Examples
iex> MapSet.equal?(MapSet.new([1, 2]), MapSet.new([2, 1, 1]))
true
iex> MapSet.equal?(MapSet.new([1, 2]), MapSet.new([3, 4]))
false
"""
@spec equal?(t, t) :: boolean
def equal?(%MapSet{map: map1}, %MapSet{map: map2}) do
Map.equal?(map1, map2)
end
def intersection(%MapSet{} = set1, %MapSet{} = set2) do
if size(set1) > size(set2), do: {set1, set2} = {set2, set1}
reduce(set1, {:cont, new}, fn value, acc ->
if member?(set2, value) do
{:cont, put(acc, value)}
@doc """
Returns a set containing only members that `set1` and `set2` have in common.
## Examples
iex> MapSet.intersection(MapSet.new([1, 2]), MapSet.new([2, 3, 4]))
#MapSet<[2]>
iex> MapSet.intersection(MapSet.new([1, 2]), MapSet.new([3, 4]))
#MapSet<[]>
"""
@spec intersection(t, t) :: t
def intersection(%MapSet{map: map1}, %MapSet{map: map2}) do
if map_size(map1) > map_size(map2), do: {map1, map2} = {map2, map1}
map = :maps.fold(fn value, _, acc ->
if Map.has_key?(map2, value) do
Map.put(acc, value, true)
else
{:cont, acc}
acc
end
end) |> elem(1)
end, %{}, map1)
%MapSet{map: map}
end
@doc """
Checks if `set` contains `value`.
## Examples
iex> MapSet.member?(MapSet.new([1, 2, 3]), 2)
true
iex> MapSet.member?(MapSet.new([1, 2, 3]), 4)
false
"""
@spec member?(t, value) :: boolean
def member?(%MapSet{map: map}, value) do
Map.has_key?(map, value)
end
@doc """
Inserts `value` into `set` if `set` doesn't already contain it.
## Examples
iex> MapSet.put(MapSet.new([1, 2, 3]), 3)
#MapSet<[1, 2, 3]>
iex> MapSet.put(MapSet.new([1, 2, 3]), 4)
#MapSet<[1, 2, 3, 4]>
"""
@spec put(t, value) :: t
def put(%MapSet{map: map} = set, value) do
%{set | map: Map.put(map, value, nil)}
%{set | map: Map.put(map, value, true)}
end
@doc """
Returns the number of elements in `set`.
## Examples
iex> MapSet.size(MapSet.new([1, 2, 3]))
3
"""
@spec size(t) :: non_neg_integer
def size(%MapSet{map: map}) do
map_size(map)
end
def subset?(%MapSet{} = set1, %MapSet{} = set2) do
if size(set1) <= size(set2) do
reduce(set1, {:cont, true}, fn value, _ ->
if member?(set2, value), do: {:cont, true}, else: {:halt, false}
end) |> elem(1)
@doc """
Checks if `set1`'s members are all contained in `set2`.
This function checks if `set1` is a subset of `set2`.
## Examples
iex> MapSet.subset?(MapSet.new([1, 2]), MapSet.new([1, 2, 3]))
true
iex> MapSet.subset?(MapSet.new([1, 2, 3]), MapSet.new([1, 2]))
false
"""
@spec subset?(t, t) :: boolean
def subset?(%MapSet{map: map1}, %MapSet{map: map2}) do
if map_size(map1) <= map_size(map2) do
:maps.fold(fn value, _, _ ->
if Map.has_key?(map2, value) do
true
else
throw({:halt, false})
end
end, true, map1)
else
false
end
catch
{:halt, false} -> false
end
@doc false
def reduce(%MapSet{} = set, acc, fun) do
Enumerable.List.reduce(to_list(set), acc, fun)
end
@doc """
Converts `set` to a list.
## Examples
iex> MapSet.to_list(MapSet.new([1, 2, 3]))
[1, 2, 3]
"""
@spec to_list(t) :: list
def to_list(%MapSet{map: map}) do
Map.keys(map)
end
@doc """
Returns a set containing all members of `set1` and `set2`.
## Examples
iex> MapSet.union(MapSet.new([1, 2]), MapSet.new([2, 3, 4]))
#MapSet<[1, 2, 3, 4]>
"""
@spec union(t, t) :: t
def union(%MapSet{map: map1}, %MapSet{map: map2}) do
%MapSet{map: Map.merge(map1, map2)}
end
defimpl Enumerable do
def reduce(set, acc, fun), do: MapSet.reduce(set, acc, fun)
def reduce(set, acc, fun), do: Enumerable.List.reduce(MapSet.to_list(set), acc, fun)
def member?(set, val), do: {:ok, MapSet.member?(set, val)}
def count(set), do: {:ok, MapSet.size(set)}
end
+168 -115
View File
@@ -1,8 +1,9 @@
defmodule Module do
@moduledoc ~S'''
This module provides many functions to deal with modules during
compilation time. It allows a developer to dynamically attach
documentation, add, delete and register attributes and so forth.
Provides functions to deal with modules during compilation time.
It allows a developer to dynamically add, delete and register
attributes, attach documentation and so forth.
After a module is compiled, using many of the functions in
this module will raise errors, since it is out of their scope
@@ -65,6 +66,30 @@ defmodule Module do
* `@behaviour` (notice the British spelling)
Behaviours can be referenced by modules to ensure they implement
required specific function signatures defined by `@callback`.
For example, you can specify the URI.Parser behaviour as follows:
defmodule URI.Parser do
@doc "Parses the given URL"
@callback parse(uri_info :: URI.t) :: URI.t
@doc "Defines a default port"
@callback default_port() :: integer
end
And then a module may use it as:
defmodule URI.HTTP do
@behaviour URI.Parser
def default_port(), do: 80
def parse(info), do: info
end
If the behaviour changes or URI.HTTP does not implement one of the
callbacks, a warning will be raised.
Specifies an OTP or user-defined behaviour.
### Example
@@ -85,18 +110,18 @@ defmodule Module do
For the list of supported options, see Erlang's
[`:compile` module](http://www.erlang.org/doc/man/compile.html).
Several uses of `@compile` will accumulate instead of overriding
Multiple uses of `@compile` will accumulate instead of overriding
previous ones.
### Example
defmodule M do
@compile {:inline, myfun: 1}
defmodule M do
@compile {:inline, myfun: 1}
def myfun(arg) do
to_string(arg)
end
def myfun(arg) do
to_string(arg)
end
end
* `@doc`
@@ -111,20 +136,20 @@ defmodule Module do
### Example
defmodule M do
@doc "Hello world"
def hello do
"world"
end
@doc """
Sums `a` to `b`.
"""
def sum(a, b) do
a + b
end
defmodule M do
@doc "Hello world"
def hello do
"world"
end
@doc """
Sums `a` to `b`.
"""
def sum(a, b) do
a + b
end
end
* `@file`
Changes the filename used in stacktraces for the function or macro that
@@ -134,13 +159,13 @@ defmodule Module do
### Example
defmodule M do
@doc "Hello world"
@file "hello.ex"
def hello do
"world"
end
defmodule M do
@doc "Hello world"
@file "hello.ex"
def hello do
"world"
end
end
* `@moduledoc`
@@ -152,11 +177,11 @@ defmodule Module do
### Example
defmodule M do
@moduledoc """
A very useful module
"""
end
defmodule M do
@moduledoc """
A very useful module
"""
end
* `@on_definition`
@@ -191,28 +216,28 @@ defmodule Module do
### Example
defmodule H do
def on_def(_env, kind, name, args, guards, body) do
IO.puts "Defining #{kind} named #{name} with args:"
IO.inspect args
IO.puts "and guards"
IO.inspect guards
IO.puts "and body"
IO.puts Macro.to_string(body)
end
defmodule H do
def on_def(_env, kind, name, args, guards, body) do
IO.puts "Defining #{kind} named #{name} with args:"
IO.inspect args
IO.puts "and guards"
IO.inspect guards
IO.puts "and body"
IO.puts Macro.to_string(body)
end
end
defmodule M do
@on_definition {H, :on_def}
def hello(arg) when is_binary(arg) or is_list(arg) do
"Hello" <> to_string(arg)
end
defmodule M do
@on_definition {H, :on_def}
def hello(arg) when is_binary(arg) or is_list(arg) do
"Hello" <> to_string(arg)
end
def hello(_) do
:ok
end
def hello(_) do
:ok
end
end
* `@on_load`
@@ -224,31 +249,31 @@ defmodule Module do
### Example
defmodule M do
@on_load :load_check
defmodule M do
@on_load :load_check
def load_check do
if some_condition() do
:ok
else
nil
end
end
def some_condition do
false
def load_check do
if some_condition() do
:ok
else
nil
end
end
def some_condition do
false
end
end
* `@vsn`
Specify the module version. Accepts any valid Elixir value.
### Example
defmodule M do
@vsn "1.0"
end
defmodule M do
@vsn "1.0"
end
* `@external_resource`
@@ -271,18 +296,18 @@ defmodule Module do
For the list of supported warnings, see
[`:dialyzer` module](http://www.erlang.org/doc/man/dialyzer.html).
Several uses of `@dialyzer` will accumulate instead of overriding
Multiple uses of `@dialyzer` will accumulate instead of overriding
previous ones.
### Example
defmodule M do
@dialyzer {:nowarn_function, myfun: 1}
defmodule M do
@dialyzer {:nowarn_function, myfun: 1}
def myfun(arg) do
M.not_a_function(arg)
end
def myfun(arg) do
M.not_a_function(arg)
end
end
The following attributes are part of typespecs and are also reserved by
Elixir (see `Kernel.Typespec` for more information about typespecs):
@@ -298,9 +323,9 @@ defmodule Module do
also be added. A custom attribute is any valid identifier prefixed with an
`@` and followed by a valid Elixir value:
defmodule M do
@custom_attr [some: "stuff"]
end
defmodule M do
@custom_attr [some: "stuff"]
end
For more advanced options available when defining custom attributes, see
`register_attribute/3`.
@@ -464,14 +489,15 @@ defmodule Module do
end
@doc """
Concatenates a list of aliases and returns a new alias only
if the alias was already referenced. If the alias was not
referenced yet, fails with `ArgumentError`.
Concatenates a list of aliases and returns a new alias only if the alias
was already referenced.
If the alias was not referenced yet, fails with `ArgumentError`.
It handles char lists, binaries and atoms.
## Examples
iex> Module.safe_concat([Unknown, Module])
iex> Module.safe_concat([Module, Unknown])
** (ArgumentError) argument error
iex> Module.safe_concat([List, Chars])
@@ -484,14 +510,15 @@ defmodule Module do
end
@doc """
Concatenates two aliases and returns a new alias only
if the alias was already referenced. If the alias was not
referenced yet, fails with `ArgumentError`.
Concatenates two aliases and returns a new alias only if the alias was
already referenced.
If the alias was not referenced yet, fails with `ArgumentError`.
It handles char lists, binaries and atoms.
## Examples
iex> Module.safe_concat(Unknown, Module)
iex> Module.safe_concat(Module, Unknown)
** (ArgumentError) argument error
iex> Module.safe_concat(List, Chars)
@@ -504,9 +531,10 @@ defmodule Module do
end
@doc """
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
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, the function signature (the signature
should be omitted for types) and the documentation, which should
be either a binary or a boolean.
@@ -619,12 +647,12 @@ defmodule Module do
length(:lists.filter(fn(el) -> el == key end, list))
end
defp camelcase_to_underscore(<<c :: utf8, rest :: binary>>) when c >= ?A and c <= ?Z,
do: do_camelcase_to_underscore(rest, <<c + 32 :: utf8>>)
defp do_camelcase_to_underscore(<<c :: utf8, rest :: binary>>, acc) when c >= ?A and c <= ?Z,
do: do_camelcase_to_underscore(rest, <<acc :: binary, ?_, c + 32 :: utf8>>)
defp do_camelcase_to_underscore(<<c :: utf8, rest :: binary>>, acc),
do: do_camelcase_to_underscore(rest, <<acc :: binary, c>>)
defp camelcase_to_underscore(<<c::utf8, rest::binary>>) when c >= ?A and c <= ?Z,
do: do_camelcase_to_underscore(rest, <<c + 32::utf8>>)
defp do_camelcase_to_underscore(<<c::utf8, rest::binary>>, acc) when c >= ?A and c <= ?Z,
do: do_camelcase_to_underscore(rest, <<acc::binary, ?_, c + 32::utf8>>)
defp do_camelcase_to_underscore(<<c::utf8, rest::binary>>, acc),
do: do_camelcase_to_underscore(rest, <<acc::binary, c>>)
defp do_camelcase_to_underscore(<<>>, acc),
do: acc
@@ -658,6 +686,7 @@ defmodule Module do
@doc """
Checks if the module defines the given function or macro.
Use `defines?/3` to assert for a specific type.
## Examples
@@ -677,8 +706,9 @@ defmodule Module do
@doc """
Checks if the module defines a function or macro of the
given `kind`. `kind` can be any of `:def`, `:defp`,
`:defmacro` or `:defmacrop`.
given `kind`.
`kind` can be any of `:def`, `:defp`, `:defmacro` or `:defmacrop`.
## Examples
@@ -736,6 +766,7 @@ defmodule Module do
@doc """
Makes the given functions in `module` overridable.
An overridable function is lazily defined, allowing a
developer to customize it. See `Kernel.defoverridable/1` for
more information and documentation.
@@ -757,11 +788,13 @@ defmodule Module do
Module.LocalsTracker.yank(module, tuple)
end
old = :elixir_def_overridable.overridable(module)
merged = :orddict.update(tuple, fn({count, _, _, _}) ->
{count + 1, clause, neighbours, false}
end, {1, clause, neighbours, false}, old)
:elixir_def_overridable.overridable(module, merged)
old = :elixir_def_overridable.overridable(module)
count = case :maps.find(tuple, old) do
{:ok, {count, _, _, _}} -> count + 1
:error -> 1
end
new = :maps.put(tuple, {count, clause, neighbours, false}, old)
:elixir_def_overridable.overridable(module, new)
end
end, tuples)
end
@@ -770,12 +803,14 @@ defmodule Module do
Returns `true` if `tuple` in `module` is marked as overridable.
"""
def overridable?(module, tuple) do
!!List.keyfind(:elixir_def_overridable.overridable(module), tuple, 0)
:maps.is_key(tuple, :elixir_def_overridable.overridable(module))
end
@doc """
Puts an Erlang attribute to the given module with the given
key and value. The semantics of putting the attribute depends
key and value.
The semantics of putting the attribute depends
if the attribute was registered or not via `register_attribute/3`.
## Examples
@@ -785,11 +820,17 @@ defmodule Module do
end
"""
def put_attribute(module, key, value) when is_atom(key) do
def put_attribute(module, key, value) do
put_attribute(module, key, value, nil)
end
@doc false
def put_attribute(module, key, value, stack) when is_atom(key) do
assert_not_compiled!(:put_attribute, module)
table = data_table_for(module)
value = preprocess_attribute(key, value)
acc = :ets.lookup_element(table, {:elixir, :acc_attributes}, 2)
warn_if_redefining_doc_attribute(stack, table, key)
new =
if :lists.member(key, acc) do
@@ -805,9 +846,12 @@ defmodule Module do
end
@doc """
Gets the given attribute from a module. If the attribute
was marked with `accumulate` with `Module.register_attribute/3`,
a list is always returned.
Gets the given attribute from a module.
If the attribute was marked with `accumulate` with
`Module.register_attribute/3`, a list is always returned. `nil` is returned
if the attribute has not been marked with `accumulate` and has not been set
to any value.
The `@` macro compiles to a call to this function. For example,
the following code:
@@ -968,7 +1012,7 @@ defmodule Module do
end
@doc false
def get_attribute(module, key, warn) when is_atom(key) and (is_list(warn) or is_nil(warn)) do
def get_attribute(module, key, stack) when is_atom(key) and (is_list(stack) or is_nil(stack)) do
assert_not_compiled!(:get_attribute, module)
table = data_table_for(module)
@@ -981,8 +1025,8 @@ defmodule Module do
cond do
:lists.member(key, acc) ->
[]
is_list(warn) ->
:elixir_errors.warn warn_info(warn), "undefined module attribute @#{key}, " <>
is_list(stack) ->
:elixir_errors.warn warn_info(stack), "undefined module attribute @#{key}, " <>
"please remove access to @#{key} or explicitly set it before access"
nil
true ->
@@ -1038,13 +1082,9 @@ defmodule Module do
defp postprocess_attribute(_, value), do: value
defp get_doc_info(table, env) do
# TODO: Use :ets.take/2 with Erlang 18
case :ets.lookup(table, :doc) do
[doc: {_, _} = pair] ->
:ets.delete(table, :doc)
pair
[] ->
{env.line, nil}
case :ets.take(table, :doc) do
[doc: {_, _} = pair] -> pair
[] -> {env.line, nil}
end
end
@@ -1061,4 +1101,17 @@ defmodule Module do
raise ArgumentError,
"could not call #{fun} on module #{inspect module} because it was already compiled"
end
defp warn_if_redefining_doc_attribute(stack, table, key)
when is_list(stack) and key in [:doc, :typedoc, :moduledoc] do
case :ets.lookup(table, key) do
[{_, {line, val}}] when val != false ->
:elixir_errors.warn warn_info(stack),
"redefining @#{key} attribute previously set at line #{line}"
_ ->
false
end
end
defp warn_if_redefining_doc_attribute(nil, _table, _key), do: false
end
+3 -3
View File
@@ -1,15 +1,15 @@
# This is a module Elixir responsible for tracking
# This is an Elixir module responsible for tracking
# calls in order to extract Elixir modules' behaviour
# during compilation time.
#
# ## Implementation
#
# The implementation uses the digraph module to track
# all dependencies. The graph starts with one main vertice:
# all dependencies. The graph starts with one main vertex:
#
# * `:local` - points to local functions
#
# We also have can the following vertices:
# We can also have the following vertices:
#
# * `Module` - a module that was invoked via an import
# * `{name, arity}` - a local function/arity pair
+35 -29
View File
@@ -1,6 +1,6 @@
defmodule OptionParser do
@moduledoc """
This module contains functions to parse command line arguments.
This module contains functions to parse command line options.
"""
@type argv :: [String.t]
@@ -11,8 +11,11 @@ defmodule OptionParser do
@doc """
Parses `argv` into a keywords list.
It returns the parsed values, remaining arguments and the
invalid options.
It returns a three-element tuple as follows:
1. parsed switches,
2. remaining arguments,
3. invalid options.
## Examples
@@ -25,30 +28,30 @@ defmodule OptionParser do
iex> OptionParser.parse(["--source-path", "lib", "test/enum_test.exs", "--verbose"])
{[source_path: "lib", verbose: true], ["test/enum_test.exs"], []}
By default, Elixir will try to automatically parse switches.
By default, Elixir will try to automatically parse all switches.
Switches followed by a value will be assigned the value, as a string.
Switches without an argument, like `--debug` will automatically
be set to `true`. Switches followed by a value will be assigned
to the value, always as strings.
be set to `true`.
Note Elixir also converts the switches to underscore atoms, as
Note: Elixir also converts the switches to underscore atoms, so
`--source-path` becomes `:source_path`, to better suit Elixir
conventions. This means that option names on the command line cannot contain
underscores; such options will be reported as `:undefined` (in strict mode)
or `:invalid` (in basic mode).
underscores; such options will be put in the invalid options list.
## Switches
## Switch Definitions
Many times though, it is better to explicitly list the available
Often it is better to explicitly list the known
switches and their formats. The switches can be specified via two
different options:
alternative options:
* `:strict` - the switches are strict. Any switch that does not
exist in the switch list is treated as an error.
* `:switches` - defines some switches. An attempt is still made to parse
switches that do not appear in the list.
* `:switches` - defines some switches. Switches that does not
exist in the switch list are still attempted to be parsed.
* `:strict` - the switches are strict. Any switch that is not specified
in the list is returned in the invalid options list.
Note only `:strict` or `:switches` may be given at once.
Note that you should only supply the `:switches` or `:strict` option. If you
supply both, an error will be raised.
For each switch, the following types are supported:
@@ -59,13 +62,14 @@ defmodule OptionParser do
* `:float` - parses the switch as a float.
* `:string` - returns the switch as a string.
If a switch can't be parsed or is not specified in the strict case,
the option is returned in the invalid options list (third element
of the returned tuple).
If a switch can't be parsed, it is returned in the invalid options list.
The following extra "types" are supported:
* `:keep` - keeps duplicated items in the list instead of overriding
* `:keep` - keeps duplicated items in the list instead of overriding them.
Note: if you want to use `:keep` with a non-string type, use a list, e.g.
`[foo: [:integer, :keep]]`.
Examples:
@@ -181,7 +185,7 @@ defmodule OptionParser do
command line)
* `{:undefined, key, value, rest}` - the option `key` is undefined
(returned on strict cases and the switch is unknown)
(returned in strict mode when the switch is unknown)
* `{:error, rest}` - there are no switches at the top of the given argv
"""
@@ -235,7 +239,7 @@ defmodule OptionParser do
end
@doc """
Receives a key-value enumerable and convert it to argv.
Receives a key-value enumerable and converts it to argv.
Keys must be atoms. Keys with nil value are discarded,
boolean values are converted to `--key` or `--no-key`
@@ -281,23 +285,23 @@ defmodule OptionParser do
end
# If we have an escaped quote, simply remove the escape
defp do_split(<<?\\, quote, t :: binary>>, buffer, acc, quote),
defp do_split(<<?\\, quote, t::binary>>, buffer, acc, quote),
do: do_split(t, <<buffer::binary, quote>>, acc, quote)
# If we have a quote and we were not in a quote, start one
defp do_split(<<quote, t :: binary>>, buffer, acc, nil) when quote in [?", ?'],
defp do_split(<<quote, t::binary>>, buffer, acc, nil) when quote in [?", ?'],
do: do_split(t, buffer, acc, quote)
# If we have a quote and we were inside it, close it
defp do_split(<<quote, t :: binary>>, buffer, acc, quote),
defp do_split(<<quote, t::binary>>, buffer, acc, quote),
do: do_split(t, buffer, acc, nil)
# If we have an escaped quote/space, simply remove the escape as long as we are not inside a quote
defp do_split(<<?\\, h, t :: binary>>, buffer, acc, nil) when h in [?\s, ?', ?"],
defp do_split(<<?\\, h, t::binary>>, buffer, acc, nil) when h in [?\s, ?', ?"],
do: do_split(t, <<buffer::binary, h>>, acc, nil)
# If we have space and we are outside of a quote, start new segment
defp do_split(<<?\s, t :: binary>>, buffer, acc, nil),
defp do_split(<<?\s, t::binary>>, buffer, acc, nil),
do: do_split(strip_leading_spaces(t), "", [buffer|acc], nil)
# All other characters are moved to buffer
@@ -326,6 +330,8 @@ defmodule OptionParser do
aliases = opts[:aliases] || []
{switches, strict} = cond do
opts[:switches] && opts[:strict] ->
raise ArgumentError, ":switches and :strict cannot be given together"
s = opts[:switches] ->
{s, false}
s = opts[:strict] ->
@@ -377,7 +383,7 @@ defmodule OptionParser do
end
end
defp tag_option(<<?-, option :: binary>>, switches, _aliases) do
defp tag_option(<<?-, option::binary>>, switches, _aliases) do
get_negated(option, switches)
end
+31 -35
View File
@@ -73,7 +73,7 @@ defmodule Path do
do: absname_join([volume|rest])
# Relative to current directory on current drive.
defp absname_vr([<<x, ?:>>|rest], [<<x, _ :: binary>>|_], relative),
defp absname_vr([<<x, ?:>>|rest], [<<x, _::binary>>|_], relative),
do: absname(absname_join(rest), relative)
# Relative to current directory on another drive.
@@ -96,13 +96,13 @@ defmodule Path do
defp absname_join(left, right),
do: do_absname_join(IO.chardata_to_string(left), relative(right), [], major_os_type())
defp do_absname_join(<<uc_letter, ?:, rest :: binary>>, relativename, [], :win32) when uc_letter in ?A..?Z, do:
defp do_absname_join(<<uc_letter, ?:, rest::binary>>, relativename, [], :win32) when uc_letter in ?A..?Z, do:
do_absname_join(rest, relativename, [?:, uc_letter+?a-?A], :win32)
defp do_absname_join(<<?\\, rest :: binary>>, relativename, result, :win32), do:
do_absname_join(<<?/, rest :: binary>>, relativename, result, :win32)
defp do_absname_join(<<?/, rest :: binary>>, relativename, [?., ?/|result], os_type), do:
defp do_absname_join(<<?\\, rest::binary>>, relativename, result, :win32), do:
do_absname_join(<<?/, rest::binary>>, relativename, result, :win32)
defp do_absname_join(<<?/, rest::binary>>, relativename, [?., ?/|result], os_type), do:
do_absname_join(rest, relativename, [?/|result], os_type)
defp do_absname_join(<<?/, rest :: binary>>, relativename, [?/|result], os_type), do:
defp do_absname_join(<<?/, rest::binary>>, relativename, [?/|result], os_type), do:
do_absname_join(rest, relativename, [?/|result], os_type)
defp do_absname_join(<<>>, <<>>, result, os_type), do:
IO.iodata_to_binary(reverse_maybe_remove_dirsep(result, os_type))
@@ -112,7 +112,7 @@ defmodule Path do
do_absname_join(relativename, <<>>, [?/|result], os_type)
defp do_absname_join(<<>>, relativename, result, os_type), do:
do_absname_join(relativename, <<>>, [?/|result], os_type)
defp do_absname_join(<<char, rest :: binary>>, relativename, result, os_type), do:
defp do_absname_join(<<char, rest::binary>>, relativename, result, os_type), do:
do_absname_join(rest, relativename, [char|result], os_type)
defp reverse_maybe_remove_dirsep([?/, ?:, letter], :win32), do:
@@ -225,7 +225,7 @@ defmodule Path do
end
end
defp unix_pathtype(<<?/, relative :: binary>>), do:
defp unix_pathtype(<<?/, relative::binary>>), do:
{:absolute, relative}
defp unix_pathtype([?/|relative]), do:
{:absolute, relative}
@@ -240,13 +240,13 @@ defmodule Path do
win32_pathtype(list++rest)
defp win32_pathtype([char, list|rest]) when is_list(list), do:
win32_pathtype([char|list++rest])
defp win32_pathtype(<<c1, c2, relative :: binary>>) when c1 in @slash and c2 in @slash, do:
defp win32_pathtype(<<c1, c2, relative::binary>>) when c1 in @slash and c2 in @slash, do:
{:absolute, relative}
defp win32_pathtype(<<c, relative :: binary>>) when c in @slash, do:
defp win32_pathtype(<<c, relative::binary>>) when c in @slash, do:
{:volumerelative, relative}
defp win32_pathtype(<<_letter, ?:, c, relative :: binary>>) when c in @slash, do:
defp win32_pathtype(<<_letter, ?:, c, relative::binary>>) when c in @slash, do:
{:absolute, relative}
defp win32_pathtype(<<_letter, ?:, relative :: binary>>), do:
defp win32_pathtype(<<_letter, ?:, relative::binary>>), do:
{:volumerelative, relative}
defp win32_pathtype([c1, c2 | relative]) when c1 in @slash and c2 in @slash, do:
@@ -628,32 +628,28 @@ defmodule Path do
end
end
defp expand_dot(<<"/../", rest::binary>>),
do: expand_dot("/" <> rest)
defp expand_dot(<<letter, ":/../", rest::binary>>) when letter in ?a..?z,
do: expand_dot(<<letter, ":/", rest::binary>>)
defp expand_dot("/.."),
do: "/"
defp expand_dot(<<letter, ":/..">>) when letter in ?a..?z,
do: expand_dot(<<letter, ":/">>)
defp expand_dot(<<"/", rest::binary>>),
do: "/" <> do_expand_dot(rest)
defp expand_dot(<<letter, ":/", rest::binary>>) when letter in ?a..?z,
do: <<letter, ":/">> <> do_expand_dot(rest)
defp expand_dot(path),
do: expand_dot(:binary.split(path, "/", [:global]), [])
do: do_expand_dot(path)
defp expand_dot([".."|t], [_, _|acc]) do
expand_dot t, acc
end
defp do_expand_dot(path),
do: do_expand_dot(:binary.split(path, "/", [:global]), [])
defp expand_dot(["."|t], acc) do
expand_dot t, acc
end
defp expand_dot([h|t], acc) do
expand_dot t, ["/", h|acc]
end
defp expand_dot([], ["/"|acc]) do
IO.iodata_to_binary(:lists.reverse(acc))
end
defp do_expand_dot([".."|t], [_, _|acc]),
do: do_expand_dot(t, acc)
defp do_expand_dot([".."|t], []),
do: do_expand_dot(t, [])
defp do_expand_dot(["."|t], acc),
do: do_expand_dot(t, acc)
defp do_expand_dot([h|t], acc),
do: do_expand_dot(t, ["/", h|acc])
defp do_expand_dot([], []),
do: ""
defp do_expand_dot([], ["/"|acc]),
do: IO.iodata_to_binary(:lists.reverse(acc))
defp major_os_type do
:os.type |> elem(0)
+54 -9
View File
@@ -3,56 +3,98 @@ defmodule Port do
Functions related to Erlang ports.
"""
@type name :: {:spawn, char_list | binary} |
{:spawn_driver, char_list | binary} |
{:spawn_executable, char_list | atom} |
{:fd, non_neg_integer, non_neg_integer}
@doc """
See [`:erlang.open_port/2`](http://www.erlang.org/doc/man/erlang.html#open_port-2)
Opens an Erlang port given a tuple `name` and a list of `settings`.
## Name
The supported values for `name` are:
* `{:spawn, command}` - to run an external program. The first space separated
word of `command` will be considered as the name of the program to run, so
use `{:spawn_executable, command}` to run a program having spaces in its name.
* `{:spawn_driver, command}` - similar to `{:spawn, command}`, but to run a
loaded driver.
* `{:spawn_executable, filename}` - similar to `{:spawn, filename}`, but to run
an external executable. With this option, `filename` in its whole is considered
the name of the program to execute.
* `{:fd, fd_in, fd_out}` - to access file descriptors used by Erlang, `fd_in`
being used for standard input, `fd_out` for standard output.
For more information, see [`:erlang.open_port/2`](http://www.erlang.org/doc/man/erlang.html#open_port-2).
Inlined by the compiler.
"""
@spec open(name, list) :: port
def open(name, settings) do
:erlang.open_port(name, settings)
end
@doc """
See [`:erlang.port_close/1`](http://www.erlang.org/doc/man/erlang.html#port_close-1)
Closes the `port`.
For more information, see [`:erlang.port_close/1`](http://www.erlang.org/doc/man/erlang.html#port_close-1).
Inlined by the compiler.
"""
@spec close(port) :: true
def close(port) do
:erlang.port_close(port)
end
@doc """
See [`:erlang.port_command/2`](http://www.erlang.org/doc/man/erlang.html#port_command-2)
Sends `data` to the port driver `port`.
For more information, see [`:erlang.port_command/2`](http://www.erlang.org/doc/man/erlang.html#port_command-2).
Inlined by the compiler.
"""
@spec command(port, iodata, [:force | :nosuspend]) :: boolean
def command(port, data, options \\ []) do
:erlang.port_command(port, data, options)
end
@doc """
See [`:erlang.port_connect/2`](http://www.erlang.org/doc/man/erlang.html#port_connect-2)
Associates the `port` identifier with a `pid`.
For more information, see [`:erlang.port_connect/2`](http://www.erlang.org/doc/man/erlang.html#port_connect-2).
Inlined by the compiler.
"""
@spec connect(port, pid) :: true
def connect(port, pid) do
:erlang.port_connect(port, pid)
end
@doc """
See [`:erlang.port_control/3`](http://www.erlang.org/doc/man/erlang.html#port_control-3)
Sends a synchronous control command to the `port` and returns its reply as a binary.
Not all port drivers support this feature.
For more information, see [`:erlang.port_control/3`](http://www.erlang.org/doc/man/erlang.html#port_control-3).
Inlined by the compiler.
"""
@spec control(port, integer, iodata) :: iodata | binary
def control(port, operation, data) do
:erlang.port_control(port, operation, data)
end
@doc """
See [`:erlang.port_call/3`](http://www.erlang.org/doc/man/erlang.html#port_call-3)
Makes a synchronous call to the `port` and returns its reply as a term.
Not all port drivers support this control feature.
For more information, see [`:erlang.port_call/3`](http://www.erlang.org/doc/man/erlang.html#port_call-3).
Inlined by the compiler.
"""
@spec call(port, integer, term) :: term
def call(port, operation, data) do
:erlang.port_call(port, operation, data)
end
@@ -61,7 +103,7 @@ defmodule Port do
Returns information about the `port`
or `nil` if the port is closed.
See [`:erlang.port_info/1`](http://www.erlang.org/doc/man/erlang.html#port_info-1)
For more information, see [`:erlang.port_info/1`](http://www.erlang.org/doc/man/erlang.html#port_info-1).
"""
def info(port) do
nillify :erlang.port_info(port)
@@ -71,7 +113,7 @@ defmodule Port do
Returns information about the `port`
or `nil` if the port is closed.
See [`:erlang.port_info/2`](http://www.erlang.org/doc/man/erlang.html#port_info-2)
For more information, see [`:erlang.port_info/2`](http://www.erlang.org/doc/man/erlang.html#port_info-2).
"""
@spec info(port, atom) :: {atom, term} | nil
def info(port, spec)
@@ -88,10 +130,13 @@ defmodule Port do
end
@doc """
See [`:erlang.ports/0`](http://www.erlang.org/doc/man/erlang.html#ports-0)
Returns a list of the ports for the current node.
For more information, see [`:erlang.ports/0`](http://www.erlang.org/doc/man/erlang.html#ports-0).
Inlined by the compiler.
"""
@spec list :: [port]
def list do
:erlang.ports
end
+64 -5
View File
@@ -19,6 +19,8 @@ defmodule Process do
and has not exited yet). Otherwise, returns `false`.
`pid` must refer to a process at the local node.
Inlined by the compiler.
"""
@spec alive?(pid) :: boolean
def alive?(pid) do
@@ -27,6 +29,8 @@ defmodule Process do
@doc """
Returns all key-values in the dictionary.
Inlined by the compiler.
"""
@spec get :: [{term, term}]
def get do
@@ -47,8 +51,20 @@ defmodule Process do
end
end
@doc """
Returns all keys in the process dictionary.
Inlined by the compiler.
"""
@spec get_keys() :: [term]
def get_keys() do
:erlang.get_keys()
end
@doc """
Returns all keys that have the given `value`.
Inlined by the compiler.
"""
@spec get_keys(term) :: [term]
def get_keys(value) do
@@ -57,6 +73,9 @@ defmodule Process do
@doc """
Stores the given key-value in the process dictionary.
The return value is the value that was previously stored under the key `key`
(or `nil` in case no value was stored under `key`).
"""
@spec put(term, term) :: term | nil
def put(key, value) do
@@ -120,11 +139,10 @@ defmodule Process do
:noconnect
"""
@spec send(dest, msg, [option]) :: result when
@spec send(dest, msg, [option]) :: :ok | :noconnect | :nosuspend when
dest: pid | port | atom | {atom, node},
msg: any,
option: :noconnect | :nosuspend,
result: :ok | :noconnect | :nosuspend
option: :noconnect | :nosuspend
def send(dest, msg, options) do
:erlang.send(dest, msg, options)
end
@@ -138,8 +156,7 @@ defmodule Process do
not refer to a process.
This function returns a timer reference, which can be read or canceled with
`:erlang.read_timer/1`, `:erlang.start_timer/3` and `:erlang.cancel_timer/1`.
Note `time` cannot be greater than `4294967295`.
`read_timer/1` and `cancel_timer/1`.
Finally, the timer will be automatically canceled if the given `dest` is a pid
which is not alive or when the given pid exits. Note that timers will not be
@@ -151,6 +168,46 @@ defmodule Process do
:erlang.send_after(time, dest, msg)
end
@doc """
Cancels a timer created by `send_after/3`.
When the result is an integer, it represents the time in milli-seconds
left until the timer will expire.
When the result is `false`, a timer corresponding to `timer_ref` could
not be found. This can be either because the timer expired, already has
been canceled, or because `timer_ref` never corresponded to a timer.
If the timer has expired, the timeout message has been sent, but it does
not tell you whether or not it has arrived at its destination yet.
Inlined by the compiler.
"""
@spec cancel_timer(reference) :: non_neg_integer | false
def cancel_timer(timer_ref) do
:erlang.cancel_timer(timer_ref)
end
@doc """
Reads a timer created by `send_after/3`.
When the result is an integer, it represents the time in milli-seconds
left until the timer will expire.
When the result is `false`, a timer corresponding to `timer_ref` could
not be found. This can be either because the timer expired, already has
been canceled, or because `timer_ref` never corresponded to a timer.
If the timer has expired, the timeout message has been sent, but it does
not tell you whether or not it has arrived at its destination yet.
Inlined by the compiler.
"""
@spec read_timer(reference) :: non_neg_integer | false
def read_timer(timer_ref) do
:erlang.read_timer(timer_ref)
end
@type spawn_opt :: :link | :monitor | {:priority, :low | :normal | :high} |
{:fullsweep_after, non_neg_integer} |
{:min_heap_size, non_neg_integer} |
@@ -199,6 +256,8 @@ defmodule Process do
The calling process starts monitoring the item given.
It returns the monitor reference.
See [the need for monitoring](http://elixir-lang.org/getting-started/mix-otp/genserver.html#the-need-for-monitoring)
for an example.
See [`:erlang.monitor/2`](http://www.erlang.org/doc/man/erlang.html#monitor-2) for more info.
Inlined by the compiler.
+20 -16
View File
@@ -217,15 +217,6 @@ defmodule Protocol do
end
end
defmacrop if_ok(expr, call) do
quote do
case unquote(expr) do
{:ok, var} -> unquote(Macro.pipe(quote(do: var), call, 0))
other -> other
end
end
end
@doc """
Returns `true` if the protocol was consolidated.
"""
@@ -263,9 +254,9 @@ defmodule Protocol do
{:error, :not_a_protocol} |
{:error, :no_beam_info}
def consolidate(protocol, types) when is_atom(protocol) do
beam_protocol(protocol)
|> if_ok(change_debug_info types)
|> if_ok(compile)
with {:ok, info} <- beam_protocol(protocol),
{:ok, code, docs} <- change_debug_info(info, types),
do: compile(code, docs)
end
@docs_chunk 'ExDc'
@@ -302,7 +293,7 @@ defmodule Protocol do
all = [Any] ++ for {_guard, mod} <- builtin, do: mod
structs = types -- all
case change_impl_for(code, protocol, types, structs, false, []) do
{:ok, ret} -> {:ok, {ret, docs}}
{:ok, ret} -> {:ok, ret, docs}
other -> other
end
end
@@ -320,7 +311,7 @@ defmodule Protocol do
end
defp change_impl_for([{:function, line, :impl_for, 1, _}|t], protocol, types, structs, is_protocol, acc) do
fallback = if Any in types, do: load_impl(protocol, Any), else: nil
fallback = if Any in types, do: load_impl(protocol, Any)
clauses = for {guard, mod} <- builtin,
mod in types,
@@ -334,7 +325,7 @@ defmodule Protocol do
end
defp change_impl_for([{:function, line, :struct_impl_for, 1, _}|t], protocol, types, structs, is_protocol, acc) do
fallback = if Any in types, do: load_impl(protocol, Any), else: nil
fallback = if Any in types, do: load_impl(protocol, Any)
clauses = for struct <- structs, do: each_struct_clause_for(struct, protocol, line)
clauses = clauses ++ [fallback_clause_for(fallback, protocol, line)]
@@ -393,7 +384,7 @@ defmodule Protocol do
end
# Finally compile the module and emit its bytecode.
defp compile({{protocol, code}, docs}) do
defp compile({protocol, code}, docs) do
opts = if Code.compiler_options[:debug_info], do: [:debug_info], else: []
{:ok, ^protocol, binary, _warnings} = :compile.forms(code, [:return|opts])
unless docs == :missing_chunk do
@@ -549,6 +540,7 @@ defmodule Protocol do
"the dict[key] syntax, please implement the Dict behaviour instead"
else
Protocol.assert_protocol!(protocol)
Protocol.__ensure_defimpl__(protocol, for, __ENV__)
end
defmodule name do
@@ -589,6 +581,7 @@ defmodule Protocol do
defp derive(protocol, for, struct, opts, env) do
extra = ", cannot derive #{inspect protocol} for #{inspect for}"
assert_protocol!(protocol, extra)
__ensure_defimpl__(protocol, for, env)
assert_impl!(protocol, Any, extra)
# Clean up variables from eval context
@@ -617,6 +610,17 @@ defmodule Protocol do
end)
end
@doc false
def __ensure_defimpl__(protocol, for, env) do
if Protocol.consolidated?(protocol) do
message =
"the #{inspect protocol} protocol has already been consolidated" <>
", an implementation for #{inspect for} has no effect"
:elixir_errors.warn(env.line, env.file, message)
end
:ok
end
@doc false
def __spec__?(module, name, arity) do
signature = {name, arity}
+23 -16
View File
@@ -1,12 +1,12 @@
defmodule Range do
@moduledoc """
Defines a Range.
Defines a range.
A Range represents a discrete number of values where
A range represents a discrete number of values where
the first and last values are integers.
Ranges can be either increasing (first <= last) or
decresing (first > last). Ranges are also always
decreasing (first > last). Ranges are also always
inclusive.
A Range is represented internally as a struct. However,
@@ -15,7 +15,7 @@ defmodule Range do
iex> range = 1..3
1..3
iex> first .. last = range
iex> first..last = range
iex> first
1
iex> last
@@ -31,12 +31,21 @@ defmodule Range do
@doc """
Creates a new range.
"""
def new(first, last) do
@spec new(integer, integer) :: t
def new(first, last) when is_integer(first) and is_integer(last) do
%Range{first: first, last: last}
end
def new(first, last) do
raise ArgumentError,
"ranges (first..last) expect both sides to be integers, " <>
"got: #{inspect first}..#{inspect last}"
end
@doc """
Returns `true` if the given argument is a range.
Returns `true` if the given `term` is a range.
It does not check if the range is valid.
## Examples
@@ -47,13 +56,15 @@ defmodule Range do
false
"""
@spec range?(%Range{}) :: true
@spec range?(term) :: false
def range?(term)
def range?(%Range{}), do: true
def range?(_), do: false
end
defimpl Enumerable, for: Range do
def reduce(first .. last, acc, fun) do
validate_range!(first, last)
reduce(first, last, acc, fun, last >= first)
end
@@ -77,8 +88,7 @@ defimpl Enumerable, for: Range do
{:done, acc}
end
def member?(first .. last, value) do
validate_range!(first, last)
def member?(first .. last, value) when is_integer(value) do
if first <= last do
{:ok, first <= value and value <= last}
else
@@ -86,20 +96,17 @@ defimpl Enumerable, for: Range do
end
end
def member?(_ .. _, _value) do
{:ok, false}
end
def count(first .. last) do
validate_range!(first, last)
if first <= last do
{:ok, last - first + 1}
else
{:ok, first - last + 1}
end
end
defp validate_range!(first, last) when is_integer(first) and is_integer(last), do: :ok
defp validate_range!(first, last) do
raise ArgumentError,
"ranges (left .. right) expect both sides to be integers, got: #{inspect first..last}"
end
end
defimpl Inspect, for: Range do
+2 -2
View File
@@ -1,6 +1,6 @@
defmodule Record do
@moduledoc """
Module to work, define and import records.
Module to work with, define and import records.
Records are simply tuples where the first element is an atom:
@@ -10,7 +10,7 @@ defmodule Record do
This module provides conveniences for working with records at
compilation time, where compile-time field names are used to
manipulate the tuples, providing fast operations on top of
the tuples compact structure.
the tuples' compact structure.
In Elixir, records are used mostly in two situations:
+27 -24
View File
@@ -1,6 +1,7 @@
defmodule Regex do
@moduledoc ~S"""
Regular expressions for Elixir built on top of Erlang's `:re` module.
Provides regular expressions for Elixir. Built on top of Erlang's `:re`
module.
As the `:re` module, Regex is based on PCRE
(Perl Compatible Regular Expressions). More information can be
@@ -22,7 +23,7 @@ defmodule Regex do
The modifiers available when creating a Regex are:
* `unicode` (u) - enables unicode specific patterns like `\p` and changes
* `unicode` (u) - enables unicode specific patterns like `\p` and change
modifiers like `\w`, `\W`, `\s` and friends to also match on unicode.
It expects valid unicode strings to be given on match
@@ -55,7 +56,7 @@ defmodule Regex do
## Captures
Many functions in this module allows what to capture in a regex
Many functions in this module handle what to capture in a regex
match via the `:capture` option. The supported values are:
* `:all` - all captured subpatterns including the complete matching string
@@ -379,7 +380,7 @@ defmodule Regex do
end
end
def split(%Regex{re_pattern: compiled}, string, opts) when is_binary(string) do
def split(%Regex{re_pattern: compiled}, string, opts) when is_binary(string) and is_list(opts) do
on = Keyword.get(opts, :on, :first)
case :re.run(string, compiled, [:global, capture: on]) do
{:match, matches} ->
@@ -469,11 +470,13 @@ defmodule Regex do
@spec replace(t, String.t, String.t | (... -> String.t), [term]) :: String.t
def replace(regex, string, replacement, options \\ [])
def replace(regex, string, replacement, options) when is_binary(replacement) do
def replace(regex, string, replacement, options)
when is_binary(string) and is_binary(replacement) and is_list(options) do
do_replace(regex, string, precompile_replacement(replacement), options)
end
def replace(regex, string, replacement, options) when is_function(replacement) do
def replace(regex, string, replacement, options)
when is_binary(string) and is_function(replacement) and is_list(options) do
{:arity, arity} = :erlang.fun_info(replacement, :arity)
do_replace(regex, string, {replacement, arity}, options)
end
@@ -495,30 +498,30 @@ defmodule Regex do
defp precompile_replacement(""),
do: []
defp precompile_replacement(<<?\\, ?g, ?{, rest :: binary>>) when byte_size(rest) > 0 do
{ns, <<?}, rest :: binary>>} = pick_int(rest)
defp precompile_replacement(<<?\\, ?g, ?{, rest::binary>>) when byte_size(rest) > 0 do
{ns, <<?}, rest::binary>>} = pick_int(rest)
[List.to_integer(ns) | precompile_replacement(rest)]
end
defp precompile_replacement(<<?\\, ?\\, rest :: binary>>) do
defp precompile_replacement(<<?\\, ?\\, rest::binary>>) do
[<<?\\>> | precompile_replacement(rest)]
end
defp precompile_replacement(<<?\\, x, rest :: binary>>) when x in ?0..?9 do
defp precompile_replacement(<<?\\, x, rest::binary>>) when x in ?0..?9 do
{ns, rest} = pick_int(rest)
[List.to_integer([x|ns]) | precompile_replacement(rest)]
end
defp precompile_replacement(<<x, rest :: binary>>) do
defp precompile_replacement(<<x, rest::binary>>) do
case precompile_replacement(rest) do
[head | t] when is_binary(head) ->
[<<x, head :: binary>> | t]
[<<x, head::binary>> | t]
other ->
[<<x>> | other]
end
end
defp pick_int(<<x, rest :: binary>>) when x in ?0..?9 do
defp pick_int(<<x, rest::binary>>) when x in ?0..?9 do
{found, rest} = pick_int(rest)
{[x|found], rest}
end
@@ -541,12 +544,12 @@ defmodule Regex do
defp apply_list(whole, string, pos, replacement, [[{mpos, _} | _] | _] = list) when mpos > pos do
length = mpos - pos
<<untouched :: binary-size(length), rest :: binary>> = string
<<untouched::binary-size(length), rest::binary>> = string
[untouched | apply_list(whole, rest, mpos, replacement, list)]
end
defp apply_list(whole, string, pos, replacement, [[{pos, length} | _] = head | tail]) do
<<_ :: size(length)-binary, rest :: binary>> = string
<<_::size(length)-binary, rest::binary>> = string
new_data = apply_replace(whole, replacement, head)
[new_data | apply_list(whole, rest, pos + length, replacement, tail)]
end
@@ -579,7 +582,7 @@ defmodule Regex do
end
defp get_index(string, {pos, len}) do
<<_ :: size(pos)-binary, res :: size(len)-binary, _ :: binary>> = string
<<_::size(pos)-binary, res::size(len)-binary, _::binary>> = string
res
end
@@ -629,17 +632,17 @@ defmodule Regex do
# Private Helpers
defp translate_options(<<?u, t :: binary>>, acc), do: translate_options(t, [:unicode, :ucp|acc])
defp translate_options(<<?i, t :: binary>>, acc), do: translate_options(t, [:caseless|acc])
defp translate_options(<<?x, t :: binary>>, acc), do: translate_options(t, [:extended|acc])
defp translate_options(<<?f, t :: binary>>, acc), do: translate_options(t, [:firstline|acc])
defp translate_options(<<?U, t :: binary>>, acc), do: translate_options(t, [:ungreedy|acc])
defp translate_options(<<?s, t :: binary>>, acc), do: translate_options(t, [:dotall, {:newline, :anycrlf}|acc])
defp translate_options(<<?m, t :: binary>>, acc), do: translate_options(t, [:multiline|acc])
defp translate_options(<<?u, t::binary>>, acc), do: translate_options(t, [:unicode, :ucp|acc])
defp translate_options(<<?i, t::binary>>, acc), do: translate_options(t, [:caseless|acc])
defp translate_options(<<?x, t::binary>>, acc), do: translate_options(t, [:extended|acc])
defp translate_options(<<?f, t::binary>>, acc), do: translate_options(t, [:firstline|acc])
defp translate_options(<<?U, t::binary>>, acc), do: translate_options(t, [:ungreedy|acc])
defp translate_options(<<?s, t::binary>>, acc), do: translate_options(t, [:dotall, {:newline, :anycrlf}|acc])
defp translate_options(<<?m, t::binary>>, acc), do: translate_options(t, [:multiline|acc])
# TODO: Deprecate by 1.2
# TODO: Remove by 2.0
defp translate_options(<<?r, t :: binary>>, acc), do: translate_options(t, [:ungreedy|acc])
defp translate_options(<<?r, t::binary>>, acc), do: translate_options(t, [:ungreedy|acc])
defp translate_options(<<>>, acc), do: acc
defp translate_options(rest, _acc), do: {:error, rest}
+10 -189
View File
@@ -1,35 +1,16 @@
defmodule Set do
@moduledoc ~S"""
This module specifies the `Set` behaviour expected to be
implemented by different representations of sets.
WARNING: this module is deprecated.
It also provides functions that redirect to the
underlying implementation, allowing a developer to work with
different `Set` implementations using a common API.
To create a new set, use the `new` function which each set implementation
defines:
HashSet.new #=> creates an empty HashSet
In the examples below, `set_impl` means a specific
`Set` implementation, for example `HashSet`.
## Protocols
Sets are required to implement both the `Enumerable` and `Collectable`
protocols.
## Matching
Sets are required to implement all equality checks using the match (`===`)
operator.
Use the `MapSet` module instead.
"""
@type value :: any
@type values :: [ value ]
@type t :: map
# TODO: Remove callbacks on 1.3
# TODO: Deprecate every function on 1.3
@callback new :: t
@callback delete(t, value) :: t
@callback difference(t, t) :: t
@@ -54,42 +35,10 @@ defmodule Set do
end
end
@doc """
Deletes `value` from `set`.
Returns a new set which is a copy of `set` but without `value`.
## Examples
iex> s = Enum.into([1, 2, 3], set_impl.new)
iex> Set.delete(s, 4) |> Enum.sort
[1, 2, 3]
iex> s = Enum.into([1, 2, 3], set_impl.new)
iex> Set.delete(s, 2) |> Enum.sort
[1, 3]
"""
@spec delete(t, value) :: t
def delete(set, value) do
target(set).delete(set, value)
end
@doc """
Returns a set that is `set1` without the members of `set2`.
Note that this function is polymorphic as it calculates the difference for
sets of the same type as well as of sets of different types. Each set
implementation also provides a `difference` function which only works with
sets of that type.
## Examples
iex> Set.difference(Enum.into([1, 2], set_impl.new), Enum.into([2, 3, 4], set_impl.new)) |> Enum.sort
[1]
"""
@spec difference(t, t) :: t
def difference(set1, set2) do
target1 = target(set1)
target2 = target(set2)
@@ -97,29 +46,12 @@ defmodule Set do
if target1 == target2 do
target1.difference(set1, set2)
else
target2.reduce(set2, {:cont, set1}, fn v, acc ->
Enumerable.reduce(set2, {:cont, set1}, fn v, acc ->
{:cont, target1.delete(acc, v)}
end) |> elem(1)
end
end
@doc """
Checks if `set1` and `set2` have no members in common.
Note that this function is polymorphic as it checks for disjoint sets of
any type. Each set implementation also provides a `disjoint?` function,
but that function can only work with sets of the same type.
## Examples
iex> Set.disjoint?(Enum.into([1, 2], set_impl.new), Enum.into([3, 4], set_impl.new))
true
iex> Set.disjoint?(Enum.into([1, 2], set_impl.new), Enum.into([2, 3], set_impl.new))
false
"""
@spec disjoint?(t, t) :: boolean
def disjoint?(set1, set2) do
target1 = target(set1)
target2 = target(set2)
@@ -127,7 +59,7 @@ defmodule Set do
if target1 == target2 do
target1.disjoint?(set1, set2)
else
target2.reduce(set2, {:cont, true}, fn member, acc ->
Enumerable.reduce(set2, {:cont, true}, fn member, acc ->
case target1.member?(set1, member) do
false -> {:cont, acc}
_ -> {:halt, false}
@@ -137,28 +69,10 @@ defmodule Set do
end
@doc false
@spec empty(t) :: t
def empty(set) do
target(set).empty(set)
end
@doc """
Checks if two sets are equal using `===`.
Note that this function is polymorphic as it compares sets of
any type. Each set implementation also provides an `equal?`
function, but that function can only work with sets of the same type.
## Examples
iex> Set.equal?(Enum.into([1, 2], set_impl.new), Enum.into([2, 1, 1], set_impl.new))
true
iex> Set.equal?(Enum.into([1, 2], set_impl.new), Enum.into([3, 4], set_impl.new))
false
"""
@spec equal?(t, t) :: boolean
def equal?(set1, set2) do
target1 = target(set1)
target2 = target(set2)
@@ -175,23 +89,7 @@ defmodule Set do
end
end
@doc """
Returns a set containing only members that `set1` and `set2` have in common.
Note that this function is polymorphic as it calculates the intersection of
any type. Each set implementation also provides an `intersection` function,
but that function can only work with sets of the same type.
## Examples
iex> Set.intersection(Enum.into([1, 2], set_impl.new), Enum.into([2, 3, 4], set_impl.new)) |> Enum.sort
[2]
iex> Set.intersection(Enum.into([1, 2], set_impl.new), Enum.into([3, 4], set_impl.new)) |> Enum.sort
[]
"""
@spec intersection(t, t) :: t
def intersection(set1, set2) do
target1 = target(set1)
target2 = target(set2)
@@ -199,79 +97,26 @@ defmodule Set do
if target1 == target2 do
target1.intersection(set1, set2)
else
target1.reduce(set1, {:cont, target1.new}, fn v, acc ->
Enumerable.reduce(set1, {:cont, target1.new}, fn v, acc ->
{:cont, if(target2.member?(set2, v), do: target1.put(acc, v), else: acc)}
end) |> elem(1)
end
end
@doc """
Checks if `set` contains `value`.
## Examples
iex> Set.member?(Enum.into([1, 2, 3], set_impl.new), 2)
true
iex> Set.member?(Enum.into([1, 2, 3], set_impl.new), 4)
false
"""
@spec member?(t, value) :: boolean
def member?(set, value) do
target(set).member?(set, value)
end
@doc """
Inserts `value` into `set` if `set` doesn't already contain it.
## Examples
iex> Set.put(Enum.into([1, 2, 3], set_impl.new), 3) |> Enum.sort
[1, 2, 3]
iex> Set.put(Enum.into([1, 2, 3], set_impl.new), 4) |> Enum.sort
[1, 2, 3, 4]
"""
@spec put(t, value) :: t
def put(set, value) do
target(set).put(set, value)
end
@doc """
Returns the number of elements in `set`.
## Examples
iex> Set.size(Enum.into([1, 2, 3], set_impl.new))
3
"""
@spec size(t) :: non_neg_integer
def size(set) do
target(set).size(set)
end
@doc """
Checks if `set1`'s members are all contained in `set2`.
This function checks if `set1` is a subset of `set2`.
Note that this function is polymorphic as it checks the subset for
any type. Each set implementation also provides a `subset?` function,
but that function can only work with sets of the same type.
## Examples
iex> Set.subset?(Enum.into([1, 2], set_impl.new), Enum.into([1, 2, 3], set_impl.new))
true
iex> Set.subset?(Enum.into([1, 2, 3], set_impl.new), Enum.into([1, 2], set_impl.new))
false
"""
@spec subset?(t, t) :: boolean
def subset?(set1, set2) do
target1 = target(set1)
target2 = target(set2)
@@ -283,34 +128,10 @@ defmodule Set do
end
end
@doc """
Converts `set` to a list.
## Examples
iex> set_impl.to_list(Enum.into([1, 2, 3], set_impl.new)) |> Enum.sort
[1, 2, 3]
"""
@spec to_list(t) :: list
def to_list(set) do
target(set).to_list(set)
end
@doc """
Returns a set containing all members of `set1` and `set2`.
Note that this function is polymorphic as it calculates the union of sets of
any type. Each set implementation also provides a `union` function,
but that function can only work with sets of the same type.
## Examples
iex> Set.union(Enum.into([1, 2], set_impl.new), Enum.into([2, 3, 4], set_impl.new)) |> Enum.sort
[1, 2, 3, 4]
"""
@spec union(t, t) :: t
def union(set1, set2) do
target1 = target(set1)
target2 = target(set2)
@@ -318,14 +139,14 @@ defmodule Set do
if target1 == target2 do
target1.union(set1, set2)
else
target2.reduce(set2, {:cont, set1}, fn v, acc ->
Enumerable.reduce(set2, {:cont, set1}, fn v, acc ->
{:cont, target1.put(acc, v)}
end) |> elem(1)
end
end
defp do_subset?(target1, target2, set1, set2) do
target1.reduce(set1, {:cont, true}, fn member, acc ->
defp do_subset?(_target1, target2, set1, set2) do
Enumerable.reduce(set1, {:cont, true}, fn member, acc ->
case target2.member?(set2, member) do
true -> {:cont, acc}
_ -> {:halt, false}
+26 -19
View File
@@ -22,7 +22,7 @@ defmodule Stream do
Notice we started with a range and then we created a stream that is
meant to multiply each item in the range by 2. At this point, no
computation was done yet. Just when `Enum.map/2` is called we
computation was done. Only when `Enum.map/2` is called we actually
enumerate over each item in the range, multiplying it by 2 and adding 1.
We say the functions in `Stream` are *lazy* and the functions in `Enum`
are *eager*.
@@ -33,10 +33,10 @@ defmodule Stream do
computations that are executed at a later moment. Let's see another
example:
1..3 |>
Enum.map(&IO.inspect(&1)) |>
Enum.map(&(&1 * 2)) |>
Enum.map(&IO.inspect(&1))
1..3
|> Enum.map(&IO.inspect(&1))
|> Enum.map(&(&1 * 2))
|> Enum.map(&IO.inspect(&1))
1
2
3
@@ -49,10 +49,10 @@ defmodule Stream do
element by 2 and finally printed each new value. In this example, the list
was enumerated 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 = 1..3
|> Stream.map(&IO.inspect(&1))
|> Stream.map(&(&1 * 2))
|> Stream.map(&IO.inspect(&1))
Enum.to_list(stream)
1
2
@@ -66,10 +66,10 @@ defmodule Stream do
printed changed! With streams, we print the first item and then print
its double. In this example, the list was enumerated just once!
That's what we meant when we first said that streams are composable,
That's what we meant when we said earlier that streams are composable,
lazy enumerables. Notice we could call `Stream.map/2` multiple times,
effectively composing the streams and they are lazy. The computations
are performed only when you call a function from the `Enum` module.
effectively composing the streams and keeping them lazy. The computations
are only performed when you call a function from the `Enum` module.
## Creating Streams
@@ -85,7 +85,7 @@ defmodule Stream do
Note the functions in this module are guaranteed to return enumerables.
Since enumerables can have different shapes (structs, anonymous functions,
and so on), the functions in this module may return any of those shapes
and that it may change at any time. For example, a function that today
and that this may change at any time. For example, a function that today
returns an anonymous function may return a struct in future releases.
"""
@@ -96,7 +96,7 @@ defmodule Stream do
@type element :: any
@type index :: non_neg_integer
@type default :: any
@type t :: %__MODULE__{}
@opaque t :: %__MODULE__{}
# Require Stream.Reducers and its callbacks
require Stream.Reducers, as: R
@@ -807,7 +807,7 @@ defmodule Stream do
@spec uniq(Enumerable.t) :: Enumerable.t
@spec uniq(Enumerable.t, (element -> term)) :: Enumerable.t
def uniq(enum, fun \\ fn x -> x end) do
lazy enum, HashSet.new, fn f1 -> R.uniq(fun, f1) end
lazy enum, %{}, fn f1 -> R.uniq(fun, f1) end
end
@doc """
@@ -820,10 +820,15 @@ defmodule Stream do
iex> Enum.to_list(stream)
[{1, 0}, {2, 1}, {3, 2}]
iex> stream = Stream.with_index([1, 2, 3], 3)
iex> Enum.to_list(stream)
[{1, 3}, {2, 4}, {3, 5}]
"""
@spec with_index(Enumerable.t) :: Enumerable.t
def with_index(enum) do
lazy enum, 0, fn(f1) -> R.with_index(f1) end
@spec with_index(Enumerable.t, integer) :: Enumerable.t
def with_index(enum, offset \\ 0) do
lazy enum, offset, fn(f1) -> R.with_index(f1) end
end
## Combiners
@@ -1023,8 +1028,10 @@ defmodule Stream do
## Examples
iex> Stream.repeatedly(&:random.uniform/0) |> Enum.take(3)
[0.4435846174457203, 0.7230402056221108, 0.94581636451987]
# Although not necessary, let's seed the random algorithm
iex> :rand.seed(:exsplus, {1, 2, 3})
iex> Stream.repeatedly(&:rand.uniform/0) |> Enum.take(3)
[0.40502929729990744, 0.45336720247823126, 0.04094511692041057]
"""
@spec repeatedly((() -> element)) :: Enumerable.t
+2 -2
View File
@@ -187,10 +187,10 @@ defmodule Stream.Reducers do
quote do
fn(entry, acc(h, prev, t) = acc) ->
value = unquote(callback).(entry)
if HashSet.member?(prev, value) do
if Map.has_key?(prev, value) do
skip(acc)
else
next_with_acc(unquote(f), entry, h, HashSet.put(prev, value), t)
next_with_acc(unquote(f), entry, h, Map.put(prev, value, true), t)
end
end
end
+283 -67
View File
@@ -7,10 +7,12 @@ defmodule String do
## Codepoints and graphemes
The functions in this module act according to the Unicode
Standard, version 6.3.0. As per the standard, a codepoint is
a Unicode Character, which may be represented by one or more
bytes. For example, the character "é" is represented with two
bytes:
Standard, version 6.3.0.
As per the standard, a codepoint is a single Unicode Character,
which may be represented by one or more bytes.
For example, the codepoint "é" is two bytes:
iex> byte_size("é")
2
@@ -21,16 +23,21 @@ defmodule String do
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,
the same "é" character written above could be represented
by the letter "e" followed by the accent ́:
graphemes. A single grapheme can consist of multiple codepoints
that may be perceived as a single character by readers. For example,
the "é" grapheme can be represented either as a single "e with acute"
codepoint (like above), or as the letter "e" followed by a
"combining acute accent" (two codepoints):
iex> string = "\u0065\u0301"
iex> byte_size(string)
3
iex> String.length(string)
1
iex> String.codepoints(string)
["e", "́"]
iex> String.graphemes(string)
["é"]
Although the example above is made of two characters, it is
perceived by users as one.
@@ -46,16 +53,16 @@ defmodule String do
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
The current Elixir version implements Extended Grapheme Cluster
algorithm.
## String and binary operations
To act accordingly to the Unicode Standard, many functions
in this module runs in linear time, as it needs to traverse
To act according to the Unicode Standard, many functions
in this module run in linear time, as they need to traverse
the whole string considering the proper Unicode codepoints.
For example, `String.length/1` is going to take longer as
For example, `String.length/1` will take longer as
the input grows. On the other hand, `Kernel.byte_size/1` always runs
in constant time (i.e. regardless of the input size).
@@ -88,7 +95,7 @@ defmodule String do
fully, so we traverse both `prefix` and `full` strings, then
slice the `full` one, traversing it again.
A first attempting at improving it could be with ranges:
A first attempt at improving it could be with ranges:
iex> take_prefix = fn full, prefix ->
...> base = String.length(prefix)
@@ -114,7 +121,7 @@ defmodule String do
iex> take_prefix = fn full, prefix ->
...> base = byte_size(prefix)
...> <<_ :: binary-size(base), rest :: binary>> = full
...> <<_::binary-size(base), rest::binary>> = full
...> rest
...> end
iex> take_prefix.("Mr. John", "Mr. ")
@@ -123,7 +130,7 @@ defmodule String do
On the other hand, if you want to dynamically slice a string
based on an integer value, then using `String.slice/3` is the
best option as it guarantees we won't incorrectly split a valid
codepoint in multiple bytes.
codepoint into multiple bytes.
## Integer codepoints
@@ -144,7 +151,7 @@ defmodule String do
Or also via pattern matching:
iex> << eacute :: utf8 >> = "á"
iex> <<eacute::utf8>> = "á"
iex> eacute
225
@@ -162,12 +169,12 @@ defmodule String do
codepoint needs to be rejected.
This module relies on this behaviour to ignore such invalid
characters. For example, `length/1` is going to return
characters. For example, `length/1` will return
a correct result even if an invalid codepoint is fed into it.
In other words, this module expects invalid data to be detected
when retrieving data from the external source. For example, a
driver that reads strings from a database will be the one
driver that reads strings from a database will be
responsible to check the validity of the encoding.
## Patterns
@@ -211,7 +218,7 @@ defmodule String do
@spec printable?(t) :: boolean
def printable?(string)
def printable?(<< h :: utf8, t :: binary >>)
def printable?(<<h::utf8, t::binary >>)
when h in 0x20..0x7E
when h in 0xA0..0xD7FF
when h in 0xE000..0xFFFD
@@ -219,15 +226,15 @@ defmodule String do
printable?(t)
end
def printable?(<<?\n, t :: binary>>), do: printable?(t)
def printable?(<<?\r, t :: binary>>), do: printable?(t)
def printable?(<<?\t, t :: binary>>), do: printable?(t)
def printable?(<<?\v, t :: binary>>), do: printable?(t)
def printable?(<<?\b, t :: binary>>), do: printable?(t)
def printable?(<<?\f, t :: binary>>), do: printable?(t)
def printable?(<<?\e, t :: binary>>), do: printable?(t)
def printable?(<<?\d, t :: binary>>), do: printable?(t)
def printable?(<<?\a, t :: binary>>), do: printable?(t)
def printable?(<<?\n, t::binary>>), do: printable?(t)
def printable?(<<?\r, t::binary>>), do: printable?(t)
def printable?(<<?\t, t::binary>>), do: printable?(t)
def printable?(<<?\v, t::binary>>), do: printable?(t)
def printable?(<<?\b, t::binary>>), do: printable?(t)
def printable?(<<?\f, t::binary>>), do: printable?(t)
def printable?(<<?\e, t::binary>>), do: printable?(t)
def printable?(<<?\d, t::binary>>), do: printable?(t)
def printable?(<<?\a, t::binary>>), do: printable?(t)
def printable?(<<>>), do: true
def printable?(binary) when is_binary(binary), do: false
@@ -294,7 +301,7 @@ defmodule String do
iex> String.split(" a b c ", ~r{\s}, trim: true)
["a", "b", "c"]
Splitting on empty patterns returns codepoints:
Splitting on empty patterns returns graphemes:
iex> String.split("abc", ~r{})
["a", "b", "c", ""]
@@ -319,15 +326,15 @@ defmodule String do
@spec split(t, pattern | Regex.t, Keyword.t) :: [t]
def split(string, pattern, options \\ [])
def split(string, %Regex{} = pattern, options) do
def split(string, %Regex{} = pattern, options) when is_binary(string) do
Regex.split(pattern, string, options)
end
def split(string, pattern, []) when pattern != "" do
def split(string, pattern, []) when is_binary(string) and pattern != "" do
:binary.split(string, pattern, [:global])
end
def split(string, pattern, options) do
def split(string, pattern, options) when is_binary(string) do
parts = Keyword.get(options, :parts, :infinity)
trim = Keyword.get(options, :trim, false)
pattern = maybe_compile_pattern(pattern)
@@ -441,6 +448,66 @@ defmodule String do
{binary_part(string, 0, byte_size), rest || ""}
end
@doc ~S"""
Returns `true` if `binary` is canonically equivalent to 'another_binary'.
It performs Normalization Form Canonical Decomposition (NFD) on the
strings before comparing them. This function is equivalent to:
String.normalize(left, :nfd) == String.normalize(right, :nfd)
Therefore, if you plan to compare multiple strings, multiple times
in a row, you may normalize them upfront and compare them directly
to avoid multiple normalization passes.
## Examples
iex> String.equivalent?("abc", "abc")
true
iex> String.equivalent?("man\u0303ana", "mañana")
true
iex> String.equivalent?("abc", "ABC")
false
iex> String.equivalent?("nø", "nó")
false
"""
@spec equivalent?(t, t) :: boolean
def equivalent?(left, right) do
normalize(left, :nfd) == normalize(right, :nfd)
end
@doc """
Converts all characters in `binary` to Unicode normalization
form identified by `form`.
## Forms
The supported forms are:
* `:nfd` - Normalization Form Canonical Decomposition.
Characters are decomposed by canonical equivalence, and
multiple combining characters are arranged in a specific
order.
* `:nfc` - Normalization Form Canonical Composition.
Characters are decomposed and then recomposed by canonical equivalence.
## Examples
iex> String.normalize("yêṩ", :nfd)
"yêṩ"
iex> String.normalize("leña", :nfc)
"leña"
"""
@spec normalize(t, atom) :: boolean
defdelegate normalize(binary, form), to: String.Normalizer
@doc """
Converts all characters in the given string to uppercase.
@@ -527,39 +594,159 @@ defmodule String do
"""
@spec rstrip(t, char) :: t
def rstrip(string, char) when is_integer(char) do
replace_trailing(string, <<char::utf8>>, "")
end
def rstrip("", _char), do: ""
@doc """
Replaces all leading occurences of `match` by `replacement` of `match` in `string`.
# Do a quick check before we traverse the whole
# binary. :binary.last is a fast operation (it
# does not traverse the whole binary).
def rstrip(string, char) when char in 0..127 do
if :binary.last(string) == char do
rstrip(binary_part(string, 0, byte_size(string) - 1), char)
else
string
Returns the string untouched if there are no occurrences.
## Examples
iex> String.replace_leading("hello world", "hello ", "")
"world"
iex> String.replace_leading("hello hello world", "hello ", "")
"world"
iex> String.replace_leading("hello world", "hello ", "ola ")
"ola world"
iex> String.replace_leading("hello hello world", "hello ", "ola ")
"ola ola world"
"""
def replace_leading(string, match, replacement)
when is_binary(string) and is_binary(match) and is_binary(replacement) do
prefix_size = byte_size(match)
suffix_size = byte_size(string) - prefix_size
replace_leading(string, match, replacement, prefix_size, suffix_size, "")
end
defp replace_leading(string, match, replacement, prefix_size, suffix_size, acc) when suffix_size > 0 do
case string do
<<prefix::size(prefix_size)-binary, suffix::size(suffix_size)-binary>> when prefix == match ->
replace_leading(suffix, match, replacement, prefix_size, suffix_size - prefix_size, acc <> replacement)
_ ->
string
end
end
def rstrip(string, char) when is_integer(char) do
do_rstrip(string, "", char)
defp replace_leading(string, _match, _replacement, _prefix_size, _suffix_size, prefix) do
prefix <> string
end
defp do_rstrip(<<char :: utf8, string :: binary>>, buffer, char) do
<<do_rstrip(string, <<char :: utf8, buffer :: binary>>, char) :: binary>>
@doc """
Replaces all trailing occurences of `match` by `replacement` in `string`.
Returns the string untouched if there are no occurrences.
## Examples
iex> String.replace_trailing("hello world", " world", "")
"hello"
iex> String.replace_trailing("hello world world", " world", "")
"hello"
iex> String.replace_trailing("hello world", " world", " mundo")
"hello mundo"
iex> String.replace_trailing("hello world world", " world", " mundo")
"hello mundo mundo"
"""
def replace_trailing(string, match, replacement)
when is_binary(string) and is_binary(match) and is_binary(replacement) do
suffix_size = byte_size(match)
prefix_size = byte_size(string) - suffix_size
replace_trailing(string, match, replacement, prefix_size, suffix_size, "")
end
defp do_rstrip(<<char :: utf8, string :: binary>>, buffer, another_char) do
<<buffer :: binary, char :: utf8, do_rstrip(string, "", another_char) :: binary>>
defp replace_trailing(string, match, replacement, prefix_size, suffix_size, acc) when prefix_size > 0 do
case string do
<<prefix::size(prefix_size)-binary, suffix::size(suffix_size)-binary>> when suffix == match ->
replace_trailing(prefix, match, replacement, prefix_size - suffix_size, suffix_size, acc <> replacement)
_ ->
string
end
end
defp do_rstrip(<<>>, _, _) do
<<>>
defp replace_trailing(string, _match, _replacement, _prefix_size, _suffix_size, suffix) do
string <> suffix
end
@doc """
Replaces prefix in `string` by `replacement` if it matches `match`.
Returns the string untouched if there is no match.
## Examples
iex> String.replace_prefix("world", "hello ", "")
"world"
iex> String.replace_prefix("hello world", "hello ", "")
"world"
iex> String.replace_prefix("hello hello world", "hello ", "")
"hello world"
iex> String.replace_prefix("world", "hello ", "ola ")
"world"
iex> String.replace_prefix("hello world", "hello ", "ola ")
"ola world"
iex> String.replace_prefix("hello hello world", "hello ", "ola ")
"ola hello world"
"""
def replace_prefix(string, match, replacement)
when is_binary(string) and is_binary(match) and is_binary(replacement) do
prefix_size = byte_size(match)
suffix_size = byte_size(string) - prefix_size
case string do
<<prefix::size(prefix_size)-binary, suffix::size(suffix_size)-binary>> when prefix == match ->
replacement <> suffix
_ ->
string
end
end
@doc """
Replaces suffix in `string` by `replacement` if it matches `match`.
Returns the string untouched if there is no match.
## Examples
iex> String.replace_suffix("hello", " world", "")
"hello"
iex> String.replace_suffix("hello world", " world", "")
"hello"
iex> String.replace_suffix("hello world world", " world", "")
"hello world"
iex> String.replace_suffix("hello", " world", " mundo")
"hello"
iex> String.replace_suffix("hello world", " world", " mundo")
"hello mundo"
iex> String.replace_suffix("hello world world", " world", " mundo")
"hello world mundo"
"""
def replace_suffix(string, match, replacement)
when is_binary(string) and is_binary(match) and is_binary(replacement) do
suffix_size = byte_size(match)
prefix_size = byte_size(string) - suffix_size
case string do
<<prefix::size(prefix_size)-binary, suffix::size(suffix_size)-binary>> when suffix == match ->
prefix <> replacement
_ ->
string
end
end
@doc """
Returns a string where all leading Unicode whitespaces
has been removed.
have been removed.
## Examples
@@ -578,12 +765,11 @@ defmodule String do
" abc _"
"""
@spec lstrip(t, char) :: t
def lstrip(string, char)
def lstrip(<<char :: utf8, rest :: binary>>, char) when is_integer(char) do
<<lstrip(rest, char) :: binary>>
def lstrip(<<char::utf8, rest::binary>>, char) when is_integer(char) do
<<lstrip(rest, char)::binary>>
end
def lstrip(string, char) when is_integer(char) do
@@ -592,7 +778,7 @@ defmodule String do
@doc """
Returns a string where all leading and trailing Unicode whitespaces
has been removed.
have been removed.
## Examples
@@ -617,7 +803,6 @@ defmodule String do
"""
@spec strip(t, char) :: t
def strip(string, char) do
rstrip(lstrip(string, char), char)
end
@@ -673,7 +858,7 @@ defmodule String do
subject_len >= len ->
subject
subject_len < len ->
fill = duplicate(<<padding :: utf8>>, len - subject_len)
fill = duplicate(<<padding::utf8>>, len - subject_len)
case type do
:left -> subject <> fill
@@ -683,11 +868,11 @@ defmodule String do
end
@doc ~S"""
Returns a new binary created by replacing occurences of `pattern` in
Returns a new string created by replacing occurences of `pattern` in
`subject` with `replacement`.
By default, it replaces all occurences, unless the `global` option is
set to `false`.
set to `false`, where it will only replace the first one
The `pattern` may be a string or a regular expression.
@@ -746,8 +931,8 @@ defmodule String do
opts
end
@doc """
Reverses the given string. Works on graphemes.
@doc ~S"""
Reverses the graphemes in given string.
## Examples
@@ -760,6 +945,21 @@ defmodule String do
iex> String.reverse("hello ∂og")
"go∂ olleh"
Keep in mind reversing the same string twice does
not necessarily yield the original string:
iex> "̀e"
"̀e"
iex> String.reverse("̀e")
"è"
iex> String.reverse String.reverse("̀e")
"è"
In the first example the accent is before the vowel, so
it is considered two graphemes. However, when you reverse
it once, you have the vowel followed by the accent, which
becomes one grapheme. Reversing it again will keep it as
one single grapheme.
"""
@spec reverse(t) :: t
def reverse(string) do
@@ -795,6 +995,8 @@ defmodule String do
@doc """
Returns all codepoints in the string.
For details about codepoints and graphemes, see the `String` module documentation.
## Examples
iex> String.codepoints("olá")
@@ -806,6 +1008,12 @@ defmodule String do
iex> String.codepoints("ἅἪῼ")
["ἅ", "Ἢ", "ῼ"]
iex> String.codepoints("\u00e9")
["é"]
iex> String.codepoints("\u0065\u0301")
["e", "́"]
"""
@spec codepoints(t) :: [codepoint]
defdelegate codepoints(string), to: String.Unicode
@@ -860,10 +1068,10 @@ defmodule String do
0x9FFFE, 0x9FFFF, 0x10FFFE, 0x10FFFF]
for noncharacter <- noncharacters do
def valid?(<< unquote(noncharacter) :: utf8, _ :: binary >>), do: false
def valid?(<<unquote(noncharacter)::utf8, _::binary >>), do: false
end
def valid?(<<_ :: utf8, t :: binary>>), do: valid?(t)
def valid?(<<_::utf8, t::binary>>), do: valid?(t)
def valid?(<<>>), do: true
def valid?(_), do: false
@@ -890,7 +1098,7 @@ defmodule String do
"""
@spec valid_character?(t) :: boolean
def valid_character?(<<_ :: utf8>> = codepoint), do: valid?(codepoint)
def valid_character?(<<_::utf8>> = codepoint), do: valid?(codepoint)
def valid_character?(_), do: false
@doc ~S"""
@@ -956,11 +1164,19 @@ defmodule String do
Cluster algorithm outlined in the [Unicode Standard Annex #29,
Unicode Text Segmentation](http://www.unicode.org/reports/tr29/).
For details about codepoints and graphemes, see the `String` module documentation.
## Examples
iex> String.graphemes("Ńaïve")
["Ń", "a", "ï", "v", "e"]
iex> String.graphemes("\u00e9")
["é"]
iex> String.graphemes("\u0065\u0301")
["é"]
"""
@spec graphemes(t) :: [grapheme]
defdelegate graphemes(string), to: String.Graphemes
@@ -1065,7 +1281,7 @@ defmodule String do
defdelegate length(string), to: String.Graphemes
@doc """
Returns the grapheme in the `position` of the given utf8 `string`.
Returns the grapheme at the `position` of the given utf8 `string`.
If `position` is greater than `string` length, then it returns `nil`.
## Examples
@@ -1113,8 +1329,8 @@ defmodule String do
If the offset is greater than string length, then it returns `""`.
Remember this function works with Unicode codepoints and considers
the slices to represent codepoint offsets. If you want to split
Remember this function works with Unicode graphemes and considers
the slices to represent grapheme offsets. If you want to split
on raw bytes, check `Kernel.binary_part/3` instead.
## Examples
@@ -1536,7 +1752,7 @@ defmodule String do
"""
@spec jaro_distance(t, t) :: 0..1
@spec jaro_distance(t, t) :: float
def jaro_distance(string1, string2)
def jaro_distance(string, string), do: 1.0
+2 -2
View File
@@ -2,14 +2,14 @@ import Kernel, except: [to_string: 1]
defprotocol String.Chars do
@moduledoc ~S"""
The String.Chars protocol is responsible for
The `String.Chars` protocol is responsible for
converting a structure to a Binary (only if applicable).
The only function required to be implemented is
`to_string` which does the conversion.
The `to_string` function automatically imported
by Kernel invokes this protocol. String
interpolation also invokes to_string in its
interpolation also invokes `to_string` in its
arguments. For example, `"foo#{bar}"` is the same
as `"foo" <> to_string(bar)`.
"""
+7 -7
View File
@@ -128,12 +128,12 @@ defmodule StringIO do
end
defp io_request({:put_chars, chars}, %{output: output} = s) do
{:ok, %{s | output: << output :: binary, IO.chardata_to_string(chars) :: binary >>}}
{:ok, %{s | output: <<output::binary, IO.chardata_to_string(chars)::binary>>}}
end
defp io_request({:put_chars, m, f, as}, %{output: output} = s) do
chars = apply(m, f, as)
{:ok, %{s | output: << output :: binary, IO.chardata_to_string(chars) :: binary >>}}
{:ok, %{s | output: <<output::binary, IO.chardata_to_string(chars)::binary>>}}
end
defp io_request({:put_chars, _encoding, chars}, s) do
@@ -205,7 +205,7 @@ defmodule StringIO do
{error, s}
{result, input} ->
if capture_prompt do
output = << output :: binary, IO.chardata_to_string(prompt) :: binary >>
output = <<output::binary, IO.chardata_to_string(prompt)::binary>>
end
{result, %{s | input: input, output: output}}
@@ -221,7 +221,7 @@ defmodule StringIO do
end
defp do_get_chars(input, :latin1, n) do
<<chars :: binary-size(n), rest :: binary>> = input
<<chars::binary-size(n), rest::binary>> = input
{chars, rest}
end
@@ -231,7 +231,7 @@ defmodule StringIO do
{buf_count, split_pos} when buf_count < n or split_pos == :none ->
{input, ""}
{_buf_count, split_pos} ->
<<chars :: binary-size(split_pos), rest :: binary>> = input
<<chars::binary-size(split_pos), rest::binary>> = input
{chars, rest}
end
catch
@@ -253,7 +253,7 @@ defmodule StringIO do
{result, input} = do_get_line(chars, encoding)
if capture_prompt do
output = << output :: binary, IO.chardata_to_string(prompt) :: binary >>
output = <<output::binary, IO.chardata_to_string(prompt)::binary>>
end
{result, %{s | input: input, output: output}}
@@ -283,7 +283,7 @@ defmodule StringIO do
{result, input, count} = do_get_until(chars, encoding, mod, fun, args)
if capture_prompt do
output = << output :: binary, :binary.copy(IO.chardata_to_string(prompt), count) :: binary >>
output = <<output::binary, :binary.copy(IO.chardata_to_string(prompt), count)::binary>>
end
input =
+29 -13
View File
@@ -1,8 +1,8 @@
defmodule Supervisor do
@moduledoc """
@moduledoc ~S"""
A behaviour module for implementing supervision functionality.
A supervisor is a process which supervises other processes called
A supervisor is a process which supervises other processes, called
child processes. Supervisors are used to build a hierarchical process
structure called a supervision tree, a nice way to structure fault-tolerant
applications.
@@ -80,7 +80,7 @@ defmodule Supervisor do
configuration, shutdown values, and restart strategies.
Continue reading this moduledoc to learn more about supervision strategies
and then follow to the `Supervisor.Spec` module documentation to learn
and then proceed to the `Supervisor.Spec` module documentation to learn
about the specification for workers and supervisors.
## Module-based supervisors
@@ -107,13 +107,13 @@ defmodule Supervisor do
You may want to use a module-based supervisor if:
* You need to do some particular action on supervisor
* You need to perform some particular action on supervisor
initialization, like setting up an ETS table.
* You want to perform partial hot-code swapping of the
tree. For example, if you add or remove children,
the module-based supervision will add and remove the
new children directly, while the dynamic supervision
new children directly, while dynamic supervision
requires the whole tree to be restarted in order to
perform such swaps.
@@ -161,7 +161,7 @@ defmodule Supervisor do
* The simple one for one specification can define only one child which
works as a template for when we call `start_child/2`
* We have define the child to have restart strategy of transient. This
* We have defined the child to have a restart strategy of transient. This
means that, if the child process exits due to a `:normal`, `:shutdown`
or `{:shutdown, term}` reason, it won't be restarted. This is useful
as it allows our workers to politely shutdown and be removed from the
@@ -187,18 +187,18 @@ defmodule Supervisor do
strategy for the worker does not restart the child in case it crashes with
reason `:normal`, `:shutdown` or `{:shutdown, term}`.
So one may ask: which exit reason should I choose when existing my worker?
So one may ask: which exit reason should I choose when exiting my worker?
There are three options:
* `:normal` - on such cases, the exit won't be logged, there is no restart
on transient mode and linked processes do not exit
* `:normal` - in such cases, the exit won't be logged, there is no restart
in transient mode and linked processes do not exit
* `:shutdown` or `{:shutdown, term}` - on such cases, the exit won't be
logged, there is no restart on transient mode and linked processes exit
* `:shutdown` or `{:shutdown, term}` - in such cases, the exit won't be
logged, there is no restart in transient mode and linked processes exit
with the same reason unless trapping exits
* any other term - on such cases, the exit will be logged, there are
restarts on transient mode and linked processes exit with the same reason
* any other term - in such cases, the exit will be logged, there are
restarts in transient mode and linked processes exit with the same reason
unless trapping exits
## Name Registration
@@ -463,6 +463,22 @@ defmodule Supervisor do
call(supervisor, :count_children) |> :maps.from_list
end
@doc """
Stops the supervisor with the given `reason`.
It returns `:ok` if the supervisor terminates with the given
reason, if it terminates with another reason, the call will
exit.
This function keeps OTP semantics regarding error reporting.
If the reason is any other than `:normal`, `:shutdown` or
`{:shutdown, _}`, an error report will be logged.
"""
@spec stop(supervisor, reason :: term, timeout) :: :ok
def stop(supervisor, reason \\ :normal, timeout \\ :infinity) do
:gen.stop(supervisor, reason, timeout)
end
@compile {:inline, call: 2}
defp call(supervisor, req) do
+4 -4
View File
@@ -52,7 +52,7 @@ defmodule Supervisor.Spec do
## Supervisor and worker options
In the example above, we have defined workers and supervisors
In the example above, we defined workers and supervisors
and each accepts the following options:
* `:id` - a name used to identify the child specification
@@ -61,7 +61,7 @@ defmodule Supervisor.Spec do
* `:function` - the function to invoke on the child to start it
* `:restart` - defines when the child process should restart
* `:restart` - defines when a terminated child process should be restarted
* `:shutdown` - defines how a child process should be terminated
@@ -90,11 +90,11 @@ defmodule Supervisor.Spec do
* `:brutal_kill` - the child process is unconditionally terminated
using `exit(child, :kill)`.
* `:infinity` - if the child process is a supervisor, it is a mechanism
* `:infinity` - if the child process is a supervisor, this is a mechanism
to give the subtree enough time to shutdown. It can also be used with
workers with care.
* Finally, it can also be any integer meaning that the supervisor tells
* Finally, the value can also be any integer meaning that the supervisor tells
the child process to terminate by calling `Process.exit(child, :shutdown)`
and then waits for an exit signal back. If no exit signal is received
within the specified time (in milliseconds), the child process is
+171 -13
View File
@@ -25,15 +25,15 @@ defmodule System do
end
end
# Tries to run "git describe --always --tags". In the case of success returns
# the most recent tag. If that is not available, tries to read the commit hash
# Tries to run "git rev-parse --short HEAD". In the case of success returns
# the short revision hash. If that is not available, tries to read the commit hash
# from .git/HEAD. If that fails, returns an empty string.
defmacrop get_describe do
defmacrop get_revision do
dirpath = :filename.join(__DIR__, "../../../.git")
case :file.read_file_info(dirpath) do
{:ok, _} ->
if :os.find_executable('git') do
data = :os.cmd('git describe --always --tags')
data = :os.cmd('git rev-parse --short HEAD')
strip_re(data, "\n")
else
read_stripped(:filename.join(".git", "HEAD"))
@@ -47,6 +47,21 @@ defmodule System do
IO.iodata_to_binary :httpd_util.rfc1123_date
end
@doc """
Returns the endianness.
"""
def endianness do
:erlang.system_info(:endian)
end
@doc """
Returns the endianness the system was compiled with.
"""
@endianness :erlang.system_info(:endian)
def compiled_endianness do
@endianness
end
@doc """
Elixir version information.
@@ -58,11 +73,11 @@ defmodule System do
@doc """
Elixir build information.
Returns a keyword list with Elixir version, git tag info and compilation date.
Returns a keyword list with Elixir version, git short revision hash and compilation date.
"""
@spec build_info() :: map
def build_info do
%{version: version, tag: get_describe, date: get_date}
%{version: version, date: get_date, revision: get_revision}
end
@doc """
@@ -284,9 +299,9 @@ defmodule System do
Sets a new value for each environment variable corresponding
to each key in `dict`.
"""
@spec put_env(Dict.t) :: :ok
def put_env(dict) do
Enum.each dict, fn {key, val} -> put_env key, val end
@spec put_env(Enumerable.t) :: :ok
def put_env(enum) do
Enum.each enum, fn {key, val} -> put_env key, val end
end
@doc """
@@ -367,13 +382,13 @@ defmodule System do
and the command exit status.
## Examples
iex> System.cmd "echo", ["hello"]
{"hello\n", 0}
iex> System.cmd "echo", ["hello"], env: [{"MIX_ENV", "test"}]
{"hello\n", 0}
iex> System.cmd "echo", ["hello"], into: IO.stream(:stdio, :line)
hello
{%IO.Stream{}, 0}
@@ -437,7 +452,16 @@ defmodule System do
{into, opts} = cmd_opts(opts, [:use_stdio, :exit_status, :binary, :hide, args: args], "")
{initial, fun} = Collectable.into(into)
do_cmd Port.open({:spawn_executable, cmd}, opts), initial, fun
try do
do_cmd Port.open({:spawn_executable, cmd}, opts), initial, fun
catch
kind, reason ->
stacktrace = System.stacktrace
fun.(initial, :halt)
:erlang.raise(kind, reason, stacktrace)
else
{acc, status} -> {fun.(acc, :done), status}
end
end
defp do_cmd(port, acc, fun) do
@@ -445,7 +469,7 @@ defmodule System do
{^port, {:data, data}} ->
do_cmd(port, fun.(acc, {:cont, data}), fun)
{^port, {:exit_status, status}} ->
{fun.(acc, :done), status}
{acc, status}
end
end
@@ -478,10 +502,144 @@ defmodule System do
defp validate_env(enum) do
Enum.map enum, fn
{k, nil} ->
{String.to_char_list(k), false}
{k, v} ->
{String.to_char_list(k), String.to_char_list(v)}
other ->
raise ArgumentError, "invalid environment key-value #{inspect other}"
end
end
@doc """
Returns the current monotonic time in the `:native` time unit.
This time is monotonically increasing and starts in an unspecified point in
time.
For more information, see the [chapter on time and time
correction](http://www.erlang.org/doc/apps/erts/time_correction.html) in the
Erlang docs.
Inlined by the compiler into `:erlang.monotonic_time/0`.
"""
@spec monotonic_time() :: integer
def monotonic_time do
:erlang.monotonic_time()
end
@doc """
Returns the current monotonic time in the given time unit.
For more information, see the [chapter on time and time
correction](http://www.erlang.org/doc/apps/erts/time_correction.html) in the
Erlang docs.
Inlined by the compiler into `:erlang.monotonic_time/1`.
"""
@spec monotonic_time(:erlang.time_unit) :: integer
def monotonic_time(unit) do
:erlang.monotonic_time(unit)
end
@doc """
Returns the current system time in the `:native` time unit.
For more information, see the [chapter on time and time
correction](http://www.erlang.org/doc/apps/erts/time_correction.html) in the
Erlang docs.
Inlined by the compiler into `:erlang.system_time/0`.
"""
@spec system_time() :: integer
def system_time do
:erlang.system_time()
end
@doc """
Returns the current system time in the given time unit.
For more information, see the [chapter on time and time
correction](http://www.erlang.org/doc/apps/erts/time_correction.html) in the
Erlang docs.
Inlined by the compiler into `:erlang.system_time/1`.
"""
@spec system_time(:erlang.time_unit) :: integer
def system_time(unit) do
:erlang.system_time(unit)
end
@doc """
Converts `time` from time unit `from_unit` to time unit `to_unit`. The result
is rounded via the floor function.
Inlined by the compiler into `:erlang.convert_time_unit/3`.
"""
@spec convert_time_unit(integer, :erlang.time_unit, :erlang.time_unit) :: integer
def convert_time_unit(time, from_unit, to_unit) do
:erlang.convert_time_unit(time, from_unit, to_unit)
end
@doc """
Returns the current time offset between the Erlang monotonic time and the
Erlang system time.
The result is returned in the `:native` time unit.
See `time_offset/1` for more information.
Inlined by the compiler into `:erlang.time_offset/0`.
"""
@spec time_offset() :: integer
def time_offset do
:erlang.time_offset()
end
@doc """
Returns the current time offset between the Erlang monotonic time and the
Erlang system time.
The result is returned in the given time unit `unit`. The returned offset,
added to an Erlang monotonic time (e.g., obtained with `monotonic_time/1`),
gives the Erlang system time that corresponds to that monotonic time.
For more information, see the [chapter on time and time
correction](http://www.erlang.org/doc/apps/erts/time_correction.html) in the
Erlang docs.
Inlined by the compiler into `:erlang.time_offset/1`.
"""
@spec time_offset(:erlang.time_unit) :: integer
def time_offset(unit) do
:erlang.time_offset(unit)
end
@doc """
Generates and returns an integer that is unique in the current runtime
instance.
"Unique" means that this function, called with the same list of `modifiers`,
will never return the same integer more than once on the current runtime
instance.
If `modifiers` is `[]`, then an unique integer (that can be positive or negative) is returned.
Other modifiers can be passed to change the properties of the returned integer:
* `:positive` - the returned integer is guaranteed to be positive.
* `:monotonic` - the returned integer is monotonically increasing. This
means that, on the same runtime instance (but even on different
processes), integers returned using the `:monotonic` modifier will always
be strictly less than integers returned by successive calls with the
`:monotonic` modifier.
All modifiers listed above can be combined; repeated modifiers in `modifiers`
will be ignored.
Inlined by the compiler into `:erlang.unique_integer/1`.
"""
@spec unique_integer([:positive | :monotonic]) :: integer
def unique_integer(modifiers \\ []) do
:erlang.unique_integer(modifiers)
end
end
+252 -92
View File
@@ -1,31 +1,33 @@
defmodule Task do
@moduledoc """
Conveniences for spawning and awaiting for tasks.
Conveniences for spawning and awaiting tasks.
Tasks are processes meant to execute one particular
action throughout their life-cycle, often with little or no
communication with other processes. The most common use case
for tasks is to compute a value asynchronously:
for tasks is to convert sequential code into concurrent code
by computing a value asynchronously:
task = Task.async(fn -> do_some_work() end)
res = do_some_other_work()
res + Task.await(task)
Tasks spawned with `async` can be awaited on by its caller
process (and only its caller) as shown in the example above.
Tasks spawned with `async` can be waited on by their caller
process (and only their caller) as shown in the example above.
They are implemented by spawning a process that sends a message
to the caller once the given computation is performed.
Besides `async/1` and `await/2`, tasks can also be
started as part of supervision trees and dynamically spawned
started as part of supervision tree and dynamically spawned
in remote nodes. We will explore all three scenarios next.
## async and await
The most common way to spawn a task is with `Task.async/1`. A new
process will be created, linked and monitored by the caller. Once
the task action finishes, a message will be sent to the caller
with the result.
One of the common use of tasks is to convert sequential code
into concurrent code with `Task.async/1` while keeping its semantics.
When invoked, a new process will be created, linked and monitored
by the caller. Once the task action finishes, a message will be sent
to the caller with the result.
`Task.await/2` is used to read the message sent by the task.
`await` will check the monitor setup by the call to `async/1` to
@@ -34,7 +36,7 @@ defmodule Task do
There are two important things to consider when using async:
1. If you are using async tasks, you must await for a reply
1. If you are using async tasks, you must await a reply
as they are *always* sent. If you are not expecting a reply,
consider using `Task.start_link/1` detailed below
@@ -42,15 +44,15 @@ defmodule Task do
means that, if the caller crashes, the task will crash
too and vice-versa. This is on purpose, if the process
meant to receive the result no longer exists, there is
no purpose in computing the result until the end. If this
no purpose in completing computation of the result. If this
is not desired, consider using `Task.start_link/1` as well
`Task.yield/2` is an alternative to `await/2` where the caller will
temporarily block waiting for a task's result. If the result does not
arrive within the timeout it can be called again at later moment. This
allows checking for the result of a task multiple times or to handle
a timeout. If a reply does not arrive within the desired time, and the
caller is not going exit, `Task.shutdown/2` can be used to stop the task.
temporarily block, waiting until the task replies or crashes. If the
result does not arrive within the timeout it can be called again at a
later moment. This allows checking for the result of a task multiple
times or to handle a timeout. If a reply does not arrive within the
desired time, `Task.shutdown/2` can be used to stop the task.
## Supervised tasks
@@ -68,7 +70,7 @@ defmodule Task do
]
Since these tasks are supervised and not directly linked to
the caller, they cannot be awaited on. Note `start_link/1`,
the caller, they cannot be waited on. Note `start_link/1`,
unlike `async/1`, returns `{:ok, pid}` (which is
the result expected by supervision trees).
@@ -144,8 +146,10 @@ defmodule Task do
* `:ref` - the task monitor reference
* `:owner` - the PID of the process that started the task
"""
defstruct pid: nil, ref: nil
defstruct pid: nil, ref: nil, owner: nil
@type t :: %__MODULE__{}
@@ -169,7 +173,7 @@ defmodule Task do
Starts a task.
This is only used when the task is used for side-effects
(i.e. no interest in its return result) and it should not
(i.e. no interest in the returned result) and it should not
be linked to the current process.
"""
@spec start(fun) :: {:ok, pid}
@@ -181,7 +185,7 @@ defmodule Task do
Starts a task.
This is only used when the task is used for side-effects
(i.e. no interest in its return result) and it should not
(i.e. no interest in the returned result) and it should not
be linked to the current process.
"""
@spec start(module, atom, [term]) :: {:ok, pid}
@@ -210,16 +214,59 @@ defmodule Task do
end
@doc """
Starts a task that can be awaited on.
Starts a task that must be awaited on.
This function spawns a process that is linked to and monitored
by the caller process. A `Task` struct is returned containing
the relevant information.
A `Task` struct is returned containing the relevant information.
Developers must eventually call `Task.await/2` or `Task.yield/2`
followed by `Task.shutdown/2` on the returned task.
Read the `Task` module documentation for more info on general
usage of `async/1` and `async/3`.
## Task's message format
## Linking
This function spawns a process that is linked to and monitored
by the caller process. The linking part is important because it
aborts the task if the parent process dies. It also guarantees
the code before async/await has the same properties after you
add the async call. For example, imagine you have this:
x = heavy_fun()
y = some_fun()
x + y
Now you want to make the `heavy_fun()` async:
x = Task.async(&heavy_fun/0)
y = some_fun()
Task.await(x) + y
As before, if `heavy_fun/0` fails, the whole computation will
fail, including the parent process. If you don't want the task
to fail then you must change the `heavy_fun/0` code in the
same way you would if you didn't have the async call. For
example to either return `{:ok, val} | :error` results or,
in more extreme cases, by using `try/rescue`. In other words,
an asynchronous task should be considered an extension of a
process rather than a mechanism to isolate it from all errors.
If you don't want to link the caller to the task, then you
must use a supervised task with `Task.Supervisor` and call
`Task.Supervisor.async_nolink/2`.
In any case, avoid any of the following:
* Setting `:trap_exit` to true - trapping exists should be
used only in special circumstances as it would make your
process immune to not only exits from the task but from
any other processes.
* Unlinking the task process started with `async`/`await`.
If you unlink the processes and the task does not belong
to any supervisor, you may leave dangling tasks in case
the parent dies.
## Message format
The reply sent by the task will be in the format `{ref, msg}`,
where `ref` is the monitoring reference held by the task.
@@ -227,10 +274,11 @@ defmodule Task do
@spec async(module, atom, [term]) :: t
def async(mod, fun, args) do
mfa = {mod, fun, args}
pid = :proc_lib.spawn_link(Task.Supervised, :async, [self, get_info(self), mfa])
owner = self()
pid = Task.Supervised.spawn_link(owner, get_info(owner), mfa)
ref = Process.monitor(pid)
send(pid, {self(), ref})
%Task{pid: pid, ref: ref}
send(pid, {owner, ref})
%Task{pid: pid, ref: ref, owner: owner}
end
defp get_info(self) do
@@ -250,18 +298,32 @@ defmodule Task do
If the timeout is exceeded, `await` will exit, however,
the task will continue to run. When the calling process exits, its
exit signal will close the task if it is not trapping exits.
exit signal will terminate the task if it is not trapping exits.
This function assumes the task's monitor is still active or the monitor's
`:DOWN` message is in the message queue. If it has been demonitored, or the
message already received, this function may wait for the duration of the
timeout awaiting the message.
This function will always demonitor the task and so the task can not be used
again. To await the task's reply multiple times use `yield/2` instead.
This function will always exit and demonitor if the task crashes or if
it times out, so the task can not be used again. To explicitly handle
the timeout or the crash, use `yield/2` instead.
"""
@spec await(t, timeout) :: term | no_return
def await(%Task{ref: ref}=task, timeout \\ 5000) do
def await(task, timeout \\ 5000)
# TODO: Remove nil check in Elixir 1.3
def await(%Task{owner: owner}=task, _) when owner != nil and owner != self() do
raise ArgumentError, invalid_owner_error(task)
end
def await(%Task{ref: ref, owner: owner}=task, timeout) do
if is_nil(owner) do
IO.write :stderr, "warning: a Task was created with the :owner field no set, " <>
"please ensure the owner field is correctly set to self()\n" <>
Exception.format_stacktrace
end
receive do
{^ref, reply} ->
Process.demonitor(ref, [:flush])
@@ -281,62 +343,32 @@ defmodule Task do
This function returns a tuple with the returned value
in case the message matches a task that exited with
success alongside the matching task. It raises in case
the found task failed or `nil` if no task was found.
success alongside the matching task. It returns `nil`
if no task was found. It exits if the task has failed.
This function is useful in situations where multiple
tasks are spawned and their results are collected
later on. For example, a `GenServer` can spawn tasks,
store the tasks in a list and later use `Task.find/2`
to see if incoming messages are from any of the tasks.
## Examples
defmodule TaskFinder do
def run do
task1 = Task.async fn -> :timer.sleep(1000); 1 end
task2 = Task.async fn -> :timer.sleep(5000); 2 end
await [task1, task2]
end
# Be careful, this will receive all messages sent
# to this process. It will return the first task
# reply and the list of tasks that came second.
def await(tasks) do
receive do
message ->
case Task.find(tasks, message) do
{reply, task} ->
{reply, List.delete(tasks, task)}
nil ->
await(tasks)
end
end
end
end
TaskFinder.run
"""
@spec find([t], any) :: {term, t} | nil | no_return
def find(tasks, msg)
def find(tasks, {ref, reply}) when is_reference(ref) do
Enum.find_value tasks, fn
%Task{ref: task_ref} = t when ref == task_ref ->
%Task{ref: ^ref} = task ->
Process.demonitor(ref, [:flush])
{reply, t}
{reply, task}
%Task{} ->
nil
end
end
def find(tasks, {:DOWN, ref, _, proc, reason} = msg) when is_reference(ref) do
find = fn(%Task{ref: task_ref}) -> task_ref == ref end
case Enum.find(tasks, find) do
%Task{} ->
exit({reason(reason, proc), {__MODULE__, :find, [tasks, msg]}})
nil ->
nil
find = fn %Task{ref: task_ref} -> task_ref == ref end
if Enum.find(tasks, find) do
exit({reason(reason, proc), {__MODULE__, :find, [tasks, msg]}})
end
end
@@ -345,9 +377,10 @@ defmodule Task do
end
@doc """
Yields, temporarily, for a task reply.
Yields for a task reply in the given time interval.
Returns `{:ok, reply}` if the reply is received.
Returns `{:ok, reply}` if the reply is received, `{:exit, reason}`
if the task exited or `nil` if no reply arrived.
A timeout, in milliseconds, can be given with default value
of `5000`. In case of the timeout, this function will return `nil`
@@ -357,37 +390,144 @@ defmodule Task do
In case the task process dies, this function will exit with the
same reason as the task.
This function assumes the task's monitor is still active or the monitor's
`:DOWN` message is in the message queue. If it has been demonitored, or the
message already received, this function wait for the duration of the timeout
awaiting the message.
This function assumes the task's monitor is still active or the
monitor's `:DOWN` message is in the message queue. If it has been
demonitored, or the message already received, this function waits
for the duration of the timeout awaiting the message.
"""
@spec yield(t, timeout) :: {:ok, term} | nil
def yield(%Task{ref: ref} = task, timeout \\ 5_000) do
@spec yield(t, timeout) :: {:ok, term} | {:exit, term} | nil
def yield(task, timeout \\ 5_000)
# TODO: Remove nil check in Elixir 1.3
def yield(%Task{owner: owner} = task, _) when owner != nil and owner != self() do
raise ArgumentError, invalid_owner_error(task)
end
def yield(%Task{ref: ref, owner: owner} = task, timeout) do
if is_nil(owner) do
IO.write :stderr, "warning: a Task was created with the :owner field no set, " <>
"please ensure the owner field is correctly set to self()\n" <>
Exception.format_stacktrace
end
receive do
{^ref, reply} ->
Process.demonitor(ref, [:flush])
{:ok, reply}
{:DOWN, ^ref, _, proc, reason} ->
exit({reason(reason, proc), {__MODULE__, :yield, [task, timeout]}})
{:DOWN, ^ref, _, proc, :noconnection} ->
exit({reason(:noconnection, proc), {__MODULE__, :yield, [task, timeout]}})
{:DOWN, ^ref, _, _, reason} ->
{:exit, reason}
after
timeout ->
nil
end
end
@doc """
Yields to multiple tasks in the given time interval.
This function receives a list of tasks and await for their
replies at once in the given time interval. It returns a list
of tuples of two elements, with tasks as the first element and
the `yield` result as the second.
Similar to `yield/2`, if the task replied in the given interval,
it will return `{:ok, term}`, `{:exit, reason}`if it crashed or
`nil` if it timed out. Check `yield/2` for more information.
## Example
`Task.yield_many/2` allows developers to spawn multiple tasks
and retrieve the results received in a given timeframe.
If we combine it with `Task.shutdown/2`, it allows us to gather
those results and cancel the tasks that have not replied in time.
Let's see an example.
tasks =
for i <- 1..10 do
Task.async(fn ->
:timer.sleep(i * 1000)
i
end)
end
tasks_with_results = Task.yield_many(tasks, 5000)
results = Enum.map(tasks_with_results, fn {task, res} ->
# Shutdown the tasks that did not reply nor exit
res || Task.shutdown(task, :brutal_kill)
end)
# Here we are matching only on {:ok, value} and
# ignoring {:exit, _} (crashed tasks) and `nil` (no replies)
for {:ok, value} <- results do
IO.inspect value
end
In the example above, we create tasks that sleep from 1
up to 10 seconds and return the amount of seconds they slept.
If you execute the code all at once, you should see 1 up to 5
printed, as those were the tasks that have replied in the
given time. All other tasks will have been shutdown, according
to the `Task.shutdown/2` call.
"""
@spec yield_many([t], timeout) :: [{t, {:ok, term} | {:exit, term} | nil}]
def yield_many(tasks, timeout \\ 5000) do
timeout_ref = make_ref()
timer_ref = Process.send_after(self(), timeout_ref, timeout)
try do
yield_many(tasks, timeout_ref, :infinity)
catch
{:noconnection, reason} ->
exit({reason, {__MODULE__, :yield_many, [tasks, timeout]}})
after
Process.cancel_timer(timer_ref)
receive do: (^timeout_ref -> :ok), after: (0 -> :ok)
end
end
defp yield_many([%Task{ref: ref, owner: owner}=task|rest], timeout_ref, timeout) do
if owner != self() do
raise ArgumentError, invalid_owner_error(task)
end
receive do
{^ref, reply} ->
Process.demonitor(ref, [:flush])
[{task, {:ok, reply}}|yield_many(rest, timeout_ref, timeout)]
{:DOWN, ^ref, _, proc, :noconnection} ->
throw({:noconnection, reason(:noconnection, proc)})
{:DOWN, ^ref, _, _, reason} ->
[{task, {:exit, reason}}|yield_many(rest, timeout_ref, timeout)]
^timeout_ref ->
[{task, nil}|yield_many(rest, timeout_ref, 0)]
after
timeout ->
[{task, nil}|yield_many(rest, timeout_ref, 0)]
end
end
defp yield_many([], _timeout_ref, _timeout) do
[]
end
@doc """
Unlinks and shutdowns the task, and then checks for a reply.
Returns `{:ok, reply}` if the reply is received while shutting down the task,
otherwise `nil`.
`{:exit, reason}` if the task exited abornormally, otherwise `nil`.
The shutdown method is either a timeout or `:brutal_kill`. In the case
The shutdown method is either a timeout or `:brutal_kill`. In case
of a `timeout`, a `:shutdown` exit signal is sent to the task process
and if it does not exit within the timeout it is killed. With `:brutal_kill`
the task is killed straight away. In the case that the task exits abnormal,
or a timeout shutdown kills the task, this function will exit with the same
reason.
the task is killed straight away. In case the task exits abnormally, or a
timeout shutdown kills the task, this function will exit with the same reason.
It is not required to call this function when terminating the caller, unless
exiting with reason `:normal` or the task is trapping exits. If the caller is
@@ -400,18 +540,32 @@ defmodule Task do
`:DOWN` message is in the message queue. If it has been demonitored, or the
message already received, this function will block forever awaiting the message.
"""
@spec shutdown(t, timeout | :brutal_kill) :: {:ok, term} | nil
@spec shutdown(t, timeout | :brutal_kill) :: {:ok, term} | {:exit, term} | nil
def shutdown(task, shutdown \\ 5_000)
def shutdown(%Task{pid: nil} = task, _) do
raise ArgumentError, "task #{inspect task} does not have an associated task process."
raise ArgumentError, "task #{inspect task} does not have an associated task process"
end
def shutdown(%Task{pid: pid} = task, :brutal_kill) do
# TODO: Remove nil check in Elixir 1.3
def shutdown(%Task{owner: owner} = task, _) when owner != nil and owner != self() do
raise ArgumentError, invalid_owner_error(task)
end
def shutdown(%Task{pid: pid, owner: owner} = task, :brutal_kill) do
if is_nil(owner) do
IO.write :stderr, "warning: a Task was created with the :owner field no set, " <>
"please ensure the owner field is correctly set to self()\n" <>
Exception.format_stacktrace
end
exit(pid, :kill)
case shutdown_receive(task, :brutal_kill, :infinity) do
{:error, reason} ->
exit({reason, {__MODULE__, :shutdown, [task, :brutal_kill]}})
{:down, proc, :noconnection} ->
exit({reason(:noconnection, proc), {__MODULE__, :shutdown, [task, :brutal_kill]}})
{:down, _, reason} ->
{:exit, reason}
result ->
result
end
@@ -420,8 +574,10 @@ defmodule Task do
def shutdown(%Task{pid: pid} = task, timeout) do
exit(pid, :shutdown)
case shutdown_receive(task, :shutdown, timeout) do
{:error, reason} ->
exit({reason, {__MODULE__, :shutdown, [task, timeout]}})
{:down, proc, :noconnection} ->
exit({reason(:noconnection, proc), {__MODULE__, :shutdown, [task, timeout]}})
{:down, _, reason} ->
{:exit, reason}
result ->
result
end
@@ -462,7 +618,7 @@ defmodule Task do
{:DOWN, ^ref, _, _, :killed} when type == :brutal_kill ->
flush_reply(ref)
{:DOWN, ^ref, _, proc, reason} ->
flush_reply(ref) || {:error, reason(reason, proc)}
flush_reply(ref) || {:down, proc, reason}
after
timeout ->
Process.exit(task.pid, :kill)
@@ -477,4 +633,8 @@ defmodule Task do
0 -> nil
end
end
defp invalid_owner_error(task) do
"task #{inspect task} must be queried from the owner but was queried from #{inspect self()}"
end
end
+33 -18
View File
@@ -1,6 +1,8 @@
defmodule Task.Supervised do
@moduledoc false
@ref_timeout 5_000
def start(info, fun) do
{:ok, :proc_lib.spawn(__MODULE__, :noreply, [info, fun])}
end
@@ -9,24 +11,38 @@ defmodule Task.Supervised do
{:ok, :proc_lib.spawn_link(__MODULE__, :noreply, [info, fun])}
end
def start_link(caller, info, fun) do
:proc_lib.start_link(__MODULE__, :reply, [caller, info, fun])
def start_link(caller, link, info, fun) do
{:ok, spawn_link(caller, link, info, fun)}
end
def async(caller, info, mfa) do
initial_call(mfa)
ref = receive do: ({^caller, ref} -> ref)
send caller, {ref, do_apply(info, mfa)}
def spawn_link(caller, link \\ :nolink, info, fun) do
:proc_lib.spawn_link(__MODULE__, :reply, [caller, link, info, fun])
end
def reply(caller, info, mfa) do
def reply(caller, link, info, mfa) do
initial_call(mfa)
:erlang.link(caller)
:proc_lib.init_ack({:ok, self()})
case link do
:link ->
Process.link(caller)
reply(caller, nil, @ref_timeout, info, mfa)
:monitor ->
mref = Process.monitor(caller)
reply(caller, mref, @ref_timeout, info, mfa)
:nolink ->
reply(caller, nil, :infinity, info, mfa)
end
end
ref =
defp reply(caller, mref, timeout, info, mfa) do
receive do
{^caller, ref} ->
_ = if mref, do: Process.demonitor(mref, [:flush])
send caller, {ref, do_apply(info, mfa)}
{:DOWN, ^mref, _, _, reason} when is_reference(mref) ->
exit(reason)
after
# There is a race condition on this operation when working across
# node that manifests if a "Task.Supervisor.async/1" call is made
# node that manifests if a "Task.Supervisor.async/2" call is made
# while the supervisor is busy spawning previous tasks.
#
# Imagine the following workflow:
@@ -41,13 +57,12 @@ defmodule Task.Supervised do
# Given no work is done in the client between the task start and
# sending the reference, 5000 should be enough to not raise false
# negatives unless the nodes are indeed not available.
receive do
{^caller, ref} -> ref
after
5000 -> exit(:timeout)
end
send caller, {ref, do_apply(info, mfa)}
#
# The same situation could occur with "Task.Supervisor.async_nolink/2",
# except a monitor is used instead of a link.
timeout ->
exit(:timeout)
end
end
def noreply(info, mfa) do
+43 -10
View File
@@ -1,6 +1,6 @@
defmodule Task.Supervisor do
@moduledoc """
A tasks supervisor.
A task supervisor.
This module defines a supervisor which can be used to dynamically
supervise tasks. Behind the scenes, this module is implemented as a
@@ -12,7 +12,7 @@ defmodule Task.Supervisor do
## Name Registration
A `Task.Supervisor` is bound to the same name registration rules as a
`GenServer`. Read more about it in the `GenServer` docs.
`GenServer`. Read more about them in the `GenServer` docs.
"""
@doc """
@@ -26,7 +26,7 @@ defmodule Task.Supervisor do
* `:restart` - the restart strategy, may be `:temporary` (the default),
`:transient` or `:permanent`. Check `Supervisor.Spec` for more info.
Defaults to temporary as most tasks can't be effectively restarted after
Defaults to `:temporary` as most tasks can't be effectively restarted after
a crash;
* `:shutdown` - `:brutal_kill` if the tasks must be killed directly on shutdown
@@ -48,7 +48,8 @@ defmodule Task.Supervisor do
Starts a task that can be awaited on.
The `supervisor` must be a reference as defined in `Task.Supervisor`.
For more information on tasks, check the `Task` module.
The task will still be linked to the caller, see `Task.async/3` for
more information and `async_nolink/2` for a non-linked variant.
"""
@spec async(Supervisor.supervisor, fun) :: Task.t
def async(supervisor, fun) do
@@ -59,15 +60,47 @@ defmodule Task.Supervisor do
Starts a task that can be awaited on.
The `supervisor` must be a reference as defined in `Task.Supervisor`.
For more information on tasks, check the `Task` module.
The task will still be linked to the caller, see `Task.async/3` for
more information and `async_nolink/2` for a non-linked variant.
"""
@spec async(Supervisor.supervisor, module, atom, [term]) :: Task.t
def async(supervisor, module, fun, args) do
args = [self, get_info(self), {module, fun, args}]
owner = self()
args = [owner, :link, get_info(owner), {module, fun, args}]
{:ok, pid} = Supervisor.start_child(supervisor, args)
Process.link(pid)
ref = Process.monitor(pid)
send pid, {owner, ref}
%Task{pid: pid, ref: ref, owner: owner}
end
@doc """
Starts a task that can be awaited on.
The `supervisor` must be a reference as defined in `Task.Supervisor`.
The task won't be linked to the caller, see `Task.async/3` for
more information.
"""
@spec async_nolink(Supervisor.supervisor, fun) :: Task.t
def async_nolink(supervisor, fun) do
async_nolink(supervisor, :erlang, :apply, [fun, []])
end
@doc """
Starts a task that can be awaited on.
The `supervisor` must be a reference as defined in `Task.Supervisor`.
The task won't be linked to the caller, see `Task.async/3` for
more information.
"""
@spec async_nolink(Supervisor.supervisor, module, atom, [term]) :: Task.t
def async_nolink(supervisor, module, fun, args) do
owner = self()
args = [owner, :monitor, get_info(owner), {module, fun, args}]
{:ok, pid} = Supervisor.start_child(supervisor, args)
ref = Process.monitor(pid)
send pid, {self(), ref}
%Task{pid: pid, ref: ref}
send pid, {owner, ref}
%Task{pid: pid, ref: ref, owner: owner}
end
@doc """
@@ -75,7 +108,7 @@ defmodule Task.Supervisor do
"""
@spec terminate_child(Supervisor.supervisor, pid) :: :ok
def terminate_child(supervisor, pid) when is_pid(pid) do
:supervisor.terminate_child(supervisor, pid)
Supervisor.terminate_child(supervisor, pid)
end
@doc """
@@ -83,7 +116,7 @@ defmodule Task.Supervisor do
"""
@spec children(Supervisor.supervisor) :: [pid]
def children(supervisor) do
:supervisor.which_children(supervisor) |> Enum.map(&elem(&1, 1))
Supervisor.which_children(supervisor) |> Enum.map(&elem(&1, 1))
end
@doc """
+11 -9
View File
@@ -2,14 +2,14 @@ defmodule Tuple do
@moduledoc """
Functions for working with tuples.
See also `Kernel.elem/2`, `Kernel.is_tuple/1`, `Kernel.put_elem/3`, and
`Kernel.tuple_size/1`.
See also `Kernel.elem/2`, `Kernel.is_tuple/1`,
`Kernel.put_elem/3`, and `Kernel.tuple_size/1`.
"""
@doc """
Creates a new tuple.
Creates a tuple of size `size` containing the
Creates a tuple of `size` containing the
given `data` at every position.
Inlined by the compiler.
@@ -28,9 +28,9 @@ defmodule Tuple do
@doc """
Inserts an element into a tuple.
Inserts `value` into `tuple` at the given zero-based `index`.
Inserts `value` into `tuple` at the given `index`.
Raises an `ArgumentError` if `index` is negative or greater than the
length of `tuple`.
length of `tuple`. Index is zero-based.
Inlined by the compiler.
@@ -49,9 +49,9 @@ defmodule Tuple do
end
@doc """
Inserts an element into the end of a tuple.
Inserts an element at the end of a tuple.
Returns a new tuple which has one element more than `tuple`, and contains
Returns a new tuple with the element appended at the end, and contains
the elements in `tuple` followed by `value` as the last element.
Inlined by the compiler.
@@ -70,9 +70,9 @@ defmodule Tuple do
@doc """
Removes an element from a tuple.
Deletes the element at the zero-based `index` from `tuple`.
Deletes the element at the given `index` from `tuple`.
Raises an `ArgumentError` if `index` is negative or greater than
or equal to the length of `tuple`.
or equal to the length of `tuple`. Index is zero-based.
Inlined by the compiler.
@@ -91,6 +91,8 @@ defmodule Tuple do
@doc """
Converts a tuple to a list.
Returns a new list with all the tuple elements.
Inlined by the compiler.
## Examples
+15 -13
View File
@@ -75,6 +75,7 @@ defmodule URI do
%{"bar" => "2", "foo" => "1"}
"""
# TODO: Deprecate giving not a map on 1.3
def decode_query(q, dict \\ %{}) when is_binary(q) do
case do_decode_query(q) do
nil -> dict
@@ -177,7 +178,7 @@ defmodule URI do
end
@doc """
Encodes a string as "x-www-urlencoded".
Encodes a string as "x-www-form-urlencoded".
## Example
@@ -222,7 +223,7 @@ defmodule URI do
end
@doc """
Decodes a string as "x-www-urlencoded".
Decodes a string as "x-www-form-urlencoded".
## Examples
@@ -339,7 +340,7 @@ defmodule URI do
# to replace those with nil for consistency.
defp nillify(l) do
for s <- l do
if byte_size(s) > 0, do: s, else: nil
if byte_size(s) > 0, do: s
end
end
@@ -361,23 +362,24 @@ defimpl String.Chars, for: URI do
end
# Based on http://tools.ietf.org/html/rfc3986#section-5.3
if uri.host do
authority = uri.host
if uri.userinfo, do: authority = uri.userinfo <> "@" <> authority
if uri.port, do: authority = authority <> ":" <> Integer.to_string(uri.port)
else
authority = uri.authority
end
authority = extract_authority(uri)
result = ""
if uri.scheme, do: result = result <> uri.scheme <> ":"
if authority, do: result = result <> "//" <> authority
if uri.path, do: result = result <> uri.path
if uri.query, do: result = result <> "?" <> uri.query
if uri.fragment, do: result = result <> "#" <> uri.fragment
result
end
defp extract_authority(%{host: nil, authority: authority}) do
authority
end
defp extract_authority(%{host: host, userinfo: userinfo, port: port}) do
authority = host
if userinfo, do: authority = userinfo <> "@" <> authority
if port, do: authority = authority <> ":" <> Integer.to_string(port)
authority
end
end
+7 -7
View File
@@ -10,7 +10,7 @@ defmodule Version do
## Versions
In a nutshell, a version is given by three numbers:
In a nutshell, a version is represented by three numbers:
MAJOR.MINOR.PATCH
@@ -24,8 +24,8 @@ defmodule Version do
## Struct
The version is represented by the Version struct and it has its
fields named according to Semver: `:major`, `:minor`, `:patch`,
The version is represented by the Version struct and fields
are named according to Semver: `:major`, `:minor`, `:patch`,
`:pre` and `:build`.
## Requirements
@@ -105,7 +105,7 @@ defmodule Version do
Returns `true` if `version` satisfies `requirement`, `false` otherwise.
Raises a `Version.InvalidRequirementError` exception if `requirement` is not
parseable, or `Version.InvalidVersionError` if `version` is not parseable.
parsable, or `Version.InvalidVersionError` if `version` is not parsable.
If given an already parsed version and requirement this function won't
raise.
@@ -144,7 +144,7 @@ defmodule Version do
the second and `:lt` for vice versa. If the two versions are equal `:eq`
is returned
Raises a `Version.InvalidVersionError` exception if `version` is not parseable.
Raises a `Version.InvalidVersionError` exception if `version` is not parsable.
If given an already parsed version this function won't raise.
## Examples
@@ -266,8 +266,8 @@ defmodule Version do
defmacro deflexer(char, acc, do: body) do
quote do
def lexer(<< unquote(char) :: utf8, rest :: binary >>, unquote(acc)) do
unquote(char) = << unquote(char) :: utf8 >>
def lexer(<<unquote(char)::utf8, rest::binary>>, unquote(acc)) do
unquote(char) = <<unquote(char)::utf8>>
lexer(rest, unquote(body))
end
+168
View File
@@ -0,0 +1,168 @@
# Typespecs
Elixir comes with a notation for declaring types and specifications. Elixir is
dynamically typed, and as such, typespecs are never used by the compiler to
optimize or modify code. Still, using typespecs is useful as documentation and
tools such as [Dialyzer](http://www.erlang.org/doc/man/dialyzer.html) can
analyze code with typespecs to find bugs.
The attributes `@type`, `@opaque`, `@typep`, `@spec`, `@callback` and
`@macrocallback` available in modules are handled by the equivalent macros
defined by this module. See sub-sections "Defining a type" and "Defining a
specification" below.
## Types and their syntax
The type syntax provided by Elixir is fairly similar to [the one in
Erlang](http://www.erlang.org/doc/reference_manual/typespec.html).
Most of the built-in types provided in Erlang (for example, `pid()`) are
expressed the same way: `pid()` or simply `pid`. Parameterized types are also
supported (`list(integer)`) and so are remote types (`Enum.t`).
Integers and atom literals are allowed as types (ex. `1`, `:atom` or
`false`). All other types are built of unions of predefined types. Certain
shorthands are allowed, such as `[...]`, `<<>>` and `{...}`.
### Basic types
type :: any() # the top type, the set of all terms
| none() # the bottom type, contains no terms
| pid()
| port()
| reference()
| tuple()
| atom()
| integer()
| non_neg_integer() # 0, 1, 2, 3, ...
| pos_integer() # 1, 2, 3, ...
| neg_integer() # ..., -3, -2, -1
| float()
| map()
| struct()
| list(type)
| nonempty_list(type)
| improper_list(type1, type2)
| maybe_improper_list(type1, type2)
| Literals # Described in section "Literals"
| Builtin # Described in section "Builtin-types"
| Remotes # Described in section "Remotes"
### Literals
The following literals are also supported in typespecs:
type :: :atom ## Atoms
| 1 ## Integers
| 1..10 ## Integers from 1 to 10
| 1.0 ## Floats
| <<>> ## Bitstrings
| <<_::size>> # size is 0 or a positive integer
| <<_::_ * unit>> # unit is an integer from 1 to 256
| <<_::size * unit>>
| [type] ## Lists
| [] # empty list
| [...] # shorthand for nonempty_list(any())
| [type, ...] # shorthand for nonempty_list(type)
| [key: type] # keyword lists
| (... -> type) ## Functions
| (... -> type) # any arity, returns type
| (() -> type) # 0-arity, returns type
| (type1, type2 -> type) # 2-arity, returns type
| %{} ## Maps
| %{key: type} # map with key :key with value of type
| %{type1 => type2} # map with keys of type1 with values of type2
| %SomeStruct{}
| %SomeStruct{key: type}
| {} ## Tuples
| {:ok, type} # two element tuple with an atom and any type
### Built-in types
These types are also provided by Elixir as shortcuts on top of the
basic and literal types.
Built-in type | Defined as
:---------------------- | :---------
`term()` | `any()`
`binary()` | `<<_::_ * 8>>`
`bitstring()` | `<<_::_ * 1>>`
`boolean()` | `false` \| `true`
`byte()` | `0..255`
`char()` | `0..0x10ffff`
`number()` | `integer()` \| `float()`
`char_list()` | `[char()]`
`list()` | `[any()]`
`maybe_improper_list()` | `maybe_improper_list(any(), any())`
`nonempty_list()` | `nonempty_list(any())`
`iodata()` | `iolist()` \| `binary()`
`iolist()` | `maybe_improper_list(byte()` \| `binary()` \| `iolist(), binary()` \| `[])`
`module()` | `atom()` \| `tuple()`
`arity()` | `0..255`
`mfa()` | `{atom(), atom(), arity()}`
`node()` | `atom()`
`timeout()` | `:infinity` \| `non_neg_integer()`
`no_return()` | `none()`
`fun()` | `(... -> any)`
`struct()` | `%{__struct__: atom()}`
`as_boolean(t)` | `t`
### Remote types
Any module is also able to define its own type and the modules in
Elixir are no exception. For example, a string is `String.t`, a
range is `Range.t`, any enumerable can be `Enum.t` and so on.
## Defining a type
@type type_name :: type
@typep type_name :: type
@opaque type_name :: type
A type defined with `@typep` is private. An opaque type, defined with
`@opaque` is a type where the internal structure of the type will not be
visible, but the type is still public.
Types can be parameterized by defining variables as parameters, these variables
can then be used to define the type.
@type dict(key, value) :: [{key, value}]
## Defining a specification
@spec function_name(type1, type2) :: return_type
@callback function_name(type1, type2) :: return_type
@macrocallback macro_name(type1, type2) :: Macro.t
Callbacks are used to define the callbacks functions of behaviours (see
`Behaviour`).
Guards can be used to restrict type variables given as arguments to the
function.
@spec function(arg) :: [arg] when arg: atom
Type variables with no restriction can also be defined.
@spec function(arg) :: [arg] when arg: var
Specifications can be overloaded just like ordinary functions.
@spec function(integer) :: atom
@spec function(atom) :: integer
## Notes
Elixir discourages the use of type `string` as it might be confused with
binaries which are referred to as "strings" in Elixir (as opposed to character
lists). In order to use the type that is called `string` in Erlang, one has to
use the `char_list` type which is a synonym for `string`. If you use `string`,
you'll get a warning from the compiler.
If you want to refer to the "string" type (the one operated on by functions in
the `String` module), use `String.t` type instead.
+102
View File
@@ -0,0 +1,102 @@
# Writing Documentation
Elixir treats documentation as a first-class citizen. This means documentation should be easy to write and easy to read. In this document you will learn how to write documentation in Elixir, covering constructs like module attributes, style practices and doctests.
## Markdown
Elixir documentation is written using Markdown. There are plenty of guides on Markdown online, we recommend the ones available at GitHub as a getting started point:
* https://help.github.com/articles/markdown-basics/
* https://help.github.com/articles/github-flavored-markdown/
## Module Attributes
Documentation in Elixir is usually attached to module attributes. Let's see an example:
defmodule MyApp.Hello do
@moduledoc """
This is the Hello module.
"""
@doc """
Says hello to the given `name`.
Returns `:ok`.
## Examples
iex> MyApp.Hello.world(:john)
:ok
"""
def world(name) do
IO.puts "hello #{name}"
end
end
The `@moduledoc` attribute is used to attach documentation to the module. `@doc` is used before a function to attach documentation to it. Besides the attributes above, `@typedoc` can also be used to attach documentation to types defined as part of typespecs.
## Function Arguments
When documenting a function, argument names are inferred by the compiler. For example:
def size(%HashDict{size: size}) do
size
end
The compiler will infer this argument as `hash_dict`. Sometimes the inference will be suboptimal, specially if the function contains multiple clauses with the argument matching on different values each time. You can specify the proper names for documentation by using a bodyless clause:
def size(dict)
## Recommendations
There are a couple tips we recommend developers to follow when writing documentation:
* Keep the first paragraph of the documentation concise and simple. Tools like ExDoc uses the first line to generate a summary.
* Markdown uses backticks (`` ` ``) to quote code. Elixir builds on top of that to automatically generate links when modules or function names are referenced. For this reason, always use full module names. If you have a module called `MyApp.Hello`, always reference it as `` `MyApp.Hello` `` and never as `` `Hello` ``. Function names must be referenced by name and arity if they are local, as in `` `world/1` ``, or by module, name and arity if pointing to an external module: `` `MyApp.Hello.world/1` ``. Referencing a `@callback` can be done by prepending `c:`, as in `` `c:world/1` ``.
* If using headings, always start from the second heading by using `##`. The first heading is reserved to the module or function name itself.
## Doctests
We recommend developers to include examples in their documentation, often under its own `## Examples` heading. To ensure examples do not get out of date, Elixir's test framework (ExUnit) provides a feature called doctests that allows developers to test the examples in their documentation. Doctests work by parsing out code samples starting with `iex>` from the documentation. You can read more about it at `ExUnit.DocTest`.
Notice doctests have limitations. When you cannot doctest a function, because it relies on state or side-effects, we recommend developers to include examples directly without the `iex>` prompt.
## Privacy
Elixir allows developers to hide both modules and functions from the documentation by setting the doc attribute to false:
defmodule MyApp.Hidden do
@moduledoc false
def this_will_be_ignored_by_tools do
# ...
end
end
Notice that, although developers can add `@doc false` to functions, it does not make the function private:
defmodule MyApp.Sample do
@doc false
def add(a, b), do: a + b
end
The function above can still be invoked as `MyApp.Sample.add(1, 2)`. Not only that, if the `MyApp.Sample` is imported, the `add/2` function will also be imported into the caller. For those reasons, be wary when adding `@doc false` to functions, instead prefer one of:
* Move the private function to a module with `@moduledoc false`, like `MyApp.Hidden`, ensuring the function won't be accidentally exposed or imported. In fact, you can use `@moduledoc false` to hide a whole module and still document each function with `@doc`. Tools will still ignore the module.
* Start the function name with underscores, for example, `__add__/2`, and add `@doc false`. The compiler does not import functions with underscore and the underscore will tell users to be wary of using it.
## Documentation != Comments
Elixir makes the difference between documentation and code comments. Documentation are for users of your API, be it your co-worker or your future self. Modules and functions must always be documented if they are part of your application public interface (API).
Code comments are for developers reading the code. They are useful to mark improvements, leave notes for developers reading the code (for example, you decided to not call a function due to a bug in a library) and so forth.
In other words, documentation is required, code comments are optional.
## Code.get_docs/2
Elixir stores documentation inside pre-defined chunks in the bytecode. It can be accessed from Elixir by using the `Code.get_docs/2` function. This also means documentation is only accessed when required and not when modules are loaded by the Virtual Machine. The only downside is that modules defined in-memory, like the ones defined via IEx, cannot have their documentation accessed.
+33 -11
View File
@@ -7,6 +7,7 @@
env_for_eval/1, env_for_eval/2, quoted_to_erl/2, quoted_to_erl/3,
eval/2, eval/3, eval_forms/3, eval_forms/4, eval_quoted/3]).
-include("elixir.hrl").
-define(system, 'Elixir.System').
%% Top level types
-export_type([char_list/0, struct/0, as_boolean/1]).
@@ -29,14 +30,32 @@ start(_Type, _Args) ->
error -> [binary]
end,
ok = io:setopts(standard_io, Opts),
%% TODO: Remove this once we support only OTP >18
ok = case io:setopts(standard_error, [{encoding, utf8}]) of
ok -> ok;
{error, _} -> io:setopts(standard_error, [{unicode, true}]) %% OTP 17.3 and earlier
case string:to_integer(erlang:system_info(otp_release)) of
{Num, _} when Num >= 18 ->
ok;
_ ->
io:format(standard_error, "unsupported Erlang version, expected Erlang 18+~n", []),
erlang:halt(1)
end,
case code:ensure_loaded(?system) of
{module, ?system} ->
Endianness = ?system:endianness(),
case ?system:compiled_endianness() of
Endianness -> ok;
_ ->
io:format(standard_error,
"warning: Elixir is running in a system with a different endianness than the one its "
"source code was compiled in. Please make sure Elixir and all source files were compiled "
"in a machine with the same endianness as the current one: ~ts~n", [Endianness])
end;
{error, _} ->
ok
end,
ok = io:setopts(standard_io, Opts),
ok = io:setopts(standard_error, [{encoding, utf8}]),
Encoding = file:native_name_encoding(),
case Encoding of
latin1 ->
@@ -55,11 +74,12 @@ start(_Type, _Args) ->
{<<"https">>, 443},
{<<"ldap">>, 389}],
URIConfig = [{{uri, Scheme}, Port} || {Scheme, Port} <- URIs],
CompilerOpts = [{docs, true}, {debug_info, true}, {warnings_as_errors, false}],
CompilerOpts = #{docs => true, ignore_module_conflict => false,
debug_info => true, warnings_as_errors => false},
{ok, [[Home] | _]} = init:get_argument(home),
Config = [{at_exit, []},
{home, unicode:characters_to_binary(Home, Encoding, Encoding)},
{compiler_options, orddict:from_list(CompilerOpts)}
{compiler_options, CompilerOpts}
| URIConfig],
Tab = elixir_config:new(Config),
case elixir_sup:start_link() of
@@ -73,7 +93,6 @@ start(_Type, _Args) ->
stop(Tab) ->
elixir_config:delete(Tab).
config_change(_Changed, _New, _Remove) ->
ok.
@@ -173,8 +192,8 @@ eval_forms(Tree, Binding, Opts) when is_list(Opts) ->
eval_forms(Tree, Binding, E) ->
eval_forms(Tree, Binding, E, elixir_env:env_to_scope(E)).
eval_forms(Tree, Binding, Env, Scope) ->
{ParsedBinding, ParsedScope} = elixir_scope:load_binding(Binding, Scope),
ParsedEnv = Env#{vars := [K || {K, _} <- ParsedScope#elixir_scope.vars]},
{ParsedBinding, ParsedVars, ParsedScope} = elixir_scope:load_binding(Binding, Scope),
ParsedEnv = Env#{vars := ParsedVars},
{Erl, NewEnv, NewScope} = quoted_to_erl(Tree, ParsedEnv, ParsedScope),
case Erl of
@@ -233,6 +252,7 @@ quoted_to_erl(Quoted, Env, Scope) ->
string_to_quoted(String, StartLine, File, Opts) when is_integer(StartLine), is_binary(File) ->
case elixir_tokenizer:tokenize(String, StartLine, [{file, File}|Opts]) of
{ok, _Line, _Column, Tokens} ->
put(elixir_parser_file, File),
try elixir_parser:parse(Tokens) of
{ok, Forms} -> {ok, Forms};
{error, {{Line, _, _}, _, [Error, Token]}} -> {error, {Line, to_binary(Error), to_binary(Token)}};
@@ -240,6 +260,8 @@ string_to_quoted(String, StartLine, File, Opts) when is_integer(StartLine), is_b
catch
{error, {{Line, _, _}, _, [Error, Token]}} -> {error, {Line, to_binary(Error), to_binary(Token)}};
{error, {Line, _, [Error, Token]}} -> {error, {Line, to_binary(Error), to_binary(Token)}}
after
erase(elixir_parser_file)
end;
{error, {Line, {ErrorPrefix, ErrorSuffix}, Token}, _Rest, _SoFar} ->
{error, {Line, {to_binary(ErrorPrefix), to_binary(ErrorSuffix)}, to_binary(Token)}};
+7 -5
View File
@@ -1,21 +1,23 @@
-define(m(M, K), maps:get(K, M)).
-define(ann(Opts), elixir_utils:get_ann(Opts)).
-define(line(Opts), elixir_utils:get_line(Opts)).
-define(generated, [{generated, true}, {location, 0}]).
-record(elixir_scope, {
context=nil, %% can be match, guards or nil
extra=nil, %% extra information about the context, like fn_match and map_key
noname=false, %% when true, don't add new names (used by try)
extra=nil, %% extra information about the context, like pin_guard and map_key
super=false, %% when true, it means super was invoked
caller=false, %% when true, it means caller was invoked
module=nil, %% the current module
function=nil, %% the current function
vars=[], %% a dict of defined variables and their alias
vars=#{}, %% a map of defined variables and their alias
backup_vars=nil, %% a copy of vars to be used on ^var
match_vars=nil, %% a set of all variables defined in a particular match
export_vars=nil, %% a dict of all variables defined in a particular clause
extra_guards=nil, %% extra guards from args expansion
counter=[], %% a dict counting the variables defined
file=(<<"nofile">>) %% the current scope filename
counter=#{}, %% a map counting the variables defined
file=(<<"nofile">>), %% the current scope filename
safe_by_default=false %% if case/cond/receive variables should be marked as safe by default
}).
-record(elixir_quote, {
+49 -4
View File
@@ -81,7 +81,11 @@ ensure_loaded(Meta, Ref, E) ->
catch
error:undef ->
Kind = case lists:member(Ref, ?m(E, context_modules)) of
true -> scheduled_module;
true ->
case ?m(E, module) of
Ref -> circular_module;
_ -> scheduled_module
end;
false -> unloaded_module
end,
elixir_errors:form_error(Meta, ?m(E, file), ?MODULE, {Kind, Ref})
@@ -137,8 +141,49 @@ lookup(Else, Dict, Counter) ->
%% Errors
format_error({unloaded_module, Module}) ->
io_lib:format("module ~ts is not loaded and could not be found", [elixir_aliases:inspect(Module)]);
io_lib:format("module ~ts is not loaded and could not be found", [inspect(Module)]);
format_error({scheduled_module, Module}) ->
io_lib:format("module ~ts is not loaded but was defined. This happens because you are trying to use a module in the same context it is defined. Try defining the module outside the context that requires it.",
[inspect(Module)]).
io_lib:format(
"module ~ts is not loaded but was defined. This happens when you depend on "
"a module in the same context it is defined. For example:\n"
"\n"
" defmodule MyApp do\n"
" defmodule Mod do\n"
" end\n"
"\n"
" use Mod\n"
" end\n"
"\n"
"Try defining the module outside the context that uses it:\n"
"\n"
" defmodule MyApp.Mod do\n"
" end\n"
"\n"
" defmodule MyApp do\n"
" use MyApp.Mod\n"
" end\n"
"\n"
"If the module is defined at the top-level and you are trying to "
"use it at the top-level, such is not supported by Elixir",
[inspect(Module)]);
format_error({circular_module, Module}) ->
io_lib:format(
"you are trying to use the module ~ts which is currently being defined.\n"
"\n"
"This may happen if you accidentally override the module you want to use. For example:\n"
"\n"
" defmodule MyApp do\n"
" defmodule Supervisor do\n"
" use Supervisor\n"
" end\n"
" end\n"
"\n"
"In the example above, the new Supervisor conflicts with Elixir's. "
"This may be fixed by using the fully qualified name on definition:\n"
"\n"
" defmodule MyApp.Supervisor do\n"
" use Supervisor\n"
" end\n",
[inspect(Module)]).
+6 -5
View File
@@ -147,7 +147,7 @@ translate(Meta, Args, S) ->
build_bitstr(Fun, Exprs, Meta, S) ->
{Final, FinalS} = build_bitstr_each(Fun, Exprs, Meta, S, []),
{{bin, ?line(Meta), lists:reverse(Final)}, FinalS}.
{{bin, ?ann(Meta), lists:reverse(Final)}, FinalS}.
build_bitstr_each(_Fun, [], _Meta, S, Acc) ->
{Acc, S};
@@ -165,11 +165,11 @@ build_bitstr_each(Fun, T, Meta, S, Acc, H, default, Types) when is_binary(H) ->
true ->
%% See explanation in elixir_utils:elixir_to_erl/1 to know
%% why we can simply convert the binary to a list.
{bin_element, ?line(Meta), {string, 0, binary_to_list(H)}, default, default};
{bin_element, ?ann(Meta), {string, 0, binary_to_list(H)}, default, default};
false ->
case types_require_conversion(Types) of
true ->
{bin_element, ?line(Meta), {string, 0, elixir_utils:characters_to_list(H)}, default, Types};
{bin_element, ?ann(Meta), {string, 0, elixir_utils:characters_to_list(H)}, default, Types};
false ->
elixir_errors:compile_error(Meta, S#elixir_scope.file, "invalid types for literal string in <<>>. "
"Accepted types are: little, big, utf8, utf16, utf32, bits, bytes, binary, bitstring")
@@ -192,10 +192,10 @@ build_bitstr_each(Fun, T, Meta, S, Acc, H, Size, Types) ->
{bin, _, Elements} ->
case (Size == default) andalso types_allow_splice(Types, Elements) of
true -> build_bitstr_each(Fun, T, Meta, NS, lists:reverse(Elements, Acc));
false -> build_bitstr_each(Fun, T, Meta, NS, [{bin_element, ?line(Meta), Expr, Size, Types}|Acc])
false -> build_bitstr_each(Fun, T, Meta, NS, [{bin_element, ?ann(Meta), Expr, Size, Types}|Acc])
end;
_ ->
build_bitstr_each(Fun, T, Meta, NS, [{bin_element, ?line(Meta), Expr, Size, Types}|Acc])
build_bitstr_each(Fun, T, Meta, NS, [{bin_element, ?ann(Meta), Expr, Size, Types}|Acc])
end.
types_require_conversion([End|T]) when End == little; End == big -> types_require_conversion(T);
@@ -224,6 +224,7 @@ elem_size({bin_element, _, _, {integer, _, Size}, Types}) -> {Size, unit_size(Ty
elem_size({bin_element, _, _, _Size, Types}) -> {unknown, unit_size(Types, 1)}.
unit_size([binary|T], _) -> unit_size(T, 8);
unit_size([bytes|T], _) -> unit_size(T, 8);
unit_size([{unit, Size}|_], _) -> Size;
unit_size([_|T], Guess) -> unit_size(T, Guess);
unit_size([], Guess) -> Guess.
+49 -54
View File
@@ -1,7 +1,7 @@
%% Handle code related to args, guard and -> matching for case,
%% fn, receive and friends. try is handled in elixir_try.
-module(elixir_clauses).
-export([match/3, clause/6, clauses/3, guards/4, get_pairs/3, get_pairs/4,
-export([match/3, clause/6, clauses/3, guards/3, get_pairs/3, get_pairs/4,
extract_splat_guards/1, extract_guards/1]).
-include("elixir.hrl").
@@ -24,33 +24,34 @@ get_pairs(Key, Clauses, As, AllowNil) ->
match(Fun, Args, #elixir_scope{context=Context, match_vars=MatchVars,
backup_vars=BackupVars, vars=Vars} = S) when Context /= match ->
{Result, NewS} = match(Fun, Args, S#elixir_scope{context=match,
match_vars=ordsets:new(), backup_vars=Vars}),
match_vars=#{}, backup_vars=Vars}),
{Result, NewS#elixir_scope{context=Context,
match_vars=MatchVars, backup_vars=BackupVars}};
match(Fun, Args, S) -> Fun(Args, S).
%% Translate clauses with args, guards and expressions
clause(Line, Fun, Args, Expr, Guards, S) when is_integer(Line) ->
clause(Meta, Fun, Args, Expr, Guards, S) when is_list(Meta) ->
{TArgs, SA} = match(Fun, Args, S#elixir_scope{extra_guards=[]}),
{TExpr, SE} = elixir_translator:translate(Expr, SA#elixir_scope{extra_guards=nil}),
{TExpr, SE} = elixir_translator:translate(Expr,
SA#elixir_scope{extra_guards=nil, export_vars=S#elixir_scope.export_vars}),
Extra = SA#elixir_scope.extra_guards,
TGuards = guards(Line, Guards, Extra, SA),
{{clause, Line, TArgs, TGuards, unblock(TExpr)}, SE}.
TGuards = guards(Guards, Extra, SA),
{{clause, ?ann(Meta), TArgs, TGuards, unblock(TExpr)}, SE}.
% Translate/Extract guards from the given expression.
guards(Line, Guards, Extra, S) ->
guards(Guards, Extra, S) ->
SG = S#elixir_scope{context=guard, extra_guards=nil},
case Guards of
[] -> case Extra of [] -> []; _ -> [Extra] end;
_ -> [translate_guard(Line, Guard, Extra, SG) || Guard <- Guards]
_ -> [translate_guard(Guard, Extra, SG) || Guard <- Guards]
end.
translate_guard(Line, Guard, Extra, S) ->
[element(1, elixir_translator:translate(elixir_quote:linify(Line, Guard), S))|Extra].
translate_guard(Guard, Extra, S) ->
[element(1, elixir_translator:translate(Guard, S))|Extra].
extract_guards({'when', _, [Left, Right]}) -> {Left, extract_or_guards(Right)};
extract_guards(Else) -> {Else, []}.
@@ -69,7 +70,7 @@ extract_splat_guards(Else) ->
% Function for translating macros with match style like case and receive.
clauses(Meta, Clauses, #elixir_scope{export_vars=CV} = S) ->
{TC, TS} = do_clauses(Meta, Clauses, S#elixir_scope{export_vars=[]}),
{TC, TS} = do_clauses(Meta, Clauses, S#elixir_scope{export_vars=#{}}),
{TC, TS#elixir_scope{export_vars=elixir_scope:merge_opt_vars(CV, TS#elixir_scope.export_vars)}}.
do_clauses(_Meta, [], S) ->
@@ -79,7 +80,7 @@ do_clauses(Meta, DecoupledClauses, S) ->
% Transform tree just passing the variables counter forward
% and storing variables defined inside each clause.
Transformer = fun(X, {SAcc, VAcc}) ->
{TX, TS} = each_clause(Meta, X, SAcc),
{TX, TS} = each_clause(X, SAcc),
{TX, {elixir_scope:mergec(S, TS), [TS#elixir_scope.export_vars|VAcc]}}
end,
@@ -88,7 +89,7 @@ do_clauses(Meta, DecoupledClauses, S) ->
% Now get all the variables defined inside each clause
CV = lists:reverse(ReverseCV),
AllVars = lists:foldl(fun elixir_scope:merge_vars/2, [], CV),
AllVars = lists:foldl(fun elixir_scope:merge_vars/2, #{}, CV),
% Create a new scope that contains a list of all variables
% defined inside all the clauses. It returns this new scope and
@@ -97,18 +98,18 @@ do_clauses(Meta, DecoupledClauses, S) ->
% is the old pointer.
{FinalVars, FS} = lists:mapfoldl(fun({Key, Val}, Acc) ->
normalize_vars(Key, Val, Acc)
end, TS, AllVars),
end, TS, maps:to_list(AllVars)),
% Expand all clauses by adding a match operation at the end
% that defines variables missing in one clause to the others.
expand_clauses(?line(Meta), TClauses, CV, FinalVars, [], FS).
expand_clauses(?ann(Meta), TClauses, CV, FinalVars, [], FS).
expand_clauses(Line, [Clause|T], [ClauseVars|V], FinalVars, Acc, S) ->
expand_clauses(Ann, [Clause|T], [ClauseVars|V], FinalVars, Acc, S) ->
case generate_match_vars(FinalVars, ClauseVars, [], []) of
{[], []} ->
expand_clauses(Line, T, V, FinalVars, [Clause|Acc], S);
expand_clauses(Ann, T, V, FinalVars, [Clause|Acc], S);
{Left, Right} ->
MatchExpr = generate_match(Line, Left, Right),
MatchExpr = generate_match(Ann, Left, Right),
ClauseExprs = element(5, Clause),
[Final|RawClauseExprs] = lists:reverse(ClauseExprs),
@@ -122,8 +123,8 @@ expand_clauses(Line, [Clause|T], [ClauseVars|V], FinalVars, Acc, S) ->
{[UserVar, MatchExpr, Final|RawClauseExprs], S};
_ ->
{VarName, _, SS} = elixir_scope:build_var('_', S),
StorageVar = {var, Line, VarName},
StorageExpr = {match, Line, StorageVar, Final},
StorageVar = {var, Ann, VarName},
StorageExpr = {match, Ann, StorageVar, Final},
{[StorageVar, MatchExpr, StorageExpr|RawClauseExprs], SS}
end;
false ->
@@ -131,34 +132,22 @@ expand_clauses(Line, [Clause|T], [ClauseVars|V], FinalVars, Acc, S) ->
end,
FinalClause = setelement(5, Clause, lists:reverse(FinalClauseExprs)),
expand_clauses(Line, T, V, FinalVars, [FinalClause|Acc], FS)
expand_clauses(Ann, T, V, FinalVars, [FinalClause|Acc], FS)
end;
expand_clauses(_Line, [], [], _FinalVars, Acc, S) ->
expand_clauses(_Ann, [], [], _FinalVars, Acc, S) ->
{lists:reverse(Acc), S}.
% Handle each key/value clause pair and translate them accordingly.
each_clause(Export, {match, Meta, [Condition], Expr}, S) ->
Fun = wrap_export_fun(Export, fun elixir_translator:translate_args/2),
each_clause({match, Meta, [Condition], Expr}, S) ->
{Arg, Guards} = extract_guards(Condition),
clause(?line(Meta), Fun, [Arg], Expr, Guards, S);
clause(Meta, fun elixir_translator:translate_args/2, [Arg], Expr, Guards, S);
each_clause(Export, {expr, Meta, [Condition], Expr}, S) ->
{TCondition, SC} = (wrap_export_fun(Export, fun elixir_translator:translate/2))(Condition, S),
{TExpr, SB} = elixir_translator:translate(Expr, SC),
{{clause, ?line(Meta), [TCondition], [], unblock(TExpr)}, SB}.
wrap_export_fun(Meta, Fun) ->
case lists:keyfind(export_head, 1, Meta) of
{export_head, true} ->
Fun;
_ ->
fun(Args, S) ->
{TArgs, TS} = Fun(Args, S),
{TArgs, TS#elixir_scope{export_vars = S#elixir_scope.export_vars}}
end
end.
each_clause({expr, Meta, [Condition], Expr}, S) ->
{TCondition, SC} = elixir_translator:translate(Condition, S),
{TExpr, SB} = elixir_translator:translate(Expr, SC#elixir_scope{export_vars = S#elixir_scope.export_vars}),
{{clause, ?ann(Meta), [TCondition], [], unblock(TExpr)}, SB}.
% Check if the given expression is a match tuple.
% This is a small optimization to allow us to change
@@ -184,16 +173,22 @@ has_match_tuple(_) -> false.
% by picking one value as reference and retrieving
% its previous value.
normalize_vars(Key, Value, #elixir_scope{vars=Vars, export_vars=ClauseVars} = S) ->
VS = S#elixir_scope{
vars=orddict:store(Key, Value, Vars),
export_vars=orddict:store(Key, Value, ClauseVars)
},
normalize_vars(Key, {Ref, Counter, _Safe},
#elixir_scope{vars=Vars, export_vars=ClauseVars, safe_by_default=SafeDefault} = S) ->
{Expr, Safe} =
case maps:find(Key, Vars) of
{ok, {PrevRef, _, _}} ->
{{var, 0, PrevRef}, true};
error ->
{{atom, 0, nil}, SafeDefault}
end,
Expr = case orddict:find(Key, Vars) of
{ok, {PreValue, _}} -> {var, 0, PreValue};
error -> {atom, 0, nil}
end,
Value = {Ref, Counter, Safe},
VS = S#elixir_scope{
vars=maps:put(Key, Value, Vars),
export_vars=maps:put(Key, Value, ClauseVars)
},
{{Key, Value, Expr}, VS}.
@@ -201,7 +196,7 @@ normalize_vars(Key, Value, #elixir_scope{vars=Vars, export_vars=ClauseVars} = S)
% or not and assigning the previous value.
generate_match_vars([{Key, Value, Expr}|T], ClauseVars, Left, Right) ->
case orddict:find(Key, ClauseVars) of
case maps:find(Key, ClauseVars) of
{ok, Value} ->
generate_match_vars(T, ClauseVars, Left, Right);
{ok, Clause} ->
@@ -216,11 +211,11 @@ generate_match_vars([{Key, Value, Expr}|T], ClauseVars, Left, Right) ->
generate_match_vars([], _ClauseVars, Left, Right) ->
{Left, Right}.
generate_match(Line, [Left], [Right]) ->
{match, Line, Left, Right};
generate_match(Ann, [Left], [Right]) ->
{match, Ann, Left, Right};
generate_match(Line, LeftVars, RightVars) ->
{match, Line, {tuple, Line, LeftVars}, {tuple, Line, RightVars}}.
generate_match(Ann, LeftVars, RightVars) ->
{match, Ann, {tuple, Ann, LeftVars}, {tuple, Ann, RightVars}}.
unblock({'block', _, Exprs}) -> Exprs;
unblock(Exprs) -> [Exprs].
+21 -21
View File
@@ -6,10 +6,10 @@
-define(timeout, 30000).
-record(elixir_code_server, {
loaded=[],
loaded=#{},
mod_pool={[], 0},
mod_ets=dict:new(),
compilation_status=[]
mod_ets=#{},
compilation_status=#{}
}).
call(Args) ->
@@ -42,24 +42,24 @@ handle_call({undefmodule, Ref}, _From, Config) ->
handle_call({acquire, Path}, From, Config) ->
Current = Config#elixir_code_server.loaded,
case orddict:find(Path, Current) of
case maps:find(Path, Current) of
{ok, true} ->
{reply, loaded, Config};
{ok, {Ref, List}} when is_list(List), is_reference(Ref) ->
Queued = orddict:store(Path, {Ref, [From|List]}, Current),
Queued = maps:put(Path, {Ref, [From|List]}, Current),
{reply, {queued, Ref}, Config#elixir_code_server{loaded=Queued}};
error ->
Queued = orddict:store(Path, {make_ref(), []}, Current),
Queued = maps:put(Path, {make_ref(), []}, Current),
{reply, proceed, Config#elixir_code_server{loaded=Queued}}
end;
handle_call(loaded, _From, Config) ->
{reply, [F || {F, true} <- Config#elixir_code_server.loaded], Config};
{reply, [F || {F, true} <- maps:to_list(Config#elixir_code_server.loaded)], 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),
CompilationStatusList = Config#elixir_code_server.compilation_status,
CompilationStatusListNew = maps:remove(CompilerPid, CompilationStatusList),
CompilationStatus = maps:get(CompilerPid, CompilationStatusList),
{reply, CompilationStatus,
Config#elixir_code_server{compilation_status=CompilationStatusListNew}};
@@ -76,35 +76,35 @@ handle_call(Request, _From, Config) ->
handle_cast({register_warning, CompilerPid}, Config) ->
CompilationStatusCurrent = Config#elixir_code_server.compilation_status,
CompilationStatusNew = orddict:store(CompilerPid, error, CompilationStatusCurrent),
CompilerOptions = elixir_config:get(compiler_options),
case orddict:find(warnings_as_errors, CompilerOptions) of
CompilationStatusNew = maps:put(CompilerPid, error, CompilationStatusCurrent),
CompilerOptions = elixir_config:get(compiler_options),
case maps:find(warnings_as_errors, CompilerOptions) of
{ok, true} -> {noreply, Config#elixir_code_server{compilation_status=CompilationStatusNew}};
_ -> {noreply, Config}
end;
handle_cast({reset_warnings, CompilerPid}, Config) ->
CompilationStatusCurrent = Config#elixir_code_server.compilation_status,
CompilationStatusNew = orddict:store(CompilerPid, ok, CompilationStatusCurrent),
CompilationStatusNew = maps:put(CompilerPid, ok, CompilationStatusCurrent),
{noreply, Config#elixir_code_server{compilation_status=CompilationStatusNew}};
handle_cast({loaded, Path}, Config) ->
Current = Config#elixir_code_server.loaded,
case orddict:find(Path, Current) of
case maps:find(Path, Current) of
{ok, true} ->
{noreply, Config};
{ok, {Ref, List}} when is_list(List), is_reference(Ref) ->
_ = [Pid ! {elixir_code_server, Ref, loaded} || {Pid, _Tag} <- lists:reverse(List)],
Done = orddict:store(Path, true, Current),
Done = maps:put(Path, true, Current),
{noreply, Config#elixir_code_server{loaded=Done}};
error ->
Done = orddict:store(Path, true, Current),
Done = maps:put(Path, true, Current),
{noreply, Config#elixir_code_server{loaded=Done}}
end;
handle_cast({unload_files, Files}, Config) ->
Current = Config#elixir_code_server.loaded,
Unloaded = lists:foldl(fun(File, Acc) -> orddict:erase(File, Acc) end, Current, Files),
Unloaded = maps:without(Files, Current),
{noreply, Config#elixir_code_server{loaded=Unloaded}};
handle_cast({return_module_name, H}, #elixir_code_server{mod_pool={T, Counter}} = Config) ->
@@ -132,13 +132,13 @@ defmodule(Pid, Tuple, #elixir_code_server{mod_ets=ModEts} = Config) ->
ets:insert(elixir_modules, Tuple),
Ref = erlang:monitor(process, Pid),
Mod = erlang:element(1, Tuple),
{Ref, Config#elixir_code_server{mod_ets=dict:store(Ref, Mod, ModEts)}}.
{Ref, Config#elixir_code_server{mod_ets=maps:put(Ref, Mod, ModEts)}}.
undefmodule(Ref, #elixir_code_server{mod_ets=ModEts} = Config) ->
case dict:find(Ref, ModEts) of
case maps:find(Ref, ModEts) of
{ok, Mod} ->
ets:delete(elixir_modules, Mod),
Config#elixir_code_server{mod_ets=dict:erase(Ref, ModEts)};
Config#elixir_code_server{mod_ets=maps:remove(Ref, ModEts)};
error ->
Config
end.
+7 -9
View File
@@ -6,10 +6,10 @@
%% Public API
get_opt(Key) ->
Dict = elixir_config:get(compiler_options),
case lists:keyfind(Key, 1, Dict) of
false -> false;
{Key, Value} -> Value
Map = elixir_config:get(compiler_options),
case maps:find(Key, Map) of
{ok, Value} -> Value;
error -> false
end.
%% Compilation entry points.
@@ -67,7 +67,7 @@ eval_compilation(Forms, Vars, E) ->
{Result, EE}.
code_loading_compilation(Forms, Vars, #{line := Line} = E) ->
Dict = [{{Name, Kind}, {Value, 0}} || {Name, Kind, Value, _} <- Vars],
Dict = [{{Name, Kind}, {Value, 0, true}} || {Name, Kind, Value, _} <- Vars],
S = elixir_env:env_to_scope_with_vars(E, Dict),
{Expr, EE, _S} = elixir:quoted_to_erl(Forms, E, S),
@@ -207,9 +207,7 @@ no_auto_import() ->
core() ->
{ok, _} = application:ensure_all_started(elixir),
New = orddict:from_list([{docs, false}, {internal, true}]),
Merge = fun(_, _, Value) -> Value end,
Update = fun(Old) -> orddict:merge(Merge, Old, New) end,
Update = fun(Old) -> maps:merge(Old, #{docs => false, internal => true}) end,
_ = elixir_config:update(compiler_options, Update),
[core_file(File) || File <- core_main()].
@@ -233,8 +231,8 @@ core_main() ->
<<"lib/elixir/lib/macro.ex">>,
<<"lib/elixir/lib/code.ex">>,
<<"lib/elixir/lib/module/locals_tracker.ex">>,
<<"lib/elixir/lib/kernel/def.ex">>,
<<"lib/elixir/lib/kernel/typespec.ex">>,
<<"lib/elixir/lib/kernel/utils.ex">>,
<<"lib/elixir/lib/behaviour.ex">>,
<<"lib/elixir/lib/exception.ex">>,
<<"lib/elixir/lib/protocol.ex">>,
+25 -20
View File
@@ -34,7 +34,7 @@ delete_definition(Module, Tuple) ->
% Invoked by the wrap definition with the function abstract tree.
% Each function is then added to the function table.
store_definition(Line, Kind, CheckClauses, Call, Body, Pos) ->
store_definition(Line, Kind, CheckClauses, Call, Body, Pos) when is_integer(Line) ->
E = (elixir_locals:get_cached_env(Pos))#{line := Line},
{NameAndArgs, Guards} = elixir_clauses:extract_guards(Call),
@@ -52,6 +52,10 @@ store_definition(Line, Kind, CheckClauses, Call, Body, Pos) ->
%% Check if there is a file information in the definition.
%% If so, we assume this come from another source and
%% we need to linify taking into account keep line numbers.
%%
%% Line and File will always point to the caller. __ENV__.line
%% will always point to the quoted one and __ENV__.file will
%% always point to the one at @file or the quoted one.
{Location, Key} =
case elixir_utils:meta_location(Meta) of
{_, _} = KeepLocation -> {KeepLocation, keep};
@@ -70,20 +74,26 @@ store_definition(Line, Kind, CheckClauses, Call, Body, Pos) ->
store_definition(Line, Kind, CheckClauses, Name, Args, Guards, Body, KeepLocation, #{module := Module} = ER) ->
Arity = length(Args),
Tuple = {Name, Arity},
E = ER#{function := Tuple},
Location = retrieve_location(KeepLocation, Module),
E = case Location of
{F, _} -> ER#{function := Tuple, file := elixir_utils:characters_to_binary(F)};
nil -> ER#{function := Tuple}
end,
elixir_locals:record_definition(Tuple, Kind, Module),
Location = retrieve_location(KeepLocation, Module),
{Function, Defaults, Super} = translate_definition(Kind, Line, Name, Args, Guards, Body, E),
run_on_definition_callbacks(Kind, Line, Module, Name, Args, Guards, expr_from_body(Line, Body), E),
DefaultsLength = length(Defaults),
elixir_locals:record_defaults(Tuple, Kind, Module, DefaultsLength),
File = ?m(E, file),
compile_super(Module, Super, E),
check_previous_defaults(Line, Module, Name, Arity, Kind, DefaultsLength, E),
%% Retrieve the file before we changed it based on @file
File = ?m(ER, file),
store_each(CheckClauses, Kind, File, Location, Module, DefaultsLength, Function),
[store_each(false, Kind, File, Location, Module, 0,
default_function_for(Kind, Name, Default)) || Default <- Defaults],
@@ -94,15 +104,10 @@ store_definition(Line, Kind, CheckClauses, Name, Args, Guards, Body, KeepLocatio
%% @on_definition
run_on_definition_callbacks(Kind, Line, Module, Name, Args, Guards, Expr, E) ->
case elixir_compiler:get_opt(internal) of
true ->
ok;
_ ->
Env = elixir_env:linify({Line, E}),
Callbacks = 'Elixir.Module':get_attribute(Module, on_definition),
_ = [Mod:Fun(Env, Kind, Name, Args, Guards, Expr) || {Mod, Fun} <- Callbacks],
ok
end.
Env = elixir_env:linify({Line, E}),
Callbacks = elixir_module:get_attribute(Module, on_definition),
_ = [Mod:Fun(Env, Kind, Name, Args, Guards, Expr) || {Mod, Fun} <- Callbacks],
ok.
make_struct_available(def, Module, '__struct__', []) ->
case erlang:get(elixir_compiler_pid) of
@@ -133,10 +138,7 @@ retrieve_location(Location, Module) ->
end.
get_location_attribute(Module) ->
case elixir_compiler:get_opt(internal) of
true -> nil;
false -> 'Elixir.Module':get_attribute(Module, file)
end.
elixir_module:get_attribute(Module, file).
normalize_location(File) ->
elixir_utils:characters_to_list(elixir_utils:relative_to_cwd(File)).
@@ -156,7 +158,10 @@ translate_definition(Kind, Line, Name, Args, Guards, Body, E) when is_integer(Li
{EArgs, EGuards, EBody, _} = elixir_exp_clauses:def(fun elixir_def_defaults:expand/2,
Args, Guards, expr_from_body(Line, Body), E),
Body == nil andalso check_args_for_bodyless_clause(Line, EArgs, E),
case Body of
nil -> check_args_for_bodyless_clause(Line, EArgs, E);
_ -> ok
end,
S = elixir_env:env_to_scope(E),
{Unpacked, Defaults} = elixir_def_defaults:unpack(Kind, Name, EArgs, S),
@@ -170,8 +175,8 @@ translate_clause(nil, _Line, _Kind, _Args, [], _Body, _S) ->
translate_clause(nil, Line, Kind, _Args, _Guards, _Body, #elixir_scope{file=File}) ->
elixir_errors:form_error([{line, Line}], File, ?MODULE, {missing_do, Kind});
translate_clause(_, Line, Kind, Args, Guards, Body, S) ->
{TClause, TS} = elixir_clauses:clause(Line,
fun elixir_translator:translate_args/2, Args, Body, Guards, S),
{TClause, TS} = elixir_clauses:clause([{line, Line}],
fun elixir_translator:translate_args/2, Args, Body, Guards, S),
FClause = case is_macro(Kind) of
true ->
+4 -4
View File
@@ -29,14 +29,14 @@ unpack_each(Kind, Name, [{'\\\\', DefMeta, [Expr, _]}|T] = List, Acc, Clauses, S
{DefArgs, SA} = elixir_clauses:match(fun elixir_translator:translate_args/2, Base ++ Args, S),
{DefInvoke, _} = elixir_translator:translate_args(Base ++ Invoke, SA),
Line = ?line(DefMeta),
Ann = ?ann(DefMeta),
Call = {call, Line,
{atom, Line, name_for_kind(Kind, Name)},
Call = {call, Ann,
{atom, Ann, name_for_kind(Kind, Name)},
DefInvoke
},
Clause = {clause, Line, DefArgs, [], [Call]},
Clause = {clause, Ann, DefArgs, [], [Call]},
unpack_each(Kind, Name, T, [Expr|Acc], [Clause|Clauses], S);
unpack_each(Kind, Name, [H|T], Acc, Clauses, S) ->
+11 -9
View File
@@ -6,7 +6,7 @@
-define(attr, {elixir, overridable}).
setup(Module) ->
overridable(Module, []).
overridable(Module, #{}).
overridable(Module) ->
ets:lookup_element(elixir_module:data_table(Module), ?attr, 2).
@@ -18,7 +18,7 @@ overridable(Module, Value) ->
ensure_defined(Meta, Module, Tuple, S) ->
Overridable = overridable(Module),
case orddict:find(Tuple, Overridable) of
case maps:find(Tuple, Overridable) of
{ok, {_, _, _, _}} -> ok;
_ -> elixir_errors:form_error(Meta, S#elixir_scope.file, ?MODULE, {no_super, Module, Tuple})
end.
@@ -29,18 +29,20 @@ name(Module, Function) ->
name(Module, Function, overridable(Module)).
name(_Module, {Name, _} = Function, Overridable) ->
{Count, _, _, _} = orddict:fetch(Function, Overridable),
{Count, _, _, _} = maps:get(Function, Overridable),
elixir_utils:atom_concat([Name, " (overridable ", Count, ")"]).
%% Store
store(Module, Function, GenerateName) ->
Overridable = overridable(Module),
case orddict:fetch(Function, Overridable) of
{_Count, _Clause, _Neighbours, true} -> ok;
case maps:get(Function, Overridable) of
{_Count, _Clause, _Neighbours, true} ->
ok;
{Count, Clause, Neighbours, false} ->
overridable(Module, orddict:store(Function, {Count, Clause, Neighbours, true}, Overridable)),
{{{Name, Arity}, Kind, Line, File, _Check, Location, {Defaults, _HasBody, _LastDefaults}}, Clauses} = Clause,
overridable(Module, maps:put(Function, {Count, Clause, Neighbours, true}, Overridable)),
{{{Name, Arity}, Kind, Line, File, _Check,
Location, {Defaults, _HasBody, _LastDefaults}}, Clauses} = Clause,
{FinalKind, FinalName} = case GenerateName of
true -> {defp, name(Module, Function, Overridable)};
@@ -62,14 +64,14 @@ super(Module, Function) ->
store(Module, Function, true).
store_pending(Module) ->
_ = [store(Module, X, false) || {X, {_, _, _, false}} <- overridable(Module),
_ = [store(Module, X, false) || {X, {_, _, _, false}} <- maps:to_list(overridable(Module)),
not 'Elixir.Module':'defines?'(Module, X)],
ok.
%% Error handling
format_error({no_super, Module, {Name, Arity}}) ->
Bins = [format_fa(X) || {X, {_, _, _, _}} <- overridable(Module)],
Bins = [format_fa(X) || {X, {_, _, _, _}} <- maps:to_list(overridable(Module))],
Joined = 'Elixir.Enum':join(Bins, <<", ">>),
io_lib:format("no super defined for ~ts/~B in module ~ts. Overridable functions available are: ~ts",
[Name, Arity, elixir_aliases:inspect(Module), Joined]).
+7 -7
View File
@@ -9,9 +9,9 @@ new() ->
file => <<"nofile">>, %% the current filename
line => 1, %% the current line
function => nil, %% the current function
context => nil, %% can be match_vars, guards or nil
context => nil, %% can be match, guard or nil
requires => [], %% a set with modules required
aliases => [], %% an orddict with aliases by new -> old names
aliases => [], %% a list of aliases by new -> old names
functions => [], %% a list with functions imported from module
macros => [], %% a list with macros imported from module
macro_aliases => [], %% keep aliases defined inside a macro
@@ -27,10 +27,10 @@ env_to_scope(#{module := Module, file := File, function := Function, context :=
#elixir_scope{module=Module, file=File, function=Function, context=Context}.
env_to_scope_with_vars(Env, Vars) ->
Map = maps:from_list(Vars),
(env_to_scope(Env))#elixir_scope{
vars=orddict:from_list(Vars),
counter=[{'_', length(Vars)}]
}.
vars=Map, counter=#{'_' => map_size(Map)}
}.
%% SCOPE MERGING
@@ -40,12 +40,12 @@ mergev(E1, E2) when is_list(E1) ->
E2#{
vars := merge_vars(E1, ?m(E2, vars)),
export_vars := merge_opt_vars(E1, ?m(E2, export_vars))
};
};
mergev(E1, E2) ->
E2#{
vars := merge_vars(?m(E1, vars), ?m(E2, vars)),
export_vars := merge_opt_vars(?m(E1, export_vars), ?m(E2, export_vars))
}.
}.
%% Receives two scopes and return the later scope
%% keeping the variables from the first (imports
+9 -1
View File
@@ -99,7 +99,6 @@ parse_erl_term(Term) ->
{ok, Parsed} = erl_parse:parse_term(Tokens ++ [{dot, 1}]),
Parsed.
%% Handle warnings and errors from Erlang land (called during module compilation)
%% Ignore on bootstrap
@@ -193,6 +192,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]),
do_raise(Line, File, 'Elixir.CompileError', elixir_utils:characters_to_binary(Message));
handle_file_error(File, {Line, erl_lint, {undefined_function, {F, A}}}) ->
Message = io_lib:format("undefined function ~ts/~B", [F, A]),
do_raise(Line, File, 'Elixir.CompileError', elixir_utils:characters_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", [elixir_aliases:inspect(M), F, A]),
do_raise(Line, File, 'Elixir.CompileError', elixir_utils:characters_to_binary(Message));
@@ -216,6 +219,11 @@ file_format(Line, File) ->
format_var(Var) ->
list_to_atom(lists:takewhile(fun(X) -> X /= $@ end, atom_to_list(Var))).
%% TODO: Remove this clause when we depend only on Erlang 19.
format_error(erl_lint, {bittype_mismatch, Val1, Val2, Kind}) ->
Desc = "conflict in ~s specification for bit field: '~p' and '~p'",
io_lib:format(Desc, [Kind, Val1, Val2]);
format_error([], Desc) ->
io_lib:format("~p", [Desc]);
+42 -7
View File
@@ -46,6 +46,20 @@ expand({'__aliases__', _, _} = Alias, E) ->
%% alias
expand({Kind, Meta, [{{'.', _, [Base, '{}']}, _, Refs} | Rest]}, E)
when Kind == alias; Kind == require; Kind == import ->
case Rest of
[] ->
expand_multi_alias_call(Kind, Meta, Base, Refs, [], E);
[Opts] ->
case lists:keymember(as, 1, Opts) of
true ->
compile_error(Meta, ?m(E, file),
":as option is not supported by multi-alias call");
false ->
expand_multi_alias_call(Kind, Meta, Base, Refs, Opts, E)
end
end;
expand({alias, Meta, [Ref]}, E) ->
expand({alias, Meta, [Ref, []]}, E);
expand({alias, Meta, [Ref, KV]}, E) ->
@@ -243,6 +257,11 @@ expand({'try', Meta, [KV]}, E) ->
expand({for, Meta, [_|_] = Args}, E) ->
elixir_for:expand(Meta, Args, E);
%% With
expand({with, Meta, [_|_] = Args}, E) ->
elixir_with:expand(Meta, Args, E);
%% Super
expand({super, Meta, Args}, E) when is_list(Args) ->
@@ -257,8 +276,8 @@ expand({'^', Meta, [Arg]}, #{context := match} = E) ->
{{Name, _, Kind} = EArg, EA} when is_atom(Name), is_atom(Kind) ->
{{'^', Meta, [EArg]}, EA};
_ ->
Msg = "invalid argument for unary operator ^, expected an existing variable, got: ^~ts",
compile_error(Meta, ?m(E, file), Msg, ['Elixir.Macro':to_string(Arg)])
Msg = "invalid argument for unary operator ^, expected an existing variable, got: ^~ts",
compile_error(Meta, ?m(E, file), Msg, ['Elixir.Macro':to_string(Arg)])
end;
expand({'^', Meta, [Arg]}, E) ->
compile_error(Meta, ?m(E, file),
@@ -362,6 +381,20 @@ expand(Other, E) ->
%% Helpers
expand_multi_alias_call(Kind, Meta, Base, Refs, Opts, E) ->
{BaseRef, EB} = expand_without_aliases_report(Base, E),
Fun = fun
({'__aliases__', _, Ref}, ER) ->
expand({Kind, Meta, [elixir_aliases:concat([BaseRef | Ref]), Opts]}, ER);
(Ref, ER) when is_atom(Ref) ->
expand({Kind, Meta, [elixir_aliases:concat([BaseRef, Ref]), Opts]}, ER);
(Other, _ER) ->
compile_error(Meta, ?m(E, file),
"invalid argument for ~ts, expected a compile time atom or alias, got: ~ts",
[atom_to_list(Kind), 'Elixir.Macro':to_string(Other)])
end,
lists:mapfoldl(Fun, EB, Refs).
expand_list([{'|', Meta, [_, _] = Args}], Fun, Acc, List) ->
{EArgs, EAcc} = lists:mapfoldl(Fun, Acc, Args),
expand_list([], Fun, EAcc, [{'|', Meta, EArgs}|List]);
@@ -483,7 +516,7 @@ expand_remote(Receiver, DotMeta, Right, Meta, Args, E, EL) ->
ok
end,
{EArgs, EA} = expand_args(Args, E),
{elixir_rewrite:rewrite(Receiver, DotMeta, Right, Meta, EArgs),
{elixir_rewrite:rewrite(Receiver, DotMeta, Right, Meta, EArgs, EA),
elixir_env:mergev(EL, EA)}.
%% Lexical helpers
@@ -586,9 +619,11 @@ expand_aliases({'__aliases__', Meta, _} = Alias, E, Report) ->
elixir_lexical:record_remote(Receiver, ?m(E, function), ?m(E, lexical_tracker)),
{Receiver, EA};
false ->
compile_error(Meta, ?m(E, file), "an alias must expand to an atom "
"at compilation time, but did not in \"~ts\". Use Module.concat/2 "
"if you want to dynamically generate aliases", ['Elixir.Macro':to_string(Alias)])
compile_error(Meta, ?m(E, file),
"invalid alias: \"~ts\". If you wanted to define an alias, an alias must expand "
"to an atom at compile time but it did not, you may use Module.concat/2 to build "
"it at runtime. If instead you wanted to invoke a function or access a field, "
"wrap the function or field name in double quotes", ['Elixir.Macro':to_string(Alias)])
end
end.
@@ -625,7 +660,7 @@ assert_no_guard_scope(Meta, _Kind, #{context := guard, file := File}) ->
assert_no_guard_scope(_Meta, _Kind, _E) -> [].
format_error({useless_literal, Term}) ->
io_lib:format("code block starting at line contains unused literal ~ts "
io_lib:format("code block contains unused literal ~ts "
"(remove the literal or assign it to _ to avoid warnings)",
['Elixir.Macro':to_string(Term)]);
format_error({useless_var, Var}) ->
+3 -13
View File
@@ -18,9 +18,9 @@ def(Fun, Args, Guards, Body, E) ->
clause(Meta, Kind, Fun, {'->', ClauseMeta, [_, _]} = Clause, E) when is_function(Fun, 3) ->
clause(Meta, Kind, fun(X, Acc) -> Fun(ClauseMeta, X, Acc) end, Clause, E);
clause(_Meta, _Kind, Fun, {'->', Meta, [Left, Right]}, E) ->
clause(_Meta, _Kind, Fun, {'->', Meta, [Left, Right]}, #{export_vars := ExportVars} = E) ->
{ELeft, EL} = Fun(Left, E),
{ERight, ER} = elixir_exp:expand(Right, EL),
{ERight, ER} = elixir_exp:expand(Right, EL#{export_vars := ExportVars}),
{{'->', Meta, [ELeft, ERight]}, ER};
clause(Meta, Kind, _Fun, _, E) ->
compile_error(Meta, ?m(E, file), "expected -> clauses in ~ts", [Kind]).
@@ -205,18 +205,8 @@ expand_one(Meta, Kind, Key, Fun) ->
%% Expands all -> pairs in a given key keeping the overall vars.
expand_with_export(Meta, Kind, Fun, {Key, Clauses}, Acc, E) when is_list(Clauses) ->
EFun =
case lists:keyfind(export_head, 1, Meta) of
{export_head, true} ->
Fun;
_ ->
fun(Args, #{export_vars := ExportVars} = EE) ->
{FArgs, FE} = Fun(Args, EE),
{FArgs, FE#{export_vars := ExportVars}}
end
end,
Transformer = fun(Clause, Vars) ->
{EClause, EC} = clause(Meta, Kind, EFun, Clause, E),
{EClause, EC} = clause(Meta, Kind, Fun, Clause, E),
{EClause, elixir_env:merge_vars(Vars, ?m(EC, export_vars))}
end,
{EClauses, EVars} = lists:mapfoldl(Transformer, Acc, Clauses),
+5 -5
View File
@@ -6,8 +6,8 @@
translate(Meta, Clauses, S) ->
Transformer = fun({'->', CMeta, [ArgsWithGuards, Expr]}, Acc) ->
{Args, Guards} = elixir_clauses:extract_splat_guards(ArgsWithGuards),
{TClause, TS } = elixir_clauses:clause(?line(CMeta), fun translate_fn_match/2,
Args, Expr, Guards, Acc),
{TClause, TS } = elixir_clauses:clause(CMeta, fun translate_fn_match/2,
Args, Expr, Guards, Acc),
{TClause, elixir_scope:mergef(S, TS)}
end,
@@ -16,15 +16,15 @@ translate(Meta, Clauses, S) ->
case lists:usort(Arities) of
[_] ->
{{'fun', ?line(Meta), {clauses, TClauses}}, NS};
{{'fun', ?ann(Meta), {clauses, TClauses}}, NS};
_ ->
compile_error(Meta, S#elixir_scope.file,
"cannot mix clauses with different arities in function definition")
end.
translate_fn_match(Arg, S) ->
{TArg, TS} = elixir_translator:translate_args(Arg, S#elixir_scope{backup_vars=orddict:new()}),
{TArg, TS#elixir_scope{backup_vars=S#elixir_scope.backup_vars}}.
{TArg, TS} = elixir_translator:translate_args(Arg, S#elixir_scope{extra=pin_guard}),
{TArg, TS#elixir_scope{extra=S#elixir_scope.extra}}.
%% Expansion
+102 -95
View File
@@ -53,30 +53,35 @@ translate(Meta, Args, Return, S) ->
{AccName, _, SA} = elixir_scope:build_var('_', S),
{VarName, _, SV} = elixir_scope:build_var('_', SA),
Line = ?line(Meta),
Acc = {var, Line, AccName},
Var = {var, Line, VarName},
Ann = ?ann(Meta),
Acc = {var, Ann, AccName},
Var = {var, Ann, VarName},
{Cases, [{do, Expr}|Opts]} = elixir_utils:split_last(Args),
{TInto, SI} =
case lists:keyfind(into, 1, Opts) of
{into, Into} -> elixir_translator:translate(Into, SV);
false when Return -> {{nil, Line}, SV};
false when Return -> {{nil, Ann}, SV};
false -> {false, SV}
end,
{TCases, SC} = translate_gen(Meta, Cases, [], SI),
{TExpr, SE} = elixir_translator:translate(Expr, SC),
{TExpr, SE} = elixir_translator:translate(wrap_expr(Expr, TInto), SC),
SF = elixir_scope:mergec(SI, SE),
case comprehension_expr(TInto, TExpr) of
{inline, TIntoExpr} ->
{build_inline(Line, TCases, TIntoExpr, TInto, Var, Acc, SE), SF};
{build_inline(Ann, TCases, TIntoExpr, TInto, Var, Acc, SE), SF};
{into, TIntoExpr} ->
build_into(Line, TCases, TIntoExpr, TInto, Var, Acc, SF)
build_into(Ann, TCases, TIntoExpr, TInto, Var, Acc, SF)
end.
%% In case we have no return, we wrap the expression
%% in a block that returns nil.
wrap_expr(Expr, false) -> {'__block__', [], [Expr, nil]};
wrap_expr(Expr, _) -> Expr.
translate_gen(ForMeta, [{'<-', Meta, [Left, Right]}|T], Acc, S) ->
{TLeft, TRight, TFilters, TT, TS} = translate_gen(Meta, Left, Right, T, S),
TAcc = [{enum, Meta, TLeft, TRight, TFilters}|Acc],
@@ -98,9 +103,14 @@ translate_gen(ForMeta, _, _, S) ->
translate_gen(_Meta, Left, Right, T, S) ->
{TRight, SR} = elixir_translator:translate(Right, S),
{TLeft, SL} = elixir_clauses:match(fun elixir_translator:translate/2, Left, SR),
{TT, {TFilters, TS}} = translate_filters(T, SL),
{TLeft, TRight, TFilters, TT, TS}.
{TLeft, SL} = elixir_clauses:match(fun elixir_translator:translate/2, Left,
SR#elixir_scope{extra=pin_guard, extra_guards=[]}),
ExtraGuards = [{nil, X} || X <- SL#elixir_scope.extra_guards],
SF = SL#elixir_scope{extra=S#elixir_scope.extra, extra_guards=nil},
{TT, {TFilters, TS}} = translate_filters(T, SF),
{TLeft, TRight, ExtraGuards ++ TFilters, TT, TS}.
translate_filters(T, S) ->
{Filters, Rest} = collect_filters(T, []),
@@ -125,56 +135,56 @@ collect_filters([H|T], Acc) ->
collect_filters([], Acc) ->
{Acc, []}.
build_inline(Line, Clauses, Expr, Into, _Var, Acc, S) ->
build_inline(Ann, Clauses, Expr, Into, _Var, Acc, S) ->
case lists:all(fun(Clause) -> element(1, Clause) == bin end, Clauses) of
true -> build_comprehension(Line, Clauses, Expr, Into);
true -> build_comprehension(Ann, Clauses, Expr, Into);
false -> build_reduce(Clauses, Expr, Into, Acc, S)
end.
build_into(Line, Clauses, Expr, Into, Fun, Acc, S) ->
{Kind, SK} = build_var(Line, S),
{Reason, SR} = build_var(Line, SK),
{Stack, ST} = build_var(Line, SR),
{Done, SD} = build_var(Line, ST),
build_into(Ann, Clauses, Expr, Into, Fun, Acc, S) ->
{Kind, SK} = build_var(Ann, S),
{Reason, SR} = build_var(Ann, SK),
{Stack, ST} = build_var(Ann, SR),
{Done, SD} = build_var(Ann, ST),
IntoExpr = {call, Line, Fun, [Acc, pair(Line, cont, Expr)]},
MatchExpr = {match, Line,
{tuple, Line, [Acc, Fun]},
elixir_utils:erl_call(Line, 'Elixir.Collectable', into, [Into])
IntoExpr = {call, Ann, Fun, [Acc, pair(Ann, cont, Expr)]},
MatchExpr = {match, Ann,
{tuple, Ann, [Acc, Fun]},
elixir_utils:erl_call(Ann, 'Elixir.Collectable', into, [Into])
},
TryExpr =
{'try', Line,
{'try', Ann,
[build_reduce_clause(Clauses, IntoExpr, Acc, Acc, SD)],
[{clause, Line,
[{clause, Ann,
[Done],
[],
[{call, Line, Fun, [Done, {atom, Line, done}]}]}],
[{clause, Line,
[{tuple, Line, [Kind, Reason, {var, Line, '_'}]}],
[{call, Ann, Fun, [Done, {atom, Ann, done}]}]}],
[{clause, Ann,
[{tuple, Ann, [Kind, Reason, {var, Ann, '_'}]}],
[],
[{match, Line, Stack, elixir_utils:erl_call(Line, erlang, get_stacktrace, [])},
{call, Line, Fun, [Acc, {atom, Line, halt}]},
elixir_utils:erl_call(Line, erlang, raise, [Kind, Reason, Stack])]}],
[{match, Ann, Stack, elixir_utils:erl_call(Ann, erlang, get_stacktrace, [])},
{call, Ann, Fun, [Acc, {atom, Ann, halt}]},
elixir_utils:erl_call(Ann, erlang, raise, [Kind, Reason, Stack])]}],
[]},
{{block, Line, [MatchExpr, TryExpr]}, SD}.
{{block, Ann, [MatchExpr, TryExpr]}, SD}.
%% Helpers
build_reduce(Clauses, Expr, false, Acc, S) ->
build_reduce_clause(Clauses, Expr, {nil, 0}, Acc, S);
build_reduce(Clauses, Expr, {nil, Line} = Into, Acc, S) ->
ListExpr = {cons, Line, Expr, Acc},
elixir_utils:erl_call(Line, lists, reverse,
build_reduce(Clauses, Expr, {nil, Ann} = Into, Acc, S) ->
ListExpr = {cons, Ann, Expr, Acc},
elixir_utils:erl_call(Ann, lists, reverse,
[build_reduce_clause(Clauses, ListExpr, Into, Acc, S)]);
build_reduce(Clauses, Expr, {bin, _, _} = Into, Acc, S) ->
{bin, Line, Elements} = Expr,
BinExpr = {bin, Line, [{bin_element, Line, Acc, default, [bitstring]}|Elements]},
{bin, Ann, Elements} = Expr,
BinExpr = {bin, Ann, [{bin_element, Ann, Acc, default, [bitstring]}|Elements]},
build_reduce_clause(Clauses, BinExpr, Into, Acc, S).
build_reduce_clause([{enum, Meta, Left, Right, Filters}|T], Expr, Arg, Acc, S) ->
Line = ?line(Meta),
Ann = ?ann(Meta),
True = build_reduce_clause(T, Expr, Acc, Acc, S),
False = Acc,
@@ -182,23 +192,23 @@ build_reduce_clause([{enum, Meta, Left, Right, Filters}|T], Expr, Arg, Acc, S) -
case is_var(Left) of
true -> [];
false ->
[{clause, -1,
[{var, Line, '_'}, Acc], [],
[{clause, ?generated,
[{var, Ann, '_'}, Acc], [],
[False]}]
end,
Clauses1 =
[{clause, Line,
[{clause, Ann,
[Left, Acc], [],
[join_filters(Line, Filters, True, False)]}|Clauses0],
[join_filters(Ann, Filters, True, False)]}|Clauses0],
Args = [Right, Arg, {'fun', Line, {clauses, Clauses1}}],
elixir_utils:erl_call(Line, 'Elixir.Enum', reduce, Args);
Args = [Right, Arg, {'fun', Ann, {clauses, Clauses1}}],
elixir_utils:erl_call(Ann, 'Elixir.Enum', reduce, Args);
build_reduce_clause([{bin, Meta, Left, Right, Filters}|T], Expr, Arg, Acc, S) ->
Line = ?line(Meta),
{Tail, ST} = build_var(Line, S),
{Fun, SF} = build_var(Line, ST),
Ann = ?ann(Meta),
{Tail, ST} = build_var(Ann, S),
{Fun, SF} = build_var(Ann, ST),
True = build_reduce_clause(T, Expr, Acc, Acc, SF),
False = Acc,
@@ -206,26 +216,26 @@ build_reduce_clause([{bin, Meta, Left, Right, Filters}|T], Expr, Arg, Acc, S) ->
{bin, _, Elements} = Left,
BinMatch =
{bin, Line, Elements ++ [{bin_element, Line, Tail, default, [bitstring]}]},
{bin, Ann, Elements ++ [{bin_element, Ann, Tail, default, [bitstring]}]},
NoVarMatch =
{bin, Line, no_var(Elements) ++ [{bin_element, Line, Tail, default, [bitstring]}]},
{bin, Ann, no_var(Elements) ++ [{bin_element, Ann, Tail, default, [bitstring]}]},
Clauses =
[{clause, Line,
[{clause, Ann,
[BinMatch, Acc], [],
[{call, Line, Fun, [Tail, join_filters(Line, Filters, True, False)]}]},
{clause, -1,
[{call, Ann, Fun, [Tail, join_filters(Ann, Filters, True, False)]}]},
{clause, ?generated,
[NoVarMatch, Acc], [],
[{call, Line, Fun, [Tail, False]}]},
{clause, -1,
[{bin, Line, []}, Acc], [],
[{call, Ann, Fun, [Tail, False]}]},
{clause, ?generated,
[{bin, Ann, []}, Acc], [],
[Acc]},
{clause, -1,
[Tail, {var, Line, '_'}], [],
[elixir_utils:erl_call(Line, erlang, error, [pair(Line, badarg, Tail)])]}],
{clause, ?generated,
[Tail, {var, Ann, '_'}], [],
[elixir_utils:erl_call(Ann, erlang, error, [pair(Ann, badarg, Tail)])]}],
{call, Line,
{named_fun, Line, element(3, Fun), Clauses},
{call, Ann,
{named_fun, Ann, element(3, Fun), Clauses},
[Right, Arg]};
build_reduce_clause([], Expr, _Arg, _Acc, _S) ->
@@ -234,31 +244,28 @@ build_reduce_clause([], Expr, _Arg, _Acc, _S) ->
is_var({var, _, _}) -> true;
is_var(_) -> false.
pair(Line, Atom, Arg) ->
{tuple, Line, [{atom, Line, Atom}, Arg]}.
pair(Ann, Atom, Arg) ->
{tuple, Ann, [{atom, Ann, Atom}, Arg]}.
build_var(Line, S) ->
build_var(Ann, S) ->
{Name, _, ST} = elixir_scope:build_var('_', S),
{{var, Line, Name}, ST}.
{{var, Ann, Name}, ST}.
no_var(Elements) ->
[{bin_element, Line, no_var_expr(Expr), Size, Types} ||
{bin_element, Line, Expr, Size, Types} <- Elements].
no_var_expr({var, Line, _}) ->
{var, Line, '_'}.
[{bin_element, Ann, no_var_expr(Expr), Size, Types} ||
{bin_element, Ann, Expr, Size, Types} <- Elements].
no_var_expr({var, Ann, _}) ->
{var, Ann, '_'}.
build_comprehension(Line, Clauses, Expr, false) ->
{block, Line, [
build_comprehension(Line, Clauses, Expr, {nil, Line}),
{nil, Line}
]};
build_comprehension(Line, Clauses, Expr, Into) ->
{comprehension_kind(Into), Line, Expr, comprehension_clause(Clauses)}.
build_comprehension(Ann, Clauses, Expr, false) ->
{lc, Ann, Expr, comprehension_clause(Clauses)};
build_comprehension(Ann, Clauses, Expr, Into) ->
{comprehension_kind(Into), Ann, Expr, comprehension_clause(Clauses)}.
comprehension_clause([{Kind, Meta, Left, Right, Filters}|T]) ->
Line = ?line(Meta),
[{comprehension_generator(Kind), Line, Left, Right}] ++
comprehension_filter(Line, Filters) ++
Ann = ?ann(Meta),
[{comprehension_generator(Kind), Ann, Left, Right}] ++
comprehension_filter(Ann, Filters) ++
comprehension_clause(T);
comprehension_clause([]) ->
[].
@@ -271,8 +278,8 @@ comprehension_generator(bin) -> b_generate.
comprehension_expr({bin, _, []}, {bin, _, _} = Expr) ->
{inline, Expr};
comprehension_expr({bin, Line, []}, Expr) ->
BinExpr = {bin, Line, [{bin_element, Line, Expr, default, [bitstring]}]},
comprehension_expr({bin, Ann, []}, Expr) ->
BinExpr = {bin, Ann, [{bin_element, Ann, Expr, default, [bitstring]}]},
{inline, BinExpr};
comprehension_expr({nil, _}, Expr) ->
{inline, Expr};
@@ -281,29 +288,29 @@ comprehension_expr(false, Expr) ->
comprehension_expr(_, Expr) ->
{into, Expr}.
comprehension_filter(Line, Filters) ->
[join_filter(Line, Filter, {atom, Line, true}, {atom, Line, false}) ||
comprehension_filter(Ann, Filters) ->
[join_filter(Ann, Filter, {atom, Ann, true}, {atom, Ann, false}) ||
Filter <- lists:reverse(Filters)].
join_filters(_Line, [], True, _False) ->
join_filters(_Ann, [], True, _False) ->
True;
join_filters(Line, [H|T], True, False) ->
join_filters(Ann, [H|T], True, False) ->
lists:foldl(fun(Filter, Acc) ->
join_filter(Line, Filter, Acc, False)
end, join_filter(Line, H, True, False), T).
join_filter(Ann, Filter, Acc, False)
end, join_filter(Ann, H, True, False), T).
join_filter(Line, {nil, Filter}, True, False) ->
{'case', Line, Filter, [
{clause, Line, [{atom, Line, true}], [], [True]},
{clause, Line, [{atom, Line, false}], [], [False]}
join_filter(Ann, {nil, Filter}, True, False) ->
{'case', Ann, Filter, [
{clause, Ann, [{atom, Ann, true}], [], [True]},
{clause, Ann, [{atom, Ann, false}], [], [False]}
]};
join_filter(Line, {Var, Filter}, True, False) ->
join_filter(Ann, {Var, Filter}, True, False) ->
Guard =
{op, Line, 'orelse',
{op, Line, '==', Var, {atom, Line, false}},
{op, Line, '==', Var, {atom, Line, nil}}},
{op, Ann, 'orelse',
{op, Ann, '==', Var, {atom, Ann, false}},
{op, Ann, '==', Var, {atom, Ann, nil}}},
{'case', Line, Filter, [
{clause, Line, [Var], [[Guard]], [False]},
{clause, Line, [{var, Line, '_'}], [], [True]}
{'case', Ann, Filter, [
{clause, Ann, [Var], [[Guard]], [False]},
{clause, Ann, [{var, Ann, '_'}], [], [True]}
]}.
+25 -5
View File
@@ -28,10 +28,12 @@ import(Meta, Ref, Opts, E) ->
{Functions, Macros}.
import_functions(Meta, Ref, Opts, E) ->
calculate(Meta, Ref, Opts, ?m(E, functions), E, fun() -> get_functions(Ref) end).
calculate(Meta, Ref, Opts, ?m(E, functions), ?m(E, file), fun() ->
get_functions(Ref)
end).
import_macros(Force, Meta, Ref, Opts, E) ->
calculate(Meta, Ref, Opts, ?m(E, macros), E, fun() ->
calculate(Meta, Ref, Opts, ?m(E, macros), ?m(E, file), fun() ->
case Force of
true -> get_macros(Meta, Ref, E);
false -> get_optional_macros(Ref)
@@ -49,13 +51,21 @@ record_warn(Meta, Ref, Opts, Added, E) ->
%% Calculates the imports based on only and except
calculate(Meta, Key, Opts, Old, E, Existing) ->
calculate(Meta, Key, Opts, Old, File, Existing) ->
New = case keyfind(only, Opts) of
{only, Only} when is_list(Only) ->
ok = ensure_keyword_list(Meta, File, Only, only),
case keyfind(except, Opts) of
false -> ok;
_ ->
elixir_errors:compile_error(Meta, File,
":only and :except can only be given together to import"
" when :only is either :functions or :macros")
end,
case Only -- get_exports(Key) of
[{Name, Arity}|_] ->
Tuple = {invalid_import, {Key, Name, Arity}},
elixir_errors:form_error(Meta, ?m(E, file), ?MODULE, Tuple);
elixir_errors:form_error(Meta, File, ?MODULE, Tuple);
_ ->
intersection(Only, Existing())
end;
@@ -63,6 +73,7 @@ calculate(Meta, Key, Opts, Old, E, Existing) ->
case keyfind(except, Opts) of
false -> remove_underscored(Existing());
{except, Except} when is_list(Except) ->
ok = ensure_keyword_list(Meta, File, Except, except),
case keyfind(Key, Old) of
false -> remove_underscored(Existing()) -- Except;
{Key, OldImports} -> OldImports -- Except
@@ -78,7 +89,7 @@ calculate(Meta, Key, Opts, Old, E, Existing) ->
[] ->
{false, keydelete(Key, Old)};
_ ->
ensure_no_special_form_conflict(Meta, ?m(E, file), Key, Final),
ensure_no_special_form_conflict(Meta, File, Key, Final),
{true, [{Key, Final}|keydelete(Key, Old)]}
end.
@@ -131,6 +142,14 @@ ensure_no_special_form_conflict(Meta, File, Key, [{Name, Arity}|T]) ->
ensure_no_special_form_conflict(_Meta, _File, _Key, []) -> ok.
ensure_keyword_list(_Meta, _File, [], _Kind) -> ok;
ensure_keyword_list(Meta, File, [{Key, _} | Rest], Kind) when is_atom(Key) ->
ensure_keyword_list(Meta, File, Rest, Kind);
ensure_keyword_list(Meta, File, _Other, Kind) ->
elixir_errors:compile_error(Meta, File, "invalid :~s option for import, expected a keyword list", [Kind]).
%% ERROR HANDLING
format_error({invalid_import, {Receiver, Name, Arity}}) ->
@@ -204,6 +223,7 @@ special_form('unquote_splicing', 1) -> true;
special_form('fn', _) -> true;
special_form('super', _) -> true;
special_form('for', _) -> true;
special_form('with', _) -> true;
special_form('cond', 1) -> true;
special_form('case', 2) -> true;
special_form('try', 2) -> true;
+13 -13
View File
@@ -67,20 +67,20 @@ translate_struct(Meta, Name, {'%{}', MapMeta, Args}, S) ->
if
TUpdate /= nil ->
Line = ?line(Meta),
Ann = ?ann(Meta),
{VarName, _, VS} = elixir_scope:build_var('_', US),
Var = {var, Line, VarName},
Map = {map, Line, [{map_field_exact, Line, {atom, Line, '__struct__'}, {atom, Line, Name}}]},
Var = {var, Ann, VarName},
Map = {map, Ann, [{map_field_exact, Ann, {atom, Ann, '__struct__'}, {atom, Ann, Name}}]},
Match = {match, Line, Var, Map},
Error = {tuple, Line, [{atom, Line, badstruct}, {atom, Line, Name}, Var]},
Match = {match, Ann, Var, Map},
Error = {tuple, Ann, [{atom, Ann, badstruct}, {atom, Ann, Name}, Var]},
{TMap, TS} = translate_map(MapMeta, Assocs, Var, VS),
{{'case', Line, TUpdate, [
{clause, Line, [Match], [], [TMap]},
{clause, Line, [Var], [], [elixir_utils:erl_call(Line, erlang, error, [Error])]}
{{'case', Ann, TUpdate, [
{clause, Ann, [Match], [], [TMap]},
{clause, Ann, [Var], [], [elixir_utils:erl_call(Ann, erlang, error, [Error])]}
]}, TS};
S#elixir_scope.context == match ->
translate_map(MapMeta, Assocs ++ [{'__struct__', Name}], nil, US);
@@ -151,15 +151,15 @@ wait_for_struct(Module) ->
translate_map(Meta, Assocs, TUpdate, #elixir_scope{extra=Extra} = S) ->
{Op, KeyFun, ValFun} = extract_key_val_op(TUpdate, S),
Line = ?line(Meta),
Ann = ?ann(Meta),
{TArgs, SA} = lists:mapfoldl(fun({Key, Value}, Acc) ->
{TKey, Acc1} = KeyFun(Key, Acc),
{TValue, Acc2} = ValFun(Value, Acc1#elixir_scope{extra=Extra}),
{{Op, ?line(Meta), TKey, TValue}, Acc2}
{{Op, ?ann(Meta), TKey, TValue}, Acc2}
end, S, Assocs),
build_map(Line, TUpdate, TArgs, SA).
build_map(Ann, TUpdate, TArgs, SA).
extract_assoc_update([{'|', _Meta, [Update, Args]}], S) ->
{TArg, SA} = elixir_translator:translate_arg(Update, S, S),
@@ -177,8 +177,8 @@ extract_key_val_op(TUpdate, S) ->
fun(X, Acc) -> elixir_translator:translate_arg(X, Acc, KS) end,
fun(X, Acc) -> elixir_translator:translate_arg(X, Acc, S) end}.
build_map(Line, nil, TArgs, SA) -> {{map, Line, TArgs}, SA};
build_map(Line, TUpdate, TArgs, SA) -> {{map, Line, TUpdate, TArgs}, SA}.
build_map(Ann, nil, TArgs, SA) -> {{map, Ann, TArgs}, SA};
build_map(Ann, TUpdate, TArgs, SA) -> {{map, Ann, TUpdate, TArgs}, SA}.
assert_struct_keys(Meta, Name, Struct, Assocs, S) ->
[begin

Some files were not shown because too many files have changed in this diff Show More