Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ed27611f48 | ||
|
|
fd29173d6d | ||
|
|
963efc4593 | ||
|
|
cfc21d04c6 | ||
|
|
507fda525b | ||
|
|
e3c6cc9b85 | ||
|
|
c78c8ddf08 | ||
|
|
8f34d58829 | ||
|
|
5b9c3e7aa6 | ||
|
|
c125636535 | ||
|
|
531bd8da0c | ||
|
|
a4d8fc2893 | ||
|
|
b96c3c52ae | ||
|
|
3f7b867740 | ||
|
|
f2e78d3d39 | ||
|
|
5d1d3f23e9 | ||
|
|
01cc9ed0ec | ||
|
|
4096b31cfe | ||
|
|
49a5b8f28c | ||
|
|
f49c351bb4 | ||
|
|
aa45edc82c | ||
|
|
2c36d8f625 | ||
|
|
d3238b8778 | ||
|
|
ec69bdfbbe | ||
|
|
3d66ab35b5 | ||
|
|
8d2034f07c | ||
|
|
b82b51a870 | ||
|
|
be5e20f7d4 | ||
|
|
26599e7812 | ||
|
|
52aa1fd068 | ||
|
|
728d08c3bd | ||
|
|
84cd94c2e5 | ||
|
|
b9ef56df27 | ||
|
|
1ffda62f2e | ||
|
|
0fad1883df | ||
|
|
d4eda432ec | ||
|
|
71831d654c | ||
|
|
13c334f70b | ||
|
|
875b4c7ecf | ||
|
|
bef41d7b55 | ||
|
|
6512fcb06e | ||
|
|
c2d1699248 | ||
|
|
e412ce6b22 | ||
|
|
e01f958b1a | ||
|
|
35d0edb3b1 | ||
|
|
ca456200cc | ||
|
|
fcc7fc4351 | ||
|
|
ca0250a9d7 | ||
|
|
f859c9d350 | ||
|
|
a576540159 | ||
|
|
a34f38ee40 | ||
|
|
de06764691 | ||
|
|
4d0d7e8f2a | ||
|
|
025c425514 | ||
|
|
48da7e8ed7 | ||
|
|
f8c0729f3b | ||
|
|
0c50664817 | ||
|
|
e7b329dbe2 | ||
|
|
5581c8255f | ||
|
|
7be936eb6d | ||
|
|
26e3ec644b | ||
|
|
29ea1df744 | ||
|
|
1334849ff7 | ||
|
|
dbf711b519 | ||
|
|
0b0bbdee5e | ||
|
|
7b9f2bd298 | ||
|
|
9016a18fa5 | ||
|
|
bd6f27a5d5 | ||
|
|
f9e74f944e | ||
|
|
3a98369842 | ||
|
|
a963269244 | ||
|
|
0463407cbf | ||
|
|
c59be9b4a8 | ||
|
|
8d6aa70acb | ||
|
|
4612877e78 | ||
|
|
c0fbea8e9b | ||
|
|
c3e50546ec | ||
|
|
b954d53585 | ||
|
|
2f848953df | ||
|
|
24a49295ef | ||
|
|
02edfed204 | ||
|
|
75fcdac740 | ||
|
|
538a29ef70 | ||
|
|
15ac6e947a | ||
|
|
01d5701419 | ||
|
|
0f91e24f31 | ||
|
|
7e9c5c97b2 | ||
|
|
a114702967 | ||
|
|
25bc992e76 | ||
|
|
196358525a | ||
|
|
f7cad1cffc | ||
|
|
fee5c5d762 | ||
|
|
dc0897ec03 | ||
|
|
369fd7506c | ||
|
|
c6ff84e2b3 | ||
|
|
0e47d8cbdc | ||
|
|
34da5f883b | ||
|
|
1c2d41974c | ||
|
|
4ad1ca375b | ||
|
|
27d958063a | ||
|
|
a8c89e3703 | ||
|
|
0872fdd733 | ||
|
|
e27707ec1a | ||
|
|
5a84acf607 | ||
|
|
5d00a9ff58 | ||
|
|
6024014482 | ||
|
|
01d7e2124c | ||
|
|
179b18a970 | ||
|
|
618f7822b5 | ||
|
|
866558710c | ||
|
|
81d4063fe9 | ||
|
|
2c5fa31ed7 | ||
|
|
238e7f4bc2 | ||
|
|
f10171bfd2 | ||
|
|
ecb23a4b42 | ||
|
|
ff825b89bc | ||
|
|
3788bf4a5e | ||
|
|
6e8bcd9245 | ||
|
|
2817b2ee90 | ||
|
|
affc44d020 | ||
|
|
b5e58473d9 | ||
|
|
30fa89e270 | ||
|
|
9084d2a4c4 | ||
|
|
592d7d9338 | ||
|
|
e811f0a538 | ||
|
|
9ff534c6d1 | ||
|
|
bc163abe1f | ||
|
|
6093aa0384 | ||
|
|
4cc92aaf8d | ||
|
|
e1e3bff3fb | ||
|
|
824168a163 | ||
|
|
07a2283845 | ||
|
|
ae6a51fc83 | ||
|
|
28ebdad467 | ||
|
|
98e488a52d | ||
|
|
0e39a552e6 | ||
|
|
88d915ffb6 | ||
|
|
18f127d69b | ||
|
|
ae0cad4ec3 | ||
|
|
22da62600e | ||
|
|
81aae93726 | ||
|
|
62eddd822d | ||
|
|
e7274fdb51 | ||
|
|
c10b5e7e4f | ||
|
|
b396696c5c | ||
|
|
6bf3ca1e89 | ||
|
|
6a24bc4ef3 | ||
|
|
24ca9c0af5 | ||
|
|
8a58722dd0 | ||
|
|
5844698f39 | ||
|
|
b55a917048 |
@@ -6,6 +6,8 @@ notifications:
|
||||
- jose.valim@plataformatec.com.br
|
||||
- yrashk@gmail.com
|
||||
otp_release:
|
||||
- R16B
|
||||
- R15B03
|
||||
- R15B02
|
||||
- R15B01
|
||||
- R15B
|
||||
|
||||
@@ -1,3 +1,43 @@
|
||||
# v0.8.2 (2013-04-20)
|
||||
|
||||
* enhancements
|
||||
* [ExUnit] Use ANSI escape codes in CLI output
|
||||
* [ExUnit] Include suite run time on CLI results
|
||||
* [ExUnit] Add support to doctests, allowing test cases to be generated from code samples
|
||||
* [File] Add `File.ls` and `File.ls!`
|
||||
* [IEx] Support `pwd` and `cd` helpers
|
||||
* [Kernel] Better error reporting for invalid bitstring generators
|
||||
* [Kernel] Improve meta-programming by allowing `unquote` on `def/2`, `defp/2`, `defmacro/2` and `defmacrop/2`
|
||||
* [Kernel] Add support to R16B new functions: `insert_elem/3` and `delete_elem/2`
|
||||
* [Kernel] Import conflicts are now lazily handled. If two modules import the same functions, it will fail only if the function is invoked
|
||||
* [Mix] Support `--cover` on mix test and `test_coverage` on Mixfiles
|
||||
* [Record] Each record now provides `Record.options` with the options supported by its `new` and `update` functions
|
||||
|
||||
* bug fix
|
||||
* [Binary] inspect no longer escapes standalone hash `#`
|
||||
* [IEx] The `h` helper can now retrieve docs for special forms
|
||||
* [Kernel] Record optimizations were not being triggered in functions inside the record module
|
||||
* [Kernel] Aliases defined inside macros should be carried over
|
||||
* [Kernel] Fix a bug where nested records could not use the Record[] syntax
|
||||
* [Path] Fix a bug on `Path.expand` when expanding paths starting with `~`
|
||||
|
||||
* deprecations
|
||||
* [Kernel] `setelem/3` is deprecated in favor of `set_elem/3`
|
||||
* [Kernel] `function(:is_atom, 1)` is deprecated in favor of `function(is_atom/1)`
|
||||
|
||||
* backwards incompatible changes
|
||||
* [Kernel] `unquote` now only applies to the closest quote. If your code contains a quote that contains another quote that calls unquote, it will no longer work. Use `Macro.escape` instead and pass your quoted contents up in steps, for example:
|
||||
|
||||
quote do
|
||||
quote do: unquote(x)
|
||||
end
|
||||
|
||||
should become:
|
||||
|
||||
quote do
|
||||
unquote(Macro.escape(x))
|
||||
end
|
||||
|
||||
# v0.8.1 (2013-02-17)
|
||||
|
||||
* enhancements
|
||||
|
||||
@@ -97,7 +97,7 @@ docs: compile
|
||||
mkdir -p ebin
|
||||
rm -rf docs
|
||||
cp -R -f lib/*/ebin/*.beam ./ebin
|
||||
bin/elixir ../exdoc/bin/exdoc
|
||||
bin/elixir ../exdoc/bin/exdoc "Elixir" "$(VERSION)" -m Kernel -u "https://github.com/elixir-lang/elixir/blob/master/%{path}#L%{line}"
|
||||
rm -rf ebin
|
||||
|
||||
release_zip: compile
|
||||
@@ -107,7 +107,7 @@ release_zip: compile
|
||||
release_docs: docs
|
||||
cd ../elixir-lang.github.com && git checkout master
|
||||
rm -rf ../elixir-lang.github.com/docs/master
|
||||
mv output ../elixir-lang.github.com/docs/master
|
||||
mv docs ../elixir-lang.github.com/docs/master
|
||||
|
||||
release_erl: compile
|
||||
@ rm -rf rel/elixir
|
||||
@@ -124,7 +124,11 @@ test_erlang: compile
|
||||
@ $(ERL) -pa lib/elixir/test/ebin -s test_helper test -s erlang halt;
|
||||
@ echo
|
||||
|
||||
test_elixir: test_kernel test_mix test_ex_unit test_eex test_iex
|
||||
test_elixir: test_kernel test_doctest test_mix test_ex_unit test_eex test_iex
|
||||
|
||||
test_doctest: compile
|
||||
@ echo "==> doctest (exunit)"
|
||||
@ cd lib/elixir && ../../bin/elixir -r "test/doctest.exs";
|
||||
|
||||
test_kernel: compile
|
||||
@ echo "==> kernel (exunit)"
|
||||
|
||||
+6
-6
@@ -2,7 +2,7 @@
|
||||
|
||||
This document simply outlines the release process:
|
||||
|
||||
1) Remove .dev extension from current versions
|
||||
1) Remove `.dev` extension from VERSION
|
||||
|
||||
2) Run `make clean test` to ensure all tests pass from scratch and the CI is green
|
||||
|
||||
@@ -12,16 +12,16 @@ This document simply outlines the release process:
|
||||
|
||||
5) Create tag from master branch
|
||||
|
||||
6) Release new docs, update elixir-lang.org
|
||||
6) Release new docs with `make release_docs`, update elixir-lang.org
|
||||
|
||||
7) Push new zip to Elixir's downloads page
|
||||
7) Release new zip with `make release_zip`, push new zip to Elixir's elixir-lang.org/packages.html
|
||||
|
||||
8) Push package to expm
|
||||
8) Push package to expm with `expm publish package.exs`
|
||||
|
||||
9) After release, bump versions and add .dev back
|
||||
9) After release, bump versions and add `.dev` back
|
||||
|
||||
## Places where version is mentioned
|
||||
|
||||
* VERSION
|
||||
* CHANGELOG
|
||||
* src/elixir.app.src
|
||||
* src/elixir.app.src
|
||||
|
||||
+2
-3
@@ -1,8 +1,8 @@
|
||||
#!/bin/sh
|
||||
if [ $# -eq 0 ]; then
|
||||
if [ $# -eq 0 ] || [ $1 = "--help" ] || [ $1 = "-h" ]; then
|
||||
echo "Usage: `basename $0` [options] [.exs file] [data]
|
||||
|
||||
-v Prints version and exit
|
||||
-v Prints version
|
||||
-e \"command\" Evaluates the given command (*)
|
||||
-r \"file\" Requires the given files/patterns (*)
|
||||
-S \"script\" Finds and executes the given script (*)
|
||||
@@ -13,7 +13,6 @@ if [ $# -eq 0 ]; then
|
||||
--erl \"switches\" Switches to be passed down to erlang (*)
|
||||
--name \"name\" Makes and assigns a name to the distributed node
|
||||
--sname \"name\" Makes and assigns a short name to the distributed node
|
||||
--remsh \"name\" Connects to a node using a remote shell (with iex)
|
||||
--detached Starts the Erlang VM detached from console
|
||||
--no-halt Does not halt the Erlang VM after execution
|
||||
|
||||
|
||||
@@ -1,4 +1,26 @@
|
||||
#!/bin/sh
|
||||
if [ $# -gt 0 ] && ([ $1 = "--help" ] || [ $1 = "-h" ]); then
|
||||
echo "Usage: `basename $0` [options] [.exs file] [data]
|
||||
|
||||
-v Prints version
|
||||
-e \"command\" Evaluates the given command (*)
|
||||
-r \"file\" Requires the given files/patterns (*)
|
||||
-S \"script\" Finds and executes the given script (*)
|
||||
-pr \"file\" Requires the given files/patterns in parallel (*)
|
||||
-pa \"path\" Prepends the given path to Erlang code path (*)
|
||||
-pz \"path\" Appends the given path to Erlang code path (*)
|
||||
--app \"app\" Start the given app and its dependencies (*)
|
||||
--erl \"switches\" Switches to be passed down to erlang (*)
|
||||
--name \"name\" Makes and assigns a name to the distributed node
|
||||
--sname \"name\" Makes and assigns a short name to the distributed node
|
||||
--remsh \"name\" Connects to a node using a remote shell
|
||||
|
||||
** 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
|
||||
exit 1
|
||||
fi
|
||||
|
||||
readlink_f () {
|
||||
cd "$(dirname "$1")" > /dev/null
|
||||
local filename="$(basename "$1")"
|
||||
|
||||
@@ -11,7 +11,6 @@
|
||||
-record(elixir_scope, {
|
||||
context=nil, %% can be assign, guards or nil
|
||||
noname=false, %% when true, don't add new names (used by try)
|
||||
check_requires=true, %% when true, check requires
|
||||
check_clauses=true, %% when true, check def clauses ordering
|
||||
super=false, %% when true, it means super was invoked
|
||||
caller=false, %% when true, it means caller was invoked
|
||||
@@ -29,8 +28,11 @@
|
||||
aliases, %% an orddict with aliases by new -> old names
|
||||
file, %% the current scope filename
|
||||
requires, %% a set with modules required
|
||||
macros, %% a list with macros imported by module
|
||||
functions}). %% a list with functions imported by module
|
||||
macro_macros=[], %% a list with macros imported from module inside a macro
|
||||
macros, %% a list with macros imported from module
|
||||
macro_functions=[], %% a list with functions imported from module inside a macro
|
||||
functions %% a list with functions imported from module
|
||||
}).
|
||||
|
||||
-record(elixir_quote, {
|
||||
line=0,
|
||||
@@ -52,4 +54,3 @@
|
||||
-define(is_word(S), ?is_digit(S) orelse ?is_upcase(S) orelse ?is_downcase(S)).
|
||||
-define(is_quote(S), S == $" orelse S == $').
|
||||
-define(is_space(S), S == $\s; S == $\r; S == $\t; S == $\n).
|
||||
|
||||
|
||||
@@ -25,11 +25,13 @@ defimpl Access, for: List do
|
||||
|
||||
## Examples
|
||||
|
||||
keywords = [a: 1, b: 2]
|
||||
keywords[:a] #=> 1
|
||||
iex> keywords = [a: 1, b: 2]
|
||||
...> keywords[:a]
|
||||
1
|
||||
|
||||
star_ratings = [{1.0, "★"}, {1.5, "★☆"}, {2.0, "★★"}]
|
||||
star_ratings[1.5] #=> "★☆"
|
||||
iex> star_ratings = [{1.0, "★"}, {1.5, "★☆"}, {2.0, "★★"}]
|
||||
...> star_ratings[1.5]
|
||||
"★☆"
|
||||
|
||||
"""
|
||||
def access([], _key), do: nil
|
||||
|
||||
@@ -51,8 +51,10 @@ defimpl Binary.Chars, for: List do
|
||||
|
||||
## Examples
|
||||
|
||||
to_binary 'foo' #=> "foo"
|
||||
to_binary ["foo", 'bar'] #=> "foobar"
|
||||
iex> to_binary 'foo'
|
||||
"foo"
|
||||
iex> to_binary ["foo", 'bar']
|
||||
"foobar"
|
||||
|
||||
"""
|
||||
def to_binary(thing) do
|
||||
|
||||
@@ -54,47 +54,52 @@ defmodule Binary.Inspect.Utils do
|
||||
|
||||
## escape
|
||||
|
||||
# It is considerably faster to loop the binary
|
||||
# and convert it to a list as we go compared
|
||||
# to looping the binary and creating a binary
|
||||
# as we go.
|
||||
def escape(other, char) do
|
||||
list_to_binary [char|do_escape(other, char)]
|
||||
b = do_escape(other, char, <<>>)
|
||||
<< char, b :: binary, char >>
|
||||
end
|
||||
|
||||
defp do_escape(<<char, t :: binary>>, char) do
|
||||
[?\\, char | do_escape(t, char)]
|
||||
@compile {:inline, do_escape: 3}
|
||||
defp do_escape(<<>>, _char, binary), do: binary
|
||||
defp do_escape(<< char, t :: binary >>, char, binary) do
|
||||
do_escape(t, char, << binary :: binary, ?\\, char >>)
|
||||
end
|
||||
|
||||
defp do_escape(<<h, t :: binary>>, char) when
|
||||
h == ?# or h == ?\a or
|
||||
h == ?\b or h == ?\d or
|
||||
h == ?\e or h == ?\f or
|
||||
h == ?\n or h == ?\r or
|
||||
h == ?\\ or h == ?\t or
|
||||
h == ?\v do
|
||||
[?\\, escape_map(h) | do_escape(t, char)]
|
||||
defp do_escape(<<?#, ?{, t :: binary>>, char, binary) do
|
||||
do_escape(t, char, << binary :: binary, ?\\, ?#, ?{ >>)
|
||||
end
|
||||
|
||||
defp do_escape(<<h, t :: binary>>, char) do
|
||||
[h | do_escape(t,char)]
|
||||
defp do_escape(<<?\a, t :: binary>>, char, binary) do
|
||||
do_escape(t, char, << binary :: binary, ?\\, ?a >>)
|
||||
end
|
||||
|
||||
defp do_escape(<<>>, char) do
|
||||
[char]
|
||||
defp do_escape(<<?\b, t :: binary>>, char, binary) do
|
||||
do_escape(t, char, << binary :: binary, ?\\, ?b >>)
|
||||
end
|
||||
defp do_escape(<<?\d, t :: binary>>, char, binary) do
|
||||
do_escape(t, char, << binary :: binary, ?\\, ?d >>)
|
||||
end
|
||||
defp do_escape(<<?\e, t :: binary>>, char, binary) do
|
||||
do_escape(t, char, << binary :: binary, ?\\, ?e >>)
|
||||
end
|
||||
defp do_escape(<<?\f, t :: binary>>, char, binary) do
|
||||
do_escape(t, char, << binary :: binary, ?\\, ?f >>)
|
||||
end
|
||||
defp do_escape(<<?\n, t :: binary>>, char, binary) do
|
||||
do_escape(t, char, << binary :: binary, ?\\, ?n >>)
|
||||
end
|
||||
defp do_escape(<<?\r, t :: binary>>, char, binary) do
|
||||
do_escape(t, char, << binary :: binary, ?\\, ?r >>)
|
||||
end
|
||||
defp do_escape(<<?\\, t :: binary>>, char, binary) do
|
||||
do_escape(t, char, << binary :: binary, ?\\, ?\\ >>)
|
||||
end
|
||||
defp do_escape(<<?\t, t :: binary>>, char, binary) do
|
||||
do_escape(t, char, << binary :: binary, ?\\, ?t >>)
|
||||
end
|
||||
defp do_escape(<<?\v, t :: binary>>, char, binary) do
|
||||
do_escape(t, char, << binary :: binary, ?\\, ?v >>)
|
||||
end
|
||||
defp do_escape(<<h, t :: binary>>, char, binary) do
|
||||
do_escape(t, char, << binary :: binary, h >>)
|
||||
end
|
||||
|
||||
defp escape_map(?#), do: ?#
|
||||
defp escape_map(?\a), do: ?a
|
||||
defp escape_map(?\b), do: ?b
|
||||
defp escape_map(?\d), do: ?d
|
||||
defp escape_map(?\e), do: ?e
|
||||
defp escape_map(?\f), do: ?f
|
||||
defp escape_map(?\n), do: ?n
|
||||
defp escape_map(?\r), do: ?r
|
||||
defp escape_map(?\\), do: ?\\
|
||||
defp escape_map(?\t), do: ?t
|
||||
defp escape_map(?\v), do: ?v
|
||||
end
|
||||
|
||||
defimpl Binary.Inspect, for: Atom do
|
||||
@@ -111,9 +116,12 @@ defimpl Binary.Inspect, for: Atom do
|
||||
|
||||
## Examples
|
||||
|
||||
inspect(:foo) #=> ":foo"
|
||||
inspect(nil) #=> "nil"
|
||||
inspect(Foo.Bar) #=> "Foo.Bar"
|
||||
iex> inspect(:foo)
|
||||
":foo"
|
||||
iex> inspect(nil)
|
||||
"nil"
|
||||
iex> inspect(Foo.Bar)
|
||||
"Foo.Bar"
|
||||
|
||||
"""
|
||||
|
||||
@@ -186,8 +194,10 @@ defimpl Binary.Inspect, for: BitString do
|
||||
|
||||
## Examples
|
||||
|
||||
inspect("bar") #=> "bar"
|
||||
inspect("f\"oo") #=> "f\"oo"
|
||||
iex> inspect("bar")
|
||||
"\"bar\""
|
||||
iex> inspect("f\"oo")
|
||||
"\"f\\\"oo\""
|
||||
|
||||
"""
|
||||
|
||||
@@ -251,9 +261,12 @@ defimpl Binary.Inspect, for: List do
|
||||
|
||||
## Examples
|
||||
|
||||
inspect('bar') #=> 'bar'
|
||||
inspect([0|'bar']) #=> "[0,98,97,114]"
|
||||
inspect([:foo,:bar]) #=> "[:foo, :bar]"
|
||||
iex> inspect('bar')
|
||||
"'bar'"
|
||||
iex> inspect([0|'bar'])
|
||||
"[0,98,97,114]"
|
||||
iex> inspect([:foo,:bar])
|
||||
"[:foo,:bar]"
|
||||
|
||||
"""
|
||||
|
||||
@@ -293,8 +306,10 @@ defimpl Binary.Inspect, for: Tuple do
|
||||
|
||||
## Examples
|
||||
|
||||
inspect({1,2,3}) #=> "{1,2,3}"
|
||||
inspect(ArgumentError.new) #=> ArgumentError[message: "argument error"]
|
||||
iex> inspect({1,2,3})
|
||||
"{1,2,3}"
|
||||
iex> inspect(ArgumentError.new)
|
||||
"ArgumentError[message: \\\"argument error\\\"]"
|
||||
|
||||
"""
|
||||
|
||||
@@ -372,7 +387,8 @@ defimpl Binary.Inspect, for: Number do
|
||||
|
||||
## Examples
|
||||
|
||||
inspect(1) #=> "1"
|
||||
iex> inspect(1)
|
||||
"1"
|
||||
|
||||
"""
|
||||
|
||||
@@ -391,7 +407,8 @@ defimpl Binary.Inspect, for: Regex do
|
||||
|
||||
## Examples
|
||||
|
||||
inspect(%r/foo/m) #=> "%r\"foo\"m"
|
||||
iex> inspect(%r/foo/m)
|
||||
"%r\"foo\"m"
|
||||
|
||||
"""
|
||||
|
||||
|
||||
@@ -6,15 +6,19 @@ defmodule Bitwise do
|
||||
The easiest way to use is to simply import them into
|
||||
your module:
|
||||
|
||||
use Bitwise
|
||||
iex> use Bitwise
|
||||
...> bnot 1
|
||||
-2
|
||||
|
||||
bnot 1 #=> -2
|
||||
1 &&& 1 #=> 1
|
||||
iex> use Bitwise
|
||||
...> 1 &&& 1
|
||||
1
|
||||
|
||||
You can select to include only or skip operators by passing options:
|
||||
|
||||
use Bitwise, only_operators: true
|
||||
1 &&& 1 #=> 1
|
||||
iex> use Bitwise, only_operators: true
|
||||
...> 1 &&& 1
|
||||
1
|
||||
|
||||
"""
|
||||
|
||||
|
||||
+21
-12
@@ -84,15 +84,15 @@ defmodule Code do
|
||||
|
||||
## Examples
|
||||
|
||||
Code.eval "a + b", [a: 1, b: 2], file: __ENV__.file, line: __ENV__.line
|
||||
#=> { 3, [ {:a, 1}, {:b, 2} ] }
|
||||
iex> Code.eval "a + b", [a: 1, b: 2], file: __ENV__.file, line: __ENV__.line
|
||||
{ 3, [ {:a, 1}, {:b, 2} ] }
|
||||
|
||||
For convenience, you can my pass `__ENV__` as argument and
|
||||
all imports, requires and aliases will be automatically carried
|
||||
over:
|
||||
|
||||
Code.eval "a + b", [a: 1, b: 2], __ENV__
|
||||
#=> { 3, [ {:a, 1}, {:b, 2} ] }
|
||||
iex> Code.eval "a + b", [a: 1, b: 2], __ENV__
|
||||
{ 3, [ {:a, 1}, {:b, 2} ] }
|
||||
|
||||
"""
|
||||
def eval(string, binding // [], opts // [])
|
||||
@@ -115,16 +115,16 @@ defmodule Code do
|
||||
|
||||
## Examples
|
||||
|
||||
contents = quote hygiene: false, do: a + b
|
||||
|
||||
Code.eval_quoted contents, [a: 1, b: 2], file: __ENV__.file, line: __ENV__.line
|
||||
#=> { 3, [ {:a, 1}, {:b, 2} ] }
|
||||
iex> contents = quote hygiene: [vars: false], do: a + b
|
||||
...> Code.eval_quoted contents, [a: 1, b: 2], file: __ENV__.file, line: __ENV__.line
|
||||
{ 3, [ {:a, 1}, {:b, 2} ] }
|
||||
|
||||
For convenience, you can my pass `__ENV__` as argument and
|
||||
all options will be automatically extracted from the environment:
|
||||
|
||||
Code.eval_quoted contents, [a: 1, b: 2], __ENV__
|
||||
#=> { 3, [ {:a, 1}, {:b, 2} ] }
|
||||
iex> contents = quote hygiene: [vars: false], do: a + b
|
||||
...> Code.eval_quoted contents, [a: 1, b: 2], __ENV__
|
||||
{ 3, [ {:a, 1}, {:b, 2} ] }
|
||||
|
||||
"""
|
||||
def eval_quoted(quoted, binding // [], opts // [])
|
||||
@@ -140,8 +140,8 @@ defmodule Code do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Converts the given string to AST. It returns { :ok, ast }
|
||||
if it succeeds, { :error, { line, error, token } } otherwise.
|
||||
Converts the given string to AST. It returns `{ :ok, ast }`
|
||||
if it succeeds, `{ :error, { line, error, token } }` otherwise.
|
||||
|
||||
## Options
|
||||
|
||||
@@ -270,6 +270,15 @@ defmodule Code do
|
||||
:elixir_compiler.string :unicode.characters_to_list(string), file
|
||||
end
|
||||
|
||||
@doc """
|
||||
Compiles the quoted expression and returns a list of tuples where
|
||||
the first element is the module name and the second one is its
|
||||
binary.
|
||||
"""
|
||||
def compile_quoted(quoted, file // "nofile") when is_binary(file) do
|
||||
:elixir_compiler.quoted [quoted], file
|
||||
end
|
||||
|
||||
@doc """
|
||||
Ensures the given module is loaded. If the module is already
|
||||
loaded, it works as no-op. If the module was not loaded yet,
|
||||
|
||||
+89
-49
@@ -1,5 +1,5 @@
|
||||
defmodule Dict do
|
||||
@moduledoc """
|
||||
@moduledoc %B"""
|
||||
This module specifies the Dict API expected to be
|
||||
implemented by different dictionaries. It also provides
|
||||
functions that redirect to the underlying Dict, allowing
|
||||
@@ -13,13 +13,28 @@ defmodule Dict do
|
||||
|
||||
For simplicity's sake, in the examples below everytime
|
||||
`new` is used, it implies one of the module-specific
|
||||
calls like the two above. Likewise, when the result of
|
||||
a function invocation is shown in the form `[a: 1, b: 2]`,
|
||||
it implies that the returned value is actually of the
|
||||
same dict type as the input one.
|
||||
calls like above. Likewise, when the result of a function
|
||||
invocation is shown in the form `[a: 1, b: 2]`, it implies
|
||||
that the returned value is actually of the same dict type
|
||||
as the input one.
|
||||
|
||||
## Protocols
|
||||
|
||||
Besides implementing the functions in this module, all
|
||||
dictionaries are also required to implement the `Access`
|
||||
protocol:
|
||||
|
||||
iex> dict = HashDict.new
|
||||
...> dict = Dict.put dict, :hello, :world
|
||||
...> dict[:hello]
|
||||
:world
|
||||
|
||||
And also the `Enum.Iterator` protocol, allowing one to write:
|
||||
|
||||
Enum.each dict, fn { k, v } ->
|
||||
IO.puts "#{k}: #{v}"
|
||||
end
|
||||
|
||||
Keep in mind that all dicts are also required to
|
||||
implement both `Access` and `Enum.Iterator` protocols.
|
||||
"""
|
||||
|
||||
use Behaviour
|
||||
@@ -63,8 +78,9 @@ defmodule Dict do
|
||||
|
||||
## Examples
|
||||
|
||||
d = new [a: 1, b: 2]
|
||||
Dict.keys d #=> [:a,:b]
|
||||
iex> d = HashDict.new [a: 1, b: 2]
|
||||
...> Enum.sort Dict.keys d
|
||||
[:a,:b]
|
||||
|
||||
"""
|
||||
@spec keys(t) :: [key]
|
||||
@@ -77,8 +93,9 @@ defmodule Dict do
|
||||
|
||||
## Examples
|
||||
|
||||
d = new [a: 1, b: 2]
|
||||
Dict.values d #=> [1,2]
|
||||
iex> d = HashDict.new [a: 1, b: 2]
|
||||
...> Enum.sort Dict.values d
|
||||
[1,2]
|
||||
|
||||
"""
|
||||
@spec values(t) :: [value]
|
||||
@@ -91,8 +108,9 @@ defmodule Dict do
|
||||
|
||||
## Examples
|
||||
|
||||
d = new [a: 1, b: 2]
|
||||
Dict.size d #=> 2
|
||||
iex> d = HashDict.new [a: 1, b: 2]
|
||||
...> Dict.size d
|
||||
2
|
||||
|
||||
"""
|
||||
@spec size(t) :: non_neg_integer
|
||||
@@ -105,9 +123,13 @@ defmodule Dict do
|
||||
|
||||
## Examples
|
||||
|
||||
d = new [a: 1]
|
||||
Dict.has_key?(d, :a) #=> true
|
||||
Dict.has_key?(d, :b) #=> false
|
||||
iex> d = HashDict.new [a: 1]
|
||||
...> Dict.has_key?(d, :a)
|
||||
true
|
||||
|
||||
iex> d = HashDict.new [a: 1]
|
||||
...> Dict.has_key?(d, :b)
|
||||
false
|
||||
|
||||
"""
|
||||
@spec has_key?(t, key) :: boolean
|
||||
@@ -121,11 +143,17 @@ defmodule Dict do
|
||||
|
||||
## Examples
|
||||
|
||||
d = new [a: 1]
|
||||
Dict.get d, :a #=> 1
|
||||
Dict.get d, :b #=> nil
|
||||
Dict.get d, :b, 3 #=> 3
|
||||
iex> d = HashDict.new [a: 1]
|
||||
...> Dict.get d, :a
|
||||
1
|
||||
|
||||
iex> d = HashDict.new [a: 1]
|
||||
...> Dict.get d, :b
|
||||
nil
|
||||
|
||||
iex> d = HashDict.new [a: 1]
|
||||
...> Dict.get d, :b, 3
|
||||
3
|
||||
"""
|
||||
@spec get(t, key, value) :: value
|
||||
def get(dict, key, default // nil) do
|
||||
@@ -138,9 +166,12 @@ defmodule Dict do
|
||||
|
||||
## Examples
|
||||
|
||||
d = new [a: 1]
|
||||
Dict.get d, :a #=> 1
|
||||
Dict.get d, :b #=> raises KeyError[key: :b]
|
||||
iex> d = HashDict.new [a: 1]
|
||||
...> Dict.get d, :a
|
||||
1
|
||||
iex> d = HashDict.new [a: 1]
|
||||
...> Dict.get! d, :b
|
||||
** (KeyError) key not found: :b
|
||||
|
||||
"""
|
||||
@spec get!(t, key) :: value | no_return
|
||||
@@ -154,9 +185,10 @@ defmodule Dict do
|
||||
|
||||
## Examples
|
||||
|
||||
d = new [a: 1, b: 2]
|
||||
Dict.put d, :a, 3
|
||||
#=> [a: 3, b: 2]
|
||||
iex> d = HashDict.new [a: 1, b: 2]
|
||||
...> d = Dict.put d, :a, 3
|
||||
...> Dict.get d, :a
|
||||
3
|
||||
|
||||
"""
|
||||
@spec put(t, key, value) :: t
|
||||
@@ -169,9 +201,10 @@ defmodule Dict do
|
||||
|
||||
## Examples
|
||||
|
||||
d = new [a: 1, b: 2]
|
||||
Dict.put_new d, :a, 3
|
||||
#=> [a: 1, b: 2]
|
||||
iex> d = HashDict.new [a: 1, b: 2]
|
||||
...> d = Dict.put_new d, :a, 3
|
||||
...> Dict.get d, :a
|
||||
1
|
||||
|
||||
"""
|
||||
@spec put_new(t, key, value) :: t
|
||||
@@ -185,11 +218,14 @@ defmodule Dict do
|
||||
|
||||
## Examples
|
||||
|
||||
d = new [a: 1, b: 2]
|
||||
Dict.delete d, :a #=> [b: 2]
|
||||
iex> d = HashDict.new [a: 1, b: 2]
|
||||
...> d = Dict.delete d, :a
|
||||
...> Dict.get d, :a
|
||||
nil
|
||||
|
||||
d = new [b: 2]
|
||||
Dict.delete d, :a #=> [b: 2]
|
||||
iex> d = HashDict.new [b: 2]
|
||||
...> Dict.delete(d, :a) == d
|
||||
true
|
||||
|
||||
"""
|
||||
@spec delete(t, key) :: t
|
||||
@@ -203,10 +239,11 @@ defmodule Dict do
|
||||
|
||||
## Examples
|
||||
|
||||
d1 = new [a: 1, b: 2]
|
||||
d2 = new [a: 3, d: 4]
|
||||
Dict.merge d1, d2
|
||||
#=> [a: 3, b: 2, d: 4]
|
||||
iex> d1 = HashDict.new [a: 1, b: 2]
|
||||
...> d2 = HashDict.new [a: 3, d: 4]
|
||||
...> d = Dict.merge d1, d2
|
||||
...> [a: Dict.get(d, :a), b: Dict.get(d, :b), d: Dict.get(d, :d)]
|
||||
[a: 3, b: 2, d: 4]
|
||||
|
||||
"""
|
||||
@spec merge(t, t) :: t
|
||||
@@ -221,12 +258,13 @@ defmodule Dict do
|
||||
|
||||
## Examples
|
||||
|
||||
d1 = new [a: 1, b: 2]
|
||||
d2 = new [a: 3, d: 4]
|
||||
Dict.merge d1, d2, fn _k, v1, v2 ->
|
||||
v1 + v2
|
||||
end
|
||||
#=> [a: 4, b: 2, d: 4]
|
||||
iex> d1 = HashDict.new [a: 1, b: 2]
|
||||
...> d2 = HashDict.new [a: 3, d: 4]
|
||||
...> d = Dict.merge d1, d2, fn _k, v1, v2 ->
|
||||
...> v1 + v2
|
||||
...> end
|
||||
...> [a: Dict.get(d, :a), b: Dict.get(d, :b), d: Dict.get(d, :d)]
|
||||
[a: 4, b: 2, d: 4]
|
||||
|
||||
"""
|
||||
@spec merge(t, t, (key, value, value -> value)) :: t
|
||||
@@ -240,9 +278,10 @@ defmodule Dict do
|
||||
|
||||
## Examples
|
||||
|
||||
d = new [a: 1, b: 2]
|
||||
Dict.update d, :a, fn val -> -val end
|
||||
#=> [a: -1, b: 2]
|
||||
iex> d = HashDict.new [a: 1, b: 2]
|
||||
...> d = Dict.update d, :a, fn val -> -val end
|
||||
...> Dict.get d, :a
|
||||
-1
|
||||
|
||||
"""
|
||||
@spec update(t, key, (value -> value)) :: t
|
||||
@@ -257,9 +296,10 @@ defmodule Dict do
|
||||
|
||||
## Examples
|
||||
|
||||
d = new [a: 1, b: 2]
|
||||
Dict.update d, :c, 3, fn val -> -val end
|
||||
#=> [a: 1, b: 2, c: 3]
|
||||
iex> d = HashDict.new [a: 1, b: 2]
|
||||
...> d = Dict.update d, :c, 3, fn val -> -val end
|
||||
...> Dict.get d, :c
|
||||
3
|
||||
|
||||
"""
|
||||
@spec update(t, key, value, (value -> value)) :: t
|
||||
|
||||
+136
-104
@@ -1,53 +1,55 @@
|
||||
defprotocol Enum.Iterator do
|
||||
@moduledoc """
|
||||
This is the protocol used by the `Enum` module.
|
||||
Usually, when you invoke a function in the module `Enum`,
|
||||
the first argument passed to `Enum` is a collection which
|
||||
is forwarded to this protocol in order to retrieve information
|
||||
on how to iterate the collection. That said, when:
|
||||
|
||||
Usually, when you invoke a function in the module `Enum`, the first argument
|
||||
passed to it is a collection which is forwarded to this protocol in order to
|
||||
retrieve information on how to iterate the collection.
|
||||
|
||||
For example, in the expression
|
||||
|
||||
Enum.map [1,2,3], &1 * 2
|
||||
|
||||
Is invoked, it invokes `Enum.Iterator.iterator([1,2,3])`
|
||||
which returns all the information required by Enum.
|
||||
Read each function documentation below for more information.
|
||||
`Enum.map` invokes `Enum.Iterator.iterator([1,2,3])` to retrieve the iterator
|
||||
function that will drive the iteration process.
|
||||
"""
|
||||
|
||||
@only [List, Record, Function]
|
||||
|
||||
@doc """
|
||||
Iteration in Elixir happens with the help of a iterator
|
||||
function. Every time this function is called, it must
|
||||
return a tuple with two elements. The first element
|
||||
is the next item and the second can be any Elixir term
|
||||
which the function is going to receive as argument the
|
||||
next time it is invoked.
|
||||
This function must return a tuple of the form `{ iter, step }` where
|
||||
`iter` is a function that yields successive values from the collection
|
||||
each time it is invoked and `step` is the first step of iteration.
|
||||
|
||||
When there are no more items to be iterated, the function
|
||||
must return the atom `:stop`.
|
||||
Iteration in Elixir happens with the help of an _iterator function_ (named
|
||||
`iter` in the paragraph above). When it is invoked, it must return a tuple
|
||||
with two elements. The first element is the next successive value from the
|
||||
collection and the second element can be any Elixir term which `iter` is
|
||||
going to receive as its argument the next time it is invoked.
|
||||
|
||||
In order to retrieve this iterator function, Elixir invokes
|
||||
`Enum.Iterator.iterator(collection)` which should return a
|
||||
tuple with two elements: the first element is the iterator
|
||||
function and the second is the first step of iteration.
|
||||
When there are no more items left to yield, `iter` must return the atom
|
||||
`:stop`.
|
||||
|
||||
As an example, here is the implementation of iterator for lists:
|
||||
As an example, here is the implementation of `iterator` for lists:
|
||||
|
||||
def iterator(list), do: { iterate(&1), iterate(list) }
|
||||
defp iterate([h|t]), do: { h, t }
|
||||
defp iterate([]), do: :stop
|
||||
|
||||
Here, `iterate` is the _iterator function_ and `{ h, t }` is a step of
|
||||
iteration.
|
||||
|
||||
## Iterating lists
|
||||
|
||||
If a data structure needs to be converted to a list in order
|
||||
to be iterated, the iterator function can simply return the
|
||||
list and the Enum module will be able to take over the list
|
||||
and retrieve the proper iterator function.
|
||||
As a special case, if a data structure needs to be converted to a list in
|
||||
order to be iterated, `iterator` can simply return the list and the `Enum`
|
||||
module will be able to take over the list and produce a proper iterator
|
||||
function for it.
|
||||
"""
|
||||
def iterator(collection)
|
||||
|
||||
@doc """
|
||||
The function used to retrieve the collection size.
|
||||
The function used to retrieve the collection's size.
|
||||
"""
|
||||
def count(collection)
|
||||
end
|
||||
@@ -79,17 +81,19 @@ defmodule Enum do
|
||||
|
||||
## Examples
|
||||
|
||||
Enum.all? [2,4,6], fn(x) -> rem(x, 2) == 0 end
|
||||
#=> true
|
||||
iex> Enum.all? [2,4,6], fn(x) -> rem(x, 2) == 0 end
|
||||
true
|
||||
|
||||
Enum.all? [2,3,4], fn(x) -> rem(x, 2) == 0 end
|
||||
#=> false
|
||||
iex> Enum.all? [2,3,4], fn(x) -> rem(x, 2) == 0 end
|
||||
false
|
||||
|
||||
If no function is given, it defaults to checking if
|
||||
all items in the collection evaluate to true.
|
||||
|
||||
Enum.all? [1,2,3] #=> true
|
||||
Enum.all? [1,nil,3] #=> false
|
||||
iex> Enum.all? [1,2,3]
|
||||
true
|
||||
iex> Enum.all? [1,nil,3]
|
||||
false
|
||||
|
||||
"""
|
||||
@spec all?(t) :: boolean
|
||||
@@ -116,17 +120,19 @@ defmodule Enum do
|
||||
|
||||
## Examples
|
||||
|
||||
Enum.any? [2,4,6], fn(x) -> rem(x, 2) == 1 end
|
||||
#=> false
|
||||
iex> Enum.any? [2,4,6], fn(x) -> rem(x, 2) == 1 end
|
||||
false
|
||||
|
||||
Enum.any? [2,3,4], fn(x) -> rem(x, 2) == 1 end
|
||||
#=> true
|
||||
iex> Enum.any? [2,3,4], fn(x) -> rem(x, 2) == 1 end
|
||||
true
|
||||
|
||||
If no function is given, it defaults to checking if
|
||||
at least one item in the collection evaluates to true.
|
||||
|
||||
Enum.any? [false,false,false] #=> false
|
||||
Enum.any? [false,true,false] #=> true
|
||||
iex> Enum.any? [false,false,false]
|
||||
false
|
||||
iex> Enum.any? [false,true,false]
|
||||
true
|
||||
|
||||
"""
|
||||
@spec any?(t) :: boolean
|
||||
@@ -156,9 +162,12 @@ defmodule Enum do
|
||||
|
||||
## Examples
|
||||
|
||||
Enum.at! [2,4,6], 0 #=> 2
|
||||
Enum.at! [2,4,6], 2 #=> 6
|
||||
Enum.at! [2,4,6], 4 #=> raises Enum.OutOfBoundsError
|
||||
iex> Enum.at! [2,4,6], 0
|
||||
2
|
||||
iex> Enum.at! [2,4,6], 2
|
||||
6
|
||||
iex> Enum.at! [2,4,6], 4
|
||||
** (Enum.OutOfBoundsError) out of bounds error
|
||||
|
||||
"""
|
||||
@spec at!(t, index) :: element | no_return
|
||||
@@ -180,7 +189,8 @@ defmodule Enum do
|
||||
|
||||
## Examples
|
||||
|
||||
Enum.count [1,2,3] #=> 3
|
||||
iex> Enum.count [1,2,3]
|
||||
3
|
||||
|
||||
"""
|
||||
@spec count(t) :: non_neg_integer
|
||||
@@ -211,9 +221,12 @@ defmodule Enum do
|
||||
|
||||
## Examples
|
||||
|
||||
Enum.drop [1,2,3], 2 #=> [3]
|
||||
Enum.drop [1,2,3], 10 #=> []
|
||||
Enum.drop [1,2,3], 0 #=> [1,2,3]
|
||||
iex> Enum.drop [1,2,3], 2
|
||||
[3]
|
||||
iex> Enum.drop [1,2,3], 10
|
||||
[]
|
||||
iex> Enum.drop [1,2,3], 0
|
||||
[1,2,3]
|
||||
|
||||
"""
|
||||
@spec drop(t, integer) :: list
|
||||
@@ -241,8 +254,8 @@ defmodule Enum do
|
||||
|
||||
## Examples
|
||||
|
||||
Enum.drop_while [1,2,3,4,5], fn(x) -> x < 3 end
|
||||
#=> [3,4,5]
|
||||
iex> Enum.drop_while [1,2,3,4,5], fn(x) -> x < 3 end
|
||||
[3,4,5]
|
||||
"""
|
||||
@spec drop_while(t, (element -> as_boolean(term))) :: list
|
||||
def drop_while(collection, fun) when is_list(collection) do
|
||||
@@ -297,8 +310,10 @@ defmodule Enum do
|
||||
|
||||
## Examples
|
||||
|
||||
Enum.empty? [] #=> true
|
||||
Enum.empty? [1,2,3] #=> false
|
||||
iex> Enum.empty? []
|
||||
true
|
||||
iex> Enum.empty? [1,2,3]
|
||||
false
|
||||
|
||||
"""
|
||||
@spec empty?(t) :: boolean
|
||||
@@ -319,8 +334,8 @@ defmodule Enum do
|
||||
|
||||
## Examples
|
||||
|
||||
Enum.filter [1, 2, 3], fn(x) -> rem(x, 2) == 0 end
|
||||
#=> [2]
|
||||
iex> Enum.filter [1, 2, 3], fn(x) -> rem(x, 2) == 0 end
|
||||
[2]
|
||||
|
||||
"""
|
||||
@spec filter(t, (element -> as_boolean(term))) :: list
|
||||
@@ -342,8 +357,8 @@ defmodule Enum do
|
||||
|
||||
## Examples
|
||||
|
||||
Enum.filter_map [1, 2, 3], fn(x) -> rem(x, 2) == 0 end, &1 * 2
|
||||
#=> [4]
|
||||
iex> Enum.filter_map [1, 2, 3], fn(x) -> rem(x, 2) == 0 end, &1 * 2
|
||||
[4]
|
||||
|
||||
"""
|
||||
@spec filter_map(t, (element -> as_boolean(term)), (element -> element)) :: list
|
||||
@@ -366,14 +381,14 @@ defmodule Enum do
|
||||
|
||||
## Examples
|
||||
|
||||
Enum.find [2,4,6], fn(x) -> rem(x, 2) == 1 end
|
||||
#=> nil
|
||||
iex> Enum.find [2,4,6], fn(x) -> rem(x, 2) == 1 end
|
||||
nil
|
||||
|
||||
Enum.find [2,4,6], 0, fn(x) -> rem(x, 2) == 1 end
|
||||
#=> 0
|
||||
iex> Enum.find [2,4,6], 0, fn(x) -> rem(x, 2) == 1 end
|
||||
0
|
||||
|
||||
Enum.find [2,3,4], fn(x) -> rem(x, 2) == 1 end
|
||||
#=> 3
|
||||
iex> Enum.find [2,3,4], fn(x) -> rem(x, 2) == 1 end
|
||||
3
|
||||
|
||||
"""
|
||||
@spec find(t, (element -> any)) :: element | :nil
|
||||
@@ -400,11 +415,11 @@ defmodule Enum do
|
||||
|
||||
## Examples
|
||||
|
||||
Enum.find_value [2,4,6], fn(x) -> rem(x, 2) == 1 end
|
||||
#=> nil
|
||||
iex> Enum.find_value [2,4,6], fn(x) -> rem(x, 2) == 1 end
|
||||
nil
|
||||
|
||||
Enum.find_value [2,3,4], fn(x) -> rem(x, 2) == 1 end
|
||||
#=> true
|
||||
iex> Enum.find_value [2,3,4], fn(x) -> rem(x, 2) == 1 end
|
||||
true
|
||||
|
||||
"""
|
||||
@spec find_value(t, (element -> any)) :: any | :nil
|
||||
@@ -433,11 +448,11 @@ defmodule Enum do
|
||||
|
||||
## Examples
|
||||
|
||||
Enum.find_index [2,4,6], fn(x) -> rem(x, 2) == 1 end
|
||||
#=> nil
|
||||
iex> Enum.find_index [2,4,6], fn(x) -> rem(x, 2) == 1 end
|
||||
nil
|
||||
|
||||
Enum.find_index [2,3,4], fn(x) -> rem(x, 2) == 1 end
|
||||
#=> 2
|
||||
iex> Enum.find_index [2,3,4], fn(x) -> rem(x, 2) == 1 end
|
||||
1
|
||||
|
||||
"""
|
||||
@spec find_index(t, (element -> any)) :: index | :nil
|
||||
@@ -459,8 +474,10 @@ defmodule Enum do
|
||||
|
||||
## Examples
|
||||
|
||||
Enum.first [] #=> nil
|
||||
Enum.first [1,2,3] #=> 1
|
||||
iex> Enum.first []
|
||||
nil
|
||||
iex> Enum.first [1,2,3]
|
||||
1
|
||||
|
||||
"""
|
||||
@spec first(t) :: :nil | element
|
||||
@@ -487,9 +504,12 @@ defmodule Enum do
|
||||
|
||||
## Examples
|
||||
|
||||
Enum.join([1,2,3]) #=> "123"
|
||||
Enum.join([1,2,3], " = ") #=> "1 = 2 = 3"
|
||||
Enum.join([1,2,3], ' = ') #=> '1 = 2 = 3'
|
||||
iex> Enum.join([1,2,3])
|
||||
"123"
|
||||
iex> Enum.join([1,2,3], " = ")
|
||||
"1 = 2 = 3"
|
||||
iex> Enum.join([1,2,3], ' = ')
|
||||
'1 = 2 = 3'
|
||||
|
||||
"""
|
||||
@spec join(t) :: String.t
|
||||
@@ -523,11 +543,11 @@ defmodule Enum do
|
||||
|
||||
## Examples
|
||||
|
||||
Enum.map [1, 2, 3], fn(x) -> x * 2 end
|
||||
#=> [2, 4, 6]
|
||||
iex> Enum.map [1, 2, 3], fn(x) -> x * 2 end
|
||||
[2, 4, 6]
|
||||
|
||||
Enum.map [a: 1, b: 2], fn({k, v}) -> { k, -v } end
|
||||
#=> [a: -1, b: -2]
|
||||
iex> Enum.map [a: 1, b: 2], fn({k, v}) -> { k, -v } end
|
||||
[a: -1, b: -2]
|
||||
|
||||
"""
|
||||
@spec map(t, (element -> any) | (element, index -> any)) :: list
|
||||
@@ -562,9 +582,12 @@ defmodule Enum do
|
||||
|
||||
## Examples
|
||||
|
||||
Enum.map_join([1,2,3], &1 * 2) #=> "246"
|
||||
Enum.map_join([1,2,3], " = ", &1 * 2) #=> "2 = 4 = 6"
|
||||
Enum.map_join([1,2,3], ' = ', &1 * 2) #=> '2 = 4 = 6'
|
||||
iex> Enum.map_join([1,2,3], &1 * 2)
|
||||
"246"
|
||||
iex> Enum.map_join([1,2,3], " = ", &1 * 2)
|
||||
"2 = 4 = 6"
|
||||
iex> Enum.map_join([1,2,3], ' = ', &1 * 2)
|
||||
'2 = 4 = 6'
|
||||
|
||||
"""
|
||||
@spec map_join(t, (element -> any)) :: String.t
|
||||
@@ -599,8 +622,8 @@ defmodule Enum do
|
||||
|
||||
## Examples
|
||||
|
||||
Enum.map_reduce [1, 2, 3], 0, fn(x, acc) -> { x * 2, x + acc } end
|
||||
#=> { [2, 4, 6], 6 }
|
||||
iex> Enum.map_reduce [1, 2, 3], 0, fn(x, acc) -> { x * 2, x + acc } end
|
||||
{ [2, 4, 6], 6 }
|
||||
|
||||
"""
|
||||
@spec map_reduce(t, any, (element, any -> any)) :: any
|
||||
@@ -624,8 +647,8 @@ defmodule Enum do
|
||||
|
||||
## Examples
|
||||
|
||||
Enum.partition [1, 2, 3], fn(x) -> rem(x, 2) == 0 end
|
||||
#=> { [2], [1,3] }
|
||||
iex> Enum.partition [1, 2, 3], fn(x) -> rem(x, 2) == 0 end
|
||||
{ [2], [1,3] }
|
||||
|
||||
"""
|
||||
@spec partition(t, (element -> any)) :: {list, list}
|
||||
@@ -649,8 +672,8 @@ defmodule Enum do
|
||||
|
||||
## Examples
|
||||
|
||||
Enum.reduce [1, 2, 3], 0, fn(x, acc) -> x + acc end
|
||||
#=> 6
|
||||
iex> Enum.reduce [1, 2, 3], 0, fn(x, acc) -> x + acc end
|
||||
6
|
||||
|
||||
"""
|
||||
@spec reduce(t, any, (element, any -> any)) :: any
|
||||
@@ -672,8 +695,8 @@ defmodule Enum do
|
||||
|
||||
## Examples
|
||||
|
||||
Enum.reverse [1, 2, 3]
|
||||
#=> [3, 2, 1]
|
||||
iex> Enum.reverse [1, 2, 3]
|
||||
[3, 2, 1]
|
||||
|
||||
"""
|
||||
@spec reverse(t) :: list
|
||||
@@ -693,7 +716,8 @@ defmodule Enum do
|
||||
|
||||
## Examples
|
||||
|
||||
Enum.sort [3,2,1] #=> [1,2,3]
|
||||
iex> Enum.sort [3,2,1]
|
||||
[1,2,3]
|
||||
|
||||
"""
|
||||
@spec sort(t) :: list
|
||||
@@ -715,7 +739,8 @@ defmodule Enum do
|
||||
|
||||
## Examples
|
||||
|
||||
Enum.sort [3,2,1], &1 > &2 #=> [1,2,3]
|
||||
iex> Enum.sort [1,2,3], &1 > &2
|
||||
[3,2,1]
|
||||
|
||||
"""
|
||||
@spec sort(t, (element, element -> boolean)) :: list
|
||||
@@ -744,11 +769,16 @@ defmodule Enum do
|
||||
|
||||
## Examples
|
||||
|
||||
Enum.split [1,2,3], 2 #=> { [1,2], [3] }
|
||||
Enum.split [1,2,3], 10 #=> { [1,2,3], [] }
|
||||
Enum.split [1,2,3], 0 #=> { [], [1,2,3] }
|
||||
Enum.split [1,2,3], -1 #=> { [1,2], [3] }
|
||||
Enum.split [1,2,3], -5 #=> { [], [1,2,3] }
|
||||
iex> Enum.split [1,2,3], 2
|
||||
{ [1,2], [3] }
|
||||
iex> Enum.split [1,2,3], 10
|
||||
{ [1,2,3], [] }
|
||||
iex> Enum.split [1,2,3], 0
|
||||
{ [], [1,2,3] }
|
||||
iex> Enum.split [1,2,3], -1
|
||||
{ [1,2], [3] }
|
||||
iex> Enum.split [1,2,3], -5
|
||||
{ [], [1,2,3] }
|
||||
|
||||
"""
|
||||
@spec split(t, integer) :: {list, list}
|
||||
@@ -771,13 +801,12 @@ defmodule Enum do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Splits `collection` at the first element, for which `fun` returns true.
|
||||
Expects an ordered collection.
|
||||
Splits `collection` in two while `fun` returns true.
|
||||
|
||||
## Examples
|
||||
|
||||
Enum.split_while [1,2,3,4], fn x -> x == 2 end
|
||||
#=> { [1], [2, 3, 4] }
|
||||
iex> Enum.split_while [1,2,3,4], fn(x) -> x < 3 end
|
||||
{ [1, 2], [3, 4] }
|
||||
"""
|
||||
@spec split_while(t, (element -> as_boolean(term))) :: {list, list}
|
||||
def split_while(collection, fun) when is_list(collection) do
|
||||
@@ -799,9 +828,12 @@ defmodule Enum do
|
||||
|
||||
## Examples
|
||||
|
||||
Enum.take [1,2,3], 2 #=> [1,2]
|
||||
Enum.take [1,2,3], 10 #=> [1,2,3]
|
||||
Enum.take [1,2,3], 0 #=> []
|
||||
iex> Enum.take [1,2,3], 2
|
||||
[1,2]
|
||||
iex> Enum.take [1,2,3], 10
|
||||
[1,2,3]
|
||||
iex> Enum.take [1,2,3], 0
|
||||
[]
|
||||
|
||||
"""
|
||||
@spec take(t, integer) :: list
|
||||
@@ -834,8 +866,8 @@ defmodule Enum do
|
||||
|
||||
## Examples
|
||||
|
||||
Enum.take_while [1,2,3], fn(x) -> x < 3 end
|
||||
#=> [1, 2]
|
||||
iex> Enum.take_while [1,2,3], fn(x) -> x < 3 end
|
||||
[1, 2]
|
||||
|
||||
"""
|
||||
@spec take_while(t, (element -> as_boolean(term))) :: list
|
||||
@@ -857,8 +889,8 @@ defmodule Enum do
|
||||
|
||||
## Examples
|
||||
|
||||
Enum.uniq [1,2,3,2,1]
|
||||
#=> [1, 2, 3]
|
||||
iex> Enum.uniq [1,2,3,2,1]
|
||||
[1, 2, 3]
|
||||
|
||||
"""
|
||||
@spec uniq(t) :: list
|
||||
|
||||
+29
-3
@@ -271,7 +271,7 @@ defmodule File do
|
||||
path. Returns `:ok` or `{ :error, reason }`.
|
||||
"""
|
||||
def write_stat(path, File.Stat[] = stat, opts // []) do
|
||||
F.write_file_info(path, setelem(stat, 0, :file_info), opts)
|
||||
F.write_file_info(path, set_elem(stat, 0, :file_info), opts)
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -795,7 +795,7 @@ defmodule File do
|
||||
|
||||
## Examples
|
||||
|
||||
File.open!("file.txt", [:read, :write], fn(file) ->
|
||||
File.open("file.txt", [:read, :write], fn(file) ->
|
||||
IO.readline(file)
|
||||
end)
|
||||
|
||||
@@ -897,6 +897,31 @@ defmodule File do
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns list of files in the given directory.
|
||||
|
||||
It returns `{ :ok, [files] }` in case of success,
|
||||
`{ :error, reason }` otherwise.
|
||||
"""
|
||||
def ls(path // ".") do
|
||||
case F.list_dir(path) do
|
||||
{ :ok, file_list } -> { :ok, Enum.map file_list, :unicode.characters_to_binary(&1) }
|
||||
{ :error, _ } = error -> error
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
The same as `ls/1` but raises `File.Error`
|
||||
in case of an error.
|
||||
"""
|
||||
def ls!(dir // ".") do
|
||||
case ls(dir) do
|
||||
{ :ok, value } -> value
|
||||
{ :error, reason } ->
|
||||
raise File.Error, reason: reason, action: "list directory", path: :unicode.characters_to_binary(dir)
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Closes the file referenced by `io_device`. It mostly returns `:ok`, except
|
||||
for some severe errors such as out of memory.
|
||||
@@ -928,7 +953,8 @@ defmodule File do
|
||||
quotes per single quotes and write each line to a target file
|
||||
is shown below:
|
||||
|
||||
source = File.iterator("README.md")
|
||||
{ :ok, device } = File.open("README.md")
|
||||
source = File.iterator(device)
|
||||
File.open "NEWREADME.md", [:write], fn(target) ->
|
||||
Enum.each source, fn(line) ->
|
||||
IO.write target, Regex.replace(%r/"/, line, "'")
|
||||
|
||||
@@ -0,0 +1,90 @@
|
||||
defmodule GenEvent.Behaviour do
|
||||
@moduledoc """
|
||||
This module is a convenience to define GenEvent callbacks in Elixir.
|
||||
|
||||
GenEvent is an OTP behaviour that encapsulates event handling functionality.
|
||||
|
||||
## Example
|
||||
|
||||
Bellow follows 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
|
||||
|
||||
:gen_event.add_handler(pid, MyEventHandler, [])
|
||||
|
||||
:gen_event.notify(pid, {:notification, 1})
|
||||
:gen_event.notify(pid, {:notification, 2})
|
||||
|
||||
: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 gen_event 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
|
||||
"""
|
||||
|
||||
@doc false
|
||||
defmacro __using__(_) do
|
||||
quote location: :keep do
|
||||
@behavior :gen_event
|
||||
|
||||
def init(args) do
|
||||
{ :ok, args }
|
||||
end
|
||||
|
||||
def handle_event(_event, state) do
|
||||
{ :ok, state }
|
||||
end
|
||||
|
||||
def handle_call(_request, state) do
|
||||
{ :ok, :ok, state }
|
||||
end
|
||||
|
||||
def handle_info(_msg, state) do
|
||||
{ :ok, state }
|
||||
end
|
||||
|
||||
def terminate(reason, state) do
|
||||
:ok
|
||||
end
|
||||
|
||||
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
|
||||
end
|
||||
+37
-35
@@ -33,27 +33,27 @@ defmodule HashDict do
|
||||
# set of buckets
|
||||
|
||||
# The ordered record contains a single bucket
|
||||
@ordered_threshold 8
|
||||
ordered_threshold = 8
|
||||
|
||||
defrecordp :ordered,
|
||||
size: 0,
|
||||
bucket: []
|
||||
|
||||
# The bucketed record contains a series of buckets.
|
||||
@expand_load 5
|
||||
@contract_load 2
|
||||
@node_bitmap 0b111
|
||||
@node_shift 3
|
||||
@node_size 8
|
||||
@node_template :erlang.make_tuple(@node_size, [])
|
||||
expand_load = 5
|
||||
contract_load = 2
|
||||
node_bitmap = 0b111
|
||||
node_shift = 3
|
||||
node_size = 8
|
||||
node_template = :erlang.make_tuple(node_size, [])
|
||||
node_escaped = Macro.escape(node_template)
|
||||
|
||||
defrecordp :trie,
|
||||
size: 0,
|
||||
depth: 0,
|
||||
expand_on: @node_size * @expand_load,
|
||||
contract_on: @contract_load,
|
||||
root: @node_template
|
||||
|
||||
expand_on: node_size * expand_load,
|
||||
contract_on: contract_load,
|
||||
root: node_template
|
||||
|
||||
import Bitwise
|
||||
|
||||
@@ -74,7 +74,7 @@ defmodule HashDict do
|
||||
## Examples
|
||||
|
||||
HashDict.new [{:b,1},{:a,2}]
|
||||
#=> HashDict[a: 1, b: 2]
|
||||
#=> #HashDict<[a: 2, b: 1]>
|
||||
|
||||
"""
|
||||
@spec new(list({key :: term, value :: term})) :: Dict.t
|
||||
@@ -91,7 +91,7 @@ defmodule HashDict do
|
||||
## Examples
|
||||
|
||||
HashDict.new ["a", "b"], fn x -> {x, x} end
|
||||
#=> HashDict[{ "a", "a" }, { "b", "b" }]
|
||||
#=> #HashDict<[{"a","a"},{"b","b"}]>
|
||||
|
||||
"""
|
||||
@spec new(list, (term -> {key :: term, value ::term})) :: Dict.t
|
||||
@@ -194,8 +194,8 @@ defmodule HashDict do
|
||||
root: root,
|
||||
size: size - 1,
|
||||
depth: depth - 1,
|
||||
contract_on: div(size, @node_size),
|
||||
expand_on: div(trie(dict, :expand_on), @node_size))
|
||||
contract_on: div(size, unquote(node_size)),
|
||||
expand_on: div(trie(dict, :expand_on), unquote(node_size)))
|
||||
else
|
||||
trie(dict, size: size - 1, root: root)
|
||||
end
|
||||
@@ -279,12 +279,12 @@ defmodule HashDict do
|
||||
end
|
||||
|
||||
defp dict_fold(trie(root: root, depth: depth), acc, fun) do
|
||||
node_fold(root, depth, acc, fun, @node_size)
|
||||
node_fold(root, depth, acc, fun, unquote(node_size))
|
||||
end
|
||||
|
||||
defp dict_put(ordered(size: @ordered_threshold, bucket: bucket), key, value) do
|
||||
defp dict_put(ordered(size: unquote(ordered_threshold), bucket: bucket), key, value) do
|
||||
root = node_relocate(bucket, 0)
|
||||
dict_put(trie(size: @ordered_threshold, root: root), key, value)
|
||||
dict_put(trie(size: unquote(ordered_threshold), root: root), key, value)
|
||||
end
|
||||
|
||||
defp dict_put(ordered(size: size, bucket: bucket) = dict, key, value) do
|
||||
@@ -295,7 +295,7 @@ defmodule HashDict do
|
||||
defp dict_put(trie(root: root, depth: depth, size: size, expand_on: size, contract_on: contract_on) = dict, key, value) do
|
||||
root = node_expand(root, depth, depth + 1)
|
||||
dict = trie(dict, root: root, depth: depth + 1,
|
||||
expand_on: size * @node_size, contract_on: contract_on * @node_size)
|
||||
expand_on: size * unquote(node_size), contract_on: contract_on * unquote(node_size))
|
||||
dict_put(dict, key, value)
|
||||
end
|
||||
|
||||
@@ -372,15 +372,15 @@ defmodule HashDict do
|
||||
end
|
||||
|
||||
defp bucket_index(hash) do
|
||||
hash &&& @node_bitmap
|
||||
hash &&& unquote(node_bitmap)
|
||||
end
|
||||
|
||||
defp bucket_nth_index(hash, n) do
|
||||
(hash >>> (@node_shift * n)) &&& @node_bitmap
|
||||
(hash >>> (unquote(node_shift) * n)) &&& unquote(node_bitmap)
|
||||
end
|
||||
|
||||
defp bucket_next(hash) do
|
||||
hash >>> @node_shift
|
||||
hash >>> unquote(node_shift)
|
||||
end
|
||||
|
||||
## Node helpers
|
||||
@@ -399,13 +399,13 @@ defmodule HashDict do
|
||||
defp node_put(node, 0, hash, key, value) do
|
||||
pos = bucket_index(hash)
|
||||
{ new, count } = bucket_put(elem(node, pos), key, value)
|
||||
{ setelem(node, pos, new), count }
|
||||
{ set_elem(node, pos, new), count }
|
||||
end
|
||||
|
||||
defp node_put(node, depth, hash, key, value) do
|
||||
pos = bucket_index(hash)
|
||||
{ new, count } = node_put(elem(node, pos), depth - 1, bucket_next(hash), key, value)
|
||||
{ setelem(node, pos, new), count }
|
||||
{ set_elem(node, pos, new), count }
|
||||
end
|
||||
|
||||
# Deletes a key from the bucket
|
||||
@@ -413,7 +413,7 @@ defmodule HashDict do
|
||||
pos = bucket_index(hash)
|
||||
case bucket_delete(elem(node, pos), key) do
|
||||
{ _, 0 } -> { node, 0 }
|
||||
{ new, -1 } -> { setelem(node, pos, new), -1 }
|
||||
{ new, -1 } -> { set_elem(node, pos, new), -1 }
|
||||
end
|
||||
end
|
||||
|
||||
@@ -421,7 +421,7 @@ defmodule HashDict do
|
||||
pos = bucket_index(hash)
|
||||
case node_delete(elem(node, pos), depth - 1, bucket_next(hash), key) do
|
||||
{ _, 0 } -> { node, 0 }
|
||||
{ new, -1 } -> { setelem(node, pos, new), -1 }
|
||||
{ new, -1 } -> { set_elem(node, pos, new), -1 }
|
||||
end
|
||||
end
|
||||
|
||||
@@ -431,7 +431,7 @@ defmodule HashDict do
|
||||
end
|
||||
|
||||
defp node_fold(node, depth, acc, fun, count) when count >= 1 do
|
||||
acc = node_fold(:erlang.element(count, node), depth - 1, acc, fun, @node_size)
|
||||
acc = node_fold(:erlang.element(count, node), depth - 1, acc, fun, unquote(node_size))
|
||||
node_fold(node, depth, acc, fun, count - 1)
|
||||
end
|
||||
|
||||
@@ -461,21 +461,23 @@ defmodule HashDict do
|
||||
end
|
||||
|
||||
defp node_contract({ b1, b2, b3, b4, b5, b6, b7, b8 }, 1, n) do
|
||||
@node_template |> each_contract(b1, n) |> each_contract(b2, n) |> each_contract(b3, n)
|
||||
|> each_contract(b4, n) |> each_contract(b5, n) |> each_contract(b6, n)
|
||||
|> each_contract(b7, n) |> each_contract(b8, n)
|
||||
unquote(node_escaped)
|
||||
|> each_contract(b1, n) |> each_contract(b2, n) |> each_contract(b3, n)
|
||||
|> each_contract(b4, n) |> each_contract(b5, n) |> each_contract(b6, n)
|
||||
|> each_contract(b7, n) |> each_contract(b8, n)
|
||||
end
|
||||
|
||||
defp each_contract(acc, { b1, b2, b3, b4, b5, b6, b7, b8 }, n) do
|
||||
acc |> node_relocate(b1, n) |> node_relocate(b2, n) |> node_relocate(b3, n)
|
||||
|> node_relocate(b4, n) |> node_relocate(b5, n) |> node_relocate(b6, n)
|
||||
|> node_relocate(b7, n) |> node_relocate(b8, n)
|
||||
acc
|
||||
|> node_relocate(b1, n) |> node_relocate(b2, n) |> node_relocate(b3, n)
|
||||
|> node_relocate(b4, n) |> node_relocate(b5, n) |> node_relocate(b6, n)
|
||||
|> node_relocate(b7, n) |> node_relocate(b8, n)
|
||||
end
|
||||
|
||||
defp node_relocate(node // @node_template, bucket, n) do
|
||||
defp node_relocate(node // unquote(node_escaped), bucket, n) do
|
||||
:lists.foldl fn { key, value }, acc ->
|
||||
pos = key |> bucket_hash() |> bucket_nth_index(n)
|
||||
setelem(acc, pos, bucket_put!(elem(acc, pos), key, value))
|
||||
set_elem(acc, pos, bucket_put!(elem(acc, pos), key, value))
|
||||
end, node, bucket
|
||||
end
|
||||
end
|
||||
|
||||
@@ -168,7 +168,7 @@ defmodule IO.ANSI do
|
||||
|
||||
## Example
|
||||
|
||||
IO.ANSI.escape "Hello %{red,bright,green}yes" #=>
|
||||
iex> IO.ANSI.escape "Hello %{red,bright,green}yes"
|
||||
"Hello \e[31m\e[1m\e[32myes\e[0m"
|
||||
"""
|
||||
@spec escape(String.t, emit :: boolean) :: String.t
|
||||
@@ -194,7 +194,7 @@ defmodule IO.ANSI do
|
||||
|
||||
## Example
|
||||
|
||||
IO.ANSI.escape "Hello %{red,bright,green}yes%{reset}" #=>
|
||||
iex> IO.ANSI.escape "Hello %{red,bright,green}yes"
|
||||
"Hello \e[31m\e[1m\e[32myes\e[0m"
|
||||
|
||||
"""
|
||||
|
||||
+605
-316
File diff suppressed because it is too large
Load Diff
@@ -17,7 +17,7 @@ defmodule Kernel.ErrorHandler do
|
||||
case Code.ensure_loaded(module) do
|
||||
{ :module, _ } -> []
|
||||
{ :error, _ } ->
|
||||
parent = Process.get(:elixir_compiler_pid)
|
||||
parent = :erlang.get(:elixir_compiler_pid)
|
||||
parent <- { :waiting, self(), module }
|
||||
receive do
|
||||
{ :release, ^parent } -> :ok
|
||||
|
||||
@@ -32,7 +32,7 @@ defmodule Kernel.ParallelCompiler do
|
||||
|
||||
defp spawn_compilers(files, path, callback) do
|
||||
Code.ensure_loaded(Kernel.ErrorHandler)
|
||||
schedulers = :erlang.system_info(:schedulers_online)
|
||||
schedulers = max(:erlang.system_info(:schedulers_online), 2)
|
||||
spawn_compilers(files, path, callback, [], [], schedulers, [])
|
||||
end
|
||||
|
||||
@@ -99,16 +99,34 @@ defmodule Kernel.ParallelCompiler do
|
||||
new_waiting = OrdDict.store(child, on, waiting)
|
||||
spawn_compilers(files, output, callback, new_waiting, queued, schedulers, result)
|
||||
{ :failure, child, kind, reason, stacktrace } ->
|
||||
extra = if match?({^child, module}, List.keyfind(waiting, child, 0)) do
|
||||
" (undefined module #{inspect module})"
|
||||
if many_missing?(child, files, waiting, queued) do
|
||||
IO.puts "== Compilation failed =="
|
||||
IO.puts "Compilation failed on the following files:\n"
|
||||
|
||||
Enum.each Enum.reverse(queued), fn { pid, file } ->
|
||||
case List.keyfind(waiting, pid, 0) do
|
||||
{ _, mod } -> IO.puts "* #{file} is missing module #{inspect mod}"
|
||||
_ -> :ok
|
||||
end
|
||||
end
|
||||
|
||||
IO.puts "\nThe first failure is shown below..."
|
||||
end
|
||||
|
||||
{^child, file} = List.keyfind(queued, child, 0)
|
||||
IO.puts "== Compilation error on file #{file}#{extra} =="
|
||||
:erlang.raise(kind, reason, stacktrace)
|
||||
{^child, file} = List.keyfind(queued, child, 0)
|
||||
IO.puts "== Compilation error on file #{file} =="
|
||||
:erlang.raise(kind, reason, stacktrace)
|
||||
end
|
||||
end
|
||||
|
||||
defp many_missing?(child, files, waiting, queued) do
|
||||
waiting_length = length(waiting)
|
||||
|
||||
match?({ ^child, _ }, List.keyfind(waiting, child, 0)) and
|
||||
waiting_length > 1 and files == [] and
|
||||
waiting_length == length(queued)
|
||||
end
|
||||
|
||||
# Release waiting processes that are waiting for the given module
|
||||
defp release_waiting_processes(module, waiting) do
|
||||
Enum.filter waiting, fn { child, waiting_module } ->
|
||||
|
||||
@@ -12,7 +12,7 @@ defmodule Kernel.ParallelRequire do
|
||||
can be optionally given as argument.
|
||||
"""
|
||||
def files(files, callback // default_callback) do
|
||||
schedulers = :erlang.system_info(:schedulers_online)
|
||||
schedulers = max(:erlang.system_info(:schedulers_online), 2)
|
||||
spawn_requires(files, [], callback, schedulers, [])
|
||||
end
|
||||
|
||||
|
||||
@@ -5,42 +5,50 @@
|
||||
defmodule Kernel.RecordRewriter do
|
||||
@moduledoc false
|
||||
|
||||
def optimize_clause(clause) do
|
||||
optimize_clause(clause, :orddict.new)
|
||||
def optimize_clause(module, clause) do
|
||||
optimize_clause(clause, module, :orddict.new)
|
||||
end
|
||||
|
||||
## Clause
|
||||
|
||||
defp optimize_clause({ :clause, line, args, guards, body }, dict) do
|
||||
{ args, dict } = optimize_args(args, dict)
|
||||
{ body, dict, res } = optimize_body(body, dict, [])
|
||||
defp optimize_clause({ :clause, line, args, guards, body }, module, dict) do
|
||||
{ args, dict } = optimize_args(args, module, dict)
|
||||
{ body, dict, res } = optimize_body(body, module, dict, [])
|
||||
{ { :clause, line, args, guards, body }, dict, res }
|
||||
end
|
||||
|
||||
defp optimize_args(args, dict) do
|
||||
defp optimize_args(args, module, dict) do
|
||||
Enum.map_reduce args, dict, fn(arg, acc) ->
|
||||
{ new_arg, new_acc, _res } = optimize_expr(arg, acc)
|
||||
{ new_arg, new_acc, _res } = optimize_expr(arg, module, acc)
|
||||
{ new_arg, new_acc }
|
||||
end
|
||||
end
|
||||
|
||||
defp optimize_body([], dict, _acc) do
|
||||
defp optimize_body([], _module, dict, _acc) do
|
||||
{ [], dict, nil }
|
||||
end
|
||||
|
||||
defp optimize_body([h], dict, acc) do
|
||||
{ new_expr, new_dict, new_res } = optimize_expr(h, dict)
|
||||
defp optimize_body([h], module, dict, acc) do
|
||||
{ new_expr, new_dict, new_res } = optimize_expr(h, module, dict)
|
||||
{ Enum.reverse([new_expr|acc]), new_dict, new_res }
|
||||
end
|
||||
|
||||
defp optimize_body([h|t], dict, acc) do
|
||||
{ new_expr, new_dict, _ } = optimize_expr(h, dict)
|
||||
optimize_body(t, new_dict, [new_expr|acc])
|
||||
defp optimize_body([h|t], module, dict, acc) do
|
||||
{ new_expr, new_dict, _ } = optimize_expr(h, module, dict)
|
||||
optimize_body(t, module, new_dict, [new_expr|acc])
|
||||
end
|
||||
|
||||
## Record helpers
|
||||
|
||||
defp record_fields(record) do
|
||||
defp record_fields(record, record) do
|
||||
fields = Module.get_attribute(record, :record_fields)
|
||||
optimizable = Module.get_attribute(record, :record_optimizable)
|
||||
unless nil?(fields) or nil?(optimizable) do
|
||||
{ (lc { k, _ } inlist fields, do: k), optimizable }
|
||||
end
|
||||
end
|
||||
|
||||
defp record_fields(_module, record) do
|
||||
if Code.ensure_loaded?(record) && function_exported?(record, :__record__, 1) do
|
||||
try do
|
||||
fields = lc { k, _ } inlist record.__record__(:fields), do: k
|
||||
@@ -60,8 +68,8 @@ defmodule Kernel.RecordRewriter do
|
||||
end
|
||||
end
|
||||
|
||||
defp optimize_call(line, { record, _ } = res, left, { :atom, _, function }, args) do
|
||||
case record_fields(record) do
|
||||
defp optimize_call(line, module, { record, _ } = res, left, { :atom, _, function }, args) do
|
||||
case record_fields(module, record) do
|
||||
{ fields, optimizable } ->
|
||||
opt_call =
|
||||
if List.member?(optimizable, { function, length(args) + 1 }) do
|
||||
@@ -81,7 +89,7 @@ defmodule Kernel.RecordRewriter do
|
||||
end
|
||||
end
|
||||
|
||||
defp optimize_call(_line, _res, _left, _right, _args) do
|
||||
defp optimize_call(_line, _module, _res, _left, _right, _args) do
|
||||
nil
|
||||
end
|
||||
|
||||
@@ -111,12 +119,12 @@ defmodule Kernel.RecordRewriter do
|
||||
|
||||
## Expr
|
||||
|
||||
defp optimize_expr({ :call, call_line, { :remote, line, left, right }, args }, dict) do
|
||||
{ left, dict, res } = optimize_expr(left, dict)
|
||||
{ right, dict, _ } = optimize_expr(right, dict)
|
||||
{ args, dict } = optimize_args(args, dict)
|
||||
defp optimize_expr({ :call, call_line, { :remote, line, left, right }, args }, module, dict) do
|
||||
{ left, dict, res } = optimize_expr(left, module, dict)
|
||||
{ right, dict, _ } = optimize_expr(right, module, dict)
|
||||
{ args, dict } = optimize_args(args, module, dict)
|
||||
|
||||
case optimize_call(call_line, res, left, right, args) do
|
||||
case optimize_call(call_line, module, res, left, right, args) do
|
||||
{ call, call_res } ->
|
||||
{ call, dict, call_res }
|
||||
nil ->
|
||||
@@ -124,15 +132,15 @@ defmodule Kernel.RecordRewriter do
|
||||
end
|
||||
end
|
||||
|
||||
defp optimize_expr({ :call, line, expr, args }, dict) do
|
||||
{ expr, dict, _ } = optimize_expr(expr, dict)
|
||||
{ args, dict } = optimize_args(args, dict)
|
||||
defp optimize_expr({ :call, line, expr, args }, module, dict) do
|
||||
{ expr, dict, _ } = optimize_expr(expr, module, dict)
|
||||
{ args, dict } = optimize_args(args, module, dict)
|
||||
{ { :call, line, expr, args }, dict, nil }
|
||||
end
|
||||
|
||||
defp optimize_expr({ :match, line, left, right }, dict) do
|
||||
{ left, dict, left_res } = optimize_expr(left, dict)
|
||||
{ right, dict, right_res } = optimize_expr(right, dict)
|
||||
defp optimize_expr({ :match, line, left, right }, module, dict) do
|
||||
{ left, dict, left_res } = optimize_expr(left, module, dict)
|
||||
{ right, dict, right_res } = optimize_expr(right, module, dict)
|
||||
|
||||
match = { :match, line, left, right }
|
||||
|
||||
@@ -147,40 +155,40 @@ defmodule Kernel.RecordRewriter do
|
||||
{ match, dict, right_res || left_res }
|
||||
end
|
||||
|
||||
defp optimize_expr({ :op, line, op, left, right }, dict) do
|
||||
{ left, dict, _ } = optimize_expr(left, dict)
|
||||
{ right, dict, _ } = optimize_expr(right, dict)
|
||||
defp optimize_expr({ :op, line, op, left, right }, module, dict) do
|
||||
{ left, dict, _ } = optimize_expr(left, module, dict)
|
||||
{ right, dict, _ } = optimize_expr(right, module, dict)
|
||||
{ { :op, line, op, left, right }, dict, nil }
|
||||
end
|
||||
|
||||
defp optimize_expr({ :op, line, op, expr }, dict) do
|
||||
{ expr, dict, _ } = optimize_expr(expr, dict)
|
||||
defp optimize_expr({ :op, line, op, expr }, module, dict) do
|
||||
{ expr, dict, _ } = optimize_expr(expr, module, dict)
|
||||
{ { :op, line, op, expr }, dict, nil }
|
||||
end
|
||||
|
||||
defp optimize_expr({ :bin, line, elements }, dict) do
|
||||
{ elements, dict } = optimize_args(elements, dict)
|
||||
defp optimize_expr({ :bin, line, elements }, module, dict) do
|
||||
{ elements, dict } = optimize_args(elements, module, dict)
|
||||
{ { :bin, line, elements }, dict, nil }
|
||||
end
|
||||
|
||||
defp optimize_expr({ :bin_element, line, expr, type1, type2 }, dict) do
|
||||
{ expr, dict, _ } = optimize_expr(expr, dict)
|
||||
defp optimize_expr({ :bin_element, line, expr, type1, type2 }, module, dict) do
|
||||
{ expr, dict, _ } = optimize_expr(expr, module, dict)
|
||||
{ { :bin_element, line, expr, type1, type2 }, dict, nil }
|
||||
end
|
||||
|
||||
defp optimize_expr({ :cons, line, left, right }, dict) do
|
||||
{ left, dict, _ } = optimize_expr(left, dict)
|
||||
{ right, dict, _ } = optimize_expr(right, dict)
|
||||
defp optimize_expr({ :cons, line, left, right }, module, dict) do
|
||||
{ left, dict, _ } = optimize_expr(left, module, dict)
|
||||
{ right, dict, _ } = optimize_expr(right, module, dict)
|
||||
{ { :cons, line, left, right }, dict, nil }
|
||||
end
|
||||
|
||||
defp optimize_expr({ :block, line, args }, dict) do
|
||||
{ args, dict, res } = optimize_body(args, dict, [])
|
||||
defp optimize_expr({ :block, line, args }, module, dict) do
|
||||
{ args, dict, res } = optimize_body(args, module, dict, [])
|
||||
{ { :block, line, args }, dict, res }
|
||||
end
|
||||
|
||||
defp optimize_expr({ :tuple, line, args }, dict) do
|
||||
{ args, dict, args_res } = optimize_tuple_args(args, dict)
|
||||
defp optimize_expr({ :tuple, line, args }, module, dict) do
|
||||
{ args, dict, args_res } = optimize_tuple_args(args, module, dict)
|
||||
args_res = if Enum.any?(args_res), do: args_res, else: nil
|
||||
|
||||
res =
|
||||
@@ -192,36 +200,36 @@ defmodule Kernel.RecordRewriter do
|
||||
{ { :tuple, line, args }, dict, { res, args_res } }
|
||||
end
|
||||
|
||||
defp optimize_expr({ :var, _, name } = var, dict) do
|
||||
defp optimize_expr({ :var, _, name } = var, _module, dict) do
|
||||
case :orddict.find(name, dict) do
|
||||
{ :ok, res } -> { var, dict, res }
|
||||
:error -> { var, dict, nil }
|
||||
end
|
||||
end
|
||||
|
||||
defp optimize_expr({ :case, line, expr, clauses }, dict) do
|
||||
{ expr, dict, _ } = optimize_expr(expr, dict)
|
||||
tuples = lc clause inlist clauses, do: optimize_clause(clause, dict)
|
||||
defp optimize_expr({ :case, line, expr, clauses }, module, dict) do
|
||||
{ expr, dict, _ } = optimize_expr(expr, module, dict)
|
||||
tuples = lc clause inlist clauses, do: optimize_clause(clause, module, dict)
|
||||
clauses = lc { clause, _, _ } inlist tuples, do: clause
|
||||
dict = join_dict(tuples)
|
||||
res = join_result(tuples)
|
||||
{ { :case, line, expr, clauses }, dict, res }
|
||||
end
|
||||
|
||||
defp optimize_expr({ :receive, line, clauses }, dict) do
|
||||
tuples = lc clause inlist clauses, do: optimize_clause(clause, dict)
|
||||
defp optimize_expr({ :receive, line, clauses }, module, dict) do
|
||||
tuples = lc clause inlist clauses, do: optimize_clause(clause, module, dict)
|
||||
clauses = lc { clause, _, _ } inlist tuples, do: clause
|
||||
dict = join_dict(tuples)
|
||||
res = join_result(tuples)
|
||||
{ { :receive, line, clauses }, dict, res }
|
||||
end
|
||||
|
||||
defp optimize_expr({ :receive, line, clauses, after_key, after_value }, dict) do
|
||||
tuples = lc clause inlist clauses, do: optimize_clause(clause, dict)
|
||||
defp optimize_expr({ :receive, line, clauses, after_key, after_value }, module, dict) do
|
||||
tuples = lc clause inlist clauses, do: optimize_clause(clause, module, dict)
|
||||
clauses = lc { clause, _, _ } inlist tuples, do: clause
|
||||
|
||||
{ after_key, dict, _ } = optimize_expr(after_key, dict)
|
||||
{ after_value, dict, res } = optimize_body(after_value, dict, [])
|
||||
{ after_key, dict, _ } = optimize_expr(after_key, module, dict)
|
||||
{ after_value, dict, res } = optimize_body(after_value, module, dict, [])
|
||||
|
||||
dict = join_dict(tuples, dict)
|
||||
res = join_result(tuples, res)
|
||||
@@ -229,58 +237,58 @@ defmodule Kernel.RecordRewriter do
|
||||
{ { :receive, line, clauses, after_key, after_value }, dict, res }
|
||||
end
|
||||
|
||||
defp optimize_expr({ :try, line, body, elses, catches, try_after }, dict) do
|
||||
tuples = lc clause inlist catches, do: optimize_clause(clause, dict)
|
||||
defp optimize_expr({ :try, line, body, elses, catches, try_after }, module, dict) do
|
||||
tuples = lc clause inlist catches, do: optimize_clause(clause, module, dict)
|
||||
catches = lc { clause, _, _ } inlist tuples, do: clause
|
||||
|
||||
tuples = lc clause inlist elses, do: optimize_clause(clause, dict)
|
||||
tuples = lc clause inlist elses, do: optimize_clause(clause, module, dict)
|
||||
elses = lc { clause, _, _ } inlist tuples, do: clause
|
||||
|
||||
{ body, _, res } = optimize_body(body, dict, [])
|
||||
{ body, _, res } = optimize_body(body, module, dict, [])
|
||||
res = join_result(tuples, res)
|
||||
|
||||
{ try_after, _, _ } = optimize_body(try_after, dict, [])
|
||||
{ try_after, _, _ } = optimize_body(try_after, module, dict, [])
|
||||
{ { :try, line, body, elses, catches, try_after }, dict, res }
|
||||
end
|
||||
|
||||
defp optimize_expr({ :fun, line, { :function, module, name, arity } }, dict) do
|
||||
{ module, dict, _ } = optimize_expr(module, dict)
|
||||
{ name, dict, _ } = optimize_expr(name, dict)
|
||||
{ arity, dict, _ } = optimize_expr(arity, dict)
|
||||
{ { :fun, line, { :function, module, name, arity } }, dict, nil }
|
||||
defp optimize_expr({ :fun, line, { :function, receiver, name, arity } }, module, dict) do
|
||||
{ receiver, dict, _ } = optimize_expr(receiver, module, dict)
|
||||
{ name, dict, _ } = optimize_expr(name, module, dict)
|
||||
{ arity, dict, _ } = optimize_expr(arity, module, dict)
|
||||
{ { :fun, line, { :function, receiver, name, arity } }, dict, nil }
|
||||
end
|
||||
|
||||
defp optimize_expr({ :fun, line, { :clauses, clauses } }, dict) do
|
||||
defp optimize_expr({ :fun, line, { :clauses, clauses } }, module, dict) do
|
||||
clauses = lc clause inlist clauses do
|
||||
{ clause, _, _ } = optimize_clause(clause, dict)
|
||||
{ clause, _, _ } = optimize_clause(clause, module, dict)
|
||||
clause
|
||||
end
|
||||
|
||||
{ { :fun, line, { :clauses, clauses } }, dict, nil }
|
||||
end
|
||||
|
||||
defp optimize_expr({ comprehension, line, expr, args }, dict) when comprehension in [:lc, :bc] do
|
||||
{ args, new_dict } = optimize_args(args, dict)
|
||||
{ expr, _, _ } = optimize_expr(expr, new_dict)
|
||||
defp optimize_expr({ comprehension, line, expr, args }, module, dict) when comprehension in [:lc, :bc] do
|
||||
{ args, new_dict } = optimize_args(args, module, dict)
|
||||
{ expr, _, _ } = optimize_expr(expr, module, new_dict)
|
||||
{ { comprehension, line, expr, args }, dict, nil }
|
||||
end
|
||||
|
||||
defp optimize_expr({ generate, line, left, right }, dict) when generate in [:generate, :b_generate] do
|
||||
{ left, dict, _ } = optimize_expr(left, dict)
|
||||
{ right, dict, _ } = optimize_expr(right, dict)
|
||||
defp optimize_expr({ generate, line, left, right }, module, dict) when generate in [:generate, :b_generate] do
|
||||
{ left, dict, _ } = optimize_expr(left, module, dict)
|
||||
{ right, dict, _ } = optimize_expr(right, module, dict)
|
||||
{ { generate, line, left, right }, dict, nil }
|
||||
end
|
||||
|
||||
defp optimize_expr(other, dict) when elem(other, 0) in [:string, :atom, :integer, :float, :nil, :fun] do
|
||||
defp optimize_expr(other, _module, dict) when elem(other, 0) in [:string, :atom, :integer, :float, :nil, :fun] do
|
||||
{ other, dict, nil }
|
||||
end
|
||||
|
||||
## Helpers
|
||||
|
||||
defp optimize_tuple_args(args, dict) do
|
||||
defp optimize_tuple_args(args, module, dict) do
|
||||
{ final_args, { final_dict, final_acc } } =
|
||||
Enum.map_reduce args, { dict, [] }, fn(arg, { acc_dict, acc_res }) ->
|
||||
{ new_arg, new_acc, res } = optimize_expr(arg, acc_dict)
|
||||
{ new_arg, new_acc, res } = optimize_expr(arg, module, acc_dict)
|
||||
{ new_arg, { new_acc, [res|acc_res] } }
|
||||
end
|
||||
|
||||
@@ -383,4 +391,4 @@ defmodule Kernel.RecordRewriter do
|
||||
defp join_result([], res) do
|
||||
res
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -46,7 +46,7 @@ defmodule Kernel.SpecialForms do
|
||||
|
||||
## Examples
|
||||
|
||||
:<<>>.(1,2,3)
|
||||
iex> << 1, 2, 3 >>
|
||||
<< 1, 2, 3 >>
|
||||
|
||||
## Bitstring types
|
||||
@@ -55,16 +55,16 @@ defmodule Kernel.SpecialForms do
|
||||
specific types. Most of the time, Elixir will figure out
|
||||
the part's type and won't require any work from you:
|
||||
|
||||
<<102, "oo">>
|
||||
#=> "foo"
|
||||
iex> <<102, "oo">>
|
||||
"foo"
|
||||
|
||||
Above we have two parts: the first is an integer and the
|
||||
second is a binary. If we use any other Elixir expression,
|
||||
Elixir can no longer guess the type:
|
||||
|
||||
rest = "oo"
|
||||
<<102, rest>>
|
||||
#=> ** (ArgumentError) argument error
|
||||
iex> rest = "oo"
|
||||
...> <<102, rest>>
|
||||
** (ArgumentError) argument error
|
||||
|
||||
When a variable or expression is given as a binary part,
|
||||
Elixir defaults the type of that part to an unsigned
|
||||
@@ -94,8 +94,8 @@ defmodule Kernel.SpecialForms do
|
||||
signedness only matters for matching. If unspecified, it
|
||||
defaults to unsigned. Example:
|
||||
|
||||
<<-100 :: signed, rest :: binary>> = <<-100, "foo">>
|
||||
#=> <<156,102,111,111>>
|
||||
iex> <<-100 :: signed, _rest :: binary>> = <<-100, "foo">>
|
||||
<<156,102,111,111>>
|
||||
|
||||
This match would have failed if we did not specify that the
|
||||
value -100 is signed. If we're matching into a variable instead
|
||||
@@ -103,9 +103,9 @@ defmodule Kernel.SpecialForms do
|
||||
will simply be interpreted as having the given (or implied)
|
||||
signedness, e.g.:
|
||||
|
||||
<<val, rest :: binary>> = <<-100, "foo">>
|
||||
val
|
||||
#=> 156
|
||||
iex> <<val, _rest :: binary>> = <<-100, "foo">>
|
||||
...> val
|
||||
156
|
||||
|
||||
Here, `val` is interpreted as unsigned.
|
||||
|
||||
@@ -130,13 +130,13 @@ defmodule Kernel.SpecialForms do
|
||||
unit is multiplied by the size to give the effective size of
|
||||
the part:
|
||||
|
||||
<<102, rest :: [size(2), unit(8)]>> = "foo"
|
||||
iex> <<102, _rest :: [size(2), unit(8)]>> = "foo"
|
||||
"foo"
|
||||
|
||||
<<102, rest :: size(16)>> = "foo"
|
||||
iex> <<102, _rest :: size(16)>> = "foo"
|
||||
"foo"
|
||||
|
||||
<<102, rest :: size(32)>> = "foo"
|
||||
iex> <<102, _rest :: size(32)>> = "foo"
|
||||
** (MatchError) no match of right hand side value: "foo"
|
||||
|
||||
In the example above, the first two expressions matches
|
||||
@@ -158,7 +158,8 @@ defmodule Kernel.SpecialForms do
|
||||
writing `size(8)`, one can write just `8` and it will be interpreted
|
||||
as `size(8)`
|
||||
|
||||
<< 1 :: 3 >> == << 1 :: size(3) >> #=> true
|
||||
iex> << 1 :: 3 >> == << 1 :: size(3) >>
|
||||
true
|
||||
|
||||
The default unit for integers, floats, and bitstrings is 1. For
|
||||
binaries, it is 8.
|
||||
@@ -245,8 +246,9 @@ defmodule Kernel.SpecialForms do
|
||||
import those functions and reference them as local functions,
|
||||
for example:
|
||||
|
||||
import List
|
||||
flatten([1,[2],3]) #=> [1,2,3]
|
||||
iex> import List
|
||||
...> flatten([1,[2],3])
|
||||
[1,2,3]
|
||||
|
||||
## Selector
|
||||
|
||||
@@ -302,6 +304,13 @@ defmodule Kernel.SpecialForms do
|
||||
All imported modules are also required by default. `import`
|
||||
also accepts `as:` as an option so it automatically sets up
|
||||
an alias. Please check `alias` for more information.
|
||||
|
||||
## Ambiguous function/macro names
|
||||
|
||||
If two modules `A` and `B` are imported and they both contain
|
||||
a `foo` function with an arity of `1`, an error is only emitted
|
||||
if an ambiguous call to `foo/1` is actually made; that is, the
|
||||
errors are emitted lazily, not eagerly.
|
||||
"""
|
||||
defmacro import(module, opts)
|
||||
|
||||
@@ -331,8 +340,8 @@ defmodule Kernel.SpecialForms do
|
||||
|
||||
## Examples
|
||||
|
||||
quote do: sum(1, 2, 3)
|
||||
#=> { :sum, [], [1, 2, 3] }
|
||||
iex> quote do: sum(1, 2, 3)
|
||||
{ :sum, [], [1, 2, 3] }
|
||||
|
||||
## Explanation
|
||||
|
||||
@@ -607,82 +616,44 @@ defmodule Kernel.SpecialForms do
|
||||
"""
|
||||
defmacro alias!(alias)
|
||||
|
||||
@doc """
|
||||
Unquotes the given expression from inside a macro.
|
||||
|
||||
## Examples
|
||||
|
||||
Imagine the situation you have a variable `name` and
|
||||
you want to inject it inside some quote. The first attempt
|
||||
would be:
|
||||
|
||||
value = 13
|
||||
quote do: sum(1, value, 3)
|
||||
|
||||
Which would then return:
|
||||
|
||||
{ :sum, [], [1, { :value, [], quoted }, 3] }
|
||||
|
||||
Which is not the expected result. For this, we use unquote:
|
||||
|
||||
value = 13
|
||||
quote do: sum(1, unquote(value), 3)
|
||||
#=> { :sum, [], [1, 13, 3] }
|
||||
|
||||
"""
|
||||
defmacro unquote(expr)
|
||||
|
||||
@doc """
|
||||
Unquotes the given list expanding its arguments. Similar
|
||||
to unquote.
|
||||
|
||||
## Examples
|
||||
|
||||
values = [2,3,4]
|
||||
quote do: sum(1, unquote_splicing(values), 5)
|
||||
#=> { :sum, [], [1, 2, 3, 4, 5] }
|
||||
|
||||
"""
|
||||
defmacro unquote_splicing(expr)
|
||||
|
||||
@doc """
|
||||
List comprehensions allow you to quickly build a list from another list:
|
||||
|
||||
lc n inlist [1,2,3,4], do: n * 2
|
||||
#=> [2,4,6,8]
|
||||
iex> lc n inlist [1,2,3,4], do: n * 2
|
||||
[2,4,6,8]
|
||||
|
||||
A comprehension accepts many generators and also filters. Generators
|
||||
are defined using both `inlist` and `inbits` operators, allowing you
|
||||
to loop lists and bitstrings:
|
||||
|
||||
# A list generator:
|
||||
lc n inlist [1,2,3,4], do: n * 2
|
||||
#=> [2,4,6,8]
|
||||
iex> lc n inlist [1,2,3,4], do: n * 2
|
||||
[2,4,6,8]
|
||||
|
||||
# A bit string generator:
|
||||
lc <<n>> inbits <<1,2,3,4>>, do: n * 2
|
||||
#=> [2,4,6,8]
|
||||
iex> lc <<n>> inbits <<1,2,3,4>>, do: n * 2
|
||||
[2,4,6,8]
|
||||
|
||||
# A generator from a variable:
|
||||
list = [1,2,3,4]
|
||||
lc n inlist list, do: n * 2
|
||||
#=> [2,4,6,8]
|
||||
iex> list = [1,2,3,4]
|
||||
...> lc n inlist list, do: n * 2
|
||||
[2,4,6,8]
|
||||
|
||||
# A comprehension with two generators
|
||||
lc x inlist [1,2], y inlist [2,3], do: x*y
|
||||
#=> [2,3,4,6]
|
||||
iex> lc x inlist [1,2], y inlist [2,3], do: x*y
|
||||
[2,3,4,6]
|
||||
|
||||
Filters can also be given:
|
||||
|
||||
# A comprehension with a generator and a filter
|
||||
lc n inlist [1,2,3,4,5,6], rem(n, 2) == 0, do: n
|
||||
#=> [2,4,6]
|
||||
iex> lc n inlist [1,2,3,4,5,6], rem(n, 2) == 0, do: n
|
||||
[2,4,6]
|
||||
|
||||
Bit string generators are quite useful when you need to
|
||||
organize bit string streams:
|
||||
|
||||
iex> pixels = <<213,45,132,64,76,32,76,0,0,234,32,15>>
|
||||
iex> lc <<r:8,g:8,b:8>> inbits pixels, do: {r,g,b}
|
||||
iex> lc <<r::8,g::8,b::8>> inbits pixels, do: {r,g,b}
|
||||
[{213,45,132},{64,76,32},{76,0,0},{234,32,15}]
|
||||
|
||||
"""
|
||||
@@ -694,7 +665,7 @@ defmodule Kernel.SpecialForms do
|
||||
be a bitstring. For example, here is how to remove all
|
||||
spaces from a string:
|
||||
|
||||
bc <<c>> inbits " hello world ", c != ? , do: <<c>>
|
||||
iex> bc <<c>> inbits " hello world ", c != ? , do: <<c>>
|
||||
"helloworld"
|
||||
|
||||
"""
|
||||
@@ -705,8 +676,8 @@ defmodule Kernel.SpecialForms do
|
||||
of expressions in Elixir. This special form is private
|
||||
and should not be invoked directly:
|
||||
|
||||
quote do: (1; 2; 3)
|
||||
#=> { :__block__, [], [1,2,3] }
|
||||
iex> quote do: (1; 2; 3)
|
||||
{ :__block__, [], [1,2,3] }
|
||||
|
||||
"""
|
||||
defmacro __block__(args)
|
||||
@@ -728,13 +699,13 @@ defmodule Kernel.SpecialForms do
|
||||
This is the special form used to hold aliases information.
|
||||
It is usually compiled to an atom:
|
||||
|
||||
quote do: Foo.Bar
|
||||
quote do: Foo.Bar #=>
|
||||
{ :__aliases__, [], [:Foo,:Bar] }
|
||||
|
||||
Elixir represents `Foo.Bar` as `__aliases__` so calls can be
|
||||
unambiguously identified by the operator `:.`. For example:
|
||||
|
||||
quote do: Foo.bar
|
||||
quote do: Foo.bar #=>
|
||||
{{:.,[],[{:__aliases__,[],[:Foo]},:bar]},[],[]}
|
||||
|
||||
Whenever an expression iterator sees a `:.` as the tuple key,
|
||||
|
||||
+48
-39
@@ -59,8 +59,8 @@ defmodule Keyword do
|
||||
|
||||
## Examples
|
||||
|
||||
Keyword.new [{:b,1},{:a,2}]
|
||||
#=> [a: 2, b: 1]
|
||||
iex> Keyword.new [{:b,1},{:a,2}]
|
||||
[a: 2, b: 1]
|
||||
|
||||
"""
|
||||
@spec new(Enum.t) :: t
|
||||
@@ -77,8 +77,8 @@ defmodule Keyword do
|
||||
|
||||
## Examples
|
||||
|
||||
Keyword.new [:a, :b], fn x -> {x,x} end
|
||||
#=> [a: :a, b: :b]
|
||||
iex> Enum.sort Keyword.new [:a, :b], fn x -> {x,x} end
|
||||
[a: :a, b: :b]
|
||||
|
||||
"""
|
||||
@spec new(Enum.t, ({key, value} -> {key, value})) :: t
|
||||
@@ -100,9 +100,12 @@ defmodule Keyword do
|
||||
|
||||
## Examples
|
||||
|
||||
Keyword.get [a: 1], :a #=> 1
|
||||
Keyword.get [a: 1], :b #=> nil
|
||||
Keyword.get [a: 1], :b, 3 #=> 3
|
||||
iex> Keyword.get [a: 1], :a
|
||||
1
|
||||
iex> Keyword.get [a: 1], :b
|
||||
nil
|
||||
iex> Keyword.get [a: 1], :b, 3
|
||||
3
|
||||
|
||||
"""
|
||||
@spec get(t, key) :: value
|
||||
@@ -120,8 +123,10 @@ defmodule Keyword do
|
||||
|
||||
## Examples
|
||||
|
||||
Keyword.get! [a: 1], :a #=> 1
|
||||
Keyword.get! [a: 1], :b #=> raises KeyError[key: :b]
|
||||
iex> Keyword.get! [a: 1], :a
|
||||
1
|
||||
iex> Keyword.get! [a: 1], :b
|
||||
** (KeyError) key not found: :b
|
||||
|
||||
"""
|
||||
@spec get!(t, key) :: value | no_return
|
||||
@@ -137,8 +142,8 @@ defmodule Keyword do
|
||||
|
||||
## Examples
|
||||
|
||||
Keyword.get_values [a: 1, a: 2], :a
|
||||
#=> [1,2]
|
||||
iex> Keyword.get_values [a: 1, a: 2], :a
|
||||
[1,2]
|
||||
|
||||
"""
|
||||
@spec get_values(t, key) :: [value]
|
||||
@@ -152,7 +157,8 @@ defmodule Keyword do
|
||||
|
||||
## Examples
|
||||
|
||||
Keyword.keys [a: 1, b: 2] #=> [:a,:b]
|
||||
iex> Keyword.keys [a: 1, b: 2]
|
||||
[:a,:b]
|
||||
|
||||
"""
|
||||
@spec keys(t) :: [key]
|
||||
@@ -165,7 +171,8 @@ defmodule Keyword do
|
||||
|
||||
## Examples
|
||||
|
||||
Keyword.values [a: 1, b: 2] #=> [1,2]
|
||||
iex> Keyword.values [a: 1, b: 2]
|
||||
[1,2]
|
||||
|
||||
"""
|
||||
@spec values(t) :: [value]
|
||||
@@ -181,8 +188,10 @@ defmodule Keyword do
|
||||
|
||||
## Examples
|
||||
|
||||
Keyword.delete [a: 1, b: 2], :a #=> [b: 2]
|
||||
Keyword.delete [b: 2], :a #=> [b: 2]
|
||||
iex> Keyword.delete [a: 1, b: 2], :a
|
||||
[b: 2]
|
||||
iex> Keyword.delete [b: 2], :a
|
||||
[b: 2]
|
||||
|
||||
"""
|
||||
@spec delete(t, key) :: t
|
||||
@@ -198,8 +207,8 @@ defmodule Keyword do
|
||||
|
||||
## Examples
|
||||
|
||||
Keyword.put [a: 1, b: 2], :a, 3
|
||||
#=> [a: 3, b: 2]
|
||||
iex> Keyword.put [a: 1, b: 2], :a, 3
|
||||
[a: 3, b: 2]
|
||||
|
||||
"""
|
||||
@spec put(t, key, value) :: t
|
||||
@@ -213,8 +222,8 @@ defmodule Keyword do
|
||||
|
||||
## Examples
|
||||
|
||||
Keyword.put_new [a: 1, b: 2], :a, 3
|
||||
#=> [a: 1, b: 2]
|
||||
iex> Keyword.put_new [a: 1, b: 2], :a, 3
|
||||
[a: 1, b: 2]
|
||||
|
||||
"""
|
||||
@spec put_new(t, key, value) :: t
|
||||
@@ -231,8 +240,8 @@ defmodule Keyword do
|
||||
|
||||
## Examples
|
||||
|
||||
Keyword.equal? [a: 1, b: 2], [b: 2, a: 1]
|
||||
#=> true
|
||||
iex> Keyword.equal? [a: 1, b: 2], [b: 2, a: 1]
|
||||
true
|
||||
|
||||
"""
|
||||
@spec equal?(t, t) :: boolean
|
||||
@@ -246,8 +255,8 @@ defmodule Keyword do
|
||||
|
||||
## Examples
|
||||
|
||||
Keyword.merge [a: 1, b: 2], [a: 3, d: 4]
|
||||
#=> [a:3, b:2, d: 4]
|
||||
iex> Enum.sort Keyword.merge [a: 1, b: 2], [a: 3, d: 4]
|
||||
[a: 3, b: 2, d: 4]
|
||||
|
||||
"""
|
||||
@spec merge(t, t) :: t
|
||||
@@ -261,10 +270,10 @@ defmodule Keyword do
|
||||
|
||||
## Examples
|
||||
|
||||
Keyword.merge [a: 1, b: 2], [a: 3, d: 4], fn _k, v1, v2 ->
|
||||
v1 + v2
|
||||
end
|
||||
#=> [a:4, b:2, d: 4]
|
||||
iex> Keyword.merge [a: 1, b: 2], [a: 3, d: 4], fn _k, v1, v2 ->
|
||||
...> v1 + v2
|
||||
iex> end
|
||||
[a: 4, b: 2, d: 4]
|
||||
|
||||
"""
|
||||
@spec merge(t, t, (key, value, value -> value)) :: t
|
||||
@@ -285,10 +294,10 @@ defmodule Keyword do
|
||||
|
||||
## Examples
|
||||
|
||||
Keyword.has_key?([a: 1], :a)
|
||||
#=> true
|
||||
Keyword.has_key?([a: 1], :b)
|
||||
#=> false
|
||||
iex> Keyword.has_key?([a: 1], :a)
|
||||
true
|
||||
iex> Keyword.has_key?([a: 1], :b)
|
||||
false
|
||||
|
||||
"""
|
||||
@spec has_key?(t, key) :: boolean
|
||||
@@ -302,10 +311,10 @@ defmodule Keyword do
|
||||
|
||||
## Examples
|
||||
|
||||
Keyword.update([a: 1], :a, &1 * 2)
|
||||
#=> [a: 2]
|
||||
Keyword.update([a: 1], :b, &1 * 2)
|
||||
#=> KeyError
|
||||
iex> Keyword.update([a: 1], :a, &1 * 2)
|
||||
[a: 2]
|
||||
iex> Keyword.update([a: 1], :b, &1 * 2)
|
||||
** (KeyError) key not found: :b
|
||||
|
||||
"""
|
||||
@spec update(t, key, (value -> value)) :: t | no_return
|
||||
@@ -327,10 +336,10 @@ defmodule Keyword do
|
||||
|
||||
## Examples
|
||||
|
||||
Keyword.update([a: 1], :a, 13, &1 * 2)
|
||||
#=> [a: 2]
|
||||
Keyword.update([a: 1], :b, 11, &1 * 2)
|
||||
#=> [a: 1, b: 11]
|
||||
iex> Keyword.update([a: 1], :a, 13, &1 * 2)
|
||||
[a: 2]
|
||||
iex> Keyword.update([a: 1], :b, 11, &1 * 2)
|
||||
[a: 1, b: 11]
|
||||
|
||||
"""
|
||||
@spec update(t, key, value, (value -> value)) :: t
|
||||
|
||||
+62
-61
@@ -15,8 +15,8 @@ defmodule List do
|
||||
|
||||
## Examples
|
||||
|
||||
List.concat [[1,[2],3], [4], [5,6]]
|
||||
#=> [1,[2],3,4,5,6]
|
||||
iex> List.concat [[1,[2],3], [4], [5,6]]
|
||||
[1,[2],3,4,5,6]
|
||||
|
||||
"""
|
||||
def concat(list) when is_list(list) do
|
||||
@@ -32,8 +32,8 @@ defmodule List do
|
||||
|
||||
## Examples
|
||||
|
||||
List.concat [1,2,3], [4,5,6]
|
||||
#=> [1,2,3,4,5,6]
|
||||
iex> List.concat [1,2,3], [4,5,6]
|
||||
[1,2,3,4,5,6]
|
||||
|
||||
"""
|
||||
def concat(list, elements) when is_list(list) and is_list(elements) do
|
||||
@@ -47,8 +47,8 @@ defmodule List do
|
||||
|
||||
## Examples
|
||||
|
||||
List.delete([1,2,3], 1)
|
||||
#=> [2,3]
|
||||
iex> List.delete([1,2,3], 1)
|
||||
[2,3]
|
||||
|
||||
"""
|
||||
def delete(list, item) do
|
||||
@@ -60,11 +60,11 @@ defmodule List do
|
||||
|
||||
## Examples
|
||||
|
||||
List.duplicate "hello", 3
|
||||
#=> ["hello","hello","hello"]
|
||||
iex> List.duplicate "hello", 3
|
||||
["hello","hello","hello"]
|
||||
|
||||
List.duplicate [1,2], 2
|
||||
#=> [[1,2],[1,2]]
|
||||
iex> List.duplicate [1,2], 2
|
||||
[[1,2],[1,2]]
|
||||
"""
|
||||
def duplicate(elem, n) do
|
||||
:lists.duplicate(n, elem)
|
||||
@@ -77,11 +77,11 @@ defmodule List do
|
||||
|
||||
## Examples
|
||||
|
||||
List.flatten [1,[[2],3]]
|
||||
#=> [1,2,3]
|
||||
iex> List.flatten [1,[[2],3]]
|
||||
[1,2,3]
|
||||
|
||||
List.flatten [1,[[2],3]], [4,5]
|
||||
#=> [1,2,3,4,5]
|
||||
iex> List.flatten [1,[[2],3]], [4,5]
|
||||
[1,2,3,4,5]
|
||||
|
||||
"""
|
||||
def flatten(list) do
|
||||
@@ -98,11 +98,11 @@ defmodule List do
|
||||
|
||||
## Examples
|
||||
|
||||
List.foldl [5,5], 10, fn x, acc -> x + acc end
|
||||
#=> 20
|
||||
iex> List.foldl [5,5], 10, fn x, acc -> x + acc end
|
||||
20
|
||||
|
||||
List.foldl [1,2,3,4], 0, fn x, acc -> x - acc end
|
||||
#=> 2
|
||||
iex> List.foldl [1,2,3,4], 0, fn x, acc -> x - acc end
|
||||
2
|
||||
|
||||
"""
|
||||
def foldl(list, acc, function) when is_list(list) and is_function(function) do
|
||||
@@ -115,8 +115,8 @@ defmodule List do
|
||||
|
||||
## Examples
|
||||
|
||||
List.foldr [1,2,3,4], 0, fn x, acc -> x - acc end
|
||||
#=> -2
|
||||
iex> List.foldr [1,2,3,4], 0, fn x, acc -> x - acc end
|
||||
-2
|
||||
|
||||
"""
|
||||
def foldr(list, acc, function) when is_list(list) and is_function(function) do
|
||||
@@ -128,12 +128,12 @@ defmodule List do
|
||||
|
||||
## Examples
|
||||
|
||||
List.last []
|
||||
#=> nil
|
||||
List.last [1]
|
||||
#=> 1
|
||||
List.last [1, 2, 3]
|
||||
#=> 3
|
||||
iex> List.last []
|
||||
nil
|
||||
iex> List.last [1]
|
||||
1
|
||||
iex> List.last [1, 2, 3]
|
||||
3
|
||||
|
||||
"""
|
||||
def last([]), do: nil
|
||||
@@ -149,11 +149,11 @@ defmodule List do
|
||||
|
||||
## Examples
|
||||
|
||||
List.member? [1,2,3], 1
|
||||
#=> true
|
||||
iex> List.member? [1,2,3], 1
|
||||
true
|
||||
|
||||
List.member? [1,2,3], 0
|
||||
#=> false
|
||||
iex> List.member? [1,2,3], 0
|
||||
false
|
||||
|
||||
"""
|
||||
def member?(list, term) do
|
||||
@@ -167,14 +167,14 @@ defmodule List do
|
||||
|
||||
## Examples
|
||||
|
||||
List.keyfind([a: 1, b: 2], :a, 0)
|
||||
#=> { :a, 1 }
|
||||
iex> List.keyfind([a: 1, b: 2], :a, 0)
|
||||
{ :a, 1 }
|
||||
|
||||
List.keyfind([a: 1, b: 2], 2, 1)
|
||||
#=> { :b, 2 }
|
||||
iex> List.keyfind([a: 1, b: 2], 2, 1)
|
||||
{ :b, 2 }
|
||||
|
||||
List.keyfind([a: 1, b: 2], :c, 0)
|
||||
#=> nil
|
||||
iex> List.keyfind([a: 1, b: 2], :c, 0)
|
||||
nil
|
||||
|
||||
"""
|
||||
def keyfind(list, key, position, default // nil) do
|
||||
@@ -188,14 +188,14 @@ defmodule List do
|
||||
|
||||
## Examples
|
||||
|
||||
List.keymember?([a: 1, b: 2], :a, 0)
|
||||
#=> true
|
||||
iex> List.keymember?([a: 1, b: 2], :a, 0)
|
||||
true
|
||||
|
||||
List.keymember?([a: 1, b: 2], 2, 1)
|
||||
#=> true
|
||||
iex> List.keymember?([a: 1, b: 2], 2, 1)
|
||||
true
|
||||
|
||||
List.keymember?([a: 1, b: 2], :c, 0)
|
||||
#=> false
|
||||
iex> List.keymember?([a: 1, b: 2], :c, 0)
|
||||
false
|
||||
|
||||
"""
|
||||
def keymember?(list, key, position) do
|
||||
@@ -208,8 +208,8 @@ defmodule List do
|
||||
|
||||
## Examples
|
||||
|
||||
List.keyreplace([a: 1, b: 2], :a, 0, { :a, 3 })
|
||||
#=> [a: 3, b: 2]
|
||||
iex> List.keyreplace([a: 1, b: 2], :a, 0, { :a, 3 })
|
||||
[a: 3, b: 2]
|
||||
|
||||
"""
|
||||
def keyreplace(list, key, position, new_tuple) do
|
||||
@@ -223,8 +223,8 @@ defmodule List do
|
||||
|
||||
## Examples
|
||||
|
||||
List.keystore([a: 1, b: 2], :a, 0, { :a, 3 })
|
||||
#=> [a: 3, b: 2]
|
||||
iex> List.keystore([a: 1, b: 2], :a, 0, { :a, 3 })
|
||||
[a: 3, b: 2]
|
||||
|
||||
"""
|
||||
def keystore(list, key, position, new_tuple) do
|
||||
@@ -238,14 +238,14 @@ defmodule List do
|
||||
|
||||
## Examples
|
||||
|
||||
List.keydelete([a: 1, b: 2], :a, 0)
|
||||
#=> [{ :b, 2 }]
|
||||
iex> List.keydelete([a: 1, b: 2], :a, 0)
|
||||
[{ :b, 2 }]
|
||||
|
||||
List.keydelete([a: 1, b: 2], 2, 1)
|
||||
#=> [{ :a, 1 }]
|
||||
iex> List.keydelete([a: 1, b: 2], 2, 1)
|
||||
[{ :a, 1 }]
|
||||
|
||||
List.keydelete([a: 1, b: 2], :c, 0)
|
||||
#=> [{ :a, 1 }, { :b, 2 }]
|
||||
iex> List.keydelete([a: 1, b: 2], :c, 0)
|
||||
[{ :a, 1 }, { :b, 2 }]
|
||||
|
||||
"""
|
||||
def keydelete(list, key, position) do
|
||||
@@ -259,7 +259,8 @@ defmodule List do
|
||||
|
||||
## Examples
|
||||
|
||||
List.wrap [1,2,3] #=> [1,2,3]
|
||||
iex> List.wrap [1,2,3]
|
||||
[1,2,3]
|
||||
|
||||
"""
|
||||
def wrap(list) when is_list(list) do
|
||||
@@ -279,11 +280,11 @@ defmodule List do
|
||||
|
||||
## Examples
|
||||
|
||||
List.zip [[1, 2], [3, 4], [5, 6]]
|
||||
#=> [{1, 3, 5}, {2, 4, 6}]
|
||||
iex> List.zip [[1, 2], [3, 4], [5, 6]]
|
||||
[{1, 3, 5}, {2, 4, 6}]
|
||||
|
||||
List.zip [[1, 2], [3], [5, 6]]
|
||||
#=> [{1, 3, 5}]
|
||||
iex> List.zip [[1, 2], [3], [5, 6]]
|
||||
[{1, 3, 5}]
|
||||
|
||||
"""
|
||||
def zip([]), do: []
|
||||
@@ -297,11 +298,11 @@ defmodule List do
|
||||
|
||||
## Examples
|
||||
|
||||
List.unzip [{1, 2}, {3, 4}]
|
||||
#=> [[1, 3], [2, 4]]
|
||||
iex> List.unzip [{1, 2}, {3, 4}]
|
||||
[[1, 3], [2, 4]]
|
||||
|
||||
List.unzip [{1, :a, "apple"}, {2, :b, "banana"}, {3, :c}]
|
||||
#=> [[1, 2, 3], [:a, :b, :c]]
|
||||
iex> List.unzip [{1, :a, "apple"}, {2, :b, "banana"}, {3, :c}]
|
||||
[[1, 2, 3], [:a, :b, :c]]
|
||||
|
||||
"""
|
||||
def unzip(list) when is_list(list) do
|
||||
|
||||
+118
-49
@@ -42,7 +42,6 @@ defmodule Macro do
|
||||
|
||||
extract_args(quote do: foo) == { :foo, [] }
|
||||
extract_args(quote do: foo()) == { :foo, [] }
|
||||
extract_args(quote do: :foo.()) == { :foo, [] }
|
||||
extract_args(quote do: foo(1,2,3)) == { :foo, [1,2,3] }
|
||||
extract_args(quote do: 1.(1,2,3)) == :error
|
||||
|
||||
@@ -52,31 +51,91 @@ defmodule Macro do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Recursively escapes the given value so it can be inserted
|
||||
into a syntax tree. Structures that are valid syntax nodes
|
||||
(like atoms, integers, binaries) are represented by themselves.
|
||||
Recursively escapes a value so it can be inserted
|
||||
into a syntax tree.
|
||||
|
||||
## Examples
|
||||
|
||||
Macro.escape(:foo)
|
||||
#=> :foo
|
||||
iex> Macro.escape(:foo)
|
||||
:foo
|
||||
|
||||
iex> Macro.escape({ :a, :b, :c })
|
||||
{ :{}, [], [:a, :b, :c] }
|
||||
|
||||
Macro.escape({ :a, :b, :c })
|
||||
#=> { :{}, [], [:a, :b, :c] }
|
||||
"""
|
||||
def escape({ left, right }) do
|
||||
{ escape(left), escape(right) }
|
||||
def escape(expr) do
|
||||
do_escape(expr, false)
|
||||
end
|
||||
|
||||
def escape(tuple) when is_tuple(tuple) do
|
||||
{ :{}, [], escape(tuple_to_list(tuple)) }
|
||||
defp do_escape({ { { :., meta, [left, :unquote] }, _, [expr] }, _, args }, true) do
|
||||
all = [do_escape(meta, true), do_escape(left, true), expr, do_escape(args, true)]
|
||||
quote do
|
||||
apply :elixir_quote, :unquote, [unquote_splicing(all), __FILE__]
|
||||
end
|
||||
end
|
||||
|
||||
def escape(list) when is_list(list) do
|
||||
lc item inlist list, do: escape(item)
|
||||
defp do_escape({ { :., meta, [left, :unquote] }, _, [expr] }, true) do
|
||||
all = [do_escape(meta, true), do_escape(left, true), expr, nil]
|
||||
quote do
|
||||
apply :elixir_quote, :unquote, [unquote_splicing(all), __FILE__]
|
||||
end
|
||||
end
|
||||
|
||||
def escape(other), do: other
|
||||
defp do_escape({ :unquote, _meta, [expr] }, true) do
|
||||
expr
|
||||
end
|
||||
|
||||
defp do_escape({ left, right }, unquote) when
|
||||
(not is_tuple(left) or elem(left, 1) != :unquote_splicing) and
|
||||
(not is_tuple(right) or elem(right, 1) != :unquote_splicing) do
|
||||
{ do_escape(left, unquote), do_escape(right, unquote) }
|
||||
end
|
||||
|
||||
defp do_escape({ :quote, meta, args }, true) when length(args) in 1..2 do
|
||||
{ :{}, [], do_escape([:quote, meta, args], false) }
|
||||
end
|
||||
|
||||
defp do_escape(tuple, unquote) when is_tuple(tuple) do
|
||||
{ :{}, [], do_escape(tuple_to_list(tuple), unquote) }
|
||||
end
|
||||
|
||||
defp do_escape(list, true) when is_list(list) do
|
||||
do_splice(Enum.reverse(list))
|
||||
end
|
||||
|
||||
defp do_escape(list, false) when is_list(list) do
|
||||
lc item inlist list, do: do_escape(item, false)
|
||||
end
|
||||
|
||||
defp do_escape(other, _unquote), do: other
|
||||
|
||||
defp do_splice([{ :|, meta, [{ :unquote_splicing, _, [left] }, right] }|t]) do
|
||||
spliced = { :++, meta, [do_splice(t, [], []), left] }
|
||||
{ :++, meta, [spliced, right] }
|
||||
end
|
||||
|
||||
defp do_splice(list) do
|
||||
do_splice(list, [], [])
|
||||
end
|
||||
|
||||
defp do_splice([{ :unquote_splicing, _meta, [expr] }|t], buffer, acc) do
|
||||
do_splice(t, [], do_splice_join(do_splice_join(expr, buffer), acc))
|
||||
end
|
||||
|
||||
defp do_splice([h|t], buffer, acc) do
|
||||
do_splice t, [do_escape(h, true)|buffer], acc
|
||||
end
|
||||
|
||||
defp do_splice([], buffer, acc), do: do_splice_join(buffer, acc)
|
||||
|
||||
defp do_splice_join([], right), do: right
|
||||
defp do_splice_join(left, []), do: left
|
||||
defp do_splice_join(left, right), do: { :++, [], [left, right] }
|
||||
|
||||
@doc false
|
||||
def escape_quoted(expr) do
|
||||
do_escape(expr, true)
|
||||
end
|
||||
|
||||
@doc %B"""
|
||||
Unescape the given chars. This is the unescaping behavior
|
||||
@@ -93,8 +152,8 @@ defmodule Macro do
|
||||
|
||||
## Examples
|
||||
|
||||
Macro.unescape_binary "example\\n"
|
||||
#=> "example\n"
|
||||
iex> Macro.unescape_binary "example\\n"
|
||||
"example\n"
|
||||
|
||||
In the example above, we pass a string with `\n` escaped
|
||||
and we return a version with it unescaped.
|
||||
@@ -105,8 +164,8 @@ defmodule Macro do
|
||||
|
||||
@doc %B"""
|
||||
Unescape the given chars according to the map given.
|
||||
Check `unescape/1` if you want to use the same map as
|
||||
Elixir single- and double-quoted strings.
|
||||
Check `unescape_binary/1` if you want to use the same map
|
||||
as Elixir single- and double-quoted strings.
|
||||
|
||||
## Map
|
||||
|
||||
@@ -152,11 +211,13 @@ defmodule Macro do
|
||||
|
||||
@doc """
|
||||
Unescape the given tokens according to the default map.
|
||||
Check `unescape/1` and `unescape/2` for more information
|
||||
about unescaping. Only tokens that are binaries are
|
||||
unescaped, all others are ignored. This function is useful
|
||||
when implementing your own sigils. Check the implementation
|
||||
of `Kernel.__b__` for examples.
|
||||
Check `unescape_binary/1` and `unescape_binary/2` for more
|
||||
information about unescaping.
|
||||
|
||||
Only tokens that are binaries are unescaped, all others are
|
||||
ignored. This function is useful when implementing your own
|
||||
sigils. Check the implementation of `Kernel.__b__`
|
||||
for examples.
|
||||
"""
|
||||
def unescape_tokens(tokens) do
|
||||
:elixir_interpolation.unescape_tokens(tokens)
|
||||
@@ -164,7 +225,7 @@ defmodule Macro do
|
||||
|
||||
@doc """
|
||||
Unescape the given tokens according to the given map.
|
||||
Check `unescape_tokens/1` and `unescaped/2` for more information.
|
||||
Check `unescape_tokens/1` and `unescape_binary/2` for more information.
|
||||
"""
|
||||
def unescape_tokens(tokens, map) do
|
||||
:elixir_interpolation.unescape_tokens(tokens, map)
|
||||
@@ -175,8 +236,8 @@ defmodule Macro do
|
||||
|
||||
## Examples
|
||||
|
||||
Macro.to_binary(quote do: foo.bar(1, 2, 3))
|
||||
#=> "foo.bar(1, 2, 3)"
|
||||
iex> Macro.to_binary(quote do: foo.bar(1, 2, 3))
|
||||
"foo.bar(1, 2, 3)"
|
||||
|
||||
"""
|
||||
def to_binary(tree)
|
||||
@@ -366,9 +427,12 @@ defmodule Macro do
|
||||
In case the expression cannot be expanded, it returns the expression itself.
|
||||
|
||||
Notice that `Macro.expand` is not recursive and it does not
|
||||
expand child expressions. For example, `!some_macro` will expand as:
|
||||
expand child expressions. In this example
|
||||
|
||||
Macro.expand(quote(do: !some_macro), __ENV__)
|
||||
|
||||
`!some_macro` will expand to something like:
|
||||
|
||||
iex> IO.puts Macro.to_binary Macro.expand(quote(do: !some_macro), __ENV__)
|
||||
case some_macro do
|
||||
false -> true
|
||||
nil -> true
|
||||
@@ -440,13 +504,15 @@ defmodule Macro do
|
||||
end
|
||||
|
||||
"""
|
||||
def expand(aliases, env)
|
||||
def expand(aliases, env) do
|
||||
expand(aliases, env, nil)
|
||||
end
|
||||
|
||||
def expand({ :__aliases__, _, _ } = original, env) do
|
||||
defp expand({ :__aliases__, _, _ } = original, env, cache) do
|
||||
case :elixir_aliases.expand(original, env.aliases, []) do
|
||||
atom when is_atom(atom) -> atom
|
||||
aliases ->
|
||||
aliases = lc alias inlist aliases, do: expand(alias, env)
|
||||
aliases = lc alias inlist aliases, do: expand(alias, env, cache)
|
||||
|
||||
case :lists.all(is_atom(&1), aliases) do
|
||||
true -> :elixir_aliases.concat(aliases)
|
||||
@@ -456,7 +522,7 @@ defmodule Macro do
|
||||
end
|
||||
|
||||
# Expand @ calls
|
||||
def expand({ :@, _, [{ name, _, args }] } = original, env) when is_atom(args) or args == [] do
|
||||
defp expand({ :@, _, [{ name, _, args }] } = original, env, _cache) when is_atom(args) or args == [] do
|
||||
case (module = env.module) && Module.open?(module) do
|
||||
true -> Module.get_attribute(module, name)
|
||||
false -> original
|
||||
@@ -464,12 +530,12 @@ defmodule Macro do
|
||||
end
|
||||
|
||||
# Expand pseudo-variables
|
||||
def expand({ :__MODULE__, _, atom }, env) when is_atom(atom), do: env.module
|
||||
def expand({ :__FILE__, _, atom }, env) when is_atom(atom), do: env.file
|
||||
def expand({ :__ENV__, _, atom }, env) when is_atom(atom), do: env
|
||||
defp expand({ :__MODULE__, _, atom }, env, _cache) when is_atom(atom), do: env.module
|
||||
defp expand({ :__FILE__, _, atom }, env, _cache) when is_atom(atom), do: env.file
|
||||
defp expand({ :__ENV__, _, atom }, env, _cache) when is_atom(atom), do: env
|
||||
|
||||
# Expand possible macro import invocation
|
||||
def expand({ atom, line, args } = original, env) when is_atom(atom) do
|
||||
defp expand({ atom, line, args } = original, env, cache) when is_atom(atom) do
|
||||
args = case is_atom(args) do
|
||||
true -> []
|
||||
false -> args
|
||||
@@ -487,7 +553,7 @@ defmodule Macro do
|
||||
end
|
||||
|
||||
expand = :elixir_dispatch.expand_import(line, { atom, length(args) }, args,
|
||||
env.module, env.function, env.requires, extra ++ env.macros, env)
|
||||
env.module, extra, to_erl_env(env, cache))
|
||||
case expand do
|
||||
{ :ok, _, expanded } -> expanded
|
||||
{ :error, _ } -> original
|
||||
@@ -496,23 +562,26 @@ defmodule Macro do
|
||||
end
|
||||
|
||||
# Expand possible macro require invocation
|
||||
def expand({ { :., _, [left, right] }, line, args } = original, env) when is_atom(right) do
|
||||
defp expand({ { :., _, [left, right] }, line, args } = original, env, cache) when is_atom(right) do
|
||||
receiver = expand(left, env)
|
||||
|
||||
case is_atom(receiver) and not is_partial?(args) do
|
||||
false -> original
|
||||
true ->
|
||||
expand = :elixir_dispatch.expand_require(line, receiver, { right, length(args) },
|
||||
args, env.module, env.function, env.requires, env)
|
||||
args, env.module, to_erl_env(env, cache))
|
||||
case expand do
|
||||
{ :ok, expanded } -> expanded
|
||||
{ :error, _ } -> original
|
||||
{ :ok, _receiver, expanded } -> expanded
|
||||
{ :error, _ } -> original
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Anything else is just returned
|
||||
def expand(other, _env), do: other
|
||||
defp expand(other, _env, _cache), do: other
|
||||
|
||||
defp to_erl_env(env, nil), do: :elixir_scope.to_erl_env(env)
|
||||
defp to_erl_env(_env, cache), do: cache
|
||||
|
||||
## Helpers
|
||||
|
||||
@@ -530,16 +599,16 @@ defmodule Macro do
|
||||
do_safe_term(terms) || :ok
|
||||
end
|
||||
|
||||
def do_safe_term({ local, _, terms }) when local in [:{}, :[], :__aliases__] do
|
||||
defp do_safe_term({ local, _, terms }) when local in [:{}, :[], :__aliases__] do
|
||||
do_safe_term(terms)
|
||||
end
|
||||
|
||||
def do_safe_term({ unary, _, [term] }) when unary in [:+, :-] do
|
||||
defp do_safe_term({ unary, _, [term] }) when unary in [:+, :-] do
|
||||
do_safe_term(term)
|
||||
end
|
||||
|
||||
def do_safe_term({ left, right }), do: do_safe_term(left) || do_safe_term(right)
|
||||
def do_safe_term(terms) when is_list(terms), do: Enum.find_value(terms, do_safe_term(&1))
|
||||
def do_safe_term(terms) when is_tuple(terms), do: { :unsafe, terms }
|
||||
def do_safe_term(_), do: nil
|
||||
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
|
||||
|
||||
@@ -25,7 +25,7 @@ defmodule Macro.Env do
|
||||
@type file :: binary
|
||||
@type line :: non_neg_integer
|
||||
@type aliases :: [{ module, module }]
|
||||
@type context :: :assign | :guard | nil
|
||||
@type context :: :match | :guard | nil
|
||||
@type requires :: [module]
|
||||
@type functions :: [{ module, [name_arity] }]
|
||||
@type macros :: [{ module, [name_arity] }]
|
||||
@@ -58,7 +58,7 @@ defmodule Macro.Env do
|
||||
Returns wether the compilation environment is currently
|
||||
inside a match clause.
|
||||
"""
|
||||
def in_match?(record), do: context(record) == :assign
|
||||
def in_match?(record), do: context(record) == :match
|
||||
|
||||
@doc """
|
||||
Returns the environment stacktrace.
|
||||
|
||||
+24
-15
@@ -116,9 +116,12 @@ defmodule Module do
|
||||
|
||||
## Examples
|
||||
|
||||
Module.concat [Foo, Bar] #=> Foo.Bar
|
||||
Module.concat [Foo, "Bar"] #=> Foo.Bar
|
||||
Module.concat [Foo, 'Bar'] #=> Foo.Bar
|
||||
iex> Module.concat [Foo, Bar]
|
||||
Foo.Bar
|
||||
iex> Module.concat [Foo, "Bar"]
|
||||
Foo.Bar
|
||||
iex> Module.concat [Foo, 'Bar']
|
||||
Foo.Bar
|
||||
|
||||
"""
|
||||
def concat(list) when is_list(list) do
|
||||
@@ -131,9 +134,12 @@ defmodule Module do
|
||||
|
||||
## Examples
|
||||
|
||||
Module.concat Foo, Bar #=> Foo.Bar
|
||||
Module.concat Foo, "Bar" #=> Foo.Bar
|
||||
Module.concat Foo, 'Bar' #=> Foo.Bar
|
||||
iex> Module.concat Foo, Bar
|
||||
Foo.Bar
|
||||
iex> Module.concat Foo, "Bar"
|
||||
Foo.Bar
|
||||
iex> Module.concat Foo, 'Bar'
|
||||
Foo.Bar
|
||||
|
||||
"""
|
||||
def concat(left, right) do
|
||||
@@ -148,11 +154,11 @@ defmodule Module do
|
||||
|
||||
## Examples
|
||||
|
||||
Module.safe_concat [Unknown, Module]
|
||||
#=> ArgumentError
|
||||
iex> Module.safe_concat [Unknown, Module]
|
||||
** (ArgumentError) argument error
|
||||
|
||||
Module.safe_concat [List, Chars]
|
||||
#=> List.Chars
|
||||
iex> Module.safe_concat [List, Chars]
|
||||
List.Chars
|
||||
|
||||
"""
|
||||
def safe_concat(list) when is_list(list) do
|
||||
@@ -167,11 +173,11 @@ defmodule Module do
|
||||
|
||||
## Examples
|
||||
|
||||
Module.safe_concat Unknown, Module
|
||||
#=> ArgumentError
|
||||
iex> Module.safe_concat Unknown, Module
|
||||
** (ArgumentError) argument error
|
||||
|
||||
Module.safe_concat List, Chars
|
||||
#=> List.Chars
|
||||
iex> Module.safe_concat List, Chars
|
||||
List.Chars
|
||||
|
||||
"""
|
||||
def safe_concat(left, right) do
|
||||
@@ -474,7 +480,8 @@ defmodule Module do
|
||||
When registering an attribute, two options can be given:
|
||||
|
||||
* `:accumulate` - Several calls to the same attribute will
|
||||
accumulate instead of override the previous one;
|
||||
accumulate instead of override the previous one. New attributes
|
||||
are always added to the top of the accumulated list.
|
||||
|
||||
* `:persist` - The attribute will be persisted in the Erlang
|
||||
Abstract Format. Useful when interfacing with Erlang libraries.
|
||||
@@ -527,6 +534,8 @@ defmodule Module do
|
||||
@doc """
|
||||
Convert a module name to binary without the Elixir prefix.
|
||||
"""
|
||||
def to_binary(Elixir), do: "Elixir"
|
||||
|
||||
def to_binary(module) do
|
||||
"Elixir-" <> rest = Binary.Chars.to_binary(module)
|
||||
bc <<r>> inbits rest, do: <<to_dot(r)>>
|
||||
|
||||
@@ -5,21 +5,21 @@ defmodule OptionParser do
|
||||
|
||||
## Examples
|
||||
|
||||
OptionParser.parse(["--debug"])
|
||||
#=> { [debug: true], [] }
|
||||
iex> OptionParser.parse(["--debug"])
|
||||
{ [debug: true], [] }
|
||||
|
||||
OptionParser.parse(["--source", "lib"])
|
||||
#=> { [source: "lib"], [] }
|
||||
iex> OptionParser.parse(["--source", "lib"])
|
||||
{ [source: "lib"], [] }
|
||||
|
||||
OptionParser.parse(["--source", "lib", "test/enum_test.exs", "--verbose"])
|
||||
#=> { [source: "lib", verbose: true], ["test/enum_test.exs"] }
|
||||
iex> OptionParser.parse(["--source", "lib", "test/enum_test.exs", "--verbose"])
|
||||
{ [source: "lib", verbose: true], ["test/enum_test.exs"] }
|
||||
|
||||
## Aliases
|
||||
|
||||
A set of aliases can be given as second argument:
|
||||
|
||||
OptionParser.parse(["-d"], aliases: [d: :debug])
|
||||
#=> { [debug: true], [] }
|
||||
iex> OptionParser.parse(["-d"], aliases: [d: :debug])
|
||||
{ [debug: true], [] }
|
||||
|
||||
## Switches
|
||||
|
||||
@@ -39,19 +39,19 @@ defmodule OptionParser do
|
||||
|
||||
Examples:
|
||||
|
||||
OptionParser.parse(["--unlock path/to/file"], switches: [unlock: :boolean])
|
||||
#=> { [unlock: true], ["path/to/file"] }
|
||||
iex> OptionParser.parse(["--unlock", "path/to/file"], switches: [unlock: :boolean])
|
||||
{ [unlock: true], ["path/to/file"] }
|
||||
|
||||
OptionParser.parse(["--unlock false path/to/file"], switches: [unlock: :boolean])
|
||||
#=> { [unlock: false], ["path/to/file"] }
|
||||
iex> OptionParser.parse(["--unlock", "false", "path/to/file"], switches: [unlock: :boolean])
|
||||
{ [unlock: false], ["path/to/file"] }
|
||||
|
||||
## Negation switches
|
||||
|
||||
Any switches starting with `--no-` are always considered to be
|
||||
booleans and never parse the next value:
|
||||
|
||||
OptionParser.parse(["--no-op path/to/file"])
|
||||
#=> { [no_op: true], ["path/to/file"] }
|
||||
iex> OptionParser.parse(["--no-op", "path/to/file"])
|
||||
{ [no_op: true], ["path/to/file"] }
|
||||
|
||||
"""
|
||||
def parse(argv, opts // []) when is_list(argv) and is_list(opts) do
|
||||
@@ -66,8 +66,8 @@ defmodule OptionParser do
|
||||
|
||||
## Example
|
||||
|
||||
OptionParser.parse_head(["--source", "lib", "test/enum_test.exs", "--verbose"])
|
||||
#=> { [source: "lib"], ["test/enum_test.exs", "--verbose"] }
|
||||
iex> OptionParser.parse_head(["--source", "lib", "test/enum_test.exs", "--verbose"])
|
||||
{ [source: "lib"], ["test/enum_test.exs", "--verbose"] }
|
||||
|
||||
"""
|
||||
def parse_head(argv, opts // []) when is_list(argv) and is_list(opts) do
|
||||
|
||||
+74
-67
@@ -14,6 +14,9 @@ defmodule Path do
|
||||
|
||||
alias :filename, as: FN
|
||||
|
||||
@type t :: char_list | atom | binary
|
||||
@type r :: char_list | binary
|
||||
|
||||
@doc """
|
||||
Converts the given filename and returns an absolute name.
|
||||
Differently from `Path.expand/1`, no attempt is made to
|
||||
@@ -49,11 +52,11 @@ defmodule Path do
|
||||
|
||||
## Examples
|
||||
|
||||
Path.absname("foo", "bar")
|
||||
#=> "bar/foo"
|
||||
iex> Path.absname("foo", "bar")
|
||||
"bar/foo"
|
||||
|
||||
Path.absname("../x", "bar")
|
||||
#=> "bar/../x"
|
||||
iex> Path.absname("../x", "bar")
|
||||
"bar/../x"
|
||||
|
||||
"""
|
||||
def absname(path, relative_to) do
|
||||
@@ -66,11 +69,12 @@ defmodule Path do
|
||||
|
||||
## Examples
|
||||
|
||||
Path.expand("/foo/bar/../bar") == "/foo/bar"
|
||||
iex> Path.expand("/foo/bar/../bar")
|
||||
"/foo/bar"
|
||||
|
||||
"""
|
||||
def expand(path) do
|
||||
normalize FN.absname(path, get_cwd(path))
|
||||
normalize FN.absname(expand_home(path), get_cwd(path))
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -80,12 +84,14 @@ defmodule Path do
|
||||
|
||||
## Examples
|
||||
|
||||
Path.expand("foo/bar/../bar", "/baz") == "/baz/foo/bar"
|
||||
Path.expand("/foo/bar/../bar", "/baz") == "/foo/bar"
|
||||
iex> Path.expand("foo/bar/../bar", "/baz")
|
||||
"/baz/foo/bar"
|
||||
iex> Path.expand("/foo/bar/../bar", "/baz")
|
||||
"/foo/bar"
|
||||
|
||||
"""
|
||||
def expand(path, relative_to) do
|
||||
normalize FN.absname(FN.absname(path, relative_to), get_cwd(path))
|
||||
normalize FN.absname(FN.absname(expand_home(path), relative_to), get_cwd(path))
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -188,9 +194,12 @@ defmodule Path do
|
||||
|
||||
## Examples
|
||||
|
||||
Path.relative_to("/usr/local/foo", "/usr/local") #=> "foo"
|
||||
Path.relative_to("/usr/local/foo", "/") #=> "foo"
|
||||
Path.relative_to("/usr/local/foo", "/etc") #=> "/usr/local/foo"
|
||||
iex> Path.relative_to("/usr/local/foo", "/usr/local")
|
||||
"foo"
|
||||
iex> Path.relative_to("/usr/local/foo", "/")
|
||||
"usr/local/foo"
|
||||
iex> Path.relative_to("/usr/local/foo", "/etc")
|
||||
"/usr/local/foo"
|
||||
|
||||
"""
|
||||
def relative_to(path, from) when is_list(path) and is_binary(from) do
|
||||
@@ -224,14 +233,14 @@ defmodule Path do
|
||||
|
||||
## Examples
|
||||
|
||||
Path.basename("foo")
|
||||
#=> "foo"
|
||||
iex> Path.basename("foo")
|
||||
"foo"
|
||||
|
||||
Path.basename("foo/bar")
|
||||
#=> "bar"
|
||||
iex> Path.basename("foo/bar")
|
||||
"bar"
|
||||
|
||||
Path.basename("/")
|
||||
#=> ""
|
||||
iex> Path.basename("/")
|
||||
""
|
||||
|
||||
"""
|
||||
def basename(path) do
|
||||
@@ -245,12 +254,12 @@ defmodule Path do
|
||||
|
||||
## Examples
|
||||
|
||||
Path.basename("~/foo/bar.ex", ".ex")
|
||||
#=> "bar"
|
||||
Path.basename("~/foo/bar.exs", ".ex")
|
||||
#=> "bar.exs"
|
||||
Path.basename("~/foo/bar.old.ex", ".ex")
|
||||
#=> "bar.old"
|
||||
iex> Path.basename("~/foo/bar.ex", ".ex")
|
||||
"bar"
|
||||
iex> Path.basename("~/foo/bar.exs", ".ex")
|
||||
"bar.exs"
|
||||
iex> Path.basename("~/foo/bar.old.ex", ".ex")
|
||||
"bar.old"
|
||||
|
||||
"""
|
||||
def basename(path, extension) do
|
||||
@@ -275,10 +284,10 @@ defmodule Path do
|
||||
|
||||
## Examples
|
||||
|
||||
Path.extname("foo.erl")
|
||||
#=> ".erl"
|
||||
Path.extname("~/foo/bar")
|
||||
#=> ""
|
||||
iex> Path.extname("foo.erl")
|
||||
".erl"
|
||||
iex> Path.extname("~/foo/bar")
|
||||
""
|
||||
|
||||
"""
|
||||
def extname(path) do
|
||||
@@ -290,10 +299,10 @@ defmodule Path do
|
||||
|
||||
## Examples
|
||||
|
||||
Path.rootname("/foo/bar")
|
||||
#=> "/foo/bar"
|
||||
Path.rootname("/foo/bar.ex")
|
||||
#=> "/foo/bar"
|
||||
iex> Path.rootname("/foo/bar")
|
||||
"/foo/bar"
|
||||
iex> Path.rootname("/foo/bar.ex")
|
||||
"/foo/bar"
|
||||
|
||||
"""
|
||||
def rootname(path) do
|
||||
@@ -306,10 +315,10 @@ defmodule Path do
|
||||
|
||||
## Examples
|
||||
|
||||
Path.rootname("/foo/bar.erl", ".erl")
|
||||
#=> "/foo/bar"
|
||||
Path.rootname("/foo/bar.erl", ".ex")
|
||||
#=> "/foo/bar.erl"
|
||||
iex> Path.rootname("/foo/bar.erl", ".erl")
|
||||
"/foo/bar"
|
||||
iex> Path.rootname("/foo/bar.erl", ".ex")
|
||||
"/foo/bar.erl"
|
||||
|
||||
"""
|
||||
def rootname(path, extension) do
|
||||
@@ -322,12 +331,12 @@ defmodule Path do
|
||||
|
||||
## Examples
|
||||
|
||||
Path.join(["~", "foo"])
|
||||
#=> "~/foo"
|
||||
Path.join(["foo"])
|
||||
#=> "foo"
|
||||
Path.join(["/", "foo", "bar"])
|
||||
#=> "/foo/bar"
|
||||
iex> Path.join(["~", "foo"])
|
||||
"~/foo"
|
||||
iex> Path.join(["foo"])
|
||||
"foo"
|
||||
iex> Path.join(["/", "foo", "bar"])
|
||||
"/foo/bar"
|
||||
|
||||
"""
|
||||
def join([name1, name2|rest]), do:
|
||||
@@ -342,8 +351,8 @@ defmodule Path do
|
||||
|
||||
## Examples
|
||||
|
||||
Path.join("foo", "bar")
|
||||
#=> "foo/bar"
|
||||
iex> Path.join("foo", "bar")
|
||||
"foo/bar"
|
||||
|
||||
"""
|
||||
def join(left, right) when is_binary(left) and is_binary(right), do:
|
||||
@@ -405,12 +414,12 @@ defmodule Path do
|
||||
|
||||
## Examples
|
||||
|
||||
Path.split("")
|
||||
#=> ["/"]
|
||||
Path.split("foo")
|
||||
#=> ["foo"]
|
||||
Path.split("/foo/bar")
|
||||
#=> ["/", "foo", "bar"]
|
||||
iex> Path.split("")
|
||||
["/"]
|
||||
iex> Path.split("foo")
|
||||
["foo"]
|
||||
iex> Path.split("/foo/bar")
|
||||
["/", "foo", "bar"]
|
||||
|
||||
"""
|
||||
def split(path) do
|
||||
@@ -484,33 +493,31 @@ defmodule Path do
|
||||
|
||||
# Normalize the given path by expanding "..", "." and "~".
|
||||
|
||||
defp normalize(path), do: do_normalize(FN.split(path))
|
||||
|
||||
defp do_normalize(["~"|t]) do
|
||||
do_normalize t, [System.user_home!]
|
||||
defp expand_home(<<?~, rest :: binary>>) do
|
||||
System.user_home! <> rest
|
||||
end
|
||||
|
||||
defp do_normalize(['~'|t]) do
|
||||
do_normalize t, [System.user_home! |> binary_to_filename_string]
|
||||
defp expand_home('~' ++ rest) do
|
||||
(System.user_home! |> binary_to_filename_string) ++ rest
|
||||
end
|
||||
|
||||
defp do_normalize(t) do
|
||||
do_normalize t, []
|
||||
defp expand_home(other), do: other
|
||||
|
||||
defp normalize(path), do: normalize(FN.split(path), [])
|
||||
|
||||
defp normalize([top|t], [_|acc]) when top in ["..", '..'] do
|
||||
normalize t, acc
|
||||
end
|
||||
|
||||
defp do_normalize([top|t], [_|acc]) when top in ["..", '..'] do
|
||||
do_normalize t, acc
|
||||
defp normalize([top|t], acc) when top in [".", '.'] do
|
||||
normalize t, acc
|
||||
end
|
||||
|
||||
defp do_normalize([top|t], acc) when top in [".", '.'] do
|
||||
do_normalize t, acc
|
||||
defp normalize([h|t], acc) do
|
||||
normalize t, [h|acc]
|
||||
end
|
||||
|
||||
defp do_normalize([h|t], acc) do
|
||||
do_normalize t, [h|acc]
|
||||
end
|
||||
|
||||
defp do_normalize([], acc) do
|
||||
defp normalize([], acc) do
|
||||
join Enum.reverse(acc)
|
||||
end
|
||||
end
|
||||
+56
-18
@@ -31,15 +31,41 @@ defmodule Record do
|
||||
@moduledoc false
|
||||
import Record.DSL
|
||||
|
||||
@record_fields []
|
||||
@record_types []
|
||||
|
||||
values = unquote(values)
|
||||
opts = unquote(opts)
|
||||
Record.deffunctions(values, __ENV__)
|
||||
unquote(block)
|
||||
Record.deftypes(values, @record_type, __ENV__)
|
||||
value = unquote(block)
|
||||
Record.deftypes(values, @record_types, __ENV__)
|
||||
value
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Import public record definition as a set of private macros (as defined by defrecordp/2)
|
||||
|
||||
## Usage
|
||||
|
||||
Record.import Record.Module, as: macro_name
|
||||
|
||||
## Example
|
||||
|
||||
defmodule Test do
|
||||
Record.import File.Stat, as: :file_stat
|
||||
|
||||
def size(file_stat(size: size)), do: size
|
||||
end
|
||||
|
||||
"""
|
||||
defmacro import(module, as: name) do
|
||||
quote do
|
||||
Record.defmacros(unquote(name), unquote(module).__record__(:fields), __ENV__, unquote(module))
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Main entry point for private records definition. It defines
|
||||
a set of macros with the given `name` and the fields specified
|
||||
@@ -80,7 +106,7 @@ defmodule Record do
|
||||
switch_recorder()
|
||||
]
|
||||
|
||||
contents = [quote(do: @__record__ unquote(escaped))|contents]
|
||||
contents = [quote(do: @record_fields unquote(escaped))|contents]
|
||||
|
||||
# Special case for bootstraping purposes
|
||||
if env == Macro.Env do
|
||||
@@ -124,31 +150,32 @@ defmodule Record do
|
||||
end
|
||||
|
||||
"""
|
||||
def defmacros(name, values, env) do
|
||||
def defmacros(name, values, env, tag // nil) do
|
||||
escaped = lc value inlist values do
|
||||
{ key, value } = convert_value(value)
|
||||
{ key, Macro.escape(value) }
|
||||
end
|
||||
|
||||
contents = quote do
|
||||
|
||||
defmacrop unquote(name)() do
|
||||
Record.access(__MODULE__, unquote(escaped), [], __CALLER__)
|
||||
Record.access(unquote(tag) || __MODULE__, unquote(escaped), [], __CALLER__)
|
||||
end
|
||||
|
||||
defmacrop unquote(name)(record) when is_tuple(record) do
|
||||
Record.to_keywords(__MODULE__, unquote(escaped), record)
|
||||
Record.to_keywords(unquote(tag) || __MODULE__, unquote(escaped), record)
|
||||
end
|
||||
|
||||
defmacrop unquote(name)(args) do
|
||||
Record.access(__MODULE__, unquote(escaped), args, __CALLER__)
|
||||
Record.access(unquote(tag) || __MODULE__, unquote(escaped), args, __CALLER__)
|
||||
end
|
||||
|
||||
defmacrop unquote(name)(record, key) when is_atom(key) do
|
||||
Record.get(__MODULE__, unquote(escaped), record, key)
|
||||
Record.get(unquote(tag) || __MODULE__, unquote(escaped), record, key)
|
||||
end
|
||||
|
||||
defmacrop unquote(name)(record, args) do
|
||||
Record.dispatch(__MODULE__, unquote(escaped), record, args, __CALLER__)
|
||||
Record.dispatch(unquote(tag) || __MODULE__, unquote(escaped), record, args, __CALLER__)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -441,19 +468,19 @@ defmodule Record do
|
||||
# end
|
||||
#
|
||||
# def :atime.(value, record) do
|
||||
# setelem(record, 1, value)
|
||||
# set_elem(record, 1, value)
|
||||
# end
|
||||
#
|
||||
# def :mtime.(record) do
|
||||
# setelem(record, 2, value)
|
||||
# set_elem(record, 2, value)
|
||||
# end
|
||||
#
|
||||
# def :atime.(callback, record) do
|
||||
# setelem(record, 1, callback.(elem(record, 1)))
|
||||
# set_elem(record, 1, callback.(elem(record, 1)))
|
||||
# end
|
||||
#
|
||||
# def :mtime.(callback, record) do
|
||||
# setelem(record, 2, callback.(elem(record, 2)))
|
||||
# set_elem(record, 2, callback.(elem(record, 2)))
|
||||
# end
|
||||
#
|
||||
defp accessors([{ :__exception__, _ }|t], 1) do
|
||||
@@ -529,21 +556,32 @@ defmodule Record do
|
||||
## Types/specs generation
|
||||
|
||||
defp core_specs(values) do
|
||||
types = lc { _, _, spec } inlist values, do: spec
|
||||
types = lc { _, _, spec } inlist values, do: spec
|
||||
options = if values == [], do: [], else: [options_specs(values)]
|
||||
|
||||
quote do
|
||||
unless Kernel.Typespec.defines_type?(__MODULE__, :t, 0) do
|
||||
@type t :: { __MODULE__, unquote_splicing(types) }
|
||||
end
|
||||
|
||||
unless Kernel.Typespec.defines_type?(__MODULE__, :options, 0) do
|
||||
@type options :: unquote(options)
|
||||
end
|
||||
|
||||
@spec new :: t
|
||||
@spec new(Keyword.t | tuple) :: t
|
||||
@spec to_keywords(t) :: Keyword.t
|
||||
@spec update(Keyword.t, t) :: t
|
||||
@spec new(options | tuple) :: t
|
||||
@spec to_keywords(t) :: options
|
||||
@spec update(options, t) :: t
|
||||
@spec __index__(atom) :: non_neg_integer | nil
|
||||
end
|
||||
end
|
||||
|
||||
defp options_specs([{ k, _, v }|t]) do
|
||||
:lists.foldl fn { k, _, v }, acc ->
|
||||
{ :|, [], [{ k, v }, acc] }
|
||||
end, { k, v }, t
|
||||
end
|
||||
|
||||
defp accessor_specs([{ :__exception__, _, _ }|t], 1, acc) do
|
||||
accessor_specs(t, 2, acc)
|
||||
end
|
||||
@@ -607,7 +645,7 @@ defmodule Record.DSL do
|
||||
escaped = lc { k, v } inlist opts, do: { k, Macro.escape(v) }
|
||||
|
||||
quote do
|
||||
@record_type Keyword.merge(@record_type || [], unquote(escaped))
|
||||
@record_types Keyword.merge(@record_types || [], unquote(escaped))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
+41
-22
@@ -86,8 +86,10 @@ defmodule Regex do
|
||||
|
||||
## Examples
|
||||
|
||||
Regex.index %r/c(d)/, "abcd" #=> 3
|
||||
Regex.index %r/e/, "abcd" #=> nil
|
||||
iex> Regex.index %r/c(d)/, "abcd"
|
||||
2
|
||||
iex> Regex.index %r/e/, "abcd"
|
||||
nil
|
||||
|
||||
"""
|
||||
def index(regex(re_pattern: compiled), string) do
|
||||
@@ -102,8 +104,10 @@ defmodule Regex do
|
||||
|
||||
## Examples
|
||||
|
||||
Regex.match? %r/foo/, "foo" #=> true
|
||||
Regex.match? %r/foo/, "bar" #=> false
|
||||
iex> Regex.match? %r/foo/, "foo"
|
||||
true
|
||||
iex> Regex.match? %r/foo/, "bar"
|
||||
false
|
||||
|
||||
"""
|
||||
def match?(regex(re_pattern: compiled), string) do
|
||||
@@ -116,8 +120,10 @@ defmodule Regex do
|
||||
|
||||
## Examples
|
||||
|
||||
Regex.run %r/c(d)/, "abcd" #=> ["cd", "d"]
|
||||
Regex.run %r/e/, "abcd" #=> nil
|
||||
iex> Regex.run %r/c(d)/, "abcd"
|
||||
["cd", "d"]
|
||||
iex> Regex.run %r/e/, "abcd"
|
||||
nil
|
||||
|
||||
"""
|
||||
def run(regex, string, options // [])
|
||||
@@ -127,7 +133,7 @@ defmodule Regex do
|
||||
|
||||
captures =
|
||||
case Keyword.get(options, :capture, :all) do
|
||||
:groups -> groups || raise "regex was not compiled with g"
|
||||
:groups -> groups || raise ArgumentError, message: "regex was not compiled with g"
|
||||
others -> others
|
||||
end
|
||||
|
||||
@@ -139,10 +145,12 @@ defmodule Regex do
|
||||
|
||||
@doc """
|
||||
Returns the given captures as a list of tuples.
|
||||
Requires the regex to be compiled with the groups option.
|
||||
|
||||
## Examples
|
||||
|
||||
Regex.captures %r/c(?<foo>d)/g, "abcd" #=> [{:foo, ["d"]}]
|
||||
iex> Regex.captures %r/c(?<foo>d)/g, "abcd"
|
||||
[foo: "d"]
|
||||
|
||||
"""
|
||||
def captures(regex(groups: groups) = regex, string, options // []) do
|
||||
@@ -150,7 +158,7 @@ defmodule Regex do
|
||||
captures = if groups do
|
||||
Enum.sort(groups)
|
||||
else
|
||||
raise "Regex was not compiled with g"
|
||||
raise ArgumentError, message: "regex was not compiled with g"
|
||||
end
|
||||
options = Keyword.put(options, :capture, captures)
|
||||
end
|
||||
@@ -159,7 +167,7 @@ defmodule Regex do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns the underlying re_pattern in the regular expression.
|
||||
Returns the underlying `re_pattern` in the regular expression.
|
||||
"""
|
||||
def re_pattern(regex(re_pattern: compiled)) do
|
||||
compiled
|
||||
@@ -170,7 +178,8 @@ defmodule Regex do
|
||||
|
||||
## Examples
|
||||
|
||||
Regex.source %r(foo) #=> "foo"
|
||||
iex> Regex.source %r(foo)
|
||||
"foo"
|
||||
|
||||
"""
|
||||
def source(regex(source: source)) do
|
||||
@@ -178,11 +187,12 @@ defmodule Regex do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns the regex options as a list.
|
||||
Returns the regex options as a string.
|
||||
|
||||
## Examples
|
||||
|
||||
Regex.opts %r(foo)m #=> 'm'
|
||||
iex> Regex.opts %r(foo)m
|
||||
"m"
|
||||
|
||||
"""
|
||||
def opts(regex(options: options)) do
|
||||
@@ -194,7 +204,8 @@ defmodule Regex do
|
||||
|
||||
## Examples
|
||||
|
||||
Regex.groups %r/(?<foo>foo)/g #=> ["foo"]
|
||||
iex> Regex.groups %r/(?<foo>foo)/g
|
||||
[:foo]
|
||||
|
||||
"""
|
||||
def groups(regex(groups: groups)) do
|
||||
@@ -209,9 +220,12 @@ defmodule Regex do
|
||||
|
||||
## Examples
|
||||
|
||||
Regex.scan %r/c(d|e)/, "abcd abce" #=> [["d"], ["e"]]
|
||||
Regex.scan %r/c(?:d|e)/, "abcd abce" #=> ["cd", "ce"]
|
||||
Regex.scan %r/e/, "abcd" #=> []
|
||||
iex> Regex.scan %r/c(d|e)/, "abcd abce"
|
||||
[["d"], ["e"]]
|
||||
iex> Regex.scan %r/c(?:d|e)/, "abcd abce"
|
||||
["cd", "ce"]
|
||||
iex> Regex.scan %r/e/, "abcd"
|
||||
[]
|
||||
|
||||
"""
|
||||
def scan(regex, string, options // [])
|
||||
@@ -256,11 +270,16 @@ defmodule Regex do
|
||||
|
||||
## Examples
|
||||
|
||||
Regex.replace(%r/d/, "abc", "d") #=> "abc"
|
||||
Regex.replace(%r/b/, "abc", "d") #=> "adc"
|
||||
Regex.replace(%r/b/, "abc", "[&]") #=> "a[b]c"
|
||||
Regex.replace(%r/b/, "abc", "[\\&]") #=> "a[&]c"
|
||||
Regex.replace(%r/(b)/, "abc", "[\\1]") #=> "a[b]c"
|
||||
iex> Regex.replace(%r/d/, "abc", "d")
|
||||
"abc"
|
||||
iex> Regex.replace(%r/b/, "abc", "d")
|
||||
"adc"
|
||||
iex> Regex.replace(%r/b/, "abc", "[&]")
|
||||
"a[b]c"
|
||||
iex> Regex.replace(%r/b/, "abc", "[\\&]")
|
||||
"a[&]c"
|
||||
iex> Regex.replace(%r/(b)/, "abc", "[\\1]")
|
||||
"a[b]c"
|
||||
|
||||
"""
|
||||
def replace(regex(re_pattern: compiled), string, replacement, options // []) do
|
||||
|
||||
+177
-87
@@ -1,17 +1,38 @@
|
||||
defmodule String do
|
||||
@moduledoc %B"""
|
||||
A string in Elixir is a UTF-8 encoded binary.
|
||||
A String in Elixir is a UTF-8 encoded binary.
|
||||
|
||||
## String and binary operations
|
||||
|
||||
The functions in this module act according to the
|
||||
Unicode Standard, version 6.2.0. A codepoint is a
|
||||
Unicode Character, which may be represented by one
|
||||
or more bytes. For example, the character "é" is
|
||||
represented with two bytes:
|
||||
Unicode Standard, version 6.2.0. For example,
|
||||
`titlecase`, `downcase`, `strip` are provided by this
|
||||
module.
|
||||
|
||||
string = "é"
|
||||
#=> "é"
|
||||
size(string)
|
||||
#=> 2
|
||||
Besides this module, Elixir provides more low-level
|
||||
operations that works directly with binaries. Some
|
||||
of those can be found in the `Kernel` module, as:
|
||||
|
||||
* `binary_part/2` and `binary_part/3` - retrieves part of the binary
|
||||
* `bit_size/1` and `byte_size/1` - size related functions
|
||||
* `is_bitstring/1` and `is_binary/1` - type checking function
|
||||
* Plus a bunch of conversion functions, like `binary_to_atom/2`,
|
||||
`binary_to_integer/2`, `binary_to_term/1` and their opposite
|
||||
like `integer_to_binary/2`
|
||||
|
||||
Finally, [the `:binary` module](http://erlang.org/doc/man/binary.html)
|
||||
provides a couple other functions that works on the byte level.
|
||||
|
||||
## Codepoints and graphemes
|
||||
|
||||
As per the Unicode Standard, a codepoint is an Unicode
|
||||
Character, which may be represented by one or more bytes.
|
||||
For example, the character "é" is represented with two
|
||||
bytes:
|
||||
|
||||
iex> string = "é"
|
||||
...> byte_size(string)
|
||||
2
|
||||
|
||||
Furthermore, this module also presents the concept of
|
||||
graphemes, which are multiple characters that may be
|
||||
@@ -19,10 +40,9 @@ defmodule String do
|
||||
the same "é" character written above could be represented
|
||||
by the letter "e" followed by the accent ́:
|
||||
|
||||
string = "\x{0065}\x{0301}"
|
||||
#=> "é"
|
||||
size(string)
|
||||
#=> 3
|
||||
iex> string = "\x{0065}\x{0301}"
|
||||
...> byte_size(string)
|
||||
3
|
||||
|
||||
Although the example above is made of two characters, it is
|
||||
perceived by users as one.
|
||||
@@ -40,26 +60,30 @@ defmodule String do
|
||||
## Integer codepoints
|
||||
|
||||
Although codepoints could be represented as integers, this
|
||||
module represents all codepoints as binaries. For example:
|
||||
module represents all codepoints as strings. For example:
|
||||
|
||||
String.codepoints "josé" #=> ["j", "o", "s", "é"]
|
||||
iex> String.codepoints "josé"
|
||||
["j", "o", "s", "é"]
|
||||
|
||||
There are a couple of ways to retrieve a character integer
|
||||
codepoint. One may use the `?` special macro:
|
||||
|
||||
?j #=> 106
|
||||
?é #=> 233
|
||||
iex> ?j
|
||||
106
|
||||
iex> ?é
|
||||
233
|
||||
|
||||
Or also via pattern matching:
|
||||
|
||||
<< eacute :: utf8 >> = "é"
|
||||
eacute #=> 233
|
||||
iex> << eacute :: utf8 >> = "é"
|
||||
...> eacute
|
||||
233
|
||||
|
||||
As we have seen above, codepoints can be inserted into
|
||||
a string by their hexadecimal code:
|
||||
|
||||
string = "jos\x{0065}\x{0301}"
|
||||
#=> "josé"
|
||||
"jos\x{0065}\x{0301}" #=>
|
||||
"josé"
|
||||
|
||||
## Self-synchronization
|
||||
|
||||
@@ -72,8 +96,10 @@ defmodule String do
|
||||
characters. For example, `String.length` is going to return
|
||||
a correct result even if an invalid codepoint is fed into it.
|
||||
|
||||
In the future, bang version of such functions may be
|
||||
provided which will rather raise on such invalid data.
|
||||
In other words, this module expects invalid data to be detected
|
||||
when retrieving data from the external source. For example, a
|
||||
driver that reads strings from a database will be the one
|
||||
responsible to check the validity of the encoding.
|
||||
"""
|
||||
|
||||
@type t :: binary
|
||||
@@ -86,7 +112,8 @@ defmodule String do
|
||||
|
||||
## Examples
|
||||
|
||||
String.printable?("abc") #=> true
|
||||
iex> String.printable?("abc")
|
||||
true
|
||||
|
||||
"""
|
||||
@spec printable?(t) :: boolean
|
||||
@@ -119,29 +146,44 @@ defmodule String do
|
||||
The string is split into as many parts as possible by
|
||||
default, unless the `global` option is set to false.
|
||||
If a pattern is not specified, the string is split on
|
||||
whitespace occurrences.
|
||||
Unicode whitespace occurrences with leading and trailing
|
||||
whitespace ignored.
|
||||
|
||||
It returns a list with the original string if the pattern
|
||||
can't be matched.
|
||||
|
||||
## Examples
|
||||
|
||||
String.split("a,b,c", ",") #=> ["a", "b", "c"]
|
||||
String.split("a,b,c", ",", global: false) #=> ["a", "b,c"]
|
||||
iex> String.split("foo bar")
|
||||
["foo", "bar"]
|
||||
iex> String.split("foo" <> <<194,133>> <> "bar")
|
||||
["foo", "bar"]
|
||||
iex> String.split(" foo bar ")
|
||||
["foo", "bar"]
|
||||
|
||||
String.split("foo bar") #=> ["foo", "bar"]
|
||||
String.split("1,2 3,4", [" ", ","]) #=> ["1", "2", "3", "4"]
|
||||
iex> String.split("a,b,c", ",")
|
||||
["a", "b", "c"]
|
||||
iex> String.split("a,b,c", ",", global: false)
|
||||
["a", "b,c"]
|
||||
|
||||
String.split("a,b,c", %r{,}) #=> ["a", "b", "c"]
|
||||
String.split("a,b,c", %r{,}, global: false) #=> ["a", "b,c"]
|
||||
String.split("a,b", %r{\.}) #=> ["a,b"]
|
||||
iex> String.split("1,2 3,4", [" ", ","])
|
||||
["1", "2", "3", "4"]
|
||||
|
||||
iex> String.split("a,b,c", %r{,})
|
||||
["a", "b", "c"]
|
||||
iex> String.split("a,b,c", %r{,}, global: false)
|
||||
["a", "b,c"]
|
||||
iex> String.split("a,b", %r{\\.})
|
||||
["a,b"]
|
||||
|
||||
"""
|
||||
@spec split(t) :: [t]
|
||||
@spec split(t, t | [t] | Regex.t) :: [t]
|
||||
@spec split(t, t | [t] | Regex.t, Keyword.t) :: [t]
|
||||
|
||||
def split(binary, pattern // " ", options // [])
|
||||
defdelegate split(binary), to: String.Unicode
|
||||
|
||||
def split(binary, pattern, options // [])
|
||||
|
||||
def split(binary, pattern, options) when is_regex(pattern) do
|
||||
Regex.split(pattern, binary, global: options[:global])
|
||||
@@ -157,9 +199,12 @@ defmodule String do
|
||||
|
||||
## Examples
|
||||
|
||||
String.upcase("abcd") #=> "ABCD"
|
||||
String.upcase("ab 123 xpto") #=> "AB 123 XPTO"
|
||||
String.upcase("josé") #=> "JOSÉ"
|
||||
iex> String.upcase("abcd")
|
||||
"ABCD"
|
||||
iex> String.upcase("ab 123 xpto")
|
||||
"AB 123 XPTO"
|
||||
iex> String.upcase("josé")
|
||||
"JOSÉ"
|
||||
|
||||
"""
|
||||
@spec upcase(t) :: t
|
||||
@@ -170,9 +215,12 @@ defmodule String do
|
||||
|
||||
## Examples
|
||||
|
||||
String.downcase("ABCD") #=> "abcd"
|
||||
String.downcase("AB 123 XPTO") #=> "ab 123 xpto"
|
||||
String.downcase("JOSÉ") #=> "josé"
|
||||
iex> String.downcase("ABCD")
|
||||
"abcd"
|
||||
iex> String.downcase("AB 123 XPTO")
|
||||
"ab 123 xpto"
|
||||
iex> String.downcase("JOSÉ")
|
||||
"josé"
|
||||
|
||||
"""
|
||||
@spec downcase(t) :: t
|
||||
@@ -189,9 +237,12 @@ defmodule String do
|
||||
|
||||
## Examples
|
||||
|
||||
String.capitalize("abcd") #=> "Abcd"
|
||||
String.capitalize("fin") #=> "Fin"
|
||||
String.capitalize("josé") #=> "José"
|
||||
iex> String.capitalize("abcd")
|
||||
"Abcd"
|
||||
iex> String.capitalize("fin")
|
||||
"Fin"
|
||||
iex> String.capitalize("josé")
|
||||
"José"
|
||||
|
||||
"""
|
||||
@spec capitalize(t) :: t
|
||||
@@ -201,12 +252,13 @@ defmodule String do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns a string where trailing whitespace characters
|
||||
and new line have been removed.
|
||||
Returns a string where trailing Unicode whitespace
|
||||
has been removed.
|
||||
|
||||
## Examples
|
||||
|
||||
String.rstrip(" abc ") #=> " abc"
|
||||
iex> String.rstrip(" abc ")
|
||||
" abc"
|
||||
|
||||
"""
|
||||
@spec rstrip(t) :: t
|
||||
@@ -217,7 +269,8 @@ defmodule String do
|
||||
|
||||
## Examples
|
||||
|
||||
String.rstrip(" abc _", ?_) #=> " abc "
|
||||
iex> String.rstrip(" abc _", ?_)
|
||||
" abc "
|
||||
|
||||
"""
|
||||
@spec rstrip(t, char) :: t
|
||||
@@ -248,12 +301,13 @@ defmodule String do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns a string where leading whitespace characters
|
||||
have been removed.
|
||||
Returns a string where leading Unicode whitespace
|
||||
has been removed.
|
||||
|
||||
## Examples
|
||||
|
||||
String.lstrip(" abc ") #=> "abc "
|
||||
iex> String.lstrip(" abc ")
|
||||
"abc "
|
||||
|
||||
"""
|
||||
defdelegate lstrip(binary), to: String.Unicode
|
||||
@@ -263,7 +317,8 @@ defmodule String do
|
||||
|
||||
## Examples
|
||||
|
||||
String.lstrip("_ abc _", ?_) #=> " abc _"
|
||||
iex> String.lstrip("_ abc _", ?_)
|
||||
" abc _"
|
||||
|
||||
"""
|
||||
|
||||
@@ -278,12 +333,13 @@ defmodule String do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns a string where leading/trailing whitespace
|
||||
and new line characters have been removed.
|
||||
Returns a string where leading/trailing Unicode whitespace
|
||||
has been removed.
|
||||
|
||||
## Examples
|
||||
|
||||
String.strip(" abc ") #=> "abc"
|
||||
iex> String.strip(" abc ")
|
||||
"abc"
|
||||
|
||||
"""
|
||||
@spec strip(t) :: t
|
||||
@@ -298,7 +354,8 @@ defmodule String do
|
||||
|
||||
## Examples
|
||||
|
||||
String.strip("a abc a", ?a) #=> " abc "
|
||||
iex> String.strip("a abc a", ?a)
|
||||
" abc "
|
||||
|
||||
"""
|
||||
@spec strip(t, char) :: t
|
||||
@@ -318,11 +375,16 @@ defmodule String do
|
||||
|
||||
## Examples
|
||||
|
||||
String.replace("a,b,c", ",", "-") #=> "a-b-c"
|
||||
String.replace("a,b,c", ",", "-", global: false) #=> "a-b,c"
|
||||
String.replace("a,b,c", "b", "[]", insert_replaced: 1) #=> "a,[b],c"
|
||||
String.replace("a,b,c", ",", "[]", insert_replaced: 2) #=> "a[],b[],c"
|
||||
String.replace("a,b,c", ",", "[]", insert_replaced: [1,1]) #=> "a[,,]b[,,]c"
|
||||
iex> String.replace("a,b,c", ",", "-")
|
||||
"a-b-c"
|
||||
iex> String.replace("a,b,c", ",", "-", global: false)
|
||||
"a-b,c"
|
||||
iex> String.replace("a,b,c", "b", "[]", insert_replaced: 1)
|
||||
"a,[b],c"
|
||||
iex> String.replace("a,b,c", ",", "[]", insert_replaced: 2)
|
||||
"a[],b[],c"
|
||||
iex> String.replace("a,b,c", ",", "[]", insert_replaced: [1,1])
|
||||
"a[,,]b[,,]c"
|
||||
|
||||
"""
|
||||
@spec replace(t, t, t) :: t
|
||||
@@ -348,8 +410,10 @@ defmodule String do
|
||||
|
||||
## Examples
|
||||
|
||||
String.duplicate("abc", 1) #=> "abc"
|
||||
String.duplicate("abc", 2) #=> "abcabc"
|
||||
iex> String.duplicate("abc", 1)
|
||||
"abc"
|
||||
iex> String.duplicate("abc", 2)
|
||||
"abcabc"
|
||||
|
||||
"""
|
||||
@spec duplicate(t, pos_integer) :: t
|
||||
@@ -362,9 +426,12 @@ defmodule String do
|
||||
|
||||
## Examples
|
||||
|
||||
String.codepoints("josé") #=> ["j", "o", "s", "é"]
|
||||
String.codepoints("оптими зации") #=> ["о","п","т","и","м","и"," ","з","а","ц","и","и"]
|
||||
String.codepoints("ἅἪῼ") #=> ["ἅ","Ἢ","ῼ"]
|
||||
iex> String.codepoints("josé")
|
||||
["j", "o", "s", "é"]
|
||||
iex> String.codepoints("оптими зации")
|
||||
["о","п","т","и","м","и"," ","з","а","ц","и","и"]
|
||||
iex> String.codepoints("ἅἪῼ")
|
||||
["ἅ","Ἢ","ῼ"]
|
||||
|
||||
"""
|
||||
@spec codepoints(t) :: [codepoint]
|
||||
@@ -384,7 +451,8 @@ defmodule String do
|
||||
|
||||
## Examples
|
||||
|
||||
String.next_codepoint("josé") #=> { "j", "osé" }
|
||||
iex> String.next_codepoint("josé")
|
||||
{ "j", "osé" }
|
||||
|
||||
"""
|
||||
@spec next_codepoint(t) :: {codepoint, t} | :no_codepoint
|
||||
@@ -398,10 +466,14 @@ defmodule String do
|
||||
|
||||
## Examples
|
||||
|
||||
String.valid_codepoint?("a") #=> true
|
||||
String.valid_codepoint?("ø") #=> true
|
||||
String.valid_codepoint?("\xffff") #=> false
|
||||
String.valid_codepoint?("asdf") #=> false
|
||||
iex> String.valid_codepoint?("a")
|
||||
true
|
||||
iex> String.valid_codepoint?("ø")
|
||||
true
|
||||
iex> String.valid_codepoint?("\xffff")
|
||||
false
|
||||
iex> String.valid_codepoint?("asdf")
|
||||
false
|
||||
|
||||
"""
|
||||
@spec valid_codepoint?(codepoint) :: boolean
|
||||
@@ -413,7 +485,8 @@ defmodule String do
|
||||
|
||||
## Examples
|
||||
|
||||
String.graphemes("Ā̀stute") # => ["Ā̀","s","t","u","t","e"]
|
||||
iex> String.graphemes("Ā̀stute")
|
||||
["Ā̀","s","t","u","t","e"]
|
||||
|
||||
"""
|
||||
@spec graphemes(t) :: [grapheme]
|
||||
@@ -428,7 +501,8 @@ defmodule String do
|
||||
|
||||
## Examples
|
||||
|
||||
String.next_grapheme("josé") #=> { "j", "osé" }
|
||||
iex> String.next_grapheme("josé")
|
||||
{ "j", "osé" }
|
||||
|
||||
"""
|
||||
@spec next_grapheme(t) :: grapheme | :no_grapheme
|
||||
@@ -439,8 +513,10 @@ defmodule String do
|
||||
|
||||
## Examples
|
||||
|
||||
String.first("elixir") #=> "e"
|
||||
String.first("եոգլի") #=> "ե"
|
||||
iex> String.first("elixir")
|
||||
"e"
|
||||
iex> String.first("եոգլի")
|
||||
"ե"
|
||||
|
||||
"""
|
||||
@spec first(t) :: grapheme | nil
|
||||
@@ -456,8 +532,10 @@ defmodule String do
|
||||
|
||||
## Examples
|
||||
|
||||
String.last("elixir") #=> "r"
|
||||
String.last("եոգլի") #=> "ի"
|
||||
iex> String.last("elixir")
|
||||
"r"
|
||||
iex> String.last("եոգլի")
|
||||
"ի"
|
||||
|
||||
"""
|
||||
@spec last(t) :: grapheme | nil
|
||||
@@ -476,8 +554,10 @@ defmodule String do
|
||||
|
||||
## Examples
|
||||
|
||||
String.length("elixir") #=> 6
|
||||
String.length("եոգլի") #=> 5
|
||||
iex> String.length("elixir")
|
||||
6
|
||||
iex> String.length("եոգլի")
|
||||
5
|
||||
|
||||
"""
|
||||
@spec length(t) :: non_neg_integer
|
||||
@@ -497,11 +577,16 @@ defmodule String do
|
||||
|
||||
## Examples
|
||||
|
||||
String.at("elixir", 0) #=> "e"
|
||||
String.at("elixir", 1) #=> "l"
|
||||
String.at("elixir", 10) #=> nil
|
||||
String.at("elixir", -1) #=> "r"
|
||||
String.at("elixir", -10) #=> nil
|
||||
iex> String.at("elixir", 0)
|
||||
"e"
|
||||
iex> String.at("elixir", 1)
|
||||
"l"
|
||||
iex> String.at("elixir", 10)
|
||||
nil
|
||||
iex> String.at("elixir", -1)
|
||||
"r"
|
||||
iex> String.at("elixir", -10)
|
||||
nil
|
||||
|
||||
"""
|
||||
@spec at(t, integer) :: grapheme | nil
|
||||
@@ -535,11 +620,16 @@ defmodule String do
|
||||
|
||||
## Examples
|
||||
|
||||
String.slice("elixir", 1, 3) #=> "lix"
|
||||
String.slice("elixir", 1, 10) #=> "lixir"
|
||||
String.slice("elixir", 10, 3) #=> nil
|
||||
String.slice("elixir", -4, 4) #=> "ixi"
|
||||
String.slice("elixir", -10, 3) #=> nil
|
||||
iex> String.slice("elixir", 1, 3)
|
||||
"lix"
|
||||
iex> String.slice("elixir", 1, 10)
|
||||
"lixir"
|
||||
iex> String.slice("elixir", 10, 3)
|
||||
nil
|
||||
iex> String.slice("elixir", -4, 4)
|
||||
"ixir"
|
||||
iex> String.slice("elixir", -10, 3)
|
||||
nil
|
||||
|
||||
"""
|
||||
@spec slice(t, integer, integer) :: grapheme | nil
|
||||
|
||||
+64
-40
@@ -57,60 +57,54 @@ defmodule String.Unicode do
|
||||
|
||||
# Downcase
|
||||
|
||||
def downcase(""), do: ""
|
||||
|
||||
lc { codepoint, _upper, lower, _title } inlist codes, lower && lower != codepoint do
|
||||
args = quote do: [unquote(codepoint) <> t]
|
||||
code = quote do: unquote(lower) <> downcase(t)
|
||||
def :downcase, args, [], do: code
|
||||
def downcase(unquote(codepoint) <> rest) do
|
||||
unquote(lower) <> downcase(rest)
|
||||
end
|
||||
end
|
||||
|
||||
def downcase(<< h, t :: binary >>) do
|
||||
<< h >> <> downcase(t)
|
||||
end
|
||||
|
||||
def downcase(<< >>) do
|
||||
<< >>
|
||||
def downcase(<< char, rest :: binary >>) do
|
||||
<< char >> <> downcase(rest)
|
||||
end
|
||||
|
||||
# Upcase
|
||||
|
||||
def upcase(""), do: ""
|
||||
|
||||
lc { codepoint, upper, _lower, _title } inlist codes, upper && upper != codepoint do
|
||||
args = quote do: [unquote(codepoint) <> t]
|
||||
code = quote do: unquote(upper) <> upcase(t)
|
||||
def :upcase, args, [], do: code
|
||||
def upcase(unquote(codepoint) <> rest) do
|
||||
unquote(upper) <> upcase(rest)
|
||||
end
|
||||
end
|
||||
|
||||
def upcase(<< h, t :: binary >>) do
|
||||
<< h >> <> upcase(t)
|
||||
end
|
||||
|
||||
def upcase(<< >>) do
|
||||
<< >>
|
||||
def upcase(<< char, rest :: binary >>) do
|
||||
<< char >> <> upcase(rest)
|
||||
end
|
||||
|
||||
# Titlecase once
|
||||
|
||||
def titlecase_once(""), do: { "", "" }
|
||||
|
||||
lc { codepoint, _upper, _lower, title } inlist codes, title && title != codepoint do
|
||||
args = quote do: [unquote(codepoint) <> t]
|
||||
code = quote do: { unquote(title), t }
|
||||
def :titlecase_once, args, [], do: code
|
||||
def titlecase_once(unquote(codepoint) <> rest) do
|
||||
{ unquote(title), rest }
|
||||
end
|
||||
end
|
||||
|
||||
def titlecase_once(<< h, t :: binary >>) do
|
||||
{ <<h>>, t }
|
||||
end
|
||||
|
||||
def titlecase_once(<< >>) do
|
||||
{ <<>>, <<>> }
|
||||
def titlecase_once(<< char, rest :: binary >>) do
|
||||
{ << char >>, rest }
|
||||
end
|
||||
|
||||
# Strip
|
||||
|
||||
def lstrip(""), do: ""
|
||||
|
||||
lc char inlist whitespace do
|
||||
args = quote do: [unquote(char) <> rest]
|
||||
exprs = quote do: lstrip(rest)
|
||||
def :lstrip, args, [], do: exprs
|
||||
lc codepoint inlist whitespace do
|
||||
def lstrip(unquote(codepoint) <> rest) do
|
||||
lstrip(rest)
|
||||
end
|
||||
end
|
||||
|
||||
def lstrip(other) when is_binary(other), do: other
|
||||
@@ -121,24 +115,54 @@ defmodule String.Unicode do
|
||||
do_rstrip(string, "")
|
||||
end
|
||||
|
||||
lc char inlist whitespace do
|
||||
args = quote do: [unquote(char) <> rest, buffer]
|
||||
exprs = quote do: do_rstrip(rest, unquote(char) <> buffer)
|
||||
defp :do_rstrip, args, [], do: exprs
|
||||
lc codepoint inlist whitespace do
|
||||
defp do_rstrip(unquote(codepoint) <> rest, buffer) do
|
||||
do_rstrip(rest, unquote(codepoint) <> buffer)
|
||||
end
|
||||
end
|
||||
|
||||
defp do_rstrip(<< char, string :: binary >>, buffer) do
|
||||
<< buffer :: binary, char, do_rstrip(string, "") :: binary >>
|
||||
defp do_rstrip(<< char, rest :: binary >>, buffer) do
|
||||
<< buffer :: binary, char, do_rstrip(rest, "") :: binary >>
|
||||
end
|
||||
|
||||
defp do_rstrip(<<>>, _), do: <<>>
|
||||
|
||||
# Split
|
||||
|
||||
def split(""), do: ""
|
||||
|
||||
def split(string) when is_binary(string) do
|
||||
:lists.reverse do_split(string, "", [])
|
||||
end
|
||||
|
||||
lc codepoint inlist whitespace do
|
||||
defp do_split(unquote(codepoint) <> rest, buffer, acc) do
|
||||
if buffer != "" do
|
||||
do_split(rest, "", [buffer | acc])
|
||||
else
|
||||
do_split(rest, buffer, acc)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defp do_split(<< char, rest :: binary >>, buffer, acc) do
|
||||
do_split(rest, << buffer :: binary, char >>, acc)
|
||||
end
|
||||
|
||||
defp do_split(<<>>, buffer, acc) do
|
||||
if buffer != "" do
|
||||
[buffer | acc]
|
||||
else
|
||||
acc
|
||||
end
|
||||
end
|
||||
|
||||
# Graphemes
|
||||
|
||||
lc codepoints inlist seqs do
|
||||
seq_args = quote do: [<< unquote_splicing(codepoints), t :: binary >>]
|
||||
seq_code = quote do: {<< unquote_splicing(codepoints) >>, t}
|
||||
def :next_grapheme, seq_args, [], do: seq_code
|
||||
def next_grapheme(<< unquote_splicing(codepoints), t :: binary >>) do
|
||||
{ << unquote_splicing(codepoints) >>, t }
|
||||
end
|
||||
end
|
||||
|
||||
def next_grapheme(<<>>) do
|
||||
|
||||
@@ -37,7 +37,7 @@ main(Args) ->
|
||||
application:start(?MODULE),
|
||||
'Elixir.Kernel.CLI':main(Args).
|
||||
|
||||
% Boot and process given options. Invoked by Elixir's script.
|
||||
%% Boot and process given options. Invoked by Elixir's script.
|
||||
|
||||
start_cli() ->
|
||||
application:start(?MODULE),
|
||||
|
||||
@@ -145,8 +145,8 @@ lookup(Else, Dict) ->
|
||||
%% Errors
|
||||
|
||||
format_error({unloaded_module, Module}) ->
|
||||
io_lib:format("module ~s is not loaded and could not be found", [elixir_errors:inspect(Module)]);
|
||||
io_lib:format("module ~ts is not loaded and could not be found", [elixir_errors:inspect(Module)]);
|
||||
|
||||
format_error({scheduled_module, Module}) ->
|
||||
io_lib:format("module ~s is not loaded but was defined. This happens because you are trying to use a module in the same context it is defined. Try defining the module outside the context that requires it.",
|
||||
io_lib:format("module ~ts is not loaded but was defined. This happens because you are trying to use a module in the same context it is defined. Try defining the module outside the context that requires it.",
|
||||
[elixir_errors:inspect(Module)]).
|
||||
@@ -19,15 +19,15 @@ get_pairs(Meta, Key, Clauses, S, AllowNil) ->
|
||||
{ Key, nil } when AllowNil ->
|
||||
[];
|
||||
{ Key, _ } ->
|
||||
elixir_errors:syntax_error(Meta, S#elixir_scope.file, "expected pairs with -> for key ~s", [Key]);
|
||||
elixir_errors:syntax_error(Meta, S#elixir_scope.file, "expected pairs with -> for key ~ts", [Key]);
|
||||
_ ->
|
||||
[]
|
||||
end.
|
||||
|
||||
% Function for translating assigns.
|
||||
|
||||
assigns(Fun, Args, #elixir_scope{context=Context} = S) when Context /= assign ->
|
||||
{ Result, NewS } = assigns(Fun, Args, S#elixir_scope{context=assign, temp_vars=orddict:new()}),
|
||||
assigns(Fun, Args, #elixir_scope{context=Context} = S) when Context /= match ->
|
||||
{ Result, NewS } = assigns(Fun, Args, S#elixir_scope{context=match, temp_vars=orddict:new()}),
|
||||
{ Result, NewS#elixir_scope{context=Context} };
|
||||
|
||||
assigns(Fun, Args, S) -> Fun(Args, S).
|
||||
@@ -176,10 +176,10 @@ each_clause(Meta, { 'after', [Condition], Expr }, S) ->
|
||||
{ { clause, ?line(Meta), [TCondition], [], TBody }, SB };
|
||||
|
||||
each_clause(Meta, { Key, [_|_], _ }, S) when Key == do; Key == 'after' ->
|
||||
elixir_errors:syntax_error(Meta, S#elixir_scope.file, "too many arguments given for ~s", [Key]);
|
||||
elixir_errors:syntax_error(Meta, S#elixir_scope.file, "too many arguments given for ~ts", [Key]);
|
||||
|
||||
each_clause(Meta, { Key, _, _ }, S) ->
|
||||
elixir_errors:syntax_error(Meta, S#elixir_scope.file, "invalid key ~s", [Key]).
|
||||
elixir_errors:syntax_error(Meta, S#elixir_scope.file, "invalid key ~ts", [Key]).
|
||||
|
||||
% Check if the given expression is a match tuple.
|
||||
% This is a small optimization to allow us to change
|
||||
|
||||
@@ -22,11 +22,14 @@ get_opts() ->
|
||||
%% Compiles the given string.
|
||||
|
||||
string(Contents, File) when is_list(Contents), is_binary(File) ->
|
||||
Forms = elixir_translator:'forms!'(Contents, 1, File, []),
|
||||
quoted(Forms, File).
|
||||
|
||||
quoted(Forms, File) when is_binary(File) ->
|
||||
Previous = get(elixir_compiled),
|
||||
|
||||
try
|
||||
put(elixir_compiled, []),
|
||||
Forms = elixir_translator:'forms!'(Contents, 1, File, []),
|
||||
eval_forms(Forms, 1, [], elixir:scope_for_eval([{file,File}])),
|
||||
lists:reverse(get(elixir_compiled))
|
||||
after
|
||||
@@ -137,11 +140,11 @@ env_default_opts() ->
|
||||
{ok,List} when is_list(List) -> List;
|
||||
{ok,Term} -> [Term];
|
||||
{error,_Reason} ->
|
||||
io:format("Ignoring bad term in ~s\n", [Key]),
|
||||
io:format("Ignoring bad term in ~ts\n", [Key]),
|
||||
[]
|
||||
end;
|
||||
{error, {_,_,_Reason}, _} ->
|
||||
io:format("Ignoring bad term in ~s\n", [Key]),
|
||||
io:format("Ignoring bad term in ~ts\n", [Key]),
|
||||
[]
|
||||
end
|
||||
end.
|
||||
@@ -195,7 +198,7 @@ core_file(File) ->
|
||||
try
|
||||
Lists = file(list_to_binary(File)),
|
||||
[binary_to_path(X, "lib/elixir/ebin") || X <- Lists],
|
||||
io:format("Compiled ~s~n", [File])
|
||||
io:format("Compiled ~ts~n", [File])
|
||||
catch
|
||||
Kind:Reason ->
|
||||
io:format("~p: ~p~nstacktrace: ~p~n", [Kind, Reason, erlang:get_stacktrace()]),
|
||||
@@ -226,6 +229,7 @@ core_main() ->
|
||||
"lib/elixir/lib/access.ex",
|
||||
"lib/elixir/lib/regex.ex",
|
||||
"lib/elixir/lib/system.ex",
|
||||
"lib/elixir/lib/process.ex",
|
||||
"lib/elixir/lib/kernel/cli.ex",
|
||||
"lib/elixir/lib/kernel/error_handler.ex",
|
||||
"lib/elixir/lib/kernel/parallel_compiler.ex",
|
||||
@@ -235,7 +239,7 @@ core_main() ->
|
||||
%% ERROR HANDLING
|
||||
|
||||
format_error({ skip_native, Module }) ->
|
||||
io_lib:format("skipping native compilation for ~s because it contains on_load attribute",
|
||||
io_lib:format("skipping native compilation for ~ts because it contains on_load attribute",
|
||||
[elixir_errors:inspect(Module)]).
|
||||
|
||||
format_errors(_File, []) ->
|
||||
|
||||
@@ -4,7 +4,9 @@
|
||||
build_table/1,
|
||||
delete_table/1,
|
||||
reset_last/1,
|
||||
wrap_definition/5,
|
||||
wrap_definition/7,
|
||||
store_definition/6,
|
||||
store_definition/8,
|
||||
store_each/7,
|
||||
unwrap_stored_definitions/2,
|
||||
@@ -44,33 +46,52 @@ reset_last(Module) ->
|
||||
%%
|
||||
%% If we just analyzed the compiled structure (i.e. the function availables
|
||||
%% before evaluating the function body), we would see both definitions.
|
||||
wrap_definition(Kind, Meta, Name, Args, Guards, Expr, S) ->
|
||||
Line = ?line(Meta),
|
||||
MetaS = elixir_scope:serialize(S),
|
||||
|
||||
Invoke = [
|
||||
{atom, Line, Kind},
|
||||
{integer, Line, Line},
|
||||
{var, Line, '_@MODULE'},
|
||||
Name,
|
||||
Args,
|
||||
Guards,
|
||||
Expr,
|
||||
MetaS
|
||||
],
|
||||
wrap_definition(Kind, Meta, Call, Expr, S) ->
|
||||
do_wrap_definition(Kind, Meta, [
|
||||
Call, Expr, elixir_scope:serialize(S)
|
||||
]).
|
||||
|
||||
wrap_definition(Kind, Meta, Name, Args, Guards, Expr, S) ->
|
||||
do_wrap_definition(Kind, Meta, [
|
||||
Name, Args, Guards, Expr, elixir_scope:serialize(S)
|
||||
]).
|
||||
|
||||
do_wrap_definition(Kind, Meta, Extra) ->
|
||||
Line = ?line(Meta),
|
||||
Invoke =
|
||||
[{atom, Line, Kind},
|
||||
{integer, Line, Line},
|
||||
{var, Line, '_@MODULE'}] ++ Extra,
|
||||
|
||||
?wrap_call(Line, ?MODULE, store_definition, Invoke).
|
||||
|
||||
% Invoked by the wrap definition with the function abstract tree.
|
||||
% Each function is then added to the function table.
|
||||
store_definition(Kind, Line, Module, Call, Body, RawS) ->
|
||||
S = elixir_scope:deserialize(RawS),
|
||||
{ NameAndArgs, Guards } = elixir_clauses:extract_guards(Call),
|
||||
|
||||
{ Name, Args } = case elixir_clauses:extract_args(NameAndArgs) of
|
||||
error ->
|
||||
Format = [Kind, 'Elixir.Macro':to_binary(NameAndArgs)],
|
||||
elixir_errors:syntax_error(Line, S#elixir_scope.file, "invalid syntax in ~ts ~ts", Format);
|
||||
Tuple ->
|
||||
Tuple
|
||||
end,
|
||||
|
||||
assert_no_aliases_name(Line, Name, Args, S),
|
||||
store_definition(Kind, Line, Module, Name, Args, Guards, Body, RawS).
|
||||
|
||||
store_definition(Kind, Line, nil, _Name, _Args, _Guards, _Body, RawS) ->
|
||||
S = elixir_scope:deserialize(RawS),
|
||||
elixir_errors:syntax_error(Line, S#elixir_scope.file, "cannot define function outside module, invalid scope for ~s", [Kind]);
|
||||
elixir_errors:syntax_error(Line, S#elixir_scope.file, "cannot define function outside module, invalid scope for ~ts", [Kind]);
|
||||
|
||||
store_definition(Kind, Line, Module, Name, Args, Guards, Body, RawS) ->
|
||||
do_store_definition(Kind, Line, Module, Name, Args, Guards, Body, elixir_scope:deserialize(RawS)).
|
||||
|
||||
do_store_definition(Kind, Line, Module, Name, Args, Guards, Body, DS) ->
|
||||
Arity = length(Args),
|
||||
DS = elixir_scope:deserialize(RawS),
|
||||
S = DS#elixir_scope{function={Name,Arity}, module=Module},
|
||||
Expr = def_body(Line, Body),
|
||||
|
||||
@@ -290,13 +311,20 @@ check_valid_defaults(_Line, _File, _Name, _Arity, 0) -> [];
|
||||
check_valid_defaults(Line, File, Name, Arity, _) ->
|
||||
elixir_errors:handle_file_warning(File, { Line, ?MODULE, { clauses_with_docs, { Name, Arity } } }).
|
||||
|
||||
assert_no_aliases_name(Line, '__aliases__', [Atom], #elixir_scope{file=File}) when is_atom(Atom) ->
|
||||
Message = "function names should start with lowercase characters or underscore, invalid name ~ts",
|
||||
elixir_errors:syntax_error(Line, File, Message, [atom_to_binary(Atom, utf8)]);
|
||||
|
||||
assert_no_aliases_name(_Meta, _Aliases, _Args, _S) ->
|
||||
ok.
|
||||
|
||||
%% Format errors
|
||||
|
||||
format_error({clauses_with_docs,{Name,Arity}}) ->
|
||||
io_lib:format("function ~s/~B has default values and multiple clauses, use a separate clause for declaring defaults", [Name, Arity]);
|
||||
io_lib:format("function ~ts/~B has default values and multiple clauses, use a separate clause for declaring defaults", [Name, Arity]);
|
||||
|
||||
format_error({changed_clause,{{Name,Arity},{ElseName,ElseArity}}}) ->
|
||||
io_lib:format("function ~s/~B does not match previous clause ~s/~B", [Name, Arity, ElseName, ElseArity]);
|
||||
io_lib:format("function ~ts/~B does not match previous clause ~ts/~B", [Name, Arity, ElseName, ElseArity]);
|
||||
|
||||
format_error({changed_kind,{Name,Arity,Previous,Current}}) ->
|
||||
io_lib:format("~s ~s/~B already defined as ~s", [Current, Name, Arity, Previous]).
|
||||
io_lib:format("~ts ~ts/~B already defined as ~ts", [Current, Name, Arity, Previous]).
|
||||
@@ -75,4 +75,4 @@ check_unused_local_macros(File, Recorded, Defmacrop) ->
|
||||
Check, not lists:member(Fun, Recorded)].
|
||||
|
||||
format_error({unused_macro,{Name, Arity}}) ->
|
||||
io_lib:format("macro ~s/~B is unused", [Name, Arity]).
|
||||
io_lib:format("macro ~ts/~B is unused", [Name, Arity]).
|
||||
@@ -88,7 +88,7 @@ store_pending(Module) ->
|
||||
format_error({ no_super, Module, { Name, Arity } }) ->
|
||||
Bins = [ format_fa(X) || { X, { _, _, _ } } <- overridable(Module)],
|
||||
Joined = 'Elixir.Enum':join(Bins, <<", ">>),
|
||||
io_lib:format("no super defined for ~s/~B in module ~s. Overridable functions available are: ~s",
|
||||
io_lib:format("no super defined for ~ts/~B in module ~ts. Overridable functions available are: ~ts",
|
||||
[Name, Arity, elixir_errors:inspect(Module), Joined]).
|
||||
|
||||
format_fa({ Name, Arity }) ->
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
-export([default_macros/0, default_functions/0, default_requires/0,
|
||||
dispatch_require/6, dispatch_import/5,
|
||||
require_function/5, import_function/4,
|
||||
expand_import/8, expand_require/8, find_import/4,
|
||||
expand_import/6, expand_require/6, find_import/4,
|
||||
format_error/1, in_erlang_functions/0, in_erlang_macros/0]).
|
||||
-include("elixir.hrl").
|
||||
-compile({parse_transform, elixir_transform}).
|
||||
@@ -18,32 +18,31 @@ default_functions() ->
|
||||
default_macros() ->
|
||||
[ { ?BUILTIN, ordsets:union(in_elixir_macros(), in_erlang_macros()) } ].
|
||||
default_requires() ->
|
||||
[ ?BUILTIN, 'Elixir.Kernel.Typespec' ].
|
||||
[ ?BUILTIN, 'Elixir.Kernel.Typespec', 'Elixir.Record' ].
|
||||
|
||||
find_import(_Meta, Name, Arity, S) ->
|
||||
find_import(Meta, Name, Arity, S) ->
|
||||
Tuple = { Name, Arity },
|
||||
case find_dispatch(Tuple, S#elixir_scope.functions) of
|
||||
false ->
|
||||
case find_dispatch(Tuple, S#elixir_scope.macros) of
|
||||
false -> false;
|
||||
Receiver -> Receiver
|
||||
end;
|
||||
Receiver -> Receiver
|
||||
|
||||
case find_dispatch(Meta, Tuple, S) of
|
||||
{ function, Receiver } -> Receiver;
|
||||
{ macro, Receiver } -> Receiver;
|
||||
_ -> false
|
||||
end.
|
||||
|
||||
%% Function retrieval
|
||||
|
||||
import_function(Meta, Name, Arity, S) ->
|
||||
Tuple = { Name, Arity },
|
||||
case find_dispatch(Tuple, S#elixir_scope.functions) of
|
||||
false ->
|
||||
case find_dispatch(Tuple, S#elixir_scope.macros) of
|
||||
false -> { { 'fun', ?line(Meta), { function, Name, Arity } }, S };
|
||||
_ -> false
|
||||
end;
|
||||
Receiver ->
|
||||
case find_dispatch(Meta, Tuple, S) of
|
||||
{ function, Receiver } ->
|
||||
elixir_import:record(import, Tuple, Receiver, S#elixir_scope.module),
|
||||
remote_function(Meta, Receiver, Name, Arity, S)
|
||||
remote_function(Meta, Receiver, Name, Arity, S);
|
||||
{ macro, _Receiver } ->
|
||||
false;
|
||||
{ import, Receiver } ->
|
||||
require_function(Meta, Receiver, Name, Arity, S);
|
||||
nomatch ->
|
||||
{ { 'fun', ?line(Meta), { function, Name, Arity } }, S }
|
||||
end.
|
||||
|
||||
require_function(Meta, Receiver, Name, Arity, S) ->
|
||||
@@ -61,10 +60,18 @@ dispatch_import(Meta, Name, Args, S, Callback) ->
|
||||
Arity = length(Args),
|
||||
Tuple = { Name, Arity },
|
||||
|
||||
case find_dispatch(Tuple, S#elixir_scope.functions) of
|
||||
false ->
|
||||
case expand_import(Meta, Tuple, Args, Module, S#elixir_scope.function,
|
||||
S#elixir_scope.requires, S#elixir_scope.macros, S) of
|
||||
case find_dispatch(Meta, Tuple, S) of
|
||||
{ function, Receiver } ->
|
||||
elixir_import:record(import, Tuple, Receiver, Module),
|
||||
Endpoint = case (Receiver == ?BUILTIN) andalso is_element(Tuple, in_erlang_functions()) of
|
||||
true -> erlang;
|
||||
false -> Receiver
|
||||
end,
|
||||
elixir_translator:translate_each({ { '.', Meta, [Endpoint, Name] }, Meta, Args }, S);
|
||||
{ import, Receiver } ->
|
||||
elixir_translator:translate_each({ { '.', Meta, [Receiver, Name] }, [{require,false}|Meta], Args }, S);
|
||||
Result ->
|
||||
case do_expand_import(Meta, Tuple, Args, Module, S, Result) of
|
||||
{ error, noexpansion } ->
|
||||
Callback();
|
||||
{ error, internal } ->
|
||||
@@ -72,14 +79,7 @@ dispatch_import(Meta, Name, Args, S, Callback) ->
|
||||
elixir_macros:translate({ Name, Meta, Args }, S);
|
||||
{ ok, _Receiver, Tree } ->
|
||||
translate_expansion(Meta, Tree, S)
|
||||
end;
|
||||
Receiver ->
|
||||
elixir_import:record(import, Tuple, Receiver, Module),
|
||||
Endpoint = case (Receiver == ?BUILTIN) andalso is_element(Tuple, in_erlang_functions()) of
|
||||
true -> erlang;
|
||||
false -> Receiver
|
||||
end,
|
||||
elixir_translator:translate_each({ { '.', Meta, [Endpoint, Name] }, Meta, Args }, S)
|
||||
end
|
||||
end.
|
||||
|
||||
dispatch_require(Meta, Receiver, Name, Args, S, Callback) ->
|
||||
@@ -89,119 +89,172 @@ dispatch_require(Meta, Receiver, Name, Args, S, Callback) ->
|
||||
|
||||
case (Receiver == ?BUILTIN) andalso is_element(Tuple, in_erlang_functions()) of
|
||||
true ->
|
||||
elixir_translator:translate_each({ { '.', Meta, [erlang, Name] }, Meta, Args }, S);
|
||||
{ TArgs, SA } = elixir_translator:translate_args(Args, S),
|
||||
{ ?wrap_call(?line(Meta), erlang, Name, TArgs), SA };
|
||||
false ->
|
||||
case expand_require(Meta, Receiver, Tuple, Args, Module,
|
||||
S#elixir_scope.function, S#elixir_scope.requires, S) of
|
||||
case expand_require(Meta, Receiver, Tuple, Args, Module, S) of
|
||||
{ error, noexpansion } ->
|
||||
Callback();
|
||||
{ error, internal } ->
|
||||
elixir_macros:translate({ Name, Meta, Args }, S);
|
||||
{ ok, Tree } ->
|
||||
{ ok, _Receiver, Tree } ->
|
||||
translate_expansion(Meta, Tree, S)
|
||||
end
|
||||
end.
|
||||
|
||||
%% Macros expansion
|
||||
|
||||
expand_import(Meta, { Name, Arity } = Tuple, Args, Module, Function, Requires, Macros, SEnv) ->
|
||||
case find_dispatch(Tuple, Macros) of
|
||||
false ->
|
||||
Fun = (Function /= Tuple) andalso
|
||||
expand_import(Meta, Tuple, Args, Module, Extra, S) ->
|
||||
Result = find_dispatch(Meta, Tuple, Extra, S),
|
||||
do_expand_import(Meta, Tuple, Args, Module, S, Result).
|
||||
|
||||
do_expand_import(Meta, { Name, Arity } = Tuple, Args, Module, S, Result) ->
|
||||
case Result of
|
||||
{ macro, ?BUILTIN } ->
|
||||
case is_element(Tuple, in_erlang_macros()) of
|
||||
true -> { error, internal };
|
||||
false ->
|
||||
elixir_import:record(import, Tuple, ?BUILTIN, Module),
|
||||
{ ok, ?BUILTIN, expand_macro_named(Meta, ?BUILTIN, Name, Arity, Args, Module, S) }
|
||||
end;
|
||||
{ macro, Receiver } ->
|
||||
elixir_import:record(import, Tuple, Receiver, Module),
|
||||
{ ok, Receiver, expand_macro_named(Meta, Receiver, Name, Arity, Args, Module, S) };
|
||||
{ import, Receiver } ->
|
||||
expand_require(Meta, Receiver, Tuple, Args, Module, S);
|
||||
_ ->
|
||||
Fun = (S#elixir_scope.function /= Tuple) andalso
|
||||
elixir_def_local:macro_for(Tuple, true, Module),
|
||||
case Fun of
|
||||
false -> { error, noexpansion };
|
||||
_ ->
|
||||
elixir_import:record(import, Tuple, Module, Module),
|
||||
{ ok, Module, expand_macro_fun(Meta, Fun, Module, Name, Args, Module, Requires, SEnv) }
|
||||
end;
|
||||
?BUILTIN ->
|
||||
case is_element(Tuple, in_erlang_macros()) of
|
||||
true -> { error, internal };
|
||||
false ->
|
||||
elixir_import:record(import, Tuple, ?BUILTIN, Module),
|
||||
{ ok, ?BUILTIN, expand_macro_named(Meta, ?BUILTIN, Name, Arity, Args, Module, Requires, SEnv) }
|
||||
end;
|
||||
Receiver ->
|
||||
elixir_import:record(import, Tuple, Receiver, Module),
|
||||
{ ok, Receiver, expand_macro_named(Meta, Receiver, Name, Arity, Args, Module, Requires, SEnv) }
|
||||
{ ok, Module, expand_macro_fun(Meta, Fun, Module, Name, Args, Module, S) }
|
||||
end
|
||||
end.
|
||||
|
||||
expand_require(Meta, ?BUILTIN, { Name, Arity } = Tuple, Args, Module, _Function, Requires, SEnv) ->
|
||||
expand_require(Meta, ?BUILTIN, { Name, Arity } = Tuple, Args, Module, S) ->
|
||||
case is_element(Tuple, in_erlang_macros()) of
|
||||
true -> { error, internal };
|
||||
false ->
|
||||
case is_element(Tuple, in_elixir_macros()) of
|
||||
true -> { ok, expand_macro_named(Meta, ?BUILTIN, Name, Arity, Args, Module, Requires, SEnv) };
|
||||
true -> { ok, ?BUILTIN, expand_macro_named(Meta, ?BUILTIN, Name, Arity, Args, Module, S) };
|
||||
false -> { error, noexpansion }
|
||||
end
|
||||
end;
|
||||
|
||||
expand_require(Meta, Receiver, { Name, Arity } = Tuple, Args, Module, Function, Requires, SEnv) ->
|
||||
Fun = (Module == Receiver) andalso (Function /= Tuple) andalso
|
||||
expand_require(Meta, Receiver, { Name, Arity } = Tuple, Args, Module, S) ->
|
||||
Fun = (Module == Receiver) andalso (S#elixir_scope.function /= Tuple) andalso
|
||||
elixir_def_local:macro_for(Tuple, false, Module),
|
||||
|
||||
case Fun of
|
||||
false ->
|
||||
case is_element(Tuple, get_optional_macros(Receiver)) of
|
||||
true -> { ok, expand_macro_named(Meta, Receiver, Name, Arity, Args, Module, Requires, SEnv) };
|
||||
true -> { ok, Receiver, expand_macro_named(Meta, Receiver, Name, Arity, Args, Module, S) };
|
||||
false -> { error, noexpansion }
|
||||
end;
|
||||
_ ->
|
||||
elixir_import:record(import, Tuple, Receiver, Module),
|
||||
{ ok, expand_macro_fun(Meta, Fun, Receiver, Name, Args, Module, Requires, SEnv) }
|
||||
{ ok, Receiver, expand_macro_fun(Meta, Fun, Receiver, Name, Args, Module, S) }
|
||||
end.
|
||||
|
||||
%% Expansion helpers
|
||||
|
||||
expand_macro_fun(Meta, Fun, Receiver, Name, Args, Module, Requires, SEnv) ->
|
||||
case (Receiver == Module) or is_element(Receiver, Requires) or skip_requires(SEnv) of
|
||||
expand_macro_fun(Meta, Fun, Receiver, Name, Args, Module, S) ->
|
||||
Requires = S#elixir_scope.requires,
|
||||
case (Receiver == Module) or is_element(Receiver, Requires) or skip_require(Meta) of
|
||||
true -> ok;
|
||||
false ->
|
||||
Tuple = { unrequired_module, { Receiver, Name, length(Args), Requires } },
|
||||
elixir_errors:form_error(Meta, elixir_scope:filename(SEnv), ?MODULE, Tuple)
|
||||
elixir_errors:form_error(Meta, S#elixir_scope.file, ?MODULE, Tuple)
|
||||
end,
|
||||
|
||||
Line = ?line(Meta),
|
||||
SArg = {Line,SEnv},
|
||||
SArg = {Line,S},
|
||||
|
||||
try
|
||||
apply(Fun, [SArg|Args])
|
||||
catch
|
||||
Kind:Reason ->
|
||||
Info = { Receiver, Name, length(Args), [{ file, elixir_scope:filename(SEnv) }, { line, Line }] },
|
||||
Info = { Receiver, Name, length(Args), [{ file, S#elixir_scope.file }, { line, Line }] },
|
||||
erlang:raise(Kind, Reason, munge_stacktrace(Info, erlang:get_stacktrace(), SArg))
|
||||
end.
|
||||
|
||||
expand_macro_named(Meta, Receiver, Name, Arity, Args, Module, Requires, SEnv) ->
|
||||
expand_macro_named(Meta, Receiver, Name, Arity, Args, Module, S) ->
|
||||
ProperName = ?elixir_macro(Name),
|
||||
ProperArity = Arity + 1,
|
||||
Fun = fun Receiver:ProperName/ProperArity,
|
||||
expand_macro_fun(Meta, Fun, Receiver, Name, Args, Module, Requires, SEnv).
|
||||
expand_macro_fun(Meta, Fun, Receiver, Name, Args, Module, S).
|
||||
|
||||
translate_expansion(Meta, Tree, S) ->
|
||||
{ TR, TS } = elixir_translator:translate_each(
|
||||
elixir_quote:linify(?line(Meta), Tree),
|
||||
S#elixir_scope{
|
||||
check_clauses=false,
|
||||
macro_aliases=[] }
|
||||
macro_functions=[],
|
||||
macro_macros=[],
|
||||
macro_aliases=[]
|
||||
}
|
||||
),
|
||||
{ TR, TS#elixir_scope{
|
||||
check_clauses=S#elixir_scope.check_clauses,
|
||||
macro_aliases=S#elixir_scope.macro_aliases } }.
|
||||
macro_functions=merge_imports(S#elixir_scope.macro_functions, TS#elixir_scope.macro_functions),
|
||||
macro_macros=merge_imports(S#elixir_scope.macro_macros, TS#elixir_scope.macro_macros),
|
||||
macro_aliases=merge_aliases(S#elixir_scope.macro_aliases, TS#elixir_scope.macro_aliases)
|
||||
} }.
|
||||
|
||||
merge_aliases(A1, A2) ->
|
||||
orddict:merge(fun(_K,_V1,V2) -> V2 end, A1, A2).
|
||||
|
||||
merge_imports(A1, A2) ->
|
||||
A2 ++ lists:foldl(fun({ Key, _ }, Acc) -> lists:keydelete(Key, 1, Acc) end, A1, A2).
|
||||
|
||||
%% Helpers
|
||||
|
||||
skip_requires(#elixir_scope{check_requires=false}) -> true;
|
||||
skip_requires(_) -> false.
|
||||
skip_require(Meta) ->
|
||||
lists:keyfind(require, 1, Meta) == { require, false }.
|
||||
|
||||
find_dispatch(Tuple, [{ Name, Values }|T]) ->
|
||||
case is_element(Tuple, Values) of
|
||||
true -> Name;
|
||||
false -> find_dispatch(Tuple, T)
|
||||
end;
|
||||
find_dispatch(Meta, Tuple, S) ->
|
||||
find_dispatch(Meta, Tuple, [], S).
|
||||
|
||||
find_dispatch(_Tuple, []) -> false.
|
||||
find_dispatch(Meta, Tuple, Extra, S) ->
|
||||
case is_import(Meta, Tuple, S) of
|
||||
{ import, _ } = Import ->
|
||||
Import;
|
||||
false ->
|
||||
Functions = S#elixir_scope.functions,
|
||||
Macros = Extra ++ S#elixir_scope.macros,
|
||||
File = S#elixir_scope.file,
|
||||
FunMatch = find_dispatch(Tuple, Functions),
|
||||
MacMatch = find_dispatch(Tuple, Macros),
|
||||
|
||||
case { FunMatch, MacMatch } of
|
||||
{ [], [Receiver] } -> { macro, Receiver };
|
||||
{ [Receiver], [] } -> { function, Receiver };
|
||||
{ [], [] } -> nomatch;
|
||||
_ ->
|
||||
{ Name, Arity } = Tuple,
|
||||
[First, Second|_] = FunMatch ++ MacMatch,
|
||||
Error = { ambiguous_call, { First, Second, Name, Arity } },
|
||||
elixir_errors:form_error(Meta, File, ?MODULE, Error)
|
||||
end
|
||||
end.
|
||||
|
||||
find_dispatch(Tuple, List) ->
|
||||
[Receiver || { Receiver, Set } <- List, is_element(Tuple, Set)].
|
||||
|
||||
is_import(Meta, Tuple, S) ->
|
||||
case lists:keyfind(import, 1, Meta) of
|
||||
{ import, _ } = Import ->
|
||||
not_an_import(Tuple, S#elixir_scope.macro_functions)
|
||||
andalso not_an_import(Tuple, S#elixir_scope.macro_macros)
|
||||
andalso Import;
|
||||
false ->
|
||||
false
|
||||
end.
|
||||
|
||||
not_an_import(Tuple, Pairs) ->
|
||||
not lists:any(fun({ _, List }) -> lists:member(Tuple, List) end, Pairs).
|
||||
|
||||
munge_stacktrace(Info, [{ _, _, [S|_], _ }|_], S) ->
|
||||
[Info];
|
||||
@@ -217,10 +270,14 @@ munge_stacktrace(_, [], _) ->
|
||||
|
||||
%% ERROR HANDLING
|
||||
|
||||
format_error({ unrequired_module,{Receiver, Name, Arity, Required }}) ->
|
||||
format_error({ unrequired_module, { Receiver, Name, Arity, Required }}) ->
|
||||
String = string:join([elixir_errors:inspect(R) || R <- Required], ", "),
|
||||
io_lib:format("tried to invoke macro ~s.~s/~B but module was not required. Required: ~s",
|
||||
[elixir_errors:inspect(Receiver), Name, Arity, String]).
|
||||
io_lib:format("tried to invoke macro ~ts.~ts/~B but module was not required. Required: ~ts",
|
||||
[elixir_errors:inspect(Receiver), Name, Arity, String]);
|
||||
|
||||
format_error({ ambiguous_call, { Mod1, Mod2, Name, Arity }}) ->
|
||||
io_lib:format("function ~ts/~B imported from both ~ts and ~ts, call is ambiguous",
|
||||
[Name, Arity, elixir_errors:inspect(Mod1), elixir_errors:inspect(Mod2)]).
|
||||
|
||||
%% INTROSPECTION
|
||||
|
||||
@@ -228,7 +285,7 @@ remote_function(Meta, Receiver, Name, Arity, S) ->
|
||||
Line = ?line(Meta),
|
||||
|
||||
Final =
|
||||
case Receiver == ?BUILTIN andalso is_element({ Name, Arity }, in_erlang_functions()) of
|
||||
case (Receiver == ?BUILTIN) andalso is_element({ Name, Arity }, in_erlang_functions()) of
|
||||
true -> erlang;
|
||||
false -> Receiver
|
||||
end,
|
||||
@@ -394,4 +451,4 @@ in_erlang_macros() ->
|
||||
{'receive',1},
|
||||
{'try',1},
|
||||
{'xor',2}
|
||||
].
|
||||
].
|
||||
|
||||
@@ -4,8 +4,8 @@
|
||||
-export([syntax_error/3, syntax_error/4, inspect/1,
|
||||
form_error/4, parse_error/4, assert_module_scope/3,
|
||||
assert_no_function_scope/3, assert_function_scope/3,
|
||||
assert_no_assign_scope/3, assert_no_guard_scope/3,
|
||||
assert_no_assign_or_guard_scope/3,
|
||||
assert_no_match_scope/3, assert_no_guard_scope/3,
|
||||
assert_no_match_or_guard_scope/3,
|
||||
handle_file_warning/2, handle_file_warning/3, handle_file_error/2,
|
||||
deprecation/3, deprecation/4, file_format/3]).
|
||||
-include("elixir.hrl").
|
||||
@@ -97,7 +97,7 @@ handle_file_warning(_, _File, { _Line, v3_kernel, bad_call }) -> [];
|
||||
%% Rewrite
|
||||
handle_file_warning(_, File, {Line,erl_lint,{undefined_behaviour_func,{Fun,Arity},Module}}) ->
|
||||
Kind = protocol_or_behaviour(Module),
|
||||
Raw = "undefined ~s function ~s/~B (for ~s ~s)",
|
||||
Raw = "undefined ~ts function ~ts/~B (for ~ts ~ts)",
|
||||
Message = io_lib:format(Raw, [Kind, Fun, Arity, Kind, inspect(Module)]),
|
||||
io:format(file_format(Line, File, Message));
|
||||
|
||||
@@ -105,7 +105,7 @@ handle_file_warning(_, File, {Line,erl_lint,{undefined_behaviour,Module}}) ->
|
||||
case elixir_compiler:get_opt(internal) of
|
||||
true -> [];
|
||||
false ->
|
||||
Message = io_lib:format("behaviour ~s undefined", [inspect(Module)]),
|
||||
Message = io_lib:format("behaviour ~ts undefined", [inspect(Module)]),
|
||||
io:format(file_format(Line, File, Message))
|
||||
end;
|
||||
|
||||
@@ -137,26 +137,26 @@ handle_file_error(File, {Line,Module,Desc}) ->
|
||||
|
||||
assert_no_function_scope(_Meta, _Kind, #elixir_scope{function=nil}) -> [];
|
||||
assert_no_function_scope(Meta, Kind, S) ->
|
||||
syntax_error(Meta, S#elixir_scope.file, "cannot invoke ~s inside a function", [Kind]).
|
||||
syntax_error(Meta, S#elixir_scope.file, "cannot invoke ~ts inside a function", [Kind]).
|
||||
|
||||
assert_no_assign_or_guard_scope(Meta, Kind, S) ->
|
||||
assert_no_assign_scope(Meta, Kind, S),
|
||||
assert_no_match_or_guard_scope(Meta, Kind, S) ->
|
||||
assert_no_match_scope(Meta, Kind, S),
|
||||
assert_no_guard_scope(Meta, Kind, S).
|
||||
|
||||
assert_no_assign_scope(Meta, Kind, #elixir_scope{context=assign} = S) ->
|
||||
syntax_error(Meta, S#elixir_scope.file, "cannot invoke ~s inside assign", [Kind]);
|
||||
assert_no_assign_scope(_Meta, _Kind, _S) -> [].
|
||||
assert_no_match_scope(Meta, Kind, #elixir_scope{context=match} = S) ->
|
||||
syntax_error(Meta, S#elixir_scope.file, "cannot invoke ~ts inside match clause", [Kind]);
|
||||
assert_no_match_scope(_Meta, _Kind, _S) -> [].
|
||||
|
||||
assert_no_guard_scope(Meta, Kind, #elixir_scope{context=guard} = S) ->
|
||||
syntax_error(Meta, S#elixir_scope.file, "cannot invoke ~s inside guard", [Kind]);
|
||||
syntax_error(Meta, S#elixir_scope.file, "cannot invoke ~ts inside guard", [Kind]);
|
||||
assert_no_guard_scope(_Meta, _Kind, _S) -> [].
|
||||
|
||||
assert_module_scope(Meta, Kind, #elixir_scope{module=nil,file=File}) ->
|
||||
syntax_error(Meta, File, "cannot invoke ~s outside module", [Kind]);
|
||||
syntax_error(Meta, File, "cannot invoke ~ts outside module", [Kind]);
|
||||
assert_module_scope(_Meta, _Kind, #elixir_scope{module=Module}) -> Module.
|
||||
|
||||
assert_function_scope(Meta, Kind, #elixir_scope{function=nil,file=File}) ->
|
||||
syntax_error(Meta, File, "cannot invoke ~s outside function", [Kind]);
|
||||
syntax_error(Meta, File, "cannot invoke ~ts outside function", [Kind]);
|
||||
assert_function_scope(_Meta, _Kind, #elixir_scope{function=Function}) -> Function.
|
||||
|
||||
%% Helpers
|
||||
|
||||
@@ -41,9 +41,9 @@ import(Meta, Ref, Opts, Selector, S) ->
|
||||
false -> S;
|
||||
true ->
|
||||
FunctionsFun = fun(K) -> remove_underscored(K andalso Selector, get_functions(Ref)) end,
|
||||
Functions = calculate(Meta, Ref, Opts,
|
||||
S#elixir_scope.functions, FunctionsFun, S),
|
||||
S#elixir_scope{functions=Functions}
|
||||
{ Functions, TempF } = calculate(Meta, Ref, Opts,
|
||||
S#elixir_scope.functions, S#elixir_scope.macro_functions, FunctionsFun, S),
|
||||
S#elixir_scope{functions=Functions, macro_functions=TempF}
|
||||
end,
|
||||
|
||||
SM = case IncludeAll or (Selector == macros) of
|
||||
@@ -55,9 +55,9 @@ import(Meta, Ref, Opts, Selector, S) ->
|
||||
false -> get_macros(Meta, Ref, SF)
|
||||
end
|
||||
end,
|
||||
Macros = calculate(Meta, Ref, Opts,
|
||||
SF#elixir_scope.macros, MacrosFun, SF),
|
||||
SF#elixir_scope{macros=Macros}
|
||||
{ Macros, TempM } = calculate(Meta, Ref, Opts,
|
||||
SF#elixir_scope.macros, SF#elixir_scope.macro_macros, MacrosFun, SF),
|
||||
SF#elixir_scope{macros=Macros, macro_macros=TempM}
|
||||
end,
|
||||
|
||||
SM.
|
||||
@@ -66,9 +66,8 @@ import(Meta, Ref, Opts, Selector, S) ->
|
||||
|
||||
%% Calculates the imports based on only and except
|
||||
|
||||
calculate(Meta, Key, Opts, Old, AvailableFun, S) ->
|
||||
calculate(Meta, Key, Opts, Old, Temp, AvailableFun, S) ->
|
||||
File = S#elixir_scope.file,
|
||||
All = keydelete(Key, Old),
|
||||
|
||||
New = case keyfind(only, Opts) of
|
||||
{ only, RawOnly } ->
|
||||
@@ -88,22 +87,20 @@ calculate(Meta, Key, Opts, Old, AvailableFun, S) ->
|
||||
Except = expand_fun_arity(Meta, except, RawExcept, S),
|
||||
case keyfind(Key, Old) of
|
||||
false -> AvailableFun(true) -- Except;
|
||||
{Key,ToRemove} -> ToRemove -- Except
|
||||
{Key,OldImports} -> OldImports -- Except
|
||||
end
|
||||
end
|
||||
end,
|
||||
|
||||
%% Normalize the data before storing it
|
||||
Set = ordsets:from_list(New),
|
||||
Final = remove_internals(Set),
|
||||
Set = ordsets:from_list(New),
|
||||
Final = remove_internals(Set),
|
||||
|
||||
case Final of
|
||||
[] -> All;
|
||||
[] -> { keydelete(Key, Old), keydelete(Key, Temp) };
|
||||
_ ->
|
||||
ensure_no_conflicts(Meta, File, Final, keydelete(Key, S#elixir_scope.macros)),
|
||||
ensure_no_conflicts(Meta, File, Final, keydelete(Key, S#elixir_scope.functions)),
|
||||
ensure_no_in_erlang_macro_conflict(Meta, File, Key, Final, internal_conflict),
|
||||
[{ Key, Final }|All]
|
||||
{ [{ Key, Final }|keydelete(Key, Old)], [{ Key, Final }|keydelete(Key, Temp)] }
|
||||
end.
|
||||
|
||||
%% Ensure we are expanding macros and stuff
|
||||
@@ -120,14 +117,14 @@ cons_to_keywords(Meta, Kind, { cons, _, Left, Right }, S) ->
|
||||
|
||||
cons_to_keywords(Meta, Kind, _, S) ->
|
||||
elixir_errors:syntax_error(Meta, S#elixir_scope.file,
|
||||
"invalid value for :~s, expected a list with functions and arities", [Kind]).
|
||||
"invalid value for :~ts, expected a list with functions and arities", [Kind]).
|
||||
|
||||
tuple_to_fun_arity(_Meta, _Kind, { tuple, _, [{ atom, _, Atom }, { integer, _, Integer }] }, _S) ->
|
||||
{ Atom, Integer };
|
||||
|
||||
tuple_to_fun_arity(Meta, Kind, _, S) ->
|
||||
elixir_errors:syntax_error(Meta, S#elixir_scope.file,
|
||||
"invalid value for :~s, expected a list with functions and arities", [Kind]).
|
||||
"invalid value for :~ts, expected a list with functions and arities", [Kind]).
|
||||
|
||||
%% Retrieve functions and macros from modules
|
||||
|
||||
@@ -207,44 +204,28 @@ ensure_no_in_erlang_macro_conflict(Meta, File, Key, [{Name,Arity}|T], Reason) ->
|
||||
|
||||
ensure_no_in_erlang_macro_conflict(_Meta, _File, _Key, [], _) -> ok.
|
||||
|
||||
%% Find conlicts in the given list of functions with the set of imports.
|
||||
%% Used internally to ensure a newly imported fun or macro does not
|
||||
%% conflict with an already imported set.
|
||||
|
||||
ensure_no_conflicts(Meta, File, Functions, [{Key,Value}|T]) ->
|
||||
Filtered = lists:filter(fun(X) -> lists:member(X, Functions) end, Value),
|
||||
case Filtered of
|
||||
[{Name,Arity}|_] ->
|
||||
Tuple = { already_imported, { Key, Name, Arity } },
|
||||
elixir_errors:form_error(Meta, File, ?MODULE, Tuple);
|
||||
[] ->
|
||||
ensure_no_conflicts(Meta, File, Functions, T)
|
||||
end;
|
||||
|
||||
ensure_no_conflicts(_Meta, _File, _Functions, _S) -> ok.
|
||||
|
||||
%% ERROR HANDLING
|
||||
|
||||
format_error({already_imported,{Receiver, Name, Arity}}) ->
|
||||
io_lib:format("function ~s/~B already imported from ~s", [Name, Arity, elixir_errors:inspect(Receiver)]);
|
||||
io_lib:format("function ~ts/~B already imported from ~ts", [Name, Arity, elixir_errors:inspect(Receiver)]);
|
||||
|
||||
format_error({invalid_import,{Receiver, Name, Arity}}) ->
|
||||
io_lib:format("cannot import ~s.~s/~B because it doesn't exist",
|
||||
io_lib:format("cannot import ~ts.~ts/~B because it doesn't exist",
|
||||
[elixir_errors:inspect(Receiver), Name, Arity]);
|
||||
|
||||
format_error({import_conflict,{Receiver, Name, Arity}}) ->
|
||||
io_lib:format("imported ~s.~s/~B conflicts with local function",
|
||||
io_lib:format("imported ~ts.~ts/~B conflicts with local function",
|
||||
[elixir_errors:inspect(Receiver), Name, Arity]);
|
||||
|
||||
format_error({local_conflict,{_, Name, Arity}}) ->
|
||||
io_lib:format("cannot define local ~s/~B because it conflicts with Elixir special forms", [Name, Arity]);
|
||||
io_lib:format("cannot define local ~ts/~B because it conflicts with Elixir special forms", [Name, Arity]);
|
||||
|
||||
format_error({internal_conflict,{Receiver, Name, Arity}}) ->
|
||||
io_lib:format("cannot import ~s.~s/~B because it conflicts with Elixir special forms",
|
||||
io_lib:format("cannot import ~ts.~ts/~B because it conflicts with Elixir special forms",
|
||||
[elixir_errors:inspect(Receiver), Name, Arity]);
|
||||
|
||||
format_error({ no_macros, Module }) ->
|
||||
io_lib:format("could not load macros from module ~s", [elixir_errors:inspect(Module)]).
|
||||
io_lib:format("could not load macros from module ~ts", [elixir_errors:inspect(Module)]).
|
||||
|
||||
%% LIST HELPERS
|
||||
|
||||
|
||||
@@ -19,8 +19,8 @@ extract(Line, _File, _Interpol, [], _Buffer, [], _Output, Last) ->
|
||||
extract(Line, File, _Interpol, [Last|Remaining], Buffer, [], Output, Last) ->
|
||||
finish_extraction(Line, File, Buffer, Output, Remaining);
|
||||
|
||||
extract(Line, _File, _Interpol, [Last], _Buffer, Search, _Output, Last) ->
|
||||
{ error, { Line, io_lib:format("unexpected end of string: ~ts", [[hd(Search)]]), [Last] } };
|
||||
extract(Line, _File, _Interpol, End, _Buffer, _Search, _Output, Last) when End == [Last]; End == [] ->
|
||||
{ error, { Line, io_lib:format("missing terminator: ~ts", [[Last]]), [] } };
|
||||
|
||||
extract(Line, File, Interpol, [$\n|Rest], Buffer, Search, Output, Last) ->
|
||||
extract(Line+1, File, Interpol, Rest, [$\n|Buffer], Search, Output, Last);
|
||||
|
||||
@@ -9,7 +9,7 @@ translate({ '<<>>', Meta, Args } = Original, S) when is_list(Args) ->
|
||||
case elixir_partials:handle(Original, S, allow_tail) of
|
||||
error ->
|
||||
case S#elixir_scope.context of
|
||||
assign ->
|
||||
match ->
|
||||
build_bitstr(fun elixir_translator:translate_each/2, Args, Meta, S);
|
||||
_ ->
|
||||
{ TArgs, { SC, SV } } = build_bitstr(fun elixir_translator:translate_arg/2, Args, Meta, { S, S }),
|
||||
@@ -47,7 +47,7 @@ translate({ '[]', _Meta, Args } = Original, S) when is_list(Args) ->
|
||||
end,
|
||||
|
||||
{ FExprs, FS } = case S#elixir_scope.context of
|
||||
assign ->
|
||||
match ->
|
||||
elixir_tree_helpers:build_reverse_list(fun elixir_translator:translate_each/2, Exprs, 0, ListS, Tail);
|
||||
_ ->
|
||||
{ TArgs, { SC, SV } } =
|
||||
@@ -219,7 +219,7 @@ extract_bit_type_or_size(_Value, _Args, _Other, _Types) ->
|
||||
handle_unknown_specifier(Meta, [H|T], Size, Types, S) ->
|
||||
case 'Elixir.Macro':expand(H, elixir_scope:to_ex_env({ ?line(Meta), S })) of
|
||||
H ->
|
||||
elixir_errors:syntax_error(Meta, S#elixir_scope.file, "unknown bitstring specifier ~s", ['Elixir.Macro':to_binary(H)]);
|
||||
elixir_errors:syntax_error(Meta, S#elixir_scope.file, "unknown bitstring specifier ~ts", ['Elixir.Macro':to_binary(H)]);
|
||||
E ->
|
||||
extract_bit_values(Meta, join_expansion(E,T), Size, Types, S)
|
||||
end.
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
-import(elixir_translator, [translate_each/2, translate_args/2, translate_apply/7]).
|
||||
-import(elixir_scope, [umergec/2]).
|
||||
-import(elixir_errors, [syntax_error/3, syntax_error/4,
|
||||
assert_no_function_scope/3, assert_module_scope/3, assert_no_assign_or_guard_scope/3]).
|
||||
assert_no_function_scope/3, assert_module_scope/3, assert_no_match_or_guard_scope/3]).
|
||||
-include("elixir.hrl").
|
||||
|
||||
-define(FUNS(Kind), Kind == def; Kind == defp; Kind == defmacro; Kind == defmacrop).
|
||||
@@ -23,7 +23,7 @@ translate({ '-', _Meta, [Expr] }, S) when is_number(Expr) ->
|
||||
|
||||
translate({ Op, Meta, Exprs }, S) when is_list(Exprs),
|
||||
Op == '<-' orelse Op == '--' ->
|
||||
assert_no_assign_or_guard_scope(Meta, Op, S),
|
||||
assert_no_match_or_guard_scope(Meta, Op, S),
|
||||
translate_each({ '__op__', Meta, [Op|Exprs] }, S);
|
||||
|
||||
translate({ Op, Meta, Exprs }, S) when is_list(Exprs),
|
||||
@@ -53,34 +53,46 @@ translate({ in, Meta, [Left, Right] }, #elixir_scope{extra_guards=Extra} = S) ->
|
||||
%% Functions
|
||||
|
||||
translate({ function, Meta, [[{do,{ '->',_,Pairs}}]] }, S) ->
|
||||
assert_no_assign_or_guard_scope(Meta, 'function', S),
|
||||
assert_no_match_or_guard_scope(Meta, 'function', S),
|
||||
elixir_translator:translate_fn(Meta, Pairs, S);
|
||||
|
||||
translate({ function, Meta, [{ '/', _, [{{ '.', _ ,[M, F] }, _ , [] }, A]}] }, S) ->
|
||||
translate({ function, _, [{ '/', _, [{{ '.', Meta, [M, F] }, _ , []}, A]}] }, S) when is_atom(F), is_integer(A) ->
|
||||
translate({ function, Meta, [M, F, A] }, S);
|
||||
|
||||
translate({ function, Meta, [{ '/', _, [{F, _, Q}, A]}] }, S) when is_atom(Q) ->
|
||||
translate({ function, Meta, [F, A] }, S);
|
||||
translate({ function, MetaFA, [{ '/', _, [{F, Meta, C}, A]}] }, S) when is_atom(F), is_integer(A), is_atom(C) ->
|
||||
assert_no_match_or_guard_scope(Meta, 'function', S),
|
||||
|
||||
WrappedMeta =
|
||||
case (lists:keyfind(import, 1, Meta) == false) andalso lists:keyfind(import_fa, 1, MetaFA) of
|
||||
{ import_fa, Receiver } -> [{ import, Receiver }|Meta];
|
||||
false -> Meta
|
||||
end,
|
||||
|
||||
case elixir_dispatch:import_function(WrappedMeta, F, A, S) of
|
||||
false -> syntax_error(WrappedMeta, S#elixir_scope.file, "cannot convert a macro to a function");
|
||||
Else -> Else
|
||||
end;
|
||||
|
||||
translate({ function, Meta, [_] }, S) ->
|
||||
assert_no_assign_or_guard_scope(Meta, 'function', S),
|
||||
assert_no_match_or_guard_scope(Meta, 'function', S),
|
||||
syntax_error(Meta, S#elixir_scope.file, "invalid args for function");
|
||||
|
||||
translate({ function, Meta, [_, _] = Args }, S) ->
|
||||
assert_no_assign_or_guard_scope(Meta, 'function', S),
|
||||
assert_no_match_or_guard_scope(Meta, 'function', S),
|
||||
|
||||
case translate_args(Args, S) of
|
||||
{ [{atom,_,Name}, {integer,_,Arity}], SA } ->
|
||||
elixir_errors:deprecation(Meta, S#elixir_scope.file, "function(:~ts, ~B) is deprecated, please use function(~ts/~B)", [Name, Arity, Name, Arity]),
|
||||
case elixir_dispatch:import_function(Meta, Name, Arity, SA) of
|
||||
false -> syntax_error(Meta, S#elixir_scope.file, "cannot convert a macro to a function");
|
||||
Else -> Else
|
||||
end;
|
||||
_ ->
|
||||
syntax_error(Meta, S#elixir_scope.file, "cannot dynamically retrieve local function. use function(module, fun, arity) instead")
|
||||
syntax_error(Meta, S#elixir_scope.file, "cannot dynamically retrieve local function, use function/3 instead")
|
||||
end;
|
||||
|
||||
translate({ function, Meta, [_,_,_] = Args }, S) when is_list(Args) ->
|
||||
assert_no_assign_or_guard_scope(Meta, 'function', S),
|
||||
assert_no_match_or_guard_scope(Meta, 'function', S),
|
||||
{ [A,B,C], SA } = translate_args(Args, S),
|
||||
{ { 'fun', ?line(Meta), { function, A, B, C } }, SA };
|
||||
|
||||
@@ -112,7 +124,7 @@ translate({'@', Meta, [{ Name, _, Args }]}, S) ->
|
||||
}, S);
|
||||
_ ->
|
||||
syntax_error(Meta, S#elixir_scope.file,
|
||||
"cannot dynamically set attribute @~s inside a function", [Name])
|
||||
"cannot dynamically set attribute @~ts inside a function", [Name])
|
||||
end;
|
||||
_ when is_atom(Args) or (Args == []) ->
|
||||
case S#elixir_scope.function of
|
||||
@@ -127,14 +139,14 @@ translate({'@', Meta, [{ Name, _, Args }]}, S) ->
|
||||
{ elixir_tree_helpers:abstract_syntax(Contents), S }
|
||||
end;
|
||||
_ ->
|
||||
syntax_error(Meta, S#elixir_scope.file, "expected 0 or 1 argument for @~s, got: ~p", [Name, length(Args)])
|
||||
syntax_error(Meta, S#elixir_scope.file, "expected 0 or 1 argument for @~ts, got: ~p", [Name, length(Args)])
|
||||
end
|
||||
end;
|
||||
|
||||
%% Case
|
||||
|
||||
translate({'case', Meta, [Expr, KV]}, S) ->
|
||||
assert_no_assign_or_guard_scope(Meta, 'case', S),
|
||||
assert_no_match_or_guard_scope(Meta, 'case', S),
|
||||
Clauses = elixir_clauses:get_pairs(Meta, do, KV, S),
|
||||
{ TExpr, NS } = translate_each(Expr, S),
|
||||
|
||||
@@ -150,7 +162,7 @@ translate({'case', Meta, [Expr, KV]}, S) ->
|
||||
|
||||
translate({'try', Meta, [Clauses]}, RawS) ->
|
||||
S = RawS#elixir_scope{noname=true},
|
||||
assert_no_assign_or_guard_scope(Meta, 'try', S),
|
||||
assert_no_match_or_guard_scope(Meta, 'try', S),
|
||||
|
||||
Do = proplists:get_value('do', Clauses, nil),
|
||||
{ TDo, SB } = elixir_translator:translate_each(Do, S),
|
||||
@@ -169,7 +181,7 @@ translate({'try', Meta, [Clauses]}, RawS) ->
|
||||
%% Receive
|
||||
|
||||
translate({'receive', Meta, [KV] }, S) ->
|
||||
assert_no_assign_or_guard_scope(Meta, 'receive', S),
|
||||
assert_no_match_or_guard_scope(Meta, 'receive', S),
|
||||
Do = elixir_clauses:get_pairs(Meta, do, KV, S, true),
|
||||
|
||||
case lists:keyfind('after', 1, KV) of
|
||||
@@ -215,7 +227,8 @@ translate({defmodule, Meta, [Ref, KV]}, S) ->
|
||||
{ TRef, S }
|
||||
end,
|
||||
|
||||
{ elixir_module:translate(Meta, FRef, Block, S#elixir_scope{check_clauses=true}), FS };
|
||||
MS = FS#elixir_scope{check_clauses=true,local=nil},
|
||||
{ elixir_module:translate(Meta, FRef, Block, MS), FS };
|
||||
|
||||
translate({Kind, Meta, [Call]}, S) when ?FUNS(Kind) ->
|
||||
translate({Kind, Meta, [Call, nil]}, S);
|
||||
@@ -223,22 +236,9 @@ translate({Kind, Meta, [Call]}, S) when ?FUNS(Kind) ->
|
||||
translate({Kind, Meta, [Call, Expr]}, S) when ?FUNS(Kind) ->
|
||||
assert_module_scope(Meta, Kind, S),
|
||||
assert_no_function_scope(Meta, Kind, S),
|
||||
|
||||
{ TCall, Guards } = elixir_clauses:extract_guards(Call),
|
||||
{ Name, Args } = case elixir_clauses:extract_args(TCall) of
|
||||
error -> syntax_error(Meta, S#elixir_scope.file,
|
||||
"invalid syntax in ~s ~s", [Kind, 'Elixir.Macro':to_binary(TCall)]);
|
||||
Tuple -> Tuple
|
||||
end,
|
||||
|
||||
assert_no_aliases_name(Meta, Name, Args, S),
|
||||
|
||||
TName = elixir_tree_helpers:abstract_syntax(Name),
|
||||
TArgs = elixir_tree_helpers:abstract_syntax(Args),
|
||||
TGuards = elixir_tree_helpers:abstract_syntax(Guards),
|
||||
TExpr = elixir_tree_helpers:abstract_syntax(Expr),
|
||||
|
||||
{ elixir_def:wrap_definition(Kind, Meta, TName, TArgs, TGuards, TExpr, S), S };
|
||||
{ TCall, SC } = elixir_quote:user_quote(Call, S),
|
||||
{ TExpr, SE } = elixir_quote:user_quote(Expr, SC),
|
||||
{ elixir_def:wrap_definition(Kind, Meta, TCall, TExpr, SE), SE };
|
||||
|
||||
translate({Kind, Meta, [Name, Args, Guards, Expr]}, S) when ?FUNS(Kind) ->
|
||||
assert_module_scope(Meta, Kind, S),
|
||||
@@ -334,7 +334,7 @@ rewrite_case_clauses(Clauses) ->
|
||||
expand_module(Raw, Module, #elixir_scope{module=Nesting}) when is_atom(Raw); Nesting == nil ->
|
||||
Module;
|
||||
|
||||
expand_module({ '__aliases__', _, _ } = Alias, Module, S) ->
|
||||
expand_module({ '__aliases__', _, List } = Alias, Module, S) when List /= ['Elixir'] ->
|
||||
case elixir_aliases:expand(Alias, S#elixir_scope.aliases, S#elixir_scope.macro_aliases) of
|
||||
Atom when is_atom(Atom) ->
|
||||
Module;
|
||||
@@ -358,10 +358,3 @@ spec_to_macro(callback) -> defcallback.
|
||||
% Pack a list of expressions from a block.
|
||||
pack({ 'block', _, Exprs }) -> Exprs;
|
||||
pack(Expr) -> [Expr].
|
||||
|
||||
assert_no_aliases_name(Meta, '__aliases__', [Atom], #elixir_scope{file=File}) when is_atom(Atom) ->
|
||||
Message = "function names should start with lowercase characters or underscore, invalid name ~s",
|
||||
syntax_error(Meta, File, Message, [atom_to_binary(Atom, utf8)]);
|
||||
|
||||
assert_no_aliases_name(_Meta, _Aliases, _Args, _S) ->
|
||||
ok.
|
||||
@@ -143,7 +143,7 @@ eval_form(Line, Module, Block, Vars, RawS) ->
|
||||
functions_form(Line, File, Module, Export, Private, Def, Defmacro, Defmacrop, RawFunctions, C) ->
|
||||
Functions = case elixir_compiler:get_opt(internal, C) of
|
||||
true -> RawFunctions;
|
||||
false -> record_rewrite_functions(RawFunctions)
|
||||
false -> record_rewrite_functions(Module, RawFunctions)
|
||||
end,
|
||||
|
||||
{ FinalExport, FinalFunctions } =
|
||||
@@ -156,11 +156,11 @@ functions_form(Line, File, Module, Export, Private, Def, Defmacro, Defmacrop, Ra
|
||||
{attribute, Line, export, lists:sort(FinalExport)} | FinalFunctions
|
||||
] }.
|
||||
|
||||
record_rewrite_functions(Functions) ->
|
||||
record_rewrite_functions(Module, Functions) ->
|
||||
lists:map(fun
|
||||
({ function, Line, Name, Arity, Clauses }) ->
|
||||
Rewriten = [begin
|
||||
{ C, _, _ } = 'Elixir.Kernel.RecordRewriter':optimize_clause(Clause),
|
||||
{ C, _, _ } = 'Elixir.Kernel.RecordRewriter':optimize_clause(Module, Clause),
|
||||
C
|
||||
end || Clause <- Clauses],
|
||||
{ function, Line, Name, Arity, Rewriten };
|
||||
@@ -324,10 +324,10 @@ else_clause() ->
|
||||
% HELPERS
|
||||
|
||||
eval_callbacks(Line, Module, Name, Args, RawS) ->
|
||||
S = RawS#elixir_scope{check_clauses=false,check_requires=false},
|
||||
S = RawS#elixir_scope{check_clauses=false},
|
||||
Binding = binding_for_eval(Module, []),
|
||||
Callbacks = lists:reverse(ets:lookup_element(data_table(Module), Name, 2)),
|
||||
Meta = [{line,Line}],
|
||||
Meta = [{line,Line},{require,false}],
|
||||
|
||||
lists:foreach(fun({M,F}) ->
|
||||
{ Tree, _ } = elixir_dispatch:dispatch_require(Meta, M, F, Args, S, fun() ->
|
||||
@@ -347,14 +347,14 @@ eval_callbacks(Line, Module, Name, Args, RawS) ->
|
||||
% ERROR HANDLING
|
||||
|
||||
format_error({ internal_function_overridden, { Name, Arity } }) ->
|
||||
io_lib:format("function ~s/~B is internal and should not be overridden", [Name, Arity]);
|
||||
io_lib:format("function ~ts/~B is internal and should not be overridden", [Name, Arity]);
|
||||
|
||||
format_error({ invalid_module, Module}) ->
|
||||
io_lib:format("invalid module name: ~p", [Module]);
|
||||
|
||||
format_error({ module_defined, Module }) ->
|
||||
io_lib:format("redefining module ~s", [elixir_errors:inspect(Module)]);
|
||||
io_lib:format("redefining module ~ts", [elixir_errors:inspect(Module)]);
|
||||
|
||||
format_error({ module_in_definition, Module }) ->
|
||||
io_lib:format("cannot define module ~s because it is currently being defined",
|
||||
io_lib:format("cannot define module ~ts because it is currently being defined",
|
||||
[elixir_errors:inspect(Module)]).
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
handle(Original, S) ->
|
||||
handle(Original, S, default).
|
||||
|
||||
handle({ _, Meta, Args } = Original, S, Opt) when is_list(Args), S#elixir_scope.context /= assign ->
|
||||
handle({ _, Meta, Args } = Original, S, Opt) when is_list(Args), S#elixir_scope.context /= match ->
|
||||
case convert(Args, S, Opt) of
|
||||
{ Call, Def, SC } when Def /= [] ->
|
||||
Final = validate(Meta, Def, SC),
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
-module(elixir_quote).
|
||||
-export([quote/3, linify/2, join_quoted/5]).
|
||||
-export([quote/3, linify/2, unquote/5, user_quote/2]).
|
||||
-include("elixir.hrl").
|
||||
|
||||
%% Apply the line from site call on quoted contents.
|
||||
@@ -21,12 +21,12 @@ do_linify(Line, List) when is_list(List) ->
|
||||
|
||||
do_linify(_, Else) -> Else.
|
||||
|
||||
%% Join quoted checks for quote arguments at runtime
|
||||
%% in order to properly insert them into the tree
|
||||
join_quoted(Meta, Left, { '__aliases__', _, Args }, nil, _File) ->
|
||||
%% Some expressions cannot be unquoted at compilation time.
|
||||
%% This function is responisble for doing runtime unquoting.
|
||||
unquote(Meta, Left, { '__aliases__', _, Args }, nil, _File) ->
|
||||
{ '__aliases__', Meta, [Left|Args] };
|
||||
|
||||
join_quoted(Meta, Left, Right, nil, _File) when is_atom(Right) ->
|
||||
unquote(Meta, Left, Right, nil, _File) when is_atom(Right) ->
|
||||
case atom_to_list(Right) of
|
||||
"Elixir-" ++ _ ->
|
||||
{ '__aliases__', Meta, [Left, Right] };
|
||||
@@ -34,23 +34,45 @@ join_quoted(Meta, Left, Right, nil, _File) when is_atom(Right) ->
|
||||
{ { '.', Meta, [Left, Right] }, Meta, [] }
|
||||
end;
|
||||
|
||||
join_quoted(Meta, Left, Right, Args, _File) when is_atom(Right) ->
|
||||
unquote(Meta, Left, Right, Args, _File) when is_atom(Right) ->
|
||||
{ { '.', Meta, [Left, Right] }, Meta, Args };
|
||||
|
||||
join_quoted(Meta, _Left, _Right, _Args, File) ->
|
||||
unquote(Meta, _Left, _Right, _Args, File) ->
|
||||
elixir_errors:syntax_error(Meta, File, "expected unquote after dot to return an atom or an alias").
|
||||
|
||||
%% Translation
|
||||
%% Similar to quote, but the given code is meant to come directly from the user.
|
||||
%% Basically, lines are kept and hygiene mechanisms are disabled.
|
||||
user_quote(Expr, S) ->
|
||||
quote(Expr, #elixir_quote{
|
||||
line=keep,
|
||||
vars_hygiene=nil,
|
||||
aliases_hygiene=false,
|
||||
imports_hygiene=false,
|
||||
unquote=true
|
||||
}, S).
|
||||
|
||||
quote({ 'unquote_splicing', Meta, _ } = Expr, #elixir_quote{unquote=true} = Q, S) ->
|
||||
%% Quotes an expression into Erlang's AST
|
||||
|
||||
quote({ 'unquote_splicing', Meta, [_] } = Expr, #elixir_quote{unquote=true} = Q, S) ->
|
||||
do_quote({ '__block__', Meta, [Expr] }, Q, S);
|
||||
|
||||
quote(Else, Q, S) ->
|
||||
do_quote(Else, Q, S).
|
||||
|
||||
do_quote({ quote, _, Args } = Tuple, #elixir_quote{unquote=true} = Q, S) when length(Args) == 1; length(Args) == 2 ->
|
||||
do_quote_tuple(Tuple, Q#elixir_quote{unquote=false}, S);
|
||||
|
||||
do_quote({ unquote, _Meta, [Expr] }, #elixir_quote{unquote=true}, S) ->
|
||||
elixir_translator:translate_each(Expr, S);
|
||||
|
||||
do_quote({ function, Meta, [{ '/', _, [{F, _, C}, A]}] = Args },
|
||||
#elixir_quote{imports_hygiene=true} = Q, S) when is_atom(F), is_integer(A), is_atom(C) ->
|
||||
do_quote_fa(function, Meta, Args, F, A, Q, S);
|
||||
|
||||
do_quote({ { '.', _, [_, function] } = Target, Meta, [{ '/', _, [{F, _, C}, A]}] = Args },
|
||||
#elixir_quote{imports_hygiene=true} = Q, S) when is_atom(F), is_integer(A), is_atom(C) ->
|
||||
do_quote_fa(Target, Meta, Args, F, A, Q, S);
|
||||
|
||||
do_quote({ 'alias!', _Meta, [Expr] }, Q, S) ->
|
||||
do_quote(Expr, Q#elixir_quote{aliases_hygiene=false}, S);
|
||||
|
||||
@@ -72,12 +94,12 @@ do_quote({ '__aliases__', Meta, [H|T] } = Alias, #elixir_quote{aliases_hygiene=t
|
||||
do_quote({ { { '.', Meta, [Left, unquote] }, _, [Expr] }, _, Args }, #elixir_quote{unquote=true} = Q, S) ->
|
||||
All = [Left, { unquote, Meta, [Expr] }, Args, S#elixir_scope.file],
|
||||
{ TAll, TS } = lists:mapfoldl(fun(X, Acc) -> do_quote(X, Q, Acc) end, S, All),
|
||||
{ ?wrap_call(?line(Meta), elixir_quote, join_quoted, [meta(Meta, Q)|TAll]), TS };
|
||||
{ ?wrap_call(?line(Meta), elixir_quote, unquote, [meta(Meta, Q)|TAll]), TS };
|
||||
|
||||
do_quote({ { '.', Meta, [Left, unquote] }, _, [Expr] }, #elixir_quote{unquote=true} = Q, S) ->
|
||||
All = [Left, { unquote, Meta, [Expr] }, nil, S#elixir_scope.file],
|
||||
{ TAll, TS } = lists:mapfoldl(fun(X, Acc) -> do_quote(X, Q, Acc) end, S, All),
|
||||
{ ?wrap_call(?line(Meta), elixir_quote, join_quoted, [meta(Meta, Q)|TAll]), TS };
|
||||
{ ?wrap_call(?line(Meta), elixir_quote, unquote, [meta(Meta, Q)|TAll]), TS };
|
||||
|
||||
do_quote({ Left, Meta, nil }, Q, S) when is_atom(Left) ->
|
||||
Line = ?line(Meta),
|
||||
@@ -139,6 +161,16 @@ do_quote_tuple({ Left, Meta, Right }, Q, S) ->
|
||||
Tuple = { tuple, ?line(Meta), [TLeft, meta(Meta, Q), TRight] },
|
||||
{ Tuple, RS }.
|
||||
|
||||
do_quote_fa(Target, Meta, Args, F, A, Q, S) ->
|
||||
NewMeta =
|
||||
case (lists:keyfind(import_fa, 1, Meta) == false) andalso
|
||||
elixir_dispatch:find_import(Meta, F, A, S) of
|
||||
false -> Meta;
|
||||
Receiver -> [{ import_fa, Receiver }|Meta]
|
||||
end,
|
||||
|
||||
do_quote_tuple({ Target, NewMeta, Args }, Q, S).
|
||||
|
||||
% Loop through the list finding each unquote_splicing entry.
|
||||
|
||||
splice([{ unquote_splicing, _, [Args] }|T], #elixir_quote{unquote=true} = Q, Buffer, Acc, S) ->
|
||||
@@ -159,11 +191,9 @@ splice([], Q, Buffer, Acc, S) ->
|
||||
case NewAcc of
|
||||
[] ->
|
||||
{ { nil, line(Q) }, NewS };
|
||||
[List] ->
|
||||
{ List, NewS };
|
||||
_ ->
|
||||
List = elixir_tree_helpers:build_simple_reverse_list(line(Q), NewAcc),
|
||||
{ ?wrap_call(line(Q), lists, append, [List]), NewS }
|
||||
[H|T] ->
|
||||
Line = line(Q),
|
||||
{ lists:foldl(fun(L, R) -> { op, Line, '++', L, R } end, H, T), NewS }
|
||||
end.
|
||||
|
||||
from_buffer_to_acc([], _Q, Acc, S) ->
|
||||
|
||||
@@ -5,9 +5,9 @@
|
||||
build_erl_var/2, build_ex_var/2,
|
||||
serialize/1, deserialize/1,
|
||||
serialize_with_vars/2, deserialize_with_vars/2,
|
||||
to_erl_env/1, to_ex_env/1, filename/1,
|
||||
to_erl_env/1, to_ex_env/1,
|
||||
umergev/2, umergec/2, merge_clause_vars/2
|
||||
]).
|
||||
]).
|
||||
-include("elixir.hrl").
|
||||
-compile({parse_transform, elixir_transform}).
|
||||
|
||||
@@ -20,7 +20,7 @@ translate_var(Meta, Name, Kind, S, Callback) ->
|
||||
{ { var, Line, Name }, S };
|
||||
_ ->
|
||||
case S#elixir_scope.context of
|
||||
assign ->
|
||||
match ->
|
||||
TempVars = S#elixir_scope.temp_vars,
|
||||
case { orddict:is_key({ Name, Kind }, Vars), orddict:find(Name, TempVars) } of
|
||||
{ true, { ok, Kind } } ->
|
||||
@@ -77,27 +77,24 @@ build_ex_var(Line, Key, Name, S) when is_integer(Line) ->
|
||||
|
||||
% Handle Macro.Env conversion
|
||||
|
||||
to_erl_env(Scope) ->
|
||||
elixir_tree_helpers:abstract_syntax(to_ex_env(Scope)).
|
||||
|
||||
to_ex_env({ Line, Tuple }) when element(1, Tuple) == 'Elixir.Macro.Env', is_integer(Line) ->
|
||||
setelement(4, Tuple, Line);
|
||||
to_erl_env({ 'Elixir.Macro.Env', Module, File, _Line, Function, Aliases, Context, Requires, Functions, Macros }) ->
|
||||
#elixir_scope{module=Module,file=File,
|
||||
function=Function,aliases=Aliases,context=Context,
|
||||
requires=Requires,macros=Macros,functions=Functions}.
|
||||
|
||||
to_ex_env({ Line, #elixir_scope{module=Module,file=File,
|
||||
function=Function,aliases=Aliases,context=Context,
|
||||
requires=Requires,macros=Macros,functions=Functions} }) when is_integer(Line) ->
|
||||
{ 'Elixir.Macro.Env', Module, File, Line, Function, Aliases, Context, Requires, Functions, Macros }.
|
||||
|
||||
filename(#elixir_scope{file=File}) -> File;
|
||||
filename(Other) -> element(3, Other).
|
||||
|
||||
% Provides a tuple with only the scope information we want to serialize.
|
||||
|
||||
serialize(S) ->
|
||||
elixir_tree_helpers:abstract_syntax(
|
||||
{ S#elixir_scope.file, S#elixir_scope.functions, S#elixir_scope.check_clauses,
|
||||
S#elixir_scope.requires, S#elixir_scope.macros, S#elixir_scope.aliases,
|
||||
S#elixir_scope.macro_aliases, S#elixir_scope.scheduled }
|
||||
S#elixir_scope.macro_functions, S#elixir_scope.macro_macros, S#elixir_scope.macro_aliases,
|
||||
S#elixir_scope.scheduled }
|
||||
).
|
||||
|
||||
serialize_with_vars(Line, S) when is_integer(Line) ->
|
||||
@@ -115,7 +112,8 @@ serialize_with_vars(Line, S) when is_integer(Line) ->
|
||||
|
||||
deserialize(Tuple) -> deserialize_with_vars(Tuple, []).
|
||||
|
||||
deserialize_with_vars({ File, Functions, CheckClauses, Requires, Macros, Aliases, MacroAliases, Scheduled }, Vars) ->
|
||||
deserialize_with_vars({ File, Functions, CheckClauses, Requires, Macros,
|
||||
Aliases, MacroFunctions, MacroMacros, MacroAliases, Scheduled }, Vars) ->
|
||||
#elixir_scope{
|
||||
file=File,
|
||||
functions=Functions,
|
||||
@@ -123,6 +121,8 @@ deserialize_with_vars({ File, Functions, CheckClauses, Requires, Macros, Aliases
|
||||
requires=Requires,
|
||||
macros=Macros,
|
||||
aliases=Aliases,
|
||||
macro_functions=MacroFunctions,
|
||||
macro_macros=MacroMacros,
|
||||
macro_aliases=MacroAliases,
|
||||
scheduled=Scheduled,
|
||||
vars=orddict:from_list(Vars),
|
||||
|
||||
@@ -107,7 +107,7 @@ tokenize([], _Line, #scope{terminators=[]}, Tokens) ->
|
||||
|
||||
tokenize([], EndLine, #scope{terminators=[{ Start, StartLine }|_]}, _Tokens) ->
|
||||
End = terminator(Start),
|
||||
Message = io_lib:format("missing terminator: ~s (for \"~s\" starting at line ~B)", [End, Start, StartLine]),
|
||||
Message = io_lib:format("missing terminator: ~ts (for \"~ts\" starting at line ~B)", [End, Start, StartLine]),
|
||||
{ error, { EndLine, Message, [] } };
|
||||
|
||||
% Base integers
|
||||
@@ -148,7 +148,7 @@ tokenize([$%,S,H|T], Line, #scope{file=File} = Scope, Tokens) when not(?is_word(
|
||||
tokenize(Final, NewLine, Scope, [{ sigil, Line, S, Parts, Modifiers }|Tokens]);
|
||||
Error ->
|
||||
Sigil = [$%,S,H],
|
||||
interpolation_error(Error, " (for sigil ~s starting at line ~B)", [Sigil, Line])
|
||||
interpolation_error(Error, " (for sigil ~ts starting at line ~B)", [Sigil, Line])
|
||||
end;
|
||||
|
||||
% Char tokens
|
||||
@@ -211,6 +211,14 @@ tokenize("..." ++ Rest, Line, Scope, Tokens) ->
|
||||
tokenize(Rest, Line, Scope, [Token|Tokens]);
|
||||
|
||||
% ## Containers
|
||||
|
||||
tokenize([$.,T|Tail], Line, Scope, Tokens) when ?is_space(T) ->
|
||||
case [T|Tail] of
|
||||
[$\r,$\n|Rest] -> tokenize([$.|Rest], Line + 1, Scope, Tokens);
|
||||
[$\n|Rest] -> tokenize([$.|Rest], Line + 1, Scope, Tokens);
|
||||
[_|Rest] -> tokenize([$.|Rest], Line, Scope, Tokens)
|
||||
end;
|
||||
|
||||
tokenize(".<<>>" ++ Rest, Line, Scope, Tokens) ->
|
||||
handle_call_identifier(Rest, Line, '<<>>', Scope, Tokens);
|
||||
|
||||
@@ -394,13 +402,15 @@ tokenize([H|_] = String, Line, Scope, Tokens) when ?is_upcase(H) ->
|
||||
tokenize([H|_] = String, Line, Scope, Tokens) when ?is_downcase(H); H == $_ ->
|
||||
{ Rest, { Kind, _, Identifier } } = tokenize_any_identifier(String, Line, [], Scope),
|
||||
case handle_keyword(Line, Kind, Identifier, Tokens) of
|
||||
false ->
|
||||
nomatch ->
|
||||
tokenize(Rest, Line, Scope, [{ Kind, Line, Identifier }|Tokens]);
|
||||
[Check|T] ->
|
||||
{ ok, [Check|T] } ->
|
||||
case handle_terminator(Check, Scope) of
|
||||
{ error, _ } = Error -> Error;
|
||||
New -> tokenize(Rest, Line, New, [Check|T])
|
||||
end
|
||||
end;
|
||||
{ error, Token } ->
|
||||
{ error, { Line, "syntax error before: ", Token } }
|
||||
end;
|
||||
|
||||
% Ambiguous unary/binary operators tokens
|
||||
@@ -450,7 +460,7 @@ handle_strings(T, Line, H, #scope{file=File} = Scope, Tokens) ->
|
||||
Atom = unsafe_to_atom(unescape_chars(Bin), Scope),
|
||||
tokenize(Rest, NewLine, Scope, [{ kw_identifier, Line, Atom }|Tokens]);
|
||||
_ ->
|
||||
{ error, { Line, "invalid interpolation in key", [$"|T] } }
|
||||
{ error, { Line, "invalid interpolation in key ", [$"|T] } }
|
||||
end;
|
||||
{ NewLine, Parts, Rest } ->
|
||||
Token = { string_type(H),Line,unescape_tokens(Parts) },
|
||||
@@ -532,7 +542,7 @@ extract_heredoc(Line0, Rest0, Marker) ->
|
||||
|
||||
heredoc_error(ErrorLine, StartLine, Marker) ->
|
||||
Terminator = [Marker, Marker, Marker],
|
||||
Message = io_lib:format("missing terminator: ~s (for heredoc starting at line ~B)", [Terminator, StartLine]),
|
||||
Message = io_lib:format("missing terminator: ~ts (for heredoc starting at line ~B)", [Terminator, StartLine]),
|
||||
{ error, { ErrorLine, Message, [] } }.
|
||||
|
||||
%% Remove spaces from heredoc based on the position of the final quotes.
|
||||
@@ -723,7 +733,7 @@ tokenize_call_identifier(Kind, Line, Atom, Rest) ->
|
||||
|
||||
verify_kw_and_space(_Line, _Atom, [H|_], #scope{}) when ?is_space(H) -> ok;
|
||||
verify_kw_and_space(Line, Atom, _, #scope{file=File}) ->
|
||||
io:format("~ts:~w: keyword argument ~s: must be followed by space~n", [File, Line, Atom]).
|
||||
io:format("~ts:~w: keyword argument ~ts: must be followed by space~n", [File, Line, Atom]).
|
||||
|
||||
next_is_block([Space|Tokens]) when Space == $\t; Space == $\s ->
|
||||
next_is_block(Tokens);
|
||||
@@ -744,7 +754,7 @@ add_token_with_nl(Left, T) -> [Left|T].
|
||||
% Error handling
|
||||
|
||||
interpolation_error({ error, { Line, Message, Token } }, Extension, Args) ->
|
||||
{ error, { Line, io_lib:format("~s" ++ Extension, [Message|Args]), Token } }.
|
||||
{ error, { Line, io_lib:format("~ts" ++ Extension, [Message|Args]), Token } }.
|
||||
|
||||
% Terminators
|
||||
|
||||
@@ -788,7 +798,7 @@ check_terminator({ E, _ }, [{ S, _ }|Terminators]) when
|
||||
check_terminator({ E, Line }, [{ Start, StartLine }|_]) when
|
||||
E == 'end'; E == ')'; E == ']'; E == '}'; E == '>>' ->
|
||||
End = terminator(Start),
|
||||
Message = io_lib:format("missing terminator: ~s (for \"~s\" starting at line ~B)", [End, Start, StartLine]),
|
||||
Message = io_lib:format("missing terminator: ~ts (for \"~ts\" starting at line ~B)", [End, Start, StartLine]),
|
||||
{ error, { Line, Message, [] } };
|
||||
|
||||
check_terminator({ E, Line }, []) when
|
||||
@@ -807,29 +817,40 @@ terminator('<<') -> '>>'.
|
||||
|
||||
% Keywords check
|
||||
handle_keyword(Line, Identifier, Atom, [{ '.', _ }|_] = Tokens) ->
|
||||
[{ Identifier, Line, Atom }|Tokens];
|
||||
{ ok, [{ Identifier, Line, Atom }|Tokens] };
|
||||
|
||||
handle_keyword(Line, Identifier, Atom, Tokens) when
|
||||
Identifier == identifier; Identifier == do_identifier;
|
||||
Identifier == bracket_identifier; Identifier == paren_identifier ->
|
||||
case keyword(Atom) of
|
||||
true -> [{ Atom, Line }|Tokens];
|
||||
op -> add_token_with_nl({ Atom, Line }, Tokens);
|
||||
block -> [{ block_identifier, Line, Atom }|Tokens];
|
||||
false -> false
|
||||
true -> { ok, [{ Atom, Line }|Tokens] };
|
||||
op -> { ok, add_token_with_nl({ Atom, Line }, Tokens) };
|
||||
block -> { ok, [{ block_identifier, Line, Atom }|Tokens] };
|
||||
do ->
|
||||
case do_keyword_valid(Tokens) of
|
||||
true -> { ok, [{ Atom, Line }|Tokens] };
|
||||
false -> { error, "do" }
|
||||
end;
|
||||
false -> nomatch
|
||||
end;
|
||||
|
||||
handle_keyword(_, _, _, _) -> false.
|
||||
handle_keyword(_, _, _, _) -> nomatch.
|
||||
|
||||
do_keyword_valid([{ Atom, _ }|_]) ->
|
||||
is_boolean(keyword(Atom));
|
||||
do_keyword_valid(_) -> true.
|
||||
|
||||
% Keywords
|
||||
keyword('fn') -> true;
|
||||
keyword('do') -> true;
|
||||
keyword('end') -> true;
|
||||
keyword('true') -> true;
|
||||
keyword('false') -> true;
|
||||
keyword('nil') -> true;
|
||||
keyword('not') -> true;
|
||||
|
||||
% Special handling for do
|
||||
keyword('do') -> do;
|
||||
|
||||
% Bin operator keywords
|
||||
keyword('and') -> op;
|
||||
keyword('or') -> op;
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
-import(elixir_scope, [umergev/2, umergec/2]).
|
||||
-import(elixir_errors, [syntax_error/3, syntax_error/4, parse_error/4,
|
||||
assert_function_scope/3, assert_module_scope/3, assert_no_guard_scope/3,
|
||||
assert_no_assign_or_guard_scope/3]).
|
||||
assert_no_match_or_guard_scope/3]).
|
||||
-include("elixir.hrl").
|
||||
-compile({parse_transform, elixir_transform}).
|
||||
|
||||
@@ -35,6 +35,9 @@ translate(Forms, S) ->
|
||||
%% Those macros are "low-level". They are the basic mechanism
|
||||
%% that makes the language work and cannot be partially applied
|
||||
%% nor overwritten.
|
||||
%%
|
||||
%% =, ^, import, require and alias could be made non-special
|
||||
%% forms without causing any side effects.
|
||||
|
||||
%% Assignment operator
|
||||
|
||||
@@ -94,7 +97,7 @@ translate_each({ alias, Meta, [Ref] }, S) ->
|
||||
translate_each({ alias, Meta, [Ref,[]] }, S);
|
||||
|
||||
translate_each({ alias, Meta, [Ref, KV] }, S) ->
|
||||
assert_no_assign_or_guard_scope(Meta, alias, S),
|
||||
assert_no_match_or_guard_scope(Meta, alias, S),
|
||||
validate_opts(Meta, alias, [as], KV, S),
|
||||
|
||||
{ TRef, SR } = translate_each(Ref, S),
|
||||
@@ -140,7 +143,7 @@ translate_each({ require, Meta, [Ref] }, S) ->
|
||||
translate_each({ require, Meta, [Ref, []] }, S);
|
||||
|
||||
translate_each({ require, Meta, [Ref, KV] }, S) ->
|
||||
assert_no_assign_or_guard_scope(Meta, 'require', S),
|
||||
assert_no_match_or_guard_scope(Meta, 'require', S),
|
||||
validate_opts(Meta, require, [as], KV, S),
|
||||
|
||||
{ TRef, SR } = translate_each(Ref, S),
|
||||
@@ -173,7 +176,7 @@ translate_each({ import, Meta, [Selector, Left] }, S) ->
|
||||
translate_each({ import, Meta, [Selector, Left, []]}, S);
|
||||
|
||||
translate_each({ import, Meta, [Left, Right, Opts] }, S) ->
|
||||
assert_no_assign_or_guard_scope(Meta, 'import', S),
|
||||
assert_no_match_or_guard_scope(Meta, 'import', S),
|
||||
{ TSelector, SL } = translate_each(Left, S),
|
||||
{ TRef, SR } = translate_each(Right, SL),
|
||||
|
||||
@@ -207,7 +210,8 @@ translate_each({ '__FILE__', _Meta, Atom }, S) when is_atom(Atom) ->
|
||||
translate_each(S#elixir_scope.file, S);
|
||||
|
||||
translate_each({ '__ENV__', Meta, Atom }, S) when is_atom(Atom) ->
|
||||
{ elixir_scope:to_erl_env({ ?line(Meta), S }), S };
|
||||
Env = elixir_scope:to_ex_env({ ?line(Meta), S }),
|
||||
{ elixir_tree_helpers:abstract_syntax(Env), S };
|
||||
|
||||
translate_each({ '__CALLER__', Meta, Atom }, S) when is_atom(Atom) ->
|
||||
{ { var, ?line(Meta), '__CALLER__' }, S#elixir_scope{caller=true} };
|
||||
@@ -232,7 +236,7 @@ translate_each({ '__aliases__', Meta, _ } = Alias, S) ->
|
||||
|
||||
%% Quoting
|
||||
|
||||
translate_each({ Unquote, Meta, _Args }, S) when Unquote == unquote; Unquote == unquote_splicing ->
|
||||
translate_each({ Unquote, Meta, [_|_] }, S) when Unquote == unquote; Unquote == unquote_splicing ->
|
||||
syntax_error(Meta, S#elixir_scope.file, "~p called outside quote", [Unquote]);
|
||||
|
||||
translate_each({ quote, Meta, [Left, Right] }, S) ->
|
||||
@@ -355,7 +359,7 @@ translate_each({ 'var!', Meta, [_, _] }, S) ->
|
||||
%% Functions
|
||||
|
||||
translate_each({ fn, Meta, [[{do, { '->', _, Pairs }}]] }, S) ->
|
||||
assert_no_assign_or_guard_scope(Meta, 'fn', S),
|
||||
assert_no_match_or_guard_scope(Meta, 'fn', S),
|
||||
translate_fn(Meta, Pairs, S);
|
||||
|
||||
%% Comprehensions
|
||||
@@ -368,7 +372,7 @@ translate_each({ Kind, Meta, Args }, S) when is_list(Args), (Kind == lc) orelse
|
||||
translate_each({ super, Meta, Args } = Original, S) ->
|
||||
case elixir_partials:handle(Original, S) of
|
||||
error ->
|
||||
assert_no_assign_or_guard_scope(Meta, super, S),
|
||||
assert_no_match_or_guard_scope(Meta, super, S),
|
||||
Module = assert_module_scope(Meta, super, S),
|
||||
Function = assert_function_scope(Meta, super, S),
|
||||
elixir_def_overridable:ensure_defined(Meta, Module, Function, S),
|
||||
@@ -397,20 +401,21 @@ translate_each({ 'super?', Meta, [] }, S) ->
|
||||
|
||||
%% Variables
|
||||
|
||||
translate_each({ '^', Meta, [ { Name, _, Kind } ] }, S) when is_list(Kind) ->
|
||||
syntax_error(Meta, S#elixir_scope.file, "cannot use ^ with expression at ^~s, ^ must be used only with variables", [Name]);
|
||||
|
||||
translate_each({ '^', Meta, [ { Name, _, Kind } ] }, #elixir_scope{context=assign} = S) when is_atom(Kind) ->
|
||||
translate_each({ '^', Meta, [ { Name, _, Kind } ] }, #elixir_scope{context=match} = S) when is_atom(Name), is_atom(Kind) ->
|
||||
case orddict:find({ Name, Kind }, S#elixir_scope.vars) of
|
||||
{ ok, Value } ->
|
||||
{ { var, Meta, Value }, S };
|
||||
error ->
|
||||
syntax_error(Meta, S#elixir_scope.file, "unbound variable ^~s", [Name])
|
||||
syntax_error(Meta, S#elixir_scope.file, "unbound variable ^~ts", [Name])
|
||||
end;
|
||||
|
||||
translate_each({ '^', Meta, [ { Name, _, Kind } ] }, S) when is_atom(Kind) ->
|
||||
translate_each({ '^', Meta, [ { Name, _, Kind } ] }, S) when is_atom(Name), is_atom(Kind) ->
|
||||
syntax_error(Meta, S#elixir_scope.file,
|
||||
"cannot access variable ^~s outside of assignment", [Name]);
|
||||
"cannot use ^~ts outside of match clauses", [Name]);
|
||||
|
||||
translate_each({ '^', Meta, [ Expr ] }, S) ->
|
||||
syntax_error(Meta, S#elixir_scope.file,
|
||||
"the unary operator ^ can only be used with variables, invalid expression ^~ts", ['Elixir.Macro':to_binary(Expr)]);
|
||||
|
||||
translate_each({ Name, Meta, Kind }, S) when is_atom(Name), is_atom(Kind) ->
|
||||
elixir_scope:translate_var(Meta, Name, Kind, S, fun() ->
|
||||
@@ -425,28 +430,21 @@ translate_each({ Atom, Meta, Args } = Original, S) when is_atom(Atom) ->
|
||||
false ->
|
||||
case elixir_partials:handle(Original, S) of
|
||||
error ->
|
||||
case lists:keyfind(import, 1, Meta) of
|
||||
{ import, Receiver } ->
|
||||
{ TRes, TS } = translate_each({ { '.', Meta, [Receiver, Atom] }, Meta, Args },
|
||||
S#elixir_scope{check_requires=false}),
|
||||
{ TRes, TS#elixir_scope{check_requires=S#elixir_scope.check_requires} };
|
||||
false ->
|
||||
Callback = fun() ->
|
||||
case S#elixir_scope.context of
|
||||
guard ->
|
||||
Arity = length(Args),
|
||||
File = S#elixir_scope.file,
|
||||
case Arity of
|
||||
0 -> syntax_error(Meta, File, "unknown variable ~s or cannot invoke local ~s/~B inside guard", [Atom, Atom, Arity]);
|
||||
_ -> syntax_error(Meta, File, "cannot invoke local ~s/~B inside guard", [Atom, Arity])
|
||||
end;
|
||||
_ ->
|
||||
translate_local(Meta, Atom, Args, S)
|
||||
end
|
||||
end,
|
||||
Callback = fun() ->
|
||||
case S#elixir_scope.context of
|
||||
match ->
|
||||
Arity = length(Args),
|
||||
File = S#elixir_scope.file,
|
||||
case Arity of
|
||||
0 -> syntax_error(Meta, File, "unknown variable ~ts or cannot invoke local ~ts/~B inside guard", [Atom, Atom, Arity]);
|
||||
_ -> syntax_error(Meta, File, "cannot invoke local ~ts/~B inside guard", [Atom, Arity])
|
||||
end;
|
||||
_ ->
|
||||
translate_local(Meta, Atom, Args, S)
|
||||
end
|
||||
end,
|
||||
|
||||
elixir_dispatch:dispatch_import(Meta, Atom, Args, S, Callback)
|
||||
end;
|
||||
elixir_dispatch:dispatch_import(Meta, Atom, Args, S, Callback);
|
||||
Else -> Else
|
||||
end;
|
||||
Else -> Else
|
||||
@@ -484,7 +482,13 @@ translate_each({ { '.', _, [Expr] }, Meta, Args } = Original, S) ->
|
||||
{ TExpr, SE } = translate_each(Expr, S),
|
||||
case TExpr of
|
||||
{ atom, _, Atom } ->
|
||||
elixir_errors:deprecation(Meta, S#elixir_scope.file, "the :~s.() syntax is deprecated, please use ~s() instead", [Atom, Atom]),
|
||||
case atom_to_list(Atom) of
|
||||
[H|_] when (H >= $a andalso H =< $z); H == $_ ->
|
||||
elixir_errors:deprecation(Meta, S#elixir_scope.file, "the :~s.() syntax is deprecated, please use ~s() instead", [Atom, Atom]);
|
||||
_ ->
|
||||
elixir_errors:deprecation(Meta, S#elixir_scope.file, "the :~s.() syntax is deprecated", [Atom])
|
||||
end,
|
||||
|
||||
translate_each({ Atom, Meta, Args }, S);
|
||||
_ ->
|
||||
case elixir_partials:handle(Original, S) of
|
||||
@@ -540,13 +544,7 @@ translate_local(Meta, Name, Args, #elixir_scope{local=nil} = S) ->
|
||||
{ { call, Line, { atom, Line, Name }, TArgs }, NS };
|
||||
|
||||
translate_local(Meta, Name, Args, S) ->
|
||||
Line = ?line(Meta),
|
||||
{ TArgs, NS } = translate_args(Args, S),
|
||||
Remote = { remote, Line,
|
||||
{ atom, Line, S#elixir_scope.local },
|
||||
{ atom, Line, Name }
|
||||
},
|
||||
{ { call, Line, Remote, TArgs }, NS }.
|
||||
translate_each({ { '.', Meta, [S#elixir_scope.local, Name] }, Meta, Args }, S).
|
||||
|
||||
expand_var_context(_Meta, Atom, _Msg, _S) when is_atom(Atom) -> Atom;
|
||||
expand_var_context(Meta, Alias, Msg, S) ->
|
||||
@@ -582,9 +580,9 @@ no_alias_expansion(Other) ->
|
||||
|
||||
translate_arg(Arg, { Acc, S }) ->
|
||||
{ TArg, TAcc } = translate_each(Arg, Acc),
|
||||
{ TArg, { umergec(S, TAcc), umergev(S, TAcc) } }.
|
||||
{ TArg, { umergec(Acc, TAcc), umergev(S, TAcc) } }.
|
||||
|
||||
translate_args(Args, #elixir_scope{context=assign} = S) ->
|
||||
translate_args(Args, #elixir_scope{context=match} = S) ->
|
||||
translate(Args, S);
|
||||
|
||||
translate_args(Args, S) ->
|
||||
@@ -660,11 +658,14 @@ translate_comprehension_do(Meta, bc, _Expr, S) ->
|
||||
translate_comprehension_do(_Meta, _Kind, Expr, S) ->
|
||||
translate_each(Expr, S).
|
||||
|
||||
translate_comprehension_clause(_Meta, {inbits, Meta, [Left, Right]}, S) ->
|
||||
translate_comprehension_clause(_Meta, {inbits, Meta, [{ '<<>>', _, _} = Left, Right]}, S) ->
|
||||
{ TRight, SR } = translate_each(Right, S),
|
||||
{ TLeft, SL } = elixir_clauses:assigns(fun elixir_translator:translate_each/2, Left, SR),
|
||||
{ { b_generate, ?line(Meta), TLeft, TRight }, SL };
|
||||
|
||||
translate_comprehension_clause(_Meta, {inbits, Meta, [_Left, _Right]}, S) ->
|
||||
syntax_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_each(Right, S),
|
||||
{ TLeft, SL } = elixir_clauses:assigns(fun elixir_translator:translate_each/2, Left, SR),
|
||||
|
||||
@@ -57,7 +57,7 @@ each_clause(Meta, {rescue,_,_}, S) ->
|
||||
elixir_errors:syntax_error(Meta, S#elixir_scope.file, "too many arguments given for rescue");
|
||||
|
||||
each_clause(Meta, {Key,_,_}, S) ->
|
||||
elixir_errors:syntax_error(Meta, S#elixir_scope.file, "invalid key ~s in try", [Key]).
|
||||
elixir_errors:syntax_error(Meta, S#elixir_scope.file, "invalid key ~ts in try", [Key]).
|
||||
|
||||
%% Helpers
|
||||
|
||||
@@ -96,7 +96,7 @@ normalize_rescue(_, { '=', Meta, [{ Name, _, Atom } = Var, { '__aliases__', _, _
|
||||
false;
|
||||
|
||||
normalize_rescue(Meta, Condition, S) ->
|
||||
case elixir_translator:translate_each(Condition, S#elixir_scope{context=assign}) of
|
||||
case elixir_translator:translate_each(Condition, S#elixir_scope{context=match}) of
|
||||
{ { atom, _, Atom }, _ } ->
|
||||
normalize_rescue(Meta, { in, Meta, [{ '_', Meta, nil }, [Atom]] }, S);
|
||||
_ ->
|
||||
@@ -265,7 +265,7 @@ validate_rescue_access(_, _, _) -> [].
|
||||
format_error({ rescue_no_match, Var, Alias }) ->
|
||||
VarBinary = 'Elixir.Macro':to_binary(Var),
|
||||
AliasBinary = 'Elixir.Macro':to_binary(Alias),
|
||||
Message = "rescue clause (~s = ~s) can never match, maybe you meant to write: ~s in [~s] ?",
|
||||
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;
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
ExUnit.start []
|
||||
|
||||
defmodule KernelTest do
|
||||
use ExUnit.Case, async: true
|
||||
doctest Kernel
|
||||
doctest Kernel.SpecialForms
|
||||
doctest Access.List
|
||||
doctest Binary.Chars.List
|
||||
doctest Binary.Inspect.Atom
|
||||
doctest Binary.Inspect.BitString
|
||||
doctest Binary.Inspect.List
|
||||
doctest Binary.Inspect.Tuple
|
||||
doctest Binary.Inspect.Number
|
||||
doctest Binary.Inspect.Regex
|
||||
doctest Bitwise
|
||||
doctest Code
|
||||
doctest Dict
|
||||
doctest Enum
|
||||
doctest IO.ANSI
|
||||
doctest Regex
|
||||
doctest String
|
||||
doctest OptionParser
|
||||
doctest Path
|
||||
doctest Module
|
||||
doctest Macro
|
||||
doctest List
|
||||
doctest Keyword
|
||||
end
|
||||
@@ -62,6 +62,8 @@ defmodule Binary.Inspect.BitStringTest do
|
||||
assert inspect("f\no") == "\"f\\no\""
|
||||
assert inspect("f\\o") == "\"f\\\\o\""
|
||||
assert inspect("f\ao") == "\"f\\ao\""
|
||||
assert inspect(%B{#{#a}) == "\"\\\#{#a\""
|
||||
assert inspect(%B{ #{#a}) == "\" \\\#{#a\""
|
||||
end
|
||||
|
||||
test :utf8 do
|
||||
|
||||
@@ -5,7 +5,9 @@ defmodule Kernel.ExceptionTest do
|
||||
|
||||
test :is_exception do
|
||||
assert is_exception(RuntimeError.new)
|
||||
refute is_exception({ :foo, :bar })
|
||||
refute is_exception(empty_tuple)
|
||||
refute is_exception(a_tuple)
|
||||
refute is_exception(a_list)
|
||||
end
|
||||
|
||||
test :format_entry_with_no_file_or_line do
|
||||
@@ -74,4 +76,8 @@ defmodule Kernel.ExceptionTest do
|
||||
test :erlang_error_message do
|
||||
assert ErlangError.new(original: :sample).message == "erlang error: :sample"
|
||||
end
|
||||
end
|
||||
|
||||
defp empty_tuple, do: {}
|
||||
defp a_tuple, do: { :foo, :bar, :baz }
|
||||
defp a_list, do: [ :foo, :bar, :baz ]
|
||||
end
|
||||
|
||||
@@ -397,6 +397,24 @@ defmodule FileTest do
|
||||
end
|
||||
end
|
||||
|
||||
test :ls do
|
||||
{ :ok, value } = File.ls(fixture_path)
|
||||
assert List.member?(value, "code_sample.exs")
|
||||
assert List.member?(value, "file.txt")
|
||||
|
||||
{ :error, :enoent } = File.ls(fixture_path("non-existent-subdirectory"))
|
||||
end
|
||||
|
||||
test :ls! do
|
||||
value = File.ls!(fixture_path)
|
||||
assert List.member?(value, "code_sample.exs")
|
||||
assert List.member?(value, "file.txt")
|
||||
|
||||
assert_raise File.Error, fn ->
|
||||
File.ls!(fixture_path("non-existent-subdirectory"))
|
||||
end
|
||||
end
|
||||
|
||||
defmodule OpenReadWrite do
|
||||
use ExUnit.Case
|
||||
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
Code.require_file "../../test_helper.exs", __FILE__
|
||||
|
||||
defmodule GenEvent.BehaviourTest do
|
||||
use ExUnit.Case, async: true
|
||||
|
||||
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
|
||||
|
||||
test :using do
|
||||
{ :ok, pid } = :gen_event.start_link
|
||||
:gen_event.add_handler(pid, MyEventHandler, [])
|
||||
|
||||
:gen_event.notify(pid, {:notification, 1})
|
||||
:gen_event.notify(pid, {:notification, 2})
|
||||
|
||||
assert :gen_event.call(pid, MyEventHandler, :notifications) == [1,2]
|
||||
assert :gen_event.call(pid, MyEventHandler, :notifications) == []
|
||||
end
|
||||
|
||||
end
|
||||
@@ -8,7 +8,6 @@ defmodule IO.ANSITest do
|
||||
"Hello, #{IO.ANSI.red}world!#{IO.ANSI.reset}"
|
||||
assert IO.ANSI.escape("Hello, %{ red }world!", true) ==
|
||||
"Hello, #{IO.ANSI.red}world!#{IO.ANSI.reset}"
|
||||
|
||||
end
|
||||
|
||||
test :escape_multiple do
|
||||
@@ -26,8 +25,8 @@ defmodule IO.ANSITest do
|
||||
end
|
||||
|
||||
test :fragment do
|
||||
assert IO.ANSI.escape("%{red}") == "#{IO.ANSI.red}#{IO.ANSI.reset}"
|
||||
assert IO.ANSI.escape_fragment("") == ""
|
||||
assert IO.ANSI.escape("%{red}", true) == "#{IO.ANSI.red}#{IO.ANSI.reset}"
|
||||
assert IO.ANSI.escape_fragment("", true) == ""
|
||||
end
|
||||
|
||||
test :noop do
|
||||
|
||||
@@ -26,6 +26,14 @@ defmodule Kernel.AliasTest do
|
||||
alias List, as: Nested
|
||||
assert Nested.flatten([[13]]) == [13]
|
||||
end
|
||||
|
||||
defmodule Elixir do
|
||||
def sample, do: 1
|
||||
end
|
||||
|
||||
test :nested_elixir_alias do
|
||||
assert Kernel.AliasTest.Elixir.sample == 1
|
||||
end
|
||||
end
|
||||
|
||||
defmodule Kernel.AliasNestingGenerator do
|
||||
@@ -40,6 +48,16 @@ defmodule Kernel.AliasNestingGenerator do
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defmacro record do
|
||||
quote do
|
||||
defexception Parent, message: nil
|
||||
|
||||
defmodule Parent.Child do
|
||||
def b, do: Parent.new(message: "ok")
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defmodule Kernel.AliasNestingTest do
|
||||
@@ -54,28 +72,14 @@ defmodule Kernel.AliasNestingTest do
|
||||
end
|
||||
end
|
||||
|
||||
defmodule Kernel.FullAliasNestingGenerator do
|
||||
defmacro create do
|
||||
quote do
|
||||
defmodule Parent do
|
||||
def a, do: :a
|
||||
end
|
||||
|
||||
defmodule Parent.Child do
|
||||
def b, do: Parent.a
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defmodule Kernel.FullAliasNestingTest do
|
||||
defmodule Kernel.AliasMacroNestingTest do
|
||||
use ExUnit.Case, async: true
|
||||
|
||||
require Kernel.FullAliasNestingGenerator
|
||||
Kernel.FullAliasNestingGenerator.create
|
||||
require Kernel.AliasNestingGenerator
|
||||
Kernel.AliasNestingGenerator.record
|
||||
|
||||
test :aliases_nesting do
|
||||
assert Parent.a == :a
|
||||
assert Parent.Child.b == :a
|
||||
assert is_record(Parent.new, Parent)
|
||||
assert Parent.Child.b.message == "ok"
|
||||
end
|
||||
end
|
||||
|
||||
@@ -96,11 +96,11 @@ defmodule Kernel.CLI.ParallelCompilerTest do
|
||||
assert File.regular?(tmp_path "Elixir-Bar.beam")
|
||||
end
|
||||
|
||||
test :deadlock_failure do
|
||||
test :possible_deadlock do
|
||||
output = elixirc('#{fixture_path("parallel_deadlock")} -o #{tmp_path}')
|
||||
foo = '== Compilation error on file #{fixture_path "parallel_deadlock/foo.ex"} (undefined module Bar) =='
|
||||
bar = '== Compilation error on file #{fixture_path "parallel_deadlock/bar.ex"} (undefined module Foo) =='
|
||||
assert :string.str(output, foo) > 0 or :string.str(output, bar) > 0,
|
||||
"Expected #{inspect output} to contain #{inspect foo} or #{inspect bar}"
|
||||
foo = '* #{fixture_path "parallel_deadlock/foo.ex"} is missing module Bar'
|
||||
bar = '* #{fixture_path "parallel_deadlock/bar.ex"} is missing module Foo'
|
||||
assert :string.str(output, foo) > 0, "expected foo.ex to miss module Bar"
|
||||
assert :string.str(output, bar) > 0, "expected bar.ex to miss module Foo"
|
||||
end
|
||||
end
|
||||
|
||||
@@ -55,12 +55,12 @@ defmodule Kernel.ErrorsTest do
|
||||
assert "nofile:1: unbound variable ^x" == format_rescue('^x = 1')
|
||||
end
|
||||
|
||||
test :unbound_not_assignment do
|
||||
assert "nofile:1: cannot access variable ^x outside of assignment" == format_rescue('^x')
|
||||
test :unbound_not_match do
|
||||
assert "nofile:1: cannot use ^x outside of match clauses" == format_rescue('^x')
|
||||
end
|
||||
|
||||
test :unbound_expr do
|
||||
assert "nofile:1: cannot use ^ with expression at ^x, ^ must be used only with variables" == format_rescue('^x(1)')
|
||||
assert "nofile:1: the unary operator ^ can only be used with variables, invalid expression ^x(1)" == format_rescue('^x(1)')
|
||||
end
|
||||
|
||||
test :name_for_defmodule do
|
||||
@@ -108,9 +108,9 @@ defmodule Kernel.ErrorsTest do
|
||||
format_rescue 'defmodule Foo do def Bar do :baz end\nend\n'
|
||||
end
|
||||
|
||||
test :erlang_function_conflict do
|
||||
assert "nofile:1: function exit/1 already imported from Kernel" ==
|
||||
format_rescue 'defmodule Foo do import Kernel.ErrorsTest.UnproperMacro, only: [exit: 1]\nend'
|
||||
test :function_import_conflict do
|
||||
assert "nofile:2: function exit/1 imported from both erlang and Kernel, call is ambiguous" ==
|
||||
format_rescue 'defmodule Foo do import :erlang\n def foo, do: exit(:test)\nend'
|
||||
end
|
||||
|
||||
test :import_invalid_macro do
|
||||
@@ -119,7 +119,7 @@ defmodule Kernel.ErrorsTest do
|
||||
end
|
||||
|
||||
test :unrequired_macro do
|
||||
assert "nofile:2: tried to invoke macro Kernel.ErrorsTest.UnproperMacro.unproper/1 but module was not required. Required: Kernel, Kernel.Typespec" ==
|
||||
assert "nofile:2: tried to invoke macro Kernel.ErrorsTest.UnproperMacro.unproper/1 but module was not required. Required: Kernel, Kernel.Typespec, Record" ==
|
||||
format_rescue 'defmodule Foo do\nKernel.ErrorsTest.UnproperMacro.unproper([])\nend'
|
||||
end
|
||||
|
||||
@@ -200,7 +200,7 @@ defmodule Kernel.ErrorsTest do
|
||||
end
|
||||
|
||||
test :invalid_access_protocol_not_alias do
|
||||
assert "invalid usage of access protocol in signature" ==
|
||||
assert "the access protocol cannot be used inside match clauses (for example, on the left hand side of a match or in function signatures)" ==
|
||||
format_rescue 'defmodule Foo do\ndef sample(config[integer: 0]), do: true\nend'
|
||||
end
|
||||
|
||||
@@ -224,7 +224,7 @@ defmodule Kernel.ErrorsTest do
|
||||
format_rescue 'defmodule Foo do\ndef sample(Kernel.ErrorsTest.Config[foo: :bar]), do: true\nend'
|
||||
end
|
||||
|
||||
test :invalid_access_protocol_invalid_keywords_outside_assignment do
|
||||
test :invalid_access_protocol_invalid_keywords_outside_match do
|
||||
assert "record Kernel.ErrorsTest.Config does not have the keys: [:foo]" ==
|
||||
format_rescue 'Kernel.ErrorsTest.Config[foo: :bar]'
|
||||
end
|
||||
@@ -234,9 +234,14 @@ defmodule Kernel.ErrorsTest do
|
||||
format_rescue 'try do\n1\nrescue\nUndefinedFunctionError[arity: 1] -> false\nend'
|
||||
end
|
||||
|
||||
test :invalid_bc do
|
||||
test :invalid_bc_return do
|
||||
assert "nofile:1: a bit comprehension expects a bit string << >> to be returned" ==
|
||||
format_rescue 'bc x in [1,2,3], do: x'
|
||||
format_rescue 'bc x inlist [1,2,3], do: x'
|
||||
end
|
||||
|
||||
test :invalid_bc_inbits_gen do
|
||||
assert "nofile:1: a bit comprehension expects a bit string << >> to be used in inbits generators" ==
|
||||
format_rescue 'bc x inbits "123", do: <<x>>'
|
||||
end
|
||||
|
||||
test :unbound_cond do
|
||||
@@ -303,4 +308,4 @@ defmodule Kernel.ErrorsTest do
|
||||
|
||||
result || raise(ExUnit.AssertionError, message: "Expected function given to format_rescue to fail")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -109,4 +109,16 @@ defmodule Kernel.ImportMacrosTest do
|
||||
assert bor(0, 1) == 1
|
||||
assert bnot(0) == -1
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defmodule Kernel.AmbiguousImportTest do
|
||||
use ExUnit.Case, async: true
|
||||
|
||||
test :import_ambiguous do
|
||||
# Simply make sure that we can indeed import functions with
|
||||
# the same name and arity from different modules without the
|
||||
# import itself causing any errors.
|
||||
import List
|
||||
import String
|
||||
end
|
||||
end
|
||||
|
||||
@@ -62,6 +62,36 @@ defmodule Kernel.QuoteTest do
|
||||
end
|
||||
end
|
||||
|
||||
test :nested_quote do
|
||||
assert { :quote, _, [[do: { :unquote, _, _ }]] } = quote(do: quote(do: unquote(x)))
|
||||
end
|
||||
|
||||
defmacrop nested_quote_in_macro do
|
||||
x = 1
|
||||
quote do
|
||||
x = unquote(x)
|
||||
quote do
|
||||
unquote(x)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
test :nested_quote_in_macro do
|
||||
assert nested_quote_in_macro == 1
|
||||
end
|
||||
|
||||
Enum.each [foo: 1, bar: 2, baz: 3], fn { k, v } ->
|
||||
def unquote(k)(arg) do
|
||||
unquote(v) + arg
|
||||
end
|
||||
end
|
||||
|
||||
test :dynamic_definition_with_unquote do
|
||||
assert foo(1) == 2
|
||||
assert bar(2) == 4
|
||||
assert baz(3) == 6
|
||||
end
|
||||
|
||||
test :splice_on_root do
|
||||
contents = [1, 2, 3]
|
||||
assert quote(do: unquote_splicing(contents)) == quote do: (1; 2; 3)
|
||||
@@ -206,9 +236,30 @@ defmodule Kernel.QuoteTest.ImportsHygieneTest do
|
||||
end
|
||||
end
|
||||
|
||||
defmacrop get_bin_size_with_partial do
|
||||
quote do
|
||||
size(&1).("hello")
|
||||
end
|
||||
end
|
||||
|
||||
defmacrop get_bin_size_with_function do
|
||||
quote do
|
||||
function(size/1).("hello")
|
||||
end
|
||||
end
|
||||
|
||||
defmacrop get_bin_size_with_kernel_function do
|
||||
quote do
|
||||
Kernel.function(size/1).("hello")
|
||||
end
|
||||
end
|
||||
|
||||
test :expand_imports do
|
||||
import Kernel, except: [size: 1]
|
||||
assert get_bin_size == 5
|
||||
assert get_bin_size_with_partial == 5
|
||||
assert get_bin_size_with_function == 5
|
||||
assert get_bin_size_with_kernel_function == 5
|
||||
end
|
||||
|
||||
defmacrop get_dict_size do
|
||||
@@ -224,4 +275,23 @@ defmodule Kernel.QuoteTest.ImportsHygieneTest do
|
||||
import Dict, only: [size: 1]
|
||||
assert get_dict_size == 2
|
||||
end
|
||||
|
||||
defmacrop with_size do
|
||||
quote do
|
||||
import Kernel, except: [size: 1]
|
||||
import Dict, only: [size: 1]
|
||||
size([a: 1, b: 2])
|
||||
end
|
||||
end
|
||||
|
||||
defmacrop with_nested_size do
|
||||
quote do
|
||||
with_size
|
||||
end
|
||||
end
|
||||
|
||||
test :explicitly_overriden_imports do
|
||||
assert with_size == 2
|
||||
assert with_nested_size == 2
|
||||
end
|
||||
end
|
||||
|
||||
@@ -24,7 +24,7 @@ defmodule Kernel.RecordRewriterTest do
|
||||
end
|
||||
|
||||
defp optimize_clause(clause) do
|
||||
{ clause, dict, res } = Kernel.RecordRewriter.optimize_clause(clause)
|
||||
{ clause, dict, res } = Kernel.RecordRewriter.optimize_clause(__MODULE__, clause)
|
||||
dict = Enum.map dict, fn { k, { v, _ } } -> { k, v }; other -> other; end
|
||||
{ clause, dict, res }
|
||||
end
|
||||
@@ -134,11 +134,6 @@ defmodule Kernel.RecordRewriterTest do
|
||||
assert optimize_clause(clause) == { clause, [], nil }
|
||||
end
|
||||
|
||||
test "inside bit comprehension" do
|
||||
clause = clause(fn -> bc x = Macro.Env[] inbits sample, do: <<x>> end)
|
||||
assert optimize_clause(clause) == { clause, [], nil }
|
||||
end
|
||||
|
||||
test "inside function retrieval" do
|
||||
clause = clause(fn -> function(x = Macro.Env[], y, z) end)
|
||||
assert optimize_clause(clause) == { clause, [x: Macro.Env], nil }
|
||||
|
||||
@@ -48,4 +48,39 @@ defmodule Kernel.SigilsTest do
|
||||
assert %C(f#{o}o) == 'f\#{o}o'
|
||||
assert %C(f\no) == 'f\\no'
|
||||
end
|
||||
end
|
||||
|
||||
test :__w__ do
|
||||
assert %w(foo bar baz) == ["foo", "bar", "baz"]
|
||||
assert %w(foo #{:bar} baz) == ["foo", "bar", "baz"]
|
||||
|
||||
assert %w(
|
||||
foo
|
||||
bar
|
||||
baz
|
||||
) == ["foo", "bar", "baz"]
|
||||
|
||||
assert %w(foo bar baz)b == ["foo", "bar", "baz"]
|
||||
assert %w(foo bar baz)a == [:foo, :bar, :baz]
|
||||
assert %w(foo bar baz)c == ['foo', 'bar', 'baz']
|
||||
|
||||
bad_modifier = quote do: %w(foo bar baz)x
|
||||
assert ArgumentError[] = catch_error(Code.eval_quoted(bad_modifier))
|
||||
end
|
||||
|
||||
test :__W__ do
|
||||
assert %W(foo #{bar} baz) == ["foo", "\#{bar}", "baz"]
|
||||
|
||||
assert %W(
|
||||
foo
|
||||
bar
|
||||
baz
|
||||
) == ["foo", "bar", "baz"]
|
||||
|
||||
assert %W(foo bar baz)b == ["foo", "bar", "baz"]
|
||||
assert %W(foo bar baz)a == [:foo, :bar, :baz]
|
||||
assert %W(foo bar baz)c == ['foo', 'bar', 'baz']
|
||||
|
||||
bad_modifier = quote do: %W(foo bar baz)x
|
||||
assert ArgumentError[] = catch_error(Code.eval_quoted(bad_modifier))
|
||||
end
|
||||
end
|
||||
|
||||
@@ -93,6 +93,20 @@ defmodule KernelTest do
|
||||
assert float_to_binary(7.0) == "7.00000000000000000000e+00"
|
||||
end
|
||||
|
||||
case :proplists.get_value(:float_to_binary,
|
||||
:proplists.get_value(:exports, :erlang.module_info, [])) do
|
||||
2 ->
|
||||
# We can only test this where this functionality is available
|
||||
test :float_to_binary_with_options do
|
||||
assert float_to_binary(7.1, [decimals: 2]) == "7.10"
|
||||
assert float_to_binary(7.1, [scientific: 2]) == "7.10e+00"
|
||||
assert float_to_binary(7.1, [decimals: 2, compact: true]) == "7.1"
|
||||
assert float_to_binary(7.1, [scientific: 2, compact: true]) == "7.10e+00"
|
||||
assert float_to_binary(7.1, [decimals: 2, compact: false]) == "7.10"
|
||||
end
|
||||
_ ->
|
||||
:ok
|
||||
end
|
||||
test :atom_to_binary_defaults_to_utf8 do
|
||||
expected = atom_to_binary :some_binary, :utf8
|
||||
actual = atom_to_binary :some_binary
|
||||
@@ -127,32 +141,19 @@ defmodule KernelTest do
|
||||
defmodule Function do
|
||||
use ExUnit.Case, async: true
|
||||
|
||||
test :retrieve_remote_function do
|
||||
assert is_function(function(:erlang, :atom_to_list, 1))
|
||||
assert :erlang.fun_info(function(:erlang, :atom_to_list, 1), :arity) == {:arity, 1}
|
||||
assert function(:erlang, :atom_to_list, 1).(:a) == 'a'
|
||||
end
|
||||
|
||||
test :remote_syntax_function do
|
||||
assert function(:erlang, :atom_to_list, 1).(:a) == 'a'
|
||||
assert function(:erlang.atom_to_list/1) ==
|
||||
function(:erlang, :atom_to_list, 1)
|
||||
assert function(Enum.map/2) == function(Enum, :map, 2)
|
||||
end
|
||||
|
||||
test :retrieve_local_function do
|
||||
assert is_function(function(:atl, 1))
|
||||
assert :erlang.fun_info(function(:atl, 1), :arity) == {:arity, 1}
|
||||
assert function(:atl, 1).(:a) == 'a'
|
||||
end
|
||||
|
||||
test :local_syntax_function do
|
||||
assert function(atl/1).(:a) == 'a'
|
||||
end
|
||||
|
||||
test :retrieve_imported_function do
|
||||
assert is_function(function(:atom_to_list, 1))
|
||||
assert :erlang.fun_info(function(:atom_to_list, 1), :arity) == {:arity, 1}
|
||||
assert function(:atom_to_list, 1).(:a) == 'a'
|
||||
assert function(atom_to_list/1).(:a) == 'a'
|
||||
end
|
||||
|
||||
test :retrieve_dynamic_function do
|
||||
@@ -160,8 +161,6 @@ defmodule KernelTest do
|
||||
b = :atom_to_list
|
||||
c = 1
|
||||
|
||||
assert is_function(function(a, b, c))
|
||||
assert :erlang.fun_info(function(a, b, c), :arity) == {:arity, 1}
|
||||
assert function(a, b, c).(:a) == 'a'
|
||||
end
|
||||
|
||||
@@ -186,6 +185,9 @@ defmodule KernelTest do
|
||||
defdelegate my_flatten(list), to: List, as: :flatten
|
||||
defdelegate [map(callback, list)], to: :lists, append_first: true
|
||||
|
||||
dynamic = :dynamic_flatten
|
||||
defdelegate unquote(dynamic)(list), to: List, as: :flatten
|
||||
|
||||
test :defdelegate_with_function do
|
||||
assert my_flatten([[1]]) == [1]
|
||||
end
|
||||
@@ -193,6 +195,10 @@ defmodule KernelTest do
|
||||
test :defdelegate_with_appended_handle do
|
||||
assert map([1], fn(x) -> x + 1 end) == [2]
|
||||
end
|
||||
|
||||
test :dynamic_defdelegate do
|
||||
assert dynamic_flatten([[1]]) == [1]
|
||||
end
|
||||
end
|
||||
|
||||
defmodule PipelineOp do
|
||||
|
||||
@@ -36,6 +36,59 @@ defmodule MacroTest do
|
||||
assert [1,{:{}, [], [:a,:b,:c]},3] == Macro.escape([1, { :a, :b, :c },3])
|
||||
end
|
||||
|
||||
test :escape_with_unquote do
|
||||
contents = quote unquote: false, do: unquote(1)
|
||||
assert Macro.escape_quoted(contents) == 1
|
||||
|
||||
contents = quote unquote: false, do: unquote(x)
|
||||
assert Macro.escape_quoted(contents) == { :x, [], MacroTest }
|
||||
end
|
||||
|
||||
defp eval_escaped(contents) do
|
||||
{ eval, [] } = Code.eval_quoted(Macro.escape_quoted(contents))
|
||||
eval
|
||||
end
|
||||
|
||||
test :escape_with_remote_unquote do
|
||||
contents = quote unquote: false, do: Kernel.unquote(:is_atom)(:ok)
|
||||
assert eval_escaped(contents) == quote(do: Kernel.is_atom(:ok))
|
||||
end
|
||||
|
||||
test :escape_with_nested_unquote do
|
||||
contents = quote do
|
||||
quote do: unquote(x)
|
||||
end
|
||||
assert eval_escaped(contents) == quote do: (quote do: unquote(x))
|
||||
end
|
||||
|
||||
test :escape_with_alias_or_no_args_remote_unquote do
|
||||
contents = quote unquote: false, do: Kernel.unquote(:self)
|
||||
assert eval_escaped(contents) == quote(do: Kernel.self())
|
||||
|
||||
contents = quote unquote: false, do: x.unquote(Foo)
|
||||
assert eval_escaped(contents) == quote(do: x.unquote(Foo))
|
||||
end
|
||||
|
||||
test :escape_with_splicing do
|
||||
contents = quote unquote: false, do: [1,2,3,4,5]
|
||||
assert Macro.escape_quoted(contents) == [1,2,3,4,5]
|
||||
|
||||
contents = quote unquote: false, do: [1,2,unquote_splicing([3,4,5])]
|
||||
assert eval_escaped(contents) == [1,2,3,4,5]
|
||||
|
||||
contents = quote unquote: false, do: [unquote_splicing([1,2,3]), 4, 5]
|
||||
assert eval_escaped(contents) == [1,2,3,4,5]
|
||||
|
||||
contents = quote unquote: false, do: [unquote_splicing([1,2,3]), unquote_splicing([4, 5])]
|
||||
assert eval_escaped(contents) == [1,2,3,4,5]
|
||||
|
||||
contents = quote unquote: false, do: [1, unquote_splicing([2]), 3, unquote_splicing([4]), 5]
|
||||
assert eval_escaped(contents) == [1,2,3,4,5]
|
||||
|
||||
contents = quote unquote: false, do: [1, unquote_splicing([2]), 3, unquote_splicing([4])|[5]]
|
||||
assert eval_escaped(contents) == [1,2,3,4,5]
|
||||
end
|
||||
|
||||
## Expand aliases
|
||||
|
||||
test :expand_with_raw_atom do
|
||||
|
||||
@@ -178,6 +178,7 @@ defmodule ModuleTest do
|
||||
end
|
||||
|
||||
test :to_binary do
|
||||
assert Module.to_binary(Elixir) == "Elixir"
|
||||
assert Module.to_binary(Hello.World) == "Hello.World"
|
||||
end
|
||||
|
||||
|
||||
@@ -98,10 +98,10 @@ defmodule PathTest do
|
||||
end
|
||||
|
||||
test :expand_path_with_user_home do
|
||||
assert is_binary Path.expand("~")
|
||||
assert System.user_home! == Path.expand("~")
|
||||
assert is_binary Path.expand("~/foo")
|
||||
|
||||
assert is_list Path.expand('~')
|
||||
assert (System.user_home! |> :unicode.characters_to_list) == Path.expand('~')
|
||||
assert is_list Path.expand('~/foo')
|
||||
end
|
||||
|
||||
|
||||
@@ -3,6 +3,16 @@ Code.require_file "../test_helper.exs", __FILE__
|
||||
defrecord RecordTest.FileInfo,
|
||||
Record.extract(:file_info, from_lib: "kernel/include/file.hrl")
|
||||
|
||||
defmodule RecordTest.FileInfo.Helper do
|
||||
Record.import RecordTest.FileInfo, as: :file_info
|
||||
|
||||
def new do
|
||||
file_info
|
||||
end
|
||||
|
||||
def size(file_info(size: size)), do: size
|
||||
end
|
||||
|
||||
defrecord RecordTest.SomeRecord, a: 0, b: 1
|
||||
defrecord RecordTest.WithNoField, []
|
||||
|
||||
@@ -33,6 +43,12 @@ end
|
||||
defmodule RecordTest.Macros do
|
||||
defrecordp :_user, name: "José", age: 25
|
||||
|
||||
defrecord Nested do
|
||||
def nested_record_alias?(Nested[]) do
|
||||
true
|
||||
end
|
||||
end
|
||||
|
||||
def new() do
|
||||
_user()
|
||||
end
|
||||
@@ -103,6 +119,11 @@ defmodule RecordTest do
|
||||
|
||||
test :is_record do
|
||||
assert is_record(RecordTest.FileInfo.new, RecordTest.FileInfo)
|
||||
assert is_record(RecordTest.WithNoField.new)
|
||||
refute is_record(empty_tuple)
|
||||
refute is_record(a_list)
|
||||
refute is_record(empty_tuple, RecordTest.FileInfo)
|
||||
refute is_record(a_tuple, RecordTest.FileInfo)
|
||||
refute is_record(a_list, RecordTest.FileInfo)
|
||||
refute is_record(RecordTest.FileInfo.new, List)
|
||||
end
|
||||
@@ -166,12 +187,23 @@ defmodule RecordTest do
|
||||
refute { :update_b, 2 } inlist RecordTest.DynamicName.__record__(:optimizable)
|
||||
end
|
||||
|
||||
test :result do
|
||||
assert { :module, _, _, "result"} = (defrecord WithResult, foo: :bar do
|
||||
"result"
|
||||
end)
|
||||
end
|
||||
|
||||
test :import do
|
||||
assert RecordTest.FileInfo.Helper.new == RecordTest.FileInfo.new
|
||||
assert RecordTest.FileInfo.Helper.size(RecordTest.FileInfo.new(size: 100)) == 100
|
||||
end
|
||||
|
||||
defp file_info do
|
||||
{ :ok, file_info } = :file.read_file_info(__FILE__)
|
||||
file_info
|
||||
end
|
||||
|
||||
defp a_list do
|
||||
[:a, :b, :c]
|
||||
end
|
||||
defp empty_tuple, do: {}
|
||||
defp a_tuple, do: { :foo, :bar, :baz }
|
||||
defp a_list, do: [ :foo, :bar, :baz ]
|
||||
end
|
||||
|
||||
@@ -17,8 +17,14 @@ defmodule StringTest do
|
||||
|
||||
test :split do
|
||||
assert String.split("foo bar") == ["foo", "bar"]
|
||||
assert String.split("a,b,c", ",") == ["a", "b", "c"]
|
||||
assert String.split(" foo bar") == ["foo", "bar"]
|
||||
assert String.split("foo bar ") == ["foo", "bar"]
|
||||
assert String.split(" foo bar ") == ["foo", "bar"]
|
||||
assert String.split("foo\t\n\v\f\r\sbar\n") == ["foo", "bar"]
|
||||
assert String.split("foo" <> <<31>> <> "bar") == ["foo", "bar"]
|
||||
assert String.split("foo" <> <<194,133>> <> "bar") == ["foo", "bar"]
|
||||
|
||||
assert String.split("a,b,c", ",") == ["a", "b", "c"]
|
||||
assert String.split("a,b", ".") == ["a,b"]
|
||||
assert String.split("1,2 3,4", [" ", ","]) == ["1", "2", "3", "4"]
|
||||
|
||||
|
||||
@@ -8,7 +8,15 @@ defmodule TupleTest do
|
||||
end
|
||||
|
||||
test :setelem do
|
||||
assert setelem({ :a, :b, :c }, 1, :d) == { :a, :d, :c }
|
||||
assert set_elem({ :a, :b, :c }, 1, :d) == { :a, :d, :c }
|
||||
end
|
||||
|
||||
test :insert_elem do
|
||||
assert insert_elem({ :bar, :baz }, 0, :foo) == { :foo, :bar, :baz }
|
||||
end
|
||||
|
||||
test :delete_elem do
|
||||
assert delete_elem({ :foo, :bar, :baz }, 0) == { :bar, :baz }
|
||||
end
|
||||
|
||||
test :optional_comma do
|
||||
|
||||
@@ -234,6 +234,9 @@ andand_test() ->
|
||||
end,
|
||||
test_helper:run_and_remove(F, ['Elixir.Bar']).
|
||||
|
||||
andand_with_literal_test() ->
|
||||
{[nil, nil, nil], _} = eval("[nil && 2, nil && 3, nil && 4]").
|
||||
|
||||
oror_test() ->
|
||||
F = fun() ->
|
||||
eval("defmodule Bar do\ndef foo, do: true\ndef bar, do: false\n def baz(x), do: x == 1\nend"),
|
||||
|
||||
@@ -88,6 +88,28 @@ function_calls_with_multiple_args_with_line_breaks_test() ->
|
||||
function_calls_with_parenthesis_test() ->
|
||||
{3, [{a,_},{b,1}]} = eval("a = (fn x -> x + 2 end).(b = 1)").
|
||||
|
||||
function_call_with_a_single_space_test() ->
|
||||
{3, _} = eval("a = fn a, b -> a + b end; a. (1,2)"),
|
||||
{3, _} = eval("a = fn a, b -> a + b end; a .(1,2)").
|
||||
|
||||
function_call_with_spaces_test() ->
|
||||
{3, _} = eval("a = fn a, b -> a + b end; a . (1,2)").
|
||||
|
||||
function_call_without_assigning_with_spaces_test() ->
|
||||
{3, _} = eval("(fn a, b -> a + b end) . (1,2)").
|
||||
|
||||
function_call_with_assignment_and_spaces_test() ->
|
||||
{3, [{a,_},{c,3}]} = eval("a = fn x -> x + 2 end; c = a . (1)").
|
||||
|
||||
function_call_with_multiple_spaces_test() ->
|
||||
{3, _} = eval("a = fn a, b -> a + b end; a . (1,2)").
|
||||
|
||||
function_call_with_multiline_test() ->
|
||||
{3, _} = eval("a = fn a, b -> a + b end; a . \n (1,2)").
|
||||
|
||||
function_call_with_tabs_test() ->
|
||||
{3, _} = eval("a = fn a, b -> a + b end; a .\n\t(1,2)").
|
||||
|
||||
%% Partial application
|
||||
|
||||
require_partial_application_test() ->
|
||||
@@ -96,4 +118,4 @@ require_partial_application_test() ->
|
||||
|
||||
import_partial_application_test() ->
|
||||
{ Fun, _ } = eval("is_atom(&1)"),
|
||||
Fun = fun erlang:is_atom/1.
|
||||
Fun = fun erlang:is_atom/1.
|
||||
|
||||
@@ -98,7 +98,8 @@ bin_string_with_escaped_interpolation_test() ->
|
||||
{<<"f#{'o}o">>, _} = eval("\"f\\#{'o}o\"").
|
||||
|
||||
invalid_string_interpolation_test() ->
|
||||
?assertError({'Elixir.SyntaxError', _, _, _, _}, eval("\"f#{{}o\"")).
|
||||
?assertError({'Elixir.TokenMissingError', _, _, _, _}, eval("\"f#{some\"")),
|
||||
?assertError({'Elixir.TokenMissingError', _, _, _, _}, eval("\"f#{1+")).
|
||||
|
||||
unterminated_string_interpolation_test() ->
|
||||
?assertError({'Elixir.TokenMissingError', _, _, _, _}, eval("\"foo")).
|
||||
|
||||
@@ -1,4 +1,12 @@
|
||||
defmodule ExUnit do
|
||||
defrecord Test, [:name, :case, :failure] do
|
||||
@moduledoc """
|
||||
A record that keeps information about the test.
|
||||
It is received by formatters and also accessible
|
||||
in the metadata under the key `:test`.
|
||||
"""
|
||||
end
|
||||
|
||||
@moduledoc """
|
||||
Basic unit test structure for Elixir.
|
||||
|
||||
@@ -43,30 +51,13 @@ defmodule ExUnit do
|
||||
The `ExUnit.Assertions` module contains a set of macros to easily
|
||||
generate assertions with appropriate error messages.
|
||||
|
||||
## User config
|
||||
|
||||
When started, ExUnit automatically reads a user configuration
|
||||
from the following locations, in this order:
|
||||
|
||||
* $EXUNIT_CONFIG environment variable
|
||||
* $HOME/.ex_unit.exs
|
||||
|
||||
If none found, no user config will be read.
|
||||
|
||||
User config is an elixir file which should return a keyword list
|
||||
with ex_unit options. Please note that explicit options passed
|
||||
to start/1 or configure/1 will take precedence over user options.
|
||||
|
||||
# User config example (~/.ex_unit.exs)
|
||||
[formatter: ExUnit.Formatter.ANSI]
|
||||
|
||||
"""
|
||||
|
||||
use Application.Behaviour
|
||||
|
||||
@doc false
|
||||
def start(_type, []) do
|
||||
ExUnit.Sup.start_link(user_options)
|
||||
ExUnit.Sup.start_link([])
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -94,23 +85,6 @@ defmodule ExUnit do
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns the configured user options.
|
||||
"""
|
||||
def user_options(user_config // nil) do
|
||||
user_config = user_config ||
|
||||
System.get_env("EXUNIT_CONFIG") ||
|
||||
Path.join(System.get_env("HOME"), ".ex_unit.exs")
|
||||
|
||||
case File.read(user_config) do
|
||||
{ :ok, contents } ->
|
||||
{ config, _ } = Code.eval(contents, [], file: user_config)
|
||||
config
|
||||
_ ->
|
||||
[]
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Configures ExUnit.
|
||||
|
||||
@@ -129,13 +103,6 @@ defmodule ExUnit do
|
||||
ExUnit.Server.merge_options(options)
|
||||
end
|
||||
|
||||
@doc false
|
||||
def after_spawn(callback) do
|
||||
IO.puts "ExUnit.after_spawn is deprecated, please use setup_all instead"
|
||||
Exception.print_stacktrace
|
||||
ExUnit.Server.add_after_spawn(callback)
|
||||
end
|
||||
|
||||
@doc """
|
||||
API used to run the tests. It is invoked automatically
|
||||
if ExUnit is started via `ExUnit.start`.
|
||||
|
||||
@@ -1,10 +1,18 @@
|
||||
defexception ExUnit.AssertionError, message: "assertion failed"
|
||||
|
||||
defexception ExUnit.ExpectationError, expected: nil, actual: nil, reason: "", negation: false, prelude: "Expected" do
|
||||
defexception ExUnit.ExpectationError, expected: nil, actual: nil, reason: "", negation: false, prelude: "Expected", description: nil do
|
||||
def message(exception) do
|
||||
"#{exception.prelude} #{inspect exception.expected} to " <>
|
||||
if(exception.negation, do: "not ", else: "") <>
|
||||
"be #{exception.reason} #{inspect exception.actual}"
|
||||
if desc = exception.description do
|
||||
"#{exception.prelude} #{desc} #{exception.full_reason} " <>
|
||||
"#{exception.expected}. Insted got #{exception.actual}"
|
||||
else
|
||||
"#{exception.prelude} #{exception.expected} " <>
|
||||
"#{exception.full_reason} #{exception.actual}"
|
||||
end
|
||||
end
|
||||
|
||||
def full_reason(exception) do
|
||||
"to" <> if(exception.negation, do: " not ", else: " ") <> exception.reason
|
||||
end
|
||||
end
|
||||
|
||||
@@ -73,84 +81,96 @@ defmodule ExUnit.Assertions do
|
||||
|
||||
## START HELPERS
|
||||
|
||||
defmacrop negation?(op) do
|
||||
quote do: (var!(op) == :! or var!(op) == :not)
|
||||
end
|
||||
|
||||
defp translate_assertion({ :=, _, [_, _] } = expr, _else) do
|
||||
expr
|
||||
defp translate_assertion({ :=, _, [left, right] }, _else) do
|
||||
quote do
|
||||
right = unquote(right)
|
||||
case right do
|
||||
unquote(left) ->
|
||||
right
|
||||
_ ->
|
||||
raise ExUnit.ExpectationError,
|
||||
expected: inspect(right),
|
||||
actual: unquote(Macro.to_binary(left)),
|
||||
reason: "match pattern (=)"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defp translate_assertion({ :==, _, [left, right] }, _else) do
|
||||
assert_operator :==, left, right, "equal to (==)"
|
||||
assert_operator :==, left, right, "be equal to (==)"
|
||||
end
|
||||
|
||||
defp translate_assertion({ :<, _, [left, right] }, _else) do
|
||||
assert_operator :<, left, right, "less than"
|
||||
assert_operator :<, left, right, "be less than"
|
||||
end
|
||||
|
||||
defp translate_assertion({ :>, _, [left, right] }, _else) do
|
||||
assert_operator :>, left, right, "more than"
|
||||
assert_operator :>, left, right, "be more than"
|
||||
end
|
||||
|
||||
defp translate_assertion({ :<=, _, [left, right] }, _else) do
|
||||
assert_operator :<=, left, right, "less than or equal to"
|
||||
assert_operator :<=, left, right, "be less than or equal to"
|
||||
end
|
||||
|
||||
defp translate_assertion({ :>=, _, [left, right] }, _else) do
|
||||
assert_operator :>=, left, right, "more than or equal to"
|
||||
assert_operator :>=, left, right, "be more than or equal to"
|
||||
end
|
||||
|
||||
defp translate_assertion({ :===, _, [left, right] }, _else) do
|
||||
assert_operator :===, left, right, "equal to (===)"
|
||||
assert_operator :===, left, right, "be equal to (===)"
|
||||
end
|
||||
|
||||
defp translate_assertion({ :!==, _, [left, right] }, _else) do
|
||||
assert_operator :!==, left, right, "not equal to (!==)"
|
||||
assert_operator :!==, left, right, "be not equal to (!==)"
|
||||
end
|
||||
|
||||
defp translate_assertion({ :!=, _, [left, right] }, _else) do
|
||||
assert_operator :!=, left, right, "not equal to (!=)"
|
||||
assert_operator :!=, left, right, "be not equal to (!=)"
|
||||
end
|
||||
|
||||
defp translate_assertion({ :=~, _, [left, right] }, _else) do
|
||||
assert_operator :=~, left, right, "a match (=~) with"
|
||||
assert_operator :=~, left, right, "match (=~)"
|
||||
end
|
||||
|
||||
defp translate_assertion({ :inlist, _, [left, right] }, _else) do
|
||||
quote do
|
||||
left = unquote(left)
|
||||
right = unquote(right)
|
||||
assert List.member?(right, left), left, right, reason: "in"
|
||||
assert List.member?(right, left), left, right, reason: "be in"
|
||||
end
|
||||
end
|
||||
|
||||
## Negative versions
|
||||
|
||||
defp translate_assertion({ op, _, [{ :=, _, [expected, received] }] }, _else) when negation?(op) do
|
||||
defp translate_assertion({ :!, _, [{ :=, _, [left, right] }] }, _else) do
|
||||
quote do
|
||||
try do
|
||||
unquote(expected) = x = unquote(received)
|
||||
flunk "Unexpected right side #{inspect x} match"
|
||||
rescue
|
||||
_ in [MatchError] -> true
|
||||
right = unquote(right)
|
||||
case right do
|
||||
unquote(left) ->
|
||||
raise ExUnit.ExpectationError,
|
||||
expected: inspect(right),
|
||||
actual: unquote(Macro.to_binary(left)),
|
||||
reason: "match pattern (=)",
|
||||
negation: true
|
||||
_ ->
|
||||
nil
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defp translate_assertion({ op, _, [{ :=~, _, [left, right] }] }, _else) when negation?(op) do
|
||||
defp translate_assertion({ :!, _, [{ :=~, _, [left, right] }] }, _else) do
|
||||
quote do
|
||||
left = unquote(left)
|
||||
right = unquote(right)
|
||||
assert !(left =~ right), left, right, reason: "a match (=~) with", negation: true
|
||||
assert !(left =~ right), left, right, reason: "match (=~)", negation: true
|
||||
end
|
||||
end
|
||||
|
||||
defp translate_assertion({ op, _, [{ :inlist, _, [left, right] }] }, _else) when negation?(op) do
|
||||
defp translate_assertion({ negation, _, [{ :inlist, _, [left, right] }] }, _else) when negation in [:!, :not] do
|
||||
quote do
|
||||
left = unquote(left)
|
||||
right = unquote(right)
|
||||
assert !List.member?(right, left), left, right, reason: "in", negation: true
|
||||
assert !List.member?(right, left), left, right, reason: "be in", negation: true
|
||||
end
|
||||
end
|
||||
|
||||
@@ -195,7 +215,10 @@ defmodule ExUnit.Assertions do
|
||||
|
||||
"""
|
||||
def assert(value, expected, actual, opts) do
|
||||
unless value, do: raise(ExUnit.ExpectationError, Keyword.merge([expected: expected, actual: actual], opts))
|
||||
unless value do
|
||||
raise ExUnit.ExpectationError,
|
||||
Keyword.merge([expected: inspect(expected), actual: inspect(actual)], opts)
|
||||
end
|
||||
true
|
||||
end
|
||||
|
||||
@@ -215,7 +238,7 @@ defmodule ExUnit.Assertions do
|
||||
|
||||
You can also match against specific patterns:
|
||||
|
||||
assert_received { :hello, _ }
|
||||
assert_receive { :hello, _ }
|
||||
|
||||
"""
|
||||
defmacro assert_receive(expected, timeout // 100, message // nil) do
|
||||
|
||||
@@ -87,7 +87,7 @@ defmodule ExUnit.Callbacks do
|
||||
defmacro setup(var // quote(do: _), block) do
|
||||
quote do
|
||||
name = :"__exunit_setup_#{length(@exunit_setup)}"
|
||||
defp name, [unquote(Macro.escape var)], [], unquote(Macro.escape block)
|
||||
defp name, [unquote(escape var)], [], unquote(escape block)
|
||||
@exunit_setup [name|@exunit_setup]
|
||||
end
|
||||
end
|
||||
@@ -95,7 +95,7 @@ defmodule ExUnit.Callbacks do
|
||||
defmacro teardown(var // quote(do: _), block) do
|
||||
quote do
|
||||
name = :"__exunit_teardown_#{length(@exunit_teardown)}"
|
||||
defp name, [unquote(Macro.escape var)], [], unquote(Macro.escape block)
|
||||
defp name, [unquote(escape var)], [], unquote(escape block)
|
||||
@exunit_teardown [name|@exunit_teardown]
|
||||
end
|
||||
end
|
||||
@@ -103,7 +103,7 @@ defmodule ExUnit.Callbacks do
|
||||
defmacro setup_all(var // quote(do: _), block) do
|
||||
quote do
|
||||
name = :"__exunit_setup_all_#{length(@exunit_setup_all)}"
|
||||
defp name, [unquote(Macro.escape var)], [], unquote(Macro.escape block)
|
||||
defp name, [unquote(escape var)], [], unquote(escape block)
|
||||
@exunit_setup_all [name|@exunit_setup_all]
|
||||
end
|
||||
end
|
||||
@@ -111,7 +111,7 @@ defmodule ExUnit.Callbacks do
|
||||
defmacro teardown_all(var // quote(do: _), block) do
|
||||
quote do
|
||||
name = :"__exunit_teardown_all_#{length(@exunit_teardown_all)}"
|
||||
defp name, [unquote(Macro.escape var)], [], unquote(Macro.escape block)
|
||||
defp name, [unquote(escape var)], [], unquote(escape block)
|
||||
@exunit_teardown_all [name|@exunit_teardown_all]
|
||||
end
|
||||
end
|
||||
@@ -125,6 +125,10 @@ defmodule ExUnit.Callbacks do
|
||||
" or { :ok, data }, got #{inspect failure} instead"
|
||||
end
|
||||
|
||||
defp escape(contents) do
|
||||
Macro.escape_quoted(contents)
|
||||
end
|
||||
|
||||
defp compile_callbacks(env, kind) do
|
||||
callbacks = Module.get_attribute(env.module, kind) |> Enum.reverse
|
||||
|
||||
|
||||
@@ -40,6 +40,7 @@ defmodule ExUnit.Case do
|
||||
|
||||
import ExUnit.Assertions
|
||||
import ExUnit.Case
|
||||
import ExUnit.DocTest, only: [doctest: 1, doctest: 2]
|
||||
end
|
||||
end
|
||||
|
||||
@@ -88,7 +89,7 @@ defmodule ExUnit.Case do
|
||||
end
|
||||
|
||||
def message, [unquote(Macro.escape var)], [], do:
|
||||
unquote(Macro.escape contents)
|
||||
unquote(Macro.escape_quoted contents)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -8,7 +8,7 @@ defmodule ExUnit.CLIFormatter do
|
||||
@timeout 30_000
|
||||
use GenServer.Behaviour
|
||||
|
||||
import Exception, only: [format_entry: 1]
|
||||
import Exception, only: [format_entry: 2]
|
||||
defrecord Config, counter: 0, failures: []
|
||||
|
||||
## Behaviour
|
||||
@@ -18,8 +18,8 @@ defmodule ExUnit.CLIFormatter do
|
||||
pid
|
||||
end
|
||||
|
||||
def suite_finished(id) do
|
||||
:gen_server.call(id, :suite_finished, @timeout)
|
||||
def suite_finished(id, ms) do
|
||||
:gen_server.call(id, { :suite_finished, ms }, @timeout)
|
||||
end
|
||||
|
||||
def case_started(_id, _test_case) do
|
||||
@@ -30,12 +30,12 @@ defmodule ExUnit.CLIFormatter do
|
||||
:ok
|
||||
end
|
||||
|
||||
def test_started(_id, _test_case, _test) do
|
||||
def test_started(_id, _test) do
|
||||
:ok
|
||||
end
|
||||
|
||||
def test_finished(id, test_case, test, result) do
|
||||
:gen_server.cast(id, { :test_finished, test_case, test, result })
|
||||
def test_finished(id, test) do
|
||||
:gen_server.cast(id, { :test_finished, test })
|
||||
end
|
||||
|
||||
## Callbacks
|
||||
@@ -44,66 +44,83 @@ defmodule ExUnit.CLIFormatter do
|
||||
{ :ok, Config.new }
|
||||
end
|
||||
|
||||
def handle_call(:suite_finished, _from, config) do
|
||||
IO.write "\n\n"
|
||||
Enum.reduce Enum.reverse(config.failures), 1, print_failure(&1, &2)
|
||||
failures_count = length(config.failures)
|
||||
IO.puts "#{config.counter} tests, #{failures_count} failures."
|
||||
{ :stop, :normal, failures_count, config }
|
||||
def handle_call({ :suite_finished, ms }, _from, config) do
|
||||
print_suite(config.counter, config.failures, ms)
|
||||
{ :stop, :normal, length(config.failures), config }
|
||||
end
|
||||
|
||||
def handle_call(_, _, _) do
|
||||
super
|
||||
end
|
||||
|
||||
def handle_cast({ :test_finished, _test_case, _test, nil }, config) do
|
||||
IO.write "."
|
||||
def handle_cast({ :test_finished, ExUnit.Test[failure: nil] }, config) do
|
||||
IO.write success(".")
|
||||
{ :noreply, config.update_counter(&1 + 1) }
|
||||
end
|
||||
|
||||
def handle_cast({ :test_finished, test_case, test, failure }, config) do
|
||||
IO.write "F"
|
||||
def handle_cast({ :test_finished, test }, config) do
|
||||
IO.write failure("F")
|
||||
{ :noreply, config.update_counter(&1 + 1).
|
||||
update_failures([{test_case, test, failure}|&1]) }
|
||||
update_failures([test|&1]) }
|
||||
end
|
||||
|
||||
def handle_cast(_, _) do
|
||||
super
|
||||
end
|
||||
|
||||
defp print_failure({test_case, test, { kind, reason, stacktrace }}, acc) do
|
||||
IO.puts "#{acc}) #{test} (#{inspect test_case})"
|
||||
defp print_suite(counter, [], ms) do
|
||||
IO.write "\n\n"
|
||||
IO.puts "Finished in #{format_ms ms} seconds"
|
||||
IO.puts success("#{counter} tests, 0 failures")
|
||||
end
|
||||
|
||||
defp print_suite(counter, failures, ms) do
|
||||
IO.write "\n\nFailures:\n\n"
|
||||
Enum.reduce Enum.reverse(failures), 1, print_failure(&1, &2, File.cwd!)
|
||||
IO.puts "Finished in #{format_ms ms} seconds"
|
||||
IO.puts failure("#{counter} tests, #{length(failures)} failures")
|
||||
end
|
||||
|
||||
defp print_failure(ExUnit.Test[case: test_case, name: test, failure: { kind, reason, stacktrace }], acc, cwd) do
|
||||
IO.puts " #{acc}) #{test} (#{inspect test_case})"
|
||||
print_kind_reason(kind, reason)
|
||||
print_stacktrace(stacktrace, test_case, test)
|
||||
print_stacktrace(stacktrace, test_case, test, cwd)
|
||||
IO.write "\n"
|
||||
acc + 1
|
||||
end
|
||||
|
||||
defp print_kind_reason(:error, record) when is_record(record, ExUnit.ExpectationError) do
|
||||
left = String.downcase record.prelude
|
||||
right = "to " <> if(record.negation, do: "not ", else: "") <> "be #{record.reason}"
|
||||
max = max(size(left), size(right))
|
||||
defp print_kind_reason(:error, ExUnit.ExpectationError[] = record) do
|
||||
prelude = String.downcase record.prelude
|
||||
reason = record.full_reason
|
||||
max = max(size(prelude), size(reason))
|
||||
|
||||
IO.puts " ** (ExUnit.ExpectationError)"
|
||||
IO.puts " #{pad(left, max)}: #{inspect record.expected}"
|
||||
IO.puts " #{pad(right, max)}: #{inspect record.actual}"
|
||||
IO.puts error_info "** (ExUnit.ExpectationError)"
|
||||
|
||||
if desc = record.description do
|
||||
IO.puts error_info " #{pad(prelude, max)}: #{maybe_multiline(desc, max)}"
|
||||
IO.puts error_info " #{pad(reason, max)}: #{maybe_multiline(record.expected, max)}"
|
||||
IO.puts error_info " #{pad("instead got", max)}: #{maybe_multiline(record.actual, max)}"
|
||||
else
|
||||
IO.puts error_info " #{pad(prelude, max)}: #{maybe_multiline(record.expected, max)}"
|
||||
IO.puts error_info " #{pad(reason, max)}: #{maybe_multiline(record.actual, max)}"
|
||||
end
|
||||
end
|
||||
|
||||
defp print_kind_reason(:error, exception) do
|
||||
IO.puts " ** (#{inspect exception.__record__(:name)}) #{exception.message}"
|
||||
IO.puts error_info "** (#{inspect exception.__record__(:name)}) #{exception.message}"
|
||||
end
|
||||
|
||||
defp print_kind_reason(kind, reason) do
|
||||
IO.puts " ** (#{kind}) #{inspect(reason)}"
|
||||
IO.puts error_info "** (#{kind}) #{inspect(reason)}"
|
||||
end
|
||||
|
||||
defp print_stacktrace([{ test_case, test, _, [ file: file, line: line ] }|_], test_case, test) do
|
||||
IO.puts " at #{file}:#{line}"
|
||||
defp print_stacktrace([{ test_case, test, _, [ file: file, line: line ] }|_], test_case, test, cwd) do
|
||||
IO.puts location_info "at #{Path.relative_to(file, cwd)}:#{line}"
|
||||
end
|
||||
|
||||
defp print_stacktrace(stacktrace, _case, _test) do
|
||||
IO.puts " stacktrace:"
|
||||
Enum.each stacktrace, fn(s) -> IO.puts " #{format_entry(s)}" end
|
||||
defp print_stacktrace(stacktrace, _case, _test, cwd) do
|
||||
IO.puts location_info "stacktrace:"
|
||||
Enum.each stacktrace, fn(s) -> IO.puts stacktrace_info format_entry(s, cwd) end
|
||||
end
|
||||
|
||||
defp pad(binary, max) do
|
||||
@@ -114,4 +131,52 @@ defmodule ExUnit.CLIFormatter do
|
||||
binary
|
||||
end
|
||||
end
|
||||
|
||||
defp format_ms(ms) do
|
||||
if ms < 100000 do
|
||||
"0.0#{div(ms, 10000)}"
|
||||
else
|
||||
ms = div ms, 100000
|
||||
"#{div(ms, 10)}.#{rem(ms, 10)}"
|
||||
end
|
||||
end
|
||||
|
||||
defp maybe_multiline(str, max) do
|
||||
unless multiline?(str) do
|
||||
String.strip(str)
|
||||
else
|
||||
"\n" <>
|
||||
Enum.join((lc line inlist String.split(str, %r/\n/), do: String.duplicate(" ", max) <> line ), "\n")
|
||||
end
|
||||
end
|
||||
|
||||
defp multiline?(<<>>), do: false
|
||||
defp multiline?(<<?\n, _ :: binary>>) do
|
||||
true
|
||||
end
|
||||
defp multiline?(<<_, rest :: binary>>) do
|
||||
multiline?(rest)
|
||||
end
|
||||
|
||||
# Print styles
|
||||
|
||||
defp success(msg) do
|
||||
IO.ANSI.escape("%{green}" <> msg)
|
||||
end
|
||||
|
||||
defp failure(msg) do
|
||||
IO.ANSI.escape("%{red}" <> msg)
|
||||
end
|
||||
|
||||
defp error_info(msg) do
|
||||
IO.ANSI.escape("%{red} " <> msg)
|
||||
end
|
||||
|
||||
defp location_info(msg) do
|
||||
IO.ANSI.escape("%{cyan} " <> msg)
|
||||
end
|
||||
|
||||
defp stacktrace_info(msg) do
|
||||
" " <> msg
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,357 @@
|
||||
defmodule ExUnit.DocTest do
|
||||
@moduledoc """
|
||||
ExUnit.DocTest implements functionality similar to [Python's
|
||||
doctest](http://docs.python.org/2/library/doctest.html).
|
||||
|
||||
In a nutshell, it allows us to generate tests from the code
|
||||
examples existing in module/function/macro's documentation.
|
||||
In order to do that, one needs to invoke `doctest/1` macro
|
||||
from their test case and write their examples according
|
||||
to some guidelines.
|
||||
|
||||
The syntax for examples is as follows. Every new test starts
|
||||
on a new line, with an "iex>" prefix. Multiline expressions
|
||||
can be employed if the following lines start with either
|
||||
"...>" (recommended) or "iex>" prefix.
|
||||
|
||||
The expected result should start at the next line after "iex>"
|
||||
or "...>" line(s) and is terminated either by a newline, new
|
||||
"iex>" prefix or end of the string literal.
|
||||
|
||||
## Examples
|
||||
|
||||
Currently, the only way to run doctests is to include them into
|
||||
an ExUnit case with a `doctest` macro:
|
||||
|
||||
defmodule MyModule.Test do
|
||||
use ExUnit.Case, async: true
|
||||
doctest MyModule
|
||||
end
|
||||
|
||||
The `doctest` macro is going to loop all functions and macros
|
||||
defined in `MyModule`, parsing their documentation in search for
|
||||
code examples.
|
||||
|
||||
A very basic example is:
|
||||
|
||||
iex> 1+1
|
||||
2
|
||||
|
||||
Multiline is also supported:
|
||||
|
||||
iex> Enum.map [1,2,3], fn(x) ->
|
||||
...> x * 2
|
||||
...> end
|
||||
[2,4,6]
|
||||
|
||||
Similarly to iex you can use numbers in your "prompts":
|
||||
|
||||
iex(1)> [1+2,
|
||||
...(1)> 3]
|
||||
[3,3]
|
||||
|
||||
This is useful in two use cases:
|
||||
|
||||
* Being able to refer to specific numbered scenarios
|
||||
* Copy-pasting examples from an actual iex sessions
|
||||
|
||||
We also allow you to select or skip some functions when calling
|
||||
`doctest`. See its documentation documentation for more info.
|
||||
|
||||
## Exceptions
|
||||
|
||||
You can also showcase expressions raising an exception, for example:
|
||||
|
||||
iex(1)> binary_to_atom((fn() -> 1 end).())
|
||||
** (ArgumentError) argument error
|
||||
|
||||
What DocTest will be looking for is a line starting with "** (" and it
|
||||
will parse it accordingly to extract the exception name and the message.
|
||||
At this moment, the exception parser would make the parser treat the next
|
||||
line as a start of a completely new expression (if it is prefixed with iex>)
|
||||
or a no-op line with documentation. Thus, multiline messages are not
|
||||
supported.
|
||||
|
||||
## When not to use doctest
|
||||
|
||||
In general, doctests are not recommended when your code examples contain
|
||||
side effects. For example, if a doctest prints to standard output, doctest
|
||||
will not try to capture the output.
|
||||
|
||||
Similarly, doctest does not run in any kind of side box. So any module
|
||||
defined in a code example is going to linger throughout the whole test suite
|
||||
run.
|
||||
"""
|
||||
|
||||
defrecord Test, fun_arity: nil, line: nil, expr: nil, expected: nil
|
||||
|
||||
@doc """
|
||||
This macro is used to generate ExUnit test cases for doctests.
|
||||
|
||||
There are three ways this macro can be used:
|
||||
|
||||
* `doctest(Module)` — will generate tests for all doctests found
|
||||
in the module `Module`
|
||||
|
||||
Options can also be supplied:
|
||||
|
||||
* `:except` — generate tests for all functions except those listed
|
||||
(list of `{function, arity}` tuples)
|
||||
|
||||
* `:only` — generate tests only forfunctions listed
|
||||
(list of `{function, arity}` tuples)
|
||||
|
||||
* `:import` — when true, one can test a function defined in the module
|
||||
without referring to the module name. However, this is not
|
||||
feasible when there is a clash with a number module like
|
||||
Kernel. In these cases, `import` should be set to `false`
|
||||
and a full `M.f` construct should be used.
|
||||
|
||||
## Examples
|
||||
|
||||
doctest MyModule, except: [trick_fun: 1]
|
||||
|
||||
This macro is auto-imported into every `ExUnit.Case`.
|
||||
"""
|
||||
defmacro doctest(mod, opts // []) do
|
||||
quote do
|
||||
lc { name, test } inlist unquote(__MODULE__).__doctests__(unquote(mod), unquote(opts)) do
|
||||
def name, quote(do: [_]), [], do: test
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@doc false
|
||||
def __doctests__(module, opts) do
|
||||
only = opts[:only] || []
|
||||
except = opts[:except] || []
|
||||
do_import = Keyword.get(opts, :import, false)
|
||||
|
||||
tests = Enum.filter(extract(module), fn(test) ->
|
||||
fa = test.fun_arity
|
||||
Enum.all?(except, &1 != fa) and Enum.all?(only, &1 == fa)
|
||||
end)
|
||||
|
||||
Enum.map_reduce(tests, 1, fn(test, acc) ->
|
||||
{ compile_test(test, module, do_import, acc), acc + 1 }
|
||||
end) |> elem(0)
|
||||
end
|
||||
|
||||
## Compilation of extracted tests
|
||||
|
||||
defp compile_test(test, module, do_import, n) do
|
||||
{ test_name(test, module, n), test_content(test, module, do_import) }
|
||||
end
|
||||
|
||||
defp test_name(Test[fun_arity: nil], m, n) do
|
||||
:"test moduledoc at #{inspect m} (#{n})"
|
||||
end
|
||||
|
||||
defp test_name(Test[fun_arity: { f, a }], m, n) do
|
||||
:"test doc at #{inspect m}.#{f}/#{a} (#{n})"
|
||||
end
|
||||
|
||||
defp test_content(Test[expected: { :test, expected }] = test, module, do_import) do
|
||||
line = test.line
|
||||
file = module.__info__(:compile)[:source]
|
||||
location = [line: line, file: Path.relative_to(file, System.cwd!)]
|
||||
stack = Macro.escape [{ module, :__MODULE__, 0, location }]
|
||||
|
||||
expr_ast = string_to_ast(module, line, file, test.expr)
|
||||
expected_ast = string_to_ast(module, line, file, expected)
|
||||
|
||||
quote do
|
||||
unquote_splicing(test_import(module, do_import))
|
||||
|
||||
try do
|
||||
v = unquote(expected_ast)
|
||||
case unquote(expr_ast) do
|
||||
^v -> :ok
|
||||
actual ->
|
||||
raise ExUnit.ExpectationError,
|
||||
[ prelude: "Expected doctest",
|
||||
description: unquote(test.expr),
|
||||
expected: inspect(v),
|
||||
reason: "evaluate to",
|
||||
actual: inspect(actual) ],
|
||||
unquote(stack)
|
||||
end
|
||||
rescue
|
||||
e in [ExUnit.ExpectationError] ->
|
||||
raise e, [], unquote(stack)
|
||||
actual ->
|
||||
raise ExUnit.ExpectationError,
|
||||
[ prelude: "Expected doctest",
|
||||
description: unquote(test.expr),
|
||||
expected: "without an exception",
|
||||
reason: "complete",
|
||||
actual: inspect(actual) ],
|
||||
unquote(stack)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defp test_content(Test[expected: { :error, exception, message }] = test, module, do_import) do
|
||||
line = test.line
|
||||
file = module.__info__(:compile)[:source]
|
||||
location = [line: line, file: Path.relative_to(file, System.cwd!)]
|
||||
stack = Macro.escape [{ module, :__MODULE__, 0, location }]
|
||||
|
||||
expr_ast = string_to_ast(module, line, file, test.expr)
|
||||
|
||||
quote do
|
||||
unquote_splicing(test_import(module, do_import))
|
||||
|
||||
try do
|
||||
v = unquote(expr_ast)
|
||||
raise ExUnit.ExpectationError,
|
||||
[ prelude: "Expected doctest",
|
||||
description: unquote(test.expr),
|
||||
expected: "#{inspect unquote(exception)}[]",
|
||||
reason: "raise",
|
||||
actual: inspect(v) ],
|
||||
unquote(stack)
|
||||
rescue
|
||||
e in [ExUnit.ExpectationError] -> raise(e)
|
||||
error in [unquote(exception)] ->
|
||||
unless error.message == unquote(message) do
|
||||
raise ExUnit.ExpectationError,
|
||||
[ prelude: "Expected doctest",
|
||||
description: unquote(test.expr),
|
||||
expected: "#{inspect unquote(exception)} with message #{inspect unquote(message)}",
|
||||
reason: "raise",
|
||||
actual: inspect(error) ],
|
||||
unquote(stack)
|
||||
end
|
||||
error ->
|
||||
raise ExUnit.ExpectationError,
|
||||
[ prelude: "Expected doctest",
|
||||
description: unquote(test.expr),
|
||||
expected: "#{inspect unquote(exception)}",
|
||||
reason: "raise",
|
||||
actual: inspect(error) ],
|
||||
unquote(stack)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defp test_import(_mod, false), do: []
|
||||
defp test_import(mod, _) do
|
||||
[quote do: import(unquote(mod))]
|
||||
end
|
||||
|
||||
defp string_to_ast(module, line, file, expr) do
|
||||
location = [line: line, file: Path.relative_to(file, System.cwd!)]
|
||||
stack = Macro.escape [{ module, :__MODULE__, 0, location }]
|
||||
try do
|
||||
Code.string_to_ast!(expr, line: line, file: file)
|
||||
rescue e ->
|
||||
quote do
|
||||
raise ExUnit.ExpectationError,
|
||||
[ prelude: "Expected doctest",
|
||||
description: unquote(expr),
|
||||
expected: "successfully",
|
||||
reason: "compile",
|
||||
actual: unquote("** #{inspect e.__record__(:name)} #{e.message}") ],
|
||||
unquote(stack)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
## Extraction of the tests
|
||||
|
||||
defp extract(module) do
|
||||
moduledocs = extract_from_moduledoc(module.__info__(:moduledoc))
|
||||
|
||||
docs = lc doc inlist module.__info__(:docs) do
|
||||
extract_from_doc(doc)
|
||||
end |> List.concat
|
||||
|
||||
moduledocs ++ docs
|
||||
end
|
||||
|
||||
defp extract_from_moduledoc({_, negative}) when negative in [false, nil], do: []
|
||||
|
||||
defp extract_from_moduledoc({line, doc}) do
|
||||
extract_tests(line, doc)
|
||||
end
|
||||
|
||||
defp extract_from_doc({_, _, _, _, negative}) when negative in [false, nil], do: []
|
||||
|
||||
defp extract_from_doc({ fa, line, _, _, doc}) do
|
||||
lc test inlist extract_tests(line, doc) do
|
||||
test.fun_arity(fa)
|
||||
end
|
||||
end
|
||||
|
||||
defp extract_tests(line, doc) do
|
||||
lines = String.split(doc, %r/\n/) |> Enum.map(function(String.strip/1))
|
||||
extract_tests(lines, line, "", "", [])
|
||||
end
|
||||
|
||||
defp extract_tests([], _line, "", "", acc), do: Enum.reverse(acc)
|
||||
|
||||
defp extract_tests([], line, expr_acc, expected_acc, acc) do
|
||||
test = Test[expr: expr_acc, line: line, expected: { :test, expected_acc }]
|
||||
Enum.reverse([test|acc])
|
||||
end
|
||||
|
||||
defp extract_tests([<< "iex>", _ :: binary>>|_] = list, line, expr_acc, expected_acc, acc) when expr_acc != "" and expected_acc != "" do
|
||||
test = Test[expr: expr_acc, line: line, expected: { :test, expected_acc }]
|
||||
extract_tests(list, line, "", "", [test|acc])
|
||||
end
|
||||
|
||||
defp extract_tests([<< "iex>", string :: binary>>|lines], line, expr_acc, expected_acc, acc) when expr_acc == "" do
|
||||
extract_tests(lines, line, string, expected_acc, acc)
|
||||
end
|
||||
|
||||
defp extract_tests([<< "iex>", string :: binary>>|lines], line, expr_acc, expected_acc, acc) do
|
||||
extract_tests(lines, line, expr_acc <> "\n" <> string, expected_acc, acc)
|
||||
end
|
||||
|
||||
defp extract_tests([<< "...>", string :: binary>>|lines], line, expr_acc, expected_acc, acc) when expr_acc != "" do
|
||||
extract_tests(lines, line, expr_acc <> "\n" <> string, expected_acc, acc)
|
||||
end
|
||||
|
||||
defp extract_tests([<< "iex(", _ :: 8, string :: binary>>|lines], line, expr_acc, expected_acc, acc) do
|
||||
extract_tests(["iex" <> skip_iex_number(string)|lines], line, expr_acc, expected_acc, acc)
|
||||
end
|
||||
|
||||
defp extract_tests([<< "...(", _ :: 8, string :: binary>>|lines], line, expr_acc, expected_acc, acc) do
|
||||
extract_tests(["..." <> skip_iex_number(string)|lines], line, expr_acc, expected_acc, acc)
|
||||
end
|
||||
|
||||
defp extract_tests([_|lines], line, "", "", acc) do
|
||||
extract_tests(lines, line, "", "", acc)
|
||||
end
|
||||
|
||||
defp extract_tests([""|lines], line, expr_acc, expected_acc, acc) do
|
||||
test = Test[expr: expr_acc, line: line, expected: { :test, expected_acc }]
|
||||
extract_tests(lines, line, "", "", [test|acc])
|
||||
end
|
||||
|
||||
defp extract_tests([<< "** (", string :: binary >>|lines], line, expr_acc, "", acc) do
|
||||
test = Test[expr: expr_acc, line: line, expected: extract_error(string, "")]
|
||||
extract_tests(lines, line, "", "", [test|acc])
|
||||
end
|
||||
|
||||
defp extract_tests([expected|lines], line, expr_acc, expected_acc, acc) do
|
||||
extract_tests(lines, line, expr_acc, expected_acc <> "\n" <> expected, acc)
|
||||
end
|
||||
|
||||
defp extract_error(<< ")", t :: binary >>, acc) do
|
||||
{ :error, Module.concat([acc]), String.strip(t) }
|
||||
end
|
||||
|
||||
defp extract_error(<< h, t :: binary >>, acc) do
|
||||
extract_error(t, << acc :: binary, h >>)
|
||||
end
|
||||
|
||||
defp skip_iex_number(<< ")", ">", string :: binary >>) do
|
||||
">" <> string
|
||||
end
|
||||
|
||||
defp skip_iex_number(<< _ :: 8, string :: binary >>) do
|
||||
skip_iex_number(string)
|
||||
end
|
||||
end
|
||||
@@ -12,11 +12,11 @@ defmodule ExUnit.Formatter do
|
||||
@type result :: { kind :: atom, reason :: term, stacktrace :: list } | nil
|
||||
|
||||
defcallback suite_started(opts :: list) :: id
|
||||
defcallback suite_finished(id) :: non_neg_integer
|
||||
defcallback suite_finished(id, miliseconds :: pos_integer) :: non_neg_integer
|
||||
|
||||
defcallback case_started(id, test_case) :: any
|
||||
defcallback case_finished(id, test_case) :: any
|
||||
|
||||
defcallback test_started(id, test_case, test) :: any
|
||||
defcallback test_finished(id, test_case, test, result) :: any
|
||||
defcallback test_started(id, test :: ExUnit.Test.t) :: any
|
||||
defcallback test_finished(id, test :: ExUnit.Test.t) :: any
|
||||
end
|
||||
|
||||
@@ -8,8 +8,13 @@ defmodule ExUnit.Runner do
|
||||
config = Config[max_cases: :erlang.system_info(:schedulers_online)]
|
||||
config = config.update(opts)
|
||||
|
||||
loop config.async_cases(async).sync_cases(sync).
|
||||
formatter_id(config.formatter.suite_started(opts))
|
||||
{ ms, config } =
|
||||
:timer.tc fn ->
|
||||
loop config.async_cases(async).sync_cases(sync).
|
||||
formatter_id(config.formatter.suite_started(opts))
|
||||
end
|
||||
|
||||
config.formatter.suite_finished(config.formatter_id, ms)
|
||||
end
|
||||
|
||||
defp loop(Config[] = config) do
|
||||
@@ -36,7 +41,7 @@ defmodule ExUnit.Runner do
|
||||
|
||||
# No more cases, we are done!
|
||||
true ->
|
||||
config.formatter.suite_finished(config.formatter_id)
|
||||
config
|
||||
end
|
||||
end
|
||||
|
||||
@@ -45,8 +50,8 @@ defmodule ExUnit.Runner do
|
||||
# cases counter and attempt to spawn new ones.
|
||||
defp wait_until_available(config) do
|
||||
receive do
|
||||
{ _pid, :test_finished, { test_case, test, final } } ->
|
||||
config.formatter.test_finished(config.formatter_id, test_case, test, final)
|
||||
{ _pid, :test_finished, test } ->
|
||||
config.formatter.test_finished(config.formatter_id, test)
|
||||
wait_until_available config
|
||||
{ _pid, :case_finished, test_case } ->
|
||||
config.formatter.case_finished(config.formatter_id, test_case)
|
||||
@@ -62,7 +67,6 @@ defmodule ExUnit.Runner do
|
||||
defp spawn_case(config, test_case) do
|
||||
pid = self()
|
||||
spawn_link fn ->
|
||||
ExUnit.Server.run_after_spawn
|
||||
run_tests(config, pid, test_case)
|
||||
end
|
||||
end
|
||||
@@ -72,42 +76,43 @@ defmodule ExUnit.Runner do
|
||||
|
||||
try do
|
||||
tests = tests_for(test_case)
|
||||
context = test_case.__exunit__(:setup_all, [])
|
||||
Enum.each tests, run_test(config, pid, test_case, context, &1)
|
||||
context = test_case.__exunit__(:setup_all, [case: test_case])
|
||||
Enum.each tests, run_test(config, pid, test_case, &1, context)
|
||||
test_case.__exunit__(:teardown_all, context)
|
||||
after
|
||||
pid <- { self, :case_finished, test_case }
|
||||
end
|
||||
end
|
||||
|
||||
defp run_test(config, pid, test_case, setup_context, test) do
|
||||
config.formatter.test_started(config.formatter_id, test_case, test)
|
||||
defp run_test(config, pid, test_case, test_name, context) do
|
||||
test = ExUnit.Test[name: test_name, case: test_case]
|
||||
config.formatter.test_started(config.formatter_id, test)
|
||||
|
||||
final = try do
|
||||
context = test_case.__exunit__(:setup, Keyword.put(setup_context, :test, test))
|
||||
test = try do
|
||||
context = test_case.__exunit__(:setup, Keyword.put(context, :test, test))
|
||||
|
||||
partial = try do
|
||||
apply test_case, test, [context]
|
||||
nil
|
||||
test = try do
|
||||
apply test_case, test_name, [context]
|
||||
test
|
||||
rescue
|
||||
error1 ->
|
||||
{ :error, error1, filtered_stacktrace }
|
||||
test.failure { :error, error1, filtered_stacktrace }
|
||||
catch
|
||||
kind1, error1 ->
|
||||
{ kind1, error1, filtered_stacktrace }
|
||||
test.failure { kind1, error1, filtered_stacktrace }
|
||||
end
|
||||
|
||||
test_case.__exunit__(:teardown, context)
|
||||
partial
|
||||
test_case.__exunit__(:teardown, Keyword.put(context, :test, test))
|
||||
test
|
||||
rescue
|
||||
error2 ->
|
||||
{ :error, error2, filtered_stacktrace }
|
||||
test.failure { :error, error2, filtered_stacktrace }
|
||||
catch
|
||||
kind2, error2 ->
|
||||
{ kind2, error2, filtered_stacktrace }
|
||||
test.failure { kind2, error2, filtered_stacktrace }
|
||||
end
|
||||
|
||||
pid <- { self, :test_finished, { test_case, test, final } }
|
||||
pid <- { self, :test_finished, test }
|
||||
end
|
||||
|
||||
## Helpers
|
||||
|
||||
@@ -4,7 +4,7 @@ defmodule ExUnit.Server do
|
||||
@timeout 30_000
|
||||
use GenServer.Behaviour
|
||||
|
||||
defrecord Config, options: [], async_cases: [], sync_cases: [], after_spawn: []
|
||||
defrecord Config, options: [], async_cases: [], sync_cases: []
|
||||
|
||||
def start_link(options) do
|
||||
:gen_server.start_link({ :local, __MODULE__ }, __MODULE__, options, [])
|
||||
@@ -20,10 +20,6 @@ defmodule ExUnit.Server do
|
||||
:gen_server.cast(__MODULE__, { :add_sync_case, name })
|
||||
end
|
||||
|
||||
def add_after_spawn(fun) when is_function(fun) do
|
||||
:gen_server.cast(__MODULE__, { :add_after_spawn, fun })
|
||||
end
|
||||
|
||||
def merge_options(options) do
|
||||
:gen_server.cast(__MODULE__, { :merge_options, options })
|
||||
end
|
||||
@@ -38,11 +34,6 @@ defmodule ExUnit.Server do
|
||||
:gen_server.call(__MODULE__, :cases, @timeout)
|
||||
end
|
||||
|
||||
def run_after_spawn do
|
||||
funs = :gen_server.call(__MODULE__, :after_spawn, @timeout)
|
||||
lc fun inlist funs, do: fun.()
|
||||
end
|
||||
|
||||
## Callbacks
|
||||
|
||||
def init(options) do
|
||||
@@ -53,10 +44,6 @@ defmodule ExUnit.Server do
|
||||
{ :reply, config.options, config }
|
||||
end
|
||||
|
||||
def handle_call(:after_spawn, _from, config) do
|
||||
{ :reply, Enum.reverse(config.after_spawn), config }
|
||||
end
|
||||
|
||||
def handle_call(:cases, _from, config) do
|
||||
{ :reply,
|
||||
{ config.async_cases, config.sync_cases },
|
||||
@@ -75,10 +62,6 @@ defmodule ExUnit.Server do
|
||||
{ :noreply, config.update_sync_cases [name|&1] }
|
||||
end
|
||||
|
||||
def handle_cast({:add_after_spawn, fun}, config) do
|
||||
{ :noreply, config.update_after_spawn [fun|&1] }
|
||||
end
|
||||
|
||||
def handle_cast({:merge_options, options}, config) do
|
||||
{ :noreply, config.update_options(&1 |> Keyword.merge(options)) }
|
||||
end
|
||||
|
||||
@@ -3,12 +3,12 @@ defmodule ExUnit.Sup do
|
||||
|
||||
use Supervisor.Behaviour
|
||||
|
||||
def start_link(user_options) do
|
||||
:supervisor.start_link({ :local, __MODULE__ }, __MODULE__, user_options)
|
||||
def start_link(options) do
|
||||
:supervisor.start_link({ :local, __MODULE__ }, __MODULE__, options)
|
||||
end
|
||||
|
||||
def init(user_options) do
|
||||
tree = [ worker(ExUnit.Server, [user_options]) ]
|
||||
def init(options) do
|
||||
tree = [ worker(ExUnit.Server, [options]) ]
|
||||
supervise(tree, strategy: :one_for_one)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -139,29 +139,51 @@ defmodule ExUnit.AssertionsTest do
|
||||
end
|
||||
end
|
||||
|
||||
test :assert_match_when_matches do
|
||||
true = assert "foo" =~ %r(o)
|
||||
test :assert_match_when_match do
|
||||
{ :ok, true } = assert { :ok, _ } = { :ok, true }
|
||||
end
|
||||
|
||||
test :assert_match_when_no_match do
|
||||
try do
|
||||
"This should never be tested" = assert "foo" =~ %r(a)
|
||||
"This should never be tested" = assert { :ok, _ } = "bar"
|
||||
rescue
|
||||
error in [ExUnit.ExpectationError] ->
|
||||
"Expected \"foo\" to be a match (=~) with %r\"a\"" = error.message
|
||||
"Expected \"bar\" to match pattern (=) {:ok, _}" = error.message
|
||||
end
|
||||
end
|
||||
|
||||
test :refute_match_when_is_matches do
|
||||
test :refute_match_when_match do
|
||||
try do
|
||||
"This should never be tested" = refute _ = "bar"
|
||||
rescue
|
||||
error in [ExUnit.ExpectationError] ->
|
||||
"Expected \"bar\" to not match pattern (=) _" = error.message
|
||||
end
|
||||
end
|
||||
|
||||
test :assert_regex_match_when_matches do
|
||||
true = assert "foo" =~ %r(o)
|
||||
end
|
||||
|
||||
test :assert_regex_match_when_no_match do
|
||||
try do
|
||||
"This should never be tested" = assert "foo" =~ %r(a)
|
||||
rescue
|
||||
error in [ExUnit.ExpectationError] ->
|
||||
"Expected \"foo\" to match (=~) %r\"a\"" = error.message
|
||||
end
|
||||
end
|
||||
|
||||
test :refute_regex_match_when_is_matches do
|
||||
false = refute "foo" =~ %r(a)
|
||||
end
|
||||
|
||||
test :refute_match_when_no_match do
|
||||
test :refute_regex_match_when_no_match do
|
||||
try do
|
||||
"This should never be tested" = refute "foo" =~ %r(o)
|
||||
rescue
|
||||
error in [ExUnit.ExpectationError] ->
|
||||
"Expected \"foo\" to not be a match (=~) with %r\"o\"" = error.message
|
||||
"Expected \"foo\" to not match (=~) %r\"o\"" = error.message
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@ defmodule ExUnit.CallbacksTest do
|
||||
if Process.get(:ex_unit_callback) do
|
||||
raise "ex_unit_callback was not cleaned"
|
||||
else
|
||||
Process.put(:ex_unit_callback, context[:test])
|
||||
Process.put(:ex_unit_callback, context[:test].name)
|
||||
end
|
||||
:ok
|
||||
end
|
||||
@@ -32,7 +32,7 @@ defmodule ExUnit.CallbacksTest do
|
||||
end
|
||||
|
||||
teardown context do
|
||||
assert Process.get(:ex_unit_callback) == context[:test]
|
||||
assert Process.get(:ex_unit_callback) == context[:test].name
|
||||
Process.delete(:ex_unit_callback)
|
||||
:ok
|
||||
end
|
||||
|
||||
@@ -36,6 +36,12 @@ end
|
||||
defmodule ExUnit.CaseTemplateTest do
|
||||
use ExUnit.SampleCase, async: true
|
||||
|
||||
two = 2
|
||||
|
||||
test "unquoting the value #{two}" do
|
||||
assert 2 == unquote(two)
|
||||
end
|
||||
|
||||
test "receives context from parent case", context do
|
||||
assert context[:context] == :setup
|
||||
end
|
||||
|
||||
@@ -44,7 +44,7 @@ defmodule ExUnit.CLIFormatterTest do
|
||||
"""
|
||||
|
||||
out_put = exec_test("failure_test.exs")
|
||||
assert out_put =~ %r/at\ #{Path.expand("failure_test.exs")}:6/
|
||||
assert out_put =~ %r/at failure_test.exs:6/
|
||||
after
|
||||
File.rm("failure_test.exs")
|
||||
end
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
Code.require_file "../../test_helper.exs", __FILE__
|
||||
|
||||
defmodule ExUnit.DocTestTest.GoodModule do
|
||||
@doc """
|
||||
iex> test_fun
|
||||
1
|
||||
iex> test_fun + 1
|
||||
2
|
||||
"""
|
||||
def test_fun, do: 1
|
||||
|
||||
@doc """
|
||||
iex> 1 + (fn() -> :a end).()
|
||||
** (ArithmeticError) bad argument in arithmetic expression
|
||||
"""
|
||||
def exception_test, do: 1
|
||||
end
|
||||
|
||||
defmodule ExUnit.DocTestTest.SomewhatGoodModule do
|
||||
@doc """
|
||||
iex> test_fun
|
||||
1
|
||||
iex> test_fun + 1
|
||||
2
|
||||
"""
|
||||
def test_fun, do: 1
|
||||
|
||||
@doc """
|
||||
iex> test_fun
|
||||
1
|
||||
iex> test_fun + 1
|
||||
1
|
||||
"""
|
||||
def test_fun1, do: 1
|
||||
end
|
||||
|
||||
defmodule ExUnit.DocTestTest.SomewhatGoodModule1 do
|
||||
@doc """
|
||||
iex> test_fun
|
||||
1
|
||||
iex> test_fun + 1
|
||||
2
|
||||
"""
|
||||
def test_fun, do: 1
|
||||
|
||||
@doc """
|
||||
iex> test_fun
|
||||
1
|
||||
iex> test_fun + 1
|
||||
1
|
||||
"""
|
||||
def test_fun1, do: 1
|
||||
end
|
||||
|
||||
defmodule ExUnit.DocTestTest.NoImport do
|
||||
@doc """
|
||||
iex> ExUnit.DocTestTest.NoImport.min(1,2)
|
||||
2
|
||||
"""
|
||||
def min(a,b), do: max(a,b)
|
||||
end
|
||||
defmodule ExUnit.DocTestTest do
|
||||
use ExUnit.Case
|
||||
|
||||
doctest ExUnit.DocTest
|
||||
doctest ExUnit.DocTestTest.GoodModule, import: true
|
||||
doctest ExUnit.DocTestTest.SomewhatGoodModule, only: [test_fun: 0], import: true
|
||||
doctest ExUnit.DocTestTest.SomewhatGoodModule1, except: [test_fun1: 0], import: true
|
||||
doctest ExUnit.DocTestTest.NoImport
|
||||
end
|
||||
@@ -7,7 +7,7 @@ defmodule ExUnit.NilFormatter do
|
||||
:ok
|
||||
end
|
||||
|
||||
def suite_finished(:ok) do
|
||||
def suite_finished(:ok, _ms) do
|
||||
1
|
||||
end
|
||||
|
||||
@@ -19,11 +19,11 @@ defmodule ExUnit.NilFormatter do
|
||||
:ok
|
||||
end
|
||||
|
||||
def test_started(:ok, _test_case, _test) do
|
||||
def test_started(:ok, _test) do
|
||||
:ok
|
||||
end
|
||||
|
||||
def test_finished(:ok, _test_case, _test, _result) do
|
||||
def test_finished(:ok, _test) do
|
||||
:ok
|
||||
end
|
||||
end
|
||||
@@ -31,14 +31,6 @@ end
|
||||
defmodule ExUnitTest do
|
||||
use ExUnit.Case, async: false
|
||||
|
||||
test "it can read user config" do
|
||||
File.write("ex_unit.test.config","[extra_option: true]")
|
||||
assert ExUnit.user_options("nosuchfile.config") == []
|
||||
assert ExUnit.user_options("ex_unit.test.config") == [extra_option: true]
|
||||
after
|
||||
File.rm("ex_unit.test.config")
|
||||
end
|
||||
|
||||
test "it supports many runs" do
|
||||
ExUnit.configure(formatter: ExUnit.NilFormatter)
|
||||
|
||||
|
||||
@@ -253,7 +253,11 @@ defmodule IEx.Autocomplete do
|
||||
|
||||
defp get_funs(mod) do
|
||||
if function_exported?(mod, :__info__, 1) do
|
||||
(mod.__info__(:functions) -- [__info__: 1]) ++ mod.__info__(:macros)
|
||||
if docs = mod.__info__(:docs) do
|
||||
lc { tuple, _line, _kind, _sign, doc } inlist docs, doc != false, do: tuple
|
||||
else
|
||||
(mod.__info__(:functions) -- [__info__: 1]) ++ mod.__info__(:macros)
|
||||
end
|
||||
else
|
||||
mod.module_info(:exports)
|
||||
end
|
||||
|
||||
+45
-226
@@ -12,15 +12,17 @@ defmodule IEx.Helpers do
|
||||
There are many other helpers available:
|
||||
|
||||
* `c/2` - compiles a file in the given path
|
||||
* `cd/1` - changes the current directory
|
||||
* `flush/0` — flush all messages sent to the shell
|
||||
* `h/0`, `h/1` - prints help/documentation
|
||||
* `t/1` — prints type information
|
||||
* `s/1` — prints spec information
|
||||
* `m/0` - prints loaded modules
|
||||
* `r/0`, `r/1` - recompiles and reloads the given module's source file
|
||||
* `l/1` - loads given module beam code by purging the current version
|
||||
* `m/0` - prints loaded modules
|
||||
* `pwd/0` - prints the current working directory
|
||||
* `r/0`, `r/1` - recompiles and reloads the given module's source file
|
||||
* `s/1` — prints spec information
|
||||
* `t/1` — prints type information
|
||||
* `v/0` - prints all commands and values
|
||||
* `v/1` - retrieves nth value from console
|
||||
* `flush/0` — flush all messages sent to the shell
|
||||
|
||||
Help for functions in this module can be consulted
|
||||
directly from the command line, as an example, try:
|
||||
@@ -43,7 +45,7 @@ defmodule IEx.Helpers do
|
||||
## Examples
|
||||
|
||||
c ["foo.ex"], "ebin"
|
||||
#=> Foo
|
||||
#=> [Foo]
|
||||
|
||||
"""
|
||||
def c(files, path // ".") do
|
||||
@@ -58,7 +60,7 @@ defmodule IEx.Helpers do
|
||||
all = Enum.map :code.all_loaded, fn { mod, file } -> { inspect(mod), file } end
|
||||
sorted = Enum.sort all
|
||||
size = Enum.reduce sorted, 0, fn({ mod, _ }, acc) -> max(byte_size(mod), acc) end
|
||||
format = "~-#{size}s ~s~n"
|
||||
format = "~-#{size}s ~ts~n"
|
||||
|
||||
Enum.each sorted, fn({ mod, file }) ->
|
||||
:io.format(format, [mod, file])
|
||||
@@ -74,14 +76,14 @@ defmodule IEx.Helpers do
|
||||
end
|
||||
|
||||
defp print_history(config) do
|
||||
IO.puts "#{config.counter}: #{config.cache}#=> #{inspect config.result}\n"
|
||||
IO.puts IO.ANSI.escape("%{yellow}#{config.counter}: #{config.cache}#=> #{inspect config.result}\n")
|
||||
end
|
||||
|
||||
@doc """
|
||||
Shows the documentation for IEx.Helpers.
|
||||
"""
|
||||
def h() do
|
||||
h(IEx.Helpers, [])
|
||||
IEx.Introspection.h(IEx.Helpers)
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -103,137 +105,36 @@ defmodule IEx.Helpers do
|
||||
"""
|
||||
defmacro h({ :/, _, [{ { :., _, [mod, fun] }, _, [] }, arity] }) do
|
||||
quote do
|
||||
h(unquote(mod), unquote(fun), unquote(arity))
|
||||
IEx.Introspection.h(unquote(mod), unquote(fun), unquote(arity))
|
||||
end
|
||||
end
|
||||
|
||||
defmacro h({ { :., _, [mod, fun] }, _, [] }) do
|
||||
quote do
|
||||
h(unquote(mod), unquote(fun))
|
||||
IEx.Introspection.h(unquote(mod), unquote(fun))
|
||||
end
|
||||
end
|
||||
|
||||
defmacro h({ :/, _, [{ fun, _, args }, arity] }) when args == [] or is_atom(args) do
|
||||
quote do
|
||||
h(unquote(fun), unquote(arity))
|
||||
IEx.Introspection.h(unquote(fun), unquote(arity))
|
||||
end
|
||||
end
|
||||
|
||||
defmacro h({ name, _, args }) when args == [] or is_atom(args) do
|
||||
quote do
|
||||
h(unquote(__MODULE__), unquote(name))
|
||||
h(Kernel, unquote(name))
|
||||
IEx.Introspection.h([unquote(__MODULE__), Kernel, Kernel.SpecialForms], unquote(name))
|
||||
end
|
||||
end
|
||||
|
||||
defmacro h(other) do
|
||||
quote do
|
||||
h(unquote(other), [])
|
||||
IEx.Introspection.h(unquote(other))
|
||||
end
|
||||
end
|
||||
|
||||
@doc false
|
||||
def h(:h, 1) do
|
||||
h(__MODULE__, :h, 1)
|
||||
end
|
||||
|
||||
def h(function, arity) when is_atom(function) and is_integer(arity) do
|
||||
if function_exported?(__MODULE__, function, arity) or
|
||||
macro_exported?(__MODULE__, function, arity) do
|
||||
h(__MODULE__, function, arity)
|
||||
else
|
||||
h(Kernel, function, arity)
|
||||
end
|
||||
end
|
||||
|
||||
def h(module, []) when is_atom(module) do
|
||||
case Code.ensure_loaded(module) do
|
||||
{ :module, _ } ->
|
||||
case module.__info__(:moduledoc) do
|
||||
{ _, binary } when is_binary(binary) ->
|
||||
IO.puts "# #{inspect module}\n"
|
||||
IO.write binary
|
||||
{ _, _ } ->
|
||||
IO.puts "No docs for #{inspect module} have been found"
|
||||
_ ->
|
||||
IO.puts "#{inspect module} was not compiled with docs"
|
||||
end
|
||||
{ :error, reason } ->
|
||||
IO.puts "Could not load module #{inspect module}: #{reason}"
|
||||
end
|
||||
end
|
||||
|
||||
def h(module, function) when is_atom(module) and is_atom(function) do
|
||||
lc {{f, arity}, _line, _type, _args, doc } inlist module.__info__(:docs),
|
||||
f == function and doc != false do
|
||||
h(module, function, arity)
|
||||
IO.puts ""
|
||||
end
|
||||
:ok
|
||||
end
|
||||
|
||||
def h(_, _) do
|
||||
IO.puts "Invalid h helper argument"
|
||||
h()
|
||||
end
|
||||
|
||||
@doc false
|
||||
def h(module, function, arity) when is_atom(module) and is_atom(function) and is_integer(arity) do
|
||||
if docs = module.__info__(:docs) do
|
||||
doc =
|
||||
cond do
|
||||
d = find_doc(docs, function, arity) -> d
|
||||
d = find_default_doc(docs, function, arity) -> d
|
||||
true -> nil
|
||||
end
|
||||
|
||||
if doc do
|
||||
IO.write "\n" <> print_signature(doc)
|
||||
else
|
||||
IO.puts "No docs for #{inspect module}.#{function}/#{arity} have been found"
|
||||
end
|
||||
else
|
||||
IO.puts "#{inspect module} was not compiled with docs"
|
||||
end
|
||||
end
|
||||
|
||||
defp find_doc(docs, function, arity) do
|
||||
List.keyfind(docs, { function, arity }, 0)
|
||||
end
|
||||
|
||||
defp find_default_doc(docs, function, min) do
|
||||
Enum.find docs, fn(doc) ->
|
||||
case elem(doc, 0) do
|
||||
{ ^function, max } when max > min ->
|
||||
defaults = Enum.count elem(doc, 3), match?({ ://, _, _ }, &1)
|
||||
min + defaults >= max
|
||||
_ ->
|
||||
false
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Get the full signature from a function.
|
||||
defp print_signature({ _info, _line, _kind, _args, false }) do
|
||||
false
|
||||
end
|
||||
|
||||
defp print_signature({ { name, _arity }, _line, kind, args, docs }) do
|
||||
args = Enum.map_join(args, ", ", signature_arg(&1))
|
||||
IO.puts "* #{kind} #{name}(#{args})"
|
||||
docs || ""
|
||||
end
|
||||
|
||||
defp signature_arg({ ://, _, [left, right] }) do
|
||||
signature_arg(left) <> " // " <> Macro.to_binary(right)
|
||||
end
|
||||
|
||||
defp signature_arg({ var, _, _ }) do
|
||||
atom_to_binary(var)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Prints all types for the given module or prints out a specified type's
|
||||
Prints all types for the given module or prints out a specified type's
|
||||
specification
|
||||
|
||||
## Examples
|
||||
@@ -245,62 +146,22 @@ defmodule IEx.Helpers do
|
||||
"""
|
||||
defmacro t({ :/, _, [{ { :., _, [mod, fun] }, _, [] }, arity] }) do
|
||||
quote do
|
||||
t(unquote(mod), unquote(fun), unquote(arity))
|
||||
IEx.Introspection.t(unquote(mod), unquote(fun), unquote(arity))
|
||||
end
|
||||
end
|
||||
|
||||
defmacro t({ { :., _, [mod, fun] }, _, [] }) do
|
||||
quote do
|
||||
t(unquote(mod), unquote(fun))
|
||||
IEx.Introspection.t(unquote(mod), unquote(fun))
|
||||
end
|
||||
end
|
||||
|
||||
defmacro t(module) do
|
||||
quote do
|
||||
t(unquote(module), [])
|
||||
IEx.Introspection.t(unquote(module))
|
||||
end
|
||||
end
|
||||
|
||||
@doc false
|
||||
def t(module, type) when is_atom(type) do
|
||||
types = lc {_, {t, _, _args}} = typespec inlist Kernel.Typespec.beam_types(module),
|
||||
t == type do
|
||||
print_type(typespec)
|
||||
typespec
|
||||
end
|
||||
|
||||
if types == [] do
|
||||
IO.puts "No types for #{inspect module}.#{type} have been found"
|
||||
end
|
||||
|
||||
:ok
|
||||
end
|
||||
|
||||
def t(module, []) do
|
||||
types = lc type inlist Kernel.Typespec.beam_types(module), do: print_type(type)
|
||||
|
||||
if types == [] do
|
||||
IO.puts "No types for #{inspect module} have been found"
|
||||
end
|
||||
|
||||
:ok
|
||||
end
|
||||
|
||||
@doc false
|
||||
def t(module, type, arity) do
|
||||
types = lc {_, {t, _, args}} = typespec inlist Kernel.Typespec.beam_types(module),
|
||||
length(args) == arity and t == type, do: typespec
|
||||
|
||||
case types do
|
||||
[] ->
|
||||
IO.puts "No types for #{inspect module}.#{type}/#{arity} have been found"
|
||||
[type] ->
|
||||
print_type(type)
|
||||
end
|
||||
|
||||
:ok
|
||||
end
|
||||
|
||||
@doc """
|
||||
Prints all specs from a given module.
|
||||
|
||||
@@ -315,94 +176,34 @@ defmodule IEx.Helpers do
|
||||
"""
|
||||
defmacro s({ :/, _, [{ { :., _, [mod, fun] }, _, [] }, arity] }) do
|
||||
quote do
|
||||
s(unquote(mod), unquote(fun), unquote(arity))
|
||||
IEx.Introspection.s(unquote(mod), unquote(fun), unquote(arity))
|
||||
end
|
||||
end
|
||||
|
||||
defmacro s({ { :., _, [mod, fun] }, _, [] }) do
|
||||
quote do
|
||||
s(unquote(mod), unquote(fun))
|
||||
IEx.Introspection.s(unquote(mod), unquote(fun))
|
||||
end
|
||||
end
|
||||
|
||||
defmacro s({ fun, _, args }) when args == [] or is_atom(args) do
|
||||
quote do
|
||||
s(Kernel, unquote(fun))
|
||||
IEx.Introspection.s(Kernel, unquote(fun))
|
||||
end
|
||||
end
|
||||
|
||||
defmacro s({ :/, _, [{ fun, _, args }, arity] }) when args == [] or is_atom(args) do
|
||||
quote do
|
||||
s(Kernel, unquote(fun), unquote(arity))
|
||||
end
|
||||
IEx.Introspection.s(Kernel, unquote(fun), unquote(arity))
|
||||
end
|
||||
end
|
||||
|
||||
defmacro s(module) do
|
||||
quote do
|
||||
s(unquote(module), [])
|
||||
IEx.Introspection.s(unquote(module))
|
||||
end
|
||||
end
|
||||
|
||||
@doc false
|
||||
def s(module, function) when is_atom(function) do
|
||||
specs = lc {_kind, {{f, _arity}, _spec}} = spec inlist beam_specs(module),
|
||||
f == function do
|
||||
print_spec(spec)
|
||||
spec
|
||||
end
|
||||
|
||||
if specs == [] do
|
||||
IO.puts "No specs for #{inspect module}.#{function} have been found"
|
||||
end
|
||||
|
||||
:ok
|
||||
end
|
||||
|
||||
def s(module, []) do
|
||||
specs = lc spec inlist beam_specs(module), do: print_spec(spec)
|
||||
|
||||
if specs == [] do
|
||||
IO.puts "No specs for #{inspect module} have been found"
|
||||
end
|
||||
|
||||
:ok
|
||||
end
|
||||
|
||||
@doc false
|
||||
def s(module, function, arity) do
|
||||
specs = lc {_kind, {{f, a}, _spec}} = spec inlist beam_specs(module),
|
||||
f == function and a == arity do
|
||||
print_spec(spec)
|
||||
spec
|
||||
end
|
||||
|
||||
if specs == [] do
|
||||
IO.puts "No specs for #{inspect module}.#{function} have been found"
|
||||
end
|
||||
|
||||
:ok
|
||||
end
|
||||
|
||||
defp beam_specs(module) do
|
||||
specs = Enum.map(Kernel.Typespec.beam_specs(module), {:spec, &1})
|
||||
callbacks = Enum.map(Kernel.Typespec.beam_callbacks(module), {:callback, &1})
|
||||
List.concat(specs, callbacks)
|
||||
end
|
||||
|
||||
defp print_type({ kind, type }) do
|
||||
ast = Kernel.Typespec.type_to_ast(type)
|
||||
IO.puts "@#{kind} #{Macro.to_binary(ast)}"
|
||||
true
|
||||
end
|
||||
|
||||
defp print_spec({kind, { { name, _arity }, specs }}) do
|
||||
Enum.each specs, fn(spec) ->
|
||||
binary = Macro.to_binary Kernel.Typespec.spec_to_ast(name, spec)
|
||||
IO.puts "@#{kind} #{binary}"
|
||||
end
|
||||
true
|
||||
end
|
||||
|
||||
@doc """
|
||||
Retrieves nth query's value from the history. Use negative
|
||||
values to lookup query's value from latest to earliest.
|
||||
@@ -459,7 +260,7 @@ defmodule IEx.Helpers do
|
||||
flush
|
||||
after
|
||||
0 -> :ok
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defp iex_reloaded do
|
||||
@@ -485,4 +286,22 @@ defmodule IEx.Helpers do
|
||||
_ -> nil
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Prints the current working directory.
|
||||
"""
|
||||
def pwd do
|
||||
IO.puts IO.ANSI.escape("%{yellow}#{System.cwd!}")
|
||||
end
|
||||
|
||||
@doc """
|
||||
Changes the shell directory to the given path.
|
||||
"""
|
||||
def cd(directory) do
|
||||
case File.cd(directory) do
|
||||
:ok -> pwd
|
||||
{ :error, :enoent } ->
|
||||
IO.puts IO.ANSI.escape("%{red}No directory #{directory}")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -0,0 +1,263 @@
|
||||
defmodule IEx.Introspection do
|
||||
# Convenience helpers for showing docs, specs and types
|
||||
# from modules. Invoked directly from IEX.Helpers.
|
||||
@moduledoc false
|
||||
|
||||
@doc false
|
||||
def h(module) when is_atom(module) do
|
||||
case Code.ensure_loaded(module) do
|
||||
{ :module, _ } ->
|
||||
case module.__info__(:moduledoc) do
|
||||
{ _, binary } when is_binary(binary) ->
|
||||
IO.puts IO.ANSI.escape("%{yellow}# #{inspect module}\n")
|
||||
IO.write IO.ANSI.escape_fragment("%{yellow}") <> binary <> IO.ANSI.escape_fragment("%{reset}")
|
||||
{ _, _ } ->
|
||||
IO.puts IO.ANSI.escape("%{red}No docs for #{inspect module} have been found")
|
||||
_ ->
|
||||
IO.puts IO.ANSI.escape("%{red}#{inspect module} was not compiled with docs")
|
||||
end
|
||||
{ :error, reason } ->
|
||||
IO.puts IO.ANSI.escape("%{red}Could not load module #{inspect module}: #{reason}")
|
||||
end
|
||||
end
|
||||
|
||||
def h(_) do
|
||||
IO.puts IO.ANSI.escape("%{red}Invalid arguments for h helper")
|
||||
end
|
||||
|
||||
@doc false
|
||||
def h(modules, function) when is_list(modules) and is_atom(function) do
|
||||
result =
|
||||
Enum.reduce modules, :not_found, fn
|
||||
module, :not_found -> h_mod_fun(module, function)
|
||||
_module, acc -> acc
|
||||
end
|
||||
|
||||
unless result == :ok, do:
|
||||
IO.puts IO.ANSI.escape("%{red}No docs for #{function} have been found")
|
||||
|
||||
:ok
|
||||
end
|
||||
|
||||
def h(module, function) when is_atom(module) and is_atom(function) do
|
||||
case h_mod_fun(module, function) do
|
||||
:ok ->
|
||||
:ok
|
||||
:no_docs ->
|
||||
IO.puts IO.ANSI.escape("%{red}#{inspect module} was not compiled with docs")
|
||||
:not_found ->
|
||||
IO.puts IO.ANSI.escape("%{red}No docs for #{inspect module}.#{function} have been found")
|
||||
end
|
||||
|
||||
:ok
|
||||
end
|
||||
|
||||
def h(function, arity) when is_atom(function) and is_integer(arity) do
|
||||
h([__MODULE__, Kernel, Kernel.SpecialForms], function, arity)
|
||||
end
|
||||
|
||||
def h(_, _) do
|
||||
IO.puts IO.ANSI.escape("%{red}Invalid arguments for h helper")
|
||||
end
|
||||
|
||||
defp h_mod_fun(mod, fun) when is_atom(mod) and is_atom(fun) do
|
||||
if docs = mod.__info__(:docs) do
|
||||
result = lc { {f, arity}, _line, _type, _args, doc } inlist docs, fun == f, doc != false do
|
||||
h(mod, fun, arity)
|
||||
IO.puts ""
|
||||
end
|
||||
|
||||
if result != [], do: :ok, else: :not_found
|
||||
else
|
||||
:no_docs
|
||||
end
|
||||
end
|
||||
|
||||
@doc false
|
||||
def h(modules, function, arity) when is_list(modules) and is_atom(function) and is_integer(arity) do
|
||||
result =
|
||||
Enum.reduce modules, :not_found, fn
|
||||
module, :not_found -> h_mod_fun_arity(module, function, arity)
|
||||
_module, acc -> acc
|
||||
end
|
||||
|
||||
unless result == :ok, do:
|
||||
IO.puts IO.ANSI.escape("%{red}No docs for #{function}/#{arity} have been found")
|
||||
|
||||
:ok
|
||||
end
|
||||
|
||||
def h(module, function, arity) when is_atom(module) and is_atom(function) and is_integer(arity) do
|
||||
case h_mod_fun_arity(module, function, arity) do
|
||||
:ok ->
|
||||
:ok
|
||||
:no_docs ->
|
||||
IO.puts IO.ANSI.escape("%{red}#{inspect module} was not compiled with docs")
|
||||
:not_found ->
|
||||
IO.puts IO.ANSI.escape("%{red}No docs for #{inspect module}.#{function}/#{arity} have been found")
|
||||
end
|
||||
|
||||
:ok
|
||||
end
|
||||
|
||||
def h(_, _, _) do
|
||||
IO.puts IO.ANSI.escape("%{red}Invalid arguments for h helper")
|
||||
end
|
||||
|
||||
defp h_mod_fun_arity(mod, fun, arity) when is_atom(mod) and is_atom(fun) and is_integer(arity) do
|
||||
if docs = mod.__info__(:docs) do
|
||||
doc =
|
||||
cond do
|
||||
d = find_doc(docs, fun, arity) -> d
|
||||
d = find_default_doc(docs, fun, arity) -> d
|
||||
true -> nil
|
||||
end
|
||||
|
||||
if doc do
|
||||
print_doc(doc)
|
||||
:ok
|
||||
else
|
||||
:not_found
|
||||
end
|
||||
else
|
||||
:no_docs
|
||||
end
|
||||
end
|
||||
|
||||
defp find_doc(docs, function, arity) do
|
||||
if doc = List.keyfind(docs, { function, arity }, 0) do
|
||||
case elem(doc, 4) do
|
||||
false -> nil
|
||||
_ -> doc
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defp find_default_doc(docs, function, min) do
|
||||
Enum.find docs, fn(doc) ->
|
||||
case elem(doc, 0) do
|
||||
{ ^function, max } when max > min ->
|
||||
defaults = Enum.count elem(doc, 3), match?({ ://, _, _ }, &1)
|
||||
min + defaults >= max
|
||||
_ ->
|
||||
false
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defp print_doc({ { fun, _ }, _line, kind, args, doc }) do
|
||||
args = Enum.map_join(args, ", ", print_doc_arg(&1))
|
||||
IO.puts IO.ANSI.escape("%{yellow}* #{kind} #{fun}(#{args})\n")
|
||||
IO.write IO.ANSI.escape_fragment("%{yellow}") <> doc <> IO.ANSI.escape_fragment("%{reset}")
|
||||
end
|
||||
|
||||
defp print_doc_arg({ ://, _, [left, right] }) do
|
||||
print_doc_arg(left) <> " // " <> Macro.to_binary(right)
|
||||
end
|
||||
|
||||
defp print_doc_arg({ var, _, _ }) do
|
||||
atom_to_binary(var)
|
||||
end
|
||||
|
||||
@doc false
|
||||
def t(module) do
|
||||
types = lc type inlist Kernel.Typespec.beam_types(module), do: print_type(type)
|
||||
|
||||
if types == [] do
|
||||
IO.puts IO.ANSI.escape("%{red}No types for #{inspect module} have been found")
|
||||
end
|
||||
|
||||
:ok
|
||||
end
|
||||
|
||||
@doc false
|
||||
def t(module, type) when is_atom(type) do
|
||||
types = lc {_, {t, _, _args}} = typespec inlist Kernel.Typespec.beam_types(module),
|
||||
t == type do
|
||||
print_type(typespec)
|
||||
typespec
|
||||
end
|
||||
|
||||
if types == [] do
|
||||
IO.puts IO.ANSI.escape("%{red}No types for #{inspect module}.#{type} have been found")
|
||||
end
|
||||
|
||||
:ok
|
||||
end
|
||||
|
||||
@doc false
|
||||
def t(module, type, arity) do
|
||||
types = lc {_, {t, _, args}} = typespec inlist Kernel.Typespec.beam_types(module),
|
||||
length(args) == arity and t == type, do: typespec
|
||||
|
||||
case types do
|
||||
[] ->
|
||||
IO.puts IO.ANSI.escape("%{red}No types for #{inspect module}.#{type}/#{arity} have been found")
|
||||
[type] ->
|
||||
print_type(type)
|
||||
end
|
||||
|
||||
:ok
|
||||
end
|
||||
|
||||
@doc false
|
||||
def s(module) do
|
||||
specs = lc spec inlist beam_specs(module), do: print_spec(spec)
|
||||
|
||||
if specs == [] do
|
||||
IO.puts IO.ANSI.escape("%{red}No specs for #{inspect module} have been found")
|
||||
end
|
||||
|
||||
:ok
|
||||
end
|
||||
|
||||
@doc false
|
||||
def s(module, function) when is_atom(function) do
|
||||
specs = lc {_kind, {{f, _arity}, _spec}} = spec inlist beam_specs(module),
|
||||
f == function do
|
||||
print_spec(spec)
|
||||
spec
|
||||
end
|
||||
|
||||
if specs == [] do
|
||||
IO.puts IO.ANSI.escape("%{red}No specs for #{inspect module}.#{function} have been found")
|
||||
end
|
||||
|
||||
:ok
|
||||
end
|
||||
|
||||
@doc false
|
||||
def s(module, function, arity) do
|
||||
specs = lc {_kind, {{f, a}, _spec}} = spec inlist beam_specs(module),
|
||||
f == function and a == arity do
|
||||
print_spec(spec)
|
||||
spec
|
||||
end
|
||||
|
||||
if specs == [] do
|
||||
IO.puts IO.ANSI.escape("%{red}No specs for #{inspect module}.#{function} have been found")
|
||||
end
|
||||
|
||||
:ok
|
||||
end
|
||||
|
||||
defp beam_specs(module) do
|
||||
specs = Enum.map(Kernel.Typespec.beam_specs(module), {:spec, &1})
|
||||
callbacks = Enum.map(Kernel.Typespec.beam_callbacks(module), {:callback, &1})
|
||||
List.concat(specs, callbacks)
|
||||
end
|
||||
|
||||
defp print_type({ kind, type }) do
|
||||
ast = Kernel.Typespec.type_to_ast(type)
|
||||
IO.puts IO.ANSI.escape("%{yellow}@#{kind} #{Macro.to_binary(ast)}")
|
||||
true
|
||||
end
|
||||
|
||||
defp print_spec({kind, { { name, _arity }, specs }}) do
|
||||
Enum.each specs, fn(spec) ->
|
||||
binary = Macro.to_binary Kernel.Typespec.spec_to_ast(name, spec)
|
||||
IO.puts IO.ANSI.escape("%{yellow}@#{kind} #{binary}")
|
||||
end
|
||||
true
|
||||
end
|
||||
end
|
||||
@@ -4,7 +4,7 @@ defmodule IEx.Server do
|
||||
def start(config) do
|
||||
IO.puts "Interactive Elixir (#{System.version}) - press Ctrl+C to exit (type h() ENTER for help)"
|
||||
Process.put :iex_history, []
|
||||
{ _, _, scope } = :elixir.eval('import IEx.Helpers', [], 0, config.scope)
|
||||
{ _, _, scope } = :elixir.eval('require IEx.Helpers', [], 0, config.scope)
|
||||
do_loop(config.scope(scope))
|
||||
end
|
||||
|
||||
@@ -64,7 +64,7 @@ defmodule IEx.Server do
|
||||
end
|
||||
|
||||
defp io_put(result) do
|
||||
IO.inspect :stdio, result, IEx.inspect_opts
|
||||
IO.puts :stdio, IO.ANSI.escape("%{yellow}#{inspect(result, IEx.inspect_opts)}")
|
||||
end
|
||||
|
||||
defp io_error(result) do
|
||||
|
||||
@@ -54,11 +54,6 @@ defmodule IEx.AutocompleteTest do
|
||||
assert expand('System.ve') == {:yes, 'rsion', []}
|
||||
end
|
||||
|
||||
test :elixir_function_nodoc do
|
||||
{:yes, '', list} = expand('Keyword.get')
|
||||
assert 'get/2' inlist list
|
||||
end
|
||||
|
||||
test :elixir_function_completion_with_arity do
|
||||
assert expand('String.printable?') == {:yes, '', ['printable?/1']}
|
||||
assert expand('String.printable?/') == {:yes, '', ['printable?/1']}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user