Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b07fbcf8b7 | ||
|
|
4ab8225cfb | ||
|
|
455eb4c4ac | ||
|
|
a6f048b3de | ||
|
|
aa87d44cba | ||
|
|
d161986e93 | ||
|
|
28367d9073 | ||
|
|
126a5a61df | ||
|
|
293711ff46 | ||
|
|
1a17634de9 | ||
|
|
2121ce1875 | ||
|
|
fd8d1f1e4c | ||
|
|
9ebe851b38 | ||
|
|
cb81c6b5c7 | ||
|
|
2cc4fd2eb5 | ||
|
|
4637b57977 | ||
|
|
207c626a6c | ||
|
|
7308d81729 | ||
|
|
177a900be3 | ||
|
|
c8813445c2 | ||
|
|
1e9cd551ce | ||
|
|
969d48ccaf | ||
|
|
f2c914926d | ||
|
|
11c4c2791d | ||
|
|
4051cf53dc | ||
|
|
543dfdeac8 | ||
|
|
b1b1cd3263 | ||
|
|
55563dac1c | ||
|
|
ec350645a4 | ||
|
|
e4aa6e1bdf | ||
|
|
f7f06010ae | ||
|
|
73dc4536b0 | ||
|
|
2b298ab9fc | ||
|
|
3a36c2e6ae | ||
|
|
6448de834c | ||
|
|
f4d145c3a0 | ||
|
|
0bacbaeff7 | ||
|
|
79d8dd0020 | ||
|
|
9bce9e4fde | ||
|
|
232a545d19 | ||
|
|
c1deb99b70 | ||
|
|
1625bdb454 | ||
|
|
b982822fdc | ||
|
|
50820dfc9c | ||
|
|
ee0fb39822 | ||
|
|
9760baff9c | ||
|
|
f7cd14d0f6 | ||
|
|
b29603f1b7 | ||
|
|
afb04a19f9 | ||
|
|
50e9ae6ca7 | ||
|
|
05a85a8e9c | ||
|
|
2af50f7b78 | ||
|
|
15dd4d3c07 | ||
|
|
058d55b68c | ||
|
|
22ebd5f2d6 | ||
|
|
8744abc39f | ||
|
|
1574b9380b | ||
|
|
66441fb2cb | ||
|
|
2466307b74 | ||
|
|
6a5b25045c | ||
|
|
742a922b90 | ||
|
|
2e2d534257 | ||
|
|
271700692a | ||
|
|
6bc699672b | ||
|
|
c07b0d0912 | ||
|
|
61cac4eebc | ||
|
|
b723da6574 | ||
|
|
0159de8a21 | ||
|
|
c961a73c42 | ||
|
|
2bafb1704b | ||
|
|
cf84ed3881 | ||
|
|
95de5bad78 | ||
|
|
7cdb3b09d9 | ||
|
|
b3cf44631c | ||
|
|
e3d96c349f | ||
|
|
f0e42f0654 | ||
|
|
bf1da40976 | ||
|
|
9994dba0f7 | ||
|
|
294885be1a | ||
|
|
82cdc51ca8 | ||
|
|
e855a85c85 | ||
|
|
b9c49c93b8 | ||
|
|
dc393ed14a | ||
|
|
cbae2aecfc | ||
|
|
66e0c53593 | ||
|
|
d7bf2aa783 | ||
|
|
90d0e76eb6 | ||
|
|
6f3c635f6f | ||
|
|
2b12cb4a7e | ||
|
|
1a9fb9caca | ||
|
|
2da92475ba | ||
|
|
36d1dcd9cc | ||
|
|
eb62c3bd27 | ||
|
|
05cd7f170f | ||
|
|
0d4655d1db | ||
|
|
e42e3e55ca | ||
|
|
2a6039da98 | ||
|
|
33035e9978 | ||
|
|
5f682023a6 | ||
|
|
fe2e4a1010 | ||
|
|
50e479332c | ||
|
|
49c3770d87 | ||
|
|
ae24cad4b8 | ||
|
|
a8eab99e44 | ||
|
|
b6d97c3f1d | ||
|
|
6daca1b630 | ||
|
|
9a3695c949 | ||
|
|
9499b9f9be | ||
|
|
5fdc6b55ee | ||
|
|
3f2b44f983 | ||
|
|
18a8272fe5 | ||
|
|
070f6a00a8 | ||
|
|
12dabedf0d | ||
|
|
24da600a72 | ||
|
|
84561408d1 | ||
|
|
9065839328 | ||
|
|
3f30bf156c | ||
|
|
b659ecfa7e | ||
|
|
c240a49f04 | ||
|
|
e89b37190e | ||
|
|
ff61a74a8a | ||
|
|
39db5a1efd | ||
|
|
0802014779 | ||
|
|
bfc30274ff | ||
|
|
bec7a0becb | ||
|
|
fe8fc1be25 | ||
|
|
bcb7cff5f2 | ||
|
|
8023c29c3f | ||
|
|
147fafb067 | ||
|
|
674f4a2cb3 | ||
|
|
b326fe12d9 | ||
|
|
263f0d1c81 | ||
|
|
47ae5b4758 | ||
|
|
25c54dad84 | ||
|
|
48b8efe9c6 | ||
|
|
5d34fcdccd | ||
|
|
e63546bda6 | ||
|
|
04c8ca9718 | ||
|
|
08d69e3975 | ||
|
|
bfc165c46d | ||
|
|
9764f3f384 | ||
|
|
b1915b1ad6 | ||
|
|
5991eb04fa | ||
|
|
b3e07652cc | ||
|
|
cad7551caf | ||
|
|
8ac132fee1 | ||
|
|
ec94c93b24 | ||
|
|
96ed1ff950 | ||
|
|
9d4f15c1c9 | ||
|
|
6bcb7adeb4 | ||
|
|
d311e10bea | ||
|
|
adf7834529 | ||
|
|
c837d8e1d3 | ||
|
|
ce61c60af5 | ||
|
|
dc0dd43406 | ||
|
|
6c2744d373 | ||
|
|
f671e1204c | ||
|
|
c0af2622af | ||
|
|
6b511cc844 | ||
|
|
4ade0f98aa | ||
|
|
67a672e6ed | ||
|
|
54e8ea13f4 | ||
|
|
3110be1ca5 |
@@ -6,6 +6,9 @@ notifications:
|
||||
- jose.valim@plataformatec.com.br
|
||||
- eric.meadows.jonsson@gmail.com
|
||||
otp_release:
|
||||
- 17.0-rc1
|
||||
- R16B03-1
|
||||
- R16B03
|
||||
- R16B02
|
||||
- R16B01
|
||||
- R16B
|
||||
|
||||
+68
-3
@@ -1,9 +1,74 @@
|
||||
# v0.12.5 (2014-03-09)
|
||||
|
||||
* Bug fixes
|
||||
* [Kernel] Ensure `try` does not generate an after clause. Generating an after clause forbade clauses in the `else` part from being tail recursive. This should improve performance and memory consumption of `Stream` functions
|
||||
* [Mix] Automatically recompile on outdated Elixir version and show proper error messages
|
||||
|
||||
* Deprecations
|
||||
* [File] `File.stream_to!/3` is deprecated
|
||||
* [GenFSM] `GenFSM` is deprecated
|
||||
* [Kernel] `%` for sigils is deprecated in favor of `~`
|
||||
* [Kernel] `is_range/1` and `is_regex/1` are deprecated in favor of `Range.range?/1` and `Regex.regex?/1`
|
||||
* [Stream] `Stream.after/1` is deprecated
|
||||
* [URI] `URI.decode_query/1` is deprecated in favor of `URI.decode_query/2` with explicit dict argument
|
||||
* [URI] Passing lists as key or values in `URI.encode_query/1` is deprecated
|
||||
|
||||
* Backwards incompatible changes
|
||||
* [Mix] Remove `MIX_GIT_FORCE_HTTPS` as Git itself already provides mechanisms for doing so
|
||||
|
||||
# v0.12.4 (2014-02-12)
|
||||
|
||||
* Enhancements
|
||||
* [Mix] `mix deps.get` and `mix deps.update` no longer compile dependencies afterwards. Instead, they mark the dependencies which are going to be automatically compiled next time `deps.check` is invoked (which is done automatically by most mix tasks). This means users should have a better workflow when migrating in between environments
|
||||
|
||||
* Deprecations
|
||||
* [Kernel] `//` for default arguments is deprecated in favor of `\\`
|
||||
* [Kernel] Using `%` for sigils is deprecated in favor of `~`. This is a soft deprecation, no warnings will be emitted for it in this release
|
||||
* [Kernel] Using `^` inside function clause heads is deprecated, please use a guard instead
|
||||
|
||||
* Backwards incompatible changes
|
||||
* [ExUnit] `CaptureIO` returns an empty string instead of nil when there is no capture
|
||||
* [Version] The `Version` module now only works with SemVer. The functions `Version.parse/1` and `Version.parse_requirement/1` now return `{:ok,res} | :error` for the cases you want to handle non SemVer cases manually. All other functions will trigger errors on non semantics versions
|
||||
|
||||
# v0.12.3 (2014-02-02)
|
||||
|
||||
* Enhancements
|
||||
* [Kernel] Warnings now are explicitly tagged with "warning:" in messages
|
||||
* [Kernel] Explicit functions inlined by the compiler, including operators. This means that `Kernel.+/2` will now expand to `:erlang.+/2` and so on
|
||||
* [Mix] Do not fail if a Mix dependency relies on an outdated Elixir version
|
||||
* [Process] Add `Process.send/2` and `Process.send_after/3`
|
||||
* [Version] Add `Version.compare/2`
|
||||
|
||||
* Bug fixes
|
||||
* [Atom] Inspect `:...` and `:foo@bar` without quoting
|
||||
* [Keyword] The list `[1, 2, three: :four]` now correctly expands to `[1, 2, {:three, :four}]`
|
||||
* [Kernel] Ensure undefined `@attributes` shows proper stacktrace in warnings
|
||||
* [Kernel] Guarantee nullary funs/macros are allowed in guards
|
||||
* [Process] Ensure monitoring functions are inlined by the compiler
|
||||
|
||||
* Deprecations
|
||||
* [IEx] The helper `m/0` has been deprecated. The goal is to group all runtime statistic related helpers into a single module
|
||||
* [Kernel] `binary_to_term/1`, `binary_to_term/2`, `term_to_binary/1` and `term_to_binary/2` are deprecated in favor of their counterparts in the `:erlang` module
|
||||
* [Kernel] `//` for default arguments is deprecated in favor of `\\`. This is a soft deprecation, no warnings will be emitted for it in this release
|
||||
* [Kernel] Deprecated `@behavior` in favor of `@behaviour`
|
||||
* [Record] `to_keywords`, `getter` and `list getter` functionalities in `defrecordp` are deprecated
|
||||
* [Record] `Record.import/2` is deprecated
|
||||
|
||||
* Backwards incompatible changes
|
||||
* [Dict] Implementations of `equal?/2` and `merge/2` in `HashDict` and `ListDict` are no longer polymorphic. To get polymorphism, use the functions in `Dict` instead
|
||||
* [File] `File.cp/3` and `File.cp_r/3` no longer carry Unix semantics where the function behaves differently if the destination is an existing previous directory or not. It now always copies source to destination, doing it recursively in the latter
|
||||
* [IEx] IEx now loads the `.iex.exs` file instead of `.iex`
|
||||
* [Kernel] Remove `**` from the list of allowed operators
|
||||
* [Kernel] Limit sigils delimiters to one of the following: `<>`, `{}`, `[]`, `()`, `||`, `//`, `"` and `'`
|
||||
* [Range] `Range` is no longer a record, instead use `first .. last` if you need pattern matching
|
||||
* [Set] Implementations of `difference/2`, `disjoint?/2`, `equal?/2`, `intersection/2`, `subset?/2` and `union/2` in `HashSet` are no longer polymorphic. To get polymorphism, use the functions in `Set` instead
|
||||
|
||||
# v0.12.2 (2014-01-15)
|
||||
|
||||
* Enhancements
|
||||
* [EEx] Allow `EEx.AssignsEngine` to accept any Dict
|
||||
* [Enum] Add `Enum.flat_map_reduce/3`
|
||||
* [ExUnit] Support @moduletag in ExUnit cases
|
||||
* [ExUnit] Support `@moduletag` in ExUnit cases
|
||||
* [Kernel] Improve stacktraces to be relative to the compilation path and include the related application
|
||||
* [Stream] Add `Stream.transform/3`
|
||||
|
||||
@@ -18,7 +83,7 @@
|
||||
|
||||
* Deprecations
|
||||
* [Enum] Deprecate `Enum.first/1` in favor of `Enum.at/2` and `List.first/1`
|
||||
* [Kernel] Deprecate continuable heredocs. In previous versions, Elixir would continue parsing on the same line the heredoc started, this behavior has been deprecated
|
||||
* [Kernel] Deprecate continuable heredocs. In previous versions, Elixir would continue parsing on the same line the heredoc started, this behaviour has been deprecated
|
||||
* [Kernel] `is_alive/0` is deprecated in favor of `Node.alive?`
|
||||
* [Kernel] `Kernel.inspect/2` with `Inspect.Opts[]` is deprecated in favor of `Inspect.Algebra.to_doc/2`
|
||||
* [Kernel] `Kernel.inspect/2` with `:raw` option is deprecated, use `:records` option instead
|
||||
@@ -718,7 +783,7 @@
|
||||
* Backwards incompatible changes
|
||||
* [Kernel] Compile files now follow `Elixir-ModuleName` convention to solve issues with Erlang embedded mode. This removes the `__MAIN__` pseudo-variable as modules are now located inside `Elixir` namespace
|
||||
* [Kernel] `__using__` callback triggered by `use` now receives just one argument. Caller information can be accessed via macros using `__CALLER__`
|
||||
* [Kernel] Comprehensions syntax changed to be more compatible with Erlang behavior
|
||||
* [Kernel] Comprehensions syntax changed to be more compatible with Erlang behaviour
|
||||
* [Kernel] loop and recur are removed in favor of recursion with named functions
|
||||
* [Module] Removed data functions in favor of unifying the attributes API
|
||||
|
||||
|
||||
@@ -76,7 +76,7 @@ unicode: $(UNICODE)
|
||||
$(UNICODE): lib/elixir/unicode/*
|
||||
@ echo "==> unicode (compile)";
|
||||
@ echo "This step can take up to a minute to compile in order to embed the Unicode database"
|
||||
$(Q) cd lib/elixir && ../../$(ELIXIRC) unicode/graphemes.ex unicode/unicode.ex -o ebin;
|
||||
$(Q) cd lib/elixir && ../../$(ELIXIRC) unicode/unicode.ex -o ebin;
|
||||
|
||||
$(eval $(call APP_TEMPLATE,ex_unit,ExUnit))
|
||||
$(eval $(call APP_TEMPLATE,eex,EEx))
|
||||
|
||||
@@ -17,7 +17,7 @@ if [ $# -gt 0 ] && ([ "$1" = "--help" ] || [ "$1" = "-h" ]); then
|
||||
--hidden Makes a hidden node
|
||||
--detached Starts the Erlang VM detached from console
|
||||
--remsh \"name\" Connects to a node using a remote shell
|
||||
--dot-iex \"path\" Overrides default .iex file and uses path instead;
|
||||
--dot-iex \"path\" Overrides default .iex.exs file and uses path instead;
|
||||
path can be empty, then no file will be loaded
|
||||
|
||||
** Options marked with (*) can be given more than once
|
||||
|
||||
+7
-7
@@ -1,7 +1,7 @@
|
||||
defexception EEx.SyntaxError, message: nil
|
||||
|
||||
defmodule EEx do
|
||||
@moduledoc %S"""
|
||||
@moduledoc ~S"""
|
||||
EEx stands for Embedded Elixir. It allows you to embed
|
||||
Elixir code inside a string in a robust way:
|
||||
|
||||
@@ -105,7 +105,7 @@ defmodule EEx do
|
||||
Sample.sample(1, 2) #=> "3"
|
||||
|
||||
"""
|
||||
defmacro function_from_string(kind, name, source, args // [], options // []) do
|
||||
defmacro function_from_string(kind, name, source, args \\ [], options \\ []) do
|
||||
quote bind_quoted: binding do
|
||||
info = Keyword.merge [file: __ENV__.file, line: __ENV__.line], options
|
||||
args = Enum.map args, fn arg -> { arg, [line: info[:line]], nil } end
|
||||
@@ -141,7 +141,7 @@ defmodule EEx do
|
||||
Sample.sample(1, 2) #=> "3"
|
||||
|
||||
"""
|
||||
defmacro function_from_file(kind, name, file, args // [], options // []) do
|
||||
defmacro function_from_file(kind, name, file, args \\ [], options \\ []) do
|
||||
quote bind_quoted: binding do
|
||||
info = Keyword.merge options, [file: file, line: 1]
|
||||
args = Enum.map args, fn arg -> { arg, [line: 1], nil } end
|
||||
@@ -159,7 +159,7 @@ defmodule EEx do
|
||||
Get a string `source` and generate a quoted expression
|
||||
that can be evaluated by Elixir or compiled to a function.
|
||||
"""
|
||||
def compile_string(source, options // []) do
|
||||
def compile_string(source, options \\ []) do
|
||||
EEx.Compiler.compile(source, options)
|
||||
end
|
||||
|
||||
@@ -167,7 +167,7 @@ defmodule EEx do
|
||||
Get a `filename` and generate a quoted expression
|
||||
that can be evaluated by Elixir or compiled to a function.
|
||||
"""
|
||||
def compile_file(filename, options // []) do
|
||||
def compile_file(filename, options \\ []) do
|
||||
options = Keyword.merge options, [file: filename, line: 1]
|
||||
compile_string(File.read!(filename), options)
|
||||
end
|
||||
@@ -181,7 +181,7 @@ defmodule EEx do
|
||||
#=> "foo baz"
|
||||
|
||||
"""
|
||||
def eval_string(source, bindings // [], options // []) do
|
||||
def eval_string(source, bindings \\ [], options \\ []) do
|
||||
compiled = compile_string(source, options)
|
||||
do_eval(compiled, bindings, options)
|
||||
end
|
||||
@@ -199,7 +199,7 @@ defmodule EEx do
|
||||
#=> "foo baz"
|
||||
|
||||
"""
|
||||
def eval_file(filename, bindings // [], options // []) do
|
||||
def eval_file(filename, bindings \\ [], options \\ []) do
|
||||
options = Keyword.put options, :file, filename
|
||||
compiled = compile_file(filename, options)
|
||||
do_eval(compiled, bindings, options)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
defmodule EEx.Engine do
|
||||
@moduledoc %S"""
|
||||
@moduledoc ~S"""
|
||||
This is the basic EEx engine that ships with Elixir.
|
||||
An engine needs to implement two functions:
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ defmodule EEx.TransformerEngine do
|
||||
@moduledoc """
|
||||
An abstract engine that is meant to be used and
|
||||
built upon in other modules. This engine implements
|
||||
the `EEx.Engine` behavior and provides a `transform`
|
||||
the `EEx.Engine` behaviour and provides a `transform`
|
||||
overridable directive that allows a developer to
|
||||
customize the expression returned by the engine.
|
||||
|
||||
@@ -13,7 +13,7 @@ defmodule EEx.TransformerEngine do
|
||||
@doc false
|
||||
defmacro __using__(_) do
|
||||
quote do
|
||||
@behavior EEx.Engine
|
||||
@behaviour EEx.Engine
|
||||
|
||||
def handle_text(buffer, text) do
|
||||
EEx.Engine.handle_text(buffer, text)
|
||||
|
||||
+2
-2
@@ -2,6 +2,6 @@ defmodule EEx.Mixfile do
|
||||
use Mix.Project
|
||||
|
||||
def project do
|
||||
[app: :eex, version: System.version]
|
||||
[app: :eex, version: System.version, build_per_environment: false]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -20,7 +20,7 @@ defmodule EEx.SmartEngineTest do
|
||||
assert_eval "1\n2\n3\n", "<%= lc x inlist [1, 2, 3] do %><%= x %>\n<% end %>"
|
||||
end
|
||||
|
||||
defp assert_eval(expected, actual, binding // []) do
|
||||
defp assert_eval(expected, actual, binding \\ []) do
|
||||
result = EEx.eval_string(actual, binding, file: __ENV__.file)
|
||||
assert result == expected
|
||||
end
|
||||
|
||||
@@ -80,7 +80,8 @@
|
||||
-define(is_atom(S), ?is_identifier(S) orelse (S == $@)).
|
||||
|
||||
-define(is_identifier(S), ?is_digit(S) orelse ?is_upcase(S) orelse ?is_downcase(S) orelse (S == $_)).
|
||||
-define(is_terminator(S), (S == $?) orelse (S == $!) orelse (S == $:)).
|
||||
-define(is_sigil(S), (S == $/) orelse (S == $<) orelse (S == $") orelse (S == $') orelse
|
||||
(S == $[) orelse (S == $() orelse (S == ${) orelse (S == $|)).
|
||||
|
||||
%% Quotes
|
||||
-define(is_quote(S), S == $" orelse S == $').
|
||||
|
||||
@@ -75,7 +75,7 @@ defmodule Application.Behaviour do
|
||||
# Application.Behaviour.start(:my_app)
|
||||
#
|
||||
@doc false
|
||||
def start(app, type // :temporary) do
|
||||
def start(app, type \\ :temporary) do
|
||||
case :application.start(app, type) do
|
||||
{ :error, { :not_started, dep } } ->
|
||||
case start(dep) do
|
||||
@@ -92,7 +92,7 @@ defmodule Application.Behaviour do
|
||||
@doc false
|
||||
defmacro __using__(_) do
|
||||
quote location: :keep do
|
||||
@behavior :application
|
||||
@behaviour :application
|
||||
|
||||
@doc false
|
||||
def stop(_state) do
|
||||
|
||||
@@ -122,6 +122,10 @@ defmodule Behaviour do
|
||||
raise ArgumentError, message: "default arguments // not supported in defcallback/defmacrocallback"
|
||||
end
|
||||
|
||||
defp ensure_not_default({ :\\, _, [_, _] }) do
|
||||
raise ArgumentError, message: "default arguments \\\\ not supported in defcallback/defmacrocallback"
|
||||
end
|
||||
|
||||
defp ensure_not_default(_), do: :ok
|
||||
|
||||
@doc false
|
||||
|
||||
@@ -10,7 +10,7 @@ defmodule Code do
|
||||
Utilities for managing code compilation, code evaluation and code loading.
|
||||
|
||||
This module complements [Erlang's code module](http://www.erlang.org/doc/man/code.html)
|
||||
to add behavior which is specific to Elixir.
|
||||
to add behaviour which is specific to Elixir.
|
||||
"""
|
||||
|
||||
@doc """
|
||||
@@ -115,7 +115,7 @@ defmodule Code do
|
||||
{3, [a: 1, b: 2]}
|
||||
|
||||
"""
|
||||
def eval_string(string, binding // [], opts // [])
|
||||
def eval_string(string, binding \\ [], opts \\ [])
|
||||
|
||||
def eval_string(string, binding, Macro.Env[] = env) do
|
||||
do_eval_string(string, binding, env.to_keywords)
|
||||
@@ -150,7 +150,7 @@ defmodule Code do
|
||||
{3, [a: 1, b: 2]}
|
||||
|
||||
"""
|
||||
def eval_quoted(quoted, binding // [], opts // [])
|
||||
def eval_quoted(quoted, binding \\ [], opts \\ [])
|
||||
|
||||
def eval_quoted(quoted, binding, Macro.Env[] = env) do
|
||||
do_eval_quoted(quoted, binding, env.to_keywords)
|
||||
@@ -225,7 +225,7 @@ defmodule Code do
|
||||
`Macro.to_string/2`, which converts a quoted form to a string/binary
|
||||
representation.
|
||||
"""
|
||||
def string_to_quoted(string, opts // []) when is_list(opts) do
|
||||
def string_to_quoted(string, opts \\ []) when is_list(opts) do
|
||||
file = Keyword.get opts, :file, "nofile"
|
||||
line = Keyword.get opts, :line, 1
|
||||
res = :elixir.string_to_quoted(to_char_list(string), line, file, opts)
|
||||
@@ -246,7 +246,7 @@ defmodule Code do
|
||||
|
||||
Check `string_to_quoted/2` for options information.
|
||||
"""
|
||||
def string_to_quoted!(string, opts // []) when is_list(opts) do
|
||||
def string_to_quoted!(string, opts \\ []) when is_list(opts) do
|
||||
file = Keyword.get opts, :file, "nofile"
|
||||
line = Keyword.get opts, :line, 1
|
||||
:elixir.string_to_quoted!(to_char_list(string), line, file, opts)
|
||||
@@ -266,7 +266,7 @@ defmodule Code do
|
||||
a given file, the given file will be loaded N times. Check
|
||||
`require_file/2` if you don't want a file to be loaded concurrently.
|
||||
"""
|
||||
def load_file(file, relative_to // nil) when is_binary(file) do
|
||||
def load_file(file, relative_to \\ nil) when is_binary(file) do
|
||||
file = find_file(file, relative_to)
|
||||
:elixir_code_server.call { :acquire, file }
|
||||
loaded = :elixir_compiler.file file
|
||||
@@ -289,7 +289,7 @@ defmodule Code do
|
||||
|
||||
Check `load_file/2` if you want a file to be loaded concurrently.
|
||||
"""
|
||||
def require_file(file, relative_to // nil) when is_binary(file) do
|
||||
def require_file(file, relative_to \\ nil) when is_binary(file) do
|
||||
file = find_file(file, relative_to)
|
||||
|
||||
case :elixir_code_server.call({ :acquire, file }) do
|
||||
@@ -346,7 +346,7 @@ defmodule Code do
|
||||
|
||||
For compiling many files at once, check `Kernel.ParallelCompiler.files/2`.
|
||||
"""
|
||||
def compile_string(string, file // "nofile") when is_binary(file) do
|
||||
def compile_string(string, file \\ "nofile") when is_binary(file) do
|
||||
:elixir_compiler.string to_char_list(string), file
|
||||
end
|
||||
|
||||
@@ -357,7 +357,7 @@ defmodule Code do
|
||||
the first element is the module name and the second one is its
|
||||
binary.
|
||||
"""
|
||||
def compile_quoted(quoted, file // "nofile") when is_binary(file) do
|
||||
def compile_quoted(quoted, file \\ "nofile") when is_binary(file) do
|
||||
:elixir_compiler.quoted quoted, file
|
||||
end
|
||||
|
||||
|
||||
+56
-57
@@ -1,5 +1,5 @@
|
||||
defmodule Dict do
|
||||
@moduledoc %S"""
|
||||
@moduledoc ~S"""
|
||||
This module specifies the Dict API expected to be
|
||||
implemented by different dictionaries. It also provides
|
||||
functions that redirect to the underlying Dict, allowing
|
||||
@@ -34,21 +34,20 @@ defmodule Dict do
|
||||
## Match
|
||||
|
||||
Dictionaries are required to implement all operations
|
||||
using the match (`===`) operator. Any deviation from
|
||||
this behaviour should be avoided and explicitly documented.
|
||||
using the match (`===`) operator.
|
||||
"""
|
||||
|
||||
use Behaviour
|
||||
|
||||
@type key :: any
|
||||
@type value :: any
|
||||
@type keys :: [ key ]
|
||||
@type t :: tuple | list
|
||||
|
||||
defcallback new :: t
|
||||
defcallback new(Keyword.t) :: t
|
||||
defcallback new(Enum.t) :: t
|
||||
defcallback new(Enum.t, (any -> { key, value })) :: t
|
||||
defcallback delete(t, key) :: t
|
||||
defcallback drop(t, keys) :: t
|
||||
defcallback drop(t, Enum.t) :: t
|
||||
defcallback empty(t) :: t
|
||||
defcallback equal?(t, t) :: boolean
|
||||
defcallback get(t, key) :: value
|
||||
@@ -56,16 +55,17 @@ defmodule Dict do
|
||||
defcallback fetch(t, key) :: { :ok, value } | :error
|
||||
defcallback fetch!(t, key) :: value | no_return
|
||||
defcallback has_key?(t, key) :: boolean
|
||||
defcallback keys(t) :: list(key)
|
||||
defcallback keys(t) :: [key]
|
||||
defcallback merge(t, t) :: t
|
||||
defcallback merge(t, t, (key, value, value -> value)) :: t
|
||||
defcallback pop(t, key) :: {value, t}
|
||||
defcallback pop(t, key, value) :: {value, t}
|
||||
defcallback put(t, key, value) :: t
|
||||
defcallback put_new(t, key, value) :: t
|
||||
defcallback reduce(t, Enumerable.acc, Enumerable.reducer) :: Enumerable.result
|
||||
defcallback size(t) :: non_neg_integer()
|
||||
defcallback split(t, keys) :: {t, t}
|
||||
defcallback take(t, keys) :: t
|
||||
defcallback split(t, Enum.t) :: {t, t}
|
||||
defcallback take(t, Enum.t) :: t
|
||||
defcallback to_list(t) :: list()
|
||||
defcallback update(t, key, value, (value -> value)) :: t
|
||||
defcallback update!(t, key, (value -> value)) :: t | no_return
|
||||
@@ -163,7 +163,7 @@ defmodule Dict do
|
||||
3
|
||||
"""
|
||||
@spec get(t, key, value) :: value
|
||||
def get(dict, key, default // nil) do
|
||||
def get(dict, key, default \\ nil) do
|
||||
target(dict).get(dict, key, default)
|
||||
end
|
||||
|
||||
@@ -258,12 +258,15 @@ defmodule Dict do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Merges the given `enum` into `dict`. If one of the `enum` keys
|
||||
already exists in `dict`, the `dict` value is replaced by the `enum`
|
||||
value.
|
||||
Merges the dict `b` into dict `a`.
|
||||
|
||||
The `enum` must yield tuples with two elements on enumeration,
|
||||
where the first element represents the key and the second the value.
|
||||
If one of the dict `b` entries already exists in the `dict`,
|
||||
the functions in entries in `b` have higher precedence unless a
|
||||
function is given to resolve conflicts.
|
||||
|
||||
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
|
||||
|
||||
@@ -273,22 +276,6 @@ defmodule Dict do
|
||||
...> [a: Dict.get(d, :a), b: Dict.get(d, :b), d: Dict.get(d, :d)]
|
||||
[a: 3, b: 2, d: 4]
|
||||
|
||||
"""
|
||||
@spec merge(t, t) :: t
|
||||
def merge(dict, enum) do
|
||||
merge(dict, enum, fn(_k, _v1, v2) -> v2 end)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Merges the given `enum` into `dict`. If one of the `enum` entries
|
||||
already exists in `dict`, the given function is invoked to resolve
|
||||
the conflict.
|
||||
|
||||
The `enum` must yield tuples with two elements on enumeration,
|
||||
where the first element represents the key and the second the value.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> d1 = dict_impl.new([a: 1, b: 2])
|
||||
...> d2 = dict_impl.new([a: 3, d: 4])
|
||||
...> d = Dict.merge(d1, d2, fn(_k, v1, v2) ->
|
||||
@@ -299,8 +286,17 @@ defmodule Dict do
|
||||
|
||||
"""
|
||||
@spec merge(t, t, (key, value, value -> value)) :: t
|
||||
def merge(dict, enum, fun) do
|
||||
target(dict).merge(dict, enum, fun)
|
||||
def merge(dict1, dict2, fun \\ fn(_k, _v1, v2) -> v2 end) do
|
||||
target1 = target(dict1)
|
||||
target2 = target(dict2)
|
||||
|
||||
if target1 == target2 do
|
||||
target1.merge(dict1, dict2, fun)
|
||||
else
|
||||
target2.reduce(dict2, { :cont, dict1 }, fn({ k, v }, acc) ->
|
||||
{ :cont, target1.update(acc, k, v, fn(other) -> fun.(k, other, v) end) }
|
||||
end) |> elem(1)
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -326,7 +322,7 @@ defmodule Dict do
|
||||
|
||||
"""
|
||||
@spec pop(t, key, value) :: {value, t}
|
||||
def pop(dict, key, default // nil) do
|
||||
def pop(dict, key, default \\ nil) do
|
||||
target(dict).pop(dict, key, default)
|
||||
end
|
||||
|
||||
@@ -347,11 +343,6 @@ defmodule Dict do
|
||||
target(dict).update!(dict, key, fun)
|
||||
end
|
||||
|
||||
@doc false
|
||||
def update(dict, key, fun) do
|
||||
target(dict).update(dict, key, fun)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Update 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
|
||||
@@ -381,7 +372,7 @@ defmodule Dict do
|
||||
|
||||
iex> d = dict_impl.new([a: 1, b: 2, c: 3, d: 4])
|
||||
...> { d1, d2 } = Dict.split(d, [:a, :c, :e])
|
||||
...> { Dict.to_list(d1), Dict.to_list(d2) }
|
||||
...> { Dict.to_list(d1) |> Enum.sort, Dict.to_list(d2) |> Enum.sort }
|
||||
{ [a: 1, c: 3], [b: 2, d: 4] }
|
||||
|
||||
iex> d = dict_impl.new([])
|
||||
@@ -391,11 +382,11 @@ defmodule Dict do
|
||||
|
||||
iex> d = dict_impl.new([a: 1, b: 2])
|
||||
...> { d1, d2 } = Dict.split(d, [:a, :b, :c])
|
||||
...> { Dict.to_list(d1), Dict.to_list(d2) }
|
||||
...> { Dict.to_list(d1) |> Enum.sort, Dict.to_list(d2) }
|
||||
{ [a: 1, b: 2], [] }
|
||||
|
||||
"""
|
||||
@spec split(t, keys) :: {t, t}
|
||||
@spec split(t, [key]) :: {t, t}
|
||||
def split(dict, keys) do
|
||||
target(dict).split(dict, keys)
|
||||
end
|
||||
@@ -413,18 +404,18 @@ defmodule Dict do
|
||||
|
||||
iex> d = dict_impl.new([a: 1, b: 2])
|
||||
...> d = Dict.drop(d, [:c, :d])
|
||||
...> Dict.to_list(d)
|
||||
...> Dict.to_list(d) |> Enum.sort
|
||||
[a: 1, b: 2]
|
||||
|
||||
"""
|
||||
@spec drop(t, keys) :: t
|
||||
@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.
|
||||
Returns a new dict where only the keys in `keys` from `dict` are included.
|
||||
|
||||
Any non-member keys are ignored.
|
||||
|
||||
## Examples
|
||||
@@ -440,7 +431,7 @@ defmodule Dict do
|
||||
[]
|
||||
|
||||
"""
|
||||
@spec take(t, keys) :: t
|
||||
@spec take(t, [key]) :: t
|
||||
def take(dict, keys) do
|
||||
target(dict).take(dict, keys)
|
||||
end
|
||||
@@ -462,13 +453,16 @@ defmodule Dict do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Check if two dicts are equal using `===`. If the dicts are
|
||||
of different types, they are first converted to lists.
|
||||
Check 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> a = dict_impl.new(a: 2, b: 3, f: 5, c: 123)
|
||||
...> b = ListDict.new(a: 2, b: 3, f: 5, c: 123)
|
||||
...> b = [a: 2, b: 3, f: 5, c: 123]
|
||||
...> Dict.equal?(a, b)
|
||||
true
|
||||
|
||||
@@ -479,16 +473,21 @@ defmodule Dict do
|
||||
|
||||
"""
|
||||
@spec equal?(t, t) :: boolean
|
||||
def equal?(a, b) do
|
||||
a_target = target(a)
|
||||
b_target = target(b)
|
||||
def equal?(dict1, dict2) do
|
||||
target1 = target(dict1)
|
||||
target2 = target(dict2)
|
||||
|
||||
cond do
|
||||
a_target == b_target ->
|
||||
a_target.equal?(a, b)
|
||||
target1 == target2 ->
|
||||
target1.equal?(dict1, dict2)
|
||||
|
||||
a_target.size(a) == b_target.size(b) ->
|
||||
ListDict.equal?(a_target.to_list(a), b_target.to_list(b))
|
||||
target1.size(dict1) == target2.size(dict2) ->
|
||||
target2.reduce(dict2, { :cont, true }, fn({ k, v }, _acc) ->
|
||||
case target1.fetch(dict1, k) do
|
||||
{ :ok, ^v } -> { :cont, true }
|
||||
_ -> { :halt, false }
|
||||
end
|
||||
end) |> elem(1)
|
||||
|
||||
true ->
|
||||
false
|
||||
|
||||
@@ -50,7 +50,7 @@ defmodule Dict.Behaviour do
|
||||
Note you can also test your custom module via `Dict`'s doctests:
|
||||
|
||||
defmodule MyDict do
|
||||
def new(keywords // []) do
|
||||
def new(keywords \\ []) do
|
||||
...
|
||||
end
|
||||
end
|
||||
@@ -63,10 +63,13 @@ defmodule Dict.Behaviour do
|
||||
|
||||
"""
|
||||
defmacro __using__(_) do
|
||||
# Use this import to guarantee proper code expansion
|
||||
import Kernel, except: [size: 1]
|
||||
|
||||
quote do
|
||||
@behaviour Dict
|
||||
|
||||
def get(dict, key, default // nil) do
|
||||
def get(dict, key, default \\ nil) do
|
||||
case fetch(dict, key) do
|
||||
{ :ok, value } -> value
|
||||
:error -> default
|
||||
@@ -88,21 +91,16 @@ defmodule Dict.Behaviour do
|
||||
update(dict, key, value, fn(v) -> v end)
|
||||
end
|
||||
|
||||
def drop(dict, []), do: dict
|
||||
|
||||
def drop(dict, [key|keys]) do
|
||||
drop(delete(dict, key), keys)
|
||||
def drop(dict, keys) do
|
||||
Enum.reduce keys, dict, &delete(&2, &1)
|
||||
end
|
||||
|
||||
def take(dict, keys) do
|
||||
take(dict, keys, new)
|
||||
end
|
||||
|
||||
defp take(_dict, [], acc), do: acc
|
||||
defp take(dict, [key|keys], acc) do
|
||||
case fetch(dict, key) do
|
||||
{ :ok, value } -> take(dict, keys, put(acc, key, value))
|
||||
:error -> take(dict, keys, acc)
|
||||
Enum.reduce keys, new, fn key, acc ->
|
||||
case fetch(dict, key) do
|
||||
{ :ok, value } -> put(acc, key, value)
|
||||
:error -> acc
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -125,24 +123,25 @@ defmodule Dict.Behaviour do
|
||||
end
|
||||
|
||||
def equal?(dict1, dict2) do
|
||||
# Use this import to avoid conflicts in the user code
|
||||
import Kernel, except: [size: 1]
|
||||
|
||||
case size(dict1) == size(dict2) do
|
||||
false -> false
|
||||
true ->
|
||||
reduce(dict1, { :cont, true }, fn({ k, v }, _acc) ->
|
||||
unless fetch(dict2, k) == { :ok, v } do
|
||||
{ :halt, false }
|
||||
else
|
||||
{ :cont, true }
|
||||
case fetch(dict2, k) do
|
||||
{ :ok, ^v } -> { :cont, true }
|
||||
_ -> { :halt, false }
|
||||
end
|
||||
end) |> elem(1)
|
||||
end
|
||||
end
|
||||
|
||||
def merge(dict, enumerable, callback // fn(_k, _v1, v2) -> v2 end) do
|
||||
Enum.reduce(enumerable, dict, fn({key, value}, acc) ->
|
||||
update(acc, key, value, fn(v1) -> callback.(key, v1, value) end)
|
||||
end)
|
||||
def merge(dict1, dict2, fun \\ fn(_k, _v1, v2) -> v2 end) do
|
||||
reduce(dict1, { :cont, dict2 }, fn { k, v1 }, acc ->
|
||||
{ :cont, update(acc, k, v1, &fun.(k, v1, &1)) }
|
||||
end) |> elem(1)
|
||||
end
|
||||
|
||||
defoverridable merge: 2, merge: 3, equal?: 2, to_list: 1, keys: 1,
|
||||
|
||||
+13
-27
@@ -145,7 +145,7 @@ defmodule Enum do
|
||||
enumeration. For dicts, the argument is always a `{ key, value }` tuple:
|
||||
|
||||
iex> dict = HashDict.new [a: 1, b: 2]
|
||||
iex> Enum.map(dict, fn { k, v } -> { k, v * 2 } end)
|
||||
iex> Enum.map(dict, fn { k, v } -> { k, v * 2 } end) |> Enum.sort
|
||||
[a: 2, b: 4]
|
||||
|
||||
Note that the functions in the `Enum` module are eager: they always start
|
||||
@@ -209,7 +209,7 @@ defmodule Enum do
|
||||
@spec all?(t) :: boolean
|
||||
@spec all?(t, (element -> as_boolean(term))) :: boolean
|
||||
|
||||
def all?(collection, fun // fn(x) -> x end)
|
||||
def all?(collection, fun \\ fn(x) -> x end)
|
||||
|
||||
def all?(collection, fun) when is_list(collection) do
|
||||
do_all?(collection, fun)
|
||||
@@ -245,7 +245,7 @@ defmodule Enum do
|
||||
@spec any?(t) :: boolean
|
||||
@spec any?(t, (element -> as_boolean(term))) :: boolean
|
||||
|
||||
def any?(collection, fun // fn(x) -> x end)
|
||||
def any?(collection, fun \\ fn(x) -> x end)
|
||||
|
||||
def any?(collection, fun) when is_list(collection) do
|
||||
do_any?(collection, fun)
|
||||
@@ -275,7 +275,7 @@ defmodule Enum do
|
||||
"""
|
||||
@spec at(t, integer) :: element | nil
|
||||
@spec at(t, integer, default) :: element | default
|
||||
def at(collection, n, default // nil) do
|
||||
def at(collection, n, default \\ nil) do
|
||||
case fetch(collection, n) do
|
||||
{ :ok, h } -> h
|
||||
:error -> default
|
||||
@@ -315,7 +315,7 @@ defmodule Enum do
|
||||
"""
|
||||
@spec chunk(t, non_neg_integer, non_neg_integer) :: [list]
|
||||
@spec chunk(t, non_neg_integer, non_neg_integer, t | nil) :: [list]
|
||||
def chunk(coll, n, step, pad // nil) when n > 0 and step > 0 do
|
||||
def chunk(coll, n, step, pad \\ nil) when n > 0 and step > 0 do
|
||||
limit = :erlang.max(n, step)
|
||||
|
||||
{ _, { acc, { buffer, i } } } =
|
||||
@@ -671,7 +671,7 @@ defmodule Enum do
|
||||
"""
|
||||
@spec find(t, (element -> any)) :: element | nil
|
||||
@spec find(t, default, (element -> any)) :: element | default
|
||||
def find(collection, ifnone // nil, fun)
|
||||
def find(collection, ifnone \\ nil, fun)
|
||||
|
||||
def find(collection, ifnone, fun) when is_list(collection) do
|
||||
do_find(collection, ifnone, fun)
|
||||
@@ -698,7 +698,7 @@ defmodule Enum do
|
||||
"""
|
||||
@spec find_value(t, (element -> any)) :: any | :nil
|
||||
@spec find_value(t, any, (element -> any)) :: any | :nil
|
||||
def find_value(collection, ifnone // nil, fun)
|
||||
def find_value(collection, ifnone \\ nil, fun)
|
||||
|
||||
def find_value(collection, ifnone, fun) when is_list(collection) do
|
||||
do_find_value(collection, ifnone, fun)
|
||||
@@ -741,20 +741,6 @@ defmodule Enum do
|
||||
end
|
||||
end
|
||||
|
||||
@doc false
|
||||
@spec first(t) :: :nil | element
|
||||
def first(list) when is_list(list) do
|
||||
IO.write "Enum.first/1 is deprecated, please use Enum.at/2 or List.first/1 instead\n#{Exception.format_stacktrace}"
|
||||
List.first(list)
|
||||
end
|
||||
|
||||
def first(collection) do
|
||||
IO.write "Enum.first/1 is deprecated, please use Enum.at/2 instead\n#{Exception.format_stacktrace}"
|
||||
Enumerable.reduce(collection, { :cont, nil }, fn(entry, _) ->
|
||||
{ :halt, entry }
|
||||
end) |> elem(1)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns a new collection appending the result of invoking `fun`
|
||||
on each corresponding item of `collection`.
|
||||
@@ -795,7 +781,7 @@ defmodule Enum do
|
||||
{ [1,2,3], 3 }
|
||||
|
||||
"""
|
||||
@spec flat_map_reduce(t, acc, fun) :: list when
|
||||
@spec flat_map_reduce(t, acc, fun) :: { [any], any } when
|
||||
fun: (element, acc -> { t, acc } | { :halt, acc }),
|
||||
acc: any
|
||||
def flat_map_reduce(collection, acc, fun) do
|
||||
@@ -860,7 +846,7 @@ defmodule Enum do
|
||||
"""
|
||||
@spec join(t) :: String.t
|
||||
@spec join(t, String.t) :: String.t
|
||||
def join(collection, joiner // "")
|
||||
def join(collection, joiner \\ "")
|
||||
|
||||
def join(collection, joiner) when is_binary(joiner) do
|
||||
reduce(collection, "", fn
|
||||
@@ -913,7 +899,7 @@ defmodule Enum do
|
||||
"""
|
||||
@spec map_join(t, (element -> any)) :: String.t
|
||||
@spec map_join(t, String.t, (element -> any)) :: String.t
|
||||
def map_join(collection, joiner // "", mapper)
|
||||
def map_join(collection, joiner \\ "", mapper)
|
||||
|
||||
def map_join(collection, joiner, mapper) when is_binary(joiner) do
|
||||
reduce(collection, "", fn
|
||||
@@ -1034,8 +1020,8 @@ defmodule Enum do
|
||||
value
|
||||
{ :error, module } ->
|
||||
module.reduce(collection, { :cont, false }, fn
|
||||
^value, _ -> { :halt, true }
|
||||
_, _ -> { :cont, false }
|
||||
v, _ when v === value -> { :halt, true }
|
||||
_, _ -> { :cont, false }
|
||||
end) |> elem(1)
|
||||
end
|
||||
end
|
||||
@@ -1599,7 +1585,7 @@ defmodule Enum do
|
||||
"""
|
||||
@spec uniq(t) :: list
|
||||
@spec uniq(t, (element -> term)) :: list
|
||||
def uniq(collection, fun // fn x -> x end)
|
||||
def uniq(collection, fun \\ fn x -> x end)
|
||||
|
||||
def uniq(collection, fun) when is_list(collection) do
|
||||
do_uniq(collection, [], fun)
|
||||
|
||||
+52
-51
@@ -10,21 +10,21 @@ defexception SystemLimitError, message: "a system limit has been reached"
|
||||
defexception SyntaxError, [file: nil, line: nil, description: "syntax error"] do
|
||||
def message(exception) do
|
||||
Exception.format_file_line(Path.relative_to_cwd(exception.file), exception.line) <>
|
||||
exception.description
|
||||
" " <> exception.description
|
||||
end
|
||||
end
|
||||
|
||||
defexception TokenMissingError, [file: nil, line: nil, description: "expression is incomplete"] do
|
||||
def message(exception) do
|
||||
Exception.format_file_line(Path.relative_to_cwd(exception.file), exception.line) <>
|
||||
exception.description
|
||||
" " <> exception.description
|
||||
end
|
||||
end
|
||||
|
||||
defexception CompileError, [file: nil, line: nil, description: "compile error"] do
|
||||
def message(exception) do
|
||||
Exception.format_file_line(Path.relative_to_cwd(exception.file), exception.line) <>
|
||||
exception.description
|
||||
" " <> exception.description
|
||||
end
|
||||
end
|
||||
|
||||
@@ -54,9 +54,16 @@ end
|
||||
|
||||
defexception BadArityError, [function: nil, args: nil] do
|
||||
def message(exception) do
|
||||
args = Enum.map_join(exception.args, ", ", &inspect/1)
|
||||
"bad arity error: #{inspect(exception.function)} called with (#{args})"
|
||||
fun = exception.function
|
||||
args = exception.args
|
||||
insp = Enum.map_join(args, ", ", &inspect/1)
|
||||
{ :arity, arity } = :erlang.fun_info(fun, :arity)
|
||||
"#{inspect(fun)} with arity #{arity} called with #{count(length(args), insp)}"
|
||||
end
|
||||
|
||||
defp count(0, _insp), do: "no arguments"
|
||||
defp count(1, insp), do: "1 argument (#{insp})"
|
||||
defp count(x, insp), do: "#{x} arguments (#{insp})"
|
||||
end
|
||||
|
||||
defexception UndefinedFunctionError, [module: nil, function: nil, arity: nil] do
|
||||
@@ -142,6 +149,15 @@ defmodule Exception do
|
||||
pass the `System.stacktrace` as argument.
|
||||
"""
|
||||
|
||||
@type stacktrace :: [stacktrace_entry]
|
||||
|
||||
@type stacktrace_entry ::
|
||||
{ module, function, arity_or_args, location } |
|
||||
{ function, arity_or_args, location }
|
||||
|
||||
@typep arity_or_args :: non_neg_integer | list
|
||||
@typep location :: Keyword.t
|
||||
|
||||
@doc """
|
||||
Normalizes an exception, converting Erlang exceptions
|
||||
to Elixir exceptions.
|
||||
@@ -214,8 +230,9 @@ defmodule Exception do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Receives a tuple representing a stacktrace entry and formats it.
|
||||
Receives an stacktrace entry and formats it into a string.
|
||||
"""
|
||||
@spec format_stacktrace_entry(stacktrace_entry) :: String.t
|
||||
def format_stacktrace_entry(entry)
|
||||
|
||||
# From Macro.Env.stacktrace
|
||||
@@ -255,43 +272,18 @@ defmodule Exception do
|
||||
calculates a new stacktrace based on the caller and formats it. As
|
||||
a consequence, the value of `System.stacktrace` is changed.
|
||||
"""
|
||||
def format_stacktrace(trace // nil) do
|
||||
def format_stacktrace(trace \\ nil) do
|
||||
trace = trace || try do
|
||||
throw(:stacktrace)
|
||||
catch
|
||||
:stacktrace -> Enum.drop(:erlang.get_stacktrace, 1)
|
||||
end
|
||||
|
||||
case trace do
|
||||
[] -> "\n"
|
||||
s -> " " <> Enum.map_join(s, "\n ", &format_stacktrace_entry(&1)) <> "\n"
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Formats the caller, i.e. the first entry in the stacktrace.
|
||||
|
||||
A stacktrace must be given as an argument. If not, this function
|
||||
calculates a new stacktrace based on the caller and formats it. As
|
||||
a consequence, the value of `System.stacktrace` is changed.
|
||||
|
||||
Notice that due to tail call optimization, the stacktrace
|
||||
may not report the direct caller of the function.
|
||||
"""
|
||||
def format_caller(trace // nil) do
|
||||
trace = trace || try do
|
||||
throw(:stacktrace)
|
||||
catch
|
||||
:stacktrace -> Enum.drop(:erlang.get_stacktrace, 1)
|
||||
end
|
||||
|
||||
if entry = Enum.at(trace, 1) do
|
||||
format_stacktrace_entry(entry)
|
||||
else
|
||||
"nofile:0: "
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Receives an anonymous function and arity and formats it as
|
||||
shown in stacktraces. The arity may also be a list of arguments.
|
||||
@@ -303,12 +295,7 @@ defmodule Exception do
|
||||
|
||||
"""
|
||||
def format_fa(fun, arity) do
|
||||
if is_list(arity) do
|
||||
inspected = lc x inlist arity, do: inspect(x)
|
||||
"#{inspect fun}(#{Enum.join(inspected, ", ")})"
|
||||
else
|
||||
"#{inspect fun}/#{arity}"
|
||||
end
|
||||
"#{inspect fun}#{format_arity(arity)}"
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -325,22 +312,32 @@ defmodule Exception do
|
||||
iex> Exception.format_mfa nil, :bar, []
|
||||
"nil.bar()"
|
||||
|
||||
Anonymous functions are reported as -func/arity-anonfn-count-,
|
||||
where func is the name of the enclosing function. Convert to
|
||||
"anonymous fn in func/arity"
|
||||
"""
|
||||
def format_mfa(module, fun, arity) do
|
||||
def format_mfa(module, fun, arity) when is_atom(fun) do
|
||||
fun =
|
||||
case inspect(fun) do
|
||||
<< ?:, erl :: binary >> -> erl
|
||||
elixir -> elixir
|
||||
":" <> fun -> fun
|
||||
fun -> fun
|
||||
end
|
||||
|
||||
if is_list(arity) do
|
||||
inspected = lc x inlist arity, do: inspect(x)
|
||||
"#{inspect module}.#{fun}(#{Enum.join(inspected, ", ")})"
|
||||
else
|
||||
"#{inspect module}.#{fun}/#{arity}"
|
||||
case match?("\"-" <> _, fun) and String.split(fun, "-") do
|
||||
[ "\"", outer_fun, "fun", _count, "\"" ] ->
|
||||
"anonymous fn#{format_arity(arity)} in #{inspect module}.#{outer_fun}"
|
||||
_ ->
|
||||
"#{inspect module}.#{fun}#{format_arity(arity)}"
|
||||
end
|
||||
end
|
||||
|
||||
defp format_arity(arity) when is_list(arity) do
|
||||
inspected = lc x inlist arity, do: inspect(x)
|
||||
"(#{Enum.join(inspected, ", ")})"
|
||||
end
|
||||
|
||||
defp format_arity(arity), do: "/#{arity}"
|
||||
|
||||
@doc """
|
||||
Formats the given file and line as shown in stacktraces.
|
||||
If any of the values are nil, they are omitted.
|
||||
@@ -348,21 +345,25 @@ defmodule Exception do
|
||||
## Examples
|
||||
|
||||
iex> Exception.format_file_line("foo", 1)
|
||||
"foo:1: "
|
||||
"foo:1:"
|
||||
|
||||
iex> Exception.format_file_line("foo", nil)
|
||||
"foo: "
|
||||
"foo:"
|
||||
|
||||
iex> Exception.format_file_line(nil, nil)
|
||||
""
|
||||
|
||||
"""
|
||||
def format_file_line(file, line) do
|
||||
format_file_line(file, line, "")
|
||||
end
|
||||
|
||||
defp format_file_line(file, line, suffix) do
|
||||
if file do
|
||||
if line && line != 0 do
|
||||
"#{file}:#{line}: "
|
||||
"#{file}:#{line}:#{suffix}"
|
||||
else
|
||||
"#{file}: "
|
||||
"#{file}:#{suffix}"
|
||||
end
|
||||
else
|
||||
""
|
||||
@@ -370,7 +371,7 @@ defmodule Exception do
|
||||
end
|
||||
|
||||
defp format_location(opts) do
|
||||
format_file_line Keyword.get(opts, :file), Keyword.get(opts, :line)
|
||||
format_file_line Keyword.get(opts, :file), Keyword.get(opts, :line), " "
|
||||
end
|
||||
|
||||
defp from_stacktrace([{ module, function, args, _ }|_]) when is_list(args) do
|
||||
|
||||
+42
-72
@@ -220,7 +220,7 @@ defmodule File do
|
||||
Default is `:local`.
|
||||
|
||||
"""
|
||||
def stat(path, opts // []) do
|
||||
def stat(path, opts \\ []) do
|
||||
case F.read_file_info(path, opts) do
|
||||
{:ok, fileinfo} ->
|
||||
{:ok, set_elem(fileinfo, 0, File.Stat) }
|
||||
@@ -233,7 +233,7 @@ defmodule File do
|
||||
Same as `stat/2` but returns the `File.Stat` directly and
|
||||
throws `File.Error` if an error is returned.
|
||||
"""
|
||||
def stat!(path, opts // []) do
|
||||
def stat!(path, opts \\ []) do
|
||||
case stat(path, opts) do
|
||||
{:ok, info} -> info
|
||||
{:error, reason} ->
|
||||
@@ -245,7 +245,7 @@ defmodule File do
|
||||
Writes the given `File.Stat` back to the filesystem at the given
|
||||
path. Returns `:ok` or `{ :error, reason }`.
|
||||
"""
|
||||
def write_stat(path, File.Stat[] = stat, opts // []) do
|
||||
def write_stat(path, File.Stat[] = stat, opts \\ []) do
|
||||
F.write_file_info(path, set_elem(stat, 0, :file_info), opts)
|
||||
end
|
||||
|
||||
@@ -253,7 +253,7 @@ defmodule File do
|
||||
Same as `write_stat/3` but raises an exception if it fails.
|
||||
Returns `:ok` otherwise.
|
||||
"""
|
||||
def write_stat!(path, File.Stat[] = stat, opts // []) do
|
||||
def write_stat!(path, File.Stat[] = stat, opts \\ []) do
|
||||
case write_stat(path, stat, opts) do
|
||||
:ok -> :ok
|
||||
{ :error, reason } ->
|
||||
@@ -265,7 +265,7 @@ defmodule File do
|
||||
Updates modification time (mtime) and access time (atime) of
|
||||
the given file. File is created if it doesn’t exist.
|
||||
"""
|
||||
def touch(path, time // :calendar.local_time) do
|
||||
def touch(path, time \\ :calendar.local_time) do
|
||||
case F.change_time(path, time) do
|
||||
{ :error, :enoent } ->
|
||||
write(path, "")
|
||||
@@ -279,7 +279,7 @@ defmodule File do
|
||||
Same as `touch/2` but raises an exception if it fails.
|
||||
Returns `:ok` otherwise.
|
||||
"""
|
||||
def touch!(path, time // :calendar.local_time) do
|
||||
def touch!(path, time \\ :calendar.local_time) do
|
||||
case touch(path, time) do
|
||||
:ok -> :ok
|
||||
{ :error, reason } ->
|
||||
@@ -309,7 +309,7 @@ defmodule File do
|
||||
Typical error reasons are the same as in `open/2`,
|
||||
`read/1` and `write/3`.
|
||||
"""
|
||||
def copy(source, destination, bytes_count // :infinity) do
|
||||
def copy(source, destination, bytes_count \\ :infinity) do
|
||||
F.copy(source, destination, bytes_count)
|
||||
end
|
||||
|
||||
@@ -317,7 +317,7 @@ defmodule File do
|
||||
The same as `copy/3` but raises an `File.CopyError` if it fails.
|
||||
Returns the `bytes_copied` otherwise.
|
||||
"""
|
||||
def copy!(source, destination, bytes_count // :infinity) do
|
||||
def copy!(source, destination, bytes_count \\ :infinity) do
|
||||
case copy(source, destination, bytes_count) do
|
||||
{ :ok, bytes_count } -> bytes_count
|
||||
{ :error, reason } ->
|
||||
@@ -329,11 +329,6 @@ defmodule File do
|
||||
@doc """
|
||||
Copies the contents in `source` to `destination` preserving its mode.
|
||||
|
||||
Similar to the command `cp` in Unix systems, this function
|
||||
behaves differently depending if `destination` is a directory
|
||||
or not. In particular, if `destination` is a directory, it
|
||||
copies the contents of `source` to `destination/source`.
|
||||
|
||||
If a file already exists in the destination, it invokes a
|
||||
callback which should return `true` if the existing file
|
||||
should be overwritten, `false` otherwise. It defaults to return `true`.
|
||||
@@ -344,17 +339,14 @@ defmodule File do
|
||||
If you want to copy contents from an io device to another device
|
||||
or do a straight copy from a source to a destination without
|
||||
preserving modes, check `copy/3` instead.
|
||||
"""
|
||||
def cp(source, destination, callback // fn(_, _) -> true end) do
|
||||
output =
|
||||
if dir?(destination) do
|
||||
mkdir(destination)
|
||||
FN.join(destination, FN.basename(source))
|
||||
else
|
||||
destination
|
||||
end
|
||||
|
||||
case do_cp_file(source, output, callback, []) do
|
||||
Note: The command `cp` in Unix systems behaves differently depending
|
||||
if `destination` is an existing directory or not. We have chosen to
|
||||
explicitly disallow this behaviour. If destination is a directory, an
|
||||
error will be returned.
|
||||
"""
|
||||
def cp(source, destination, callback \\ fn(_, _) -> true end) do
|
||||
case do_cp_file(source, destination, callback, []) do
|
||||
{ :error, reason, _ } -> { :error, reason }
|
||||
_ -> :ok
|
||||
end
|
||||
@@ -364,7 +356,7 @@ defmodule File do
|
||||
The same as `cp/3`, but raises `File.CopyError` if it fails.
|
||||
Returns the list of copied files otherwise.
|
||||
"""
|
||||
def cp!(source, destination, callback // fn(_, _) -> true end) do
|
||||
def cp!(source, destination, callback \\ fn(_, _) -> true end) do
|
||||
case cp(source, destination, callback) do
|
||||
:ok -> :ok
|
||||
{ :error, reason } ->
|
||||
@@ -374,16 +366,12 @@ defmodule File do
|
||||
end
|
||||
end
|
||||
|
||||
@doc %S"""
|
||||
@doc ~S"""
|
||||
Copies the contents in source to destination.
|
||||
Similar to the command `cp -r` in Unix systems,
|
||||
this function behaves differently depending
|
||||
if `source` and `destination` are a file or a directory.
|
||||
|
||||
If both are files, it simply copies `source` to
|
||||
`destination`. However, if `destination` is a directory,
|
||||
it copies the contents of `source` to `destination/source`
|
||||
recursively.
|
||||
If the source is a file, it copies `source` to
|
||||
`destination`. If the source is a directory, it copies
|
||||
the contents inside source into the destination.
|
||||
|
||||
If a file already exists in the destination,
|
||||
it invokes a callback which should return
|
||||
@@ -403,33 +391,26 @@ defmodule File do
|
||||
success with all files and directories copied in no
|
||||
specific order, `{ :error, reason }` otherwise.
|
||||
|
||||
Note: The command `cp` in Unix systems behaves differently
|
||||
depending if `destination` is an existing directory or not.
|
||||
We have chosen to explicitly disallow this behaviour.
|
||||
|
||||
## Examples
|
||||
|
||||
# Copies "a.txt" to "tmp/a.txt"
|
||||
File.cp_r "a.txt", "tmp"
|
||||
|
||||
# Copies all files in "samples" to "tmp/samples"
|
||||
File.cp_r "samples", "tmp"
|
||||
# Copies "a.txt" to "tmp"
|
||||
File.cp_r "a.txt", "tmp.txt"
|
||||
|
||||
# Copies all files in "samples" to "tmp"
|
||||
File.cp_r "samples/.", "tmp"
|
||||
File.cp_r "samples", "tmp"
|
||||
|
||||
# Same as before, but asks the user how to proceed in case of conflicts
|
||||
File.cp_r "samples/.", "tmp", fn(source, destination) ->
|
||||
File.cp_r "samples", "tmp", fn(source, destination) ->
|
||||
IO.gets("Overwriting #{destination} by #{source}. Type y to confirm.") == "y"
|
||||
end
|
||||
|
||||
"""
|
||||
def cp_r(source, destination, callback // fn(_, _) -> true end) when is_function(callback) do
|
||||
output =
|
||||
if dir?(destination) || dir?(source) do
|
||||
mkdir(destination)
|
||||
FN.join(destination, FN.basename(source))
|
||||
else
|
||||
destination
|
||||
end
|
||||
|
||||
case do_cp_r(source, output, callback, []) do
|
||||
def cp_r(source, destination, callback \\ fn(_, _) -> true end) when is_function(callback) do
|
||||
case do_cp_r(source, destination, callback, []) do
|
||||
{ :error, _, _ } = error -> error
|
||||
res -> { :ok, res }
|
||||
end
|
||||
@@ -439,7 +420,7 @@ defmodule File do
|
||||
The same as `cp_r/3`, but raises `File.CopyError` if it fails.
|
||||
Returns the list of copied files otherwise.
|
||||
"""
|
||||
def cp_r!(source, destination, callback // fn(_, _) -> true end) do
|
||||
def cp_r!(source, destination, callback \\ fn(_, _) -> true end) do
|
||||
case cp_r(source, destination, callback) do
|
||||
{ :ok, files } -> files
|
||||
{ :error, reason, file } ->
|
||||
@@ -547,14 +528,14 @@ defmodule File do
|
||||
|
||||
Check `File.open/2` for existing modes.
|
||||
"""
|
||||
def write(path, content, modes // []) do
|
||||
def write(path, content, modes \\ []) do
|
||||
F.write_file(path, content, modes)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Same as `write/3` but raises an exception if it fails, returns `:ok` otherwise.
|
||||
"""
|
||||
def write!(path, content, modes // []) do
|
||||
def write!(path, content, modes \\ []) do
|
||||
case F.write_file(path, content, modes) do
|
||||
:ok -> :ok
|
||||
{ :error, reason } ->
|
||||
@@ -767,7 +748,7 @@ defmodule File do
|
||||
|
||||
If a function is given two modes (instead of a list), it dispatches to `open/3`.
|
||||
|
||||
Check http://www.erlang.org/doc/man/file.html#open-2 for more information about
|
||||
Check http:\\www.erlang.org/doc/man/file.html#open-2 for more information about
|
||||
other options like `read_ahead` and `delayed_write`.
|
||||
|
||||
This function returns:
|
||||
@@ -788,7 +769,7 @@ defmodule File do
|
||||
File.close(file)
|
||||
|
||||
"""
|
||||
def open(path, modes // [])
|
||||
def open(path, modes \\ [])
|
||||
|
||||
def open(path, modes) when is_list(modes) do
|
||||
F.open(path, open_defaults(modes, true))
|
||||
@@ -835,7 +816,7 @@ defmodule File do
|
||||
Same as `open/2` but raises an error if file could not be opened.
|
||||
Returns the `io_device` otherwise.
|
||||
"""
|
||||
def open!(path, modes // []) do
|
||||
def open!(path, modes \\ []) do
|
||||
case open(path, modes) do
|
||||
{ :ok, device } -> device
|
||||
{ :error, reason } ->
|
||||
@@ -922,7 +903,7 @@ defmodule File do
|
||||
It returns `{ :ok, [files] }` in case of success,
|
||||
`{ :error, reason }` otherwise.
|
||||
"""
|
||||
def ls(path // ".") do
|
||||
def ls(path \\ ".") do
|
||||
case F.list_dir(path) do
|
||||
{ :ok, file_list } -> { :ok, Enum.map(file_list, &to_string(&1)) }
|
||||
{ :error, _ } = error -> error
|
||||
@@ -933,7 +914,7 @@ defmodule File do
|
||||
The same as `ls/1` but raises `File.Error`
|
||||
in case of an error.
|
||||
"""
|
||||
def ls!(dir // ".") do
|
||||
def ls!(dir \\ ".") do
|
||||
case ls(dir) do
|
||||
{ :ok, value } -> value
|
||||
{ :error, reason } ->
|
||||
@@ -966,7 +947,7 @@ defmodule File do
|
||||
the file is opened with an encoding, then the slower `IO.read/2`
|
||||
is used to do the proper data conversion and guarantees.
|
||||
"""
|
||||
def stream!(path, modes // [], line_or_bytes // :line) do
|
||||
def stream!(path, modes \\ [], line_or_bytes \\ :line) do
|
||||
modes = open_defaults(modes, true)
|
||||
bin = nil? List.keyfind(modes, :encoding, 0)
|
||||
|
||||
@@ -988,20 +969,9 @@ defmodule File do
|
||||
Stream.resource(start_fun, next_fun, &F.close/1)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Receives a stream and returns a new stream that will open the file
|
||||
at the given `path` for write with the extra `modes` and write
|
||||
each value to the file.
|
||||
|
||||
The returned stream will fail for the same reasons as
|
||||
`File.open!/2`. Note that the file is opened only and every time
|
||||
streaming begins.
|
||||
|
||||
Note that stream by default uses `IO.binwrite/2` unless
|
||||
the file is opened with an encoding, then the slower `IO.write/2`
|
||||
is used to do the proper data conversion and guarantees.
|
||||
"""
|
||||
def stream_to!(stream, path, modes // []) do
|
||||
@doc false
|
||||
def stream_to!(stream, path, modes \\ []) do
|
||||
IO.write :stderr, "File.stream_to!/3 is deprecated and will be removed/1\n#{Exception.format_stacktrace}"
|
||||
modes = open_defaults([:write|List.delete(modes, :write)], true)
|
||||
bin = nil? List.keyfind(modes, :encoding, 0)
|
||||
|
||||
|
||||
@@ -72,7 +72,7 @@ defmodule Float do
|
||||
{ floatify(int, float, decimal), bitstring }
|
||||
end
|
||||
|
||||
defp floatify(int, float, decimal, exponential // 0) do
|
||||
defp floatify(int, float, decimal, exponential \\ 0) do
|
||||
multiplier = if int < 0, do: -1.0, else: 1.0
|
||||
|
||||
# Try to ensure the minimum amount of rounding errors
|
||||
@@ -82,7 +82,7 @@ defmodule Float do
|
||||
# iex(1)> 0.0001 * 75
|
||||
# 0.007500000000000001
|
||||
# Due to IEEE 754 floating point standard
|
||||
# http://docs.oracle.com/cd/E19957-01/806-3568/ncg_goldberg.html
|
||||
# http:\\docs.oracle.com/cd/E19957-01/806-3568/ncg_goldberg.html
|
||||
|
||||
final_decimal_places = decimal - exponential
|
||||
if final_decimal_places > 0 do
|
||||
|
||||
@@ -31,15 +31,15 @@ defmodule GenFSM.Behaviour do
|
||||
# API functions
|
||||
|
||||
def start_link() do
|
||||
:gen_fsm.start_link({:local, :cvm}, __MODULE__, [], [])
|
||||
:gen_fsm.start_link(__MODULE__, [], [])
|
||||
end
|
||||
|
||||
def insert_coin() do
|
||||
:gen_fsm.send_event(:cvm, :coin)
|
||||
def insert_coin(pid) do
|
||||
:gen_fsm.send_event(pid, :coin)
|
||||
end
|
||||
|
||||
def request_coffee() do
|
||||
:gen_fsm.send_event(:cvm, :request_coffee)
|
||||
def request_coffee(pid) do
|
||||
:gen_fsm.send_event(pid, :request_coffee)
|
||||
end
|
||||
|
||||
# Callbacks
|
||||
@@ -83,17 +83,17 @@ defmodule GenFSM.Behaviour do
|
||||
end
|
||||
end
|
||||
|
||||
{ :ok, _pid } = MyFsm.start_link()
|
||||
{ :ok, pid } = MyFsm.start_link()
|
||||
|
||||
MyFsm.insert_coin
|
||||
MyFsm.insert_coin(pid)
|
||||
#=> :ok
|
||||
MyFsm.insert_coin
|
||||
MyFsm.insert_coin(pid)
|
||||
#=> :ok
|
||||
|
||||
MyFsm.request_coffee
|
||||
MyFsm.request_coffee(pid)
|
||||
#=> :ok
|
||||
|
||||
MyFsm.insert_coin
|
||||
MyFsm.insert_coin(pid)
|
||||
#=> :ok
|
||||
#=> Here's your coffee!
|
||||
|
||||
@@ -166,7 +166,9 @@ defmodule GenFSM.Behaviour do
|
||||
@doc false
|
||||
defmacro __using__(_) do
|
||||
quote location: :keep do
|
||||
@behavior :gen_fsm
|
||||
IO.puts :stderr, "warning: GenFSM is deprecated and it will be removed in upcoming releases\n#{Exception.format_stacktrace}"
|
||||
|
||||
@behaviour :gen_fsm
|
||||
|
||||
@doc false
|
||||
def handle_event(event, state_name, state_data) do
|
||||
|
||||
@@ -86,7 +86,7 @@ defmodule GenServer.Behaviour do
|
||||
@doc false
|
||||
defmacro __using__(_) do
|
||||
quote location: :keep do
|
||||
@behavior :gen_server
|
||||
@behaviour :gen_server
|
||||
|
||||
@doc false
|
||||
def init(args) do
|
||||
|
||||
+156
-356
@@ -2,46 +2,14 @@ defmodule HashDict do
|
||||
@moduledoc """
|
||||
A key-value store.
|
||||
|
||||
The `HashDict` is meant to work well with both small and
|
||||
large set of keys and it is an implementation of the `Dict`
|
||||
behaviour. For more information about the functions and
|
||||
their APIs, please consult the `Dict` module.
|
||||
The `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.Behaviour
|
||||
|
||||
# A dictionary (key-value) implementation based on dynamic hashing.
|
||||
#
|
||||
# This implementation is based on hash tries. We first start with
|
||||
# a set of 8 buckets and expand when the density is about 5 entries
|
||||
# per bucket. We use bit shifting to make rehashing faster on
|
||||
# expansion.
|
||||
#
|
||||
# Compared to dict, it provides many enhancements:
|
||||
#
|
||||
# 1. HashDict buckets are ordered sets, this gives us faster access
|
||||
# and modification times
|
||||
#
|
||||
# 2. It uses phash2 to calculate the hash (instead of phash)
|
||||
#
|
||||
# 3. The dictionary first starts with a single bucket, instead of
|
||||
# a set of 8 buckets. This allow us to skip hashing altogher
|
||||
# for small dictionaries, providing faster operations and
|
||||
# reducing memory consumption
|
||||
#
|
||||
# 4. Once we reach 8 elements, the dictionary is promoted to a
|
||||
# set of buckets
|
||||
|
||||
# The ordered record contains a single bucket
|
||||
@ordered_threshold 8
|
||||
|
||||
defrecordp :ordered, HashDict,
|
||||
size: 0,
|
||||
bucket: []
|
||||
|
||||
# The bucketed record contains a series of buckets.
|
||||
@expand_load 5
|
||||
@contract_load 2
|
||||
@node_bitmap 0b111
|
||||
@node_shift 3
|
||||
@node_size 8
|
||||
@@ -49,23 +17,18 @@ defmodule HashDict do
|
||||
|
||||
defrecordp :trie, HashDict,
|
||||
size: 0,
|
||||
depth: 0,
|
||||
expand_on: @node_size * @expand_load,
|
||||
contract_on: @contract_load,
|
||||
root: @node_template
|
||||
|
||||
import Bitwise
|
||||
|
||||
# Let's inline common instructions
|
||||
# Inline common instructions
|
||||
@compile :inline_list_funcs
|
||||
@compile { :inline, bucket_hash: 1, bucket_index: 1, bucket_nth_index: 2, bucket_next: 1 }
|
||||
@compile { :inline, key_hash: 1, key_mask: 1, key_shift: 1 }
|
||||
|
||||
@doc """
|
||||
Creates a new empty dict.
|
||||
"""
|
||||
@spec new :: Dict.t
|
||||
def new do
|
||||
ordered()
|
||||
trie()
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -77,9 +40,9 @@ defmodule HashDict do
|
||||
#=> #HashDict<[a: 2, b: 1]>
|
||||
|
||||
"""
|
||||
@spec new(list({key :: term, value :: term})) :: Dict.t
|
||||
def new(pairs) do
|
||||
Enum.reduce pairs, ordered(), fn { k, v }, dict ->
|
||||
@spec new(Enum.t) :: Dict.t
|
||||
def new(enum) do
|
||||
Enum.reduce enum, trie(), fn { k, v }, dict ->
|
||||
put(dict, k, v)
|
||||
end
|
||||
end
|
||||
@@ -94,386 +57,223 @@ defmodule HashDict do
|
||||
#=> #HashDict<[{"a","a"},{"b","b"}]>
|
||||
|
||||
"""
|
||||
@spec new(list, (term -> {key :: term, value ::term})) :: Dict.t
|
||||
def new(list, transform) when is_function(transform) do
|
||||
Enum.reduce list, new(), fn i, dict ->
|
||||
@spec new(Enum.t, (term -> {key :: term, value ::term})) :: Dict.t
|
||||
def new(enum, transform) when is_function(transform) do
|
||||
Enum.reduce enum, trie(), fn i, dict ->
|
||||
{ k, v } = transform.(i)
|
||||
put(dict, k, v)
|
||||
end
|
||||
end
|
||||
|
||||
def put(dict, key, value) do
|
||||
{ dict, _ } = dict_put(dict, key, { :put, value })
|
||||
dict
|
||||
def empty(trie()) do
|
||||
trie()
|
||||
end
|
||||
|
||||
def update!(dict, key, fun) when is_function(fun, 1) do
|
||||
case dict_put(dict, key, { :update, nil, fun }) do
|
||||
{ dict, 0 } ->
|
||||
dict
|
||||
{ _dict, 1 } ->
|
||||
raise KeyError, key: key
|
||||
end
|
||||
def put(trie(root: root, size: size), key, value) do
|
||||
{ root, counter } = do_put(root, key, value, key_hash(key))
|
||||
trie(root: root, size: size + counter)
|
||||
end
|
||||
|
||||
def update(dict, key, initial, fun) when is_function(fun, 1) do
|
||||
{ dict, _ } = dict_put(dict, key, { :update, initial, fun })
|
||||
dict
|
||||
def update!(trie(root: root, size: size), key, fun) when is_function(fun, 1) do
|
||||
{ root, counter } = do_update(root, key, fn -> raise KeyError, key: key end, fun, key_hash(key))
|
||||
trie(root: root, size: size + counter)
|
||||
end
|
||||
|
||||
def fetch(ordered(bucket: bucket), key) do
|
||||
bucket_get(bucket, key)
|
||||
def update(trie(root: root, size: size), key, initial, fun) when is_function(fun, 1) do
|
||||
{ root, counter } = do_update(root, key, fn -> initial end, fun, key_hash(key))
|
||||
trie(root: root, size: size + counter)
|
||||
end
|
||||
|
||||
def fetch(trie(root: root, depth: depth), key) do
|
||||
bucket_get(node_bucket(root, depth, bucket_hash(key)), key)
|
||||
end
|
||||
|
||||
def pop(dict, key, default // nil) do
|
||||
case dict_delete(dict, key) do
|
||||
{ dict, _, 0 } -> { default, dict }
|
||||
{ dict, value, _ } -> { value, dict }
|
||||
end
|
||||
def fetch(trie(root: root), key) do
|
||||
do_fetch(root, key, key_hash(key))
|
||||
end
|
||||
|
||||
def delete(dict, key) do
|
||||
{ dict, _, _ } = dict_delete(dict, key)
|
||||
dict
|
||||
end
|
||||
|
||||
def size(dict) do
|
||||
elem(dict, 1)
|
||||
end
|
||||
|
||||
def empty(_) do
|
||||
ordered()
|
||||
end
|
||||
|
||||
def merge(dict, enum, callback // fn(_k, _v1, v2) -> v2 end)
|
||||
|
||||
def merge(dict1, dict2, callback) when is_record(dict1, HashDict) and is_record(dict2, HashDict) and elem(dict1, 1) < elem(dict2, 1) do
|
||||
dict_fold dict1, dict2, fn [k|v1], acc ->
|
||||
update(acc, k, v1, &callback.(k, v1, &1))
|
||||
case dict_delete(dict, key) do
|
||||
{ dict, _value } -> dict
|
||||
:error -> dict
|
||||
end
|
||||
end
|
||||
|
||||
def merge(dict1, dict2, callback) when is_record(dict1, HashDict) and is_record(dict2, HashDict) do
|
||||
dict_fold dict2, dict1, fn [k|v2], acc ->
|
||||
update(acc, k, v2, &callback.(k, &1, v2))
|
||||
def pop(dict, key, default \\ nil) do
|
||||
case dict_delete(dict, key) do
|
||||
{ dict, value } -> { value, dict }
|
||||
:error -> { default, dict }
|
||||
end
|
||||
end
|
||||
|
||||
def merge(dict, enumerable, callback) when is_record(dict, HashDict) do
|
||||
super(dict, enumerable, callback)
|
||||
def size(trie(size: size)) do
|
||||
size
|
||||
end
|
||||
|
||||
def reduce(trie(root: root), acc, fun) do
|
||||
do_reduce(root, acc, fun, @node_size, fn
|
||||
{:suspend, acc} -> {:suspended, acc, &{ :done, elem(&1, 1) }}
|
||||
{:halt, acc} -> {:halted, acc}
|
||||
{:cont, acc} -> {:done, acc}
|
||||
end)
|
||||
end
|
||||
|
||||
def split(dict, keys) do
|
||||
split(keys, new, dict)
|
||||
end
|
||||
|
||||
defp split([], including, excluding) do
|
||||
{ including, excluding }
|
||||
end
|
||||
|
||||
defp split([key|keys], including, excluding) do
|
||||
case dict_delete(excluding, key) do
|
||||
{ excluding, _, 0 } -> split(keys, including, excluding)
|
||||
{ excluding, value, _ } -> split(keys, put(including, key, value), excluding)
|
||||
Enum.reduce keys, { new, dict }, fn key, { inc, exc } = acc ->
|
||||
case dict_delete(exc, key) do
|
||||
{ exc, value } -> { put(inc, key, value), exc }
|
||||
:error -> acc
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@doc false
|
||||
def reduce(ordered(bucket: bucket), acc, fun) do
|
||||
bucket_reduce(bucket, acc, fun)
|
||||
def merge(trie(size: size1) = dict1, trie(size: size2) = dict2, callback) when size1 < size2 do
|
||||
reduce(dict1, { :cont, dict2 }, fn { k, v1 }, acc ->
|
||||
{ :cont, update(acc, k, v1, &callback.(k, v1, &1)) }
|
||||
end) |> elem(1)
|
||||
end
|
||||
|
||||
def reduce(trie(root: root, depth: depth), acc, fun) do
|
||||
node_reduce(root, depth, acc, fun, @node_size,
|
||||
fn { :cont, acc }, _f -> { :done, acc } end)
|
||||
def merge(trie() = dict1, trie() = dict2, callback) do
|
||||
reduce(dict2, { :cont, dict1 }, fn { k, v2 }, acc ->
|
||||
{ :cont, update(acc, k, v2, &callback.(k, &1, v2)) }
|
||||
end) |> elem(1)
|
||||
end
|
||||
|
||||
## Dict-wide functions
|
||||
## General helpers
|
||||
|
||||
defp dict_fold(ordered(bucket: bucket), acc, fun) do
|
||||
bucket_fold(bucket, acc, fun)
|
||||
end
|
||||
|
||||
defp dict_fold(trie(root: root, depth: depth), acc, fun) do
|
||||
node_fold(root, depth, acc, fun, @node_size)
|
||||
end
|
||||
|
||||
defp dict_put(ordered(size: @ordered_threshold, bucket: bucket), key, value) do
|
||||
root = node_relocate(bucket, 0)
|
||||
dict_put(trie(size: @ordered_threshold, root: root), key, value)
|
||||
end
|
||||
|
||||
defp dict_put(ordered(size: size, bucket: bucket) = dict, key, value) do
|
||||
{ new, count } = bucket_put(bucket, key, value)
|
||||
{ ordered(dict, size: size + count, bucket: new), count }
|
||||
end
|
||||
|
||||
defp dict_put(trie(root: root, depth: depth, size: size, expand_on: size, contract_on: contract_on) = dict, key, value) do
|
||||
root = node_expand(root, depth, depth + 1)
|
||||
dict = trie(dict, root: root, depth: depth + 1,
|
||||
expand_on: size * @node_size, contract_on: contract_on * @node_size)
|
||||
dict_put(dict, key, value)
|
||||
end
|
||||
|
||||
defp dict_put(trie(root: root, size: size, depth: depth) = dict, key, value) do
|
||||
pos = bucket_hash(key)
|
||||
{ root, count } = node_put(root, depth, pos, key, value)
|
||||
{ trie(dict, size: size + count, root: root), count }
|
||||
end
|
||||
|
||||
defp dict_delete(ordered(bucket: bucket, size: size) = dict, key) do
|
||||
case bucket_delete(bucket, key) do
|
||||
{ _, value, 0 } ->
|
||||
{ dict, value, 0 }
|
||||
{ new_bucket, value, -1 } ->
|
||||
{ ordered(dict, size: size - 1, bucket: new_bucket), value, -1 }
|
||||
defp dict_delete(trie(root: root, size: size), key) do
|
||||
case do_delete(root, key, key_hash(key)) do
|
||||
{ root, value } -> { trie(root: root, size: size - 1), value }
|
||||
:error -> :error
|
||||
end
|
||||
end
|
||||
|
||||
defp dict_delete(trie(root: root, size: size, depth: depth) = dict, key) do
|
||||
pos = bucket_hash(key)
|
||||
case node_delete(root, depth, pos, key) do
|
||||
{ _, value, 0 } ->
|
||||
{ dict, value, 0 }
|
||||
{ root, value, -1 } ->
|
||||
{ if depth > 0 and trie(dict, :contract_on) == size do
|
||||
root = node_contract(root, depth)
|
||||
trie(dict,
|
||||
root: root,
|
||||
size: size - 1,
|
||||
depth: depth - 1,
|
||||
contract_on: div(size, @node_size),
|
||||
expand_on: div(trie(dict, :expand_on), @node_size))
|
||||
else
|
||||
trie(dict, size: size - 1, root: root)
|
||||
end, value, -1 }
|
||||
## Dict manipulation
|
||||
|
||||
defp do_fetch(node, key, hash) do
|
||||
index = key_mask(hash)
|
||||
case elem(node, index) do
|
||||
[^key|v] -> {:ok, v}
|
||||
{^key, v, _} -> {:ok, v}
|
||||
{_, _, n} -> do_fetch(n, key, key_shift(hash))
|
||||
_ -> :error
|
||||
end
|
||||
end
|
||||
|
||||
## Bucket helpers
|
||||
|
||||
# Get value from the bucket
|
||||
defp bucket_get([[k|_]|_bucket], key) when k > key do
|
||||
:error
|
||||
defp do_put(node, key, value, hash) do
|
||||
index = key_mask(hash)
|
||||
case elem(node, index) do
|
||||
[] ->
|
||||
{set_elem(node, index, [key|value]), 1}
|
||||
[^key|_] ->
|
||||
{set_elem(node, index, [key|value]), 0}
|
||||
[k|v] ->
|
||||
n = set_elem(@node_template, key_mask(key_shift(hash)), [key|value])
|
||||
{set_elem(node, index, {k, v, n}), 1}
|
||||
{^key, _, n} ->
|
||||
{set_elem(node, index, {key, value, n}), 0}
|
||||
{k, v, n} ->
|
||||
{n, counter} = do_put(n, key, value, key_shift(hash))
|
||||
{set_elem(node, index, {k, v, n}), counter}
|
||||
end
|
||||
end
|
||||
|
||||
defp bucket_get([[key|value]|_bucket], key) do
|
||||
{ :ok, value }
|
||||
defp do_update(node, key, initial, fun, hash) do
|
||||
index = key_mask(hash)
|
||||
case elem(node, index) do
|
||||
[] ->
|
||||
{set_elem(node, index, [key|initial.()]), 1}
|
||||
[^key|value] ->
|
||||
{set_elem(node, index, [key|fun.(value)]), 0}
|
||||
[k|v] ->
|
||||
n = set_elem(@node_template, key_mask(key_shift(hash)), [key|initial.()])
|
||||
{set_elem(node, index, {k, v, n}), 1}
|
||||
{^key, value, n} ->
|
||||
{set_elem(node, index, {key, fun.(value), n}), 0}
|
||||
{k, v, n} ->
|
||||
{n, counter} = do_update(n, key, initial, fun, key_shift(hash))
|
||||
{set_elem(node, index, {k, v, n}), counter}
|
||||
end
|
||||
end
|
||||
|
||||
defp bucket_get([_e|bucket], key) do
|
||||
bucket_get(bucket, key)
|
||||
defp do_delete(node, key, hash) do
|
||||
index = key_mask(hash)
|
||||
case elem(node, index) do
|
||||
[] ->
|
||||
:error
|
||||
[^key|value] ->
|
||||
{set_elem(node, index, []), value}
|
||||
[_|_] ->
|
||||
:error
|
||||
{^key, value, n} ->
|
||||
{set_elem(node, index, do_compact_node(n)), value}
|
||||
{k, v, n} ->
|
||||
case do_delete(n, key, key_shift(hash)) do
|
||||
{@node_template, value} ->
|
||||
{set_elem(node, index, [k|v]), value}
|
||||
{n, value} ->
|
||||
{set_elem(node, index, {k, v, n}), value}
|
||||
:error ->
|
||||
:error
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defp bucket_get([], _key) do
|
||||
:error
|
||||
Enum.each 0..(@node_size - 1), fn index ->
|
||||
defp do_compact_node(node) when elem(node, unquote(index)) != [] do
|
||||
case elem(node, unquote(index)) do
|
||||
[k|v] ->
|
||||
case set_elem(node, unquote(index), []) do
|
||||
@node_template -> [k|v]
|
||||
n -> {k, v, n}
|
||||
end
|
||||
{k, v, n} ->
|
||||
{k, v, set_elem(node, unquote(index), do_compact_node(n))}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Puts a value in the bucket
|
||||
defp bucket_put([[k|_]|_]=bucket, key, { :put, value }) when k > key do
|
||||
{ [[key|value]|bucket], 1 }
|
||||
## Dict reduce
|
||||
|
||||
defp do_reduce_each(_node, {:halt, acc}, _fun, _next) do
|
||||
{:halted, acc}
|
||||
end
|
||||
|
||||
defp bucket_put([[k|_]|_]=bucket, key, { :update, initial, _fun }) when k > key do
|
||||
{ [[key|initial]|bucket], 1 }
|
||||
defp do_reduce_each(node, {:suspend, acc}, fun, next) do
|
||||
{:suspended, acc, &do_reduce_each(node, &1, fun, next)}
|
||||
end
|
||||
|
||||
defp bucket_put([[key|_]|bucket], key, { :put, value }) do
|
||||
{ [[key|value]|bucket], 0 }
|
||||
defp do_reduce_each([], acc, _fun, next) do
|
||||
next.(acc)
|
||||
end
|
||||
|
||||
defp bucket_put([[key|value]|bucket], key, { :update, _initial, fun }) do
|
||||
{ [[key|fun.(value)]|bucket], 0 }
|
||||
defp do_reduce_each([k|v], {:cont, acc}, fun, next) do
|
||||
next.(fun.({k,v}, acc))
|
||||
end
|
||||
|
||||
defp bucket_put([e|bucket], key, value) do
|
||||
{ rest, count } = bucket_put(bucket, key, value)
|
||||
{ [e|rest], count }
|
||||
defp do_reduce_each({k, v, n}, {:cont, acc}, fun, next) do
|
||||
do_reduce(n, fun.({k, v}, acc), fun, @node_size, next)
|
||||
end
|
||||
|
||||
defp bucket_put([], key, { :put, value }) do
|
||||
{ [[key|value]], 1 }
|
||||
defp do_reduce(node, acc, fun, count, next) when count > 0 do
|
||||
do_reduce_each(:erlang.element(count, node), acc, fun, &do_reduce(node, &1, fun, count - 1, next))
|
||||
end
|
||||
|
||||
defp bucket_put([], key, { :update, initial, _fun }) do
|
||||
{ [[key|initial]], 1 }
|
||||
defp do_reduce(_node, acc, _fun, 0, next) do
|
||||
next.(acc)
|
||||
end
|
||||
|
||||
# Puts a value in the bucket without returning
|
||||
# the operation value
|
||||
defp bucket_put!([[k|_]|_]=bucket, key, value) when k > key, do: [[key|value]|bucket]
|
||||
defp bucket_put!([[key|_]|bucket], key, value), do: [[key|value]|bucket]
|
||||
defp bucket_put!([e|bucket], key, value), do: [e|bucket_put!(bucket, key, value)]
|
||||
defp bucket_put!([], key, value), do: [[key|value]]
|
||||
## Key operations
|
||||
|
||||
# Deletes a key from the bucket
|
||||
defp bucket_delete([[k|_]|_]=bucket, key) when k > key do
|
||||
{ bucket, nil, 0 }
|
||||
end
|
||||
import Bitwise
|
||||
|
||||
defp bucket_delete([[key|value]|bucket], key) do
|
||||
{ bucket, value, -1 }
|
||||
end
|
||||
|
||||
defp bucket_delete([e|bucket], key) do
|
||||
{ rest, value, count } = bucket_delete(bucket, key)
|
||||
{ [e|rest], value, count }
|
||||
end
|
||||
|
||||
defp bucket_delete([], _key) do
|
||||
{ [], nil, 0 }
|
||||
end
|
||||
|
||||
# Reduces the bucket
|
||||
defp bucket_reduce(_, { :halt, acc }, _fun), do: { :halted, acc }
|
||||
defp bucket_reduce(list, { :suspend, acc }, fun), do: { :suspended, acc, &bucket_reduce(list, &1, fun) }
|
||||
defp bucket_reduce([[k|v]|t], { :cont, acc }, fun), do: bucket_reduce(t, fun.({ k, v }, acc), fun)
|
||||
defp bucket_reduce([], { :cont, acc }, _fun), do: { :done, acc }
|
||||
|
||||
defp bucket_fold(bucket, acc, fun) do
|
||||
:lists.foldl(fun, acc, bucket)
|
||||
end
|
||||
|
||||
defp bucket_hash(key) do
|
||||
defp key_hash(key) do
|
||||
:erlang.phash2(key)
|
||||
end
|
||||
|
||||
defp bucket_index(hash) do
|
||||
defp key_mask(hash) do
|
||||
hash &&& @node_bitmap
|
||||
end
|
||||
|
||||
defp bucket_nth_index(hash, n) do
|
||||
(hash >>> (@node_shift * n)) &&& @node_bitmap
|
||||
end
|
||||
|
||||
defp bucket_next(hash) do
|
||||
defp key_shift(hash) do
|
||||
hash >>> @node_shift
|
||||
end
|
||||
|
||||
## Node helpers
|
||||
|
||||
# Gets a bucket from the node
|
||||
defp node_bucket(node, 0, hash) do
|
||||
elem(node, bucket_index(hash))
|
||||
end
|
||||
|
||||
defp node_bucket(node, depth, hash) do
|
||||
child = elem(node, bucket_index(hash))
|
||||
node_bucket(child, depth - 1, bucket_next(hash))
|
||||
end
|
||||
|
||||
# Puts a key-value into a node
|
||||
defp node_put(node, 0, hash, key, value) do
|
||||
pos = bucket_index(hash)
|
||||
{ new, count } = bucket_put(elem(node, pos), key, value)
|
||||
{ set_elem(node, pos, new), count }
|
||||
end
|
||||
|
||||
defp node_put(node, depth, hash, key, value) do
|
||||
pos = bucket_index(hash)
|
||||
{ new, count } = node_put(elem(node, pos), depth - 1, bucket_next(hash), key, value)
|
||||
{ set_elem(node, pos, new), count }
|
||||
end
|
||||
|
||||
# Deletes a key from the bucket
|
||||
defp node_delete(node, 0, hash, key) do
|
||||
pos = bucket_index(hash)
|
||||
case bucket_delete(elem(node, pos), key) do
|
||||
{ _, value, 0 } -> { node, value, 0 }
|
||||
{ new, value, -1 } -> { set_elem(node, pos, new), value, -1 }
|
||||
end
|
||||
end
|
||||
|
||||
defp node_delete(node, depth, hash, key) do
|
||||
pos = bucket_index(hash)
|
||||
case node_delete(elem(node, pos), depth - 1, bucket_next(hash), key) do
|
||||
{ _, value, 0 } -> { node, value, 0 }
|
||||
{ new, value, -1 } -> { set_elem(node, pos, new), value, -1 }
|
||||
end
|
||||
end
|
||||
|
||||
# Reduces a node recursively
|
||||
defp node_reduce(_, -1, { :halt, acc }, _fun, _count, _next) do
|
||||
{ :halted, acc }
|
||||
end
|
||||
|
||||
defp node_reduce(list, -1, { :suspend, acc }, fun, count, next) do
|
||||
{ :suspended, acc, &node_reduce(list, -1, &1, fun, count, next) }
|
||||
end
|
||||
|
||||
defp node_reduce([[k|v]|t], -1, { :cont, acc }, fun, _count, next) do
|
||||
node_reduce(t, -1, fun.({ k, v }, acc), fun, _count, next)
|
||||
end
|
||||
|
||||
defp node_reduce([], -1, { :cont, _ } = acc, fun, _count, next) do
|
||||
next.(acc, fun)
|
||||
end
|
||||
|
||||
defp node_reduce(node, depth, acc, fun, count, next) when count >= 1 do
|
||||
node_reduce(:erlang.element(count, node), depth - 1, acc, fun, @node_size,
|
||||
&node_reduce(node, depth, &1, &2, count - 1, next))
|
||||
end
|
||||
|
||||
defp node_reduce(_node, _, acc, fun, 0, next) do
|
||||
next.(acc, fun)
|
||||
end
|
||||
|
||||
# Folds a node recursively
|
||||
defp node_fold(bucket, -1, acc, fun, _) do
|
||||
bucket_fold(bucket, acc, fun)
|
||||
end
|
||||
|
||||
defp node_fold(node, depth, acc, fun, count) when count >= 1 do
|
||||
acc = node_fold(:erlang.element(count, node), depth - 1, acc, fun, @node_size)
|
||||
node_fold(node, depth, acc, fun, count - 1)
|
||||
end
|
||||
|
||||
defp node_fold(_node, _, acc, _fun, 0) do
|
||||
acc
|
||||
end
|
||||
|
||||
# Node resizing
|
||||
defp node_expand({ b1, b2, b3, b4, b5, b6, b7, b8 }, 0, n) do
|
||||
{ node_relocate(b1, n), node_relocate(b2, n), node_relocate(b3, n),
|
||||
node_relocate(b4, n), node_relocate(b5, n), node_relocate(b6, n),
|
||||
node_relocate(b7, n), node_relocate(b8, n) }
|
||||
end
|
||||
|
||||
defp node_expand({ b1, b2, b3, b4, b5, b6, b7, b8 }, depth, n) do
|
||||
depth = depth - 1
|
||||
{ node_expand(b1, depth, n), node_expand(b2, depth, n), node_expand(b3, depth, n),
|
||||
node_expand(b4, depth, n), node_expand(b5, depth, n), node_expand(b6, depth, n),
|
||||
node_expand(b7, depth, n), node_expand(b8, depth, n) }
|
||||
end
|
||||
|
||||
defp node_contract({ b1, b2, b3, b4, b5, b6, b7, b8 }, depth) when depth > 0 do
|
||||
depth = depth - 1
|
||||
{ node_contract(b1, depth), node_contract(b2, depth), node_contract(b3, depth),
|
||||
node_contract(b4, depth), node_contract(b5, depth), node_contract(b6, depth),
|
||||
node_contract(b7, depth), node_contract(b8, depth) }
|
||||
end
|
||||
|
||||
defp node_contract({ b1, b2, b3, b4, b5, b6, b7, b8 }, 0) do
|
||||
b1 |> each_contract(b2) |> each_contract(b3) |> each_contract(b4)
|
||||
|> each_contract(b5) |> each_contract(b6) |> each_contract(b7)
|
||||
|> each_contract(b8)
|
||||
end
|
||||
|
||||
defp each_contract([[k|_v]=e|acc], [[key|_value]|_]=bucket) when k < key, do: [e|each_contract(acc, bucket)]
|
||||
defp each_contract(acc, [e|bucket]), do: [e|each_contract(acc, bucket)]
|
||||
defp each_contract([], bucket), do: bucket
|
||||
defp each_contract(acc, []), do: acc
|
||||
|
||||
defp node_relocate(node // @node_template, bucket, n) do
|
||||
:lists.foldl fn [key|value], acc ->
|
||||
pos = key |> bucket_hash() |> bucket_nth_index(n)
|
||||
set_elem(acc, pos, bucket_put!(elem(acc, pos), key, value))
|
||||
end, node, bucket
|
||||
end
|
||||
end
|
||||
|
||||
defimpl Enumerable, for: HashDict do
|
||||
|
||||
+170
-397
@@ -2,49 +2,33 @@ defmodule HashSet do
|
||||
@moduledoc """
|
||||
A set store.
|
||||
|
||||
The `HashSet` is meant to work well with both small and
|
||||
large sets. It is an implementation of the `Set` behaviour.
|
||||
For more information about the functions and their APIs,
|
||||
please consult 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
|
||||
|
||||
# The ordered record contains a single bucket.
|
||||
@ordered_threshold 8
|
||||
|
||||
defrecordp :ordered, HashSet,
|
||||
size: 0,
|
||||
bucket: []
|
||||
|
||||
# The bucketed record contains a series of buckets.
|
||||
@expand_load 5
|
||||
@contract_load 2
|
||||
@node_bitmap 0b1111
|
||||
@node_shift 4
|
||||
@node_size 16
|
||||
@node_bitmap 0b111
|
||||
@node_shift 3
|
||||
@node_size 8
|
||||
@node_template :erlang.make_tuple(@node_size, [])
|
||||
|
||||
@expand_default (@node_size * @expand_load)
|
||||
@contract_default @contract_load
|
||||
|
||||
defrecordp :trie, HashSet,
|
||||
size: 0,
|
||||
depth: 0,
|
||||
expand_on: @expand_default,
|
||||
contract_on: @contract_default,
|
||||
root: @node_template
|
||||
|
||||
import Bitwise
|
||||
|
||||
# Inline common instructions
|
||||
@compile :inline_list_funcs
|
||||
@compile { :inline, bucket_hash: 1, bucket_index: 1, bucket_nth_index: 2, bucket_next: 1 }
|
||||
@compile { :inline, key_hash: 1, key_mask: 1, key_shift: 1 }
|
||||
|
||||
@doc """
|
||||
Creates a new empty set.
|
||||
"""
|
||||
def new() do
|
||||
ordered()
|
||||
@spec new :: Set.t
|
||||
def new do
|
||||
trie()
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -52,180 +36,64 @@ defmodule HashSet do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> HashSet.new [1, 1, 2, 3, 3] |> HashSet.to_list
|
||||
[1,2,3]
|
||||
HashSet.new [1, 2]
|
||||
#=> #HashSet<[1, 2]>
|
||||
|
||||
"""
|
||||
def new(members) do
|
||||
Enum.reduce members, ordered(), fn member, set ->
|
||||
put(set, member)
|
||||
@spec new(Enum.t) :: Set.t
|
||||
def new(enum) do
|
||||
Enum.reduce enum, trie(), fn i, set ->
|
||||
put(set, i)
|
||||
end
|
||||
end
|
||||
|
||||
def union(set1, set2) when is_record(set1, HashSet) and is_record(set2, HashSet) and elem(set1, 1) <= elem(set2, 1) do
|
||||
set_fold set1, set2, fn v1, acc ->
|
||||
put(acc, v1)
|
||||
@doc """
|
||||
Creates a new set from the enumerable with the
|
||||
help of the transformation function.
|
||||
|
||||
## Examples
|
||||
|
||||
HashSet.new [1, 2], &integer_to_binary/1
|
||||
#=> #HashSet<[1, 2]>
|
||||
|
||||
"""
|
||||
@spec new(Enum.t, (term -> term)) :: Set.t
|
||||
def new(enum, transform) when is_function(transform) do
|
||||
Enum.reduce enum, trie(), fn i, set ->
|
||||
put(set, transform.(i))
|
||||
end
|
||||
end
|
||||
|
||||
def union(set1, set2) when is_record(set1, HashSet) and is_record(set2, HashSet) do
|
||||
set_fold set2, set1, fn v, acc ->
|
||||
put(acc, v)
|
||||
def union(trie(size: size1) = set1, trie(size: size2) = set2) when size1 <= size2 do
|
||||
set_fold set1, set2, fn v, acc -> put(acc, v) end
|
||||
end
|
||||
|
||||
def union(trie() = set1, trie() = set2) do
|
||||
set_fold set2, set1, fn v, acc -> put(acc, v) end
|
||||
end
|
||||
|
||||
def intersection(trie() = set1, trie() = set2) do
|
||||
set_fold set1, trie(), fn v, acc ->
|
||||
if member?(set2, v), do: put(acc, v), else: acc
|
||||
end
|
||||
end
|
||||
|
||||
def intersection(set1, set2) when is_record(set1, HashSet) and is_record(set2, HashSet) and elem(set1, 1) <= elem(set2, 1) do
|
||||
set_filter set1, fn e -> set_member?(set2, e) end
|
||||
end
|
||||
|
||||
def intersection(set1, set2) when is_record(set1, HashSet) and is_record(set2, HashSet) do
|
||||
set_filter set2, fn e -> set_member?(set1, e) end
|
||||
end
|
||||
|
||||
def difference(set1, set2) when is_record(set1, HashSet) and is_record(set2, HashSet) do
|
||||
set_filter set1, fn m -> not set_member?(set2, m) end
|
||||
end
|
||||
|
||||
def member?(set, member) when is_record(set, HashSet) do
|
||||
set_member?(set, member)
|
||||
end
|
||||
|
||||
def empty(_) do
|
||||
ordered()
|
||||
end
|
||||
|
||||
def size(set) do
|
||||
elem(set, 1)
|
||||
end
|
||||
|
||||
def to_list(ordered(bucket: bucket)) do
|
||||
bucket
|
||||
def difference(trie() = set1, trie() = set2) do
|
||||
set_fold set2, set1, fn v, acc -> delete(acc, v) end
|
||||
end
|
||||
|
||||
def to_list(set) do
|
||||
set_fold(set, [], &[&1|&2]) |> :lists.reverse
|
||||
end
|
||||
|
||||
def put(set, member) do
|
||||
{ set, _ } = set_put(set, member)
|
||||
set
|
||||
end
|
||||
|
||||
def delete(set, member) do
|
||||
{ set, _ } = set_delete(set, member)
|
||||
set
|
||||
end
|
||||
|
||||
def equal?(set1, set2) do
|
||||
size = elem(set1, 1)
|
||||
case elem(set2, 1) do
|
||||
^size ->
|
||||
set_equal?(set1, set2)
|
||||
_ ->
|
||||
false
|
||||
def equal?(trie(size: size1) = set1, trie(size: size2) = set2) do
|
||||
case size1 do
|
||||
^size2 -> subset?(set1, set2)
|
||||
_ -> false
|
||||
end
|
||||
end
|
||||
|
||||
def subset?(set1, set2) do
|
||||
set_equal?(set1, set2)
|
||||
end
|
||||
|
||||
def disjoint?(set1, set2) do
|
||||
set_disjoint?(set1, set2)
|
||||
end
|
||||
|
||||
@doc false
|
||||
def reduce(ordered(bucket: bucket), acc, fun) do
|
||||
bucket_reduce(bucket, acc, fun)
|
||||
end
|
||||
|
||||
def reduce(trie(root: root, depth: depth), acc, fun) do
|
||||
node_reduce(root, depth, acc, fun, @node_size,
|
||||
fn { :cont, acc }, _f -> { :done, acc } end)
|
||||
end
|
||||
|
||||
## HashSet-wide functions
|
||||
|
||||
defp set_filter(ordered(bucket: bucket, size: size), fun) do
|
||||
{ new, removed_count } = bucket_filter(bucket, fun, [], 0)
|
||||
ordered(bucket: new, size: size - removed_count)
|
||||
end
|
||||
|
||||
defp set_filter(trie(root: root, depth: depth, size: size) = set, fun) do
|
||||
{ new, removed_count } = node_filter(root, depth, fun, @node_size)
|
||||
|
||||
if depth > 0 and trie(set, :contract_on) >= (size - removed_count) do
|
||||
contract_trie(trie(root: new,
|
||||
size: size - removed_count,
|
||||
depth: depth,
|
||||
contract_on: trie(set, :contract_on),
|
||||
expand_on: trie(set, :expand_on)))
|
||||
else
|
||||
trie(size: size - removed_count, root: new, depth: depth)
|
||||
end
|
||||
end
|
||||
|
||||
defp set_put(ordered(size: @ordered_threshold, bucket: bucket), member) do
|
||||
root = node_relocate(bucket, 0)
|
||||
set_put(trie(size: @ordered_threshold, root: root), member)
|
||||
end
|
||||
|
||||
defp set_put(ordered(size: size, bucket: bucket) = set, member) do
|
||||
{ new, count } = bucket_put(bucket, member)
|
||||
{ ordered(set, size: size + count, bucket: new), count }
|
||||
end
|
||||
|
||||
defp set_put(trie(root: root, depth: depth, size: size, expand_on: size, contract_on: contract_on) = set, member) do
|
||||
root = node_expand(root, depth, depth + 1)
|
||||
set = trie(set, root: root, depth: depth + 1,
|
||||
expand_on: size * @node_size, contract_on: contract_on * @node_size)
|
||||
set_put(set, member)
|
||||
end
|
||||
|
||||
defp set_put(trie(root: root, size: size, depth: depth) = set, member) do
|
||||
pos = bucket_hash(member)
|
||||
{ root, count } = node_put(root, depth, pos, member)
|
||||
{ trie(set, size: size + count, root: root), count }
|
||||
end
|
||||
|
||||
defp set_member?(ordered(bucket: bucket), member) do
|
||||
:lists.member(member, bucket)
|
||||
end
|
||||
|
||||
defp set_member?(trie(root: root, depth: depth), member) do
|
||||
:lists.member(member, node_bucket(root, depth, bucket_hash(member)))
|
||||
end
|
||||
|
||||
defp set_delete(ordered(bucket: bucket, size: size) = set, member) do
|
||||
case bucket_delete(bucket, member) do
|
||||
{ _, 0 } ->
|
||||
{ set, 0 }
|
||||
{ new_bucket, -1 } ->
|
||||
{ ordered(set, size: size - 1, bucket: new_bucket), -1 }
|
||||
end
|
||||
end
|
||||
|
||||
defp set_delete(trie(root: root, size: size, depth: depth) = set, member) do
|
||||
pos = bucket_hash(member)
|
||||
case node_delete(root, depth, pos, member) do
|
||||
{ _, 0 } ->
|
||||
{ set, 0 }
|
||||
{ root, -1 } ->
|
||||
{ if depth > 0 and trie(set, :contract_on) == size do
|
||||
root = node_contract(root, depth)
|
||||
trie(set,
|
||||
root: root,
|
||||
size: size - 1,
|
||||
depth: depth - 1,
|
||||
contract_on: div(size, @node_size),
|
||||
expand_on: div(trie(set, :expand_on), @node_size))
|
||||
else
|
||||
trie(set, size: size - 1, root: root)
|
||||
end, -1 }
|
||||
end
|
||||
end
|
||||
|
||||
defp set_equal?(set1, set2) do
|
||||
def subset?(trie() = set1, trie() = set2) do
|
||||
reduce(set1, { :cont, true }, fn member, acc ->
|
||||
case member?(set2, member) do
|
||||
true -> { :cont, acc }
|
||||
@@ -234,273 +102,178 @@ defmodule HashSet do
|
||||
end) |> elem(1)
|
||||
end
|
||||
|
||||
defp set_disjoint?(set1, set2) do
|
||||
reduce(set1, { :cont, true }, fn member, acc ->
|
||||
case member?(set2, member) do
|
||||
def disjoint?(trie() = set1, trie() = set2) do
|
||||
reduce(set2, { :cont, true }, fn member, acc ->
|
||||
case member?(set1, member) do
|
||||
false -> { :cont, acc }
|
||||
_ -> { :halt, false }
|
||||
end
|
||||
end) |> elem(1)
|
||||
end
|
||||
|
||||
defp set_fold(ordered(bucket: bucket), acc, fun) do
|
||||
bucket_fold(bucket, acc, fun)
|
||||
def empty(trie()) do
|
||||
trie()
|
||||
end
|
||||
|
||||
defp set_fold(trie(root: root, depth: depth), acc, fun) do
|
||||
node_fold(root, depth, acc, fun, @node_size)
|
||||
def member?(trie(root: root), term) do
|
||||
do_member?(root, term, key_hash(term))
|
||||
end
|
||||
|
||||
## Bucket helpers
|
||||
def put(trie(root: root, size: size), term) do
|
||||
{root, counter} = do_put(root, term, key_hash(term))
|
||||
trie(root: root, size: size + counter)
|
||||
end
|
||||
|
||||
defp bucket_filter([e|bucket], fun, acc, count) do
|
||||
case fun.(e) do
|
||||
true -> bucket_filter(bucket, fun, [e|acc], count)
|
||||
false -> bucket_filter(bucket, fun, acc, count + 1)
|
||||
def delete(trie(root: root, size: size) = set, term) do
|
||||
case do_delete(root, term, key_hash(term)) do
|
||||
{:ok, root} -> trie(root: root, size: size - 1)
|
||||
:error -> set
|
||||
end
|
||||
end
|
||||
|
||||
defp bucket_filter([], _fun, acc, count) do
|
||||
{ :lists.reverse(acc), count }
|
||||
def reduce(trie(root: root), acc, fun) do
|
||||
do_reduce(root, acc, fun, @node_size, fn
|
||||
{:suspend, acc} -> {:suspended, acc, &{ :done, elem(&1, 1) }}
|
||||
{:halt, acc} -> {:halted, acc}
|
||||
{:cont, acc} -> {:done, acc}
|
||||
end)
|
||||
end
|
||||
|
||||
defp bucket_put([m|_]=bucket, member) when m > member do
|
||||
{ [member|bucket], 1 }
|
||||
def size(trie(size: size)) do
|
||||
size
|
||||
end
|
||||
|
||||
defp bucket_put([member|bucket], member) do
|
||||
{ [member|bucket], 0 }
|
||||
## Set helpers
|
||||
|
||||
defp set_fold(trie(root: root), acc, fun) do
|
||||
do_fold(root, acc, fun, @node_size)
|
||||
end
|
||||
|
||||
defp bucket_put([e|bucket], member) do
|
||||
{ rest, count } = bucket_put(bucket, member)
|
||||
{ [e|rest], count }
|
||||
end
|
||||
## Set manipulation
|
||||
|
||||
defp bucket_put([], member) do
|
||||
{ [member], 1 }
|
||||
end
|
||||
|
||||
defp bucket_put!([m|_]=bucket, member) when m > member, do: [member|bucket]
|
||||
defp bucket_put!([member|bucket], member), do: [member|bucket]
|
||||
defp bucket_put!([e|bucket], member), do: [e|bucket_put!(bucket, member)]
|
||||
defp bucket_put!([], member), do: [member]
|
||||
|
||||
# Deletes a key from the bucket
|
||||
defp bucket_delete([m,_|_]=bucket, member) when m > member do
|
||||
{ bucket, 0 }
|
||||
end
|
||||
|
||||
defp bucket_delete([member|bucket], member) do
|
||||
{ bucket, -1 }
|
||||
end
|
||||
|
||||
defp bucket_delete([e|bucket], member) do
|
||||
{ rest, count } = bucket_delete(bucket, member)
|
||||
{ [e|rest], count }
|
||||
end
|
||||
|
||||
defp bucket_delete([], _member) do
|
||||
{ [], 0 }
|
||||
end
|
||||
|
||||
defp bucket_reduce(_, { :halt, acc }, _fun), do: { :halted, acc }
|
||||
defp bucket_reduce(list, { :suspend, acc }, fun), do: { :suspended, acc, &bucket_reduce(list, &1, fun) }
|
||||
defp bucket_reduce([h|t], { :cont, acc }, fun), do: bucket_reduce(t, fun.(h, acc), fun)
|
||||
defp bucket_reduce([], { :cont, acc }, _fun), do: { :done, acc }
|
||||
|
||||
defp bucket_fold(bucket, acc, fun) do
|
||||
:lists.foldl(fun, acc, bucket)
|
||||
end
|
||||
|
||||
defp bucket_hash(key) do
|
||||
:erlang.phash2(key)
|
||||
end
|
||||
|
||||
defp bucket_nth_index(hash, n) do
|
||||
(hash >>> (@node_shift * n)) &&& @node_bitmap
|
||||
end
|
||||
|
||||
defp bucket_index(hash) do
|
||||
hash &&& @node_bitmap
|
||||
end
|
||||
|
||||
defp bucket_next(hash) do
|
||||
hash >>> @node_shift
|
||||
end
|
||||
|
||||
# Trie resizing
|
||||
|
||||
defp contract_trie(trie(depth: 0) = set) do
|
||||
set
|
||||
end
|
||||
|
||||
defp contract_trie(trie(root: root, depth: depth, size: size, contract_on: contract_on, expand_on: expand_on) = set) when size <= contract_on do
|
||||
new_contract_on = div(contract_on, @node_size)
|
||||
new_expand_on = div(expand_on, @node_size)
|
||||
|
||||
if new_contract_on == 0, do: new_contract_on = @contract_default
|
||||
if new_expand_on == 0, do: new_expand_on = @expand_default
|
||||
|
||||
contract_trie(trie(set, root: node_contract(root, depth),
|
||||
size: size,
|
||||
depth: depth - 1,
|
||||
contract_on: new_contract_on,
|
||||
expand_on: new_expand_on))
|
||||
end
|
||||
|
||||
defp contract_trie(set) do
|
||||
set
|
||||
end
|
||||
|
||||
# Node helpers
|
||||
|
||||
# Gets a bucket from the node
|
||||
defp node_bucket(node, 0, hash) do
|
||||
elem(node, bucket_index(hash))
|
||||
end
|
||||
|
||||
defp node_bucket(node, depth, hash) do
|
||||
child = elem(node, bucket_index(hash))
|
||||
node_bucket(child, depth - 1, bucket_next(hash))
|
||||
end
|
||||
|
||||
defp node_put(node, 0, hash, member) do
|
||||
pos = bucket_index(hash)
|
||||
{ new, count } = bucket_put(elem(node, pos), member)
|
||||
{ set_elem(node, pos, new), count }
|
||||
end
|
||||
|
||||
defp node_put(node, depth, hash, member) do
|
||||
pos = bucket_index(hash)
|
||||
{ new, count } = node_put(elem(node, pos), depth - 1, bucket_next(hash), member)
|
||||
{ set_elem(node, pos, new), count }
|
||||
end
|
||||
|
||||
# Deletes a key from the bucket
|
||||
defp node_delete(node, 0, hash, member) do
|
||||
pos = bucket_index(hash)
|
||||
case bucket_delete(elem(node, pos), member) do
|
||||
{ _, 0 } -> { node, 0 }
|
||||
{ new, -1 } -> { set_elem(node, pos, new), -1 }
|
||||
defp do_member?(node, term, hash) do
|
||||
index = key_mask(hash)
|
||||
case elem(node, index) do
|
||||
[] -> false
|
||||
[^term|_] -> true
|
||||
[_] -> false
|
||||
[_|n] -> do_member?(n, term, key_shift(hash))
|
||||
end
|
||||
end
|
||||
|
||||
defp node_delete(node, depth, hash, member) do
|
||||
pos = bucket_index(hash)
|
||||
case node_delete(elem(node, pos), depth - 1, bucket_next(hash), member) do
|
||||
{ _, 0 } -> { node, 0 }
|
||||
{ new, -1 } -> { set_elem(node, pos, new), -1 }
|
||||
defp do_put(node, term, hash) do
|
||||
index = key_mask(hash)
|
||||
case elem(node, index) do
|
||||
[] ->
|
||||
{set_elem(node, index, [term]), 1}
|
||||
[^term|_] ->
|
||||
{node, 0}
|
||||
[t] ->
|
||||
n = set_elem(@node_template, key_mask(key_shift(hash)), [term])
|
||||
{set_elem(node, index, [t|n]), 1}
|
||||
[t|n] ->
|
||||
{n, counter} = do_put(n, term, key_shift(hash))
|
||||
{set_elem(node, index, [t|n]), counter}
|
||||
end
|
||||
end
|
||||
|
||||
# Reduces a node recursively
|
||||
defp node_reduce(_, -1, { :halt, acc }, _fun, _count, _next) do
|
||||
{ :halted, acc }
|
||||
defp do_delete(node, term, hash) do
|
||||
index = key_mask(hash)
|
||||
case elem(node, index) do
|
||||
[] ->
|
||||
:error
|
||||
[^term] ->
|
||||
{:ok, set_elem(node, index, [])}
|
||||
[_] ->
|
||||
:error
|
||||
[^term|n] ->
|
||||
{:ok, set_elem(node, index, do_compact_node(n))}
|
||||
[t|n] ->
|
||||
case do_delete(n, term, key_shift(hash)) do
|
||||
{:ok, @node_template} ->
|
||||
{:ok, set_elem(node, index, [t])}
|
||||
{:ok, n} ->
|
||||
{:ok, set_elem(node, index, [t|n])}
|
||||
:error ->
|
||||
:error
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defp node_reduce(list, -1, { :suspend, acc }, fun, count, next) do
|
||||
{ :suspended, acc, &node_reduce(list, -1, &1, fun, count, next) }
|
||||
Enum.each 0..(@node_size - 1), fn index ->
|
||||
defp do_compact_node(node) when elem(node, unquote(index)) != [] do
|
||||
case elem(node, unquote(index)) do
|
||||
[t] ->
|
||||
case set_elem(node, unquote(index), []) do
|
||||
@node_template -> [t]
|
||||
n -> [t|n]
|
||||
end
|
||||
[t|n] ->
|
||||
[t|set_elem(node, unquote(index), do_compact_node(n))]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defp node_reduce([h|t], -1, { :cont, acc }, fun, _count, next) do
|
||||
node_reduce(t, -1, fun.(h, acc), fun, _count, next)
|
||||
## Set fold
|
||||
|
||||
defp do_fold_each([], acc, _fun), do: acc
|
||||
defp do_fold_each([t], acc, fun), do: fun.(t, acc)
|
||||
defp do_fold_each([t|n], acc, fun), do: do_fold(n, fun.(t, acc), fun, @node_size)
|
||||
|
||||
defp do_fold(node, acc, fun, count) when count > 0 do
|
||||
acc = do_fold_each(:erlang.element(count, node), acc, fun)
|
||||
do_fold(node, acc, fun, count - 1)
|
||||
end
|
||||
|
||||
defp node_reduce([], -1, { :cont, _ } = acc, fun, _count, next) do
|
||||
next.(acc, fun)
|
||||
end
|
||||
|
||||
defp node_reduce(node, depth, acc, fun, count, next) when count >= 1 do
|
||||
node_reduce(:erlang.element(count, node), depth - 1, acc, fun, @node_size,
|
||||
&node_reduce(node, depth, &1, &2, count - 1, next))
|
||||
end
|
||||
|
||||
defp node_reduce(_node, _, acc, fun, 0, next) do
|
||||
next.(acc, fun)
|
||||
end
|
||||
|
||||
# Folds a node recursively
|
||||
defp node_fold(bucket, -1, acc, fun, _) do
|
||||
bucket_fold(bucket, acc, fun)
|
||||
end
|
||||
|
||||
defp node_fold(node, depth, acc, fun, count) when count >= 1 do
|
||||
acc = node_fold(:erlang.element(count, node), depth - 1, acc, fun, @node_size)
|
||||
node_fold(node, depth, acc, fun, count - 1)
|
||||
end
|
||||
|
||||
defp node_fold(_node, _, acc, _fun, 0) do
|
||||
defp do_fold(_node, acc, _fun, 0) do
|
||||
acc
|
||||
end
|
||||
|
||||
defp node_filter(bucket, -1, fun, _) do
|
||||
bucket_filter(bucket, fun, [], 0)
|
||||
## Set reduce
|
||||
|
||||
defp do_reduce_each(_node, {:halt, acc}, _fun, _next) do
|
||||
{:halted, acc}
|
||||
end
|
||||
|
||||
defp node_filter(node, depth, fun, count) when count >= 1 do
|
||||
case node_filter(:erlang.element(count, node), depth - 1, fun, @node_size) do
|
||||
{ _, 0 } ->
|
||||
node_filter(node, depth, fun, count - 1)
|
||||
{ new_element, count1 } ->
|
||||
{ new_node, count2 } = node_filter(:erlang.setelement(count, node, new_element), depth, fun, count - 1)
|
||||
{ new_node, count1 + count2 }
|
||||
end
|
||||
defp do_reduce_each(node, {:suspend, acc}, fun, next) do
|
||||
{:suspended, acc, &do_reduce_each(node, &1, fun, next)}
|
||||
end
|
||||
|
||||
defp node_filter(node, _, _fun, 0) do
|
||||
{ node, 0 }
|
||||
defp do_reduce_each([], acc, _fun, next) do
|
||||
next.(acc)
|
||||
end
|
||||
|
||||
defp node_relocate(node // @node_template, bucket, n) do
|
||||
:lists.foldl fn member, acc ->
|
||||
pos = member |> bucket_hash() |> bucket_nth_index(n)
|
||||
set_elem(acc, pos, bucket_put!(elem(acc, pos), member))
|
||||
end, node, bucket
|
||||
defp do_reduce_each([t], {:cont, acc}, fun, next) do
|
||||
next.(fun.(t, acc))
|
||||
end
|
||||
|
||||
# Node resizing
|
||||
defp node_expand({ b1, b2, b3, b4, b5, b6, b7, b8, b9, b10, b11, b12, b13, b14, b15, b16 }, 0, n) do
|
||||
{ node_relocate(b1, n), node_relocate(b2, n), node_relocate(b3, n),
|
||||
node_relocate(b4, n), node_relocate(b5, n), node_relocate(b6, n),
|
||||
node_relocate(b7, n), node_relocate(b8, n), node_relocate(b9, n),
|
||||
node_relocate(b10, n), node_relocate(b11, n), node_relocate(b12, n),
|
||||
node_relocate(b13, n), node_relocate(b14, n), node_relocate(b15, n),
|
||||
node_relocate(b16, n) }
|
||||
defp do_reduce_each([t|n], {:cont, acc}, fun, next) do
|
||||
do_reduce(n, fun.(t, acc), fun, @node_size, next)
|
||||
end
|
||||
|
||||
defp node_expand({ b1, b2, b3, b4, b5, b6, b7, b8, b9, b10, b11, b12, b13, b14, b15, b16 }, depth, n) do
|
||||
depth = depth - 1
|
||||
{ node_expand(b1, depth, n), node_expand(b2, depth, n), node_expand(b3, depth, n),
|
||||
node_expand(b4, depth, n), node_expand(b5, depth, n), node_expand(b6, depth, n),
|
||||
node_expand(b7, depth, n), node_expand(b8, depth, n), node_expand(b9, depth, n),
|
||||
node_expand(b10, depth, n), node_expand(b11, depth, n), node_expand(b12, depth, n),
|
||||
node_expand(b13, depth, n), node_expand(b14, depth, n), node_expand(b15, depth, n),
|
||||
node_expand(b16, depth, n) }
|
||||
defp do_reduce(node, acc, fun, count, next) when count > 0 do
|
||||
do_reduce_each(:erlang.element(count, node), acc, fun, &do_reduce(node, &1, fun, count - 1, next))
|
||||
end
|
||||
|
||||
defp node_contract({ b1, b2, b3, b4, b5, b6, b7, b8, b9, b10, b11, b12, b13, b14, b15, b16 }, depth) when depth > 0 do
|
||||
depth = depth - 1
|
||||
{ node_contract(b1, depth), node_contract(b2, depth), node_contract(b3, depth),
|
||||
node_contract(b4, depth), node_contract(b5, depth), node_contract(b6, depth),
|
||||
node_contract(b7, depth), node_contract(b8, depth), node_contract(b9, depth),
|
||||
node_contract(b10, depth), node_contract(b11, depth), node_contract(b12, depth),
|
||||
node_contract(b13, depth), node_contract(b14, depth), node_contract(b15, depth),
|
||||
node_contract(b16, depth) }
|
||||
defp do_reduce(_node, acc, _fun, 0, next) do
|
||||
next.(acc)
|
||||
end
|
||||
|
||||
defp node_contract({ b1, b2, b3, b4, b5, b6, b7, b8, b9, b10, b11, b12, b13, b14, b15, b16 }, 0) do
|
||||
b1 |> each_contract(b2) |> each_contract(b3) |> each_contract(b4)
|
||||
|> each_contract(b5) |> each_contract(b6) |> each_contract(b7)
|
||||
|> each_contract(b8) |> each_contract(b9) |> each_contract(b10)
|
||||
|> each_contract(b11) |> each_contract(b12) |> each_contract(b13)
|
||||
|> each_contract(b14) |> each_contract(b15) |> each_contract(b16)
|
||||
## Key operations
|
||||
|
||||
import Bitwise
|
||||
|
||||
defp key_hash(key) do
|
||||
:erlang.phash2(key)
|
||||
end
|
||||
|
||||
defp each_contract([m1|acc], [m2|_]=bucket) when m1 < m2, do: [m1|each_contract(acc, bucket)]
|
||||
defp each_contract(acc, [m|bucket]), do: [m|each_contract(acc, bucket)]
|
||||
defp each_contract([], bucket), do: bucket
|
||||
defp each_contract(acc, []), do: acc
|
||||
defp key_mask(hash) do
|
||||
hash &&& @node_bitmap
|
||||
end
|
||||
|
||||
defp key_shift(hash) do
|
||||
hash >>> @node_shift
|
||||
end
|
||||
end
|
||||
|
||||
defimpl Enumerable, for: HashSet do
|
||||
|
||||
+20
-15
@@ -100,7 +100,7 @@ defimpl Inspect, for: Atom do
|
||||
valid_ref_identifier?(binary) ->
|
||||
"Elixir." <> rest = binary
|
||||
rest
|
||||
atom in [:{}, :<<>>] ->
|
||||
atom in [:{}, :<<>>, :...] ->
|
||||
":" <> binary
|
||||
atom in Macro.binary_ops or atom in Macro.unary_ops ->
|
||||
":" <> binary
|
||||
@@ -127,16 +127,21 @@ 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
|
||||
case valid_identifier?(t) do
|
||||
<<>> -> true
|
||||
<<??>> -> true
|
||||
<<?!>> -> true
|
||||
_ -> false
|
||||
end
|
||||
valid_atom_piece?(t)
|
||||
end
|
||||
|
||||
defp valid_atom_identifier?(_), do: false
|
||||
|
||||
defp valid_atom_piece?(t) do
|
||||
case valid_identifier?(t) do
|
||||
<<>> -> true
|
||||
<<??>> -> true
|
||||
<<?!>> -> true
|
||||
<<?@, t::binary>> -> valid_atom_piece?(t)
|
||||
_ -> false
|
||||
end
|
||||
end
|
||||
|
||||
defp valid_identifier?(<<h, t :: binary>>)
|
||||
when h in ?a..?z
|
||||
when h in ?A..?Z
|
||||
@@ -149,7 +154,7 @@ defimpl Inspect, for: Atom do
|
||||
end
|
||||
|
||||
defimpl Inspect, for: BitString do
|
||||
@doc %S"""
|
||||
@doc ~S"""
|
||||
Represents a string as itself escaping all necessary
|
||||
characters. Binaries that contain non-printable characters
|
||||
are printed using the bitstring syntax.
|
||||
@@ -275,7 +280,7 @@ defimpl Inspect, for: BitString do
|
||||
end
|
||||
|
||||
defimpl Inspect, for: List do
|
||||
@doc %S"""
|
||||
@doc ~S"""
|
||||
Represents a list, checking if it can be printed or not.
|
||||
If so, a single-quoted representation is returned,
|
||||
otherwise the brackets syntax is used. Keywords are
|
||||
@@ -436,17 +441,17 @@ defimpl Inspect, for: Float do
|
||||
end
|
||||
|
||||
defimpl Inspect, for: Regex do
|
||||
@doc %S"""
|
||||
Represents the Regex using the `%r""` syntax.
|
||||
@doc ~S"""
|
||||
Represents the Regex using the `~r""` syntax.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> inspect(%r/foo/m)
|
||||
"%r\"foo\"m"
|
||||
iex> inspect(~r/foo/m)
|
||||
"~r\"foo\"m"
|
||||
|
||||
"""
|
||||
def inspect(regex, opts) when size(regex) == 5 do
|
||||
concat ["%r", to_doc(Regex.source(regex), opts), Regex.opts(regex)]
|
||||
concat ["~r", to_doc(Regex.source(regex), opts), Regex.opts(regex)]
|
||||
end
|
||||
|
||||
def inspect(other, Inspect.Opts[] = opts) do
|
||||
@@ -476,7 +481,7 @@ defimpl Inspect, for: Function do
|
||||
end
|
||||
|
||||
defp default_inspect(mod, fun_info) do
|
||||
"#Function<#{uniq(fun_info)} in #{Inspect.Atom.inspect(mod)}.#{extract_name(fun_info[:name])}>"
|
||||
"#Function<#{uniq(fun_info)}/#{fun_info[:arity]} in #{Inspect.Atom.inspect(mod)}.#{extract_name(fun_info[:name])}>"
|
||||
end
|
||||
|
||||
defp extract_name(name) do
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
defmodule Inspect.Algebra do
|
||||
@moduledoc %S"""
|
||||
@moduledoc ~S"""
|
||||
A set of functions for creating and manipulating algebra
|
||||
documents, as described in ["Strictly Pretty" (2000) by Christian Lindig][0].
|
||||
|
||||
@@ -73,7 +73,7 @@ defmodule Inspect.Algebra do
|
||||
|
||||
# Functional interface to `doc` records
|
||||
|
||||
@type t :: :doc_nil | doc_cons_t | doc_nest_t | doc_break_t | doc_group_t | binary
|
||||
@type t :: :doc_nil | :doc_line | doc_cons_t | doc_nest_t | doc_break_t | doc_group_t | binary
|
||||
defrecordp :doc_cons, left: :doc_nil :: t, right: :doc_nil :: t
|
||||
defrecordp :doc_nest, indent: 1 :: non_neg_integer, doc: :doc_nil :: t
|
||||
defrecordp :doc_break, str: " " :: binary
|
||||
@@ -94,7 +94,7 @@ defmodule Inspect.Algebra do
|
||||
defp do_is_doc(doc) do
|
||||
quote do
|
||||
is_binary(unquote(doc)) or
|
||||
unquote(doc) == :doc_nil or
|
||||
unquote(doc) in [:doc_nil, :doc_line] or
|
||||
(is_tuple(unquote(doc)) and
|
||||
elem(unquote(doc), 0) in [:doc_cons, :doc_nest, :doc_break, :doc_group])
|
||||
end
|
||||
@@ -110,8 +110,12 @@ defmodule Inspect.Algebra do
|
||||
if elem(opts, Inspect.Opts.__record__(:index, :records)) do
|
||||
try do
|
||||
Inspect.inspect(arg, opts)
|
||||
catch
|
||||
_, _ -> Inspect.Tuple.inspect(arg, opts)
|
||||
rescue
|
||||
e ->
|
||||
res = Inspect.Tuple.inspect(arg, opts)
|
||||
IO.puts :stderr, "** (Inspect.Error) Got #{inspect e.__record__(:name)} with message " <>
|
||||
"#{e.message} while inspecting #{pretty(res, opts.width)}"
|
||||
res
|
||||
end
|
||||
else
|
||||
Inspect.Tuple.inspect(arg, opts)
|
||||
@@ -178,7 +182,7 @@ defmodule Inspect.Algebra do
|
||||
doc_nest(indent: i, doc: x)
|
||||
end
|
||||
|
||||
@doc %S"""
|
||||
@doc ~S"""
|
||||
Document entity representing a break. This break can
|
||||
be rendered as a linebreak or as spaces, depending on the
|
||||
`mode` of the chosen layout or the provided separator.
|
||||
@@ -218,7 +222,7 @@ defmodule Inspect.Algebra do
|
||||
@spec glue(t, binary, t) :: doc_cons_t
|
||||
def glue(x, g, y) when is_binary(g), do: concat(x, concat(break(g), y))
|
||||
|
||||
@doc %S"""
|
||||
@doc ~S"""
|
||||
Returns a group containing the specified document.
|
||||
|
||||
## Examples
|
||||
@@ -263,7 +267,7 @@ defmodule Inspect.Algebra do
|
||||
@spec space(t, t) :: doc_cons_t
|
||||
def space(x, y), do: concat(x, concat(" ", y))
|
||||
|
||||
@doc %S"""
|
||||
@doc ~S"""
|
||||
Inserts a mandatory linebreak between two document entities.
|
||||
|
||||
## Examples
|
||||
@@ -274,7 +278,7 @@ defmodule Inspect.Algebra do
|
||||
|
||||
"""
|
||||
@spec line(t, t) :: doc_cons_t
|
||||
def line(x, y), do: concat(x, concat(@newline, y))
|
||||
def line(x, y), do: concat(x, concat(:doc_line, y))
|
||||
|
||||
@doc """
|
||||
Folds a list of document entities into a document entity
|
||||
@@ -297,7 +301,7 @@ defmodule Inspect.Algebra do
|
||||
|
||||
# Elixir conveniences
|
||||
|
||||
@doc %S"""
|
||||
@doc ~S"""
|
||||
Surrounds a document with characters.
|
||||
|
||||
Puts the document between left and right enclosing and nesting it.
|
||||
@@ -316,7 +320,7 @@ defmodule Inspect.Algebra do
|
||||
group concat left, concat(nest(doc, @nesting), right)
|
||||
end
|
||||
|
||||
@doc %S"""
|
||||
@doc ~S"""
|
||||
Maps and glues a collection of items together using the given separator
|
||||
and surrounds them. A limit can be passed which, once reached, stops
|
||||
gluing and outputs "..." instead.
|
||||
@@ -337,7 +341,7 @@ defmodule Inspect.Algebra do
|
||||
|
||||
"""
|
||||
@spec surround_many(binary, [any], binary, integer | :infinity, (term -> t), binary) :: t
|
||||
def surround_many(left, docs, right, limit, fun, separator // @surround_separator)
|
||||
def surround_many(left, docs, right, limit, fun, separator \\ @surround_separator)
|
||||
|
||||
def surround_many(left, [], right, _, _fun, _) do
|
||||
concat(left, right)
|
||||
@@ -397,6 +401,7 @@ defmodule Inspect.Algebra do
|
||||
@spec fits?(integer, [{ integer, mode, t }]) :: boolean
|
||||
def fits?(w, _) when w < 0, do: false
|
||||
def fits?(_, []), do: true
|
||||
def fits?(_, [{_, _, :doc_line} | _]), do: true
|
||||
def fits?(w, [{_, _, :doc_nil} | t]), do: fits?(w, t)
|
||||
def fits?(w, [{i, m, doc_cons(left: x, right: y)} | t]), do: fits?(w, [{i, m, x} | [{i, m, y} | t]])
|
||||
def fits?(w, [{i, m, doc_nest(indent: j, doc: x)} | t]), do: fits?(w, [{i + j, m, x} | t])
|
||||
@@ -408,6 +413,7 @@ defmodule Inspect.Algebra do
|
||||
@doc false
|
||||
@spec format(integer | :infinity, integer, [{ integer, mode, t }]) :: [binary]
|
||||
def format(_, _, []), do: []
|
||||
def format(w, _, [{i, _, :doc_line} | t]), do: [indent(i) | format(w, i, t)]
|
||||
def format(w, k, [{_, _, :doc_nil} | t]), do: format(w, k, t)
|
||||
def format(w, k, [{i, m, doc_cons(left: x, right: y)} | t]), do: format(w, k, [{i, m, x} | [{i, m, y} | t]])
|
||||
def format(w, k, [{i, m, doc_nest(indent: j, doc: x)} | t]), do: format(w, k, [{i + j, m, x} | t])
|
||||
@@ -420,11 +426,13 @@ defmodule Inspect.Algebra do
|
||||
if w == :infinity or fits?(w - k, t) do
|
||||
[s | format(w, k, t)]
|
||||
else
|
||||
prefix = :binary.copy(" ", i)
|
||||
[@newline, prefix | format(w, i, t)]
|
||||
[indent(i) | format(w, i, t)]
|
||||
end
|
||||
end
|
||||
|
||||
defp indent(0), do: @newline
|
||||
defp indent(i), do: @newline <> :binary.copy(" ", i)
|
||||
|
||||
@doc false
|
||||
@spec render([binary]) :: binary
|
||||
def render(sdoc) do
|
||||
|
||||
@@ -49,7 +49,7 @@ defmodule IO do
|
||||
NFS file system.
|
||||
"""
|
||||
@spec read(device, :line | non_neg_integer) :: chardata | nodata
|
||||
def read(device // group_leader, chars_or_line)
|
||||
def read(device \\ group_leader, chars_or_line)
|
||||
|
||||
def read(device, :line) do
|
||||
:io.get_line(map_dev(device), '')
|
||||
@@ -72,7 +72,7 @@ defmodule IO do
|
||||
NFS file system.
|
||||
"""
|
||||
@spec binread(device, :line | non_neg_integer) :: iodata | nodata
|
||||
def binread(device // group_leader, chars_or_line)
|
||||
def binread(device \\ group_leader, chars_or_line)
|
||||
|
||||
def binread(device, :line) do
|
||||
case :file.read_line(map_dev(device)) do
|
||||
@@ -106,7 +106,7 @@ defmodule IO do
|
||||
|
||||
"""
|
||||
@spec write(device, chardata) :: :ok
|
||||
def write(device // group_leader(), item) do
|
||||
def write(device \\ group_leader(), item) do
|
||||
:io.put_chars map_dev(device), to_chardata(item)
|
||||
end
|
||||
|
||||
@@ -117,7 +117,7 @@ defmodule IO do
|
||||
Check `write/2` for more information.
|
||||
"""
|
||||
@spec binwrite(device, iodata) :: :ok | { :error, term }
|
||||
def binwrite(device // group_leader(), item) when is_iolist(item) do
|
||||
def binwrite(device \\ group_leader(), item) when is_iolist(item) do
|
||||
:file.write map_dev(device), item
|
||||
end
|
||||
|
||||
@@ -127,7 +127,7 @@ defmodule IO do
|
||||
to be a chardata.
|
||||
"""
|
||||
@spec puts(device, chardata) :: :ok
|
||||
def puts(device // group_leader(), item) do
|
||||
def puts(device \\ group_leader(), item) do
|
||||
erl_dev = map_dev(device)
|
||||
:io.put_chars erl_dev, [to_chardata(item), ?\n]
|
||||
end
|
||||
@@ -146,7 +146,7 @@ defmodule IO do
|
||||
|
||||
"""
|
||||
@spec inspect(term, Keyword.t) :: term
|
||||
def inspect(item, opts // []) do
|
||||
def inspect(item, opts \\ []) do
|
||||
inspect group_leader(), item, opts
|
||||
end
|
||||
|
||||
@@ -185,7 +185,7 @@ defmodule IO do
|
||||
"""
|
||||
@spec getn(chardata, pos_integer) :: chardata | nodata
|
||||
@spec getn(device, chardata) :: chardata | nodata
|
||||
def getn(prompt, count // 1)
|
||||
def getn(prompt, count \\ 1)
|
||||
|
||||
def getn(prompt, count) when is_integer(count) do
|
||||
getn(group_leader, prompt, count)
|
||||
@@ -219,7 +219,7 @@ defmodule IO do
|
||||
NFS file system.
|
||||
"""
|
||||
@spec gets(device, chardata) :: chardata | nodata
|
||||
def gets(device // group_leader(), prompt) do
|
||||
def gets(device \\ group_leader(), prompt) do
|
||||
:io.get_line(map_dev(device), to_chardata(prompt))
|
||||
end
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
defmodule IO.ANSI.Sequence do
|
||||
@moduledoc false
|
||||
|
||||
defmacro defsequence(name, code // "", terminator // "m") do
|
||||
defmacro defsequence(name, code \\ "", terminator \\ "m") do
|
||||
quote bind_quoted: [name: name, code: code, terminator: terminator] do
|
||||
def unquote(name)() do
|
||||
"\e[#{unquote(code)}#{unquote(terminator)}"
|
||||
@@ -17,7 +17,7 @@ end
|
||||
defmodule IO.ANSI do
|
||||
@moduledoc """
|
||||
Functionality to render ANSI escape sequences
|
||||
(http://en.wikipedia.org/wiki/ANSI_escape_code) — characters embedded
|
||||
(http:\\en.wikipedia.org/wiki/ANSI_escape_code) — characters embedded
|
||||
in text used to control formatting, color, and other output options
|
||||
on video text terminals.
|
||||
"""
|
||||
@@ -32,7 +32,7 @@ defmodule IO.ANSI do
|
||||
"""
|
||||
@spec terminal? :: boolean
|
||||
@spec terminal?(:io.device) :: boolean
|
||||
def terminal?(device // :erlang.group_leader) do
|
||||
def terminal?(device \\ :erlang.group_leader) do
|
||||
match?({:ok, _}, :io.columns(device))
|
||||
end
|
||||
|
||||
@@ -134,11 +134,11 @@ defmodule IO.ANSI do
|
||||
end
|
||||
|
||||
defp escape_sequence(other) do
|
||||
[spec|_] = String.split(other, %r/(,|\})/)
|
||||
[spec|_] = String.split(other, ~r/(,|\})/)
|
||||
raise ArgumentError, message: "invalid ANSI sequence specification: #{spec}"
|
||||
end
|
||||
|
||||
@doc %S"""
|
||||
@doc ~S"""
|
||||
Escapes a string by converting named ANSI sequences into actual ANSI codes.
|
||||
|
||||
The format for referring to sequences is `%{red}` and `%{red,bright}` (for
|
||||
@@ -159,7 +159,7 @@ defmodule IO.ANSI do
|
||||
|
||||
"""
|
||||
@spec escape(String.t, emit :: boolean) :: String.t
|
||||
def escape(string, emit // terminal?) do
|
||||
def escape(string, emit \\ terminal?) do
|
||||
{rendered, emitted} = do_escape(string, false, emit, false, [])
|
||||
if emitted and emit do
|
||||
rendered <> reset
|
||||
@@ -168,7 +168,7 @@ defmodule IO.ANSI do
|
||||
end
|
||||
end
|
||||
|
||||
@doc %S"""
|
||||
@doc ~S"""
|
||||
Escapes a string by converting named ANSI sequences into actual ANSI codes.
|
||||
|
||||
The format for referring to sequences is `%{red}` and `%{red,bright}` (for
|
||||
@@ -188,7 +188,7 @@ defmodule IO.ANSI do
|
||||
|
||||
"""
|
||||
@spec escape_fragment(String.t, emit :: boolean) :: String.t
|
||||
def escape_fragment(string, emit // terminal?) do
|
||||
def escape_fragment(string, emit \\ terminal?) do
|
||||
{rendered, _emitted} = do_escape(string, false, emit, false, [])
|
||||
rendered
|
||||
end
|
||||
|
||||
@@ -16,7 +16,7 @@ defmodule IO.ANSI.Docs do
|
||||
|
||||
See `print/3` for docs on the supported options.
|
||||
"""
|
||||
def print_heading(heading, colors // @default_colors) do
|
||||
def print_heading(heading, colors \\ @default_colors) do
|
||||
IO.puts IO.ANSI.reset
|
||||
width = column_width()
|
||||
padding = div(width + String.length(heading), 2)
|
||||
@@ -46,7 +46,7 @@ defmodule IO.ANSI.Docs do
|
||||
* Intensity: `normal bright`
|
||||
* Decoration: `underline reverse`
|
||||
"""
|
||||
def print(doc, colors // @default_colors) do
|
||||
def print(doc, colors \\ @default_colors) do
|
||||
doc
|
||||
|> String.split(["\r\n","\n"], trim: false)
|
||||
|> Enum.map(&String.rstrip/1)
|
||||
@@ -185,7 +185,7 @@ defmodule IO.ANSI.Docs do
|
||||
|> Enum.join(" ")
|
||||
|> handle_links
|
||||
|> handle_inline(nil, [], [], colors)
|
||||
|> String.split(%r{\s})
|
||||
|> String.split(~r{\s})
|
||||
|> write_with_wrap(column_width() - size(indent), indent, from_list)
|
||||
|
||||
unless from_list, do: IO.puts(IO.ANSI.reset)
|
||||
@@ -276,16 +276,16 @@ defmodule IO.ANSI.Docs do
|
||||
end
|
||||
|
||||
defp escape_underlines_in_link(text) do
|
||||
case Regex.match?(%r{.*(https?\S*)}, text) do
|
||||
case Regex.match?(~r{.*(https?\S*)}, text) do
|
||||
true ->
|
||||
Regex.replace(%r{_}, text, "\\\\_")
|
||||
Regex.replace(~r{_}, text, "\\\\_")
|
||||
_ ->
|
||||
text
|
||||
end
|
||||
end
|
||||
|
||||
defp remove_square_brackets_in_link(text) do
|
||||
Regex.replace(%r{\[(.*?)\]\((.*?)\)}, text, "\\1 (\\2)")
|
||||
Regex.replace(~r{\[(.*?)\]\((.*?)\)}, text, "\\1 (\\2)")
|
||||
end
|
||||
|
||||
# Single inline quotes.
|
||||
|
||||
+569
-493
File diff suppressed because it is too large
Load Diff
@@ -34,7 +34,7 @@ defmodule Kernel.CLI do
|
||||
This function is used by Elixir's CLI and also
|
||||
by escripts generated by Elixir.
|
||||
"""
|
||||
def run(fun, halt // true) do
|
||||
def run(fun, halt \\ true) do
|
||||
try do
|
||||
fun.()
|
||||
if halt do
|
||||
|
||||
@@ -25,10 +25,10 @@ defmodule Kernel.ErrorHandler do
|
||||
{ ^ref, :ready } ->
|
||||
:ok
|
||||
{ ^ref, :release } ->
|
||||
# On release, get rid of the compiler pid,
|
||||
# no further allow elixir_ensure_compiled directives
|
||||
# and revert to the original error handler.
|
||||
:erlang.erase(:elixir_compiler_pid)
|
||||
# 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.
|
||||
:erlang.erase(:elixir_ensure_compiled)
|
||||
:erlang.process_flag(:error_handler, :error_handler)
|
||||
end
|
||||
|
||||
@@ -24,7 +24,7 @@ defmodule Kernel.ParallelCompiler do
|
||||
The compiler doesn't care about the return values of the callbacks.
|
||||
Returns the modules generated by each compiled file.
|
||||
"""
|
||||
def files(files, callbacks // [])
|
||||
def files(files, callbacks \\ [])
|
||||
|
||||
def files(files, callbacks) when is_list(callbacks) do
|
||||
spawn_compilers(files, nil, callbacks)
|
||||
@@ -34,7 +34,7 @@ defmodule Kernel.ParallelCompiler do
|
||||
Compiles the given files to the given path.
|
||||
Read `files/2` for more information.
|
||||
"""
|
||||
def files_to_path(files, path, callbacks // [])
|
||||
def files_to_path(files, path, callbacks \\ [])
|
||||
|
||||
def files_to_path(files, path, callbacks) when is_binary(path) and is_list(callbacks) do
|
||||
spawn_compilers(files, path, callbacks)
|
||||
@@ -151,9 +151,10 @@ defmodule Kernel.ParallelCompiler do
|
||||
|
||||
# Sometimes we may have spurious entries in the waiting
|
||||
# list because someone invoked try/rescue UndefinedFunctionError
|
||||
new_files = List.delete(files, down_pid)
|
||||
new_waiting = List.keydelete(waiting, down_pid, 0)
|
||||
new_queued = List.keydelete(queued, down_pid, 0)
|
||||
spawn_compilers(files, original, output, callbacks, new_waiting, new_queued, schedulers, result)
|
||||
spawn_compilers(new_files, original, output, callbacks, new_waiting, new_queued, schedulers, result)
|
||||
|
||||
{ :DOWN, down_ref, :process, _down_pid, { :failure, kind, reason, stacktrace } } ->
|
||||
handle_failure(down_ref, kind, reason, stacktrace, files, waiting, queued)
|
||||
|
||||
@@ -13,7 +13,7 @@ defmodule Kernel.ParallelRequire do
|
||||
|
||||
Returns the modules generated by each required file.
|
||||
"""
|
||||
def files(files, callback // default_callback) do
|
||||
def files(files, callback \\ default_callback) do
|
||||
schedulers = max(:erlang.system_info(:schedulers_online), 2)
|
||||
spawn_requires(files, [], callback, schedulers, [])
|
||||
end
|
||||
|
||||
@@ -72,7 +72,7 @@ defmodule Kernel.RecordRewriter do
|
||||
case record_fields(module, record) do
|
||||
{ fields, optimizable } ->
|
||||
opt_call =
|
||||
if :lists.member({ function, length(args) + 1 }, optimizable) do
|
||||
if { function, length(args) + 1 } in optimizable do
|
||||
case record_field_info(function) do
|
||||
{ kind, field } ->
|
||||
if index = Enum.find_index(fields, &(field == &1)) do
|
||||
|
||||
@@ -331,7 +331,7 @@ defmodule Kernel.SpecialForms do
|
||||
Elixir won't emit any warnings though, since the alias
|
||||
was not explicitly defined.
|
||||
|
||||
Both warning behaviors could be changed by explicitily
|
||||
Both warning behaviours could be changed by explicitily
|
||||
setting the `:warn` option to true or false.
|
||||
"""
|
||||
defmacro alias(module, opts)
|
||||
@@ -443,7 +443,7 @@ defmodule Kernel.SpecialForms do
|
||||
Elixir won't emit any warnings though, since the import
|
||||
was not explicitly defined.
|
||||
|
||||
Both warning behaviors could be changed by explicitily
|
||||
Both warning behaviours could be changed by explicitily
|
||||
setting the `:warn` option to true or false.
|
||||
|
||||
## Ambiguous function/macro names
|
||||
@@ -495,8 +495,8 @@ defmodule Kernel.SpecialForms do
|
||||
value, instead of rebinding. This can be done with the `^` special form:
|
||||
|
||||
iex> x = 1
|
||||
iex> ^x = 1
|
||||
iex> ^x = 2
|
||||
iex> ^x = List.first([1])
|
||||
iex> ^x = List.first([2])
|
||||
** (MatchError) no match of right hand side value: 2
|
||||
|
||||
Note that `^` always refers to the value of x prior to the match. The
|
||||
@@ -510,13 +510,13 @@ defmodule Kernel.SpecialForms do
|
||||
"""
|
||||
defmacro ^(var)
|
||||
|
||||
@doc %S"""
|
||||
@doc ~S"""
|
||||
Gets the representation of any expression.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> quote do: sum(1, 2, 3)
|
||||
{ :sum, [], [1, 2, 3] }
|
||||
quote do: sum(1, 2, 3)
|
||||
#=> { :sum, [], [1, 2, 3] }
|
||||
|
||||
## Explanation
|
||||
|
||||
|
||||
@@ -298,7 +298,7 @@ defmodule Kernel.Typespec do
|
||||
if export do
|
||||
Module.add_doc(module, caller.line, kind, { name, arity }, doc)
|
||||
else
|
||||
:elixir_errors.warn "#{caller.file}:#{caller.line}: type #{name}/#{arity} is private, " <>
|
||||
:elixir_errors.warn caller.line, caller.file, "type #{name}/#{arity} is private, " <>
|
||||
"@typedoc's are always discarded for private types\n"
|
||||
end
|
||||
end
|
||||
@@ -519,21 +519,7 @@ defmodule Kernel.Typespec do
|
||||
## Macro callbacks
|
||||
|
||||
@doc false
|
||||
def deftype(kind, { :::, _, [type, definition] }, caller) do
|
||||
do_deftype(kind, type, definition, caller)
|
||||
end
|
||||
|
||||
def deftype(kind, {name, _meta, args} = type, caller)
|
||||
when is_atom(name) and not is_list(args) do
|
||||
do_deftype(kind, type, { :term, [line: caller.line], nil }, caller)
|
||||
end
|
||||
|
||||
def deftype(_kind, other, caller) do
|
||||
type_spec = Macro.to_string(other)
|
||||
compile_error caller, "invalid type specification: #{type_spec}"
|
||||
end
|
||||
|
||||
defp do_deftype(kind, { name, _, args }, definition, caller) do
|
||||
def deftype(kind, { :::, _, [{ name, _, args }, definition] }, caller) when is_atom(name) and name != ::: do
|
||||
args =
|
||||
if is_atom(args) do
|
||||
[]
|
||||
@@ -550,8 +536,13 @@ defmodule Kernel.Typespec do
|
||||
define_type(caller, kind, type)
|
||||
end
|
||||
|
||||
def deftype(_kind, other, caller) do
|
||||
type_spec = Macro.to_string(other)
|
||||
compile_error caller, "invalid type specification: #{type_spec}"
|
||||
end
|
||||
|
||||
@doc false
|
||||
def defspec(type, { :::, meta, [{ name, _, args }, return_and_guard] }, caller) do
|
||||
def defspec(type, { :::, meta, [{ name, _, args }, return_and_guard] }, caller) when is_atom(name) and name != ::: do
|
||||
if is_atom(args), do: args = []
|
||||
{ return, guard } = split_return_and_guard(return_and_guard)
|
||||
|
||||
@@ -622,11 +613,11 @@ defmodule Kernel.Typespec do
|
||||
collect_vars(type)
|
||||
end
|
||||
|
||||
defp collect_vars({:paren_type, _line, [type]}) do
|
||||
defp collect_vars({ :paren_type, _line, [type] }) do
|
||||
collect_vars(type)
|
||||
end
|
||||
|
||||
defp collect_vars({:var, _line, var}) do
|
||||
defp collect_vars({ :var, _line, var }) do
|
||||
[erl_to_ex_var(var)]
|
||||
end
|
||||
|
||||
@@ -721,16 +712,16 @@ defmodule Kernel.Typespec do
|
||||
end
|
||||
|
||||
defp typespec_to_ast({ :typed_record_field,
|
||||
{ :record_field, line, { :atom, line1, name }},
|
||||
{ :record_field, line, { :atom, line1, name } },
|
||||
type }) do
|
||||
typespec_to_ast({ :ann_type, line, [{ :var, line1, name }, type] })
|
||||
end
|
||||
|
||||
defp typespec_to_ast({:type, _, :any}) do
|
||||
defp typespec_to_ast({ :type, _, :any }) do
|
||||
quote do: ...
|
||||
end
|
||||
|
||||
defp typespec_to_ast({:paren_type, _, [type]}) do
|
||||
defp typespec_to_ast({ :paren_type, _, [type] }) do
|
||||
typespec_to_ast(type)
|
||||
end
|
||||
|
||||
@@ -779,7 +770,7 @@ defmodule Kernel.Typespec do
|
||||
end
|
||||
|
||||
# Handle ranges
|
||||
defp typespec({:"..", meta, args}, vars, caller) do
|
||||
defp typespec({:.., meta, args}, vars, caller) do
|
||||
typespec({:range, meta, args}, vars, caller)
|
||||
end
|
||||
|
||||
@@ -800,7 +791,7 @@ defmodule Kernel.Typespec do
|
||||
end
|
||||
|
||||
# Handle type operator
|
||||
defp typespec({:"::", meta, [var, expr] }, vars, caller) do
|
||||
defp typespec({:::, meta, [var, expr] }, vars, caller) do
|
||||
left = typespec(var, [elem(var, 0)|vars], caller)
|
||||
right = typespec(expr, vars, caller)
|
||||
{ :ann_type, line(meta), [left, right] }
|
||||
@@ -857,7 +848,7 @@ defmodule Kernel.Typespec do
|
||||
|
||||
# Handle local calls
|
||||
defp typespec({:string, meta, arguments}, vars, caller) do
|
||||
:elixir_errors.warn "warning: string() type use is discouraged. For character lists, use " <>
|
||||
:elixir_errors.warn caller.line, caller.file, "string() type use is discouraged. For character lists, use " <>
|
||||
"char_list() type, for strings, String.t()\n#{Exception.format_stacktrace(caller.stacktrace)}"
|
||||
arguments = lc arg inlist arguments, do: typespec(arg, vars, caller)
|
||||
{ :type, line(meta), :string, arguments }
|
||||
|
||||
@@ -116,7 +116,7 @@ defmodule Keyword do
|
||||
"""
|
||||
@spec get(t, key) :: value
|
||||
@spec get(t, key, value) :: value
|
||||
def get(keywords, key, default // nil) when is_atom(key) do
|
||||
def get(keywords, key, default \\ nil) when is_atom(key) do
|
||||
case :lists.keyfind(key, 1, keywords) do
|
||||
{ ^key, value } -> value
|
||||
false -> default
|
||||
@@ -324,7 +324,7 @@ defmodule Keyword do
|
||||
|
||||
"""
|
||||
@spec equal?(t, t) :: boolean
|
||||
def equal?(left, right) do
|
||||
def equal?(left, right) when is_list(left) and is_list(right) do
|
||||
:lists.sort(left) == :lists.sort(right)
|
||||
end
|
||||
|
||||
@@ -339,7 +339,7 @@ defmodule Keyword do
|
||||
|
||||
"""
|
||||
@spec merge(t, t) :: t
|
||||
def merge(d1, d2) do
|
||||
def merge(d1, d2) when is_list(d1) and is_list(d2) do
|
||||
d2 ++ lc({ k, _ } = tuple inlist d1, not has_key?(d2, k), do: tuple)
|
||||
end
|
||||
|
||||
@@ -356,7 +356,7 @@ defmodule Keyword do
|
||||
|
||||
"""
|
||||
@spec merge(t, t, (key, value, value -> value)) :: t
|
||||
def merge(d1, d2, fun) do
|
||||
def merge(d1, d2, fun) when is_list(d1) and is_list(d2) do
|
||||
do_merge(d2, d1, fun)
|
||||
end
|
||||
|
||||
@@ -456,7 +456,7 @@ defmodule Keyword do
|
||||
acc = { [], [] }
|
||||
|
||||
{ take, drop } = Enum.reduce dict, acc, fn({ k, v }, { take, drop }) ->
|
||||
case :lists.member(k, keys) do
|
||||
case k in keys do
|
||||
true -> { [{k, v}|take], drop }
|
||||
false -> { take, [{k, v}|drop] }
|
||||
end
|
||||
@@ -481,7 +481,7 @@ defmodule Keyword do
|
||||
|
||||
"""
|
||||
def take(dict, keys) do
|
||||
lc { k, _ } = tuple inlist dict, :lists.member(k, keys), do: tuple
|
||||
lc { k, _ } = tuple inlist dict, k in keys, do: tuple
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -500,7 +500,7 @@ defmodule Keyword do
|
||||
|
||||
"""
|
||||
def drop(dict, keys) do
|
||||
lc { k, _ } = tuple inlist dict, not :lists.member(k, keys), do: tuple
|
||||
lc { k, _ } = tuple inlist dict, not k in keys, do: tuple
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -528,7 +528,7 @@ defmodule Keyword do
|
||||
{1,[]}
|
||||
|
||||
"""
|
||||
def pop(dict, key, default // nil) do
|
||||
def pop(dict, key, default \\ nil) do
|
||||
{ get(dict, key, default), delete(dict, key) }
|
||||
end
|
||||
|
||||
@@ -557,7 +557,7 @@ defmodule Keyword do
|
||||
{1,[a: 2]}
|
||||
|
||||
"""
|
||||
def pop_first(dict, key, default // nil) do
|
||||
def pop_first(dict, key, default \\ nil) do
|
||||
{ get(dict, key, default), delete_first(dict, key) }
|
||||
end
|
||||
end
|
||||
|
||||
@@ -169,7 +169,7 @@ defmodule List do
|
||||
|
||||
"""
|
||||
@spec keyfind([tuple], any, non_neg_integer, any) :: any
|
||||
def keyfind(list, key, position, default // nil) do
|
||||
def keyfind(list, key, position, default \\ nil) do
|
||||
:lists.keyfind(key, position + 1, list) || default
|
||||
end
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
defprotocol List.Chars do
|
||||
@moduledoc %S"""
|
||||
@moduledoc ~S"""
|
||||
The List.Chars protocol is responsible for
|
||||
converting a structure to a list (only if applicable).
|
||||
The only function required to be implemented is
|
||||
|
||||
@@ -47,7 +47,7 @@ defmodule ListDict do
|
||||
def has_key?([{ _, _ }|t], key), do: has_key?(t, key)
|
||||
def has_key?([], _key), do: false
|
||||
|
||||
def get(dict, key, default // nil)
|
||||
def get(dict, key, default \\ nil)
|
||||
def get([{ key, value }|_], key, _default), do: value
|
||||
def get([{ _, _ }|t], key, default), do: get(t, key, default)
|
||||
def get([], _key, default), do: default
|
||||
@@ -64,7 +64,7 @@ defmodule ListDict do
|
||||
end
|
||||
end
|
||||
|
||||
def pop(dict, key, default // nil) do
|
||||
def pop(dict, key, default \\ nil) do
|
||||
{ get(dict, key, default), delete(dict, key) }
|
||||
end
|
||||
|
||||
@@ -84,7 +84,7 @@ defmodule ListDict do
|
||||
def delete([{ _, _ } = h|t], key), do: [h|delete(t, key)]
|
||||
def delete([], _key), do: []
|
||||
|
||||
def merge(dict, enum, callback // fn(_k, _v1, v2) -> v2 end) do
|
||||
def merge(dict, enum, callback \\ fn(_k, _v1, v2) -> v2 end) do
|
||||
Enum.reduce enum, dict, fn { k, v2 }, acc ->
|
||||
update(acc, k, v2, fn(v1) -> callback.(k, v1, v2) end)
|
||||
end
|
||||
@@ -94,7 +94,7 @@ defmodule ListDict do
|
||||
acc = { [], [] }
|
||||
|
||||
{take, drop} = Enum.reduce dict, acc, fn({ k, v }, { take, drop }) ->
|
||||
if :lists.member(k, keys) do
|
||||
if k in keys do
|
||||
{ [{k, v}|take], drop }
|
||||
else
|
||||
{ take, [{k, v}|drop] }
|
||||
@@ -105,11 +105,11 @@ defmodule ListDict do
|
||||
end
|
||||
|
||||
def take(dict, keys) do
|
||||
lc { k, _ } = tuple inlist dict, :lists.member(k, keys), do: tuple
|
||||
lc { k, _ } = tuple inlist dict, k in keys, do: tuple
|
||||
end
|
||||
|
||||
def drop(dict, keys) do
|
||||
lc { k, _ } = tuple inlist dict, not :lists.member(k, keys), do: tuple
|
||||
lc { k, _ } = tuple inlist dict, not k in keys, do: tuple
|
||||
end
|
||||
|
||||
def update!([{key, value}|dict], key, fun) do
|
||||
@@ -142,5 +142,10 @@ defmodule ListDict do
|
||||
:lists.keysort(1, dict) === :lists.keysort(1, other)
|
||||
end
|
||||
|
||||
def reduce(_, { :halt, acc }, _fun), do: { :halted, acc }
|
||||
def reduce(list, { :suspend, acc }, fun), do: { :suspended, acc, &reduce(list, &1, fun) }
|
||||
def reduce([], { :cont, acc }, _fun), do: { :done, acc }
|
||||
def reduce([{_,_}=h|t], { :cont, acc }, fun), do: reduce(t, fun.(h, acc), fun)
|
||||
|
||||
def to_list(dict), do: dict
|
||||
end
|
||||
|
||||
+30
-34
@@ -13,7 +13,7 @@ defmodule Macro do
|
||||
|
||||
@binary_ops [:===, :!==,
|
||||
:==, :!=, :<=, :>=,
|
||||
:&&, :||, :<>, :++, :--, :**, ://, :::, :<-, :.., :|>, :=~,
|
||||
:&&, :||, :<>, :++, :--, ://, :\\, :::, :<-, :.., :|>, :=~,
|
||||
:<, :>, :->,
|
||||
:+, :-, :*, :/, :=, :|, :.,
|
||||
:and, :or, :xor, :when, :in, :inlist, :inbits,
|
||||
@@ -30,16 +30,16 @@ defmodule Macro do
|
||||
@spec binary_op_props(atom) :: { :left | :right, precedence :: integer }
|
||||
defp binary_op_props(o) do
|
||||
case o do
|
||||
::: -> {:right, 40}
|
||||
o when o in [:inlist, :inbits, ://, :|] -> {:right, 50}
|
||||
o when o in [:<-, :inlist, :inbits, ://, :\\, :::] -> {:left, 40}
|
||||
:| -> {:right, 50}
|
||||
:when -> {:right, 70}
|
||||
:= -> {:right, 80}
|
||||
o when o in [:||, :|||, :or, :xor] -> {:left, 130}
|
||||
o when o in [:&&, :&&&, :and] -> {:left, 140}
|
||||
o when o in [:==, :!=, :<, :<=, :>=, :>, :=~, :===, :!==] -> {:left, 150}
|
||||
o when o in [:<-, :|>, :<<<, :>>>] -> {:right, 160}
|
||||
o when o in [:|>, :<<<, :>>>] -> {:right, 160}
|
||||
:in -> {:left, 170}
|
||||
o when o in [:++, :--, :**, :.., :<>] -> {:right, 200}
|
||||
o when o in [:++, :--, :.., :<>] -> {:right, 200}
|
||||
o when o in [:+, :-] -> {:left, 210}
|
||||
o when o in [:*, :/] -> {:left, 220}
|
||||
:^^^ -> {:left, 250}
|
||||
@@ -66,7 +66,11 @@ defmodule Macro do
|
||||
argument in the given `position`.
|
||||
"""
|
||||
@spec pipe(Macro.t, Macro.t, integer) :: Macro.t | no_return
|
||||
def pipe(expr, call_args, integer // 0)
|
||||
def pipe(expr, call_args, integer \\ 0)
|
||||
|
||||
def pipe(expr, { :&, _, _ } = call_args, _integer) do
|
||||
raise ArgumentError, message: "cannot pipe #{to_string expr} into #{to_string call_args}"
|
||||
end
|
||||
|
||||
def pipe(expr, { call, line, atom }, integer) when is_atom(atom) do
|
||||
{ call, line, List.insert_at([], integer, expr) }
|
||||
@@ -175,24 +179,23 @@ defmodule Macro do
|
||||
"""
|
||||
@spec escape(term) :: Macro.t
|
||||
@spec escape(term, Keyword.t) :: Macro.t
|
||||
def escape(expr, opts // []) do
|
||||
def escape(expr, opts \\ []) do
|
||||
elem(:elixir_quote.escape(expr, Keyword.get(opts, :unquote, false)), 0)
|
||||
end
|
||||
|
||||
@doc %S"""
|
||||
@doc ~S"""
|
||||
Unescape the given chars.
|
||||
|
||||
This is the unescaping behavior
|
||||
used by default in Elixir single- and double-quoted strings.
|
||||
Check `unescape_string/2` for information on how to customize
|
||||
the escaping map.
|
||||
This is the unescaping behaviour used by default in Elixir
|
||||
single- and double-quoted strings. Check `unescape_string/2`
|
||||
for information on how to customize the escaping map.
|
||||
|
||||
In this setup, Elixir will escape the following: `\a`, `\b`,
|
||||
`\d`, `\e`, `\f`, `\n`, `\r`, `\s`, `\t` and `\v`. Octals are
|
||||
also escaped according to the latin1 set they represent.
|
||||
|
||||
This function is commonly used on sigil implementations
|
||||
(like `%r`, `%s` and others) which receive a raw, unescaped
|
||||
(like `~r`, `~s` and others) which receive a raw, unescaped
|
||||
string.
|
||||
|
||||
## Examples
|
||||
@@ -208,7 +211,7 @@ defmodule Macro do
|
||||
:elixir_interpolation.unescape_chars(chars)
|
||||
end
|
||||
|
||||
@doc %S"""
|
||||
@doc ~S"""
|
||||
Unescape the given chars according to the map given.
|
||||
|
||||
Check `unescape_string/1` if you want to use the same map
|
||||
@@ -294,7 +297,7 @@ defmodule Macro do
|
||||
"""
|
||||
@spec to_string(Macro.t) :: String.t
|
||||
@spec to_string(Macro.t, (Macro.t, String.t -> String.t)) :: String.t
|
||||
def to_string(tree, fun // fn(_ast, string) -> string end)
|
||||
def to_string(tree, fun \\ fn(_ast, string) -> string end)
|
||||
|
||||
# Variables
|
||||
def to_string({ var, _, atom } = ast, fun) when is_atom(atom) do
|
||||
@@ -362,7 +365,7 @@ defmodule Macro do
|
||||
end
|
||||
|
||||
# Binary ops
|
||||
def to_string({ op, _, [left, right] } = ast, fun) when op in @binary_ops do
|
||||
def to_string({ op, _, [left, right] } = ast, fun) when op in unquote(@binary_ops) do
|
||||
fun.(ast, op_to_string(left, fun, op, :left) <> " #{op} " <> op_to_string(right, fun, op, :right))
|
||||
end
|
||||
|
||||
@@ -377,7 +380,7 @@ defmodule Macro do
|
||||
fun.(ast, "not " <> to_string(arg, fun))
|
||||
end
|
||||
|
||||
def to_string({ op, _, [arg] } = ast, fun) when op in @unary_ops do
|
||||
def to_string({ op, _, [arg] } = ast, fun) when op in unquote(@unary_ops) do
|
||||
fun.(ast, atom_to_binary(op) <> to_string(arg, fun))
|
||||
end
|
||||
|
||||
@@ -421,7 +424,7 @@ defmodule Macro do
|
||||
@kw_keywords [:do, :catch, :rescue, :after, :else]
|
||||
|
||||
defp kw_blocks?([_|_] = kw) do
|
||||
Enum.all?(kw, &match?({x, _} when x in @kw_keywords, &1))
|
||||
Enum.all?(kw, &match?({x, _} when x in unquote(@kw_keywords), &1))
|
||||
end
|
||||
defp kw_blocks?(_), do: false
|
||||
|
||||
@@ -488,7 +491,7 @@ defmodule Macro do
|
||||
"(" <> to_string(expr, fun) <> ")"
|
||||
end
|
||||
|
||||
defp op_to_string({ op, _, [_, _] } = expr, fun, parent_op, side) when op in @binary_ops do
|
||||
defp op_to_string({ op, _, [_, _] } = expr, fun, parent_op, side) when op in unquote(@binary_ops) do
|
||||
{ parent_assoc, parent_prec } = binary_op_props(parent_op)
|
||||
{ _, prec } = binary_op_props(op)
|
||||
cond do
|
||||
@@ -506,7 +509,7 @@ defmodule Macro do
|
||||
|
||||
defp op_to_string(expr, fun, _, _), do: to_string(expr, fun)
|
||||
|
||||
defp arrow_to_string(pairs, fun, paren // false) do
|
||||
defp arrow_to_string(pairs, fun, paren \\ false) do
|
||||
Enum.map_join(pairs, "; ", fn({ :->, _, [left, right] }) ->
|
||||
left = comma_join_or_empty_paren(left, fun, paren)
|
||||
left <> "-> " <> to_string(right, fun)
|
||||
@@ -641,12 +644,6 @@ defmodule Macro do
|
||||
do: { env.module, true }
|
||||
defp do_expand_once({ :__DIR__, _, atom }, env) when is_atom(atom),
|
||||
do: { :filename.dirname(env.file), true }
|
||||
|
||||
defp do_expand_once({ :__FILE__, _, atom }, env) when is_atom(atom) do
|
||||
IO.write "__FILE__ is deprecated, please use __DIR__ or __ENV__.file instead\n#{Exception.format_stacktrace}"
|
||||
{ env.file, true }
|
||||
end
|
||||
|
||||
defp do_expand_once({ :__ENV__, _, atom }, env) when is_atom(atom),
|
||||
do: { { :{}, [], tuple_to_list(env) }, true }
|
||||
defp do_expand_once({ { :., _, [{ :__ENV__, _, atom }, field] }, _, [] } = original, env) when
|
||||
@@ -672,19 +669,18 @@ defmodule Macro do
|
||||
|
||||
defp do_expand_once({ atom, meta, args } = original, env)
|
||||
when is_atom(atom) and is_list(args) and is_list(meta) do
|
||||
module = env.module
|
||||
|
||||
extra = if function_exported?(module, :__info__, 1) do
|
||||
[{ module, module.__info__(:macros) }]
|
||||
else
|
||||
[]
|
||||
end
|
||||
|
||||
arity = length(args)
|
||||
|
||||
if :elixir_import.special_form(atom, arity) do
|
||||
{ original, false }
|
||||
else
|
||||
module = env.module
|
||||
extra = if function_exported?(module, :__info__, 1) do
|
||||
[{ module, module.__info__(:macros) }]
|
||||
else
|
||||
[]
|
||||
end
|
||||
|
||||
expand = :elixir_dispatch.expand_import(meta, { atom, length(args) }, args,
|
||||
:elixir_env.ex_to_env(env), extra)
|
||||
|
||||
|
||||
@@ -81,12 +81,16 @@ defmodule Macro.Env do
|
||||
def stacktrace(record) do
|
||||
cond do
|
||||
nil?(record.module) ->
|
||||
[{ :elixir_compiler, :__FILE__, 1, location(record) }]
|
||||
[{ :elixir_compiler, :__FILE__, 1, relative_location(record) }]
|
||||
nil?(record.function) ->
|
||||
[{ module(record), :__MODULE__, 0, location(record) }]
|
||||
[{ module(record), :__MODULE__, 0, relative_location(record) }]
|
||||
true ->
|
||||
{ name, arity } = record.function
|
||||
[{ module(record), name, arity, location(record) }]
|
||||
[{ module(record), name, arity, relative_location(record) }]
|
||||
end
|
||||
end
|
||||
|
||||
defp relative_location(record) do
|
||||
[file: Path.relative_to_cwd(file(record)), line: line(record)]
|
||||
end
|
||||
end
|
||||
|
||||
+33
-25
@@ -5,7 +5,7 @@ defmodule Module do
|
||||
end
|
||||
end
|
||||
|
||||
@moduledoc %S'''
|
||||
@moduledoc ~S'''
|
||||
This module provides many functions to deal with modules during
|
||||
compilation time. It allows a developer to dynamically attach
|
||||
documentation, add, delete and register attributes and so forth.
|
||||
@@ -257,7 +257,7 @@ defmodule Module do
|
||||
* `@typep` - defines a private type to be used in `@spec`
|
||||
* `@opaque` - defines an opaque type to be used in `@spec`
|
||||
* `@spec` - provides a specification for a function
|
||||
* `@callback` - provides a specification for the behavior callback
|
||||
* `@callback` - provides a specification for the behaviour callback
|
||||
|
||||
In addition to the built-in attributes outlined above, custom attributes may
|
||||
also be added. A custom attribute is any valid identifier prefixed with an
|
||||
@@ -340,7 +340,7 @@ defmodule Module do
|
||||
Foo.sum(1, 2) #=> 3
|
||||
|
||||
"""
|
||||
def eval_quoted(module, quoted, binding // [], opts // [])
|
||||
def eval_quoted(module, quoted, binding \\ [], opts \\ [])
|
||||
|
||||
def eval_quoted(env, quoted, binding, opts) when is_env(env) do
|
||||
eval_quoted(env.module, quoted, binding, Keyword.merge(env.to_keywords, opts))
|
||||
@@ -387,7 +387,7 @@ defmodule Module do
|
||||
when defining the module, while `defmodule` automatically
|
||||
shares the same environment.
|
||||
"""
|
||||
def create(module, quoted, opts // [])
|
||||
def create(module, quoted, opts \\ [])
|
||||
|
||||
def create(module, quoted, env) when is_env(env) do
|
||||
create(module, quoted, env.to_keywords)
|
||||
@@ -498,7 +498,7 @@ defmodule Module do
|
||||
end
|
||||
|
||||
"""
|
||||
def add_doc(module, line, kind, tuple, signature // [], doc)
|
||||
def add_doc(module, line, kind, tuple, signature \\ [], doc)
|
||||
|
||||
def add_doc(_module, _line, kind, _tuple, _signature, doc) when kind in [:defp, :defmacrop, :typep] do
|
||||
if doc, do: { :error, :private_doc }, else: :ok
|
||||
@@ -531,8 +531,8 @@ defmodule Module do
|
||||
|
||||
# Simplify signatures to be stored in docs
|
||||
|
||||
defp simplify_signature({ ://, defline, [left, right ] }, line, i) do
|
||||
{ ://, defline, [simplify_signature(left, line, i), right] }
|
||||
defp simplify_signature({ :\\, defline, [left, right ] }, line, i) do
|
||||
{ :\\, defline, [simplify_signature(left, line, i), right] }
|
||||
end
|
||||
|
||||
defp simplify_signature({ var, line, atom }, _, _i) when is_atom(atom) do
|
||||
@@ -564,11 +564,11 @@ defmodule Module do
|
||||
[]
|
||||
end
|
||||
|
||||
defp merge_signature({ ://, line, [left, right] }, newer, i) do
|
||||
{ ://, line, [merge_signature(left, newer, i), right] }
|
||||
defp merge_signature({ :\\, line, [left, right] }, newer, i) do
|
||||
{ :\\, line, [merge_signature(left, newer, i), right] }
|
||||
end
|
||||
|
||||
defp merge_signature(older, { ://, _, [left, _] }, i) do
|
||||
defp merge_signature(older, { :\\, _, [left, _] }, i) do
|
||||
merge_signature(older, left, i)
|
||||
end
|
||||
|
||||
@@ -631,7 +631,7 @@ defmodule Module do
|
||||
|
||||
defmodule Example do
|
||||
def version, do: 1
|
||||
Module.definitions_in __MODULE__ #=> [{:version,1}]
|
||||
Module.definitions_in __MODULE__ #=> [{:version,0}]
|
||||
end
|
||||
|
||||
"""
|
||||
@@ -649,7 +649,7 @@ defmodule Module do
|
||||
|
||||
defmodule Example do
|
||||
def version, do: 1
|
||||
Module.definitions_in __MODULE__, :def #=> [{:version,1}]
|
||||
Module.definitions_in __MODULE__, :def #=> [{:version,0}]
|
||||
Module.definitions_in __MODULE__, :defp #=> []
|
||||
end
|
||||
|
||||
@@ -745,12 +745,11 @@ defmodule Module do
|
||||
|
||||
Module.get_attribute(__MODULE__, :foo, true)
|
||||
|
||||
Notice the third argument is used to indicate if a warning
|
||||
should be emitted when the attribute was not previously defined.
|
||||
`warn` may also be a stacktrace, which will be used in the
|
||||
warning. The default value for `warn` is false for direct calls
|
||||
but the `@foo` macro sets it to the proper stacktrace automatically,
|
||||
warning every time `@foo` is used but not set previously.
|
||||
Notice the third argument may be given to indicate a stacktrace
|
||||
to be emitted when the attribute was not previously defined.
|
||||
The default value for `warn` is nil for direct calls but the `@foo`
|
||||
macro sets it to the proper stacktrace automatically, warning
|
||||
every time `@foo` is used but not set previously.
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -764,8 +763,9 @@ defmodule Module do
|
||||
end
|
||||
|
||||
"""
|
||||
@spec get_attribute(module, atom, warn :: boolean | [tuple]) :: term
|
||||
def get_attribute(module, key, warn // false) when is_atom(key) do
|
||||
@spec get_attribute(module, atom, warn :: nil | [tuple]) :: term
|
||||
def get_attribute(module, key, warn \\ nil) when
|
||||
is_atom(key) and (is_list(warn) or nil?(warn)) do
|
||||
assert_not_compiled!(:get_attribute, module)
|
||||
table = data_table_for(module)
|
||||
|
||||
@@ -777,9 +777,8 @@ defmodule Module do
|
||||
cond do
|
||||
:lists.member(key, acc) ->
|
||||
[]
|
||||
warn ->
|
||||
stack = is_list(warn) and warn
|
||||
:elixir_errors.warn "#{Exception.format_caller(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 to nil before access\n"
|
||||
nil
|
||||
true ->
|
||||
@@ -788,6 +787,15 @@ defmodule Module do
|
||||
end
|
||||
end
|
||||
|
||||
defp warn_info([entry|_]) do
|
||||
opts = elem(entry, size(entry) - 1)
|
||||
Exception.format_file_line(Keyword.get(opts, :file), Keyword.get(opts, :line)) <> " "
|
||||
end
|
||||
|
||||
defp warn_info([]) do
|
||||
""
|
||||
end
|
||||
|
||||
@doc """
|
||||
Deletes all attributes that match the given key.
|
||||
|
||||
@@ -878,7 +886,7 @@ defmodule Module do
|
||||
:ok ->
|
||||
:ok
|
||||
{ :error, :private_doc } ->
|
||||
:elixir_errors.warn "#{env.file}:#{line} function #{name}/#{arity} is private, @doc's are always discarded for private functions\n"
|
||||
:elixir_errors.warn line, env.file, "function #{name}/#{arity} is private, @doc's are always discarded for private functions\n"
|
||||
end
|
||||
|
||||
delete_attribute(module, :doc)
|
||||
@@ -906,7 +914,7 @@ defmodule Module do
|
||||
{ atom, 0 }
|
||||
end
|
||||
|
||||
defp normalize_attribute(kind, atom) when kind in [:behavior, :behaviour] and is_atom(atom) do
|
||||
defp normalize_attribute(:behaviour, atom) when is_atom(atom) do
|
||||
Code.ensure_compiled(atom)
|
||||
atom
|
||||
end
|
||||
|
||||
@@ -34,8 +34,8 @@
|
||||
defmodule Module.LocalsTracker do
|
||||
@moduledoc false
|
||||
|
||||
@timeout 30_000
|
||||
@behavior :gen_server
|
||||
@timeout 30_000
|
||||
@behaviour :gen_server
|
||||
|
||||
@type ref :: pid | module
|
||||
@type name :: atom
|
||||
@@ -145,6 +145,7 @@ defmodule Module.LocalsTracker do
|
||||
d = :gen_server.call(pid, :digraph, @timeout)
|
||||
|
||||
lc { name, arity } inlist all_defined,
|
||||
:digraph.in_neighbours(d, { :import, name, arity }) != [],
|
||||
n = :digraph.out_neighbours(d, { :import, name, arity }),
|
||||
n != [] do
|
||||
{ n, name, arity }
|
||||
|
||||
+19
-2
@@ -1,6 +1,11 @@
|
||||
defmodule Node do
|
||||
@moduledoc """
|
||||
Functions related to Erlang nodes.
|
||||
Functions related to VM nodes.
|
||||
|
||||
Some of the functions in this module are inlined by the compiler,
|
||||
similar to functions in the `Kernel` module and they are explicitly
|
||||
marked in their docs as "inlined by the compiler". For more information
|
||||
about inlined functions, check out the `Kernel` module.
|
||||
"""
|
||||
|
||||
@type t :: atom
|
||||
@@ -97,6 +102,8 @@ defmodule Node do
|
||||
|
||||
Check http://www.erlang.org/doc/man/erlang.html#spawn-2 for
|
||||
the list of available options.
|
||||
|
||||
Inlined by the compiler.
|
||||
"""
|
||||
@spec spawn(t, (() -> any)) :: pid
|
||||
def spawn(node, fun) do
|
||||
@@ -109,6 +116,8 @@ defmodule Node do
|
||||
|
||||
Check http://www.erlang.org/doc/man/erlang.html#spawn_opt-3 for
|
||||
the list of available options.
|
||||
|
||||
Inlined by the compiler.
|
||||
"""
|
||||
@spec spawn(t, (() -> any), Process.spawn_opts) :: pid | {pid, reference}
|
||||
def spawn(node, fun, opts) do
|
||||
@@ -122,6 +131,8 @@ defmodule Node do
|
||||
|
||||
Check http://www.erlang.org/doc/man/erlang.html#spawn-4 for
|
||||
the list of available options.
|
||||
|
||||
Inlined by the compiler.
|
||||
"""
|
||||
@spec spawn(t, module, atom, [any]) :: pid
|
||||
def spawn(node, module, fun, args) do
|
||||
@@ -135,6 +146,8 @@ defmodule Node do
|
||||
|
||||
Check http://www.erlang.org/doc/man/erlang.html#spawn_opt-5 for
|
||||
the list of available options.
|
||||
|
||||
Inlined by the compiler.
|
||||
"""
|
||||
@spec spawn(t, module, atom, [any], Process.spawn_opts) :: pid | {pid, reference}
|
||||
def spawn(node, module, fun, args, opts) do
|
||||
@@ -147,6 +160,8 @@ defmodule Node do
|
||||
new process, atomically. If `node` does not exist, a useless pid is returned
|
||||
(and due to the link, an exit signal with exit reason `:noconnection` will be
|
||||
received).
|
||||
|
||||
Inlined by the compiler.
|
||||
"""
|
||||
@spec spawn_link(t, (() -> any)) :: pid
|
||||
def spawn_link(node, fun) do
|
||||
@@ -159,6 +174,8 @@ defmodule Node do
|
||||
process and the new process, atomically. If `node` does not exist, a useless
|
||||
pid is returned (and due to the link, an exit signal with exit reason
|
||||
`:noconnection` will be received).
|
||||
|
||||
Inlined by the compiler.
|
||||
"""
|
||||
@spec spawn_link(t, module, atom, [any]) :: pid
|
||||
def spawn_link(node, module, fun, args) do
|
||||
@@ -172,7 +189,7 @@ defmodule Node do
|
||||
|
||||
This function will raise `FunctionClauseError` if the given `node` is not alive.
|
||||
"""
|
||||
def set_cookie(node // Node.self, cookie) when is_atom(cookie) do
|
||||
def set_cookie(node \\ Node.self, cookie) when is_atom(cookie) do
|
||||
:erlang.set_cookie(node, cookie)
|
||||
end
|
||||
|
||||
|
||||
@@ -79,7 +79,7 @@ defmodule OptionParser do
|
||||
{ [op: false], ["path/to/file"], [] }
|
||||
|
||||
"""
|
||||
def parse(argv, opts // []) when is_list(argv) and is_list(opts) do
|
||||
def parse(argv, opts \\ []) when is_list(argv) and is_list(opts) do
|
||||
parse(argv, opts, true)
|
||||
end
|
||||
|
||||
@@ -97,7 +97,7 @@ defmodule OptionParser do
|
||||
iex> OptionParser.parse_head(["--verbose", "--source", "lib", "test/enum_test.exs", "--unlock"])
|
||||
{ [verbose: true, source: "lib"], ["test/enum_test.exs", "--unlock"], [] }
|
||||
"""
|
||||
def parse_head(argv, opts // []) when is_list(argv) and is_list(opts) do
|
||||
def parse_head(argv, opts \\ []) when is_list(argv) and is_list(opts) do
|
||||
parse(argv, opts, false)
|
||||
end
|
||||
|
||||
|
||||
+2
-11
@@ -408,8 +408,8 @@ defmodule Path do
|
||||
do_join(rest, relativename, [?/|result], os_type)
|
||||
defp do_join(<<?/, rest :: binary>>, relativename, [?/|result], os_type), do:
|
||||
do_join(rest, relativename, [?/|result], os_type)
|
||||
defp do_join(<<>>, <<>>, result, os_type), do:
|
||||
iolist_to_binary(maybe_remove_dirsep(result, os_type))
|
||||
defp do_join(<<>>, <<>>, result, _os_type), do:
|
||||
iolist_to_binary(:lists.reverse(result))
|
||||
defp do_join(<<>>, relativename, [?:|rest], :win32), do:
|
||||
do_join(relativename, <<>>, [?:|rest], :win32)
|
||||
defp do_join(<<>>, relativename, [?/|result], os_type), do:
|
||||
@@ -419,15 +419,6 @@ defmodule Path do
|
||||
defp do_join(<<char, rest :: binary>>, relativename, result, os_type) when is_integer(char), do:
|
||||
do_join(rest, relativename, [char|result], os_type)
|
||||
|
||||
defp maybe_remove_dirsep([?/, ?:, letter], :win32), do:
|
||||
[letter, ?:, ?/]
|
||||
defp maybe_remove_dirsep([?/], _), do:
|
||||
[?/]
|
||||
defp maybe_remove_dirsep([?/|name], _), do:
|
||||
:lists.reverse(name)
|
||||
defp maybe_remove_dirsep(name, _), do:
|
||||
:lists.reverse(name)
|
||||
|
||||
@doc """
|
||||
Returns a list with the path split by the path separator.
|
||||
If an empty string is given, returns the root path.
|
||||
|
||||
@@ -20,7 +20,7 @@ defmodule Port do
|
||||
@doc """
|
||||
See http://www.erlang.org/doc/man/erlang.html#port_command-2.
|
||||
"""
|
||||
def command(port, data, options // []) do
|
||||
def command(port, data, options \\ []) do
|
||||
:erlang.port_command(port, data, options)
|
||||
end
|
||||
|
||||
|
||||
+75
-16
@@ -1,11 +1,11 @@
|
||||
defmodule Process do
|
||||
@moduledoc """
|
||||
This module provides convenience functions around processes and
|
||||
the process dictionary. In Erlang, most of these functions are
|
||||
auto-imported, but in Elixir they are grouped in a module for
|
||||
convenience. Notice that these functions, different from Erlang's,
|
||||
always return nil instead of undefined. You can use their Erlang
|
||||
version if you want the undefined value.
|
||||
Conveniences for working with processes and the process dictionary.
|
||||
|
||||
Some of the functions in this module are inlined by the compiler,
|
||||
similar to functions in the `Kernel` module and they are explicitly
|
||||
marked in their docs as "inlined by the compiler". For more information
|
||||
about inlined functions, check out the `Kernel` module.
|
||||
"""
|
||||
|
||||
@doc """
|
||||
@@ -32,7 +32,7 @@ defmodule Process do
|
||||
"""
|
||||
@spec get(term) :: term
|
||||
@spec get(term, default :: term) :: term
|
||||
def get(key, default // nil) do
|
||||
def get(key, default \\ nil) do
|
||||
case :erlang.get(key) do
|
||||
:undefined ->
|
||||
default
|
||||
@@ -76,21 +76,23 @@ defmodule Process do
|
||||
@doc """
|
||||
Sends an exit signal with the given reason to the pid.
|
||||
|
||||
The following behavior applies if reason is any term except `:normal` or `:kill`:
|
||||
The following behaviour applies if reason is any term except `:normal` or `:kill`:
|
||||
|
||||
1) If pid is not trapping exits, pid will exit with the given reason;
|
||||
|
||||
2) If pid is trapping exits, the exit signal is transformed into a message
|
||||
{'EXIT', from, reason} and delivered to the message queue of pid;
|
||||
{:EXIT, from, reason} and delivered to the message queue of pid;
|
||||
|
||||
3) If reason is the atom `:normal`, pid will not exit. If it is trapping exits,
|
||||
the exit signal is transformed into a message {'EXIT', from, :normal} and
|
||||
the exit signal is transformed into a message {:EXIT, from, :normal} and
|
||||
delivered to its message queue;
|
||||
|
||||
4) If reason is the atom `:kill`, that is if `exit(pid, :kill)` is called, an
|
||||
untrappable exit signal is sent to pid which will unconditionally exit with
|
||||
exit reason `:killed`.
|
||||
|
||||
Inlined by the compiler.
|
||||
|
||||
## Examples
|
||||
|
||||
Process.exit(pid, :kill)
|
||||
@@ -110,12 +112,37 @@ defmodule Process do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns the pid of a new process started by the application of `fun`.
|
||||
It behaves exactly the same as `Kernel.spawn/1`.
|
||||
Sends a message to the given process.
|
||||
|
||||
It does the same as `Kernel.send/2`.
|
||||
"""
|
||||
@spec spawn((() -> any)) :: pid
|
||||
def spawn(fun) do
|
||||
:erlang.spawn(fun)
|
||||
@spec send(dest, msg) :: msg when
|
||||
dest: pid | port | atom | { atom, node },
|
||||
msg: any
|
||||
def send(dest, msg) do
|
||||
:erlang.send(dest, msg)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Sends `msg` to `dest` after `time` millisecons.
|
||||
|
||||
If `dest` is a pid, it has to be a pid of a local process, dead or alive.
|
||||
If `dest` is an atom, it is supposed to be the name of a registered process
|
||||
which is looked up at the time of delivery. No error is given if the name does
|
||||
not refer to a process.
|
||||
|
||||
This function returns a timer reference, which can be read or canceled with
|
||||
`:erlang.read_timer/1`, `:erlang.start_timer/3` and `:erlang.cancel_timer/1`.
|
||||
Note `time` cannot be greater than `4294967295`.
|
||||
|
||||
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
|
||||
automatically canceled when `dest` is an atom (as the atom resolution is done
|
||||
on delivery).
|
||||
"""
|
||||
@spec send_after(pid | atom, term, non_neg_integer) :: reference
|
||||
def send_after(dest, msg, time) do
|
||||
:erlang.send_after(time, dest, msg)
|
||||
end
|
||||
|
||||
@type spawn_opt :: :link | :monitor | {:priority, :low | :normal | :high} |
|
||||
@@ -124,11 +151,24 @@ defmodule Process do
|
||||
{:min_bin_vheap_size, non_neg_integer}
|
||||
@type spawn_opts :: [spawn_opt]
|
||||
|
||||
@doc """
|
||||
Returns the pid of a new process started by the application of `fun`.
|
||||
It behaves exactly the same as `Kernel.spawn/1`.
|
||||
|
||||
Inlined by the compiler.
|
||||
"""
|
||||
@spec spawn((() -> any)) :: pid
|
||||
def spawn(fun) do
|
||||
:erlang.spawn(fun)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns the pid of a new process started by the application of `fun`.
|
||||
|
||||
It also accepts extra options, for the list of available options
|
||||
check http://www.erlang.org/doc/man/erlang.html#spawn_opt-2
|
||||
|
||||
Inlined by the compiler.
|
||||
"""
|
||||
@spec spawn((() -> any), spawn_opts) :: pid | {pid, reference}
|
||||
def spawn(fun, opts) do
|
||||
@@ -141,6 +181,8 @@ defmodule Process do
|
||||
scheduler queue and be run some time later.
|
||||
|
||||
It behaves exactly the same as the `Kernel.spawn/3` function.
|
||||
|
||||
Inlined by the compiler.
|
||||
"""
|
||||
@spec spawn(module, atom, [any]) :: pid
|
||||
def spawn(mod, fun, args) do
|
||||
@@ -155,6 +197,7 @@ defmodule Process do
|
||||
It also accepts extra options, for the list of available options
|
||||
check http://www.erlang.org/doc/man/erlang.html#spawn_opt-4
|
||||
|
||||
Inlined by the compiler.
|
||||
"""
|
||||
@spec spawn(module, atom, [any], spawn_opts) :: pid | {pid, reference}
|
||||
def spawn(mod, fun, args, opts) do
|
||||
@@ -165,6 +208,8 @@ defmodule Process do
|
||||
Returns the pid of a new process started by the application of `fun`.
|
||||
A link is created between the calling process and the new
|
||||
process, atomically.
|
||||
|
||||
Inlined by the compiler.
|
||||
"""
|
||||
@spec spawn_link((() -> any)) :: pid
|
||||
def spawn_link(fun) do
|
||||
@@ -175,6 +220,8 @@ defmodule Process do
|
||||
Returns the pid of a new process started by the application of
|
||||
`module.function(args)`. A link is created between the calling process
|
||||
and the new process, atomically. Otherwise works like spawn/3.
|
||||
|
||||
Inlined by the compiler.
|
||||
"""
|
||||
@spec spawn_link(module, atom, [any]) :: pid
|
||||
def spawn_link(mod, fun, args) do
|
||||
@@ -184,6 +231,8 @@ defmodule Process do
|
||||
@doc """
|
||||
Returns the pid of a new process started by the application of `fun`
|
||||
and reference for a monitor created to the new process.
|
||||
|
||||
Inlined by the compiler.
|
||||
"""
|
||||
@spec spawn_monitor((() -> any)) :: {pid, reference}
|
||||
def spawn_monitor(fun) do
|
||||
@@ -194,6 +243,8 @@ defmodule Process do
|
||||
A new process is started by the application of `module.function(args)`
|
||||
and the process is monitored at the same time. Returns the pid and a
|
||||
reference for the monitor. Otherwise works like spawn/3.
|
||||
|
||||
Inlined by the compiler.
|
||||
"""
|
||||
@spec spawn_monitor(module, atom, [any]) :: {pid, reference}
|
||||
def spawn_monitor(mod, fun, args) do
|
||||
@@ -205,6 +256,8 @@ defmodule Process do
|
||||
It returns the monitor reference.
|
||||
|
||||
See http://www.erlang.org/doc/man/erlang.html#monitor-2 for more info.
|
||||
|
||||
Inlined by the compiler.
|
||||
"""
|
||||
@spec monitor(pid | {reg_name :: atom, node :: atom} | reg_name :: atom) :: reference
|
||||
def monitor(item) do
|
||||
@@ -217,10 +270,12 @@ defmodule Process do
|
||||
If the monitoring is already turned off, nothing happens.
|
||||
|
||||
See http://www.erlang.org/doc/man/erlang.html#demonitor-2 for more info.
|
||||
|
||||
Inlined by the compiler.
|
||||
"""
|
||||
@spec demonitor(reference) :: true
|
||||
@spec demonitor(reference, options :: [:flush | :info]) :: boolean
|
||||
def demonitor(monitor_ref, options // []) do
|
||||
def demonitor(monitor_ref, options \\ []) do
|
||||
:erlang.demonitor(monitor_ref, options)
|
||||
end
|
||||
|
||||
@@ -244,6 +299,8 @@ defmodule Process do
|
||||
(or port) `pid`, if there is not such a link already.
|
||||
|
||||
See http://www.erlang.org/doc/man/erlang.html#link-1 for more info.
|
||||
|
||||
Inlined by the compiler.
|
||||
"""
|
||||
@spec link(pid | port) :: true
|
||||
def link(pid) do
|
||||
@@ -256,6 +313,8 @@ defmodule Process do
|
||||
fail, even if there is no link or `id` does not exist
|
||||
|
||||
See http://www.erlang.org/doc/man/erlang.html#unlink-1 for more info.
|
||||
|
||||
Inlined by the compiler.
|
||||
"""
|
||||
@spec unlink(pid | port) :: true
|
||||
def unlink(pid) do
|
||||
|
||||
+42
-4
@@ -1,7 +1,45 @@
|
||||
defrecord Range, [:first, :last] do
|
||||
defmodule Range do
|
||||
@moduledoc """
|
||||
Defines a Range.
|
||||
"""
|
||||
|
||||
@type t :: { Range, any, any }
|
||||
@type t(first, last) :: { Range, first, last }
|
||||
|
||||
@doc """
|
||||
Creates a new range.
|
||||
"""
|
||||
def new(first, last) do
|
||||
{ Range, first, last }
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns the first item of the range.
|
||||
"""
|
||||
def first({ Range, first, _ }) do
|
||||
first
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns the last item of the range.
|
||||
"""
|
||||
def last({ Range, _, last }) do
|
||||
last
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns true if the given argument is a range.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Range.range?(1..3)
|
||||
true
|
||||
iex> Range.range?(0)
|
||||
false
|
||||
|
||||
"""
|
||||
def range?({ Range, _, _ }), do: true
|
||||
def range?(_), do: false
|
||||
end
|
||||
|
||||
defprotocol Range.Iterator do
|
||||
@@ -55,7 +93,7 @@ defimpl Enumerable, for: Range do
|
||||
end
|
||||
|
||||
defimpl Range.Iterator, for: Integer do
|
||||
def next(first, Range[last: last]) when is_integer(last) do
|
||||
def next(first, _ .. last) when is_integer(last) do
|
||||
if last >= first do
|
||||
&(&1 + 1)
|
||||
else
|
||||
@@ -63,7 +101,7 @@ defimpl Range.Iterator, for: Integer do
|
||||
end
|
||||
end
|
||||
|
||||
def count(first, Range[last: last]) when is_integer(last) do
|
||||
def count(first, _ .. last) when is_integer(last) do
|
||||
if last >= first do
|
||||
last - first + 1
|
||||
else
|
||||
@@ -75,7 +113,7 @@ end
|
||||
defimpl Inspect, for: Range do
|
||||
import Inspect.Algebra
|
||||
|
||||
def inspect(Range[first: first, last: last], opts) do
|
||||
def inspect(first .. last, opts) do
|
||||
concat [to_doc(first, opts), "..", to_doc(last, opts)]
|
||||
end
|
||||
end
|
||||
|
||||
+15
-127
@@ -1,5 +1,5 @@
|
||||
defmodule Record do
|
||||
@moduledoc %S"""
|
||||
@moduledoc ~S"""
|
||||
Functions for working with Records.
|
||||
|
||||
A record is a tagged tuple which contains one or more elements
|
||||
@@ -84,41 +84,9 @@ defmodule Record do
|
||||
|
||||
defrecord User, name: "José", age: 25
|
||||
|
||||
Notice that, unlike `defrecordp`, `defrecord` expects an
|
||||
alias as the first argument. This is because `defrecord` is going
|
||||
to create a module named `User` with all the relevant metadata.
|
||||
This module can then be imported and we can manipulate the user
|
||||
as with `defrecordp`:
|
||||
|
||||
Record.import User, as: :user
|
||||
user() #=> User[name: "José", age: 25]
|
||||
user(age: 26) #=> User[name: "José", age: 26]
|
||||
|
||||
Notice that now since the record definition is accessible, Elixir
|
||||
shows the record nicely formatted, no longer as a simple tuple. We
|
||||
can get the raw formatting by passing `records: false` to `inspect`:
|
||||
|
||||
inspect user(), records: false
|
||||
{ User, "José", 25 }
|
||||
|
||||
Since working with external records is common, Elixir allows
|
||||
developers to skip importing the record altogether in favor
|
||||
of a `Module[args]` syntax:
|
||||
|
||||
# Skipping a field uses its default value
|
||||
User[] #=> User[name: "José", age: 25]
|
||||
User[age: 26] #=> User[name: "José", age: 26]
|
||||
|
||||
The macro name is replaced by the module name and the parentheses
|
||||
are replaced by brackets. When this syntax is used, there is no
|
||||
need to import the record.
|
||||
|
||||
Before we sum up the differences between `defrecord` and
|
||||
`defrecordp`, there is one last functionality introduced by
|
||||
`defrecord` that we need to discuss.
|
||||
|
||||
## Runtime access
|
||||
|
||||
All the functionality discussed above happens at compilation time.
|
||||
This means that both `user(age: 26)` and `User[age: 26]` are expanded
|
||||
into a tuple at compile time.
|
||||
@@ -207,36 +175,6 @@ defmodule Record do
|
||||
Record.Extractor.retrieve(name, opts)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Import a public record definition as a set of private macros.
|
||||
|
||||
Macros defined as in `Kernel.defrecordp/3`. This is useful when one
|
||||
desires to manipulate a record via a set of macros instead
|
||||
of the regular access syntax.
|
||||
|
||||
## Example
|
||||
|
||||
defmodule Test do
|
||||
Record.import File.Stat, as: :file_stat
|
||||
|
||||
def size(file_stat(size: size)), do: size
|
||||
end
|
||||
|
||||
"""
|
||||
defmacro import(module, as: name) do
|
||||
quote do
|
||||
module = unquote(module)
|
||||
|
||||
fields = if module == __MODULE__ do
|
||||
@record_fields
|
||||
else
|
||||
module.__record__(:fields)
|
||||
end
|
||||
|
||||
Record.defmacros(unquote(name), fields, __ENV__, module)
|
||||
end
|
||||
end
|
||||
|
||||
@doc false
|
||||
def defrecord(name, fields, opts) do
|
||||
block = Keyword.get(opts, :do, nil)
|
||||
@@ -394,7 +332,7 @@ defmodule Record do
|
||||
end
|
||||
|
||||
"""
|
||||
def defmacros(name, values, env, tag // nil)
|
||||
def defmacros(name, values, env, tag \\ nil)
|
||||
when is_atom(name) and is_list(values) and is_atom(tag) do
|
||||
|
||||
escaped = lc value inlist values do
|
||||
@@ -409,10 +347,6 @@ defmodule Record do
|
||||
Record.access(unquote(tag), unquote(escaped), [], __CALLER__)
|
||||
end
|
||||
|
||||
defmacrop unquote(name)(record) when is_tuple(record) do
|
||||
Record.to_keywords(unquote(tag), unquote(escaped), record)
|
||||
end
|
||||
|
||||
defmacrop unquote(name)(args) do
|
||||
Record.access(unquote(tag), unquote(escaped), args, __CALLER__)
|
||||
end
|
||||
@@ -453,14 +387,24 @@ defmodule Record do
|
||||
Module.put_attribute(module, :record_optimizable, functions)
|
||||
end
|
||||
|
||||
@doc false
|
||||
# Implements the access macro used by records.
|
||||
# It returns the index of the given field.
|
||||
def access(atom, fields, arg, _caller) when is_atom(arg) do
|
||||
if index = find_index(fields, arg, 0) do
|
||||
index + 1
|
||||
else
|
||||
raise ArgumentError, message: "record #{inspect atom} does not have the key: #{inspect arg}"
|
||||
end
|
||||
end
|
||||
|
||||
# Implements the access macro used by records.
|
||||
# It returns a quoted expression that defines
|
||||
# a record or a match in case the record is
|
||||
# inside a match.
|
||||
@doc false
|
||||
def access(atom, fields, keyword, caller) do
|
||||
unless is_keyword(keyword) do
|
||||
raise ArgumentError, message: "expected contents inside brackets to be a keyword list, got: #{inspect keyword}"
|
||||
raise ArgumentError, message: "expected contents inside brackets to be a keyword list or an atom, got: #{inspect keyword}"
|
||||
end
|
||||
|
||||
in_match = caller.in_match?
|
||||
@@ -494,27 +438,6 @@ defmodule Record do
|
||||
end
|
||||
end
|
||||
|
||||
# Implements to_keywords macro defined by defmacros.
|
||||
# It returns a quoted expression that represents
|
||||
# converting record to keywords list.
|
||||
@doc false
|
||||
def to_keywords(_atom, fields, record) do
|
||||
{ var, extra } = cache_var(record)
|
||||
|
||||
keywords = Enum.map fields,
|
||||
fn { key, _default } ->
|
||||
index = find_index(fields, key, 0)
|
||||
quote do
|
||||
{ unquote(key), :erlang.element(unquote(index + 2), unquote(var)) }
|
||||
end
|
||||
end
|
||||
|
||||
quote do
|
||||
unquote_splicing(extra)
|
||||
unquote(keywords)
|
||||
end
|
||||
end
|
||||
|
||||
# Dispatch the call to either get, update or to_list depending on the args given.
|
||||
@doc false
|
||||
def dispatch(atom, fields, record, args, caller) do
|
||||
@@ -523,8 +446,6 @@ defmodule Record do
|
||||
get(atom, fields, record, args)
|
||||
is_keyword(args) ->
|
||||
update(atom, fields, record, args, caller)
|
||||
is_list(args) ->
|
||||
to_list(atom, fields, record, args)
|
||||
true ->
|
||||
raise ArgumentError, message: "expected arguments to be a compile time atom, list or keywords"
|
||||
end
|
||||
@@ -568,42 +489,8 @@ defmodule Record do
|
||||
end
|
||||
end
|
||||
|
||||
# Implements to_list macro defined by defmacros.
|
||||
# It returns a quoted expression that represents
|
||||
# extracting given fields from record.
|
||||
@doc false
|
||||
defp to_list(atom, fields, record, keys) do
|
||||
unless is_list(fields) do
|
||||
raise ArgumentError, message: "expected arguments to be a compile time list"
|
||||
end
|
||||
|
||||
{ var, extra } = cache_var(record)
|
||||
|
||||
list = Enum.map keys,
|
||||
fn(key) ->
|
||||
index = find_index(fields, key, 0)
|
||||
if index do
|
||||
quote do: :erlang.element(unquote(index + 2), unquote(var))
|
||||
else
|
||||
raise ArgumentError, message: "record #{inspect atom} does not have the key: #{inspect key}"
|
||||
end
|
||||
end
|
||||
|
||||
quote do
|
||||
unquote_splicing(extra)
|
||||
unquote(list)
|
||||
end
|
||||
end
|
||||
|
||||
defp cache_var({ var, _, kind } = tuple) when is_atom(var) and is_atom(kind) do
|
||||
{ tuple, [] }
|
||||
end
|
||||
|
||||
defp cache_var(other) do
|
||||
quote do
|
||||
{ x, [x = unquote(other)] }
|
||||
end
|
||||
end
|
||||
|
||||
## Function generation
|
||||
|
||||
@@ -906,6 +793,7 @@ defmodule Record do
|
||||
raise ArgumentError, message: "record field default value #{inspect atom} can only contain " <>
|
||||
"functions that point to an existing &Mod.fun/arity"
|
||||
end
|
||||
other
|
||||
end
|
||||
|
||||
defp check_value(atom, other) when is_reference(other) or is_pid(other) or is_port(other) do
|
||||
|
||||
+61
-46
@@ -1,17 +1,17 @@
|
||||
defmodule Regex do
|
||||
@moduledoc %S"""
|
||||
@moduledoc ~S"""
|
||||
Regular expressions for Elixir built on top of the `re` module
|
||||
in the Erlang Standard Library. More information can be found
|
||||
in the [`re` documentation](http://www.erlang.org/doc/man/re.html).
|
||||
|
||||
Regular expressions in Elixir can be created using `Regex.compile!/2`
|
||||
or using the special form with [`%r`](Kernel.html#sigil_r/2):
|
||||
or using the special form with [`~r`](Kernel.html#sigil_r/2):
|
||||
|
||||
# A simple regular expressions that matches foo anywhere in the string
|
||||
%r/foo/
|
||||
~r/foo/
|
||||
|
||||
# A regular expression with case insensitive options
|
||||
%r/foo/i
|
||||
~r/foo/i
|
||||
|
||||
The `re` module provides several options, the ones available in Elixir, followed by
|
||||
their shortcut in parenthesis, are:
|
||||
@@ -37,9 +37,6 @@ defmodule Regex do
|
||||
* `newline` - not available, use `(*CR)` or `(*LF)` or `(*CRLF)` or `(*ANYCRLF)`
|
||||
or `(*ANY)` at the beginning of the regexp according to the re documentation
|
||||
|
||||
Most of the functions in this module accept either a binary or a char list
|
||||
as subject. The result is based on the argument (a binary will return
|
||||
a binary, a char list will return a char list).
|
||||
"""
|
||||
|
||||
defrecordp :regex, Regex, [:re_pattern, :source, :options, :groups]
|
||||
@@ -51,7 +48,7 @@ defmodule Regex do
|
||||
Compiles the regular expression.
|
||||
|
||||
The given options can either be a binary with the characters
|
||||
representing the same regex options given to the `%r` sigil,
|
||||
representing the same regex options given to the `~r` sigil,
|
||||
or a list of options, as expected by the [Erlang `re` docs](http://www.erlang.org/doc/man/re.html).
|
||||
|
||||
It returns `{ :ok, regex }` in case of success,
|
||||
@@ -60,13 +57,13 @@ defmodule Regex do
|
||||
## Examples
|
||||
|
||||
iex> Regex.compile("foo")
|
||||
{:ok, %r"foo"}
|
||||
{:ok, ~r"foo"}
|
||||
iex> Regex.compile("*foo")
|
||||
{:error, {'nothing to repeat', 0}}
|
||||
|
||||
"""
|
||||
@spec compile(binary, binary | [term]) :: { :ok, t } | { :error, any }
|
||||
def compile(source, options // "")
|
||||
def compile(source, options \\ "")
|
||||
|
||||
def compile(source, options) when is_binary(options) do
|
||||
case translate_options(options) do
|
||||
@@ -100,7 +97,7 @@ defmodule Regex do
|
||||
Compiles the regular expression according to the given options.
|
||||
Fails with `Regex.CompileError` if the regex cannot be compiled.
|
||||
"""
|
||||
def compile!(source, options // "") do
|
||||
def compile!(source, options \\ "") do
|
||||
case compile(source, options) do
|
||||
{ :ok, regex } -> regex
|
||||
{ :error, { reason, at } } -> raise Regex.CompileError, message: "#{reason} at position #{at}"
|
||||
@@ -112,16 +109,31 @@ defmodule Regex do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Regex.match?(%r/foo/, "foo")
|
||||
iex> Regex.match?(~r/foo/, "foo")
|
||||
true
|
||||
iex> Regex.match?(%r/foo/, "bar")
|
||||
iex> Regex.match?(~r/foo/, "bar")
|
||||
false
|
||||
|
||||
"""
|
||||
def match?(regex(re_pattern: compiled), string) do
|
||||
_ = return_for(string)
|
||||
:re.run(string, compiled, [{ :capture, :none }]) == :match
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns true if the given argument is a regex.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Regex.regex?(~r/foo/)
|
||||
true
|
||||
iex> Regex.regex?(0)
|
||||
false
|
||||
|
||||
"""
|
||||
def regex?(regex()), do: true
|
||||
def regex?(_), do: false
|
||||
|
||||
@doc """
|
||||
Runs the regular expression against the given string until the first match.
|
||||
It returns a list with all captures or `nil` if no match occurred.
|
||||
@@ -131,15 +143,15 @@ defmodule Regex do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Regex.run(%r/c(d)/, "abcd")
|
||||
iex> Regex.run(~r/c(d)/, "abcd")
|
||||
["cd", "d"]
|
||||
iex> Regex.run(%r/e/, "abcd")
|
||||
iex> Regex.run(~r/e/, "abcd")
|
||||
nil
|
||||
iex> Regex.run(%r/c(d)/, "abcd", return: :index)
|
||||
iex> Regex.run(~r/c(d)/, "abcd", return: :index)
|
||||
[{2,2},{3,1}]
|
||||
|
||||
"""
|
||||
def run(regex, string, options // [])
|
||||
def run(regex, string, options \\ [])
|
||||
|
||||
def run(regex(re_pattern: compiled, groups: groups), string, options) do
|
||||
return = Keyword.get(options, :return, return_for(string))
|
||||
@@ -163,15 +175,15 @@ defmodule Regex do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Regex.named_captures(%r/c(?<foo>d)/g, "abcd")
|
||||
iex> Regex.named_captures(~r/c(?<foo>d)/g, "abcd")
|
||||
[foo: "d"]
|
||||
iex> Regex.named_captures(%r/a(?<foo>b)c(?<bar>d)/g, "abcd")
|
||||
iex> Regex.named_captures(~r/a(?<foo>b)c(?<bar>d)/g, "abcd")
|
||||
[foo: "b", bar: "d"]
|
||||
iex> Regex.named_captures(%r/a(?<foo>b)c(?<bar>d)/g, "efgh")
|
||||
iex> Regex.named_captures(~r/a(?<foo>b)c(?<bar>d)/g, "efgh")
|
||||
nil
|
||||
|
||||
"""
|
||||
def named_captures(regex(groups: groups) = regex, string, options // []) do
|
||||
def named_captures(regex(groups: groups) = regex, string, options \\ []) do
|
||||
options = Keyword.put_new(options, :capture, :groups)
|
||||
results = run(regex, string, options)
|
||||
if results, do: Enum.zip(groups, results)
|
||||
@@ -189,7 +201,7 @@ defmodule Regex do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Regex.source(%r(foo))
|
||||
iex> Regex.source(~r(foo))
|
||||
"foo"
|
||||
|
||||
"""
|
||||
@@ -202,7 +214,7 @@ defmodule Regex do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Regex.opts(%r(foo)m)
|
||||
iex> Regex.opts(~r(foo)m)
|
||||
"m"
|
||||
|
||||
"""
|
||||
@@ -215,7 +227,7 @@ defmodule Regex do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Regex.groups(%r/(?<foo>bar)/g)
|
||||
iex> Regex.groups(~r/(?<foo>bar)/g)
|
||||
[:foo]
|
||||
|
||||
"""
|
||||
@@ -237,15 +249,15 @@ defmodule Regex do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Regex.scan(%r/c(d|e)/, "abcd abce")
|
||||
iex> Regex.scan(~r/c(d|e)/, "abcd abce")
|
||||
[["cd", "d"], ["ce", "e"]]
|
||||
iex> Regex.scan(%r/c(?:d|e)/, "abcd abce")
|
||||
iex> Regex.scan(~r/c(?:d|e)/, "abcd abce")
|
||||
[["cd"], ["ce"]]
|
||||
iex> Regex.scan(%r/e/, "abcd")
|
||||
iex> Regex.scan(~r/e/, "abcd")
|
||||
[]
|
||||
|
||||
"""
|
||||
def scan(regex, string, options // [])
|
||||
def scan(regex, string, options \\ [])
|
||||
|
||||
def scan(regex(re_pattern: compiled, groups: groups), string, options) do
|
||||
return = Keyword.get(options, :return, return_for(string))
|
||||
@@ -269,19 +281,19 @@ defmodule Regex do
|
||||
If no number of parts is given, it defaults to `:infinity`.
|
||||
|
||||
## Examples
|
||||
iex> Regex.split(%r/-/, "a-b-c")
|
||||
iex> Regex.split(~r/-/, "a-b-c")
|
||||
["a","b","c"]
|
||||
iex> Regex.split(%r/-/, "a-b-c", [parts: 2])
|
||||
iex> Regex.split(~r/-/, "a-b-c", [parts: 2])
|
||||
["a","b-c"]
|
||||
iex> Regex.split(%r/-/, "abc")
|
||||
iex> Regex.split(~r/-/, "abc")
|
||||
["abc"]
|
||||
iex> Regex.split(%r//, "abc")
|
||||
iex> Regex.split(~r//, "abc")
|
||||
["a", "b", "c", ""]
|
||||
iex> Regex.split(%r//, "abc", trim: true)
|
||||
iex> Regex.split(~r//, "abc", trim: true)
|
||||
["a", "b", "c"]
|
||||
"""
|
||||
|
||||
def split(regex, string, options // [])
|
||||
def split(regex, string, options \\ [])
|
||||
|
||||
def split(regex(re_pattern: compiled), string, options) do
|
||||
parts =
|
||||
@@ -302,7 +314,7 @@ defmodule Regex do
|
||||
end
|
||||
end
|
||||
|
||||
@doc %S"""
|
||||
@doc ~S"""
|
||||
Receives a regex, a binary and a replacement, returns a new
|
||||
binary where the all matches are replaced by replacement.
|
||||
|
||||
@@ -313,29 +325,29 @@ defmodule Regex do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Regex.replace(%r/d/, "abc", "d")
|
||||
iex> Regex.replace(~r/d/, "abc", "d")
|
||||
"abc"
|
||||
iex> Regex.replace(%r/b/, "abc", "d")
|
||||
iex> Regex.replace(~r/b/, "abc", "d")
|
||||
"adc"
|
||||
iex> Regex.replace(%r/b/, "abc", "[&]")
|
||||
iex> Regex.replace(~r/b/, "abc", "[&]")
|
||||
"a[b]c"
|
||||
iex> Regex.replace(%r/b/, "abc", "[\\&]")
|
||||
iex> Regex.replace(~r/b/, "abc", "[\\&]")
|
||||
"a[&]c"
|
||||
iex> Regex.replace(%r/(b)/, "abc", "[\\1]")
|
||||
iex> Regex.replace(~r/(b)/, "abc", "[\\1]")
|
||||
"a[b]c"
|
||||
|
||||
"""
|
||||
def replace(regex(re_pattern: compiled), string, replacement, options // []) do
|
||||
def replace(regex(re_pattern: compiled), string, replacement, options \\ []) do
|
||||
opts = if Keyword.get(options, :global) != false, do: [:global], else: []
|
||||
return = Keyword.get(options, :return, return_for(string))
|
||||
opts = [{ :return, return }|opts]
|
||||
:re.replace(string, compiled, replacement, opts)
|
||||
end
|
||||
|
||||
{ :ok, pattern } = :re.compile(%S"[.^$*+?()[{\\\|\s#]", [:unicode])
|
||||
{ :ok, pattern } = :re.compile(~S"[.^$*+?()[{\\\|\s#]", [:unicode])
|
||||
@escape_pattern pattern
|
||||
|
||||
@doc %S"""
|
||||
@doc ~S"""
|
||||
Escapes a string to be literally matched in a regex.
|
||||
|
||||
## Examples
|
||||
@@ -346,7 +358,7 @@ defmodule Regex do
|
||||
"\\\\what\\ if"
|
||||
|
||||
"""
|
||||
@spec escape(String.t | char_list) :: String.t | char_list
|
||||
@spec escape(String.t) :: String.t
|
||||
def escape(string) do
|
||||
:re.replace(string, @escape_pattern, "\\\\&", [:global, { :return, return_for(string) }])
|
||||
end
|
||||
@@ -366,7 +378,10 @@ defmodule Regex do
|
||||
# Private Helpers
|
||||
|
||||
defp return_for(element) when is_binary(element), do: :binary
|
||||
defp return_for(element) when is_list(element), do: :list
|
||||
defp return_for(element) when is_list(element) do
|
||||
IO.write :stderr, "Passing char lists to Regex is deprecated, please use binaries instead\n#{Exception.format_stacktrace}"
|
||||
:list
|
||||
end
|
||||
|
||||
defp translate_options(<<?u, t :: binary>>) do
|
||||
IO.write "The /u flag for regular expressions is no longer needed\n#{Exception.format_stacktrace}"
|
||||
@@ -382,7 +397,7 @@ defmodule Regex do
|
||||
defp translate_options(<<>>), do: []
|
||||
defp translate_options(rest), do: { :error, rest }
|
||||
|
||||
{ :ok, pattern } = :re.compile(%S"\(\?<(?<G>[^>]*)>")
|
||||
{ :ok, pattern } = :re.compile(~S"\(\?<(?<G>[^>]*)>")
|
||||
@groups_pattern pattern
|
||||
|
||||
defp parse_groups(source) do
|
||||
|
||||
+112
-17
@@ -1,5 +1,5 @@
|
||||
defmodule Set do
|
||||
@moduledoc %S"""
|
||||
@moduledoc ~S"""
|
||||
This module specifies the Set API expected to be
|
||||
implemented by different representations.
|
||||
|
||||
@@ -38,6 +38,9 @@ defmodule Set do
|
||||
@type values :: [ value ]
|
||||
@type t :: tuple
|
||||
|
||||
defcallback new :: t
|
||||
defcallback new(Enum.t) :: t
|
||||
defcallback new(Enum.t, (any -> any)) :: t
|
||||
defcallback delete(t, value) :: t
|
||||
defcallback difference(t, t) :: t
|
||||
defcallback disjoint?(t, t) :: boolean
|
||||
@@ -46,6 +49,7 @@ defmodule Set do
|
||||
defcallback intersection(t, t) :: t
|
||||
defcallback member?(t, value) :: boolean
|
||||
defcallback put(t, value) :: t
|
||||
defcallback reduce(t, Enumerable.acc, Enumerable.reducer) :: Enumerable.result
|
||||
defcallback size(t) :: non_neg_integer
|
||||
defcallback subset?(t, t) :: boolean
|
||||
defcallback to_list(t) :: list()
|
||||
@@ -67,11 +71,11 @@ defmodule Set do
|
||||
## Examples
|
||||
|
||||
iex> s = HashSet.new([1, 2, 3])
|
||||
...> Set.delete(s, 4) |> HashSet.to_list
|
||||
...> Set.delete(s, 4) |> Enum.sort
|
||||
[1, 2, 3]
|
||||
|
||||
iex> s = HashSet.new([1, 2, 3])
|
||||
...> Set.delete(s, 2) |> HashSet.to_list
|
||||
...> Set.delete(s, 2) |> Enum.sort
|
||||
[1, 3]
|
||||
"""
|
||||
@spec delete(t, value) :: t
|
||||
@@ -82,20 +86,37 @@ defmodule Set do
|
||||
@doc """
|
||||
Returns a set that is `set1` without the members of `set2`.
|
||||
|
||||
Notice this function is polymorphic as it calculates the difference
|
||||
for of any type. Each set implementation also provides a `difference`
|
||||
function, but they can only work with sets of the same type.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Set.difference(HashSet.new([1,2]), HashSet.new([2,3,4])) |> HashSet.to_list
|
||||
iex> Set.difference(HashSet.new([1,2]), HashSet.new([2,3,4])) |> Enum.sort
|
||||
[1]
|
||||
|
||||
"""
|
||||
@spec difference(t, t) :: t
|
||||
def difference(set1, set2) do
|
||||
target(set1).difference(set1, set2)
|
||||
target1 = target(set1)
|
||||
target2 = target(set2)
|
||||
|
||||
if target1 == target2 do
|
||||
target1.difference(set1, set2)
|
||||
else
|
||||
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.
|
||||
|
||||
Notice this function is polymorphic as it checks for disjoint sets of
|
||||
any type. Each set implementation also provides a `disjoint?` function,
|
||||
but they can only work with sets of the same type.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Set.disjoint?(HashSet.new([1, 2]), HashSet.new([3, 4]))
|
||||
@@ -106,7 +127,19 @@ defmodule Set do
|
||||
"""
|
||||
@spec disjoint?(t, t) :: boolean
|
||||
def disjoint?(set1, set2) do
|
||||
target(set1).disjoint?(set1, set2)
|
||||
target1 = target(set1)
|
||||
target2 = target(set2)
|
||||
|
||||
if target1 == target2 do
|
||||
target1.disjoint?(set1, set2)
|
||||
else
|
||||
target2.reduce(set2, { :cont, true }, fn member, acc ->
|
||||
case target1.member?(set1, member) do
|
||||
false -> { :cont, acc }
|
||||
_ -> { :halt, false }
|
||||
end
|
||||
end) |> elem(1)
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -118,7 +151,11 @@ defmodule Set do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Checks if `set1` and `set2` are equal.
|
||||
Check if two sets are equal using `===`.
|
||||
|
||||
Notice this function is polymorphic as it compares sets of
|
||||
any type. Each set implementation also provides an `equal?`
|
||||
function, but they can only work with sets of the same type.
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -131,24 +168,49 @@ defmodule Set do
|
||||
"""
|
||||
@spec equal?(t, t) :: boolean
|
||||
def equal?(set1, set2) do
|
||||
target(set1).equal?(set1, set2)
|
||||
target1 = target(set1)
|
||||
target2 = target(set2)
|
||||
|
||||
cond do
|
||||
target1 == target2 ->
|
||||
target1.equal?(set1, set2)
|
||||
|
||||
target1.size(set1) == target2.size(set2) ->
|
||||
do_subset?(target1, target2, set1, set2)
|
||||
|
||||
true ->
|
||||
false
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns a set containing only members in common between `set1` and `set2`.
|
||||
|
||||
Notice this function is polymorphic as it calculates the intersection of
|
||||
any type. Each set implementation also provides a `intersection` function,
|
||||
but they can only work with sets of the same type.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Set.intersection(HashSet.new([1,2]), HashSet.new([2,3,4])) |> HashSet.to_list
|
||||
iex> Set.intersection(HashSet.new([1,2]), HashSet.new([2,3,4])) |> Enum.sort
|
||||
[2]
|
||||
|
||||
iex> Set.intersection(HashSet.new([1,2]), HashSet.new([3,4])) |> HashSet.to_list
|
||||
iex> Set.intersection(HashSet.new([1,2]), HashSet.new([3,4])) |> Enum.sort
|
||||
[]
|
||||
|
||||
"""
|
||||
@spec intersection(t, t) :: t
|
||||
def intersection(set1, set2) do
|
||||
target(set1).intersection(set1, set2)
|
||||
target1 = target(set1)
|
||||
target2 = target(set2)
|
||||
|
||||
if target1 == target2 do
|
||||
target1.intersection(set1, set2)
|
||||
else
|
||||
target1.reduce(set1, { :cont, target1.empty(set1) }, fn v, acc ->
|
||||
{ :cont, if(target2.member?(set2, v), do: target1.put(acc, v), else: acc) }
|
||||
end) |> elem(1)
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -173,10 +235,10 @@ defmodule Set do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Set.put(HashSet.new([1, 2, 3]), 3) |> Set.to_list
|
||||
iex> Set.put(HashSet.new([1, 2, 3]), 3) |> Enum.sort
|
||||
[1, 2, 3]
|
||||
|
||||
iex> Set.put(HashSet.new([1, 2, 3]), 4) |> Set.to_list
|
||||
iex> Set.put(HashSet.new([1, 2, 3]), 4) |> Enum.sort
|
||||
[1, 2, 3, 4]
|
||||
|
||||
"""
|
||||
@@ -202,6 +264,10 @@ defmodule Set do
|
||||
@doc """
|
||||
Checks if `set1`'s members are all contained in `set2`.
|
||||
|
||||
Notice this function is polymorphic as it checks the subset for
|
||||
any type. Each set implementation also provides a `subset?` function,
|
||||
but they can only work with sets of the same type.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Set.subset?(HashSet.new([1, 2]), HashSet.new([1, 2, 3]))
|
||||
@@ -211,7 +277,14 @@ defmodule Set do
|
||||
"""
|
||||
@spec subset?(t, t) :: boolean
|
||||
def subset?(set1, set2) do
|
||||
target(set1).subset?(set1, set2)
|
||||
target1 = target(set1)
|
||||
target2 = target(set2)
|
||||
|
||||
if target1 == target2 do
|
||||
target1.subset?(set1, set2)
|
||||
else
|
||||
do_subset?(target1, target2, set1, set2)
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -219,7 +292,7 @@ defmodule Set do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> HashSet.to_list(HashSet.new([1, 2, 3]))
|
||||
iex> HashSet.to_list(HashSet.new([1, 2, 3])) |> Enum.sort
|
||||
[1,2,3]
|
||||
|
||||
"""
|
||||
@@ -231,15 +304,37 @@ defmodule Set do
|
||||
@doc """
|
||||
Returns a set containing all members of `set1` and `set2`.
|
||||
|
||||
Notice this function is polymorphic as it calculates the union of
|
||||
any type. Each set implementation also provides a `union` function,
|
||||
but they can only work with sets of the same type.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Set.union(HashSet.new([1,2]), HashSet.new([2,3,4])) |> HashSet.to_list
|
||||
iex> Set.union(HashSet.new([1,2]), HashSet.new([2,3,4])) |> Enum.sort
|
||||
[1,2,3,4]
|
||||
|
||||
"""
|
||||
@spec union(t, t) :: t
|
||||
def union(set1, set2) do
|
||||
target(set1).union(set1, set2)
|
||||
target1 = target(set1)
|
||||
target2 = target(set2)
|
||||
|
||||
if target1 == target2 do
|
||||
target1.union(set1, set2)
|
||||
else
|
||||
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
|
||||
target1.reduce(set1, { :cont, true }, fn member, acc ->
|
||||
case target2.member?(set2, member) do
|
||||
true -> { :cont, acc }
|
||||
_ -> { :halt, false }
|
||||
end
|
||||
end) |> elem(1)
|
||||
end
|
||||
|
||||
defp unsupported_set(set) do
|
||||
|
||||
+12
-32
@@ -173,25 +173,17 @@ defmodule Stream do
|
||||
|
||||
## Transformers
|
||||
|
||||
@doc """
|
||||
Executes the given function when the stream is done, halted
|
||||
or an error happened during streaming. Useful for resource
|
||||
clean up.
|
||||
|
||||
Callbacks registered later will be executed earlier.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> stream = Stream.after [1,2,3], fn -> Process.put(:done, true) end
|
||||
iex> Enum.to_list(stream)
|
||||
[1,2,3]
|
||||
iex> Process.get(:done)
|
||||
true
|
||||
|
||||
"""
|
||||
@doc false
|
||||
@spec unquote(:after)(Enumerable.t, (() -> term)) :: Enumerable.t
|
||||
def unquote(:after)(Lazy[after: funs] = lazy, fun), do: lazy.after([fun|funs])
|
||||
def unquote(:after)(enum, fun), do: Lazy[enum: enum, after: [fun]]
|
||||
def unquote(:after)(Lazy[after: funs] = lazy, fun) do
|
||||
IO.write :stderr, "Stream.after/1 is deprecated and will be removed/1\n#{Exception.format_stacktrace}"
|
||||
lazy.after([fun|funs])
|
||||
end
|
||||
|
||||
def unquote(:after)(enum, fun) do
|
||||
IO.write :stderr, "Stream.after/1 is deprecated and will be removed/1\n#{Exception.format_stacktrace}"
|
||||
Lazy[enum: enum, after: [fun]]
|
||||
end
|
||||
|
||||
@doc """
|
||||
Shortcut to `chunk(enum, n, n)`.
|
||||
@@ -226,7 +218,7 @@ defmodule Stream do
|
||||
"""
|
||||
@spec chunk(Enumerable.t, non_neg_integer, non_neg_integer) :: Enumerable.t
|
||||
@spec chunk(Enumerable.t, non_neg_integer, non_neg_integer, Enumerable.t | nil) :: Enumerable.t
|
||||
def chunk(enum, n, step, pad // nil) when n > 0 and step > 0 do
|
||||
def chunk(enum, n, step, pad \\ nil) when n > 0 and step > 0 do
|
||||
limit = :erlang.max(n, step)
|
||||
lazy enum, { [], 0 },
|
||||
fn(f1) -> R.chunk(n, step, limit, f1) end,
|
||||
@@ -447,18 +439,6 @@ defmodule Stream do
|
||||
|
||||
This is useful when a stream needs to be run, for side effects,
|
||||
and there is no interest in its return result.
|
||||
|
||||
## Examples
|
||||
|
||||
Open up a file, replace all `#` by `%` and stream to another file
|
||||
without loading the whole file in memory:
|
||||
|
||||
stream = File.stream!("code")
|
||||
|> Stream.map(&String.replace(&1, "#", "%"))
|
||||
|> File.stream_to!("new")
|
||||
|
||||
No computation will be done until we call one of the Enum functions
|
||||
or `Stream.run/1`.
|
||||
"""
|
||||
@spec run(Enumerable.t) :: :ok
|
||||
def run(stream) do
|
||||
@@ -729,7 +709,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
|
||||
def uniq(enum, fun \\ fn x -> x end) do
|
||||
lazy enum, [], fn f1 -> R.uniq(fun, f1) end
|
||||
end
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ defmodule Stream.Reducers do
|
||||
# Collection of reducers shared by Enum and Stream.
|
||||
@moduledoc false
|
||||
|
||||
defmacro chunk(n, step, limit, f // nil) do
|
||||
defmacro chunk(n, step, limit, f \\ nil) do
|
||||
quote do
|
||||
fn entry, acc(h, { buffer, count }, t) ->
|
||||
buffer = [entry|buffer]
|
||||
@@ -25,7 +25,7 @@ defmodule Stream.Reducers do
|
||||
end
|
||||
end
|
||||
|
||||
defmacro chunk_by(callback, f // nil) do
|
||||
defmacro chunk_by(callback, f \\ nil) do
|
||||
quote do
|
||||
fn
|
||||
entry, acc(h, { buffer, value }, t) ->
|
||||
@@ -41,7 +41,7 @@ defmodule Stream.Reducers do
|
||||
end
|
||||
end
|
||||
|
||||
defmacro drop(f // nil) do
|
||||
defmacro drop(f \\ nil) do
|
||||
quote do
|
||||
fn
|
||||
_entry, acc(h, n, t) when n > 0 ->
|
||||
@@ -52,7 +52,7 @@ defmodule Stream.Reducers do
|
||||
end
|
||||
end
|
||||
|
||||
defmacro drop_while(callback, f // nil) do
|
||||
defmacro drop_while(callback, f \\ nil) do
|
||||
quote do
|
||||
fn entry, acc(h, bool, t) = orig ->
|
||||
if bool and unquote(callback).(entry) do
|
||||
@@ -64,7 +64,7 @@ defmodule Stream.Reducers do
|
||||
end
|
||||
end
|
||||
|
||||
defmacro filter(callback, f // nil) do
|
||||
defmacro filter(callback, f \\ nil) do
|
||||
quote do
|
||||
fn(entry, acc) ->
|
||||
if unquote(callback).(entry) do
|
||||
@@ -76,7 +76,7 @@ defmodule Stream.Reducers do
|
||||
end
|
||||
end
|
||||
|
||||
defmacro filter_map(filter, mapper, f // nil) do
|
||||
defmacro filter_map(filter, mapper, f \\ nil) do
|
||||
quote do
|
||||
fn(entry, acc) ->
|
||||
if unquote(filter).(entry) do
|
||||
@@ -88,7 +88,7 @@ defmodule Stream.Reducers do
|
||||
end
|
||||
end
|
||||
|
||||
defmacro map(callback, f // nil) do
|
||||
defmacro map(callback, f \\ nil) do
|
||||
quote do
|
||||
fn(entry, acc) ->
|
||||
cont(unquote(f), unquote(callback).(entry), acc)
|
||||
@@ -96,7 +96,7 @@ defmodule Stream.Reducers do
|
||||
end
|
||||
end
|
||||
|
||||
defmacro reject(callback, f // nil) do
|
||||
defmacro reject(callback, f \\ nil) do
|
||||
quote do
|
||||
fn(entry, acc) ->
|
||||
unless unquote(callback).(entry) do
|
||||
@@ -108,7 +108,7 @@ defmodule Stream.Reducers do
|
||||
end
|
||||
end
|
||||
|
||||
defmacro scan_2(callback, f // nil) do
|
||||
defmacro scan_2(callback, f \\ nil) do
|
||||
quote do
|
||||
fn
|
||||
entry, acc(h, :first, t) ->
|
||||
@@ -120,7 +120,7 @@ defmodule Stream.Reducers do
|
||||
end
|
||||
end
|
||||
|
||||
defmacro scan_3(callback, f // nil) do
|
||||
defmacro scan_3(callback, f \\ nil) do
|
||||
quote do
|
||||
fn(entry, acc(h, acc, t)) ->
|
||||
value = unquote(callback).(entry, acc)
|
||||
@@ -129,7 +129,7 @@ defmodule Stream.Reducers do
|
||||
end
|
||||
end
|
||||
|
||||
defmacro take(f // nil) do
|
||||
defmacro take(f \\ nil) do
|
||||
quote do
|
||||
fn(entry, acc(h, n, t) = orig) ->
|
||||
if n >= 1 do
|
||||
@@ -141,7 +141,7 @@ defmodule Stream.Reducers do
|
||||
end
|
||||
end
|
||||
|
||||
defmacro take_every(nth, f // nil) do
|
||||
defmacro take_every(nth, f \\ nil) do
|
||||
quote do
|
||||
fn
|
||||
entry, acc(h, n, t) when n === :first
|
||||
@@ -153,7 +153,7 @@ defmodule Stream.Reducers do
|
||||
end
|
||||
end
|
||||
|
||||
defmacro take_while(callback, f // nil) do
|
||||
defmacro take_while(callback, f \\ nil) do
|
||||
quote do
|
||||
fn(entry, acc) ->
|
||||
if unquote(callback).(entry) do
|
||||
@@ -165,7 +165,7 @@ defmodule Stream.Reducers do
|
||||
end
|
||||
end
|
||||
|
||||
defmacro uniq(callback, f // nil) do
|
||||
defmacro uniq(callback, f \\ nil) do
|
||||
quote do
|
||||
fn(entry, acc(h, prev, t) = acc) ->
|
||||
value = unquote(callback).(entry)
|
||||
@@ -178,7 +178,7 @@ defmodule Stream.Reducers do
|
||||
end
|
||||
end
|
||||
|
||||
defmacro with_index(f // nil) do
|
||||
defmacro with_index(f \\ nil) do
|
||||
quote do
|
||||
fn(entry, acc(h, counter, t)) ->
|
||||
cont_with_acc(unquote(f), { entry, counter }, h, counter + 1, t)
|
||||
|
||||
+58
-41
@@ -1,7 +1,7 @@
|
||||
import Kernel, except: [length: 1]
|
||||
|
||||
defmodule String do
|
||||
@moduledoc %S"""
|
||||
@moduledoc ~S"""
|
||||
A String in Elixir is a UTF-8 encoded binary.
|
||||
|
||||
## String and binary operations
|
||||
@@ -168,7 +168,7 @@ defmodule String do
|
||||
@spec split(t) :: [t]
|
||||
defdelegate split(binary), to: String.Unicode
|
||||
|
||||
@doc %S"""
|
||||
@doc ~S"""
|
||||
Divides a string into substrings based on a pattern,
|
||||
returning a list of these substrings. The pattern can
|
||||
be a string, a list of strings or a regular expression.
|
||||
@@ -197,16 +197,16 @@ defmodule String do
|
||||
|
||||
A regular expression:
|
||||
|
||||
iex> String.split("a,b,c", %r{,})
|
||||
iex> String.split("a,b,c", ~r{,})
|
||||
["a", "b", "c"]
|
||||
iex> String.split("a,b,c", %r{,}, global: false)
|
||||
iex> String.split("a,b,c", ~r{,}, global: false)
|
||||
["a", "b,c"]
|
||||
iex> String.split(" a b c ", %r{\s}, trim: true)
|
||||
iex> String.split(" a b c ", ~r{\s}, trim: true)
|
||||
["a", "b", "c"]
|
||||
|
||||
Splitting on empty patterns returns codepoints:
|
||||
|
||||
iex> String.split("abc", %r{})
|
||||
iex> String.split("abc", ~r{})
|
||||
["a", "b", "c", ""]
|
||||
iex> String.split("abc", "")
|
||||
["a", "b", "c", ""]
|
||||
@@ -218,24 +218,24 @@ defmodule String do
|
||||
"""
|
||||
@spec split(t, t | [t] | Regex.t) :: [t]
|
||||
@spec split(t, t | [t] | Regex.t, Keyword.t) :: [t]
|
||||
def split(binary, pattern, options // [])
|
||||
def split(binary, pattern, options \\ [])
|
||||
|
||||
def split("", _pattern, _options), do: [""]
|
||||
|
||||
def split(binary, "", options), do: split(binary, %r"", options)
|
||||
|
||||
def split(binary, pattern, options) when is_regex(pattern) do
|
||||
Regex.split(pattern, binary, options)
|
||||
end
|
||||
def split(binary, "", options), do: split(binary, ~r"", options)
|
||||
|
||||
def split(binary, pattern, options) do
|
||||
opts = if options[:global] != false, do: [:global], else: []
|
||||
splits = :binary.split(binary, pattern, opts)
|
||||
|
||||
if Keyword.get(options, :trim, false) do
|
||||
lc split inlist splits, split != "", do: split
|
||||
if Regex.regex?(pattern) do
|
||||
Regex.split(pattern, binary, options)
|
||||
else
|
||||
splits
|
||||
opts = if options[:global] != false, do: [:global], else: []
|
||||
splits = :binary.split(binary, pattern, opts)
|
||||
|
||||
if Keyword.get(options, :trim, false) do
|
||||
lc split inlist splits, split != "", do: split
|
||||
else
|
||||
splits
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -333,7 +333,7 @@ defmodule String do
|
||||
end
|
||||
end
|
||||
|
||||
def rstrip(string, char) do
|
||||
def rstrip(string, char) when is_integer(char) do
|
||||
do_rstrip(string, "", char)
|
||||
end
|
||||
|
||||
@@ -373,11 +373,11 @@ defmodule String do
|
||||
|
||||
@spec lstrip(t, char) :: t
|
||||
|
||||
def lstrip(<<char :: utf8, rest :: binary>>, char) do
|
||||
def lstrip(<<char :: utf8, rest :: binary>>, char) when is_integer(char) do
|
||||
<<lstrip(rest, char) :: binary>>
|
||||
end
|
||||
|
||||
def lstrip(other, _char) do
|
||||
def lstrip(other, char) when is_integer(char) do
|
||||
other
|
||||
end
|
||||
|
||||
@@ -413,7 +413,7 @@ defmodule String do
|
||||
rstrip(lstrip(string, char), char)
|
||||
end
|
||||
|
||||
@doc %S"""
|
||||
@doc ~S"""
|
||||
Returns a new string of length `len` with `subject` right justified and
|
||||
padded with `padding`. If `padding` is not present, it defaults to
|
||||
whitespace. When `len` is less than the length of `subject`, `subject` is
|
||||
@@ -438,7 +438,7 @@ defmodule String do
|
||||
do_justify(subject, len, padding, :right)
|
||||
end
|
||||
|
||||
@doc %S"""
|
||||
@doc ~S"""
|
||||
Returns a new string of length `len` with `subject` left justified and padded
|
||||
with `padding`. If `padding` is not present, it defaults to whitespace. When
|
||||
`len` is less than the length of `subject`, `subject` is returned.
|
||||
@@ -482,7 +482,7 @@ defmodule String do
|
||||
end
|
||||
end
|
||||
|
||||
@doc %S"""
|
||||
@doc ~S"""
|
||||
Returns a new binary based on `subject` by replacing the parts
|
||||
matching `pattern` by `replacement`. By default, it replaces
|
||||
all entries, except if the `global` option is set to `false`.
|
||||
@@ -499,7 +499,7 @@ defmodule String do
|
||||
The pattern can also be a regex. In those cases, one can give `\N`
|
||||
in the `replacement` string to access a specific capture in the regex:
|
||||
|
||||
iex> String.replace("a,b,c", %r/,(.)/, ",\\1\\1")
|
||||
iex> String.replace("a,b,c", ~r/,(.)/, ",\\1\\1")
|
||||
"a,bb,cc"
|
||||
|
||||
Notice we had to escape the escape character `\`. By giving `&`,
|
||||
@@ -519,15 +519,15 @@ defmodule String do
|
||||
@spec replace(t, t, t) :: t
|
||||
@spec replace(t, t, t, Keyword.t) :: t
|
||||
|
||||
def replace(subject, pattern, replacement, options // [])
|
||||
|
||||
def replace(subject, pattern, replacement, options) when is_regex(pattern) do
|
||||
Regex.replace(pattern, subject, replacement, global: options[:global])
|
||||
end
|
||||
def replace(subject, pattern, replacement, options \\ [])
|
||||
|
||||
def replace(subject, pattern, replacement, options) do
|
||||
opts = translate_replace_options(options)
|
||||
:binary.replace(subject, pattern, replacement, opts)
|
||||
if Regex.regex?(pattern) do
|
||||
Regex.replace(pattern, subject, replacement, global: options[:global])
|
||||
else
|
||||
opts = translate_replace_options(options)
|
||||
:binary.replace(subject, pattern, replacement, opts)
|
||||
end
|
||||
end
|
||||
|
||||
defp translate_replace_options(options) do
|
||||
@@ -620,7 +620,7 @@ defmodule String do
|
||||
@spec next_codepoint(t) :: {codepoint, t} | nil
|
||||
defdelegate next_codepoint(string), to: String.Unicode
|
||||
|
||||
@doc %S"""
|
||||
@doc ~S"""
|
||||
Checks whether `str` contains only valid characters.
|
||||
|
||||
## Examples
|
||||
@@ -651,7 +651,7 @@ defmodule String do
|
||||
def valid?(<<>>), do: true
|
||||
def valid?(_), do: false
|
||||
|
||||
@doc %S"""
|
||||
@doc ~S"""
|
||||
Checks whether `str` is a valid character.
|
||||
|
||||
All characters are codepoints, but some codepoints
|
||||
@@ -913,7 +913,7 @@ defmodule String do
|
||||
last = total + last
|
||||
end
|
||||
|
||||
if first > 0 do
|
||||
if first >= 0 do
|
||||
do_slice(next_grapheme(string), first, last, 0, "")
|
||||
end
|
||||
end
|
||||
@@ -974,7 +974,7 @@ defmodule String do
|
||||
end
|
||||
|
||||
defp do_starts_with(string, prefix) when is_binary(prefix) do
|
||||
match?({0, _}, :binary.match(string, prefix))
|
||||
Kernel.match?({0, _}, :binary.match(string, prefix))
|
||||
end
|
||||
|
||||
defp do_starts_with(_, _) do
|
||||
@@ -1021,7 +1021,24 @@ defmodule String do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns `true` if `string` contains match, otherwise `false`.
|
||||
Check if `string` matches the given regular expression.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> String.match?("foo", ~r/foo/)
|
||||
true
|
||||
iex> String.match?("bar", ~r/foo/)
|
||||
false
|
||||
|
||||
"""
|
||||
@spec match?(t, Regex.t) :: boolean
|
||||
def match?(string, regex) do
|
||||
Regex.match?(regex, string)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Check if `string` contains any of the given `contents`.
|
||||
|
||||
`matches` can be either a single string or a list of strings.
|
||||
|
||||
## Examples
|
||||
@@ -1036,12 +1053,12 @@ defmodule String do
|
||||
"""
|
||||
@spec contains?(t, t | [t]) :: boolean
|
||||
|
||||
def contains?(string, matches) when is_list(matches) do
|
||||
Enum.any?(matches, &do_contains(string, &1))
|
||||
def contains?(string, contents) when is_list(contents) do
|
||||
Enum.any?(contents, &do_contains(string, &1))
|
||||
end
|
||||
|
||||
def contains?(string, match) do
|
||||
do_contains(string, match)
|
||||
def contains?(string, content) do
|
||||
do_contains(string, content)
|
||||
end
|
||||
|
||||
defp do_contains(string, "") when is_binary(string) do
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import Kernel, except: [to_string: 1]
|
||||
|
||||
defprotocol String.Chars do
|
||||
@moduledoc %S"""
|
||||
@moduledoc ~S"""
|
||||
The String.Chars protocol is responsible for
|
||||
converting a structure to a Binary (only if applicable).
|
||||
The only function required to be implemented is
|
||||
|
||||
@@ -152,7 +152,7 @@ defmodule Supervisor.Behaviour do
|
||||
|
||||
#{@child_doc}
|
||||
"""
|
||||
def worker(module, args, options // []) do
|
||||
def worker(module, args, options \\ []) do
|
||||
child(:worker, module, args, options)
|
||||
end
|
||||
|
||||
@@ -166,7 +166,7 @@ defmodule Supervisor.Behaviour do
|
||||
|
||||
#{@child_doc}
|
||||
"""
|
||||
def supervisor(module, args, options // []) do
|
||||
def supervisor(module, args, options \\ []) do
|
||||
options = Keyword.update(options, :shutdown, :infinity, fn(x) -> x end)
|
||||
child(:supervisor, module, args, options)
|
||||
end
|
||||
|
||||
+13
-18
@@ -1,13 +1,4 @@
|
||||
defmodule System do
|
||||
defexception NoHomeError,
|
||||
message: "could not find the user home, please set the HOME environment variable"
|
||||
|
||||
defexception NoTmpDirError,
|
||||
message: "could not get a writable temporary directory, please set the TMPDIR environment variable"
|
||||
|
||||
defexception NoAccessCwdError,
|
||||
message: "could not get a current working directory, the current location is not accessible"
|
||||
|
||||
@moduledoc """
|
||||
The System module provides access to variables used or
|
||||
maintained by the VM and to functions that interact directly
|
||||
@@ -29,7 +20,7 @@ defmodule System do
|
||||
# Read and strip the version from the `VERSION` file.
|
||||
defmacrop get_version do
|
||||
case read_stripped(:filename.join(__DIR__, "../../../VERSION")) do
|
||||
"" -> raise ArgumentError, message: "could not read the version number from VERSION"
|
||||
"" -> raise RuntimeError, message: "could not read the version number from VERSION"
|
||||
data -> data
|
||||
end
|
||||
end
|
||||
@@ -111,10 +102,11 @@ defmodule System do
|
||||
@doc """
|
||||
Current working directory, exception on error.
|
||||
|
||||
Returns the current working directory or raises `System.NoAccessCwdError`.
|
||||
Returns the current working directory or raises `RuntimeError`.
|
||||
"""
|
||||
def cwd! do
|
||||
cwd || raise NoAccessCwdError
|
||||
cwd ||
|
||||
raise RuntimeError, message: "could not get a current working directory, the current location is not accessible"
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -133,11 +125,12 @@ defmodule System do
|
||||
@doc """
|
||||
User home directory, exception on error.
|
||||
|
||||
Same as `user_home/0` but raises `System.NoHomeError`
|
||||
Same as `user_home/0` but raises `RuntimeError`
|
||||
instead of returning `nil` if no user home is set.
|
||||
"""
|
||||
def user_home! do
|
||||
user_home || raise NoHomeError
|
||||
user_home ||
|
||||
raise RuntimeError, message: "could not find the user home, please set the HOME environment variable"
|
||||
end
|
||||
|
||||
defp get_unix_home do
|
||||
@@ -154,7 +147,7 @@ defmodule System do
|
||||
)
|
||||
end
|
||||
|
||||
@doc %S"""
|
||||
@doc ~S"""
|
||||
Writable temporary directory.
|
||||
|
||||
Returns a writable temporary directory.
|
||||
@@ -179,11 +172,13 @@ defmodule System do
|
||||
@doc """
|
||||
Writable temporary directory, exception on error.
|
||||
|
||||
Same as `tmp_dir/0` but raises `System.NoTmpDirError`
|
||||
Same as `tmp_dir/0` but raises `RuntimeError`
|
||||
instead of returning `nil` if no temp dir is set.
|
||||
"""
|
||||
def tmp_dir! do
|
||||
tmp_dir || raise NoTmpDirError
|
||||
tmp_dir ||
|
||||
raise RuntimeError, message: "could not get a writable temporary directory, " <>
|
||||
"please set the TMPDIR environment variable"
|
||||
end
|
||||
|
||||
defp write_env_tmp_dir(env) do
|
||||
@@ -376,7 +371,7 @@ defmodule System do
|
||||
"""
|
||||
@spec halt() :: no_return
|
||||
@spec halt(non_neg_integer | binary | :abort) :: no_return
|
||||
def halt(status // 0)
|
||||
def halt(status \\ 0)
|
||||
|
||||
def halt(status) when is_integer(status) or status == :abort do
|
||||
:erlang.halt(status)
|
||||
|
||||
+35
-15
@@ -75,8 +75,14 @@ defmodule URI do
|
||||
"""
|
||||
def encode_query(l), do: Enum.map_join(l, "&", &pair/1)
|
||||
|
||||
@doc false
|
||||
def decode_query(q) do
|
||||
IO.write :stderr, "URI.decode_query/1 is deprecated, please use URI.decode_query/2 with an explicit argument\n#{Exception.format_stacktrace}"
|
||||
decode_query(q, HashDict.new)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Decodes a query string into a `HashDict`.
|
||||
Decodes a query string into a dictionary.
|
||||
|
||||
Given a query string of the form "key1=value1&key2=value2...", produces a
|
||||
`HashDict` with one entry for each key-value pair. Each key and value will be a
|
||||
@@ -86,9 +92,6 @@ defmodule URI do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> URI.decode_query("foo=1&bar=2") |> Dict.to_list
|
||||
[{"bar", "2"}, {"foo", "1"}]
|
||||
|
||||
iex> hd = HashDict.new()
|
||||
iex> URI.decode_query("foo=1&bar=2", hd) |> HashDict.keys
|
||||
["bar", "foo"]
|
||||
@@ -96,7 +99,7 @@ defmodule URI do
|
||||
["2", "1"]
|
||||
|
||||
"""
|
||||
def decode_query(q, dict // HashDict.new) when is_binary(q) do
|
||||
def decode_query(q, dict) when is_binary(q) do
|
||||
Enum.reduce query_decoder(q), dict, fn({ k, v }, acc) -> Dict.put(acc, k, v) end
|
||||
end
|
||||
|
||||
@@ -134,6 +137,16 @@ defmodule URI do
|
||||
{ current, next }
|
||||
end
|
||||
|
||||
defp pair({k, v}) when is_list(k) do
|
||||
IO.write :stderr, "Passing list keys to URI.encode_query/1 is deprecated\n#{Exception.format_stacktrace}"
|
||||
encode(to_string(k)) <> "=" <> encode(to_string(v))
|
||||
end
|
||||
|
||||
defp pair({k, v}) when is_list(v) do
|
||||
IO.write :stderr, "Passing list values to URI.encode_query/1 is deprecated\n#{Exception.format_stacktrace}"
|
||||
encode(to_string(k)) <> "=" <> encode(to_string(v))
|
||||
end
|
||||
|
||||
defp pair({k, v}) do
|
||||
encode(to_string(k)) <> "=" <> encode(to_string(v))
|
||||
end
|
||||
@@ -175,19 +188,26 @@ defmodule URI do
|
||||
"http://elixir-lang.com"
|
||||
|
||||
"""
|
||||
def decode(<<?%, hex1, hex2, tail :: binary >>) do
|
||||
<< bsl(hex2dec(hex1), 4) + hex2dec(hex2) >> <> decode(tail)
|
||||
def decode(uri) do
|
||||
decode(uri, uri)
|
||||
end
|
||||
|
||||
def decode(<<head, tail :: binary >>) do
|
||||
<<check_plus(head)>> <> decode(tail)
|
||||
def decode(<<?%, hex1, hex2, tail :: binary >>, uri) do
|
||||
<< bsl(hex2dec(hex1, uri), 4) + hex2dec(hex2, uri) >> <> decode(tail, uri)
|
||||
end
|
||||
|
||||
def decode(<<>>), do: <<>>
|
||||
def decode(<<head, tail :: binary >>, uri) do
|
||||
<<check_plus(head)>> <> decode(tail, uri)
|
||||
end
|
||||
|
||||
defp hex2dec(n) when n in ?A..?F, do: n - ?A + 10
|
||||
defp hex2dec(n) when n in ?a..?f, do: n - ?a + 10
|
||||
defp hex2dec(n) when n in ?0..?9, do: n - ?0
|
||||
def decode(<<>>, _uri), do: <<>>
|
||||
|
||||
defp hex2dec(n, _uri) when n in ?A..?F, do: n - ?A + 10
|
||||
defp hex2dec(n, _uri) when n in ?a..?f, do: n - ?a + 10
|
||||
defp hex2dec(n, _uri) when n in ?0..?9, do: n - ?0
|
||||
defp hex2dec(_n, uri) do
|
||||
raise ArgumentError, message: "malformed URI #{inspect uri}"
|
||||
end
|
||||
|
||||
defp check_plus(?+), do: 32
|
||||
defp check_plus(c), do: c
|
||||
@@ -210,7 +230,7 @@ defmodule URI do
|
||||
"""
|
||||
def parse(s) when is_binary(s) do
|
||||
# From http://tools.ietf.org/html/rfc3986#appendix-B
|
||||
regex = %r/^(([^:\/?#]+):)?(\/\/([^\/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?/
|
||||
regex = ~r/^(([^:\/?#]+):)?(\/\/([^\/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?/
|
||||
parts = nillify(Regex.run(regex, s))
|
||||
|
||||
destructure [_, _, scheme, _, authority, path, _, query, _, fragment], parts
|
||||
@@ -240,7 +260,7 @@ defmodule URI do
|
||||
# Split an authority into its userinfo, host and port parts.
|
||||
defp split_authority(s) do
|
||||
s = s || ""
|
||||
components = Regex.run %r/(^(.*)@)?(\[[a-zA-Z0-9:.]*\]|[^:]*)(:(\d*))?/, s
|
||||
components = Regex.run ~r/(^(.*)@)?(\[[a-zA-Z0-9:.]*\]|[^:]*)(:(\d*))?/, s
|
||||
|
||||
destructure [_, _, userinfo, host, _, port], nillify(components)
|
||||
port = if port, do: binary_to_integer(port)
|
||||
|
||||
+189
-215
@@ -1,17 +1,12 @@
|
||||
defmodule Version do
|
||||
@moduledoc %S"""
|
||||
@moduledoc ~S"""
|
||||
Functions for parsing and matching versions against requirements.
|
||||
|
||||
A version is a string in a specific format or a `Version.Schema`
|
||||
generated after parsing via `Version.parse/1`.
|
||||
|
||||
`Version` parsing and requirements follow
|
||||
[SemVer 2.0 schema](http://semver.org/) and you will get
|
||||
the most of Mix's version system by following it. In order
|
||||
to support integration with projects that may
|
||||
follow different versioning schemas, Elixir won't choke
|
||||
on unknown versions, however you won't be able to use
|
||||
Mix requirements with such unformatted versions.
|
||||
[SemVer 2.0 schema](http://semver.org/).
|
||||
|
||||
## Versions
|
||||
|
||||
@@ -54,7 +49,6 @@ defmodule Version do
|
||||
|
||||
@type t :: String.t | Version.Schema.t
|
||||
@type requirement :: String.t | Version.Requirement.t
|
||||
|
||||
@type matchable :: { major :: String.t | non_neg_integer,
|
||||
minor :: non_neg_integer | nil,
|
||||
patch :: non_neg_integer | nil,
|
||||
@@ -62,98 +56,147 @@ defmodule Version do
|
||||
|
||||
import Kernel, except: [match?: 2]
|
||||
|
||||
defrecord Schema, major: 0, minor: 0, patch: 0, pre: nil, build: nil, source: nil
|
||||
defrecord Requirement, source: nil, matchspec: nil
|
||||
defrecord Schema, [:major, :minor, :patch, :pre, :build, :source]
|
||||
defrecord Requirement, [:source, :matchspec]
|
||||
|
||||
defexception InvalidRequirement, [:message] do
|
||||
def exception(opts) do
|
||||
message =
|
||||
if is_binary opts[:reason] do
|
||||
{ first, rest } = String.next_grapheme(opts[:reason])
|
||||
String.downcase(first) <> rest
|
||||
else
|
||||
"invalid version specification"
|
||||
end
|
||||
|
||||
InvalidRequirement[message: message]
|
||||
end
|
||||
end
|
||||
defexception InvalidRequirement, [:message]
|
||||
defexception InvalidVersion, [:message]
|
||||
|
||||
@doc """
|
||||
Check if the given version matches the specification.
|
||||
|
||||
Returns `true` if `version` satisfies `requirement`, `false` otherwise.
|
||||
Raises a `Version.InvalidRequirement` exception if `requirement` is not parseable.
|
||||
Raises a `Version.InvalidRequirement` exception if `requirement` is not
|
||||
parseable, or `Version.InvalidVersion` if `version` is not parseable.
|
||||
If given an already parsed version and requirement this function won't
|
||||
raise.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Version.match?("2.0", ">1.0")
|
||||
iex> Version.match?("2.0.0", ">1.0.0")
|
||||
true
|
||||
iex> Version.match?("2.0", "==1.0")
|
||||
|
||||
iex> Version.match?("2.0.0", "==1.0.0")
|
||||
false
|
||||
|
||||
iex> Version.match?("foo", "==1.0.0")
|
||||
** (Version.InvalidVersion) foo
|
||||
|
||||
iex> Version.match?("2.0.0", "== ==1.0.0")
|
||||
** (Version.InvalidRequirement) == ==1.0.0
|
||||
|
||||
"""
|
||||
@spec match?(t, requirement) :: boolean
|
||||
def match?(version, requirement) when is_binary(requirement) do
|
||||
case Version.Parser.parse_requirement(requirement) do
|
||||
def match?(vsn, req) when is_binary(req) do
|
||||
case parse_requirement(req) do
|
||||
{ :ok, req } ->
|
||||
match?(version, req)
|
||||
{ :error, reason } ->
|
||||
raise InvalidRequirement, reason: reason
|
||||
match?(vsn, req)
|
||||
:error ->
|
||||
raise InvalidRequirement, message: req
|
||||
end
|
||||
end
|
||||
|
||||
def match?(version, requirement) when is_binary(version) do
|
||||
match?(parse(version), requirement)
|
||||
end
|
||||
|
||||
def match?(Schema[] = version, Requirement[matchspec: spec]) do
|
||||
case :ets.test_ms(to_matchable(version), spec) do
|
||||
{ :ok, result } ->
|
||||
result != false
|
||||
{ :error, reason } ->
|
||||
raise InvalidRequirement, reason: reason
|
||||
end
|
||||
def match?(version, Requirement[matchspec: spec]) do
|
||||
{ :ok, result } = :ets.test_ms(to_matchable(version), spec)
|
||||
result != false
|
||||
end
|
||||
|
||||
@doc """
|
||||
Check if a version string is compatible with [semver](http://semver.org/).
|
||||
Compares two versions. Returns `:gt` if first version is greater than
|
||||
the second and `:lt` for vice versa. If the two versions are equal `:eq`
|
||||
is returned
|
||||
|
||||
Raises a `Version.InvalidVersion` exception if `version` is not parseable.
|
||||
If given an already parsed version this function won't raise.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Version.valid?("2.0")
|
||||
true
|
||||
iex> Version.valid?("invalid")
|
||||
false
|
||||
iex> Version.compare("2.0.1-alpha1", "2.0.0")
|
||||
:gt
|
||||
|
||||
iex> Version.compare("2.0.1+build0", "2.0.1")
|
||||
:eq
|
||||
|
||||
iex> Version.compare("invalid", "2.0.1")
|
||||
** (Version.InvalidVersion) invalid
|
||||
|
||||
"""
|
||||
@spec valid?(String.t | Schema.t) :: boolean
|
||||
def valid?(string) when is_binary(string) do
|
||||
Version.Parser.valid_version?(string)
|
||||
@spec compare(String.t | Schema.t, String.t | Schema.t) :: :gt | :eq | :lt
|
||||
def compare(vsn1, vsn2) do
|
||||
do_compare(to_matchable(vsn1), to_matchable(vsn2))
|
||||
end
|
||||
|
||||
def valid?(Version.Schema[major: nil]), do: false
|
||||
def valid?(Version.Schema[]), do: true
|
||||
defp do_compare({ major1, minor1, patch1, pre1 }, { major2, minor2, patch2, pre2 }) do
|
||||
cond do
|
||||
{ major1, minor1, patch1 } > { major2, minor2, patch2 } -> :gt
|
||||
{ major1, minor1, patch1 } < { major2, minor2, patch2 } -> :lt
|
||||
pre1 == [] and pre2 != [] -> :gt
|
||||
pre1 != [] and pre2 == [] -> :lt
|
||||
pre1 > pre2 -> :gt
|
||||
pre1 < pre2 -> :lt
|
||||
true -> :eq
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Parse a version string into a `Version.Schema`.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Version.parse("2.0.1-alpha1")
|
||||
iex> Version.parse("2.0.1-alpha1") |> elem(1)
|
||||
#Version.Schema<2.0.1-alpha1>
|
||||
|
||||
iex> Version.parse("2.0-alpha1")
|
||||
:error
|
||||
|
||||
"""
|
||||
@spec parse(String.t) :: Schema.t
|
||||
@spec parse(String.t) :: { :ok, Schema.t } | :error
|
||||
def parse(string) when is_binary(string) do
|
||||
case Version.Parser.parse_version(string) do
|
||||
{ :ok, matchable } -> from_matchable(matchable).source(string).build(get_build(string))
|
||||
{ :error, _ } -> Version.Schema[source: string]
|
||||
{ :ok, { major, minor, patch, pre } } ->
|
||||
vsn = Version.Schema[major: major, minor: minor, patch: patch,
|
||||
pre: pre, source: string, build: get_build(string)]
|
||||
{ :ok, vsn }
|
||||
:error ->
|
||||
:error
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Parse a version requirement string into a `Version.Schema`.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Version.parse_requirement("== 2.0.1") |> elem(1)
|
||||
#Version.Requirement<== 2.0.1>
|
||||
|
||||
iex> Version.parse_requirement("== == 2.0.1")
|
||||
:error
|
||||
|
||||
"""
|
||||
@spec parse_requirement(String.t) :: { :ok, Requirement.t } | :error
|
||||
def parse_requirement(string) when is_binary(string) do
|
||||
case Version.Parser.parse_requirement(string) do
|
||||
{ :ok, spec } ->
|
||||
{ :ok, Requirement[source: string, matchspec: spec] }
|
||||
:error ->
|
||||
:error
|
||||
end
|
||||
end
|
||||
|
||||
defp to_matchable(Schema[major: major, minor: minor, patch: patch, pre: pre]) do
|
||||
{ major, minor, patch, pre }
|
||||
end
|
||||
|
||||
defp to_matchable(string) do
|
||||
case Version.Parser.parse_version(string) do
|
||||
{ :ok, vsn } -> vsn
|
||||
:error -> raise InvalidVersion, message: string
|
||||
end
|
||||
end
|
||||
|
||||
defp get_build(string) do
|
||||
case Regex.run(%r/\+([^\s]+)$/, string) do
|
||||
case Regex.run(~r/\+([^\s]+)$/, string) do
|
||||
nil ->
|
||||
nil
|
||||
|
||||
@@ -162,69 +205,6 @@ defmodule Version do
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Convert a version to a `Version.matchable`
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Version.to_matchable("2.0.1-alpha.1")
|
||||
{2, 0, 1, ["alpha", 1]}
|
||||
|
||||
"""
|
||||
@spec to_matchable(String.t | Schema.t) :: Version.matchable
|
||||
def to_matchable(Schema[major: nil, source: source]) do
|
||||
{ source, nil, nil, [] }
|
||||
end
|
||||
|
||||
def to_matchable(Version.Schema[major: major, minor: minor, patch: patch, pre: nil]) do
|
||||
{ major, minor, patch, [] }
|
||||
end
|
||||
|
||||
def to_matchable(Version.Schema[major: major, minor: minor, patch: patch, pre: pre]) do
|
||||
{ major, minor, patch, Version.Parser.parse_pre(pre) }
|
||||
end
|
||||
|
||||
def to_matchable(string) do
|
||||
to_matchable(parse(string))
|
||||
end
|
||||
|
||||
@doc """
|
||||
Convert a matchable to a `Version.Schema`.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Version.from_matchable({2, 0, 1, ["alpha", 1]})
|
||||
#Version.Schema<2.0.1-alpha.1>
|
||||
|
||||
"""
|
||||
@spec from_matchable(Version.matchable) :: Schema.t
|
||||
def from_matchable({ source, nil, nil, nil }) when is_binary(source) do
|
||||
Version.Schema[source: source]
|
||||
end
|
||||
|
||||
def from_matchable({ major, minor, patch, pre }) do
|
||||
source = "#{major}"
|
||||
|
||||
if minor do
|
||||
source = "#{source}.#{minor}"
|
||||
|
||||
if patch do
|
||||
source = "#{source}.#{patch}"
|
||||
|
||||
case pre do
|
||||
[] ->
|
||||
pre = nil
|
||||
|
||||
list ->
|
||||
pre = Enum.join(list, ".")
|
||||
source = "#{source}-#{pre}"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
Version.Schema[major: major, minor: minor, patch: patch, pre: pre, source: source]
|
||||
end
|
||||
|
||||
defmodule Parser.DSL do
|
||||
@moduledoc false
|
||||
|
||||
@@ -292,78 +272,75 @@ defmodule Version do
|
||||
Enum.filter(Enum.reverse(acc), &(&1 != :' '))
|
||||
end
|
||||
|
||||
@version_regex %r/^(\d+)(?:\.(\d+)(?:\.(\d+))?)?(?:\-([^\s]+))?(?:\+[^\d]+)?$/
|
||||
@version_regex ~r/^
|
||||
(\d+) # major
|
||||
(?:\.(\d+))? # minor
|
||||
(?:\.(\d+))? # patch
|
||||
(?:\-([\d\w\.\-]+))? # pre
|
||||
(?:\+([\d\w\-]+))? # build
|
||||
$/x
|
||||
|
||||
@spec parse_requirement(String.t) :: { :ok, Version.Requirement.t } | { :error, binary | atom }
|
||||
@spec parse_requirement(String.t) :: {:ok, Version.Requirement.t} | :error
|
||||
def parse_requirement(source) do
|
||||
lexed = lexer(source, [])
|
||||
|
||||
if valid_requirement?(lexed) do
|
||||
spec = to_matchspec(lexed)
|
||||
|
||||
case :ets.test_ms({}, spec) do
|
||||
{ :ok, _ } ->
|
||||
{ :ok, Requirement[source: source, matchspec: spec] }
|
||||
|
||||
{ :error, errors } ->
|
||||
{ :error, Enum.map(errors, fn { :error, reason } ->
|
||||
to_string(reason)
|
||||
end) }
|
||||
end
|
||||
else
|
||||
{ :error, :invalid_requirement }
|
||||
end
|
||||
to_matchspec(lexed)
|
||||
end
|
||||
|
||||
defp nillify(""), do: nil
|
||||
defp nillify(o), do: o
|
||||
|
||||
@spec parse_version(String.t) :: { :ok, Version.matchable } | { :error, :invalid_version }
|
||||
def parse_version(string) when is_binary(string) do
|
||||
if valid_version?(string) do
|
||||
destructure [_, major, minor, patch, pre], Regex.run(@version_regex, string)
|
||||
@spec parse_version(String.t) :: {:ok, Version.matchable} | :error
|
||||
def parse_version(string, approximate? \\ false) when is_binary(string) do
|
||||
if parsed = Regex.run(@version_regex, string) do
|
||||
destructure [_, major, minor, patch, pre], parsed
|
||||
patch = nillify(patch)
|
||||
pre = nillify(pre)
|
||||
|
||||
major = binary_to_integer(major)
|
||||
minor = binary_to_integer(minor |> nillify || "0")
|
||||
patch = binary_to_integer(patch |> nillify || "0")
|
||||
pre = pre && parse_pre(pre) || []
|
||||
|
||||
{ :ok, { major, minor, patch, pre } }
|
||||
else
|
||||
{ :error, :invalid_version }
|
||||
end
|
||||
end
|
||||
|
||||
@doc false
|
||||
def parse_pre(pre) do
|
||||
String.split(pre, ".") |> Enum.map fn piece ->
|
||||
if piece =~ %r/^(0|[1-9][0-9]*)$/ do
|
||||
binary_to_integer(piece)
|
||||
if nil?(minor) or (nil?(patch) and not approximate?) do
|
||||
:error
|
||||
else
|
||||
piece
|
||||
major = binary_to_integer(major)
|
||||
minor = binary_to_integer(minor)
|
||||
patch = patch && binary_to_integer(patch)
|
||||
|
||||
case parse_pre(pre) do
|
||||
{ :ok, pre } ->
|
||||
{ :ok, { major, minor, patch, pre } }
|
||||
:error ->
|
||||
:error
|
||||
end
|
||||
end
|
||||
else
|
||||
:error
|
||||
end
|
||||
end
|
||||
|
||||
@spec valid_requirement?(list) :: boolean
|
||||
def valid_requirement?([]) do
|
||||
false
|
||||
defp parse_pre(nil), do: { :ok, [] }
|
||||
defp parse_pre(pre), do: parse_pre(String.split(pre, "."), [])
|
||||
|
||||
defp parse_pre([piece|t], acc) do
|
||||
cond do
|
||||
piece =~ ~r/^(0|[1-9][0-9]*)$/ ->
|
||||
parse_pre(t, [binary_to_integer(piece)|acc])
|
||||
piece =~ ~r/^[0-9]*$/ ->
|
||||
:error
|
||||
true ->
|
||||
parse_pre(t, [piece|acc])
|
||||
end
|
||||
end
|
||||
|
||||
def valid_requirement?([a | next]) do
|
||||
valid_requirement?(a, next)
|
||||
defp parse_pre([], acc) do
|
||||
{ :ok, Enum.reverse(acc) }
|
||||
end
|
||||
|
||||
defp valid_requirement?([]), do: false
|
||||
defp valid_requirement?([a | next]), do: valid_requirement?(a, next)
|
||||
|
||||
# it must finish with a version
|
||||
defp valid_requirement?(a, []) when is_binary(a) do
|
||||
true
|
||||
end
|
||||
|
||||
# version version
|
||||
defp valid_requirement?(a, [b | _]) when is_binary(a) and is_binary(b) do
|
||||
false
|
||||
end
|
||||
|
||||
# or <op> | and <op>
|
||||
defp valid_requirement?(a, [b | next]) when is_atom(a) and is_atom(b) and a in [:'||', :'&&'] do
|
||||
valid_requirement?(b, next)
|
||||
@@ -379,73 +356,57 @@ defmodule Version do
|
||||
valid_requirement?(b, next)
|
||||
end
|
||||
|
||||
# <op> <version>; also checks operators work on valid versions
|
||||
# <op> <version>
|
||||
defp valid_requirement?(a, [b | next]) when is_atom(a) and is_binary(b) do
|
||||
if valid_version?(b) do
|
||||
valid_requirement?(b, next)
|
||||
else
|
||||
if a in [:'==', :'!='] and Regex.match? %r/^\w/, b do
|
||||
valid_requirement?(b, next)
|
||||
else
|
||||
false
|
||||
end
|
||||
end
|
||||
valid_requirement?(b, next)
|
||||
end
|
||||
|
||||
defp valid_requirement?(_, _) do
|
||||
false
|
||||
end
|
||||
|
||||
@spec valid_version?(String.t) :: boolean
|
||||
def valid_version?(string) do
|
||||
Regex.match? %r/^\d+(\.\d+(\.\d+)?)?(\-[^\s]+)?(?:\+[^\s]+)?$/, string
|
||||
end
|
||||
defp approximate_upper(version) do
|
||||
case version do
|
||||
{ major, _minor, nil, _ } ->
|
||||
{ major + 1, 0, 0, [0] }
|
||||
|
||||
defp approximate(version) do
|
||||
Version.from_matchable(case Regex.run(@version_regex, version) do
|
||||
[_, major] ->
|
||||
{ binary_to_integer(major) + 1, 0, 0, [0] }
|
||||
|
||||
[_, major, _] ->
|
||||
{ binary_to_integer(major) + 1, 0, 0, [0] }
|
||||
|
||||
[_, major, minor, _] ->
|
||||
{ binary_to_integer(major), binary_to_integer(minor) + 1, 0, [0] }
|
||||
|
||||
[_, major, minor, _, _] ->
|
||||
{ binary_to_integer(major), binary_to_integer(minor) + 1, 0, [0] }
|
||||
end)
|
||||
{ major, minor, _patch, _ } ->
|
||||
{ major, minor + 1, 0, [0] }
|
||||
end
|
||||
end
|
||||
|
||||
defp to_matchspec(lexed) do
|
||||
first = to_condition(lexed)
|
||||
rest = Enum.drop(lexed, 2)
|
||||
|
||||
[{{ :'$1', :'$2', :'$3', :'$4' }, [to_condition(first, rest)], [:'$_'] }]
|
||||
if valid_requirement?(lexed) do
|
||||
first = to_condition(lexed)
|
||||
rest = Enum.drop(lexed, 2)
|
||||
{ :ok, [{{ :'$1', :'$2', :'$3', :'$4' }, [to_condition(first, rest)], [:'$_'] }] }
|
||||
else
|
||||
:error
|
||||
end
|
||||
catch
|
||||
:invalid_matchspec -> :error
|
||||
end
|
||||
|
||||
defp to_condition([:'==', version | _]) do
|
||||
version = Version.to_matchable(version)
|
||||
|
||||
version = parse_condition(version)
|
||||
{ :'==', :'$_', { :const, version } }
|
||||
end
|
||||
|
||||
defp to_condition([:'!=', version | _]) do
|
||||
version = Version.to_matchable(version)
|
||||
|
||||
version = parse_condition(version)
|
||||
{ :'/=', :'$_', { :const, version } }
|
||||
end
|
||||
|
||||
defp to_condition([:'~>', version | _]) do
|
||||
from = Version.parse(version)
|
||||
to = approximate(version)
|
||||
from = parse_condition(version, true)
|
||||
to = approximate_upper(from)
|
||||
|
||||
{ :andalso, to_condition([:'>=', to_string(from)]),
|
||||
to_condition([:'<', to_string(to)]) }
|
||||
{ :andalso, to_condition([:'>=', matchable_to_string(from)]),
|
||||
to_condition([:'<', matchable_to_string(to)]) }
|
||||
end
|
||||
|
||||
defp to_condition([:'>', version | _]) do
|
||||
{ major, minor, patch, pre } = Version.to_matchable(version)
|
||||
{ major, minor, patch, pre } = parse_condition(version)
|
||||
|
||||
{ :andalso, { :not, { :is_binary, :'$1' } },
|
||||
{ :orelse, { :'>', {{ :'$1', :'$2', :'$3' }},
|
||||
@@ -461,7 +422,7 @@ defmodule Version do
|
||||
end
|
||||
|
||||
defp to_condition([:'>=', version | _]) do
|
||||
matchable = Version.to_matchable(version)
|
||||
matchable = parse_condition(version)
|
||||
|
||||
{ :orelse, { :andalso, { :not, { :is_binary, :'$1' } },
|
||||
{ :'==', :'$_', { :const, matchable } } },
|
||||
@@ -469,7 +430,7 @@ defmodule Version do
|
||||
end
|
||||
|
||||
defp to_condition([:'<', version | _]) do
|
||||
{ major, minor, patch, pre } = Version.to_matchable(version)
|
||||
{ major, minor, patch, pre } = parse_condition(version)
|
||||
|
||||
{ :andalso, { :not, { :is_binary, :'$1' } },
|
||||
{ :orelse, { :'<', {{ :'$1', :'$2', :'$3' }},
|
||||
@@ -485,7 +446,7 @@ defmodule Version do
|
||||
end
|
||||
|
||||
defp to_condition([:'<=', version | _]) do
|
||||
matchable = Version.to_matchable(version)
|
||||
matchable = parse_condition(version)
|
||||
|
||||
{ :orelse, { :andalso, { :not, { :is_binary, :'$1' } },
|
||||
{ :'==', :'$_', { :const, matchable } } },
|
||||
@@ -503,6 +464,19 @@ defmodule Version do
|
||||
defp to_condition(current, [:'||', operator, version | rest]) do
|
||||
to_condition({ :orelse, current, to_condition([operator, version]) }, rest)
|
||||
end
|
||||
|
||||
defp parse_condition(version, approximate? \\ false) do
|
||||
case parse_version(version, approximate?) do
|
||||
{ :ok, version } -> version
|
||||
:error -> throw :invalid_matchspec
|
||||
end
|
||||
end
|
||||
|
||||
defp matchable_to_string({ major, minor, patch, pre }) do
|
||||
patch = if patch, do: "#{patch}", else: "0"
|
||||
pre = if pre != [], do: "-#{Enum.join(pre, ".")}"
|
||||
"#{major}.#{minor}.#{patch}#{pre}"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -519,13 +493,13 @@ defimpl Inspect, for: Version.Schema do
|
||||
end
|
||||
|
||||
defimpl String.Chars, for: Version.Requirement do
|
||||
def to_string({ _, source, _ }) do
|
||||
def to_string(Version.Requirement[source: source]) do
|
||||
source
|
||||
end
|
||||
end
|
||||
|
||||
defimpl Inspect, for: Version.Requirement do
|
||||
def inspect({ _, source, _ }, _opts) do
|
||||
def inspect(Version.Requirement[source: source], _opts) do
|
||||
"#Version.Requirement<" <> source <> ">"
|
||||
end
|
||||
end
|
||||
|
||||
@@ -4,6 +4,7 @@ defmodule Elixir.Mixfile do
|
||||
def project do
|
||||
[ app: :elixir,
|
||||
version: System.version,
|
||||
build_per_environment: false,
|
||||
escript_embed_elixir: false,
|
||||
escript_main_module: :elixir,
|
||||
escript_emu_args: "%%! -noshell\n" ]
|
||||
|
||||
@@ -24,4 +24,4 @@
|
||||
{verbose, false}
|
||||
]}.
|
||||
|
||||
{require_otp_vsn,"(R16B|R17A).*"}.
|
||||
{require_otp_vsn,"(R16B|17).*"}.
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
-export([call/1, cast/1]).
|
||||
-export([start_link/0, init/1, handle_call/3, handle_cast/2,
|
||||
handle_info/2, terminate/2, code_change/3]).
|
||||
-behavior(gen_server).
|
||||
-behaviour(gen_server).
|
||||
|
||||
-define(timeout, 30000).
|
||||
-record(elixir_code_server, {
|
||||
|
||||
@@ -186,7 +186,7 @@ core_file(File) ->
|
||||
catch
|
||||
Kind:Reason ->
|
||||
io:format("~p: ~p~nstacktrace: ~p~n", [Kind, Reason, erlang:get_stacktrace()]),
|
||||
exit(1)
|
||||
erlang:halt(1)
|
||||
end.
|
||||
|
||||
core_main() ->
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
-module(elixir_counter).
|
||||
-export([start_link/0, init/1, handle_call/3, handle_cast/2,
|
||||
handle_info/2, terminate/2, code_change/3, next/0]).
|
||||
-behavior(gen_server).
|
||||
-behaviour(gen_server).
|
||||
|
||||
-define(timeout, 1000). %% 1 second
|
||||
-define(limit, 4294967296). %% 2^32
|
||||
|
||||
@@ -6,9 +6,15 @@
|
||||
expand(Args, E) ->
|
||||
lists:mapfoldl(fun
|
||||
({ '//', Meta, [Left, Right] }, Acc) ->
|
||||
elixir_errors:deprecation(Meta, E#elixir_env.file, "using // for default arguments is deprecated, "
|
||||
"please use \\\\ instead"),
|
||||
{ ELeft, EL } = elixir_exp:expand(Left, Acc),
|
||||
{ ERight, _ } = elixir_exp:expand(Right, Acc#elixir_env{context=nil}),
|
||||
{ { '//', Meta, [ELeft, ERight] }, EL };
|
||||
{ { '\\\\', Meta, [ELeft, ERight] }, EL };
|
||||
({ '\\\\', Meta, [Left, Right] }, Acc) ->
|
||||
{ ELeft, EL } = elixir_exp:expand(Left, Acc),
|
||||
{ ERight, _ } = elixir_exp:expand(Right, Acc#elixir_env{context=nil}),
|
||||
{ { '\\\\', Meta, [ELeft, ERight] }, EL };
|
||||
(Left, Acc) ->
|
||||
elixir_exp:expand(Left, Acc)
|
||||
end, E, Args).
|
||||
@@ -21,7 +27,7 @@ unpack(Kind, Name, Args, S) ->
|
||||
%% Unpack default from given args.
|
||||
%% Returns the given arguments without their default
|
||||
%% clauses and a list of clauses for the default calls.
|
||||
unpack_each(Kind, Name, [{'//', Line, [Expr, _]}|T] = List, Acc, Clauses, S) ->
|
||||
unpack_each(Kind, Name, [{'\\\\', Line, [Expr, _]}|T] = List, Acc, Clauses, S) ->
|
||||
Base = build_match(Acc, Line, []),
|
||||
{ Args, Invoke } = extract_defaults(List, Line, length(Base), [], []),
|
||||
|
||||
@@ -44,7 +50,7 @@ unpack_each(_Kind, _Name, [], Acc, Clauses, _S) ->
|
||||
|
||||
% Extract default values from args following the current default clause.
|
||||
|
||||
extract_defaults([{'//', _, [_Expr, Default]}|T], Line, Counter, NewArgs, NewInvoke) ->
|
||||
extract_defaults([{'\\\\', _, [_Expr, Default]}|T], Line, Counter, NewArgs, NewInvoke) ->
|
||||
extract_defaults(T, Line, Counter, NewArgs, [Default|NewInvoke]);
|
||||
|
||||
extract_defaults([_|T], Line, Counter, NewArgs, NewInvoke) ->
|
||||
|
||||
+165
-104
@@ -9,14 +9,18 @@
|
||||
find_import/4, format_error/1]).
|
||||
-include("elixir.hrl").
|
||||
-import(ordsets, [is_element/2]).
|
||||
|
||||
%% Module macros
|
||||
-define(kernel, 'Elixir.Kernel').
|
||||
-define(node, 'Elixir.Node').
|
||||
-define(process, 'Elixir.Process').
|
||||
|
||||
default_functions() ->
|
||||
[ { ?kernel, elixir_imported_functions() } ].
|
||||
default_macros() ->
|
||||
[ { ?kernel, elixir_imported_macros() } ].
|
||||
default_requires() ->
|
||||
[ 'Elixir.Integer', 'Elixir.Kernel', 'Elixir.Kernel.Typespec', 'Elixir.Record' ].
|
||||
[ 'Elixir.Kernel', 'Elixir.Kernel.Typespec' ].
|
||||
|
||||
find_import(Meta, Name, Arity, E) ->
|
||||
Tuple = { Name, Arity },
|
||||
@@ -64,13 +68,10 @@ require_function(_Meta, Receiver, Name, Arity, E) ->
|
||||
|
||||
remote_function(Receiver, Name, Arity, E) ->
|
||||
elixir_lexical:record_remote(Receiver, E#elixir_env.lexical_tracker),
|
||||
Tuple = { Name, Arity },
|
||||
Final =
|
||||
case (Receiver == ?kernel) andalso is_element(Tuple, in_erlang_functions()) of
|
||||
true -> erlang;
|
||||
false -> Receiver
|
||||
end,
|
||||
{ remote, Final, Name, Arity }.
|
||||
case inline(Receiver, Name, Arity) of
|
||||
{ AR, AN } -> { remote, AR, AN, Arity };
|
||||
false -> { remote, Receiver, Name, Arity }
|
||||
end.
|
||||
|
||||
%% Dispatches
|
||||
|
||||
@@ -87,19 +88,19 @@ dispatch_import(Meta, Name, Args, E, Callback) ->
|
||||
|
||||
dispatch_require(Meta, Receiver, Name, Args, E, Callback) when is_atom(Receiver) ->
|
||||
Arity = length(Args),
|
||||
Tuple = { Name, Arity },
|
||||
|
||||
case (Receiver == ?kernel) andalso is_element(Tuple, in_erlang_functions()) of
|
||||
true -> Callback(erlang);
|
||||
case rewrite(Receiver, Name, Args, Arity) of
|
||||
{ ok, AR, AN, AA } ->
|
||||
Callback(AR, AN, AA);
|
||||
false ->
|
||||
case expand_require(Meta, Receiver, Tuple, Args, E) of
|
||||
case expand_require(Meta, Receiver, { Name, Arity }, Args, E) of
|
||||
{ ok, Receiver, Quoted } -> expand_quoted(Meta, Receiver, Name, Arity, Quoted, E);
|
||||
error -> Callback(Receiver)
|
||||
error -> Callback(Receiver, Name, Args)
|
||||
end
|
||||
end;
|
||||
|
||||
dispatch_require(_Meta, Receiver, _Name, _Args, _E, Callback) ->
|
||||
Callback(Receiver).
|
||||
dispatch_require(_Meta, Receiver, Name, Args, _E, Callback) ->
|
||||
Callback(Receiver, Name, Args).
|
||||
|
||||
%% Macros expansion
|
||||
|
||||
@@ -111,13 +112,13 @@ expand_import(Meta, { Name, Arity } = Tuple, Args, E, Extra) ->
|
||||
elixir_locals:macro_for(Module, Name, Arity),
|
||||
|
||||
case Dispatch of
|
||||
%% In case it is an import, or the receive is the same as the
|
||||
%% current module (happens in bootstraping), we dispatch the import.
|
||||
{ Kind, Receiver } when Kind == import; Receiver == Module ->
|
||||
%% In case it is an import, we dispatch the import.
|
||||
{ Kind, _ } when Kind == import ->
|
||||
do_expand_import(Meta, Tuple, Args, Module, E, Dispatch);
|
||||
|
||||
%% There is a local and an import. This is a conflict.
|
||||
{ _, Receiver } when Local /= false ->
|
||||
%% There is a local and an import. This is a conflict unless
|
||||
%% the receiver is the same as module (happens on bootstrap).
|
||||
{ _, Receiver } when Local /= false, Receiver /= Module ->
|
||||
Error = { macro_conflict, { Receiver, Name, Arity } },
|
||||
elixir_errors:form_error(Meta, E#elixir_env.file, ?MODULE, Error);
|
||||
|
||||
@@ -136,11 +137,12 @@ do_expand_import(Meta, { Name, Arity } = Tuple, Args, Module, E, Result) ->
|
||||
{ function, Receiver } ->
|
||||
elixir_lexical:record_import(Receiver, E#elixir_env.lexical_tracker),
|
||||
elixir_locals:record_import(Tuple, Receiver, Module, E#elixir_env.function),
|
||||
Endpoint = case (Receiver == ?kernel) andalso is_element(Tuple, in_erlang_functions()) of
|
||||
true -> erlang;
|
||||
false -> Receiver
|
||||
end,
|
||||
{ ok, Endpoint };
|
||||
case rewrite(Receiver, Name, Args, Arity) of
|
||||
{ ok, AR, AN, AA } ->
|
||||
{ ok, AR, { { '.', [], [AR, AN] }, [], AA } };
|
||||
false ->
|
||||
{ ok, Receiver }
|
||||
end;
|
||||
{ macro, Receiver } ->
|
||||
elixir_lexical:record_import(Receiver, E#elixir_env.lexical_tracker),
|
||||
elixir_locals:record_import(Tuple, Receiver, Module, E#elixir_env.function),
|
||||
@@ -150,6 +152,13 @@ do_expand_import(Meta, { Name, Arity } = Tuple, Args, Module, E, Result) ->
|
||||
{ ok, _, _ } = Response -> Response;
|
||||
error -> { ok, Receiver }
|
||||
end;
|
||||
false when Module == ?kernel ->
|
||||
case rewrite(Module, Name, Args, Arity) of
|
||||
{ ok, AR, AN, AA } ->
|
||||
{ ok, AR, { { '.', [], [AR, AN] }, [], AA } };
|
||||
false ->
|
||||
error
|
||||
end;
|
||||
false ->
|
||||
error
|
||||
end.
|
||||
@@ -303,7 +312,7 @@ elixir_imported_functions() ->
|
||||
try
|
||||
?kernel:'__info__'(functions)
|
||||
catch
|
||||
error:undef -> in_erlang_functions()
|
||||
error:undef -> []
|
||||
end.
|
||||
|
||||
elixir_imported_macros() ->
|
||||
@@ -313,81 +322,133 @@ elixir_imported_macros() ->
|
||||
error:undef -> []
|
||||
end.
|
||||
|
||||
%% Functions imported from Erlang module. MUST BE SORTED.
|
||||
%% TODO: Inline operators here as well once bug in erl_eval is fixed.
|
||||
in_erlang_functions() ->
|
||||
[
|
||||
{ abs, 1 },
|
||||
{ apply, 2 },
|
||||
{ apply, 3 },
|
||||
{ atom_to_list, 1 },
|
||||
{ binary_part, 3 },
|
||||
{ binary_to_float, 1 },
|
||||
{ binary_to_float, 2 },
|
||||
{ binary_to_integer, 1 },
|
||||
{ binary_to_integer, 2 },
|
||||
{ binary_to_term, 1 },
|
||||
{ binary_to_term, 2 },
|
||||
{ bit_size, 1 },
|
||||
{ bitstring_to_list, 1 },
|
||||
{ byte_size, 1 },
|
||||
% { date, 0 },
|
||||
{ exit, 1 },
|
||||
% { float, 1 },
|
||||
{ float_to_binary, 1 },
|
||||
% { float_to_binary, 2 },
|
||||
{ float_to_list, 1 },
|
||||
% { float_to_list, 2 },
|
||||
{ hd, 1 },
|
||||
{ integer_to_binary, 1 },
|
||||
{ integer_to_binary, 2 },
|
||||
{ integer_to_list, 1 },
|
||||
{ integer_to_list, 2 },
|
||||
{ iolist_size, 1 },
|
||||
{ iolist_to_binary, 1 },
|
||||
{ is_atom, 1 },
|
||||
{ is_binary, 1 },
|
||||
{ is_bitstring, 1 },
|
||||
{ is_boolean, 1 },
|
||||
{ is_float, 1 },
|
||||
{ is_function, 1 },
|
||||
{ is_function, 2 },
|
||||
{ is_integer, 1 },
|
||||
{ is_list, 1 },
|
||||
{ is_number, 1 },
|
||||
{ is_pid, 1 },
|
||||
{ is_port, 1 },
|
||||
{ is_reference, 1 },
|
||||
{ is_tuple, 1 },
|
||||
{ length, 1 },
|
||||
{ list_to_atom, 1 },
|
||||
{ list_to_bitstring, 1 },
|
||||
{ list_to_existing_atom, 1 },
|
||||
{ list_to_float, 1 },
|
||||
{ list_to_integer, 1 },
|
||||
{ list_to_integer, 2 },
|
||||
{ list_to_tuple, 1 },
|
||||
{ make_ref, 0 },
|
||||
{ max, 2 },
|
||||
{ min, 2 },
|
||||
{ node, 0 },
|
||||
{ node, 1 },
|
||||
% { now, 0 },
|
||||
{ round, 1 },
|
||||
{ self, 0 },
|
||||
{ send, 2 },
|
||||
{ size, 1 },
|
||||
{ spawn, 1 },
|
||||
{ spawn, 3 },
|
||||
{ spawn_link, 1 },
|
||||
{ spawn_link, 3 },
|
||||
% { split_binary, 2 },
|
||||
{ term_to_binary, 1 },
|
||||
{ term_to_binary, 2 },
|
||||
{ throw, 1 },
|
||||
% { time, 0 },
|
||||
{ tl, 1 },
|
||||
{ trunc, 1 },
|
||||
{ tuple_size, 1 },
|
||||
{ tuple_to_list, 1 }
|
||||
].
|
||||
rewrite(?kernel, atom_to_binary, [Arg], 1) ->
|
||||
{ ok, erlang, atom_to_binary, [Arg, utf8] };
|
||||
rewrite(?kernel, binary_to_atom, [Arg], 1) ->
|
||||
{ ok, erlang, binary_to_atom, [Arg, utf8] };
|
||||
rewrite(?kernel, binary_to_existing_atom, [Arg], 1) ->
|
||||
{ ok, erlang, binary_to_existing_atom, [Arg, utf8] };
|
||||
rewrite(?kernel, elem, [Tuple, Index], 2) ->
|
||||
{ ok, erlang, element, [increment(Index), Tuple] };
|
||||
rewrite(?kernel, set_elem, [Tuple, Index, Value], 2) ->
|
||||
{ ok, erlang, setelement, [increment(Index), Tuple, Value] };
|
||||
rewrite(?process, monitor, [Arg], 1) ->
|
||||
{ ok, erlang, monitor, [process, Arg] };
|
||||
rewrite(Receiver, Name, Args, Arity) ->
|
||||
case inline(Receiver, Name, Arity) of
|
||||
{ AR, AN } -> { ok, AR, AN, Args };
|
||||
false -> false
|
||||
end.
|
||||
|
||||
increment(Number) when is_number(Number) ->
|
||||
Number + 1;
|
||||
increment(Other) ->
|
||||
{ { '.', [], [erlang, '+'] }, [], [Other, 1] }.
|
||||
|
||||
inline(?kernel, '+', 2) -> { erlang, '+' };
|
||||
inline(?kernel, '-', 2) -> { erlang, '-' };
|
||||
inline(?kernel, '+', 1) -> { erlang, '+' };
|
||||
inline(?kernel, '-', 1) -> { erlang, '-' };
|
||||
inline(?kernel, '*', 2) -> { erlang, '*' };
|
||||
inline(?kernel, '/', 2) -> { erlang, '/' };
|
||||
inline(?kernel, '++', 2) -> { erlang, '++' };
|
||||
inline(?kernel, '--', 2) -> { erlang, '--' };
|
||||
inline(?kernel, 'xor', 2) -> { erlang, 'xor' };
|
||||
inline(?kernel, 'not', 1) -> { erlang, 'not' };
|
||||
inline(?kernel, '<', 2) -> { erlang, '<' };
|
||||
inline(?kernel, '>', 2) -> { erlang, '>' };
|
||||
inline(?kernel, '<=', 2) -> { erlang, '=<' };
|
||||
inline(?kernel, '>=', 2) -> { erlang, '>=' };
|
||||
inline(?kernel, '==', 2) -> { erlang, '==' };
|
||||
inline(?kernel, '!=', 2) -> { erlang, '/=' };
|
||||
inline(?kernel, '===', 2) -> { erlang, '=:=' };
|
||||
inline(?kernel, '!==', 2) -> { erlang, '=/=' };
|
||||
inline(?kernel, abs, 1) -> { erlang, abs };
|
||||
inline(?kernel, apply, 2) -> { erlang, apply };
|
||||
inline(?kernel, apply, 3) -> { erlang, apply };
|
||||
inline(?kernel, atom_to_list, 1) -> { erlang, atom_to_list };
|
||||
inline(?kernel, binary_part, 3) -> { erlang, binary_part };
|
||||
inline(?kernel, binary_to_float, 1) -> { erlang, binary_to_float };
|
||||
inline(?kernel, binary_to_float, 2) -> { erlang, binary_to_float };
|
||||
inline(?kernel, binary_to_integer, 1) -> { erlang, binary_to_integer };
|
||||
inline(?kernel, binary_to_integer, 2) -> { erlang, binary_to_integer };
|
||||
inline(?kernel, bit_size, 1) -> { erlang, bit_size };
|
||||
inline(?kernel, bitstring_to_list, 1) -> { erlang, bitstring_to_list };
|
||||
inline(?kernel, byte_size, 1) -> { erlang, byte_size };
|
||||
inline(?kernel, 'div', 2) -> { erlang, 'div' };
|
||||
inline(?kernel, exit, 1) -> { erlang, exit };
|
||||
inline(?kernel, float_to_binary, 1) -> { erlang, float_to_binary };
|
||||
inline(?kernel, float_to_list, 1) -> { erlang, float_to_list };
|
||||
inline(?kernel, hd, 1) -> { erlang, hd };
|
||||
inline(?kernel, integer_to_binary, 1) -> { erlang, integer_to_binary };
|
||||
inline(?kernel, integer_to_binary, 2) -> { erlang, integer_to_binary };
|
||||
inline(?kernel, integer_to_list, 1) -> { erlang, integer_to_list };
|
||||
inline(?kernel, integer_to_list, 2) -> { erlang, integer_to_list };
|
||||
inline(?kernel, iolist_size, 1) -> { erlang, iolist_size };
|
||||
inline(?kernel, iolist_to_binary, 1) -> { erlang, iolist_to_binary };
|
||||
inline(?kernel, is_atom, 1) -> { erlang, is_atom };
|
||||
inline(?kernel, is_binary, 1) -> { erlang, is_binary };
|
||||
inline(?kernel, is_bitstring, 1) -> { erlang, is_bitstring };
|
||||
inline(?kernel, is_boolean, 1) -> { erlang, is_boolean };
|
||||
inline(?kernel, is_float, 1) -> { erlang, is_float };
|
||||
inline(?kernel, is_function, 1) -> { erlang, is_function };
|
||||
inline(?kernel, is_function, 2) -> { erlang, is_function };
|
||||
inline(?kernel, is_integer, 1) -> { erlang, is_integer };
|
||||
inline(?kernel, is_list, 1) -> { erlang, is_list };
|
||||
inline(?kernel, is_number, 1) -> { erlang, is_number };
|
||||
inline(?kernel, is_pid, 1) -> { erlang, is_pid };
|
||||
inline(?kernel, is_port, 1) -> { erlang, is_port };
|
||||
inline(?kernel, is_reference, 1) -> { erlang, is_reference };
|
||||
inline(?kernel, is_tuple, 1) -> { erlang, is_tuple };
|
||||
inline(?kernel, length, 1) -> { erlang, length };
|
||||
inline(?kernel, list_to_atom, 1) -> { erlang, list_to_atom };
|
||||
inline(?kernel, list_to_bitstring, 1) -> { erlang, list_to_bitstring };
|
||||
inline(?kernel, list_to_existing_atom, 1) -> { erlang, list_to_existing_atom };
|
||||
inline(?kernel, list_to_float, 1) -> { erlang, list_to_float };
|
||||
inline(?kernel, list_to_integer, 1) -> { erlang, list_to_integer };
|
||||
inline(?kernel, list_to_integer, 2) -> { erlang, list_to_integer };
|
||||
inline(?kernel, list_to_tuple, 1) -> { erlang, list_to_tuple };
|
||||
inline(?kernel, make_ref, 0) -> { erlang, make_ref };
|
||||
inline(?kernel, max, 2) -> { erlang, max };
|
||||
inline(?kernel, min, 2) -> { erlang, min };
|
||||
inline(?kernel, node, 0) -> { erlang, node };
|
||||
inline(?kernel, node, 1) -> { erlang, node };
|
||||
inline(?kernel, 'rem', 2) -> { erlang, 'rem' };
|
||||
inline(?kernel, round, 1) -> { erlang, round };
|
||||
inline(?kernel, self, 0) -> { erlang, self };
|
||||
inline(?kernel, send, 2) -> { erlang, send };
|
||||
inline(?kernel, size, 1) -> { erlang, size };
|
||||
inline(?kernel, spawn, 1) -> { erlang, spawn };
|
||||
inline(?kernel, spawn, 3) -> { erlang, spawn };
|
||||
inline(?kernel, spawn_link, 1) -> { erlang, spawn_link };
|
||||
inline(?kernel, spawn_link, 3) -> { erlang, spawn_link };
|
||||
inline(?kernel, throw, 1) -> { erlang, throw };
|
||||
inline(?kernel, tl, 1) -> { erlang, tl };
|
||||
inline(?kernel, trunc, 1) -> { erlang, trunc };
|
||||
inline(?kernel, tuple_size, 1) -> { erlang, tuple_size };
|
||||
inline(?kernel, tuple_to_list, 1) -> { erlang, tuple_to_list };
|
||||
|
||||
inline(?process, exit, 2) -> { erlang, exit };
|
||||
inline(?process, spawn, 1) -> { erlang, spawn };
|
||||
inline(?process, spawn, 2) -> { erlang, spawn_opt };
|
||||
inline(?process, spawn, 3) -> { erlang, spawn };
|
||||
inline(?process, spawn, 4) -> { erlang, spawn_opt };
|
||||
inline(?process, spawn_link, 1) -> { erlang, spawn_link };
|
||||
inline(?process, spawn_link, 3) -> { erlang, spawn_link };
|
||||
inline(?process, spawn_monitor, 1) -> { erlang, spawn_monitor };
|
||||
inline(?process, spawn_monitor, 3) -> { erlang, spawn_monitor };
|
||||
inline(?process, demonitor, 1) -> { erlang, demonitor };
|
||||
inline(?process, demonitor, 2) -> { erlang, demonitor };
|
||||
inline(?process, link, 1) -> { erlang, link };
|
||||
inline(?process, unlink, 1) -> { erlang, unlink };
|
||||
|
||||
inline(?node, spawn, 2) -> { erlang, spawn };
|
||||
inline(?node, spawn, 3) -> { erlang, spawn_opt };
|
||||
inline(?node, spawn, 4) -> { erlang, spawn };
|
||||
inline(?node, spawn, 5) -> { erlang, spawn_opt };
|
||||
inline(?node, spawn_link, 2) -> { erlang, spawn_link };
|
||||
inline(?node, spawn_link, 4) -> { erlang, spawn_link };
|
||||
inline(?node, spawn_monitor, 2) -> { erlang, spawn_monitor };
|
||||
inline(?node, spawn_monitor, 4) -> { erlang, spawn_monitor };
|
||||
|
||||
inline(_, _, _) -> false.
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
% This is not exposed in the Elixir language.
|
||||
-module(elixir_errors).
|
||||
-export([compile_error/3, compile_error/4,
|
||||
form_error/4, parse_error/4, warn/1,
|
||||
form_error/4, parse_error/4, warn/2, warn/3,
|
||||
handle_file_warning/2, handle_file_warning/3, handle_file_error/2,
|
||||
deprecation/3, deprecation/4]).
|
||||
-include("elixir.hrl").
|
||||
@@ -16,6 +16,12 @@ warn(Warning) ->
|
||||
end,
|
||||
io:put_chars(standard_error, Warning).
|
||||
|
||||
warn(Caller, Warning) ->
|
||||
warn([Caller, "warning: ", Warning]).
|
||||
|
||||
warn(Line, File, Warning) ->
|
||||
warn(file_format(Line, File, "warning: " ++ Warning)).
|
||||
|
||||
%% Raised during expansion/translation/compilation.
|
||||
|
||||
-spec form_error(list(), binary(), module(), any()) -> no_return().
|
||||
@@ -93,7 +99,7 @@ handle_file_warning(_, File, { Line, sys_core_fold, { no_effect, { erlang, F, A
|
||||
end
|
||||
end,
|
||||
Message = io_lib:format(Fmt, Args),
|
||||
warn(file_format(Line, File, Message));
|
||||
warn(Line, File, Message);
|
||||
|
||||
%% Rewrite undefined behaviour to check for protocols
|
||||
handle_file_warning(_, File, {Line,erl_lint,{undefined_behaviour_func,{Fun,Arity},Module}}) ->
|
||||
@@ -106,14 +112,14 @@ handle_file_warning(_, File, {Line,erl_lint,{undefined_behaviour_func,{Fun,Arity
|
||||
Kind = protocol_or_behaviour(Module),
|
||||
Raw = "undefined ~ts ~ts ~ts/~B (for ~ts ~ts)",
|
||||
Message = io_lib:format(Raw, [Kind, DefKind, Def, DefArity, Kind, elixir_aliases:inspect(Module)]),
|
||||
warn(file_format(Line, File, Message));
|
||||
warn(Line, File, Message);
|
||||
|
||||
handle_file_warning(_, File, {Line,erl_lint,{undefined_behaviour,Module}}) ->
|
||||
case elixir_compiler:get_opt(internal) of
|
||||
true -> [];
|
||||
false ->
|
||||
Message = io_lib:format("behaviour ~ts undefined", [elixir_aliases:inspect(Module)]),
|
||||
warn(file_format(Line, File, Message))
|
||||
warn(Line, File, Message)
|
||||
end;
|
||||
|
||||
%% Ignore unused vars at "weird" lines (<= 0)
|
||||
@@ -127,12 +133,12 @@ handle_file_warning(_, _File, {_Line,erl_lint,{shadowed_var,_Var,_Where}}) ->
|
||||
%% Properly format other unused vars
|
||||
handle_file_warning(_, File, {Line,erl_lint,{unused_var,Var}}) ->
|
||||
Message = format_error(erl_lint, { unused_var, format_var(Var) }),
|
||||
warn(file_format(Line, File, Message));
|
||||
warn(Line, File, Message);
|
||||
|
||||
%% Default behavior
|
||||
%% Default behaviour
|
||||
handle_file_warning(_, File, {Line,Module,Desc}) ->
|
||||
Message = format_error(Module, Desc),
|
||||
warn(file_format(Line, File, Message)).
|
||||
warn(Line, File, Message).
|
||||
|
||||
handle_file_warning(File, Desc) ->
|
||||
handle_file_warning(false, File, Desc).
|
||||
|
||||
@@ -122,9 +122,6 @@ expand({ import, Meta, [Ref, KV] }, E) ->
|
||||
|
||||
expand({ '__MODULE__', _, Atom }, E) when is_atom(Atom) ->
|
||||
{ E#elixir_env.module, E };
|
||||
expand({ '__FILE__', Meta, Atom }, E) when is_atom(Atom) ->
|
||||
elixir_errors:deprecation(Meta, E#elixir_env.file, "__FILE__ is deprecated, please use __DIR__ or __ENV__.file instead"),
|
||||
{ E#elixir_env.file, E };
|
||||
expand({ '__DIR__', _, Atom }, E) when is_atom(Atom) ->
|
||||
{ filename:dirname(E#elixir_env.file), E };
|
||||
expand({ '__CALLER__', _, Atom } = Caller, E) when is_atom(Atom) ->
|
||||
@@ -296,9 +293,6 @@ expand({ Name, Meta, Kind } = Var, #elixir_env{vars=Vars} = E) when is_atom(Name
|
||||
|
||||
compile_error(Meta, E#elixir_env.file, "expected var ~ts~ts to expand to an existing "
|
||||
"variable or be a part of a match", [Name, Extra]);
|
||||
E#elixir_env.context == guard ->
|
||||
compile_error(Meta, E#elixir_env.file, "unknown variable ~ts or cannot invoke "
|
||||
"function ~ts/0 inside guard", [Name, Name]);
|
||||
true ->
|
||||
expand({ Name, Meta, [] }, E)
|
||||
end
|
||||
@@ -319,8 +313,8 @@ expand({ { '.', DotMeta, [Left, Right] }, Meta, Args }, E)
|
||||
when (is_tuple(Left) orelse is_atom(Left)), is_atom(Right), is_list(Meta), is_list(Args) ->
|
||||
{ ELeft, EL } = expand(Left, E),
|
||||
|
||||
elixir_dispatch:dispatch_require(Meta, ELeft, Right, Args, EL, fun(Receiver) ->
|
||||
expand_remote(Receiver, DotMeta, Right, Meta, Args, E, EL)
|
||||
elixir_dispatch:dispatch_require(Meta, ELeft, Right, Args, EL, fun(AR, AF, AA) ->
|
||||
expand_remote(AR, DotMeta, AF, Meta, AA, E, EL)
|
||||
end);
|
||||
|
||||
%% Anonymous calls
|
||||
@@ -402,7 +396,7 @@ expand_many(Args, E) ->
|
||||
%% 3
|
||||
%%
|
||||
%% However, lexical information is.
|
||||
expand_arg(Arg, Acc) when is_number(Arg); is_atom(Arg); is_binary(Arg); is_pid(Arg); is_function(Arg) ->
|
||||
expand_arg(Arg, Acc) when is_number(Arg); is_atom(Arg); is_binary(Arg); is_pid(Arg) ->
|
||||
{ Arg, Acc };
|
||||
expand_arg(Arg, { Acc1, Acc2 }) ->
|
||||
{ EArg, EAcc } = expand(Arg, Acc1),
|
||||
|
||||
@@ -105,7 +105,7 @@ build(Line, File, Module, Lexical) ->
|
||||
_ -> ets:insert(DataTable, { on_definition, [] })
|
||||
end,
|
||||
|
||||
Attributes = [behavior, behaviour, on_load, spec, type, export_type, opaque, callback, compile],
|
||||
Attributes = [behaviour, on_load, spec, type, export_type, opaque, callback, compile],
|
||||
ets:insert(DataTable, { ?acc_attr, [before_compile, after_compile, on_definition|Attributes] }),
|
||||
ets:insert(DataTable, { ?persisted_attr, [vsn|Attributes] }),
|
||||
ets:insert(DataTable, { ?docs_attr, ets:new(DataTable, [ordered_set, public]) }),
|
||||
@@ -360,8 +360,8 @@ eval_callbacks(Line, Module, Name, Args, E) ->
|
||||
Meta = [{line,Line},{require,false}],
|
||||
|
||||
lists:foldl(fun({M,F}, Acc) ->
|
||||
{ Expr, ET } = elixir_dispatch:dispatch_require(Meta, M, F, Args, Acc, fun(_) ->
|
||||
apply(M, F, Args),
|
||||
{ Expr, ET } = elixir_dispatch:dispatch_require(Meta, M, F, Args, Acc, fun(AM, AF, AA) ->
|
||||
apply(AM, AF, AA),
|
||||
{ nil, Acc }
|
||||
end),
|
||||
|
||||
|
||||
@@ -4,14 +4,14 @@ Nonterminals
|
||||
bracket_expr bracket_at_expr base_expr matched_expr unmatched_expr
|
||||
op_expr matched_op_expr no_parens_op_expr
|
||||
comp_op_eol at_op_eol unary_op_eol and_op_eol or_op_eol
|
||||
add_op_eol mult_op_eol exp_op_eol two_op_eol type_op_eol stab_op_eol
|
||||
add_op_eol mult_op_eol exp_op_eol two_op_eol pipe_op_eol stab_op_eol
|
||||
arrow_op_eol match_op_eol when_op_eol in_op_eol in_match_op_eol
|
||||
open_paren close_paren empty_paren
|
||||
open_bracket close_bracket
|
||||
open_curly close_curly
|
||||
open_bit close_bit
|
||||
container_comma_expr_base container_comma_expr container_arg container_args
|
||||
call_args_parens_comma_expr call_args_parens parens_call
|
||||
container_args_base container_args
|
||||
call_args_parens_base call_args_parens parens_call
|
||||
call_args_no_parens_one call_args_no_parens_expr call_args_no_parens_comma_expr
|
||||
call_args_no_parens_all call_args_no_parens_many call_args_no_parens_many_strict
|
||||
stab stab_eol stab_expr stab_maybe_expr stab_parens_many
|
||||
@@ -19,7 +19,7 @@ Nonterminals
|
||||
call_args_no_parens_kw_expr call_args_no_parens_kw_comma call_args_no_parens_kw
|
||||
dot_op dot_alias dot_identifier dot_op_identifier dot_do_identifier
|
||||
dot_paren_identifier dot_bracket_identifier
|
||||
var list bit_string tuple
|
||||
var list list_args bit_string tuple
|
||||
do_block fn_eol do_eol end_eol block_eol block_item block_list
|
||||
.
|
||||
|
||||
@@ -30,8 +30,8 @@ Terminals
|
||||
number signed_number atom atom_string bin_string list_string sigil
|
||||
dot_call_op op_identifier
|
||||
comp_op at_op unary_op and_op or_op arrow_op match_op in_op in_match_op
|
||||
dual_op add_op mult_op exp_op two_op type_op stab_op when_op
|
||||
'true' 'false' 'nil' 'do' eol ',' '.' '&'
|
||||
dual_op add_op mult_op exp_op two_op pipe_op stab_op when_op
|
||||
'true' 'false' 'nil' 'do' eol ',' '.'
|
||||
'(' ')' '[' ']' '{' '}' '<<' '>>'
|
||||
.
|
||||
|
||||
@@ -44,16 +44,16 @@ Expect 2.
|
||||
Left 5 do.
|
||||
Right 10 stab_op_eol. %% ->
|
||||
Left 20 ','.
|
||||
Right 40 type_op_eol. %% ::
|
||||
Right 50 in_match_op_eol. %% inlist, inbits, //, | (allowed in matches, lower precedence than =)
|
||||
Left 40 in_match_op_eol. %% <-, inlist, inbits, //, \\, :: (allowed in matches along =)
|
||||
Right 50 pipe_op_eol. %% |
|
||||
Right 70 when_op_eol. %% when
|
||||
Right 80 match_op_eol. %% =
|
||||
Left 130 or_op_eol. %% ||, |||, or, xor
|
||||
Left 140 and_op_eol. %% &&, &&&, and
|
||||
Left 150 comp_op_eol. %% <, >, <=, >=, ==, !=, =~, ===, !==
|
||||
Right 160 arrow_op_eol. %% < (op), (op) > (e.g <-, |>, <<<, >>>)
|
||||
Right 160 arrow_op_eol. %% < (op), (op) > (e.g |>, <<<, >>>)
|
||||
Left 170 in_op_eol. %% in
|
||||
Right 200 two_op_eol. %% ++, --, **, .., <>
|
||||
Right 200 two_op_eol. %% ++, --, .., <>
|
||||
Left 210 add_op_eol. %% + (op), - (op)
|
||||
Left 220 mult_op_eol. %% * (op), / (op)
|
||||
Left 250 exp_op_eol. %% ^ (op) (e.g ^^^)
|
||||
@@ -105,6 +105,7 @@ expr -> unmatched_expr : '$1'.
|
||||
%% segments and act accordingly.
|
||||
matched_expr -> matched_expr matched_op_expr : build_op(element(1, '$2'), '$1', element(2, '$2')).
|
||||
matched_expr -> matched_expr no_parens_op_expr : build_op(element(1, '$2'), '$1', element(2, '$2')).
|
||||
matched_expr -> unary_op_eol number : build_unary_op('$1', ?exprs('$2')).
|
||||
matched_expr -> unary_op_eol matched_expr : build_unary_op('$1', '$2').
|
||||
matched_expr -> unary_op_eol no_parens_expr : build_unary_op('$1', '$2').
|
||||
matched_expr -> at_op_eol matched_expr : build_unary_op('$1', '$2').
|
||||
@@ -137,7 +138,7 @@ op_expr -> or_op_eol expr : { '$1', '$2' }.
|
||||
op_expr -> in_op_eol expr : { '$1', '$2' }.
|
||||
op_expr -> in_match_op_eol expr : { '$1', '$2' }.
|
||||
op_expr -> when_op_eol expr : { '$1', '$2' }.
|
||||
op_expr -> type_op_eol expr : { '$1', '$2' }.
|
||||
op_expr -> pipe_op_eol expr : { '$1', '$2' }.
|
||||
op_expr -> comp_op_eol expr : { '$1', '$2' }.
|
||||
op_expr -> arrow_op_eol expr : { '$1', '$2' }.
|
||||
|
||||
@@ -150,7 +151,7 @@ no_parens_op_expr -> and_op_eol no_parens_expr : { '$1', '$2' }.
|
||||
no_parens_op_expr -> or_op_eol no_parens_expr : { '$1', '$2' }.
|
||||
no_parens_op_expr -> in_op_eol no_parens_expr : { '$1', '$2' }.
|
||||
no_parens_op_expr -> in_match_op_eol no_parens_expr : { '$1', '$2' }.
|
||||
no_parens_op_expr -> type_op_eol no_parens_expr : { '$1', '$2' }.
|
||||
no_parens_op_expr -> pipe_op_eol no_parens_expr : { '$1', '$2' }.
|
||||
no_parens_op_expr -> comp_op_eol no_parens_expr : { '$1', '$2' }.
|
||||
no_parens_op_expr -> arrow_op_eol no_parens_expr : { '$1', '$2' }.
|
||||
|
||||
@@ -168,7 +169,7 @@ matched_op_expr -> or_op_eol matched_expr : { '$1', '$2' }.
|
||||
matched_op_expr -> in_op_eol matched_expr : { '$1', '$2' }.
|
||||
matched_op_expr -> in_match_op_eol matched_expr : { '$1', '$2' }.
|
||||
matched_op_expr -> when_op_eol matched_expr : { '$1', '$2' }.
|
||||
matched_op_expr -> type_op_eol matched_expr : { '$1', '$2' }.
|
||||
matched_op_expr -> pipe_op_eol matched_expr : { '$1', '$2' }.
|
||||
matched_op_expr -> comp_op_eol matched_expr : { '$1', '$2' }.
|
||||
matched_op_expr -> arrow_op_eol matched_expr : { '$1', '$2' }.
|
||||
|
||||
@@ -210,7 +211,6 @@ base_expr -> list_string : build_list_string('$1').
|
||||
base_expr -> atom_string : build_atom_string('$1').
|
||||
base_expr -> bit_string : '$1'.
|
||||
base_expr -> sigil : build_sigil('$1').
|
||||
base_expr -> '&' number : { '&', meta('$1'), [?exprs('$2')] }.
|
||||
|
||||
%% Blocks
|
||||
|
||||
@@ -244,7 +244,7 @@ stab_expr -> call_args_no_parens_all stab_op_eol stab_maybe_expr :
|
||||
stab_expr -> stab_parens_many stab_op_eol stab_maybe_expr :
|
||||
build_op('$2', unwrap_splice('$1'), '$3').
|
||||
stab_expr -> stab_parens_many when_op expr stab_op_eol stab_maybe_expr :
|
||||
build_op('$4', [{ 'when', meta('$2'), unwrap_splice('$1') ++ ['$3'] }], '$5').
|
||||
build_op('$4', [{ 'when', meta('$2'), unwrap_splice('$1') ++ ['$3'] }], '$5').
|
||||
|
||||
stab_maybe_expr -> 'expr' : '$1'.
|
||||
stab_maybe_expr -> '$empty' : nil.
|
||||
@@ -297,8 +297,8 @@ exp_op_eol -> exp_op eol : '$1'.
|
||||
two_op_eol -> two_op : '$1'.
|
||||
two_op_eol -> two_op eol : '$1'.
|
||||
|
||||
type_op_eol -> type_op : '$1'.
|
||||
type_op_eol -> type_op eol : '$1'.
|
||||
pipe_op_eol -> pipe_op : '$1'.
|
||||
pipe_op_eol -> pipe_op eol : '$1'.
|
||||
|
||||
unary_op_eol -> unary_op : '$1'.
|
||||
unary_op_eol -> unary_op eol : '$1'.
|
||||
@@ -396,27 +396,22 @@ container_expr -> matched_expr : '$1'.
|
||||
container_expr -> unmatched_expr : '$1'.
|
||||
container_expr -> no_parens_expr : throw_no_parens_many_strict('$1').
|
||||
|
||||
container_comma_expr_base -> container_expr ',' : ['$1'].
|
||||
container_comma_expr_base -> container_comma_expr_base container_expr ',' : ['$2'|'$1'].
|
||||
container_args_base -> container_expr : ['$1'].
|
||||
container_args_base -> container_args_base ',' container_expr : ['$3'|'$1'].
|
||||
|
||||
container_comma_expr -> container_expr : ['$1'].
|
||||
container_comma_expr -> kw : ['$1'].
|
||||
container_comma_expr -> container_comma_expr_base : '$1'.
|
||||
container_comma_expr -> container_comma_expr_base container_expr : ['$2'|'$1'].
|
||||
container_comma_expr -> container_comma_expr_base kw : ['$2'|'$1'].
|
||||
container_args -> kw : ['$1'].
|
||||
container_args -> container_args_base : lists:reverse('$1').
|
||||
container_args -> container_args_base ',' : lists:reverse('$1').
|
||||
container_args -> container_args_base ',' kw : lists:reverse(['$3'|'$1']).
|
||||
|
||||
container_arg -> container_expr : '$1'.
|
||||
container_arg -> container_expr ',' : '$1'.
|
||||
container_args -> container_comma_expr : lists:reverse('$1').
|
||||
|
||||
call_args_parens_comma_expr -> container_expr : ['$1'].
|
||||
call_args_parens_comma_expr -> call_args_parens_comma_expr ',' container_expr : ['$3'|'$1'].
|
||||
call_args_parens_base -> container_expr : ['$1'].
|
||||
call_args_parens_base -> call_args_parens_base ',' container_expr : ['$3'|'$1'].
|
||||
|
||||
call_args_parens -> empty_paren : [].
|
||||
call_args_parens -> open_paren no_parens_expr close_paren : ['$2'].
|
||||
call_args_parens -> open_paren kw close_paren : ['$2'].
|
||||
call_args_parens -> open_paren call_args_parens_comma_expr close_paren : lists:reverse('$2').
|
||||
call_args_parens -> open_paren call_args_parens_comma_expr ',' kw close_paren : lists:reverse(['$4'|'$2']).
|
||||
call_args_parens -> open_paren call_args_parens_base close_paren : lists:reverse('$2').
|
||||
call_args_parens -> open_paren call_args_parens_base ',' kw close_paren : lists:reverse(['$4'|'$2']).
|
||||
|
||||
% KV
|
||||
|
||||
@@ -426,6 +421,7 @@ kw_eol -> kw_identifier eol : '$1'.
|
||||
kw_expr -> kw_eol container_expr : { ?exprs('$1'),'$2' }.
|
||||
kw_comma -> kw_expr ',' : ['$1'].
|
||||
kw_comma -> kw_comma kw_expr ',' : ['$2'|'$1'].
|
||||
|
||||
kw -> kw_expr : ['$1'].
|
||||
kw -> kw_comma : lists:reverse('$1').
|
||||
kw -> kw_comma kw_expr : lists:reverse(['$2'|'$1']).
|
||||
@@ -437,16 +433,18 @@ call_args_no_parens_kw -> call_args_no_parens_kw_comma : '$1'.
|
||||
|
||||
% Lists
|
||||
|
||||
list_args -> kw : '$1'.
|
||||
list_args -> container_args_base : lists:reverse('$1').
|
||||
list_args -> container_args_base ',' : lists:reverse('$1').
|
||||
list_args -> container_args_base ',' kw : lists:reverse('$1') ++ '$3'.
|
||||
|
||||
list -> open_bracket ']' : { [], ?line('$1') }.
|
||||
list -> open_bracket kw close_bracket : { '$2', ?line('$1') }.
|
||||
list -> open_bracket container_arg close_bracket : { ['$2'], ?line('$1') }.
|
||||
list -> open_bracket container_expr ',' container_args close_bracket : { ['$2'|'$4'], ?line('$1') }.
|
||||
list -> open_bracket list_args close_bracket : { '$2', ?line('$1') }.
|
||||
|
||||
% Tuple
|
||||
|
||||
tuple -> open_curly '}' : build_tuple('$1', []).
|
||||
tuple -> open_curly container_arg close_curly : build_tuple('$1', ['$2']).
|
||||
tuple -> open_curly container_expr ',' container_args close_curly : build_tuple('$1', ['$2'|'$4']).
|
||||
tuple -> open_curly container_args close_curly : build_tuple('$1', '$2').
|
||||
|
||||
% Bitstrings
|
||||
|
||||
@@ -458,10 +456,7 @@ Erlang code.
|
||||
-define(id(Node), element(1, Node)).
|
||||
-define(line(Node), element(2, Node)).
|
||||
-define(exprs(Node), element(3, Node)).
|
||||
-define(lexical(Kind), Kind == import; Kind == alias; Kind == '__aliases__').
|
||||
|
||||
-define(rearrange_uop(Op), Op == 'not' orelse Op == '!').
|
||||
-define(rearrange_bop(Op), Op == 'in' orelse Op == 'inlist' orelse Op == 'inbits').
|
||||
|
||||
%% The following directive is needed for (significantly) faster
|
||||
%% compilation of the generated .erl file by the HiPE compiler
|
||||
@@ -479,8 +474,8 @@ build_op({ _Kind, Line, '/' }, { '&', _, [{ Kind, _, Atom } = Left] }, Right) wh
|
||||
build_op({ _Kind, Line, '/' }, { '&', _, [{ { '.', _, [_, _] }, _, [] } = Left] }, Right) when is_number(Right) ->
|
||||
{ '&', meta(Line), [{ '/', meta(Line), [Left, Right] }] };
|
||||
|
||||
build_op({ _Kind, Line, BOp }, { UOp, _, [Left] }, Right) when ?rearrange_bop(BOp), ?rearrange_uop(UOp) ->
|
||||
{ UOp, meta(Line), [{ BOp, meta(Line), [Left, Right] }] };
|
||||
build_op({ _Kind, Line, 'in' }, { UOp, _, [Left] }, Right) when ?rearrange_uop(UOp) ->
|
||||
{ UOp, meta(Line), [{ 'in', meta(Line), [Left, Right] }] };
|
||||
|
||||
build_op({ _Kind, Line, Op }, Left, Right) ->
|
||||
{ Op, meta(Line), [Left, Right] }.
|
||||
@@ -513,11 +508,17 @@ build_dot_alias(Dot, Other, { 'aliases', _, Right }) ->
|
||||
build_dot(Dot, Left, Right) ->
|
||||
{ '.', meta(Dot), [Left, extract_identifier(Right)] }.
|
||||
|
||||
extract_identifier({ Kind, _, Identifier }) when
|
||||
Kind == identifier; Kind == bracket_identifier; Kind == paren_identifier;
|
||||
Kind == do_identifier; Kind == op_identifier ->
|
||||
Identifier.
|
||||
|
||||
%% Identifiers
|
||||
|
||||
build_nested_parens(Dot, Args1, Args2) ->
|
||||
Identifier = build_identifier(Dot, Args1),
|
||||
{ Identifier, ?line(Identifier), Args2 }.
|
||||
Meta = element(2, Identifier),
|
||||
{ Identifier, Meta, Args2 }.
|
||||
|
||||
build_identifier({ '.', Meta, _ } = Dot, Args) ->
|
||||
FArgs = case Args of
|
||||
@@ -532,19 +533,9 @@ build_identifier({ Keyword, Line }, Args) when Keyword == fn ->
|
||||
build_identifier({ op_identifier, Line, Identifier }, [Arg]) ->
|
||||
{ Identifier, [{ambiguous_op,nil}|meta(Line)], [Arg] };
|
||||
|
||||
build_identifier({ _, Line, Identifier }, Args) when ?lexical(Identifier) ->
|
||||
{ Identifier, meta(Line, 0), Args };
|
||||
|
||||
build_identifier({ _, Line, Identifier }, Args) ->
|
||||
{ Identifier, meta(Line), Args }.
|
||||
|
||||
extract_identifier({ Kind, _, Identifier }) when
|
||||
Kind == identifier; Kind == bracket_identifier; Kind == paren_identifier;
|
||||
Kind == do_identifier; Kind == op_identifier ->
|
||||
Identifier;
|
||||
|
||||
extract_identifier(Other) -> Other.
|
||||
|
||||
%% Fn
|
||||
|
||||
build_fn(Op, Stab) ->
|
||||
|
||||
+140
-134
@@ -24,7 +24,6 @@
|
||||
-define(two_op(T1, T2),
|
||||
T1 == $+, T2 == $+;
|
||||
T1 == $-, T2 == $-;
|
||||
T1 == $*, T2 == $*;
|
||||
T1 == $<, T2 == $>;
|
||||
T1 == $., T2 == $.).
|
||||
|
||||
@@ -39,7 +38,6 @@
|
||||
T1 == $>, T2 == $>, T3 == $>).
|
||||
|
||||
-define(arrow_op(T1, T2),
|
||||
T1 == $<, T2 == $-;
|
||||
T1 == $|, T2 == $>).
|
||||
|
||||
-define(comp_op(T),
|
||||
@@ -72,17 +70,17 @@
|
||||
-define(match_op(T),
|
||||
T == $=).
|
||||
|
||||
-define(in_match_op(T),
|
||||
T == $|).
|
||||
|
||||
-define(in_match_op2(T1, T2),
|
||||
T1 == $/, T2 == $/).
|
||||
-define(in_match_op(T1, T2),
|
||||
T1 == $<, T2 == $-;
|
||||
T1 == $/, T2 == $/;
|
||||
T1 == $\\, T2 == $\\;
|
||||
T1 == $:, T2 == $:).
|
||||
|
||||
-define(stab_op(T1, T2),
|
||||
T1 == $-, T2 == $>).
|
||||
|
||||
-define(type_op(T1, T2),
|
||||
T1 == $:, T2 == $:).
|
||||
-define(pipe_op(T1),
|
||||
T == $|).
|
||||
|
||||
tokenize(String, Line, #elixir_tokenizer{} = Scope) ->
|
||||
tokenize(String, Line, Scope, []);
|
||||
@@ -140,15 +138,21 @@ tokenize([$#|String], Line, Scope, Tokens) ->
|
||||
% Sigils
|
||||
|
||||
tokenize([$%,S,H,H,H|T] = Original, Line, Scope, Tokens) when ?is_quote(H), ?is_upcase(S) orelse ?is_downcase(S) ->
|
||||
io:format(standard_error, "~ts:~p: warning: using % for sigils is deprecated, "
|
||||
"please use ~~ instead~n", [Scope#elixir_tokenizer.file, Line]),
|
||||
|
||||
case extract_heredoc_with_interpolation(Line, Scope, ?is_downcase(S), T, H) of
|
||||
{ error, Reason } ->
|
||||
{ error, Reason, Original, Tokens };
|
||||
{ Parts, Rest } ->
|
||||
{ ok, NewLine, Parts, Rest } ->
|
||||
{ Final, Modifiers } = collect_modifiers(Rest, []),
|
||||
tokenize(Final, Line, Scope, [{ sigil, Line, S, Parts, Modifiers }|Tokens])
|
||||
tokenize(Final, NewLine, Scope, [{ sigil, Line, S, Parts, Modifiers }|Tokens]);
|
||||
{ error, Reason } ->
|
||||
{ error, Reason, Original, Tokens }
|
||||
end;
|
||||
|
||||
tokenize([$%,S,H|T] = Original, Line, Scope, Tokens) when not(?is_identifier(H)), not(?is_terminator(H)), ?is_upcase(S) orelse ?is_downcase(S) ->
|
||||
tokenize([$%,S,H|T] = Original, Line, Scope, Tokens) when ?is_sigil(H), ?is_upcase(S) orelse ?is_downcase(S) ->
|
||||
io:format(standard_error, "~ts:~p: warning: using % for sigils is deprecated, "
|
||||
"please use ~~ instead~n", [Scope#elixir_tokenizer.file, Line]),
|
||||
|
||||
case elixir_interpolation:extract(Line, Scope, ?is_downcase(S), T, sigil_terminator(H)) of
|
||||
{ NewLine, Parts, Rest } ->
|
||||
{ Final, Modifiers } = collect_modifiers(Rest, []),
|
||||
@@ -158,6 +162,25 @@ tokenize([$%,S,H|T] = Original, Line, Scope, Tokens) when not(?is_identifier(H))
|
||||
interpolation_error(Reason, Original, Tokens, " (for sigil ~ts starting at line ~B)", [Sigil, Line])
|
||||
end;
|
||||
|
||||
tokenize([$~,S,H,H,H|T] = Original, Line, Scope, Tokens) when ?is_quote(H), ?is_upcase(S) orelse ?is_downcase(S) ->
|
||||
case extract_heredoc_with_interpolation(Line, Scope, ?is_downcase(S), T, H) of
|
||||
{ ok, NewLine, Parts, Rest } ->
|
||||
{ Final, Modifiers } = collect_modifiers(Rest, []),
|
||||
tokenize(Final, NewLine, Scope, [{ sigil, Line, S, Parts, Modifiers }|Tokens]);
|
||||
{ error, Reason } ->
|
||||
{ error, Reason, Original, Tokens }
|
||||
end;
|
||||
|
||||
tokenize([$~,S,H|T] = Original, Line, Scope, Tokens) when ?is_sigil(H), ?is_upcase(S) orelse ?is_downcase(S) ->
|
||||
case elixir_interpolation:extract(Line, Scope, ?is_downcase(S), T, sigil_terminator(H)) of
|
||||
{ NewLine, Parts, Rest } ->
|
||||
{ Final, Modifiers } = collect_modifiers(Rest, []),
|
||||
tokenize(Final, NewLine, Scope, [{ sigil, Line, S, Parts, Modifiers }|Tokens]);
|
||||
{ error, Reason } ->
|
||||
Sigil = [$~,S,H],
|
||||
interpolation_error(Reason, Original, Tokens, " (for sigil ~ts starting at line ~B)", [Sigil, Line])
|
||||
end;
|
||||
|
||||
% Char tokens
|
||||
|
||||
tokenize([$?,$\\,P,${,A,B,C,D,E,F,$}|T], Line, Scope, Tokens) when (P == $x orelse P == $X), ?is_hex(A), ?is_hex(B), ?is_hex(C), ?is_hex(D), ?is_hex(E), ?is_hex(F) ->
|
||||
@@ -211,56 +234,6 @@ tokenize([$?,$\\,H|T], Line, Scope, Tokens) ->
|
||||
tokenize([$?,Char|T], Line, Scope, Tokens) ->
|
||||
tokenize(T, Line, Scope, [{ number, Line, Char }|Tokens]);
|
||||
|
||||
% Dot identifier/operators
|
||||
tokenize("..." ++ Rest, Line, Scope, Tokens) ->
|
||||
Token = check_call_identifier(identifier, Line, '...', Rest),
|
||||
tokenize(Rest, Line, Scope, [Token|Tokens]);
|
||||
|
||||
tokenize([$.,T|Tail], Line, Scope, Tokens) when ?is_space(T) ->
|
||||
case [T|Tail] of
|
||||
[$\r,$\n|Rest] -> tokenize([$.|Rest], Line + 1, Scope, Tokens);
|
||||
[$\n|Rest] -> tokenize([$.|Rest], Line + 1, Scope, Tokens);
|
||||
[_|Rest] -> tokenize([$.|Rest], Line, Scope, Tokens)
|
||||
end;
|
||||
|
||||
% ## Three Token Operators
|
||||
tokenize([$.,T1,T2,T3|Rest], Line, Scope, Tokens) when
|
||||
?unary_op3(T1, T2, T3); ?comp_op3(T1, T2, T3); ?and_op3(T1, T2, T3); ?or_op3(T1, T2, T3);
|
||||
?arrow_op3(T1, T2, T3); ?exp_op3(T1, T2, T3) ->
|
||||
handle_call_identifier(Rest, Line, list_to_atom([T1, T2, T3]), Scope, Tokens);
|
||||
|
||||
% ## Two Token Operators
|
||||
tokenize([$.,T1,T2|Rest], Line, Scope, Tokens) when
|
||||
?comp_op2(T1, T2); ?and_op(T1, T2); ?or_op(T1, T2); ?arrow_op(T1, T2);
|
||||
?in_match_op2(T1, T2); ?two_op(T1, T2); ?stab_op(T1, T2); ?type_op(T1, T2) ->
|
||||
handle_call_identifier(Rest, Line, list_to_atom([T1, T2]), Scope, Tokens);
|
||||
|
||||
% ## Single Token Operators
|
||||
tokenize([$.,T|Rest], Line, Scope, Tokens) when
|
||||
?at_op(T); ?unary_op(T); ?dual_op(T); ?mult_op(T); ?comp_op(T);
|
||||
?match_op(T); ?in_match_op(T) ->
|
||||
handle_call_identifier(Rest, Line, list_to_atom([T]), Scope, Tokens);
|
||||
|
||||
% Dot call
|
||||
|
||||
% ## Exception for .( as it needs to be treated specially in the parser
|
||||
tokenize([$.,$(|Rest], Line, Scope, Tokens) ->
|
||||
tokenize([$(|Rest], Line, Scope, add_token_with_nl({ dot_call_op, Line, '.' }, Tokens));
|
||||
|
||||
tokenize([$.,H|T] = Original, Line, Scope, Tokens) when ?is_quote(H) ->
|
||||
case elixir_interpolation:extract(Line, Scope, true, T, H) of
|
||||
{ NewLine, [Part], Rest } when is_binary(Part) ->
|
||||
case unsafe_to_atom(Part, Line, Scope) of
|
||||
{ ok, Atom } ->
|
||||
Token = check_call_identifier(identifier, Line, Atom, Rest),
|
||||
tokenize(Rest, NewLine, Scope, [Token|add_token_with_nl({ '.', Line }, Tokens)]);
|
||||
{ error, Reason } ->
|
||||
{ error, Reason, Original, Tokens }
|
||||
end;
|
||||
{ error, Reason } ->
|
||||
interpolation_error(Reason, Original, Tokens, " (for function name starting at line ~B)", [Line])
|
||||
end;
|
||||
|
||||
% Heredocs
|
||||
|
||||
tokenize("\"\"\"" ++ T, Line, Scope, Tokens) ->
|
||||
@@ -318,13 +291,13 @@ tokenize([$:,T1,T2,T3|Rest], Line, Scope, Tokens) when
|
||||
% ## Two Token Operators
|
||||
tokenize([$:,T1,T2|Rest], Line, Scope, Tokens) when
|
||||
?comp_op2(T1, T2); ?and_op(T1, T2); ?or_op(T1, T2); ?arrow_op(T1, T2);
|
||||
?in_match_op2(T1, T2); ?two_op(T1, T2); ?stab_op(T1, T2); ?type_op(T1, T2) ->
|
||||
?in_match_op(T1, T2); ?two_op(T1, T2); ?stab_op(T1, T2) ->
|
||||
tokenize(Rest, Line, Scope, [{ atom, Line, list_to_atom([T1,T2]) }|Tokens]);
|
||||
|
||||
% ## Single Token Operators
|
||||
tokenize([$:,T|Rest], Line, Scope, Tokens) when
|
||||
?at_op(T); ?unary_op(T); ?dual_op(T); ?mult_op(T); ?comp_op(T);
|
||||
?match_op(T); ?in_match_op(T); T == $. ->
|
||||
?match_op(T); ?pipe_op(T); T == $. ->
|
||||
tokenize(Rest, Line, Scope, [{ atom, Line, list_to_atom([T]) }|Tokens]);
|
||||
|
||||
% End of line
|
||||
@@ -349,9 +322,13 @@ tokenize("\r\n" ++ Rest, Line, Scope, Tokens) ->
|
||||
|
||||
% Stand-alone tokens
|
||||
|
||||
tokenize("..." ++ Rest, Line, Scope, Tokens) ->
|
||||
Token = check_call_identifier(identifier, Line, '...', Rest),
|
||||
tokenize(Rest, Line, Scope, [Token|Tokens]);
|
||||
|
||||
% ## Three token operators
|
||||
tokenize([T1,T2,T3|Rest], Line, Scope, Tokens) when ?unary_op3(T1, T2, T3) ->
|
||||
handle_nonl_op(Rest, Line, unary_op, list_to_atom([T1,T2,T3]), Scope, Tokens);
|
||||
handle_unary_op(Rest, Line, unary_op, list_to_atom([T1,T2,T3]), Scope, Tokens);
|
||||
|
||||
tokenize([T1,T2,T3|Rest], Line, Scope, Tokens) when ?comp_op3(T1, T2, T3) ->
|
||||
handle_op(Rest, Line, comp_op, list_to_atom([T1,T2,T3]), Scope, Tokens);
|
||||
@@ -394,32 +371,25 @@ tokenize([T1,T2|Rest], Line, Scope, Tokens) when ?and_op(T1, T2) ->
|
||||
tokenize([T1,T2|Rest], Line, Scope, Tokens) when ?or_op(T1, T2) ->
|
||||
handle_op(Rest, Line, or_op, list_to_atom([T1, T2]), Scope, Tokens);
|
||||
|
||||
tokenize([T1,T2|Rest], Line, Scope, Tokens) when ?in_match_op2(T1, T2) ->
|
||||
tokenize([T1,T2|Rest], Line, Scope, Tokens) when ?in_match_op(T1, T2) ->
|
||||
handle_op(Rest, Line, in_match_op, list_to_atom([T1, T2]), Scope, Tokens);
|
||||
|
||||
tokenize([T1,T2|Rest], Line, Scope, Tokens) when ?stab_op(T1, T2) ->
|
||||
handle_op(Rest, Line, stab_op, list_to_atom([T1, T2]), Scope, Tokens);
|
||||
|
||||
tokenize([T1,T2|Rest], Line, Scope, Tokens) when ?type_op(T1, T2) ->
|
||||
handle_op(Rest, Line, type_op, list_to_atom([T1, T2]), Scope, Tokens);
|
||||
|
||||
% ## Single Token Operators
|
||||
|
||||
%% Handle &1 and friends with special precedence.
|
||||
tokenize([$&,D|Rest], Line, Scope, Tokens) when ?is_digit(D) ->
|
||||
tokenize([D|Rest], Line, Scope, [{ '&', Line }|Tokens]);
|
||||
|
||||
tokenize([T|Rest], Line, Scope, Tokens) when ?at_op(T) ->
|
||||
handle_nonl_op(Rest, Line, at_op, list_to_atom([T]), Scope, Tokens);
|
||||
handle_unary_op(Rest, Line, at_op, list_to_atom([T]), Scope, Tokens);
|
||||
|
||||
tokenize([T|Rest], Line, Scope, Tokens) when ?unary_op(T) ->
|
||||
handle_nonl_op(Rest, Line, unary_op, list_to_atom([T]), Scope, Tokens);
|
||||
handle_unary_op(Rest, Line, unary_op, list_to_atom([T]), Scope, Tokens);
|
||||
|
||||
tokenize([T|Rest], Line, Scope, Tokens) when ?comp_op(T) ->
|
||||
handle_op(Rest, Line, comp_op, list_to_atom([T]), Scope, Tokens);
|
||||
|
||||
tokenize([T|Rest], Line, Scope, Tokens) when ?dual_op(T) ->
|
||||
handle_nonl_op(Rest, Line, dual_op, list_to_atom([T]), Scope, Tokens);
|
||||
handle_unary_op(Rest, Line, dual_op, list_to_atom([T]), Scope, Tokens);
|
||||
|
||||
tokenize([T|Rest], Line, Scope, Tokens) when ?mult_op(T) ->
|
||||
handle_op(Rest, Line, mult_op, list_to_atom([T]), Scope, Tokens);
|
||||
@@ -427,11 +397,14 @@ tokenize([T|Rest], Line, Scope, Tokens) when ?mult_op(T) ->
|
||||
tokenize([T|Rest], Line, Scope, Tokens) when ?match_op(T) ->
|
||||
handle_op(Rest, Line, match_op, list_to_atom([T]), Scope, Tokens);
|
||||
|
||||
tokenize([T|Rest], Line, Scope, Tokens) when ?in_match_op(T) ->
|
||||
handle_op(Rest, Line, in_match_op, list_to_atom([T]), Scope, Tokens);
|
||||
tokenize([T|Rest], Line, Scope, Tokens) when ?pipe_op(T) ->
|
||||
handle_op(Rest, Line, pipe_op, list_to_atom([T]), Scope, Tokens);
|
||||
|
||||
tokenize([$.|Rest], Line, Scope, Tokens) ->
|
||||
tokenize(Rest, Line, Scope, add_token_with_nl({ '.', Line }, Tokens));
|
||||
% Dot
|
||||
|
||||
tokenize([$.|T], Line, Scope, Tokens) ->
|
||||
{ Rest, Counter } = strip_space(T, 0),
|
||||
handle_dot([$.|Rest], Line + Counter, Scope, Tokens);
|
||||
|
||||
% Integers and floats
|
||||
|
||||
@@ -481,15 +454,22 @@ tokenize([Space, Sign, NotMarker|T], Line, Scope, [{ Identifier, _, _ } = H|Toke
|
||||
% Spaces
|
||||
|
||||
tokenize([T|Rest], Line, Scope, Tokens) when ?is_horizontal_space(T) ->
|
||||
tokenize(Rest, Line, Scope, Tokens);
|
||||
|
||||
%% TODO: This token is deprecated once continuable heredocs are removed
|
||||
tokenize([{line,Line}|Rest], _Line, Scope, Tokens) ->
|
||||
tokenize(Rest, Line, Scope, Tokens);
|
||||
|
||||
tokenize(strip_horizontal_space(Rest), Line, Scope, Tokens);
|
||||
tokenize(T, Line, _Scope, Tokens) ->
|
||||
{ error, { Line, "invalid token: ", until_eol(T) }, T, Tokens }.
|
||||
|
||||
strip_horizontal_space([H|T]) when ?is_horizontal_space(H) ->
|
||||
strip_horizontal_space(T);
|
||||
strip_horizontal_space(T) ->
|
||||
T.
|
||||
|
||||
strip_space(T, Counter) ->
|
||||
case strip_horizontal_space(T) of
|
||||
"\r\n" ++ Rest -> strip_space(Rest, Counter + 1);
|
||||
"\n" ++ Rest -> strip_space(Rest, Counter + 1);
|
||||
Rest -> { Rest, Counter }
|
||||
end.
|
||||
|
||||
until_eol("\r\n" ++ _) -> [];
|
||||
until_eol("\n" ++ _) -> [];
|
||||
until_eol([]) -> [];
|
||||
@@ -503,11 +483,11 @@ escape_char(List) ->
|
||||
|
||||
handle_heredocs(T, Line, H, Scope, Tokens) ->
|
||||
case extract_heredoc_with_interpolation(Line, Scope, true, T, H) of
|
||||
{ error, Reason } ->
|
||||
{ error, Reason, [H, H, H] ++ T, Tokens };
|
||||
{ Parts, Rest } ->
|
||||
{ ok, NewLine, Parts, Rest } ->
|
||||
Token = { string_type(H), Line, unescape_tokens(Parts) },
|
||||
tokenize(Rest, Line, Scope, [Token|Tokens])
|
||||
tokenize(Rest, NewLine, Scope, [Token|Tokens]);
|
||||
{ error, Reason } ->
|
||||
{ error, Reason, [H, H, H] ++ T, Tokens }
|
||||
end.
|
||||
|
||||
handle_strings(T, Line, H, Scope, Tokens) ->
|
||||
@@ -531,17 +511,62 @@ handle_strings(T, Line, H, Scope, Tokens) ->
|
||||
tokenize(Rest, NewLine, Scope, [Token|Tokens])
|
||||
end.
|
||||
|
||||
handle_nonl_op([$:|Rest], Line, _Kind, Op, Scope, Tokens) when ?is_space(hd(Rest)) ->
|
||||
handle_unary_op([$:|Rest], Line, _Kind, Op, Scope, Tokens) when ?is_space(hd(Rest)) ->
|
||||
tokenize(Rest, Line, Scope, [{ kw_identifier, Line, Op }|Tokens]);
|
||||
|
||||
handle_nonl_op(Rest, Line, Kind, Op, Scope, Tokens) ->
|
||||
tokenize(Rest, Line, Scope, [{ Kind, Line, Op }|Tokens]).
|
||||
handle_unary_op(Rest, Line, Kind, Op, Scope, Tokens) ->
|
||||
case strip_space(Rest, 0) of
|
||||
{ [$/|_], _ } -> tokenize(Rest, Line, Scope, [{ identifier, Line, Op }|Tokens]);
|
||||
_ -> tokenize(Rest, Line, Scope, [{ Kind, Line, Op }|Tokens])
|
||||
end.
|
||||
|
||||
handle_op([$:|Rest], Line, _Kind, Op, Scope, Tokens) when ?is_space(hd(Rest)) ->
|
||||
tokenize(Rest, Line, Scope, [{ kw_identifier, Line, Op }|Tokens]);
|
||||
|
||||
handle_op(Rest, Line, Kind, Op, Scope, Tokens) ->
|
||||
tokenize(Rest, Line, Scope, add_token_with_nl({ Kind, Line, Op }, Tokens)).
|
||||
case strip_space(Rest, 0) of
|
||||
{ [$/|_], _ } -> tokenize(Rest, Line, Scope, [{ identifier, Line, Op }|Tokens]);
|
||||
_ -> tokenize(Rest, Line, Scope, add_token_with_nl({ Kind, Line, Op }, Tokens))
|
||||
end.
|
||||
|
||||
% ## Three Token Operators
|
||||
handle_dot([$.,T1,T2,T3|Rest], Line, Scope, Tokens) when
|
||||
?unary_op3(T1, T2, T3); ?comp_op3(T1, T2, T3); ?and_op3(T1, T2, T3); ?or_op3(T1, T2, T3);
|
||||
?arrow_op3(T1, T2, T3); ?exp_op3(T1, T2, T3) ->
|
||||
handle_call_identifier(Rest, Line, list_to_atom([T1, T2, T3]), Scope, Tokens);
|
||||
|
||||
% ## Two Token Operators
|
||||
handle_dot([$.,T1,T2|Rest], Line, Scope, Tokens) when
|
||||
?comp_op2(T1, T2); ?and_op(T1, T2); ?or_op(T1, T2); ?arrow_op(T1, T2);
|
||||
?in_match_op(T1, T2); ?two_op(T1, T2); ?stab_op(T1, T2) ->
|
||||
handle_call_identifier(Rest, Line, list_to_atom([T1, T2]), Scope, Tokens);
|
||||
|
||||
% ## Single Token Operators
|
||||
handle_dot([$.,T|Rest], Line, Scope, Tokens) when
|
||||
?at_op(T); ?unary_op(T); ?dual_op(T); ?mult_op(T); ?comp_op(T);
|
||||
?match_op(T); ?pipe_op(T) ->
|
||||
handle_call_identifier(Rest, Line, list_to_atom([T]), Scope, Tokens);
|
||||
|
||||
% ## Exception for .( as it needs to be treated specially in the parser
|
||||
handle_dot([$.,$(|Rest], Line, Scope, Tokens) ->
|
||||
tokenize([$(|Rest], Line, Scope, add_token_with_nl({ dot_call_op, Line, '.' }, Tokens));
|
||||
|
||||
handle_dot([$.,H|T] = Original, Line, Scope, Tokens) when ?is_quote(H) ->
|
||||
case elixir_interpolation:extract(Line, Scope, true, T, H) of
|
||||
{ NewLine, [Part], Rest } when is_binary(Part) ->
|
||||
case unsafe_to_atom(Part, Line, Scope) of
|
||||
{ ok, Atom } ->
|
||||
Token = check_call_identifier(identifier, Line, Atom, Rest),
|
||||
tokenize(Rest, NewLine, Scope, [Token|add_token_with_nl({ '.', Line }, Tokens)]);
|
||||
{ error, Reason } ->
|
||||
{ error, Reason, Original, Tokens }
|
||||
end;
|
||||
{ error, Reason } ->
|
||||
interpolation_error(Reason, Original, Tokens, " (for function name starting at line ~B)", [Line])
|
||||
end;
|
||||
|
||||
handle_dot([$.|Rest], Line, Scope, Tokens) ->
|
||||
tokenize(Rest, Line, Scope, add_token_with_nl({ '.', Line }, Tokens)).
|
||||
|
||||
handle_call_identifier(Rest, Line, Op, Scope, Tokens) ->
|
||||
Token = check_call_identifier(identifier, Line, Op, Rest),
|
||||
@@ -575,34 +600,33 @@ collect_modifiers(Rest, Buffer) ->
|
||||
%% Heredocs
|
||||
|
||||
extract_heredoc_with_interpolation(Line, Scope, Interpol, T, H) ->
|
||||
case extract_heredoc(Line, T, H, Scope) of
|
||||
{ error, _ } = Error ->
|
||||
Error;
|
||||
{ Body, Rest } ->
|
||||
case extract_heredoc(Line, T, H) of
|
||||
{ ok, NewLine, Body, Rest } ->
|
||||
case elixir_interpolation:extract(Line + 1, Scope, Interpol, Body, 0) of
|
||||
{ error, Reason } ->
|
||||
{ error, interpolation_format(Reason, " (for heredoc starting at line ~B)", [Line]) };
|
||||
{ _, Parts, [] } ->
|
||||
{ Parts, Rest }
|
||||
end
|
||||
{ ok, NewLine, Parts, Rest }
|
||||
end;
|
||||
{ error, _ } = Error ->
|
||||
Error
|
||||
end.
|
||||
|
||||
extract_heredoc(Line0, Rest0, Marker, Scope) ->
|
||||
case extract_heredoc_header(Rest0, [], {Line0,Scope#elixir_tokenizer.file}) of
|
||||
{ ok, Extra, Rest1 } ->
|
||||
extract_heredoc(Line0, Rest0, Marker) ->
|
||||
case extract_heredoc_header(Rest0) of
|
||||
{ ok, Rest1 } ->
|
||||
%% We prepend a new line so we can transparently remove
|
||||
%% spaces later. This new line is removed by calling `tl`
|
||||
%% in the final heredoc body three lines below.
|
||||
case extract_heredoc_body(Line0, Marker, [$\n|Rest1], []) of
|
||||
{ Line1, Body, Rest2, Spaces } ->
|
||||
{ tl(remove_heredoc_spaces(Body, Spaces)), merge_heredoc_extra(Line1, Extra, Rest2) };
|
||||
{ ok, Line1, Body, Rest2, Spaces } ->
|
||||
{ ok, Line1, tl(remove_heredoc_spaces(Body, Spaces)), Rest2 };
|
||||
{ error, ErrorLine } ->
|
||||
Terminator = [Marker, Marker, Marker],
|
||||
Message = "missing terminator: ~ts (for heredoc starting at line ~B)",
|
||||
{ error, { ErrorLine, io_lib:format(Message, [Terminator, Line0]), [] } }
|
||||
end;
|
||||
error ->
|
||||
%% TODO: Test me once the deprecated continuable heredocs are removed
|
||||
Terminator = [Marker, Marker, Marker],
|
||||
Message = "heredoc start ~ts must be followed by a new line",
|
||||
{ error, { Line0, io_lib:format(Message, [Terminator]), [] } }
|
||||
@@ -625,32 +649,25 @@ remove_heredoc_spaces([], Buffer, _Spaces, _Original) ->
|
||||
|
||||
%% Extract the heredoc header.
|
||||
|
||||
extract_heredoc_header("\r\n" ++ Rest, Buffer, _Scope) ->
|
||||
{ ok, [$\n|Buffer], Rest };
|
||||
extract_heredoc_header("\n" ++ Rest, Buffer, _Scope) ->
|
||||
{ ok, [$\n|Buffer], Rest };
|
||||
extract_heredoc_header([H|T], Buffer, {Line,File}) when not ?is_horizontal_space(H) ->
|
||||
elixir_errors:deprecation([{line,Line}], File, "continuable heredocs are deprecated, parsing will no longer continue on the same line as the heredoc starts"),
|
||||
extract_heredoc_header(T, [H|Buffer], nil);
|
||||
extract_heredoc_header([H|T], Buffer, _Scope) ->
|
||||
extract_heredoc_header(T, [H|Buffer], _Scope);
|
||||
extract_heredoc_header(_, _, _Scope) ->
|
||||
extract_heredoc_header("\r\n" ++ Rest) ->
|
||||
{ ok, Rest };
|
||||
extract_heredoc_header("\n" ++ Rest) ->
|
||||
{ ok, Rest };
|
||||
extract_heredoc_header([_|T]) ->
|
||||
extract_heredoc_header(T);
|
||||
extract_heredoc_header(_) ->
|
||||
error.
|
||||
|
||||
%% Extract heredoc body. It returns the heredoc body (in reverse order),
|
||||
%% the remaining of the document and the number of spaces the heredoc
|
||||
%% is aligned.
|
||||
|
||||
%% TODO: This token is deprecated once continuable heredocs are removed
|
||||
extract_heredoc_body(_Line, Marker, [{line,NewLine}|Rest], Buffer) ->
|
||||
extract_heredoc_body(NewLine, Marker, Rest, Buffer);
|
||||
|
||||
extract_heredoc_body(Line, Marker, Rest, Buffer) ->
|
||||
case extract_heredoc_line(Marker, Rest, Buffer, 0) of
|
||||
{ ok, NewBuffer, NewRest } ->
|
||||
extract_heredoc_body(Line + 1, Marker, NewRest, NewBuffer);
|
||||
{ ok, NewBuffer, NewRest, Spaces } ->
|
||||
{ Line, NewBuffer, NewRest, Spaces };
|
||||
{ ok, Line, NewBuffer, NewRest, Spaces };
|
||||
{ error, eof } ->
|
||||
{ error, Line }
|
||||
end.
|
||||
@@ -675,17 +692,6 @@ extract_heredoc_line(Marker, [Marker,Marker,Marker|T], Buffer, Counter) ->
|
||||
extract_heredoc_line(_Marker, Rest, Buffer, _Counter) ->
|
||||
extract_heredoc_line(Rest, Buffer).
|
||||
|
||||
%% Merge heredoc extra by replying it on the buffer. It also adds
|
||||
%% a special { line, Line } token to force a line change along the way.
|
||||
|
||||
%% TODO: This token is deprecated once continuable heredocs are removed
|
||||
merge_heredoc_extra(Line, Extra, Buffer) ->
|
||||
merge_heredoc_extra(Extra, [{line,Line}|Buffer]).
|
||||
merge_heredoc_extra([H|T], Buffer) ->
|
||||
merge_heredoc_extra(T, [H|Buffer]);
|
||||
merge_heredoc_extra([], Buffer) ->
|
||||
Buffer.
|
||||
|
||||
%% Integers and floats
|
||||
%% At this point, we are at least sure the first digit is a number.
|
||||
|
||||
|
||||
@@ -86,14 +86,19 @@ translate({'try', Meta, [Clauses]}, RS) when is_list(Clauses) ->
|
||||
Catch = [Tuple || { X, _ } = Tuple <- Clauses, X == 'rescue' orelse X == 'catch'],
|
||||
{ TCatch, SC } = elixir_try:clauses(Meta, Catch, mergec(S, SB)),
|
||||
|
||||
After = proplists:get_value('after', Clauses, nil),
|
||||
{ TAfter, SA } = translate(After, mergec(S, SC)),
|
||||
case lists:keyfind('after', 1, Clauses) of
|
||||
{ 'after', After } ->
|
||||
{ TBlock, SA } = translate(After, mergec(S, SC)),
|
||||
TAfter = unblock(TBlock);
|
||||
false ->
|
||||
{ TAfter, SA } = { [], mergec(S, SC) }
|
||||
end,
|
||||
|
||||
Else = elixir_clauses:get_pairs(else, Clauses),
|
||||
{ TElse, SE } = elixir_clauses:clauses(Meta, Else, mergec(S, SA)),
|
||||
|
||||
SF = (mergec(S, SE))#elixir_scope{noname=RS#elixir_scope.noname},
|
||||
{ { 'try', ?line(Meta), unblock(TDo), TElse, TCatch, unblock(TAfter) }, SF };
|
||||
{ { 'try', ?line(Meta), unblock(TDo), TElse, TCatch, TAfter }, SF };
|
||||
|
||||
%% Receive
|
||||
|
||||
@@ -144,6 +149,8 @@ translate({ '^', Meta, [ { Name, VarMeta, Kind } = Var ] },
|
||||
Tuple = { Name, var_kind(VarMeta, Kind) },
|
||||
case orddict:find(Tuple, S#elixir_scope.backup_vars) of
|
||||
{ ok, { Value, _Counter } } ->
|
||||
elixir_errors:deprecation(Meta, S#elixir_scope.file, "using ^ inside function clause heads is "
|
||||
"deprecated, please use a guard instead"),
|
||||
Line = ?line(Meta),
|
||||
{ TVar, TS } = translate(Var, S),
|
||||
Guard = { op, Line, '=:=', { var, ?line(Meta), Value }, TVar },
|
||||
@@ -198,17 +205,23 @@ translate({ Name, Meta, Args }, S) when is_atom(Name), is_list(Meta), is_list(Ar
|
||||
|
||||
translate({ { '.', _, [Left, Right] }, Meta, Args }, S)
|
||||
when (is_tuple(Left) orelse is_atom(Left)), is_atom(Right), is_list(Meta), is_list(Args) ->
|
||||
{ TLeft, SL } = translate(Left, S),
|
||||
{ TRight, SR } = translate(Right, mergec(S, SL)),
|
||||
{ TLeft, SL } = translate(Left, S),
|
||||
{ TArgs, SA } = translate_args(Args, mergec(S, SL)),
|
||||
|
||||
case S#elixir_scope.context of
|
||||
Context when Left /= erlang, (Context == match) orelse (Context == guard) ->
|
||||
compile_error(Meta, S#elixir_scope.file, "cannot invoke remote function ~ts.~ts/~B inside ~ts",
|
||||
['Elixir.Macro':to_string(Left), Right, length(Args), Context]);
|
||||
_ ->
|
||||
Line = ?line(Meta),
|
||||
{ TArgs, SA } = translate_args(Args, mergec(S, SR)),
|
||||
{ { call, Line, { remote, Line, TLeft, TRight }, TArgs }, mergev(SL, mergev(SR, SA)) }
|
||||
Line = ?line(Meta),
|
||||
Arity = length(Args),
|
||||
|
||||
%% We need to rewrite erlang function calls as operators
|
||||
%% because erl_eval chokes on them. We can remove this
|
||||
%% once a fix is merged into Erlang, keeping only the
|
||||
%% list operators one (since it is required for inlining
|
||||
%% [1,2,3] ++ Right in matches).
|
||||
case (Left == erlang) andalso erl_op(Right, Arity) of
|
||||
true ->
|
||||
{ list_to_tuple([op, Line, Right] ++ TArgs), mergev(SL, SA) };
|
||||
false ->
|
||||
assert_allowed_in_context(Meta, Left, Right, Arity, S),
|
||||
{ { call, Line, { remote, Line, TLeft, { atom, 0, Right } }, TArgs }, mergev(SL, SA) }
|
||||
end;
|
||||
|
||||
%% Anonymous function calls
|
||||
@@ -236,6 +249,12 @@ translate(Other, S) ->
|
||||
|
||||
%% Helpers
|
||||
|
||||
erl_op(Op, Arity) ->
|
||||
erl_internal:list_op(Op, Arity) orelse
|
||||
erl_internal:comp_op(Op, Arity) orelse
|
||||
erl_internal:bool_op(Op, Arity) orelse
|
||||
erl_internal:arith_op(Op, Arity).
|
||||
|
||||
translate_list([{ '|', _, [_, _]=Args}], Fun, Acc, List) ->
|
||||
{ [TLeft,TRight], TAcc } = lists:mapfoldl(Fun, Acc, Args),
|
||||
{ build_list([TLeft|List], TRight), TAcc };
|
||||
@@ -334,3 +353,14 @@ assert_module_scope(_Meta, _Kind, #elixir_scope{module=Module}) -> Module.
|
||||
assert_function_scope(Meta, Kind, #elixir_scope{function=nil,file=File}) ->
|
||||
compile_error(Meta, File, "cannot invoke ~ts outside function", [Kind]);
|
||||
assert_function_scope(_Meta, _Kind, #elixir_scope{function=Function}) -> Function.
|
||||
|
||||
assert_allowed_in_context(Meta, Left, Right, Arity, #elixir_scope{context=Context} = S)
|
||||
when (Context == match) orelse (Context == guard) ->
|
||||
case (Left == erlang) andalso erl_internal:guard_bif(Right, Arity) of
|
||||
true -> ok;
|
||||
false ->
|
||||
compile_error(Meta, S#elixir_scope.file, "cannot invoke remote function ~ts.~ts/~B inside ~ts",
|
||||
['Elixir.Macro':to_string(Left), Right, Arity, Context])
|
||||
end;
|
||||
assert_allowed_in_context(_, _, _, _, _) ->
|
||||
ok.
|
||||
|
||||
@@ -22,6 +22,7 @@ defmodule KernelTest do
|
||||
doctest OptionParser
|
||||
doctest Path
|
||||
doctest Protocol.Consolidation
|
||||
doctest Range
|
||||
doctest Regex
|
||||
doctest Set
|
||||
doctest Stream
|
||||
|
||||
@@ -58,14 +58,14 @@ defmodule BehaviourTest do
|
||||
assert_raise ArgumentError, fn ->
|
||||
defmodule WithDefault do
|
||||
use Behaviour
|
||||
defcallback hello(num // 0 :: integer) :: integer
|
||||
defcallback hello(num \\ 0 :: integer) :: integer
|
||||
end
|
||||
end
|
||||
|
||||
assert_raise ArgumentError, fn ->
|
||||
defmodule WithDefault do
|
||||
use Behaviour
|
||||
defcallback hello(num :: integer // 0) :: integer
|
||||
defcallback hello(num :: integer \\ 0) :: integer
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,20 +1,26 @@
|
||||
Code.require_file "test_helper.exs", __DIR__
|
||||
|
||||
# A TestDict implementation used only for testing.
|
||||
defmodule TestDict do
|
||||
def new(list \\ []) when is_list(list) do
|
||||
{ TestDict, list }
|
||||
end
|
||||
|
||||
def reduce({ TestDict, list }, acc, fun) do
|
||||
Enumerable.reduce(list, acc, fun)
|
||||
end
|
||||
|
||||
def size({ TestDict, list }) do
|
||||
length(list)
|
||||
end
|
||||
end
|
||||
|
||||
defmodule DictTest.Common do
|
||||
defmacro __using__(_) do
|
||||
quote location: :keep do
|
||||
# Most underlying Dict implementations have no key
|
||||
# order guarantees, sort them before we compare:
|
||||
defmacrop dicts_equal(actual, expected) do
|
||||
quote do
|
||||
cmp = fn { k1, _ }, { k2, _ } -> k1 < k2 end
|
||||
Enum.sort(unquote(expected), cmp) == Enum.sort(unquote(actual), cmp)
|
||||
end
|
||||
end
|
||||
import Enum, only: [sort: 1]
|
||||
|
||||
defp empty_dict, do: dict_impl.new
|
||||
|
||||
defp new_dict(list // [{"first_key", 1}, {"second_key", 2}]) do
|
||||
defp new_dict(list \\ [{"first_key", 1}, {"second_key", 2}]) do
|
||||
dict_impl.new list
|
||||
end
|
||||
|
||||
@@ -26,284 +32,304 @@ defmodule DictTest.Common do
|
||||
dict_impl.new [{1,1}]
|
||||
end
|
||||
|
||||
test :access do
|
||||
dict = new_dict [{"first_key", 1}, {"second_key", 2}]
|
||||
test "access" do
|
||||
dict = new_dict()
|
||||
assert dict["first_key"] == 1
|
||||
assert dict["third_key"] == nil
|
||||
assert dict["other_key"] == nil
|
||||
end
|
||||
|
||||
test :access_with_match do
|
||||
assert int_dict[1] == 1
|
||||
assert int_dict[1.0] == nil
|
||||
test "access uses match operation" do
|
||||
dict = int_dict()
|
||||
assert dict[1] == 1
|
||||
assert dict[1.0] == nil
|
||||
end
|
||||
|
||||
test :new_pairs do
|
||||
dict = new_dict [{"first_key", 1}, {"second_key", 2}]
|
||||
assert 2 == Dict.size dict
|
||||
|
||||
assert ["first_key", "second_key"] == Enum.sort Dict.keys dict
|
||||
assert [1, 2] == Enum.sort Dict.values dict
|
||||
test "new/1" do
|
||||
dict = new_dict()
|
||||
assert Dict.size(dict) == 2
|
||||
assert Enum.sort(dict) == [{"first_key", 1}, {"second_key", 2}]
|
||||
end
|
||||
|
||||
test :new_pairs_with_transform do
|
||||
dict = new_dict [{1}, {2}, {3}], fn {x} -> { <<x + 64>>, x } end
|
||||
assert 3 == Dict.size dict
|
||||
|
||||
assert ["A", "B", "C"] == Enum.sort Dict.keys dict
|
||||
assert [1, 2, 3] == Enum.sort Dict.values dict
|
||||
test "new/2" do
|
||||
dict = new_dict([{1}, {2}, {3}], fn {x} -> { <<x + 64>>, x } end)
|
||||
assert Dict.size(dict) == 3
|
||||
assert Enum.sort(dict) == [{"A", 1}, {"B", 2}, {"C", 3}]
|
||||
end
|
||||
|
||||
test :get do
|
||||
assert 1 == Dict.get(new_dict, "first_key")
|
||||
assert 2 == Dict.get(new_dict, "second_key")
|
||||
assert nil == Dict.get(new_dict, "other_key")
|
||||
assert "default" == Dict.get(empty_dict, "first_key", "default")
|
||||
test "get/2 and get/3" do
|
||||
dict = new_dict()
|
||||
assert Dict.get(dict, "first_key") == 1
|
||||
assert Dict.get(dict, "second_key") == 2
|
||||
assert Dict.get(dict, "other_key") == nil
|
||||
assert Dict.get(dict, "other_key", 3) == 3
|
||||
end
|
||||
|
||||
test :get_with_match do
|
||||
test "get/2 with match" do
|
||||
assert Dict.get(int_dict, 1) == 1
|
||||
assert Dict.get(int_dict, 1.0) == nil
|
||||
end
|
||||
|
||||
test :fetch do
|
||||
assert { :ok, 1 } == Dict.fetch(new_dict, "first_key")
|
||||
assert { :ok, 2 } == Dict.fetch(new_dict, "second_key")
|
||||
assert :error == Dict.fetch(new_dict, "other_key")
|
||||
test "fetch/2" do
|
||||
dict = new_dict()
|
||||
assert Dict.fetch(dict, "first_key") == {:ok, 1}
|
||||
assert Dict.fetch(dict, "second_key") == {:ok, 2}
|
||||
assert Dict.fetch(dict, "other_key") == :error
|
||||
end
|
||||
|
||||
test :fetch_with_match do
|
||||
assert Dict.fetch(int_dict, 1) == { :ok, 1 }
|
||||
test "fetch/2 with match" do
|
||||
assert Dict.fetch(int_dict, 1) == {:ok, 1}
|
||||
assert Dict.fetch(int_dict, 1.0) == :error
|
||||
end
|
||||
|
||||
test :fetch! do
|
||||
assert 1 == Dict.fetch!(new_dict, "first_key")
|
||||
assert 2 == Dict.fetch!(new_dict, "second_key")
|
||||
test "fetch!/2" do
|
||||
dict = new_dict()
|
||||
assert Dict.fetch!(dict, "first_key") == 1
|
||||
assert Dict.fetch!(dict, "second_key") == 2
|
||||
assert_raise KeyError, fn ->
|
||||
Dict.fetch!(new_dict, "other_key")
|
||||
Dict.fetch!(dict, "other_key")
|
||||
end
|
||||
end
|
||||
|
||||
test :fetch_with_match! do
|
||||
assert Dict.fetch!(int_dict, 1) == 1
|
||||
assert_raise KeyError, fn ->
|
||||
assert Dict.fetch!(int_dict, 1.0)
|
||||
end
|
||||
test "put/3" do
|
||||
dict = new_dict() |> Dict.put("first_key", {1})
|
||||
assert Dict.get(dict, "first_key") == {1}
|
||||
assert Dict.get(dict, "second_key") == 2
|
||||
end
|
||||
|
||||
test :put do
|
||||
dict = Dict.put(new_dict, "first_key", {1})
|
||||
assert {1} == Dict.get dict, "first_key"
|
||||
assert 2 == Dict.get dict, "second_key"
|
||||
test "put/3 with_match" do
|
||||
dict = int_dict()
|
||||
assert Dict.get(Dict.put(dict, 1, :other), 1) == :other
|
||||
assert Dict.get(Dict.put(dict, 1.0, :other), 1) == 1
|
||||
assert Dict.get(Dict.put(dict, 1, :other), 1.0) == nil
|
||||
assert Dict.get(Dict.put(dict, 1.0, :other), 1.0) == :other
|
||||
end
|
||||
|
||||
test :put_with_match do
|
||||
assert Dict.get(Dict.put(int_dict, 1, :other), 1) == :other
|
||||
assert Dict.get(Dict.put(int_dict, 1.0, :other), 1) == 1
|
||||
assert Dict.get(Dict.put(int_dict, 1, :other), 1.0) == nil
|
||||
assert Dict.get(Dict.put(int_dict, 1.0, :other), 1.0) == :other
|
||||
test "put_new/3" do
|
||||
dict = Dict.put_new(new_dict(), "first_key", {1})
|
||||
assert Dict.get(dict, "first_key") == 1
|
||||
end
|
||||
|
||||
test :put_new do
|
||||
dict = Dict.put_new(new_dict, "first_key", {1})
|
||||
assert 1 == Dict.get dict, "first_key"
|
||||
end
|
||||
|
||||
test :put_new_with_match do
|
||||
test "put_new/3 with_match" do
|
||||
assert Dict.get(Dict.put_new(int_dict, 1, :other), 1) == 1
|
||||
assert Dict.get(Dict.put_new(int_dict, 1.0, :other), 1) == 1
|
||||
assert Dict.get(Dict.put_new(int_dict, 1, :other), 1.0) == nil
|
||||
assert Dict.get(Dict.put_new(int_dict, 1.0, :other), 1.0) == :other
|
||||
end
|
||||
|
||||
test :keys do
|
||||
assert Enum.sort(Dict.keys new_dict) == ["first_key", "second_key"]
|
||||
assert Dict.keys(empty_dict) == []
|
||||
test "keys/1" do
|
||||
assert Enum.sort(Dict.keys(new_dict())) == ["first_key", "second_key"]
|
||||
assert Dict.keys(new_dict([])) == []
|
||||
end
|
||||
|
||||
test :values do
|
||||
assert Enum.sort(Dict.values(new_dict)) == [1, 2]
|
||||
assert Dict.values(empty_dict) == []
|
||||
test "values/1" do
|
||||
assert Enum.sort(Dict.values(new_dict())) == [1, 2]
|
||||
assert Dict.values(new_dict([])) == []
|
||||
end
|
||||
|
||||
test :delete do
|
||||
mdict = Dict.delete new_dict, "second_key"
|
||||
assert Dict.size(mdict) == 1
|
||||
assert Dict.has_key? mdict, "first_key"
|
||||
refute Dict.has_key? mdict, "second_key"
|
||||
test "delete/2" do
|
||||
dict = Dict.delete(new_dict(), "second_key")
|
||||
assert Dict.size(dict) == 1
|
||||
assert Dict.has_key?(dict, "first_key")
|
||||
refute Dict.has_key?(dict, "second_key")
|
||||
|
||||
mdict = Dict.delete(new_dict, "other_key")
|
||||
assert mdict == new_dict
|
||||
assert Dict.size(Dict.delete(empty_dict, "other_key")) == 0
|
||||
dict = Dict.delete(new_dict(), "other_key")
|
||||
assert dict == new_dict()
|
||||
assert Dict.size(dict) == 2
|
||||
end
|
||||
|
||||
test :delete_with_match do
|
||||
test "delete/2 with match" do
|
||||
assert Dict.get(Dict.delete(int_dict, 1), 1) == nil
|
||||
assert Dict.get(Dict.delete(int_dict, 1.0), 1) == 1
|
||||
end
|
||||
|
||||
test :merge do
|
||||
dict = new_dict
|
||||
assert dict == Dict.merge empty_dict, dict
|
||||
assert dict == Dict.merge dict, empty_dict
|
||||
assert dict == Dict.merge dict, dict
|
||||
assert empty_dict == Dict.merge empty_dict, empty_dict
|
||||
test "merge/2" do
|
||||
dict = new_dict()
|
||||
assert Dict.merge(new_dict([]), dict) == dict
|
||||
assert Dict.merge(dict, new_dict([])) == dict
|
||||
assert Dict.merge(dict, dict) == dict
|
||||
assert Dict.merge(new_dict([]), new_dict([])) == new_dict([])
|
||||
|
||||
dict1 = new_dict Enum.zip ["a", "b", "c"], [1, 2, 3]
|
||||
dict2 = new_dict Enum.zip ["a", "c", "d"], [3, :a, 0]
|
||||
actual = Dict.merge(dict1, dict2)
|
||||
expected = new_dict Enum.zip ["a", "b", "c", "d"], [3, 2, :a, 0]
|
||||
assert dicts_equal actual, expected
|
||||
dict1 = new_dict [{"a", 1}, {"b", 2}, {"c", 3}]
|
||||
dict2 = new_dict [{"a", 3}, {"c", :a}, {"d", 0}]
|
||||
assert Dict.merge(dict1, dict2) |> Enum.sort ==
|
||||
[{"a", 3}, {"b", 2}, {"c", :a}, {"d", 0}]
|
||||
end
|
||||
|
||||
test :merge_with_enum do
|
||||
dict1 = new_dict Enum.zip ["a", "b", "c"], [1, 2, 3]
|
||||
dict2 = Enum.zip ["a", "c", "d"], [3, :a, 0]
|
||||
test "merge/2 with other dict" do
|
||||
dict1 = new_dict [{"a", 1}, {"b", 2}, {"c", 3}]
|
||||
dict2 = TestDict.new [{"a",3}, {"c",:a}, {"d",0}]
|
||||
actual = Dict.merge(dict1, dict2)
|
||||
expected = new_dict(Enum.zip ["a", "b", "c", "d"], [3, 2, :a, 0])
|
||||
assert dicts_equal actual, expected
|
||||
assert Dict.merge(dict1, dict2) |> Enum.sort ==
|
||||
[{"a", 3}, {"b", 2}, {"c", :a}, {"d", 0}]
|
||||
end
|
||||
|
||||
test :merge_with_function do
|
||||
dict1 = new_dict Enum.zip ["a", "b"], [1, 2]
|
||||
dict2 = new_dict Enum.zip ["a", "d"], [3, 4]
|
||||
test "merge/3" do
|
||||
dict1 = new_dict [{"a", 1}, {"b", 2}]
|
||||
dict2 = new_dict [{"a", 3}, {"d", 4}]
|
||||
actual = Dict.merge dict1, dict2, fn _k, v1, v2 -> v1 + v2 end
|
||||
expected = new_dict(Enum.zip ["a", "b", "d"], [4, 2, 4])
|
||||
assert dicts_equal actual, expected
|
||||
assert Enum.sort(actual) == [{"a", 4}, {"b", 2}, {"d", 4}]
|
||||
end
|
||||
|
||||
test :has_key do
|
||||
dict = new_dict [{"a", 1}]
|
||||
assert Dict.has_key?(dict, "a")
|
||||
refute Dict.has_key?(dict, "b")
|
||||
test "has_key?/2" do
|
||||
dict = new_dict()
|
||||
assert Dict.has_key?(dict, "first_key")
|
||||
refute Dict.has_key?(dict, "other_key")
|
||||
end
|
||||
|
||||
test :has_key_with_match do
|
||||
test "has_key?/2 with match" do
|
||||
assert Dict.has_key?(int_dict, 1)
|
||||
refute Dict.has_key?(int_dict, 1.0)
|
||||
end
|
||||
|
||||
test :size do
|
||||
assert 2 == Dict.size new_dict
|
||||
assert 0 == Dict.size empty_dict
|
||||
test "size/1" do
|
||||
assert Dict.size(new_dict()) == 2
|
||||
assert Dict.size(new_dict([])) == 0
|
||||
end
|
||||
|
||||
test :update do
|
||||
dict = Dict.update!(new_dict, "first_key", fn val -> -val end)
|
||||
test "update!/3" do
|
||||
dict = Dict.update!(new_dict(), "first_key", fn val -> -val end)
|
||||
assert Dict.get(dict, "first_key") == -1
|
||||
|
||||
dict = Dict.update(dict, "non-existent", "...", fn val -> -val end)
|
||||
assert_raise KeyError, fn ->
|
||||
Dict.update!(new_dict(), "non-existent", fn val -> -val end)
|
||||
end
|
||||
end
|
||||
|
||||
test "update!/3 with match" do
|
||||
assert Dict.get(Dict.update!(int_dict(), 1, &(&1 + 1)), 1) == 2
|
||||
end
|
||||
|
||||
test "update/4" do
|
||||
dict = Dict.update(new_dict(), "first_key", 0, fn val -> -val end)
|
||||
assert Dict.get(dict, "first_key") == -1
|
||||
|
||||
dict = Dict.update(new_dict(), "non-existent", "...", fn val -> -val end)
|
||||
assert Dict.get(dict, "non-existent") == "..."
|
||||
end
|
||||
|
||||
test :update_with_match do
|
||||
assert Dict.get(Dict.update!(int_dict, 1, &(&1 + 1)), 1) == 2
|
||||
assert Dict.get(Dict.update(int_dict, 1.0, 2, &(&1 + 1)), 1) == 1
|
||||
assert Dict.get(Dict.update(int_dict, 1.0, 2, &(&1 + 1)), 1.0) == 2
|
||||
test "update/4 with match" do
|
||||
dict = int_dict()
|
||||
assert Dict.get(Dict.update(dict, 1.0, 2, &(&1 + 1)), 1) == 1
|
||||
assert Dict.get(Dict.update(dict, 1.0, 2, &(&1 + 1)), 1.0) == 2
|
||||
end
|
||||
|
||||
test :pop do
|
||||
{v, actual} = Dict.pop(new_dict, "first_key")
|
||||
assert 1 == v
|
||||
assert dicts_equal actual, Dict.delete(new_dict, "first_key")
|
||||
test "pop/2 and pop/3" do
|
||||
dict = new_dict()
|
||||
|
||||
{v, actual} = Dict.pop(new_dict, "second_key")
|
||||
assert 2 == v
|
||||
assert dicts_equal actual, Dict.delete(new_dict, "second_key")
|
||||
|
||||
{v, actual} = Dict.pop(new_dict, "other_key")
|
||||
assert nil == v
|
||||
assert dicts_equal actual, new_dict
|
||||
|
||||
{v, actual} = Dict.pop(empty_dict, "first_key", "default")
|
||||
assert "default" == v
|
||||
assert dicts_equal actual, empty_dict
|
||||
|
||||
{v, actual} = Dict.pop(new_dict, "other_key", "default")
|
||||
assert "default" == v
|
||||
assert dicts_equal actual, new_dict
|
||||
end
|
||||
|
||||
test :pop_with_match do
|
||||
{v, actual} = Dict.pop(int_dict, 1)
|
||||
{v, actual} = Dict.pop(dict, "first_key")
|
||||
assert v == 1
|
||||
assert dicts_equal actual, []
|
||||
assert actual == new_dict([{"second_key", 2}])
|
||||
|
||||
{v, actual} = Dict.pop(int_dict, 1.0)
|
||||
{v, actual} = Dict.pop(dict, "other_key")
|
||||
assert v == nil
|
||||
assert dicts_equal actual, int_dict
|
||||
assert dict == actual
|
||||
|
||||
{v, actual} = Dict.pop(dict, "other_key", "default")
|
||||
assert v == "default"
|
||||
assert dict == actual
|
||||
end
|
||||
|
||||
test :split do
|
||||
split_keys = []
|
||||
{take, drop} = Dict.split(new_dict, split_keys)
|
||||
assert dicts_equal take, Dict.empty(new_dict)
|
||||
assert dicts_equal drop, new_dict
|
||||
test "pop/2 and pop/3 with match" do
|
||||
dict = int_dict()
|
||||
|
||||
split_keys = ["unknown_key"]
|
||||
{take, drop} = Dict.split(new_dict, split_keys)
|
||||
assert dicts_equal take, Dict.empty(new_dict)
|
||||
assert dicts_equal drop, new_dict
|
||||
{v, actual} = Dict.pop(dict, 1)
|
||||
assert v == 1
|
||||
assert Enum.sort(actual) == []
|
||||
|
||||
{v, actual} = Dict.pop(dict, 1.0)
|
||||
assert v == nil
|
||||
assert actual == dict
|
||||
end
|
||||
|
||||
test "split/2" do
|
||||
dict = new_dict()
|
||||
|
||||
{take, drop} = Dict.split(dict, [])
|
||||
assert take == new_dict([])
|
||||
assert drop == dict
|
||||
|
||||
{take, drop} = Dict.split(dict, ["unknown_key"])
|
||||
assert take == new_dict([])
|
||||
assert drop == dict
|
||||
|
||||
split_keys = ["first_key", "second_key", "unknown_key"]
|
||||
{take, drop} = Dict.split(new_dict, split_keys)
|
||||
{take, drop} = Dict.split(dict, split_keys)
|
||||
|
||||
first_val = Dict.get(new_dict, "first_key")
|
||||
second_val = Dict.get(new_dict, "second_key")
|
||||
take_expected = Dict.empty(new_dict)
|
||||
take_expected = Dict.put(take_expected, "first_key", first_val)
|
||||
take_expected = Dict.put(take_expected, "second_key", second_val)
|
||||
drop_expected = new_dict
|
||||
drop_expected = Dict.delete(drop_expected, "first_key")
|
||||
drop_expected = Dict.delete(drop_expected, "second_key")
|
||||
take_expected = new_dict([])
|
||||
|> Dict.put("first_key", 1)
|
||||
|> Dict.put("second_key", 2)
|
||||
|
||||
assert dicts_equal take, take_expected
|
||||
assert dicts_equal drop, drop_expected
|
||||
drop_expected = new_dict([])
|
||||
|> Dict.delete("first_key")
|
||||
|> Dict.delete("second_key")
|
||||
|
||||
assert Enum.sort(take) == Enum.sort(take_expected)
|
||||
assert Enum.sort(drop) == Enum.sort(drop_expected)
|
||||
end
|
||||
|
||||
test :split_with_match do
|
||||
{ take, drop } = Dict.split(int_dict, [1])
|
||||
assert dicts_equal take, int_dict
|
||||
assert dicts_equal drop, []
|
||||
test "split/2 with match" do
|
||||
dict = int_dict()
|
||||
{ take, drop } = Dict.split(dict, [1])
|
||||
assert take == dict
|
||||
assert drop == new_dict([])
|
||||
|
||||
{ take, drop } = Dict.split(int_dict, [1.0])
|
||||
assert dicts_equal take, []
|
||||
assert dicts_equal drop, int_dict
|
||||
{ take, drop } = Dict.split(dict, [1.0])
|
||||
assert take == new_dict([])
|
||||
assert drop == dict
|
||||
end
|
||||
|
||||
test :take do
|
||||
result = Dict.take(new_dict, ["unknown_key"])
|
||||
assert dicts_equal result, Dict.empty(new_dict)
|
||||
|
||||
result = Dict.take(new_dict, ["first_key"])
|
||||
first_val = Dict.get(new_dict, "first_key")
|
||||
expected = Dict.put(Dict.empty(new_dict), "first_key", first_val)
|
||||
assert dicts_equal result, expected
|
||||
test "split/2 with enum" do
|
||||
dict = int_dict()
|
||||
{ take, drop } = Dict.split(dict, 1..3)
|
||||
assert take == dict
|
||||
assert drop == new_dict([])
|
||||
end
|
||||
|
||||
test :take_with_match do
|
||||
assert dicts_equal Dict.take(int_dict, [1]), int_dict
|
||||
assert dicts_equal Dict.take(int_dict, [1.0]), []
|
||||
test "take/2" do
|
||||
dict = new_dict()
|
||||
take = Dict.take(dict, ["unknown_key"])
|
||||
assert take == new_dict([])
|
||||
|
||||
take = Dict.take(dict, ["first_key"])
|
||||
assert take == new_dict([{"first_key", 1}])
|
||||
end
|
||||
|
||||
test :drop do
|
||||
result = Dict.drop(new_dict, ["unknown_key"])
|
||||
assert dicts_equal result, new_dict
|
||||
|
||||
result = Dict.drop(new_dict, ["first_key"])
|
||||
assert dicts_equal result, Dict.delete(new_dict, "first_key")
|
||||
test "take/2 with match" do
|
||||
dict = int_dict()
|
||||
assert Dict.take(dict, [1]) == dict
|
||||
assert Dict.take(dict, [1.0]) == new_dict([])
|
||||
end
|
||||
|
||||
test :drop_with_match do
|
||||
assert dicts_equal Dict.drop(int_dict, [1]), []
|
||||
assert dicts_equal Dict.drop(int_dict, [1.0]), int_dict
|
||||
test "take/2 with enum" do
|
||||
dict = int_dict()
|
||||
assert Dict.take(dict, 1..3) == dict
|
||||
end
|
||||
|
||||
test :empty do
|
||||
assert empty_dict == Dict.empty new_dict
|
||||
test "drop/2" do
|
||||
dict = new_dict()
|
||||
drop = Dict.drop(dict, ["unknown_key"])
|
||||
assert drop == dict
|
||||
|
||||
drop = Dict.drop(dict, ["first_key"])
|
||||
assert drop == new_dict([{"second_key", 2}])
|
||||
end
|
||||
|
||||
test :equal? do
|
||||
test "drop/2 with match" do
|
||||
dict = int_dict()
|
||||
assert Dict.drop(dict, [1]) == new_dict([])
|
||||
assert Dict.drop(dict, [1.0]) == dict
|
||||
end
|
||||
|
||||
test "drop/2 with enum" do
|
||||
dict = int_dict()
|
||||
assert Dict.drop(dict, 1..3) == new_dict([])
|
||||
end
|
||||
|
||||
test "empty" do
|
||||
assert Dict.empty(new_dict) == new_dict([])
|
||||
end
|
||||
|
||||
test "equal?/2" do
|
||||
dict1 = new_dict(a: 2, b: 3, f: 5, c: 123)
|
||||
dict2 = new_dict(a: 2, b: 3, f: 5, c: 123)
|
||||
assert dict_impl.equal?(dict1, dict2)
|
||||
@@ -318,20 +344,37 @@ defmodule DictTest.Common do
|
||||
refute Dict.equal?(dict3, dict1)
|
||||
end
|
||||
|
||||
test :equal_with_match? do
|
||||
test "equal?/2 with match" do
|
||||
dict1 = new_dict([{1,1}])
|
||||
dict2 = new_dict([{1.0,1}])
|
||||
assert dict_impl.equal?(dict1, dict1)
|
||||
refute dict_impl.equal?(dict1, dict2)
|
||||
assert Dict.equal?(dict1, dict1)
|
||||
refute Dict.equal?(dict1, dict2)
|
||||
end
|
||||
|
||||
test :enum_member do
|
||||
assert Enum.member?(new_dict([{1,1}]), { 1, 1 })
|
||||
refute Enum.member?(new_dict([{1,1}]), { 1.0, 1 })
|
||||
refute Enum.member?(new_dict([{1,1}]), { 1, 1.0 })
|
||||
refute Enum.member?(new_dict([{1,1}]), { 1.0, 1.0 })
|
||||
test "equal?/2 with other dict" do
|
||||
dict = new_dict([{1,1}])
|
||||
assert Dict.equal?(dict, TestDict.new([{1,1}]))
|
||||
refute Dict.equal?(dict, TestDict.new([{1.0,1}]))
|
||||
end
|
||||
|
||||
test "implements Enumerable" do
|
||||
dict = new_dict()
|
||||
assert Enum.empty?(new_dict([]))
|
||||
refute Enum.empty?(dict)
|
||||
assert Enum.member?(dict, { "first_key", 1 })
|
||||
refute Enum.member?(dict, { "first_key", 2 })
|
||||
assert Enum.count(dict) == 2
|
||||
assert Enum.reduce(dict, 0, fn({ k, v }, acc) -> v + acc end) == 3
|
||||
end
|
||||
|
||||
test "is zippable" do
|
||||
dict = new_dict()
|
||||
list = Dict.to_list(dict)
|
||||
assert Enum.zip(list, list) == Enum.zip(dict, dict)
|
||||
|
||||
dict = new_dict(1..120, fn i -> { i, i } end)
|
||||
list = Dict.to_list(dict)
|
||||
assert Enum.zip(list, list) == Enum.zip(dict, dict)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -830,31 +830,27 @@ defmodule EnumTest.Others do
|
||||
|
||||
import ExUnit.CaptureIO
|
||||
|
||||
test :reverse do
|
||||
test "reverse custom enumerable" do
|
||||
assert Enum.reverse(URI.query_decoder("foo=bar&baz=bat")) ==
|
||||
[{ "baz", "bat" }, { "foo", "bar" }]
|
||||
end
|
||||
|
||||
test :count do
|
||||
assert Enum.count(URI.query_decoder("foo=bar&baz=bat")) == 2
|
||||
end
|
||||
|
||||
test :sort do
|
||||
d = HashDict.new [a: 1, another: 1, some: 1, multi_word: 1,
|
||||
this: 2, punctuation: 1, is: 2, sentence: 2, with: 1]
|
||||
test "sort with custom enumerable" do
|
||||
d = HashDict.new [a: 7, another: 4, some: 2, multi_word: 3,
|
||||
this: 9, punctuation: 1, is: 8, sentence: 6, with: 5]
|
||||
assert Enum.sort(d, fn({_, v1}, {_, v2}) -> v1 > v2 end) ==
|
||||
[this: 2, is: 2, sentence: 2, with: 1, a: 1, another: 1,
|
||||
multi_word: 1, some: 1, punctuation: 1]
|
||||
[this: 9, is: 8, a: 7, sentence: 6, with: 5,
|
||||
another: 4, multi_word: 3, some: 2, punctuation: 1]
|
||||
end
|
||||
|
||||
test :take_with_side_effects do
|
||||
test "take with side effects" do
|
||||
stream = Stream.unfold(1, fn x -> IO.puts x; { x, x + 1 } end)
|
||||
assert capture_io(fn ->
|
||||
Enum.take(stream, 1)
|
||||
end) == "1\n"
|
||||
end
|
||||
|
||||
test :take_does_not_consume_next_without_a_need do
|
||||
test "takes does not consume next without a need" do
|
||||
import PathHelpers
|
||||
File.open!(fixture_path("one-liner.txt"), [], fn file ->
|
||||
iterator = IO.stream(file, :line)
|
||||
@@ -863,7 +859,7 @@ defmodule EnumTest.Others do
|
||||
end)
|
||||
end
|
||||
|
||||
test :take_with_no_item_works_as_no_op do
|
||||
test "take with no item works as no-op" do
|
||||
import PathHelpers
|
||||
iterator = File.stream!(fixture_path("unknown.txt"))
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ defmodule Kernel.ExceptionTest do
|
||||
use ExUnit.Case, async: true
|
||||
|
||||
# Ensure fields passed through an expression are valid
|
||||
defexception Custom, %w(message)a
|
||||
defexception Custom, ~w(message)a
|
||||
|
||||
test "is_exception" do
|
||||
assert is_exception(RuntimeError.new)
|
||||
@@ -42,13 +42,17 @@ defmodule Kernel.ExceptionTest do
|
||||
end
|
||||
|
||||
test "format_stacktrace_entry with application" do
|
||||
assert Exception.format_stacktrace_entry({Exception, :bar, [], [file: 'file.ex']}) == "(elixir) file.ex: Exception.bar()"
|
||||
assert Exception.format_stacktrace_entry({Exception, :bar, [], [file: 'file.ex', line: 10]}) == "(elixir) file.ex:10: Exception.bar()"
|
||||
assert Exception.format_stacktrace_entry({:lists, :bar, [1, 2, 3], []}) == "(stdlib) :lists.bar(1, 2, 3)" end
|
||||
assert Exception.format_stacktrace_entry({Exception, :bar, [], [file: 'file.ex']}) ==
|
||||
"(elixir) file.ex: Exception.bar()"
|
||||
assert Exception.format_stacktrace_entry({Exception, :bar, [], [file: 'file.ex', line: 10]}) ==
|
||||
"(elixir) file.ex:10: Exception.bar()"
|
||||
assert Exception.format_stacktrace_entry({:lists, :bar, [1, 2, 3], []}) ==
|
||||
"(stdlib) :lists.bar(1, 2, 3)"
|
||||
end
|
||||
|
||||
test "format_stacktrace_entry with fun" do
|
||||
assert Exception.format_stacktrace_entry({fn(x) -> x end, [1], []}) =~ %r/#Function<.+>\(1\)/
|
||||
assert Exception.format_stacktrace_entry({fn(x, y) -> { x, y } end, 2, []}) =~ %r"#Function<.+>/2"
|
||||
assert Exception.format_stacktrace_entry({fn(x) -> x end, [1], []}) =~ ~r/#Function<.+>\(1\)/
|
||||
assert Exception.format_stacktrace_entry({fn(x, y) -> { x, y } end, 2, []}) =~ ~r"#Function<.+>/2"
|
||||
end
|
||||
|
||||
test "format_mfa" do
|
||||
@@ -58,11 +62,12 @@ defmodule Kernel.ExceptionTest do
|
||||
assert Exception.format_mfa(nil, :bar, []) == "nil.bar()"
|
||||
assert Exception.format_mfa(:foo, :bar, [1, 2]) == ":foo.bar(1, 2)"
|
||||
assert Exception.format_mfa(Foo, :"bar baz", 1) == "Foo.\"bar baz\"/1"
|
||||
assert Exception.format_mfa(Foo, :"-func/2-fun-0-", 4) == "anonymous fn/4 in Foo.func/2"
|
||||
end
|
||||
|
||||
test "format_fa" do
|
||||
assert Exception.format_fa(fn -> end, 1) =~
|
||||
%r"#Function<\d\.\d+ in Kernel\.ExceptionTest\.test format_fa/1>/1"
|
||||
~r"#Function<\d\.\d+/0 in Kernel\.ExceptionTest\.test format_fa/1>/1"
|
||||
end
|
||||
|
||||
test "runtime error message" do
|
||||
@@ -98,10 +103,9 @@ defmodule Kernel.ExceptionTest do
|
||||
[top|_] = System.stacktrace
|
||||
top
|
||||
end
|
||||
|
||||
file = __ENV__.file |> Path.relative_to_cwd |> String.to_char_list!
|
||||
assert {Kernel.ExceptionTest, :"test raise preserves the stacktrace", _,
|
||||
[file: ^file, line: 96]} = stacktrace
|
||||
[file: ^file, line: 101]} = stacktrace
|
||||
end
|
||||
|
||||
test "defexception" do
|
||||
|
||||
@@ -44,16 +44,13 @@ defmodule FileTest do
|
||||
end
|
||||
|
||||
test :cp_with_src_file_and_dest_dir do
|
||||
src = fixture_path("file.txt")
|
||||
dest = tmp_path("tmp")
|
||||
final = Path.join(dest, "file.txt")
|
||||
src = fixture_path("file.txt")
|
||||
dest = tmp_path("tmp")
|
||||
|
||||
File.mkdir(dest)
|
||||
|
||||
try do
|
||||
refute File.exists?(final)
|
||||
assert File.cp(src, dest) == :ok
|
||||
assert File.exists?(final)
|
||||
assert File.cp(src, dest) == { :error, :eisdir }
|
||||
after
|
||||
File.rm_rf dest
|
||||
end
|
||||
@@ -154,14 +151,11 @@ defmodule FileTest do
|
||||
test :cp_r_with_src_file_and_dest_dir do
|
||||
src = fixture_path("file.txt")
|
||||
dest = tmp_path("tmp")
|
||||
final = Path.join(dest, "file.txt")
|
||||
|
||||
File.mkdir(dest)
|
||||
|
||||
try do
|
||||
refute File.exists?(final)
|
||||
assert File.cp_r(src, dest) == { :ok, [final] }
|
||||
assert File.exists?(final)
|
||||
assert io_error? File.cp_r(src, dest)
|
||||
after
|
||||
File.rm_rf dest
|
||||
end
|
||||
@@ -186,30 +180,6 @@ defmodule FileTest do
|
||||
|
||||
File.mkdir(dest)
|
||||
|
||||
try do
|
||||
refute File.exists?(tmp_path("tmp/cp_r/a/1.txt"))
|
||||
refute File.exists?(tmp_path("tmp/cp_r/a/a/2.txt"))
|
||||
refute File.exists?(tmp_path("tmp/cp_r/b/3.txt"))
|
||||
|
||||
{ :ok, files } = File.cp_r(src, dest)
|
||||
assert length(files) == 7
|
||||
assert tmp_path("tmp/cp_r/a") in files
|
||||
assert tmp_path("tmp/cp_r/a/1.txt") in files
|
||||
|
||||
assert File.exists?(tmp_path("tmp/cp_r/a/1.txt"))
|
||||
assert File.exists?(tmp_path("tmp/cp_r/a/a/2.txt"))
|
||||
assert File.exists?(tmp_path("tmp/cp_r/b/3.txt"))
|
||||
after
|
||||
File.rm_rf dest
|
||||
end
|
||||
end
|
||||
|
||||
test :cp_r_with_src_dir_dot_and_dest_dir do
|
||||
src = fixture_path("cp_r/.")
|
||||
dest = tmp_path("tmp")
|
||||
|
||||
File.mkdir(dest)
|
||||
|
||||
try do
|
||||
refute File.exists?(tmp_path("tmp/a/1.txt"))
|
||||
refute File.exists?(tmp_path("tmp/a/a/2.txt"))
|
||||
@@ -217,6 +187,8 @@ defmodule FileTest do
|
||||
|
||||
{ :ok, files } = File.cp_r(src, dest)
|
||||
assert length(files) == 7
|
||||
assert tmp_path("tmp/a") in files
|
||||
assert tmp_path("tmp/a/1.txt") in files
|
||||
|
||||
assert File.exists?(tmp_path("tmp/a/1.txt"))
|
||||
assert File.exists?(tmp_path("tmp/a/a/2.txt"))
|
||||
@@ -243,16 +215,16 @@ defmodule FileTest do
|
||||
dest = tmp_path("tmp")
|
||||
|
||||
try do
|
||||
refute File.exists?(tmp_path("tmp/cp_r/a/1.txt"))
|
||||
refute File.exists?(tmp_path("tmp/cp_r/a/a/2.txt"))
|
||||
refute File.exists?(tmp_path("tmp/cp_r/b/3.txt"))
|
||||
refute File.exists?(tmp_path("tmp/a/1.txt"))
|
||||
refute File.exists?(tmp_path("tmp/a/a/2.txt"))
|
||||
refute File.exists?(tmp_path("tmp/b/3.txt"))
|
||||
|
||||
{ :ok, files } = File.cp_r(src, dest)
|
||||
assert length(files) == 7
|
||||
|
||||
assert File.exists?(tmp_path("tmp/cp_r/a/1.txt"))
|
||||
assert File.exists?(tmp_path("tmp/cp_r/a/a/2.txt"))
|
||||
assert File.exists?(tmp_path("tmp/cp_r/b/3.txt"))
|
||||
assert File.exists?(tmp_path("tmp/a/1.txt"))
|
||||
assert File.exists?(tmp_path("tmp/a/a/2.txt"))
|
||||
assert File.exists?(tmp_path("tmp/b/3.txt"))
|
||||
after
|
||||
File.rm_rf dest
|
||||
end
|
||||
@@ -265,20 +237,20 @@ defmodule FileTest do
|
||||
end
|
||||
|
||||
test :cp_r_with_dir_and_file_conflict do
|
||||
src = fixture_path("cp_r/.")
|
||||
src = fixture_path("cp_r")
|
||||
dest = tmp_path("tmp")
|
||||
|
||||
try do
|
||||
File.mkdir(dest)
|
||||
File.write!(Path.join(dest, "a"), "hello")
|
||||
assert (File.cp_r(src, dest) |> io_error?)
|
||||
assert io_error? File.cp_r(src, dest)
|
||||
after
|
||||
File.rm_rf dest
|
||||
end
|
||||
end
|
||||
|
||||
test :cp_r_with_src_dir_and_dest_dir_using_lists do
|
||||
src = fixture_path("cp_r/.") |> to_char_list
|
||||
src = fixture_path("cp_r") |> to_char_list
|
||||
dest = tmp_path("tmp") |> to_char_list
|
||||
|
||||
File.mkdir(dest)
|
||||
@@ -301,7 +273,7 @@ defmodule FileTest do
|
||||
end
|
||||
|
||||
test :cp_r_with_src_with_file_conflict do
|
||||
src = fixture_path("cp_r/.")
|
||||
src = fixture_path("cp_r")
|
||||
dest = tmp_path("tmp")
|
||||
|
||||
File.mkdir_p tmp_path("tmp/a")
|
||||
@@ -317,7 +289,7 @@ defmodule FileTest do
|
||||
end
|
||||
|
||||
test :cp_r_with_src_with_file_conflict_callback do
|
||||
src = fixture_path("cp_r/.")
|
||||
src = fixture_path("cp_r")
|
||||
dest = tmp_path("tmp")
|
||||
|
||||
File.mkdir_p tmp_path("tmp/a")
|
||||
@@ -337,7 +309,7 @@ defmodule FileTest do
|
||||
end
|
||||
|
||||
test :cp_r! do
|
||||
src = fixture_path("cp_r/.")
|
||||
src = fixture_path("cp_r")
|
||||
dest = tmp_path("tmp")
|
||||
|
||||
File.mkdir(dest)
|
||||
@@ -595,7 +567,7 @@ defmodule FileTest do
|
||||
fixture = fixture_path("file.txt")
|
||||
invalid = Path.join fixture, "test"
|
||||
assert File.exists?(fixture)
|
||||
assert (File.mkdir(invalid) |> io_error?)
|
||||
assert io_error? File.mkdir(invalid)
|
||||
refute File.exists?(invalid)
|
||||
end
|
||||
|
||||
@@ -614,7 +586,7 @@ defmodule FileTest do
|
||||
fixture = fixture_path("file.txt")
|
||||
invalid = Path.join fixture, "test"
|
||||
assert File.exists?(fixture)
|
||||
assert_raise File.Error, %r"^could not make directory #{escape invalid}: (not a directory|no such file or directory)", fn ->
|
||||
assert_raise File.Error, ~r"^could not make directory #{escape invalid}: (not a directory|no such file or directory)", fn ->
|
||||
File.mkdir!(invalid)
|
||||
end
|
||||
end
|
||||
@@ -676,7 +648,7 @@ defmodule FileTest do
|
||||
test :mkdir_p_with_invalid_path do
|
||||
assert File.exists?(fixture_path("file.txt"))
|
||||
invalid = Path.join fixture_path("file.txt"), "test/foo"
|
||||
assert (File.mkdir(invalid) |> io_error?)
|
||||
assert io_error? File.mkdir(invalid)
|
||||
refute File.exists?(invalid)
|
||||
end
|
||||
|
||||
@@ -695,16 +667,15 @@ defmodule FileTest do
|
||||
fixture = fixture_path("file.txt")
|
||||
invalid = Path.join fixture, "test"
|
||||
assert File.exists?(fixture)
|
||||
assert_raise File.Error, %r"^could not make directory \(with -p\) #{escape invalid}: (not a directory|no such file or directory)", fn ->
|
||||
assert_raise File.Error, ~r"^could not make directory \(with -p\) #{escape invalid}: (not a directory|no such file or directory)", fn ->
|
||||
File.mkdir_p!(invalid)
|
||||
end
|
||||
end
|
||||
|
||||
defp io_error?(result) do
|
||||
{:error,errorcode} = result
|
||||
{ :error, errorcode } = result
|
||||
errorcode in [:enotdir, :eio, :enoent, :eisdir]
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
defmodule Rm do
|
||||
@@ -749,7 +720,7 @@ defmodule FileTest do
|
||||
end
|
||||
|
||||
test :rmdir_with_file do
|
||||
assert (File.rmdir(fixture_path("file.txt")) |> io_error?)
|
||||
assert io_error? File.rmdir(fixture_path("file.txt"))
|
||||
end
|
||||
|
||||
test :rmdir! do
|
||||
@@ -762,7 +733,7 @@ defmodule FileTest do
|
||||
|
||||
test :rmdir_with_file! do
|
||||
fixture = fixture_path("file.txt")
|
||||
assert_raise File.Error, %r"^could not remove directory #{escape fixture}: (not a directory|I/O error)", fn ->
|
||||
assert_raise File.Error, ~r"^could not remove directory #{escape fixture}: (not a directory|I/O error)", fn ->
|
||||
File.rmdir!(fixture)
|
||||
end
|
||||
end
|
||||
@@ -770,7 +741,7 @@ defmodule FileTest do
|
||||
test :rm_rf do
|
||||
fixture = tmp_path("tmp")
|
||||
File.mkdir(fixture)
|
||||
File.cp_r!(fixture_path("cp_r/."), fixture)
|
||||
File.cp_r!(fixture_path("cp_r"), fixture)
|
||||
|
||||
assert File.exists?(tmp_path("tmp/a/1.txt"))
|
||||
assert File.exists?(tmp_path("tmp/a/a/2.txt"))
|
||||
@@ -811,7 +782,7 @@ defmodule FileTest do
|
||||
test :rm_rf_with_char_list do
|
||||
fixture = tmp_path("tmp") |> to_char_list
|
||||
File.mkdir(fixture)
|
||||
File.cp_r!(fixture_path("cp_r/."), fixture)
|
||||
File.cp_r!(fixture_path("cp_r"), fixture)
|
||||
|
||||
assert File.exists?(tmp_path("tmp/a/1.txt"))
|
||||
assert File.exists?(tmp_path("tmp/a/a/2.txt"))
|
||||
@@ -847,7 +818,7 @@ defmodule FileTest do
|
||||
test :rm_rf! do
|
||||
fixture = tmp_path("tmp")
|
||||
File.mkdir(fixture)
|
||||
File.cp_r!(fixture_path("cp_r/."), fixture)
|
||||
File.cp_r!(fixture_path("cp_r"), fixture)
|
||||
|
||||
assert File.exists?(tmp_path("tmp/a/1.txt"))
|
||||
assert File.exists?(tmp_path("tmp/a/a/2.txt"))
|
||||
@@ -995,23 +966,6 @@ defmodule FileTest do
|
||||
end
|
||||
end
|
||||
|
||||
test :stream_to do
|
||||
src = fixture_path("file.txt")
|
||||
dest = tmp_path("tmp_test.txt")
|
||||
|
||||
try do
|
||||
stream = File.stream!(src)
|
||||
|> Stream.map(&String.replace(&1, "O", "A"))
|
||||
|> File.stream_to!(dest)
|
||||
|
||||
refute File.exists?(dest)
|
||||
assert Stream.run(stream) == :ok
|
||||
assert File.read(dest) == { :ok, "FAA\n" }
|
||||
after
|
||||
File.rm(dest)
|
||||
end
|
||||
end
|
||||
|
||||
test :stream_bytes do
|
||||
src = fixture_path("file.txt")
|
||||
dest = tmp_path("tmp_test.txt")
|
||||
@@ -1131,11 +1085,11 @@ defmodule FileTest do
|
||||
end
|
||||
|
||||
test :invalid_cd do
|
||||
assert(File.cd(fixture_path("file.txt")) |> io_error?)
|
||||
assert io_error? File.cd(fixture_path("file.txt"))
|
||||
end
|
||||
|
||||
test :invalid_cd! do
|
||||
message = %r"^could not set current working directory to #{escape fixture_path("file.txt")}: (not a directory|no such file or directory)"
|
||||
message = ~r"^could not set current working directory to #{escape fixture_path("file.txt")}: (not a directory|no such file or directory)"
|
||||
assert_raise File.Error, message, fn ->
|
||||
File.cd!(fixture_path("file.txt"))
|
||||
end
|
||||
@@ -1182,7 +1136,7 @@ defmodule FileTest do
|
||||
|
||||
test :touch_with_failure do
|
||||
fixture = fixture_path("file.txt/bar")
|
||||
assert (File.touch(fixture) |> io_error?)
|
||||
assert io_error? File.touch(fixture)
|
||||
end
|
||||
|
||||
test :touch_with_success! do
|
||||
@@ -1191,7 +1145,7 @@ defmodule FileTest do
|
||||
|
||||
test :touch_with_failure! do
|
||||
fixture = fixture_path("file.txt/bar")
|
||||
assert_raise File.Error, %r"could not touch #{escape fixture}: (not a directory|no such file or directory)", fn ->
|
||||
assert_raise File.Error, ~r"could not touch #{escape fixture}: (not a directory|no such file or directory)", fn ->
|
||||
File.touch!(fixture)
|
||||
end
|
||||
end
|
||||
@@ -1234,52 +1188,52 @@ defmodule FileTest do
|
||||
end
|
||||
end
|
||||
|
||||
test :chmod_with_failue do
|
||||
test :chmod_with_failure do
|
||||
fixture = tmp_path("tmp_test.txt")
|
||||
File.rm(fixture)
|
||||
|
||||
assert File.chmod(fixture, 0100777) == {:error,:enoent}
|
||||
end
|
||||
|
||||
test :chmod_with_failue! do
|
||||
test :chmod_with_failure! do
|
||||
fixture = tmp_path("tmp_test.txt")
|
||||
File.rm(fixture)
|
||||
|
||||
message = %r"could not change mode for #{escape fixture}: no such file or directory"
|
||||
message = ~r"could not change mode for #{escape fixture}: no such file or directory"
|
||||
assert_raise File.Error, message, fn ->
|
||||
File.chmod!(fixture, 0100777)
|
||||
end
|
||||
end
|
||||
|
||||
test :chgrp_with_failue do
|
||||
test :chgrp_with_failure do
|
||||
fixture = tmp_path("tmp_test.txt")
|
||||
File.rm(fixture)
|
||||
|
||||
assert File.chgrp(fixture, 1) == {:error,:enoent}
|
||||
end
|
||||
|
||||
test :chgrp_with_failue! do
|
||||
test :chgrp_with_failure! do
|
||||
fixture = tmp_path("tmp_test.txt")
|
||||
File.rm(fixture)
|
||||
|
||||
message = %r"could not change group for #{escape fixture}: no such file or directory"
|
||||
message = ~r"could not change group for #{escape fixture}: no such file or directory"
|
||||
assert_raise File.Error, message, fn ->
|
||||
File.chgrp!(fixture, 1)
|
||||
end
|
||||
end
|
||||
|
||||
test :chown_with_failue do
|
||||
test :chown_with_failure do
|
||||
fixture = tmp_path("tmp_test.txt")
|
||||
File.rm(fixture)
|
||||
|
||||
assert File.chown(fixture, 1) == {:error,:enoent}
|
||||
end
|
||||
|
||||
test :chown_with_failue! do
|
||||
test :chown_with_failure! do
|
||||
fixture = tmp_path("tmp_test.txt")
|
||||
File.rm(fixture)
|
||||
|
||||
message = %r"could not change owner for #{escape fixture}: no such file or directory"
|
||||
message = ~r"could not change owner for #{escape fixture}: no such file or directory"
|
||||
assert_raise File.Error, message, fn ->
|
||||
File.chown!(fixture, 1)
|
||||
end
|
||||
@@ -1294,7 +1248,7 @@ defmodule FileTest do
|
||||
end
|
||||
|
||||
defp io_error?(result) do
|
||||
{:error,errorcode} = result
|
||||
{ :error, errorcode } = result
|
||||
errorcode in [:enotdir, :eio, :enoent, :eisdir]
|
||||
end
|
||||
end
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
defmodule AttributeWarning do
|
||||
@foo
|
||||
end
|
||||
@@ -5,7 +5,7 @@ defmodule CompiledWithDocs do
|
||||
def example(false), do: 0
|
||||
def example(var), do: var && private
|
||||
|
||||
def nodoc(var // 0)
|
||||
def nodoc(var \\ 0)
|
||||
def nodoc(_), do: 2
|
||||
|
||||
defp private, do: 1
|
||||
|
||||
@@ -1,43 +0,0 @@
|
||||
Code.require_file "../test_helper.exs", __DIR__
|
||||
|
||||
defmodule GenFSM.BehaviourTest do
|
||||
use ExUnit.Case
|
||||
|
||||
setup_all do
|
||||
:error_logger.tty(false)
|
||||
:ok
|
||||
end
|
||||
|
||||
teardown_all do
|
||||
:error_logger.tty(true)
|
||||
:ok
|
||||
end
|
||||
|
||||
defmodule Sample do
|
||||
use GenFSM.Behaviour
|
||||
|
||||
def init(args) do
|
||||
{ :ok, :sample, args }
|
||||
end
|
||||
end
|
||||
|
||||
test "sync event stops server on unknown requests" do
|
||||
Process.flag(:trap_exit, true)
|
||||
assert { :ok, pid } = :gen_fsm.start_link(Sample, [:hello], [])
|
||||
|
||||
catch_exit(:gen_fsm.sync_send_all_state_event(pid, :unknown_request))
|
||||
assert_receive { :EXIT, ^pid, {:bad_sync_event, :sample, :unknown_request} }
|
||||
after
|
||||
Process.flag(:trap_exit, false)
|
||||
end
|
||||
|
||||
test "event stops server on unknown requests" do
|
||||
Process.flag(:trap_exit, true)
|
||||
assert { :ok, pid } = :gen_fsm.start_link(Sample, [:hello], [])
|
||||
|
||||
:gen_fsm.send_all_state_event(pid, :unknown_request)
|
||||
assert_receive { :EXIT, ^pid, {:bad_event, :sample, :unknown_request} }
|
||||
after
|
||||
Process.flag(:trap_exit, false)
|
||||
end
|
||||
end
|
||||
@@ -28,98 +28,7 @@ defmodule HashDictTest do
|
||||
smoke_test(1200..1)
|
||||
end
|
||||
|
||||
test "fetch!/2" do
|
||||
dict = filled_dict(8)
|
||||
assert HashDict.fetch!(dict, 1) == 1
|
||||
assert_raise KeyError, fn ->
|
||||
HashDict.fetch!(dict, 11)
|
||||
end
|
||||
end
|
||||
|
||||
test "empty/1" do
|
||||
assert HashDict.empty filled_dict(8) == HashDict.new
|
||||
assert HashDict.empty filled_dict(20) == HashDict.new
|
||||
assert HashDict.empty filled_dict(120) == HashDict.new
|
||||
end
|
||||
|
||||
test "fetch/2" do
|
||||
dict = filled_dict(8)
|
||||
assert HashDict.fetch(dict, 4) == { :ok, 4 }
|
||||
assert HashDict.fetch(dict, 16) == :error
|
||||
end
|
||||
|
||||
test "equal?/2" do
|
||||
assert HashDict.equal?(filled_dict(3), filled_dict(3)) == true
|
||||
|
||||
assert HashDict.equal?(HashDict.new([{:a, 1}, {:b, 2}]),
|
||||
HashDict.new([{:a, 2}, {:b, 3}])) == false
|
||||
end
|
||||
|
||||
test "has_key?/2" do
|
||||
dict = filled_dict(8)
|
||||
assert HashDict.has_key? dict, 4
|
||||
refute HashDict.has_key? dict, 16
|
||||
|
||||
dict = filled_dict(20)
|
||||
assert HashDict.has_key? dict, 10
|
||||
refute HashDict.has_key? dict, 30
|
||||
|
||||
dict = filled_dict(120)
|
||||
assert HashDict.has_key? dict, 60
|
||||
refute HashDict.has_key? dict, 240
|
||||
end
|
||||
|
||||
test "put_new/3" do
|
||||
dict = filled_dict(8)
|
||||
|
||||
dict = HashDict.put_new(dict, 1, 11)
|
||||
assert HashDict.get(dict, 1) == 1
|
||||
assert HashDict.size(dict) == 8
|
||||
|
||||
dict = HashDict.put_new(dict, 11, 13)
|
||||
assert HashDict.get(dict, 11) == 13
|
||||
assert HashDict.size(dict) == 9
|
||||
|
||||
dict = HashDict.put_new(dict, 11.0, 15)
|
||||
assert HashDict.get(dict, 11.0) == 15
|
||||
assert HashDict.get(dict, 11) == 13
|
||||
assert HashDict.size(dict) == 10
|
||||
end
|
||||
|
||||
test "update/3" do
|
||||
dict = filled_dict(8)
|
||||
|
||||
dict = HashDict.update!(dict, 1, &(&1 * 2))
|
||||
assert HashDict.get(dict, 1) == 2
|
||||
assert HashDict.size(dict) == 8
|
||||
|
||||
assert_raise KeyError, fn ->
|
||||
HashDict.update!(dict, 11, &(&1 * 2))
|
||||
end
|
||||
|
||||
dict = HashDict.update(dict, 3, 3, &(&1 * 2))
|
||||
assert HashDict.get(dict, 3) == 6
|
||||
assert HashDict.size(dict) == 8
|
||||
|
||||
dict = HashDict.update(dict, 11, 13, &(&1 * 2))
|
||||
assert HashDict.get(dict, 11) == 13
|
||||
assert HashDict.size(dict) == 9
|
||||
|
||||
assert_raise KeyError, fn->
|
||||
HashDict.update!(dict, 11.0, &(&1 * 2))
|
||||
end
|
||||
|
||||
dict = HashDict.update(dict, 11.0, 15, &(&1 * 2))
|
||||
assert HashDict.get(dict, 11.0) == 15
|
||||
assert HashDict.size(dict) == 10
|
||||
|
||||
dict = HashDict.update(dict, 11.0, 15, &(&1 * 2))
|
||||
assert HashDict.get(dict, 11.0) == 30
|
||||
assert HashDict.get(dict, 11) == 13
|
||||
assert HashDict.size(dict) == 10
|
||||
end
|
||||
|
||||
test "to_list/1" do
|
||||
test "reduce/3 (via to_list)" do
|
||||
dict = filled_dict(8)
|
||||
list = dict |> HashDict.to_list
|
||||
assert length(list) == 8
|
||||
@@ -139,158 +48,16 @@ defmodule HashDictTest do
|
||||
assert list == Enum.to_list(dict)
|
||||
end
|
||||
|
||||
test "keys/1" do
|
||||
list = filled_dict(8) |> HashDict.keys
|
||||
assert length(list) == 8
|
||||
assert 1 in list
|
||||
|
||||
list = filled_dict(20) |> HashDict.keys
|
||||
assert length(list) == 20
|
||||
assert 1 in list
|
||||
|
||||
list = filled_dict(120) |> HashDict.keys
|
||||
assert length(list) == 120
|
||||
assert 1 in list
|
||||
end
|
||||
|
||||
test "values/1" do
|
||||
list = filled_dict(8) |> HashDict.values
|
||||
assert length(list) == 8
|
||||
assert 1 in list
|
||||
|
||||
list = filled_dict(20) |> HashDict.values
|
||||
assert length(list) == 20
|
||||
assert 1 in list
|
||||
|
||||
list = filled_dict(120) |> HashDict.values
|
||||
assert length(list) == 120
|
||||
assert 1 in list
|
||||
end
|
||||
|
||||
test "implements Enumerable" do
|
||||
dict = filled_dict(10)
|
||||
assert Enum.empty?(HashDict.new)
|
||||
refute Enum.empty?(dict)
|
||||
assert Enum.member?(dict, { 5, 5 })
|
||||
refute Enum.member?(dict, { 5, 8 })
|
||||
assert Enum.count(dict) == 10
|
||||
assert Enum.map(filled_dict(3), fn({ k, v }) -> k + v end) == [2, 4, 6]
|
||||
end
|
||||
|
||||
test "access" do
|
||||
assert filled_dict(8)[1] == 1
|
||||
assert filled_dict(8)[5] == 5
|
||||
assert filled_dict(8)[9] == nil
|
||||
end
|
||||
|
||||
test "inspect" do
|
||||
assert inspect(filled_dict(8)) =~ "#HashDict<"
|
||||
end
|
||||
|
||||
test "small dict merge" do
|
||||
dict1 = filled_dict(8)
|
||||
dict2 = Enum.reduce 6..10, HashDict.new, fn(i, d) -> HashDict.put(d, i, i * 2) end
|
||||
test "comparison when subsets" do
|
||||
d1 = HashDict.new(a: 0)
|
||||
d2 = HashDict.new(a: 0, b: 1)
|
||||
|
||||
dict = HashDict.merge(dict1, dict2)
|
||||
assert HashDict.get(dict, 3) == 3
|
||||
assert HashDict.get(dict, 6) == 12
|
||||
assert HashDict.get(dict, 10) == 20
|
||||
assert HashDict.size(dict) == 10
|
||||
|
||||
dict = HashDict.merge(dict2, dict1)
|
||||
assert HashDict.get(dict, 3) == 3
|
||||
assert HashDict.get(dict, 6) == 6
|
||||
assert HashDict.get(dict, 10) == 20
|
||||
assert HashDict.size(dict) == 10
|
||||
|
||||
dict = HashDict.merge(dict1, dict2, fn(k, v1, v2) -> 3*k + 2*v1 + v2 end)
|
||||
assert HashDict.get(dict, 3) == 3
|
||||
assert HashDict.get(dict, 6) == 6 * 7
|
||||
assert HashDict.get(dict, 10) == 20
|
||||
assert HashDict.size(dict) == 10
|
||||
|
||||
list = Enum.map 6..10, fn x -> { x, x * 2 } end
|
||||
dict = HashDict.merge(dict1, list)
|
||||
assert HashDict.get(dict, 3) == 3
|
||||
assert HashDict.get(dict, 6) == 12
|
||||
assert HashDict.get(dict, 10) == 20
|
||||
assert HashDict.size(dict) == 10
|
||||
end
|
||||
|
||||
test "medium dict merge" do
|
||||
dict1 = filled_dict(20)
|
||||
dict2 = Enum.reduce 18..22, HashDict.new, fn(i, d) -> HashDict.put(d, i, i * 2) end
|
||||
|
||||
dict = HashDict.merge(dict1, dict2)
|
||||
assert HashDict.get(dict, 16) == 16
|
||||
assert HashDict.get(dict, 18) == 36
|
||||
assert HashDict.get(dict, 22) == 44
|
||||
assert HashDict.size(dict) == 22
|
||||
|
||||
dict = HashDict.merge(dict2, dict1)
|
||||
assert HashDict.get(dict, 16) == 16
|
||||
assert HashDict.get(dict, 18) == 18
|
||||
assert HashDict.get(dict, 22) == 44
|
||||
assert HashDict.size(dict) == 22
|
||||
|
||||
dict = HashDict.merge(dict1, dict2, fn(k, v1, v2) -> 3*k + 2*v1 + v2 end)
|
||||
assert HashDict.get(dict, 16) == 16
|
||||
assert HashDict.get(dict, 18) == 18 * 7
|
||||
assert HashDict.get(dict, 22) == 44
|
||||
assert HashDict.size(dict) == 22
|
||||
|
||||
list = Enum.map 18..22, fn x -> { x, x * 2 } end
|
||||
dict = HashDict.merge(dict1, list)
|
||||
assert HashDict.get(dict, 16) == 16
|
||||
assert HashDict.get(dict, 18) == 36
|
||||
assert HashDict.get(dict, 22) == 44
|
||||
assert HashDict.size(dict) == 22
|
||||
end
|
||||
|
||||
test "large dict merge" do
|
||||
dict1 = filled_dict(120)
|
||||
dict2 = Enum.reduce 118..122, HashDict.new, fn(i, d) -> HashDict.put(d, i, i * 2) end
|
||||
|
||||
dict = HashDict.merge(dict1, dict2)
|
||||
assert HashDict.get(dict, 116) == 116
|
||||
assert HashDict.get(dict, 118) == 236
|
||||
assert HashDict.get(dict, 122) == 244
|
||||
assert HashDict.size(dict) == 122
|
||||
|
||||
dict = HashDict.merge(dict2, dict1)
|
||||
assert HashDict.get(dict, 116) == 116
|
||||
assert HashDict.get(dict, 118) == 118
|
||||
assert HashDict.get(dict, 122) == 244
|
||||
assert HashDict.size(dict) == 122
|
||||
|
||||
dict = HashDict.merge(dict1, dict2, fn(k, v1, v2) -> 3*k + 2*v1 + v2 end)
|
||||
assert HashDict.get(dict, 116) == 116
|
||||
assert HashDict.get(dict, 118) == 118 * 7
|
||||
assert HashDict.get(dict, 122) == 244
|
||||
assert HashDict.size(dict) == 122
|
||||
|
||||
list = Enum.map 118..122, fn x -> { x, x * 2 } end
|
||||
dict = HashDict.merge(dict1, list)
|
||||
assert HashDict.get(dict, 116) == 116
|
||||
assert HashDict.get(dict, 118) == 236
|
||||
assert HashDict.get(dict, 122) == 244
|
||||
assert HashDict.size(dict) == 122
|
||||
end
|
||||
|
||||
test "trie contraction" do
|
||||
dict = filled_dict(120)
|
||||
dict = Enum.reduce 16..120, dict, fn(x, acc) -> HashDict.delete(acc, x) end
|
||||
assert (Enum.filter 1..120, fn(x) -> HashDict.get(dict, x) == x end) == (Enum.sort 1..15)
|
||||
end
|
||||
|
||||
test "is zippable" do
|
||||
dict = filled_dict(8)
|
||||
list = Dict.to_list(dict)
|
||||
assert Enum.zip(list, list) == Enum.zip(dict, dict)
|
||||
|
||||
dict = filled_dict(120)
|
||||
list = Dict.to_list(dict)
|
||||
assert Enum.zip(list, list) == Enum.zip(dict, dict)
|
||||
refute HashDict.equal?(d1, d2)
|
||||
refute HashDict.equal?(d2, d1)
|
||||
end
|
||||
|
||||
defp smoke_test(range) do
|
||||
@@ -312,7 +79,7 @@ defmodule HashDictTest do
|
||||
{ acc, i - 1 }
|
||||
end
|
||||
|
||||
dict
|
||||
assert dict == HashDict.new
|
||||
end
|
||||
|
||||
defp filled_dict(range) do
|
||||
|
||||
@@ -3,65 +3,28 @@ Code.require_file "test_helper.exs", __DIR__
|
||||
defmodule HashSetTest do
|
||||
use ExUnit.Case, async: true
|
||||
|
||||
test "union with ordered sets" do
|
||||
assert HashSet.union(HashSet.new([1, 2, 3]), HashSet.new([3, 4, 5])) == HashSet.new([1, 2, 3, 4, 5])
|
||||
assert HashSet.union(HashSet.new, HashSet.new) == HashSet.new
|
||||
end
|
||||
|
||||
test "union with nodes" do
|
||||
test "union" do
|
||||
assert HashSet.union(filled_set(21), filled_set(22)) == filled_set(22)
|
||||
|
||||
assert HashSet.union(filled_set(121), filled_set(120)) == filled_set(121)
|
||||
end
|
||||
|
||||
test "intersection with ordered sets" do
|
||||
assert HashSet.intersection(HashSet.new([1, 2, 3, 4, 6]), HashSet.new([3, 4, 5])) == HashSet.new([3, 4])
|
||||
assert HashSet.intersection(HashSet.new, filled_set(8)) == HashSet.new
|
||||
assert HashSet.intersection(filled_set(8), HashSet.new) == HashSet.new
|
||||
assert HashSet.intersection(HashSet.new, HashSet.new) == HashSet.new
|
||||
end
|
||||
|
||||
test "intersection with nodes" do
|
||||
test "intersection" do
|
||||
assert HashSet.intersection(filled_set(21), filled_set(20)) == filled_set(20)
|
||||
|
||||
assert HashSet.equal?(HashSet.intersection(filled_set(120), filled_set(121)), filled_set(120))
|
||||
end
|
||||
|
||||
test "difference with ordered sets" do
|
||||
assert HashSet.difference(HashSet.new([1, 2, 3, 5]), HashSet.new([3, 4, 5])) == HashSet.new([1, 2])
|
||||
assert HashSet.difference(HashSet.new, HashSet.new([1])) == HashSet.new([])
|
||||
assert HashSet.difference(HashSet.new, HashSet.new) == HashSet.new
|
||||
assert HashSet.difference(HashSet.new([1]), HashSet.new) == HashSet.new([1])
|
||||
end
|
||||
|
||||
test "difference with nodes" do
|
||||
test "difference" do
|
||||
assert HashSet.equal?(HashSet.difference(filled_set(20), filled_set(21)), HashSet.new([]))
|
||||
|
||||
diff = HashSet.difference(filled_set(9000), filled_set(9000))
|
||||
assert HashSet.equal?(diff, HashSet.new([]))
|
||||
|
||||
# Ensure the trie has correctly resized
|
||||
assert { HashSet, 0, 0, 80, 2, _ } = diff
|
||||
end
|
||||
|
||||
test "member? with ordered sets" do
|
||||
assert HashSet.member?(filled_set(8), 8)
|
||||
refute HashSet.member?(filled_set(8), 10)
|
||||
end
|
||||
|
||||
test "member? with nodes" do
|
||||
assert HashSet.member?(filled_set(20), 19)
|
||||
refute HashSet.member?(filled_set(20), 100)
|
||||
|
||||
assert HashSet.member?(filled_set(120), 90)
|
||||
refute HashSet.member?(filled_set(120), 200)
|
||||
assert HashSet.size(diff) == 0
|
||||
end
|
||||
|
||||
test "subset?" do
|
||||
assert HashSet.subset?(HashSet.new, HashSet.new)
|
||||
assert HashSet.subset?(filled_set(6), filled_set(10))
|
||||
assert HashSet.subset?(filled_set(6), filled_set(120))
|
||||
|
||||
refute HashSet.subset?(filled_set(120), filled_set(6))
|
||||
end
|
||||
|
||||
@@ -71,54 +34,22 @@ defmodule HashSetTest do
|
||||
assert HashSet.equal?(filled_set(120), filled_set(120))
|
||||
end
|
||||
|
||||
test "empty" do
|
||||
assert HashSet.empty filled_set(8) == HashSet.new
|
||||
assert HashSet.empty filled_set(20) == HashSet.new
|
||||
assert HashSet.empty filled_set(120) == HashSet.new
|
||||
end
|
||||
|
||||
test "to_list" do
|
||||
set = filled_set(8)
|
||||
list = set |> HashSet.to_list
|
||||
assert length(list) == 8
|
||||
assert 1 in list
|
||||
assert list == Enum.to_list(set)
|
||||
|
||||
set = filled_set(20)
|
||||
list = set |> HashSet.to_list
|
||||
list = HashSet.to_list(set)
|
||||
assert length(list) == 20
|
||||
assert 1 in list
|
||||
assert list == Enum.to_list(set)
|
||||
assert Enum.sort(list) == Enum.sort(1..20)
|
||||
|
||||
set = filled_set(120)
|
||||
list = set |> HashSet.to_list
|
||||
list = HashSet.to_list(set)
|
||||
assert length(list) == 120
|
||||
assert 1 in list
|
||||
assert list == Enum.to_list(set)
|
||||
end
|
||||
|
||||
test "delete" do
|
||||
assert HashSet.delete(filled_set(8), 8) == filled_set(7)
|
||||
assert HashSet.delete(filled_set(8), 9) == filled_set(8)
|
||||
assert HashSet.delete(HashSet.new, 10) == HashSet.new
|
||||
|
||||
assert HashSet.delete(filled_set(21), 21) == filled_set(20)
|
||||
|
||||
assert HashSet.delete(filled_set(121), 121) == filled_set(120)
|
||||
end
|
||||
|
||||
test "is zippable" do
|
||||
set = filled_set(8)
|
||||
list = Dict.to_list(set)
|
||||
assert Enum.zip(list, list) == Enum.zip(set, set)
|
||||
|
||||
set = filled_set(120)
|
||||
list = Dict.to_list(set)
|
||||
assert Enum.zip(list, list) == Enum.zip(set, set)
|
||||
assert Enum.sort(list) == Enum.sort(1..120)
|
||||
end
|
||||
|
||||
defp filled_set(range) do
|
||||
Enum.reduce 1..range, HashSet.new, &HashSet.put(&2, &1)
|
||||
HashSet.new(1..range)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -17,94 +17,93 @@ defmodule Inspect.AlgebraTest do
|
||||
|
||||
def factor(doc, w), do: format(w, 0, [{0, :flat, group(doc)}])
|
||||
|
||||
test :empty do
|
||||
test "empty doc" do
|
||||
# Consistence with definitions
|
||||
assert empty == :doc_nil
|
||||
|
||||
# Consistence of corresponding sdoc
|
||||
assert factor(empty, 80) == []
|
||||
|
||||
# Consistent formatting
|
||||
assert pretty(empty, 80) == ""
|
||||
end
|
||||
|
||||
test :break do
|
||||
test "break doc" do
|
||||
# Consistence with definitions
|
||||
## Normal case
|
||||
assert break("break") == { :doc_break, "break" }
|
||||
## Degeneracy
|
||||
assert break("") == { :doc_break, "" }
|
||||
## ong argument type
|
||||
|
||||
# Wrong argument type
|
||||
assert_raise FunctionClauseError, fn -> break(42) end
|
||||
|
||||
# Consistence of corresponding sdoc
|
||||
assert factor(break("_"), 80) == ["_"]
|
||||
|
||||
# Consistent formatting
|
||||
assert pretty(break("_"), 80) == "_"
|
||||
end
|
||||
|
||||
test :glue do
|
||||
test "glue doc" do
|
||||
# Consistence with definitions
|
||||
assert glue("a", "->", "b") == { :doc_cons,
|
||||
"a", { :doc_cons, { :doc_break, "->" }, "b" }
|
||||
}
|
||||
assert glue("a", "b") == glue("a", " ", "b")
|
||||
|
||||
## Wrong argument type
|
||||
# Wrong argument type
|
||||
assert_raise FunctionClauseError, fn -> glue("a", 42, "b") end
|
||||
end
|
||||
|
||||
test :text do
|
||||
# Consistence of corresponding docfactor
|
||||
test "text doc" do
|
||||
# Consistence of corresponding sdoc
|
||||
assert factor("_", 80) == ["_"]
|
||||
|
||||
# Consistent formatting
|
||||
assert pretty("_", 80) == "_"
|
||||
end
|
||||
|
||||
test :space do
|
||||
test "space doc" do
|
||||
# Consistency with definitions
|
||||
assert space("a", "b") == { :doc_cons,
|
||||
"a", { :doc_cons, " ", "b" }
|
||||
}
|
||||
end
|
||||
|
||||
test :nest do
|
||||
test "nest doc" do
|
||||
# Consistence with definitions
|
||||
## Normal case
|
||||
assert nest(empty, 1) == { :doc_nest, 1, empty }
|
||||
## Degeneracy
|
||||
assert nest(empty, 0) == :doc_nil
|
||||
## ong argument type
|
||||
|
||||
# Wrong argument type
|
||||
assert_raise FunctionClauseError, fn -> nest("foo", empty) end
|
||||
|
||||
a1 = fn -> nest("a", 1) end
|
||||
alb1 = fn -> nest(glue("a", "b"), 1) end
|
||||
# Consistence of corresponding sdoc
|
||||
## Trivial case
|
||||
assert factor(a1.(), 80) == ["a"]
|
||||
## Correctly indenting line forcing linebreak
|
||||
assert format(2, 0, [{0, :break, alb1.()}]) == ["a", "\n", " ", "b"]
|
||||
assert factor(nest("a", 1), 80) == ["a"]
|
||||
assert format(2, 0, [{0, :break, nest(glue("a", "b"), 1)}]) == ["a", "\n ", "b"]
|
||||
|
||||
# Consistent formatting
|
||||
## Trivial case
|
||||
assert pretty(a1.(), 80) == "a"
|
||||
## Correctly indenting line
|
||||
assert render(format 2, 0, [{0, :break, alb1.()}]) == "a\n b"
|
||||
assert pretty(nest("a", 1), 80) == "a"
|
||||
assert render(format 2, 0, [{0, :break, nest(glue("a", "b"), 1)}]) == "a\n b"
|
||||
end
|
||||
|
||||
test :infinity do
|
||||
# w = :infinity should disable pretty printer
|
||||
s = String.duplicate "x", 50
|
||||
g = ";"
|
||||
big_document = group(glue(s, g, s) |> glue(g, s) |> glue(g, s) |> glue(g, s))
|
||||
test "line doc" do
|
||||
# Consistency with definitions
|
||||
assert line("a", "b") ==
|
||||
{ :doc_cons, "a", { :doc_cons, :doc_line, "b" } }
|
||||
|
||||
assert pretty(big_document, :infinity) == s <> g <> s <> g <> s <> g <> s <> g <> s
|
||||
# Consistence of corresponding sdoc
|
||||
assert factor(line("a", "b"), 1) == ["a", "\n", "b"]
|
||||
assert factor(line("a", "b"), 9) == ["a", "\n", "b"]
|
||||
|
||||
# Consistent formatting
|
||||
assert pretty(line(glue("aaa", "bbb"), glue("ccc", "ddd")), 10) ==
|
||||
"aaa bbb\nccc ddd"
|
||||
end
|
||||
|
||||
test :group do
|
||||
test "group doc" do
|
||||
# Consistency with definitions
|
||||
## Normal case
|
||||
assert group(glue("a", "b")) ==
|
||||
{ :doc_group, { :doc_cons, "a", concat(break, "b") }}
|
||||
|
||||
## Degeneracy
|
||||
assert group(empty) == { :doc_group, empty }
|
||||
|
||||
# Consistence of corresponding sdoc
|
||||
@@ -115,4 +114,12 @@ defmodule Inspect.AlgebraTest do
|
||||
assert pretty(helloabcd, 5) == "hello\na b\ncd"
|
||||
assert pretty(helloabcd, 80) == "hello a b cd"
|
||||
end
|
||||
|
||||
test "formatting with infinity" do
|
||||
s = String.duplicate "x", 50
|
||||
g = ";"
|
||||
doc = group(glue(s, g, s) |> glue(g, s) |> glue(g, s) |> glue(g, s))
|
||||
|
||||
assert pretty(doc, :infinity) == s <> g <> s <> g <> s <> g <> s <> g <> s
|
||||
end
|
||||
end
|
||||
|
||||
@@ -37,11 +37,22 @@ defmodule Inspect.AtomTest do
|
||||
end
|
||||
|
||||
test :op do
|
||||
assert inspect(:@) == ":@"
|
||||
assert inspect(:+) == ":+"
|
||||
assert inspect(:&&&) == ":&&&"
|
||||
assert inspect(:~~~) == ":~~~"
|
||||
end
|
||||
|
||||
test :... do
|
||||
assert inspect(:...) == ":..."
|
||||
end
|
||||
|
||||
test :@ do
|
||||
assert inspect(:@) == ":@"
|
||||
assert inspect(:foo@bar) == ":foo@bar"
|
||||
assert inspect(:foo@bar@) == ":foo@bar@"
|
||||
assert inspect(:foo@bar@baz) == ":foo@bar@baz"
|
||||
end
|
||||
|
||||
test :container do
|
||||
assert inspect(:<<>>) == ":<<>>"
|
||||
assert inspect(:{}) == ":{}"
|
||||
@@ -71,15 +82,15 @@ defmodule Inspect.BitStringTest do
|
||||
end
|
||||
|
||||
test :opt_infer do
|
||||
assert inspect(<<"eric", 193, "mj">>, binaries: :infer) == %s(<<101, 114, 105, 99, 193, 109, 106>>)
|
||||
assert inspect(<<"eric">>, binaries: :infer) == %s("eric")
|
||||
assert inspect(<<193>>, binaries: :infer) == %s(<<193>>)
|
||||
assert inspect(<<"eric", 193, "mj">>, binaries: :infer) == ~s(<<101, 114, 105, 99, 193, 109, 106>>)
|
||||
assert inspect(<<"eric">>, binaries: :infer) == ~s("eric")
|
||||
assert inspect(<<193>>, binaries: :infer) == ~s(<<193>>)
|
||||
end
|
||||
|
||||
test :opt_as_strings do
|
||||
assert inspect(<<"eric", 193, "mj">>, binaries: :as_strings) == %s("eric\\301mj")
|
||||
assert inspect(<<"eric">>, binaries: :as_strings) == %s("eric")
|
||||
assert inspect(<<193>>, binaries: :as_strings) == %s("\\301")
|
||||
assert inspect(<<"eric", 193, "mj">>, binaries: :as_strings) == ~s("eric\\301mj")
|
||||
assert inspect(<<"eric">>, binaries: :as_strings) == ~s("eric")
|
||||
assert inspect(<<193>>, binaries: :as_strings) == ~s("\\301")
|
||||
end
|
||||
|
||||
test :opt_as_binaries do
|
||||
@@ -145,20 +156,20 @@ defmodule Inspect.TupleTest do
|
||||
assert inspect(RuntimeError.new) == "RuntimeError[message: \"runtime error\"]"
|
||||
end
|
||||
|
||||
defrecord :something, [:a, :b]
|
||||
|
||||
test :non_module_record do
|
||||
# They are on purpose not treated as Elixir records
|
||||
# otherwise it affects inspect performance very strongly
|
||||
assert inspect(:something.new) == "{:something, nil, nil}"
|
||||
end
|
||||
|
||||
defrecord Rec, value: 1
|
||||
|
||||
test :two_items_record do
|
||||
assert inspect({ Rec[value: 1], 1 }) == "{Inspect.TupleTest.Rec[value: 1], 1}"
|
||||
end
|
||||
|
||||
test :false_positives do
|
||||
import ExUnit.CaptureIO
|
||||
|
||||
assert capture_io(:stderr, fn ->
|
||||
assert inspect({ Range, nil }) == "{Range, nil}"
|
||||
end) =~ "** (Inspect.Error) Got FunctionClauseError with message no function clause matching in Inspect.Range.inspect/2"
|
||||
end
|
||||
|
||||
test :empty do
|
||||
assert inspect({}) == "{}"
|
||||
end
|
||||
@@ -188,7 +199,7 @@ defmodule Inspect.ListTest do
|
||||
assert inspect([a: 1]) == "[a: 1]"
|
||||
assert inspect([a: 1, b: 2]) == "[a: 1, b: 2]"
|
||||
assert inspect([a: 1, a: 2, b: 2]) == "[a: 1, a: 2, b: 2]"
|
||||
assert inspect(["123": 1]) == %s(["123": 1])
|
||||
assert inspect(["123": 1]) == ~s(["123": 1])
|
||||
|
||||
assert inspect([foo: [1,2,3,:bar], bazzz: :bat], [pretty: true, width: 30]) ==
|
||||
"[foo: [1, 2, 3, :bar],\n bazzz: :bat]"
|
||||
@@ -270,6 +281,6 @@ defmodule Inspect.OthersTest do
|
||||
end
|
||||
|
||||
test :regex do
|
||||
"%r\"foo\"m" = inspect(%r(foo)m)
|
||||
"~r\"foo\"m" = inspect(~r(foo)m)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -2,8 +2,10 @@ Code.require_file "test_helper.exs", __DIR__
|
||||
|
||||
defmodule IntegerTest do
|
||||
use ExUnit.Case, async: true
|
||||
require Integer
|
||||
|
||||
test :odd? do
|
||||
assert Integer.odd?(0) == false
|
||||
assert Integer.odd?(1) == true
|
||||
assert Integer.odd?(2) == false
|
||||
assert Integer.odd?(3) == true
|
||||
@@ -13,6 +15,7 @@ defmodule IntegerTest do
|
||||
end
|
||||
|
||||
test :even? do
|
||||
assert Integer.even?(0) == true
|
||||
assert Integer.even?(1) == false
|
||||
assert Integer.even?(2) == true
|
||||
assert Integer.even?(3) == false
|
||||
|
||||
@@ -35,6 +35,12 @@ bar """
|
||||
"""
|
||||
end
|
||||
|
||||
test :heredoc_in_call do
|
||||
assert "foo\nbar" == Kernel.<>("""
|
||||
foo
|
||||
""", "bar")
|
||||
end
|
||||
|
||||
test :utf8 do
|
||||
assert size(" ゆんゆん") == 13
|
||||
end
|
||||
@@ -112,19 +118,19 @@ bar """
|
||||
|
||||
test :literal_errors do
|
||||
assert_raise CompileError, fn ->
|
||||
Code.eval_string(%s[<< "foo" :: integer >>])
|
||||
Code.eval_string(~s[<< "foo" :: integer >>])
|
||||
end
|
||||
|
||||
assert_raise CompileError, fn ->
|
||||
Code.eval_string(%s[<< "foo" :: float >>])
|
||||
Code.eval_string(~s[<< "foo" :: float >>])
|
||||
end
|
||||
|
||||
assert_raise CompileError, fn ->
|
||||
Code.eval_string(%s[<< 'foo' :: binary >>])
|
||||
Code.eval_string(~s[<< 'foo' :: binary >>])
|
||||
end
|
||||
|
||||
assert_raise ArgumentError, fn ->
|
||||
Code.eval_string(%s[<<1::size(4)>> <> "foo"])
|
||||
Code.eval_string(~s[<<1::size(4)>> <> "foo"])
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ defmodule Kernel.ComprehensionTest do
|
||||
end
|
||||
|
||||
test :list_comprehensions_with_truthy_object do
|
||||
assert [2, 4, 6] == lc x inlist [1, 2, 3], 1, do: x * 2
|
||||
assert [2, 4, 6] == lc x inlist [1, 2, 3], List.first([x]), do: x * 2
|
||||
end
|
||||
|
||||
test :list_comprehensions_with_inlist_of_bins do
|
||||
@@ -48,7 +48,7 @@ defmodule Kernel.ComprehensionTest do
|
||||
end
|
||||
|
||||
test :bit_comprehensions_with_truthy_object do
|
||||
assert <<2, 4, 6>> == bc x inlist [1, 2, 3], 1, do: <<x * 2>>
|
||||
assert <<2, 4, 6>> == bc x inlist [1, 2, 3], List.first([1]), do: <<x * 2>>
|
||||
end
|
||||
|
||||
test :bit_comprehensions_with_inlist_of_bins do
|
||||
|
||||
@@ -11,7 +11,7 @@ defmodule Kernel.DocsTest do
|
||||
|
||||
expected = [
|
||||
{{:example, 1}, 5, :def, [{:var, [line: 6], nil}], "Some example"},
|
||||
{{:nodoc, 1}, 8, :def, [{:"//", [line: 8], [{:var, [line: 8], nil}, 0]}], nil}
|
||||
{{:nodoc, 1}, 8, :def, [{:\\, [line: 8], [{:var, [line: 8], nil}, 0]}], nil}
|
||||
]
|
||||
|
||||
assert CompiledWithDocs.__info__(:docs) == expected
|
||||
|
||||
@@ -25,16 +25,16 @@ defmodule Kernel.ErrorsTest do
|
||||
|
||||
test :sigil_terminator do
|
||||
assert_compile_fail TokenMissingError,
|
||||
"nofile:3: missing terminator: \" (for sigil %r\" starting at line 1)",
|
||||
'%r"foo\n\n'
|
||||
"nofile:3: missing terminator: \" (for sigil ~r\" starting at line 1)",
|
||||
'~r"foo\n\n'
|
||||
|
||||
assert_compile_fail TokenMissingError,
|
||||
"nofile:3: missing terminator: } (for sigil %r{ starting at line 1)",
|
||||
'%r{foo\n\n'
|
||||
"nofile:3: missing terminator: } (for sigil ~r{ starting at line 1)",
|
||||
'~r{foo\n\n'
|
||||
|
||||
assert_compile_fail TokenMissingError,
|
||||
"nofile:3: missing terminator: ) (for sigil %r( starting at line 1)",
|
||||
'%r(f(oo)\n\n'
|
||||
"nofile:3: missing terminator: ) (for sigil ~r( starting at line 1)",
|
||||
'~r(f(oo)\n\n'
|
||||
end
|
||||
|
||||
test :dot_terminator do
|
||||
@@ -49,6 +49,12 @@ defmodule Kernel.ErrorsTest do
|
||||
'"bar'
|
||||
end
|
||||
|
||||
test :heredoc_start do
|
||||
assert_compile_fail TokenMissingError,
|
||||
"nofile:1: heredoc start \"\"\" must be followed by a new line",
|
||||
'"""bar'
|
||||
end
|
||||
|
||||
test :heredoc_terminator do
|
||||
assert_compile_fail TokenMissingError,
|
||||
"nofile:2: missing terminator: \"\"\" (for heredoc starting at line 1)",
|
||||
@@ -98,11 +104,9 @@ defmodule Kernel.ErrorsTest do
|
||||
assert_compile_fail SyntaxError, msg, 'foo 1, foo 2, 3'
|
||||
assert_compile_fail SyntaxError, msg, 'foo(1, foo 2, 3)'
|
||||
|
||||
assert is_list []
|
||||
assert is_list do: 1
|
||||
assert is_list List.flatten [1]
|
||||
assert is_atom is_record 1..3, Range
|
||||
assert is_atom(is_record 1..3, Range)
|
||||
assert is_atom is_record range, Range
|
||||
assert is_atom(is_record range, Range)
|
||||
end
|
||||
|
||||
test :syntax_error_with_no_token do
|
||||
@@ -115,10 +119,10 @@ defmodule Kernel.ErrorsTest do
|
||||
assert_compile_fail CompileError,
|
||||
"nofile:3: def hello/1 has default values and multiple clauses, " <>
|
||||
"use a separate clause for declaring defaults",
|
||||
'''
|
||||
~C'''
|
||||
defmodule ErrorsTest do
|
||||
def hello(arg // 0), do: nil
|
||||
def hello(arg // 1), do: nil
|
||||
def hello(arg \\ 0), do: nil
|
||||
def hello(arg \\ 1), do: nil
|
||||
end
|
||||
'''
|
||||
end
|
||||
@@ -136,19 +140,19 @@ defmodule Kernel.ErrorsTest do
|
||||
test :different_defs_with_defaults do
|
||||
assert_compile_fail CompileError,
|
||||
"nofile:3: def hello/3 defaults conflicts with def hello/2",
|
||||
'''
|
||||
~C'''
|
||||
defmodule ErrorsTest do
|
||||
def hello(a, b // nil), do: a + b
|
||||
def hello(a, b // nil, c // nil), do: a + b + c
|
||||
def hello(a, b \\ nil), do: a + b
|
||||
def hello(a, b \\ nil, c \\ nil), do: a + b + c
|
||||
end
|
||||
'''
|
||||
|
||||
assert_compile_fail CompileError,
|
||||
"nofile:3: def hello/2 conflicts with defaults from def hello/3",
|
||||
'''
|
||||
~C'''
|
||||
defmodule ErrorsTest do
|
||||
def hello(a, b // nil, c // nil), do: a + b + c
|
||||
def hello(a, b // nil), do: a + b
|
||||
def hello(a, b \\ nil, c \\ nil), do: a + b + c
|
||||
def hello(a, b \\ nil), do: a + b
|
||||
end
|
||||
'''
|
||||
end
|
||||
@@ -238,7 +242,7 @@ defmodule Kernel.ErrorsTest do
|
||||
"nofile:1: imported Kernel.&&/2 conflicts with local function",
|
||||
'''
|
||||
defmodule ErrorsTest do
|
||||
1 && 2
|
||||
def other, do: 1 && 2
|
||||
def _ && _, do: :error
|
||||
end
|
||||
'''
|
||||
@@ -485,7 +489,7 @@ defmodule Kernel.ErrorsTest do
|
||||
end
|
||||
|
||||
test :invalid_access_protocol_not_keywords do
|
||||
assert_raise ArgumentError, "expected contents inside brackets to be a keyword list, got: [0]", fn ->
|
||||
assert_raise ArgumentError, "expected contents inside brackets to be a keyword list or an atom, got: [0]", fn ->
|
||||
defmodule ErrorsTest do
|
||||
def sample(Kernel.ErrorsTest.Config[0]), do: true
|
||||
end
|
||||
@@ -648,6 +652,10 @@ defmodule Kernel.ErrorsTest do
|
||||
quote(do: _)
|
||||
end
|
||||
|
||||
defp range do
|
||||
1..3
|
||||
end
|
||||
|
||||
## Helpers
|
||||
|
||||
defp rescue_stacktrace(expr) do
|
||||
|
||||
@@ -127,10 +127,10 @@ defmodule Kernel.ExpansionTest do
|
||||
test "vars: forces variable to exist" do
|
||||
assert expand(quote do: (var!(a) = 1; var!(a)))
|
||||
|
||||
message = %r"expected var a to expand to an existing variable or be a part of a match"
|
||||
message = ~r"expected var a to expand to an existing variable or be a part of a match"
|
||||
assert_raise CompileError, message, fn -> expand(quote do: var!(a)) end
|
||||
|
||||
message = %r"expected var a \(context Unknown\) to expand to an existing variable or be a part of a match"
|
||||
message = ~r"expected var a \(context Unknown\) to expand to an existing variable or be a part of a match"
|
||||
assert_raise CompileError, message, fn -> expand(quote do: var!(a, Unknown)) end
|
||||
end
|
||||
|
||||
@@ -139,13 +139,13 @@ defmodule Kernel.ExpansionTest do
|
||||
end
|
||||
|
||||
test "^: raises outside match" do
|
||||
assert_raise CompileError, %r"cannot use \^a outside of match clauses", fn ->
|
||||
assert_raise CompileError, ~r"cannot use \^a outside of match clauses", fn ->
|
||||
expand(quote do: ^a)
|
||||
end
|
||||
end
|
||||
|
||||
test "^: raises without var" do
|
||||
assert_raise CompileError, %r"invalid argument for unary operator \^, expected an existing variable, got: \^1", fn ->
|
||||
assert_raise CompileError, ~r"invalid argument for unary operator \^, expected an existing variable, got: \^1", fn ->
|
||||
expand(quote do: ^1 = 1)
|
||||
end
|
||||
end
|
||||
@@ -162,6 +162,11 @@ defmodule Kernel.ExpansionTest do
|
||||
quote(do: :"Elixir.Hello".a())
|
||||
end
|
||||
|
||||
test "locals: in guards" do
|
||||
assert expand(quote(do: fn pid when :erlang.==(pid, self) -> pid end)) ==
|
||||
quote(do: fn pid when :erlang.==(pid, :erlang.self()) -> pid end)
|
||||
end
|
||||
|
||||
test "locals: custom imports" do
|
||||
assert expand(quote do: (import Kernel.ExpansionTarget; seventeen)) ==
|
||||
quote do: (import :"Elixir.Kernel.ExpansionTarget", []; 17)
|
||||
@@ -187,7 +192,7 @@ defmodule Kernel.ExpansionTest do
|
||||
end
|
||||
|
||||
test "anonymous calls: raises on atom base" do
|
||||
assert_raise CompileError, %r"invalid function call :foo.()", fn ->
|
||||
assert_raise CompileError, ~r"invalid function call :foo.()", fn ->
|
||||
expand(quote do: :foo.(a))
|
||||
end
|
||||
end
|
||||
@@ -213,7 +218,7 @@ defmodule Kernel.ExpansionTest do
|
||||
end
|
||||
|
||||
test "remote calls: raises when not required" do
|
||||
msg = %r"you must require Kernel\.ExpansionTarget before invoking the macro Kernel\.ExpansionTarget\.seventeen/0"
|
||||
msg = ~r"you must require Kernel\.ExpansionTarget before invoking the macro Kernel\.ExpansionTarget\.seventeen/0"
|
||||
assert_raise CompileError, msg, fn ->
|
||||
expand(quote do: Kernel.ExpansionTarget.seventeen)
|
||||
end
|
||||
@@ -381,9 +386,13 @@ defmodule Kernel.ExpansionTest do
|
||||
## Invalid
|
||||
|
||||
test "handles invalid expressions" do
|
||||
assert_raise CompileError, %r"invalid quoted expression: {1, 2, 3}", fn ->
|
||||
assert_raise CompileError, ~r"invalid quoted expression: {1, 2, 3}", fn ->
|
||||
expand(quote do: unquote({ 1, 2, 3 }))
|
||||
end
|
||||
|
||||
assert_raise CompileError, ~r"invalid quoted expression: #Function<", fn ->
|
||||
expand(quote do: unquote({ :sample, fn -> end }))
|
||||
end
|
||||
end
|
||||
|
||||
## Helpers
|
||||
|
||||
@@ -11,11 +11,6 @@ defmodule Kernel.FnTest do
|
||||
assert (fn +1 -> :ok end).(1) == :ok
|
||||
end
|
||||
|
||||
test "clause with ^" do
|
||||
x = 1
|
||||
assert (fn ^x -> :ok; _ -> :error end).(1) == :ok
|
||||
end
|
||||
|
||||
test "capture remote" do
|
||||
assert (&:erlang.atom_to_list/1).(:a) == 'a'
|
||||
assert (&Kernel.atom_to_list/1).(:a) == 'a'
|
||||
@@ -52,6 +47,11 @@ defmodule Kernel.FnTest do
|
||||
assert (&Kernel.to_string(&1)).(:a) == "a"
|
||||
end
|
||||
|
||||
test "capture operator" do
|
||||
assert is_function &+/2
|
||||
assert is_function &(&&/2)
|
||||
end
|
||||
|
||||
test "local partial application" do
|
||||
assert (&atb(&1, :utf8)).(:a) == "a"
|
||||
assert (&atb(list_to_atom(&1), :utf8)).('a') == "a"
|
||||
|
||||
@@ -21,7 +21,7 @@ defmodule Kernel.MacrosTest do
|
||||
quote do: 1 + 3
|
||||
end
|
||||
|
||||
defmacro my_macro_with_default(value // 5) do
|
||||
defmacro my_macro_with_default(value \\ 5) do
|
||||
quote do: 1 + unquote(value)
|
||||
end
|
||||
|
||||
|
||||
@@ -27,7 +27,7 @@ defmodule Kernel.QuoteTest do
|
||||
end
|
||||
|
||||
test :unquote_call do
|
||||
assert quote(do: foo(bar)[:baz])
|
||||
assert quote(do: foo(bar)[unquote(:baz)]) == quote(do: foo(bar)[:baz])
|
||||
assert quote(do: unquote(:bar)()) == quote(do: bar())
|
||||
assert quote(do: unquote(:bar)(1) do 2 + 3 end) == quote(do: bar(1) do 2 + 3 end)
|
||||
assert quote(do: foo.unquote(:bar)) == quote(do: foo.bar)
|
||||
@@ -151,6 +151,12 @@ defmodule Kernel.QuoteTest do
|
||||
test :with_dynamic_opts do
|
||||
assert quote(dynamic_opts, do: bar(1, 2, 3)) == { :bar, [line: 3], [1, 2, 3] }
|
||||
end
|
||||
|
||||
test :unary_with_integer_precedence do
|
||||
assert quote(do: +1.foo) == quote(do: (+1).foo)
|
||||
assert quote(do: @1.foo) == quote(do: (@1).foo)
|
||||
assert quote(do: &1.foo) == quote(do: (&1).foo)
|
||||
end
|
||||
end
|
||||
|
||||
## DO NOT MOVE THIS LINE
|
||||
@@ -180,7 +186,7 @@ defmodule Kernel.QuoteTest.ErrorsTest do
|
||||
|
||||
mod = Kernel.QuoteTest.ErrorsTest
|
||||
file = __ENV__.file |> Path.relative_to_cwd |> String.to_char_list!
|
||||
assert [{ ^mod, :add, 2, [file: ^file, line: 160] }|_] = System.stacktrace
|
||||
assert [{ ^mod, :add, 2, [file: ^file, line: 166] }|_] = System.stacktrace
|
||||
end
|
||||
|
||||
test :outside_function_error do
|
||||
@@ -190,7 +196,7 @@ defmodule Kernel.QuoteTest.ErrorsTest do
|
||||
|
||||
mod = Kernel.QuoteTest.ErrorsTest
|
||||
file = __ENV__.file |> Path.relative_to_cwd |> String.to_char_list!
|
||||
assert [{ ^mod, _, _, [file: ^file, line: 188] }|_] = System.stacktrace
|
||||
assert [{ ^mod, _, _, [file: ^file, line: 194] }|_] = System.stacktrace
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
Code.require_file "../test_helper.exs", __DIR__
|
||||
|
||||
defrecord BadRange, first: 0, last: 0 do
|
||||
defrecord Rec, first: 0, last: 0
|
||||
|
||||
defrecord BadRec, first: 0, last: 0 do
|
||||
defoverridable [first: 1]
|
||||
|
||||
def first(_) do
|
||||
@@ -67,11 +69,11 @@ defmodule Kernel.RecordRewriterTest do
|
||||
end
|
||||
|
||||
test "with tuple match" do
|
||||
clause = clause(fn({ x, y } = { Macro.Env[], Range[] }) -> :foo end)
|
||||
assert optimize_clause(clause) == { clause, [x: Macro.Env, y: Range], nil }
|
||||
clause = clause(fn({ x, y } = { Macro.Env[], Rec[] }) -> :foo end)
|
||||
assert optimize_clause(clause) == { clause, [x: Macro.Env, y: Rec], nil }
|
||||
|
||||
clause = clause(fn({ Macro.Env[], y } = { x, Range[] }) -> :foo end)
|
||||
assert optimize_clause(clause) == { clause, [x: Macro.Env, y: Range], nil }
|
||||
clause = clause(fn({ Macro.Env[], y } = { x, Rec[] }) -> :foo end)
|
||||
assert optimize_clause(clause) == { clause, [x: Macro.Env, y: Rec], nil }
|
||||
end
|
||||
|
||||
test "inside body" do
|
||||
@@ -80,13 +82,13 @@ defmodule Kernel.RecordRewriterTest do
|
||||
end
|
||||
|
||||
test "inside body with variable overridden" do
|
||||
clause = clause(fn(x = Macro.Env[], y = Range[]) -> y = x end)
|
||||
assert optimize_clause(clause) == { clause, [x: Macro.Env, y: Range, "y@1": Macro.Env], { Macro.Env, nil } }
|
||||
clause = clause(fn(x = Macro.Env[], y = Rec[]) -> y = x end)
|
||||
assert optimize_clause(clause) == { clause, [x: Macro.Env, y: Rec, "y@1": Macro.Env], { Macro.Env, nil } }
|
||||
end
|
||||
|
||||
test "inside body with nested tuple" do
|
||||
clause = clause(fn(x = Range[]) -> ^x = Range[first: { :hello, 2 }] end)
|
||||
assert optimize_clause(clause) == { clause, [x: Range], { Range, [nil, { :hello, nil }, nil] } }
|
||||
clause = clause(fn(x = Rec[]) -> ^x = Rec[first: { :hello, 2 }] end)
|
||||
assert optimize_clause(clause) == { clause, [x: Rec], { Rec, [nil, { :hello, nil }, nil] } }
|
||||
end
|
||||
|
||||
test "with setelement" do
|
||||
@@ -102,7 +104,7 @@ defmodule Kernel.RecordRewriterTest do
|
||||
end
|
||||
|
||||
test "conflicting definition" do
|
||||
clause = clause(fn(x = Macro.Env[]) -> ^x = Range[]; :foo end)
|
||||
clause = clause(fn(x = Macro.Env[]) -> ^x = Rec[]; :foo end)
|
||||
assert optimize_clause(clause) == { clause, [x: nil], nil }
|
||||
end
|
||||
|
||||
@@ -132,13 +134,13 @@ defmodule Kernel.RecordRewriterTest do
|
||||
end
|
||||
|
||||
test "inside local call" do
|
||||
clause = clause(fn -> (x = Macro.Env[]).(y = Range[]) end)
|
||||
assert optimize_clause(clause) == { clause, [x: Macro.Env, y: Range], nil }
|
||||
clause = clause(fn -> (x = Macro.Env[]).(y = Rec[]) end)
|
||||
assert optimize_clause(clause) == { clause, [x: Macro.Env, y: Rec], nil }
|
||||
end
|
||||
|
||||
test "inside remote call" do
|
||||
clause = clause(fn -> x.call(y = Range[]) end)
|
||||
assert optimize_clause(clause) == { clause, [y: Range], nil }
|
||||
clause = clause(fn -> x.call(y = Rec[]) end)
|
||||
assert optimize_clause(clause) == { clause, [y: Rec], nil }
|
||||
end
|
||||
|
||||
test "inside list comprehension" do
|
||||
@@ -161,19 +163,19 @@ defmodule Kernel.RecordRewriterTest do
|
||||
clause = clause(fn -> case something do 1 -> x = Macro.Env[]; 2 -> x = Macro.Env[] end end)
|
||||
assert optimize_clause(clause) == { clause, [x: Macro.Env], { Macro.Env, nil } }
|
||||
|
||||
clause = clause(fn -> case something do 1 -> x = Macro.Env[]; 2 -> x = Range[] end end)
|
||||
clause = clause(fn -> case something do 1 -> x = Macro.Env[]; 2 -> x = Rec[] end end)
|
||||
assert optimize_clause(clause) == { clause, [x: nil], nil }
|
||||
|
||||
clause = clause(fn -> case something do x = Macro.Env[] -> x; x = Range[] -> x; _ -> :ok end end)
|
||||
clause = clause(fn -> case something do x = Macro.Env[] -> x; x = Rec[] -> x; _ -> :ok end end)
|
||||
assert optimize_clause(clause) == { clause, [], nil }
|
||||
end
|
||||
|
||||
test "inside case with nested tuple" do
|
||||
clause = clause(fn -> case something do x = Range[first: { :foo, 2 }] -> x = x; Range[] = x -> x = x end end)
|
||||
assert optimize_clause(clause) == { clause, ["x@4": Range], { Range, nil } }
|
||||
clause = clause(fn -> case something do x = Rec[first: { :foo, 2 }] -> x = x; Rec[] = x -> x = x end end)
|
||||
assert optimize_clause(clause) == { clause, ["x@4": Rec], { Rec, nil } }
|
||||
|
||||
clause = clause(fn -> case something do x = Range[first: { :foo, 2 }] -> x = x; Range[first: { :foo, 2 }] = x -> x = x end end)
|
||||
assert optimize_clause(clause) == { clause, ["x@4": Range], { Range, [nil, { :foo, nil }, nil] } }
|
||||
clause = clause(fn -> case something do x = Rec[first: { :foo, 2 }] -> x = x; Rec[first: { :foo, 2 }] = x -> x = x end end)
|
||||
assert optimize_clause(clause) == { clause, ["x@4": Rec], { Rec, [nil, { :foo, nil }, nil] } }
|
||||
end
|
||||
|
||||
test "empty receive" do
|
||||
@@ -188,7 +190,7 @@ defmodule Kernel.RecordRewriterTest do
|
||||
clause = clause(fn -> receive do 1 -> x = Macro.Env[]; 2 -> x = Macro.Env[] end end)
|
||||
assert optimize_clause(clause) == { clause, [x: Macro.Env], { Macro.Env, nil } }
|
||||
|
||||
clause = clause(fn -> receive do 1 -> x = Macro.Env[]; 2 -> x = Range[] end end)
|
||||
clause = clause(fn -> receive do 1 -> x = Macro.Env[]; 2 -> x = Rec[] end end)
|
||||
assert optimize_clause(clause) == { clause, [x: nil], nil }
|
||||
end
|
||||
|
||||
@@ -196,7 +198,7 @@ defmodule Kernel.RecordRewriterTest do
|
||||
clause = clause(fn -> receive do 1 -> x = Macro.Env[]; after 2 -> x = Macro.Env[]; end end)
|
||||
assert optimize_clause(clause) == { clause, [x: Macro.Env], { Macro.Env, nil } }
|
||||
|
||||
clause = clause(fn -> receive do 1 -> x = Macro.Env[]; after 2 -> x = Range[]; end end)
|
||||
clause = clause(fn -> receive do 1 -> x = Macro.Env[]; after 2 -> x = Rec[]; end end)
|
||||
assert optimize_clause(clause) == { clause, [x: nil], nil }
|
||||
end
|
||||
|
||||
@@ -215,54 +217,54 @@ defmodule Kernel.RecordRewriterTest do
|
||||
|
||||
test "getter call is rewriten" do
|
||||
{ clause, rewriten } =
|
||||
{ clause(fn(x = Range[]) -> x.first end), clause(fn(x = Range[]) -> :erlang.element(2, x) end) }
|
||||
{ clause(fn(x = Rec[]) -> x.first end), clause(fn(x = Rec[]) -> :erlang.element(2, x) end) }
|
||||
|
||||
assert optimize_clause(clause) == { rewriten, [x: Range], nil }
|
||||
assert optimize_clause(clause) == { rewriten, [x: Rec], nil }
|
||||
end
|
||||
|
||||
test "direct getter call is rewriten" do
|
||||
{ clause, rewriten } =
|
||||
{ clause(fn() -> Range[].first end), clause(fn() -> :erlang.element(2, Range[]) end) }
|
||||
{ clause(fn() -> Rec[].first end), clause(fn() -> :erlang.element(2, Rec[]) end) }
|
||||
|
||||
assert optimize_clause(clause) == { rewriten, [], nil }
|
||||
end
|
||||
|
||||
test "setter call is rewriten" do
|
||||
{ clause, rewriten } =
|
||||
{ clause(fn(x = Range[]) -> x.first(:first) end), clause(fn(x = Range[]) -> :erlang.setelement(2, x, :first) end) }
|
||||
{ clause(fn(x = Rec[]) -> x.first(:first) end), clause(fn(x = Rec[]) -> :erlang.setelement(2, x, :first) end) }
|
||||
|
||||
assert optimize_clause(clause) == { rewriten, [x: Range], { Range, nil } }
|
||||
assert optimize_clause(clause) == { rewriten, [x: Rec], { Rec, nil } }
|
||||
end
|
||||
|
||||
test "nested setter call is rewriten" do
|
||||
{ clause, rewriten } =
|
||||
{ clause(fn(x = Range[]) -> x.first(:first).last(:last) end), clause(fn(x = Range[]) -> :erlang.setelement(3, :erlang.setelement(2, x, :first), :last) end) }
|
||||
{ clause(fn(x = Rec[]) -> x.first(:first).last(:last) end), clause(fn(x = Rec[]) -> :erlang.setelement(3, :erlang.setelement(2, x, :first), :last) end) }
|
||||
|
||||
assert optimize_clause(clause) == { rewriten, [x: Range], { Range, nil } }
|
||||
assert optimize_clause(clause) == { rewriten, [x: Rec], { Rec, nil } }
|
||||
end
|
||||
|
||||
test "updater call is rewriten" do
|
||||
{ clause, rewriten } =
|
||||
{ clause(fn(x = Range[]) -> x.update_first(&(&1 + 1)) end), clause(fn(x = Range[]) -> Range.update_first(&(&1 + 1), x) end) }
|
||||
assert optimize_clause(clause) == { rewriten, [x: Range], { Range, nil } }
|
||||
{ clause(fn(x = Rec[]) -> x.update_first(&(&1 + 1)) end), clause(fn(x = Rec[]) -> Rec.update_first(&(&1 + 1), x) end) }
|
||||
assert optimize_clause(clause) == { rewriten, [x: Rec], { Rec, nil } }
|
||||
end
|
||||
|
||||
test "update call is rewriten" do
|
||||
{ clause, rewriten } =
|
||||
{ clause(fn(x = Range[]) -> x.update(first: 1) end), clause(fn(x = Range[]) -> Range.update([first: 1], x) end) }
|
||||
assert optimize_clause(clause) == { rewriten, [x: Range], { Range, nil } }
|
||||
{ clause(fn(x = Rec[]) -> x.update(first: 1) end), clause(fn(x = Rec[]) -> Rec.update([first: 1], x) end) }
|
||||
assert optimize_clause(clause) == { rewriten, [x: Rec], { Rec, nil } }
|
||||
end
|
||||
|
||||
test "fallback for unknown fields" do
|
||||
{ clause, rewriten } =
|
||||
{ clause(fn(x = Range[]) -> x.unknown(1, 2) end), clause(fn(x = Range[]) -> Range.unknown(1, 2, x) end) }
|
||||
assert optimize_clause(clause) == { rewriten, [x: Range], nil }
|
||||
{ clause(fn(x = Rec[]) -> x.unknown(1, 2) end), clause(fn(x = Rec[]) -> Rec.unknown(1, 2, x) end) }
|
||||
assert optimize_clause(clause) == { rewriten, [x: Rec], nil }
|
||||
end
|
||||
|
||||
test "fallback for rewriten fields" do
|
||||
{ clause, rewriten } =
|
||||
{ clause(fn(x = BadRange[]) -> x.first end), clause(fn(x = BadRange[]) -> BadRange.first(x) end) }
|
||||
assert optimize_clause(clause) == { rewriten, [x: BadRange], nil }
|
||||
{ clause(fn(x = BadRec[]) -> x.first end), clause(fn(x = BadRec[]) -> BadRec.first(x) end) }
|
||||
assert optimize_clause(clause) == { rewriten, [x: BadRec], nil }
|
||||
end
|
||||
|
||||
test "noop for not records fields" do
|
||||
@@ -271,7 +273,7 @@ defmodule Kernel.RecordRewriterTest do
|
||||
end
|
||||
|
||||
test "noop for conflicting inference" do
|
||||
clause = clause(fn(x = Macro.Env[]) -> ^x = Range[]; x.first end)
|
||||
clause = clause(fn(x = Macro.Env[]) -> ^x = Rec[]; x.first end)
|
||||
assert optimize_clause(clause) == { clause, [x: nil], nil }
|
||||
end
|
||||
end
|
||||
|
||||
@@ -169,7 +169,7 @@ defmodule Kernel.RescueTest do
|
||||
|
||||
test :badarity_error do
|
||||
fun = fn(x) -> x end
|
||||
string = "bad arity error: #{inspect(fun)} called with (1, 2)"
|
||||
string = "#{inspect(fun)} with arity 1 called with 2 arguments (1, 2)"
|
||||
|
||||
result = try do
|
||||
fun.(1, 2)
|
||||
@@ -194,25 +194,25 @@ defmodule Kernel.RescueTest do
|
||||
test :badmatch_error do
|
||||
x = :example
|
||||
result = try do
|
||||
^x = :other
|
||||
^x = zero(0)
|
||||
rescue
|
||||
x in [MatchError] -> x.message
|
||||
end
|
||||
|
||||
assert result == "no match of right hand side value: :other"
|
||||
assert result == "no match of right hand side value: 0"
|
||||
end
|
||||
|
||||
test :case_clause_error do
|
||||
x = :example
|
||||
result = try do
|
||||
case :other do
|
||||
case zero(0) do
|
||||
^x -> nil
|
||||
end
|
||||
rescue
|
||||
x in [CaseClauseError] -> x.message
|
||||
end
|
||||
|
||||
assert result == "no case clause matching: :other"
|
||||
assert result == "no case clause matching: 0"
|
||||
end
|
||||
|
||||
test :try_clause_error do
|
||||
|
||||
@@ -4,105 +4,107 @@ defmodule Kernel.SigilsTest do
|
||||
use ExUnit.Case, async: true
|
||||
|
||||
test :sigil_s do
|
||||
assert %s(foo) == "foo"
|
||||
assert %s(f#{:o}o) == "foo"
|
||||
assert %s(f\no) == "f\no"
|
||||
assert ~s(foo) == "foo"
|
||||
assert ~s(f#{:o}o) == "foo"
|
||||
assert ~s(f\no) == "f\no"
|
||||
end
|
||||
|
||||
test :sigil_s_with_heredoc do
|
||||
assert " foo\n\n" == %s"""
|
||||
assert " foo\n\n" == ~s"""
|
||||
f#{:o}o\n
|
||||
"""
|
||||
end
|
||||
|
||||
test :sigil_S do
|
||||
assert %S(foo) == "foo"
|
||||
assert %S[foo] == "foo"
|
||||
assert %S{foo} == "foo"
|
||||
assert %S'foo' == "foo"
|
||||
assert %S"foo" == "foo"
|
||||
assert %S|foo| == "foo"
|
||||
assert %S(f#{o}o) == "f\#{o}o"
|
||||
assert %S(f\no) == "f\\no"
|
||||
assert ~S(foo) == "foo"
|
||||
assert ~S[foo] == "foo"
|
||||
assert ~S{foo} == "foo"
|
||||
assert ~S'foo' == "foo"
|
||||
assert ~S"foo" == "foo"
|
||||
assert ~S<foo> == "foo"
|
||||
assert ~S/foo/ == "foo"
|
||||
assert ~S|foo| == "foo"
|
||||
assert ~S(f#{o}o) == "f\#{o}o"
|
||||
assert ~S(f\no) == "f\\no"
|
||||
end
|
||||
|
||||
test :sigil_S_with_heredoc do
|
||||
assert " f\#{o}o\\n\n" == %S"""
|
||||
assert " f\#{o}o\\n\n" == ~S"""
|
||||
f#{o}o\n
|
||||
"""
|
||||
end
|
||||
|
||||
test :sigil_c do
|
||||
assert %c(foo) == 'foo'
|
||||
assert %c(f#{:o}o) == 'foo'
|
||||
assert %c(f\no) == 'f\no'
|
||||
assert ~c(foo) == 'foo'
|
||||
assert ~c(f#{:o}o) == 'foo'
|
||||
assert ~c(f\no) == 'f\no'
|
||||
end
|
||||
|
||||
test :sigil_C do
|
||||
assert %C(foo) == 'foo'
|
||||
assert %C[foo] == 'foo'
|
||||
assert %C{foo} == 'foo'
|
||||
assert %C'foo' == 'foo'
|
||||
assert %C"foo" == 'foo'
|
||||
assert %C|foo| == 'foo'
|
||||
assert %C(f#{o}o) == 'f\#{o}o'
|
||||
assert %C(f\no) == 'f\\no'
|
||||
assert ~C(foo) == 'foo'
|
||||
assert ~C[foo] == 'foo'
|
||||
assert ~C{foo} == 'foo'
|
||||
assert ~C'foo' == 'foo'
|
||||
assert ~C"foo" == 'foo'
|
||||
assert ~C|foo| == 'foo'
|
||||
assert ~C(f#{o}o) == 'f\#{o}o'
|
||||
assert ~C(f\no) == 'f\\no'
|
||||
end
|
||||
|
||||
test :sigil_w do
|
||||
assert %w() == []
|
||||
assert %w(foo bar baz) == ["foo", "bar", "baz"]
|
||||
assert %w(foo #{:bar} baz) == ["foo", "bar", "baz"]
|
||||
assert ~w() == []
|
||||
assert ~w(foo bar baz) == ["foo", "bar", "baz"]
|
||||
assert ~w(foo #{:bar} baz) == ["foo", "bar", "baz"]
|
||||
|
||||
assert %w(
|
||||
assert ~w(
|
||||
foo
|
||||
bar
|
||||
baz
|
||||
) == ["foo", "bar", "baz"]
|
||||
|
||||
assert %w(foo bar baz)s == ["foo", "bar", "baz"]
|
||||
assert %w(foo bar baz)a == [:foo, :bar, :baz]
|
||||
assert %w(foo bar baz)c == ['foo', 'bar', 'baz']
|
||||
assert ~w(foo bar baz)s == ["foo", "bar", "baz"]
|
||||
assert ~w(foo bar baz)a == [:foo, :bar, :baz]
|
||||
assert ~w(foo bar baz)c == ['foo', 'bar', 'baz']
|
||||
|
||||
bad_modifier = quote do: %w(foo bar baz)x
|
||||
bad_modifier = quote do: ~w(foo bar baz)x
|
||||
assert ArgumentError[] = catch_error(Code.eval_quoted(bad_modifier))
|
||||
|
||||
assert %w(Foo Bar)a == [:"Foo", :"Bar"]
|
||||
assert %w(Foo.#{Bar}.Baz)a == [:"Foo.Elixir.Bar.Baz"]
|
||||
assert %w(Foo.Bar)s == ["Foo.Bar"]
|
||||
assert %w(Foo.#{Bar})c == ['Foo.Elixir.Bar']
|
||||
assert ~w(Foo Bar)a == [:"Foo", :"Bar"]
|
||||
assert ~w(Foo.#{Bar}.Baz)a == [:"Foo.Elixir.Bar.Baz"]
|
||||
assert ~w(Foo.Bar)s == ["Foo.Bar"]
|
||||
assert ~w(Foo.#{Bar})c == ['Foo.Elixir.Bar']
|
||||
|
||||
# Ensure it is fully expanded at compile time
|
||||
assert Macro.expand(quote(do: %w(a b c)a), __ENV__) == [:a, :b, :c]
|
||||
assert Macro.expand(quote(do: ~w(a b c)a), __ENV__) == [:a, :b, :c]
|
||||
end
|
||||
|
||||
test :sigil_W do
|
||||
assert %W(foo #{bar} baz) == ["foo", "\#{bar}", "baz"]
|
||||
assert ~W(foo #{bar} baz) == ["foo", "\#{bar}", "baz"]
|
||||
|
||||
assert %W(
|
||||
assert ~W(
|
||||
foo
|
||||
bar
|
||||
baz
|
||||
) == ["foo", "bar", "baz"]
|
||||
|
||||
assert %W(foo bar baz)s == ["foo", "bar", "baz"]
|
||||
assert %W(foo bar baz)a == [:foo, :bar, :baz]
|
||||
assert %W(foo bar baz)c == ['foo', 'bar', 'baz']
|
||||
assert ~W(foo bar baz)s == ["foo", "bar", "baz"]
|
||||
assert ~W(foo bar baz)a == [:foo, :bar, :baz]
|
||||
assert ~W(foo bar baz)c == ['foo', 'bar', 'baz']
|
||||
|
||||
bad_modifier = quote do: %W(foo bar baz)x
|
||||
bad_modifier = quote do: ~W(foo bar baz)x
|
||||
assert ArgumentError[] = catch_error(Code.eval_quoted(bad_modifier))
|
||||
|
||||
assert %W(Foo #{Bar})a == [:"Foo", :"\#{Bar}"]
|
||||
assert %W(Foo.Bar.Baz)a == [:"Foo.Bar.Baz"]
|
||||
assert ~W(Foo #{Bar})a == [:"Foo", :"\#{Bar}"]
|
||||
assert ~W(Foo.Bar.Baz)a == [:"Foo.Bar.Baz"]
|
||||
end
|
||||
|
||||
test :sigils_matching do
|
||||
assert %s(f(o)o) == "f(o)o"
|
||||
assert %s(f\(oo) == "f(oo"
|
||||
assert %s(fo\)o) == "fo)o"
|
||||
assert %s(f\(o\)o) == "f(o)o"
|
||||
assert ~s(f(o)o) == "f(o)o"
|
||||
assert ~s(f\(oo) == "f(oo"
|
||||
assert ~s(fo\)o) == "fo)o"
|
||||
assert ~s(f\(o\)o) == "f(o)o"
|
||||
|
||||
assert %s(f[oo) == "f[oo"
|
||||
assert %s(fo]o) == "fo]o"
|
||||
assert ~s(f[oo) == "f[oo"
|
||||
assert ~s(fo]o) == "fo]o"
|
||||
end
|
||||
end
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user