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 | ||
|
|
90ff71b0cc | ||
|
|
7dd0aabbe2 | ||
|
|
658d7c7913 | ||
|
|
1f491dfbe2 | ||
|
|
8ca4517563 | ||
|
|
4e8690cd8e | ||
|
|
8f57b4c9af | ||
|
|
19d402b7d5 | ||
|
|
cf6751a3c8 | ||
|
|
42a0719c8a | ||
|
|
4527c1e849 | ||
|
|
5557e620ff | ||
|
|
683153ef05 | ||
|
|
8a77e878ab | ||
|
|
a19a4b893d | ||
|
|
b8689da1f5 | ||
|
|
a05592fdd9 | ||
|
|
3147df88fa | ||
|
|
1946feb4e1 | ||
|
|
a0101bc056 | ||
|
|
129725a168 | ||
|
|
e93613bc05 | ||
|
|
7b51500a60 | ||
|
|
f6058afb26 | ||
|
|
67b5236874 | ||
|
|
1e18d8e08e | ||
|
|
9ebce19f36 | ||
|
|
fb82b0c679 | ||
|
|
9dc7f44049 | ||
|
|
5e2c6e14be | ||
|
|
e896e311c1 | ||
|
|
ced13c159f | ||
|
|
5fe1544ff3 | ||
|
|
96c7f68a62 | ||
|
|
d0dc9f5c2c | ||
|
|
8a130978b2 | ||
|
|
71e2252064 | ||
|
|
ddc9733cf2 | ||
|
|
9bcc4be3f5 | ||
|
|
f66206b4f3 | ||
|
|
28211b2714 | ||
|
|
93f1bb920b | ||
|
|
109c07e757 | ||
|
|
eb36c2b288 | ||
|
|
3537bbbbf4 | ||
|
|
cd51c80362 | ||
|
|
a1a576e9d1 | ||
|
|
1dc4bf0989 | ||
|
|
1812e652db | ||
|
|
7f73cb3128 | ||
|
|
0123edd8a3 | ||
|
|
87af330589 | ||
|
|
3ae634a13c | ||
|
|
de3bd8ec86 | ||
|
|
3fea472cc2 | ||
|
|
7cb18091d0 | ||
|
|
8994feb0a3 | ||
|
|
f4aed8e873 | ||
|
|
008ee62f8b | ||
|
|
d543650f70 | ||
|
|
febf5e46b6 | ||
|
|
7196543060 | ||
|
|
fade5452e1 | ||
|
|
9e46866851 | ||
|
|
1dd1f2b213 | ||
|
|
3137bfc614 | ||
|
|
4d3d61e173 | ||
|
|
2e66218f58 | ||
|
|
3a1fbde95d | ||
|
|
088978fa04 | ||
|
|
249f83e82b | ||
|
|
608f0a3dcb | ||
|
|
d8aa534e3a | ||
|
|
c2df5fa5ff | ||
|
|
fee8836366 | ||
|
|
35a43afb22 | ||
|
|
c359f212f8 | ||
|
|
0f2dd3f7d4 | ||
|
|
20942ee329 | ||
|
|
d2f76453d6 | ||
|
|
50d375e33b | ||
|
|
119d48e093 | ||
|
|
6960f856f4 | ||
|
|
eb11254369 | ||
|
|
f686b08e5e | ||
|
|
80bd148852 |
@@ -6,6 +6,8 @@ notifications:
|
||||
- jose.valim@plataformatec.com.br
|
||||
- yrashk@gmail.com
|
||||
otp_release:
|
||||
- R16B
|
||||
- R15B03
|
||||
- R15B02
|
||||
- R15B01
|
||||
- R15B
|
||||
|
||||
@@ -1,3 +1,70 @@
|
||||
# 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
|
||||
* [ExUnit] Tests can now receive metadata set on setup/teardown callbacks
|
||||
* [ExUnit] Add support to ExUnit.CaseTemplate to share callbacks in between test cases
|
||||
* [IO] Add `IO.ANSI` to make it easy to write ANSI escape codes
|
||||
* [Kernel] Better support for Unicode lists
|
||||
* [Kernel] Reduce variables footprint in `case`/`receive` clauses
|
||||
* [Kernel] Disable native compilation when on_load attributes is present to work around an Erlang bug
|
||||
* [Macro] `Macro.expand` also considers macros from the current `__ENV__` module
|
||||
* [Mix] Improve support for compilation of `.erl` files
|
||||
* [Mix] Add support for compilation of `.yrl` and `.xrl` files
|
||||
* [OptionParser] Switches are now overridden by default but can be kept in order if chosen
|
||||
* [Typespec] Better error reporting for invalid typespecs
|
||||
|
||||
* bug fix
|
||||
* [Mix] Allow Mix projects to be generated with just one letter
|
||||
|
||||
* backwards incompatible changes
|
||||
* [Kernel] `before_compile` and `after_compile` callbacks now receive the environment as first argument instead of the module
|
||||
|
||||
* deprecations
|
||||
* [ExUnit] Explicitly defined test/setup/teardown functions are deprecated
|
||||
* [Kernel] Tidy up and clean `quote` API
|
||||
* [Kernel] Old `:local.(args)` syntax is deprecated
|
||||
* [Process] `Process.self` is deprecated in favor `Kernel.self`
|
||||
|
||||
# v0.8.0 (2013-01-28)
|
||||
|
||||
* enhancements
|
||||
|
||||
@@ -46,7 +46,7 @@ erlang:
|
||||
# file, then mix and then compile eex fully
|
||||
elixir: kernel unicode lib/eex/ebin/Elixir-EEx.beam mix ex_unit eex iex
|
||||
|
||||
kernel: $(KERNEL)
|
||||
kernel: $(KERNEL) VERSION
|
||||
$(KERNEL): lib/elixir/lib/*.ex lib/elixir/lib/*/*.ex
|
||||
@ if [ ! -f $(KERNEL) ]; then \
|
||||
echo "==> bootstrap (compile)"; \
|
||||
@@ -93,23 +93,23 @@ clean:
|
||||
|
||||
#==> Release tasks
|
||||
|
||||
docs:
|
||||
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:
|
||||
release_zip: compile
|
||||
rm -rf v$(VERSION).zip
|
||||
zip -9 -r v$(VERSION).zip bin CHANGELOG.md LEGAL lib/*/ebin LICENSE README.md rel VERSION
|
||||
|
||||
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:
|
||||
release_erl: compile
|
||||
@ rm -rf rel/elixir
|
||||
@ cd rel && ../rebar generate
|
||||
|
||||
@@ -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)"
|
||||
|
||||
+7
-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,15 +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
|
||||
* CHANGELOG
|
||||
* src/elixir.app.src
|
||||
|
||||
+5
-5
@@ -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
|
||||
|
||||
@@ -69,10 +68,11 @@ SELF=$(readlink_f "$0")
|
||||
SCRIPT_PATH=$(dirname "$SELF")
|
||||
|
||||
if [ -f "$HOME/.elixirrc" ]; then . "$HOME/.elixirrc"; fi
|
||||
if [ "$ELIXIR_NO_CLI" != "1" ]; then ERL="$ERL -s elixir start_cli"; fi
|
||||
|
||||
if [ -f "$SCRIPT_PATH/../releases/RELEASES" ] && [ -f "$SCRIPT_PATH/erl" ]
|
||||
then
|
||||
exec "$SCRIPT_PATH"/erl -env ERL_LIBS $ERL_LIBS:"$SCRIPT_PATH/../lib" -boot elixir -noshell $ELIXIR_ERL_OPTS $ERL -s elixir start_cli -extra "$@"
|
||||
exec "$SCRIPT_PATH"/erl -env ERL_LIBS $ERL_LIBS:"$SCRIPT_PATH/../lib" -boot elixir -noshell $ELIXIR_ERL_OPTS $ERL -extra "$@"
|
||||
else
|
||||
exec erl -env ERL_LIBS $ERL_LIBS:"$SCRIPT_PATH/../lib" -noshell $ELIXIR_ERL_OPTS $ERL -s elixir start_cli -extra "$@"
|
||||
exec erl -env ERL_LIBS $ERL_LIBS:"$SCRIPT_PATH/../lib" -noshell $ELIXIR_ERL_OPTS $ERL -extra "$@"
|
||||
fi
|
||||
|
||||
+7
-5
@@ -1,9 +1,11 @@
|
||||
@echo off
|
||||
if "%*" == "" (
|
||||
goto documentation
|
||||
set argc=0
|
||||
for %%x in (%*) do set /A argc+=1
|
||||
if %argc% == 0 (
|
||||
goto documentation
|
||||
) else (
|
||||
goto run
|
||||
)
|
||||
goto run
|
||||
)
|
||||
:documentation
|
||||
echo Usage: %~nx0 [options] [.exs file] [data]
|
||||
echo.
|
||||
@@ -20,4 +22,4 @@ echo ** Options marked with (*) can be given more than once
|
||||
echo ** Options given after the .exs file or -- are passed down to the executed code
|
||||
echo ** Options can be passed to the erlang runtime using ELIXIR_ERL_OPTS
|
||||
:run
|
||||
erl -env ERL_LIBS %ERL_LIBS%;"%~dp0\..\lib" -noshell %ELIXIR_ERL_OPTS% -s elixir start_cli -extra %*
|
||||
erl -env ERL_LIBS %ERL_LIBS%;"%~dp0\..\lib" -noshell %ELIXIR_ERL_OPTS% -s elixir start_cli -extra %*
|
||||
|
||||
+7
-5
@@ -1,9 +1,11 @@
|
||||
@echo off
|
||||
if "%*" == "" (
|
||||
goto documentation
|
||||
set argc=0
|
||||
for %%x in (%*) do set /A argc+=1
|
||||
if %argc% == 0 (
|
||||
goto documentation
|
||||
) else (
|
||||
goto run
|
||||
)
|
||||
goto run
|
||||
)
|
||||
:documentation
|
||||
echo Usage: %~nx0 [elixir switches] [compiler switches] [.ex files]
|
||||
echo.
|
||||
@@ -16,4 +18,4 @@ echo ** Options marked with (*) can be given more than once
|
||||
echo ** Options given after -- are passed down to the executed code
|
||||
echo ** Options can be passed to the erlang runtime using ELIXIR_ERL_OPTS" >&2
|
||||
:run
|
||||
call "%~dp0\elixir.bat" --compile %*
|
||||
call "%~dp0\elixir.bat" --compile %*
|
||||
|
||||
@@ -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,4 +33,4 @@ readlink_f () {
|
||||
|
||||
SELF=$(readlink_f "$0")
|
||||
SCRIPT_PATH=$(dirname "$SELF")
|
||||
exec "$SCRIPT_PATH"/elixir --no-halt --erl "-user Elixir-IEx-CLI" "$@"
|
||||
ELIXIR_NO_CLI=1 exec "$SCRIPT_PATH"/elixir --no-halt --erl "-user Elixir-IEx-CLI" "$@"
|
||||
+2
-1
@@ -1 +1,2 @@
|
||||
call "%~dp0\elixir.bat" "%~dp0\mix" %*
|
||||
@echo off
|
||||
call "%~dp0\elixir.bat" "%~dp0\mix" %*
|
||||
|
||||
+1
-1
@@ -191,7 +191,7 @@ defmodule EEx do
|
||||
def function_from_quoted(module, kind, name, args, source, info) do
|
||||
args = Enum.map args, fn arg -> { arg, [], nil } end
|
||||
quote = quote do
|
||||
unquote(kind).(unquote(name).(unquote_splicing(args)), do: unquote(source))
|
||||
unquote(kind)(unquote(name)(unquote_splicing(args)), do: unquote(source))
|
||||
end
|
||||
Module.eval_quoted module, quote, [], info
|
||||
end
|
||||
|
||||
@@ -112,7 +112,7 @@ defmodule EEx.Tokenizer do
|
||||
else
|
||||
:middle_expr
|
||||
end
|
||||
error ->
|
||||
_error ->
|
||||
:middle_expr
|
||||
end
|
||||
end
|
||||
|
||||
@@ -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
|
||||
@@ -25,17 +24,21 @@
|
||||
counter=[], %% a counter for the variables defined
|
||||
local=nil, %% the scope to evaluate local functions against
|
||||
scheduled=[], %% scheduled modules to be loaded
|
||||
file, %% the current scope filename
|
||||
macro_aliases=[], %% keep aliases defined inside a macro
|
||||
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,
|
||||
var_context=nil,
|
||||
expand_aliases=true,
|
||||
expand_imports=true,
|
||||
vars_hygiene=nil,
|
||||
aliases_hygiene=true,
|
||||
imports_hygiene=true,
|
||||
unquote=true
|
||||
}).
|
||||
|
||||
@@ -51,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]"
|
||||
|
||||
"""
|
||||
|
||||
@@ -261,8 +274,8 @@ defimpl Binary.Inspect, for: List do
|
||||
|
||||
def inspect(thing, opts) do
|
||||
cond do
|
||||
printable?(thing) ->
|
||||
escape(list_to_binary(thing), ?')
|
||||
:io_lib.printable_list(thing) ->
|
||||
escape(:unicode.characters_to_binary(thing), ?')
|
||||
Keyword.keyword?(thing) ->
|
||||
"[" <> join_keywords(thing, opts) <> "]"
|
||||
true ->
|
||||
@@ -282,19 +295,6 @@ defimpl Binary.Inspect, for: List do
|
||||
other -> other
|
||||
end
|
||||
end
|
||||
|
||||
## printable?
|
||||
|
||||
defp printable?([c|cs]) when is_integer(c) and c in 32..126 do
|
||||
printable?(cs)
|
||||
end
|
||||
|
||||
defp printable?([c|cs]) when c in [?\n, ?\r, ?\t, ?\v, ?\b, ?\f, ?\e, ?\a] do
|
||||
printable?(cs)
|
||||
end
|
||||
|
||||
defp printable?([]), do: true
|
||||
defp printable?(_), do: false
|
||||
end
|
||||
|
||||
defimpl Binary.Inspect, for: Tuple do
|
||||
@@ -306,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\\\"]"
|
||||
|
||||
"""
|
||||
|
||||
@@ -385,7 +387,8 @@ defimpl Binary.Inspect, for: Number do
|
||||
|
||||
## Examples
|
||||
|
||||
inspect(1) #=> "1"
|
||||
iex> inspect(1)
|
||||
"1"
|
||||
|
||||
"""
|
||||
|
||||
@@ -404,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
|
||||
|
||||
@@ -1,274 +0,0 @@
|
||||
defmodule Dict.Common do
|
||||
@moduledoc false
|
||||
|
||||
defmacro __using__(_) do
|
||||
quote do
|
||||
@behavior Dict
|
||||
|
||||
@doc """
|
||||
Creates a new empty dict.
|
||||
"""
|
||||
def new do
|
||||
IO.puts "#{inspect __MODULE__} is deprecated, please use HashDict instead"
|
||||
Exception.print_stacktrace
|
||||
empty(nil)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Creates a new dict from a list of pairs.
|
||||
|
||||
## Examples
|
||||
|
||||
#{inspect(__MODULE__)}.new [{:b,1},{:a,2}]
|
||||
#=> [a: 1, b: 2]
|
||||
|
||||
"""
|
||||
def new(pairs) do
|
||||
IO.puts "#{inspect __MODULE__} is deprecated, please use HashDict instead"
|
||||
Exception.print_stacktrace
|
||||
|
||||
Enum.reduce pairs, new, fn { k, v }, dict ->
|
||||
put(dict, k, v)
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Creates a new dict from a list of elements with the
|
||||
help of the transformation function.
|
||||
|
||||
## Examples
|
||||
|
||||
#{inspect(__MODULE__)}.new ["a", "b"], fn x -> {x, x} end
|
||||
#=> ["a": "a", "b": "b"]
|
||||
"""
|
||||
def new(list, transform) when is_function(transform) do
|
||||
IO.puts "#{inspect __MODULE__} is deprecated, please use HashDict instead"
|
||||
Exception.print_stacktrace
|
||||
|
||||
Enum.reduce list, new(), fn i, dict ->
|
||||
{ k, v } = transform.(i)
|
||||
put(dict, k, v)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defmodule OrdDict do
|
||||
@moduledoc """
|
||||
This module implements a dictionary type that stores items
|
||||
as a list of tuples. It is a simple wrapper around
|
||||
[Erlang's orddict module](http://www.erlang.org/doc/man/orddict.html)
|
||||
and exposed via the `Dict` module.
|
||||
|
||||
Check the `Dict` module for examples and documentation.
|
||||
"""
|
||||
|
||||
use Dict.Common
|
||||
|
||||
defmacrop dict(data) do
|
||||
quote do
|
||||
{ OrdDict, unquote(data) }
|
||||
end
|
||||
end
|
||||
|
||||
@doc false
|
||||
def keys(dict(data)) do
|
||||
lc { k, _ } inlist data, do: k
|
||||
end
|
||||
|
||||
@doc false
|
||||
def values(dict(data)) do
|
||||
lc { _, v } inlist data, do: v
|
||||
end
|
||||
|
||||
@doc false
|
||||
def size(dict(data)) do
|
||||
length(data)
|
||||
end
|
||||
|
||||
@doc false
|
||||
def has_key?(dict(data), key) do
|
||||
:orddict.is_key key, data
|
||||
end
|
||||
|
||||
@doc false
|
||||
def get(dict(data), key, default // nil) do
|
||||
case :orddict.find(key, data) do
|
||||
{:ok, value} -> value
|
||||
:error -> default
|
||||
end
|
||||
end
|
||||
|
||||
@doc false
|
||||
def get!(dict(data), key) do
|
||||
case :orddict.find(key, data) do
|
||||
{:ok, value} -> value
|
||||
:error -> raise(KeyError, key: key)
|
||||
end
|
||||
end
|
||||
|
||||
@doc false
|
||||
def put(dict(data), key, value) do
|
||||
dict(:orddict.store key, value, data)
|
||||
end
|
||||
|
||||
@doc false
|
||||
def put_new(dict(data), key, value) do
|
||||
dict(:orddict.update key, fn(x) -> x end, value, data)
|
||||
end
|
||||
|
||||
@doc false
|
||||
def delete(dict(data), key) do
|
||||
dict(:orddict.erase key, data)
|
||||
end
|
||||
|
||||
@doc false
|
||||
def merge(d1, d2, fun // fn(_k, _v1, v2) -> v2 end)
|
||||
|
||||
def merge(dict(d1), dict(d2), fun) do
|
||||
dict(:orddict.merge fun, d1, d2)
|
||||
end
|
||||
|
||||
def merge(dict(_) = d1, d2, fun) do
|
||||
merge(d1, new(d2), fun)
|
||||
end
|
||||
|
||||
@doc false
|
||||
def update(dict(data), key, fun) do
|
||||
dict(:orddict.update key, fun, data)
|
||||
end
|
||||
|
||||
@doc false
|
||||
def update(dict(data), key, initial, fun) do
|
||||
dict(:orddict.update key, fun, initial, data)
|
||||
end
|
||||
|
||||
@doc false
|
||||
def empty(_) do
|
||||
dict([])
|
||||
end
|
||||
|
||||
@doc false
|
||||
def to_list(dict(data)) do
|
||||
data
|
||||
end
|
||||
end
|
||||
|
||||
defimpl Enum.Iterator, for: OrdDict do
|
||||
def iterator({ OrdDict, data }), do: data
|
||||
def count({ OrdDict, data }), do: length(data)
|
||||
end
|
||||
|
||||
defimpl Access, for: OrdDict do
|
||||
def access(dict, key), do: OrdDict.get(dict, key, nil)
|
||||
end
|
||||
|
||||
defmodule Binary.Dict do
|
||||
@moduledoc """
|
||||
This module implements a dictionary that forces the keys to be
|
||||
converted to binaries on insertion. Currently it is implemented
|
||||
using an `OrdDict`, but this may change in the future.
|
||||
|
||||
Check the `Dict` module for examples and documentation.
|
||||
"""
|
||||
|
||||
use Dict.Common
|
||||
|
||||
defmacrop dict(data) do
|
||||
quote do
|
||||
{ Binary.Dict, unquote(data) }
|
||||
end
|
||||
end
|
||||
|
||||
@doc false
|
||||
def keys(dict(data)) do
|
||||
lc { k, _ } inlist data, do: k
|
||||
end
|
||||
|
||||
@doc false
|
||||
def values(dict(data)) do
|
||||
lc { _, v } inlist data, do: v
|
||||
end
|
||||
|
||||
@doc false
|
||||
def size(dict(data)) do
|
||||
length(data)
|
||||
end
|
||||
|
||||
@doc false
|
||||
def has_key?(dict(data), key) do
|
||||
:orddict.is_key to_binary(key), data
|
||||
end
|
||||
|
||||
@doc false
|
||||
def get(dict(data), key, default // nil) do
|
||||
case :orddict.find(to_binary(key), data) do
|
||||
{:ok, value} -> value
|
||||
:error -> default
|
||||
end
|
||||
end
|
||||
|
||||
@doc false
|
||||
def get!(dict(data), key) do
|
||||
case :orddict.find(to_binary(key), data) do
|
||||
{:ok, value} -> value
|
||||
:error -> raise(KeyError, key: key)
|
||||
end
|
||||
end
|
||||
|
||||
@doc false
|
||||
def put(dict(data), key, value) do
|
||||
dict(:orddict.store to_binary(key), value, data)
|
||||
end
|
||||
|
||||
@doc false
|
||||
def put_new(dict(data), key, value) do
|
||||
dict(:orddict.update to_binary(key), fn(x) -> x end, value, data)
|
||||
end
|
||||
|
||||
@doc false
|
||||
def delete(dict(data), key) do
|
||||
dict(:orddict.erase to_binary(key), data)
|
||||
end
|
||||
|
||||
@doc false
|
||||
def merge(d1, d2, fun // fn(_k, _v1, v2) -> v2 end)
|
||||
|
||||
def merge(dict(d1), dict(d2), fun) do
|
||||
dict(:orddict.merge fun, d1, d2)
|
||||
end
|
||||
|
||||
def merge(dict(_) = d1, d2, fun) do
|
||||
merge(d1, new(d2), fun)
|
||||
end
|
||||
|
||||
@doc false
|
||||
def update(dict(data), key, fun) do
|
||||
dict(:orddict.update to_binary(key), fun, data)
|
||||
end
|
||||
|
||||
@doc false
|
||||
def update(dict(data), key, initial, fun) do
|
||||
dict(:orddict.update to_binary(key), fun, initial, data)
|
||||
end
|
||||
|
||||
@doc false
|
||||
def empty(_) do
|
||||
dict([])
|
||||
end
|
||||
|
||||
@doc false
|
||||
def to_list(dict(data)) do
|
||||
data
|
||||
end
|
||||
end
|
||||
|
||||
defimpl Enum.Iterator, for: Binary.Dict do
|
||||
def iterator({ Binary.Dict, data }), do: data
|
||||
def count({ Binary.Dict, data }), do: length(data)
|
||||
end
|
||||
|
||||
defimpl Access, for: Binary.Dict do
|
||||
def access(dict, key), do: Binary.Dict.get(dict, key, nil)
|
||||
end
|
||||
+145
-113
@@ -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,21 +81,23 @@ 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
|
||||
@spec all?(t, (element -> boolean)) :: boolean
|
||||
@spec all?(t, (element -> as_boolean(term))) :: boolean
|
||||
|
||||
def all?(collection, fun // fn(x) -> x end)
|
||||
|
||||
@@ -116,21 +120,23 @@ 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
|
||||
@spec any?(t, (element -> boolean)) :: boolean
|
||||
@spec any?(t, (element -> as_boolean(term))) :: boolean
|
||||
|
||||
def any?(collection, fun // fn(x) -> x end)
|
||||
|
||||
@@ -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
|
||||
@@ -191,7 +201,7 @@ defmodule Enum do
|
||||
@doc """
|
||||
Counts for how many items the function returns true.
|
||||
"""
|
||||
@spec count(t, (element -> boolean)) :: non_neg_integer
|
||||
@spec count(t, (element -> as_boolean(term))) :: non_neg_integer
|
||||
def count(collection, fun) when is_list(collection) do
|
||||
do_count(collection, fun)
|
||||
end
|
||||
@@ -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,10 +254,10 @@ 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 -> boolean)) :: list
|
||||
@spec drop_while(t, (element -> as_boolean(term))) :: list
|
||||
def drop_while(collection, fun) when is_list(collection) do
|
||||
do_drop_while(collection, fun)
|
||||
end
|
||||
@@ -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,11 +334,11 @@ 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 -> boolean)) :: list
|
||||
@spec filter(t, (element -> as_boolean(term))) :: list
|
||||
def filter(collection, fun) when is_list(collection) do
|
||||
lc item inlist collection, fun.(item), do: item
|
||||
end
|
||||
@@ -342,11 +357,11 @@ 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 -> boolean), (element -> element)) :: list
|
||||
@spec filter_map(t, (element -> as_boolean(term)), (element -> element)) :: list
|
||||
def filter_map(collection, filter, mapper) when is_list(collection) do
|
||||
lc item inlist collection, filter.(item), do: mapper.(item)
|
||||
end
|
||||
@@ -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
|
||||
@@ -497,7 +517,7 @@ defmodule Enum do
|
||||
def join(collection, joiner // "")
|
||||
|
||||
def join(collection, joiner) when is_list(joiner) do
|
||||
binary_to_list join(collection, list_to_binary(joiner))
|
||||
:unicode.characters_to_list join(collection, :unicode.characters_to_binary(joiner))
|
||||
end
|
||||
|
||||
def join(collection, joiner) when is_list(collection) and is_binary(joiner) do
|
||||
@@ -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,15 +801,14 @@ 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 -> boolean)) :: {list, list}
|
||||
@spec split_while(t, (element -> as_boolean(term))) :: {list, list}
|
||||
def split_while(collection, fun) when is_list(collection) do
|
||||
do_split_while(collection, fun, [])
|
||||
end
|
||||
@@ -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,11 +866,11 @@ 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 -> boolean)) :: list
|
||||
@spec take_while(t, (element -> as_boolean(term))) :: list
|
||||
def take_while(collection, fun) when is_list(collection) do
|
||||
do_take_while(collection, fun)
|
||||
end
|
||||
@@ -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
|
||||
|
||||
@@ -96,11 +96,6 @@ defmodule Exception do
|
||||
"""
|
||||
def format_stacktrace(trace // nil)
|
||||
|
||||
def format_stacktrace(trace) when is_tuple(trace) do
|
||||
IO.puts "Exception.format_stacktrace is deprecated, please use format_entry instead"
|
||||
print_stacktrace
|
||||
end
|
||||
|
||||
def format_stacktrace(trace) do
|
||||
trace = trace || try do
|
||||
throw(:stacktrace)
|
||||
|
||||
+53
-105
@@ -62,7 +62,7 @@ defmodule File do
|
||||
|
||||
In order to write and read files, one must use the
|
||||
functions in the IO module. By default, a file is
|
||||
opened on binary mode which requires the functions
|
||||
opened in binary mode which requires the functions
|
||||
`IO.binread`, `IO.binwrite` and `IO.binreadline` to
|
||||
interact with the file. A developer may pass `:utf8`
|
||||
as an option when opening the file and then all other
|
||||
@@ -105,90 +105,6 @@ defmodule File do
|
||||
alias :filename, as: FN
|
||||
alias :filelib, as: FL
|
||||
|
||||
@doc false
|
||||
def expand_path(path) do
|
||||
IO.puts "File.expand_path is deprecated, please use Path.expand instead"
|
||||
Exception.print_stacktrace
|
||||
Path.expand(path)
|
||||
end
|
||||
|
||||
@doc false
|
||||
def expand_path(path, relative_to) do
|
||||
IO.puts "File.expand_path is deprecated, please use Path.expand instead"
|
||||
Exception.print_stacktrace
|
||||
Path.expand(path, relative_to)
|
||||
end
|
||||
|
||||
@doc false
|
||||
def basename(path) do
|
||||
IO.puts "File.basename is deprecated, please use Path.basename instead"
|
||||
Exception.print_stacktrace
|
||||
FN.basename(path)
|
||||
end
|
||||
|
||||
@doc false
|
||||
def basename(path, extension) do
|
||||
IO.puts "File.basename is deprecated, please use Path.basename instead"
|
||||
Exception.print_stacktrace
|
||||
FN.basename(path, extension)
|
||||
end
|
||||
|
||||
@doc false
|
||||
def dirname(path) do
|
||||
IO.puts "File.dirname is deprecated, please use Path.dirname instead"
|
||||
Exception.print_stacktrace
|
||||
FN.dirname(path)
|
||||
end
|
||||
|
||||
@doc false
|
||||
def extname(path) do
|
||||
IO.puts "File.extname is deprecated, please use Path.extname instead"
|
||||
Exception.print_stacktrace
|
||||
FN.extension(path)
|
||||
end
|
||||
|
||||
@doc false
|
||||
def rootname(path) do
|
||||
IO.puts "File.rootname is deprecated, please use Path.rootname instead"
|
||||
Exception.print_stacktrace
|
||||
FN.rootname(path)
|
||||
end
|
||||
|
||||
@doc false
|
||||
def rootname(path, extension) do
|
||||
IO.puts "File.rootname is deprecated, please use Path.rootname instead"
|
||||
Exception.print_stacktrace
|
||||
FN.rootname(path, extension)
|
||||
end
|
||||
|
||||
@doc false
|
||||
def join(paths) do
|
||||
IO.puts "File.join is deprecated, please use Path.join instead"
|
||||
Exception.print_stacktrace
|
||||
FN.join(paths)
|
||||
end
|
||||
|
||||
@doc false
|
||||
def join(left, right) do
|
||||
IO.puts "File.join is deprecated, please use Path.join instead"
|
||||
Exception.print_stacktrace
|
||||
FN.join(left, right)
|
||||
end
|
||||
|
||||
@doc false
|
||||
def split(path) do
|
||||
IO.puts "File.split is deprecated, please use Path.split instead"
|
||||
Exception.print_stacktrace
|
||||
FN.split(path)
|
||||
end
|
||||
|
||||
@doc false
|
||||
def wildcard(glob) do
|
||||
IO.puts "File.wildcard is deprecated, please use Path.wildcard instead"
|
||||
Exception.print_stacktrace
|
||||
Path.wildcard(glob)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns true if the path is a regular file.
|
||||
|
||||
@@ -253,7 +169,7 @@ defmodule File do
|
||||
case mkdir(path) do
|
||||
:ok -> :ok
|
||||
{ :error, reason } ->
|
||||
raise File.Error, reason: reason, action: "make directory", path: to_binary(path)
|
||||
raise File.Error, reason: reason, action: "make directory", path: :unicode.characters_to_binary(path)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -278,7 +194,7 @@ defmodule File do
|
||||
case mkdir_p(path) do
|
||||
:ok -> :ok
|
||||
{ :error, reason } ->
|
||||
raise File.Error, reason: reason, action: "make directory (with -p)", path: to_binary(path)
|
||||
raise File.Error, reason: reason, action: "make directory (with -p)", path: :unicode.characters_to_binary(path)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -311,7 +227,7 @@ defmodule File do
|
||||
{ :ok, binary } ->
|
||||
binary
|
||||
{ :error, reason } ->
|
||||
raise File.Error, reason: reason, action: "read file", path: to_binary(path)
|
||||
raise File.Error, reason: reason, action: "read file", path: :unicode.characters_to_binary(path)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -346,7 +262,7 @@ defmodule File do
|
||||
case stat(path, opts) do
|
||||
{:ok, info} -> info
|
||||
{:error, reason} ->
|
||||
raise File.Error, reason: reason, action: "read file stats", path: to_binary(path)
|
||||
raise File.Error, reason: reason, action: "read file stats", path: :unicode.characters_to_binary(path)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -355,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 """
|
||||
@@ -366,7 +282,7 @@ defmodule File do
|
||||
case write_stat(path, stat, opts) do
|
||||
:ok -> :ok
|
||||
{ :error, reason } ->
|
||||
raise File.Error, reason: reason, action: "write file stats", path: to_binary(path)
|
||||
raise File.Error, reason: reason, action: "write file stats", path: :unicode.characters_to_binary(path)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -389,7 +305,7 @@ defmodule File do
|
||||
case touch(path, time) do
|
||||
:ok -> :ok
|
||||
{ :error, reason } ->
|
||||
raise File.Error, reason: reason, action: "touch", path: to_binary(path)
|
||||
raise File.Error, reason: reason, action: "touch", path: :unicode.characters_to_binary(path)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -421,7 +337,7 @@ defmodule File do
|
||||
{ :ok, bytes_count } -> bytes_count
|
||||
{ :error, reason } ->
|
||||
raise File.CopyError, reason: reason, action: "copy",
|
||||
source: to_binary(source), destination: to_binary(destination)
|
||||
source: :unicode.characters_to_binary(source), destination: :unicode.characters_to_binary(destination)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -472,7 +388,7 @@ defmodule File do
|
||||
:ok -> :ok
|
||||
{ :error, reason } ->
|
||||
raise File.CopyError, reason: reason, action: "copy recursively",
|
||||
source: to_binary(source), destination: to_binary(destination)
|
||||
source: :unicode.characters_to_binary(source), destination: :unicode.characters_to_binary(destination)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -546,7 +462,7 @@ defmodule File do
|
||||
{ :ok, files } -> files
|
||||
{ :error, reason } ->
|
||||
raise File.CopyError, reason: reason, action: "copy recursively",
|
||||
source: to_binary(source), destination: to_binary(destination)
|
||||
source: :unicode.characters_to_binary(source), destination: :unicode.characters_to_binary(destination)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -651,7 +567,7 @@ defmodule File do
|
||||
case F.write_file(path, content, modes) do
|
||||
:ok -> :ok
|
||||
{ :error, reason } ->
|
||||
raise File.Error, reason: reason, action: "write to file", path: to_binary(path)
|
||||
raise File.Error, reason: reason, action: "write to file", path: :unicode.characters_to_binary(path)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -688,7 +604,7 @@ defmodule File do
|
||||
case rm(path) do
|
||||
:ok -> :ok
|
||||
{ :error, reason } ->
|
||||
raise File.Error, reason: reason, action: "remove file", path: to_binary(path)
|
||||
raise File.Error, reason: reason, action: "remove file", path: :unicode.characters_to_binary(path)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -716,7 +632,7 @@ defmodule File do
|
||||
case rmdir(path) do
|
||||
:ok -> :ok
|
||||
{ :error, reason } ->
|
||||
raise File.Error, reason: reason, action: "remove directory", path: to_binary(path)
|
||||
raise File.Error, reason: reason, action: "remove directory", path: :unicode.characters_to_binary(path)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -789,14 +705,20 @@ defmodule File do
|
||||
case rm_rf(path) do
|
||||
{ :ok, files } -> files
|
||||
{ :error, reason } ->
|
||||
raise File.Error, reason: reason, action: "remove files and directories recursively from", path: to_binary(path)
|
||||
raise File.Error, reason: reason, action: "remove files and directories recursively from", path: :unicode.characters_to_binary(path)
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Opens the given `path` according to the given list of modes.
|
||||
|
||||
By default, the file is opened in read mode, as a binary with utf8 encoding.
|
||||
In order to write and read files, one must use the functions
|
||||
in the IO module. By default, a file is opened in binary mode
|
||||
which requires the functions `IO.binread`, `IO.binwrite` and
|
||||
`IO.binreadline` to interact with the file. A developer may pass
|
||||
`:utf8` as an option when opening the file and then all other
|
||||
functions from IO are available, since they work directly with
|
||||
Unicode data.
|
||||
|
||||
The allowed modes:
|
||||
|
||||
@@ -873,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)
|
||||
|
||||
@@ -898,7 +820,7 @@ defmodule File do
|
||||
case open(path, modes) do
|
||||
{ :ok, device } -> device
|
||||
{ :error, reason } ->
|
||||
raise File.Error, reason: reason, action: "open", path: to_binary(path)
|
||||
raise File.Error, reason: reason, action: "open", path: :unicode.characters_to_binary(path)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -910,7 +832,7 @@ defmodule File do
|
||||
case open(path, modes, function) do
|
||||
{ :ok, device } -> device
|
||||
{ :error, reason } ->
|
||||
raise File.Error, reason: reason, action: "open", path: to_binary(path)
|
||||
raise File.Error, reason: reason, action: "open", path: :unicode.characters_to_binary(path)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -953,7 +875,7 @@ defmodule File do
|
||||
case F.set_cwd(path) do
|
||||
:ok -> :ok
|
||||
{ :error, reason } ->
|
||||
raise File.Error, reason: reason, action: "set current working directory to", path: to_binary(path)
|
||||
raise File.Error, reason: reason, action: "set current working directory to", path: :unicode.characters_to_binary(path)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -975,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.
|
||||
@@ -1006,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
|
||||
+42
-36
@@ -33,26 +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
|
||||
|
||||
@@ -62,6 +63,7 @@ defmodule HashDict do
|
||||
@doc """
|
||||
Creates a new empty dict.
|
||||
"""
|
||||
@spec new :: Dict.t
|
||||
def new do
|
||||
ordered()
|
||||
end
|
||||
@@ -72,9 +74,10 @@ 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
|
||||
def new(pairs) do
|
||||
Enum.reduce pairs, ordered(), fn { k, v }, dict ->
|
||||
put(dict, k, v)
|
||||
@@ -88,9 +91,10 @@ 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
|
||||
def new(list, transform) when is_function(transform) do
|
||||
Enum.reduce list, new(), fn i, dict ->
|
||||
{ k, v } = transform.(i)
|
||||
@@ -123,7 +127,7 @@ defmodule HashDict do
|
||||
case dict_put(dict, key, { :update, nil, fun }) do
|
||||
{ dict, 0 } ->
|
||||
dict
|
||||
{ dict, 1 } ->
|
||||
{ _dict, 1 } ->
|
||||
raise KeyError, key: key
|
||||
end
|
||||
end
|
||||
@@ -184,14 +188,14 @@ defmodule HashDict do
|
||||
{ _, 0 } ->
|
||||
dict
|
||||
{ root, -1 } ->
|
||||
res = if depth > 0 and trie(dict, :contract_on) == size do
|
||||
if depth > 0 and trie(dict, :contract_on) == size do
|
||||
root = node_contract(root, depth, depth - 1)
|
||||
trie(dict,
|
||||
root: root,
|
||||
size: size - 1,
|
||||
depth: depth - 1,
|
||||
contract_on: div(size, @node_size),
|
||||
expand_on: div(trie(dict, :expand_on), @node_size))
|
||||
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
|
||||
@@ -275,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
|
||||
@@ -291,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
|
||||
|
||||
@@ -368,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
|
||||
@@ -395,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
|
||||
@@ -409,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
|
||||
|
||||
@@ -417,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
|
||||
|
||||
@@ -427,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
|
||||
|
||||
@@ -457,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
|
||||
|
||||
+25
-11
@@ -23,6 +23,8 @@ defmodule IO do
|
||||
|
||||
"""
|
||||
|
||||
import :erlang, only: [group_leader: 0]
|
||||
|
||||
@doc """
|
||||
Reads `count` bytes from the IO device. It returns:
|
||||
|
||||
@@ -34,7 +36,7 @@ defmodule IO do
|
||||
for instance {:error, :estale} if reading from an
|
||||
NFS file system.
|
||||
"""
|
||||
def read(device // :stdio, count) do
|
||||
def read(device // group_leader(), count) do
|
||||
:io.get_chars(map_dev(device), "", count)
|
||||
end
|
||||
|
||||
@@ -44,7 +46,7 @@ defmodule IO do
|
||||
|
||||
Check `read/2` for more information.
|
||||
"""
|
||||
def binread(device // :stdio, count) do
|
||||
def binread(device // group_leader(), count) do
|
||||
case :file.read(map_dev(device), count) do
|
||||
{ :ok, data } -> data
|
||||
other -> other
|
||||
@@ -65,7 +67,7 @@ defmodule IO do
|
||||
This function does the same as `gets/2`,
|
||||
except the prompt is not required as argument.
|
||||
"""
|
||||
def readline(device // :stdio) do
|
||||
def readline(device // group_leader()) do
|
||||
:io.get_line(map_dev(device), "")
|
||||
end
|
||||
|
||||
@@ -75,7 +77,7 @@ defmodule IO do
|
||||
|
||||
Check `readline/1` for more information.
|
||||
"""
|
||||
def binreadline(device // :stdio) do
|
||||
def binreadline(device // group_leader()) do
|
||||
case :file.read_line(map_dev(device)) do
|
||||
{ :ok, data } -> data
|
||||
other -> other
|
||||
@@ -99,7 +101,7 @@ defmodule IO do
|
||||
#=> "error"
|
||||
|
||||
"""
|
||||
def write(device // :stdio, item) do
|
||||
def write(device // group_leader(), item) do
|
||||
:io.put_chars map_dev(device), to_iodata(item)
|
||||
end
|
||||
|
||||
@@ -109,7 +111,7 @@ defmodule IO do
|
||||
|
||||
Check `write/2` for more information.
|
||||
"""
|
||||
def binwrite(device // :stdio, item) do
|
||||
def binwrite(device // group_leader(), item) do
|
||||
:file.write map_dev(device), to_iodata(item)
|
||||
end
|
||||
|
||||
@@ -118,16 +120,28 @@ defmodule IO do
|
||||
but adds a new line at the end. The argument is expected
|
||||
to be a chardata.
|
||||
"""
|
||||
def puts(device // :stdio, item) do
|
||||
def puts(device // group_leader(), item) do
|
||||
erl_dev = map_dev(device)
|
||||
:io.put_chars erl_dev, [to_iodata(item), ?\n]
|
||||
end
|
||||
|
||||
@doc """
|
||||
Inspects and writes the given argument to the device
|
||||
followed by a new line. Returns the item given.
|
||||
followed by a new line. A set of options can be given.
|
||||
|
||||
## Examples
|
||||
|
||||
IO.inspect Process.list
|
||||
|
||||
"""
|
||||
def inspect(device // :stdio, item, opts // []) do
|
||||
def inspect(item, opts // []) do
|
||||
inspect group_leader(), item, opts
|
||||
end
|
||||
|
||||
@doc """
|
||||
Inspects the item with options using the given device.
|
||||
"""
|
||||
def inspect(device, item, opts) do
|
||||
puts device, Binary.Inspect.inspect(item, opts)
|
||||
item
|
||||
end
|
||||
@@ -143,7 +157,7 @@ defmodule IO do
|
||||
for instance {:error, :estale} if reading from an
|
||||
NFS file system.
|
||||
"""
|
||||
def getb(device // :stdio, prompt, count // 1) do
|
||||
def getb(device // group_leader(), prompt, count // 1) do
|
||||
:io.get_chars(map_dev(device), to_iodata(prompt), count)
|
||||
end
|
||||
|
||||
@@ -159,7 +173,7 @@ defmodule IO do
|
||||
for instance {:error, :estale} if reading from an
|
||||
NFS file system.
|
||||
"""
|
||||
def gets(device // :stdio, prompt) do
|
||||
def gets(device // group_leader(), prompt) do
|
||||
:io.get_line(map_dev(device), to_iodata(prompt))
|
||||
end
|
||||
|
||||
|
||||
@@ -0,0 +1,233 @@
|
||||
defmodule IO.ANSI.Sequence do
|
||||
@moduledoc false
|
||||
|
||||
defmacro defsequence(name, code) do
|
||||
binary_name = atom_to_binary(name)
|
||||
|
||||
quote do
|
||||
def unquote(name)() do
|
||||
"\e[#{unquote(code)}m"
|
||||
end
|
||||
|
||||
defp escape_sequence(<< unquote(binary_name), rest :: binary >>) do
|
||||
{ "\e[#{unquote(code)}m", rest }
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defmodule IO.ANSI do
|
||||
@moduledoc """
|
||||
This module provides functionality to render ANSI escape sequences
|
||||
(http://en.wikipedia.org/wiki/ANSI_escape_code) — characters embedded
|
||||
in the text used to control formatting, color, and other output options
|
||||
on video text terminals.
|
||||
|
||||
Please be aware that in Erlang/OTP versions prior to R16, you will not
|
||||
be able to render ANSI escape sequences in iex or erlang shell
|
||||
"""
|
||||
|
||||
import IO.ANSI.Sequence
|
||||
|
||||
@doc """
|
||||
Checks whether the default I/O device is a terminal or a file.
|
||||
|
||||
Used to identify whether printing ANSI escape sequences will likely
|
||||
be printed as intended.
|
||||
|
||||
Please note that invoked while in shell (iex) in Erlang/OTP
|
||||
prior to R16, terminal?/0 will always return false because
|
||||
Erlang shell did not support ANSI escape sequences up until
|
||||
R16.
|
||||
"""
|
||||
@spec terminal? :: boolean
|
||||
@spec terminal?(:io.device) :: boolean
|
||||
def terminal?(device // :erlang.group_leader) do
|
||||
if :erlang.system_info(:otp_release) < 'R16' and
|
||||
Process.whereis(:user) != device do
|
||||
# Shell prior to R16 doesn't support ANSI escape
|
||||
# sequences
|
||||
false
|
||||
else
|
||||
match?({:ok, _}, :io.columns(device))
|
||||
end
|
||||
end
|
||||
|
||||
@doc "Resets all attributes"
|
||||
defsequence :reset, 0
|
||||
|
||||
@doc "Bright (increased intensity) or Bold"
|
||||
defsequence :bright, 1
|
||||
|
||||
@doc "Faint (decreased intensity), not widely supported"
|
||||
defsequence :faint, 2
|
||||
|
||||
@doc "Italic: on. Not widely supported. Sometimes treated as inverse."
|
||||
defsequence :italic, 3
|
||||
|
||||
@doc "Underline: Single"
|
||||
defsequence :underline, 4
|
||||
|
||||
@doc "Blink: Slow. Less than 150 per minute"
|
||||
defsequence :blink_slow, 5
|
||||
|
||||
@doc "Blink: Rapid. MS-DOS ANSI.SYS; 150 per minute or more; not widely supported"
|
||||
defsequence :blink_rapid, 6
|
||||
|
||||
@doc "Image: Negative. Swap foreground and background"
|
||||
defsequence :inverse, 7
|
||||
|
||||
@doc "Image: Negative. Swap foreground and background"
|
||||
defsequence :reverse, 7
|
||||
|
||||
@doc "Conceal. Not widely supported"
|
||||
defsequence :conceal, 8
|
||||
|
||||
@doc "Crossed-out. Characters legible, but marked for deletion. Not widely supported."
|
||||
defsequence :crossed_out, 9
|
||||
|
||||
@doc "Sets primary (default) font"
|
||||
defsequence :primary_font, 10
|
||||
|
||||
lc font_n inlist [1,2,3,4,5,6,7,8,9] do
|
||||
Module.eval_quoted __ENV__, (quote do
|
||||
@doc "Sets alternative font #{unquote(font_n)}"
|
||||
defsequence(unquote(:"font_#{font_n}"), unquote(font_n + 10))
|
||||
end)
|
||||
end
|
||||
|
||||
@doc "Normal color or intensity"
|
||||
defsequence :normal, 22
|
||||
|
||||
@doc "Not italic"
|
||||
defsequence :not_italic, 23
|
||||
|
||||
@doc "Underline: None"
|
||||
defsequence :no_underline, 24
|
||||
|
||||
@doc "Blink: off"
|
||||
defsequence :blink_off, 25
|
||||
|
||||
colors = [:black, :red, :green, :yellow, :blue, :magenta, :cyan, :white]
|
||||
colors = Enum.zip(0..(length(colors)-1), colors)
|
||||
|
||||
lc { code, color } inlist colors do
|
||||
Module.eval_quoted __ENV__, (quote do
|
||||
@doc "Sets foreground color to #{unquote(color)}"
|
||||
defsequence unquote(color), unquote(code + 30)
|
||||
|
||||
@doc "Sets background color to #{unquote(color)}"
|
||||
defsequence unquote(:"#{color}_background"), unquote(code + 40)
|
||||
end)
|
||||
end
|
||||
|
||||
@doc "Default text color"
|
||||
defsequence :default_color, 39
|
||||
|
||||
@doc "Default background color"
|
||||
defsequence :default_background, 49
|
||||
|
||||
@doc "Framed"
|
||||
defsequence :framed, 51
|
||||
|
||||
@doc "Encircled"
|
||||
defsequence :encircled, 52
|
||||
|
||||
@doc "Overlined"
|
||||
defsequence :overlined, 53
|
||||
|
||||
@doc "Not framed or encircled"
|
||||
defsequence :not_framed_encircled, 54
|
||||
|
||||
@doc "Not overlined"
|
||||
defsequence :not_overlined, 55
|
||||
|
||||
# Catch spaces between codes
|
||||
defp escape_sequence(<< ?\s, rest :: binary >>) do
|
||||
escape_sequence(rest)
|
||||
end
|
||||
|
||||
defp escape_sequence(other) do
|
||||
[spec|_] = String.split(other, %r/(,|\})/)
|
||||
raise ArgumentError, message: "invalid ANSI sequence specification: #{spec}"
|
||||
end
|
||||
|
||||
@doc %B"""
|
||||
Escapes a string coverting named ANSI sequences into actual ANSI codes.
|
||||
|
||||
The format for referring sequences is `%{red}` and `%{red,bright}` (for
|
||||
multiple sequences)
|
||||
|
||||
It will also force a %{reset} to get appended to every string. If you don't
|
||||
want this behaviour, use `escape_fragment/1` and `escape_fragment/2`.
|
||||
|
||||
An optional boolean parameter can be passed to enable or disable
|
||||
emitting actual ANSI codes. When false, no ANSI codes will emitted.
|
||||
By default, standard output will be checked if it is a terminal capable
|
||||
of handling these sequences (using `terminal?/0` function)
|
||||
|
||||
## Example
|
||||
|
||||
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
|
||||
def escape(string, emit // terminal?) do
|
||||
{rendered, emitted} = do_escape(string, false, emit, false, [])
|
||||
if emitted and emit do
|
||||
rendered <> reset
|
||||
else
|
||||
rendered
|
||||
end
|
||||
end
|
||||
|
||||
@doc %B"""
|
||||
Escapes a string coverting named ANSI sequences into actual ANSI codes.
|
||||
|
||||
The format for referring sequences is `%{red}` and `%{red,bright}` (for
|
||||
multiple sequences)
|
||||
|
||||
An optional boolean parameter can be passed to enable or disable
|
||||
emitting actual ANSI codes. When false, no ANSI codes will emitted.
|
||||
By default, standard output will be checked if it is a terminal capable
|
||||
of handling these sequences (using `terminal?/0` function)
|
||||
|
||||
## Example
|
||||
|
||||
iex> IO.ANSI.escape "Hello %{red,bright,green}yes"
|
||||
"Hello \e[31m\e[1m\e[32myes\e[0m"
|
||||
|
||||
"""
|
||||
@spec escape_fragment(String.t, emit :: boolean) :: String.t
|
||||
def escape_fragment(string, emit // terminal?) do
|
||||
{rendered, _emitted} = do_escape(string, false, emit, false, [])
|
||||
rendered
|
||||
end
|
||||
|
||||
defp do_escape(<< ?%, ?{, rest :: binary >>, false, emit, _emitted, acc) do
|
||||
do_escape_sequence(rest, emit, acc)
|
||||
end
|
||||
defp do_escape(<< ?,, rest :: binary >>, true, emit, _emitted, acc) do
|
||||
do_escape_sequence(rest, emit, acc)
|
||||
end
|
||||
defp do_escape(<< ?\s, rest :: binary >>, true, emit, emitted, acc) do
|
||||
do_escape(rest, true, emit, emitted, acc)
|
||||
end
|
||||
defp do_escape(<< ?}, rest :: binary >>, true, emit, emitted, acc) do
|
||||
do_escape(rest, false, emit, emitted, acc)
|
||||
end
|
||||
defp do_escape(<< x :: [binary, size(1)], rest :: binary>>, false, emit, emitted, acc) do
|
||||
do_escape(rest, false, emit, emitted, [x|acc])
|
||||
end
|
||||
defp do_escape("", false, _emit, emitted, acc) do
|
||||
{list_to_binary(Enum.reverse(acc)), emitted}
|
||||
end
|
||||
|
||||
defp do_escape_sequence(rest, emit, acc) do
|
||||
{code, rest} = escape_sequence(rest)
|
||||
if emit do
|
||||
acc = [code|acc]
|
||||
end
|
||||
do_escape(rest, true, emit, true, acc)
|
||||
end
|
||||
end
|
||||
+658
-380
File diff suppressed because it is too large
Load Diff
@@ -16,23 +16,9 @@ defmodule Kernel.CLI do
|
||||
|
||||
run fn ->
|
||||
Enum.map Enum.reverse(config.commands), process_command(&1, config)
|
||||
:elixir_code_server.cast(:finished)
|
||||
end, config.halt
|
||||
end
|
||||
|
||||
@doc """
|
||||
Wait until the CLI finishes processing options.
|
||||
"""
|
||||
def wait_until_finished do
|
||||
case :elixir_code_server.call({ :wait_until_finished, self }) do
|
||||
:wait ->
|
||||
receive do
|
||||
{ :elixir_code_server, :finished } -> :ok
|
||||
end
|
||||
:ok -> :ok
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Runs the given function by catching any failure
|
||||
and printing them to stdout. `at_exit` hooks are
|
||||
@@ -104,7 +90,7 @@ defmodule Kernel.CLI do
|
||||
|
||||
defp shared_option?(list, config, callback) do
|
||||
case process_shared(list, config) do
|
||||
{ [h|t], _ } when h == hd(list) ->
|
||||
{ [h|_], _ } when h == hd(list) ->
|
||||
invalid_option h
|
||||
{ new_list, new_config } ->
|
||||
callback.(new_list, new_config)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -91,7 +91,7 @@ defmodule Kernel.ParallelCompiler do
|
||||
# list because someone invoked try/rescue UndefinedFunctionError
|
||||
new_waiting = List.keydelete(waiting, child, 0)
|
||||
spawn_compilers(files, output, callback, new_waiting, new_queued, schedulers, result)
|
||||
{ :module_available, child, module, binary } ->
|
||||
{ :module_available, _child, module, binary } ->
|
||||
new_waiting = release_waiting_processes(module, waiting)
|
||||
new_result = [{module, binary}|result]
|
||||
wait_for_messages(files, output, callback, new_waiting, queued, schedulers, new_result)
|
||||
@@ -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
|
||||
@@ -55,13 +63,13 @@ defmodule Kernel.RecordRewriter do
|
||||
defp record_field_info(function) do
|
||||
case atom_to_list(function) do
|
||||
'update' -> nil
|
||||
'update_' ++ field -> nil
|
||||
'update_' ++ _field -> nil
|
||||
_ -> { :accessor, function }
|
||||
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,81 +155,81 @@ 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 =
|
||||
case args do
|
||||
[{ :atom, _, atom }|t] -> atom
|
||||
[{ :atom, _, atom }|_] -> atom
|
||||
_ -> nil
|
||||
end
|
||||
|
||||
{ { :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
|
||||
|
||||
@@ -355,15 +364,12 @@ defmodule Kernel.SpecialForms do
|
||||
|
||||
## Options
|
||||
|
||||
* `:hygiene` - When false, disables hygiene for variables, aliases and imports;
|
||||
* `:unquote` - When false, disables unquoting. Useful when you have a quote
|
||||
inside another quote and want to control which quote is
|
||||
able to unquote;
|
||||
* `:location` - When set to `:keep`, keeps the current line and file on quotes.
|
||||
Read the Stacktrace information section below for more information;
|
||||
* `:expand_aliases` - When false, do not expand aliases;
|
||||
* `:expand_imports` - When false, do not expand imports;
|
||||
* `:var_context` - The context for quoted variables. Defaults to the current module;
|
||||
* `:hygiene` - Allows a developer to disable hygiene selectively;
|
||||
|
||||
## Macro literals
|
||||
|
||||
@@ -379,17 +385,15 @@ defmodule Kernel.SpecialForms do
|
||||
|
||||
## Hygiene
|
||||
|
||||
Elixir macros are hygienic. This means aliases and imports
|
||||
defined inside the quoted often refer to the context that
|
||||
defined the macro.
|
||||
Elixir macros are hygienic via means of deferred resolution.
|
||||
|
||||
Furthermore, variables inside quote are also hygienic. That
|
||||
said, a variable defined in a macro cannot affect the scope
|
||||
where the macro is included.
|
||||
This means aliases and imports defined inside the quoted refer
|
||||
to the context that defined the macro and not the context
|
||||
where the macro is expanded.
|
||||
|
||||
The option `hygiene` can turn off all the hygiene mechanisms
|
||||
defined below. However, they can also be changed in a one by
|
||||
one basis.
|
||||
Furthermore, variables inside quote are also hygienic: a
|
||||
variable defined in a macro cannot affect the variables where
|
||||
the macro is expanded.
|
||||
|
||||
### Hygiene in variables
|
||||
|
||||
@@ -451,14 +455,13 @@ defmodule Kernel.SpecialForms do
|
||||
# Access the variable a from Hygiene1
|
||||
quote do: var!(a, Hygiene1) = 2
|
||||
|
||||
Another option is to set the `var_context` option, affecting
|
||||
all variables in the block:
|
||||
Hygiene for variables can be disabled overall as:
|
||||
|
||||
quote var_context: Hygiene1, do: a = 2
|
||||
quote hygiene: [vars: false], do: x
|
||||
|
||||
### Hygiene in aliases
|
||||
|
||||
Aliases inside quote are expanded by default.
|
||||
Aliases inside quote are hygienic by default.
|
||||
Consider the following example:
|
||||
|
||||
defmodule Hygiene do
|
||||
@@ -470,24 +473,43 @@ defmodule Kernel.SpecialForms do
|
||||
end
|
||||
|
||||
require Hygiene
|
||||
Hygiene.no_interference #=> HashDict[]
|
||||
Hygiene.no_interference #=> #HashDict<[]>
|
||||
|
||||
Notice that, even though the alias `D` is not available
|
||||
in the context the macro is expanded, the code above works
|
||||
because `D` was expanded when the quote was generated.
|
||||
because `D` still expands to `HashDict`.
|
||||
|
||||
There are two ways to disable this behaviour. By giving
|
||||
the `expand_aliases` equals to false to quote or by using
|
||||
the `alias!` macro inside the quote:
|
||||
In some particular cases you may want to access an alias
|
||||
or a module defined in the caller. In such scenarios, you
|
||||
can access it by disabling hygiene with `hygiene: [aliases: false]`
|
||||
or by using the `alias!` macro inside the quote:
|
||||
|
||||
defmodule NoHygiene do
|
||||
defmodule Hygiene do
|
||||
# This will expand to Elixir.Nested.hello
|
||||
defmacro no_interference do
|
||||
quote do: Nested.hello
|
||||
end
|
||||
|
||||
# This will expand to Nested.hello for
|
||||
# whatever is Nested in the caller
|
||||
defmacro interference do
|
||||
quote do: alias!(D).new
|
||||
quote do: alias!(Nested).hello
|
||||
end
|
||||
end
|
||||
|
||||
require NoHygiene
|
||||
NoHygiene.interference #=> UndefinedFunctionError
|
||||
defmodule Parent do
|
||||
defmodule Nested do
|
||||
def hello, do: "world"
|
||||
end
|
||||
|
||||
require Hygiene
|
||||
Hygiene.no_interference
|
||||
#=> ** (UndefinedFunctionError) ...
|
||||
|
||||
Hygiene.interference
|
||||
#=> "world"
|
||||
end
|
||||
|
||||
|
||||
## Hygiene in imports
|
||||
|
||||
@@ -536,7 +558,7 @@ defmodule Kernel.SpecialForms do
|
||||
Lazy.return_size #=> 2
|
||||
|
||||
As in aliases, imports expansion can be explicitly disabled
|
||||
via the `expand_imports` option.
|
||||
via the `hygiene: [imports: false]` option.
|
||||
|
||||
## Stacktrace information
|
||||
|
||||
@@ -570,7 +592,7 @@ defmodule Kernel.SpecialForms do
|
||||
particular, the macro `__FILE__` and exceptions happening inside
|
||||
the quote will always point to `GenServer.Behaviour` file.
|
||||
"""
|
||||
defmacro quote(opts, do: contents)
|
||||
defmacro quote(opts, block)
|
||||
|
||||
@doc """
|
||||
When used inside quoting, marks that the variable should
|
||||
@@ -594,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}]
|
||||
|
||||
"""
|
||||
@@ -681,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"
|
||||
|
||||
"""
|
||||
@@ -692,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)
|
||||
@@ -715,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,
|
||||
|
||||
@@ -75,7 +75,7 @@ defmodule Kernel.Typespec do
|
||||
Elixir discourages the use of type `string()` as it might be confused
|
||||
with binaries which are referred to as "strings" in Elixir (as opposed to
|
||||
character lists). In order to use the type that is called `string()` in Erlang,
|
||||
one has to use the `char_list()` type which is a synonym to `string()`. If yu
|
||||
one has to use the `char_list()` type which is a synonym for `string()`. If you
|
||||
use `string()`, you'll get a warning from the compiler.
|
||||
|
||||
If you want to refer to the "string" type (the one operated by functions in the
|
||||
@@ -325,7 +325,7 @@ defmodule Kernel.Typespec do
|
||||
|
||||
defp abstract_code(module) do
|
||||
case :beam_lib.chunks(abstract_code_beam(module), [:abstract_code]) do
|
||||
{:ok, { _, [{ :abstract_code, { raw_abstract_v1, abstract_code } }] } } ->
|
||||
{:ok, { _, [{ :abstract_code, { _raw_abstract_v1, abstract_code } }] } } ->
|
||||
{ :ok, abstract_code }
|
||||
_ ->
|
||||
[]
|
||||
@@ -350,10 +350,17 @@ defmodule Kernel.Typespec do
|
||||
do_deftype(kind, type, definition, caller)
|
||||
end
|
||||
|
||||
def deftype(kind, type, caller) do
|
||||
def deftype(kind, {name, _meta, args} = type, caller) when
|
||||
is_atom(name) and
|
||||
not is_list(args) do
|
||||
do_deftype(kind, type, { :term, [line: caller.line], nil }, caller)
|
||||
end
|
||||
|
||||
def deftype(_kind, other, _caller) do
|
||||
type_spec = Macro.to_binary(other)
|
||||
raise ArgumentError, message: "invalid type specification #{type_spec}"
|
||||
end
|
||||
|
||||
defp do_deftype(kind, { name, _, args }, definition, caller) do
|
||||
args =
|
||||
if is_atom(args) do
|
||||
@@ -390,6 +397,11 @@ defmodule Kernel.Typespec do
|
||||
code
|
||||
end
|
||||
|
||||
def defspec(_type, other, _caller) do
|
||||
spec = Macro.to_binary(other)
|
||||
raise ArgumentError, message: "invalid function type specification #{spec}"
|
||||
end
|
||||
|
||||
defp guard_to_constraints({ :is_subtype, meta, [{ name, _, _ }, type] }, caller) do
|
||||
line = line(meta)
|
||||
contraints = [{ :atom, line, :is_subtype }, [{:var, line, name}, typespec(type, [], caller)]]
|
||||
@@ -467,6 +479,11 @@ defmodule Kernel.Typespec do
|
||||
typespec_to_ast({:type, line, :char_list, []})
|
||||
end
|
||||
|
||||
defp typespec_to_ast({ :remote_type, line, [{:atom, _, :elixir}, {:atom, _, :as_boolean}, [arg]] }) do
|
||||
typespec_to_ast({:type, line, :as_boolean, [arg]})
|
||||
end
|
||||
|
||||
|
||||
defp typespec_to_ast({ :remote_type, line, [mod, name, args] }) do
|
||||
args = lc arg inlist args, do: typespec_to_ast(arg)
|
||||
dot = { :., [line: line], [typespec_to_ast(mod), typespec_to_ast(name)] }
|
||||
@@ -542,6 +559,7 @@ defmodule Kernel.Typespec do
|
||||
end
|
||||
|
||||
# Handle funs
|
||||
|
||||
defp typespec({:->, meta, [{[{:fun, _, arguments}], return}]}, vars, caller) when is_list(arguments) do
|
||||
typespec({:->, meta, [{arguments, return}]}, vars, caller)
|
||||
end
|
||||
@@ -566,7 +584,7 @@ defmodule Kernel.Typespec do
|
||||
# Handle access macro
|
||||
defp typespec({{:., meta, [Kernel, :access]}, meta1, [target, args]}, vars, caller) do
|
||||
access = {{:., meta, [Kernel, :access]}, meta1,
|
||||
[target, args ++ [_: (quote hygiene: false, do: any)]]}
|
||||
[target, args ++ [_: { :any, [], [] }]]}
|
||||
typespec(Macro.expand(access, caller), vars, caller)
|
||||
end
|
||||
|
||||
@@ -591,6 +609,12 @@ defmodule Kernel.Typespec do
|
||||
{ :type, line(meta), :tuple, args }
|
||||
end
|
||||
|
||||
# Handle blocks
|
||||
|
||||
defp typespec({:__block__, _meta, [arg]}, vars, caller) do
|
||||
typespec(arg, vars, caller)
|
||||
end
|
||||
|
||||
# Handle variables or local calls
|
||||
defp typespec({name, meta, atom}, vars, caller) when is_atom(atom) do
|
||||
if List.member?(vars, name) do
|
||||
@@ -612,6 +636,10 @@ defmodule Kernel.Typespec do
|
||||
typespec((quote do: :elixir.char_list(unquote_splicing(arguments))), vars, caller)
|
||||
end
|
||||
|
||||
defp typespec({:as_boolean, _meta, arguments}, vars, caller) do
|
||||
typespec((quote do: :elixir.as_boolean(unquote_splicing(arguments))), vars, caller)
|
||||
end
|
||||
|
||||
defp typespec({name, meta, arguments}, vars, caller) do
|
||||
arguments = lc arg inlist arguments, do: typespec(arg, vars, caller)
|
||||
{ :type, line(meta), name, arguments }
|
||||
@@ -676,4 +704,4 @@ defmodule Kernel.Typespec do
|
||||
defp variable({name, meta, _}) do
|
||||
{:var, line(meta), name}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
+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
|
||||
|
||||
+127
-50
@@ -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
|
||||
case :elixir_aliases.expand(original, env.aliases) 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
|
||||
@@ -478,8 +544,16 @@ defmodule Macro do
|
||||
case not is_partial?(args) do
|
||||
false -> original
|
||||
true ->
|
||||
module = env.module
|
||||
|
||||
extra = if function_exported?(module, :__info__, 1) do
|
||||
[{ module, module.__info__(:macros) }]
|
||||
else
|
||||
[]
|
||||
end
|
||||
|
||||
expand = :elixir_dispatch.expand_import(line, { atom, length(args) }, args,
|
||||
env.module, env.function, env.requires, env.macros, env)
|
||||
env.module, extra, to_erl_env(env, cache))
|
||||
case expand do
|
||||
{ :ok, _, expanded } -> expanded
|
||||
{ :error, _ } -> original
|
||||
@@ -488,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
|
||||
|
||||
@@ -522,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.
|
||||
|
||||
+25
-16
@@ -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
|
||||
@@ -210,7 +216,7 @@ defmodule Module do
|
||||
[] ->
|
||||
ETS.insert(table, { tuple, line, kind, signature, doc })
|
||||
:ok
|
||||
[{ tuple, line, old_kind, old_sign, old_doc }] when old_doc == nil or doc == nil or old_doc == doc ->
|
||||
[{ tuple, line, _old_kind, old_sign, old_doc }] when old_doc == nil or doc == nil or old_doc == doc ->
|
||||
ETS.insert(table, {
|
||||
tuple,
|
||||
line,
|
||||
@@ -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)>>
|
||||
|
||||
+13
-1
@@ -74,11 +74,23 @@ defmodule Node do
|
||||
|
||||
See http://www.erlang.org/doc/man/erlang.html#disconnect_node-1 for more info.
|
||||
"""
|
||||
@spec disconnect(t) :: boolean | :ginored
|
||||
@spec disconnect(t) :: boolean | :ignored
|
||||
def disconnect(node) do
|
||||
:erlang.disconnect_node(node)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Establishes a connection to Node. Returns true if successful,
|
||||
false if not, and the atom `:ignored` if the local node is not
|
||||
alive.
|
||||
|
||||
See http://erlang.org/doc/man/net_kernel.html#connect_node-1 for more info.
|
||||
"""
|
||||
@spec connect(t) :: boolean | :ignored
|
||||
def connect(node) do
|
||||
:net_kernel.connect_node(node)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns the pid of a new process started by the application of `fun`
|
||||
on `node`. If `node` does not exist, a useless pid is returned.
|
||||
|
||||
@@ -5,45 +5,53 @@ 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
|
||||
|
||||
Extra information about switches can be given as argument too. This is useful
|
||||
in order to say a switch must behave as a boolean, list, etc. The following
|
||||
types are supported:
|
||||
Extra information about switches can be given as argument too.
|
||||
This is useful in order to say a switch must behave as a boolean
|
||||
or if duplicated switches should be kept, overriden or accumulated.
|
||||
|
||||
* `:boolean` - They never consume the next value unless it is true/false;
|
||||
The following types are supported:
|
||||
|
||||
* `:boolean` - Mark the given switch as boolean. Boolean switches
|
||||
never consumes the following value unless it is
|
||||
true or false;
|
||||
|
||||
The following extra options are supported:
|
||||
|
||||
* `:keep` - Keep duplicated items in the list instead of overriding;
|
||||
|
||||
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
|
||||
@@ -58,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
|
||||
@@ -69,11 +77,6 @@ defmodule OptionParser do
|
||||
## Helpers
|
||||
|
||||
defp parse(argv, opts, bool) do
|
||||
if flags = opts[:flags] do
|
||||
IO.write "OptionParser option :flags is deprecated, please use :switches instead\n#{Exception.format_stacktrace}"
|
||||
opts = Keyword.put(opts, :switches, lc(k inlist flags, do: { k, :boolean }))
|
||||
end
|
||||
|
||||
aliases = opts[:aliases] || []
|
||||
switches = opts[:switches] || []
|
||||
parse(argv, aliases, switches, bool)
|
||||
@@ -85,16 +88,18 @@ defmodule OptionParser do
|
||||
|
||||
defp parse(["-" <> option|t], aliases, switches, dict, args, all) do
|
||||
{ option, value } = normalize_option(option, aliases)
|
||||
kind = switches[option]
|
||||
|
||||
if value == nil do
|
||||
{ value, t } =
|
||||
case switch_type(switches, option) do
|
||||
:boolean -> boolean_from_tail(t)
|
||||
_ -> value_from_tail(t)
|
||||
if is_switch_a? :boolean, kind do
|
||||
boolean_from_tail(t)
|
||||
else
|
||||
value_from_tail(t)
|
||||
end
|
||||
end
|
||||
|
||||
dict = store_option dict, option, value
|
||||
dict = store_option dict, option, value, kind
|
||||
parse(t, aliases, switches, dict, args, all)
|
||||
end
|
||||
|
||||
@@ -117,16 +122,22 @@ defmodule OptionParser do
|
||||
defp value_from_tail([h|t]), do: { h, t }
|
||||
defp value_from_tail([]), do: { true, [] }
|
||||
|
||||
defp store_option(dict, option, value) when value in ["false", "true"] do
|
||||
store_option(dict, option, binary_to_atom(value))
|
||||
defp store_option(dict, option, value, switches) when value in ["true", "false"] do
|
||||
store_option dict, option, binary_to_atom(value), switches
|
||||
end
|
||||
|
||||
defp store_option(dict, option, value) do
|
||||
[{ option, value }|dict]
|
||||
defp store_option(dict, option, value, kind) do
|
||||
if is_switch_a? :keep, kind do
|
||||
[{ option, value }|dict]
|
||||
else
|
||||
[{ option, value }|Keyword.delete(dict, option)]
|
||||
end
|
||||
end
|
||||
|
||||
defp reverse_dict(dict, switches) do
|
||||
switches = lc { k, :boolean } inlist switches, not Keyword.has_key?(dict, k), do: { k, false }
|
||||
switches = lc { k, v } inlist switches,
|
||||
is_switch_a?(:boolean, v),
|
||||
not Keyword.has_key?(dict, k), do: { k, false }
|
||||
Enum.reverse switches ++ dict
|
||||
end
|
||||
|
||||
@@ -155,7 +166,7 @@ defmodule OptionParser do
|
||||
defp is_no?("no-" <> _), do: true
|
||||
defp is_no?(_), do: false
|
||||
|
||||
defp switch_type(switches, option) do
|
||||
switches[option] || :default
|
||||
end
|
||||
defp is_switch_a?(kind, list) when is_list(list), do: List.member?(list, kind)
|
||||
defp is_switch_a?(kind, kind), do: true
|
||||
defp is_switch_a?(_, _), do: false
|
||||
end
|
||||
|
||||
+76
-69
@@ -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
|
||||
@@ -456,12 +465,12 @@ defmodule Path do
|
||||
end
|
||||
|
||||
def wildcard(glob) when is_list(glob) do
|
||||
Enum.map wildcard(list_to_binary(glob)), binary_to_list(&1)
|
||||
:elixir_glob.wildcard glob
|
||||
end
|
||||
|
||||
## Helpers
|
||||
|
||||
defp get_cwd(path) when is_list(path), do: System.cwd! |> binary_to_list
|
||||
defp get_cwd(path) when is_list(path), do: System.cwd! |> binary_to_filename_string
|
||||
defp get_cwd(_), do: System.cwd!
|
||||
|
||||
defp binary_to_filename_string(binary) 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_list]
|
||||
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
|
||||
@@ -19,11 +19,10 @@ defmodule Process do
|
||||
:erlang.is_process_alive(pid)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns the current process.
|
||||
"""
|
||||
@spec self :: pid
|
||||
@doc false
|
||||
def self do
|
||||
IO.puts "Process.self is deprecated, please use Kernel.self instead"
|
||||
Exception.print_stacktrace
|
||||
:erlang.self()
|
||||
end
|
||||
|
||||
|
||||
@@ -287,7 +287,7 @@ defmodule Protocol do
|
||||
# Generate all others protocols.
|
||||
defp each_impl_for({ kind, fun, _ }, _, _) do
|
||||
quote do
|
||||
defp __raw_impl__(arg) when unquote(fun).(arg) do
|
||||
defp __raw_impl__(arg) when unquote(fun)(arg) do
|
||||
__MODULE__.unquote(kind)
|
||||
end
|
||||
end
|
||||
@@ -404,7 +404,7 @@ defmodule Protocol.DSL do
|
||||
|
||||
# Generate a fake definition with the user
|
||||
# signature that will be used by docs
|
||||
Kernel.def unquote(name).(unquote_splicing(args))
|
||||
Kernel.def unquote(name)(unquote_splicing(args))
|
||||
|
||||
{ args, body } = Protocol.DSL.args_and_body(__MODULE__, name, arity)
|
||||
Kernel.def unquote(name), args, [], do: body
|
||||
|
||||
+64
-26
@@ -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__)
|
||||
|
||||
defmacrop unquote(name)() do
|
||||
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)
|
||||
defmacrop unquote(name)(record) when is_tuple(record) do
|
||||
Record.to_keywords(unquote(tag) || __MODULE__, unquote(escaped), record)
|
||||
end
|
||||
|
||||
defmacrop unquote(name).(args) do
|
||||
Record.access(__MODULE__, unquote(escaped), args, __CALLER__)
|
||||
defmacrop unquote(name)(args) do
|
||||
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)
|
||||
defmacrop unquote(name)(record, key) when is_atom(key) do
|
||||
Record.get(unquote(tag) || __MODULE__, unquote(escaped), record, key)
|
||||
end
|
||||
|
||||
defmacrop unquote(name).(record, args) do
|
||||
Record.dispatch(__MODULE__, unquote(escaped), record, args, __CALLER__)
|
||||
defmacrop unquote(name)(record, args) do
|
||||
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
|
||||
@@ -465,17 +492,17 @@ defmodule Record do
|
||||
|
||||
contents = quote do
|
||||
@doc false
|
||||
def unquote(key).(record) do
|
||||
def unquote(key)(record) do
|
||||
:erlang.element(unquote(i + 1), record)
|
||||
end
|
||||
|
||||
@doc false
|
||||
def unquote(key).(value, record) do
|
||||
def unquote(key)(value, record) do
|
||||
:erlang.setelement(unquote(i + 1), record, value)
|
||||
end
|
||||
|
||||
@doc false
|
||||
def unquote(update).(function, record) do
|
||||
def unquote(update)(function, record) do
|
||||
:erlang.setelement(unquote(i + 1), record,
|
||||
function.(:erlang.element(unquote(i + 1), record)))
|
||||
end
|
||||
@@ -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
|
||||
|
||||
+43
-25
@@ -54,8 +54,7 @@ defmodule Regex do
|
||||
It returns `{ :ok, regex }` in case of success,
|
||||
`{ :error, reason }` otherwise.
|
||||
"""
|
||||
def compile(source, options // "") do
|
||||
source = to_binary(source)
|
||||
def compile(source, options // "") when is_binary(source) do
|
||||
options = to_binary(options)
|
||||
opts = translate_options(options)
|
||||
re_opts = opts -- [:groups]
|
||||
@@ -87,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
|
||||
@@ -103,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
|
||||
@@ -117,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 // [])
|
||||
@@ -128,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
|
||||
|
||||
@@ -140,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
|
||||
@@ -151,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
|
||||
@@ -160,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
|
||||
@@ -171,7 +178,8 @@ defmodule Regex do
|
||||
|
||||
## Examples
|
||||
|
||||
Regex.source %r(foo) #=> "foo"
|
||||
iex> Regex.source %r(foo)
|
||||
"foo"
|
||||
|
||||
"""
|
||||
def source(regex(source: source)) do
|
||||
@@ -179,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
|
||||
@@ -195,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
|
||||
@@ -210,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 // [])
|
||||
@@ -257,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
|
||||
@@ -302,7 +320,7 @@ defmodule Regex do
|
||||
lc result inlist results do
|
||||
case result do
|
||||
[t] -> t
|
||||
[h|t] -> t
|
||||
[_|t] -> t
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
+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
|
||||
|
||||
@@ -24,4 +24,4 @@
|
||||
{verbose, false}
|
||||
]}.
|
||||
|
||||
{require_otp_vsn,"R15"}.
|
||||
{require_otp_vsn,"(R15|R16).*"}.
|
||||
|
||||
@@ -8,8 +8,9 @@
|
||||
-compile({parse_transform, elixir_transform}).
|
||||
|
||||
%% Top level types
|
||||
-export_type([char_list/0]).
|
||||
-export_type([char_list/0, as_boolean/1]).
|
||||
-type char_list() :: string().
|
||||
-type as_boolean(T) :: T.
|
||||
|
||||
% OTP APPLICATION API
|
||||
|
||||
@@ -36,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),
|
||||
|
||||
@@ -1,26 +1,51 @@
|
||||
-module(elixir_aliases).
|
||||
-export([nesting/2, last/1, concat/1, safe_concat/1, lookup/2,
|
||||
format_error/1, ensure_loaded/3, expand/2]).
|
||||
-export([nesting_alias/2, last/1, concat/1, safe_concat/1,
|
||||
format_error/1, ensure_loaded/3, expand/3]).
|
||||
-include("elixir.hrl").
|
||||
-compile({parse_transform, elixir_transform}).
|
||||
|
||||
%% Expand an alias returning an atom if possible.
|
||||
%% Otherwise returns the list of aliases args.
|
||||
%% Expand an alias. It returns an atom (meaning that there
|
||||
%% was an expansion) or a list of atoms.
|
||||
|
||||
expand({ '__aliases__', _Line, [H] }, Aliases) when H /= 'Elixir' ->
|
||||
expand_alias(H, Aliases);
|
||||
expand({ '__aliases__', Meta, _ } = Alias, Aliases, MacroAliases) ->
|
||||
case lists:keyfind(alias, 1, Meta) of
|
||||
{ alias, false } ->
|
||||
expand(Alias, MacroAliases);
|
||||
{ alias, Atom } when is_atom(Atom) ->
|
||||
case expand(Alias, MacroAliases) of
|
||||
OtherAtom when is_atom(OtherAtom) -> OtherAtom;
|
||||
OtherAliases when is_list(OtherAliases) -> Atom
|
||||
end;
|
||||
false ->
|
||||
expand(Alias, Aliases)
|
||||
end.
|
||||
|
||||
expand({ '__aliases__', _Line, [H|T] }, Aliases) when is_atom(H) ->
|
||||
concat(case H of
|
||||
'Elixir' -> [H|T];
|
||||
_ -> [expand_alias(H, Aliases)|T]
|
||||
end);
|
||||
expand({ '__aliases__', _Meta, [H] }, Aliases) when H /= 'Elixir' ->
|
||||
case expand_one(H, Aliases) of
|
||||
false -> [H];
|
||||
Atom -> Atom
|
||||
end;
|
||||
|
||||
expand({ '__aliases__', _Line, List }, _Aliases) ->
|
||||
expand({ '__aliases__', _Meta, [H|T] }, Aliases) when is_atom(H) ->
|
||||
case H of
|
||||
'Elixir' ->
|
||||
concat(T);
|
||||
_ ->
|
||||
case expand_one(H, Aliases) of
|
||||
false -> [H|T];
|
||||
Atom -> concat([Atom|T])
|
||||
end
|
||||
end;
|
||||
|
||||
expand({ '__aliases__', _Meta, List }, _Aliases) ->
|
||||
List.
|
||||
|
||||
expand_alias(H, Aliases) ->
|
||||
lookup(list_to_atom("Elixir-" ++ atom_to_list(H)), Aliases).
|
||||
expand_one(H, Aliases) ->
|
||||
Lookup = list_to_atom("Elixir-" ++ atom_to_list(H)),
|
||||
case lookup(Lookup, Aliases) of
|
||||
Lookup -> false;
|
||||
Else -> Else
|
||||
end.
|
||||
|
||||
%% Ensure a module is loaded before its usage.
|
||||
|
||||
@@ -39,7 +64,7 @@ ensure_loaded(Line, Ref, S) ->
|
||||
elixir_errors:form_error(Line, S#elixir_scope.file, ?MODULE, { Kind, Ref })
|
||||
end.
|
||||
|
||||
%% Receives an atom and returns the last alias.
|
||||
%% Receives an atom and returns the last bit as an alias.
|
||||
|
||||
last(Atom) ->
|
||||
Last = last(lists:reverse(atom_to_list(Atom)), []),
|
||||
@@ -49,27 +74,43 @@ last([$-|_], Acc) -> Acc;
|
||||
last([H|T], Acc) -> last(T, [H|Acc]);
|
||||
last([], Acc) -> Acc.
|
||||
|
||||
%% Returns the nesting between two aliases.
|
||||
%% Gets two modules names and return an alias
|
||||
%% which can be passed down to the alias directive
|
||||
%% and it will create a proper shortcut representing
|
||||
%% the given nesting.
|
||||
%%
|
||||
%% Examples:
|
||||
%%
|
||||
%% nesting_alias('Elixir.Foo.Bar', 'Elixir.Foo.Bar.Baz.Bat')
|
||||
%% { 'Elixir.Baz', 'Elixir.Foo.Bar.Baz' }
|
||||
%%
|
||||
%% When passed to alias, the example above will generate an
|
||||
%% alias like:
|
||||
%%
|
||||
%% 'Elixir.Baz' => 'Elixir.Foo.Bar.Baz'
|
||||
%%
|
||||
nesting_alias(nil, _Full) -> false;
|
||||
|
||||
nesting(nil, _Full) -> false;
|
||||
|
||||
nesting(Prefix, Full) ->
|
||||
nesting_alias(Prefix, Full) ->
|
||||
PrefixList = list_nesting(Prefix),
|
||||
FullList = list_nesting(Full),
|
||||
(PrefixList /= []) andalso do_nesting(PrefixList, FullList, []).
|
||||
|
||||
(PrefixList /= []) andalso
|
||||
(PrefixList /= FullList) andalso
|
||||
lists:prefix(PrefixList, FullList) andalso
|
||||
binary_to_atom(<<"Elixir-", (hd(FullList -- PrefixList))/binary>>, utf8).
|
||||
do_nesting([X|PreTail], [X|Tail], Acc) ->
|
||||
do_nesting(PreTail, Tail, [X|Acc]);
|
||||
do_nesting([], [H|_], Acc) ->
|
||||
{ list_to_atom("Elixir-" ++ H), concat(lists:reverse([H|Acc])) };
|
||||
do_nesting(_, _, _Acc) ->
|
||||
false.
|
||||
|
||||
list_nesting(Atom) ->
|
||||
case binary:split(atom_to_binary(Atom, utf8), <<$->>, [global]) of
|
||||
[<<"Elixir">>|T] -> T;
|
||||
case string:tokens(atom_to_list(Atom), "-") of
|
||||
["Elixir"|T] -> T;
|
||||
_ -> []
|
||||
end.
|
||||
|
||||
%% Receives a list of atoms representing modules
|
||||
%% and concatenate them.
|
||||
%% Receives a list of atoms, binaries or lists
|
||||
%% representing modules and concatenates them.
|
||||
|
||||
concat(Args) -> list_to_atom(raw_concat(Args)).
|
||||
safe_concat(Args) -> list_to_existing_atom(raw_concat(Args)).
|
||||
@@ -93,7 +134,7 @@ dot_to_dash(List) ->
|
||||
_ -> X
|
||||
end || X <- List].
|
||||
|
||||
%% Lookup an alias in the current scope
|
||||
%% Lookup an alias in the current scope.
|
||||
|
||||
lookup(Else, Dict) ->
|
||||
case orddict:find(Else, Dict) of
|
||||
@@ -104,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).
|
||||
@@ -108,63 +108,58 @@ do_match(Meta, DecoupledClauses, S) ->
|
||||
{ TX, { umergec(S, TAcc), [TAcc#elixir_scope.clause_vars|CV] } }
|
||||
end,
|
||||
|
||||
{ TClauses, { TS, RawCV } } = lists:mapfoldl(Transformer, {S, []}, DecoupledClauses),
|
||||
{ TClauses, { TS, ReverseCV } } = lists:mapfoldl(Transformer, {S, []}, DecoupledClauses),
|
||||
|
||||
% Now get all the variables defined inside each clause
|
||||
CV = lists:reverse(RawCV),
|
||||
CV = lists:reverse(ReverseCV),
|
||||
AllVars = lists:foldl(fun(KV, Acc) ->
|
||||
elixir_scope:merge_clause_vars(Acc, KV)
|
||||
end, orddict:new(), CV),
|
||||
|
||||
AllVars = lists:umerge([orddict:fetch_keys(X) || X <- CV]),
|
||||
SharedVars = ordsets:intersection(CV),
|
||||
% Create a new scope that contains a list of all variables
|
||||
% defined inside all the clauses. It returns this new scope and
|
||||
% a list of tuples where the first element is the variable name,
|
||||
% the second one is the new pointer to the variable and the third
|
||||
% is the old pointer.
|
||||
{ FinalVars, FS } = lists:mapfoldl(fun({ Key, Ref }, Acc) ->
|
||||
normalize_vars(Key, Ref, Acc)
|
||||
end, TS, AllVars),
|
||||
|
||||
case AllVars of
|
||||
[] -> { TClauses, TS };
|
||||
_ ->
|
||||
% Create a new scope that contains a list of all variables
|
||||
% defined inside all the clauses. It returns this new scope and
|
||||
% a list of tuples where the first element is the variable name,
|
||||
% the second one is the new pointer to the variable and the third
|
||||
% is the old pointer.
|
||||
{ FinalVars, FS } = lists:mapfoldl(fun(X, Acc) -> normalize_vars(X, SharedVars, Acc) end, TS, AllVars),
|
||||
% Expand all clauses by adding a match operation at the end
|
||||
% that assigns variables missing in one clause to the others.
|
||||
expand_clauses(?line(Meta), TClauses, CV, FinalVars, [], FS).
|
||||
|
||||
% Defines a tuple that will be used as left side of the match operator
|
||||
Line = ?line(Meta),
|
||||
LeftVars = [{var, Line, NewValue} || { _, _, NewValue, _ } <- FinalVars],
|
||||
expand_clauses(Line, [Clause|T], [ClauseVars|V], FinalVars, Acc, S) ->
|
||||
case generate_match_vars(FinalVars, ClauseVars, [], []) of
|
||||
{ [], [] } ->
|
||||
expand_clauses(Line, T, V, FinalVars, [Clause|Acc], S);
|
||||
{ Left, Right } ->
|
||||
MatchExpr = generate_match(Line, Left, Right),
|
||||
ClauseExprs = element(5, Clause),
|
||||
[Final|RawClauseExprs] = lists:reverse(ClauseExprs),
|
||||
|
||||
% Expand all clauses by adding a match operation at the end
|
||||
% that assigns variables missing in one clause to the others.
|
||||
expand_clauses(Meta, TClauses, CV, LeftVars, FinalVars, [], FS)
|
||||
end.
|
||||
% If the last sentence has a match clause, we need to assign its value
|
||||
% in the variable list. If not, we insert the variable list before the
|
||||
% final clause in order to keep it tail call optimized.
|
||||
{ FinalClauseExprs, FS } = case has_match_tuple(Final) of
|
||||
true ->
|
||||
case Final of
|
||||
{ match, _, { var, _, UserVarName } = UserVar, _ } when UserVarName /= '_' ->
|
||||
{ [UserVar,MatchExpr,Final|RawClauseExprs], S };
|
||||
_ ->
|
||||
{ StorageVar, SS } = elixir_scope:build_erl_var(Line, S),
|
||||
StorageExpr = { match, Line, StorageVar, Final },
|
||||
{ [StorageVar,MatchExpr,StorageExpr|RawClauseExprs], SS }
|
||||
end;
|
||||
false ->
|
||||
{ [Final,MatchExpr|RawClauseExprs], S }
|
||||
end,
|
||||
|
||||
expand_clauses(Meta, [Clause|T], [ClauseVars|V], LeftVars, FinalVars, Acc, S) ->
|
||||
RightVars = [normalize_clause_var(Var, Kind, OldValue, ClauseVars) ||
|
||||
{ Var, Kind, _, OldValue } <- FinalVars],
|
||||
FinalClause = setelement(5, Clause, lists:reverse(FinalClauseExprs)),
|
||||
expand_clauses(Line, T, V, FinalVars, [FinalClause|Acc], FS)
|
||||
end;
|
||||
|
||||
AssignExpr = generate_match(Meta, LeftVars, RightVars),
|
||||
ClauseExprs = element(5, Clause),
|
||||
[Final|RawClauseExprs] = lists:reverse(ClauseExprs),
|
||||
|
||||
% If the last sentence has a match clause, we need to assign its value
|
||||
% in the variable list. If not, we insert the variable list before the
|
||||
% final clause in order to keep it tail call optimized.
|
||||
{ FinalClauseExprs, FS } = case has_match_tuple(Final) of
|
||||
true ->
|
||||
case Final of
|
||||
{ match, _, { var, _, UserVarName } = UserVar, _ } when UserVarName /= '_' ->
|
||||
{ [UserVar,AssignExpr,Final|RawClauseExprs], S };
|
||||
_ ->
|
||||
Line = ?line(Meta),
|
||||
{ StorageVar, SS } = elixir_scope:build_erl_var(Line, S),
|
||||
StorageExpr = { match, Line, StorageVar, Final },
|
||||
{ [StorageVar,AssignExpr,StorageExpr|RawClauseExprs], SS }
|
||||
end;
|
||||
false ->
|
||||
{ [Final,AssignExpr|RawClauseExprs], S }
|
||||
end,
|
||||
|
||||
FinalClause = setelement(5, Clause, lists:reverse(FinalClauseExprs)),
|
||||
expand_clauses(Meta, T, V, LeftVars, FinalVars, [FinalClause|Acc], FS);
|
||||
|
||||
expand_clauses(_Meta, [], [], _LeftVars, _FinalVars, Acc, S) ->
|
||||
expand_clauses(_Line, [], [], _FinalVars, Acc, S) ->
|
||||
{ lists:reverse(Acc), S }.
|
||||
|
||||
% Handle each key/value clause pair and translate them accordingly.
|
||||
@@ -181,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
|
||||
@@ -210,24 +205,14 @@ has_match_tuple(H) when is_list(H) ->
|
||||
|
||||
has_match_tuple(_) -> false.
|
||||
|
||||
% Normalize the given var checking its existence in the scope var dictionary.
|
||||
% Normalize the given var in between clauses
|
||||
% by picking one value as reference and retriving
|
||||
% its previous value.
|
||||
|
||||
normalize_vars({ Var, Kind } = Key, Shared, #elixir_scope{vars=Vars,clause_vars=ClauseVars} = S) ->
|
||||
{ NewValue, S1 } =
|
||||
case orddict:find(Key, Shared) of
|
||||
{ ok, SharedValue } ->
|
||||
{ SharedValue, S };
|
||||
error ->
|
||||
{ { _, _, ErlValue }, ErlS } = case (Kind /= nil) or (S#elixir_scope.noname) of
|
||||
true -> elixir_scope:build_erl_var(0, S);
|
||||
false -> elixir_scope:build_erl_var(0, Var, "_@" ++ atom_to_list(Var), S)
|
||||
end,
|
||||
{ ErlValue, ErlS }
|
||||
end,
|
||||
|
||||
S2 = S1#elixir_scope{
|
||||
vars=orddict:store(Key, NewValue, Vars),
|
||||
clause_vars=orddict:store(Key, NewValue, ClauseVars)
|
||||
normalize_vars(Key, Value, #elixir_scope{vars=Vars,clause_vars=ClauseVars} = S) ->
|
||||
FS = S#elixir_scope{
|
||||
vars=orddict:store(Key, Value, Vars),
|
||||
clause_vars=orddict:store(Key, Value, ClauseVars)
|
||||
},
|
||||
|
||||
Expr = case orddict:find(Key, Vars) of
|
||||
@@ -235,27 +220,29 @@ normalize_vars({ Var, Kind } = Key, Shared, #elixir_scope{vars=Vars,clause_vars=
|
||||
error -> { atom, 0, nil }
|
||||
end,
|
||||
|
||||
{ { Var, Kind, NewValue, Expr }, S2 }.
|
||||
{ { Key, Value, Expr }, FS }.
|
||||
|
||||
% Normalize a var by checking if it was defined in the clause.
|
||||
% If so, use it, otherwise use from main scope.
|
||||
% Generate match vars by checking if they were updated
|
||||
% or not and assigning the previous value.
|
||||
|
||||
normalize_clause_var(Var, Kind, OldValue, ClauseVars) ->
|
||||
case orddict:find({ Var, Kind }, ClauseVars) of
|
||||
{ ok, ClauseValue } -> { var, 0, ClauseValue };
|
||||
error -> OldValue
|
||||
end.
|
||||
generate_match_vars([{ Key, NewValue, OldValue }|T], ClauseVars, Left, Right) ->
|
||||
case orddict:find(Key, ClauseVars) of
|
||||
{ ok, NewValue } ->
|
||||
generate_match_vars(T, ClauseVars, Left, Right);
|
||||
{ ok, ClauseValue } ->
|
||||
generate_match_vars(T, ClauseVars, [{ var, 0, NewValue }|Left], [{ var, 0, ClauseValue }|Right]);
|
||||
error ->
|
||||
generate_match_vars(T, ClauseVars, [{ var, 0, NewValue }|Left], [OldValue|Right])
|
||||
end;
|
||||
|
||||
%% generate_match
|
||||
generate_match_vars([], _ClauseVars, Left, Right) ->
|
||||
{ Left, Right }.
|
||||
|
||||
generate_match(Meta, [Left], [Right]) ->
|
||||
{ match, ?line(Meta), Left, Right };
|
||||
generate_match(Line, [Left], [Right]) ->
|
||||
{ match, Line, Left, Right };
|
||||
|
||||
generate_match(Meta, LeftVars, RightVars) ->
|
||||
Line = ?line(Meta),
|
||||
generate_match(Line, LeftVars, RightVars) ->
|
||||
{ match, Line, { tuple, Line, LeftVars }, { tuple, Line, RightVars } }.
|
||||
|
||||
%% Listify
|
||||
|
||||
listify(Expr) when not is_list(Expr) -> [Expr];
|
||||
listify(Expr) -> Expr.
|
||||
@@ -30,13 +30,6 @@ start_link() ->
|
||||
init(_args) ->
|
||||
{ ok, #elixir_code_server{} }.
|
||||
|
||||
handle_call({ wait_until_finished, Pid }, _, Config) ->
|
||||
Waiting = Config#elixir_code_server.waiting,
|
||||
case is_list(Waiting) of
|
||||
true -> { reply, wait, Config#elixir_code_server{waiting=[Pid|Waiting]} };
|
||||
false -> { reply, ok, Config }
|
||||
end;
|
||||
|
||||
handle_call({ acquire, Path }, From, Config) ->
|
||||
Current = Config#elixir_code_server.loaded,
|
||||
case orddict:find(Path, Current) of
|
||||
@@ -87,11 +80,6 @@ handle_cast({ compiler_options, Options }, Config) ->
|
||||
Final = orddict:merge(fun(_,_,V) -> V end, Config#elixir_code_server.compiler_options, Options),
|
||||
{ noreply, Config#elixir_code_server{compiler_options=Final} };
|
||||
|
||||
handle_cast(finished, Config) ->
|
||||
Waiting = Config#elixir_code_server.waiting,
|
||||
[Pid ! { elixir_code_server, finished } || Pid <- lists:reverse(Waiting)],
|
||||
{ noreply, Config#elixir_code_server{waiting=done} };
|
||||
|
||||
handle_cast({ loaded, Path }, Config) ->
|
||||
Current = Config#elixir_code_server.loaded,
|
||||
case orddict:find(Path, Current) of
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
-module(elixir_compiler).
|
||||
-export([get_opts/0, get_opt/1, get_opt/2, string/2, file/1, file_to_path/2]).
|
||||
-export([core/0, module/3, eval_forms/4]).
|
||||
-export([core/0, module/3, eval_forms/4, format_error/1]).
|
||||
-include("elixir.hrl").
|
||||
-compile({parse_transform, elixir_transform}).
|
||||
|
||||
@@ -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
|
||||
@@ -89,11 +92,17 @@ module(Forms, S, Callback) ->
|
||||
end,
|
||||
module(Forms, S#elixir_scope.file, Options, false, Callback).
|
||||
|
||||
module(Forms, File, Options, Bootstrap, Callback) when
|
||||
is_binary(File), is_list(Forms), is_list(Options), is_boolean(Bootstrap), is_function(Callback) ->
|
||||
module(Forms, File, RawOptions, Bootstrap, Callback) when
|
||||
is_binary(File), is_list(Forms), is_list(RawOptions), is_boolean(Bootstrap), is_function(Callback) ->
|
||||
{ Options, SkipNative } = compile_opts(Forms, RawOptions),
|
||||
Listname = binary_to_list(File),
|
||||
case compile:forms([no_auto_import()|Forms], [return,{source,Listname}|Options]) of
|
||||
{ok, ModuleName, Binary, Warnings} ->
|
||||
|
||||
case compile:noenv_forms([no_auto_import()|Forms], [return,{source,Listname}|Options]) of
|
||||
{ok, ModuleName, Binary, RawWarnings} ->
|
||||
Warnings = case SkipNative of
|
||||
true -> [{?MODULE,[{0,?MODULE,{skip_native,ModuleName}}]}|RawWarnings];
|
||||
false -> RawWarnings
|
||||
end,
|
||||
format_warnings(Bootstrap, File, Warnings),
|
||||
code:load_binary(ModuleName, Listname, Binary),
|
||||
Callback(ModuleName, Binary);
|
||||
@@ -112,6 +121,38 @@ core() ->
|
||||
|
||||
%% HELPERS
|
||||
|
||||
compile_opts(Forms, Options) ->
|
||||
EnvOptions = env_default_opts(),
|
||||
SkipNative = lists:member(native, EnvOptions) and contains_on_load(Forms),
|
||||
case SkipNative or lists:member([{native,false}], Options) of
|
||||
true -> { Options ++ lists:delete(native, EnvOptions), SkipNative };
|
||||
false -> { Options ++ EnvOptions, SkipNative }
|
||||
end.
|
||||
|
||||
env_default_opts() ->
|
||||
Key = "ERL_COMPILER_OPTIONS",
|
||||
case os:getenv(Key) of
|
||||
false -> [];
|
||||
Str when is_list(Str) ->
|
||||
case erl_scan:string(Str) of
|
||||
{ok,Tokens,_} ->
|
||||
case erl_parse:parse_term(Tokens ++ [{dot, 1}]) of
|
||||
{ok,List} when is_list(List) -> List;
|
||||
{ok,Term} -> [Term];
|
||||
{error,_Reason} ->
|
||||
io:format("Ignoring bad term in ~ts\n", [Key]),
|
||||
[]
|
||||
end;
|
||||
{error, {_,_,_Reason}, _} ->
|
||||
io:format("Ignoring bad term in ~ts\n", [Key]),
|
||||
[]
|
||||
end
|
||||
end.
|
||||
|
||||
contains_on_load([{ attribute, _, on_load, _ }|_]) -> true;
|
||||
contains_on_load([_|T]) -> contains_on_load(T);
|
||||
contains_on_load([]) -> false.
|
||||
|
||||
no_auto_import() ->
|
||||
Bifs = [{ Name, Arity } || { Name, Arity } <- erlang:module_info(exports), erl_internal:bif(Name, Arity)],
|
||||
{ attribute, 0, compile, { no_auto_import, Bifs } }.
|
||||
@@ -157,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()]),
|
||||
@@ -188,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",
|
||||
@@ -196,8 +238,12 @@ core_main() ->
|
||||
|
||||
%% ERROR HANDLING
|
||||
|
||||
format_error({ skip_native, Module }) ->
|
||||
io_lib:format("skipping native compilation for ~ts because it contains on_load attribute",
|
||||
[elixir_errors:inspect(Module)]).
|
||||
|
||||
format_errors(_File, []) ->
|
||||
exit({nocompile, "compilation failed but no error was raised"});
|
||||
exit({ nocompile, "compilation failed but no error was raised" });
|
||||
|
||||
format_errors(File, Errors) ->
|
||||
lists:foreach(fun ({_, Each}) ->
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -110,8 +131,8 @@ run_on_definition_callbacks(Kind, Line, Module, Name, Args, Guards, Expr, S, CO)
|
||||
ok;
|
||||
_ ->
|
||||
Env = elixir_scope:to_ex_env({ Line, S }),
|
||||
Callbacks = 'Elixir.Module':get_attribute(Module, on_definition),
|
||||
[Mod:Fun(Env, Kind, Name, Args, Guards, Expr) || { Mod, Fun } <- Callbacks]
|
||||
elixir_module:eval_callbacks(Line, Module, on_definition,
|
||||
[Env, Kind, Name, Args, Guards, Expr], S)
|
||||
end.
|
||||
|
||||
%% Retrieve @file or fallback to default
|
||||
@@ -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,112 +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) 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}
|
||||
S#elixir_scope{
|
||||
check_clauses=false,
|
||||
macro_functions=[],
|
||||
macro_macros=[],
|
||||
macro_aliases=[]
|
||||
}
|
||||
),
|
||||
{ TR, TS#elixir_scope{check_clauses=S#elixir_scope.check_clauses} }.
|
||||
{ TR, TS#elixir_scope{
|
||||
check_clauses=S#elixir_scope.check_clauses,
|
||||
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
|
||||
|
||||
find_dispatch(Tuple, [{ Name, Values }|T]) ->
|
||||
case is_element(Tuple, Values) of
|
||||
true -> Name;
|
||||
false -> find_dispatch(Tuple, T)
|
||||
end;
|
||||
skip_require(Meta) ->
|
||||
lists:keyfind(require, 1, Meta) == { require, false }.
|
||||
|
||||
find_dispatch(_Tuple, []) -> false.
|
||||
find_dispatch(Meta, Tuple, S) ->
|
||||
find_dispatch(Meta, Tuple, [], S).
|
||||
|
||||
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];
|
||||
@@ -210,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
|
||||
|
||||
@@ -221,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,
|
||||
@@ -387,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").
|
||||
@@ -77,10 +77,10 @@ form_error(Meta, File, Module, Desc) ->
|
||||
|
||||
%% Shows a deprecation message
|
||||
|
||||
deprecation(Line, File, Message) -> deprecation(Line, File, Message, []).
|
||||
deprecation(Meta, File, Message) -> deprecation(Meta, File, Message, []).
|
||||
|
||||
deprecation(Line, File, Message, Args) ->
|
||||
io:format(file_format(Line, File, io_lib:format(Message, Args))).
|
||||
deprecation(Meta, File, Message, Args) ->
|
||||
io:format(file_format(?line(Meta), File, io_lib:format(Message, Args))).
|
||||
|
||||
%% Handle warnings and errors (called during module compilation)
|
||||
|
||||
@@ -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,10 +105,13 @@ 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;
|
||||
|
||||
handle_file_warning(_, _File, {Line,erl_lint,{unused_var,_Var}}) when Line =< 0 ->
|
||||
[];
|
||||
|
||||
handle_file_warning(_, File, {Line,erl_lint,{unused_var,Var}}) ->
|
||||
Message = format_error(erl_lint, { unused_var, format_var(Var) }),
|
||||
io:format(file_format(Line, File, Message));
|
||||
@@ -134,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
|
||||
@@ -161,11 +164,17 @@ assert_function_scope(_Meta, _Kind, #elixir_scope{function=Function}) -> Functio
|
||||
raise(Meta, File, Kind, Message) when is_list(Meta) ->
|
||||
raise(?line(Meta), File, Kind, Message);
|
||||
|
||||
raise(none, File, Kind, Message) ->
|
||||
raise(0, File, Kind, Message);
|
||||
|
||||
raise(Line, File, Kind, Message) when is_integer(Line) ->
|
||||
Stacktrace = erlang:get_stacktrace(),
|
||||
Exception = Kind:new([{description, Message}, {file, iolist_to_binary(File)}, {line, Line}]),
|
||||
erlang:raise(error, Exception, Stacktrace).
|
||||
|
||||
file_format(0, File, Message) ->
|
||||
io_lib:format("~ts: ~ts~n", [File, Message]);
|
||||
|
||||
file_format(Line, File, Message) ->
|
||||
io_lib:format("~ts:~w: ~ts~n", [File, Line, Message]).
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -196,11 +208,15 @@ translate({defmodule, Meta, [Ref, KV]}, S) ->
|
||||
|
||||
{ FRef, FS } = case TRef of
|
||||
{ atom, _, Module } ->
|
||||
FullModule = module_ref(Ref, Module, S#elixir_scope.module),
|
||||
FullModule = expand_module(Ref, Module, S),
|
||||
|
||||
RS = case elixir_aliases:nesting(S#elixir_scope.module, FullModule) of
|
||||
false -> S;
|
||||
Alias -> element(2, translate_each({ alias, Meta, [FullModule, [{ as, Alias }]] }, S))
|
||||
RS = case elixir_aliases:nesting_alias(S#elixir_scope.module, FullModule) of
|
||||
{ New, Old } ->
|
||||
S#elixir_scope{
|
||||
aliases=orddict:store(New, Old, S#elixir_scope.aliases),
|
||||
macro_aliases=orddict:store(New, Old, S#elixir_scope.macro_aliases)};
|
||||
false ->
|
||||
S
|
||||
end,
|
||||
|
||||
{
|
||||
@@ -211,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);
|
||||
@@ -219,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),
|
||||
@@ -327,14 +331,19 @@ rewrite_case_clauses([{do,[{in,_,[{'_',_,_},[false,nil]]}],False},{do,[{'_',_,_}
|
||||
rewrite_case_clauses(Clauses) ->
|
||||
Clauses.
|
||||
|
||||
module_ref(Raw, Module, Nesting) when is_atom(Raw); Nesting == nil ->
|
||||
expand_module(Raw, Module, #elixir_scope{module=Nesting}) when is_atom(Raw); Nesting == nil ->
|
||||
Module;
|
||||
|
||||
module_ref({ '__aliases__', _, ['Elixir'|_] }, Module, _Nesting) ->
|
||||
Module;
|
||||
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;
|
||||
Aliases when is_list(Aliases) ->
|
||||
elixir_aliases:concat([S#elixir_scope.module, Module])
|
||||
end;
|
||||
|
||||
module_ref(_Raw, Module, Nesting) ->
|
||||
elixir_aliases:concat([Nesting, Module]).
|
||||
expand_module(_Raw, Module, S) ->
|
||||
elixir_aliases:concat([S#elixir_scope.module, Module]).
|
||||
|
||||
is_reserved_data(moduledoc) -> true;
|
||||
is_reserved_data(doc) -> true;
|
||||
@@ -349,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.
|
||||
@@ -1,5 +1,6 @@
|
||||
-module(elixir_module).
|
||||
-export([translate/4, compile/5, data_table/1, eval_quoted/4, format_error/1]).
|
||||
-export([translate/4, compile/5, data_table/1, eval_quoted/4,
|
||||
format_error/1, eval_callbacks/5]).
|
||||
-include("elixir.hrl").
|
||||
-compile({parse_transform, elixir_transform}).
|
||||
|
||||
@@ -133,7 +134,8 @@ eval_form(Line, Module, Block, Vars, RawS) ->
|
||||
S = scope_for_eval(Module, RawS),
|
||||
{ Value, NewS } = elixir_compiler:eval_forms([Block], Line, Vars, S),
|
||||
elixir_def_overridable:store_pending(Module),
|
||||
eval_callbacks(Line, Module, before_compile, [Module], NewS),
|
||||
Env = elixir_scope:to_ex_env({ Line, S }),
|
||||
eval_callbacks(Line, Module, before_compile, [Env], NewS),
|
||||
elixir_def_overridable:store_pending(Module),
|
||||
Value.
|
||||
|
||||
@@ -141,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 } =
|
||||
@@ -154,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 };
|
||||
@@ -238,7 +240,8 @@ spec_for_macro(Else) -> Else.
|
||||
load_form(Line, Forms, S) ->
|
||||
elixir_compiler:module(Forms, S, fun(Module, Binary) ->
|
||||
EvalS = scope_for_eval(Module, S),
|
||||
eval_callbacks(Line, Module, after_compile, [Module, Binary], EvalS),
|
||||
Env = elixir_scope:to_ex_env({ Line, EvalS }),
|
||||
eval_callbacks(Line, Module, after_compile, [Env, Binary], EvalS),
|
||||
|
||||
case get(elixir_compiled) of
|
||||
Current when is_list(Current) ->
|
||||
@@ -324,17 +327,13 @@ eval_callbacks(Line, Module, Name, Args, RawS) ->
|
||||
S = RawS#elixir_scope{check_clauses=false},
|
||||
Binding = binding_for_eval(Module, []),
|
||||
Callbacks = lists:reverse(ets:lookup_element(data_table(Module), Name, 2)),
|
||||
Requires = S#elixir_scope.requires,
|
||||
Meta = [{line,Line}],
|
||||
Meta = [{line,Line},{require,false}],
|
||||
|
||||
lists:foreach(fun({M,F}) ->
|
||||
Expr = { { '.', Meta, [M,F] }, Meta, Args },
|
||||
Scope = case ordsets:is_element(M, Requires) of
|
||||
true -> S;
|
||||
false -> S#elixir_scope{requires=ordsets:add_element(M, Requires)}
|
||||
end,
|
||||
|
||||
{ Tree, _ } = elixir_translator:translate_each(Expr, Scope),
|
||||
{ Tree, _ } = elixir_dispatch:dispatch_require(Meta, M, F, Args, S, fun() ->
|
||||
apply(M, F, Args),
|
||||
{ { nil, 0 }, S }
|
||||
end),
|
||||
|
||||
try
|
||||
erl_eval:exprs([Tree], Binding)
|
||||
@@ -348,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)]).
|
||||
|
||||
@@ -553,10 +553,10 @@ build_sigil({ sigil, Line, Sigil, Parts, Modifiers }) ->
|
||||
build_bin_string({ bin_string, _Line, [H] }) when is_binary(H) -> H;
|
||||
build_bin_string({ bin_string, Line, Args }) -> { '<<>>', [{line,Line}], Args }.
|
||||
|
||||
build_list_string({ list_string, _Line, [H] }) when is_binary(H) -> binary_to_list(H);
|
||||
build_list_string({ list_string, _Line, [H] }) when is_binary(H) -> unicode:characters_to_list(H);
|
||||
build_list_string({ list_string, Line, Args }) ->
|
||||
Meta = [{line,Line}],
|
||||
{ { '.', Meta, [erlang, binary_to_list] }, Meta, [{ '<<>>', Meta, Args}] }.
|
||||
{ { '.', Meta, [unicode, characters_to_list] }, Meta, [{ '<<>>', Meta, Args}] }.
|
||||
|
||||
build_atom({ atom, _Line, Atom }) when is_atom(Atom) -> Atom;
|
||||
build_atom({ atom, Line, Args }) ->
|
||||
|
||||
@@ -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,56 +34,83 @@ 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{expand_aliases=false}, S);
|
||||
do_quote(Expr, Q#elixir_quote{aliases_hygiene=false}, S);
|
||||
|
||||
do_quote({ '__aliases__', Meta, [H|T] } = Alias, #elixir_quote{aliases_hygiene=true} = Q, S) when is_atom(H) and (H /= 'Elixir') ->
|
||||
Annotation = case elixir_aliases:expand(Alias, S#elixir_scope.aliases, S#elixir_scope.macro_aliases) of
|
||||
Atom when is_atom(Atom) -> Atom;
|
||||
Aliases when is_list(Aliases) -> false
|
||||
end,
|
||||
|
||||
do_quote({ '__aliases__', Meta, [H|_] = Aliases }, #elixir_quote{expand_aliases=true} = Q, S) when is_atom(H) and H /= 'Elixir' ->
|
||||
Line = ?line(Meta),
|
||||
{ TAliases, SA } = do_quote(['Elixir'|Aliases], Q, S),
|
||||
{ TAliases, SA } = do_quote([H|T], Q, S),
|
||||
|
||||
{ { tuple, Line, [
|
||||
{ atom, Line, '__aliases__' },
|
||||
meta(Meta, Q),
|
||||
meta([{alias,Annotation}|Meta], Q),
|
||||
TAliases
|
||||
] }, SA };
|
||||
|
||||
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),
|
||||
Tuple = { tuple, Line, [
|
||||
{ atom, Line, Left },
|
||||
meta(Meta, Q),
|
||||
{ atom, Line, Q#elixir_quote.var_context }
|
||||
{ atom, Line, Q#elixir_quote.vars_hygiene }
|
||||
] },
|
||||
{ Tuple, S };
|
||||
|
||||
do_quote({ Name, Meta, ArgsOrAtom } = Tuple, #elixir_quote{expand_imports=true} = Q, S) when is_atom(Name) ->
|
||||
do_quote({ Name, Meta, ArgsOrAtom } = Tuple, #elixir_quote{imports_hygiene=true} = Q, S) when is_atom(Name) ->
|
||||
Arity = case is_atom(ArgsOrAtom) of
|
||||
true -> 0;
|
||||
false -> length(ArgsOrAtom)
|
||||
@@ -134,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) ->
|
||||
@@ -154,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) ->
|
||||
|
||||
@@ -3,13 +3,11 @@
|
||||
-module(elixir_scope).
|
||||
-export([translate_var/5,
|
||||
build_erl_var/2, build_ex_var/2,
|
||||
build_erl_var/3, build_ex_var/3,
|
||||
build_erl_var/4, build_ex_var/4,
|
||||
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}).
|
||||
|
||||
@@ -22,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 } } ->
|
||||
@@ -30,8 +28,8 @@ translate_var(Meta, Name, Kind, S, Callback) ->
|
||||
{ Else, _ } ->
|
||||
{ NewVar, NS } = if
|
||||
Kind /= nil -> build_erl_var(Line, S);
|
||||
Else -> build_erl_var(Line, Name, S);
|
||||
S#elixir_scope.noname -> build_erl_var(Line, Name, S);
|
||||
Else -> build_erl_var(Line, Name, Name, S);
|
||||
S#elixir_scope.noname -> build_erl_var(Line, Name, Name, S);
|
||||
true -> { { var, Line, Name }, S }
|
||||
end,
|
||||
RealName = element(3, NewVar),
|
||||
@@ -58,9 +56,6 @@ translate_var(Meta, Name, Kind, S, Callback) ->
|
||||
build_ex_var(Line, S) -> build_ex_var(Line, '', "_", S).
|
||||
build_erl_var(Line, S) -> build_erl_var(Line, '', "_", S).
|
||||
|
||||
build_ex_var(Line, Key, S) -> build_ex_var(Line, Key, Key, S).
|
||||
build_erl_var(Line, Key, S) -> build_erl_var(Line, Key, Key, S).
|
||||
|
||||
build_var_counter(Key, #elixir_scope{counter=Counter} = S) ->
|
||||
New = orddict:update_counter(Key, 1, Counter),
|
||||
{ orddict:fetch(Key, New), S#elixir_scope{counter=New} }.
|
||||
@@ -82,26 +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.scheduled }
|
||||
S#elixir_scope.requires, S#elixir_scope.macros, S#elixir_scope.aliases,
|
||||
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) ->
|
||||
@@ -119,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, Scheduled }, Vars) ->
|
||||
deserialize_with_vars({ File, Functions, CheckClauses, Requires, Macros,
|
||||
Aliases, MacroFunctions, MacroMacros, MacroAliases, Scheduled }, Vars) ->
|
||||
#elixir_scope{
|
||||
file=File,
|
||||
functions=Functions,
|
||||
@@ -127,6 +121,9 @@ 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),
|
||||
counter=[{'',length(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),
|
||||
@@ -107,7 +110,8 @@ translate_each({ alias, Meta, [Ref, KV] }, S) ->
|
||||
{ as, false } ->
|
||||
{ Old, SR };
|
||||
{ as, Other } ->
|
||||
{ TOther, SA } = translate_each(Other, SR#elixir_scope{aliases=[]}),
|
||||
{ TOther, SA } = translate_each(no_alias_expansion(Other),
|
||||
SR#elixir_scope{aliases=[],macro_aliases=[]}),
|
||||
case TOther of
|
||||
{ atom, _, Atom } -> { Atom, SA };
|
||||
_ -> syntax_error(Meta, S#elixir_scope.file,
|
||||
@@ -127,7 +131,8 @@ translate_each({ alias, Meta, [Ref, KV] }, S) ->
|
||||
end,
|
||||
|
||||
{ { nil, ?line(Meta) }, SF#elixir_scope{
|
||||
aliases=orddict:store(New, Old, S#elixir_scope.aliases)
|
||||
aliases=orddict:store(New, Old, S#elixir_scope.aliases),
|
||||
macro_aliases=orddict:store(New, Old, S#elixir_scope.macro_aliases)
|
||||
} }
|
||||
end;
|
||||
_ ->
|
||||
@@ -138,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),
|
||||
@@ -171,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),
|
||||
|
||||
@@ -205,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} };
|
||||
@@ -213,8 +219,8 @@ translate_each({ '__CALLER__', Meta, Atom }, S) when is_atom(Atom) ->
|
||||
%% Aliases
|
||||
|
||||
translate_each({ '__aliases__', Meta, _ } = Alias, S) ->
|
||||
case elixir_aliases:expand(Alias, S#elixir_scope.aliases) of
|
||||
Atom when is_atom(Atom) -> { { atom, ?line(Meta), Atom}, S };
|
||||
case elixir_aliases:expand(Alias, S#elixir_scope.aliases, S#elixir_scope.macro_aliases) of
|
||||
Atom when is_atom(Atom) -> { { atom, ?line(Meta), Atom }, S };
|
||||
Aliases ->
|
||||
{ TAliases, SA } = translate_args(Aliases, S),
|
||||
|
||||
@@ -230,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) ->
|
||||
@@ -244,13 +250,25 @@ translate_each({ quote, Meta, [T] }, S) when is_list(T) ->
|
||||
syntax_error(Meta, S#elixir_scope.file, "invalid args for quote")
|
||||
end,
|
||||
|
||||
Context = case lists:keyfind(var_context, 1, T) of
|
||||
{ var_context, VarContext } ->
|
||||
expand_var_context(Meta, VarContext,
|
||||
"invalid argument given for var_context in quote", S);
|
||||
Hygiene = case lists:keyfind(hygiene, 1, T) of
|
||||
{ hygiene, true } ->
|
||||
elixir_errors:deprecation(Meta, S#elixir_scope.file, "hygiene: true is deprecated, please set it to [] instead", []),
|
||||
[];
|
||||
{ hygiene, List } when is_list(List) ->
|
||||
List;
|
||||
{ hygiene, false } ->
|
||||
elixir_errors:deprecation(Meta, S#elixir_scope.file, "hygiene: false is deprecated, please set it to [vars: false] instead", []),
|
||||
[{vars,false}, {imports,false}, {aliases,false}];
|
||||
false ->
|
||||
case lists:keyfind(hygiene, 1, T) of
|
||||
{ hygiene, false } -> nil;
|
||||
[]
|
||||
end,
|
||||
|
||||
Vars = case lists:keyfind(var_context, 1, T) of
|
||||
{ var_context, VarContext } ->
|
||||
expand_var_context(Meta, VarContext, "invalid argument given for var_context in quote", S);
|
||||
false ->
|
||||
case lists:keyfind(vars, 1, Hygiene) of
|
||||
{ vars, false } -> nil;
|
||||
_ ->
|
||||
case S#elixir_scope.module of
|
||||
nil -> 'Elixir';
|
||||
@@ -259,20 +277,18 @@ translate_each({ quote, Meta, [T] }, S) when is_list(T) ->
|
||||
end
|
||||
end,
|
||||
|
||||
GetExpansion = fun(Key) ->
|
||||
GetExpansion = fun(Key, New) ->
|
||||
case lists:keyfind(Key, 1, T) of
|
||||
{ Key, Bool } when is_boolean(Bool) ->
|
||||
elixir_errors:deprecation(Meta, S#elixir_scope.file, "the ~s option for quote is deprecated, please use hygiene: [~s: ~s] instead", [Key, New, Bool]),
|
||||
Bool;
|
||||
false ->
|
||||
case lists:keyfind(hygiene, 1, T) of
|
||||
{ hygiene, Bool } when is_boolean(Bool) -> Bool;
|
||||
false -> true
|
||||
end
|
||||
not(lists:keyfind(New, 1, Hygiene) == { New, false })
|
||||
end
|
||||
end,
|
||||
|
||||
Aliases = GetExpansion(expand_aliases),
|
||||
Imports = GetExpansion(expand_imports),
|
||||
Aliases = GetExpansion(aliases_hygiene, aliases),
|
||||
Imports = GetExpansion(imports_hygiene, imports),
|
||||
|
||||
{ DefaultLine, DefaultFile } = case lists:keyfind(location, 1, T) of
|
||||
{ location, keep } -> { keep, keep };
|
||||
@@ -314,8 +330,8 @@ translate_each({ quote, Meta, [T] }, S) when is_list(T) ->
|
||||
_ -> true
|
||||
end,
|
||||
|
||||
Q = #elixir_quote{var_context=Context, line=TLine, unquote=Unquote,
|
||||
expand_aliases=Aliases, expand_imports=Imports},
|
||||
Q = #elixir_quote{vars_hygiene=Vars, line=TLine, unquote=Unquote,
|
||||
aliases_hygiene=Aliases, imports_hygiene=Imports},
|
||||
|
||||
elixir_quote:quote(TExprs, Q, SL);
|
||||
|
||||
@@ -343,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
|
||||
@@ -356,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),
|
||||
@@ -385,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() ->
|
||||
@@ -413,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
|
||||
@@ -472,6 +482,13 @@ translate_each({ { '.', _, [Expr] }, Meta, Args } = Original, S) ->
|
||||
{ TExpr, SE } = translate_each(Expr, S),
|
||||
case TExpr of
|
||||
{ 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
|
||||
@@ -527,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) ->
|
||||
@@ -544,6 +555,12 @@ expand_var_context(Meta, Alias, Msg, S) ->
|
||||
syntax_error(Meta, S#elixir_scope.file, "~ts, expected a compile time available alias or an atom", [Msg])
|
||||
end.
|
||||
|
||||
no_alias_expansion({ '__aliases__', Meta, [H|T] }) when (H /= 'Elixir') and is_atom(H) ->
|
||||
{ '__aliases__', Meta, ['Elixir',H|T] };
|
||||
|
||||
no_alias_expansion(Other) ->
|
||||
Other.
|
||||
|
||||
%% Translate args
|
||||
|
||||
%% Variables in arguments are not propagated from one
|
||||
@@ -563,9 +580,9 @@ expand_var_context(Meta, Alias, Msg, S) ->
|
||||
|
||||
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) ->
|
||||
@@ -641,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
|
||||
@@ -173,7 +175,7 @@ defmodule Binary.Inspect.ListTest do
|
||||
end
|
||||
|
||||
test :codepoints do
|
||||
assert inspect('é') == "[195,169]"
|
||||
assert inspect('é') == "'é'"
|
||||
end
|
||||
|
||||
test :empty do
|
||||
|
||||
@@ -18,7 +18,7 @@ bar '''
|
||||
end
|
||||
|
||||
test :utf8 do
|
||||
assert length(' ゆんゆん') == 13
|
||||
assert length(' ゆんゆん') == 5
|
||||
end
|
||||
|
||||
test :octals do
|
||||
@@ -37,10 +37,9 @@ bar '''
|
||||
assert '\xfF' == 'ÿ'
|
||||
assert '\x{A}' == '\n'
|
||||
assert '\x{e9}' == 'é'
|
||||
assert '\x{10F}' == [196,143]
|
||||
assert '\x{10FF}' == [225,131,191]
|
||||
assert '\x{10FFF}' == [240,144,191,191]
|
||||
assert '\x{10FFFF}' == [244,143,191,191]
|
||||
assert '\x{10F}' == [271]
|
||||
assert '\x{10FF}' == [4351]
|
||||
assert '\x{10FFF}' == [69631]
|
||||
assert '\x{10FFFF}' == [1114111]
|
||||
end
|
||||
|
||||
end
|
||||
@@ -61,8 +61,8 @@ defmodule CodeTest do
|
||||
end
|
||||
|
||||
test :string_to_ast do
|
||||
assert Code.string_to_ast("1 + 2") == { :ok, quote hygiene: false, line: 1, do: 1 + 2 }
|
||||
assert Code.string_to_ast("1 + 2; 3 + 4") == { :ok, quote hygiene: false, line: 1, do: (1 + 2; 3 + 4) }
|
||||
assert Code.string_to_ast("1 + 2") == { :ok, quote hygiene: [imports: false], line: 1, do: 1 + 2 }
|
||||
assert Code.string_to_ast("1 + 2; 3 + 4") == { :ok, quote hygiene: [imports: false], line: 1, do: (1 + 2; 3 + 4) }
|
||||
assert { :error, _ } = Code.string_to_ast("a.1")
|
||||
end
|
||||
|
||||
@@ -72,7 +72,7 @@ defmodule CodeTest do
|
||||
end
|
||||
|
||||
test :string_to_ast! do
|
||||
assert Code.string_to_ast!("1 + 2") == quote hygiene: false, line: 1, do: 1 + 2
|
||||
assert Code.string_to_ast!("1 + 2") == quote hygiene: [imports: false], line: 1, do: 1 + 2
|
||||
|
||||
assert_raise SyntaxError, fn ->
|
||||
Code.string_to_ast!("a.1")
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -10,14 +10,16 @@ defmodule FileTest do
|
||||
|
||||
# cp_r/c is managed in setup/teardown because if it is stored in
|
||||
# the repository, reltool can't build a release
|
||||
def setup do
|
||||
setup do
|
||||
src = fixture_path("cp_r")
|
||||
:file.make_symlink 'certainly/invalid', Path.join([src, "c"])
|
||||
:ok
|
||||
end
|
||||
|
||||
def teardown do
|
||||
teardown do
|
||||
src = fixture_path("cp_r")
|
||||
File.rm Path.join([src, "c"])
|
||||
:ok
|
||||
end
|
||||
|
||||
test :cp_with_src_file_and_dest_file do
|
||||
@@ -395,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
|
||||
|
||||
@@ -677,14 +697,16 @@ defmodule FileTest do
|
||||
|
||||
# cp_r/c is managed in setup/teardown because if it is stored in
|
||||
# the repository, reltool can't build a release
|
||||
def setup do
|
||||
setup do
|
||||
src = fixture_path("cp_r")
|
||||
:file.make_symlink 'certainly/invalid', Path.join([src, "c"])
|
||||
:ok
|
||||
end
|
||||
|
||||
def teardown do
|
||||
teardown do
|
||||
src = fixture_path("cp_r")
|
||||
File.rm Path.join([src, "c"])
|
||||
:ok
|
||||
end
|
||||
|
||||
test :rm_file do
|
||||
|
||||
@@ -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
|
||||
@@ -0,0 +1,44 @@
|
||||
Code.require_file "../../test_helper.exs", __FILE__
|
||||
|
||||
defmodule IO.ANSITest do
|
||||
use ExUnit.Case, async: true
|
||||
|
||||
test :escape_single do
|
||||
assert IO.ANSI.escape("Hello, %{red}world!", true) ==
|
||||
"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
|
||||
assert IO.ANSI.escape("Hello, %{red,bright}world!", true) ==
|
||||
"Hello, #{IO.ANSI.red}#{IO.ANSI.bright}world!#{IO.ANSI.reset}"
|
||||
assert IO.ANSI.escape("Hello, %{red, bright}world!", true) ==
|
||||
"Hello, #{IO.ANSI.red}#{IO.ANSI.bright}world!#{IO.ANSI.reset}"
|
||||
assert IO.ANSI.escape("Hello, %{red , bright}world!", true) ==
|
||||
"Hello, #{IO.ANSI.red}#{IO.ANSI.bright}world!#{IO.ANSI.reset}"
|
||||
end
|
||||
|
||||
test :no_emit do
|
||||
assert IO.ANSI.escape("Hello, %{red,bright}world!", false) ==
|
||||
"Hello, world!"
|
||||
end
|
||||
|
||||
test :fragment do
|
||||
assert IO.ANSI.escape("%{red}", true) == "#{IO.ANSI.red}#{IO.ANSI.reset}"
|
||||
assert IO.ANSI.escape_fragment("", true) == ""
|
||||
end
|
||||
|
||||
test :noop do
|
||||
assert IO.ANSI.escape("") == ""
|
||||
end
|
||||
|
||||
test :invalid do
|
||||
assert_raise ArgumentError, "invalid ANSI sequence specification: brigh", fn ->
|
||||
IO.ANSI.escape("%{brigh}, yes")
|
||||
end
|
||||
assert_raise ArgumentError, "invalid ANSI sequence specification: brigh", fn ->
|
||||
IO.ANSI.escape("%{brigh,red}, yes")
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -26,4 +26,60 @@ defmodule Kernel.AliasTest do
|
||||
alias List, as: Nested
|
||||
assert Nested.flatten([[13]]) == [13]
|
||||
end
|
||||
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
|
||||
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
|
||||
|
||||
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
|
||||
use ExUnit.Case, async: true
|
||||
|
||||
require Kernel.AliasNestingGenerator
|
||||
Kernel.AliasNestingGenerator.create
|
||||
|
||||
test :aliases_nesting do
|
||||
assert Parent.a == :a
|
||||
assert Parent.Child.b == :a
|
||||
end
|
||||
end
|
||||
|
||||
defmodule Kernel.AliasMacroNestingTest do
|
||||
use ExUnit.Case, async: true
|
||||
|
||||
require Kernel.AliasNestingGenerator
|
||||
Kernel.AliasNestingGenerator.record
|
||||
|
||||
test :aliases_nesting do
|
||||
assert is_record(Parent.new, Parent)
|
||||
assert Parent.Child.b.message == "ok"
|
||||
end
|
||||
end
|
||||
|
||||
@@ -18,6 +18,18 @@ defmodule Kernel.CaseTest do
|
||||
assert vars_case(-1, -1) == { 0, 1 }
|
||||
end
|
||||
|
||||
test :nested_vars_match do
|
||||
x = { :error, { :ok, :done } }
|
||||
assert (case x do
|
||||
{ :ok, right } ->
|
||||
right
|
||||
{ _left, right } ->
|
||||
case right do
|
||||
{ :ok, right } -> right
|
||||
end
|
||||
end) == :done
|
||||
end
|
||||
|
||||
test :match_with_in do
|
||||
assert(case 3 do
|
||||
x in [1,2,3] ->
|
||||
|
||||
@@ -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
|
||||
@@ -270,7 +275,7 @@ defmodule Kernel.ErrorsTest do
|
||||
end
|
||||
|
||||
test :macros_compiled_callback do
|
||||
assert [{Kernel.ErrorsTest,:__before_compile__,[Foo],_}|_] =
|
||||
assert [{Kernel.ErrorsTest,:__before_compile__,[Macro.Env[module: Foo]],_}|_] =
|
||||
rescue_stacktrace("defmodule Foo do\nModule.put_attribute(__MODULE__, :before_compile, Kernel.ErrorsTest)\nend")
|
||||
end
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -15,12 +15,6 @@ defmodule Kernel.PartialApplicationTest do
|
||||
assert fun.(13, 6) == 7
|
||||
end
|
||||
|
||||
test :partial_with_atom_call_and_one_item do
|
||||
fun = :minus.(10, &1)
|
||||
assert fun.(5) == 5
|
||||
assert fun.(3) == 7
|
||||
end
|
||||
|
||||
test :partial_with_funcall_and_one_item do
|
||||
fun = minus(&1, &2)
|
||||
fun = fun.(10, &1)
|
||||
|
||||
@@ -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)
|
||||
@@ -90,7 +120,7 @@ defmodule Kernel.QuoteTest.VarHygiene do
|
||||
end
|
||||
|
||||
defmacro no_hygiene do
|
||||
quote [hygiene: false] do
|
||||
quote [hygiene: [vars: false]] do
|
||||
a = 1
|
||||
end
|
||||
end
|
||||
@@ -157,10 +187,43 @@ end
|
||||
defmodule Kernel.QuoteTest.AliasHygieneTest do
|
||||
use ExUnit.Case, async: true
|
||||
|
||||
alias Dict, as: SuperDict
|
||||
|
||||
defmacro dict do
|
||||
quote do: Dict.Bar
|
||||
end
|
||||
|
||||
defmacro super_dict do
|
||||
quote do: SuperDict.Bar
|
||||
end
|
||||
|
||||
test :annotate_aliases do
|
||||
assert quote(do: Foo.Bar) == { :__aliases__, [alias: false], [:Foo, :Bar] }
|
||||
assert quote(do: Dict.Bar) == { :__aliases__, [alias: false], [:Dict, :Bar] }
|
||||
assert quote(do: SuperDict.Bar) == { :__aliases__, [alias: Dict.Bar], [:SuperDict, :Bar] }
|
||||
assert quote(do: alias!(SuperDict.Bar)) == { :__aliases__, [], [:SuperDict, :Bar] }
|
||||
end
|
||||
|
||||
test :expand_aliases do
|
||||
assert Code.eval_quoted(quote do: Foo.Bar) == { Elixir.Foo.Bar, [] }
|
||||
assert Code.eval_quoted(quote do: alias!(Foo.Bar)) == { Foo.Bar, [] }
|
||||
assert Code.eval_quoted(quote expand_aliases: false, do: Foo.Bar) == { Foo.Bar, [] }
|
||||
assert Code.eval_quoted(quote do: SuperDict.Bar) == { Elixir.Dict.Bar, [] }
|
||||
assert Code.eval_quoted(quote do: alias!(SuperDict.Bar)) == { Elixir.SuperDict.Bar, [] }
|
||||
|
||||
assert Code.eval_quoted(quote do
|
||||
alias HashDict, as: SuperDict
|
||||
SuperDict.Bar
|
||||
end) == { Elixir.HashDict.Bar, [] }
|
||||
|
||||
assert Code.eval_quoted(quote do
|
||||
alias HashDict, as: Dict
|
||||
require Kernel.QuoteTest.AliasHygieneTest
|
||||
Kernel.QuoteTest.AliasHygieneTest.dict
|
||||
end) == { Elixir.Dict.Bar, [] }
|
||||
|
||||
assert Code.eval_quoted(quote do
|
||||
alias HashDict, as: SuperDict
|
||||
require Kernel.QuoteTest.AliasHygieneTest
|
||||
Kernel.QuoteTest.AliasHygieneTest.super_dict
|
||||
end) == { Elixir.Dict.Bar, [] }
|
||||
end
|
||||
end
|
||||
|
||||
@@ -173,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
|
||||
@@ -191,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
|
||||
|
||||
@@ -58,7 +58,7 @@ defmodule KernelTest do
|
||||
end
|
||||
|
||||
test :function_from___ENV__ do
|
||||
assert __ENV__.function == { :test_function_from___ENV__, 0 }
|
||||
assert __ENV__.function == { :test_function_from___ENV__, 1 }
|
||||
end
|
||||
|
||||
defp x(value) when value in [1,2,3], do: true
|
||||
@@ -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
|
||||
@@ -347,7 +353,7 @@ defmodule KernelTest do
|
||||
test :invalid_match do
|
||||
a = 3
|
||||
assert_raise CaseClauseError, fn ->
|
||||
destructure [^a,b,c], a_list
|
||||
destructure [^a,_b,_c], a_list
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -3,13 +3,9 @@ Code.require_file "../test_helper.exs", __FILE__
|
||||
defmodule ListTest do
|
||||
use ExUnit.Case, async: true
|
||||
|
||||
test :brackets_function do
|
||||
assert :[].(1,2,3) == [1,2,3]
|
||||
end
|
||||
|
||||
test :optional_comma do
|
||||
assert :[].(1,) == [ 1, ]
|
||||
assert :[].(1,2,3,) == [1,2,3,]
|
||||
assert [1] == [ 1, ]
|
||||
assert [1,2,3] == [1,2,3,]
|
||||
end
|
||||
|
||||
test :partial_application 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
|
||||
@@ -82,6 +135,14 @@ defmodule MacroTest do
|
||||
assert Macro.expand(quote(do: :foo), __ENV__) == :foo
|
||||
end
|
||||
|
||||
defmacro local_macro do
|
||||
:local_macro
|
||||
end
|
||||
|
||||
test :expand_local_macro do
|
||||
assert Macro.expand(quote(do: local_macro), __ENV__) == :local_macro
|
||||
end
|
||||
|
||||
test :expand_with_imported_macro do
|
||||
assert Macro.expand(quote(do: 1 || false), __ENV__) == (quote var_context: Kernel do
|
||||
case 1 do
|
||||
@@ -288,7 +349,7 @@ defmodule MacroTest do
|
||||
|
||||
test :env_stacktrace do
|
||||
env = __ENV__.file("foo").line(12)
|
||||
assert env.stacktrace == [{ __MODULE__, :test_env_stacktrace, 0, [file: "foo", line: 12] }]
|
||||
assert env.stacktrace == [{ __MODULE__, :test_env_stacktrace, 1, [file: "foo", line: 12] }]
|
||||
env = env.function(nil)
|
||||
assert env.stacktrace == [{ __MODULE__, :__MODULE__, 0, [file: "foo", line: 12] }]
|
||||
env = env.module(nil)
|
||||
|
||||
@@ -16,12 +16,12 @@ defmodule ModuleTest.ToBeUsed do
|
||||
quote do: (def before_compile, do: true)
|
||||
end
|
||||
|
||||
defmacro __after_compile__(ModuleTest.ToUse, bin) when is_binary(bin) do
|
||||
defmacro __after_compile__(Macro.Env[module: ModuleTest.ToUse], bin) when is_binary(bin) do
|
||||
# IO.puts "HELLO"
|
||||
end
|
||||
|
||||
defmacro callback(target) do
|
||||
value = Module.get_attribute(target, :has_callback)
|
||||
defmacro callback(env) do
|
||||
value = Module.get_attribute(env.module, :has_callback)
|
||||
quote do
|
||||
name = :original_value
|
||||
args = [1]
|
||||
@@ -57,7 +57,7 @@ defmodule ModuleTest do
|
||||
Module.eval_quoted __MODULE__, contents, [], file: "sample.ex", line: 13
|
||||
|
||||
defmacrop in_module(block) do
|
||||
quote expand_aliases: false do
|
||||
quote do
|
||||
defmodule Temp, unquote(block)
|
||||
:code.purge(Temp)
|
||||
:code.delete(Temp)
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -71,11 +71,18 @@ defmodule OptionParserTest do
|
||||
end
|
||||
|
||||
test "parses more than one key/value options" do
|
||||
assert OptionParser.parse(["--source", "from_docs/", "--docs", "false"]) == { [source: "from_docs/", docs: false], [] }
|
||||
assert OptionParser.parse(["--source", "from_docs/", "--docs", "false"]) ==
|
||||
{ [source: "from_docs/", docs: false], [] }
|
||||
end
|
||||
|
||||
test "accumulates options accumulators" do
|
||||
assert OptionParser.parse(["--require", "foo", "--require", "bar", "baz"]) == { [require: "foo", require: "bar"], ["baz"] }
|
||||
test "overrides options by default" do
|
||||
assert OptionParser.parse(["--require", "foo", "--require", "bar", "baz"]) ==
|
||||
{ [require: "bar"], ["baz"] }
|
||||
end
|
||||
|
||||
test "keeps options on keep" do
|
||||
assert OptionParser.parse(["--require", "foo", "--require", "bar", "baz"], switches: [require: :keep]) ==
|
||||
{ [require: "foo", require: "bar"], ["baz"] }
|
||||
end
|
||||
|
||||
test "parses mixed options" do
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -116,7 +116,7 @@ defmodule ProtocolTest do
|
||||
assert_protocol_for(ProtocolTest.WithAll, Tuple, {Bar,2,3})
|
||||
assert_protocol_for(ProtocolTest.WithAll, BitString, "foo")
|
||||
assert_protocol_for(ProtocolTest.WithAll, BitString, <<1>>)
|
||||
assert_protocol_for(ProtocolTest.WithAll, PID, Process.self)
|
||||
assert_protocol_for(ProtocolTest.WithAll, PID, Kernel.self)
|
||||
assert_protocol_for(ProtocolTest.WithAll, Port, hd(:erlang.ports))
|
||||
assert_protocol_for(ProtocolTest.WithAll, Reference, make_ref)
|
||||
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
|
||||
|
||||
@@ -127,19 +127,6 @@ end
|
||||
defmodule Regex.ListTest do
|
||||
use ExUnit.Case, async: true
|
||||
|
||||
test :compile do
|
||||
assert is_record(Regex.compile!('foo'), Regex)
|
||||
assert is_regex(Regex.compile!('foo'))
|
||||
end
|
||||
|
||||
test :source do
|
||||
assert Regex.source(Regex.compile!('foo')) == "foo"
|
||||
end
|
||||
|
||||
test :opts do
|
||||
assert Regex.opts(Regex.compile!('foo', 'u')) == "u"
|
||||
end
|
||||
|
||||
test :match? do
|
||||
assert Regex.match?(%r(foo), 'foo')
|
||||
refute Regex.match?(%r(foo), 'FOO')
|
||||
|
||||
@@ -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,12 +8,20 @@ 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
|
||||
assert :{}.(1,) == { 1, }
|
||||
assert :{}.(1, 2, 3) == { 1, 2, 3, }
|
||||
assert { 1 } == { 1, }
|
||||
assert { 1, 2, 3 } == { 1, 2, 3, }
|
||||
end
|
||||
|
||||
test :partial_application do
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user