Compare commits

..
20 Commits
Author SHA1 Message Date
José Valim 5591ab2dff Update CHANGELOG.md 2016-10-29 10:41:14 +02:00
José Valim 59910fff71 Make prune part of the public API in the formatter
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2016-05-13 09:24:59 +02:00
José Valim f179a23675 Ensure poorly formatted chardata is pruned in Logger watcher
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2016-05-13 09:24:54 +02:00
José Valim bfa16ebf35 Support canonical URLs
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2016-02-21 21:36:27 +01:00
James Fish 4906beeb2d Fix remsh'ing to node with different beam files
Signed-off-by: James Fish <james@fishcakez.com>
2015-11-07 14:42:44 +00:00
José Valim 7ab6d2e982 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.

Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2015-10-30 11:31:29 -02:00
Eric Meadows-Jönsson ac502ebee2 inspect invalid ANSI sequences
Signed-off-by: Eric Meadows-Jönsson <eric.meadows.jonsson@gmail.com>
2015-10-05 17:36:26 -05:00
Luper Rouch 4fd0c4e013 Fix default value of :time option in File.Stat docs
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2015-10-01 16:42:35 +02:00
José Valim 05fecbcbcf Merge pull request #3811 from michalmuskala/optimize_map
Optimize Enum.map/2 for lists

Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2015-10-01 16:41:58 +02:00
José Valim 448f473d08 Merge pull request #3810 from lexmag/optimize-keyword-new
Optimize `Keyword.new/2` function

Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2015-10-01 16:39:06 +02:00
José Valim eb8d293ac5 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.

Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2015-09-29 17:28:48 +02:00
José Valim 59588ca424 Ensure we print unicode codepoints on errors, closes #3804
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2015-09-29 15:03:36 +02:00
José Valim 64ffd185cc Ensure we keep ordering and last properties in keyword
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2015-09-29 13:28:02 +02:00
José Valim 40beaf4f07 Add a test for umbrellas with build embedded
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2015-09-28 23:53:07 +02:00
José Valim 139d2eac59 Release v1.1.1 2015-09-28 23:26:38 +02:00
José Valim 84b33461f3 Fix non integer checks in ranges 2015-09-28 23:23:35 +02:00
José Valim e88a1b6738 Ensure stacktraces are shown on app_path failures
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2015-09-28 22:50:06 +02:00
CrowdHailer 2e4c7abe4d removed obsolete type option from the GenEvent handler type
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2015-09-28 22:48:19 +02:00
Eric Entin a6723dcbc6 Fix assertion errors with more than 1 pinned var
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2015-09-26 18:24:21 +02:00
José Valim e878ddee4c Update docs directory 2015-09-25 11:00:20 +02:00
318 changed files with 7205 additions and 14512 deletions
+4
View File
@@ -1,6 +1,10 @@
language: erlang
otp_release:
- 17.0
- 17.3
- 17.4
- 17.5
- 18.0
sudo: false
+166 -235
View File
@@ -1,282 +1,213 @@
# Changelog for Elixir v1.2
# Changelog for Elixir v1.1
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.
v1.1 brings enhancements, bug fixes, performance improvements and more
into Elixir.
## Erlang 18 support
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.
We have brought many features specific to Erlang 18. Here are the highlights:
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`.
* 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
On the deprecation side, this release includes one major deprecation and
some soft deprecations.
## Language improvements
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.
This release includes four notable 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).
* The addition of multi aliases/imports/require:
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.
alias MyApp.{Foo, Bar, Baz}
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.
* Support for variables in map keys:
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!
%{key => value}
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.
* Support for the pin operator in map keys and function clauses:
## v1.1.1 (2015-09-28)
%{^key => value} = %{key => value}
fn ^key -> :ok end
### Bug fixes
* Addition of the `with` special form to match on multiple expressions:
* [Enum] Fix non-integer member checks with ranges
* [ExUnit] Fix assertion errors with more than 1 pinned var
* [Mix] Ensure umbrella apps can boot with build_embedded is true
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.6 (2016-06-06)
### 1. Enhancements
* [Kernel] Support Erlang 19
* [Kernel] Supported generated: true in the `quote` special form
### 2. Bug fixes
* [Path] Fix a bug in path join with "/" followed by empty segments
* [String] Fix a bug in NFD normalization when followed by one-byte sized graphemes
* [Typespec] Correctly support `<<_::size, _::_*unit>>` syntax
## v1.2.5 (2016-04-30)
### 1. Bug fixes
* [Logger] Stringify truncated function data in Logger
* [Logger] Ensure poorly formatted char data can also be logged by using the replacement character "�" (diamond question mark)
* [Mix] Do not assume `@impl` is always a list
* [String] Fix bugs in `String.replace_*` functions where it would not include the accumulated value for certain replacements
## v1.2.4 (2016-04-01)
### 1. Enhancements
* [Mix] Add `:archives` configuration to `def project` that allows projects to list archive dependencies. `--no-archives-check` (as well as `--no-deps-check`) will disable the archive check. The `:archives` option is not checked for dependencies.
* [Mix] Add `deps.precompile` task as hook
* [Mix] Support `--include-children` in `mix deps.compile` option
* [String] Update version of the Unicode database to 8.0.0
### 2. Bug fixes
* [Application] Ensure `spec/2` returns nil for unknown applications
* [Integer] Fix a possible binary leak in `parse/1`
* [Mix] Purge Erlang modules on recompilation
* [String] Ensure `split/1` does not break on non-breakable whitespace
* [String] Ensure NFC and NFD normalization pass all of Unicode 8.0.0 tests
* [Version] Allow dots in build info for versions in `Version.parse/1`
## v1.2.3 (2016-02-21)
### 1. Enhancements
* [Base] Add `:ignore` and `:padding` option to encoding/decoding functions
* [Mix] Add `Mix.Projects.deps_paths` that returns the dependencies path as a map
### 2. Bug fixes
* [ExUnit] Do not provide negative line numbers without generated annotation (for compatibility with Erlang 19)
* [Mix] Reject non fullfilled optional dependencies later on in the convergence resolution for proper dependency sorting
* [String] Fix incomplete data trimming on both `String.replace_trailing` and `String.rstrip`
* [String] Attach debug_info back into Unicode modules for Dialyzer support
## v1.2.2 (2016-01-31)
### 1. Enhancements
* [Kernel] Support `@compile {:autoload, false}` to disable automatic loading after compilation
### 2. Bug fixes
* [ExUnit] Raise if trying to override reserved tag in `setup` blocks
* [Mix] Ensure retrieve compile manifests do fail if some compilers are not yet available
* [Mix] Automatically merge managers according to the mix > rebar3 > rebar > make order
* [Mix] Force recompilation if dependency was recently fetched
## 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)
## v1.1.0 (2015-09-25)
### 1. Enhancements
#### Elixir
* [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)
* [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
#### ExUnit
* [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
* [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
#### IEx
* [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
* [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
#### Logger
* [Logger] Add file to logger metadata
* [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
#### Mix
* [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`
* [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
### 2. Bug fixes
#### Kernel
#### Elixir
* [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
* [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
#### IEx
#### EEx
* [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] 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
* [EEx] Allow EEx interpolation to also apply inside quotations `<%%= ... %>`
#### ExUnit
* [ExUnit] Include file and line in all compilation errors for doctests
* [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
### 3. Soft deprecations (no warnings emitted)
#### Logger
#### Kernel
* [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`
* [Logger] Include metadata in `Logger.log/3`, use `Logger.bare_log/3` for runtime-only, with no metadata behaviour
#### Mix
* [Mix] `Mix.Utils.camelize/1` and `Mix.Utils.underscore/1` are soft deprecated in favor of `Macro.camelize/1` and `Macro.underscore/1`
* [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
## v1.1
### 3. Soft deprecations (no warnings emitted)
The CHANGELOG for v1.1 releases can be found [in the v1.1 branch](https://github.com/elixir-lang/elixir/blob/v1.1/CHANGELOG.md).
#### Elixir
* [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)
### 4. Deprecations
#### Elixir
* [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
## v1.0
The CHANGELOG for v1.0 releases can be found [in the v1.0 branch](https://github.com/elixir-lang/elixir/blob/v1.0/CHANGELOG.md).
+9
View File
@@ -0,0 +1,9 @@
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)
+27 -11
View File
@@ -1,6 +1,6 @@
REBAR ?= "$(CURDIR)/rebar"
PREFIX ?= /usr/local
DOCS := v1.2
DOCS := v1.1
CANONICAL := stable
ELIXIRC := bin/elixirc --verbose --ignore-module-conflict
ERLC := erlc -I lib/elixir/include
@@ -18,11 +18,13 @@ 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) >= "18"])' -s erlang halt | grep -q '^true'; \
if [ $$? != 0 ]; then \
echo "At least Erlang 18.0 is required to build Elixir"; \
exit 1; \
$(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; \
fi;
endef
@@ -41,7 +43,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): compile $(1)
test_$(1): $(1)
@ echo "==> $(1) (exunit)"
$(Q) cd lib/$(1) && ../../bin/elixir -r "test/test_helper.exs" -pr "test/**/*_test.exs";
endef
@@ -129,14 +131,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 "v$(VERSION)" $(call LOGO_PATH) -o doc/$(2) -a http://elixir-lang.org/docs/$(CANONICAL)/$(2)/ -p http://elixir-lang.org/docs.html $(4)
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 "v$(VERSION)" $(call LOGO_PATH) -o doc/$(2) -a http://elixir-lang.org/docs/$(CANONICAL)/$(2)/ -p http://elixir-lang.org/docs.html
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,-e "lib/elixir/pages/Typespecs.md" -e "lib/elixir/pages/Writing Documentation.md")
$(call COMPILE_DOCS,Elixir,elixir,Kernel)
docs_eex: compile ../ex_doc/bin/ex_doc
@ echo "==> ex_doc (eex)"
@@ -171,12 +173,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 CHANGELOG.md doc NOTICE LICENSE README.md
zip -9 -r Docs-v$(VERSION).zip doc
@ 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 lib/*/ebin LICENSE man NOTICE README.md VERSION
zip -9 -r Precompiled-v$(VERSION).zip bin CHANGELOG.md LEGAL lib/*/ebin LICENSE man README.md VERSION
@ echo "Precompiled file created $(CURDIR)/Precompiled-v$(VERSION).zip"
#==> Publish
@@ -187,6 +189,16 @@ 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
@@ -204,7 +216,11 @@ $(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_mix test_eex test_iex
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_stdlib: compile
@ echo "==> elixir (exunit)"
-22
View File
@@ -1,22 +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 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.
+16 -38
View File
@@ -1,15 +1,12 @@
![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
@@ -17,50 +14,32 @@ 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 18.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 17.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 18 [erts-7.0] [source] [smp:2:2] [async-threads:10] [hipe]
[kernel-poll:false]`
`Erlang/OTP 17 [erts-6.0] [source-07b8f44] [64-bit] [smp:4:4] [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
alongside Elixir.
Building the documentation requires [ExDoc](https://github.com/elixir-lang/ex_doc) to be installed and built in the same containing folder as Elixir.
```sh
# After cloning and compiling Elixir, in its parent directory:
# After cloning and compiling Elixir
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
@@ -75,15 +54,14 @@ We usually keep a list of features and bugs [in the issue tracker][2].
[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]: https://webchat.freenode.net/?channels=#elixir-lang
[6]: http://www.freenode.net
[5]: irc://chat.freenode.net/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.
Elixir source code is released under Apache 2 License with some parts under Erlang's license (EPL).
Check [NOTICE](NOTICE) and [LICENSE](LICENSE) files for more
information.
Check [LEGAL](LEGAL) and [LICENSE](LICENSE) files for more information.
+3 -1
View File
@@ -30,10 +30,12 @@ This document simply outlines the release process:
12. Create a new branch "vMAJOR.MINOR"
13. Move docs generation to `docs/vMAJOR.MINOR` in Makefile and copy them from `docs/stable` (change index.html accordingly)
13. Move docs generation to `docs/vMAJOR.MINOR` and copy them from `docs/stable`
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.2.6
1.1.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 exits
-v Prints version and exit
-e \"command\" Evaluates the given command (*)
-r \"file\" Requires the given files/patterns (*)
-S \"script\"   Finds and executes the given script
+2 -2
View File
@@ -1,4 +1,4 @@
@if defined ELIXIR_CLI_ECHO (@echo on) else (@echo off)
@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 exits
echo -v Prints version and exit
echo -e command Evaluates the given command (*)
echo -r file Requires the given files/patterns (*)
echo -S script Finds and executes the given script
+1 -1
View File
@@ -1,4 +1,4 @@
@if defined ELIXIR_CLI_ECHO (@echo on) else (@echo off)
@echo off
set argc=0
for %%A in (%*) do (
if "%%A"=="--help" goto documentation
+2 -4
View File
@@ -1,4 +1,2 @@
@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=
@echo off
call "%~dp0\elixir.bat" +iex --erl "-user Elixir.IEx.CLI" --no-halt %*
+2 -2
View File
@@ -1,2 +1,2 @@
@if defined ELIXIR_CLI_ECHO (@echo on) else (@echo off)
call "%~dp0\elixir.bat" "%~dp0\mix" %*
@echo off
call "%~dp0\elixir.bat" -e Mix.start -e Mix.CLI.main %*
+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 %>` translates to:
In other words, `<%= @foo %>` is simply translated to:
<%= {:ok, v} = Access.fetch(assigns, :foo); v %>
<%= Dict.get assigns, :foo %>
The assigns extension is useful when the number of variables
required by the template is not specified at compilation time.
+3 -21
View File
@@ -53,9 +53,6 @@ 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`:
@@ -67,26 +64,11 @@ 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: EEx.Engine.fetch_assign!(var!(assigns), unquote(name))
end
def handle_assign(arg) do
arg
quote line: line, do: Dict.get(var!(assigns), unquote(name))
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
def handle_assign(arg) do
arg
end
@doc """
-4
View File
@@ -236,10 +236,6 @@ 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 = """
+44 -90
View File
@@ -1,16 +1,15 @@
defmodule Access do
@moduledoc """
Key-based access to data structures via the `foo[bar]` syntax.
Dictionary-like access to data structures via the `foo[bar]` syntax.
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.
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`.
## Key-based lookups
## Examples
Out of the box, Access works with `Keyword` and `Map`:
Out of the box, Access works with built-in dictionaries: `Keyword`
and `Map`:
iex> keywords = [a: 1, b: 2]
iex> keywords[:a]
@@ -24,68 +23,13 @@ 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
Since Access is a behaviour, it can be implemented to key-value
data structures. The implementation should be added to the
module that defines the struct being access. 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
The key comparison must be implemented using the `===` operator.
"""
@type t :: list | map | nil
@@ -95,20 +39,6 @@ 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.
"""
@@ -117,27 +47,19 @@ 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) and is_atom(key) do
def fetch(list, key) when is_list(list) 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
@@ -169,9 +91,6 @@ 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
@@ -193,3 +112,38 @@ 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
+9 -22
View File
@@ -17,7 +17,7 @@ defmodule Agent do
defmodule Mix.TasksServer do
def start_link do
Agent.start_link(fn -> MapSet.new end, name: __MODULE__)
Agent.start_link(fn -> HashSet.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__, &MapSet.put(&1, item))
Agent.update(__MODULE__, &Set.put(&1, item))
end
@doc "Resets the executed tasks and returns the previous list of tasks"
@doc "Resets the executed tasks and return the previous list of tasks"
def take_all() do
Agent.get_and_update(__MODULE__, fn set ->
{Enum.into(set, []), MapSet.new}
{Enum.into(set, []), HashSet.new}
end)
end
end
@@ -295,25 +295,12 @@ defmodule Agent do
end
@doc """
Stops the agent with the given `reason`.
Stops the agent.
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.
Returns `:ok` if the agent is stopped within the given `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
@spec stop(agent, timeout) :: :ok
def stop(agent, timeout \\ 5000) do
GenServer.call(agent, :stop, timeout)
end
end
+15
View File
@@ -23,6 +23,10 @@ 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
@@ -39,6 +43,17 @@ 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
+3 -51
View File
@@ -44,6 +44,9 @@ 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
@@ -109,57 +112,6 @@ 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 supported keys. If the given
specification parameter does not exist, this function
will raise. Returns `nil` if the application is not loaded.
"""
@spec spec(app, key) :: value | nil
def spec(app, key) when key in @application_keys do
case :application.get_key(app, key) do
{:ok, value} -> value
:undefined -> nil
end
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`.
"""
+116 -378
View File
@@ -98,55 +98,49 @@ defmodule Base do
b32_alphabet = Enum.with_index 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567'
b32hex_alphabet = Enum.with_index '0123456789ABCDEFGHIJKLMNOPQRSTUV'
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}) ->
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}) ->
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: #{inspect <<c>>, binaries: :as_strings} (byte #{c})"
raise ArgumentError, "non-alphabet digit found: #{<<c>>}"
end
end
@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 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))
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>>}\" (byte #{char})")
do: raise(ArgumentError, "non-alphabet digit found: #{<<char>>}")
defp from_mixed(char) when char in ?a..?z,
do: char - (?a - ?A)
defp from_mixed(char),
do: char
defp maybe_pad(subject, false, _, _),
do: subject
defp maybe_pad(subject, _, group_size, pad) do
case rem(byte_size(subject), group_size) do
0 -> subject
x -> subject <> String.duplicate(pad, group_size - x)
end
end
@doc """
Encodes a binary string into a base 16 encoded string.
@@ -166,9 +160,10 @@ 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(case, data)
do_encode16(data, encode_case(case, &enc16/1))
end
@doc """
Decodes a base 16 encoded string into a binary string.
@@ -190,8 +185,9 @@ defmodule Base do
"""
@spec decode16(binary) :: {:ok, binary} | :error
@spec decode16(binary, Keyword.t) :: {:ok, binary} | :error
def decode16(string, opts \\ []) do
{:ok, decode16!(string, opts)}
def decode16(string, opts \\ []) when is_binary(string) do
case = Keyword.get(opts, :case, :upper)
{:ok, do_decode16(string, decode_case(case, &dec16/1))}
rescue
ArgumentError -> :error
end
@@ -220,70 +216,37 @@ defmodule Base do
"""
@spec decode16!(binary) :: binary
@spec decode16!(binary, Keyword.t) :: binary
def decode16!(string, opts \\ [])
def decode16!(string, opts) when is_binary(string) and rem(byte_size(string), 2) == 0 do
def decode16!(string, opts \\ []) when is_binary(string) do
case = Keyword.get(opts, :case, :upper)
do_decode16(case, string)
end
def decode16!(string, _opts) when is_binary(string) do
raise ArgumentError, "odd-length string"
do_decode16(string, decode_case(case, &dec16/1))
end
@doc """
Encodes a binary string into a base 64 encoded string.
Accepts `padding: false` option which will omit padding from
the output string.
## Examples
iex> Base.encode64("foobar")
"Zm9vYmFy"
iex> Base.encode64("foob")
"Zm9vYg=="
iex> Base.encode64("foob", padding: false)
"Zm9vYg"
"""
@spec encode64(binary) :: binary
@spec encode64(binary, Keyword.t) :: binary
def encode64(data, opts \\ []) when is_binary(data) do
pad_flag = Keyword.get(opts, :padding, true)
do_encode64(data, pad_flag)
def encode64(data) when is_binary(data) do
do_encode64(data, &enc64/1)
end
@doc """
Decodes a base 64 encoded string into a binary string.
Accepts `ignore: :whitespace` option which will ignore all the
whitespace characters in the input string.
Accepts `padding: false` option which will ignore padding from
the input string.
## Examples
iex> Base.decode64("Zm9vYmFy")
{:ok, "foobar"}
iex> Base.decode64("Zm9vYmFy\\n", ignore: :whitespace)
{:ok, "foobar"}
iex> Base.decode64("Zm9vYg==")
{:ok, "foob"}
iex> Base.decode64("Zm9vYg", padding: false)
{:ok, "foob"}
"""
@spec decode64(binary) :: {:ok, binary} | :error
@spec decode64(binary, Keyword.t) :: {:ok, binary} | :error
def decode64(string, opts \\ []) when is_binary(string) do
{:ok, decode64!(string, opts)}
def decode64(string) when is_binary(string) do
{:ok, do_decode64(string, &dec64/1)}
rescue
ArgumentError -> :error
end
@@ -291,11 +254,7 @@ defmodule Base do
@doc """
Decodes a base 64 encoded string into a binary string.
Accepts `ignore: :whitespace` option which will ignore all the
whitespace characters in the input string.
Accepts `padding: false` option which will ignore padding from
the input string.
The following alphabet is used both for encoding and decoding:
An `ArgumentError` exception is raised if the padding is incorrect or
a non-alphabet character is present in the string.
@@ -305,72 +264,40 @@ defmodule Base do
iex> Base.decode64!("Zm9vYmFy")
"foobar"
iex> Base.decode64!("Zm9vYmFy\\n", ignore: :whitespace)
"foobar"
iex> Base.decode64!("Zm9vYg==")
"foob"
iex> Base.decode64!("Zm9vYg", padding: false)
"foob"
"""
@spec decode64!(binary) :: binary
@spec decode64!(binary, Keyword.t) :: binary
def decode64!(string, opts \\ []) when is_binary(string) do
pad_flag = Keyword.get(opts, :padding, true)
string |> filter_ignored(opts[:ignore]) |> do_decode64(pad_flag)
def decode64!(string) when is_binary(string) do
do_decode64(string, &dec64/1)
end
@doc """
Encodes a binary string into a base 64 encoded string with URL and filename
safe alphabet.
Accepts `padding: false` option which will omit padding from
the output string.
## Examples
iex> Base.url_encode64(<<255, 127, 254, 252>>)
"_3_-_A=="
iex> Base.url_encode64(<<255, 127, 254, 252>>, padding: false)
"_3_-_A"
"""
@spec url_encode64(binary) :: binary
@spec url_encode64(binary, Keyword.t) :: binary
def url_encode64(data, opts \\ []) when is_binary(data) do
pad_flag = Keyword.get(opts, :padding, true)
do_encode64url(data, pad_flag)
def url_encode64(data) when is_binary(data) do
do_encode64(data, &enc64url/1)
end
@doc """
Decodes a base 64 encoded string with URL and filename safe alphabet
into a binary string.
Accepts `ignore: :whitespace` option which will ignore all the
whitespace characters in the input string.
Accepts `padding: false` option which will ignore padding from
the input string.
## Examples
iex> Base.url_decode64("_3_-_A==")
{:ok, <<255, 127, 254, 252>>}
iex> Base.url_decode64("_3_-_A==\\n", ignore: :whitespace)
{:ok, <<255, 127, 254, 252>>}
iex> Base.url_decode64("_3_-_A", padding: false)
{:ok, <<255, 127, 254, 252>>}
"""
@spec url_decode64(binary) :: {:ok, binary} | :error
@spec url_decode64(binary, Keyword.t) :: {:ok, binary} | :error
def url_decode64(string, opts \\ []) when is_binary(string) do
{:ok, url_decode64!(string, opts)}
def url_decode64(string) when is_binary(string) do
{:ok, do_decode64(string, &dec64url/1)}
rescue
ArgumentError -> :error
end
@@ -379,12 +306,6 @@ defmodule Base do
Decodes a base 64 encoded string with URL and filename safe alphabet
into a binary string.
Accepts `ignore: :whitespace` option which will ignore all the
whitespace characters in the input string.
Accepts `padding: false` option which will ignore padding from
the input string.
An `ArgumentError` exception is raised if the padding is incorrect or
a non-alphabet character is present in the string.
@@ -393,18 +314,10 @@ defmodule Base do
iex> Base.url_decode64!("_3_-_A==")
<<255, 127, 254, 252>>
iex> Base.url_decode64!("_3_-_A==\\n", ignore: :whitespace)
<<255, 127, 254, 252>>
iex> Base.url_decode64!("_3_-_A", padding: false)
<<255, 127, 254, 252>>
"""
@spec url_decode64!(binary) :: binary
@spec url_decode64!(binary, Keyword.t) :: binary
def url_decode64!(string, opts \\ []) when is_binary(string) do
pad_flag = Keyword.get(opts, :padding, true)
string |> filter_ignored(opts[:ignore]) |> do_decode64url(pad_flag)
def url_decode64!(string) when is_binary(string) do
do_decode64(string, &dec64url/1)
end
@doc """
@@ -413,9 +326,6 @@ defmodule Base do
Accepts an atom `:upper` (default) for encoding to upper case characters or
`:lower` for lower case characters.
Accepts `padding: false` option which will omit padding from
the output string.
## Examples
iex> Base.encode32("foobar")
@@ -424,16 +334,12 @@ defmodule Base do
iex> Base.encode32("foobar", case: :lower)
"mzxw6ytboi======"
iex> Base.encode32("foobar", padding: false)
"MZXW6YTBOI"
"""
@spec encode32(binary) :: binary
@spec encode32(binary, Keyword.t) :: binary
def encode32(data, opts \\ []) when is_binary(data) do
case = Keyword.get(opts, :case, :upper)
pad_flag = Keyword.get(opts, :padding, true)
do_encode32(case, data, pad_flag)
do_encode32(data, encode_case(case, &enc32/1))
end
@doc """
@@ -443,9 +349,6 @@ defmodule Base do
`:lower` for lower case characters. `:mixed` can be given for mixed case
characters.
Accepts `padding: false` option which will ignore padding from
the input string.
## Examples
iex> Base.decode32("MZXW6YTBOI======")
@@ -457,14 +360,12 @@ defmodule Base do
iex> Base.decode32("mzXW6ytBOi======", case: :mixed)
{:ok, "foobar"}
iex> Base.decode32("MZXW6YTBOI", padding: false)
{:ok, "foobar"}
"""
@spec decode32(binary) :: {:ok, binary} | :error
@spec decode32(binary, Keyword.t) :: {:ok, binary} | :error
def decode32(string, opts \\ []) do
{:ok, decode32!(string, opts)}
case = Keyword.get(opts, :case, :upper)
{:ok, do_decode32(string, decode_case(case, &dec32/1))}
rescue
ArgumentError -> :error
end
@@ -476,9 +377,6 @@ defmodule Base do
`:lower` for lower case characters. `:mixed` can be given for mixed case
characters.
Accepts `padding: false` option which will ignore padding from
the input string.
An `ArgumentError` exception is raised if the padding is incorrect or
a non-alphabet character is present in the string.
@@ -493,16 +391,12 @@ defmodule Base do
iex> Base.decode32!("mzXW6ytBOi======", case: :mixed)
"foobar"
iex> Base.decode32!("MZXW6YTBOI", padding: false)
"foobar"
"""
@spec decode32!(binary) :: binary
@spec decode32!(binary, Keyword.t) :: binary
def decode32!(string, opts \\ []) when is_binary(string) do
def decode32!(string, opts \\ []) do
case = Keyword.get(opts, :case, :upper)
pad_flag = Keyword.get(opts, :padding, true)
do_decode32(case, string, pad_flag)
do_decode32(string, decode_case(case, &dec32/1))
end
@doc """
@@ -512,9 +406,6 @@ defmodule Base do
Accepts an atom `:upper` (default) for encoding to upper case characters or
`:lower` for lower case characters.
Accepts `padding: false` option which will omit padding from
the output string.
## Examples
iex> Base.hex_encode32("foobar")
@@ -523,16 +414,12 @@ defmodule Base do
iex> Base.hex_encode32("foobar", case: :lower)
"cpnmuoj1e8======"
iex> Base.hex_encode32("foobar", padding: false)
"CPNMUOJ1E8"
"""
@spec hex_encode32(binary) :: binary
@spec hex_encode32(binary, Keyword.t) :: binary
def hex_encode32(data, opts \\ []) when is_binary(data) do
case = Keyword.get(opts, :case, :upper)
pad_flag = Keyword.get(opts, :padding, true)
do_hex_encode32(case, data, pad_flag)
do_encode32(data, encode_case(case, &enc32hex/1))
end
@doc """
@@ -543,9 +430,6 @@ defmodule Base do
`:lower` for lower case characters. `:mixed` can be given for mixed case
characters.
Accepts `padding: false` option which will ignore padding from
the input string.
## Examples
iex> Base.hex_decode32("CPNMUOJ1E8======")
@@ -557,14 +441,12 @@ defmodule Base do
iex> Base.hex_decode32("cpnMuOJ1E8======", case: :mixed)
{:ok, "foobar"}
iex> Base.hex_decode32("CPNMUOJ1E8", padding: false)
{:ok, "foobar"}
"""
@spec hex_decode32(binary) :: {:ok, binary} | :error
@spec hex_decode32(binary, Keyword.t) :: {:ok, binary} | :error
def hex_decode32(string, opts \\ []) do
{:ok, hex_decode32!(string, opts)}
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))}
rescue
ArgumentError -> :error
end
@@ -577,9 +459,6 @@ defmodule Base do
`:lower` for lower case characters. `:mixed` can be given for mixed case
characters.
Accepts `padding: false` option which will ignore padding from
the input string.
An `ArgumentError` exception is raised if the padding is incorrect or
a non-alphabet character is present in the string.
@@ -594,261 +473,120 @@ defmodule Base do
iex> Base.hex_decode32!("cpnMuOJ1E8======", case: :mixed)
"foobar"
iex> Base.hex_decode32!("CPNMUOJ1E8", padding: false)
"foobar"
"""
@spec hex_decode32!(binary) :: binary
@spec hex_decode32!(binary, Keyword.t) :: binary
def hex_decode32!(string, opts \\ []) when is_binary(string) do
case = Keyword.get(opts, :case, :upper)
pad_flag = Keyword.get(opts, :padding, true)
do_hex_decode32(case, string, pad_flag)
do_decode32(string, decode_case(case, &dec32hex/1))
end
defp filter_ignored(string, nil), do: string
defp filter_ignored(string, :whitespace) do
for <<c::8 <- string>>, not c in '\s\t\r\n', into: <<>>, do: <<c::8>>
defp do_encode16(<<>>, _), do: <<>>
defp do_encode16(data, enc) do
for <<c::4 <- data>>, into: <<>>, do: <<enc.(c)::8>>
end
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>>
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>>
end
end
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
defp do_decode16(_, _) do
raise ArgumentError, "odd-length string"
end
defp do_encode64(<<>>, _), do: <<>>
defp do_encode64(data, pad_flag) do
defp do_encode64(data, enc) do
split = 3 * div(byte_size(data), 3)
<<main::size(split)-binary, rest::binary>> = data
main = for <<c::6 <- main>>, into: <<>>, do: <<enc64(c)::8>>
tail = case rest do
main = for <<c::6 <- main>>, into: <<>>, do: <<enc.(c)::8>>
case rest do
<<c1::6, c2::6, c3::4>> ->
<<enc64(c1)::8, enc64(c2)::8, enc64(bsl(c3, 2))::8>>
<<main::binary, enc.(c1)::8, enc.(c2)::8, enc.(bsl(c3, 2))::8, ?=>>
<<c1::6, c2::2>> ->
<<enc64(c1)::8, enc64(bsl(c2, 4))::8>>
<<main::binary, enc.(c1)::8, enc.(bsl(c2, 4))::8, ?=, ?=>>
<<>> ->
<<>>
main
end
main <> maybe_pad(tail, pad_flag, 4, "=")
end
defp do_decode64(<<>>, _), do: <<>>
defp do_decode64(string, false) do
maybe_pad(string, true, 4, "=") |> do_decode64(true)
end
defp do_decode64(string, _pad_flag) when rem(byte_size(string), 4) == 0 do
defp do_decode64(string, dec) when rem(byte_size(string), 4) == 0 do
split = byte_size(string) - 4
<<main::size(split)-binary, rest::binary>> = string
main = for <<c::8 <- main>>, into: <<>>, do: <<dec64(c)::6>>
tail = case rest do
main = for <<c::8 <- main>>, into: <<>>, do: <<dec.(c)::6>>
case rest do
<<c1::8, c2::8, ?=, ?=>> ->
<<dec64(c1)::6, bsr(dec64(c2), 4)::2>>
<<main::binary, dec.(c1)::6, bsr(dec.(c2), 4)::2>>
<<c1::8, c2::8, c3::8, ?=>> ->
<<dec64(c1)::6, dec64(c2)::6, bsr(dec64(c3), 2)::4>>
<<main::binary, dec.(c1)::6, dec.(c2)::6, bsr(dec.(c3), 2)::4>>
<<c1::8, c2::8, c3::8, c4::8>> ->
<<dec64(c1)::6, dec64(c2)::6, dec64(c3)::6, dec64(c4)::6>>
<<main::binary, dec.(c1)::6, dec.(c2)::6, dec.(c3)::6, dec.(c4)::6>>
<<>> ->
<<>>
main
end
main <> tail
end
defp do_decode64(_, _) do
raise ArgumentError, "incorrect padding"
end
defp do_encode64url(<<>>, _), do: <<>>
defp do_encode64url(data, pad_flag) do
split = 3 * div(byte_size(data), 3)
defp do_encode32(<<>>, _), do: <<>>
defp do_encode32(data, enc) do
split = 5 * div(byte_size(data), 5)
<<main::size(split)-binary, rest::binary>> = data
main = for <<c::6 <- main>>, into: <<>>, do: <<enc64url(c)::8>>
tail = case rest do
<<c1::6, c2::6, c3::4>> ->
<<enc64url(c1)::8, enc64url(c2)::8, enc64url(bsl(c3, 2))::8>>
<<c1::6, c2::2>> ->
<<enc64url(c1)::8, enc64url(bsl(c2, 4))::8>>
main = for <<c::5 <- main>>, into: <<>>, do: <<enc.(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, ?=, ?=,
?=, ?=, ?=, ?=>>
<<>> ->
<<>>
main
end
main <> maybe_pad(tail, pad_flag, 4, "=")
end
defp do_decode64url(<<>>, _), do: <<>>
defp do_decode64url(string, false) do
maybe_pad(string, true, 4, "=") |> do_decode64url(true)
end
defp do_decode64url(string, _pad_flag) when rem(byte_size(string), 4) == 0 do
split = byte_size(string) - 4
defp do_decode32(<<>>, _), do: <<>>
defp do_decode32(string, dec) when rem(byte_size(string), 8) == 0 do
split = byte_size(string) - 8
<<main::size(split)-binary, rest::binary>> = string
main = for <<c::8 <- main>>, into: <<>>, do: <<dec64url(c)::6>>
tail = case rest do
<<c1::8, c2::8, ?=, ?=>> ->
<<dec64url(c1)::6, bsr(dec64url(c2), 4)::2>>
<<c1::8, c2::8, c3::8, ?=>> ->
<<dec64url(c1)::6, dec64url(c2)::6, bsr(dec64url(c3), 2)::4>>
<<c1::8, c2::8, c3::8, c4::8>> ->
<<dec64url(c1)::6, dec64url(c2)::6, dec64url(c3)::6, dec64url(c4)::6>>
main = for <<c::8 <- main>>, into: <<>>, do: <<dec.(c)::5>>
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>>
<<>> ->
<<>>
main
end
main <> tail
end
defp do_decode64url(_, _) do
defp do_decode32(_, _) do
raise ArgumentError, "incorrect padding"
end
defp do_encode32(_, <<>>, _), do: <<>>
for {case, fun} <- [upper: :to_upper, lower: :to_lower] do
defp do_encode32(unquote(case), data, pad_flag) 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>>
tail = case rest do
<<c1::5, c2::5, c3::5, c4::5, c5::5, c6::5, c7::2>> ->
<<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>> ->
<<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>> ->
<<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>> ->
<<unquote(fun)(enc32(c1))::8, unquote(fun)(enc32(bsl(c2, 2)))::8>>
<<>> ->
<<>>
end
main <> maybe_pad(tail, pad_flag, 8, "=")
end
end
defp do_decode32(_, <<>>, _), do: <<>>
defp do_decode32(case, string, false),
do: do_decode32(case, maybe_pad(string, true, 8, "="), true)
for {case, fun} <- [upper: :from_upper, lower: :from_lower, mixed: :from_mixed] do
defp do_decode32(unquote(case), string, _pad_flag) when rem(byte_size(string), 8) == 0 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>>
tail = case rest do
<<c1::8, c2::8, ?=, ?=, ?=, ?=, ?=, ?=>> ->
<<dec32(unquote(fun)(c1))::5, bsr(dec32(unquote(fun)(c2)), 2)::3>>
<<c1::8, c2::8, c3::8, c4::8, ?=, ?=, ?=, ?=>> ->
<<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, ?=, ?=, ?=>> ->
<<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, ?=>> ->
<<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>> ->
<<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>>
<<>> ->
<<>>
end
main <> tail
end
end
defp do_decode32(_, _, _),
do: raise ArgumentError, "incorrect padding"
defp do_hex_encode32(_, <<>>, _), do: <<>>
for {case, fun} <- [upper: :to_upper, lower: :to_lower] do
defp do_hex_encode32(unquote(case), data, pad_flag) 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>>
tail = case rest do
<<c1::5, c2::5, c3::5, c4::5, c5::5, c6::5, c7::2>> ->
<<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>> ->
<<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>> ->
<<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>> ->
<<unquote(fun)(enc32hex(c1))::8, unquote(fun)(enc32hex(bsl(c2, 2)))::8>>
<<>> ->
<<>>
end
main <> maybe_pad(tail, pad_flag, 8, "=")
end
end
defp do_hex_decode32(_, <<>>, _), do: <<>>
defp do_hex_decode32(case, string, false),
do: do_hex_decode32(case, maybe_pad(string, true, 8, "="), true)
for {case, fun} <- [upper: :from_upper, lower: :from_lower, mixed: :from_mixed] do
defp do_hex_decode32(unquote(case), string, _pad_flag) when rem(byte_size(string), 8) == 0 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>>
tail = case rest do
<<c1::8, c2::8, ?=, ?=, ?=, ?=, ?=, ?=>> ->
<<dec32hex(unquote(fun)(c1))::5, bsr(dec32hex(unquote(fun)(c2)), 2)::3>>
<<c1::8, c2::8, c3::8, c4::8, ?=, ?=, ?=, ?=>> ->
<<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, ?=, ?=, ?=>> ->
<<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, ?=>> ->
<<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>> ->
<<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>>
<<>> ->
<<>>
end
main <> tail
end
end
defp do_hex_decode32(_, _, _),
do: raise ArgumentError, "incorrect padding"
end
+2 -3
View File
@@ -1,9 +1,8 @@
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
+9 -11
View File
@@ -374,8 +374,7 @@ defmodule Code do
## Examples
Code.compiler_options
#=> %{debug_info: true, docs: true,
warnings_as_errors: false, ignore_module_conflict: false}
#=> [debug_info: true, docs: true, warnings_as_errors: false]
"""
def compiler_options do
@@ -422,18 +421,17 @@ defmodule Code do
## Examples
Code.compiler_options(debug_info: true)
#=> %{debug_info: true, docs: true,
warnings_as_errors: false, ignore_module_conflict: false}
#=> [debug_info: true, docs: true, warnings_as_errors: false]
"""
def compiler_options(opts) do
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)
{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
end
@doc """
+504 -11
View File
@@ -1,20 +1,118 @@
defmodule Dict do
@moduledoc ~S"""
WARNING: this module is deprecated.
This module specifies the Dict API expected to be
implemented by different dictionaries.
If you need a general dictionary, use the `Map` module.
If you need to manipulate keyword lists, use `Keyword`.
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
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
@@ -47,9 +145,6 @@ 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
@@ -230,71 +325,276 @@ 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)
@@ -307,6 +607,27 @@ 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)
@@ -325,46 +646,207 @@ 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)
@@ -378,7 +860,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)
@@ -387,6 +869,17 @@ 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)
+585 -714
View File
File diff suppressed because it is too large Load Diff
+13 -28
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/exit.
Normalizes and formats any throw, error and 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 stacktraces.
Normalizes and formats throw/errors/exits and stacktrace.
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. It returns a string.
Formats an exit, returns a string.
Often there are errors/exceptions inside exits. Exits are often
wrapped by the caller and provide stacktraces too. This function
@@ -365,10 +365,9 @@ defmodule Exception do
end
defp format_application(module) do
if app = Application.get_application(module) do
"(" <> Atom.to_string(app) <> ") "
else
""
case :application.get_application(module) do
{:ok, app} -> "(" <> Atom.to_string(app) <> ") "
:undefined -> ""
end
end
@@ -395,7 +394,7 @@ defmodule Exception do
## Examples
Exception.format_fa(fn -> nil end, 1)
Exception.format_fa(fn -> end, 1)
#=> "#Function<...>/1"
"""
@@ -448,7 +447,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
@@ -627,23 +626,19 @@ 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
@@ -708,12 +703,8 @@ defmodule UnicodeConversionError do
"encoding starting at #{inspect rest}"
end
defp detail([h|_]) when is_integer(h) do
"code point #{h}"
end
defp detail([h|_]) do
detail(h)
"code point #{h}"
end
end
@@ -800,8 +791,6 @@ 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
@@ -809,10 +798,6 @@ 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
+44 -45
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 files or IO devices, like `open/2`,
to interact with the file 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 providing the proper data guarantees.
doing the proper conversions and 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 UTF-8
encoding. Binary filenames are considered raw and passed
shell and the operating system are configured to use UTF8
encoding. Binary filenames are considering 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 functions also have a variant
that ends with `!` which returns the result (instead of the
otherwise. Those function are also followed by a variant
that ends with `!` which returns the result (without the
`{:ok, result}` tuple) in case of success or raises an
exception in case it fails. For example:
@@ -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 the process that
to a file is equivalent to sending messages to that 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 on large files or
and `:delayed_write` are also useful when operating 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 a binary with the contents of the given filename or raises
Returns binary with the contents of the given filename or raises
`File.Error` if an error occurs.
"""
@spec read!(Path.t) :: binary | no_return
@@ -261,8 +261,7 @@ 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 same time zone as the
machine
* `:local` - returns a `{date, time}` tuple using the machine time
* `:posix` - returns the time as integer seconds since epoch
"""
@@ -292,8 +291,8 @@ defmodule File do
end
@doc """
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
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
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).
@@ -323,7 +322,7 @@ defmodule File do
end
@doc """
Same as `lstat/2` but returns the `File.Stat` struct directly and
Same as `lstat/2` but returns the `File.Stat` directly and
throws `File.Error` if an error is returned.
"""
@spec lstat!(Path.t, stat_options) :: File.Stat.t | no_return
@@ -364,7 +363,7 @@ defmodule File do
Updates modification time (mtime) and access time (atime) of
the given file.
The file is created if it doesn’t exist. Requires datetime in UTC.
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
@@ -450,12 +449,12 @@ defmodule File do
end
@doc """
Renames the `source` file to `destination` file. It can be used to move files
Renames the `source` file to `destination` file. If 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
its directory.
it's 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.
@@ -479,9 +478,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. The callback defaults to return `true`.
should be overwritten, `false` otherwise. It defaults to return `true`.
The function returns `:ok` in case of success, returns
It returns `:ok` in case of success, returns
`{:error, reason}` otherwise.
If you want to copy contents from an io device to another device
@@ -535,20 +534,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. The callback defaults to return `true`.
`false` otherwise. It defaults to return `true`.
If a directory already exists in the destination
where a file is meant to be (or vice versa), this
where a file is meant to be (or otherwise), 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 file which have already been copied
won't be removed.
directory in a dirty state, where already
copied files won't be removed.
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.
It returns `{:ok, files_and_directories}` in case of
success with all files and directories copied in no
specific order, `{:error, reason, file}` otherwise.
Note: The command `cp` in Unix systems behaves differently
depending if `destination` is an existing directory or not.
@@ -683,13 +682,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
than calling this function multiple times.
then 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
@@ -728,7 +727,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
@@ -922,7 +921,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
@@ -956,10 +955,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.
@@ -1000,9 +999,9 @@ defmodule File do
end
@doc """
Similar to `open/2` but expects a function as its last argument.
Similar to `open/2` but expects a function as last argument.
The file is opened, given to the function as an argument and
The file is opened, given to the function as argument and
automatically closed after the function returns, regardless
if there was an error when executing the function.
@@ -1066,7 +1065,7 @@ defmodule File do
Gets the current working directory.
In rare circumstances, this function can fail on Unix. It may happen
if read permissions do not exist for the parent directories of the
if read permission does not exist for the parent directories of the
current directory. For this reason, returns `{:ok, cwd}` in case
of success, `{:error, reason}` otherwise.
"""
@@ -1124,8 +1123,8 @@ defmodule File do
@doc """
Changes the current directory to the given `path`,
executes the given function and then reverts back
to the previous path regardless of whether there is an exception.
executes the given function and then revert back
to the previous path regardless if there is an exception.
Raises an error if retrieving or changing the current
directory fails.
@@ -1142,7 +1141,7 @@ defmodule File do
end
@doc """
Returns the list of files in the given directory.
Returns list of files in the given directory.
It returns `{:ok, [files]}` in case of success,
`{:error, reason}` otherwise.
@@ -1192,9 +1191,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 each 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 only and
every time streaming begins. There is no need to pass `:read` and
`:write` modes, as those are automatically set by Elixir.
## Raw files
@@ -1268,7 +1267,7 @@ defmodule File do
end
@doc """
Changes the group given by the group id `gid`
Changes the user group given by the group id `gid`
for a given `file`. Returns `:ok` on success, or
`{:error, reason}` on failure.
"""
+4 -5
View File
@@ -2,7 +2,7 @@ require Record
defmodule File.Stat do
@moduledoc """
A struct that holds file information.
A struct responsible to hold file information.
In Erlang, this struct is represented by a `:file_info` record.
Therefore this module also provides functions for converting
@@ -41,11 +41,10 @@ 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. Will be zero for non-Unix file
systems.
* `uid` - indicates the owner of the file.
* `gid` - indicates the group that owns 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.
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`,
+8 -9
View File
@@ -20,15 +20,14 @@ defmodule File.Stream do
raw = :lists.keyfind(:encoding, 1, modes) == false
modes =
case raw do
true ->
if :lists.keyfind(:read_ahead, 1, modes) == {:read_ahead, false} do
[:raw | modes]
else
[:raw, :read_ahead | modes]
end
false ->
modes
if raw do
if :lists.keyfind(:read_ahead, 1, modes) == {:read_ahead, false} do
[:raw|modes]
else
[:raw, :read_ahead|modes]
end
else
modes
end
%File.Stream{path: path, modes: modes, raw: raw, line_or_bytes: line_or_bytes}
+8 -12
View File
@@ -9,11 +9,7 @@ defmodule Float do
Parses a binary into a float.
If successful, returns a tuple of the form `{float, remainder_of_binary}`;
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.
otherwise, `:error`.
If a float formatted string wants to be directly converted to a float,
`String.to_float/1` can be used instead.
@@ -55,16 +51,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:
@@ -90,7 +86,7 @@ defmodule Float do
iex> Float.floor(-56.5)
-57.0
iex> Float.floor(34.259, 2)
iex> Float.floor(34.253, 2)
34.25
"""
@@ -104,7 +100,7 @@ defmodule Float do
end
@doc """
Rounds a float to the smallest integer greater than or equal to `num`.
Rounds a float to the largest 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).
@@ -120,7 +116,7 @@ defmodule Float do
iex> Float.ceil(-56.5)
-56.0
iex> Float.ceil(34.251, 2)
iex> Float.ceil(34.253, 2)
34.26
"""
+17 -30
View File
@@ -22,7 +22,6 @@ 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
@@ -37,21 +36,17 @@ 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]
@@ -86,7 +81,7 @@ defmodule GenEvent do
asynchronously.
On `GenEvent.sync_notify/2`, the manager acknowledges an event
just after it is processed by all event handlers.
just after it was 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).
@@ -130,7 +125,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 also normalizes the `{:error, _}` tuples returned
Furthermore, Elixir's also normalizes the `{:error, _}` tuples returned
by many functions, in order to be more consistent with themselves and
the `GenServer` module.
"""
@@ -422,10 +417,6 @@ 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
@@ -616,24 +607,18 @@ defmodule GenEvent do
end
@doc """
Stops the manager with the given `reason`.
Terminates the event `manager`.
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.
Before terminating, the event manager will call `terminate(:stop, ...)`
for each installed event handler.
"""
@spec stop(manager, reason :: term, timeout) :: :ok
def stop(manager, reason \\ :normal, timeout \\ :infinity) do
:gen.stop(manager, reason, timeout)
@spec stop(manager) :: :ok
def stop(manager) do
rpc(manager, :stop)
end
defp rpc(module, cmd) do
# TODO: Change the tag on OTP 18
{:ok, reply} = :gen.call(module, self(), cmd, :infinity)
reply
end
@@ -650,12 +635,7 @@ defmodule GenEvent do
def init_it(starter, parent, name, _, _, options) do
Process.put(:"$initial_call", {__MODULE__, :init_it, 6})
debug =
if function_exported?(:gen, :debug_options, 2) do
:gen.debug_options(name, options)
else
:gen.debug_options(options)
end
debug = :gen.debug_options(options)
:proc_lib.init_ack(starter, {:ok, self()})
loop(parent, name(name), [], debug, false)
end
@@ -747,6 +727,13 @@ 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 `GenEvent.stream/2`. The struct is public and
This is a struct returned by `stream/2`. The struct is public and
contains the following fields:
* `:manager` - the manager reference given to `GenEvent.stream/2`
+5 -25
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 like any other Elixir process and it can be used
A GenServer is a process as 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,11 +76,8 @@ 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 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.
mechanism and name. The `:via` option expects a module name to control
the registration mechanism alongside a name which can be any term.
For example, we could start and register our Stack server locally as follows:
@@ -225,7 +222,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 received.
called after `timeout` milliseconds if no messages are receved.
Returning `{:reply, reply, new_state, :hibernate}` is similar to
`{:reply, reply, new_state}` except the process is hibernated and will
@@ -517,23 +514,6 @@ 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.
@@ -553,7 +533,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
+12 -3
View File
@@ -1,8 +1,17 @@
defmodule HashDict do
@moduledoc """
WARNING: this module is deprecated.
A key-value store.
Use the `Map` module instead.
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 Dict
@@ -227,7 +236,7 @@ end
defimpl Collectable, for: HashDict do
def into(original) do
{original, fn
dict, {:cont, {k, v}} -> HashDict.put(dict, k, v)
dict, {:cont, {k, v}} -> Dict.put(dict, k, v)
dict, :done -> dict
_, :halt -> :ok
end}
+14 -2
View File
@@ -1,8 +1,17 @@
defmodule HashSet do
@moduledoc """
WARNING: this module is deprecated.
A set store.
Use the `MapSet` module instead.
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.
"""
@behaviour Set
@@ -20,6 +29,9 @@ 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{}
+62 -65
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 `MapSet`'s `inspect`
of existing entities. For example, here is `HashSet`'s `inspect`
implementation:
defimpl Inspect, for: MapSet do
defimpl Inspect, for: HashSet do
import Inspect.Algebra
def inspect(dict, opts) do
concat ["#MapSet<", to_doc(MapSet.to_list(dict), opts), ">"]
concat ["#HashSet<", to_doc(HashSet.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 `"MapSet<"` (all strings are
it is concatenating the string `"HashSet<"` (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.MapSet above,
implementation directly. For example, to test Inspect.HashSet above,
you can invoke it as:
Inspect.MapSet.inspect(MapSet.new, %Inspect.Opts{})
Inspect.HashSet.inspect(HashSet.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::8, h::binary >> = head
t = <<h::binary, t::binary>>
escape(t, char, <<binary::binary, escape_char(byte)::binary>>)
<< byte :: size(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::8>>
<<a::4, b::4>> = <<char::size(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::16>>
<<a::4, b::4, c::4, d::4>> = <<char::size(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::24>>
<<a::4, b::4, c::4, d::4, e::4, f::4>> = <<char::size(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::8>>, _counter, acc) do
defp each_bit(<<h :: size(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,7 +285,6 @@ defimpl Inspect, for: List do
end
end
@doc false
def keyword({key, value}, opts) do
concat(
key_to_binary(key) <> ": ",
@@ -293,7 +292,6 @@ 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
@@ -304,19 +302,6 @@ 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
@@ -325,6 +310,18 @@ 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
+6 -22
View File
@@ -11,14 +11,6 @@ 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
@@ -30,14 +22,6 @@ 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
@@ -142,14 +126,14 @@ defmodule Integer do
raise ArgumentError, "invalid base #{base}"
end
defp parse_in_base("-" <> bin, base) do
defp parse_in_base(<< ?-, bin :: binary >>, base) do
case do_parse(bin, base) do
:error -> :error
{number, remainder} -> {-number, remainder}
end
end
defp parse_in_base("+" <> bin, base) do
defp parse_in_base(<< ?+, bin :: binary >>, base) do
do_parse(bin, base)
end
@@ -157,9 +141,9 @@ defmodule Integer do
do_parse(bin, base)
end
defp do_parse(<<char, rest::binary>>, base) do
defp do_parse(<< char, bin :: binary >>, base) do
if valid_digit_in_base?(char, base) do
do_parse(rest, base, parse_digit(char, base))
do_parse(bin, base, parse_digit(char, base))
else
:error
end
@@ -167,11 +151,11 @@ defmodule Integer do
defp do_parse(_, _), do: :error
defp do_parse(<<char, rest::binary>> = bin, 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, bin}
{acc, << char, rest :: binary >>}
end
end
+2 -15
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,
functions will convert to string via the `String.Chars` protocol
it will do a conversion to string via the `String.Chars` protocol
(as shown in typespecs).
The functions starting with `bin*` expect iodata as an argument,
@@ -27,11 +27,6 @@ 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
@@ -252,9 +247,7 @@ 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)
@@ -264,12 +257,6 @@ 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
-28
View File
@@ -42,34 +42,6 @@ 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
+45 -58
View File
@@ -104,15 +104,23 @@ defmodule IO.ANSI.Docs do
defp process(all=[line | rest], text, indent, options) do
{stripped, count} = strip_spaces(line, 0, :infinity)
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)
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
end
end
@@ -135,22 +143,6 @@ 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
@@ -178,11 +170,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
@@ -262,7 +254,7 @@ defmodule IO.ANSI.Docs do
## Tables
defp process_table(lines, indent, options) do
{table, rest} = Enum.split_while(lines, &table_line?/1)
{table, rest} = Enum.split_while(lines, &is_table_line?/1)
table_lines(table, options)
newline_after_block
process(rest, [], indent, options)
@@ -342,7 +334,7 @@ defmodule IO.ANSI.Docs do
end
end
defp table_line?(line) do
defp is_table_line?(line) do
Regex.match?(~r'''
( ^ \s{0,3} \| (?: [^|]+ \|)+ \s* $ )
|
@@ -352,14 +344,6 @@ 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),
@@ -401,11 +385,11 @@ defmodule IO.ANSI.Docs do
{Enum.reverse(acc), []}
end
defp length_without_escape(<<?\e, ?[, _, _, ?m>> <> rest, count) do
defp length_without_escape(<< ?\e, ?[, _, _, ?m, rest :: binary >>, count) do
length_without_escape(rest, count)
end
defp length_without_escape(<<?\e, ?[, _, ?m>> <> rest, count) do
defp length_without_escape(<< ?\e, ?[, _, ?m, rest :: binary >>, count) do
length_without_escape(rest, count)
end
@@ -423,7 +407,10 @@ defmodule IO.ANSI.Docs do
end
defp escape_underlines_in_link(text) do
Regex.replace(~r{https?\S*}, text, &String.replace(&1, "_", "\\_"))
case Regex.match?(~r{.*(https?\S*)}, text) do
true -> Regex.replace(~r{_}, text, "\\\\_")
_ -> text
end
end
defp remove_square_brackets_in_link(text) do
@@ -450,11 +437,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
@@ -464,72 +451,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 an `IO.Stream` struct returned by `IO.stream/2` and `IO.binstream/2`.
Defines a `IO.Stream` struct returned by `IO.stream/2` and `IO.binstream/2`.
The following fields are public:
+90 -131
View File
@@ -7,12 +7,11 @@ import :elixir_bootstrap
defmodule Kernel do
@moduledoc """
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:
`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:
import Kernel, except: [if: 2]
@@ -1340,31 +1339,19 @@ defmodule Kernel do
:erlang.error unquote(alias).exception([])
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
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
clauses =
:lists.map(fn {:->, meta, args} ->
{:->, [generated: true] ++ Keyword.put(meta, :line, -1), args}
end, clauses)
{fun, meta, [arg, [do: clauses]]}
end
end
end
@@ -1603,47 +1590,33 @@ defmodule Kernel do
"""
@spec struct(module | map, Enum.t) :: map
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)
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)
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` module to traverse the structures
Uses the `Access` protocol to traverse the structures
according to the given `keys`, unless the `key` is a
function.
@@ -1662,7 +1635,7 @@ defmodule Kernel do
27
In case any of entries in the middle returns `nil`, `nil` will be returned
as per the Access module:
as per the Access protocol:
iex> users = %{"john" => %{age: 27}, "meg" => %{age: 23}}
iex> get_in(users, ["unknown", :age])
@@ -1702,7 +1675,7 @@ defmodule Kernel do
@doc """
Puts a value in a nested structure.
Uses the `Access` module to traverse the structures
Uses the `Access` protocol 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`.
@@ -1724,7 +1697,7 @@ defmodule Kernel do
@doc """
Updates a key in a nested structure.
Uses the `Access` module to traverse the structures
Uses the `Access` protocol 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`.
@@ -1749,7 +1722,7 @@ defmodule Kernel do
It expects a tuple to be returned, containing the value
retrieved and the update one.
Uses the `Access` module to traverse the structures
Uses the `Access` protocol to traverse the structures
according to the given `keys`, unless the `key` is a
function.
@@ -2188,17 +2161,17 @@ defmodule Kernel do
true ->
raise ArgumentError, "cannot set attribute @#{name} inside function/macro"
false ->
cond do
name == :behavior ->
arg = case name do
:behavior ->
:elixir_errors.warn env.line, env.file,
"@behavior attribute is not supported, please use @behaviour instead"
: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))
:doc -> {env.line, arg}
:typedoc -> {env.line, arg}
:moduledoc -> {env.line, arg}
_ -> arg
end
quote do: Module.put_attribute(__MODULE__, unquote(name), unquote(arg))
end
end
@@ -2268,7 +2241,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, [generated: true], c})}
{v, wrap_binding(in_match?, {v, [warn: false], c})}
end
end
@@ -2417,9 +2390,19 @@ defmodule Kernel do
do).
"""
defmacro destructure(left, right) when is_list(left) do
quote do
unquote(left) =
Kernel.Utils.destructure(unquote(right), unquote(length(left)))
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}
end
end
@@ -2446,21 +2429,16 @@ 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) or
is_list(first) or is_list(last) do
is_binary(first) or is_binary(last) do
true ->
raise ArgumentError,
"ranges (first..last) expect both sides to be integers, " <>
"ranges (left .. right) expect both sides to be integers, " <>
"got: #{Macro.to_string({:.., [], [first, last]})}"
false ->
case __CALLER__.context do
nil -> quote do: Elixir.Range.new(unquote(first), unquote(last))
_ -> {:%{}, [], [__struct__: Elixir.Range, first: first, last: last]}
end
{:%{}, [], [__struct__: Elixir.Range, first: first, last: last]}
end
end
@doc """
Provides a short-circuit operator that evaluates and returns
the second expression only if the first one evaluates to `true`
@@ -2484,8 +2462,9 @@ defmodule Kernel do
false
Note that, unlike `and/2`, this operator accepts any expression
as the first argument, not only booleans.
Note that, unlike Erlang's `and` operator,
this operator accepts any expression as the first argument,
not only booleans.
"""
defmacro left && right do
quote do
@@ -2519,8 +2498,9 @@ defmodule Kernel do
iex> Enum.empty?([]) || throw(:bad)
true
Note that, unlike `or/2`, this operator accepts any expression
as the first argument, not only booleans.
Note that, unlike Erlang's `or` operator,
this operator accepts any expression as the first argument,
not only booleans.
"""
defmacro left || right do
quote do
@@ -2682,7 +2662,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
@@ -2931,8 +2911,8 @@ defmodule Kernel do
defp module_vars([{key, kind}|vars], counter) do
var =
case is_atom(kind) do
true -> {key, [generated: true], kind}
false -> {key, [counter: kind, generated: true], nil}
true -> {key, [warn: false], kind}
false -> {key, [counter: kind, warn: false], nil}
end
under = String.to_atom(<<"_@", :erlang.integer_to_binary(counter)::binary>>)
@@ -3056,7 +3036,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
@@ -3210,7 +3190,7 @@ defmodule Kernel do
"""
defmacro defstruct(fields) do
quote bind_quoted: [fields: fields] do
fields = Kernel.Utils.defstruct(__MODULE__, fields)
fields = Kernel.Def.struct(__MODULE__, fields)
@struct fields
case Module.get_attribute(__MODULE__, :derive) do
@@ -3375,16 +3355,15 @@ 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 `MapSet`. We can implement the `Blank` protocol
for those types as well:
structs, like `HashDict` and `HashSet`. We can implement the
`Blank` protocol for those types as well:
defimpl Blank, for: MapSet do
defimpl Blank, for: [HashDict, HashSet] 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]
@@ -3620,31 +3599,17 @@ defmodule Kernel do
please define it a module which will be imported accordingly.
"""
defmacro use(module, opts \\ []) do
calls = Enum.map(expand_aliases(module, __CALLER__), fn
expanded when is_atom(expanded) ->
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 ->
quote do
require unquote(expanded)
unquote(expanded).__using__(unquote(opts))
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
end
@doc """
@@ -3700,10 +3665,7 @@ defmodule Kernel do
raise ArgumentError, "expected to: to be given as argument"
for fun <- List.wrap(funs) do
{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
{name, args, as, as_args} = Kernel.Def.delegate(fun, opts)
def unquote(name)(unquote_splicing(args)) do
unquote(target).unquote(as)(unquote_splicing(as_args))
end
@@ -3729,7 +3691,7 @@ defmodule Kernel do
"""
defmacro sigil_S(term, modifiers)
defmacro sigil_S({:<<>>, _, [binary]}, []) when is_binary(binary), do: binary
defmacro sigil_S(string, []), do: string
@doc ~S"""
Handles the sigil `~s`.
@@ -3750,9 +3712,6 @@ 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
+1 -12
View File
@@ -146,18 +146,7 @@ defmodule Kernel.CLI do
# Parse shared options
defp parse_shared([opt|_t], _config) when opt in ["-v", "--version"] do
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
IO.puts "Elixir #{System.version}"
System.halt 0
end
@@ -1,19 +1,10 @@
import Kernel, except: [destructure: 2, defdelegate: 2, defstruct: 2]
defmodule Kernel.Utils do
defmodule Kernel.Def do
@moduledoc false
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
@doc """
Callback invoked at compile time for `defdelegate`.
"""
def delegate(fun, opts) do
append_first = Keyword.get(opts, :append_first, false)
{name, args} =
@@ -22,8 +13,6 @@ defmodule Kernel.Utils 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)]
@@ -34,18 +23,10 @@ defmodule Kernel.Utils do
{name, args, as, as_args}
end
# 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
@doc """
Callback invoked at compile time for `defstruct`.
"""
def struct(module, fields) do
case fields do
fs when is_list(fs) -> :ok
other ->
+1 -1
View File
@@ -14,7 +14,7 @@ defmodule Kernel.ErrorHandler do
end
def release() do
# On release, no longer allow elixir_ensure_compiled
# On release, no further 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 an Elixir module responsible for tracking
# This is a module Elixir responsible for tracking
# the usage of aliases, imports and requires in the Elixir scope.
#
# The implementation simply stores dispatch information in an
+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.
# compilation status will be set to error and we fail with CompileError
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"
+19 -24
View File
@@ -3,6 +3,8 @@ 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.
@@ -11,21 +13,9 @@ defmodule Kernel.ParallelRequire do
Returns the modules generated by each required file.
"""
def files(files, callback \\ fn x -> x end) do
compiler_pid = self()
:elixir_code_server.cast({:reset_warnings, compiler_pid})
def files(files, callback \\ default_callback) do
schedulers = max(:erlang.system_info(:schedulers_online), 2)
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
spawn_requires(files, [], callback, schedulers, [])
end
defp spawn_requires([], [], _callback, _schedulers, result), do: result
@@ -39,9 +29,22 @@ defmodule Kernel.ParallelRequire do
end
defp spawn_requires([h|t], waiting, callback, schedulers, result) do
parent = self()
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)
{pid, ref} = :erlang.spawn_monitor fn ->
:erlang.put(:elixir_compiler_pid, parent)
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)
exit(try do
new = Code.require_file(h) || []
@@ -72,14 +75,6 @@ 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
+66 -105
View File
@@ -1,11 +1,12 @@
defmodule Kernel.SpecialForms do
@moduledoc """
Special forms are the basic building blocks of Elixir, and therefore
they cannot be overridden by the developer.
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.
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.
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.
This module also documents Elixir's pseudo variables (`__ENV__`,
`__MODULE__`, `__DIR__` and `__CALLER__`). Pseudo variables return
@@ -87,7 +88,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 `KeyError`.
Trying to update a key that does not exist will raise an `ArgumentError`.
## AST representation
@@ -95,8 +96,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)
@@ -168,8 +169,8 @@ defmodule Kernel.SpecialForms do
## Examples
iex> <<1, 2, 3>>
<<1, 2, 3>>
iex> << 1, 2, 3 >>
<< 1, 2, 3 >>
## Types
@@ -192,7 +193,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 default expanded to integers:
string or a literal char list, which are by expanded to integers:
iex> <<0, "foo">>
<<0, 102, 111, 111>>
@@ -206,15 +207,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
@@ -222,13 +223,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
@@ -267,9 +268,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
@@ -292,11 +293,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
@@ -304,7 +305,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
@@ -540,7 +541,7 @@ defmodule Kernel.SpecialForms do
defmacro require(module, opts)
@doc """
Imports functions and macros from other modules.
Imports function and macros from other modules.
`import` allows one to easily access functions or macros from
others modules without using the qualified name.
@@ -765,10 +766,6 @@ defmodule Kernel.SpecialForms do
quote. Read the Stacktrace information section below for more
information.
* `:generated` - marks the given chunk as generated so it does not emit warnings.
Currently it only works on special forms (for example, you cannot annotate
a `case` but not an `if`).
* `:context` - sets the resolution context.
* `:bind_quoted` - passes a binding to the macro. Whenever a binding is
@@ -824,7 +821,7 @@ defmodule Kernel.SpecialForms do
Returning 5
Returning 5
Got 25
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
@@ -884,7 +881,7 @@ defmodule Kernel.SpecialForms do
import Math
squared(5)
x #=> ** (CompileError) undefined variable x or undefined function x/0
x #=> ** (RuntimeError) undefined function or variable: x
We can see that `x` did not leak to the user context. This happens
because Elixir macros are hygienic, a topic we will discuss at length
@@ -943,7 +940,7 @@ defmodule Kernel.SpecialForms do
Hygiene.write
Hygiene.read
#=> ** (RuntimeError) undefined variable a or undefined function a/0
#=> ** (RuntimeError) undefined function or variable: a
For such, you can explicitly pass the current module scope as
argument:
@@ -972,34 +969,34 @@ defmodule Kernel.SpecialForms do
Consider the following example:
defmodule Hygiene do
alias Map, as: M
alias HashDict, as: D
defmacro no_interference do
quote do: M.new
quote do: D.new
end
end
require Hygiene
Hygiene.no_interference #=> %{}
Hygiene.no_interference #=> #HashDict<[]>
Notice that, even though the alias `M` is not available
Notice that, even though the alias `D` is not available
in the context the macro is expanded, the code above works
because `M` still expands to `Map`.
because `D` still expands to `HashDict`.
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 Map, as: M
alias HashDict, as: D
defmacro no_interference do
quote do: M.new
quote do: D.new
end
end
require Hygiene
alias SomethingElse, as: M
Hygiene.no_interference #=> %{}
alias SomethingElse, as: D
Hygiene.no_interference #=> #HashDict<[]>
In some cases, you want to access an alias or a module defined
in the caller. For such, you can use the `alias!` macro:
@@ -1036,54 +1033,54 @@ defmodule Kernel.SpecialForms do
following code:
defmodule Hygiene do
defmacrop get_length do
defmacrop get_size do
quote do
length([1,2,3])
size("hello")
end
end
def return_length do
import Kernel, except: [length: 1]
get_length
def return_size do
import Kernel, except: [size: 1]
get_size
end
end
Hygiene.return_length #=> 3
Hygiene.return_size #=> 5
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:
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:
def return_length do
import String, only: [length: 1]
get_length
def return_size do
import Dict, only: [size: 1]
get_size
end
Calling this new `return_length` will still return 3 as result.
Calling this new `return_size` will still return 5 as result.
Elixir is smart enough to delay the resolution to the latest
moment possible. So, if you call `length([1, 2, 3])` inside quote,
but no `length/1` function is available, it is then expanded in
moment possible. So, if you call `size("hello")` inside quote,
but no `size/1` function is available, it is then expanded in
the caller:
defmodule Lazy do
defmacrop get_length do
import Kernel, except: [length: 1]
defmacrop get_size do
import Kernel, except: [size: 1]
quote do
length("hello")
size([a: 1, b: 2])
end
end
def return_length do
import Kernel, except: [length: 1]
import String, only: [length: 1]
get_length
def return_size do
import Kernel, except: [size: 1]
import Dict, only: [size: 1]
get_size
end
end
Lazy.return_length #=> 5
Lazy.return_size #=> 2
## Stacktrace information
@@ -1255,7 +1252,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: "james", user: "meg"] do
iex> for {:user, name} <- [user: "john", admin: "john", user: "meg"] do
...> String.upcase(name)
...> end
["JOHN", "MEG"]
@@ -1294,42 +1291,6 @@ 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.
@@ -1715,7 +1676,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
+216 -36
View File
@@ -1,5 +1,173 @@
defmodule Kernel.Typespec do
@moduledoc false
@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.
"""
@doc """
Defines a type.
@@ -340,9 +508,13 @@ defmodule Kernel.Typespec do
end
defp get_doc_info(table, attr, caller) do
case :ets.take(table, attr) do
[{^attr, {line, doc}}] -> {line, doc}
[] -> {caller.line, nil}
# 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}
end
end
@@ -580,13 +752,13 @@ defmodule Kernel.Typespec do
defp typespec_to_ast({:type, line, :binary, [arg1, arg2]}) do
[arg1, arg2] = for arg <- [arg1, arg2], do: typespec_to_ast(arg)
case {typespec_to_ast(arg1), typespec_to_ast(arg2)} do
{arg1, 0} ->
cond do
arg2 == 0 ->
quote line: line, do: <<_ :: unquote(arg1)>>
{0, arg2} ->
arg1 == 0 ->
quote line: line, do: <<_ :: _ * unquote(arg2)>>
{arg1, arg2} ->
quote line: line, do: <<_ :: unquote(arg1), _ :: _ * unquote(arg2)>>
true ->
quote line: line, do: <<_ :: unquote(arg1) * unquote(arg2)>>
end
end
@@ -674,9 +846,9 @@ defmodule Kernel.Typespec do
defp erl_to_ex_var(var) do
case Atom.to_string(var) do
<<"_", c::binary-1, rest::binary>> ->
<<"_", c :: binary-size(1), rest :: binary>> ->
String.to_atom("_#{String.downcase(c)}#{rest}")
<<c::binary-1, rest::binary>> ->
<<c :: binary-size(1), rest :: binary>> ->
String.to_atom("#{String.downcase(c)}#{rest}")
end
end
@@ -707,32 +879,33 @@ defmodule Kernel.Typespec do
{:type, line(meta), :binary, [{:integer, line(meta), 0}, {:integer, line(unit_meta), unit}]}
end
defp typespec({:<<>>, meta, [{:::, shared_meta, [{:_, _, ctx}, {:*, _, [size, unit]}]}]}, _, _)
when is_atom(ctx) and is_integer(unit) and is_integer(size) do
{:type, line(meta), :binary, [{:integer, line(shared_meta), size}, {:integer, line(shared_meta), unit}]}
end
defp typespec({:<<>>, meta, [{:::, size_meta, [{:_, _, ctx}, size]}]}, _, _)
when is_atom(ctx) and is_integer(size) do
{:type, line(meta), :binary, [{:integer, line(size_meta), size}, {:integer, line(meta), 0}]}
end
defp typespec({:<<>>, meta, [{:::, size_meta, [{:_, _, ctx1}, size]}, {:::, unit_meta, [{:_, _, ctx2}, {:*, _, [{:_, _, ctx3}, unit]}]}]}, _, _)
when is_atom(ctx1) and is_atom(ctx2) and is_atom(ctx3) and is_integer(size) and is_integer(unit) do
{:type, line(meta), :binary, [{:integer, line(size_meta), size}, {:integer, line(unit_meta), unit}]}
end
## Handle maps and structs
defp typespec({:map, meta, args}, _vars, _caller) when args == [] or is_atom(args) do
{:type, line(meta), :map, :any}
end
defp typespec({:%{}, meta, fields} = map, vars, caller) do
defp typespec({:%{}, meta, fields}, vars, caller) do
fields =
:lists.map(fn
{k, v} ->
# TODO: Remove else once we support only OTP >18
if :erlang.system_info(:otp_release) >= '18' do
:lists.map(fn {k, v} ->
{:type, line(meta), :map_field_assoc, [typespec(k, vars, caller), typespec(v, vars, caller)]}
{:|, _, [_, _]} ->
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)
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
{:type, line(meta), :map, fields}
end
@@ -748,16 +921,14 @@ 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
types =
struct =
:lists.map(fn {field, _} ->
{field, Keyword.get(fields, field, quote(do: term()))}
end, struct)
{field, quote do: term()}
end, Map.to_list(struct))
:lists.foreach(fn {field, _} ->
unless Keyword.has_key?(struct, field) do
@@ -765,7 +936,8 @@ defmodule Kernel.Typespec do
end
end, fields)
typespec({:%{}, meta, [__struct__: module] ++ types}, vars, caller)
fields = Keyword.merge(struct, [__struct__: module] ++ fields)
typespec({:%{}, meta, fields}, vars, caller)
end
# Handle records
@@ -776,9 +948,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) ->
types =
keyword =
:lists.map(fn {field, _} ->
Keyword.get(fields, field, quote(do: term()))
{field, quote do: term()}
end, keyword)
:lists.foreach(fn {field, _} ->
@@ -787,6 +959,9 @@ 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}")
@@ -893,9 +1068,14 @@ defmodule Kernel.Typespec do
defp typespec({name, meta, arguments}, vars, caller) do
arguments = for arg <- arguments, do: typespec(arg, vars, caller)
arity = length(arguments)
type = if :erl_internal.is_type(name, arity), do: :type, else: :user_type
{type, line(meta), name, arguments}
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
end
# Handle literals
+95 -194
View File
@@ -1,15 +1,13 @@
defmodule Keyword do
@moduledoc """
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 is a list of 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 so they work similarly to
the functions you would find in the `Map` module.
behave exactly as a dictionary and mimic the API defined
by the `Dict` behaviour.
For example, `Keyword.get/3` will get the first entry matching
the given key, regardless if duplicated entries exist.
@@ -29,6 +27,7 @@ defmodule Keyword do
"""
@compile :inline_list_funcs
@behaviour Dict
@type key :: atom
@type value :: any
@@ -38,38 +37,18 @@ 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: []
@@ -78,13 +57,13 @@ 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}])
[b: 1, a: 2]
[a: 2, b: 1]
iex> Keyword.new([{:a, 1}, {:a, 2}, {:a, 3}])
[a: 3]
@@ -104,7 +83,7 @@ defmodule Keyword do
## Examples
iex> Keyword.new([:a, :b], fn (x) -> {x, x} end)
iex> Keyword.new([:a, :b], fn (x) -> {x, x} end) |> Enum.sort
[a: :a, b: :b]
"""
@@ -114,36 +93,29 @@ defmodule Keyword do
{k, v} = transform.(el)
put_new(acc, k, v)
end
:lists.foldl(fun, [], Enum.reverse(pairs))
keywords = :lists.foldl(fun, [], Enum.reverse(pairs))
:lists.reverse(keywords)
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
@@ -160,7 +132,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 setup and teardown again.
generally difficult to set-up and tear-down again.
If duplicated entries exist, the first one is returned.
Use `get_values/2` to retrieve all entries.
@@ -170,12 +142,12 @@ defmodule Keyword do
iex> keyword = [a: 1]
iex> fun = fn ->
...> # some expensive operation here
...> 13
...> :result
...> end
iex> Keyword.get_lazy(keyword, :a, fun)
1
iex> Keyword.get_lazy(keyword, :b, fun)
13
:result
"""
@spec get_lazy(t, key, (() -> value)) :: value
@@ -200,18 +172,13 @@ 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 -> {get, value})) :: {get, t} when get: term
@spec get_and_update(t, key, (value -> {value, value})) :: {value, t}
def get_and_update(keywords, key, fun)
when is_list(keywords) and is_atom(key),
do: get_and_update(keywords, [], key, fun)
@@ -229,48 +196,6 @@ 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.
@@ -280,6 +205,7 @@ defmodule Keyword do
iex> Keyword.fetch([a: 1], :a)
{:ok, 1}
iex> Keyword.fetch([a: 1], :b)
:error
@@ -301,6 +227,7 @@ defmodule Keyword do
iex> Keyword.fetch!([a: 1], :a)
1
iex> Keyword.fetch!([a: 1], :b)
** (KeyError) key :b not found in: [a: 1]
@@ -318,10 +245,6 @@ 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]
@@ -332,6 +255,7 @@ defmodule Keyword do
{k, v} when k === key -> {true, v}
{_, _} -> false
end
:lists.filtermap(fun, keywords)
end
@@ -344,6 +268,7 @@ defmodule Keyword do
iex> Keyword.keys([a: 1, b: 2])
[:a, :b]
iex> Keyword.keys([a: 1, b: 2, a: 3])
[:a, :b, :a]
@@ -356,14 +281,10 @@ 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]
@@ -380,12 +301,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([a: 1], :a, 5)
[a: 1]
iex> Keyword.delete([a: 1], :b, 5)
[a: 1]
iex> Keyword.delete([b: 2], :a, 5)
[b: 2]
"""
@spec delete(t, key, value) :: t
@@ -404,8 +325,10 @@ 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]
@@ -424,6 +347,7 @@ 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]
@@ -441,10 +365,9 @@ 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]
@@ -458,8 +381,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 setup and teardown again.
This is useful if the value is very expensive to calculate or generally
difficult to set-up and tear-down again.
## Examples
@@ -491,6 +414,7 @@ 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]
@@ -513,10 +437,6 @@ 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
@@ -527,72 +447,44 @@ defmodule Keyword do
@doc """
Merges two keyword lists into one.
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.
If they have duplicated keys, the one given in the second argument wins.
## Examples
iex> Keyword.merge([a: 1, b: 2], [a: 3, d: 4])
[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]
[a: 3, d: 4, b: 2]
"""
@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
:lists.filter(fun, keywords1) ++ keywords2
keywords2 ++ :lists.filter(fun, keywords1)
end
@doc """
Merges two keyword lists into one.
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.
If they have duplicated keys, the given function is invoked to solve conflicts.
## Examples
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 ->
iex> Keyword.merge([a: 1, b: 2], [a: 3, d: 4], fn (_k, v1, v2) ->
...> v1 + v2
...> end)
[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]
[a: 4, b: 2, d: 4]
"""
@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, keywords1, fun)
do_merge(keywords2, keywords1, fun)
end
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
defp do_merge([{k, v2}|t], acc, fun) do
do_merge t, update(acc, k, v2, fn(v1) -> fun.(k, v1, v2) end), fun
end
defp do_merge([], acc, rest, _original, _fun) do
rest ++ :lists.reverse(acc)
defp do_merge([], acc, _fun) do
acc
end
@doc """
@@ -602,6 +494,7 @@ defmodule Keyword do
iex> Keyword.has_key?([a: 1], :a)
true
iex> Keyword.has_key?([a: 1], :b)
false
@@ -623,8 +516,6 @@ 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]
@@ -659,8 +550,7 @@ 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]
@@ -692,10 +582,13 @@ defmodule Keyword do
## Examples
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]}
iex> d = [a: 1, b: 2, c: 3, d: 4]
iex> Keyword.split(d, [:a, :c, :e])
{[a: 1, c: 3], [b: 2, d: 4]}
iex> d = [a: 1, b: 2, c: 3, d: 4, a: 5]
iex> Keyword.split(d, [:a, :c, :e])
{[a: 1, c: 3, a: 5], [b: 2, d: 4]}
"""
def split(keywords, keys) when is_list(keywords) do
@@ -719,9 +612,12 @@ defmodule Keyword do
## Examples
iex> Keyword.take([a: 1, b: 2, c: 3], [:a, :c, :e])
iex> d = [a: 1, b: 2, c: 3, d: 4]
iex> Keyword.take(d, [:a, :c, :e])
[a: 1, c: 3]
iex> Keyword.take([a: 1, b: 2, c: 3, a: 5], [:a, :c, :e])
iex> d = [a: 1, b: 2, c: 3, d: 4, a: 5]
iex> Keyword.take(d, [:a, :c, :e])
[a: 1, c: 3, a: 5]
"""
@@ -736,9 +632,12 @@ defmodule Keyword do
## Examples
iex> Keyword.drop([a: 1, b: 2, c: 3], [:b, :d])
iex> d = [a: 1, b: 2, c: 3, d: 4]
iex> Keyword.drop(d, [:b, :d])
[a: 1, c: 3]
iex> Keyword.drop([a: 1, b: 2, b: 3, c: 3, a: 5], [:b, :d])
iex> d = [a: 1, b: 2, b: 3, c: 3, d: 4, a: 5]
iex> Keyword.drop(d, [:b, :d])
[a: 1, c: 3, a: 5]
"""
@@ -747,20 +646,24 @@ defmodule Keyword do
end
@doc """
Returns and removes all values associated with `key` in the keyword list.
Returns the first value associated with `key` in the keyword
list as well as the keyword list without `key`.
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, []}
"""
@@ -775,10 +678,11 @@ defmodule Keyword do
end
@doc """
Lazily returns and removes all values associated with `key` in the keyword list.
Returns the first value associated with `key` in the keyword
list as well as the keyword list without `key`.
This is useful if the default value is very expensive to calculate or
generally difficult to setup and teardown again.
generally difficult to set-up and tear-down again.
All duplicated keys are removed. See `pop_first/3` for
removing only the first entry.
@@ -788,12 +692,12 @@ defmodule Keyword do
iex> keyword = [a: 1]
iex> fun = fn ->
...> # some expensive operation here
...> 13
...> :result
...> end
iex> Keyword.pop_lazy(keyword, :a, fun)
{1, []}
iex> Keyword.pop_lazy(keyword, :b, fun)
{13, [a: 1]}
{:result, [a: 1]}
"""
@spec pop_lazy(t, key, (() -> value)) :: {value, t}
@@ -808,7 +712,9 @@ defmodule Keyword do
end
@doc """
Returns and removes the first value associated with `key` in the keyword list.
Returns the first value associated with `key` in the keyword
list as well as the keyword list without that particular occurrence
of `key`.
Duplicated keys are not removed.
@@ -816,10 +722,13 @@ 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]}
@@ -832,23 +741,15 @@ defmodule Keyword do
end
end
@doc """
Returns the keyword list itself.
# Dict callbacks
## 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
+9 -40
View File
@@ -1,45 +1,19 @@
defmodule List do
@moduledoc """
Specialized functions that only work on lists.
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.
In general, favor using the `Enum` API instead of `List`.
Index access for list is linear. Negative indexes are also
Some functions in this module expect an index. 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 perform the
one to calculate the proper index and another to the
operation.
A decision was taken to delegate most functions to
Erlang's standard library but follow Elixir's convention
of receiving the subject (in this case, a list) as the
of receiving the target (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
@@ -511,7 +485,7 @@ defmodule List do
end
@doc """
Converts a char list to an existing atom. Raises an `ArgumentError`
Converts a char list to an existing atom. Raises an `ArguementError`
if the atom does not exist.
Currently Elixir does not support conversions from char lists
@@ -617,12 +591,7 @@ defmodule List do
"""
@spec to_string(:unicode.charlist) :: String.t
def to_string(list) when is_list(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
case :unicode.characters_to_binary(list) do
result when is_binary(result) ->
result
+77 -211
View File
@@ -20,6 +20,8 @@ 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 [:===, :!==,
@@ -86,17 +88,13 @@ defmodule Macro do
def pipe(expr, call_args, position)
def pipe(expr, {:&, _, _} = call_args, _integer) do
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)
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
@@ -108,12 +106,12 @@ defmodule Macro do
end
def pipe(expr, call_args, _integer) do
raise ArgumentError, bad_pipe(expr, call_args)
bad_pipe(expr, call_args)
end
defp bad_pipe(expr, call_args) do
"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.()"
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.()"
end
@doc """
@@ -167,52 +165,6 @@ 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.
"""
@@ -227,7 +179,43 @@ defmodule Macro do
"""
@spec prewalk(t, any, (t, any -> {t, any})) :: {t, any}
def prewalk(ast, acc, fun) when is_function(fun, 2) do
traverse(ast, acc, fun, fn x, a -> {x, a} end)
{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}
end
@doc """
@@ -244,7 +232,34 @@ defmodule Macro do
"""
@spec postwalk(t, any, (t, any -> {t, any})) :: {t, any}
def postwalk(ast, acc, fun) when is_function(fun, 2) do
traverse(ast, acc, fn x, a -> {x, a} end, fun)
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)
end
@doc """
@@ -478,19 +493,14 @@ defmodule Macro do
end
# Bits containers
def to_string({:<<>>, _, parts} = ast, fun) do
def to_string({:<<>>, _, args} = ast, fun) do
if interpolated?(ast) do
fun.(ast, interpolate(ast, fun))
else
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
fun.(ast, case Enum.map_join(args, ", ", &to_string(&1, fun)) do
"<" <> rest -> "<< <" <> rest <> " >>"
rest -> "<<" <> rest <> ">>"
end)
fun.(ast, "<<" <> result <> ">>")
end
end
@@ -527,12 +537,6 @@ 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) <> ")")
@@ -571,10 +575,6 @@ 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,11 +590,6 @@ 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
@@ -634,30 +629,6 @@ 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]
@@ -695,7 +666,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_atom(func) and is_list(args) do
defp sigil_call({func, _, [{:<<>>, _, _} = bin, args]} = ast, fun) when is_list(args) do
sigil =
case Atom.to_string(func) do
<<"sigil_", name>> ->
@@ -1046,109 +1017,4 @@ 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 second its arity; returns
* `function` - a tuple as `{atom, integer`}, where the first
element is the function name and the seconds its arity; returns
`nil` if not inside a function
* `context` - the context of the environment; it can be `nil`
(default context), inside a guard or inside a match
* `aliases` - a list of two-item tuples, where the first
(default context), inside a guard or inside an assign
* `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
+16 -509
View File
@@ -1,341 +1,37 @@
defmodule Map do
@moduledoc """
A set of functions for working with maps.
A Dict implementation that works on maps.
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.
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.
"""
@type key :: any
@type value :: any
use Dict
defdelegate [keys(map), values(map), size(map), merge(map1, map2), to_list(map)], to: :maps
@compile {:inline, fetch: 2, put: 3, delete: 2, has_key?: 2}
@doc """
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)
@@ -343,131 +39,8 @@ defmodule Map do
end
@doc """
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}
Updates the value in the map with the given function.
"""
@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} ->
@@ -480,30 +53,8 @@ defmodule Map do
def update!(map, _key, _fun), do: :erlang.error({:badmap, map})
@doc """
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}}
Gets a value and updates a map in one operation.
"""
@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
@@ -517,30 +68,8 @@ defmodule Map do
def get_and_update(map, _key, _fun), do: :erlang.error({:badmap, map})
@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 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
Gets a value and updates a map only if the key exists in one operation.
"""
@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} ->
@@ -572,7 +101,6 @@ 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
@@ -581,27 +109,6 @@ defmodule Map do
:maps.remove(:__struct__, struct)
end
@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)
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
+41 -214
View File
@@ -1,270 +1,97 @@
defmodule MapSet do
@moduledoc """
A set of functions for working with sets.
A set store.
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.
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.
"""
@opaque t :: %__MODULE__{map: map}
@type value :: term
@behaviour Set
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
@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}
def difference(%MapSet{} = set1, %MapSet{} = set2) do
reduce(set2, {:cont, set1}, fn value, acc ->
{:cont, delete(acc, value)}
end) |> elem(1)
end
@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})
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}
else
true
{:cont, true}
end
end, true, map1)
catch
{:halt, false} -> false
end) |> elem(1)
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
@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)
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)}
else
acc
{:cont, acc}
end
end, %{}, map1)
%MapSet{map: map}
end) |> elem(1)
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, true)}
%{set | map: Map.put(map, value, nil)}
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
@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)
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)
else
false
end
catch
{:halt, false} -> false
end
@doc """
Converts `set` to a list.
@doc false
def reduce(%MapSet{} = set, acc, fun) do
Enumerable.List.reduce(to_list(set), acc, fun)
end
## 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: Enumerable.List.reduce(MapSet.to_list(set), acc, fun)
def reduce(set, acc, fun), do: MapSet.reduce(set, acc, fun)
def member?(set, val), do: {:ok, MapSet.member?(set, val)}
def count(set), do: {:ok, MapSet.size(set)}
end
+114 -167
View File
@@ -1,9 +1,8 @@
defmodule Module do
@moduledoc ~S'''
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.
This module provides many functions to deal with modules during
compilation time. It allows a developer to dynamically attach
documentation, add, delete and register attributes and so forth.
After a module is compiled, using many of the functions in
this module will raise errors, since it is out of their scope
@@ -66,30 +65,6 @@ 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
@@ -110,18 +85,18 @@ defmodule Module do
For the list of supported options, see Erlang's
[`:compile` module](http://www.erlang.org/doc/man/compile.html).
Multiple uses of `@compile` will accumulate instead of overriding
Several 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)
def myfun(arg) do
to_string(arg)
end
end
end
* `@doc`
@@ -136,19 +111,19 @@ defmodule Module do
### Example
defmodule M do
@doc "Hello world"
def hello do
"world"
end
defmodule M do
@doc "Hello world"
def hello do
"world"
end
@doc """
Sums `a` to `b`.
"""
def sum(a, b) do
a + b
@doc """
Sums `a` to `b`.
"""
def sum(a, b) do
a + b
end
end
end
* `@file`
@@ -159,13 +134,13 @@ defmodule Module do
### Example
defmodule M do
@doc "Hello world"
@file "hello.ex"
def hello do
"world"
defmodule M do
@doc "Hello world"
@file "hello.ex"
def hello do
"world"
end
end
end
* `@moduledoc`
@@ -177,11 +152,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`
@@ -216,28 +191,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
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)
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
def hello(_) do
:ok
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
end
end
* `@on_load`
@@ -249,31 +224,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
def load_check do
if some_condition() do
:ok
else
nil
end
end
def some_condition do
false
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`
@@ -296,18 +271,18 @@ defmodule Module do
For the list of supported warnings, see
[`:dialyzer` module](http://www.erlang.org/doc/man/dialyzer.html).
Multiple uses of `@dialyzer` will accumulate instead of overriding
Several 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)
def myfun(arg) do
M.not_a_function(arg)
end
end
end
The following attributes are part of typespecs and are also reserved by
Elixir (see `Kernel.Typespec` for more information about typespecs):
@@ -323,9 +298,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`.
@@ -489,15 +464,14 @@ 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([Module, Unknown])
iex> Module.safe_concat([Unknown, Module])
** (ArgumentError) argument error
iex> Module.safe_concat([List, Chars])
@@ -510,15 +484,14 @@ 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(Module, Unknown)
iex> Module.safe_concat(Unknown, Module)
** (ArgumentError) argument error
iex> Module.safe_concat(List, Chars)
@@ -531,10 +504,9 @@ 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.
@@ -647,12 +619,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
@@ -686,7 +658,6 @@ defmodule Module do
@doc """
Checks if the module defines the given function or macro.
Use `defines?/3` to assert for a specific type.
## Examples
@@ -706,9 +677,8 @@ 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
@@ -766,7 +736,6 @@ 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.
@@ -788,13 +757,11 @@ defmodule Module do
Module.LocalsTracker.yank(module, tuple)
end
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)
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)
end
end, tuples)
end
@@ -803,14 +770,12 @@ defmodule Module do
Returns `true` if `tuple` in `module` is marked as overridable.
"""
def overridable?(module, tuple) do
:maps.is_key(tuple, :elixir_def_overridable.overridable(module))
!!List.keyfind(:elixir_def_overridable.overridable(module), tuple, 0)
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
@@ -820,17 +785,11 @@ defmodule Module do
end
"""
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
def put_attribute(module, key, value) 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
@@ -846,12 +805,9 @@ 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. `nil` is returned
if the attribute has not been marked with `accumulate` and has not been set
to any value.
Gets the given attribute from a module. If the attribute
was marked with `accumulate` with `Module.register_attribute/3`,
a list is always returned.
The `@` macro compiles to a call to this function. For example,
the following code:
@@ -1012,7 +968,7 @@ defmodule Module do
end
@doc false
def get_attribute(module, key, stack) when is_atom(key) and (is_list(stack) or is_nil(stack)) do
def get_attribute(module, key, warn) when is_atom(key) and (is_list(warn) or is_nil(warn)) do
assert_not_compiled!(:get_attribute, module)
table = data_table_for(module)
@@ -1025,8 +981,8 @@ defmodule Module do
cond do
:lists.member(key, acc) ->
[]
is_list(stack) ->
:elixir_errors.warn warn_info(stack), "undefined module attribute @#{key}, " <>
is_list(warn) ->
:elixir_errors.warn warn_info(warn), "undefined module attribute @#{key}, " <>
"please remove access to @#{key} or explicitly set it before access"
nil
true ->
@@ -1082,9 +1038,13 @@ defmodule Module do
defp postprocess_attribute(_, value), do: value
defp get_doc_info(table, env) do
case :ets.take(table, :doc) do
[doc: {_, _} = pair] -> pair
[] -> {env.line, nil}
# TODO: Use :ets.take/2 with Erlang 18
case :ets.lookup(table, :doc) do
[doc: {_, _} = pair] ->
:ets.delete(table, :doc)
pair
[] ->
{env.line, nil}
end
end
@@ -1101,17 +1061,4 @@ 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 an Elixir module responsible for tracking
# This is a module Elixir responsible for tracking
# calls in order to extract Elixir modules' behaviour
# during compilation time.
#
# ## Implementation
#
# The implementation uses the digraph module to track
# all dependencies. The graph starts with one main vertex:
# all dependencies. The graph starts with one main vertice:
#
# * `:local` - points to local functions
#
# We can also have the following vertices:
# We also have can the following vertices:
#
# * `Module` - a module that was invoked via an import
# * `{name, arity}` - a local function/arity pair
+29 -35
View File
@@ -1,6 +1,6 @@
defmodule OptionParser do
@moduledoc """
This module contains functions to parse command line options.
This module contains functions to parse command line arguments.
"""
@type argv :: [String.t]
@@ -11,11 +11,8 @@ defmodule OptionParser do
@doc """
Parses `argv` into a keywords list.
It returns a three-element tuple as follows:
1. parsed switches,
2. remaining arguments,
3. invalid options.
It returns the parsed values, remaining arguments and the
invalid options.
## Examples
@@ -28,30 +25,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 all switches.
Switches followed by a value will be assigned the value, as a string.
By default, Elixir will try to automatically parse switches.
Switches without an argument, like `--debug` will automatically
be set to `true`.
be set to `true`. Switches followed by a value will be assigned
to the value, always as strings.
Note: Elixir also converts the switches to underscore atoms, so
Note Elixir also converts the switches to underscore atoms, as
`--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 put in the invalid options list.
underscores; such options will be reported as `:undefined` (in strict mode)
or `:invalid` (in basic mode).
## Switch Definitions
## Switches
Often it is better to explicitly list the known
Many times though, it is better to explicitly list the available
switches and their formats. The switches can be specified via two
alternative options:
different options:
* `:switches` - defines some switches. An attempt is still made to parse
switches that do not appear in the list.
* `:strict` - the switches are strict. Any switch that does not
exist in the switch list is treated as an error.
* `:strict` - the switches are strict. Any switch that is not specified
in the list is returned in the invalid options list.
* `:switches` - defines some switches. Switches that does not
exist in the switch list are still attempted to be parsed.
Note that you should only supply the `:switches` or `:strict` option. If you
supply both, an error will be raised.
Note only `:strict` or `:switches` may be given at once.
For each switch, the following types are supported:
@@ -62,14 +59,13 @@ defmodule OptionParser do
* `:float` - parses the switch as a float.
* `:string` - returns the switch as a string.
If a switch can't be parsed, it is returned in the invalid options list.
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).
The following extra "types" are supported:
* `: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]]`.
* `:keep` - keeps duplicated items in the list instead of overriding
Examples:
@@ -185,7 +181,7 @@ defmodule OptionParser do
command line)
* `{:undefined, key, value, rest}` - the option `key` is undefined
(returned in strict mode when the switch is unknown)
(returned on strict cases and the switch is unknown)
* `{:error, rest}` - there are no switches at the top of the given argv
"""
@@ -239,7 +235,7 @@ defmodule OptionParser do
end
@doc """
Receives a key-value enumerable and converts it to argv.
Receives a key-value enumerable and convert it to argv.
Keys must be atoms. Keys with nil value are discarded,
boolean values are converted to `--key` or `--no-key`
@@ -285,23 +281,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
@@ -330,8 +326,6 @@ 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] ->
@@ -383,7 +377,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
+36 -33
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:
@@ -470,11 +470,10 @@ defmodule Path do
end
defp do_join("", right, os_type), do: relative(right, os_type)
defp do_join("/", right, os_type), do: "/" <> relative(right, os_type)
defp do_join(left, "", _os_type), do: left
defp do_join(left, right, os_type), do: remove_dirsep(left, os_type) <> "/" <> relative(right, os_type)
defp remove_dirsep("", _os_type), do: ""
defp remove_dirsep("/", _os_type), do: "/"
defp remove_dirsep(bin, os_type) do
last = :binary.last(bin)
if last == ?/ or (last == ?\\ and os_type == :win32) do
@@ -629,28 +628,32 @@ defmodule Path do
end
end
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(<<"/../", 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(path),
do: do_expand_dot(path)
do: expand_dot(:binary.split(path, "/", [:global]), [])
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 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 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 major_os_type do
:os.type |> elem(0)
+9 -54
View File
@@ -3,98 +3,56 @@ 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 """
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).
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 """
Closes the `port`.
For more information, see [`:erlang.port_close/1`](http://www.erlang.org/doc/man/erlang.html#port_close-1).
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 """
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).
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 """
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).
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 """
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).
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 """
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).
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
@@ -103,7 +61,7 @@ defmodule Port do
Returns information about the `port`
or `nil` if the port is closed.
For more information, see [`:erlang.port_info/1`](http://www.erlang.org/doc/man/erlang.html#port_info-1).
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)
@@ -113,7 +71,7 @@ defmodule Port do
Returns information about the `port`
or `nil` if the port is closed.
For more information, see [`:erlang.port_info/2`](http://www.erlang.org/doc/man/erlang.html#port_info-2).
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)
@@ -130,13 +88,10 @@ defmodule Port do
end
@doc """
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).
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
+5 -64
View File
@@ -19,8 +19,6 @@ 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
@@ -29,8 +27,6 @@ defmodule Process do
@doc """
Returns all key-values in the dictionary.
Inlined by the compiler.
"""
@spec get :: [{term, term}]
def get do
@@ -51,20 +47,8 @@ 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
@@ -73,9 +57,6 @@ 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
@@ -139,10 +120,11 @@ defmodule Process do
:noconnect
"""
@spec send(dest, msg, [option]) :: :ok | :noconnect | :nosuspend when
@spec send(dest, msg, [option]) :: result when
dest: pid | port | atom | {atom, node},
msg: any,
option: :noconnect | :nosuspend
option: :noconnect | :nosuspend,
result: :ok | :noconnect | :nosuspend
def send(dest, msg, options) do
:erlang.send(dest, msg, options)
end
@@ -156,7 +138,8 @@ defmodule Process do
not refer to a process.
This function returns a timer reference, which can be read or canceled with
`read_timer/1` and `cancel_timer/1`.
`:erlang.read_timer/1`, `:erlang.start_timer/3` and `:erlang.cancel_timer/1`.
Note `time` cannot be greater than `4294967295`.
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
@@ -168,46 +151,6 @@ 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} |
@@ -256,8 +199,6 @@ 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.
+17 -21
View File
@@ -217,6 +217,15 @@ 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.
"""
@@ -254,9 +263,9 @@ defmodule Protocol do
{:error, :not_a_protocol} |
{:error, :no_beam_info}
def consolidate(protocol, types) when is_atom(protocol) do
with {:ok, info} <- beam_protocol(protocol),
{:ok, code, docs} <- change_debug_info(info, types),
do: compile(code, docs)
beam_protocol(protocol)
|> if_ok(change_debug_info types)
|> if_ok(compile)
end
@docs_chunk 'ExDc'
@@ -293,7 +302,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
@@ -311,7 +320,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)
fallback = if Any in types, do: load_impl(protocol, Any), else: nil
clauses = for {guard, mod} <- builtin,
mod in types,
@@ -325,7 +334,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)
fallback = if Any in types, do: load_impl(protocol, Any), else: nil
clauses = for struct <- structs, do: each_struct_clause_for(struct, protocol, line)
clauses = clauses ++ [fallback_clause_for(fallback, protocol, line)]
@@ -384,7 +393,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
@@ -537,10 +546,9 @@ defmodule Protocol do
if Atom.to_string(protocol) =~ "Elixir.Access" do
:elixir_errors.warn __ENV__.line, __ENV__.file,
"implementation of the Access protocol is deprecated. For customization of " <>
"the data[key] syntax, please implement the Access behaviour in your struct"
"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
@@ -581,7 +589,6 @@ 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
@@ -610,17 +617,6 @@ 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}
+15 -18
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
decreasing (first > last). Ranges are also always
decresing (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,21 +31,12 @@ defmodule Range do
@doc """
Creates a new range.
"""
@spec new(integer, integer) :: t
def new(first, last) when is_integer(first) and is_integer(last) do
def new(first, 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 `term` is a range.
It does not check if the range is valid.
Returns `true` if the given argument is a range.
## Examples
@@ -56,15 +47,13 @@ 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
@@ -89,6 +78,7 @@ defimpl Enumerable, for: Range do
end
def member?(first .. last, value) when is_integer(value) do
validate_range!(first, last)
if first <= last do
{:ok, first <= value and value <= last}
else
@@ -101,12 +91,19 @@ defimpl Enumerable, for: Range do
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 with, define and import records.
Module to work, 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:
+24 -27
View File
@@ -1,7 +1,6 @@
defmodule Regex do
@moduledoc ~S"""
Provides regular expressions for Elixir. Built on top of Erlang's `:re`
module.
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
@@ -23,7 +22,7 @@ defmodule Regex do
The modifiers available when creating a Regex are:
* `unicode` (u) - enables unicode specific patterns like `\p` and change
* `unicode` (u) - enables unicode specific patterns like `\p` and changes
modifiers like `\w`, `\W`, `\s` and friends to also match on unicode.
It expects valid unicode strings to be given on match
@@ -56,7 +55,7 @@ defmodule Regex do
## Captures
Many functions in this module handle what to capture in a regex
Many functions in this module allows what to capture in a regex
match via the `:capture` option. The supported values are:
* `:all` - all captured subpatterns including the complete matching string
@@ -380,7 +379,7 @@ defmodule Regex do
end
end
def split(%Regex{re_pattern: compiled}, string, opts) when is_binary(string) and is_list(opts) do
def split(%Regex{re_pattern: compiled}, string, opts) when is_binary(string) do
on = Keyword.get(opts, :on, :first)
case :re.run(string, compiled, [:global, capture: on]) do
{:match, matches} ->
@@ -470,13 +469,11 @@ 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(string) and is_binary(replacement) and is_list(options) do
def replace(regex, string, replacement, options) when is_binary(replacement) do
do_replace(regex, string, precompile_replacement(replacement), options)
end
def replace(regex, string, replacement, options)
when is_binary(string) and is_function(replacement) and is_list(options) do
def replace(regex, string, replacement, options) when is_function(replacement) do
{:arity, arity} = :erlang.fun_info(replacement, :arity)
do_replace(regex, string, {replacement, arity}, options)
end
@@ -498,30 +495,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
@@ -544,12 +541,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
@@ -582,7 +579,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
@@ -632,17 +629,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}
+189 -10
View File
@@ -1,16 +1,35 @@
defmodule Set do
@moduledoc ~S"""
WARNING: this module is deprecated.
This module specifies the `Set` behaviour expected to be
implemented by different representations of sets.
Use the `MapSet` module instead.
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.
"""
@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
@@ -35,10 +54,42 @@ 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)
@@ -46,12 +97,29 @@ defmodule Set do
if target1 == target2 do
target1.difference(set1, set2)
else
Enumerable.reduce(set2, {:cont, set1}, fn v, acc ->
target2.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)
@@ -59,7 +127,7 @@ defmodule Set do
if target1 == target2 do
target1.disjoint?(set1, set2)
else
Enumerable.reduce(set2, {:cont, true}, fn member, acc ->
target2.reduce(set2, {:cont, true}, fn member, acc ->
case target1.member?(set1, member) do
false -> {:cont, acc}
_ -> {:halt, false}
@@ -69,10 +137,28 @@ 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)
@@ -89,7 +175,23 @@ 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)
@@ -97,26 +199,79 @@ defmodule Set do
if target1 == target2 do
target1.intersection(set1, set2)
else
Enumerable.reduce(set1, {:cont, target1.new}, fn v, acc ->
target1.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)
@@ -128,10 +283,34 @@ 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)
@@ -139,14 +318,14 @@ defmodule Set do
if target1 == target2 do
target1.union(set1, set2)
else
Enumerable.reduce(set2, {:cont, set1}, fn v, acc ->
target2.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
Enumerable.reduce(set1, {:cont, true}, fn member, acc ->
defp do_subset?(target1, target2, set1, set2) do
target1.reduce(set1, {:cont, true}, fn member, acc ->
case target2.member?(set2, member) do
true -> {:cont, acc}
_ -> {:halt, false}
+19 -26
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. Only when `Enum.map/2` is called we actually
computation was done yet. Just when `Enum.map/2` is called we
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 said earlier that streams are composable,
That's what we meant when we first said that streams are composable,
lazy enumerables. Notice we could call `Stream.map/2` multiple times,
effectively composing the streams and keeping them lazy. The computations
are only performed when you call a function from the `Enum` module.
effectively composing the streams and they are lazy. The computations
are performed only 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 this may change at any time. For example, a function that today
and that it 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
@opaque t :: %__MODULE__{}
@type 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, %{}, fn f1 -> R.uniq(fun, f1) end
lazy enum, HashSet.new, fn f1 -> R.uniq(fun, f1) end
end
@doc """
@@ -820,15 +820,10 @@ 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
@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
def with_index(enum) do
lazy enum, 0, fn(f1) -> R.with_index(f1) end
end
## Combiners
@@ -1028,10 +1023,8 @@ defmodule Stream do
## Examples
# 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]
iex> Stream.repeatedly(&:random.uniform/0) |> Enum.take(3)
[0.4435846174457203, 0.7230402056221108, 0.94581636451987]
"""
@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 Map.has_key?(prev, value) do
if HashSet.member?(prev, value) do
skip(acc)
else
next_with_acc(unquote(f), entry, h, Map.put(prev, value, true), t)
next_with_acc(unquote(f), entry, h, HashSet.put(prev, value), t)
end
end
end
+87 -308
View File
@@ -7,12 +7,10 @@ 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 single Unicode Character,
which may be represented by one or more bytes.
For example, the codepoint "é" is two bytes:
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:
iex> byte_size("é")
2
@@ -23,21 +21,16 @@ defmodule String do
1
Furthermore, this module also presents the concept of
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):
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 ́:
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.
@@ -53,16 +46,16 @@ defmodule String do
More information about graphemes can be found in the [Unicode
Standard Annex #29](http://www.unicode.org/reports/tr29/).
The current Elixir version implements Extended Grapheme Cluster
This current Elixir version implements Extended Grapheme Cluster
algorithm.
## String and binary operations
To act according to the Unicode Standard, many functions
in this module run in linear time, as they need to traverse
To act accordingly to the Unicode Standard, many functions
in this module runs in linear time, as it needs to traverse
the whole string considering the proper Unicode codepoints.
For example, `String.length/1` will take longer as
For example, `String.length/1` is going to 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).
@@ -95,7 +88,7 @@ defmodule String do
fully, so we traverse both `prefix` and `full` strings, then
slice the `full` one, traversing it again.
A first attempt at improving it could be with ranges:
A first attempting at improving it could be with ranges:
iex> take_prefix = fn full, prefix ->
...> base = String.length(prefix)
@@ -121,7 +114,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. ")
@@ -130,7 +123,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 into multiple bytes.
codepoint in multiple bytes.
## Integer codepoints
@@ -151,7 +144,7 @@ defmodule String do
Or also via pattern matching:
iex> <<eacute::utf8>> = "á"
iex> << eacute :: utf8 >> = "á"
iex> eacute
225
@@ -169,12 +162,12 @@ defmodule String do
codepoint needs to be rejected.
This module relies on this behaviour to ignore such invalid
characters. For example, `length/1` will return
characters. For example, `length/1` is going to 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
driver that reads strings from a database will be the one
responsible to check the validity of the encoding.
## Patterns
@@ -218,7 +211,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
@@ -226,24 +219,22 @@ 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
@doc ~S"""
@doc """
Divides a string into substrings at each Unicode whitespace
occurrence with leading and trailing whitespace ignored. Groups
of whitespace are treated as a single occurrence. Divisions do
not occur on non-breaking whitespace.
occurrence with leading and trailing whitespace ignored.
## Examples
@@ -256,12 +247,9 @@ defmodule String do
iex> String.split(" foo bar ")
["foo", "bar"]
iex> String.split("no\u00a0break")
["no\u00a0break"]
"""
@spec split(t) :: [t]
defdelegate split(binary), to: String.Break
defdelegate split(binary), to: String.Unicode
@doc ~S"""
Divides a string into substrings based on a pattern.
@@ -306,7 +294,7 @@ defmodule String do
iex> String.split(" a b c ", ~r{\s}, trim: true)
["a", "b", "c"]
Splitting on empty patterns returns graphemes:
Splitting on empty patterns returns codepoints:
iex> String.split("abc", ~r{})
["a", "b", "c", ""]
@@ -331,15 +319,15 @@ defmodule String do
@spec split(t, pattern | Regex.t, Keyword.t) :: [t]
def split(string, pattern, options \\ [])
def split(string, %Regex{} = pattern, options) when is_binary(string) do
def split(string, %Regex{} = pattern, options) do
Regex.split(pattern, string, options)
end
def split(string, pattern, []) when is_binary(string) and pattern != "" do
def split(string, pattern, []) when pattern != "" do
:binary.split(string, pattern, [:global])
end
def split(string, pattern, options) when is_binary(string) do
def split(string, pattern, options) do
parts = Keyword.get(options, :parts, :infinity)
trim = Keyword.get(options, :trim, false)
pattern = maybe_compile_pattern(pattern)
@@ -449,70 +437,10 @@ defmodule String do
end
defp do_split_at(string, position) do
{byte_size, rest} = String.Unicode.split_at(string, position)
{byte_size, rest} = String.Graphemes.split_at(string, position)
{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) :: t
defdelegate normalize(string, form), to: String.Normalizer
@doc """
Converts all characters in the given string to uppercase.
@@ -529,7 +457,7 @@ defmodule String do
"""
@spec upcase(t) :: t
defdelegate upcase(binary), to: String.Casing
defdelegate upcase(binary), to: String.Unicode
@doc """
Converts all characters in the given string to lowercase.
@@ -547,7 +475,7 @@ defmodule String do
"""
@spec downcase(t) :: t
defdelegate downcase(binary), to: String.Casing
defdelegate downcase(binary), to: String.Unicode
@doc """
Converts the first character in the given string to
@@ -572,7 +500,7 @@ defmodule String do
"""
@spec capitalize(t) :: t
def capitalize(string) when is_binary(string) do
{char, rest} = String.Casing.titlecase_once(string)
{char, rest} = String.Unicode.titlecase_once(string)
char <> downcase(rest)
end
@@ -587,7 +515,7 @@ defmodule String do
"""
@spec rstrip(t) :: t
defdelegate rstrip(binary), to: String.Break
defdelegate rstrip(binary), to: String.Unicode
@doc """
Returns a string where all trailing `char`s have been removed.
@@ -599,159 +527,39 @@ defmodule String do
"""
@spec rstrip(t, char) :: t
def rstrip("", _char), do: ""
# 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
end
end
def rstrip(string, char) when is_integer(char) do
replace_trailing(string, <<char::utf8>>, "")
do_rstrip(string, "", char)
end
@doc """
Replaces all leading occurences of `match` by `replacement` of `match` in `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, "")
defp do_rstrip(<<char :: utf8, string :: binary>>, buffer, char) do
<<do_rstrip(string, <<char :: utf8, buffer :: binary>>, char) :: binary>>
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)
_ ->
acc <> string
end
defp do_rstrip(<<char :: utf8, string :: binary>>, buffer, another_char) do
<<buffer :: binary, char :: utf8, do_rstrip(string, "", another_char) :: binary>>
end
defp replace_leading(string, _match, _replacement, _prefix_size, _suffix_size, prefix) do
prefix <> string
end
@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 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 <> acc
end
end
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
defp do_rstrip(<<>>, _, _) do
<<>>
end
@doc """
Returns a string where all leading Unicode whitespaces
have been removed.
has been removed.
## Examples
@@ -759,7 +567,7 @@ defmodule String do
"abc "
"""
defdelegate lstrip(binary), to: String.Break
defdelegate lstrip(binary), to: String.Unicode
@doc """
Returns a string where all leading `char`s have been removed.
@@ -770,11 +578,12 @@ 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
@@ -783,7 +592,7 @@ defmodule String do
@doc """
Returns a string where all leading and trailing Unicode whitespaces
have been removed.
has been removed.
## Examples
@@ -808,6 +617,7 @@ defmodule String do
"""
@spec strip(t, char) :: t
def strip(string, char) do
rstrip(lstrip(string, char), char)
end
@@ -863,7 +673,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
@@ -873,11 +683,11 @@ defmodule String do
end
@doc ~S"""
Returns a new string created by replacing occurences of `pattern` in
Returns a new binary created by replacing occurences of `pattern` in
`subject` with `replacement`.
By default, it replaces all occurences, unless the `global` option is
set to `false`, where it will only replace the first one
set to `false`.
The `pattern` may be a string or a regular expression.
@@ -936,8 +746,8 @@ defmodule String do
opts
end
@doc ~S"""
Reverses the graphemes in given string.
@doc """
Reverses the given string. Works on graphemes.
## Examples
@@ -950,21 +760,6 @@ 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
@@ -1000,8 +795,6 @@ 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á")
@@ -1013,12 +806,6 @@ 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
@@ -1073,10 +860,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
@@ -1103,7 +890,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"""
@@ -1169,22 +956,14 @@ 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.Unicode
defdelegate graphemes(string), to: String.Graphemes
@compile {:inline, next_grapheme: 1, next_grapheme_size: 1}
@@ -1223,7 +1002,7 @@ defmodule String do
"""
@spec next_grapheme_size(t) :: {pos_integer, t} | nil
defdelegate next_grapheme_size(string), to: String.Unicode
defdelegate next_grapheme_size(string), to: String.Graphemes
@doc """
Returns the first grapheme from a utf8 string,
@@ -1283,10 +1062,10 @@ defmodule String do
"""
@spec length(t) :: non_neg_integer
defdelegate length(string), to: String.Unicode
defdelegate length(string), to: String.Graphemes
@doc """
Returns the grapheme at the `position` of the given utf8 `string`.
Returns the grapheme in the `position` of the given utf8 `string`.
If `position` is greater than `string` length, then it returns `nil`.
## Examples
@@ -1322,7 +1101,7 @@ defmodule String do
end
defp do_at(string, position) do
case String.Unicode.split_at(string, position) do
case String.Graphemes.split_at(string, position) do
{_, nil} -> nil
{_, rest} -> first(rest)
end
@@ -1334,8 +1113,8 @@ defmodule String do
If the offset is greater than string length, then it returns `""`.
Remember this function works with Unicode graphemes and considers
the slices to represent grapheme offsets. If you want to split
Remember this function works with Unicode codepoints and considers
the slices to represent codepoint offsets. If you want to split
on raw bytes, check `Kernel.binary_part/3` instead.
## Examples
@@ -1372,10 +1151,10 @@ defmodule String do
end
def slice(string, start, len) when start >= 0 and len >= 0 do
case String.Unicode.split_at(string, start) do
case String.Graphemes.split_at(string, start) do
{_, nil} -> ""
{start_bytes, rest} ->
{len_bytes, _} = String.Unicode.split_at(rest, len)
{len_bytes, _} = String.Graphemes.split_at(rest, len)
binary_part(string, start_bytes, len_bytes)
end
end
@@ -1443,7 +1222,7 @@ defmodule String do
def slice("", _.._), do: ""
def slice(string, first..-1) when first >= 0 do
case String.Unicode.split_at(string, first) do
case String.Graphemes.split_at(string, first) do
{_, nil} ->
""
{start_bytes, _} ->
@@ -1757,7 +1536,7 @@ defmodule String do
"""
@spec jaro_distance(t, t) :: float
@spec jaro_distance(t, t) :: 0..1
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 =
+13 -29
View File
@@ -1,8 +1,8 @@
defmodule Supervisor do
@moduledoc ~S"""
@moduledoc """
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 proceed to the `Supervisor.Spec` module documentation to learn
and then follow 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 perform some particular action on supervisor
* You need to do 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 dynamic supervision
new children directly, while the 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 defined the child to have a restart strategy of transient. This
* We have define the child to have 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 exiting my worker?
So one may ask: which exit reason should I choose when existing my worker?
There are three options:
* `:normal` - in such cases, the exit won't be logged, there is no restart
in transient mode and linked processes do not exit
* `:normal` - on such cases, the exit won't be logged, there is no restart
on transient mode and linked processes do not 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
* `:shutdown` or `{:shutdown, term}` - on such cases, the exit won't be
logged, there is no restart on transient mode and linked processes exit
with the same reason unless trapping exits
* 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
* 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
unless trapping exits
## Name Registration
@@ -463,22 +463,6 @@ 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 defined workers and supervisors
In the example above, we have 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 a terminated child process should be restarted
* `:restart` - defines when the child process should restart
* `: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, this is a mechanism
* `:infinity` - if the child process is a supervisor, it is a mechanism
to give the subtree enough time to shutdown. It can also be used with
workers with care.
* Finally, the value can also be any integer meaning that the supervisor tells
* Finally, it 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
+13 -171
View File
@@ -25,15 +25,15 @@ defmodule System do
end
end
# 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
# Tries to run "git describe --always --tags". In the case of success returns
# the most recent tag. If that is not available, tries to read the commit hash
# from .git/HEAD. If that fails, returns an empty string.
defmacrop get_revision do
defmacrop get_describe do
dirpath = :filename.join(__DIR__, "../../../.git")
case :file.read_file_info(dirpath) do
{:ok, _} ->
if :os.find_executable('git') do
data = :os.cmd('git rev-parse --short HEAD')
data = :os.cmd('git describe --always --tags')
strip_re(data, "\n")
else
read_stripped(:filename.join(".git", "HEAD"))
@@ -47,21 +47,6 @@ 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.
@@ -73,11 +58,11 @@ defmodule System do
@doc """
Elixir build information.
Returns a keyword list with Elixir version, git short revision hash and compilation date.
Returns a keyword list with Elixir version, git tag info and compilation date.
"""
@spec build_info() :: map
def build_info do
%{version: version, date: get_date, revision: get_revision}
%{version: version, tag: get_describe, date: get_date}
end
@doc """
@@ -299,9 +284,9 @@ defmodule System do
Sets a new value for each environment variable corresponding
to each key in `dict`.
"""
@spec put_env(Enumerable.t) :: :ok
def put_env(enum) do
Enum.each enum, fn {key, val} -> put_env key, val end
@spec put_env(Dict.t) :: :ok
def put_env(dict) do
Enum.each dict, fn {key, val} -> put_env key, val end
end
@doc """
@@ -382,13 +367,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}
@@ -452,16 +437,7 @@ defmodule System do
{into, opts} = cmd_opts(opts, [:use_stdio, :exit_status, :binary, :hide, args: args], "")
{initial, fun} = Collectable.into(into)
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
do_cmd Port.open({:spawn_executable, cmd}, opts), initial, fun
end
defp do_cmd(port, acc, fun) do
@@ -469,7 +445,7 @@ defmodule System do
{^port, {:data, data}} ->
do_cmd(port, fun.(acc, {:cont, data}), fun)
{^port, {:exit_status, status}} ->
{acc, status}
{fun.(acc, :done), status}
end
end
@@ -502,144 +478,10 @@ 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
+92 -252
View File
@@ -1,33 +1,31 @@
defmodule Task do
@moduledoc """
Conveniences for spawning and awaiting tasks.
Conveniences for spawning and awaiting for 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 convert sequential code into concurrent code
by computing a value asynchronously:
for tasks is to compute 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 waited on by their caller
process (and only their caller) as shown in the example above.
Tasks spawned with `async` can be awaited on by its caller
process (and only its 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 tree and dynamically spawned
started as part of supervision trees and dynamically spawned
in remote nodes. We will explore all three scenarios next.
## async and await
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.
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.
`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
@@ -36,7 +34,7 @@ defmodule Task do
There are two important things to consider when using async:
1. If you are using async tasks, you must await a reply
1. If you are using async tasks, you must await for a reply
as they are *always* sent. If you are not expecting a reply,
consider using `Task.start_link/1` detailed below
@@ -44,15 +42,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 completing computation of the result. If this
no purpose in computing the result until the end. 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 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.
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.
## Supervised tasks
@@ -70,7 +68,7 @@ defmodule Task do
]
Since these tasks are supervised and not directly linked to
the caller, they cannot be waited on. Note `start_link/1`,
the caller, they cannot be awaited on. Note `start_link/1`,
unlike `async/1`, returns `{:ok, pid}` (which is
the result expected by supervision trees).
@@ -146,10 +144,8 @@ defmodule Task do
* `:ref` - the task monitor reference
* `:owner` - the PID of the process that started the task
"""
defstruct pid: nil, ref: nil, owner: nil
defstruct pid: nil, ref: nil
@type t :: %__MODULE__{}
@@ -173,7 +169,7 @@ defmodule Task do
Starts a task.
This is only used when the task is used for side-effects
(i.e. no interest in the returned result) and it should not
(i.e. no interest in its return result) and it should not
be linked to the current process.
"""
@spec start(fun) :: {:ok, pid}
@@ -185,7 +181,7 @@ defmodule Task do
Starts a task.
This is only used when the task is used for side-effects
(i.e. no interest in the returned result) and it should not
(i.e. no interest in its return result) and it should not
be linked to the current process.
"""
@spec start(module, atom, [term]) :: {:ok, pid}
@@ -214,59 +210,16 @@ defmodule Task do
end
@doc """
Starts a task that must be awaited on.
Starts a task that can be awaited on.
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.
This function spawns a process that is linked to and monitored
by the caller process. A `Task` struct is returned containing
the relevant information.
Read the `Task` module documentation for more info on general
usage of `async/1` and `async/3`.
## 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
## Task's 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.
@@ -274,11 +227,10 @@ defmodule Task do
@spec async(module, atom, [term]) :: t
def async(mod, fun, args) do
mfa = {mod, fun, args}
owner = self()
pid = Task.Supervised.spawn_link(owner, get_info(owner), mfa)
pid = :proc_lib.spawn_link(Task.Supervised, :async, [self, get_info(self), mfa])
ref = Process.monitor(pid)
send(pid, {owner, ref})
%Task{pid: pid, ref: ref, owner: owner}
send(pid, {self(), ref})
%Task{pid: pid, ref: ref}
end
defp get_info(self) do
@@ -298,32 +250,18 @@ 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 terminate the task if it is not trapping exits.
exit signal will close 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 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.
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.
"""
@spec await(t, timeout) :: term | no_return
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
def await(%Task{ref: ref}=task, timeout \\ 5000) do
receive do
{^ref, reply} ->
Process.demonitor(ref, [:flush])
@@ -343,32 +281,62 @@ 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 returns `nil`
if no task was found. It exits if the task has failed.
success alongside the matching task. It raises in case
the found task failed or `nil` if no task was found.
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: ^ref} = task ->
%Task{ref: task_ref} = t when ref == task_ref ->
Process.demonitor(ref, [:flush])
{reply, task}
{reply, t}
%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
if Enum.find(tasks, find) do
exit({reason(reason, proc), {__MODULE__, :find, [tasks, msg]}})
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
end
end
@@ -377,10 +345,9 @@ defmodule Task do
end
@doc """
Yields for a task reply in the given time interval.
Yields, temporarily, for a task reply.
Returns `{:ok, reply}` if the reply is received, `{:exit, reason}`
if the task exited or `nil` if no reply arrived.
Returns `{:ok, reply}` if the reply is received.
A timeout, in milliseconds, can be given with default value
of `5000`. In case of the timeout, this function will return `nil`
@@ -390,144 +357,37 @@ 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 waits
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 wait for the duration of the timeout
awaiting the message.
"""
@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
@spec yield(t, timeout) :: {:ok, term} | nil
def yield(%Task{ref: ref} = task, timeout \\ 5_000) do
receive do
{^ref, reply} ->
Process.demonitor(ref, [:flush])
{:ok, reply}
{:DOWN, ^ref, _, proc, :noconnection} ->
exit({reason(:noconnection, proc), {__MODULE__, :yield, [task, timeout]}})
{:DOWN, ^ref, _, _, reason} ->
{:exit, reason}
{:DOWN, ^ref, _, proc, reason} ->
exit({reason(reason, proc), {__MODULE__, :yield, [task, timeout]}})
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,
`{:exit, reason}` if the task exited abornormally, otherwise `nil`.
otherwise `nil`.
The shutdown method is either a timeout or `:brutal_kill`. In case
The shutdown method is either a timeout or `:brutal_kill`. In the 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 case the task exits abnormally, or a
timeout shutdown kills the task, this function will exit with the same reason.
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.
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
@@ -540,32 +400,18 @@ 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} | {:exit, term} | nil
@spec shutdown(t, timeout | :brutal_kill) :: {:ok, 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
# 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
def shutdown(%Task{pid: pid} = task, :brutal_kill) do
exit(pid, :kill)
case shutdown_receive(task, :brutal_kill, :infinity) do
{:down, proc, :noconnection} ->
exit({reason(:noconnection, proc), {__MODULE__, :shutdown, [task, :brutal_kill]}})
{:down, _, reason} ->
{:exit, reason}
{:error, reason} ->
exit({reason, {__MODULE__, :shutdown, [task, :brutal_kill]}})
result ->
result
end
@@ -574,10 +420,8 @@ defmodule Task do
def shutdown(%Task{pid: pid} = task, timeout) do
exit(pid, :shutdown)
case shutdown_receive(task, :shutdown, timeout) do
{:down, proc, :noconnection} ->
exit({reason(:noconnection, proc), {__MODULE__, :shutdown, [task, timeout]}})
{:down, _, reason} ->
{:exit, reason}
{:error, reason} ->
exit({reason, {__MODULE__, :shutdown, [task, timeout]}})
result ->
result
end
@@ -618,7 +462,7 @@ defmodule Task do
{:DOWN, ^ref, _, _, :killed} when type == :brutal_kill ->
flush_reply(ref)
{:DOWN, ^ref, _, proc, reason} ->
flush_reply(ref) || {:down, proc, reason}
flush_reply(ref) || {:error, reason(reason, proc)}
after
timeout ->
Process.exit(task.pid, :kill)
@@ -633,8 +477,4 @@ 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
+19 -34
View File
@@ -1,8 +1,6 @@
defmodule Task.Supervised do
@moduledoc false
@ref_timeout 5_000
def start(info, fun) do
{:ok, :proc_lib.spawn(__MODULE__, :noreply, [info, fun])}
end
@@ -11,38 +9,24 @@ defmodule Task.Supervised do
{:ok, :proc_lib.spawn_link(__MODULE__, :noreply, [info, fun])}
end
def start_link(caller, link, info, fun) do
{:ok, spawn_link(caller, link, info, fun)}
def start_link(caller, info, fun) do
:proc_lib.start_link(__MODULE__, :reply, [caller, info, fun])
end
def spawn_link(caller, link \\ :nolink, info, fun) do
:proc_lib.spawn_link(__MODULE__, :reply, [caller, link, info, fun])
end
def reply(caller, link, info, mfa) do
def async(caller, info, mfa) do
initial_call(mfa)
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
ref = receive do: ({^caller, ref} -> ref)
send caller, {ref, do_apply(info, mfa)}
end
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
def reply(caller, info, mfa) do
initial_call(mfa)
:erlang.link(caller)
:proc_lib.init_ack({:ok, self()})
ref =
# There is a race condition on this operation when working across
# node that manifests if a "Task.Supervisor.async/2" call is made
# node that manifests if a "Task.Supervisor.async/1" call is made
# while the supervisor is busy spawning previous tasks.
#
# Imagine the following workflow:
@@ -57,12 +41,13 @@ 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.
#
# The same situation could occur with "Task.Supervisor.async_nolink/2",
# except a monitor is used instead of a link.
timeout ->
exit(:timeout)
end
receive do
{^caller, ref} -> ref
after
5000 -> exit(:timeout)
end
send caller, {ref, do_apply(info, mfa)}
end
def noreply(info, mfa) do
+10 -43
View File
@@ -1,6 +1,6 @@
defmodule Task.Supervisor do
@moduledoc """
A task supervisor.
A tasks 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 them in the `GenServer` docs.
`GenServer`. Read more about it 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,8 +48,7 @@ defmodule Task.Supervisor do
Starts a task that can be awaited on.
The `supervisor` must be a reference as defined in `Task.Supervisor`.
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.
For more information on tasks, check the `Task` module.
"""
@spec async(Supervisor.supervisor, fun) :: Task.t
def async(supervisor, fun) do
@@ -60,47 +59,15 @@ defmodule Task.Supervisor do
Starts a task that can be awaited on.
The `supervisor` must be a reference as defined in `Task.Supervisor`.
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.
For more information on tasks, check the `Task` module.
"""
@spec async(Supervisor.supervisor, module, atom, [term]) :: Task.t
def async(supervisor, module, fun, args) do
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}]
args = [self, get_info(self), {module, fun, args}]
{:ok, pid} = Supervisor.start_child(supervisor, args)
ref = Process.monitor(pid)
send pid, {owner, ref}
%Task{pid: pid, ref: ref, owner: owner}
send pid, {self(), ref}
%Task{pid: pid, ref: ref}
end
@doc """
@@ -108,7 +75,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 """
@@ -116,7 +83,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 """
+9 -11
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` containing the
Creates a tuple of size `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 `index`.
Inserts `value` into `tuple` at the given zero-based `index`.
Raises an `ArgumentError` if `index` is negative or greater than the
length of `tuple`. Index is zero-based.
length of `tuple`.
Inlined by the compiler.
@@ -49,9 +49,9 @@ defmodule Tuple do
end
@doc """
Inserts an element at the end of a tuple.
Inserts an element into the end of a tuple.
Returns a new tuple with the element appended at the end, and contains
Returns a new tuple which has one element more than `tuple`, 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 given `index` from `tuple`.
Deletes the element at the zero-based `index` from `tuple`.
Raises an `ArgumentError` if `index` is negative or greater than
or equal to the length of `tuple`. Index is zero-based.
or equal to the length of `tuple`.
Inlined by the compiler.
@@ -91,8 +91,6 @@ defmodule Tuple do
@doc """
Converts a tuple to a list.
Returns a new list with all the tuple elements.
Inlined by the compiler.
## Examples
+13 -15
View File
@@ -75,7 +75,6 @@ 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
@@ -178,7 +177,7 @@ defmodule URI do
end
@doc """
Encodes a string as "x-www-form-urlencoded".
Encodes a string as "x-www-urlencoded".
## Example
@@ -223,7 +222,7 @@ defmodule URI do
end
@doc """
Decodes a string as "x-www-form-urlencoded".
Decodes a string as "x-www-urlencoded".
## Examples
@@ -340,7 +339,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
if byte_size(s) > 0, do: s, else: nil
end
end
@@ -362,24 +361,23 @@ defimpl String.Chars, for: URI do
end
# Based on http://tools.ietf.org/html/rfc3986#section-5.3
authority = extract_authority(uri)
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
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
+8 -8
View File
@@ -10,7 +10,7 @@ defmodule Version do
## Versions
In a nutshell, a version is represented by three numbers:
In a nutshell, a version is given by three numbers:
MAJOR.MINOR.PATCH
@@ -24,8 +24,8 @@ defmodule Version do
## Struct
The version is represented by the Version struct and fields
are named according to Semver: `:major`, `:minor`, `:patch`,
The version is represented by the Version struct and it has its
fields 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
parsable, or `Version.InvalidVersionError` if `version` is not parsable.
parseable, or `Version.InvalidVersionError` if `version` is not parseable.
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 parsable.
Raises a `Version.InvalidVersionError` exception if `version` is not parseable.
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
@@ -317,7 +317,7 @@ defmodule Version do
(?:\.(\d+))? # minor
(?:\.(\d+))? # patch
(?:\-([\d\w\.\-]+))? # pre
(?:\+([\d\w\.\-]+))? # build
(?:\+([\d\w\-]+))? # build
$/x
@spec parse_requirement(String.t) :: {:ok, Version.Requirement.t} | :error
-168
View File
@@ -1,168 +0,0 @@
# 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())`
`iolist()` | `maybe_improper_list(byte() \| binary() \| iolist(), binary() \| [])`
`iodata()` | `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
@@ -1,102 +0,0 @@
# 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.
+11 -33
View File
@@ -7,7 +7,6 @@
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]).
@@ -30,31 +29,13 @@ start(_Type, _Args) ->
error -> [binary]
end,
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}]),
%% 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
end,
Encoding = file:native_name_encoding(),
case Encoding of
@@ -74,12 +55,11 @@ start(_Type, _Args) ->
{<<"https">>, 443},
{<<"ldap">>, 389}],
URIConfig = [{{uri, Scheme}, Port} || {Scheme, Port} <- URIs],
CompilerOpts = #{docs => true, ignore_module_conflict => false,
debug_info => true, warnings_as_errors => false},
CompilerOpts = [{docs, true}, {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, CompilerOpts}
{compiler_options, orddict:from_list(CompilerOpts)}
| URIConfig],
Tab = elixir_config:new(Config),
case elixir_sup:start_link() of
@@ -93,6 +73,7 @@ start(_Type, _Args) ->
stop(Tab) ->
elixir_config:delete(Tab).
config_change(_Changed, _New, _Remove) ->
ok.
@@ -192,8 +173,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, ParsedVars, ParsedScope} = elixir_scope:load_binding(Binding, Scope),
ParsedEnv = Env#{vars := ParsedVars},
{ParsedBinding, ParsedScope} = elixir_scope:load_binding(Binding, Scope),
ParsedEnv = Env#{vars := [K || {K, _} <- ParsedScope#elixir_scope.vars]},
{Erl, NewEnv, NewScope} = quoted_to_erl(Tree, ParsedEnv, ParsedScope),
case Erl of
@@ -252,7 +233,6 @@ 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)}};
@@ -260,8 +240,6 @@ 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)}};
+6 -9
View File
@@ -1,23 +1,21 @@
-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 pin_guard and map_key
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)
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 map of defined variables and their alias
vars=[], %% a dict 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 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
counter=[], %% a dict counting the variables defined
file=(<<"nofile">>) %% the current scope filename
}).
-record(elixir_quote, {
@@ -29,8 +27,7 @@
imports_hygiene=true,
unquote=true,
unquoted=false,
escape=false,
generated=false
escape=false
}).
-record(elixir_tokenizer, {
+4 -49
View File
@@ -81,11 +81,7 @@ ensure_loaded(Meta, Ref, E) ->
catch
error:undef ->
Kind = case lists:member(Ref, ?m(E, context_modules)) of
true ->
case ?m(E, module) of
Ref -> circular_module;
_ -> scheduled_module
end;
true -> scheduled_module;
false -> unloaded_module
end,
elixir_errors:form_error(Meta, ?m(E, file), ?MODULE, {Kind, Ref})
@@ -141,49 +137,8 @@ lookup(Else, Dict, Counter) ->
%% Errors
format_error({unloaded_module, Module}) ->
io_lib:format("module ~ts is not loaded and could not be found", [inspect(Module)]);
io_lib:format("module ~ts is not loaded and could not be found", [elixir_aliases:inspect(Module)]);
format_error({scheduled_module, 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)]).
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)]).
+5 -6
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, ?ann(Meta), lists:reverse(Final)}, FinalS}.
{{bin, ?line(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, ?ann(Meta), {string, 0, binary_to_list(H)}, default, default};
{bin_element, ?line(Meta), {string, 0, binary_to_list(H)}, default, default};
false ->
case types_require_conversion(Types) of
true ->
{bin_element, ?ann(Meta), {string, 0, elixir_utils:characters_to_list(H)}, default, Types};
{bin_element, ?line(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, ?ann(Meta), Expr, Size, Types}|Acc])
false -> build_bitstr_each(Fun, T, Meta, NS, [{bin_element, ?line(Meta), Expr, Size, Types}|Acc])
end;
_ ->
build_bitstr_each(Fun, T, Meta, NS, [{bin_element, ?ann(Meta), Expr, Size, Types}|Acc])
build_bitstr_each(Fun, T, Meta, NS, [{bin_element, ?line(Meta), Expr, Size, Types}|Acc])
end.
types_require_conversion([End|T]) when End == little; End == big -> types_require_conversion(T);
@@ -224,7 +224,6 @@ 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.
+53 -48
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/3, get_pairs/3, get_pairs/4,
-export([match/3, clause/6, clauses/3, guards/4, get_pairs/3, get_pairs/4,
extract_splat_guards/1, extract_guards/1]).
-include("elixir.hrl").
@@ -24,34 +24,33 @@ 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=#{}, backup_vars=Vars}),
match_vars=ordsets:new(), 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(Meta, Fun, Args, Expr, Guards, S) when is_list(Meta) ->
clause(Line, Fun, Args, Expr, Guards, S) when is_integer(Line) ->
{TArgs, SA} = match(Fun, Args, S#elixir_scope{extra_guards=[]}),
{TExpr, SE} = elixir_translator:translate(Expr,
SA#elixir_scope{extra_guards=nil, export_vars=S#elixir_scope.export_vars}),
{TExpr, SE} = elixir_translator:translate(Expr, SA#elixir_scope{extra_guards=nil}),
Extra = SA#elixir_scope.extra_guards,
TGuards = guards(Guards, Extra, SA),
{{clause, ?ann(Meta), TArgs, TGuards, unblock(TExpr)}, SE}.
TGuards = guards(Line, Guards, Extra, SA),
{{clause, Line, TArgs, TGuards, unblock(TExpr)}, SE}.
% Translate/Extract guards from the given expression.
guards(Guards, Extra, S) ->
guards(Line, Guards, Extra, S) ->
SG = S#elixir_scope{context=guard, extra_guards=nil},
case Guards of
[] -> case Extra of [] -> []; _ -> [Extra] end;
_ -> [translate_guard(Guard, Extra, SG) || Guard <- Guards]
_ -> [translate_guard(Line, Guard, Extra, SG) || Guard <- Guards]
end.
translate_guard(Guard, Extra, S) ->
[element(1, elixir_translator:translate(Guard, S))|Extra].
translate_guard(Line, Guard, Extra, S) ->
[element(1, elixir_translator:translate(elixir_quote:linify(Line, Guard), S))|Extra].
extract_guards({'when', _, [Left, Right]}) -> {Left, extract_or_guards(Right)};
extract_guards(Else) -> {Else, []}.
@@ -70,7 +69,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) ->
@@ -80,7 +79,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(X, SAcc),
{TX, TS} = each_clause(Meta, X, SAcc),
{TX, {elixir_scope:mergec(S, TS), [TS#elixir_scope.export_vars|VAcc]}}
end,
@@ -89,7 +88,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
@@ -98,18 +97,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, maps:to_list(AllVars)),
end, TS, AllVars),
% Expand all clauses by adding a match operation at the end
% that defines variables missing in one clause to the others.
expand_clauses(?ann(Meta), TClauses, CV, FinalVars, [], FS).
expand_clauses(?line(Meta), TClauses, CV, FinalVars, [], FS).
expand_clauses(Ann, [Clause|T], [ClauseVars|V], FinalVars, Acc, S) ->
expand_clauses(Line, [Clause|T], [ClauseVars|V], FinalVars, Acc, S) ->
case generate_match_vars(FinalVars, ClauseVars, [], []) of
{[], []} ->
expand_clauses(Ann, T, V, FinalVars, [Clause|Acc], S);
expand_clauses(Line, T, V, FinalVars, [Clause|Acc], S);
{Left, Right} ->
MatchExpr = generate_match(Ann, Left, Right),
MatchExpr = generate_match(Line, Left, Right),
ClauseExprs = element(5, Clause),
[Final|RawClauseExprs] = lists:reverse(ClauseExprs),
@@ -123,8 +122,8 @@ expand_clauses(Ann, [Clause|T], [ClauseVars|V], FinalVars, Acc, S) ->
{[UserVar, MatchExpr, Final|RawClauseExprs], S};
_ ->
{VarName, _, SS} = elixir_scope:build_var('_', S),
StorageVar = {var, Ann, VarName},
StorageExpr = {match, Ann, StorageVar, Final},
StorageVar = {var, Line, VarName},
StorageExpr = {match, Line, StorageVar, Final},
{[StorageVar, MatchExpr, StorageExpr|RawClauseExprs], SS}
end;
false ->
@@ -132,22 +131,34 @@ expand_clauses(Ann, [Clause|T], [ClauseVars|V], FinalVars, Acc, S) ->
end,
FinalClause = setelement(5, Clause, lists:reverse(FinalClauseExprs)),
expand_clauses(Ann, T, V, FinalVars, [FinalClause|Acc], FS)
expand_clauses(Line, T, V, FinalVars, [FinalClause|Acc], FS)
end;
expand_clauses(_Ann, [], [], _FinalVars, Acc, S) ->
expand_clauses(_Line, [], [], _FinalVars, Acc, S) ->
{lists:reverse(Acc), S}.
% Handle each key/value clause pair and translate them accordingly.
each_clause({match, Meta, [Condition], Expr}, S) ->
each_clause(Export, {match, Meta, [Condition], Expr}, S) ->
Fun = wrap_export_fun(Export, fun elixir_translator:translate_args/2),
{Arg, Guards} = extract_guards(Condition),
clause(Meta, fun elixir_translator:translate_args/2, [Arg], Expr, Guards, S);
clause(?line(Meta), Fun, [Arg], Expr, Guards, S);
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}.
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.
% Check if the given expression is a match tuple.
% This is a small optimization to allow us to change
@@ -173,30 +184,24 @@ has_match_tuple(_) -> false.
% by picking one value as reference and retrieving
% its previous value.
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,
Value = {Ref, Counter, Safe},
normalize_vars(Key, Value, #elixir_scope{vars=Vars, export_vars=ClauseVars} = S) ->
VS = S#elixir_scope{
vars=maps:put(Key, Value, Vars),
export_vars=maps:put(Key, Value, ClauseVars)
vars=orddict:store(Key, Value, Vars),
export_vars=orddict:store(Key, Value, ClauseVars)
},
Expr = case orddict:find(Key, Vars) of
{ok, {PreValue, _}} -> {var, 0, PreValue};
error -> {atom, 0, nil}
end,
{{Key, Value, Expr}, VS}.
% Generate match vars by checking if they were updated
% or not and assigning the previous value.
generate_match_vars([{Key, Value, Expr}|T], ClauseVars, Left, Right) ->
case maps:find(Key, ClauseVars) of
case orddict:find(Key, ClauseVars) of
{ok, Value} ->
generate_match_vars(T, ClauseVars, Left, Right);
{ok, Clause} ->
@@ -211,11 +216,11 @@ generate_match_vars([{Key, Value, Expr}|T], ClauseVars, Left, Right) ->
generate_match_vars([], _ClauseVars, Left, Right) ->
{Left, Right}.
generate_match(Ann, [Left], [Right]) ->
{match, Ann, Left, Right};
generate_match(Line, [Left], [Right]) ->
{match, Line, Left, Right};
generate_match(Ann, LeftVars, RightVars) ->
{match, Ann, {tuple, Ann, LeftVars}, {tuple, Ann, RightVars}}.
generate_match(Line, LeftVars, RightVars) ->
{match, Line, {tuple, Line, LeftVars}, {tuple, Line, 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=#{},
compilation_status=#{}
mod_ets=dict:new(),
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 maps:find(Path, Current) of
case orddict:find(Path, Current) of
{ok, true} ->
{reply, loaded, Config};
{ok, {Ref, List}} when is_list(List), is_reference(Ref) ->
Queued = maps:put(Path, {Ref, [From|List]}, Current),
Queued = orddict:store(Path, {Ref, [From|List]}, Current),
{reply, {queued, Ref}, Config#elixir_code_server{loaded=Queued}};
error ->
Queued = maps:put(Path, {make_ref(), []}, Current),
Queued = orddict:store(Path, {make_ref(), []}, Current),
{reply, proceed, Config#elixir_code_server{loaded=Queued}}
end;
handle_call(loaded, _From, Config) ->
{reply, [F || {F, true} <- maps:to_list(Config#elixir_code_server.loaded)], Config};
{reply, [F || {F, true} <- Config#elixir_code_server.loaded], Config};
handle_call({compilation_status, CompilerPid}, _From, Config) ->
CompilationStatusList = Config#elixir_code_server.compilation_status,
CompilationStatusListNew = maps:remove(CompilerPid, CompilationStatusList),
CompilationStatus = maps:get(CompilerPid, CompilationStatusList),
CompilationStatusList = Config#elixir_code_server.compilation_status,
CompilationStatusListNew = orddict:erase(CompilerPid, CompilationStatusList),
CompilationStatus = orddict:fetch(CompilerPid, CompilationStatusList),
{reply, CompilationStatus,
Config#elixir_code_server{compilation_status=CompilationStatusListNew}};
@@ -76,35 +76,35 @@ handle_call(Request, _From, Config) ->
handle_cast({register_warning, CompilerPid}, Config) ->
CompilationStatusCurrent = Config#elixir_code_server.compilation_status,
CompilationStatusNew = maps:put(CompilerPid, error, CompilationStatusCurrent),
CompilerOptions = elixir_config:get(compiler_options),
case maps:find(warnings_as_errors, CompilerOptions) of
CompilationStatusNew = orddict:store(CompilerPid, error, CompilationStatusCurrent),
CompilerOptions = elixir_config:get(compiler_options),
case orddict: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 = maps:put(CompilerPid, ok, CompilationStatusCurrent),
CompilationStatusNew = orddict:store(CompilerPid, ok, CompilationStatusCurrent),
{noreply, Config#elixir_code_server{compilation_status=CompilationStatusNew}};
handle_cast({loaded, Path}, Config) ->
Current = Config#elixir_code_server.loaded,
case maps:find(Path, Current) of
case orddict:find(Path, Current) of
{ok, true} ->
{noreply, Config};
{ok, {Ref, List}} when is_list(List), is_reference(Ref) ->
_ = [Pid ! {elixir_code_server, Ref, loaded} || {Pid, _Tag} <- lists:reverse(List)],
Done = maps:put(Path, true, Current),
Done = orddict:store(Path, true, Current),
{noreply, Config#elixir_code_server{loaded=Done}};
error ->
Done = maps:put(Path, true, Current),
Done = orddict:store(Path, true, Current),
{noreply, Config#elixir_code_server{loaded=Done}}
end;
handle_cast({unload_files, Files}, Config) ->
Current = Config#elixir_code_server.loaded,
Unloaded = maps:without(Files, Current),
Unloaded = lists:foldl(fun(File, Acc) -> orddict:erase(File, Acc) end, Current, Files),
{noreply, Config#elixir_code_server{loaded=Unloaded}};
handle_cast({return_module_name, 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=maps:put(Ref, Mod, ModEts)}}.
{Ref, Config#elixir_code_server{mod_ets=dict:store(Ref, Mod, ModEts)}}.
undefmodule(Ref, #elixir_code_server{mod_ets=ModEts} = Config) ->
case maps:find(Ref, ModEts) of
case dict:find(Ref, ModEts) of
{ok, Mod} ->
ets:delete(elixir_modules, Mod),
Config#elixir_code_server{mod_ets=maps:remove(Ref, ModEts)};
Config#elixir_code_server{mod_ets=dict:erase(Ref, ModEts)};
error ->
Config
end.
+22 -29
View File
@@ -6,10 +6,10 @@
%% Public API
get_opt(Key) ->
Map = elixir_config:get(compiler_options),
case maps:find(Key, Map) of
{ok, Value} -> Value;
error -> false
Dict = elixir_config:get(compiler_options),
case lists:keyfind(Key, 1, Dict) of
false -> false;
{Key, Value} -> Value
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, true}} || {Name, Kind, Value, _} <- Vars],
Dict = [{{Name, Kind}, {Value, 0}} || {Name, Kind, Value, _} <- Vars],
S = elixir_env:env_to_scope_with_vars(E, Dict),
{Expr, EE, _S} = elixir:quoted_to_erl(Forms, E, S),
@@ -78,8 +78,9 @@ code_loading_compilation(Forms, Vars, #{line := Line} = E) ->
%% Pass {native, false} to speed up bootstrap
%% process when native is set to true
ErlOpts = options() -- [native, warn_missing_spec],
inner_module(Form, ErlOpts, [{bootstrap, true}], E, fun(_, Binary) ->
AllOpts = options(),
FinalOpts = AllOpts -- [native, warn_missing_spec],
inner_module(Form, FinalOpts, true, E, fun(_, Binary) ->
%% If we have labeled locals, anonymous functions
%% were created and therefore we cannot ditch the
%% module
@@ -168,32 +169,22 @@ allows_fast_compilation(_) -> false.
%% executes the callback in case of success. This automatically
%% handles errors and warnings. Used by this module and elixir_module.
module(Forms, Opts, E, Callback) ->
ErlOpts =
case proplists:get_value(debug_info, Opts) of
true -> [debug_info];
false -> [];
undefined ->
case get_opt(debug_info) of
true -> [debug_info];
false -> []
end
Final =
case (get_opt(debug_info) == true) orelse
lists:member(debug_info, Opts) of
true -> [debug_info] ++ options();
false -> options()
end,
inner_module(Forms, ErlOpts ++ options(), Opts, E, Callback).
inner_module(Forms, Final, false, E, Callback).
inner_module(Forms, ErlOpts, ExOpts, #{file := File} = E, Callback) when
is_list(Forms), is_list(ErlOpts), is_list(ExOpts), is_function(Callback) ->
inner_module(Forms, Options, Bootstrap, #{file := File} = E, Callback) when
is_list(Forms), is_list(Options), is_boolean(Bootstrap), is_function(Callback) ->
Source = elixir_utils:characters_to_list(File),
Autoload = proplists:get_value(autoload, ExOpts, true),
Bootstrap = proplists:get_value(bootstrap, ExOpts, false),
case compile:noenv_forms([no_auto_import()|Forms], [return, {source, Source}|ErlOpts]) of
case compile:noenv_forms([no_auto_import()|Forms], [return, {source, Source}|Options]) of
{ok, Module, Binary, Warnings} ->
format_warnings(Bootstrap, Warnings),
{module, Module} =
case Autoload of
true -> code:load_binary(Module, beam_location(E), Binary);
false -> {module, Module}
end,
{module, Module} = code:load_binary(Module, beam_location(E), Binary),
Callback(Module, Binary);
{error, Errors, Warnings} ->
format_warnings(Bootstrap, Warnings),
@@ -216,7 +207,9 @@ no_auto_import() ->
core() ->
{ok, _} = application:ensure_all_started(elixir),
Update = fun(Old) -> maps:merge(Old, #{docs => false, internal => true}) end,
New = orddict:from_list([{docs, false}, {internal, true}]),
Merge = fun(_, _, Value) -> Value end,
Update = fun(Old) -> orddict:merge(Merge, Old, New) end,
_ = elixir_config:update(compiler_options, Update),
[core_file(File) || File <- core_main()].
@@ -240,8 +233,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">>,
+94 -99
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) when is_integer(Line) ->
store_definition(Line, Kind, CheckClauses, Call, Body, Pos) ->
E = (elixir_locals:get_cached_env(Pos))#{line := Line},
{NameAndArgs, Guards} = elixir_clauses:extract_guards(Call),
@@ -52,10 +52,6 @@ store_definition(Line, Kind, CheckClauses, Call, Body, Pos) when is_integer(Line
%% 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};
@@ -65,38 +61,29 @@ store_definition(Line, Kind, CheckClauses, Call, Body, Pos) when is_integer(Line
LinifyArgs = elixir_quote:linify(Line, Key, Args),
LinifyGuards = elixir_quote:linify(Line, Key, Guards),
LinifyBody = elixir_quote:linify(Line, Key, Body),
LinifyMeta = lists:keystore(line, 1, Meta, {line, Line}),
assert_no_aliases_name(LinifyMeta, Name, Args, E),
assert_valid_name(LinifyMeta, Kind, Name, Args, E),
store_definition(LinifyMeta, Line, Kind, DoCheckClauses, Name,
assert_no_aliases_name(Line, Name, Args, E),
assert_valid_name(Line, Kind, Name, Args, E),
store_definition(Line, Kind, DoCheckClauses, Name,
LinifyArgs, LinifyGuards, LinifyBody, Location, E).
store_definition(Meta, Line, Kind, CheckClauses, Name, Args, Guards, Body, KeepLocation,
#{module := Module} = ER) ->
store_definition(Line, Kind, CheckClauses, Name, Args, Guards, Body, KeepLocation, #{module := Module} = ER) ->
Arity = length(Args),
Tuple = {Name, Arity},
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,
E = ER#{function := Tuple},
elixir_locals:record_definition(Tuple, Kind, Module),
WrappedBody = expr_from_body(Line, Body),
{Function, Defaults, Super} = translate_definition(Kind, Meta, Name, Args, Guards, Body, WrappedBody, E),
run_on_definition_callbacks(Kind, Line, Module, Name, Args, Guards, WrappedBody, E),
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],
@@ -107,10 +94,15 @@ store_definition(Meta, Line, Kind, CheckClauses, Name, Args, Guards, Body, KeepL
%% @on_definition
run_on_definition_callbacks(Kind, Line, Module, Name, Args, Guards, Expr, E) ->
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.
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.
make_struct_available(def, Module, '__struct__', []) ->
case erlang:get(elixir_compiler_pid) of
@@ -141,7 +133,10 @@ retrieve_location(Location, Module) ->
end.
get_location_attribute(Module) ->
elixir_module:get_attribute(Module, file).
case elixir_compiler:get_opt(internal) of
true -> nil;
false -> 'Elixir.Module':get_attribute(Module, file)
end.
normalize_location(File) ->
elixir_utils:characters_to_list(elixir_utils:relative_to_cwd(File)).
@@ -155,39 +150,39 @@ compile_super(_Module, _, _E) -> ok.
%% Translate the given call and expression given
%% and then store it in memory.
translate_definition(Kind, Meta, Name, Args, Guards, Body, WrappedBody, E) ->
translate_definition(Kind, Line, Name, Args, Guards, Body, E) when is_integer(Line) ->
Arity = length(Args),
{EArgs, EGuards, EBody, _} = elixir_exp_clauses:def(fun elixir_def_defaults:expand/2,
Args, Guards, WrappedBody, E),
Args, Guards, expr_from_body(Line, Body), E),
Body == nil andalso check_args_for_bodyless_clause(Line, EArgs, E),
S = elixir_env:env_to_scope(E),
{Unpacked, Defaults} = elixir_def_defaults:unpack(Kind, Name, EArgs, S),
{Clauses, Super} = translate_clause(Body, Kind, Meta, Unpacked, EGuards, EBody, S),
{Clauses, Super} = translate_clause(Body, Line, Kind, Unpacked, EGuards, EBody, S),
Function = {function, ?ann(Meta), Name, Arity, Clauses},
Function = {function, Line, Name, Arity, Clauses},
{Function, Defaults, Super}.
translate_clause(nil, _Kind, Meta, Args, [], _Body, S) ->
check_args_for_bodyless_clause(Meta, Args, S),
translate_clause(nil, _Line, _Kind, _Args, [], _Body, _S) ->
{[], false};
translate_clause(nil, Kind, Meta, _Args, _Guards, _Body, #elixir_scope{file=File}) ->
elixir_errors:form_error(Meta, File, ?MODULE, {missing_do, Kind});
translate_clause(_, Kind, Meta, Args, Guards, Body, S) ->
{TClause, TS} = elixir_clauses:clause(Meta,
fun elixir_translator:translate_args/2, Args, Body, Guards, 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),
FClause = case is_macro(Kind) of
true ->
Ann = ?ann(Meta),
FArgs = {var, Ann, '_@CALLER'},
FArgs = {var, Line, '_@CALLER'},
MClause = setelement(3, TClause, [FArgs|element(3, TClause)]),
case TS#elixir_scope.caller of
true ->
FBody = {'match', Ann,
{'var', Ann, '__CALLER__'},
elixir_utils:erl_call(Ann, elixir_env, linify, [{var, Ann, '_@CALLER'}])
FBody = {'match', Line,
{'var', Line, '__CALLER__'},
elixir_utils:erl_call(Line, elixir_env, linify, [{var, Line, '_@CALLER'}])
},
setelement(5, MClause, [FBody|element(5, TClause)]);
false ->
@@ -219,21 +214,21 @@ unwrap_definitions(File, Module) ->
split_definition(All, Unreachable, [], [], [], [], [], {[], []}).
unwrap_definition([Fun|T], File, Module, CTable, All, Private) ->
{Tuple, Kind, Ann, _, Check, Location, {Defaults, _, _}} = Fun,
{Tuple, Kind, Line, _, Check, Location, {Defaults, _, _}} = Fun,
Export = export(Kind, Tuple),
case [Clause || {_, Clause} <- ets:lookup(CTable, Tuple)] of
[] ->
warn_bodyless_function(Ann, File, Module, Kind, Tuple),
warn_bodyless_function(Line, File, Module, Kind, Tuple),
unwrap_definition(T, File, Module, CTable, All, Private);
Clauses ->
Unwrapped = {Tuple, Kind, Ann, Location,
function_for_stored_definition(Ann, Export, Clauses)},
Unwrapped = {Tuple, Kind, Line, Location,
function_for_stored_definition(Line, Export, Clauses)},
NewPrivate =
if
Kind == defp; Kind == defmacrop ->
[{Tuple, Kind, Ann, Check, Defaults}|Private];
[{Tuple, Kind, Line, Check, Defaults}|Private];
true ->
Private
end,
@@ -244,30 +239,30 @@ unwrap_definition([Fun|T], File, Module, CTable, All, Private) ->
unwrap_definition([], _File, _Module, _CTable, All, Private) ->
{All, Private}.
split_definition([{Tuple, def, Ann, Location, Body}|T], Unreachable,
split_definition([{Tuple, def, Line, Location, Body}|T], Unreachable,
Def, Defp, Defmacro, Defmacrop, Exports, Functions) ->
split_definition(T, Unreachable, [Tuple|Def], Defp, Defmacro, Defmacrop,
[export(def, Tuple)|Exports],
add_definition(Ann, Location, Body, Functions));
add_definition(Line, Location, Body, Functions));
split_definition([{Tuple, defp, Ann, Location, Body}|T], Unreachable,
split_definition([{Tuple, defp, Line, Location, Body}|T], Unreachable,
Def, Defp, Defmacro, Defmacrop, Exports, Functions) ->
case lists:member(Tuple, Unreachable) of
false ->
split_definition(T, Unreachable, Def, [Tuple|Defp], Defmacro, Defmacrop,
Exports, add_definition(Ann, Location, Body, Functions));
Exports, add_definition(Line, Location, Body, Functions));
true ->
split_definition(T, Unreachable, Def, [Tuple|Defp], Defmacro, Defmacrop,
Exports, Functions)
end;
split_definition([{Tuple, defmacro, Ann, Location, Body}|T], Unreachable,
split_definition([{Tuple, defmacro, Line, Location, Body}|T], Unreachable,
Def, Defp, Defmacro, Defmacrop, Exports, Functions) ->
split_definition(T, Unreachable, Def, Defp, [Tuple|Defmacro], Defmacrop,
[export(defmacro, Tuple)|Exports],
add_definition(Ann, Location, Body, Functions));
add_definition(Line, Location, Body, Functions));
split_definition([{Tuple, defmacrop, _Ann, _Location, _Body}|T], Unreachable,
split_definition([{Tuple, defmacrop, _Line, _Location, _Body}|T], Unreachable,
Def, Defp, Defmacro, Defmacrop, Exports, Functions) ->
split_definition(T, Unreachable, Def, Defp, Defmacro, [Tuple|Defmacrop],
Exports, Functions);
@@ -282,33 +277,33 @@ export(Kind, {Name, Arity}) when Kind == defmacro; Kind == defmacrop ->
export(Kind, {Name, Arity}) when Kind == def; Kind == defp ->
{Name, Arity}.
function_for_stored_definition(Ann, {Name, Arity}, Clauses) ->
{function, Ann, Name, Arity, Clauses}.
function_for_stored_definition(Line, {Name, Arity}, Clauses) ->
{function, Line, Name, Arity, Clauses}.
add_definition(_Ann, nil, Body, {Head, Tail}) ->
add_definition(_Line, nil, Body, {Head, Tail}) ->
{[Body|Head], Tail};
add_definition(Ann, Location, Body, {Head, Tail}) ->
add_definition(Line, Location, Body, {Head, Tail}) ->
{Head,
[{attribute, Ann, file, Location}, Body|Tail]}.
[{attribute, Line, file, Location}, Body|Tail]}.
default_function_for(Kind, Name, {clause, Ann, Args, _Guards, _Exprs} = Clause)
default_function_for(Kind, Name, {clause, Line, Args, _Guards, _Exprs} = Clause)
when Kind == defmacro; Kind == defmacrop ->
{function, Ann, Name, length(Args) - 1, [Clause]};
default_function_for(_, Name, {clause, Ann, Args, _Guards, _Exprs} = Clause) ->
{function, Ann, Name, length(Args), [Clause]}.
{function, Line, Name, length(Args) - 1, [Clause]};
default_function_for(_, Name, {clause, Line, Args, _Guards, _Exprs} = Clause) ->
{function, Line, Name, length(Args), [Clause]}.
warn_bodyless_function(_Ann, _File, Special, _Kind, _Tuple)
warn_bodyless_function(_Line, _File, Special, _Kind, _Tuple)
when Special == 'Elixir.Kernel.SpecialForms'; Special == 'Elixir.Module' ->
ok;
warn_bodyless_function(Ann, File, _Module, Kind, Tuple) ->
elixir_errors:form_warn([{line, erl_anno:line(Ann)}], File, ?MODULE, {bodyless_fun, Kind, Tuple}),
warn_bodyless_function(Line, File, _Module, Kind, Tuple) ->
elixir_errors:form_warn([{line, Line}], File, ?MODULE, {bodyless_fun, Kind, Tuple}),
ok.
%% Store each definition in the table.
%% This function also checks and emit warnings in case
%% the kind, of the visibility of the function changes.
store_each(Check, Kind, File, Location, Module, Defaults, {function, Ann, Name, Arity, Clauses}) ->
store_each(Check, Kind, File, Location, Module, Defaults, {function, Line, Name, Arity, Clauses}) ->
Data = elixir_module:data_table(Module),
Defs = elixir_module:defs_table(Module),
Clas = elixir_module:clas_table(Module),
@@ -317,60 +312,60 @@ store_each(Check, Kind, File, Location, Module, Defaults, {function, Ann, Name,
HasBody = Clauses =/= [],
case ets:lookup(Defs, Tuple) of
[{Tuple, StoredKind, StoredAnn, StoredFile, StoredCheck,
[{Tuple, StoredKind, StoredLine, StoredFile, StoredCheck,
StoredLocation, {StoredDefaults, LastHasBody, LastDefaults}}] ->
FinalAnn = StoredAnn,
FinalLine = StoredLine,
FinalLocation = StoredLocation,
FinalDefaults = {max(Defaults, StoredDefaults), HasBody, Defaults},
check_valid_kind(Ann, File, Name, Arity, Kind, StoredKind),
check_valid_kind(Line, File, Name, Arity, Kind, StoredKind),
(Check and StoredCheck) andalso
check_valid_clause(Ann, File, Name, Arity, Kind, Data, StoredAnn, StoredFile),
check_valid_defaults(Ann, File, Name, Arity, Kind, Defaults, StoredDefaults, LastDefaults, LastHasBody);
check_valid_clause(Line, File, Name, Arity, Kind, Data, StoredLine, StoredFile),
check_valid_defaults(Line, File, Name, Arity, Kind, Defaults, StoredDefaults, LastDefaults, LastHasBody);
[] ->
FinalAnn = Ann,
FinalLine = Line,
FinalLocation = Location,
FinalDefaults = {Defaults, HasBody, Defaults}
end,
Check andalso ets:insert(Data, {?last_def, {Name, Arity}}),
ets:insert(Clas, [{Tuple, Clause} || Clause <- Clauses]),
ets:insert(Defs, {Tuple, Kind, FinalAnn, File, Check, FinalLocation, FinalDefaults}).
ets:insert(Defs, {Tuple, Kind, FinalLine, File, Check, FinalLocation, FinalDefaults}).
%% Validations
check_valid_kind(_Ann, _File, _Name, _Arity, Kind, Kind) -> [];
check_valid_kind(Ann, File, Name, Arity, Kind, StoredKind) ->
elixir_errors:form_error([{line, erl_anno:line(Ann)}], File, ?MODULE,
check_valid_kind(_Line, _File, _Name, _Arity, Kind, Kind) -> [];
check_valid_kind(Line, File, Name, Arity, Kind, StoredKind) ->
elixir_errors:form_error([{line, Line}], File, ?MODULE,
{changed_kind, {Name, Arity, StoredKind, Kind}}).
check_valid_clause(Ann, File, Name, Arity, Kind, Data, StoredAnn, StoredFile) ->
check_valid_clause(Line, File, Name, Arity, Kind, Data, StoredLine, StoredFile) ->
case ets:lookup_element(Data, ?last_def, 2) of
{Name, Arity} -> [];
[] -> [];
_ ->
Relative = elixir_utils:relative_to_cwd(elixir_utils:relative_to_cwd(StoredFile)),
elixir_errors:form_warn([{line, erl_anno:line(Ann)}], File, ?MODULE,
{ungrouped_clause, {Kind, Name, Arity, erl_anno:line(StoredAnn), Relative}})
elixir_errors:form_warn([{line, Line}], File, ?MODULE,
{ungrouped_clause, {Kind, Name, Arity, StoredLine, Relative}})
end.
% Clause with defaults after clause with defaults
check_valid_defaults(Ann, File, Name, Arity, Kind, Defaults, StoredDefaults, _, _) when Defaults > 0, StoredDefaults > 0 ->
elixir_errors:form_error([{line, erl_anno:line(Ann)}], File, ?MODULE,
check_valid_defaults(Line, File, Name, Arity, Kind, Defaults, StoredDefaults, _, _) when Defaults > 0, StoredDefaults > 0 ->
elixir_errors:form_error([{line, Line}], File, ?MODULE,
{clauses_with_defaults, {Kind, Name, Arity}});
% Clause with defaults after clause(s) without defaults
check_valid_defaults(Ann, File, Name, Arity, Kind, Defaults, 0, 0, _) when Defaults > 0 ->
elixir_errors:form_warn([{line, erl_anno:line(Ann)}], File, ?MODULE, {out_of_order_defaults, {Kind, Name, Arity}});
check_valid_defaults(Line, File, Name, Arity, Kind, Defaults, 0, 0, _) when Defaults > 0 ->
elixir_errors:form_warn([{line, Line}], File, ?MODULE, {out_of_order_defaults, {Kind, Name, Arity}});
% Clause without defaults directly after clause with defaults (body less does not count)
check_valid_defaults(Ann, File, Name, Arity, Kind, 0, _, LastDefaults, true) when LastDefaults > 0 ->
elixir_errors:form_warn([{line, erl_anno:line(Ann)}], File, ?MODULE,
check_valid_defaults(Line, File, Name, Arity, Kind, 0, _, LastDefaults, true) when LastDefaults > 0 ->
elixir_errors:form_warn([{line, Line}], File, ?MODULE,
{out_of_order_defaults, {Kind, Name, Arity}});
% Clause without defaults
check_valid_defaults(_Ann, _File, _Name, _Arity, _Kind, 0, _, _, _) -> [].
check_valid_defaults(_Line, _File, _Name, _Arity, _Kind, 0, _, _, _) -> [].
check_previous_defaults(Ann, Module, Name, Arity, Kind, Defaults, E) ->
check_previous_defaults(Line, Module, Name, Arity, Kind, Defaults, E) ->
Matches = ets:match(elixir_module:defs_table(Module), {{Name, '$2'}, '$1', '_', '_', '_', '_', {'$3', '_', '_'}}),
[ begin
elixir_errors:form_error([{line, erl_anno:line(Ann)}], ?m(E, file), ?MODULE,
elixir_errors:form_error([{line, Line}], ?m(E, file), ?MODULE,
{defs_with_defaults, Name, {Kind, Arity}, {K, A}})
end || [K, A, D] <- Matches, A /= Arity, D /= 0, defaults_conflict(A, D, Arity, Defaults)].
@@ -378,9 +373,9 @@ defaults_conflict(A, D, Arity, Defaults) ->
((Arity >= (A - D)) andalso (Arity < A)) orelse
((A >= (Arity - Defaults)) andalso (A < Arity)).
check_args_for_bodyless_clause(Meta, Args, S) ->
check_args_for_bodyless_clause(Line, Args, E) ->
[ begin
elixir_errors:form_error(Meta, S#elixir_scope.file, ?MODULE,
elixir_errors:form_error([{line, Line}], ?m(E, file), ?MODULE,
invalid_args_for_bodyless_clause)
end || Arg <- Args, invalid_arg(Arg) ].
@@ -391,14 +386,14 @@ invalid_arg({'\\\\', _, [{Name, _, Kind}, _]}) when is_atom(Name), is_atom(Kind)
invalid_arg(_) ->
true.
assert_no_aliases_name(Meta, '__aliases__', [Atom], #{file := File}) when is_atom(Atom) ->
elixir_errors:form_error(Meta, File, ?MODULE, {no_alias, Atom});
assert_no_aliases_name(_Meta, _Aliases, _Args, _S) ->
assert_no_aliases_name(Line, '__aliases__', [Atom], #{file := File}) when is_atom(Atom) ->
elixir_errors:form_error([{line, Line}], File, ?MODULE, {no_alias, Atom});
assert_no_aliases_name(_Line, _Aliases, _Args, _S) ->
ok.
assert_valid_name(Meta, Kind, is_record, [_, _], #{file := File}) when Kind == defp; Kind == def ->
elixir_errors:form_error(Meta, File, ?MODULE, {is_record, Kind});
assert_valid_name(_Meta, _Kind, _Name, _Args, _S) ->
assert_valid_name(Line, Kind, is_record, [_, _], #{file := File}) when Kind == defp; Kind == def ->
elixir_errors:form_error([{line, Line}], File, ?MODULE, {is_record, Kind});
assert_valid_name(_Line, _Kind, _Name, _Args, _S) ->
ok.
%% Format errors
+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),
Ann = ?ann(DefMeta),
Line = ?line(DefMeta),
Call = {call, Ann,
{atom, Ann, name_for_kind(Kind, Name)},
Call = {call, Line,
{atom, Line, name_for_kind(Kind, Name)},
DefInvoke
},
Clause = {clause, Ann, DefArgs, [], [Call]},
Clause = {clause, Line, DefArgs, [], [Call]},
unpack_each(Kind, Name, T, [Expr|Acc], [Clause|Clauses], S);
unpack_each(Kind, Name, [H|T], Acc, Clauses, S) ->
+9 -11
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 maps:find(Tuple, Overridable) of
case orddict:find(Tuple, Overridable) of
{ok, {_, _, _, _}} -> ok;
_ -> elixir_errors:form_error(Meta, S#elixir_scope.file, ?MODULE, {no_super, Module, Tuple})
end.
@@ -29,20 +29,18 @@ name(Module, Function) ->
name(Module, Function, overridable(Module)).
name(_Module, {Name, _} = Function, Overridable) ->
{Count, _, _, _} = maps:get(Function, Overridable),
{Count, _, _, _} = orddict:fetch(Function, Overridable),
elixir_utils:atom_concat([Name, " (overridable ", Count, ")"]).
%% Store
store(Module, Function, GenerateName) ->
Overridable = overridable(Module),
case maps:get(Function, Overridable) of
{_Count, _Clause, _Neighbours, true} ->
ok;
case orddict:fetch(Function, Overridable) of
{_Count, _Clause, _Neighbours, true} -> ok;
{Count, Clause, Neighbours, false} ->
overridable(Module, maps:put(Function, {Count, Clause, Neighbours, true}, Overridable)),
{{{Name, Arity}, Kind, Line, File, _Check,
Location, {Defaults, _HasBody, _LastDefaults}}, Clauses} = Clause,
overridable(Module, orddict:store(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)};
@@ -64,14 +62,14 @@ super(Module, Function) ->
store(Module, Function, true).
store_pending(Module) ->
_ = [store(Module, X, false) || {X, {_, _, _, false}} <- maps:to_list(overridable(Module)),
_ = [store(Module, X, false) || {X, {_, _, _, false}} <- overridable(Module),
not 'Elixir.Module':'defines?'(Module, X)],
ok.
%% Error handling
format_error({no_super, Module, {Name, Arity}}) ->
Bins = [format_fa(X) || {X, {_, _, _, _}} <- maps:to_list(overridable(Module))],
Bins = [format_fa(X) || {X, {_, _, _, _}} <- overridable(Module)],
Joined = 'Elixir.Enum':join(Bins, <<", ">>),
io_lib:format("no super defined for ~ts/~B in module ~ts. Overridable functions available are: ~ts",
[Name, Arity, elixir_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, guard or nil
context => nil, %% can be match_vars, guards or nil
requires => [], %% a set with modules required
aliases => [], %% a list of aliases by new -> old names
aliases => [], %% an orddict with 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=Map, counter=#{'_' => map_size(Map)}
}.
vars=orddict:from_list(Vars),
counter=[{'_', length(Vars)}]
}.
%% 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
+1 -9
View File
@@ -99,6 +99,7 @@ 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
@@ -192,10 +193,6 @@ 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));
@@ -219,11 +216,6 @@ 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]);
+9 -50
View File
@@ -46,20 +46,6 @@ 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) ->
@@ -157,7 +143,7 @@ expand({quote, Meta, [KV, Do]}, E) when is_list(Do) ->
false -> compile_error(Meta, E#elixir_scope.file, "missing do keyword in quote")
end,
ValidOpts = [context, location, line, file, unquote, bind_quoted, generated],
ValidOpts = [context, location, line, file, unquote, bind_quoted],
{EKV, ET} = expand_opts(Meta, quote, ValidOpts, KV, E),
Context = case lists:keyfind(context, 1, EKV) of
@@ -198,13 +184,7 @@ expand({quote, Meta, [KV, Do]}, E) when is_list(Do) ->
false -> DefaultUnquote
end,
Generated = lists:keyfind(generated, 1, EKV) == {generated, true},
%% TODO: Do not allow negative line numbers once Erlang 18
%% support is dropped as it only allows negative line
%% annotations alongside the generated check.
Q = #elixir_quote{line=Line, file=File, unquote=Unquote,
context=Context, generated=Generated},
Q = #elixir_quote{line=Line, file=File, unquote=Unquote, context=Context},
{Quoted, _Q} = elixir_quote:quote(Exprs, Binding, Q, ET),
expand(Quoted, ET);
@@ -263,11 +243,6 @@ 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) ->
@@ -282,8 +257,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),
@@ -387,20 +362,6 @@ 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]);
@@ -522,7 +483,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, EA),
{elixir_rewrite:rewrite(Receiver, DotMeta, Right, Meta, EArgs),
elixir_env:mergev(EL, EA)}.
%% Lexical helpers
@@ -625,11 +586,9 @@ 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),
"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)])
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)])
end
end.
@@ -666,7 +625,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 contains unused literal ~ts "
io_lib:format("code block starting at line contains unused literal ~ts "
"(remove the literal or assign it to _ to avoid warnings)",
['Elixir.Macro':to_string(Term)]);
format_error({useless_var, Var}) ->
+13 -3
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]}, #{export_vars := ExportVars} = E) ->
clause(_Meta, _Kind, Fun, {'->', Meta, [Left, Right]}, E) ->
{ELeft, EL} = Fun(Left, E),
{ERight, ER} = elixir_exp:expand(Right, EL#{export_vars := ExportVars}),
{ERight, ER} = elixir_exp:expand(Right, EL),
{{'->', Meta, [ELeft, ERight]}, ER};
clause(Meta, Kind, _Fun, _, E) ->
compile_error(Meta, ?m(E, file), "expected -> clauses in ~ts", [Kind]).
@@ -205,8 +205,18 @@ 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, Fun, Clause, E),
{EClause, EC} = clause(Meta, Kind, EFun, 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(CMeta, fun translate_fn_match/2,
Args, Expr, Guards, Acc),
{TClause, TS } = elixir_clauses:clause(?line(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', ?ann(Meta), {clauses, TClauses}}, NS};
{{'fun', ?line(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{extra=pin_guard}),
{TArg, TS#elixir_scope{extra=S#elixir_scope.extra}}.
{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}}.
%% Expansion
+95 -102
View File
@@ -53,35 +53,30 @@ translate(Meta, Args, Return, S) ->
{AccName, _, SA} = elixir_scope:build_var('_', S),
{VarName, _, SV} = elixir_scope:build_var('_', SA),
Ann = ?ann(Meta),
Acc = {var, Ann, AccName},
Var = {var, Ann, VarName},
Line = ?line(Meta),
Acc = {var, Line, AccName},
Var = {var, Line, 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, Ann}, SV};
false when Return -> {{nil, Line}, SV};
false -> {false, SV}
end,
{TCases, SC} = translate_gen(Meta, Cases, [], SI),
{TExpr, SE} = elixir_translator:translate(wrap_expr(Expr, TInto), SC),
{TExpr, SE} = elixir_translator:translate(Expr, SC),
SF = elixir_scope:mergec(SI, SE),
case comprehension_expr(TInto, TExpr) of
{inline, TIntoExpr} ->
{build_inline(Ann, TCases, TIntoExpr, TInto, Var, Acc, SE), SF};
{build_inline(Line, TCases, TIntoExpr, TInto, Var, Acc, SE), SF};
{into, TIntoExpr} ->
build_into(Ann, TCases, TIntoExpr, TInto, Var, Acc, SF)
build_into(Line, 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],
@@ -103,14 +98,9 @@ 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#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}.
{TLeft, SL} = elixir_clauses:match(fun elixir_translator:translate/2, Left, SR),
{TT, {TFilters, TS}} = translate_filters(T, SL),
{TLeft, TRight, TFilters, TT, TS}.
translate_filters(T, S) ->
{Filters, Rest} = collect_filters(T, []),
@@ -135,56 +125,56 @@ collect_filters([H|T], Acc) ->
collect_filters([], Acc) ->
{Acc, []}.
build_inline(Ann, Clauses, Expr, Into, _Var, Acc, S) ->
build_inline(Line, Clauses, Expr, Into, _Var, Acc, S) ->
case lists:all(fun(Clause) -> element(1, Clause) == bin end, Clauses) of
true -> build_comprehension(Ann, Clauses, Expr, Into);
true -> build_comprehension(Line, Clauses, Expr, Into);
false -> build_reduce(Clauses, Expr, Into, Acc, S)
end.
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),
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),
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])
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])
},
TryExpr =
{'try', Ann,
{'try', Line,
[build_reduce_clause(Clauses, IntoExpr, Acc, Acc, SD)],
[{clause, Ann,
[{clause, Line,
[Done],
[],
[{call, Ann, Fun, [Done, {atom, Ann, done}]}]}],
[{clause, Ann,
[{tuple, Ann, [Kind, Reason, {var, Ann, '_'}]}],
[{call, Line, Fun, [Done, {atom, Line, done}]}]}],
[{clause, Line,
[{tuple, Line, [Kind, Reason, {var, Line, '_'}]}],
[],
[{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])]}],
[{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])]}],
[]},
{{block, Ann, [MatchExpr, TryExpr]}, SD}.
{{block, Line, [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, Ann} = Into, Acc, S) ->
ListExpr = {cons, Ann, Expr, Acc},
elixir_utils:erl_call(Ann, lists, reverse,
build_reduce(Clauses, Expr, {nil, Line} = Into, Acc, S) ->
ListExpr = {cons, Line, Expr, Acc},
elixir_utils:erl_call(Line, lists, reverse,
[build_reduce_clause(Clauses, ListExpr, Into, Acc, S)]);
build_reduce(Clauses, Expr, {bin, _, _} = Into, Acc, S) ->
{bin, Ann, Elements} = Expr,
BinExpr = {bin, Ann, [{bin_element, Ann, Acc, default, [bitstring]}|Elements]},
{bin, Line, Elements} = Expr,
BinExpr = {bin, Line, [{bin_element, Line, 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) ->
Ann = ?ann(Meta),
Line = ?line(Meta),
True = build_reduce_clause(T, Expr, Acc, Acc, S),
False = Acc,
@@ -192,23 +182,23 @@ build_reduce_clause([{enum, Meta, Left, Right, Filters}|T], Expr, Arg, Acc, S) -
case is_var(Left) of
true -> [];
false ->
[{clause, ?generated,
[{var, Ann, '_'}, Acc], [],
[{clause, -1,
[{var, Line, '_'}, Acc], [],
[False]}]
end,
Clauses1 =
[{clause, Ann,
[{clause, Line,
[Left, Acc], [],
[join_filters(Ann, Filters, True, False)]}|Clauses0],
[join_filters(Line, Filters, True, False)]}|Clauses0],
Args = [Right, Arg, {'fun', Ann, {clauses, Clauses1}}],
elixir_utils:erl_call(Ann, 'Elixir.Enum', reduce, Args);
Args = [Right, Arg, {'fun', Line, {clauses, Clauses1}}],
elixir_utils:erl_call(Line, 'Elixir.Enum', reduce, Args);
build_reduce_clause([{bin, Meta, Left, Right, Filters}|T], Expr, Arg, Acc, S) ->
Ann = ?ann(Meta),
{Tail, ST} = build_var(Ann, S),
{Fun, SF} = build_var(Ann, ST),
Line = ?line(Meta),
{Tail, ST} = build_var(Line, S),
{Fun, SF} = build_var(Line, ST),
True = build_reduce_clause(T, Expr, Acc, Acc, SF),
False = Acc,
@@ -216,26 +206,26 @@ build_reduce_clause([{bin, Meta, Left, Right, Filters}|T], Expr, Arg, Acc, S) ->
{bin, _, Elements} = Left,
BinMatch =
{bin, Ann, Elements ++ [{bin_element, Ann, Tail, default, [bitstring]}]},
{bin, Line, Elements ++ [{bin_element, Line, Tail, default, [bitstring]}]},
NoVarMatch =
{bin, Ann, no_var(Elements) ++ [{bin_element, Ann, Tail, default, [bitstring]}]},
{bin, Line, no_var(Elements) ++ [{bin_element, Line, Tail, default, [bitstring]}]},
Clauses =
[{clause, Ann,
[{clause, Line,
[BinMatch, Acc], [],
[{call, Ann, Fun, [Tail, join_filters(Ann, Filters, True, False)]}]},
{clause, ?generated,
[{call, Line, Fun, [Tail, join_filters(Line, Filters, True, False)]}]},
{clause, -1,
[NoVarMatch, Acc], [],
[{call, Ann, Fun, [Tail, False]}]},
{clause, ?generated,
[{bin, Ann, []}, Acc], [],
[{call, Line, Fun, [Tail, False]}]},
{clause, -1,
[{bin, Line, []}, Acc], [],
[Acc]},
{clause, ?generated,
[Tail, {var, Ann, '_'}], [],
[elixir_utils:erl_call(Ann, erlang, error, [pair(Ann, badarg, Tail)])]}],
{clause, -1,
[Tail, {var, Line, '_'}], [],
[elixir_utils:erl_call(Line, erlang, error, [pair(Line, badarg, Tail)])]}],
{call, Ann,
{named_fun, Ann, element(3, Fun), Clauses},
{call, Line,
{named_fun, Line, element(3, Fun), Clauses},
[Right, Arg]};
build_reduce_clause([], Expr, _Arg, _Acc, _S) ->
@@ -244,28 +234,31 @@ build_reduce_clause([], Expr, _Arg, _Acc, _S) ->
is_var({var, _, _}) -> true;
is_var(_) -> false.
pair(Ann, Atom, Arg) ->
{tuple, Ann, [{atom, Ann, Atom}, Arg]}.
pair(Line, Atom, Arg) ->
{tuple, Line, [{atom, Line, Atom}, Arg]}.
build_var(Ann, S) ->
build_var(Line, S) ->
{Name, _, ST} = elixir_scope:build_var('_', S),
{{var, Ann, Name}, ST}.
{{var, Line, Name}, ST}.
no_var(Elements) ->
[{bin_element, Ann, no_var_expr(Expr), Size, Types} ||
{bin_element, Ann, Expr, Size, Types} <- Elements].
no_var_expr({var, Ann, _}) ->
{var, Ann, '_'}.
[{bin_element, Line, no_var_expr(Expr), Size, Types} ||
{bin_element, Line, Expr, Size, Types} <- Elements].
no_var_expr({var, Line, _}) ->
{var, Line, '_'}.
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)}.
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)}.
comprehension_clause([{Kind, Meta, Left, Right, Filters}|T]) ->
Ann = ?ann(Meta),
[{comprehension_generator(Kind), Ann, Left, Right}] ++
comprehension_filter(Ann, Filters) ++
Line = ?line(Meta),
[{comprehension_generator(Kind), Line, Left, Right}] ++
comprehension_filter(Line, Filters) ++
comprehension_clause(T);
comprehension_clause([]) ->
[].
@@ -278,8 +271,8 @@ comprehension_generator(bin) -> b_generate.
comprehension_expr({bin, _, []}, {bin, _, _} = Expr) ->
{inline, Expr};
comprehension_expr({bin, Ann, []}, Expr) ->
BinExpr = {bin, Ann, [{bin_element, Ann, Expr, default, [bitstring]}]},
comprehension_expr({bin, Line, []}, Expr) ->
BinExpr = {bin, Line, [{bin_element, Line, Expr, default, [bitstring]}]},
{inline, BinExpr};
comprehension_expr({nil, _}, Expr) ->
{inline, Expr};
@@ -288,29 +281,29 @@ comprehension_expr(false, Expr) ->
comprehension_expr(_, Expr) ->
{into, Expr}.
comprehension_filter(Ann, Filters) ->
[join_filter(Ann, Filter, {atom, Ann, true}, {atom, Ann, false}) ||
comprehension_filter(Line, Filters) ->
[join_filter(Line, Filter, {atom, Line, true}, {atom, Line, false}) ||
Filter <- lists:reverse(Filters)].
join_filters(_Ann, [], True, _False) ->
join_filters(_Line, [], True, _False) ->
True;
join_filters(Ann, [H|T], True, False) ->
join_filters(Line, [H|T], True, False) ->
lists:foldl(fun(Filter, Acc) ->
join_filter(Ann, Filter, Acc, False)
end, join_filter(Ann, H, True, False), T).
join_filter(Line, Filter, Acc, False)
end, join_filter(Line, H, True, False), T).
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, {nil, Filter}, True, False) ->
{'case', Line, Filter, [
{clause, Line, [{atom, Line, true}], [], [True]},
{clause, Line, [{atom, Line, false}], [], [False]}
]};
join_filter(Ann, {Var, Filter}, True, False) ->
join_filter(Line, {Var, Filter}, True, False) ->
Guard =
{op, Ann, 'orelse',
{op, Ann, '==', Var, {atom, Ann, false}},
{op, Ann, '==', Var, {atom, Ann, nil}}},
{op, Line, 'orelse',
{op, Line, '==', Var, {atom, Line, false}},
{op, Line, '==', Var, {atom, Line, nil}}},
{'case', Ann, Filter, [
{clause, Ann, [Var], [[Guard]], [False]},
{clause, Ann, [{var, Ann, '_'}], [], [True]}
{'case', Line, Filter, [
{clause, Line, [Var], [[Guard]], [False]},
{clause, Line, [{var, Line, '_'}], [], [True]}
]}.
+5 -25
View File
@@ -28,12 +28,10 @@ import(Meta, Ref, Opts, E) ->
{Functions, Macros}.
import_functions(Meta, Ref, Opts, E) ->
calculate(Meta, Ref, Opts, ?m(E, functions), ?m(E, file), fun() ->
get_functions(Ref)
end).
calculate(Meta, Ref, Opts, ?m(E, functions), E, fun() -> get_functions(Ref) end).
import_macros(Force, Meta, Ref, Opts, E) ->
calculate(Meta, Ref, Opts, ?m(E, macros), ?m(E, file), fun() ->
calculate(Meta, Ref, Opts, ?m(E, macros), E, fun() ->
case Force of
true -> get_macros(Meta, Ref, E);
false -> get_optional_macros(Ref)
@@ -51,21 +49,13 @@ record_warn(Meta, Ref, Opts, Added, E) ->
%% Calculates the imports based on only and except
calculate(Meta, Key, Opts, Old, File, Existing) ->
calculate(Meta, Key, Opts, Old, E, 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, File, ?MODULE, Tuple);
elixir_errors:form_error(Meta, ?m(E, file), ?MODULE, Tuple);
_ ->
intersection(Only, Existing())
end;
@@ -73,7 +63,6 @@ calculate(Meta, Key, Opts, Old, File, 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
@@ -89,7 +78,7 @@ calculate(Meta, Key, Opts, Old, File, Existing) ->
[] ->
{false, keydelete(Key, Old)};
_ ->
ensure_no_special_form_conflict(Meta, File, Key, Final),
ensure_no_special_form_conflict(Meta, ?m(E, file), Key, Final),
{true, [{Key, Final}|keydelete(Key, Old)]}
end.
@@ -142,14 +131,6 @@ 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}}) ->
@@ -223,7 +204,6 @@ 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;

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