Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3c48099718 | ||
|
|
419e2a1d2a | ||
|
|
39c84f4324 | ||
|
|
8652ee5838 | ||
|
|
beb1bbeb3d | ||
|
|
cc9783a437 | ||
|
|
e566d7cda0 | ||
|
|
5c43548b0f | ||
|
|
82518c3376 | ||
|
|
8ab8663370 | ||
|
|
d44d71cce1 | ||
|
|
4d66ac72e1 | ||
|
|
fb49aa1e99 | ||
|
|
a6b9d2ed72 | ||
|
|
df1366c1d6 | ||
|
|
0bcc02d816 | ||
|
|
1c9dff1fee | ||
|
|
ed4ee3fe5c | ||
|
|
cc8f6a4ead | ||
|
|
82f800e1c5 | ||
|
|
18aeb78547 | ||
|
|
83ccde4db6 | ||
|
|
59900260a1 | ||
|
|
6bfcf2f33c | ||
|
|
45bd25057f | ||
|
|
3f6e6101d8 | ||
|
|
eb17182ed4 | ||
|
|
5b08851a8d | ||
|
|
bc0adc2f4f | ||
|
|
67f2be2eb9 | ||
|
|
fe5d8e6483 | ||
|
|
4cc28c2e8c | ||
|
|
78453597c2 | ||
|
|
927594bb14 | ||
|
|
fab45f58c6 | ||
|
|
a9375903ae | ||
|
|
44d4b5a7bf | ||
|
|
ed582c3977 | ||
|
|
9d4231c92c | ||
|
|
ecdb3e9aa9 | ||
|
|
fb2686fe37 | ||
|
|
55e885f5c5 | ||
|
|
f92d8393da | ||
|
|
cb0c3d8a2a | ||
|
|
d9ece7edb0 | ||
|
|
24a256686a | ||
|
|
ce9b381f67 | ||
|
|
a0d42b47b6 | ||
|
|
e3e5b9b85b | ||
|
|
5f4180a898 | ||
|
|
184cb36e00 | ||
|
|
48a0211156 | ||
|
|
9dc7172ec5 | ||
|
|
00db100bd4 | ||
|
|
1804db48c8 | ||
|
|
b92368104e | ||
|
|
6f0c0269d2 | ||
|
|
9b431a8831 | ||
|
|
d53509efd2 | ||
|
|
ea0226b3da | ||
|
|
4346a41811 | ||
|
|
b342739dba | ||
|
|
b570850faf | ||
|
|
5882cf5fb6 | ||
|
|
4dde1b076b | ||
|
|
142ebc3ecd | ||
|
|
339c57f1c6 | ||
|
|
76260b057f | ||
|
|
40a384aef6 | ||
|
|
e7fcaff839 | ||
|
|
a4b01b2f97 | ||
|
|
ee561bd473 | ||
|
|
6696133abb | ||
|
|
42b0c24f6a | ||
|
|
b8d02e94b2 | ||
|
|
d9a8bbb65e | ||
|
|
c0bc64fb19 | ||
|
|
0445647d63 | ||
|
|
da03542309 | ||
|
|
2e24e228d1 | ||
|
|
f83eb30975 | ||
|
|
80d81ed47a | ||
|
|
dc9803de6d | ||
|
|
4e5c851c44 | ||
|
|
b76d39e9d4 | ||
|
|
be34a75124 | ||
|
|
78c16c0e89 | ||
|
|
860ca5b5cb | ||
|
|
ce8733fc6f | ||
|
|
24109337fe | ||
|
|
90c2959ed7 | ||
|
|
70e3be3ccf | ||
|
|
eeb796107e | ||
|
|
166bbb17f9 | ||
|
|
4ed84f86cd | ||
|
|
540c763c85 | ||
|
|
3d4877ffda | ||
|
|
84262257ef | ||
|
|
68dd952b45 | ||
|
|
a068b5a4b0 | ||
|
|
c6da7b56ba | ||
|
|
dbe918c911 | ||
|
|
4977ec8c05 | ||
|
|
bf8f5c4bb8 | ||
|
|
3bb8e17e9e | ||
|
|
6b755112ce | ||
|
|
9650344c76 | ||
|
|
ca6788113e | ||
|
|
095e4a7d2e | ||
|
|
23475f9590 | ||
|
|
e69a35a85b | ||
|
|
a6392a8220 | ||
|
|
21399ebfa6 | ||
|
|
deb0191a21 | ||
|
|
489b1e04c7 | ||
|
|
a80f9d9ae1 | ||
|
|
22eb707aab | ||
|
|
c22e8e85dc | ||
|
|
98b1acfe28 | ||
|
|
cd8f52aa92 | ||
|
|
efc6963e79 | ||
|
|
dcf3692722 | ||
|
|
30294998e5 | ||
|
|
48863afca4 | ||
|
|
8622ed53e2 | ||
|
|
5e34d0a7f1 | ||
|
|
5d775f2c24 | ||
|
|
9acf4b2df9 | ||
|
|
45ee18578b | ||
|
|
fafdf40687 | ||
|
|
22206f87bf | ||
|
|
2e7d938707 | ||
|
|
57ccb29bc4 | ||
|
|
79bb046ad6 | ||
|
|
24c17c350c | ||
|
|
2187db5415 | ||
|
|
84cdbe3562 | ||
|
|
95ed007b1c | ||
|
|
55bd1d4e5a | ||
|
|
e6e1af7d84 | ||
|
|
1019917f1f | ||
|
|
9f0a30de43 | ||
|
|
ef67a9527a | ||
|
|
ff04acd3a6 | ||
|
|
a79f7c291f | ||
|
|
10ea9bdd40 | ||
|
|
ba62dc9522 | ||
|
|
d9bb48f691 | ||
|
|
580107d29c | ||
|
|
bb8f3aaa8d | ||
|
|
f4b436cbc3 |
+48
-1
@@ -1,3 +1,51 @@
|
||||
# v0.13.3 (2014-05-24)
|
||||
|
||||
* Enhancements
|
||||
* [OptionParser] Add `:strict` option that only parses known switches
|
||||
* [OptionParser] Add `next/2` useful for manual parsing of options
|
||||
* [Macro] Add `Macro.prewalk/2/3` and `Macro.postwalk/2/3`
|
||||
* [Kernel] `GenEvent`, `GenServer`, `Supervisor`, `Agent` and `Task` modules added
|
||||
* [Kernel] Make deprecations compiler warnings to avoid the same deprecation being printed multiple times
|
||||
|
||||
* Bug fixes
|
||||
* [Enum] Fix `Enum.join/2` and `Enum.map_join/3` for empty binaries at the beginning of the collection
|
||||
* [ExUnit] Ensure the formatter doesn't error when printing :EXITs
|
||||
* [Kernel] Rename `ELIXIR_ERL_OPTS` to `ELIXIR_ERL_OPTIONS` for consistency with `ERL_COMPILER_OPTIONS`
|
||||
* [OptionParser] Parse `-` as a plain argument
|
||||
* [OptionParser] `--` is always removed from argument list on `parse/2` and when it is the leading entry on `parse_head/2`
|
||||
* [Regex] Properly escape regex (previously regex controls were double escaped)
|
||||
|
||||
* Soft deprecations (no warnings emitted)
|
||||
* [Dict] `Dict.Behaviour` is deprecated in favor of `Dict`
|
||||
* [Kernel] `Application.Behaviour`, `GenEvent.Behaviour`, `GenServer.Behaviour` and `Supervisor.Behaviour` are deprecated in favor of `Application`, `GenEvent`, `GenServer` and `Supervisor`
|
||||
* [Kernel] `defexception/3` is deprecated in favor of `defexception/1`
|
||||
* [Kernel] `raise/3` is deprecated in favor of `reraise/2`
|
||||
* [Kernel] `set_elem/3` is deprecated in favor of `put_elem/3`
|
||||
|
||||
* Soft deprecations for conversions (no warnings emitted)
|
||||
* [Kernel] `atom_to_binary/1` and `atom_to_list/1` are deprecated in favor of `Atom.to_string/1` and `Atom.to_char_list/1`
|
||||
* [Kernel] `bitstring_to_list/1` and `list_to_bitstring/1` are deprecated in favor of the `:erlang` ones
|
||||
* [Kernel] `binary_to_atom/1`, `binary_to_existing_atom/1`, `binary_to_float/1`, `binary_to_integer/1` and `binary_to_integer/2` are deprecated in favor of conversion functions in `String`
|
||||
* [Kernel] `float_to_binary/*` and `float_to_list/*` are deprecated in favor of `Float.to_string/*` and `Float.to_char_list/*`
|
||||
* [Kernel] `integer_to_binary/*` and `integer_to_list/*` are deprecated in favor of `Integer.to_string/*` and `Integer.to_char_list/*`
|
||||
* [Kernel] `iodata_to_binary/1` and `iodata_length/1` are deprecated `IO.iodata_to_binary/1` and `IO.iodata_length/1`
|
||||
* [Kernel] `list_to_atom/1`, `list_to_existing_atom/1`, `list_to_float/1`, `list_to_integer/1`, `list_to_integer/2` and `list_to_tuple/1` are deprecated in favor of conversion functions in `List`
|
||||
* [Kernel] `tuple_to_list/1` is deprecated in favor of `Tuple.to_list/1`
|
||||
* [List] `List.from_char_data/1` and `List.from_char_data!/1` deprecated in favor of `String.to_char_list/1`
|
||||
* [String] `String.from_char_data/1` and `String.from_char_data!/1` deprecated in favor of `List.to_string/1`
|
||||
|
||||
* Deprecations
|
||||
* [Kernel] `is_exception/1`, `is_record/1` and `is_record/2` are deprecated in favor of `Exception.exception?1`, `Record.record?/1` and `Record.record?/2`
|
||||
* [Kernel] `defrecord/3` is deprecated in favor of structs
|
||||
* [Kernel] `:hygiene` in `quote` is deprecated
|
||||
* [Mix] `Mix.project/0` is deprecated in favor of `Mix.Project.config/0`
|
||||
* [Process] `Process.spawn/1`, `Process.spawn/3`, `Process.spawn_link/1`, `Process.spawn_link/3`, `Process.spawn_monitor/1`, `Process.spawn_monitor/3`, `Process.send/2` and `Process.self/0` are deprecated in favor of the ones in `Kernel`
|
||||
|
||||
* Backwards incompatible changes
|
||||
* [Exception] Exceptions now generate structs instead of records
|
||||
* [OptionParser] Errors on parsing returns the switch and value as binaries (unparsed)
|
||||
* [String] `String.to_char_list/1` (previously deprecated) no longer returns a tuple but the char list only and raises in case of failure
|
||||
|
||||
# v0.13.2 (2014-05-11)
|
||||
|
||||
* Enhancements
|
||||
@@ -30,7 +78,6 @@
|
||||
* [System] Convert remaining functions in System to rely on char data
|
||||
|
||||
* Soft deprecations (no warnings emitted)
|
||||
* [Application] `use Application.Behaviour` is deprecated in favor of `use Application`
|
||||
* [Exception] `exception.message` is deprecated in favor `Exception.message/1` for retrieving exception messages
|
||||
* [Kernel] `is_exception/1`, `is_record/1` and `is_record/2` are deprecated in favor of `Exception.exception?1`, `Record.record?/1` and `Record.record?/2`
|
||||
* [Mix] `Mix.project/0` is deprecated in favor of `Mix.Project.config/0`
|
||||
|
||||
@@ -67,10 +67,10 @@ erlang:
|
||||
|
||||
# Since Mix depends on EEx and EEx depends on
|
||||
# Mix, we first compile EEx without the .app
|
||||
# file, then mix and then compile eex fully
|
||||
elixir: kernel lib/eex/ebin/Elixir.EEx.beam mix ex_unit eex iex
|
||||
# file, then mix and then compile EEx fully
|
||||
elixir: stdlib lib/eex/ebin/Elixir.EEx.beam mix ex_unit eex iex
|
||||
|
||||
kernel: $(KERNEL) VERSION
|
||||
stdlib: $(KERNEL) VERSION
|
||||
$(KERNEL): lib/elixir/lib/*.ex lib/elixir/lib/*/*.ex
|
||||
$(Q) if [ ! -f $(KERNEL) ]; then \
|
||||
echo "==> bootstrap (compile)"; \
|
||||
@@ -149,20 +149,26 @@ release_docs: docs
|
||||
|
||||
test: test_erlang test_elixir
|
||||
|
||||
test_erlang: compile
|
||||
TEST_ERL = lib/elixir/test/erlang
|
||||
TEST_EBIN = lib/elixir/test/ebin
|
||||
TEST_ERLS = $(addprefix $(TEST_EBIN)/, $(addsuffix .beam, $(basename $(notdir $(wildcard $(TEST_ERL)/*.erl)))))
|
||||
|
||||
test_erlang: compile $(TEST_ERLS)
|
||||
@ echo "==> elixir (eunit)"
|
||||
$(Q) mkdir -p lib/elixir/test/ebin
|
||||
$(Q) $(ERLC) -pa lib/elixir/ebin -o lib/elixir/test/ebin lib/elixir/test/erlang/*.erl
|
||||
$(Q) $(ERL) -pa lib/elixir/test/ebin -s test_helper test -s erlang halt;
|
||||
$(Q) $(ERL) -pa $(TEST_EBIN) -s test_helper test;
|
||||
@ echo ""
|
||||
|
||||
test_elixir: test_kernel test_ex_unit test_doc_test test_mix test_eex test_iex
|
||||
$(TEST_EBIN)/%.beam: $(TEST_ERL)/%.erl
|
||||
$(Q) mkdir -p $(TEST_EBIN)
|
||||
$(Q) $(ERLC) -o $(TEST_EBIN) $<
|
||||
|
||||
test_elixir: test_stdlib test_ex_unit test_doc_test test_mix test_eex test_iex
|
||||
|
||||
test_doc_test: compile
|
||||
@ echo "==> doctest (exunit)"
|
||||
$(Q) cd lib/elixir && ../../bin/elixir -r "test/doc_test.exs";
|
||||
|
||||
test_kernel: compile
|
||||
test_stdlib: compile
|
||||
@ echo "==> elixir (exunit)"
|
||||
$(Q) cd lib/elixir && ../../bin/elixir -r "test/elixir/test_helper.exs" -pr "test/elixir/**/*_test.exs";
|
||||
|
||||
|
||||
@@ -54,4 +54,4 @@ We appreciate any contribution to Elixir, so check out our [CONTRIBUTING.md](CON
|
||||
|
||||
Elixir source code is released under Apache 2 License with some parts under Erlang's license (EPL).
|
||||
|
||||
Check LEGAL and LICENSE files for more information.
|
||||
Check [LEGAL](LEGAL) and [LICENSE](LICENSE) files for more information.
|
||||
|
||||
+2
-2
@@ -21,7 +21,7 @@ if [ $# -eq 0 ] || [ "$1" = "--help" ] || [ "$1" = "-h" ]; then
|
||||
|
||||
** Options marked with (*) can be given more than once
|
||||
** Options given after the .exs file or -- are passed down to the executed code
|
||||
** Options can be passed to the erlang runtime using ELIXIR_ERL_OPTS or --erl" >&2
|
||||
** Options can be passed to the erlang runtime using ELIXIR_ERL_OPTIONS or --erl" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
@@ -94,4 +94,4 @@ if [ -z "$ERL_PATH" ]; then
|
||||
fi
|
||||
fi
|
||||
|
||||
exec "$ERL_PATH" -pa "$SCRIPT_PATH"/../lib/*/ebin -noshell $ELIXIR_ERL_OPTS $ERL -extra "$@"
|
||||
exec "$ERL_PATH" -pa "$SCRIPT_PATH"/../lib/*/ebin -noshell $ELIXIR_ERL_OPTIONS $ERL -extra "$@"
|
||||
|
||||
+7
-11
@@ -1,12 +1,8 @@
|
||||
@echo off
|
||||
set argc=0
|
||||
for %%A in (%*) do (
|
||||
if "%%A"=="--help" goto documentation
|
||||
if "%%A"=="-h" goto documentation
|
||||
if "%%A"=="/h" goto documentation
|
||||
set /A argc+=1
|
||||
)
|
||||
if %argc%==0 goto documentation
|
||||
if "%1"=="" goto documentation
|
||||
if "%1"=="--help" goto documentation
|
||||
if "%1"=="-h" goto documentation
|
||||
if "%1"=="/h" goto documentation
|
||||
goto parseopts
|
||||
|
||||
:documentation
|
||||
@@ -31,7 +27,7 @@ echo --gen-debug Turns on default debugging for all GenServers
|
||||
echo.
|
||||
echo ** Options marked with (*) can be given more than once
|
||||
echo ** Options given after the .exs file or -- are passed down to the executed code
|
||||
echo ** Options can be passed to the erlang runtime using ELIXIR_ERL_OPTS or --erl
|
||||
echo ** Options can be passed to the erlang runtime using ELIXIR_ERL_OPTIONS or --erl
|
||||
goto :EOF
|
||||
|
||||
:parseopts
|
||||
@@ -95,7 +91,7 @@ for /d %%d in ("%originPath%..\lib\*.") do (
|
||||
SETLOCAL disabledelayedexpansion
|
||||
:run
|
||||
IF %useWerl% EQU 1 (
|
||||
werl %ext_libs% -noshell %ELIXIR_ERL_OPTS% %parsErlang% -s elixir start_cli %beforeExtra% -extra %*
|
||||
werl %ext_libs% -noshell %ELIXIR_ERL_OPTIONS% %parsErlang% -s elixir start_cli %beforeExtra% -extra %*
|
||||
) ELSE (
|
||||
erl %ext_libs% -noshell %ELIXIR_ERL_OPTS% %parsErlang% -s elixir start_cli %beforeExtra% -extra %*
|
||||
erl %ext_libs% -noshell %ELIXIR_ERL_OPTIONS% %parsErlang% -s elixir start_cli %beforeExtra% -extra %*
|
||||
)
|
||||
|
||||
+2
-1
@@ -10,7 +10,8 @@ if [ $# -eq 0 ] || [ "$1" = "--help" ] || [ "$1" = "-h" ]; then
|
||||
--verbose Print informational messages.
|
||||
|
||||
** Options given after -- are passed down to the executed code
|
||||
** Options can be passed to the erlang runtime using ELIXIR_ERL_OPTS" >&2
|
||||
** Options can be passed to the erlang runtime using ELIXIR_ERL_OPTIONS
|
||||
** Options can be passed to the erlang compiler using ERL_COMPILER_OPTS" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
|
||||
+2
-2
@@ -19,8 +19,8 @@ echo --ignore-module-conflict
|
||||
echo --warnings-as-errors Treat warnings as errors and return non-zero exit code
|
||||
echo --verbose Print informational messages.
|
||||
echo.
|
||||
echo ** Options marked with (*) can be given more than once
|
||||
echo ** Options given after -- are passed down to the executed code
|
||||
echo ** Options can be passed to the erlang runtime using ELIXIR_ERL_OPTS" >&2
|
||||
echo ** Options can be passed to the erlang runtime using ELIXIR_ERL_OPTIONS
|
||||
echo ** Options can be passed to the erlang compiler using ERL_COMPILER_OPTS >&2
|
||||
:run
|
||||
call "%~dp0\elixir.bat" +elixirc %*
|
||||
|
||||
@@ -23,7 +23,7 @@ if [ $# -gt 0 ] && ([ "$1" = "--help" ] || [ "$1" = "-h" ]); then
|
||||
|
||||
** Options marked with (*) can be given more than once
|
||||
** Options given after the .exs file or -- are passed down to the executed code
|
||||
** Options can be passed to the VM using ELIXIR_ERL_OPTS or --erl" >&2
|
||||
** Options can be passed to the VM using ELIXIR_ERL_OPTIONS or --erl" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
|
||||
+3
-1
@@ -1,4 +1,6 @@
|
||||
defexception EEx.SyntaxError, message: nil
|
||||
defmodule EEx.SyntaxError do
|
||||
defexception [:message]
|
||||
end
|
||||
|
||||
defmodule EEx do
|
||||
@moduledoc ~S"""
|
||||
|
||||
@@ -21,7 +21,7 @@ defmodule EEx.Compiler do
|
||||
# Generates the buffers by handling each expression from the tokenizer
|
||||
|
||||
defp generate_buffer([{:text, chars}|t], buffer, scope, state) do
|
||||
buffer = state.engine.handle_text(buffer, String.from_char_data!(chars))
|
||||
buffer = state.engine.handle_text(buffer, IO.chardata_to_string(chars))
|
||||
generate_buffer(t, buffer, scope, state)
|
||||
end
|
||||
|
||||
@@ -68,7 +68,7 @@ defmodule EEx.Compiler do
|
||||
defp wrap_expr(current, line, buffer, chars, state) do
|
||||
new_lines = List.duplicate(?\n, line - state.line)
|
||||
key = length(state.quoted)
|
||||
placeholder = '__EEX__(' ++ integer_to_list(key) ++ ');'
|
||||
placeholder = '__EEX__(' ++ Integer.to_char_list(key) ++ ');'
|
||||
{current ++ placeholder ++ new_lines ++ chars,
|
||||
%{state | quoted: [{key, buffer}|state.quoted]}}
|
||||
end
|
||||
|
||||
@@ -13,7 +13,7 @@ defmodule EEx.Tokenizer do
|
||||
|
||||
"""
|
||||
def tokenize(bin, line) when is_binary(bin) do
|
||||
tokenize(List.from_char_data!(bin), line)
|
||||
tokenize(String.to_char_list(bin), line)
|
||||
end
|
||||
|
||||
def tokenize(list, line) do
|
||||
|
||||
@@ -7,7 +7,7 @@ defmodule EExText.Compiled do
|
||||
fill_in_stacktrace
|
||||
{__ENV__.line, hd(tl(System.stacktrace))}
|
||||
end
|
||||
{:erlang, 1, 2}.tuple_to_list
|
||||
|
||||
EEx.function_from_string :def, :string_sample, "<%= a + b %>", [:a, :b]
|
||||
|
||||
filename = Path.join(__DIR__, "fixtures/eex_template_with_bindings.eex")
|
||||
|
||||
@@ -42,7 +42,7 @@ defimpl Access, for: List do
|
||||
end
|
||||
|
||||
def access(_dict, key) do
|
||||
raise ArgumentError, message: "the access protocol for lists expect the key to be an atom, got: #{inspect key}"
|
||||
raise ArgumentError, "the access protocol for lists expect the key to be an atom, got: #{inspect key}"
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -0,0 +1,226 @@
|
||||
defmodule Agent do
|
||||
@moduledoc """
|
||||
Agents are a simple abstraction around state.
|
||||
|
||||
Often in Elixir there is a need to share or store state that
|
||||
must be accessed from different processes or by a same process
|
||||
in different points in time.
|
||||
|
||||
The Agent module provides a basic server implementation that
|
||||
allows state to be retrieved and updated via a simple API.
|
||||
|
||||
## Examples
|
||||
|
||||
For example, in the Mix tool that ships with Elixir, we need
|
||||
to keep a set of all tasks executed by a given project. Since
|
||||
this set is shared, we can implement it with an Agent:
|
||||
|
||||
defmodule Mix.TasksServer do
|
||||
def start_link do
|
||||
Agent.start_link(fn -> HashSet.new end, name: __MODULE__)
|
||||
end
|
||||
|
||||
@doc "Checks if the task has already executed"
|
||||
def executed?(task, project) do
|
||||
item = {task, project}
|
||||
Agent.get(__MODULE__, fn set ->
|
||||
item in set
|
||||
end)
|
||||
end
|
||||
|
||||
@doc "Marks a task as executed"
|
||||
def put_task(task, project) do
|
||||
item = {task, project}
|
||||
Agent.update(__MODULE__, &Set.put(&1, item))
|
||||
end
|
||||
end
|
||||
|
||||
Note that agents still provide a segregation in between the
|
||||
client and server APIs, as seen in GenServers. In particular,
|
||||
all code inside the function passed to the agent is executed
|
||||
by the agent. This distinction is important because you may
|
||||
want to avoid expensive operations inside the agent, as it will
|
||||
effectively block the agent until the request is fullfilled.
|
||||
|
||||
Consider these two examples:
|
||||
|
||||
# Compute in the agent/server
|
||||
def get_something(agent) do
|
||||
Agent.get(agent, fn state -> do_something_expensive(state) end)
|
||||
end
|
||||
|
||||
# Compute in the agent/client
|
||||
def get_something(agent) do
|
||||
Agent.get(agent, &(&1)) |> do_something_expensive()
|
||||
end
|
||||
|
||||
The first one blocks the agent while the second one copies
|
||||
all the state to the client and executes the operation in the client.
|
||||
The trade-off here is exactly if the data is small enough to be
|
||||
sent to the client cheaply or large enough to require processing on
|
||||
the server (or at least some initial processing).
|
||||
|
||||
## Name registration
|
||||
|
||||
An Agent is bound to the same name registration rules as GenServers.
|
||||
Read more about it in the `GenServer` docs.
|
||||
|
||||
## A word on distributed agents
|
||||
|
||||
It is important to consider the limitations of distributed agents. Agents
|
||||
work by sending anonymous functions in between the caller and the agent.
|
||||
In a distributed setup with multiple nodes, agents only work if the caller
|
||||
(client) and the agent have the same version of a given module.
|
||||
|
||||
This setup may exhibit issues when doing "rolling upgrades". By rolling
|
||||
upgrades we mean the following situation: you wish to deploy a new version of
|
||||
your software by *shutting down* some of your nodes and replacing them by
|
||||
nodes running a new version of the software. In this setup, part of your
|
||||
environment will have one version of a given module and the other part
|
||||
another version (the newer one) of the same module; this may cause agents to
|
||||
crash. That said, if you plan to run in distributed environments, agents
|
||||
should likely be avoided.
|
||||
|
||||
Note, however, that agents work fine if you want to perform hot code
|
||||
swapping, as it keeps both the old and new versions of a given module.
|
||||
We detail how to do hot code swapping with agents in the next section.
|
||||
|
||||
## Hot code swapping
|
||||
|
||||
An agent can have its code hot swapped live by simply passing a module,
|
||||
function and args tuple to the update instruction. For example, imagine
|
||||
you have an agent named `:sample` and you want to convert its inner state
|
||||
from some dict structure to a map. It can be done with the following
|
||||
instruction:
|
||||
|
||||
{:update, :sample, {:advanced, {Enum, :into, [%{}]}}}
|
||||
|
||||
The agent's state will be added to the given list as the first argument.
|
||||
"""
|
||||
|
||||
@typedoc "Return values of `start*` functions"
|
||||
@type on_start :: {:ok, pid} | {:error, {:already_started, pid} | term}
|
||||
|
||||
@typedoc "The agent name"
|
||||
@type name :: atom | {:global, term} | {:via, module, term}
|
||||
|
||||
@typedoc "The agent reference"
|
||||
@type agent :: pid | {atom, node} | name
|
||||
|
||||
@typedoc "The agent state"
|
||||
@type state :: term
|
||||
|
||||
@doc """
|
||||
Starts an agent linked to the current process.
|
||||
|
||||
This is often used to start the agent as part of a supervision tree.
|
||||
|
||||
Once the agent is spawned, the given function is invoked and its return
|
||||
value is used as the agent state. Note that `start_link` does not return
|
||||
until the given function has returned.
|
||||
|
||||
## Options
|
||||
|
||||
The `:name` option is used for registration as described in the module
|
||||
documentation. If the `:timeout` option is present, the agent is allowed to
|
||||
spend at most the given amount of milliseconds on initialization or it will
|
||||
be terminated and the start function will return `{:error, :timeout}`.
|
||||
|
||||
If the `:debug` option is present, the corresponding function in the
|
||||
[`:sys` module](http://www.erlang.org/doc/man/sys.html) will be invoked.
|
||||
|
||||
If the `:spawn_opt` option is present, its value will be passed as options
|
||||
to the underlying process as in `Process.spawn/3`.
|
||||
|
||||
## Return values
|
||||
|
||||
If the server is successfully created and initialized, the function returns
|
||||
`{:ok, pid}`, where pid is the pid of the server. If there already exists
|
||||
an agent with the specified name, the function returns
|
||||
`{:error, {:already_started, pid}}` with the pid of that process.
|
||||
|
||||
If the given function callback fails with `reason`, the function returns
|
||||
`{:error, reason}`.
|
||||
"""
|
||||
@spec start_link((() -> term), GenServer.options) :: on_start
|
||||
def start_link(fun, options \\ []) when is_function(fun, 0) do
|
||||
GenServer.start_link(Agent.Server, fun, options)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Starts an agent process without links (outside of a supervision tree).
|
||||
|
||||
See `start_link/2` for more information.
|
||||
"""
|
||||
@spec start((() -> term), GenServer.options) :: on_start
|
||||
def start(fun, options \\ []) when is_function(fun, 0) do
|
||||
GenServer.start(Agent.Server, fun, options)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Gets the agent value and executes the given function.
|
||||
|
||||
The function `fun` is sent to the `agent` which invokes the function
|
||||
passing the agent state. The result of the function invocation is
|
||||
returned.
|
||||
|
||||
A timeout can also be specified (it has a default value of 5000).
|
||||
"""
|
||||
@spec get(agent, (state -> a), timeout) :: a when a: var
|
||||
def get(agent, fun, timeout \\ 5000) when is_function(fun, 1) do
|
||||
GenServer.call(agent, {:get, fun}, timeout)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Gets and updates the agent state in one operation.
|
||||
|
||||
The function `fun` is sent to the `agent` which invokes the function
|
||||
passing the agent state. The function must return a tuple with two
|
||||
elements, the first being the value to return (i.e. the get value)
|
||||
and the second one is the new state.
|
||||
|
||||
A timeout can also be specified (it has a default value of 5000).
|
||||
"""
|
||||
@spec get_and_update(agent, (state -> {a, state}), timeout) :: a when a: var
|
||||
def get_and_update(agent, fun, timeout \\ 5000) when is_function(fun, 1) do
|
||||
GenServer.call(agent, {:get_and_update, fun}, timeout)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Updates the agent state.
|
||||
|
||||
The function `fun` is sent to the `agent` which invokes the function
|
||||
passing the agent state. The function must return the new state.
|
||||
|
||||
A timeout can also be specified (it has a default value of 5000).
|
||||
This function always returns `:ok`.
|
||||
"""
|
||||
@spec update(agent, (state -> state)) :: :ok
|
||||
def update(agent, fun, timeout \\ 5000) when is_function(fun, 1) do
|
||||
GenServer.call(agent, {:update, fun}, timeout)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Performs a cast (fire and forget) operation on the agent state.
|
||||
|
||||
The function `fun` is sent to the `agent` which invokes the function
|
||||
passing the agent state. The function must return the new state.
|
||||
|
||||
Note that `cast` returns `:ok` immediately, regardless of whether the
|
||||
destination node or agent exists.
|
||||
"""
|
||||
@spec cast(agent, (state -> state)) :: :ok
|
||||
def cast(agent, fun) when is_function(fun, 1) do
|
||||
GenServer.cast(agent, fun)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Stops the agent.
|
||||
|
||||
Returns `:ok` if the agent is stopped within the given `timeout`.
|
||||
"""
|
||||
@spec stop(agent, timeout) :: :ok
|
||||
def stop(agent, timeout \\ 5000) do
|
||||
GenServer.call(agent, :stop, timeout)
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,53 @@
|
||||
defmodule Agent.Server do
|
||||
@moduledoc false
|
||||
|
||||
use GenServer
|
||||
|
||||
def init(fun) do
|
||||
{:ok, fun.()}
|
||||
end
|
||||
|
||||
def handle_call({:get, fun}, _from, state) do
|
||||
{:reply, fun.(state), state}
|
||||
end
|
||||
|
||||
def handle_call({:get_and_update, fun}, _from, state) do
|
||||
{reply, state} = fun.(state)
|
||||
{:reply, reply, state}
|
||||
end
|
||||
|
||||
def handle_call({:update, fun}, _from, state) do
|
||||
{:reply, :ok, fun.(state)}
|
||||
end
|
||||
|
||||
def handle_call(:stop, _from, state) do
|
||||
{:stop, :normal, :ok, state}
|
||||
end
|
||||
|
||||
def handle_call(msg, from, state) do
|
||||
super(msg, from, state)
|
||||
end
|
||||
|
||||
def handle_cast(fun, state) when is_function(fun, 1) do
|
||||
{:noreply, fun.(state)}
|
||||
end
|
||||
|
||||
def handle_cast(msg, state) do
|
||||
super(msg, state)
|
||||
end
|
||||
|
||||
def code_change(_old, state, { m, f, a }) do
|
||||
{:ok, apply(m, f, [state|a])}
|
||||
end
|
||||
|
||||
def terminate(_reason, _state) do
|
||||
# There is a race condition if the agent is
|
||||
# restarted too fast and it is registered.
|
||||
try do
|
||||
self |> Process.info(:registered_name) |> elem(1) |> Process.unregister
|
||||
rescue
|
||||
_ -> :ok
|
||||
end
|
||||
:ok
|
||||
end
|
||||
end
|
||||
@@ -246,7 +246,7 @@ defmodule Application do
|
||||
@doc """
|
||||
Stops the given `app`.
|
||||
|
||||
When stopped, the application is still loaded.
|
||||
When stopped, the application is still loaded.
|
||||
"""
|
||||
@spec stop(app) :: :ok | {:error, term}
|
||||
def stop(app) do
|
||||
@@ -305,8 +305,8 @@ defmodule Application do
|
||||
@spec app_dir(app) :: String.t
|
||||
def app_dir(app) when is_atom(app) do
|
||||
case :code.lib_dir(app) do
|
||||
lib when is_list(lib) -> String.from_char_data!(lib)
|
||||
{:error, :bad_name} -> raise ArgumentError, message: "unknown application: #{inspect app}"
|
||||
lib when is_list(lib) -> IO.chardata_to_string(lib)
|
||||
{:error, :bad_name} -> raise ArgumentError, "unknown application: #{inspect app}"
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -2,7 +2,6 @@ defmodule Application.Behaviour do
|
||||
@moduledoc false
|
||||
|
||||
defmacro __using__(_) do
|
||||
# IO.write :stderr, "use Application.Behaviour is deprecated, please use Application instead\n#{Exception.format_stacktrace}"
|
||||
quote location: :keep do
|
||||
@behaviour :application
|
||||
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
defmodule Atom do
|
||||
@doc """
|
||||
Convenience functions for working with atoms.
|
||||
"""
|
||||
|
||||
@doc """
|
||||
Converts an atom to string.
|
||||
|
||||
Inlined by the compiler.
|
||||
"""
|
||||
@spec to_string(atom) :: String.t
|
||||
def to_string(atom) do
|
||||
:erlang.atom_to_binary(atom, :utf8)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Converts an atom to a char list.
|
||||
|
||||
Inlined by the compiler.
|
||||
"""
|
||||
@spec to_char_list(atom) :: char_list
|
||||
def to_char_list(atom) do
|
||||
:erlang.atom_to_list(atom)
|
||||
end
|
||||
end
|
||||
@@ -25,7 +25,7 @@ defmodule Base do
|
||||
defp unquote(dec)(unquote(encoding)), do: unquote(value)
|
||||
end
|
||||
defp unquote(dec)(c) do
|
||||
raise ArgumentError, message: "non-alphabet digit found: #{<<c>>}"
|
||||
raise ArgumentError, "non-alphabet digit found: #{<<c>>}"
|
||||
end
|
||||
end
|
||||
|
||||
@@ -365,7 +365,7 @@ defmodule Base do
|
||||
end
|
||||
end
|
||||
defp do_decode16(_, _) do
|
||||
raise ArgumentError, message: "odd-length string"
|
||||
raise ArgumentError, "odd-length string"
|
||||
end
|
||||
|
||||
defp do_encode64(<<>>, _), do: <<>>
|
||||
@@ -400,7 +400,7 @@ defmodule Base do
|
||||
end
|
||||
end
|
||||
defp do_decode64(_, _) do
|
||||
raise ArgumentError, message: "incorrect padding"
|
||||
raise ArgumentError, "incorrect padding"
|
||||
end
|
||||
|
||||
defp do_encode32(<<>>, _), do: <<>>
|
||||
@@ -458,7 +458,7 @@ defmodule Base do
|
||||
end
|
||||
end
|
||||
defp do_decode32(_, _) do
|
||||
raise ArgumentError, message: "incorrect padding"
|
||||
raise ArgumentError, "incorrect padding"
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
@@ -78,7 +78,7 @@ defmodule Behaviour do
|
||||
{name, args} ->
|
||||
do_callback(:def, name, args, name, length(args), args, return, guards, caller)
|
||||
_ ->
|
||||
raise ArgumentError, message: "invalid syntax in defcallback #{Macro.to_string(spec)}"
|
||||
raise ArgumentError, "invalid syntax in defcallback #{Macro.to_string(spec)}"
|
||||
end
|
||||
end
|
||||
|
||||
@@ -88,7 +88,7 @@ defmodule Behaviour do
|
||||
do_callback(:defmacro, :"MACRO-#{name}", [quote(do: env :: Macro.Env.t)|args],
|
||||
name, length(args), args, return, guards, caller)
|
||||
_ ->
|
||||
raise ArgumentError, message: "invalid syntax in defmacrocallback #{Macro.to_string(spec)}"
|
||||
raise ArgumentError, "invalid syntax in defmacrocallback #{Macro.to_string(spec)}"
|
||||
end
|
||||
end
|
||||
|
||||
@@ -111,7 +111,7 @@ defmodule Behaviour do
|
||||
end
|
||||
|
||||
defp ensure_not_default({:\\, _, [_, _]}) do
|
||||
raise ArgumentError, message: "default arguments \\\\ not supported in defcallback/defmacrocallback"
|
||||
raise ArgumentError, "default arguments \\\\ not supported in defcallback/defmacrocallback"
|
||||
end
|
||||
|
||||
defp ensure_not_default(_), do: :ok
|
||||
|
||||
+6
-13
@@ -1,11 +1,4 @@
|
||||
defmodule Code do
|
||||
defexception LoadError, [:file, :message] do
|
||||
def exception(opts) do
|
||||
file = opts[:file]
|
||||
LoadError[message: "could not load #{file}", file: file]
|
||||
end
|
||||
end
|
||||
|
||||
@moduledoc """
|
||||
Utilities for managing code compilation, code evaluation and code loading.
|
||||
|
||||
@@ -135,14 +128,14 @@ defmodule Code do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> contents = quote(hygiene: [vars: false], do: a + b)
|
||||
iex> contents = quote(do: var!(a) + var!(b))
|
||||
iex> Code.eval_quoted(contents, [a: 1, b: 2], file: __ENV__.file, line: __ENV__.line)
|
||||
{3, [a: 1, b: 2]}
|
||||
|
||||
For convenience, you can pass `__ENV__` as the `opts` argument and
|
||||
all options will be automatically extracted from the current environment:
|
||||
|
||||
iex> contents = quote(hygiene: [vars: false], do: a + b)
|
||||
iex> contents = quote(do: var!(a) + var!(b))
|
||||
iex> Code.eval_quoted(contents, [a: 1, b: 2], __ENV__)
|
||||
{3, [a: 1, b: 2]}
|
||||
|
||||
@@ -171,7 +164,7 @@ defmodule Code do
|
||||
valid = is_list(requires) and Enum.all?(requires, &is_atom(&1))
|
||||
|
||||
unless valid do
|
||||
raise ArgumentError, message: "expected :#{kind} option given to eval in the format: [module]"
|
||||
raise ArgumentError, "expected :#{kind} option given to eval in the format: [module]"
|
||||
end
|
||||
end
|
||||
|
||||
@@ -181,7 +174,7 @@ defmodule Code do
|
||||
end)
|
||||
|
||||
unless valid do
|
||||
raise ArgumentError, message: "expected :#{kind} option given to eval in the format: [{module, module}]"
|
||||
raise ArgumentError, "expected :#{kind} option given to eval in the format: [{module, module}]"
|
||||
end
|
||||
end
|
||||
|
||||
@@ -193,7 +186,7 @@ defmodule Code do
|
||||
end)
|
||||
|
||||
unless valid do
|
||||
raise ArgumentError, message: "expected :#{kind} option given to eval in the format: [{module, [{name, arity}]}]"
|
||||
raise ArgumentError, "expected :#{kind} option given to eval in the format: [{module, [{name, arity}]}]"
|
||||
end
|
||||
end
|
||||
|
||||
@@ -470,7 +463,7 @@ defmodule Code do
|
||||
if File.regular?(file) do
|
||||
file
|
||||
else
|
||||
raise LoadError, file: file
|
||||
raise Code.LoadError, file: file
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
+205
-4
@@ -31,6 +31,70 @@ defmodule Dict do
|
||||
|
||||
Dictionaries are required to implement all operations
|
||||
using the match (`===`) operator.
|
||||
|
||||
## Default implementation
|
||||
|
||||
Default implementations for some functions in the `Dict` module
|
||||
are provided via `use Dict`.
|
||||
|
||||
For example:
|
||||
|
||||
defmodule MyDict do
|
||||
use Dict.Behaviour
|
||||
|
||||
# implement required functions (see below)
|
||||
# override default implementations if optimization
|
||||
# is needed
|
||||
end
|
||||
|
||||
The client module must contain the following functions:
|
||||
|
||||
* `delete/2`
|
||||
* `fetch/2`
|
||||
* `put/3`
|
||||
* `reduce/3`
|
||||
* `size/1`
|
||||
|
||||
All functions, except `reduce/3`, are required by the Dict behaviour.
|
||||
`reduce/3` must be implemtented as per the Enumerable protocol.
|
||||
|
||||
Based on these functions, `Dict` generates default implementations
|
||||
for the following functions:
|
||||
|
||||
* `drop/2`
|
||||
* `equal?/2`
|
||||
* `fetch!/2`
|
||||
* `get/2`
|
||||
* `get/3`
|
||||
* `has_key?/2`
|
||||
* `keys/1`
|
||||
* `merge/2`
|
||||
* `merge/3`
|
||||
* `pop/2`
|
||||
* `pop/3`
|
||||
* `put_new/3`
|
||||
* `split/2`
|
||||
* `take/2`
|
||||
* `to_list/1`
|
||||
* `update/4`
|
||||
* `update!/3`
|
||||
* `values/1`
|
||||
|
||||
All of these functions are defined as overridable, so you can provide
|
||||
your own implementation if needed.
|
||||
|
||||
Note you can also test your custom module via `Dict`'s doctests:
|
||||
|
||||
defmodule MyDict do
|
||||
# ...
|
||||
end
|
||||
|
||||
defmodule MyTests do
|
||||
use ExUnit.Case
|
||||
doctest Dict
|
||||
defp dict_impl, do: MyDict
|
||||
end
|
||||
|
||||
"""
|
||||
|
||||
use Behaviour
|
||||
@@ -63,6 +127,146 @@ defmodule Dict do
|
||||
defcallback update!(t, key, (value -> value)) :: t | no_return
|
||||
defcallback values(t) :: list(value)
|
||||
|
||||
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
|
||||
case fetch(dict, key) do
|
||||
{:ok, value} -> value
|
||||
:error -> default
|
||||
end
|
||||
end
|
||||
|
||||
def fetch!(dict, key) do
|
||||
case fetch(dict, key) do
|
||||
{:ok, value} -> value
|
||||
:error -> raise KeyError, key: key, term: dict
|
||||
end
|
||||
end
|
||||
|
||||
def has_key?(dict, key) do
|
||||
match? {:ok, _}, fetch(dict, key)
|
||||
end
|
||||
|
||||
def put_new(dict, key, value) do
|
||||
case has_key?(dict, key) do
|
||||
true -> dict
|
||||
false -> put(dict, key, value)
|
||||
end
|
||||
end
|
||||
|
||||
def drop(dict, keys) do
|
||||
Enum.reduce(keys, dict, &delete(&2, &1))
|
||||
end
|
||||
|
||||
def take(dict, keys) do
|
||||
Enum.reduce(keys, new, fn key, acc ->
|
||||
case fetch(dict, key) do
|
||||
{:ok, value} -> put(acc, key, value)
|
||||
:error -> acc
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
def to_list(dict) do
|
||||
reduce(dict, {:cont, []}, fn
|
||||
kv, acc -> {:cont, [kv|acc]}
|
||||
end) |> elem(1) |> :lists.reverse
|
||||
end
|
||||
|
||||
def keys(dict) do
|
||||
reduce(dict, {:cont, []}, fn
|
||||
{k, _}, acc -> {:cont, [k|acc]}
|
||||
end) |> elem(1) |> :lists.reverse
|
||||
end
|
||||
|
||||
def values(dict) do
|
||||
reduce(dict, {:cont, []}, fn
|
||||
{_, v}, acc -> {:cont, [v|acc]}
|
||||
end) |> elem(1) |> :lists.reverse
|
||||
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) ->
|
||||
case fetch(dict2, k) do
|
||||
{:ok, ^v} -> {:cont, true}
|
||||
_ -> {:halt, false}
|
||||
end
|
||||
end) |> elem(1)
|
||||
end
|
||||
end
|
||||
|
||||
def merge(dict1, dict2, fun \\ fn(_k, _v1, v2) -> v2 end) do
|
||||
# Use this import to avoid conflicts in the user code
|
||||
import Kernel, except: [size: 1]
|
||||
|
||||
if size(dict1) < size(dict2) do
|
||||
reduce(dict1, {:cont, dict2}, fn {k, v1}, acc ->
|
||||
{:cont, update(acc, k, v1, &fun.(k, v1, &1))}
|
||||
end)
|
||||
else
|
||||
reduce(dict2, {:cont, dict1}, fn {k, v2}, acc ->
|
||||
{:cont, update(acc, k, v2, &fun.(k, &1, v2))}
|
||||
end)
|
||||
end |> elem(1)
|
||||
end
|
||||
|
||||
def update(dict, key, initial, fun) do
|
||||
case fetch(dict, key) do
|
||||
{:ok, value} ->
|
||||
put(dict, key, fun.(value))
|
||||
:error ->
|
||||
put(dict, key, initial)
|
||||
end
|
||||
end
|
||||
|
||||
def update!(dict, key, fun) do
|
||||
case fetch(dict, key) do
|
||||
{:ok, value} ->
|
||||
put(dict, key, fun.(value))
|
||||
:error ->
|
||||
raise KeyError, key: key, term: dict
|
||||
end
|
||||
end
|
||||
|
||||
def pop(dict, key, default \\ nil) do
|
||||
case fetch(dict, key) do
|
||||
{:ok, value} ->
|
||||
{value, delete(dict, key)}
|
||||
:error ->
|
||||
{default, dict}
|
||||
end
|
||||
end
|
||||
|
||||
def split(dict, keys) do
|
||||
Enum.reduce(keys, {new, dict}, fn key, {inc, exc} = acc ->
|
||||
case fetch(exc, key) do
|
||||
{:ok, value} ->
|
||||
{put(inc, key, value), delete(exc, key)}
|
||||
:error ->
|
||||
acc
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
defoverridable merge: 2, merge: 3, equal?: 2, to_list: 1, keys: 1,
|
||||
values: 1, take: 2, drop: 2, get: 2, get: 3, fetch!: 2,
|
||||
has_key?: 2, put_new: 3, pop: 2, pop: 3, split: 2,
|
||||
update: 4, update!: 3
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
defmacrop target(dict) do
|
||||
quote do
|
||||
case unquote(dict) do
|
||||
@@ -70,9 +274,6 @@ defmodule Dict do
|
||||
x
|
||||
%{} ->
|
||||
Map
|
||||
x when is_tuple(x) ->
|
||||
IO.write :stderr, "Using records with the Dict module is deprecated, please use structs instead\n#{Exception.format_stacktrace}"
|
||||
elem(x, 0)
|
||||
x when is_list(x) ->
|
||||
Keyword
|
||||
x ->
|
||||
@@ -487,6 +688,6 @@ defmodule Dict do
|
||||
end
|
||||
|
||||
defp unsupported_dict(dict) do
|
||||
raise ArgumentError, message: "unsupported dict: #{inspect dict}"
|
||||
raise ArgumentError, "unsupported dict: #{inspect dict}"
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,199 +1,9 @@
|
||||
defmodule Dict.Behaviour do
|
||||
@moduledoc ~S"""
|
||||
Default implementations for some required functions in the `Dict` module.
|
||||
@moduledoc false
|
||||
|
||||
This module makes it easier to create your own `Dict` compliant
|
||||
module, by providing default implementations for some required functions.
|
||||
|
||||
## Example
|
||||
|
||||
defmodule MyDict do
|
||||
use Dict.Behaviour
|
||||
|
||||
# implement required functions (see below)
|
||||
|
||||
# override default implementations if optimization is needed
|
||||
end
|
||||
|
||||
The client module must contain the following functions:
|
||||
|
||||
* `delete/2`
|
||||
* `fetch/2`
|
||||
* `put/3`
|
||||
* `reduce/3`
|
||||
* `size/1`
|
||||
|
||||
All functions, except `reduce/3`, are required by the Dict behaviour.
|
||||
`reduce/3` must be implemtented as per the Enumerable protocol.
|
||||
|
||||
Based on these functions, `Dict.Behaviour` generates default implementations
|
||||
for the following functions:
|
||||
|
||||
* `drop/2`
|
||||
* `equal?/2`
|
||||
* `fetch!/2`
|
||||
* `get/2`
|
||||
* `get/3`
|
||||
* `has_key?/2`
|
||||
* `keys/1`
|
||||
* `merge/2`
|
||||
* `merge/3`
|
||||
* `pop/2`
|
||||
* `pop/3`
|
||||
* `put_new/3`
|
||||
* `split/2`
|
||||
* `take/2`
|
||||
* `to_list/1`
|
||||
* `values/1`
|
||||
* `update/4`
|
||||
* `update!/3`
|
||||
|
||||
All of these functions are defined as overridable, so you can provide your own
|
||||
implementation if needed.
|
||||
|
||||
Note you can also test your custom module via `Dict`'s doctests:
|
||||
|
||||
defmodule MyDict do
|
||||
def new(keywords \\ []) do
|
||||
...
|
||||
end
|
||||
end
|
||||
|
||||
defmodule MyTests do
|
||||
use ExUnit.Case
|
||||
doctest Dict
|
||||
defp dict_impl, do: MyDict
|
||||
end
|
||||
|
||||
"""
|
||||
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
|
||||
case fetch(dict, key) do
|
||||
{:ok, value} -> value
|
||||
:error -> default
|
||||
end
|
||||
end
|
||||
|
||||
def fetch!(dict, key) do
|
||||
case fetch(dict, key) do
|
||||
{:ok, value} -> value
|
||||
:error -> raise KeyError, key: key, term: dict
|
||||
end
|
||||
end
|
||||
|
||||
def has_key?(dict, key) do
|
||||
match? {:ok, _}, fetch(dict, key)
|
||||
end
|
||||
|
||||
def put_new(dict, key, value) do
|
||||
update(dict, key, value, fn(v) -> v end)
|
||||
end
|
||||
|
||||
def drop(dict, keys) do
|
||||
Enum.reduce keys, dict, &delete(&2, &1)
|
||||
end
|
||||
|
||||
def take(dict, keys) do
|
||||
Enum.reduce keys, new, fn key, acc ->
|
||||
case fetch(dict, key) do
|
||||
{:ok, value} -> put(acc, key, value)
|
||||
:error -> acc
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def to_list(dict) do
|
||||
reduce(dict, {:cont, []}, fn
|
||||
kv, acc -> {:cont, [kv|acc]}
|
||||
end) |> elem(1) |> :lists.reverse
|
||||
end
|
||||
|
||||
def keys(dict) do
|
||||
reduce(dict, {:cont, []}, fn
|
||||
{k, _}, acc -> {:cont, [k|acc]}
|
||||
end) |> elem(1) |> :lists.reverse
|
||||
end
|
||||
|
||||
def values(dict) do
|
||||
reduce(dict, {:cont, []}, fn
|
||||
{_, v}, acc -> {:cont, [v|acc]}
|
||||
end) |> elem(1) |> :lists.reverse
|
||||
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) ->
|
||||
case fetch(dict2, k) do
|
||||
{:ok, ^v} -> {:cont, true}
|
||||
_ -> {:halt, false}
|
||||
end
|
||||
end) |> elem(1)
|
||||
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
|
||||
|
||||
def update(dict, key, initial, fun) do
|
||||
case fetch(dict, key) do
|
||||
{ :ok, value } ->
|
||||
put(dict, key, fun.(value))
|
||||
|
||||
:error ->
|
||||
put(dict, key, initial)
|
||||
end
|
||||
end
|
||||
|
||||
def update!(dict, key, fun) do
|
||||
case fetch(dict, key) do
|
||||
{ :ok, value } ->
|
||||
put(dict, key, fun.(value))
|
||||
|
||||
:error ->
|
||||
raise KeyError, key: key, term: dict
|
||||
end
|
||||
end
|
||||
|
||||
def pop(dict, key, default \\ nil) do
|
||||
case fetch(dict, key) do
|
||||
{ :ok, value } ->
|
||||
{ value, delete(dict, key) }
|
||||
|
||||
:error ->
|
||||
{ default, dict }
|
||||
end
|
||||
end
|
||||
|
||||
def split(dict, keys) do
|
||||
Enum.reduce keys, { new, dict }, fn key, { inc, exc } = acc ->
|
||||
case fetch(exc, key) do
|
||||
{ :ok, value } ->
|
||||
{ put(inc, key, value), delete(exc, key) }
|
||||
|
||||
:error ->
|
||||
acc
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defoverridable merge: 2, merge: 3, equal?: 2, to_list: 1, keys: 1,
|
||||
values: 1, take: 2, drop: 2, get: 2, get: 3, fetch!: 2,
|
||||
has_key?: 2, put_new: 3, pop: 2, pop: 3, update: 4, update!: 3,
|
||||
split: 2
|
||||
use Dict
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
+16
-5
@@ -936,10 +936,15 @@ defmodule Enum do
|
||||
def join(collection, joiner \\ "")
|
||||
|
||||
def join(collection, joiner) when is_binary(joiner) do
|
||||
reduce(collection, "", fn
|
||||
entry, "" -> to_string(entry)
|
||||
reduced = reduce(collection, :first, fn
|
||||
entry, :first -> to_string(entry)
|
||||
entry, acc -> acc <> joiner <> to_string(entry)
|
||||
end)
|
||||
if reduced == :first do
|
||||
""
|
||||
else
|
||||
reduced
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -990,10 +995,16 @@ defmodule Enum do
|
||||
def map_join(collection, joiner \\ "", mapper)
|
||||
|
||||
def map_join(collection, joiner, mapper) when is_binary(joiner) do
|
||||
reduce(collection, "", fn
|
||||
entry, "" -> to_string(mapper, entry)
|
||||
entry, acc -> acc <> joiner <> to_string(mapper, entry)
|
||||
reduced = reduce(collection, :first, fn
|
||||
entry, :first -> to_string(mapper, entry)
|
||||
entry, acc -> acc <> joiner <> to_string(mapper, entry)
|
||||
end)
|
||||
|
||||
if reduced == :first do
|
||||
""
|
||||
else
|
||||
reduced
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
|
||||
+356
-244
@@ -1,180 +1,26 @@
|
||||
# Some exceptions implement `message/1` instead of `exception/1` mostly
|
||||
# for bootstrap reasons. It is recommended for applications to implement
|
||||
# `exception/1` instead of `message/1` as described in `defexception/3` docs.
|
||||
|
||||
defexception RuntimeError, message: "runtime error"
|
||||
defexception ArgumentError, message: "argument error"
|
||||
defexception ArithmeticError, message: "bad argument in arithmetic expression"
|
||||
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
|
||||
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
|
||||
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
|
||||
end
|
||||
end
|
||||
|
||||
defexception BadFunctionError, [term: nil] do
|
||||
def message(exception) do
|
||||
"expected a function, got: #{inspect(exception.term)}"
|
||||
end
|
||||
end
|
||||
|
||||
defexception BadStructError, [struct: nil, term: nil] do
|
||||
def message(exception) do
|
||||
"expected a struct named #{inspect(exception.struct)}, got: #{inspect(exception.term)}"
|
||||
end
|
||||
end
|
||||
|
||||
defexception MatchError, [term: nil] do
|
||||
def message(exception) do
|
||||
"no match of right hand side value: #{inspect(exception.term)}"
|
||||
end
|
||||
end
|
||||
|
||||
defexception CaseClauseError, [term: nil] do
|
||||
def message(exception) do
|
||||
"no case clause matching: #{inspect(exception.term)}"
|
||||
end
|
||||
end
|
||||
|
||||
defexception TryClauseError, [term: nil] do
|
||||
def message(exception) do
|
||||
"no try clause matching: #{inspect(exception.term)}"
|
||||
end
|
||||
end
|
||||
|
||||
defexception BadArityError, [function: nil, args: nil] do
|
||||
def message(exception) do
|
||||
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
|
||||
def message(exception) do
|
||||
if exception.function do
|
||||
formatted = Exception.format_mfa exception.module, exception.function, exception.arity
|
||||
"undefined function: #{formatted}"
|
||||
else
|
||||
"undefined function"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defexception FunctionClauseError, [module: nil, function: nil, arity: nil] do
|
||||
def message(exception) do
|
||||
if exception.function do
|
||||
formatted = Exception.format_mfa exception.module, exception.function, exception.arity
|
||||
"no function clause matching in #{formatted}"
|
||||
else
|
||||
"no function clause matches"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defexception Protocol.UndefinedError, [protocol: nil, value: nil, description: nil] do
|
||||
def message(exception) do
|
||||
msg = "protocol #{inspect exception.protocol} not implemented for #{inspect exception.value}"
|
||||
if exception.description do
|
||||
msg <> ", " <> exception.description
|
||||
else
|
||||
msg
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defexception ErlangError, [original: nil] do
|
||||
def message(exception) do
|
||||
"erlang error: #{inspect(exception.original)}"
|
||||
end
|
||||
end
|
||||
|
||||
defexception KeyError, key: nil, term: nil do
|
||||
def message(exception) do
|
||||
"key #{inspect exception.key} not found in: #{inspect exception.term}"
|
||||
end
|
||||
end
|
||||
|
||||
defexception UnicodeConversionError, [:encoded, :message] do
|
||||
def exception(opts) do
|
||||
UnicodeConversionError[
|
||||
encoded: opts[:encoded],
|
||||
message: "#{opts[:kind]} #{detail(opts[:rest])}"
|
||||
]
|
||||
end
|
||||
|
||||
defp detail(rest) when is_binary(rest) do
|
||||
"encoding starting at #{inspect rest}"
|
||||
end
|
||||
|
||||
defp detail([h|_]) do
|
||||
"code point #{h}"
|
||||
end
|
||||
end
|
||||
|
||||
defexception Enum.OutOfBoundsError, message: "out of bounds error"
|
||||
|
||||
defexception Enum.EmptyError, message: "empty error"
|
||||
|
||||
defexception File.Error, [reason: nil, action: "", path: nil] do
|
||||
def message(exception) do
|
||||
formatted = iodata_to_binary(:file.format_error(reason exception))
|
||||
"could not #{action exception} #{path exception}: #{formatted}"
|
||||
end
|
||||
end
|
||||
|
||||
defexception File.CopyError, [reason: nil, action: "", source: nil, destination: nil, on: nil] do
|
||||
def message(exception) do
|
||||
formatted = iodata_to_binary(:file.format_error(reason exception))
|
||||
location = if on = on(exception), do: ". #{on}", else: ""
|
||||
"could not #{action exception} from #{source exception} to " <>
|
||||
"#{destination exception}#{location}: #{formatted}"
|
||||
end
|
||||
end
|
||||
|
||||
defmodule Exception do
|
||||
@moduledoc """
|
||||
Functions to work with and pretty print exceptions.
|
||||
Functions to format throw/catch/exit and exceptions.
|
||||
|
||||
Note that stacktraces in Elixir are updated on throw,
|
||||
errors and exits. For example, at any given moment,
|
||||
`System.stacktrace` will return the stacktrace for the
|
||||
last throw/error/exit that ocurred in the current process.
|
||||
|
||||
Finally note developers should not rely on the particular
|
||||
format of the `format` functions provided by this module.
|
||||
They may be changed in future releases in order to better
|
||||
suit Elixir's tool chain. In other words, by using the
|
||||
functions in this module it is guarantee you will format
|
||||
exceptions as in the current Elixir version being used.
|
||||
Do not rely on the particular format returned by the `format`
|
||||
functions in this module. They may be changed in future releases
|
||||
in order to better suit Elixir's tool chain. In other words,
|
||||
by using the functions in this module it is guarantee you will
|
||||
format exceptions as in the current Elixir version being used.
|
||||
"""
|
||||
|
||||
@type t :: tuple
|
||||
@typedoc "The exception type (as generated by defexception)"
|
||||
@type t :: %{__struct__: module, __exception__: true}
|
||||
|
||||
@typedoc "The kind handled by formatting functions"
|
||||
@type kind :: :error | :exit | :throw | {:EXIT, pid}
|
||||
|
||||
@type stacktrace :: [stacktrace_entry]
|
||||
@type process ::
|
||||
pid | atom | {atom, node} | {:global, term} | {:via, module, term}
|
||||
@type stacktrace_entry ::
|
||||
{module, function, arity_or_args, location} |
|
||||
{function, arity_or_args, location}
|
||||
@@ -182,24 +28,25 @@ defmodule Exception do
|
||||
@typep arity_or_args :: non_neg_integer | list
|
||||
@typep location :: Keyword.t
|
||||
|
||||
@callback exception(term) :: t
|
||||
@callback message(t) :: String.t
|
||||
|
||||
@doc """
|
||||
Returns true if the given argument is an exception.
|
||||
"""
|
||||
def exception?(exception) do
|
||||
is_tuple(exception) and tuple_size(exception) > 1 and
|
||||
:erlang.element(2, exception) == :__exception__
|
||||
end
|
||||
def exception?(%{__struct__: struct, __exception__: true}) when is_atom(struct), do: true
|
||||
def exception?(_), do: false
|
||||
|
||||
@doc """
|
||||
Gets the message for an exception.
|
||||
"""
|
||||
def message(exception) do
|
||||
def message(%{__struct__: module, __exception__: true} = exception) when is_atom(module) do
|
||||
try do
|
||||
exception.message
|
||||
module.message(exception)
|
||||
rescue
|
||||
e ->
|
||||
raise ArgumentError, message:
|
||||
"Got #{inspect e.__record__(:name)} with message " <>
|
||||
raise ArgumentError,
|
||||
"Got #{inspect e.__struct__} with message " <>
|
||||
"\"#{message(e)}\" while retrieving message for #{inspect(exception)}"
|
||||
end
|
||||
end
|
||||
@@ -220,16 +67,17 @@ defmodule Exception do
|
||||
an empty stacktrace, `[]`, must be used.
|
||||
"""
|
||||
@spec normalize(:error, any, stacktrace) :: t
|
||||
@spec normalize(:exit | :throw, any, stacktrace) :: any
|
||||
# Generating a stacktrace is expensive, default to nil to only fetch it when
|
||||
# needed.
|
||||
@spec normalize(kind, payload, stacktrace) :: payload when payload: var
|
||||
|
||||
# Generating a stacktrace is expensive, default to nil
|
||||
# to only fetch it when needed.
|
||||
def normalize(kind, payload, stacktrace \\ nil)
|
||||
|
||||
def normalize(:error, exception, stacktrace) do
|
||||
if exception?(exception) do
|
||||
exception
|
||||
else
|
||||
normalize_error(exception, stacktrace)
|
||||
ErlangError.normalize(exception, stacktrace)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -237,60 +85,6 @@ defmodule Exception do
|
||||
payload
|
||||
end
|
||||
|
||||
defp normalize_error(:badarg, _stacktrace) do
|
||||
ArgumentError[]
|
||||
end
|
||||
|
||||
defp normalize_error(:badarith, _stacktrace) do
|
||||
ArithmeticError[]
|
||||
end
|
||||
|
||||
defp normalize_error(:system_limit, _stacktrace) do
|
||||
SystemLimitError[]
|
||||
end
|
||||
|
||||
defp normalize_error({:badarity, {fun, args}}, _stacktrace) do
|
||||
BadArityError[function: fun, args: args]
|
||||
end
|
||||
|
||||
defp normalize_error({:badfun, term}, _stacktrace) do
|
||||
BadFunctionError[term: term]
|
||||
end
|
||||
|
||||
defp normalize_error({:badstruct, struct, term}, _stacktrace) do
|
||||
BadStructError[struct: struct, term: term]
|
||||
end
|
||||
|
||||
defp normalize_error({:badmatch, term}, _stacktrace) do
|
||||
MatchError[term: term]
|
||||
end
|
||||
|
||||
defp normalize_error({:case_clause, term}, _stacktrace) do
|
||||
CaseClauseError[term: term]
|
||||
end
|
||||
|
||||
defp normalize_error({:try_clause, term}, _stacktrace) do
|
||||
TryClauseError[term: term]
|
||||
end
|
||||
|
||||
defp normalize_error(:undef, stacktrace) do
|
||||
{mod, fun, arity} = from_stacktrace(stacktrace || :erlang.get_stacktrace)
|
||||
UndefinedFunctionError[module: mod, function: fun, arity: arity]
|
||||
end
|
||||
|
||||
defp normalize_error(:function_clause, stacktrace) do
|
||||
{mod, fun, arity} = from_stacktrace(stacktrace || :erlang.get_stacktrace)
|
||||
FunctionClauseError[module: mod, function: fun, arity: arity]
|
||||
end
|
||||
|
||||
defp normalize_error({:badarg, payload}, _stacktrace) do
|
||||
ArgumentError[message: "argument error: #{inspect(payload)}"]
|
||||
end
|
||||
|
||||
defp normalize_error(other, _stacktrace) do
|
||||
ErlangError[original: other]
|
||||
end
|
||||
|
||||
@doc """
|
||||
Normalizes and formats any throw, error and exit.
|
||||
|
||||
@@ -304,12 +98,12 @@ defmodule Exception do
|
||||
not return the stacktrace corresponding to the exception
|
||||
an empty stacktrace, `[]`, must be used.
|
||||
"""
|
||||
@spec format_banner(:error | :exit | :throw, any, stacktrace) :: String.t
|
||||
@spec format_banner(kind, any, stacktrace | nil) :: String.t
|
||||
def format_banner(kind, exception, stacktrace \\ nil)
|
||||
|
||||
def format_banner(:error, exception, stacktrace) do
|
||||
exception = normalize(:error, exception, stacktrace)
|
||||
"** (" <> inspect(exception.__record__(:name)) <> ") " <> message(exception)
|
||||
"** (" <> inspect(exception.__struct__) <> ") " <> message(exception)
|
||||
end
|
||||
|
||||
def format_banner(:throw, reason, _stacktrace) do
|
||||
@@ -320,18 +114,34 @@ defmodule Exception do
|
||||
"** (exit) " <> format_exit(reason, <<"\n ">>)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Normalizes and formats an exception and stacktrace.
|
||||
def format_banner({:EXIT, pid}, reason, _stacktrace) do
|
||||
"** (EXIT from #{inspect pid}) " <> format_exit(reason, <<"\n ">>)
|
||||
end
|
||||
|
||||
Behaves the same as `format_banner/3` but also includes
|
||||
the formatted stacktrace.
|
||||
@doc """
|
||||
Normalizes and formats throw/errors/exits and stacktrace.
|
||||
|
||||
It relies on `format_banner/3` and `format_stacktrace/1`
|
||||
to generate the final format.
|
||||
|
||||
Note that `{:EXIT, pid}` do not generate a stacktrace though
|
||||
(as they are retrieved as messages without stacktraces).
|
||||
"""
|
||||
@spec format(:error | :exit | :throw, any, stacktrace) :: String.t
|
||||
def format(kind, exception, stacktrace \\ :erlang.get_stacktrace()) do
|
||||
message = format_banner(kind, exception, stacktrace)
|
||||
|
||||
@spec format(kind, any, stacktrace | nil) :: String.t
|
||||
|
||||
def format(kind, payload, stacktrace \\ nil)
|
||||
|
||||
def format({:EXIT, _} = kind, any, _) do
|
||||
format_banner(kind, any)
|
||||
end
|
||||
|
||||
def format(kind, payload, stacktrace) do
|
||||
stacktrace = stacktrace || System.stacktrace
|
||||
message = format_banner(kind, payload, stacktrace)
|
||||
case stacktrace do
|
||||
[] -> message
|
||||
_ -> message <> "\n" <> format_stacktrace(stacktrace)
|
||||
_ -> message <> "\n" <> format_stacktrace(stacktrace)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -547,7 +357,7 @@ defmodule Exception do
|
||||
|
||||
defp format_application(module) do
|
||||
case :application.get_application(module) do
|
||||
{:ok, app} -> "(" <> atom_to_binary(app) <> ") "
|
||||
{:ok, app} -> "(" <> Atom.to_string(app) <> ") "
|
||||
:undefined -> ""
|
||||
end
|
||||
end
|
||||
@@ -624,7 +434,7 @@ defmodule Exception do
|
||||
end
|
||||
|
||||
defp format_arity(arity) when is_integer(arity) do
|
||||
"/" <> integer_to_binary(arity)
|
||||
"/" <> Integer.to_string(arity)
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -662,6 +472,308 @@ defmodule Exception do
|
||||
defp format_location(opts) when is_list(opts) do
|
||||
format_file_line Keyword.get(opts, :file), Keyword.get(opts, :line), " "
|
||||
end
|
||||
end
|
||||
|
||||
# Some exceptions implement `message/1` instead of `exception/1` mostly
|
||||
# for bootstrap reasons. It is recommended for applications to implement
|
||||
# `exception/1` instead of `message/1` as described in `defexception/1`
|
||||
# docs.
|
||||
|
||||
defmodule RuntimeError do
|
||||
defexception message: "runtime error"
|
||||
|
||||
def exception(msg) when is_binary(msg) do
|
||||
%RuntimeError{message: msg}
|
||||
end
|
||||
|
||||
def exception(arg) do
|
||||
super(arg)
|
||||
end
|
||||
end
|
||||
|
||||
defmodule ArgumentError do
|
||||
defexception message: "argument error"
|
||||
|
||||
def exception(msg) when is_binary(msg) do
|
||||
%ArgumentError{message: msg}
|
||||
end
|
||||
|
||||
def exception(arg) do
|
||||
super(arg)
|
||||
end
|
||||
end
|
||||
|
||||
defmodule ArithmeticError do
|
||||
defexception []
|
||||
|
||||
def message(_) do
|
||||
"bad argument in arithmetic expression"
|
||||
end
|
||||
end
|
||||
|
||||
defmodule SystemLimitError do
|
||||
defexception []
|
||||
|
||||
def message(_) do
|
||||
"a system limit has been reached"
|
||||
end
|
||||
end
|
||||
|
||||
defmodule SyntaxError do
|
||||
defexception [file: nil, line: nil, description: "syntax error"]
|
||||
|
||||
def message(exception) do
|
||||
Exception.format_file_line(Path.relative_to_cwd(exception.file), exception.line) <>
|
||||
" " <> exception.description
|
||||
end
|
||||
end
|
||||
|
||||
defmodule TokenMissingError do
|
||||
defexception [file: nil, line: nil, description: "expression is incomplete"]
|
||||
|
||||
def message(exception) do
|
||||
Exception.format_file_line(Path.relative_to_cwd(exception.file), exception.line) <>
|
||||
" " <> exception.description
|
||||
end
|
||||
end
|
||||
|
||||
defmodule CompileError do
|
||||
defexception [file: nil, line: nil, description: "compile error"]
|
||||
|
||||
def message(exception) do
|
||||
Exception.format_file_line(Path.relative_to_cwd(exception.file), exception.line) <>
|
||||
" " <> exception.description
|
||||
end
|
||||
end
|
||||
|
||||
defmodule BadFunctionError do
|
||||
defexception [term: nil]
|
||||
|
||||
def message(exception) do
|
||||
"expected a function, got: #{inspect(exception.term)}"
|
||||
end
|
||||
end
|
||||
|
||||
defmodule BadStructError do
|
||||
defexception [struct: nil, term: nil]
|
||||
|
||||
def message(exception) do
|
||||
"expected a struct named #{inspect(exception.struct)}, got: #{inspect(exception.term)}"
|
||||
end
|
||||
end
|
||||
|
||||
defmodule MatchError do
|
||||
defexception [term: nil]
|
||||
|
||||
def message(exception) do
|
||||
"no match of right hand side value: #{inspect(exception.term)}"
|
||||
end
|
||||
end
|
||||
|
||||
defmodule CaseClauseError do
|
||||
defexception [term: nil]
|
||||
|
||||
def message(exception) do
|
||||
"no case clause matching: #{inspect(exception.term)}"
|
||||
end
|
||||
end
|
||||
|
||||
defmodule TryClauseError do
|
||||
defexception [term: nil]
|
||||
|
||||
def message(exception) do
|
||||
"no try clause matching: #{inspect(exception.term)}"
|
||||
end
|
||||
end
|
||||
|
||||
defmodule BadArityError do
|
||||
defexception [function: nil, args: nil]
|
||||
|
||||
def message(exception) do
|
||||
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
|
||||
|
||||
defmodule UndefinedFunctionError do
|
||||
defexception [module: nil, function: nil, arity: nil]
|
||||
|
||||
def message(exception) do
|
||||
if exception.function do
|
||||
formatted = Exception.format_mfa exception.module, exception.function, exception.arity
|
||||
"undefined function: #{formatted}"
|
||||
else
|
||||
"undefined function"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defmodule FunctionClauseError do
|
||||
defexception [module: nil, function: nil, arity: nil]
|
||||
|
||||
def message(exception) do
|
||||
if exception.function do
|
||||
formatted = Exception.format_mfa exception.module, exception.function, exception.arity
|
||||
"no function clause matching in #{formatted}"
|
||||
else
|
||||
"no function clause matches"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defmodule Code.LoadError do
|
||||
defexception [:file, :message]
|
||||
|
||||
def exception(opts) do
|
||||
file = opts[:file]
|
||||
%Code.LoadError{message: "could not load #{file}", file: file}
|
||||
end
|
||||
end
|
||||
|
||||
defmodule Protocol.UndefinedError do
|
||||
defexception [protocol: nil, value: nil, description: nil]
|
||||
|
||||
def message(exception) do
|
||||
msg = "protocol #{inspect exception.protocol} not implemented for #{inspect exception.value}"
|
||||
if exception.description do
|
||||
msg <> ", " <> exception.description
|
||||
else
|
||||
msg
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defmodule KeyError do
|
||||
defexception key: nil, term: nil
|
||||
|
||||
def message(exception) do
|
||||
"key #{inspect exception.key} not found in: #{inspect exception.term}"
|
||||
end
|
||||
end
|
||||
|
||||
defmodule UnicodeConversionError do
|
||||
defexception [:encoded, :message]
|
||||
|
||||
def exception(opts) do
|
||||
%UnicodeConversionError{
|
||||
encoded: opts[:encoded],
|
||||
message: "#{opts[:kind]} #{detail(opts[:rest])}"
|
||||
}
|
||||
end
|
||||
|
||||
defp detail(rest) when is_binary(rest) do
|
||||
"encoding starting at #{inspect rest}"
|
||||
end
|
||||
|
||||
defp detail([h|_]) do
|
||||
"code point #{h}"
|
||||
end
|
||||
end
|
||||
|
||||
defmodule Enum.OutOfBoundsError do
|
||||
defexception []
|
||||
|
||||
def message(_) do
|
||||
"out of bounds error"
|
||||
end
|
||||
end
|
||||
|
||||
defmodule Enum.EmptyError do
|
||||
defexception []
|
||||
|
||||
def message(_) do
|
||||
"empty error"
|
||||
end
|
||||
end
|
||||
|
||||
defmodule File.Error do
|
||||
defexception [reason: nil, action: "", path: nil]
|
||||
|
||||
def message(exception) do
|
||||
formatted = IO.iodata_to_binary(:file.format_error(exception.reason))
|
||||
"could not #{exception.action} #{exception.path}: #{formatted}"
|
||||
end
|
||||
end
|
||||
|
||||
defmodule File.CopyError do
|
||||
defexception [reason: nil, action: "", source: nil, destination: nil, on: nil]
|
||||
|
||||
def message(exception) do
|
||||
formatted = IO.iodata_to_binary(:file.format_error(exception.reason))
|
||||
location = if on = exception.on, do: ". #{on}", else: ""
|
||||
"could not #{exception.action} from #{exception.source} to " <>
|
||||
"#{exception.destination}#{location}: #{formatted}"
|
||||
end
|
||||
end
|
||||
|
||||
defmodule ErlangError do
|
||||
defexception [original: nil]
|
||||
|
||||
def message(exception) do
|
||||
"erlang error: #{inspect(exception.original)}"
|
||||
end
|
||||
|
||||
@doc false
|
||||
def normalize(:badarg, _stacktrace) do
|
||||
%ArgumentError{}
|
||||
end
|
||||
|
||||
def normalize(:badarith, _stacktrace) do
|
||||
%ArithmeticError{}
|
||||
end
|
||||
|
||||
def normalize(:system_limit, _stacktrace) do
|
||||
%SystemLimitError{}
|
||||
end
|
||||
|
||||
def normalize({:badarity, {fun, args}}, _stacktrace) do
|
||||
%BadArityError{function: fun, args: args}
|
||||
end
|
||||
|
||||
def normalize({:badfun, term}, _stacktrace) do
|
||||
%BadFunctionError{term: term}
|
||||
end
|
||||
|
||||
def normalize({:badstruct, struct, term}, _stacktrace) do
|
||||
%BadStructError{struct: struct, term: term}
|
||||
end
|
||||
|
||||
def normalize({:badmatch, term}, _stacktrace) do
|
||||
%MatchError{term: term}
|
||||
end
|
||||
|
||||
def normalize({:case_clause, term}, _stacktrace) do
|
||||
%CaseClauseError{term: term}
|
||||
end
|
||||
|
||||
def normalize({:try_clause, term}, _stacktrace) do
|
||||
%TryClauseError{term: term}
|
||||
end
|
||||
|
||||
def normalize(:undef, stacktrace) do
|
||||
{mod, fun, arity} = from_stacktrace(stacktrace || :erlang.get_stacktrace)
|
||||
%UndefinedFunctionError{module: mod, function: fun, arity: arity}
|
||||
end
|
||||
|
||||
def normalize(:function_clause, stacktrace) do
|
||||
{mod, fun, arity} = from_stacktrace(stacktrace || :erlang.get_stacktrace)
|
||||
%FunctionClauseError{module: mod, function: fun, arity: arity}
|
||||
end
|
||||
|
||||
def normalize({:badarg, payload}, _stacktrace) do
|
||||
%ArgumentError{message: "argument error: #{inspect(payload)}"}
|
||||
end
|
||||
|
||||
def normalize(other, _stacktrace) do
|
||||
%ErlangError{original: other}
|
||||
end
|
||||
|
||||
defp from_stacktrace([{module, function, args, _}|_]) when is_list(args) do
|
||||
{module, function, length(args)}
|
||||
|
||||
+50
-50
@@ -228,7 +228,7 @@ defmodule File do
|
||||
"""
|
||||
@spec regular?(Path.t) :: boolean
|
||||
def regular?(path) do
|
||||
FL.is_regular(String.from_char_data!(path))
|
||||
FL.is_regular(IO.chardata_to_string(path))
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -236,7 +236,7 @@ defmodule File do
|
||||
"""
|
||||
@spec dir?(Path.t) :: boolean
|
||||
def dir?(path) do
|
||||
FL.is_dir(String.from_char_data!(path))
|
||||
FL.is_dir(IO.chardata_to_string(path))
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -258,7 +258,7 @@ defmodule File do
|
||||
"""
|
||||
@spec exists?(Path.t) :: boolean
|
||||
def exists?(path) do
|
||||
match?({:ok, _}, F.read_file_info(String.from_char_data!(path)))
|
||||
match?({:ok, _}, F.read_file_info(IO.chardata_to_string(path)))
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -276,7 +276,7 @@ defmodule File do
|
||||
"""
|
||||
@spec mkdir(Path.t) :: :ok | {:error, posix}
|
||||
def mkdir(path) do
|
||||
F.make_dir(String.from_char_data!(path))
|
||||
F.make_dir(IO.chardata_to_string(path))
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -284,7 +284,7 @@ defmodule File do
|
||||
"""
|
||||
@spec mkdir!(Path.t) :: :ok | no_return
|
||||
def mkdir!(path) do
|
||||
path = String.from_char_data!(path)
|
||||
path = IO.chardata_to_string(path)
|
||||
case mkdir(path) do
|
||||
:ok -> :ok
|
||||
{:error, reason} ->
|
||||
@@ -312,7 +312,7 @@ defmodule File do
|
||||
"""
|
||||
@spec mkdir_p!(Path.t) :: :ok | no_return
|
||||
def mkdir_p!(path) do
|
||||
path = String.from_char_data!(path)
|
||||
path = IO.chardata_to_string(path)
|
||||
case mkdir_p(path) do
|
||||
:ok -> :ok
|
||||
{:error, reason} ->
|
||||
@@ -338,7 +338,7 @@ defmodule File do
|
||||
"""
|
||||
@spec read(Path.t) :: {:ok, binary} | {:error, posix}
|
||||
def read(path) do
|
||||
F.read_file(String.from_char_data!(path))
|
||||
F.read_file(IO.chardata_to_string(path))
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -347,7 +347,7 @@ defmodule File do
|
||||
"""
|
||||
@spec read!(Path.t) :: binary | no_return
|
||||
def read!(path) do
|
||||
path = String.from_char_data!(path)
|
||||
path = IO.chardata_to_string(path)
|
||||
case read(path) do
|
||||
{:ok, binary} ->
|
||||
binary
|
||||
@@ -372,7 +372,7 @@ defmodule File do
|
||||
"""
|
||||
@spec stat(Path.t, stat_options) :: {:ok, File.Stat.t} | {:error, posix}
|
||||
def stat(path, opts \\ []) do
|
||||
case F.read_file_info(String.from_char_data!(path), opts) do
|
||||
case F.read_file_info(IO.chardata_to_string(path), opts) do
|
||||
{:ok, fileinfo} ->
|
||||
{:ok, File.Stat.from_record(fileinfo)}
|
||||
error ->
|
||||
@@ -386,7 +386,7 @@ defmodule File do
|
||||
"""
|
||||
@spec stat!(Path.t, stat_options) :: File.Stat.t | no_return
|
||||
def stat!(path, opts \\ []) do
|
||||
path = String.from_char_data!(path)
|
||||
path = IO.chardata_to_string(path)
|
||||
case stat(path, opts) do
|
||||
{:ok, info} -> info
|
||||
{:error, reason} ->
|
||||
@@ -400,7 +400,7 @@ defmodule File do
|
||||
"""
|
||||
@spec write_stat(Path.t, File.Stat.t, stat_options) :: :ok | {:error, posix}
|
||||
def write_stat(path, stat, opts \\ []) do
|
||||
F.write_file_info(String.from_char_data!(path), File.Stat.to_record(stat), opts)
|
||||
F.write_file_info(IO.chardata_to_string(path), File.Stat.to_record(stat), opts)
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -409,7 +409,7 @@ defmodule File do
|
||||
"""
|
||||
@spec write_stat!(Path.t, File.Stat.t, stat_options) :: :ok | no_return
|
||||
def write_stat!(path, stat, opts \\ []) do
|
||||
path = String.from_char_data!(path)
|
||||
path = IO.chardata_to_string(path)
|
||||
case write_stat(path, stat, opts) do
|
||||
:ok -> :ok
|
||||
{:error, reason} ->
|
||||
@@ -423,7 +423,7 @@ defmodule File do
|
||||
"""
|
||||
@spec touch(Path.t, :calendar.datetime) :: :ok | {:error, posix}
|
||||
def touch(path, time \\ :calendar.local_time) do
|
||||
path = String.from_char_data!(path)
|
||||
path = IO.chardata_to_string(path)
|
||||
case F.change_time(path, time) do
|
||||
{:error, :enoent} ->
|
||||
write(path, "")
|
||||
@@ -439,7 +439,7 @@ defmodule File do
|
||||
"""
|
||||
@spec touch!(Path.t, :calendar.datetime) :: :ok | no_return
|
||||
def touch!(path, time \\ :calendar.local_time) do
|
||||
path = String.from_char_data!(path)
|
||||
path = IO.chardata_to_string(path)
|
||||
case touch(path, time) do
|
||||
:ok -> :ok
|
||||
{:error, reason} ->
|
||||
@@ -482,7 +482,7 @@ defmodule File do
|
||||
"""
|
||||
@spec copy(Path.t, Path.t, pos_integer | :infinity) :: {:ok, non_neg_integer} | {:error, posix}
|
||||
def copy(source, destination, bytes_count \\ :infinity) do
|
||||
F.copy(String.from_char_data!(source), String.from_char_data!(destination), bytes_count)
|
||||
F.copy(IO.chardata_to_string(source), IO.chardata_to_string(destination), bytes_count)
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -491,8 +491,8 @@ defmodule File do
|
||||
"""
|
||||
@spec copy!(Path.t, Path.t, pos_integer | :infinity) :: non_neg_integer | no_return
|
||||
def copy!(source, destination, bytes_count \\ :infinity) do
|
||||
source = String.from_char_data!(source)
|
||||
destination = String.from_char_data!(destination)
|
||||
source = IO.chardata_to_string(source)
|
||||
destination = IO.chardata_to_string(destination)
|
||||
case copy(source, destination, bytes_count) do
|
||||
{:ok, bytes_count} -> bytes_count
|
||||
{:error, reason} ->
|
||||
@@ -522,8 +522,8 @@ defmodule File do
|
||||
"""
|
||||
@spec cp(Path.t, Path.t, (Path.t, Path.t -> boolean)) :: :ok | {:error, posix}
|
||||
def cp(source, destination, callback \\ fn(_, _) -> true end) do
|
||||
source = String.from_char_data!(source)
|
||||
destination = String.from_char_data!(destination)
|
||||
source = IO.chardata_to_string(source)
|
||||
destination = IO.chardata_to_string(destination)
|
||||
|
||||
case do_cp_file(source, destination, callback, []) do
|
||||
{:error, reason, _} -> {:error, reason}
|
||||
@@ -537,8 +537,8 @@ defmodule File do
|
||||
"""
|
||||
@spec cp!(Path.t, Path.t, (Path.t, Path.t -> boolean)) :: :ok | no_return
|
||||
def cp!(source, destination, callback \\ fn(_, _) -> true end) do
|
||||
source = String.from_char_data!(source)
|
||||
destination = String.from_char_data!(destination)
|
||||
source = IO.chardata_to_string(source)
|
||||
destination = IO.chardata_to_string(destination)
|
||||
|
||||
case cp(source, destination, callback) do
|
||||
:ok -> :ok
|
||||
@@ -593,8 +593,8 @@ defmodule File do
|
||||
"""
|
||||
@spec cp_r(Path.t, Path.t, (Path.t, Path.t -> boolean)) :: {:ok, [binary]} | {:error, posix, binary}
|
||||
def cp_r(source, destination, callback \\ fn(_, _) -> true end) when is_function(callback) do
|
||||
source = String.from_char_data!(source)
|
||||
destination = String.from_char_data!(destination)
|
||||
source = IO.chardata_to_string(source)
|
||||
destination = IO.chardata_to_string(destination)
|
||||
|
||||
case do_cp_r(source, destination, callback, []) do
|
||||
{:error, _, _} = error -> error
|
||||
@@ -608,8 +608,8 @@ defmodule File do
|
||||
"""
|
||||
@spec cp_r!(Path.t, Path.t, (Path.t, Path.t -> boolean)) :: [binary] | no_return
|
||||
def cp_r!(source, destination, callback \\ fn(_, _) -> true end) do
|
||||
source = String.from_char_data!(source)
|
||||
destination = String.from_char_data!(destination)
|
||||
source = IO.chardata_to_string(source)
|
||||
destination = IO.chardata_to_string(destination)
|
||||
|
||||
case cp_r(source, destination, callback) do
|
||||
{:ok, files} -> files
|
||||
@@ -725,7 +725,7 @@ defmodule File do
|
||||
"""
|
||||
@spec write(Path.t, iodata, list) :: :ok | {:error, posix}
|
||||
def write(path, content, modes \\ []) do
|
||||
F.write_file(String.from_char_data!(path), content, [:raw|modes])
|
||||
F.write_file(IO.chardata_to_string(path), content, [:raw|modes])
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -733,7 +733,7 @@ defmodule File do
|
||||
"""
|
||||
@spec write!(Path.t, iodata, list) :: :ok | no_return
|
||||
def write!(path, content, modes \\ []) do
|
||||
path = String.from_char_data!(path)
|
||||
path = IO.chardata_to_string(path)
|
||||
case F.write_file(path, content, modes) do
|
||||
:ok -> :ok
|
||||
{:error, reason} ->
|
||||
@@ -765,7 +765,7 @@ defmodule File do
|
||||
"""
|
||||
@spec rm(Path.t) :: :ok | {:error, posix}
|
||||
def rm(path) do
|
||||
F.delete(String.from_char_data!(path))
|
||||
F.delete(IO.chardata_to_string(path))
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -773,7 +773,7 @@ defmodule File do
|
||||
"""
|
||||
@spec rm!(Path.t) :: :ok | no_return
|
||||
def rm!(path) do
|
||||
path = String.from_char_data!(path)
|
||||
path = IO.chardata_to_string(path)
|
||||
case rm(path) do
|
||||
:ok -> :ok
|
||||
{:error, reason} ->
|
||||
@@ -796,7 +796,7 @@ defmodule File do
|
||||
"""
|
||||
@spec rmdir(Path.t) :: :ok | {:error, posix}
|
||||
def rmdir(path) do
|
||||
F.del_dir(String.from_char_data!(path))
|
||||
F.del_dir(IO.chardata_to_string(path))
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -804,7 +804,7 @@ defmodule File do
|
||||
"""
|
||||
@spec rmdir!(Path.t) :: :ok | {:error, posix}
|
||||
def rmdir!(path) do
|
||||
path = String.from_char_data!(path)
|
||||
path = IO.chardata_to_string(path)
|
||||
case rmdir(path) do
|
||||
:ok -> :ok
|
||||
{:error, reason} ->
|
||||
@@ -832,7 +832,7 @@ defmodule File do
|
||||
"""
|
||||
@spec rm_rf(Path.t) :: {:ok, [binary]} | {:error, posix, binary}
|
||||
def rm_rf(path) do
|
||||
do_rm_rf(String.from_char_data!(path), {:ok, []})
|
||||
do_rm_rf(IO.chardata_to_string(path), {:ok, []})
|
||||
end
|
||||
|
||||
defp do_rm_rf(path, {:ok, _} = entry) do
|
||||
@@ -907,7 +907,7 @@ defmodule File do
|
||||
"""
|
||||
@spec rm_rf!(Path.t) :: [binary] | no_return
|
||||
def rm_rf!(path) do
|
||||
path = String.from_char_data!(path)
|
||||
path = IO.chardata_to_string(path)
|
||||
case rm_rf(path) do
|
||||
{:ok, files} -> files
|
||||
{:error, reason, _} ->
|
||||
@@ -977,7 +977,7 @@ defmodule File do
|
||||
def open(path, modes \\ [])
|
||||
|
||||
def open(path, modes) when is_list(modes) do
|
||||
F.open(String.from_char_data!(path), open_defaults(modes, true))
|
||||
F.open(IO.chardata_to_string(path), open_defaults(modes, true))
|
||||
end
|
||||
|
||||
def open(path, function) when is_function(function) do
|
||||
@@ -1024,7 +1024,7 @@ defmodule File do
|
||||
"""
|
||||
@spec open!(Path.t, list) :: io_device | no_return
|
||||
def open!(path, modes \\ []) do
|
||||
path = String.from_char_data!(path)
|
||||
path = IO.chardata_to_string(path)
|
||||
case open(path, modes) do
|
||||
{:ok, device} -> device
|
||||
{:error, reason} ->
|
||||
@@ -1038,7 +1038,7 @@ defmodule File do
|
||||
"""
|
||||
@spec open!(Path.t, list, (io_device -> res)) :: res | no_return when res: var
|
||||
def open!(path, modes, function) do
|
||||
path = String.from_char_data!(path)
|
||||
path = IO.chardata_to_string(path)
|
||||
case open(path, modes, function) do
|
||||
{:ok, device} -> device
|
||||
{:error, reason} ->
|
||||
@@ -1057,7 +1057,7 @@ defmodule File do
|
||||
@spec cwd() :: {:ok, binary} | {:error, posix}
|
||||
def cwd() do
|
||||
case F.get_cwd do
|
||||
{:ok, base} -> {:ok, String.from_char_data!(base)}
|
||||
{:ok, base} -> {:ok, IO.chardata_to_string(base)}
|
||||
{:error, _} = error -> error
|
||||
end
|
||||
end
|
||||
@@ -1068,7 +1068,7 @@ defmodule File do
|
||||
@spec cwd!() :: binary | no_return
|
||||
def cwd!() do
|
||||
case F.get_cwd do
|
||||
{:ok, cwd} -> String.from_char_data!(cwd)
|
||||
{:ok, cwd} -> IO.chardata_to_string(cwd)
|
||||
{:error, reason} ->
|
||||
raise File.Error, reason: reason, action: "get current working directory"
|
||||
end
|
||||
@@ -1081,7 +1081,7 @@ defmodule File do
|
||||
"""
|
||||
@spec cd(Path.t) :: :ok | {:error, posix}
|
||||
def cd(path) do
|
||||
F.set_cwd(String.from_char_data!(path))
|
||||
F.set_cwd(IO.chardata_to_string(path))
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -1089,7 +1089,7 @@ defmodule File do
|
||||
"""
|
||||
@spec cd!(Path.t) :: :ok | no_return
|
||||
def cd!(path) do
|
||||
path = String.from_char_data!(path)
|
||||
path = IO.chardata_to_string(path)
|
||||
case F.set_cwd(path) do
|
||||
:ok -> :ok
|
||||
{:error, reason} ->
|
||||
@@ -1124,8 +1124,8 @@ defmodule File do
|
||||
"""
|
||||
@spec ls(Path.t) :: {:ok, [binary]} | {:error, posix}
|
||||
def ls(path \\ ".") do
|
||||
case F.list_dir(String.from_char_data!(path)) do
|
||||
{:ok, file_list} -> {:ok, Enum.map(file_list, &String.from_char_data!/1)}
|
||||
case F.list_dir(IO.chardata_to_string(path)) do
|
||||
{:ok, file_list} -> {:ok, Enum.map(file_list, &IO.chardata_to_string/1)}
|
||||
{:error, _} = error -> error
|
||||
end
|
||||
end
|
||||
@@ -1136,7 +1136,7 @@ defmodule File do
|
||||
"""
|
||||
@spec ls!(Path.t) :: [binary] | no_return
|
||||
def ls!(path \\ ".") do
|
||||
path = String.from_char_data!(path)
|
||||
path = IO.chardata_to_string(path)
|
||||
case ls(path) do
|
||||
{:ok, value} -> value
|
||||
{:error, reason} ->
|
||||
@@ -1197,7 +1197,7 @@ defmodule File do
|
||||
modes
|
||||
end
|
||||
|
||||
%File.Stream{path: String.from_char_data!(path), modes: modes,
|
||||
%File.Stream{path: IO.chardata_to_string(path), modes: modes,
|
||||
raw: raw, line_or_bytes: line_or_bytes}
|
||||
end
|
||||
|
||||
@@ -1208,7 +1208,7 @@ defmodule File do
|
||||
"""
|
||||
@spec chmod(Path.t, integer) :: :ok | {:error, posix}
|
||||
def chmod(path, mode) do
|
||||
F.change_mode(String.from_char_data!(path), mode)
|
||||
F.change_mode(IO.chardata_to_string(path), mode)
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -1216,7 +1216,7 @@ defmodule File do
|
||||
"""
|
||||
@spec chmod!(Path.t, integer) :: :ok | no_return
|
||||
def chmod!(path, mode) do
|
||||
path = String.from_char_data!(path)
|
||||
path = IO.chardata_to_string(path)
|
||||
case chmod(path, mode) do
|
||||
:ok -> :ok
|
||||
{:error, reason} ->
|
||||
@@ -1231,7 +1231,7 @@ defmodule File do
|
||||
"""
|
||||
@spec chgrp(Path.t, integer) :: :ok | {:error, posix}
|
||||
def chgrp(path, gid) do
|
||||
F.change_group(String.from_char_data!(path), gid)
|
||||
F.change_group(IO.chardata_to_string(path), gid)
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -1239,7 +1239,7 @@ defmodule File do
|
||||
"""
|
||||
@spec chgrp!(Path.t, integer) :: :ok | no_return
|
||||
def chgrp!(path, gid) do
|
||||
path = String.from_char_data!(path)
|
||||
path = IO.chardata_to_string(path)
|
||||
case chgrp(path, gid) do
|
||||
:ok -> :ok
|
||||
{:error, reason} ->
|
||||
@@ -1254,7 +1254,7 @@ defmodule File do
|
||||
"""
|
||||
@spec chown(Path.t, integer) :: :ok | {:error, posix}
|
||||
def chown(path, uid) do
|
||||
F.change_owner(String.from_char_data!(path), uid)
|
||||
F.change_owner(IO.chardata_to_string(path), uid)
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -1262,7 +1262,7 @@ defmodule File do
|
||||
"""
|
||||
@spec chown!(Path.t, integer) :: :ok | no_return
|
||||
def chown!(path, uid) do
|
||||
path = String.from_char_data!(path)
|
||||
path = IO.chardata_to_string(path)
|
||||
case chown(path, uid) do
|
||||
:ok -> :ok
|
||||
{:error, reason} ->
|
||||
|
||||
@@ -169,4 +169,84 @@ defmodule Float do
|
||||
def round(number, precision) when is_float(number) and is_integer(precision) and precision in 0..15 do
|
||||
Kernel.round(number * :math.pow(10, precision)) / :math.pow(10, precision)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns a char list which corresponds to the text representation of the given float.
|
||||
|
||||
Inlined by the compiler.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Float.to_char_list(7.0)
|
||||
'7.00000000000000000000e+00'
|
||||
|
||||
"""
|
||||
@spec to_char_list(float) :: char_list
|
||||
def to_char_list(number) do
|
||||
:erlang.float_to_list(number)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns a list which corresponds to the text representation
|
||||
of `float`.
|
||||
|
||||
## Options
|
||||
|
||||
* `:decimals` — number of decimal points to show
|
||||
* `:scientific` — number of decimal points to show, in scientific format
|
||||
* `:compact` — when true, use the most compact representation (ignored with the `scientific` option)
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Float.to_char_list 7.1, [decimals: 2, compact: true]
|
||||
'7.1'
|
||||
|
||||
"""
|
||||
@spec to_char_list(float, list) :: char_list
|
||||
def to_char_list(float, options) do
|
||||
:erlang.float_to_list(float, expand_compact(options))
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns a binary which corresponds to the text representation
|
||||
of `some_float`.
|
||||
|
||||
Inlined by the compiler.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Float.to_string(7.0)
|
||||
"7.00000000000000000000e+00"
|
||||
|
||||
"""
|
||||
@spec to_string(float) :: String.t
|
||||
def to_string(some_float) do
|
||||
:erlang.float_to_binary(some_float)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns a binary which corresponds to the text representation
|
||||
of `float`.
|
||||
|
||||
## Options
|
||||
|
||||
* `:decimals` — number of decimal points to show
|
||||
* `:scientific` — number of decimal points to show, in scientific format
|
||||
* `:compact` — when true, use the most compact representation (ignored with the `scientific` option)
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Float.to_string 7.1, [decimals: 2, compact: true]
|
||||
"7.1"
|
||||
|
||||
"""
|
||||
@spec to_string(float, list) :: String.t
|
||||
def to_string(float, options) do
|
||||
:erlang.float_to_binary(float, expand_compact(options))
|
||||
end
|
||||
|
||||
defp expand_compact([{:compact, false}|t]), do: expand_compact(t)
|
||||
defp expand_compact([{:compact, true}|t]), do: [:compact|expand_compact(t)]
|
||||
defp expand_compact([h|t]), do: [h|expand_compact(t)]
|
||||
defp expand_compact([]), do: []
|
||||
end
|
||||
|
||||
@@ -0,0 +1,581 @@
|
||||
defmodule GenEvent do
|
||||
@moduledoc """
|
||||
A behaviour module for implementing event handling functionality.
|
||||
|
||||
The event handling model consists of a generic event manager
|
||||
process with an arbitrary number of event handlers which are
|
||||
added and deleted dynamically.
|
||||
|
||||
An event manager implemented using this module will have a standard
|
||||
set of interface functions and include functionality for tracing and
|
||||
error reporting. It will also fit into an supervision tree.
|
||||
|
||||
## Example
|
||||
|
||||
There are many use cases for event handlers. For example, a logging
|
||||
system can be built using event handlers where which log message is
|
||||
an event and different event handlers can be plugged to handle the
|
||||
log messages. One handler may print error messages on the terminal,
|
||||
another can write it to a file, while a third one can keep the
|
||||
messages in memory (like a buffer) until they are read.
|
||||
|
||||
As an example, let's have a GenEvent that accumulates messages until
|
||||
they are collected by an explicit call.
|
||||
|
||||
defmodule LoggerHandler do
|
||||
use GenEvent
|
||||
|
||||
# Callbacks
|
||||
|
||||
def handle_event({:log, x}, messages) do
|
||||
{:ok, [x|messages]}
|
||||
end
|
||||
|
||||
def handle_call(:messages, messages) do
|
||||
{:ok, Enum.reverse(messages), []}
|
||||
end
|
||||
end
|
||||
|
||||
{:ok, pid} = GenEvent.start_link()
|
||||
|
||||
GenEvent.add_handler(pid, LoggerHandler, [])
|
||||
#=> :ok
|
||||
|
||||
GenEvent.notify(pid, {:log, 1})
|
||||
#=> :ok
|
||||
|
||||
GenEvent.notify(pid, {:log, 2})
|
||||
#=> :ok
|
||||
|
||||
GenEvent.call(pid, LoggerHandler, :messages)
|
||||
#=> [1, 2]
|
||||
|
||||
GenEvent.call(pid, LoggerHandler, :messages)
|
||||
#=> []
|
||||
|
||||
We start a new event manager by calling `GenEvent.start_link/0`.
|
||||
Notifications can be sent to the event manager which will then
|
||||
invoke `handle_event/0` for each registered handler.
|
||||
|
||||
We can add new handlers with `add_handler/4`. Calls can also
|
||||
be made to specific handlers by using `call/3`.
|
||||
|
||||
## Callbacks
|
||||
|
||||
There are 6 callbacks required to be implemented in a `GenEvent`. By
|
||||
adding `use GenEvent` to your module, Elixir will automatically define
|
||||
all 6 callbacks for you, leaving it up to you to implement the ones
|
||||
you want to customize. The callbacks are:
|
||||
|
||||
* `init(args)` - invoked when the event handler is added
|
||||
|
||||
It must return:
|
||||
|
||||
{:ok, state}
|
||||
{:ok, state, :hibernate}
|
||||
{:error, reason}
|
||||
|
||||
* `handle_event(msg, state)` - invoked whenever an event is sent via
|
||||
`notify/2` or `sync_notify/2`.
|
||||
|
||||
It must return:
|
||||
|
||||
{:ok, new_state}
|
||||
{:ok, new_state, :hibernate}
|
||||
{:swap_handler, args1, new_state, handler2, args2}
|
||||
:remove_handler
|
||||
|
||||
* `handle_call(msg, state)` - invoked when a `call/3` is done to a specific handler.
|
||||
|
||||
It must return:
|
||||
|
||||
{:ok, reply, new_state}
|
||||
{:ok, reply, new_state, :hibernate}
|
||||
{:swap_handler, reply, args1, new_state, handler2, args2}
|
||||
{:remove_handler, reply}
|
||||
|
||||
* `handle_info(msg, state)` - invoked to handle all other messages which
|
||||
are received by the process. Must return the same values as `handle_event/2`;
|
||||
|
||||
It must return:
|
||||
|
||||
{:noreply, state}
|
||||
{:noreply, state, timeout}
|
||||
{:stop, reason, state}
|
||||
|
||||
* `terminate(reason, state)` - called when the event handler is removed or the
|
||||
event manager is terminating. It can return any term.
|
||||
|
||||
* `code_change(old_vsn, state, extra)` - called when the application
|
||||
code is being upgraded live (hot code swap).
|
||||
|
||||
It must return:
|
||||
|
||||
{:ok, new_state}
|
||||
|
||||
## Name registering
|
||||
|
||||
A GenEvent is bound to the same name registering rules as a `GenServer`.
|
||||
Read more about it in the `GenServer` docs.
|
||||
|
||||
## Streaming
|
||||
|
||||
`GenEvent`'s can be streamed from and streamed with the help of `stream/2`.
|
||||
Here are some examples:
|
||||
|
||||
stream = GenEvent.stream(pid)
|
||||
|
||||
# Take the next 10 events
|
||||
Enum.take(stream, 10)
|
||||
|
||||
# Print all other events
|
||||
for event <- stream do
|
||||
IO.inspect event
|
||||
end
|
||||
|
||||
A stream may also be given an id, which allows all streams with the given
|
||||
id to be cancelled at any moment via `cancel_streams/1`.
|
||||
|
||||
## Learn more
|
||||
|
||||
In case you desire to learn more about gen events, Elixir getting started
|
||||
guides provide a tutorial-like introduction. The documentation and links
|
||||
in Erlang can also provide extra insight.
|
||||
|
||||
* http://elixir-lang.org/getting_started/mix/1.html
|
||||
* http://www.erlang.org/doc/man/gen_event.html
|
||||
* http://learnyousomeerlang.com/event-handlers
|
||||
"""
|
||||
|
||||
@typedoc "Return values of `start*` functions"
|
||||
@type on_start :: {:ok, pid} | {:error, {:already_started, pid}}
|
||||
|
||||
@typedoc "The GenEvent manager name"
|
||||
@type name :: atom | {:global, term} | {:via, module, term}
|
||||
|
||||
@typedoc "Options used by the `start*` functions"
|
||||
@type options :: [name: name]
|
||||
|
||||
@typedoc "The event manager reference"
|
||||
@type manager :: pid | name | {atom, node}
|
||||
|
||||
@typedoc "Supported values for new handlers"
|
||||
@type handler :: module | {module, term}
|
||||
|
||||
@doc """
|
||||
Defines a `GenEvent` stream.
|
||||
|
||||
This is a struct returned by `stream/2`. The struct is public and
|
||||
contains the following fields:
|
||||
|
||||
* `:manager` - the manager reference given to `GenEvent.stream/2`
|
||||
* `:id` - the event stream id for cancellation
|
||||
* `:timeout` - the timeout in between events, defaults to `:infinity`
|
||||
* `:duration` - the duration of the subscription, defaults to `:infinity`
|
||||
"""
|
||||
defstruct manager: nil, id: nil, timeout: :infinity, duration: :infinity
|
||||
|
||||
@doc false
|
||||
defmacro __using__(_) do
|
||||
quote location: :keep do
|
||||
@behaviour :gen_event
|
||||
|
||||
@doc false
|
||||
def init(args) do
|
||||
{:ok, args}
|
||||
end
|
||||
|
||||
@doc false
|
||||
def handle_event(_event, state) do
|
||||
{:ok, state}
|
||||
end
|
||||
|
||||
@doc false
|
||||
def handle_call(_request, state) do
|
||||
{:ok, {:error, :bad_call}, state}
|
||||
end
|
||||
|
||||
@doc false
|
||||
def handle_info(_msg, state) do
|
||||
{:ok, state}
|
||||
end
|
||||
|
||||
@doc false
|
||||
def terminate(reason, state) do
|
||||
:ok
|
||||
end
|
||||
|
||||
@doc false
|
||||
def code_change(_old, state, _extra) do
|
||||
{:ok, state}
|
||||
end
|
||||
|
||||
defoverridable [init: 1, handle_event: 2, handle_call: 2,
|
||||
handle_info: 2, terminate: 2, code_change: 3]
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Starts an event manager linked to the current process.
|
||||
|
||||
This is often used to start the `GenServer` as part of a supervision tree.
|
||||
|
||||
It accepts a the `:name` which is described under the `Name Registering`
|
||||
section in the `GenServer` module docs.
|
||||
|
||||
If the event manager is successfully created and initialized the function
|
||||
returns `{:ok, pid}`, where pid is the pid of the server. If there already
|
||||
exists a process with the specified server name the function returns
|
||||
`{:error, {:already_started, pid}}` with the pid of that process.
|
||||
"""
|
||||
@spec start_link(options) :: on_start
|
||||
def start_link(options \\ []) when is_list(options) do
|
||||
do_start(:link, options)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Starts an event manager process without links (outside of a supervision tree).
|
||||
|
||||
See `spawn_link/1` for more information.
|
||||
"""
|
||||
@spec start(options) :: on_start
|
||||
def start(options \\ []) when is_list(options) do
|
||||
do_start(:nolink, options)
|
||||
end
|
||||
|
||||
defp do_start(mode, options) do
|
||||
case Keyword.get(options, :name) do
|
||||
nil ->
|
||||
:gen.start(:gen_event, mode, :"no callback module", [], [])
|
||||
atom when is_atom(atom) ->
|
||||
:gen.start(:gen_event, mode, {:local, atom}, :"no callback module", [], [])
|
||||
other when is_tuple(other) ->
|
||||
:gen.start(:gen_event, mode, other, :"no callback module", [], [])
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns a stream that consumes and notifies events to the `manager`.
|
||||
|
||||
The stream is a `GenEvent` struct that implements the `Enumerable`
|
||||
protocol. The supported options are:
|
||||
|
||||
* `:id` - an id to identify all live stream instances. When an `:id` is given,
|
||||
existing streams can be called with via `cancel_streams`;
|
||||
* `:timeout` (Enumerable) - raises if no event arrives in X milliseconds;
|
||||
* `:duration` (Enumerable) - only consume events during the X milliseconds
|
||||
from the streaming start;
|
||||
"""
|
||||
def stream(manager, options \\ []) do
|
||||
%GenEvent{manager: manager,
|
||||
id: Keyword.get(options, :id),
|
||||
timeout: Keyword.get(options, :timeout, :infinity),
|
||||
duration: Keyword.get(options, :duration, :infinity)}
|
||||
end
|
||||
|
||||
@doc """
|
||||
Adds a new event handler to the event `manager`.
|
||||
|
||||
The event manager will call the `init/1` callback with `args` to
|
||||
initiate the event handler and its internal state.
|
||||
|
||||
If `init/1` returns a correct value indicating successful completion,
|
||||
the event manager adds the event handler and this function returns
|
||||
`:ok`. If the callback fails with `reason` or returns `{:error, reason}`,
|
||||
the event handler is ignored and this function returns `{:EXIT, reason}`
|
||||
or `{:error, reason}`, respectively.
|
||||
|
||||
## Linked handlers
|
||||
|
||||
When adding a handler, a `:link` option may be given as true.
|
||||
This means the event handler and the calling process are now linked.
|
||||
|
||||
If the calling process later terminates with `reason`, the event manager
|
||||
will delete the event handler by calling the `terminate/2` callback with
|
||||
`{:stop, reason}` as argument. If the event handler later is deleted,
|
||||
the event manager sends a message `{:gen_event_EXIT, handler, reason}`
|
||||
to the calling process. Reason is one of the following:
|
||||
|
||||
* `:normal` - if the event handler has been removed due to a call to
|
||||
`remove_handler/3`, or `:remove_handler` has been returned by a callback
|
||||
function;
|
||||
|
||||
* `:shutdown` - if the event handler has been removed because the event
|
||||
manager is terminating;
|
||||
|
||||
* `{:swapped, new_handler, pid}` - if the process pid has replaced the
|
||||
event handler by another;
|
||||
|
||||
* a term - if the event handler is removed due to an error. Which term
|
||||
depends on the error;
|
||||
|
||||
"""
|
||||
@spec add_handler(manager, handler, term, [link: boolean]) :: :ok | {:EXIT, term} | {:error, term}
|
||||
def add_handler(manager, handler, args, options \\ []) do
|
||||
case Keyword.get(options, :link, false) do
|
||||
true -> :gen_event.add_sup_handler(manager, handler, args)
|
||||
false -> :gen_event.add_handler(manager, handler, args)
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Sends an event notification to the event `manager`.
|
||||
|
||||
The event manager will call `handle_event/2` for each installed event handler.
|
||||
|
||||
`notify` is asynchronous and will return immediately after the notification is
|
||||
sent. `notify` will not fail even if the specified event manager does not exist,
|
||||
unless it is specified as `name` (atom).
|
||||
"""
|
||||
@spec notify(manager, term) :: :ok
|
||||
defdelegate notify(manager, event), to: :gen_event
|
||||
|
||||
@doc """
|
||||
Sends a sync event notification to the event `manager`.
|
||||
|
||||
In other words, this function only returns `:ok` after the event manager
|
||||
invokes the `handle_event/2` on each installed event handler.
|
||||
|
||||
See `notify/2` for more info.
|
||||
"""
|
||||
@spec sync_notify(manager, term) :: :ok
|
||||
defdelegate sync_notify(manager, event), to: :gen_event
|
||||
|
||||
@doc """
|
||||
Makes a synchronous call to the event `handler` installed in `manager`.
|
||||
|
||||
The given `request` is sent and the caller waits until a reply arrives or
|
||||
a timeout occurs. The event manager will call `handle_call/2` to handle
|
||||
the request.
|
||||
|
||||
The return value `reply` is defined in the return value of `handle_call/2`.
|
||||
If the specified event handler is not installed, the function returns
|
||||
`{:error, :bad_module}`.
|
||||
"""
|
||||
@spec call(manager, handler, term, timeout) :: term | {:error, term}
|
||||
def call(manager, handler, request, timeout \\ 5000) do
|
||||
:gen_event.call(manager, handler, request, timeout)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Cancels all streams currently running with the given `:id`.
|
||||
|
||||
In order for a stream to be cancelled, an `:id` must be passed
|
||||
when the stream is created via `stream/2`. Passing a stream without
|
||||
an id leads to an argument error.
|
||||
"""
|
||||
@spec cancel_streams(t) :: :ok
|
||||
def cancel_streams(%GenEvent{id: nil}) do
|
||||
raise ArgumentError, "cannot cancel streams without an id"
|
||||
end
|
||||
|
||||
def cancel_streams(%GenEvent{manager: manager, id: id}) do
|
||||
handlers = :gen_event.which_handlers(manager)
|
||||
|
||||
for {Enumerable.GenEvent, {handler_id, _}} = ref <- handlers,
|
||||
handler_id === id do
|
||||
:gen_event.delete_handler(manager, ref, :remove_handler)
|
||||
end
|
||||
|
||||
:ok
|
||||
end
|
||||
|
||||
@doc """
|
||||
Removes an event handler from the event `manager`.
|
||||
|
||||
The event manager will call `terminate/2` to terminate the event handler
|
||||
and return the callback value. If the specified event handler is not
|
||||
installed, the function returns `{:error, :module_not_found}`.
|
||||
"""
|
||||
@spec remove_handler(manager, handler, term) :: term | {:error, term}
|
||||
def remove_handler(manager, handler, args) do
|
||||
:gen_event.delete_handler(manager, handler, args)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Replaces an old event handler with a new one in the event `manager`.
|
||||
|
||||
First, the old event handler is deleted by calling `terminate/2` with
|
||||
the given `args1` and collects the return value. Then the new event handler
|
||||
is added and initiated by calling `init({args2, term}), where term is the
|
||||
return value of calling `terminate/2` in the old handler. This makes it
|
||||
possible to transfer information from one handler to another.
|
||||
|
||||
The new handler will be added even if the specified old event handler
|
||||
is not installed in which case `term = :error` or if the handler fails to
|
||||
terminate with a given reason.
|
||||
|
||||
If there was a linked connection between handler1 and a process pid, there
|
||||
will be a link connection between handle2 and pid instead. A new link in
|
||||
between the caller process and the new handler can also be set with by
|
||||
giving `link: true` as option. See `add_handler/4` for more information.
|
||||
|
||||
If `init/1` in the second handler returns a correct value, this function
|
||||
returns `:ok`.
|
||||
"""
|
||||
@spec swap_handler(manager, handler, term, handler, term, [link: boolean]) :: :ok | {:error, term}
|
||||
def swap_handler(manager, handler1, args1, handler2, args2, options \\ []) do
|
||||
case Keyword.get(options, :link, false) do
|
||||
true -> :gen_event.swap_sup_handler(manager, {handler1, args1}, {handler2, args2})
|
||||
false -> :gen_event.swap_handler(manager, {handler1, args1}, {handler2, args2})
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns a list of all event handlers installed in the `manager`.
|
||||
"""
|
||||
@spec which_handlers(manager) :: [handler]
|
||||
defdelegate which_handlers(manager), to: :gen_event
|
||||
|
||||
@doc """
|
||||
Terminates the event `manager`.
|
||||
|
||||
Before terminating, the event manager will call `terminate(:stop, ...)`
|
||||
for each installed event handler.
|
||||
"""
|
||||
@spec stop(manager) :: :ok
|
||||
defdelegate stop(manager), to: :gen_event
|
||||
end
|
||||
|
||||
defimpl Enumerable, for: GenEvent do
|
||||
use GenEvent
|
||||
|
||||
@doc false
|
||||
def init({mon_pid, pid, ref}) do
|
||||
# Tell the mon_pid we are good to go, and send self() so that this handler
|
||||
# can be removed later without using the managers name.
|
||||
send(mon_pid, {:UP, ref, self()})
|
||||
{:ok, {pid, ref}}
|
||||
end
|
||||
|
||||
@doc false
|
||||
def handle_event(event, {pid, ref} = state) do
|
||||
send pid, {ref, event}
|
||||
{:ok, state}
|
||||
end
|
||||
|
||||
def reduce(stream, acc, fun) do
|
||||
start_fun = fn() -> start(stream) end
|
||||
next_fun = &next(stream, &1)
|
||||
stop_fun = &stop(stream, &1)
|
||||
Stream.resource(start_fun, next_fun, stop_fun).(acc, fun)
|
||||
end
|
||||
|
||||
def count(_stream) do
|
||||
{:error, __MODULE__}
|
||||
end
|
||||
|
||||
def member?(_stream, _item) do
|
||||
{:error, __MODULE__}
|
||||
end
|
||||
|
||||
defp start(%{manager: manager, id: id, duration: duration} = stream) do
|
||||
{mon_pid, mon_ref} = add_handler(manager, id, duration)
|
||||
send mon_pid, {:UP, mon_ref, self()}
|
||||
|
||||
receive do
|
||||
# The subscription process gave us a go.
|
||||
{:UP, ^mon_ref, manager_pid} ->
|
||||
{mon_ref, manager_pid}
|
||||
# The subscription process died due to an abnormal reason.
|
||||
{:DOWN, ^mon_ref, _, _, reason} ->
|
||||
exit({reason, {__MODULE__, :start, [stream]}})
|
||||
end
|
||||
end
|
||||
|
||||
defp next(%{timeout: timeout} = stream, {mon_ref, _manager_pid} = acc) do
|
||||
receive do
|
||||
{^mon_ref, event} -> {event, acc}
|
||||
{:DOWN, ^mon_ref, _, _, :normal} -> nil
|
||||
{:DOWN, ^mon_ref, _, _, reason} ->
|
||||
exit({reason, {__MODULE__, :next, [stream, acc]}})
|
||||
after
|
||||
timeout ->
|
||||
exit({:timeout, {__MODULE__, :next, [stream, acc]}})
|
||||
end
|
||||
end
|
||||
|
||||
defp stop(%{id: id}, {mon_ref, manager_pid}) do
|
||||
remove_handler(mon_ref, manager_pid, id)
|
||||
flush_events(mon_ref)
|
||||
end
|
||||
|
||||
defp add_handler(manager, id, duration) do
|
||||
parent = self()
|
||||
|
||||
# The subscription is managed by another process, that dies if
|
||||
# the handler dies, and is killed when there is a need to remove
|
||||
# the subscription.
|
||||
spawn_monitor(fn ->
|
||||
# It is possible that the handler could be removed, and then the GenEvent
|
||||
# could exit before this process has exited normally. Because the removal
|
||||
# does not cause an unlinking this process would exit with the same
|
||||
# reason. Trapping exits ensures that no errors is raised in this case.
|
||||
Process.flag(:trap_exit, true)
|
||||
parent_ref = Process.monitor(parent)
|
||||
|
||||
# Receive the notification from the parent, unless it died.
|
||||
mon_ref = receive do
|
||||
{:UP, ref, ^parent} -> ref
|
||||
{:DOWN, ^parent_ref, _, _, _} -> exit(:normal)
|
||||
end
|
||||
|
||||
cancel = cancel_ref(id, mon_ref)
|
||||
:ok = :gen_event.add_sup_handler(manager, {__MODULE__, cancel},
|
||||
{self(), parent, mon_ref})
|
||||
|
||||
receive do
|
||||
# This message is already in the mailbox if we got this far.
|
||||
{:UP, ^mon_ref, manager_pid} ->
|
||||
send(parent, {:UP, mon_ref, manager_pid})
|
||||
receive do
|
||||
# If the parent died, we can exit normally.
|
||||
{:DOWN, ^parent_ref, _, _, _} ->
|
||||
exit(:normal)
|
||||
|
||||
# reason should be normal unless the handler is swapped.
|
||||
{:gen_event_EXIT, {__MODULE__, ^cancel}, reason} ->
|
||||
exit(reason)
|
||||
|
||||
# Exit if the manager dies, so the streamer is notified.
|
||||
{:EXIT, ^manager_pid, :noconnection} ->
|
||||
exit({:nodedown, node(manager_pid)})
|
||||
|
||||
{:EXIT, ^manager_pid, reason} ->
|
||||
exit(reason)
|
||||
after
|
||||
# Our time is over, notify the parent.
|
||||
duration -> exit(:normal)
|
||||
end
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
defp cancel_ref(nil, mon_ref), do: mon_ref
|
||||
defp cancel_ref(id, mon_ref), do: {id, mon_ref}
|
||||
|
||||
defp remove_handler(mon_ref, manager_pid, id) do
|
||||
Process.demonitor(mon_ref, [:flush])
|
||||
handler = {__MODULE__, cancel_ref(id, mon_ref)}
|
||||
# handler may nolonger be there, if it is the removal will cause the monitor
|
||||
# process to exit. If this returns successfuly then no more events will be
|
||||
# forwarded.
|
||||
_ = :gen_event.delete_handler(manager_pid, handler, :remove_handler)
|
||||
catch
|
||||
# Do not want to overide the exit reason of the mon_pid so catch errors.
|
||||
# However if the exit is due to a disconnection, exit because messages could
|
||||
# leak if the nodes are reconnected before the manager on the other node
|
||||
# removes the handler. In this case it is very likely that the mon_pid
|
||||
# exited with the same reason.
|
||||
:exit, reason when reason !== {:nodedown, node(manager_pid)} ->
|
||||
:ok
|
||||
end
|
||||
|
||||
defp flush_events(mon_ref) do
|
||||
receive do
|
||||
{^mon_ref, _} -> flush_events(mon_ref)
|
||||
after
|
||||
0 -> :ok
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -1,60 +1,5 @@
|
||||
defmodule GenEvent.Behaviour do
|
||||
@moduledoc """
|
||||
This module is a convenience for defining GenEvent callbacks in Elixir.
|
||||
|
||||
GenEvent is an OTP behaviour that encapsulates event handling functionality.
|
||||
|
||||
## Example
|
||||
|
||||
Below is an example of a GenEvent that stores notifications
|
||||
until they are fetched:
|
||||
|
||||
defmodule MyEventHandler do
|
||||
use GenEvent.Behaviour
|
||||
|
||||
# Callbacks
|
||||
|
||||
def init(_) do
|
||||
{:ok, []}
|
||||
end
|
||||
|
||||
def handle_event({:notification, x}, notifications) do
|
||||
{:ok, [x|notifications]}
|
||||
end
|
||||
|
||||
def handle_call(:notifications, notifications) do
|
||||
{:ok, Enum.reverse(notifications), []}
|
||||
end
|
||||
end
|
||||
|
||||
{:ok, pid} = :gen_event.start_link
|
||||
#=> {:ok,#PID<0.42.0>}
|
||||
|
||||
:gen_event.add_handler(pid, MyEventHandler, [])
|
||||
#=> :ok
|
||||
|
||||
:gen_event.notify(pid, {:notification, 1})
|
||||
#=> :ok
|
||||
|
||||
:gen_event.notify(pid, {:notification, 2})
|
||||
#=> :ok
|
||||
|
||||
:gen_event.call(pid, MyEventHandler, :notifications)
|
||||
#=> [1, 2]
|
||||
|
||||
:gen_event.call(pid, MyEventHandler, :notifications)
|
||||
#=> []
|
||||
|
||||
Notice we never call the server callbacks directly, they are called
|
||||
by OTP whenever we interact with the server.
|
||||
|
||||
Starting and sending messages to the GenEvent is done
|
||||
via Erlang's `:gen_event` module. For more information,
|
||||
please refer to the following:
|
||||
|
||||
* http://www.erlang.org/doc/man/gen_event.html
|
||||
* http://learnyousomeerlang.com/event-handlers
|
||||
"""
|
||||
@moduledoc false
|
||||
|
||||
@doc false
|
||||
defmacro __using__(_) do
|
||||
|
||||
@@ -0,0 +1,424 @@
|
||||
defmodule GenServer do
|
||||
@moduledoc """
|
||||
A behaviour module for implementing the server of a client-server relation.
|
||||
|
||||
A GenServer is a process as any other Elixir process and it can be used
|
||||
to keep state, execute code asynchronously and so on. The advantage of using
|
||||
a generic server process (GenServer) implemented using this module is that it
|
||||
will have a standard set of interface functions and include functionality for
|
||||
tracing and error reporting. It will also fit into a supervision tree.
|
||||
|
||||
## Example
|
||||
|
||||
The GenServer behaviour abstracts the common client-server interaction.
|
||||
Developer are only required to implement the callbacks and functionality they are
|
||||
interested in.
|
||||
|
||||
Let's start with a code example and then explore the available callbacks.
|
||||
Imagine we want a GenServer that works like a stack, allowing us to push
|
||||
and pop items:
|
||||
|
||||
defmodule Stack do
|
||||
use GenServer
|
||||
|
||||
# Callbacks
|
||||
|
||||
def handle_call(:pop, _from, [h|t]) do
|
||||
{:reply, h, t}
|
||||
end
|
||||
|
||||
def handle_cast({:push, item}, state) do
|
||||
{:noreply, [item|state]}
|
||||
end
|
||||
end
|
||||
|
||||
# Start the server
|
||||
{:ok, pid} = GenServer.start_link(Stack, [:hello])
|
||||
|
||||
# This is the client
|
||||
GenServer.call(pid, :pop)
|
||||
#=> :hello
|
||||
|
||||
GenServer.cast(pid, {:push, :world})
|
||||
#=> :ok
|
||||
|
||||
GenServer.call(pid, :pop)
|
||||
#=> :world
|
||||
|
||||
We start our `Stack` by calling `start_link/2`, passing the module
|
||||
with the server implementation and its initial argument (a list
|
||||
representing the stack containing the item `:hello`). We can primarily
|
||||
interact with the server by sending two types of messages. **call**
|
||||
messages expect a reply from the server (and is therefore synchronous)
|
||||
while **cast** messages do not.
|
||||
|
||||
Every time you do a `GenServer.call/2`, the client will send a message
|
||||
that must be handled by the `handle_call/3` callback in the GenServer.
|
||||
A `cast/2` message must be handled by `handle_cast/2`.
|
||||
|
||||
## Callbacks
|
||||
|
||||
There are 6 callbacks required to be implemented in a `GenServer`. By
|
||||
adding `use GenServer` to your module, Elixir will automatically define
|
||||
all 6 callbacks for you, leaving it up to you to implement the ones
|
||||
you want to customize. The callbacks are:
|
||||
|
||||
* `init(args)` - invoked when the server is started
|
||||
|
||||
It must return:
|
||||
|
||||
{:ok, state}
|
||||
{:ok, state, timeout}
|
||||
:ignore
|
||||
{:stop, reason}
|
||||
|
||||
* `handle_call(msg, {from, ref}, state)` and `handle_cast(msg, state)` -
|
||||
invoked to handle call (sync) and cast (async) messages.
|
||||
|
||||
It must return:
|
||||
|
||||
{:reply, reply, new_state}
|
||||
{:reply, reply, new_state, timeout}
|
||||
{:reply, reply, new_state, :hibernate}
|
||||
{:noreply, new_state}
|
||||
{:noreply, new_state, timeout}
|
||||
{:noreply, new_state, :hibernate}
|
||||
{:stop, reason, new_state}
|
||||
{:stop, reason, reply, new_state}
|
||||
|
||||
* `handle_info(msg, state)` - invoked to handle all other messages which
|
||||
are received by the process.
|
||||
|
||||
It must return:
|
||||
|
||||
{:noreply, state}
|
||||
{:noreply, state, timeout}
|
||||
{:stop, reason, state}
|
||||
|
||||
* `terminate(reason, state)` - called when the server is about to
|
||||
terminate, useful for cleaning up. It must return `:ok`
|
||||
|
||||
* `code_change(old_vsn, state, extra)` - called when the application
|
||||
code is being upgraded live (hot code swap).
|
||||
|
||||
It must return:
|
||||
|
||||
{:ok, new_state}
|
||||
{:error, reason}
|
||||
|
||||
## Names registering
|
||||
|
||||
Both `start_link/3` and `start/3` support the `GenServer` to register
|
||||
a name on start via the `:name` option. Registered names are also
|
||||
automatically clean up on termination. The supported values are:
|
||||
|
||||
* an atom - the GenServer is registered locally with the given name
|
||||
using `Process.register/2`;
|
||||
|
||||
* `{:global, term}`- the GenServer is registered globally with the given
|
||||
term using the functions in the `:global` module;
|
||||
|
||||
* `{:via, module, term}` - the GenServer is registered with the given
|
||||
mechanism and name. The `:via` option expects a module name to control
|
||||
the registration mechanism along side a name which can be any term;
|
||||
|
||||
For example, we could start and register our Stack server locally as follows:
|
||||
|
||||
# Start the server and register it locally with name MyStack
|
||||
{:ok, _} = GenServer.start_link(Stack, [:hello], name: MyStack)
|
||||
|
||||
# Now messages can be sent directly to MyStack
|
||||
GenServer.call(MyStack, :pop) #=> :hello
|
||||
|
||||
Once the server is started, the remaining functions in this module (`call/2`,
|
||||
`cast/2` and friends) will also accept an atom, or any `:global` or `:via`
|
||||
tuples. In general, the following formats are supported:
|
||||
|
||||
* a `pid`
|
||||
* an `atom` if the server is locally registered
|
||||
* `{atom, node}` if the server is locally registered at another node
|
||||
* `{:global, term}` if the server is globally registered
|
||||
* `{:via, module, name}` if the server is registered through an alternative registry
|
||||
|
||||
## Client / Server APIs
|
||||
|
||||
Although in the example above we have used `GenServer.start_link/2` and
|
||||
friends to directly start and communicate with the server, most of the
|
||||
time we don't call the `GenServer` functions directly, instead, we wrap
|
||||
them in functions too.
|
||||
|
||||
Here is a better implementation of our Stack module:
|
||||
|
||||
defmodule Stack do
|
||||
use GenServer
|
||||
|
||||
# Client
|
||||
|
||||
def start_link(default) do
|
||||
GenServer.start_link(__MODULE__, default)
|
||||
end
|
||||
|
||||
def push(pid, item) do
|
||||
GenServer.cast(pid, {:push, item})
|
||||
end
|
||||
|
||||
def pop(pid) do
|
||||
GenServer.call(pid, :pop)
|
||||
end
|
||||
|
||||
# Server (callbacks)
|
||||
|
||||
def handle_call(:pop, _from, [h|t]) do
|
||||
{:reply, h, t}
|
||||
end
|
||||
|
||||
def handle_call(request, from, state) do
|
||||
# Call the default implementation from GenServer
|
||||
super(request, from, state)
|
||||
end
|
||||
|
||||
def handle_cast({:push, item}, state) do
|
||||
{:noreply, [item|state]}
|
||||
end
|
||||
|
||||
def handle_cast(request, state) do
|
||||
super(request, state)
|
||||
end
|
||||
end
|
||||
|
||||
In practice, it is common to have both server and client functions in
|
||||
the same module. If the server and/or client implementations are growing
|
||||
complex, you may want to have them in different modules.
|
||||
|
||||
## Learn more
|
||||
|
||||
In case you desire to learn more about gen servers, Elixir getting started
|
||||
guides provide a tutorial-like introduction. The documentation and links
|
||||
in Erlang can also provide extra insight.
|
||||
|
||||
* http://elixir-lang.org/getting_started/mix/1.html
|
||||
* http://www.erlang.org/doc/man/gen_server.html
|
||||
* http://www.erlang.org/doc/design_principles/gen_server_concepts.html
|
||||
* http://learnyousomeerlang.com/clients-and-servers
|
||||
"""
|
||||
|
||||
@typedoc "Return values of `start*` functions"
|
||||
@type on_start :: {:ok, pid} | :ignore | {:error, {:already_started, pid} | term}
|
||||
|
||||
@typedoc "The GenServer name"
|
||||
@type name :: atom | {:global, term} | {:via, module, term}
|
||||
|
||||
@typedoc "Options used by the `start*` functions"
|
||||
@type options :: [debug: debug,
|
||||
name: name,
|
||||
timeout: timeout,
|
||||
spawn_opt: Process.spawn_opt]
|
||||
|
||||
@typedoc "debug options supported by the `start*` functions"
|
||||
@type debug :: [:trace | :log | :statistics | {:log_to_file, Path.t}]
|
||||
|
||||
@typedoc "The server reference"
|
||||
@type server :: pid | name | {atom, node}
|
||||
|
||||
@doc false
|
||||
defmacro __using__(_) do
|
||||
quote location: :keep do
|
||||
@behaviour :gen_server
|
||||
|
||||
@doc false
|
||||
def init(args) do
|
||||
{:ok, args}
|
||||
end
|
||||
|
||||
@doc false
|
||||
def handle_call(msg, _from, state) do
|
||||
{:stop, {:bad_call, msg}, state}
|
||||
end
|
||||
|
||||
@doc false
|
||||
def handle_info(_msg, state) do
|
||||
{:noreply, state}
|
||||
end
|
||||
|
||||
@doc false
|
||||
def handle_cast(msg, state) do
|
||||
{:stop, {:bad_cast, msg}, state}
|
||||
end
|
||||
|
||||
@doc false
|
||||
def terminate(_reason, _state) do
|
||||
:ok
|
||||
end
|
||||
|
||||
@doc false
|
||||
def code_change(_old, state, _extra) do
|
||||
{:ok, state}
|
||||
end
|
||||
|
||||
defoverridable [init: 1, handle_call: 3, handle_info: 2,
|
||||
handle_cast: 2, terminate: 2, code_change: 3]
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Starts a `GenServer` process linked to the current process.
|
||||
|
||||
This is often used to start the `GenServer` as part of a supervision tree.
|
||||
|
||||
Once the server is started, it calls the `init/1` function in the given `module`
|
||||
passing the given `args` to initialize it. To ensure a synchronized start-up
|
||||
procedure, this function does not return until `init/1` has returned.
|
||||
|
||||
## Options
|
||||
|
||||
The `:name` option is used for name registered as described in the module
|
||||
documentation. If the option `:timeout` option is present, the server is
|
||||
allowed to spend the given milliseconds initializing or it will be
|
||||
terminated and the start function will return `{:error, :timeout}`.
|
||||
|
||||
If the option `:debug` is present, the corresponding function in the
|
||||
[`:sys` module](http://www.erlang.org/doc/man/sys.html) will be invoked.
|
||||
|
||||
If the option `:spawn_opt` is present, the given options will be passed
|
||||
to the underlying process as in `Process.spawn/3`.
|
||||
|
||||
## Return values
|
||||
|
||||
If the server is successfully created and initialized the function returns
|
||||
`{:ok, pid}`, where pid is the pid of the server. If there already exists a
|
||||
process with the specified server name the function returns
|
||||
`{:error, {:already_started, pid}}` with the pid of that process.
|
||||
|
||||
If the `init/1` callback fails with reason, the function returns
|
||||
`{:error, reason}`. Otherwise, if it returns `{:stop, reason}`
|
||||
or `:ignore`, the process is terminated and the function returns
|
||||
`{:error, reason}` or `:ignore`, respectively.
|
||||
"""
|
||||
@spec start_link(module, any, options) :: on_start
|
||||
def start_link(module, args, options \\ []) when is_atom(module) and is_list(options) do
|
||||
do_start(:link, module, args, options)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Starts a `GenServer` process without links (outside of a supervision tree).
|
||||
|
||||
See `start_link/3` for more information.
|
||||
"""
|
||||
@spec start(module, any, options) :: on_start
|
||||
def start(module, args, options \\ []) when is_atom(module) and is_list(options) do
|
||||
do_start(:nolink, module, args, options)
|
||||
end
|
||||
|
||||
defp do_start(link, module, args, options) do
|
||||
case Keyword.pop(options, :name) do
|
||||
{nil, opts} ->
|
||||
:gen.start(:gen_server, link, module, args, opts)
|
||||
{atom, opts} when is_atom(atom) ->
|
||||
:gen.start(:gen_server, link, {:local, atom}, module, args, opts)
|
||||
{other, opts} when is_tuple(other) ->
|
||||
:gen.start(:gen_server, link, other, module, args, opts)
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Makes a synchronous call to the `server` and wait for its reply.
|
||||
|
||||
The client sends the given `request` to the server and waits until a reply
|
||||
arrives or a timeout occurs. `handle_call/3` will be called on the server
|
||||
to handle the request.
|
||||
|
||||
The server can be any of the values described in the `Name Registering`
|
||||
section of the module documentation.
|
||||
|
||||
## Timeouts
|
||||
|
||||
The `timeout` is an integer greater than zero which specifies how many
|
||||
milliseconds to wait for a reply, or the atom `:infinity` to wait
|
||||
indefinitely. The default value is 5000. If no reply is received within
|
||||
the specified time, the function call fails. If the caller catches the
|
||||
failure and continues running, and the server is just late with the reply,
|
||||
it may arrive at any time later into the caller's message queue. The caller
|
||||
must in this case be prepared for this and discard any such garbage messages
|
||||
that are two element tuples with a reference as the first element.
|
||||
"""
|
||||
@spec call(server, term, timeout) :: term
|
||||
def call(server, request, timeout \\ 5000) do
|
||||
:gen_server.call(server, request, timeout)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Sends an asynchronous request to the `server`.
|
||||
|
||||
This function returns `:ok` immediately, ignoring if the destination node
|
||||
or server does not exist. `handle_cast/2` will be called on the server
|
||||
to handle the request.
|
||||
"""
|
||||
@spec cast(server, term) :: :ok
|
||||
defdelegate cast(server, request), to: :gen_server
|
||||
|
||||
@doc """
|
||||
Casts all servers locally registered as `name` at the specified nodes.
|
||||
|
||||
The function returns immediately and ignores nodes that do not exist, or where the
|
||||
server name does not exist.
|
||||
|
||||
See `multi_call/4` for more information.
|
||||
"""
|
||||
@spec abcast([node], name :: atom, term) :: :abcast
|
||||
def abcast(nodes \\ nodes(), name, request) do
|
||||
:gen_server.abcast(nodes, name, request)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Calls all servers locally registered as `name` at the specified `nodes`.
|
||||
|
||||
The `request` is first sent to every node and then we wait for the
|
||||
replies. This function returns a tuple containing the node and its reply
|
||||
as first element and all bad nodes as second element. The bad nodes is a
|
||||
list of nodes that either did not exist, or where a server with the given
|
||||
`name` did not exist or did not reply.
|
||||
|
||||
Nodes is a list of node names to which the request is sent. The default
|
||||
value is the list of all known nodes.
|
||||
|
||||
To avoid that late answers (after the timeout) pollute the caller's message
|
||||
queue, a middleman process is used to do the actual calls. Late answers will
|
||||
then be discarded when they arrive to a terminated process.
|
||||
"""
|
||||
@spec multi_call([node], name :: atom, term, timeout) ::
|
||||
{replies :: [{node, term}], bad_nodes :: [node]}
|
||||
def multi_call(nodes \\ nodes(), name, request, timeout \\ :infinity) do
|
||||
:gen_server.multi_call(nodes, name, request, timeout)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Replies a client.
|
||||
|
||||
This function can be used by a server to explicitly send a reply to a
|
||||
client that called `call/3` or `multi_call/4`. When the reply cannot be
|
||||
defined in the return value of `handle_call/3`.
|
||||
|
||||
The `client` must be the `from` argument (the second argument) received
|
||||
in `handle_call/3` callbacks. Reply is an arbitrary term which will be
|
||||
given back to the client as the return value of the call.
|
||||
|
||||
This function always return `:ok`.
|
||||
"""
|
||||
@spec reply({pid, reference}, term) :: :ok
|
||||
def reply(client, reply)
|
||||
|
||||
def reply({to, tag}, reply) do
|
||||
try do
|
||||
send(to, {tag, reply})
|
||||
:ok
|
||||
catch
|
||||
_, _ -> :ok
|
||||
end
|
||||
end
|
||||
|
||||
@compile {:inline, [nodes: 0]}
|
||||
|
||||
defp nodes do
|
||||
[node()|:erlang.nodes()]
|
||||
end
|
||||
end
|
||||
@@ -1,87 +1,5 @@
|
||||
defmodule GenServer.Behaviour do
|
||||
@moduledoc """
|
||||
This module is a convenience for defining GenServer callbacks in Elixir.
|
||||
|
||||
A server is responsible for reacting to messages received from a client.
|
||||
A GenServer is an OTP behaviour that encapsulates common server
|
||||
functionalities.
|
||||
|
||||
## Example
|
||||
|
||||
Below is an example of a GenServer that pushes and pops items
|
||||
onto a stack:
|
||||
|
||||
defmodule MyServer do
|
||||
use GenServer.Behaviour
|
||||
|
||||
# Callbacks
|
||||
|
||||
def handle_call(:pop, _from, [h|t]) do
|
||||
{:reply, h, t}
|
||||
end
|
||||
|
||||
def handle_call(request, from, config) do
|
||||
# Call the default implementation from GenServer.Behaviour
|
||||
super(request, from, config)
|
||||
end
|
||||
|
||||
def handle_cast({:push, item}, config) do
|
||||
{:noreply, [item|config]}
|
||||
end
|
||||
|
||||
def handle_cast(request, config) do
|
||||
super(request, config)
|
||||
end
|
||||
end
|
||||
|
||||
{:ok, pid} = :gen_server.start_link(MyServer, [:hello], [])
|
||||
|
||||
:gen_server.call(pid, :pop)
|
||||
#=> :hello
|
||||
|
||||
:gen_server.cast(pid, {:push, :world})
|
||||
#=> :ok
|
||||
|
||||
:gen_server.call(pid, :pop)
|
||||
#=> :world
|
||||
|
||||
Notice we never call the server callbacks directly, they are called
|
||||
by OTP whenever we interact with the server. **cast** messages are
|
||||
asynchronous while **call** ones are synchronous. For a
|
||||
GenServer, there are 8 different values a callback such as
|
||||
`handle_call` or `handle_cast` can return:
|
||||
|
||||
{:reply, reply, new_state}
|
||||
{:reply, reply, new_state, timeout}
|
||||
{:reply, reply, new_state, :hibernate}
|
||||
{:noreply, new_state}
|
||||
{:noreply, new_state, timeout}
|
||||
{:noreply, new_state, :hibernate}
|
||||
{:stop, reason, new_state}
|
||||
{:stop, reason, reply, new_state}
|
||||
|
||||
There are 6 callbacks required to be implemented in a GenServer. The
|
||||
`GenServer.Behaviour` module defines all of them automatically, but
|
||||
allows us to customize the ones we need. The required callbacks are:
|
||||
|
||||
* `init(args)` - invoked when the server is started;
|
||||
* `handle_call(msg, from, state)` - invoked to handle call messages;
|
||||
* `handle_cast(msg, state)` - invoked to handle cast messages;
|
||||
* `handle_info(msg, state)` - handle all other messages which are
|
||||
normally received by processes;
|
||||
* `terminate(reason, state)` - called when the server is about to
|
||||
terminate, useful for cleaning up;
|
||||
* `code_change(old_vsn, state, extra)` - called when the application
|
||||
code is being upgraded live (hot code swap);
|
||||
|
||||
Starting and sending messages to the GenServer is done
|
||||
via Erlang's `:gen_server` module. For more information,
|
||||
please refer to the following:
|
||||
|
||||
* http://www.erlang.org/doc/man/gen_server.html
|
||||
* http://www.erlang.org/doc/design_principles/gen_server_concepts.html
|
||||
* http://learnyousomeerlang.com/clients-and-servers
|
||||
"""
|
||||
@moduledoc false
|
||||
|
||||
@doc false
|
||||
defmacro __using__(_) do
|
||||
|
||||
+21
-42
@@ -43,7 +43,7 @@ defmodule HashDict do
|
||||
|
||||
def update!(%HashDict{root: root, size: size} = dict, key, fun) when is_function(fun, 1) do
|
||||
{root, counter} = do_update(root, key, fn -> raise KeyError, key: key, term: dict end,
|
||||
fun, key_hash(key))
|
||||
fun, key_hash(key))
|
||||
%HashDict{root: root, size: size + counter}
|
||||
end
|
||||
|
||||
@@ -59,14 +59,14 @@ defmodule HashDict do
|
||||
def delete(dict, key) do
|
||||
case dict_delete(dict, key) do
|
||||
{dict, _value} -> dict
|
||||
:error -> dict
|
||||
:error -> dict
|
||||
end
|
||||
end
|
||||
|
||||
def pop(dict, key, default \\ nil) do
|
||||
case dict_delete(dict, key) do
|
||||
{dict, value} -> {value, dict}
|
||||
:error -> {default, dict}
|
||||
:error -> {default, dict}
|
||||
end
|
||||
end
|
||||
|
||||
@@ -83,27 +83,6 @@ defmodule HashDict do
|
||||
end)
|
||||
end
|
||||
|
||||
def split(dict, keys) do
|
||||
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
|
||||
|
||||
def merge(%HashDict{size: size1} = dict1, %HashDict{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 merge(%HashDict{} = dict1, %HashDict{} = dict2, callback) do
|
||||
reduce(dict2, {:cont, dict1}, fn {k, v2}, acc ->
|
||||
{:cont, update(acc, k, v2, &callback.(k, &1, v2))}
|
||||
end) |> elem(1)
|
||||
end
|
||||
|
||||
## General helpers
|
||||
|
||||
defp dict_delete(%HashDict{root: root, size: size}, key) do
|
||||
@@ -129,17 +108,17 @@ defmodule HashDict do
|
||||
index = key_mask(hash)
|
||||
case elem(node, index) do
|
||||
[] ->
|
||||
{set_elem(node, index, [key|value]), 1}
|
||||
{put_elem(node, index, [key|value]), 1}
|
||||
[^key|_] ->
|
||||
{set_elem(node, index, [key|value]), 0}
|
||||
{put_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}
|
||||
n = put_elem(@node_template, key_mask(key_shift(hash)), [key|value])
|
||||
{put_elem(node, index, {k, v, n}), 1}
|
||||
{^key, _, n} ->
|
||||
{set_elem(node, index, {key, value, n}), 0}
|
||||
{put_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}
|
||||
{put_elem(node, index, {k, v, n}), counter}
|
||||
end
|
||||
end
|
||||
|
||||
@@ -147,17 +126,17 @@ defmodule HashDict do
|
||||
index = key_mask(hash)
|
||||
case elem(node, index) do
|
||||
[] ->
|
||||
{set_elem(node, index, [key|initial.()]), 1}
|
||||
{put_elem(node, index, [key|initial.()]), 1}
|
||||
[^key|value] ->
|
||||
{set_elem(node, index, [key|fun.(value)]), 0}
|
||||
{put_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}
|
||||
n = put_elem(@node_template, key_mask(key_shift(hash)), [key|initial.()])
|
||||
{put_elem(node, index, {k, v, n}), 1}
|
||||
{^key, value, n} ->
|
||||
{set_elem(node, index, {key, fun.(value), n}), 0}
|
||||
{put_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}
|
||||
{put_elem(node, index, {k, v, n}), counter}
|
||||
end
|
||||
end
|
||||
|
||||
@@ -167,17 +146,17 @@ defmodule HashDict do
|
||||
[] ->
|
||||
:error
|
||||
[^key|value] ->
|
||||
{set_elem(node, index, []), value}
|
||||
{put_elem(node, index, []), value}
|
||||
[_|_] ->
|
||||
:error
|
||||
{^key, value, n} ->
|
||||
{set_elem(node, index, do_compact_node(n)), value}
|
||||
{put_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}
|
||||
{put_elem(node, index, [k|v]), value}
|
||||
{n, value} ->
|
||||
{set_elem(node, index, {k, v, n}), value}
|
||||
{put_elem(node, index, {k, v, n}), value}
|
||||
:error ->
|
||||
:error
|
||||
end
|
||||
@@ -188,12 +167,12 @@ defmodule HashDict do
|
||||
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
|
||||
case put_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))}
|
||||
{k, v, put_elem(node, unquote(index), do_compact_node(n))}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
+10
-10
@@ -134,15 +134,15 @@ defmodule HashSet do
|
||||
index = key_mask(hash)
|
||||
case elem(node, index) do
|
||||
[] ->
|
||||
{set_elem(node, index, [term]), 1}
|
||||
{put_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}
|
||||
n = put_elem(@node_template, key_mask(key_shift(hash)), [term])
|
||||
{put_elem(node, index, [t|n]), 1}
|
||||
[t|n] ->
|
||||
{n, counter} = do_put(n, term, key_shift(hash))
|
||||
{set_elem(node, index, [t|n]), counter}
|
||||
{put_elem(node, index, [t|n]), counter}
|
||||
end
|
||||
end
|
||||
|
||||
@@ -152,17 +152,17 @@ defmodule HashSet do
|
||||
[] ->
|
||||
:error
|
||||
[^term] ->
|
||||
{:ok, set_elem(node, index, [])}
|
||||
{:ok, put_elem(node, index, [])}
|
||||
[_] ->
|
||||
:error
|
||||
[^term|n] ->
|
||||
{:ok, set_elem(node, index, do_compact_node(n))}
|
||||
{:ok, put_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, put_elem(node, index, [t])}
|
||||
{:ok, n} ->
|
||||
{:ok, set_elem(node, index, [t|n])}
|
||||
{:ok, put_elem(node, index, [t|n])}
|
||||
:error ->
|
||||
:error
|
||||
end
|
||||
@@ -173,12 +173,12 @@ defmodule HashSet do
|
||||
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
|
||||
case put_elem(node, unquote(index), []) do
|
||||
@node_template -> [t]
|
||||
n -> [t|n]
|
||||
end
|
||||
[t|n] ->
|
||||
[t|set_elem(node, unquote(index), do_compact_node(n))]
|
||||
[t|put_elem(node, unquote(index), do_compact_node(n))]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
+97
-26
@@ -70,7 +70,7 @@ defimpl Inspect, for: Atom do
|
||||
def inspect(:""), do: ":\"\""
|
||||
|
||||
def inspect(atom) do
|
||||
binary = atom_to_binary(atom)
|
||||
binary = Atom.to_string(atom)
|
||||
|
||||
cond do
|
||||
valid_ref_identifier?(binary) ->
|
||||
@@ -142,7 +142,7 @@ end
|
||||
defimpl Inspect, for: BitString do
|
||||
def inspect(thing, %Inspect.Opts{binaries: bins} = opts) when is_binary(thing) do
|
||||
if bins == :as_strings or (bins == :infer and String.printable?(thing)) do
|
||||
<< ?", escape(thing, ?") :: binary, ?" >>
|
||||
<<?", escape(thing, ?") :: binary, ?">>
|
||||
else
|
||||
inspect_bitstring(thing, opts)
|
||||
end
|
||||
@@ -202,19 +202,42 @@ defimpl Inspect, for: BitString do
|
||||
else
|
||||
<< byte :: size(8), h :: binary >> = head
|
||||
t = << h :: binary, t :: binary >>
|
||||
escape(t, char, << binary :: binary, octify(byte) :: binary >>)
|
||||
escape(t, char, << binary :: binary, escape_char(byte) :: binary >>)
|
||||
end
|
||||
end
|
||||
defp escape(<<h, t :: binary>>, char, binary) do
|
||||
escape(t, char, << binary :: binary, octify(h) :: binary >>)
|
||||
escape(t, char, << binary :: binary, escape_char(h) :: binary >>)
|
||||
end
|
||||
defp escape(<<>>, _char, binary), do: binary
|
||||
|
||||
|
||||
@doc false
|
||||
# also used by Regex
|
||||
def escape_char(char) when char in ?\000..?\377,
|
||||
do: octify(char)
|
||||
|
||||
def escape_char(char), do: hexify(char)
|
||||
|
||||
defp octify(byte) do
|
||||
<< hi :: size(2), mi :: size(3), lo :: size(3) >> = << byte >>
|
||||
<< ?\\, ?0 + hi, ?0 + mi, ?0 + lo >>
|
||||
end
|
||||
|
||||
defp hexify(char) when char < 0x10000 do
|
||||
<<a::4, b::4, c::4, d::4>> = <<char::size(16)>>
|
||||
<<?\\, ?x, ?{, to_hex(a), to_hex(b), to_hex(c), to_hex(d), ?}>>
|
||||
end
|
||||
|
||||
defp hexify(char) when char < 0x1000000 do
|
||||
<<a::4, b::4, c::4, d::4, e::4, f::4>> = <<char::size(24)>>
|
||||
<<?\\, ?x, ?{, to_hex(a), to_hex(b), to_hex(c),
|
||||
to_hex(d), to_hex(e), to_hex(f), ?}>>
|
||||
end
|
||||
|
||||
defp to_hex(c) when c in 0..9, do: ?0+c
|
||||
defp to_hex(c) when c in 10..15, do: ?a+c-10
|
||||
|
||||
|
||||
defp append(<<h, t :: binary>>, binary), do: append(t, << binary :: binary, h >>)
|
||||
defp append(<<>>, binary), do: binary
|
||||
|
||||
@@ -229,11 +252,11 @@ defimpl Inspect, for: BitString do
|
||||
end
|
||||
|
||||
defp each_bit(<<h, t :: bitstring>>, counter, acc) when t != <<>> do
|
||||
each_bit(t, decrement(counter), acc <> integer_to_binary(h) <> ", ")
|
||||
each_bit(t, decrement(counter), acc <> Integer.to_string(h) <> ", ")
|
||||
end
|
||||
|
||||
defp each_bit(<<h :: size(8)>>, _counter, acc) do
|
||||
acc <> integer_to_binary(h)
|
||||
acc <> Integer.to_string(h)
|
||||
end
|
||||
|
||||
defp each_bit(<<>>, _counter, acc) do
|
||||
@@ -243,7 +266,7 @@ defimpl Inspect, for: BitString do
|
||||
defp each_bit(bitstring, _counter, acc) do
|
||||
size = bit_size(bitstring)
|
||||
<<h :: size(size)>> = bitstring
|
||||
acc <> integer_to_binary(h) <> "::size(" <> integer_to_binary(size) <> ")"
|
||||
acc <> Integer.to_string(h) <> "::size(" <> Integer.to_string(size) <> ")"
|
||||
end
|
||||
|
||||
defp decrement(:infinity), do: :infinity
|
||||
@@ -275,7 +298,7 @@ defimpl Inspect, for: List do
|
||||
def inspect(thing, %Inspect.Opts{char_lists: lists} = opts) do
|
||||
cond do
|
||||
lists == :as_char_lists or (lists == :infer and printable?(thing)) ->
|
||||
<< ?', Inspect.BitString.escape(String.from_char_data!(thing), ?') :: binary, ?' >>
|
||||
<< ?', Inspect.BitString.escape(IO.chardata_to_string(thing), ?') :: binary, ?' >>
|
||||
keyword?(thing) ->
|
||||
surround_many("[", thing, "]", opts.limit, &keyword(&1, opts))
|
||||
true ->
|
||||
@@ -291,7 +314,7 @@ defimpl Inspect, for: List do
|
||||
end
|
||||
|
||||
def keyword?([{key, _value} | rest]) when is_atom(key) do
|
||||
case atom_to_list(key) do
|
||||
case Atom.to_char_list(key) do
|
||||
'Elixir.' ++ _ -> false
|
||||
_ -> keyword?(rest)
|
||||
end
|
||||
@@ -329,14 +352,14 @@ defimpl Inspect, for: Tuple do
|
||||
if opts.records do
|
||||
record_inspect(tuple, opts)
|
||||
else
|
||||
surround_many("{", tuple_to_list(tuple), "}", opts.limit, &to_doc(&1, opts))
|
||||
surround_many("{", Tuple.to_list(tuple), "}", opts.limit, &to_doc(&1, opts))
|
||||
end
|
||||
end
|
||||
|
||||
## Helpers
|
||||
|
||||
defp record_inspect(record, opts) do
|
||||
[name|tail] = tuple_to_list(record)
|
||||
[name|tail] = Tuple.to_list(record)
|
||||
|
||||
if is_atom(name) && (fields = record_fields(name)) && (length(fields) == size(record) - 1) do
|
||||
surround_record(name, fields, tail, opts)
|
||||
@@ -346,7 +369,7 @@ defimpl Inspect, for: Tuple do
|
||||
end
|
||||
|
||||
defp record_fields(name) do
|
||||
case atom_to_binary(name) do
|
||||
case Atom.to_string(name) do
|
||||
"Elixir." <> _ ->
|
||||
try do
|
||||
name.__record__(:fields)
|
||||
@@ -365,7 +388,7 @@ defimpl Inspect, for: Tuple do
|
||||
end
|
||||
|
||||
defp zip_fields([{key, _}|tk], [value|tv]) do
|
||||
case atom_to_binary(key) do
|
||||
case Atom.to_string(key) do
|
||||
"_" <> _ -> zip_fields(tk, tv)
|
||||
key -> [{key, value}|zip_fields(tk, tv)]
|
||||
end
|
||||
@@ -408,20 +431,68 @@ end
|
||||
|
||||
defimpl Inspect, for: Integer do
|
||||
def inspect(thing, _opts) do
|
||||
integer_to_binary(thing)
|
||||
Integer.to_string(thing)
|
||||
end
|
||||
end
|
||||
|
||||
defimpl Inspect, for: Float do
|
||||
def inspect(thing, _opts) do
|
||||
iodata_to_binary(:io_lib_format.fwrite_g(thing))
|
||||
IO.iodata_to_binary(:io_lib_format.fwrite_g(thing))
|
||||
end
|
||||
end
|
||||
|
||||
defimpl Inspect, for: Regex do
|
||||
def inspect(regex, opts) do
|
||||
concat ["~r", to_doc(regex.source, opts), regex.opts]
|
||||
def inspect(regex, _opts) do
|
||||
delim =?/
|
||||
concat ["~r",
|
||||
<<delim, escape(regex.source, delim)::binary, delim>>,
|
||||
regex.opts]
|
||||
end
|
||||
|
||||
|
||||
defp escape(bin, term),
|
||||
do: escape(bin, <<>>, term)
|
||||
|
||||
defp escape(<<?\\, term>> <> rest, buf, term),
|
||||
do: escape(rest, buf <> <<?\\, term>>, term)
|
||||
|
||||
defp escape(<<term>> <> rest, buf, term),
|
||||
do: escape(rest, buf <> <<?\\, term>>, term)
|
||||
|
||||
# the list of characters is from `String.printable?` impl
|
||||
# minus characters treated specially by regex: \s, \d, \b, \e
|
||||
|
||||
defp escape(<<?\n>> <> rest, buf, term),
|
||||
do: escape(rest, <<buf::binary, ?\\, ?n>>, term)
|
||||
|
||||
defp escape(<<?\r>> <> rest, buf, term),
|
||||
do: escape(rest, <<buf::binary, ?\\, ?r>>, term)
|
||||
|
||||
defp escape(<<?\t>> <> rest, buf, term),
|
||||
do: escape(rest, <<buf::binary, ?\\, ?t>>, term)
|
||||
|
||||
defp escape(<<?\v>> <> rest, buf, term),
|
||||
do: escape(rest, <<buf::binary, ?\\, ?v>>, term)
|
||||
|
||||
defp escape(<<?\f>> <> rest, buf, term),
|
||||
do: escape(rest, <<buf::binary, ?\\, ?f>>, term)
|
||||
|
||||
defp escape(<<?\a>> <> rest, buf, term),
|
||||
do: escape(rest, <<buf::binary, ?\\, ?a>>, term)
|
||||
|
||||
defp escape(<<c::utf8>> <> rest, buf, term) do
|
||||
charstr = <<c::utf8>>
|
||||
if String.printable?(charstr) and not c in [?\d, ?\b, ?\e] do
|
||||
escape(rest, buf <> charstr, term)
|
||||
else
|
||||
escape(rest, buf <> Inspect.BitString.escape_char(c), term)
|
||||
end
|
||||
end
|
||||
|
||||
defp escape(<<c>> <> rest, buf, term),
|
||||
do: escape(rest, <<buf::binary, Inspect.BitString.escape_char(c)>>, term)
|
||||
|
||||
defp escape(<<>>, buf, _), do: buf
|
||||
end
|
||||
|
||||
defimpl Inspect, for: Function do
|
||||
@@ -432,7 +503,7 @@ defimpl Inspect, for: Function do
|
||||
if fun_info[:type] == :external and fun_info[:env] == [] do
|
||||
"&#{Inspect.Atom.inspect(mod)}.#{fun_info[:name]}/#{fun_info[:arity]}"
|
||||
else
|
||||
case atom_to_list(mod) do
|
||||
case Atom.to_char_list(mod) do
|
||||
'elixir_compiler_' ++ _ ->
|
||||
if function_exported?(mod, :__RELATIVE__, 0) do
|
||||
"#Function<#{uniq(fun_info)} in file:#{mod.__RELATIVE__}>"
|
||||
@@ -455,7 +526,7 @@ defimpl Inspect, for: Function do
|
||||
end
|
||||
|
||||
defp extract_name(name) do
|
||||
name = atom_to_binary(name)
|
||||
name = Atom.to_string(name)
|
||||
case :binary.split(name, "-", [:global]) do
|
||||
["", name | _] -> "." <> name
|
||||
_ -> "." <> name
|
||||
@@ -463,27 +534,27 @@ defimpl Inspect, for: Function do
|
||||
end
|
||||
|
||||
defp uniq(fun_info) do
|
||||
integer_to_binary(fun_info[:new_index]) <> "." <>
|
||||
integer_to_binary(fun_info[:uniq])
|
||||
Integer.to_string(fun_info[:new_index]) <> "." <>
|
||||
Integer.to_string(fun_info[:uniq])
|
||||
end
|
||||
end
|
||||
|
||||
defimpl Inspect, for: PID do
|
||||
def inspect(pid, _opts) do
|
||||
"#PID" <> iodata_to_binary(:erlang.pid_to_list(pid))
|
||||
"#PID" <> IO.iodata_to_binary(:erlang.pid_to_list(pid))
|
||||
end
|
||||
end
|
||||
|
||||
defimpl Inspect, for: Port do
|
||||
def inspect(port, _opts) do
|
||||
iodata_to_binary :erlang.port_to_list(port)
|
||||
IO.iodata_to_binary :erlang.port_to_list(port)
|
||||
end
|
||||
end
|
||||
|
||||
defimpl Inspect, for: Reference do
|
||||
def inspect(ref, _opts) do
|
||||
'#Ref' ++ rest = :erlang.ref_to_list(ref)
|
||||
"#Reference" <> iodata_to_binary(rest)
|
||||
"#Reference" <> IO.iodata_to_binary(rest)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -496,8 +567,8 @@ defimpl Inspect, for: Any do
|
||||
else
|
||||
dunder ->
|
||||
if :maps.keys(dunder) == :maps.keys(map) do
|
||||
Inspect.Map.inspect(:maps.remove(:__struct__, map),
|
||||
Inspect.Atom.inspect(struct, opts), opts)
|
||||
pruned = :maps.remove(:__exception__, :maps.remove(:__struct__, map))
|
||||
Inspect.Map.inspect(pruned, Inspect.Atom.inspect(struct, opts), opts)
|
||||
else
|
||||
Inspect.Map.inspect(map, opts)
|
||||
end
|
||||
|
||||
@@ -114,19 +114,25 @@ defmodule Inspect.Algebra do
|
||||
|
||||
@type t :: :doc_nil | :doc_line | doc_cons | doc_nest | doc_break | doc_group | binary
|
||||
|
||||
require Record
|
||||
|
||||
@typep doc_cons :: {:doc_cons, t, t}
|
||||
Record.defrecordp :doc_cons, left: :doc_nil, right: :doc_nil
|
||||
defmacrop doc_cons(left, right) do
|
||||
quote do: {:doc_cons, unquote(left), unquote(right)}
|
||||
end
|
||||
|
||||
@typep doc_nest :: {:doc_nest, non_neg_integer, t}
|
||||
Record.defrecordp :doc_nest, indent: 1, doc: :doc_nil
|
||||
@typep doc_nest :: {:doc_nest, t, non_neg_integer}
|
||||
defmacrop doc_nest(doc, indent) do
|
||||
quote do: {:doc_nest, unquote(doc), unquote(indent) }
|
||||
end
|
||||
|
||||
@typep doc_break :: {:doc_break, binary}
|
||||
Record.defrecordp :doc_break, str: " "
|
||||
defmacrop doc_break(break) do
|
||||
quote do: {:doc_break, unquote(break)}
|
||||
end
|
||||
|
||||
@typep doc_group :: {:doc_group, t}
|
||||
Record.defrecordp :doc_group, doc: :doc_nil
|
||||
defmacrop doc_group(group) do
|
||||
quote do: {:doc_group, unquote(group)}
|
||||
end
|
||||
|
||||
defmacrop is_doc(doc) do
|
||||
if Macro.Env.in_guard?(__CALLER__) do
|
||||
@@ -154,17 +160,13 @@ defmodule Inspect.Algebra do
|
||||
according to the inspect protocol.
|
||||
"""
|
||||
@spec to_doc(any, Inspect.Opts.t) :: t
|
||||
def to_doc(arg, %Inspect.Opts{} = opts) when is_record(arg) do
|
||||
# IO.write :stderr, "inspect records is deprecated (just remove this code when protocols+records are removed)\n#{Exception.format_stacktrace}"
|
||||
def to_doc(arg, %Inspect.Opts{} = opts) when is_tuple(arg) and is_atom(elem(arg, 0)) do
|
||||
# Remove this code when protocols+records are removed"
|
||||
if opts.records do
|
||||
try do
|
||||
Inspect.inspect(arg, opts)
|
||||
rescue
|
||||
e ->
|
||||
res = Inspect.Tuple.inspect(arg, opts)
|
||||
raise ArgumentError, message:
|
||||
"Got #{inspect e.__record__(:name)} with message " <>
|
||||
"#{Exception.message(e)} while inspecting #{pretty(res, opts.width)}"
|
||||
_ -> Inspect.Tuple.inspect(arg, opts)
|
||||
end
|
||||
else
|
||||
Inspect.Tuple.inspect(arg, opts)
|
||||
@@ -178,8 +180,8 @@ defmodule Inspect.Algebra do
|
||||
rescue
|
||||
e ->
|
||||
res = Inspect.Map.inspect(map, opts)
|
||||
raise ArgumentError, message:
|
||||
"Got #{inspect e.__record__(:name)} with message " <>
|
||||
raise ArgumentError,
|
||||
"Got #{inspect e.__struct__} with message " <>
|
||||
"\"#{Exception.message(e)}\" while inspecting #{pretty(res, opts.width)}"
|
||||
end
|
||||
else
|
||||
@@ -217,7 +219,7 @@ defmodule Inspect.Algebra do
|
||||
"""
|
||||
@spec concat(t, t) :: doc_cons
|
||||
def concat(x, y) when is_doc(x) and is_doc(y) do
|
||||
doc_cons(left: x, right: y)
|
||||
doc_cons(x, y)
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -245,7 +247,7 @@ defmodule Inspect.Algebra do
|
||||
end
|
||||
|
||||
def nest(x, i) when is_doc(x) and is_integer(i) do
|
||||
doc_nest(indent: i, doc: x)
|
||||
doc_nest(x, i)
|
||||
end
|
||||
|
||||
@doc ~S"""
|
||||
@@ -270,10 +272,10 @@ defmodule Inspect.Algebra do
|
||||
|
||||
"""
|
||||
@spec break(binary) :: doc_break
|
||||
def break(s) when is_binary(s), do: doc_break(str: s)
|
||||
def break(s) when is_binary(s), do: doc_break(s)
|
||||
|
||||
@spec break() :: doc_break
|
||||
def break(), do: doc_break(str: @break)
|
||||
def break(), do: doc_break(@break)
|
||||
|
||||
@doc """
|
||||
Inserts a break between two docs. See `break/1` for more info.
|
||||
@@ -317,7 +319,7 @@ defmodule Inspect.Algebra do
|
||||
"""
|
||||
@spec group(t) :: doc_group
|
||||
def group(d) when is_doc(d) do
|
||||
doc_group(doc: d)
|
||||
doc_group(d)
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -393,15 +395,15 @@ defmodule Inspect.Algebra do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> doc = Inspect.Algebra.surround_many("[", Enum.to_list(1..5), "]", :infinity, &integer_to_binary(&1))
|
||||
iex> doc = Inspect.Algebra.surround_many("[", Enum.to_list(1..5), "]", :infinity, &Integer.to_string(&1))
|
||||
iex> Inspect.Algebra.pretty(doc, 5)
|
||||
"[1,\n 2,\n 3,\n 4,\n 5]"
|
||||
|
||||
iex> doc = Inspect.Algebra.surround_many("[", Enum.to_list(1..5), "]", 3, &integer_to_binary(&1))
|
||||
iex> doc = Inspect.Algebra.surround_many("[", Enum.to_list(1..5), "]", 3, &Integer.to_string(&1))
|
||||
iex> Inspect.Algebra.pretty(doc, 20)
|
||||
"[1, 2, 3, ...]"
|
||||
|
||||
iex> doc = Inspect.Algebra.surround_many("[", Enum.to_list(1..5), "]", 3, &integer_to_binary(&1), "!")
|
||||
iex> doc = Inspect.Algebra.surround_many("[", Enum.to_list(1..5), "]", 3, &Integer.to_string(&1), "!")
|
||||
iex> Inspect.Algebra.pretty(doc, 20)
|
||||
"[1! 2! 3! ...]"
|
||||
|
||||
@@ -451,7 +453,7 @@ defmodule Inspect.Algebra do
|
||||
"""
|
||||
@spec pretty(t, non_neg_integer | :infinity) :: binary
|
||||
def pretty(d, w) do
|
||||
sdoc = format w, 0, [{0, default_mode(w), doc_group(doc: d)}]
|
||||
sdoc = format w, 0, [{0, default_mode(w), doc_group(d)}]
|
||||
render(sdoc)
|
||||
end
|
||||
|
||||
@@ -465,28 +467,28 @@ defmodule Inspect.Algebra do
|
||||
|
||||
@doc false
|
||||
@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])
|
||||
def fits?(w, [{i, _, doc_group(doc: x)} | t]), do: fits?(w, [{i, :flat, x} | t])
|
||||
def fits?(w, [{_, _, s} | t]) when is_binary(s), do: fits?((w - byte_size s), t)
|
||||
def fits?(w, [{_, :flat, doc_break(str: s)} | t]), do: fits?((w - byte_size s), t)
|
||||
def fits?(_, [{_, :break, doc_break(str: _)} | _]), do: true
|
||||
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(x, y)} | t]), do: fits?(w, [{i, m, x} | [{i, m, y} | t]])
|
||||
def fits?(w, [{i, m, doc_nest(x, j)} | t]), do: fits?(w, [{i + j, m, x} | t])
|
||||
def fits?(w, [{i, _, doc_group(x)} | t]), do: fits?(w, [{i, :flat, x} | t])
|
||||
def fits?(w, [{_, _, s} | t]) when is_binary(s), do: fits?((w - byte_size s), t)
|
||||
def fits?(w, [{_, :flat, doc_break(s)} | t]), do: fits?((w - byte_size s), t)
|
||||
def fits?(_, [{_, :break, doc_break(_)} | _]), do: true
|
||||
|
||||
@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])
|
||||
def format(w, k, [{i, m, doc_group(doc: x)} | t]), do: format(w, k, [{i, m, x} | t])
|
||||
def format(w, k, [{_, _, s} | t]) when is_binary(s), do: [s | format(w, (k + byte_size s), t)]
|
||||
def format(w, k, [{_, :flat, doc_break(str: s)} | t]), do: [s | format(w, (k + byte_size s), t)]
|
||||
def format(w, k, [{i, :break, doc_break(str: s)} | t]) do
|
||||
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(x, y)} | t]), do: format(w, k, [{i, m, x} | [{i, m, y} | t]])
|
||||
def format(w, k, [{i, m, doc_nest(x, j)} | t]), do: format(w, k, [{i + j, m, x} | t])
|
||||
def format(w, k, [{i, m, doc_group(x)} | t]), do: format(w, k, [{i, m, x} | t])
|
||||
def format(w, k, [{_, _, s} | t]) when is_binary(s), do: [s | format(w, (k + byte_size s), t)]
|
||||
def format(w, k, [{_, :flat, doc_break(s)} | t]), do: [s | format(w, (k + byte_size s), t)]
|
||||
def format(w, k, [{i, :break, doc_break(s)} | t]) do
|
||||
k = k + byte_size(s)
|
||||
|
||||
if w == :infinity or fits?(w - k, t) do
|
||||
@@ -502,6 +504,6 @@ defmodule Inspect.Algebra do
|
||||
@doc false
|
||||
@spec render([binary]) :: binary
|
||||
def render(sdoc) do
|
||||
iodata_to_binary sdoc
|
||||
IO.iodata_to_binary sdoc
|
||||
end
|
||||
end
|
||||
|
||||
@@ -69,4 +69,71 @@ defmodule Integer do
|
||||
defp do_parse(bitstring, acc) do
|
||||
{acc, bitstring}
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns a binary which corresponds to the text representation
|
||||
of `some_integer`.
|
||||
|
||||
Inlined by the compiler.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Integer.to_string(123)
|
||||
"123"
|
||||
|
||||
"""
|
||||
@spec to_string(integer) :: String.t
|
||||
def to_string(some_integer) do
|
||||
:erlang.integer_to_binary(some_integer)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns a binary which corresponds to the text representation
|
||||
of `some_integer` in base `base`.
|
||||
|
||||
Inlined by the compiler.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Integer.to_string(100, 16)
|
||||
"64"
|
||||
|
||||
"""
|
||||
@spec to_string(integer, pos_integer) :: String.t
|
||||
def to_string(some_integer, base) do
|
||||
:erlang.integer_to_binary(some_integer, base)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns a char list which corresponds to the text representation of the given integer.
|
||||
|
||||
Inlined by the compiler.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Integer.to_char_list(7)
|
||||
'7'
|
||||
|
||||
"""
|
||||
@spec to_char_list(integer) :: list
|
||||
def to_char_list(number) do
|
||||
:erlang.integer_to_list(number)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns a char list which corresponds to the text representation of the
|
||||
given integer in the given case.
|
||||
|
||||
Inlined by the compiler.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Integer.to_char_list(1023, 16)
|
||||
'3FF'
|
||||
|
||||
"""
|
||||
@spec to_char_list(integer, pos_integer) :: list
|
||||
def to_char_list(number, base) do
|
||||
:erlang.integer_to_list(number, base)
|
||||
end
|
||||
end
|
||||
|
||||
+101
-80
@@ -1,66 +1,3 @@
|
||||
defexception IO.StreamError, [:reason, :message] do
|
||||
def exception(opts) do
|
||||
reason = opts[:reason]
|
||||
formatted = iodata_to_binary(:file.format_error(reason))
|
||||
IO.StreamError[message: "error during streaming: #{formatted}", reason: reason]
|
||||
end
|
||||
end
|
||||
|
||||
defmodule IO.Stream do
|
||||
@moduledoc """
|
||||
Defines a `IO.Stream` struct returned by `IO.stream/2` and `IO.binstream/2`.
|
||||
|
||||
The following fields are public:
|
||||
|
||||
* `device` - the IO device
|
||||
* `raw` - a boolean indicating if bin functions should be used
|
||||
* `line_or_bytes` - if reading should read lines or a given amount of bytes
|
||||
|
||||
"""
|
||||
|
||||
defstruct device: nil, raw: true, line_or_bytes: :line
|
||||
|
||||
defimpl Collectable do
|
||||
def empty(stream) do
|
||||
stream
|
||||
end
|
||||
|
||||
def into(%{device: device, raw: raw} = stream) do
|
||||
{:ok, into(stream, device, raw)}
|
||||
end
|
||||
|
||||
defp into(stream, device, raw) do
|
||||
fn
|
||||
:ok, {:cont, x} ->
|
||||
case raw do
|
||||
true -> IO.binwrite(device, x)
|
||||
false -> IO.write(device, x)
|
||||
end
|
||||
:ok, _ -> stream
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defimpl Enumerable do
|
||||
def reduce(%{device: device, raw: raw, line_or_bytes: line_or_bytes}, acc, fun) do
|
||||
next_fun =
|
||||
case raw do
|
||||
true -> &IO.each_binstream(&1, line_or_bytes)
|
||||
false -> &IO.each_stream(&1, line_or_bytes)
|
||||
end
|
||||
Stream.unfold(device, next_fun).(acc, fun)
|
||||
end
|
||||
|
||||
def count(_stream) do
|
||||
{:error, __MODULE__}
|
||||
end
|
||||
|
||||
def member?(_stream, _term) do
|
||||
{:error, __MODULE__}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defmodule IO do
|
||||
@moduledoc """
|
||||
Functions handling IO.
|
||||
@@ -95,6 +32,7 @@ defmodule IO do
|
||||
|
||||
@type device :: atom | pid
|
||||
@type nodata :: {:error, term} | :eof
|
||||
@type chardata() :: :unicode.chardata()
|
||||
|
||||
import :erlang, only: [group_leader: 0]
|
||||
|
||||
@@ -116,7 +54,7 @@ defmodule IO do
|
||||
for instance `{:error, :estale}` if reading from an
|
||||
NFS file system.
|
||||
"""
|
||||
@spec read(device, :line | non_neg_integer) :: char_data | nodata
|
||||
@spec read(device, :line | non_neg_integer) :: chardata | nodata
|
||||
def read(device \\ group_leader, chars_or_line)
|
||||
|
||||
def read(device, :line) do
|
||||
@@ -171,9 +109,9 @@ defmodule IO do
|
||||
#=> "error"
|
||||
|
||||
"""
|
||||
@spec write(device, char_data | String.Chars.t) :: :ok
|
||||
@spec write(device, chardata | String.Chars.t) :: :ok
|
||||
def write(device \\ group_leader(), item) do
|
||||
:io.put_chars map_dev(device), to_char_data(item)
|
||||
:io.put_chars map_dev(device), to_chardata(item)
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -190,12 +128,12 @@ defmodule IO do
|
||||
@doc """
|
||||
Writes the argument to the device, similar to `write/2`,
|
||||
but adds a newline at the end. The argument is expected
|
||||
to be a char_data.
|
||||
to be a chardata.
|
||||
"""
|
||||
@spec puts(device, char_data | String.Chars.t) :: :ok
|
||||
@spec puts(device, chardata | String.Chars.t) :: :ok
|
||||
def puts(device \\ group_leader(), item) do
|
||||
erl_dev = map_dev(device)
|
||||
:io.put_chars erl_dev, [to_char_data(item), ?\n]
|
||||
:io.put_chars erl_dev, [to_chardata(item), ?\n]
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -244,8 +182,8 @@ defmodule IO do
|
||||
for instance `{:error, :estale}` if reading from an
|
||||
NFS file system.
|
||||
"""
|
||||
@spec getn(char_data | String.Chars.t, pos_integer) :: char_data | nodata
|
||||
@spec getn(device, char_data | String.Chars.t) :: char_data | nodata
|
||||
@spec getn(chardata | String.Chars.t, pos_integer) :: chardata | nodata
|
||||
@spec getn(device, chardata | String.Chars.t) :: chardata | nodata
|
||||
def getn(prompt, count \\ 1)
|
||||
|
||||
def getn(prompt, count) when is_integer(count) do
|
||||
@@ -262,9 +200,9 @@ defmodule IO do
|
||||
the number of unicode codepoints to be retrieved.
|
||||
Otherwise, `count` is the number of raw bytes to be retrieved.
|
||||
"""
|
||||
@spec getn(device, char_data | String.Chars.t, pos_integer) :: char_data | nodata
|
||||
@spec getn(device, chardata | String.Chars.t, pos_integer) :: chardata | nodata
|
||||
def getn(device, prompt, count) do
|
||||
:io.get_chars(map_dev(device), to_char_data(prompt), count)
|
||||
:io.get_chars(map_dev(device), to_chardata(prompt), count)
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -279,9 +217,9 @@ defmodule IO do
|
||||
for instance `{:error, :estale}` if reading from an
|
||||
NFS file system.
|
||||
"""
|
||||
@spec gets(device, char_data | String.Chars.t) :: char_data | nodata
|
||||
@spec gets(device, chardata | String.Chars.t) :: chardata | nodata
|
||||
def gets(device \\ group_leader(), prompt) do
|
||||
:io.get_line(map_dev(device), to_char_data(prompt))
|
||||
:io.get_line(map_dev(device), to_chardata(prompt))
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -310,7 +248,8 @@ defmodule IO do
|
||||
"""
|
||||
@spec stream(device, :line | pos_integer) :: Enumerable.t
|
||||
def stream(device, line_or_codepoints) do
|
||||
%IO.Stream{device: map_dev(device), raw: false, line_or_bytes: line_or_codepoints}
|
||||
struct IO.Stream,
|
||||
device: map_dev(device), raw: false, line_or_bytes: line_or_codepoints
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -328,7 +267,89 @@ defmodule IO do
|
||||
"""
|
||||
@spec binstream(device, :line | pos_integer) :: Enumerable.t
|
||||
def binstream(device, line_or_bytes) do
|
||||
%IO.Stream{device: map_dev(device), raw: true, line_or_bytes: line_or_bytes}
|
||||
struct IO.Stream,
|
||||
device: map_dev(device), raw: true, line_or_bytes: line_or_bytes
|
||||
end
|
||||
|
||||
@doc """
|
||||
Converts chardata (a list of integers representing codepoints,
|
||||
lists and strings) into a string.
|
||||
|
||||
In case the conversion fails, it raises a `UnicodeConversionError`.
|
||||
If a string is given, returns the string itself.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> IO.chardata_to_string([0x00E6, 0x00DF])
|
||||
"æß"
|
||||
|
||||
iex> IO.chardata_to_string([0x0061, "bc"])
|
||||
"abc"
|
||||
|
||||
"""
|
||||
@spec chardata_to_string(chardata) :: String.t | no_return
|
||||
def chardata_to_string(string) when is_binary(string) do
|
||||
string
|
||||
end
|
||||
|
||||
def chardata_to_string(list) when is_list(list) do
|
||||
case :unicode.characters_to_binary(list) do
|
||||
result when is_binary(result) ->
|
||||
result
|
||||
|
||||
{:error, encoded, rest} ->
|
||||
raise UnicodeConversionError, encoded: encoded, rest: rest, kind: :invalid
|
||||
|
||||
{:incomplete, encoded, rest} ->
|
||||
raise UnicodeConversionError, encoded: encoded, rest: rest, kind: :incomplete
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Converts iodata (a list of integers representing bytes, lists
|
||||
and binaries) into a binary.
|
||||
|
||||
Notice that this function treats lists of integers as raw bytes
|
||||
and does not perform any kind of encoding conversion. If you want
|
||||
to convert from a char list to a string (UTF-8 encoded), please
|
||||
use `chardata_to_string/1` instead.
|
||||
|
||||
If this function receives a binary, the same binary is returned.
|
||||
|
||||
Inlined by the compiler.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> bin1 = <<1, 2, 3>>
|
||||
iex> bin2 = <<4, 5>>
|
||||
iex> bin3 = <<6>>
|
||||
iex> IO.iodata_to_binary([bin1, 1, [2, 3, bin2], 4|bin3])
|
||||
<<1,2,3,1,2,3,4,5,4,6>>
|
||||
|
||||
iex> bin = <<1, 2, 3>>
|
||||
iex> IO.iodata_to_binary(bin)
|
||||
<<1,2,3>>
|
||||
|
||||
"""
|
||||
@spec iodata_to_binary(iodata) :: binary
|
||||
def iodata_to_binary(item) do
|
||||
:erlang.iolist_to_binary(item)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns the size of an iodata.
|
||||
|
||||
Inlined by the compiler.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> IO.iodata_length([1, 2|<<3, 4>>])
|
||||
4
|
||||
|
||||
"""
|
||||
@spec iodata_length(iodata) :: non_neg_integer
|
||||
def iodata_length(item) do
|
||||
:erlang.iolist_size(item)
|
||||
end
|
||||
|
||||
@doc false
|
||||
@@ -355,13 +376,13 @@ defmodule IO do
|
||||
end
|
||||
end
|
||||
|
||||
@compile {:inline, map_dev: 1, to_char_data: 1}
|
||||
@compile {:inline, map_dev: 1, to_chardata: 1}
|
||||
|
||||
# Map the Elixir names for standard io and error to Erlang names
|
||||
defp map_dev(:stdio), do: :standard_io
|
||||
defp map_dev(:stderr), do: :standard_error
|
||||
defp map_dev(other) when is_atom(other) or is_pid(other) or is_tuple(other), do: other
|
||||
|
||||
defp to_char_data(list) when is_list(list), do: list
|
||||
defp to_char_data(other), do: to_string(other)
|
||||
defp to_chardata(list) when is_list(list), do: list
|
||||
defp to_chardata(other), do: to_string(other)
|
||||
end
|
||||
|
||||
@@ -7,7 +7,7 @@ defmodule IO.ANSI.Sequence do
|
||||
"\e[#{unquote(code)}#{unquote(terminator)}"
|
||||
end
|
||||
|
||||
defp escape_sequence(unquote(atom_to_list(name))) do
|
||||
defp escape_sequence(unquote(Atom.to_char_list(name))) do
|
||||
unquote(name)()
|
||||
end
|
||||
end
|
||||
@@ -131,7 +131,7 @@ defmodule IO.ANSI do
|
||||
defsequence :clear, "2", "J"
|
||||
|
||||
defp escape_sequence(other) do
|
||||
raise ArgumentError, message: "invalid ANSI sequence specification: #{other}"
|
||||
raise ArgumentError, "invalid ANSI sequence specification: #{other}"
|
||||
end
|
||||
|
||||
@doc ~S"""
|
||||
@@ -210,8 +210,8 @@ defmodule IO.ANSI do
|
||||
end
|
||||
|
||||
defp do_escape(<<>>, _emit, _emitted, buffer, _acc) when is_list(buffer) do
|
||||
buffer = iodata_to_binary Enum.reverse(buffer)
|
||||
raise ArgumentError, message: "missing } for escape fragment #{buffer}"
|
||||
buffer = IO.iodata_to_binary Enum.reverse(buffer)
|
||||
raise ArgumentError, "missing } for escape fragment #{buffer}"
|
||||
end
|
||||
|
||||
defp do_escape(<<?%, ?{, t :: binary>>, emit, emitted, nil, acc) do
|
||||
@@ -223,6 +223,6 @@ defmodule IO.ANSI do
|
||||
end
|
||||
|
||||
defp do_escape(<<>>, _emit, emitted, nil, acc) do
|
||||
{iodata_to_binary(Enum.reverse(acc)), emitted}
|
||||
{IO.iodata_to_binary(Enum.reverse(acc)), emitted}
|
||||
end
|
||||
end
|
||||
|
||||
@@ -355,7 +355,7 @@ defmodule IO.ANSI.Docs do
|
||||
end
|
||||
|
||||
defp handle_inline(<<>>, _mark, buffer, acc, _options) do
|
||||
iodata_to_binary Enum.reverse([Enum.reverse(buffer)|acc])
|
||||
IO.iodata_to_binary Enum.reverse([Enum.reverse(buffer)|acc])
|
||||
end
|
||||
|
||||
defp inline_buffer(buffer, options) do
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
defmodule IO.StreamError do
|
||||
defexception [:reason, :message]
|
||||
|
||||
def exception(opts) do
|
||||
reason = opts[:reason]
|
||||
formatted = IO.iodata_to_binary(:file.format_error(reason))
|
||||
%IO.StreamError{message: "error during streaming: #{formatted}", reason: reason}
|
||||
end
|
||||
end
|
||||
|
||||
defmodule IO.Stream do
|
||||
@moduledoc """
|
||||
Defines a `IO.Stream` struct returned by `IO.stream/2` and `IO.binstream/2`.
|
||||
|
||||
The following fields are public:
|
||||
|
||||
* `device` - the IO device
|
||||
* `raw` - a boolean indicating if bin functions should be used
|
||||
* `line_or_bytes` - if reading should read lines or a given amount of bytes
|
||||
|
||||
"""
|
||||
|
||||
defstruct device: nil, raw: true, line_or_bytes: :line
|
||||
|
||||
defimpl Collectable do
|
||||
def empty(stream) do
|
||||
stream
|
||||
end
|
||||
|
||||
def into(%{device: device, raw: raw} = stream) do
|
||||
{:ok, into(stream, device, raw)}
|
||||
end
|
||||
|
||||
defp into(stream, device, raw) do
|
||||
fn
|
||||
:ok, {:cont, x} ->
|
||||
case raw do
|
||||
true -> IO.binwrite(device, x)
|
||||
false -> IO.write(device, x)
|
||||
end
|
||||
:ok, _ -> stream
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defimpl Enumerable do
|
||||
def reduce(%{device: device, raw: raw, line_or_bytes: line_or_bytes}, acc, fun) do
|
||||
next_fun =
|
||||
case raw do
|
||||
true -> &IO.each_binstream(&1, line_or_bytes)
|
||||
false -> &IO.each_stream(&1, line_or_bytes)
|
||||
end
|
||||
Stream.unfold(device, next_fun).(acc, fun)
|
||||
end
|
||||
|
||||
def count(_stream) do
|
||||
{:error, __MODULE__}
|
||||
end
|
||||
|
||||
def member?(_stream, _term) do
|
||||
{:error, __MODULE__}
|
||||
end
|
||||
end
|
||||
end
|
||||
+346
-738
File diff suppressed because it is too large
Load Diff
@@ -9,15 +9,13 @@ defmodule Kernel.CLI do
|
||||
This is the API invoked by Elixir boot process.
|
||||
"""
|
||||
def main(argv) do
|
||||
argv = for arg <- argv, do: String.from_char_data!(arg)
|
||||
argv = for arg <- argv, do: IO.chardata_to_string(arg)
|
||||
|
||||
{config, argv} = process_argv(argv, @blank_config)
|
||||
{config, argv} = parse_argv(argv)
|
||||
System.argv(argv)
|
||||
|
||||
run fn ->
|
||||
command_results = Enum.map(Enum.reverse(config.commands), &process_command(&1, config))
|
||||
command_errors = for {:error, msg} <- command_results, do: msg
|
||||
errors = Enum.reverse(config.errors) ++ command_errors
|
||||
errors = process_commands(config)
|
||||
|
||||
if errors != [] do
|
||||
Enum.each(errors, &IO.puts(:stderr, &1))
|
||||
@@ -55,6 +53,23 @@ defmodule Kernel.CLI do
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Parses ARGV returning the CLI config and trailing args.
|
||||
"""
|
||||
def parse_argv(argv) do
|
||||
parse_argv(argv, @blank_config)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Process commands according to the parsed config from `parse_argv/1`.
|
||||
Returns all errors.
|
||||
"""
|
||||
def process_commands(config) do
|
||||
results = Enum.map(Enum.reverse(config.commands), &process_command(&1, config))
|
||||
errors = for {:error, msg} <- results, do: msg
|
||||
Enum.reverse(config.errors, errors)
|
||||
end
|
||||
|
||||
## Helpers
|
||||
|
||||
defp at_exit(status) do
|
||||
@@ -75,7 +90,7 @@ defmodule Kernel.CLI do
|
||||
end
|
||||
|
||||
defp shared_option?(list, config, callback) do
|
||||
case process_shared(list, config) do
|
||||
case parse_shared(list, config) do
|
||||
{[h|hs], _} when h == hd(list) ->
|
||||
new_config = %{config | errors: ["#{h} : Unknown option" | config.errors]}
|
||||
callback.(hs, new_config)
|
||||
@@ -106,160 +121,164 @@ defmodule Kernel.CLI do
|
||||
[]
|
||||
end
|
||||
|
||||
# Process shared options
|
||||
# Parse shared options
|
||||
|
||||
defp process_shared([opt|_t], _config) when opt in ["-v", "--version"] do
|
||||
defp parse_shared([opt|_t], _config) when opt in ["-v", "--version"] do
|
||||
IO.puts "Elixir #{System.version}"
|
||||
System.halt 0
|
||||
end
|
||||
|
||||
defp process_shared(["-pa", h|t], config) do
|
||||
defp parse_shared(["-pa", h|t], config) do
|
||||
Enum.each Path.wildcard(Path.expand(h)), &Code.prepend_path(&1)
|
||||
process_shared t, config
|
||||
parse_shared t, config
|
||||
end
|
||||
|
||||
defp process_shared(["-pz", h|t], config) do
|
||||
defp parse_shared(["-pz", h|t], config) do
|
||||
Enum.each Path.wildcard(Path.expand(h)), &Code.append_path(&1)
|
||||
process_shared t, config
|
||||
parse_shared t, config
|
||||
end
|
||||
|
||||
defp process_shared(["--app", h|t], config) do
|
||||
process_shared t, %{config | commands: &[{:app, h}|&1]}
|
||||
defp parse_shared(["--app", h|t], config) do
|
||||
parse_shared t, %{config | commands: &[{:app, h}|&1]}
|
||||
end
|
||||
|
||||
defp process_shared(["--no-halt"|t], config) do
|
||||
process_shared t, %{config | halt: false}
|
||||
defp parse_shared(["--no-halt"|t], config) do
|
||||
parse_shared t, %{config | halt: false}
|
||||
end
|
||||
|
||||
defp process_shared(["-e", h|t], config) do
|
||||
process_shared t, %{config | commands: [{:eval, h} | config.commands]}
|
||||
defp parse_shared(["-e", h|t], config) do
|
||||
parse_shared t, %{config | commands: [{:eval, h} | config.commands]}
|
||||
end
|
||||
|
||||
defp process_shared(["-r", h|t], config) do
|
||||
process_shared t, %{config | commands: [{:require, h} | config.commands]}
|
||||
defp parse_shared(["-r", h|t], config) do
|
||||
parse_shared t, %{config | commands: [{:require, h} | config.commands]}
|
||||
end
|
||||
|
||||
defp process_shared(["-pr", h|t], config) do
|
||||
process_shared t, %{config | commands: [{:parallel_require, h} | config.commands]}
|
||||
defp parse_shared(["-pr", h|t], config) do
|
||||
parse_shared t, %{config | commands: [{:parallel_require, h} | config.commands]}
|
||||
end
|
||||
|
||||
defp process_shared([erl, _|t], config) when erl in ["--erl", "--sname", "--name", "--cookie"] do
|
||||
process_shared t, config
|
||||
defp parse_shared([erl, _|t], config) when erl in ["--erl", "--sname", "--name", "--cookie"] do
|
||||
parse_shared t, config
|
||||
end
|
||||
|
||||
defp process_shared([erl|t], config) when erl in ["--detached", "--hidden", "--gen-debug"] do
|
||||
process_shared t, config
|
||||
defp parse_shared([erl|t], config) when erl in ["--detached", "--hidden", "--gen-debug"] do
|
||||
parse_shared t, config
|
||||
end
|
||||
|
||||
defp process_shared(list, config) do
|
||||
defp parse_shared(list, config) do
|
||||
{list, config}
|
||||
end
|
||||
|
||||
# Process init options
|
||||
|
||||
defp process_argv(["--"|t], config) do
|
||||
defp parse_argv(["--"|t], config) do
|
||||
{config, t}
|
||||
end
|
||||
|
||||
defp process_argv(["+elixirc"|t], config) do
|
||||
process_compiler t, config
|
||||
defp parse_argv(["+elixirc"|t], config) do
|
||||
parse_compiler t, config
|
||||
end
|
||||
|
||||
defp process_argv(["+iex"|t], config) do
|
||||
process_iex t, config
|
||||
defp parse_argv(["+iex"|t], config) do
|
||||
parse_iex t, config
|
||||
end
|
||||
|
||||
defp process_argv(["-S", h|t], config) do
|
||||
defp parse_argv(["-S", h|t], config) do
|
||||
{%{config | commands: [{:script, h} | config.commands]}, t}
|
||||
end
|
||||
|
||||
defp process_argv([h|t] = list, config) do
|
||||
defp parse_argv([h|t] = list, config) do
|
||||
case h do
|
||||
"-" <> _ ->
|
||||
shared_option? list, config, &process_argv(&1, &2)
|
||||
shared_option? list, config, &parse_argv(&1, &2)
|
||||
_ ->
|
||||
{%{config | commands: [{:file, h} | config.commands]}, t}
|
||||
if Keyword.has_key?(config.commands, :eval) do
|
||||
{config, list}
|
||||
else
|
||||
{%{config | commands: [{:file, h} | config.commands]}, t}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defp process_argv([], config) do
|
||||
defp parse_argv([], config) do
|
||||
{config, []}
|
||||
end
|
||||
|
||||
# Process compiler options
|
||||
# Parse compiler options
|
||||
|
||||
defp process_compiler(["--"|t], config) do
|
||||
defp parse_compiler(["--"|t], config) do
|
||||
{config, t}
|
||||
end
|
||||
|
||||
defp process_compiler(["-o", h|t], config) do
|
||||
process_compiler t, %{config | output: h}
|
||||
defp parse_compiler(["-o", h|t], config) do
|
||||
parse_compiler t, %{config | output: h}
|
||||
end
|
||||
|
||||
defp process_compiler(["--no-docs"|t], config) do
|
||||
process_compiler t, %{config | compiler_options: [{:docs, false} | config.compiler_options]}
|
||||
defp parse_compiler(["--no-docs"|t], config) do
|
||||
parse_compiler t, %{config | compiler_options: [{:docs, false} | config.compiler_options]}
|
||||
end
|
||||
|
||||
defp process_compiler(["--no-debug-info"|t], config) do
|
||||
process_compiler t, %{config | compiler_options: [{:debug_info, false} | config.compiler_options]}
|
||||
defp parse_compiler(["--no-debug-info"|t], config) do
|
||||
parse_compiler t, %{config | compiler_options: [{:debug_info, false} | config.compiler_options]}
|
||||
end
|
||||
|
||||
defp process_compiler(["--ignore-module-conflict"|t], config) do
|
||||
process_compiler t, %{config | compiler_options: [{:ignore_module_conflict, true} | config.compiler_options]}
|
||||
defp parse_compiler(["--ignore-module-conflict"|t], config) do
|
||||
parse_compiler t, %{config | compiler_options: [{:ignore_module_conflict, true} | config.compiler_options]}
|
||||
end
|
||||
|
||||
defp process_compiler(["--warnings-as-errors"|t], config) do
|
||||
process_compiler t, %{config | compiler_options: [{:warnings_as_errors, true} | config.compiler_options]}
|
||||
defp parse_compiler(["--warnings-as-errors"|t], config) do
|
||||
parse_compiler t, %{config | compiler_options: [{:warnings_as_errors, true} | config.compiler_options]}
|
||||
end
|
||||
|
||||
defp process_compiler(["--verbose"|t], config) do
|
||||
process_compiler t, %{config | verbose_compile: true}
|
||||
defp parse_compiler(["--verbose"|t], config) do
|
||||
parse_compiler t, %{config | verbose_compile: true}
|
||||
end
|
||||
|
||||
defp process_compiler([h|t] = list, config) do
|
||||
defp parse_compiler([h|t] = list, config) do
|
||||
case h do
|
||||
"-" <> _ ->
|
||||
shared_option? list, config, &process_compiler(&1, &2)
|
||||
shared_option? list, config, &parse_compiler(&1, &2)
|
||||
_ ->
|
||||
pattern = if :filelib.is_dir(h), do: "#{h}/**/*.ex", else: h
|
||||
process_compiler t, %{config | compile: [pattern | config.compile]}
|
||||
parse_compiler t, %{config | compile: [pattern | config.compile]}
|
||||
end
|
||||
end
|
||||
|
||||
defp process_compiler([], config) do
|
||||
defp parse_compiler([], config) do
|
||||
{%{config | commands: [{:compile, config.compile}|config.commands]}, []}
|
||||
end
|
||||
|
||||
# Process iex options
|
||||
# Parse iex options
|
||||
|
||||
defp process_iex(["--"|t], config) do
|
||||
defp parse_iex(["--"|t], config) do
|
||||
{config, t}
|
||||
end
|
||||
|
||||
# This clause is here so that Kernel.CLI does not error out with "unknown
|
||||
# option"
|
||||
defp process_iex(["--dot-iex", _|t], config) do
|
||||
process_iex t, config
|
||||
# This clause is here so that Kernel.CLI does not
|
||||
# error out with "unknown option"
|
||||
defp parse_iex(["--dot-iex", _|t], config) do
|
||||
parse_iex t, config
|
||||
end
|
||||
|
||||
defp process_iex([opt, _|t], config) when opt in ["--remsh"] do
|
||||
process_iex t, config
|
||||
defp parse_iex([opt, _|t], config) when opt in ["--remsh"] do
|
||||
parse_iex t, config
|
||||
end
|
||||
|
||||
defp process_iex(["-S", h|t], config) do
|
||||
defp parse_iex(["-S", h|t], config) do
|
||||
{%{config | commands: [{:script, h} | config.commands]}, t}
|
||||
end
|
||||
|
||||
defp process_iex([h|t] = list, config) do
|
||||
defp parse_iex([h|t] = list, config) do
|
||||
case h do
|
||||
"-" <> _ ->
|
||||
shared_option? list, config, &process_iex(&1, &2)
|
||||
shared_option? list, config, &parse_iex(&1, &2)
|
||||
_ ->
|
||||
{%{config | commands: [{:file, h} | config.commands]}, t}
|
||||
end
|
||||
end
|
||||
|
||||
defp process_iex([], config) do
|
||||
defp parse_iex([], config) do
|
||||
{config, []}
|
||||
end
|
||||
|
||||
@@ -267,7 +286,7 @@ defmodule Kernel.CLI do
|
||||
|
||||
defp process_command({:cookie, h}, _config) do
|
||||
if Node.alive? do
|
||||
wrapper fn -> Node.set_cookie(binary_to_atom(h)) end
|
||||
wrapper fn -> Node.set_cookie(String.to_atom(h)) end
|
||||
else
|
||||
{:error, "--cookie : Cannot set cookie if the node is not alive (set --name or --sname)"}
|
||||
end
|
||||
@@ -278,7 +297,7 @@ defmodule Kernel.CLI do
|
||||
end
|
||||
|
||||
defp process_command({:app, app}, _config) when is_binary(app) do
|
||||
case Application.ensure_all_started(binary_to_atom(app)) do
|
||||
case Application.ensure_all_started(String.to_atom(app)) do
|
||||
{:error, {app, reason}} ->
|
||||
{:error, "--app : Could not start application #{app}: " <>
|
||||
Application.format_error(reason)}
|
||||
|
||||
@@ -7,7 +7,8 @@
|
||||
# Note that since this is required for bootstrap, we can't use
|
||||
# any of the `GenServer.Behaviour` conveniences.
|
||||
defmodule Kernel.LexicalTracker do
|
||||
@timeout 30_000
|
||||
@moduledoc false
|
||||
@timeout 30_000
|
||||
@behaviour :gen_server
|
||||
|
||||
@import 2
|
||||
|
||||
@@ -29,7 +29,7 @@ defmodule Kernel.SpecialForms do
|
||||
Conveniences for manipulating tuples can be found in the
|
||||
`Tuple` module. Some functions for working with tuples are
|
||||
also available in `Kernel`, namely `Kernel.elem/2`,
|
||||
`Kernel.set_elem/3` and `Kernel.tuple_size/1`.
|
||||
`Kernel.put_elem/3` and `Kernel.tuple_size/1`.
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -667,7 +667,6 @@ defmodule Kernel.SpecialForms do
|
||||
able to unquote;
|
||||
* `:location` - When set to `:keep`, keeps the current line and file from quote.
|
||||
Read the Stacktrace information section below for more information;
|
||||
* `:hygiene` - Allows a developer to disable hygiene selectively;
|
||||
* `:context` - Sets the resolution context;
|
||||
* `:bind_quoted` - Passes a binding to the macro. Whenever a binding is given,
|
||||
`unquote` is automatically disabled;
|
||||
@@ -864,10 +863,6 @@ defmodule Kernel.SpecialForms do
|
||||
ContextHygiene.read
|
||||
#=> 1
|
||||
|
||||
Hygiene for variables can be disabled overall as:
|
||||
|
||||
quote hygiene: [vars: false], do: x
|
||||
|
||||
## Hygiene in aliases
|
||||
|
||||
Aliases inside quote are hygienic by default.
|
||||
@@ -987,9 +982,6 @@ defmodule Kernel.SpecialForms do
|
||||
|
||||
Lazy.return_size #=> 2
|
||||
|
||||
As in aliases, import expansion can be explicitly disabled
|
||||
via the `hygiene: [imports: false]` option.
|
||||
|
||||
## Stacktrace information
|
||||
|
||||
When defining functions via macros, developers have the option of
|
||||
|
||||
@@ -153,8 +153,6 @@ defmodule Kernel.Typespec do
|
||||
|
||||
@type dict(key, value) :: [{key, value}]
|
||||
|
||||
Types can also be defined for records, see `defrecord/3`.
|
||||
|
||||
## Defining a specification
|
||||
|
||||
@spec function_name(type1, type2) :: return_type
|
||||
@@ -707,10 +705,6 @@ defmodule Kernel.Typespec do
|
||||
typespec_to_ast({:type, line, :char_list, []})
|
||||
end
|
||||
|
||||
defp typespec_to_ast({:remote_type, line, [{:atom, _, :elixir}, {:atom, _, :char_data}, []]}) do
|
||||
typespec_to_ast({:type, line, :char_data, []})
|
||||
end
|
||||
|
||||
defp typespec_to_ast({:remote_type, line, [{:atom, _, :elixir}, {:atom, _, :as_boolean}, [arg]]}) do
|
||||
typespec_to_ast({:type, line, :as_boolean, [arg]})
|
||||
end
|
||||
@@ -746,11 +740,11 @@ defmodule Kernel.Typespec do
|
||||
defp typespec_to_ast(other), do: other
|
||||
|
||||
defp erl_to_ex_var(var) do
|
||||
case atom_to_binary(var) do
|
||||
case Atom.to_string(var) do
|
||||
<<"_", c :: [binary, size(1)], rest :: binary>> ->
|
||||
binary_to_atom("_#{String.downcase(c)}#{rest}")
|
||||
String.to_atom("_#{String.downcase(c)}#{rest}")
|
||||
<<c :: [binary, size(1)], rest :: binary>> ->
|
||||
binary_to_atom("#{String.downcase(c)}#{rest}")
|
||||
String.to_atom("#{String.downcase(c)}#{rest}")
|
||||
end
|
||||
end
|
||||
|
||||
@@ -884,10 +878,6 @@ defmodule Kernel.Typespec do
|
||||
typespec((quote do: :elixir.char_list()), vars, caller)
|
||||
end
|
||||
|
||||
defp typespec({:char_data, _meta, []}, vars, caller) do
|
||||
typespec((quote do: :elixir.char_data()), vars, caller)
|
||||
end
|
||||
|
||||
defp typespec({:as_boolean, _meta, [arg]}, vars, caller) do
|
||||
typespec((quote do: :elixir.as_boolean(unquote(arg))), vars, caller)
|
||||
end
|
||||
|
||||
@@ -6,13 +6,14 @@ defmodule Keyword do
|
||||
|
||||
A keyword may have duplicated keys so it is not strictly
|
||||
a dictionary. However most of the functions in this module
|
||||
behaves exactly as a dictionary and mimic the API defined
|
||||
behave exactly as a dictionary and mimic the API defined
|
||||
by the `Dict` behaviour.
|
||||
|
||||
For example, `Keyword.get` will get the first entry matching
|
||||
the given key, regardless if duplicated entries exist.
|
||||
Similarly, `Keyword.put` and `Keyword.delete` ensure all
|
||||
duplicated entries for a given key are removed when invoked.
|
||||
|
||||
A handful of functions exist to handle duplicated keys, in
|
||||
particular, `from_enum` allows creating a new keywords without
|
||||
removing duplicated keys, `get_values` returns all values for
|
||||
@@ -387,6 +388,9 @@ defmodule Keyword do
|
||||
Updates the `key` with the given function. If the `key` does
|
||||
not exist, raises `KeyError`.
|
||||
|
||||
If there are duplicated entries, they are all removed and only the first one
|
||||
is updated.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Keyword.update!([a: 1], :a, &(&1 * 2))
|
||||
@@ -417,6 +421,9 @@ defmodule Keyword do
|
||||
Updates the `key` with the given function. If the `key` does
|
||||
not exist, inserts the given `initial` value.
|
||||
|
||||
If there are duplicated entries, they are all removed and only the first one
|
||||
is updated.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Keyword.update([a: 1], :a, 13, &(&1 * 2))
|
||||
@@ -440,8 +447,13 @@ defmodule Keyword do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Splits the given keywords in two given the given keys.
|
||||
Duplicated keys are preserved in the split keyword list.
|
||||
Takes all entries corresponding to the given keys and extracts them into a
|
||||
separate keyword list. Returns a tuple with the new list and the old list
|
||||
with removed keys.
|
||||
|
||||
Keys for which there are no entires in the keyword list are ignored.
|
||||
|
||||
Entries with duplicated keys end up in the same keyword list.
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -468,7 +480,9 @@ defmodule Keyword do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Takes the given keys from the dict.
|
||||
Takes all entries corresponding to the given keys and returns them in a new
|
||||
keyword list.
|
||||
|
||||
Duplicated keys are preserved in the new keyword list.
|
||||
|
||||
## Examples
|
||||
@@ -488,6 +502,7 @@ defmodule Keyword do
|
||||
|
||||
@doc """
|
||||
Drops the given keys from the dict.
|
||||
|
||||
Duplicated keys are preserved in the new keyword list.
|
||||
|
||||
## Examples
|
||||
@@ -496,7 +511,7 @@ defmodule Keyword do
|
||||
iex> Keyword.drop(d, [:b, :d])
|
||||
[a: 1, c: 3]
|
||||
|
||||
iex> d = [a: 1, b: 2, c: 3, d: 4, a: 5]
|
||||
iex> d = [a: 1, b: 2, b: 3, c: 3, d: 4, a: 5]
|
||||
iex> Keyword.drop(d, [:b, :d])
|
||||
[a: 1, c: 3, a: 5]
|
||||
|
||||
|
||||
+125
-31
@@ -337,7 +337,7 @@ defmodule List do
|
||||
"""
|
||||
@spec unzip([tuple]) :: [list]
|
||||
def unzip(list) when is_list(list) do
|
||||
:lists.map &tuple_to_list/1, zip(list)
|
||||
:lists.map &Tuple.to_list/1, zip(list)
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -454,21 +454,133 @@ defmodule List do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Converts char data into a char list.
|
||||
Converts a char list to an atom.
|
||||
|
||||
A char data is a string or a list of integers and strings and it
|
||||
is converted into a list with each codepoint represented as its
|
||||
respective integer value.
|
||||
Currently Elixir does not support conversions from char lists
|
||||
which contains Unicode codepoints greater than 0xFF.
|
||||
|
||||
Inlined by the compiler.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> List.from_char_data("æß")
|
||||
{:ok, 'æß'}
|
||||
iex> List.from_char_data([?a, "bc"])
|
||||
{:ok, 'abc'}
|
||||
iex> List.to_atom('elixir')
|
||||
:elixir
|
||||
|
||||
"""
|
||||
@spec from_char_data(char_data) :: {:ok, char_list} | {:error, list, binary} | {:incomplete, list, binary}
|
||||
@spec to_atom(char_list) :: atom
|
||||
def to_atom(char_list) do
|
||||
:erlang.list_to_atom(char_list)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Converts a char list to an existing atom.
|
||||
|
||||
Currently Elixir does not support conversions from char lists
|
||||
which contains Unicode codepoints greater than 0xFF.
|
||||
|
||||
Inlined by the compiler.
|
||||
"""
|
||||
@spec to_existing_atom(char_list) :: atom
|
||||
def to_existing_atom(char_list) do
|
||||
:erlang.list_to_existing_atom(char_list)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns the float whose text representation is `char_list`.
|
||||
|
||||
Inlined by the compiler.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> List.to_float('2.2017764e+0')
|
||||
2.2017764
|
||||
|
||||
"""
|
||||
@spec to_float(char_list) :: float
|
||||
def to_float(char_list) do
|
||||
:erlang.list_to_float(char_list)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns an integer whose text representation is `char_list`.
|
||||
|
||||
Inlined by the compiler.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> List.to_integer('123')
|
||||
123
|
||||
|
||||
"""
|
||||
@spec to_integer(char_list) :: integer
|
||||
def to_integer(char_list) do
|
||||
:erlang.list_to_integer(char_list)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns an integer whose text representation is `char_list` in base `base`.
|
||||
|
||||
Inlined by the compiler.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> List.to_integer('3FF', 16)
|
||||
1023
|
||||
|
||||
"""
|
||||
@spec to_integer(char_list, non_neg_integer) :: integer
|
||||
def to_integer(char_list, base) do
|
||||
:erlang.list_to_integer(char_list, base)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Converts a list to a tuple.
|
||||
|
||||
Inlined by the compiler.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> List.to_tuple([:share, [:elixir, 163]])
|
||||
{:share, [:elixir, 163]}
|
||||
|
||||
"""
|
||||
@spec to_tuple(list) :: tuple
|
||||
def to_tuple(list) do
|
||||
:erlang.list_to_tuple(list)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Converts a list of integers representing codepoints, lists or
|
||||
strings into a string.
|
||||
|
||||
Notice that this function expect a list of integer representing
|
||||
UTF-8 codepoints. If you have a list of bytes, you must instead use
|
||||
[the `:binary` module](http://erlang.org/doc/man/binary.html).
|
||||
|
||||
## Examples
|
||||
|
||||
iex> List.to_string([0x00E6, 0x00DF])
|
||||
"æß"
|
||||
|
||||
iex> List.to_string([0x0061, "bc"])
|
||||
"abc"
|
||||
|
||||
"""
|
||||
@spec to_string(:unicode.char_list) :: String.t
|
||||
def to_string(list) when is_list(list) do
|
||||
case :unicode.characters_to_binary(list) do
|
||||
result when is_binary(result) ->
|
||||
result
|
||||
|
||||
{:error, encoded, rest} ->
|
||||
raise UnicodeConversionError, encoded: encoded, rest: rest, kind: :invalid
|
||||
|
||||
{:incomplete, encoded, rest} ->
|
||||
raise UnicodeConversionError, encoded: encoded, rest: rest, kind: :incomplete
|
||||
end
|
||||
end
|
||||
|
||||
@doc false
|
||||
def from_char_data(char_data) do
|
||||
case :unicode.characters_to_list(char_data) do
|
||||
result when is_list(result) ->
|
||||
@@ -482,25 +594,7 @@ defmodule List do
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Converts char data into a char list.
|
||||
|
||||
A char data is a string or a list of integers and strings and it
|
||||
is converted into a list with each codepoint represented as its
|
||||
respective integer value.
|
||||
|
||||
In case the conversion fails or is incomplete, it raises a
|
||||
`UnicodeConversionError`.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> List.from_char_data!("æß")
|
||||
'æß'
|
||||
iex> List.from_char_data!("abc")
|
||||
'abc'
|
||||
|
||||
"""
|
||||
@spec from_char_data!(char_data) :: char_list | no_return
|
||||
@doc false
|
||||
def from_char_data!(char_data) do
|
||||
case :unicode.characters_to_list(char_data) do
|
||||
result when is_list(result) ->
|
||||
@@ -592,7 +686,7 @@ defmodule List do
|
||||
|
||||
case heads do
|
||||
nil -> :lists.reverse acc
|
||||
_ -> do_zip mlist, [list_to_tuple(:lists.reverse(heads))|acc]
|
||||
_ -> do_zip mlist, [:erlang.list_to_tuple(:lists.reverse(heads))|acc]
|
||||
end
|
||||
end
|
||||
|
||||
@@ -608,6 +702,6 @@ defmodule List do
|
||||
{nil, nil}
|
||||
end
|
||||
|
||||
defp to_list(tuple) when is_tuple(tuple), do: tuple_to_list(tuple)
|
||||
defp to_list(tuple) when is_tuple(tuple), do: Tuple.to_list(tuple)
|
||||
defp to_list(list) when is_list(list), do: list
|
||||
end
|
||||
|
||||
@@ -13,7 +13,7 @@ defprotocol List.Chars do
|
||||
end
|
||||
|
||||
defimpl List.Chars, for: Atom do
|
||||
def to_char_list(atom), do: atom_to_list(atom)
|
||||
def to_char_list(atom), do: Atom.to_char_list(atom)
|
||||
end
|
||||
|
||||
defimpl List.Chars, for: BitString do
|
||||
@@ -21,7 +21,7 @@ defimpl List.Chars, for: BitString do
|
||||
Returns the given binary converted to a char list.
|
||||
"""
|
||||
def to_char_list(thing) when is_binary(thing) do
|
||||
List.from_char_data!(thing)
|
||||
String.to_char_list(thing)
|
||||
end
|
||||
|
||||
def to_char_list(thing) do
|
||||
@@ -38,7 +38,7 @@ end
|
||||
|
||||
defimpl List.Chars, for: Integer do
|
||||
def to_char_list(thing) do
|
||||
integer_to_list(thing)
|
||||
Integer.to_char_list(thing)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -47,10 +47,10 @@ defimpl List.Chars, for: Float do
|
||||
@limit :math.pow(10, @digits)
|
||||
|
||||
def to_char_list(thing) when thing > @limit do
|
||||
float_to_list(thing, scientific: @digits)
|
||||
Float.to_char_list(thing, scientific: @digits)
|
||||
end
|
||||
|
||||
def to_char_list(thing) do
|
||||
float_to_list(thing, compact: true, decimals: @digits)
|
||||
Float.to_char_list(thing, compact: true, decimals: @digits)
|
||||
end
|
||||
end
|
||||
|
||||
+120
-55
@@ -16,7 +16,7 @@ defmodule Macro do
|
||||
:&&, :||, :<>, :++, :--, :\\, :::, :<-, :.., :|>, :=~,
|
||||
:<, :>, :->,
|
||||
:+, :-, :*, :/, :=, :|, :.,
|
||||
:and, :or, :xor, :when, :in, :inlist, :inbits,
|
||||
:and, :or, :xor, :when, :in,
|
||||
:<<<, :>>>, :|||, :&&&, :^^^, :~~~]
|
||||
|
||||
@doc false
|
||||
@@ -30,7 +30,7 @@ defmodule Macro do
|
||||
@spec binary_op_props(atom) :: {:left | :right, precedence :: integer}
|
||||
defp binary_op_props(o) do
|
||||
case o do
|
||||
o when o in [:<-, :inlist, :inbits, :\\, :::] -> {:left, 40}
|
||||
o when o in [:<-, :\\, :::] -> {:left, 40}
|
||||
:| -> {:right, 50}
|
||||
:when -> {:right, 70}
|
||||
:= -> {:right, 80}
|
||||
@@ -68,7 +68,7 @@ defmodule Macro do
|
||||
def pipe(expr, call_args, position)
|
||||
|
||||
def pipe(expr, {:&, _, _} = call_args, _integer) do
|
||||
raise ArgumentError, message: "cannot pipe #{to_string expr} into #{to_string call_args}"
|
||||
raise ArgumentError, "cannot pipe #{to_string expr} into #{to_string call_args}"
|
||||
end
|
||||
|
||||
def pipe(expr, {call, line, atom}, integer) when is_atom(atom) do
|
||||
@@ -80,17 +80,15 @@ defmodule Macro do
|
||||
end
|
||||
|
||||
def pipe(expr, call_args, _integer) do
|
||||
raise ArgumentError,
|
||||
message: "cannot pipe #{to_string expr} into #{to_string call_args}"
|
||||
raise ArgumentError, "cannot pipe #{to_string expr} into #{to_string call_args}"
|
||||
end
|
||||
|
||||
@doc """
|
||||
Recurs the quoted expression applying the given function to
|
||||
each metadata node.
|
||||
Applies the given function to the node metadata if it contains one.
|
||||
|
||||
This is often useful to remove information like lines and
|
||||
hygienic counters from the expression for either storage or
|
||||
comparison.
|
||||
This is often useful when used with `Macro.prewalk/1` to remove
|
||||
information like lines and hygienic counters from the expression
|
||||
for either storage or comparison.
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -104,21 +102,110 @@ defmodule Macro do
|
||||
def update_meta(quoted, fun)
|
||||
|
||||
def update_meta({left, meta, right}, fun) when is_list(meta) do
|
||||
{update_meta(left, fun), fun.(meta), update_meta(right, fun)}
|
||||
end
|
||||
|
||||
def update_meta({left, right}, fun) do
|
||||
{update_meta(left, fun), update_meta(right, fun)}
|
||||
end
|
||||
|
||||
def update_meta(list, fun) when is_list(list) do
|
||||
for x <- list, do: update_meta(x, fun)
|
||||
{left, fun.(meta), right}
|
||||
end
|
||||
|
||||
def update_meta(other, _fun) do
|
||||
other
|
||||
end
|
||||
|
||||
@doc """
|
||||
Performs a depth-first, pre-order traversal of quoted expressions.
|
||||
"""
|
||||
@spec prewalk(t, (t -> t)) :: t
|
||||
def prewalk(ast, fun) when is_function(fun, 1) do
|
||||
elem(prewalk(ast, nil, fn x, nil -> {fun.(x), nil} end), 0)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Performs a depth-first, pre-order traversal of quoted expressions
|
||||
using an accumulator.
|
||||
"""
|
||||
@spec prewalk(t, any, (t, any -> {t, any})) :: {t, any}
|
||||
def prewalk(ast, acc, fun) when is_function(fun, 2) do
|
||||
{ast, acc} = fun.(ast, acc)
|
||||
do_prewalk(ast, acc, fun)
|
||||
end
|
||||
|
||||
defp do_prewalk({form, meta, args}, acc, fun) do
|
||||
unless is_atom(form) do
|
||||
{form, acc} = fun.(form, acc)
|
||||
{form, acc} = do_prewalk(form, acc, fun)
|
||||
end
|
||||
|
||||
unless is_atom(args) do
|
||||
{args, acc} = Enum.map_reduce(args, acc, fn x, acc ->
|
||||
{x, acc} = fun.(x, acc)
|
||||
do_prewalk(x, acc, fun)
|
||||
end)
|
||||
end
|
||||
|
||||
{{form, meta, args}, acc}
|
||||
end
|
||||
|
||||
defp do_prewalk({left, right}, acc, fun) do
|
||||
{left, acc} = fun.(left, acc)
|
||||
{left, acc} = do_prewalk(left, acc, fun)
|
||||
{right, acc} = fun.(right, acc)
|
||||
{right, acc} = do_prewalk(right, acc, fun)
|
||||
{{left, right}, acc}
|
||||
end
|
||||
|
||||
defp do_prewalk(list, acc, fun) when is_list(list) do
|
||||
Enum.map_reduce(list, acc, fn x, acc ->
|
||||
{x, acc} = fun.(x, acc)
|
||||
do_prewalk(x, acc, fun)
|
||||
end)
|
||||
end
|
||||
|
||||
defp do_prewalk(x, acc, _fun) do
|
||||
{x, acc}
|
||||
end
|
||||
|
||||
@doc """
|
||||
Performs a depth-first, post-order traversal of quoted expressions.
|
||||
"""
|
||||
@spec postwalk(t, (t -> t)) :: t
|
||||
def postwalk(ast, fun) when is_function(fun, 1) do
|
||||
elem(postwalk(ast, nil, fn x, nil -> {fun.(x), nil} end), 0)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Performs a depth-first, post-order traversal of quoted expressions
|
||||
using an accumulator.
|
||||
"""
|
||||
@spec postwalk(t, any, (t, any -> {t, any})) :: {t, any}
|
||||
def postwalk(ast, acc, fun) when is_function(fun, 2) do
|
||||
do_postwalk(ast, acc, fun)
|
||||
end
|
||||
|
||||
defp do_postwalk({form, meta, args}, acc, fun) do
|
||||
unless is_atom(form) do
|
||||
{form, acc} = do_postwalk(form, acc, fun)
|
||||
end
|
||||
|
||||
unless is_atom(args) do
|
||||
{args, acc} = Enum.map_reduce(args, acc, &do_postwalk(&1, &2, fun))
|
||||
end
|
||||
|
||||
fun.({form, meta, args}, acc)
|
||||
end
|
||||
|
||||
defp do_postwalk({left, right}, acc, fun) do
|
||||
{left, acc} = do_postwalk(left, acc, fun)
|
||||
{right, acc} = do_postwalk(right, acc, fun)
|
||||
fun.({left, right}, acc)
|
||||
end
|
||||
|
||||
defp do_postwalk(list, acc, fun) when is_list(list) do
|
||||
{list, acc} = Enum.map_reduce(list, acc, &do_postwalk(&1, &2, fun))
|
||||
fun.(list, acc)
|
||||
end
|
||||
|
||||
defp do_postwalk(x, acc, fun) do
|
||||
fun.(x, acc)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Decomposes a local or remote call into its remote part (when provided),
|
||||
function name and argument list.
|
||||
@@ -300,7 +387,7 @@ defmodule Macro do
|
||||
|
||||
# Variables
|
||||
def to_string({var, _, atom} = ast, fun) when is_atom(atom) do
|
||||
fun.(ast, atom_to_binary(var))
|
||||
fun.(ast, Atom.to_string(var))
|
||||
end
|
||||
|
||||
# Aliases
|
||||
@@ -389,7 +476,7 @@ defmodule Macro do
|
||||
# Unary ops
|
||||
def to_string({unary, _, [{binary, _, [_, _]} = arg]} = ast, fun)
|
||||
when unary in unquote(@unary_ops) and binary in unquote(@binary_ops) do
|
||||
fun.(ast, atom_to_binary(unary) <> "(" <> to_string(arg, fun) <> ")")
|
||||
fun.(ast, Atom.to_string(unary) <> "(" <> to_string(arg, fun) <> ")")
|
||||
end
|
||||
|
||||
def to_string({:not, _, [arg]} = ast, fun) do
|
||||
@@ -397,7 +484,7 @@ defmodule Macro do
|
||||
end
|
||||
|
||||
def to_string({op, _, [arg]} = ast, fun) when op in unquote(@unary_ops) do
|
||||
fun.(ast, atom_to_binary(op) <> to_string(arg, fun))
|
||||
fun.(ast, Atom.to_string(op) <> to_string(arg, fun))
|
||||
end
|
||||
|
||||
# Access
|
||||
@@ -425,7 +512,7 @@ defmodule Macro do
|
||||
list == [] ->
|
||||
"[]"
|
||||
:io_lib.printable_list(list) ->
|
||||
"'" <> Inspect.BitString.escape(String.from_char_data!(list), ?') <> "'"
|
||||
"'" <> Inspect.BitString.escape(IO.chardata_to_string(list), ?') <> "'"
|
||||
Keyword.keyword?(list) ->
|
||||
"[" <> kw_list_to_string(list, fun) <> "]"
|
||||
true ->
|
||||
@@ -447,7 +534,7 @@ defmodule Macro do
|
||||
defp module_to_string(atom, _fun) when is_atom(atom), do: inspect(atom, records: false)
|
||||
defp module_to_string(other, fun), do: call_to_string(other, fun)
|
||||
|
||||
defp call_to_string(atom, _fun) when is_atom(atom), do: atom_to_binary(atom)
|
||||
defp call_to_string(atom, _fun) when is_atom(atom), do: Atom.to_string(atom)
|
||||
defp call_to_string({:., _, [arg]}, fun), do: module_to_string(arg, fun) <> "."
|
||||
defp call_to_string({:., _, [left, right]}, fun), do: module_to_string(left, fun) <> "." <> call_to_string(right, fun)
|
||||
defp call_to_string(other, fun), do: to_string(other, fun)
|
||||
@@ -481,7 +568,7 @@ defmodule Macro do
|
||||
|
||||
defp kw_block_to_string(key, value, fun) do
|
||||
block = adjust_new_lines block_to_string(value, fun), "\n "
|
||||
atom_to_binary(key) <> "\n " <> block <> "\n"
|
||||
Atom.to_string(key) <> "\n " <> block <> "\n"
|
||||
end
|
||||
|
||||
defp block_to_string([{:->, _, _}|_] = block, fun) do
|
||||
@@ -510,7 +597,11 @@ defmodule Macro do
|
||||
|
||||
defp kw_list_to_string(list, fun) do
|
||||
Enum.map_join(list, ", ", fn {key, value} ->
|
||||
atom_to_binary(key) <> ": " <> to_string(value, fun)
|
||||
atom_name = case Inspect.Atom.inspect(key) do
|
||||
":" <> rest -> rest
|
||||
other -> other
|
||||
end
|
||||
atom_name <> ": " <> to_string(value, fun)
|
||||
end)
|
||||
end
|
||||
|
||||
@@ -590,7 +681,7 @@ defmodule Macro do
|
||||
Consider the implementation below:
|
||||
|
||||
defmacro defmodule_with_length(name, do: block) do
|
||||
length = length(atom_to_list(name))
|
||||
length = length(Atom.to_char_list(name))
|
||||
|
||||
quote do
|
||||
defmodule unquote(name) do
|
||||
@@ -630,7 +721,7 @@ defmodule Macro do
|
||||
|
||||
defmacro defmodule_with_length(name, do: block) do
|
||||
expanded = Macro.expand(name, __CALLER__)
|
||||
length = length(atom_to_list(expanded))
|
||||
length = length(Atom.to_char_list(expanded))
|
||||
|
||||
quote do
|
||||
defmodule unquote(name) do
|
||||
@@ -722,7 +813,7 @@ defmodule Macro do
|
||||
{:ok, receiver, quoted} ->
|
||||
next = :elixir_counter.next
|
||||
{:elixir_quote.linify_with_context_counter(0, {receiver, next}, quoted), true}
|
||||
{:ok, _receiver} ->
|
||||
{:ok, _receiver, _name, _args} ->
|
||||
{original, false}
|
||||
:error ->
|
||||
{original, false}
|
||||
@@ -770,30 +861,4 @@ defmodule Macro do
|
||||
defp expand_until({tree, false}, _env) do
|
||||
tree
|
||||
end
|
||||
|
||||
@doc """
|
||||
Recursively traverses the quoted expression checking if all sub-terms are
|
||||
safe.
|
||||
|
||||
Terms are considered safe if they represent data structures and don't actually
|
||||
evaluate code. Returns `:ok` unless a given term is unsafe,
|
||||
which is returned as `{:unsafe, term}`.
|
||||
"""
|
||||
def safe_term(terms) do
|
||||
IO.write :stderr, "Macro.safe_term/1 is deprecated\n#{Exception.format_stacktrace}"
|
||||
do_safe_term(terms) || :ok
|
||||
end
|
||||
|
||||
defp do_safe_term({local, _, terms}) when local in [:{}, :%{}, :__aliases__] do
|
||||
do_safe_term(terms)
|
||||
end
|
||||
|
||||
defp do_safe_term({unary, _, [term]}) when unary in [:+, :-] do
|
||||
do_safe_term(term)
|
||||
end
|
||||
|
||||
defp do_safe_term({left, right}), do: do_safe_term(left) || do_safe_term(right)
|
||||
defp do_safe_term(terms) when is_list(terms), do: Enum.find_value(terms, &do_safe_term(&1))
|
||||
defp do_safe_term(terms) when is_tuple(terms), do: {:unsafe, terms}
|
||||
defp do_safe_term(_), do: nil
|
||||
end
|
||||
|
||||
+3
-43
@@ -15,6 +15,8 @@ defmodule Map do
|
||||
|
||||
defdelegate [keys(map), values(map), size(map), merge(map1, map2), to_list(map)], to: :maps
|
||||
|
||||
@compile {:inline, fetch: 2, put: 3, delete: 2, has_key?: 2}
|
||||
|
||||
@doc """
|
||||
Returns a new empty map.
|
||||
"""
|
||||
@@ -24,21 +26,10 @@ defmodule Map do
|
||||
|
||||
def fetch(map, key), do: :maps.find(key, map)
|
||||
|
||||
def pop(map, key, default \\ nil) do
|
||||
{get(map, key, default), delete(map, key)}
|
||||
end
|
||||
|
||||
def put(map, key, val) do
|
||||
:maps.put(key, val, map)
|
||||
end
|
||||
|
||||
def put_new(map, key, val) do
|
||||
case has_key?(map, key) do
|
||||
true -> map
|
||||
false -> :maps.put(key, val, map)
|
||||
end
|
||||
end
|
||||
|
||||
def delete(map, key), do: :maps.remove(key, map)
|
||||
|
||||
def merge(map1, map2, callback) do
|
||||
@@ -47,36 +38,5 @@ defmodule Map do
|
||||
end, map1, map2
|
||||
end
|
||||
|
||||
def split(map, keys) do
|
||||
acc = {%{}, %{}}
|
||||
|
||||
:maps.fold fn k, v, {take, drop} ->
|
||||
if k in keys do
|
||||
{put(take, k, v), drop}
|
||||
else
|
||||
{take, put(drop, k, v)}
|
||||
end
|
||||
end, acc, map
|
||||
end
|
||||
|
||||
def update!(map, key, fun) do
|
||||
case :maps.find(key, map) do
|
||||
:error ->
|
||||
raise(KeyError, key: key, term: map)
|
||||
{:ok, val} ->
|
||||
:maps.put(key, fun.(val), map)
|
||||
end
|
||||
end
|
||||
|
||||
def update(map, key, initial, fun) do
|
||||
case :maps.find(key, map) do
|
||||
:error ->
|
||||
:maps.put(key, initial, map)
|
||||
{:ok, val} ->
|
||||
:maps.put(key, fun.(val), map)
|
||||
end
|
||||
end
|
||||
|
||||
def equal?(map, map), do: true
|
||||
def equal?(_, _), do: false
|
||||
def equal?(%{} = map1, %{} = map2), do: map1 === map2
|
||||
end
|
||||
|
||||
@@ -534,8 +534,8 @@ defmodule Module do
|
||||
end
|
||||
|
||||
defp simplify_signature({:%, _, [left, _]}, _i) when is_atom(left) do
|
||||
last = List.last(String.split(atom_to_binary(left), "."))
|
||||
atom = binary_to_atom(downcase(last))
|
||||
last = List.last(String.split(Atom.to_string(left), "."))
|
||||
atom = String.to_atom(downcase(last))
|
||||
{atom, [], nil}
|
||||
end
|
||||
|
||||
@@ -545,7 +545,7 @@ defmodule Module do
|
||||
|
||||
defp simplify_signature({var, _, atom}, _i) when is_atom(atom) do
|
||||
case atom_to_binary(var) do
|
||||
"_" <> rest -> {binary_to_atom(rest), [], Elixir}
|
||||
"_" <> rest -> {String.to_atom(rest), [], Elixir}
|
||||
_ -> {var, [], nil}
|
||||
end
|
||||
end
|
||||
@@ -945,7 +945,7 @@ defmodule Module do
|
||||
end
|
||||
|
||||
defp normalize_attribute(key, _value) when key in [:type, :typep, :export_type, :opaque, :callback] do
|
||||
raise ArgumentError, message: "attributes type, typep, export_type, opaque and callback " <>
|
||||
raise ArgumentError, "attributes type, typep, export_type, opaque and callback " <>
|
||||
"must be set via Kernel.Typespec"
|
||||
end
|
||||
|
||||
@@ -968,7 +968,7 @@ defmodule Module do
|
||||
defp assert_not_compiled!(fun, module) do
|
||||
open?(module) ||
|
||||
raise ArgumentError,
|
||||
message: "could not call #{fun} on module #{inspect module} because it was already compiled"
|
||||
"could not call #{fun} on module #{inspect module} because it was already compiled"
|
||||
end
|
||||
|
||||
defp loaded?(module), do: is_tuple :code.is_loaded(module)
|
||||
|
||||
@@ -49,8 +49,8 @@ defmodule Node do
|
||||
|
||||
See http://www.erlang.org/doc/man/erlang.html#nodes-1 for more info.
|
||||
"""
|
||||
@typep list_arg :: :visible | :hidden | :connected | :this | :known
|
||||
@spec list(list_arg | [list_arg]) :: [t]
|
||||
@typep state :: :visible | :hidden | :connected | :this | :known
|
||||
@spec list(state | [state]) :: [t]
|
||||
def list(args) do
|
||||
:erlang.nodes(args)
|
||||
end
|
||||
|
||||
+227
-85
@@ -3,6 +3,11 @@ defmodule OptionParser do
|
||||
This module contains functions to parse command line arguments.
|
||||
"""
|
||||
|
||||
@type argv :: [String.t]
|
||||
@type parsed :: Keyword.t
|
||||
@type errors :: [{String.t, String.t | nil}]
|
||||
@type options :: [switches: Keyword.t, strict: Keyword.t, aliases: Keyword.t]
|
||||
|
||||
@doc """
|
||||
Parses `argv` into a keywords list.
|
||||
|
||||
@@ -32,7 +37,18 @@ defmodule OptionParser do
|
||||
## Switches
|
||||
|
||||
Many times though, it is better to explicitly list the available
|
||||
switches and their formats. The following types are supported:
|
||||
switches and their formats. The switches can be specified via two
|
||||
different options:
|
||||
|
||||
* `:strict` - the switches are strict. Any switch that does not
|
||||
exist in the switch list is treated as an error;
|
||||
|
||||
* `:switches` - configure some switches. Switches that does not
|
||||
exist in the switch list are still attempted to be parsed;
|
||||
|
||||
Note only `:strict` or `:switches` may be given at once.
|
||||
|
||||
For each switch, the following types are supported:
|
||||
|
||||
* `:boolean` - Marks the given switch as a boolean. Boolean switches
|
||||
never consume the following value unless it is
|
||||
@@ -41,8 +57,9 @@ defmodule OptionParser do
|
||||
* `:float` - Parses the switch as a float;
|
||||
* `:string` - Returns the switch as a string;
|
||||
|
||||
If a switch can't be parsed, the option is returned in the invalid
|
||||
options list (third element of the returned tuple).
|
||||
If a switch can't be parsed or is not specfied in the strict case,
|
||||
the option is returned in the invalid options list (third element
|
||||
of the returned tuple).
|
||||
|
||||
The following extra "types" are supported:
|
||||
|
||||
@@ -50,18 +67,25 @@ defmodule OptionParser do
|
||||
|
||||
Examples:
|
||||
|
||||
iex> OptionParser.parse(["--unlock", "path/to/file"], switches: [unlock: :boolean])
|
||||
iex> OptionParser.parse(["--unlock", "path/to/file"], strict: [unlock: :boolean])
|
||||
{[unlock: true], ["path/to/file"], []}
|
||||
|
||||
iex> OptionParser.parse(["--unlock", "--limit", "0", "path/to/file"],
|
||||
...> switches: [unlock: :boolean, limit: :integer])
|
||||
...> strict: [unlock: :boolean, limit: :integer])
|
||||
{[unlock: true, limit: 0], ["path/to/file"], []}
|
||||
|
||||
iex> OptionParser.parse(["--limit", "3"], switches: [limit: :integer])
|
||||
iex> OptionParser.parse(["--limit", "3"], strict: [limit: :integer])
|
||||
{[limit: 3], [], []}
|
||||
|
||||
iex> OptionParser.parse(["--limit", "yyz"], switches: [limit: :integer])
|
||||
{[], [], [limit: "yyz"]}
|
||||
iex> OptionParser.parse(["--limit", "xyz"], strict: [limit: :integer])
|
||||
{[], [], [{"--limit", "xyz"}]}
|
||||
|
||||
iex> OptionParser.parse(["--unknown", "xyz"], strict: [])
|
||||
{[], ["xyz"], [{"--unknown", nil}]}
|
||||
|
||||
iex> OptionParser.parse(["--limit", "3", "--unknown", "xyz"],
|
||||
...> switches: [limit: :integer])
|
||||
{[limit: 3, unknown: "xyz"], [], []}
|
||||
|
||||
## Negation switches
|
||||
|
||||
@@ -85,8 +109,10 @@ defmodule OptionParser do
|
||||
{[debug: true], [], []}
|
||||
|
||||
"""
|
||||
@spec parse(argv, options) :: {parsed, argv, errors}
|
||||
def parse(argv, opts \\ []) when is_list(argv) and is_list(opts) do
|
||||
parse(argv, opts, true)
|
||||
config = compile_config(opts, true)
|
||||
do_parse(argv, config, [], [], [])
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -104,71 +130,156 @@ defmodule OptionParser do
|
||||
{[verbose: true, source: "lib"], ["test/enum_test.exs", "--unlock"], []}
|
||||
|
||||
"""
|
||||
@spec parse_head(argv, options) :: {parsed, argv, errors}
|
||||
def parse_head(argv, opts \\ []) when is_list(argv) and is_list(opts) do
|
||||
parse(argv, opts, false)
|
||||
config = compile_config(opts, false)
|
||||
do_parse(argv, config, [], [], [])
|
||||
end
|
||||
|
||||
defp do_parse([], _config, opts, args, invalid) do
|
||||
{Enum.reverse(opts), Enum.reverse(args), Enum.reverse(invalid)}
|
||||
end
|
||||
|
||||
defp do_parse(argv, {aliases, switches, strict, all}=config, opts, args, invalid) do
|
||||
case next(argv, aliases, switches, strict) do
|
||||
{:ok, option, value, rest} ->
|
||||
# the option exist and it was successfully parsed
|
||||
kinds = List.wrap Keyword.get(switches, option)
|
||||
new_opts = do_store_option(opts, option, value, kinds)
|
||||
do_parse(rest, config, new_opts, args, invalid)
|
||||
|
||||
{:invalid, option, value, rest} ->
|
||||
# the option exist but it has wrong value
|
||||
do_parse(rest, config, opts, args, [{option, value}|invalid])
|
||||
|
||||
{:undefined, option, _value, rest} ->
|
||||
# the option does not exist (for strict cases)
|
||||
do_parse(rest, config, opts, args, [{option, nil}|invalid])
|
||||
|
||||
{:error, ["--"|rest]} ->
|
||||
{Enum.reverse(opts), Enum.reverse(args, rest), Enum.reverse(invalid)}
|
||||
|
||||
{:error, [arg|rest]=remaining_args} ->
|
||||
# there is no option
|
||||
if all do
|
||||
do_parse(rest, config, opts, [arg|args], invalid)
|
||||
else
|
||||
{Enum.reverse(opts), Enum.reverse(args, remaining_args), Enum.reverse(invalid)}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Low-level function that parses one option.
|
||||
|
||||
It accepts the same options as `parse/2` and `parse_head/2`
|
||||
as both functions are built on top of next. This function
|
||||
may return:
|
||||
|
||||
* `{:ok, key, value, rest}` - the option `key` with `value` was successfully parsed
|
||||
|
||||
* `{:invalid, key, value, rest}` - the option `key` is invalid with `value`
|
||||
(returned when the switch type does not match the one given via the command line)
|
||||
|
||||
* `{:undefined, key, value, rest}` - the option `key` is undefined
|
||||
(returned on strict cases and the switch is unknown)
|
||||
|
||||
* `{:error, rest}` - there are no switches at the top of the given argv
|
||||
"""
|
||||
|
||||
@spec next(argv, options) ::
|
||||
{:ok, key :: atom, value :: term, argv} |
|
||||
{:invalid, key :: atom, value :: term, argv} |
|
||||
{:undefined, key :: atom, value :: term, argv} |
|
||||
{:error, argv}
|
||||
|
||||
def next(argv, opts \\ []) when is_list(argv) and is_list(opts) do
|
||||
{aliases, switches, strict, _} = compile_config(opts, true)
|
||||
next(argv, aliases, switches, strict)
|
||||
end
|
||||
|
||||
defp next([], _aliases, _switches, _strict) do
|
||||
{:error, []}
|
||||
end
|
||||
|
||||
defp next(["--"|_]=argv, _aliases, _switches, _strict) do
|
||||
{:error, argv}
|
||||
end
|
||||
|
||||
defp next(["-"|_]=argv, _aliases, _switches, _strict) do
|
||||
{:error, argv}
|
||||
end
|
||||
|
||||
defp next(["- " <> _|_]=argv, _aliases, _switches, _strict) do
|
||||
{:error, argv}
|
||||
end
|
||||
|
||||
defp next(["-" <> option|rest], aliases, switches, strict) do
|
||||
{option, value} = split_option(option)
|
||||
opt_name_bin = "-" <> option
|
||||
tagged = tag_option(option, value, switches, aliases)
|
||||
|
||||
if strict and not option_defined?(tagged, switches) do
|
||||
{:undefined, opt_name_bin, value, rest}
|
||||
else
|
||||
{opt_name, kinds, value} = normalize_option(tagged, value, switches)
|
||||
{value, kinds, rest} = normalize_value(value, kinds, rest, strict)
|
||||
case validate_option(opt_name, value, kinds) do
|
||||
{:ok, new_value} -> {:ok, opt_name, new_value, rest}
|
||||
:invalid -> {:invalid, opt_name_bin, value, rest}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defp next(argv, _aliases, _switches, _strict) do
|
||||
{:error, argv}
|
||||
end
|
||||
|
||||
## Helpers
|
||||
|
||||
defp parse(argv, opts, bool) do
|
||||
aliases = opts[:aliases] || []
|
||||
switches = opts[:switches] || []
|
||||
parse(argv, aliases, switches, bool)
|
||||
defp compile_config(opts, all) do
|
||||
aliases = opts[:aliases] || []
|
||||
|
||||
{switches, strict} = cond do
|
||||
s = opts[:switches] ->
|
||||
{s, false}
|
||||
s = opts[:strict] ->
|
||||
{s, true}
|
||||
true ->
|
||||
{[], false}
|
||||
end
|
||||
|
||||
{aliases, switches, strict, all}
|
||||
end
|
||||
|
||||
defp parse(argv, aliases, switches, all) do
|
||||
parse(argv, aliases, switches, [], [], [], all)
|
||||
end
|
||||
defp validate_option(option, value, kinds) do
|
||||
{invalid_opt, value} = cond do
|
||||
:invalid in kinds ->
|
||||
{option, value}
|
||||
:boolean in kinds ->
|
||||
case value do
|
||||
t when t in [true, "true"] -> {nil, true}
|
||||
f when f in [false, "false"] -> {nil, false}
|
||||
_ -> {option, value}
|
||||
end
|
||||
:integer in kinds ->
|
||||
case Integer.parse(value) do
|
||||
{value, ""} -> {nil, value}
|
||||
_ -> {option, value}
|
||||
end
|
||||
:float in kinds ->
|
||||
case Float.parse(value) do
|
||||
{value, ""} -> {nil, value}
|
||||
_ -> {option, value}
|
||||
end
|
||||
true ->
|
||||
{nil, value}
|
||||
end
|
||||
|
||||
defp parse(["--"|_] = value, _aliases, _switches, dict, args, invalid, _all) do
|
||||
{Enum.reverse(dict), Enum.reverse(args, value), Enum.reverse(invalid)}
|
||||
end
|
||||
|
||||
defp parse(["-" <> option|t], aliases, switches, dict, args, invalid, all) do
|
||||
{option, value} = split_option(option)
|
||||
{option, kinds, value} = normalize_option(option, value, switches, aliases)
|
||||
{value, kinds, t} = normalize_value(value, kinds, t)
|
||||
{dict, invalid} = store_option(dict, invalid, option, value, kinds)
|
||||
parse(t, aliases, switches, dict, args, invalid, all)
|
||||
end
|
||||
|
||||
defp parse([h|t], aliases, switches, dict, args, invalid, true) do
|
||||
parse(t, aliases, switches, dict, [h|args], invalid, true)
|
||||
end
|
||||
|
||||
defp parse([], _, _switches, dict, args, invalid, true) do
|
||||
{Enum.reverse(dict), Enum.reverse(args), Enum.reverse(invalid)}
|
||||
end
|
||||
|
||||
defp parse(value, _, _switches, dict, _args, invalid, false) do
|
||||
{Enum.reverse(dict), value, Enum.reverse(invalid)}
|
||||
end
|
||||
|
||||
defp store_option(dict, invalid, option, value, kinds) do
|
||||
{invalid_option, value} =
|
||||
cond do
|
||||
:invalid in kinds ->
|
||||
{option, value}
|
||||
:boolean in kinds ->
|
||||
{nil, value in [true, "true"]}
|
||||
:integer in kinds ->
|
||||
case Integer.parse(value) do
|
||||
{value, ""} -> {nil, value}
|
||||
_ -> {option, value}
|
||||
end
|
||||
:float in kinds ->
|
||||
case Float.parse(value) do
|
||||
{value, ""} -> {nil, value}
|
||||
_ -> {option, value}
|
||||
end
|
||||
true ->
|
||||
{nil, value}
|
||||
end
|
||||
|
||||
if invalid_option do
|
||||
{dict, [{option, value}|invalid]}
|
||||
if invalid_opt do
|
||||
:invalid
|
||||
else
|
||||
{do_store_option(dict, option, value, kinds), invalid}
|
||||
{:ok, value}
|
||||
end
|
||||
end
|
||||
|
||||
@@ -181,20 +292,36 @@ defmodule OptionParser do
|
||||
end
|
||||
end
|
||||
|
||||
defp normalize_option(<<?-, option :: binary>>, value, switches, _aliases) do
|
||||
normalize_option(get_negated(option, switches), value, switches)
|
||||
defp tag_option(<<?-, option :: binary>>, value, switches, _aliases) do
|
||||
get_negated(option, value, switches)
|
||||
end
|
||||
|
||||
defp normalize_option(option, value, switches, aliases) do
|
||||
option = get_option(option)
|
||||
if alias = aliases[option] do
|
||||
normalize_option({:default, alias}, value, switches)
|
||||
defp tag_option(option, _value, _switches, aliases) when is_binary(option) do
|
||||
opt = get_option(option)
|
||||
if alias = aliases[opt] do
|
||||
{:default, alias}
|
||||
else
|
||||
{option, [:invalid], value}
|
||||
{:unknown, opt}
|
||||
end
|
||||
end
|
||||
|
||||
defp normalize_option({:negated, option}, _value, switches) do
|
||||
defp option_defined?({:unknown, _option}, _switches) do
|
||||
false
|
||||
end
|
||||
|
||||
defp option_defined?({:negated, option}, switches) do
|
||||
Keyword.has_key?(switches, option)
|
||||
end
|
||||
|
||||
defp option_defined?({:default, option}, switches) do
|
||||
Keyword.has_key?(switches, option)
|
||||
end
|
||||
|
||||
defp normalize_option({:unknown, option}, value, _switches) do
|
||||
{option, [:invalid], value}
|
||||
end
|
||||
|
||||
defp normalize_option({:negated, option}, nil, switches) do
|
||||
kinds = List.wrap(switches[option])
|
||||
|
||||
cond do
|
||||
@@ -203,15 +330,20 @@ defmodule OptionParser do
|
||||
kinds == [] ->
|
||||
{option, kinds, true}
|
||||
true ->
|
||||
{option, [:invalid], false}
|
||||
{reverse_negated(option), [:invalid], nil}
|
||||
end
|
||||
end
|
||||
|
||||
defp normalize_option({:negated, option}, value, _switches) do
|
||||
{option, [:invalid], value}
|
||||
end
|
||||
|
||||
defp normalize_option({:default, option}, value, switches) do
|
||||
{option, List.wrap(switches[option]), value}
|
||||
end
|
||||
|
||||
defp normalize_value(nil, kinds, t) do
|
||||
defp normalize_value(nil, kinds, t, strict) do
|
||||
nil_or_true = if strict, do: nil, else: true
|
||||
cond do
|
||||
:boolean in kinds ->
|
||||
{true, kinds, t}
|
||||
@@ -219,19 +351,21 @@ defmodule OptionParser do
|
||||
[h|t] = t
|
||||
{h, kinds, t}
|
||||
kinds == [] ->
|
||||
{true, kinds, t}
|
||||
{nil_or_true, kinds, t}
|
||||
true ->
|
||||
{true, [:invalid], t}
|
||||
{nil, [:invalid], t}
|
||||
end
|
||||
end
|
||||
|
||||
defp normalize_value(value, kinds, t) do
|
||||
defp normalize_value(value, kinds, t, _) do
|
||||
{value, kinds, t}
|
||||
end
|
||||
|
||||
defp value_in_tail?(["-" <> _|_]), do: false
|
||||
defp value_in_tail?([]), do: false
|
||||
defp value_in_tail?(_), do: true
|
||||
defp value_in_tail?(["-"|_]), do: true
|
||||
defp value_in_tail?(["- " <> _|_]), do: true
|
||||
defp value_in_tail?(["-" <> _|_]), do: false
|
||||
defp value_in_tail?([]), do: false
|
||||
defp value_in_tail?(_), do: true
|
||||
|
||||
defp split_option(option) do
|
||||
case :binary.split(option, "=") do
|
||||
@@ -245,16 +379,24 @@ defmodule OptionParser do
|
||||
end
|
||||
|
||||
defp get_option(option) do
|
||||
option |> to_underscore |> binary_to_atom
|
||||
option |> to_underscore |> String.to_atom
|
||||
end
|
||||
|
||||
defp get_negated("no-" <> rest = option, switches) do
|
||||
defp reverse_negated(negated) do
|
||||
String.to_atom("no_" <> Atom.to_string(negated))
|
||||
end
|
||||
|
||||
defp get_negated("no-" <> rest = option, value, switches) do
|
||||
negated = get_option(rest)
|
||||
option = if Keyword.has_key?(switches, negated), do: negated, else: get_option(option)
|
||||
option = if Keyword.has_key?(switches, negated) and value == nil do
|
||||
negated
|
||||
else
|
||||
get_option(option)
|
||||
end
|
||||
{:negated, option}
|
||||
end
|
||||
|
||||
defp get_negated(rest, _switches) do
|
||||
defp get_negated(rest, _value, _switches) do
|
||||
{:default, get_option(rest)}
|
||||
end
|
||||
end
|
||||
|
||||
+33
-20
@@ -13,7 +13,7 @@ defmodule Path do
|
||||
"""
|
||||
|
||||
alias :filename, as: FN
|
||||
@type t :: char_data
|
||||
@type t :: :unicode.chardata()
|
||||
|
||||
@doc """
|
||||
Converts the given path to an absolute one. Unlike
|
||||
@@ -58,7 +58,7 @@ defmodule Path do
|
||||
"""
|
||||
@spec absname(t, t) :: binary
|
||||
def absname(path, relative_to) do
|
||||
path = String.from_char_data!(path)
|
||||
path = IO.chardata_to_string(path)
|
||||
case type(path) do
|
||||
:relative -> join(relative_to, path)
|
||||
:absolute ->
|
||||
@@ -68,7 +68,7 @@ defmodule Path do
|
||||
path
|
||||
end
|
||||
:volumerelative ->
|
||||
relative_to = String.from_char_data!(relative_to)
|
||||
relative_to = IO.chardata_to_string(relative_to)
|
||||
absname_vr(split(path), split(relative_to), relative_to)
|
||||
end
|
||||
end
|
||||
@@ -85,7 +85,7 @@ defmodule Path do
|
||||
defp absname_vr([<<x, ?:>>|name], _, _relative) do
|
||||
cwd =
|
||||
case :file.get_cwd([x, ?:]) do
|
||||
{:ok, dir} -> String.from_char_data!(dir)
|
||||
{:ok, dir} -> IO.chardata_to_string(dir)
|
||||
{:error, _} -> <<x, ?:, ?/>>
|
||||
end
|
||||
absname(join(name), cwd)
|
||||
@@ -181,7 +181,7 @@ defmodule Path do
|
||||
case :os.type() do
|
||||
{:win32, _} -> win32_pathtype(name)
|
||||
_ -> unix_pathtype(name)
|
||||
end |> elem(1) |> String.from_char_data!
|
||||
end |> elem(1) |> IO.chardata_to_string
|
||||
end
|
||||
|
||||
defp unix_pathtype(<<?/, relative :: binary>>), do:
|
||||
@@ -245,7 +245,7 @@ defmodule Path do
|
||||
"""
|
||||
@spec relative_to(t, t) :: binary
|
||||
def relative_to(path, from) do
|
||||
path = String.from_char_data!(path)
|
||||
path = IO.chardata_to_string(path)
|
||||
relative_to(split(path), split(from), path)
|
||||
end
|
||||
|
||||
@@ -269,7 +269,7 @@ defmodule Path do
|
||||
@spec relative_to_cwd(t) :: binary
|
||||
def relative_to_cwd(path) do
|
||||
case :file.get_cwd do
|
||||
{:ok, base} -> relative_to(path, String.from_char_data!(base))
|
||||
{:ok, base} -> relative_to(path, IO.chardata_to_string(base))
|
||||
_ -> path
|
||||
end
|
||||
end
|
||||
@@ -292,7 +292,7 @@ defmodule Path do
|
||||
"""
|
||||
@spec basename(t) :: binary
|
||||
def basename(path) do
|
||||
FN.basename(String.from_char_data!(path))
|
||||
FN.basename(IO.chardata_to_string(path))
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -314,7 +314,7 @@ defmodule Path do
|
||||
"""
|
||||
@spec basename(t, t) :: binary
|
||||
def basename(path, extension) do
|
||||
FN.basename(String.from_char_data!(path), String.from_char_data!(extension))
|
||||
FN.basename(IO.chardata_to_string(path), IO.chardata_to_string(extension))
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -330,7 +330,7 @@ defmodule Path do
|
||||
"""
|
||||
@spec dirname(t) :: binary
|
||||
def dirname(path) do
|
||||
FN.dirname(String.from_char_data!(path))
|
||||
FN.dirname(IO.chardata_to_string(path))
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -347,7 +347,7 @@ defmodule Path do
|
||||
"""
|
||||
@spec extname(t) :: binary
|
||||
def extname(path) do
|
||||
FN.extension(String.from_char_data!(path))
|
||||
FN.extension(IO.chardata_to_string(path))
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -364,7 +364,7 @@ defmodule Path do
|
||||
"""
|
||||
@spec rootname(t) :: binary
|
||||
def rootname(path) do
|
||||
FN.rootname(String.from_char_data!(path))
|
||||
FN.rootname(IO.chardata_to_string(path))
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -382,7 +382,7 @@ defmodule Path do
|
||||
"""
|
||||
@spec rootname(t, t) :: binary
|
||||
def rootname(path, extension) do
|
||||
FN.rootname(String.from_char_data!(path), String.from_char_data!(extension))
|
||||
FN.rootname(IO.chardata_to_string(path), IO.chardata_to_string(extension))
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -407,7 +407,7 @@ defmodule Path do
|
||||
def join([name1, name2|rest]), do:
|
||||
join([join(name1, name2)|rest])
|
||||
def join([name]), do:
|
||||
do_join(String.from_char_data!(name), <<>>, [], major_os_type())
|
||||
do_join(IO.chardata_to_string(name), <<>>, [], major_os_type())
|
||||
|
||||
@doc """
|
||||
Joins two paths.
|
||||
@@ -420,7 +420,7 @@ defmodule Path do
|
||||
"""
|
||||
@spec join(t, t) :: binary
|
||||
def join(left, right),
|
||||
do: do_join(String.from_char_data!(left), relative(right), [], major_os_type())
|
||||
do: do_join(IO.chardata_to_string(left), relative(right), [], major_os_type())
|
||||
|
||||
defp major_os_type do
|
||||
:os.type |> elem(0)
|
||||
@@ -435,7 +435,7 @@ defmodule Path do
|
||||
defp do_join(<<?/, rest :: binary>>, relativename, [?/|result], os_type), do:
|
||||
do_join(rest, relativename, [?/|result], os_type)
|
||||
defp do_join(<<>>, <<>>, result, os_type), do:
|
||||
iodata_to_binary(maybe_remove_dirsep(result, os_type))
|
||||
IO.iodata_to_binary(maybe_remove_dirsep(result, os_type))
|
||||
defp do_join(<<>>, relativename, [?:|rest], :win32), do:
|
||||
do_join(relativename, <<>>, [?:|rest], :win32)
|
||||
defp do_join(<<>>, relativename, [?/|result], os_type), do:
|
||||
@@ -476,7 +476,7 @@ defmodule Path do
|
||||
def split(""), do: []
|
||||
|
||||
def split(path) do
|
||||
FN.split(String.from_char_data!(path))
|
||||
FN.split(IO.chardata_to_string(path))
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -515,15 +515,28 @@ defmodule Path do
|
||||
@spec wildcard(t) :: [binary]
|
||||
def wildcard(glob) do
|
||||
glob
|
||||
|> List.from_char_data!
|
||||
|> chardata_to_list
|
||||
|> :filelib.wildcard
|
||||
|> Enum.map(&String.from_char_data!/1)
|
||||
|> Enum.map(&IO.chardata_to_string/1)
|
||||
end
|
||||
|
||||
# Normalize the given path by expanding "..", "." and "~".
|
||||
|
||||
defp chardata_to_list(chardata) do
|
||||
case :unicode.characters_to_list(chardata) do
|
||||
result when is_list(result) ->
|
||||
result
|
||||
|
||||
{:error, encoded, rest} ->
|
||||
raise UnicodeConversionError, encoded: encoded, rest: rest, kind: :invalid
|
||||
|
||||
{:incomplete, encoded, rest} ->
|
||||
raise UnicodeConversionError, encoded: encoded, rest: rest, kind: :incomplete
|
||||
end
|
||||
end
|
||||
|
||||
defp expand_home(type) do
|
||||
case String.from_char_data!(type) do
|
||||
case IO.chardata_to_string(type) do
|
||||
"~" <> rest -> System.user_home! <> rest
|
||||
rest -> rest
|
||||
end
|
||||
|
||||
+10
-21
@@ -2,10 +2,16 @@ defmodule Process do
|
||||
@moduledoc """
|
||||
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.
|
||||
Besides the functions available in this module, the `Kernel` module
|
||||
exposes and auto-imports some basic functionality related to processes
|
||||
available through the functions:
|
||||
|
||||
* `Kernel.spawn/1` and `Kernel.spawn/3`
|
||||
* `Kernel.spawn_link/1` and `Kernel.spawn_link/3`
|
||||
* `Kernel.spawn_monitor/1` and `Kernel.spawn_monitor/3`
|
||||
* `Kernel.self/0`
|
||||
* `Kernel.send/2`
|
||||
|
||||
"""
|
||||
|
||||
@doc """
|
||||
@@ -57,15 +63,6 @@ defmodule Process do
|
||||
nillify :erlang.put(key, value)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Deletes all items in the dictionary.
|
||||
"""
|
||||
@spec delete :: [{term, term}]
|
||||
def delete() do
|
||||
IO.write :stderr, "Process.delete/0 is deprecated\n#{Exception.format_stacktrace}"
|
||||
:erlang.erase()
|
||||
end
|
||||
|
||||
@doc """
|
||||
Deletes the given key from the dictionary.
|
||||
"""
|
||||
@@ -199,49 +196,41 @@ defmodule Process do
|
||||
|
||||
@doc false
|
||||
def spawn(fun) do
|
||||
# IO.write :stderr, "Process.spawn/1 is deprecated, use the one in Kernel instead\n#{Exception.format_stacktrace}"
|
||||
:erlang.spawn(fun)
|
||||
end
|
||||
|
||||
@doc false
|
||||
def spawn(mod, fun, args) do
|
||||
# IO.write :stderr, "Process.spawn/3 is deprecated, use the one in Kernel instead\n#{Exception.format_stacktrace}"
|
||||
:erlang.spawn(mod, fun, args)
|
||||
end
|
||||
|
||||
@doc false
|
||||
def spawn_link(fun) do
|
||||
# IO.write :stderr, "Process.spawn_link/1 is deprecated, use the one in Kernel instead\n#{Exception.format_stacktrace}"
|
||||
:erlang.spawn_link(fun)
|
||||
end
|
||||
|
||||
@doc false
|
||||
def spawn_link(mod, fun, args) do
|
||||
# IO.write :stderr, "Process.spawn_link/3 is deprecated, use the one in Kernel instead\n#{Exception.format_stacktrace}"
|
||||
:erlang.spawn_link(mod, fun, args)
|
||||
end
|
||||
|
||||
@doc false
|
||||
def spawn_monitor(fun) do
|
||||
# IO.write :stderr, "Process.spawn_monitor/1 is deprecated, use the one in Kernel instead\n#{Exception.format_stacktrace}"
|
||||
:erlang.spawn_monitor(fun)
|
||||
end
|
||||
|
||||
@doc false
|
||||
def spawn_monitor(mod, fun, args) do
|
||||
# IO.write :stderr, "Process.spawn_monitor/3 is deprecated, use the one in Kernel instead\n#{Exception.format_stacktrace}"
|
||||
:erlang.spawn_monitor(mod, fun, args)
|
||||
end
|
||||
|
||||
@doc false
|
||||
def self() do
|
||||
# IO.write :stderr, "Process.self/0 is deprecated, use the one in Kernel instead\n#{Exception.format_stacktrace}"
|
||||
:erlang.self()
|
||||
end
|
||||
|
||||
@doc false
|
||||
def send(dest, msg) do
|
||||
# IO.write :stderr, "Process.send/2 is deprecated, use the one in Kernel instead\n#{Exception.format_stacktrace}"
|
||||
:erlang.send(dest, msg)
|
||||
end
|
||||
|
||||
|
||||
@@ -35,7 +35,8 @@ defmodule Protocol do
|
||||
quote unquote: false do
|
||||
# == Deprecated records handling ==
|
||||
{arg, impl} = Protocol.rec_impl_for(__MODULE__)
|
||||
Kernel.def impl_for(unquote(arg)) when Kernel.is_record(unquote(arg)), do: unquote(impl)
|
||||
Kernel.def impl_for(unquote(arg)) when
|
||||
is_tuple(unquote(arg)) and is_atom(elem(unquote(arg), 0)), do: unquote(impl)
|
||||
# == Deprecated records handling ==
|
||||
|
||||
@spec impl_for(term) :: module | nil
|
||||
@@ -154,14 +155,14 @@ defmodule Protocol do
|
||||
def assert_protocol(module) do
|
||||
case Code.ensure_compiled(module) do
|
||||
{:module, ^module} -> nil
|
||||
_ -> raise ArgumentError, message: "#{inspect module} is not loaded"
|
||||
_ -> raise ArgumentError, "#{inspect module} is not loaded"
|
||||
end
|
||||
|
||||
try do
|
||||
module.__protocol__(:name)
|
||||
rescue
|
||||
UndefinedFunctionError ->
|
||||
raise ArgumentError, message: "#{inspect module} is not a protocol"
|
||||
raise ArgumentError, "#{inspect module} is not a protocol"
|
||||
end
|
||||
end
|
||||
|
||||
@@ -199,7 +200,7 @@ defmodule Protocol do
|
||||
impl_for = quote do
|
||||
atom = :erlang.element(1, unquote(arg))
|
||||
|
||||
case not(atom in unquote(all)) and match?('Elixir.' ++ _, atom_to_list(atom)) do
|
||||
case not(atom in unquote(all)) and match?('Elixir.' ++ _, Atom.to_char_list(atom)) do
|
||||
true ->
|
||||
target = Module.concat(unquote(current), atom)
|
||||
case impl_for?(target) do
|
||||
@@ -220,7 +221,7 @@ defmodule Protocol.DSL do
|
||||
|
||||
@doc false
|
||||
defmacro def({_, _, args}) when args == [] or is_atom(args) do
|
||||
raise ArgumentError, message: "protocol functions expect at least one argument"
|
||||
raise ArgumentError, "protocol functions expect at least one argument"
|
||||
end
|
||||
|
||||
defmacro def({name, _, args}) when is_atom(name) and is_list(args) do
|
||||
@@ -230,7 +231,7 @@ defmodule Protocol.DSL do
|
||||
type_args = [quote(do: t) | type_args]
|
||||
|
||||
call_args = for i <- :lists.seq(2, arity),
|
||||
do: {binary_to_atom(<<?x, i + 64>>), [], __MODULE__}
|
||||
do: {String.to_atom(<<?x, i + 64>>), [], __MODULE__}
|
||||
call_args = [quote(do: t) | call_args]
|
||||
|
||||
quote do
|
||||
@@ -256,7 +257,7 @@ defmodule Protocol.DSL do
|
||||
end
|
||||
|
||||
defmacro def(_) do
|
||||
raise ArgumentError, message: "invalid args for def inside defprotocol"
|
||||
raise ArgumentError, "invalid args for def inside defprotocol"
|
||||
end
|
||||
|
||||
@doc false
|
||||
|
||||
@@ -51,7 +51,7 @@ defmodule Protocol.Consolidation do
|
||||
"""
|
||||
@spec extract_impls(module, [char_list | String.t]) :: [atom]
|
||||
def extract_impls(protocol, paths) when is_atom(protocol) do
|
||||
prefix = atom_to_list(protocol) ++ '.'
|
||||
prefix = Atom.to_char_list(protocol) ++ '.'
|
||||
extract_matching_by_attribute paths, prefix, fn
|
||||
_mod, attributes ->
|
||||
case attributes[:impl] do
|
||||
@@ -125,7 +125,7 @@ defmodule Protocol.Consolidation do
|
||||
{:error, :not_a_protocol} |
|
||||
{:error, :no_beam_info}
|
||||
def apply_to(protocol, types) when is_atom(protocol) do
|
||||
raise ArgumentError, message: "consolidation is disabled as we can't consolidate records " <>
|
||||
raise ArgumentError, "consolidation is disabled as we can't consolidate records " <>
|
||||
"and structs at once. Consolidation will be added back once " <>
|
||||
"polymorphic records are removed"
|
||||
ensure_protocol(protocol)
|
||||
|
||||
+119
-11
@@ -12,11 +12,9 @@ defmodule Record do
|
||||
manipulate the tuples, providing fast operations on top of
|
||||
the tuples compact structure.
|
||||
|
||||
In Elixir, records are used in two situations:
|
||||
|
||||
1. To work with short, internal data. See `Inspect.Algebra`
|
||||
implementation for a good example;
|
||||
In Elixir, records are used mostly in two situations:
|
||||
|
||||
1. To work with short, internal data;
|
||||
2. To interface with Erlang records;
|
||||
|
||||
The macros `defrecord/3` and `defrecordp/3` can be used to create
|
||||
@@ -151,20 +149,21 @@ defmodule Record do
|
||||
Record.defrecord :user, User, name: nil
|
||||
end
|
||||
|
||||
user() #=> {User, nil}
|
||||
require User
|
||||
User.user() #=> {User, nil}
|
||||
|
||||
"""
|
||||
defmacro defrecord(name, tag \\ nil, kv) do
|
||||
quote bind_quoted: [name: name, tag: tag, kv: kv] do
|
||||
tag = tag || name
|
||||
fields = Macro.escape Record.Backend.default_fields(:record, kv)
|
||||
fields = Macro.escape Record.__fields__(:defrecord, kv)
|
||||
|
||||
defmacro(unquote(name)(args \\ [])) do
|
||||
Record.Backend.access(unquote(tag), unquote(fields), args, __CALLER__)
|
||||
Record.__access__(unquote(tag), unquote(fields), args, __CALLER__)
|
||||
end
|
||||
|
||||
defmacro(unquote(name)(record, args)) do
|
||||
Record.Backend.access(unquote(tag), unquote(fields), record, args, __CALLER__)
|
||||
Record.__access__(unquote(tag), unquote(fields), record, args, __CALLER__)
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -175,15 +174,124 @@ defmodule Record do
|
||||
defmacro defrecordp(name, tag \\ nil, kv) do
|
||||
quote bind_quoted: [name: name, tag: tag, kv: kv] do
|
||||
tag = tag || name
|
||||
fields = Macro.escape Record.Backend.default_fields(:record, kv)
|
||||
fields = Macro.escape Record.__fields__(:defrecordp, kv)
|
||||
|
||||
defmacrop(unquote(name)(args \\ [])) do
|
||||
Record.Backend.access(unquote(tag), unquote(fields), args, __CALLER__)
|
||||
Record.__access__(unquote(tag), unquote(fields), args, __CALLER__)
|
||||
end
|
||||
|
||||
defmacrop(unquote(name)(record, args)) do
|
||||
Record.Backend.access(unquote(tag), unquote(fields), record, args, __CALLER__)
|
||||
Record.__access__(unquote(tag), unquote(fields), record, args, __CALLER__)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Normalizes of record fields to have default values.
|
||||
@doc false
|
||||
def __fields__(type, fields) do
|
||||
:lists.map(fn
|
||||
{ key, _ } = pair when is_atom(key) -> pair
|
||||
key when is_atom(key) -> { key, nil }
|
||||
other -> raise ArgumentError, "#{type} fields must be atoms, got: #{inspect other}"
|
||||
end, fields)
|
||||
end
|
||||
|
||||
# Callback invoked from record/0 and record/1 macros.
|
||||
@doc false
|
||||
def __access__(atom, fields, args, caller) do
|
||||
cond do
|
||||
is_atom(args) ->
|
||||
index(atom, fields, args)
|
||||
Keyword.keyword?(args) ->
|
||||
create(atom, fields, args, caller)
|
||||
true ->
|
||||
msg = "expected arguments to be a compile time atom or keywords, got: #{Macro.to_string args}"
|
||||
raise ArgumentError, msg
|
||||
end
|
||||
end
|
||||
|
||||
# Callback invoked from the record/2 macro.
|
||||
@doc false
|
||||
def __access__(atom, fields, record, args, caller) do
|
||||
cond do
|
||||
is_atom(args) ->
|
||||
get(atom, fields, record, args)
|
||||
Keyword.keyword?(args) ->
|
||||
update(atom, fields, record, args, caller)
|
||||
true ->
|
||||
msg = "expected arguments to be a compile time atom or keywords, got: #{Macro.to_string args}"
|
||||
raise ArgumentError, msg
|
||||
end
|
||||
end
|
||||
|
||||
# Gets the index of field.
|
||||
defp index(atom, fields, field) do
|
||||
if index = find_index(fields, field, 0) do
|
||||
index - 1 # Convert to Elixir index
|
||||
else
|
||||
raise ArgumentError, "record #{inspect atom} does not have the key: #{inspect field}"
|
||||
end
|
||||
end
|
||||
|
||||
# Creates a new record with the given default fields and keyword values.
|
||||
defp create(atom, fields, keyword, caller) do
|
||||
in_match = Macro.Env.in_match?(caller)
|
||||
|
||||
{match, remaining} =
|
||||
Enum.map_reduce(fields, keyword, fn({field, default}, each_keyword) ->
|
||||
new_fields =
|
||||
case Keyword.has_key?(each_keyword, field) do
|
||||
true -> Keyword.get(each_keyword, field)
|
||||
false ->
|
||||
case in_match do
|
||||
true -> {:_, [], nil}
|
||||
false -> Macro.escape(default)
|
||||
end
|
||||
end
|
||||
|
||||
{new_fields, Keyword.delete(each_keyword, field)}
|
||||
end)
|
||||
|
||||
case remaining do
|
||||
[] ->
|
||||
{:{}, [], [atom|match]}
|
||||
_ ->
|
||||
keys = for {key, _} <- remaining, do: key
|
||||
raise ArgumentError, "record #{inspect atom} does not have the key: #{inspect hd(keys)}"
|
||||
end
|
||||
end
|
||||
|
||||
# Updates a record given by var with the given keyword.
|
||||
defp update(atom, fields, var, keyword, caller) do
|
||||
if Macro.Env.in_match?(caller) do
|
||||
raise ArgumentError, "cannot invoke update style macro inside match"
|
||||
end
|
||||
|
||||
Enum.reduce keyword, var, fn({key, value}, acc) ->
|
||||
index = find_index(fields, key, 0)
|
||||
if index do
|
||||
quote do
|
||||
:erlang.setelement(unquote(index), unquote(acc), unquote(value))
|
||||
end
|
||||
else
|
||||
raise ArgumentError, "record #{inspect atom} does not have the key: #{inspect key}"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Gets a record key from the given var.
|
||||
defp get(atom, fields, var, key) do
|
||||
index = find_index(fields, key, 0)
|
||||
if index do
|
||||
quote do
|
||||
:erlang.element(unquote(index), unquote(var))
|
||||
end
|
||||
else
|
||||
raise ArgumentError, "record #{inspect atom} does not have the key: #{inspect key}"
|
||||
end
|
||||
end
|
||||
|
||||
defp find_index([{k, _}|_], k, i), do: i + 2
|
||||
defp find_index([{_, _}|t], k, i), do: find_index(t, k, i + 1)
|
||||
defp find_index([], _k, _i), do: nil
|
||||
end
|
||||
|
||||
@@ -1,153 +0,0 @@
|
||||
defmodule Record.Backend do
|
||||
# Callback functions invoked by defrecord, defrecordp and friends.
|
||||
@moduledoc false
|
||||
|
||||
@doc """
|
||||
Normalizes a list of record or struct fields to have default values.
|
||||
"""
|
||||
def default_fields(type, fields) do
|
||||
Enum.map(fields, fn
|
||||
{ key, _ } = pair when is_atom(key) -> pair
|
||||
key when is_atom(key) -> { key, nil }
|
||||
other -> raise ArgumentError, message: "#{type} fields must be atoms, got: #{inspect other}"
|
||||
end)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Splits a keywords list into fields and types.
|
||||
|
||||
This logic is shared by records and structs.
|
||||
"""
|
||||
def split_fields_and_types(kv) do
|
||||
if Keyword.keyword?(kv) do
|
||||
split_fields_and_types(kv, [], [])
|
||||
else
|
||||
{kv, []}
|
||||
end
|
||||
end
|
||||
|
||||
defp split_fields_and_types([{field, {:::, _, [default, type]}}|t], fields, types) do
|
||||
split_fields_and_types(t, [{field, default}|fields], [{field, type}|types])
|
||||
end
|
||||
|
||||
defp split_fields_and_types([{field, default}|t], fields, types) do
|
||||
split_fields_and_types(t, [{field, default}|fields], [{field, quote(do: term)}|types])
|
||||
end
|
||||
|
||||
defp split_fields_and_types([field|t], fields, types) do
|
||||
split_fields_and_types(t, [field|fields], [{field, quote(do: term)}|types])
|
||||
end
|
||||
|
||||
defp split_fields_and_types([], fields, types) do
|
||||
{:lists.reverse(fields), :lists.reverse(types)}
|
||||
end
|
||||
|
||||
@doc """
|
||||
Callback invoked from record/0 and record/1 macros.
|
||||
"""
|
||||
def access(atom, fields, args, caller) do
|
||||
cond do
|
||||
is_atom(args) ->
|
||||
index(atom, fields, args)
|
||||
Keyword.keyword?(args) ->
|
||||
create(atom, fields, args, caller)
|
||||
true ->
|
||||
raise ArgumentError,
|
||||
message: "expected arguments to be a compile time atom or keywords, got: #{Macro.to_string args}"
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Callback invoked from the record/2 macro.
|
||||
"""
|
||||
def access(atom, fields, record, args, caller) do
|
||||
cond do
|
||||
is_atom(args) ->
|
||||
get(atom, fields, record, args)
|
||||
Keyword.keyword?(args) ->
|
||||
update(atom, fields, record, args, caller)
|
||||
true ->
|
||||
raise ArgumentError,
|
||||
message: "expected arguments to be a compile time atom or keywords, got: #{Macro.to_string args}"
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Gets the index of field.
|
||||
"""
|
||||
def index(atom, fields, field) do
|
||||
if index = find_index(fields, field, 0) do
|
||||
index - 1 # Convert to Elixir index
|
||||
else
|
||||
raise ArgumentError, message: "record #{inspect atom} does not have the key: #{inspect field}"
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Creates a new record with the given default fields and keyword values.
|
||||
"""
|
||||
def create(atom, fields, keyword, caller) do
|
||||
in_match = Macro.Env.in_match?(caller)
|
||||
|
||||
{match, remaining} =
|
||||
Enum.map_reduce(fields, keyword, fn({field, default}, each_keyword) ->
|
||||
new_fields =
|
||||
case Keyword.has_key?(each_keyword, field) do
|
||||
true -> Keyword.get(each_keyword, field)
|
||||
false ->
|
||||
case in_match do
|
||||
true -> {:_, [], nil}
|
||||
false -> Macro.escape(default)
|
||||
end
|
||||
end
|
||||
|
||||
{new_fields, Keyword.delete(each_keyword, field)}
|
||||
end)
|
||||
|
||||
case remaining do
|
||||
[] ->
|
||||
{:{}, [], [atom|match]}
|
||||
_ ->
|
||||
keys = for {key, _} <- remaining, do: key
|
||||
raise ArgumentError, message: "record #{inspect atom} does not have the key: #{inspect hd(keys)}"
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Updates a record given by var with the given keyword.
|
||||
"""
|
||||
def update(atom, fields, var, keyword, caller) do
|
||||
if Macro.Env.in_match?(caller) do
|
||||
raise ArgumentError, message: "cannot invoke update style macro inside match"
|
||||
end
|
||||
|
||||
Enum.reduce keyword, var, fn({key, value}, acc) ->
|
||||
index = find_index(fields, key, 0)
|
||||
if index do
|
||||
quote do
|
||||
:erlang.setelement(unquote(index), unquote(acc), unquote(value))
|
||||
end
|
||||
else
|
||||
raise ArgumentError, message: "record #{inspect atom} does not have the key: #{inspect key}"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Gets a record key from the given var.
|
||||
"""
|
||||
def get(atom, fields, var, key) do
|
||||
index = find_index(fields, key, 0)
|
||||
if index do
|
||||
quote do
|
||||
:erlang.element(unquote(index), unquote(var))
|
||||
end
|
||||
else
|
||||
raise ArgumentError, message: "record #{inspect atom} does not have the key: #{inspect key}"
|
||||
end
|
||||
end
|
||||
|
||||
defp find_index([{k, _}|_], k, i), do: i + 2
|
||||
defp find_index([{_, _}|t], k, i), do: find_index(t, k, i + 1)
|
||||
defp find_index([], _k, _i), do: nil
|
||||
end
|
||||
@@ -1,6 +1,16 @@
|
||||
defmodule Record.Deprecated do
|
||||
@moduledoc false
|
||||
|
||||
def defexception(name, fields, opts) do
|
||||
quote do
|
||||
fields = unquote(fields)
|
||||
defmodule unquote(name) do
|
||||
defexception fields
|
||||
unquote(Keyword.get opts, :do)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def defrecord(name, fields, opts) do
|
||||
block = Keyword.get(opts, :do, nil)
|
||||
record_check!(fields)
|
||||
@@ -23,7 +33,7 @@ defmodule Record.Deprecated do
|
||||
end
|
||||
|
||||
defp record_check!([{field, {:::, _, [_, _]}}|_]) when is_atom(field) do
|
||||
raise ArgumentError, message: "typespecs are not supported inlined with defrecord, " <>
|
||||
raise ArgumentError, "typespecs are not supported inlined with defrecord, " <>
|
||||
"please use record_type instead"
|
||||
end
|
||||
|
||||
@@ -40,7 +50,7 @@ defmodule Record.Deprecated do
|
||||
Record.Deprecated.defmacros(name, fields, __ENV__, tag)
|
||||
|
||||
if def_type do
|
||||
type = binary_to_atom(atom_to_binary(name) <> "_t")
|
||||
type = String.to_atom(Atom.to_string(name) <> "_t")
|
||||
@typep unquote(type)() :: {unquote(tag || name), unquote_splicing(types)}
|
||||
end
|
||||
end
|
||||
@@ -156,6 +166,9 @@ defmodule Record.Deprecated do
|
||||
|
||||
defmacro __before_compile__(_) do
|
||||
quote do
|
||||
@spec __record__(atom) :: term
|
||||
@spec __record__(atom, term) :: term
|
||||
@spec __record__(atom, term, term) :: term
|
||||
def __record__(:optimizable), do: @record_optimizable
|
||||
end
|
||||
end
|
||||
@@ -179,13 +192,13 @@ defmodule Record.Deprecated 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}"
|
||||
raise ArgumentError, "record #{inspect atom} does not have the key: #{inspect arg}"
|
||||
end
|
||||
end
|
||||
|
||||
def access(atom, fields, keyword, caller) do
|
||||
unless is_keyword(keyword) do
|
||||
raise ArgumentError, message: "expected contents inside brackets to be a keyword list or an atom, got: #{inspect keyword}"
|
||||
raise ArgumentError, "expected contents inside brackets to be a keyword list or an atom, got: #{inspect keyword}"
|
||||
end
|
||||
|
||||
in_match = Macro.Env.in_match?(caller)
|
||||
@@ -215,7 +228,7 @@ defmodule Record.Deprecated do
|
||||
{:{}, [], [atom|match]}
|
||||
_ ->
|
||||
keys = for {key, _} <- remaining, do: key
|
||||
raise ArgumentError, message: "record #{inspect atom} does not have the key: #{inspect hd(keys)}"
|
||||
raise ArgumentError, "record #{inspect atom} does not have the key: #{inspect hd(keys)}"
|
||||
end
|
||||
end
|
||||
|
||||
@@ -226,17 +239,17 @@ defmodule Record.Deprecated do
|
||||
is_keyword(args) ->
|
||||
update(atom, fields, record, args, caller)
|
||||
true ->
|
||||
raise ArgumentError, message: "expected arguments to be a compile time atom or keywords"
|
||||
raise ArgumentError, "expected arguments to be a compile time atom or keywords"
|
||||
end
|
||||
end
|
||||
|
||||
defp update(atom, fields, var, keyword, caller) do
|
||||
unless is_keyword(keyword) do
|
||||
raise ArgumentError, message: "expected arguments to be compile time keywords"
|
||||
raise ArgumentError, "expected arguments to be compile time keywords"
|
||||
end
|
||||
|
||||
if Macro.Env.in_match?(caller) do
|
||||
raise ArgumentError, message: "cannot invoke update style macro inside match context"
|
||||
raise ArgumentError, "cannot invoke update style macro inside match context"
|
||||
end
|
||||
|
||||
Enum.reduce keyword, var, fn({key, value}, acc) ->
|
||||
@@ -246,7 +259,7 @@ defmodule Record.Deprecated do
|
||||
:erlang.setelement(unquote(index + 2), unquote(acc), unquote(value))
|
||||
end
|
||||
else
|
||||
raise ArgumentError, message: "record #{inspect atom} does not have the key: #{inspect key}"
|
||||
raise ArgumentError, "record #{inspect atom} does not have the key: #{inspect key}"
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -258,7 +271,7 @@ defmodule Record.Deprecated do
|
||||
:erlang.element(unquote(index + 2), unquote(var))
|
||||
end
|
||||
else
|
||||
raise ArgumentError, message: "record #{inspect atom} does not have the key: #{inspect key}"
|
||||
raise ArgumentError, "record #{inspect atom} does not have the key: #{inspect key}"
|
||||
end
|
||||
end
|
||||
|
||||
@@ -297,7 +310,7 @@ defmodule Record.Deprecated do
|
||||
# the given key from the ordered dict, falling back to the
|
||||
# default value if one does not exist.
|
||||
atom_selective = for {k, v} <- values, do: initialize_lookup(k, v)
|
||||
string_selective = for {k, v} <- values, do: initialize_lookup(atom_to_binary(k), v)
|
||||
string_selective = for {k, v} <- values, do: initialize_lookup(Atom.to_string(k), v)
|
||||
|
||||
quote do
|
||||
@doc false
|
||||
@@ -338,7 +351,7 @@ defmodule Record.Deprecated do
|
||||
end
|
||||
|
||||
defp accessors([{key, _default}|t], i) do
|
||||
update = binary_to_atom "update_" <> atom_to_binary(key)
|
||||
update = String.to_atom "update_" <> Atom.to_string(key)
|
||||
|
||||
contents = quote do
|
||||
@doc false
|
||||
@@ -372,7 +385,7 @@ defmodule Record.Deprecated do
|
||||
for {key, _default} <- values, do: updater_lookup(key, key, values)
|
||||
|
||||
string_fields =
|
||||
for {key, _default} <- values, do: updater_lookup(atom_to_binary(key), key, values)
|
||||
for {key, _default} <- values, do: updater_lookup(Atom.to_string(key), key, values)
|
||||
|
||||
atom_contents = quote do: {__MODULE__, unquote_splicing(atom_fields)}
|
||||
string_contents = quote do: {__MODULE__, unquote_splicing(string_fields)}
|
||||
@@ -446,7 +459,7 @@ defmodule Record.Deprecated do
|
||||
end
|
||||
|
||||
defp accessor_specs([{key, _default, spec}|t], i, acc) do
|
||||
update = binary_to_atom "update_" <> atom_to_binary(key)
|
||||
update = String.to_atom "update_" <> Atom.to_string(key)
|
||||
|
||||
contents = quote do
|
||||
@spec unquote(key)(t) :: unquote(spec)
|
||||
@@ -481,14 +494,14 @@ defmodule Record.Deprecated do
|
||||
end
|
||||
|
||||
defp check_value(atom, other) when is_tuple(other) do
|
||||
for(i <- tuple_to_list(other), do: check_value(atom, i))
|
||||
for(i <- Tuple.to_list(other), do: check_value(atom, i))
|
||||
other
|
||||
end
|
||||
|
||||
defp check_value(atom, other) when is_function(other) do
|
||||
unless :erlang.fun_info(other, :env) == {:env, []} and
|
||||
:erlang.fun_info(other, :type) == {:type, :external} do
|
||||
raise ArgumentError, message: "record field default value #{inspect atom} can only contain " <>
|
||||
raise ArgumentError, "record field default value #{inspect atom} can only contain " <>
|
||||
"functions that point to an existing &Mod.fun/arity"
|
||||
end
|
||||
other
|
||||
|
||||
@@ -4,7 +4,7 @@ defmodule Record.Extractor do
|
||||
# Retrieve a record definition from an Erlang file using
|
||||
# the same lookup as the *include* attribute from Erlang modules.
|
||||
def extract(name, from: file) when is_binary(file) do
|
||||
file = List.from_char_data!(file)
|
||||
file = String.to_char_list(file)
|
||||
|
||||
realfile =
|
||||
case :code.where_is_file(file) do
|
||||
@@ -18,11 +18,11 @@ defmodule Record.Extractor do
|
||||
# Retrieve a record definition from an Erlang file using
|
||||
# the same lookup as the *include_lib* attribute from Erlang modules.
|
||||
def extract(name, from_lib: file) when is_binary(file) do
|
||||
[app|path] = :filename.split(List.from_char_data!(file))
|
||||
[app|path] = :filename.split(String.to_char_list(file))
|
||||
|
||||
case :code.lib_dir(list_to_atom(app)) do
|
||||
case :code.lib_dir(List.to_atom(app)) do
|
||||
{:error, _} ->
|
||||
raise ArgumentError, message: "lib file #{file} could not be found"
|
||||
raise ArgumentError, "lib file #{file} could not be found"
|
||||
libpath ->
|
||||
extract_record name, :filename.join([libpath|path])
|
||||
end
|
||||
@@ -35,7 +35,7 @@ defmodule Record.Extractor do
|
||||
if record = List.keyfind(records, name, 0) do
|
||||
parse_record(record, form)
|
||||
else
|
||||
raise ArgumentError, message: "no record #{name} found at #{file}"
|
||||
raise ArgumentError, "no record #{name} found at #{file}"
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
+12
-16
@@ -70,7 +70,9 @@ defmodule Regex do
|
||||
|
||||
defstruct re_pattern: nil :: term, source: "" :: binary, opts: "" :: binary
|
||||
|
||||
defexception CompileError, message: "regex could not be compiled"
|
||||
defmodule CompileError do
|
||||
defexception message: "regex could not be compiled"
|
||||
end
|
||||
|
||||
@doc """
|
||||
Compiles the regular expression.
|
||||
@@ -338,17 +340,8 @@ defmodule Regex do
|
||||
def split(regex, string, options \\ [])
|
||||
|
||||
def split(%Regex{re_pattern: compiled}, string, options) when is_binary(string) do
|
||||
if options[:global] != nil do
|
||||
IO.write :stderr, "Support for :global in Regex.split/3 is deprecated, please use :parts instead\n#{Exception.format_stacktrace}"
|
||||
end
|
||||
parts =
|
||||
cond do
|
||||
Keyword.get(options, :global) == false -> 2
|
||||
p = Keyword.get(options, :parts) -> if p == 0, do: :infinity, else: p
|
||||
true -> :infinity
|
||||
end
|
||||
|
||||
opts = [return: :binary, parts: parts]
|
||||
parts = Keyword.get(options, :parts, :infinity)
|
||||
opts = [return: :binary, parts: zero_to_infinity(parts)]
|
||||
splits = :re.split(string, compiled, opts)
|
||||
|
||||
if Keyword.get(options, :trim, false) do
|
||||
@@ -358,6 +351,9 @@ defmodule Regex do
|
||||
end
|
||||
end
|
||||
|
||||
defp zero_to_infinity(0), do: :infinity
|
||||
defp zero_to_infinity(n), do: n
|
||||
|
||||
@doc ~S"""
|
||||
Receives a regex, a binary and a replacement, returns a new
|
||||
binary where the all matches are replaced by replacement.
|
||||
@@ -414,9 +410,9 @@ defmodule Regex do
|
||||
:nomatch ->
|
||||
string
|
||||
{:match, [mlist|t]} when is_list(mlist) ->
|
||||
apply_list(string, replacement, [mlist|t]) |> iodata_to_binary
|
||||
apply_list(string, replacement, [mlist|t]) |> IO.iodata_to_binary
|
||||
{:match, slist} ->
|
||||
apply_list(string, replacement, [slist]) |> iodata_to_binary
|
||||
apply_list(string, replacement, [slist]) |> IO.iodata_to_binary
|
||||
end
|
||||
end
|
||||
|
||||
@@ -434,7 +430,7 @@ defmodule Regex do
|
||||
|
||||
defp precompile_replacement(<<?\\, rest :: binary>>) when byte_size(rest) > 0 do
|
||||
{ns, rest} = pick_int(rest)
|
||||
[list_to_integer(ns) | precompile_replacement(rest)]
|
||||
[List.to_integer(ns) | precompile_replacement(rest)]
|
||||
end
|
||||
|
||||
defp precompile_replacement(<<x, rest :: binary>>) do
|
||||
@@ -488,7 +484,7 @@ defmodule Regex do
|
||||
end
|
||||
|
||||
defp apply_replace(string, repl, indexes) do
|
||||
indexes = list_to_tuple(indexes)
|
||||
indexes = List.to_tuple(indexes)
|
||||
|
||||
for part <- repl do
|
||||
cond do
|
||||
|
||||
@@ -50,9 +50,6 @@ defmodule Set do
|
||||
case unquote(set) do
|
||||
%{__struct__: x} when is_atom(x) ->
|
||||
x
|
||||
x when is_tuple(x) ->
|
||||
IO.write :stderr, "Using records with the Set module is deprecated, please use structs instead\n#{Exception.format_stacktrace}"
|
||||
elem(x, 0)
|
||||
x ->
|
||||
unsupported_set(x)
|
||||
end
|
||||
@@ -334,6 +331,6 @@ defmodule Set do
|
||||
end
|
||||
|
||||
defp unsupported_set(set) do
|
||||
raise ArgumentError, message: "unsupported set: #{inspect set}"
|
||||
raise ArgumentError, "unsupported set: #{inspect set}"
|
||||
end
|
||||
end
|
||||
|
||||
+81
-69
@@ -89,55 +89,7 @@ defmodule Stream do
|
||||
returns an anonymous function may return a struct in future releases.
|
||||
"""
|
||||
|
||||
defmodule Lazy do
|
||||
@moduledoc false
|
||||
defstruct enum: nil, funs: [], accs: [], done: nil
|
||||
end
|
||||
|
||||
defimpl Enumerable, for: Lazy do
|
||||
@compile :inline_list_funs
|
||||
|
||||
def reduce(lazy, acc, fun) do
|
||||
do_reduce(lazy, acc, fn x, [acc] ->
|
||||
{reason, acc} = fun.(x, acc)
|
||||
{reason, [acc]}
|
||||
end)
|
||||
end
|
||||
|
||||
def count(_lazy) do
|
||||
{:error, __MODULE__}
|
||||
end
|
||||
|
||||
def member?(_lazy, _value) do
|
||||
{:error, __MODULE__}
|
||||
end
|
||||
|
||||
defp do_reduce(%Lazy{enum: enum, funs: funs, accs: accs, done: done}, acc, fun) do
|
||||
composed = :lists.foldl(fn fun, acc -> fun.(acc) end, fun, funs)
|
||||
do_each(&Enumerable.reduce(enum, &1, composed),
|
||||
done && {done, fun}, :lists.reverse(accs), acc)
|
||||
end
|
||||
|
||||
defp do_each(reduce, done, accs, {command, acc}) do
|
||||
case reduce.({command, [acc|accs]}) do
|
||||
{:suspended, [acc|accs], continuation} ->
|
||||
{:suspended, acc, &do_each(continuation, done, accs, &1)}
|
||||
{:halted, [acc|_]} ->
|
||||
{:halted, acc}
|
||||
{:done, [acc|_] = accs} ->
|
||||
case done do
|
||||
nil ->
|
||||
{:done, acc}
|
||||
{done, fun} ->
|
||||
case done.(fun).(accs) do
|
||||
{:cont, [acc|_]} -> {:done, acc}
|
||||
{:halt, [acc|_]} -> {:halted, acc}
|
||||
{:suspend, [acc|_]} -> {:suspended, acc, &({:done, elem(&1, 1)})}
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
defstruct enum: nil, funs: [], accs: [], done: nil
|
||||
|
||||
@type acc :: any
|
||||
@type element :: any
|
||||
@@ -538,7 +490,7 @@ defmodule Stream do
|
||||
|
||||
"""
|
||||
@spec take(Enumerable.t, non_neg_integer) :: Enumerable.t
|
||||
def take(_enum, 0), do: %Lazy{enum: []}
|
||||
def take(_enum, 0), do: %Stream{enum: []}
|
||||
|
||||
def take(enum, n) when n > 0 do
|
||||
lazy enum, n, fn(f1) -> R.take(f1) end
|
||||
@@ -590,7 +542,7 @@ defmodule Stream do
|
||||
lazy enum, n, fn(f1) -> R.take_every(n, f1) end
|
||||
end
|
||||
|
||||
def take_every(_enum, 0), do: %Lazy{enum: []}
|
||||
def take_every(_enum, 0), do: %Stream{enum: []}
|
||||
|
||||
@doc """
|
||||
Lazily takes elements of the enumerable while the given
|
||||
@@ -656,8 +608,9 @@ defmodule Stream do
|
||||
user.(val, user_acc)
|
||||
catch
|
||||
kind, reason ->
|
||||
stacktrace = System.stacktrace
|
||||
next.({:halt, next_acc})
|
||||
:erlang.raise(kind, reason, :erlang.get_stacktrace)
|
||||
:erlang.raise(kind, reason, stacktrace)
|
||||
else
|
||||
{[], user_acc} ->
|
||||
do_transform(user_acc, user, fun, next_acc, next, inner_acc, inner)
|
||||
@@ -700,8 +653,9 @@ defmodule Stream do
|
||||
next.({:halt, next_acc})
|
||||
{:halted, h}
|
||||
kind, reason ->
|
||||
stacktrace = System.stacktrace
|
||||
next.({:halt, next_acc})
|
||||
:erlang.raise(kind, reason, :erlang.get_stacktrace)
|
||||
:erlang.raise(kind, reason, stacktrace)
|
||||
else
|
||||
{_, acc} ->
|
||||
do_transform(user_acc, user, fun, next_acc, next, {:cont, acc}, inner)
|
||||
@@ -835,8 +789,9 @@ defmodule Stream do
|
||||
do_zip(zips, acc, callback, [], [])
|
||||
catch
|
||||
kind, reason ->
|
||||
stacktrace = System.stacktrace
|
||||
do_zip_close(zips)
|
||||
:erlang.raise(kind, reason, :erlang.get_stacktrace)
|
||||
:erlang.raise(kind, reason, stacktrace)
|
||||
else
|
||||
{:next, buffer, acc} ->
|
||||
do_zip(buffer, acc, callback)
|
||||
@@ -856,7 +811,7 @@ defmodule Stream do
|
||||
end
|
||||
|
||||
defp do_zip([], acc, callback, list, buffer) do
|
||||
zipped = list_to_tuple(:lists.reverse(list))
|
||||
zipped = List.to_tuple(:lists.reverse(list))
|
||||
{:next, :lists.reverse(buffer), callback.(zipped, acc)}
|
||||
end
|
||||
|
||||
@@ -886,8 +841,9 @@ defmodule Stream do
|
||||
@spec cycle(Enumerable.t) :: Enumerable.t
|
||||
def cycle(enumerable) do
|
||||
fn acc, fun ->
|
||||
reduce = &Enumerable.reduce(enumerable, &1, fun)
|
||||
do_cycle(reduce, reduce, acc)
|
||||
inner = &do_cycle_each(&1, &2, fun)
|
||||
outer = &Enumerable.reduce(enumerable, &1, inner)
|
||||
do_cycle(outer, outer, acc)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -900,16 +856,26 @@ defmodule Stream do
|
||||
end
|
||||
|
||||
defp do_cycle(reduce, cycle, acc) do
|
||||
case reduce.(acc) do
|
||||
{:done, acc} ->
|
||||
do_cycle(cycle, cycle, {:cont, acc})
|
||||
{:halted, acc} ->
|
||||
try do
|
||||
reduce.(acc)
|
||||
catch
|
||||
{:stream_cycle, acc} ->
|
||||
{:halted, acc}
|
||||
else
|
||||
{state, acc} when state in [:done, :halted] ->
|
||||
do_cycle(cycle, cycle, {:cont, acc})
|
||||
{:suspended, acc, continuation} ->
|
||||
{:suspended, acc, &do_cycle(continuation, cycle, &1)}
|
||||
end
|
||||
end
|
||||
|
||||
defp do_cycle_each(x, acc, f) do
|
||||
case f.(x, acc) do
|
||||
{:halt, h} -> throw({:stream_cycle, h})
|
||||
{_, _} = o -> o
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Emit a sequence of values, starting with `start_value`. Successive
|
||||
values are generated by calling `next_fun` on the previous value.
|
||||
@@ -1006,8 +972,9 @@ defmodule Stream do
|
||||
end
|
||||
catch
|
||||
kind, reason ->
|
||||
stacktrace = System.stacktrace
|
||||
after_fun.(next_acc)
|
||||
:erlang.raise(kind, reason, :erlang.get_stacktrace)
|
||||
:erlang.raise(kind, reason, stacktrace)
|
||||
else
|
||||
nil ->
|
||||
after_fun.(next_acc)
|
||||
@@ -1045,7 +1012,7 @@ defmodule Stream do
|
||||
|
||||
defp do_unfold(next_acc, next_fun, {:cont, acc}, fun) do
|
||||
case next_fun.(next_acc) do
|
||||
nil -> {:done, acc}
|
||||
nil -> {:done, acc}
|
||||
{v, next_acc} -> do_unfold(next_acc, next_fun, fun.(v, acc), fun)
|
||||
end
|
||||
end
|
||||
@@ -1054,18 +1021,63 @@ defmodule Stream do
|
||||
|
||||
@compile {:inline, lazy: 2, lazy: 3, lazy: 4}
|
||||
|
||||
defp lazy(%Lazy{funs: funs} = lazy, fun),
|
||||
defp lazy(%Stream{funs: funs} = lazy, fun),
|
||||
do: %{lazy | funs: [fun|funs] }
|
||||
defp lazy(enum, fun),
|
||||
do: %Lazy{enum: enum, funs: [fun]}
|
||||
do: %Stream{enum: enum, funs: [fun]}
|
||||
|
||||
defp lazy(%Lazy{funs: funs, accs: accs} = lazy, acc, fun),
|
||||
defp lazy(%Stream{funs: funs, accs: accs} = lazy, acc, fun),
|
||||
do: %{lazy | funs: [fun|funs], accs: [acc|accs] }
|
||||
defp lazy(enum, acc, fun),
|
||||
do: %Lazy{enum: enum, funs: [fun], accs: [acc]}
|
||||
do: %Stream{enum: enum, funs: [fun], accs: [acc]}
|
||||
|
||||
defp lazy(%Lazy{done: nil, funs: funs, accs: accs} = lazy, acc, fun, done),
|
||||
defp lazy(%Stream{done: nil, funs: funs, accs: accs} = lazy, acc, fun, done),
|
||||
do: %{lazy | funs: [fun|funs], accs: [acc|accs], done: done}
|
||||
defp lazy(enum, acc, fun, done),
|
||||
do: %Lazy{enum: enum, funs: [fun], accs: [acc], done: done}
|
||||
do: %Stream{enum: enum, funs: [fun], accs: [acc], done: done}
|
||||
end
|
||||
|
||||
defimpl Enumerable, for: Stream do
|
||||
@compile :inline_list_funs
|
||||
|
||||
def reduce(lazy, acc, fun) do
|
||||
do_reduce(lazy, acc, fn x, [acc] ->
|
||||
{reason, acc} = fun.(x, acc)
|
||||
{reason, [acc]}
|
||||
end)
|
||||
end
|
||||
|
||||
def count(_lazy) do
|
||||
{:error, __MODULE__}
|
||||
end
|
||||
|
||||
def member?(_lazy, _value) do
|
||||
{:error, __MODULE__}
|
||||
end
|
||||
|
||||
defp do_reduce(%Stream{enum: enum, funs: funs, accs: accs, done: done}, acc, fun) do
|
||||
composed = :lists.foldl(fn fun, acc -> fun.(acc) end, fun, funs)
|
||||
do_each(&Enumerable.reduce(enum, &1, composed),
|
||||
done && {done, fun}, :lists.reverse(accs), acc)
|
||||
end
|
||||
|
||||
defp do_each(reduce, done, accs, {command, acc}) do
|
||||
case reduce.({command, [acc|accs]}) do
|
||||
{:suspended, [acc|accs], continuation} ->
|
||||
{:suspended, acc, &do_each(continuation, done, accs, &1)}
|
||||
{:halted, [acc|_]} ->
|
||||
{:halted, acc}
|
||||
{:done, [acc|_] = accs} ->
|
||||
case done do
|
||||
nil ->
|
||||
{:done, acc}
|
||||
{done, fun} ->
|
||||
case done.(fun).(accs) do
|
||||
{:cont, [acc|_]} -> {:done, acc}
|
||||
{:halt, [acc|_]} -> {:halted, acc}
|
||||
{:suspend, [acc|_]} -> {:suspended, acc, &({:done, elem(&1, 1)})}
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
+127
-85
@@ -18,12 +18,8 @@ defmodule String do
|
||||
* `Kernel.binary_part/3` - retrieves part of the binary
|
||||
* `Kernel.bit_size/1` and `Kernel.byte_size/1` - size related functions
|
||||
* `Kernel.is_bitstring/1` and `Kernel.is_binary/1` - type checking function
|
||||
* Plus a number of conversion functions, like `Kernel.binary_to_atom/1`,
|
||||
`Kernel.binary_to_integer/2`, `Kernel.binary_to_term/1` and their inverses,
|
||||
like `Kernel.integer_to_binary/2`
|
||||
|
||||
Finally, the [`:binary` module](http://erlang.org/doc/man/binary.html)
|
||||
provides a few other functions that work on the byte level.
|
||||
* Plus a number of functions for working with binaries (bytes)
|
||||
[in the `:binary` module](http://erlang.org/doc/man/binary.html)
|
||||
|
||||
## Codepoints and graphemes
|
||||
|
||||
@@ -147,6 +143,7 @@ defmodule String do
|
||||
def printable?(<<?\b, t :: binary>>), do: printable?(t)
|
||||
def printable?(<<?\f, t :: binary>>), do: printable?(t)
|
||||
def printable?(<<?\e, t :: binary>>), do: printable?(t)
|
||||
def printable?(<<?\d, t :: binary>>), do: printable?(t)
|
||||
def printable?(<<?\a, t :: binary>>), do: printable?(t)
|
||||
|
||||
def printable?(<<>>), do: true
|
||||
@@ -172,8 +169,9 @@ defmodule String do
|
||||
defdelegate split(binary), to: String.Unicode
|
||||
|
||||
@doc ~S"""
|
||||
Divides a string into substrings based on a pattern,
|
||||
returning a list of these substrings. The pattern can
|
||||
Divides a string into substrings based on a pattern.
|
||||
|
||||
Returns a list of these substrings. The pattern can
|
||||
be a string, a list of strings or a regular expression.
|
||||
|
||||
The string is split into as many parts as possible by
|
||||
@@ -236,22 +234,16 @@ defmodule String do
|
||||
def split(binary, "", options), do: split(binary, ~r""u, options)
|
||||
|
||||
def split(binary, pattern, options) do
|
||||
if options[:global] != nil do
|
||||
IO.write :stderr, "Support for :global in String.split/3 is deprecated, please use :parts instead\n#{Exception.format_stacktrace}"
|
||||
end
|
||||
if Regex.regex?(pattern) do
|
||||
Regex.split(pattern, binary, options)
|
||||
else
|
||||
splits = case options[:parts] do
|
||||
num when is_number(num) and num > 0 ->
|
||||
split_parts(binary, pattern, num - 1)
|
||||
num ->
|
||||
if options[:global] != false or num == 0 do
|
||||
splits =
|
||||
case Keyword.get(options, :parts, :infinity) do
|
||||
num when is_number(num) and num > 0 ->
|
||||
split_parts(binary, pattern, num - 1)
|
||||
_ ->
|
||||
:binary.split(binary, pattern, [:global])
|
||||
else
|
||||
:binary.split(binary, pattern, [])
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if Keyword.get(options, :trim, false) do
|
||||
for split <- splits, split != "", do: split
|
||||
@@ -266,10 +258,9 @@ defmodule String do
|
||||
defp split_parts("", _pattern, _num, parts), do: Enum.reverse([""|parts])
|
||||
defp split_parts(binary, _pattern, 0, parts), do: Enum.reverse([binary|parts])
|
||||
defp split_parts(binary, pattern, num, parts) do
|
||||
[head|tail] = :binary.split(binary, pattern)
|
||||
case tail do
|
||||
[] -> Enum.reverse([head|parts])
|
||||
[str] -> split_parts(str, pattern, num - 1, [head|parts])
|
||||
case :binary.split(binary, pattern) do
|
||||
[head] -> Enum.reverse([head|parts])
|
||||
[head, rest] -> split_parts(rest, pattern, num - 1, [head|parts])
|
||||
end
|
||||
end
|
||||
|
||||
@@ -654,7 +645,7 @@ defmodule String do
|
||||
do_reverse(next_grapheme(rest), [grapheme|acc])
|
||||
end
|
||||
|
||||
defp do_reverse(nil, acc), do: iodata_to_binary(acc)
|
||||
defp do_reverse(nil, acc), do: IO.iodata_to_binary(acc)
|
||||
|
||||
@doc """
|
||||
Returns a binary `subject` duplicated `n` times.
|
||||
@@ -1102,10 +1093,6 @@ defmodule String do
|
||||
Kernel.match?({0, _}, :binary.match(string, prefix))
|
||||
end
|
||||
|
||||
defp do_starts_with(_, _) do
|
||||
raise ArgumentError
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns `true` if `string` ends with any of the suffixes given, otherwise
|
||||
`false`. `suffixes` can be either a single suffix or a list of suffixes.
|
||||
@@ -1143,10 +1130,6 @@ defmodule String do
|
||||
(suffix_size <= string_size) and (:nomatch != :binary.match(string, suffix, [scope: scope]))
|
||||
end
|
||||
|
||||
defp do_ends_with(_, _) do
|
||||
raise ArgumentError
|
||||
end
|
||||
|
||||
@doc """
|
||||
Check if `string` matches the given regular expression.
|
||||
|
||||
@@ -1199,49 +1182,123 @@ defmodule String do
|
||||
:nomatch != :binary.match(string, match)
|
||||
end
|
||||
|
||||
defp do_contains(_, _) do
|
||||
raise ArgumentError
|
||||
end
|
||||
|
||||
@doc false
|
||||
def to_char_list(list) do
|
||||
IO.write :stderr, "String.to_char_list/1 is deprecated, please use List.from_char_data/1 instead\n#{Exception.format_stacktrace}"
|
||||
List.from_char_data(list)
|
||||
end
|
||||
|
||||
@doc false
|
||||
def to_char_list!(list) do
|
||||
IO.write :stderr, "String.to_char_list!/1 is deprecated, please use List.from_char_data!/1 instead\n#{Exception.format_stacktrace}"
|
||||
List.from_char_data!(list)
|
||||
end
|
||||
|
||||
@doc false
|
||||
def from_char_list(list) do
|
||||
IO.write :stderr, "String.from_char_list/1 is deprecated, please use String.from_char_data/1 instead\n#{Exception.format_stacktrace}"
|
||||
from_char_data(list)
|
||||
end
|
||||
|
||||
@doc false
|
||||
def from_char_list!(list) do
|
||||
IO.write :stderr, "String.from_char_list!/1 is deprecated, please use String.from_char_data!/1 instead\n#{Exception.format_stacktrace}"
|
||||
from_char_data!(list)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Converts char data (a list of integers and strings) into a string.
|
||||
|
||||
If a string is given, returns the string itself.
|
||||
Converts a string into a char list.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> String.from_char_data([0x00E6, 0x00DF])
|
||||
{:ok, "æß"}
|
||||
iex> String.to_char_list("æß")
|
||||
'æß'
|
||||
|
||||
iex> String.from_char_data([0x0061, "bc"])
|
||||
{:ok, "abc"}
|
||||
Notice that this function expect a list of integer representing
|
||||
UTF-8 codepoints. If you have a raw binary, you must instead use
|
||||
[the `:binary` module](http://erlang.org/doc/man/binary.html).
|
||||
"""
|
||||
@spec to_char_list(t) :: char_list
|
||||
def to_char_list(string) when is_binary(string) do
|
||||
case :unicode.characters_to_list(string) do
|
||||
result when is_list(result) ->
|
||||
result
|
||||
|
||||
{:error, encoded, rest} ->
|
||||
raise UnicodeConversionError, encoded: encoded, rest: rest, kind: :invalid
|
||||
|
||||
{:incomplete, encoded, rest} ->
|
||||
raise UnicodeConversionError, encoded: encoded, rest: rest, kind: :incomplete
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Converts a string to an atom.
|
||||
|
||||
Currently Elixir does not support conversions from strings
|
||||
which contains Unicode codepoints greater than 0xFF.
|
||||
|
||||
Inlined by the compiler.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> String.to_atom("my_atom")
|
||||
:my_atom
|
||||
|
||||
"""
|
||||
@spec from_char_data(char_data) :: {:ok, String.t} | {:error, binary, binary} | {:incomplete, binary, binary}
|
||||
@spec to_atom(String.t) :: atom
|
||||
def to_atom(string) do
|
||||
:erlang.binary_to_atom(string, :utf8)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Converts a string to an existing atom.
|
||||
|
||||
Currently Elixir does not support conversions from strings
|
||||
which contains Unicode codepoints greater than 0xFF.
|
||||
|
||||
Inlined by the compiler.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> :my_atom
|
||||
iex> String.to_existing_atom("my_atom")
|
||||
:my_atom
|
||||
|
||||
iex> String.to_existing_atom("this_atom_will_never_exist")
|
||||
** (ArgumentError) argument error
|
||||
|
||||
"""
|
||||
@spec to_existing_atom(String.t) :: atom
|
||||
def to_existing_atom(string) do
|
||||
:erlang.binary_to_existing_atom(string, :utf8)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns a integer whose text representation is `string`.
|
||||
|
||||
Inlined by the compiler.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> String.to_integer("123")
|
||||
123
|
||||
|
||||
"""
|
||||
@spec to_integer(String.t) :: integer
|
||||
def to_integer(string) do
|
||||
:erlang.binary_to_integer(string)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns an integer whose text representation is `string` in base `base`.
|
||||
|
||||
Inlined by the compiler.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> String.to_integer("3FF", 16)
|
||||
1023
|
||||
|
||||
"""
|
||||
@spec to_integer(String.t, pos_integer) :: integer
|
||||
def to_integer(string, base) do
|
||||
:erlang.binary_to_integer(string, base)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns a float whose text representation is `string`.
|
||||
|
||||
Inlined by the compiler.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> String.to_float("2.2017764e+0")
|
||||
2.2017764
|
||||
|
||||
"""
|
||||
@spec to_float(String.t) :: float
|
||||
def to_float(string) do
|
||||
:erlang.binary_to_float(string)
|
||||
end
|
||||
|
||||
@doc false
|
||||
def from_char_data(binary) when is_binary(binary) do
|
||||
binary
|
||||
end
|
||||
@@ -1259,22 +1316,7 @@ defmodule String do
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Converts char data (a list of integers and strings) into a string.
|
||||
|
||||
In case the conversion fails, it raises a `UnicodeConversionError`.
|
||||
If a string is given, returns the string itself.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> String.from_char_data!([0x00E6, 0x00DF])
|
||||
"æß"
|
||||
|
||||
iex> String.from_char_data!([0x0061, "bc"])
|
||||
"abc"
|
||||
|
||||
"""
|
||||
@spec from_char_data!(char_data) :: String.t | no_return
|
||||
@doc false
|
||||
def from_char_data!(binary) when is_binary(binary) do
|
||||
binary
|
||||
end
|
||||
|
||||
@@ -18,23 +18,16 @@ defprotocol String.Chars do
|
||||
end
|
||||
|
||||
defimpl String.Chars, for: Atom do
|
||||
@doc """
|
||||
Convert the atom literally to a binary, except
|
||||
`nil` which is converted to an empty string.
|
||||
"""
|
||||
def to_string(nil) do
|
||||
""
|
||||
end
|
||||
|
||||
def to_string(atom) do
|
||||
atom_to_binary(atom)
|
||||
Atom.to_string(atom)
|
||||
end
|
||||
end
|
||||
|
||||
defimpl String.Chars, for: BitString do
|
||||
@doc """
|
||||
Returns the given binary or raises an error for bitstrings.
|
||||
"""
|
||||
def to_string(thing) when is_binary(thing) do
|
||||
thing
|
||||
end
|
||||
@@ -48,38 +41,17 @@ defimpl String.Chars, for: BitString do
|
||||
end
|
||||
|
||||
defimpl String.Chars, for: List do
|
||||
@doc """
|
||||
Consider the list is an iolist and converts it
|
||||
to a binary. This allows a list of binaries, or
|
||||
a charlist, or a mix of both, to be converted
|
||||
successfully.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> to_string('foo')
|
||||
"foo"
|
||||
|
||||
iex> to_string(["foo", 'bar'])
|
||||
"foobar"
|
||||
|
||||
"""
|
||||
def to_string(char_list), do: String.from_char_data!(char_list)
|
||||
def to_string(char_list), do: List.to_string(char_list)
|
||||
end
|
||||
|
||||
defimpl String.Chars, for: Integer do
|
||||
@doc """
|
||||
Simply converts the integer to a string.
|
||||
"""
|
||||
def to_string(thing) do
|
||||
integer_to_binary(thing)
|
||||
Integer.to_string(thing)
|
||||
end
|
||||
end
|
||||
|
||||
defimpl String.Chars, for: Float do
|
||||
@doc """
|
||||
Simply converts the float to a string.
|
||||
"""
|
||||
def to_string(thing) do
|
||||
iodata_to_binary(:io_lib_format.fwrite_g(thing))
|
||||
IO.iodata_to_binary(:io_lib_format.fwrite_g(thing))
|
||||
end
|
||||
end
|
||||
|
||||
@@ -10,7 +10,7 @@ defmodule StringIO do
|
||||
|
||||
"""
|
||||
|
||||
use GenServer.Behaviour
|
||||
use GenServer
|
||||
|
||||
@doc """
|
||||
Creates an IO device.
|
||||
@@ -106,12 +106,12 @@ defmodule StringIO do
|
||||
end
|
||||
|
||||
defp io_request({:put_chars, chars}, %{output: output} = s) do
|
||||
{:ok, %{s | output: << output :: binary, String.from_char_data!(chars) :: binary >>}}
|
||||
{:ok, %{s | output: << output :: binary, IO.chardata_to_string(chars) :: binary >>}}
|
||||
end
|
||||
|
||||
defp io_request({:put_chars, m, f, as}, %{output: output} = s) do
|
||||
chars = apply(m, f, as)
|
||||
{:ok, %{s | output: << output :: binary, String.from_char_data!(chars) :: binary >>}}
|
||||
{:ok, %{s | output: << output :: binary, IO.chardata_to_string(chars) :: binary >>}}
|
||||
end
|
||||
|
||||
defp io_request({:put_chars, _encoding, chars}, s) do
|
||||
@@ -183,7 +183,7 @@ defmodule StringIO do
|
||||
{error, s}
|
||||
{result, input} ->
|
||||
if capture_prompt do
|
||||
output = << output :: binary, String.from_char_data!(prompt) :: binary >>
|
||||
output = << output :: binary, IO.chardata_to_string(prompt) :: binary >>
|
||||
end
|
||||
|
||||
{result, %{s | input: input, output: output}}
|
||||
@@ -231,7 +231,7 @@ defmodule StringIO do
|
||||
{result, input} = do_get_line(chars, encoding)
|
||||
|
||||
if capture_prompt do
|
||||
output = << output :: binary, String.from_char_data!(prompt) :: binary >>
|
||||
output = << output :: binary, IO.chardata_to_string(prompt) :: binary >>
|
||||
end
|
||||
|
||||
{result, %{s | input: input, output: output}}
|
||||
@@ -261,7 +261,7 @@ defmodule StringIO do
|
||||
{result, input, count} = do_get_until(chars, encoding, mod, fun, args)
|
||||
|
||||
if capture_prompt do
|
||||
output = << output :: binary, :binary.copy(String.from_char_data!(prompt), count) :: binary >>
|
||||
output = << output :: binary, :binary.copy(IO.chardata_to_string(prompt), count) :: binary >>
|
||||
end
|
||||
|
||||
input =
|
||||
@@ -335,6 +335,6 @@ defmodule StringIO do
|
||||
send from, {:io_reply, reply_as, reply}
|
||||
end
|
||||
|
||||
defp to_reply(list) when is_list(list), do: String.from_char_data!(list)
|
||||
defp to_reply(list) when is_list(list), do: IO.chardata_to_string(list)
|
||||
defp to_reply(other), do: other
|
||||
end
|
||||
|
||||
@@ -0,0 +1,383 @@
|
||||
defmodule Supervisor do
|
||||
@moduledoc """
|
||||
A behaviour module for implementing supevision functionality.
|
||||
|
||||
A supervisor is a process which supervises other processes called
|
||||
child processes. Supervisors are used to build an hierarchical process
|
||||
structure called a supervision tree, a nice way to structure a fault
|
||||
tolerant applications.
|
||||
|
||||
A supervisor implemented using this module will have a standard set
|
||||
of interface functions and include functionality for tracing and error
|
||||
reporting. It will also fit into an supervision tree.
|
||||
|
||||
## Example
|
||||
|
||||
In order to define a supervisor, we need to first define a child process
|
||||
that is going to be supervised. In order to do so, we will define a GenServer
|
||||
that represents a stack:
|
||||
|
||||
defmodule Stack do
|
||||
use GenServer
|
||||
|
||||
def start_link(state) do
|
||||
GenServer.start_link(__MODULE__, state, [name: :sup_stack])
|
||||
end
|
||||
|
||||
def handle_call(:pop, _from, [h|t]) do
|
||||
{:reply, h, t}
|
||||
end
|
||||
|
||||
def handle_cast({:push, h}, _from, t) do
|
||||
{:noreply, [h|t]}
|
||||
end
|
||||
end
|
||||
|
||||
We can now define our supervisor and start it as follows:
|
||||
|
||||
# Import helpers for defining supervisors
|
||||
import Supervisor.Spec
|
||||
|
||||
# We are going to supervise the Stack server which will
|
||||
# be started with a single argument [:hello]
|
||||
children = [
|
||||
worker(Stack, [[:hello]])
|
||||
]
|
||||
|
||||
# Start the supervisor with our one children
|
||||
{:ok, pid} = Supervisor.start_link(children, strategy: :one_for_one)
|
||||
|
||||
Notice that when starting the GenServer, we have registered it
|
||||
with name `:sup_stack`, which allows us to call it directly and
|
||||
get what is on the stack:
|
||||
|
||||
GenServer.call(:sup_stack, :pop)
|
||||
#=> :hello
|
||||
|
||||
GenServer.cast(:sup_stack, {:push, :world})
|
||||
#=> :ok
|
||||
|
||||
GenServer.call(:sup_stack, :pop)
|
||||
#=> :world
|
||||
|
||||
However, there is a bug in our stack server. If we call `:pop` and
|
||||
the stack is empty, it is going to crash because no clause matches.
|
||||
Let's try it:
|
||||
|
||||
GenServer.call(:sup_stack, :pop)
|
||||
=ERROR REPORT====
|
||||
|
||||
Luckily, since the server is being supervised by a supervisor, the
|
||||
supervisor will automatically start a new one, with the default stack
|
||||
of `[:hello]` like before:
|
||||
|
||||
GenServer.call(:sup_stack, :pop) == :hello
|
||||
|
||||
Supervisors support different strategies, in the example above, we
|
||||
have chosen `:one_for_one`. Furthermore, each supervisor can have many
|
||||
workers and supervisors as children, each of them with their specific
|
||||
configuration, shutdown values and restart strategies.
|
||||
|
||||
Continue reading this module to learn more about supervision strategies
|
||||
and then follow to the `Supervisor.Spec` module documentation to learn
|
||||
about the specification for workers and supervisors.
|
||||
|
||||
## Module-based supervisors
|
||||
|
||||
In the example above, a supervisor was dynamically created by passing
|
||||
the supervision structure to `start_link/2`. However, supervisors
|
||||
can also be created by explicitly defining a supevision module:
|
||||
|
||||
defmodule MyApp.Supervisor do
|
||||
use Supervisor
|
||||
|
||||
def start_link do
|
||||
Supervisor.start_link(__MODULE__, [])
|
||||
end
|
||||
|
||||
def init([]) do
|
||||
import Supervisor.Spec
|
||||
|
||||
children = [
|
||||
worker(Stack, [[:hello]])
|
||||
]
|
||||
|
||||
supervisor(children, strategy: :one_for_one)
|
||||
end
|
||||
end
|
||||
|
||||
You may want to use a module-based supervisor if:
|
||||
|
||||
* You need to do some particular action on supervisor
|
||||
initialization, like setting up a ETS table;
|
||||
|
||||
* You want to perform partial hot-code swapping of the
|
||||
tree. For example, if you add or remove a children,
|
||||
the module-based supervision will add and remove the
|
||||
new children directly, while the dynamic supervision
|
||||
requires the whole tree to be restarted in order to
|
||||
perform such swaps;
|
||||
|
||||
## Strategies
|
||||
|
||||
* `:one_for_one` - If a child process terminates, only that
|
||||
process is restarted;
|
||||
|
||||
* `:one_for_all` - If a child process terminates, all other child
|
||||
processes are terminated and then all child processes, including
|
||||
the terminated one, are restarted;
|
||||
|
||||
* `:rest_for_one` - If a child process terminates, the "rest" of
|
||||
the child processes, i.e. the child processes after the terminated
|
||||
process in start order, are terminated. Then the terminated child
|
||||
process and the rest of the child processes are restarted;
|
||||
|
||||
* `:simple_one_for_one` - Similar to `:one_for_one` but suits better
|
||||
when dynamically attaching children. This strategy requires the
|
||||
supervisor specification to contain only one children. Many functions
|
||||
in this module behave slightly differently when this strategy is
|
||||
used;
|
||||
|
||||
## Name registering
|
||||
|
||||
A supervisor is bound to the same name registering rules as a `GenServer`.
|
||||
Read more about it in the `GenServer` docs.
|
||||
"""
|
||||
|
||||
@doc false
|
||||
defmacro __using__(_) do
|
||||
quote location: :keep do
|
||||
@behaviour :supervisor
|
||||
import Supervisor.Spec
|
||||
end
|
||||
end
|
||||
|
||||
@typedoc "Return values of `start_link` functions"
|
||||
@type on_start :: {:ok, pid} | :ignore |
|
||||
{:error, {:already_started, pid} | {:shutdown, term} | term}
|
||||
|
||||
@typedoc "Return values of `start_child` functions"
|
||||
@type on_start_child :: {:ok, child} | {:ok, child, info :: term} |
|
||||
{:error, {:already_started, child} | :already_present | term}
|
||||
|
||||
@type child :: pid | :undefined
|
||||
|
||||
@typedoc "The Supervisor name"
|
||||
@type name :: atom | {:global, term} | {:via, module, term}
|
||||
|
||||
@typedoc "Options used by the `start*` functions"
|
||||
@type options :: [name: name,
|
||||
strategy: Supervisor.Spec.strategy,
|
||||
max_restarts: non_neg_integer,
|
||||
max_seconds: non_neg_integer]
|
||||
|
||||
@typedoc "The supervisor reference"
|
||||
@type supervisor :: pid | name | {atom, node}
|
||||
|
||||
@doc """
|
||||
Starts a supervisor with the given children.
|
||||
|
||||
A strategy is required to be given as an option. Furthermore,
|
||||
the `:max_restarts` and `:max_seconds` value can be configured
|
||||
as described in `Supervisor.Spec.supervise/2` docs.
|
||||
|
||||
The options can also be used to register a supervisor name,
|
||||
the supported values are described under the `Name Registering`
|
||||
section in the `GenServer` module docs.
|
||||
|
||||
If the supervisor and its child processes are successfully created
|
||||
(i.e. if all child process start functions return `{:ok, child}`,
|
||||
`{:ok, child, info}`, or `:ignore`) the function returns
|
||||
`{:ok, pid}`, where `pid` is the pid of the supervisor. If there
|
||||
already exists a process with the specified name the function returns
|
||||
`{:error, {:already_started, pid}}`, where pid is the pid of that
|
||||
process.
|
||||
|
||||
If any child process start function fails or returns an error tuple or
|
||||
an erroneous value, the supervisor will first terminate all already
|
||||
started child processes with reason `:shutdown` and then terminate
|
||||
itself and return `{:error, {:shutdown, reason}}`.
|
||||
"""
|
||||
@spec start_link([tuple], options) :: on_start
|
||||
def start_link(children, options) when is_list(children) do
|
||||
spec = Supervisor.Spec.supervise(children, options)
|
||||
start_link(Supervisor.Default, spec, options)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Starts a supervisor module with the given `arg`.
|
||||
|
||||
To start the supervisor, the `init/1` callback will be invoked
|
||||
in the given module. The `init/1` callback must return a
|
||||
supervision specification which can be created with the help
|
||||
of `Supervisor.Spec` module.
|
||||
|
||||
If the `init/1` callback returns `:ignore`, this function returns
|
||||
`:ignore` as well and the supervisor terminates with reason `:normal`.
|
||||
If it fails or returns an incorrect value, this function returns
|
||||
`{:error, term} where term is a term with information about the
|
||||
error, and the supervisor terminates with reason `term`.
|
||||
|
||||
The `:name` option can also be given in order to register a supervisor
|
||||
name, the supported values are described under the `Name Registering`
|
||||
section in the `GenServer` module docs.
|
||||
|
||||
Other failure conditions are specified in `start_link/2` docs.
|
||||
"""
|
||||
@spec start_link(module, term, options) :: on_start
|
||||
def start_link(module, arg, options \\ []) when is_list(options) do
|
||||
case Keyword.get(options, :name) do
|
||||
nil ->
|
||||
:supervisor.start_link(module, arg)
|
||||
atom when is_atom(atom) ->
|
||||
:supervisor.start_link({:local, atom}, module, arg)
|
||||
other when is_tuple(other) ->
|
||||
:supervisor.start_link(other, module, arg)
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Dynamically adds and starts a child specification to the supervisor.
|
||||
|
||||
`child_spec` should be a valid child specification (unless the supervisor
|
||||
is a `:simple_one_for_one` supervisor, see below). The child process will
|
||||
be started as defined in the child specification.
|
||||
|
||||
In the case of a `:simple_one_for_one`, the child specification defined in
|
||||
the supervisor will be used and instead of a `child_spec`, an arbitrary list
|
||||
of terms is expected. The child process will then be started by appending
|
||||
the given list to the existing function arguments in the child specification.
|
||||
|
||||
If there already exists a child specification with the specified id,
|
||||
`child_spec` is discarded and the function returns an error with `:already_started`
|
||||
or `:already_present` if the corresponding child process is running or not.
|
||||
|
||||
If the child process start function returns `{:ok, child}` or `{:ok, child, info}`,
|
||||
the child specification and pid is added to the supervisor and the function returns
|
||||
the same value.
|
||||
|
||||
If the child process start function returns `:ignore, the child specification is
|
||||
added to the supervisor, the pid is set to undefined and the function returns
|
||||
`{:ok, :undefined}`.
|
||||
|
||||
If the child process start function returns an error tuple or an erroneous value,
|
||||
or if it fails, the child specification is discarded and the function returns
|
||||
`{:error, error}` where error is a term containing information about the error
|
||||
and child specification.
|
||||
"""
|
||||
@spec start_child(supervisor, Supervisor.Spec.spec | [term]) :: on_start_child
|
||||
defdelegate start_child(supervisor, child_spec_or_args), to: :supervisor
|
||||
|
||||
@doc """
|
||||
Terminates the given pid or child id.
|
||||
|
||||
If the supervisor is not a `simple_one_for_one`, the child id is expected
|
||||
and the process, if there is one, is terminated and, the child specification is
|
||||
kept unless the child is temporary.
|
||||
|
||||
In case of a `simple_one_for_one` supervisor, a pid is expected. If the child
|
||||
specification identifier is given instead instead of a `pid`, the function will
|
||||
return `{:error, :simple_one_for_one}`.
|
||||
|
||||
Non-temporary child process may later be restarted by the supervisor. The child
|
||||
process can also be restarted explicitly by calling `restart_child/2`. Use
|
||||
`delete_child/2` to remove the child specification.
|
||||
|
||||
If successful, the function returns ok. If there is no child specification or
|
||||
pid, the function returns `{:error, :not_found}`.
|
||||
"""
|
||||
@spec terminate_child(supervisor, pid | Supervisor.Spec.child_id) :: :ok | {:error, error}
|
||||
when error: :not_found | :simple_one_for_one
|
||||
defdelegate terminate_child(supervisor, pid_or_child_id), to: :supervisor
|
||||
|
||||
@doc """
|
||||
Deletes the child specification identified by `child_id`.
|
||||
|
||||
The corresponding child process must not be running, use `terminate_child/2`
|
||||
to terminate it.
|
||||
|
||||
If successful, the function returns `:ok`. This function may error with an
|
||||
appropriate error tuple if the `child_id` is not found, or if the current
|
||||
process is running or being restarted.
|
||||
|
||||
This operation is not supported by `simple_one_for_one` supervisors.
|
||||
"""
|
||||
@spec delete_child(supervisor, Supervisor.Spec.child_id) :: :ok | {:error, error}
|
||||
when error: :not_found | :simple_one_for_one | :running | :restarting
|
||||
defdelegate delete_child(supervisor, child_id), to: :supervisor
|
||||
|
||||
@doc """
|
||||
Restarts a child process identified by `child_id`.
|
||||
|
||||
The child specification must exist and the corresponding child process must not
|
||||
be running.
|
||||
|
||||
Note that for temporary children, the child specification is automatically deleted
|
||||
when the child terminates, and thus it is not possible to restart such children.
|
||||
|
||||
If the child process start function returns `{:ok, child}` or
|
||||
`{:ok, child, info}`, the pid is added to the supervisor and the function returns
|
||||
the same value.
|
||||
|
||||
If the child process start function returns `:ignore`, the pid remains set to
|
||||
`:undefined` and the function returns `{:ok, :undefined}`.
|
||||
|
||||
This function may error with an appropriate error tuple if the `child_id` is not
|
||||
found, or if the current process is running or being restarted.
|
||||
|
||||
If the child process start function returns an error tuple or an erroneous value,
|
||||
or if it fails, the function returns `{:error, error}`.
|
||||
|
||||
This operation is not supported by `simple_one_for_one` supervisors.
|
||||
"""
|
||||
@spec restart_child(supervisor, Supervisor.Spec.child_id) ::
|
||||
{:ok, child} | {:ok, child, term} | {:error, error}
|
||||
when error: :not_found | :simple_one_for_one | :running | :restarting | term
|
||||
defdelegate restart_child(supervisor, child_id), to: :supervisor
|
||||
|
||||
@doc """
|
||||
Returns a list with information about all children.
|
||||
|
||||
Note that calling this function when supervising a large number of children
|
||||
under low memory conditions can cause an out of memory exception.
|
||||
|
||||
This function returns a list of tuples containing:
|
||||
|
||||
* the `id` - as defined in the child specification or `:undefined` in the case
|
||||
of a `simple_one_for_one` supervisor;
|
||||
|
||||
* the `child` - the pid of the corresponding child process, the atom `:restarting`
|
||||
if the process is about to be restarted or `:undefined` if there is no such process;
|
||||
|
||||
* the `type` - `:worker` or `:supervisor` as defined in the child specification;
|
||||
|
||||
* the `modules` as defined in the child specification;
|
||||
"""
|
||||
@spec which_children(supervisor) ::
|
||||
[{Supervisor.Spec.child_id | :undefined,
|
||||
child | :restarting,
|
||||
Supervisor.Spec.worker,
|
||||
Supervisor.Spec.modules}]
|
||||
defdelegate which_children(supervisor), to: :supervisor
|
||||
|
||||
@doc """
|
||||
Returns a map containing count value for the supervisor.
|
||||
|
||||
The map contains the following keys:
|
||||
|
||||
* `:specs` - the total count of children, dead or alive;
|
||||
|
||||
* `:active` - the count of all actively running child processes managed by this supervisor;
|
||||
|
||||
* `:supervisors` - the count of all supervisors whether or not the child process is still alive;
|
||||
|
||||
* `:workers` - the count of all workers, whether or not the child process is still alive;
|
||||
|
||||
"""
|
||||
@spec count_children(supervisor) ::
|
||||
[specs: non_neg_integer, active: non_neg_integer,
|
||||
supervisors: non_neg_integer, workers: non_neg_integer]
|
||||
def count_children(supervisor) do
|
||||
:supervisor.count_children(supervisor) |> :maps.from_list
|
||||
end
|
||||
end
|
||||
@@ -1,33 +1,5 @@
|
||||
defmodule Supervisor.Behaviour do
|
||||
@moduledoc """
|
||||
This module is a convenience to define Supervisor
|
||||
callbacks in Elixir. By using this module, you get
|
||||
the module behaviour automatically tagged as
|
||||
`:supervisor` and some helper functions are imported
|
||||
to make defining supervisors easier.
|
||||
|
||||
For more information on supervisors, please check the
|
||||
remaining functions defined in this module or refer to
|
||||
the following:
|
||||
|
||||
http://www.erlang.org/doc/man/supervisor.html
|
||||
http://www.erlang.org/doc/design_principles/sup_princ.html
|
||||
http://learnyousomeerlang.com/supervisors
|
||||
|
||||
## Example
|
||||
|
||||
defmodule ExUnit.Sup do
|
||||
use Supervisor.Behaviour
|
||||
|
||||
def init(user_options) do
|
||||
tree = [ worker(ExUnit.Runner, [user_options]) ]
|
||||
supervise(tree, strategy: :one_for_one)
|
||||
end
|
||||
end
|
||||
|
||||
{:ok, pid} = :supervisor.start_link(MyServer, [])
|
||||
|
||||
"""
|
||||
@moduledoc false
|
||||
|
||||
@doc false
|
||||
defmacro __using__(_) do
|
||||
@@ -81,7 +53,7 @@ defmodule Supervisor.Behaviour do
|
||||
"""
|
||||
def supervise(children, options) do
|
||||
unless strategy = options[:strategy] do
|
||||
raise ArgumentError, message: "expected :strategy option to be given to supervise"
|
||||
raise ArgumentError, "expected :strategy option to be given to supervise"
|
||||
end
|
||||
|
||||
maxR = Keyword.get(options, :max_restarts, 5)
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
defmodule Supervisor.Default do
|
||||
@moduledoc false
|
||||
@behaviour :supervisor
|
||||
|
||||
@doc """
|
||||
Supevisor callback that simply returns the given args.
|
||||
|
||||
This is the supervisor used by `Supervisor.start_link/2`.
|
||||
"""
|
||||
def init(args) do
|
||||
args
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,247 @@
|
||||
defmodule Supervisor.Spec do
|
||||
@moduledoc """
|
||||
Convenience functions for defining a supervision specification.
|
||||
|
||||
## Example
|
||||
|
||||
By using the functions in this module one can define a supervisor
|
||||
and start it with `Supervisor.start_link/2`:
|
||||
|
||||
import Supervisor.Spec
|
||||
|
||||
children = [
|
||||
worker(MyWorker, [arg1, arg2, arg3]),
|
||||
supervisor(MySupervisor, [arg1])
|
||||
]
|
||||
|
||||
Supervisor.start_link(children, strategy: :one_for_one)
|
||||
|
||||
In many situations, it may be handy to define supervisors backed
|
||||
by a module:
|
||||
|
||||
defmodule MySupervisor do
|
||||
use Supervisor
|
||||
|
||||
def start_link(arg) do
|
||||
Supervisor.start_link(__MODULE__, arg)
|
||||
end
|
||||
|
||||
def init(arg) do
|
||||
children = [
|
||||
worker(MyWorker, [arg], restart: :temporary)
|
||||
]
|
||||
|
||||
supervise(children, strategy: :simple_one_for_one)
|
||||
end
|
||||
end
|
||||
|
||||
Notice in this case we don't have to explicitly import
|
||||
`Supervisor.Spec` as `use Supervisor` automatically does so.
|
||||
|
||||
Explicit supervisors as above are required when there is a need to:
|
||||
|
||||
1. partialy change the supervision tree during hot-code swaps;
|
||||
|
||||
2. define supervisors inside other supervisors;
|
||||
|
||||
3. perform actions inside the supervision `init/1` callback.
|
||||
For example, you may want to start an ETS table that is linked to
|
||||
the supervisor (i.e. if the supervision tree needs to be restarted,
|
||||
the ETS table must be restarted too);
|
||||
|
||||
## Supervisor and worker options
|
||||
|
||||
In the example above, we have defined workers and supervisors
|
||||
and each accepts the following options:
|
||||
|
||||
* `:id` - a name used to identify the child specification
|
||||
internally by the supervisor. Defaults to the given module
|
||||
name;
|
||||
|
||||
* `:function` - the function to invoke on the child to start it;
|
||||
|
||||
* `:restart` - defines when the child process should restart;
|
||||
|
||||
* `:shutdown` - defines how a child process should be terminated;
|
||||
|
||||
* `:modules` - it should be a list with one element `[module]`,
|
||||
where module is the name of the callback module only if the
|
||||
child process is a `Supervisor` or `GenServer` If the child
|
||||
process is a `GenEvent`, modules should be `:dynamic`;
|
||||
|
||||
### Restart values
|
||||
|
||||
The following restart values are supported:
|
||||
|
||||
* `:permanent` - the child process is always restarted;
|
||||
|
||||
* `:temporary` - the child process is never restarted (not even
|
||||
when the supervisor's strategy is `:rest_for_one` or `:one_for_all`);
|
||||
|
||||
* `:transient` - the child process is restarted only if it
|
||||
terminates abnormally, i.e. with another exit reason than
|
||||
`:normal`, `:shutdown` or `{:shutdown, term}`;
|
||||
|
||||
### Shutdown values
|
||||
|
||||
The following shutdown values are supported:
|
||||
|
||||
* `:brutal_kill` - the child process is unconditionally terminated
|
||||
using `exit(child, :kill)`;
|
||||
|
||||
* `:infinity` - if the child process is a supervisor, it is a mechanism
|
||||
to give the subtree enough time to shutdown. It can also be used with
|
||||
workers with care;
|
||||
|
||||
* Finally, it can also be any integer meaning that the supervisor tells
|
||||
the child process to terminate by calling `Process.exit(child, :shutdown)`
|
||||
and then waits for an exit signal back. If no exit signal is received
|
||||
within the specified time (in miliseconds), the child process is
|
||||
unconditionally terminated using `Process.exit(child, :kill)`;
|
||||
"""
|
||||
|
||||
@typedoc "Supported strategies"
|
||||
@type strategy :: :simple_one_for_one | :one_for_one | :one_for_all | :rest_for_one
|
||||
|
||||
@typedoc "Supported restart values"
|
||||
@type restart :: :permanent | :transient | :temporary
|
||||
|
||||
@typedoc "Supported shutdown values"
|
||||
@type shutdown :: :brutal_kill | :infinity | non_neg_integer
|
||||
|
||||
@typedoc "Supported worker values"
|
||||
@type worker :: :worker | :supervisor
|
||||
|
||||
@typedoc "Supported module values"
|
||||
@type modules :: :dynamic | [module]
|
||||
|
||||
@typedoc "Supported id values"
|
||||
@type child_id :: term
|
||||
|
||||
@typedoc "The supervisor specification"
|
||||
@type spec :: {child_id,
|
||||
start_fun :: {module, atom, [term]},
|
||||
restart,
|
||||
shutdown,
|
||||
worker,
|
||||
modules}
|
||||
|
||||
@doc """
|
||||
Receives a list of children (workers or supervisors) to
|
||||
supervise and a set of options.
|
||||
|
||||
Returns a tuple containing the supervisor specification.
|
||||
|
||||
## Examples
|
||||
|
||||
supervise children, strategy: :one_for_one
|
||||
|
||||
## Options
|
||||
|
||||
* `:strategy` - the restart strategy option It can be either
|
||||
`:one_for_one`, `:rest_for_one`, `:one_for_all` and
|
||||
`:simple_one_for_one`. You can learn more about strategies
|
||||
in the `Supervisor` module;
|
||||
|
||||
* `:max_restarts` - the maximum amount of restarts allowed in
|
||||
a time frame. Defaults to 5;
|
||||
|
||||
* `:max_seconds` - the time frame in which max_restarts applies.
|
||||
Defaults to 5;
|
||||
|
||||
The `:strategy` option is required and by default maximum 5 restarts
|
||||
are allowed in 5 seconds. Please check the `Supervisor` module for
|
||||
a complete description of the available strategies.
|
||||
"""
|
||||
@spec supervise([spec], strategy: strategy,
|
||||
max_restarts: non_neg_integer,
|
||||
max_seconds: non_neg_integer) :: {:ok, tuple}
|
||||
def supervise(children, options) do
|
||||
unless strategy = options[:strategy] do
|
||||
raise ArgumentError, "expected :strategy option to be given"
|
||||
end
|
||||
|
||||
maxR = Keyword.get(options, :max_restarts, 5)
|
||||
maxS = Keyword.get(options, :max_seconds, 5)
|
||||
|
||||
assert_unique_ids(Enum.map(children, &elem(&1, 0)))
|
||||
{:ok, {{strategy, maxR, maxS}, children}}
|
||||
end
|
||||
|
||||
defp assert_unique_ids([id|rest]) do
|
||||
if id in rest do
|
||||
raise ArgumentError,
|
||||
"duplicated id #{inspect id} found in the supervisor specification, " <>
|
||||
"please explicitly pass the :id option when defining this worker/supervisor"
|
||||
else
|
||||
assert_unique_ids(rest)
|
||||
end
|
||||
end
|
||||
|
||||
defp assert_unique_ids([]) do
|
||||
:ok
|
||||
end
|
||||
|
||||
@doc """
|
||||
Defines the given `module` as a worker which will be started
|
||||
with the given arguments.
|
||||
|
||||
worker ExUnit.Runner, [], restart: :permanent
|
||||
|
||||
By default, the function `:start_link` is invoked on the given
|
||||
module. Overall, the default values for the options are:
|
||||
|
||||
[id: module,
|
||||
function: :start_link,
|
||||
restart: :permanent,
|
||||
shutdown: 5000,
|
||||
modules: [module]]
|
||||
|
||||
Check `Supervisor.Spec` module docs for more information on
|
||||
the options.
|
||||
"""
|
||||
@spec worker(module, [term], [restart: restart, shutdown: shutdown,
|
||||
id: term, function: atom, modules: modules]) :: spec
|
||||
def worker(module, args, options \\ []) do
|
||||
child(:worker, module, args, options)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Defines the given `module` as a supervisor which will be started
|
||||
with the given arguments.
|
||||
|
||||
supervisor ExUnit.Runner, [], restart: :permanent
|
||||
|
||||
By default, the function `:start_link` is invoked on the given
|
||||
module. Overall, the default values for the options are:
|
||||
|
||||
[id: module,
|
||||
function: :start_link,
|
||||
restart: :permanent,
|
||||
shutdown: :infinity,
|
||||
modules: [module]]
|
||||
|
||||
Check `Supervisor.Spec` module docs for more information on
|
||||
the options.
|
||||
"""
|
||||
@spec supervisor(module, [term], [restart: restart, shutdown: shutdown,
|
||||
id: term, function: atom, modules: modules]) :: spec
|
||||
def supervisor(module, args, options \\ []) do
|
||||
options = Keyword.put_new(options, :shutdown, :infinity)
|
||||
child(:supervisor, module, args, options)
|
||||
end
|
||||
|
||||
defp child(type, module, args, options) do
|
||||
id = Keyword.get(options, :id, module)
|
||||
modules = Keyword.get(options, :modules, modules(module))
|
||||
function = Keyword.get(options, :function, :start_link)
|
||||
restart = Keyword.get(options, :restart, :permanent)
|
||||
shutdown = Keyword.get(options, :shutdown, 5000)
|
||||
|
||||
{id, {module, function, args},
|
||||
restart, shutdown, type, modules}
|
||||
end
|
||||
|
||||
defp modules(GenEvent), do: :dynamic
|
||||
defp modules(module), do: [module]
|
||||
end
|
||||
+13
-13
@@ -44,7 +44,7 @@ defmodule System do
|
||||
|
||||
# Get the date at compilation time.
|
||||
defmacrop get_date do
|
||||
iodata_to_binary :httpd_util.rfc1123_date
|
||||
IO.iodata_to_binary :httpd_util.rfc1123_date
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -94,7 +94,7 @@ defmodule System do
|
||||
"""
|
||||
def cwd do
|
||||
case :file.get_cwd do
|
||||
{:ok, base} -> String.from_char_data!(base)
|
||||
{:ok, base} -> IO.chardata_to_string(base)
|
||||
_ -> nil
|
||||
end
|
||||
end
|
||||
@@ -193,7 +193,7 @@ defmodule System do
|
||||
{:ok, stat} ->
|
||||
case {stat.type, stat.access} do
|
||||
{:directory, access} when access in [:read_write, :write] ->
|
||||
String.from_char_data!(dir)
|
||||
IO.chardata_to_string(dir)
|
||||
_ ->
|
||||
nil
|
||||
end
|
||||
@@ -233,7 +233,7 @@ defmodule System do
|
||||
end
|
||||
|
||||
def cmd(command) when is_binary(command) do
|
||||
String.from_char_data! :os.cmd(List.from_char_data!(command))
|
||||
List.to_string :os.cmd(String.to_char_list(command))
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -256,9 +256,9 @@ defmodule System do
|
||||
end
|
||||
|
||||
def find_executable(program) when is_binary(program) do
|
||||
case :os.find_executable(List.from_char_data!(program)) do
|
||||
case :os.find_executable(String.to_char_list(program)) do
|
||||
false -> nil
|
||||
other -> String.from_char_data!(other)
|
||||
other -> List.to_string(other)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -271,7 +271,7 @@ defmodule System do
|
||||
@spec get_env() :: %{String.t => String.t}
|
||||
def get_env do
|
||||
Enum.into(:os.getenv, %{}, fn var ->
|
||||
var = String.from_char_data! var
|
||||
var = IO.chardata_to_string var
|
||||
[k, v] = String.split var, "=", parts: 2
|
||||
{k, v}
|
||||
end)
|
||||
@@ -286,9 +286,9 @@ defmodule System do
|
||||
"""
|
||||
@spec get_env(binary) :: binary | nil
|
||||
def get_env(varname) when is_binary(varname) do
|
||||
case :os.getenv(List.from_char_data!(varname)) do
|
||||
case :os.getenv(String.to_char_list(varname)) do
|
||||
false -> nil
|
||||
other -> String.from_char_data!(other)
|
||||
other -> List.to_string(other)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -301,7 +301,7 @@ defmodule System do
|
||||
See http://www.erlang.org/doc/man/os.html#getpid-0 for more info.
|
||||
"""
|
||||
@spec get_pid() :: binary
|
||||
def get_pid, do: iodata_to_binary(:os.getpid)
|
||||
def get_pid, do: IO.iodata_to_binary(:os.getpid)
|
||||
|
||||
@doc """
|
||||
Set an environment variable value.
|
||||
@@ -310,7 +310,7 @@ defmodule System do
|
||||
"""
|
||||
@spec put_env(binary, binary) :: :ok
|
||||
def put_env(varname, value) when is_binary(varname) and is_binary(value) do
|
||||
:os.putenv List.from_char_data!(varname), List.from_char_data!(value)
|
||||
:os.putenv String.to_char_list(varname), String.to_char_list(value)
|
||||
:ok
|
||||
end
|
||||
|
||||
@@ -332,7 +332,7 @@ defmodule System do
|
||||
"""
|
||||
@spec delete_env(String.t) :: :ok
|
||||
def delete_env(varname) do
|
||||
:os.unsetenv(List.from_char_data!(varname))
|
||||
:os.unsetenv(String.to_char_list(varname))
|
||||
:ok
|
||||
end
|
||||
|
||||
@@ -385,6 +385,6 @@ defmodule System do
|
||||
end
|
||||
|
||||
def halt(status) when is_binary(status) do
|
||||
:erlang.halt(List.from_char_data!(status))
|
||||
:erlang.halt(String.to_char_list(status))
|
||||
end
|
||||
end
|
||||
|
||||
@@ -0,0 +1,227 @@
|
||||
defmodule Task do
|
||||
@moduledoc """
|
||||
Conveniences for spawning and awaiting for tasks.
|
||||
|
||||
Tasks are processes that meant to execute one particular
|
||||
action throughout their life-cycle, often with little
|
||||
explicit communication with other processes. The most common
|
||||
use case for tasks is to compute a value asynchronously:
|
||||
|
||||
task = Task.async(fn -> do_some_work() end)
|
||||
res = do_some_other_work()
|
||||
res + Task.await(task)
|
||||
|
||||
Tasks spawned with async can be awaited on by its caller
|
||||
process (and only its caller) as shown in the example above.
|
||||
They are implemented by spawning a process that sends a message
|
||||
to the caller once the given computation is performed.
|
||||
|
||||
Besides `async/1` and `await/1`, tasks can also be used as part
|
||||
of supervision trees and dynamically spawned in remote nodes.
|
||||
We will explore all three scenarios next.
|
||||
|
||||
## async and await
|
||||
|
||||
The most common way to spawn a task is with `Task.async/1`. A new
|
||||
process will be created and this process is linked and monitored
|
||||
by the caller. However, the processes are unlinked right before
|
||||
the task finishes, allowing the proper error to be triggered only
|
||||
on `await/1`.
|
||||
|
||||
This implies three things:
|
||||
|
||||
1) In case the caller crashes, the task will be killed and its
|
||||
computation will abort;
|
||||
|
||||
2) In case the task crashes due to an error, the parent will
|
||||
crash only on `await/1`;
|
||||
|
||||
3) In case the task crashes because a linked process caused
|
||||
it to crash, the parent will crash immediately;
|
||||
|
||||
## Supervised tasks
|
||||
|
||||
It is also possible to spawn a task inside a supervision tree
|
||||
with `start_link/1` and `start_link/3`:
|
||||
|
||||
Task.start_link(fn -> IO.puts "ok" end)
|
||||
|
||||
Such can be mounted in your supervision tree as:
|
||||
|
||||
import Supervisor.Spec
|
||||
|
||||
children = [
|
||||
worker(Task, [fn -> IO.puts "ok" end])
|
||||
]
|
||||
|
||||
Since such tasks are supervised and not directly linked to
|
||||
the caller, they cannot be awaited on. For such reason,
|
||||
differently from `async/1`, `start_link/1` returns `{:ok, pid}`
|
||||
(which is the result expected by supervision trees).
|
||||
|
||||
Such tasks are useful as workers that run during your application
|
||||
life-cycle and rarely communicate with other workers. For example,
|
||||
a worker that pushes data to another server or a worker that consumes
|
||||
events from an event manager and writes it to a log file.
|
||||
|
||||
## Supervision trees
|
||||
|
||||
The `Task.Supervisor` module allows developers to start supervisors
|
||||
that dynamically supervise tasks:
|
||||
|
||||
{:ok, pid} = Task.Supervisor.start_link()
|
||||
Task.Supervisor.async(pid, fn -> do_work() end)
|
||||
|
||||
`Task.Supervisor` also makes it possible to spawn tasks in remote nodes as
|
||||
long as the supervisor is registered locally or globally:
|
||||
|
||||
# In the remote node
|
||||
Task.Supervisor.start_link(name: :tasks_sup)
|
||||
|
||||
# On the client
|
||||
Task.Supervisor.async({:tasks_sup, :remote@local}, fn -> do_work() end)
|
||||
|
||||
`Task.Supervisor` is more often started in your supervision tree as:
|
||||
|
||||
import Supervisor.Spec
|
||||
|
||||
children = [
|
||||
supervisor(Task.Supervisor, [[name: :tasks_sup]])
|
||||
]
|
||||
|
||||
Check `Task.Supervisor` for other operations supported by the Task supervisor.
|
||||
"""
|
||||
|
||||
@doc """
|
||||
The Task struct.
|
||||
|
||||
It contains two fields:
|
||||
|
||||
* `:pid` - the proces reference of the task process. It may be a pid
|
||||
or a tuple containing the process and node names;
|
||||
|
||||
* `:ref` - the task monitor reference;
|
||||
|
||||
"""
|
||||
defstruct pid: nil, ref: nil
|
||||
|
||||
@doc """
|
||||
Starts a task as part of a supervision tree.
|
||||
"""
|
||||
@spec start_link(fun) :: {:ok, pid}
|
||||
def start_link(fun) do
|
||||
start_link(:erlang, :apply, [fun, []])
|
||||
end
|
||||
|
||||
@doc """
|
||||
Starts a task as part of a supervision tree.
|
||||
"""
|
||||
@spec start_link(module, atom, [term]) :: {:ok, pid}
|
||||
def start_link(mod, fun, args) do
|
||||
Task.Supervised.start_link(:undefined, {mod, fun, args})
|
||||
end
|
||||
|
||||
@doc """
|
||||
Starts a task that can be awaited on.
|
||||
|
||||
This function spawns a process that is linked and monitored
|
||||
to the caller process. A `Task` struct is returned containing
|
||||
the relevant information.
|
||||
|
||||
## Task's message format
|
||||
|
||||
The reply sent by the task will be in the format `{ref, msg}`,
|
||||
where `ref` is the monitoring reference hold by the task.
|
||||
"""
|
||||
@spec async(fun) :: t
|
||||
def async(fun) do
|
||||
async(:erlang, :apply, [fun, []])
|
||||
end
|
||||
|
||||
@doc """
|
||||
Starts a task that can be awaited on.
|
||||
|
||||
Similar to `async/1`, but the task is specified by the given
|
||||
module, function and arguments.
|
||||
"""
|
||||
@spec async(module, atom, [term]) :: t
|
||||
def async(mod, fun, args) do
|
||||
mfa = {mod, fun, args}
|
||||
pid = :proc_lib.spawn_link(Task.Supervised, :async, [self(), mfa])
|
||||
ref = Process.monitor(pid)
|
||||
send(pid, {self(), ref})
|
||||
%Task{pid: pid, ref: ref}
|
||||
end
|
||||
|
||||
@doc """
|
||||
Awaits for a task reply.
|
||||
|
||||
A timeout, in miliseconds, can be given with default value
|
||||
of `5000`. In case the task process dies, this function will
|
||||
exit with the same reason as the task.
|
||||
"""
|
||||
@spec await(t, timeout) :: term | no_return
|
||||
def await(%Task{ref: ref}=task, timeout \\ 5000) do
|
||||
receive do
|
||||
{^ref, reply} ->
|
||||
Process.demonitor(ref, [:flush])
|
||||
reply
|
||||
{:DOWN, ^ref, _, _, :noconnection} ->
|
||||
mfa = {__MODULE__, :await, [task, timeout]}
|
||||
exit({{:nodedown, get_node(task.pid)}, mfa})
|
||||
{:DOWN, ^ref, _, _, reason} ->
|
||||
exit({reason, {__MODULE__, :await, [task, timeout]}})
|
||||
after
|
||||
timeout ->
|
||||
Process.demonitor(ref, [:flush])
|
||||
exit({:timeout, {__MODULE__, :await, [task, timeout]}})
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Receives a group of tasks and a message and finds
|
||||
a task that matches the given message.
|
||||
|
||||
This function returns a tuple with the task and the
|
||||
returned value in case the message matches a task that
|
||||
exited with success, it raises in case the found task
|
||||
failed or nil if no task was found.
|
||||
|
||||
This function is useful in situations where multiple
|
||||
tasks are spawned and their results are collected just
|
||||
later on. For example, a GenServer can spawn tasks,
|
||||
store the tasks in a list and later use `Task.find/2`
|
||||
to see if upcoming messages are from any of the tasks.
|
||||
"""
|
||||
@spec find([t], any) :: {term, t} | nil | no_return
|
||||
def find(tasks, msg)
|
||||
|
||||
def find(tasks, {ref, reply}) when is_reference(ref) do
|
||||
Enum.find_value tasks, fn
|
||||
%Task{ref: task_ref} = t when ref == task_ref ->
|
||||
Process.demonitor(ref, [:flush])
|
||||
{reply, t}
|
||||
%Task{} ->
|
||||
nil
|
||||
end
|
||||
end
|
||||
|
||||
def find(tasks, {:DOWN, ref, _, _, reason} = msg) when is_reference(ref) do
|
||||
find = fn(%Task{ref: task_ref}) -> task_ref == ref end
|
||||
case Enum.find(tasks, find) do
|
||||
%Task{pid: pid} when reason == :noconnection ->
|
||||
exit({{:nodedown, get_node(pid)}, {__MODULE__, :find, [tasks, msg]}})
|
||||
%Task{} ->
|
||||
exit({reason, {__MODULE__, :find, [tasks, msg]}})
|
||||
nil ->
|
||||
nil
|
||||
end
|
||||
end
|
||||
|
||||
def find(_tasks, _msg) do
|
||||
nil
|
||||
end
|
||||
|
||||
defp get_node({_, n}) when is_atom(n), do: n
|
||||
defp get_node(pid) when is_pid(pid), do: pid
|
||||
end
|
||||
@@ -0,0 +1,68 @@
|
||||
defmodule Task.Supervised do
|
||||
@moduledoc false
|
||||
|
||||
def start_link(:undefined, fun) do
|
||||
:proc_lib.start_link(__MODULE__, :noreply, [fun])
|
||||
end
|
||||
|
||||
def start_link(caller, fun) do
|
||||
:proc_lib.start_link(__MODULE__, :reply, [caller, fun])
|
||||
end
|
||||
|
||||
def async(caller, {module, fun, args}) do
|
||||
ref =
|
||||
# There is a race condition on this operation when working accross
|
||||
# node that manifests if a `Task.Supervisor.async/1` call is made
|
||||
# while the supervisor is busy spawning previous tasks.
|
||||
#
|
||||
# Imagine the following workflow:
|
||||
#
|
||||
# 1. The nodes disconnect
|
||||
# 2. The async call fails and is caught, the calling process does not exit
|
||||
# 3. The task is spawned and links to the calling process, causing the nodes to reconnect
|
||||
# 4. The calling process has not exited and so does not send its monitor reference
|
||||
# 5. The spawned task waits forever for the monitor reference so it can begin
|
||||
#
|
||||
# We have solved this by specifying a timeout of 5000 seconds.
|
||||
# Given no work is done in the client in between the task start and
|
||||
# sending the reference, 5000 should be enough to not raise false
|
||||
# negatives unless the nodes are indeed not available.
|
||||
receive do
|
||||
{^caller, ref} -> ref
|
||||
after
|
||||
5000 -> exit(:timeout)
|
||||
end
|
||||
|
||||
try do
|
||||
apply(module, fun, args)
|
||||
else
|
||||
result ->
|
||||
send caller, {ref, result}
|
||||
catch
|
||||
:error, reason ->
|
||||
exit({reason, System.stacktrace()})
|
||||
:throw, value ->
|
||||
exit({{:nocatch, value}, System.stacktrace()})
|
||||
after
|
||||
:erlang.unlink(caller)
|
||||
end
|
||||
end
|
||||
|
||||
def reply(caller, mfa) do
|
||||
:erlang.link(caller)
|
||||
:proc_lib.init_ack({:ok, self()})
|
||||
async(caller, mfa)
|
||||
end
|
||||
|
||||
def noreply({module, fun, args}) do
|
||||
:proc_lib.init_ack({:ok, self()})
|
||||
try do
|
||||
apply(module, fun, args)
|
||||
catch
|
||||
:error, reason ->
|
||||
exit({reason, System.stacktrace()})
|
||||
:throw, value ->
|
||||
exit({{:nocatch, value}, System.stacktrace()})
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,103 @@
|
||||
defmodule Task.Supervisor do
|
||||
@moduledoc """
|
||||
A tasks supervisor.
|
||||
|
||||
This module defines a supervisor which can be used to dynamically
|
||||
supervise tasks. Behind the scenes, this module is implemented as a
|
||||
`:simple_one_for_one` supervisor where the workers are temporary
|
||||
(i.e. they are not restarted after they die).
|
||||
|
||||
The functions in this module allow tasks can be spawned and awaited
|
||||
from a supervisor, similar to the functions defined in the `Task` module.
|
||||
|
||||
## Name registering
|
||||
|
||||
A `Task.Supervisor` is bound to the same name registering rules as a
|
||||
`GenServer`. Read more about it in the `GenServer` docs.
|
||||
"""
|
||||
|
||||
@doc """
|
||||
Starts a new supervisor.
|
||||
|
||||
The supported options are:
|
||||
|
||||
* `:name` - used to register a supervisor name, the supported values are
|
||||
described under the `Name Registering` section in the `GenServer` module
|
||||
docs;
|
||||
|
||||
* `:shutdown` - `:brutal_kill` if the tasks must be killed directly on shutdown
|
||||
or an integer indicating the timeout value, defaults to 5000 miliseconds;
|
||||
"""
|
||||
@spec start_link(Supervisor.options) :: Supervisor.on_start
|
||||
def start_link(opts \\ []) do
|
||||
import Supervisor.Spec
|
||||
{shutdown, opts} = Keyword.pop(opts, :shutdown, 5000)
|
||||
children = [worker(Task.Supervised, [], restart: :temporary, shutdown: shutdown)]
|
||||
Supervisor.start_link(children, [strategy: :simple_one_for_one] ++ opts)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Starts a task that can be awaited on.
|
||||
|
||||
The `supervisor` must be a reference as defined in `Task.Supervisor`.
|
||||
For more information on tasks, check the `Task` module.
|
||||
"""
|
||||
@spec async(Supervisor.supervisor, fun) :: Task.t
|
||||
def async(supervisor, fun) do
|
||||
async(supervisor, :erlang, :apply, [fun, []])
|
||||
end
|
||||
|
||||
@doc """
|
||||
Starts a task that can be awaited on.
|
||||
|
||||
The `supervisor` must be a reference as defined in `Task.Supervisor`.
|
||||
For more information on tasks, check the `Task` module.
|
||||
"""
|
||||
@spec async(Supervisor.supervisor, module, atom, [term]) :: Task.t
|
||||
def async(supervisor, module, fun, args) do
|
||||
{:ok, pid} = Supervisor.start_child(supervisor, [self(), {module, fun, args}])
|
||||
ref = Process.monitor(pid)
|
||||
send pid, {self(), ref}
|
||||
%Task{pid: pid, ref: ref}
|
||||
end
|
||||
|
||||
@doc """
|
||||
Terminates the given child at pid.
|
||||
"""
|
||||
@spec terminate_child(Supervisor.supervisor, pid) :: :ok
|
||||
def terminate_child(supervisor, pid) when is_pid(pid) do
|
||||
:supervisor.terminate_child(supervisor, pid)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns all children pids.
|
||||
"""
|
||||
@spec children(Supervisor.supervisor) :: [pid]
|
||||
def children(supervisor) do
|
||||
:supervisor.which_children(supervisor) |> Enum.map(&elem(&1, 1))
|
||||
end
|
||||
|
||||
@doc """
|
||||
Starts a task as child of the given `supervisor`.
|
||||
|
||||
Note the spawned process is not linked to the caller but
|
||||
only to the supervisor. This command is useful in case the
|
||||
task needs to emit side-effects (like I/O) and does not need
|
||||
to report back to the caller.
|
||||
"""
|
||||
@spec start_child(Supervisor.supervisor, fun) :: {:ok, pid}
|
||||
def start_child(supervisor, fun) do
|
||||
start_child(supervisor, :erlang, :apply, [fun, []])
|
||||
end
|
||||
|
||||
@doc """
|
||||
Starts a task as child of the given `supervisor`.
|
||||
|
||||
Similar to `start_child/2` except the task is specified
|
||||
by the given `module`, `fun` and `args`.
|
||||
"""
|
||||
@spec start_child(Supervisor.supervisor, module, atom, [term]) :: {:ok, pid}
|
||||
def start_child(supervisor, module, fun, args) do
|
||||
Supervisor.start_child(supervisor, [:undefined, {module, fun, args}])
|
||||
end
|
||||
end
|
||||
+13
-3
@@ -4,7 +4,7 @@ defmodule Tuple do
|
||||
"""
|
||||
|
||||
@doc """
|
||||
Create a new tuple.
|
||||
Creates a new tuple.
|
||||
|
||||
Creates a tuple of size `size` containing the
|
||||
given `data` at every position.
|
||||
@@ -23,7 +23,7 @@ defmodule Tuple do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Insert an element into a tuple.
|
||||
Inserts an element into a tuple.
|
||||
|
||||
Inserts `value` into `tuple` at the given zero-based `index`.
|
||||
Raises an `ArgumentError` if `index` is greater than the
|
||||
@@ -44,7 +44,7 @@ defmodule Tuple do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Remove an element from a tuple.
|
||||
Removes an element from a tuple.
|
||||
|
||||
Deletes the element at the zero-based `index` from `tuple`.
|
||||
Raises an `ArgumentError` if `index` is greater than
|
||||
@@ -63,4 +63,14 @@ defmodule Tuple do
|
||||
def delete_at(tuple, index) do
|
||||
:erlang.delete_element(index + 1, tuple)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Converts a tuple to a list.
|
||||
|
||||
Inlined by the compiler.
|
||||
"""
|
||||
@spec to_list(tuple) :: list
|
||||
def to_list(tuple) do
|
||||
:erlang.tuple_to_list(tuple)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -58,7 +58,7 @@ defmodule URI do
|
||||
"""
|
||||
def default_port(scheme, port) when is_binary(scheme) and port > 0 do
|
||||
{:ok, dict} = Application.fetch_env(:elixir, :uri)
|
||||
Application.put_env(:elixir, :uri, Map.put(dict, scheme, port), persist: true)
|
||||
Application.put_env(:elixir, :uri, Map.put(dict, scheme, port), persistent: true)
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -134,11 +134,11 @@ defmodule URI do
|
||||
end
|
||||
|
||||
defp pair({k, _}) when is_list(k) do
|
||||
raise ArgumentError, message: "encode_query/1 keys cannot be lists, got: #{inspect k}"
|
||||
raise ArgumentError, "encode_query/1 keys cannot be lists, got: #{inspect k}"
|
||||
end
|
||||
|
||||
defp pair({_, v}) when is_list(v) do
|
||||
raise ArgumentError, message: "encode_query/1 values cannot be lists, got: #{inspect v}"
|
||||
raise ArgumentError, "encode_query/1 values cannot be lists, got: #{inspect v}"
|
||||
end
|
||||
|
||||
defp pair({k, v}) do
|
||||
@@ -200,7 +200,7 @@ defmodule URI do
|
||||
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}"
|
||||
raise ArgumentError, "malformed URI #{inspect uri}"
|
||||
end
|
||||
|
||||
defp check_plus(?+), do: 32
|
||||
@@ -235,7 +235,7 @@ defmodule URI do
|
||||
|
||||
if userinfo, do: authority = authority <> userinfo <> "@"
|
||||
if host, do: authority = authority <> host
|
||||
if port, do: authority = authority <> ":" <> integer_to_binary(port)
|
||||
if port, do: authority = authority <> ":" <> Integer.to_string(port)
|
||||
end
|
||||
|
||||
scheme = normalize_scheme(scheme)
|
||||
@@ -257,7 +257,7 @@ defmodule URI do
|
||||
components = Regex.run ~r/(^(.*)@)?(\[[a-zA-Z0-9:.]*\]|[^:]*)(:(\d*))?/, s
|
||||
|
||||
destructure [_, _, userinfo, host, _, port], nillify(components)
|
||||
port = if port, do: binary_to_integer(port)
|
||||
port = if port, do: String.to_integer(port)
|
||||
host = if host, do: host |> String.lstrip(?[) |> String.rstrip(?])
|
||||
|
||||
{userinfo, host, port}
|
||||
@@ -285,7 +285,7 @@ defimpl String.Chars, for: URI do
|
||||
if uri.scheme, do: result = result <> uri.scheme <> "://"
|
||||
if uri.userinfo, do: result = result <> uri.userinfo <> "@"
|
||||
if uri.host, do: result = result <> uri.host
|
||||
if uri.port, do: result = result <> ":" <> integer_to_binary(uri.port)
|
||||
if uri.port, do: result = result <> ":" <> Integer.to_string(uri.port)
|
||||
if uri.path, do: result = result <> uri.path
|
||||
if uri.query, do: result = result <> "?" <> uri.query
|
||||
if uri.fragment, do: result = result <> "#" <> uri.fragment
|
||||
|
||||
+14
-14
@@ -68,15 +68,15 @@ defmodule Version do
|
||||
defstruct [:source, :matchspec]
|
||||
end
|
||||
|
||||
defexception InvalidRequirement, [:message]
|
||||
defexception InvalidVersion, [:message]
|
||||
defexception InvalidRequirementError, [:message]
|
||||
defexception InvalidVersionError, [: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, or `Version.InvalidVersion` if `version` is not parseable.
|
||||
Raises a `Version.InvalidRequirementError` exception if `requirement` is not
|
||||
parseable, or `Version.InvalidVersionError` if `version` is not parseable.
|
||||
If given an already parsed version and requirement this function won't
|
||||
raise.
|
||||
|
||||
@@ -89,10 +89,10 @@ defmodule Version do
|
||||
false
|
||||
|
||||
iex> Version.match?("foo", "==1.0.0")
|
||||
** (Version.InvalidVersion) foo
|
||||
** (Version.InvalidVersionError) foo
|
||||
|
||||
iex> Version.match?("2.0.0", "== ==1.0.0")
|
||||
** (Version.InvalidRequirement) == ==1.0.0
|
||||
** (Version.InvalidRequirementError) == ==1.0.0
|
||||
|
||||
"""
|
||||
@spec match?(version, requirement) :: boolean
|
||||
@@ -101,7 +101,7 @@ defmodule Version do
|
||||
{:ok, req} ->
|
||||
match?(vsn, req)
|
||||
:error ->
|
||||
raise InvalidRequirement, message: req
|
||||
raise InvalidRequirementError, message: req
|
||||
end
|
||||
end
|
||||
|
||||
@@ -115,7 +115,7 @@ defmodule Version do
|
||||
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.
|
||||
Raises a `Version.InvalidVersionError` exception if `version` is not parseable.
|
||||
If given an already parsed version this function won't raise.
|
||||
|
||||
## Examples
|
||||
@@ -127,7 +127,7 @@ defmodule Version do
|
||||
:eq
|
||||
|
||||
iex> Version.compare("invalid", "2.0.1")
|
||||
** (Version.InvalidVersion) invalid
|
||||
** (Version.InvalidVersionError) invalid
|
||||
|
||||
"""
|
||||
@spec compare(version, version) :: :gt | :eq | :lt
|
||||
@@ -200,7 +200,7 @@ defmodule Version do
|
||||
defp to_matchable(string) do
|
||||
case Version.Parser.parse_version(string) do
|
||||
{:ok, vsn} -> vsn
|
||||
:error -> raise InvalidVersion, message: string
|
||||
:error -> raise InvalidVersionError, message: string
|
||||
end
|
||||
end
|
||||
|
||||
@@ -308,9 +308,9 @@ defmodule Version do
|
||||
if nil?(minor) or (nil?(patch) and not approximate?) do
|
||||
:error
|
||||
else
|
||||
major = binary_to_integer(major)
|
||||
minor = binary_to_integer(minor)
|
||||
patch = patch && binary_to_integer(patch)
|
||||
major = String.to_integer(major)
|
||||
minor = String.to_integer(minor)
|
||||
patch = patch && String.to_integer(patch)
|
||||
|
||||
case parse_pre(pre) do
|
||||
{:ok, pre} ->
|
||||
@@ -330,7 +330,7 @@ defmodule Version do
|
||||
defp parse_pre([piece|t], acc) do
|
||||
cond do
|
||||
piece =~ ~r/^(0|[1-9][0-9]*)$/ ->
|
||||
parse_pre(t, [binary_to_integer(piece)|acc])
|
||||
parse_pre(t, [String.to_integer(piece)|acc])
|
||||
piece =~ ~r/^[0-9]*$/ ->
|
||||
:error
|
||||
true ->
|
||||
|
||||
@@ -9,9 +9,8 @@
|
||||
-include("elixir.hrl").
|
||||
|
||||
%% Top level types
|
||||
-export_type([char_list/0, char_data/0, as_boolean/1]).
|
||||
-export_type([char_list/0, as_boolean/1]).
|
||||
-type char_list() :: string().
|
||||
-type char_data() :: unicode:chardata().
|
||||
-type as_boolean(T) :: T.
|
||||
|
||||
%% OTP Application API
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
%% Handle code related to args, guard and -> matching for case,
|
||||
%% fn, receive and friends. try is handled in elixir_try.
|
||||
-module(elixir_clauses).
|
||||
-export([match/3, clause/7, clauses/4, get_pairs/2, get_pairs/3,
|
||||
-export([match/3, clause/7, clauses/4, guards/4, get_pairs/2, get_pairs/3,
|
||||
extract_splat_guards/1, extract_guards/1]).
|
||||
-include("elixir.hrl").
|
||||
|
||||
@@ -35,18 +35,20 @@ clause(Line, Fun, Args, Expr, Guards, Return, S) when is_integer(Line) ->
|
||||
{TArgs, SA} = match(Fun, Args, S#elixir_scope{extra_guards=[]}),
|
||||
{TExpr, SE} = elixir_translator:translate_block(Expr, Return, SA#elixir_scope{extra_guards=nil}),
|
||||
|
||||
SG = SA#elixir_scope{context=guard, extra_guards=nil},
|
||||
Extra = SA#elixir_scope.extra_guards,
|
||||
|
||||
TGuards = case Guards of
|
||||
[] -> case Extra of [] -> []; _ -> [Extra] end;
|
||||
_ -> [translate_guard(Line, Guard, Extra, SG) || Guard <- Guards]
|
||||
end,
|
||||
|
||||
TGuards = guards(Line, Guards, Extra, SA),
|
||||
{{clause, Line, TArgs, TGuards, unblock(TExpr)}, SE}.
|
||||
|
||||
% Translate/Extract guards from the given expression.
|
||||
|
||||
guards(Line, Guards, Extra, S) ->
|
||||
SG = S#elixir_scope{context=guard, extra_guards=nil},
|
||||
|
||||
case Guards of
|
||||
[] -> case Extra of [] -> []; _ -> [Extra] end;
|
||||
_ -> [translate_guard(Line, Guard, Extra, SG) || Guard <- Guards]
|
||||
end.
|
||||
|
||||
translate_guard(Line, Guard, Extra, S) ->
|
||||
[element(1, elixir_translator:translate(elixir_quote:linify(Line, Guard), S))|Extra].
|
||||
|
||||
|
||||
@@ -68,7 +68,9 @@ code_loading_compilation(Forms, Vars, #{line := Line} = E) ->
|
||||
|
||||
%% Pass {native, false} to speed up bootstrap
|
||||
%% process when native is set to true
|
||||
module(Form, ?m(E, file), [{native,false}], true, fun(_, Binary) ->
|
||||
AllOpts = elixir_code_server:call(erl_compiler_options),
|
||||
FinalOpts = AllOpts -- [native, warn_missing_spec],
|
||||
module(Form, ?m(E, file), FinalOpts, true, fun(_, Binary) ->
|
||||
%% If we have labeled locals, anonymous functions
|
||||
%% were created and therefore we cannot ditch the
|
||||
%% module
|
||||
@@ -130,14 +132,13 @@ module(Forms, File, Opts, Callback) ->
|
||||
Final =
|
||||
case (get_opt(debug_info) == true) orelse
|
||||
lists:member(debug_info, Opts) of
|
||||
true -> [debug_info];
|
||||
false -> []
|
||||
true -> [debug_info] ++ elixir_code_server:call(erl_compiler_options);
|
||||
false -> elixir_code_server:call(erl_compiler_options)
|
||||
end,
|
||||
module(Forms, File, Final, false, Callback).
|
||||
|
||||
module(Forms, File, RawOptions, Bootstrap, Callback) when
|
||||
is_binary(File), is_list(Forms), is_list(RawOptions), is_boolean(Bootstrap), is_function(Callback) ->
|
||||
Options = RawOptions ++ elixir_code_server:call(erl_compiler_options),
|
||||
module(Forms, File, Options, Bootstrap, Callback) when
|
||||
is_binary(File), is_list(Forms), is_list(Options), is_boolean(Bootstrap), is_function(Callback) ->
|
||||
Listname = elixir_utils:characters_to_list(File),
|
||||
|
||||
case compile:noenv_forms([no_auto_import()|Forms], [return,{source,Listname}|Options]) of
|
||||
@@ -172,38 +173,32 @@ core_file(File) ->
|
||||
end.
|
||||
|
||||
core_main() ->
|
||||
[
|
||||
<<"lib/elixir/lib/kernel.ex">>,
|
||||
<<"lib/elixir/lib/macro/env.ex">>,
|
||||
<<"lib/elixir/lib/keyword.ex">>,
|
||||
<<"lib/elixir/lib/module.ex">>,
|
||||
<<"lib/elixir/lib/list.ex">>,
|
||||
<<"lib/elixir/lib/record.ex">>,
|
||||
<<"lib/elixir/lib/record/backend.ex">>,
|
||||
<<"lib/elixir/lib/record/deprecated.ex">>,
|
||||
<<"lib/elixir/lib/macro.ex">>,
|
||||
<<"lib/elixir/lib/exception.ex">>,
|
||||
<<"lib/elixir/lib/code.ex">>,
|
||||
<<"lib/elixir/lib/module/locals_tracker.ex">>,
|
||||
<<"lib/elixir/lib/protocol.ex">>,
|
||||
<<"lib/elixir/lib/stream/reducers.ex">>,
|
||||
<<"lib/elixir/lib/enum.ex">>,
|
||||
<<"lib/elixir/lib/inspect/algebra.ex">>,
|
||||
<<"lib/elixir/lib/inspect.ex">>,
|
||||
<<"lib/elixir/lib/range.ex">>,
|
||||
<<"lib/elixir/lib/regex.ex">>,
|
||||
<<"lib/elixir/lib/string.ex">>,
|
||||
<<"lib/elixir/lib/string/chars.ex">>,
|
||||
<<"lib/elixir/lib/collectable.ex">>,
|
||||
<<"lib/elixir/lib/io.ex">>,
|
||||
<<"lib/elixir/lib/path.ex">>,
|
||||
<<"lib/elixir/lib/system.ex">>,
|
||||
<<"lib/elixir/lib/kernel/typespec.ex">>,
|
||||
<<"lib/elixir/lib/kernel/cli.ex">>,
|
||||
<<"lib/elixir/lib/kernel/error_handler.ex">>,
|
||||
<<"lib/elixir/lib/kernel/parallel_compiler.ex">>,
|
||||
<<"lib/elixir/lib/kernel/lexical_tracker.ex">>
|
||||
].
|
||||
[<<"lib/elixir/lib/kernel.ex">>,
|
||||
<<"lib/elixir/lib/macro/env.ex">>,
|
||||
<<"lib/elixir/lib/keyword.ex">>,
|
||||
<<"lib/elixir/lib/module.ex">>,
|
||||
<<"lib/elixir/lib/list.ex">>,
|
||||
<<"lib/elixir/lib/macro.ex">>,
|
||||
<<"lib/elixir/lib/code.ex">>,
|
||||
<<"lib/elixir/lib/exception.ex">>,
|
||||
<<"lib/elixir/lib/module/locals_tracker.ex">>,
|
||||
<<"lib/elixir/lib/protocol.ex">>,
|
||||
<<"lib/elixir/lib/stream/reducers.ex">>,
|
||||
<<"lib/elixir/lib/enum.ex">>,
|
||||
<<"lib/elixir/lib/inspect/algebra.ex">>,
|
||||
<<"lib/elixir/lib/inspect.ex">>,
|
||||
<<"lib/elixir/lib/range.ex">>,
|
||||
<<"lib/elixir/lib/regex.ex">>,
|
||||
<<"lib/elixir/lib/string.ex">>,
|
||||
<<"lib/elixir/lib/string/chars.ex">>,
|
||||
<<"lib/elixir/lib/io.ex">>,
|
||||
<<"lib/elixir/lib/path.ex">>,
|
||||
<<"lib/elixir/lib/system.ex">>,
|
||||
<<"lib/elixir/lib/kernel/typespec.ex">>,
|
||||
<<"lib/elixir/lib/kernel/cli.ex">>,
|
||||
<<"lib/elixir/lib/kernel/error_handler.ex">>,
|
||||
<<"lib/elixir/lib/kernel/parallel_compiler.ex">>,
|
||||
<<"lib/elixir/lib/kernel/lexical_tracker.ex">>].
|
||||
|
||||
binary_to_path({ModuleName, Binary}, CompilePath) ->
|
||||
Path = filename:join(CompilePath, atom_to_list(ModuleName) ++ ".beam"),
|
||||
|
||||
@@ -10,19 +10,25 @@
|
||||
-include("elixir.hrl").
|
||||
-import(ordsets, [is_element/2]).
|
||||
|
||||
-define(atom, 'Elixir.Atom').
|
||||
-define(float, 'Elixir.Float').
|
||||
-define(io, 'Elixir.IO').
|
||||
-define(integer, 'Elixir.Integer').
|
||||
-define(kernel, 'Elixir.Kernel').
|
||||
-define(list, 'Elixir.List').
|
||||
-define(map, 'Elixir.Map').
|
||||
-define(node, 'Elixir.Node').
|
||||
-define(process, 'Elixir.Process').
|
||||
-define(string, 'Elixir.String').
|
||||
-define(system, 'Elixir.System').
|
||||
-define(tuple, 'Elixir.Tuple').
|
||||
|
||||
default_functions() ->
|
||||
[ {?kernel, elixir_imported_functions()} ].
|
||||
[{?kernel, elixir_imported_functions()}].
|
||||
default_macros() ->
|
||||
[ {?kernel, elixir_imported_macros()} ].
|
||||
[{?kernel, elixir_imported_macros()}].
|
||||
default_requires() ->
|
||||
[ 'Elixir.Kernel', 'Elixir.Kernel.Typespec' ].
|
||||
['Elixir.Kernel', 'Elixir.Kernel.Typespec'].
|
||||
|
||||
find_import(Meta, Name, Arity, E) ->
|
||||
Tuple = {Name, Arity},
|
||||
@@ -48,7 +54,7 @@ import_function(Meta, Name, Arity, E) ->
|
||||
{function, Receiver} ->
|
||||
elixir_lexical:record_import(Receiver, ?m(E, lexical_tracker)),
|
||||
elixir_locals:record_import(Tuple, Receiver, ?m(E, module), ?m(E, function)),
|
||||
remote_function(Receiver, Name, Arity, E);
|
||||
remote_function(Meta, Receiver, Name, Arity, E);
|
||||
{macro, _Receiver} ->
|
||||
false;
|
||||
{import, Receiver} ->
|
||||
@@ -62,17 +68,19 @@ import_function(Meta, Name, Arity, E) ->
|
||||
end
|
||||
end.
|
||||
|
||||
require_function(_Meta, Receiver, Name, Arity, E) ->
|
||||
require_function(Meta, Receiver, Name, Arity, E) ->
|
||||
case is_element({Name, Arity}, get_optional_macros(Receiver)) of
|
||||
true -> false;
|
||||
false -> remote_function(Receiver, Name, Arity, E)
|
||||
false -> remote_function(Meta, Receiver, Name, Arity, E)
|
||||
end.
|
||||
|
||||
remote_function(Receiver, Name, Arity, E) ->
|
||||
remote_function(Meta, Receiver, Name, Arity, E) ->
|
||||
check_deprecation(Meta, Receiver, Name, Arity, E),
|
||||
|
||||
elixir_lexical:record_remote(Receiver, ?m(E, lexical_tracker)),
|
||||
case inline(Receiver, Name, Arity) of
|
||||
{AR, AN} -> {remote, AR, AN, Arity};
|
||||
false -> {remote, Receiver, Name, Arity}
|
||||
false -> {remote, Receiver, Name, Arity}
|
||||
end.
|
||||
|
||||
%% Dispatches
|
||||
@@ -82,8 +90,8 @@ dispatch_import(Meta, Name, Args, E, Callback) ->
|
||||
case expand_import(Meta, {Name, Arity}, Args, E, []) of
|
||||
{ok, Receiver, Quoted} ->
|
||||
expand_quoted(Meta, Receiver, Name, Arity, Quoted, E);
|
||||
{ok, Receiver} ->
|
||||
elixir_exp:expand({{'.', [], [Receiver, Name]}, Meta, Args}, E);
|
||||
{ok, Receiver, NewName, NewArgs} ->
|
||||
elixir_exp:expand({{'.', [], [Receiver, NewName]}, Meta, NewArgs}, E);
|
||||
error ->
|
||||
Callback()
|
||||
end.
|
||||
@@ -140,32 +148,30 @@ do_expand_import(Meta, {Name, Arity} = Tuple, Args, Module, E, Result) ->
|
||||
elixir_lexical:record_import(Receiver, ?m(E, lexical_tracker)),
|
||||
elixir_locals:record_import(Tuple, Receiver, Module, ?m(E, function)),
|
||||
case rewrite(Receiver, Name, Args, Arity) of
|
||||
{ok, AR, AN, AA} ->
|
||||
{ok, AR, {{'.', [], [AR, AN]}, [], AA}};
|
||||
false ->
|
||||
{ok, Receiver}
|
||||
{ok, _, _, _} = Res -> Res;
|
||||
false -> {ok, Receiver, Name, Args}
|
||||
end;
|
||||
{macro, Receiver} ->
|
||||
check_deprecation(Meta, Receiver, Name, Arity, E),
|
||||
elixir_lexical:record_import(Receiver, ?m(E, lexical_tracker)),
|
||||
elixir_locals:record_import(Tuple, Receiver, Module, ?m(E, function)),
|
||||
{ok, Receiver, expand_macro_named(Meta, Receiver, Name, Arity, Args, E)};
|
||||
{import, Receiver} ->
|
||||
case expand_require([{require,false}|Meta], Receiver, Tuple, Args, E) of
|
||||
{ok, _, _} = Response -> Response;
|
||||
error -> {ok, Receiver}
|
||||
error -> {ok, Receiver, Name, Args}
|
||||
end;
|
||||
false when Module == ?kernel ->
|
||||
case rewrite(Module, Name, Args, Arity) of
|
||||
{ok, AR, AN, AA} ->
|
||||
{ok, AR, {{'.', [], [AR, AN]}, [], AA}};
|
||||
false ->
|
||||
error
|
||||
{ok, _, _, _} = Res -> Res;
|
||||
false -> error
|
||||
end;
|
||||
false ->
|
||||
error
|
||||
end.
|
||||
|
||||
expand_require(Meta, Receiver, {Name, Arity} = Tuple, Args, E) ->
|
||||
check_deprecation(Meta, Receiver, Name, Arity, E),
|
||||
Module = ?m(E, module),
|
||||
|
||||
case is_element(Tuple, get_optional_macros(Receiver)) of
|
||||
@@ -331,27 +337,12 @@ elixir_imported_macros() ->
|
||||
error:undef -> []
|
||||
end.
|
||||
|
||||
rewrite(?kernel, atom_to_binary, [Arg], _) ->
|
||||
rewrite(?atom, to_string, [Arg], _) ->
|
||||
{ok, erlang, atom_to_binary, [Arg, utf8]};
|
||||
rewrite(?kernel, binary_to_atom, [Arg], _) ->
|
||||
{ok, erlang, binary_to_atom, [Arg, utf8]};
|
||||
rewrite(?kernel, binary_to_existing_atom, [Arg], _) ->
|
||||
{ok, erlang, binary_to_existing_atom, [Arg, utf8]};
|
||||
rewrite(?kernel, elem, [Tuple, Index], _) ->
|
||||
{ok, erlang, element, [increment(Index), Tuple]};
|
||||
rewrite(?kernel, set_elem, [Tuple, Index, Value], _) ->
|
||||
rewrite(?kernel, put_elem, [Tuple, Index, Value], _) ->
|
||||
{ok, erlang, setelement, [increment(Index), Tuple, Value]};
|
||||
|
||||
rewrite(?process, monitor, [Arg], _) ->
|
||||
{ok, erlang, monitor, [process, Arg]};
|
||||
|
||||
rewrite(?tuple, insert_at, [Tuple, Index, Term], _) ->
|
||||
{ok, erlang, insert_element, [increment(Index), Tuple, Term]};
|
||||
rewrite(?tuple, delete_at, [Tuple, Index], _) ->
|
||||
{ok, erlang, delete_element, [increment(Index), Tuple]};
|
||||
rewrite(?tuple, duplicate, [Data, Size], _) ->
|
||||
{ok, erlang, make_tuple, [Size, Data]};
|
||||
|
||||
rewrite(?map, 'has_key?', [Map, Key], _) ->
|
||||
{ok, maps, is_key, [Key, Map]};
|
||||
rewrite(?map, fetch, [Map, Key], _) ->
|
||||
@@ -360,6 +351,19 @@ rewrite(?map, put, [Map, Key, Value], _) ->
|
||||
{ok, maps, put, [Key, Value, Map]};
|
||||
rewrite(?map, delete, [Map, Key], _) ->
|
||||
{ok, maps, remove, [Key, Map]};
|
||||
rewrite(?process, monitor, [Arg], _) ->
|
||||
{ok, erlang, monitor, [process, Arg]};
|
||||
rewrite(?string, to_atom, [Arg], _) ->
|
||||
{ok, erlang, binary_to_atom, [Arg, utf8]};
|
||||
rewrite(?string, to_existing_atom, [Arg], _) ->
|
||||
{ok, erlang, binary_to_existing_atom, [Arg, utf8]};
|
||||
rewrite(?tuple, insert_at, [Tuple, Index, Term], _) ->
|
||||
{ok, erlang, insert_element, [increment(Index), Tuple, Term]};
|
||||
rewrite(?tuple, delete_at, [Tuple, Index], _) ->
|
||||
{ok, erlang, delete_element, [increment(Index), Tuple]};
|
||||
rewrite(?tuple, duplicate, [Data, Size], _) ->
|
||||
{ok, erlang, make_tuple, [Size, Data]};
|
||||
|
||||
rewrite(Receiver, Name, Args, Arity) ->
|
||||
case inline(Receiver, Name, Arity) of
|
||||
{AR, AN} -> {ok, AR, AN, Args};
|
||||
@@ -371,6 +375,22 @@ increment(Number) when is_number(Number) ->
|
||||
increment(Other) ->
|
||||
{{'.', [], [erlang, '+']}, [], [Other, 1]}.
|
||||
|
||||
inline(?atom, to_char_list, 1) -> {erlang, atom_to_list};
|
||||
inline(?io, iodata_length, 1) -> {erlang, iolist_size};
|
||||
inline(?io, iodata_to_binary, 1) -> {erlang, iolist_to_binary};
|
||||
inline(?integer, to_string, 1) -> {erlang, integer_to_binary};
|
||||
inline(?integer, to_string, 2) -> {erlang, integer_to_binary};
|
||||
inline(?integer, to_char_list, 1) -> {erlang, integer_to_list};
|
||||
inline(?integer, to_char_list, 2) -> {erlang, integer_to_list};
|
||||
inline(?float, to_string, 1) -> {erlang, float_to_binary};
|
||||
inline(?float, to_char_list, 1) -> {erlang, float_to_list};
|
||||
inline(?list, to_atom, 1) -> {erlang, list_to_atom};
|
||||
inline(?list, to_existing_atom, 1) -> {erlang, list_to_existing_atom};
|
||||
inline(?list, to_float, 1) -> {erlang, list_to_float};
|
||||
inline(?list, to_integer, 1) -> {erlang, list_to_integer};
|
||||
inline(?list, to_integer, 2) -> {erlang, list_to_integer};
|
||||
inline(?list, to_tuple, 1) -> {erlang, list_to_tuple};
|
||||
|
||||
inline(?kernel, '+', 2) -> {erlang, '+'};
|
||||
inline(?kernel, '-', 2) -> {erlang, '-'};
|
||||
inline(?kernel, '+', 1) -> {erlang, '+'};
|
||||
@@ -392,26 +412,12 @@ 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, iodata_length, 1) -> {erlang, iolist_size};
|
||||
inline(?kernel, iodata_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};
|
||||
@@ -428,13 +434,6 @@ 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, map_size, 1) -> {erlang, map_size};
|
||||
inline(?kernel, max, 2) -> {erlang, max};
|
||||
@@ -456,7 +455,19 @@ 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(?map, keys, 1) -> {maps, keys};
|
||||
inline(?map, merge, 2) -> {maps, merge};
|
||||
inline(?map, size, 1) -> {maps, size};
|
||||
inline(?map, values, 1) -> {maps, values};
|
||||
inline(?map, to_list, 1) -> {maps, to_list};
|
||||
|
||||
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(?process, exit, 2) -> {erlang, exit};
|
||||
inline(?process, spawn, 2) -> {erlang, spawn_opt};
|
||||
@@ -466,21 +477,85 @@ 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(?string, to_float, 1) -> {erlang, binary_to_float};
|
||||
inline(?string, to_integer, 1) -> {erlang, binary_to_integer};
|
||||
inline(?string, to_integer, 2) -> {erlang, binary_to_integer};
|
||||
inline(?system, stacktrace, 0) -> {erlang, get_stacktrace};
|
||||
|
||||
inline(?map, keys, 1) -> {maps, keys};
|
||||
inline(?map, merge, 2) -> {maps, merge};
|
||||
inline(?map, size, 1) -> {maps, size};
|
||||
inline(?map, values, 1) -> {maps, values};
|
||||
inline(?map, to_list, 1) -> {maps, to_list};
|
||||
inline(?tuple, to_list, 1) -> {erlang, tuple_to_list};
|
||||
|
||||
inline(_, _, _) -> false.
|
||||
|
||||
check_deprecation(Meta, Receiver, Name, Arity, #{file := File}) ->
|
||||
case deprecation(Receiver, Name, Arity) of
|
||||
false -> ok;
|
||||
Message ->
|
||||
Warning = deprecation_message(Receiver, Name, Arity, Message),
|
||||
elixir_errors:warn(?line(Meta), File, Warning)
|
||||
end.
|
||||
|
||||
deprecation_message(Receiver, '__using__', _Arity, Message) ->
|
||||
Warning = io_lib:format("use ~s is deprecated", [elixir_aliases:inspect(Receiver)]),
|
||||
deprecation_message(Warning, Message);
|
||||
|
||||
deprecation_message(Receiver, Name, Arity, Message) ->
|
||||
Warning = io_lib:format("~s.~s/~B is deprecated",
|
||||
[elixir_aliases:inspect(Receiver), Name, Arity]),
|
||||
deprecation_message(Warning, Message).
|
||||
|
||||
deprecation_message(Warning, Message) ->
|
||||
case Message of
|
||||
true -> Warning;
|
||||
Message -> Warning ++ ", " ++ Message
|
||||
end.
|
||||
|
||||
% deprecation('Elixir.Application.Behaviour', '__using__', _) ->
|
||||
% "use Application instead";
|
||||
% deprecation('Elixir.Dict.Behaviour', '__using__', _) ->
|
||||
% "use Dict instead";
|
||||
% deprecation('Elixir.GenEvent.Behaviour', '__using__', _) ->
|
||||
% "use GenEvent instead";
|
||||
% deprecation('Elixir.GenServer.Behaviour', '__using__', _) ->
|
||||
% "use GenServer instead";
|
||||
% deprecation('Elixir.Supervisor.Behaviour', '__using__', _) ->
|
||||
% "use Supervisor instead";
|
||||
% deprecation('Elixir.Kernel', raise, 3) ->
|
||||
% "use reraise/2 instead";
|
||||
% deprecation('Elixir.Kernel', defexception, 3) ->
|
||||
% "use defexception/1 instead";
|
||||
% deprecation('Elixir.List', 'from_char_data', 1) ->
|
||||
% "use String.to_char_list instead";
|
||||
% deprecation('Elixir.List', 'from_char_data!', 1) ->
|
||||
% "use String.to_char_list instead";
|
||||
% deprecation('Elixir.String', 'from_char_data', 1) ->
|
||||
% "use List.to_string instead";
|
||||
% deprecation('Elixir.String', 'from_char_data!', 1) ->
|
||||
% "use List.to_string instead";
|
||||
|
||||
deprecation('Elixir.Kernel', is_record, _) ->
|
||||
"use Record.record? instead";
|
||||
deprecation('Elixir.Kernel', defrecord, _) ->
|
||||
"use structs instead";
|
||||
deprecation('Elixir.Kernel', defrecordp, _) ->
|
||||
"use Record.defrecordp/3 instead";
|
||||
deprecation('Elixir.Process', spawn, 1) ->
|
||||
"use the one in Kernel instead";
|
||||
deprecation('Elixir.Process', spawn, 3) ->
|
||||
"use the one in Kernel instead";
|
||||
deprecation('Elixir.Process', spawn_link, _) ->
|
||||
"use the one in Kernel instead";
|
||||
deprecation('Elixir.Process', spawn_monitor, _) ->
|
||||
"use the one in Kernel instead";
|
||||
deprecation('Elixir.Process', self, 0) ->
|
||||
"use the one in Kernel instead";
|
||||
deprecation('Elixir.Process', send, 2) ->
|
||||
"use the one in Kernel instead";
|
||||
deprecation('Elixir.Record', defmacros, 4) ->
|
||||
true;
|
||||
deprecation('Elixir.Record', deftypes, 3) ->
|
||||
true;
|
||||
deprecation('Elixir.Record', deffunctions, 2) ->
|
||||
true;
|
||||
deprecation('Elixir.Mix', project, 0) ->
|
||||
"use Mix.Project.config/0 instead";
|
||||
deprecation(_, _, _) ->
|
||||
false.
|
||||
|
||||
@@ -3,8 +3,7 @@
|
||||
-module(elixir_errors).
|
||||
-export([compile_error/3, compile_error/4,
|
||||
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]).
|
||||
handle_file_warning/2, handle_file_warning/3, handle_file_error/2]).
|
||||
-include("elixir.hrl").
|
||||
|
||||
-type line_or_meta() :: integer() | list().
|
||||
@@ -21,7 +20,7 @@ warn(Warning) ->
|
||||
warn(Caller, Warning) ->
|
||||
warn([Caller, "warning: ", Warning]).
|
||||
|
||||
warn(Line, File, Warning) ->
|
||||
warn(Line, File, Warning) when is_integer(Line) ->
|
||||
warn(file_format(Line, File, "warning: " ++ Warning)).
|
||||
|
||||
%% Raised during expansion/translation/compilation.
|
||||
@@ -56,23 +55,24 @@ parse_error(Meta, File, <<"syntax error before: ">>, <<"'end'">>) ->
|
||||
raise(Meta, File, 'Elixir.SyntaxError', <<"unexpected token: end">>);
|
||||
|
||||
%% Binaries are wrapped in [<<...>>], so we need to unwrap them
|
||||
parse_error(Meta, File, Error, <<"[<<", Token/binary>>) when is_binary(Error) ->
|
||||
Rest = binary_part(Token, 0, byte_size(Token) - 3),
|
||||
Message = <<Error / binary, Rest / binary >>,
|
||||
raise(Meta, File, 'Elixir.SyntaxError', Message);
|
||||
parse_error(Meta, File, Error, <<"[", _/binary>> = Full) when is_binary(Error) ->
|
||||
Rest =
|
||||
case binary:split(Full, <<"<<">>) of
|
||||
[Lead, Token] ->
|
||||
case binary:split(Token, <<">>">>) of
|
||||
[Part, _] when Lead == <<$[>> -> Part;
|
||||
_ -> <<$">>
|
||||
end;
|
||||
[_] ->
|
||||
<<$">>
|
||||
end,
|
||||
raise(Meta, File, 'Elixir.SyntaxError', <<Error/binary, Rest/binary >>);
|
||||
|
||||
%% Everything else is fine as is
|
||||
parse_error(Meta, File, Error, Token) when is_binary(Error), is_binary(Token) ->
|
||||
Message = <<Error / binary, Token / binary >>,
|
||||
raise(Meta, File, 'Elixir.SyntaxError', Message).
|
||||
|
||||
%% Shows a deprecation message
|
||||
|
||||
deprecation(Meta, File, Message) -> deprecation(Meta, File, Message, []).
|
||||
|
||||
deprecation(Meta, File, Message, Args) ->
|
||||
io:format(file_format(?line(Meta), File, io_lib:format(Message, Args))).
|
||||
|
||||
%% Handle warnings and errors (called during module compilation)
|
||||
|
||||
%% Ignore on bootstrap
|
||||
@@ -177,7 +177,7 @@ raise(Line, File, Kind, Message) when is_integer(Line), is_binary(File) ->
|
||||
ok -> ok
|
||||
end,
|
||||
Stacktrace = erlang:get_stacktrace(),
|
||||
Exception = Kind:new([{description, Message}, {file, File}, {line, Line}]),
|
||||
Exception = Kind:exception([{description, Message}, {file, File}, {line, Line}]),
|
||||
erlang:raise(error, Exception, tl(Stacktrace)).
|
||||
|
||||
file_format(0, File, Message) when is_binary(File) ->
|
||||
|
||||
@@ -169,22 +169,23 @@ expand({quote, Meta, [KV, Do]}, E) when is_list(Do) ->
|
||||
false -> compile_error(Meta, E#elixir_scope.file, "missing do keyword in quote")
|
||||
end,
|
||||
|
||||
ValidOpts = [hygiene, context, var_context, location, line, unquote, bind_quoted],
|
||||
ValidOpts = [hygiene, context, location, line, unquote, bind_quoted],
|
||||
{EKV, ET} = expand_opts(Meta, quote, ValidOpts, KV, E),
|
||||
|
||||
Hygiene = case lists:keyfind(hygiene, 1, EKV) of
|
||||
{hygiene, List} when is_list(List) ->
|
||||
elixir_errors:warn(?line(Meta), ?m(E, file), "the :hygiene option in quote is deprecated"),
|
||||
List;
|
||||
false ->
|
||||
[]
|
||||
end,
|
||||
|
||||
Context = case lists:keyfind(context, 1, EKV) of
|
||||
{context, Atom} when is_atom(Atom) ->
|
||||
Atom;
|
||||
{context, Ctx} when is_atom(Ctx) and (Ctx /= nil) ->
|
||||
Ctx;
|
||||
{context, Ctx} ->
|
||||
compile_error(Meta, ?m(E, file), "invalid :context for quote, "
|
||||
"expected a compile time atom or alias, got: ~ts", ['Elixir.Kernel':inspect(Ctx)]);
|
||||
"expected non nil compile time atom or alias, got: ~ts", ['Elixir.Kernel':inspect(Ctx)]);
|
||||
false ->
|
||||
case ?m(E, module) of
|
||||
nil -> 'Elixir';
|
||||
@@ -255,10 +256,6 @@ expand({'try', Meta, [KV]}, E) ->
|
||||
|
||||
%% Comprehensions
|
||||
|
||||
expand({Kind, Meta, Args}, E) when is_list(Args), (Kind == lc) orelse (Kind == bc) ->
|
||||
elixir_errors:deprecation(Meta, ?m(E, file), "~ts is deprecated, please use for comprehensions instead", [Kind]),
|
||||
expand_comprehension(Meta, Kind, Args, E);
|
||||
|
||||
expand({for, Meta, Args}, E) when is_list(Args) ->
|
||||
elixir_for:expand(Meta, Args, E);
|
||||
|
||||
@@ -539,25 +536,6 @@ expand_as({as, Other}, Meta, _IncludeByDefault, _Ref, E) ->
|
||||
compile_error(Meta, ?m(E, file),
|
||||
"invalid value for keyword :as, expected an alias, got: ~ts", ['Elixir.Macro':to_string(Other)]).
|
||||
|
||||
%% Comprehensions
|
||||
|
||||
expand_comprehension(Meta, Kind, Args, E) ->
|
||||
case elixir_utils:split_last(Args) of
|
||||
{Cases, [{do,Expr}]} ->
|
||||
{ECases, EC} = lists:mapfoldl(fun expand_comprehension_clause/2, E, Cases),
|
||||
{EExpr, _} = expand(Expr, EC),
|
||||
{{Kind, Meta, ECases ++ [[{do,EExpr}]]}, E};
|
||||
_ ->
|
||||
compile_error(Meta, ?m(E, file), "missing do keyword in comprehension ~ts", [Kind])
|
||||
end.
|
||||
|
||||
expand_comprehension_clause({Gen, Meta, [Left, Right]}, E) when Gen == inbits; Gen == inlist ->
|
||||
{ERight, ER} = expand(Right, E),
|
||||
{ELeft, EL} = elixir_exp_clauses:match(fun expand/2, Left, E),
|
||||
{{Gen, Meta, [ELeft, ERight]}, elixir_env:mergev(EL, ER)};
|
||||
expand_comprehension_clause(X, E) ->
|
||||
expand(X, E).
|
||||
|
||||
%% Assertions
|
||||
|
||||
assert_no_match_or_guard_scope(Meta, Kind, E) ->
|
||||
|
||||
@@ -141,13 +141,7 @@ expand_rescue(Arg, E) ->
|
||||
normalize_rescue({'_', _, Atom} = N) when is_atom(Atom) -> N;
|
||||
normalize_rescue(Atom) when is_atom(Atom) -> [Atom];
|
||||
normalize_rescue(Other) ->
|
||||
is_list(Other)
|
||||
andalso lists:all(fun is_var_or_atom/1, Other)
|
||||
andalso Other.
|
||||
|
||||
is_var_or_atom({Name, _, Atom}) when is_atom(Name), is_atom(Atom) -> true;
|
||||
is_var_or_atom(Atom) when is_atom(Atom) -> true;
|
||||
is_var_or_atom(_) -> false.
|
||||
is_list(Other) andalso lists:all(fun is_atom/1, Other) andalso Other.
|
||||
|
||||
%% Expansion helpers
|
||||
|
||||
|
||||
@@ -205,8 +205,6 @@ special_form('unquote_splicing', 1) -> true;
|
||||
special_form('fn', _) -> true;
|
||||
special_form('super', _) -> true;
|
||||
special_form('for', _) -> true;
|
||||
special_form('bc', _) -> true;
|
||||
special_form('lc', _) -> true;
|
||||
special_form('case', 2) -> true;
|
||||
special_form('try', 2) -> true;
|
||||
special_form('receive', 1) -> true;
|
||||
|
||||
@@ -135,11 +135,11 @@ eval_form(Line, Module, Block, Vars, E) ->
|
||||
%% Return the form with exports and function declarations.
|
||||
|
||||
functions_form(Line, File, Module, BaseAll, BaseExport, Def, Defmacro, BaseFunctions) ->
|
||||
Info = add_info_function(Line, File, Module, BaseExport, Def, Defmacro),
|
||||
{InfoSpec, Info} = add_info_function(Line, File, Module, BaseExport, Def, Defmacro),
|
||||
|
||||
All = [{'__info__', 1}|BaseAll],
|
||||
Export = [{'__info__', 1}|BaseExport],
|
||||
Functions = [Info|BaseFunctions],
|
||||
Functions = [InfoSpec,Info|BaseFunctions],
|
||||
|
||||
{All, [
|
||||
{attribute, Line, export, lists:sort(Export)} | Functions
|
||||
@@ -259,9 +259,8 @@ load_form(Line, Forms, Opts, #{file := File} = E) ->
|
||||
end).
|
||||
|
||||
check_module_availability(Line, File, Module) ->
|
||||
Reserved = ['Elixir.Atom', 'Elixir.BitString', 'Elixir.Function',
|
||||
'Elixir.PID', 'Elixir.Reference', 'Elixir.Any',
|
||||
'Elixir.Elixir', 'Elixir'],
|
||||
Reserved = ['Elixir.Any', 'Elixir.BitString', 'Elixir.Function', 'Elixir.PID',
|
||||
'Elixir.Reference', 'Elixir.Elixir', 'Elixir'],
|
||||
|
||||
case lists:member(Module, Reserved) of
|
||||
true -> elixir_errors:handle_file_error(File, {Line, ?MODULE, {module_reserved, Module}});
|
||||
@@ -313,14 +312,19 @@ add_info_function(Line, File, Module, Export, Def, Defmacro) ->
|
||||
elixir_errors:form_error(Line, File, ?MODULE, {internal_function_overridden, Pair});
|
||||
false ->
|
||||
Docs = elixir_compiler:get_opt(docs),
|
||||
{function, 0, '__info__', 1, [
|
||||
functions_clause(Def),
|
||||
macros_clause(Defmacro),
|
||||
docs_clause(Module, Docs),
|
||||
moduledoc_clause(Line, Module, Docs),
|
||||
module_clause(Module),
|
||||
else_clause()
|
||||
]}
|
||||
{
|
||||
{attribute, Line, spec, {{'__info__', 1},
|
||||
[{type, Line, 'fun', [{type, Line, product, [ {type, Line, atom, []}]}, {type, Line, term, []} ]}]
|
||||
}},
|
||||
{function, 0, '__info__', 1, [
|
||||
functions_clause(Def),
|
||||
macros_clause(Defmacro),
|
||||
docs_clause(Module, Docs),
|
||||
moduledoc_clause(Line, Module, Docs),
|
||||
module_clause(Module),
|
||||
else_clause()
|
||||
]}
|
||||
}
|
||||
end.
|
||||
|
||||
functions_clause(Def) ->
|
||||
|
||||
@@ -23,10 +23,10 @@ Nonterminals
|
||||
.
|
||||
|
||||
Terminals
|
||||
identifier kw_identifier kw_identifier_string bracket_identifier
|
||||
identifier kw_identifier kw_identifier_safe kw_identifier_unsafe bracket_identifier
|
||||
paren_identifier do_identifier block_identifier
|
||||
fn 'end' aliases
|
||||
number signed_number atom atom_string bin_string list_string sigil
|
||||
number signed_number atom atom_safe atom_unsafe 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 type_op
|
||||
dual_op add_op mult_op exp_op two_op pipe_op stab_op when_op assoc_op
|
||||
@@ -45,7 +45,7 @@ Expect 2.
|
||||
Left 5 do.
|
||||
Right 10 stab_op_eol. %% ->
|
||||
Left 20 ','.
|
||||
Left 40 in_match_op_eol. %% <-, inlist, inbits, \\ (allowed in matches along =)
|
||||
Left 40 in_match_op_eol. %% <-, \\ (allowed in matches along =)
|
||||
Right 50 when_op_eol. %% when
|
||||
Right 60 type_op_eol. %% ::
|
||||
Right 70 pipe_op_eol. %% |
|
||||
@@ -208,7 +208,8 @@ access_expr -> max_expr : '$1'.
|
||||
|
||||
%% Aliases and properly formed calls. Used by map_expr.
|
||||
max_expr -> atom : ?exprs('$1').
|
||||
max_expr -> atom_string : build_atom_string('$1').
|
||||
max_expr -> atom_safe : build_quoted_atom('$1', true).
|
||||
max_expr -> atom_unsafe : build_quoted_atom('$1', false).
|
||||
max_expr -> parens_call call_args_parens : build_identifier('$1', '$2').
|
||||
max_expr -> parens_call call_args_parens call_args_parens : build_nested_parens('$1', '$2', '$3').
|
||||
max_expr -> dot_alias : '$1'.
|
||||
@@ -410,7 +411,6 @@ container_expr -> no_parens_expr : throw_no_parens_many_strict('$1').
|
||||
container_args_base -> container_expr : ['$1'].
|
||||
container_args_base -> container_args_base ',' container_expr : ['$3'|'$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']).
|
||||
@@ -428,8 +428,10 @@ call_args_parens -> open_paren call_args_parens_base ',' kw close_paren : revers
|
||||
|
||||
kw_eol -> kw_identifier : ?exprs('$1').
|
||||
kw_eol -> kw_identifier eol : ?exprs('$1').
|
||||
kw_eol -> kw_identifier_string : build_atom_string('$1').
|
||||
kw_eol -> kw_identifier_string eol : build_atom_string('$1').
|
||||
kw_eol -> kw_identifier_safe : build_quoted_atom('$1', true).
|
||||
kw_eol -> kw_identifier_safe eol : build_quoted_atom('$1', true).
|
||||
kw_eol -> kw_identifier_unsafe : build_quoted_atom('$1', false).
|
||||
kw_eol -> kw_identifier_unsafe eol : build_quoted_atom('$1', false).
|
||||
|
||||
kw_base -> kw_eol container_expr : [{'$1', '$2'}].
|
||||
kw_base -> kw_base ',' kw_eol container_expr : [{'$3', '$4'}|'$1'].
|
||||
@@ -513,7 +515,6 @@ Erlang code.
|
||||
-define(line(Node), element(2, Node)).
|
||||
-define(exprs(Node), element(3, Node)).
|
||||
-define(rearrange_uop(Op), (Op == 'not' orelse Op == '!')).
|
||||
-define(is_atom_string(Atom), (Atom == atom_string orelse Atom == kw_identifier_string)).
|
||||
|
||||
%% The following directive is needed for (significantly) faster
|
||||
%% compilation of the generated .erl file by the HiPE compiler
|
||||
@@ -631,11 +632,11 @@ build_list_string({list_string, _Line, [H]}) when is_binary(H) ->
|
||||
elixir_utils:characters_to_list(H);
|
||||
build_list_string({list_string, Line, Args}) ->
|
||||
Meta = meta(Line),
|
||||
{{'.', Meta, ['Elixir.List', 'from_char_data!']}, Meta, [{'<<>>', Meta, string_parts(Args)}]}.
|
||||
{{'.', Meta, ['Elixir.String', to_char_list]}, Meta, [{'<<>>', Meta, string_parts(Args)}]}.
|
||||
|
||||
build_atom_string({Atom, _Line, Safe, [H]}) when ?is_atom_string(Atom) andalso is_binary(H) ->
|
||||
build_quoted_atom({_, _Line, [H]}, Safe) when is_binary(H) ->
|
||||
Op = binary_to_atom_op(Safe), erlang:Op(H, utf8);
|
||||
build_atom_string({Atom, Line, Safe, Args}) when ?is_atom_string(Atom) ->
|
||||
build_quoted_atom({_, Line, Args}, Safe) ->
|
||||
Meta = meta(Line),
|
||||
{{'.', Meta, [erlang, binary_to_atom_op(Safe)]}, Meta, [{'<<>>', Meta, string_parts(Args)}, utf8]}.
|
||||
|
||||
|
||||
@@ -238,8 +238,11 @@ tokenize([$:,H|T] = Original, Line, Scope, Tokens) when ?is_quote(H) ->
|
||||
case elixir_interpolation:extract(Line, Scope, true, T, H) of
|
||||
{NewLine, Parts, Rest} ->
|
||||
Unescaped = unescape_tokens(Parts),
|
||||
ExistingAtomsOnly = Scope#elixir_tokenizer.existing_atoms_only,
|
||||
tokenize(Rest, NewLine, Scope, [{atom_string, Line, ExistingAtomsOnly, Unescaped}|Tokens]);
|
||||
Key = case Scope#elixir_tokenizer.existing_atoms_only of
|
||||
true -> atom_safe;
|
||||
false -> atom_unsafe
|
||||
end,
|
||||
tokenize(Rest, NewLine, Scope, [{Key, Line, Unescaped}|Tokens]);
|
||||
{error, Reason} ->
|
||||
interpolation_error(Reason, Original, Tokens, " (for atom starting at line ~B)", [Line])
|
||||
end;
|
||||
@@ -453,7 +456,8 @@ tokenize([Space, Sign, NotMarker|T], Line, Scope, [{Identifier, _, _} = H|Tokens
|
||||
?dual_op(Sign),
|
||||
?is_horizontal_space(Space),
|
||||
not(?is_space(NotMarker)),
|
||||
NotMarker /= $(, NotMarker /= $+, NotMarker /= $-, NotMarker /= $>,
|
||||
NotMarker /= $(, NotMarker /= $[, NotMarker /= $<, NotMarker /= ${, %% containers
|
||||
NotMarker /= $%, NotMarker /= $+, NotMarker /= $-, NotMarker /= $/, NotMarker /= $>, %% operators
|
||||
Identifier == identifier ->
|
||||
Rest = [NotMarker|T],
|
||||
tokenize(Rest, Line, Scope, [{dual_op, Line, list_to_atom([Sign])}, setelement(1, H, op_identifier)|Tokens]);
|
||||
@@ -503,8 +507,11 @@ handle_strings(T, Line, H, Scope, Tokens) ->
|
||||
interpolation_error(Reason, [H|T], Tokens, " (for string starting at line ~B)", [Line]);
|
||||
{NewLine, Parts, [$:|Rest]} when ?is_space(hd(Rest)) ->
|
||||
Unescaped = unescape_tokens(Parts),
|
||||
ExistingAtomsOnly = Scope#elixir_tokenizer.existing_atoms_only,
|
||||
tokenize(Rest, NewLine, Scope, [{kw_identifier_string, Line, ExistingAtomsOnly, Unescaped}|Tokens]);
|
||||
Key = case Scope#elixir_tokenizer.existing_atoms_only of
|
||||
true -> kw_identifier_safe;
|
||||
false -> kw_identifier_unsafe
|
||||
end,
|
||||
tokenize(Rest, NewLine, Scope, [{Key, Line, Unescaped}|Tokens]);
|
||||
{NewLine, Parts, Rest} ->
|
||||
Token = {string_type(H), Line, unescape_tokens(Parts)},
|
||||
tokenize(Rest, NewLine, Scope, [Token|Tokens])
|
||||
@@ -626,9 +633,8 @@ extract_heredoc(Line0, Rest0, Marker) ->
|
||||
{error, {ErrorLine, io_lib:format(Message, [Terminator, Line0]), []}}
|
||||
end;
|
||||
error ->
|
||||
Terminator = [Marker, Marker, Marker],
|
||||
Message = "heredoc start ~ts must be followed by a new line",
|
||||
{error, {Line0, io_lib:format(Message, [Terminator]), []}}
|
||||
Message = "heredoc start must be followed by a new line after ",
|
||||
{error, {Line0, io_lib:format(Message, []), [Marker, Marker, Marker]}}
|
||||
end.
|
||||
|
||||
%% Remove spaces from heredoc based on the position of the final quotes.
|
||||
@@ -928,8 +934,6 @@ keyword('or') -> or_op;
|
||||
keyword('xor') -> or_op;
|
||||
keyword('when') -> when_op;
|
||||
keyword('in') -> in_op;
|
||||
keyword('inlist') -> in_match_op;
|
||||
keyword('inbits') -> in_match_op;
|
||||
|
||||
% Block keywords
|
||||
keyword('after') -> block;
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
%% Expects the tree to be expanded.
|
||||
-module(elixir_translator).
|
||||
-export([translate/2, translate_arg/3, translate_args/2, translate_block/3]).
|
||||
-import(elixir_scope, [mergev/2, mergec/2, mergef/2]).
|
||||
-import(elixir_scope, [mergev/2, mergec/2]).
|
||||
-import(elixir_errors, [compile_error/3, compile_error/4]).
|
||||
-include("elixir.hrl").
|
||||
|
||||
@@ -129,9 +129,6 @@ translate({'receive', Meta, [KV]}, #elixir_scope{return=Return} = RS) when is_li
|
||||
|
||||
%% Comprehensions
|
||||
|
||||
translate({Kind, Meta, Args}, S) when is_list(Args), (Kind == lc) orelse (Kind == bc) ->
|
||||
translate_comprehension(Meta, Kind, Args, S);
|
||||
|
||||
translate({for, Meta, Args}, S) when is_list(Args) ->
|
||||
elixir_for:translate(Meta, Args, S);
|
||||
|
||||
@@ -229,11 +226,19 @@ translate({{'.', _, [Left, Right]}, Meta, Args}, S)
|
||||
true ->
|
||||
{Var, _, SV} = elixir_scope:build_var('_', SC),
|
||||
TVar = {var, Line, Var},
|
||||
TMap = {tuple, Line, [
|
||||
{atom, Line, 'Elixir.KeyError'},
|
||||
{atom, Line, '__exception__'},
|
||||
TRight,
|
||||
TVar]},
|
||||
TMap = {map, Line, [
|
||||
{map_field_assoc, Line,
|
||||
{atom, Line, '__struct__'},
|
||||
{atom, Line, 'Elixir.KeyError'}},
|
||||
{map_field_assoc, Line,
|
||||
{atom, Line, '__exception__'},
|
||||
{atom, Line, 'true'}},
|
||||
{map_field_assoc, Line,
|
||||
{atom, Line, key},
|
||||
{atom, Line, TRight}},
|
||||
{map_field_assoc, Line,
|
||||
{atom, Line, term},
|
||||
TVar}]},
|
||||
|
||||
%% TODO There is a bug in dialyzer that makes it fail on
|
||||
%% empty maps. We work around the bug below by using
|
||||
@@ -371,42 +376,6 @@ handles_no_return({'receive', _, [_]}) -> true;
|
||||
handles_no_return({'__block__', _, [_|_]}) -> true;
|
||||
handles_no_return(_) -> false.
|
||||
|
||||
%% Comprehensions
|
||||
|
||||
translate_comprehension(Meta, Kind, Args, S) ->
|
||||
{Cases, [{do,Expr}]} = elixir_utils:split_last(Args),
|
||||
{TCases, SC} = lists:mapfoldl(fun(C, Acc) -> translate_comprehension_clause(Meta, C, Acc) end, S, Cases),
|
||||
{TExpr, SE} = translate_comprehension_do(Meta, Kind, Expr, SC),
|
||||
{{Kind, ?line(Meta), TExpr, TCases}, mergef(S, SE)}.
|
||||
|
||||
translate_comprehension_do(_Meta, bc, {'<<>>', _, _} = Expr, S) ->
|
||||
translate(Expr, S);
|
||||
|
||||
translate_comprehension_do(Meta, bc, _Expr, S) ->
|
||||
compile_error(Meta, S#elixir_scope.file, "a bit comprehension expects a bit string << >> to be returned");
|
||||
|
||||
translate_comprehension_do(_Meta, _Kind, Expr, S) ->
|
||||
translate(Expr, S).
|
||||
|
||||
translate_comprehension_clause(_Meta, {inbits, Meta, [{'<<>>', _, _} = Left, Right]}, S) ->
|
||||
{TRight, SR} = translate(Right, S),
|
||||
{TLeft, SL } = elixir_clauses:match(fun elixir_translator:translate/2, Left, SR),
|
||||
{{b_generate, ?line(Meta), TLeft, TRight}, SL};
|
||||
|
||||
translate_comprehension_clause(_Meta, {inbits, Meta, [_Left, _Right]}, S) ->
|
||||
compile_error(Meta, S#elixir_scope.file, "a bit comprehension expects a bit string << >> to be used in inbits generators");
|
||||
|
||||
translate_comprehension_clause(_Meta, {inlist, Meta, [Left, Right]}, S) ->
|
||||
{TRight, SR} = translate(Right, S),
|
||||
{TLeft, SL } = elixir_clauses:match(fun elixir_translator:translate/2, Left, SR),
|
||||
{{generate, ?line(Meta), TLeft, TRight}, SL};
|
||||
|
||||
translate_comprehension_clause(Meta, X, S) ->
|
||||
Line = ?line(Meta),
|
||||
{TX, TS} = translate(X, S),
|
||||
{BX, BS} = elixir_utils:convert_to_boolean(Line, TX, true, TS),
|
||||
{{match, Line, {var, Line, '_'}, BX}, BS}.
|
||||
|
||||
%% Assertions
|
||||
|
||||
assert_module_scope(Meta, Kind, #elixir_scope{module=nil,file=File}) ->
|
||||
|
||||
+85
-117
@@ -1,15 +1,17 @@
|
||||
-module(elixir_try).
|
||||
-export([clauses/4, format_error/1]).
|
||||
-export([clauses/4]).
|
||||
-include("elixir.hrl").
|
||||
|
||||
clauses(_Meta, Clauses, Return, S) ->
|
||||
Catch = elixir_clauses:get_pairs('catch', Clauses),
|
||||
Rescue = elixir_clauses:get_pairs(rescue, Clauses),
|
||||
Transformer = fun(X, SAcc) ->
|
||||
{TX, TS} = each_clause(X, Return, SAcc),
|
||||
{TX, elixir_scope:mergec(S, TS)}
|
||||
end,
|
||||
lists:mapfoldl(Transformer, S, Rescue ++ Catch).
|
||||
reduce_clauses(Rescue ++ Catch, [], S, Return, S).
|
||||
|
||||
reduce_clauses([H|T], Acc, SAcc, Return, S) ->
|
||||
{TH, TS} = each_clause(H, Return, SAcc),
|
||||
reduce_clauses(T, TH ++ Acc, elixir_scope:mergec(S, TS), Return, S);
|
||||
reduce_clauses([], Acc, SAcc, _Return, _S) ->
|
||||
{lists:reverse(Acc), SAcc}.
|
||||
|
||||
each_clause({'catch', Meta, Raw, Expr}, Return, S) ->
|
||||
{Args, Guards} = elixir_clauses:extract_splat_guards(Raw),
|
||||
@@ -17,37 +19,35 @@ each_clause({'catch', Meta, Raw, Expr}, Return, S) ->
|
||||
Final = case Args of
|
||||
[X] -> [throw, X, {'_', Meta, nil}];
|
||||
[X,Y] -> [X, Y, {'_', Meta, nil}];
|
||||
_ ->
|
||||
elixir_errors:compile_error(Meta, S#elixir_scope.file, "too many arguments given for catch")
|
||||
_ ->
|
||||
elixir_errors:compile_error(Meta, S#elixir_scope.file,
|
||||
"too many arguments given for catch")
|
||||
end,
|
||||
|
||||
Condition = [{'{}', Meta, Final}],
|
||||
elixir_clauses:clause(?line(Meta), fun elixir_translator:translate_args/2,
|
||||
Condition, Expr, Guards, Return, S);
|
||||
{TC, TS} = elixir_clauses:clause(?line(Meta), fun elixir_translator:translate_args/2,
|
||||
Condition, Expr, Guards, Return, S),
|
||||
{[TC], TS};
|
||||
|
||||
each_clause({rescue, Meta, [{in, _, [Left, Right]}], Expr}, Return, S) ->
|
||||
case Left of
|
||||
{'_', _, LAtom} when is_atom(LAtom) ->
|
||||
{VarName, _, CS} = elixir_scope:build_var('_', S),
|
||||
{Clause, _} = rescue_guards(Meta, {VarName, Meta, nil}, Right, S),
|
||||
each_clause({'catch', Meta, Clause, Expr}, Return, CS);
|
||||
_ ->
|
||||
{Clause, Safe} = rescue_guards(Meta, Left, Right, S),
|
||||
case Safe of
|
||||
true ->
|
||||
each_clause({'catch', Meta, Clause, Expr}, Return, S);
|
||||
false ->
|
||||
{VarName, _, CS} = elixir_scope:build_var('_', S),
|
||||
ClauseVar = {VarName, Meta, nil},
|
||||
{FinalClause, _} = rescue_guards(Meta, ClauseVar, Right, S),
|
||||
Match = {'=', Meta, [
|
||||
Left,
|
||||
{{'.', Meta, ['Elixir.Exception', normalize]}, Meta, [error, ClauseVar]}
|
||||
]},
|
||||
FinalExpr = prepend_to_block(Meta, Match, Expr),
|
||||
each_clause({'catch', Meta, FinalClause, FinalExpr}, Return, CS)
|
||||
end
|
||||
end;
|
||||
{VarName, _, CS} = elixir_scope:build_var('_', S),
|
||||
Var = {VarName, Meta, nil},
|
||||
{Parts, Safe, FS} = rescue_guards(Meta, Var, Right, CS),
|
||||
|
||||
Body =
|
||||
case Left of
|
||||
{'_', _, Atom} when is_atom(Atom) ->
|
||||
Expr;
|
||||
_ ->
|
||||
Normalized =
|
||||
case Safe of
|
||||
true -> Var;
|
||||
false -> {{'.', Meta, ['Elixir.Exception', normalize]}, Meta, [error, Var]}
|
||||
end,
|
||||
prepend_to_block(Meta, {'=', Meta, [Left, Normalized]}, Expr)
|
||||
end,
|
||||
|
||||
build_rescue(Meta, Parts, Body, Return, FS);
|
||||
|
||||
each_clause({rescue, Meta, _, _}, _Return, S) ->
|
||||
elixir_errors:compile_error(Meta, S#elixir_scope.file, "invalid arguments for rescue in try");
|
||||
@@ -57,90 +57,70 @@ each_clause({Key, Meta, _, _}, _Return, S) ->
|
||||
|
||||
%% Helpers
|
||||
|
||||
build_rescue(Meta, Parts, Body, Return, S) ->
|
||||
Matches = [Match || {Match, _} <- Parts],
|
||||
|
||||
{{clause, Line, TMatches, _, TBody}, TS} =
|
||||
elixir_clauses:clause(?line(Meta), fun elixir_translator:translate_args/2,
|
||||
Matches, Body, [], Return, S),
|
||||
|
||||
TClauses =
|
||||
[begin
|
||||
TArgs = [{tuple, Line, [{atom, Line, error}, TMatch, {var, Line, '_'}]}],
|
||||
TGuards = elixir_clauses:guards(Line, Guards, [], TS),
|
||||
{clause, Line, TArgs, TGuards, TBody}
|
||||
end || {TMatch, {_, Guards}} <- lists:zip(TMatches, Parts)],
|
||||
|
||||
{TClauses, TS}.
|
||||
|
||||
%% Convert rescue clauses into guards.
|
||||
rescue_guards(_, Var, {'_', _, _}, _) -> {[error, Var], false};
|
||||
rescue_guards(_, Var, {'_', _, _}, S) -> {[{Var, []}], false, S};
|
||||
|
||||
rescue_guards(Meta, Var, Guards, S) ->
|
||||
{RawElixir, RawErlang} = rescue_each_var(Meta, Var, Guards),
|
||||
{Elixir, Erlang, Safe} = rescue_each_ref(Meta, Var, Guards, RawElixir, RawErlang, RawErlang == [], S),
|
||||
rescue_guards(Meta, Var, Aliases, S) ->
|
||||
{Elixir, Erlang} = rescue_each_ref(Meta, Var, Aliases, [], [], S),
|
||||
|
||||
Final = case Elixir == [] of
|
||||
true -> Erlang;
|
||||
false ->
|
||||
IsTuple = {erl(Meta, is_tuple), Meta, [Var]},
|
||||
IsException = {erl(Meta, '=='), Meta, [
|
||||
{erl(Meta, element), Meta, [2, Var]}, '__exception__'
|
||||
]},
|
||||
OrElse = join(Meta, fun erl_or/3, Elixir),
|
||||
[join(Meta, fun erl_and/3, [IsTuple, IsException, OrElse])|Erlang]
|
||||
end,
|
||||
{
|
||||
[{'when', Meta, [error, Var, join_when(Meta, Final)]}],
|
||||
Safe
|
||||
}.
|
||||
{ElixirParts, ES} =
|
||||
case Elixir of
|
||||
[] -> {[], S};
|
||||
_ ->
|
||||
{VarName, _, CS} = elixir_scope:build_var('_', S),
|
||||
StructVar = {VarName, Meta, nil},
|
||||
Map = {'%{}', Meta, [{'__struct__', StructVar}, {'__exception__', true}]},
|
||||
Match = {'=', Meta, [Map, Var]},
|
||||
Guards = [{erl(Meta, '=='), Meta, [StructVar, Mod]} || Mod <- Elixir],
|
||||
{[{Match, Guards}], CS}
|
||||
end,
|
||||
|
||||
%% Handle variables in the right side of rescue.
|
||||
ErlangParts =
|
||||
case Erlang of
|
||||
[] -> [];
|
||||
_ -> [{Var, Erlang}]
|
||||
end,
|
||||
|
||||
rescue_each_var(Meta, ClauseVar, Guards) ->
|
||||
Vars = [Var || Var <- Guards, is_var(Var)],
|
||||
|
||||
case Vars == [] of
|
||||
true -> {[], []};
|
||||
false ->
|
||||
Elixir = [erl_exception_compare(Meta, ClauseVar, Var) || Var <- Vars],
|
||||
Erlang = lists:map(fun(Rescue) ->
|
||||
Compares = [{erl(Meta, '=='), Meta, [Rescue, Var]} || Var <- Vars],
|
||||
erl_and(Meta,
|
||||
erl_rescue_guard_for(Meta, ClauseVar, Rescue),
|
||||
join(Meta, fun erl_or/3, Compares))
|
||||
end, erlang_rescues()),
|
||||
{Elixir, Erlang}
|
||||
end.
|
||||
{ElixirParts ++ ErlangParts, ErlangParts == [], ES}.
|
||||
|
||||
%% Rescue each atom name considering their Erlang or Elixir matches.
|
||||
%% Matching of variables is done with Erlang exceptions is done in another
|
||||
%% method for optimization.
|
||||
%% Matching of variables is done with Erlang exceptions is done in
|
||||
%% function for optimization.
|
||||
|
||||
%% Ignore variables
|
||||
rescue_each_ref(Meta, Var, [{Name, _, Atom}|T], Elixir, Erlang, Safe, S) when is_atom(Name), is_atom(Atom) ->
|
||||
rescue_each_ref(Meta, Var, T, Elixir, Erlang, Safe, S);
|
||||
|
||||
rescue_each_ref(Meta, Var, [H|T], Elixir, Erlang, _Safe, S) when
|
||||
rescue_each_ref(Meta, Var, [H|T], Elixir, Erlang, S) when
|
||||
H == 'Elixir.UndefinedFunctionError'; H == 'Elixir.ErlangError';
|
||||
H == 'Elixir.ArgumentError'; H == 'Elixir.ArithmeticError';
|
||||
H == 'Elixir.BadArityError'; H == 'Elixir.BadFunctionError';
|
||||
H == 'Elixir.MatchError'; H == 'Elixir.CaseClauseError';
|
||||
H == 'Elixir.TryClauseError'; H == 'Elixir.FunctionClauseError';
|
||||
H == 'Elixir.SystemLimitError'; H == 'Elixir.BadStructError' ->
|
||||
Expr = erl_or(Meta,
|
||||
erl_rescue_guard_for(Meta, Var, H),
|
||||
erl_exception_compare(Meta, Var, H)),
|
||||
rescue_each_ref(Meta, Var, T, Elixir, [Expr|Erlang], false, S);
|
||||
Expr = erl_rescue_guard_for(Meta, Var, H),
|
||||
rescue_each_ref(Meta, Var, T, [H|Elixir], [Expr|Erlang], S);
|
||||
|
||||
rescue_each_ref(Meta, Var, [H|T], Elixir, Erlang, Safe, S) when is_atom(H) ->
|
||||
rescue_each_ref(Meta, Var, T, [erl_exception_compare(Meta, Var, H)|Elixir], Erlang, Safe, S);
|
||||
rescue_each_ref(Meta, Var, [H|T], Elixir, Erlang, S) when is_atom(H) ->
|
||||
rescue_each_ref(Meta, Var, T, [H|Elixir], Erlang, S);
|
||||
|
||||
rescue_each_ref(Meta, Var, [H|T], Elixir, Erlang, Safe, S) ->
|
||||
case elixir_translator:translate(H, S) of
|
||||
{{atom, _, Atom}, _} ->
|
||||
rescue_each_ref(Meta, Var, [Atom|T], Elixir, Erlang, Safe, S);
|
||||
_ ->
|
||||
rescue_each_ref(Meta, Var, T, [erl_exception_compare(Meta, Var, H)|Elixir], Erlang, Safe, S)
|
||||
end;
|
||||
|
||||
rescue_each_ref(_, _, [], Elixir, Erlang, Safe, _) ->
|
||||
{Elixir, Erlang, Safe}.
|
||||
rescue_each_ref(_, _, [], Elixir, Erlang, _) ->
|
||||
{Elixir, Erlang}.
|
||||
|
||||
%% Handle erlang rescue matches.
|
||||
|
||||
erlang_rescues() ->
|
||||
[
|
||||
'Elixir.UndefinedFunctionError', 'Elixir.ArgumentError', 'Elixir.ArithmeticError',
|
||||
'Elixir.BadArityError', 'Elixir.BadFunctionError', 'Elixir.MatchError',
|
||||
'Elixir.CaseClauseError', 'Elixir.TryClauseError', 'Elixir.FunctionClauseError',
|
||||
'Elixir.SystemLimitError', 'Elixir.ErlangError', 'Elixir.BadStructError'
|
||||
].
|
||||
|
||||
erl_rescue_guard_for(Meta, Var, List) when is_list(List) ->
|
||||
join(Meta, fun erl_or/3, [erl_rescue_guard_for(Meta, Var, X) || X <- List]);
|
||||
|
||||
@@ -159,39 +139,39 @@ erl_rescue_guard_for(Meta, Var, 'Elixir.ArithmeticError') ->
|
||||
erl_rescue_guard_for(Meta, Var, 'Elixir.BadArityError') ->
|
||||
erl_and(Meta,
|
||||
erl_tuple_size(Meta, Var, 2),
|
||||
erl_exception_compare(Meta, Var, badarity));
|
||||
erl_record_compare(Meta, Var, badarity));
|
||||
|
||||
erl_rescue_guard_for(Meta, Var, 'Elixir.BadFunctionError') ->
|
||||
erl_and(Meta,
|
||||
erl_tuple_size(Meta, Var, 2),
|
||||
erl_exception_compare(Meta, Var, badfun));
|
||||
erl_record_compare(Meta, Var, badfun));
|
||||
|
||||
erl_rescue_guard_for(Meta, Var, 'Elixir.MatchError') ->
|
||||
erl_and(Meta,
|
||||
erl_tuple_size(Meta, Var, 2),
|
||||
erl_exception_compare(Meta, Var, badmatch));
|
||||
erl_record_compare(Meta, Var, badmatch));
|
||||
|
||||
erl_rescue_guard_for(Meta, Var, 'Elixir.CaseClauseError') ->
|
||||
erl_and(Meta,
|
||||
erl_tuple_size(Meta, Var, 2),
|
||||
erl_exception_compare(Meta, Var, case_clause));
|
||||
erl_record_compare(Meta, Var, case_clause));
|
||||
|
||||
erl_rescue_guard_for(Meta, Var, 'Elixir.TryClauseError') ->
|
||||
erl_and(Meta,
|
||||
erl_tuple_size(Meta, Var, 2),
|
||||
erl_exception_compare(Meta, Var, try_clause));
|
||||
erl_record_compare(Meta, Var, try_clause));
|
||||
|
||||
erl_rescue_guard_for(Meta, Var, 'Elixir.BadStructError') ->
|
||||
erl_and(Meta,
|
||||
erl_tuple_size(Meta, Var, 3),
|
||||
erl_exception_compare(Meta, Var, badstruct));
|
||||
erl_record_compare(Meta, Var, badstruct));
|
||||
|
||||
erl_rescue_guard_for(Meta, Var, 'Elixir.ArgumentError') ->
|
||||
erl_or(Meta,
|
||||
{erl(Meta, '=='), Meta, [Var, badarg]},
|
||||
erl_and(Meta,
|
||||
erl_tuple_size(Meta, Var, 2),
|
||||
erl_exception_compare(Meta, Var, badarg)));
|
||||
erl_record_compare(Meta, Var, badarg)));
|
||||
|
||||
erl_rescue_guard_for(Meta, Var, 'Elixir.ErlangError') ->
|
||||
IsNotTuple = {erl(Meta, 'not'), Meta, [{erl(Meta, is_tuple), Meta, [Var]}]},
|
||||
@@ -202,19 +182,10 @@ erl_rescue_guard_for(Meta, Var, 'Elixir.ErlangError') ->
|
||||
|
||||
%% Helpers
|
||||
|
||||
format_error({rescue_no_match, Var, Alias}) ->
|
||||
VarBinary = 'Elixir.Macro':to_string(Var),
|
||||
AliasBinary = 'Elixir.Macro':to_string(Alias),
|
||||
Message = "rescue clause (~ts = ~ts) can never match, maybe you meant to write: ~ts in [~ts] ?",
|
||||
io_lib:format(Message, [AliasBinary, VarBinary, VarBinary, AliasBinary]).
|
||||
|
||||
is_var({Name, _, Atom}) when is_atom(Name), is_atom(Atom) -> true;
|
||||
is_var(_) -> false.
|
||||
|
||||
erl_tuple_size(Meta, Var, Size) ->
|
||||
{erl(Meta, '=='), Meta, [{erl(Meta, tuple_size), Meta, [Var]}, Size]}.
|
||||
|
||||
erl_exception_compare(Meta, Var, Expr) ->
|
||||
erl_record_compare(Meta, Var, Expr) ->
|
||||
{erl(Meta, '=='), Meta, [
|
||||
{erl(Meta, element), Meta, [1, Var]},
|
||||
Expr
|
||||
@@ -223,9 +194,6 @@ erl_exception_compare(Meta, Var, Expr) ->
|
||||
join(Meta, Kind, [H|T]) ->
|
||||
lists:foldl(fun(X, Acc) -> Kind(Meta, Acc, X) end, H, T).
|
||||
|
||||
join_when(Meta, [H|T]) ->
|
||||
lists:foldl(fun(X, Acc) -> {'when', Meta, [X, Acc]} end, H, T).
|
||||
|
||||
prepend_to_block(_Meta, Expr, {'__block__', Meta, Args}) ->
|
||||
{'__block__', Meta, [Expr|Args]};
|
||||
|
||||
|
||||
@@ -43,7 +43,7 @@ file_type(File, Op) ->
|
||||
relative_to_cwd(Path) ->
|
||||
case elixir_compiler:get_opt(internal) of
|
||||
true -> Path;
|
||||
false -> 'Elixir.List':'from_char_data!'('Elixir.Path':relative_to_cwd(Path))
|
||||
false -> 'Elixir.String':to_char_list('Elixir.Path':relative_to_cwd(Path))
|
||||
end.
|
||||
|
||||
characters_to_list(Data) when is_list(Data) ->
|
||||
@@ -51,7 +51,7 @@ characters_to_list(Data) when is_list(Data) ->
|
||||
characters_to_list(Data) ->
|
||||
case elixir_compiler:get_opt(internal) of
|
||||
true -> unicode:characters_to_list(Data);
|
||||
false -> 'Elixir.List':'from_char_data!'(Data)
|
||||
false -> 'Elixir.String':to_char_list(Data)
|
||||
end.
|
||||
|
||||
characters_to_binary(Data) when is_binary(Data) ->
|
||||
@@ -59,7 +59,7 @@ characters_to_binary(Data) when is_binary(Data) ->
|
||||
characters_to_binary(Data) ->
|
||||
case elixir_compiler:get_opt(internal) of
|
||||
true -> unicode:characters_to_binary(Data);
|
||||
false -> 'Elixir.String':'from_char_data!'(Data)
|
||||
false -> 'Elixir.List':to_string(Data)
|
||||
end.
|
||||
|
||||
%% elixir to erl. Handles only valid quoted expressions,
|
||||
|
||||
@@ -4,6 +4,7 @@ defmodule KernelTest do
|
||||
use ExUnit.Case, async: true
|
||||
|
||||
doctest Access
|
||||
doctest Atom
|
||||
doctest Base
|
||||
doctest Bitwise
|
||||
doctest Code
|
||||
@@ -14,6 +15,7 @@ defmodule KernelTest do
|
||||
doctest Inspect
|
||||
doctest Inspect.Algebra
|
||||
doctest Integer
|
||||
doctest IO
|
||||
doctest IO.ANSI
|
||||
doctest Kernel
|
||||
doctest Kernel.SpecialForms
|
||||
@@ -26,13 +28,13 @@ defmodule KernelTest do
|
||||
doctest OptionParser
|
||||
doctest Path
|
||||
doctest Process
|
||||
doctest Protocol.Consolidation
|
||||
doctest Protocol
|
||||
doctest Range
|
||||
doctest Record
|
||||
doctest Regex
|
||||
doctest Stream
|
||||
doctest String
|
||||
doctest String.Chars.List
|
||||
doctest String.Chars
|
||||
doctest StringIO
|
||||
doctest Tuple
|
||||
doctest URI
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
Code.require_file "test_helper.exs", __DIR__
|
||||
|
||||
defmodule AgentTest do
|
||||
use ExUnit.Case, async: true
|
||||
|
||||
test "start_link/2 workflow with unregistered name" do
|
||||
{:ok, pid} = Agent.start_link(fn -> %{} end)
|
||||
|
||||
{:links, links} = Process.info(self, :links)
|
||||
assert pid in links
|
||||
|
||||
assert Agent.update(pid, &Map.put(&1, :hello, :world)) == :ok
|
||||
assert Agent.get(pid, &Map.get(&1, :hello), 3000) == :world
|
||||
assert Agent.get_and_update(pid, &Map.pop(&1, :hello), 3000) == :world
|
||||
assert Agent.get(pid, &(&1)) == %{}
|
||||
assert Agent.stop(pid) == :ok
|
||||
wait_until_dead(pid)
|
||||
end
|
||||
|
||||
test "start/2 workflow with registered name" do
|
||||
{:ok, pid} = Agent.start(fn -> %{} end, name: :agent)
|
||||
assert Process.info(pid, :registered_name) == {:registered_name, :agent}
|
||||
assert Agent.cast(:agent, &Map.put(&1, :hello, :world)) == :ok
|
||||
assert Agent.get(:agent, &Map.get(&1, :hello)) == :world
|
||||
assert Agent.get_and_update(:agent, &Map.pop(&1, :hello)) == :world
|
||||
assert Agent.get(:agent, &(&1)) == %{}
|
||||
assert Agent.stop(:agent) == :ok
|
||||
assert Process.info(pid, :registered_name) == nil
|
||||
end
|
||||
|
||||
test ":sys.change_code/4 with mfa" do
|
||||
{ :ok, pid } = Agent.start_link(fn -> %{} end)
|
||||
:ok = :sys.suspend(pid)
|
||||
mfa = { Map, :put, [:hello, :world] }
|
||||
assert :sys.change_code(pid, __MODULE__, "vsn", mfa) == :ok
|
||||
:ok = :sys.resume(pid)
|
||||
assert Agent.get(pid, &Map.get(&1, :hello)) == :world
|
||||
assert Agent.stop(pid) == :ok
|
||||
end
|
||||
|
||||
test ":sys.change_code/4 with raising mfa" do
|
||||
{ :ok, pid } = Agent.start_link(fn -> %{} end)
|
||||
:ok = :sys.suspend(pid)
|
||||
mfa = { :erlang, :error, [] }
|
||||
assert match?({ :error, _ }, :sys.change_code(pid, __MODULE__, "vsn", mfa))
|
||||
:ok = :sys.resume(pid)
|
||||
assert Agent.get(pid, &(&1)) == %{}
|
||||
assert Agent.stop(pid) == :ok
|
||||
end
|
||||
|
||||
defp wait_until_dead(pid) do
|
||||
if Process.alive?(pid) do
|
||||
wait_until_dead(pid)
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,13 @@
|
||||
Code.require_file "test_helper.exs", __DIR__
|
||||
|
||||
defmodule AtomTest do
|
||||
use ExUnit.Case, async: true
|
||||
|
||||
test "to_string/1" do
|
||||
assert Atom.to_string(:"héllo") == "héllo"
|
||||
end
|
||||
|
||||
test "to_char_list/1" do
|
||||
assert Atom.to_char_list(:"héllo") == 'héllo'
|
||||
end
|
||||
end
|
||||
@@ -42,28 +42,4 @@ bar '''
|
||||
assert '\x{10FFF}' == [69631]
|
||||
assert '\x{10FFFF}' == [1114111]
|
||||
end
|
||||
|
||||
test :from_char_data do
|
||||
assert List.from_char_data("æß") == {:ok, [?æ, ?ß]}
|
||||
assert List.from_char_data("abc") == {:ok, [?a, ?b, ?c]}
|
||||
|
||||
assert List.from_char_data(<< 0xDF, 0xFF >>) == {:error, [], << 223, 255 >>}
|
||||
assert List.from_char_data(<< 106, 111, 115, 195 >>) == {:incomplete, 'jos', << 195 >>}
|
||||
end
|
||||
|
||||
test :from_char_data! do
|
||||
assert List.from_char_data!("æß") == [?æ, ?ß]
|
||||
assert List.from_char_data!("abc") == [?a, ?b, ?c]
|
||||
|
||||
assert_raise UnicodeConversionError,
|
||||
"invalid encoding starting at <<223, 255>>", fn ->
|
||||
List.from_char_data!(<< 0xDF, 0xFF >>)
|
||||
end
|
||||
|
||||
assert_raise UnicodeConversionError,
|
||||
"incomplete encoding starting at <<195>>", fn ->
|
||||
List.from_char_data!(<< 106, 111, 115, 195 >>)
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
@@ -29,7 +29,7 @@ defmodule CodeTest do
|
||||
end
|
||||
|
||||
test :eval_with_unnamed_scopes do
|
||||
assert {RuntimeError[], [a: RuntimeError[]]} =
|
||||
assert {%RuntimeError{}, [a: %RuntimeError{}]} =
|
||||
Code.eval_string("a = (try do (raise \"hello\") rescue e -> e end)")
|
||||
end
|
||||
|
||||
@@ -70,6 +70,10 @@ defmodule CodeTest do
|
||||
|
||||
test :eval_file do
|
||||
assert Code.eval_file(fixture_path("code_sample.exs")) == {3, [var: 3]}
|
||||
|
||||
assert_raise Code.LoadError, fn ->
|
||||
Code.eval_file("non_existent.exs")
|
||||
end
|
||||
end
|
||||
|
||||
test :require do
|
||||
@@ -111,7 +115,7 @@ defmodule CodeTest do
|
||||
end
|
||||
|
||||
test :compile_source do
|
||||
assert __MODULE__.__info__(:compile)[:source] == List.from_char_data!(__ENV__.file)
|
||||
assert __MODULE__.__info__(:compile)[:source] == String.to_char_list(__ENV__.file)
|
||||
end
|
||||
|
||||
test :compile_info_returned_with_source_accessible_through_keyword_module do
|
||||
|
||||
@@ -1,57 +0,0 @@
|
||||
Code.require_file "../test_helper.exs", __DIR__
|
||||
|
||||
defmodule Record.AccessTest do
|
||||
use ExUnit.Case, async: true
|
||||
|
||||
defrecord User, age: 0, name: nil do
|
||||
def get_age(__MODULE__[age: age]) do
|
||||
age
|
||||
end
|
||||
end
|
||||
|
||||
test "access with keywords" do
|
||||
assert User[] == {User, 0, nil}
|
||||
assert User[age: 1] == {User, 1, nil}
|
||||
end
|
||||
|
||||
test "access with variable inside match" do
|
||||
assert get_var(User.new) == 0
|
||||
assert get_var(User.new(age: 1)) == 1
|
||||
end
|
||||
|
||||
test "access match on record name" do
|
||||
assert is_user(User.new) == true
|
||||
assert is_user({Access.AtomTest, 1}) == false
|
||||
assert is_user({User, 1, 2, 3}) == false
|
||||
end
|
||||
|
||||
test "access with field match" do
|
||||
assert is_zero(User.new) == true
|
||||
assert is_zero(User.new(age: 1)) == false
|
||||
end
|
||||
|
||||
test "match (=)" do
|
||||
assert User[age: 1] = User.new(age: 1)
|
||||
refute User[age: 1] = User.new(age: 0)
|
||||
end
|
||||
|
||||
test "access with underscore" do
|
||||
assert User[age: 0, name: 0] == User[_: 0]
|
||||
assert User[_: _] = User[_: "x"]
|
||||
refute match?(User[_: 0], User[_: 1])
|
||||
end
|
||||
|
||||
test "access during record definition" do
|
||||
assert User.new(age: 13).get_age == 13
|
||||
end
|
||||
|
||||
defp get_var(User[age: age]) do
|
||||
age
|
||||
end
|
||||
|
||||
defp is_zero(User[age: 0]), do: true
|
||||
defp is_zero(User[age: _]), do: false
|
||||
|
||||
defp is_user(User[]), do: true
|
||||
defp is_user(_), do: false
|
||||
end
|
||||
@@ -1,116 +0,0 @@
|
||||
Code.require_file "../test_helper.exs", __DIR__
|
||||
|
||||
defmodule Record.PrivateTest do
|
||||
use ExUnit.Case, async: true
|
||||
|
||||
require Record
|
||||
|
||||
defmodule Macros do
|
||||
defrecordp :_user, __MODULE__, name: "José", age: 25
|
||||
defrecordp :_my_user, :my_user, name: "José", age: 25
|
||||
|
||||
name = :_file_info
|
||||
defrecordp name, Record.extract(:file_info, from_lib: "kernel/include/file.hrl")
|
||||
|
||||
Record.defmacros(:_macro, [c: 2, d: 3], __ENV__)
|
||||
|
||||
def new() do
|
||||
_user()
|
||||
end
|
||||
|
||||
def new(name, age) do
|
||||
_user(name: name, age: age)
|
||||
end
|
||||
|
||||
def name(_user(name: name)) do
|
||||
name
|
||||
end
|
||||
|
||||
def age(user) do
|
||||
_user(user, :age)
|
||||
end
|
||||
|
||||
def add_bar_to_name(_user(name: name) = user) do
|
||||
_user(user, name: name <> " bar")
|
||||
end
|
||||
|
||||
def name_ix() do
|
||||
_user(:name)
|
||||
end
|
||||
|
||||
def age_ix() do
|
||||
_user(:age)
|
||||
end
|
||||
|
||||
def my_new() do
|
||||
_my_user()
|
||||
end
|
||||
|
||||
def my_new(name, age) do
|
||||
_my_user(name: name, age: age)
|
||||
end
|
||||
|
||||
def my_name(_my_user(name: name)) do
|
||||
name
|
||||
end
|
||||
|
||||
def my_age(user) do
|
||||
_my_user(user, :age)
|
||||
end
|
||||
|
||||
def my_add_bar_to_name(_my_user(name: name) = user) do
|
||||
_my_user(user, name: name <> " bar")
|
||||
end
|
||||
|
||||
def my_name_ix() do
|
||||
_my_user(:name)
|
||||
end
|
||||
|
||||
def my_age_ix() do
|
||||
_my_user(:age)
|
||||
end
|
||||
|
||||
def file_info() do
|
||||
_file_info()
|
||||
end
|
||||
|
||||
def macro() do
|
||||
_macro()
|
||||
end
|
||||
end
|
||||
|
||||
test "defrecordp generates macros that generates tuples" do
|
||||
record = Macros.my_new
|
||||
assert Macros.my_name(record) == "José"
|
||||
assert Macros.my_age(record) == 25
|
||||
|
||||
record = Macros.my_new("Foo", 25)
|
||||
assert Macros.my_name(record) == "Foo"
|
||||
|
||||
record = Macros.my_add_bar_to_name(record)
|
||||
assert Macros.my_name(record) == "Foo bar"
|
||||
|
||||
assert elem(record, 0) == :my_user
|
||||
end
|
||||
|
||||
test "defrecordp access index" do
|
||||
assert Macros.name_ix == 1
|
||||
assert Macros.age_ix == 2
|
||||
assert Macros.my_name_ix == 1
|
||||
assert Macros.my_age_ix == 2
|
||||
end
|
||||
|
||||
test "defrecordp with dynamic arguments" do
|
||||
assert [:_file_info|_] = Macros.file_info() |> tuple_to_list
|
||||
end
|
||||
|
||||
test "defmacros" do
|
||||
assert {:_macro, 2, 3} = Macros.macro()
|
||||
end
|
||||
|
||||
defrecordp :match, [:name]
|
||||
|
||||
test "defrecordp generates macros that are matchable" do
|
||||
assert match(name: 42) = match(name: 42)
|
||||
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